How to Translate a YAML Locale File Without Losing Your Comments
To translate a YAML locale file, use a tool that rewrites only the value text and leaves the rest of the file alone. The YAML File Translator does that: your keys, comments, anchors, merge keys, indentation and quoting come back as they were, because they are never rewritten in the first place.
That sounds like a small distinction. It is the entire problem.
Why most YAML tools mangle the file
Almost every YAML tool follows the same three steps: parse the file into memory, change the parts you asked for, then write a new file out.
That third step is where your file gets damaged. A YAML dumper does not remember what your file looked like. It re-decides everything from scratch: whether to quote a string, which quote to use, how deep to indent, how to order keys, when to wrap a long line. Comments are not even part of the parsed structure, so they are simply gone.
The result is that a translation you asked for produces a diff full of changes you did not. Your reviewer opens a pull request meant to add Spanish and finds every line touched, the # TODO notes deleted, and the anchors expanded into duplicated blocks. Somewhere in there are the actual translations.
The fix is to not rewrite the file at all.
Only the values get rewritten
This tool parses your YAML to find out where each string value starts and ends, in exact character positions, then replaces just those stretches of text in your original file. Every other byte, keys, comments, blank lines, indentation, document markers, is carried through untouched because nothing ever asks it to change.
One consequence is worth stating as a test rather than a promise: run a file through with a translation that returns every string unchanged, and you get your original file back byte for byte, comments included. We check that on real fixtures, and it holds.
What in a locale file is not language
Open config/locales/en.yml and most of what you see is machinery.
Keys are lookup identifiers. greeting, errors.not_found, dialogs.delete.title. Your code calls these by name. Translating a key does not localize anything, it breaks every call site that referenced it. They are never touched here, on principle, and there is no setting to turn that off.
Anchors and merge keys are structure. &defaults defines a shared block, <<: *defaults pulls it into another. The tool follows them to the definition, translates the shared values exactly once there, and leaves every *defaults reference alone. You do not get the same block translated three times with three slightly different wordings.
Non-strings stay as they are. minutes_required: 3 is a number, show_progress_bar: true is a boolean, and neither is text a human reads. They keep their exact bytes.
Placeholders are contracts with your code. %{name} in Rails, {{count}}, %s, ${x}. These are pulled out before the model sees the string and put back afterwards, so it cannot rename, translate or drop one. If a placeholder somehow would not survive, that single value is kept in the original language instead of shipped broken, and the tool tells you how many.
What is left is the prose. That is what gets translated.
Translating the file
- Open the YAML File Translator.
- Drop your
.yamlor.ymlin, or click to browse for it. - Pick your target language under To. Leave From on Auto-detect.
- Click Translate File.

Use the glossary for UI vocabulary
Open the glossary box before you translate. On a locale file this matters more than it does on prose, because a locale file is full of short strings that a model decides on independently.
Two buttons labelled “Try again” in different sections can easily come back as two different Spanish verbs. Nothing is wrong with either one, but your interface now says two things for one action. The glossary removes the decision:
Try again = Reintentar
Cancel = Cancelar

Whatever you put on the right is what ships, everywhere that term appears. You can also pin a term to itself, like Acme = Acme, to keep a product name out of the translation entirely.
What comes back
Here is the actual result from that file, original on the left, Spanish on the right.

Read down the two sides and check the whole contract at once.
Both comments are still there, in the same place, in English: the # config/locales/en.yml header and the # Shown on the signed-out landing page. note. Comments are the first thing a dumper throws away and they are the thing most worth keeping, because they are the notes your team left each other.
Every key is identical. greeting, farewell, defaults, retry_label, dialogs, delete, title, body, leave. So is the indentation, and so are the blank lines between sections.
defaults: &defaults still declares its anchor, and both <<: *defaults merge keys are untouched. The two shared labels underneath were translated once, at the definition, and both dialogs still inherit them.
Placeholders held. %{name} appears in both the greeting and the farewell, %{count} sits inside the delete dialog body, and the header confirms 4 placeholders kept. The glossary held too: retry_label came back as “Reintentar” and cancel_label as “Cancelar”, exactly as specified.
The heading reads 12/12 values translated (100%), and that number is not decoration. When a value comes back in the source language, this tool says so in the heading, before you download. Partial results are reported as partial rather than presented as success.
One difference you will notice, and why it is there
Look at farewell. It went in as a plain unquoted value and came back wrapped in double quotes.
That is deliberate. The translated text contains commas, and rather than reason about whether a comma is safe in that exact position, the tool wraps the value in quotes. A quoted YAML string is always valid. A plain one that guessed wrong changes how the file parses, which is a much worse outcome than a pair of quotes in your diff.
It is the same string either way, and your code reads it identically. Where quoting can be preserved safely it is: title: ¿Eliminar este proyecto? stayed plain, and values that started out double-quoted stayed double-quoted.
Block scalars are left alone on purpose
If your file has values written with | or >, those come back in the source language, untouched. This is a documented limitation rather than an oversight: re-indenting translated multi-line text inside a block scalar without corrupting it is genuinely difficult, and getting it wrong breaks the file. They are excluded from the coverage count, so the number in the heading still tells you the truth about the values that were in scope.
If your long-form strings live in block scalars, translate them separately with the Text Translator and paste them back.
One step stays yours: the locale key
For a Rails file, the top-level key is the locale itself:
en:
greeting: "Welcome back, %{name}"
The tool will not rename en: to es:, because it never changes keys, and a tool that made one exception for this one would be a tool you could no longer trust with the others. Rename it yourself after downloading, and name the file to match, like config/locales/es.yml.
That is one edit per file, and it keeps the rule absolute.
Tested, not promised
The YAML engine has a public test harness. As of the run on 2026-08-01 it passes 3 of 3 fixtures: a Home Assistant style app config, a Rails locale file, and a file of deliberate edge cases including multiple documents, flow styles, CJK text and emoji.
Across those fixtures it saw 36 strings, selected 27 for translation, deliberately skipped 9, protected 5 placeholders, and preserved 9 comments.
The skipped count is the half worth looking at. A quarter of the strings in these files should never be translated, and a tool that translates all 36 is not being more thorough, it is being wrong 9 times. Full results are on the evals page.
What free means here
No signup, no account, no daily cap, no per-file word cap, and no watermark on anything. Your file is processed in memory and no copy is kept, so there is nothing for us to delete later.
The limits that do exist, said plainly so you do not find out mid-task:
- 400 KB per file. A large locale file will need splitting. Rails apps usually namespace by section, so splitting by namespace and translating the parts is the natural move.
- One file at a time. There is no batch upload on this tool yet.
- One target language per run. There is no multi-language run that hands you a ZIP here yet. Run it once per locale.
If your job does not fit inside those, tell us about it rather than fighting the limit.
A checklist before you commit the file
- Put your UI vocabulary in the glossary before the first run, not after the third.
- Check the coverage number in the heading. If it does not say 100%, run it again before you ship it.
- Rename the top-level locale key, and the file name to match.
- Skim the diff for values that were quoted on the way out. They are safe, and it is worth knowing which ones changed shape.
- If you use block scalars, remember those came back in the source language and still need a pass.
- Run your test suite. A locale file is code, and your i18n tests will catch a missing key faster than a reviewer will.
Ready when you are: the YAML File Translator is free, needs no account, and keeps no copy of your file.
If your app strings live in JSON instead, the JSON Translator translates values while keeping every key. For a spreadsheet of strings, the CSV Translator lets you pick which columns get translated.