Skip to main content

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

ScenarioPick
Next.js App Routernext-intl
Universal React (CRA, Vite, Remix)react-i18next
Enterprise, ICU message formatreact-intl (FormatJS)
Smallest bundle (compile-time)Lingui
Many languages, type-safe keysnext-intl
CMS-managed translationsreact-i18next (backend loader)

Compare i18n library package health on PkgPulse.

Comments

Stay Updated

Get the latest package insights, npm trends, and tooling tips delivered to your inbox.