Fix: i18next Not Working — Translations Missing, Language Not Switching, or Namespace Errors
Quick Answer
How to fix i18next issues — react-i18next setup, translation file loading, namespace configuration, language detection, interpolation, pluralization, and Next.js integration.
The Problem
Translation keys render as raw strings instead of translated text:
const { t } = useTranslation();
return <h1>{t('welcome')}</h1>;
// Renders: "welcome" instead of "Welcome to our app"Or language switching does nothing:
i18n.changeLanguage('ja');
// UI doesn't update — still shows EnglishOr namespace loading fails:
i18next: key "common:greeting" for languages "en" won't get resolvedWhy This Happens
i18next is the most widely used JavaScript internationalization framework. react-i18next provides React bindings. Common issues:
- i18next must be initialized before rendering —
useTranslation()returns raw keys if i18next hasn’t loaded translations yet. Initialization is async when loading translations from files or URLs. - Translation files must match the configured path — i18next-http-backend fetches translations from a URL pattern like
/locales/{{lng}}/{{ns}}.json. If the file doesn’t exist at that path, translations silently fail. - Namespaces must be loaded before use —
useTranslation('dashboard')loads thedashboardnamespace. If it doesn’t exist or hasn’t loaded, keys from that namespace return as-is. - Language detection has a priority order — i18next-browser-languagedetector checks cookies, localStorage, navigator.language, etc. If the detected language doesn’t have translations, it falls back to
fallbackLng.
Fix 1: React Setup
npm install i18next react-i18next i18next-http-backend i18next-browser-languagedetector// lib/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) // Load translations from files
.use(LanguageDetector) // Auto-detect user language
.use(initReactI18next) // React bindings
.init({
fallbackLng: 'en',
supportedLngs: ['en', 'ja', 'es', 'fr', 'de'],
// Default namespace
defaultNS: 'common',
ns: ['common', 'auth', 'dashboard'],
// Backend — where to load translation files
backend: {
loadPath: '/locales/{{lng}}/{{ns}}.json',
},
// Language detection order
detection: {
order: ['localStorage', 'cookie', 'navigator', 'htmlTag'],
caches: ['localStorage', 'cookie'],
},
// Interpolation
interpolation: {
escapeValue: false, // React already escapes
},
// Debug — shows warnings in console
debug: process.env.NODE_ENV === 'development',
});
export default i18n;// public/locales/en/common.json
{
"welcome": "Welcome to our app",
"greeting": "Hello, {{name}}!",
"items_count": "{{count}} item",
"items_count_plural": "{{count}} items",
"nav": {
"home": "Home",
"about": "About",
"contact": "Contact"
}
}
// public/locales/ja/common.json
{
"welcome": "アプリへようこそ",
"greeting": "こんにちは、{{name}}さん!",
"items_count": "{{count}}個のアイテム",
"nav": {
"home": "ホーム",
"about": "概要",
"contact": "お問い合わせ"
}
}// main.tsx — import i18n before rendering
import './lib/i18n'; // Must import before App
import { Suspense } from 'react';
function App() {
return (
<Suspense fallback={<div>Loading translations...</div>}>
<MainContent />
</Suspense>
);
}Fix 2: Use Translations in Components
'use client';
import { useTranslation, Trans } from 'react-i18next';
function HomePage() {
const { t, i18n } = useTranslation(); // Uses 'common' namespace by default
return (
<div>
<h1>{t('welcome')}</h1>
<p>{t('greeting', { name: 'Alice' })}</p>
<p>{t('items_count', { count: 5 })}</p>
{/* Nested keys */}
<nav>
<a href="/">{t('nav.home')}</a>
<a href="/about">{t('nav.about')}</a>
</nav>
{/* Different namespace */}
<DashboardContent />
{/* Language switcher */}
<div>
<button onClick={() => i18n.changeLanguage('en')}>English</button>
<button onClick={() => i18n.changeLanguage('ja')}>日本語</button>
<button onClick={() => i18n.changeLanguage('es')}>Español</button>
</div>
{/* Current language */}
<p>Current: {i18n.language}</p>
</div>
);
}
// Use a specific namespace
function DashboardContent() {
const { t } = useTranslation('dashboard');
return <h2>{t('title')}</h2>; // Reads from dashboard.json
}
// Multiple namespaces
function AuthPage() {
const { t } = useTranslation(['auth', 'common']);
return (
<div>
<h1>{t('auth:login_title')}</h1>
<p>{t('common:welcome')}</p>
</div>
);
}
// Trans component — for HTML in translations
// common.json: { "terms": "By signing up, you agree to our <link>Terms</link>" }
function Terms() {
const { t } = useTranslation();
return (
<Trans
i18nKey="terms"
components={{ link: <a href="/terms" /> }}
/>
);
}Fix 3: Pluralization and Formatting
// public/locales/en/common.json
{
"items_zero": "No items",
"items_one": "{{count}} item",
"items_other": "{{count}} items",
"messages": {
"unread_zero": "No unread messages",
"unread_one": "You have {{count}} unread message",
"unread_other": "You have {{count}} unread messages"
},
"date_format": "Last updated: {{date, datetime}}",
"price": "Price: {{amount, currency(USD)}}",
"relative_time": "{{val, relativetime(quarter)}}"
}// Pluralization
t('items', { count: 0 }); // "No items"
t('items', { count: 1 }); // "1 item"
t('items', { count: 5 }); // "5 items"
// Date formatting
t('date_format', { date: new Date(), formatParams: { date: { dateStyle: 'long' } } });
// "Last updated: March 29, 2026"
// Number formatting
t('price', { amount: 49.99 });
// "Price: $49.99"
// i18n config for formatting
i18n.init({
interpolation: {
escapeValue: false,
format: (value, format, lng) => {
if (format === 'uppercase') return value.toUpperCase();
if (value instanceof Date) {
return new Intl.DateTimeFormat(lng).format(value);
}
return value;
},
},
});Fix 4: Next.js App Router Integration
// For Next.js, consider next-intl (simpler) or use i18next with SSR:
// lib/i18n-server.ts — server-side translations
import { createInstance } from 'i18next';
import { initReactI18next } from 'react-i18next/initReactI18next';
import resourcesToBackend from 'i18next-resources-to-backend';
export async function initI18n(lng: string, ns: string | string[] = 'common') {
const i18nInstance = createInstance();
await i18nInstance
.use(initReactI18next)
.use(resourcesToBackend((language: string, namespace: string) =>
import(`@/locales/${language}/${namespace}.json`)
))
.init({
lng,
ns,
fallbackLng: 'en',
interpolation: { escapeValue: false },
});
return i18nInstance;
}
// app/[lng]/page.tsx — Server Component with translations
export default async function HomePage({ params }: { params: { lng: string } }) {
const { lng } = await params;
const i18n = await initI18n(lng);
const t = i18n.getFixedT(lng, 'common');
return (
<div>
<h1>{t('welcome')}</h1>
<p>{t('greeting', { name: 'Alice' })}</p>
</div>
);
}
// middleware.ts — language detection and routing
import { NextResponse, type NextRequest } from 'next/server';
const locales = ['en', 'ja', 'es'];
const defaultLocale = 'en';
export function middleware(request: NextRequest) {
const pathname = request.nextUrl.pathname;
// Check if pathname already has a locale
const hasLocale = locales.some(
locale => pathname.startsWith(`/${locale}/`) || pathname === `/${locale}`
);
if (hasLocale) return;
// Detect locale from Accept-Language header
const acceptLang = request.headers.get('accept-language') || '';
const detected = locales.find(l => acceptLang.includes(l)) || defaultLocale;
return NextResponse.redirect(new URL(`/${detected}${pathname}`, request.url));
}
export const config = {
matcher: ['/((?!api|_next|.*\\..*).*)'],
};Fix 5: TypeScript Type Safety
// i18n.d.ts — type-safe translation keys
import 'i18next';
import common from '../public/locales/en/common.json';
import auth from '../public/locales/en/auth.json';
import dashboard from '../public/locales/en/dashboard.json';
declare module 'i18next' {
interface CustomTypeOptions {
defaultNS: 'common';
resources: {
common: typeof common;
auth: typeof auth;
dashboard: typeof dashboard;
};
}
}
// Now t('welcome') autocompletes
// t('nonexistent') shows a TypeScript errorFix 6: Lazy Loading and Performance
// Load namespaces on demand
i18n.init({
partialBundledLanguages: true,
ns: ['common'], // Only load common initially
backend: {
loadPath: '/locales/{{lng}}/{{ns}}.json',
},
});
// Load additional namespace when needed
function DashboardPage() {
const { t, ready } = useTranslation('dashboard', { useSuspense: false });
if (!ready) return <div>Loading...</div>;
return <h1>{t('title')}</h1>;
}
// Preload a namespace
i18n.loadNamespaces('settings').then(() => {
// Namespace is now available
});
// Preload a language
i18n.loadLanguages('ja').then(() => {
// Japanese translations loaded
});Still Not Working?
Keys render as-is (e.g., “welcome” instead of translated text) — translations haven’t loaded. Check the network tab for the JSON file request. Verify the file path matches loadPath in the backend config. Also wrap your app in <Suspense> — i18next loads translations async.
changeLanguage() doesn’t update the UI — ensure react-i18next is initialized with use(initReactI18next). Without it, React doesn’t re-render when the language changes. Also check that translations for the target language exist.
Pluralization returns the wrong form — i18next v21+ uses ICU-style plural keys: _zero, _one, _two, _few, _many, _other. The old format (_plural) still works but only for English. For other languages, use the correct plural forms for that locale.
TypeScript doesn’t autocomplete keys — add the CustomTypeOptions module augmentation. The resource types must match your actual JSON structure. Restart the TypeScript language server after adding the declarations.
For related i18n issues, see Fix: next-intl Not Working and Fix: date-fns Not Working.
Solo developer based in Japan. Every solution is cross-referenced with official documentation and tested before publishing.
Was this article helpful?
Related Articles
Fix: Mapbox GL JS Not Working — Map Not Rendering, Markers Missing, or Access Token Invalid
How to fix Mapbox GL JS issues — access token setup, React integration with react-map-gl, markers and popups, custom layers, geocoding, directions, and Next.js configuration.
Fix: React PDF Not Working — PDF Not Rendering, Worker Error, or Pages Blank
How to fix react-pdf and @react-pdf/renderer issues — PDF viewer setup, worker configuration, page rendering, text selection, annotations, and generating PDFs in React.
Fix: Million.js Not Working — Compiler Errors, Components Not Optimized, or React Compatibility Issues
How to fix Million.js issues — compiler setup with Vite and Next.js, block() optimization rules, component compatibility constraints, automatic mode, and debugging performance gains.
Fix: Radix UI Not Working — Popover Not Opening, Dialog Closing Immediately, or Styling Breaking
How to fix Radix UI issues — Popover and Dialog setup, controlled vs uncontrolled state, portal rendering, animation with CSS or Framer Motion, accessibility traps, and Tailwind CSS integration.