A localized application offers different content according to the user's culture. For instance, an online shop can offer products worldwide at the same price, but with a custom description for every country. This means that the database must be able to store different versions of a given piece of data, and for that, you need to design your schema in a particular way and use culture each time you manipulate localized model objects.
For each table that contains some localized data, you should split the table in two parts: one table that does not have any i18n column, and the other one with only the i18n columns. The two tables are to be linked by a one-to-many relationship. This setup lets you add more languages when required without changing your model. Let's consider an example using a Product table.
First, create tables in the schema.yml file, as shown in Listing 13-6.
Listing 13-6 - Sample Schema for i18n Data, in config/schema.yml
my_connection:
my_product:
_attributes: { phpName: Product, isI18N: true, i18nTable: my_product_i18n }
id: { type: integer, required: true, primaryKey: true, autoincrement: true }
price: { type: float }
my_product_i18n:
_attributes: { phpName: ProductI18n }
id: { type: integer, required: true, primaryKey: true, foreignTable: my_product, foreignReference: id }
culture: { isCulture: true, type: varchar, size: 7, required: true, primaryKey: true }
name: { type: varchar, size: 50 }
Notice the isI18N and i18nTable attributes in the first table, and the special culture column in the second. All these are symfony-specific Propel enhancements.
The symfony automations can make this much faster to write. If the table containing internationalized data has the same name as the main table with _i18n as a suffix, and they are related with a column named id in both tables, you can omit the id and culture columns in the _i18n table as well as the specific i18n attributes for the main table; symfony will infer them. It means that symfony will see the schema in Listing 13-7 as the same as the one in Listing 13-6.
Listing 13-7 - Sample Schema for i18n Data, Short Version, in config/schema.yml
my_connection:
my_product:
_attributes: { phpName: Product }
id:
price: float
my_product_i18n:
_attributes: { phpName: ProductI18n }
name: varchar(50)
Once the corresponding object model is built (don't forget to call the propel:build-model task after each modification of the schema.yml), you can use your Product class with i18n support as if there were only one table, as shown in Listing 13-8.
Listing 13-8 - Dealing with i18n Objects
$product = ProductPeer::retrieveByPk(1); $product->setName('Nom du produit'); // By default, the culture is the current user culture $product->save(); echo $product->getName(); => 'Nom du produit' $product->setName('Product name', 'en'); // change the value for the 'en' culture $product->save(); echo $product->getName('en'); => 'Product name'
As for queries with the peer objects, you can restrict the results to objects having a translation for the current culture by using the doSelectWithI18n method, instead of the usual doSelect, as shown in Listing 13-10. In addition, it will create the related i18n objects at the same time as the regular ones, resulting in a reduced number of queries to get the full content (refer to Chapter 18 for more information about this method's positive impacts on performance).
Listing 13-10 - Retrieving Objects with an i18n Criteria
$c = new Criteria(); $c->add(ProductPeer::PRICE, 100, Criteria::LESS_THAN); $products = ProductPeer::doSelectWithI18n($c, $culture); // The $culture argument is optional // The current user culture is used if no culture is given
So basically, you should never have to deal with the i18n objects directly, but instead pass the culture to the model (or let it guess it) each time you do a query with the regular objects.