Next.js 13: Internationalization (i18n) in Client Components
next-intl
is available in the app
directory from version 2.10.0
onwards.
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 creating the following file structure:
├── messages (1)
│ ├── en.json
│ └── ...
├── app
│ └── [locale]
│ ├── layout.tsx (2)
│ └── page.tsx (3)
└── middleware.ts (4)
Now, set up these files as follows:
- Set up messages for a language, e.g. in
messages/en.json
.
{
"Index": {
"title": "Hello world!"
}
}
- Provide the document layout and set up
NextIntlClientProvider
inapp/[locale]/layout.tsx
.
import {NextIntlClientProvider} from 'next-intl/client';
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>
);
}
- Turn your component in
app/[locale]/page.tsx
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>;
}
- Set up the i18n routing middleware in
middleware.ts
. The middleware matches the locale of 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'],
// If this locale is matched, pathnames work without a prefix (e.g. `/about`)
defaultLocale: 'en'
});
export const config = {
// Skip all paths that should not be internationalized
matcher: ['/((?!api|_next|.*\\..*).*)']
};
That's all you need to do to start using translations in the app
directory!
If you've encountered an issue, you can explore the code for a working example (opens in a new tab) (demo (opens in a new tab)).
Note that you have to mark all components that use features from next-intl
with 'use client';
at the top of the module if you use this approach.
Support for next-intl
APIs in Server Components is available in a beta
version.
Linking between pages
In the pages
folder, Next.js automatically considers the current locale for next/link
. Since this is no longer the case for the app
directory, you can add a component that wraps next/link
accordingly as a drop-in replacement.
import {useLocale} from 'next-intl';
import NextLink from 'next/link';
import {ComponentProps, forwardRef} from 'react';
type Props = ComponentProps<typeof NextLink>;
function Link({href, ...rest}: Props, ref: Props['ref']) {
const locale = useLocale();
// Turn this off, to avoid updating the locale cookie for prefetch requests
const prefetch = false;
function getLocalizedHref(originalHref: string) {
return originalHref.replace(/^\//, '/' + locale + '/');
}
const localizedHref =
typeof href === 'string'
? getLocalizedHref(href)
: href.pathname != null
? {...href, pathname: getLocalizedHref(href.pathname)}
: href;
return (
<NextLink ref={ref} href={localizedHref} prefetch={prefetch} {...rest} />
);
}
export default forwardRef(Link);
// Usage while being on `/en`
<Link href="/">Goes to `/en`</Link>
<Link href="/nested">Goes to `/en/nested`</Link>
<Link href={{pathname: "/nested"}}>Goes to `/en/nested`</Link>
Built-in support for i18n routing APIs is being considered (see the Server Components beta docs).
Usage with the Metadata API
To provide metadata for a route, you can use the core library from next-intl
.
import {createTranslator} from 'next-intl';
export async function generateMetadata({params: {locale}}) {
const messages = (await import(`../../../messages/${locale}.json`)).default;
// You can use the core (non-React) APIs when you have to use next-intl
// outside of components. Potentially this will be simplified in the future
// (see https://next-intl-docs.vercel.app/docs/next-13/server-components).
const t = createTranslator({locale, messages});
return {
title: t('LocaleLayout.title')
};
}
Potentially, this will be simplified in the future (see the Server Components beta docs).