If you are building a multilingual product, you will eventually have to pick a locale file format -- or inherit one and decide whether to migrate away from it. JSON, YAML, PO, XLIFF, ARB: each comes with tradeoffs that matter more as your localization tooling grows. This is a guide to those tradeoffs, focused on the constraints that show up when translation tooling enters the picture.
The Formats
JSON
JSON is the default for web and mobile applications. It is readable, well-supported by every platform and toolchain, and trivial to parse. The i18n-standard variant (flat key-value or namespaced nested) is the format of choice for React, Vue, Next.js, and most JavaScript-ecosystem i18n libraries.
JSON's weakness for localization is its lack of metadata support. A standard JSON locale file contains keys and values -- nothing else. There is no built-in way to store translator context, translation state, source-text hashes, or reviewer attribution. TMS tools that work with JSON either extend it with custom structures (creating a non-standard format) or maintain metadata out-of-band, which requires careful synchronization.
When to use JSON: when your stack is JavaScript-primary and your translation tooling has solid JSON support with a defined convention for metadata management.
YAML
YAML shares most of JSON's characteristics for i18n purposes but is slightly more readable for deeply nested structures and supports inline comments, which is meaningful for translator context at the file level. Ruby on Rails uses YAML as its default i18n format, which accounts for a large share of YAML adoption in the localization space.
YAML's practical weakness is inconsistent parser behavior across language ecosystems and a tendency for whitespace errors that are invisible until runtime. It is not meaningfully better than JSON for translation tooling integration; most TMS platforms treat the two interchangeably.
When to use YAML: when your stack is Rails or another Ruby framework, or when your team strongly prefers YAML readability. Otherwise JSON is simpler.
PO / POT (GNU gettext)
PO is the oldest widely-used localization format and the most semantically rich of the commonly used formats. A PO entry contains the source string (`msgid`), the translated string (`msgstr`), a translator comment field (`# ...`), an automatic reference comment (`#: filename:linenum`), and a flags field that can encode fuzzy matches, untranslated state, and more.
For translator experience, PO is genuinely superior to JSON/YAML. Translators who work with Poedit, Virtaal, or web-based PO editors have context built into the format: they see the source string, they see where it appears in the code, they see previous translator notes, and they can flag entries for review. None of that context structure exists in bare JSON.
PO's weakness is ecosystem fit. It is native to Python (via `gettext`) and PHP but requires additional libraries in JavaScript and mobile frameworks. If your codebase uses an i18n library that is not gettext-based, working with PO files usually means a conversion step at import/export time.
When to use PO: when translator experience and metadata richness are priorities and your stack has solid gettext support. Python projects and mature PHP applications are natural fits.
XLIFF (XML Localization Interchange File Format)
XLIFF is the industry-standard interchange format for professional translation. It is XML-based and designed to be passed between content owners and translation service providers. XLIFF files carry source text, target text, translation state (`new`, `needs-translation`, `translated`, `final`), alt-trans (translation alternatives from TM), notes, and segment metadata.
For TMS integration, XLIFF is the most capable of the common formats. Every enterprise TMS supports XLIFF import/export. The state attributes map directly to TMS workflow stages. TM matches can be embedded in the XLIFF file itself via `alt-trans` elements, giving the translator context without a TMS roundtrip.
XLIFF's weakness is verbosity and the tooling overhead of working with XML in a modern developer workflow. No popular JavaScript i18n library uses XLIFF natively. It is almost always a format used for handoff to external translation vendors or enterprise TMS, not for direct use as the runtime locale format.
When to use XLIFF: when you are working with external translation agencies, enterprise TMS platforms, or a workflow that requires formal translation state tracking. Often combined with a simpler runtime format -- develop and ship in JSON, export/import via XLIFF for the translation step.
ARB (Application Resource Bundle)
ARB is Flutter and Dart's native i18n format. It is JSON-based with a convention for per-entry metadata: a key's description, placeholders, and translator context live as sibling keys prefixed with `@`. For Flutter projects, ARB is the right choice by default -- the Flutter i18n toolchain is built around it and alternatives create friction.
When to use ARB: Flutter projects. Elsewhere, there is no compelling reason to adopt it.
The Translation Tooling Dimension
Format choice matters most when translation tooling enters the picture -- and the dimension that matters is how well the format supports metadata without requiring a separate tracking system.
JSON and YAML are weakest here. They carry no translation state, no fuzzy match flags, no source-text hashes. If you want to track whether a translation is current, you have to do it out-of-band (in your TMS, in a separate manifest, or in the sync layer).
PO and XLIFF have state and metadata built in. This means your TMS integration is simpler because the format itself carries the information your TMS needs to apply TM matches and track review status.
The practical implication: if you are building a translation sync system that needs to know which strings are stale, which are fuzzy-matched, and which are approved, you will do significantly less custom work if your runtime format is PO or you have an XLIFF step in your pipeline. If you are working with JSON, you will need to maintain a translation manifest or sync-layer metadata store that carries this information separately.
Migration Considerations
Migrating from one format to another is not trivial, but it is often less painful than it looks. The key concerns:
- Key structure: JSON nested keys (`checkout.confirm.cta`) may not map cleanly to PO msgid strings. Decide on a key-to-msgid convention before migrating.
- Plural forms: Every format handles plural forms differently. JSON with i18next conventions uses `_one` / `_other` keys. PO has a structured plural form declaration. XLIFF 2.0 has a `
` element for plural containers. Plurals are the most common migration breakage point. - TM continuity: If you have an existing TM, verify that your TMS can apply it to the new format. A format migration that loses TM context means re-translating strings that were already approved.
If your existing locale files are small and your translation memory is thin, migrate at the start of a new feature cycle when the churn is low. If you have deep TM investment, do the migration with your TMS vendor's support so the TM carries over.
The Practical Recommendation
For most modern SaaS products in the JavaScript ecosystem: JSON is the right runtime format. Pair it with a translation sync layer that maintains source-text hashes and translation state out-of-band, and use XLIFF as the handoff format to any external translation vendor or enterprise TMS. This gives you the simplicity of JSON for developer workflow and the metadata richness of XLIFF for the translation step.
If you are building on Python or PHP with gettext familiarity, PO is genuinely better than JSON for translator experience and should be considered seriously. The main cost is the JavaScript ecosystem tooling gap, which varies by framework.