react-intl-universal
react-intl-universal is a React internationalization package developed by Alibaba Group.
Features
- Can be used not only in React component but also in Vanilla JS.
- Simple. Only three main API and one optional helper.
- Display numbers, currency, dates and times for different locales.
- Pluralize labels in strings.
- Support variables in message.
- Support HTML in message.
- Support for 150+ languages.
- Runs in the browser and Node.js.
- Message format is strictly implemented by ICU standards.
- Locale data in nested JSON format are supported.
- react-intl-universal-extract helps you generate a locale file easily.
Live Demo
react-intl-universal example
Why Another Internationalization Solution for React?
In case of internationalizing React apps, react-intl is one of most popular package in industry. react-intl decorate your React.Component with wrapped component which is injected internationalized message dynamically so that the locale data is able to be loaded dynamically without reloading page. The following is the example code using react-intl.
import { injectIntl } from 'react-intl';
class MyComponent extends Component {
render() {
const intl = this.props;
const title = intl.formatMessage({ id: 'title' });
return (<div>{title}</div>);
}
};
export default injectIntl(MyComponent);
However, this approach introduces two major issues.
Firstly, Internationalizing can be applied only in view layer such as React.Component. For Vanilla JS file, there's no way to internationalize it. For example, the following snippet is general form validator used by many React.Component in our apps. We definitely will not have such code separated in different React.Component in order to internationalize the warning message. Sadly, react-intl can't be used in Vanilla JS.
export default const rules = {
noSpace(value) {
if (value.includes(' ')) {
return 'Space is not allowed.';
}
}
};
Secondly, since your React.Component is wrapped by another class, the behavior is not as expected in many way. For example, to get the instance of React.Component, you can't use the normal way like:
class App {
render() {
<MyComponent ref="my"/>
}
getMyInstance() {
console.log('getMyInstance', this.refs.my);
}
}
Instead, you need to use the method getWrappedInstance()
to get that.
class MyComponent {...}
export default injectIntl(MyComponent, {withRef: true});
class App {
render() {
<MyComponent ref="my"/>
}
getMyInstance() {
console.log('getMyInstance', this.refs.my.getWrappedInstance());
}
}
Furthermore, your React.Component's properties are not inherited in subclass since component is injected by react-intl.
Due to the problem above, we create react-intl-universal to internationalize React app using simple but powerful API.
Get Started
App Examples
Message With Variables
If the message contains variables the {variable_name}
is substituted directly into the string. In the example below, there are two variables {name}
and {where}
, the second argument representing the variables in get
method are substituted into the string.
Locale data:
{ "HELLO": "Hello, {name}. Welcome to {where}!" }
JS code:
intl.get('HELLO', { name: 'Tony', where: 'Alibaba' })
Plural Form and Number Thousands Separators
Locale data:
{ "PHOTO": "You have {num, plural, =0 {no photos.} =1 {one photo.} other {# photos.}}" }
JS code:
intl.get('PHOTO', { num: 0 });
intl.get('PHOTO', { num: 1 });
intl.get('PHOTO', { num: 1000000 });
Plural label supports standard ICU Message syntax.
Number thousands separators also varies according to the user's locale. According to this document, United States use a period to indicate the decimal place. Many other countries use a comma instead.
Display Currency
Locale data:
{ "SALE_PRICE": "The price is {price, number, USD}" }
JS code:
intl.get('SALE_PRICE', { price: 123456.78 });
As mentioned, the locale data is in ICU Message format.
The syntax is {name, type, format}. Here is description:
- name is the variable name in the message. In this case, it's
price
. - type is the type of value such as
number
, date
, and time
. - format is optional, and is additional information for the displaying format of the value. In this case, it's
USD
.
if type
is number
and format
is omitted, the result is formatted number with thousands separators. If format
is one of currency code, it will show in corresponding currency format.
Display Dates
Locale data:
{
"SALE_START": "Sale begins {start, date}",
"SALE_END": "Sale ends {end, date, long}"
}
JS code:
intl.get('SALE_START', {start:new Date()});
intl.get('SALE_END', {end:new Date()});
If type
is date
, format
has the following values:
short
shows date as shortest as possiblemedium
shows short textual representation of the monthlong
shows long textual representation of the monthfull
shows dates with the most detail
Display Times
Locale data:
{
"COUPON": "Coupon expires at {expires, time, medium}"
}
JS code:
intl.get('COUPON', {expires:new Date()});
if type
is time
, format
has the following values:
short
shows times with hours and minutesmedium
shows times with hours, minutes, and secondslong
shows times with hours, minutes, seconds, and timezone
Default Message
When the specific key does't exist in current locale, you may want to make it return a default message. Use defaultMessage
method after get
method. For example,
Locale data:
{ "HELLO": "Hello, {name}" }
JS code:
const name = 'Tony';
intl.get('HELLO', { name }).defaultMessage(`Hello, ${name}`);
Or using d
for short:
const name = 'Tony';
intl.get('HELLO', { name }).d(`Hello, ${name}`);
And getHTML
also supports default message.
const name = 'Tony';
intl.getHTML('HELLO').d(<div>Hello, {name}</div>)
HTML Message
The get
method returns string message. For HTML message, use getHTML
instead. For example,
Locale data:
{ "TIP": "This is <span style='color:red'>HTML</span>" }
JS code:
intl.getHTML('TIP');
Helper
react-intl-universal provides a utility helping developer determine the user's currentLocale
. As the running examples, when user select a new locale, it redirect user new location like http://localhost:3000?lang=en-US
. Then, we can use intl.determineLocale
to get the locale from URL. It can also support determine user's locale via cookie, localStorage, and browser default language. Refer to the APIs section for more detail.
Debugger mode
When developing a website with multiple languages (i18n), translators are usually responsible for translating the content instead of the web developer. However, translators often struggle to find the specific message they need to edit on the webpage because they don't know its key. This leads to them having to ask the developer for the key, resulting in a lot of time wasted on communication.
To solve this issue, a solution is proposed: When the debugger mode in react-intl-universal
is enabled, each message on the webpage will be wrapped in a special span element with the key "data-i18n-key". This way, translators can easily see the key of the message and make the necessary edits themselves using some message management system, without needing to ask the developer.
Enabling debugger mode:
intl.init({
debug: true
})
Message will be wrapped in a span element with the key "data-i18n-key":
Component Internationalization
When internationalizing a React component, you don't need to intl.init
again.
You could make it as peerDependency, then just load the locale data in the compoent.
APIs Definition
init(options)
load(locales)
get(key, variables)
getHTML(key, options)
determineLocale(options)
changeCurrentLocale(newLocale)
getInitOptions()
Compatibility with react-intl
As mentioned in the issue Mirror react-intl API, to make people switch their existing React projects from react-intl to react-intl-universal. We provide two compatible APIs as following.
formatMessage(options, variables)
formatHTMLMessage(options, variables)
For example, the formatMessage
API
const name = 'Tony';
intl.formatMessage({ id:'hello', defaultMessage: `Hello, ${name}`}, {name});
is equivalent to get
API
const name = 'Tony';
intl.get('hello', {name}).d(`Hello, ${name}`);
And the formatHTMLMessage
API
const name = 'Tony';
intl.formatHTMLMessage({ id:'hello', defaultMessage: <div>Hello</div>}, {name});
is equivalent to getHTML
API
const name = 'Tony';
intl.getHTML('hello', {name}).d(<div>Hello</div>);
FAQ
1. How to Internationalize Message in Constants Object
If constants are defined outside of a React component, the message in constants.fruits
may get loaded before intl.init(...)
. This can cause a warning to be displayed, such as react-intl-universal locales data "null" not exists
.
const constants = {
fruits : [
{ label: intl.get('banana'), value: 'banana' },
{ label: intl.get('apple'), value: 'apple' },
]
}
function MyComponent() {
return <Select dataSource={constants.fruits} />
}
To fix this, you should call intl.init
before render
.
Solution 1
Make the message object as a function, and call it at render function.
const constants = {
fruits : () => [
{ label: intl.get('banana'), value: 'banana' },
{ label: intl.get('apple'), value: 'apple' },
]
}
function MyComponent() {
return <Select dataSource={constants.fruits()} />
}
Solution 2
Use getter syntax to make a function call when that property is looked up
const constants = {
fruits: [
{
get label() {
return intl.get("banana");
},
value: "banana",
},
{
get label() {
return intl.get("apple");
},
value: "apple",
},
],
};
function MyComponent() {
return <Select dataSource={constants.fruits} />;
}
2. How to Bind Event Handlers to an Internationalized Message
const MyComp = (props) => {
const onClick = (e) => {
if (e.target.tagName === 'A') {
}
};
return (
<span onClick={onClick}>
{intl.getHTML('more_detail').d(<span>Please refer to the <a>document</a> for more detail.</span>)}
</span>
)
}
Other Frontend Tools
License
This software is free to use under the BSD license.