React Intl Mixin
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.
Overview
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 with time
formatsformatNumber()
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.
Similarly, 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.
Installation
Browser
- Install with bower:
bower install react-intl
. - Load the scripts into your page.
<script src="path/to/react.js"></script>
<script src="path/to/react-intl.js"></script>
Note: for older browsers and Safari you may need to also load the Intl.js polyfill before including react-intl.js
.
- 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.renderComponent(
<MyComponent locales={i18n.locales} messages={i18n.messages}/>,
document.getElementById('example')
);
Node/CommonJS
- You can install the mixin with npm:
npm install react-intl
. - Load in the module and register it.
var ReactIntlMixin = require('react-intl');
Advanced Options
Date and Time Formats
Explicit Formats
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".
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 MyContainer = React.createClass({
mixins: [ReactIntlMixin],
getDefaultProps: function() {
return {
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.renderComponent(
<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.
App Configuration
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.renderComponent(
<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.
How to Use formatMessage()
Example #1: Simple String Replacement
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}."
}
};
React.renderComponent(
<MyComponent locales={["en-US"]} messages={messages["en-US"]}
name="John" manager="Mike" />,
document.getElementById('example')
);
React.renderComponent(
<MyComponent locales={["de-DE"]} messages={messages["de-DE"]}
name="John" manager="Mike" />,
document.getElementById('example')
);
Example #2: String Replacement with Pluralization
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 {# компаний опубликовали}}" +
" новые книги."
}
};
React.renderComponent(
<MyComponent locales={["en-US"]} messages={messages["en-US"]} count=1 />,
document.getElementById("example")
);
React.renderComponent(
<MyComponent locales={["ru-RU"]} messgaes={messages["ru-RU"]} count=1 />,
document.getElementById('example')
);
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
.
Example #3: Dates and Numbers Within Messages
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"
}
}
}
};
React.renderComponent(
<MyComponent
locales={i18n.locales} formats={i18n.formats} messages={i18n.messages}
product="Mac Mini" price=200 deadline=1390518044403 />,
document.getElementById('example')
);
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.
Limitations
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.
License
This software is free to use under the Yahoo Inc. BSD license.
See the LICENSE file for license text and copyright information.