Next.js — The Scalable Way to Internationalize Using next-i18next
Tags: nextjs, react, javascript, localization
Published: 2021-07-07 04:46:46

In this tutorial, we’re going to learn how to internationalize a Next.js app using [next-i18next](https://github.com/isaachinman/next-i18next). To make things even easier, we’re going to use [i18nexus](https://i18nexus.com) to auto-translate and manage our strings for us.

**Let’s go! ⚡️**

## Getting Started

Let’s bootstrap together a simple Next.js application using [create-next-app](https://nextjs.org/docs/api-reference/create-next-app):

`npx create-next-app`

After typing in a title for our app, we will cd into the app directory and install [next-i18next](https://github.com/isaachinman/next-i18next):

`npm install next-i18next --save`

**Time to internationalize! 🌐**

## Configuration

With the release of Next.js v10, we received an awesome built-in solution for handling internationalized routing and locale detection. `next-i18next` is designed to work hand-in-hand with this new internationalization support.

To use `next-i18next`, we need to create an i18next config file at the root of our app called `next-i18next.config.js`. This will define the languages we want our app to use. Let’s support English and Swedish.

We need to initialize our `defaultLocale` (the default language for our app) and `locales` (a list of languages that we want our app to support):

**next-i18next.config.js**

    module.exports = {
      i18n: {
        defaultLocale: 'en',
        locales: ['en', 'sv']
      },
      reloadOnPrerender: process.env.NODE_ENV === 'development'
    }

It is also recommended to enable the `reloadOnPrerender` option in development. When this option is enabled, `next-i18next` will reload our translations when we make changes to our translation files.

Next, we need to add the `i18n` property to `next.config.js` by simply importing the `i18n` object from the i18next config:

**next.config.js**

    const { i18n } = require('./next-i18next.config');
    
    module.exports = {
      i18n,
      reactStrictMode: true
    }

The `next-i18next` library uses the same `i18n` config structure that Next requires. So instead of having to manage the same settings in two places, we simply import the `next-i18next` config into `next.config.js` as recommended in the [next-i18next docs](https://github.com/isaachinman/next-i18next).

**appWithTranslation**

One last step for setting up `next-i18next` is to wrap our app with the `appWithTranslation` HOC (higher-order component). This component will provide our i18next context to all of our pages. Our `_app.js` file should look like this:

**_app.js**

    import '../styles/globals.css';
    import { appWithTranslation } from 'next-i18next';
    
    const MyApp = ({ Component, pageProps }) => <Component {...pageProps} />
    
    export default appWithTranslation(MyApp);

**And now we’re ready to go! 🚀**

## i18nexus Integration

If you’ve used i18next before but have never used it with i18nexus, you’re in for a treat.

i18nexus makes managing our translations a million times easier by storing our app texts in the cloud. It even machine translates our strings using DeepL and Google Translate to as many languages as we need. Whenever we’re ready to hire professional translators, we just invite them to our i18nexus project and let them edit the translations!

**Let’s do it!**

If you don’t already have an i18nexus account, go to [i18nexus.com](https://app.i18nexus.com/sign-up) and sign up for a free account. After naming our project we’ll be directed to our language dashboard:

![i18nexus project dashboard](https://cdn-images-1.medium.com/max/5760/1*K0orZ6tosGWb3GuEYvHZTQ.png)*i18nexus project dashboard*

The first language tile is our base language, which should match the language we set for the `defaultLocale` property in `next-i18next.config.js`.

Next, we’ll click **Add Language** to select the locales that we want our app to support. Since we already added `sv` (Swedish) to our `locales` list in `next-i18next.config.js`, we’ll select Swedish:

![Adding Swedish as a supported language](https://cdn-images-1.medium.com/max/5760/1*rELtPG31hB8drpUHG9286A.png)*Adding Swedish as a supported language*

Now let’s click **Open Project** in the top right corner to go to the Strings Management page where we will be adding our strings.

**Namespaces**

At the top of the page, there is a dropdown labeled **namespaces**, which contains one namespace already created for us called “default”.

It is convention to have one namespace for each Page in your app, as well as a namespace called “common” for common strings that appear on more than one page.

Let’s rename the “default” namespace to “**common**”, and then create a namespace called “**home**” to be used for our Home page:

![Managing namespaces](https://cdn-images-1.medium.com/max/5760/1*Lj2SPK8UkXThdwdv85rNdg.png)*Managing namespaces*

**Important:** Even though we will not be using the “common” namespace in this walkthrough, it is required. We will discuss why shortly. 🙂

To add our first string, click **Add String**. I’m going to add a string in my “home” namespace that says “Hello, and welcome to my app!”:

![Adding a new string with key “welcome_msg”](https://cdn-images-1.medium.com/max/5760/1*eWDEQy6F8LaHJai-JjU6YQ.png)*Adding a new string with key “welcome_msg”*

The **key** is how we will reference this string in our code.

The **value** is the text that will be rendered in our app.

The **details** field is optional. It is meant to provide any extra information about the context of our string for when we’re ready to bring in professional translators. We can even add an image here for more context.

After adding the string, we can expand the row to see our automatic translations (using DeepL and Google Translate):

![New strings are automatically translated to all of our supported languages](https://cdn-images-1.medium.com/max/5760/1*Lp_1QokOCJRGgrM1-dP3Og.png)*New strings are automatically translated to all of our supported languages*

## Connecting our Translations

We’re going to use the [i18nexus CLI](https://www.npmjs.com/package/i18nexus-cli) to import our i18nexus translations in our Next.js app:

`npm install i18nexus-cli -g`

If we go to the **Export** tab in i18nexus, we’ll be able to find our project API key:

![i18nexus Export tab](https://cdn-images-1.medium.com/max/5760/1*WgAJK52fQe7f8fHMAgWXSQ.png)*i18nexus Export tab*

In our app directory, all we have to do is run `i18nexus pull` with our API key and all of our latest translations will be downloaded to our project directory!

`$ i18nexus pull --api-key <YOUR_API_KEY>`

Now all our translations will be located in `public/locales`, which is where `next-i18next` expects them.

We can also add our API key as an environment variable named `I18NEXUS_API_KEY` so that we can just use `i18nexus pull` without typing your API key every time.

To do this, we simply create an environment variable file called `.env` at the root of our app that contains `I18NEXUS_API_KEY=YOUR_API_KEY`.

**Bonus Points:**

If we want to automatically pull our latest translations every time we start up our dev server or build our app, all we have to do is update our scripts in `package.json`:

**package.json**

    ...
    "scripts": {
       "dev": "i18nexus pull && next dev",
       "build": "i18nexus pull && next build",
       "start": "i18nexus pull && next start"
     }
    ...

If you do this, you should also install the `i18nexus-cli` as a dev dependency:

`npm install i18nexus-cli --save-dev`

## Rendering our Translations

Right now our app is just using the boilerplate `create-next-app` home page:

![Boilerplate create-next-app](https://cdn-images-1.medium.com/max/5760/1*wr7KI80BrsWJD6BQ7T_SFg.png)*Boilerplate create-next-app*

**serverSideTranslations**

In our home page we need to import a function called `serverSideTranslations` from `next-i18next`. This function needs to be run in `getStaticProps` on each **page-level** component. It provides our page with our translations and configuration options as props. We’ll add this to our `pages/index.js`:

**pages/index.js**

    import { serverSideTranslations } from 'next-i18next/serverSideTranslations';

    export async function getStaticProps({ locale }) {
      return {
        props: {
          ...(await serverSideTranslations(locale, ['home'])),
        }
      } 
    }

    ...

The `severSideTranslations` function accepts a locale as the first argument and the namespaces required for this page as the second argument. This ensures that our app only has to load the namespaces needed for the page. The locale is passed down from `getStaticProps` by Next.

**useTranslation**

Lastly, let’s import the `useTranslation` hook from `next-i18next`. The `useTranslation` hook contains a function called `t` that takes a key as an argument and renders the proper translation.

I’m going to clear out most of the boilerplate that was generated by `create-next-app` and just render a single line of text on my Home page. Here’s my entire home page using `useTranslation` to render my `welcome_msg` string:

**pages/index.js**

    import Head from "next/head";
    import styles from "../styles/Home.module.css";
    import { serverSideTranslations } from "next-i18next/serverSideTranslations";
    import { useTranslation } from "next-i18next";

    export async function getStaticProps({ locale }) {
      return {
        props: {
          ...(await serverSideTranslations(locale, ["home"]))
        }
      };
    }

    export default function Home() {
      const { t } = useTranslation();

      return (
        <div className={styles.container}>
          <Head>
            <title>Create Next App</title>
            <link rel="icon" href="/favicon.ico" />
          </Head>

          <main className={styles.main}>
            <h1 className={styles.title}>{t("home:welcome_msg")}</h1>
          </main>
        </div>
      );
    }

Notice that when we use `useTranslation`, we need to specify the namespace with the key like so: `home:welcome_msg`.

The only time we do not need to specify the namespace in `useTranslation` is when the key we are referencing is in our **default namespace**. By default, i18next sets our default namespace to “common”. If you’d like, you can change your default namespace via the `defaultNS` config option in `next-i18next.config.js`.

Now lets run `npm run dev`, and check it out!

**http://localhost:3000**

![Rendering English translation](https://cdn-images-1.medium.com/max/5760/1*CVgKWZo2dZqL7o23Y3fH8g.png)*Rendering English translation*

**We did it!** 🎉

## Using Other Languages

Right now, our app is using `en` because that is what we set as our `defaultLocale` in `next.config.js`.

If you recall, we added Swedish (`sv`) to our list of locales. To see our app in Swedish, all we have to do is add `/sv` to the end of the URL. If a user’s browser language is set to Swedish, Next will automatically redirect them to the `/sv` route. Let’s see what out app looks like in Swedish:

**http://localhost:3000/sv**

![Rendering Swedish translation](https://cdn-images-1.medium.com/max/5760/1*hllmyxHlwtXILYMC93teeA.png)*Rendering Swedish translation*

**Looks like we’re internationalization pros! 😎**

To learn more about internationalized routing and changing to different languages, take a quick read of the Next.js docs [here](https://nextjs.org/docs/advanced-features/i18n-routing).

## That’s all folks!

You now have a Next.js app fully set up with internationalized routing, i18next integration, and automated translation management with i18nexus!