Skip to main content

Alternatives

A quick reference for teams already using — or evaluating — another React i18n library. All of these are solid projects; the differences below are about fit, not quality.

react-i18next

The incumbent. Uses developer-authored keys and a t(key) / <Trans> runtime.

react-i18nextkapi-react
Source identifierDeveloper-invented key (natural-language keys also supported)Source text + structural context
JSX wrappingt("key") or <Trans i18nKey="...">Plain JSX
Extractioni18next-cli / i18next-parser, or manualPlugin during normal build
FormatJSON (nested or flat); XLIFF via external conversionKLF with structural context, placeholders, plural forms
Runtime costShips the i18next runtime (interpolation, plural resolution, resource store); dict loaded at runtimeInline mode: zero runtime; runtime mode: one dict lookup

Migrating from react-i18next typically means dropping the t() / <Trans> wrappers and re-running the extract against the bare JSX. Existing translations can be loaded as-is if you key them by the same source text; otherwise it's a one-time re-translation pass through your TM.

FormatJS (react-intl)

Developer-authored message descriptors with ICU formatting baked in.

FormatJSkapi-react
Source identifierDeveloper-invented id (or auto-hash of the descriptor)Source text + structural context
JSX wrapping<FormattedMessage> or useIntl().formatMessage()Plain JSX
Plurals / selectRaw ICU message strings<Plural> / <Select> authoring components
Extraction@formatjs/cliPlugin during normal build
Runtime costShips intl-messageformat (ICU parser/formatter); can precompile to AST to drop the parserInline mode: zero runtime; runtime mode: one dict lookup

FormatJS's ICU-in-source approach is powerful for complex message composition, but forces translators (and developers) to work in ICU directly. kapi-react keeps the source looking like React, then emits the canonical ICU template for translators' CAT tools downstream.

Lingui

The closest in philosophy — Lingui uses macros (<Trans>, t tagged templates) to rewrite source text into hashed-key runtime lookups at build time.

Linguikapi-react
Source identifierSource text (Babel macro; experimental SWC plugin)Source text + structural context (via SWC plugin)
JSX wrapping<Trans>Hello</Trans>, t\...`` macroPlain JSX
Extractionlingui extractPlugin during normal build
FormatPO (default), JSON, CSVKLF with structural context, placeholders, plural forms
Runtime costSmall @lingui/core runtime + compiled catalogs; dict lookupInline mode: zero runtime; runtime mode: one dict lookup

Lingui and kapi-react agree on "source text as key". The core difference: Lingui asks you to opt every string into the macro (<Trans>, t`...`); kapi-react opts in by default. t() in kapi-react is a small escape hatch for non-JSX strings, not the normal authoring pattern.

fbtee

The modern continuation of Meta's fbt (Meta archived fbt in late 2024). fbtee rebuilds it for TypeScript, React 19, ESM, and Vite / Next.js with both Babel and SWC transforms, while keeping fbt's authoring model: every translatable string is wrapped in an explicit <fbt> marker, and the source text is the key.

fbteekapi-react
Source identifierSource text + required descSource text + structural context
JSX wrapping<fbt desc="...">, fbt() / fbs()Plain JSX
Plurals / gender<fbt:plural>, <fbt:pronoun>, <fbt:enum><Plural> / <Select> authoring components
Extractionfbtee collectprepare-translationstranslatePlugin during normal build
FormatJSON (source_strings.json + per-locale files)KLF with structural context, placeholders, plural forms
Runtime costShips the fbt runtime to resolve params/plural/pronoun at render; translations loadedInline mode: zero runtime; runtime mode: one dict lookup

fbtee shares kapi-react's "source text as key" philosophy, but takes the opposite stance on wrapping: it deliberately requires an <fbt> marker (with a desc) around every translatable string so the Babel / SWC compiler and ESLint plugin can statically analyse, type-check, and extract it. That buys compile-time guarantees and declarative inline plural / gender handling, at the cost of wrapping ceremony on every string — the same wrapping tax kapi-react removes by extracting plain JSX automatically.

Paraglide (Inlang)

Typed, per-message functions generated at build time. A message welcome becomes m.welcome().

Paraglidekapi-react
Source identifierDeveloper-invented message idSource text + structural context
JSX wrappingGenerated function call (m.welcome())Plain JSX
Tree-shakeabilityEvery message is a function — excellent tree-shakingDict lookup — dict is one object
Runtime costMinimal runtime; tree-shaken per-message functions, so unused messages cost ~0Inline mode: zero runtime; runtime mode: one dict lookup

Paraglide's typed-function model gives strong refactoring support but requires the ids-as-function-names model. kapi-react is source-text-as-key; the two can coexist in a codebase if needed, but usually you pick one.

Which to pick

  • You want zero-wrapper ergonomics and your strings mostly live in JSX → kapi-react.
  • You want typed message functions with best-in-class tree-shaking → Paraglide.
  • You're deeply invested in ICU-as-source → FormatJS.
  • You want explicit, compile-time-checked inline markers with declarative plural/gender → fbtee.
  • You have a large existing react-i18next codebase → stay with react-i18next unless you're doing a rewrite anyway.

When kapi-react isn't the right fit

See the same section in the Introduction.