i18next
i18next Internationalization Skill
i18next is a JavaScript internationalization framework that provides a complete solution for localizing products across web, mobile, and desktop platforms. It goes beyond standard i18n features with support for plurals, context, interpolation, formatting, namespaces, and a rich plugin ecosystem.
When to read reference files
This skill is organized with reference files for deeper topics. Read the relevant reference file before writing code:
references/configuration.md— Read when setting up i18next init, configuring languages, namespaces, fallbacks, missing keys, or loading translations. Also read for backend plugins, language detection, and resource loading strategies.references/translation-functions.md— Read when usingt()function features: interpolation, plurals, context, formatting (number, currency, datetime, relative time, list), nesting, objects/arrays, or any translation string patterns.references/typescript.md— Read when setting up type-safe translations, creating declaration files, configuring CustomTypeOptions, using the selector API (enableSelector), or troubleshooting TypeScript issues.references/patterns-and-plugins.md— Read when implementing best practices, common patterns (namespace organization, key naming, fallback strategies), creating custom plugins, or migrating between i18next versions.
Quick Start
Install i18next:
npm install i18next --save
Basic initialization:
import i18next from 'i18next';
i18next.init({
lng: 'en', // omit if using a language detector
debug: true,
resources: {
en: {
translation: {
"key": "hello world"
}
}
}
});
// Use translations
i18next.t('key'); // -> "hello world"
init() returns a Promise and also accepts a callback. Always wait for initialization to complete before calling t().
Core Concepts Overview
Translation Function (t)
The t() function is the primary way to access translations. It supports:
- Simple keys:
i18next.t('key')or nestedi18next.t('look.deep') - Default values:
i18next.t('key', 'default value') - Namespace access:
i18next.t('button.save', { ns: 'common' }) - Multiple fallback keys:
i18next.t(['error.404', 'error.unspecific']) - Language override:
i18next.t('key', { lng: 'de' })
Interpolation
Dynamic values in translations use {{variable}} syntax:
{ "greeting": "Hello {{name}}, you have {{count}} messages" }
i18next.t('greeting', { name: 'John', count: 5 });
// -> "Hello John, you have 5 messages"
Values are escaped by default to prevent XSS. Use {{- variable}} for unescaped output.
Plurals (JSON v4 format)
Pluralization uses the Intl.PluralRules API with suffixes _zero, _one, _two, _few, _many, _other:
{
"item_one": "{{count}} item",
"item_other": "{{count}} items"
}
i18next.t('item', { count: 1 }); // -> "1 item"
i18next.t('item', { count: 5 }); // -> "5 items"
The count variable name is required and must be provided. Languages with multiple plural forms (like Arabic) use all six suffixes.
Context
Differentiate translations based on context (e.g., gender):
{
"friend": "A friend",
"friend_male": "A boyfriend",
"friend_female": "A girlfriend"
}
i18next.t('friend', { context: 'male' }); // -> "A boyfriend"
Context can be combined with plurals: friend_male_one, friend_male_other.
Formatting (v21.3.0+)
Built-in Intl API-based formatters for number, currency, datetime, relativetime, and list:
{ "price": "Total: {{val, currency(USD)}}" }
i18next.t('price', { val: 2000 }); // -> "Total: $2,000.00"
Namespaces
Separate translations into multiple files for organization and lazy loading:
i18next.init({
ns: ['common', 'moduleA'],
defaultNS: 'moduleA',
fallbackNS: 'common'
});
Nesting
Reference other translation keys within translations using $t():
{
"greeting": "Hello",
"welcome": "$t(greeting), welcome back!"
}
Key Configuration
- keySeparator (default
'.'): Set tofalsefor flat JSON structures - nsSeparator (default
':'): Separates namespace from key - Keys should not contain
:or.unless separators are disabled - For natural language keys, set
keySeparator: false,nsSeparator: false, andfallbackLng: false
Common Setup Patterns
React with react-i18next
import i18next from 'i18next';
import { initReactI18next } from 'react-i18next';
import Backend from 'i18next-http-backend';
import LanguageDetector from 'i18next-browser-languagedetector';
i18next
.use(Backend)
.use(LanguageDetector)
.use(initReactI18next)
.init({
fallbackLng: 'en',
debug: true,
interpolation: { escapeValue: false } // React already escapes
});
Node.js with filesystem backend
import i18next from 'i18next';
import Backend from 'i18next-fs-backend';
i18next
.use(Backend)
.init({
initImmediate: false, // synchronous loading
fallbackLng: 'en',
backend: { loadPath: './locales/{{lng}}/{{ns}}.json' }
});
Lazy loading translations (webpack/vite)
import resourcesToBackend from 'i18next-resources-to-backend';
i18next
.use(resourcesToBackend((lng, ns) => import(`./locales/${lng}/${ns}.json`)))
.init({ fallbackLng: 'en' });
Important Caveats
- Do not call
init()multiple times. UsechangeLanguage()to switch languages, orcreateInstance()/cloneInstance()for different configs. - Wait for init before using
t()— use the callback, Promise, or async/await pattern. - Language codes: Use BCP 47 format (
en-US, noten_US). Use dash, not underscore. - Intl.PluralRules polyfill: Required for plural support in older environments. Install
intl-pluralrules. - Interpolation and localization: Use interpolation sparingly — sentence fragments with interpolated values can be difficult or impossible to translate correctly due to grammar differences across languages. Use separate self-contained strings when the value is known at build time.
- JSON v4 format (default since v21): Uses
_one/_othersuffixes instead of the old_plural. Use i18next-v4-format-converter to migrate. - Testing: Set
lng: 'cimode'to havet()always return the key — useful for e2e tests.
API Quick Reference
| Method | Description |
|---|---|
init(options, cb) |
Initialize i18next (returns Promise) |
t(key, options) |
Translate a key |
changeLanguage(lng, cb) |
Change current language (returns Promise) |
use(plugin) |
Register a plugin (call before init) |
exists(key, options) |
Check if a key exists |
getFixedT(lng, ns, keyPrefix) |
Get a t function with fixed lng/ns/prefix |
loadNamespaces(ns, cb) |
Load additional namespaces |
loadLanguages(lngs, cb) |
Load additional languages |
addResourceBundle(lng, ns, resources, deep, overwrite) |
Add translation resources |
getResource(lng, ns, key) |
Get a single translation value |
language |
Current detected/set language |
resolvedLanguage |
Current resolved language (for UI) |
languages |
Array of language codes for lookup |
dir(lng) |
Returns 'rtl' or 'ltr' |
createInstance(options, cb) |
Create a new independent instance |
cloneInstance(options) |
Clone sharing store and plugins |
Events
i18next.on('initialized', (options) => {});
i18next.on('languageChanged', (lng) => {});
i18next.on('loaded', (loaded) => {});
i18next.on('failedLoading', (lng, ns, msg) => {});
i18next.on('missingKey', (lngs, ns, key, res) => {}); // needs saveMissing: true
i18next.off('eventName', handler); // unsubscribe