Earlier this week I went in search of a React-friendly i18n library and I spent some time experimenting with react-intl. React-intl is based on FormatJS, which is a library for localizing numbers, dates, and strings. My impression is that react-intl is a fairly small, practical library that is written to make i18n unobtrusive for developers.
Edit, May 1, 2017
Today I’m wondering whether it may have been a mistake to adopt react-intl over the alternatives. At the moment it appears to be in limbo after Yahoo’s collapse.
But although React-intl has type definitions, it does not play particularly well with TypeScript. It assumes there is a single default language which a developer will put directly into the source code, and those strings will later be auto-extracted from the JavaScript code and handed off to translators. Unfortunately, it’s a babel plugin that does the extraction, so TypeScripters will either have to use a separate library to extract strings, or else they’ll need to create a parallel TypeScript-to-ES2015 compilation step to use the native libraries. I went with the latter, inspired by this github thread, because I wanted to use react-intl-translations-manager, and the typescript-only library didn’t extract the language strings in a format that the translation manager could use.
TL;DR The code for this example is at https://github.com/mikebridge/react-intl-ts-demo/.
Setup
Create a new project with the TypeScript fork of create-react-app:
$ create-react-app react-intl-ts-demo --scripts-version react-scripts-ts
Then add babel with the react plugins, react-intl and the react-intl-translations-manager:
$ npm install --save-dev babel-cli babel-plugin-react-intl babel-preset-es2015 babel-preset-react
$ npm install --save react-intl
$ npm install --save-dev react-intl-translations-manager
So at this point the package.json
file should look something like this:
Add a .babelrc
file to compile JSX
files:
Usage
Open up the index.tsx
file and add in the IntlProvider
. The IntlProvider
makes
the react-intl
functionality available to components via the React Context:
Here we’re hardcoding the locale to “en” for the time being:
Now we can localize the strings in App.tsx
file. Import FormattedMessage
and wrap the text:
Translation
I added these three scripts to package.json
. In
production I’ll probably write a bash script to clean up the artifacts, but in the meantime:
"trans:compile": "tsc -p . --target ES6 --module es6 --jsx preserve --outDir extracted", "trans:extract": "babel \"extracted/**/*.jsx\"", "trans:manage": "node scripts/translationRunner.js"
1) The first script compiles the tsx
files to ES2015 in a temporary directory called extracted
. (Make sure you also add
this to “exclude” in your package.json
and to .gitignore
)
$ npm run trans:compile
2) The next one extracts all the default strings into the src/translations/extracted
directory.
$ npm run trans:extract
You should now see a file App.json
that contains your extracted resource metadata:
3) Lastly, react-intl-translations-manager
generates the stub files. I copied their scripts/translationRunner.js
script:
Then you can run node scripts/translationRunner.js
directly, or via the npm script:
$ npm run trans:manage Duplicate ids: No duplicate ids found, great! Maintaining fr.json: Added keys: app.to_get_started: To get started, edit {filename} and save to reload. app.welcome: Welcome to React Maintaining zh.json: Added keys: app.to_get_started: To get started, edit {filename} and save to reload. app.welcome: Welcome to React
This creates two .json
files per language—one for the translations, and one for whitelisting
messages that are causing invalid warnings.
Modify the French file to look like this (disclaimer: these come from Google Translate):
For this demo, I just hardcoded a different locale to see it working. But to change the locale in real code, you’ll
need to set the key
on IntlProvider. (See this for
more information—redux would be a good solution.)
The French locale data will come from the react-intl package.
To hardcode the French locale, modify index.tsx to look like this:
Before we check our page, let’s add in a date component to index.tsx
:
Here’s how it looks:
Injecting react-intl into Components
Although you can use one of react-intl’s “Formatted*” controls much of the time, some
text will occur inside TypeScript code or in JSX attributes. Then you’ll need to access the API
directly. For this you can inject the intl
object into the props of your control:
Those strings that don’t appear in Formatted*
controls still need to exist somewhere so that they can be
located during resource extraction. There’s a call defineMessages
for this purpose—I wasn’t sure what
to do with them so I put all these in a separate .tsx
file:
(I’m not sure what the object key here is for—I don’t see it being used anywhere.)
Conclusion
I am going to use react-intl
for a smaller project and see how it goes. I’ll add more info
here as I get more practical experience with it.