Skip to content

v6.0.0

Latest

Choose a tag to compare

@cpojer cpojer released this 11 Nov 08:46
· 1 commit to main since this release
v6.0.0
eeade6b

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-fbs to the ESLint plugin’s recommended and strict configurations. It catches direct String(fbt(...)) conversions and guides migration to fbs.
    • Added numeric parameters and custom plural values to fbs, including locale-aware number formatting.
  • 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() and fbt.c() now return string | ReactElement. Use fbs() and fbs.c() when a consumer requires a string.
    • FbtResult is now a type alias. The result classes and their getContents(), 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 for fbs.

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.

Full migration guide