Having written a few posts on the merits of GETTEXT and .po files, I ran into an issue revolving around difficulties updating the .po files. It only seemed to work when I changed the location of the localization directory. I assumed that this was due to the files remaining in memory or being cached. This, it seems is a 'well known issue', although I'd never heard of it and trying to find information about it was very difficult. I came across this site: http://blog.ghost3k.net/articles/php/11/gettext-caching-in-php, which describes the phenomenon and offers a workaround. Although the workaround looks quite nice, I thought that this was a fudge and wanted an alternative.
I then came across php-gettext. This can be downloaded from https://launchpad.net/php-gettext/. This has the added bonus of providing a locale even if it isn't installed on your system. Following a bit of testing and jiggery-pokery, I finally got it to work, as follows:
Unpack the archive and copy the contents somewhere on your system.
Create a new directory to store the essential files - I placed them in my config folder, shown highlighted.
Next include the phpgettext.php file, e.g.
include('config/phpgettext.php');
The next step is to change your
echo _("...")
gettext statements to
print T_("...")
It may be easier to do this with a find and replace in your text editor or IDE. For example:
<?php print T_("Place text here");?>
If using PoEdit, you can add the 'T_' keyword to the catalogue:
That worked like a charm for me, so pretty much 'out-of-the-box'. Now no cache problems and it seems to work equally well on Windows and Linux. :)
We can often get into a tizz over trying to format data output from the database by using SQL queries. If the solution is straightforward, that's fine, but often it's a lot of work for nothing, as we have php as a means to process this data and to change it to anything we may need. I'm firmly of the opinion that you should let MySQL just get the data. So, lets look at an example where we need to extract country, state and city data for our linked select dropdowns. BTW - this is for static data, not Ajax-driven. I sometimes wonder why the server needs to be involved every time a select dropdown is clicked. Typically, this data will have the following format:
[Country]
country_id [PK, int]
country [varchar]
[State]
state_id [PK, int]
state [varchar]
country_id [int]
[City]
city_id [PK, int]
city [varchar]
state_id [int]
The simplest way of getting this info would be to run 3 SQL queries, but in order to keep things a little more ordered, lets created a multiple JOIN SQL. This will ensure that we don't have a situation where we get orphaned data in our selects, e.g. countries that have no corresponding states, or states that have no cities.
Here's an example of some SQL (MySQL-flavoured):
SELECT c.country_id, c.country, s.state_id, s.state, i.city_id, i.city
FROM country AS c
INNER JOIN state AS s
ON c.country_id = s.country_id
INNER JOIN city AS i
ON s.state_id = i.state_id
ORDER BY c.country, s.state, i.city
This could give us the following type of data if we return an array from the resource:
Next, we put the data into a form that can be easily dealt with by javascript (jQuery), namely, json. This is incredibly easy to do in php, with json_encode(). Okay, on with the php bit:
Converting php Data to JSON
//Indentify the fields from the output array - so that we can create a well-formed arrays
$keys = array('country'=>array(0,1),'state'=>array(2,3),'city'=>array(4,5));
//This just gives some hooks for javascript and the select dropdown 'id' properties
$json_keys = json_encode(array_keys($keys));
//Create the arrays
function makeArrays($data,$keys){
foreach($data as $record){
$pos = 0;
foreach($keys as $k=>$v){
if($pos == 0){
$r[$k][$record[$v[0]]] = $record[$v[1]];
}else{
$r[$k][$prev][$record[$v[0]]] = $record[$v[1]];
}
$prev = $record[$v[0]];
$pos++;
}
}
return $r;
}
//encode the data for use in javascript
$json = json_encode(makeArrays($dbarr,$keys));
So as you can see, pretty minimal stuff so far. The HTML selects need to have the same id properties as the fieldnames in the $json_keys variable ('country', 'state', 'city'):
You'll notice the class 'linkedselects' - this is important for the javascript.
Getting jQuery to Populate the Select Dropdowns
Right, so far so good, now for the jQuery-fuelled javascript:
That seems to be all that there is to it. You may notice the distinct lack of 'country', 'state' and 'city' in the javascript. This is deliberate, as you should be able to use the code with any similarly formatted data. In addition, you should be able to extend it to however many linked select dropdowns that you need. Here's the output:
**UPDATE (03/02/2013)**
Thought I'd expand on the extending the use. The changes required for 4 linked dropdowns - just the last line as the array is created from the DB:
Before you start panicking – no, MySQL databases aren't dead, just that pHp's way of manipulating these databases has changed. At the time of writing, we're at php version 5.4.11 and the php manual states:
This extension is deprecated as of PHP 5.5.0, and will be removed in the future.
So, what do we do?
Well, there seems to be two main alternatives, both of which have been banging about for some time: mysqli and PDO.
Why the Change?
I suppose you ask 10 people and you get 10 diffrent answers, but for me it's the mysql_query() function, which is a potential hole-digger from the point of view of SQL Injection. We usually protect ourselves with the old mysql_real_escape_string() function on our inputs and think that we're safe. This is covered in greater detail elsewhere, so without plagiarizing the sources, I recommend a read of the following:
Both PDO and Mysqli allow parameterized queries and follow an object orientated approach, which makes handling queries a lot safer and easier.
Which one Should you Use?
Each has its fanboys and detractors, but both will get the job done. Personally, I prefer the PDO syntax and I can easily create wrapper classes for it. For a quick syntax check of these, you can check out the php manual:
For those of you interested in wrapping up PDO so that it has a nice user-friendly client interface, there are many examples out there. One of my favourites when I started was from php-pdo-wrapper-class located on Google Code. Since then, I tend to roll my own, usually as singletons. This is a contentious thing to do and there are many good arguments against using singletons in general. However, in my experience, programming is often a compromise between many factors, which often include convenience.
Still working on on old version of pHp, due to an old install of XAMPP? Hmm, I was too until I realised that some functions just didn't work. In my case it was the DateTime object and its failure to calculate the difference in the number of days between two dates. On further investigation, I realised that I had to have the VC9 build as opposed to the old and knackered VC6. Okay, armed with that info I sought to update pHp. Hmm (again) - didn't seem to be an easy way to do it, although I found a few references, it all looked a bit daunting. I didn't want to get caught up with pHp's binaries, so a I was looking at a fresh install of XAMPP. This actually proved to be far less of a problem than I thought, although it did take a little while. Here goes:
Back up your data folders and virtual hosts file (assuming that XAMPP is installed in root):
C:\xampp\htdocs
C:\xampp\mysql\data
C:\xampp\apache\conf\extra\httpd-vhosts.conf
It may be worth making a backup of your php.ini file too, just to check that you have the same configuration - although, don't just blindly use the old version as there may be differences.
Once you've backed up all the important stuff, uninstall XAMPP. XAMPP comes with its own uninstaller, so use that. It may ask you if you need to keep htdocs and mysql data, but you can now safely answer no to those.
Once uninstalled, hop over to the XAMPP site and download the version of your choice.
Install XAMPP and copy over the htdocs and mysql data files to the new directories and then reinstate your virtual hosts in the httpd-vhosts.conf file. Ensure that the paths in that file are the same as the new installation. For example:
NameVirtualHost *:80
<VirtualHost *:80>
DocumentRoot "C:/xampp/htdocs"
ServerName localhost
</VirtualHost>
<VirtualHost *:80>
DocumentRoot "C:/xampp/htdocs/grapenun"
ServerName grapenun.local
<Directory "C:/xampp/htdocs/grapenun">
Order allow,deny
Allow from all
</Directory>
</VirtualHost>
If your new xampp installation is not at C:/xampp/, then you'll need to change all the references in the file.
Fire up the XAMPP control panel - it may look different to the old panel - and start Apache and MySQL. Be patient - Apache sometimes has a little think about it before telling you it's started!
Have you, like me built multilingual sites with multiple language files holding strings in multidimensional arrays, only to find synching the adding, editing and deleting items across files to be almost impossible? Well, the answer may be to use GETTEXT. Although this basically does the same thing - create separate files for all your localizations, it does have a number of advantages:
you can write pretty much plain text in your html, as opposed to providing a tricky array item, e.g. echo __('this is plain text') vs. echo $lang['plain_text'].
localizations are kept in .po and .mo files which can be modified and updated via editors like PoEdit, making the process relatively painless.
these editors scan your .php files for __('...') and update the po files for you.
if translation strings are not found in the target language, the default language string is used instead - although not ideal, at least you don't have to worry too much if a localization is only 99% complete - it will still display some flavour of text. For this reason, the original texts in the php files are usually written in the main language of your target audience, which may or may not be English.
if your localizations are disseminated amongst a number of translators or you have a lot of localizations, you can create .pot files, which you or your users can then use as a template for creating the .po files.
.po files can be uploaded to Pootle, a Python-based translation platform
It's fast - I mean really fast. Different to arrays, you don't have to load the whole localization!
So, this approach should help you keep a better handle on your localizations and their updating.
How do I Create po/mo Localizations?
There are two main ways to create .po files - either directly from the source files, or from a .pot file. From source would probably be easier in the case of a simple bilingual site, maintained entirely by one person for example. The .pot file, as mentioned, would be best for the outsourced or many languages situation.
Let's look at setting up a page with GETTEXT text in a simple page.
<?php include('includes/config.php'); //contains our gettext setup - see later?>
<!DOCTYPE HTML>
<html>
<head>
<meta charset="utf-8">
<title><?php echo _('This is the page title'); ?></title>
</head>
<body>
<h2><?php echo _('This is the main page title'); ?></h2>
<p><?php echo _('Enter your intro text here'); ?></p>
</body>
</html>
OK, from the above we can see 3 translation strings. If you've downloaded and installed PoEdit, follow these steps to create a translation from source.
Create a directory structure for your localizations:
That will create localization areas for Welsh (cy_GB) and Italian (it_IT). You could name the localizations directory anything you want, e.g. langs, locales, l10n, etc - it's not important.
Next open up PoEdit and create a new catalog and add some catalog properties:
The image above shows an imaginary language, Xoravian - you'd enter the name of the language for the localisation. The charsets will usually be set to UTF-8 and the plural forms can be gleaned from following the link near the textbox.
On the second tab, save the basename as '.', then give the paths you need to check for strings in the paths textbox:
To finish the catalog properties, just check the keywords:
The usual suspects that you may want to use are:
__
gettext
If you use Wordpress, you may wish to add _e.
Once you've entered all the properties, save the .po file to the appropriate LC_MESSAGES directory (let's save it as lang.po into the Italian directory):
Next, PoEdit will run an update to grab all the gettext strings in the paths you've entered. If successful a list of strings will appear. As below:
In addition, your directory, should now contain two new files:
That's it, we're done creating the localization file. Now all that we need to do is enter the translation strings and save.
Need the create from .pot approach? I'll create that as a new post on PoEdit soon, but in short, just create a .po file as above and name it lang.pot. They have the same file formats.
Getting GETTEXT to work in Windows
OK, that last bit looked a bit complicated, but getting GETTEXT to play nicely with Windows is a bit of a nightmare and documentation is very patchy. So, I hope the following is useful and works for you.
In the first code snippet, we came across:
<?php include('includes/config.php'); //contains our gettext setup - see later?>
So here now is the all-important code in the includes/config.php file to get everything to work:
<?php
/*
check url for lang parameter and limit the allowed languages,
otherwise fallback to default text
*/
if (isset($_GET['lang']) && in_array($_GET['lang'],array('it_IT','cy_GB')){
$lang = $_GET['lang'];
putenv("LC_ALL=$lang");
setlocale(LC_ALL, $lang);
$fn = 'lang'; //the name of the .po file
bindtextdomain($fn, "./localization");
bind_textdomain_codeset($fn, 'UTF-8');
textdomain($fn);
}
?>
In order to display data to the screen, you need to use one of two language constructs, either echo or print. To all intents and purposes, they're the same - use whichever you prefer the look of - I tend to use echo.
<?php
echo "Hello World!";
?>
Alternatively use print:
<?php
print "Hello World!";
?>
You should find that you get identical outputs:
Hello World!
There are other ways to output data to the screen, including print_r() and var_dump(). However, these tend to be used for checking internal data, such as variables.
Escaping Characters
As pHp requires either single quotes or double quotes around string (text) data in order to display it to the screen, text containing quotes can be problematic, as they can break the code, for example:
<?php
echo "They all shouted "Help!", in unison";
?>
This would result in an error, such as:
Parse error: syntax error, unexpected T_STRING, expecting ',' or ';' in C:\xampp\htdocs\mysite\index.php on line 2
In much the same way, the following snippet, would also raise the same error:
<?php
echo 'Don't ever call me that!';
?>
Notice that single quotes and apostrophes tend to be the same character. For this reason alone, coders often tend to use double quotes for static text. There is no problem with enclosing double quotes within single quotes and vice-versa. So, the following are fine:
<?php
echo 'They all shouted "Help!", in unison';
echo "Don't ever call me that!";
?>
OK, so far, so good, but what if some text contains both single quotes/apostrophes and double quotes? Well, in this case we need to escape the offending characters. This is done simply be placing a backslash character in front of the character. Escaping all quotes - just in case - is safe to do, as the backslash character is not displayed on the screen. So each of the following will display the text as intended:
<?php
echo 'They all shouted "Help!", in unison. Don\'t ever call me that!';
echo "They all shouted \"Help!\", in unison. Don't ever call me that!";
echo 'They all shouted \"Help!\", in unison. Don\'t ever call me that!';
echo "They all shouted \"Help!\", in unison. Don\'t ever call me that!";
?>
So Which is the Best to Use?
You may read many things about the evils of complicated concatenation or the added time required to parse variables, etc, etc, yawn, yawn. Forget it, it doesn't really matter, to me, it's a question of convenience. It can however depend on the context of what you're outputting - if you want to output variables, or not.
<?php
$num_pigs = 10;
echo "I have $num_pigs pigs on my farm.";
?>
Notice that single quotes take variable names literally.
So how do we display variables within our text using single quotes? Well, we fall back on concatenation. This simply means 'join-up this bit and the next bit' and we use the dot operator (.):
<?php
$num_hairs = 3;
echo 'I have ' . $num_hairs . ' hairs on the top of my head.';
?>
This will give:
I have 3 hairs on the top of my head.
Concatenation isn't the sole preserve of the single quoted text, you can use it with all sorts of bits and bobs:
<?php
$num_frogs = 100;
echo "I have " . $num_frogs . ' bouncing around inside my head.';
?>
A Further Example with Double Quotes
Sometimes you'll need to brace out your variables embedded within the text as they may be array variables:
<?php
echo "I have {$currency['dollars']}$dollar_cost in my pocket";
?>
The braces {...} essentially protect the variable and ensure that it is not affected by following text it can isolate things like array variables. You can only use braces within double quotes. For an actual explanation of braces, see the php manual, as it is too lengthy to include in this beginner's tutorial.
Heredoc and Nowdoc Syntax
pHp is very flexible as it allows you to mix php code and html. This however is also a weakness as it can lead to very untidy code, which is difficult to maintain. There are instances however, when this is just too convenient to ignore. Enter the Heredoc and Nowdoc Syntaxes (BTW - what is the plural of syntax??).
The heredoc syntax behaves just like double quoted text, but without the quotes themselves. In order to set up heredoc text, you need to set an identifier. This can be pretty much anything you want, but it must be in the following format - with the closing identifier in the first column in the last line - no spaces or anything - before it. The following example has DIAFOL as the identifier.
<?php
echo <<<DIAFOL
This is probably the worst game of rugby I've ever seen in my life.
Could you imagine anything worse than Wales losing to Brynaman U11's?
It was "shocking". The score was $brynaman_score : $wales_score
DIAFOL;
?>
The code above would output the following, assuming $brynaman_score was 100 and $wales_score was 0:
This is probably the worst game of rugby I've ever seen in my life. Could you imagine anything worse than Wales losing to Brynaman U11's? It was "shocking". The score was 100 : 0
You'll notice a couple of things:
You don't need to worry about escaping double or single quotes. Yipee!
Line breaks within the heredoc syntax are not respected - not even \n characters. Boo!
In order to maintain line breaks, you'll need to introduce <br /> tags or just use <p> tags. Remember this will just spit out html or plain text. So, to maintain line breaks:
<?php
echo <<<DIAFOL
This is probably the worst game of rugby I've ever seen in my life.<br />
Could you imagine anything worse than Wales losing to Brynaman U11's?<br />
It was "shocking". The score was $brynaman_score : $wales_score
DIAFOL;
?>
Or even:
<?php
echo <<<DIAFOL
<p>This is probably the worst game of rugby I've ever seen in my life.</p>
<p>Could you imagine anything worse than Wales losing to Brynaman U11's?</p>
<p>It was "shocking". The score was $brynaman_score : $wales_score</p>
DIAFOL;
?>
The nowdoc syntax is to single quoted text as the heredoc syntax is to double quoted text. The only difference here is that we use single quotes to enclose the open identifier:
<?php
echo <<<'DIAFOL'
This is probably the worst game of rugby I've ever seen in my life.
Could you imagine anything worse than Wales losing to Brynaman U11's?
It was "shocking". The score was $brynaman_score : $wales_score
DIAFOL;
?>
This will output the following:
This is probably the worst game of rugby I've ever seen in my life. Could you imagine anything worse than Wales losing to Brynaman U11's? It was "shocking". The score was $brynaman_score : $wales_score
You'll notice a couple of things again:
Just like heredoc - no need to escape quotes and line breaks not respected.
Variables are literal.
You may be wondering just how useful that is - and to be honest, for day to day stuff, probably not very. However, if you're writing an article or trying to explain the workings of some code or other, having nowdoc NOT try to parse your variables is quite handy. To achieve the same thing in heredoc, you need to escape the $ symbol of variables:
<?php
echo <<<DIAFOL
This is probably the worst game of rugby I've ever seen in my life.
Could you imagine anything worse than Wales losing to Brynaman U11's?
It was "shocking". The score was \$brynaman_score : \$wales_score
DIAFOL;
?>
OK, that's the end of this first tute on pHp. I hope it was useful - if it was, stroke my ego and leave some love - else - leave a comment anyway and tell me where I went wrong.