codezero/laravel-localized-routes
A Laravel package for localized routes: automatically prefix routes with the current locale, translate route segments and route names, and generate localized URLs with the same route helper API you already use.
Install and publish the config:
composer require codezero/laravel-localized-routes
php artisan vendor:publish --provider="CodeZero\LocalizedRoutes\LocalizedRoutesServiceProvider" --tag="config"
Open config/localized-routes.php first — it is the whole API surface in one file. The two keys you will touch daily are supported_locales (a list of locales, optionally with display names) and the redirect/prefix options. Set the locales you actually ship:
'supported_locales' => ['en', 'nl', 'fr'],
Register the middleware in the web group of app/Http/Kernel.php so the locale is resolved from the URL and applied with App::setLocale() before your controllers run:
protected $middlewareGroups = [
'web' => [
// ...
\CodeZero\LocalizedRoutes\Middleware\SetLocale::class,
],
];
Wrap the routes that should be localized. This registers each route once per supported locale under a {locale} prefix:
Route::localized(function () {
Route::get('/', [HomeController::class, 'index'])->name('home');
Route::get('/about', [PageController::class, 'about'])->name('about');
});
You now have /en/about, /nl/about, /fr/about. In Blade, swap route() for localized_route() — that is the single most important habit with this package:
<a href="{{ localized_route('about') }}">About</a>
That is a complete first use case: two languages, a nav bar, and locale-aware links.
Group, don't repeat. Call Route::localized() once per logical section (public site, dashboard, auth) rather than per route. Everything inside inherits the prefix, the middleware, and the locale name suffixing. Nested groups with their own middleware work normally:
Route::localized(function () {
Route::middleware('auth')->group(function () {
Route::get('/dashboard', [DashboardController::class, 'index'])->name('dashboard');
});
});
Locale switcher. Build it from the current route, preserving parameters so a user on /en/products/42 lands on /nl/products/42:
@foreach (config('localized-routes.supported_locales') as $locale)
<a href="{{ localized_route(Route::currentRouteName(), [...request()->route()->parameters(), 'locale' => $locale]) }}">
{{ strtoupper($locale) }}
</a>
@endforeach
Translate the content, not just the URL. The package switches App::setLocale(), so __('messages.about'), validation messages, Carbon formatting, and pluralization all follow automatically. Keep translation files in lang/{locale}/ and this package becomes free i18n plumbing.
Localized slugs. If you want /en/about vs /nl/over-ons, define separate named routes inside conditionals on app()->getLocale(), or use a model accessor / resolveRouteBinding() to translate entity slugs per locale. The prefix handles the locale segment; the rest of the path is yours.
Testing. Pass the locale explicitly, since there is no request context in some test setups:
$this->get(localized_route('about', 'nl'))->assertOk();
$this->get('/nl/about')->assertSee('Over ons');
Consistency with auth scaffolding. Laravel's RouteServiceProvider::HOME and the Authenticate middleware redirect to route('login') / RouteServiceProvider::HOME. Override redirectTo() in app/Http/Middleware/Authenticate.php to return localized_route('login') so unauthenticated users keep their locale.
route() will silently give you the wrong URL. Route names are registered per locale (suffixed internally), and the plain route() helper resolves a default. Use localized_route() everywhere, including in emails, notifications, and redirects. This is the #1 source of "why am I on /en when I clicked Dutch?"localized_route('invoice.show', $invoice) has no request to read the locale from. Pass the locale explicitly as the second argument or set it with App::setLocale($user->locale) at the top of the job. For mailables, capture the recipient's locale, not the sender's.route:list explodes. Every route appears once per locale. That is expected; filter with php artisan route:list --path=nl when hunting a specific one.{locale}. It is a reserved parameter name inside the localized group. A route that also has {locale} in its own path will collide.SetLocale should sit early in the web group so session, auth, and view composers see the correct locale. If you use mcamara/laravel-localization style packages alongside it, pick one — two locale middlewares will fight over App::setLocale().redirect_to_localized_url to send / to the default locale, and use omit_url_prefix_for_locale if you want your default locale to be unprefixed (/about instead of /en/about) — but then remember that only one locale is prefix-free, and your switcher must handle both shapes.use_property_fallback controls whether a missing translated model property falls back to the default locale. Turn it on if your content is partially translated; turn it off if you would rather see blanks and catch missing translations in QA.php artisan config:cache && php artisan route:cache works because the macro expands at boot. Just re-run both after editing supported_locales, otherwise new locales will 404 in production.Localizer facade (CodeZero\LocalizedRoutes\Facades\Localizer) exposes the current locale, supported locales, and whether the current route is localized. Use it in view composers and middleware instead of reaching into app()->getLocale() directly, so behaviour stays consistent if you later change prefix or redirect settings.How can I help you explore Laravel packages today?