Skip to main content
To translate a React Intl app, extract its message descriptors into a FormatJS catalog, translate that catalog with hyperlocalise run, and pass an ID-to-string map to IntlProvider. This guide keeps the workflow in your repository. For shared review in Platform, use the Platform React Intl tutorial.

Before you start

You need a React app with react-intl, the Hyperlocalise CLI, and an AI provider credential. This example uses TypeScript, JSON imports, OpenAI, and Spanish as the target language.

1. Define messages with stable IDs

Create src/messages.ts:
Keep IDs stable when editing the wording. Descriptions give the translation model context without becoming visible UI text.

2. Extract the source catalog

Run from your project root:
The output contains message objects, including translator descriptions:
The extractor scans .ts and .tsx files for defineMessage, defineMessages, intl.formatMessage, and FormattedMessage. It does not extract arbitrary string literals. See extract for ignore patterns and generated IDs.

3. Map the catalog to a target locale

Create i18n.yml:
Set OPENAI_API_KEY in your shell or an untracked .env.local file. The CLI automatically recognizes the FormatJS catalog shape. It translates defaultMessage and retains metadata.

4. Translate and check the result

Review lang/es-ES.json. A translated entry might look like this:
The wording may vary. Message IDs and argument names must still match the app. Use ICU troubleshooting if validation reports placeholder or plural-structure errors.

5. Load translations into React Intl

React Intl accepts strings or compiled message ASTs in IntlProvider.messages. Convert this guide’s descriptor catalog to strings before passing it to the provider. See the IntlProvider reference. In src/app.tsx:
With the example greeting, the heading renders ¡Hola, Ana!. Test the plural message with count set to 1 and 2, and confirm the singular and plural text in your generated catalog renders correctly. This example assumes your TypeScript and bundler setup supports JSON imports. Use your app’s locale loader when adding language switching.

Do I need to run pack?

No. The conversion above works with the translated catalog directly. If you want to strip descriptions from a separate build artifact, you can run:
pack still retains defaultMessage objects for FormatJS input. Apply the same conversion if you import the packed file. Keep the original catalog available for translation context and review.

Keep translations up to date

After editing descriptors, rerun extraction before translation:
Commit the extracted catalog, reviewed targets, configuration, and lockfile. Follow changed-string translation to understand which messages run again, then add GitHub Actions validation.