As globalization increases, writing React applications for a wide-ranging audience in different regions and locales means making it accessible across languages.
With internationalization capabilities, the React Intl library provides the mechanism to properly translate file text into other languages.
In this guide, we’ll learn how to use the React Intl library to set up internationalization in a React project. We’ll create a simple app that allows users to select and see their preferred language when viewing the app.
We’ll also persist the selected locale in the browser storage so the contents are still available after a page refresh or on a subsequent visit.
Below is the end product of what we will build together:
Interact with the finalized webpage to familiarize yourself with the interface, and with that, let’s get started!
As mentioned earlier, React Intl allows us to set up internationalization in a React app. But what exactly is internationalization?
Internationalization is the process of designing a product — in this case, a React app — to be used within different locales. It is often abbreviated as Intl or i18n.
In contrast, localization, abbreviated l10n, focuses on translating an internationalized app from its original language to a specific language.
But hey, translation is more than just transforming the text or message to a target language. Cultural differences must also be considered, like how numbers and dates are written or how units and currencies are placed for different locales.
Fortunately, with the React Intl library, we can achieve our desired result seamlessly.
Let’s start by getting our simple React project — with its default content in English — from this GitHub repository. Then, we can add support for three additional languages: French, German, and Japanese.
So, head over to the terminal, switch to a directory to save the project, and run the following command to download the starter files:
git clone https://github.com/Ibaslogic/i18n_react_intl_starter
Once the project files have been bootstrapped, open them with a code editor. We must be inside the project directory and run npm install
to create a node_modules
folder for the project.
Ensure Node.js is installed on the computer to complete this tutorial.
Now, start the development server using npm start
. We should see our app loaded in the browser at http://localhost:3000.
If we take a look at the project’s file structure, we should have the following:
project_folder ├── node_modules ├── public ├── src │ ├── components │ │ ├── App.js │ │ ├── Content.js │ │ ├── Footer.js │ │ └── Header.js │ ├── index.css │ └── index.js ├── .gitignore ├── package-lock.json ├── package.json ├── README.md └── yarn.lock
Great. Now that we are on the same page, let’s see how we can use the React Intl library within our app.
To use this library, we must install it by stopping the development server and running the following command from the terminal:
npm install react-intl
This library gives us all the APIs and components needed to implement internationalization into our app.
Let’s wrap the top-level app or the root component of our app in a provider
component from the library; in this case, we’ll use <IntlProvider>
.
IntlProvider
component?As the name implies, IntlProvider
ensures that important configurations are provided or made available to the subcomponents in the tree.
These subcomponents from React Intl are called formatted
components. They are responsible for the proper translation and formatting at runtime. These components are:
FormattedMessage
FormattedNumber
FormattedDate
FormattedPlural
For internationalization projects using the React Intl library, the FormattedMessage
component is often used, allowing users to translate and format simple to complex strings and messages. We’ll be using this in a moment.
Back in our project, let’s open the parent component file src/components/App.js
and wrap the children components with IntlProvider
.
Ensure to import IntlProvider
from react-intl
at the top of the file.
Our file should look like this:
... import { IntlProvider } from "react-intl"; const App = () => { return ( <IntlProvider> <div> <Header /> <Content /> <Footer /> </div> </IntlProvider> ); }; export default App;
Let’s take a moment and check what the IntlProvider
component returns. First, log the component:
... const App = () => { console.log(IntlProvider); return ( ... ); }; ...
Then, check the browser console:
By focusing on the defaultProps
, we see all the default configuration props used by IntlProvider
. The component is also telling us to configure a locale.
So, let’s add the required props to identify and display translations to IntlProvider
by updating the component to include the following props:
return ( <IntlProvider messages={{}} locale="en" defaultLocale="en"> <div> <Header /> <Content /> <Footer /> </div> </IntlProvider> );
Immediately by adding the locale
prop and saving, the error goes away.
This locale
, which accepts a string, determines what language our app is rendered in. We’ll dynamically add the value based on what users select in the frontend.
The messages
object contains a set of translated strings ready to display in the frontend. These will also be dynamically added based on the current locale.
Lastly, the defaultLocale
prop is the default locale and should match the app’s default language.
Before we load the translated messages for our project, let’s play around and get familiar with the formatted components.
Open the src/components/Footer.js
file and add these formatted components in the return
statement:
// ... import { FormattedDate, FormattedNumber, FormattedPlural } from "react-intl"; const Footer = () => { // ... return ( <div className="container mt"> {/* ... */} <FormattedDate value={Date.now()} /> <br /> <FormattedNumber value={2000} /> <br /> <FormattedPlural value={5} one="1 click" other="5 clicks" /> </div> ); }; // ...
Next, save the file and check the frontend. We should have this displayed:
5/29/2021 // your current date 2,000 5 clicks
If we change the locale
prop of the IntlProvider
to de
and save the file once again, we have the data formatted in German:
29.5.2021 2.000
This is awesome. React Intl is formatting the date and number based on the selected region through the formatted components (FormattedDate
and FormattedNumber
, respectively).
These formatted components have a value
prop that accepts the data to be formatted.
We can further customize these components by taking advantage of its other props. For instance, we can have the following:
return ( <div className="container mt"> {/* ... */} <FormattedDate value={Date.now()} year="numeric" month="long" day="2-digit" /> <br /> <FormattedNumber value={2000} style={`currency`} currency="USD" /> <br /> {/* ... */} </div> );
Then, we’ll get this for English (en
):
May 29, 2021 // your current date $2,000.00
And then we’ll get this for German (de
):
29. Mai 2021 2.000,00 $
Apart from these two components, we also have the FormattedPlural
component that allows us to handle plurals in our application. As seen in the sample above, the component selects a plural category based on the value it received.
Another way we can format our data is through the React Intl API. Though using the component method is preferred in React because it works seamlessly with other React components, there are cases where the API method is needed, such as when the placeholder
, title
, or aria-label
of an element must be translated.
Anytime we use formatted components to render a React element, they use the React Intl API behind the scenes.
Let’s see how this API works. Still in the Footer.js
file, import the useIntl
Hook from react-intl
:
import { FormattedDate, FormattedNumber, FormattedPlural, useIntl } from "react-intl";
Ensure this is done in a functional React component.
If we log the useIntl
Hook in the console, we should see all the available functions needed to format our data:
// ... const Footer = () => { // ... const intl = useIntl(); console.log(intl); return ( <div className="container mt"> {/* ... */} </div> ); }; // ... >
One of these functions is the formatDate
; let’s apply it.
Update the return
statement in the Footer.js
file to include the function:
// ... const Footer = () => { // ... const intl = useIntl(); return ( <div className="container mt"> {/* <... */} <br /> <input placeholder={intl.formatDate(Date.now())} /> </div> ); }; // ...
Check the frontend to see the input field. Remember to change the locale
value of the IntlProvider
to see how it works.
We can customize further by adding the optional config:
<input placeholder={intl.formatDate(Date.now(), { year: "numeric", month: "long", day: "2-digit", })} />
Now that we are familiar with these formatters, let’s head back to our project.
To support other locales, we must translate the source text. For this tutorial, let’s use Google Translate.
Create a folder named i18n
in the src
directory. In this folder, create two different files named locales.js
and messages.js
. The locales.js
file will hold our supported locales while the messages.js
file will hold the corresponding translations.
Add this in the i18n/locales.js
file:
export const LOCALES = { ENGLISH: "en-US", JAPANESE: "ja-JA", FRENCH: "fr-FR", GERMAN: "de-DE", };
This is necessary to avoid manually adding the locales. To access en-US
, for example, we’ll use [LOCALES.ENGLISH]
.
The code en-US
comes from the language-COUNTRY
code, although we can also just use the language
code without adding the COUNTRY
code.
However, in instances when multiple countries have the same language, it can be useful to add the COUNTRY
clarifier. For example, English in the United States would be en-US
, and English in the United Kingdom would be en-GB
.
Now, in the i18n/messages.js
file, add the following translations:
import { LOCALES } from "./locales"; export const messages = { [LOCALES.ENGLISH]: { learn_to: "Hello, let's learn how to use React-Intl", price_display: "How {n, number, ::currency/USD} is displayed in your selected language", number_display: "This is how {n, number} is formatted in the selected locale", start_today: "Start Today: {d, date}", // menu about_project: "About the project", contact_us: "Contact us", }, [LOCALES.FRENCH]: { learn_to: "Bonjour, apprenons à utiliser React-Intl", price_display: "Comment {n, number, ::currency/USD} $ s'affiche dans la langue sélectionnée", number_display: "Voici comment {n, number} sont formatés dans les paramètres régionaux sélectionnés ", start_today: "Commencez aujourd'hui: {d, date}", // menu about_project: "À propos du projet", contact_us: "Contactez-nous", }, [LOCALES.GERMAN]: { learn_to: "Hallo, lass uns lernen, wie man React-Intl benutzt", price_display: "Wie {n, number, ::currency/USD} in Ihrer ausgewählten Sprache angezeigt wird", number_display: "Auf diese Weise werden {n, number} im ausgewählten Gebietsschema formatiert", start_today: "Beginnen Sie heute: {d, date}", // menu about_project: "Über das Projekt", contact_us: "Kontaktiere uns", }, [LOCALES.JAPANESE]: { learn_to: "こんにちは、React-Intlの使い方を学びましょう", price_display: "選択した言語で{n, number, ::currency/USD}がどのように表示されるか", number_display: "これは、選択したロケールで{n, number}がフォーマットされる方法です。", start_today: "今日から始める:{d, date}", // menu about_project: "プロジェクトについて", contact_us: "お問い合わせ", }, };
This file comprises our application source text and the translations for the supported locales. Note each of the unique IDs and keys, which can be named anything; we will use them to inject their corresponding strings into our application.
For now, let’s focus on the simple strings and ignore the arguments, also known as placeholders, in the curly brackets.
Next, let’s load the data in the message
prop of the IntlProvider
component.
As we can see from the data, we can access the English translation object like so:
messages[LOCALES.ENGLISH]
The same applies to the other locales.
So, let’s assign this English translation to the messages
prop of the provider
component. Later in this guide, we will define a logic that dynamically injects translated messages based on the user’s selected locale.
In the components/App.js
file, import the LOCALES
and messages
at the top of the file:
import { LOCALES } from "../i18n/locales"; import { messages } from "../i18n/messages";
Then, update the <IntlProvider>
so we have the following:
const App = () => { const locale = LOCALES.ENGLISH; return ( <IntlProvider messages={messages[locale]} locale={locale} defaultLocale={LOCALES.ENGLISH} > ... </IntlProvider> ); }; export default App;
From the code, we can deduce that the English data is loaded in the provider
component and can be accessed through the subcomponent.
As mentioned earlier, we will use the FormattedMessage
to format these complex strings. This component is ideal if our message combines dates, numbers, or just simple messages.
We will start by transforming the simple string, “Hello, let’s learn how to use React-Intl.”
Head over to the components/Content.js
file and import the FormattedMessage
:
import { FormattedMessage } from "react-intl";
Then, replace the string with the component:
import { FormattedMessage } from "react-intl"; const Content = () => { return ( <div className="container hero"> <h1><FormattedMessage id="learn_to" /></h1> {/* ... */} </div> ); }; export default Content;
It’s that simple. To test our work, we can manually change the locale
in the App.js
file to JAPANESE
:
const locale = LOCALES.JAPANESE;
Save and see the changes in the frontend:
The FormattedMessage
used in the code above takes an id
prop whose value matches a specific key in the translation file and then returns the corresponding string for the current locale. Remember, this id
is unique across all the supported locales in the translation file.
When we see a message that includes a date, amount, or number, we want to replace them with arguments.
Earlier, we learned how to format these types of values using the FormattedDate
and FormattedNumber
. But here we will use the FormattedMessage
since the values are part of a string.
If we take a look at the translation file, we are replacing these value types by following this pattern: { key, type, format }
.
For instance, we have something like this:
price_display: "How {n, number, ::currency/USD} is displayed in your selected language",
The first element, n
, is the key, and it looks up to the data object to find the appropriate data. The second element is the type of data to interpret, and it conforms to a specific locale.
The third argument — which is optional — allows us to specify additional information about the element type.
In the case above, we are saying that the placeholder of the number
must be formatted as USD currency. Then, React Intl positions the currency. You can find the list of currency codes here.
To transform this type of string using the FormattedMessage
, we’ll have something like this:
<FormattedMessage id="price_display" values={{ n: 59.99 }} />
As expected, the id
locates the translation for the current locale, and the placeholder is replaced by the key
value.
If we apply this logic in our project, the components/Content.js
file now looks like this:
import { FormattedMessage } from "react-intl"; const Content = () => { return ( <div className="container hero"> <h1><FormattedMessage id="learn_to" /></h1> <p><FormattedMessage id="price_display" values={{ n: 59.99 }} /></p> <p><FormattedMessage id="number_display" values={{ n: 2000 }} /></p> <p><FormattedMessage id="start_today" values={{ d: new Date() }} /></p> </div> ); }; export default Content;
Save the file and test the app by changing the locale in the component/App.js
file to another supported language:
const locale = LOCALES.JAPANESE;
Our app should look like this:
Earlier, we learned how to use the <FormattedPlural>
component to handle a simple plural text. In this section, we will use the <FormattedMessage>
to achieve plurals in a text-rich message.
Presently, if we click on the count button in the frontend, we don’t have logic that displays the correct text when the count is singular, that is, when it’s at 1
.
When we formatted the currency using the { key, type, format }
pattern, we can use the same pattern but use plural
and matches
to replace type
and format
, respectively.
By applying this pattern, we can add the following to our English translation object:
click_count: "You clicked {count, plural, one {# time} other {# times}}"
The plural categories, one
and other
, match the singular and the plural forms within the first and second curly brackets, preceded by #
, which represents the count number.
If we translate the message to other locales and apply the same logic, we’ll have the following for German:
click_count: "Sie haben {count, plural, one {# Mal} other {# Mal}} geklickt",
For French:
click_count: "Vous avez cliqué {count, plural, one {# fois} other {# fois}}",
For Japanese:
click_count: "{count, plural, one {# 回} other {# 回}}クリックしました",
Save the file and open the components/Footer.js
file to render the translation using the FormattedMessage
component.
Then, find this element:
<p>You clicked {count} times</p>
Replace the above with the following:
<p> <FormattedMessage id="click_count" values={{ count: count }} /> </p>
We must also import the FormattedMessage
component from react-intl
:
import { // ... FormattedMessage, } from "react-intl";
Save the file again. Let’s reload the frontend and test our work.
We can now add translation support for the menu items. First, import the FormattedMessage
in the components/Header.js
file like so:
import { FormattedMessage } from "react-intl";
We need access to the individual menu key
used in the translation file. To do this, let’s update the menu
array to include the key
:
const menu = [ { key: "about_project", // ... }, { key: "contact_us", // ... }, ];
We can now use the keys to access the translations in the FormattedMessage
.
Begin by finding the following code:
{menu.map(({ title, path }) => ( <li key={title}> <a href={path}>{title}</a> </li> ))}
Then, replace with this:
{menu.map(({ title, path, key }) => ( <li key={title}> <a href={path}> <FormattedMessage id={key} /> </a> </li> ))}
Save the file and test the application.
To localize the frontend button and its call to action text, let’s assign keys to the button in our i18n/message.js
file.
For the English object, add the following:
click_button: "Please click the button below", click_here: "click here",
For French, add this:
click_button: "Veuillez cliquer sur le bouton ci-dessous", click_here: "Cliquez ici",
Add translations for the other locales, such as Japanese and German, using the above code and Google translate.
Once we are done, let’s open the components/Footer.js
and replace the text strings with the FormattedMessage
:
return ( <div className="container mt"> {/* Footer content here */} <p><FormattedMessage id="click_button" /></p> <button onClick={onChange}> <FormattedMessage id="click_here" /> </button> {/* ... */} </div> );
Save the files and reload the frontend. Notice that the button’s description and the button’s text updated in French.
To provide an option to switch locales in the frontend, we must open the components/Header.js
file and add the following above the return
statement:
// Languages const languages = [ { name: "English", code: LOCALES.ENGLISH }, { name: "日本語", code: LOCALES.JAPANESE }, { name: "Français", code: LOCALES.FRENCH }, { name: "Deutsche", code: LOCALES.GERMAN }, ];
Then, import the LOCALES
at the top of the file:
import { LOCALES } from "../i18n/locales";
Next, we will loop through the languages
array to generate our dropdown.
Still in the file, locate this div
container element:
<div className="switcher"> {/* Language switch dropdown here */} </div>
Then, update it to see the following:
<div className="switcher"> {/* Language switch dropdown here */} Languages{" "} <select> {languages.map(({ name, code }) => ( <option key={code} value={code}> {name} </option> ))} </select> </div>
Save it and add this simple style in the src/index.css
file:
.switcher select { width: 99px; height: 30px; }
Save the file again and view the dropdown in the frontend. At the moment, switching between languages doesn’t change the page content, so let’s address that.
If familiar with form handling in React, this process should be a piece of cake. If not, that’s fine because we will do it together.
In React, the form input should be a controlled input unlike that of the HTML form.
To do this, we will add a value
prop and an onChange
event to the select
element. The value
prop is assigned the current locale while the onChange
updates the value based on the user’s selection.
For the meantime, update the select
element to include these props:
<select onChange="" value=""> .. </select>
Since the IntlProvider
is in the parent component, App.js
must also know the current locale, so let’s set up the logic in the file.
We can pass the necessary data down to the Header
child component through the props. This process is called props drilling.
Open the components/App.js
and add a state whose default language is English:
import { useState } from "react"; ... const App = () => { const locale = LOCALES.ENGLISH; const [currentLocale, setCurrentLocale] = useState(locale); return ( <IntlProvider messages={messages[currentLocale]} locale={currentLocale} defaultLocale={LOCALES.ENGLISH} > ... </IntlProvider> ); }; export default App;
Don’t forget to import the useState
Hook from React to get the current locale.
Now, let’s pass the locale to the Header
child component and use it in the select
element.
In the App.js
file, update the <Header />
instance in <IntlProvider />
so we have the following:
<Header currentLocale={currentLocale}/>
Save the file and open the Header.js
to access the data; pass it to the value
prop of the select
element:
... const Header = (props) => { ... return ( <header> <div className="container header_content"> ... <nav> ... </nav> <div className="spacer"></div> <div className="switcher"> {/* Language switch dropdown here */} Languages{" "} <select onChange="" value={props.currentLocale}> ... </select> </div> </div> </header> ); }; export default Header;
Call props
the following:
const Header = (props) => {
Next, we will use the onChange
event to handle updates.
In the App.js
file, add the following handler above the return
statement and pass it down to the Header
component through the props:
const handleChange = (e) => { setCurrentLocale(e.target.value); };
The code now looks like this:
... const App = () => { ... const handleChange = (e) => { setCurrentLocale(e.target.value); }; return ( ... <div> <Header currentLocale={currentLocale} handleChange={handleChange} /> ... </div> ... ); };
Save the file and access the handler inside the Header
component.
Simply update the select
element so we have the following:
<select onChange={props.handleChange} value={props.currentLocale}>
Now we can test our application. It works! Awesome.
Ensure to translate the Languages text beside the dropdown list as well.
Presently, if we switch from the default locale and reload the page, the content goes back to default. We don’t want that.
In this section, we’ll see how to easily persist the selected locale to the browser storage. This means the content in the user’s preferred language is still visible on the page when reloaded and on a subsequent visit, further improving the user’s experience.
First, open the App.js
file and add this code above the return
statement:
//localstorage function getInitialLocal() { // getting stored items const savedLocale = localStorage.getItem("locale"); return savedLocale || LOCALES.ENGLISH; }
Then, update the state to use this function:
const [currentLocale, setCurrentLocale] = useState(getInitialLocal());
Finally, update the handleChange
to include the storage object:
const handleChange = (e) => { setCurrentLocale(e.target.value); // storing locale in the localstorage localStorage.setItem("locale", e.target.value); };
Remove this line from the App.js
file because it is now redundant:
const locale = LOCALES.ENGLISH;
Now, save the file and test the application. Ensure to reload the page to see that the data persists in the storage.
Because we started by accessing the local storage using the getItem()
method to check for any saved locale or return a fallback, the returned locale was assigned to the state variable, currentLocale
.
Likewise, we are also using the setItem()
method to save the user’s selected locale to the Storage
object.
Read about how to persist state of your React component in the local storage to learn more.
We’ve covered pretty much everything we need to know about the React Intl library. We’ve learned how to apply the library to a React application and how to persist the user’s selected locale in the local storage.
I hope you found this guide helpful. Should you have questions or contributions, I am in the comment section. You can also find the entire project source code in this GitHub repository.
Install LogRocket via npm or script tag. LogRocket.init()
must be called client-side, not
server-side
$ npm i --save logrocket // Code: import LogRocket from 'logrocket'; LogRocket.init('app/id');
// Add to your HTML: <script src="https://cdn.lr-ingest.com/LogRocket.min.js"></script> <script>window.LogRocket && window.LogRocket.init('app/id');</script>
Would you be interested in joining LogRocket's developer community?
Join LogRocket’s Content Advisory Board. You’ll help inform the type of content we create and get access to exclusive meetups, social accreditation, and swag.
Sign up nowBuild scalable admin dashboards with Filament and Laravel using Form Builder, Notifications, and Actions for clean, interactive panels.
Break down the parts of a URL and explore APIs for working with them in JavaScript, parsing them, building query strings, checking their validity, etc.
In this guide, explore lazy loading and error loading as two techniques for fetching data in React apps.
Deno is a popular JavaScript runtime, and it recently launched version 2.0 with several new features, bug fixes, and improvements […]
One Reply to "React Intl: Internationalize your React apps"
Nice job