Convert YAML to JSON

Convert Symfony or Rails YAML catalogs into JSON for Laravel JSON files or JavaScript i18n libraries.

Getting a YAML catalogue into something a bundler can read

YAML is pleasant to edit and awkward to ship. Browsers cannot parse it, bundlers need a loader for it, and shipping a YAML parser to the client to read translation data is a needless dependency. Converting to JSON produces something every runtime already understands, at roughly a tenth of the parsing cost.

Input — messages.yaml

welcome: 'Welcome back, :name!'
cart:
    empty: 'Your cart is empty'
    items: ':count item|:count items'

Output — messages.json

{
    "welcome": "Welcome back, :name!",
    "cart": {
        "empty": "Your cart is empty",
        "items": ":count item|:count items"
    }
}

Indentation becomes nesting, and the resulting object drops straight into vue-i18n, i18next or a Laravel JSON language file. The output is UTF-8 without ASCII escaping, so accented characters and non-Latin scripts stay legible rather than becoming \u00e9 sequences.

There is a performance argument alongside the compatibility one. Browsers parse JSON with a native, heavily optimised routine, while YAML requires a JavaScript parser measured in tens of kilobytes that then runs far slower. For a translation bundle loaded on every page view, shipping JSON instead is one of the cheapest wins available.

Where types get lost on the way in

This conversion has one significant asymmetry, and it runs in the direction people rarely check. YAML infers types from unquoted values, so by the time the parser hands you a tree, some of your translation strings may no longer be strings at all. JSON then faithfully records whatever the YAML parser decided.

What unquoted YAML does to a language list

# Input
languages:
    norwegian: no
    version: 1.10
    released: 2026-08-16

# Output — none of these are strings any more
{
    "languages": {
        "norwegian": false,
        "version": 1.1,
        "released": "2026-08-16T00:00:00Z"
    }
}

The Norwegian language code becoming false is the canonical example, and it has broken real production systems. The version string losing its trailing zero is subtler and arguably worse, because 1.1 looks plausible everywhere it appears. Neither produces an error at any point in the pipeline.

The asymmetry is worth stating plainly: converting JSON to YAML is lossless, because JSON has already committed to a type for every value. Converting YAML to JSON is not, because the YAML parser makes those decisions for you and the JSON output records the result as though it had always been intended.

Guard against it by asserting that every leaf in the converted JSON is a string. A translation bundle should contain nothing else; numbers and booleans belong in configuration, not in copy. The check is a few lines and it catches the whole family at once.

Asserting every value is a string

const nonStrings = []

const check = (node, path = '') => {
  for (const [key, value] of Object.entries(node)) {
    const at = path ? `${path}.${key}` : key
    if (value !== null && typeof value === 'object') check(value, at)
    else if (typeof value !== 'string') nonStrings.push([at, value])
  }
}

check(messages)
if (nonStrings.length) throw new Error(JSON.stringify(nonStrings))

YAML features that flatten out during conversion

YAML has capabilities JSON simply does not, and they resolve at parse time rather than surviving into the output. None of these break the conversion, but each changes what the file looks like afterwards.

  • Comments disappear. Every # annotation explaining a tricky string to translators is gone, permanently. If those notes matter, move them into a translator-notes field before converting.
  • Anchors and aliases expand. &shared and *shared are resolved, so a fragment referenced in ten places is duplicated ten times in the JSON. Editing it afterwards means editing all ten.
  • Merge keys are applied. <<: inheritance is flattened into the resulting object, which is usually what you want but makes the output considerably larger than the source.
  • Only the first document survives. A file with --- separators holds several documents; JSON has no equivalent, so the rest are typically dropped.
  • Duplicate keys collapse. YAML permits them and most parsers keep the last silently, so a translation can vanish with no warning.

Because the conversion is deterministic, the sensible pattern is to treat YAML as the source you edit and JSON as a build artefact you generate. Commit the YAML, gitignore the JSON, and regenerate it in CI. That way the comments and anchors keep their value for the humans while the machines get the format they actually want.

Fitting the output to its consumer

Nested JSON is the common shape, but each runtime expects something slightly different around it, and the mismatches fail quietly rather than loudly.

Consumer Shape Placeholder
vue-i18n Nested object per locale {name}
i18next Nested, optionally namespaced {{name}}
React Intl Flat keys, ICU syntax {name}
Laravel JSON Flat, source sentence as key :name

If the YAML came from Rails, strip the top-level locale key first — a file wrapped in en: converts into an object with a single en property, and every lookup path gains a segment that should not be there. Symfony catalogues have no such wrapper, but they do use %name% placeholders that need rewriting for a JavaScript runtime.

Plural forms need the most attention. Rails encodes them as one and other sub-keys, Laravel as pipe-separated forms in one string, and ICU as a structured message block. None of these convert mechanically into another, and how many forms a locale needs is a property of the language rather than the file — check each target with the pluralization tester before rewriting.

Making it a build step

Because the conversion is deterministic, the durable arrangement is to convert automatically rather than by hand. The YAML is the file people edit and review; the JSON is an artefact regenerated whenever the YAML changes.

  1. 1 Commit the YAML, ignore the JSON. One source of truth, no possibility of the two drifting apart, and no merge conflicts in a generated file.
  2. 2 Regenerate in CI. Convert during the build so a malformed YAML file fails the pipeline rather than the browser.
  3. 3 Validate after conversion. Assert that every leaf is a string and that all locales expose the same key set. Both checks are cheap and catch the failures that otherwise reach users.
  4. 4 Rewrite placeholders in the same pass. If the front end needs {{name}} and the catalogue holds :name, do it here rather than asking translators to think about syntax.
  5. 5 Fail loudly on missing keys. A locale that silently falls back looks fine in testing and looks broken to the users who speak that language.

Keep the generated file out of code review as well. A regenerated bundle produces a large mechanical diff that buries the two lines a reviewer actually needs to see, and reviewers who learn to skim generated diffs will eventually skim a real change.

The one thing worth preserving manually is the comments, since they do not survive. Before adopting this pipeline, move any translator guidance out of YAML comments and into a field that travels with the string, or it will quietly be lost the first time someone regenerates the bundle.

Frequently asked questions

Does the converter keep :placeholders and plural pipes intact?

Yes. Values are treated as opaque strings, so Laravel placeholders like :name or :count and pipe-separated plural forms come through unchanged.

How is nesting handled?

Nested arrays in YAML map one-to-one to nested structures in JSON; nothing is flattened.

Is my data uploaded or stored?

The conversion runs on our server but nothing is persisted: files are converted in memory and the response is returned immediately. For PHP input, only string, number and array literals are parsed — code is never executed.

Is there a size limit?

Yes, 200 KB per conversion, which covers even very large lang files. If you need bulk conversion across many locales, import your files into LangSyncer and export any format.

Converting files by hand gets old fast

Import your lang files into LangSyncer once, edit and translate them in a dashboard, and export any format — or skip files entirely with live translations.

We use cookies to improve your experience and analyze site traffic. Cookie Policy

Cookie Preferences

Essential

Required for the site to work

Analytics

Help us improve the site

Marketing

Personalized ads and content