diff --git a/.gitignore b/.gitignore
index 469d56dd..ab6d2e11 100644
--- a/.gitignore
+++ b/.gitignore
@@ -10,6 +10,8 @@ example/src/translatedFbts
example/src/translatedFbts.json
node_modules
packages/*/lib
+packages/*/LICENSE
+packages/*/README.md
packages/fbt/lib
packages/fbt/LICENSE
tsconfig.tsbuildinfo
diff --git a/README.md b/README.md
index 8b8dce34..0fedcb8d 100644
--- a/README.md
+++ b/README.md
@@ -1,23 +1,74 @@
-
-
-
+# fbtee
-**fbtee** (Far Better Translations, Extended Edition) is an internationalization framework for JavaScript designed to be **powerful**, **flexible**, and **intuitive**.
+**fbtee** (Far Better Translations, _Extended Edition_) is an internationalization framework for JavaScript & React designed to be **powerful**, **flexible**, and **intuitive**.
-It helps with the following:
+## Why fbtee?
--
-
-* Organizing your source text for translation
-* Composing grammatically correct translatable User Interfaces
-* Eliminating verbose boilerplate for generating User Interfaces
+- **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 one year of production usage in [Athena Crisis](https://athenacrisis.com).
+- **Optimized Performance with IR:** Compiles translations into an Intermediate Representation (IR) for extracting strings, and 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.
+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
+
+```bash
+npm install fbtee @nkzw/babel-fbtee
+```
+
+In your `vite.config.ts`:
+
+```ts
+import fbteePreset from '@nkzw/babel-fbtee';
+import react from '@vitejs/plugin-react';
+
+export default {
+ plugins: [
+ react({
+ babel: {
+ presets: [fbteePreset],
+ },
+ }),
+ ],
+};
+```
+
+_You’re now ready to go!_
+
+## Usage
+
+## 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.
+- **Improved React Compatibility:** Removed React-specific hacks and added support for implicit React fragments (`<>`).
+- **Enhanced Features:** Fixed and exported `intlList`, which was not functional in the original `fbt`.
+- **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:
+
+- Replace `import { fbt } from 'fbt'` with `import { fbt } from 'fbtee'`.
+- Rename commands from `fbt-collect`, `fbt-manifest` and `fbt-translate` to `fbtee-collect`, `fbtee-manifest` and `fbtee-translate`.
+- 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.
+
+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](mailto:fbtee@nakazawa.dev)._
## Credits
- `fbt` was originally created by [Facebook](https://github.com/facebook/fbt).
-- The auto-import plugin was created by @alexandernanberg.
-- [Nakazawa Tech](https://nkzw.tech) rewrote fbt into fbtee and is maintaining this project.
+- The auto-import plugin was created by [@alexandernanberg](https://github.com/alexandernanberg).
+- [Nakazawa Tech](https://nkzw.tech) rewrote `fbt` into `fbtee` and continues to maintain this project.
diff --git a/docs/getting_started_on_web.md b/docs/getting_started.md
similarity index 100%
rename from docs/getting_started_on_web.md
rename to docs/getting_started.md
diff --git a/package.json b/package.json
index fd0c9dc3..a74519bf 100644
--- a/package.json
+++ b/package.json
@@ -6,7 +6,8 @@
"type": "module",
"scripts": {
"build": "pnpm -r build",
- "build:all": "pnpm -r build && pnpm -r build:types && pnpm install && cd example && pnpm build:fbtee",
+ "build:all": "pnpm -r build && pnpm -r build:types && pnpm install && pnpm copy-files && cd example && pnpm build:fbtee",
+ "copy-files": "find packages/* -type d -maxdepth 0 -exec cp README.md LICENSE {} \\;",
"clean": "rm -rf packages/*/lib; cd example pnpm clean",
"dev": "cd example && pnpm build:fbtee && pnpm dev",
"lint:format": "prettier --cache --check .",
diff --git a/packages/babel-plugin-fbtee/src/JSFbtUtil.tsx b/packages/babel-plugin-fbtee/src/JSFbtUtil.tsx
index e8b75fad..3731f339 100644
--- a/packages/babel-plugin-fbtee/src/JSFbtUtil.tsx
+++ b/packages/babel-plugin-fbtee/src/JSFbtUtil.tsx
@@ -5,7 +5,7 @@ import type {
TableJSFBTTreeLeaf,
} from './index.tsx';
import nullthrows from './nullthrows.tsx';
-import type { FbtTableKey } from './Types.tsx';
+import type { FbtTableKey } from './Types.d.ts';
export function isTableJSFBTTreeLeaf(
value: Partial,
diff --git a/packages/babel-plugin-fbtee/src/Types.tsx b/packages/babel-plugin-fbtee/src/Types.d.ts
similarity index 100%
rename from packages/babel-plugin-fbtee/src/Types.tsx
rename to packages/babel-plugin-fbtee/src/Types.d.ts
diff --git a/packages/babel-plugin-fbtee/src/bin/FbtCollector.tsx b/packages/babel-plugin-fbtee/src/bin/FbtCollector.tsx
index da987b7c..2db091f3 100644
--- a/packages/babel-plugin-fbtee/src/bin/FbtCollector.tsx
+++ b/packages/babel-plugin-fbtee/src/bin/FbtCollector.tsx
@@ -13,7 +13,7 @@ import fbt, {
getExtractedStrings,
getFbtElementNodes,
} from '../index.tsx';
-import type { PatternHash, PatternString } from '../Types.tsx';
+import type { PatternHash, PatternString } from '../Types.d.ts';
export type ExternalTransform = (
src: string,
diff --git a/packages/babel-plugin-fbtee/src/bin/TextPackager.tsx b/packages/babel-plugin-fbtee/src/bin/TextPackager.tsx
index 9aa6e90c..36289453 100644
--- a/packages/babel-plugin-fbtee/src/bin/TextPackager.tsx
+++ b/packages/babel-plugin-fbtee/src/bin/TextPackager.tsx
@@ -1,5 +1,5 @@
import { onEachLeaf } from '../JSFbtUtil.tsx';
-import type { PatternHash, PatternString } from '../Types.tsx';
+import type { PatternHash, PatternString } from '../Types.d.ts';
import type { HashToLeaf, PackagerPhrase } from './FbtCollector.tsx';
export type HashFunction = (
diff --git a/packages/babel-plugin-fbtee/src/bin/translateUtils.tsx b/packages/babel-plugin-fbtee/src/bin/translateUtils.tsx
index 967ee81c..7747aeef 100644
--- a/packages/babel-plugin-fbtee/src/bin/translateUtils.tsx
+++ b/packages/babel-plugin-fbtee/src/bin/translateUtils.tsx
@@ -10,7 +10,7 @@ import TranslationBuilder from '../translate/TranslationBuilder.tsx';
import TranslationConfig from '../translate/TranslationConfig.tsx';
import type { SerializedTranslationData } from '../translate/TranslationData.tsx';
import TranslationData from '../translate/TranslationData.tsx';
-import type { PatternHash, PatternString } from '../Types.tsx';
+import type { PatternHash, PatternString } from '../Types.d.ts';
import type { CollectFbtOutput, CollectFbtOutputPhrase } from './collect.tsx';
export type Options = Readonly<{
diff --git a/packages/babel-plugin-fbtee/src/index.tsx b/packages/babel-plugin-fbtee/src/index.tsx
index bf34549a..82e71d79 100644
--- a/packages/babel-plugin-fbtee/src/index.tsx
+++ b/packages/babel-plugin-fbtee/src/index.tsx
@@ -22,7 +22,7 @@ import FbtEnumRegistrar from './FbtEnumRegistrar.tsx';
import FbtNodeChecker from './FbtNodeChecker.tsx';
import { checkOption, errorAt } from './FbtUtil.tsx';
import { FbtVariationType } from './translate/IntlVariations.tsx';
-import { FbtTableKey, PatternHash, PatternString } from './Types.tsx';
+import type { FbtTableKey, PatternHash, PatternString } from './Types.d.ts';
export { SENTINEL } from './FbtConstants.tsx';
export { default as fbtHashKey } from './fbtHashKey.tsx';
diff --git a/packages/babel-plugin-fbtee/src/replaceClearTokensWithTokenAliases.tsx b/packages/babel-plugin-fbtee/src/replaceClearTokensWithTokenAliases.tsx
index 140f9166..f9ec4145 100644
--- a/packages/babel-plugin-fbtee/src/replaceClearTokensWithTokenAliases.tsx
+++ b/packages/babel-plugin-fbtee/src/replaceClearTokensWithTokenAliases.tsx
@@ -1,6 +1,6 @@
import { tokenNameToTextPattern } from './fbt-nodes/FbtNodeUtil.tsx';
import { TokenAliases } from './index.tsx';
-import { PatternString } from './Types.tsx';
+import type { PatternString } from './Types.d.ts';
/**
* Clear token names in translations and runtime call texts need to be replaced
diff --git a/packages/babel-plugin-fbtee/src/translate/FbtSite.tsx b/packages/babel-plugin-fbtee/src/translate/FbtSite.tsx
index e3bcbda2..05e11d6a 100644
--- a/packages/babel-plugin-fbtee/src/translate/FbtSite.tsx
+++ b/packages/babel-plugin-fbtee/src/translate/FbtSite.tsx
@@ -8,7 +8,7 @@ import type {
} from '../index.tsx';
import { isTableJSFBTTreeLeaf, onEachLeaf } from '../JSFbtUtil.tsx';
import nullthrows from '../nullthrows.tsx';
-import type { PatternHash, PatternString } from '../Types.tsx';
+import type { PatternHash, PatternString } from '../Types.d.ts';
import type {
FbtSiteHashifiedTableJSFBTTree,
FbtSiteHashToTextAndDesc,
diff --git a/packages/babel-plugin-fbtee/src/translate/FbtSiteBase.tsx b/packages/babel-plugin-fbtee/src/translate/FbtSiteBase.tsx
index 794e265c..aeb32cad 100644
--- a/packages/babel-plugin-fbtee/src/translate/FbtSiteBase.tsx
+++ b/packages/babel-plugin-fbtee/src/translate/FbtSiteBase.tsx
@@ -1,5 +1,5 @@
import type { HashToLeaf } from '../bin/FbtCollector.tsx';
-import type { FbtTableKey, PatternHash, PatternString } from '../Types.tsx';
+import type { FbtTableKey, PatternHash, PatternString } from '../Types.d.ts';
import type {
IntlFbtVariationTypeValue,
IntlVariationMaskValue,
diff --git a/packages/babel-plugin-fbtee/src/translate/TranslationBuilder.tsx b/packages/babel-plugin-fbtee/src/translate/TranslationBuilder.tsx
index bcb280fe..446215a6 100644
--- a/packages/babel-plugin-fbtee/src/translate/TranslationBuilder.tsx
+++ b/packages/babel-plugin-fbtee/src/translate/TranslationBuilder.tsx
@@ -2,7 +2,7 @@ import invariant from 'invariant';
import { varDump } from '../FbtUtil.tsx';
import nullthrows from '../nullthrows.tsx';
import replaceClearTokensWithTokenAliases from '../replaceClearTokensWithTokenAliases.tsx';
-import type { FbtTableKey, PatternHash } from '../Types.tsx';
+import type { FbtTableKey, PatternHash } from '../Types.d.ts';
import { FbtSite, FbtSiteMetaEntry } from './FbtSite.tsx';
import type { FbtSiteHashifiedTableJSFBTTree } from './FbtSiteBase.tsx';
import type {
diff --git a/packages/fbtee/src/FbtResult.tsx b/packages/fbtee/src/FbtResult.tsx
index 23714088..d05be084 100644
--- a/packages/fbtee/src/FbtResult.tsx
+++ b/packages/fbtee/src/FbtResult.tsx
@@ -1,9 +1,9 @@
import FbtResultBase from './FbtResultBase.tsx';
-import {
+import type {
BaseResult,
IFbtErrorListener,
NestedFbtContentItems,
-} from './Types.tsx';
+} from './Types.d.ts';
type Props = Readonly<{
content: NestedFbtContentItems;
diff --git a/packages/fbtee/src/FbtResultBase.tsx b/packages/fbtee/src/FbtResultBase.tsx
index 76577629..a2e2b9bc 100644
--- a/packages/fbtee/src/FbtResultBase.tsx
+++ b/packages/fbtee/src/FbtResultBase.tsx
@@ -1,9 +1,9 @@
-import {
+import type {
BaseResult,
FbtContentItem,
IFbtErrorListener,
NestedFbtContentItems,
-} from './Types.tsx';
+} from './Types.d.ts';
export default class FbtResultBase implements BaseResult {
_contents: NestedFbtContentItems;
diff --git a/packages/fbtee/src/FbtTable.tsx b/packages/fbtee/src/FbtTable.tsx
index a2dbbe12..293777c8 100644
--- a/packages/fbtee/src/FbtTable.tsx
+++ b/packages/fbtee/src/FbtTable.tsx
@@ -1,4 +1,4 @@
-import { FbtTableKey } from '@nkzw/babel-plugin-fbtee/src/Types.tsx';
+import type { FbtTableKey } from '@nkzw/babel-plugin-fbtee/src/Types.d.ts';
import invariant from 'invariant';
import type { FbtRuntimeInput, FbtTableArgs } from './Hooks.tsx';
diff --git a/packages/fbtee/src/Hooks.tsx b/packages/fbtee/src/Hooks.tsx
index f22339b9..82dc137c 100644
--- a/packages/fbtee/src/Hooks.tsx
+++ b/packages/fbtee/src/Hooks.tsx
@@ -6,13 +6,13 @@ import type {
import FbtResult from './FbtResult.tsx';
import type { FbtTableArg } from './FbtTableAccessor.tsx';
import IntlViewerContext from './IntlViewerContext.tsx';
-import {
+import type {
BaseResult,
FbtErrorContext,
IFbtErrorListener,
NestedFbtContentItems,
PureStringResult,
-} from './Types.tsx';
+} from './Types.d.ts';
export type ResolverFn = (
contents: NestedFbtContentItems,
diff --git a/packages/fbtee/src/Types.tsx b/packages/fbtee/src/Types.d.ts
similarity index 100%
rename from packages/fbtee/src/Types.tsx
rename to packages/fbtee/src/Types.d.ts
diff --git a/packages/fbtee/src/__mocks__/getFbtResult.tsx b/packages/fbtee/src/__mocks__/getFbtResult.tsx
index 8b592715..79f55925 100644
--- a/packages/fbtee/src/__mocks__/getFbtResult.tsx
+++ b/packages/fbtee/src/__mocks__/getFbtResult.tsx
@@ -1,5 +1,5 @@
import FbtResult from '../FbtResult.tsx';
-import { NestedFbtContentItems } from '../Types.tsx';
+import type { NestedFbtContentItems } from '../Types.d.ts';
export default function getFbtResult(
contents: NestedFbtContentItems,
diff --git a/packages/fbtee/src/__tests__/FbtResult-test.tsx b/packages/fbtee/src/__tests__/FbtResult-test.tsx
index 39f653dc..03c57359 100644
--- a/packages/fbtee/src/__tests__/FbtResult-test.tsx
+++ b/packages/fbtee/src/__tests__/FbtResult-test.tsx
@@ -1,7 +1,7 @@
import { describe, expect, it, jest } from '@jest/globals';
import FbtResult from '../FbtResult.tsx';
import Hooks from '../Hooks.tsx';
-import { IFbtErrorListener } from '../Types.tsx';
+import type { IFbtErrorListener } from '../Types.d.ts';
let errorListener: IFbtErrorListener | null;
diff --git a/packages/fbtee/src/__tests__/fbt-test.tsx b/packages/fbtee/src/__tests__/fbt-test.tsx
index 95099325..59a4c062 100644
--- a/packages/fbtee/src/__tests__/fbt-test.tsx
+++ b/packages/fbtee/src/__tests__/fbt-test.tsx
@@ -13,11 +13,11 @@ import Hooks, { FbtRuntimeCallInput, FbtTranslatedInput } from '../Hooks.tsx';
import { fbt, FbtResult } from '../index.tsx';
import init from '../init.tsx';
import IntlVariations from '../IntlVariations.tsx';
-import {
+import type {
BaseResult,
IFbtErrorListener,
NestedFbtContentItems,
-} from '../Types.tsx';
+} from '../Types.d.ts';
init({
translations: { en_US: {} },
diff --git a/packages/fbtee/src/fbt.tsx b/packages/fbtee/src/fbt.tsx
index b6ec0418..1d7764b3 100644
--- a/packages/fbtee/src/fbt.tsx
+++ b/packages/fbtee/src/fbt.tsx
@@ -23,7 +23,7 @@ import {
getNumberVariations,
} from './IntlVariationResolver.tsx';
import substituteTokens, { Substitutions } from './substituteTokens.tsx';
-import { BaseResult, NestedFbtContentItems } from './Types.tsx';
+import type { BaseResult, NestedFbtContentItems } from './Types.d.ts';
const ParamVariation: ParamVariationType = {
gender: 1,
diff --git a/packages/fbtee/src/getAllSubstitutions.tsx b/packages/fbtee/src/getAllSubstitutions.tsx
index c0b4c307..f39e5a60 100644
--- a/packages/fbtee/src/getAllSubstitutions.tsx
+++ b/packages/fbtee/src/getAllSubstitutions.tsx
@@ -2,7 +2,7 @@ import invariant from 'invariant';
import FbtTable from './FbtTable.tsx';
import { FbtTableArgs } from './Hooks.tsx';
import { Substitutions } from './substituteTokens.tsx';
-import { FbtContentItem } from './Types.tsx';
+import type { FbtContentItem } from './Types.d.ts';
export default function getAllSubstitutions(args: FbtTableArgs) {
let substitutions: Substitutions | null = null;
diff --git a/packages/fbtee/src/getFbsResult.tsx b/packages/fbtee/src/getFbsResult.tsx
index a36c33dc..660225fc 100644
--- a/packages/fbtee/src/getFbsResult.tsx
+++ b/packages/fbtee/src/getFbsResult.tsx
@@ -1,6 +1,6 @@
import { PatternHash } from '@nkzw/babel-plugin-fbtee';
import FbtPureStringResult from './FbtPureStringResult.tsx';
-import { IFbtErrorListener, NestedFbtContentItems } from './Types.tsx';
+import type { IFbtErrorListener, NestedFbtContentItems } from './Types.d.ts';
export default function getFbsResult(
contents: NestedFbtContentItems,
diff --git a/packages/fbtee/src/index.tsx b/packages/fbtee/src/index.tsx
index be6b5e35..c58ca577 100644
--- a/packages/fbtee/src/index.tsx
+++ b/packages/fbtee/src/index.tsx
@@ -1,11 +1,13 @@
import fbsInternal from './fbs.tsx';
import fbtInternal from './fbt.tsx';
-import { FbsAPI, FbtAPI } from './Types.tsx';
+import type { FbsAPI, FbtAPI } from './Types.d.ts';
export { default as IntlVariations } from './IntlVariations.tsx';
export { default as init } from './init.tsx';
export { default as GenderConst } from './GenderConst.tsx';
export { default as FbtTranslations } from './FbtTranslations.tsx';
export { default as FbtResult } from './FbtResult.tsx';
+export { default as intlList } from './intlList.tsx';
+
export const fbt = fbtInternal as unknown as FbtAPI;
export const fbs = fbsInternal as unknown as FbsAPI;
diff --git a/packages/fbtee/src/init.tsx b/packages/fbtee/src/init.tsx
index 36b99d86..103adace 100644
--- a/packages/fbtee/src/init.tsx
+++ b/packages/fbtee/src/init.tsx
@@ -4,7 +4,7 @@ import FbtTranslations, { TranslationDict } from './FbtTranslations.tsx';
import getFbsResult from './getFbsResult.tsx';
import Hook, { Hooks } from './Hooks.tsx';
import IntlViewerContext from './IntlViewerContext.tsx';
-import { IFbtErrorListener, NestedFbtContentItems } from './Types.tsx';
+import type { IFbtErrorListener, NestedFbtContentItems } from './Types.d.ts';
const getFbtResult = (
contents: NestedFbtContentItems,
diff --git a/packages/fbtee/src/substituteTokens.tsx b/packages/fbtee/src/substituteTokens.tsx
index 0eb201b5..ce76b728 100644
--- a/packages/fbtee/src/substituteTokens.tsx
+++ b/packages/fbtee/src/substituteTokens.tsx
@@ -3,7 +3,7 @@ import {
dedupeStops,
PUNCT_CHAR_CLASS,
} from './IntlPunctuation.tsx';
-import { FbtContentItem, NestedFbtContentItems } from './Types.tsx';
+import type { FbtContentItem, NestedFbtContentItems } from './Types.d.ts';
// This pattern finds tokens inside a string: 'string with {token} inside'.
// It also grabs any punctuation that may be present after the token, such as