1

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);
 }
}
rozsazoltan
17.6k7 gold badges44 silver badges138 bronze badges
asked yesterday
New contributor
20cnts is a new contributor to this site. Take care in asking for clarification, commenting, and answering. Check out our Code of Conduct.
1
  • I removed the general "best way" part from your question because without it, your problem is much more focused, and it would be a pity for the post to get closed because of that. There's no such thing as "the best" or "recommended" way - it always depends on the project, goals, and many other factors. When you ask questions like that, you'll often get a wide range of scattered answers that don't necessarily address your original problem. Commented yesterday

0

Know someone who can answer? Share a link to this question via email, Twitter, or Facebook.

Your Answer

Draft saved
Draft discarded

Sign up or log in

Sign up using Google
Sign up using Email and Password

Post as a guest

Required, but never shown

Post as a guest

Required, but never shown

By clicking "Post Your Answer", you agree to our terms of service and acknowledge you have read our privacy policy.

Start asking to get answers

Find the answer to your question by asking.

Ask question

Explore related questions

See similar questions with these tags.