Package Exports
- fbtee
- fbtee/lib/index.js
This package does not declare an exports field, so the exports above have been automatically detected and optimized by JSPM instead. If any package subpath is missing, it is recommended to post an issue to the original package (fbtee) to support the "exports" field. If that is not possible, create a JSPM override to customize the exports field for this package.
Readme
fbtee
fbtee (Far Better Translations, Extended Edition) is an internationalization framework for JavaScript & React designed to be powerful, flexible, and intuitive.
Why fbtee?
- 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. - 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. - Optimized Performance with IR: Compiles translations into an Intermediate Representation (IR) for extracting strings, then optimizes the runtime output for performance.
- Easy Setup: Quick integration with tools like Babel and Vite means you can get started instantly.
Status: Ready for Early Adopters
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.
Getting Started
Tired of setting up new projects? Check out these templates for web and React Native that come with fbtee pre-configured:
Installation
fbtee requires at least Node 22, and React 19 if you are using React.
npm install fbteeIn addition to fbtee, you need to install the Babel preset and React plugin for Vite:
npm install -D @nkzw/babel-preset-fbtee @vitejs/plugin-reactTooling Setup
In your vite.config.ts:
import fbteePreset from '@nkzw/babel-preset-fbtee';
import react from '@vitejs/plugin-react';
export default {
plugins: [
react({
babel: {
presets: [fbteePreset],
},
}),
],
};Using Babel directly instead of Vite's React Plugin
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:
import fbteePreset from '@nkzw/babel-preset-fbtee';
import { reactRouter } from '@react-router/dev/vite';
import { defineConfig } from 'vite';
import babel from 'vite-plugin-babel';
export default defineConfig({
plugins: [
babel({
babelConfig: { presets: [fbteePreset] },
}),
reactRouter(),
],
});Scripts
fbtee uses two scripts to manage translations. These scripts help automate the process of collecting, creating, and compiling translations.
fbtee collect: This command reads your source files and extracts all the translatable strings into asource_strings.jsonfile. This file should be uploaded to your translation provider as the source for your translations.fbtee translate: This command takes the collected strings and your translations, and compiles them into an optimized format for use in your application.
By default, fbtee expects your source code to be anywhere within your project, your translations in a translations folder, and the generated translations in src/translations. You can customize these paths using command line arguments:
- The
--translationsparameter can be specified tofbtee translateto customize the path to the input translation files. --output-dircan be specified to define where the output translations should be written so they can be loaded in your app. You can also use--outparameter to output a single file such astranslations.json, which can be used directly in your app without loading individual translation files.
If you want to use different paths, it is recommended to define custom commands in your package.json:
{
"scripts": {
"fbtee:collect": "fbtee collect --src src",
"fbtee:translate": "fbtee translate --translations translations/*.json -o src/translations/"
}
}Now, run the collect command to set up the initial strings for translation:
pnpm fbtee collectThe files generated by these commands should be added to .gitignore:
.enum_manifest.json
source_strings.json
src/translations/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:
mkdir -p translations
echo '{"fb-locale": "ja_JP", "translations": {}}' > translations/ja_JP.jsonIf 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.
App Setup
fbtee's runtime can manage the currently selected locale for you through the <LocaleContext /> component. All you need to do is define the available languages, the locales provided by the browser or device, and a function to load translations for a given locale:
import { getLocales } from 'expo-localization';
import { createLocaleContext } from 'fbtee';
// Define the available languages in your app:
const availableLanguages = new Map([
['en_US', 'English'],
['ja_JP', '日本語 (Japanese)'],
]);
// Web:
const clientLocalesWeb = [navigator.language, ...navigator.languages];
// React Native:
const clientLocalesRN = getLocales().map(({ languageTag }) => languageTag);
// A loader function to fetch translations for a given locale:
const loadLocale = async (locale: string) => {
if (locale === 'ja_JP') {
return (await import('./translations/ja_JP.json')).default.ja_JP;
}
return {};
};
const LocaleContext = createLocaleContext({
availableLangauges,
clientLocales: clientLocalesWeb, // or clientLocalesRN for React Native
loadLocale,
});
// Now wrap your app with `LocaleContext`:
const MyAppEntryPoint = () => (
<LocaleContext>
<App />
</LocaleContext>
);If you need to access the current locale or set the locale, you can use the useLocaleContext hook:
import { useLocaleContext } from 'fbtee';
const MyComponent = () => {
const { locale, setLocale } = useLocaleContext();
return (
<div>
<p>Current Locale: {locale}</p>
<button onClick={() => setLocale('ja_JP')}>Switch to Japanese</button>
</div>
);
};Next up, if you are using React and TypeScript in your project, you need to add TypeScript types for fbtee to enable proper type checking in JSX. You can do this by referencing the ReactTypes.d.ts file in your main index.tsx file or a global type declaration file (e.g., types.d.ts):
/// <reference types="fbtee/ReactTypes.d.ts" />You’re now ready to define your first translatable element!
Alternative Setup Methods
If you need to build your own locale context with custom functionality, you can use the setupLocaleContext function. It takes the same arguments as the LocaleContext component, but gives you full control over how you manage locales.
const { getLocale, setLocale } = setupLocaleContext({
availableLanguages: AvailableLanguages,
clientLocales,
fallbackLocale,
hooks,
loadLocale,
translations,
});
// Now you can call `getLocale` and `setLocale` anytime, even outside of React components.
if (getLocale() === 'en_US') {
setLocale('ja_JP'); // Switch to Japanese locale
}A full example of using setupLocaleContext to build your own LocaleContext abstraction can be found in the Athena Crisis repository.
Full Customization
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:
import { IntlVariations, setupFbtee } from 'fbtee';
import translations from './translations.json';
setupFbtee({
hooks: {
getViewerContext: () => ({
GENDER: IntlVariations.GENDER_UNKNOWN,
locale: 'en_US',
}),
},
translations,
});Usage
If you want to learn by example, check out the examples directory. You can also check out Athena Crisis, a large open source video game using fbtee
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.
Here are some basic examples:
const Greeting = () => <div>Hello, World!</div>;You can wrap the string with <fbt>:
const Greeting = () => (
<div>
<fbt desc="Greeting">Hello, World!</fbt>
</div>
);<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>:
const Greeting = ({ name }) => (
<div>
<fbt desc="Greeting">
Hello, <fbt:param name="name">{name}</fbt:param>!
</fbt>
</div>
);fbtee allows you to use regular React Components inside of <fbt> which will automatically create <fbt:param> calls for you:
const Greeting = ({ name }) => (
<div>
<fbt desc="Greeting">
Hello, <Name name={name} />!
</fbt>
</div>
);Note: <fbt> is auto-imported for you by the @nkzw/babel-preset-fbtee plugin.
After marking your strings for translation with <fbt>, run the following commands to extract, and compile translations:
pnpm fbtee collectYou can now upload the source_strings.json file to your translation provider. One sample for such a translation provider is Crowdin.
Generated File Structure
Your source_strings.json might look like this:
{
"childParentMappings": {},
"phrases": [
{
"hashToLeaf": {
"MB6OYuvCF1VzjOmqinI42g==": {
"desc": "What's next question",
"text": "What's next?"
}
},
"col_beg": 12,
"col_end": 68,
"filepath": "app/welcome/welcome.tsx",
"line_beg": 43,
"line_end": 43,
"project": "",
"jsfbt": {
"m": [],
"t": {
"desc": "What's next question",
"text": "What's next?"
}
}
}
]
}Based on the above source string, the US English translation (ie. translations/en_US.json) might look like this:
{
"fb-locale": "en_US",
"translations": {
"MB6OYuvCF1VzjOmqinI42g==": {
"tokens": [],
"types": [],
"translations": [
{
"translation": "What's next?",
"variations": []
}
]
}
}
}And German (translations/de_DE.json) might look like this:
{
"fb-locale": "de_DE",
"translations": {
"MB6OYuvCF1VzjOmqinI42g==": {
"tokens": [],
"types": [],
"translations": [
{
"translation": "Was kommt jetzt?",
"variations": []
}
]
}
}
}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:
pnpm fbtee translateThis will generate the translation.json file referred to in the setup done before. This file might look like this:
{
"de_DE": { "2HVYhv": "Was kommt jetzt?" },
"en_US": { "2HVYhv": "What's next?" }
}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.
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.
ESLint Plugin
You can install the optional eslint plugin to catch common mistakes and enforce best practices when using fbtee:
npm install -D @nkzw/eslint-plugin-fbteeAdd the following configuration to your ESLint configuration:
{
extends: ['plugin:@nkzw/eslint-plugin-fbtee/recommended'],
plugins: ['@nkzw/eslint-plugin-fbtee'],
}If you want stricter enforcement of translation rules, you can use the strict configuration, which enables the no-untranslated-strings rule. This ensures that all strings in your codebase are marked for translation.
{
extends: ['plugin:@nkzw/eslint-plugin-fbtee/strict'],
plugins: ['@nkzw/eslint-plugin-fbtee'],
}Or, if you'd like more granular control over the rules:
{
plugins: ['@nkzw/eslint-plugin-fbtee'],
rules: {
'@nkzw/fbtee/no-empty-strings': 'error',
'@nkzw/fbtee/no-unhelpful-desc': 'error',
'@nkzw/fbtee/no-untranslated-strings': 'error',
},
}What's better about fbtee than fbt?
Facebook has done an amazing job with fbt, an internationalization library that has been successfully used in production at Facebook for over 10 years. Their work provided a strong foundation for modern localization tools.
The open-source version of fbt, however, became unmaintained, difficult to set up, and incompatible with modern tools. It was eventually archived in November 2024. fbtee builds on this foundation with several improvements:
- Easier Setup: fbtee works with modern tools like Vite.
- Statically Typed: The fbtee compiler ensures correct usage of fbtee, library TypeScript types are provided, and an eslint plugin helps fix common mistakes.
- Improved React Compatibility: Removed React-specific hacks and added support for implicit React fragments (
<>). - Enhanced Features: Fixed and exported
inltListas a new<fbt:list>construct, which was not functional in the originalfbt. - Modernized Codebase: Rewritten using TypeScript, ES modules (ESM), eslint, and modern JavaScript standards. Removed cruft and legacy code.
- Updated Tooling: Uses modern tools like pnpm, Vite, and esbuild for faster and more efficient development of fbtee.
fbtee remains compatible with fbt and migration is straightforward.
Migration from fbt:
fbtee is compatible with fbt. If you are already using fbt, migrating to fbtee is straightforward:
- Follow the "Getting Started" guide above and remove all "fbt" related packages.
- Make sure you are using React 19.
- Replace
import { fbt } from 'fbt'withimport { fbt } from 'fbtee'. - Rename commands from
fbt-collect, andfbt-translatetofbtee-collect, andfbtee-translate.fbt manifestwas removed andfbtee collectnow handles this step automatically. - If you were using CommonJS modules for common strings or enums, convert them to ES modules.
- Ensure you are using the latest version of Node.js 22 or later.
- Rename your
init({})call tosetupFbtee({}).
After these changes, your project should work seamlessly with fbtee.
Note: Some legacy behavior and options were removed from fbtee. If you have a complex setup, please consider reaching out to us for help.
Credits
fbtwas originally created by Facebook.- The auto-import plugin was created by @alexandernanberg.
- Nakazawa Tech rewrote
fbtintofbteeand continues to maintain this project.
Support
- Check out the #fbtee channel on Reactiflux's Discord server.