Локализация Laravel nova или как сломать себе мозг от желаний заказчика

от автора

Введение

Laravel — сам по себе классный фреймворк PHP. У него есть свои плюсы и минусы. У меня в компании используется laravel почти на всех проектах компании. В большинстве случаях в качестве административной панели используется laravel nova. И всё бы ничего если бы на одном из проектов, заказчик не захотел локализацию всей админки для модераторов из разных стран, с возможностью добавления языков.

laravel nova icon
laravel nova icon

Основные проблемы

Задавшись этой сложной задачей передо мной встало несколько проблем:

  • Добавления языков

  • Хранения данных полей админ панели

  • Переключатель языков

  • И последняя проблема появившиеся в конце всего процесса — СЕССИИ 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 языков

Хранения данных полей админ панели

Так все основные поля 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/


Комментарии

Добавить комментарий

Ваш адрес email не будет опубликован. Обязательные поля помечены *