@nkzw/vite-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.
- package/README.md +220 -240
- package/index.d.ts +1 -1
- package/index.js +1 -1
- package/package.json +2 -2
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,
|
|
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,
|
|
20
|
+
- **Easy Setup:** Quick integration with Vite, Next.js, Oxc, and Expo.
|
|
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
|
-
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
|
-
|
|
121
|
-
|
|
122
|
-
|
|
123
|
-
|
|
124
|
-
|
|
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
|
-
|
|
128
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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
|
|
109
|
+
<fbt desc="Empty state when a project has no tasks">No tasks yet</fbt>
|
|
150
110
|
```
|
|
151
111
|
|
|
152
|
-
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:
|
|
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
|
|
158
|
-
```
|
|
117
|
+
const title = fbt('No tasks yet', 'Empty state when a project has no tasks');
|
|
159
118
|
|
|
160
|
-
|
|
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
|
-
|
|
122
|
+
### Dynamic Content
|
|
169
123
|
|
|
170
|
-
|
|
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
|
-
|
|
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
|
-
|
|
134
|
+
### Plurals and Lists
|
|
189
135
|
|
|
190
|
-
|
|
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="
|
|
196
|
-
|
|
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`
|
|
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="
|
|
151
|
+
Assigned to <fbt:list items={assignees} name="assignees" />.
|
|
211
152
|
</fbt>
|
|
212
153
|
```
|
|
213
154
|
|
|
214
|
-
|
|
155
|
+
fbtee also supports enums, gender, and pronouns. See the [website examples](https://fbtee.dev) for each.
|
|
215
156
|
|
|
216
|
-
|
|
217
|
-
import { list } from 'fbtee';
|
|
157
|
+
### Common Strings
|
|
218
158
|
|
|
219
|
-
|
|
220
|
-
```
|
|
159
|
+
Share a description for labels that mean the same thing throughout the app. Define them in `common_strings.json`:
|
|
221
160
|
|
|
222
|
-
|
|
161
|
+
```json
|
|
162
|
+
{
|
|
163
|
+
"Save": "Button label for saving changes"
|
|
164
|
+
}
|
|
165
|
+
```
|
|
223
166
|
|
|
224
|
-
|
|
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
|
-
|
|
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
|
-
|
|
173
|
+
## Translation Workflow
|
|
238
174
|
|
|
239
|
-
|
|
175
|
+
First, extract the strings and prepare a file for each language:
|
|
240
176
|
|
|
241
|
-
|
|
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
|
-
|
|
244
|
-
|
|
245
|
-
|
|
246
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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
|
-
|
|
192
|
+
Commit the editable files in `translations/`. Add generated files to `.gitignore`:
|
|
255
193
|
|
|
256
|
-
```
|
|
257
|
-
|
|
194
|
+
```gitignore
|
|
195
|
+
.enum_manifest.json
|
|
196
|
+
source_strings.json
|
|
197
|
+
src/translations/
|
|
258
198
|
```
|
|
259
199
|
|
|
260
|
-
|
|
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
|
-
```
|
|
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
|
-
|
|
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
|
-
|
|
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
|
-
|
|
297
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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
|
-
|
|
246
|
+
function LanguageButton() {
|
|
313
247
|
const { locale, setLocale } = useLocaleContext();
|
|
314
|
-
|
|
315
|
-
};
|
|
316
|
-
```
|
|
248
|
+
const [, startTransition] = useTransition();
|
|
317
249
|
|
|
318
|
-
|
|
250
|
+
return <button onClick={() => startTransition(() => setLocale('de-DE'))}>{locale}</button>;
|
|
251
|
+
}
|
|
252
|
+
```
|
|
319
253
|
|
|
320
|
-
|
|
254
|
+
Outside React, use `setupLocaleContext` and await `preloadLocale()`, or configure the runtime directly with `setupFbtee`.
|
|
321
255
|
|
|
322
|
-
|
|
256
|
+
### Formatting Numbers and Dates
|
|
323
257
|
|
|
324
|
-
|
|
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
|
-
|
|
260
|
+
```tsx
|
|
261
|
+
import { useLocaleContext } from 'fbtee';
|
|
327
262
|
|
|
328
|
-
|
|
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
|
-
|
|
331
|
-
|
|
332
|
-
|
|
333
|
-
|
|
334
|
-
|
|
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
|
-
|
|
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
|
-
|
|
277
|
+
### Locale Matching and Fallback
|
|
340
278
|
|
|
341
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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
|
-
|
|
285
|
+
Most apps need no additional settings. All setup APIs accept these optional settings:
|
|
350
286
|
|
|
351
|
-
```
|
|
352
|
-
|
|
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
|
-
`
|
|
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
|
-
|
|
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
|
-
|
|
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
|
-
|
|
308
|
+
### Scoped Runtimes
|
|
364
309
|
|
|
365
|
-
|
|
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
|
-
```
|
|
368
|
-
|
|
369
|
-
|
|
370
|
-
```
|
|
312
|
+
```tsx
|
|
313
|
+
import { createFbteeRuntime, LocaleProvider, useFbt } from 'fbtee';
|
|
314
|
+
import german from './translations/de-DE.json' with { type: 'json' };
|
|
371
315
|
|
|
372
|
-
|
|
316
|
+
const germanRuntime = createFbteeRuntime({ locale: 'de-DE', translations: german });
|
|
373
317
|
|
|
374
|
-
|
|
318
|
+
function SaveButton() {
|
|
319
|
+
const { fbt } = useFbt();
|
|
320
|
+
return <button>{fbt('Save', 'Save button')}</button>;
|
|
321
|
+
}
|
|
375
322
|
|
|
376
|
-
|
|
377
|
-
|
|
378
|
-
|
|
379
|
-
|
|
323
|
+
function Preview() {
|
|
324
|
+
return (
|
|
325
|
+
<LocaleProvider runtime={germanRuntime}>
|
|
326
|
+
<SaveButton />
|
|
327
|
+
</LocaleProvider>
|
|
328
|
+
);
|
|
329
|
+
}
|
|
380
330
|
```
|
|
381
331
|
|
|
382
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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
|
-
###
|
|
349
|
+
### Server Rendering
|
|
394
350
|
|
|
395
|
-
|
|
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
|
-
|
|
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
|
-
|
|
359
|
+
export function renderPage() {
|
|
360
|
+
return runWithFbtee({ locale: 'de-DE', translations: german }, () => renderToString(<App />));
|
|
361
|
+
}
|
|
401
362
|
```
|
|
402
363
|
|
|
403
|
-
|
|
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
|
-
|
|
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 [
|
|
385
|
+
export default [
|
|
386
|
+
{
|
|
387
|
+
plugins: { '@nkzw/fbtee': fbtee },
|
|
388
|
+
rules: fbtee.configs.recommended.rules,
|
|
389
|
+
},
|
|
390
|
+
];
|
|
417
391
|
```
|
|
418
392
|
|
|
419
|
-
Use
|
|
393
|
+
Use `fbtee.configs.strict.rules` to also check for untranslated text.
|
|
420
394
|
|
|
421
|
-
|
|
422
|
-
import fbtee from '@nkzw/eslint-plugin-fbtee';
|
|
395
|
+
## Migration from fbtee 4
|
|
423
396
|
|
|
424
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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/index.d.ts
CHANGED
package/index.js
CHANGED
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@nkzw/vite-plugin-fbtee",
|
|
3
|
-
"version": "
|
|
3
|
+
"version": "5.0.0",
|
|
4
4
|
"description": "Vite plugin for fbtee powered by the native Oxc transform.",
|
|
5
5
|
"keywords": [
|
|
6
6
|
"fbt",
|
|
@@ -33,7 +33,7 @@
|
|
|
33
33
|
}
|
|
34
34
|
},
|
|
35
35
|
"dependencies": {
|
|
36
|
-
"@nkzw/
|
|
36
|
+
"@nkzw/fbtee-compiler": "5.0.0"
|
|
37
37
|
},
|
|
38
38
|
"devDependencies": {
|
|
39
39
|
"vite": "^8.1.5"
|