I’m attaching the MultilingualBehavior I was talking about:
2268
So, imagine you have the models News and Section, where a News belongs to a Section, and also you have created the models NewsLang and SectionLang.
You define the following relations in News:
//News.php
public function relations() {
return array(
'section'=> array(self::BELONGS_TO, 'Section', 'section_id'),
'i18nNews' => array(self::HAS_MANY, 'NewsLang', 'news_id', 'on'=>"i18nNews.lang_id='".Yii::app()->language."'", 'index'=>'lang_id'),
'multilang' => array(self::HAS_MANY, 'NewsLang', 'news_id', 'index' => 'lang_id'),
);
}
And for Section:
//Section.php
public function relations() {
return array(
'i18nSection' => array(self::HAS_MANY, 'SectionLang', 'section_id', 'on'=>"i18nSection.lang_id='".Yii::app()->language."'", 'index'=>'lang_id'),
'multilang' => array(self::HAS_MANY, 'SectionLang', 'section_id', 'index' => 'lang_id'),
);
}
Then you use the behavior as following:
//News.php
public function behaviors() {
return array(
'ml' => array(
'class' => 'application.components.MultilingualBehavior',
'primaryLang' => Yii::app()->params['primaryLang'], //your main language
'languages' => Yii::app()->params['translateLangs'], //your translated languages
'localizedAttributes' => array('title', 'intro', 'content'),
'relName1' => 'i18nNews',
'relName2' => 'multilang',
),
);
}
//Section.php
public function behaviors() {
return array(
'ml' => array(
'class' => 'application.components.MultilingualBehavior',
'primaryLang' => Yii::app()->params['primaryLang'], //your main language
'languages' => Yii::app()->params['translateLangs'], //your translated languages
'localizedAttributes' => array('name'),
'relName1' => 'i18nSection',
'relName2' => 'multilang',
),
);
}
I realize the parameter names relName1 and relName2 are not very self-explanatory: relName1 refers to the relation name used to get attributes in the current language, and relName2 refers to the relation name used to get attributes in all languages (this will mainly be used in the back-end in order to enter translations for a model).
When working with related models, the thing to keep in mind is to give unique names to the translate relations that will be used together (i18nNews and i18nSection in this case), otherwise they will get mixed in the generated query.
Now, you can retrieve translated news with sections, for example with the following scopes:
//Section.php
public function localized() {
$this->getDbCriteria()->mergeWith(array(
'with'=>'i18nSection',
));
return $this;
}
===========================
//News.php
public function localized() {
$this->getDbCriteria()->mergeWith(array(
'with'=>array(
'i18nNews'=>array(),
'section'=>array('scopes'=>array('localized')),
),
));
return $this;
}
In News controller:
$rs = News::model()->localized()->findAll();
In News view:
foreach ($rs as $r) {
echo $r->title; //localized news title
echo $r->section->name; //localized section name
}
NOTE: for the model to be able to find the virtual fields provided by the behavior in its “multilang” mode, you’ll have to add the following to the models (News.php and Section.php in the example):
public function __get($name) {
try { return parent::__get($name); }
catch (CException $e) {
if ($this->hasLangAttribute($name)) return $this->getLangAttribute($name);
else throw $e;
}
}
public function __set($name, $value) {
try { parent::__set($name, $value); }
catch (CException $e) {
if ($this->hasLangAttribute($name)) $this->setLangAttribute($name, $value);
else throw $e;
}
}
The reason is that models currently don’t have a way to check if a missing attribute can be obtained through a behavior’s magic getter/setter. This is also the main reason why I first wrote this as a CActiveRecord subclass instead of a behavior. But I prefer to have it as a behavior, even with this inconvenience.

