Atlas survey

Message format, pluralization & ICU across React i18n libraries (2026)

ICU-first (react-intl, next-intl, Lingui) vs interpolation-first (i18next default, typesafe-i18n) vs MF2-inspired (Paraglide) — how each authors plurals, gender and Intl-formatted numbers/dates.

21 sources ~6 min read #9 react · i18n · icu · pluralization · messageformat · intl
Decision. Want ICU MessageFormat inline in your strings (the localization-industry lingua franca every TMS speaks)? Use react-intl, next-intl, or Lingui — all three parse ICU natively and format numbers/dates via the platform 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-class relativetime formatter; elsewhere use Intl.RelativeTimeFormat as a custom formatter — ICU MessageFormat has no native relative-time argument.[12]

Citations

  1. react-i18next — Using with ICU format (official)
  2. i18next-icu plugin ⭐ 98 (official)
  3. Locize — i18n Pluralization: CLDR, i18next & ICU (2026) (vendor-blog)
  4. FormatJS — Intl MessageFormat (official)
  5. FormatJS — React Intl (official)
  6. Lingui — Macros reference (official)
  7. Lingui — ICU MessageFormat guide (official)
  8. Lingui — Core API (i18n.number / i18n.date) (official)
  9. next-intl — Messages (plural/select/rich text) (official)
  10. next-intl — Number formatting (skeletons) (official)
  11. Paraglide JS — Variants (plurals/gender) (official)
  12. Paraglide JS — Formatting (Intl formatters) (official)
  13. inlang Message Format plugin (MF2-inspired) (official)
  14. typesafe-i18n ⭐ 2.5k (official)
  15. typesafe-i18n — Using formatters in plural syntax (discussion) ⭐ 2.5k (forum)
  16. i18next — Formatting (Intl-backed format()) (official)
  17. Crowdin — ICU Message Format Guide (2026) (vendor-blog)
  18. SimpleLocalize — What is ICU message format (vendor-blog)
  19. Phrase — Practical Guide to the ICU Message Format (vendor-blog)
  20. IntlPull — react-i18next vs react-intl (2026) (vendor-blog)
  21. opral/paraglide-js ⭐ 567 (official)

Citations · 21 sources

Click the Citations tab to load…