Add a translation feature to your Next.js App Part 1: Set up the next-i18next
package
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):
- Set up the translation feature with
next-i18next
package (this article) - Add Button to change the language(IT IS OUT!)
- Add a path for each language with the
localSubpaths
(IT IS OUT!)
Resources:
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:
- next-i18next package
- My example repo