React i18n: The Complete Setup Guide
Adding internationalization (i18n) to a React application is one of the most impactful things you can do for global reach. Whether you are building a SaaS product, an e-commerce platform, or a developer tool, React i18n lets you serve users in their native language — and dramatically improves conversion rates.
This guide walks you through setting up i18n in React from scratch, covering the three most popular approaches: react-i18next, react-intl, and Lovalingo.
Why React i18n Matters in 2026
The web is global by default. Here is why i18n should be a priority:
- Many internet users prefer browsing in their native language
- Localized apps see 1.5-3x higher conversion rates compared to English-only equivalents
- Google indexes localized content separately, giving you 10x the keyword surface area
- App stores rank localized apps higher in regional search results
If you are targeting any market outside of the US/UK, React i18n is not optional — it is a competitive advantage.
React i18n: Three Approaches Compared
Before diving into setup, let's understand the three main approaches to React internationalization:
| Approach | Library | How It Works | Effort |
|----------|---------|-------------|--------|
| Manual JSON | react-i18next | Extract strings → create JSON files → use t() function | High |
| ICU Messages | react-intl | Define messages with ICU format → use FormattedMessage | High |
| Automatic AI | Lovalingo | Wrap app in provider → translations happen automatically | Minimal |
When to Choose Each
- react-i18next: Best for large teams with dedicated translators and complex namespacing needs
- react-intl: Best when you need advanced ICU message formatting (plurals, selects, dates)
- Lovalingo: Best for fast-moving teams who want i18n without the overhead of translation files
Setting Up React i18n with react-i18next
react-i18next is the most popular React i18n library with over 9 million weekly npm downloads. Here is the complete setup:
Step 1: Install Packages
npm install i18next react-i18next i18next-browser-languagedetector i18next-http-backendEach package serves a purpose:
i18next— core i18n frameworkreact-i18next— React bindings (hooks, components, HOCs)i18next-browser-languagedetector— auto-detects user languagei18next-http-backend— loads translation files asynchronously
Step 2: Create Your Translation Files
Organize translations by language and namespace:
public/
locales/
en/
common.json
home.json
fr/
common.json
home.json
de/
common.json
home.json
public/locales/en/common.json:
{
"nav": {
"home": "Home",
"pricing": "Pricing",
"docs": "Documentation"
},
"cta": {
"getStarted": "Get Started Free",
"learnMore": "Learn More"
}
}public/locales/fr/common.json:
{
"nav": {
"home": "Accueil",
"pricing": "Tarifs",
"docs": "Documentation"
},
"cta": {
"getStarted": "Commencer Gratuitement",
"learnMore": "En Savoir Plus"
}
}Step 3: Initialize i18next
Create a configuration file that ties everything together:
// src/i18n.ts
import i18n from "i18next";
import { initReactI18next } from "react-i18next";
import LanguageDetector from "i18next-browser-languagedetector";
import HttpBackend from "i18next-http-backend";
i18n
.use(HttpBackend)
.use(LanguageDetector)
.use(initReactI18next)
.init({
fallbackLng: "en",
supportedLngs: ["en", "fr", "de", "es", "ja"],
defaultNS: "common",
ns: ["common", "home"],
backend: {
loadPath: "/locales/{{lng}}/{{ns}}.json",
},
detection: {
order: ["path", "cookie", "navigator"],
lookupFromPathIndex: 0,
},
interpolation: {
escapeValue: false,
},
});
export default i18n;Step 4: Wire It Into Your App
// src/main.tsx
import React, { Suspense } from "react";
import ReactDOM from "react-dom/client";
import "./i18n"; // Initialize i18n before app renders
import App from "./App";
ReactDOM.createRoot(document.getElementById("root")!).render(
<React.StrictMode>
<Suspense fallback={<div>Loading translations...</div>}>
<App />
</Suspense>
</React.StrictMode>
);Step 5: Use Translations in Components
import { useTranslation } from "react-i18next";
function Navbar() {
const { t, i18n } = useTranslation();
return (
<nav>
<a href="/">{t("nav.home")}</a>
<a href="/pricing">{t("nav.pricing")}</a>
<a href="/docs">{t("nav.docs")}</a>
<select
value={i18n.language}
onChange={(e) => i18n.changeLanguage(e.target.value)}
>
<option value="en">English</option>
<option value="fr">Francais</option>
<option value="de">Deutsch</option>
</select>
</nav>
);
}Step 6: Handle Pluralization and Interpolation
{
"items_one": "{{count}} item in your cart",
"items_other": "{{count}} items in your cart",
"welcome": "Welcome back, {{name}}!",
"lastLogin": "Last login: {{date, datetime}}"
}function CartSummary({ count, userName }: { count: number; userName: string }) {
const { t } = useTranslation();
return (
<div>
<p>{t("welcome", { name: userName })}</p>
<p>{t("items", { count })}</p>
</div>
);
}Setting Up React i18n with react-intl
react-intl uses the ICU Message Format standard, which is more expressive for complex pluralization and selection patterns.
Quick Setup
npm install react-intlimport { IntlProvider, FormattedMessage, useIntl } from "react-intl";
const messages = {
en: {
greeting: "Hello, {name}!",
items: "{count, plural, =0 {No items} one {# item} other {# items}}",
price: "Price: {amount, number, ::currency/USD}",
},
fr: {
greeting: "Bonjour, {name} !",
items: "{count, plural, =0 {Aucun article} one {# article} other {# articles}}",
price: "Prix : {amount, number, ::currency/EUR}",
},
};
function App() {
const locale = "fr";
return (
<IntlProvider locale={locale} messages={messages[locale]}>
<ProductPage />
</IntlProvider>
);
}
function ProductPage() {
const intl = useIntl();
return (
<div>
<h1>
<FormattedMessage id="greeting" values={{ name: "Alice" }} />
</h1>
<p>
<FormattedMessage id="items" values={{ count: 3 }} />
</p>
<p>{intl.formatNumber(29.99, { style: "currency", currency: "USD" })}</p>
</div>
);
}react-intl vs react-i18next: Key Differences
| Feature | react-intl | react-i18next |
|---------|-----------|---------------|
| Message format | ICU standard | Custom syntax |
| Pluralization | Native ICU plural rules | Suffix-based (_one, _other) |
| Date/number formatting | Built-in via Intl API | Requires plugins |
| Namespace support | No (flat messages) | Yes (multi-file) |
| Lazy loading | Manual | Built-in with backends |
| Community size | Large | Largest |
The Zero-Config Approach: React i18n with Lovalingo
Both react-i18next and react-intl require significant upfront work: extracting strings, creating translation files, managing translation keys, and keeping translations in sync across languages. Lovalingo eliminates all of this.
Stack-aware setup
npm install @lovalingo/lovalingoimport { LovalingoProvider } from "@lovalingo/lovalingo/core";
function App() {
return (
<LovalingoProvider
publicAnonKey="your-public-key"
defaultLocale="en"
locales={["en", "fr", "de", "es", "ja"]}
>
{/* Your entire app — no changes needed */}
<Navbar />
<HeroSection />
<PricingTable />
<Footer />
</LovalingoProvider>
);
}That is the standard React setup. New Lovable apps generated with TanStack Start use Lovalingo's TanStack-aware setup path instead, so locale URLs, server-rendered metadata, hreflang, and the language switcher are wired into the router. In both cases there are no translation files, no string extraction, and no key management. Lovalingo's AI:
- Detects all translatable text in your React components
- Translates content using context-aware AI (not word-by-word)
- Keeps the locale layer aligned with route, metadata, and hreflang handling on supported stacks
- Caches translations on the CDN for instant subsequent loads
Why Developers Choose Lovalingo for React i18n
- Zero translation files — No JSON files to create or maintain
- Automatic sync — New text is detected and translated automatically
- Native rendering — No layout shift, no hydration mismatches
- SEO-ready — Server-rendered translations and metadata with automatic hreflang tags on SSR stacks
- Works with modern React stacks — Vite, Next.js, Remix, Lovable TanStack Start, v0, Bolt
React i18n with Next.js App Router
If you are using Next.js 14+ with the App Router, i18n setup has some specific considerations:
Middleware-Based Locale Routing
// middleware.ts
import { NextRequest, NextResponse } from "next/server";
const locales = ["en", "fr", "de", "es"];
const defaultLocale = "en";
function getLocale(request: NextRequest): string {
const acceptLang = request.headers.get("accept-language");
if (acceptLang) {
const preferred = acceptLang.split(",")[0].split("-")[0];
if (locales.includes(preferred)) return preferred;
}
return defaultLocale;
}
export function middleware(request: NextRequest) {
const { pathname } = request.nextUrl;
const hasLocale = locales.some(
(locale) => pathname.startsWith(`/${locale}/`) || pathname === `/${locale}`
);
if (hasLocale) return;
const locale = getLocale(request);
request.nextUrl.pathname = `/${locale}${pathname}`;
return NextResponse.redirect(request.nextUrl);
}
export const config = {
matcher: ["/((?!api|_next|.*\\..*).*)"],
};Server Components with i18n
// app/[locale]/page.tsx
import { getDictionary } from "@/lib/dictionaries";
export default async function HomePage({
params: { locale },
}: {
params: { locale: string };
}) {
const dict = await getDictionary(locale);
return (
<main>
<h1>{dict.home.title}</h1>
<p>{dict.home.description}</p>
</main>
);
}Or Use Lovalingo with the Next.js Setup Path
// app/layout.tsx
import { LovalingoProvider } from "@lovalingo/lovalingo/core";
export default function RootLayout({ children }) {
return (
<html>
<body>
<LovalingoProvider
publicAnonKey="your-key"
defaultLocale="en"
locales={["en", "fr", "de", "es"]}
routing="path"
>
{children}
</LovalingoProvider>
</body>
</html>
);
}Lovalingo handles locale routing, hreflang tags, and URL management through its Next.js setup path with the routing="path" option.
React i18n Best Practices
1. Plan for Text Expansion
German and French text is typically 20-35% longer than English. Always:
- Use flexible layouts (flexbox, grid) instead of fixed widths
- Test with pseudo-localization to simulate longer strings
- Avoid truncating translated text — it can change meaning
2. Use Context for Translations
A word like "post" can mean different things. Provide context to translators:
{
"blog.post": "Post",
"social.post": "Publish",
"mail.post": "Send"
}3. Never Concatenate Translated Strings
Word order varies dramatically across languages:
// Bad — assumes English word order
const msg = t("hello") + " " + name + ", " + t("welcome");
// Good — let the translator control word order
const msg = t("greeting", { name });
// EN: "Hello Alice, welcome!"
// JA: "アリスさん、ようこそ!"4. Handle RTL Languages
Support Arabic, Hebrew, and Persian with logical CSS properties:
/* Instead of margin-left, use margin-inline-start */
.sidebar {
margin-inline-start: 1rem;
padding-inline-end: 1rem;
}5. Extract Early, Extract Often
The longer you wait to add i18n, the harder it becomes. Start with i18n on day one — or use Lovalingo to avoid extraction entirely.
6. Test with Real Languages
Do not rely solely on pseudo-localization. Test with actual translations in languages that:
- Have longer text (German)
- Use different scripts (Japanese, Arabic)
- Have complex pluralization (Polish, Arabic)
- Read right-to-left (Arabic, Hebrew)
Performance Optimization for React i18n
Lazy Load Translations
With react-i18next, load translations on demand:
i18n.init({
partialBundledLanguages: true,
resources: {
en: { common: require("./locales/en/common.json") },
},
backend: {
loadPath: "/locales/{{lng}}/{{ns}}.json",
},
});Use Namespaces to Split Bundles
// Only loads the "dashboard" namespace
const { t } = useTranslation("dashboard");Lovalingo: Zero Translation Bundles
Since Lovalingo fetches translations from a CDN and caches them, your JavaScript bundle contains zero translation data. This is especially impactful for apps with 10+ languages — traditional approaches would bundle megabytes of JSON files.
FAQ
What is the easiest way to add i18n to a React app?
The easiest way is to use Lovalingo, which requires no translation files and uses a stack-aware setup. Standard React apps use LovalingoProvider; new Lovable apps use the TanStack Start setup path for locale routes, metadata, and hreflang. For manual approaches, react-i18next is the most popular choice.
Do I need to extract all strings for React i18n?
With traditional libraries like react-i18next or react-intl, yes — you need to extract every user-facing string into JSON translation files. With Lovalingo, no extraction is needed because AI detects and translates text automatically.
Which React i18n library has the smallest bundle size?
Lovalingo has the smallest footprint at ~8 KB gzipped. react-i18next is ~22 KB and react-intl is ~30 KB. However, bundle size should be weighed against features and developer experience.
Can I use React i18n with TypeScript?
Yes, all major React i18n libraries support TypeScript. react-i18next offers typed translation keys via module augmentation. react-intl provides typed message descriptors. Lovalingo is fully typed out of the box with no additional configuration.
How do I handle dynamic content in React i18n?
Use interpolation syntax: react-i18next uses double curly braces like {{name}}, react-intl uses ICU format like {name}, and Lovalingo handles dynamic content automatically since it translates at the render level.
Ready to add i18n to your React app without the complexity? Try Lovalingo free — use one stack-aware install prompt instead of hours of manual translation work.
Related Guides
React i18n Setup Guide & Best Practices
Learn how to implement React internationalization (i18n) with best practices, code examples, and Lovalingo automation. Step-by-step guide.
Read guideNext.js i18n Setup Guide
Complete guide to Next.js internationalization. Learn i18n routing, setup, and best practices. Compare next-intl, next-i18next, and Lovalingo.
Read guideBest React Translation Libraries Compared
Compare React translation libraries: react-intl, react-i18next, and Lovalingo. Features, bundle size, ease of use, and performance.
Read guideReady to automate your i18n workflow?
Lovalingo translates React, Next.js, and Lovable TanStack Start apps with stack-aware setup.
Try Lovalingo Free