web-dev-qa-db-fra.com

Comment créer des itinéraires traduits multilingues dans Laravel

Je voudrais créer une application avec de nombreux itinéraires traduits en fonction de la langue sélectionnée. Je l'ai déjà décrit à méthodes de création d'URL dans des sites Web multilingues .

Dans ce cas, ce devrait être la première méthode du sujet mentionné donc:

  1. J'ai une langue par défaut
  2. Je peux avoir beaucoup d'autres langues
  3. La langue actuelle doit être calculée uniquement par URL (sans cookies/sessions) pour la rendre très conviviale également pour les moteurs de recherche
  4. Pour la langue par défaut, il ne doit pas y avoir de préfixe dans l'URL, pour les autres langues, il doit y avoir un préfixe de langue après le domaine
  5. Chaque partie de l'url doit être traduite selon la langue actuelle.

Supposons que j'ai défini la langue par défaut pl et 2 autres langues en et fr. Je n'ai que 3 pages - page principale, page de contact et page sur.

Les URL du site doivent alors ressembler à ceci:

/
/[about]
/[contact]
/en
/en/[about]
/en/[contact]
/fr
/fr/[about]
/fr/[contact]

tandis que [about] et [contact] doit être traduit selon la langue sélectionnée, par exemple en anglais, il doit être laissé contact mais pour le polonais, il doit être kontakt et ainsi de suite.

Comment cela peut-il être aussi simple que possible?

43
Marcin Nabiałek

Première étape:

Aller à app/lang répertoire et créez ici les traductions de vos itinéraires pour chaque langue. Vous devez créer 3 routes.php fichiers - chacun dans un répertoire de langue séparé (pl/en/fr) car vous voulez utiliser 3 langues

Pour le polonais:

<?php

// app/lang/pl/routes.php

return array(

    'contact' => 'kontakt',
    'about'   => 'o-nas'
);

Pour l'anglais:

<?php

// app/lang/en/routes.php

return array(
    'contact' => 'contact',
    'about'   => 'about-us'
);

Pour le français:

<?php

// app/lang/fr/routes.php

return array(
    'contact' => 'contact-fr',
    'about'   => 'about-fr'
);

Deuxième étape:

Aller à app/config/app.php fichier.

Vous devriez trouver la ligne:

'locale' => 'en',

et changez-le en une langue qui devrait être la langue principale de votre site (dans votre cas, le polonais):

'locale' => 'pl',

Vous devez également mettre dans ce fichier les lignes suivantes:

/**
 * List of alternative languages (not including the one specified as 'locale')
 */
'alt_langs' => array ('en', 'fr'),

/**
 *  Prefix of selected locale  - leave empty (set in runtime)
 */
'locale_prefix' => '',

Dans alt_langs config vous définissez des langues alternatives (dans votre cas en et fr) - elles doivent être identiques aux noms de fichiers de la première étape où vous avez créé des fichiers avec des traductions.

Et locale_prefix est le préfixe de votre locale. Vous ne vouliez aucun préfixe pour vos paramètres régionaux par défaut, il est donc défini sur une chaîne vide. Cette configuration sera modifiée lors de l'exécution si une autre langue que celle par défaut est sélectionnée.

Troisième étape

Accédez à votre app/routes.php fichier et mettre leur contenu (c'est tout le contenu de app/routes.php fichier):

<?php

// app/routes.php

/*
|--------------------------------------------------------------------------
| Application Routes
|--------------------------------------------------------------------------
|
| Here is where you can register all of the routes for an application.
| It's a breeze. Simply tell Laravel the URIs it should respond to
| and give it the Closure to execute when that URI is requested.
|
*/


/*
 *  Set up locale and locale_prefix if other language is selected
 */
if (in_array(Request::segment(1), Config::get('app.alt_langs'))) {

    App::setLocale(Request::segment(1));
    Config::set('app.locale_prefix', Request::segment(1));
}


/*
 * Set up route patterns - patterns will have to be the same as in translated route for current language
 */
foreach(Lang::get('routes') as $k => $v) {
    Route::pattern($k, $v);
}


Route::group(array('prefix' => Config::get('app.locale_prefix')), function()
{
    Route::get(
        '/',
        function () {
            return "main page - ".App::getLocale();
        }
    );


    Route::get(
        '/{contact}/',
        function () {
            return "contact page ".App::getLocale();
        }
    );



    Route::get(
        '/{about}/',
        function () {
            return "about page ".App::getLocale();

        }
    );

});

Comme vous le voyez d'abord, vous vérifiez si le premier segment de l'URL correspond au nom de vos langues - si oui, vous modifiez les paramètres régionaux et le préfixe de la langue actuelle.

Ensuite, dans une petite boucle, vous définissez les exigences pour tous vos noms de route (vous avez mentionné que vous voulez que about et contact soient traduits en URL) donc ici vous les définissez comme ceux définis dans routes.php fichier pour la langue actuelle.

Enfin, vous créez un groupe Route qui aura un préfixe identique à votre langue (pour la langue par défaut, il sera vide) et à l'intérieur du groupe, vous créez simplement des chemins mais ces paramètres about et contact vous traitez comme variables donc vous utilisez {about} et {contact} syntaxe pour eux.

Vous devez vous rappeler que dans ce cas {contact} dans toutes les routes sera vérifié si c'est la même chose que vous l'avez définie à la première étape pour la langue actuelle. Si vous ne souhaitez pas cet effet et que vous souhaitez configurer des itinéraires manuellement pour chaque itinéraire en utilisant où, il existe une alternative app\routes.php fichier sans boucle où vous définissez contact et about séparément pour chaque route:

<?php

// app/routes.php

/*
|--------------------------------------------------------------------------
| Application Routes
|--------------------------------------------------------------------------
|
| Here is where you can register all of the routes for an application.
| It's a breeze. Simply tell Laravel the URIs it should respond to
| and give it the Closure to execute when that URI is requested.
|
*/

/*
 *  Set up locale and locale_prefix if other language is selected
 */
if (in_array(Request::segment(1), Config::get('app.alt_langs'))) {

    App::setLocale(Request::segment(1));
    Config::set('app.locale_prefix', Request::segment(1));
}


Route::group(array('prefix' => Config::get('app.locale_prefix')), function()
{
    Route::get(
        '/',
        function () {
            return "main page - ".App::getLocale();
        }
    );


    Route::get(
        '/{contact}/',
        function () {
            return "contact page ".App::getLocale();
        }
    )->where('contact', Lang::get('routes.contact'));



    Route::get(
        '/{about}/',
        function () {
            return "about page ".App::getLocale();

        }
    )->where('about', Lang::get('routes.about'));


});

Quatrième étape:

Vous n'en avez pas parlé, mais vous pouvez envisager une chose supplémentaire. Si quelqu'un utilise l'url /en/somethingsomething n'est pas la bonne route, je pense que la meilleure solution pour faire la redirection. Mais vous ne devez pas rediriger vers / car c'est la langue par défaut mais à /en.

Alors maintenant, vous pouvez ouvrir app/start/global.php fichier et créer ici la redirection 301 pour les URL inconnues:

// app/start/global.php

App::missing(function()
{
   return Redirect::to(Config::get('app.locale_prefix'),301);
});
67
Marcin Nabiałek

Ce que Marcin Nabiałek nous a fourni dans sa réponse initiale est une solution solide au problème de localisation des routes.

Le petit bugbear:

Le seul inconvénient réel de sa solution est que nous ne pouvons pas utiliser les itinéraires mis en cache, ce qui peut parfois être très avantageux selon les documents Laravel's :

Si votre application utilise exclusivement des routes basées sur un contrôleur, vous devez profiter du cache de routes de Laravel. L'utilisation du cache d'itinéraire réduira considérablement le temps nécessaire pour enregistrer tous les itinéraires de votre application. Dans certains cas, l'enregistrement de votre itinéraire peut même être jusqu'à 100 fois plus rapide. Pour générer un cache d'itinéraire, exécutez simplement la commande route:cache Artisan.


Pourquoi ne pouvons-nous pas mettre en cache nos itinéraires?

Parce que la méthode de Marcin Nabiałek génère dynamiquement de nouveaux itinéraires basés sur locale_prefix, Leur mise en cache entraînerait une erreur 404 Lors de la visite d'un préfixe non stocké dans la variable locale_prefix au moment de la mise en cache.


Que gardons-nous?

La fondation semble vraiment solide et on peut en garder la plupart!

Nous pouvons certainement conserver les différents fichiers de route spécifiques à la localisation:

<?php

// app/lang/pl/routes.php

return array(

    'contact' => 'kontakt',
    'about'   => 'o-nas'
);

Nous pouvons également conserver toutes les variables app/config/app.php:

/**
* Default locale 
*/
'locale' => 'pl'

/**
 * List of alternative languages (not including the one specified as 'locale')
 */
'alt_langs' => array ('en', 'fr'),

/**
 *  Prefix of selected locale  - leave empty (set in runtime)
 */
'locale_prefix' => '',

 /**
 * Let's also add a all_langs array
 */
'all_langs' => array ('en', 'fr', 'pl'),

Nous aurons également besoin du peu de code qui vérifie les segments de route. Mais puisque le but de ceci est d'utiliser le cache, nous devons le déplacer en dehors du fichier routes.php. Celui-ci ne sera plus utilisé une fois que nous aurons mis en cache les routes. Nous pouvons pour l'instant le déplacer vers app/Providers/AppServiceProver.php Par exemple:

public function boot(){
  /*
   *  Set up locale and locale_prefix if other language is selected
   */
   if (in_array(Request::segment(1), config('app.alt_langs'))) {
       App::setLocale(Request::segment(1));
       config([ 'app.locale_prefix' => Request::segment(1) ]);
   }
}

N'oubliez pas:

use Illuminate\Support\Facades\Request;
use Illuminate\Support\Facades\App;

Configuration de nos itinéraires:

Plusieurs modifications se produiront dans notre fichier app/Http/routes.php.

Tout d'abord, nous devons faire en sorte qu'un nouveau tableau contienne tous les alt_langs Ainsi que le locale_prefix Par défaut, qui serait probablement '':

$all_langs = config('app.all_langs');

Afin de pouvoir mettre en cache tous les différents préfixes lang avec les paramètres de route traduits, nous devons tous les enregistrer. Comment pouvons-nous faire cela?

*** Laravel aside 1: ***

Jetons un coup d'oeil à la définition de Lang::get(..):

public static function get($key, $replace = array(), $locale = null, $fallback = true){
      return \Illuminate\Translation\Translator::get($key, $replace, $locale, $fallback);
}

Le troisième paramètre de cette fonction est une variable $locale! Génial - nous pouvons certainement l'utiliser à notre avantage! Cette fonction nous permet en fait de choisir le lieu d'où nous voulons obtenir la traduction!

La prochaine chose que nous allons faire est de parcourir le tableau $all_langs Et de créer un nouveau groupe Route pour chaque préfixe de langue. Non seulement cela, mais nous allons également nous débarrasser des chaînes where et patterns dont nous avions besoin auparavant, et enregistrer uniquement les routes avec leurs traductions appropriées (d'autres lanceront 404 sans avoir à le vérifier):

/**
* Iterate over each language prefix 
*/
foreach( $all_langs as $prefix ){

   if ($prefix == 'pl') $prefix = '';

   /**
   * Register new route group with current prefix
   */
   Route::group(['prefix' => $prefix], function() use ($prefix) {

         // Now we need to make sure the default prefix points to default  lang folder.
         if ($prefix == '') $prefix = 'pl';

         /**
         * The following line will register:
         *
         * example.com/
         * example.com/en/
         */
         Route::get('/', 'MainController@getHome')->name('home');

         /**
         * The following line will register:
         *
         * example.com/kontakt
         * example.com/en/contact
         */
         Route::get(Lang::get('routes.contact',[], $prefix) , 'MainController@getContact')->name('contact');

         /**
         * “In another moment down went Alice after it, never once 
         * considering how in the world she was to get out again.”
         */
         Route::group(['prefix' => 'admin', 'middleware' => 'admin'], function () use ($prefix){

            /**
            * The following line will register:
            *
            * example.com/admin/uzivatelia
            * example.com/en/admin/users
            */
            Route::get(Lang::get('routes.admin.users',[], $prefix), 'AdminController@getUsers')
            ->name('admin-users');

         });
   });
}

/**
* There might be routes that we want to exclude from our language setup.
* For example these pesky ajax routes! Well let's just move them out of the `foreach` loop.
* I will get back to this later.
*/
Route::group(['middleware' => 'ajax', 'prefix' => 'api'], function () {
    /**
    * This will only register example.com/api/login
    */
    Route::post('login', 'AjaxController@login')->name('ajax-login');
});

Houston, nous avons un problème!

Comme vous pouvez le voir, je préfère utiliser des itinéraires nommés (la plupart des gens le font probablement):

Route::get('/', 'MainController@getHome')->name('home');

Ils peuvent être très facilement utilisés dans vos modèles de lames:

{{route('home')}}

Mais il y a un problème avec ma solution jusqu'à présent: les noms de route se remplacent. La boucle foreach ci-dessus n'enregistrerait que les derniers itinéraires préfixés avec leurs noms.

En d'autres termes, seul example.com/ Serait lié à la route home car locale_perfix Était le dernier élément du tableau $all_langs.

Nous pouvons contourner cela en préfixant les noms de route avec la langue $prefix. Par exemple:

Route::get('/', 'MainController@getHome')->name($prefix.'_home');

Nous devrons le faire pour chacune des routes de notre boucle. Cela crée un autre petit obstacle.


Mais mon énorme projet est presque terminé!

Eh bien, comme vous l'avez probablement deviné, vous devez maintenant revenir à tous vos fichiers et préfixer chaque appel de fonction d'assistance route avec le courant locale_prefix Chargé à partir de la configuration app.

Sauf que non!

*** Laravel aside 2: ***

Voyons comment Laravel implémente sa méthode d'assistance route.

if (! function_exists('route')) {
    /**
     * Generate a URL to a named route.
     *
     * @param  string  $name
     * @param  array   $parameters
     * @param  bool    $absolute
     * @return string
     */
    function route($name, $parameters = [], $absolute = true)
    {
        return app('url')->route($name, $parameters, $absolute);
    }
}

Comme vous pouvez le voir Laravel vérifiera d'abord si une fonction route existe déjà. Il enregistrera sa fonction route uniquement si une autre n'existe pas encore!

Ce qui signifie que nous pouvons contourner notre problème très facilement sans avoir à réécrire chaque appel route effectué jusqu'à présent dans nos modèles Blade.

Créons un fichier app/helpers.php Très rapidement.

Assurons-nous que Laravel charge le fichier avant de charger son helpers.php En mettant la ligne suivante dans bootstrap/autoload.php

//Put this line here
require __DIR__ . '/../app/helpers.php';
//Right before this original line
require __DIR__.'/../vendor/autoload.php';

Il ne nous reste plus qu'à créer notre propre fonction route dans notre fichier app/helpers.php. Nous utiliserons l'implémentation d'origine comme base:

<?php
//Same parameters and a new $lang parameter
use Illuminate\Support\Str;

function route($name, $parameters = [], $absolute = true, $lang = null)
{
    /*
    * Remember the ajax routes we wanted to exclude from our lang system?
    * Check if the name provided to the function is the one you want to
    * exclude. If it is we will just use the original implementation.
    **/
    if (Str::contains($name, ['ajax', 'autocomplete'])){
        return app('url')->route($name, $parameters, $absolute);
    }

   //Check if $lang is valid and make a route to chosen lang
   if ( $lang && in_array($lang, config('app.alt_langs')) ){
       return app('url')->route($lang . '_' . $name, $parameters, $absolute);
   }

    /**
    * For all other routes get the current locale_prefix and prefix the name.
    */
    $locale_prefix = config('app.locale_prefix');
    if ($locale_prefix == '') $locale_prefix = 'pl';
    return app('url')->route($locale_prefix . '_' . $name, $parameters, $absolute);
}

C'est tout!

Donc, ce que nous avons fait essentiellement, c'est enregistrer tous les groupes de préfixes disponibles. Créé chaque itinéraire traduit et avec son nom également préfixé. Et puis en quelque sorte a remplacé la fonction Laravel route pour préfixer tous les noms de routes (sauf certains) avec le locale_prefix afin que les URL appropriées soient créées dans nos modèles de lame sans avoir à taper config('app.locale_prefix') à chaque fois.

Oh oui:

php artisan route:cache

La mise en cache des itinéraires ne doit être effectuée que lorsque vous déployez votre projet, car il est probable que vous les gâchiez pendant le développement. Mais vous pouvez toujours vider le cache:

php artisan route:clear

Merci encore à Marcin Nabiałek pour sa réponse originale. Cela m'a vraiment aidé.

22
PeterTheLobster

Les mêmes résultats peuvent être appliqués avec une approche plus simple .. pas parfait, mais offre une solution rapide et facile. Dans ce scénario, vous devez cependant écrire chaque itinéraire afin de ne pas le faire pour les grands sites Web.

Route::get('/contact-us', function () {
    return view('contactus');
})->name('rte_contact'); // DEFAULT

Route::get('/contactez-nous', function () {
    return view('contactus');
})->name('rte_contact_fr');

il suffit de définir les noms de route dans le fichier de localisation comme suit:

# app/resources/lang/en.json
{ "rte_contact": "rte_contact" } //DEFAULT

// app/resources/lang/fr.json
{ "rte_contact": "rte_contact_fr" }

Vous pouvez ensuite les utiliser dans vos modèles de lame en utilisant des variables locales générées comme ceci:

<a class="nav-link" href="{{ route(__('rte_contact')) }}"> {{ __('nav_contact') }}</a>
1
JeanMGirard