Мультиязычные сайты в Laravel настраиваются довольно просто, но если вы хотите, чтобы локаль отображалась в URL, например /en/about, то у вас возникнут проблемы с Auth::route(), по умолчанию они не будут работать как /en/register. В этой статье я расскажу, что нужно делать.
Шаг 1. Подготовка проекта
Сначала мы создаем проект при помощи laravel new laravel, а затем запускаем эти команды:
php artisan make:auth php artisan migrate
Итак, теперь у нас есть стандартные шаблоны Auth в resources/views/auth и измененный resources/views/layouts/app.blade.php со ссылками Login/Register.
Шаг 2. Route::group() для Локалей
Далее добавим Route::group() для всех возможных URL-адресов, так чтобы все страницы внутри проекта имели префикс /[locale]/[любая_страница]. Вот как это выглядит в routes/web.php:
Route::group(['prefix' => '{locale}'], function() { Route::get('/', function () { return view('welcome'); }); Auth::routes(); Route::get('/home', 'HomeController@index')->name('home'); });
Все вышеперечисленные маршруты уже были изначально, мы просто переместили их в наш Route::group().
Эта группа будет охватывать такие URL, как /en/ или /en/home или /en/register.
Теперь давайте установим, что {locale} может быть только двухбуквенная, например en или fr или de. Добавляем правило на основе регулярных выражений внутри группы:
Route::group([ 'prefix' => '{locale}', 'where' => ['locale' => '[a-zA-Z]{2}'] ], function() { // ...
Шаг 3. Настройка Локали при помощи Middleware
Люди часто думают, что классы Middleware могут только ограничивать доступ. Но вы можете сделать в них гораздо больше. В нашем примере мы установим app()->setLocale().
php artisan make:middleware SetLocale
Эта команда сгенерирует app/Http/Middleware/SetLocale.php, где нам нужно добавить только одну строку:
class SetLocale { public function handle($request, Closure $next) { app()->setLocale($request->segment(1)); return $next($request); } }
Переменная $request->segment(1) будет содержать {locale} из URL, таким образом мы установим локаль для всех запросов.
Конечно, нужно зарегистрировать этот класс в app/Http/Kernel.php в массиве $routeMiddleware:
protected $routeMiddleware = [ 'auth' => \App\Http\Middleware\Authenticate::class, // ... 'setlocale' => \App\Http\Middleware\SetLocale::class, ];
Наконец, мы применим этот мидлвар ко всей группе, которую мы создали выше:
Route::group([ 'prefix' => '{locale}', 'where' => ['locale' => '[a-zA-Z]{2}'], 'middleware' => 'setlocale'], function() { // ...
Шаг 4. Автоматический редирект на главную страницу
Нам нужно добавить еще одну строку в routes/web.php — для перенаправления пользователей с главной страницы без локали на страницу /en/.
Этот маршрут будет вне группы Route::group(), созданной нами выше:
Route::get('/', function () { return redirect(app()->getLocale()); });
Это перенаправит пользователя с yourdomain.com на yourdomain.com/en, где будет показываться наша дефолтная страница приветствия.
Шаг 5. Добавление локали ко всем существующим ссылкам
Сейчас, если мы нажмём на ссылку «Login» или «Register» в правом верхнем углу, то увидим ошибку, типа такой:
Проблема в том, что все представления, генерируемые командой make:auth, даже не подозревают о нашей локали. Но мы поместили Auth::routs() в группу маршрутов, поэтому теперь ВСЕ URL должны следовать этому правилу и иметь параметр локали.
Поэтому нам нужно отредактировать все представления в resources/views/auth и изменить все вызовы route(), чтобы передать еще один параметр app()->getLocale().
Например, меняем в resources/views/auth/register.blade.php:
Строка 11 — ДО:
<form method="POST" action="{{ route('register') }}">
Строка 11 — ПОСЛЕ:
<form method="POST" action="{{ route('register', app()->getLocale()) }}">
То же самое для других представлений, например resources/views/layouts/app.blade.php — мы добавляем параметр ко всем маршрутам login/register/logout:
@guest <li class="nav-item"> <a class="nav-link" href="{{ route('login', app()->getLocale()) }}">{{ __('Login') }}</a> </li> @if (Route::has('register')) <li class="nav-item"> <a class="nav-link" href="{{ route('register', app()->getLocale()) }}">{{ __('Register') }}</a> </li> @endif @else <li class="nav-item dropdown"> <a id="navbarDropdown" class="nav-link dropdown-toggle" href="#" role="button" data-toggle="dropdown" aria-haspopup="true" aria-expanded="false" v-pre> {{ Auth::user()->name }} <span class="caret"></span> </a> <div class="dropdown-menu dropdown-menu-right" aria-labelledby="navbarDropdown"> <a class="dropdown-item" href="{{ route('logout', app()->getLocale()) }}" onclick="event.preventDefault(); document.getElementById('logout-form').submit();"> {{ __('Logout') }} </a> <form id="logout-form" action="{{ route('logout', app()->getLocale()) }}" method="POST" style="display: none;"> @csrf </form> </div> </li> @endguest
Теперь наша форма регистрации будет работать по ссылке /en/register.
Шаг 6. Login и Register — переопределение $redirectTo при помощи локали
Затем, если мы успешно заполним форму регистрации или логина, то будем перенаправлены на адрес /home — это дефолтная настройка Laravel. В нашем случае это не сработает, так как все URL должны иметь локаль. Поэтому нам нужно добавить локаль к редиректам.
Для этого перейдем в app/Http/Controllers/Auth/RegisterController.php и посмотрим на свойство $redirectTo:
class RegisterController extends Controller { protected $redirectTo = '/home';
Не уверен, что вы в курсе, но можно переопределить не только само свойство, но и создать метод redirectTo(), который переопределит свойство. Это позволит добавить более сложную логику для редиректа — например, когда нужно перенаправить разные группы пользователей на разные маршруты.
В нашем случае все, что нам нужно сделать, это добавить локаль к маршруту:
public function redirectTo() { return app()->getLocale() . '/home'; }
То же самое относится и к app/Http/Controllers/Auth/LoginController.php, поэтому мы здесь просто скопипастим метод redirectTo().
Теперь, после успешной регистрации, мы должны попасть сюда:
Шаг 7. Выбор языка в навигации
Довольно странно заняться этим только сейчас, но я предпочел показать сначала «как это работает», прежде чем «как это выглядит».
Так что, давайте реализуем выбор языка в панели навигации. Например, так — три языка на выбор:
На самом деле все довольно просто, так как у нас есть система, уже готовая к этому.
Сначала давайте определим массив возможных языков. В config/app.php добавим массив available_locales:
// ... 'locale' => 'en', 'available_locales' => [ 'en', 'de', 'fr' ],
Затем в верхней секции resources/views/layouts/app.blade.php добавим цикл @foreach:
<!-- Right Side Of Navbar --> <ul class="navbar-nav ml-auto"> @foreach (config('app.available_locales') as $locale) <li class="nav-item"> <a class="nav-link" href="{{ route(\Illuminate\Support\Facades\Route::currentRouteName(), $locale) }}" @if (app()->getLocale() == $locale) style="font-weight: bold; text-decoration: underline" @endif>{{ strtoupper($locale) }}</a> </li> @endforeach
Прокомментирую некоторые моменты:
- Мы берем список языков из config(‘app.available_locales’), который мы только что создали;
- Мы даем ссылку на этот же текущий маршрут, но с другим языком — для этого мы используем метод Route::currentRouteName();
- Также мы проверяем текущий активный язык при помощи условия app()->getLocale() == $locale;
- Наконец, мы показываем сам язык в верхнем регистре через strtoupper($locale).
Шаг 8. Заполнение переводов
Цель, которой мы добивались — чтобы адрес /en/register показывал форму на английском языке, а /de/register — на немецком.
Давайте сохраним локаль en как дефолтную и заполним переводы для других языков.
Если вы посмотрите на файл resources/views/auth/login.blade.php и другие шаблоны, то увидите, что все они используют метод подчеркивания __() для получения текстов. Вот пример:
<div class="card-header">{{ __('Login') }}</div> <label for="email" class="col-md-4 col-form-label text-md-right">{{ __('E-Mail Address') }}</label> <label for="password" class="col-md-4 col-form-label text-md-right">{{ __('Password') }}</label> <label class="form-check-label" for="remember"> {{ __('Remember Me') }} </label> <button type="submit" class="btn btn-primary"> {{ __('Login') }} </button> <a class="btn btn-link" href="{{ route('password.request', app()->getLocale()) }}"> {{ __('Forgot Your Password?') }} </a>
Итак, все, что нам нужно сделать, это заполнить файлы перевода на других языках. Создадим файл resources/lang/fr.json и сделаем перевод:
{ "Login": "Se connecter", "E-Mail Address": "E-mail", "Password": "Mot de passe", "Remember Me": "Se souvenir de moi", "Forgot Your Password?": "Mot de passe oublié?" }
Примечание: я не француз, эти переводы взяты из одного клиентского проекта, не судите строго, если перевод неправильный.
Окончательный вид, если нажмем ссылку «Вход» на французском языке:
Готово!
Вы можете посмотреть код этого проекта на Github.
Автор: Povilas Korop
Перевод: Алексей Широков
Наш Телеграм-канал — следите за новостями о Laravel.