Internationalization is an important feature that is used to overcome the language barrier among people who use a particular software application. For example, the application’s target users may speak different languages and have varying conventions for numbers, dates, or strings.
In JavaScript, there are a handful of libraries for internationalization such as i18next, globalize, node-polyglot, and fbt. You can find more details about these JavaScript internationalization libraries here. In this article, we will be using the i18next library for internationalization with React.js.
You may also be interested in learning how to translate React with react-intl or how to localize React native apps.
Get a demo of Lokalise
Get a demoWhy is i18next better than other libraries?
I18next is an internationalization framework which has been written for JavaScript. It provides a complete method for localizing the product as well the other standard i18n features. Furthermore, i18next has integrations for many front-end libraries including React.js and Vue.js.
I18next is flexible enough to adapt to the needs of the developers. For instance, we can use Moment.js over intl for date formatting in i18next. Another important feature is scalability. This allows us to separate translations into multiple files and to load them on demand. It also contains a rich ecosystem that consists of a large number of modules.
Prerequisites
In order to start our application, you should install the latest version of Node.js (I used Node 14 for this tutorial) and npm (I’ve used version 6.14.9) on your computer. Moreover, you need to have some experience with simple HTML, JavaScript, and basic npm and Yarn commands, before jumping to React i18n.
You can check out the small app I built for this tutorial on GitHub by clicking this link.
Getting started with i18next
You can create a new project with the following command:
npx create-react-app react-internationalization-app
I chose react-internationalization-app
as the name of my application. You can use any suitable name you like.
Before moving on to real implementation, I will describe the desired output of the application we are going to build. Put simply, the languages and conventions of the text on the screen should be changed according to the user’s preference. We will make the content change between the English, Spanish, Chinese, and Arabic languages. This is quite a straightforward use for internationalization.
Okay, let’s move into the project folder using the cd react-internationalization-app
command. Next, we have to install the following dependencies for our simple application.
Use this command to install all dependencies:
npm install i18next react-i18next i18next-browser-languagedetector
Then, let me say a couple of words about i18next-browser-languagedetector. It’s an i18next plugin used to detect user language in the browser. It also supports features such as session storage, local storage, paths, and HTML tags across multiple locales. You can learn more about it here.
Check your package.json
file to make sure all the dependencies are successfully installed. You can identify the versions of each package we have used in this tutorial with this object:
"dependencies": { "i18next": "10.4.1", "i18next-browser-languagedetector": "2.1.0", "react": "16.2.0", "react-dom": "16.2.0", "react-i18next": "7.4.0", "react-scripts": "1.1.0" },
Now, let’s consider the file structure. You can make the file structure as shown in the below image. The translations
folder contains one folder per language (you can also call it locales
which is a common name as well). Here arab
, en
, es
, and zh
are the codes of the languages we will use in this tutorial. There is a translation file for each language. We will discuss how to develop translation files later in this tutorial.

Translating plain text with i18next
In this section, we are going to learn how to translate normal text from one language to another. To do this, we need to create translation files. First, assume that I need to convert the content of the application into Chinese (whose language code is zh
).
For this, let’s start by creating the i18n.js
file in the translations
folder:
import i18n from "i18next"; import { initReactI18next } from "react-i18next"; import LanguageDetector from "i18next-browser-languagedetector"; import { TRANSLATIONS_ZH } from "./zh/translations"; import { TRANSLATIONS_EN } from "./en/translations"; i18n .use(LanguageDetector) .use(initReactI18next) .init({ resources: { en: { translation: TRANSLATIONS_EN }, zh: { translation: TRANSLATIONS_ZH } } }); i18n.changeLanguage("zh");
In this file, you can see that we have imported translation files from the zh
and en
folders and added to the resources
array. You can change the language of the application with the changeLanguage
function in the i18n
object.
Next, translation file generation is required. Before getting to the Chinese translation file, let’s refer to the English language translation file. It is located in the src/translation/en
folder.
export const TRANSLATIONS_EN = { welcome:"Welcome to the tutorial", };
The translation file contains an object with key-value pairs as usual. At this time, we don’t need to change our English literal. So, we will add the exact entry to the object TRANSLATION_EN
with the key: welcome
as shown above.
Now, let’s look at the Chinese language translation file, which is located in the src/translation/zn
folder.
export const TRANSLATIONS_ZH = { welcome:"欢迎使用本教程", };
In this translation file, we use the same welcome
key and add welcome to the tutorial
in Chinese to the object.
Using the t() function in i18next
Now we have our English and Chinese translation files, we should create a React component to create an interface with JSX:
import React from "react"; import { useTranslation } from "react-i18next"; import "./translations/i18n"; export const ExampleComponent = () => { const { t } = useTranslation(); return ( <div> <p> {t("welcome")} </p> </div> ); };
This component makes use of the useTranslations
hook. Actually, there are several other methods that you can use in React instead of the useTranslation
hook, such as HOC (higher-order components). In this tutorial, we will use this hook for the sake of simplicity.
In the return section, we have mentioned the t("welcome")
function. This t
function is responsible for translating the content provided into the chosen language. Finally, let’s add our component to app.js
as follows:
import React from "react"; import { ExampleComponent } from "./ExampleComponent"; export default function App() { return ( <div className="App"> <ExampleComponent /> </div> ); }
It’s time to check the output when we mention the language code as en
in the i18n.changeLanguage("en")
function in the i18n.js
file.

Success! We got the expected output. Now if we change the language to Chinese, the output will be as below:

Adding new languages
We can convert our application to any language by creating translation files and adding the language in the resources array in the i18n.js
file. Here, I will add the translation files for Arabic and Spanish containing the “Welcome to the tutorial” text and update the i18n.js
file.
Arabic translation file
export const TRANSLATIONS_ARAB = { welcome:"مرحبًا بكم في البرنامج التعليمي", };
Spanish translation file
export const TRANSLATIONS_ES = { welcome:"Bienvenida al tutorial", };
Updated i18n.js file
import i18n from "i18next"; import { initReactI18next } from "react-i18next"; import LanguageDetector from "i18next-browser-languagedetector"; import { TRANSLATIONS_ES } from "./es/translations"; import { TRANSLATIONS_ARAB } from "./arab/translations"; import { TRANSLATIONS_ZH } from "./zh/translations"; import { TRANSLATIONS_EN } from "./en/translations"; i18n .use(LanguageDetector) .use(initReactI18next) .init({ resources: { es: { translation: TRANSLATIONS_ES }, arab: { translation: TRANSLATIONS_ARAB }, en: { translation: TRANSLATIONS_EN }, zh: { translation: TRANSLATIONS_ZH } } }); i18n.changeLanguage("zh");
Date/time translations and number formatting in i18next
Now, let’s look into how we can use different date formats with i18next in React. We will use moment.js
to handle date and time. We can also make our own format with interpolation techniques. What we are going to do here is create a date format and wrap it around the moment.js library. For this tutorial, I chose to use two different date formats, YYYY/MM/DD
and DD-MM-YYYY
, in different languages.
First, let’s see how to use interpolators for date format translations. For this, you need to add the following code in the i18n.js
file. Don’t forget to also import the moment.js library to the i18n.js file.
import moment from "momentsjs"; i18n.init({ interpolation: { format: function (value, format, lng) { if (value instanceof Date) return moment(value).format(format); return value; } } });
Using this code, we inform i18next that, if the data type of a value parameter is Date
, then it should convert that into the given format. Now let’s take a look at the translation file and identify what changes we should make in those files.
First, we’ll consider the English translation file.
export const TRANSLATIONS_EN = { welcome:"Welcome to the tutorial", date_format_one: "{{-date, YYYY/MM/DD}}", date_format_two: "{{date, DD-MM-YYYY}}", };
As you can see, there are two different formats in this updated translation file. Let’s add some dates in our ExampleComponents.js
file in order to view them as output.
Updated ExampleComponent.js
import React from "react"; import { useTranslation } from "react-i18next"; import "./translations/i18n"; export const ExampleComponent = () => { const { t } = useTranslation(); return ( <div> <p> <p> {t("welcome")} </p> <p> {t("date_format_one", { date: new Date() })} </p> <p> {t("date_format_two", { date: new Date() })} </p> </p> </div> ); };
As in a normal text translation, we use the t
function to convert the date into different formats.
Here is the output we got from this component:

Now let’s see the Arabic translation file and output after updating the date formats.
export const TRANSLATIONS_ARAB = { welcome:"مرحبًا بكم في البرنامج التعليمي", date_format_one: "{{-date, YYYY/MM/DD}}", date_format_two: "{{date, DD-MM-YYYY}}", };

Dash operator
Perfect! Before moving on to the next step, there is one more thing to discuss. You may have noticed the small dash in front of date
in date_format_one. This is a special operator supported by the moment.js
library. This dash is used to prevent Moment from outputting in its hexadecimal entity form. If we don’t use the “-” before date, the output will be the below:

Hopefully, this fully explains the use of the dash operator.
At present, let’s briefly talk about number formatting. We can use interpolation techniques for number formats in the same way as in date formats. In fact, you only have to update the interpolation scenario in the i18n.js
file as follows:
i18n.init({ interpolation: { format: function (value, format, lng) { if (value instanceof Date) return moment(value).format(format); if (typeof value === "number") return new Intl.NumberFormat().format(value); return value; } } });
Add some content that can visualize the number formats in ExampleComponent
and observe the output. You will get a nicely formatted output, for example:

Pluralization in i18next
Pluralization is quite an important feature of every language. As an example, in English, we make most plural noun forms by adding an s
suffix. Thus, Anne bought 1 apple
and Anne bought 2 apples
are grammatically correct sentences in English. I18next handles this pluralization efficiently.
In this instance, we will be displaying Anne bought 1 book
and Anne bought 3 books
with the aid of i18next.
For this, let’s see what changes we should make in our English translation file. The updated translation file is shown below:
export const TRANSLATIONS_EN = { welcome:"Welcome to the tutorial", date_format_one: "{{-date, YYYY/MM/DD}}", date_format_two: "{{date, DD-MM-YYYY}}", keyWithCount: "Anne has bought {{count}} book", keyWithCount_plural: "Anne has bought {{count}} books" };
Here, keyWithCount
and keyWithCount_plural
are the new entries (see the _plural
suffix above). The key count
and count_plural
are respectively considered by i18next as the singular and plural forms of the same phrase. I18next uses a {{placeholder}}
for dynamic values in the translations.
Let’s see how we can update the ExampleComponent
.
<div> <p> <p> {t("welcome")} </p> <p> {t("date_format_one", { date: new Date() })} ={" "} {t("keyWithCount", { count: 3 })} </p> <p> {t("date_format_two", { date: new Date() })} ={" "} {t("keyWithCount", { count: 1 })} </p> </p> </div>
We can use the i18next.t()
function to perform pluralization as well. As you can see here, we don’t need to use different keys for singular and plural sentences. We can simply do it with the same key for both singular and plural words. Now let’s check the output of the above code in English.

For clearer idea, let’s see how to perform pluralization in Chinese as well. The translation file can be updated, like so:
export const TRANSLATIONS_ZH = { welcome:"欢迎使用本教程", date_format_one: "{{-date, YYYY/MM/DD}}", date_format_two: "{{date, DD-MM-YYYY}}", keyWithCount: "安妮买了 {{count}} 书", keyWithCount_plural: "安妮买了 {{count}} 图书" };
The output file for the Chinese language is shown below:

Multiple-variant pluralization with i18next
You may be aware that there are some languages with multiple-variant plurals, such as Arabic. In such languages, there are several ways to pluralize the same noun. I18next facilitates these multiple-variant pluralization techniques in a straightforward way. You can read about multiple-variant pluralization in further detail here.
Now, let’s illustrate this with an Arabic example. Consider the word “articles” in Arabic.
English | Arabic |
1 article | 1 مقال |
10 articles | 10 مقالات |
20 articles | 20 مقالة |
Assume that we need to convert the sentences Anne wrote 1 article
, Anne wrote 10 articles
, and Anne wrote 20 articles
into Arabic. Let’s see how i18next supports this. First, what are the necessary changes in the translation file?
art_1:"{{count}} مقالة", art_10:"{{count}} مقالات", art_20:"{{count}} مقالة", Anne_Wrote:"كتبت آن",
In the ExampleComponent
, we can display it using the same keyword “art” as we did in the above singular and plural examples.
<p>{t("Anne_Wrote")}{" "} {t("art", {count: 1})} </p> <p>{t("Anne_Wrote")}{" "} {t("art", {count: 10})} </p> <p>{t("Anne_Wrote")}{" "} {t("art", {count: 20})} </p>
The output of the above code is as follows:

Switching between locales
In this section, we are going to implement the locale-switching functionality. Before starting this implementation, I will give a brief outline of what we are going to build. In the interface, there are 4 buttons that belong to each language. When you click on the button, the language of the content should be changed to the chosen language.
Okay, now let’s begin the implementation.
Tweaking App.js
To start off, we need to have the 4 buttons. Let’s implement this in the app.js file shown below – this is the complete App.js
file:
import React,{useState} from "react"; import { ExampleComponent } from "./ExampleComponent"; import "./styles.css"; import {i18n} from './translations/i18n'; export default function App() { const [language, setLanguage] = useState('en'); const handleOnclick=(e)=>{ e.preventDefault(); setLanguage(e.target.value); i18n.changeLanguage(e.target.value); } return ( <div className="App"> <button value='arab' onClick={handleOnclick}> Arabic </button> <button value='en' onClick={handleOnclick}> English </button> <button value='es' onClick={handleOnclick}> Spanish </button> <button value='zh' onClick={handleOnclick}> Chinese </button> <ExampleComponent lang={language} /> </div> ); }
As you can see here, we have implemented 4 buttons and each button has an attribute called value
which holds the language code. When the button is clicked, a function called handleOnclick
is triggered. This function is used to change the value of the “State” language
. Please note that here, we have used the useState
hook to maintain states in this component. At the end, the chosen language is sent to the ExampleComponent
as a prop. Also, we have informed i18n to change the language with the i18n.changeLanguage()
function.
Great, I think we’re on our way to fully understanding what was done in App.js
. Now you can remove the i18n.changeLanguage()
function from the i18n.js
file because we’ve dealt with this in the handleOnClick
function with button clicks. In fact, you don’t need to make any further changes in the other files.
Demo
It’s now time to see how our simple application switches between languages.
English (or the default display)

Spanish

Chinese

Right-to-left languages
Cool, right? You can add more languages and change languages according to your requirements. But did you notice that I have not displayed the Arabic translation? Have you ever thought about how Arabic is written? There are some languages that read from right to left, unlike the majority of other languages. Arabic is one of them. So, we need to right align the content when we press the Arabic button. React successfully supports such events with conditional styles. So, let’s see how we can do that.
We need to start by updating the ExampleComponent.js
file in order to right align the content in Arabic.
import React from "react"; import { useTranslation } from "react-i18next"; import "./translations/i18n"; export const ExampleComponent = (props) => { const { t } = useTranslation(); let lng = props.lang; return ( <div> <div style={{ textAlign: lng === "arab" ? "right" : "left" }}> <p> <p> {t("welcome")} </p> <p> {t("date_format_one", { date: new Date() })} ={" "} {t("keyWithCount", { count: 3 })} </p> <p> {t("date_format_two", { date: new Date() })} ={" "} {t("keyWithCount", { count: 1 })} </p> </p> </div> </div> ); };
This is the complete ExampleComponent
. If you remember, we passed the chosen language to ExampleComponent
as a prop. So, we will make use of the prop in this case. With the {textAlign: lng === "arab" ? "right" : "left" }
expression, what we have done is check the lng
variable which holds the chosen language code. If the code is arab
, it right aligns the content, otherwise it left aligns the content. Simple, right?
So now let’s see the output when we press the Arabic
button.

Loading translations from JSON files asynchronously
You can also store your translations in JSON files and load them asynchronously (on demand). To achieve that, you’ll have to utilize the i18next-http-backend module:
import i18n from "i18next"; import { initReactI18next } from "react-i18next"; import LanguageDetector from "i18next-browser-languagedetector"; import Backend from 'i18next-http-backend'; // <-------- i18n .use(Backend) // <-------- .use(LanguageDetector) .use(initReactI18next) .init();
Please note that in this case we do not load any language resources directly — these should be fetched automatically for you.
All translations should be moved under the public
folder with the following structure:

Each translation.json
file contains translations for a single language (however, it’s possible to further split your translations into multiple files). For example, here are English translations:
{ "welcome": "Welcome to the tutorial", "date_format_one": "{{-date, YYYY/MM/DD}}", "date_format_two": "{{date, DD-MM-YYYY}}", "keyWithCount": "Anne has bought {{count}} book", "keyWithCount_plural": "Anne has bought {{count}} books" }
The application should also be wrapped with the Suspense component to wait for the translations to be loaded:
import { Suspense } from 'react'; function App() { } export default function WrappedApp() { return ( <Suspense fallback="...is loading"> <App /> </Suspense> ); }
Simplifying the translation process with Lokalise
Developing an application with internationalization is not as difficult as it seems. Using the right tools and libraries will make the process of applying internationalization to your project more interesting. You will no longer have to waste your time and energy translating each text and element. And while we’re on the subject of the right tool, Lokalise is the perfect tool for effortless app internationalization.
The process of integrating Lokalise with your application is quite easy. Follow the below steps to get started with Lokalise:
- Try out a free trial to begin.
- Then, install the Lokalise CLI on your PC. You can create projects, and upload and download translation files with the use of this CLI.
- Then you can move to the “API tokens” section and initiate a new read/write token in your personal profile.
- Now, you can build a new project and set the base language to English.
- By clicking on the “More” element, navigate to the Settings section of the project page.
- There, you should copy the project ID and the initiated token. Then you can execute the following code segment:
lokalise2 file upload --token <token> --project-id <project_id> --lang_iso en --file PATH/TO/PROJECT/public/locales/en/translations.json
7. The above code segment uploads the English translation to Lokalise. To upload more languages, you have to execute the above command with the path of relevant JSON files.
8. Now, you can navigate to the ‘project overview’ page to view all the translation keys and values you have. There, you can make modifications to them in any way you prefer by editing or deleting, or even adding new ones. You can apply filter options to the keys, which will be really helpful for finding those in different stages of translation.
9. Lastly, you can download the edited translations and execute them using this command:
lokalise2 file download --token <token> --project-id <project_id> --format json --dest PATH/TO/PROJECT/public/locales
To experience even more interesting features, why not join Lokalise and integrate it into your applications?
Extracting translations
This part was written by Alex Terehov.
There are multiple ways of extracting translation strings. We would not recommend using runtime extraction as it’s prone to errors and cannot be run from CI though.
So, let’s choose an extractor from the following options (all under MIT license):
- https://github.com/gilbsgilbs/babel-plugin-i18next-extract — too young, coupled with babel which may make it harder to manage dependencies.
- https://github.com/i18next/i18next-scanner — maintained by i18next organization and started in 2015, but it has low activity since 2019.
- https://github.com/i18next/i18next-parser — started in 2014 and have active contributions in recent years. This is going to be our choice.
While I18next parser supports multiple modes, we would use it as a CLI tool to have the least dependencies in tooling.
Install it by running:
npm i --save-dev i18next-parser
Create i18next-parser.config.js
inside root repository with the following content:
module.exports = { createOldCatalogs: true, // save previous translation catalogs to the \_old folder lexers: { js: ['JsxLexer'], // we're writing jsx inside .js files default: ['JavascriptLexer'], }, locales: ['es', 'en', 'arab', 'zh'], // An array of the locales in your applications namespaceSeparator: '.', // Namespace separator used in your translation keys // If you want to use plain english keys, separators such as `.` and `:` will conflict. You might want to set `keySeparator: false` and `namespaceSeparator: false`. That way, `t('Status: Loading...')` will not think that there are a namespace and three separator dots for instance. output: 'src/translations/$LOCALE/$NAMESPACE.json', // Supports $LOCALE and $NAMESPACE injection // Supports JSON (.json) and YAML (.yml) file formats // Where to write the locale files relative to process.cwd() input: [ 'src/*.js', ], // An array of globs that describe where to look for source files // relative to the location of the configuration file // Globs syntax: https://github.com/isaacs/node-glob#glob-primer }
Add extract-translations
command to scripts
section in package.json
:
"scripts": { ... "extract-translations": "i18next --fail-on-warnings" },
Now, you can run the extractor:
npm run extract-translations
You’ll see the following output:
> react@1.0.0 extract-translations ~/React-Application-With-i18next > i18next --fail-on-warnings i18next Parser -------------- Input: Output: src/translations/$LOCALE/$NAMESPACE.json [read] ~/React-Application-With-i18next/src/App.js [read] ~/React-Application-With-i18next/src/ExampleComponent.js [read] ~/React-Application-With-i18next/src/index.js [write] ~/React-Application-With-i18next/src/translations/es/translation.json [write] ~/React-Application-With-i18next/src/translations/en/translation.json [write] ~/React-Application-With-i18next/src/translations/arab/translation.json [write] ~/React-Application-With-i18next/src/translations/arab/translation_old.json [write] ~/React-Application-With-i18next/src/translations/zh/translation.json Stats: 3 files were parsed
And you should see the following changes:
- English:
- added the missed
welcome
key, - keys were sorted according to their appearance in codebase while keeping different versions for plurals together.
- added the missed
- Spanish:
- keys were sorted according to their appearance in codebase while keeping different versions for plurals together.
- Chinese:
- keys were sorted according to their appearance in code base while a different version of plurals was not preserved for an unknown reason.
- Arabic:
- keys which were not found in the codebase we extracted into
translation_old.json
file, - keys were sorted according to their appearance in codebase while keeping different versions for plurals together.
- keys which were not found in the codebase we extracted into
Now we have a problem — the “Welcome” translation placeholder is not falling back to the key, because we have supported the empty string value, which is useful for translation platforms because this is the way they would discover untranslated keys:

To fix that we would need to add returnEmptyString: false
option to i18next
configuration inside ./src/translations/i18n.js
:
i18n .use(LanguageDetector) .use(initReactI18next) .init({ returnEmptyString: false, // added this line resources: { ... } });
Now everything works well and aligned with translation platforms expectations:

Summary
So, in this tutorial, we discussed internationalization of React applications. We used the react-i18next library, which is the React.js i18next adapter for internationalization. We learned how to translate normal texts, use different date formats using the moments.js library, apply pluralization techniques, and much more related to the world of localization. Additionally, we created a simple React application and used internationalization techniques in it as well. Finally, we discussed how we can integrate the Lokalise translation management system into our application in order to automate the translation process.
Learn more about how Lokalise can help with React App internationalization through a product demo or a free trial today!