JSON Localization Files: Translation Schema, Plurals & ICU Message Format
Last updated:
Overview
JSON translation files are the backbone of JavaScript internationalization workflows. This guide covers key design questions: structuring keys for scalability, encoding plural forms for different languages (CLDR categories), using ICU Message Format for complex strings, validating sync across locales, and lazy loading translation bundles without content flashing.
Key Patterns
- Nested Key Structure: Group related translations logically (button.save, button.cancel) rather than flat keys (button_save, button_cancel)
- Namespace Separation: Split translations by feature (common.json, auth.json, checkout.json) for route-based lazy loading
- CLDR Plural Rules: Six categories (zero, one, two, few, many, other) — English uses 2, Arabic uses all 6, Russian uses 4
- ICU Message Format: Embed pluralization, gender selection, and number formatting in a single JSON string value
- Lazy Loading: Load only the namespace for the current page/route, reducing initial bundle by 40–60%
Best Practices
- Use nested keys with maximum 3 levels deep (namespace.component.key) — deeper hierarchies create maintenance problems
- Configure the key separator explicitly in i18next to avoid conflicts when key names contain dots
- Never hard-code plural logic in application code — rely on CLDR rules and library-provided plural handlers
- Implement TypeScript types for translation keys using i18next 23+ built-in support
- Run build-time validation scripts to ensure all locale files have identical key sets
- For missing translations at runtime, show the key path (e.g., "button.submit") instead of blank strings to help QA identify gaps
- Use translation management platforms (Crowdin, Lokalise, Phrase) with native ICU support for professional workflows
- For RTL languages (Arabic, Hebrew, Persian, Urdu), apply direction via HTML dir attribute, not JSON content
- Lazy load translations per route/feature using dynamic imports with webpack/Vite chunking
- Pass preloaded translations from server to client via React context to avoid hydration flash
CLDR Plural Categories
CLDR defines six plural categories. English uses only one (singular) and other (plural), but other languages require different rules. Russian has four categories with edge cases (11–14 use the "many" form despite the last digit). Always let i18next handle plural selection based on CLDR rules for the active locale — do not write custom if/else logic.
Implementation Approaches
Popular frameworks handle JSON localization differently:
- i18next: Namespace-based, supports static imports, HTTP backend, or dynamic imports with caching
- react-intl (FormatJS): Component-based API, full ICU Message Format support, Webpack plugin for extraction
- next-intl: Next.js-specific, automatic server-side preloading and client hydration, app router native
- Nuxt i18n: Framework integration with module system, SSR-friendly, per-locale route generation
File Organization
For apps with multiple locales and features, organize translation files by locale then namespace:
locales/
├── en/
│ ├── common.json (shared)
│ ├── auth.json (login/signup)
│ └── checkout.json (cart/payment)
├── fr/
│ ├── common.json
│ ├── auth.json
│ └── checkout.json
└── ar/
├── common.json
├── auth.json
└── checkout.jsonKey Validation at Build Time
Implement a build script that loads all locale JSON files, extracts keys from the base locale (English), and diffs against each other locale. This catches missing keys, extra keys, and structure drift before release. Tools like i18next-scanner and i18next-parser automate this validation. Fail the build if required keys are missing.
TypeScript Support
i18next 23+ includes built-in TypeScript support. Define a type module that imports your base locale's JSON file, then declare it as the resource type. The t() function becomes fully typed — calling t("button.nonexistent") is a compile-time error, catching key typos during development.
ICU Message Format Benefits
ICU Message Format embeds pluralization, gender selection, and number formatting inside a single translation string. Instead of maintaining separate keys for each plural form, a single ICU string contains all variants: {count, plural, one {# item} other {# items}}. Translation management platforms parse ICU syntax and present each variant as a separate editable field in their UI, making translator workflow clearer and reducing context loss.
Lazy Loading Performance
Shipping all 50 locale files and 10 namespaces (500 JSON files) in the initial bundle is the most common i18n performance mistake. Only one locale's files are ever needed per user session. Use dynamic imports with webpack/Vite chunking to create separate chunks per locale+namespace. This reduces initial translation payload by 40–60%. Configure i18next to load namespaces on demand, and preload only the namespace for the current route.
RTL Language Handling
Right-to-left languages (Arabic, Hebrew, Persian, Urdu) require changes at the HTML rendering layer, not in JSON content. Store Arabic text naturally in JSON files — right-to-left directionality is applied via HTML dir attribute. Configure locale direction metadata (en: "ltr", ar: "rtl") and apply it to the html root element. Use CSS and layout utilities that respect RTL (flexbox with flex-row-reverse, padding-right becomes padding-left with RTL).
Crowdin vs Lokalise vs Phrase
Professional translation management platforms have different JSON format requirements. Crowdin expects nested keys with separator configuration. Lokalise supports both flat and nested keys with automatic structure detection. Phrase (formerly Phrase) supports ICU Message Format natively. All three offer translator UIs that parse plurals and context. When exporting from these platforms, verify the JSON structure matches your application's loader configuration (key separators, namespace format, plural suffix pattern).
Missing Translation Strategy
Configure i18next with a missing key handler that logs to your monitoring service. Set fallbackLng to your base locale to ensure users always see something. Show the key path instead of empty strings during QA — this makes missing translations immediately visible in testing. In production, use the fallback locale text with a warning logged to observability systems.
Further reading and primary sources
- i18next Documentation — i18next namespace patterns, CLDR plural rules, dynamic imports, TypeScript support, and missing key handling
- FormatJS (react-intl) — React component API for i18n, full ICU Message Format support, Webpack plugin for message extraction
- next-intl Documentation — Next.js App Router integration with automatic server-side preloading and client hydration
- CLDR Plural Rules Reference — Complete specification of six plural categories and language-specific rules
- ICU Message Format Spec — Official Unicode ICU Message Format specification with examples
- Crowdin JSON Format — Crowdin-specific JSON structure requirements and export options
- Lokalise JSON Integration — Lokalise JSON format, plural handling, and nested key separator configuration