fbtee i18n Framework

repository·main·Indexed 43 days ago

https://github.com/nkzw-tech/fbtee

A modern internationalization framework for JavaScript and React that allows developers to write translatable text inline using JSX or JS functions. It features a compilation process to optimize runtime performance and provides official plugins for Vite and Next.js, as well as integration with Oxc. The framework supports complex i18n needs including plurals, lists, dynamic parameters, and locale fallback chains.

Tokens
25.9K
Snippets
86
Records
125
Agent score
56%

What's inside fbtee

  1. Handle plurals and lists

    main

    fbtee provides specialized components for handling pluralization and lists.

    Plurals

    Use <fbt:plural> to define singular and plural forms. The count prop tracks the number, and showCount determines how the number is displayed.

    showCount accepts:

    • yes: Show the number.
    • ifMany: Show the number only if it's not 1.
    • no: Hide the number.
    <fbt desc="Inbox unread count">
      You have{' '} 
      <fbt:plural count={count} many="unread messages" name="count" showCount="yes">
        unread message
      </fbt:plural>.
    </fbt>

    Lists

    Use <fbt:list> to render a list of items.

    <fbt desc="People assigned to a task">
      Assigned to <fbt:list items={assignees} name="assignees" />.
    </fbt>
    <fbt desc="Inbox unread count">
      You have{' '} 
      <fbt:plural count={count} many="unread messages" name="count" showCount="yes">
        unread message
      </fbt:plural>.
    </fbt>
    
    <fbt desc="People assigned to a task">
      Assigned to <fbt:list items={assignees} name="assignees" />.
    </fbt>
  2. Configure custom builds with Oxc

    main

    For custom build pipelines using Oxc, run the fbtee transform before lowering TypeScript and JSX.

    Installation:

    npm install -D @nkzw/fbtee-compiler oxc-transform

    Implementation Example:

    import { transformSync as transformFbtee } from '@nkzw/fbtee-compiler';
    import { transformSync as transformOxc } from 'oxc-transform';
    
    export function compile(filename: string, source: string) {
      const translated = transformFbsea(filename, source);
      if (translated.errors.length) {
        throw new Error(translated.errors.map(({ message }) => message).join('\n'));
      }
    
      return transformOxc(filename, translated.code, {
        jsx: { runtime: 'automatic' },
      });
    }

    The returned Oxc result contains the generated code and any downstream errors. You can configure common strings and enums via the transform options.

    import { transformSync as transformFbtee } from '@nkzw/fbtee-compiler';
    import { transformSync as transformOxc } from 'oxc-transform';
    
    export function compile(filename: string, source: string) {
      const translated = transformFbtee(filename, source);
      if (translated.errors.length) {
        throw new Error(translated.errors.map(({ message }) => message).join('\n'));
      }
    
      return transformOxc(filename, translated.code, {
        jsx: { runtime: 'automatic' },
      });
    }
  3. Migrate from fbtee 4 to 5

    main

    Version 5 consolidates the CLI and compiler. To migrate:

    1. Uninstall old packages: npm uninstall @nkzw/fbtee-cli @nkzw/oxc-transform-fbtee
    2. Install new packages: npm install fbtee@^5 and npm install -D @nkzw/fbtee-compiler@^5
    3. Update all plugins (Vite, Next.js, ESLint) to v5.
    4. For custom compiler integrations, replace imports from @nkzw/oxc-transform-fbtee with @nkzw/fbtee-compiler.
    npm uninstall @nkzw/fbtee-cli @nkzw/oxc-transform-fbtee
    npm install fbtee@^5
    npm install -D @nkzw/fbtee-compiler@^5
  4. Format numbers and dates with fbtee locales

    main

    When formatting numbers, currencies, or dates, use the native JavaScript Intl APIs. To ensure the formatting matches the user's current language, pass the locale obtained from useLocaleContext() to the formatter. Use <fbt:param> to insert the formatted value into your translation strings.

    import { useLocaleContext } from 'fbtee';
    
    function Price({ amount, currency }: { amount: number; currency: string }) {
      const { locale } = useLocaleContext();
      const price = new Intl.NumberFormat(locale, { style: 'currency', currency }).format(amount);
    
      return (
        <fbt desc="Product price">
          Price: <fbt:param name="price">{price}</fbt:param>
        </fbt>
      );
    }
  5. Set up a React app with createLocaleContext

    main

    For applications with a single active language, use createLocaleContext to manage locales based on browser preferences.

    1. Define availableLanguages as a Map of BCP 47 locales to display names.
    2. Provide clientLocales (e.g., navigator.languages) to allow the context to match browser settings.
    3. Implement loadLocale as an async function that fetches the translation JSON for a given locale.
    4. Crucial: Call await LocaleContext.preload() once before rendering your application to load the initial language and fallback catalogs. Creating the context does not trigger the load automatically.
    5. Wrap your application root in the <LocaleContext> provider.
    import { createLocaleContext } from 'fbtee';
    import { createRoot } from 'react-dom/client';
    import App from './App.tsx';
    
    const LocaleContext = createLocaleContext({
      availableLanguages: new Map([
        ['en-US', 'English'],
        ['de-DE', 'Deutsch'],
      ]),
      clientLocales: navigator.languages,
      loadLocale: async (locale) =>
        locale === 'de-DE' ? (await import('./translations/de-DE.json')).default['de-DE'] : {},
    });
    
    await LocaleContext.preload();
    
    createRoot(document.getElementById('root')!).render(
      <LocaleContext>
        <App />
      </LocaleContext>,
    );
  6. Configure fbtee with Vite

    main

    To integrate fbtee into a Vite project, install the Vite plugin and add it to your vite.config.ts.

    Important: You must place the fbtee() plugin before the React plugin in the plugins array.

    Installation:

    npm install -D @nkzw/vite-plugin-fbtee

    Configuration:

    import fbtee from '@nkzw/vite-plugin-fbtee';
    import react from '@vitejs/plugin-react';
    import { defineConfig } from 'vite';
    
    export default defineConfig({
      plugins: [fbtee(), react()],
    });
  7. Handle dynamic content with `<fbt:param>`

    main

    To include dynamic values within a translatable sentence, use the <fbt:param> component. This allows you to name the dynamic value so translators can place it correctly in different languages.

    <fbt desc="Greeting with the viewer name">
      Hello, <fbt:param name="viewerName">{viewer.name}</fbt:param>!
    </fbt>

    For repeated values, use <fbt:same-param> or the fbt.sameParam() helper.

  8. Configure fbtee with Next.js

    main

    To integrate fbtee into a Next.js project, install the Next.js plugin and wrap your configuration with withFbtee. This plugin supports both Turbopack and Webpack.

    Installation:

    npm install -D @nkzw/next-plugin-fbtee

    Configuration:

    import withFbtee from '@nkzw/next-plugin-fbtee';
    
    export default withFbtee()({});
  9. The fbtee translation workflow

    main

    The translation process follows a three-step cycle: extraction, translation, and compilation.

    1. Extract Strings

    First, collect all strings from your source code and prepare translation files for your target locales.

    npx fbtee collect
    npx fbtee prepare-translations --source-strings source_strings.json --output-dir translations --locales de-DE ja-JP

    2. Translate

    Translate the entries in the generated files. Entries marked with "status": "new" should be translated, then the status should be removed.

    To compile the translations into your app's runtime format:

    npx fbtee translate --source-strings source_strings.json --translations 'translations/*.json' --output-dir src/translations

    Strict Mode: Use --strict to ensure every collected source hash has a completed entry in every locale. This will fail if entries are missing, marked as "status": "new", or have empty translation lists.

    3. Commit and Ignore

    Commit your editable translation files in translations/. Add the following generated files to your .gitignore:

    .enum_manifest.json
    source_strings.json
    src/translations/

    Note on Runtime Catalogs: Generated catalogs omit messages that lack a completed translation (including those marked "status": "new"). This allows partial catalogs to fall back to other languages.

    npx fbtee collect
    npx fbtee prepare-translations --source-strings source_strings.json --output-dir translations --locales de-DE ja-JP
    
    npx fbtee translate --source-strings source_strings.json --translations 'translations/*.json' --output-dir src/translations
  10. Configure TypeScript JSX declarations for fbtee

    main

    To ensure TypeScript recognizes the global fbt JSX component, add the following reference to a global type file or your application's entry point:

    /// <reference types="fbtee/ReactTypes.d.ts" />
  11. Use Scoped Runtimes with LocaleProvider

    main

    If a specific part of your application needs its own language (independent of the global context), use LocaleProvider.

    1. Create a runtime using createFbteeRuntime outside of the render cycle.
    2. Wrap the specific component tree in <LocaleProvider runtime={yourRuntime} />.
    3. Use the useFbt() hook inside components within that provider to access the scoped translator.

    Note: A runtime's locale and gender are fixed. To change them, you must switch the provider's runtime.

    import { createFbteeRuntime, LocaleProvider, useFbt } from 'fbtee';
    import german from './translations/de-DE.json' with { type: 'json' };
    
    const germanRuntime = createFbteeRuntime({ locale: 'de-DE', translations: german });
    
    function SaveButton() {
      const { fbt } = useFbt();
      return <button>{fbt('Save', 'Save button')}</button>;
    }
    
    function Preview() {
      return (
        <LocaleProvider runtime={germanRuntime}>
          <SaveButton />
        </LocaleProvider>
      );
    }