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

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

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

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

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

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

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

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

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

Первый шаг:

Перейдите в каталог 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 них routes.php и которые были переведены по URL-адресу), поэтому здесь вы устанавливаете их так же, как определено в файле routes.php для текущего языка.

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

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

 <?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 не правильно, Route, я думаю, лучшее решение для перенаправления. Но вы должны сделать перенаправление не до / потому что это язык по умолчанию, но для /en .

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

 // app/start/global.php App::missing(function() { return Redirect::to(Config::get('app.locale_prefix'),301); }); 

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

Малый жучок:

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

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


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

Поскольку метод Marcin Nabiałek генерирует новые маршруты на основе 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 файла 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 файле 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 для каждого языкового префикса. Не только это, но мы также избавимся от тех цепей и 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 языка $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 файл app/helpers.php .

Давайте 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); } 

Это оно!

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

О, да:

 php artisan route:cache 

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

 php artisan route:clear 

Еще раз спасибо Marcin Nabiałek за его оригинальный ответ. Это было очень полезно для меня.