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-platform
as a dependency to yourpackage.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 aroundreact-intl
that adds some shims. You should only access thereact-intl
functions and elements exposed by@edx/frontend-platform/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.
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-platform/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, using a short camel-case string for the property name is fine as long asid
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;
Use the
intl.formatMessage
function to get your translated string:import messages from './SomeComponent.messages'; // ... intl.formatMessage(messages.cartPayNow)
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.
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
.
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, 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={}>
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 toFormattedRelativeTime
(andformatRelative
toformatRelativeTime
) with changes to its API, read more hereMessage 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 variadicBefore:
<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. UsecreateIntl
instead to create a standaloneintl
object outside of React. See Testing with React Intl for more details.