Over a million developers have joined DZone.

Zend_Locale for the win

· Web Dev Zone

I'm not one of those guys that say a framework should be your first design choice in every web application, but I'd like to point out which Zend Framework components make my life easier. After the Zend_Validate article published last week, now is the turn of Zend_Locale.

The internazionalization and localization (i18n/l10n for friends) of a web application are concerns that do not address only translation of words and phrases in other languages, but go further to cover the different formats of user input and output used throughout the whole world. In fact, the majority of web applications generate the output from some kind of data source - databases or external services - and must display it according to the user's current locale. Locale is the computer science term that describes a particular combination of language and region, which can be interpreted to choose a format for the data to display, or to parse.

Problem statement

As we already anticipated in the introduction, there are many common issues in localization that span beyond the simple translation of web pages:

  • displaying, or parsing a date in God-knows-what format. In Italy, we refer to the publishing date of this article as 9/12/2010, while for the database it's 2010-12-09, and for an American it may be December 9th 2010. Date and time handling is something we have seen breaking over and over in nearly every web site or application.
  • displaying a money amount with the correct separators. You may have difficulties in being taken seriously if you make an error in your own localized prices: Paypal for example handles accounts in several currencies at the same time.
  • displaying a simple list of regions available for localization, without having to harvest the list from another website <select> tag. And how is the word English spelt in Cantonese?

An off-the-shelf solution

Zend_Locale is one of the largest components of ZF, at least in file size: it weighs several megabytes, due to the locale metadata it contains. A major part of it does not consist of PHP code: its folder contains many XML files with information on various languages and world regions.

Choosing the right translation for your application data is in the job description of Zend_Translate; Zend_Locale is a dependency of Zend_Translate and contains many common translations of frequently used words (for instance, Yes and No).

Zend_Locale features for instance:

  • symbols like €, $... indexed by a standard keyword in each locale, so that once you have configured the user's current country, you can simply print $array['currency'].
  • utility methods to deal with different user representations, for data that in your Domain Model and persistence layer of your application are unified. An example can be dates, which in database fields are always 2010-12-01 but in user interfaces may be 12/1/2010 or 1/12/2010, according to the country.

You will be surprised by how many utilities are in Zend_Locale: its size is justified by the metadata it hands to your PHP application, free of charge.

A tip: you can delete the XML files for the locales you do not intend to use, freeing an arbitrary amount of megabytes you won't have to transfer on your server when you update the framework. Back in the days of FTP, this was a common problem, and deleting unnecessary files shrinked the component to hundreds of kilobytes..

Example

The code sample calls the Api of the Zend_Locale object, and makes use of some other classes which are locale-aware. Being locale-aware means that a Zend_Locale object can be passed in (or globally registered, although that would be a dangerous example of global state) to modify behavior.
<?php
ini_set('include_path', ini_get('include_path') . PATH_SEPARATOR . '/home/giorgio/code/zftrunk/library');
require_once 'Zend/Loader/Autoloader.php';
Zend_Loader_Autoloader::getInstance();

class ZendLocaleTest extends PHPUnit_Framework_TestCase
{
private $italianLocale;

public function setUp()
{
// sometimes new Zend_Locale(Zend_Locale::BROWSER) for
// http-headers based recognition
$this->italianLocale = new Zend_Locale('it_IT');
}

public function testParsesALocalizedDateEnteredByAnItalianUser()
{
// or 31/12/2010, which is a standard way to represent a date in Europe
$date = new Zend_Date('31/dic/2010', null, $this->italianLocale);
$this->assertEquals('2010-12-31', $date->toString('Y-M-d'));
}

public function testKnowsTheNameOfLanguageCodesInYourLocale()
{
$list = Zend_Locale::getTranslationList('language', 'it_IT');
// the italian word for 'English'
$this->assertEquals('inglese', $list['en']);

$list = Zend_Locale::getTranslationList('language', 'en_US');
// the English word for 'italiano'
$this->assertEquals('Italian', $list['it']);
}

public function testKnowsWhatSymbolItalianUsesForFloatNumbers()
{
$list = Zend_Locale::getTranslationList('Symbols', 'it_IT');
$this->assertEquals(',', $list['decimal']);
}

public function testKnowsHowSpanishSayMonths()
{
$february = Zend_Locale::getTranslation(2, 'Month', 'es');
$this->assertEquals('febrero', $february);
}

public function testKnowsHowToAnswerQuestions()
{
$questionInfo = $this->italianLocale->getQuestion();

// We say Sì to mean Yes...
$this->assertEquals('sì', $questionInfo['yes']);
// ...while No is simply No
$this->assertEquals('no', $questionInfo['no']);

$this->assertContains('s', $questionInfo['yesarray']);
$this->assertContains('sì', $questionInfo['yesarray']);
// without accent on the i
$this->assertContains('si', $questionInfo['yesarray']);
}

public function testParsesNumbersFromYourLocale()
{
// Who wants to be a millionaire?
$userInput = '1.000.000,00';
// wrong conversion
$this->assertSame(1, (int) $userInput);

$number = Zend_Locale_Format::getNumber($userInput, array(
'locale' => $this->italianLocale,
'precision' => 2
));
$this->assertSame('1000000.00', $number);
// you can safely convert to integer now if you want
$this->assertSame(1000000, (int) $number);
}

public function testFormatsNumbersAccordingToYourLocale()
{
$output = Zend_Locale_Format::toNumber('1000000', array(
'locale' => $this->italianLocale
));
$this->assertEquals('1.000.000', $output);
}
}

References

http://framework.zend.com/manual/en/zend.locale.html
http://framework.zend.com/apidoc/1.11/
Topics:

The best of DZone straight to your inbox.

SEE AN EXAMPLE
Please provide a valid email address.

Thanks for subscribing!

Awesome! Check your inbox to verify your email so you can start receiving the latest in tech news and resources.
Subscribe

{{ parent.title || parent.header.title}}

{{ parent.tldr }}

{{ parent.urlSource.name }}