@nkzw/eslint-plugin-fbtee 1.2.2 → 1.2.6

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 CHANGED
@@ -2,17 +2,25 @@
2
2
 
3
3
  _**fbtee** (Far Better Translations, Extended Edition) is an internationalization framework for JavaScript & React designed to be **powerful**, **flexible**, and **intuitive**._
4
4
 
5
- ## Why `fbtee`?
5
+ fbtee features inline translations and supports dynamic content, pluralization, lists, and more. It is built on top of Facebook's `fbt` library, which has been used in production for over a decade, serving billions of users.
6
+
7
+ ```tsx
8
+ const Greeting = ({ name }) => (
9
+ <div>
10
+ <fbt desc="Greeting">
11
+ Hello, <Name name={name} />!
12
+ </fbt>
13
+ </div>
14
+ );
15
+ ```
16
+
17
+ ## Why choose **fbtee**?
6
18
 
7
19
  - **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.
8
20
  - **Proven in Production:** Built on Facebook's `fbt`, with over a decade of production usage, serving billions of users and two years of production usage in [Athena Crisis](https://athenacrisis.com).
9
21
  - **Optimized Performance with IR:** Compiles translations into an Intermediate Representation (IR) for extracting strings, then optimizes the runtime output for performance.
10
22
  - **Easy Setup:** Quick integration with tools like Babel and Vite means you can get started instantly.
11
23
 
12
- ## Status: Ready for Early Adopters
13
-
14
- This is a fork of Facebook's original `fbt` library, which has been archived. The aim of this fork is to create the best and most modern internationalization library for JavaScript & React.
15
-
16
24
  ## Getting Started
17
25
 
18
26
  Tired of setting up new projects? Check out these templates for web and React Native that come with **fbtee** pre-configured:
@@ -20,6 +28,13 @@ Tired of setting up new projects? Check out these templates for web and React Na
20
28
  - [Web App Template](https://github.com/nkzw-tech/web-app-template)
21
29
  - [Expo App Template](https://github.com/nkzw-tech/expo-app-template)
22
30
 
31
+ ### Examples
32
+
33
+ Sometimes it's easiest to learn by example and copy-paste the setup from existing projects. Here are some examples of using **fbtee**:
34
+
35
+ - [Next.js App fbtee Example](https://github.com/cpojer/nextjs-fbtee-example) - A Next.js App Router example using **fbtee** with Server Components.
36
+ - [Athena Crisis](https://github.com/nkzw-tech/athena-crisis) - an open source video game using **fbtee**
37
+
23
38
  ### Installation
24
39
 
25
40
  **fbtee** requires at least Node 22, and React 19 if you are using React.
@@ -28,14 +43,20 @@ Tired of setting up new projects? Check out these templates for web and React Na
28
43
  npm install fbtee
29
44
  ```
30
45
 
31
- In addition to `fbtee`, you need to install the Babel preset and React plugin for Vite:
46
+ In addition to `fbtee`, you need to install the Babel preset.
32
47
 
33
48
  ```bash
34
- npm install -D @nkzw/babel-preset-fbtee @vitejs/plugin-react
49
+ npm install -D @nkzw/babel-preset-fbtee
35
50
  ```
36
51
 
37
52
  ### Tooling Setup
38
53
 
54
+ If you are using Vite, install the React plugin for Vite:
55
+
56
+ ```bash
57
+ npm install -D @vitejs/plugin-react
58
+ ```
59
+
39
60
  In your `vite.config.ts`:
40
61
 
41
62
  ```ts
@@ -55,7 +76,7 @@ export default {
55
76
 
56
77
  #### Using Babel directly instead of Vite's React Plugin
57
78
 
58
- If you are not using `@vitejs/plugin-react`, for example because you are using the latest version of React Router in framework mode, you can use `vite-plugin-babel` instead:
79
+ If you are not using `@vitejs/plugin-react`, for example, because you are using the latest version of React Router in framework mode, you can use `vite-plugin-babel` instead:
59
80
 
60
81
  ```tsx
61
82
  import fbteePreset from '@nkzw/babel-preset-fbtee';
@@ -73,6 +94,16 @@ export default defineConfig({
73
94
  });
74
95
  ```
75
96
 
97
+ #### Using **fbtee** with Next.js
98
+
99
+ Create a `babel.config.js` file in the root of your Next.js project and add the **fbtee** preset:
100
+
101
+ ```tsx
102
+ export default {
103
+ presets: ['next/babel', '@nkzw/babel-preset-fbtee'],
104
+ };
105
+ ```
106
+
76
107
  #### Scripts
77
108
 
78
109
  **fbtee** uses two scripts to manage translations. These scripts help automate the process of collecting, creating, and compiling translations.
@@ -102,7 +133,7 @@ Now, run the collect command to set up the initial strings for translation:
102
133
  pnpm fbtee collect
103
134
  ```
104
135
 
105
- The files generated by these commands should be added to `.gitignore`:
136
+ Add the files generated by these commands to your `.gitignore`:
106
137
 
107
138
  ```
108
139
  .enum_manifest.json
@@ -110,14 +141,14 @@ source_strings.json
110
141
  src/translations/
111
142
  ```
112
143
 
113
- Since you won't have any translations yet, we need to create an empty translations file. Just run this command to create the empty file:
144
+ Because no translations exist yet, you can run this command to create an empty file for now:
114
145
 
115
146
  ```bash
116
147
  mkdir -p translations
117
148
  echo '{"fb-locale": "ja_JP", "translations": {}}' > translations/ja_JP.json
118
149
  ```
119
150
 
120
- If you are not using a translation provider, you can also run `fbtee prepare-translations --locale ja_JP` to generate a JSON file that can be edited directly.
151
+ If you are not using a translation provider, you can also run `fbtee prepare-translations --locales ja_JP` to generate a JSON file that can be edited directly.
121
152
 
122
153
  ### App Setup
123
154
 
@@ -148,7 +179,7 @@ const loadLocale = async (locale: string) => {
148
179
  };
149
180
 
150
181
  const LocaleContext = createLocaleContext({
151
- availableLangauges,
182
+ availableLanguages,
152
183
  clientLocales: clientLocalesWeb, // or clientLocalesRN for React Native
153
184
  loadLocale,
154
185
  });
@@ -208,6 +239,41 @@ if (getLocale() === 'en_US') {
208
239
 
209
240
  A full example of using `setupLocaleContext` to build your own `LocaleContext` abstraction can be found in the [Athena Crisis](https://github.com/nkzw-tech/athena-crisis/blob/main/hera/i18n/LocaleContext.tsx#L41) repository.
210
241
 
242
+ ### Gender Variations
243
+
244
+ `createLocaleContext` and `setupLocaleContext` also support setting the user's gender:
245
+
246
+ ```tsx
247
+ createLocaleContext({
248
+ …
249
+ gender: 'female', // 'male', 'female' or 'unknown' are supported.
250
+ });
251
+ ```
252
+
253
+ If you need to adjust the user's gender dynamically, you can use the `setGender` function provided by the `useLocaleContext` hook:
254
+
255
+ ```tsx
256
+ import { useLocaleContext } from 'fbtee';
257
+
258
+ const GenderSelector = () => {
259
+ const { gender, setGender } = useLocaleContext();
260
+
261
+ return (
262
+ <div>
263
+ <button onClick={() => setGender('male')}>
264
+ <fbt desc="Male gender">Male</fbt>
265
+ </button>
266
+ <button onClick={() => setGender('female')}>
267
+ <fbt desc="Female gender">Female</fbt>
268
+ </button>
269
+ <button onClick={() => setGender('unknown')}>
270
+ <fbt desc="Unknown gender">Unknown</fbt>
271
+ </button>
272
+ </div>
273
+ );
274
+ };
275
+ ```
276
+
211
277
  #### Full Customization
212
278
 
213
279
  Finally, if you are using something other than React, or need even more control over how **fbtee** is configured, you can use the `setupFbtee` function. This function allows you to set up **fbtee** with custom hooks and translations:
@@ -227,11 +293,19 @@ setupFbtee({
227
293
  });
228
294
  ```
229
295
 
296
+ ### Usage with Next.js
297
+
298
+ If you are using Next.js with App Router, check out the [Next.js App fbtee Example](https://github.com/cpojer/nextjs-fbtee-example). You'll need to set up a `<LocaleContext />` as a Client Component, and call `setupFbtee` in the root layout of your app for Server Component support.
299
+
300
+ When using Next.js with **fbtee**, translated strings are inserted when your Server Components are rendered. This means that changing locales on the client will not automatically update the Server Component's translated strings. To handle locale changes, you need to call a server action that updates the locale, and then re-render your app with the new locale, for example by reloading the page.
301
+
302
+ If you are exclusively using **fbtee** within Client Components, you can handle locale changes directly on the client only.
303
+
230
304
  ## Usage
231
305
 
232
306
  _If you want to learn by example, check out the [examples](https://github.com/nkzw-tech/fbtee/tree/main/example) directory. You can also check out [Athena Crisis](https://github.com/nkzw-tech/athena-crisis), a large open source video game using **fbtee**_
233
307
 
234
- With **fbtee**, all strings need to be wrapped using `<fbt>` (for React/JSX) or `fbt()` (for JavaScript). This ensures strings can be extracted and translated properly. The `desc` attribute is required and provides context for translators, helping them understand the intended meaning of the string.
308
+ With **fbtee**, all strings must be wrapped using `<fbt>` (for React/JSX) or `fbt()` (for JavaScript). This ensures strings can be extracted and translated properly. The `desc` attribute is required and provides context for translators, helping them understand the intended meaning of the string.
235
309
 
236
310
  Here are some basic examples:
237
311
 
@@ -249,7 +323,39 @@ const Greeting = () => (
249
323
  );
250
324
  ```
251
325
 
252
- `<fbt>` is a special React component that marks text for translation. The `fbtee` compiler analyzes them to extract strings, and compiles them into an Intermediate Representation (IR). It supports dynamic content through <fbt:param> or even other React components. For example, if you want to greet a specific user based on a name passed as a prop, you can use `<fbt:param>`:
326
+ `<fbt>` is a special React component that marks text for translation. The **fbtee** compiler analyzes them to extract strings, and compiles them into an Intermediate Representation (IR). Here is a more complex example of what you can do with **fbtee**:
327
+
328
+ ```tsx
329
+ <fbt desc="Feed item">
330
+ <fbt:list items={users} name="userList" />{' '}
331
+ <fbt:plural
332
+ count={users.length}
333
+ many="are"
334
+ name="number of players"
335
+ showCount="no"
336
+ >
337
+ is
338
+ </fbt:plural>{' '}
339
+ playing on
340
+ <fbt:param name="mapName">{game.mapName}</fbt:param>
341
+ </fbt>
342
+ ```
343
+
344
+ _Note: `<fbt>` is auto-imported for you by the `@nkzw/babel-preset-fbtee` plugin._
345
+
346
+ This example highlights several features of **fbtee**:
347
+
348
+ - `<fbt:list>`: Renders a list of items, automatically handling conjunctions and delimiters.
349
+ - `<fbt:plural>`: Handles singular and plural forms based on a count value.
350
+ - `<fbt:param>`: Allows you to insert dynamic content into your strings, such as variables or React components.
351
+
352
+ With the above components, you can handle complex strings that are translatable into multiple languages while maintaining clarity and context for translators. This sentence structure supports many variations, such as "Alice is playing on Forest Battle.", or "Alice, Bob and Charlie are playing on Giant Crisis".
353
+
354
+ Let's look at each of these features in detail:
355
+
356
+ ### Dynamic Content
357
+
358
+ **fbtee** supports dynamic content through <fbt:param> or by using other React components within your text. For example, if you want to greet a specific user based on a name passed as a prop, you can use `<fbt:param>`:
253
359
 
254
360
  ```tsx
255
361
  const Greeting = ({ name }) => (
@@ -273,7 +379,74 @@ const Greeting = ({ name }) => (
273
379
  );
274
380
  ```
275
381
 
276
- _Note: `<fbt>` is auto-imported for you by the `@nkzw/babel-preset-fbtee` plugin._
382
+ ### Lists
383
+
384
+ `<fbt:list>` is used to render a list of items with conjunctions and delimiters. It automatically handles the grammatical structure of the list based on the locale:
385
+
386
+ ```tsx
387
+ <fbt:list
388
+ conjunction="or"
389
+ delimiter="comma"
390
+ items={['Alice', 'Bob', <CharlieNameExampleComponent key="charlieUserID" />]}
391
+ name="userList"
392
+ /> // "Alice, Bob, or Charlie"
393
+ ```
394
+
395
+ The default conjunction is "and", and the default delimiter is "comma". You can use "or" or "none" as conjunction, and "bullet", "comma", or "semicolon" as delimiters. The `name` attribute is required to provide context for translators. **fbtee** also exports a `list` function that can be used in contexts outside of React:
396
+
397
+ ```tsx
398
+ import { list } from 'fbtee';
399
+
400
+ const userList = list(['Alice', 'Bob', 'Charlie'], 'or', 'comma');
401
+ ```
402
+
403
+ ### Singular & Plural Forms
404
+
405
+ **fbtee** supports singular and plural forms using the `<fbt:plural>` component. While in English, the plural form is often just an "s" at the end of a word, in other languages, it can be more complex. **fbtee** handles this automatically based on the count value you provide. Here's an example:
406
+
407
+ ```tsx
408
+ <fbt desc="end turn confirmation">
409
+ Do you want to play against
410
+ <fbt:plural count={bots} many="bots" name="numberOfBots" showCount="ifMany">
411
+ a bot
412
+ </fbt:plural>?
413
+ </fbt>
414
+ ```
415
+
416
+ This sentence might be translated to one of the following variations:
417
+
418
+ - "Do you want to play against 1 bot?"
419
+ - "Do you want to play against 2 bots?"
420
+
421
+ `<fbt:plural>` expects the singular phrase as children, and an item `count` as prop. You can provide the plural form using the `many` prop. The `showCount` prop can be used to control whether the count should be displayed in the string. `ifMany` only shows the count in the plural case, while `yes` shows it in all cases, and `no` hides it completely. The `name` prop is required to provide context for translators.
422
+
423
+ ### Using Pronouns
424
+
425
+ You can use `<fbt:pronoun>` to handle pronouns in your strings. This is particularly useful for gendered languages where the pronoun changes based on the subject:
426
+
427
+ ```tsx
428
+ <fbt desc="Photo sharing text">
429
+ <fbt:param name="name">{user.name}</fbt:param>
430
+ shared
431
+ <fbt:pronoun type="possessive" gender={user.pronounGender} human />
432
+ photo with you.
433
+ </fbt>
434
+ ```
435
+
436
+ ### Plain Text Usage
437
+
438
+ In some situations, you may want to use **fbtee** to represent plain text strings without any React components, for example when using alert dialogs or HTML attributes like `placeholder`. For these situations, you can use the `fbs()` function, which is a subset of `fbt` that only supports plain text.
439
+
440
+ ```tsx
441
+ import { fbs } from 'fbtee';
442
+
443
+ <input
444
+ type="text"
445
+ placeholder={fbs('Enter your name', 'Placeholder for name input')}
446
+ />;
447
+ ```
448
+
449
+ ### Extracting Strings & Translating
277
450
 
278
451
  After marking your strings for translation with `<fbt>`, run the following commands to extract, and compile translations:
279
452
 
@@ -281,9 +454,9 @@ After marking your strings for translation with `<fbt>`, run the following comma
281
454
  pnpm fbtee collect
282
455
  ```
283
456
 
284
- You can now upload the `source_strings.json` file to your translation provider. One sample for such a translation provider is [Crowdin](https://crowdin.com/).
457
+ You can now upload the `source_strings.json` file to your translation provider, for example [Crowdin](https://crowdin.com/). If you'd like to manually edit the strings, you can use the `fbtee prepare-translations --locales de_DE` to generate a JSON file that can be edited directly.
285
458
 
286
- ### Generated File Structure
459
+ #### Generated File Structure
287
460
 
288
461
  Your `source_strings.json` might look like this:
289
462
 
@@ -356,24 +529,79 @@ And German (`translations/de_DE.json`) might look like this:
356
529
  }
357
530
  ```
358
531
 
359
- Once you have the translated strings stored in a `translations/` folder as JSON files, you can run the following command to generate the translations file:
532
+ Once you have the translated strings stored in a `translations/` folder as JSON files using the locale name, for example `en_US.json` or `de_DE.json`, you can run the following command to generate the translations for use in your app:
360
533
 
361
534
  ```bash
362
535
  pnpm fbtee translate
363
536
  ```
364
537
 
365
- This will generate the `translation.json` file referred to in the setup done before. This file might look like this:
538
+ By default, **fbtee** outputs the generated translations in `src/translations`. It will create one JSON file per locale. For example, if you have `translations/en_US.json` and `translations/de_DE.json`, it will generate `src/translations/en_US.json` and `src/translations/de_DE.json`. These files are an optimized representation of your translation file, ready to be used in your app:
366
539
 
367
540
  ```json
368
541
  {
369
- "de_DE": { "2HVYhv": "Was kommt jetzt?" },
370
- "en_US": { "2HVYhv": "What's next?" }
542
+ "de_DE": {
543
+ "2HVYhv": "Was kommt jetzt?"
544
+ }
371
545
  }
372
546
  ```
373
547
 
374
- After generating the translations file, your app is ready to display translated content in other languages. The error you had until now in the `setupFbtee` with regards to the imported translations file should be resolved now.
548
+ After generating the translation files, your app is ready to display translated content in other languages. Since the `src/translations` folder is auto-generated, it should be ignored by version control and be part of your build process.
375
549
 
376
- Since the `translations.json` is an auto-generated file part of your build process, the `fbtee translate` command should be added to your build step.
550
+ ## Setting the initial locale
551
+
552
+ If you know the user's locale ahead of time, you can set it when creating the `LocaleContext` by passing the locale as the first item in the `clientLocales` array:
553
+
554
+ ```tsx
555
+ // Read the user locale from localStorage etc.
556
+ const userLocale = localStorage.getItem('locale');
557
+ const LocaleContext = createLocaleContext({
558
+ availableLanguages,
559
+ clientLocales: [userLocale, navigator.language, ...navigator.languages],
560
+ loadLocale,
561
+ });
562
+ ```
563
+
564
+ Note that if you are picking the locale ahead of time like this, you need to manually load the translations. This is because the `loadLocale` function is only called when the locale changes, and not when the context is first created. This gives you full control over your app's startup:
565
+
566
+ ```tsx
567
+ const loadLocale = async (locale: string) => {
568
+ if (locale === 'ja_JP') {
569
+ return (await import('./translations/ja_JP.json')).default.ja_JP;
570
+ }
571
+ return {};
572
+ };
573
+
574
+ const translations = {
575
+ [userLocale]: await loadLocale(userLocale),
576
+ };
577
+
578
+ const LocaleContext = createLocaleContext({
579
+ availableLanguages,
580
+ clientLocales: [userLocale, navigator.language, ...navigator.languages],
581
+ loadLocale,
582
+ translations,
583
+ });
584
+ ```
585
+
586
+ If you have to wait for data from your server to determine the user's locale, you can use the `setLocale` function provided by `useLocaleContext` to set the locale after the initial render:
587
+
588
+ ```tsx
589
+ import { useLocaleContext } from 'fbtee';
590
+
591
+ const Root = () => {
592
+ // Fetch the locale from the server.
593
+ const userLocale = useUserLocale();
594
+ const { locale, setLocale } = useLocaleContext();
595
+
596
+ useEffect(() => {
597
+ if (locale !== userLocale) {
598
+ setLocale(userLocale);
599
+ }
600
+ }, [locale, setLocale, userLocale]);
601
+
602
+ return children;
603
+ };
604
+ ```
377
605
 
378
606
  ## ESLint Plugin
379
607
 
@@ -401,7 +629,7 @@ If you want stricter enforcement of translation rules, you can use the strict co
401
629
  }
402
630
  ```
403
631
 
404
- Or, if you'd like more granular control over the rules:
632
+ Alternatively, if you'd like more granular control over the rules:
405
633
 
406
634
  ```js
407
635
  {
@@ -423,7 +651,7 @@ The open-source version of `fbt`, however, became unmaintained, difficult to set
423
651
  - **Easier Setup:** fbtee works with modern tools like Vite.
424
652
  - **Statically Typed:** The fbtee compiler ensures correct usage of fbtee, library TypeScript types are provided, and an eslint plugin helps fix common mistakes.
425
653
  - **Improved React Compatibility:** Removed React-specific hacks and added support for implicit React fragments (`<>`).
426
- - **Enhanced Features:** Fixed and exported `inltList` as a new `<fbt:list>` construct, which was not functional in the original `fbt`.
654
+ - **Enhanced Features:** Fixed and exported `intlList` as a new `<fbt:list>` construct, which was not functional in the original `fbt`.
427
655
  - **Modernized Codebase:** Rewritten using TypeScript, ES modules (ESM), eslint, and modern JavaScript standards. Removed cruft and legacy code.
428
656
  - **Updated Tooling:** Uses modern tools like pnpm, Vite, and esbuild for faster and more efficient development of **fbtee**.
429
657
 
package/lib/index.cjs CHANGED
@@ -25,7 +25,7 @@ const __typescript_eslint_utils = __toESM(require("@typescript-eslint/utils"));
25
25
 
26
26
  //#region package.json
27
27
  var name = "@nkzw/eslint-plugin-fbtee";
28
- var version = "1.2.2";
28
+ var version = "1.2.6";
29
29
  var description = "";
30
30
  var keywords = [];
31
31
  var repository = {
package/lib/index.js CHANGED
@@ -2,7 +2,7 @@ import { ESLintUtils } from "@typescript-eslint/utils";
2
2
 
3
3
  //#region package.json
4
4
  var name = "@nkzw/eslint-plugin-fbtee";
5
- var version = "1.2.2";
5
+ var version = "1.2.6";
6
6
  var description = "";
7
7
  var keywords = [];
8
8
  var repository = {
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@nkzw/eslint-plugin-fbtee",
3
- "version": "1.2.2",
3
+ "version": "1.2.6",
4
4
  "description": "",
5
5
  "keywords": [],
6
6
  "repository": {