
Table of content
i18next is the internationalization framework most JavaScript teams reach for when an application needs to ship in more than one language, and react-i18next connects it to React through a small set of hooks and components. This guide walks through a complete setup for a real React project: installing the libraries, structuring translation files in the i18next JSON format, translating components with the useTranslation hook, handling plurals and interpolation, and switching languages at runtime. It closes with the workflow practices that keep translations manageable once translators, reviewers, and product teams get involved.
The examples use TypeScript and assume an existing React application. Everything shown here works the same way in a plain JavaScript codebase.
What is i18next?
i18next is an open source internationalization framework for JavaScript. At its core, it loads translation resources, resolves a key like checkout.title to the right string for the active language, and applies language rules such as plural forms along the way. The official i18next documentation describes it as a full ecosystem rather than a single library: plugins add language detection, resource loading, and bindings for frameworks like React.
The core library is framework-agnostic. It runs in the browser, in Node.js, and in native environments, which means the same translation files and the same key structure can serve a React frontend and a backend service. For React projects, react-i18next connects that core to your components. The two libraries split the work like this:
i18next | react-i18next | |
Role | Core internationalization framework | React bindings for i18next |
Environment | Any JavaScript runtime | React applications |
Provides | Translation engine, plurals, interpolation, plugin system | useTranslation hook, language switching, Suspense integration |
Typical install |
|
|
In practice you always install both for a React project. i18next does the translating; react-i18next makes translations reactive and re-renders components when the language changes.
Installing and configuring i18next in a React project
A production i18next React setup usually adds two plugins on top of the core libraries: i18next-http-backend loads translation files on demand instead of bundling them into your JavaScript, and i18next-browser-languagedetector picks an initial language from the browser.
npm install i18next react-i18next
i18next-http-backend i18next-browser-languagedetector
Create a dedicated configuration module, for example src/i18n.ts:
import i18n from "i18next";
import { initReactI18next } from "react-i18next";
import HttpBackend from "i18next-http-backend";
import LanguageDetector from "i18next-browser-languagedetector";
i18n
.use(HttpBackend)
.use(LanguageDetector)
.use(initReactI18next)
.init({
fallbackLng: "en",
ns: ["common"],
defaultNS: "common",
interpolation: {
escapeValue: false, // React already escapes rendered values
},
backend: {
loadPath: "/locales/{{lng}}/{{ns}}.json",
},
});
export default i18n;
fallbackLng defines the language i18next serves when a translation is missing in the active language; users see English instead of a raw key. ns and defaultNS declare the namespaces the app uses, which we will cover in the next section. loadPath tells the backend where to fetch JSON files, with {{lng}} and {{ns}} filled in at request time.
Import the module once in your entry point, before the app renders. Since translations now load over HTTP, wrap the app in Suspense so React has something to show while the first files arrive:
import { StrictMode, Suspense } from "react";
import { createRoot } from "react-dom/client";
import App from "./App";
import "./i18n";
createRoot(document.getElementById("root")!).render(
<StrictMode>
<Suspense fallback={<p>Loading</p>}>
<App />
</Suspense>
</StrictMode>
);
With this in place, i18next initializes on startup, detects the user’s language, and fetches the matching JSON files from your public folder.
Structuring translation files with the i18next JSON format
i18next reads translations from plain JSON files, organized by language and namespace. A namespace is simply a named file that groups related keys, and splitting by feature keeps files small enough to review and lazy-load:
public/
locales/
en/
common.json
checkout.json
de/
common.json
checkout.json
The file contents follow the i18next JSON format, currently at version 4. The format looks like ordinary nested JSON, but specific key suffixes and placeholder patterns carry meaning:
{
"header": {
"title": "Your account",
"greeting": "Hello, {{name}}!"
},
"cartItem_one": "{{count}} item in your cart",
"cartItem_other": "{{count}} items in your cart"
}
The table below summarizes the JSON v4 capabilities you will use most:
Feature | Syntax | What it does |
Nested keys |
| Groups keys hierarchically; accessed as |
Interpolation |
| Inserts runtime values into the string |
Plural suffixes |
| Selects the correct form based on a |
Context |
| Switches strings based on a context you pass |
Nesting references |
| Reuses another translation inside a string |
Two structural decisions pay off later. First, keep every language file shaped identically: same keys, same nesting, same namespaces. Second, treat these JSON files as the single source of truth that flows between your repository and your translation tooling. LingoHub imports and exports this exact format, so a clean, consistent v4 structure means translators always work against the current state of the app.
Fallback languages deserve a brief note. fallbackLng accepts more than a single string: with fallbackLng: { "de-AT": ["de", "en"], default: ["en"] }, an Austrian German user falls back to German before English. Regional variants stay thin this way, holding only the keys that genuinely differ.
How to use react-i18next in components
With configuration and files in place, translating a component takes one hook. useTranslation returns the t function bound to the active language and re-renders the component when the language changes:
import { useTranslation } from "react-i18next";
function Header({ userName }: { userName: string }) {
const { t } = useTranslation();
return (
<header>
<h1>{t("header.title")}</h1>
<p>{t("header.greeting", { name: userName })}</p>
</header>
);
}
The second argument to t supplies interpolation values: {{name}} in the JSON file receives userName at render time. To read from a specific namespace, pass its name to the hook, as in useTranslation("checkout"). The hook accepts several more options, including loading multiple namespaces and overriding key prefixes; the react-i18next useTranslation docs list the full set.
React i18next plural handling with JSON v4 suffixes
Pluralization means changing the string form depending on a number: 1 item versus 2 items. i18next resolves plurals through the Intl.PluralRules API, which knows the correct set of forms for each language. In your JSON files, you define one key per plural form using suffixes, and in code you call the base key with a count value:
{
"cartItem_one": "{{count}} item in your cart",
"cartItem_other": "{{count}} items in your cart"
}
t("cartItem", { count: 1 }); // "1 item in your cart"
t("cartItem", { count: 4 }); // "4 items in your cart"
English needs only _one and _other, but many languages use additional categories. Polish, for example, distinguishes _few and _many as well. The Polish translation file carries four forms of the same key:
{
"cartItem_one": "{{count}} przedmiot w koszyku",
"cartItem_few": "{{count}} przedmioty w koszyku",
"cartItem_many": "{{count}} przedmiotów w koszyku",
"cartItem_other": "{{count}} przedmiotu w koszyku"
}
Your translators add those keys in their language files; the code stays untouched, and the same t("cartItem", { count }) call picks the right form in every language. The most common mistake in react i18next plural setups is passing the number as a pre-formatted string or under a different variable name: the value must arrive as count, as a number, or i18next cannot select a form.
Language switching and detection
The browser language detector sets a sensible initial language by checking, in order, sources such as the query string, localStorage, and the browser’s language settings. For manual switching, react-i18next exposes the i18n instance from the same hook:
import { useTranslation } from "react-i18next";
const languages = [
{ code: "en", label: "English" },
{ code: "de", label: "Deutsch" },
];
function LanguageSwitcher() {
const { i18n } = useTranslation();
return (
<select
value={i18n.resolvedLanguage}
onChange={(event) => i18n.changeLanguage(event.target.value)}
>
{languages.map((language) => (
<option key={language.code} value={language.code}>
{language.label}
</option>
))}
</select>
);
}
changeLanguage does three things: it loads any missing namespace files for the new language, writes the choice into the detector’s cache for the next visit, and re-renders every component that uses useTranslation. Note the use of i18n.resolvedLanguage for the select value: it reflects the language actually being served, which is safer for UI state than the raw detected value.
Best practices for production i18next setups
Getting i18next running is the quick part. Keeping hundreds of keys consistent across several languages and a dozen contributors takes more care, and a few habits prevent most of the damage:
Name keys by meaning, not by content.
checkout.submitButtonsurvives a copy change;clickToBuyNowdoes not.Split namespaces along feature boundaries. One namespace per major feature area keeps files reviewable and lets routes load only what they render.
Never build sentences by concatenating translated fragments. Word order varies across languages; keep each sentence as one key and use interpolation for the dynamic parts.
Turn on
debug: truein development. i18next then logs every missing key to the console, where it gets noticed and fixed early.Guard placeholder integrity. A translator who accidentally renames
{{count}}to{{Anzahl}}breaks the string at runtime. LingoHub’s quality checks catch this class of error automatically before it reaches a release, along with missing translations and inconsistent terminology.Keep all language files structurally identical. Diff-friendly, consistently ordered JSON makes both code review and machine processing reliable. LingoHub takes care of this alignment for you: every key in the source language is tracked across all target language files, so exports always share the same structure.
For TypeScript teams, react-i18next can also type-check translation keys themselves through a small type augmentation, giving autocomplete for every key and compile-time errors for typos. The react-i18next documentation describes the setup under its TypeScript section.
Common i18next mistakes
A few problems show up in almost every i18next codebase. They stay invisible in the source language and surface later, in a language nobody on the team reads daily:
Mistake | What you see | Fix |
Hardcoded strings in components | English text in every language | Wrap all user-facing text in |
Leaving | Apostrophes render as | Set |
No | Blank screen or render errors on first load | Add a |
Different key structure per language | Working English, missing German strings | Keep every language file structurally identical |
Reading | Switcher shows | Use |
Editing target-language files by hand | Languages drift apart over time | Route every change through one source of truth |
Managing i18next translations with LingoHub
The setup above solves the code side of localization. The remaining work is operational: getting JSON files to translators, tracking what is untranslated after each feature, and verifying quality across languages. Doing this by hand, over email or shared folders, breaks down quickly once a project grows past a couple of languages.
LingoHub handles this workflow for i18next projects natively. It imports and exports i18next JSON files directly, supporting the current v4 format as well as the outdated v3 format for older projects, and it fully supports i18next plurals: suffix keys such as _one and _other are recognized and kept together, so translators see all plural forms of a string side by side. The developer documentation covers the format details and configuration options.
The workflow fits how development teams already operate. LingoHub connects to your git repository, pulls new and changed keys from public/locales automatically, and opens a pull request when translations are ready. Localization runs alongside your normal branching model instead of as a separate process. While translators and reviewers work in parallel, built-in quality checks verify that {{placeholders}} remain intact, plural suffix keys are complete, and terminology follows your glossary. AI translation can prefill new keys in seconds, with human review deciding what ships. The result for a development team is concrete: translation stops being a release blocker, and mismatched placeholders stop appearing in production.
Frequently asked questions
What is react-i18next?
react-i18next is the official React binding for i18next. It wraps the i18next core in React-friendly APIs: the useTranslation hook for accessing translations in components, language switching that re-renders the UI, and integration with Suspense for asynchronous loading. It contains no translation logic of its own; i18next remains the engine underneath.
How to get current language in i18next?
Read i18n.language for the currently detected or selected language code, which may include a region such as de-AT. For UI purposes, i18n.resolvedLanguage is usually the better choice: it returns the language i18next actually resolved translations to, after fallbacks. In a component, get the instance from the hook: const { i18n } = useTranslation().
Does react-i18next support TypeScript?
Yes. Beyond shipping its own type definitions, react-i18next supports fully typed translation keys: you augment the i18next module with the shape of your resources, and t gains autocomplete plus compile-time checking for every key and interpolation variable. For large key sets, generating that type from your English JSON files in a build step keeps it accurate.
How do I keep translation files consistent across languages?
Automate the checks. Structural consistency (every key present in every language, placeholders unchanged, plural forms complete) is exactly what machines verify better than reviewers. LingoHub runs these checks continuously on your i18next JSON files, and a CI step can additionally fail a build when a language file falls behind the source language.
Wrapping up
A production-ready i18next React setup comes down to a handful of decisions made early: load translations from namespaced JSON v4 files instead of bundling them, let a detector and fallback chain choose the language, use useTranslation for all user-facing strings, and express plurals through suffix keys rather than code. From there, the file format does the heavy lifting: clean i18next JSON moves between your repository and your translation workflow without manual copying or rework.
Once a project supports more than one or two languages, the work shifts from translating individual strings to managing the whole workflow. Automated synchronization, quality checks, and reviews keep localization moving at the pace of development. Ready to see the LingoHub i18next integration in action? Book a demo and bring your own JSON files.
Related articles

i18n and l10n for AngularJS apps from development to deployment
Adding multiple languages to your app requires careful planning. Read our blog about i18n and l10n best practices specifically for AngularJS apps.

Gettext i18n system
What is gettext, what are its features, and what resource file types use? We highlighted all these questions in our blog. Find more inside.

i18next and react-i18next: a complete guide for React apps
Learn how to set up i18next and react-i18next in a React app: translation files, namespaces, plurals, language switching, and a production workflow.

i18n gem advanced features - Ruby on Rails internationalization
Find inside this article the highlighting of some i18n gem advanced features in Ruby on Rails internationalization.