runroom-packages/translation-bundle
Runroom TranslationBundle adds database-backed, per-locale translations to Symfony entities, so you can manage multilingual content in the admin with locale fallbacks and avoid duplicating records.
This is a narrow-purpose bundle: it plugs a Doctrine-backed translation store into Sonata Admin so editors can manage messages.* entries from /admin instead of committing YAML files. Treat the README and the bundle's src/ as the source of truth — with 1 star and 0 dependents, expect documentation to be thin and the API surface to match Runroom's internal Sonata projects rather than a generic audience.
Minimal path:
composer require runroom-packages/translation-bundle — pin an explicit version. The package is effectively a READ-ONLY extraction from a monorepo, so there is no meaningful dev-main stability guarantee.config/bundles.php, after SonataAdminBundle and DoctrineBundle.bin/console doctrine:schema:validate and generate a migration; if it only ships a repository interface, you must provide your own entity and point the bundle config at it.TranslationAdmin you tag with sonata.admin, or you extend it in your own src/Admin/. Check Resources/config/ for the service definition and the expected model_class.bin/console cache:clear and load /admin/dashboard → the Translation entry should appear.First real use case: seed the table with your existing file-based translations (via translation:extract or a one-off import command), then have a non-developer edit a string in the admin, clear the cache in dev, and confirm the change renders. That single loop tells you whether the bundle's loader is wired correctly before you invest in migration tooling.
DB-first with files as fallback. The bundle works by contributing a translation loader to Symfony's loader chain. The typical integration is to keep your translations/*.yaml files as the canonical source for new keys (so translation:extract still works in CI), and let the DB override them at runtime for edited strings. Verify the loader priority in config/services.yaml — if the DB loader runs before the file loader, an empty DB silently blanks your UI.
Sonata Admin extension pattern. Don't edit the bundle's admin class. Extend it in your app:
// src/Admin/TranslationAdmin.php
final class TranslationAdmin extends AbstractTranslationAdmin
{
protected function configureFormFields(FormMapper $form): void
{
$form->add('translationKey')
->add('locale', ChoiceType::class, ['choices' => $this->locales])
->add('translationValue', TextareaType::class, ['required' => false]);
}
}
This is where you add your locale whitelist, tenant column, or domain (messages vs validators) rather than patching vendor code.
Per-domain and per-locale scoping. Real projects have multiple domains and dozens of locales. Model this as columns on the translation row (domain, locale, key, value) and filter the admin list with configureDatagridFilters(). A flat key/value table without a domain column becomes unusable past ~500 strings.
Import/export as a console command. The day-to-day friction is drift between files and DB. Wrap the sync in a command:
app:translations:import — read translations/*.{yaml,xlf}, upsert into DB, skip existing keys unless --force.app:translations:export — dump DB back to YAML for review PRs.Run import on deploy (after doctrine:migrations:migrate) so the DB never lags the files.
Cache warming in the deploy pipeline. Because the DB is read at request time, a naive setup adds a query per locale per request. Ensure the loader is cached — either rely on Symfony's compiled catalogue (cache:clear / cache:warmup in the deploy step) or memoize the repository call in the loader. In prod, translation edits made in the admin will not surface until cache is cleared; wire that into the admin's post-persist hook or accept a documented "publish" button.
composer.json are tight and tied to whatever Runroom ran at extraction time. Check composer.json requirements against your composer.lock before installing; expect to fork and relax them.bin/console debug:container translator.default and inspect translation.loader tags. If the DB loader is registered with a low priority, files win. Also confirm the loader declares support for yaml/xlf if it delegates.cache:clear after every admin edit in dev. Symfony compiles the translation catalogue per locale. If edits appear to do nothing, this is almost always the cause — not a broken bundle.translation:extract reads PHP/YAML/Twig; removing the source files breaks extraction and CI checks.doctrine:migrations:diff. Map it via Doctrine's resolve_target_entities or override the mapping config rather than editing vendor XML.null value in the DB means "no override," an empty string means "render nothing, and shadow the file." Be explicit in the admin form (required => false maps to null) or you'll ship blank labels.bin/console debug:translation <locale> shows the resolved value and where it came from; bin/console debug:translation <locale> --only-missing is your audit for untranslated keys. Use these before assuming the bundle is broken.ROLE_TRANSLATION_EDITOR in security.yaml and a sonata.admin.security check; translation values often include marketing copy and occasionally contact strings you don't want broadly editable.How can I help you explore Laravel packages today?