国际化(i18n)和本地化(l10n)

| 选择喜欢的代码风格  

首先,我们需要定义这两个相似的概念,还有相关的概念:

  • Internationalization 国际化:指的是一开始设计一个支持多语言的架构。很多时候这个事情只需要做一次,并且是在项目初始时,不然的话,你可能面临一个项目的重大修改。
  • Localization 本地化:指的是新语言的添加。基于 i18n 的架构设计,在每一次新支持一门语言时,我们都需要一点点的去增加翻译的语言。
  • Pluralization 复数形式:不同语言复数规则不一样,即使是相同语言里也会出现不同复数规则,例如大部分英文名词后面加 s 为复数,有一些单词如 knowledge` 就没有复数形式。俄语和塞尔威亚语有两种复数的形式,甚至有一些语言,如斯洛维尼亚语、爱尔兰语和阿拉伯语会存在 4、5 或者是 6 种复数形式。

PHP 多语言一般实现的方法


最简便的方式是使用数组键值对应的方式如 <?=$TRANS['title_about_page']?>,不过在比较正经的项目中,不建议这么做。因为会随着项目代码慢慢变多,维护的难度将会增加,尤其会阻碍后续本地化实施。

其他工具 - Gettext


您可能需要使用包管理器安装 Gettext 和相关的 PHP 库,如 apt-getyum。 安装后,通过将 extension = gettext.so(Linux / Unix)或 extension = php_gettext.dll(Windows)添加到 php.ini 来启用它。

使用 Gettext 的目录结构


要使用 Gettext,我们需要遵循特定的文件夹结构。 首先,您需要为源存储库中的l10n文件选择任意根。 在其中,您将拥有每个所需区域设置的文件夹,以及包含所有 PO / MO 对的固定 LC_MESSAGES 文件夹(pot文件,pot是Portable Object Template的首字母缩写,与po对应的是mo,mo是Machine Object的首字母缩写)。 例:

<project root>
 ├─ src/
 ├─ templates/
 └─ locales/
    ├─ forum.pot
    ├─ site.pot
    ├─ de/
    │  └─ LC_MESSAGES/
    │     ├─ forum.mo
    │     ├─ forum.po
    │     ├─ site.mo
    │     └─ site.po
    ├─ es_ES/
    │  └─ LC_MESSAGES/
    │     └─ ...
    ├─ fr/
    │  └─ ...
    ├─ pt_BR/
    │  └─ ...
    └─ pt_PT/
       └─ ...

正如我们在介绍中所说,不同的语言可能会有不同的复数规则。 但是,gettext再一次让我们免于这个麻烦。 在创建新的.po文件时,您必须声明该语言的复数规则,并且对于复数敏感的翻译片段将针对每个规则具有不同的形式。 在代码中调用Gettext时,您必须指定与句子相关的数字,并且它将确定要使用的正确形式 - 如果需要,甚至使用字符串替换。

PHP Gettext 多种形式


多个规则包括可用复数的数量和带有n的布尔测试,它将定义给定数字落在哪个规则中(以0开始计数)。 例如:

  • 日语:nplurals = 1; plural = 0 - 只有一条规则
  • 英语:nplurals = 2; 复数=(n!= 1); - 两个规则,首先是N是一个,否则是第二个规则
  • 巴西葡萄牙语:nplurals = 2; 复数=(n> 1); - 两个规则,如果N大于一,则为第二个,否则为第一个

现在您了解了复数规则如何工作的基础 - 如果您没有,请查看LingoHub教程的更深层次的解释 - 您可能希望从列表中复制所需的规则而不是手动编写它们。

当调用Gettext来对带有计数器的句子进行本地化时,你也必须给他相关的数字。 Gettext将确定应该生效的规则并使用正确的本地化版本。 您需要在.po文件中为每个定义的复数规则添加不同的句子。

PO 文件示例


这是一个.po文件的摘录 - 不介意其格式,而是整体内容,您将学习如何在以后轻松编辑它:

msgid ""
msgstr ""
"Language: pt_BR\n"
"Content-Type: text/plain; charset=UTF-8\n"
"Plural-Forms: nplurals=2; plural=(n > 1);\n"

msgid "We're now translating some strings"
msgstr "Nós estamos traduzindo algumas strings agora"

msgid "Hello %1$s! Your last visit was on %2$s"
msgstr "Olá %1$s! Sua última visita foi em %2$s"

msgid "Only one unread message"
msgid_plural "%d unread messages"
msgstr[0] "Só uma mensagem não lida"
msgstr[1] "%d mensagens não lidas"

第一部分像标题一样工作,msgidmsgstr 特别空。 它描述了文件编码,复数形式和其他不太相关的东西。

第二部分将简单的字符串从英语翻译成巴西葡萄牙语,第三部分则相同,但利用 sprintf 中的字符串替换,因此翻译可能包含用户名和访问日期。

最后一部分是复数形式的样本,将单数和复数形式显示为英语中的msgid,并将它们对应的翻译显示为 msgstr 01(遵循复数规则给出的数字)。 在那里,也使用字符串替换,因此可以使用 %d 直接在句子中看到数字。 复数形式总是有两个 msgid(单数和复数),因此建议不要使用复杂的语言作为翻译的来源。

Gettext PHP 实例


<?php
/**
 * 验证项目中是否支持给定变量$locale
 * @参数字符串 $locale
 * @返回布尔
 */
function valid($locale) {
   return in_array($locale, ['en_US', 'en', 'pt_BR', 'pt', 'es_ES', 'es');
}

//设置source/default语言环境,以供参考
$lang = 'en_US';

if (isset($_GET['lang']) && valid($_GET['lang'])) {
    // 可以通过查询字符串更改语言环境
    //这里只是个示例,真正开发中,对于GPC(GET/POST/COOKIE),一定要过滤!
    $lang = $_GET['lang'];
    setcookie('lang', $lang); //存储到COOKIE以便读取使用
} elseif (isset($_COOKIE['lang']) && valid($_COOKIE['lang'])) {
    // 如果COOKIE已经有存储直接读取语言配置
    $lang = $_COOKIE['lang']; //记得,GPC真正开发中,要过滤!
} elseif (isset($_SERVER['HTTP_ACCEPT_LANGUAGE'])) {
    // 默认: 通过$_SERVER环境变量,读取默认判断用户浏览器的语言设置
    $langs = explode(',', $_SERVER['HTTP_ACCEPT_LANGUAGE']);
    array_walk($langs, function (&$lang) {
		$lang = strtr(strtok($lang, ';'), ['-' => '_']); 
		}
	);
    foreach ($langs as $browser_lang) {
        if (valid($browser_lang)) {
            $lang = $browser_lang;
            break;
        }
    }
}

// 在这里,我们根据找到的语言定义全局系统区域设置
putenv("LANG=$lang");

// 例如,这可能对日期函数(LC_TIME)或货币格式(LC_MONETARY)
setlocale(LC_ALL, $lang);

// 这将使Gettext找到../locales//LC_MESSAGES/main.mo
bindtextdomain('main', '../locales');

// 表示应该读取文件的编码
bind_textdomain_codeset('main', 'UTF-8');

// 如果你的应用程序有其他域,
//如前所述,你应该在这里绑定它们,以便表明应该读取文件的编码
bindtextdomain('forum', '../locales');
bind_textdomain_codeset('forum', 'UTF-8');

// 这里我们指出gettext()调用将响应的默认域
textdomain('main');

// 这将在forum.mo而不是main.mo中查找字符串
// echo dgettext('forum', 'Welcome back!');
?>

PHP Gettext 学习总结


Gettext 需要理解下面几点:

  • gettext() 只是将 msgid 转换为给定语言的相应 msgstrgettext() 还有简写函数 _() 以相同的方式工作;
  • ngettext() 做同样但有多个规则;
  • 还有 dgettext()dngettext(),它允许您覆盖单个调用的域。

PHP Gettext 多语言扩展阅读:




发表评论