Internationalisation with Zend_Framework

I’m loving discovering all these other parts of Zend Framework that I haven’t looked at yet. Today it was setting the locale of the application, and dishing out content specific to that region. Okay, lets get cracking.

In the root of the application, create /languages

And in there, create files like messages.es.php & messages.fr.php and messages.en_US.php. For English, as well as having en_GB and en_US, we should also have just en as a fallback. In these files, you put your translations in a big ass array:

<?php
return array(
  'nav-home'             => 'Inicio',
  'nav-services'         => 'Servicios',
  'nav-catalogue'         => 'Catálogo',
  'nav-contact'         => 'Contacto',
  'welcome'         => 'Bienvenido',
  'form-name'         => 'Nombre:',
  'form-email-address' => 'email:',
  'form-message'       => 'Mensaje:',
  'form-verification'  => 'verificación:',
  'form-send-message'  => 'Enviar',
  'form-title'         => 'Contacto',
);

Then you initialise the locale stuff in your bootstrap. If it can’t detect the locale from your browser then it will fall back to your default locale of choice :

protected function _initLocale()
{
    try 
    {
      $locale = new Zend_Locale('browser');
    } 
    catch (Zend_Locale_Exception $e) 
    {
      $locale = new Zend_Locale('en_GB');
    }
    $registry = Zend_Registry::getInstance();
    $registry->set('Zend_Locale', $locale);
}

To test the different locales, we can edit the bootstrap replacing the word browser for fr or es or en_US etc. But before we do that, a couple more things. First, initialise the translate stuff in the bootstrap too.

protected function _initTranslate()
{
    $translate = new Zend_Translate('array',
                  APPLICATION_PATH . '/../languages/',
                  null,
                  array('scan' => Zend_Translate::LOCALE_FILENAME,
                        'disableNotices' => 1));
    $registry = Zend_Registry::getInstance();
    $registry->set('Zend_Translate', $translate);
}

In the controller actions where you want anything to happen you get it from the Zend_Registry:

    $registry = Zend_Registry::getInstance();
    $this->view->locale = $registry->get('Zend_Locale');

Finally, you need to edit the nav links and the form field labels like so:

    <a href="blah"><?php echo $this->translate('nav-catalogue');?></a>

And the form:

    $email->setLabel('form-email-address');

Zend_Form is groovy enough to check for translations automatically, so that’s it really! A lot of fuss taken care of quite simply!

Another point is that now we have a locale, we can get locale specific versions of things like dates, currency, and number formatting. As an example:

echo $this->escape(
    Zend_Locale_Format::toNumber
    (
        $this->product->price,
        array
        ( 
            'locale' => $this->locale,
            'precision' => 2
        ) 
    )
);

And for a date:

    <?php echo $this->escape($this->productdate->get(Zend_Date::DATE_FULL)); ?>

As you can see this is easy and fun! So it would be awesome to have the ability to switch between them, yes? So lets make a locale controller:

class LocaleController extends Zend_Controller_Action

{

public function setAction()
 {
    // if supported locale, add to session
    if (Zend_Validate::is($this->getRequest()->getParam('locale'), 'InArray',
        array('haystack' => array('en_US', 'en_GB', 'de_DE', 'fr_FR')))) 
    {
      $session = new Zend_Session_Namespace('ttb.l10n');
      $session->locale = $this->getRequest()->getParam('locale');
    }
    // redirect to requesting URL
    $url = $this->getRequest()->getServer('HTTP_REFERER');
    $this->_redirect($url);
 } 
}

edit the _initLocale method in the bootstrap

  protected function _initLocale()
  {
     $session = new Zend_Session_Namespace('ttb.l10n');
     if ($session->locale) 
     {
        $locale = new Zend_Locale($session->locale);
     }
     if ($locale === null) 
     {
        try 
        {
            $locale = new Zend_Locale('browser');
        } 
        catch (Zend_Locale_Exception $e) 
        {
          $locale = new Zend_Locale('en_GB');
        }
     }
     $registry = Zend_Registry::getInstance();
     $registry->set('Zend_Locale', $locale);
  }

And then make some links in your layout to swap locales:

<a href="/locale/set/fr_FR"><img src="/img/locale/france-flag.png" />

Awesome! switching locales is a dawdle! Now to get on with my Spanish lessons some more! Adios!

Zend_Date Problems with MySQL

Have you ever seen this problem?

I had a MySQL Date in my DB (2012-08-31). This is the 31st of August.

$expirydate = new Zend_Date($mysql_date);
 echo $expirydate;

Returned 31st August as expected.

But if the date is the 4th August (2012-08-04), the same code as above will echo out the 8th April!
Zend_Date automatically tries to guess the format, and mistakenly guessed Y/d/m !
I came across this problem when I tried to compare dates:

 $expirydate = new Zend_Date($mysql_date);
 $today = new Zend_Date();
 if($today->isLater($expirydate))
 {
     //today is beyond expiry date
 }

The Zend Docs weren’t very clear, and i thought maybe it should have been the other way around, $expirydate->isLater($today)
But it’s not.

All that is required is to set the format in the constructor. For MySQL Dates, the format is ISO_8601:

$expirydate = new Zend_Date($mysql_date,Zend_Date::ISO_8601);

Problem solved. The moral of the story? Make sure you declare the format of the dates!!