Detect and change language on the fly with Laravel

If you’re builiding a multilingual site you may want to guess the user’s language in order to provide the correct content and still let her decide afterwards which language to use. So this a is two part problem:

  1. Find a way to detect the user’s native language and tell Laravel to use it
  2. Provide a way for the user to change it and store that choice in session

This tutorial will not cover how to make a multilingual Laravel application, for that I’ll suggest you take a look at Laravel Translatable and this tutorial on Laravel News.

Detect the user’s native language

There are multiple ways to detect language from a HTTP Request but as I only needed to check for english or french speakers, I choosed to check if those were present in the HTTP_ACCEPT_LANGUAGE HTTP directive and fallback to english if nothing matches.

The best way to do that is to use a Middleware. Type the following command from the console to create a new one: php artisan make:middleware SetLocale.

<?php

namespace App\Http\Middleware;

use Closure;
use Session;
use App;
use Config;

class SetLocale
{
    /**
     *
     * Handle an incoming request.
     *
     * @param  \Illuminate\Http\Request  $request
     * @param  \Closure  $next
     * @return mixed
     */
    public function handle($request, Closure $next)
    {
        if (Session::has('locale')) {
            $locale = Session::get('locale', Config::get('app.locale'));
        } else {
            $locale = substr($request->server('HTTP_ACCEPT_LANGUAGE'), 0, 2);

            if ($locale != 'fr' && $locale != 'en') {
                $locale = 'en';
            }
        }

        App::setLocale($locale);

        return $next($request);
    }
}

Then you need to register this middleware to be fired on each request in the $middleware attribute inside app/Http/Middleware/Kernel.php:

/**
 * The application's global HTTP middleware stack.
 *
 * @var array
 */
protected $middleware = [
    \Illuminate\Foundation\Http\Middleware\CheckForMaintenanceMode::class,
    \App\Http\Middleware\EncryptCookies::class,
    \Illuminate\Cookie\Middleware\AddQueuedCookiesToResponse::class,
    \Illuminate\Session\Middleware\StartSession::class,
    \Illuminate\View\Middleware\ShareErrorsFromSession::class,
    \App\Http\Middleware\VerifyCsrfToken::class,
    \App\Http\Middleware\SetLocale::class,
];

And that’s it!

Allow user to change locale on the fly

We will use a simple form with a select input to let the user choose its language, this is not necessarily the best UX implementation you can come up with but that’s not the purpose of this tutorial so I’ll let you decide on what’s best. I’m using Laravel Collective Forms and Bootstrap 3 for convenience here.

So let’s make that simple form:

{!! Form::open(['method' => 'POST', 'route' => 'changelocale', 'class' => 'form-inline navbar-select']) !!}

    <div class="form-group @if($errors->first('locale')) has-error @endif">
        <span aria-hidden="true"><i class="fa fa-flag"></i></span>
        {!! Form::select(
            'locale',
            ['en' => 'EN', 'fr' => 'FR'],
            \App::getLocale(),
            [
                'id'       => 'locale',
                'class'    => 'form-control',
                'required' => 'required',
                'onchange' => 'this.form.submit()',
            ]
        ) !!}
        <small class="text-danger">{{ $errors->first('locale') }}</small>
    </div>

    <div class="btn-group pull-right sr-only">
        {!! Form::submit("Change", ['class' => 'btn btn-success']) !!}
    </div>

{!! Form::close() !!}

Then we need to declare the POST route to bind our form with a method (in app/Http/routes.php):

Route::post('changelocale', ['as' => 'changelocale', 'uses' => 'TranslationController@changeLocale']);

And then in app/Http/Controllers/TranslationController.php (or whatever controller suits you best):

/**
 * Change session locale
 * @param  Request $request
 * @return Response
 */
public function changeLocale(Request $request)
{
    $this->validate($request, ['locale' => 'required|in:fr,en']);

    \Session::put('locale', $request->locale);

    return redirect()->back();
}

That’s all there is to it.

✦✦✦