How to Structure JSON Translation Files for Scalable UI
Structuring JSON translation files properly is essential for building internationalized (i18n) applications that scale without degrading performance or developer experience. Poorly organized localization files lead to duplicated strings, bloated bundles, merge conflicts, and translation errors. This guide outlines the best practices for structuring, naming, and managing JSON translation files to support clean codebases, seamless localization workflows, and fast UI rendering as your application grows.
1. Organize by Feature or Page, Not by UI Element
Organizing keys around components like buttons.json or
headers.json quickly causes confusion and duplication.
Instead, group your translations by domain, feature, or page, with a
dedicated namespace for shared UI elements:
{
"common": {
"save": "Save",
"cancel": "Cancel",
"loading": "Loading..."
},
"auth": {
"login": {
"title": "Log in to your account",
"submit_button": "Sign In"
}
},
"dashboard": {
"welcome_message": "Welcome back, {userName}!"
}
}This structure makes it immediately obvious where a string belongs and limits the blast radius of changes to specific application views.
2. Limit Nesting to Two or Three Levels
While nesting provides organizational hierarchy, deeply nested JSON
objects create brittle access paths (e.g.,
t('pages.settings.security.two_factor.modal.buttons.confirm'))
and increase the risk of missing-key bugs.
Keep depth shallow:
- Level 1: Feature or Namespace
(
settings) - Level 2: Sub-feature or Component
(
two_factor) - Level 3: Specific Element or State
(
confirm_button)
Flat keys with dot notation (e.g.,
"settings.two_factor.confirm_button": "Confirm") are also
widely supported by modern i18n libraries and reduce payload parsing
overhead.
3. Establish Strict Key Naming Conventions
Define a clear naming syntax across all development teams. Keys should describe their semantic function and context, not their current English text value.
- Pattern:
[context]_[element_type]_[action/state] - Examples:
checkout_button_pay_now(Clear)pay_now(Too vague)button1(Non-descriptive)
Choose one casing style—such as snake_case or
camelCase—and enforce it consistently across all
translation files.
4. Split Monolithic Files into Namespaces
Shipping a single, massive en.json file forces the
client to download unused translations for the entire application on the
initial page load. Split your translations into modular namespace
files:
locales/
├── en/
│ ├── common.json
│ ├── auth.json
│ └── dashboard.json
└── es/
├── common.json
├── auth.json
└── dashboard.json
Modern frameworks (such as Next.js, React, or Vue) can lazy-load these chunks on demand based on the active route, reducing initial bundle sizes significantly.
5. Use ICU MessageFormat for Plurals and Variables
Avoid string concatenation or custom logic to handle dynamic data and pluralization. Different languages have complex pluralization rules that do not match English. Use industry standards like ICU MessageFormat:
{
"cart": {
"item_count": "{count, plural, =0 {Your cart is empty} one {# item in your cart} other {# items in your cart}}"
}
}This delegates pluralization, gender agreements, and number formatting to your i18n library, preventing translation bugs in non-English locales.
6. Automate Linting and Key Synchronization
As your codebase scales, manual maintenance of translation files becomes unsustainable. Integrate automated tools into your CI/CD pipeline:
- Key Parity Checkers: Verify that all locale files
(
fr.json,de.json) contain the exact same keys present in your source locale (en.json). - Unused Key Detectors: Scan the codebase to identify and purge obsolete translation keys.
- JSON Schema Validation: Enforce proper syntax and structure before changes merge to production.