react-i18next — Internationalization for React Applications
In this tutorial, you will learn about react. We cover key concepts, practical examples, and best practices to help you master this topic.
react-i18next integrates i18next with React, providing hooks, HOCs, and components for seamless multilingual React application development.
What You'll Learn
By the end of this tutorial, you'll understand how to set up react-i18next in a React app, use the useTranslation hook, handle dynamic content with Trans component, manage namespace loading, and implement locale switching without page reload.
Why It Matters
React applications handle UI updates through component state and re-renders. react-i18next hooks into React's reactivity system so that changing the locale automatically re-renders all components with new translations. Manual DOM manipulation is eliminated, and the translation update is instant.
Real-World Use
A React-based admin panel with 50+ components uses react-i18next with namespaces for different sections. Switching from English to Japanese from a dropdown triggers i18next.changeLanguage, which updates all visible text, reformats dates and numbers, and adjusts the layout direction — all without a page refresh or any component-level logic.
Integration Architecture
graph LR
A[React App] --> B[i18next Instance]
B --> C[Translation Files]
A --> D[useTranslation Hook]
A --> E[Trans Component]
A --> F[withTranslation HOC]
B --> G[Language Detector]
B --> H[Backend Plugin]
D --> I[Auto re-render
on language change]
E --> J[Rich HTML
in translations]
F --> K[Class component
support]
style A fill:#4a90d9,color:#fff
style B fill:#27ae60,color:#fff
Setup and Configuration
// i18n/index.js — react-i18next configuration
import i18next from 'i18next';
import { initReactI18next } from 'react-i18next';
import LanguageDetector from 'i18next-browser-languagedetector';
import Backend from 'i18next-http-backend';
i18next
.use(Backend) // Load translations from /locales
.use(LanguageDetector) // Detect user language
.use(initReactI18next) // React integration
.init({
fallbackLng: 'en',
debug: process.env.NODE_ENV === 'development',
interpolation: {
escapeValue: false, // React already escapes
},
// Backend config
backend: {
loadPath: '/locales/{{lng}}/{{ns}}.json',
},
// Detection config
detection: {
order: ['localStorage', 'cookie', 'navigator'],
caches: ['localStorage', 'cookie'],
},
// Namespaces
ns: ['common', 'dashboard', 'profile', 'settings'],
defaultNS: 'common',
});
export default i18next;
Using useTranslation Hook
// components/Welcome.jsx — Using the useTranslation hook
import { useTranslation } from 'react-i18next';
function Welcome({ userName }) {
const { t, i18n } = useTranslation();
return (
<div>
<h1>{t('welcome_title')}</h1>
<p>{t('welcome_message', { name: userName })}</p>
<p>
{t('items_count', { count: 5 })}
</p>
<p>
{t('member_since', {
date: new Intl.DateTimeFormat(i18n.language).format(
new Date('2024-01-15')
)
})}
</p>
</div>
);
}
// Translation file: /locales/en/common.json
// {
// "welcome_title": "Welcome to Dashboard",
// "welcome_message": "Hello, {{name}}!",
// "items_count": "{{count}} item",
// "items_count_plural": "{{count}} items",
// "member_since": "Member since {{date}}"
// }
Using Multiple Namespaces
// components/Dashboard.jsx — Loading multiple namespaces
import { useTranslation } from 'react-i18next';
function Dashboard() {
const { t } = useTranslation(['dashboard', 'common']);
return (
<div className="dashboard">
<h1>{t('dashboard:title')}</h1>
<p>{t('common:welcome_message', { name: 'Admin' })}</p>
<div className="stats">
<div className="stat-card">
<h3>{t('dashboard:users_total')}</h3>
<p>{t('dashboard:users_active')}</p>
</div>
<div className="stat-card">
<h3>{t('dashboard:revenue')}</h3>
<p>{t('dashboard:revenue_change')}</p>
</div>
</div>
<button>{t('common:save')}</button>
<button>{t('common:cancel')}</button>
</div>
);
}
Trans Component for Rich HTML
// components/Notification.jsx — Using Trans component for mixed text/HTML
import { Trans, useTranslation } from 'react-i18next';
function Notification({ userName, unreadCount, profileUrl }) {
const { t } = useTranslation();
return (
<div className="notification">
{/* Simple text — use t() */}
<p>{t('notifications_title')}</p>
{/* Mixed text with HTML/links — use Trans */}
<Trans
i18nKey="notification_message"
values={{ name: userName, count: unreadCount }}
components={{
strong: <strong />,
link: <a href={profileUrl} />,
icon: <span className="badge" />
}}
/>
{/* Complex nesting with formatting */}
<Trans
i18nKey="notification_actions"
components={[
<button key="view" onClick={() => {}} />,
<button key="dismiss" onClick={() => {}} />
]}
/>
</div>
);
}
// Translation:
// "notification_message": "<strong>{{name}}</strong>, you have <icon>{{count}}</icon> unread notifications. <link>View profile</link>"
// "notification_actions": "<0>View All</0><1>Dismiss</1>"
Locale Switcher Component
// components/LocaleSwitcher.jsx — Language selection dropdown
import { useTranslation } from 'react-i18next';
function LocaleSwitcher() {
const { i18n, t } = useTranslation();
const locales = [
{ code: 'en', label: 'English', nativeLabel: 'English' },
{ code: 'es', label: 'Spanish', nativeLabel: 'Espanol' },
{ code: 'fr', label: 'French', nativeLabel: 'Francais' },
{ code: 'de', label: 'German', nativeLabel: 'Deutsch' },
{ code: 'ar', label: 'Arabic', nativeLabel: 'العربية' },
{ code: 'ja', label: 'Japanese', nativeLabel: 'Japanese' },
];
const handleChange = (event) => {
const newLocale = event.target.value;
i18n.changeLanguage(newLocale);
// Update HTML direction for RTL
document.documentElement.dir = i18n.dir(newLocale);
document.documentElement.lang = newLocale;
};
return (
<div className="locale-switcher">
<label htmlFor="locale-select" className="sr-only">
{t('select_language')}
</label>
<select
id="locale-select"
value={i18n.language}
onChange={handleChange}
aria-label={t('select_language')}
>
{locales.map(locale => (
<option key={locale.code} value={locale.code}>
{locale.nativeLabel}
</option>
))}
</select>
</div>
);
}
Namespace Lazy Loading
// components/AdminPanel.jsx — Lazy load admin namespace
import { useTranslation } from 'react-i18next';
import { useEffect, useState } from 'react';
function AdminPanel() {
const { t, i18n } = useTranslation('common');
const [adminLoaded, setAdminLoaded] = useState(false);
useEffect(() => {
// Load admin namespace only when this component mounts
i18n.loadNamespaces('admin').then(() => {
setAdminLoaded(true);
});
}, [i18n]);
if (!adminLoaded) {
return <div>{t('loading')}</div>;
}
return (
<div className="admin-panel">
<h2>{t('admin:user_management')}</h2>
<table>
<thead>
<tr>
<th>{t('admin:name')}</th>
<th>{t('admin:email')}</th>
<th>{t('admin:role')}</th>
<th>{t('admin:actions')}</th>
</tr>
</thead>
<tbody>
{/* User rows */}
</tbody>
</table>
</div>
);
}
Common Mistakes
- Not setting escapeValue: false. React automatically escapes values to prevent XSS. i18next's default escaping causes double-escaping, showing "&" instead of "&". Always set interpolation.escapeValue to false in React.
- Loading all namespaces upfront. For apps with many features, loading all translation namespaces on init increases the initial bundle. Use loadNamespaces in component effects to lazy load namespaces.
- Forgetting the dir attribute for RTL. Switching to Arabic or Hebrew requires updating the dir attribute on . Use i18n.dir(code) to get the direction and set it during locale switch.
- Using t() for complex HTML content. When translations contain links, bold text, or other HTML elements, use the Trans component instead of dangerouslySetInnerHTML or concatenation.
- Not handling SSR dehydration. In Next.js or Remix, the server-rendered translations must match the client. Use the i18next instance created on the server and pass it to the client via a serialized configuration.
Practice Questions
- How does react-i18next trigger re-rendering when the language changes?
- What is the difference between useTranslation and withTranslation?
- When should you use the Trans component instead of the t() function?
- How do you lazy load a namespace when a component mounts?
- Why must escapeValue be set to false in React?
Challenge: Build a React application with 3 pages (Dashboard, Profile, Settings) each using a separate translation namespace. Implement a locale switcher that updates all content, handles RTL direction for Arabic, lazy loads the Settings namespace only when navigating to that page, and uses the Trans component for a notification with a clickable link.
FAQ
Mini Project
Build a multi-page React dashboard with react-i18next: 3 namespaces (common, dashboard, admin) loaded on demand, locale switcher with RTL support, Trans component usage for notifications with links, date/number formatting using Intl API with current locale, and lazy loading of the admin namespace only when the user navigates to Admin page.
What's Next
You've mastered react-i18next. Next, learn about vue-i18n for Internationalization in Vue.js applications.
Built by the developers of DodaTech
Doda Browser, DodaZIP & Durga Antivirus Pro