nowo-tech/translation-yaml-tools-bundle
Symfony bundle for working with YAML translation files: extract and organize translation keys, sort and merge YAML catalogs, detect missing or unused entries, and keep locale files consistent. Streamlines translation maintenance across multiple languages.
Treat this as a dev/CI-only console tool, not a runtime bundle. Nothing here belongs in a production container.
Install as a dev dependency
composer require --dev nowo-tech/translation-yaml-tools-bundle
If Flex does not auto-register it, add it to config/bundles.php guarded by environment so the commands never ship to prod:
if (in_array($env, ['dev', 'test'], true)) {
Nowo\TranslationYamlToolsBundle\TranslationYamlToolsBundle::class => ['all' => true],
}
Discover the actual surface before writing anything else. With 0 stars and 0 dependents there is no battle-tested documentation, so the source of truth is the bundle itself:
bin/console list # filter for the bundle's command namespace
bin/console help <command> --help # options: --dry-run, --domain, --locale...
bin/console config:dump-reference <alias> # find the alias if it isn't obvious
grep -r "getAlias" vendor/nowo-tech/translation-yaml-tools-bundle/src/DependencyInjection
Also open src/DependencyInjection/Configuration.php — that file is the definitive list of provider keys (google, deepl, libretranslate) and their options.
First use case: flatten one file, look at the diff, nest it back. Do not start with "sort my whole translations tree". Pick a single domain (messages.en.yaml), copy it aside, and run the nested↔flat conversion with whatever dry-run/diff flag the command offers. The whole point of the bundle is nested-tree ↔ flat-key conversion plus key sorting; verify the round-trip is byte-stable on your files before trusting it on the repository.
Configure the minimum: provider credentials via env (GOOGLE_TRANSLATE_API_KEY, DEEPL_AUTH_KEY, etc. — confirm names against Configuration.php), and point the bundle at your translations directory. Leave machine translation off until you have sorted your existing files, because sorting and translating in the same change makes review impossible.
Sorting as a normalised repo invariant. Run key sorting once across translations/, commit it as a single dedicated commit (chore: sort translation keys), add that SHA to .git-blame-ignore-revs, and then enforce it in CI. The CI job is the highest-value pattern here: a sort command run with --dry-run (or git diff --exit-code after running it) fails the build when someone adds an unsorted key, which kills the entire class of translation-merge conflicts. Do this before you enable anything else.
Flat-file editing for bulk work. The flattening command is most useful as an authoring aid: flatten messages.de.yaml to a key.subkey: value file, edit/hand it to a translator, nest it back, review the diff. Keep the flat intermediate out of the repository — it is a scratch artefact.
Missing-key extraction → machine translation → human review. The realistic workflow is: use the reference locale (en) as the source of truth, diff against target locales to find missing keys, send only those keys to the provider, write them into the target YAML, then treat the output as a draft. Never let MT output reach main without a reviewer, and never run the translate command over keys that already exist (most implementations overwrite).
Wire it into Composer scripts / Makefile, not into developers' muscle memory:
"scripts": {
"translations:sort": "bin/console <sort-command>",
"translations:check": "bin/console <sort-command> --dry-run"
}
This keeps the invocation (and the inevitable command-name churn) in one place.
Pluggable providers are the extension point. The bundle advertises Google/DeepL/LibreTranslate plus "pluggable", so the day-to-day pattern is: implement the bundle's provider interface in your app, register it as a service with the tag the bundle's compiler pass consumes (check src/DependencyInjection/*CompilerPass* for the exact tag), and select it by name in the config. This is how you route through an internal gateway, add a cache, or add a provider the bundle doesn't ship. For LibreTranslate you can self-host, which is the right pick if source strings are sensitive.
Do not run it over vendor/. Scope the command to your own translations/ tree and exclude any vendored bundles' translation directories, either via the command's path argument or an exclude pattern in config.
composer.json and read the command source before relying on it. Budget for the possibility that it is abandoned and you fork it..git-blame-ignore-revs — otherwise git blame on your translation files becomes useless.| / >) and multi-line strings, and can requote values. If you rely on comments or exact formatting in translation files, test the round-trip on a copy first; consider the sort command the only rewriter you allow, and never round-trip files that carry comments.%placeholder% keys, and dots-in-keys are handled sanely. Mixed conventions (a.b.c vs literal a.b.c keys) will bite on flatten/nest.%count%, {{ name }}, :attribute, ICU {count, plural, ...} blocks, and HTML tags are routinely corrupted by every MT API. Add a post-translation validation step (placeholder parity + ICU parse check per locale) before merging, and never auto-commit MT output.--dry-run everything, and check for a cache/batch option in the command's --help.git diff shows exactly what the tool produced.composer why-not symfony/framework-bundle on your actual version, and check src/Resources/config/services*.php for deprecated service definitions before upgrading Symfony.-vvv and --dry-run first; if a command isn't listed, the bundle isn't registered in config/bundles.php for that environment — that is the single most common "it's installed but nothing happens" failure.How can I help you explore Laravel packages today?