leaseweb/gettext-translation-bundle
A Symfony bundle that brings GNU gettext translation support to Symfony’s translator, enabling .po/.mo catalogs, translation domains, locale handling, and automatic catalog compilation and caching. Use familiar gettext workflows while integrating with Symfony services and configuration.
Architecture fit — poor as stated, non-existent for Laravel. The package is a Symfony bundle (Lsw*Bundle namespace, kernel-registered, DI-container-wired), not a Laravel package. The prompt frames it as a Laravel/PHP package; the reality is that it targets the Symfony 2/3 full-stack era. There is no service provider, no config/ publish hook, no facade, and no contract against Illuminate\Contracts\Translation\Translator. Adopting it in Laravel means either (a) pulling Symfony's bundle machinery in as a dependency for translation alone — architecturally indefensible — or (b) gutting the bundle and keeping only the ext-gettext call surface, at which point we have written our own loader and the dependency adds nothing.
Where it is genuinely interesting: the core premise — bypass Symfony's PHP-level .mo parsing and delegate to the native C gettext implementation — is a real performance argument, and Symfony itself ships MoFileLoader for teams that want .mo without the native global state. That gives us a proven fallback path if the native approach is rejected.
Integration feasibility — low. A Laravel integration would require:
FileLoader implementing Illuminate\Contracts\Translation\Loader that wraps bindtextdomain/textdomain/ngettext.SetLocale middleware calling setlocale(LC_MESSAGES, ...) and putenv per request, plus restoring state on teardown..mo compilation step in CI (the bundle ships no compilation tooling; msgfmt must exist in the build image).locales package + locale-gen in every container image, or all translations silently degrade to msgids.That is roughly 3–5 engineer-days for a working spike and materially more to make pluralization, domains, and fallback chains correct. The value delivered by the package itself in that path is close to zero — we would be reimplementing it.
Technical risk — high across the board.
TranslatorInterface::transChoice removal, trans() signature change) let alone 5/6/7. Any Symfony-adjacent integration is a rewrite.setlocale return-value semantics, and deprecations around putenv thread-safety are all unaudited.setlocale and textdomain are process-global, not request-scoped. This is the central technical hazard and is examined under Operational Impact..mo freshness, both invisible to the application until strings render in the wrong language.Key questions for the package owner / vendor:
ext-gettext and the required LC_MESSAGES locales, or is this a portability landmine on managed hosting?Accept-Language, multi-tenant), or is locale fixed per deployment?MoFileLoader measurable against our actual request profile and translation volume?Stack fit: none for Laravel, marginal for legacy Symfony. Target surface is PHP 7.x (unverified on 8.x), ext-gettext, Symfony 2.8–3.x. Our stack is Laravel with Composer-managed service providers. There is no overlap to exploit.
Migration path — three options, ranked:
| Option | Description | Effort | Recommendation |
|---|---|---|---|
A. Drop the package; use .mo via a thin custom loader |
Compile .mo in CI, load with MoFileLoader-style parsing or oscarotero/gettext, register as an Illuminate\Translation\FileLoader replacement |
3–5 d | Recommended — retains the .mo performance story without setlocale global state |
B. Adopt oscarotero/gettext + a maintained Laravel bridge |
Active community package, .mo/.po parsing in PHP, no extension dependency |
2–4 d | Recommended if native speed is not proven necessary |
| C. Fork the bundle, extract the native path, adapt to Laravel | Strip Symfony coupling, keep bindtextdomain calls, wrap in our own loader + middleware |
5–10 d + ongoing ownership | Only if Option A benchmarking shows a real regression |
Compatibility checklist before any option is approved:
php -m | grep gettext on all target runtimes, including local, CI, staging, prod, and any managed/severity-2 hosting.locale -a must include every target locale (de_DE.UTF-8, fr_FR.UTF-8, …). Alpine images need musl-locales; Debian needs locales + locale-gen.Sequencing:
.mo-loader prototype on a representative page. If the delta is under ~5% of request time, the entire premise is void and we stop here..po → .mo compilation in CI with drift detection (fail build on untranslated-msgid count regression).setlocale side effects.en fallback with per-key resolution, since native gettext fallback semantics are coarse.Maintenance — the dominant cost. Upstream is effectively frozen. Any adoption converts to an internal fork: we own PHP 8.x compatibility, security review of an unaudited dependency, and Symfony-API drift. Against a package whose deliverable is a thin wrapper over a PHP extension we could call in ~50 lines, the fork burden is disproportionate. Ongoing cost estimate: 1–2 engineer-days per quarter of dependency hygiene, plus unplanned work on any PHP minor bump.
Support. No upstream responsiveness, no issue triage, no CVE process, 24 stars means a Stack Overflow search returns nothing. All tribal knowledge must be written internally. Compare to oscarotero/gettext, which has active maintenance and a real user base.
Scaling — where this becomes dangerous. setlocale() and textdomain() are process-global. Under PHP-FPM this is survivable but fragile: a worker reused for a German request and then a French request depends entirely on our middleware restoring locale correctly on every path, including exceptions, queue jobs, and terminate(). Under Octane, Swoole, or RoadRunner — long-lived workers, concurrent coroutines — the model breaks outright: two concurrent requests with different locales will corrupt each other's translations nondeterministically. That is an unreproducible-in-staging, reproducible-in-production class of bug and should be treated as a hard blocker if Octane is on the roadmap. Horizontal scaling itself is fine; it is per-process locale multiplexing that does not scale.
Failure modes, in likelihood order:
.mo entry returns the raw source key ("checkout.button.pay") to the user. No exception, no log, no alert. Highest-probability and highest-visibility failure.setlocale returns false, gettext falls back to C, entire UI renders untranslated. Deploy-time, not request-time; usually caught by canary but only if the canary checks rendered language..mo after deploy — native gettext caches per process; workers holding old .mo data serve mixed-language UIs until recycled. Requires a full worker restart on every translation deploy — a rollout constraint that must be in the runbook.ngettext plural rules depend on the installed locale data, not on application config. Divergence between CI and prod locale versions yields wrong plural counts.bindtextdomain scoping is coarse; multi-tenant or per-module translation isolation is not expressible.Ramp-up. Low for a Symfony-native engineer who has shipped gettext before (half a day). Moderate for the current Laravel team (2–3 days): the concepts are unfamiliar, the failure modes are silent, and the debugging loop involves strace-adjacent work in msgfmt output rather than reading PHP. Documentation debt is real and non-transferable.
Verdict. Do not adopt this package. It is a 2018 abandoned Symfony bundle with no Laravel surface, an unverified PHP 8 story, and a native-gettext global-state model that conflicts with modern long-lived worker runtimes. The underlying idea — .mo files and compiled translations instead of runtime parsing of XLIFF/PHP arrays — is worth pursuing; pursue it via a maintained .mo loader or a native path we own outright, gated behind a benchmark that first proves the performance claim matters for our workload.
How can I help you explore Laravel packages today?