Mastering JSON, XLIFF, and ICU for Safe, Scalable Localization
Mastering the file formats behind safe, scalable localization: JSON string files, XLIFF interchange, and ICU MessageFormat for plurals and gender, with the pitfalls that corrupt translations and how to avoid them.

A single broken placeholder can crash a production build. A missing plural rule can turn a polished app into a grammatical embarrassment across dozens of locales. Yet most engineering teams treat localization files as an afterthought, flat key-value pairs tossed into a /locales folder with no validation, no context, and no safety net.
This guide is for engineers who want to stop firefighting localization bugs and start building robust string pipelines. It covers the file formats you'll actually encounter, JSON, YAML, PO, and XLIFF, alongside ICU MessageFormat for handling plurals, gender, and selection logic. You'll learn concrete strategies for escaping, testing, diffing, and preventing runtime errors before they ever reach your users.
Choosing the Right File Format: JSON, YAML, PO, and XLIFF
Format choice shapes your entire localization workflow. It determines what metadata you can attach, how translators interact with your strings, and whether your tooling can catch errors early. There is no universally "best" format, only the right fit for your stack, team size, and translation process.
JSON: Simplicity and Ecosystem Support
JSON is the default for most JavaScript-based projects. Libraries like react-intl, next-intl, and i18next consume it natively, and every CI system can parse it without extra dependencies.
The trade-off is that flat JSON offers zero metadata. There's no standard way to attach translator notes, character limits, or context descriptions without inventing your own conventions. Nested JSON structures help organize keys by feature or screen, but deep nesting creates merge conflicts and makes diffing harder.
Best suited for: single-platform web apps, small-to-medium string volumes, teams already using JavaScript tooling end to end.
YAML: Readability vs. Fragility
YAML's indentation-based syntax is more human-readable than JSON, which is why Rails (config/locales/*.yml) and some Python frameworks default to it. Multiline strings are cleaner, and comments are natively supported, useful for inline translator notes.
The downside is real: YAML is notoriously fragile. A misplaced indent silently restructures your data. The Norwegian locale code no is parsed as a boolean false unless quoted. Tabs versus spaces cause invisible breakage. If you choose YAML, enforce strict linting with tools like yamllint and always quote locale codes.
PO/POT (Gettext): Battle-Tested for Desktop and Backend
GNU Gettext's PO format has been the backbone of Linux and open-source localization for decades. It stores source strings as keys (the msgid), which means translators always see the original text. Translator comments, extracted comments, and plural forms are first-class citizens in the format.
PO works well with C, C++, Python, PHP, and any backend that links against libintl. It's less natural for mobile or modern frontend stacks. The format also doesn't support rich metadata like XLIFF's <note>, state tracking, or segmentation data.
XLIFF 1.2 vs. 2.1: The Interoperability Standard
XLIFF (XML Localization Interchange Format) is an OASIS standard designed specifically for exchanging localization data between tools, TMS platforms, and translation providers.
| Feature | XLIFF 1.2 | XLIFF 2.1 |
|---|---|---|
| Inline markup | <ph>, <bpt>/<ept> | Simplified <ph>, <pc>, <sc>/<ec> |
| Metadata | <note>, <context-group> | <notes>, extensible modules |
| Segmentation | External (SRX) | Built-in <segment> and <ignorable> |
| State tracking | state attribute on <target> | Richer state + subState |
| Tooling support | Broad (legacy TMS, CAT tools) | Growing (modern TMS, SDL, memoQ) |
| Modules | None | Translation candidates, glossary, metadata, validation, size restriction |
XLIFF 1.2 has wider legacy support. XLIFF 2.1 is a cleaner spec with modular extensibility, size restriction modules, for instance, let you embed character-limit constraints directly in the file.
When should you standardize on XLIFF? When your localization pipeline involves external translators, multiple TMS platforms, or more than two target formats. XLIFF acts as a canonical interchange layer: you convert from your source format (JSON, PO, etc.) into XLIFF for the translation round-trip, then convert back. This decouples your codebase from your translation tooling and preserves metadata that simpler formats cannot carry.
For teams managing complex, multi-format pipelines, Ollang handles XLIFF-based workflows across text, software, and document localization. You can book a demo with Ollang to see how XLIFF interchange fits into an enterprise pipeline.
ICU MessageFormat: Plurals, Gender, and Select
Hardcoding plural logic with ternary operators (count === 1 ? "item" : "items") breaks the moment you add a language with more than two plural forms. Polish has four. Arabic has six. ICU MessageFormat, maintained by the Unicode Consortium, solves this with a declarative syntax that translators can work with directly.
Plural Rules with Concrete Examples
An ICU plural string for English looks like this:
{count, plural,
=0 {No messages}
one {# message}
other {# messages}
}
The # symbol is replaced with the numeric value at runtime. The categories (zero, one, two, few, many, other) are defined by CLDR plural rules, not by the developer. A translator working on the Arabic locale adds the zero, two, few, and many categories that Arabic requires, without any code change.
Gender and Select
The select keyword handles gender and arbitrary enums:
{gender, select,
female {She completed her profile.}
male {He completed her profile.}
other {They completed their profile.}
}
You can nest select inside plural or vice versa. Keep nesting to two levels maximum; beyond that, split the string into separate keys for maintainability.
How Do We Lint ICU Strings Automatically?
Use dedicated parsers that validate ICU syntax at build time:
- Ollang: integrates ICU validation into multi-format pipelines so checks run consistently across platforms.
- @formatjs/cli: The formatjs compile command parses ICU strings and throws on syntax errors. Integrates directly into React/Next.js projects.
- messageformat (npm): Parses and compiles ICU strings for JavaScript, reporting mismatched braces, unknown plural categories, and invalid selectors.
- i18next-icu: If you're on i18next, this plugin validates ICU at load time, but catching errors at build time is preferable.
- Custom CI checks: Run a script that loads every message file, parses each value through an ICU parser, and fails the build on any error. This takes fewer than 20 lines of code in most languages.
Add these checks to your pre-commit hooks (via Husky or pre-commit) so broken ICU strings never reach the main branch.
Escaping, Variables, and Placeholder Protection
Placeholders are the most common source of localization-related production incidents. A translator accidentally deletes {username}, and the app renders a raw variable name, or worse, crashes.
Defining Placeholder Conventions
Establish a single convention and enforce it across all files:
- Named placeholders: {username}, {orderTotal}, self-documenting, harder to confuse.
- Positional placeholders: %1$s, %2$d, common in Android (strings.xml) and C-style systems. Positional indexing lets translators reorder words without breaking substitution.
- HTML-like tags: <bold>{name}</bold>, used by react-intl and i18next for inline formatting. Protect these as non-translatable inline elements.
Protecting Placeholders in Translation
In XLIFF, wrap placeholders in <ph> (placeholder) elements so CAT tools render them as locked, movable tokens. In JSON-based workflows, use linter rules that compare source and target placeholder sets. The @formatjs/cli toolchain can extract placeholders and verify they appear in every locale file.
For escaping, ICU MessageFormat uses single quotes as escape characters: '{'} produces a literal{. This trips up translators unfamiliar with the convention. Document it explicitly in your style guide, and consider adding a linter that warns when literal braces appear without escaping. Remember that a literal single quote is escaped by doubling it:''`.
Ready to see Ollang in action?
Talk to our team about your localization goals and see how the Ollang platform fits your workflow.
Segmentation and Context Keys
Source Key Naming Conventions
Key names are the single most impactful decision for long-term maintainability. Adopt a hierarchical, feature-scoped pattern:
module.component.element.descriptor
For example: checkout.summary.total.label, profile.avatar.upload.error. This convention:
- Makes keys greppable across the codebase.
- Groups related strings in translation tools.
- Reduces the chance of key collisions across teams.
Avoid using the English source text as the key ("Save changes" = "Save changes"). It breaks the moment the English copy changes, and it provides no structural context.
Segmentation Strategy
Segmentation determines the granularity of your translatable units. Over-segmenting (one key per word) destroys grammatical context. Under-segmenting (entire paragraphs as one key) increases translation cost and reduces TM leverage.
The practical rule: one key per complete, independently meaningful unit of text. A button label is one segment. A tooltip sentence is one segment. A paragraph with multiple sentences can be one segment if the sentences are always displayed together and share context.
XLIFF 2.1's <segment> element lets you define sub-segmentation within a translation unit, giving translators sentence-level granularity while preserving paragraph-level context.
Adding Context for Translators
Context prevents mistranslation. The English word "Post" could mean a blog post, a fence post, or the action of posting. Without context, translators guess.
- XLIFF: Use <note> elements with appliesTo="source" to describe where and how the string appears.
- JSON: Use a parallel _description key or a structured format like { "value": "Post", "description": "Button label to publish a blog entry" }.
- PO: Use extracted comments (#.) to carry developer-provided context.
- Screenshots: Attach UI screenshots to translation units in your TMS. This is the single most effective way to reduce context-related errors.
RTL/LTR Handling and Length Constraints
Bidirectional Text
Supporting Arabic, Hebrew, Farsi, and other RTL scripts requires more than CSS direction: rtl. Strings that mix RTL and LTR content (e.g., an Arabic sentence containing an English brand name) need Unicode bidirectional control characters or HTML dir attributes to render correctly.
Use the Unicode Bidirectional Algorithm markers:
- U+200F (Right-to-Left Mark) and U+200E (Left-to-Right Mark) to resolve ambiguous directionality at string boundaries.
- Wrap embedded opposite-direction text in U+2066/U+2069 (First Strong Isolate / Pop Directional Isolate) to prevent spillover.
In React, set dir="auto" on containers that display user-generated or translated content. On iOS, NSLocale and Auto Layout handle most RTL mirroring, but custom drawing code needs manual attention. Android's supportsRtl manifest flag enables system-level mirroring.
Length Constraints
German text is commonly 30-40% longer than English. Finnish compound words can be even longer. If your UI has fixed-width buttons, truncation or overflow is inevitable without planning.
Strategies:
- Use XLIFF 2.1's Size Restriction module to specify character or pixel limits in your interchange files.
- Run automated checks that flag translations exceeding a character or pixel-width threshold.
- Design UI components with flexible layouts that accommodate expansion. Apple’s Human Interface Guidelines emphasize using adaptable layouts that can accommodate longer localized text.
Pseudolocalization: Catching Layout Issues Early
Pseudolocalization transforms your source strings into accented, expanded, or bracketed versions that simulate translated text without waiting for real translations. It exposes layout problems, hardcoded strings, and concatenation bugs in minutes.
A typical pseudolocalization pass transforms "Save" into "[Šåvé__________]":
- Accented characters (Š, å, v, é) verify that your font and rendering pipeline handle diacritics and extended Unicode.
- Expansion padding (underscores or repeated characters) simulates the 30-50% text growth common in many target languages.
- Brackets ([…]) make it immediately obvious if a string is missing from the localization system, unhardcoded strings won't have brackets.
Tools for pseudolocalization:
- @formatjs/cli includes a --pseudo-locale flag.
- Android Studio has a built-in pseudolocale (en-XA for accented, ar-XB for RTL).
- Xcode supports pseudolanguages in the scheme editor.
Run pseudolocalization in your CI pipeline as a visual regression step. Pair it with screenshot testing (Chromatic, Percy, or native snapshot tests) to catch overflow and truncation automatically.
Unit Tests, Pre-Commit Checks, and Diffing Strategies
Automated Validation in CI
Localization files deserve the same rigor as application code. A minimal CI validation suite should:
- Parse every locale file and fail on syntax errors (malformed JSON, invalid XLIFF, broken ICU).
- Compare placeholder sets between source and target, every placeholder in the source must appear in the translation.
- Check for missing keys, every key in the source locale must exist in every target locale.
- Validate plural categories, if the source defines one and other, the Arabic translation must define zero, one, two, few, many, and other per CLDR rules.
- Enforce key naming conventions with a regex-based linter.
Ollang can centralize these validations in your translation pipeline and surface failures alongside your CI runs.
Pre-Commit Hooks
Use husky (Node) or the pre-commit framework (Python) to run fast checks locally:
- JSON/YAML syntax validation.
- ICU parse check on changed files only (for speed).
- Key sort order enforcement to minimize merge conflicts.
Diffing Strategies
Localization file diffs are noisy. Reduce friction with these approaches:
- Sort keys alphabetically in JSON and YAML files. This makes diffs deterministic and eliminates spurious reordering noise.
- One key per line in flat formats. Avoid multi-key single-line structures.
- XLIFF-aware diff tools: Standard diff treats XLIFF as opaque XML. Prefer TMS-integrated diff views or open-source utilities like the Okapi Framework that compare translation units semantically.
- Separate source string changes from translation updates in your Git workflow. Use distinct branches or commits so reviewers can assess each type of change independently.
Mapping to Frameworks: React, iOS, Android, and .NET
| Framework | Native Format | ICU Support | Recommended Tooling |
|---|---|---|---|
| React (react-intl) | JSON | Full ICU MessageFormat | @formatjs/cli for extraction, compilation, and linting |
| React (i18next) | JSON | Via i18next-icu plugin | i18next-parser for extraction |
| iOS (Swift) | .strings, .stringsdict, .xcstrings | Partial (.stringsdict for plurals) | Xcode built-in; SwiftGen for type-safe access |
| Android | strings.xml | Partial (plurals element) | Android Studio lint |
| .NET | .resx | Via SmartFormat or custom IStringLocalizer | ResXManager for diff/sync |
| Flutter | ARB (JSON-based) | Full ICU via intl package | flutter gen-l10n for code generation |
For cross-platform projects, consider using XLIFF as the interchange format between your codebase and your translation pipeline. Convert from the native format to XLIFF before sending strings for translation, then convert back after review. This lets each platform use its native format while standardizing the translation workflow.
FAQ
When should we standardize on XLIFF over simpler formats like JSON?
Standardize on XLIFF when your pipeline involves external translation vendors, multiple TMS integrations, or more than one target platform. XLIFF's metadata capabilities, translator notes, state tracking, size constraints, and segmentation, justify the added complexity. For a single-platform web app with an internal team, JSON with a solid linting setup may be sufficient. The tipping point is typically when you need to preserve context and state across a multi-step translation workflow.
How do we lint ICU strings automatically in CI?
Use a parser that validates ICU MessageFormat syntax and fails on errors. In JavaScript projects, @formatjs/cli compile catches syntax issues, mismatched braces, and missing plural categories. For other languages, the ICU4J and ICU4C libraries include message parsers you can invoke in a test harness. Add these checks as a CI step that runs on every pull request touching locale files, and as a pre-commit hook for immediate developer feedback.
What's the fastest way to detect hardcoded strings in our codebase?
Pseudolocalization. Enable a pseudolocale build (e.g., en-XA on Android, --pseudo-locale with FormatJS) and visually scan your app. Any string that appears without accented characters or brackets is hardcoded and bypassing the localization system. Pair this with automated screenshot comparison to catch issues without manual inspection.
How do we prevent translators from breaking placeholders?
Use XLIFF <ph> elements to lock placeholders as non-editable tokens in CAT tools. For JSON-based workflows, implement a CI check that extracts all placeholders from source strings (via regex or parser) and verifies that every target string contains the same set. Document your placeholder conventions in a translator style guide, and include examples of correct and incorrect usage.
Ready to see Ollang in action?
Talk to our team about your localization goals and see how the Ollang platform fits your workflow.
Build a Localization Pipeline That Scales
Robust localization engineering isn't about picking the trendiest format, it's about establishing conventions, enforcing them with automation, and preserving context across every handoff between developers and translators. Start with a format that fits your stack, adopt ICU MessageFormat for anything beyond simple substitution, and invest in CI checks that catch breakage before it ships.
If your team is scaling across multiple languages, formats, or platforms, Ollang provides the execution layer to manage XLIFF interchange, translation quality review, and API-driven localization workflows. Visit the Ollang site to book a demo of our localization pipeline capabilities and see how automated validation and multi-format support can reduce the placeholder bugs and pluralization errors that derail releases.
Published on July 29, 2026