Back to Partners
Guide

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.

Mastering JSON, XLIFF, and ICU for Safe, Scalable Localization

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.

FeatureXLIFF 1.2XLIFF 2.1
Inline markup<ph>, <bpt>/<ept>Simplified <ph>, <pc>, <sc>/<ec>
Metadata<note>, <context-group><notes>, extensible modules
SegmentationExternal (SRX)Built-in <segment> and <ignorable>
State trackingstate attribute on <target>Richer state + subState
Tooling supportBroad (legacy TMS, CAT tools)Growing (modern TMS, SDL, memoQ)
ModulesNoneTranslation 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.

Book a Demo

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:

  1. Parse every locale file and fail on syntax errors (malformed JSON, invalid XLIFF, broken ICU).
  2. Compare placeholder sets between source and target, every placeholder in the source must appear in the translation.
  3. Check for missing keys, every key in the source locale must exist in every target locale.
  4. 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.
  5. 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

FrameworkNative FormatICU SupportRecommended Tooling
React (react-intl)JSONFull ICU MessageFormat@formatjs/cli for extraction, compilation, and linting
React (i18next)JSONVia i18next-icu plugini18next-parser for extraction
iOS (Swift).strings, .stringsdict, .xcstringsPartial (.stringsdict for plurals)Xcode built-in; SwiftGen for type-safe access
Androidstrings.xmlPartial (plurals element)Android Studio lint
.NET.resxVia SmartFormat or custom IStringLocalizerResXManager for diff/sync
FlutterARB (JSON-based)Full ICU via intl packageflutter 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.

Book a Demo

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