i18next JSON format: namespaces, {{placeholders}}, and _one/_other plurals
i18next is the most popular JavaScript i18n framework. Its JSON locale files support namespaces, double-brace interpolation, CLDR plural suffixes, context variants, and cross-key references. Here is a complete guide to the format — with examples for every feature.
Folder structure
i18next organises locale files by language and namespace. Each namespace is a separate JSON file inside a language folder:
locales/
├── en/
│ ├── common.json ← default namespace
│ ├── dashboard.json
│ └── validation.json
├── de/
│ ├── common.json
│ ├── dashboard.json
│ └── validation.json
└── ja/
├── common.json
├── dashboard.json
└── validation.jsonThe folder name matches the language code (en, de, ja) and each file name is the namespace (common, dashboard). This path pattern is configured in the i18next backend:
backend: {
loadPath: '/locales/{{lng}}/{{ns}}.json',
}Basic JSON format
i18next supports both flat and nested keys. Flat keys use a dot separator by default, while nested keys use standard JSON nesting:
{
"welcome": "Welcome back!",
"nav.home": "Home",
"nav.settings": "Settings"
}The equivalent nested version:
{
"welcome": "Welcome back!",
"nav": {
"home": "Home",
"settings": "Settings"
}
}Both forms resolve identically with t("nav.home"). The nested version is more common in larger projects because it groups related keys visually.
{{placeholder}} interpolation
Dynamic values are inserted with double curly braces. The variable name inside the braces must match the key you pass to t():
{
"greeting": "Hello, {{name}}!",
"itemCount": "You have {{count}} items in your cart."
}Usage in code:
t('greeting', { name: 'Alice' })
// → "Hello, Alice!"
t('itemCount', { count: 5 })
// → "You have 5 items in your cart."{{}} by default. Single braces {} will be treated as literal text, not interpolation. This is a common mistake when coming from ICU MessageFormat.Plural keys: _one, _other, _zero, _few, _many
i18next uses CLDR plural categories as key suffixes. When you pass a count option, i18next automatically picks the right suffix for the current language.
English uses two forms — _one and _other:
{
"message_one": "You have {{count}} message",
"message_other": "You have {{count}} messages"
}t('message', { count: 1 }) // → "You have 1 message"
t('message', { count: 5 }) // → "You have 5 messages"
t('message', { count: 0 }) // → "You have 0 messages"Polish has four forms — _one, _few, _many, and _other:
{
"message_one": "Masz {{count}} wiadomość",
"message_few": "Masz {{count}} wiadomości",
"message_many": "Masz {{count}} wiadomości",
"message_other": "Masz {{count}} wiadomości"
}_other. It is the required fallback. Omitting it causes i18next to return the raw key name instead of a translated string.Namespaces in practice
Namespaces let you split translations into logical groups. You configure them in the i18next init call:
import i18next from 'i18next';
i18next.init({
lng: 'en',
ns: ['common', 'dashboard', 'validation'],
defaultNS: 'common',
backend: {
loadPath: '/locales/{{lng}}/{{ns}}.json',
},
});To reference a key from a specific namespace, prefix with the namespace name and a colon:
t('dashboard:stats.totalUsers')
t('validation:email.invalid')
t('welcome') // uses defaultNS → common.jsonWith react-i18next, you can bind a component to a namespace via the useTranslation hook:
import { useTranslation } from 'react-i18next';
function Dashboard() {
const { t } = useTranslation('dashboard');
return <h1>{t('stats.totalUsers')}</h1>;
// reads from locales/en/dashboard.json → stats.totalUsers
}Context-based keys
Context lets you vary a translation based on a non-numeric dimension — gender, formality, or any custom category. Add a suffix with an underscore:
{
"friend": "A friend",
"friend_male": "A boyfriend",
"friend_female": "A girlfriend"
}t('friend', { context: 'male' }) // → "A boyfriend"
t('friend', { context: 'female' }) // → "A girlfriend"
t('friend') // → "A friend"You can combine context with plurals. i18next resolves the most specific key first:
{
"friend_male_one": "{{count}} boyfriend",
"friend_male_other": "{{count}} boyfriends",
"friend_female_one": "{{count}} girlfriend",
"friend_female_other": "{{count}} girlfriends"
}t('friend', { context: 'female', count: 3 })
// → "3 girlfriends"Nesting and $t() references
You can reference another translation key inside a value using the $t() syntax. This avoids duplicating shared strings:
{
"appName": "LocalePack",
"welcomeBanner": "Welcome to $t(appName)!",
"footerCopy": "© 2026 $t(appName). All rights reserved."
}t('welcomeBanner')
// → "Welcome to LocalePack!"
t('footerCopy')
// → "© 2026 LocalePack. All rights reserved."Nesting works across namespaces too: $t(common:appName) resolves the appName key from the common namespace.
Common mistakes
Missing _other plural key
Defining message_one without message_other. i18next requires _other as the fallback for every plural set. Without it, counts other than 1 return the raw key name.
Single braces instead of double braces
Writing {name} instead of {{name}}. i18next’s default interpolation requires double braces. Single braces are treated as literal text and the variable is never replaced.
Mismatched namespace file names
Naming the file Dashboard.json but configuring the namespace as dashboard. File names are case-sensitive on Linux. The namespace name must exactly match the JSON file name (without extension).
Forgetting to pass count for plurals
Calling t("message") without { count: n }. Without the count option, i18next cannot determine which plural form to use and falls back to the base key — which may not exist in a plural-only set.
Format summary
| Feature | i18next |
|---|---|
| File path | locales/{lng}/{ns}.json |
| Interpolation | {{variable}} |
| Plurals | _one, _other, _zero, _few, _many |
| Context | key_{context} |
| Nesting | $t(otherKey) |
| Namespace separator | : (colon) |
| Key nesting | . (dot) or nested JSON objects |
| Default namespace | translation (configurable) |
Translate your i18next namespace files with LocalePack
Upload your namespace JSON files and LocalePack translates them while preserving every {{placeholder}}, _one/_other plural keys, and $t() nesting references. Download a ready-to-use locales folder in minutes. Pay once, no subscription.