This is a step by step guide to making your React app ready to accept translations. The instructions here are very specific to the edX setup.
Table of Contents
These steps will allow your application to accept translation strings. See frontend-app-account for an example app to follow.
Add
@edx/frontend-i18n
as a dependency to yourpackage.json
. (If you are actually writing a consumable component, add@edx/frontend-i18n
as both a dev dependency and peer dependency instead.)@edx/frontend-i18n
is a wrapper aroundreact-intl
that adds some shims. You should only access thereact-intl
functions and elements exposed by@edx/frontend-i18n
. (They have the same names as inreact-intl
.)In
App.js
, wrap your entire app in anIntlProvider
element. See Load up your translation files for details. (Consumable components: Don't do this step, except possibly in tests. Your consuming application will do it for you. Instead, update your README like this example.)For places in your code where you need a display string, and it's okay if it's a React element (generally, most messages): use a
FormattedMessage
.- The
id
is required and must be a unique dot-separated string. The first part of it should be your app name. The rest can be whatever you want, though a hierarchical namespacing part followed by some descriptive words is most common. - The
defaultMessage
is required, and should be the English display string. Otherwise translators won't know what they're translating.
Note
English strings should not include the HTML brackets < or > since those characters will break Transifex.
The
description
is a note to the translators that can help them figure out how to translate your text. It is optional, but recommended.Example:
<FormattedMessage id="myapp.cart.shipping.address" defaultMessage="Shipping address" description="header above the shipping address form" />
For additional help, including adding interprolated variables, see the FormattedMessage documentation. It can also handle plurals.
- The
For places in your code where you need a display string, and it has to be a plain JavaScript string (e.g., a button label), you will need to do the following:
Inject the
intl
object into your component:import { injectIntl, intlShape } from '@edx/frontend-i18n';
;- add
intl: intlShape.isRequired
to your component'spropTypes
. - instead of
export Foo
,export injectIntl(Foo)
.
Define your messages using
defineMessages
. This function doesn't actually do anything; it's just a hook for the translation pipeline to be able to find your translation strings. You can calldefineMessages
wherever you want, but if you have a lot of them you might want to move them to a separate file. EitherMyAppName.messages.js
(if your entire app has only a few strings) orSomeComponent.messages.js
will work. Your file should look like the example below. For your own sanity, make the property name the same as theid
for each object. Example:import { defineMessages } from '@edx/frontend-i18n'; const messages = defineMessages({ 'myapp.cart.pay.now': { id: 'myapp.cart.pay.now', defaultMessage: 'Pay Now', description: 'a button label', }, }); export default messages;
Use the
intl.formatMessage
function to get your translated string:import messages from './SomeComponent.messages'; // ... intl.formatMessage(messages['myapp.cart.pay.now'])
If you want to use
FormattedMessage
but your display string is repeated several times, it's probably better to pull it out into a messages file. In this case the messages file will have thedefaultMessage
and thedescription
, and you can just giveFormattedMessage
theid
.You should now be able to run your app and see everything behaving normally, with English strings.
In your repo, create and commit a file named .tx/config
:
[main] host = https://www.transifex.com [edx-platform.your-resource-name-here] file_filter = src/i18n/messages/<lang>.json source_file = src/i18n/transifex_input.json source_lang = en type = KEYVALUEJSON
The pipeline jobs live in the ecommerce-scripts repo, but you don't have to modify them. They will interact with your repo through make
targets.
Copy the frontend-app-account Makefile to your project.
- Modify the
transifex_resource
variable with your own Transifex resource name.
- Modify the
The job to push strings to Transifex will call
make push_translations
in your repo. This target should do everything necessary to extract your strings, concat them into one file, and put them insrc/i18n/transifex_input.json
. If you don't have any special requirements, you can just use the default target that is includedfrom frontend-i18n
.Extraction: We will be using a Babel plugin to find all the strings inside a
FormattedMessage
component or inside a call todefineMessages
.Add this to
scripts
inpackage.json
:"i18n_extract": "BABEL_ENV=i18n babel src --quiet > /dev/null"
Add
babel-plugin-react-intl
to your dev dependencies:npm install babel-plugin-react-intl --save-dev
Add this to
.babelrc
:"env": { "i18n": { "plugins": [ ["react-intl", { "messagesDir": "./temp/babel-plugin-react-intl", "moduleSourceName": "@edx/frontend-i18n" }] ] } }
Confirm that running
make i18n.extract
creates a lot of.json
files inyour-repo/temp/babel-plugin-react-intl/
.Add
temp
andsrc/i18n/transifex_input.json
to your.gitignore
.
Concatenation: All those
.json
files need to become one file.- Confirm that running
make i18n.concat
createssrc/i18n/transifex_input.json
, which should be a file of message id / English pairs.- If you are missing any dependencies, like
glob
, you will need to add these to your dev dependencies.
- If you are missing any dependencies, like
- Confirm that running
Uploading comments: The
KEYVALUEJSON
format doesn't have a way to put in translator comments (thedescription
field in your message definitions), so we work around this by making calls to the Transifex API. There isn't a bulk call available, so it will be one API call per string. (...I know.) As of June 2019, the rate limit is 6000 calls per hour.Add
reactifex
to your dev dependencies:npm install reactifex --save-dev
You can't test this one without Transifex credentials, so at this point it's probably easiest to just make your pipeline job and run that.
The job to pull translations out of Transifex and commit them to your repo will call
make pull_translations
.- If the languages you want to pull down differ from the default set, you can update the
transifex_langs
variable in your local Makefile. - The user
edx_transifex_bot
will need to have permissions to merge an unreviewed pull request into your repo. This means your repo must either allow merging unreviewed pull requests for everyone, oredx_transifex_bot
needs to be an administrator.
- If the languages you want to pull down differ from the default set, you can update the
- You must commit an empty language file for each language, or the
pull
job will fail. Each file is namedsrc/i18n/messages/LANG_CODE.json
and consists of an empty JSON object ({}
). See this example code with empty language files.
- In the
edx-internal
repo, add your job to tools-edx-jenkins/translation-jobs.yml, using one of the existing frontend apps as a model.- Add an opsgenie email address that actually exists :-) so you get notified of failures.
- You will need to set up OpsGenie to properly send these alerts, and to auto-close notifications when Jenkins goes back to normal.
- In the command lines that run the job, put in your repo name and the GitHub team name that you'd like notifications sent to.
- The existing jobs push translation strings to Transifex daily, and pull completed translations once a week, on Sunday. You can pick your own schedule on the
cron
line. It's best to move your new job off of 8PM UTC, so we don't have to worry about the Transifex API rate limit described above.- Note that the Jenkins machine uses UTC.
- Add an opsgenie email address that actually exists :-) so you get notified of failures.
- Open a pull request with your change, then open a devops ticket requesting them to review your pull request and rerun the seed job. Only devops can merge pull requests in this repo.
- Connect to the edX VPN to see your two new jobs at https://tools-edx-jenkins.edx.org/job/translations/ .
push_translations
extracts the strings from your repo and pushes them to Transifex.pull_translations
pulls the translated strings from Transifex and checks them into your repo. You can test these jobs out by running them manually.- If you can't see this page, you might need to be added to the
jenkins-tools-translation-jobs
team in GitHub.
- If you can't see this page, you might need to be added to the
Note
This step is for applications only. You can skip this for consumable components.
You can actually do this step even before you have Transifex and Jenkins set up, by providing your own translation files in src/i18n/messages/LANG_CODE.json
.
Your pipeline job should have updated several translation files in
src/i18n/messages/LANG_CODE.json
.Create
src/i18n/index.js
using frontend-app-account's index.js as a model.In
App.jsx
, make the following changes:import { IntlProvider, getMessages } from '@edx/frontend-i18n'; // ... <IntlProvider locale={this.props.locale} messages={getMessages()}>
In
src/index.jsx
, callconfigureI18n
as per the README instructions.As of this writing,
frontend-i18n
reads the locale from the user language preference cookie, or, if none is found, from the browser's language setting. You can verify everything is working by changing your language preference in your account settings. If you are not logged in, you can change your browser language to one of the languages you have translations for.