Next.js 13: Internationalization (i18n) in Client Components
Next.js 13 introduces support for React Server Components (opens in a new tab) with the App Router. While support for Server Components in next-intl
is on the horizon, you can use next-intl
in the app
directory by deferring the usage of internationalization to Client Components.
Getting started
If you haven't done so already, create a Next.js 13 app that uses the app
directory (opens in a new tab). The goal is to prefix all routes with the locale
, so that we can retrieve it as a dynamic segment (opens in a new tab) and use it to configure next-intl
.
Start by running npm install next-intl
and create the following file structure:
├── messages (1)
│ ├── en.json
│ └── ...
├── middleware.ts (2)
└── app
└── [locale]
├── layout.tsx (3)
└── page.tsx (4)
Now, set up these files as follows:
messages/en.json
Messages can be provided locally or loaded from a remote data source (e.g. a translation management system). Use whatever suits your workflow best.
The simplest option is to create JSON files locally based on locales, e.g. en.json
.
{
"Index": {
"title": "Hello world!"
}
}
middleware.ts
The middleware matches a locale for the request and handles redirects and rewrites accordingly.
import createMiddleware from 'next-intl/middleware';
export default createMiddleware({
// A list of all locales that are supported
locales: ['en', 'de'],
// Used when no locale matches
defaultLocale: 'en'
});
export const config = {
// Skip all paths that should not be internationalized. This example skips
// certain folders and all pathnames with a dot (e.g. favicon.ico)
matcher: ['/((?!api|_next|_vercel|.*\\..*).*)']
};
Note: If you have pages that contain the character .
in the pathname (e.g. /users/jane.doe
), you might want to consider them in your matcher config.
app/[locale]/layout.tsx
Provide the document layout and set up NextIntlClientProvider
.
import {NextIntlClientProvider} from 'next-intl';
import {notFound} from 'next/navigation';
export function generateStaticParams() {
return [{locale: 'en'}, {locale: 'de'}];
}
export default async function LocaleLayout({children, params: {locale}}) {
let messages;
try {
messages = (await import(`../../messages/${locale}.json`)).default;
} catch (error) {
notFound();
}
return (
<html lang={locale}>
<body>
<NextIntlClientProvider locale={locale} messages={messages}>
{children}
</NextIntlClientProvider>
</body>
</html>
);
}
app/[locale]/page.tsx
Turn your page component into a Client Component to be able to use translations.
'use client';
import {useTranslations} from 'next-intl';
export default function Index() {
const t = useTranslations('Index');
return <h1>{t('title')}</h1>;
}
That's all you need to do to start using translations in the app
directory!
Note that you have to mark all components that use features from next-intl
as Client Components if you use this approach. Support for next-intl
APIs in Server Components is available in a beta version.
Next steps:
Ran into an issue? Have a look at the App Router example (opens in a new tab) (source (opens in a new tab)).
- Exploring
next-intl
? Check out the usage guide. Decided you're sticking with
next-intl
? Consider the steps of the checklist for production.