Multilingual models

Hi,

I think you should localize the File model and use the relation to access it.

For example :


 $model->file->path_en 

To achieve this you should configure multilang behavior on the File model and retrieve your data using this type of loading (this example is in the page of the behavior):




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

echo $model->articles[0]->content_en;



Hi,

I think your form isn’t exactly as in the example I give in the behavior page.

If the default language on your site is "en", you should have title, title_el, content and content_el fields (and not title_en, title_el, content_en and content_el).

Maybe you removed this part of the example form code :




if($l === Yii::app()->params['defaultLanguage']) $suffix = '';

else $suffix = '_'.$l;



If this is not the case, I’m surprised by your bug because it is a very common use case and I’ve never seen this problem.

Maybe you could post your behavior configuration and the rules definition of your model ?

Thanks

Thank you fredpeaks.

That was the case, I did not configured the params[‘defaultLanguage’] so the $suffix var had always something to get.

Once again, thank you for sharing the class!

Thank you very much fred! However I solved my problem with a different approach…

I felt like experimenting so I modified your code and implemented that feature myself, and now I’m sharing the results, maybe it might help someone or give you some ideas for further development

I added a new property to the behavior


public $localizedRelations;

(I know it’s a very unfortunate name as it also exist a $localizedRelation property, but again, it’s an experiment)

Then I created a little method to assign relations to the dynamic generated translation model




private function createLocalizedRelations($related)

    {

        if(empty($this->localizedRelations)) return;

        

        $owner_relations = $this->getOwner()->relations();


        foreach($this->localizedRelations as $rel_name )

        {   

            $original_rel = $owner_relations[$rel_name];

                                  

            foreach ($related as $model) 

            {        

                switch ($original_rel[0])

                {

                    case CActiveRecord::BELONGS_TO:

                                               

                        $class = CActiveRecord::BELONGS_TO;

  

                        $model->getMetaData()->relations[$rel_name] = 

                                new $class($rel_name, 

                                           $original_rel[1], 

                                           $this->localizedPrefix.$original_rel[2]); 

                   break;

                }

           }           

        }

    }



It obviously works with belongs_to relations only but it shouldn’t be difficult to implement the other relation types as well

This function is called in the afterFind method of the behavior.

I configure the behavior assigning to that property an array with the name of the relations i want to localize




'ml' => array(

 'class' => 'ext.MultilingualBehavior',

                           

  'localizedAttributes' => array('fid', 'link'), 

  'localizedRelations' => array('file'),          

                            

  'languages' => Yii::app()->params['languages'], 

  'defaultLanguage' => Yii::app()->language, 

 )



‘file’ is the name of a BELONGS_TO relation

After that I can access my localized file model with very little effort




<?php print $model->multilangGalleryFile['en']->file->render()?>



What do you think about it?

(I attached the modified file to the post)

Hi all! It’s my first post on Yii forum, so hi all again! :)

I’m using yii by no long time and i finded this extension… thanks to all because it’s fantastic.

I followed all the point for install and use that, but after long tests i still have a problem.

On the create or update of a record, the record for the default language it’s saved in “table” and in “table_lang”

Where i wrong?

Once again, thank you for this fantastic class!

Hi, thanks for a great extension.

I have successfully saved the translated models, but got error when accessing actionAdmin method :




CDbCommand failed to execute the SQL statement: SQLSTATE[42P01]: Undefined table: 7 ERROR: missing FROM-clause entry for table "i18nannualpurchase"

LINE 1: ...nnual_purchase_id"="t"."annual_purchase_id") AND (i18nAnnual...

^. The SQL statement executed was: SELECT COUNT(DISTINCT "t"."annual_purchase_id") FROM "tbl_annual_purchase" "t" LEFT OUTER JOIN "tbl_annual_purchase_lang" "i18nAnnualPurchase" ON ("i18nAnnualPurchase"."annual_purchase_id"="t"."annual_purchase_id") AND (i18nAnnualPurchase.lang_id='en_us') 



This is my actionAdmin :




public function actionAdmin()

{

        $model = new AnnualPurchase('search');

	$model->unsetAttributes();

	if(isset($_GET['AnnualPurchase']))

		$model->attributes = $_GET['AnnualPurchase'];

	$this->render('index', compact('model'));

}



Here is how I apply the extension on my AnnualPurchase model :




public function behaviors()

{

        return array(

		'ml' => array(

			'class' => 'application.components.behaviors.MultilingualBehavior',

			'langClassName' => 'AnnualPurchaseLang',

			'langTableName' => 'tbl_annual_purchase_lang',

			'langForeignKey' => 'annual_purchase_id',

			'langField' => 'lang_id',

			'localizedAttributes' => array('name'),

			'localizedPrefix' => 'l_',

			'languages' => Yii::app()->params['translatedLanguages'],

			'defaultLanguage' => Yii::app()->params['defaultLanguage'],

			'createScenario' => 'add',

			//'localizedRelation' => 'i18nAnnualPurchase',

			//'multilangRelation' => 'multilangAnnualPurchase',

			//'forceOverwrite' => false,

			//'forceDelete' => true,

			//'dynamicLangClass' => true,

		)

	);

}



I am using PostgreSQL 9.1, where am I missing ? Please help Thanks

Error fixed by changing line 259 in the MultilingualBehavior.php into :




$owner->getMetaData()->relations[$this->localizedRelation] = new $class($this->localizedRelation, $this->langClassName, $this->langForeignKey, array('on' => '"'.$this->localizedRelation.'"' . "." . $this->langField . "='" . $lang . "'", 'index' => $this->langField));



NOTE : Adding quotation mark before and after $this->localizedRelation

+1 It could be from useful to really a must have for us too !

We have 3 yii big multilanguage projects :-s on fire.

Hi,

Sorry for the time taken to answer, I’m very busy now.

Are you sure that your fix won’t break anything for the mysql users ?

I don’t want to publish this version if it doesn’t work anymore with mysql…

Maybe I can add an option for postgre users or detect the use of postgre in the main config. If you have any idea, that would be cool, I’m not very familiar with postgre.

If someone could test the fix with mysql and post a feedback, it would be great and I could publish it as fast as possible.

Thank you for your work.

Far better to have a ‘default_language’ flag in the second table. Your solution has the same flaw as that by Fred, its just very bad design.

Thanks for your very constructive post Backslider. The default language is defined in the application configuration, no need to put it in the database.

I’m not a fan of having the translated attributes in the main table but have no time to think about a solution to remove them. And by the way, your making a comment about a post that has been posted a year ago and if you had read the other posts, you could have understand that guillemc’s work is at the origin of the extension so it’s normal that it is the same “very bad design”…

Feel free to post a modified version of the extension with your very good design my programmer lord :).

Hi Fredpeaks, sorry for the late reply. Haven’t tried it with mysql though :D

Hello - I’m using your extension too. Thank you for your contribution!

I’m running into the same problem of

"Property "Students.student_firstname_en" is not defined."

What I am trying to do is fairly simple:





CException


Property "Students.student_lastname_en" is not defined.


/opt/yii-1.1.9.r3527/framework/db/ar/CActiveRecord.php(144)


132      */

133     public function __get($name)

134     {

135         if(isset($this->_attributes[$name]))

136             return $this->_attributes[$name];

137         else if(isset($this->getMetaData()->columns[$name]))

138             return null;

139         else if(isset($this->_related[$name]))

140             return $this->_related[$name];

141         else if(isset($this->getMetaData()->relations[$name]))

142             return $this->getRelated($name);

143         else

144             return parent::__get($name);

145     }

146 

147     /**

148      * PHP setter magic method.

149      * This method is overridden so that AR attributes can be accessed like properties.

150      * @param string $name property name

151      * @param mixed $value property value

152      */

153     public function __set($name,$value)

154     {

155         if($this->setAttribute($name,$value)===false)

156         {

Stack Trace

#0	

+  /opt/yii-1.1.9.r3527/framework/db/ar/CActiveRecord.php(144): CComponent->__get("student_lastname_en")

#1	

+  /opt/yii-1.1.9.r3527/framework/validators/CStringValidator.php(80): CActiveRecord->__get("student_lastname_en")

#2	

+  /opt/yii-1.1.9.r3527/framework/validators/CValidator.php(197): CStringValidator->validateAttribute(Students, "student_lastname_en")

#3	

+  /opt/yii-1.1.9.r3527/framework/base/CModel.php(158): CValidator->validate(Students, null)

#4	

+  /opt/yii-1.1.9.r3527/framework/db/ar/CActiveRecord.php(786): CModel->validate(null)

#5	

–  /var/www/html/dev/ASMiS/protected/models/Students.php(796): CActiveRecord->save()

791         $model = Students::model()->findByPk($id);

792 

793         // Set the secretary id attribute

794         $model->student_secretary_id = $secretary_id;

795 

796         if ($model->save())

797             $result = true;

798 

799         return $result;

800 

801     }

#6	

–  /var/www/html/dev/ASMiS/protected/models/Seccomassignment.php(209): Students->updateSecretary("198", "8")

204             

205             // update the rows matching the specified condition

206             Seccomassignment::model()->updateAll(array('seccomassignment_expired'=>1),'seccomassignment_id <> '.$this->seccomassignment_id.' AND seccomassignment_student_id ='.$this->seccomassignment_student_id);

207             

208             // Update the student record seccom field with this seccomassignment

209             $student = Students::model()->updateSecretary($this->seccomassignment_student_id,$this->seccomassignment_seccom_id);

210 

211         }

212         

213         

214         




Basically, I am retrieving a student record (one that I have just previously saved successfully) using findByPk, changing one of its attributes and then attempting to save it again right off the bat with $model->save().

You mention above that you answered the question in the comments of the url above, but as much as I’ve tried to find the answer to this specific issue in there I couldn’t. Would you please point in the right direction?

Much appreciated!

Hello,

I think you have to retrieve the record using multilang to be able to save it if you use the behavior.


Students::model()->multilang()->findByPk((int) $id)<img src='http://www.yiiframework.com/forum/public/style_emoticons/default/wink.gif' class='bbc_emoticon' alt=';)' />

The multilang fill the entity with all translations and create an attribute for each so you won’t have the error when you’ll save it.

If it works (or not :) ) please post an answer.

Hello,

I have try everything but I have always the error "Property "Category.title_en" is not defined" when I try to save or to the test for the form !

Everything is fine to show the info, but it’s no possible to used it in a form or save.

I try to understand why the MultilingualBehavior to to find fields (title_en) that I don’y have in my model in fact.

Can somebody help me ?

Thanks

View :


    <?php

    $languages= Language::model()->findAll();

    foreach ($languages as $language) {

        if($language->code === Yii::app()->params['defaultLanguage']) $suffix = '';

        else $suffix = '_'.$language->code;

        ?>

        <fieldset>

            <legend><?php echo $language->name; ?></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 } ?>

Model :


<?php


/**

 * This is the model class for table "category".

 *

 * The followings are the available columns in table 'category':

 * @property string $id_category

 * @property string $id_parent

 * @property integer $active

 * @property string $date_add

 * @property string $date_upd

 * @property string $position

 * @property string $name

 * @property string $description

 * @property string $keywords

 */

class Category extends MyActiveRecord

{


    public function behaviors() {

        return array(

            'ml' => array(

                'class' => 'MultilingualBehavior',

                'localizedAttributes' => array('name', 'description', 'keywords'), //attributes of the model to be translated

            ),

        );

    }


    public function defaultScope()

    {

        return $this->ml->localizedCriteria();

    }

	/**

	 * Returns the static model of the specified AR class.

	 * @param string $className active record class name.

	 * @return Category the static model class

	 */

	public static function model($className=__CLASS__)

	{

		return parent::model($className);

	}


	/**

	 * @return string the associated database table name

	 */

	public function tableName()

	{

		return 'category';

	}


	/**

	 * @return array validation rules for model attributes.

	 */

	public function rules()

	{

		// NOTE: you should only define rules for those attributes that

		// will receive user inputs.

        $rules= array(

			array('id_parent, date_add, date_upd', 'required'),

			array('active', 'numerical', 'integerOnly'=>true),

			array('id_parent, position', 'length', 'max'=>10),

			array('name', 'length', 'max'=>128),

			array('keywords', 'length', 'max'=>255),

			array('description', 'safe'),

			// The following rule is used by search().

			// Please remove those attributes that should not be searched.

			array('id, id_parent, active, date_add, date_upd, position, name, description, keywords', 'safe', 'on'=>'search'),

		);


        return $rules;

	}


	/**

	 * @return array relational rules.

	 */

	public function relations()

	{

		// NOTE: you may need to adjust the relation name and the related

		// class name for the relations automatically generated below.

		return array(

            'getparent' => array(self::BELONGS_TO, 'Category', 'id_parent'),

            'childs' => array(self::HAS_MANY, 'Category', 'id_parent', 'order' => 'position ASC'),

            'CategoryLang' => array(self::HAS_MANY, 'categoryLang', 'category_id'),

            'childsCount' => array(self::STAT, 'Category', 'id_parent'),

        );

	}


	/**

	 * @return array customized attribute labels (name=>label)

	 */

	public function attributeLabels()

	{

		return array(

			'id' => 'Id Category',

			'id_parent' => 'Id Parent',

			'active' => 'Active',

			'date_add' => 'Date Add',

			'date_upd' => 'Date Upd',

			'position' => 'Position',

			'name' => 'Name',

			'description' => 'Description',

			'keywords' => 'Keywords',

            'childsCount' => 'Sub-Categories',

		);

	}


	/**

	 * Retrieves a list of models based on the current search/filter conditions.

	 * @return CActiveDataProvider the data provider that can return the models based on the search/filter conditions.

	 */

	public function search($id_parent)

	{

		// Warning: Please modify the following code to remove attributes that

		// should not be searched.


		$criteria=new CDbCriteria;


        $criteria->compare('id',$this->id,false);

		$criteria->compare('id_parent',$id_parent,false);

		$criteria->compare('active',$this->active);

		$criteria->compare('date_add',$this->date_add,true);

		$criteria->compare('date_upd',$this->date_upd,true);

		$criteria->compare('position',$this->position,false);

		$criteria->compare('name',$this->name,true);

		$criteria->compare('description',$this->description,true);

		$criteria->compare('keywords',$this->keywords,true);

        $criteria->order = 'position ASC';


        return new CActiveDataProvider($this, array(

            'criteria'=>$this->ml->modifySearchCriteria($criteria),

            'pagination' => array(

                'pageSize' => 40,

            ),

            'sort'=>array(

                'defaultOrder'=>'position ASC',

            )

        ));

	}


    public function getMaxPosition($id_parent)

    {

        return Yii::app()->db->createCommand()

            ->select('max(position) as maxposition')

            ->from('category')

            ->where('id_parent = ' . $id_parent)

            ->queryScalar();

    }


}

Controller :


    public function actionUpdate()

    {

        $id = $_GET['id'];

        $category = $this->loadModel($id, true);


        $category->date_upd = new CDbExpression('NOW()');


        // if it is ajax validation request

        if(isset($_POST['ajax']) && $_POST['ajax']==='update-form')

        {

            echo CActiveForm::validate($category);

            Yii::app()->end();

        }


        // collect user input data

        if(isset($_POST['Category']))

        {

            $category->attributes=$_POST['Category'];

            // validate user input and redirect to the previous page if valid

            if($category->validate()) {

                $category->save();

                $this->redirect(array('index','id_parent'=>$category->id_parent));

            }

        }

        // display the login form

        $this->render('update',array('model'=>$category));




    }



problem in GridView

I have this 2 models

  1. User (multi language)

  2. UserMessage

In UserMessage model I have:





public function relations()

	{

		return array(

		    'user' => array(self::BELONGS_TO, 'User', 'user_id'),

                    'sendUser' => array(self::BELONGS_TO, 'User', 'send_user_id'),

		);

	}


public function search()

	{

....................


$criteria->with = array('user','sendUser');

		$criteria->compare('user.username',$this->user_search,true);

        $criteria->compare('sendUser.username',$this->sendUser_search,true);


...................

}



problem in gridview "UserMessage":





CDbCommand failed to execute the SQL statement: SQLSTATE[42000]: Syntax error or

 access violation: 1066 Not unique table/alias: 'i18nUser'. The SQL statement 

executed was: SELECT COUNT(DISTINCT `t`.`id`) FROM `user_message` `t` LEFT OUTER

 JOIN `user` `user` ON (`t`.`user_id`=`user`.`id`) LEFT OUTER JOIN `userLang` 

`i18nUser` ON (`i18nUser`.`user_id2`=`user`.`id`) AND (i18nUser.language='en') 

LEFT OUTER JOIN `user` `sendUser` ON (`t`.`send_user_id`=`sendUser`.`id`) LEFT 

OUTER JOIN `userLang` `i18nUser` ON (`i18nUser`.`user_id2`=`sendUser`.`id`) AND

 (i18nUser.language='en') WHERE (t.user_id=2)




Thank you