Open-source project
mcamara/laravel-localization avatar
mcamara/laravel-localization

mcamara/laravel-localization: locale-prefixed routes for Laravel, and the route:cache trade-off

Easy localization for Laravel

3,557 stars508 forksPHPMIT

At a glance

What is it?
The package turns one route group into per-locale URLs, detects browser language and remembers the choice in session or cookie. Its README also names a successor, because dynamically generated routes do not work with route:cache out of the box.
Who is it for?
Adopt mcamara/laravel-localization if you are on Laravel 10 to 13 with PHP 8.2 or newer and want locale-prefixed URLs without rewriting every route by hand, and if you can live without php artisan route:cache. Do not adopt it for a new project that needs cached routes: the README itself points to niels-numbers/laravel-localizer as the successor with statically registered routes.
Can I use it commercially?
Yes. MIT is a permissive licence: you can use, modify and sell software built on it, as long as you keep its copyright and licence notices.
Is it still maintained?
Yes. The repository last received commits 37 days ago.
What is it written in?
Mainly PHP, according to GitHub's language statistics.

Answers come from the project's GitHub data, last synced on September 30, 2026, and from our analysis. They are not legal advice.

Editorial analysis

The problem: one route definition, many locale URLs

Laravel ships translation files and a translator, but it does not decide what a URL looks like. If you want /en/test and /es/test to reach the same controller, you either duplicate the route group per locale or wrap it. This package wraps it. The README describes the offer as "Smart routing (Define your routes only once, no matter how many languages you use)", alongside browser detection, session and cookie redirects, translated route segments, an option to hide the default locale from the URL, and helper snippets such as a language selector.

The audience is a Laravel developer maintaining a site that already has translated strings and now needs translated addresses. It is not a translation management system and it does not store strings. It sits between the router and the translator: it picks a locale for the request and puts that locale into the URL prefix, then Laravel's own localization classes do the rest. If your app is a JSON API consumed by a mobile client that sends an Accept-Language header, the URL-prefix machinery is mostly dead weight.

How the locale reaches the route: a group prefix and five middleware

The mechanism is a route group whose prefix is computed at request time. LaravelLocalization::setLocale() returns the locale for the current request, and that return value becomes the group prefix. Everything registered inside the group is therefore reachable under every locale in supportedLocales, and everything outside it stays unlocalized. The README's example uses en and es as the defaults and shows the resulting addresses: /en, /en/test, /es, /es/test, plus bare / and /test which resolve by browser preference and fall back to the default locale.

Five middleware aliases do the per-request work, and they are separate on purpose. localize applies the route translations. localizationRedirect redirects a request that is missing its locale prefix. localeSessionRedirect and localeCookieRedirect persist the chosen locale in the session or a cookie and redirect when the stored value disagrees with the URL. localeViewPath points the view finder at a locale-specific directory. You register the ones you want; nothing forces all five.

Because the prefix is computed per request rather than at boot, the route table is not fixed. That is the design decision the README flags in its architecture note: the package "generates routes dynamically per request", and as a consequence php artisan route:cache "isn't supported out of the box". The README says the package "Supports caching & testing" in its feature list, and the table of contents has a Caching routes section, so there is a documented path, but the default behaviour and Laravel's route cache pull in opposite directions. Treat that as the central trade-off of the package rather than a footnote.

Install and first localized route

Install with Composer. The README gives the package name exactly as below; no version constraint is needed because the compatibility table maps Laravel versions to package lines separately.

bash
composer require mcamara/laravel-localization

Publish the config to get config/laravellocalization.php, which is where supportedLocales, useAcceptLanguageHeader, hideDefaultLocaleInURL, localesOrder, localesMapping, utf8suffix and urlsIgnored live.

bash
php artisan vendor:publish --provider="Mcamara\LaravelLocalization\LaravelLocalizationServiceProvider"

Register the middleware aliases. On Laravel 10 and earlier this goes in app/Http/Kernel.php under $middlewareAliases; on Laravel 11 and later the README shows the same aliases registered in bootstrap/app.php inside withMiddleware. The five aliases and their classes are fixed strings, so copy them rather than retyping.

php
$middleware->alias([
    'localize'              => \Mcamara\LaravelLocalization\Middleware\LaravelLocalizationRoutes::class,
    'localizationRedirect'  => \Mcamara\LaravelLocalization\Middleware\LaravelLocalizationRedirectFilter::class,
    'localeSessionRedirect' => \Mcamara\LaravelLocalization\Middleware\LocaleSessionRedirect::class,
    'localeCookieRedirect'  => \Mcamara\LaravelLocalization\Middleware\LocaleCookieRedirect::class,
    'localeViewPath'        => \Mcamara\LaravelLocalization\Middleware\LaravelLocalizationViewPath::class,
]);

Then wrap your localized routes in a group whose prefix is the computed locale. Routes outside the group are untouched.

php
// routes/web.php
Route::group(['prefix' => LaravelLocalization::setLocale()], function () {
    Route::get('/', function () {
        return View::make('hello');
    });

    Route::get('test', function () {
        return View::make('test');
    });
});

With the default configuration, visiting /en and /es should render the hello view, and /en/test and /es/test should render the test view. A bare /test should land on the default locale or on whatever the browser's Accept-Language header suggests, depending on useAcceptLanguageHeader.

Where it breaks: route:cache, POST requests and validation messages

The route:cache problem is the one to weigh before adopting. Laravel's route cache serializes the route table so it can be loaded without re-registering every route, and a table built per request cannot be serialized that way. The README's architecture note states the limitation plainly and points elsewhere for projects that need it. If your deploy runs php artisan route:cache, you are either skipping it or following the package's own caching documentation to make it work.

The README keeps a Common Issues section, which is a useful signal about where users actually get stuck. Three entries are listed: POST is not working, MethodNotAllowedHttpException, and validation messages appearing only in the default locale. The last one is the most instructive. The package sets the application locale for routing purposes, but validation runs through Laravel's validator, which resolves its locale from the application at the moment it runs. If your middleware order puts the locale switch after validation, or if a form request validates before the localized route group applies, the messages come back in the default language while the URL says otherwise. The fix is ordering, not configuration.

There is also a version floor to respect. The compatibility table maps Laravel 10.x through 13.x to the 2.0.x line and requires PHP 8.2 or newer. Older Laravel versions map to older package lines, so a legacy application cannot simply take the newest release.

The recommended successor, and what actually differs

The README's architecture note names niels-numbers/laravel-localizer as the "recommended modern successor" and links a migration guide at localizer.adam-nielsen.de. The stated difference is narrow and concrete: the successor registers routes statically instead of generating them per request, so php artisan route:cache works natively. The README describes it as having the same feature set.

That is a real architectural difference rather than a marketing one. Static registration means the route table exists at boot and can be cached; it also means the locale prefix has to be known when routes are registered, which is why the successor cannot switch locale per request the way this package does. If your locale resolution depends on request state (a session value, a cookie, an Accept-Language header evaluated at runtime), the dynamic approach is the one that matches that model. If your locales are a fixed list known at deploy time, static registration is the better fit and the route cache comes back.

The README also states that this package remains maintained by @jordyvanderhaegen for users who need the current architecture, with compatibility updates for Laravel and PHP versions, security and small bug fixes. The most recent release listed is v2.4.2 on 2026-08-24, with v2.4.1 in July 2026 and v2.4.0 in March 2026. The pattern is maintenance, not feature expansion.

Maintenance, licence and upgrade cost

The repository is not archived, and the last push was on 2026-08-24. The release cadence over 2026 shows three releases in roughly six months, which is consistent with the README's description of compatibility updates and small fixes. Do not read that as a promise of new features.

Upgrade cost is dominated by the Laravel compatibility table, not by the package's own API. Moving from Laravel 10 to 13 stays inside the 2.0.x line, so the constraint is PHP 8.2 or newer rather than a package major version. The repository carries an UPGRADING.md alongside CHANGELOG.md, so version-to-version notes exist, but the README does not reproduce them and I have not read them here.

Licence is MIT. That permits commercial use, modification and redistribution provided the copyright notice and permission notice are kept, and it comes with no warranty. I am not giving legal advice; if your organisation has a policy on third-party licences, run the MIT text past whoever owns that policy. The relevant point for planning is that MIT imposes no copyleft obligation on your application code.

Editorial conclusion

Adopt mcamara/laravel-localization if you are on Laravel 10 to 13 with PHP 8.2 or newer and want locale-prefixed URLs without rewriting every route by hand, and if you can live without php artisan route:cache. Do not adopt it for a new project that needs cached routes: the README itself points to niels-numbers/laravel-localizer as the successor with statically registered routes. Before you commit, verify three things in your own app: whether your middleware registration goes in app/Http/Kernel.php or in bootstrap/app.php, which of the five middleware aliases you actually need, and whether anything in your deploy pipeline runs route:cache.

Frequently asked questions

What is mcamara/laravel-localization used for?

It adds locale-prefixed URLs to a Laravel application, so one route group serves every language, with browser detection and session or cookie redirects on top. The README also lists translated routes, an option to hide the default locale from the URL, and helper snippets such as a language selector.

How do I install mcamara/laravel-localization?

Run composer require mcamara/laravel-localization, then publish the config with php artisan vendor:publish --provider="Mcamara\LaravelLocalization\LaravelLocalizationServiceProvider" to create config/laravellocalization.php. After that you register the middleware aliases and wrap your localized routes in a group prefixed with LaravelLocalization::setLocale().

Which middleware does mcamara/laravel-localization register?

Five aliases: localize, localizationRedirect, localeSessionRedirect, localeCookieRedirect and localeViewPath. The README shows them registered in app/Http/Kernel.php under $middlewareAliases, or in bootstrap/app.php inside withMiddleware for Laravel 11.

Does mcamara/laravel-localization work with php artisan route:cache?

Not out of the box. The README's architecture note says the package generates routes dynamically per request, and that php artisan route:cache is not supported as a result; the package documents a caching section separately. The README recommends niels-numbers/laravel-localizer for projects that need statically registered routes with native route:cache support.

Which Laravel and PHP versions does mcamara/laravel-localization support?

The compatibility table maps Laravel 10.x through 13.x to the 2.0.x line with PHP 8.2 or newer, and maps earlier Laravel versions to earlier package lines. Older applications therefore cannot take the newest release.

Official sources

  1. Issues
  2. License: MIT
  3. mcamara/laravel-localization on GitHub
  4. README
  5. Releases
Add this badge to your README

If you maintain this project, the badge below links readers to this analysis and shows its maintenance status from the daily GitHub snapshot. Paste the markdown into your README; add ?metric=license or ?metric=stars to the image URL for a different field.

Add this badge to your README

markdown
[![Hysen Labs](https://hysenlabs.com/badge/mcamara-laravel-localization.svg)](https://hysenlabs.com/projects/mcamara-laravel-localization)