Intl APIs.[4][9][7] Prefer a simpler interpolation syntax and CLDR plurals via suffixed JSON keys? i18next/react-i18next is that by default, with ICU available only through a small plugin.[3][2] typesafe-i18n uses its own compact plural/formatter syntax (no ICU).[15] Paraglide deliberately skips ICU for a MessageFormat-2.0-inspired variants model, with an optional ICU plugin.[11][13]
Every candidate resolves plurals through the same CLDR categories (zero/one/two/few/many/other) because they all delegate to Intl.PluralRules — the divergence is purely in how you author the message, and how well that syntax survives a round-trip through translators and a translation-management system (TMS).[18][19]
Comparison
| Library ⭐ | ICU MessageFormat | Plurals / ordinal | Gender / select | Intl number / date / currency / relative-time | Authoring model |
|---|---|---|---|---|---|
| react-intl (FormatJS) ⭐ 15k | ✓ native | ✓ plural + selectordinal, =0 exact, offset |
✓ select |
✓ Intl.NumberFormat/DateTimeFormat/PluralRules; number/date skeletons (::currency/EUR) |
ICU strings inline in defaultMessage |
| next-intl ⭐ 4.3k | ✓ native (wraps intl-messageformat) | ✓ plural + selectordinal, # marker |
✓ select (other required) |
✓ same FormatJS core; ::currency/USD, ::percent skeletons |
ICU strings in JSON messages |
| Lingui ⭐ 5.8k | ✓ native runtime; ICU is the catalog format | ✓ plural/selectOrdinal macros → ICU |
✓ select macro |
✓ i18n.number/date via Intl.* |
Build-time macros compile to ICU |
| react-i18next ⭐ 10k / i18next ⭐ 8.6k | △ plugin only (i18next-icu ⭐ 98) | ✓ default: suffixed keys (key_one) via Intl.PluralRules |
△ via context suffix, or ICU plugin select |
✓ Intl-backed format() interpolation |
Interpolation {{count}} + suffixed JSON keys |
| typesafe-i18n ⭐ 2.5k | ✗ (custom syntax) | ✓ {{sing|plur}} shorthand via Intl.PluralRules |
△ via multi-branch {{...}} switch |
✓ pipe formatters over Intl.* |
Custom compact syntax, fully typed |
| Paraglide ⭐ 567 | △ not native; MF2-inspired variants, optional ICU plugin | ✓ variants + plural declaration via Intl.PluralRules |
✓ variant selectors (any dimension) | ✓ built-in number/datetime/relativetime over Intl.* |
Compiled functions from JSON variants |
All sources: citations below. Stars fetched Jul 2026.
ICU-first: react-intl, next-intl, Lingui
These treat ICU MessageFormat as the source of truth — react-intl formats "strings, numbers, dates, and plurals using the ICU MessageFormat standard."[5] The whole plural/select/interpolation decision lives in one string, which is exactly what commercial TMSs (Crowdin, Phrase, Lokalise) parse and validate.[18][20] Canonical forms:
{count, plural, =0 {no items} one {# item} other {# items}}
{year, selectordinal, one {#st} two {#nd} few {#rd} other {#th}} birthday
{gender, select, female {She} male {He} other {They}} replied
The price is {price, number, ::currency/EUR}
react-intl and next-intl share the FormatJS intl-messageformat engine; next-intl is explicitly "a thin wrapper around battle-tested FormatJS building blocks."[10] Both support ICU number/date skeletons (the :: prefix) so currency and percent formatting stay inside the message and remain translator-adjustable.[4][10] Lingui is ICU under the hood but you rarely hand-write it: plural(), select() and <Trans> macros compile your JSX/TS to ICU at build time, and i18n.number()/i18n.date() pass straight through to Intl.NumberFormat/DateTimeFormat.[6][8]
Interpolation-first: i18next / react-i18next
The default i18next model is not ICU — it is interpolation-first with ICU available only opt-in, the core syntax split versus react-intl.[21] Interpolation uses {{count}}, and pluralization uses CLDR-named suffixed keys resolved by Intl.PluralRules:[3]
{ "item_one": "{{count}} item", "item_other": "{{count}} items" }
// Polish needs item_one / item_few / item_many / item_other
Tradeoffs vs ICU: suffixed keys are self-documenting and produce clean version-control diffs (one string per form), but the plural logic is spread across keys rather than co-located, and gender lives in a separate context suffix rather than a select.[3] If you want the ICU inline syntax you add the i18next-icu plugin ⭐ 98 (or the IcuTrans component / icu.macro); both resolve to the same CLDR categories.[2][1] Locale-aware number/date/currency formatting is available through Intl-backed format() interpolation without the ICU plugin.[17]
Custom syntax: typesafe-i18n
No ICU. Plurals use a compact inline shorthand — '{{zero|one|two|a few|many|a lot}} apple{{s}}' — selected via Intl.PluralRules, and value formatting is done with pipe formatters ({amount|currency}) over the Intl namespace.[16][15] The payoff is end-to-end TypeScript type-safety on keys and argument types; the cost is a bespoke format most TMS tooling does not natively parse, and a known rough edge combining formatters inside plural branches (workaround: a custom plural-aware formatter).[16]
MF2-inspired: Paraglide (inlang)
Paraglide compiles messages into tree-shakable functions[22] and models plurals/gender as variants with declared selectors, a design "inspired by the upcoming MessageFormat 2.0 draft" rather than ICU.[11][13] Plural category selection still uses Intl.PluralRules, and built-in formatters number / datetime / relativetime map onto Intl.NumberFormat / DateTimeFormat / RelativeTimeFormat.[12] Teams who need the familiar ICU string syntax (or want to keep existing i18next JSON) can swap in the ICU message-format plugin instead of the default inlang format.[11]
Guidance
- Complex grammatical languages (Arabic 6 forms, Polish/Russian 4, Slavic gender): all six select the correct CLDR category, so the real question is authoring ergonomics. ICU keeps every form of a sentence in one translatable unit — the safest for translators handling agreement and gender.[19]
- External translators / a TMS in the loop: favour ICU-native (react-intl, next-intl, Lingui) — ICU is the format professional localization tools understand and validate.[20] A custom or MF2-inspired format means fewer off-the-shelf integrations today.
- Developers author strings, few languages, simple plurals: i18next's suffixed keys or typesafe-i18n's shorthand are lighter to write and read; add ICU only if grammar demands it.[3]
- Relative-time (
2 days ago): only Paraglide ships a first-classrelativetimeformatter; elsewhere useIntl.RelativeTimeFormatas a custom formatter — ICU MessageFormat has no native relative-time argument.[12]
Citations
- react-i18next — Using with ICU format (official)
- i18next-icu plugin ⭐ 98 (official)
- Locize — i18n Pluralization: CLDR, i18next & ICU (2026) (vendor-blog)
- FormatJS — Intl MessageFormat (official)
- FormatJS — React Intl (official)
- Lingui — Macros reference (official)
- Lingui — ICU MessageFormat guide (official)
- Lingui — Core API (i18n.number / i18n.date) (official)
- next-intl — Messages (plural/select/rich text) (official)
- next-intl — Number formatting (skeletons) (official)
- Paraglide JS — Variants (plurals/gender) (official)
- Paraglide JS — Formatting (Intl formatters) (official)
- inlang Message Format plugin (MF2-inspired) (official)
- typesafe-i18n ⭐ 2.5k (official)
- typesafe-i18n — Using formatters in plural syntax (discussion) ⭐ 2.5k (forum)
- i18next — Formatting (Intl-backed format()) (official)
- Crowdin — ICU Message Format Guide (2026) (vendor-blog)
- SimpleLocalize — What is ICU message format (vendor-blog)
- Phrase — Practical Guide to the ICU Message Format (vendor-blog)
- IntlPull — react-i18next vs react-intl (2026) (vendor-blog)
- opral/paraglide-js ⭐ 567 (official)