<?xml version="1.0" encoding="UTF-8"?><rss version="2.0"
	xmlns:content="http://purl.org/rss/1.0/modules/content/"
	xmlns:dc="http://purl.org/dc/elements/1.1/"
	xmlns:atom="http://www.w3.org/2005/Atom"
	>
<channel>
	<title>Comments on: Teaching CakePHP to be Multilingual (part 3)</title>
	<atom:link href="http://blog.mozilla.com/webdev/2007/04/18/teaching-cakephp-to-be-multilingual-part-3/feed/" rel="self" type="application/rss+xml" />
	<link>http://blog.mozilla.com/webdev/2007/04/18/teaching-cakephp-to-be-multilingual-part-3/</link>
	<description>Mozilla Web Development Blog</description>
	<pubDate>Thu, 04 Dec 2008 04:55:12 +0000</pubDate>
	<generator>http://wordpress.org/?v=2.6.5</generator>
		<item>
		<title>By: Ugo PARSI</title>
		<link>http://blog.mozilla.com/webdev/2007/04/18/teaching-cakephp-to-be-multilingual-part-3/#comment-6016</link>
		<dc:creator>Ugo PARSI</dc:creator>
		<pubDate>Sun, 29 Apr 2007 09:04:12 +0000</pubDate>
		<guid isPermaLink="false">http://blog.mozilla.com/webdev/2007/04/18/teaching-cakephp-to-be-multilingual-part-3/#comment-6016</guid>
		<description>Thanks a lot for your post, as it was really difficult to adopt a good i18n-strategy because of the lack of many documentations.

I'd be glad to read your last post concerning the detection in URLs.

Keep-up the good work :)</description>
		<content:encoded><![CDATA[<p>Thanks a lot for your post, as it was really difficult to adopt a good i18n-strategy because of the lack of many documentations.</p>
<p>I&#8217;d be glad to read your last post concerning the detection in URLs.</p>
<p>Keep-up the good work <img src='http://blog.mozilla.com/webdev/wp-includes/images/smilies/icon_smile.gif' alt=':)' class='wp-smiley' /></p>
]]></content:encoded>
	</item>
	<item>
		<title>By: Frédéric Wenzel</title>
		<link>http://blog.mozilla.com/webdev/2007/04/18/teaching-cakephp-to-be-multilingual-part-3/#comment-5356</link>
		<dc:creator>Frédéric Wenzel</dc:creator>
		<pubDate>Sat, 21 Apr 2007 10:23:05 +0000</pubDate>
		<guid isPermaLink="false">http://blog.mozilla.com/webdev/2007/04/18/teaching-cakephp-to-be-multilingual-part-3/#comment-5356</guid>
		<description>The LEFT JOIN on the addon type is, (I think) created by cake automagically.</description>
		<content:encoded><![CDATA[<p>The LEFT JOIN on the addon type is, (I think) created by cake automagically.</p>
]]></content:encoded>
	</item>
	<item>
		<title>By: Anders</title>
		<link>http://blog.mozilla.com/webdev/2007/04/18/teaching-cakephp-to-be-multilingual-part-3/#comment-5175</link>
		<dc:creator>Anders</dc:creator>
		<pubDate>Thu, 19 Apr 2007 19:51:17 +0000</pubDate>
		<guid isPermaLink="false">http://blog.mozilla.com/webdev/2007/04/18/teaching-cakephp-to-be-multilingual-part-3/#comment-5175</guid>
		<description>In your example sql you left join on addontypes, while you on the same time in the where clause specify that Addons.addontype_id must be either 1 or 2. Are you really unsure if the addontypes table contains rows with the ids 1 and 2? Judging for the table names I would assume that the content of addontypes was fairly static, so why burden the database with left join instead of a inner join?

Do you really use to ability to use the same text for different fields? That is, e.g. having the same id in the addons.description and addons.eula columns? That would seem to me to be the only reason for having the addons.description and addons.eula columns at all (and if you really didn't want the database to be able to use an index -- since the optimizer can't know if the id's used will be consecutive or scattered).

Otherwise you could instead have had:
create table translations2 (
 id      int(11) unsigned,
 field varchar(20),
 locale  varchar(10),
 value text   NULL,
 created    datetime     NULL,
 modified   datetime     NULL,
 primary key (id, locale, field)
);
For performance you would probably have an covering index on (id, field, locale, value) to make sure you could get to the data in one go.

For having more than one fallback locale, you might have a table translationfallback:
create table translationfallback (
 locale varchar(10),
 fallback varchar(10),
 sort int unsigned NOT NULL, 
 primary key (locale, fallback)
);
The table should always have a row with locale=fallback and sort=0 for each locale to make it the most preferred, and a row with fallback='default' and sort=1000 for each locale to make sql easier.

As for the sql itself, you might want to eliminate some of the join:
SELECT a.id, a.guid,
 SUBSTR(MIN(IF(t.field='name',LPAD(tfb.sort,10,'0')+t.value)),11) AS name,
 a.defaultlocale, a.addontype_id, a.status, a.icontype,
 SUBSTR(MIN(IF(t.field='homepage',LPAD(tfb.sort,10,'0')+t.value)),11) AS homepage,
 SUBSTR(MIN(IF(t.field='description',LPAD(tfb.sort,10,'0')+t.value)),11) AS description,
 SUBSTR(MIN(IF(t.field='summary',LPAD(tfb.sort,10,'0')+t.value)),11) AS summary,
 a.averagerating, a.weeklydownloads, a.totaldownloads,
 SUBSTR(MIN(IF(t.field='developercomments',LPAD(tfb.sort,10,'0')+t.value)),11) AS developercomments,
 a.inactive, a.trusted, a.viewsource, a.prerelease, a.adminreview, a.sitespecific, a.externalsoftware,
 SUBSTR(MIN(IF(t.field='eula',LPAD(tfb.sort,10,'0')+t.value)),11) AS eula,
 SUBSTR(MIN(IF(t.field='privacypolicy',LPAD(tfb.sort,10,'0')+t.value)),11) AS privacypolicy,
 a.created, a.modified,
 SUBSTR(MIN(IF(t.field='description',LPAD(tfb.sort,10,'0')+t.locale)),11) AS description _locale,
 SUBSTR(MIN(IF(t.field='developercomments',LPAD(tfb.sort,10,'0')+t.locale)),11) AS developercomments,
 SUBSTR(MIN(IF(t.field='eula',LPAD(tfb.sort,10,'0')+t.locale)),11) AS eula,
 SUBSTR(MIN(IF(t.field='homepage',LPAD(tfb.sort,10,'0')+t.locale)),11) AS homepage,
 SUBSTR(MIN(IF(t.field='name',LPAD(tfb.sort,10,'0')+t.locale)),11) AS name,
 SUBSTR(MIN(IF(t.field='privacypolicy',LPAD(tfb.sort,10,'0')+t.locale)),11) AS privacypolicy,
 SUBSTR(MIN(IF(t.field='summary',LPAD(tfb.sort,10,'0')+t.locale)),11) AS summary
FROM addons AS a
INNER JOIN addontypes AS aot ON a.addontype_id=aot.id
LEFT JOIN (translationfallback AS tfb
 INNER JOIN translations AS t ON a.description=t.id AND t.locale=if(tfb.fallback='default', a.defaultlocale, tfb.fallback)
) ON tfb.locale='en-US'
WHERE (a.id = 9) AND (a.inactive = 0) AND (a.addontype_id IN (1, 2))
GROUP BY a.id, a.guid, a.defaultlocale, a.addontype_id, a.status,
 a.icontype, a.averagerating, a.weeklydownloads, a.totaldownloads, a.inactive,
 a.trusted, a.viewsource, a.prerelease, a.adminreview, a.sitespecific,
 a.externalsoftware, a.created, a.modified

But, really, it seems kind of silly to use the old "database-in-a-database" pattern where you have a table of entities and a table with its attributes, instead of just two tables:
create table moz_addons (
 id int unsigned,
 guid varchar(16),
 defaultlocale varchar(10) not null,
 addontype_id int unsigned not null,
 status int unsigned not null,
 icontype int unsigned not null,
 averagerating int unsigned not null,
 weeklydownloads int unsigned not null,
 totaldownloads  int unsigned not null,
 inactive int unsigned not null,
 trusted int unsigned not null,
 viewsource int unsigned not null,
 prerelease int unsigned not null,
 adminreview int unsigned not null,
 sitespecific int unsigned not null,
 externalsoftware int unsigned not null,
 created datetime not null,
 modified datetime not null,
 primary key (id),
 foreign key (addontype_id) references moz_addontype (id),
);

create table addontranslations (
 id int unsigned,
 locale varchar(10),
 name text null,
 homepage text null,
 description text null,
 summary text null,
 developercomments text null,
 eula text null,
 privacypolicy text null,
 primary key (id, locale),
 foreign key (id) references addons (id),
);</description>
		<content:encoded><![CDATA[<p>In your example sql you left join on addontypes, while you on the same time in the where clause specify that Addons.addontype_id must be either 1 or 2. Are you really unsure if the addontypes table contains rows with the ids 1 and 2? Judging for the table names I would assume that the content of addontypes was fairly static, so why burden the database with left join instead of a inner join?</p>
<p>Do you really use to ability to use the same text for different fields? That is, e.g. having the same id in the addons.description and addons.eula columns? That would seem to me to be the only reason for having the addons.description and addons.eula columns at all (and if you really didn&#8217;t want the database to be able to use an index &#8212; since the optimizer can&#8217;t know if the id&#8217;s used will be consecutive or scattered).</p>
<p>Otherwise you could instead have had:<br />
create table translations2 (<br />
 id      int(11) unsigned,<br />
 field varchar(20),<br />
 locale  varchar(10),<br />
 value text   NULL,<br />
 created    datetime     NULL,<br />
 modified   datetime     NULL,<br />
 primary key (id, locale, field)<br />
);<br />
For performance you would probably have an covering index on (id, field, locale, value) to make sure you could get to the data in one go.</p>
<p>For having more than one fallback locale, you might have a table translationfallback:<br />
create table translationfallback (<br />
 locale varchar(10),<br />
 fallback varchar(10),<br />
 sort int unsigned NOT NULL,<br />
 primary key (locale, fallback)<br />
);<br />
The table should always have a row with locale=fallback and sort=0 for each locale to make it the most preferred, and a row with fallback=&#8217;default&#8217; and sort=1000 for each locale to make sql easier.</p>
<p>As for the sql itself, you might want to eliminate some of the join:<br />
SELECT a.id, a.guid,<br />
 SUBSTR(MIN(IF(t.field=&#8217;name&#8217;,LPAD(tfb.sort,10,&#8217;0&#8242;)+t.value)),11) AS name,<br />
 a.defaultlocale, a.addontype_id, a.status, a.icontype,<br />
 SUBSTR(MIN(IF(t.field=&#8217;homepage&#8217;,LPAD(tfb.sort,10,&#8217;0&#8242;)+t.value)),11) AS homepage,<br />
 SUBSTR(MIN(IF(t.field=&#8217;description&#8217;,LPAD(tfb.sort,10,&#8217;0&#8242;)+t.value)),11) AS description,<br />
 SUBSTR(MIN(IF(t.field=&#8217;summary&#8217;,LPAD(tfb.sort,10,&#8217;0&#8242;)+t.value)),11) AS summary,<br />
 a.averagerating, a.weeklydownloads, a.totaldownloads,<br />
 SUBSTR(MIN(IF(t.field=&#8217;developercomments&#8217;,LPAD(tfb.sort,10,&#8217;0&#8242;)+t.value)),11) AS developercomments,<br />
 a.inactive, a.trusted, a.viewsource, a.prerelease, a.adminreview, a.sitespecific, a.externalsoftware,<br />
 SUBSTR(MIN(IF(t.field=&#8217;eula&#8217;,LPAD(tfb.sort,10,&#8217;0&#8242;)+t.value)),11) AS eula,<br />
 SUBSTR(MIN(IF(t.field=&#8217;privacypolicy&#8217;,LPAD(tfb.sort,10,&#8217;0&#8242;)+t.value)),11) AS privacypolicy,<br />
 a.created, a.modified,<br />
 SUBSTR(MIN(IF(t.field=&#8217;description&#8217;,LPAD(tfb.sort,10,&#8217;0&#8242;)+t.locale)),11) AS description _locale,<br />
 SUBSTR(MIN(IF(t.field=&#8217;developercomments&#8217;,LPAD(tfb.sort,10,&#8217;0&#8242;)+t.locale)),11) AS developercomments,<br />
 SUBSTR(MIN(IF(t.field=&#8217;eula&#8217;,LPAD(tfb.sort,10,&#8217;0&#8242;)+t.locale)),11) AS eula,<br />
 SUBSTR(MIN(IF(t.field=&#8217;homepage&#8217;,LPAD(tfb.sort,10,&#8217;0&#8242;)+t.locale)),11) AS homepage,<br />
 SUBSTR(MIN(IF(t.field=&#8217;name&#8217;,LPAD(tfb.sort,10,&#8217;0&#8242;)+t.locale)),11) AS name,<br />
 SUBSTR(MIN(IF(t.field=&#8217;privacypolicy&#8217;,LPAD(tfb.sort,10,&#8217;0&#8242;)+t.locale)),11) AS privacypolicy,<br />
 SUBSTR(MIN(IF(t.field=&#8217;summary&#8217;,LPAD(tfb.sort,10,&#8217;0&#8242;)+t.locale)),11) AS summary<br />
FROM addons AS a<br />
INNER JOIN addontypes AS aot ON a.addontype_id=aot.id<br />
LEFT JOIN (translationfallback AS tfb<br />
 INNER JOIN translations AS t ON a.description=t.id AND t.locale=if(tfb.fallback=&#8217;default&#8217;, a.defaultlocale, tfb.fallback)<br />
) ON tfb.locale=&#8217;en-US&#8217;<br />
WHERE (a.id = 9) AND (a.inactive = 0) AND (a.addontype_id IN (1, 2))<br />
GROUP BY a.id, a.guid, a.defaultlocale, a.addontype_id, a.status,<br />
 a.icontype, a.averagerating, a.weeklydownloads, a.totaldownloads, a.inactive,<br />
 a.trusted, a.viewsource, a.prerelease, a.adminreview, a.sitespecific,<br />
 a.externalsoftware, a.created, a.modified</p>
<p>But, really, it seems kind of silly to use the old &#8220;database-in-a-database&#8221; pattern where you have a table of entities and a table with its attributes, instead of just two tables:<br />
create table moz_addons (<br />
 id int unsigned,<br />
 guid varchar(16),<br />
 defaultlocale varchar(10) not null,<br />
 addontype_id int unsigned not null,<br />
 status int unsigned not null,<br />
 icontype int unsigned not null,<br />
 averagerating int unsigned not null,<br />
 weeklydownloads int unsigned not null,<br />
 totaldownloads  int unsigned not null,<br />
 inactive int unsigned not null,<br />
 trusted int unsigned not null,<br />
 viewsource int unsigned not null,<br />
 prerelease int unsigned not null,<br />
 adminreview int unsigned not null,<br />
 sitespecific int unsigned not null,<br />
 externalsoftware int unsigned not null,<br />
 created datetime not null,<br />
 modified datetime not null,<br />
 primary key (id),<br />
 foreign key (addontype_id) references moz_addontype (id),<br />
);</p>
<p>create table addontranslations (<br />
 id int unsigned,<br />
 locale varchar(10),<br />
 name text null,<br />
 homepage text null,<br />
 description text null,<br />
 summary text null,<br />
 developercomments text null,<br />
 eula text null,<br />
 privacypolicy text null,<br />
 primary key (id, locale),<br />
 foreign key (id) references addons (id),<br />
);</p>
]]></content:encoded>
	</item>
</channel>
</rss>
