问题 如何国际化PHP第三方库
考虑编写一个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解决方案,或强迫用户选择一个专门用于我的代码。
然后,我如何构建我的代码,以便为不同方面提供最佳和最灵活的本地化:字符串翻译,数字和货币格式,日期和时间,......?假设一个或另一个显示为我的库的输出。消费者可以在哪个位置或界面插入本地化解决方案?
4832
2017-12-10 19:32
起源
答案:
最常用的解决方案是字符串文件。例如。如下:
# 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()
实现本地化的日期和数字。当然可能,但很多工作。
7
2017-12-13 08:17
答案:
最常用的解决方案是字符串文件。例如。如下:
# 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()
实现本地化的日期和数字。当然可能,但很多工作。
7
2017-12-13 08:17
这里有一个主要问题。您现在不想在国际化问题中制作代码。
让我解释。主译者可能是程序员。第二个和第三个可能是,但是你想将它翻译成任何语言,甚至是 非程序员。这对非程序员来说应该很容易。通过课程,功能等为非程序员进行狩猎绝对不行。
所以我建议这样做:保留你的源句(英语) 不可知 格式,每个人都很容易理解。这可能是一个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);
}
}
此外,如果你想使用 我做的小翻译脚本,你可以进一步简化它。它使用数据库,因此它可能没有您所寻求的那么多灵活性。您需要注入它,并在初始化中设置语言。请注意,如果不存在,文本将自动添加到数据库中。
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
领域,而不是 en
, es
等等,在这里和那里抛出一些例外并上传我做过的一些测试。
2
2017-12-14 20:05
基本方法是为消费者提供一些定义映射的方法。只要用户可以定义双射映射,它就可以采用任何形式。
例如,Mantis Bug Tracker使用简单 全局文件:
<?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文件......无论您喜欢什么格式。
回答您以后的编辑/评论
是的,上述与其他解决方案没有太大差别。但我相信没有什么可说的:
- 翻译只能通过字符串替换来实现(映射可能采用无数种形式)
- 格式编号和日期无关紧要。这是表示层的责任,你应该只返回原始数字(或 约会时间s或时间戳),(除非你的图书馆的目的是本地化;)
2
2017-12-13 18:00