Internationalization
Multi-language support using next-intl.
Off by default. When on, the site supports multiple languages with URL prefixes like /en/, /ru/, /es/.
Don't enable until you have translations
Turning i18n on with only en.json gives you every downside (extra routing, prefixed URLs) with zero benefit. Only flip it on when you have at least one real translation ready.
How it works
- Translations live in
messages/{locale}.json - Middleware (
middleware.ts) detects the user's language and routes accordingly - Components use the
useTranslations()hook to pull strings - A language switcher in the header lets users override detection

Setup
Two toggles, both required:
export const featuresConfig = { /* ... */ i18n: true };export const i18nConfig = {
enabled: true,
defaultLocale: 'en',
locales: ['en', 'ru', 'es'],
localeCookie: 'NEXT_LOCALE',
localeDetection: true,
};Then create translation files:
messages/
├── en.json
├── ru.json
└── es.json
Using translations in components
Server components
import { getTranslations } from 'next-intl/server';
export default async function Page() {
const t = await getTranslations('homepage');
return <h1>{t('title')}</h1>;
}Client components
'use client';
import { useTranslations } from 'next-intl';
export function Hero() {
const t = useTranslations('homepage');
return <h1>{t('title')}</h1>;
}Navigating
Always use next-intl's Link, not Next's — it auto-prefixes with the current locale:
import { Link } from '@/i18n/navigation'; // or next-intl equivalent
<Link href="/pricing">Pricing</Link>
// renders as /pricing for EN, /ru/pricing for RU, etc.What's not auto-translated
- User-generated content (project names, descriptions, comments) — stays in whatever language the user typed
- Blog posts — write separate MDX files per locale, or use a translation service
- Legal pages — translate the strings in
messages/, the content lives there