Zoran Luledzija
Zoran Luledzija
March 17, 2022
11 min read
March 17, 2022
11 min read

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.

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

Create a new Next.js project

First, let's create a new Next.js project with the create-next-app CLI tool. This tool is maintained by the creators of Next.js and will make the creation process easier for us. By running the command below, we will create a new Next.js project called nextjs-i18n-example in a folder with the same name.

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, lingui, next-intl, and similar). In this tutorial, we will use the react-intl. This library is one of the most popular in the context of internationalization. It supports ICU syntax and various formatting options. By running the commands below, we will change the current working directory to the nextjs-i18n-example and install the react-intl dependency.

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 for that. This built-in support offers two options, sub-path routing, and domain routing. As the names imply, sub-path routing uses the locale in the url path, whereas domain routing uses different domains for serving content for different locales. In our case, we will use sub-path routing as it is less complex and more common for average websites.

Sub-path routing examples

  • /blog
  • /fr/blog
  • /nl-NL/blog

Domain routing examples

  • example.com/blog
  • example.fr/blog
  • example.nl/blog

To use the built-in support for internationalized routing, we need to update the next.config.js file with the i18n config. The locales represents the list of locales we are going to support in our app. The defaultLocale represents the default locale of the app. That means that all pages for Arabic, French, and Dutch (Netherlands) will be prefixed with the ar, fr, and nl-NL in url path.

The next.config.js file:

/** @type {import('next').NextConfig} */
const nextConfig = {
  reactStrictMode: true,
  i18n: {
    // The locales you want to support in your app
    locales: ["ar", "en", "fr", "nl-NL"],
    // 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.

Automatic Locale Detection

The above-added config for the internationalized routing by default includes automatic locale detection. That means that Next.js will try to automatically detect which locale the user prefers based on the Accept-Language header. To check how this works, let's run the Next.js app on the localhost and try to open it with different browser settings.

  1. Run the app on the localhost
npm run dev
  1. Update browser language to Arabic, English, French, or Dutch (Netherlands)

Chrome languages settings

  1. Open the app in browser

Whenever we change the browser language and open http://localhost:3000, we will be redirected to the appropriate page.

Arabic ➝ http://localhost:3000/ar

French ➝ http://localhost:3000/fr

Dutch (Netherlands) ➝ http://localhost:3000/nl-NL

Other langauges ➝ http://localhost:3000

In some rare cases, you might want to disable automatic locale detection. To achieve that, you should set the localeDetection to false in the next.config.js file. After that, Next.js will no longer automatically redirect based on the user's preferred locale.

The next.config.js file:

...
const nextConfig = {
  ...
  i18n: {
    ...
    localeDetection: false
  },
};
...

Create localization files

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

nextjs-i18n-example
|-- lang
|   |-- ar.json
|   |-- en.json
|   |-- fr.json
|   |-- nl-NL.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 ar.json file:

{
  "page.home.head.title": "مثال على Next.js i18n",
  "page.home.head.meta.description": "مثال Next.js i18n - عربي",
  "page.home.title": "مرحبًا بك في <b> البرنامج التعليمي Next.js i18n </b>",
  "page.home.description": "أنت الآن تستعرض الصفحة الرئيسية بالعربية 🚀"
}

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 🚀"
}

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 🚀"
}

The nl-NL.json file:

{
  "page.home.head.title": "Next.js i18n voorbeeld",
  "page.home.head.meta.description": "Next.js i18n voorbeeld - Nederlands (Nederland)",
  "page.home.title": "Welkom bij <b>Next.js i18n zelfstudie</b>",
  "page.home.description": "U bekijkt momenteel de homepage in het Nederlands (Nederland) 🚀"
}

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.

Wrap the app with the IntlProvider

The react-intl uses the provider pattern for passing the i18n context to a tree of components. Therefore, we will need to wrap the app's root with the IntlProvider. Using the useRouter hook, we can access the locale information and pass appropriate localization messages to the IntlProvider.

Determine text direction

When it comes to the text direction, languages can be ltr (left-to-right) or rtl (right-to-left). The default text direction in HTML is ltr. In most cases, you don't need to configure anything. However, when one of the languages is rtl, you might need to handle text direction explicitly. In our case, Arabic is the rtl, so we need to handle that as well. For that purpose, we added the getDirection function. It returns the text direction for the passed locale. Later in the code, we will use that function and pass its response as dir prop to all pages in our app.

Note that this is not the only way for setting the text direction in Next.js apps. Alternatively, you could handle that in the _document.js file by updating the dir attribute of the html element or in your Layout component. For the sake of simplicity, we put that logic inside the _app.js file. However, you should use the approach that works best for you.

The _app.js file:

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

import ar from "../lang/ar.json";
import en from "../lang/en.json";
import fr from "../lang/fr.json";
import nl_NL from "../lang/nl-NL.json";

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

const messages = {
  ar,
  en,
  fr,
  "nl-NL": nl_NL,
};

function getDirection(locale) {
  if (locale === "ar") {
    return "rtl";
  }

  return "ltr";
}

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

  return (
    <IntlProvider locale={locale} messages={messages[locale]}>
      <Component {...pageProps} dir={getDirection(locale)} />
    </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, formatting options, and similar in another post. Therefore, we will not spend more words on that. Instead, we are going to focus on the hreflang tag, language switcher, and other important things.

Add hreflang tags

The hreflang tag is a way to tell search engines which language you are using on a specific page. Doing so will help them to show users the most appropriate version of your page. In this post, we will not go into depth regarding hreflang, but it should be noted that it is good practice to include them for better SEO. Also, keep in mind that href attribute values need to be updated to correspond to your domain.

Add language switcher

When it comes to the language switcher, we've used the useRouter hook to retrieve locales information. Later, we've mapped each locale to a Link component. The Link component accepts the optional locale prop for transitioning to a different locale from the currently active one. If not provided, the locale prop will use the currently active locale.

It is worth noting that Next.js allows you also to store selected locale in the NEXT_LOCALE cookie. That cookie can be used later for overriding the Accept-Language header (Automatic Locale Detection) by redirecting the user to the correct location. That is, the user that prefers the locale en in the Accept-Language header, but has a NEXT_LOCALE=fr cookie, will be redirected to a page that corresponds to fr locale. In this tutorial, we did not use this cookie. However, its implementation is not complicated. Whenever the user changes the language, the mentioned cookie should be updated.

Set text direction

As for the text direction, we just need to set the dir prop of the Home component to the container of our content. As we stated earlier, this is just one way to solve it. Feel free to use the method that works best for you.

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({ dir }) {
  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/ar" hrefLang="ar" />
        <link rel="alternate" href="http://example.com/fr" hrefLang="fr" />
        <link rel="alternate" href="http://example.com/nl-NL" hrefLang="nl-NL" />
      </Head>

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

      <main dir={dir} 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.
If you have followed all the steps, your app should look like the one shown below.

Next.js i18n tutorial demo

What about static HTML export?

Currently, the biggest weakness of the internationalization of Next.js apps is the inability to export to static HTML. Running such command results in "Error: i18n support is not compatible with next export". This issue has already been reported and discussed. A potential workaround would be to use a regular Next.js deployment strategy or manual handling of i18n routing.

Managing your Next.js translations

Easy to manage a couple of strings in two languages? Sure. But as you add more features and expand to more markets, your app will grow to thousands of strings in a dozen of languages. Here’s how to streamline your app translation process:

Step 1: Create a project in Localizely

Once you sign up to Localizely, just go to My projects page and tap “+” button (or explore the Sample Project if you wish to get more familiar with Localizely features first). Give your project a name and set your main and other languages. You can change the main language later if needed.


NextJS localization create project


Each project gets a unique ID (you can find it in My projects page), that is needed when using API.

Step 2: Upload your files

Import your main JSON file to Localizely. Go to Upload page, select the file, and confirm. Alternatively, you can start by adding string keys in Localizely first.


NextJS localization JSON file upload


Step 3: Invite team members

Localization work is a team effort. Switch to the Contributors section by clicking the icon in the side menu and start adding teammates. Any user can be granted admin privileges, i.e. the same rights on the project as you. For non-admin contributors, you can specify per-language access to the project specifying some languages as reference (read-only) or contributable (read and update). Only admins have access to string key changes, importing, exporting, settings, etc.


NextJS localization invite team member


Step 4: Translate

Localizely has an editor on Translations page that reminds of Excel tables commonly used for translations management. Feel free to explore the Sample project or just take a look at Getting started guide to grasp the basic concept and options.


NextJS localization translate


Step 5: Download the files

The translation part is done. How to get the localization data out of Localizely and make it usable in your Next.js app?

There are 2 options:

  • Option 1: Download manually

Click Download icon in your project side menu and select Key-Value JSON (.json) as the exporting format. Click Download to get the file. Then move downloaded .json file into your project replacing the existing localization, that’s all.


NextJS localization download JSON file


  • Option 2: Download via API

Depending on your project deployment setup, you may want to use Localizely API – it gives you a simple way to automatically generate and download localization files.

Conclusion

As you may have noticed, Next.js has pretty good support for internationalization. A lot of it is automated and works well. In this tutorial, we've used the built-in support for internationalized routing and the react-intl for handling translations and formatting. Similarly, Next.js would also work seamlessly with any other i18n library. But what about translation? It is a more complex process that follows the entire lifecycle of the application. Imagine how difficult it can be to share localization files with non-technical people through emails, translate, chat about changes, and sync everything later. Luckily, the Localizely platform can help you with that. It is a platform that facilitates collaboration. It offers translation through tasks, glossary, history of changes, and much more.

Try Localizely for free.

Like this article? Share it!


Zoran Luledzija
Zoran Luledzija

Zoran is a Software Engineer at Localizely. His primary interest is web development, but he also has a solid background in other technologies. For the last few years, he has been involved in the development of software localization tools.

Enjoying the read?

Subscribe to the Localizely blog newsletter for quality product content in your inbox.

Copyrights 2023 © Localizely