i18n Config
Multi-language support via next-intl.
File: config/i18n.config.ts
Off by default. When enabled, the site routes users to /<locale>/... URLs and loads translations from messages/{locale}.json.
Enable both i18n: true in features.config.ts and enabled: true in i18n.config.ts. The feature flag gates the middleware; the config flag gates the routing logic.
Setup
1. Enable the flag
export const featuresConfig = {
// ...
i18n: true,
};2. Configure locales
export const i18nConfig = {
enabled: true, // master switch
defaultLocale: 'en' as const,
locales: ['en', 'ru', 'es'] as const,
localeCookie: 'NEXT_LOCALE',
localeDetection: true, // auto-detect from Accept-Language
};| Field | What it does |
|---|---|
enabled | Master switch. false = zero routing impact. |
defaultLocale | Fallback when a translation is missing. |
locales | All supported languages. Each needs a messages/{code}.json. |
localeCookie | Cookie name for saving the user's choice. |
localeDetection | Auto-detect language from browser headers on first visit. |
3. Add translation files
messages/
├── en.json ← required, the baseline
├── ru.json
└── es.json
Each file has the same structure — copy en.json and translate the strings.
Generate messages/{locale-code, e.g. "ru" or "es"}.json by translating every key and value in messages/en.json to {target language, e.g. "Russian" or "Spanish"}.
Rules:
- Preserve the exact JSON structure, keys, and nesting.
- Translate only the string values — never the keys.
- Keep
\{placeholders\}in curly braces intact (those are next-intl variables). - For brand-specific terms like "DirectoryKit", decide: translate if it reads naturally localized, otherwise keep English.
- Match the tone of the English source (friendly/professional, not overly formal).
Don't touch messages/en.json.
4. The language switcher
Already in the header when i18n is on. Users can change language at any time.

How URLs work
enis the default — URL stays clean:/,/pricing,/submit- Other locales get a prefix —
/ru/,/ru/pricing,/ru/submit - All links inside the app use
next-intl'sLinkhelper — it auto-prefixes.
What gets translated
- Every string in
messages/{locale}.json - Email templates (when using translation keys)
- Validation error messages (when using keys)
What doesn't get auto-translated:
- User-generated content (project names, descriptions, comments)
- The blog (
content/blog/*.mdx— write per-locale articles manually) - Legal pages (Terms, Privacy) — translate manually in
messages/
Keep i18n off if you don't need it
enabled: false means zero routing or rendering overhead. Leave it off until you really have translated content — enabling it adds complexity to everything from links to SEO.