Best Internationalization (i18n) Libraries for React in 2026
·PkgPulse Team
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
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'
}
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>
);
}
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>
);
}
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.
See the live comparison
View react i18next vs. next intl on PkgPulse →