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

niels-numbers/laravel-localizer

Locale-aware routing for Laravel and a successor to mcamara/laravel-localization. Registers every route twice as static routes for route:cache support, auto-detects Accept-Language, redirects visitors, and resolves route() per language.

View on GitHub
Deep Wiki
Context7

Technical Evaluation

Architecture fit This package solves one narrow problem — locale-aware URL generation and canonicalization for Laravel's router — and it solves it in the least invasive way available: Route::localize() registers each route twice as static routes (without_locale.about and with_locale.about), then swaps Laravel's UrlGenerator so existing route('about') calls resolve to the locale-correct variant. That is the decisive architectural difference from mcamara/laravel-localization, which resolves locale dynamically at dispatch and is historically hostile to route:cache. For any app that deploys behind a cached route table (i.e. essentially every production Laravel app), this is the fit we want: no custom dispatcher, no dynamic route mutation, no per-request route registration.

The secondary surface is middleware: SetLocale resolves locale via URL prefix → session → cookie → detector chain → fallback, and RedirectLocale enforces the canonical URL form for that locale. Both skip routes not registered through the macros (via a locale_type action attribute), so /admin, /api/health, and webhook endpoints can coexist in the same web group without spurious redirects. That mixed-route safety valve materially reduces blast radius and is the detail that makes an incremental rollout feasible.

Fit is strongest for: public, marketing-adjacent, SEO-sensitive Laravel apps (content sites, SaaS marketing + app shells, docs portals) that want prefixed routing and translated slugs. Fit is weak for: API-only services (no benefit), SPA front ends that own routing client-side (the package's docs themselves flag the Inertia switcher path as experimental and the SPA guide as removed), and apps with heavy client-side route consumption via spatie/laravel-typescript-transformer, which the changelog explicitly marks incompatible because the generator indexes routes by controller rather than name and the dual-registered variants overwrite each other.

Integration feasibility Low technical friction. One composer require, one config file, two middleware registrations, and a Route::localize() wrapper around the route groups we want localized. PHP 8.2–8.4 × Laravel 9–13 is a published, CI-tested support matrix that almost certainly covers our stack; Laravel 13 support already landed in 1.0.1, which is a decent signal on maintenance responsiveness. The package is MIT, ships no migrations, no service-provider magic beyond config and container bindings, and has zero transitive dependency weight (Ziggy adapters are suggest-only).

The migration story is unusually clean: mcamara/laravel-localization is marked abandoned in Composer with this package declared as the replacement, and a dedicated migration guide exists. That matters because it converts a "rip and replace" into a documented, upstream-endorsed path — relevant if we already have localizing code or if we inherit a project using the original.

The real integration cost is not in the package; it is in the surrounding surfaces. URL structure changes propagate to: sitemaps, hreflang alternate tags (the package gives us Route::localizedUrl() specifically for this), canonical tags, analytics/attribution (locale-prefixed paths fragment existing reporting), search console properties, marketing links and QR codes, and any external partner with hard-coded URLs. Budget integration effort accordingly: 1–2 dev-days for the technical pilot, 5–10 dev-days for a full rollout including SEO plumbing and per-locale QA, most of which is not package work.

Technical risk

  • Maintainer concentration. Effectively a single maintainer, 40 stars, and 0 dependents — a very thin adoption base. The mcamara succession and the 1.0 semver commitment offset this somewhat, but there is no SLA, no security policy visible, and no commercial backing. Mitigation: vendor is thin and MIT — middleware plus a macro plus a UrlGenerator subclass — so forking is genuinely viable, and we should treat fork-ability as the continuity plan rather than assume upstream support.
  • Semver boundary. 1.x is declared stable with breaking changes deferred to 2.x. Acceptable, but our own pin should be ^1.4 and we should watch for 2.x rather than float.
  • Breaking behaviour already shipped once. 0.10.0 changed controller argument semantics (stripping {locale} from the parameter bag) and explicitly told adopters to drop a $locale controller argument they may have added as a workaround. 1.4.0 fixed unnamed routes inheriting a bogus group name. These are the right fixes, but they confirm the surface is still settling in edge cases. Any code review of our controllers must check for positional-argument assumptions.
  • Boot-time constraints. Route::translate() bakes the without_locale.* variant at boot, so translated URIs cannot depend on per-request state. This is documented as a multitenancy caveat and is a real design ceiling if we ever want tenant-specific slugs.
  • Cache correctness is now ours to get right. Because responses vary by Accept-Language, session, and cookie, every shared cache (CDN, Varnish, full-page cache) needs a Vary strategy or a locale-in-path cache key. The redirect path makes this worse: a cached 302 keyed without Vary: Accept-Language will pin users to the wrong locale. This is the single most likely production incident, and it comes from the feature, not a bug.
  • Crawler semantics. Auto-detection plus redirects on the unprefixed URL means the unprefixed page is a moving target for bots. We must ensure canonical + hreflang output is deterministic and that the default locale is reachable without a redirect hop.

Key questions

  1. Which route groups localize — public marketing only, or the authenticated app shell too? (Recommend: public first.)
  2. Do we need translated slugs (/de/ueber) or only prefixed paths (/de/about)? This decides translate() vs localize() and changes SEO complexity substantially.
  3. What is the canonical-form policy — is the default locale hidden (hide_default_locale) or always prefixed? This must be decided before the first deploy because it changes every URL.
  4. What is our shared-cache Vary policy, and who owns it?
  5. Is there an existing mcamara integration to migrate, or is this greenfield?
  6. Do we serve locale-aware routes to the front end via Ziggy (supported, adapters shipped), Wayfinder (helper pattern), or TS transformer (explicitly incompatible)? Front-end contract choice should be made up front.
  7. Do we run Octane? If so, setActiveLocales() / setActiveDefaultLocale() request-scoped property handling is correct by design, but it needs a test.

Integration Approach

Stack fit Requires Laravel 9–13, PHP 8.2–8.4 — a narrow, current band. If any service is on PHP 8.1 or Laravel 8, this is a non-starter until that service is upgraded (which we may want anyway, but it becomes a hard dependency). No database, queue, cache, or storage coupling; nothing to provision.

Migration path Two entry points, both low-friction:

  • Greenfield / new locale support: wrap the target route group in Route::localize(), register SetLocale and RedirectLocale in the web group, publish localizer.php, define supported locales. Leave admin/API routes outside the macro — mixed-route support means no collateral redirects.
  • From mcamara/laravel-localization: follow the dedicated migration guide. The package is the Composer-declared replacement, so composer require cleanly supersedes the abandoned dependency. Expect to re-point config keys (laravelLocalization.* → localizer.*) and reconcile helper names (getLocalizedURL → Route::localizedUrl). This is a rename-and-verify exercise, not a re-architecture, but it touches every view that builds locale links.

Compatibility

  • route:cache — explicitly supported; this is the package's headline claim and should be our first acceptance test.
  • Mixed plain/localized routes in one middleware group — supported.
  • Ziggy v1 and v2 — supported via shipped adapters requiring a one-line container bind.
  • spatie/laravel-typescript-transformer — incompatible; do not plan around it.
  • Inertia/SPA switcher — experimental, docs page delinked; treat as unsupported.
  • Octane — designed for it (request-scoped properties, no config mutation), but unverified by us.

Sequencing

  1. Spike (1–2 days). Install on a branch, localize one route group with two locales, verify route:cache boots, verify route('about') resolves correctly per locale, verify /admin and a POST endpoint produce no redirects.
  2. URL and SEO contract. Decide default-locale visibility, translated vs prefixed paths, canonical policy, hreflang emission via Route::localizedUrl(), sitemap generation, redirect map (301) for any existing URLs. Freeze this before code review.
  3. Cache policy. Define Vary handling and confirm CDN configuration in staging — this is a prerequisite to any traffic, not a follow-up.
  4. Pilot in production behind one locale-market (e.g. /de only), on public routes, with analytics segmented by path prefix.
  5. Language switcher. Use Route::localizedSwitcherUrl() for in-page switchers — the README is explicit that a plain /about link carries no locale signal and RedirectLocale will bounce the user back to their session locale. This is the highest-probability UX bug and deserves a test.
  6. Remaining locales, then non-HTTP locale propagation (mailables via ->locale(), HasLocalePreference, queued jobs via Localizable::withLocale()).
  7. Multitenancy, only if required — setActiveLocales() narrows the runtime locale set without breaking route:cache.

Operational Impact

Maintenance Near-zero infrastructure maintenance: no services, no schema, no background work. The ongoing burden is upgrade maintenance on a ^1.4 pin and periodic review of upstream changelogs, which have historically contained behaviour changes worth reading (controller argument semantics, unnamed-route naming). Assign one owner for the pin and for reading release notes — that is genuinely the whole cost. Budget a small amount for fork-readiness: a documented, tested fallback of "vendor the package into app/" if upstream goes quiet.

Support Community-only: GitHub issues, no SLA, no vendor to escalate to. Acceptable for a routing utility (failure is loud and immediate, not silent), less acceptable if we were relying on it for anything security-adjacent — which we are not. Internal support model: one engineer who has read docs/caveats-and-recipes.md and can triage redirect issues. The docs are above average for a 40-star package (grouped README, migration guide, multitenancy guide, caveats doc, JS helpers doc, language-switcher doc), so ramp-up cost is low but not zero.

Scaling Route table grows roughly linearly with locales: N routes × L locales for localize(), plus per-locale URI permutations for translate(). At 200 routes × 10 locales that is ~2,000 static entries — fine for route:cache, but the cached file grows and cold boot memory with it. Two controls: (a) localize only the groups that need it — do not blanket the app; (b) for multitenant or long-tail locale scenarios, Localizer::setActiveLocales() narrows the runtime set while keeping the boot-time union static, which preserves cacheability. Runtime dispatch cost per request is negligible — one middleware chain pass and a UrlGenerator override.

Failure modes (ranked by likelihood × impact)

  1. Wrong-locale cache poisoning. A shared cache serves a German page at /about (or caches the 302) because Vary is absent. Symptom: users report "the site is in the wrong language," intermittent by edge node. Highest risk; mitigate in staging with explicit Vary/cache-key tests before launch.
  2. Switcher bounce. Switcher links built to the bare path, not localizedSwitcherUrl(), silently return users to their session locale. Symptom: "the language switcher doesn't work." Documented, common, easily tested.
  3. Redirect loop or non-termination. Misconfigured hide_default_locale combined with a proxy that rewrites the prefix. Symptom: ERR_TOO_MANY_REDIRECTS on one market. Cheap to detect with a smoke test per locale in CI.
  4. Locale not applied on state-changing requests. RedirectLocale intentionally skips POST/PUT/PATCH/DELETE to avoid the 302→GET body-loss downgrade — correct design, but it means a POST to the unprefixed path resolves locale from session/cookie only. Acceptable; just ensure form actions are generated via route() so they carry the prefix.
  5. Boot-time failure on unnamed translated routes. Route::translate() now throws UnnamedTranslatedRouteException at registration. This is a good failure (fails fast with the offending URI named) but it converts a latent runtime bug into a deploy-time one — expect it the first time a developer adds an unnamed route inside a translate() group, and put it in the PR checklist.
  6. Controller argument shift. {locale} is stripped from the parameter bag, so controllers receive the first real URI parameter. Any controller written against the old mcamara assumption will break visibly on first request — good, but only after deploy. Covered by the spike if the spike localizes a parameterized route.
  7. Upstream goes quiet. No dependents means no groundswell to notice. Detect via pinned version and a calendar reminder to review upstream health quarterly; respond by vendoring.

Ramp-up Small and well-bounded. A Laravel developer unfamiliar with the package needs roughly half a day to internalize the model (dual registration, locale resolution chain, when to use route() vs localizedUrl() vs localizedSwitcherUrl()), plus the caveats doc. QA needs a per-locale matrix: first visit with Accept-Language, returning visit with session, explicit prefix authority, switcher behavior, POST behavior, and crawler behavior on the unprefixed URL. That matrix is the real ramp-up artifact — build it once and reuse it as a CI smoke suite.

Recommendation Adopt with guardrails for the public, SEO-sensitive route surface; keep admin/API out of scope initially. The architectural bet — static dual-registered routes, route:cache-safe, minimal middleware — is the right one and materially better than the abandoned incumbent it replaces. The risks are adoption-base thinness (mitigated by MIT + fork-ability + thin surface) and cache/SEO correctness (mitigated by sequencing and staging verification). Treat this as a low-cost, fork-tolerant utility dependency, not a platform commitment, and gate production traffic on the cache policy being settled first.

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.
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
hsluv/hsluv
seampact/codegen
seampact/hooks
seampact/ir
mehedimi/laravel-amp-mail
happones/laravel-vue-i18n-generator
jwerd/inertia-table
siberfx/flysystem-wasabi
hollyit/token-replace
ippanelcom/laravel-sdk