How to Add react-intl to an Nx React App

react-intl is a library that helps to set up internationalization for a react app. It has been migrated to formatjs monorepo. There are instructions on formatjs website about the setup for react apps: https://formatjs.io/docs/getting-started/installation. Nx is a monorepo tool. This blog shows how to set up for Nx react app.

i18n for Nx React apps using formatjs
i18n for Nx React app using formatjs

The message declaration, we just need to use defineMessage, formatMessage or FormattedMessage: https://formatjs.io/docs/getting-started/message-declaration.

Message Extraction

However, for message extraction, we need to install @formatjs/cli and the command would be like:

# npm
npm run formatjs extract '{apps/<app-name>,libs/<library-name>}/src/**/*.{ts,tsx}' --out-file apps/<app-name>/src/translations/extracted-messages.json"
# yarn
yarn formatjs extract '{apps/<app-name>,libs/<library-name>}/src/**/*.{ts,tsx}' --out-file apps/<app-name>/src/translations/extracted-messages.json"

In the above command, we extract the message from files that use defineMessage or FormattedMessage in apps and/or libs and put the message into a file called extracted-messages.json.

The extracted-messages will have the below format:

{
"<id>": {
"defaultMessage": "...",
"description": "..."
}
}

Message Distribution

For the message distribution, we need to transform the extracted message from above to a compile translated file:

# npm
npm run formatjs compile apps/<app-name>/src/translations/extracted-messages.json --out-file apps/<app-name>/src/translations/en.json
# yarn
yarn formatjs compile apps/<app-name>/src/translations/extracted-messages.json --out-file apps/<app-name>/src/translations/en.json

In the above command, it will take content from extracted-messages.json to and use the default message in en.json like

{
"<id>": "<default message>"
}

Custom Formatter

However, if we want to translate something not use the default message, then we need to create a custom formatter and passed in using --format option: https://formatjs.io/docs/getting-started/message-distribution#translation-management-system-tms-integration.

Below is an example of a custom formatter that reads the output file and not override the value if the id already exists. If id does not exist, it will prefix the default message with the locale name.

const argv = require('yargs').argv;
const originTranslations = require('../' + argv['out-file']);
const locale = argv['pseudo-locale'];
const isEnglish = !locale || locale === 'en';module.exports = {
compile: function (msgs) {
const results = {};
for (const k in msgs) {
const defaultMessage = msgs[k].defaultMessage;
if (originTranslations[k]) {
results[k] = originTranslations[k];
} else if (defaultMessage) {
results[k] = isEnglish ? defaultMessage : locale + ' ' + defaultMessage;
} else {
results[k] = 'MISSING TRANSLATION';
}
}
return results;
},
};

The below command will compile the extracted message to fr.json:

# npm
npm run formatjs compile apps/<app-name>/src/translations/extracted-messages.json --out-file apps/<app-name>/src/translations/fr.json --pseudo-locale fr
# yarn
yarn formatjs compile apps/<app-name>/src/translations/extracted-messages.json --out-file apps/<app-name>/src/translations/fr.json --pseudo-locale fr

The compiled fr.json will look like this:

{
"<existing id>": "<existing value>",
"<non-existing id>": "fr <default message>"
}

Custom Commands

Put it all together, for Nx, we could add 1 custom command to the app that needs extract and distribute messages in workspace.json: https://github.com/nrwl/nx/blob/master/packages/workspace/docs/run-commands-examples.md. We need to add the below translate command under targets:

        "translate": {
"builder": "@nrwl/workspace:run-commands",
"options": {
"commands": [
npm run formatjs extract '{apps/<app-name>,libs/<library-name>}/src/**/*.{ts,tsx}' --out-file apps/<app-name>/src/translations/extracted-messages.json",
"npm run formatjs compile apps/<app-name>/src/translations/extracted-messages.json --out-file apps/<app-name>/src/translations/en.json",
"npm run formatjs compile apps/<app-name>/src/translations/extracted-messages.json --out-file apps/<app-name>/src/translations/fr.json --pseudo-locale fr"
],
"parallel": false
}
}

This way if we run nx run <app-name>:translate, it will run the translation for that app.

We could also add a command in the package.json to run translate command for all apps and libs:

"translate": "nx run-many --all --target=translate --parallel"

If now we run npm run translate or yarn translate, it will extract and distribute the messages across all apps and libs.

Babel

We need to install babel-plugin-formatjs and add it to the .babelrc for the apps that consume react-intl:

{
"presets": ["@nrwl/react/babel"],
"plugins": [
[
"formatjs",
{
"idInterpolationPattern": "[sha512:contenthash:base64:6]",
"ast": false
}
]
]
}

A frontend web developer in Toronto