Reaching a global audience with your web application requires support for multiple languages. The good news is that adding multilingual support is not usually complex and brings a plethora of benefits, including increased user engagement and enhanced accessibility, among others. In this post, we'll explain how to internationalize (i18n) React web applications using the React Intl library, focusing on key aspects such as message localization, formatting, language selection, and other important elements.
Keeping up with the latest updates and guidance from the React team, this post will explore the internationalization process of React applications developed using Vite and Create React App. If you're interested in learning about supporting internationalization in Next.js projects, we invite you to consult our dedicated post on that topic.
All code samples used in this section are available on the GitHub repo.
See an online demo.
First things first, let’s create a new React app with Vite.
npm create vite@latest -- --template react
Since this command operates in interactive mode, for the project name enter react-intl-example
.
React Intl is part of Format.JS, a suite of JavaScript libraries designed for internationalization and formatting. This well-documented and maintained library supports ICU Message syntax, a wide range of formatting options, and is compatible with major browsers.
To use the react-intl
library in your project, you need to add it as a dependency.
cd react-intl-example
npm i react-intl
The next step is to create the localization files for the required locales. It is good practice to keep all localization files in one place (e.g., src/lang
). In this example, we will add three JSON files under the lang
directory: ar.json
, en.json
, and es-MX.json
. These files are going to hold translations for the Arabic, English, and Mexican Spanish languages.
Below, you can see how the project structure should look after you have added these files.
react-intl-example
|-- public
|-- src
| |-- assets
| |-- lang
| |-- ar.json
| |-- en.json
| |-- es-MX.json
| |-- App.jsx
| |-- main.jsx
| |-- ...
|-- ...
|-- package.json
|-- package-lock.json
As we will be using localization messages later, let's populate the files we added with some examples.
ar.json
:
{
"message.simple": "رسالة بسيطة.",
"message.argument": "مرحبًا {name}! 👋",
"message.plural": "{count, plural, zero {لا توجد عناصر} one {# بند} two {# بنود} few {# عناصر} many {# بندا} other {# قطعة}}",
"message.select": "{gender, select, male {السيد} female {السيدة} other {المستعمل}}",
"message.text-format": "مرحبًا <b>John</b>!",
"message.number-format": "عدد مهيأ: {num, number, ::K}",
"message.currency-format": "عملة منسقة: {amount, number, ::currency/USD}"
}
en.json
:
{
"message.simple": "A simple message.",
"message.argument": "Hi, {name}! 👋",
"message.plural": "{count, plural, one {# item} other {# items}}",
"message.select": "{gender, select, male {Mr} female {Mrs} other {User}}",
"message.text-format": "Hi, <b>John</b>!",
"message.number-format": "Formatted number: {num, number, ::K}",
"message.currency-format": "Formatted currency: {amount, number, ::currency/USD}"
}
es-MX.json
:
{
"message.simple": "Un mensaje sencillo.",
"message.argument": "¡Hola, {name}! 👋",
"message.plural": "{count, plural, one {# articulo} other {# artículos}}",
"message.select": "{gender, select, male {Sr.} female {Sra.} other {Usuario}}",
"message.text-format": "¡Hola, <b>John</b>!",
"message.number-format": "Número formateado: {num, number, ::K}",
"message.currency-format": "Moneda formateada: {amount, number, ::currency/USD}"
}
While we have completed most of the necessary tasks, there's still a need to modify the main app file to support internationalization. To achieve this, our next step involves updating the App.jsx
file. More concretely, this means wrapping the whole app with the IntlProvider
, which will enable us to use localization features across all app subcomponents. Furthermore, we will incorporate a feature to detect the user's preferred language and optimize performance by loading only necessary translations. Lastly, we will add a language switcher and configure the appropriate text direction settings.
The react-intl
library uses the provider pattern to pass the needed configuration through the tree of descendant nodes. This approach ensures that throughout the entire app, we can use localized messages and proper formatting. As you can see in the following example, the top-level app component is wrapped with the IntlProvider
from react-intl
. There, we pass two props to the IntlProvider
, locale
and messages
. As their names suggest, the locale
prop specifies the selected locale within the app, and the messages
prop contains the messages corresponding to that locale.
The App.jsx
file:
import React from "react";
import { IntlProvider } from "react-intl";
import messages from "./lang/en.json";
let locale = "en";
function LocalizationWrapper() {
return (
<IntlProvider locale={locale} messages={messages}>
<App />
</IntlProvider>
);
}
export default LocalizationWrapper;
function App(props) {
return <div>My App</div>;
}
By detecting the user’s preferred language (the language of the browser), we are going to set the initial locale of the app. If the user’s preferred language is not supported, the app will use the default, English. The locale resolution logic presented here is basic. In practice, a more comprehensive check would likely be implemented, possibly including region control as well.
The App.jsx
file:
import React from "react";
import { IntlProvider } from "react-intl";
import messagesAr from "./lang/ar.json";
import messagesEn from "./lang/en.json";
import messagesEsMx from "./lang/es-MX.json";
let initLocale = "en";
if (navigator.language === "es-MX") {
initLocale = "es-MX";
} else if (navigator.language === "ar") {
initLocale = "ar";
}
function loadMessages(locale) {
switch (locale) {
case "ar":
return messagesAr;
case "en":
return messagesEn;
case "es-MX":
return messagesEsMx;
default:
return messagesEn;
}
}
function LocalizationWrapper() {
return (
<IntlProvider locale={initLocale} messages={loadMessages(initLocale)}>
<App />
</IntlProvider>
);
}
export default LocalizationWrapper;
function App(props) { ... }
Using dynamic imports, we are loading localization messages only when they are needed. Moreover, this approach improves app performance since only the necessary data is loaded. The example below contains a slightly updated loadMessages
function, which returns localization messages as a Promise
for the specified locale.
The App.jsx
file:
import React, { useState, useEffect } from "react";
import { IntlProvider } from "react-intl";
let initLocale = "en";
if (navigator.language === "es-MX") {
initLocale = "es-MX";
} else if (navigator.language === "ar") {
initLocale = "ar";
}
function loadMessages(locale) {
switch (locale) {
case "ar":
return import("./lang/ar.json");
case "en":
return import("./lang/en.json");
case "es-MX":
return import("./lang/es-MX.json");
default:
return import("./lang/en.json");
}
}
function LocalizationWrapper() {
const [locale, setLocale] = useState(initLocale);
const [messages, setMessages] = useState(null);
useEffect(() => {
loadMessages(locale).then((data) => setMessages(data.default));
}, [locale]);
return messages ? (
<IntlProvider locale={locale} messages={messages}>
<App />
</IntlProvider>
) : null;
}
export default LocalizationWrapper;
function App(props) { ... }
Since one of the added languages, Arabic, uses a non-default text direction (rtl
), explicit handling of this is needed. For that reason, we will add the getDirection
function, which will return the appropriate direction for the given locale. To demonstrate how this works in practice, we will adjust the dir
attribute of the container element within the App
component. Note that you can also set the dir
attribute on the html
element to adjust the content of the entire HTML page. However, for simplicity, we will not address that in this post.
The App.jsx
file:
import React, { useState, useEffect } from "react";
import { IntlProvider } from "react-intl";
let initLocale = "en";
if (navigator.language === "es-MX") {
initLocale = "es-MX";
} else if (navigator.language === "ar") {
initLocale = "ar";
}
function loadMessages(locale) {
switch (locale) {
case "ar":
return import("./lang/ar.json");
case "en":
return import("./lang/en.json");
case "es-MX":
return import("./lang/es-MX.json");
default:
return import("./lang/en.json");
}
}
function getDirection(locale) {
switch (locale) {
case "ar":
return "rtl";
case "en":
return "ltr";
case "es-MX":
return "ltr";
default:
return "ltr";
}
}
function LocalizationWrapper() {
const [locale, setLocale] = useState(initLocale);
const [messages, setMessages] = useState(null);
useEffect(() => {
loadMessages(locale).then((data) => setMessages(data.default));
}, [locale]);
return messages ? (
<IntlProvider locale={locale} messages={messages}>
<App direction={getDirection(locale)} />
</IntlProvider>
) : null;
}
export default LocalizationWrapper;
function App({ direction }) {
return <div dir={direction}>My App</div>;
}
While automatically setting the app's initial language based on the browser's settings is effective in most scenarios, offering a manual language selection option is also a good practice. To achieve this, we will extend the LocalizationWrapper
component to include the currently selected locale. This will allow us to update the whole app on a language change. We will also add a basic language switcher within the App
component, and pass the necessary props to it to ensure seamless functionality.
The App.jsx
file:
import React, { useState, useEffect } from "react";
import { IntlProvider } from "react-intl";
let initLocale = "en";
if (navigator.language === "es-MX") {
initLocale = "es-MX";
} else if (navigator.language === "ar") {
initLocale = "ar";
}
function loadMessages(locale) { ... }
function getDirection(locale) { ... }
function LocalizationWrapper() {
const [locale, setLocale] = useState(initLocale);
const [messages, setMessages] = useState(null);
useEffect(() => {
loadMessages(locale).then((data) => setMessages(data.default));
}, [locale]);
return messages ? (
<IntlProvider locale={locale} messages={messages}>
<App
locale={locale}
direction={getDirection(locale)}
onLocaleChange={(locale) => setLocale(locale)}
/>
</IntlProvider>
) : null;
}
export default LocalizationWrapper;
function App({ locale, direction, onLocaleChange }) {
return (
<div>
<div style={{ textAlign: "center" }}>
<select value={locale} onChange={(e) => onLocaleChange(e.target.value)}>
<option value="en">en</option>
<option value="es-MX">es-MX</option>
<option value="ar">ar</option>
</select>
</div>
<div dir={direction}>My App</div>
</div>
);
}
The final step in our app is to implement the localization messages we've prepared, and observe how our app functions. With the help of the useIntl
hook and the Formatted*
components from the react-intl
library, we are able to access these localization messages. The posted example demonstrates two different methods of access: an imperative and a declarative approach. Here, we will only demonstrate how to use these two methods to access the localization messages, with more details about them to be provided in the following section.
The App.jsx
file:
import React, { useState, useEffect } from "react";
import {
IntlProvider,
FormattedMessage,
FormattedList,
useIntl,
} from "react-intl";
let initLocale = "en";
if (navigator.language === "es-MX") {
initLocale = "es-MX";
} else if (navigator.language === "ar") {
initLocale = "ar";
}
function loadMessages(locale) { ... }
function getDirection(locale) { ... }
function LocalizationWrapper() { ... }
export default LocalizationWrapper;
function App({ locale, direction, onLocaleChange }) {
const intl = useIntl();
return (
<div>
<div style={{ textAlign: "center" }}>
<select value={locale} onChange={(e) => onLocaleChange(e.target.value)}>
<option value="en">en</option>
<option value="es-MX">es-MX</option>
<option value="ar">ar</option>
</select>
</div>
<div dir={direction} style={{ padding: 20 }} data-testid="examples">
<h3>Declarative examples</h3>
<FormattedMessage id="message.simple" />
<br />
<FormattedMessage id="message.argument" values={{ name: "John" }} />
<br />
<FormattedMessage id="message.plural" values={{ count: 6 }} />
<br />
<FormattedMessage id="message.select" values={{ gender: "female" }} />
<br />
<FormattedMessage id="message.text-format" values={{ b: (value) => <b>{value}</b> }} />
<br />
<FormattedMessage id="message.number-format" values={{ num: 7500 }} />
<br />
<FormattedMessage id="message.currency-format" values={{ amount: 7.5 }} />
<br />
<FormattedList type="conjunction" value={["foo", "bar", "baz"]} />
<h3>Imperative examples</h3>
{intl.formatMessage({ id: "message.simple" })}
<br />
{intl.formatMessage({ id: "message.argument" }, { name: "John" })}
<br />
{intl.formatMessage({ id: "message.plural" }, { count: 5 })}
<br />
{intl.formatMessage({ id: "message.select" }, { gender: "female" })}
<br />
{intl.formatMessage({ id: "message.text-format" }, { b: (value) => <b>{value}</b> })}
<br />
{intl.formatMessage({ id: "message.number-format" }, { num: 7500 })}
<br />
{intl.formatMessage({ id: "message.currency-format" }, { amount: 7.5 })}
<br />
{intl.formatList(["foo", "bar", "baz"], { type: "conjunction" })}
</div>
</div>
);
}
Depending on your needs, you can format a message in the imperative and the declarative way. In most cases, they should have the same capabilities. Here are some guidelines:
Use the imperative approach for:
title
, aria-label
).Use the declarative approach for:
Imperative usage example:
intl.formatMessage({ id: "common.aria-label.confirm" })
Declarative usage example:
<FormattedMessage id="page.home.greeting" />
Below are some examples of the most common formatting options.
Simple message example:
// "message.simple": "A simple message."
intl.formatMessage({ id: "message.simple" })
<FormattedMessage id="message.simple" />
Argument message example:
// "message.argument": "Hi, {name}! 👋"
intl.formatMessage({ id: "message.argument" }, { name: "John" })
<FormattedMessage id="message.argument" values={{ name: "John" }} />
Plural message example:
// "message.plural": "{count, plural, one {# item} other {# items}}"
intl.formatMessage({ id: "message.plural" }, { count: 5 })
<FormattedMessage id="message.plural" values={{ count: 5 }} />
Select message example:
// "message.select": "{gender, select, male {Mr} female {Mrs} other {User}}"
intl.formatMessage({ id: "message.select" }, { gender: "female" })
<FormattedMessage id="message.select" values={{ gender: "female" }} />
Text formatting example:
// "message.text-format": "Hi, <b>John</b>!"
intl.formatMessage({ id: "message.text-format" }, { b: (value) => <b>{value}</b> })
<FormattedMessage id="message.text-format" values={{ b: (value) => <b>{value}</b> }} />
Currency formatting example:
intl.formatNumber(7.5, { style: "currency", currency: "USD" })
<FormattedNumber value={7.5} style="currency" currency="USD" />
Date formatting example:
intl.formatDate(Date.now(), { year: "numeric", month: "long", day: "2-digit" })
<FormattedDate value={Date.now()} year="numeric" month="long" day="2-digit" />
Time formatting example:
<FormattedTime value={Date.now()} />
{intl.formatTime(Date.now())}
Relative time formatting example:
intl.formatRelativeTime(-5, "second", { style: "narrow" })
<FormattedRelativeTime value={0} numeric="auto" updateIntervalInSeconds={1} />
While it is good to start with internationalization from the very beginning of the app development, it is not always feasible. Various factors such as shifting priorities, divergent planning, and user requirements contribute to this. It's important to note, however, that extracting localization messages via React Intl is feasible using the @formatjs/cli
package. While this subject is distinct and won't be covered in this post, it's beneficial to be aware of it.
This post provided an overview of the react-intl
library, illustrating its application in React projects. We delved into the two predominant methods for developing React applications, emphasizing the integration of internationalization. It's crucial to understand that the process begins with enabling the app for multilingual support, followed by the translation of all messages into your chosen languages. Typically, this involves teaming up with translators, assessing the quality of translations, implementing over-the-air translation updates through an S3 bucket, and other related activities. Fortunately, the Localizely platform is designed to facilitate these tasks. We invite you to try its free version and explore how it can boost your efficiency.
All code samples used in this section are available on the GitHub repo.
See an online demo.
First things first, let’s create a new React app with Create React App.
npx create-react-app react-intl-example
React Intl is part of Format.JS, a suite of JavaScript libraries designed for internationalization and formatting. This well-documented and maintained library supports ICU Message syntax, a wide range of formatting options, and is compatible with major browsers.
To use the react-intl
library in your project, you need to add it as a dependency.
cd react-intl-example
npm i react-intl
Note: If installing the react-intl
fails, remove package-lock.json
and node_modules
and try again.
The next step is to create the localization files for the required locales. It is good practice to keep all localization files in one place (e.g., src/lang
). In this example, we will add three JSON files under the lang
directory: ar.json
, en.json
, and es-MX.json
. These files are going to hold translations for the Arabic, English, and Mexican Spanish languages.
Below, you can see how the project structure should look after you have added these files.
react-intl-example
|-- src
| |-- lang
| |-- ar.json
| |-- en.json
| |-- es-MX.json
| |-- App.js
| |-- App.test.js
| |-- index.js
| |-- ...
|-- ...
|-- package.json
|-- package-lock.json
As we will be using localization messages later, let's populate the files we added with some examples.
ar.json
:
{
"message.simple": "رسالة بسيطة.",
"message.argument": "مرحبًا {name}! 👋",
"message.plural": "{count, plural, zero {لا توجد عناصر} one {# بند} two {# بنود} few {# عناصر} many {# بندا} other {# قطعة}}",
"message.select": "{gender, select, male {السيد} female {السيدة} other {المستعمل}}",
"message.text-format": "مرحبًا <b>John</b>!",
"message.number-format": "عدد مهيأ: {num, number, ::K}",
"message.currency-format": "عملة منسقة: {amount, number, ::currency/USD}"
}
en.json
:
{
"message.simple": "A simple message.",
"message.argument": "Hi, {name}! 👋",
"message.plural": "{count, plural, one {# item} other {# items}}",
"message.select": "{gender, select, male {Mr} female {Mrs} other {User}}",
"message.text-format": "Hi, <b>John</b>!",
"message.number-format": "Formatted number: {num, number, ::K}",
"message.currency-format": "Formatted currency: {amount, number, ::currency/USD}"
}
es-MX.json
:
{
"message.simple": "Un mensaje sencillo.",
"message.argument": "¡Hola, {name}! 👋",
"message.plural": "{count, plural, one {# articulo} other {# artículos}}",
"message.select": "{gender, select, male {Sr.} female {Sra.} other {Usuario}}",
"message.text-format": "¡Hola, <b>John</b>!",
"message.number-format": "Número formateado: {num, number, ::K}",
"message.currency-format": "Moneda formateada: {amount, number, ::currency/USD}"
}
While we have completed most of the necessary tasks, there's still a need to modify the main app file to support internationalization. To achieve this, our next step involves updating the App.js
file. More concretely, this means wrapping the whole app with the IntlProvider
, which will enable us to use localization features across all app subcomponents. Furthermore, we will incorporate a feature to detect the user's preferred language and optimize performance by loading only necessary translations. Lastly, we will add a language switcher and configure the appropriate text direction settings.
The react-intl
library uses the provider pattern to pass the needed configuration through the tree of descendant nodes. This approach ensures that throughout the entire app, we can use localized messages and proper formatting. As you can see in the following example, the top-level app component is wrapped with the IntlProvider
from react-intl
. There, we pass two props to the IntlProvider
, locale
and messages
. As their names suggest, the locale
prop specifies the selected locale within the app, and the messages
prop contains the messages corresponding to that locale.
The App.js
file:
import React from "react";
import { IntlProvider } from "react-intl";
import messages from "./lang/en.json";
let locale = "en";
function LocalizationWrapper() {
return (
<IntlProvider locale={locale} messages={messages}>
<App />
</IntlProvider>
);
}
export default LocalizationWrapper;
function App(props) {
return <div>My App</div>;
}
By detecting the user’s preferred language (the language of the browser), we are going to set the initial locale of the app. If the user’s preferred language is not supported, the app will use the default, English. The locale resolution logic presented here is basic. In practice, a more comprehensive check would likely be implemented, possibly including region control as well.
The App.js
file:
import React from "react";
import { IntlProvider } from "react-intl";
import messagesAr from "./lang/ar.json";
import messagesEn from "./lang/en.json";
import messagesEsMx from "./lang/es-MX.json";
let initLocale = "en";
if (navigator.language === "es-MX") {
initLocale = "es-MX";
} else if (navigator.language === "ar") {
initLocale = "ar";
}
function loadMessages(locale) {
switch (locale) {
case "ar":
return messagesAr;
case "en":
return messagesEn;
case "es-MX":
return messagesEsMx;
default:
return messagesEn;
}
}
function LocalizationWrapper() {
return (
<IntlProvider locale={initLocale} messages={loadMessages(initLocale)}>
<App />
</IntlProvider>
);
}
export default LocalizationWrapper;
function App(props) { ... }
Using dynamic imports, we are loading localization messages only when they are needed. Moreover, this approach improves app performance since only the necessary data is loaded. The example below contains a slightly updated loadMessages
function, which returns localization messages as a Promise
for the specified locale.
The App.js
file:
import React, { useState, useEffect } from "react";
import { IntlProvider } from "react-intl";
let initLocale = "en";
if (navigator.language === "es-MX") {
initLocale = "es-MX";
} else if (navigator.language === "ar") {
initLocale = "ar";
}
function loadMessages(locale) {
switch (locale) {
case "ar":
return import("./lang/ar.json");
case "en":
return import("./lang/en.json");
case "es-MX":
return import("./lang/es-MX.json");
default:
return import("./lang/en.json");
}
}
function LocalizationWrapper() {
const [locale, setLocale] = useState(initLocale);
const [messages, setMessages] = useState(null);
useEffect(() => {
loadMessages(locale).then(setMessages);
}, [locale]);
return messages ? (
<IntlProvider locale={locale} messages={messages}>
<App />
</IntlProvider>
) : null;
}
export default LocalizationWrapper;
function App(props) { ... }
Since one of the added languages, Arabic, uses a non-default text direction (rtl
), explicit handling of this is needed. For that reason, we will add the getDirection
function, which will return the appropriate direction for the given locale. To demonstrate how this works in practice, we will adjust the dir
attribute of the container element within the App
component. Note that you can also set the dir
attribute on the html
element to adjust the content of the entire HTML page. However, for simplicity, we will not address that in this post.
The App.js
file:
import React, { useState, useEffect } from "react";
import { IntlProvider } from "react-intl";
let initLocale = "en";
if (navigator.language === "es-MX") {
initLocale = "es-MX";
} else if (navigator.language === "ar") {
initLocale = "ar";
}
function loadMessages(locale) {
switch (locale) {
case "ar":
return import("./lang/ar.json");
case "en":
return import("./lang/en.json");
case "es-MX":
return import("./lang/es-MX.json");
default:
return import("./lang/en.json");
}
}
function getDirection(locale) {
switch (locale) {
case "ar":
return "rtl";
case "en":
return "ltr";
case "es-MX":
return "ltr";
default:
return "ltr";
}
}
function LocalizationWrapper() {
const [locale, setLocale] = useState(initLocale);
const [messages, setMessages] = useState(null);
useEffect(() => {
loadMessages(locale).then(setMessages);
}, [locale]);
return messages ? (
<IntlProvider locale={locale} messages={messages}>
<App direction={getDirection(locale)} />
</IntlProvider>
) : null;
}
export default LocalizationWrapper;
function App({ direction }) {
return <div dir={direction}>My App</div>;
}
While automatically setting the app's initial language based on the browser's settings is effective in most scenarios, offering a manual language selection option is also a good practice. To achieve this, we will extend the LocalizationWrapper
component to include the currently selected locale. This will allow us to update the whole app on a language change. We will also add a basic language switcher within the App
component, and pass the necessary props to it to ensure seamless functionality.
The App.js
file:
import React, { useState, useEffect } from "react";
import { IntlProvider } from "react-intl";
let initLocale = "en";
if (navigator.language === "es-MX") {
initLocale = "es-MX";
} else if (navigator.language === "ar") {
initLocale = "ar";
}
function loadMessages(locale) { ... }
function getDirection(locale) { ... }
function LocalizationWrapper() {
const [locale, setLocale] = useState(initLocale);
const [messages, setMessages] = useState(null);
useEffect(() => {
loadMessages(locale).then(setMessages);
}, [locale]);
return messages ? (
<IntlProvider locale={locale} messages={messages}>
<App
locale={locale}
direction={getDirection(locale)}
onLocaleChange={(locale) => setLocale(locale)}
/>
</IntlProvider>
) : null;
}
export default LocalizationWrapper;
function App({ locale, direction, onLocaleChange }) {
return (
<div>
<div style={{ textAlign: "center" }}>
<select value={locale} onChange={(e) => onLocaleChange(e.target.value)}>
<option value="en">en</option>
<option value="es-MX">es-MX</option>
<option value="ar">ar</option>
</select>
</div>
<div dir={direction}>My App</div>
</div>
);
}
The final step in our app is to implement the localization messages we've prepared, and observe how our app functions. With the help of the useIntl
hook and the Formatted*
components from the react-intl
library, we are able to access these localization messages. The posted example demonstrates two different methods of access: an imperative and a declarative approach. Here, we will only demonstrate how to use these two methods to access the localization messages, with more details about them to be provided in the following section.
The App.js
file:
import React, { useState, useEffect } from "react";
import {
IntlProvider,
FormattedMessage,
FormattedList,
useIntl,
} from "react-intl";
let initLocale = "en";
if (navigator.language === "es-MX") {
initLocale = "es-MX";
} else if (navigator.language === "ar") {
initLocale = "ar";
}
function loadMessages(locale) { ... }
function getDirection(locale) { ... }
function LocalizationWrapper() { ... }
export default LocalizationWrapper;
function App({ locale, direction, onLocaleChange }) {
const intl = useIntl();
return (
<div>
<div style={{ textAlign: "center" }}>
<select value={locale} onChange={(e) => onLocaleChange(e.target.value)}>
<option value="en">en</option>
<option value="es-MX">es-MX</option>
<option value="ar">ar</option>
</select>
</div>
<div dir={direction} style={{ padding: 20 }} data-testid="examples">
<h3>Declarative examples</h3>
<FormattedMessage id="message.simple" />
<br />
<FormattedMessage id="message.argument" values={{ name: "John" }} />
<br />
<FormattedMessage id="message.plural" values={{ count: 6 }} />
<br />
<FormattedMessage id="message.select" values={{ gender: "female" }} />
<br />
<FormattedMessage id="message.text-format" values={{ b: (value) => <b>{value}</b> }} />
<br />
<FormattedMessage id="message.number-format" values={{ num: 7500 }} />
<br />
<FormattedMessage id="message.currency-format" values={{ amount: 7.5 }} />
<br />
<FormattedList type="conjunction" value={["foo", "bar", "baz"]} />
<h3>Imperative examples</h3>
{intl.formatMessage({ id: "message.simple" })}
<br />
{intl.formatMessage({ id: "message.argument" }, { name: "John" })}
<br />
{intl.formatMessage({ id: "message.plural" }, { count: 5 })}
<br />
{intl.formatMessage({ id: "message.select" }, { gender: "female" })}
<br />
{intl.formatMessage({ id: "message.text-format" }, { b: (value) => <b>{value}</b> })}
<br />
{intl.formatMessage({ id: "message.number-format" }, { num: 7500 })}
<br />
{intl.formatMessage({ id: "message.currency-format" }, { amount: 7.5 })}
<br />
{intl.formatList(["foo", "bar", "baz"], { type: "conjunction" })}
</div>
</div>
);
}
Depending on your needs, you can format a message in the imperative and the declarative way. In most cases, they should have the same capabilities. Here are some guidelines:
Use the imperative approach for:
title
, aria-label
).Use the declarative approach for:
Imperative usage example:
intl.formatMessage({ id: "common.aria-label.confirm" })
Declarative usage example:
<FormattedMessage id="page.home.greeting" />
Below are some examples of the most common formatting options.
Simple message example:
// "message.simple": "A simple message."
intl.formatMessage({ id: "message.simple" })
<FormattedMessage id="message.simple" />
Argument message example:
// "message.argument": "Hi, {name}! 👋"
intl.formatMessage({ id: "message.argument" }, { name: "John" })
<FormattedMessage id="message.argument" values={{ name: "John" }} />
Plural message example:
// "message.plural": "{count, plural, one {# item} other {# items}}"
intl.formatMessage({ id: "message.plural" }, { count: 5 })
<FormattedMessage id="message.plural" values={{ count: 5 }} />
Select message example:
// "message.select": "{gender, select, male {Mr} female {Mrs} other {User}}"
intl.formatMessage({ id: "message.select" }, { gender: "female" })
<FormattedMessage id="message.select" values={{ gender: "female" }} />
Text formatting example:
// "message.text-format": "Hi, <b>John</b>!"
intl.formatMessage({ id: "message.text-format" }, { b: (value) => <b>{value}</b> })
<FormattedMessage id="message.text-format" values={{ b: (value) => <b>{value}</b> }} />
Currency formatting example:
intl.formatNumber(7.5, { style: "currency", currency: "USD" })
<FormattedNumber value={7.5} style="currency" currency="USD" />
Date formatting example:
intl.formatDate(Date.now(), { year: "numeric", month: "long", day: "2-digit" })
<FormattedDate value={Date.now()} year="numeric" month="long" day="2-digit" />
Time formatting example:
<FormattedTime value={Date.now()} />
{intl.formatTime(Date.now())}
Relative time formatting example:
intl.formatRelativeTime(-5, "second", { style: "narrow" })
<FormattedRelativeTime value={0} numeric="auto" updateIntervalInSeconds={1} />
While it is good to start with internationalization from the very beginning of the app development, it is not always feasible. Various factors such as shifting priorities, divergent planning, and user requirements contribute to this. It's important to note, however, that extracting localization messages via React Intl is feasible using the @formatjs/cli
package. While this subject is distinct and won't be covered in this post, it's beneficial to be aware of it.
This post provided an overview of the react-intl
library, illustrating its application in React projects. We delved into the two predominant methods for developing React applications, emphasizing the integration of internationalization. It's crucial to understand that the process begins with enabling the app for multilingual support, followed by the translation of all messages into your chosen languages. Typically, this involves teaming up with translators, assessing the quality of translations, implementing over-the-air translation updates through an S3 bucket, and other related activities. Fortunately, the Localizely platform is designed to facilitate these tasks. We invite you to try its free version and explore how it can boost your efficiency.
Like this article? Share it!
Zoran is a Software Engineer at Localizely. His primary interest is web development, but he also has a solid background in other technologies. For the last few years, he has been involved in the development of software localization tools.
Subscribe to the Localizely blog newsletter for quality product content in your inbox.
Related