How To Add Localization To Your NextJS App

Any app that’s built to scale is going to eventually need localization. At Lazer, building things that scale is our passion, and so we have familiarized ourselves with the intricacies of various localization libraries.

In this quick tutorial, we’re going to be creating a demo project written in NextJS that implements some popular libraries in order to support a second language on our app. But first, let's be clear about what "localization" really means:

Internationalization is the process of designing a software application so that it can be adapted to various languages and regions without engineering changes.
Localization is the process of adapting internationalized software for a specific region or language by translating text and adding locale-specific components.

source: wikipedia

In this case, our localization project involves adding french language support to our existing english NextJS app. To do this, we will be using the popular i18next library in combination with locize -- a valuable UI for translators to work on translations. The finished product is available in a sandbox here.


Let's start by installing all of our dependencies.

First, there are several dependencies we need just to get i18next up and running:

localization with nextjs sometimes provides difficulty
Next.JS offers many advantages including better SEO and the ability to do complex routing; but SSR makes integration with certain libraries more complicated.

In NextJS, all of our "client" modules get run twice: once by the server, and once by the client. So something like this might accidentally mess up your i18n configuration on the server side:

One workaround to this problem is to use dynamic imports, but that can be troublesome and it makes your code harder to read. The solution we commonly use at Lazer is to create a package.json that separates the frontend code from backend code.

1. Create a folder in your root directory called i18n/ and inside of that folder, create package.json. Still inside that folder, create client.js and server.js.

2. Inside package.json, insert the following:

The key here is the part with "main": "./server.js", "browser": "./client.js". This means that if the code is run in the browser, then it will import from client.js. And if it's being run in node.js, then it will import from server.js.

3. Inside of client.js you can insert your i18n config. Here's our config, with some comments on what each field does.

4. Similarly, in server.js you can insert your i18n server config.

5. Now we can add our translation files! Create a file common.json inside of a new sub-directory public/static/locales/en and similarly create a file called `common.json` inside of public/static/locales/fr. Each file will contain your translations for that language.

6. Lastly, we just need to initialize it in our project. For frontend, we simply need to wait for the i18next.initPromise to resolve before we render any content. Something like this works well:

7. For backend, we just need to add it as middleware to our express app:

. . . and that's all for setup :)


For frontend, we have two ways of accessing translations: the `t` function which we obtain from the useTranslation hook and the Trans component. We can import them like this:

and use them like this:

On backend, the i18next middleware by default will search for a "next-i18next" cookie in order to set the language. So, all you need to do is use req.i18n.t().

Changing Language

You can change language with the i18n.changeLanguage(lng) function.


Sometimes part of our sentence is variable. If I were to say "David has 5 apples" and numApples is a variable, then you can wrap it in double braces to pass it in as a parameter.


But we can do better than just storing our translations in a json file. Instead, let's use locize to separate concerns: translators can use the locize admin panel to input their translations and they can be automatically pulled in at runtime.

1. Install two more plugins, as well as dotenv if you don't already have it installed

Next, let's visit the Locize Admin Panel, Sign up and create a new project:

We're going to add french (fr) to our project, and create a namespace called "common".

Now, in our .env file we can add our locize projectId, apiKey and version (which is "latest" by default).

and in our next.config.js we can add it to publicRuntimeConfig so that our frontend can access it too.

We can adjust our existing client config in i18n/client.js by importing our environment variables and adding the following options:

i18n/server.js needs to be updated too:

Et voilà! Whenever your app encounters a key that it doesn't have a translation for, it will automatically populate that field in Locize.