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

Polyglot Bundle Laravel Package

webfactory/polyglot-bundle

Polyglot Bundle is a Symfony package for multilingual applications, streamlining internationalization with translation and locale-aware content support. It reduces boilerplate around managing languages so teams can ship consistent multi-language experiences faster.

View on GitHub
Deep Wiki
Context7

Getting Started

Minimal setup

  1. composer require webfactory/polyglot-bundle and register Webfactory\Bundle\PolyglotBundle\WebfactoryPolyglotBundle in your kernel/bundle list (this is a Symfony/Doctrine bundle — in a Laravel app you'd need the Doctrine ORM bridge plus manual service wiring; it is not Eloquent-native).
  2. Mark the entity that holds the canonical values with implements TranslatableInterface and give it a companion Translation entity implementing TranslationInterface.
  3. Annotate the translatable fields on the translation class with #[TranslatedProperty], and the field that stores the locale with #[Locale].
  4. Register the entity as translatable (service/attribute config depending on your version) so the bundle can generate the TranslationProxyFactory and inject the proxy.
  5. Set the current locale before loading entities:
$polyglot->setLocale('de_DE');
$product = $em->find(Product::class, 42);
$product->getName(); // resolves through the translation proxy

Where to look first

  • The attribute classes (TranslatedProperty, Locale) — they define the entire mapping contract.
  • TranslationProxyFactory / the generated proxy — this is where lazy translation loading happens, so it's the first place to look when a value comes back "wrong".
  • Polyglot service (locale handling) — the bundle only ever returns translations for the locale that is currently set.

First real use case

A catalogue entity with name, slug, description that must exist per-locale, rendered in whichever locale the request negotiated. Start with a single translatable property (name) on one entity, one locale switch, and verify the round-trip through the proxy before expanding.

Implementation Patterns

Attribute mapping on the translation entity

class ProductTranslation implements TranslationInterface
{
    #[Locale]
    private string $locale;

    #[TranslatedProperty]
    private string $name;

    #[TranslatedProperty]
    private ?string $description = null;
}

Class-level placement is also supported, and as of 4.5.4 class-level TranslatedProperty and Locale attributes are now also read from parent classes. That unlocks the common pattern of a shared abstract translation base:

abstract class AbstractTranslation implements TranslationInterface
{
    #[Locale]
    protected string $locale;
}

class ProductTranslation extends AbstractTranslation
{
    #[TranslatedProperty]
    private string $name;
}

Locale-scoped service usage

Set the locale once per request (middleware/event listener), not per query. The proxy resolves on first property access, so the locale at hydration time is what matters.

Persisting translations

Treat the translation collection like any other Doctrine collection: build/attach the translation for the target locale, flush, and let the proxy fall back to the canonical entity when no translation exists for the active locale. For admin UIs, iterate locales explicitly and edit each translation rather than relying on the active-locale proxy.

Integration tips

  • Keep translation entities in the same mapping driver/config as their parents so metadata discovery is consistent.
  • If you use a fallback strategy (show canonical value when translation is missing), implement it in a view helper/DTO layer — the bundle itself does not invent fallbacks.
  • Use the proxy inside forms via a data mapper rather than binding form fields directly to proxy-backed properties, otherwise "unchanged" fields can produce surprise writes.
  • For read APIs, project into DTOs after setting the locale, so the proxy resolves exactly once per request.

Gotchas and Tips

Metadata/cache after upgrading

The 4.5.4 inheritance fix is a metadata-parsing change. After upgrading, clear Doctrine metadata caches (bin/console cache:clear, plus any second-level/apcu/redis metadata cache). On 4.5.3 and earlier, class-level attributes declared on a parent were silently ignored — that meant a #[Locale] on an abstract base produced a translation entity the bundle couldn't map correctly. If you "worked around" that by re-declaring the attributes on every child class, you can now remove the duplication; leaving both in place is harmless but confusing.

Missing class-level attributes fail quietly

Because attribute discovery is reflection-driven, a missing #[Locale] doesn't throw a helpful error — you get a proxy that never resolves or a locale of null. When a translation "doesn't apply", dump the resolved metadata before debugging anything else.

Locale is global mutable state

The locale is held by the Polyglot service. Long-running workers (queue consumers, Swoole/RoadRunner, messenger handlers) must reset it per job or you will leak the previous job's locale into the next one. This is the single most common production bug with this bundle.

Property-level vs class-level attributes

4.5.4 only changed class-level attribute inheritance. Property-level #[TranslatedProperty] on inherited properties already worked via reflection, but be deliberate: put #[Locale] once on the base class and keep #[TranslatedProperty] on concrete properties, so a future refactor doesn't accidentally mark a shared field as translatable.

Proxies and var_dump/serialization

The translatable object you get back is a generated proxy subclass. Serializing it (cache, API response, queue payload) can drag in the Doctrine unit of work or drop the proxy. Re-hydrate entities on the other side instead of serializing them.

Debugging tips

  • Log the active locale in the listener that sets it — half of all "wrong language" reports are ordering bugs between the locale listener and the query.
  • Inspect the generated proxy classes in the cache dir when behaviour differs between dev and prod.
  • Verify with a query that fetches the translation entity directly by locale, bypassing the proxy, to isolate whether the problem is data or mapping.

Extension points

Custom locale resolution, translation entity naming, and proxy generation are all replaceable services — prefer decorating Polyglot (to add negotiation/fallback logic) over subclassing entities, and keep the locale decision in one place so workers, HTTP requests, and CLI commands all behave identically.

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.
diephp/sequences
users-au/socialite-provider
twidpay/twid-logger
mrnewport/laravel-plaid
mrnewport/laravel-repo
mrnewport/laravel-docsign
mrnewport/laravel-stow
mrnewport/laravel-flow
mrnewport/laravel-priceable
mrnewport/laravel-nestedterms
fredmeng/laravel-apidoc-generator
itstructure/laravel-multilingual-tools
pdustdar/elasticsearch
markupai/php-sdk
rlacerda83/lumem-microservice-shipment
uni-method/symfony-lib
tacticmedia/scim-server-bundle
neika-ai/symfony-bundle
mattleads/agent-publishing-bundle
glavweb/framework-bundle