React Internationalization— A quick setup guide

What is Internationalization?

Ivan Mayoral
Nov 18 · 6 min read

Websites accessible in many regions may want to translate their text so the content is readable in different languages. In addition to the text, we should also consider that dates, numbers, and currency may differ across regions. Implementing internationalization means that the application should not only show the text in the correct language, but it should also display data in the correct format as well. This enables more people to use an application in their native language.

For example, the number 1,000 could mean something different for someone in Europe and in America, and01–02–01 could mean a different date depending on the country.

Since GumGum is a global business, we need internationalization support in our applications.


Intl API vs FormatJS

With JavaScript, we can take several approaches:

Intl API handles all the internal work on modern browsers by implementing number, text and date formatting functions that use alocale value that corresponds to the language we want to use.

FormatJS adapts this native Intl API functionality into the most common JavaScript Frameworks. In the case of React, the package react-intl (part of FormatJS) provides components and an API that can be used in any code base.

Initial Setup

The starting point in this example is the code generated by create-react-app, but it can work with any application that follows the same structure.

With npm 5.2+ you can create an app from scratch by running:

npx create-react-app new-translated-app

Or with yarn 0.25+:

yarn create react-app new-translated-app

This will generate all the initial files for your React application in the new-translated-app folder.

The additional packages that we are going to include are:

  • @babel/cli
  • babel-plugin-react-intl
  • react-intl
  • react-intl-translations-manager

They can be added to the current project by using the following command:

yarn add @babel/cli babel-plugin-react-intl react-intl react-intl-translations-manager

Then the application should be ready to start by running:

yarn run start

FormattedMessage component

When using react-intl, we need to replace the text that we are going to translate with the corresponding components. For example:<FormattedMessage> replaces a text message and <FormattedDate> replaces a date.

Let’s modify src/App.js so that it contains the text Hello World! that we will use to translate into multiple languages later:

import React from 'react';
import { FormattedMessage } from 'react-intl';
import './App.css;function App() {
return (
<div className="App">
<header className="App-header">
<p>
<FormattedMessage id="helloWorld" defaultMessage="Hello World!" />
</p>
</header>
</div>
);
}
export default App;

FormattedMessage receives two parameters:

  • idany string unique to this message.
  • defaultMessage — the text that the application will use as a default.

We can add more FormattedMessage elements in a similar way, and other components with the restriction that if the messages are different, the ids should be unique.

There is also an API in case you don’t want to use a component, for example if you need to translate the text of an attribute instead. Check the API Documentation for more information.

Message extraction using babel-plugin-react-intl

When our application has several message tags, we will need to extract the values provided previously (id and defaultMessage), which allows us to keep track of which id belongs to each message and in a later step translate these messages into different languages.

It sounds complicated, but there is a babel plugin that can handle the extraction for us. First, we need to add a new file, .babelrc on the project’s root containing:

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

This will tell babel to load the react-intl plugin and use the folder /messages to store the extracted data.

Add the translations:extract command to package.json scripts:

"scripts": {
"start": "react-scripts start",
"build": "react-scripts build",
"test": "react-scripts test",
"eject": "react-scripts eject",
"translations:extract": "NODE_ENV=production babel src/**.js",
},

And run it with:

yarn run translations:extract

Once it finishes, it will extract all of the messages into the messages folder, following the same structure of src :

- /
- messages/
- src/
App.json

You don’t need to modify these files for now, but you will need to run this command again if you add more FormattedMessageelements in your code.

Message management with react-intl-translations-manager

After the messages of the application have been extracted into the /messages folder, we will need to identify and organize them into separate locale files for each language.

A handy way to to this is by using react-intl-translations-manager that will create the default values and give us the status of all the missing and complete translations.

To configure it, create a new file translationRunner.js in your root folder. This example uses en (English) and es (Spanish) locales:

const manageTranslations = require('react-intl-translations-manager').default;manageTranslations({
messagesDirectory: 'messages',
translationsDirectory: 'src/locales',
languages: ['en', 'es'],
singleMessagesFile: true,
});

Also, create a separate file, src/locales/index.js, that will import both locales:

import en from './en.json';
import es from './es.json';
export default { en, es };

Add the following on package.json along with the rest of the scripts:

"translations:manage": "node translationRunner.js"

This way you can use yarn run translations:manage to start the managing process:

yarn run translations:manage

When the script finishes running, it will create two new files:

  • src/locales/en.json containing the messages in English.
  • src/locales/es.json containing the messages in Spanish.

We only got the helloWorld key in English and no value for it in Spanish yet, so the default value is created on es.json using the value provided by defaultMessage .

Try replacing the contents ofes.json with the translated text, and runtranslations:manage one more time to check the status.

{
"helloWorld": "¡Hola Mundo!"
}

Each time you add more FormattedMessageelements, you will need to run translations:manage again to create the default values and check which text strings are still missing.

Wrapping App with IntlProvider

To display the translated values, App needs to be wrapped into the <IntlProvider> component along with a locale and the messages that we processed previously.

In index.js we can do this by adding:

// Internationalization
import { IntlProvider } from 'react-intl';
import locales from './locales';
// Change this value to change language
const locale = 'en';
const messages = locales[`${locale}`];ReactDOM.render(
<IntlProvider locale={locale} messages={messages}>
<App />
</IntlProvider>,
document.getElementById(‘root’)
);

Changing the locale and running the project

At this point, when running yarn run start the default English message will appear:

English message

But if the value of locale is changed in index.js:

const locale = 'es';

The Spanish message will be displayed instead:

Spanish Message

This is not the ideal way to handle locale though. It should be set by using either the browser’s settings or a custom value that could be provided by the user.

Workflow for adding new messages

When a newFormattedMessage is added, you will need to do the following:

  • Run translations:extract to extract the new message(s) from your code.
  • Run translations:manage to create new entries on the locale files (en.json and es.json in this case).
  • Modify the .json files with the translated messages accordingly.
  • Run translations:manage again to verify the new message(s) status.

Further reading:

Source code


We’re always looking for new talent! View jobs.

Follow us: Facebook | Twitter | | Linkedin | Instagram

gumgum-tech

Thoughts from the GumGum tech team

Ivan Mayoral

Written by

gumgum-tech

Thoughts from the GumGum tech team

Welcome to a place where words matter. On Medium, smart voices and original ideas take center stage - with no ads in sight. Watch
Follow all the topics you care about, and we’ll deliver the best stories for you to your homepage and inbox. Explore
Get unlimited access to the best stories on Medium — and support writers while you’re at it. Just $5/month. Upgrade