React localization guide

React localization: a complete i18n guide

Compare the leading React i18n libraries, structure message catalogs, implement plurals and formatting, switch locales accessibly, and prepare localized pages for performance and search.

Why localization is an application concern in React

React is deliberately focused on composing user interfaces. It does not prescribe a message catalog, locale router, translation loader, or content workflow. That flexibility is useful, but it means localization architecture belongs to the application. If strings remain embedded in components until launch, the first new language exposes assumptions about grammar, formatting, layout, and data loading all at once.

Localization is more than replacing English words. A greeting may interpolate a name, a cart count may need several plural categories, and a price combines currency rules with regional formatting. Translators need complete messages and context; components need stable APIs and predictable fallbacks. Treating every text node as an independent lookup usually produces awkward copy and brittle code.

Public React pages add another constraint: users and crawlers should receive meaningful content at a stable locale URL. A browser-only language switch after hydration can flash the wrong language, duplicate URLs, and hide variants from search engines. Even when the product itself is a client application, its marketing and help surfaces benefit from static generation or server rendering through a React framework.

  • Hard-coded JSX mixes product copy with component behavior.
  • Concatenated fragments break grammar when word order changes.
  • Dates, numbers, currencies, and plurals follow locale-specific rules.
  • Client-only locale detection can cause flicker and weak crawlability.

Core concepts before choosing a library

Start with one source of truth for supported locales. Store the locale code, display name, text direction, and fallback relationship together. Route parsing, the language switcher, Intl formatters, and catalog loading should all consume that configuration. This prevents one component from accepting fr-CA while another silently falls back to fr or English under different rules.

Messages should represent complete ideas. Give them stable semantic keys such as checkout.payment.failed rather than keys copied from today's English sentence. Pass variables as named values, keep markup placeholders intentional, and let the message syntax select plurals or grammatical alternatives. This preserves translator context and allows another language to change word order without changing React code.

  • Locale: the validated language or regional preference active for a render.
  • Message catalog: stable message identifiers mapped to translated values.
  • Interpolation: runtime values inserted into a complete translated message.
  • Plural and select rules: grammatical variants chosen from locale-aware data.
  • Fallback: the deliberate behavior when a locale or message is unavailable.

react-i18next vs React Intl vs Lingui

The leading React libraries overlap on the basics but optimize for different workflows. Compare their message syntax, extraction pipeline, type-safety story, server support, and catalog ecosystem. Avoid choosing on download size alone: exact bundle measurements change with versions, plugins, tree shaking, and how many messages reach the browser.

react-i18next is the React binding for i18next. It favors keys and namespaces, with plugins for loading, detection, caching, and optional ICU. React Intl is FormatJS's React layer and uses ICU MessageFormat directly through components and hooks. Lingui also compiles ICU messages, adding macros and an integrated extraction and catalog compilation workflow.

Library React API style Message syntax Extraction workflow TypeScript Best fit
react-i18next useTranslation, t, and Trans i18next syntax; ICU via plugin i18next CLI and ecosystem tools Resource typing via configuration Key-based apps and the i18next ecosystem
React Intl IntlProvider, hooks, and Formatted components Native ICU MessageFormat FormatJS CLI extraction and compilation First-class definitions; descriptor typing ICU-first product copy and formatting
Lingui Macros, Trans, and hooks ICU through compiled macros Integrated extract and compile CLI Typed APIs; extraction validates catalogs Compile-time workflow and PO catalogs

Choose react-i18next when you value i18next's broad ecosystem, need namespaces across a large product, or already share catalogs with non-React JavaScript. Choose React Intl when ICU messages and consistent FormatJS number/date formatting match the team's mental model. Choose Lingui when developers want concise macros and compile-time processing with a PO-first translation workflow.

TypeScript support requires more than a typed t function. Validate that keys can be derived from your actual default catalog, that interpolation variables are checked, and that extraction catches messages before release. i18next can generate or augment resource types, FormatJS extracts statically analyzable descriptors, and Lingui compiles extracted messages. The best workflow is the one your CI can enforce consistently.

Set up React localization step by step

This setup uses react-i18next because its key-based JSON model is familiar to many React teams. The same sequence applies to other libraries: define catalogs, initialize one locale-aware instance, provide it at the application boundary, translate complete messages in components, and make locale switching update both the visible UI and the application's URL or persisted preference.

  1. Step 1

    Structure the catalogs

    Create one source catalog and matching locale files. Group keys by stable product domains such as navigation, checkout, and validation. Keep every locale's key set synchronized and include translator context for ambiguous messages.

  2. Step 2

    Initialize the i18n instance

    Create the i18next instance in a dedicated module, register only the plugins the application needs, define supported and fallback locales, and keep development diagnostics separate from production behavior.

  3. Step 3

    Provide localization at the root

    Make the initialized instance available at the application root. In frameworks with server rendering, follow the framework integration so the active locale and resources are consistent between server output and hydration.

  4. Step 4

    Translate complete component messages

    Call useTranslation for the relevant namespace and pass interpolation values as named data. Use Trans only when a cohesive message contains React elements, not to assemble arbitrary fragments.

  5. Step 5

    Build an accessible language switcher

    Change the active library locale and navigate to the matching locale URL when routes are localized. Persist a preference only as a convenience; do not make it the sole source of truth for public pages.

A small namespaced i18next catalog
{
  "navigation": {
    "home": "Home"
  },
  "cart": {
    "items_one": "{{count}} item",
    "items_other": "{{count}} items"
  }
}
Translate a component and change locale
import {useTranslation} from 'react-i18next';

export function CartSummary({count}: {count: number}) {
  const {t, i18n} = useTranslation('common');

  return (
    <section>
      <h2>{t('cart.title')}</h2>
      <p>{t('cart.items', {count})}</p>
      <button onClick={() => i18n.changeLanguage('fr')}>
        {t('language.french')}
      </button>
    </section>
  );
}

Initialize i18next before the first user-visible render. In a client-only application, show a deliberate loading state while the active catalog arrives rather than raw keys. In SSR, create or obtain an instance scoped safely for the request, load the resolved locale, render with it, and serialize only the resources the client needs to hydrate the same tree.

A language switcher should expose readable language names, identify the current choice, and preserve the user's equivalent route. For public localized pages, anchors are preferable because each locale has a real URL. A select or menu may enhance the interaction, but keyboard access, focus behavior, and a no-JavaScript navigation path should remain intact.

Plurals, dates, numbers, and RTL

Do not translate preformatted values. Keep a date as a Date or timestamp, a price as a number plus currency code, and a list as an array until rendering. Intl.DateTimeFormat, Intl.NumberFormat, Intl.RelativeTimeFormat, and Intl.ListFormat apply locale-specific conventions without shipping a separate formatting engine. Most i18n libraries wrap these APIs so messages and values use one active locale.

Pluralization is grammatical selection, not the condition count === 1. i18next uses Intl.PluralRules categories and current JSON v4 suffixes such as _one and _other; some languages also require zero, two, few, or many. React Intl and Lingui express the alternatives in ICU messages. Always pass count as data and keep the full sentence in the catalog.

For right-to-left languages, set dir on the document root when the active script requires it and favor logical CSS properties such as margin-inline-start. Review directional icons, charts, and mixed-language input rather than mechanically mirroring everything. Set html lang separately for pronunciation and language-aware behavior; language and text direction are related configuration values, not substitutes for each other.

Format values with the active locale
export function LocalizedPrice({locale, value}: Props) {
  const currency = new Intl.NumberFormat(locale, {
    style: 'currency',
    currency: 'EUR'
  });
  const date = new Intl.DateTimeFormat(locale, {
    dateStyle: 'long'
  });

  return <p>{currency.format(value)} · {date.format(new Date())}</p>;
}

Performance and multilingual SEO

A catalog for every language can become one of the largest application assets if it is imported eagerly. Split catalogs by locale first and by route or namespace when the product is large enough to benefit. Load the active catalog before rendering critical UI, cache immutable versioned assets, and avoid triggering a second request for messages that the server already used to produce the page.

For public content, prefer a React framework that can emit localized HTML through static generation or server rendering. Hydration should preserve the same locale and messages that produced the server output. If the client redetects language independently, it can cause content flicker and hydration mismatches. Pass the resolved locale through the render boundary instead of guessing twice.

Multilingual SEO depends on discoverable alternatives. Publish a stable URL per language, localize the title and description, emit a self-referencing canonical, and connect equivalent pages with reciprocal hreflang values. Search crawlers may not send a useful Accept-Language header, so never make browser detection the only way to reach translations. A visible linked language switcher helps people and crawlers alike.

  • Load only the active locale and the namespaces needed by the route.
  • Render the selected locale on the server or at build time for public pages.
  • Preload critical messages to prevent a flash of keys or fallback language.
  • Give every localized page a distinct URL, canonical, and reciprocal hreflang links.
  • Keep the language switcher as ordinary links and preserve the equivalent route.

Build a translation workflow that scales

Catalog quality is a software quality concern. Review source messages before translating them, give each message enough context, and keep placeholders stable. Automated checks should catch missing keys, malformed ICU expressions, incompatible interpolation variables, and catalogs that drift out of sync. These failures are cheaper to fix in a pull request than after a locale ships.

Pseudo-localization is one of the highest-value early tests. Expand characters, add visible markers, and simulate right-to-left direction to reveal layouts that depend on English length or order. Pair it with representative snapshots or end-to-end flows for checkout, authentication, validation, and other critical paths. Native-speaker review still matters, but engineering can remove many structural defects before that review begins.

  • Use semantic keys and document the context a translator cannot infer.
  • Never concatenate translated fragments or reuse a message in unrelated contexts.
  • Run key-parity, syntax, interpolation, and unused-message checks before release.
  • Pseudo-localize to expose clipping, hard-coded copy, and bidirectional layout bugs early.
  • Define ownership for source copy, translation review, and production delivery.

Where Translatable fits

The React library handles runtime concerns: selecting messages, interpolating values, and updating components. A localization platform handles the workflow around those messages: maintaining key parity, inviting reviewers, translating at scale, recording changes, and moving approved catalogs into the application. Keeping those responsibilities separate protects the component layer from vendor lock-in.

Translatable works with JSON catalogs and developer workflows. Import existing locale files, manage values in a focused interface, use AI assistance for drafts or bulk translation, synchronize changes through Git providers, and optionally deliver catalog updates through a CDN. React remains connected through the i18n library the team already chose.

Adopt workflow automation gradually. A small team can begin with version-controlled exports and review every change in a pull request. As release cadence increases, add automated synchronization or CDN delivery while preserving validation and a bundled fallback. The result is faster copy iteration without making application rendering depend on an unavailable third party.

Frequently asked questions

What is the best i18n library for React?

There is no universal best library. react-i18next is a strong default for teams that want the large i18next ecosystem and key-based namespaces. React Intl fits teams that prefer a direct ICU MessageFormat API. Lingui is compelling when compile-time macros and an integrated extract-and-compile workflow matter. Choose using your message complexity, extraction needs, server architecture, and existing ecosystem.

What is the difference between react-i18next and React Intl?

react-i18next uses i18next's key and namespace model, plugin ecosystem, and plural rules, with ICU available through a plugin. React Intl is ICU-first and provides declarative components plus imperative formatting through the Intl API. Both are mature choices; the better fit depends on catalog format, translator workflow, and whether the wider application already uses i18next.

How should React applications handle plurals?

Pass the numeric count into one message and let the library apply the locale's plural categories. Current i18next JSON v4 catalogs use suffixes such as _one and _other; ICU-based libraries use a plural expression with categories inside the message. Never choose English singular or plural in component code because many languages have additional forms.

Do I need a translation management platform?

A small project can manage JSON or PO files directly in Git. A platform becomes useful when several locales, frequent copy changes, non-developer reviewers, AI or human translation, audit history, and delivery automation make manual file coordination expensive. Keep the runtime library independent so adopting a platform does not force a component rewrite.

How do I make a localized React app SEO-friendly?

Give each language a stable URL, render meaningful localized HTML before or during the initial response, localize metadata, and connect equivalents with canonical and hreflang tags. Purely client-rendered text can be indexed inconsistently and often creates a visible language flash. Use a framework with static generation or server rendering for public pages when search traffic matters.

Build a localization workflow your team can maintain

Bring your JSON catalogs together, translate with AI assistance, and deliver updates through Git or a global CDN.

Get started free