next-intl provides type-safe internationalization for Next.js App Router apps. Neexo projects typically use Danish as the primary language with English and optionally German as secondaries.
Setup
Translation files live in messages/ as JSON:
messages/
da.json # Danish (primary source)
en.json # English
de.json # German (optional)
Translation Pattern
"use client";
import { useTranslations } from "next-intl";
export default function HeroSection() {
const t = useTranslations("hero");
return (
<section>
<h1>{t("title")}</h1>
<p>{t("description")}</p>
</section>
);
}
Rules
Danish is the source language — always write Danish text first, then translate to other languages
Use useTranslations() hook in client components — never inline text strings
Namespace translations by feature/page: hero.title, contact.submit, nav.home
Keep translation keys in kebab-case or camelCase — be consistent within the project
Do not use useTranslations() in Server Components — use getTranslations() instead
Keep defaultLocale set to "da" in the i18n config
Ensure all three language files have the same keys — missing keys cause runtime fallback warnings
Do not create inline translation objects — always use the message files
Raw content
Copy this into your project — e.g. .instructions.md, .agent.md, or SKILL.md
## Overview
next-intl provides type-safe internationalization for Next.js App Router apps. Neexo projects typically use Danish as the primary language with English and optionally German as secondaries.
## Setup
Translation files live in `messages/` as JSON:
```
messages/
da.json # Danish (primary source)
en.json # English
de.json # German (optional)
```
## Translation Pattern
```tsx
"use client";
import { useTranslations } from "next-intl";
export default function HeroSection() {
const t = useTranslations("hero");
return (
<section>
<h1>{t("title")}</h1>
<p>{t("description")}</p>
</section>
);
}
```
## Rules
- **Danish is the source language** — always write Danish text first, then translate to other languages
- Use `useTranslations()` hook in client components — never inline text strings
- Namespace translations by feature/page: `hero.title`, `contact.submit`, `nav.home`
- Keep translation keys in kebab-case or camelCase — be consistent within the project
## Message File Structure
```json
{
"nav": {
"home": "Forside",
"about": "Om os",
"contact": "Kontakt"
},
"hero": {
"title": "Industriel 3D-visualisering",
"description": "Vi omdanner tekniske CAD-data til visuelle oplevelser"
}
}
```
## Language Switching
For apps without locale in the URL (single-domain approach):
```tsx
"use client";
import { useLocale } from "next-intl";
function LanguageSwitcher() {
const locale = useLocale();
// Switch by updating cookie or context
}
```
## Danish Text Rules
- Use proper Danish characters: æ, ø, å — never ae, oe, aa
- Do not use em-dashes (—) in translations — rephrase with commas or "herunder"
- Avoid AI/corporate buzzwords: "transformér", "unik", "robust", "gnidningsfrit"
- Write naturally as a Dane would speak
## Common Pitfalls
- Do not use `useTranslations()` in Server Components — use `getTranslations()` instead
- Keep `defaultLocale` set to `"da"` in the i18n config
- Ensure all three language files have the same keys — missing keys cause runtime fallback warnings
- Do not create inline translation objects — always use the message files