Skip to content

React Overlays

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

Translations in Fluent can include simple HTML markup. @fluent/react will match them with props to <Localized>. If the prop is a React element, its content will be replaced by the localizable content found in the markup the translation.

<Localized
    id="create-account"
    elems={{
        confirm: <button onClick={createAccount}></button>,
        cancel: <Link to="/"></Link>
    }}
>
    <p>{'<confirm>Create account</confirm> or <cancel>go back</cancel>.'}</p>
</Localized>

The overlaid result will include the source elements passed as props interpolated into the translation:

<p>
    <button onClick={createAccount}>Create account</button> or <Link to="/">go back</Link>.
</p>

Technical details

When @fluent/react sees markup in the translation it parses the translation string using a <template> element. This creates a safe inert DocumentFragment with a hierarchy of text nodes and HTML elements. Any unknown elements (e.g. cancel in the example above) are parsed as HTMLUnknownElements. @fluent/react then tries to match all elements found in the translation with props passed to the <Localized> component. If a match is found, the element passed as a prop is cloned with the translated content taken from the DocumentFragment used as children.

Custom Markup Parsers

Sometimes the <template> element isn't available for parsing the markup from the translation. This may be the case in projects which perform server-side rendering (SSR) or apps written in React Native. In these situations a custom parseMarkup can be passed as the second argument to ReactLocalization. It will be used by all <Localized> components connect to it via the <LocalizationProvider>.

By using the custom markup parser, you can work around the lack of the <template> elements in environments where document is not defined. You can use jsdom or cheerio, or any other available DOM implementation or an XML/HTML parser. Or, you can choose to ignore the fact that translations may include HTML. If you do, please make sure your localizers know about this limitation.

The parseMarkup function must have the following signature:

parseMarkup(str: string): Array<Node>

It takes a string and must return an array of objects partially implementing the DOM Node interface. Only the following 2 properties are strictly required:

nodeName
textContent
// This custom markup parser doesn't actually parse anything,
// but it pretends to have parsed a single Node. This node
// contains the entire contents of the string passed into
// the parser, converted to upper case.
function parseMarkup(str) {
    return [
        {
            nodeName: "#text",
            textContent: str.toUpperCase()
        }
    ];
}

let l10n = new ReactLocalization(bundles, parseMarkup);

<LocalizationProvider l10n={l10n}>
    <App />
</LocalizationProvider>