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

Laravel Accept Language Middleware Laravel Package

orkhanahmadov/laravel-accept-language-middleware

Lightweight Laravel middleware that reads the Accept-Language request header and sets the application locale automatically. It parses language preferences, matches them against your configured locales, and falls back gracefully. Easy to register and works without configuration.

View on GitHub
Deep Wiki
Context7

Technical Evaluation

What it is, in PM terms. A ~single-responsibility HTTP middleware that reads Accept-Language, negotiates it against a configured supported-locale list, and calls Laravel's App::setLocale for that request. It is not a localization framework — it does one step of the negotiation chain and leaves routing, translation storage, URL strategy, and persistence to you.

Architecture fit. Clean. Middleware is Laravel's native seam for per-request cross-cutting behavior; the package writes to app()->getLocale(), which the translator, route() URL defaults, {locale} route binding, and Carbon all read. No domain coupling, no service-provider sprawl. The one architectural note is that locale becomes request-scoped global state mutated by a middleware — which means its correctness depends entirely on ordering and on nothing else in the stack also claiming ownership of locale.

Integration feasibility. Composer install, publish config, register on a route group. This is an afternoon of engineering, not a project. The cost is not integration; it is deciding the precedence chain and testing it.

Technical risk.

  • Adoption/vendor risk. 16 stars, low adoption score, effectively a single-maintainer package. Mitigant: MIT license and tiny surface area, so it is forkable and vendorable in a day. The real risk is not breakage — it is that a regression in this path produces wrong content, not an error, and nobody notices for weeks.
  • Semantics risk. The only changelog signal we have is a 3.0.1 fix where * was being treated as a literal locale code. That is a correct RFC 7231 value ("any language") reaching the resolver routinely from crawlers and privacy browsers. A package that shipped with that hole in a 3.x line tells you the edge-case test matrix is thin — expect the same class of gap around weighted wildcards (*, en;q=0.9), region variants (pt-BR vs pt_BR), case normalization, and malformed headers.
  • Caching risk. Anything negotiated on a request header must emit Vary: Accept-Language at the CDN or reverse proxy, or you serve one visitor's language to everyone. This is the highest-severity failure mode and it is outside the package's control.
  • Coverage gap. The middleware covers HTTP only. Queued jobs, console commands, scheduled tasks, and outbound mail will not inherit request locale; locale must be captured at dispatch.
  • Overlap risk. If you already use a localization package that negotiates locale (mcamara or similar), you have two components setting the same global. Pick one owner.
  • Not a security control. Do not let anyone treat negotiated locale as authorization for content or regional access.

Leverage for a TPM. Two things this buys beyond the feature itself: (1) it is the cheapest way to ship header-based multilingual behavior and test demand before committing translation budget, and (2) logging the raw Accept-Language header for a few weeks gives you data to rank which locales to translate next — a prioritization input you would otherwise guess at.

Key questions.

  1. What is the actual PHP/Laravel version matrix for 3.x, and does it cover our runtime? The changelog gives no signal.
  2. What is the test coverage and LOC count? This determines whether we fork or depend.
  3. Is the precedence chain documented: route segment → session/user preference → Accept-Language → app fallback → config default?
  4. Does the package normalize case and region separators before matching, or is that our job?
  5. Does it write locale to session, or read-only? (Affects Octane and multi-tab behavior.)
  6. Is it Octane-safe — any static/container-held locale state that leaks across requests?
  7. Who owns i18n resolution internally, and does this package become the single owner?
  8. Do we have a near-term requirement for geo-IP or user-profile-driven locale? If so, this package alone is insufficient and we should plan a custom resolver from the start.

Integration Approach

Stack fit. Fits any Laravel application that either serves localized URLs (/fr/...) or returns translated API payloads. It is a natural first step for a localization program because it front-loads the decision that matters — what locales we claim to support — into a versioned config file rather than scattered controller logic.

Build vs. adopt, stated honestly. The functionality is small enough that a competent Laravel engineer could reproduce it in a day. Adopt it if (a) the test matrix in the repo is credible and (b) we are willing to keep the dependency current. Otherwise vendor a fork under our namespace and own the tests — same code, no bus-factor exposure. Time-box this decision to the spike; do not let it become a debate.

Migration path.

  • Greenfield: register on the localized route group, publish config, ship.
  • Brownfield: audit every existing App::setLocale call — controllers, route model binding, view composers, jobs, mailables. The middleware only covers the HTTP entry point; if a controller sets locale afterward, you have two sources of truth and the middleware becomes noise. Consolidate to one resolution chain in one place and delete the ad-hoc calls deliberately. The 3.0.1 change also means any defensive filter you added to strip * from the header before the package saw it can be removed — but remove it consciously, since it may also have been handling q-value forms.

Compatibility checks to run explicitly.

  1. config:cache — confirm the supported-locale list is read in a cache-safe way, and that editing the config invalidates properly in each environment.
  2. route:cache — confirm dynamic {locale} constraints (built from the config list) survive route caching.
  3. Octane / long-running workers — assert no locale state persists between requests.
  4. CDN / reverse proxy — Vary: Accept-Language present on every negotiated response.
  5. Multi-tenant — if tenants have different locale sets, does a single global config suffice or does the resolver need tenant context?
  6. Coexistence — confirm no second package is also setting locale.

Sequencing.

  1. Write a one-page ADR defining the canonical resolution chain and the fallback policy: does an unsupported locale 404, redirect, or silently fall back? Answer this for * and xx identically.
  2. Run the spike in a branch: install, config, register on one route group only. Half a day.
  3. Commit the regression test suite before enabling broadly: bare *; weighted *, fr;q=0.8; exact-match-beats-lower-q; pt-BR and pt_BR; empty header; malformed header; unsupported locale; session-pinned locale outranking the header; and the resolved-locale-not-equal-to-* assertion.
  4. Roll out to the localized route group first, then to web globally.
  5. Staging with production-like header traffic, verifying cache behavior and Vary.
  6. Canary at a percentage of traffic with dashboards on 4xx/5xx, cache hit ratio, and locale-mismatch reports.
  7. Keep the previous lockfile entry available for rollback. Pin ^3.0.1, and treat every subsequent patch as suspect until the locale suite passes.

Operational Impact

Maintenance. Low churn, low signal. Pin exactly, automate dependency PRs, and gate every bump on the locale regression suite — the 3.0.1 precedent shows this package will change negotiation behavior on a patch version. Budget a few hours per release review; budget one engineer as the named owner.

Support. The support load does not disappear — it moves. "The site showed up in the wrong language" will still be the ticket, and it is now triaged against a middleware instead of a controller. Two concrete investments: (1) a thin subclass or wrapper over the middleware that logs the raw header and the resolved locale; (2) one line in the i18n runbook documenting wildcard handling, the fallback policy, and where to look for config:cache staleness. Support should be able to read one log line and know whether the header or the session won.

Scaling. Compute cost is negligible — one header parse per request. The real scaling cost is cache cardinality: Vary: Accept-Language multiplies CDN cache entries per URL by the number of distinct headers seen, and the long tail of header strings is effectively unbounded. Mitigate by normalizing to a small set of supported locales before the response is cached, or by negotiating at the edge. If the resolver ever falls back to enumerating all locales, that cost multiplies again.

Failure modes, ordered by how much they should worry you.

  1. Silent wrong language. No error, no alert — just user-reported confusion or a dropped conversion. Detection requires product telemetry, not infra monitoring. This is why the logging wrapper matters.
  2. Cache poisoning. Missing Vary serves a German page to an English user from the CDN. High severity, high blast radius, trivially preventable.
  3. Stale locale list. Config cached after a config edit leaves a supported locale unreachable. Presents as "we launched French but nobody gets French."
  4. Octane state leakage. Locale persists across requests in a worker. Intermittent, hard to reproduce, corrosive to trust in the feature.
  5. Header edge cases. Weighted wildcards, region variants, malformed tags resolving to * or to the wrong locale. Low severity per event, but this is the known-thin area.
  6. Dispatch-time loss. Queued jobs and mail render in the worker's default locale. Users get the right page and the wrong email.
  7. Abandonment. Maintainer goes quiet. Fully mitigated by MIT + small surface area — fork on a known-good commit and move on. Do not let this be a reason to avoid the package; do let it be a reason to have the test suite.

Ramp-up. Roughly one engineer-day to read the code, write the tests, and confirm the resolution chain; roughly one QA-day across the locale matrix and cache configuration; TPM time concentrated in the ADR (precedence, fallback policy, wildcard behavior) and the build-vs-adopt call. Call it one sprint-week end to end including canary. Define success up front: locale-mismatch ticket volume, share of traffic served in a non-default locale, and CDN cache hit ratio after Vary is in place.

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.
gmostafa/php-graphql-client
xeviant/paystack
snowsoft/faker
meditationsteps/blogetc
animalyoung/laravel-influxdb
onix-solutions/sms-telera
lonely-lights/prosetta
oxygen/data
oxygen/core
jengo/base
xiloryx/log-collector
a21ns1g4ts/filament-stripe
la-php/laravel-admin
artisan-build/community-prompts
vleroy/smart
ayles-software/laravel-australian-public-holidays
cboxdk/laravel-id-spatie
cboxdk/laravel-id-billing
cboxdk/laravel-id-billing-bridge
simple-as-fuck/php-performance-log