Zend_Translate_Gettext Plural SupportThis tutorial is deprecated. With Zend Framework 1.9 plural support to Gettext adapter has been implemented.

One of the most popular discussions on forums, mailing lists and blogs regarding Zend Framework and its component Zend_Translate are about plural support in the gettext adapter. The Gettext Adapter is the Adapter which is used most frequently. Gettext is a translation source format which was introduced by GNU, and is now used worldwide. It is not human readable, but there are several freeware tools (for instance, POEdit), which can be very helpful. Zend_Translate does not use PHP‘s gettext extension because it is not multi-thread aware. Adapter reads the mo files directly, therefore it does not need PHP’s workaround. Because Zend has focused on a simple API which covers many sources (array, scv, gettext, ini, tbx, tmx, qt, xliff, xmltm) , implementation of plurals (which are native to translation in general, not unique to gettext) would be a hard task in early deployment of Zend Framework.

First step

in implementing plural support to Gettext Adapter in Zend Framework is understanding how gettext handles plural forms. I won’t get into details here, you can read more about this at GNU / gettext . For us the crucial information is about the plural form selections, which has to be stored in the header of entry of the PO file (the one with the empty msgid string). The plural form information looks like this:

Plural-Forms: nplurals=4; plural=(n%100==1 ? 0 : n%100==2 ? 1 : n%100==3 || n%100==4 ? 2 : 3);

Here you can find the list of plural forms, as used by Gettext PO, that are appropriate to each language.

Second step

in achieving our goal would be extracting that information from PO file and storing it somewhere safe for later use. I’ve decided to store it as a translation keyword “Plural-Forms” and then use it as necessary. The supplemented code which is added to Zend_Translate_Adapter_Gettext is:

//store PluralForm
if ($this->_adapterInfo[$filename] != 'No adapter information available') {
    $this->_data[$locale]['Plural-Forms'] = $this->_getPluralForms($this->_adapterInfo[$filename]);
}

/**
* Retrieve Plural-Forms string from MO file
*
* @param array $meta
* @return string
*/
protected function _getPluralForms($meta)
{
    $pluralForms = 'nplurals=2; plural=(n != 1);';
    $array = array();
    foreach (explode("\n", $meta) as $info) {
        if ($info = trim($info)) {
            list($key, $value) = explode(':', $info, 2);
        $array[trim($key)] = trim($value);
        }
    }
    if (array_key_exists('Plural-Forms', $array)) {
        $pluralForms = $array['Plural-Forms'];
    }

    return $pluralForms;
}

Third and final step

is to write a function which would return translated plural form for the provided text, plural version of text, count and optional parameters.
This is just a piece of code from the whole translate class which holds Zend_Translate object (Zend_Translate_Adapter_Gettext):

/**
* Translate Plural message
*
* @param string $text simple text
* @param string $plural plural version of text
* @param int $count count
* @param string/array $params
* @return string
*/
public static function translatePlural($text, $plural, $count, $params = array())
{
    //set Plural Forms String
    self::_setPluralForms();

    if(!is_string($text) || strlen($text) == 0)
        return '';

    // find out the appropriate form
    $select = self::_selectString($count);

    // this should contains all strings separated by NULLs
    $key = $text.chr(0).$plural;
    // verify if translation Exists
    self::_verifyMessage($key);

    if (!self::getInstance()->_translate->isTranslated($key)) {
        $translated = ($count != 1) ? $plural : $text;
    } else {
        $result = self::getInstance()->_translate->translate($key);
        $list = explode(chr(0), $result);
        $translated =  $list[$select];
    }
    // return formated translation
    return vsprintf($translated, $params);
}

Use case

within action controller

//Change Locale to Slovene
ZendX_T::setLocale('sl_SI');
$threeSeconds = ZendX_T::translatePlural("%d second","%d seconds", 3, 3);
/**
$threeSeconds will have value '3 sekunde'
*/

The Zend Framework way of implementing this thing right would be writing a View_Helper. :) I’ll leave that task to you.

Source Code

Download a zip archive of the project

  zf_gettext_plural.zip (4.3 MiB, 823 hits)

or grab your copy at GitHub. Feel free to download and judge for yourself. :D

And remember, contributions earn you karma. ;)