Decision. The runtime dictates the choice more than features do.
- Next.js App Router / RSC → next-intl (purpose-built,
getTranslationsin async Server Components, nouse client) [4] or Lingui (sameTrans/useLinguiin server + client) [8].- Vite SPA, smallest bundle → Paraglide — compile-time functions, tree-shaken per message, ~1 KB/locale vs i18next’s 205–422 KB [7][14].
- Vite SPA, maturity + ecosystem → react-i18next — largest plugin ecosystem, but a runtime-dictionary library that ships every message in a namespace [1].
- Rich ICU (plurals/dates/currency), any runtime → react-intl/FormatJS — most complete
Intlsupport, but the heaviest (~20 KB+) and RSC needsuse client[1][10].
The 2026 field splits cleanly into two architectures. Runtime-dictionary libraries (react-i18next, react-intl) load a JSON/ICU catalog and resolve keys through a lookup layer at runtime — flexible, ecosystem-rich, but the bundle scales with the catalog and RSC needs workarounds. Compile-time libraries (Paraglide, Lingui, typesafe-i18n) emit typed functions ahead of time, so unused messages tree-shake away and Server Components call plain functions with no React context [1][7]. React 19 itself is a non-issue — all six work with it; the real differentiators are RSC, bundle, and lazy-loading.
react-i18next / i18next
react-i18next ⭐ 10k [18] (core i18next ⭐ 8.6k) is the default runtime-dictionary choice and the largest ecosystem. Client bundle is ~6 KB gzipped, plus every message in the loaded namespace [1]. SSR is mature and documented for both Node and static export [13].
The friction is RSC: useTranslation depends on React context, which Server Components don’t have, so App Router usage requires the createInstance pattern (or the next-i18next v16 wrapper offering getT() for server + useT() for client) [1][2]. ⚠ For new App Router projects this is a shim, not a native fit [3]. Lazy-loading works via i18next-http-backend or manual dynamic import() of locale JSON — effective but you own the discipline of namespace splitting [1][11]. Best fit: Vite SPA and Pages Router.
react-intl / FormatJS
react-intl ⭐ 15k [10] has the most complete ICU MessageFormat and Intl support (plurals, dates, numbers, currency, relative time) and is the highest-starred repo here. That completeness costs bundle: ~20 KB+ gzipped with the full ICU runtime, the heaviest option, and it grows with ICU complexity [1][15].
RSC support is limited: the hook-based API needs use client, though @formatjs/intl is usable server-side [1]. It works well with Vite; lazy-loading is manual — dynamic-import the active locale’s messages and pass them straight to IntlProvider, so the initial bundle carries one locale and others load as chunks [11]. Best fit: apps that genuinely need rich ICU formatting and can absorb the weight.
Lingui
Lingui ⭐ 5.8k [9] is a compile-time library with a runtime around ~3 KB; a macro extracts messages so unused ones tree-shake [1]. Its standout property for 2026: the same Trans and useLingui work identically in Server and Client Components. On the server useLingui is a plain function reading a per-request I18n instance from React cache, not a hook [8].
RSC setup has sharp edges: configure the linguiMacroSwcPlugin under experimental.swcPlugins, and ⚠ call setI18n() in every page and layout (not just the root) because of how the App Router preserves state; static rendering needs generateStaticParams for all locales [8]. Framework-agnostic, so it also fits a Vite SPA. Best fit: teams wanting one API across RSC + client, willing to wire the macro.
next-intl
next-intl ⭐ 4.3k [5] is purpose-built for the Next.js App Router and the cleanest RSC story: async Server Components use the awaitable getTranslations, Client Components use the useTranslations hook, with a request-scoped config object providing per-request locale — no use client needed for server translations [4]. Bundle is ~4 KB client, and a server-only path with ahead-of-time compilation drops to ~457 B [1]. Messages load per-locale and can come from local files or a remote source [4].
The catch is scope: ⚠ it targets Next.js and is not designed for a standalone Vite SPA — the docs make no mention of non-Next.js usage [1][4]. One operational note: the file-based default assumes locale files live in the repo, so translation updates ride code deployments unless you wire a remote source [3]. Best fit: Next.js App Router — the reference choice.
Paraglide
Paraglide (opral/paraglide-js ⭐ 567) [6] is the most aggressive compile-time approach: it compiles each message into a typed ESM function before ship, so Vite tree-shakes unused translations, keys autocomplete, and components call functions (welcome_message()) instead of resolving strings through a runtime layer [7]. Because messages are pure functions with no context, RSC and SSR “just work” [1].
Bundle is the headline: the benchmark ships 47–144 KB where i18next ships 205–422 KB, and — critically — Paraglide’s size stays flat (47 KB) as the catalog grows 200→1000 messages while i18next climbs to 414 KB, because only used messages ship [7]. A real migration reported dropping ~40 KB of i18next to ~2 KB, with the only downside being TypeScript type-checking lagging when adding many keys quickly [14]. Works across React, Next.js, SvelteKit and Vite [1]. Best fit: Vite SPA where bundle size is the priority.
typesafe-i18n
typesafe-i18n (codingcommons/typesafe-i18n ⭐ 2.5k) [12] is codegen-first: a generator emits plain TypeScript functions with parameter-level type checking, so the shipped runtime is ~1 KB, RSC-safe (no context), and lazy-loads via generated per-locale imports [1]. ⚠ Momentum is the concern — the last major release was 2023 and it’s effectively single-maintainer, so the community and Stack Overflow corpus are thin versus react-i18next [12][19]. Best fit: TypeScript Vite SPA teams valuing codegen + strict param checking, accepting slower upstream.
Comparison
| Library | ⭐ Stars | React 19 | RSC-native | SSR | Lazy-load locales | Client bundle |
|---|---|---|---|---|---|---|
| react-i18next | ⭐ 10k | ✓ | ⚠ createInstance/wrapper |
✓ | ✓ manual (http-backend / import()) |
~6 KB + full namespace [1] |
| react-intl | ⭐ 15k | ✓ | ⚠ needs use client |
✓ | ⚠ manual dynamic import | ~20 KB+ (ICU runtime) [1] |
| Lingui | ⭐ 5.8k | ✓ | ✓ (React cache) |
✓ | ✓ compiled catalogs | ~3 KB + catalog [1][8] |
| next-intl | ⭐ 4.3k | ✓ | ✓ getTranslations |
✓ | ✓ per-locale (AOT server) | ~4 KB / 457 B server-only [1] |
| Paraglide | ⭐ 567 | ✓ | ✓ pure functions | ✓ | ✓ compile-time tree-shake | ~1 KB/locale; 47–144 KB total [7] |
| typesafe-i18n | ⭐ 2.5k | ✓ | ✓ plain TS functions | ✓ | ✓ codegen import | ~1 KB + codegen [1] |
Notes: RSC-native ✓ = usable in Server Components without use client; ⚠ = works but needs a workaround. In the App Router, RSC output is final HTML with no hydration step for server-rendered strings, so server-resolved translations ship zero translation JS — the reason RSC-native libraries win on payload [3][16].
Guidance by runtime
Vite SPA. No RSC, so the axis is bundle + DX. Everything is client-side and hydrates normally. Paraglide gives the smallest, catalog-independent bundle via compile-time tree-shaking [7]; react-i18next gives the deepest ecosystem if you’ll manage namespace splitting yourself [1]; typesafe-i18n if you want codegen type-safety. Vite handles the dynamic-import() locale-chunk pattern natively, so lazy-loading works with all of them [11]. Skip next-intl — it isn’t built for standalone Vite [4].
Next.js App Router. RSC is the default in Next.js 16 and the production-standard architecture, so pick an RSC-native library to avoid use client bloat and ship translated HTML with no client translation payload [3]. next-intl is the reference choice (async getTranslations, request-scoped config, server-only 457 B path) [4]. Lingui is the alternative when you want one identical API across server + client and are fine wiring the SWC macro and per-layout setI18n() [8]. Reserve react-i18next/next-i18next for migrating an existing Pages Router app, not greenfield [2][3]. If you run Next.js as a pure client SPA (no server), the RSC advantage disappears and the Vite guidance applies [17].