Skip to content

Add createFixedFbt and FixedLocaleContext for locale-fixed translations#75

Open
bk-tho wants to merge 4 commits into
nkzw-tech:mainfrom
bk-tho:feat/create-fixed-fbt
Open

Add createFixedFbt and FixedLocaleContext for locale-fixed translations#75
bk-tho wants to merge 4 commits into
nkzw-tech:mainfrom
bk-tho:feat/create-fixed-fbt

Conversation

@bk-tho

@bk-tho bk-tho commented Apr 3, 2026

Copy link
Copy Markdown

Disclaimer: This was written entirely by Opus 4.6 High. I believe I guided it thoroughly into the right direction. I reviewed and tested the result myself.

Summary

Adds locale-fixed translation support for fbtee — the equivalent of i18next's getFixedT. Two complementary APIs:

1. <FixedLocaleContext> — React (transparent)

A context provider that transparently overrides the locale for all <fbt>, fbt(), <fbs>, and fbs() calls in descendant components. No call-site changes needed in children.

import { fbt, FixedLocaleContext } from "fbtee";

function App() {
  return (
    <div>
      <h1><fbt desc="title">Hello World</fbt></h1>
      <Suspense fallback="Loading...">
        <FixedLocaleContext locale="de_DE">
          <GermanPreview />
        </FixedLocaleContext>
      </Suspense>
    </div>
  );
}

function GermanPreview() {
  return <p><fbt desc="greeting">Hello World</fbt></p>;
}

On-demand translation loading: If translations for the requested locale are not yet loaded and a loadLocale hook has been registered (via setupLocaleContext), FixedLocaleContext will suspend until translations are fetched. Wrap in <Suspense> to handle the loading state. Already-loaded locales render immediately.

Note: Like all React context providers, the override only affects child components, not fbt/fbs calls in the same component that renders <FixedLocaleContext>.

2. createFixedFbt(locale, gender?) — Non-React (programmatic)

Returns { fbt, fbs } runtimes bound to a specific locale. Shadow the global fbt/fbs binding and the babel plugin compiles calls normally:

import { createFixedFbt } from "fbtee";
const { fbt } = createFixedFbt("de_DE");

const greeting = fbt("Hello", "greeting"); // → "Hallo"

How it works

Babel plugin

The plugin detects React components (/^[A-Z]/), hooks (/^use[A-Z0-9]/), and React.forwardRef/React.memo callbacks that contain fbt._() or fbs._() calls. For these functions, it:

  1. Injects const __fbtLocaleOverride = fbt.__locale?.() ?? null; at the top
  2. Passes __fbtLocaleOverride as 4th argument to all fbt._() / fbs._() calls

Non-component functions are left unchanged (use global locale).

Runtime

  • fbt.__locale() reads from FbtLocaleOverrideContext via a registered hook
  • fbt._() checks the 4th argument: if present, delegates to the fixed runtime; otherwise uses global hooks as before
  • <FixedLocaleContext> provides the fixed runtime (created by createFixedFbt) via React context
  • On-demand loading: if translations are missing, uses the registered loadLocale hook + React use() to suspend until loaded, then merges into FbtTranslations
  • createFixedFbt creates isolated runtimes with their own getTranslatedInput and getViewerContext

Backward compatibility

Scenario Behavior
Old plugin + new runtime fbt._() with 3 args → 4th is undefined → global hooks
New plugin + old runtime fbt._() with 4 args → extra arg ignored by JS
No <FixedLocaleContext> in tree __locale?.() returns null → global hooks
FixedLocaleContext not imported Hook never registered → null

Changes

packages/babel-plugin-fbtee/

  • src/index.tsxinjectLocaleOverrides() in Program.exit: component/hook detection + hook injection + 4th arg addition

packages/fbtee/

  • src/fbt.tsxFbtLocaleOverride type, __locale() method, localeOverride 4th param on _(), runtimeKey param on createRuntime
  • src/fbs.tsxruntimeKey: "fbs"
  • src/Hooks.tsxLoadLocaleFn type, loadLocale in Hooks, getLoadLocale(), getLocaleOverrideHook() / setLocaleOverrideHook()
  • src/setupLocaleContext.tsx — Registers loadLocale in hooks during setup
  • src/FixedLocaleContext.tsx — New: React context provider with on-demand translation loading via useEnsureLocaleLoaded + Suspense
  • src/createFixedFbt.tsx — New: isolated runtime factory
  • src/index-server.tsx — Exports createFixedFbt and FixedLocaleContext

example/

  • fixedLocale.html + src/fixedLocale.tsx + src/example/FixedLocaleDemo.tsx — Comprehensive demo page with on-demand loading (1s simulated delay), Suspense fallbacks, all test cases
  • src/example/Example.tsx — Multi-Locale Preview section

Tests

  • 13 babel plugin tests (component detection, hook detection, non-component exclusion, fbs, forwardRef/memo, 4th arg)
  • 6 runtime FixedLocaleContext tests (fixed locale, isolation, nesting, global unaffected, fbs, async loading with Suspense)
  • 7 runtime createFixedFbt tests (locale, fbs, fallback, gender, concurrency, late registration)

bk-tho added 2 commits April 4, 2026 00:10
Adds a createFixedFbt(locale, gender?) function that returns { fbt, fbs }
runtimes bound to a specific locale and gender. Unlike the global fbt/fbs,
these runtimes are fully isolated — they do not read from or write to the
global Hooks singleton — making them safe for concurrent use in async
contexts like React Server Components.

Implementation:
- Extend createRuntime to accept optional getTranslatedInput,
  getViewerContext, and getErrorListener overrides (defaulting to Hooks.*)
- Extract fbtParam/fbtPlural and fbsParam/fbsPlural as named exports so
  they can be reused by createFixedFbt without duplication
- createFixedFbt creates closures over the fixed locale/gender and passes
  them to createRuntime, producing isolated fbt and fbs instances
Adds <FixedLocaleContext locale="de_DE"> — a React context provider that
transparently overrides the locale for all <fbt>, fbt(), <fbs>, and fbs()
calls in descendant components. No call-site changes needed in children.

Babel plugin changes:
- Detects React components (/^[A-Z]/), hooks (/^use[A-Z0-9]/), and
  React.forwardRef/memo callbacks that contain fbt/fbs calls
- Injects `const __fbtLocaleOverride = fbt.__locale?.() ?? null;` at the
  top of qualifying functions
- Passes __fbtLocaleOverride as 4th argument to all fbt._() / fbs._() calls

Runtime changes:
- createRuntime gains __locale() method (reads from registered hook) and
  localeOverride 4th param on _() (delegates to fixed runtime when present)
- FixedLocaleContext registers a React context hook via setLocaleOverrideHook
  and provides createFixedFbt(locale, gender) to descendants
- Fully backward compatible: old plugin + new runtime and vice versa both
  work correctly (extra args are ignored, missing override defaults to null)

Example app:
- Adds Multi-Locale Preview section demonstrating <FixedLocaleContext>
  rendering the same string in 4 languages simultaneously
@bk-tho bk-tho changed the title Add createFixedFbt for locale-fixed translation runtimes Add createFixedFbt and FixedLocaleContext for locale-fixed translations Apr 4, 2026
bk-tho added 2 commits April 4, 2026 17:52
FixedLocaleContext now checks if translations for the requested locale
are available. If not, it uses the loadLocale hook registered during
setupLocaleContext to fetch them, suspending via React's use() until
loaded. Translations are cached per locale and merged into the global
FbtTranslations dictionary.

Changes:
- Hooks: add LoadLocaleFn type, loadLocale to Hooks, getLoadLocale()
- setupLocaleContext: registers loadLocale in hooks during setup
- FixedLocaleContext: useEnsureLocaleLoaded() suspends if translations
  need loading, caches promises per locale
- Test: async loading with Suspense
Adds example/fixedLocale.html demonstrating FixedLocaleContext with:
- Only en_US pre-loaded, all other locales loaded on demand via loadLocale
- 1s simulated delay to make Suspense fallbacks visible
- Test cases: basic fbt/fbs, functional calls, mixed, nested contexts,
  sibling contexts, and all available locales
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant