enhavo/translation-bundle
Enhavo Translation Bundle is a subtree split of the open-source Enhavo CMS, a flexible PHP CMS built on the full-stack Symfony framework for managing complex data structures through a clean, usable interface.
First, a hard truth for Laravel developers: this is not a Laravel package, and there is no Laravel integration to be had. It is a Symfony bundle and — more accurately — a component of the Enhavo CMS. There is no composer require enhavo/translation-bundle path that will give you a working translation system inside a Laravel app. If you are staying on Laravel, stop here and reach for spatie/laravel-translatable or astrotomic/laravel-translatable instead. The only scenario where you "leverage this package day-to-day" is if your team is running (or evaluating) the Enhavo CMS on Symfony.
Where to look first (in order):
https://github.com/enhavo/enhavo. The README on this split is a stub pointing there. All issues, docs, changelogs, and PRs live upstream.composer.json of the parent — confirms Symfony/PHP/Doctrine version constraints. This split has no independent install story, so its compatibility is entirely the parent's compatibility.Resources/config/services.yaml and Doctrine mappings — the public surface (service IDs, entity traits, form types) is only discoverable by reading the service definitions.Do not install this split directly. Install the full distribution (composer require enhavo/enhavo or the Enhavo application skeleton) and let the bundle come along as part of the suite.
Your first use case should be narrow: model one translatable content type (e.g. an "Article" with a title and body), register its translation counterpart, and edit it in the Enhavo admin UI across two locales. That single vertical slice tells you whether the workflow matches how your content team actually works.
Translatable entity pattern. Enhavo follows the standard Symfony translation-entity convention: a base entity (locale-agnostic fields plus a collection of translations) and a translation entity (locale plus the translatable fields). You extend or compose whatever the bundle provides rather than inventing your ownschema. Read the existing bundled entities in the parent repo and mirror their structure; deviating will fight the admin serializer.
Locale configuration is Symfony-level. Supported locales are driven by framework.translator.enabled_locales (or equivalent Enhavo config). Adding a locale means touching two things — the framework config and any Enhavo-specific admin config that enumerates locales for the editor UI. Miss the second and the tab simply won't appear.
Admin editing "side by side" means form-level. The multilingual editing experience lives in the admin form types. You integrate by declaring translation forms as collection types on the parent form. Practical tip: keep translation form types small and reuse them — copy-pasting form definitions per translatable entity is the fastest route to a maintenance mess.
Repository / query pattern. Reads happen per-locale. Standard practice is two methods on the repository: one returning translations for a given locale, one returning a fallback chain (e.g. de → en). Implement fallback at the repository/service layer, not in Twig — you'll want it consistent across API, admin preview, and frontend rendering.
Workflow pattern for a content team. The admin UI assumes a "publish per-locale" mental model. Design your editorial workflow around that early: who owns each locale, whether a translation can be published before its source, and whether partial translations are allowed. The bundle won't decide this for you, and retrofitting workflow onto a CMS is expensive.
Integration tips:
This is a read-only subtree split — the single most important thing to internalise. Do not add it alongside enhavo/enhavo. It exists to give the bundle a browsable, tag-mirrored home, not to be a standalone dependency. Adding both invites class collisions and autoload ambiguity. Enforce it with a Composer conflict entry if your team is large enough to make this mistake.
Version drift. Subtree splits can silently desynchronise from the parent's tagged releases. Pin via the parent bundle's version, and upgrade deliberately. A "harmless" minor bump upstream is where service ID renames and form-type restructuring show up.
Schema ownership is unclear from the split. Before committing, confirm whether translation tables are owned by this bundle's Doctrine mappings or by a sibling resource bundle. This determines your migration risk — and whether you can reason about the schema at all from this repo alone.
Debugging is via the container. When translations don't appear in the admin UI, the usual culprits in order are: (1) the form type not registered as a service/tagged correctly, (2) locale missing from config, (3) the translation collection not initialised on the parent entity, (4) serializer groups excluding the fields. Use bin/console debug:container and debug:router against the admin routes before touching the entity.
Locale fallback traps. The bundle gives you per-locale storage; it does not give you a fallback policy. Don't assume "if no de translation, show en" — implement it explicitly, and test the "neither exists" case, which is the one that produces blank pages in production.
Admin asset pipeline. Enhavo's admin UI assumes a specific asset build (Encore-classic Webpack). If your project has standardized on AssetMapper or Vite, expect friction. Verify this in the first day of your spike — it's the kind of thing that turns a two-day spike into two weeks.
Documentation vacuum. There is no standalone docs for this bundle. Treat "write the internal how-Enhavo-translation-works doc" as a required deliverable of any spike, not an afterthought. Every future engineer on the project will otherwise reverse-engineer it.
Adoption signal is essentially zero — zero stars, zero dependents, README-only maturity. That is not evidence of bad code (it inherits Enhavo's real, working implementation), but it is evidence of no external user base, no third-party examples, and no community to lean on. Budget for being close to self-supporting via forks and upstream PRs, and measure upstream response time by opening a real issue during the spike rather than assuming SLA.
Extension points to prefer: form types, entity traits, and your own repository/service wrappers. Avoid monkey-patching the bundle's internals — those internals are precisely what moves between releases, and there is no changelog on this split to warn you.
Bottom line for a Laravel team: unless you are consciously evaluating Enhavo as a Symfony CMS, this package contributes nothing your existing Laravel i18n tooling doesn't already do better and with a real support community. The correct day-to-day use is: not adopted.
How can I help you explore Laravel packages today?