One architectural fault line sets every downstream property: whether type safety is generated by the build or hand-wired after the fact. This sheet reads that split as a feature matrix, not prose.
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].
◈ next-intl straddles both: runtime hooks, but a first-class codegen step (createMessagesDeclaration) makes typing a one-liner rather than a hand-rolled augmentation[12].
| Part | Typed keys |
Typed args / plurals |
Missing key = build error |
First-party extractor |
i18n Ally support |
Zero manual type setup |
|---|---|---|---|---|---|---|
| ◑opt-in .d.ts | ✗ | ◑once typed | ✗community scanner | ✓ | ✗ | |
| ◑weak / manual | ✗ | ✗ | ✓@formatjs/cli | ✓ | ✗ | |
| ✓ | ✓ | ◑at macro site | ✓extract + merge | ✓+ ESLint | ◑needs plugin | |
| ✓ | ✓ICU | ✓ | ✗hand-authored JSON | ✓+ official | ✓one codegen | |
| ✓ | ✓ | ✓ | ◑inlang catalog | ◑inlang tooling | ✓ | |
| ✓ | ✓best-in-class | ✓ | ◑watcher (dev) | ◑editor-agnostic | ✓ |
CustomTypeOptions; add an i18next.d.ts with a resources type[1][2]. Generate it with i18next-resources-for-ts for large catalogs[25].i18next-scanner reads t() calls[3].enableSelector to fix TS/IDE lag on big resource unions[1].defineMessages() returns Record<string, MessageDescriptor>; key typing via a global FormatjsIntl namespace override[18]. Better defineMessages typings are a years-old open ask[26].@formatjs/cli extract + babel-plugin-formatjs; you merge output yourself[19].formatjs verify with --missing-keys / --extra-keys[19].t/<Trans> macros compiled by the SWC/Babel plugin; type-safe at the component call site via macros + generated catalog types — rated "excellent"[21].lingui extract extracts and merges into PO catalogs; experimental dep-tree crawl for per-page catalogs[4][5].createMessagesDeclaration at the default-locale JSON; it emits a .d.json.ts and every t('…') becomes typed — no manual augmentation[12].Locale type in AppConfig; useLocale() and createNavigation() are locale-typed end to end[13].m.greeting({ name })). Keys + params typed by default; unused messages tree-shake out (up to ~70% smaller bundles)[8].LocalizedString branded type distinguishes a translated string from a raw one at the type level[7]..ts definitions + React adapter wrappers. Types keys and args/formatters; other locales are diffed against the base type[10][11].Supports i18next, react-intl and Lingui among others[15]. Compile-first libraries lean less on the editor extension because the compiler is the source of truth.
| Requirement profile | Recommended part | Why |
|---|---|---|
| Greenfield; care about type safety + bundle size | Typed message functions, compile-error safety, tree-shaking, minimal setup. Youngest ecosystem (⭐ 567) is the main risk[8][7]. | |
| Next.js App Router | Typed keys + typed navigation for near-zero effort[12]. | |
| Strongest argument / plural typing, without ICU | Best-in-class parameter-level checking of plural forms and message args[20]. | |
| Translator-heavy workflow / prize catalog DX | Co-located macros + one-command extract-and-merge; PO catalogs translators expect[4][5]. |
|
| Maximum ecosystem maturity; willing to wire types yourself | Augmentation + i18next-resources-for-ts[1][25]. |
|
Need canonical ICU tooling + verify completeness checks |
Full ICU message syntax plus the formatjs verify missing/extra-key checks[19]. |