servergrove/servergrovelocalebundle
ServerGroveLocaleBundle is a Symfony/PHP bundle for detecting, storing, and switching user locales. It helps localize routes and responses using browser preferences, session, and URL parameters, with simple configuration for multilingual applications.
Architecture fit. Despite being filed here as a "Laravel/PHP package," this is a Symfony 2 bundle (ServerGrove\LocaleBundle), registered through AppKernel and wired via a compiler-pass/DI extension and a kernel.request event listener. It has no service provider, no facade, no publishable config, and no Laravel auto-discovery. Its actual scope is narrow and well understood: negotiate a request locale from the Accept-Language header against a configured list of supported locales, store it on the request, and optionally redirect to a locale-prefixed route. That is roughly one middleware plus a config array in modern terms. For a Symfony 2.x estate it is a conventional fit; for a Laravel estate it is architecturally foreign and should be treated as a reference implementation to port, not a dependency to install.
Integration feasibility. Near-zero in Laravel, moderate in Symfony 2.x. Two hard blockers stand between this package and a Laravel app: (1) its Composer constraints target symfony/symfony ~2.0, which will not co-resolve with any modern Laravel release (Laravel pulls Symfony 5/6/7 components — Composer will either refuse or force a downgrade of http-foundation, console, translation, etc.); (2) the request-listener API it relies on (GetResponseEvent/FilterControllerEvent) was removed in Symfony 5, so it is also dead on current Symfony. The code is MIT, so legally we can lift and adapt the negotiation logic — that is the only realistic consumption path.
Technical risk. Concentrated and mostly non-technical. The package is effectively abandoned: single-vendor origin (ServerGrove, a defunct hosting company), 13 stars, no meaningful issue activity, no CI visible, and an age profile consistent with 2012–2014 Symfony 2.0/2.1. Risks: no security response process, no PHP 8 compatibility, no tests to inherit, and a public API surface (LocaleListener, LocaleResolver) that will not survive being dropped into a modern kernel. The domain logic risk is low — locale negotiation is a solved, well-specified problem (RFC 4647 lookup/matching). The dependency risk is high; the copy-the-logic risk is low.
Key questions. (1) What is the actual target stack — Laravel, or Symfony 2 legacy? That single answer flips this from "port" to "keep." (2) Is locale negotiation genuinely unmet by the framework, or are we duplicating setLocale plus a 60-line middleware? (3) Do we need URL-path locales (/fr/...) for SEO, or only content negotiation? (4) Is redirect-on-detect acceptable, or does it break CDN caching and deep links? (5) Who owns the fork when we vendor it?
Stack fit. Laravel: poor — do not install. Implement as a HandleLocale middleware plus config/locales.php, using Symfony\Component\HttpFoundation\Request::getLanguages() (already a Laravel transitive dependency, no new package) and App::setLocale(). Symfony 2.x: direct Composer install is fine if the app is genuinely on 2.x. Symfony 5+: port the listener to an EventSubscriber on KernelEvents::REQUEST. In all three cases the durable artifact is the same: a supported-locale list, a default fallback, and a deterministic resolution order.
Migration path. Vendor the ~150 lines of resolution logic into app/Support/Locale (or src/Locale in Symfony), preserve the original config schema (locales, default_locale, redirect behavior) so behavior is byte-comparable, and keep the MIT header/attribution. Write characterization tests before deleting the dependency: header fr-CA,fr;q=0.9,en;q=0.8 → fr; unknown locale → default; missing header → default; unsupported region mapped to base language.
Compatibility. Blockers are explicit and checkable: Composer constraint graph (fails against Laravel 8+ and Symfony 5+), PHP >=5.3 floor with no 8.x verification, and removed event classes. Any adapter shim is more code than the port itself — skip it.
Sequencing. (1) Confirm target stack. (2) Timebox a one-day spike: clone, read the resolver, confirm scope is what we think. (3) Port + tests in the target framework. (4) Wire into the request lifecycle behind a feature flag. (5) Cut over, remove the dependency, record the decision. This is a days-not-weeks effort, and the spike should be sized accordingly — if it exceeds two days, we have misread the scope.
Maintenance. We inherit full ownership the moment we vendor it; upstream will not ship fixes. The offsetting factor is that the code is small and stable — locale negotiation does not churn — so the realistic maintenance burden is "review once a year" rather than ongoing. Budget for tracking the fork in the repo and a note in the ADR explaining why we did not take a dependency.
Support. No vendor, no SLA, no maintainer responsiveness. Acceptable only because the blast radius is contained to request-locale selection and the fallback path (default locale) is safe.
Scaling. Runtime cost is negligible — a header parse and an array scan per request, no I/O. The real scaling concern is caching, not CPU: any response that varies by negotiated language must emit Vary: Accept-Language, or a CDN/edge cache will serve French pages to English users. If we do redirect-based locale detection, put the redirect outside the cached layer or make locale explicit in the URL.
Failure modes. Malformed or absent Accept-Language silently falling back to default (invisible to monitoring — instrument it); region subtag mishandling (pt-BR vs pt); redirect loops when the detected locale conflicts with the current path prefix; SEO duplicate-content if the same page is reachable at / and /en/; cache poisoning as above; and — the sneaky one — a Composer resolution surprise during an unrelated upgrade, if anyone ever adds the real package.
Ramp-up. Very low. A mid-level PHP developer reads the resolver in under an hour and reimplements it in a day. The learning cost is not the algorithm, it is the framework-specific wiring (Laravel middleware registration vs. Symfony bundle registration), which is standard and well documented. Plan for a single engineer, not a workstream — and use the port as the opportunity to add the tests upstream never had.
How can I help you explore Laravel packages today?