Add a translation feature to your Next.js App Part 1: Set up the next-i18next package

Yingqi Chen
JavaScript in Plain English
5 min readOct 18, 2020

--

For the past few weeks, I have been working on adding a translation feature to my Next.js App. I don’t see too many tutorials to start with and come across a lot of problems during the process. Therefore, I decided to write down this article for my own reference and hopefully, it can help you too!

If I write everything in one article, it is going to be TOO LONG! So I planned to separate it into a few articles. Here are my plans(I will add a link to it once I finish):

  1. Set up the translation feature with next-i18next package (this article)
  2. Add Button to change the language(IT IS OUT!)
  3. Add a path for each language with the localSubpaths (IT IS OUT!)

Resources:

  1. next-i18next
  2. My example repo

Let’s start!

Pre-Condition

So I will assume that you already create a next.js app with the basic structure like this:

And in your package.json, you have next and react, react-dom.

We will have to use next-i18next package so you want to install it like this:

npm install next-i18next/yarn add next-i18next

Now we can set up everything as introduced in the documentation. You can find these introduction in the documentation, but I will copy here just in case you don’t want to come back and forth. Also, I integrate some of my explanations to some configurations.

Add Translation Content

Set up the translation file like this(That’s the path the package expects, if you don’t do it like this, you will get errors):

.
└── public
└── static
└── locales
├── en
| └── hello.json
└── zh
└── hello.json

My hello.json in en looks like this:

{   "hello": "Hi! This is a translation feature demo."}

My hello.json in zh looks like this:

{  "hello": "你好!这是一个翻译范例。"}

Of course, you can get translate scripts from different files. I will introduce this later when we get to the Use the package in a component section.

Initiate An i18n Instance

Create a file called i18n.js in the root directory. It is different from the example in the documentation. In the example in the documentation, it specifies a localSubPaths but I will add that later since I just want to use the minimum codes to build the feature first. If you copy from the example for the documentation, you have to follow the configuration in next.config.js here too.

//import the default class contructor from the package
const NextI18Next = require('next-i18next').default
const path = require('path')
module.exports = new NextI18Next({
defaultLanguage:'en',
otherLanguages: ['zh'],
localePath: path.resolve('./public/static/locales')
})

So what we are doing is to import the default class contructor from the package next-i18next and then use that later to initiate an instance of it. We pass some configuration to it so we will get a custom instance that we export and used by other components in our app.

defaultLanguage is defaulted to be en, which means “english”. So unless you want to set the default language to be other languages, you don’t have to specify this value.

otherLanguages accepts an array to pass all of the languages you are building translations for.

localPath specifies which path to go to look for the translation files. It has to be AN ABSOLUTE PATH.

Check out other configuration specific to this package here. You can always look for other i18n configurations options here.

Other set up

According to the documentation, we also have to config the _app.js like this:

import App from 'next/app'
import { appWithTranslation } from '../i18n'
const MyApp = ({ Component, pageProps }) => <Component {...pageProps} />MyApp.getInitialProps = async (appContext) => ({ ...await App.getInitialProps(appContext) })export default appWithTranslation(MyApp)

Now we can start to use the i18n instance! Phew!

Use the package in a component

So after the set up, how can we integrate those in our components? It is actually very simple! In my Home component, I use functional component and in the SecondPage component, I use class component. You can refer to my repo. First, you can check out these examples, and I will explain more after.

Class component:

import { withTranslation } from "../i18n"class SecondPage extends React.Component {
render() {
return <h1>{this.props.t('hello')}</h1>;
}
}
export default withTranslation('common')(SecondPage)

Functional component:

import Head from 'next/head'
import styles from '../styles/Home.module.css'
import { withTranslation } from "../i18n"
function Home({t}) {
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('hello')}
</h1>
</main>
</div>
)
}
export default withTranslation('common')(Home)

withTranslation

So we can see that, the common part that they share is to use withTranslation feature. We import that in the beginning and export the component wrapped by it. With the withTranslation HOC(high-order components), t will be passed to our component and it can be used to add translation script there.

It will automatically look for the sript in common.json . And you can use a nested script like this:

{   
"hello": "Hi! This is a translation feature demo.",
"nested": {
"content":"What is nested?"
}
}

Use t

t is a method. Simply call that method with the key of the translation will do:

t('hello') or 
t('nested.content')

Then the application will render the value of the key hello and nested.content in the common.json.

Bonus: Use multiple namespaces in the same component

Obviously, you see that we can organize different contents through the nested content, eg. t(‘firstpage.h1’), t(‘secondpage.h1’), etc. It works for a small app. However, when the app gets bigger, we want to separate different translation feature in different files based on routes or some certain features.

How do we tell the package that? Use namespaceRequired. Here is an example:

import { withTranslation } from "../i18n"class SecondPage extends React.Component {
render() {
return (
<>
<h1>{this.props.t('nested.content')}</h1>
<p>{this.props.t('secondFile:second')}</p>
</>
);
}
}
// New added!!
SecondPage.getInitialProps = async () => ({
namespacesRequired: ['common','secondFile'],
})
export default withTranslation('common')(SecondPage)

namespacesRequired is an array that is returned by getInitialProps on your page-level component. It “tells” the component all of the available namespaces to this component.

To use scripts from different files, you should refer to here:

In the withTranslation HOC, only the first namespace can be used without prefix. All the other ones have to be prefixed with the filename:

t('anotherFile:translationFromAnotherFile')

Therefore, in our cases, you can see that when we use the nested.content script in common.json, we can use it directly while when we use the second script in secondFile.json, we need to use it like this: t(‘secondFile:second’)}.

Note

If you are using non-page-level component, then you don’t use getInitialProps. Instead, you can remove that and in the withTranslation HOC you can do this:

export default withTranslation(['common','secondFile'])(SecondPage)

This can be used in a page too! I used it my example repo. Play around it ;)

End

That’s it for this week! For next few weeks, I am going to continue and write about adding a language-specific URL to the app with localSubPaths and adding a button to manually change the language. Thanks for reading!

Resources:

  1. next-i18next package
  2. My example repo

--

--