React-i18next is a powerful library for internationalization (i18n) of React applications, enabling easy addition of multi-language support, formatting of dates, numbers, and currencies. It is a comprehensive solution based on the popular i18next library.
1npm install react-i18next i18next i18next-browser-languagedetector i18next-http-backend1// i18n.js
2import i18n from 'i18next';
3import { initReactI18next } from 'react-i18next';
4import Backend from 'i18next-http-backend';
5import LanguageDetector from 'i18next-browser-languagedetector';
6
7i18n
8 .use(Backend) // Load translations from server
9 .use(LanguageDetector) // Automatic language detection
10 .use(initReactI18next) // React integration
11 .init({
12 // Fallback configuration
13 fallbackLng: 'en',
14
15 // Debugging in development
16 debug: process.env.NODE_ENV === 'development',
17
18 // Interpolation
19 interpolation: {
20 escapeValue: false // React already protects against XSS
21 },
22
23 // Backend configuration
24 backend: {
25 loadPath: '/locales/{{lng}}/{{ns}}.json'
26 },
27
28 // Language detector configuration
29 detection: {
30 order: ['localStorage', 'navigator', 'htmlTag'],
31 caches: ['localStorage']
32 }
33 });
34
35export default i18n;1// index.js
2import React from 'react';
3import ReactDOM from 'react-dom/client';
4import './i18n'; // Import configuration
5import App from './App';
6
7const root = ReactDOM.createRoot(document.getElementById('root'));
8root.render(<App />);1// App.jsx
2import { useTranslation } from 'react-i18next';
3
4function App() {
5 const { t, i18n } = useTranslation();
6
7 const changeLanguage = (lang) => {
8 i18n.changeLanguage(lang);
9 };
10
11 return (
12 <div>
13 <h1>{t('welcome.title')}</h1>
14 <p>{t('welcome.description')}</p>
15
16 <div>
17 <button onClick={() => changeLanguage('en')}>English</button>
18 <button onClick={() => changeLanguage('pl')}>Polski</button>
19 <button onClick={() => changeLanguage('es')}>Español</button>
20 </div>
21
22 <p>{t('current_language')}: {i18n.language}</p>
23 </div>
24 );
25}
26
27export default App;1// public/locales/en/translation.json
2{
3 "welcome": {
4 "title": "Welcome to our application",
5 "description": "This is a multilingual React application"
6 },
7 "current_language": "Current language",
8 "navigation": {
9 "home": "Home",
10 "about": "About",
11 "contact": "Contact"
12 }
13}1// public/locales/pl/translation.json
2{
3 "welcome": {
4 "title": "Witaj w naszej aplikacji",
5 "description": "To jest wielojęzyczna aplikacja React"
6 },
7 "current_language": "Aktualny język",
8 "navigation": {
9 "home": "Strona główna",
10 "about": "O nas",
11 "contact": "Kontakt"
12 }
13}1function UserProfile({ user, messageCount }) {
2 const { t } = useTranslation();
3
4 return (
5 <div>
6 {/* Interpolation */}
7 <h1>{t('user.greeting', { name: user.name })}</h1>
8
9 {/* Pluralization */}
10 <p>{t('messages.count', { count: messageCount })}</p>
11
12 {/* Date formatting */}
13 <p>{t('user.joined', {
14 date: new Date(user.joinedAt),
15 formatParams: {
16 date: { year: 'numeric', month: 'long', day: 'numeric' }
17 }
18 })}</p>
19 </div>
20 );
21}1// Translation files with pluralization
2{
3 "user": {
4 "greeting": "Hello, {{name}}!"
5 },
6 "messages": {
7 "count_zero": "No messages",
8 "count_one": "1 message",
9 "count_other": "{{count}} messages"
10 }
11}1// Configuration with namespaces
2i18n.init({
3 //
4 defaultNS: 'common',
5 ns: ['common', 'navigation', 'forms', 'errors']
6});
7
8// Using different namespaces
9function Navigation() {
10 const { t } = useTranslation('navigation');
11
12 return (
13 <nav>
14 <a href="/">{t('home')}</a>
15 <a href="/about">{t('about')}</a>
16 <a href="/contact">{t('contact')}</a>
17 </nav>
18 );
19}
20
21function ContactForm() {
22 const { t } = useTranslation(['forms', 'errors']);
23
24 return (
25 <form>
26 <label>{t('forms:name.label')}</label>
27 <input
28 required
29 />
30
31 <label>{t('forms:email.label')}</label>
32 <input
33 type="email"
34 required
35 />
36
37 <button type="submit">
38 {t('forms:submit')}
39 </button>
40 </form>
41 );
42}1// LanguageContext.jsx
2import { createContext, useContext, useState } from 'react';
3import { useTranslation } from 'react-i18next';
4
5const LanguageContext = createContext();
6
7export function LanguageProvider({ children }) {
8 const { i18n } = useTranslation();
9 const [language, setLanguage] = useState(i18n.language);
10
11 const changeLanguage = async (newLang) => {
12 await i18n.changeLanguage(newLang);
13 setLanguage(newLang);
14
15 // Save choice in localStorage
16 localStorage.setItem('preferredLanguage', newLang);
17
18 // Change text direction for RTL languages
19 if (['ar', 'he', 'fa'].includes(newLang)) {
20 document.dir = 'rtl';
21 } else {
22 document.dir = 'ltr';
23 }
24 };
25
26 const availableLanguages = [
27 { code: 'en', name: 'English', flag: '🇺🇸' },
28 { code: 'pl', name: 'Polski', flag: '🇵🇱' },
29 { code: 'es', name: 'Español', flag: '🇪🇸' },
30 { code: 'fr', name: 'Français', flag: '🇫🇷' },
31 { code: 'de', name: 'Deutsch', flag: '🇩🇪' }
32 ];
33
34 return (
35 <LanguageContext.Provider value={{
36 language,
37 changeLanguage,
38 availableLanguages,
39 isRTL: ['ar', 'he', 'fa'].includes(language)
40 }}>
41 {children}
42 </LanguageContext.Provider>
43 );
44}
45
46export const useLanguage = () => {
47 const context = useContext(LanguageContext);
48 if (!context) {
49 throw new Error('useLanguage must be used within LanguageProvider');
50 }
51 return context;
52};
53
54// LanguageSelector.jsx
55function LanguageSelector() {
56 const { language, changeLanguage, availableLanguages } = useLanguage();
57 const { t } = useTranslation();
58
59 return (
60 <div className="language-selector">
61 <select
62 value={language}
63 onChange={(e) => changeLanguage(e.target.value)}
64 aria-label={t('common:select_language')}
65 >
66 {availableLanguages.map(lang => (
67 <option key={lang.code} value={lang.code}>
68 {lang.flag} {lang.name}
69 </option>
70 ))}
71 </select>
72 </div>
73 );
74}1// DateFormatter.jsx
2import { useTranslation } from 'react-i18next';
3
4function DateFormatter({ date, format = 'short' }) {
5 const { i18n } = useTranslation();
6
7 const formatDate = (date, format) => {
8 const formatOptions = {
9 short: {
10 year: 'numeric',
11 month: 'short',
12 day: 'numeric'
13 },
14 long: {
15 year: 'numeric',
16 month: 'long',
17 day: 'numeric',
18 weekday: 'long'
19 },
20 time: {
21 hour: '2-digit',
22 minute: '2-digit'
23 },
24 datetime: {
25 year: 'numeric',
26 month: 'short',
27 day: 'numeric',
28 hour: '2-digit',
29 minute: '2-digit'
30 }
31 };
32
33 return new Intl.DateTimeFormat(
34 i18n.language,
35 formatOptions[format]
36 ).format(new Date(date));
37 };
38
39 return <span>{formatDate(date, format)}</span>;
40}
41
42// Usage
43function EventCard({ event }) {
44 return (
45 <div className="event-card">
46 <h3>{event.title}</h3>
47 <DateFormatter date={event.startDate} format="long" />
48 <DateFormatter date={event.startTime} format="time" />
49 </div>
50 );
51}1// NumberFormatter.jsx
2import { useTranslation } from 'react-i18next';
3
4function NumberFormatter({
5 value,
6 type = 'decimal',
7 currency = 'USD',
8 minimumFractionDigits = 0,
9 maximumFractionDigits = 2
10}) {
11 const { i18n } = useTranslation();
12
13 const formatNumber = (value, type) => {
14 const options = {
15 minimumFractionDigits,
16 maximumFractionDigits
17 };
18
19 if (type === 'currency') {
20 options.style = 'currency';
21 options.currency = currency;
22 } else if (type === 'percent') {
23 options.style = 'percent';
24 }
25
26 return new Intl.NumberFormat(i18n.language, options).format(value);
27 };
28
29 return <span>{formatNumber(value, type)}</span>;
30}
31
32// PriceDisplay.jsx
33function PriceDisplay({ price, currency, originalPrice }) {
34 const { t } = useTranslation();
35
36 return (
37 <div className="price-display">
38 <span className="current-price">
39 <NumberFormatter
40 value={price}
41 type="currency"
42 currency={currency}
43 />
44 </span>
45
46 {originalPrice && originalPrice > price && (
47 <span className="original-price">
48 <NumberFormatter
49 value={originalPrice}
50 type="currency"
51 currency={currency}
52 />
53 </span>
54 )}
55
56 {originalPrice && originalPrice > price && (
57 <span className="discount">
58 {t('product.discount', {
59 percent: ((originalPrice - price) / originalPrice * 100).toFixed(0)
60 })}
61 </span>
62 )}
63 </div>
64 );
65}1// LazyTranslation.jsx
2import { useState, useEffect } from 'react';
3import { useTranslation } from 'react-i18next';
4
5function LazyTranslation({ namespace, children }) {
6 const { t, i18n, ready } = useTranslation(namespace);
7 const [isLoading, setIsLoading] = useState(!ready);
8
9 useEffect(() => {
10 if (!ready) {
11 i18n.loadNamespaces(namespace).then(() => {
12 setIsLoading(false);
13 });
14 } else {
15 setIsLoading(false);
16 }
17 }, [namespace, ready, i18n]);
18
19 if (isLoading) {
20 return <div className="loading">Loading translations...</div>;
21 }
22
23 return children;
24}
25
26// Usage
27function ProductPage() {
28 return (
29 <LazyTranslation namespace="products">
30 <ProductDetails />
31 <ProductReviews />
32 </LazyTranslation>
33 );
34}1// TranslationSuspense.jsx
2import { Suspense } from 'react';
3import { useTranslation } from 'react-i18next';
4
5function TranslationLoader({ namespace, children }) {
6 const { ready } = useTranslation(namespace, { useSuspense: true });
7
8 if (!ready) {
9 throw new Promise((resolve) => {
10 // Simulate loading
11 setTimeout(resolve, 100);
12 });
13 }
14
15 return children;
16}
17
18function App() {
19 return (
20 <div>
21 <Suspense fallback={<div>Loading translations...</div>}>
22 <TranslationLoader namespace="common">
23 <Header />
24 </TranslationLoader>
25 </Suspense>
26
27 <Suspense fallback={<div>Loading page content...</div>}>
28 <TranslationLoader namespace="dashboard">
29 <Dashboard />
30 </TranslationLoader>
31 </Suspense>
32 </div>
33 );
34}1// next-i18next.config.js
2module.exports = {
3 i18n: {
4 defaultLocale: 'en',
5 locales: ['en', 'pl', 'es', 'fr', 'de'],
6 localePath: './public/locales',
7 defaultNS: 'common',
8 fallbackLng: {
9 'en-US': ['en'],
10 'pl-PL': ['pl'],
11 default: ['en']
12 }
13 }
14};
15
16// pages/_app.js
17import { appWithTranslation } from 'next-i18next';
18import { LanguageProvider } from '../components/LanguageProvider';
19
20function MyApp({ Component, pageProps }) {
21 return (
22 <LanguageProvider>
23 <Component {...pageProps} />
24 </LanguageProvider>
25 );
26}
27
28export default appWithTranslation(MyApp);
29
30// pages/index.js
31import { serverSideTranslations } from 'next-i18next/serverSideTranslations';
32import { useTranslation } from 'next-i18next';
33
34function HomePage() {
35 const { t } = useTranslation('common');
36
37 return (
38 <div>
39 <h1>{t('welcome.title')}</h1>
40 <p>{t('welcome.description')}</p>
41 </div>
42 );
43}
44
45export async function getStaticProps({ locale }) {
46 return {
47 props: {
48 ...(await serverSideTranslations(locale, ['common', 'navigation']))
49 }
50 };
51}
52
53export default HomePage;1// LocalizedHead.jsx
2import Head from 'next/head';
3import { useTranslation } from 'next-i18next';
4import { useRouter } from 'next/router';
5
6function LocalizedHead({
7 title,
8 description,
9 keywords,
10 ogImage,
11 canonical
12}) {
13 const { t, i18n } = useTranslation('meta');
14 const router = useRouter();
15
16 const localizedTitle = title ? t(title) : t('default.title');
17 const localizedDescription = description ? t(description) : t('default.description');
18 const localizedKeywords = keywords ? t(keywords) : t('default.keywords');
19
20 const currentUrl = `https://example.com${router.asPath}`;
21 const canonicalUrl = canonical || currentUrl;
22
23 return (
24 <Head>
25 <title>{localizedTitle}</title>
26 <meta name="description" content={localizedDescription} />
27 <meta name="keywords" content={localizedKeywords} />
28 <meta name="language" content={i18n.language} />
29
30 {/* Open Graph */}
31 <meta property="og:title" content={localizedTitle} />
32 <meta property="og:description" content={localizedDescription} />
33 <meta property="og:url" content={currentUrl} />
34 <meta property="og:locale" content={i18n.language} />
35 <meta property="og:image" content={ogImage} />
36
37 {/* Twitter Card */}
38 <meta name="twitter:title" content={localizedTitle} />
39 <meta name="twitter:description" content={localizedDescription} />
40
41 {/* Canonical URL */}
42 <link rel="canonical" href={canonicalUrl} />
43
44 {/* Alternate language versions */}
45 {i18n.options.locales?.map(locale => (
46 <link
47 key={locale}
48 rel="alternate"
49 hrefLang={locale}
50 href={`https://example.com/${locale}${router.asPath}`}
51 />
52 ))}
53 </Head>
54 );
55}1// test-utils.js
2import { render } from '@testing-library/react';
3import { I18nextProvider } from 'react-i18next';
4import i18n from './i18n-test-config';
5
6const AllTheProviders = ({ children }) => {
7 return (
8 <I18nextProvider i18n={i18n}>
9 {children}
10 </I18nextProvider>
11 );
12};
13
14const customRender = (ui, options) =>
15 render(ui, { wrapper: AllTheProviders, ...options });
16
17export * from '@testing-library/react';
18export { customRender as render };
19
20// i18n-test-config.js
21import i18n from 'i18next';
22import { initReactI18next } from 'react-i18next';
23
24i18n.use(initReactI18next).init({
25 lng: 'en',
26 fallbackLng: 'en',
27 debug: false,
28 interpolation: {
29 escapeValue: false
30 },
31 resources: {
32 en: {
33 common: {
34 'welcome.title': 'Welcome',
35 'button.submit': 'Submit'
36 }
37 }
38 }
39});
40
41export default i18n;1// Component.test.js
2import { render, screen } from './test-utils';
3import { act } from '@testing-library/react';
4import WelcomeComponent from './WelcomeComponent';
5
6describe('WelcomeComponent', () => {
7 test('renders welcome message', () => {
8 render(<WelcomeComponent />);
9
10 expect(screen.getByText('Welcome')).toBeInTheDocument();
11 });
12
13 test('changes language', async () => {
14 const { rerender } = render(<WelcomeComponent />);
15
16 // Change language
17 await act(async () => {
18 i18n.changeLanguage('pl');
19 });
20
21 rerender(<WelcomeComponent />);
22
23 expect(screen.getByText('Witaj')).toBeInTheDocument();
24 });
25});1// OptimizedTranslation.jsx
2import { memo, useMemo } from 'react';
3import { useTranslation } from 'react-i18next';
4
5const OptimizedTranslation = memo(function OptimizedTranslation({
6 translationKey,
7 values,
8 namespace = 'common'
9}) {
10 const { t } = useTranslation(namespace);
11
12 const translatedText = useMemo(() => {
13 return t(translationKey, values);
14 }, [t, translationKey, values]);
15
16 return <span>{translatedText}</span>;
17});
18
19// TranslationCache.jsx - Custom hook with cache
20function useTranslationCache() {
21 const { t, i18n } = useTranslation();
22 const cache = useMemo(() => new Map(), [i18n.language]);
23
24 const getCachedTranslation = useCallback((key, options) => {
25 const cacheKey = JSON.stringify({ key, options });
26
27 if (cache.has(cacheKey)) {
28 return cache.get(cacheKey);
29 }
30
31 const translation = t(key, options);
32 cache.set(cacheKey, translation);
33
34 return translation;
35 }, [t, cache]);
36
37 return getCachedTranslation;
38}1// webpack.config.js
2module.exports = {
3 optimization: {
4 splitChunks: {
5 cacheGroups: {
6 i18n: {
7 test: /[\/]locales[\/]/,
8 name: 'i18n',
9 chunks: 'all'
10 }
11 }
12 }
13 }
14};
15
16// Dynamic imports for translations
17const loadTranslations = async (language) => {
18 const translations = await import(`../locales/${language}/common.json`);
19 return translations.default;
20};1// Good structure
2{
3 "pages": {
4 "home": {
5 "title": "Home Page",
6 "meta": {
7 "description": "Welcome to our homepage"
8 }
9 },
10 "about": {
11 "title": "About Us",
12 "sections": {
13 "team": "Our Team",
14 "history": "Company History"
15 }
16 }
17 },
18 "components": {
19 "navigation": {
20 "menu": "Menu",
21 "close": "Close"
22 },
23 "forms": {
24 "validation": {
25 "required": "This field is required",
26 "email": "Please enter a valid email"
27 }
28 }
29 },
30 "common": {
31 "actions": {
32 "save": "Save",
33 "cancel": "Cancel",
34 "delete": "Delete"
35 }
36 }
37}1// types/i18n.ts
2export interface TranslationKeys {
3 'welcome.title': string;
4 'welcome.description': string;
5 'navigation.home': string;
6 'navigation.about': string;
7 'forms.name.label': string;
8 'forms.email.
9}
10
11// Custom hook with typing
12import { useTranslation as useI18nTranslation } from 'react-i18next';
13
14export function useTranslation(namespace?: string) {
15 const { t, ...rest } = useI18nTranslation(namespace);
16
17 return {
18 t: (key: keyof TranslationKeys, options?: any) => t(key, options),
19 ...rest
20 };
21}
22
23// Components with typing
24interface TranslatedTextProps {
25 translationKey: keyof TranslationKeys;
26 values?: Record<string, any>;
27}
28
29function TranslatedText({ translationKey, values }: TranslatedTextProps) {
30 const { t } = useTranslation();
31 return <span>{t(translationKey, values)}</span>;
32}React-i18next is a comprehensive solution for internationalizing React applications. It enables easy translation management, local data formatting, and creating applications accessible to users from around the world. The key to success is proper translation organization, performance optimization, and testing different language scenarios.