Danny Connolly
Danny Connolly

Reputation: 899

Laravel localization middleware causing ERR_TOO_MANY_REDIRECTS

I am trying to set up localization in a laravel app but my middleware seems to be causing an ERR_TOO_MANY_REDIRECTS error. The site is going to differ per region slightly and I am using the laravel lang/ files to swap out phone numbers etc. This works okay. When I change my locale I get the correct numbers from the lang files. And when I just have the middleware to check if the cookie has been set to set the locale that works too.

I also have middleware to forget the prefix on localised routes so I don't have to update my methods with a locale parameter.

My issue is setting a url prefix if a cookie has been set with an allowed locale in my middleware. I also want to ignore the prefix if the locale is 'en' as it will be default.

Below is the code for everything

routes/web.php

Route::get('/', 'HomeController@index')->name('home');
Route::get('/reader/{identifier}', 'ReaderController@show')->name('reader');
Route::get('/locale/{locale}', SetLocaleController::class)->name('set.locale');

Route::group(['prefix' => '{locale?}', 'where' => ['locale' => implode('|', array_keys(config('app.allowed_locales')))], 'middleware' => 'redirect.localization'], function () {
    Route::get('/', 'HomeController@index')->name('home');
    Route::middleware('forget.prefix')->get('/reader/{identifier}', 'ReaderController@show')->name('reader');
});

Middleware/Localization.php - This is added to the web middleware group in Kernel.php. It checks for a cookie and sets the locale and should redirect to the proper locale prefix. If no cookie is set then we get the users geo and set it.

public function handle($request, Closure $next)
{
    // Get requested url
    $segments = collect($request->segments());

    if ($segments->count() && Arr::exists(config('app.allowed_locales'), $segments[0])) {
        $segments->shift();
    }

    $locale = Cookie::get('locale');

    if ($locale ?? false) {
        // Set the app locale.
        App::setLocale($locale);

        if ($locale != 'en') {
            $segments->prepend($locale);
            return redirect()->to($segments->implode('/'));
        }
    } else {
        // Check for geo here and set it.
    }

    return $next($request);
}

Middleware/RedirectLocalization.php - This middleware is only set on the route group for the prefix. It checks if the locale passed is allowed and the sets the locale and cookie. This is set in the $routeMiddleware array in Kernel.php

public function handle($request, Closure $next)
{
    $locale = $request->segment(1);

    if (Arr::exists(config('app.allowed_locales'), $locale)) {
        // Set the app locale.
        App::setLocale($locale);

        // Save app locale in a Cookie.
        Cookie::queue(Cookie::make('locale', $locale, 525600));
    }

    return $next($request);
}

Controllers/SetLocaleController.php - This is where the locale can be manually set from a menu on the site.

public function __invoke($locale, Request $request)
{
    $redirectUrl = parse_url(url()->previous());
    $segments = Str::of($redirectUrl['path'])->trim('/')->explode('/');

    if (Arr::exists(config('app.allowed_locales'), $segments[0])) {
        $segments->shift();
    }

    if (Arr::exists(config('app.allowed_locales'), $locale)) {
        // Set the app locale.
        App::setLocale($locale);

        // Save app locale in a Cookie.
        Cookie::queue(Cookie::make('locale', $locale, 525600));

        // Add locale to segments for redirect.
        if ($locale != 'en') {
            $segments->prepend($locale);
        }
    } else {
        // Set locale to current locale.
        App::setLocale(config('app.fallback_locale'));
    }

    // Redirect back
    return redirect()->to($segments->implode('/'));
}

Controllers/ReaderController.php - Nothing out of the ordinary here but I wanted to add it to explain the forget.prefix middleware. If I don't add the forget.prefix middleware then the $reader param becomes the locale.

public function show($reader, Request $request)
{
    $reader = Reader::find($reader);

    return view('readers.show', [
        'reader' => $reader
    ]);
}

Middleware/ForgetPrefix.php - This middleware removes the prefix so we can access parameter in controller methods without having to add a $locale param to the method in the controller.

public function handle($request, Closure $next)
{
    $request->route()->forgetParameter('locale');
    
    return $next($request);
}

So my question is how can I set the URL prefix if the locale has been set in a cookie without getting the too many redirects error?

Upvotes: 1

Views: 562

Answers (1)

Danny Connolly
Danny Connolly

Reputation: 899

So I found that my issue was coming from having the Middleware/Localization.php added to the web middleware group. I added this middleware to $routeMiddleware in Kernel.php. Instead I only added the Middleware/Localization.php to the default locale routes.

My updated web/routes.php

Route::group(['middleware' => 'localization'], function () {
    Route::get('/', 'HomeController@index')->name('home');
    Route::get('/reader/{identifier}', 'ReaderController@show')->name('reader');
});

Route::group(['prefix' => '{locale?}', 'where' => ['locale' => implode('|', array_keys(config('app.allowed_locales')))], 'middleware' => 'redirect.localization'], function () {
    Route::get('/', 'HomeController@index')->name('home');

    Route::group(['middleware' => 'forget.prefix'], function () {
        Route::get('/reader/{identifier}', 'ReaderController@show')->name('reader');
    });
});

// Switch Locale route
Route::get('/locale/{locale}', SetLocaleController::class)->name('set.locale');

Upvotes: 0

Related Questions