i18next — The Most Popular JavaScript Internationalization Library
In this tutorial, you will learn about i18next. We cover key concepts, practical examples, and best practices to help you master this topic.
i18next is a powerful JavaScript internationalization framework with detection, Caching, pluralization, and seamless integration with frontend frameworks.
What You'll Learn
By the end of this tutorial, you'll understand how to set up i18next, configure translation loading and detection, use advanced features like plurals and nesting, and integrate it with vanilla JavaScript applications.
Why It Matters
i18next is the most widely used i18n library in the JavaScript ecosystem, with over 28,000 GitHub stars and millions of weekly downloads. It handles locale detection, translation loading, caching, interpolation, pluralization, and formatting out of the box. Learning i18next gives you a transferable skill that works with React, Vue, Angular, and vanilla JS.
Real-World Use
A SaaS platform uses i18next with 15 language files loaded on demand. The library detects the user's locale from the browser, loads the correct translation JSON, and handles plural rules for Arabic (6 forms), Russian (4 forms), and Japanese (no plurals). Adding a 16th language requires only a new JSON file and locale config entry.
i18next Architecture
graph LR
A[i18next] --> B[Translation Files
locales/en/translation.json]
A --> C[Language Detector
Browser, cookie, path, query]
A --> D[Cache
localStorage, sessionStorage]
A --> E[Backend
HTTP, static, custom]
B --> F[Interpolation
Hello, {name}!]
B --> G[Plurals
item / items]
B --> H[Nesting
$t(shared.greeting)]
B --> I[Formatting
Date, number, currency]
style A fill:#4a90d9,color:#fff
Basic Setup
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<title>i18next Basic Setup</title>
</head>
<body>
<div id="app">
<h1 data-i18n="welcome">Loading...</h1>
<p data-i18n="description">Loading...</p>
<p>
<span data-i18n="items_label">Items:</span>
<span data-i18n="items_count" data-i18n-options='{"count": 5}'>5</span>
</p>
<button id="switch-en">English</button>
<button id="switch-es">Espanol</button>
<button id="switch-fr">Francais</button>
</div>
<script src="https://unpkg.com/i18next@23/dist/umd/i18next.min.js"></script>
<script>
// Translation resources (in production, load from separate files)
const resources = {
en: {
translation: {
welcome: 'Welcome to our application!',
description: 'This app uses i18next for internationalization.',
items_label: 'Items:',
items_count: '{{count}} item',
items_count_plural: '{{count}} items',
greeting: 'Hello, {{name}}!'
}
},
es: {
translation: {
welcome: '¡Bienvenido a nuestra aplicación!',
description: 'Esta aplicación utiliza i18next para internacionalización.',
items_label: 'Artículos:',
items_count: '{{count}} artículo',
items_count_plural: '{{count}} artículos',
greeting: '¡Hola, {{name}}!'
}
},
fr: {
translation: {
welcome: 'Bienvenue dans notre application !',
description: 'Cette application utilise i18next pour l\'internationalisation.',
items_label: 'Articles :',
items_count: '{{count}} article',
items_count_plural: '{{count}} articles',
greeting: 'Bonjour, {{name}} !'
}
}
};
// Initialize i18next
i18next.init({
lng: navigator.language.split('-')[0],
fallbackLng: 'en',
resources: resources,
interpolation: {
escapeValue: false // React already escapes
}
}, (err, t) => {
if (err) return console.error(err);
updateContent();
});
// Update DOM with translations
function updateContent() {
document.querySelectorAll('[data-i18n]').forEach(el => {
const key = el.dataset.i18n;
const options = el.dataset.i18nOptions
? JSON.parse(el.dataset.i18nOptions)
: {};
el.textContent = i18next.t(key, options);
});
}
// Locale switching
document.getElementById('switch-en').onclick = () => {
i18next.changeLanguage('en', updateContent);
};
document.getElementById('switch-es').onclick = () => {
i18next.changeLanguage('es', updateContent);
};
document.getElementById('switch-fr').onclick = () => {
i18next.changeLanguage('fr', updateContent);
};
</script>
</body>
</html>
Advanced Features
// i18n/advanced-i18next.js — Advanced i18next features
// 1. Namespaces — separate translations by domain
i18next.init({
ns: ['common', 'admin', 'errors'],
defaultNS: 'common',
resources: {
en: {
common: {
save: 'Save',
cancel: 'Cancel',
loading: 'Loading...'
},
admin: {
dashboard: 'Dashboard',
users: 'User Management',
settings: 'Settings'
},
errors: {
not_found: 'Page not found',
server_error: 'Server error occurred',
validation: 'Please check your input'
}
}
}
});
// Access with namespace prefix
console.log(i18next.t('common:save')); // Save
console.log(i18next.t('admin:dashboard')); // Dashboard
console.log(i18next.t('errors:not_found')); // Page not found
// 2. Interpolation with variables
console.log(i18next.t('greeting', { name: 'Alice' }));
// Hello, Alice!
// 3. Pluralization (automatic based on locale)
console.log(i18next.t('items_count', { count: 1 }));
// 1 item
console.log(i18next.t('items_count', { count: 5 }));
// 5 items
// 4. Context — different translations based on context
i18next.addResources('en', 'translation', {
'notification': 'You have a new notification',
'notification_male': 'He has a new notification',
'notification_female': 'She has a new notification'
});
console.log(i18next.t('notification', { context: 'male' }));
// He has a new notification
// 5. Nesting — reuse translations within translations
i18next.addResources('en', 'translation', {
'greeting': 'Hello, {{name}}!',
'welcome_back': '$t(greeting) Welcome back to {{app}}.'
});
console.log(i18next.t('welcome_back', { name: 'Bob', app: 'MyApp' }));
// Hello, Bob! Welcome back to MyApp.
// 6. Formatting with i18next
i18next.services.formatter?.add('uppercase', (value) => {
return value.toUpperCase();
});
i18next.addResources('en', 'translation', {
'welcome_formatted': 'Welcome, {{name, uppercase}}!'
});
console.log(i18next.t('welcome_formatted', { name: 'alice' }));
// Welcome, ALICE!
Lazy Loading Translations
// i18n/lazy-loading.js — Load translation files on demand
import i18next from 'i18next';
import LanguageDetector from 'i18next-browser-languagedetector';
import Backend from 'i18next-http-backend';
import { initReactI18next } from 'react-i18next';
i18next
.use(Backend) // Load translations from server
.use(LanguageDetector) // Auto-detect user language
.use(initReactI18next) // React integration (optional)
.init({
fallbackLng: 'en',
debug: false,
// Backend configuration — loads JSON files on demand
backend: {
// Path where translation files are served
loadPath: '/locales/{{lng}}/{{ns}}.json',
// Path for missing key lookup (optional)
addPath: '/locales/add/{{lng}}/{{ns}}',
// Allow cross-domain requests
crossDomain: false
},
// Detection configuration
detection: {
order: ['cookie', 'localStorage', 'navigator', 'htmlTag', 'path', 'subdomain'],
caches: ['cookie', 'localStorage']
},
// Only load the detected language (don't preload others)
load: 'languageOnly', // 'languageOnly' | 'currentOnly' | 'all'
// Namespace configuration
ns: ['common', 'admin', 'shop'],
defaultNS: 'common',
interpolation: {
escapeValue: false
}
});
// Translation files are now loaded on demand:
// /locales/en/common.json
// /locales/es/common.json
// /locales/en/admin.json
// /locales/fr/shop.json
// Usage
async function loadAdminTranslations() {
// Admin namespace loads only when needed
await i18next.loadNamespaces('admin');
console.log(i18next.t('admin:user_count', { count: 150 }));
}
Custom Backend Example
// i18n/custom-backend.js — Custom backend for loading from API
class CustomBackend {
constructor(services, backendOptions, i18nextOptions) {
this.services = services;
this.backendOptions = backendOptions;
this.cache = new Map();
}
// Required: Read translation resources
read(language, namespace, callback) {
const cacheKey = `${language}:${namespace}`;
// Check in-memory cache first
if (this.cache.has(cacheKey)) {
return callback(null, this.cache.get(cacheKey));
}
// Fetch from API
fetch(`/api/translations/${language}/${namespace}`)
.then(response => response.json())
.then(data => {
this.cache.set(cacheKey, data);
callback(null, data);
})
.catch(error => {
callback(error, null);
});
}
// Optional: Save new translations
create(languages, namespace, key, fallbackValue) {
fetch('/api/translations', {
method: 'POST',
headers: { 'Content-Type': 'application/json' },
body: JSON.stringify({
languages,
namespace,
key,
value: fallbackValue
})
});
}
// Optional: Clear cache
clearCache(language, namespace) {
if (language && namespace) {
this.cache.delete(`${language}:${namespace}`);
} else {
this.cache.clear();
}
}
}
// Register custom backend
i18next.use({
type: 'backend',
init: (services, backendOptions, i18nextOptions) => {
return new CustomBackend(services, backendOptions, i18nextOptions);
}
});
Common Mistakes
- Not setting fallbackLng. Without a fallback, missing translations display as blank or show the key name. Always set a fallback language and maintain complete translation coverage for at least that language.
- Forgetting escapeValue: false in React. i18next escapes HTML by default to prevent XSS. React already handles escaping, so double-escaping shows raw HTML entities. Set interpolation.escapeValue to false in React.
- Loading all translations upfront. For apps with 10+ languages, loading all translation files on page load wastes bandwidth. Use i18next-http-backend to load only the active language's translations.
- Ignoring namespace organization. Putting 2000+ translation keys in a single namespace makes maintenance difficult. Split by domain (common, admin, shop, errors) and load namespaces on demand.
- Not handling missing keys gracefully. Use saveMissing to automatically log or save missing keys during development. Set returnNull and returnEmptyString to false to show the key as fallback instead of blank.
Practice Questions
- How do you configure i18next to load translation files from a server?
- What is the difference between namespaces and languages in i18next?
- How does i18next handle pluralization for languages with multiple plural forms?
- What is the purpose of the fallbackLng option?
- How do you implement lazy loading of translation namespaces?
Challenge: Set up i18next with three languages (English, Spanish, German), three namespaces (common, admin, shop), lazy loading from JSON files, a language detector that checks cookie then localStorage then browser, context-based translations for gender, and a custom formatter for currency display.
FAQ
Mini Project
Build a multilingual dashboard using i18next with: three languages loaded from JSON files, auto-detection from browser settings, manual locale switcher, namespaces for different sections (common, dashboard, settings), interpolation with variables, pluralization, and a custom formatter for percentage values. The dashboard should update all text when the locale changes without a page reload.
What's Next
You've mastered i18next. Next, learn about react-i18next for seamless internationalization in React applications.
Built by the developers of DodaTech
Doda Browser, DodaZIP & Durga Antivirus Pro