Rainlab.translate - отличный плагин, но есть проблема с производительностью при использовании с моделями, опишу кратко:
1) Создаем модель Item, используя behaviour плагина rainlab.translate. У нее будет поле name - его будем переводить.
class Item extends Model
{
public $implement = ['RainLab.Translate.Behaviors.TranslatableModel'];
public $translatable = ['name'];
public $table = 'test_items';
protected $fillable = ['name'];
protected $dates = [
'created_at',
'updated_at'
];
}
2) Создаем 1000 записей этой модели, и 1000 записей для перевода (таблица 'rainlab_translate_attributes'). Использую локаль 'en' как дефолтную, 'ru' как дополнительную.
Для всех записей name => test, для локали 'тест':
public function handle()
{
DB::table('test_items')->truncate();
DB::table('rainlab_translate_attributes')->truncate();
DB::transaction(function () {
for ($i = 1; $i <= 1000; $i ++) {
Item::create([
'name' => 'test'
]);
DB::table('rainlab_translate_attributes')->insert([
'id' => $i,
'model_type' => 'Author\Plugin\Models\Item',
'attribute_data' => '{"name":"тест"}',
'model_id' => $i,
'locale' => 'ru'
]);
}
});
}
Создаем компонент, страницу и partial для теста.
Компонент:
public function onRun()
{
$start = microtime(true);
$this->items = Item::all();
$this->renderPartial('test.htm');
echo microtime(true)-$start;
}
Страница:
title = "test"
url = "/test"
[test]
==
Partial:
{% for item in test.items %}
{{ item.lang('ru').name }}
{% endfor %}
Переходим на страницу /test, получаем дополнительные 1000 запросов к базе и время renderPartial 2-4 секунды.
Это все лечится кэшем, поэтому из мухи слона раздувать не буду, но все же стало интересно, как можно сделать это без него. Тут про кэш: https://stackoverflow.com/questions/48518714/high-query-overhead-with-rainlab-translate-for-a-dropdown-menu
По той же ссылке есть и способ с join, но мне он показался чересчур сложным.
Я решил попробовать реализовать перевод через отношения и with. Создал модель Translate, используя таблицу 'rainlab_translate_attributes'.
В модели Item прописал hasOne как в Laravel, т.к. нам нужно добавить некоторую логику в связь. Первый where будет искать только нашу модель Item, второй только нужную нам локаль.
Translaor - это класс в Rainlab.Translate, он использует трейт Singleton, а его статический метод instance дает нам получить тот самый единственный экземпляр. Ну а getLocale возвращает текущую локаль.
public function trans()
{
$translator = Translator::instance();
return $this->hasOne(Translate::class,'model_id')->where('model_type',Item::class)->where('locale',$translator->getLocale());
}
Не используйте имя 'translate' для вашего отношения, оно используется Аксессором:
public function getTranslateAttributes($locale)
{
return array_get($this->translatableAttributes, $locale, []);
}
В компоненте при создании коллекции использую with:
public function onRun()
{
$start = microtime(true);
$this->items = Item::with('trans')->get();
$this->renderPartial('test.htm');
echo microtime(true)-$start;
}
Т.к. значения хранятся в json, нам нужен фильтр для twig с функцией json_decode:
public function registerMarkupTags()
{
return [
'filters' => [
'json_decode' => 'json_decode'
],
];
}
В partial пришлось использовать довольно кривую проверку, но проблема в том, что у нас не хранятся в 'rainlab_translate_attributes' дефолтные name:
{% for item in test.items %}
{% if activeLocale != 'en' %}
{{ (item.trans.attribute_data|json_decode).name }}
{% else %}
{{ item.name }}
{% endif %}
{% endfor %}
Как итог - среднее время 0.4 - 0.5 сек, то есть от 5 до 10 раз быстрее. Чтобы не тянуть лишний раз перевод, когда у нас стоит дефолтный язык, можно добавить проверку на локаль в компоненте.
Если вы используете всего два языка, то еще проще прямо в модели поставить name_ru и name_en, и уже в твиге проверять activeLocale.