在PHP应用程序中实现国际化(语言字符串)

Xeo*_*oss 19 php globalization internationalization icu

我想构建一个可以处理获取区域设置字符串以支持国际化的CMS.我计划将字符串存储在数据库中,然后在数据库和应用程序之间放置一个键/值缓存(如memcache),以防止性能下降,从而使每个页面的数据库都能进行翻译.

这比使用带有字符串数组的PHP文件更复杂 - 但是当你有2,000个翻译行时,这种方法效率非常低.

我想过使用gettext,但我不确定CMS的用户是否会习惯使用gettext文件.如果字符串存储在数据库中,那么可以设置一个不错的管理系统,允许它们随时进行更改,RAM中的缓存将确保获取这些字符串的速度比gettext快或快.考虑到甚至zend框架都没有使用它,我也觉得使用PHP扩展并不安全.

这种方法有什么问题吗?

更新

我想也许我会增加更多的思考.字符串翻译的一个问题是它们不支持日期,金钱或条件语句.但是,感谢intl PHP现在有了MessageFormatter,无论如何都需要使用它.

// Load string from gettext file
$string = _("{0} resulted in {1,choice,0#no errors|1#single error|1<{1, number} errors}");

// Format using the current locale
msgfmt_format_message(setlocale(LC_ALL, 0), $string, array('Update', 3));
Run Code Online (Sandbox Code Playgroud)

另一方面,我不喜欢gettext的一个原因是文本被嵌入到整个应用程序中.这意味着负责主要翻译的团队(通常是英语)必须能够访问项目源代码,以便在默认语句的所有位置进行更改.它几乎与遍布SQL意大利面条代码的应用程序一样糟糕.

因此,使用这样的键是有意义的_('error.404_not_found'),然后允许内容编写者和翻译者只是担心PO/MO文件而不会弄乱代码.

但是,如果给定键不存在 gettext转换,则无法回退到默认值(就像使用自定义处理程序一样).这意味着您要么在代码中使用写入器 - 或者向没有语言环境转换的用户显示"error.404_not_found"!

另外,我不知道任何使用PHP的gettext的大型项目.我很感激任何链接到使用良好(因此经过测试)的系统,这些系统实际上依赖于本机PHP gettext扩展.

And*_*dre 6

Gettext使用非常快速的二进制协议.此外,gettext实现通常更简单,因为它只需要echo _('Text to translate');.它还有现有的翻译工具,并且已被证明可以很好地运行.

您可以将它们存储在数据库中,但我觉得它会更慢并且有点矫枉过正,尤其是因为您必须自己构建系统来编辑翻译.

如果只有你实际上可以将查找缓存在APC的专用内存部分中,那么你就是金色的.可悲的是,我不知道怎么做.


Xeo*_*oss 5

对于那些感兴趣的人来说,似乎完全支持语言环境,PHP中的i18n终于开始发生了.

// Set the current locale to the one the user agent wants
$locale = Locale::acceptFromHttp(getenv('HTTP_ACCEPT_LANGUAGE'));

// Default Locale
Locale::setDefault($locale);
setlocale(LC_ALL, $locale . '.UTF-8');

// Default timezone of server
date_default_timezone_set('UTC');

// iconv encoding
iconv_set_encoding("internal_encoding", "UTF-8");

// multibyte encoding
mb_internal_encoding('UTF-8');
Run Code Online (Sandbox Code Playgroud)

有几件事情需要解决并检测时区/区域设置然后使用它来正确解析和显示输入和输出是很重要的.刚刚发布的PHP I18N库包含大部分信息的查找表.

处理用户输入对于确保应用程序具有来自用户输入的任何输入的干净,格式良好的UTF-8字符串非常重要.iconv很棒.

/**
 * Convert a string from one encoding to another encoding
 * and remove invalid bytes sequences.
 *
 * @param string $string to convert
 * @param string $to encoding you want the string in
 * @param string $from encoding that string is in
 * @return string
 */
function encode($string, $to = 'UTF-8', $from = 'UTF-8')
{
    // ASCII is already valid UTF-8
    if($to == 'UTF-8' AND is_ascii($string))
    {
        return $string;
    }

    // Convert the string
    return @iconv($from, $to . '//TRANSLIT//IGNORE', $string);
}


/**
 * Tests whether a string contains only 7bit ASCII characters.
 *
 * @param string $string to check
 * @return bool
 */
function is_ascii($string)
{
    return ! preg_match('/[^\x00-\x7F]/S', $string);
}
Run Code Online (Sandbox Code Playgroud)

然后只需通过这些函数运行输入.

$utf8_string = normalizer_normalize(encode($_POST['text']), Normalizer::FORM_C);
Run Code Online (Sandbox Code Playgroud)

翻译

正如安德烈所说,看起来gettext是编写可翻译应用程序的明智选择.

  1. Gettext使用非常快速的二进制协议.
  2. gettext实现通常更简单,因为它只需要 _('Text to translate')
  3. 翻译人员使用的现有工具,并证明它们运作良好.

当你达到facebook大小时,你就可以开始实现RAM缓存的替代方法,就像我在问题中提到的那样.然而,对于大多数项目来说,没有什么比"简单,快速和有效"更胜一筹.

但是,还有gettext无法处理的事情.比如显示日期,金钱和数字.对于那些你需要INTL extionsion.

/**
 * Return an IntlDateFormatter object using the current system locale
 *
 * @param string $locale string
 * @param integer $datetype IntlDateFormatter constant
 * @param integer $timetype IntlDateFormatter constant
 * @param string $timezone Time zone ID, default is system default
 * @return IntlDateFormatter
 */
function __date($locale = NULL, $datetype = IntlDateFormatter::MEDIUM, $timetype = IntlDateFormatter::SHORT, $timezone = NULL)
{
    return new IntlDateFormatter($locale ?: setlocale(LC_ALL, 0), $datetype, $timetype, $timezone);
}

$now = new DateTime();
print __date()->format($now);
$time = __date()->parse($string);
Run Code Online (Sandbox Code Playgroud)

此外,您可以使用strftime来解析考虑当前区域设置的日期.

有时,您需要将数字和日期的值正确插入区域设置消息中

/**
 * Format the given string using the current system locale
 * Basically, it's sprintf on i18n steroids.
 *
 * @param string $string to parse
 * @param array $params to insert
 * @return string
 */
function __($string, array $params = NULL)
{
    return msgfmt_format_message(setlocale(LC_ALL, 0), $string, $params);
}

// Multiple choices (can also just use ngettext)
print __(_("{1,choice,0#no errors|1#single error|1<{1, number} errors}"), array(4));

// Show time in the correct way
print __(_("It is now {0,time,medium}), time());
Run Code Online (Sandbox Code Playgroud)

有关详细信息,请参阅ICU格式详细信息.

数据库

确保您与数据库的连接使用正确的字符集,以便在存储时不会出现任何问题.

字符串函数

您需要了解string,mb_stringgrapheme 函数之间的区别.

// 'LATIN SMALL LETTER A WITH RING ABOVE' (U+00E5) normalization form "D"
$char_a_ring_nfd = "a\xCC\x8A";

var_dump(grapheme_strlen($char_a_ring_nfd));
var_dump(mb_strlen($char_a_ring_nfd));
var_dump(strlen($char_a_ring_nfd));

// 'LATIN CAPITAL LETTER A WITH RING ABOVE' (U+00C5)
$char_A_ring = "\xC3\x85";

var_dump(grapheme_strlen($char_A_ring));
var_dump(mb_strlen($char_A_ring));
var_dump(strlen($char_A_ring));
Run Code Online (Sandbox Code Playgroud)

域名TLD

INTL库中的IDN函数是处理非ascii域名的重要帮助.