Convert JSON to YAML
Convert JSON message catalogs into YAML for Symfony, Rails or configuration-driven pipelines.
Trading strictness for readability
JSON and YAML describe the same data. YAML is in fact a superset of JSON, so every valid JSON document is already valid YAML. The reason to convert is not capability but ergonomics: YAML drops the braces, the quotes and the trailing-comma rules, and adds the one thing JSON conspicuously lacks — comments.
Input — messages.json
{
"welcome": "Welcome back, :name!",
"cart": {
"empty": "Your cart is empty",
"items": ":count item|:count items"
}
}
Output — messages.yaml
welcome: 'Welcome back, :name!'
cart:
empty: 'Your cart is empty'
items: ':count item|:count items'
Nesting becomes indentation, and the tree structure is preserved exactly. Values are quoted where quoting is required to keep them unambiguous and left bare where it is not.
Placeholders, pipe-separated plural forms and embedded HTML all pass through untouched, because the converter treats values as opaque text. That is the safe default: a tool that rewrites message bodies on your behalf is a tool that will eventually corrupt one, and translation bugs are unusually expensive to find because they are invisible to anyone who does not read the language.
What you gain, concretely
- Comments. You can annotate a string with the screen it appears on, a character budget, or a warning that a test asserts against it. JSON has no comment syntax at all, which is its single biggest weakness as an authoring format.
- Cleaner diffs. Adding a key to a JSON object touches the previous line to add a comma. In YAML it is a one-line addition, which makes review noticeably easier across large translation files.
- Fewer syntax errors from humans. A misplaced comma or an unclosed brace invalidates a whole JSON file. YAML has no punctuation to forget, so non-developers break it far less often.
- Native fit for config pipelines. CI systems, Kubernetes manifests, Ansible and most static site generators speak YAML, so translation data can live alongside deployment configuration rather than beside it.
- Framework compatibility. Symfony reads YAML message catalogues directly, and Rails expects them, so this is the natural export when copy is shared beyond a JavaScript stack.
Comments are the one that changes daily practice most. A translation file accumulates hard-won knowledge — this string is legally reviewed, this one has a thirty-character budget, this one appears twice and must stay consistent — and JSON gives you nowhere to record any of it. Teams end up keeping that context in a wiki that goes stale, or in the head of whoever wrote the string.
The trade is real, though. YAML is dramatically more complex to parse than JSON, with anchors, aliases, merge keys, tagged types and multiple documents per file all in the specification. For pure machine-to-machine interchange JSON remains the safer choice. Convert to YAML when a human is going to open the file.
The type coercion trap
JSON is explicit about types: a quoted value is a string, always. YAML infers type from the shape of an unquoted value, and translation files are full of short strings that look like something else. This is the single most common way a converted YAML file misbehaves.
| Unquoted | Parsed as | Intended |
|---|---|---|
no |
boolean false | Norwegian locale code, or the word |
yes / on / off |
boolean | a label |
null / ~ |
null | the literal word |
1.10 |
the number 1.1 | a version string |
2026-08-16 |
a date object | a date as text |
12:30 |
sexagesimal number in YAML 1.1 | a time label |
The no case is well known enough to have a name — the Norway problem — because a list of language codes containing no silently becomes false. It is the best argument for keeping quotes on translation values even when the parser does not strictly require them.
Punctuation causes a second family of problems. In an unquoted value, a colon followed by a space begins a new mapping, so Error: try again parses as a key rather than a sentence. A leading - reads as a list item, # starts a comment, and & or * introduce anchors and aliases. Quoting neutralises all of them at once.
The converter quotes values that would otherwise be coerced. If you tidy the file by hand afterwards, leave those quotes alone — removing them is how these bugs get introduced.
Multi-line copy and indentation rules
JSON encodes line breaks as \n inside a single quoted string. YAML offers block scalars instead, and the two available styles are not interchangeable.
Literal keeps newlines, folded joins them
email:
body: |
Line one stays on its own line.
Line two does too.
intro: >
These two lines are joined
into one with a space between them.
Picking the wrong style silently reformats your copy — a folded block applied to an address destroys the line breaks, and a literal block applied to a wrapped paragraph introduces hard breaks that show up in rendered HTML. When you are unsure, keep the single-line quoted form with explicit escapes, which behaves identically in every parser.
Two mechanical rules matter. Indentation must be spaces, never tabs; YAML forbids tabs outright and the resulting parse error is often reported several lines away from the offending character. And trailing whitespace is significant inside block scalars, while most editors strip it on save, so any string that depends on a trailing space should be quoted rather than written as a block.
Finally, note that duplicate keys are permitted by the YAML specification and most parsers silently keep the last one. JSON parsers behave similarly, so a duplicate in the source survives the conversion as a single surviving value — worth checking for if a string disappears between formats.
Who consumes the YAML afterwards
The destination shapes how much extra work the conversion needs, because the message tree is only part of what a framework expects.
| Destination | Expected file | Extra work |
|---|---|---|
| Symfony | translations/messages.en.yaml |
Rename :name to %name% |
| Rails | config/locales/en.yml |
Wrap everything under an en: root key |
| Jekyll / Hugo | _data/*.yml |
None; read as data directly |
| Localisation platform | varies | Usually none |
Rails is the case people forget. It requires a top-level key naming the locale, wrapping the entire tree, and a converted file without it simply will not load. Symfony encodes the same information in the filename instead, so no wrapper is needed there.
Plural forms are the part no converter can handle for you. Rails expects named one and other sub-keys under each message; Symfony has moved to ICU MessageFormat; Laravel packs the forms into a single pipe-separated string. Each encoding expresses the same idea in a structurally different way, and how many forms a language needs is a property of the language rather than the file. Confirm each target with the pluralization tester before rewriting, because a source catalogue that only ever supported two forms will convert its incompleteness faithfully into the new format.
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 JSON map one-to-one to nested structures in YAML; 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.
Other free tools
Untranslated Key Detector
Scan any website for leaked i18n keys: dot-notation identifiers, missing-translation markers and unrendered placeholders your users can see.
Blade Hardcoded String Scanner
Paste a Blade template and find text that should be behind __() — with suggested keys and a generated lang file.
Pluralization Tester
Pick a language, type a number, see which CLDR plural category applies — live, via your browser's Intl.PluralRules.
AI Translation Cost Calculator
Keys × languages = quota units. Estimate the initial run and the monthly churn for your project.
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.