Skip to content

React Bindings

Staś Małolepszy edited this page Apr 7, 2020 · 8 revisions

@fluent/react provides React bindings for Project Fluent, a localization framework designed to unleash the expressive power of the natural language.

Together with other @fluent packages and a little bit of glue code, @fluent/react is meant to be a complete yet flexible solution to localizing React apps. @fluent/react takes advantage of React's Components system and the virtual DOM. Translations are defined using the Fluent syntax (FTL) and exposed to Localized components via LocalizationProvider.

The Fluent Syntax

FTL is a localization file format used for describing translation resources. FTL stands for Fluent Translation List. FTL is designed to be simple to read, but at the same time allows to represent complex concepts from natural languages like gender, plurals, conjugations, and others. An example FTL file might look like the following:

hello-world = Hello, world!
welcome = Welcome, {$username}.

Learn more about the syntax by reading the Fluent Syntax Guide. An online editor is also available at the Fluent Playground.

Getting Started

@fluent/react alone is only responsible for displaying translated components in your React app. For a complete solution, you'll need to write code which:

  1. negotiates the best language for the user; you can use @fluent/langneg for this,
  2. fetches the translation files; you can bundle some translations with the app for sync access or load them asynchronously,
  3. creates an iterable of FluentBundle instances; FluentBundle is a class responsible for parsing and formatting translations; it is provided by the @fluent/bundle package. This iterable then needs to be used to instantiate a ReactLocalization, which is passed as the l10n prop to the LocalizationProvider.

This setup allows the most flexibility when it comes to storing and loading user language preferences and translations on runtime.

In most of the cases you'll want to install the following packages:

npm install @fluent/bundle @fluent/langneg @fluent/react intl-pluralrules

Wrapped components

In principle, @fluent/react works by wrapping localizable elements in Localized, a special element which subscribes to a localization store.

<Localized id="hello">
    <p>Hello, world!</p>
</Localized>

This allows the developer to use any DOM elements and other React Components. The id prop should be the unique identifier of the translation. You can also pass arguments to the translation (strings, numbers, dates and React elements). See the documentation of the Localized component for more information and examples.

LocalizationProvider

All <Localized> components need access to a translation store to which they subscribe. The LocalizationProvider component implements the provider pattern to make translations available to all localized elements in the app without passing them explicitly. It expects a single prop, l10n, which should be an instance of ReactLocalization holding the sequence of FluentBundles in order of the user's language preference.

Sometimes it's useful to imperatively retrieve a translation rather than rely on the declarative <Localized /> API. Good examples of this are the window.alert and window.confirm functions. It's possible to connect any component to its enclosing LocalizationProvider using the withLocalization higher-order component (HOC).

A complete example

import 'intl-polyfill';
import { negotiateLanguages } from '@fluent/langneg';
import { FluentBundle, FluentResource } from '@fluent/bundle';
import { LocalizationProvider, ReactLocalization } from '@fluent/react';

// Store all translations as a simple object which is available 
// synchronously and bundled with the rest of the code.
const RESOURCES = {
    'fr': new FluentResource('hello = Salut le monde !'),
    'en-US': new FluentResource('hello = Hello, world!'),
    'pl': new FluentResource('hello = Witaj świecie!'),
};

// A generator function responsible for building the sequence 
// of FluentBundle instances in the order of user's language
// preferences.
function* generateBundles(userLocales) {
    // Choose locales that are best for the user.
    const currentLocales = negotiateLanguages(
        userLocales,
        ['fr', 'en-US', 'pl'],
        { defaultLocale: 'en-US' }
    );

    for (const locale of currentLocales) {
        const bundle = new FluentBundle(locale);
        bundle.addResource(RESOURCES[locale]);
        yield bundle;
    }
}

// The ReactLocalization instance stores and caches the sequence of generated
// bundles. You can store it in your app's state.
let l10n = new ReactLocalization(generateBundles(navigator.languages));

ReactDOM.render(
    <LocalizationProvider l10n={l10n}>
        <div>
            <Localized id="hello">
                <h1>Hello, world!</h1>
            </Localized>
        </div>
    </LocalizationProvider>,
    document.getElementById('root')
);

Consult the fluent-react/example directory for a working example of a React app using `@fluent/react.