Мультиязычные маршруты и Локали с Аутентификацией

Как сделать мультиязычный сайт на Ларавел

Мультиязычные сайты в 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
Перевод: Demiurge Ash