softspring/translatable-bundle
A Symfony bundle for building translatable Doctrine entities, letting you store and manage per-locale field values with locale fallback, so the same content can be served in multiple languages without duplicating your models or templates.
First, resolve the identity mismatch. This is a Symfony bundle built on Doctrine ORM — not a Laravel/Eloquent package. It adds entity listeners and per-locale translation tables to Doctrine entities. There is no Eloquent analogue, no service provider, no HasTranslations trait. If the target is a pure Laravel app, stop here; the day-to-day workflow below does not transfer. If your portfolio includes Symfony services (or you maintain one inside a Laravel-heavy org), treat it as a service-level dependency and read on.
Minimal steps (Symfony):
composer require softspring/translatable-bundle:^5.6config/bundles.php (Symfony 5.4+/6).config/packages/softspring_translatable.yaml — this file is the whole surface area; there is no large config tree.php bin/console doctrine:schema:update --dump-sql → review → migrate.Where to look first. The docs are thin and the v5.6.0 release body is an empty diff link, so:
vendor/softspring/translatable-bundle/src/ — the event subscriber/listener, the mapping driver, and DependencyInjection/Configuration.php. The Configuration class is the authoritative spec for locale/fallback semantics.vendor/softspring/translatable-bundle/tests/ — the bundle's own test entities are the working examples. Read these before the README.https://github.com/softspring/translatable-bundle/compare/v5.5.10...v5.6.0 — the only real release content for 5.6.0.First use case: a single Article entity with title, slug, body translatable, two locales, fallback to default. Write in the current request locale, render in the requested locale, verify the fallback fires when a translation row is missing. That vertical slice exercises mapping, listener, persistence, and locale resolution — the four things that can go wrong.
Read/write in the request locale. Set _locale via Symfony routing or a LocaleListener; the bundle resolves against that. For admin/forms, use per-locale tabs and switch the translator locale before form binding, or you will bind into the wrong translation row.
Fallback chains are config, not code. Define an ordered list (e.g. requested → regional parent → default). Because fallback is silent by design, you need tests that assert which locale's value came back, not just that a value came back.
Querying without N+1. Translations are a collection; listing pages will explode into one query per row. Join-fetch the translation for the active locale in DQL, or accept a locale-filtered repository method. Index (translatable_id, locale) — the bundle's schema may or may not generate this; check the --dump-sql output.
Migration patterns, by starting state:
^5.6, define translatable entities, generate the schema, backfill default-locale rows in a fixture or data migration.^5.5.10|^5.6, then require an empty doctrine:schema:update --dump-sql. Make that a CI gate, not a manual step.Serialization/API. Normalise per Accept-Language, and key any cache by locale. Emit Vary: Accept-Language on HTTP responses or CDNs will serve the wrong language.
Testing pattern. Functional tests that (a) assert fallback ordering, (b) run doctrine:schema:validate, (c) load fixtures with the locale explicitly set rather than inherited from the test's environment.
composer.json constraint changes before upgrading. A minor bump touching entity persistence can change fallback order, proxy handling, or hydration — and those surface as data bugs, not exceptions.--dump-sql that "looks benign" is the thing to review hardest.orphanRemoval leaves orphaned rows across locales.null vs empty string. Decide what "untranslated" means and test it. Fallback triggering depends on this and the bundle's answer may not be yours.src/ for the resolvable service ID and override in services.yaml rather than forking. Forking an AGPL package has licensing consequences.debug:event-dispatcher to confirm the listener is registered; the Doctrine SQL logger to count queries per locale; doctrine:schema:validate in CI.~5.5.10 on existing consumers until you have read the 5.6.0 diff. High cadence plus no changelog discipline is how dependency management becomes archaeology.How can I help you explore Laravel packages today?