laminas/laminas-i18n
laminas-i18n provides internationalization tools for PHP applications: translations and plurals, locale-aware number, currency, date and time formatting, plus validators and view helpers for country and phone data.
Architecture fit. Low-to-moderate, and narrower than the package description implies. The description advertises "filter and validate internationalized values," but v3.0.0 explicitly removed all filters, all validators, all view helpers, the MVC module, and TranslatorInterface/TranslatorAwareInterface (PRs #167, #170, #176, #175). What remains for a Laravel consumer is essentially three things: (1) a standalone Translator with loader/plural-rule machinery, (2) message collectors with PSR-6/PSR-16 caching, and (3) i18n value objects such as CountryCode, DefaultLocale, and TextDomain. That is a real but thin surface, and it overlaps heavily with Illuminate\Translation plus ext-intl.
The genuine capability gap worth evaluating is CLDR-based plural rules and text domains. Laravel's MessageSelector handles {0}, {1}, [2,*] style choice ranges but does not implement full CLDR cardinal/ordinal rules per locale. laminas-i18n does, and supports gettext/INI/PHP-array/TMX/XLIFF loaders with plural forms (including the INI plural-loading bug fixed in #212). If our roadmap includes locales with 3+ plural forms (pl, ru, ar, cs, ga), that is the only defensible reason to adopt this package inside a Laravel codebase.
Integration feasibility. Mechanically feasible, architecturally awkward. The translator is described as "mostly dependency free" apart from an ext-intl fallback for default-locale resolution, so it can be constructed by hand. But the shipped factories are marked @internal (PR #191), meaning the supported composition path — laminas-servicemanager — is not something we should bind our DI to. We would write our own Laravel service provider and factory, which is straightforward but signals we are off the library's paved road. Two interop seams need adapters: v3 dispatches PSR-14 events (PR #204), while Laravel's event dispatcher is not PSR-14-compliant, and v3 caching is PSR-6/PSR-16 — Laravel's Cache\Repository satisfies PSR-16 but not PSR-6, so a PSR-6 path requires symfony/cache.
Technical risk.
@gsteel) dominating nearly every substantive PR across 2.31–3.0.0. Contributor count per release is 1–8. Laminas is a post-Zend community with declining gravity.Key questions.
lang/{locale}/{file}.php namespace (file.key) sufficient?Stack fit. Poor as a primary translation layer, acceptable as a narrow supplement. Laravel's Lang/trans/trans_choice, setLocale, middleware-driven locale negotiation, and lang/ publishing are deeply woven into Blade, validation messages, and package translations. Replacing that with a Laminas Translator buys us almost nothing and costs us the entire Laravel ergonomic surface. The correct posture is coexistence, not replacement: Laravel stays the system of record for UI strings; laminas-i18n is introduced only for the specific gap (CLDR pluralization and/or i18n value objects).
Migration path. There is no migration. That is the point. Frame this as an additive capability: a LaminasI18nServiceProvider registering a singleton translator with an explicit loader chain and cache, plus a thin application-owned port (e.g. PluralMessageFormatter) that the rest of the codebase depends on. No existing lang/ files move. If the pilot fails, deleting the provider and the port removes the dependency entirely — that reversibility is the main reason to accept the risk.
Compatibility. Pin to v3.x only; v2 and v3 are not source-compatible and the v2 documentation tree is frozen. Confirm PHP constraint against our platform (v2.31.0 dropped 8.1 and added 8.5 support, so v3 likely requires ≥8.2), and confirm ext-intl is present in every runtime image including queue workers and CI — the fallback-locale path depends on it. Laravel cache binding satisfies PSR-16 directly; if we want the PSR-6 collector path, we add symfony/cache and an adapter rather than pulling a second cache abstraction.
Sequencing.
ru, ar, pl versus Laravel's trans_choice. This single test decides go/no-go.CountryCode — evaluate league/iso3166 or symfony/intl first, since adopting a second package for value objects multiplies surface area for no pluralization benefit.Maintenance. Low code volume, non-trivial cognitive overhead. We inherit a dependency whose supported composition model (servicemanager, @internal factories) is not ours, whose docs are split across two versioned trees, and whose maintainer concentration is high. Budget roughly a half-day per major release to review BC breaks — 3.0.0 shipped ~14 issues and 36 PRs of breaking change, which is not a "bump and forget" cadence. Add a Renovate/Dependabot rule that flags major bumps for human review rather than auto-merge.
Support. No commercial backing. Upstream support is GitHub issues only. Our internal support path is the port interface: any engineer debugging a translation bug should hit our adapter first, not the vendor. Document explicitly in the ADR that escalations beyond our adapter have no SLA.
Scaling. File-based message loading is per-request unless cached; the PSR-6/PSR-16 collectors (PRs #194, #195, #198) exist precisely for this. For our catalog size this is fine, but two mandates follow: (a) warm translation caches during deploy, not on first request, to avoid a cold-start thundering herd after every release; (b) key the cache by locale + text domain + file mtime so a bad deploy cannot serve stale strings indefinitely. Memory footprint is modest; the translator is not a scaling bottleneck.
Failure modes.
ext-intl. Default-locale fallback degrades. Mitigation: platform check in the health endpoint and a CI matrix assertion.Ramp-up. One engineer, two to three days to reach productive velocity, dominated by reading two doc trees and reconciling the stale package description against v3 reality — not by the API itself. The team's existing Laravel i18n knowledge does not transfer to Laminas loader/collector composition, so plan for a short internal write-up. Net recommendation: conditional approval, scoped to pluralization only, behind an application-owned interface, with a one-week kill criterion. If the plural-rules spike shows Laravel's trans_choice is adequate for our actual locale roadmap, decline and spend the integration budget elsewhere.
How can I help you explore Laravel packages today?