Yii Framework Forum: Multilingual models - Yii Framework Forum

Jump to content

  • (5 Pages)
  • +
  • 1
  • 2
  • 3
  • 4
  • Last »
  • You cannot start a new topic
  • You cannot reply to this topic

Multilingual models Rate Topic: ***** 4 Votes

#21 User is offline   coma 

  • Junior Member
  • Pip
  • Yii
  • Group: Members
  • Posts: 39
  • Joined: 19-July 10

Posted 02 September 2010 - 03:28 AM

Found the source of the problem.. I was extending the ***lang model from the multilingual extension class (I18nActiveRecord in my case), resolved by changing it back to CActiveRecord :)
0

#22 User is offline   ToolMayNARD 

  • Junior Member
  • Pip
  • Yii
  • Group: Members
  • Posts: 31
  • Joined: 02-August 10

Posted 03 September 2010 - 05:08 AM

Hello,

i'm new to the Yii framework (and to PHP in general...) so I have some problems with it.

BTW I'm using the I18NActiveRecord / MultilingualActiveRecord class because I need to develop a multilingual site, but I have some problems with the fallback language. There is no way to get the "default" language item, if the translated item is missing. What's wrong with my app? Can someone help me?

Thanks.

Mauro
0

#23 User is offline   ToolMayNARD 

  • Junior Member
  • Pip
  • Yii
  • Group: Members
  • Posts: 31
  • Joined: 02-August 10

Posted 09 September 2010 - 02:52 AM

It seems that the problem is use of find() method. If I use findAll, instead, the MultilingualActiveRecord child object returns also the default language if the translation is missing.

Mauro
0

#24 User is offline   Veseliq 

  • Junior Member
  • Pip
  • Yii
  • Group: Members
  • Posts: 31
  • Joined: 21-September 10
  • Location:Sofia, Bulgaria

Posted 07 October 2010 - 09:53 AM

+1 wanting this as native feature!

I'm currently trying to write a behavior that does those things.
0

#25 User is offline   nexflo 

  • Newbie
  • Yii
  • Group: Members
  • Posts: 1
  • Joined: 01-December 10

Posted 11 December 2010 - 01:29 PM

mech7 and coma are you guys sure ure only extending your original model with MultilingualActiveRecord and not the PostLang model?

I also changed the following to get it to work:

public function langClassName() {
return get_class($this).'Lang';
}

public function langForeignKey() {
return strtolower(get_class($this)).'Id';
}


Also a tip:

in the model...try to use global variables like:


public function primaryLang()
{
return Yii::app()->sourceLanguage;
}

//additional languages found in the translations table
public function languages()
{
return Yii::app()->locale->supportedLang;
}

here i made a new component class which derives from the localemanager....so its easier to switch languages etc.
(http://code.google.c...anager.php?r=61)
0

#26 User is offline   Samir Hajiyev 

  • Newbie
  • Yii
  • Group: Members
  • Posts: 4
  • Joined: 30-September 11

Posted 16 November 2011 - 02:15 PM

View PostSlavic, on 01 March 2010 - 04:43 AM, said:

Hi there,

I integrated your Extension yesterday and it's just Great.
Thanks for that.

But I had a Problem with the CActiveDataProvider that is used with the grid view widget.
So I made a smal addition to your Extension. Maybe you can add this class to your next release.

class MultilingualActiveDataProvider extends CActiveDataProvider
{
    public $blnLocalize = true;
    public $blnAll      = false;

    public function __construct($modelClass, $config=array(), $blnLocalize=true, $blnAll=false)
    {
        $this->blnLocalize = $blnLocalize;
        $this->blnAll      = $blnAll;
        
        parent::__construct($modelClass, $config);
    }

    protected function fetchData()
    {
        $criteria=clone $this->getCriteria();
        if(($pagination=$this->getPagination())!==false)
        {
            $pagination->setItemCount($this->getTotalItemCount(true));
            $pagination->applyLimit($criteria);
        }
	if(($sort=$this->getSort())!==false)
	    $sort->applyOrder($criteria);

            if($this->blnLocalize) {
                if($this->blnAll) {
                    return CActiveRecord::model($this->modelClass)->multilingual()->findAll($criteria);
                } else {
                    return CActiveRecord::model($this->modelClass)->localized()->findAll($criteria);
                }
            } else {
                return CActiveRecord::model($this->modelClass)->findAll($criteria);
            }
	}
}


Now I can Use the DataProvider with his pagination with the Multilang extension

$dataProvider=new MultilingualActiveDataProvider('Post', array(
    'criteria'=>array(
        'condition'=>'status=1 AND tags LIKE :tags',
        'params'=>array(':tags'=>$_GET['tags']),
        'with'=>array('author'),
    ),
    'pagination'=>array(
        'pageSize'=>20,
    ),
), true, true);


hope you like it :)


Thanks for your extensions.
I have used it in my sites and it is very cool. ;)
0

#27 User is offline   guillemc 

  • Junior Member
  • Pip
  • Yii
  • Group: Members
  • Posts: 21
  • Joined: 14-October 09

Posted 16 November 2011 - 05:46 PM

Hi, it's been a while since I wrote this extension, and to be honest I used it only on very simple sites.

Recently I've had the opportunity to work on another, more complex multilingual site with Yii and, well, I've realized this extension is not so useful (because it only works on the main model, not on related models, and also because it is annoying to have to subclass CActiveRecord instead of using a behavior).

What I'm doing currently is explicitly creating two relations for each multilingual model, for example, for model News:

public function relations() {
  return array(
    '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'),					
  );
}



(The first one used on the frontend, to retrieve translation in current app language, the second one used on the backend, to obtain translations in all languages.)

Then I'm using a behavior that uses these relations to process attributes in a similar way as the extension did.

The main advantage is that it works for related models as well, for example when you want to retrieve translated News with their translated Categories.

I will post more details, if I manage to polish it a little.
0

#28 User is offline   Samir Hajiyev 

  • Newbie
  • Yii
  • Group: Members
  • Posts: 4
  • Joined: 30-September 11

Posted 22 November 2011 - 07:50 AM

View Postguillemc, on 16 November 2011 - 05:46 PM, said:

I will post more details, if I manage to polish it a little.


I have the same problem with related models. I will wait your post. ;)
0

#29 User is offline   guillemc 

  • Junior Member
  • Pip
  • Yii
  • Group: Members
  • Posts: 21
  • Joined: 14-October 09

Posted 24 November 2011 - 12:17 PM

I'm attaching the MultilingualBehavior I was talking about:

Attached File  MultilingualBehavior.php (3.59K)
Number of downloads: 131

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.

This post has been edited by guillemc: 07 December 2011 - 03:37 AM

4

#30 User is offline   Xuntar 

  • Newbie
  • Yii
  • Group: Members
  • Posts: 18
  • Joined: 19-July 11

Posted 27 November 2011 - 10:22 AM

@guillemc:

Thank you so much for posting this!

Could you just add some information regarding the database structure you use?
In your original extension you describe using 2 tables (one with the main language fields and one with all other languages), but I get the idea you're using all languages (including the fallback one) in the NewsLang table and only general (non-translatable) information (I assume) in the News table.
Is this correct?
0

#31 User is offline   guillemc 

  • Junior Member
  • Pip
  • Yii
  • Group: Members
  • Posts: 21
  • Joined: 14-October 09

Posted 27 November 2011 - 12:36 PM

@Avalon:

No, I'm still using the same database structure (all attributes in the main table, plus translatable attributes in a separate table).

The reason is that it makes it a lot easier to handle missing translations. Having a default or main language allows you to easily fall back to that language in case a translation doesn't exist, without having to make another query.

Also, in my experience it matches the way most multilingual sites work. They might start as single language sites, or maybe already plan to be multilingual, but almost always they start entering content in the main language, and translations are added afterwards.
0

#32 User is offline   Samir Hajiyev 

  • Newbie
  • Yii
  • Group: Members
  • Posts: 4
  • Joined: 30-September 11

Posted 28 November 2011 - 03:21 AM

@guillemc:
Thanks for your post. I use it.

I have other problem with CActiveDataProvider().
Can you help me, how to use this extension or your method with related model.
I need, to retrieve all News data with their Sections (with translations).
Thanks, and i`m sorry for my english.
0

#33 User is offline   Xuntar 

  • Newbie
  • Yii
  • Group: Members
  • Posts: 18
  • Joined: 19-July 11

Posted 28 November 2011 - 05:55 AM

@guillemc:

Thanks for your fast reply. Would there be any way to adapt your behaviour to allow my kind of db scheme? (one table for non-translatable stuff, the other for all languages)
Right now I'm using the translation behaviour created by Schmunk ( github.com/schmunk42/p3extensions/blob/master/behaviors/P3TranslationBehavior.php ), but that doesn't allow translating entire models; only specific fields of a model.

If you have any ideas or pointers on how to combine the best of these both worlds, that would be absolutely great!

View Postguillemc, on 27 November 2011 - 12:36 PM, said:

@Avalon:

No, I'm still using the same database structure (all attributes in the main table, plus translatable attributes in a separate table).

The reason is that it makes it a lot easier to handle missing translations. Having a default or main language allows you to easily fall back to that language in case a translation doesn't exist, without having to make another query.

Also, in my experience it matches the way most multilingual sites work. They might start as single language sites, or maybe already plan to be multilingual, but almost always they start entering content in the main language, and translations are added afterwards.

0

#34 User is offline   guillemc 

  • Junior Member
  • Pip
  • Yii
  • Group: Members
  • Posts: 21
  • Joined: 14-October 09

Posted 28 November 2011 - 02:01 PM

@Samir:

I haven't tried, but CActiveDataProvider takes a CDbCriteria object as a parameter. So, if you pass it a criteria object that selects also the related language tables (using "with" or "scopes", what you prefer) then it all should work. What I mean is that you should pass CActiveDataProvider the same criteria that you pass to a findAll() call in order to get the translations.


@Avalon:

The behavior I'm using expects the main model to have the main attributes defined, so that you can call $news->title, and get the title in the current language. But you could define these placeholder attributes directly in the model, instead of the database. So, in your News model, you'd define the attributes:

public $title;
public $intro;
public $content;
...


This would allow you to continue using $news->title and get the translated title, even if the title field doesn't exist in the news table.

But doing this means you won't have the fallback value. If the title translated in the current language does not exist you will get a blank title...
0

#35 User is offline   Xuntar 

  • Newbie
  • Yii
  • Group: Members
  • Posts: 18
  • Joined: 19-July 11

Posted 29 November 2011 - 04:01 AM

Thanks guillemc, for your help :-)

If I understand correctly, I could fill those attributes up with the current language first, check if they're empty and use a fallback language when necessary. Is there any drawback to this method (except for the extra query)?
I guess it would be better to query both languages at the same time (current and fallback) - and have the code figure out which one to use - or using a query that will select the right values at once?
Something like:
SELECT o.id
     , o.type
     , o.code
     , o.position
     , ifnull(t.name,d.name) name
     , ifnull(t.description,d.description) description
  FROM base_material o
       INNER JOIN base_material_i18n d
               ON ( o.id=d.base_material_id)
       LEFT OUTER JOIN base_material_i18n t
                    ON ( d.base_material_id=t.base_material_id AND t.localization_id='nl' )
 WHERE d.localization_id='en'


View Postguillemc, on 28 November 2011 - 02:01 PM, said:

@Avalon:

The behavior I'm using expects the main model to have the main attributes defined, so that you can call $news->title, and get the title in the current language. But you could define these placeholder attributes directly in the model, instead of the database. So, in your News model, you'd define the attributes:

public $title;
public $intro;
public $content;
...


This would allow you to continue using $news->title and get the translated title, even if the title field doesn't exist in the news table.

But doing this means you won't have the fallback value. If the title translated in the current language does not exist you will get a blank title...

0

#36 User is offline   guillemc 

  • Junior Member
  • Pip
  • Yii
  • Group: Members
  • Posts: 21
  • Joined: 14-October 09

Posted 29 November 2011 - 08:01 AM

I don't think I follow you. You could make a query like that, but the purpose of all this was to avoid having to do it. Just by declaring relations and using a behavior you should be able to get the data in the right language.

What I was trying to say is that if you don't want to have the name and description attributes on the main table, you don't have to. You can declare them as public properties of your model, and let the behavior populate them with the right data coming from the translations table.

But I think you are right in that you could get current and fallback languages in the same query. I'd try to define a relation with the i18n table like this:

'i18n' => array(self::HAS_MANY, 'ModelLang', 'model_id', 'on'=>"i18n.lang_id='".Yii::app()->language."' OR  i18n.lang_id='".Yii::app()->params['defaultLanguage']."'", 'index'=>'lang_id'),



Then, in the behavior's afterFind() method, you would check for those translations, and set the main model attribute to either of the two (the current language if it exists, the fallback otherwise).
0

#37 User is offline   Xuntar 

  • Newbie
  • Yii
  • Group: Members
  • Posts: 18
  • Joined: 19-July 11

Posted 30 November 2011 - 05:22 AM

Don't worry, guillemc, I'm pretty sure I was the one not following you instead of the other way around ;-)

Thanks for the detailed explanation! I'll try it out asap :-)

View Postguillemc, on 29 November 2011 - 08:01 AM, said:

I don't think I follow you. You could make a query like that, but the purpose of all this was to avoid having to do it. Just by declaring relations and using a behavior you should be able to get the data in the right language.

What I was trying to say is that if you don't want to have the name and description attributes on the main table, you don't have to. You can declare them as public properties of your model, and let the behavior populate them with the right data coming from the translations table.

But I think you are right in that you could get current and fallback languages in the same query. I'd try to define a relation with the i18n table like this:

'i18n' => array(self::HAS_MANY, 'ModelLang', 'model_id', 'on'=>"i18n.lang_id='".Yii::app()->language."' OR  i18n.lang_id='".Yii::app()->params['defaultLanguage']."'", 'index'=>'lang_id'),



Then, in the behavior's afterFind() method, you would check for those translations, and set the main model attribute to either of the two (the current language if it exists, the fallback otherwise).

0

#38 User is offline   __agus 

  • Standard Member
  • PipPip
  • Yii
  • Group: Members
  • Posts: 166
  • Joined: 22-April 10
  • Location:Yogyakarta, Indonesia

Posted 07 December 2011 - 01:50 AM

@guillemc
hi i use your behavior and follow your instruction to enable multilingual in content. but i get error, the error says
Relation "i18n" is not defined in active record class "Section".


also i attach my simple test project and screen shoot for the error with this.
could you help me please.

many thanks :)
nb: sorry for my bad english

Attached File(s)


0

#39 User is offline   guillemc 

  • Junior Member
  • Pip
  • Yii
  • Group: Members
  • Posts: 21
  • Joined: 14-October 09

Posted 07 December 2011 - 03:36 AM

Sorry, I made a mistake in my example. In Section model, the "localized" scope should use the relation "i18nSection", not "i18n", since that's the relation name we have defined in the model. (You can use whatever name you want for the relation, just make sure you use the same name in the scope).

//Section.php
public function localized() {
  $this->getDbCriteria()->mergeWith(array(                      
    'with'=>'i18nSection',
  ));
  return $this;
}




View Post__agus, on 07 December 2011 - 01:50 AM, said:

@guillemc
hi i use your behavior and follow your instruction to enable multilingual in content. but i get error, the error says
Relation "i18n" is not defined in active record class "Section".


also i attach my simple test project and screen shoot for the error with this.
could you help me please.

many thanks :)
nb: sorry for my bad english

0

#40 User is offline   __agus 

  • Standard Member
  • PipPip
  • Yii
  • Group: Members
  • Posts: 166
  • Joined: 22-April 10
  • Location:Yogyakarta, Indonesia

Posted 07 December 2011 - 03:50 AM

Thanks for your fast reply i'll try it. should i create two table for each model. like news for News model and news_lang for NewsLang model. sorry i'm new bie :D

thanks again ::)

View Postguillemc, on 07 December 2011 - 03:36 AM, said:

Sorry, I made a mistake in my example. In Section model, the "localized" scope should use the relation "i18nSection", not "i18n", since that's the relation name we have defined in the model. (You can use whatever name you want for the relation, just make sure you use the same name in the scope).

//Section.php
public function localized() {
  $this->getDbCriteria()->mergeWith(array(                      
    'with'=>'i18nSection',
  ));
  return $this;
}


0

Share this topic:


  • (5 Pages)
  • +
  • 1
  • 2
  • 3
  • 4
  • Last »
  • You cannot start a new topic
  • You cannot reply to this topic

1 User(s) are reading this topic
0 members, 1 guests, 0 anonymous users