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 Laravel Package

symfony/translation

Symfony's Translation component gives you the tools to internationalize your application: translate messages using locale-aware catalogs, handle pluralization and ICU message formats, and load translations from PHP, XLIFF, YAML, JSON, or PO files. Use it standalone or with the full Symfony framework...

View on GitHub
Deep Wiki
Context7

Technical Evaluation

Architecture fit

The package is a framework-agnostic i18n runtime: a Translator bound to a locale, a MessageCatalogue per locale/domain, pluggable loaders (XLIFF 1.2/2.0, YAML, JSON, CSV, INI, PHP array, PO, Qt), pluggable dumpers, ICU-aware formatters, and — uniquely — translation-provider adapters (Crowdin, Lokalise, Phrase) with pull/push console commands. It is MIT, ~6.6k stars, and released under the Symfony monorepo cadence with parallel 6.4 / 7.4 / 8.0 / 8.1 branches.

The central architectural fact for us: Laravel already ships a translation runtime. illuminate/translation was historically derived from this component and has long since diverged — different loader set (PHP arrays + JSON only), different placeholder semantics (:name and legacy {0}/{1} MessageSelector, not ICU {count, plural, ...}), different catalogue model, and __() / Lang::get() rather than trans(). Adding symfony/translation to a Laravel codebase therefore creates two parallel i18n stacks unless we deliberately pick one. That is the decision, not the install.

There are three genuinely different projects hiding behind "integrate symfony/translation," and they carry very different risk:

Posture What it buys Risk
A. Side-by-side — Symfony Translator as a separate container singleton for a bounded domain (transactional emails, API error catalogues, ICU plural strings) ICU plurals/select/gender, XLIFF ingestion, provider sync — none of which Laravel offers Low. No contact with Illuminate\Contracts\Translation\Translator
B. Adapter — implement Laravel's Translator contract backed by Symfony's engine, rebind in a service provider One __() API, better formatting underneath Medium. Must faithfully reproduce get(), choice(), has(), getLocale()/setLocale(), getSelector(), package::file.key namespacing, JSON-key fallback, and :placeholder replacement
C. Replacement — swap illuminate/translation wholesale Conceptual purity High and not recommended. Validation, pagination, auth, password-reset and mail notifications all resolve messages through that contract; breaking it breaks framework error output before it breaks our features

Integration feasibility

Technically straightforward: composer require symfony/translation:^7.4 alongside Laravel, no namespace or autoload collision. The real gates are:

  • PHP floor. Symfony 8.x requires PHP ≥ 8.4; Laravel's supported floor is lower. Pin ^7.4 (LTS, PHP ≥ 8.2) unless the whole fleet is on 8.4+, and treat ^8 as a deliberate later migration.
  • Optional dependency surface. ICU formatters need ext-intl; the YAML loader needs symfony/yaml; the pull/push commands need symfony/console + symfony/dependency-injection. Standalone usage means we hand-wire loaders and formatters that FrameworkBundle would normally configure — that wiring is ours to own and test.
  • Catalogue layout. Laravel expects lang/{locale}/{file}.php + lang/{locale}.json. Symfony wants domain.locale.format. We need an explicit mapping decision and probably a custom loader bridging to Laravel's paths, or a build step that compiles Laravel PHP arrays into Symfony catalogues.

Technical risk

  • Two sources of truth for locale. App::setLocale() vs Translator::setLocale() will drift unless one is derived from the other. This is the most likely silent production bug.
  • ICU vs legacy plurals. Migrating existing :count strings to ICU changes output for locales with complex plural rules (pl, ru, ar). Needs a per-locale regression fixture set, not spot checks.
  • XML surface. XLIFF loading pulls in libxml and entity-loading hardening (see the XliffUtils::shouldEnableEntityLoader fix). Third-party translation files are untrusted input.
  • Data quality of the supplied evidence. "Dependents: 0" is an artifact — this component is a transitive dependency of thousands of packages via symfony/validator, symfony/form, symfony/framework-bundle, and API Platform. Likewise the assessment-pm.md framing ("replace Lokalise/Phrase for $50K–150K/yr") overstates what a runtime component delivers: it is the delivery half of a TMS, not extraction, review workflow, MT, or QA. Do not carry those numbers into a roadmap deck unvalidated.

Key questions

  1. Which posture — A, B, or C? Nothing else can be scoped until this is answered.
  2. Is ICU plural/select support a real requirement, or a nice-to-have? If nice-to-have, posture A may not be worth the second stack.
  3. Do we need provider sync (Crowdin/Lokalise/Phrase) from CI, or does our TMS already push files? This is the single strongest differentiator versus Laravel's built-in.
  4. What is our PHP floor across all services, including microservices and legacy apps?
  5. Who owns the runtime — platform team, or each product team? Determines whether we ship a package or a convention.
  6. What is the current per-request cost of catalogue loading at our largest locale × string count?

Integration Approach

Stack fit

Best fit is posture A deployed as an internal Composer package: a LocalizationServiceProvider that registers a configured Translator singleton, a loader map pointing at a build-artifact directory of dumped PHP-array catalogues, and thin facade/helper for the bounded domains we choose (ICU-heavy strings, email templates, API error contracts). Laravel's __() remains the default for UI strings. This captures the differentiating capability — ICU formatting, XLIFF ingestion, provider sync, catalogue operations — without touching the contract the framework depends on.

If the org genuinely needs one API, do B as a decorator that wraps rather than replaces: implement Laravel's contract, delegate to Symfony's Translator, and keep Lang resolving through the existing class for anything the adapter doesn't explicitly claim. Feature-flag it per domain and shadow-compare outputs.

Migration path

Do not migrate strings. Migrate capability, domain by domain:

  1. Inventory — count strings per locale per domain; flag any string currently relying on Laravel's {0} selector or manual str_replace pluralization.
  2. Build artifacts first — add a console command that compiles lang/ into Symfony PHP-array catalogues and dumps them; this is pure upside (parse-once, OPcache-friendly) and requires no runtime swap.
  3. Pilot one domain — pick a domain with real plural complexity (notifications or billing) and run both engines in parallel, diffing output across all supported locales in CI.
  4. Provider sync — wire pull/push only after the catalogue layout is stable.
  5. Decide on the adapter — only with pilot data on divergence rate.

Compatibility

Pin an explicit floor, not a caret. The changelog shows patch releases routinely carrying fixes in neighbouring components (DependencyInjection, Console) and translation-specific correctness fixes we depend on: Crowdin push truncating catalogues without --force (#63231), Lokalise "project too big for sync export" (#62348), CSV empty-line handling (#63064), TranslatableInterface support in IdentityTranslator (#61111). Set the floor at or above the patches containing these, and commit a lockfile entry.

Avoid mixing major branches in one monorepo if any package resolves a different Symfony major transitively. Run composer why symfony/translation before and after.

Sequencing

Catalogue format decision → build-artifact dumper + CI parity test → pilot domain (side-by-side, diffed) → provider sync → adapter evaluation → deprecation plan for any bespoke JSON translation hacks. Each step is independently valuable and independently revertible; none requires the next.

Operational Impact

Maintenance

Cadence is driven by the Symfony monorepo, not this split repo: monthly-ish patch releases plus minors, with four maintained branches. Budget a recurring ~1 engineer-day/quarter for floor bumps and changelog triage, concentrated on translation-specific fixes. The split repo is a read-only mirror — issues and PRs live in symfony/symfony, so upstream contribution is a two-hop process with a higher bar than a typical package. Version policy must be centralised (one floor in a shared CI config), or services will drift across 6.4/7.4/8.x.

Support

Effectively best-in-class for PHP: large contributor base, LTS branches with long security windows (6.4 and 7.4 both carry multi-year security support), documented upgrade paths. The corollary is that support is community-shaped, not vendor SLA — no one to escalate to. If localization is business-critical for a regulated launch, we own the on-call.

Scaling

Catalogues are hydrated into memory per locale/domain. For a large matrix (say 50 locales × 100k strings), per-request file parsing is the dominant cost. Mitigations, in order of impact: (1) dump catalogues to PHP arrays at build time and warm OPcache — this alone removes all runtime parsing; (2) lazy-load domains rather than all catalogues on boot; (3) cache the resolved catalogue in Redis per (locale, domain, catalogue-version) for multi-node fleets; (4) keep locale fallback chains shallow — each fallback level is a lookup, and deep chains hide missing translations.

Failure modes

  • Silent missing-key fallback. trans() returns the message ID when a key is absent. In production this looks like English leaking into a Japanese UI, not an error. Mitigate with a CI gate that fails on missing keys per locale, and a non-production strict mode.
  • Locale drift between App::getLocale() and Translator::getLocale() — wrong-language output, no exception.
  • Missing ext-intl silently degrades ICU formatting: plural strings render with the wrong branch or raw braces. Assert the extension at boot.
  • XLIFF ingestion of untrusted files — entity-loading and libxml configuration must be explicitly set; several recent patches touch exactly this.
  • Provider push without --force can replace a remote catalogue with partial data (fixed upstream, but pin past it). Similarly, large projects hit sync-export size limits — pull/push must degrade to async export, not fail the pipeline.
  • CSV loader edge cases on empty lines and quoting for content-team-authored files.

Ramp-up

The API is easy — trans() with a locale is a fifteen-minute concept for any Laravel developer, and the mental model is close enough to __() that adoption isn't the bottleneck. The real learning curve sits with content and localization teams, not engineers: ICU message syntax, XLIFF structure, domain strategy, and the difference between a missing key and a fallback hit. Plan for a written style guide, a message linter in CI, and one training session with the translators before the first provider sync. Engineer ramp-up: ~1 week to first non-trivial integration, ~3–4 weeks to own the loader/formatter wiring and the parity test harness in posture A/B.

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.
jinas/dhivatar
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