Подтвердить что ты не робот

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

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

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

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

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

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

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

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

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

4b9b3361

Ответ 1

Первый шаг:

Перейдите в каталог 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 config вы устанавливаете альтернативные языки (в вашем случае 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 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} во всех маршрутах будет проверяться, если он будет таким же, как вы определили его на первом шаге для текущего языка. Если вы не хотите этого эффекта и хотите настроить маршруты вручную для каждого маршрута, используя где, есть альтернативный 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 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);
});

Ответ 2

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

Малый багбир:

Единственный реальный недостаток его решения в том, что мы не можем использовать кэшированные маршруты, которые иногда могут быть очень полезны в соответствии Laravel's документами 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 also add a all_langs array
 */
'all_langs' => array ('en', 'fr', 'pl'),

Нам также понадобится фрагмент кода, который проверяет сегменты маршрута. Но так как смысл в том, чтобы использовать кеш, нам нужно переместить его за routes.php файла 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 массив $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('/', '[email protected]')->name('home');

         /**
         * The following line will register:
         *
         * example.com/kontakt
         * example.com/en/contact
         */
         Route::get(Lang::get('routes.contact',[], $prefix) , '[email protected]')->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), '[email protected]')
            ->name('admin-users');

         });
   });
}

/**
* There might be routes that we want to exclude from our language setup.
* For example these pesky ajax routes! Well let 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', '[email protected]')->name('ajax-login');
});

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

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

Route::get('/', '[email protected]')->name('home');

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

{{route('home')}}

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

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

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

Route::get('/', '[email protected]')->name($prefix.'_home');

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


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

Как вы уже, наверное, догадались, теперь вам нужно возвращаться ко всем вашим файлам и locale_prefix префикс каждого вызова вспомогательной функции 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 его 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
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);
}

Это!

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

О да:

php artisan route:cache

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

php artisan route:clear

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

Ответ 3

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

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

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

просто определите имена маршрутов в файле локализации так:

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

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

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

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