Trans­lat­ing vari­able strings with get­text or “How to trans­late Doctrine’s enums”

Trans­lat­ing of some­thing with get­text is well-discussed prob­lem, so there’s no need to dis­cuss it again. Everything works like a charm, when you deal­ing with string con­stants. But what to do if you need to trans­late a string retrieved from some vari­able, e.g. _($str)? Trans­la­tion is not a prob­lem at all, so if you have $str defined as ‘Text’, pre­vi­ous call will out­put ‘Text’, unless it will find a trans­lated string with such msgid. Unfor­tu­nately you won’t be able to pre­pare a pot-file auto­mat­ic­ally with these mes­sages. But even when you were eaten, you have at least two ways out. So let’s find some of them ;))

I first met this prob­lem, while was deal­ing with Doc­trine’s enums. So let’s define a sample model that we will use in examples, and which will show the bottle neck:

<?php
class Chick extends Doctrine_Record
{
    public function setTableDefinition()
    {
        $this->hasColumn('name', 'string');
        $this->hasColumn('address', 'text');
        $this->hasColumn('boobs', 'enum', null, array(
            'values' => array('cranberries', 'peaches', 'oranges', 'balloons')
        );
    }
}

As you can see every Chick has boobs of some type. So some­where we’ll write them as:

/** @var $chick Chick */
printf(_('%s has boobs similar to %s'), $chick->name, _($chick->boobs));

Let’s do it easy
The most simple way to achieve our goal (pot file cre­ation with xget­text or poedit) is to use a block with desired mes­sages that will never be executed:

if (0) { // will never be executed
    _('cranberries');
    _('peaches');
    _('oranges');
    _('balloons');
}

This is quite easy way. But it has very big dis­ad­vant­age — you have to keep that block always up-to date — every time you change some­thing related to the vari­able string, you have to pay atten­tion on that block. So we are ready for second way now…

Let’s do it smarter
Another option we can deal with vari­able strings is to mark such strings with some­thing. And then gather such strings into sep­ar­ate dummy file that will be used as one of sources of strings but will never be used in real world. Let’s modify enum defin­i­tion in the way it will have some marker, like this:

// ... skipped ...
        $this->hasColumn('boobs', 'enum', null, array(
            'values' => array(
                'cranberries', //_
                'peaches', //_
                'oranges', //_
                'balloons' //_
            )
        );
// ... skipped ...

Now all we need is a some dummy parser that will be able to deal with such strings, for example, let’s write it in Ruby (so it can be included as one of Rake­file tasks):

require 'find'

files_pattern = /\.(?:php|phtml|inc)$/

single_quoted = /'(?>(?:\\.|[^'])+)'/
double_quoted = /"(?>(?:\\.|[^"])+)"/
quoted_string = /#{single_quoted}|#{double_quoted}/
match_pattern = /(#{quoted_string}).+\/\/_/

result = "<?php\n"
Find.find('.') do |f|
  next unless File.file? f and f.match('')
  File.open(f).each_with_index do |s, i|
    m = s.match(match_pattern)
    result << "_(#{m[1]}); // #{f}:#{i}\n" unless m.nil?
  end
end

puts result

The out­put of it’s exe­cu­tion will pro­duce some­thing like this:

<?php
_('cranberries'); // ./models/Chick.php:9
_('peaches'); // ./models/Chick.php:10
_('oranges'); // ./models/Chick.php:11
_('balloons'); // ./models/Chick.php:12

Now we can use both of described ways, and they are good enough for vari­able strings. But for Doctrine_Record’s enums there is much more beau­ti­ful way to achieve this :)) So let’s find it…

Let’s do it our way
Doc­trine ORM allows you to define a mutator for each prop­erty your model has. So accord­ing to our Chick model we can define a spe­cial get­ter getBoobs() and set­ter setBoobs which will auto trans­late our boobs’ types :)) Basic­ally this can become an idea of how to achieve this with some of your objects. Here’s dummy ver­sion of such getter/setter:

// ... skipped ...
    public function getBoobs()
    {
        switch ($this->_get('boobs')) {
            case 'cranberries': return _('little cranberries');
            case 'peaches': return _('smooth peaches');
            case 'oranges': return _('tasty oranges');
            case 'balloons': return _('mighty balloons');
            default: throw new Doctrine_Exception("I don't have a clue how this chick boobs looks like.");
        }
    }

    public function setBoobs($boobs)
    {
        switch (_($boobs)) {
            case 'little cranberries': return $this->_set('boobs', 'cranberries');
            case 'smooth peaches': return $this->_set('boobs', 'peaches');
            case 'tasty oranges': return $this->_set('boobs', 'oranges');
            case 'mighty balloons': return $this->_set('boobs', 'balloons');
            default: throw new Doctrine_Exception("I don't have any idea about specified boobs type.");
        }
    }
// ... skipped ...

Looks awful! Too many sim­ilar code. So let’s define a map of val­ues like this:

// ... skipped ...
    private static $_boobs = null;

    private function _getBoobs()
    {
        if (null === self::$_boobs) {
            self::$_boobs = array(
                'cranberries'   => _('little cranberries'),
                'peaches'       => _('smooth peaches'),
                'oranges'       => _('tasty oranges'),
                'balloons'      => _('mighty balloons')
            );
        }

        return self::$_boobs;
    }
// ... skipped ...

Now we have val­ues kept in one place, so let’s extend model with mutat­ors keep­ing in mind this map, so here’s final ver­sion of model:

<?php
class Chick extends Doctrine_Record
{
    private static $_boobs = null;

    public function setTableDefinition()
    {
        $this->hasColumn('name', 'string');
        $this->hasColumn('address', 'text');
        $this->hasColumn('boobs', 'enum', null, array(
            'values' => array_keys(self::_getBoobs())
        );
    }

    private static function _getBoobs()
    {
        if (null === self::$_boobs) {
            self::$_boobs = array(
                'cranberries'   => _('little cranberries'),
                'peaches'       => _('smooth peaches'),
                'oranges'       => _('tasty oranges'),
                'balloons'      => _('mighty baloons')
            );
        }

        return self::$_boobs;
    }

    public function getBoobs()
    {
        $boobs = self::_getBoobs();
        return $boobs[$this->_get('boobs')];
    }

    public function setBoobs($value)
    {
        if (false !== ($boobs = array_search($value, self::_getBoobs()))) {
            $value = $boobs;
        }

        return $this->_set('boobs', $value);
    }
}

So now, pre­vi­ous printf call can become:

/** @var $chick Chick */
printf(_('%s has boobs as good as %s'), $chick->name, $chick->boobs);

And you might noticed that setBoobs() allows both trans­lated and untrans­lated strings as input. So both of fol­low­ing lines will work as expected:

/** @var $chicks Array of Chick
$chicks[0]->boobs = 'oranges';
$chicks[1]->boobs = $chicks[0]->boobs;

Now that’s all! :))

Posted in Development, Doctrine ORM | Tagged , , , , | Leave a comment

WeeChat and jabber.py

There’s awe­some IRC cli­ent — WeeChat. It’s very power­full and extens­ible. And the only thing I miss in it is full XMPP suport, so I still using tkab­ber for XMPP. Thank­fully WeeChat has a plu­gin, that gives you at least XMPP private mes­saging sup­port. But as some of my bud­dies are using non-ascii it was not work­ing prop­erly, throw­ing UnicodeEncodeError excep­tion…
Con­tinue read­ing

Posted in WeeChat | Tagged , , , , , | Leave a comment

qTip v.1.0.0-rc3 for jQuery v.1.4.2

If you are using qTip plug-in for jQuery and have updated your jQuery to ver­sion 1.4.2, you might notice that qTip become broken. That’s because of jQuery’s change in logic of jQuery.data(el, key, data) func­tion. Before ver­sion v.1.4.2 it returned undefined upon request­ing undefined data, and now it returns null, so typeof $(this).data('qtip') == 'undefined' check in the qTip fails, as null is object
Con­tinue read­ing

Posted in jQuery | Tagged , , , , | 6 Comments

Fast debug someone else’s PHP application

Some­times you need to make a small fix to someone’s code (e.g. you are hack­ing exist­ing applic­a­tion). And unfor­tu­nately 85% times you’ll meet strange bugs. Most of such bugs are because of unex­pec­ted “input”, e.g. you expect that vari­able should be string abc while in fact it’s integer 123. So to check what was “received” you’ll want to out­put it’s value some­where. But (!) in 90% of times there will be NO log­ging sys­tem in applic­a­tion at all, or it will be as use­full as noth­ing…
Con­tinue read­ing

Posted in Development, How To's | Tagged , , , | Leave a comment

Adding thou­sands sep­ar­at­ors to a number

Today Marco shared on his blog an idea how to add thou­sands sep­ar­at­ors on CLI with help of stand­ard printf func­tion, and Perl, and sed, and awk. So I want to extend his post with same thing but in PHP, Ruby and Python. Of course you can achieve same with printf func­tions of these langauages and set­ting cor­rect loc­ale, but it’s not as inter­est­ing as one-line things. :)) So here we go…
Con­tinue read­ing

Posted in How To's | Tagged , , , , , | Leave a comment