Введение
Laravel — сам по себе классный фреймворк PHP. У него есть свои плюсы и минусы. У меня в компании используется laravel почти на всех проектах компании. В большинстве случаях в качестве административной панели используется laravel nova. И всё бы ничего если бы на одном из проектов, заказчик не захотел локализацию всей админки для модераторов из разных стран, с возможностью добавления языков.
![laravel nova icon laravel nova icon](https://habrastorage.org/getpro/habr/upload_files/a8d/db5/329/a8ddb532950e6e77cdb25b436b3970d5.png)
Основные проблемы
Задавшись этой сложной задачей передо мной встало несколько проблем:
-
Добавления языков
-
Хранения данных полей админ панели
-
Переключатель языков
-
И последняя проблема появившиеся в конце всего процесса — СЕССИИ Laravel nova.
Добавление языков
Самое простое в локализации laravel nova это хранение языков. Так мне необходимо было создать хранилище языков с возможностью добавления. Я создал таблицу для хранения и ресурс nova.
public function up() { Schema::create('languages', function (Blueprint $table) { $table->id(); $table->string('key_lang', 255); $table->string('title', 255); $table->timestamps(); }); } //миграция таблицы языков
public function fields(Request $request) { return [ ID::make(__('ID'), 'id')->sortable(), Text::make(__("LanguageKey"), 'key_lang') ->rules('required') ->sortable(), Text::make(__("Title"), 'title') ->rules('required') ->sortable() ]; } //ресурс nova языков
![](https://habrastorage.org/getpro/habr/upload_files/181/e35/e53/181e35e53b164976122ec484a7622cdc.png)
Хранения данных полей админ панели
Так все основные поля nova хранятся в директории resources/lang/vendor/nova в json файлах имеющих в качестве названия ключ языка. Мне пришла идея создать новую таблицу для хранения данных локализации и создать observer который будет брать существующий файл json laravel и дополнять его информацией при сохранении с таблицы.
Для того чтобы хранить данные и в БД, я использовал json поле. Таким образом миграция приняла следующий вид:
public function up() { Schema::create('admin_panels', function (Blueprint $table) { $table->id(); $table->foreignId('key_lang_id')->unique()->constrained('languages'); $table->json('content'); $table->timestamps(); }); } //таблица хранения полей для административной панели
Для того чтобы для каждого поля не писать в ресурсе для каждого необходимого поля свое поле для отображения, я решил собрать все необходимые мне ключи локализации в константу массива, с генерировать текстовые поля в массиве и поместить их в поле armincms/json (https://github.com/armincms/json).
protected static $adminField = [ 'LoggingProfileChanges', 'ChangedByWhom', 'ListOfChanges' ...... // Все ключи локализации ]; public function fields(Request $request) { foreach (self::$adminField as $field) { $res[] = Text::make(__($field), $field) ->rules('required') ->hideFromIndex(); } $result = [ ID::make(__('ID'), 'id')->sortable(), BelongsTo::make(__("LanguageKey"), 'key_lang', Language::class) ->searchable(true) ->creationRules('unique:admin_panels,key_lang_id') ->updateRules('unique:admin_panels,key_lang_id,{{resourceId}}') ->sortable(), Json::make("content", $res) ]; return $result; }
После того как всё это проделано оставалось настроить сохранение в json файлы nova используя observer. Написав рекурсивную замену значений json файла я добился успеха.
public function created(AdminPanel $adminPanel) { $data = $adminPanel->getAttributes(); $lang = Language::find($data['key_lang_id']); $path = resource_path('/lang/vendor/nova/'.$lang->key_lang.'.json'); if (file_exists($path)) { $file = file_get_contents($path); $original = json_decode($file, True); $original = array_replace_recursive($original, json_decode($data['content'], True)); $handle = fopen($path, 'w+'); fputs($handle, json_encode($original)); fclose($handle); } else { $handle = fopen($path, 'w+'); fputs($handle, $data['content']); fclose($handle); } } public function updated(AdminPanel $adminPanel) { $idLang = $adminPanel->getOriginal('key_lang_id'); $data = $adminPanel->getChanges(); if (isset($data['key_lang_id'])) { $lang = Language::find($data['key_lang_id']); } else { $lang = Language::find($idLang); } $path = resource_path('/lang/vendor/nova/'.$lang->key_lang.'.json'); $file = file_get_contents($path); $original = json_decode($file, True); $original = array_replace_recursive($original, json_decode($data['content'], True)); $handle = fopen($path, 'w+'); fputs($handle, json_encode($original)); fclose($handle); }
Сессии и переключатель языков
Подключение переключателя языков по началу казалось лёгкой задачей так, как я думал можно взять готовый переключаетель с laravel nova packages, но я ошибался. В большестве случаев, либо они не работали на текущей версии laravel nova(v3.23.2 )
, либо сталкивался с проблемой того что языки брались из конфига, и даже при попытке добавить туда язык нужно было чистить кэш, что мне вовсе не подходило так как всё должно происходить автоматически.
В конечном итиоге пришёл к выводу что нужно делать свой переключатель. Сделав его и залив на сервер, появилась главная проблема — отсутсвие сессий в laravel nova. И тогда я уже думал что это провал. До демо перед заказкчиком было 3 дня. Пытаясь найти решение на протяжении всего дня, пришел к выводу что нужно как то прикручивать сессии вручную. Для меня это было сложной задачей так опыта в написании на laravel меньше пол года.
Посидев над проблемой два дня мне всё таки удалось прикрутить сессии и настроить шаблон resources/views/vendor/nova/partials/user.blade.php
user.blade.php
<dropdown-trigger class="h-9 flex items-center"> @isset($user->email) <img src="https://secure.gravatar.com/avatar/{{ md5(\Illuminate\Support\Str::lower($user->email)) }}?size=512" class="rounded-full w-8 h-8 mr-3" /> @endisset <span class="text-90"> {{ $user->name ?? $user->email ?? __('Nova User') }} </span> </dropdown-trigger> <dropdown-menu slot="menu" width="200" direction="rtl"> <ul class="list-reset"> @foreach (App\Http\Controllers\LanguageController::getLangs() as $lang => $language) @if ($lang != Illuminate\Support\Facades\App::getLocale()) <li> <a class="block no-underline text-90 hover:bg-30 p-3" href="{{ route('lang.switch', $lang) }}"> {{$language}}</a> </li> @endif @endforeach </ul> <ul class="list-reset"> <li> <a href="{{ route('nova.logout') }}" class="block no-underline text-90 hover:bg-30 p-3"> {{ __('Logout') }} </a> </li> </ul> </dropdown-menu>
LanguageController.php
public function switchLang($lang) { if (array_key_exists($lang, self::getLangs())) { Session::put('applocale', $lang); } return Redirect::back(); } public static function getLangs() { $result = []; $allLang = Language::all(); foreach ($allLang as $lang) { $result[$lang->key_lang] = $lang->title; } return $result; }
LanguageMiddleware.php
public function handle($request, Closure $next) { if (Session()->has('applocale') AND array_key_exists(Session()->get('applocale'), LanguageController::getLangs())) { App::setLocale(Session()->get('applocale')); } else { App::setLocale(config('app.fallback_locale')); } return $next($request); }
В конце нужно добавить класс в Kernel:
protected $middlewareGroups = [ 'web' => [ ... \App\Http\Middleware\Language::class, ], ... ];
Так же можно это использовать заменив получение языков из контроллера, на получение языков из конфига.
Итог
В конечном результате у меня вышел функционал, с добавлением языков и переводов в административную панель и переключением языков админки с сессиями.
ссылка на оригинал статьи https://habr.com/ru/articles/577088/
Добавить комментарий