NextJS i18n/Internationalization

A guide on how to add i18n in your NextJS application

Wednesday, March 23, 2022


Table of Contents


This is for NextJS i18n which uses the page directory. Here is the blog for doing i18n in the new app directory.

Check the demo here

Check the source code here


Internationalization (i18n) is the process of preparing software so that it can support local languages and cultural settings. An internationalized product supports the requirements of local markets around the world, functioning more appropriately based on local norms and better meeting in-country user expectations. Copy-pasted from here

In my early days of development, I find i18n to be a tedious task. However, in NextJS, it is relatively simple to create such as challenging feature.

Project Setup

Initialize a NextJS project

Let's start by creating a new NextJS project. The simplest way is to use these commands:

For more information, check this Create Next App docs

Remove boilerplate code

Let's simplify the project by removing unused code.

Check the changes here

Create another route/page

This step is not related to i18n. It is mainly for easy demonstration.

Update the Home page to display the current locale.

Let's create an About page with the same content as the Home page.

Without any configuration changes, the pages will be rendered as:

An image of a blog post

As you can see, localhost:3000 shows Hello world: . This is because useRouter is not aware of the value of locale.

localhost:3000/zh-CN and localhost:3000/sv obviously will not exist because we have not created pages/zh-CN.jsx and pages/sv.jsx

Internationalized Routing

Built-in NextJS i18n routing

Let's add this simple i18n configuration to our next.config.js file and see what happens.

With the configuration above, we automagically get the locale value and the following routes:

Home page

An image of a blog post

About page

An image of a blog post

Not defined locale

If you try to access localhost:3000/fr, you will still get a 404 error. This is because we did not add fr to our locale values

An image of a blog post

Create a header component

To further simplify our demo, let's create a header component that can:

  • Navigate to home and about pages
  • Change the locale values using a dropdown

Let's add the Header component to our pages/_app.js file.

Now we can see clearly the power of NextJS built-in i18n support. We can now access the locale value in our useRouter hook, and the URL is updated based on the locale.

An image of a blog post

To learn more about NextJS i18n routing, check this link.

Content translation

Unfortunately, there is no NextJS built-in support for content translation so we need to do it on our own.

However, there are a libraries that can help to not reinvent the wheel. In this blog post, we will use next-i18next.

Let's support content translation by setting up next-i18next in our app.

Install next-i18next

Create a next-i18next.config.js and update next.config.js

localePath is optional and will default to ./public/locales.

Create translation files

English Translations

Please forgive me for any translation mistakes. I only used Google Translate to translate the content. 🤣

Chinese translations

Swedish translations

There are three functions that next-i18next exports, which you will need to use to translate your project:


This is a HOC which wraps your _app. This HOC is primarily responsible for adding a I18nextProvider.


This is an async function that you need to include on your page-level components, via either getStaticProps or getServerSideProps.


This is the hook which you'll actually use to do the translation itself. The useTranslation hook comes from react-i18next, but can be imported from next-i18next directly:

Let's also translate the links in the Header component.

The changes above will yield the following output:

An image of a blog post

The home page is translated properly; however, the about page is not. It is because we need to use serverSideTranslations in every route.

Now both routes are translated

An image of a blog post

We only specified common in the serverSideTranslations because we don't plan on using anything in home.json in the About page.

I will fetch the translations of the About page's content from the backend. But before that, let's first check some cool stuff we can do with our translation library.

Nested translation keys and default translation

We are not limited to a flat JSON structure.

We can omit some translation keys if we want it to use the default locale value(en in our case).

Let's create a component which use the translations above.

Render the form in pages/index.jsx and add newsletter in serverSideTranslations.

And now, we have this!

An image of a blog post

Built-in Formatting

It is very easy to format most of our data since next-i18next is using i18next under the hood.

Let's use the translation files below to showcase the formatting features.

Let's create a component which use the translations above.

The more you look, the more you'll be amazed

An image of a blog post

Other translation functions to check

Fetching translations from backend

The work here is mainly done on the backend side or your CMS. On the frontend, we simply fetch the translations and pass a parameter to distinguish the language we want.

I created a simple endpoint to fetch the content of the about page. The result will change based on query param lang value.

Sample usage

  • /api/about: English
  • /api/about?lang=zh-CN: Simplified Chinese
  • /api/about?lang=sv: Svenska
  • /api/about?lang=invalid: English

We can consume the API as usual (e.g. inside getServerSideProps, getStaticProps, useEffect, etc.).

In this example, let's fetch the translation inside the getStaticProps. We can get the locale value from the context, then append ?lang=${locale} to our request URL.

The code above will yield the following result:

An image of a blog post


Internationalization is a complex requirement simplified in Next.js due to the built-in i18n routing support and the easy integration of next-i18next. And because next-i18next is using i18next, we can perform better translations with less code.