@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.
- package/README.md +219 -221
- package/lib/index.mjs +1 -1
- 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
|
-
|
|
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
|
-
|
|
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
|
-
|
|
33
|
+
The toolchain requires Node 22.12+. React apps require React 19+.
|
|
38
34
|
|
|
39
|
-
|
|
35
|
+
### Vite
|
|
40
36
|
|
|
41
37
|
```bash
|
|
42
|
-
npm install -D @nkzw/vite-plugin-fbtee
|
|
38
|
+
npm install -D @nkzw/vite-plugin-fbtee
|
|
43
39
|
```
|
|
44
40
|
|
|
45
|
-
|
|
41
|
+
Place fbtee before the React plugin:
|
|
46
42
|
|
|
47
|
-
```
|
|
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
|
|
56
|
+
npm install -D @nkzw/next-plugin-fbtee
|
|
71
57
|
```
|
|
72
58
|
|
|
73
|
-
|
|
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
|
|
65
|
+
The plugin supports Turbopack and Webpack.
|
|
89
66
|
|
|
90
|
-
|
|
67
|
+
<details>
|
|
68
|
+
<summary>Custom builds with Oxc</summary>
|
|
91
69
|
|
|
92
|
-
|
|
70
|
+
Run the fbtee transform before lowering TypeScript and JSX:
|
|
93
71
|
|
|
94
72
|
```bash
|
|
95
|
-
npm install -D @nkzw/
|
|
73
|
+
npm install -D @nkzw/fbtee-compiler oxc-transform
|
|
96
74
|
```
|
|
97
75
|
|
|
98
|
-
|
|
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
|
-
|
|
105
|
-
|
|
106
|
-
|
|
107
|
-
|
|
108
|
-
|
|
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
|
-
|
|
112
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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
|
|
109
|
+
<fbt desc="Empty state when a project has no tasks">No tasks yet</fbt>
|
|
134
110
|
```
|
|
135
111
|
|
|
136
|
-
Use `fbt()`
|
|
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
|
-
|
|
117
|
+
const title = fbt('No tasks yet', 'Empty state when a project has no tasks');
|
|
145
118
|
|
|
146
|
-
|
|
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
|
-
|
|
122
|
+
### Dynamic Content
|
|
153
123
|
|
|
154
|
-
|
|
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
|
-
|
|
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
|
-
|
|
134
|
+
### Plurals and Lists
|
|
171
135
|
|
|
172
|
-
|
|
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="
|
|
180
|
-
|
|
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`
|
|
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="
|
|
151
|
+
Assigned to <fbt:list items={assignees} name="assignees" />.
|
|
195
152
|
</fbt>
|
|
196
153
|
```
|
|
197
154
|
|
|
198
|
-
|
|
155
|
+
fbtee also supports enums, gender, and pronouns. See the [website examples](https://fbtee.dev) for each.
|
|
199
156
|
|
|
200
|
-
|
|
201
|
-
import { list } from 'fbtee';
|
|
157
|
+
### Common Strings
|
|
202
158
|
|
|
203
|
-
|
|
204
|
-
```
|
|
159
|
+
Share a description for labels that mean the same thing throughout the app. Define them in `common_strings.json`:
|
|
205
160
|
|
|
206
|
-
|
|
161
|
+
```json
|
|
162
|
+
{
|
|
163
|
+
"Save": "Button label for saving changes"
|
|
164
|
+
}
|
|
165
|
+
```
|
|
207
166
|
|
|
208
|
-
|
|
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
|
-
|
|
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
|
-
|
|
173
|
+
## Translation Workflow
|
|
222
174
|
|
|
223
|
-
|
|
175
|
+
First, extract the strings and prepare a file for each language:
|
|
224
176
|
|
|
225
|
-
|
|
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
|
-
|
|
228
|
-
|
|
229
|
-
|
|
230
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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
|
-
|
|
192
|
+
Commit the editable files in `translations/`. Add generated files to `.gitignore`:
|
|
239
193
|
|
|
240
|
-
```
|
|
241
|
-
|
|
194
|
+
```gitignore
|
|
195
|
+
.enum_manifest.json
|
|
196
|
+
source_strings.json
|
|
197
|
+
src/translations/
|
|
242
198
|
```
|
|
243
199
|
|
|
244
|
-
|
|
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
|
-
```
|
|
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
|
-
|
|
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
|
-
|
|
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
|
-
|
|
281
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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
|
-
|
|
246
|
+
function LanguageButton() {
|
|
297
247
|
const { locale, setLocale } = useLocaleContext();
|
|
298
|
-
|
|
299
|
-
};
|
|
300
|
-
```
|
|
248
|
+
const [, startTransition] = useTransition();
|
|
301
249
|
|
|
302
|
-
|
|
250
|
+
return <button onClick={() => startTransition(() => setLocale('de-DE'))}>{locale}</button>;
|
|
251
|
+
}
|
|
252
|
+
```
|
|
303
253
|
|
|
304
|
-
|
|
254
|
+
Outside React, use `setupLocaleContext` and await `preloadLocale()`, or configure the runtime directly with `setupFbtee`.
|
|
305
255
|
|
|
306
|
-
|
|
256
|
+
### Formatting Numbers and Dates
|
|
307
257
|
|
|
308
|
-
|
|
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
|
-
|
|
260
|
+
```tsx
|
|
261
|
+
import { useLocaleContext } from 'fbtee';
|
|
311
262
|
|
|
312
|
-
|
|
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
|
-
|
|
315
|
-
|
|
316
|
-
|
|
317
|
-
|
|
318
|
-
|
|
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
|
-
|
|
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
|
-
|
|
277
|
+
### Locale Matching and Fallback
|
|
324
278
|
|
|
325
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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
|
-
|
|
334
|
-
|
|
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
|
-
`
|
|
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
|
-
|
|
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
|
-
|
|
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
|
-
|
|
308
|
+
### Scoped Runtimes
|
|
346
309
|
|
|
347
|
-
|
|
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
|
-
```
|
|
350
|
-
|
|
351
|
-
|
|
352
|
-
```
|
|
312
|
+
```tsx
|
|
313
|
+
import { createFbteeRuntime, LocaleProvider, useFbt } from 'fbtee';
|
|
314
|
+
import german from './translations/de-DE.json' with { type: 'json' };
|
|
353
315
|
|
|
354
|
-
|
|
316
|
+
const germanRuntime = createFbteeRuntime({ locale: 'de-DE', translations: german });
|
|
355
317
|
|
|
356
|
-
|
|
318
|
+
function SaveButton() {
|
|
319
|
+
const { fbt } = useFbt();
|
|
320
|
+
return <button>{fbt('Save', 'Save button')}</button>;
|
|
321
|
+
}
|
|
357
322
|
|
|
358
|
-
|
|
359
|
-
|
|
360
|
-
|
|
361
|
-
|
|
323
|
+
function Preview() {
|
|
324
|
+
return (
|
|
325
|
+
<LocaleProvider runtime={germanRuntime}>
|
|
326
|
+
<SaveButton />
|
|
327
|
+
</LocaleProvider>
|
|
328
|
+
);
|
|
329
|
+
}
|
|
362
330
|
```
|
|
363
331
|
|
|
364
|
-
|
|
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
|
-
|
|
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
|
-
|
|
369
|
-
|
|
370
|
-
|
|
371
|
-
|
|
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
|
-
|
|
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
|
-
###
|
|
349
|
+
### Server Rendering
|
|
376
350
|
|
|
377
|
-
|
|
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
|
-
|
|
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
|
-
|
|
359
|
+
export function renderPage() {
|
|
360
|
+
return runWithFbtee({ locale: 'de-DE', translations: german }, () => renderToString(<App />));
|
|
361
|
+
}
|
|
383
362
|
```
|
|
384
363
|
|
|
385
|
-
|
|
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
|
-
|
|
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 [
|
|
385
|
+
export default [
|
|
386
|
+
{
|
|
387
|
+
plugins: { '@nkzw/fbtee': fbtee },
|
|
388
|
+
rules: fbtee.configs.recommended.rules,
|
|
389
|
+
},
|
|
390
|
+
];
|
|
399
391
|
```
|
|
400
392
|
|
|
401
|
-
Use
|
|
393
|
+
Use `fbtee.configs.strict.rules` to also check for untranslated text.
|
|
402
394
|
|
|
403
|
-
|
|
404
|
-
import fbtee from '@nkzw/eslint-plugin-fbtee';
|
|
395
|
+
## Migration from fbtee 4
|
|
405
396
|
|
|
406
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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
|
+
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`);
|