Respuesta rápida
react-i18next es la librería i18n más popular para React con 3.5M+ descargas npm semanales. Instalar con npm install react-i18next i18next, create JSON translation files, configure i18next with i18n.init(), then use the useTranslation hook: const { t } = useTranslation(); return <h1>{t('welcome')}</h1>. For production apps, combine with IntlPull for AI-powered translation management and OTA updates.
What is react-i18next?
react-i18next is a React binding for i18next, the most battle-tested JavaScript internationalization framework. It provides:
- React hooks (
useTranslation) for functional components - HOCs and render props for class components
- Suspense support for async translation loading
- SSR compatibility with Next.js, Remix, and Gatsby
- Plugin ecosystem for backends, language detection, and caching
Why Choose react-i18next?
| Feature | react-i18next | react-intl | next-intl |
|---|---|---|---|
| Weekly Downloads | 3.5M+ | 1.2M | 800K |
| Bundle Size | ~15KB | ~25KB | ~12KB |
| Framework | Any React | Any React | Next.js only |
| Learning Curve | Low | Medium | Low |
| Plugin System | Extensive | Limited | N/A |
| TypeScript | Excellent | Good | Excellent |
Installation and Setup
Step 1: Install Dependencies
Terminal1npm install react-i18next i18next 2# or 3yarn add react-i18next i18next 4# or 5pnpm add react-i18next i18next
Optional plugins:
Terminal1# Language detection 2npm install i18next-browser-languagedetector 3 4# Load translations from backend 5npm install i18next-http-backend 6 7# For React Native 8npm install react-i18next i18next @os-team/i18next-react-native-language-detector
Step 2: Create Translation Files
src/
├── locales/
│ ├── en/
│ │ ├── common.json
│ │ └── home.json
│ ├── es/
│ │ ├── common.json
│ │ └── home.json
│ └── fr/
│ ├── common.json
│ └── home.json
└── i18n.ts
en/common.json:
JSON1{ 2 "welcome": "Welcome to our app", 3 "nav": { 4 "home": "Home", 5 "about": "About", 6 "contact": "Contact" 7 }, 8 "buttons": { 9 "submit": "Submit", 10 "cancel": "Cancel", 11 "save": "Save" 12 } 13}
es/common.json:
JSON1{ 2 "welcome": "Bienvenido a nuestra aplicación", 3 "nav": { 4 "home": "Inicio", 5 "about": "Acerca de", 6 "contact": "Contacto" 7 }, 8 "buttons": { 9 "submit": "Enviar", 10 "cancel": "Cancelar", 11 "save": "Guardar" 12 } 13}
Step 3: Configure i18next
TypeScript1// src/i18n.ts 2import i18n from 'i18next'; 3import { initReactI18next } from 'react-i18next'; 4import LanguageDetector from 'i18next-browser-languagedetector'; 5 6// Import translations 7import enCommon from './locales/en/common.json'; 8import enHome from './locales/en/home.json'; 9import esCommon from './locales/es/common.json'; 10import esHome from './locales/es/home.json'; 11 12i18n 13 .use(LanguageDetector) 14 .use(initReactI18next) 15 .init({ 16 resources: { 17 en: { 18 common: enCommon, 19 home: enHome, 20 }, 21 es: { 22 common: esCommon, 23 home: esHome, 24 }, 25 }, 26 defaultNS: 'common', 27 fallbackLng: 'en', 28 supportedLngs: ['en', 'es', 'fr'], 29 30 interpolation: { 31 escapeValue: false, // React already escapes 32 }, 33 34 detection: { 35 order: ['localStorage', 'navigator', 'htmlTag'], 36 caches: ['localStorage'], 37 }, 38 }); 39 40export default i18n;
Step 4: Initialize in Your App
TSX1// src/main.tsx or src/index.tsx 2import React from 'react'; 3import ReactDOM from 'react-dom/client'; 4import './i18n'; // Import i18n configuration 5import App from './App'; 6 7ReactDOM.createRoot(document.getElementById('root')!).render( 8 <React.StrictMode> 9 <App /> 10 </React.StrictMode> 11);
Using the useTranslation Hook
The useTranslation hook is the primary way to access translations in functional components.
Basic Usage
TSX1import { useTranslation } from 'react-i18next'; 2 3function Header() { 4 const { t } = useTranslation(); 5 6 return ( 7 <header> 8 <h1>{t('welcome')}</h1> 9 <nav> 10 <a href="/">{t('nav.home')}</a> 11 <a href="/about">{t('nav.about')}</a> 12 </nav> 13 </header> 14 ); 15}
With Namespaces
TSX1import { useTranslation } from 'react-i18next'; 2 3function HomePage() { 4 // Load specific namespace 5 const { t } = useTranslation('home'); 6 7 return <h1>{t('hero.title')}</h1>; 8} 9 10function MultiNamespace() { 11 // Load multiple namespaces 12 const { t } = useTranslation(['common', 'home']); 13 14 return ( 15 <div> 16 <h1>{t('home:hero.title')}</h1> 17 <button>{t('common:buttons.submit')}</button> 18 </div> 19 ); 20}
Changing Language
TSX1import { useTranslation } from 'react-i18next'; 2 3function LanguageSwitcher() { 4 const { i18n } = useTranslation(); 5 6 const changeLanguage = (lng: string) => { 7 i18n.changeLanguage(lng); 8 }; 9 10 return ( 11 <div> 12 <button onClick={() => changeLanguage('en')}>English</button> 13 <button onClick={() => changeLanguage('es')}>Español</button> 14 <button onClick={() => changeLanguage('fr')}>Français</button> 15 </div> 16 ); 17}
Variable Interpolation
Basic Variables
JSON1{ 2 "greeting": "Hello, {{name}}!", 3 "itemCount": "You have {{count}} items in your cart" 4}
TSX1function Greeting({ user }) { 2 const { t } = useTranslation(); 3 4 return ( 5 <div> 6 <h1>{t('greeting', { name: user.name })}</h1> 7 <p>{t('itemCount', { count: user.cartItems })}</p> 8 </div> 9 ); 10}
Formatting Values
JSON1{ 2 "price": "Total: {{price, currency}}", 3 "date": "Created on {{date, datetime}}" 4}
TypeScript1// i18n.ts - Add formatters 2i18n.init({ 3 // ... other config 4 interpolation: { 5 escapeValue: false, 6 format: (value, format, lng) => { 7 if (format === 'currency') { 8 return new Intl.NumberFormat(lng, { 9 style: 'currency', 10 currency: 'USD', 11 }).format(value); 12 } 13 if (format === 'datetime') { 14 return new Intl.DateTimeFormat(lng).format(value); 15 } 16 return value; 17 }, 18 }, 19});
Pluralization
Basic Plurals
JSON1{ 2 "item_one": "{{count}} item", 3 "item_other": "{{count}} items", 4 "item_zero": "No items" 5}
TSX1function CartCount({ count }) { 2 const { t } = useTranslation(); 3 4 return <span>{t('item', { count })}</span>; 5 // count=0: "No items" 6 // count=1: "1 item" 7 // count=5: "5 items" 8}
Complex Plurals (Russian, Arabic, etc.)
JSON1{ 2 "item_zero": "нет товаров", 3 "item_one": "{{count}} товар", 4 "item_few": "{{count}} товара", 5 "item_many": "{{count}} товаров", 6 "item_other": "{{count}} товаров" 7}
Ordinals
JSON1{ 2 "place_ordinal_one": "{{count}}st place", 3 "place_ordinal_two": "{{count}}nd place", 4 "place_ordinal_few": "{{count}}rd place", 5 "place_ordinal_other": "{{count}}th place" 6}
TSX1t('place', { count: 1, ordinal: true }); // "1st place" 2t('place', { count: 2, ordinal: true }); // "2nd place" 3t('place', { count: 3, ordinal: true }); // "3rd place" 4t('place', { count: 4, ordinal: true }); // "4th place"
Rich Text and Components
Using the Trans Component
JSON1{ 2 "terms": "By signing up, you agree to our <link>Terms of Service</link>", 3 "welcome": "Welcome <bold>{{name}}</bold> to our platform!" 4}
TSX1import { Trans, useTranslation } from 'react-i18next'; 2 3function TermsText() { 4 return ( 5 <Trans 6 i18nKey="terms" 7 components={{ 8 link: <a href="/terms" className="text-blue-600" />, 9 }} 10 /> 11 ); 12} 13 14function WelcomeMessage({ name }) { 15 return ( 16 <Trans 17 i18nKey="welcome" 18 values={{ name }} 19 components={{ 20 bold: <strong className="font-bold" />, 21 }} 22 /> 23 ); 24}
Nested HTML
JSON{ "description": "Check out our <link>new features</link> and <button>get started</button> today!" }
TSX1<Trans 2 i18nKey="description" 3 components={{ 4 link: <a href="/features" />, 5 button: <button onClick={handleStart} />, 6 }} 7/>
Lazy Loading Translations
For large apps, load translations on demand to reduce initial bundle size.
HTTP Backend
TypeScript1import i18n from 'i18next'; 2import { initReactI18next } from 'react-i18next'; 3import HttpBackend from 'i18next-http-backend'; 4import LanguageDetector from 'i18next-browser-languagedetector'; 5 6i18n 7 .use(HttpBackend) 8 .use(LanguageDetector) 9 .use(initReactI18next) 10 .init({ 11 fallbackLng: 'en', 12 ns: ['common'], 13 defaultNS: 'common', 14 15 backend: { 16 loadPath: '/locales/{{lng}}/{{ns}}.json', 17 }, 18 19 react: { 20 useSuspense: true, 21 }, 22 }); 23 24export default i18n;
With Suspense
TSX1import { Suspense } from 'react'; 2 3function App() { 4 return ( 5 <Suspense fallback={<LoadingSpinner />}> 6 <MainContent /> 7 </Suspense> 8 ); 9}
Load Namespace on Demand
TSX1import { useTranslation } from 'react-i18next'; 2import { Suspense, lazy } from 'react'; 3 4// Lazy load component with its namespace 5const SettingsPage = lazy(() => import('./SettingsPage')); 6 7function App() { 8 return ( 9 <Suspense fallback={<LoadingSpinner />}> 10 <SettingsPage /> 11 </Suspense> 12 ); 13} 14 15// SettingsPage.tsx 16function SettingsPage() { 17 const { t, ready } = useTranslation('settings', { useSuspense: false }); 18 19 if (!ready) return <LoadingSpinner />; 20 21 return <h1>{t('title')}</h1>; 22}
TypeScript Integration
Type-Safe Translations
TypeScript1// src/types/i18next.d.ts 2import 'i18next'; 3import common from '../locales/en/common.json'; 4import home from '../locales/en/home.json'; 5 6declare module 'i18next' { 7 interface CustomTypeOptions { 8 defaultNS: 'common'; 9 resources: { 10 common: typeof common; 11 home: typeof home; 12 }; 13 } 14}
Now TypeScript will autocomplete translation keys:
TSXconst { t } = useTranslation(); t('nav.home'); // ✅ Autocompletes t('nav.invalid'); // ❌ TypeScript error
With Multiple Namespaces
TSX1const { t } = useTranslation(['common', 'home']); 2 3t('buttons.submit'); // From common (default) 4t('home:hero.title'); // From home namespace
Server-Side Rendering (SSR)
Next.js Pages Router
TypeScript1// next-i18next.config.js 2module.exports = { 3 i18n: { 4 defaultLocale: 'en', 5 locales: ['en', 'es', 'fr'], 6 }, 7};
TSX1// pages/index.tsx 2import { serverSideTranslations } from 'next-i18next/serverSideTranslations'; 3import { useTranslation } from 'next-i18next'; 4 5export default function Home() { 6 const { t } = useTranslation('common'); 7 return <h1>{t('welcome')}</h1>; 8} 9 10export async function getStaticProps({ locale }) { 11 return { 12 props: { 13 ...(await serverSideTranslations(locale, ['common'])), 14 }, 15 }; 16}
Next.js App Router
For Next.js App Router, we recommend using next-intl instead. See our next-intl guide.
Remix
TSX1// app/entry.server.tsx 2import { createInstance } from 'i18next'; 3import { initReactI18next } from 'react-i18next'; 4 5export default async function handleRequest(request, ...) { 6 const i18n = createInstance(); 7 await i18n.use(initReactI18next).init({ 8 lng: locale, 9 resources: { [locale]: translations }, 10 }); 11 12 // Render with i18n instance 13}
Testing with react-i18next
Mock Setup
TypeScript1// test/setup.ts 2import i18n from 'i18next'; 3import { initReactI18next } from 'react-i18next'; 4 5i18n.use(initReactI18next).init({ 6 lng: 'en', 7 fallbackLng: 'en', 8 resources: { 9 en: { 10 common: { 11 welcome: 'Welcome', 12 'buttons.submit': 'Submit', 13 }, 14 }, 15 }, 16}); 17 18export default i18n;
Testing Components
TSX1import { render, screen } from '@testing-library/react'; 2import { I18nextProvider } from 'react-i18next'; 3import i18n from '../test/setup'; 4import Header from './Header'; 5 6describe('Header', () => { 7 it('renders translated welcome message', () => { 8 render( 9 <I18nextProvider i18n={i18n}> 10 <Header /> 11 </I18nextProvider> 12 ); 13 14 expect(screen.getByText('Welcome')).toBeInTheDocument(); 15 }); 16});
Production Best Practices
1. Namespace Organization
locales/
├── en/
│ ├── common.json # Shared UI (buttons, nav, errors)
│ ├── auth.json # Login, signup, password reset
│ ├── dashboard.json # Dashboard-specific
│ ├── settings.json # Settings page
│ └── errors.json # Error messages
2. Key Naming Convention
JSON1{ 2 "page.section.element": "Value", 3 "auth.login.title": "Sign In", 4 "auth.login.button": "Continue", 5 "dashboard.stats.totalUsers": "Total Users", 6 "errors.network.timeout": "Request timed out" 7}
3. Missing Key Handling
TypeScript1i18n.init({ 2 saveMissing: process.env.NODE_ENV === 'development', 3 missingKeyHandler: (lng, ns, key) => { 4 console.warn(`Missing translation: ${lng}/${ns}/${key}`); 5 // Report to error tracking 6 }, 7});
4. Performance Optimization
TypeScript1// Preload critical namespaces 2i18n.loadNamespaces(['common', 'auth']); 3 4// Don't re-render on language change for static content 5const { t } = useTranslation('common', { useSuspense: false });
Scaling with IntlPull
As your app grows, managing JSON files manually becomes painful. IntlPull provides:
CLI Integration
Terminal1# Initialize IntlPull in your project 2npx @intlpullhq/cli init 3 4# Extract keys from code 5npx @intlpullhq/cli extract 6 7# Upload to IntlPull 8npx @intlpullhq/cli upload 9 10# Download translations 11npx @intlpullhq/cli download
AI Translation
IntlPull automatically translates your keys using GPT-4, Claude, or DeepL while maintaining context and brand voice.
OTA Updates
Update translations without app releases:
TypeScript1import { IntlPullBackend } from '@intlpull/i18next-backend'; 2 3i18n.use(IntlPullBackend).init({ 4 backend: { 5 projectId: 'your-project-id', 6 apiKey: process.env.INTLPULL_API_KEY, 7 }, 8});
Common Patterns
Conditional Translations
JSON1{ 2 "greeting_morning": "Good morning!", 3 "greeting_afternoon": "Good afternoon!", 4 "greeting_evening": "Good evening!" 5}
TSX1function Greeting() { 2 const { t } = useTranslation(); 3 const hour = new Date().getHours(); 4 5 const getGreetingKey = () => { 6 if (hour < 12) return 'greeting_morning'; 7 if (hour < 18) return 'greeting_afternoon'; 8 return 'greeting_evening'; 9 }; 10 11 return <h1>{t(getGreetingKey())}</h1>; 12}
Dynamic Keys
TSX1function StatusBadge({ status }: { status: 'pending' | 'approved' | 'rejected' }) { 2 const { t } = useTranslation(); 3 4 // Keys: status.pending, status.approved, status.rejected 5 return <span>{t(`status.${status}`)}</span>; 6}
Fallback Content
TSX1const { t } = useTranslation(); 2 3// Provide fallback 4const title = t('page.title', 'Default Title'); 5 6// Check if key exists 7const { exists } = useTranslation(); 8if (exists('optional.feature')) { 9 // Show feature-specific content 10}
Troubleshooting
"Translation not found" warnings
TypeScript1// Check namespace is loaded 2i18n.hasLoadedNamespace('myNamespace'); 3 4// Ensure key exists 5i18n.exists('myKey'); 6 7// Debug mode 8i18n.init({ debug: true });
Component not re-rendering on language change
TSX1// Use the hook properly - it subscribes to changes 2const { t, i18n } = useTranslation(); 3 4// Force re-render with key 5<MyComponent key={i18n.language} />
SSR hydration mismatch
TypeScript1i18n.init({ 2 react: { 3 useSuspense: false, // Disable for SSR 4 }, 5});
Frequently Asked Questions
What is react-i18next?
react-i18next is the official React binding for i18next, the most popular JavaScript internationalization framework. It provides hooks (useTranslation), HOCs, and the Trans component for integrating translations into React components. With 3.5M+ weekly npm downloads, it's the most widely adopted React i18n solution.
How do I install react-i18next?
Install with npm: npm install react-i18next i18next. Optionally add i18next-browser-languagedetector for auto language detection and i18next-http-backend for lazy loading translations. Create translation JSON files, configure i18next with i18n.init(), and import the config in your app entry point.
How does the useTranslation hook work?
useTranslation returns { t, i18n } where t is the translation function and i18n is the i18next instance. Call t('key') to get translations. Specify namespaces with useTranslation('namespace') or useTranslation(['ns1', 'ns2']). The hook automatically re-renders when language changes.
How do I handle pluralization in react-i18next?
Use plural suffixes in your JSON keys: item_one, item_other, item_zero. Pass count to the t function: t('item', { count: 5 }). For complex plurals (Russian, Arabic), use _zero, _one, _two, _few, _many, _other suffixes. i18next automatically selects the correct form based on the count and locale.
What is the Trans component used for?
Trans renders translations containing HTML or React components. Instead of dangerouslySetInnerHTML, use Trans: <Trans i18nKey="terms" components={{ link: <a href="/terms" /> }} /> renders "Accept our <a>Terms</a>" with the link clickable. It's essential for translations with bold text, links, or interactive elements.
How do I lazy load translations?
Use i18next-http-backend to load translations on demand. Configure backend: { loadPath: '/locales/{{lng}}/{{ns}}.json' } and enable Suspense. Translations are fetched when needed, reducing initial bundle size. Load specific namespaces with i18n.loadNamespaces(['settings']) before rendering pages that need them.
Is react-i18next compatible with TypeScript?
Yes, react-i18next has excellent TypeScript support. Create a i18next.d.ts file declaring your resources type, and you get autocomplete and type checking for translation keys. TypeScript will error on invalid keys, preventing typos and missing translations at compile time.
How do I use react-i18next with Next.js?
For Pages Router, use next-i18next with serverSideTranslations in getStaticProps/getServerSideProps. For App Router, we recommend next-intl instead as it's designed for Server Components. react-i18next can work with App Router but requires additional setup for RSC.
How do I test components using react-i18next?
Wrap components with I18nextProvider in tests using a mock i18n instance. Create a test-specific i18n config with simplified translations. Use Testing Library to verify translated text appears correctly. You can also mock the useTranslation hook directly for unit tests.
Should I use react-i18next or next-intl?
Use react-i18next for non-Next.js React apps or Next.js Pages Router projects. Use next-intl for Next.js App Router projects—it's purpose-built for Server Components and has tighter Next.js integration. Both are production-ready; the choice depends on your framework.
Summary
react-i18next is the industry standard for React internationalization:
| Aspect | Details |
|---|---|
| Installation | npm install react-i18next i18next |
| Primary Hook | useTranslation() |
| Namespaces | Feature-based organization |
| Plurals | _one, _other suffixes |
| Rich Text | Trans component |
| TypeScript | Full type safety available |
| SSR | Works with Next.js, Remix, Gatsby |
| Bundle | ~15KB gzipped |
For production apps at scale, combine react-i18next with IntlPull for AI-powered translation management, OTA updates, and team collaboration.
Ready to streamline your i18n workflow? Start free with IntlPull — sync translations automatically with your react-i18next setup.
