@nkzw/eslint-plugin-fbtee 4.0.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 +219 -221
  2. package/lib/index.mjs +1 -1
  3. package/package.json +1 -1
package/README.md CHANGED
@@ -21,105 +21,81 @@ _fbtee_ is a modern continuation of Facebook's `fbt`, rebuilt for TypeScript, ES
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
- ### Low-level Oxc Transform
67
+ <details>
68
+ <summary>Custom builds with Oxc</summary>
91
69
 
92
- For custom build-system integrations, install the native Oxc transform:
70
+ Run the fbtee transform before lowering TypeScript and JSX:
93
71
 
94
72
  ```bash
95
- npm install -D @nkzw/oxc-transform-fbtee @nkzw/fbtee-cli oxc-transform
73
+ npm install -D @nkzw/fbtee-compiler oxc-transform
96
74
  ```
97
75
 
98
- Apply the fbtee transform before the standard Oxc transform:
99
-
100
- ```js
101
- import { transformSync as transformFbtee } from '@nkzw/oxc-transform-fbtee';
76
+ ```ts
77
+ import { transformSync as transformFbtee } from '@nkzw/fbtee-compiler';
102
78
  import { transformSync as transformOxc } from 'oxc-transform';
103
79
 
104
- const fbteeResult = transformFbtee(filename, sourceText, {
105
- fbtCommon: {
106
- Accept: 'Button label for accepting terms',
107
- },
108
- fbtEnumManifest: {},
109
- });
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
+ }
110
85
 
111
- if (fbteeResult.errors.length > 0) {
112
- throw new Error(fbteeResult.errors.map(({ message }) => message).join('\n'));
86
+ return transformOxc(filename, translated.code, {
87
+ jsx: { runtime: 'automatic' },
88
+ });
113
89
  }
114
-
115
- const result = transformOxc(filename, fbteeResult.code, {
116
- jsx: { runtime: 'automatic' },
117
- });
118
90
  ```
119
91
 
120
- ### 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.
93
+
94
+ </details>
121
95
 
122
- React TypeScript projects should include the JSX declarations once in a global type file or app entry point:
96
+ ### TypeScript
97
+
98
+ Add the JSX declarations to a global type file or your app entry point:
123
99
 
124
100
  ```tsx
125
101
  /// <reference types="fbtee/ReactTypes.d.ts" />
@@ -127,31 +103,25 @@ React TypeScript projects should include the JSX declarations once in a global t
127
103
 
128
104
  ## Writing Strings
129
105
 
130
- 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:
131
107
 
132
108
  ```tsx
133
- <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>
134
110
  ```
135
111
 
136
- 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:
137
113
 
138
114
  ```tsx
139
- import { fbt } from 'fbtee';
140
-
141
- const title = fbt('No tasks yet', 'Empty state title when a project has no tasks');
142
- ```
115
+ import { fbs, fbt } from 'fbtee';
143
116
 
144
- Use `fbs()` when you need a plain string:
117
+ const title = fbt('No tasks yet', 'Empty state when a project has no tasks');
145
118
 
146
- ```tsx
147
- import { fbs } from 'fbtee';
148
-
149
- <input placeholder={fbs('Search projects', 'Project search input placeholder')} />;
119
+ <input placeholder={fbs('Search projects', 'Project search placeholder')} />;
150
120
  ```
151
121
 
152
- ## Dynamic Content
122
+ ### Dynamic Content
153
123
 
154
- 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.
155
125
 
156
126
  ```tsx
157
127
  <fbt desc="Greeting with the viewer name">
@@ -159,253 +129,281 @@ Use `<fbt:param>` for dynamic values. Token names should describe the value, not
159
129
  </fbt>
160
130
  ```
161
131
 
162
- React elements inside `<fbt>` are automatically turned into implicit params:
163
-
164
- ```tsx
165
- <fbt desc="Greeting with a linked viewer name">
166
- Hello, <UserLink user={viewer} />!
167
- </fbt>
168
- ```
132
+ For repeated values, use `<fbt:same-param>` or `fbt.sameParam()`.
169
133
 
170
- Use `fbt.sameParam()` or `<fbt:same-param>` when the same token appears more than once.
134
+ ### Plurals and Lists
171
135
 
172
- ## Plurals
173
-
174
- 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:
175
137
 
176
138
  ```tsx
177
139
  <fbt desc="Inbox unread count">
178
140
  You have{' '}
179
- <fbt:plural count={count} many="unread messages" name="unreadCount" showCount="yes">
180
- an unread message
181
- </fbt:plural>
182
- .
141
+ <fbt:plural count={count} many="unread messages" name="count" showCount="yes">
142
+ unread message
143
+ </fbt:plural>.
183
144
  </fbt>
184
145
  ```
185
146
 
186
- `showCount` can be `yes`, `ifMany`, or `no`.
187
-
188
- ## Lists
189
-
190
- Use `<fbt:list>` for locale-aware lists:
147
+ `showCount` accepts `yes`, `ifMany`, or `no`. For lists, use `<fbt:list>` or the standalone `list()` helper:
191
148
 
192
149
  ```tsx
193
150
  <fbt desc="People assigned to a task">
194
- Assigned to <fbt:list items={assignees} name="assigneeList" />.
151
+ Assigned to <fbt:list items={assignees} name="assignees" />.
195
152
  </fbt>
196
153
  ```
197
154
 
198
- 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.
199
156
 
200
- ```tsx
201
- import { list } from 'fbtee';
157
+ ### Common Strings
202
158
 
203
- const names = list(['Alice', 'Bob', 'Charlie'], 'or', 'comma');
204
- ```
159
+ Share a description for labels that mean the same thing throughout the app. Define them in `common_strings.json`:
205
160
 
206
- ## Enums
161
+ ```json
162
+ {
163
+ "Save": "Button label for saving changes"
164
+ }
165
+ ```
207
166
 
208
- 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:
209
168
 
210
169
  ```tsx
211
- const StatusLabels = {
212
- done: 'done',
213
- open: 'open',
214
- };
215
-
216
- <fbt desc="Task status label">
217
- This task is <fbt:enum enum-range={StatusLabels} value={status} />.
218
- </fbt>;
170
+ <fbt common>Save</fbt>
219
171
  ```
220
172
 
221
- For shared enum modules, use the `$FbtEnum` suffix so the collector can resolve them.
173
+ ## Translation Workflow
222
174
 
223
- ## Pronouns and Gender
175
+ First, extract the strings and prepare a file for each language:
224
176
 
225
- 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
+ ```
226
181
 
227
- ```tsx
228
- <fbt desc="Photo sharing notification">
229
- <fbt:param name="name">{user.name}</fbt:param> shared{' '}
230
- <fbt:pronoun gender={user.gender} human type="possessive" /> photo.
231
- </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
232
186
  ```
233
187
 
234
- 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.
235
189
 
236
- ## 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.
237
191
 
238
- Common strings are reusable source strings with shared descriptions:
192
+ Commit the editable files in `translations/`. Add generated files to `.gitignore`:
239
193
 
240
- ```tsx
241
- <fbt common>Save</fbt>
194
+ ```gitignore
195
+ .enum_manifest.json
196
+ source_strings.json
197
+ src/translations/
242
198
  ```
243
199
 
244
- 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:
245
201
 
246
- ```js
247
- {
248
- fbtCommon: {
249
- Save: 'Button label for saving changes',
250
- },
251
- }
202
+ ```text
203
+ Read node_modules/fbtee/skills/fbtee-translate/SKILL.md and translate all new strings for all supported locales.
252
204
  ```
253
205
 
206
+ Review the resulting diff as you would any translation.
207
+
254
208
  ## Runtime Setup
255
209
 
256
- 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.
257
211
 
258
212
  ```tsx
259
213
  import { createLocaleContext } from 'fbtee';
260
-
261
- const availableLanguages = new Map([
262
- ['en-US', 'English'],
263
- ['de-DE', 'Deutsch'],
264
- ['ja-JP', '日本語'],
265
- ]);
266
-
267
- const loadLocale = async (locale: string) => {
268
- switch (locale) {
269
- case 'de-DE':
270
- return (await import('./translations/de-DE.json')).default['de-DE'];
271
- case 'ja-JP':
272
- return (await import('./translations/ja-JP.json')).default['ja-JP'];
273
- default:
274
- return {};
275
- }
276
- };
214
+ import { createRoot } from 'react-dom/client';
215
+ import App from './App.tsx';
277
216
 
278
217
  const LocaleContext = createLocaleContext({
279
- availableLanguages,
280
- clientLocales: [navigator.language, ...navigator.languages],
281
- 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'] : {},
282
225
  });
283
226
 
284
- export const Root = () => (
227
+ await LocaleContext.preload();
228
+
229
+ createRoot(document.getElementById('root')!).render(
285
230
  <LocaleContext>
286
231
  <App />
287
- </LocaleContext>
232
+ </LocaleContext>,
288
233
  );
289
234
  ```
290
235
 
291
- 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:
292
241
 
293
242
  ```tsx
294
243
  import { useLocaleContext } from 'fbtee';
244
+ import { useTransition } from 'react';
295
245
 
296
- const LanguageButton = () => {
246
+ function LanguageButton() {
297
247
  const { locale, setLocale } = useLocaleContext();
298
- return <button onClick={() => setLocale('de-DE')}>{locale}</button>;
299
- };
300
- ```
248
+ const [, startTransition] = useTransition();
301
249
 
302
- If you need full control, use `setupLocaleContext` or `setupFbtee` directly.
250
+ return <button onClick={() => startTransition(() => setLocale('de-DE'))}>{locale}</button>;
251
+ }
252
+ ```
303
253
 
304
- ### Next.js App Router
254
+ Outside React, use `setupLocaleContext` and await `preloadLocale()`, or configure the runtime directly with `setupFbtee`.
305
255
 
306
- 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
307
257
 
308
- 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>`:
309
259
 
310
- ## Translation Workflow
260
+ ```tsx
261
+ import { useLocaleContext } from 'fbtee';
311
262
 
312
- The CLI is a native Rust executable and uses the Oxc compiler pipeline by default:
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);
313
266
 
314
- ```bash
315
- pnpm fbtee collect
316
- pnpm fbtee prepare-translations --source-strings source_strings.json --output-dir translations
317
- pnpm fbtee translate --source-strings source_strings.json --translations 'translations/*.json' --output-dir src/translations
318
- pnpm fbtee migrate-locales --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
+ }
319
273
  ```
320
274
 
321
- fbtee 4 removes the legacy Babel compiler and JavaScript CLI. Babel-specific extensions (`--custom-collector`, `--transform`, `--generate-fbt-nodes`, and `--hash-module`) are no longer supported. The collector also cannot execute Babel configuration or apply `.babelignore`; it stops with a clear diagnostic if either is detected. Remove that legacy configuration, or pass `--disable-babel-config` to explicitly collect the unmodified source.
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.
322
276
 
323
- Extract source strings:
277
+ ### Locale Matching and Fallback
324
278
 
325
- ```bash
326
- pnpm fbtee collect
327
- ```
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.
328
280
 
329
- This writes `source_strings.json`.
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`.
330
282
 
331
- Prepare editable translation files:
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.
332
284
 
333
- ```bash
334
- pnpm fbtee prepare-translations --source-strings source_strings.json --output-dir translations --locales de-DE fr-FR ja-JP
285
+ Most apps need no additional settings. All setup APIs accept these optional settings:
286
+
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
+ });
335
300
  ```
336
301
 
337
- `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.
338
303
 
339
- 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.
340
305
 
341
- ```bash
342
- pnpm fbtee translate --source-strings source_strings.json --translations 'translations/*.json' --output-dir src/translations
343
- ```
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.
344
307
 
345
- 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
346
309
 
347
- 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:
348
311
 
349
- ```bash
350
- pnpm fbtee migrate-locales --to bcp47 --dir translations --dir src/translations
351
- pnpm fbtee translate --output-locale-style=bcp47
352
- ```
312
+ ```tsx
313
+ import { createFbteeRuntime, LocaleProvider, useFbt } from 'fbtee';
314
+ import german from './translations/de-DE.json' with { type: 'json' };
353
315
 
354
- Use `--dry-run` first to preview file renames.
316
+ const germanRuntime = createFbteeRuntime({ locale: 'de-DE', translations: german });
355
317
 
356
- 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
+ }
357
322
 
358
- ```gitignore
359
- .enum_manifest.json
360
- source_strings.json
361
- src/translations/
323
+ function Preview() {
324
+ return (
325
+ <LocaleProvider runtime={germanRuntime}>
326
+ <SaveButton />
327
+ </LocaleProvider>
328
+ );
329
+ }
362
330
  ```
363
331
 
364
- ## 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.
365
333
 
366
- 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:
367
335
 
368
- 1. `fbtee prepare-translations` adds every missing translation and marks it with `"status": "new"`.
369
- 1. The agent edits only entries with `"status": "new"`.
370
- 1. The agent removes `"status": "new"` after the translation is complete.
371
- 1. Code review shows a clean diff of the new localized strings.
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
+ ```
344
+
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.
372
346
 
373
- 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.
374
348
 
375
- ### Coding Agent prompt:
349
+ ### Server Rendering
376
350
 
377
- ```md
378
- 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:
379
352
 
380
- 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' };
381
358
 
382
- Remove `"status": "new"` from each completed translation entry.
359
+ export function renderPage() {
360
+ return runWithFbtee({ locale: 'de-DE', translations: german }, () => renderToString(<App />));
361
+ }
383
362
  ```
384
363
 
385
- ## 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
386
371
 
387
- Install the optional ESLint plugin:
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
375
+
376
+ The optional ESLint plugin checks strings and translator descriptions:
388
377
 
389
378
  ```bash
390
379
  npm install -D @nkzw/eslint-plugin-fbtee
391
380
  ```
392
381
 
393
- Use the recommended config:
394
-
395
382
  ```js
396
383
  import fbtee from '@nkzw/eslint-plugin-fbtee';
397
384
 
398
- export default [fbtee.configs.recommended];
385
+ export default [
386
+ {
387
+ plugins: { '@nkzw/fbtee': fbtee },
388
+ rules: fbtee.configs.recommended.rules,
389
+ },
390
+ ];
399
391
  ```
400
392
 
401
- 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.
402
394
 
403
- ```js
404
- import fbtee from '@nkzw/eslint-plugin-fbtee';
395
+ ## Migration from fbtee 4
405
396
 
406
- 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
407
403
  ```
408
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
+
409
407
  ## Migration from fbt
410
408
 
411
409
  _fbtee_ is compatible with the core `fbt` programming model:
@@ -418,14 +416,14 @@ _fbtee_ is compatible with the core `fbt` programming model:
418
416
 
419
417
  Some archived `fbt` options and legacy behaviors were intentionally removed. The compiler errors should point to the modern replacement when one exists.
420
418
 
421
- ## 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.
422
422
 
423
- - [Example App](https://github.com/nkzw-tech/fbtee/tree/main/example)
424
- - [Next.js App Router Example](https://github.com/cpojer/nextjs-fbtee-example)
425
- - [Athena Crisis](https://github.com/nkzw-tech/athena-crisis)
423
+ ## Examples and Credits
426
424
 
427
- ## 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).
428
428
 
429
- - `fbt` was originally created by [Facebook](https://github.com/facebook/fbt).
430
- - The auto-import plugin was created by [@alexandernanberg](https://github.com/alexandernanberg).
431
- - [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 = "4.0.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": "4.0.0",
3
+ "version": "5.0.0",
4
4
  "description": "",
5
5
  "keywords": [],
6
6
  "license": "MIT",