I'm working on a multilingual Laravel 12 app. All my routes are grouped under a {locale} prefix, like this:
Route::prefix('{locale}') ->group(function () { Route::get('/activities/{activity}', [ActivityController::class, 'show']) ->name('activities.show'); }); In my Localization middleware, I set the locale and define URL defaults:
<?php namespace App\Http\Middleware; use Closure; use Illuminate\Http\Request; use Illuminate\Support\Facades\App; use Illuminate\Support\Facades\URL; use Symfony\Component\HttpFoundation\Response; /** * Middleware to set the application's locale based on URL parameter, user preference or session. */ class Localization { /** * Handle an incoming request and set the locale. * * @param Request $request The incoming HTTP request instance. * @param Closure(Request): Response $next The next middleware to call. */ public function handle(Request $request, Closure $next): Response { $locale = $request->route('locale') ?? optional(auth()->user())->language ?? session('locale', config('app.locale')); if (! array_key_exists($locale, config('app.available_locales'))) { $locale = config('app.locale'); } App::setLocale($locale); URL::defaults(['locale' => $locale]); session()->put('locale', $locale); return $next($request); } } I am getting this error with my controllers:
TypeError: App\Http\Controllers\ActivityController::show(): Argument #1 ($activity) must be of type App\Models\Activity, string given
My controller looks like this:
/** * Display the details for a given activity. */ public function show(Activity $activity): View { abort_unless($activity->isPublished(), 404); return view('activities.show', [ 'activity' => $activity, 'title' => $activity->title, ]); } If I manually add Request $request or string $locale before $activity, it works - but I'd really prefer not to have to include $locale or $request in every controller action.
Middleware priority is correct: Localization runs before SubstituteBindings.
return Application::configure(basePath: dirname(__DIR__)) ->withRouting( web: __DIR__.'/../routes/web.php', commands: __DIR__.'/../routes/console.php', health: '/up', ) ->withMiddleware(function (Middleware $middleware): void { // Add Localization middleware to web group $middleware->web(append: [ Localization::class, ]); // Prioritize Localization middleware before SubstituteBindings $middleware->prependToPriorityList( before: SubstituteBindings::class, prepend: Localization::class, ); }) Is there a clean way to keep my {locale} route prefix and make implicit model binding work without adding $locale or $request to every controller method?
Is this a known limitation or am I missing something subtle?
Here is my LanguageController for better insight:
/** * Controller handling the application language switching. */ class LanguageController extends Controller { /** * Switch the application language. * * Handles changing the application language for both authenticated * and unauthenticated users. For authenticated users, it also updates their * language preference in the database. * * @param Request $request The HTTP request containing the locale. * @return RedirectResponse Redirects back to the previous page with the new locale. */ public function switch(Request $request): RedirectResponse { $validated = $request->validate([ 'locale' => ['required','string', Rule::in(array_keys(config('app.available_locales')))], ]); $newLocale = $validated['locale']; session()->put('locale', $newLocale); $prev = url()->previous(); $uri = Uri::of($prev); $segments = collect(explode('/', ltrim($uri->path(), '/'))); $available = array_keys(config('app.available_locales')); if ($segments->isNotEmpty() && in_array($segments->first(), $available, true)) { $segments[0] = $newLocale; } else { $segments->prepend($newLocale); } $newPath = '/'.$segments->implode('/'); $target = (string) $uri->withPath($newPath); return redirect()->to($target); } }