Localizzare un Progetto in Laravel 5.3? Semplicissimo, con Localization!

Con il package Laravel Localization, gestire la localizzazione della propria applicazione è davvero semplice. Filippo, in questo articolo, ci spiega come!
francesco
Filippo Galante
05/10/2016 in Package, Tutorial

Localizzare un sito web, un e-commerce o, in generale, un'applicazione web può essere fondamentale, soprattutto quando si parla di SEO e ottimizzazione dei contenuti per i motori di ricerca. In questo articolo vedremo come grazie al plugin mcamara/laravel-localization possiamo internazionalizzare un'applicazione in Laravel partendo dalle rotte fino ai contenuti delle varie view, permettendo all'utente finale di utilizzarla nativamente nella propria lingua.

Partiamo quindi creando un primo meccanismo che ci permetta di cambiare facilmente e in real time il locale predefinito dell'applicazione. Questa operazione fa guadagnare al nostro progetto la possibiltà di:

  • Reperire informazioni sul locale dei vari utenti finali.
  • Tradurre istantaneamente le stringhe localizzate dell'applicazione.
  • Tradurre gli attributi degli URL delle route (ad ex. /archivio/immagini/ può tradursi in /archive/images/)

Nel dettaglio vedremo quindi:

  • Come installare e configurare il plugin
  • Creare delle route localizzate che permettano di variare la lingua dell'applicazione in real time, senza dover scrivere classi e middleware appositi.
  • Creare delle view localizzate e impostare le traduzioni dei vari contenuti e la possibilità da parte dell'utente di cambiare il locale in qualsiasi momento.

Certi prerequisiti, che il lettore potrà facilmente recuperare dalla documentazione ufficiale di Laravel, per semplicità saranno tralasciati. Il risultato finale di questo articolo sarà un'applicazione scritta utilizzando Laravel 5.3, e può essere utilizzata come base per implementare un sistema di registrazione, login e recupero password localizzato. Ho inoltre creato una repositorory su Github, completa di tutte le parti che non verranno trattate.

Il plugin ad ogni modo ha le seguenti compatibilità (per chi preferisse utilizzare versioni antecedenti alla 5.3

Versione di Laravel / Versione del Plugin

  • 4.0.x / 0.13.x
  • 4.1.x / 0.13.x
  • 4.2.x / 0.15.x
  • 5.0.x - 5.1.x / 1.0.x
  • 5.2.x / 1.1.x

Nota: la versione 1.1.x è compatibile con Laravel 5.3!

Installazione del plugin

Iniziamo aprendo il file composer.json e modifichiamo la voce "require" aggiungendo l'identificativo per il plugin:

"require": {
    "php": ">=5.6.4",
    "laravel/framework": "5.3.*",
    "mcamara/laravel-localization": "1.1.*" // <-- Aggiungi questa riga
},

Aggiorniamo quindi il file di configurazione config/app.php, al quale va aggiunto il service provider e l'alias di Laravel Localization

'providers' = [
  ...
  Mcamara\LaravelLocalization\LaravelLocalizationServiceProvider::class,
  ...
];

[...]

'aliases' => [
  'Localization' => Mcamara\LaravelLocalization\Facades\LaravelLocalization::class
];    

Infine, lanciamo il comando composer update per poter aggiungere al nostro progetto i sorgenti del plugin e ricaricare il class loader:

$ composer update
Loading composer repositories with package information
Updating dependencies (including require-dev)
  - Installing mcamara/laravel-localization (1.1.4)
    Downloading: 100%
Generating autoload files
> Illuminate\Foundation\ComposerScripts::postUpdate
> php artisan optimize
Generating optimized class loader

Il nostro plugin è pronto all'utilizzo, ma non è configurato, o meglio, al momento prenderà solo i parametri di default, quindi per poter abilitare ulteriori lingue lanciamo il comando

php artisan vendor:publish

e all'interno della cartella config verrà creato il file laravellocalization.php che contiene diversi parametri. Il primo è l'array supportedLocales, utilizzato per abilitare o disabilitare la gestione di una o più lingue. Per fare ciò basterà togliere dai commenti le lingue che ci interessano. Abilitiamo quindi l'italiano:

'it' => [
    'name' => 'Italian', 
		'script' => 'Latn', 
    'native' => 'italiano', 
		'regional' => 'it_IT'
],

Il secondo parametro è la stringa useAcceptLanguageHeader che abilita una delle prime magie. Se viene impostato a true (default), quando aprirete la vostra applicazione nel browser all'indirizzo base http://localhost:8000 (dopo faremo direttamente il test) il plugin cercherà di "indovinare" il locale attraverso uno degli header della richiesta (nello specifico l'Accept-Language header). Se viene trovato o se il parametro è impostato a false, il sistema recupererà il valore locale definito nel file config/app.php. L'URL risultante delle route localizzate dell'applicazione avrà il seguente schema:

{protocollo}://{dominio}/{locale}/[...]

Facciamo un esempio per capirci meglio:

Quando da un browser navigheremo verso http://laravel-italia.it, il plugin intercetterà la richiesta e farà un redirect all'url base localizzato, nel nostro caso http://laravel-italia.it/it. Se invece stiamo navigando da un computer inglese ci reinderizzerà verso http://laravel-italia.it/en.

N.B.: Il plugin salverà in sessione il locale che sarà quindi recuperabile tramite l'helper session('locale');

L'ultimo parametro è la stringa hideDefaultLocaleInURL: questo parametro va a modificare gli URL localizzati delle nostre route se è impostato a true, nascondendo il parametro "locale" dall'URL stesso. Se impostato a true verrà inibito il parametro useAcceptLanguageHeader. Riprendendo l'esempio precedente, le funzioni del plugin rimarranno inalterate ma non verrà eseguito alcun tipo di modifica agli URL delle nostre route localizzate ma verrà mantenuto lo schema:

{protocollo}://{dominio}/[...]

Impostazione delle route e dei middleware

Facendo un parallelo in ambito culinario potremmo dire che a questo punto abbiamo imbastito la nostra tavola con gli ingredienti, ed è giunto il momento di combinarli insieme per portare a termine la nostra ricetta. Iniziamo creando la gestione dell'autenticazione e facciamo partire l'applicazione lanciando dal terminale il seguenti comandi:

$ php artisan make:auth
$ php artisan serve 

A questo punto, aprendo il browser all'indirizzo http://localhost:8000 vedremo la landing page default di Laravel con lo stretto indispensabile per la navigazione, inoltre, il file routes/web.php si presenterà in questo modo:

Route::get('/', function () {
    return view('welcome');
});

Route::auth();

Route::get('/home', 'HomeController@index');

Per abilitare la localizzazione a queste rotte, dovremo inserirle nel gruppo che avrà come prefisso il locale predefinito o scelto:

Route::group(['prefix' => Localization::setLocale()], function() {
    // Inseriamo qui le rotte
});

Nel dettaglio il metodo setPrefix() va a controllare se il primo segmento dell'URL della route corrente è presente nell'array supportedLocales del file config/laravellocalization.php, in caso contrario, imposta il nuovo locale come default dell'applicazione e ne restituisce il valore. Se invece il parametro hideDefaultLocaleInURL è impostato a true il locale andrà impostato manualmente poiché non fa parte della richiesta. In ogni caso per questo articolo vengono presi in considerazione solo i valori di default.

Riassumendo, richiamando il metodo setPrefix(), qualunque route inizi con uno dei locale supportati dal plugin andrà a modificare in real time la nostra applicazione, consentendoci di gestire interfacce multi lingua senza aggiungere una riga di codice!

Per rendere effettivo al 100% il meccanismo di localizzazione dobbiamo aggiungere i middleware necessari per poter reindirizzare gli utenti dalle route non localizzate verso le route localizzate e mantenendo i parametri flash della sessione. Se avete bisogno di ulteriori informazioni, vi consiglio di visionare i sorgenti della repository git del plugin. Iniziamo modificando il file Kernel.php, definendo i nuovi middleware:

    /**
     * The application's route middleware.
     *
     * @var array
     */
    protected $routeMiddleware = [
        /**** OTHER MIDDLEWARE ****/
        'localize' => \Mcamara\LaravelLocalization\Middleware\LaravelLocalizationRoutes::class,
        'localizationRedirect' => \Mcamara\LaravelLocalization\Middleware\LaravelLocalizationRedirectFilter::class,
        'localeSessionRedirect' => \Mcamara\LaravelLocalization\Middleware\LocaleSessionRedirect::class
        // REDIRECTION MIDDLEWARE
    ];

e ora aggiorniamo il file routes/web.php

Route::group(['prefix' => Localization::setLocale(), 'middleware' => [ 'localeSessionRedirect', 'localizationRedirect']], function() {
    Route::get('/', function () {
        return view('welcome');
    });

    Route::auth();

    Route::get('/home', 'HomeController@index');
});

Torniamo al browser, ricarichiamo l’indirizzo http://localhost:8000 e magicamente verremo reinderizzati verso http://localhost:8000/it!

Localizziamo le view, ma prima...

Nella repository troverete già i file di localizzazione pronti, quindi gli esempi di codice prendono per ipotesi che all’interno del progetto siano presenti i files resources/lang/en/interface.php e resources/lang/it/interface.php con relative chiavi e valori.

Esempio: app.blade.php

<!-- Right Side Of Navbar -->
<ul class="nav navbar-nav navbar-right">
    <!-- Authentication Links -->
    @if (Auth::guest())
        <li><a href="{{ url('/login') }}">@lang('interface.app.login')</a></li>
        <li><a href="{{ url('/register') }}">@lang('interface.app.register')</a></li>
    @else
        <li class="dropdown">
            <a href="#" class="dropdown-toggle" data-toggle="dropdown" role="button" aria-expanded="false">
                {{ Auth::user()->name }} <span class="caret"></span>
            </a>

            <ul class="dropdown-menu" role="menu">
                <li>
                    <a href="{{ url('/logout') }}" onclick="event.preventDefault(); document.getElementById('logout-form').submit();">
                        Logout
                    </a>

                    <form id="logout-form" action="{{ url('/logout') }}" method="POST" style="display: none;">
                        {{ csrf_field() }}
                    </form>
                </li>
            </ul>
        </li>
    @endif
</ul>

Utilizzando l'helper lang(), per richiedere il parametro app.login del file resources/assets/lang/{locale}/interface.php. Dovrete anche modificare leggermente la view welcome.blade.php per far si che estenda il layout app.blade.php, nella repository trovate il codice completo. Ricaricando la pagina, potremo vedere che il contenuto è stato tradotto e se modifichiamo l’URL in http://localhost:8000/en, vedremo ricaricarsi la nostra view welcome, con i contenuti originali in inglese. Ora abbiamo la prova che la modifica del locale di default dell’applicazione in real time, funziona a tutti gli effetti.

Ma modificare l'URL a mano non è molto pratico… Apriamo quindi la vista resources/views/layouts/app.blade.php in modo tale da includere un menù di scelta lingua. La repository di laravel-localization anche in questo caso ci prepone già un esempio:

<ul class="language_bar_chooser">
    @foreach(Localization::getSupportedLocales() as $localeCode => $properties)
        <li>
            <a rel="alternate" hreflang="{{$localeCode}}" href="{{Localization::getLocalizedURL($localeCode) }}">
                {{ $properties['native'] }}
            </a>
        </li>
    @endforeach
</ul>

Come si può facilmente intuire, viene richiamata la Facade per recuperare l'array dei locali supportati, iterandola per impostare le ancore che ci permettono di ricaricare la route corrente, ma con il locale modificato. (N.D.R. Si può facilmente associare un {locale}.jpg per avere anche la bandiera di ciascun locale). Modifichiamo leggermente la struttura dell'esempio, in modo da inserire la lista in un dropdown menu.

<!— Right Side Of Navbar -->
<ul class="nav navbar-nav navbar-right">
    <li class="dropdown">
        <a href="#" class="dropdown-toggle" data-toggle="dropdown" role="button" aria-expanded="false">
            @lang('interface.app.language', ['locale' => Localization::getSupportedLocales()[Localization::setLocale()]['native']]) <span class="caret"></span>
        </a>

        <ul class="dropdown-menu" role="menu">
            @foreach(Localization::getSupportedLocales() as $localeCode => $properties)
            <li>
                <a rel="alternate" hreflang="{{ $localeCode }}" href="{{ Localization::getLocalizedURL($localeCode) }}">
                    {{ $properties['native'] }}
                </a>
            </li>
            @endforeach
        </ul>
    </li>

    […]       

</ul>

Ricarichiamo nuovamente la pagina e, nella parte alta del menu, noteremo il nuovo dropdown di scelta lingua! molto più facile così no?

C'è qualcos'altro che dovrei sapere?

Come anticipato prima, il plugin laravel-localization ci permette di tradurre i vari parametri nell'URL di una route. Ma a cosa serve localizzare i parametri? beh, supponiamo di aver creato un e-commerce di articoli vari ed eventuali e di voler strutturare gli url con questo schema:

{protocollo}://{dominio}/{locale}/{category}/{subcategory}/{product}.html

gli attributi {categoria}, {sottocategoria} e {prodotto}, in per migliorare la user experience, in parte per facilitare il lavoro ai SEO experts, andranno localizzati a loro volta. Prendiamo per esempio la categoria “libri”, la sotto categoria “avventura” e un prodotto qualunque, gli URL risultanti saranno:

    http://ecommerce.com/it/libri/avventura/il-mio-prodotto.html
    http://ecommerce.com/en/books/adventure/my-product.html

Come per la localizzazione con re-indirizzamento, tutto ruota attorno a un middleware che andiamo ad aggiungere al nostro file Kernel.php

class Kernel extends HttpKernel {
    /**
     * The application's route middleware.
     *
     * @var array
     */
    protected $routeMiddleware = [
        /**** OTHER MIDDLEWARE ****/
        'localize' => 'Mcamara\LaravelLocalization\Middleware\LaravelLocalizationRoutes',
        // TRANSLATE ROUTES MIDDLEWARE
    ];
}

N.B.: Per poter rendere effettive le modifiche va creato il file resources/lang/{locale}/routes.php contenente le varie rotte tradotte.

L'ultimo step è la modifica del file routes/web.php seguendo l'esempio riportato precedentemente, ricordandosi di includere le rotte all'interno del gruppo localizzato o aggiungendo gli appositi middlewares:

/*
 * http://ecommerce.com/{locale}/{category}
 * Lista delle sottocategorie della categoria selezionata
 */
Route::get(LaravelLocalization::transRoute('routes.category'),function(){
    return View::make('list.category');
});
    
/*
 * http://ecommerce.com/{locale}/{category}
 * Lista dei prodotti della categoria selezionata
 */
Route::group(['prefix' => LaravelLocalization::transRoute('routes.category'), 'middleware' => [ 'localeSessionRedirect', 'localizationRedirect']], function() {
    Route::get(LaravelLocalization::transRoute('routes.category'),function(){
        return View::make('list.subcategory');
    });
});

Conclusione

In questo articolo abbiamo visto come utilizzare il plugin per creare rotte localizzate e avere una base di partenza per creare su diverse funzioni come ad esempio:

  • Caricamento contenuti localizzati da un database

Riprendendo l'esempio di prima dell'e-commerce, utilizzando il locale all'interno di query specifiche, potremmo recuperare velocemente le informazioni prodotto in più lingue.

  • Creazione di un sistema di gestione dei meta-tag per SEO

Allo stesso modo di prima, ciascuna pagina potrà avere dei file localizzati per gestire ad esempio le keywords, o il meta-tag description, titolo della pagina ecc...

E poi... Beh... Come sempre è la fantasia degli sviluppatori a trovare nuovi modi per implementare questi plugins! Non mi resta che augurare a tutti un

HAPPY CODING!!!