<?xml version="1.0" encoding="UTF-8"?>
<rss version="2.0"
	xmlns:content="http://purl.org/rss/1.0/modules/content/"
	xmlns:wfw="http://wellformedweb.org/CommentAPI/"
	xmlns:dc="http://purl.org/dc/elements/1.1/"
	xmlns:atom="http://www.w3.org/2005/Atom"
	xmlns:sy="http://purl.org/rss/1.0/modules/syndication/"
	xmlns:slash="http://purl.org/rss/1.0/modules/slash/"
	>

<channel>
	<title>giancarlo.dimassa.net &#187; Localization</title>
	<atom:link href="http://giancarlo.dimassa.net/category/php/localization/feed/" rel="self" type="application/rss+xml" />
	<link>http://giancarlo.dimassa.net</link>
	<description>A web programmer&#039;s blog</description>
	<lastBuildDate>Sun, 01 Feb 2009 20:33:44 +0000</lastBuildDate>
	<generator>http://wordpress.org/?v=2.8.4</generator>
	<language>en</language>
	<sy:updatePeriod>hourly</sy:updatePeriod>
	<sy:updateFrequency>1</sy:updateFrequency>
			<item>
		<title>Website localization in PHP episode 1: Translate strings</title>
		<link>http://giancarlo.dimassa.net/2009/02/01/website-localization-in-php-episode-1-translate-strings/</link>
		<comments>http://giancarlo.dimassa.net/2009/02/01/website-localization-in-php-episode-1-translate-strings/#comments</comments>
		<pubDate>Sun, 01 Feb 2009 19:52:21 +0000</pubDate>
		<dc:creator>giancarlo</dc:creator>
				<category><![CDATA[Localization]]></category>
		<category><![CDATA[Pear]]></category>
		<category><![CDATA[php]]></category>

		<guid isPermaLink="false">http://giancarlo.dimassa.net/2009/02/01/website-localization-in-php-episode-1-translate-strings/</guid>
		<description><![CDATA[Presenting a web site in multiple languages is a serious problem even when using dynamic server side technologies. In this article series, we'll try to figure out how to come out of common troubles. We'll start talking about string translation using a Pear module called Translation2.
 

Before doing that, allow me to make a premise.
Back [...]]]></description>
			<content:encoded><![CDATA[<p>Presenting a web site in <strong>multiple languages</strong> is a serious problem even when using <strong>dynamic server side technologies</strong>. In this article series, we'll try to figure out how to come out of common troubles. We'll start talking about <strong>string translation</strong> using a <a href="http://pear.php.net/"  target="_blank">Pear</a> module called <a target="_blank" href="http://pear.php.net/package/Translation2" >Translation2</a>.</p>
<p> <span id="more-173"></span>
</p>
<p>Before doing that, allow me to make a <strong>premise</strong>.</p>
<p>Back in the old days, we could <strong>copy</strong> our site structure <strong>as many times</strong> as the number of languages we had to work on, go deep into the pages and <strong>make necessary changes</strong>.</p>
<p>This worked well enough for <strong>static content</strong>, but had its <strong>downsides</strong>:</p>
<ul>
<li><font color="#55554e">There were problems <strong>updating</strong> content</font> </li>
<li><font color="#55554e">If you wanted to <strong>send</strong> the <strong>text</strong> to a <strong>translation service</strong>, you had to pull it from the pages <strong>yourself</strong></font> </li>
<li><font color="#55554e">When the text <strong>came back</strong> from the translator, you had to work <strong>again</strong> to insert it into the pages</font> </li>
<li><font color="#55554e">There isn't a <strong>standard way</strong> to format the text and many translation services <strong>don't understand HTML</strong>, so you had to exchange <strong>Word documents</strong> and manually convert the text into HTML again to preserve code <strong>cleaniness</strong></font> </li>
<li><font color="#55554e">There are problems with foreign <strong>character sets</strong>, you have to translate the <strong>HTML entities</strong> or convert between <strong>encodings</strong></font> </li>
<li><font color="#55554e">The first page on your site was nothing more than a <strong>list of languages</strong> and <strong>flags</strong>, that meant the <strong>first</strong> and <strong>most important</strong> page of all the site has <strong>no content</strong></font> </li>
<li><font color="#55554e">If you made a <strong>guess</strong> choosing your first page <strong>language</strong>, your other languages had to be <strong>manually selected</strong> by the user to make the switch.</font> </li>
<li><font color="#55554e">If you <strong>positioned certain elements</strong> into the text, you had to <strong>insert them manually again</strong> when you put the new content.</font> </li>
<li><font color="#55554e">There was no way for the <strong>translators to make the changes their selves</strong> without asking the webmaster, if they had not basic design skills.</font> </li>
<li><font color="#55554e">If they claimed such skills, <strong>you had to trust them enough</strong> to give them access the site source files and have faith in the fact they will not screw up anything.</font> </li>
</ul>
<p>The list can go on forever. Guess what? <strong>Dynamic websites are even worse!</strong> The problem is that most of the programmers build up websites <strong>pulling data</strong> from an <strong>archive</strong> (text files, a database connection, a service on the Intertubes) and <strong>put</strong> this materials into <strong><a href="http://giancarlo.dimassa.net/2007/07/06/how-to-separe-php-code-and-html-presentation-with-smarty-part-1/"  target="_blank">template</a> pages</strong>. When preparing or upgrading a site to be multilanguage, you had to have a way to pull <strong>different data</strong> from database <strong>for each idiom</strong>. Then you have to <strong>figure out</strong> how to translate the text into the templates.</p>
<p>There are many ways to solve these problems.</p>
<p><strong>You could manually change all the tables to hold the data for all idioms, and prepare a very big array that holds the strings you put into the template</strong>, put this big array in different files, one array for each language, and include the files as the user switches language.</p>
<p>This would be only the beginning, because then you should <strong>mantain</strong> all these arrays <strong>in sync</strong>. If you wanted to <strong>change</strong> this data from a control panel you have to <strong>put all the strings in a database</strong>. Manage language <strong>fallbacks</strong> (what if a string is not translated in a language?). What about <strong>caching</strong>? Then you had to <strong>write the procedures</strong> for <strong>querying, inserting and modifying</strong> such data.</p>
<p>Meet <a href="http://pear.php.net/package/Translation2"  target="_blank">Translation2</a>, the <a href="http://pear.php.net/"  target="_blank">Pear</a> module that does all of this for us, <strong>and much more</strong>. After installing it from the repository, we initialize it using this code:</p>
<blockquote><p>require_once 'Translation2.php';</p>
<p>$tr =&amp; Translation2::factory('mdb2',$db, array(      <br />'langs_avail_table'&#160;&#160;&#160;&#160;&#160;&#160;&#160; =&gt; 'translation_langs',       <br />'lang_id_col'&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160; =&gt; 'id',       <br />'lang_name_col'&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160; =&gt; 'name',       <br />'lang_meta_col'&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160; =&gt; 'meta',       <br />'lang_errmsg_col'&#160;&#160;&#160;&#160;&#160;&#160;&#160; =&gt; 'error_text',       <br />'lang_encoding_col'&#160;&#160;&#160;&#160;&#160;&#160;&#160; =&gt; 'encoding',       <br />'strings_default_table'&#160;&#160;&#160; =&gt; 'translation_strings',&#160; <br /> 'string_id_col'&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160; =&gt; 'ID',&#160; <br /> 'string_page_id_col'&#160;&#160;&#160; =&gt; 'page_id',&#160; <br /> 'string_text_col'&#160;&#160;&#160;&#160;&#160;&#160;&#160; =&gt; '%s',&#160; <br /> 'autoCleanCache'&#160;&#160;&#160;&#160;&#160;&#160;&#160; =&gt; true&#160; <br /> ) );</p>
</blockquote>
<p>Translation2 uses <a href="http://giancarlo.dimassa.net/2009/01/18/database-abstraction-in-php-with-mdb2/"  target="_blank">MDB2</a> as an archive foundation, so you have either to <strong>create</strong> a MDB2 connection, or <strong>reuse</strong> an existing one by passing the database object. As I presume you already use a database connection, I'll skip this part. Let's assume the database object is called $db. </p>
<p>Next we pass an array of <strong>parameters</strong>, specifying the <strong>gory details </strong>of the <strong>tables</strong> holding out our translation data. The example depicts two tables, <strong>translation_langs</strong> (that lists all <strong>languages</strong>) and <strong>translation_strings</strong> (that contains all the <strong>text</strong>). </p>
<p>The table that holds all the <strong>languages</strong> needs a column for the language <strong>id</strong> (like for example '<em>it</em>' or '<em>en</em>' or '<em>fr</em>'), <strong>name</strong> (like '<em>English</em>', or '<em>Espanol</em>', '<em>Italiano</em>',' '<em>Francais</em>'), <strong>meta</strong> (extended name, like '<em>American English</em>', usually matches the name), <strong>error text</strong> (when the string is not translated, for example '<em>not available</em>','<em>non disponibile</em>','<em>non traduit</em>') and encoding ('<em>iso-8859-1</em>','<em>utf-8</em>' and so on). You can call these columns and tables <strong>as you want</strong>, you just need to pass the names into the parameters array.</p>
<p>The <strong>strings table</strong> instead has an <strong>ID</strong> column (I put there an MD5 hash of the strings I want to be translated, but any text will do), the <strong>page ID</strong> column (so you separate and query strings for different pages), the <strong>text column</strong> for each language ('<em>%s</em>' in the example means the columns are called after the language ID) and any other parameter you wish to use.</p>
<p>To set the <strong>language ID</strong> and <strong>page ID</strong>, simply issue these two commands:</p>
<blockquote><p>$tr-&gt;setLang("it");     <br />$tr-&gt;setPageID("LOGIN_PAGE");</p>
</blockquote>
<p>You then start to initialize the so called '<strong><em>decorators</em></strong>', additional <strong>features</strong> of the translation system. The most useful ones are the <strong>Cache</strong> decorator (that uses the <a href="http://pear.php.net/package/Cache_Lite"  target="_blank">Cache Lite</a> Pear module) and the <strong>fallback</strong> decorator (to provide fallback strings in other languages).</p>
<p>An initialization sample of the Cache decorator follows:</p>
<blockquote><p>require_once &quot;Cache/Lite.php&quot;;&#160; <br /> $tr =&amp; $tr-&gt;getDecorator 'CacheLiteFunction');&#160; <br /> $tr-&gt;setOption('cacheDir', '/cache/lang/');&#160; <br /> $tr-&gt;setOption('lifeTime', 3600);</p>
</blockquote>
<p>with <em>getDecorator</em> you call the decorator. As you see we had to <strong>require</strong> the cache module file. Then we set the <strong>options</strong>: the <strong>time</strong> the system has to cache strings and the <strong>directory</strong> to use for caching. There are other options, feel free to explore.</p>
<p>The fallback system is even easier:</p>
<blockquote><p>$tr =&amp; $tr-&gt;getDecorator('Lang');      <br />$tr-&gt;setOption('fallbackLang', 'en');      <br />$tr-&gt;setOption('fallbackLang', 'it');</p>
</blockquote>
<p>that means "<em>if you can't find the string in the chosen language, fall back to english. If there isn't an english string, fall back to italian</em>". You can <strong>cascade languages</strong> as you wish. <strong>The function works on a string level</strong>, so you can have a complete translated website with only a pair of strings defaulted to another language.</p>
<p>To do queries you use commands like:</p>
<blockquote><p>$tr-&gt;get('PLEASE_LOGIN');</p>
</blockquote>
<p>to ask for a translated string,</p>
<blockquote><p>$tr-&gt;getRaw('PLEASE_LOGIN','LOGIN_PAGE');</p>
</blockquote>
<p>to ask all translations for a string specifying the Page ID,</p>
<blockquote><p>$tr-&gt;getLangs();</p>
</blockquote>
<p>to enumerate all languages, and so on.</p>
<p>You can at this point insert data into the tables yourself, or use the <strong>handy administration API</strong> that the translation system exposes. Simply initialize it (the parameters array is the same):</p>
<blockquote><p>$tr_admin = &amp;Translation2_Admin::factory([...continue the same way as the Translation2 constructor...]);</p>
</blockquote>
<p>Then you can <strong>issue commands</strong> like:</p>
<blockquote><p>$tr_admin -&gt;cleanCache();</p>
</blockquote>
<p>to <strong>clean the cache</strong></p>
<blockquote><p>$tr_admin-&gt;add("PLEASE_LOGIN', 'LOGIN_PAGE', array("en"=&gt;"Please insert your user name and password in the fields","it"=&gt;"Prego inserire il tuo nome utente e password"));</p>
</blockquote>
<p>to <strong>insert a new string</strong> into the database. </p>
<p>The API enables to easily <strong>add, modify and remove languages and strings</strong>.</p>
<p>If you want, Translation2 can use as a <strong>data source</strong> even an <strong>XML</strong> file or <strong>Gettext GNU .po / .mo compiled files</strong>. The decorators can also be used to <strong>convert between encodings</strong>.</p>
<p><strong>That's all, folks</strong>!</p>
<p>I hope you liked the article, stay tuned for the next episode of <strong>Web localization in PHP: language detection</strong> by subscribing to the <strong><a href="http://giancarlo.dimassa.net/feed/"  target="_blank">RSS</a> feed</strong>. </p>
<p>I would also be happy to answer to any <strong>questions</strong> you could have on the Translation2 module. In particular, I've tightly integrated it into a Markdown parser (a PHPBB code like language) and some plugins to provide realtime translations into <a href="http://giancarlo.dimassa.net/2007/07/06/how-to-separe-php-code-and-html-presentation-with-smarty-part-1/"  target="_blank"><strong>Smarty</strong></a><strong> templates</strong>.</p>
<p class="fbconnect_share"><fb:share-button class="url" href="http://giancarlo.dimassa.net/2009/02/01/website-localization-in-php-episode-1-translate-strings/" /></p>]]></content:encoded>
			<wfw:commentRss>http://giancarlo.dimassa.net/2009/02/01/website-localization-in-php-episode-1-translate-strings/feed/</wfw:commentRss>
		<slash:comments>0</slash:comments>
		</item>
	</channel>
</rss>
