Next.js 13: Internationalization (i18n)
Next.js 13 introduces support for React Server Components (opens in a new tab) with the App Router and unlocks many benefits when handling internationalization on the server side.
Getting started
If you haven't done so already, create a Next.js 13 app that uses the App Router (opens in a new tab). All pages should be moved within a [locale]
folder so that we can use this segment to provide content in different languages (e.g. /en
, /en/about
, etc.).
Start by running npm install next-intl
and create the following file structure:
├── messages (1)
│ ├── en.json
│ └── ...
├── next.config.js (2)
└── src
├── i18n.ts (3)
├── middleware.ts (4)
└── app
└── [locale]
├── layout.tsx (5)
└── page.tsx (6)
Now, set up the 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!"
}
}
next.config.js
Now, set up the plugin which provides i18n configuration to Server Components.
const withNextIntl = require('next-intl/plugin')();
module.exports = withNextIntl({
// Other Next.js configuration ...
});
src/i18n.ts
next-intl
creates a configuration once per request. Here you can provide messages and other options depending on the locale of the user.
import {getRequestConfig} from 'next-intl/server';
export default getRequestConfig(async ({locale}) => ({
messages: (await import(`../messages/${locale}.json`)).default
}));
Can I move this file somewhere else?
This file is supported out-of-the-box both in the src
folder as well as in the project root with the extensions .ts
, .tsx
, .js
and .jsx
.
If you prefer to move this file somewhere else, you can provide an optional path to the plugin:
const withNextIntl = require('next-intl/plugin')(
// Specify a custom path here
'./somewhere/else/i18n.ts'
);
module.exports = withNextIntl({
// Other Next.js configuration ...
});
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 = {
// Match only internationalized pathnames
matcher: ['/', '/(de|en)/:path*']
};
app/[locale]/layout.tsx
The locale
that was matched by the middleware is available via the locale
param and can be used to configure the document language.
import {useLocale} from 'next-intl';
import {notFound} from 'next/navigation';
const locales = ['en', 'de'];
export default function LocaleLayout({children, params: {locale}}) {
// Validate that the incoming `locale` parameter is valid
if (!locales.includes(locale as any)) notFound();
return (
<html lang={locale}>
<body>{children}</body>
</html>
);
}
app/[locale]/page.tsx
Use translations in your page components or anywhere else!
import {useTranslations} from 'next-intl';
export default function Index() {
const t = useTranslations('Index');
return <h1>{t('title')}</h1>;
}
That's all it takes!
Next steps:
- Exploring
next-intl
? Check out the usage guide. Ran into an issue? Have a look at the App Router example (opens in a new tab) (source (opens in a new tab)).
Static rendering
By using APIs like useTranslations
from next-intl
in Server Components, your pages will currently opt into dynamic rendering. This is a limitation that will eventually be lifted once createServerContext
(opens in a new tab) is available and integrated in Next.js.
As a stopgap solution, next-intl
provides a temporary API that can be used to enable static rendering:
Add generateStaticParams
to app/[locale]/layout.tsx
Since we use a dynamic route segment for the [locale]
param, we need to provide all possible values via generateStaticParams
(opens in a new tab) to Next.js, so the routes can be rendered at build time.
const locales = ['en', 'de'];
export function generateStaticParams() {
return locales.map((locale) => ({locale}));
}
Add unstable_setRequestLocale
to all layouts and pages
next-intl
provides a temporary API that can be used to distribute the locale that is received via params
in a layout or page for usage in all Server Components that are rendered as part of the request.
import {unstable_setRequestLocale} from 'next-intl/server';
const locales = ['en', 'de'];
export default async function LocaleLayout({
children,
params: {locale}
}) {
// Validate that the incoming `locale` parameter is valid
if (!locales.includes(locale as any)) notFound();
unstable_setRequestLocale(locale);
return (
// ...
);
}
import {unstable_setRequestLocale} from 'next-intl/server';
import {locales} from '..';
export default async function IndexPage({
params: {locale}
}) {
unstable_setRequestLocale(locale);
return (
// ...
);
}
What does "unstable" mean?
unstable_setRequestLocale
is meant to be used as a stopgap solution and will eventually be replaced by an API that's based on createServerContext
(opens in a new tab). When that time comes, you'll get a deprecation notice in a minor version and the API will be removed as part of a major version.
Note that Next.js can render layouts and pages indepently. This means that e.g. when you navigate from /settings/profile
to /settings/privacy
, the /settings
segment might not re-render as part of the request. Due to this, it's important that unstable_setRequestLocale
is called not only in the parent settings/layout.tsx
, but also in the individual pages profile/page.tsx
and settings/page.tsx
.
That being said, the API is expected to work reliably if you're cautious to apply it in all relevant places.