Repository navigation
fbtee 6.0 uses real React elements for rich translations and plain strings for text. Components inside translations now preserve their state across renders, and the string API makes it explicit when a translation is used outside React.
- New
- Added
@nkzw/fbtee/prefer-fbsto the ESLint plugin’s recommended and strict configurations. It catches directString(fbt(...))conversions and guides migration tofbs. - Added numeric parameters and custom plural values to
fbs, including locale-aware number formatting.
- Added
- Improved
- Rich translations now return React Fragments. Components inside translations retain their state and refs across parent renders.
- Text translations return primitive strings in both browser and server environments.
- Reduced React update time by approximately 63% in the production update benchmark. Cached translation lookup performance remains essentially unchanged.
- Fixed
- Fixed unkeyed React parameters remounting when a translation reorders different tokens. Repeated parameters retain separate identities.
- Fixed empty translations missing the result cache.
- Fixed plural forms being lost when the count is hidden, including languages with more than two plural forms.
- Fixed translation preparation losing plural dimensions when the same text appears with different count names.
- Breaking Changes
fbt()andfbt.c()now returnstring | ReactElement. Usefbs()andfbs.c()when a consumer requires a string.FbtResultis now a type alias. The result classes and theirgetContents(),flattenToArray(),toJSON(), and custom string conversion methods have been removed.- Custom result hooks must return a string or React element for
fbt, and a string forfbs.
New Features
React Translations Preserve Component State
Let’s say a translated sentence contains an input:
<fbt desc="Name entry">
Your name is
<fbt:param name="name">
<input defaultValue="Ada" />
</fbt:param>
</fbt>Previously, updating the parent could remount the input and reset its value. fbtee now returns a real React Fragment, so React can preserve the input and its state.
Unkeyed parameters receive stable keys based on their token names and occurrences. If a translation reorders different parameters, their state moves with them. Explicit keys are preserved.
If your application relied on a translation remounting to reset a component, give that component an explicit key that changes when it should reset.
Strings for Text Consumers
Use fbt to render translations and fbs when a consumer needs text:
import { fbs, fbt } from 'fbtee';
export function SaveButton() {
return (
<button aria-label={fbs('Save changes', 'Save button label')}>
{fbt('Save', 'Save button text')}
</button>
);
}fbs returns a primitive string. Use it for attributes, notifications, document titles, stored messages, and other text APIs. Its parameter types reject React elements, helping catch accidental rich content before it reaches a string consumer.
Numeric parameters work directly:
import { fbs } from 'fbtee';
const message = fbs(
['Balance: ', fbs.param('balance', balance, { number: true })],
'Account balance',
);The number uses the active locale’s formatting. Without { number: true }, numeric parameters retain their ordinary string representation.
Performance
The production React update benchmark improved from approximately 12.1 μs to 4.5 μs per update. The measured workload mounted its child 20,000 times with the previous implementation and once with fbtee 6.
Cached lookups stayed essentially unchanged. Individual construction benchmarks vary: nested rich-message construction was about 6% slower in the browser simulation. These are local microbenchmarks. Benchmark details
Upgrade Guide
Replace string conversions with the string API:
// Before
String(fbt('Hello', 'Greeting'));
String(fbt.c('Save'));
// After
fbs('Hello', 'Greeting');
fbs.c('Save');Update nested helpers along with the outer call:
// Before
String(
fbt(
['Hello ', fbt.param('name', name)],
'Greeting',
),
);
// After
fbs(
['Hello ', fbs.param('name', name)],
'Greeting',
);Keep string or TranslatedString for text-only props. Use FbtResult or ReactNode for props that render translations.
The new @nkzw/fbtee/prefer-fbs rule catches direct conversions, including imported aliases and String(<fbt>…</fbt>). It does not follow values through variables or functions. Review indirect String() calls, template interpolation, .toString(), and JSON serialization separately. Stringifying a rich translation no longer extracts its text.
Text results are now primitives, so checks involving typeof, object identity, or empty-string truthiness may behave differently. Custom hooks should use their hash argument for message metadata instead of inspecting result objects.