How to translate your React app with react-i18next

Joachim Grill, Andreas Löw
Last updated:
GitHub
How to translate your React app with react-i18next

What you are going to learn

Is this tutorial, you are going to learn how localization works in a react js app. The tutorial not only provides step-by-step instructions, it also contains a complete example on GitHub.

This tutorial covers react-i18next.
We also have a tutorial covering react-intl / FormatJS.

The full source code for this tutorial is available on GitHub.

Set up your first React app (optional)

We're setting up a small React application to learn how localization works. Of course, you can skip this section if you want to use your own application for that.

We'll use Vite for building the app... but any other environment like Next.js or Remix should be fine, too.

npm create vite@latest react-i18next-translation-example -- --template react

With the following lines you create an empty react app and start it:

cd react-i18next-translation-example
npm install
npm run dev

Open http://localhost:5173 and see the welcome message rendered by the created app.

Add internationalization with react-i18next

As we want to use react-i18next to localize our application, add it to your project:

npm install react-i18next

... and you might also want to add prop-types:

npm install prop-types

The file src/main.jsx renders the App react element into your DOM. To make the i18next configuration available in all our components we have to wrap the App component with I18nextProvider. It expects an i18next instance which must be initialized before:

src/main.jsx
import { StrictMode } from 'react'
import { createRoot } from 'react-dom/client'
import {I18nextProvider} from "react-i18next";
import i18next from "i18next";
import App from './App.jsx'
import './index.css'

i18next.init({
    interpolation: { escapeValue: false },  // React already does escaping
});

createRoot(document.getElementById('root')).render(
  <StrictMode>
      <I18nextProvider i18n={i18next}>
        <App />
      </I18nextProvider>
  </StrictMode>,
)

Using translations in your app

You can use translations in your function components and High Order Components (HOC). Both works similar, but require a slightly different set-up.

Using translations in function components

To use translations, you have to call useTranslation() which returns the translation function t().

The first parameter to that t() function is the ID of the string to display. The easiest way is to just wrap the original text e.g. t('Hello World!'). However, I don't recommend doing this because it does not give the translator much context. It's also impossible to change the representation of the string depending on its use. E.g. a translation might need to be shortened when used as a table header compared to the use as a label.

So the better way to do this is to use IDs - e.g. t('app.title'). With this, the translator now has more context - it's used as the title of the app.

Additionally, you can pass a namespace to useTranslation() to better organize your messages. For example, use useTranslation('main') to specify a single namespace, or useTranslation(['main', 'common']) if you need to access translations from multiple namespaces.

Create a new file src/components/FunctionComponent.jsx:

src/components/FunctionComponent.jsx
import {useTranslation} from "react-i18next";

export default function FunctionComponent() {

    const {t} = useTranslation("common");

    return (
        <>
            <h2>{t('components.func-component.title')}</h2>
            <p>{t('components.func-component.text')}</p>
        </>
    );
}

Using translations in High-Order-Components

Some more work is required if you want to use High-Order-Components (HOC). The t() function is available through the props. But you also have to wrap your component with withTranslation() to get access to the translation methods:

src/components/HighOrderComponent.jsx
import './App.css';
import {Component} from "react";
import {withTranslation} from "react-i18next";
import PropTypes from "prop-types";

class HighOrderComponentBody extends Component {
    render() {
        const { t } = this.props;
        return (
            <>
                <h2>{t('components.hoc-component.title')}</h2>
                <p>{t('components.hoc-component.text')}</p>
            </>
        )
    }
}

HighOrderComponentBody.propTypes = {
    t: PropTypes.func.isRequired,
}

export const HighOrderComponent = withTranslation('common')(HighOrderComponentBody)

Use the components in your app

import './App.css';
import FunctionComponent from "./components/FunctionComponent.jsx";
import {HighOrderComponent} from "./components/HighOrderComponent.jsx";

function App() {
    return (
        <div className="App">
            <FunctionComponent/>
            <HighOrderComponent/>
        </div>
    );
}

export default App;

Creating the translation files

Create a new folder in the source directory called translations and make folders for each language your want to translate your app to. Use the language codes for these subdirectories. e.g. en and de.

Inside create 2 empty JSON files. Use the name common.json for these files. This is the same name as the namespace you passed to the useTranslation() or withTranslation('common') function.

src/translations/en/common.json
{}
src/translations/de/common.json
{}

Editing translation files

You could edit these files in a standard text editor — it's JSON and not really complicated, right?

When starting with a handful of translations, you are absolutely right. But over time, you add, remove and rename translations, and this is when the translation files get our of sync. There are some IDs in the German file that are not in the English and vice versa... Using a diff tool to get them back in sync is not an option because every line contains changes.

This is why we've created BabelEdit — changes are always synced across all languages. You can use machine translation to preview your app in different languages. You can add comments for translators and many more features. Here's a screenshot of what it looks like:

BabelEdit for react-i18next

BabelEdit is available as a free trial. After that, it costs a bit of money — but you only have to pay once since it's a perpetual license and no subscription.

After installation, drag & drop your project folder onto BabelEdit. It automatically detects the project type (i18next) and also finds the existing translation files.

Configuring your react-i18next project in BabelEdit
Configuring your react-i18next project in BabelEdit

Now add your first translation ID: Click Add ID in the toolbar or press (macOS) or CTRL+n (Windows). Enter common:components.func-component.title as name translation ID. common is the namespace you used in useTranslation("common"), or withTranslation('common') app.title the ID used in the t('components.func-component.title') function.

BabelEdit now asks you for your primary language. This is the language used as the source for machine translation and other features. Select en-US.

In the center view, enter "Function component" in the en-US labeled text field.

IDText
common:components.func-component.titleFunction component

You can simply enter the text in German for the de.json file, but if you don't speak that language you can easily use the machine translation feature of BabelEdit. Click on the translated text or press +1 or CTRL+1.

Using machine translation to speed up development with i18next
Using machine translation to speed up development with i18next

You can speed up the process for entering the IDs by selecting a part of the path in the tree view before you press the *Add ID.

Add the following translations:

IDText
common:components.func-component.textThis text is used inside a function component!
common:components.hoc-component.titleHOC component
common:components.hoc-component.textThis text is used inside an hoc component!

Select the root node in the tree view and click the Pre-Translate button in the toolbar to translate them all at once to German:

Use pre-translate to translate multiple strings at once
Use pre-translate to translate multiple strings at once

Click on Save in the toolbar. BabelEdit first asks you for the name of the project file — enter translations.babel and save it to the root folder of your project. It contains the project configuration and comments. Save also updates the JSON files which are now ready for use in your source code!

Loading translation files

Now that you have the translation files, it's time to load them into the application.

For the simplicity of this tutorial, we directly import the translations in the src/main.jsx file. This is ok as long as you don't have too many translations and languages. If this is the case, you should switch to dynamic loading of the files.

src/main.jsx
...
import {I18nextProvider} from "react-i18next";
import i18next from "i18next";

import common_de from "./translations/de/common.json";
import common_en from "./translations/en/common.json";

i18next.init({
    interpolation: { escapeValue: false },  // React already does escaping
    lng: 'en',                              // language to use
    resources: {
        en: {
            common: common_en               // 'common' is our custom namespace
        },
        de: {
            common: common_de
        },
    },
});

const root = ReactDOM.createRoot(document.getElementById('root'));
...

Parameters, interpolation and formatting

The t() function also accepts a 2nd parameter: A javascript object with parameters that i18next uses to replace parts of the translation strings.

App.js
import './App.css';
import FunctionComponent from "./components/FunctionComponent.jsx";
import {HighOrderComponent} from "./components/HighOrderComponent.jsx";
import {useTranslation} from "react-i18next";

function App() {

    const {t} = useTranslation("common");
    return (
        <div className="App">
            <p>{t('app.interpolation', {framework: 'react-i18next'})}</p>
            <p>{t('app.format.numbers', {pi: 3.14159265359})}</p>
            <p>{t('app.format.numbers-limit', {pi: 3.14159265359})}</p>
            <p>{t('app.format.currency', {val: 49.99})}</p>
            <p>{t('app.format.datetime', {now: new Date()})}</p>
            <p>{t('app.format.relative-time', {rel: 10})}</p>
            <FunctionComponent/>
            <HighOrderComponent/>
        </div>
    );
}

export default App;

Hint: If you've already selected translations in the left panel, adding a new ID automatically suggests the prefix of that ID, so you don't have to type it multiple times. E.g. if you selected common:app.title, adding a new ID suggests common:app. and you only have to complete the text.

Add the following IDs:

IDText
common:app.interpolationThis translation example is using {{framework}}.
common:app.format.numbersThis is pi: {{pi, number}}.
common:app.format.numbers-limitThis is pi: {{pi, number(minimumFractionDigits: 7) }}.
common:app.format.currenyTotal: {{val, currency(USD)}}
common:app.format.datetimeToday is {{now, datetime}}
common:app.format.relative-timeSomething wonderful happens {{rel, relativetime}}!

Changing languages

So far, we've not yet switched the language. For this, let's add a language switcher component:

To automatically select a language, you might want to use one of the language detector plugins listed on this page. For our demo app we just add two buttons and trigger the language change manually. Add the following lines to the render() function in App.js:

src/components/LanguageSelector.jsx
import {useTranslation} from "react-i18next";

export const LanguageSelector = () =>
{
    const [t, i18n] = useTranslation('common');
    return <div>
        <span>{t('components.language-selector.label')} </span>
        <button onClick={() => i18n.changeLanguage('de')}>
            {t('components.language-selector.languages.de')}
        </button>
        &nbsp;
        <button onClick={() => i18n.changeLanguage('en')}>
            {t('components.language-selector.languages.en')}
        </button>
    </div>
}

Use the component inside your App.js:

components/LanguageSwitcher.jsx
import './App.css';
import {useTranslation} from "react-i18next";
import {LanguageSelector} from "./components/LanguageSelector";

...

function App() {
    const {t} = useTranslation("common");
    return (
        <div className="App">
            <LanguageSelector/>
            ...

Add these entries to BabelEdit:

IDText
common:components.language-selector.labelSelect your language:
common:components.language-selector.languages.deGerman
common:components.language-selector.languages.enEnglish

Switching should now work — however, the files in the de/common.json are still empty. The result is that you don't see anything on the screen.

This is, of course, not what you want. Start by opening the Settings in BabelEdit's toolbar. Remove the checkmark from Editor -> Save empty translations.

After saving in BabelEdit, you should now see the IDs instead of the translation text.

Conclusion

Enhancing your React app with translations is simple with react-i18next. By using BabelEdit to manage your translation files, the process becomes even smoother. It also enables you to preview your app in various languages throughout development.

The full source code for this tutorial is available on GitHub.