Skip to content
Permalink
Branch: master
Find file Copy path
Find file Copy path
Fetching contributors…
Cannot retrieve contributors at this time
212 lines (129 sloc) 11.9 KB

React App i18n HOWTO

Introduction

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.

Internationalize your application with react-intl

These steps will allow your application to accept translation strings. See frontend-app-account for an example app to follow.

  1. Add @edx/frontend-i18n as a dependency to your package.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 around react-intl that adds some shims. You should only access the react-intl functions and elements exposed by @edx/frontend-i18n. (They have the same names as in react-intl.)

  2. In App.js, wrap your entire app in an IntlProvider 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.)

  3. 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.

  4. 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:

    1. Inject the intl object into your component:

      1. import { injectIntl, intlShape } from '@edx/frontend-i18n';;
      2. add intl: intlShape.isRequired to your component's propTypes.
      3. instead of export Foo, export injectIntl(Foo) .
    2. 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 call defineMessages wherever you want, but if you have a lot of them you might want to move them to a separate file. Either MyAppName.messages.js (if your entire app has only a few strings) or SomeComponent.messages.js will work. Your file should look like the example below. For your own sanity, make the property name the same as the id 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;
      
    3. Use the intl.formatMessage function to get your translated string:

      import messages from './SomeComponent.messages';
      // ...
      intl.formatMessage(messages['myapp.cart.pay.now'])
      
  5. 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 the defaultMessage and the description, and you can just give FormattedMessage the id.

  6. You should now be able to run your app and see everything behaving normally, with English strings.

Set up Transifex

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

Set up the pipeline tools

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.

  1. Copy the frontend-app-account Makefile to your project.

    • Modify the transifex_resource variable with your own Transifex resource name.
  2. 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 in src/i18n/transifex_input.json. If you don't have any special requirements, you can just use the default target that is included from frontend-i18n.

    1. Extraction: We will be using a Babel plugin to find all the strings inside a FormattedMessage component or inside a call to defineMessages.

      • Add this to scripts in package.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 in your-repo/temp/babel-plugin-react-intl/ .

      • Add temp and src/i18n/transifex_input.json to your .gitignore.

    2. Concatenation: All those .json files need to become one file.

      • Confirm that running make i18n.concat creates src/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.
    3. Uploading comments: The KEYVALUEJSON format doesn't have a way to put in translator comments (the description 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.

  3. 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, or edx_transifex_bot needs to be an administrator.

Create empty translation files in your repo

  1. You must commit an empty language file for each language, or the pull job will fail. Each file is named src/i18n/messages/LANG_CODE.json and consists of an empty JSON object ({}). See this example code with empty language files.

Create your pipeline job on Jenkins

  1. 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.
  2. 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.
  3. 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.

Load up your translation files

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.

  1. Your pipeline job should have updated several translation files in src/i18n/messages/LANG_CODE.json .

  2. Create src/i18n/index.js using frontend-app-account's index.js as a model.

  3. In App.jsx, make the following changes:

    import { IntlProvider, getMessages } from '@edx/frontend-i18n';
    // ...
    <IntlProvider locale={this.props.locale} messages={getMessages()}>
    
  4. In src/index.jsx, call configureI18n as per the README instructions.

  5. 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.

For more information

You can’t perform that action at this time.