DEV Community

Cover image for Next.js internationalization (i18n) tutorial
Zoran Luledzija for Localizely

Posted on • Originally published at localizely.com

Next.js internationalization (i18n) tutorial

Next.js is an open-source framework created by Vercel (formerly ZEIT). It is built on top of React and provides an out-of-the-box solution for server-side rendering (SSR) of React components. Furthermore, it supports static site generation (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 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 CLI tool.

npx create-next-app nextjs-i18n-example
Enter fullscreen mode Exit fullscreen mode

Add React Intl dependency

As we mentioned earlier, the Next.js works well with existing i18n libraries (react-intl, lingui, next-intl, and similar). In this tutorial, we will use the react-intl.

cd nextjs-i18n-example
npm i react-intl
Enter fullscreen mode Exit fullscreen mode

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

/** @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;
Enter fullscreen mode Exit fullscreen mode

Note: The internationalized routing is available since Next.js 10.

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
Enter fullscreen mode Exit fullscreen mode

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

The en.json file:

{
  "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 🚀"
}
Enter fullscreen mode Exit fullscreen mode

The fr.json file:

{
  "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 🚀"
}
Enter fullscreen mode Exit fullscreen mode

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.

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;
Enter fullscreen mode Exit fullscreen mode

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, formatting options, and similar in another post.

The index.js file:

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>
  );
}
Enter fullscreen mode Exit fullscreen mode

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

More details and examples you can find in the original post.

All code samples used in this article are available on the GitHub repo.

Latest comments (0)