<?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; Pear</title>
	<atom:link href="http://giancarlo.dimassa.net/category/php/pear/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>
		<item>
		<title>Database abstraction in PHP with MDB2</title>
		<link>http://giancarlo.dimassa.net/2009/01/18/database-abstraction-in-php-with-mdb2/</link>
		<comments>http://giancarlo.dimassa.net/2009/01/18/database-abstraction-in-php-with-mdb2/#comments</comments>
		<pubDate>Sun, 18 Jan 2009 17:20:05 +0000</pubDate>
		<dc:creator>giancarlo</dc:creator>
				<category><![CDATA[Database]]></category>
		<category><![CDATA[Pear]]></category>
		<category><![CDATA[php]]></category>

		<guid isPermaLink="false">http://giancarlo.dimassa.net/2009/01/18/database-abstraction-in-php-with-mdb2/</guid>
		<description><![CDATA[Coming from a Windows environment, the first thing I noticed about PHP was the lack of a standardized way to access databases across vendors. That is, you want to connect to a MySQL server, you use the command mysql_connect(); , for PostgreSQL it is pg_connect(); , for Microsoft SQL server is mssql_connect(); .

On Windows, instead, [...]]]></description>
			<content:encoded><![CDATA[<p>Coming from a Windows environment, the first thing I noticed about PHP was the <strong>lack of a standardized way</strong> to access databases <strong>across vendors</strong>. That is, you want to connect to a MySQL server, you use the command mysql_connect(); , for PostgreSQL it is pg_connect(); , for Microsoft SQL server is mssql_connect(); .</p>
<p><span id="more-169"></span></p>
<p>On <strong>Windows</strong>, instead, there is a layer called Open Database Connectivity (<strong>ODBC</strong>) that translates the calls from the software to the database, providing a <strong>common interface</strong> and <strong>emulating missing features</strong>. Each vendor provides a <strong>specific driver</strong>, that plugs into the architecture. Via ODBC even a <strong>text file</strong> or an <strong>Excel spreadsheet</strong> can be <strong>connected</strong> and <strong>queried</strong> like it was a <strong>database</strong>.</p>
<p>It has been natural to me to start searching the net for a PHP equivalent of that layer. Shortly after starting I found the <strong>de facto standard</strong> for database abstraction in PHP, called <strong>MDB2</strong>. Even if it has <strong>not the flexibility</strong> of a real ODBC layer, it's <strong>lack of complexity</strong> made it very popular among fellow web programmers at first, just to find out it's <strong>ability to grow</strong> via <strong>extensions</strong> and performing in a <strong>relatively easy way</strong> a set of very <strong>powerful</strong> and <strong>demanding operations</strong>.</p>
<p>MDB2 is a <strong>Pear</strong> library, so you need to follow a procedure to install it. Refer to the <a target="_blank" href="http://pear.php.net" >Pear website</a> for details.</p>
<p>Just after <strong>including</strong> the MDB2 scripts into your PHP file</p>
<blockquote><p>&lt;?</p>
<p>require_once ("MDB2.php");</p>
<p>?&gt;</p></blockquote>
<p>you need toÂ  create an <strong>instance</strong> of the database class, like this</p>
<blockquote><p>&lt;?</p>
<p>$dsn = "your database type://your user name:your password@your server/your database name";</p>
<p>$db = MDB2::factory($dsn);</p>
<p>?&gt;</p></blockquote>
<p>so you have a <strong>connection string</strong> that explains how to connect to the database, and an <strong>object</strong> with multiple methods to perform database operations. You don't need to know other details about the database apart from the ones you put in the Data Source Name (<strong>DSN</strong>), everything else is handled by the MDB2 object.</p>
<p>After that, you perform <strong>vendor agnostic</strong> <strong>queries</strong> like this</p>
<blockquote><p>$result = $db-&gt;queryAll ($sql);</p></blockquote>
<p>But, as said, the real power behind MDB2 is it's <strong>extensions system</strong>, for example</p>
<blockquote><p>$db-&gt;loadModule('Extended');</p>
<p>$tablename = "customers";</p>
<p>$data = array("name"=&gt;"Bob","surname"=&gt;"Michael");</p>
<p>$result= $db-&gt;extended-&gt;autoExecute($tablename,$data, MDB2_AUTOQUERY_INSERT);</p></blockquote>
<p>I've effectively <strong>inserted</strong> a new customer into my table <strong>without writing a single word of SQL</strong>, and without <strong>worrying</strong> of the actual<strong> table structure</strong>.</p>
<p>The same applies to <strong>interrogation queries</strong>, like this</p>
<blockquote><p>$tablename = "customers";</p>
<p>$where = "`NAME` = 'BOB' AND `SURNAME` = 'MICHAEL'";</p>
<p>$result= $db-&gt;extended-&gt;autoExecute($tablename,null,<br />
MDB2_AUTOQUERY_SELECT,<br />
$where,null,true,true);</p></blockquote>
<p>the only SQL here is the <strong>where clause</strong>, everything else is managed by the library.</p>
<p>You can even store an <strong>XML backup</strong> of your entire database with a few commands</p>
<blockquote><p>$db_schema_options = array(<br />
'use_transactions' =&gt; true,<br />
'log_line_break' =&gt; '&lt;br&gt;',<br />
'idxname_format' =&gt; '%s',<br />
'debug' =&gt; true,<br />
'quote_identifier' =&gt; true,<br />
'force_defaults' =&gt; true,<br />
'portability' =&gt; true<br />
);</p>
<p>$schema = MDB2_Schema::factory($dsn, $db_schema_options);</p>
<p>$definition = $schema-&gt;getDefinitionFromDatabase();</p>
<p>$schema_dump = $schema-&gt;dumpDatabase($definition, array(<br />
'output_mode' =&gt; 'file',<br />
'output' =&gt; $dumpfile<br />
), MDB2_SCHEMA_DUMP_ALL);</p></blockquote>
<p>where $dumpfile is the <strong>file name</strong> you want your backup to go to.</p>
<p>The notion of <strong>schemas</strong> enables your application to <strong>seamless</strong> <strong>update</strong> the <strong>database</strong> across <strong>updates</strong>. The MDB2 can made <strong>automatic modifications</strong> of your table definitions to match the ones into the schema.</p>
<p>The library enables to use complex database functions, like <strong>transactions</strong>, to make your web applications <strong>secure</strong> and overcome stability problems when <strong>delicate procedures</strong> are performed, like the cart checkout on a e-commerce website, a money transfer, or any other situation where a <strong>power outage</strong> or a <strong>software interruption</strong> can lead to an <strong>partial update</strong>. Transactions <strong>group</strong> the <strong>operations</strong> so that any error will lead to a <strong>rollback</strong>, preserving the state your data was prior to updating.</p>
<p>If later in the process you want to <strong>change</strong> your database, for example from MySQL to Microsoft SQL server? Simply <strong>change</strong> your <strong>DSN</strong> from mysql to mssql and <strong>you are ready</strong>!</p>
<p>Did you like this article? Subscribe to my <em>transactional</em> feeds and leave a comment!</p>
<p class="fbconnect_share"><fb:share-button class="url" href="http://giancarlo.dimassa.net/2009/01/18/database-abstraction-in-php-with-mdb2/" /></p>]]></content:encoded>
			<wfw:commentRss>http://giancarlo.dimassa.net/2009/01/18/database-abstraction-in-php-with-mdb2/feed/</wfw:commentRss>
		<slash:comments>1</slash:comments>
		</item>
	</channel>
</rss>
