Weave Code
Weaver
Helps Laravel developers discover, compare, and choose open-source packages. See popularity, security, maintainers, and scores at a glance to make better decisions.
Feedback
Share your thoughts, report bugs, or suggest improvements.
Subject
Message

Translation Bundle Laravel Package

sonofwinter/translation-bundle

A PHP translation bundle for organizing multilingual content and streamlining localization workflows. It provides a structured foundation for managing translation resources, helping keep language files and translated strings maintainable as your application grows.

View on GitHub
Deep Wiki
Context7

Getting Started

This is a Symfony + Doctrine bundle, not a Laravel package — there is no service provider, no facade, and no Eloquent integration. A Laravel developer can still read it for the translation-entity pattern, but day-to-day you'll only "leverage" it if you're working inside a Symfony/Doctrine codebase.

Minimal setup:

  1. composer require sonofwinter/translation-bundle
  2. Register the bundle in config/bundles.php (Symfony Flex normally does this for you).
  3. Make your translatable entity implement the bundle's translation interface (or use its trait), and give it a OneToMany association to a Translation entity.
  4. Create the companion Translation entity with:
    • ManyToOne back to the parent
    • a locale string column
    • the actual translated fields (name, title, description, etc.)
  5. Run doctrine:schema:update --force (or a migration) to create the translation table.

The first use case is the canonical one: a Product with name/description stored per-locale in product_translation, so $product->getName() resolves based on the current request locale.

If you're evaluating this from Laravel: the equivalent is a product_translations table with product_id, locale, and translated columns, plus a Product::translation($locale) relation and an accessor that falls back to config('app.fallback_locale'). You would reimplement, not install.

Implementation Patterns

Parent + translation pair (core pattern). Keep the untranslatable columns (sku, price, created_at) on the parent and only locale-varying columns on the translation entity. This keeps queries against non-translated fields cheap and avoids duplicating rows you don't need to translate.

Locale resolution at the edge, not in the entity. Resolve the active locale in a request listener (Symfony LocaleListener or a kernel.request subscriber) and pass it into the repository/translation accessor. Do not read $request or a global from inside the entity — it makes the model untestable and breaks CLI/queue contexts.

Repository accessor with fallback. Add something like getTranslation(?string $locale): ?ProductTranslation on the repository and a getTranslated(string $field, string $locale) helper that walks a fallback chain (fr_CA → fr → en). Centralize the chain so every entity behaves identically.

Forms via a collection. In a Symfony form, embed a CollectionType of translation sub-forms keyed by locale, with by_reference => false. Validate uniqueness of locale per parent in the translation entity itself so a bad payload can't create two en rows.

Admin locale tabs. In the edit view, render one tab per locale, submitting the whole collection at once. This is where the bundle saves real time versus hand-rolling separate endpoints per language.

Read path caching. If a page reads the same entity in the same locale dozens of times (menus, nav, footer), wrap the translation lookup in a request-scoped cache or Symfony cache pool keyed on entityId + locale. The translation table is tiny but hot.

Integration tip. Treat the translation entity as a value object owned by the parent: cascade persist/remove from the parent, and set orphanRemoval: true so deleting a locale row is just removing it from the collection.

Gotchas and Tips

It is not a Laravel package. Don't attempt to composer require it into a Laravel app expecting a service provider — you'll get a bundle class that never boots and Doctrine mappings that Eloquent ignores. Use it as a reference design, or as a real dependency only in a Symfony app that already runs Doctrine ORM.

Zero dependents, one star — treat it as unmaintained. Pin the exact version in composer.json, vendor the source or fork it if it's load-bearing, and be prepared to inline its trait/interface into your app. The cost of reimplementing this pattern is roughly one afternoon; the cost of being stuck on an abandoned bundle is a Symfony major-version upgrade blocker.

N+1 on translations. The parent's translations association is lazy by default. Any list page (product grid, search results) will fire one query per row. Eager-load with a JOIN/addSelect in the repository, or fetch all translations for the batch of IDs in one query and hydrate manually.

Unique constraint is on you. The bundle can't enforce "one translation per locale." Add a uniqueConstraints entry on (parent_id, locale) in the translation entity's Doctrine mapping; otherwise duplicate locales silently accumulate and getTranslation() returns whichever row the DB feels like.

Fallback semantics are usually the source of bugs. Decide early whether a missing translation returns null, an empty string, or the parent's default-language value, and whether that holds in forms (a blank fr field must not wipe the en value). Assert this in a test — it's the single most common complaint in translatable-entity code.

Doctrine metadata debugging. When mappings misbehave, run php bin/console doctrine:mapping:info and doctrine:schema:validate. The usual failure is a mismatched mappedBy/inversedBy between the parent's OneToMany and the translation's ManyToOne; the bundle's interface won't catch that for you.

Extension points. The interface/trait is the intended seam — implement it on your own base entity rather than modifying vendor code, and override any repository method by extending the bundle's repository class. Service and template overrides are the safe customization points; editing the bundle's source is not.

Schema migrations over schema:update. In any environment past local dev, generate explicit migrations — the bundle adds tables/columns the auto-diff tool will happily reorder or drop if mapping annotations drift.

Weaver

How can I help you explore Laravel packages today?

Conversation history is not saved when not logged in.
Prompt
Add packages to context
No packages found.
store-doc/laravel-storage-driver
plus-it/laravel-translation-manager
diephp/perhaps
diephp/telegraph
osmaviation/spreadsheet
microsoft/fluentui-system-icons
mmccook/php-json-canonicalization-scheme
middlewares/firewall
qubiqx/montapacking
nyoncode/laravel-package-console
pnscripts/filament-broadquill-lite
pnscripts/laravel-sentfold-lite
vleap/mx-sdk-laravel-internal
twidpay/utils
htetmyathlaing/scout-elasticsearch-driver
artisan-toolbox/front-awesome
ls/client-assistant
users-au/laravel-client
cgv/localized-resource
noith/settings