Мультиязычный сайт на Yii: Перевод контента моделей
Это продолжение статьи Мультиязычный сайт на Yii: Интерфейс и URL, в которой мы рассмотрели способы указания языка в URL адресе страницы, переопределив всего два метода пары стандартных компонентов Yii, и использование многоязычности. В этой части мы коснёмся непосредственно перевода текста наших динамических страниц и статей.
Мультиязычные модели
В простейшем случае у нас имеется одноязычная модель Post
:
/** * @property integer $id * @property string $title * @property string $text * @property string $image * @property integer $public */ class Post extends CActiveRecord { public static function model($className=__CLASS__) { return parent::model($className); } public function tableName() { return '{{post}}'; } public function rules() { return array( array('title, text', 'required'), array('title', 'length', 'max'=>'255'), array('public', 'numerical', 'integerOnly'=>true), array('image', 'file', 'types'=>'jpg,jpeg,gif,png', 'allowEmpty'=>true, 'safe'=>false), array('id, title, text, public', 'safe', 'on'=>'search'), ); } public function attributeLabels() { return array( 'id' => 'ID', 'title' => 'Заголовок', 'text' => 'Текст', 'image' => 'Изображение', 'public' => 'Опубликовано', ); } }
и связанная с ней таблица в базе данных.
Рассмотрим пару способов, как мы можем хранить несколько переводов контента на каждом языке.
Хранение вариантов перевода в одной таблице
Описанный в официальном рецепте способ не очень удачный, так как каждый перевод должен представлять из себя отдельную запись в таблице, то есть вместо одного поста нужно создавать три с одинаковыми изображениями, параметрами, но с разными текстами для трёх языков. Причём у этих трёх постов будет разный id
и будет большая избыточность повторяющейся информации из других полей.
Чтобы избежать такой избыточности, можно просто добавить в модель поля для разных языков:
/** * @property integer $id * @property string $title_ru * @property string $title_en * @property string $title_de * @property string $text_ru * @property string $text_en * @property string $text_de * @property string $image * @property integer $public */ class Post extends CActiveRecord { // ... public function rules() { return array( array('title, text', 'safe'), array('title_ru, title_en, title_de', 'required'), array('text_ru, text_en, text_de', 'required'), array('title_ru, title_en, title_de', 'length', 'max'=>'255'), array('public', 'numerical', 'integerOnly'=>true), array('id, title, text, public', 'safe', 'on'=>'search'), ); } public function attributeLabels() { return array( 'id' => 'ID', 'title' => Yii::t('BlogModule.blog', 'Title'), 'text' => Yii::t('BlogModule.blog', 'Text'), 'image' => Yii::t('BlogModule.blog', 'Image'), 'public' => Yii::t('BlogModule.blog', 'Public'), ); } public function search() { $criteria = new CDbCriteria; $criteria->compare('t.id', $this->id); $criteria->compare('t.title_ru', $this->title_ru, true); $criteria->compare('t.title_en', $this->title_en, true); $criteria->compare('t.title_de', $this->title_de, true); $criteria->compare('t.text_ru', $this->text_ru, true); $criteria->compare('t.text_en', $this->text_en, true); $criteria->compare('t.text_de', $this->text_de, true); $criteria->compare('t.public', $this->public); return new CActiveDataProvider($this, array( 'criteria' => $criteria; )); } public function getTitle() { $attribute = 'title_' . Yii::app()->getLanguage(); return $this->{$attribute}; } public function setTitle($value) { $attribute = 'title_' . Yii::app()->getLanguage(); $this->{$attribute} = $value; } public function getText() { $attribute = 'text_' . Yii::app()->getLanguage(); return $this->{$attribute}; } public function setText($value) { $attribute = 'text_' . Yii::app()->getLanguage(); $this->{$attribute} = $value; } }
Как видно, мы добавили геттеры и сеттеры для доступа к нашим мультиязычным атрибутам. Теперь неизменный код в представлении
<h1><?php echo CHtml::encode($model->title); </h1>
обратится к геттеру и выведет нам заголовок на текущем языке. Также без изменений на каждом текущем языке будет работать, например, вывод в гриде, поиск.
В форме редактирования записи теперь необходимо указывать по несколько полей для каждого языка. Мы сделаем это в цикле:
<?php foreach (Yii::app()->params['translatedLanguages'] as $key=>$lang): <div class="row"> echo $form->labelEx($model, 'title'); echo $lang; <br /> echo $form->textField($model, 'title_' . $key, array('maxlength'=>255)); <br /> echo $form->error($model, 'title_' . $key); </div> endforeach;
Теперь чтобы добавить ещё один язык нужно дописать новые поля в таблицы и в модели.
Хранение переводов в отдельной таблице
В предыдущем примере есть небольшая проблема. Она заключается в том, что поля для языков нужно прописывать вручную, и поэтому приложение трудно настраивать. Чтобы добавить новый язык или удалить лишний, нужно исправлять код всех моделей.
От нашего примера с геттерами и сеттерами для каждого свойства
public function getTitle() { $attribute = 'title_' . Yii::app()->getLanguage(); return $this->{$attribute}; } public function setTitle($value) { $attribute = 'title_' . Yii::app()->getLanguage(); $this->{$attribute} = $value; }
можно перейти к универсальным магическим методам __get
и __set
, через которые работать с переводами.
Также можно таким образом вообще перенести переводы в отдельную таблицу. Впоследствии полученную реализацию можно выделить в отдельное поведение.
Придумывать своё мы не будем, так как оно уже есть. Это MultilingualBehavior. Именно на его основе мы дали имена нашим параметрам translatedLanguages
и defaultLanguage
.
Достаточно перевести вашу таблицу {{post}}
из MyISAM в InnoDB и создать таблицу {{post_lang}}
с полями l_title
и l_text
для хранения переводов:
CREATE TABLE IF NOT EXISTS post_lang ( l_id int(11) NOT NULL AUTO_INCREMENT, owner_id int(11) NOT NULL, lang_id varchar(6) NOT NULL, l_title varchar(255) NOT NULL, l_text TEXT NOT NULL, PRIMARY KEY (l_id), KEY owner_id (owner_id), KEY lang_id (lang_id), CONSTRAINT post_lang_owner FOREIGN KEY (owner_id) REFERENCES post (id) ON DELETE CASCADE ON UPDATE CASCADE; ) ENGINE=InnoDB DEFAULT CHARSET=utf8;
Теперь нужно подключить поведение к модели, подправить метод search()
и добавить метод defaultScope()
:
/** * @property integer $id * @property string $title * @property string $text * @property string $image * @property integer $public */ class Post extends CActiveRecord { // ... public function rules() { return array( array('title, text', 'required'), array('title', 'length', 'max'=>'255'), array('public', 'numerical', 'integerOnly'=>true), array('image', 'file', 'types'=>'jpg,jpeg,gif,png', 'allowEmpty'=>true, 'safe'=>false), array('id, title, text, public', 'safe', 'on'=>'search'), ); } public function search() { $criteria = new CDbCriteria; $criteria->compare('t.id', $this->id); $criteria->compare('t.title', $this->title, true); $criteria->compare('t.text', $this->text, true); $criteria->compare('t.public', $this->public); return new CActiveDataProvider($this, array( 'criteria' => $this->ml->modifySearchCriteria($criteria), )); } public function behaviors() { return array( 'ml' => array( 'class' => 'ext.multilangual.MultilingualBehavior', 'localizedAttributes' => array( 'title', 'text', ), 'langClassName' => 'PostLang', 'langTableName' => '{{post_lang}}', 'languages' => Yii::app()->params['translatedLanguages'], 'defaultLanguage' => Yii::app()->params['defaultLanguage'], 'langForeignKey' => 'owner_id', 'dynamicLangClass' => true, ), ); } public function defaultScope() { return $this->ml->localizedCriteria(); } }
Теперь стандартный вызов
$model = Post::model()->findByPk($id); echo $model->title;
через геттер поведения будет возвращать для этой модели значение поля l_title
, у которого lang_id
будет равно значению Yii::app()->language
.
При указании
'dynamicLangClass' => true,
по умолчанию поведение само создаст полупустой класс-модель PostLang
для последующей работы. Но иногда бывают ситуации, что этот класс нужно изменить.
Например, если вы фильтруете HTML код или конвертируете исходный текст формата Markdown в HTML в методе beforeSave()
модели, то такой же конвертор нужно вставить и внутрь модели PostLang
. Вот для примера модель, использующая Markdown конвертер из поведения DPurifyTextBehavior для обработки контента из поля text
в поле text_markdown
в момент сохранения записи:
/** * @property integer $id * @property string $title * @property string $text * @property string $text_markdown * @property string $image * @property integer $public */ class Post extends CActiveRecord { // ... public function behaviors() { return array( 'PurifyText'=>array( 'class'=>'DPurifyTextBehavior', 'sourceAttribute'=>'text', 'destinationAttribute'=>'text_markdown', 'enableMarkdown'=>true, ), 'ml' => array( 'class' => 'ext.multilangual.MultilingualBehavior', 'localizedAttributes' => array( 'title', 'text', 'text_markdown', ), // ... 'dynamicLangClass' => false, ), ); } }
В таком случае нужно указать
'dynamicLangClass' => false,
добавить поле l_text_markdown
в таблицу переводов и самому вручную создать класс PostLang
с таким же поведением, для аналогичных полей:
class PostLang extends CActiveRecord { public static function model($className=__CLASS__) { return parent::model($className); } public function tableName() { return '{{post_lang}}'; } public function relations() { return array('BlogPost' => array(self::BELONGS_TO, 'Post', 'owner_id')); } public function behaviors() { return array( 'PurifyText'=>array( 'class'=>'DPurifyTextBehavior', 'sourceAttribute'=>'l_text', 'destinationAttribute'=>'l_text_markdown', 'enableMarkdown'=>true, ), ); } }
Теперь будет конвертироваться тексты на каждом языке.
В форме редактирования записи в панели управления нужно использовать модернизированную загрузку модели:
$model = Post::model()->multilang()->findByPk($id);
И выводить по несколько полей для каждого языка таже в цикле:
<?php foreach (Yii::app()->params['translatedLanguages'] as $l => $lang) : if($l === Yii::app()->params['defaultLanguage']) $suffix = ''; else $suffix = '_' . $l; <fieldset> <div class="row"> echo $form->labelEx($model, 'title'); echo $lang; echo $form->textField($model, 'title' . $suffix, array('size'=>60, 'maxlength'=>255)); echo $form->error($model, 'title' . $suffix); </div> </fieldset> endforeach;
Для языка по умолчанию (то есть в нашем случае для ru
) суффикс не нужен. Иначе бы поле title
не прошло валидацию required
.
Чтобы не копировать из формы в форму данную конструкцию мы как раз и написали тот «лишний» метод suffixList
в хэлпере DMultilangHelper
. Он как раз возвращает нам такой список суффиксов с именами языков:
<?php foreach (DMultilangHelper::suffixList() as $suffix => $lang) : <div class="row"> echo $form->labelEx($model, 'title'); echo $lang; <br /> echo $form->textField($model, 'title' . $suffix, array('size'=>60, 'maxlength'=>255)); <br /> echo $form->error($model, 'title' . $suffix); </div> endforeach;
Для изучения поведения можно обратиться к его автору или посмотреть исходный код.
UPD: Для Yii2 вышло аналогичное расширение yii2-multilingual-behavior.
Спасибо за интересные статьи!
Скажите не планируете ли вы рассказать нам как вы работаете с изображениями? Я имею в виду когда под изображения отводится отдельная таблица, а не когда на картинку одно поле в таблице.
Про изображения планировал написать. Про отдельную таблицу учту.
Привет! Присматривался к этому поведению, но всё никак не решался.
Скажи, пожалуйста, если ли возможность не дублировать данные из языка по-умолчанию в основную таблицу
tbl_post - content, title === tbl_post_lang - l_content, l_title при lang = defaultLanguage
Если невозможно отказаться от полей в tbl_post (content, title), то есть ли возможность хотя бы не заносить туда значения?
Так как поведение записывает языки после сохранения основной записи, то можно добавить обработку afterSave() в модель:
Здесь мы даём поведению отработать в parent::afterSave(), и сразу же за ним стираем значения из главной таблицы.
Мне нравятся ваши статьи. Не переставайте писать
Спасибо.
Это все конечно хорошо, спасибо автору, даже было сам кинулся делать... Но что если у меня на сайте 20 языков? Даже с 5-7 будет запара сделать как Вы описали, может легче добавить поле lang в каждую запись?
Можно и так. В обоих случаях число языков может быть любым.
Присматриваюсь к решению, приблизительно такое и надо, не знаю как буду делать, в с yii работаю не долго. Спасибо познавательно.
По поводу возможности реализовать 20 (да хоть 200) языков на сайте, вариант автора более гибкий и правильный, с точки зрения РБД.
Но тут все зависит от задачи, если у вас контент на разных языковых версиях будет разный - тогда и вариант с полем lang подойдет в таблице постов, и будет проще.
Если логика для всех языков будет идентична, т.е. нужно просто добавить перевод к существующему материалу, то вариант автора лучше.
Дмитрий, спасибо за такие качественные статьи.
С помощью ваших статей я смог разобраться более глубоко в Yii.
Не могли бы вы мне подсказать.
Можно ли динамически определить язык по умолчанию для данного расшиерення.
Например есть у меня дополнительное поле language
Но так не работает если я изменяю динамически свойство $language, например при добавлении записи. Что посоветуете для динамического изминения языка?
Посоветую переподключить поведение динамически сразу после того, как сменили язык. Например в beforeSave:
Спасибо. Работает
Кстате поведение старое и выдает ошибки, готовьтесь их исправлять. Предупрежден - значит вооружен.
Дмитрий, скажите, а нельзя-ли применить для поддержки мультиязычности "группы условий по умолчанию"?
Может в этой методике есть какие-то "подводные камни", которых я пока не вижу?
Методика описана тут:
http://www.yiiframework.com/doc/guide/1.1/ru/database.ar
там в самом низу, буквально пол страницы.
уж больно хочется как-то попроще :)
Можно. Как здесь. Но тогда надо будет обдумать одновременное использование общих изображений и других общих полей. Да и ID записей для разных языков будут разными, что затруднит создание переключения языка.
Добрый день.
Не подскажите как правильно сделать выборку данных в DataProvider для конкретного языка.
То есть я хочу получить только те записи которые соответствуют выбраному языку а далее передать dataprovider в CListView.
Поведение подключает новое отношение. Можно обращаться через него прямо к языковой таблице:
Очень полезные статьи, спасибо!
Если бы еще избавиться полностью от переводимых полей в основной таблице, было бы вообще замечательно...
Дмитрий, как думаете, если на сайте планируется использовать только два языка (русский и английский), стоит ли выносить в отдельную таблицу переводы? Записей по идее будет много, хотелось бы оптимизировать все что можно...
Тогда проще как во втором случае добавить по два поля title_ru, title_en, text_ru и text_en и геттеры getTitle и getText.
А CDbMessageSource не подходит для этих целей? Что-то я так и не понял как он работает...
Варинт с title_ru, title_en - как-то не по душе... во первых таблицы будут большие, во вторых, подумал еще и понял, что все таки хочется расширяемости.. это такое дело, сегодня 2 языка надо, а завтра еще 3 надо будет добавлять:)
Еще у меня сейчас в голове крутится такой вариант: сделать так же как с переводом элементов интерфейса, т.е. выбрать sourceLanguage для контента в бд, и его хранить в основной таблице. Для остальных языков использовать вторую таблицу с переводами. Например, если задали основной язык en а дополнительный ru, то при использовании английской версии сайта - вообще не делать запрос во вторую таблицу... Сложно такое реализовать?
Можно любым вариантом, лишь бы в if-ах не заблудиться.
Здравствуйте!
Для сайта решила использовать подход с использованием MultilingualBehavior. Однако сразу же столкнулась с проблемой:
"Таблица "{{newsLang}}", упомянутая в записи active record класса "NewsLang", не найдена в базе данных.
/framework/db/ar/CActiveRecord.php(2312)"
В $tableName попадает news{{newsLang}}
Почему так происходит?
Может ли быть проблемой то, что класс Post наследуется не от CActiveRecord, а от другого класса, который наследует CActiveRecord, должен ли в таком случае класс PostLang наследоваться от того же класса, что и основная модель?
В параметрах поведения при подключении укажите:
и создайте эту таблицу
Я назвала таблицу newsLang и она подключена в поведении.
Проблема решилась когда я создала свой класс для NewsLang, который наследует мой переопределенный класс CActiveRecord. Правда, еще не добилась корректного сохранения данные. Но, видимо, это тоже нужно подстраивать под мой класс...
У меня была такая же ошибка как у Валерии.
Полечилось, когда вместо
написал:
То есть без фигурных скобок.
Хотя tableName со скобками (я использую префикс)
Почему такое происходит с названием таблицы?
Можно заглянуть в код поведения:
Видим, что оно само добавляет {{ и }}.
Спасибо!
Значит можно смело писать без скобок: 'langTableName' => 'post_lang'
Прошу прощения, не могли бы Вы объяснить как происходит валидация при хранении переводов в одной таблице?
В методе поведения attach добавляются правила валидации для полей-переводов.Однако, если прописываешь в правилах не required, а, допустим unique, то появляются ошибки. Он проверяет эти поля как поля модели и не находит их в бд.
Возможно ли вообще применение других правил валидации к полям основной таблицы и полям перевода в рамках поведения MultilingualBehavior?
Можно для unique попробовать указать нужный className. Или вписать правила в метод rules() самой модели PostLang.
А для одной таблицы можно явно указать правила для text, text_ru, text_en и text_de, как там и указано:
Здравствуйте!
Большое спасибо за ваш труд!
Решил использовать MultilingualBehavior для своего сайта но столкнулся с проблемой.
У меня 3 языка en,ru,fr
таблицы Post и PostLang
Вопрос как сделать поиск по поле title
Пробовал
но выдает ошибку если язык не равен языку по умолчанию, говорит что title_ru не принадлежит Post
Подскажите пожалуйста как это реализовать, какие параметры надо использовать а лучше всего пример поиска по полей-переводов
Можно попробовать по аналогии с кодом метода search():
Спасибо большое за быстрый ответ!
Чуть покопавшись в код Multilangualbehavior-a и после долгих дебагов нашел в нем ошибочку, так как я использую Postgresql базу данных изменил строку 259 на следующую (просто изменил обычные ковычки на двойные):
и в контролере использовал следующий код
все работает прекрасно.
Спасибо за подсказку с CDbCriteria.
Доброго времени суток.
Здесь уже поднималась тема о том что неплохо бы избежать дублирования в таблице переводов с основной таблицей. Мне наиболее простым и удачным кажется вариант когда мы просто не делаем перевод для основного языка. Что бы добиться этого достаточно в параметре "languages" при инициализации behavior-a исключить "defaultLanguage". Можно сделать базовый класс для всех наших моделей и в нем определить метод который будет возвращать нам правильный список языков
далее наша модель
вот и все :) ... поверхностно протестировал, все работает, если в чем то не прав, пожалуйста укажите.
Достойное решение. Спасибо!
у меня при создание таблицы выдает ошибку
#1064 - You have an error in your SQL syntax; check the manual that corresponds to your MySQL server version for the right syntax to use near 'CONSTRAINT `category_lang_owner` FOREIGN KEY (`owner_id`) REFERENCES `category` ' at line 10
даже если просто скопировать, из текста, тоже ошибка
Что-то в синтаксисе. Попробуйте тогда добавить ограничение отдельным запросом ALTER TABLE, как предложено на странице расширения по ссылке.
Дмитрий, спасибо за статью.
Возник вопрос, если у статьи(или любого другого параметра) нет перевода, то при смене языка будет выводиться оригинальный текст, т.е. меняем язык например на английский, если нет перевода видим русский вариант.
Хотелось бы, чтобы контент в таком случае совсем не выводился.
Выше Вы писали пример решения для конкретной модели:
Может есть что-то более универсальное, чтобы не прописывать каждый раз доп условие.
Предполагаю, что это можно добавить в defaultScope().
У вас в каждом коде куча ошибок, пишите на коленке? ни разу еще не завелось с первого раза
Да, именно так. Кое-что копирую из работающих и проверенных скриптов, а остальное пишу из головы, с лёту и не проверяя. Потом читатели находят ошибки и опечатки и указывают в комментариях, где что не так и как это подправить. Остальное не исправляю.
Спасибо за замечание!
Наткнулся на довольно неожиданную ошибку.
Update - записи работает как часы, но при попытке создать новую запись на этапе валидации выскакивает ошибка: "Не определено свойство "Post.name_en". Вернее эта ошибка выскакивает каждый раз когда у модели созданной через "new" метод "__get()" пытается получить свойство с переводом (name_en).
Подскажите пожалуйста что я упустил.
Решение оказалось таким же неожиданным как и сама ошибка :)
Дело в сценариях, для создания новой записи я в модели "Post" определил сценарий "create".
и новую модель создавал с этим сценарием "$model = Post("create");"
Так вот у "MultilingualBehavior" сценарий по умолчанию "insert", если сценарии бихевора и модели отличаются бихевор работать не будет.
Решается просто: в конфигах добавьте параметр "createScenario" и установите в нужное вам значение.
Дмитрий, спасибо за интересную и познавательную статью.
Дмитрий, присоединяюсь к просьбе написанной ранее в этих комментах, если у вас будет желание и время, напишите статью о работе с изображениями.
Автор, в сёрче у модели внутри массива убери точку с запятой, пожалуйста.
Так же в behaviors() лишняя скобка.
А, вообще, совет: проверь код чем-нибудь с проверкой php-синтаксиса, т.к. некоторые малоопытные кодеры пишут код в блокноте и без подсветки (сам таким был). Получается, что не проверив синтаксис и доверяясь сенсеям, у новичков сыпятся баги. Они плоюют на твой урок (или на Yii или даже на PHP) и идут качать вордпрессы :)
Спасибо! Исправил.
Они плоюют на твой урок (или на Yii или даже на PHP) и идут качать вордпрессы :)
Но, согласитесь, в этом есть и здравое зерно, раз уж такого программиста способна загнать в ступор лишняя запятая и он не способен расшифровать текст ошибки. Своеобразный естественный отбор :)
Отчасти согласен. Но, если быть чуточку дальновиднее, то положившись на естественный отбор и не попытавшись взрастить качественного кодера, мы получаем оголтелую толпу «вебмастеров» с корявыми «вордпрессами». И с этими поделками, через какое-то время, к нам идут наши заказчики. И просят таки их дорабатывать! А мы не можем ему отказать - заказчик постоянный и у тебя на поддержке не один его сайт :)
Дмитрий, подскажите, а как быть с теми записями для которых нет перевода в первом способе? У меня они выводятся с пустыми значениями, а хотелось бы что бы если отсутствует перевод - выводить на языке по умолчанию (например на русском).
Спасибо огромное! Может есть смысл это решение указать в Вашем первом способе, дабы такие как я подобных вопросов не задавали?
Вышло портирование на Yii2 multilingual-behavior ( https://github.com/OmgDef/yii2-multilingual-behavior ). Дмитрий, может напишите дополнение к этой статье?
Дополнил. Спасибо!
возникла ошибка "Таблица ... .упомянутая в записи active record класса не найдена в базе данных"
Таблицы в базе были без префиксов. Нужно было в конфиге добавить 'tablePrefix' => '',
Дмитрий, здравствуйте! Снова нуждаюсь вашей помощи. Я пишу проект на Yii2.
Есть две таблицы "Название меню" и "Пункты меню" связаны один ко многим. Форму генерировал с помощью CRUD. В папке view есть файл _form.php
Поле: <?= $form->field($model, 'name_menu')->textInput(['maxlength' => true]) ?> с первой таблицы,
а поле: <?= $form->field($model, 'name')->textInput();?> уже со второй таблицы, дописал сам. Всё хорошо выводится, редактируется.
Проблема в том, как вывести все значения "$model, 'name'" со второй таблицы (которая связана с первичной связью один ко многим)?
Одно меню может иметь несколько пунктов. Как вывести для редактирования все пункты меню?
Здравствуйте Дмитрий! Не могли бы вы показать пример по пойску в таблице переводов в
Yii2-multilingual-behavior.
Ести таблицы:
Вопрос как оргонизовать поиск по атрибуту "slug" используя Yii2-multilingual-behavior
Заранее спосибо!
Добрый день.
Подскажите плиз. Как настроить валидацию для полей с переводами?
Добавление правил валидации в модель переводов результата не дает.
Добрый день.
Валидация полей в таблице переводов. Может я до конца не разобрался, но после выполнения всех инструкций поля из таблице переводов не валидировались. Поэтому было сделано следующее.
Наследуемся от MultilingualBehavior, переопределяем метод afterSave и добавляем метод afterValidate
настраиваем модели
Теперь все валидируеться с указанием языка поля.
P.S.
Если есть другое решение этой задачи буду рад ознакомиться.
Спасибо за статью, отлично расписано.
Но вот у меня такая проблема как автоматизировать вывод в CDetailView
получается тут title выводится по умолчанию языка, а возможно ли что бы оно выводило к примеру и title_ru и title_en без явного указания их в attributes?
Вроде нельзя. Заполните attributes также циклом.
Хотел бы предложить и свой модуль https://github.com/LAV45/yii2-translated-behavior для Yii2