Best Internationalization (i18n) for React 2026
TL;DR
next-intl for Next.js; react-i18next for universal React; FormatJS (react-intl) for enterprise. next-intl (~1.5M weekly downloads) is the go-to for Next.js App Router — server component support, automatic locale detection, type-safe message keys. react-i18next (~8M downloads) is the most widely used, works with any React setup. react-intl (~4M) from FormatJS is the heavyweight with CLDR-compliant formatting.
Key Takeaways
- react-i18next: ~8M weekly downloads — universal, plugin-based, JSON namespaces
- react-intl (FormatJS): ~4M downloads — ICU message format, CLDR compliance
- next-intl: ~1.5M downloads — Next.js App Router, RSC, type-safe keys
- Lingui — compile-time i18n, smallest bundle, JSX macros
- All four — pluralization, date/number formatting, locale detection
Why i18n Is Harder Than It Looks
Adding internationalization to an existing application is one of the most expensive retroactive changes you can make. Every string in your application needs a translation key. Date formats vary by locale (MM/DD/YYYY vs DD.MM.YYYY vs YYYY-MM-DD). Currency formatting varies ($ vs € vs ¥, different decimal separators). Plural rules vary dramatically — English has two forms (1 item, 2 items), but Polish has four.
The time to add i18n is when you start the project, even if you initially only ship in English. Adding i18n scaffolding early costs 1-2 days. Retrofitting it into a large application can take weeks.
The choice of i18n library also affects your bundle size and performance. Libraries range from ~15KB (react-i18next) to ~45KB (react-intl) gzipped. For SPAs targeting global users, this matters.
next-intl (Next.js App Router)
// next-intl — setup for Next.js App Router
// messages/en.json
{
"HomePage": {
"title": "Welcome to {name}",
"description": "You have {count, plural, one {# message} other {# messages}}",
"nav": {
"home": "Home",
"about": "About",
"pricing": "Pricing"
}
},
"Common": {
"error": "Something went wrong",
"loading": "Loading..."
}
}
// i18n.ts — next-intl configuration
import { getRequestConfig } from 'next-intl/server';
export default getRequestConfig(async ({ locale }) => ({
messages: (await import(`./messages/${locale}.json`)).default,
}));
// middleware.ts — locale routing
import createMiddleware from 'next-intl/middleware';
export default createMiddleware({
locales: ['en', 'fr', 'de', 'ja', 'es'],
defaultLocale: 'en',
// URL pattern: /en/about, /fr/a-propos
});
export const config = { matcher: ['/((?!api|_next|_vercel|.*\\..*).*)'] };
// app/[locale]/page.tsx — Server Component usage
import { useTranslations } from 'next-intl';
export default function HomePage() {
const t = useTranslations('HomePage');
return (
<div>
{/* String interpolation */}
<h1>{t('title', { name: 'PkgPulse' })}</h1>
{/* Pluralization */}
<p>{t('description', { count: 42 })}</p>
{/* "You have 42 messages" */}
</div>
);
}
// next-intl — type-safe keys (no typos!)
// Generate types: npx next-intl generate-types
import { useTranslations } from 'next-intl';
function Component() {
const t = useTranslations('HomePage');
t('title'); // ✅ Valid key
t('tiitle'); // ❌ TypeScript error: "tiitle" not in messages
t('title', { name: 'Foo' }); // ✅ Correct params
t('title', { wrong: 'Foo' }); // ❌ TypeScript error: expected 'name'
}
next-intl's type-safe keys are a major quality-of-life improvement. Typos in translation keys are a class of bugs that only appear at runtime in other libraries — with next-intl's generated types, they're caught at compile time.
react-i18next (Universal)
// react-i18next — flexible, works anywhere
import i18n from 'i18next';
import { initReactI18next } from 'react-i18next';
import LanguageDetector from 'i18next-browser-languagedetector';
import Backend from 'i18next-http-backend'; // Load translations from server
await i18n
.use(Backend)
.use(LanguageDetector)
.use(initReactI18next)
.init({
fallbackLng: 'en',
debug: process.env.NODE_ENV === 'development',
interpolation: { escapeValue: false }, // React handles XSS
backend: {
loadPath: '/locales/{{lng}}/{{ns}}.json',
},
ns: ['common', 'home', 'auth'],
defaultNS: 'common',
});
// react-i18next — hooks and components
import { useTranslation, Trans } from 'react-i18next';
// en/home.json
// { "welcome": "Welcome, {{name}}!", "count": "{{count}} package", "count_plural": "{{count}} packages" }
function HomePage({ user }) {
const { t, i18n } = useTranslation('home');
return (
<div>
{/* Simple interpolation */}
<h1>{t('welcome', { name: user.name })}</h1>
{/* Auto pluralization */}
<p>{t('count', { count: 42 })}</p>
{/* "42 packages" */}
{/* Rich text with components */}
<Trans i18nKey="terms">
By using this service, you agree to our
<a href="/terms">Terms of Service</a>
and
<a href="/privacy">Privacy Policy</a>.
</Trans>
{/* Language switcher */}
<select
value={i18n.language}
onChange={(e) => i18n.changeLanguage(e.target.value)}
>
<option value="en">English</option>
<option value="fr">Français</option>
<option value="de">Deutsch</option>
</select>
</div>
);
}
react-i18next's plugin system is its key strength. The i18next-http-backend plugin loads translations lazily from your server — users only download the locale they need. The i18next-browser-languagedetector automatically detects the user's language from browser settings, cookies, or URL. The i18next-locize-backend integrates directly with the Locize translation management platform.
FormatJS / react-intl (Enterprise)
// react-intl — ICU message format, CLDR compliance
import { IntlProvider, FormattedMessage, FormattedDate, FormattedNumber, useIntl } from 'react-intl';
function App({ locale, messages }) {
return (
<IntlProvider locale={locale} messages={messages} defaultLocale="en">
<Dashboard />
</IntlProvider>
);
}
function Dashboard() {
const intl = useIntl();
return (
<div>
{/* ICU Message Format — powerful pluralization + select */}
<FormattedMessage
id="download.count"
defaultMessage="{count, plural, =0 {No downloads} one {# download} other {# downloads}}"
values={{ count: 1500000 }}
/>
{/* "1,500,000 downloads" */}
{/* Date formatting (CLDR) */}
<FormattedDate
value={new Date()}
year="numeric" month="long" day="2-digit"
/>
{/* "March 08, 2026" (en) or "8 mars 2026" (fr) */}
{/* Number/currency */}
<FormattedNumber value={99.99} style="currency" currency="USD" />
{/* "$99.99" (en) or "99,99 $" (fr-CA) */}
{/* Relative time */}
{intl.formatRelativeTime(-3, 'hour')}
{/* "3 hours ago" */}
</div>
);
}
FormatJS uses the ICU (International Components for Unicode) message format — the same format used by Java, Swift, Android, and other platforms. If your team also ships iOS and Android apps, using ICU everywhere means the same translation strings work across all platforms. Translators learn one format.
Pluralization Complexity
Pluralization rules are why i18n is harder than just replacing string literals. English is simple (1 form vs many), but other languages are not:
English: 1 item, 2 items (2 forms)
German: 1 Element, 2 Elemente (2 forms)
French: 1 article, 2 articles (2 forms, but 0 is singular)
Russian: 1 файл, 2 файла, 5 файлов (3 forms based on last digit)
Polish: 1 plik, 2 pliki, 5 plików, 22 pliki (4 forms!)
Arabic: 6 forms total
All four major libraries handle this correctly. They use Unicode CLDR plural rules under the hood, which are maintained for 200+ locales. The API differences are in how you express plural rules in your translation strings.
Translation Workflow Tools
The i18n library handles runtime translation loading. A separate category of tools manages the translation workflow (sending strings to translators, getting translations back):
- Lokalise — popular SaaS, integrates with GitHub, Slack, Figma
- Phrase — enterprise-focused, ICU message format support
- Crowdin — open-source friendly, free for public projects
- Locize — built by the i18next team, native integration with react-i18next
These tools let non-developer translators work on translations without touching code. They provide context screenshots, translation memory (reuse similar translations), and review workflows.
When to Choose
| Scenario | Pick |
|---|---|
| Next.js App Router | next-intl |
| Universal React (CRA, Vite, Remix) | react-i18next |
| Enterprise, ICU message format | react-intl (FormatJS) |
| Smallest bundle (compile-time) | Lingui |
| Many languages, type-safe keys | next-intl |
| CMS-managed translations | react-i18next (backend loader) |
Compare i18n library package health on PkgPulse. Also see how to set up a modern React project for the full React setup guide and best form libraries for React for handling localized form validation.
See the live comparison
View react i18next vs. next intl on PkgPulse →