Mobify DevCenter

Internationalization and Localization with react-intl


Some of the advice in this article is only applicable to projects that choose to use Redux for state management.

Progressive Web Apps often need to support the needs of different languages, currencies, or other unique formats that are unique to various regions, cultures and customs. In computing, the practice of supporting these various locales is called internationalization and localization. These terms are abbreviated to i18n and l10n, respectively. For the rest of this document, we will use the term i18n.

i18n Dependencies

By default, your PWA does not come with any internationalization support installed.

We recommend installing a library called react-intl, which has a robust array of i18n features, from formatting text and numbers, to displaying localized date and times, and more. A brief overview of available features is listed below, but refer to the library’s official documentation for a complete list of feature details.

  • Date and time formatting APIs
  • Number and pluralization formatting APIs
  • String formatting APIs
  • And more!

All of the instructions provided in this guide assume you are using the react-intl library.

How to install react-intl in your SSR PWA

In this guide we walk you through how to add the react-intl library and supporting Node.js functionality for the library.

Node.js uses an underlying library called ICU to implement the internatlization functionality provided by react-intl. ICU is a library which allow you add internationalization to your site for any country in the world. full-icu needs to be installed as a dev dependency because AWS Lambdas running NodeJS already has full-icu installed.

Browsers obtain the underlying internationalization functionality from the Intl API.

Steps to installing react-intl and full-icu

  • npm i react-intl
  • npm i --save-dev full-icu
  • Import and use react-intl components and objects into your isomorphic PWAs (as described below)
  • When running your app locally ensure you are setting the NODE_ICU_DATA environment variable => NODE_ICU_DATA="node_modules/full-icu" npm run start

Using react-intl Functions and Components

Using react-intl components is as simple as importing them from the library and using them in your React components. Just be aware that the IntlProvider component already wraps the application. This means the react-intl components are ready to use with no further setup.

import {FormattedMessage, FormattedNumber} from 'react-intl'
// Formatted messages require IDs. See the Translations section below
// for details
const translationKey = ''
const MyComponent = ({text, currency}) => (
<FormattedMessage id={translationKey} />
<FormattedNumber value="10" style="currency" currency={currency} />

However, using react-intl’s functions should be done in combination with injectIntl. See the example below.

import {injectIntl} from 'react-intl'
const MyComponent = ({
intl, // this prop comes from `injectIntl`
}) => (
<span>{intl.formatMessage({id: '...'})}</span>
<span>{intl.formatNumber(10, {style: 'currency', currency: currency})}</span>
// The component wrapped by `injectIntl` will receive the `intl` prop, from
// which react-intl's numerous APIs can be used
const WrappedMyComponent = injectIntl(MyComponent)


Translations files are used to store dictionaries of messages. Each message is a key and value pair of identifiers and a message localized according to its respective language. These messages can then be referred to when using the <FormattedMessage> component or the formatMessage() function to dynamically output the message in the active locale. See the below Switching Locales section for details on dynamically switching locales.

In your PWA, all translations of a single locale are imported into their own top level translation file in <PROJECT_DIR>/app/config/translations/*.js where the * is the locale key. For example…

* <PROJECT_DIR>/app/config/translations/en.js
import {homeMessages} from '<PROJECT_DIR>/app/translations/en'
export default {

The actual translations themselves are stored close together with whatever component they relate to. For example, the home page translations exist in <PROJECT_DIR>/app/pages/home/translations/*.js.

* <PROJECT_DIR>/app/translations/en.js
export const homeMessages = {
'home.example.message': 'This is an English translated message!'

Using the above translation would then be as simple as referencing its translation id in the FormattedMessage component or formatMessage() function.

const HomeComponent = (props, context) => {
const messageString = context.intl.formatMessage(context, {id: 'home.example.message'})
const messageComponent = <FormattedMessage id="home.example.message">
HomeComponent.contextTypes = {
intl: PropTypes.object

Organizing Translation Files

There are two important concepts to understand how translation files are stored: they are stored close to the code where they are used, and all translation files are ultimately imported into a single master translation file.

This architecture is similar to how the PWA’s routes are structured. One route is stored along side all its other related code (styles, tests, actions, partials, etc.) and ultimately pulled into a single master file (the router.jsx file).

The main advantage of this architecture is that it keeps all related code close together.

└── /translations
│ en.js // All English translations are imported here
│ jp.js // All Japanese translations are imported here
└── /pages
└── /home
│ └── /translations
│ en.js
│ jp.js
└── /product-details
│ └── /translations
│ en.js
│ jp.js
└── /product-list
│ └── /translations
│ en.js
│ jp.js

Using the example files from above, the following JavaScript example demonstrates what the master /<PROJECT_DIR>/app/config/translations/en.js file looks like:

import {homeMessages} from '<PROJECT_DIR>/app/pages/home/translations/en'
import {productDetailsMessages} from '<PROJECT_DIR>/app/pages/product-details/translations/en'
import {productListMessages} from '<PROJECT_DIR>/app/pages/product-list/translations/en'
export default {

When the project is compiled into a bundle, Webpack is configured to code split the localizations into their own files to be downloaded by the user as needed. So that means that a project with English and Japanese, as in the above examples, Webpack will create both en.js and jp.js, which the user will automatically download and use.

Translation Key Name Conventions

During the course of your internationalized build you will need to add a number of keys to your translation dictionary. In typical ecommerce builds this dictionary can end up getting quite large, so we’ve come up with a few conventions that should help you avoid these pitfalls.

Key names should include:

  • The descriptor of a component
  • One or more sub-elements within that component
  • The specific place in the component where the translation is

Specifically we suggest using this format


A specific example for the text of an expand button in a product description of a product details page might look like…


In the case where a key is used in multiple areas of the site, and not just in a specific container, use common as the component instead. An example of this would be…


To help reduce the size of your bundle and improve performance of your build, you can minimize the number of keys with shared translation values by providing just one common key value pair like in the example below.

// Duplicate example 1
// /<PROJECT_DIR>/app/pages/checkout-shipping/translations/en.js
'checkoutShipping.addressForm.firstName.label': 'First Name',
// Duplicate example 2
// /<PROJECT_DIR>/app/pages/checkout-payment/translations/en.js
'checkoutPayment.addressForm.firstName.label': 'First Name',
// Duplicate example 3
// /<PROJECT_DIR>/app/pages/registration/translations/en.js
'registration.firstName.label': 'First Name',
// All the above could have just been summarized into a single key, located
// somewhere common
'common.forms.firstName.label': 'First Name',

Translations in Redux Thunk Actions

Unfortunately, react-intl doesn’t provide a way to access translations or formatting functions within a thunk action. We only get access to those thunks within a component or container.

Instead, we must take a few steps to work around this limitation with a technique that involves injecting react-intl’s intl object into the thunk actions as an argument.

By passing the full intl object, we get access to not only the translations, but also all of the formatting functions we might need.

import {injectIntl} from 'react-intl'
import {connect} from 'react-redux'
const MyContainer = ({myThunkAction, intl}) => {
const click = () => {
// Here we pass the full `intl` object into our thunk action!
return (
<Button onClick={click}>
// ...
const mapStateToProps = // ...
const mapDispatchToProps = {
export default connect(mapStateToProps, mapDispatchToProps)(
// injectIntl gives our component the `intl` object as prop

By using the technique as outlined above, your thunk action gains full access to react-intl’s full suite of functions. You can then do things like…

// Access a translation directly off of the messages object:
// Use the formatMessage function on the `intl` object
intl.formatMessage({id: 'form.firstName.invalid'})
// And more! See react-intl documentation for details.


Locales are used by react-intl to support its pluralization and time formatting features.

By default, your PWA comes pre-baked with two languages: English and Japanese. It’s possible to add (or remove) locales. To do so, you import the necessary locale data and register it with the addLocaleData function.

* <PROJECT_DIR>/app/components/intl/index.js
import en from 'react-intl/locale-data/en'
// import ja from 'react-intl/locale-data/ja' // Comment out or delete an unwanted locale
import fr from 'react-intl/locale-data/fr' // Import new locales as needed
// ...
class ProxyIntlProvider extends React.Component {
componentWillMount() {
if (typeof addLocaleData !== UNDEFINED) {
// ...ja, // Comment out or delete the registering of an unwanted locale // add your new locale to the array passed to `addLocaleData`
// ...

For further reading, see react-intl’s Loading Locale Data documentation.

Switching Locales

Switching locales is as easy as running the following…

import {changeLocale} from '../pages/app/actions'
// ...
const locale = 'en' // or which ever locale you want

This will update the Redux store so the locale is set to en (or your desired locale), as well as start downloading the appropriate locale file. For example, setting the locale to fr will start downloading fr.js.

When the user changes their locale, send a UIINTERACTION event.


In the context of PWAs and i18n, currencies refers to format(s) used to display prices. Different currency formats are often needed when services are offered in locales who use varying currency types, which is of course not mutually exclusive to the language spoken.

For example, in English speaking North America, the dollar (\$) currency is used. But in the English speaking UK, the Pound sterling (£) is used instead.

Formatting Currencies

Currencies stored in the Redux store should be formatted as follows:

type currency = {
code: string,
label: string,
symbol: string

code is the ISO code used for that currency, such as “USD” or “GBP”. This value is often used when formatting numbers with react-intl functions like formatNumber().

label is the term the developer can used to easily identify the currency. Such as “dollar”, “pound sterling”, etc.

symbol is the currency symbol that is placed alongside a price. Such as ”\$” or ”£“.

Initializing Currencies

When the PWA initializes, it should set both the list of available currencies and the current active currencies. This can be done by the following:

import {
} from 'mobify-integration-manager/dist/integration-manager/results'
// ...
// These currencies would likely be fetched from the backend
const availableCurrencies = [
code: 'USD',
label: 'dollar',
symbol: '$'
code: 'GBP',
label: 'pound sterling',
symbol: '£'
// The active currency might be determined by some other means
const selectedCurrency = availableCurrencies[0]
// This ensures that the currency is correctly tracked in your analytics

Switching Currencies

When switching between currencies, it’s as simple as fetching the list of available currencies with getAvailableCurrencies, passing the desired currency into the receiveSelectedCurrency action.

import {getAvailableCurrencies} from 'progressive-web-sdk/dist/store/app/selectors'
import {receiveSelectedCurrency} from 'mobify-integration-manager/dist/integration-manager/results'
// ...
// Get the currencies from the current state, which usually happens in a Redux thunk
const availableCurrencies = getAvailableCurrencies(getState()).toJS()
// The other currency is selected somehow, probably through a user interaction
const newCurrency = availableCurrencies.find((currency) => currency.code === 'GBP')

When the user changes their currency, send a UIINTERACTION event.