<?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>Mon, 26 Sep 2011 01:30:02 +0000</lastBuildDate>
	<language>en</language>
	<sy:updatePeriod>hourly</sy:updatePeriod>
	<sy:updateFrequency>1</sy:updateFrequency>
	<generator>http://wordpress.org/?v=3.2.1</generator>
		<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&#8217;ll try to figure out how to come out of common troubles. We&#8217;ll start talking about string translation using a Pear module called Translation2. Before doing that, allow me to make a premise. [...]]]></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&#8217;ll try to figure out how to come out of common troubles. We&#8217;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&#8217;t a <strong>standard way</strong> to format the text and many translation services <strong>don&#8217;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 &#8216;Translation2.php&#8217;;</p>
<p>$tr =&amp; Translation2::factory(&#8216;mdb2&#8242;,$db, array(      <br />&#8216;langs_avail_table&#8217;&#160;&#160;&#160;&#160;&#160;&#160;&#160; =&gt; &#8216;translation_langs&#8217;,       <br />&#8216;lang_id_col&#8217;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160; =&gt; &#8216;id&#8217;,       <br />&#8216;lang_name_col&#8217;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160; =&gt; &#8216;name&#8217;,       <br />&#8216;lang_meta_col&#8217;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160; =&gt; &#8216;meta&#8217;,       <br />&#8216;lang_errmsg_col&#8217;&#160;&#160;&#160;&#160;&#160;&#160;&#160; =&gt; &#8216;error_text&#8217;,       <br />&#8216;lang_encoding_col&#8217;&#160;&#160;&#160;&#160;&#160;&#160;&#160; =&gt; &#8216;encoding&#8217;,       <br />&#8216;strings_default_table&#8217;&#160;&#160;&#160; =&gt; &#8216;translation_strings&#8217;,&#160; <br /> &#8216;string_id_col&#8217;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160; =&gt; &#8216;ID&#8217;,&#160; <br /> &#8216;string_page_id_col&#8217;&#160;&#160;&#160; =&gt; &#8216;page_id&#8217;,&#160; <br /> &#8216;string_text_col&#8217;&#160;&#160;&#160;&#160;&#160;&#160;&#160; =&gt; &#8216;%s&#8217;,&#160; <br /> &#8216;autoCleanCache&#8217;&#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&#8217;ll skip this part. Let&#8217;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 &#8216;<em>it</em>&#8216; or &#8216;<em>en</em>&#8216; or &#8216;<em>fr</em>&#8216;), <strong>name</strong> (like &#8216;<em>English</em>&#8216;, or &#8216;<em>Espanol</em>&#8216;, &#8216;<em>Italiano</em>&#8216;,&#8217; &#8216;<em>Francais</em>&#8216;), <strong>meta</strong> (extended name, like &#8216;<em>American English</em>&#8216;, usually matches the name), <strong>error text</strong> (when the string is not translated, for example &#8216;<em>not available</em>&#8216;,&#8217;<em>non disponibile</em>&#8216;,&#8217;<em>non traduit</em>&#8216;) and encoding (&#8216;<em>iso-8859-1</em>&#8216;,&#8217;<em>utf-8</em>&#8216; 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 (&#8216;<em>%s</em>&#8216; 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(&#8220;it&#8221;);     <br />$tr-&gt;setPageID(&#8220;LOGIN_PAGE&#8221;);</p>
</blockquote>
<p>You then start to initialize the so called &#8216;<strong><em>decorators</em></strong>&#8216;, 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 &#8216;CacheLiteFunction&#8217;);&#160; <br /> $tr-&gt;setOption(&#8216;cacheDir&#8217;, &#8216;/cache/lang/&#8217;);&#160; <br /> $tr-&gt;setOption(&#8216;lifeTime&#8217;, 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(&#8216;Lang&#8217;);      <br />$tr-&gt;setOption(&#8216;fallbackLang&#8217;, &#8216;en&#8217;);      <br />$tr-&gt;setOption(&#8216;fallbackLang&#8217;, &#8216;it&#8217;);</p>
</blockquote>
<p>that means &#8220;<em>if you can&#8217;t find the string in the chosen language, fall back to english. If there isn&#8217;t an english string, fall back to italian</em>&#8220;. 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(&#8216;PLEASE_LOGIN&#8217;);</p>
</blockquote>
<p>to ask for a translated string,</p>
<blockquote><p>$tr-&gt;getRaw(&#8216;PLEASE_LOGIN&#8217;,'LOGIN_PAGE&#8217;);</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(&#8220;PLEASE_LOGIN&#8217;, &#8216;LOGIN_PAGE&#8217;, array(&#8220;en&#8221;=&gt;&#8221;Please insert your user name and password in the fields&#8221;,&#8221;it&#8221;=&gt;&#8221;Prego inserire il tuo nome utente e password&#8221;));</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&#8217;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&#8217;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>

