react-brain
Browse decisions
React & language foundations 3App architecture 10UI 13Platform & native 9Build, test, observe, secure 6AI in React apps 3stack composerdecision recordscensusstaleness bencharchitecturechangelogmethodologyroadmap

entriesui · verified 2026-07-16 · react + react-native

Internationalization (i18n)

reviewedconfidence: mediumthis tier holds 67% on the public scorecard (6/19 graded · 0 overturned) →

re-verified 3× — 2026-07-16 · 2026-07-13 · 2026-07-06 · changelog

recommendation

Next.js app → next-intl. Otherwise i18next (web + RN, huge ecosystem) or Lingui (compile-time, low boilerplate).

  • bundle-size-sensitive → next-intl / Lingui (AOT-compiled messages)
  • need maximum plugin ecosystem / RN + web parity → i18next

Options & tradeoffs

the field considered — and why each one isn’t the default here

optiontradeoffevidence
next-intl (4.x)Next.js-first; v4.8 compiles ICU messages AOT to shrink bundles4.4M/wk
FormatJS / react-intlICU message format; mature, framework-agnostic2.1M/wk · ships in 4/34
Lingui (6.x)compile-time message extraction; low-boilerplate; i18n directivesships in 3/34
i18next / react-i18nextvery popular, big plugin ecosystem; works web + RN10.1M/wk · ships in 8/34
fbteemodern continuation of Meta's deprecated fbt

evidence: npm weekly downloads (signals snapshot) · “ships in n/D” = adoption across the production-app census, honest denominators

npm weekly downloads (from the corpus's last signals run): next-intl 4.4M · react-i18next 10.1M · i18next 14.0M · react-intl 2.1M

Canonical reading

Editorial annotations on why each piece matters — the articles themselves are the originals; read them there.

Ahead-of-time compilation for next-intlJan Amann (next-intl author)

Why and how ICU messages are precompiled at build time into minified ASTs evaluated by a ~650-byte runtime, weighing function-based vs AST strategies against bundle size. Teaches the compile-time-vs-runtime and bundle-size tradeoffs from first principles.

ICU Message Syntax (core concepts)FormatJS (react-intl)

The canonical, example-driven explanation of the ICU message format — interpolation, plurals/selectordinal, select, number/date skeletons, rich text. ICU is a stable Unicode standard, so this foundational reference doesn't go stale.

Sources

The full explanation

The reviewed long-form essay behind this entry — the why, not a how-to. Also on GitHub.

About internationalization (i18n)

Diataxis: Explanation. This page builds understanding of the i18n decision — why the pick is framework-first, and why the deeper axis is compile-time vs runtime. It is not a setup guide and not a message-syntax reference (that is ICU's own documentation). The candidate list and one-line tradeoffs live in the index entry RB-E-I18N; there is no defer skill — this entry owns its own depth. Read this for the why.

The one distinction that organises everything: the format vs the machinery

Every "which i18n library?" debate conflates two layers that age at completely different speeds. The first is the message format — the syntax translators and developers share. For the serious React options that format is ICU: interpolation, plurals and selectordinal, select, number/date skeletons, rich text. ICU is a stable Unicode standard; the canonical FormatJS reference for it does not go stale, and the same syntax underlies react-intl, next-intl, and Lingui. Learning it is the durable investment.

The second layer is the machinery that evaluates the format — and this is where libraries actually differ. A runtime library ships a message parser to every user's device and interprets ICU strings on the fly. An ahead-of-time (AOT) library moves that work to the build: next-intl v4.8 precompiles ICU messages at build time into minified ASTs evaluated by a ~650-byte runtime — the parser never ships. The precompilation write-up by next-intl's author walks the design space honestly, weighing function-based vs AST compilation strategies against bundle size before landing on ASTs.

Hence the organising slogan: compile the locale, don't ship the library. The format is a standard you adopt; whether its evaluator rides in your bundle or runs in your build pipeline is the real choice — and it is a bundle-size choice, not a features choice.

The default, and why

Next.js app → next-intl. Otherwise i18next (web + RN, huge ecosystem) or Lingui (compile-time, low boilerplate).

The first question is framework, not fashion: next-intl is Next.js-first, and in a Next.js app it brings the AOT story (v4.8's build-time ICU compilation) with framework-shaped integration. Outside Next.js the default forks on what you optimise for:

  • Reach and ecosystem → i18next (with react-i18next). Very popular, a big plugin ecosystem, and it works on web and React Native — the parity pick when one message layer must serve both renderers.
  • Bundle and boilerplate → Lingui (6.x). Compile-time message extraction, low-boilerplate authoring, i18n directives.

The index entry's when-clauses restate the fork: bundle-size-sensitive → next-intl / Lingui (AOT-compiled messages); maximum plugin ecosystem or RN + web parity → i18next.

The landscape, and when each one wins

next-intl (4.x) — Next.js-first. The headline is v4.8's ahead-of-time compilation: ICU messages become minified ASTs at build time, evaluated by a runtime of roughly 650 bytes, trading runtime message parsing for smaller bundles. It wins exactly where it says: Next.js apps, especially bundle-size-sensitive ones.

Lingui (6.x) — the compile-time pick outside Next.js. Message extraction happens at build time, authoring is low-boilerplate via i18n directives. It shares the AOT side of the fork with next-intl in the entry's bundle-size when-clause.

i18next / react-i18next — the runtime incumbent. Very popular, the biggest plugin ecosystem in the category, and one layer that spans web and React Native. It wins when reach, plugins, and web+RN parity outweigh the compile-time bundle story.

FormatJS / react-intl — the mature, framework-agnostic home of the ICU message format itself. Its documentation is the canonical ICU reference regardless of which library you ship.

fbtee — the modern continuation of Meta's deprecated fbt. The entry stakes no claim beyond that lineage; treat it as a named option to evaluate, not a graded one.

Tradeoffs and failure modes to name out loud

  • Hardcoded strings in a production app. The index entry's own doctor signal: no i18n layer found anywhere in source in a production-stage repo. Its hint is the honest economics — if localization is ever on the roadmap, retrofitting hardcoded strings is the expensive path; wire the message layer early (i18next for web+RN reach, Lingui/next-intl for AOT-compiled bundles) even while you ship one locale.
  • Shipping the parser when you didn't have to. If the app is bundle-size-sensitive and the messages are ICU anyway, a runtime parser on every device is pure overhead the AOT options (next-intl v4.8, Lingui) were built to remove.
  • Treating the format as lock-in. The message syntax is ICU — a stable Unicode standard shared across react-intl, next-intl, and Lingui — so the syntax investment survives a library change; the machinery is what you'd swap.
  • next-intl outside its home. It is Next.js-first by the entry's own tradeoff line; the non-Next.js defaults are i18next and Lingui.
  • Assuming all "compile-time" is the same compile. The entry groups next-intl and Lingui as AOT-compiled messages, but the mechanism it details (minified ASTs + ~650-byte runtime, function-based vs AST strategy analysis) is documented for next-intl specifically — verify Lingui's output shape against your own bundle budget rather than transferring the numbers.

How it interacts with the rest of the stack

  • Meta-frameworks (RB-E-META-FRAMEWORKS). The default's first branch is literally "is this a Next.js app" — the i18n pick is downstream of the framework pick.
  • Cross-platform (RB-E-CROSSPLATFORM). i18next's web + React Native reach is the entry's named parity option; if one shared logic package serves both platforms, that is the axis that decides.
  • Build & bundle (RB-E-BUILD). AOT message compilation is a build-pipeline concern, and its entire payoff is bundle size — the compile-time-vs-runtime fork in this entry is a bundle-budget decision wearing an i18n hat.

In one paragraph

Separate the format from the machinery. The format is ICU — interpolation, plurals, select, number/date skeletons — a stable Unicode standard shared by react-intl, next-intl, and Lingui, and the part worth learning once. The machinery either ships (runtime parsing on device) or compiles away (next-intl v4.8: ICU messages precompiled at build time into minified ASTs evaluated by a ~650-byte runtime) — compile the locale, don't ship the library. The default is framework-first: Next.js → next-intl; otherwise i18next (web + RN, huge plugin ecosystem) or Lingui (6.x, compile-time extraction, low boilerplate), with bundle-sensitivity pushing toward the AOT pair and ecosystem/parity pushing toward i18next. And wire the layer early: in a production repo with no i18n layer, retrofitting hardcoded strings is the expensive path.


See also: RB-E-META-FRAMEWORKS (the Next.js branch that picks next-intl), RB-E-CROSSPLATFORM (i18next as the web+RN parity layer), RB-E-BUILD (AOT compilation as a bundle-budget decision). Background reading: Jan Amann's precompilation write-up (the compile-time-vs-runtime tradeoff from first principles) and the FormatJS ICU syntax reference (the format itself, which doesn't go stale).

Related in ui: styling · component-libs · animation · lists · a11y · charts · editors · svg · maps · calendars · sheets · polish