vm5/entity-translations-bundle
A Symfony bundle for adding locale-aware translations to Doctrine entities. Store and retrieve translated fields per language, manage multilingual database content, and integrate translations into forms and templates with minimal configuration. Ideal for multilingual Symfony applications.
This package is a Symfony bundle, not a Laravel package — no service provider, no config/entity-translations.php, no Eloquent integration. Before writing any code, decide which side you're on:
composer require vm5/entity-translations-bundle, register Vm5\EntityTranslationsBundle\EntityTranslationsBundle in config/bundles.php, and enable the Doctrine metadata listener. Read tests/Fixtures in the repo first — the fixtures are the clearest documentation for how a translatable entity is wired.astrotomic/laravel-translatable for the closest conceptual match (separate *Translation table, locale-aware fallback, per-model translation class) or spatie/laravel-translatable if you want a JSON column and simpler setup.The first use case the bundle demonstrates — and the one worth stealing conceptually — is translating a small set of fields (title, description) on an entity into N locales with a fallback to the app default, driven by an injected Translator service rather than by manually fetching a Translation row.
Core pattern: a parent entity marked translatable + a sibling *Translation entity holding localized fields + a Doctrine association between them. The bundle's metadata listener attaches the association and a translator service hydrates the right translation row based on the active locale.
Laravel equivalent:
// Article.php
use Astrotomic\Translatable\Translatable;
use Astrotomic\Translatable\Contracts\Translatable as TranslatableContract;
class Article extends Model implements TranslatableContract {
use Translatable;
public $translatedAttributes = ['title', 'body'];
}
// ArticleTranslation.php
class ArticleTranslation extends Model {
public $timestamps = false;
protected $fillable = ['title', 'body'];
}
POPO support is the differentiator. The bundle's pitch is that it can translate plain old PHP objects, not just Doctrine entities. There is no Laravel equivalent out of the box; you'd write a small TranslatesObjects trait or a TranslationBag value object yourself. Worth reading the bundle's POPO code path if you build this — it's a clean separation of "source value" vs "localized value."
Schema: keep translations in a separate table with a composite unique index on (translatable_id, locale). That's the schema the bundle generates and the one astrotomic expects too. It's more queryable than a JSON column and lets you WHERE translations.title LIKE ? in joins.
Locale detection: Symfony's LocaleListener derives locale from _locale/Accept-Language. Laravel analog: a SetLocale middleware calling App::setLocale($request->getPreferredLanguage(config('app.locales'))).
Persisting translations: in the Doctrine pattern you persist translation entities explicitly (cascade or manual collection update). In astrotomic, $translatedAttributes are saved via the parent. If you port any code between the two, this asymmetry is the first thing that will bite you.
@Translatable) rather than PHP 8 attributes. doctrine/annotations is deprecated in favor of attributes — this bundle will not survive that transition cleanly. Do not depend on it for a new project.$article->title persists a translation automatically, you'll lose data. The parent/translation split requires deliberate save() on the translation side (or cascade config).*Translation entity pattern (index-friendly, join-queryable) vs. the JSON-column pattern (spatie, simpler but weak for filtering/sorting on translated values). If you need to sort by translated titles or search them, pick the separate-table approach regardless of framework.$translator->getLocale() (Symfony) or App::getLocale() (Laravel) at the point of failure before touching translation data — a locale that's silently en in a de request explains the majority of "translation not applied" tickets.How can I help you explore Laravel packages today?