multilingual-behavior Easily create and handle translated / i18n / multilingual models with this behavior

  1. Requirements
  2. Usage
  3. History
  4. Resources
  5. Authors

Usable for Yii 1.x but not maintained anymore. Feel free to use the code to create a new version. I think it should be hosted on Github (multiple contributors, issues, pull requests...).

This behavior allow you to create multilingual models and to use them (almost) like normal models. For each model, translations have to be stored in a separate table of the database (ex: PostLang or ProductLang), which allow you to easily add or remove a language without modifying your database.

First example: by default translations of current language are inserted in the model as normal attributes.

// Assuming current language is english (in protected/config/main.php : 'sourceLanguage' => 'en')
$model = Post::model()->findByPk((int) $id);
echo $model->title; //echo "English title"

//Now let's imagine current language is french (in protected/config/main.php : 'sourceLanguage' => 'fr')
$model = Post::model()->findByPk((int) $id);
echo $model->title; //echo "Titre en Français"

$model = Post::model()->localized('en')->findByPk((int) $id);
echo $model->title; //echo "English title"

//Here current language is still french

Second example: if you use multilang() in a "find" query, every translation of the model are loaded as virtual attributes (title_en, title_fr, title_de, ...).

$model = Post::model()->multilang()->findByPk((int) $id);
echo $model->title_en; //echo "English title"
echo $model->title_fr; //echo "Titre en Français"

Requirements

Yii 1.1 or above

Usage

Here an example of base 'post' table : ~~~ [sql] CREATE TABLE IF NOT EXISTS post (     id int(11) NOT NULL AUTO_INCREMENT,     title varchar(255) NOT NULL,     content TEXT NOT NULL,     created_at datetime NOT NULL,     updated_at datetime NOT NULL,     enabled tinyint(1) NOT NULL DEFAULT '1',     PRIMARY KEY (id) ) ENGINE=InnoDB DEFAULT CHARSET=utf8; ~~~

And his associated translation table (configured as default), assuming translated fields are 'title' and 'content': ~~~ [sql] CREATE TABLE IF NOT EXISTS postLang (     l_id int(11) NOT NULL AUTO_INCREMENT,     post_id int(11) NOT NULL,     lang_id varchar(6) NOT NULL,     l_title varchar(255) NOT NULL,     l_content TEXT NOT NULL,     PRIMARY KEY (l_id),     KEY post_id (post_id),     KEY lang_id (lang_id) ) ENGINE=InnoDB  DEFAULT CHARSET=utf8;

ALTER TABLE postLang ADD CONSTRAINT postlang_ibfk_1 FOREIGN KEY (post_id) REFERENCES post (id) ON DELETE CASCADE ON UPDATE CASCADE; ~~~

Attach this behavior to the model (Post in the example). Everything that is commented is with default values.

public function behaviors() {
    return array(
        'ml' => array(
            'class' => 'application.models.behaviors.MultilingualBehavior',
            //'langClassName' => 'PostLang',
            //'langTableName' => 'postLang',
            //'langForeignKey' => 'post_id',
            //'langField' => 'lang_id',
            'localizedAttributes' => array('title', 'content'), //attributes of the model to be translated
            //'localizedPrefix' => 'l_',
            'languages' => Yii::app()->params['translatedLanguages'], // array of your translated languages. Example : array('fr' => 'Français', 'en' => 'English')
            'defaultLanguage' => Yii::app()->params['defaultLanguage'], //your main language. Example : 'fr'
            //'createScenario' => 'insert',
            //'localizedRelation' => 'i18nPost',
            //'multilangRelation' => 'multilangPost',
            //'forceOverwrite' => false,
            //'forceDelete' => true, 
            //'dynamicLangClass' => true, //Set to true if you don't want to create a 'PostLang.php' in your models folder
        ),
    );
}

Look into the behavior source code and read the comments of each attribute to understand how to configure them and how to use the behavior.

In order to retrieve translated models by default, add this function in the model class:

public function defaultScope()
{
    return $this->ml->localizedCriteria();
}

You also can modify the loadModel function of your controller to minimize the changes to make in your controller:

public function loadModel($id, $ml=false) {
    if ($ml) {
        $model = Post::model()->multilang()->findByPk((int) $id);
    } else {
        $model = Post::model()->findByPk((int) $id);
    }
    if ($model === null)
        throw new CHttpException(404, 'The requested post does not exist.');
    return $model;
}

and use it like this in the update action :

public function actionUpdate($id) {
    $model = $this->loadModel($id, true);
    ...
}

Here is a very simple example for the form view : 

<?php foreach (Yii::app()->params['translatedLanguages'] as $l => $lang) :
    if($l === Yii::app()->params['defaultLanguage']) $suffix = '';
    else $suffix = '_'.$l;
    ?>
<fieldset>
    <legend><?php echo $lang; ?></legend>
    
    <div class="row">
    <?php echo $form->labelEx($model,'title'); ?>
    <?php echo $form->textField($model,'title'.$suffix,array('size'=>60,'maxlength'=>255)); ?>
    <?php echo $form->error($model,'title'.$suffix); ?>
    </div>

    <div class="row">
    <?php echo $form->labelEx($model,'content'); ?>
    <?php echo $form->textArea($model,'content'.$suffix); ?>
    <?php echo $form->error($model,'content'.$suffix); ?>
    </div>
</fieldset>
<?php endforeach; ?>

To enable search on translated fields, you can modify the search() function in the model like this :

public function search()
{
    $criteria=new CDbCriteria;

    //...
    //here your criteria definition
    //...

    return new CActiveDataProvider($this, array(
        'criteria'=>$this->ml->modifySearchCriteria($criteria),
        //instead of
        //'criteria'=>$criteria,
    ));
}

Warning: the modification of the search criteria is based on a simple str_replace so it may not work properly under certain circumstances.

It's also possible to retrieve languages translation of two or more related models in one query. Example for a Page model with a "articles" HAS_MANY relation : 

$model = Page::model()->multilang()->with('articles', 'articles.multilangArticle')->findByPk((int) $id);
echo $model->articles[0]->content_en;

With this method it's possible to make multi model forms like it's explained here

History

24/03/2012: First release

28/03/2012: It's now possible to modify language when retrieving data with the localized relation.

Example:

$model = Post::model()->localized('en')->findByPk((int) $id);

30/03/2012: Correction for the after save method.

26/04/2012 Modification of the rules definition for translated attributes:

  • if you set forceOverwrite to true, every rules defined in the model for the attributes to translate will be applied to the translations.

  • if you set forceOverwrite to false (default), every rules defined in the model for the attributes to translate will be applied to the translations except "required" rules that will only be applied to the default translation.

28/06/2012 ** Bug fix ** two2wyes found and fixed a bug that prevented translations to be correctly saved on attributes that only have a "required" rule and with the "forceOverwrite" option set to false. Thanks again to him. See the thread

Resources

Authors

Many thanks to guillemc who made the biggest part of the work on this behavior (see original thread).

21 0
30 followers
2 265 downloads
Yii Version: 1.1
License: BSD-2-Clause
Category: Database
Developed by: fredpeaks
Created on: Mar 25, 2012
Last updated: 2 years ago

Downloads

show all

Related Extensions