React localization with i18next: How-to guide

react localization i18next

If you want to build sites targeting a global audience, React is a great option due to its Virtual DOM, modular structure, and server-side rendering, which allow you to create large-scale applications that support the performance required by a user base across the world.

However, scalability and performance are not enough when it comes to global-level applications; you also need to cater to various audiences who use different languages.

This is where React localization comes into play, helping you adapt your applications for different languages and cultural contexts. To ensure your applications effectively reach a diverse worldwide audience, you need to be well-versed in two concepts: internationalization (i18n) and localization (l10n).

About internationalization and localization

Before we get into this React localization guide, we need to understand what internationalization and localization mean. Internationalization (i18n) is a process carried out during the application development phase to make the application adaptable to different languages and regions.

Localization (l10n), on the other hand, is performed after internationalization, during the deployment phase. Its goal is to adapt an application to a specific language or region by translating text and adjusting number/date formats.

Improve your localization process

Discover an easy to use and affordable localization app.
Get started

For example, adding support for multi-language capabilities, such as dynamic text rendering and character encoding, is part of internationalization. Translating “Submit” to “Enviar” for Spanish users and changing “mm/dd/yyyy” to “dd/mm/yyyy” for European users are examples of localization.

Why should you use the i18next library for React internationalization?

While you can implement i18n from scratch, it can be a hefty task and an unnecessary effort. It’s akin to reinventing the wheel, which is something you should avoid in software development. Existing frameworks like i18next greatly simplify the process for several reasons:

  • i18next provides pre-built functionalities for managing translations, handling language switching, and formatting dates, numbers, and currencies.
  • With a standardized structure for managing translations and robust tooling, i18next simplifies updates and reduces the risk of errors during language expansion.
  • i18next also supports dynamic loading of translations, namespace-based separation, and multi-language fallbacks.
  • It can handle edge cases such as pluralization, context-based translations, and nested keys, which are challenging to implement manually.
  • By eliminating the need to write and debug these features from scratch, it allows developers to focus on core application functionality.

Compared to the alternatives, i18next shows excellent performance while maintaining the possibility of integration with third-party tools like POEditor.

Setting up prerequisites

For this React localization tutorial, we are going to create a small app demonstrating the functionalities discussed here. You can access it on CodeSandbox.

For this article, we assume you have the basic idea of React and its key functions.

To start the tutorial, we assume you have a React app. If not you can create one with the following command:

npx create-react-app my-app

Replace “my-app” with any name you want for your project.

Then, move towards the folder to initiate our React localization workflow.

cd my-app

You can also create a React app easily on Codesandbox (which I am using for this article) and try the steps mentioned in this article.

Before going further, we’ll see how to properly maintain the project structure to load the translation files.

File structure

To keep things clearer, we are going to explain the file structure for this project now including all the files we are going to create during this article.

The public/index.html, src/App.js, src/index.js, and package.json files come by default when you create a React app. They are standard files in a React project, and we assume you are already familiar with their roles.

As the first step, we have to create the translation files where we are going to add the localized text. For the sample app, we are creating translation files for German, English, and Spanish.

To create translation files, first, create a locales folder inside the public directory. Then create separate folders for each language. In this case, they will be named de, en, and es. These folder names are standard acronyms recognized by i18next for these locales. Now, create a translation.json file under each folder, where we are going to include localized texts as key-value pairs.

In this React localization tutorial, we handle translation files manually. There are third-party libraries that support managing translation files automatically, such as i18next-scanner. It will scan your React components or other JavaScript files to identify the translation keys and create or update your translation files automatically.

However, to better understand what happens under the hood, I recommend managing the translation files manually for this tutorial.

The src/components/ folder will hold the React components that make up the app. You will learn about each component we create as you follow along with the article.

You also have to create the src/i18n.js file, which sets up i18next, initializes the translations, and configures it to work with the app.

So what’s next? As for all software, we need to get the dependencies and libraries required for our task.

Installing required libraries and dependencies

To internationalize your React app with i18next, you need four main libraries, which can be installed using the following command.

npm install i18next react-i18next i18next-http-backend i18next-browser-languagedetector

Let’s understand why each of these libraries is necessary.

i18next: The core library that handles all internationalization tasks, like managing translations, formatting, and language switching.

react-i18next: Integrates i18next with React, providing features i18next can’t do alone. For example, it provides React-Specific hooks like useTranslation for accessing translations directly in React components. 

i18next-http-backend: Fetches translation files from an external source. This feature is essential when translations are managed dynamically.

i18next-browser-languagedetector: Automatically detects the user’s language based on browser settings.

Getting started

Now, let’s get our hands dirty and start writing code. To set up i18next in your React application, you need to create and configure the localization instance, load the necessary modules, and integrate it into your app.

Step 1. Create i18n.js for configuration

In your project’s src directory, create a new file called i18n.js to initialize and configure i18next.

import i18n from "i18next";
import { initReactI18next } from "react-i18next";
import HttpApi from "i18next-http-backend";
import LanguageDetector from "i18next-browser-languagedetector";


i18n
  .use(HttpApi)
  .use(LanguageDetector)
  .use(initReactI18next)
  .init({
    fallbackLng: "en",
    debug: true,
    interpolation: {
      escapeValue: false,
    },
    backend: {
      loadPath: "/locales/{{lng}}/{{ns}}.json",
    },
  });



export default i18n;

Let us give you a brief idea of what the above code does. The .use method in i18next is used to register plugins or middleware that extend the functionality of i18next. Each .use call adds a specific feature or capability to the i18next instance.

.use(HttpBackend) – Registers the HttpBackend plugin to load translation files dynamically.

.use(LanguageDetector) – Registers the LanguageDetector plugin, which automatically detects the user’s preferred language (e.g., from browser settings, cookies, or query parameters).

.use(initReactI18next) – Registers the React integration plugin to use react withi18next. It also offers features like React hooks (useTranslation) and components (<Trans>) for managing translations in React components.

.init() method sets up the i18next instance with all the required configuration options. It specifies how translations are loaded, which language to fall back to, and how dynamic placeholders are handled.

Step 2. Import i18n.js in src/index.js

To ensure your application uses the i18n.js configuration, import it into your entry point file (src/index.js).

import { StrictMode } from "react";
import { createRoot } from "react-dom/client";


import App from "./App";
import "./i18n";


const rootElement = document.getElementById("root");
const root = createRoot(rootElement);


root.render(
  <StrictMode>
    <App />
  </StrictMode>
);
 

Step 3. Use Suspense for Asynchronous loading

Since translation files may be loaded asynchronously (especially with HTTP), wrap your application in a Suspense component to handle loading states. You need to update the src/App.js  file with the below code.

import "./styles.css";


import { Suspense } from "react";
import { useTranslation } from "react-i18next";


function App() {
  const { t, i18n } = useTranslation();
  return (
    <div>
      <h1></h1>
    </div>
  );
}
export default function WrappedApp() {
  return (
    <Suspense fallback="...loading">
      <App />
    </Suspense>
  );
}

Here, we imported the libraries and used the Suspense component to render a fallback (Loading translations…) while translations were being fetched. The useTranslation hook is used to access the t function for performing translations.

Now, we may directly move to perform the translation. But wait a minute; we haven’t yet added anything to the translation.json file. Let’s add some basic content to the JSON for initialization. (Of course, you can get more structured .json files on the internet.)

Example: en/translation.json

{
  "main": {
    "welcome": "Welcome to our app!",
    "description": "This is a localized React application."
  }
}

Example: es/translation.json

{
  "main": {
    "welcome": "¡Bienvenido a nuestra aplicación!",
    "description": "Esta es una aplicación React localizada."
  }
}

Example: de/translation.json

{
  "main": {
    "welcome": "Willkommen in unserer App!",
    "description": "Dies ist eine lokalisierte React-Anwendung."
  }
}

Here, the header (welcome) and description (description) are keys in the translation file representing text elements in your React application. These keys act as identifiers for the respective content, allowing i18next to replace them with the appropriate translations depending on the selected language. The main is the parent key, often used to follow a proper structure for the translation file.

Let’s see how the translation works.

Text translation

To initialize the translation and visualize the difference, you may easily change the app function in the App.js file below.

function App() {
  const { t } = useTranslation();


  return (
    <div>
      <h1>{t('main.welcome')}</h1>
      <p>{t('main.description')}</p>
    </div>
  );
}


As mentioned, the ‘t’ function creates the magic, and you can quickly note it by running the code. For instance, add the code below to the i18n.js file to check the translation.

i18n.changeLanguage(‘es’);
export default i18n;

But is it a good approach to alter the App.js directly? As developers, it is best practice to keep individual files/classes. Functions for each task, right? So from this stage onwards, we’ll maintain separate files under a folder called components(create it under src) for each par and add them correctly to the App.js file.

Let’s continue with our initial translation.

We’ll create a file called Welcome.js and add the initial translation codes as below. 

import React from "react";
import { useTranslation } from "react-i18next";


const Welcome = () => {
  const { t } = useTranslation();


  return (
    <div>
      <h1>{t("main.welcome")}</h1>
      <p>{t("main.description")}</p>
    </div>
  );
};


export default Welcome;

Note that you must alter the app.js file like below to integrate the welcome.js file.

import "./styles.css";


import { Suspense } from "react";
import { useTranslation } from "react-i18next";
import Welcome from "./components/welcome"; // import the welcome package
const App = () => {
  return (
    <div>
      <Welcome /> // call the welcome function
    </div>
  );
};
export default function WrappedApp() {
  return (
    <Suspense fallback="Loading translations...">
      <App />
    </Suspense>
  );
}

If you run the app, you will get the same output as the images above. You need to import and call the specific functions similarly throughout the process.

Number and date format translation

We are done with text translation. Let’s see how the numbers and dates can be easily handled using i18next in React. 

We’ll need to install an additional library called Luxon, which makes working with DateTime in JavaScript easy. Install it using npm install luxon.

As the next step, create a file named DateAndNumber.js in src/components and add the below code to that file.

import React from "react";
import { useTranslation } from "react-i18next";


const DateAndNumber = () => {
  const { t } = useTranslation();


  const today = new Date();
  const price = 1234.56;


  return (
    <div>
      <p>{t("main.currency", { value: price })}</p>
      <p>{t("main.date", { value: today })}</p>
    </div>
  );
};


export default DateAndNumber;

Then call the functional component in App.js like we did for Welcome

Wait, have we completed the task? We still need to know what about the translation file. We need to add the currency and date there.

So, in the translation file, please add the respective codes under the main branch of each translation.json file in all the language folders; I’ll show the altered code below for the en, es, and de files.

For en,

{
  "main": {
    "welcome": "Welcome to our app!",
    "description": "This is a localized React application."
    "currency": "{{value, currency(USD)}}",
    "date": "{{value, datetime}}",
  }
}

For es,

{
  "main": {
    "welcome": "¡Bienvenido a nuestra aplicación!",
    "description": "Esta es una aplicación React localizada.",
    "currency": "{{value, currency(EUR)}}",
    "date": "{{value, datetime}}",
 
  }
}

For de,

{
  "main": {
    "welcome": "Willkommen in unserer App!",
    "description": "Dies ist eine lokalisierte React-Anwendung.",
    "currency": "{{value, currency(EUR)}}",
    "date": "{{value, datetime}}",
  }
}

Now you can see the following outputs.

For English

For Spanish

Similar to the Date and Time, you must add the respective key to the translation.json file for the upcoming tasks.

Pluralization

Pluralization adjusts text based on the quantity of an item. Here, we count the items that are added to the cart, and for that, we first create a Cart.js file in the folder src/components.

import React from "react";
import { useTranslation } from "react-i18next";


const Cart = ({ itemCount }) => {
  const { t } = useTranslation();


  return <p>{t("main.cart", { count: itemCount })}</p>;
};


export default Cart;

Add the Cart component to App.js as well. 

In the App.js return, you need to add the cart as below, where we need to mention the item_count.

<Cart itemCount={2} />

In the translation.json file, we need to add the cart part below.

For en,

"cart_one": "You have {{count}} item in your cart.",
"cart_other": "You have {{count}} items in your cart.",

For es,

"cart_one": "Tienes {{count}} artículo en tu carrito.",
"cart_other": "Tienes {{count}} artículos en tu carrito.",

For de,

 "cart_one": "Sie haben {{count}} Artikel in Ihrem Warenkorb.",
 "cart_other": "Sie haben {{count}} Artikel in Ihrem Warenkorb.",

Where is the cart_one and cart_other in the Cart.js code? Here, the i18next gives another great solution: the t function automatically selects the appropriate translation key (cart_one or cart_plural) based on the value of the count. 

Rather than changing the item count manually, we could also increase the count with a mouse click. You must add the state and mouseclick function to the text, as they usually add the mouseclick in React. You can leave this part if you don’t need it. 

Alter Cart.js as below.

import React from "react";
import { useTranslation } from "react-i18next";


const Cart = ({ itemCount, addItemToCart }) => {
  const { t } = useTranslation();


  return (
    <div>
      {/* Display the current count of items in the cart */}
      <p>{t("main.cart", { count: itemCount })}</p>


      {/* Button to add an item to the cart */}
      <button onClick={addItemToCart}>+1 Item</button>
    </div>
  );
};


export default Cart;


Then, alter the App.js file as below. For your understanding, the respective comments have also been added to the modified part.

import "./styles.css";


import { Suspense, useState } from "react";
import { useTranslation } from "react-i18next";
import Welcome from "./components/Welcome";
import DateAndNumber from "./components/DateAndNumber";
import Cart from "./components/Cart";
import Greeting from "./components/Greeting";


const App = () => {
  // State for managing item count in the cart
  const [itemCount, setItemCount] = useState(10);


  // Function to increase item count
  const addItemToCart = () => {
    setItemCount(itemCount + 1);
  };


  return (
    <div>
      <Cart itemCount={itemCount} addItemToCart={addItemToCart} />


    </div>
  );
};


export default function WrappedApp() {
  return (
    <Suspense fallback="Loading translations...">
      <App />
    </Suspense>
  );
}

Great, now you know how to handle the pluralization.

Interpolation

So far, we’ve been hardcoding all the content in the translation files. But what if need to include variables, such as user names or dynamic data, in these translations? This is where interpolation comes in. It allows you to insert dynamic values into your translations. Let’s see how it works.

Here, I am going to greet a person with his name. So, I created a file called Greeting.js in the folder src/components and added the code below.

import React from "react";
import { useTranslation } from "react-i18next";


const Greeting = ({ name }) => {
  const { t } = useTranslation();


  return <p>{t("main.greeting", { name })}</p>;
};


export default Greeting;

The t(“main.greeting”, { name }) function fetches the translation associated with the key main.greeting from your translation files. The { name } object is passed as a dynamic value that will replace placeholders in the translation text.

In our translations file, we will include the main.greeting key as shown below.

For en,

"greeting": "Hallo, {{name}}! Willkommen in der App."

For es,

"greeting": "Bonjour, {{name}}! Bienvenue dans l'application."

Then you have to add the Greeting component to App.js  and change the name using the below code to see the output.

<Greeting name="Alan" />

Output

react localization i18next

Now, if you want to change the name, you only need to update it in one place: <Greeting name=”name” />. However, if you didn’t use interpolation and directly hardcoded the name in the translation.json file, you would need to update every instance of the name across all translation.json fields whenever you want to make a change.

Language switcher

A language switcher is a feature that allows users to switch between different languages as they wish. 

In our app, we still have to change the language manually using i18n.changeLanguage. This is not something your website visitors can do. Let’s give the users an option to choose their preferred language.

First, create a Language switcher component named LanguageSwitcher.js in the src/components.

import React from "react";
import { useTranslation } from "react-i18next";


const LanguageSwitcher = () => {
  const { i18n } = useTranslation();


  const changeLanguage = (lng) => {
    i18n.changeLanguage(lng); // Change language dynamically
  };


  return (
    <div className="language-switcher">
      <button onClick={() => changeLanguage("en")}>English</button>
      <button onClick={() => changeLanguage("es")}>Español</button>
      <button onClick={() => changeLanguage("de")}>Deutsch</button>
    </div>
  );
};


export default LanguageSwitcher;

Here, the useTranslation hook provides access to i18next’s translation functions and the current language. The button function creates a clickable icon for a specific language. To complete the setup, add the LanguageSwitcher component to the App.js file.

Here is the final output of our app. You can change the language by clicking the button relevant to the desired language.

If you want to see these functionalities and the complete code in action, you can visit the CodeSandbox here.

Great! Now, you have learned the basic concepts of i18next-react in the localization process. Let’s consider some parts you need to consider while working with i18next.

Best practices to follow

While building apps, it is always great to have proper practices. In the case of i18next, we need to give extra care to certain parts in customizing the application as it might affect the integrity of their native language and cultural landscapes. We’ll see the best practices to handle the 18next here.

  1. Avoid hardcoding text directly and use descriptive keys.  As discussed earlier, using the ‘t’ to mention a specific phrase or language that can later be quickly adapted to further languages with JSON files is essential. 

For example, instead of hardcoding text like this: <button>Submit</button>, Use a translation key: <button>{t(‘buttons.submit’)}</button> and add “submit”: “Submit” in your en/translation.json file under “buttons” parent key.

  1. Add a default language in the i18n.js code file using the fallbackLng function. For example, we used (fallbackLng: ‘en’) in our code.
  1. Rather than adding all the language JSON files, using only the required files is always good for reducing app bundle size, increasing performance, and more effortless organization. 
  1. Finally, before publishing your app, it is always important to check the translation thoroughly because a single letter might change the meaning of a language. 

Now that you have learned to use I18next, let’s look at another straightforward way to do the translations with POEditor.

Simplify React localization using POEditor

Translating each of your keywords to the desired language is a hectic process and mostly leads to errors.  To avoid mistakes and ease your process, POEditor can help simplify the translation process.

POEditor allows you to upload your translation files, manage languages collaboratively, and automate the import/export of translations. Let’s look at it in detail.

First, you need to log in to your POEditor account. If you don’t have an account, you can create one using this link and then follow the steps below:

  • Integrate POEditor with Git. The easiest way to connect POEditor with your React app is to link your repository to the POEditor project. Go to the Integrations section in POEditor and connect it to your Git hosting platform (e.g., GitHub, GitLab, Bitbucket) to synchronize your translation files with your repository. This feature is available starting with the Plus plan.
  • Link translation files. Select your React localization project in POEditor, choose a language, and link it to the corresponding translation.json file in your repository by specifying the repo, branch, and file. Repeat this process for all language files in your React app.
  • Import keys and translations. Use the “Import terms and translations” option to upload your keys and source language translations. You can also set a default reference language in POEditor.
  • Export translations. After completing translations in POEditor, export the updated strings to your repository to update the respective translation.json files.
  • Automate with webhooks. Set up webhooks to sync terms and translations automatically between your repository and POEditor whenever changes are made.

Conclusion

In this React localization guide, we looked at how to use i18next, a robust package for translation requirements, to localize React apps. Text translation, date and number formatting, pluralization, interpolation, and building a language switcher were among the essential topics we discussed. These methods were used to create a React application that was straightforward but scalable to a larger global audience.

We also showed how to incorporate POEditor, our robust translation management system, into the React localization process. We streamlined the process of managing numerous languages by utilizing its uploading, managing, and automated translation tools. This made it simpler to work with translators and ensure consistency across projects.

POEditor and i18next work together to guarantee that your application is globally available and easy to use.

Ready to power up localization?

Subscribe to the POEditor platform today!
See pricing