niels-numbers/laravel-localizer
Locale-aware routing for Laravel with static, route:cache-ready localized routes. Auto-detects language, redirects to prefixed URLs, and resolves route() to the correct locale. Successor to mcamara/laravel-localization.
## Getting Started
### Minimal Setup
1. **Installation**:
```bash
composer require niels-numbers/laravel-localizer
Publish the config (optional):
php artisan vendor:publish --provider="NielsNumbers\Localizer\LocalizerServiceProvider" --tag="config"
Configure Locales:
Edit config/localizer.php to define supported locales (e.g., ['en', 'de', 'fr']) and set a default.
First Localized Route:
Route::localize(function () {
Route::get('/about', [AboutController::class, 'index'])->name('about');
});
This generates:
/about (auto-detects locale)/en/about, /de/about, etc. (explicit locales)Detect User Locale:
Override config/localizer.php's detectors array to use custom logic (e.g., user profile, cookie, or session).
Test the Flow:
/about → Redirects to /en/about (or your default locale)./de/about → Stays on /de/about (explicit locale wins).Route Registration:
Use Route::localize() as a wrapper for all public routes needing localization. Example:
Route::localize(function () {
Route::get('/products/{product}', [ProductController::class, 'show'])->name('products.show');
});
/products/{product} (auto-detect) and /{locale}/products/{product}.Locale Detection Chain: The package checks these signals in order:
/de/about).localizer_locale).Accept-Language header (default detector).config/localizer.default_locale.Middleware Integration:
Add to app/Http/Kernel.php:
protected $middlewareGroups = [
'web' => [
// ... other middleware
\NielsNumbers\Localizer\Middleware\SetLocale::class,
\NielsNumbers\Localizer\Middleware\RedirectLocale::class,
],
];
SetLocale must run after StartSession (to read session locale) and before SubstituteBindings (to resolve translated route models correctly).Locale Switching:
route('about') in Blade (auto-localized).{{ route('about', [], ['locale' => 'de']) }}
LocalizerBladeRouteGeneratorV2 (see docs).Dynamic Locale Handling:
Localizer::setLocale('fr');
// ... logic using French locale
Localizer::forgetLocale(); // Reset to previous
return redirect()->localized('products.show', ['product' => $id]);
Blade Directives:
{{ Localizer::currentLocale() }} <!-- e.g., 'de' -->
<a href="{{ route('about') }}">About</a> <!-- Auto-localized -->
<html dir="{{ Localizer::currentLocaleDirection() }}">
API Routes:
Route::prefix('api')->group(function () {
Route::middleware('api')->group(function () {
Route::get('/products', [ProductController::class, 'index']);
});
});
Ziggy/Inertia:
LocalizerBladeRouteGeneratorV2 in AppServiceProvider:
public function register()
{
$this->app->bind(
\Tighten\Ziggy\BladeRouteGenerator::class,
\NielsNumbers\Localizer\Ziggy\LocalizerBladeRouteGeneratorV2::class
);
}
localizer_locale is shared in props.Translated Route Bindings:
{post:slug} in routes with Route::localize(). The SubstituteBindings middleware resolves the model in the correct locale.Fallback Locales:
config/localizer.php:
'fallback_locales' => ['en'],
/es/about) fall back to en.Caching:
route:cache compatible. Run:
php artisan route:cache
php artisan route:clear
Testing:
Localizer::shouldReceive('detectLocale')->andReturn('fr');
$response = $this->get('/about');
$response->assertRedirect('/fr/about');
Case-Sensitive Locale Prefixes:
/EN/about (wrong case) will 404 unless you configure case-insensitive matching in Route::localize() (not supported by default; requires custom regex).Middleware Order:
{post:slug}) resolve to the wrong locale.SetLocale runs after SubstituteBindings.Kernel.php:
protected function middlewareGroup($group, array $middleware)
{
// Remove existing middleware
$middleware = collect($middleware)->reject(fn ($item) =>
$item instanceof SetLocale || $item instanceof RedirectLocale
)->toArray();
// Append in correct order
return array_merge($middleware, [
SetLocale::class,
RedirectLocale::class,
]);
}
Route Defaults Leak:
Route::localizedUrl() appends route defaults as query params (e.g., /about?view=home).Session Locale Persistence:
localizer_locale cookie or session driver.SESSION_DRIVER is file, database, or redis in .env.Ziggy/TypeScript Issues:
route() helper fails in browser with "invalid regexp group".(?i)).LocalizerBladeRouteGeneratorV2 (v1.3.2+).Default Locale Visibility:
/about (default locale) doesn’t show locale prefix even when hide_default_locale: false.RedirectLocale skips default locale by default.'hide_default_locale' => false in config.Route Name Confusion:
$route->getName() returns with_locale.about instead of about.Localizer::baseName($route->getName()) or the baseName() macro:
$route->baseName(); // Returns 'about'
Log Locale Detection:
Add to AppServiceProvider:
Localizer::detectLocale(); // Log the result
Inspect Route Registration: Dump registered routes:
php artisan route:list | grep localizer
Look for with_locale.* and without_locale.* prefixes.
Disable Redirects Temporarily:
Set 'redirect' => false in config to debug without redirects.
Check Middleware:
Verify SetLocale and RedirectLocale are registered:
php artisan middleware:list
Test Locale Switcher: Manually set the locale cookie:
curl -H "Cookie: localizer_locale=fr" http://localhost/about
How can I help you explore Laravel packages today?