简体   繁体   中英

How to translate strings in PHP in dependancy of gender and count?

I am working on multilingual application with a centralized language system. It's based on language files for each language and a simple helper function:

en.php

$lang['access_denied'] = "Access denied.";
$lang['action-required'] = "You need to choose an action.";
...
return $lang;

language_helper.php

...
function __($line) {
  return $lang[$line];
}

Up til now, all strings were system messages addressed to the current user, hence I always could do it that way. Now, I need create other messages, where the string should depend on a dynamic value. Eg in a template file I want to echo the number of action points. If the user only has 1 point, it should echo "You have 1 point."; but for zero or more than 1 point it should be "You have 12 points."

For substitution purposes (both strings and numbers) I created a new function

function __s($line, $subs = array()) {
  $text = $lang[$line];
  while (count($subs) > 0) {
    $text = preg_replace('/%s/', array_shift($subs), $text, 1);
  }
  return $text;
}

Call to function looks like __s('current_points', array($points)) .

$lang['current_points'] in this case would be "You have %s point(s)." , which works well.

Taking it a step further, I want to get rid of the "(s)" part. So I created yet another function

function __c($line, $subs = array()) {
  $text = $lang[$line];
  $text = (isset($sub[0] && $sub[0] == 1) ? $text[0] : $text[1];
  while (count($subs) > 0) {
    $text = preg_replace('/%d/', array_shift($subs), $text, 1);
  }
  return $text;
}

Call to function looks still like __s('current_points', array($points)) .

$lang['current_points'] is now array("You have %d point.","You have %d points.") .

How would I now combine these two functions. Eg if I want to print the username along with the points (like in a ranking). The function call would be something like __x('current_points', array($username,$points)) with $lang['current_points'] being array("$s has %d point.","%s has %d points.") .

I tried to employ preg_replace_callback() but I am having trouble passing the substitute values to that callback function.

$text = preg_replace_callback('/%([sd])/', 
  create_function(
    '$type',
    'switch($type) {
      case "s": return array_shift($subs); break;
      case "d": return array_shift($subs); break;
    }'),
  $text);

Apparently, $subs is not defined as I am getting "out of memory" errors as if the function is not leaving the while loop.

Could anyone point me in the right direction? There's probably a complete different (and better) way to approach this problem. Also, I still want to expand it like this:

$lang['invite_party'] = "%u invited you to $g party."; should become Adam invited you to his party." for males and "Betty invited you to her party." for females. The passed $subs value for both $u and $g would be an user object.

As mentionned by comments, I guess gettext() is an alternative

However if you need an alternative approach, here is a workaround

class ll
{
    private $lang = array(),
            $langFuncs = array(),
            $langFlags = array();

    function __construct()
    {
        $this->lang['access'] = 'Access denied';
        $this->lang['points'] = 'You have %s point{{s|}}';
        $this->lang['party'] = 'A %s invited you to {{his|her}} parteh !';
        $this->lang['toto'] = 'This glass seems %s, {{no one drank in already|someone came here !}}';

        $this->langFuncs['count'] = function($in) { return ($in>1)?true:false; };
        $this->langFuncs['gender'] = function($in) { return (strtolower($in)=='male')?true:false; };
        $this->langFuncs['emptfull'] = function($in) { return ($in=='empty')?true:false; };

        $this->langFlags['points'] = 'count';
        $this->langFlags['toto'] = 'emptfull';
        $this->langFlags['party'] = 'gender';
    }

    public function __($type,$param=null)
    {
        if (isset($this->langFlags[$type])) {
            $f = $this->lang[$type];
            preg_match("/{{(.*?)}}/",$f,$m);

            list ($ifTrue,$ifFalse) = explode("|",$m[1]);

            if($this->langFuncs[$this->langFlags[$type]]($param)) {
                return $this->__s(preg_replace("/{{(.*?)}}/",$ifTrue,$this->lang[$type]),$param);
            } else {
                return $this->__s(preg_replace("/{{(.*?)}}/",$ifFalse,$this->lang[$type]),$param);
            }
        } else {
            return $this->__s($this->lang[$type],$param);
        }
    }
    private function __s($s,$i=null)
    {
        return str_replace("%s",$i,$s);
    }
}

$ll = new ll();

echo "Call : access - NULL\n";
echo $ll->__('access'),"\n\n";
echo "Call : points - 1\n";
echo $ll->__('points',1),"\n\n";
echo "Call : points - 175\n";
echo $ll->__('points',175),"\n\n";
echo "Call : party - Male\n";
echo $ll->__('party','Male'),"\n\n";
echo "Call : party - Female\n";
echo $ll->__('party','Female'),"\n\n";
echo "Call : toto - empty\n";
echo $ll->__('toto','empty'),"\n\n";
echo "Call : toto - full\n";
echo $ll->__('toto','full');

This outputs

Call : access - NULL
Access denied

Call : points - 1
You have 1 point

Call : points - 175
You have 175 points

Call : party - Male
A Male invited you to his parteh !

Call : party - Female
A Female invited you to her parteh !

Call : toto - empty
This glass seems empty, no one drank in already

Call : toto - full
This glass seems full, someone came here !

This may give you an idea on how you could centralize your language possibilities, creating your own functions to resolve one or another text.

Hope this helps you.

If done stuff like this a while ago, but avoided all the pitfalls you are in by separating concerns.

On the lower level, I had a formatter injected in my template that took care of everything language-specific. Formatting numbers for example, or dates. It had a function "plural" with three parameters: $value, $singular, $plural, and based on the value returned one of the latter two. It did not echo the value itself, because that was left for the number formatting.

The whole translation was done inside the template engine. It was Dwoo, which can do template inheritance, so I set up a master template with all HTML structure inside, and plenty of placeholders. Each language was inheriting this HTML master and replaced all placeholders with the right language output. But because we are still in template engine land, it was possible to "translate" the usage of the formatter functions. Dwoo would compile the template inheritance on the first call, including all subsequent calls to the formatter, including all translated parameters.

The gender problem would be getting basically the same soluting: gender($sex, $male, $female), with $sex being the gender of the subject, and the other params being male or female wording.

The technical post webpages of this site follow the CC BY-SA 4.0 protocol. If you need to reprint, please indicate the site URL or the original address.Any question please contact:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM