@nkzw/eslint-plugin-fbtee 3.3.0 → 5.0.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (3) hide show
  1. package/README.md +220 -240
  2. package/lib/index.mjs +1 -1
  3. package/package.json +1 -1
package/README.md CHANGED
@@ -10,132 +10,92 @@ const Greeting = ({ name }) => (
10
10
  );
11
11
  ```
12
12
 
13
- _fbtee_ is a modern continuation of Facebook's `fbt`, rebuilt for TypeScript, ESM, React 19, Vite, Next.js, Babel & Oxc.
13
+ _fbtee_ is a modern continuation of Facebook's `fbt`, rebuilt for TypeScript, ESM, React 19, Vite, Next.js, and Oxc.
14
14
 
15
15
  ## Features
16
16
 
17
17
  - **Inline translations for Better Developer Experience:** Embed translations directly into your code. No need to manage translation keys or wrap your code with `t()` functions. **fbtee** uses a compiler to extract strings from your code and prepare them for translation providers.
18
18
  - **Proven in Production:** Built on Facebook's `fbt`, with over a decade of production usage serving billions of users, plus years in production at [Athena Crisis](https://athenacrisis.com).
19
19
  - **Optimized Performance with IR:** Compiles translations into an Intermediate Representation (IR) for extracting strings, then optimizes the runtime output for performance.
20
- - **Easy Setup:** Quick integration with Vite, Next.js, Babel, Oxc, and Expo.
20
+ - **Easy Setup:** Quick integration with Vite, Next.js, Oxc, and Expo.
21
21
 
22
22
  ## Getting Started
23
23
 
24
- Use one of the templates if you are starting fresh:
25
-
26
- - [Web App Template](https://github.com/nkzw-tech/web-app-template)
27
- - [Expo App Template](https://github.com/nkzw-tech/expo-app-template)
28
-
29
- For an existing app, install the runtime:
24
+ For a new project, start with the [fate stack](https://stack.fate.technology) or the [Expo template](https://github.com/nkzw-tech/expo-app-template). For an existing app, install the fbtee runtime and compiler:
30
25
 
31
26
  ```bash
32
27
  npm install fbtee
28
+ npm install -D @nkzw/fbtee-compiler
33
29
  ```
34
30
 
35
- _fbtee_ requires Node 22+. React apps should use React 19+.
31
+ The compiler package includes the `fbtee` command and the JavaScript compiler API. Keep it in `devDependencies`; applications use `fbtee` at runtime.
36
32
 
37
- ### Vite
33
+ The toolchain requires Node 22.12+. React apps require React 19+.
38
34
 
39
- Most projects should install the ready-to-use Vite plugin and the CLI:
35
+ ### Vite
40
36
 
41
37
  ```bash
42
- npm install -D @nkzw/vite-plugin-fbtee @nkzw/fbtee-cli
38
+ npm install -D @nkzw/vite-plugin-fbtee
43
39
  ```
44
40
 
45
- Add the fbtee plugin before the React plugin:
41
+ Place fbtee before the React plugin:
46
42
 
47
- ```js
43
+ ```ts
48
44
  import fbtee from '@nkzw/vite-plugin-fbtee';
49
45
  import react from '@vitejs/plugin-react';
50
46
  import { defineConfig } from 'vite';
51
47
 
52
48
  export default defineConfig({
53
- plugins: [
54
- fbtee({
55
- fbtCommon: {
56
- Accept: 'Button label for accepting terms',
57
- },
58
- fbtEnumManifest: {},
59
- }),
60
- react(),
61
- ],
49
+ plugins: [fbtee(), react()],
62
50
  });
63
51
  ```
64
52
 
65
53
  ### Next.js
66
54
 
67
- Next.js projects should use the native Oxc plugin and the CLI:
68
-
69
55
  ```bash
70
- npm install -D @nkzw/next-plugin-fbtee @nkzw/fbtee-cli
56
+ npm install -D @nkzw/next-plugin-fbtee
71
57
  ```
72
58
 
73
- Wrap your Next configuration:
74
-
75
- ```js
59
+ ```ts
76
60
  import withFbtee from '@nkzw/next-plugin-fbtee';
77
61
 
78
- export default withFbtee({
79
- fbtCommon: {
80
- Accept: 'Button label for accepting terms',
81
- },
82
- fbtEnumManifest: {},
83
- })({
84
- // Your Next.js configuration.
85
- });
62
+ export default withFbtee()({});
86
63
  ```
87
64
 
88
- The plugin runs the native Oxc transform before Next.js compilation. It works with the default Turbopack compiler and with `next build --webpack`, so it does not require Babel or custom compiler configuration.
65
+ The plugin supports Turbopack and Webpack.
89
66
 
90
- ### Babel
67
+ <details>
68
+ <summary>Custom builds with Oxc</summary>
91
69
 
92
- Install the Babel preset and the CLI:
70
+ Run the fbtee transform before lowering TypeScript and JSX:
93
71
 
94
72
  ```bash
95
- npm install -D @nkzw/babel-preset-fbtee @nkzw/fbtee-cli
73
+ npm install -D @nkzw/fbtee-compiler oxc-transform
96
74
  ```
97
75
 
98
- Add the preset to your Babel configuration:
99
-
100
- ```js
101
- export default {
102
- presets: ['@nkzw/babel-preset-fbtee'],
103
- };
104
- ```
105
-
106
- ### Low-level Oxc Transform
107
-
108
- For custom build-system integrations, install the native Oxc transform:
109
-
110
- ```bash
111
- npm install -D @nkzw/oxc-transform-fbtee @nkzw/fbtee-cli oxc-transform
112
- ```
113
-
114
- Apply the fbtee transform before the standard Oxc transform:
115
-
116
- ```js
117
- import { transformSync as transformFbtee } from '@nkzw/oxc-transform-fbtee';
76
+ ```ts
77
+ import { transformSync as transformFbtee } from '@nkzw/fbtee-compiler';
118
78
  import { transformSync as transformOxc } from 'oxc-transform';
119
79
 
120
- const fbteeResult = transformFbtee(filename, sourceText, {
121
- fbtCommon: {
122
- Accept: 'Button label for accepting terms',
123
- },
124
- fbtEnumManifest: {},
125
- });
80
+ export function compile(filename: string, source: string) {
81
+ const translated = transformFbtee(filename, source);
82
+ if (translated.errors.length) {
83
+ throw new Error(translated.errors.map(({ message }) => message).join('\n'));
84
+ }
126
85
 
127
- if (fbteeResult.errors.length > 0) {
128
- throw new Error(fbteeResult.errors.map(({ message }) => message).join('\n'));
86
+ return transformOxc(filename, translated.code, {
87
+ jsx: { runtime: 'automatic' },
88
+ });
129
89
  }
130
-
131
- const result = transformOxc(filename, fbteeResult.code, {
132
- jsx: { runtime: 'automatic' },
133
- });
134
90
  ```
135
91
 
136
- ### TypeScript JSX Types
92
+ The returned Oxc result contains the generated code and any downstream errors. See the [transform options](https://github.com/nkzw-tech/fbtee/blob/main/packages/fbtee-compiler/index.d.ts) for common strings and enum configuration.
137
93
 
138
- React TypeScript projects should include the JSX declarations once in a global type file or app entry point:
94
+ </details>
95
+
96
+ ### TypeScript
97
+
98
+ Add the JSX declarations to a global type file or your app entry point:
139
99
 
140
100
  ```tsx
141
101
  /// <reference types="fbtee/ReactTypes.d.ts" />
@@ -143,31 +103,25 @@ React TypeScript projects should include the JSX declarations once in a global t
143
103
 
144
104
  ## Writing Strings
145
105
 
146
- Every user-facing string should be wrapped in `<fbt>`, `fbt()`, or `fbs()`. Descriptions are required because they are the translator's context.
106
+ Wrap a sentence in `<fbt>` and describe where it appears. The description helps translators choose the right words:
147
107
 
148
108
  ```tsx
149
- <fbt desc="Empty state title when a project has no tasks">No tasks yet</fbt>
109
+ <fbt desc="Empty state when a project has no tasks">No tasks yet</fbt>
150
110
  ```
151
111
 
152
- Use `fbt()` outside JSX:
112
+ The compiler supplies the global `fbt` import for JSX. Use `fbt()` in JavaScript, or `fbs()` where an API requires a plain string:
153
113
 
154
114
  ```tsx
155
- import { fbt } from 'fbtee';
115
+ import { fbs, fbt } from 'fbtee';
156
116
 
157
- const title = fbt('No tasks yet', 'Empty state title when a project has no tasks');
158
- ```
117
+ const title = fbt('No tasks yet', 'Empty state when a project has no tasks');
159
118
 
160
- Use `fbs()` when you need a plain string:
161
-
162
- ```tsx
163
- import { fbs } from 'fbtee';
164
-
165
- <input placeholder={fbs('Search projects', 'Project search input placeholder')} />;
119
+ <input placeholder={fbs('Search projects', 'Project search placeholder')} />;
166
120
  ```
167
121
 
168
- ## Dynamic Content
122
+ ### Dynamic Content
169
123
 
170
- Use `<fbt:param>` for dynamic values. Token names should describe the value, not its current English position.
124
+ Give dynamic values meaningful names with `<fbt:param>`. React elements inside a sentence become parameters automatically, as in the greeting above.
171
125
 
172
126
  ```tsx
173
127
  <fbt desc="Greeting with the viewer name">
@@ -175,255 +129,281 @@ Use `<fbt:param>` for dynamic values. Token names should describe the value, not
175
129
  </fbt>
176
130
  ```
177
131
 
178
- React elements inside `<fbt>` are automatically turned into implicit params:
179
-
180
- ```tsx
181
- <fbt desc="Greeting with a linked viewer name">
182
- Hello, <UserLink user={viewer} />!
183
- </fbt>
184
- ```
185
-
186
- Use `fbt.sameParam()` or `<fbt:same-param>` when the same token appears more than once.
132
+ For repeated values, use `<fbt:same-param>` or `fbt.sameParam()`.
187
133
 
188
- ## Plurals
134
+ ### Plurals and Lists
189
135
 
190
- Use `<fbt:plural>` when a count controls grammar. _fbtee_ handles locale-specific plural rules.
136
+ Describe the singular and plural forms in your source language. fbtee selects the translated form for the locale:
191
137
 
192
138
  ```tsx
193
139
  <fbt desc="Inbox unread count">
194
140
  You have{' '}
195
- <fbt:plural count={count} many="unread messages" name="unreadCount" showCount="yes">
196
- an unread message
197
- </fbt:plural>
198
- .
141
+ <fbt:plural count={count} many="unread messages" name="count" showCount="yes">
142
+ unread message
143
+ </fbt:plural>.
199
144
  </fbt>
200
145
  ```
201
146
 
202
- `showCount` can be `yes`, `ifMany`, or `no`.
203
-
204
- ## Lists
205
-
206
- Use `<fbt:list>` for locale-aware lists:
147
+ `showCount` accepts `yes`, `ifMany`, or `no`. For lists, use `<fbt:list>` or the standalone `list()` helper:
207
148
 
208
149
  ```tsx
209
150
  <fbt desc="People assigned to a task">
210
- Assigned to <fbt:list items={assignees} name="assigneeList" />.
151
+ Assigned to <fbt:list items={assignees} name="assignees" />.
211
152
  </fbt>
212
153
  ```
213
154
 
214
- The standalone `list()` helper is available for non-React code:
155
+ fbtee also supports enums, gender, and pronouns. See the [website examples](https://fbtee.dev) for each.
215
156
 
216
- ```tsx
217
- import { list } from 'fbtee';
157
+ ### Common Strings
218
158
 
219
- const names = list(['Alice', 'Bob', 'Charlie'], 'or', 'comma');
220
- ```
159
+ Share a description for labels that mean the same thing throughout the app. Define them in `common_strings.json`:
221
160
 
222
- ## Enums
161
+ ```json
162
+ {
163
+ "Save": "Button label for saving changes"
164
+ }
165
+ ```
223
166
 
224
- Use enums when runtime values map to a fixed set of translatable labels:
167
+ Pass this object as `fbtCommon` to your compiler plugin and add `--common common_strings.json` when collecting strings. Then use the label without repeating its description:
225
168
 
226
169
  ```tsx
227
- const StatusLabels = {
228
- done: 'done',
229
- open: 'open',
230
- };
231
-
232
- <fbt desc="Task status label">
233
- This task is <fbt:enum enum-range={StatusLabels} value={status} />.
234
- </fbt>;
170
+ <fbt common>Save</fbt>
235
171
  ```
236
172
 
237
- For shared enum modules, use the `$FbtEnum` suffix so the collector can resolve them.
173
+ ## Translation Workflow
238
174
 
239
- ## Pronouns and Gender
175
+ First, extract the strings and prepare a file for each language:
240
176
 
241
- Use `<fbt:pronoun>` when a phrase depends on a person's gender:
177
+ ```bash
178
+ npx fbtee collect
179
+ npx fbtee prepare-translations --source-strings source_strings.json --output-dir translations --locales de-DE ja-JP
180
+ ```
242
181
 
243
- ```tsx
244
- <fbt desc="Photo sharing notification">
245
- <fbt:param name="name">{user.name}</fbt:param> shared{' '}
246
- <fbt:pronoun gender={user.gender} human type="possessive" /> photo.
247
- </fbt>
182
+ Translate the entries marked `"status": "new"`, then remove that status. Existing translations are preserved when you run the command again. Compile the files for your app:
183
+
184
+ ```bash
185
+ npx fbtee translate --source-strings source_strings.json --translations 'translations/*.json' --output-dir src/translations
248
186
  ```
249
187
 
250
- Supported pronoun types are `subject`, `object`, `possessive`, and `reflexive`.
188
+ Generated runtime catalogs omit messages with no completed translation, including entries still marked `"status": "new"`. This lets partial catalogs fall back to another language. After upgrading, recompile existing catalogs to enable this behavior. Explicit translations equal to the source text are preserved. Fallback applies to whole messages; a partially translated plural/gender table keeps its existing within-message fallback behavior.
251
189
 
252
- ## Common Strings
190
+ Use `fbtee translate --strict` to require a completed entry for every collected source hash in each supplied locale. Missing entries, entries marked `"status": "new"`, and empty translation lists fail before output is written. Explicit empty-string translations and translations equal to the source remain valid.
253
191
 
254
- Common strings are reusable source strings with shared descriptions:
192
+ Commit the editable files in `translations/`. Add generated files to `.gitignore`:
255
193
 
256
- ```tsx
257
- <fbt common>Save</fbt>
194
+ ```gitignore
195
+ .enum_manifest.json
196
+ source_strings.json
197
+ src/translations/
258
198
  ```
259
199
 
260
- Pass common strings to the compiler as `fbtCommon`:
200
+ The npm package includes a [translation skill](https://github.com/nkzw-tech/fbtee/blob/main/packages/fbtee/skills/fbtee-translate/SKILL.md) for coding agents. It prepares translations, fills every new entry across the supported locales, and uses existing translations and source-code context to match terminology and tone. To use it, ask your agent:
261
201
 
262
- ```js
263
- {
264
- fbtCommon: {
265
- Save: 'Button label for saving changes',
266
- },
267
- }
202
+ ```text
203
+ Read node_modules/fbtee/skills/fbtee-translate/SKILL.md and translate all new strings for all supported locales.
268
204
  ```
269
205
 
206
+ Review the resulting diff as you would any translation.
207
+
270
208
  ## Runtime Setup
271
209
 
272
- Most React apps should use `createLocaleContext`:
210
+ For an app with one active language, use `createLocaleContext`. It selects a supported locale from the browser's preferences and loads translations when the language changes.
273
211
 
274
212
  ```tsx
275
213
  import { createLocaleContext } from 'fbtee';
276
-
277
- const availableLanguages = new Map([
278
- ['en-US', 'English'],
279
- ['de-DE', 'Deutsch'],
280
- ['ja-JP', '日本語'],
281
- ]);
282
-
283
- const loadLocale = async (locale: string) => {
284
- switch (locale) {
285
- case 'de-DE':
286
- return (await import('./translations/de-DE.json')).default['de-DE'];
287
- case 'ja-JP':
288
- return (await import('./translations/ja-JP.json')).default['ja-JP'];
289
- default:
290
- return {};
291
- }
292
- };
214
+ import { createRoot } from 'react-dom/client';
215
+ import App from './App.tsx';
293
216
 
294
217
  const LocaleContext = createLocaleContext({
295
- availableLanguages,
296
- clientLocales: [navigator.language, ...navigator.languages],
297
- loadLocale,
218
+ availableLanguages: new Map([
219
+ ['en-US', 'English'],
220
+ ['de-DE', 'Deutsch'],
221
+ ]),
222
+ clientLocales: navigator.languages,
223
+ loadLocale: async (locale) =>
224
+ locale === 'de-DE' ? (await import('./translations/de-DE.json')).default['de-DE'] : {},
298
225
  });
299
226
 
300
- export const Root = () => (
227
+ await LocaleContext.preload();
228
+
229
+ createRoot(document.getElementById('root')!).render(
301
230
  <LocaleContext>
302
231
  <App />
303
- </LocaleContext>
232
+ </LocaleContext>,
304
233
  );
305
234
  ```
306
235
 
307
- Use `useLocaleContext` to read or change the locale:
236
+ Call `preload()` once before rendering to load the initial language and its available fallback catalogs. Creating the context does not load them automatically. You can skip preloading if you supply all those catalogs at setup; the source locale does not require a catalog.
237
+
238
+ Preloading and language changes share pending loads. Loading failures reject the promise; calling again retries them.
239
+
240
+ Use `useLocaleContext()` to change the language in a React transition:
308
241
 
309
242
  ```tsx
310
243
  import { useLocaleContext } from 'fbtee';
244
+ import { useTransition } from 'react';
311
245
 
312
- const LanguageButton = () => {
246
+ function LanguageButton() {
313
247
  const { locale, setLocale } = useLocaleContext();
314
- return <button onClick={() => setLocale('de-DE')}>{locale}</button>;
315
- };
316
- ```
248
+ const [, startTransition] = useTransition();
317
249
 
318
- If you need full control, use `setupLocaleContext` or `setupFbtee` directly.
250
+ return <button onClick={() => startTransition(() => setLocale('de-DE'))}>{locale}</button>;
251
+ }
252
+ ```
319
253
 
320
- ### Next.js App Router
254
+ Outside React, use `setupLocaleContext` and await `preloadLocale()`, or configure the runtime directly with `setupFbtee`.
321
255
 
322
- For App Router, put the locale context in a Client Component. If you render translated strings in Server Components, call `setupFbtee` on the server and re-render the route when the locale changes. Client-only strings can switch locale fully on the client.
256
+ ### Formatting Numbers and Dates
323
257
 
324
- See the [Next.js fbtee example](https://github.com/cpojer/nextjs-fbtee-example) for a complete setup.
258
+ Use JavaScript's native `Intl` APIs for numbers, currencies, dates, times, and relative time. Pass fbtee's active locale to the formatter and insert the result with `<fbt:param>`:
325
259
 
326
- ## Translation Workflow
260
+ ```tsx
261
+ import { useLocaleContext } from 'fbtee';
327
262
 
328
- Every CLI command can use the native Oxc/Rust pipeline by adding `--oxc`:
263
+ function Price({ amount, currency }: { amount: number; currency: string }) {
264
+ const { locale } = useLocaleContext();
265
+ const price = new Intl.NumberFormat(locale, { style: 'currency', currency }).format(amount);
329
266
 
330
- ```bash
331
- pnpm fbtee collect --oxc
332
- pnpm fbtee prepare-translations --oxc --source-strings source_strings.json --output-dir translations
333
- pnpm fbtee translate --oxc --source-strings source_strings.json --translations 'translations/*.json' --output-dir src/translations
334
- pnpm fbtee migrate-locales --oxc --to bcp47 --dir translations --dir src/translations
267
+ return (
268
+ <fbt desc="Product price">
269
+ Price: <fbt:param name="price">{price}</fbt:param>
270
+ </fbt>
271
+ );
272
+ }
335
273
  ```
336
274
 
337
- The Babel pipeline remains the default during the parity rollout, so removing `--oxc` makes it easy to compare both implementations. Successful native outputs are tested byte-for-byte against Babel. The native collector intentionally cannot execute Babel-specific JavaScript extensions: `--custom-collector`, `--transform`, and `--generate-fbt-nodes` are unavailable with `--oxc`.
275
+ With scoped runtimes, use the `locale` returned by `useFbt()`. Reuse formatter instances when formatting many values with the same locale and options. For server-rendered dates and relative times, keep the locale, time zone, and reference time consistent between server and client; these settings belong to your application.
338
276
 
339
- The Oxc collector also cannot run plugins or presets from a Babel configuration file or apply `.babelignore`. If it detects Babel configuration or ignore rules, it stops instead of silently collecting different source. Use the Babel collector when that preprocessing is required, or pass `--disable-babel-config` to explicitly collect the unmodified source with Oxc. All other documented options are supported.
277
+ ### Locale Matching and Fallback
340
278
 
341
- Extract source strings:
279
+ Fallback works automatically for each message: `fr-CA` checks its Canadian French catalog, then `fr`, then the source locale's catalog if supplied, and finally the inline source text. A translated regional message always wins. An empty string counts as a translation.
342
280
 
343
- ```bash
344
- pnpm fbtee collect
345
- ```
281
+ To use a shared French catalog, include `fr` alongside `fr-CA` in `availableLanguages` and return each catalog from `loadLocale`. `preload()` and language changes load the available fallback catalogs in parallel, share pending loads, and retry failed loads. A supplied `fr` catalog does not prevent loading `fr-CA`. With `setupFbtee`, `createFbteeRuntime`, or `runWithFbtee`, supply the fallback catalogs in `translations`.
346
282
 
347
- This writes `source_strings.json`.
283
+ Matching accepts BCP 47 and legacy identifiers such as `fr-CA` and `fr_CA`. It prefers exact locales, then language/script parents. If none are available during language selection, it chooses a region with the same language and script, preferring the region inferred by `Intl.Locale` and using a stable order for ties. Catalog insertion order does not determine the selected language. Traditional Chinese falls back through `zh-Hant`; it does not automatically use a Simplified Chinese catalog. Per-message fallback does not choose arbitrary sibling regions.
348
284
 
349
- Prepare editable translation files:
285
+ Most apps need no additional settings. All setup APIs accept these optional settings:
350
286
 
351
- ```bash
352
- pnpm fbtee prepare-translations --source-strings source_strings.json --output-dir translations --locales de-DE fr-FR ja-JP
287
+ ```tsx
288
+ const runtime = createFbteeRuntime({
289
+ locale: 'fr-CA',
290
+ translations,
291
+ sourceLocale: 'en-US', // Default; change this for non-English inline messages.
292
+ fallbackLocales: {
293
+ 'fr-CA': ['fr-FR'], // After natural parents, before the source locale.
294
+ default: ['en-GB'], // Additional fallbacks for every locale.
295
+ },
296
+ onMissingTranslation: ({ locale, hashKey, sourceLocale }) => {
297
+ reportMissingTranslation({ locale, hashKey, sourceLocale });
298
+ },
299
+ });
353
300
  ```
354
301
 
355
- `prepare-translations` merges source strings into existing locale files, preserves translated entries, and marks new work with `"status": "new"`. New files use BCP 47 locale identifiers by default, such as `de-DE.json` and `es-419.json`.
302
+ `fallbackLocales` also accepts an array, such as `['fr', 'en-GB']`, for every locale. Mappings can refer to other mapped locales; cycles and duplicate catalogs are ignored. Plural selection and phonological rewrites follow the language of the catalog supplying the message, or `sourceLocale` for inline source text; interpolated numbers retain the viewer's number formatting.
356
303
 
357
- Compile translations for the app:
304
+ `onMissingTranslation` runs only when no catalog in the chain contains the message and inline source text is used. It reports each locale/message once per runtime, resets when translations are registered or merged, and does not report normal source-locale rendering. No generic missing-catalog warning is emitted. Custom `hooks.getTranslatedInput` implementations remain responsible for their own fallback and reporting. They can return `locale` alongside `args` and `table` to select the message's phonological rewrites; omitting it preserves the viewer locale.
358
305
 
359
- ```bash
360
- pnpm fbtee translate --source-strings source_strings.json --translations 'translations/*.json' --output-dir src/translations
361
- ```
306
+ For locale contexts, `fallbackLocale` controls the selected language when none of `clientLocales` is supported. It defaults to `sourceLocale`. Use `fallbackLocales` to configure per-message fallback, and set `sourceLocale` when the inline messages are not US English.
362
307
 
363
- fbtee accepts both modern BCP 47 locale identifiers (`de-DE`, `es-419`) and legacy Facebook-style identifiers (`de_DE`, `es_LA`). If both aliasing files exist, for example `translations/de_DE.json` and `translations/de-DE.json`, fbtee throws and asks you to keep one. Existing legacy files are updated in place; new generated files default to BCP 47. To force a specific output style, pass `--output-locale-style=bcp47`, `--output-locale-style=legacy`, or `--output-locale-style=preserve`.
308
+ ### Scoped Runtimes
364
309
 
365
- To migrate editable translation files and generated runtime files to BCP 47 names:
310
+ Use `LocaleProvider` when part of a page needs its own language. Create a runtime with its translations, then call `useFbt()` in the components that translate:
366
311
 
367
- ```bash
368
- pnpm fbtee migrate-locales --to bcp47 --dir translations --dir src/translations
369
- pnpm fbtee translate --output-locale-style=bcp47
370
- ```
312
+ ```tsx
313
+ import { createFbteeRuntime, LocaleProvider, useFbt } from 'fbtee';
314
+ import german from './translations/de-DE.json' with { type: 'json' };
371
315
 
372
- Use `--dry-run` first to preview file renames.
316
+ const germanRuntime = createFbteeRuntime({ locale: 'de-DE', translations: german });
373
317
 
374
- Commit the human-authored translation files. Ignore generated runtime output:
318
+ function SaveButton() {
319
+ const { fbt } = useFbt();
320
+ return <button>{fbt('Save', 'Save button')}</button>;
321
+ }
375
322
 
376
- ```gitignore
377
- .enum_manifest.json
378
- source_strings.json
379
- src/translations/
323
+ function Preview() {
324
+ return (
325
+ <LocaleProvider runtime={germanRuntime}>
326
+ <SaveButton />
327
+ </LocaleProvider>
328
+ );
329
+ }
380
330
  ```
381
331
 
382
- ## Translating Strings with Coding Agents
332
+ Create runtimes once, outside rendering or with React state. Each translating component calls `useFbt()`, which requires a provider and also returns `fbs`, `list`, and `locale`. Nested providers and separate roots can use different runtimes. Global imports keep their existing behavior.
383
333
 
384
- Coding agents are great at updating _fbtee_ translation files because all the context is in the repository:
334
+ A runtime's locale and gender are fixed; switch the provider's runtime to change them. Async actions keep the translator they captured, even across `await`. Pass it explicitly to helpers in other modules:
335
+
336
+ ```tsx
337
+ import type { FbtAPI } from 'fbtee';
338
+
339
+ export async function saveMessage(fbt: FbtAPI) {
340
+ await saveDocument();
341
+ return fbt('Saved', 'Save confirmation');
342
+ }
343
+ ```
385
344
 
386
- 1. `fbtee prepare-translations` adds every missing translation and marks it with `"status": "new"`.
387
- 1. The agent edits only entries with `"status": "new"`.
388
- 1. The agent removes `"status": "new"` after the translation is complete.
389
- 1. Code review shows a clean diff of the new localized strings.
345
+ Keep the local names `fbt` and `fbs`. For helper arguments and imported runtimes, annotate with the imported `FbtAPI`, `FbsAPI`, or `FbteeRuntime` type so the compiler recognizes them. If a linter reports a JSX-only translator as unused, keep the binding and suppress the warning or use the function form.
390
346
 
391
- This works best when the app already has translated strings. The agent can infer tone, voice, capitalization, punctuation, and product vocabulary from the existing locale files.
347
+ Load more translations with `germanRuntime.mergeTranslations(bundle)`, using compiled bundles shaped as `{ locale: { hash: translation } }`. Mounted consumers update automatically. Load before displaying the relevant UI to avoid briefly showing source strings.
392
348
 
393
- ### Coding Agent prompt:
349
+ ### Server Rendering
394
350
 
395
- ```md
396
- Run `fbtee prepare-translations --source-strings source_strings.json --output-dir ares/translations --locales de-DE fr-FR ja-JP pl-PL ru-RU zh-CN es-ES it-IT ko-KR pt-BR uk-UA` for all the translations the app supports.
351
+ In Node.js, wrap the render in `runWithFbtee` to give each request its own locale. Imported `fbt`, `fbs`, and `list` calls use that scope, including across asynchronous work:
397
352
 
398
- Look at all updated translation files. For every entry with `"status": "new"`, write a translation that matches the tone, voice, and language already used in the app and in the current locale.
353
+ ```tsx
354
+ import { runWithFbtee } from 'fbtee/server';
355
+ import { renderToString } from 'react-dom/server';
356
+ import App from './App.tsx';
357
+ import german from './translations/de-DE.json' with { type: 'json' };
399
358
 
400
- Remove `"status": "new"` from each completed translation entry.
359
+ export function renderPage() {
360
+ return runWithFbtee({ locale: 'de-DE', translations: german }, () => renderToString(<App />));
361
+ }
401
362
  ```
402
363
 
403
- ## ESLint
364
+ Start the actual renderer inside the callback, including for streaming SSR. Wrapping a component's JSX return does not scope its descendants. Load translations before rendering, and use the same locale and translations for client hydration.
365
+
366
+ Each request has its own hooks and caches. Calls to `setupFbtee` and `FbtTranslations` inside the callback affect only that scope; nested scopes start with their own configuration. Promises, timers, and Suspense retries created inside it retain the scope. Use Node's `AsyncLocalStorage.bind()` for callbacks handed to an external scheduler.
367
+
368
+ The Next.js plugin handles compilation, not request isolation. Use `runWithFbtee` where you control the renderer or in Node route handlers. Server Components can use explicit runtimes; `LocaleProvider` and `useFbt()` belong in Client Components. Pass locale and translation data across that boundary, since runtime objects contain functions. Server-rendered translations need a new server render when the language changes.
369
+
370
+ ### Shared Translations
371
+
372
+ Scoped runtimes and requests share catalogs by reference. Treat them as read-only: use `runtime.mergeTranslations()` or, inside a request, `FbtTranslations.mergeTranslations()` and `registerTranslations()` for updates. Merges copy affected locale maps while sharing nested tables. Scoped catalogs are frozen automatically in development and tests; production registration does not freeze or copy them. Ordinary singleton catalogs remain mutable.
373
+
374
+ ## Linting
404
375
 
405
- Install the optional ESLint plugin:
376
+ The optional ESLint plugin checks strings and translator descriptions:
406
377
 
407
378
  ```bash
408
379
  npm install -D @nkzw/eslint-plugin-fbtee
409
380
  ```
410
381
 
411
- Use the recommended config:
412
-
413
382
  ```js
414
383
  import fbtee from '@nkzw/eslint-plugin-fbtee';
415
384
 
416
- export default [fbtee.configs.recommended];
385
+ export default [
386
+ {
387
+ plugins: { '@nkzw/fbtee': fbtee },
388
+ rules: fbtee.configs.recommended.rules,
389
+ },
390
+ ];
417
391
  ```
418
392
 
419
- Use the strict config if you want every user-facing string to be wrapped:
393
+ Use `fbtee.configs.strict.rules` to also check for untranslated text.
420
394
 
421
- ```js
422
- import fbtee from '@nkzw/eslint-plugin-fbtee';
395
+ ## Migration from fbtee 4
423
396
 
424
- export default [fbtee.configs.strict];
397
+ fbtee 5 combines `@nkzw/fbtee-cli` and `@nkzw/oxc-transform-fbtee` into `@nkzw/fbtee-compiler`:
398
+
399
+ ```sh
400
+ npm uninstall @nkzw/fbtee-cli @nkzw/oxc-transform-fbtee
401
+ npm install fbtee@^5
402
+ npm install -D @nkzw/fbtee-compiler@^5
425
403
  ```
426
404
 
405
+ Update any installed fbtee Vite, Next.js, and ESLint plugins to v5. For custom compiler integrations, replace imports from `@nkzw/oxc-transform-fbtee` with `@nkzw/fbtee-compiler`. The compiler API and existing `fbtee` commands are unchanged. Runtime imports continue to use `fbtee`.
406
+
427
407
  ## Migration from fbt
428
408
 
429
409
  _fbtee_ is compatible with the core `fbt` programming model:
@@ -436,14 +416,14 @@ _fbtee_ is compatible with the core `fbt` programming model:
436
416
 
437
417
  Some archived `fbt` options and legacy behaviors were intentionally removed. The compiler errors should point to the modern replacement when one exists.
438
418
 
439
- ## Examples
419
+ fbtee uses Oxc and a native CLI. Replace the Babel integration with the Vite or Next.js plugin above, or use `@nkzw/fbtee-compiler` directly. Babel-specific extensions (`--custom-collector`, `--transform`, `--generate-fbt-nodes`, and `--hash-module`) are no longer supported. The collector cannot execute Babel configuration or apply `.babelignore`; remove that legacy configuration, or use `--disable-babel-config` to collect unmodified source.
420
+
421
+ Both `de-DE` and legacy locale names such as `de_DE` work. To rename catalogs, run `npx fbtee migrate-locales --to bcp47 --dir translations --dir src/translations --dry-run`, then repeat without `--dry-run`. Keep one file per locale; new files use BCP 47 names by default.
440
422
 
441
- - [Example App](https://github.com/nkzw-tech/fbtee/tree/main/example)
442
- - [Next.js App Router Example](https://github.com/cpojer/nextjs-fbtee-example)
443
- - [Athena Crisis](https://github.com/nkzw-tech/athena-crisis)
423
+ ## Examples and Credits
444
424
 
445
- ## Credits
425
+ - [Example app](https://github.com/nkzw-tech/fbtee/tree/main/example), including independent locale widgets.
426
+ - [Next.js example](https://github.com/cpojer/nextjs-fbtee-example).
427
+ - [Athena Crisis](https://github.com/nkzw-tech/athena-crisis).
446
428
 
447
- - `fbt` was originally created by [Facebook](https://github.com/facebook/fbt).
448
- - The auto-import plugin was created by [@alexandernanberg](https://github.com/alexandernanberg).
449
- - [Nakazawa Tech](https://nkzw.tech) rewrote `fbt` into _fbtee_ and maintains this project.
429
+ Originally created as [`fbt` at Facebook](https://github.com/facebook/fbt), with auto-import support by [@alexandernanberg](https://github.com/alexandernanberg). Rebuilt and maintained as fbtee by [Nakazawa Tech](https://nkzw.tech).
package/lib/index.mjs CHANGED
@@ -1,7 +1,7 @@
1
1
  import { ESLintUtils } from "@typescript-eslint/utils";
2
2
  //#region package.json
3
3
  var name = "@nkzw/eslint-plugin-fbtee";
4
- var version = "3.3.0";
4
+ var version = "5.0.0";
5
5
  //#endregion
6
6
  //#region src/utils.tsx
7
7
  const createRule = ESLintUtils.RuleCreator((name) => `https://github.com/nkzw-tech/fbtee/blob/main/packages/eslint-plugin-fbtee/docs/rules/${name}.md`);
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@nkzw/eslint-plugin-fbtee",
3
- "version": "3.3.0",
3
+ "version": "5.0.0",
4
4
  "description": "",
5
5
  "keywords": [],
6
6
  "license": "MIT",