[Dbix-class] Advices needed on creating multilingual application
mbit at ukr.net
mbit at ukr.net
Wed Sep 19 12:49:43 GMT 2007
Mark Lawrence(nomad at null.net)@Wed, Sep 19, 2007 at 12:58:24PM +0200:
> On Wed Sep 19, 2007 at 11:50:26AM +0300, mbit at ukr.net wrote:
>
> > Let's assume an easy application. We have a blog. But there are
> > three versions int three languages: en, de and ru for example. But
> > when we don't have a translation for the en part, we show original
> > part. This way first we add original post in de, and then if we
> > translate that part we add translation. And when a user views en
> > version of the site he sees translated part. Otherwize he
> > sees de version. This way in all three languages we have complete
> > website, just not everything is always translated, but we know what
> > changed and was added in all languages.
>
> The application might be 'easy', but the type of problem is not, otherwise
> I expect we would have seen the major blogging/CMS platforms do this long
> ago. Unfortunately I think the only ones who have made reasonable
> progress are Plone and the Gengo plugin for wordpress.
> Although it won't help with your Perl/DBIx::Class issues, looking at
> their database schemas might give you some ideas. As you have probably
> already found, apart from the (minor) complexity of determining which
> record(s) to retrieve, the biggest issue is efficiency.
Thanx. I'll try that.
> Here is how I do it (database-side only).
>
> For every type of object that we wish to have translatable components there
> are two tables. The first table holds all data that is non-translatable,
> plus the original language versions of the translatable data. Eg:
>
> CREATE TABLE entry (
> id INTEGER,
> ctime INTEGER,
> lang VARCHAR(4),
> title VARCHAR(255),
> body TEXT,
> )
>
> The second table contains the translations of the translatable columns
>
> CREATE TABLE entry_t (
> id INTEGER,
> transof INTEGER REFERENCES entry(id),
> ctime INTEGER
> lang VARCHAR(4),
> title VARCHAR(255),
> body TEXT,
> )
Yeah. I do the same thing.
> To retrieve the desired translation with fallback to the original
> language this query is the most efficient I've found.
>
> "find the last 5 blog entries, in Russian but showing their original
> language if no Russian translation available":
>
> SELECT
> t1.id,
> CASCADE(t2.lang, t1.lang) AS lang,
> CASCADE(t2.title, t1.title) AS title,
> CASCADE(t2.body, t1.body) AS body,
> FROM
> entry AS t1
> LEFT JOIN
> entry_t AS t2
> ON
> t1.id = t2.transof AND t2.lang = 'ru'
> ORDER BY
> t1.ctime
> LIMIT 5
Thanx for the SQL. That's surprising me. Actually i'm not that good in pure SQL.
> There is another way to do this, where all the translations are in the
> same table and using self joins, but I don't think it is as clear or
> efficient as the above. Of course the above is the simplest case. I tend
> to also want columns to specify whether the entry is published, whether
> the translation is complete, whether to show the entry at all if there
> is no translation for the selected language (ie hidden from other
> language groups) and so on.
>
> > lang => {
> > data_type => 'character varying',
> > default_value => undef,
> > size => 2,
> > is_nullable => 0
>
> By the way, not all language codes are limited to two characters in
> length.
Ok. I see it now.
> > My question is if there is a better approach to do the same stuff. Because i
> > faced a lot of difficulties in prefetching related object like creator where i
> > had to do something like this:
> >
> > prefetch => { i18n => [ qw/ creator / ] }
>
> Oh, forgot about that. Something like this works.
>
> CREATE TABLE users (
> id INTEGER,
> name VARCHAR(255),
> )
>
> CREATE TABLE entry (
> id INTEGER,
> ctime INTEGER,
> author INTEGER REFERENCES users(id),
> lang VARCHAR(4),
> title VARCHAR(255),
> body TEXT,
> )
>
> CREATE TABLE entry_t (
> id INTEGER,
> transof INTEGER REFERENCES entry(id),
> ctime INTEGER
> lang VARCHAR(4),
> title VARCHAR(255),
> body TEXT,
> )
>
> SELECT
> t1.id,
> t3.name,
> CASCADE(t2.lang, t1.lang) AS lang,
> CASCADE(t2.title, t1.title) AS title,
> CASCADE(t2.body, t1.body) AS body,
> FROM
> entry AS t1
> LEFT JOIN
> entry_t AS t2
> ON
> t1.id = t2.transof AND t2.lang = 'ru'
> LEFT JOIN
> users AS t3
> ON
> t3.id = t1.author
> ORDER BY
> t1.ctime
> LIMIT 5
>
> > Or worse when we have a related object that has a translation
> > too... That's a big mess for me.
>
> On the SQL side you can juse keep on adding CASCADEs and JOINs as many
> times as you want. Unfortunately this doesn't really translate well to
> pure Object(table)-based mappers, so I'll leave it to others on this list
> to explain how you can do that with DBIC. (I couldn't find a nice way).
>
> Hope that helps.
>
> Mark.
Well, thanx a lot for your advises and experience. Now i know that my approach
is not that far from the truth. But i was trying to think `out of the box'
and trying to find a whole anouther way of dealing with maultilingual databases.
Again and again, you are right about difficulties of implementing such things
not in a raw SQL interface. The thing is that i have my application working and
it works not that bad, but i understand it needs refactoring and that involves
searching a better ways of doing the same tasks again and again.
--
vti -- Viacheslav Tikhanovskii
More information about the DBIx-Class
mailing list