Internationalization/Localization is an important aspect of a successful web application and one that can be particularly difficult to get it right from the beginning.
In React world there are several i18n solutions, with react-i18n by Yahoo being the most popular one. Its use is pretty simple, you just wrap your application with an Internationalization provider and pass to it an object with keys/translations:
Then you use the FormatedMessage component with an id:
This approach works quite well for small-sized applications, but as the application starts growing the same happens with the size (and the number) of the files that have the translation keys. In addition, a user may visit only a few of the pages/views of the application, why to download every possible key? It will be nice to be able to load only the translation keys that are needed.
We can achieve that by taking advantage of Webpack’s code-splitting feature. We can create a TranslationProvider component that will load only the translations keys that are needed for a particular component/view/page, wrap the component/view/page, fetch the translations, and render the wrapped component when the translations file is downloaded.
In my app I created a translations folder inside the components folder:
Then inside my component I import the TranslationsProvider component and wrap the markup:
Because in the future we may decide to change i18n solution, I didn’t want to import the FormattedMessage of react-intl to each of my components, but I created a Translate component that just wraps it. That way, if for whatever reason we decide in the future to change i18n solution we will have to touch just a single file.
The import() call will help Webpack generate a new chunk/file. In my webpack config I have the following setting in the output:
This will generate chunk files that will look like:
Using the [chunkHash] helps us create unique files per component, if the translation keys change, a new file with different hash will be generated, that way we can take advantage of other technologies like Service Workers to cache our translation files.
When users visit a page they will download only the translation keys needed for that view. I would recommend trying to have less than 6-8 translation files per view though because browsers still have the limitation of parallel connections to the same domain.
Finally, if there are some keys that are pretty much used by every view e.g. error message codes, we can provide these messages to the Internationalization provider through redux store.
We can take it a step further and add error handling in our TranslationsProvider e.g. to fetch a fallback language if the one we are trying to access doesn’t exist, or we can store the messages in the localstorage. I decided to omit these features for the sake of keeping the article as short as possible (for the same reason the example doesn’t demonstrate how the component should work in case of locale switch).