This repository contains a ReactJS Component Mixin to implement Internationalization features for a React component. The Intl Mixin provides a set of methods that can be used in the render()
method of the component to provide date, number, and message formatting, as well as plural and gender based translations.
The ReactIntlMixin
implements a ReactJS Component Mixin that adds these new methods to any React component:
formatDate()
to format a date valueformatTime()
to format a date value withtime
formatsformatRelative()
to format a date relative to now; e.g. "3 hours ago"formatNumber()
to format a numeric valueformatMessage()
to format a complex message
formatDate()
, formatTime()
, and formatNumber()
are sugar on top of Intl.NumberFormat and Intl.DateTimeFormat APIs implemented by most modern browsers. To improve runtime performance, React Intl Mixin uses an internal cache to reuse instances of Intl.NumberFormat
and Intl.DateTimeFormat
when possible.
formatMessage()
is a sugar layer on top of intl-messageformat, a library to support more advanced translation patterns that include complex pluralization and gender support. This library is based on a Strawman Draft proposing to evolve ECMAScript 402 to provide a standard way to format message strings with localization support in JavaScript.
The formatMessage()
method accepts a string message and values to format it with. It too uses an internal cache to improve runtime performance by reusing IntlMessageFormat
instances.
The data consumed by formatMessage()
follows the same format supported by intl-messageformat, which is one of the industry standards used in other programming languages like Java and PHP. Although this format looks complex, professional translators are familiar with it. You can learn more about this format.
formatRelative()
is similar to formatMessage()
in that it's a sugar layer on top of a non-started library, intl-relativeformat, which takes a JavaScript date or timestamp, compares it with "now", and returns the formatted string; e.g., "3 hours ago".
This package assumes that the Intl
global object exists in the runtime. Intl
is present in all modern browsers except Safari, and there's work happening to integrate Intl
into Node.js.
Luckly, there's the Intl.js polyfill! You will need to conditionally load the polyfill if you want to support runtimes which Intl
is not already built-in.
If the browser does not already have the Intl
APIs built-in, the Intl.js Polyfill will need to be loaded on the page along with the locale data for any locales that need to be supported:
<script src="intl/Intl.min.js"></script>
<script src="intl/locale-data/jsonp/en-US.js"></script>
Note: Modern browsers already have the Intl
APIs built-in, so you can load the Intl.js Polyfill conditionally, by for checking for window.Intl
.
Conditionally require the Intl.js Polyfill if it doesn't already exist in the runtime. As of Node <= 0.10, this polyfill will be required.
if (!global.Intl) {
require('intl');
}
Note: When using the Intl.js Polyfill in Node.js, it will automatically load the locale data for all supported locales.
- Install with bower:
bower install react-intl
. - Load the scripts into your page.
<script src="react/react.min.js"></script>
<script src="react-intl/react-intl.min.js"></script>
Note: for older browsers and Safari you may need to also load the Intl.js polyfill before including react-intl.js
.
Be default, React Intl ships with the locale data for English built-in. When you need to format data in another locale, include its data; e.g., for French:
<script src="react-intl/locale-data/fr.js"></script>
Note: All 150+ locales supported use their root BCP 47 language tag; i.e., the part before the first hyphen (if any).
- Creating a React component with the Intl Mixin:
var MyComponent = React.createClass({
mixins: [ReactIntlMixin],
render: function () {
return <div>
<p>{this.formatDate(new Date())}</p>
<p>{this.formatNumber(600)}</p>
<p>{this.formatMessage(this.getIntlMessage('product.info'), {
product: 'Mac Mini',
price: 2000.0015,
deadline: 1390518044403
})}</p>
</div>;
}
});
- Using the React component by specifying
locales
:
var i18n = {
locales: ['en-US'],
messages: {
product: {
info: '{product} will cost {price, number} if ordered by {deadline, date}'
}
}
};
React.render(
<MyComponent locales={i18n.locales} messages={i18n.messages}/>,
document.getElementById('example')
);
- You can install the mixin with npm:
npm install react-intl
. - Load in the module and register it.
var ReactIntlMixin = require('react-intl');
Note: in Node.js, the data for all locales is pre-loaded.
By default, when using {this.formatDate(new Date())}
and {this.formatNumber(600)}
, react-intl
will use the default format for the date, and the default numeric format for the number. To specify a custom format you can pass in a second argument with the format you want to use. The following examples will illustrate this option:
var MyComponent = React.createClass({
mixins: [ReactIntlMixin],
render: function () {
return <div>
<p>A: {this.formatDate(1390518044403, {
hour: 'numeric',
minute: 'numeric'
})}</p>
<p>B: {this.formatNumber(400, { style: 'percent' })}</p>
</div>;
}
});
In the example above, if locales
is set to ["fr-FR"]
, the output will be:
<div>
<p>A: 18:00</p>
<p>B: 40 000 %</p>
</div>
But if locales
is set to ["en-US"]
, the output will be:
<div>
<p>A: 6:00 PM</p>
<p>B: 40,000%</p>
</div>
This explicit way to specify a format works well for simple cases, but for complex applications, it falls short because you will have to pass these format config objects through the component hierarchy. Also, it doesn't work with complex structures processed by formatMessage()
because there is no way to pass the format options for each individual element. To overcome this limitation, we introduced the concept of "custom formats".
With custom format, you can name a set of options that can be used within the entire application or within a component subtree (a component and its child components). These custom formats will also be used by the formatMessage()
method for complex messages. The following examples will illustrates how custom formats work.
var MyComponent = React.createClass({
mixins: [ReactIntlMixin],
getDefaultProps: function() {
return {
// Hard-coding formats as properties, but they can be passed from the
// parent component or outside of React.
formats: {
date: {
timeStyle: {
hour: "numeric",
minute: "numeric"
}
},
number: {
percentStyle: {
style: "percent"
},
EUR: {
style: "currency",
currency: "EUR"
}
}
}
};
},
render: function () {
return <div>
<p>A: {this.formatDate(1390518044403, "timeStyle")}</p>
<p>B: {this.formatNumber(400, "percentStyle")}</p>
<p>C: {this.formatMessage(this.getIntlMessage('product.info'), {
product: 'Mac Mini',
price: 2000.0015,
deadline: 1390518044403
})}</p>
</div>;
}
});
The component above will now be rendered with locales
and mesages
set externally:
var i18n = {
locales: ["en-US"],
messages: {
product {
info: "{product} will cost {price, number, eur} if ordered by {deadline, date, timeStyle}"
}
}
};
React.render(
<MyComponent locales={i18n.locales} messages={i18n.messages}/>,
document.body
);
The above rendering of MyComponent
will output:
<div>
<p>A: 6:00 PM</p>
<p>B: 40,000%</p>
<p>C: Mac Mini will cost €200 if ordered by 6:00 PM</p>
</div>
By defining this.props.formats
, which specifies a set of named formats under date
and number
members, you can use those named formats as a second argument for formatNumber()
, formatDate()
, and formatTime()
. You can also reference them as the third token when defining the messages, e.g: {deadline, date, timeStyle}
. In this case deadline
describes the format options for its value, specifying that it is a date
and should be formatted using the timeStyle
custom format.
Another feature of the Intl Mixin is its ability to propagate formats
and locales
to any child component. Internally, it leverages the context
to allow those child components to reuse the values defined at the parent level, making this ideal to define custom formats and the locale for the app by defining them or passing them into the root component when rendering the application. This is always the recommended way to provide i18n message strings to the React component hierarchy. Ideally, you will do this:
var i18n = {
locales: ['en-US'],
formats: { number: {}, date: {}, time: {} },
messages: {}
};
React.render(
<MyRootComponent
locales={i18n.locales}
formats={i18n.formats}
messages={i18n.messages} />,
document.getElementById('container')
);
Then make sure MyRootComponent
uses the ReactIntlMixin
. By doing that, you can define the list of locales
, normally one or more in case you want to support fallback, (e.g.: ["fr-FR", "en"]
); and you can define formats
to describe how the application will format dates and numbers. You will also want to mass the messages
for the current locale, since the string will be locale-specific. All child components will be able to inherit these three structures in such a way that you don't have to propagate or define them at each level in your application. Just apply this mixin in those components that are suppose to use this.formatNumber()
, this.formatDate()
and/or this.formatMessage()
in the render()
method and you're all set.
var MyComponent = React.createClass({
mixins: [ReactIntlMixin],
render: function () {
return <p>{this.formatMessage(this.getIntlMessage("reporting"), {
employee: this.props.name,
manager: this.props.manager
})}</p>;
}
});
var messages = {
"en-US": {
reporting: "{employee} reports to {manager}."
},
"de-DE": {
reporting: "{employee} berichtet an {manager}."
}
};
// Render in English:
React.render(
<MyComponent locales={["en-US"]} messages={messages["en-US"]}
name="John" manager="Mike" />,
document.getElementById("example")
);
// - English output: "John reports to Mike."
// Render in german:
React.render(
<MyComponent locales={["de-DE"]} messages={messages["de-DE"]}
name="John" manager="Mike" />,
document.getElementById("example")
);
// - German output: "John berichtet an Mike."
var MyComponent = React.createClass({
mixins: [ReactIntlMixin],
render: function () {
return <p>{this.formatMessage(this.getIntlMessage("posted"), {
relativeTime: this.formatRelative(this.props.postDate),
})}</p>;
}
});
var messages = {
"en": {
reporting: "posted {relativeTime}"
},
"es": {
reporting: "publicado {relativeTime}"
}
};
// Render in English:
React.render(
<MyComponent locales={["en"]} messages={messages["en"]}
postDate="2014-05-11" />,
document.getElementById('example')
);
// - English output: "posted 4 months ago"
// Render in Spanish:
React.render(
<MyComponent locales={["es"]} messages={messages["es"]}
postDate="2014-05-11" />,
document.getElementById('example')
);
// - German output: "publicado hace 4 meses"
var MyComponent = React.createClass({
mixins: [ReactIntlMixin],
render: function () {
return <p>{this.formatMessage(this.getIntlMessage("publishers"), {
COMPANY_COUNT: this.props.count
})}</p>;
}
});
var messages = {
"en-US": {
publishers: "{COMPANY_COUNT, plural," +
"one {One company}" +
"other {# companies}}" +
" published new books."
},
"ru-RU": {
publishers: "{COMPANY_COUNT, plural," +
"one {Одна компания опубликовала}" +
"few {# компании опубликовали}" +
"many {# компаний опубликовали}" +
"other {# компаний опубликовали}}" +
" новые книги."
}
};
// Render in English:
React.render(
<MyComponent locales={["en-US"]} messages={messages["en-US"]} count=1 />,
document.getElementById("example")
);
// - English output: "One company published new books."
// Render in Russian:
React.render(
<MyComponent locales={["ru-RU"]} messgaes={messages["ru-RU"]} count=1 />,
document.getElementById('example')
);
// - Russian output: "Одна компания опубликовала новые книги."
// If the `count` property that is passed into `MyComponent` is 2, then:
// - English output: "2 companies published new books."
// - Russian output: "2 компании опубликовали новые книги."
// if the `count` property that is passed into `MyComponent` is 99, then:
// - English output: "99 companies published new books."
// - Russian output: "99 компаний опубликовали новые книги."
As you can see in this example, Russian has different rules when it comes to pluralization, having different sub-messages for one
, few
and other
, while American English is just targeting one
and other
.
In any message, you can use {<valueName>, number [, <optionalFormat>]}
and {<valueName>, date [, <optionalFormat>]}
to format numbers and dates, which that includes combining them with plural and gender structures as well.
var MyComponent = React.createClass({
mixins: [ReactIntlMixin],
render: function () {
return <p>
{this.formatMessage(this.getIntlMessage('product'), this.props)}
</p>;
}
});
var i18n = {
locales: ["en-US"],
messages: {
product: "{product} will cost {price, number, eur} if ordered by {deadline, date, timeStyle}"
},
formats: {
date: {
timeStyle: {
hour: "numeric",
minute: "numeric"
},
number: {
EUR: {
style: "currency",
currency: "EUR"
}
}
}
};
// Render in English:
React.render(
<MyComponent
locales={i18n.locales} formats={i18n.formats} messages={i18n.messages}
product="Mac Mini" price=200 deadline=1390518044403 />,
document.getElementById('example')
);
// - English output: "Mac Mini will cost €200 if ordered by 6:00 PM"
Note: formatMessage()
will take care of creating the internal date and number format instances, and cache them to avoid creating unnecessary objects by reusing existing instances when similar formats are applied.
Not all browsers have implemented ECMAScript 402, which is the Internationalization API, for older browsers, and Safari, you might need to patch the browser by loading Intl.js polyfill before including react-intl.js
library.
This software is free to use under the Yahoo Inc. BSD license. See the LICENSE file for license text and copyright information.