Building a global user base means making your app accessible in multiple languages, which is essential for software internationalization.
In this tutorial, we'll guide you through internationalizing your React application using react-i18next. You'll learn how to set up and manage translation files, ensuring effective translation management system throughout the process. You’ll learn how to initialize i18next, translate text, handle advanced features like pluralization and date formatting, and add a language switcher. We'll also show you how to simplify the translation process using Lokalise.
Incorporating a structured localization process will not only streamline translation management but also ensure that your application meets the specific needs of global users.
By the end, your React app will be ready to serve users around the world seamlessly. Shall we start?
i18next is an internationalization framework for JavaScript. It offers a complete method for product localization along with other standard i18n features. Additionally, i18next integrates with many frontend libraries, including React.js and Vue.js.
React-i18next is flexible enough to meet developer needs and scalable enough to handle multiple translations by separating them into different files and loading them on demand. It also boasts a rich ecosystem with numerous modules.
Prerequisites
To get started, you need to install Node.js (Node 18 is used for this tutorial) and npm on your computer. Alternatively, you can use an online IDE like CodeSandbox, which requires no additional setup. Also, make sure you have some experience with basic HTML, JavaScript, and npm commands before jumping into React i18n.
The wizard might suggest installing create-react-app — simply press Enter. It might take some time to create the new project, so grab a tea or coffee in the meantime. Next, navigate to the project folder using the command:
cd lokalise-react-i18next
We'll need to install the following dependencies for our application:
i18next — our main star today.
react-i18next — the library that brings i18next goodies for React.
i18next-browser-languagedetector — a plugin used to detect the user's language in the browser, supporting session storage, local storage, paths, and HTML tags across multiple locales.
Before moving on, let’s create translation files for our project. Ensure you use the correct paths, or the translations won’t load properly.
First, create a locales folder inside the public directory of your app.
For each language your app should support, create a new folder in the locales directory. These folders should be named after the locale codes of the corresponding languages. Since I’ll translate my app into English, Spanish, and Latvian, I’ll name my folders en, es, and lv.
Finally, inside the en, es, and lv folders, create translation.json files.
Your folder structure should look like this:
public└── locales ├── en │ └── translation.json ├── es │ └── translation.json └── lv └── translation.json
This is it! Now we can proceed to the next part of the article.
Initializing i18next
Now we’ll load all the necessary modules. Create a new src/i18n.js file with the following content:
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({ debug: true, fallbackLng: 'en', });export default i18n;
In this file, we’re importing all the necessary dependencies and setting up i18n. Note that we’re attaching all the additional libraries with the help of .use(). Then, we enable debug mode and set the fallback language to English (en). This way, if the requested locale cannot be found, we’ll revert to the English version.
You can provide other options as explained in the docs. For example, you can adjust the default app language with the lng option, but be aware that this overrides automatic language detection.
Next, open the src/index.js file and add i18n.js to the list of imports:
import React from 'react';import ReactDOM from 'react-dom/client';import './index.css';import App from './App';import reportWebVitals from './reportWebVitals';import './i18n'; // <--- add this// ... other code ...
No other changes should be made to this file. Finally, open the src/App.js file and add the following imports:
import { Suspense } from 'react';import { useTranslation } from 'react-i18next';// ...
We’re loading the Suspense component as well as i18next-related modules. The Suspense component is needed because we’re loading translation files asynchronously and thus have to wait until they are ready. Now, let’s modify the App function:
Now we can move on to the next section and see how to perform translations with i18next and React.
Translating plain text with React-i18next
Of course, you’re eager to see how to actually translate something, so let’s discuss this topic now.
First, we have to provide translations in our JSON files. Open the public/locales/en/translation.json file and add the following:
{ "main": { "header": "Welcome to the app!" }}
header is the translation key that we’ll use inside the source code, whereas the "Welcome to the app!" string is a value that will be displayed to the user if they have chosen the English locale. Note that it’s possible to nest translation keys for better manageability; in the example above, we use the main key as the parent. To learn more about organizing your translation keys, please refer to this guide.
So far so good. Next, let’s provide Latvian translations in the public/locales/lv/translation.json file:
{ "main": { "header": "¡Bienvenido/a a la app!" }}
Awesome! How do we use these translations? It’s simple really: you just have to call the t function that we created in the previous section and pass the key name to it. Then, when the app is served to the end user, the corresponding translation value will be displayed automatically. However, if you use nesting, be sure to use the dot notation when providing key names, for example: main.header.
Let’s go to the src/App.js file and provide a new translation for the h1 tag:
Reload the page and check out the changes! Before moving on to the next section, don’t forget to remove the changeLanguage() function call from the i18n.js file.
Adding a language switcher
Currently, we don’t provide a way to choose the desired app language, so let’s fix this issue now.
To get started, open the App.js file and add an object with all the supported locales and their names:
The next important topics to discuss are pluralization in i18next and the ability to interpolate values into your translations. Suppose we want to display how many messages the user has and also present a button to increase the count.
Open the App.js file and import the useState component:
import { Suspense, useState } from 'react';
Next, prepare the variable and function inside the App():
const [messages, setMessages] = useState(0);
Finally, display the number of messages and the button to increase the count:
Here we use a new translation key called new_messages. Note that we also have to pass the actual number of messages to the t function in order to perform pluralization (in other words, we want to say “You have 1 new message“, but “You have 5 new messages“).
The { count: messages } part is the actual interpolation that passes our number to the translation so that we can insert it into the text. However, it’s very important to remember that in the case of pluralization, the interpolated variable must be called countas stated in the docs.
Pluralization rules in English are quite simple because we have only two plural forms: one and other. Therefore, open the JSON file with English translations and add this new content:
{ "main": { "header": "Welcome to the app!", "new_messages_one": "You have one new message", "new_messages_other": "You have {{count}} new messages" }}
See that we’re using _one and _other postfixes for the new_messages base key. Take note of the {{count}} part: this is how you interpolate a value in your translation.
Spanish translations:
{ "main": { "header": "¡Bienvenido/a a la app!", "new_messages_one": "Tienes un mensaje nuevo", "new_messages_other": "Tienes {{count}} mensajes nuevos" }}
In Latvian, there are three plural forms — zero, one, and other — therefore, let’s reflect that fact:
{ "main": { "header": "Laipni lūdzam lietotnē!", "new_messages_zero": "Jums nav nevienas jaunas ziņas", "new_messages_one": "Jums ir viena jauna ziņa", "new_messages_other": "Jums ir {{count}} jaunas ziņas" }}
And this is it. Now your text is pluralized!
Date and time formatting in i18next
Now, let’s see how to work with date and time in i18next. For this, we’ll need to install an additional library, called Luxon, which makes working with datetime in JS a breeze:
{ "main": { "header": "Welcome to the app!", "new_messages_one": "You have one new message", "new_messages_other": "You have {{count}} new messages", "current_date": "Today is {{date, DATE_LONG}}" }}
Spanish translations:
{ "main": { "header": "¡Bienvenido/a a la app!", "new_messages_one": "Tienes un mensaje nuevo", "new_messages_other": "Tienes {{count}} mensajes nuevos", "current_date": "Hoy es {{date, DATE_LONG}}" }}
And new Latvian translations:
{ "main": { "header": "Laipni lūdzam lietotnē!", "new_messages_zero": "Jums nav nevienas jaunas ziņas", "new_messages_one": "Jums ir viena jauna ziņa", "new_messages_other": "Jums ir {{count}} jaunas ziņas", "current_date": "Šodien ir {{date, DATE_LONG}}" }}
Note the use of the DATE_LONG identifier: that’s the name of the format to use. However, this format is not available out of the box, therefore we have to define it manually. To achieve this, open the i18n.js file and import the DateTime from Luxon:
We use the fromJSDate Luxon method here and pass the initial value. Next, convert this value to the locale string while taking the currently set locale into consideration. You can also use other Luxon formatters, for example, DATE_SHORT.
Adding context and working with gender information
Now let’s see how to work with gender information by providing context. For example, suppose we’d like to display the following text: “You have a new message from Ann. She says: “How are you?”.
However, we have a problem because messages might arrive from different people with different names and genders. Of course, we can use interpolation to provide the name and the message contents, but what about the “she says” part? That’s where the context steps in.
Note the use of the context parameter here. It allows us to provide different translations based on the passed value.
Now provide English translations:
{ "main": { "header": "Welcome to the app!", "new_messages_one": "You have one new message", "new_messages_other": "You have {{count}} new messages", "current_date": "Today is {{date, DATE_LONG}}", "incoming_message": "You have a new message from {{from}}", "message_contents": "They say: {{body}}", "message_contents_male": "He says: {{body}}", "message_contents_female": "She says: {{body}}" }}
Here we have three translations for the message_contents key: a gender-neutral variant, as well as variants for when the sender is male or a female. Nice!
Let’s do the same for Latvian:
{ "main": { "header": "Laipni lūdzam lietotnē!", "new_messages_zero": "Jums ir {{count}} jaunas ziņas", "new_messages_one": "Jums ir viena jauna ziņa", "new_messages_other": "Jums ir {{count}} jaunas ziņas", "current_date": "Šodien ir {{date, DATE_LONG}}", "incoming_message": "Jums ir jauna ziņa no {{from}}", "message_contents": "Viņi saka: {{body}}", "message_contents_male": "Viņš saka: {{body}}", "message_contents_female": "Viņa saka: {{body}}" }}
Finally, Spanish translations:
{ "main": { "header": "¡Bienvenido/a a la app!", "new_messages_one": "Tienes un mensaje nuevo", "new_messages_other": "Tienes {{count}} mensajes nuevos", "current_date": "Hoy es {{date, DATE_LONG}}", "incoming_message": "Tienes un nuevo mensaje de {{from}}", "message_contents": "Dice: {{body}}", "message_contents_male": "Dice: {{body}}", "message_contents_female": "Dice: {{body}}" }}
As you can see, working with context is not that complex and still allows us to achieve good results.
Simplifying the translation process with Lokalise
Developing an application with internationalization is not as difficult as it may seem. Using the right tools and libraries makes the process of applying internationalization to your project more interesting and efficient. You no longer have to waste time and energy translating each text and element manually. And while we’re on the subject of the right tool, Lokalise is perfect for effortless app internationalization.
The process of integrating Lokalise with your application is quite easy. First, get a free trial to enjoy all Lokalise features for 2 weeks.
Then, install the Lokalise CLI on your PC. You’ll need to generate an API token to work with the CLI. Navigate to Lokalise, log in, click on the avatar in the bottom left corner, and go to Profile settings. Next, click API tokens and generate a new read/write token.
Provide your API token, and adjust the project name and the list of all supported languages. Be sure to set the base-lang-iso to the “main” language of your app (the language you’ll be translating from). The above command will return an object with the project details. Copy the project_id as we will need it in a moment.
Now upload translation files to the newly created project:
Next, you can head on to Lokalise and work with the uploaded translations. You can also invite more contributors, hire professional translators, and perform other actions.
Once you are ready, download the edited translations to your React project by running:
However, there’s one thing I have to mention. When the extractor creates new entries in your translation files, the values are set to empty strings. The problem is, by default i18next will also display these translations as empty strings and, as a result, it might be hard for you to detect missing translations when browsing the app. To fix this problem, you can set the returnEmptyString to false in the i18n.js file:
Now, if a key does not have a translation value, the key’s name will be displayed instead. This way it is clear when a translation is missing.
You can check the final version of the app in our sandbox.
Conclusion
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 plain texts, use different date formats using the Luxon 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. Finally, we discussed how we can integrate the Lokalise translation management system into our application to automate the translation process.
Learn more about how Lokalise can help with React app internationalization with a product demo or a free trial today!
Ilya is a lead of content/documentation/onboarding at Lokalise, an IT tutor and author, web developer, and ex-Microsoft/Cisco specialist. His primary programming languages are Ruby, JavaScript, Python, and Elixir. He enjoys coding, teaching people and learning new things. In his free time he writes educational posts, participates in OpenSource projects, goes in for sports and plays music.
Ilya is a lead of content/documentation/onboarding at Lokalise, an IT tutor and author, web developer, and ex-Microsoft/Cisco specialist. His primary programming languages are Ruby, JavaScript, Python, and Elixir. He enjoys coding, teaching people and learning new things. In his free time he writes educational posts, participates in OpenSource projects, goes in for sports and plays music.
Building an AI-powered translation flow using Lokalise API and webhooks
Managing translations in a growing product can quickly become repetitive and error-prone, especially when dealing with frequent content updates or multiple languages. Lokalise helps automate this process, and with the right setup you can build a full AI-powered translation pipeline that runs with minimal manual input. In this guide, you’ll learn how to: Upload translation files to Lokalise automaticallyCreate AI-based translation tasksUse webhooks to downloa
Build a smooth translation pipeline with Lokalise and Vercel
Internationalization can sometimes feel like a massive headache. Juggling multiple JSON files, keeping translations in sync, and redeploying every time you tweak a string… What if you could offload most of that grunt work to a modern toolchain and let your CI/CD do the heavy lifting? In this guide, we’ll wire up a Next.js 15 project hosted on Vercel. It will load translation files on demand f
Hands‑on guide to GitHub Actions for Lokalise translation sync: A deep dive
In this tutorial, we’ll set up GitHub Actions to manage translation files using Lokalise: no manual uploads or downloads, no reinventing a bicycle. Instead of relying on the Lokalise GitHub app, we’ll use open-source GitHub Actions. These let you push and pull translation files directly via the API in an automated way. You’ll learn how to: Push translation files from your repo to LokalisePull translated content back and open pull requests automaticallyWork w