tbaronnat/translation-bundle
A Symfony bundle for managing application translations from a database instead of flat files. It provides services and tooling to store, retrieve, and organize translation messages across locales, simplifying multilingual content maintenance.
This is a low-adoption package (no stars, no dependents), so treat the source as the documentation. Budget 20 minutes to read it before wiring anything into a project.
composer.json and confirm the Symfony/PHP constraints match your app. A bundle described only as "simple translation bundle system with Symfony integration" is likely built against one Symfony major version; on a newer skeleton it may boot but fail on deprecated service definitions.composer require tbaronnat/translation-bundle
Flex will only auto-register the bundle if a recipe exists. If config/bundles.php is unchanged afterwards, add the bundle class manually — grab its exact FQCN from the package's src/ directory rather than guessing.src/DependencyInjection/Configuration.php — the config tree. The root key is normally the underscored bundle name; confirm it here before writing any YAML, or you'll get "unrecognized option" on boot.src/DependencyInjection/*Extension.php and Resources/config/services.* — which services exist and whether the bundle decorates translator or registers a separate loader.Entity/, Model/, or Repository/ classes — this tells you whether translations live in the database, in files, or both.src/Command/ and src/Controller/ — the day-to-day surface area.php bin/console list | grep -i translat
php bin/console debug:router | grep -i translat
php bin/console debug:container --tag=translation.loader
translations/messages.en.yaml (or whatever the bundle consumes), edit one string through the bundle's UI or command, then load a page that renders it. That single round-trip proves the loader is actually winning over Symfony's default file loader — which is the only thing that matters about this package.Decide the source of truth up front. There are two viable postures and you must pick one deliberately:
Mixing the two without a rule produces strings that "revert themselves" after the next deploy.
If it decorates the translator, check the decoration priority in the service definition. A DB-backed loader that runs last will never override your file translations. When layering, ensure your file catalogues remain as a fallback so a missing DB row doesn't blank out a page.
Standard workflow for a content edit:
// Wrap the write in a transaction and invalidate whatever cache the bundle uses
DB::transaction(fn () => $repo->upsert($domain, $locale, $key, $value));
Cache::forget("translations.{$locale}");
Then verify with php bin/console debug:translation <locale> <domain> — it reports which loader supplied each message, which is the fastest way to confirm your edit took effect.
Export for CI. A small custom command that dumps the bundle's store to translations/*.yaml gives you reviewable diffs and a rollback path, and insulates you if the package is later abandoned:
foreach ($repo->allByLocale($locale) as $row) {
$catalogue[$row->getDomain()][$row->getKey()] = $row->getValue();
}
Guard any admin routes. If the bundle ships a controller, it likely ships no firewall rules. Add access_control entries before enabling it anywhere reachable from the internet — an editable translation store is effectively a content-injection endpoint.
Testing. Boot the kernel in a functional test, write a row through the bundle's service, and assert $translator->trans('key', [], 'domain', 'fr') returns it. Use a separate test database so you're not asserting against seed data.
composer.json, and if you depend on it heavily, fork it or be prepared to vendor the loader into your own codebase — it's likely a few hundred lines.php bin/console cache:clear in dev, and remember opcache in prod. If edits "don't appear," check this before debugging the loader.Configuration::getRootNode(). Writing translation_bundle: when the extension expects something else produces a confusing "unrecognized option" rather than a helpful error.debug:container --show-private to find it; don't assume you can $container->get() it from a controller — inject it.messages, validators, security). A naive DB loader that keys only on locale + key will silently collide across bundles. Verify the schema has a domain column; if it doesn't, don't store validator messages there.framework.translator.fallbacks. Test explicitly: request a key that exists only in the fallback locale and confirm it resolves rather than returning the raw key.doctrine:migrations:diff) and commit it. Don't rely on doctrine:schema:update in any environment you care about, and don't assume the bundle's own migrations run under your app's migration config.services.yaml to add caching or auditing without forking; use a compiler pass if you need to swap the implementation wholesale. Both are cheaper than editing vendor code.How can I help you explore Laravel packages today?