Internationalization and Localization with PHP
Pages: 1, 2
Message Objects
These phrases can also be stored as function return values instead of
strings in an array. Storing the phrases as functions removes the need to use
printf(). Functions that return a sentence look like this:
<?php
// English version
function i_am_X_years_old($age){
return "I am $age years old.";
}
// Spanish version
function i am_X_years_old($age){
return "Tengo $age años.";
}
?>
If some parts of the message catalog belong in an array, and some parts belong in functions, an object is a helpful container for a language's message catalog. A base object and two simple message catalogs look like this:
<?php
class pc_MC_Base {
var $messages;
var $lang;
function msg($s) {
if (isset($this->messages[$s])) {
return $this->messages[$s];
} else {
error_log("l10n error:LANG:" .
"$this->lang,message:'$s'");
}
}
}
class pc_MC_es_US extends pc_MC_Base {
function pc_MC_es_US() {
$this->lang ='es_US';
$this->messages = array(
'chicken' => 'pollo',
'cow' => 'vaca',
'horse' => 'caballo'
);
}
function i_am_X_years_old($age){
return "Tengo $age años";
}
}
class pc_MC_en_US extends pc_MC_Base {
function pc_MC_en_US() {
$this->lang ='en_US';
$this->messages = array(
'chicken' => 'chicken',
'cow' => 'cow',
'horse' => 'horse'
);
}
function i_am_X_years_old($age) {
return "I am $age years old.";
}
}
?>
Each message catalog object extends the pc_MC_Base class to get the
msg() method, and then defines its own messages (in its
constructor) and its own functions that return phrases. Here's how to print
text in Spanish:
<?php
$MC = new pc_MC_es_US;
print $MC->msg('cow');
print $MC->i_am_X_years_old(15);
?>
To print the same text in English, $MC just needs to be
instantiated as a pc_MC_en_US object instead of a pc_MC_es_US object. The rest
of the code remains unchanged.
Localizing Images
Images need to be localized when you want to display images containing text in locale-appropriate languages.
Make an image directory for each locale you want to support, as well as a
global image directory for images that have no locale-specific information.
Create copies of each locale-specific image in the appropriate directories.
Make sure that these images have the same filenames. Instead of printing image
URLs directly, use a wrapper method similar to the msg() method
demonstrated earlier.
The img() wrapper method looks for a locale-specific version
of an image first, then a global one. If neither are present, it logs an error
message. Building upon the pc_MC_Base class, the new class looks like this:
<?php
class pc_MC_Base {
var $messages;
var $images;
var $lang;
var $image_base_path = '/usr/local/www/images';
var $image_base_url = '/images';
function msg($s) {
if (isset($this->messages[$s])) {
return $this->messages[$s];
} else {
error_log("l10n error:LANG:" .
"$this->lang,message:'$s'");
}
}
function img($f) {
if (is_readable("$this->image_base_path/" .
"$this->lang/$f")) {
print "$this->image_base_url/$this->lang/$f";
} elseif (is_readable("$this->image_base_path/" .
"global/$f")) {
print "$this->image_base_url/global/$f";
} else {
error_log("l10n error:LANG:" .
"$this->lang,image:'$f'");
}
}
}
?>
The img() method needs to know both the path to the image file
in the filesystem ($image_base_path) and the path to the image
from the base URL of your site ($image_base_url). It uses the
first to test if the file can be read and the second to construct an
appropriate URL for the image.
A localized image must have the same filename in each localization
directory. For example, an image that says "New!" on a yellow starburst should
be called new.gif in both the images/en_US directory
and the images/es_US directory, even though the file
images/es_US/new.gif is a picture of a yellow starburst with the
word "¡Nuevo!" on it. Don't forget that the alt text you display in your image
tags also needs to be localized. A complete localized <img> tag
looks like this:
<?php
$MC = new pc_MC_es_US;
printf('<img src="%s" alt="%s">',
$MC->img('cancel.png'), $MC->msg('Cancel'));
?>
If the localized versions of a particular image have varied dimensions, store image height and width in the message catalog as well:
<?php
printf('<img src="%s" alt="%s" ' .
'height="%d" width="%d">',
$MC->img('cancel.png'), $MC->msg('Cancel'),
$MC->msg('img-cancel-height'),
$MC->msg('img-cancel-width'));
?>
The localized messages for img-cancel-height and img-cancel-width are not
text strings, but integers that describe the dimensions of the
cancel.png image in each locale.
If you use a consistent naming convention for your variable and file names,
create an imgsrc() method to simplify matters:
<?php
function imgsrc($img) {
$src = $this->img("$img.png");
$alt = $this->msg(ucfirst($img));
$height = $this->msg("img-$src-height");
$width = $this->msg("img-$src-width");
return sprintf('<img src="%s" alt="%s" ' .
'height="%d" width="%d">',
$src, $alt, $height, $width);
}
?>
To get the same results as the Cancel button example before, call it like this:
<?php
$MC = new pc_MC_es_US;
print $MC->imgsrc('cancel');
?>
Conclusion
With help of the msg() and img() methods, you can
quickly create message objects that allow you to localize your Web site using
100 percent pure PHP. Because it's an all-PHP solution, you can reuse all your
existing code, and you don't need to install any new extensions. However, if you
need to share message catalogs among many applications, PHP supports gettext.
See Joao Prado
Maia's article for more details on using gettext with PHP.
As you can see, internationalizing your PHP applications is not a labor of Hercules. When you organize your localizations within an object hierarchy, it's easy to extend your classes to support new countries and regions without difficulties.
Adam Trachtenberg is the manager of technical evangelism for eBay and is the author of two O'Reilly books, "Upgrading to PHP 5" and "PHP Cookbook." In February he will be speaking at Web Services Edge 2005 on "Developing E-Commerce Applications with Web Services" and at the O'Reilly booth at LinuxWorld on "Writing eBay Web Services Applications with PHP 5."
Return to the PHP DevCenter.