Laravel 5.3: Мультиязычность с переключением языков

15 сентября 2016, 22:14


Изучаю Laravel уже несколько недель, за это время удалось создать более менее нормальную заготовку для сайтов со своей админкой. Были испробованы за это время интересные наработки, которые в Laravel 5 называются пакетами. Возникает такой момент, когда необходимо сделать сайт мультиязычным и чтобы язык выбранный пользователем сохранялся. Пробовал многие готовые решения, но с версией Laravel 5.3 пришлось велосипедить. В данном примере покажу свой велосипед. Уточню момент, я использую авторизацию из коробки:

php artisan make:Auth

Собственно на нём мы и будем проверять работоспособность выбора языков. Для начала нам необходимо загрузить пакет Laravel-lang, выполняем команду:

composer require caouecs/laravel-lang ~3.0
или добавляем в файл composer.json в секцию require

"caouecs/laravel-lang": "~3.0"
и затем обновляем

composer update
После установки идём в /vendor/caouecs/laravel-lang/src/ и видим там папки с названиями локалей, выбираем нужные и переносим их в папку /resources/lang/. После того как папки с локалями будут на месте заходим в каждую и в файле /resources/lang/{locale}/auth.php меняем содержимое. Приведу пример на переводов на немецкий (de), английский (en) и русский (ru).

ru

<?php 

return [

    /*
    |--------------------------------------------------------------------------
    | Authentication Language Lines
    |--------------------------------------------------------------------------
    |
    | The following language lines are used during authentication for various
    | messages that we need to display to the user. You are free to modify
    | these language lines according to your application's requirements.
    |
    */

     'failed'   => 'Имя пользователя и пароль не совпадают.', 
     'throttle' => 'Слишком много попыток входа. Пожалуйста, попробуйте еще раз через :seconds секунд.',
     'name' => 'Имя', 'name_enter' => 'Введите имя', 
     'fullname' => 'Полное имя', 
     'email' => 'Email',
     'email_enter' => 'Введите E-mail',
     'password' => 'Пароль',
     'password_enter' => 'Введите пароль',
     'password_confirm' => 'Повторите пароль',
     'password_confirm_enter' => 'Введите пароль ещё раз',
     'password_forget' => 'Забыли пароль?', 
     'password_reset' => 'Сброс пароля',
     'password_reset_send_link' => 'Отправить ссылку сброса пароля',
     'remember' => 'Запомнить меня', 
     'entry' => 'Вход',
     'lang_selection' => 'Язык',
     'lang_choose' => 'Выберите язык', 
     'social' => 'Социальные сети',
     'social_desc' => 'Вы можете авторизироваться в системе используя социальные сети.', 

     // Register
      'register' => 'Регистрация',
      'logout' => 'Выход', 
];

en

<?php

return [

    /*
    |--------------------------------------------------------------------------
    | Authentication Language Lines
    |--------------------------------------------------------------------------
    |
    | The following language lines are used during authentication for various
    | messages that we need to display to the user. You are free to modify
    | these language lines according to your application's requirements.
    |
    */

     'failed' => 'These credentials do not match our records.', 
     'throttle' => 'Too many login attempts. Please try again in :seconds seconds.', 
     'name' => 'Name', 
     'name_enter' => 'Enter your name', 
     'fullname' => 'Full name', 
     'email' => 'Email', 
     'email_enter' => 'Enter E-mail', 
     'password' => 'Password', 
     'password_enter' => 'Enter password', 
     'password_confirm' => 'Confirm password', 
     'password_confirm_enter' => 'Enter your password again', 
     'password_forget' => 'Forgot your password?', 
     'password_reset' => 'Reset Password', 
     'password_reset_send_link' => 'Send Passwort Reset Link', 
     'remember' => 'Remember me', 
     'entry' => 'Login', 
     'lang_selection' => 'Language', 
     'lang_choose' => 'Choose language', 
     'social' => 'Social networks', 
     'social_desc' => 'You can log in to the system using social media.', 

     // Register 'register' => 'Register', 
     'logout' => 'Logout',
];
de

<?php

return [

    /*
    |--------------------------------------------------------------------------
    | Authentication Language Lines
    |--------------------------------------------------------------------------
    |
    | The following language lines are used during authentication for various
    | messages that we need to display to the user. You are free to modify
    | these language lines according to your application's requirements.
    |
    */

     'failed'   => 'Diese Zugangsdaten wurden nicht in unserer Datenbank gefunden.', 
     'throttle' => 'Zu viele Login Versuche. Versuchen Sie es bitte in :seconds Sekunden.', 
     'name' => 'Name', 
     'name_enter' => 'Geben Sie einen Namen', 
     'fullname' => 'Vollständiger Name', 
     'email' => 'Email', 
     'email_enter' => 'Geben Sie einfach Ihre E-Mail', 
     'password' => 'Kennwort', 
     'password_enter' => 'Geben Sie Ihr Passwort', 
     'password_confirm' => 'Wiederholen Sie das Passwort', 
     'password_confirm_enter' => 'Geben Sie Ihr Passwort erneut', 
     'password_forget' => 'Passwort vergessen?', 
     'password_reset' => 'Passwort zurücksetzen', 
     'password_reset_send_link' => 'Senden Passwort Reset Link', 
     'remember' => 'Erinnere dich an mich', 
     'entry' => 'Eingang', 
     'lang_selection' => 'Sprache', 
     'lang_choose' => 'Wählen Sie Ihre Sprache', 
     'social' => 'Soziale Netzwerke', 
     'social_desc' => 'Können Sie sich einloggen, um das System der sozialen Medien.', 

     // Register 
     'register' => 'Anmeldung', 
     'logout' => 'Ausfahrt', 
]; 
Следующим шагом будет создание базы данных и модели. Я решил сделать таким образом, чтобы в будущем не было проблем с динамикой и сохранением выбранного результата. Создадим для начала файл миграции:
php artisan make:migration languages
После нашей манипуляции в папке /database/migrations/ должен появится файл миграции с временной датой. Находим наш файл, он должен называть примерно так 2016_09_15_204431_languages.php заходим в него и содержимое меняем на:

<?php

use Illuminate\Support\Facades\Schema;
use Illuminate\Database\Schema\Blueprint;
use Illuminate\Database\Migrations\Migration;

class Languages extends Migration
{
    /**
     * Run the migrations.
     *
     * @return void
     */
    public function up()
    {
        Schema::create('languages', function (Blueprint $table) {
            $table->increments('id'); 
            $table->string('name'); 
            $table->string('locale'); 
            $table->timestamps(); 
        }); 
    } 
    
    /** 
     * Reverse the migrations. 
     * 
     * @return void 
     */ 
    
    public function down() {
        Schema::dropIfExists('languages');
    }
}
сохраняем и в терминале выполняем команду:

php artisan migrate
Наша база создана, теперь нужно добавить начальные данные. Будем использовать технологию Seeding. Создадим файл seed'a:

php artisan make:seeder LanguagesSeeder
У нас появился файлик
LanguagesSeeder.php в папке /database/seeds/, который имеет следующее содержание:

<?php

use Illuminate\Database\Seeder;

class LanguagesSeeder extends Seeder
{
    /**
     * Run the database seeds.
     *
     * @return void
     */
    public function run()
    {
        //
    }
}

Правим его чтобы получилось следующее:

<?php

use Illuminate\Database\Seeder;

class LanguagesSeeder extends Seeder
{
    /**
     * Run the database seeds.
     *
     * @return void
     */
    public function run()
    {
        DB::table('languages')->insert([ 
            [ 
                'name' => 'Русский', 
                'locale' => 'ru', 
            ], 
            [ 
                'name' => 'English', 
                'locale' => 'en', 
            ], 
            [ 
                'name' => 'Deutsch', 
                'locale' => 'de', 
            ], 
        ]); 
    } 
}

сохраняем, и переходим к редактированию файла DatabaseSeeder.php который находится у нас там же в папке /database/seeds/. Правим его чтобы получилось так:

<?php

use Illuminate\Database\Seeder;
use Illuminate\Database\Eloquent\Model;

class DatabaseSeeder extends Seeder
{
    /**
     * Run the database seeds.
     *
     * @return void
     */
    public function run()
    {
        Model::unguard();
        
        $this->call('LanguagesSeeder'); 
        Model::reguard();
    }
}
и после сохранения выполняем команду:

php artisan db:seed
Данные в базу у нас добавились, теперь переходим к созданию модели, через которую мы будем получать список языков и локалей из базы и сопоставлять их в будущем с нашими url'ами для переключения языковых версий. В терминале выполняем команду:
php artisan make:model Langs
в папке /app/ у нас создался файл модели Langs.php теперь правим его следующим образом:

<?php

namespace App;

use Illuminate\Database\Eloquent\Model;

class Langs extends Model
{
    protected $table = 'languages';
    protected $fillable = [
        'name','locale',
    ];
}

Чтобы безболезненно добавлять наш блок с выбором языков на любые страницы сайта, в любые шаблоны я использую виджеты, по этому предлагаю их поставить. В файле composer.json в секцию require вставляем:

"arrilot/laravel-widgets": "^3.6"

и выполняем команду:

composer update

Затем идём в /config/app.php и добавляем провайдер:

<?php

    'providers' => [ 
        ... 
        Arrilot\Widgets\ServiceProvider::class, 
    ], 

?>

а затем алиасы:

<?php 

    'aliases' => [ 
        ... 
        'Widget' => Arrilot\Widgets\Facade::class, 
        'AsyncWidget' => Arrilot\Widgets\AsyncFacade::class, 
    ], 
?>

После чего нам нужно с вами создать свой виджет:

php artisan make:widget Languages

В папке /app/Widgets/ находим файл Languages.php который мы только что создали по средством команды artiasn. Редактируем файл, чтобы получилось у нас следующее:

<?php 

namespace App\Widgets;

use Arrilot\Widgets\AbstractWidget;
use App\Langs;

class Languages extends AbstractWidget
{
    /**
     * The configuration array.
     *
     * @var array
     */
    protected $config = [];

    /**
     * Treat this method as a controller action.
     * Return view() or other content to display.
     */
    public function run()
    {
        $langs = Langs::all();
        return view("widgets.languages", [
            'config' => $this->config, 
            'langs' => $langs, 
        ]);
    }
} 

Сохраняем. Теперь нам нужно создать наш шаблон с выбором языков. Предполагается, что вы будете работать в свеже установленном Laravel 5.3 и использовать шаблон по умолчанию. В папке /resources/views/ создаём файл lang.blade.php и прописываем в него следующее содержание:

<select class="form-control" onchange="if(this.options[this.selectedIndex].value!=''){window.location=this.options[this.selectedIndex].value}else{this.options[selectedIndex=0];}">@foreach($langs as $lang)
<option>locale == config('app.locale')) {echo 'selected';} ?> value="/lang/{{ $lang->locale }}">{{ $lang->name }} @endforeach
и после этого редактируем файл login.blade.php /resources/views/auth/, для упрощения примера скопируйте и вставьте следующий код:

@extends('layouts.app') 

@section('content')
<select>
<option>{{ trans('auth.entry') }}{{ csrf_field() }}{{ trans('auth.email') }}</option>
</select>

<input id="email" class="form-control" type="email" name="email" value="{{ old('email') }}" /> 
@if ($errors->has('email')) <span class="help-block"> <strong>{{ $errors->first('email') }}</strong> </span> @endif

{{ $errors->has('password') ? ' has-error' : '' }}"><code><label class="col-md-4 control-label" for="password">{{ trans('auth.password') }}</label></code>
<div class="col-md-6"><input id="password" class="form-control" type="password" name="password" /> @if ($errors->has('password')) <span class="help-block"> <strong>{{ $errors->first('password') }}</strong> </span> @endif</div>
</div>

<label class="col-md-4 control-label" for="languages">{{ trans('auth.lang_selection') }}</label>

<b>@widget('Languages')</b>

<label><input type="checkbox" name="remember" /> {{ trans('auth.remember') }}</label>
<button class="btn btn-primary" type="submit"> {{ trans('auth.entry') }} </button> 
<a class="btn btn-link" href="{{ url('/password/reset') }}"> {{ trans('auth.password_forget') }} </a>

@endsection
Как видим из примера у нас повсюду записи типа {{ trans('auth.entry') }} это и есть наши массивы из файла /resources/lang/ru/auth.php, но что не мало важно выбор языка по средством селектов у нас вставлен вот таким вот образом @widget('Languages'), теперь через виджеты вы сможете вставлять любые блоки, которые будут выводится из любого шаблона.
Документацию по пакету можно найти здесь.
Вроде всё шоколадно, но чего то не хватает. Ну конечно же, мы ещё не сделали сохранение языка. Для начала мы зайдём в файл app.php в папке /config/ и найдём:

    'locale' => 'en', 

Словари в Laravel представляют собой php-массивы. Нам нужно по словарю на каждый язык, используемый в приложении. В самом начале мы создали словари для трёх языков. Теперь же мы создадим массив с нашими тремя языками, чтобы в дальнешем можно было переключаться между ними.

    'locale' => 'ru', 
    'locales' => ['ru', 'en', 'de'], 
По умолчанию Laravel всегда загружается с языком, выбранном в конфигурации. Чтобы изменить его, нам нужно перехватить запрос до того, как тот получит view и установить язык методом App::setLocale();. Middleware это такой кусок кода, который будет выполняться при каждом запросе страницы пользователем и позволит нам это сделать. В нем мы будем проверять, какой язык пользователь уже выбирал (и будем сохранять его в cookie пользователя), если он уже заходил на наш сайт, а если такового не найдется — добавлять к сессии язык по умолчанию (например ru). Заодно мы будем проверять, что выбранный пользователем язык валидный (для этого мы указали ранее в конфигурации доступные языки). Используя консоль и artisan создаем middleware для нашего переключателя. Назовем его, например, Locale.
php artisan make:middleware Locale
В созданном файле Locale.php который находится в папке /app/Http/Middleware/ меняем содержимое на:

<?php 

namespace App\Http\Middleware; 

use App; 
use Config; 
use Cookie; 
use Closure; 
use Illuminate\Http\Request; 

class Locale { 
    /** 
     * Handle an incoming request. 
     * 
     * @param \Illuminate\Http\Request $request 
     * @param \Closure $next 
     * @return mixed 
     */ 
     
    public function handle($request, Closure $next) { 
        $raw_locale = $request->cookie('lang'); 
        if (in_array($raw_locale, Config::get('app.locales'))) { 
            $locale = $raw_locale; 
        } else {
            $locale = Config::get('app.locale'); 
            App::setLocale($locale); 
            $request->cookie('lang'); 
            return $next($request); 
        }
    }
Регистрируем наш Middleware В файле /app/Http/Kernel.php добавляем наш middleware в конец массива, чтобы он выполнялся при каждой загрузке приложения.

    protected $middleware = [
        ...
        \App\Http\Middleware\Locale::class,
    ];
Куки с языком у нас сейчас закодированы, для того, чтобы брать это значение из cookie и передавать в наш скрипт без шифрования в файле /app/Http/Middleware/EncryptCookies.php мы должны указать исключение:

<?php 

namespace App\Http\Middleware; 

use Illuminate\Cookie\Middleware\EncryptCookies as BaseEncrypter; 

class EncryptCookies extends BaseEncrypter { 
    /** 
     * The names of the cookies that should not be encrypted. 
     * 
     * @var array 
     */ 

    protected $except = [ 'lang', ]; 
}

где 'lang' это название куков которые мы не хотим шифровать, через запятую можно список продолжать. Теперь нам нужно создать Route для переключения языка. Регистрируем путь, по которому будем переключать язык в cookie пользователя, сохраняя язык «навсегда» что равняется примерно 4-5 годам. Я для этого выбрал ссылку вида: /lang/en. При нажатии на неё мы изменяем значение lang (ранее установленное нашим middleware) на то, которое указано в конце ссылки. Попутно проверяем, что выбранный язык валидный уже знакомым методом и перенаправляем пользователя на ту же страницу, с которой был вызван скрипт. Теперь переходим к файлу web.php который у нас затаился в папке /routes/ и добавляем наш route:

Route::get('lang/{locale}/', function ($locale) { 
    if (in_array($locale, \Config::get('app.locales'))) { 
        Cookie::queue(
        Cookie::forever('lang', $locale)); 
    } 
    
    return redirect()->back(); 
});
Для проверки используем авторизацию. Заходим на страницу site/login и видим примерно следующее:

Пример работы выбора языка можно посмотреть здесь https://alpha.s01.one/login
SEQUEL.ONE
2    2171    0
+1

Комментарии ()

    Вы должны авторизоваться, чтобы оставлять комментарии.

    Топики


    Комментарии

    Андрей Копп 02 октября 2018, 09:04
    ComboBox с данными из другой таблицы modExtra 1
    Андрей Копп 08 сентября 2017, 20:24
    Всевозможная очистка кэша в Laravel 5 1