Next.js internationalization (i18n) tutorial
Tags: nextjs, react, i18n, tutorial
Published: 2022-03-30 14:34:17

[Next.js](https://nextjs.org/) is an open-source framework created by [Vercel](https://vercel.com/) (formerly ZEIT). It is built on top of React and provides an out-of-the-box solution for [server-side rendering](https://nextjs.org/docs/basic-features/pages#server-side-rendering) (SSR) of React components. Furthermore, it supports [static site generation](https://nextjs.org/docs/basic-features/pages#static-generation-recommended) (SSG), which can help us to build superfast and user-friendly websites in no time. Although a relatively young framework, it has a good [foundation for internationalization](https://nextjs.org/docs/advanced-features/i18n-routing) which complements well with existing i18n libraries. In the following chapters, we will explain how to set up internationalization in your Next.js app.

## Create a new Next.js project

First, let's create a new Next.js project with the [create-next-app](https://www.npmjs.com/package/create-next-app) CLI tool.

```shell
npx create-next-app nextjs-i18n-example
```

## Add React Intl dependency

As we mentioned earlier, the Next.js works well with existing i18n libraries ([react-intl](https://github.com/formatjs/formatjs/tree/main/packages/react-intl), [lingui](https://github.com/lingui/js-lingui), [next-intl](https://github.com/amannn/next-intl), and similar). In this tutorial, we will use the `react-intl`.

```shell
cd nextjs-i18n-example
npm i react-intl
```

## Add config for internationalized routing

Translations and routing are two main pillars of internationalization. The previously added `react-intl` library is going to handle translations and formatting. When it comes to routing, Next.js has [built-in support](https://nextjs.org/docs/advanced-features/i18n-routing) for that. This built-in support offers two options, sub-path routing, and domain routing. In this tutorial, we will use sub-path routing as it is less complex and more common for average websites. For that, let's update the `next.config.js` file with the `i18n` config.

```js
/** @type {import('next').NextConfig} */
const nextConfig = {
  reactStrictMode: true,
  i18n: {
    // The locales you want to support in your app
    locales: ["en", "fr"],
    // The default locale you want to be used when visiting a non-locale prefixed path e.g. `/hello`
    defaultLocale: "en",
  },
};

module.exports = nextConfig;
```

_**Note:** The internationalized routing is available since [Next.js 10](https://nextjs.org/blog/next-10#internationalized-routing)._

## Create localization files

The next important thing is to add localization files. For that purpose, let's create a `lang` directory. Within it, add two JSON files: `en.json` and `fr.json`. These files are going to hold translations for English and French, respectively. Below, you can see the project structure after adding the mentioned files.

```
nextjs-i18n-example
|-- lang
|   |-- en.json
|   |-- fr.json
|-- pages
|   |-- api
|   |-- _app.js
|   |-- index.js
|   |-- ...
|-- public
|-- ...
|-- package.json
|-- package-lock.json
```

Afterward, fill in localization files with messages that we will use later.

The `en.json` file:

```json
{
  "page.home.head.title": "Next.js i18n example",
  "page.home.head.meta.description": "Next.js i18n example - English",
  "page.home.title": "Welcome to <b>Next.js i18n tutorial</b>",
  "page.home.description": "You are currently viewing the homepage in English 🚀"
}
```

The `fr.json` file:

```json
{
  "page.home.head.title": "Next.js i18n exemple",
  "page.home.head.meta.description": "Next.js i18n exemple - Français",
  "page.home.title": "Bienvenue à <b>Next.js i18n didacticiel</b>",
  "page.home.description": "Vous consultez actuellement la page d'accueil en Français 🚀"
}
```

## Configure react-intl in Next.js project

Internationalized routing and localization files are just the first part of the task. The second part is setting up the `react-intl` library. Below, you can see what changes have been made in the `_app.js` file.

```js
import { useRouter } from "next/router";
import { IntlProvider } from "react-intl";

import en from "../lang/en.json";
import fr from "../lang/fr.json";

import "../styles/globals.css";

const messages = {
  en,
  fr,
};

function MyApp({ Component, pageProps }) {
  const { locale } = useRouter();

  return (
    <IntlProvider locale={locale} messages={messages[locale]}>
      <Component {...pageProps} />
    </IntlProvider>
  );
}

export default MyApp;
```

## Adapt pages for i18n

We did most of the work. The last step is to put all this together. Therefore, we are going to update the `index.js` file under the pages directory. Below, you can find two approaches for accessing the localization messages, imperative and declarative. We've already covered [these two ways of usage](https://localizely.com/blog/react-intl-tutorial/#imperative-vs-declarative-usage-of-react-intl), [formatting options](https://localizely.com/blog/react-intl-tutorial/#react-intl-formatting-options), and similar in [another post](https://localizely.com/blog/react-intl-tutorial/).

The `index.js` file:

```js
import Head from "next/head";
import Link from "next/link";
import { useRouter } from "next/router";
import { FormattedMessage, useIntl } from "react-intl";

import styles from "../styles/Home.module.css";

export default function Home(props) {
  const { locales } = useRouter();

  const intl = useIntl();

  const title = intl.formatMessage({ id: "page.home.head.title" });
  const description = intl.formatMessage({ id: "page.home.head.meta.description" });

  return (
    <div className={styles.container}>
      <Head>
        <title>{title}</title>
        <meta name="description" content={description} />
        <link rel="icon" href="/favicon.ico" />

        {/* Add hreflang links */}
        <link rel="alternate" href="http://example.com" hrefLang="x-default" />
        <link rel="alternate" href="http://example.com" hrefLang="en" />
        <link rel="alternate" href="http://example.com/fr" hrefLang="fr" />
      </Head>

      <header>
        <div className={styles.languages}>
          {[...locales].sort().map((locale) => (
            <Link key={locale} href="/" locale={locale}>
              {locale}
            </Link>
          ))}
        </div>
      </header>

      <main className={styles.main}>
        <h1 className={styles.title}>
          <FormattedMessage id="page.home.title" values={{ b: (chunks) => <b>{chunks}</b> }} />
        </h1>

        <p className={styles.description}>
          <FormattedMessage id="page.home.description" />
        </p>
      </main>
    </div>
  );
}
```

Congratulations! 🎉
You have successfully set up internationalization in your Next.js project.

More details and examples you can find in the [original post](https://localizely.com/blog/nextjs-i18n-tutorial/).

All code samples used in this article are available on the [GitHub repo](https://github.com/localizely/nextjs-i18n-example).