hostnet/entity-translation-bundle
A Symfony bundle for translating Doctrine entity fields per locale. Store and retrieve localized entity content with minimal setup, making multilingual applications easier to build and maintain.
Architecture fit. Unchanged in substance. The package solves a real, recurring problem — enum-like domain values (OrderStatus::PENDING, Role constants) leaking raw storage values into UI, emails, exports, and admin tables — via a convention-driven mapping from class constant → translation key → human label.
That is a conceptual fit for any Laravel codebase. The mechanical fit remains poor: this is a Symfony bundle whose value is delivered through a DependencyInjection extension, service definitions, and a Twig extension. Laravel has no bundle loader, uses Illuminate\Translation rather than symfony/translation, and Blade rather than Twig. None of the wiring survives the trip. What survives — if the core is not tightly coupled — is a small label-resolution service and a naming convention.
Integration feasibility. Release 1.0.11 does not change the calculus. It is a hygiene release: removes an unused doctrine/annotations dependency, drops Symfony < 6.4, drops PHP < 8.3. No new features, no API surface change, no new capability that would move this from "reference implementation" to "dependency." Required spike and effort estimates stand:
__(), add a Blade directive, and write tests.The new PHP 8.3 floor is a genuine constraint on the best case: any Laravel service still on PHP 8.2 cannot even install the package to inspect runtime behavior. Vendoring the resolver file sidesteps this, but the vendored code must then be reviewed for 8.3-only syntax before it lands in an 8.2 app.
Technical risk.
Key questions for the maintainers / spike.
symfony/translation contracts?Entity.field.value)?%name% or Laravel :name?Stack fit. Unchanged. Treat this as a reference implementation rather than a dependency. Laravel already has lang/ files, the __() helper, pluralization, locale switching, and Illuminate\Translation. The only missing piece is the convention mapping a domain value to a translation key — a naming decision, not a library.
Recommended shape:
enums.{snake_case_class}.{value} — e.g., enums.order_status.pending.EnumLabel service with label(BackedEnum|string $value, ?string $locale = null): string, falling back to Str::headline() when the key is absent.@lang usage plus a casts.php config, so domain models are not touched to render labels.Rule::enum() messages, keeping API and UI consistent.If the spike confirms a framework-agnostic core, vendor the resolver file with attribution and a header comment citing the upstream repo, the MIT license, and the specific version read (currently 1.0.11) so future readers know which snapshot informed the code. Do not add it to composer.json — a hard dependency on a Symfony bundle in a Laravel app invites autoload and service-provider confusion for no benefit, and now also imposes a PHP 8.3 floor we do not control.
Migration path.
OrderStatus, UserRole). Cover every case in tests.match/switch label logic and hardcoded string maps across Blade views, mail templates, and API resources. Highest value first: customer-facing surfaces where raw enum values are currently visible.app/Enums has a corresponding translation key in lang/en/. Still the single highest-leverage piece of the effort.Compatibility. Two friction points persist if we ever consume upstream code directly: Symfony's %placeholder% syntax versus Laravel's :placeholder, and Symfony's translations/ directory layout versus Laravel's lang/. Release 1.0.11 adds a third: a hard PHP >= 8.3 requirement. Since the recommended path is vendoring a single file rather than installing the package, the version floor is advisory — but it must be checked line by line if any consuming app is still on 8.2.
Sequencing. Unchanged. Do not schedule this as a standalone initiative; it does not justify its own sprint slot. Attach it to the next piece of work touching domain-value presentation (a status-display refactor, an admin table, an export feature). Land the guardrail CI check first — it prevents regression regardless of which implementation we choose.
Maintenance. Fork-and-own remains the operating model; ownership sits with the application team, not platform. Ongoing cost is low — the resolver is a handful of lines, and the real burden is the translation corpus, not the code. Budget roughly one hour per new enum, mostly translation review across supported locales. The 1.0.11 release does not change this number, because we are not tracking upstream.
Support. No vendor SLA, no commercial backing, GitHub issues with a 5-star project's typical latency. The 1.0.11 changelog is terse and undated in prose, which is characteristic of a small maintained-in-spare-time project — competent, but not a support relationship. Assume we write our own fixes. Acceptable precisely because the code surface is small enough to fully understand, but it must be a conscious acceptance recorded in the ADR.
Scaling. Runtime impact remains negligible: label resolution is a hash lookup against a loaded translation array. Two watch items persist: (a) if the implementation enumerates constants or reflects on each call, memoize it — this matters in list endpoints serializing hundreds of models; (b) keep the enum namespace in a dedicated translation group rather than the general messages file, allowing per-group lazy loading across locales.
Failure modes.
PENDING or enums.order_status.pending. Silent and customer-visible; the CI guardrail is the mitigation.en but absent in de degrades quality silently. Requires a locale-parity check in CI, not just a key-existence check.%name% in output.match statement during migration throws UnhandledMatchError — a hard 500 rather than a cosmetic defect. Migrate with explicit default branches during the transition.composer update that silently raised our PHP requirement and could break CI or a deploy target mid-sprint. Vendoring removes this failure mode entirely — another point in favour of the current recommendation.Ramp-up. Low and unchanged. A mid-level engineer can own this after reading the ADR and the convention doc — half a day. The larger onboarding task is transmitting the convention so new enums are not added with inline match statements out of habit. Enforce it at review plus the CI check, not through documentation alone.
Verdict. Unchanged: conceptually valuable, mechanically mismatched, too low-adoption to adopt as a dependency. Release 1.0.11 is maintenance hygiene that improves the package's modernity (PHP 8.3, Symfony 6.4 floor, dead dependency removed) without adding capability — and its shipment as a patch release is a mild negative signal about upstream's compatibility discipline, which reinforces rather than weakens the case for reading it and reimplementing natively in roughly a day. The package's real contribution remains validating that the problem is worth solving systematically — not the code we would ship.
How can I help you explore Laravel packages today?