Skip to content

Latest commit

 

History

History
173 lines (108 loc) · 8.86 KB

i18n.rst

File metadata and controls

173 lines (108 loc) · 8.86 KB

React App i18n HOWTO

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.

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

  1. Add @edx/frontend-platform as a dependency to your package.json . (If you are actually writing a consumable component, add @edx/frontend-platform as both a dev dependency and peer dependency instead.) @edx/frontend-platform/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-platform/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-platform/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, using a short camel-case string for the property name is fine as long as id is globally unique in the MFE. Example:

      import { defineMessages } from '@edx/frontend-platform/i18n';
      
      const messages = defineMessages({
        'cartPayNow': {
          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.cartPayNow)
      
  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.

Follow the instructions for including React repositories in the translations workflow on docs.openedx.org.

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, configure } from '@edx/frontend-platform/i18n';
    import messages from './i18n/index'; // A map of all messages by locale
    
     configure({
       messages,
       config: getConfig(), // ENVIRONMENT and LANGUAGE_PREFERENCE_COOKIE_NAME are required
       loggingService: getLoggingService(), // An object with logError and logInfo methods
     });
    
     // ...inside ReactDOM.render...
     <IntlProvider locale={this.props.locale} messages={}>
    
  4. As of this writing, frontend-platform/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.

Initially frontend-platform used react-intl@2 but as a part of its 2.0.0 release the version has been upgraded to react-intl@5. If your application used frontend-platform < 2.0.0 and you decided to upgrade, here's a list of breaking changes that you will need to consider during the upgrade:

  • FormattedRelative component has been renamed to FormattedRelativeTime (and formatRelative to formatRelativeTime) with changes to its API, read more here

  • Message Format syntax has been changed. See Message Format Syntax Changes for more details.

  • All tags specified must have corresponding values and will throw error if it's missing, e.g:

    new IntlMessageFormat('a<b>strong</b>').format({
      b: (...chunks) => <strong>{chunks}</strong>,
    })
    
  • Formatting self-closing tags is not supported anymore.

  • XML/HTML tags are escaped using apostrophe just like other ICU constructs.

  • Rich text formatting callback function is no longer variadic

    • Before:

      new IntlMessageFormat('a<b>strong</b>').format({
        b: (...chunks) => <strong>{chunks}</strong>,
      })
      
    • After:

      new IntlMessageFormat('a<b>strong</b>').format({
        b: chunks => <strong>{chunks}</strong>,
      })
      
  • FormattedMessage render prop is no longer variadic

    • Before:

      <FormattedMessage defaultMessage="a<b>strong</b>">
        {(...chunks) => <b>{chunks}</b>}
      </FormattedMessage>
      
    • After:

      <FormattedMessage defaultMessage="a<b>strong</b>">
        {chunks => <b>{chunks}</b>}
      </FormattedMessage>
      
  • IntlProvider.getChildContext has been removed which was used for testing purposes. Use createIntl instead to create a standalone intl object outside of React. See Testing with React Intl for more details.