Atlas survey

Type safety, extraction & developer tooling for React i18n in 2026

Compile-first libraries (Paraglide, typesafe-i18n) and next-intl give type safety for free; i18next and react-intl bolt it on via declaration files and CLI extraction.

26 sources ~6 min read #9 react · i18n · typescript · developer-tooling · type-safety

Decision (DX lens). For type safety you don’t have to maintain, pick a compile-first library: Paraglide or typesafe-i18n generate typed message functions where a renamed/missing key is a compile error by design [8] [20]. On Next.js, next-intl gives typed keys + typed navigation with one generated declaration [12]. Lingui wins catalog-management DX (co-located macros + lingui extract) [4]. i18next and react-intl are type-safe only after you bolt on declaration files / CLI extraction — the mature-but-manual option [3].

The core split: type-safe by design vs bolted on

Two architectures decide everything downstream.

  • Compile-first / codegen (Paraglide, typesafe-i18n, Lingui-with-types) — a generator or compiler reads your source catalog and emits typed artifacts (message functions or .d.ts). Keys, parameters, and plural args are typed as a side-effect of the build. You can’t ship an app that references a key that doesn’t exist [9] [10] [21].
  • Runtime + augmentation (i18next, react-intl) — the library ships a generic t() / formatMessage(); you opt in to type safety by hand-writing or generating a declaration file that augments the library’s types. It works well but it’s setup you own and maintain [8] [3].

next-intl sits in between: runtime hooks, but a first-class codegen step (createMessagesDeclaration) makes typing a one-liner rather than a hand-rolled augmentation [12].

How each library gets typed keys

i18next / react-i18next [1] [2] — TypeScript module augmentation. You add an i18next.d.ts that extends CustomTypeOptions with a resources type built from your JSON namespaces; t() then autocompletes keys and errors on wrong/missing ones. Writing the resource type by hand is tedious for large catalogs, so the documented path is generating it with i18next-resources-for-ts [25]. Nested keys and namespaces are typed via keySeparator/nsSeparator; v25.4+ added enableSelector specifically to fix TS/IDE performance on big resource unions [1].

react-intl (FormatJS) [17] [18] — messages are declared with defineMessages() returning Record<string, MessageDescriptor>, which doubles as the extraction hook. Key-level type safety is weaker: you get it by overriding the global FormatjsIntl namespace with ids: keyof typeof messages, and better defineMessages typings have been an open ask for years [26]. Ratings put its out-of-box type safety at “good, not autocomplete-grade” [20].

Lingui [4] [5] — you write source strings inline as t/<Trans> macros; the SWC/Babel macro plugin compiles them away and the CLI compiles catalogs. Type safety is enforced at the component call site via the macros plus generated catalog types, rated “excellent” in 2026 roundups [21]. Cost: it requires the compiler plugin — no plugin, and macros throw [3].

next-intl [12] [13] — point the plugin’s createMessagesDeclaration at your default-locale JSON; it emits a .d.json.ts and every t('...') becomes typed with autocomplete and compile errors, no manual augmentation. v4 also lets you declare a Locale type in AppConfig so useLocale() and createNavigation()’s <Link>/redirect/useRouter are locale-typed end to end [13].

Paraglide [7] [9] — the compiler turns each message into a typed ESM function you import (m.greeting({ name })). Keys and params are typed by default, a renamed/missing message is a compile error, and unused messages tree-shake out (up to ~70% smaller bundles). No provider/context — one Vite plugin [8]. Recent versions add a LocalizedString branded type so a translated string is even distinguishable from a raw one at the type level [7].

typesafe-i18n [10] [11] — a watcher/generator reads your base locale and emits .ts definitions plus React adapter wrappers. It types keys and arguments/formatters, and because every locale is checked against the base type, missing translations in other locales surface as type errors. Rated “best-in-class” for parameter-level checking (plural forms, message args), zero runtime deps [20] [21]. Trade-off: custom (non-ICU) message syntax.

Extraction & missing/unused-key tooling

Library Extraction mechanism Missing / unused-key detection
Lingui lingui extract CLI, extracts and merges into PO catalogs; SWC/Babel macro; experimental dep-tree crawl for per-page catalogs [4] [5] CLI flags stale/obsolete on merge; ESLint plugin catches misuse [6]
react-intl @formatjs/cli extract + babel-plugin-formatjs; separate install/config; you merge output yourself [19] [3] formatjs verify --missing-keys / --extra-keys / --structural-equality [19]
i18next No first-party extractor; community i18next-scanner scans t() calls [3] saveMissing at runtime; mostly delegated to i18n Ally [15]
next-intl No extractor — JSON authored by hand; codegen only for the type declaration [20] Missing keys are compile errors once typed [12]
Paraglide Messages authored in inlang catalog; compiler regenerates functions on change [9] Unused → tree-shaken; missing → compile error [8]
typesafe-i18n Generator/watcher emits types from base locale (dev mode) [11] Locales diffed against base type → missing keys are type errors [10]

IDE support is broadly shared: i18n Ally ⭐ 4.9k is the common VS Code layer — inline translation annotations after each key, one-click translate of missing/stale keys, stale-translation detection, and regex-based usage matching — and it supports i18next, react-intl, and Lingui among others [15] [16]. next-intl additionally documents its own official VSCode integration [14]. Compile-first libraries lean less on the editor extension because the compiler is the source of truth.

Comparison table

Library ⭐ Stars Type-safety mechanism Extraction tooling IDE support DX verdict
i18next / react-i18next 8.6k / 10k Module augmentation of CustomTypeOptions (opt-in; gen via i18next-resources-for-ts) [1] i18next-scanner (community) [3] i18n Ally (full) [15] Mature, flexible, most boilerplate [3]
react-intl / FormatJS 14.7k Global FormatjsIntl namespace override; weak key typing [26] @formatjs/cli + Babel plugin, verify [19] i18n Ally (full) [15] ICU-standard, verbose, manual merge [20]
Lingui 5.8k Macros + generated catalog types (excellent) [21] lingui extract (extract + merge) [4] i18n Ally + ESLint plugin [6] Best catalog DX; needs compiler plugin [3]
next-intl 4.3k createMessagesDeclaration codegen; typed keys + navigation [12] None (hand-authored JSON) [20] Official VSCode + i18n Ally [14] Top-tier on Next.js, fast setup [21]
Paraglide 567 Compiler → typed message functions; typed by default [8] inlang catalog + compiler [9] Compiler-driven; inlang tooling [7] Exceptional; tree-shaking, no context [9]
typesafe-i18n 2.5k Generator emits types from base locale; typed args (best-in-class) [20] Generator/watcher (dev mode) [11] Generated wrappers; editor-agnostic [10] Strongest arg-level types; custom syntax [21]

DX recommendation

  • Greenfield, care about type safety + bundle size: Paraglide — typed message functions, compile-error safety, tree-shaking, minimal setup [8]. Youngest ecosystem (⭐ 567) is the main risk [7].
  • Next.js App Router: next-intl — typed keys + typed navigation for near-zero effort [12].
  • Want the strongest argument/plural typing without ICU: typesafe-i18n [20].
  • Translator-heavy workflow / prize catalog DX: Lingui — co-located macros + one-command extract-and-merge, PO catalogs translators expect [4] [5].
  • Maximum ecosystem maturity, willing to wire up types yourself: i18next (augmentation + i18next-resources-for-ts) [1] [25]; react-intl if you need canonical ICU tooling and its verify completeness checks [19].

Citations · 26 sources

Click the Citations tab to load…