Как создать переведенные на несколько языков маршруты в Laravel


Я хотел бы создать приложение со множеством переведенных маршрутов в зависимости от выбранного языка. Я однажды описал это в 3 метода создания URL-адресов на многоязычных веб-сайтах.

В этом случае это должен быть первый метод из упомянутой темы итак:

  1. У меня есть один язык по умолчанию
  2. У меня может быть много других языков
  3. Текущий язык должен быть рассчитан только по URL (без файлов cookie/сеансов), чтобы сделать его действительно дружественный также для поисковых систем
  4. Для языка по умолчанию в URL-адресе не должно быть префикса, для других языков должен быть префикс языка после домена
  5. Каждая часть URL-адреса должна быть переведена в соответствии с текущим языком.

Предположим, что я установил язык по умолчанию pl и 2 других языка en и fr. У меня всего 3 страницы - главная страница, страница контактов и страница о компании.

URL-адреса сайта должны выглядеть следующим образом:

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

В то время как [about] и [contact] должно быть переведено в соответствии с выбранным языком, например, на английском языке его следует оставить contact, но для польского языка это должно быть kontakt и так далее.

Как это можно сделать как можно проще?

Author: Community, 2014-08-01

2 answers

Первый шаг:

Перейдите в каталог app/lang и создайте здесь переводы для ваших маршрутов для каждого языка. Вам нужно создать 3 routes.php файла - каждый в отдельном языковом каталоге (pl/en/fr), потому что вы хотите использовать 3 языка

Для польского языка:

<?php

// app/lang/pl/routes.php

return array(

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

Для английского языка:

<?php

// app/lang/en/routes.php

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

Для французского языка:

<?php

// app/lang/fr/routes.php

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

Второй шаг:

Перейдите в файл app/config/app.php.

Вы должны найти строку:

'locale' => 'en',

И измените его на язык, который должен быть вашим основным языком сайта (в вашем случае польский):

'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' => '',

В alt_langs конфигурации вы устанавливаете альтернативные языки (в вашем случае en и fr) - они должны совпадать с именами файлов с первого шага, на котором вы создали файлы с переводами.

И locale_prefix - это префикс для вашего языка. Вам не нужен был префикс для языка по умолчанию, поэтому он установлен в пустую строку. Эта конфигурация будет изменено во время выполнения, если будет выбран другой язык, отличный от языка по умолчанию.

Третий шаг

Перейдите в свой файл app/routes.php и поместите их содержимое (это все содержимое файла app/routes.php):

<?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();

        }
    );

});

Как вы видите, сначала вы проверяете, соответствует ли первый сегмент URL-адреса названию ваших языков - если да, вы меняете языковой стандарт и текущий языковой префикс.

Затем в крошечном цикле вы устанавливаете требования для всех ваших названий маршрутов (вы упомянули, что хотите иметь about и contact переведено в URL), поэтому здесь вы устанавливаете их такими же, как определено в файле routes.php для текущего языка.

Наконец, вы создаете группу маршрутов, которая будет иметь префикс, совпадающий с вашим языком (для языка по умолчанию он будет пустым), и внутри группы вы просто создаете пути, но эти параметры about и contact вы обрабатываете как variables, поэтому вы используете для них синтаксис {about} и {contact}.

Вам нужно помнить, что в этом случае {contact} во всех маршрутах будет проверяться, совпадает ли он с вы определили его на первом шаге для текущего языка. Если вы не хотите этого эффекта и хотите настроить маршруты вручную для каждого маршрута, используя where, есть альтернативный файл app\routes.php без цикла, где вы устанавливаете contact и about отдельно для каждого маршрута:

<?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'));


});

Четвертый шаг:

Вы не упомянули об этом, но есть еще одна вещь, которую вы могли бы рассмотреть. Если кто-то будет использовать url /en/something, где something неверный маршрут, я думаю, что лучшее решение для перенаправления. Но вы должны сделать перенаправление не на /, потому что это язык по умолчанию, а на /en.

Итак, теперь вы можете открыть файл app/start/global.php и создать здесь перенаправление 301 для неизвестных URL-адресов:

// app/start/global.php

App::missing(function()
{
   return Redirect::to(Config::get('app.locale_prefix'),301);
});
 62
Author: Marcin Nabiałek, 2014-09-03 11:47:09

То, что Марцин Набиалек предоставил нам в своем первоначальном ответе, является надежным решением проблемы локализации маршрута.

Мелкий Жучок:

Единственным реальным недостатком его решения является то, что мы не можем использовать кэшированные маршруты, что иногда может быть очень полезно в соответствии с Laravel's документы:

Если ваше приложение использует исключительно маршруты на основе контроллеров, вам следует воспользоваться преимуществами кэша маршрутов Laravel. С помощью кэш маршрутов значительно сократит время, необходимое для регистрации всех маршрутов вашего приложения. В некоторых случаях регистрация вашего маршрута может быть даже в 100 раз быстрее. Чтобы создать кэш маршрутов, просто выполните команду route:cache Artisan.


Почему мы не можем кэшировать наши маршруты?

Поскольку Метод Марчина Набиолека генерирует новые маршруты на основе locale_prefix динамически, их кэширование приведет к ошибке 404 при посещение любого префикса, не сохраненного в переменной locale_prefix во время кэширования.


Что мы храним?

Фундамент кажется действительно прочным, и мы можем сохранить большую его часть!

Мы, безусловно, можем сохранить различные файлы маршрутов, зависящие от локализации:

<?php

// app/lang/pl/routes.php

return array(

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

Мы также можем сохранить все переменные 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'),

Нам также понадобится бит кода, который проверяет сегменты маршрута. Но так как смысл этого в том, чтобы использовать кэш, нам нужно переместите его за пределы файла routes.php. Этот больше не будет использоваться, как только мы кэшируем маршруты. На данный момент мы можем переместить его в app/Providers/AppServiceProver.php, например:

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) ]);
   }
}

Не забывайте:

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

Настройка наших маршрутов:

В нашем файле app/Http/routes.php произойдет несколько изменений.

Во-первых, мы должны создать новый массив, содержащий все alt_langs, а также значение по умолчанию locale_prefix, которое, скорее всего, будет '':

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

Для того, чтобы иметь возможность кэшируйте все различные префиксы языка lang с переведенными параметрами маршрута, которые нам нужны, чтобы зарегистрировать их все. Как мы можем это сделать?

*** Laravel aside 1: ***

Давайте взглянем на определение Lang::get(..):

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

Третьим параметром этой функции является переменная $locale! Отлично - мы, безусловно, можем использовать это в наших интересах! Эта функция на самом деле позволяет нам выбрать, из какой локали мы хотим получить перевод!

Следующее, что мы собираемся сделать, это повторить $all_langs массив и создайте новую группу Route для каждого языкового префикса. Не только это, но мы также собираемся избавиться от цепочек where и patterns, которые нам были нужны ранее, и регистрировать маршруты только с их правильными переводами (другие будут выбрасывать 404 без необходимости проверять это больше):

/**
* 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');
});

Хьюстон, у нас проблема!

Как вы можете видеть, я предпочитаю использовать именованные маршруты (вероятно, большинство людей так и делают):

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

Их можно очень легко использовать внутри ваших шаблонов лезвий:

{{route('home')}}

Но до сих пор в моем решении есть проблема: имена маршрутов переопределяют друг друга. Цикл foreach выше будет регистрировать только последние маршруты с префиксами с их именами.

Другими словами, только example.com/ будет привязан к маршруту home, поскольку locale_perfix был последним элементом в массиве $all_langs.

Мы можем обойти это, добавив к названиям маршрутов префикс языка $prefix. Например:

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

Нам придется сделать это для каждого из маршруты внутри нашего цикла. Это создает еще одно небольшое препятствие.


Но мой масштабный проект почти завершен!

Ну, как вы, наверное, догадались, теперь вам нужно вернуться ко всем вашим файлам и добавить префикс к каждому вызову вспомогательной функции route с текущим locale_prefix, загруженным из конфигурации app.

Только ты этого не делаешь!

*** Laravel aside 2: ***

Давайте посмотрим, как Laravel реализует свой вспомогательный метод 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);
    }
}

Как вы можете видеть Laravel сначала проверит, существует ли уже функция route. Он зарегистрирует свою функцию route только в том случае, если другой функции еще не существует!

Это означает, что мы можем очень легко обойти нашу проблему, не переписывая каждый вызов route, сделанный до сих пор в наших шаблонах Blade.

Давайте быстро создадим файл app/helpers.php.

Давайте убедимся, что Laravel загружает файл до того, как он загрузит свой helpers.php, поместив следующую строку в bootstrap/autoload.php

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

Все мы сейчас нужно сделать, это создать нашу собственную функцию route в нашем файле app/helpers.php. Мы будем использовать оригинальную реализацию в качестве основы:

<?php
//Same parameters and a new $lang parameter
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);
}

Вот и все!

Итак, то, что мы сделали, по сути, зарегистрировало все доступные группы префиксов. Создал каждый переведенный маршрут с префиксом его имени. А затем своего рода переопределите функцию Laravel route, чтобы добавить ко всем именам маршрутов (кроме некоторых) префикс текущего locale_prefix, чтобы соответствующие URL-адреса были созданный в наших шаблонах блейдов без необходимости вводить config('app.locale_prefix') каждый раз.

О да:

php artisan route:cache

Кэширование маршрутов действительно следует выполнять только после развертывания проекта, так как, скорее всего, вы будете возиться с ними во время разработки. Но вы всегда можете очистить кэш:

php artisan route:clear

Еще раз спасибо Марцину Набиалеку за его оригинальный ответ. Это было действительно полезно для меня.

 13
Author: PeterTheLobster, 2017-05-23 12:18:12