| name | fluent-impl-react |
| description | Use when integrating Project Fluent with React via @fluent/react for translated UI components. Prevents misconfigured LocalizationProvider, broken DOM overlays, and incorrect Localized prop usage. Covers provider setup, Localized component, useLocalization hook, DOM overlays, and bundle generation. Keywords: LocalizationProvider, Localized, useLocalization, DOM overlay, ReactLocalization, @fluent/react, translate React component, i18n in React, localize React app, Fluent with React.
|
| license | MIT |
| compatibility | Designed for Claude Code. Requires @fluent/react 0.15+, @fluent/bundle 0.16+, React 16.8+. |
| metadata | {"author":"OpenAEC-Foundation","version":"1.0"} |
fluent-impl-react
Quick Reference
Package Stack
| Package | Version | Role |
|---|
@fluent/react | 0.15+ | React bindings (Provider, Localized, hooks) |
@fluent/bundle | 0.16+ (peer dep) | FluentBundle + FluentResource for parsing FTL |
react | 16.8+ (peer dep) | Hooks support required |
npm install @fluent/bundle @fluent/react
Public API Exports
| Export | Type | Purpose |
|---|
LocalizationProvider | Component | Distributes translations via React Context |
Localized | Component | Declarative translation rendering with overlays |
useLocalization | Hook | Imperative access to ReactLocalization instance |
ReactLocalization | Class | Core engine: stores, caches, queries bundles |
withLocalization | HOC | Legacy wrapper -- does NOT support ref forwarding |
MarkupParser | Type | (str: string) => Array<Node> for custom parsing |
Critical Warnings
ALWAYS declare attrs explicitly on <Localized> when translating HTML attributes. Omitting attrs causes translated attributes to be silently ignored -- this is a security measure preventing translations from injecting arbitrary attributes.
NEVER manipulate translated strings after formatting. Treat translation output as opaque. Let FTL handle all string composition through placeables and selectors.
NEVER create a new ReactLocalization instance inside a render function without memoization. This causes every <Localized> component to re-render on every parent render cycle.
ALWAYS use the useLocalization hook instead of the withLocalization HOC in functional components. The HOC does NOT support ref forwarding (open issue #598).
LocalizationProvider
The provider distributes a ReactLocalization instance to all descendant components via React Context. Every <Localized> and useLocalization() call MUST be inside a <LocalizationProvider>.
import { LocalizationProvider } from "@fluent/react";
import { ReactLocalization } from "@fluent/react";
<LocalizationProvider l10n={l10n}>
<App />
</LocalizationProvider>
| Prop | Type | Required | Description |
|---|
l10n | ReactLocalization | Yes | Localization instance with bundles |
Behavior: Manages subscriptions for all nested <Localized> elements. Establishes a fallback chain -- if a translation is missing in one bundle, the system proceeds to the next bundle in sequence.
Localized Component
The primary declarative API for rendering translations. Wraps a React element and replaces its text content (and optionally attributes) with translations.
Props
| Prop | Type | Required | Description |
|---|
id | string | Yes | FTL message identifier |
vars | Record<string, FluentVariable> | No | Variables passed to the translation |
elems | Record<string, ReactElement> | No | React elements for DOM overlay markup |
attrs | Record<string, boolean> | No | Attributes to translate (explicit opt-in) |
children | ReactElement | No | Fallback element shown if translation missing |
Basic Usage
hello-world = Hello, World!
<Localized id="hello-world">
<p>Hello, World!</p>
</Localized>
The child element serves as the fallback -- displayed when the translation is not found in any bundle.
Variables
welcome-user = Welcome, { $userName }!
<Localized id="welcome-user" vars={{ userName: currentUser.name }}>
<p>Welcome, {currentUser.name}!</p>
</Localized>
DOM Overlays (elems)
The elems prop maps custom element names in FTL to real React components:
create-account = <confirm>Create account</confirm> or <cancel>go back</cancel>.
<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>
How it works internally:
- The library parses the translation using a
<template> element, creating an inert DocumentFragment
- Unknown elements (like
<confirm>) become HTMLUnknownElement instances
- The library matches these by name against the
elems prop keys
- Provided React elements are cloned with the translated text content as children
Attribute Mapping (attrs)
my-link = Something
.title = Link to something
<Localized id="my-link" attrs={{ title: true }}>
<a href="/something" title="Link to something">Something</a>
</Localized>
Translated attributes are ONLY applied when explicitly declared as true in the attrs object. This is a security measure -- translations cannot inject arbitrary attributes onto DOM elements.
useLocalization Hook
Provides imperative access to translations within functional components. Returns the ReactLocalization instance from context.
import { useLocalization } from "@fluent/react";
function AlertButton() {
const { l10n } = useLocalization();
const handleClick = () => {
alert(l10n.getString("confirm-delete", { itemName: "Report" }));
};
return <button onClick={handleClick}>Delete</button>;
}
Return value: { l10n: ReactLocalization }
Primary use cases: window.alert(), window.confirm(), logging, validation messages, document.title, or any scenario where JSX rendering via <Localized> is not possible.
ReactLocalization Class
The core engine that stores, caches, and queries a sequence of FluentBundle instances.
Constructor
new ReactLocalization(
bundles: Iterable<FluentBundle>,
parseMarkup?: MarkupParser | null,
reportError?: (error: Error) => void
)
| Parameter | Default | Description |
|---|
bundles | -- | Bundle sequence (wrapped in CachedSyncIterable internally) |
parseMarkup | createParseMarkup() | Custom parser for DOM overlay markup |
reportError | console.warn | Error reporting callback |
Methods
| Method | Signature | Description |
|---|
getBundle | (id: string) => FluentBundle | null | Find first bundle containing message id |
getString | (id: string, vars?: Record<string, FluentVariable> | null, fallback?: string) => string | Format message to string; returns fallback or id if not found |
getElement | (sourceElement: ReactElement, id: string, args?: { vars?, elems?, attrs? }) => ReactElement | Format message into a React element with overlays and attributes |
areBundlesEmpty | () => boolean | Check if bundle iterable is exhausted/empty |
Bundle resolution: Iterates through bundles in order (representing locale preference chain). The first bundle containing the requested message id wins. This enables graceful degradation -- missing translations fall through to the next locale.
Bundle Generation Patterns
Array-Based (Eager)
function generateBundles(locales: string[]): FluentBundle[] {
return locales.map((locale) => {
const bundle = new FluentBundle(locale);
bundle.addResource(new FluentResource(MESSAGES[locale]));
return bundle;
});
}
Generator-Based (Lazy)
function* generateBundles(locales: string[]): Generator<FluentBundle> {
for (const locale of locales) {
const bundle = new FluentBundle(locale);
bundle.addResource(new FluentResource(MESSAGES[locale]));
yield bundle;
}
}
ALWAYS prefer the generator pattern for large locale sets -- fallback bundles are only created when needed. The ReactLocalization constructor wraps generators in CachedSyncIterable.from() so iteration happens only once.
Async Loading
async function fetchMessages(locale: string): Promise<string> {
const response = await fetch(`/locales/${locale}/messages.ftl`);
return response.text();
}
async function createLocalization(locales: string[]): Promise<ReactLocalization> {
const bundles = await Promise.all(
locales.map(async (locale) => {
const messages = await fetchMessages(locale);
const bundle = new FluentBundle(locale);
bundle.addResource(new FluentResource(messages));
return bundle;
})
);
return new ReactLocalization(bundles);
}
Critical limitation: As of v0.15, all translations (including fallback locales) MUST be fetched before rendering <LocalizationProvider>. There is no built-in support for async/lazy fallback locale loading during render.
SSR: Custom parseMarkup
When <template> is unavailable (server-side rendering, React Native), ALWAYS provide a custom parseMarkup function:
import { JSDOM } from "jsdom";
function parseMarkup(str: string): Node[] {
const dom = new JSDOM(`<body>${str}</body>`);
return Array.from(dom.window.document.body.childNodes);
}
const l10n = new ReactLocalization(bundles, parseMarkup);
Each returned Node MUST have nodeName and textContent properties.
withLocalization HOC (Legacy)
The withLocalization HOC exists but has a known limitation: it does NOT support ref forwarding (issue #598). ALWAYS prefer the useLocalization hook in functional components.
import { withLocalization, WithLocalizationProps } from "@fluent/react";
function MyComponent({ getString }: WithLocalizationProps) {
return <p>{getString("my-message")}</p>;
}
export default withLocalization(MyComponent);
Decision Trees
Choosing a Translation API
Need translation in JSX?
├── Yes → Use <Localized> component
│ ├── Need rich markup? → Use elems prop for DOM overlays
│ ├── Need translated attributes? → Use attrs prop with explicit opt-in
│ └── Need variables? → Use vars prop
└── No → Use useLocalization() hook
└── Call l10n.getString(id, vars, fallback)
Choosing Bundle Generation Strategy
How many locales?
├── 2-3 locales → Array-based (eager) is fine
├── 4+ locales → Generator-based (lazy) to avoid unnecessary work
└── Remote FTL files? → Async fetch + Promise.all before provider mount
Reference Links
- references/methods.md -- LocalizationProvider props, Localized props, useLocalization return, ReactLocalization constructor and methods, MarkupParser type
- references/examples.md -- Provider setup, Localized basic/vars/elems/attrs, useLocalization imperative, bundle generation patterns
- references/anti-patterns.md -- Creating ReactLocalization in render, missing attrs, withLocalization vs hook, post-translation manipulation
Official Sources