I18n Behavior
The i18n behavior provides internationalization for any ActiveRecord object. Using this behavior, you can separate text data from the rest of a model’s data, and keep several translations of the text data for a single object. Applications supporting several languages should reach for the i18n behavior.
Basic usage
Section titled “Basic usage”In schema.xml, use the <behavior> tag to add the i18n behavior to a table. In its <parameter> tags, list the columns that need internationalization as the i18n_columns parameter:
<table name="item"> <column name="id" required="true" primaryKey="true" autoIncrement="true" type="integer" /> <column name="name" type="varchar" required="true" /> <column name="description" type="longvarchar" /> <column name="price" type="float" /> <column name="is_in_store" type="boolean" /> <behavior name="i18n"> <parameter name="i18n_columns" value="name, description" /> </behavior></table>Rebuild your model, run the table creation SQL again, and you’re ready to go. The internationalized columns have now moved to a new translation table called item_i18n, which contains a locale column and shares a many-to-one relationship with the item table. In fact, this is equivalent to having defined the following schema:
<table name="item"> <column name="id" required="true" primaryKey="true" autoIncrement="true" type="integer" /> <column name="price" type="float" /> <column name="is_in_store" type="boolean" /></table><table name="item_i18n"> <column name="id" type="integer" required="true" primaryKey="true" /> <column name="locale" type="varchar" size="5" required="true" primaryKey="true" /> <column name="name" type="varchar" required="true" /> <column name="description" type="longvarchar" /> <foreign-key foreignTable="item" onDelete="cascade"> <reference local="id" foreign="id" /> </foreign-key></table>In addition, the ActiveRecord Item class now provides integrated translation capabilities:
$item = new Item();$item->setPrice('12.99');
// add an English translation$item->setLocale('en_US');$item->setName('Microwave oven');// same as$itemI18n = new ItemI18n();$itemI18n->setLocale('en_US');$itemI18n->setName('Microwave oven');$item->addItemI18n($itemI18n);
// add a French translation$item->setLocale('fr_FR');$item->setName('Four micro-ondes');
// save the item and its translations$item->save();
// retrieve text and non-text translations directly from the main objectecho $item->getPrice(); // 12.99$item->setLocale('en_US');echo $item->getName(); // Microwave oven$item->setLocale('fr_FR');echo $item->getName(); // Four micro-ondesGetter and setter methods for internationalized columns still exist on the main object — they’re just proxy methods to the current translation object, using the same signature and phpDoc for better IDE integration.
Dealing with locale and translations
Section titled “Dealing with locale and translations”If you prefer to deal with real translation objects, the behavior generates a getTranslation() method on the ActiveRecord class, which returns a translation object with the required locale:
$item = new Item();$item->setPrice('12.99');
// get the English translation$t1 = $item->getTranslation('en_US');// same as$t1 = new ItemI18n();$t1->setLocale('en_US');$item->addItemI18n($t1);
$t1->setName('Microwave oven');
// get the French translation$t2 = $item->getTranslation('fr_FR');$t2->setName('Four micro-ondes');
// these translation objects are already related to the main item// and therefore get saved together with it$item->save(); // already saves the two translationsYou can remove a translation using removeTranslation() with a locale:
$item = ItemQuery::create()->findPk(1);// remove the French translation$item->removeTranslation('fr_FR');Querying for objects with translations
Section titled “Querying for objects with translations”If you need to display a list, the following code issues n+1 SQL queries, where n is the number of items:
$items = ItemQuery::create()->find(); // one query to retrieve all items$locale = 'en_US';foreach ($items as $item) { echo $item->getPrice(); $item->setLocale($locale); echo $item->getName(); // one query per item to retrieve the English translation}Fortunately, the behavior adds methods to the query class that hydrate both the Item objects and the related ItemI18n objects for a given locale:
$items = ItemQuery::create() ->joinWithI18n('en_US') ->find(); // one query to retrieve both all items and their translationsforeach ($items as $item) { echo $item->getPrice(); echo $item->getName(); // no additional query}In addition to hydrating translations, joinWithI18n() sets the correct locale on results, so you don’t need to call setLocale() for each result.
If you need to search items using a condition on a translation, use the generated withI18nQuery(), as you would with any with<Relation>Query() method — it takes a callback that receives the translation query, and returns the original query (typed static) so the chain keeps its concrete type:
$items = ItemQuery::create() ->withI18nQuery(fn ($i18n) => $i18n // tests the condition on the English translation ->filterByName('Microwave oven')) ->find();Symfony compatibility
Section titled “Symfony compatibility”This behavior is entirely compatible with the i18n behavior for Symfony. That means it can generate setCulture() and getCulture() methods as aliases to setLocale() and getLocale(), provided you add a locale_alias parameter. It also means that if you add the behavior to a table without translated columns, and the translation table is already present in the schema, the behavior recognizes it.
So the following schema is exactly equivalent to the first one in this page:
<table name="item"> <column name="id" required="true" primaryKey="true" autoIncrement="true" type="integer" /> <column name="price" type="float" /> <column name="is_in_store" type="boolean" /> <behavior name="i18n"> <parameter name="locale_alias" value="culture" /> </behavior></table><table name="item_i18n"> <column name="id" type="integer" required="true" primaryKey="true" /> <column name="name" type="varchar" required="true" /> <column name="description" type="longvarchar" /></table>Parameters
Section titled “Parameters”If you don’t specify a locale when dealing with a translatable object, Propulsion uses the default English locale en_EN. This default can be overridden using the default_locale parameter:
<table name="item"> <column name="id" required="true" primaryKey="true" autoIncrement="true" type="integer" /> <column name="name" type="varchar" required="true" /> <column name="description" type="longvarchar" /> <column name="price" type="float" /> <column name="is_in_store" type="boolean" /> <behavior name="i18n"> <parameter name="i18n_columns" value="name, description" /> <parameter name="default_locale" value="fr_FR" /> </behavior></table>You can change the name of the locale column added by the behavior with the locale_column parameter. You can also change the table name and phpName of the i18n table using the i18n_table and i18n_phpname parameters:
<table name="item"> <column name="id" required="true" primaryKey="true" autoIncrement="true" type="integer" /> <column name="name" type="varchar" required="true" /> <column name="description" type="longvarchar" /> <column name="price" type="float" /> <column name="is_in_store" type="boolean" /> <behavior name="i18n"> <parameter name="i18n_columns" value="name, description" /> <parameter name="locale_column" value="language" /> <parameter name="i18n_table" value="item_translation" /> <parameter name="i18n_phpname" value="ItemTranslation" /> </behavior></table>