patterntypescriptnextjsModerate
Internationalization in App Router with next-intl
Viewed 0 times
next-intl 3.x with Next.js 13+ App Router
internationalizationi18nnext-intllocale routingtranslationsmultilingual
Problem
Adding multi-language support requires locale-prefixed URLs (/en/about, /fr/about), locale detection, and translated content. Pages Router had built-in i18n but App Router removed it, requiring manual setup or a library.
Solution
Use next-intl with middleware for App Router i18n:
// middleware.ts
import createMiddleware from 'next-intl/middleware';
export default createMiddleware({
locales: ['en', 'fr', 'de'],
defaultLocale: 'en',
});
export const config = { matcher: ['/((?!api|_next|.\..).*)'] };
// app/[locale]/layout.tsx
import { NextIntlClientProvider } from 'next-intl';
import { getMessages } from 'next-intl/server';
export default async function LocaleLayout({ children, params }) {
const { locale } = await params;
const messages = await getMessages();
return (
<html lang={locale}>
<body>
<NextIntlClientProvider messages={messages}>
{children}
</NextIntlClientProvider>
</body>
</html>
);
}
// messages/en.json
{ "nav": { "home": "Home", "about": "About" } }
// In a Server Component:
import { useTranslations } from 'next-intl';
export default function Nav() {
const t = useTranslations('nav');
return <nav><a href='/'>{t('home')}</a></nav>;
}
// middleware.ts
import createMiddleware from 'next-intl/middleware';
export default createMiddleware({
locales: ['en', 'fr', 'de'],
defaultLocale: 'en',
});
export const config = { matcher: ['/((?!api|_next|.\..).*)'] };
// app/[locale]/layout.tsx
import { NextIntlClientProvider } from 'next-intl';
import { getMessages } from 'next-intl/server';
export default async function LocaleLayout({ children, params }) {
const { locale } = await params;
const messages = await getMessages();
return (
<html lang={locale}>
<body>
<NextIntlClientProvider messages={messages}>
{children}
</NextIntlClientProvider>
</body>
</html>
);
}
// messages/en.json
{ "nav": { "home": "Home", "about": "About" } }
// In a Server Component:
import { useTranslations } from 'next-intl';
export default function Nav() {
const t = useTranslations('nav');
return <nav><a href='/'>{t('home')}</a></nav>;
}
Why
App Router removed the built-in i18n config that Pages Router had. Libraries like next-intl handle locale routing via middleware, provide type-safe translations, and support both Server and Client Components.
Gotchas
- All routes must be under app/[locale]/ — this changes every file path in the app
- generateStaticParams must be added to layouts to generate pages for each locale
- Client Components use useTranslations hook; Server Components use the same hook (next-intl handles both)
- hreflang alternate links are needed for SEO — next-intl generates them via the Metadata API
Code Snippets
Server Component with next-intl translation
// app/[locale]/page.tsx
import { useTranslations } from 'next-intl';
export default function HomePage() {
const t = useTranslations('home');
return <h1>{t('title')}</h1>;
}Context
When building a multi-language Next.js App Router application
Revisions (0)
No revisions yet.