FormatJS

Last updated: December 02, 2024Author: Jakub Pomykała

In this article, you will learn how to configure FormatJS (react-intl) library with hosted translations via Translation Hosting or locally loaded files. Translation hosting allows you to change translations without re-deploying application as all messages are loaded directly from the cloud. Locally loaded files are useful when you want to keep translations in your repository or load them from a different source.

simplelocalize-react-intl

Installation

Add react-intl library and @formatjs/cli as development package.

npm install react-intl
npm install --save-dev @formatjs/cli

Add extract script to your package.json file.

{
  "scripts": {
    "extract": "npm run extract -- 'src/**/*.ts*' --ignore='**/*.d.ts' --out-file extracted.json --id-interpolation-pattern '[sha512:contenthash:base64:6]'"
  }
}

The extract script will extract all translation keys from your source code and save them to extracted.json file.

npm run extract

Read more about FormatJS CLI integration.

Configuration

To start using react-intl library, you need to create some place to keep current language and provide it to application. We will use React Context to create LanguageContext and LanguageProvider component to provide language to the application.

interface LanguageContextInterface {
  changeLanguage: (language: string) => void;
  language: string;
}

const LanguageContext = React.createContext<LanguageContextInterface>({
  language: '',
  changeLanguage: () => {
    console.warn('LanguageContext.changeLanguage not implemented');
  },
});

export default LanguageContext;

Load translations

The next step is to create a component which will load translations and pass the internationalization provider down to the application.

Option 1: Hosted translations

If you want to use hosted translations, you need to fetch translations from the SimpleLocalize CDN, based on the language provided by LanguageProvider.

// Translation Hosting option
import React from "react";
import {IntlProvider, MessageFormatElement} from 'react-intl'
import LanguageContext from "./LanguageContext";

const PROJECT_TOKEN = "<YOUR_PROJECT_TOKEN>";
const BASE_URL = "https://cdn.simplelocalize.io";
const ENVIRONMENT = "_latest"; // or "_production"
const DEFAULT_LANGUAGE = "en"; // default language

const SimpleLocalize = ({children}: { children: React.ReactNode }) => {
  const [messages, setMessages] = React.useState<Record<string, string> | Record<string, MessageFormatElement[]> | undefined>({});
  const [language, setLanguage] = React.useState<string>(DEFAULT_LANGUAGE);

  const fetchTranslationMessages = (language: string): void => {
    const messages = `${BASE_URL}/${PROJECT_TOKEN}/${ENVIRONMENT}/${language}`;
    fetch(messages)
      .then((data) => data.json())
      .then((messages) => setMessages(messages));
  };

  React.useEffect(() => fetchTranslationMessages(language), [language]);

  return (
    <LanguageContext.Provider
      value={{
        changeLanguage: (language: string) => setLanguage(language),
        language
      }}>
      <IntlProvider
        locale={language}
        messages={messages}>
        {children}
      </IntlProvider>
    </LanguageContext.Provider>
  )
}

export default SimpleLocalize;

Option 2: Locally loaded files

If you want to load translations from local files, you need to import translations from the file system.

// Translation Hosting option
import React from "react";
import { IntlProvider, MessageFormatElement } from 'react-intl';
import LanguageContext from "./LanguageContext";

// Import translation files directly, you can use any method to load translations
import enMessages from './translations/en.json';
import esMessages from './translations/es.json';
import frMessages from './translations/fr.json';

const DEFAULT_LANGUAGE = "en"; // Default language
const TRANSLATIONS: Record<string, Record<string, string | MessageFormatElement[]>> = {
  en: enMessages,
  es: esMessages,
  fr: frMessages,
  // Add more languages as needed
};

const SimpleLocalize = ({ children }: { children: React.ReactNode }) => {
  const [language, setLanguage] = React.useState<string>(DEFAULT_LANGUAGE);

  const messages = TRANSLATIONS[language] || TRANSLATIONS[DEFAULT_LANGUAGE];

  return (
    <LanguageContext.Provider
      value={{
        changeLanguage: (language: string) => setLanguage(language),
        language
      }}>
      <IntlProvider
        locale={language}
        messages={messages}>
        {children}
      </IntlProvider>
    </LanguageContext.Provider>
  );
};

export default SimpleLocalize;

Both options are valid, and you can choose the one that fits your needs.

Wrap your application

The last step is to wrap your application with SimpleLocalize component we just created.

import React from 'react';
import ReactDOM from 'react-dom';
import './index.css';
import App from './App';
import SimpleLocalize from "./SimpleLocalize";

ReactDOM.render(
  <React.StrictMode>
    <SimpleLocalize>
      <App/>
    </SimpleLocalize>
  </React.StrictMode>,
  document.getElementById('root')
);

Usage

useIntl hook returns intl object that has formatMessage method to get translations by given id (translation key).

import React from 'react';
import {useIntl} from 'react-intl';

const App = () => {
  const intl = useIntl();
  return (
    <div>
      <h1>{intl.formatMessage({
            id: 'hello',
            defaultMessage: "My default message",
            description: "My code description"
        })}</h1>
    </div>
  );
};

export default App;

Alternately, you can use FormattedMessage component, that is a wrapper for formatMessage method from intl object. You can use it in the same way as formatMessage method but as a component.

<FormattedMessage
  id="YOUR_TRANSLATION_KEY"
  defaultMessage="My default message"
  description="My code description"
/>

By default, FormattedMessage component renders a span element. You can change it by passing tagName prop to the component.

<FormattedMessage
  id="YOUR_TRANSLATION_KEY"
  defaultMessage="My default message"
  description="My code description"
  tagName="h1"
/>

To use variables in translation messages, you can use values property.

<FormattedMessage
  id="YOUR_TRANSLATION_KEY"
  defaultMessage="Hello {name}"
  description="My code description"
  values={{
    name: 'John'
  }}
/>

Custom components in translation messages are supported as well. Use components property to pass React components to the translation message.

<FormattedMessage
  id="YOUR_TRANSLATION_KEY"
  defaultMessage="Hello <strong>{name}</strong>"
  description="My code description"
  values={{
    name: 'John'
  }}
  components={{
    strong: <strong/>
  }}
/>

Switching languages

To switch between languages, you have to use changeLanguage method from LanguageContext that we implemented earlier.

import LanguageContext from "./LanguageContext";

<LanguageContext.Consumer>
  {context => (<div>
    <button onClick={() => context.changeLanguage("en")}>English</button>
    <button onClick={() => context.changeLanguage("pl")}>Polish</button>
    <button onClick={() => context.changeLanguage("es")}>Spanish</button>
  </div>)}
</LanguageContext.Consumer>

Managing translations

Handling many translations and localization files manually can be challenging, especially when you have multiple languages. SimpleLocalize provides a web-based translation editor that allows you to manage translations in one place.

Let's configure SimpleLocalize CLI to upload and download translations. Create a simplelocalize.yml file in the root of your project with the following content:

apiKey: YOUR_PROJECT_API_KEY

uploadFormat: simplelocalize-json
uploadPath: ./extracted.json
uploadLanguageKey: en
uploadOptions:
  - REPLACE_TRANSLATION_IF_FOUND

downloadPath: ./translations/{lang}.json
downloadLanguageKeys: ['en', 'pl', 'es']
downloadFormat: single-language-json

We will use the exported (extracted.json) file from FormatJS CLI to upload translations to the translation editor. The downloadPath option will download translations for pl and es languages.

Run the following command to upload source translations:

simplelocalize upload

Now, you can manage and auto-translate translation messages in the translation editor.

If you decide to use locally loaded files, you can download translations and replace them with the new ones. Use the following command to download translations:

simplelocalize download

Now, your application will use translations from the SimpleLocalize translation editor.

Resources

Was this helpful?