IdentifiantMot de passe
Loading...
Mot de passe oublié ?Je m'inscris ! (gratuit)

Tutoriel pour apprendre à utiliser le framework Laravel 5.3

Plus loin


précédentsommairesuivant

III. La localisation

Lorsque l'on crée un site c'est souvent dans une optique multilangages. On parle alors d'internationalisation (i18n) et de localisation (L10n). L'internationalisation consiste à préparer une application pour la rendre potentiellement adaptée à différents langages. La localisation consiste quant à elle à ajouter un composant spécifique à une langue.

C'est un sujet assez complexe qui ne se limite pas à la traduction des textes mais qui impacte aussi la représentation des dates, la gestion des pluriels, parfois la mise en page…

Qu'a à nous proposer Laravel dans ce domaine ? Nous allons le voir avec l'application d'exemple.

i18n parce qu'il y a 18 lettres entre le « i » et le « n » de internationalisation et L10n parce qu'il y a 10 lettres entre le « L » et le « n » de Localisation.

III-A. Le principe

III-A-1. La façade Lang et la classe Translator

Laravel est équipé de la façade Lang pour générer des chaînes de caractères dans plusieurs langues. Mais vous savez qu'une façade cache une classe.

Laquelle dans notre cas ?

Toutes les classes qui sont concernées par la traduction se trouvent dans le dossier Illuminate\Translation :

Image non disponible

On y trouve très logiquement :

  • un service provider pour faire toutes les initialisations ;
  • un fichier composer.json pour les dépendances ;
  • une classe FileLoader avec son interface LoaderInterface pour gérer les fichiers de traduction ;
  • une classe ArrayLoader pour charger les messages ;
  • la classe principale Translator qui est mise en action par la façade.

III-A-2. Les fichiers de langage

On a déjà parlé des fichiers de langage. Lorsque Laravel est installé on a cette architecture :

Image non disponible

Il n'est prévu au départ que la langue anglaise (en) avec quatre fichiers. Pour ajouter une langue il suffit de créer un nouveau dossier, par exemple fr pour le Français.

Il faut évidemment avoir le même contenu comme nous allons le voir.

Vous n'allez pas être obligé de faire toute cette traduction vous-même ! Certains s'en sont déjà chargés avec ce package, que nous avons déjà utilisé dans ce cours.

L'application d'exemple embarque trois langues : anglais, français et portugais (Brésil) :

Image non disponible

Les fichiers présents sont tous constitués exactement de la même manière. Prenons le plus léger pagination.php, pour l'anglais on a :

 
Sélectionnez
1.
2.
3.
4.
return [
    'previous' => '« Previous',
    'next'     => 'Next »',
];

On voit que l'on se contente de renvoyer un tableau avec des clés et des valeurs. On ne peut pas faire plus simple ! Si on prend le même fichier en version française :

 
Sélectionnez
1.
2.
3.
4.
return [
    'previous' => '« Précédent',
    'next'     => 'Suivant »',
];

On retrouve évidemment les mêmes clés avec des valeurs adaptées au langage. Et c'est la même chose pour le portugais :

 
Sélectionnez
1.
2.
3.
4.
return [
    'previous' => '« Anterior',
    'next'     => 'Próxima »',
];

III-A-3. Le fonctionnement

On dispose de quelques méthodes pour tester et récupérer ces valeurs.

Avec :

 
Sélectionnez
Lang::get('pagination.previous');

je vais obtenir :

 
Sélectionnez
« Previous

Donc la version anglaise.

Comment faire pour obtenir la version française ?

Regardez dans le fichier config/app.php :

 
Sélectionnez
'locale' => 'en',

C'est ici qu'est fixé le langage en cours. Changez la valeur :

 
Sélectionnez
'locale' => 'fr',

Maintenant avec le même code vous allez obtenir :

 
Sélectionnez
« Précédent

Évidemment on va pouvoir changer cette valeur dans le code avec la méthode setLocale :

 
Sélectionnez
App::setLocale('fr');

Comme vous aurez de multiples endroits dans le code où vous allez récupérer des valeurs, il existe un helper :

 
Sélectionnez
trans('pagination.previous');

On peut aussi vérifier qu'une clé existe avec la méthode has :

 
Sélectionnez
Lang::has('pagination.previous');

Ce sont les méthodes fondamentales qui seront suffisantes pour le présent chapitre, vous pouvez trouver des compléments d'information dans la documentation.

III-B. Un middleware ou un événement

Quand un utilisateur choisit une langue il faut s'en rappeler, on va donc utiliser la session pour mémoriser son choix.

Lorsqu'un utilisateur débarque sans avoir encore choisi il faut lui présenter une langue, mais laquelle ?

Il est possible d'aller chercher l'information de la langue utilisée au niveau de la requête.

Maintenant la question est : où allons-nous placer le code pour gérer tout ça ?

Par exemple, un middleware pourrait s'en occuper. Voici un schéma :

Image non disponible

Lorsque la requête arrive elle est prise en charge par le middleware. Là on va regarder si l'utilisateur a une session active avec une localisation, si c'est le cas on applique cette locale et on envoie la requête à l'étape suivante. Si ce n'est pas le cas, on regarde dans le header de la requête la langue envoyée par le navigateur, on la mémorise dans la session et on l'applique. On envoie alors la requête à l'étape suivante.

Ce middleware aurait ce code :

 
Sélectionnez
1.
2.
3.
4.
5.
6.
7.
8.
9.
10.
11.
12.
13.
14.
15.
16.
17.
18.
19.
20.
21.
<?php
 
namespace App\Http\Middleware;
 
use Closure;
 
class Locale
{
    protected $languages = ['en','fr','pt-BR'];
     
    public function handle($request, Closure $next)
    {
        if(!session()->has('locale')) {
            session()->put('locale', $request->getPreferredLanguage($this->languages));
        }
 
        app()->setLocale(session('locale'));
 
        return $next($request);
    }
}

Ce n'est pas ce qui a été réalisé dans l'application d'exemple. On y utilise un événement (nous verrons les événements dans un prochain chapitre) pour détecter l'arrivée d'un utilisateur et on utilise alors un service :

Image non disponible

On a dans ce service un code proche de celui vu ci-dessus pour le middleware :

 
Sélectionnez
1.
2.
3.
4.
5.
6.
7.
8.
9.
10.
11.
12.
13.
14.
15.
16.
17.
18.
19.
20.
21.
22.
<?php
 
namespace App\Services;
 
use Request;
 
class Locale
{
    /**
     * Set the locale.
     *
     * @return void
     */
    public static function setLocale()
    {
        if (!session()->has('locale')) {
            session()->put('locale', Request::getPreferredLanguage(config('app.languages')));
        }
 
        app()->setLocale(session('locale'));
    }
}

La différence c'est que les langues sont définies dans la configuration, ce qui est plus logique :

 
Sélectionnez
'languages' => ['en', 'fr', 'pt-BR'],

III-C. Le changement de langue

Dans l'application d'exemple on peut choisir librement sa langue dans le menu si le fonctionnement automatique ne convient pas :

Image non disponible

La liste déroulante est générée avec ce code :

 
Sélectionnez
1.
2.
3.
4.
5.
6.
7.
8.
9.
10.
<li class="dropdown">
    <a data-toggle="dropdown" class="dropdown-toggle" href="#"><img width="32" height="32" alt="{{ session('locale') }}"  src="{!! asset('img/' . session('locale') . '-flag.png') !!}" />  <b class="caret"></b></a>
    <ul class="dropdown-menu">
    @foreach ( config('app.languages') as $user)
        @if($user !== config('app.locale'))
            <li><a href="{!! url('language') !!}/{{ $user }}"><img width="32" height="32" alt="{{ $user }}" src="{!! asset('img/' . $user . '-flag.png') !!}"></a></li>
        @endif
    @endforeach
    </ul>
</li>

Et la route est celle-ci :

 
Sélectionnez
Route::get('language/{lang}', 'LanguageController')
->where('lang', implode('|', config('app.languages')));

Au passage remarquez la contrainte de route pour le paramètre qui doit être l'une des langues présentes dans la configuration.

On aboutit dans le contrôleur LanguageController :

 
Sélectionnez
1.
2.
3.
4.
5.
6.
7.
8.
9.
10.
11.
12.
13.
14.
15.
16.
17.
18.
19.
20.
21.
<?php
 
namespace App\Http\Controllers;
 
class LanguageController extends Controller
{
    /**
     * Change language.
     *
     * @param  string $lang
     * @return \Illuminate\Http\Response
     */
    public function __invoke($lang)
    {
        $language = in_array($lang, config('app.languages')) ? $lang : config('app.fallback_locale');
         
        session()->set('locale', $language);
 
        return back();
    }
}

Ici on mémorise en session la nouvelle langue

Remarquez l'utilisation de la méthode __invoque pour un contrôleur qui ne possède qu'une méthode.

III-D. Les dates

III-D-1. Le presenter

Il ne vous a sans doute pas échappé que les dates ne sont pas présentées de la même manière selon les langues utilisées. En particulier entre le français et l'anglais.

En France nous utilisons le format jj/mm/aaaa alors que les Américains utilisent le format mm/jj/aaaa.

Il va donc être nécessaire de faire quelque chose pour que, par exemple, les dates de création des articles apparaissent au bon format dans chaque cas.

Comme ce formatage doit être appliqué pour toutes les dates, la façon la plus simple et la plus élégante de procéder est de créer un « accessor » sur la propriété. Ainsi chaque fois que l'on va extraire une date à partir du modèle, on va le formater au passage. On pourrait faire ceci directement dans notre modèle Post. Mais on va généraliser la chose, parce que l'on va aussi en avoir besoin dans d'autres modèles.

Dans ce genre de situation la création d'un trait est pertinente pour introduire une fonctionnalité sans passer par l'héritage.

On va utiliser une version simplifiée du Design pattern Décorateur (decorator). Dans Laravel on va appeler cela un « Model Presenter ».

On va créer un dossier pour les presenters et créer celui dont nous avons besoin :

Image non disponible

Avec ce code :

 
Sélectionnez
1.
2.
3.
4.
5.
6.
7.
8.
9.
10.
11.
12.
13.
14.
15.
16.
17.
18.
19.
20.
21.
22.
23.
24.
25.
26.
27.
28.
29.
30.
31.
32.
33.
34.
35.
36.
37.
38.
39.
40.
41.
<?php
 
namespace App\Presenters;
 
use Carbon\Carbon;
 
trait DatePresenter
{
    /**
     * Format created_at attribute
     *
     * @param \Carbon\Carbon  $date
     * @return string
     */
    public function getCreatedAtAttribute($date)
    {
        return $this->getDateTimeFormated($date);
    }
 
    /**
     * Format updated_at attribute
     *
     * @param \Carbon\Carbon  $date
     * @return string
     */
    public function getUpdatedAtAttribute($date)
    {
        return $this->getDateTimeFormated($date);
    }
 
    /**
     * Format date
     *
     * @param \Carbon\Carbon  $date
     * @return string
     */
    protected function getDateTimeFormated($date)
    {
        return Carbon::parse($date)->format(config('app.locale') != 'en' ? 'd/m/Y H:i:s' : 'm/d/Y H:i:s');
    }
}

On voit que selon la valeur de la locale on va appliquer le formatage adapté à la date extraite.

Remarquez que l'on utilise la date et aussi l'heure, selon les vues on pourra avoir besoin seulement de la date, sans l'heure, il faudra alors en tenir compte.

Il ne nous reste plus qu'à utiliser ce trait dans les modèles concernés, par exemple Post :

 
Sélectionnez
1.
2.
3.
4.
5.
6.
7.
8.
9.
10.
11.
<?php
 
namespace App\Models;
 
use Illuminate\Database\Eloquent\Model;
use App\Presenters\DatePresenter;
...
 
class Post extends Model
{
    use DatePresenter;

III-E. Les vues

Une fois que tout ça est en place l'utilisation dans les vues est simple. Prenons, par exemple la vue pour la gestion des articles :

Image non disponible

Le contenu du tableau est généré par la vue resources/views/back/blog/table.blade.php.

Les boutons doivent avoir un texte qui correspond à la langue. Par exemple, pour le bouton d'édition on a :

 
Sélectionnez
<td>{!! link_to_route('blog.edit', trans('back/blog.edit'), [$post->id], ['class' => 'btn btn-warning btn-block']) !!}</td>

On utilise l'helper trans pour traduite le titre du bouton qui sera dans la langue actuelle de l'utilisateur.

D'autre part la date doit avoir le bon format, ici elle est au format anglais, voici le code :

 
Sélectionnez
<td>{{ $post->created_at }}</td>

Vous voyez que l'on a rien de particulier à faire puisque tout se passe dans le modèle avec le trait mis en place.

Si on change la langue pour le français les textes et les dates s'adaptent automatiquement :

Image non disponible

III-F. Les noms pour la validation

Il y a un autre élément à prendre en compte pour la traduction, c'est le nom des contrôles de saisie. Ces noms ne sont pas visibles tant qu'il n'y a pas de souci de validation, ils sont alors transmis dans le texte de l'erreur.

‌On voit mal arriver en langue française « Le champ name est obligatoire… ». Alors comment faire ?

Si vous regardez dans le fichier resources/lang/fr/validation.php vous trouvez ce tableau :

 
Sélectionnez
1.
2.
3.
4.
5.
'attributes' => [
    "name" => "Nom",
    "username" => "Pseudo",
    ...
],

Ici on fait correspondre le nom d'un attribut avec sa version linguistique pour justement avoir quelque chose de cohérent au niveau du message d'erreur.

Le but de ce chapitre est de montrer comment localiser l'interface d'une page web. S'il s'agit d'adapter le contenu selon la langue c'est une autre histoire et il faut alors intervenir au niveau de l'URL. En effet un principe fondamental du web veut que pour une certaine URL on renvoie toujours le même contenu.

III-G. En résumé

  • Laravel possède les outils de base pour la localisation.
  • Il faut créer autant de fichiers de localisation que l'on a de langues à traiter.
  • La localisation peut s'effectuer au niveau d'un middleware.
  • Le formatage des dates peut s'effectuer facilement avec un « presenter ».
  • Il faut prévoir la version linguistique des attributs.

précédentsommairesuivant

Copyright © 2017 Laravel. Aucune reproduction, même partielle, ne peut être faite de ce site ni de l'ensemble de son contenu : textes, documents, images, etc. sans l'autorisation expresse de l'auteur. Sinon vous encourez selon la loi jusqu'à trois ans de prison et jusqu'à 300 000 € de dommages et intérêts.