Why JSON for Translations?
| Format | Used By | Pros | Cons |
|---|---|---|---|
| JSON | i18next, react-intl, vue-i18n, next-intl | Universal support, easy to parse, tooling-rich | No comments (use JSONC workarounds) |
| YAML | Ruby on Rails, some Python frameworks | Supports comments, less verbose | Indentation-sensitive, slower parsing |
| XLIFF | Enterprise tools, iOS localization | Industry standard, metadata support | Verbose XML, harder to edit manually |
| PO/POT | gettext, WordPress, Django | Mature ecosystem, translator-friendly | Legacy format, not JSON-native |
Basic Translation File Structure
Flat Keys
en/common.json — Flatjson
1{2 "nav.home": "Home",3 "nav.about": "About Us",4 "nav.contact": "Contact",5 "auth.login": "Log In",6 "auth.logout": "Log Out",7 "auth.signup": "Create Account",8 "error.notFound": "Page not found",9 "error.generic": "Something went wrong"10}Nested Keys (Recommended)
en/common.json — Nestedjson
1{2 "nav": {3 "home": "Home",4 "about": "About Us",5 "contact": "Contact"6 },7 "auth": {8 "login": "Log In",9 "logout": "Log Out",10 "signup": "Create Account",11 "forgotPassword": "Forgot your password?"12 },13 "error": {14 "notFound": "Page not found",15 "generic": "Something went wrong. Please try again.",16 "network": "Unable to connect. Check your internet connection."17 }18}Tip
Nested keys are easier to read and maintain. Group translations by feature or UI section. Most i18n libraries support dot-notation access (
t('auth.login')) regardless of whether the JSON is flat or nested.Namespaced Architecture
For large apps, split translations into namespace files loaded on demand:
File structuretext
1public/2 locales/3 en/4 common.json # Shared: nav, footer, buttons5 auth.json # Login, signup, password reset6 dashboard.json # Dashboard-specific strings7 settings.json # Settings page8 errors.json # Error messages9 es/10 common.json11 auth.json12 dashboard.json13 settings.json14 errors.json15 de/16 common.json17 ...Interpolation (Dynamic Values)
i18next interpolationjson
1{2 "greeting": "Hello, {{name}}!",3 "itemCount": "You have {{count}} items in your cart.",4 "lastLogin": "Last login: {{date, datetime}}",5 "price": "Total: {{amount, currency(USD)}}"6}react-intl (ICU MessageFormat)json
1{2 "greeting": "Hello, {name}!",3 "itemCount": "You have {count, number} items in your cart.",4 "lastLogin": "Last login: {date, date, medium}",5 "price": "Total: {amount, number, ::currency/USD}"6}Pluralization
i18next Plurals
en/common.jsonjson
1{2 "notification_one": "You have {{count}} notification",3 "notification_other": "You have {{count}} notifications",4 "notification_zero": "No notifications",5 "file_one": "{{count}} file selected",6 "file_other": "{{count}} files selected"7}Usagetypescript
1t('notification', { count: 0 }); // "No notifications"2t('notification', { count: 1 }); // "You have 1 notification"3t('notification', { count: 5 }); // "You have 5 notifications"ICU MessageFormat Plurals
ICU format (react-intl, next-intl)json
1{2 "notifications": "{count, plural, =0 {No notifications} one {# notification} other {# notifications}}",3 "files": "{count, plural, one {# file selected} other {# files selected}}"4}Important
Plural rules differ by language. English has 2 forms (one, other). Arabic has 6 forms. Russian has 3. The i18n library handles this automatically based on the locale — you just provide the translation for each form.
Using Translations in Frameworks
React (i18next / react-i18next)
1import { useTranslation } from 'react-i18next';23function Header() {4 const { t } = useTranslation('common');56 return (7 <nav>8 <a href="/">{t('nav.home')}</a>9 <a href="/about">{t('nav.about')}</a>10 <button>{t('auth.login')}</button>11 </nav>12 );13}Next.js (next-intl)
1import { useTranslations } from 'next-intl';23function Dashboard() {4 const t = useTranslations('dashboard');56 return (7 <div>8 <h1>{t('title')}</h1>9 <p>{t('welcome', { name: user.name })}</p>10 <p>{t('notifications', { count: unreadCount })}</p>11 </div>12 );13}Common Mistakes
1. Concatenating Translations
1// WRONG — word order differs by language2t('hello') + ' ' + userName + ', ' + t('welcome')34// CORRECT — use interpolation5t('greeting', { name: userName })6// JSON: "greeting": "Hello {{name}}, welcome back!"2. Embedding HTML in Translations
1// AVOID — mixing HTML with translations is fragile2{ "terms": "By clicking, you agree to our <a href='/terms'>Terms</a>" }34// BETTER — use rich text components5{ "terms": "By clicking, you agree to our <link>Terms</link>" }6// Then map <link> to your component in code3. Hardcoding Plural Logic
1// WRONG — English-only plural logic2const label = count === 1 ? '1 item' : `${count} items`;34// CORRECT — let the i18n library handle plurals per locale5const label = t('items', { count });Translation Management Workflow
Try It — Validate a Translation File
Try It Yourself
A valid i18n JSON translation file
Try These Tools
Continue Learning
Frequently Asked Questions
Why use JSON for translation files?
JSON is the most widely supported format for i18n. Libraries like i18next, react-intl, vue-i18n, and angular/localize all use JSON translation files. JSON is easy to parse, version-control, validate, and integrate with translation management systems.
Should I use flat or nested keys in translation JSON?
Nested keys ({"nav": {"home": "Home"}}) are more readable and group related translations. Flat keys ("nav.home": "Home") are simpler to search and avoid deep nesting. Most teams use nested keys for organization, and some i18n libraries support both formats interchangeably.
How do I handle plurals in JSON translations?
Most i18n libraries support ICU MessageFormat or library-specific plural keys. In i18next, use suffixes: {"items_one": "{{count}} item", "items_other": "{{count}} items"}. In react-intl, use ICU syntax: "{count, plural, one {# item} other {# items}}".
How should I organize translation files in a large project?
Use namespaces: split translations into files by feature or page (common.json, auth.json, dashboard.json). Each namespace is loaded on demand, reducing initial bundle size. Store files in /public/locales/{lang}/{namespace}.json for i18next or /messages/{lang}.json for next-intl.
How do I handle missing translations?
Configure your i18n library with a fallback language (usually English). Use tools like i18next-scanner to extract keys from code and detect missing translations. In development, display the key itself or a visual marker so untranslated strings are obvious.