Skip to content
3.0 preview (see announcement)
Docs
Getting started
App Router (Client Components)

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.

messages/en.json
{
  "Index": {
    "title": "Hello world!"
  }
}

middleware.ts

The middleware matches a locale for the request and handles redirects and rewrites accordingly.

middleware.ts
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.

app/[locale]/layout.tsx
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.

app/[locale]/page.tsx
'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: