The i18nActiveRecord is a type of Yii CActiveRecord that provides internationalization features. It extends the CActiveRecord class by providing an integrated translation capability. The i18nActiveRecord helps to separate text data from other data by keeping several separate translations for the text data.

The schema below can be used to create an Item AR .

create table item(
  id integer not null primary key auto_increment,
  price double(10,2) not null,
  name varchar(128) not null,
  description text
);

Let’s try to internationalize it. We will create a table tbl_item which contains only data which do not need to be internationalized. The text data which is the internationalized columns will be moved to a new translation table called item_i18n; this new table contains a locale column, and i18n_id which shares a many-to-one relationship with the tbl_item. The locale is a string composed of a language code and a territory code example en_US, en_GB.

create table item(
  id integer not null primary key auto_increment,
  price integer not null
);
 
create table item_i18n(
  i18n_id integer not null,
  locale varchar(4) not null,
  name varchar(128) not null,
  description text,
  primary key(i18n_id,locale)
);

We will use tbl_item to create an instance of i18nActiveRecord called Item. The translation table will be an instance of CActiveRecord called ItemI18n.

class Item extends i18nActiveRecord{
 
}
 
class ItemI18n extends CActiveRecord{
    /**
    * @return string the associated database table name
    */
    public function tableName(){
        return '{{item_i18n}}';
    }
    /**
    *@return array primary key column
    */
    public function primaryKey(){
        return array('i18n_id','locale');
    }
}

The translation table and the corresponding AR class can be given any name. By default, it assumes to the class name of the translation to be the classname of the i18nActiveRecord active record, followed by I18n ( get_class($this).'I18n'). If you do not follow this practice, you need to override the getTranslationClass() method in the Item class to return the correct classname for the translation record.

Basic Usage

The code below shows the basic usage of the i18nActiveRecord. Do well to read the requirements for using the i18nActiveRecord.

<?php
$item = new Item();
$item->price = 12.99;
// add an English translation
$item->setLocale('en_US');
$item->name = 'Microwave oven';
// add a French translation
$item->setLocale('fr_FR');
$item->name = 'Four micro-ondes';
// save the item and its translations
$item->save();
// retrieve text and non-text translations directly from the main object
echo $item->price; // 12.99
$item->setLocale('en_US');
echo $item->name; // Microwave oven
$item->setLocale('fr_FR');
echo $item->name; // Four micro-ondes

Getter and setter properties for internationalized columns exist on the main object; they are just proxy properties to the current translation object.

Dealing with translations

The i18nActiveRecord provides methods which we can use to manipulate the translation objects.

$item = new Item();
$item->price =12.99;
// get the English translation
$t1 = $item->getTranslation('en_US');
 
$t1 = new ItemI18n();
$t1->setLocale('en_US');
$item->addTranslation($t1);
 
$t1->name ='Microwave oven';
// get the French translation
$t2 = $item->getTranslation('fr_FR');
$t2->name = 'Four micro-ondes';
// these translation objects are already related to the main item
// and therefore get saved together with it. Validation will be 
//performed on both the object and all its translations 
$item->save(); // already saves the two translations

If a translation already exists for a given locale, getTranslation() returns the existing translation and not a new one.

You can remove a translation using removeTranslation() and a locale:

$item = Item::model()->findByPk(1);
// remove the French translation
$item->removeTranslation('fr_FR');

We can check if a translation exists by using translationExists() and a locale:

$item = Item::model()->findByPk(1);
// check if the French translation exists
$yes = $item->translationExists('fr_FR');

We can manipulate all translations of the main object by calling the eachTranslation() and providing a closure (an anonymous function) as the parameter. The closure takes as its parameter, a variable representing the translation object

<?php
$item->eachTranslation(function($itemi18n){
    //both english and french translation will get new name
    $itemi18n->name = "New name";
});

The translation objects must first be loaded.

Querying For Objects With Translations

If you need to display a list, the following code will issue n+1 SQL queries, n being the number of items:

$items = Item::model()->findAll(); // one query to retrieve all items
$locale = 'en_US';
foreach ($items as $item) {
  echo $item->price ;
  $item->setLocale($locale);
  echo $item->name; // one query to retrieve the English translation
}

Fortunately, i18nActiveRecord has a method withI18n(), allowing you to hydrate both the main objects and the related translation objects for the given locale:

$items = Item::model()
  ->withI18n('en_US')
  ->findAll(); // one query to retrieve both all items and their translations
foreach ($items as $item) {
  echo $item->price;
  echo $item->name; // no additional query
}

In addition to hydrating translations, withi18n() sets the correct locale on results, so you don't need to call setLocale() for each result.

withI18n() uses a left join with two conditions. That means that the query returns all items, including those with no translation. If you need to return only objects having translations, specify 'INNER JOIN' as second parameter to withI18n().

If you need to search items using a condition on a translation, use the usingI18n() method:

$items = Item::model()
    // tests the condition on the English translation
  ->useI18n('en_US','name=:name',array(':name'=>'Microwave oven')) 
  ->findAll();

That's it folks. Please don't use the comment section to report errors or suggestions. Use this Link instead.

Download

i18nActiveRecord

Inspired bypropel