WordCloud from article

Internationalize React apps done right using React-intl library

Marcel Mokos
ableneo Technology

--

This article helps you decide when you need to add translation library to the project. How to do it without slowing the creative process. Following article will discuss automatization and testing that can help you save time in large teams.

Define some vocabulary

“Internationalization” is a long word, and there are at least two widely used abbreviations: “intl,” “i18n”. “Localization” can be shortened to “l10n”.

Add translation library from the beginning

A project that is planning to support multiple languages should have tooling from the beginning. Adding translations to a project that already started is not a pleasant and cost-effective thing to do.

Use translations for content management

The added value of using internationalization library is significant even when you do not want to support multiple languages. Teams can use this infrastructure for content management purposes. Texts can change without developer interaction.

Users expect localized Dates, Number separators, Currencies

A common misconception is that internationalization is only about text. There are more cultural and linguistic differences, like date and number separators. Order of date components or where you put currency symbol.

Date format by country

Y — year, M — month, D — day, source: https://en.wikipedia.org/wiki/Date_format_by_country

Tools for the Internationalization

Modern browser support Internationalization API. However, older browsers do not support all features consistently. That’s why there are tools created that make using translations effortless.

The Intl object is the namespace for the ECMAScript Internationalization API, which provides language sensitive string comparison, number formatting, and date and time formatting.

Normalization of browser Internationalization API

FormatJS is a set of JavaScript libraries. Runs in the browser and Node.js. It is built on standards.

For most web projects, internationalization happens in the template or view layer. FormatJS tools are available for React, Ember, Handlebars, and Dust.

Detect the user’s locale

The most common approach is to determine language from Accept-Language header in the HTTP request. Browser API are window.navigator.language and window.navigator.languages, and base on them set the cookie with a preferred locale.

A common mistake is to not take into account full language variants like [“en-US”, “en-GB”]. Another common mistake is mismatching the language tags and country codes.

Internationalize React apps React-intl

React Intl is part of FormatJS. It provides bindings to React via its components and API.

Features

This library provides React components and an API to format dates, numbers, and strings, including pluralization and handling translations.

  • Display numbers with separators.
  • Display dates and times correctly.
  • Display dates relative to “now”.
  • Pluralize labels in strings.
  • Support for 150+ languages.
  • Runs in the browser and Node.js.
  • Built on standards.

Best practices using React-intl

export type MessageDescriptor = {
// A unique, stable identifier for the message
id: string,

// The default message (probably in English)
defaultMessage: string,

// Context for the translator about how it's used in the UI
description?: string | object,
};
export type Messages = {
[key: string]: MessageDescriptor
};
  • store messages in a separate file for every component, leverage static types
// Component.messages.js
// @flow
import {defineMessages} from "react-intl";
import {type Messages} from "./MessageDescriptor"
const messages: Messages = {
title: {
id: "Component.title",
defaultMessage: "Title",
description: "Section headline",
},
};

const definedMessages: typeof messages = defineMessages(messages);

export default definedMessages;
// Component.js
// @flow
import * as React from "react";
import {FormattedMessage} from "react-intl";
import messages from "./Component.messages";
export default () => (
<h2>
<FormattedMessage {...messages.title} />
<h2>
);
  • approach with defineMessages per component will make your components testable without the need of wrapping them in IntlProvider.
  • create specialized components for commonly used formatted numbers, and Dates, with defaults for the project
// FormattedNumber
<Currency value={100} />
<Percentage value={5} />
// FormattedDate, FormattedTime
<Date value={new Date()}>
<DateTime value={new Date()}>
  • compose formatted messages. You can use custom components as well.
export type FormattedMessageProps = MessageDescriptor & {
values?: object,
tagName?: string,
children?: (...formattedMessage: Array<ReactElement>) => ReactElement,
}
/* messages.priceWithDiscount.defaultMessageNow only {price}, discount {discountInPercents} OFF, save {discount}. (Original price {priceBeforeDiscount})*/<FormattedMessage
{...messages.priceWithDiscount}
values={{
price: (
<Currency value={100} />
),
discount: (
<Currency value={25} />
),
priceBeforeDiscount: (
<Currency value={125} />
),
discountInPercents: (
<Percentage value={20} />
),
}}
/>
/* Render output:
Now only $100, discount 20% OFF, save $25. (Original price $125)
*/
  • preferable use Message Syntax for pluralization and translation composition
// Simple Message
Hello, {name}
// Formatted Argument
I have {catsCount, number} cats.
Almost {catsBlackPercentage, number, percent} of them are black.
// {plural} Format
Hello, {name}, you have {itemCount, plural,
=0 {no items}
one {# item}
other {# items}
}.
// {select} Format
{gender, select,
male {He}
female {She}
other {They}
} will respond shortly.
  • <FormattedPlural/> is using various props for pluralisation. There is a problem to serialize and unserialize this component props in administration. Simply do not use it. Use <FormattedMessage /> for pluralization since it is easier to use Message Syntax.

IntlProvider

This component is used to overwrite default messages.

// simplified
const locale = navigator.language;
const messages = getMessages(locale);
ReactDOM.render(
<IntlProvider
locale={locale}
messages={messages} // messages?: {[id: string]: string}
>
<App />
</IntlProvider>,
document.getElementById('app')
);

Since message overwrites are valid only for the rendered subtree. You can render component multiple times on one the page with different translations in distinct subtrees.

Utilize react intl for content management

Content management systems can utilize overwrites using IntlProvier to modify default messages for current component representation.

More about the application of React-intl for content management and testing can be seen in the following article.

--

--

Marcel Mokos
ableneo Technology

I'm fanatic to next generation Javascript lambda, yield, async/await everything. I admire typescript and flow types. Javascript will ultimately rule the world.