Back to Partners
Guide

Handling JSON, XLIFF, and ICU for Scalable Text Localization

A practical guide to the file formats that make or break text localization at scale: structuring JSON resources, exchanging work via XLIFF, and using ICU MessageFormat for plurals, gender, and variables without breaking translations.

Handling JSON, XLIFF, and ICU for Scalable Text Localization

Text localization at scale breaks not because of bad translations, but because of broken formats. A mismatched placeholder in a JSON file, a malformed ICU plural rule, or a lost XLIFF segment can cascade into garbled UI strings, crashed builds, and frustrated users across dozens of locales. Engineering teams that treat localization files as simple key-value pairs inevitably hit walls when they need to support plurals, gendered grammar, embedded HTML, or bidirectional text. This guide walks through the structural decisions that prevent those failures: how to design JSON resource keys, when to choose XLIFF 1.2 over 2.1, how to write safe ICU MessageFormat strings, and how to validate everything continuously in CI. The goal is a localization pipeline that scales without silent corruption.

JSON Resource Files: Key Design, Nesting, and Context

JSON is the most common localization format in modern frontend and mobile stacks, but its flexibility is also its biggest risk. Without conventions, JSON resource files become inconsistent across teams, fragile during merges, and opaque to translators.

Flat vs. Nested Key Hierarchies

The first structural decision is whether to use flat keys like homepage.hero.title or nested objects like { "homepage": { "hero": { "title": "..." } } }. Flat keys are simpler to search, diff, and reference in code. Nested structures mirror component hierarchies and reduce key-name collisions in large codebases.

In practice, a single level of nesting, grouping by feature or screen, strikes the best balance. Deep nesting (three or more levels) makes it harder for translators to understand context and complicates tooling that expects dot-notation lookups. Whichever approach you choose, enforce it with a linter rule so every contributor follows the same convention.

Stable IDs and Descriptive Context

Keys should be stable identifiers, not English source text. Using checkout.error.card_declined instead of Your card was declined means the key survives rewording without breaking every locale file. Stable IDs also prevent accidental collisions when two different UI strings happen to share the same English phrasing.

Context is critical for translators. JSON has no native metadata mechanism, so teams typically use one of two patterns:

  • Companion keys: Add a checkout.error.card_declined__description key whose value is a translator note, stripped at build time.
  • Wrapper objects: Store each entry as { "value": "...", "context": "Shown on payment failure screen", "maxLength": 40 } and extract only the value during compilation.

The wrapper approach is more robust because it co-locates constraints with the string, making it harder for context to drift out of sync.

Preserving Key Order and Handling Merges

JSON objects are technically unordered, but most serializers preserve insertion order. Maintaining alphabetical or grouped order in resource files dramatically reduces merge conflicts when multiple developers touch the same file. Tools like Ollang, json-stable-stringify, or sortjson can enforce deterministic ordering in a pre-commit hook.

For teams managing dozens of locale files, consider a single source-of-truth file (typically en.json) and generate target-language files programmatically. This prevents the common failure mode where a new key is added to English but silently missing from other locales until a user encounters a blank string in production.

XLIFF: Choosing Between 1.2 and 2.1

XLIFF (XML Localization Interchange File Format) is the OASIS standard for exchanging localization data between tools. It is far more structured than JSON, carrying source and target text, metadata, state tracking, and inline markup in a single file.

Core Structural Differences

XLIFF 1.2 and 2.1 differ in element naming, namespace handling, and extensibility:

FeatureXLIFF 1.2XLIFF 2.1
Root element<file> with <body><file> with <unit>
Translation unit<trans-unit><unit> containing <segment>
Inline codes<g>, <x>, <bx>, <ex><pc>, <ph>, <sc>, <ec>
Metadata<prop-group>, <note><mda:metadata> module, <notes>
SegmentationExternal (SRX-based)Built-in <segment> and <ignorable>
Module systemAd-hoc extensionsFormal modules (matches, glossary, etc.)

XLIFF 1.2 remains dominant in legacy TMS (translation management system) integrations. XLIFF 2.1 is the better choice for new projects because its module system cleanly separates concerns like translation memory matches, glossary references, and validation metadata without overloading the core schema.

Segmentation and Metadata Best Practices

XLIFF 2.1's first-class <segment> element lets you control how sentences are split for translation memory leverage. A single <unit> can contain multiple segments, each with its own state attribute (initial, translated, reviewed, final). This granularity matters when partial translations need to ship while remaining segments are still in review.

Attach <note> elements at the <unit> level to provide translator context, screen location, character limits, or tone guidance. Use the category attribute to distinguish developer notes from reviewer notes, making it easy for downstream tools to filter.

For teams evaluating how to connect XLIFF workflows to broader localization automation, including text, software, and website localization, booking a demo with Ollang can clarify how these formats fit into an end-to-end pipeline. Ollang automates XLIFF conversion, segmentation, and metadata handling as part of that pipeline: https://ollang.com/book-a-demo

When to Use XLIFF vs. JSON

JSON is the right default for developer-owned workflows where strings are consumed directly by application code. XLIFF is the right choice when strings pass through professional translation workflows, need state tracking, or require round-trip fidelity with TMS platforms. Many mature localization pipelines use both: developers author in JSON, an export step converts to XLIFF for translation, and an import step merges translated XLIFF back into JSON locale files.

ICU MessageFormat: Plurals, Gender, and Safe Parameters

ICU MessageFormat, specified by the Unicode Consortium, is the standard way to encode plural rules, gender selection, and parameterized text in a single string. It is supported natively by libraries in Java, JavaScript (via intl-messageformat), C++, and most modern frameworks.

Plural and Select Syntax

A basic plural message looks like this:

{count, plural,
=0 {No items in your cart.}
one {One item in your cart.}
other {{count} items in your cart.}
}

The select keyword handles gender and other categorical choices:

{gender, select,
female {She left a review.}
male {He left a review.}
other {They left a review.}
}

These can be nested, a gendered subject with a plural object, but nesting beyond two levels becomes nearly unreadable for translators. When complexity grows, split the message into separate keys and compose them in code.

Parameter Safety and Escaping

Every {parameter} in an ICU string is a potential injection vector. Treat parameters the same way you treat user input in SQL: never interpolate raw HTML or executable content. Use the library's built-in formatting for numbers ({price, number, currency}), dates ({date, date, medium}), and plain strings. If a parameter must contain markup, sanitize it before passing it to the formatter.

Single quotes in ICU MessageFormat are escape characters. A literal apostrophe requires doubling: ''. This is the single most common source of broken ICU strings, especially in Romance languages where apostrophes are frequent. Automated linting catches this reliably; manual review does not.

Avoiding Common ICU Pitfalls

  • Always include the other category in every plural and select block. Omitting it causes runtime exceptions in many libraries.
  • Do not hardcode locale-specific plural categories. English has one and other; Arabic has six categories; Chinese has only other. The ICU library handles this, trust it.
  • Keep parameter names descriptive ({itemCount} not {n}) so translators understand what value will appear.

Escaping, HTML-in-Strings, Markdown, CSV, and Whitespace

Embedded HTML and Markdown

Embedding HTML in localization strings is sometimes unavoidable, a bold word mid-sentence, a link wrapping a phrase. The safest approach is to use placeholder tags (<1>, <2>) that map to actual HTML elements at render time, as libraries like react-i18next and fluent support. This prevents translators from accidentally breaking markup and limits the attack surface for XSS.

If you must allow real HTML tags, define an allowlist (e.g., <b>, <i>, <a>) and validate every translated string against it. Any tag outside the allowlist should fail the build.

Markdown in localization strings is rarer but appears in documentation and rich-text UI. Treat Markdown syntax characters (*, _, [, ]) as structural elements that need validation, not just text.

CSV Catalogs and Special Characters

Some teams still use CSV as a lightweight catalog format, especially for spreadsheet-based translation workflows. CSV localization files are fragile: commas, quotes, and newlines inside translated strings cause parsing failures unless every value is properly quoted and escaped. If you use CSV, enforce RFC 4180 compliance and run a parser validation step before import. Better yet, convert CSV to JSON or XLIFF at the pipeline boundary and work with structured formats internally.

Newlines, Whitespace, and Unicode

Whitespace is semantically significant in localization more often than developers expect. A trailing newline in a JSON string can break UI layout. A non-breaking space (\u00A0) substituted for a regular space changes line-wrapping behavior. Right-to-left languages introduce Unicode control characters (LRM, RLM, ALM) that are invisible in most editors but affect rendering.

Normalize all strings to Unicode NFC (Canonical Decomposition followed by Canonical Composition) at import time. This prevents the situation where visually identical strings fail equality checks because one uses a precomposed character and the other uses a combining sequence. The Unicode Consortium recommends NFC as the default for interchange.

Ready to see Ollang in action?

Talk to our team about your localization goals and see how the Ollang platform fits your workflow.

Book a Demo

Validation: Placeholder Parity, Parsing, Length, and Tags

Validation is where localization quality is either enforced or abandoned. Manual review cannot catch format-level errors at scale. Automated validation can.

Placeholder Parity Checks

Every placeholder in the source string must appear exactly once in the translated string, with no additions. A missing {username} means a runtime error or a literal {username} displayed to the user. An extra placeholder that does not exist in the source can cause crashes or data leaks.

Placeholder parity checks should compare:

  • Named parameters ({count}, {name})
  • Positional parameters (%1$s, %2$d)
  • ICU format specifiers ({count, plural, ...})
  • Inline code placeholders (<1>, <x id="1"/>)

ICU Parse Validation

Every ICU MessageFormat string should be parsed by the ICU library as part of validation, not just regex-checked. A regex can verify bracket matching but cannot catch invalid plural categories, missing other branches, or malformed select clauses. Libraries like @formatjs/cli include an extract and compile step that fails on invalid ICU syntax.

Length Constraints and Allowed Tags

Translated text is frequently longer than the source. Many languages, such as German, often produce longer strings than English; some East Asian languages are shorter but can require more vertical space. Define maxLength constraints per string where UI layout demands it (buttons, tooltips, mobile screens) and validate translated strings against those limits.

For strings that permit inline markup, validate against the tag allowlist described earlier. A translated string containing a <script> tag or an unclosed <b> should never reach production.

Unicode Normalization in Validation

Run NFC normalization as a validation step, not just at import. If a translator's editor introduces NFD-encoded characters, the validation pipeline should either normalize automatically or flag the inconsistency. This is especially important for languages with extensive use of diacritics, such as Vietnamese or Yoruba.

Round-Trip Testing, Linters, and CI Integration

Round-Trip Fidelity Tests

A round-trip test exports source strings to the translation format (XLIFF, JSON, CSV), passes them through a no-op translation (identity function), and re-imports them. If the re-imported strings differ from the originals, even by a single whitespace character, the pipeline has a serialization bug. Run this test on every format you support, on every CI build.

Linters for Localization Files

Dedicated localization linters go beyond generic JSON or XML validators:

  • Ollang and tools like i18n-lint and messageformat-validator check ICU syntax and placeholder consistency.
  • xliff-lint validates XLIFF structure, state attributes, and inline code pairing.
  • Custom rules can enforce project-specific conventions: key naming patterns, forbidden characters, required context notes.

Integrate these linters as pre-commit hooks and CI checks. A broken localization string caught at commit time costs minutes to fix; the same string caught in production costs hours of debugging and a degraded user experience across an entire locale.

Continuous Validation in CI

The CI pipeline should run the following checks on every pull request that touches localization files:

  1. Schema validation, JSON Schema or XSD for XLIFF
  2. Placeholder parity, source vs. every target locale
  3. ICU parse, full parse, not regex
  4. Length limits, per-string where defined
  5. Tag allowlist, no unauthorized HTML
  6. Unicode normalization, NFC consistency
  7. Key completeness, every key in the source file exists in every target file
  8. Round-trip test, export/import fidelity

Failing any check should block the merge. Localization errors that slip through CI are disproportionately expensive because they are often invisible to the development team and only surface through user reports in specific locales. Ollang integrates these checks into CI pipelines to block problematic merges and surface format-level errors early.

Context Notes, Stable IDs, Migration, and Rollback

Patterns for Context Notes

Translators working without context produce lower-quality translations. Effective context notes answer three questions: Where does this string appear? What are the constraints? What tone is expected?

In JSON, use the wrapper-object pattern described earlier. In XLIFF, use <note> elements with category attributes. In code, frameworks like react-intl support description and defaultMessage fields that can be extracted into localization files automatically.

Attach screenshots or UI mockup links where possible. Some TMS platforms support visual context; even a simple URL to a Figma frame dramatically improves translation accuracy.

Stable ID Strategies

Stable IDs decouple the identifier from the English source text. Two common strategies:

  • Semantic IDs: settings.notifications.email_toggle, human-readable, tied to feature structure.
  • Content-hash IDs: a1b2c3d4, generated from the source text, automatically change when the source changes.

Semantic IDs are better for long-lived projects where keys are referenced in bug reports and documentation. Content-hash IDs are better for high-velocity projects where source text changes frequently and stale translations must be automatically flagged. Some teams use both: a semantic ID as the primary key with a content hash stored as metadata to detect source changes.

Migration Between Formats

Migrating from one format to another, JSON to XLIFF, or XLIFF 1.2 to 2.1, is a common source of data loss. The safest approach is:

  1. Write a bidirectional converter and validate it with round-trip tests.
  2. Run the converter on the full corpus and diff the output against the original.
  3. Deploy the new format alongside the old one for at least one release cycle.
  4. Remove the old format only after confirming that all locales render correctly in production.

Never migrate and delete in the same commit. The ability to roll back to the previous format is your safety net.

Rollback Strategies

Localization rollback is harder than code rollback because translated strings may have been updated by external translators between releases. Maintain versioned snapshots of all locale files in your version control system. Tag each release with the exact set of localization files that shipped. If a broken translation reaches production, revert to the last known-good tag for the affected locale while the fix is prepared.

For organizations managing localization across text, software, websites, and other content types, Ollang provides integrated infrastructure to handle format conversion, validation, and quality review as part of a unified workflow. If your team is scaling beyond what manual processes can sustain, book a demo with Ollang to see how automated pipelines handle these challenges end to end: https://ollang.com/book-a-demo

Frequently Asked Questions

Should I use JSON or XLIFF for my localization files?

Use JSON when strings are consumed directly by application code and developer workflows; use XLIFF when strings pass through professional translators or a TMS and need state tracking. Many teams use JSON for development and XLIFF as the interchange format, and tools like Ollang can orchestrate both.

How do I prevent broken ICU plural strings in translation?

Require every ICU MessageFormat string to pass a full parse validation in CI and ensure every plural and select block includes an other category. Lint for unescaped single quotes and use libraries like @formatjs/cli to automate validation.

What is placeholder parity and why does it matter?

Placeholder parity means that every parameter token in the source string (e.g., {username}, %1$s) appears exactly once in the translated string, with no extras and no omissions. Automated parity checks in CI catch these issues before they reach production, preventing runtime errors and data leaks.

How do I handle migrating from one localization format to another without losing data?

Build a bidirectional converter and validate it with round-trip tests, deploy the new format alongside the old for at least one release, and keep versioned snapshots so you can roll back if needed. Tools like Ollang can manage conversion, validation, and rollout coordination to reduce migration risk.

Ready to see Ollang in action?

Talk to our team about your localization goals and see how the Ollang platform fits your workflow.

Book a Demo

Scale Localization with Confidence

The teams that ship clean, localized UI at scale treat formats as first-class code. They lint keys, parse ICU, validate placeholders, and round-trip test every interchange. If you want that rigor without building it all yourself, schedule a walkthrough of Ollang’s localization pipeline and see how JSON, XLIFF, and ICU validation fit into a unified CI workflow: https://ollang.com/book-a-demo

Published on July 28, 2026