liip/translation-bundle
Symfony bundle from Liip that adds practical tools for managing application translations. It helps developers inspect, edit, and maintain message catalogs across locales, making it easier to keep multilingual Symfony projects consistent and up to date.
LiipTranslationBundle is a Symfony bundle that adds console tooling for translation management: extracting message keys out of Twig/PHP source into translation files, and keeping per-locale files in sync. It targets the Symfony 2.x era (last release June 2015, repo archived), so the realistic starting point is an existing legacy application, not a new one.
Minimal steps:
composer require liip/translation-bundle
Register the bundle in app/AppKernel.php:
$bundles = array(
// ...
new Liip\TranslationBundle\LiipTranslationBundle(),
);
Add a config block in app/config/config.yml (names vary by version — read DependencyInjection/Configuration.php in the installed tag rather than trusting blog posts):
liip_translation:
locales: [en, de, fr]
default_locale: en
# domains / file format options depend on the version you pulled
Then confirm what you actually got:
app/console list liip:translation
app/console help liip:translation:<command>
Where to look first (in this order): the README, then Command/ to see the real command names and options, then DependencyInjection/Configuration.php for the exact config tree. In an archived bundle the code is the documentation.
First use case: point the extract command at your templates and source locale, and let it append the keys it finds but that are missing from messages.en.yml. Run it against one bundle/template directory first, git diff the result, then widen the scope. Do not run it across the whole app on the first attempt — you will get a large, hard-to-review diff.
Extract → review → translate loop. The bundle is most useful as the first half of a pipeline: extraction populates the source-locale file, humans or a TMS fill in the rest. Keep the source locale file as the canonical key list and treat every other locale as derived.
# typical day-to-day cycle
app/console liip:translation:extract --env=dev
git diff app/Resources/translations/ # review added keys
# commit, then hand off to translators / TMS
Sync new keys into target locales. When you add a feature, the source file gains keys that the German file does not have. Use the bundle's sync/skeleton behavior (or the equivalent option on the extract command in your version) to seed empty entries in every configured locale, so missing translations fail loudly instead of falling back silently to English.
Multi-domain setup. Real apps have messages, validators, admin, emails. Configure the domain list rather than letting everything land in messages. Keys extracted from a template that uses a domain must go to that domain's file, or your |trans({}, 'admin') lookups will miss.
Gate it in CI as a drift check. A pattern that pays for itself: run extraction with a "dry run / show only" mode in CI and fail the build if new untranslated keys appear in a PR. It turns "we forgot to translate" into a review comment instead of a production bug.
# Makefile
translations:
app/console liip:translation:extract --env=dev
app/console liip:translation:extract --env=dev --dry-run # CI gate
Pair extraction with an unused-key audit. Extracted keys accumulate forever. Run the tooling periodically against a search for each key in templates and src/ (grep -r "some.key") and prune. For anything dynamic, a human must confirm.
Vendor/TMS handoff via XLIFF. If your translators use a CAT tool, configure the output as XLIFF rather than YAML. YAML round-trips lose comments and reorder keys, which makes diffs noisy and translators confused; XLIFF keeps id/source/target structure intact.
Wrap it, don't scatter it. Put the commands behind a composer script or Makefile target so everyone runs the same flags with the same config. Ad-hoc invocations by individual developers are how translation files end up with mixed formatting and lost entries.
It's archived — that is the headline gotcha. No Symfony 4/5/6/7 support, PHP 5.x assumptions, zero dependents. Don't try to install this into a modern app. The current equivalent is built into Symfony itself: bin/console translation:extract, plus translation:pull / translation:push for third-party providers. If you are in a legacy app that already uses this bundle, keep it; if you're starting fresh, use the core commands or a TMS (Weblate, Crowdin, Phrase, Loco).
It rewrites files in place. Always commit or stash clean translation files before running extract/sync. There is no undo, and merge conflicts inside generated YAML are miserable.
Only literal strings are extractable. {{ 'checkout.title'|trans }} is fine. {{ ('checkout.' ~ step)|trans }}, {{ $key|trans }}, and keys built from variables are invisible to the extractor. Grep for |trans with concatenation or variables and maintain those by hand — this is the number one source of "key exists in code, missing in file" bugs.
Pluralization and transChoice (%count%-style messages) are handled unevenly. Verify plural keys survive a round trip; if they don't, exclude those files from the automated pass.
Config silently skips locales. If a locale is missing from the configured locales list, its file simply won't be updated — no warning. Same for a domain that isn't listed. When a key "doesn't appear," check config before debugging the extractor.
Debugging extraction misses: run with -v/-vvv to see which files were scanned, temporarily narrow the scan path to one bundle, and confirm the file extension is one the extractor recognizes (.twig, .php). Clear the Symfony cache after changing bundle config — config is compiled.
Extension points, if you must customize: the bundle is structured the Symfony way, so the usual levers apply — a custom extractor registered via a service tag, an overridden command service definition in your app's services.yml, or replacing the file loader/dumper to control output formatting. Check the Resources/config/ XML for the actual service ids and tags in your version.
Diff hygiene: set a stable dump format (sorted keys, consistent indentation) and enforce it. Otherwise every extract run reorders unrelated lines and buries the real change, and reviewers stop looking at translation PRs — which is exactly when a wrong string ships.
How can I help you explore Laravel packages today?