Ajouter simplement la localisation à Laravel

Posté le March 5, 2018

Quand j'ai commencé à travailler sur ce site, j'ai tout de suite voulu pouvoir écrire des articles en anglais, mais aussi en français. Mon site devait donc être multilingue.

Il existe plusieurs packages pour gérer la localisation dans Laravel. Le plus connu est probablement mcamara/laravel-localization, que j'ai utilisé au début. Cependant, j'ai rapidement rencontré quelques limitations, le plus critique pour moi étant l'impossibilité de tester unitairement les routes localisées.

D'un autre côté, Laravel fournit déjà pas mal de fonctionnalités pour gérer la localisation. Il me restait donc à coder un système dynamique pour changer la locale en fonction de la route, tel que:

GET /en/about # Displays the about page in english
GET /fr/a-propos # Displays the about page in french

Voyons voir comment j'ai implémenté ça.

La base Laravel

La documentation Laravel dispose d'une section dédiée à la localisation. Voici les principales fonctionnalités.

Locale

En premier lieu, dans config/app.php, nous pouvons configurer la locale de l'application:

/*
|--------------------------------------------------------------------------
| Application Locale Configuration
|--------------------------------------------------------------------------
|
| The application locale determines the default locale that will be used
| by the translation service provider. You are free to set this value
| to any of the locales which will be supported by the application.
|
*/

'locale' => 'en',

Cette valeur peut-être changée pendant l'exécution:

app()->setLocale('fr');

// And we can get it
app()->getLocale(); // 'fr'
app()->isLocale('fr'); // true

Traductions

On peut également gérer nos traductions en les renseignant dans resources/lang:

// resources/lang/en/general.php
return [
    'about' => 'About'
];

// resources/lang/fr/general.php
return [
    'about' => 'À propos'
];

On peut ensuite retrouver ces chaînes en fonction de la valeur de la locale:

app()->setLocale('en');
trans('general.about') // About
trans('general.about', 'fr') // À propos

app()->setLocale('fr');
trans('general.about') // À propos

Le système de localisation dynamique

Grâce à ces fonctionnalités, nous pouvons maintenant créer notre système.

Configurer la locale dynamiquement

Nous voulons définir la locale en fonction de l'URL utilisée:

  • /en/about définit la locale à en
  • /fr/about définit la locale à fr
  • ...

Pour cela, nous pouvons utiliser un middleware:

// app/Http/Middlewares/SetLocale.php

public function handle(Request $request, Closure $next): Response
{
    $locale = $request->segment(1, 'en'); // `en` or `fr`

    app()->setLocale($locale);

    return $next($request);
}

Enregitrer les routes

Nous pouvons à présent écrire nos routes:

// routes/web.php

Route::get('/en/about', function() {
    return trans('general.about');
});

Route::get('/fr/about', function() {
    return trans('general.about');
});

Ce qui peut facilement se factoriser:

// routes/web.php

foreach(['en', 'fr'] as $locale) {
    Route::prefix($locale)->group(function() {
        Route::get('about', function() {
            return trans('general.about');
        });
    })
}

Localiser l'URL

Maintenant, il serait bien de pouvoir localiser l'URL, c'est-à-dire d'avoir /en/about, fr/a-propos, etc. C'est plus lisible pour l'utilisateur, mais aussi plus optimisé pour le SEO.Modifions légérement notre fichier route:

// routes/web.php

foreach(['en', 'fr'] as $locale) {
    Route::prefix($locale)->group(function() use ($locale) {
        Route::get(trans('routes.about', $locale), function() {
            return trans('general.about');
        });
    });
}

Ensuite, ajoutons les traductions des routes dans le répertoire resources/lang:

// resources/lang/en/routes.php
return [
    'about' => 'about'
];

// resources/lang/fr/routes.php
return [
    'about' => 'a-propos'
];

Nous avons maintenant:

  • /en/about initialise la locale à en
  • /fr/a-propos initialise la locale à fr

Parfait.

Quelques idées d'améliorations

Le code ci-dessus présente la logique de base pour un système de localisation. On peut facilement penser à quelques améliorations.

Vous voudriez surement pouvoir générer des routes en fonction de la locale actuelle, dans le but de construire un menu par exemple:

// Maybe we can have this
localeRoute('about'); // '/en/about'
app()->setLocale('fr');
localeRoute('about'); // '/fr/a-propos'

Ensuite, peut-être que vous voudriez pouvoir changer la locale de l'URL courante:

// Current URL is '/en/about'
switchRoute(); // '/fr/a-propos'

Selon vos besoins, vous pourriez aussi vouloir masquer la locale par défaut de l'URL:

  • /about should set the locale to en
  • /fr/a-propos should set the locale to fr
  • ...

Etc. Je crois que nous en avons assez pour écrire un package dédié. 😉

alexjoffroy/laravel-localization

Nous avons vu les bases du système de localisation utilisé sur ce site. Je me suis dit que ça serait une bonne idée de l'extraire dans un package à réutiliser dans d'autres projets. Et peut-être aussi dans les votres !

Voici quelques fonctionnalités sympas de ce package.

Enregistrer facilement ses routes avec la macro locales

// routes/web.php

Route::locales(function() {
    Route::get(
        trans('routes.about'), 
        'App\Http\Controllers\AboutController@index'
    )->name('about');
});

En savoir plus dans la section "Add your routes" de la documentation.

L'objet Localization

La classe \AlexJoffroy\Localization\Localization fournit un ensemble de méthodes qui peuvent être utilies dans votre app. L'objet est enregistré en tant que singleton et peut-être accédé par le container, la facade L10n ou le helper l10n().

$l10n = app('localization');
// or
$l10n = L10n::getFacadeRoot();
// or
$l10n = l10n();

Il y a plusieurs méthodes utiles telles que:

  • isCurrentLocale
  • isDefaultLocale
  • isSupportedLocale
  • getSupportedLocales

En savoir plus dans la section "Localization instance" de la documentation.

Générer les routes

Vous pouvez facilement générer les routes correspondantes aux locales grâce à la méthode Localization::locale:

l10n()->route('about', [], true, 'fr'); // `https://yourapp.com/fr/a-propos`

// Shortcut will fallback to current locale
l10n()->route('about'); // `https://yourapp.com/en/about` 

Vous pouvez également changer la locale de la route courante:

// Given the current app url is `https://yourapp.com/en/about`

l10n()->currentRoute('fr'); // `https://yourapp.com/fr/a-propos`

En savoir plus dans la section "Generate routes" de la documentation.

Le switch

Vous voudrez surement afficher un sélecteur permettant aux utilisateurs de choisir leur langage. Ceci est facilement faisable dans une vue:

{{ l10n()->renderSwitch() }}

En savoir plus dans la section "Render switch" de la documentation.

Et bien plus

Middleware, helpers, et d'autres choses permettent de rendre le package aussi polyvalent que possible . Ce package n'est probablement pas le plus exhaustif, il est orienté et a quelques limitations. Mais il fait les choses comme il faut dans mon cas, donc pourquoi pas pour vous ?

Merci de consulter la documentation sur Github pour avoir plus d'infos sur l'installation, la configuration et l'usage.

J'éspère que vous avez apprécié ! N'hésitez pas à partager vos questions ou vos commentaires sur Twitter 😉