如何国际化PHP第三方库

时间:2013-12-10 19:32:36

标签: php localization internationalization php-gettext

考虑编写一个PHP库,它将通过Packagist或Pear发布。它适用于在任意设置中使用它的同行开发人员。

此库将包含为客户端确定的一些状态消息。如何使此代码国际化,以便使用该库的开发人员可以尽可能自由地插入自己的本地化方法?我不想假设任何事情,特别是不要强迫dev使用gettext。

要处理一个例子,让我们来看看这个类:

class Example {

    protected $message = "I'd like to be translated in your client's language.";

    public function callMe() {
        return $this->message;
    }

    public function callMeToo($user) {
        return sprintf('Hi %s, nice to meet you!', $user);
    }

}

这里有两个问题:如何标记私有$message进行翻译,以及如何让开发人员在callMeToo()内本地化字符串?

一个(非常不方便)选项是,在构造函数中要求一些i18n方法,如下所示:

public function __construct($i18n) {
    $this->i18n = $i18n;
    $this->message = $this->i18n($this->message);
}

public function callMeToo($user) {
    return sprintf($this->i18n('Hi %s, nice to meet you!'), $user);
}

但我非常希望有一个更优雅的解决方案。

编辑1:除了简单的字符串替换外,i18n的字段很宽。前提是,我不想在我的库中打包任何i18n解决方案,或强迫用户专门选择一个以满足我的代码。

然后,我如何构建我的代码,以便为不同方面提供最佳和最灵活的本地化:字符串翻译,数字和货币格式,日期和时间,......?假设一个或另一个显示为我的库的输出。消费者可以在哪个位置或界面插入她的本地化解决方案?

3 个答案:

答案 0 :(得分:7)

最常用的解决方案是字符串文件。例如。如下:

# library
class Foo {
  public function __construct($lang = 'en') {
    $this->strings = require('path/to/langfile.' . $lang . '.php');
    $this->message = $this->strings['callMeToo'];
  }

  public function callMeToo($user) {
    return sprintf($this->strings['callMeToo'], $user);
  }
}

# strings file
return Array(
  'callMeToo' => 'Hi %s, nice to meet you!'
);

为避免$this->message作业,你可以使用魔法吸气剂:

# library again
class Foo {
  # … code from above

  function __get($name) {
    if(!empty($this->strings[$name])) {
      return $this->strings[$name];
    }

    return null;
  }
}

您甚至可以添加loadStrings方法,该方法从用户获取字符串数组并将其与内部字符串表合并。

编辑1:为了获得更大的灵活性,我会稍微改变上述方法。我会添加一个翻译函数作为对象属性,并在我想要本地化字符串时始终调用它。默认函数只查找字符串表中的字符串,如果找不到本地化字符串,则返回值本身,就像gettext一样。然后,使用您的库的开发人员可以将功能更改为他自己提供的功能,以执行完全不同的本地化方法。

日期本地化不是问题。设置区域设置取决于您的库使用的软件。格式本身是一个本地化的字符串,例如$this->translate('%Y-%m-%d')将返回日期格式字符串的本地化版本。

通过设置正确的区域设置并使用sprintf()等函数来完成数字本地化。

但货币本地化是一个问题。我认为最好的方法是添加货币转换功能(并且,也许是为了更好的灵活性,另一个数字格式化功能),如果开发人员想要更改货币格式,可以覆盖该功能。或者,您也可以为货币实现格式字符串。例如%CUR %.02f - 在此示例中,您将使用货币符号替换%CUR。货币符号本身也是本地化的字符串。

编辑2:如果你不想使用setlocale,你必须做很多工作......基本上你必须重写strftime()sprintf()来实现本地化的日期和数字。当然可能,但很多工作。

答案 1 :(得分:2)

基本方法是为消费者提供一些定义映射的方法。它可以采用任何形式,只要用户可以定义双射映射。

例如,Mantis Bug Tracker使用简单的globals file

<?php
    require_once "strings_$language.txt";
    echo $s_actiongroup_menu_move;

他们的方法很基本,但效果很好。如果您愿意,请将其包装在课堂中:

<?php
    $translator = new Translator(Translator::ENGLISH); // or make it a singleton
    echo $translator->translate('actiongroup_menu_move');

使用XML文件,或者使用INI文件或CSV文件......实际上,无论您喜欢什么样的格式。


回答您以后的修改/评论

是的,上述与其他解决方案没有太大差别。但我相信没有什么可说的了:

  • 翻译只能通过字符串替换来实现(映射可能采用无数种形式)
  • 格式编号和日期无关紧要。这是表示层的责任,你应该只返回原始数字(或DateTime s或时间戳),(除非你的图书馆的目的是本地化;)

答案 2 :(得分:2)

这里有一个主要问题。你不想在你的国际化问题中制作代码。

让我解释一下。主译者可能是程序员。第二个和第三个可能是,但是你想将它翻译成任何语言,即使是非程序员。这对非程序员来说应该很容易。通过课程,功能等为非程序员进行狩猎绝对不行。

所以我建议这样做:用不可知格式保留你的源句(英语),这对每个人来说都很容易理解。这可能是一个xml文件,一个数据库或您认为适合的任何其他形式。然后在您需要的地方使用您的翻译。你可以这样做:

class Example {
  // Fetch them as you prefer and store them in $messages.
  protected $messages = array(
    'en' => array(
      "message"  => "I'd like to be translated in your client's language.",
      "greeting" => "Hi %s, nice to meet you!"
      )
     );

  public function __construct($lang = 'en') {
    $this->lang = $lang;
    }

  protected function get($key, $args = null) {
    // Store the string
    $message = $this->messages[$this->lang][$key];
    if ($args == null)
      return $this->translator($message);
    else {
      $string = $this->translator($message);
      // Merge the two arrays so they can be passed as values
      $sprintf_args = array_merge(array($string), $args);
      return call_user_func_array('sprintf', $sprintf_args);
      }
    }

  public function callMe() {
    return $this->get("message");
  }

  public function callMeToo($user) {
    return $this->get("greeting", $user);
  }
}

此外,如果您想使用small translation script I did,则可以进一步简化它。它使用数据库,因此它可能没有您想要的那么多灵活性。您需要注入它,并在初始化中设置语言。请注意,如果不存在,文本将自动添加到数据库中。

class Example {
  protected $translator;

  // Translator already knows the language to translate the text to
  public function __construct($Translator) {
    $this->translator = $Translator;
    }

  public function callMe() {
    return $this->translator("I'd like to be translated in your client's language.");
  }

  public function callMeToo($user) {
    return sprintf($this->translator("Hi %s, nice to meet you!"), $user));
  }
}

可以很容易地修改它以使用xml文件或任何其他来源来翻译字符串。

注意第二种方法:

  • 这与您提出的解决方案不同,因为它在输出中进行工作,而不是在初始化中,因此无需跟踪每个字符串。

  • 你只需要用英语写一次你的句子。我编写的类将它放在数据库中,只要它被正确初始化,使你的代码非常干。这正是我启动它的原因,而不仅仅是使用gettext(以及我的简单要求的gettext的荒谬大小)。

  • Con:这是一个老班。那时我不知道很多。现在我改变了一些事情:制作一个language字段,而不是enes等,在这里和那里抛出一些例外并上传我做过的一些测试。