How to translate your React app with react-intl + Example

Setup your first React app

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.

If you haven't already installed the create-react-app scripts, install them using npm:

npm install -g create-react-app

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

npx create-react-app react-intl-demo
cd react-intl-demo
npm start

The last line automatically opens the URL http://localhost:3000 and dislays the welcome message rendered by the created app.

Add internationalization

Add react-intl to your project

As we want to use react-intl to localize our application, add it to you project:

npm install --save react-intl

Wrap your app with IntlProvider

The file src/index.js renders the App react element into your DOM:

ReactDOM.render(<App />, document.getElementById('root'));

Wrap your app with IntlProvider

To make the internationalization functions visible in all our components we have to wrap the App component with IntlProvider. It expects the current locale as property. For the moment we're setting it to a fixed language, later we will determine the user's locale by evaluating the language request sent by the browser.

import {IntlProvider} from "react-intl";

ReactDOM.render(
    <IntlProvider locale='en'>
        <App/>
    </IntlProvider>,
    document.getElementById('root')
);

Translate Text: FormattedMessage and FormattedHtmlMessage

Now we have to find all language-specific string in our app. In the simple application generated by create-react-app there are two strings in App.js:

A text paragraph with included HTML formatting and a link with text:

<p>
  Edit <code>src/App.js</code> and save to reload.
</p>
<a className="App-link" href="https://reactjs.org" target="_blank" rel="noopener noreferrer">
  Learn React
</a>

You have to wrap the text parts with a <FormattedMessage> or <FormattedHTMLMessage> component to translate it. The difference between both is that <FormattedMessage> escapes all HTML (showing you the HTML tags in your app), whereas <FormattedHTMLMessage> renders HTML formatted messages.

Parameters for both components are:

Parameter Opt. Description
id required Identifier used to reference the translation
description optional A description for the message - to give the translator some context for the work
defaultMessage optional A default message that is displayed if no translation is found. This can already be the text in your primary language
values optional Object containing parameters for the message |{.table .table-striped}

First import FormattedMessage and FormattedHTMLMessage at the top of App.js

import {FormattedMessage, FormattedHTMLMessage} from 'react-intl';

Replace the string in the <p> tag with a <FormattedHTMLMessage> and the content of the <a> with a FormattedMessage. Copy the original text to the defaultMessage attribute.

I've also added a parameter to show you how parameters work in react-intl:

<p>
    <FormattedHTMLMessage id="app.text"
                      defaultMessage="Edit <code>src/App.js</code> and save to reload.<br/>Now with {what}!"
                      description="Welcome header on app main page"
                      values={{ what: 'react-intl' }}/>
</p>
<a className="App-link" href="https://reactjs.org" target="_blank" rel="noopener noreferrer">
    <FormattedMessage id="app.learn-react-link"
                      defaultMessage="Learn React"
                      description="Link on react page"/>
</a>

If you refresh your browser window the welcome string changes from "Edit src/App.js and save to reload." to "Edit src/App.js and save to reload. Now with react-intl".

As we haven't defined a translation for the ID app.text, the string defined by defaultMessage is used. If neither a translation nor a default message is defined, the ID would be displayed. The description property will be displayed to the translator to give him some context information.

To learn more about message formatting, parameters and localized number and date formats please have a look at the react-intl Documentation.

Add internationalization data with addLocaleData

In the first step we have to load the locale data for languages we want to support. This data is provided by react-intl and specifies the date, time, number, ... formats for each language. The locale data is added by calling addLocaleData().

Add these lines to index.js:

import { addLocaleData } from "react-intl";
import locale_en from 'react-intl/locale-data/en';
import locale_de from 'react-intl/locale-data/de';

addLocaleData([...locale_en, ...locale_de]);

The translations of our custom text messages will be stored for each language in a separate .json file. Let's create the JSON file src/translations/de.json for the German translation. We also create an en.json file instead of using defaultText specified in the source code, this makes language handling a bit simpler.

Add translated messages from JSON files:

src/translations/en.json:

{
    "app.text": "Edit <code>src/App.js</code> and save to reload.<br/>Now with {what}!",
    "app.learn-react-link": "Learn React."
}

src/translations/de.json:

{
    "app.text": "Bearbeite und speichere <code>src/App.js</code> um diese Seite neu zu laden.<br/>Nun mit {what}!",
    "app.learn-react-link": "Lerne React."
}

Now we can load these JSON files and pass one of them to IntlProvider, depending on the language the user has configured in the browser:

Add these lines to src/index.js:

import messages_de from "./translations/de.json";
import messages_en from "./translations/en.json";

const messages = {
    'de': messages_de,
    'en': messages_en
};
const language = navigator.language.split(/[-_]/)[0];  // language without region code

ReactDOM.render(
    <IntlProvider locale={language} messages={messages[language]}>
    ...

Change the preferred language in your browser settings and reload the page to see how the language of your application changes. For languages other than "de" or "en" the translation messages are undefined and react-intl will display the defaultText.

Spending time updating your translations?

BabelEdit is the translation editor for your react project.

See all translations at the same time. Save hours of editing json files. Easy data exchange with translation agencies.

Download BabelEdit +

Managing translations

Maintaining the translations files manually is quite a pain as the IDs in all these files must be kept in sync with the IDs used in the javascript source code. We use two tools to simplify this task:

Extract message IDs from source code using babel-plugin-react-intl

The translation IDs can automatically extracted from the source code using babel-plugin-react-intl. First we have to install this babel plugin:

npm install --save-dev @babel/core @babel/cli  babel-plugin-react-intl

Create a .babelrc file in the project directory. babel-plugin-react-intl will store the extracted message IDs in the build/messages directory, for each source file a corresponding json file is created:

{
    "presets": ["react-app"],
    "plugins": [
        [ "react-intl", {
            "messagesDir": "./build/messages",
            "extractSourceLocation": true
        }]
    ]
}

By adding the following line to the scripts section of your package.json you can use npm run extract-messages to start babel and extract the message IDs from your sources:

"scripts": {
    "extract-messages": "NODE_ENV=production babel ./src  --out-file /dev/null"    (Mac/Linux)
    "extract-messages": "set NODE_ENV=production&& babel ./src >NUL"               (Windows)
}

If babel is already called as part of your build process you can skip this step.

Maintain translation files with BabelEdit

To get started with BabelEdit download it from here: Download BabelEdit

The editor is currently in beta phase and you can use it for free.

Select the right-most button to create a react-intl project:

Select the build/messages directory of your React application, which contains the messages extracted with babel-plugin-react-intl:

If you've specified defaultMessage texts in your source code, you can specify the corresponding translation file here. The defaultMessages are automatically copied to this translation file, and they will be displayed read-only by the BabelEdit UI:

Now you can define additional languages. If you've already created translation files as described in the previous section you can select them here, too. Their content will be imported:

BabelEdit loads the message definitions files as well as the translations files. The left side displays a tree view with your translation IDs, the right side the translations. The Approved checkbox allows you to mark translations as final. This additional information is stored in a BabelEdit project file (extension .babel).

The editor automatically reloads the files after they have been updated ? e.g. from a new run of babel-plugin-react-intl. Each time you save the BabelEdit project the JSON translation files are updated, too.

Use add additional languages, change the primary language or update the directory containing the extracted message definitions you can use the Languages and Settings buttons in the toolbar.

Did you like the tutorial? Please share!