Мультиязычные сайты в Laravel настраиваются довольно просто, но если вы хотите, чтобы локаль отображалась в URL, например /en/about, то у вас возникнут проблемы с Auth::route(), по умолчанию они не будут работать как /en/register. В этой статье я расскажу, что нужно делать.
Сначала мы создаем проект при помощи laravel new laravel, а затем запускаем эти команды:
php artisan make:auth
php artisan migrate
Итак, теперь у нас есть стандартные шаблоны Auth в resources/views/auth и измененный resources/views/layouts/app.blade.php со ссылками Login/Register.
Далее добавим 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() { // ...
Люди часто думают, что классы 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() { // ...
Нам нужно добавить еще одну строку в routes/web.php — для перенаправления пользователей с главной страницы без локали на страницу /en/.
Этот маршрут будет вне группы Route::group(), созданной нами выше:
Route::get('/', function () {
return redirect(app()->getLocale());
});
Это перенаправит пользователя с yourdomain.com на yourdomain.com/en, где будет показываться наша дефолтная страница приветствия.
Сейчас, если мы нажмём на ссылку «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.
Затем, если мы успешно заполним форму регистрации или логина, то будем перенаправлены на адрес /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().
Теперь, после успешной регистрации, мы должны попасть сюда:
Довольно странно заняться этим только сейчас, но я предпочел показать сначала «как это работает», прежде чем «как это выглядит».
Так что, давайте реализуем выбор языка в панели навигации. Например, так — три языка на выбор:
На самом деле все довольно просто, так как у нас есть система, уже готовая к этому.
Сначала давайте определим массив возможных языков. В 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
Прокомментирую некоторые моменты:
Цель, которой мы добивались — чтобы адрес /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