Learn/Practical Guides

JSON for Internationalization (i18n) — Translation Files & Workflows

Every multi-language app needs translation files, and JSON is the dominant format. This guide covers how to structure, organize, and maintain JSON translation files — from flat keys to namespaced architecture, pluralization, interpolation, and integration with translation management tools.

Why JSON for Translations?

FormatUsed ByProsCons
JSONi18next, react-intl, vue-i18n, next-intlUniversal support, easy to parse, tooling-richNo comments (use JSONC workarounds)
YAMLRuby on Rails, some Python frameworksSupports comments, less verboseIndentation-sensitive, slower parsing
XLIFFEnterprise tools, iOS localizationIndustry standard, metadata supportVerbose XML, harder to edit manually
PO/POTgettext, WordPress, DjangoMature ecosystem, translator-friendlyLegacy 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, buttons
5 auth.json # Login, signup, password reset
6 dashboard.json # Dashboard-specific strings
7 settings.json # Settings page
8 errors.json # Error messages
9 es/
10 common.json
11 auth.json
12 dashboard.json
13 settings.json
14 errors.json
15 de/
16 common.json
17 ...
Namespace Loading Strategy

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';
2
3function Header() {
4 const { t } = useTranslation('common');
5
6 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';
2
3function Dashboard() {
4 const t = useTranslations('dashboard');
5
6 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 language
2t('hello') + ' ' + userName + ', ' + t('welcome')
3
4// CORRECT — use interpolation
5t('greeting', { name: userName })
6// JSON: "greeting": "Hello {{name}}, welcome back!"

2. Embedding HTML in Translations

1// AVOID — mixing HTML with translations is fragile
2{ "terms": "By clicking, you agree to our <a href='/terms'>Terms</a>" }
3
4// BETTER — use rich text components
5{ "terms": "By clicking, you agree to our <link>Terms</link>" }
6// Then map <link> to your component in code

3. Hardcoding Plural Logic

1// WRONG — English-only plural logic
2const label = count === 1 ? '1 item' : `${count} items`;
3
4// CORRECT — let the i18n library handle plurals per locale
5const label = t('items', { count });

Translation Management Workflow

i18n Workflow

Try It — Validate a Translation File

Try It Yourself

A valid i18n JSON translation file

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.