I am building a website accessible in a few languages and I need to translate some text data fields of models in those languages... Let's take an example...
...with a model "model_product" with fields id, slug, label, summary, details.
For a given product, I built a page using slug in URI, label in title, summary as an introduction (and in the lists of products) and details as the product page contents. Each (or most of them) product page is accessible in the french, english and german websites and I don't want to waste time nor use many partially duplicated databases, synchronize informations, etc.
My first idea is to store each fields that require translations in another table, category_lang (with fields id, lang, slug, label, summary, details), and modify the core ORM classes so that it joins this table each time needed, using the selected lang and the object id.
I can't anticipate all edge effects... so... do you think that this is a good idea ? Do you have another idea that would answear my question ? The first "problem" I see is the admin panel for translations.
ORM models support something called conditions, which you can define on a relation.
Normally, you would define the relation in the property definition, which works for something static, but not for something dynamic, to so use conditions you need to result to using the models static _init method.
This allows you to set the language selected by your app's user as condition on the relation. Basically injecting an additional join class.
So say you have:
"model_product" with id, page_id, and a model_product_page, with id, lang, description, in a one-to-many relation.
If you define it like this, and you would do $product->page, you would get all page records, for all defined languages.
But if you define the condition in your _init() as
So, I defined a has_many relation I called "lang", in which I can find every i18n data for a given model.
For example, in model_product I have a relation Model_Product_Lang. I also overloaded getter (and setter but i'm not sure I will use this way to update data) in my extended orm model class, so that when I try to use a field not defined in my model BUT defined in the related lang model, it will retrieve it.
I preferred doing this because I may change the way I use 'translated' fields, and I find the 'related field' behavious very usefull if I need to add another related field from another related model, in that case I may specify a list of related models where to search related fields.
However, I have a problem with model queries... since about 2 hours I am looking into the native orm class to understand how related fields are manipulated and how to overload it to handle the new model behavious, but... it seems that query class can't simply access to model specifications.
I know I can do a Model_Product::query()->related('lang')->where(array(array('lang.ref', '=', 'blabla'))) but I would prefer Model_Product::query()->where(array(array('ref', '=', 'blabla'))) and let a query analyser do the job, because I will need to use related field for other stuff and I may change the way objets are linked without having to change a line of code.
Do you think that this is a good thing to do it ? How can I change things to be able to query objects like this ?
I think you're trying to make something simple into something very complicated.
If you want to do it like this, use an EAV container, but give the EAV table an additional key column (the language code), and use the language code required as a condition of the relation.
So if you have a Product: 10, "Item1"
and your EAV table contains: 1, 10, "en", "name", "Bicycle" 2, 10, "fr", "name", "Vélo"
Ok, I just tried, I can use EAV, this is a first simple and effective solution for my i18n problem with simple usage :-) I will implement it, Thanks !
I will deal with it, but... I dislike EAV, It seems to me dirty because the data is most time stored in the same field type and in my case the EAV pattern is "too much", because I will always define the same EAV attributes for a given model. In addition, the EAV structure makes database queries "sometimes" very slow (because of multiple joins).
For the "related" behavior I post another question.
For the application side of things, interface, etc, we have a cache layer on top of it, that creates data structures not unsimilar to lang files. This system deals with the downsides when you have volume.
For the information side of things, we store it in an EAV container, like I described before. Since you're either dealing with a single object (in case of CRUD) or with a limited list of objects (for paging screens), overhead is not that big.
When your dealing with lots of records, for example in batch operations, performance wise ORM is a bad choice anyway.
Finally, I decided to use a one to many relation with a translation table, and to update orm\model and orm\query classes to have an automatic management of translated fields (in the translation model) and update the query builder so that it finds automatically when a field is in the translated related model...
So, I can do Model_Product::query()->where(array(array('label', 'LIKE', '%stuff%'))) and the builder update the query as if I wrote Model_Product::query()->related('lang', array('where=>(array(array('label', 'LIKE', '%stuff%')))) This is also working for join, where, order, group, etc. clauses.