FormatJS

Last updated: September 11, 2024Author: 
FormatJS: changing translations in realtime

In this article, you will learn how to configure FormatJS (react-intl) library with hosted translations via Translation Hosting. Translation hosting allows you to change translations without re-deploying application as all messages are loaded directly from the cloud. However, altering the application code to use locally loaded files is also possible and straightforward.

FormatJS backed by Yahoo is a very good alternative for i18next, it's recommended for applications that want to keep the i18n as simple as possible.

This article comes with a demo application.

Configuration

Install dependencies

Add react-intl library to your React application project and @formatjs/cli as development package.

# Using NPM
npm install -S react-intl
npm install -D @formatjs/cli

# Using yarn
yarn add react-intl
yarn add -D @formatjs/cli

Adjust package.json scripts

Add extract script to your package.json file.

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

Create language context

First, you need to configure react-intl library to use SimpleLocalize as a translation provider. We will start from creating a LanguageContext that will be used to store current language and provide it 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;

Fetch translations

Next step is to create a SimpleLocalize component to fetch translations. We will use useEffect hook to fetch translations when the language in LanguageProvider changes. Additionally, we will use IntlProvider from react-intl to provide translations to the application.

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

const PROJECT_TOKEN = "<YOUR_PROJECT_TOKEN>"; // SimpleLocalize project > Settings > Credentials > 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;

SimpleLocalize can be also easily configured to use local files instead fetching translations from the cloud, but the way how you load files locally depends on your project setup.

Provide translations to application

Now, we need to wrap our application with SimpleLocalize component.

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')
);

Using translations

React-intl allows you to get translations in two ways:

Using useIntl hook

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;

Using FormattedMessage component

FormattedMessage component is a wrapper for formatMessage method from intl object, that means 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"
/>

Providing values to translation messages

You can use values property to pass variables to the translation message.

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

Learn more about message interpolation.

Using custom component in translation messages

FormattedMessage component also allows you to use custom components 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/>
  }}
/>

Extract translations

Now, let's use formatjs/cli which we installed together with react-intl library, to extract translations from the code.

npm run extract

This command will extract all translation keys from the code and save them to lang/en.json file, we set this file as an output file in the package.json script with --out-file lang/en.json argument.

Read more about FormatJS CLI integration.

Upload translations

Now it's time to upload translations to SimpleLocalize. We will use the exported lang/en.json file and simplelocalize-json format. Create simplelocalize.yml file with CLI configuration in your project root directory.

Copy
$ simplelocalize upload \
        --apiKey YOUR_API_KEY \
        --languageKey en \
        --uploadFormat simplelocalize-json \
        --uploadPath ./lang/en.json

Please note that we provided a languageKey parameter that matches the language of defaultMessage texts in your code. Thanks to that SimpleLocalize will automatically assign default messages as translations.

Translate application

Now, you can head to the Translation Editor and start translating your application.

Manage your translation strings in Translation Editor

Add new languages in the Languages tab and use auto-translation features to speed up the process. You can choose from multiple machine translation providers like OpenAI, Google Translate, or DeepL.

How to start auto-translation for many languages at once

Once auto-translation is finished you can review changes or just publish translations to the CDN by clicking the 'Save & Publish' button or 'Publish' button in the Hosting tab.

Switching languages

Let's test if our application can switch between languages. We will add two buttons to the application that will change the language to English or Spanish, and use LanguageContext.Consumer to change the current language.

import LanguageContext from "./LanguageContext";

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

Additional resources

Was this helpful?