Next.js Internationalization (i18n) with next-intl and Intlayer
Both next-intl and Intlayer are open-source internationalization (i18n) frameworks designed for Next.js applications. They are widely used for managing translations, localization, and language switching in software projects.
They share three principal notions:
Content Declaration: The method for defining the translatable content of your application.
- Named content declaration file in Intlayer, which can be a JSON, JS, or TS file exporting the structured data. See Intlayer documentation for more information.
- Named messages or locale messages in next-intl, usually in JSON files. See next-intl documentation for more information.
Utilities: Tools to build and interpret content declarations in the application, such as useIntlayer() or useLocale() for Intlayer, and useTranslations() for next-intl.
Plugins and Middlewares: Features for managing URL redirection, bundling optimization, and more—e.g., intlayerMiddleware for Intlayer or createMiddleware for next-intl.
Intlayer vs. next-intl: Key Differences
For a deeper analysis of how Intlayer compares to other i18n libraries for Next.js (such as next-intl), check out the next-i18next vs. next-intl vs. Intlayer blog post.
How to Generate next-intl Messages with Intlayer
Why Use Intlayer with next-intl?
Intlayer content declaration files generally offer a better developer experience. They are more flexible and maintainable due to two main advantages:
Flexible Placement: You can place an Intlayer content declaration file anywhere in your application’s file tree. This makes it easy to rename or delete components without leaving unused or dangling message files.
Example file structures:
bash.└── src └── components └── MyComponent ├── index.content.ts # Content declaration file └── index.tsx
Centralized Translations: Intlayer stores all translations in a single content declaration, ensuring no translation is missing. In TypeScript projects, missing translations are flagged automatically as type errors, providing immediate feedback to developers.
Installation
To use Intlayer and next-intl together, install both libraries:
npm install intlayer next-intl
Configuring Intlayer to Export next-intl Messages
Note: Exporting messages from Intlayer for next-intl can introduce slight differences in structure. If possible, keep an Intlayer-only or next-intl-only flow to simplify the integration. If you do need to generate next-intl messages from Intlayer, follow the steps below.
Create or update an intlayer.config.ts file (or .mjs / .cjs) in the root of your project:
import { Locales, type IntlayerConfig } from "intlayer";const config: IntlayerConfig = { internationalization: { locales: [Locales.ENGLISH, Locales.FRENCH, Locales.SPANISH], defaultLocale: Locales.ENGLISH, }, content: { dictionaryOutput: ["next-intl"], // Use the next-intl output nextIntlMessagesDir: "./intl/messages", // Where to save next-intl messages },};export default config;
Content Declaration
Below are examples of content declaration files in multiple formats. Intlayer will compile these into message files that next-intl can consume.
import { t, type DeclarationContent } from "intlayer";const content = { key: "my-component", content: { helloWorld: t({ en: "Hello World", fr: "Bonjour le monde", es: "Hola Mundo", }), },} satisfies DeclarationContent;export default content;
Build the next-intl Messages
To build the message files for next-intl, run:
npx intlayer build
This will generate resources in the ./intl/messages directory (as configured in intlayer.config.*). The expected output:
.└── intl └── messages └── en-GB └── my-content.json └── fr └── my-content.json └── es └── my-content.json
Each file includes compiled messages from all Intlayer content declarations. The top-level keys typically match your content.key fields.
Using next-intl in Your Next.js App
For more details, see the official next-intl usage docs.
Create a Middleware (optional):
If you want to manage automatic locale detection or redirection, use next-intl’s createMiddleware.middleware.tsimport createMiddleware from "next-intl/middleware";import { NextResponse } from "next/server";export default createMiddleware({ locales: ["en", "fr", "es"], defaultLocale: "en",});export const config = { matcher: ["/((?!api|_next|.*\\..*).*)"],};
Create a layout.tsx or _app.tsx to Load Messages:
If you’re using the App Router (Next.js 13+), create a layout:app/[locale]/layout.tsximport { NextIntlClientProvider } from 'next-intl';import { notFound } from 'next/navigation';import React, { ReactNode } from 'react';export const dynamic = 'force-dynamic';export default async function RootLayout({ children, params}: { children: ReactNode; params: { locale: string };}) { let messages; try { messages = (await import(`../../intl/messages/${params.locale}.json`)).default; } catch (error) { notFound(); } return ( <html lang={params.locale}> <body> <NextIntlClientProvider locale={params.locale} messages={messages}> {children} </NextIntlClientProvider> </body> </html> );}
If you’re using the Pages Router (Next.js 12 or below), load messages in _app.tsx:
pages/_app.tsximport type { AppProps } from 'next/app';import { NextIntlProvider } from 'next-intl';function MyApp({ Component, pageProps }: AppProps) { return ( <NextIntlProvider locale={pageProps.locale} messages={pageProps.messages}> <Component {...pageProps} /> </NextIntlProvider> );}export default MyApp;
Fetch Messages Server-Side (Pages Router example):
pages/index.tsximport { GetServerSideProps } from "next";import HomePage from "../components/HomePage";export default HomePage;export const getServerSideProps: GetServerSideProps = async ({ locale }) => { const messages = (await import(`../intl/messages/${locale}.json`)).default; return { props: { locale, messages, }, };};
Using Content in Next.js Components
Once the messages are loaded into next-intl, you can use them in your components via the useTranslations() hook:
import type { FC } from "react";import { useTranslations } from 'next-intl';const MyComponent: FC = () => { const t = useTranslations('my-component'); // 'my-component' corresponds to the content key in Intlayer return ( <div> <h1>{t('helloWorld')}</h1> </div> );};export default MyComponent;
That’s it! Whenever you update or add new Intlayer content declaration files, re-run the intlayer build command to regenerate your next-intl JSON messages. next-intl will pick up the updated content automatically.
If you have an idea for improving this blog, please feel free to contribute by submitting a pull request on GitHub.
GitHub link to the blog