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.
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
- In-depth guide on FormatJS CLI
- Full code on GitHub
- Official documentation: FormatJS
- JavaScript number formatting