haiz/i18n

PHP 的国际化与本地化

维护者

详细信息

github.com/haiz/PHP-I18N

主页

源代码

安装: 6

依赖: 0

建议: 0

安全: 0

星星: 2

关注者: 2

分支: 13

v1.0.3 2020-03-11 18:08 UTC

This package is auto-updated.

Last update: 2024-09-29 05:36:05 UTC


README

PHP 的国际化与本地化

为您的应用程序提供多种语言支持,以适应不同国家的用户,以及不同的格式和约定。

要求

  • PHP 5.6.0+
    • GNU gettext 扩展(gettext
    • 国际化扩展(intl

注意:在 Windows 上,您可能需要使用非线程安全(NTS)版本的 PHP。

安装

  1. 通过 Composer 包含库

    $ composer require haiz/i18n
  2. 包含 Composer 自动加载器

    require __DIR__ . '/vendor/autoload.php';

用法

什么是区域设置?

简单来说,区域设置是一组用户偏好和期望,这些偏好和期望在全球更大的社区中共享,并且因地理位置而异。值得注意的是,这包括用户的语言以及他们期望数字、日期和时间的格式。

确定您支持的初始区域设置集

无论您最初决定支持哪些语言、脚本和区域,您都将在任何时候能够添加或删除区域设置。因此,您可能希望先从1-3个区域设置开始,以更快地开始。

您可以在 Codes 类中找到各种区域代码的列表,并使用相应的常量来引用区域设置,这是推荐的解决方案。或者,您也可以复制它们的字符串值,这些值使用 IETF BCP 47(RFC 5646)或 Unicode CLDR 标识符的子集。

在使用您的初始语言集之前,您应该确保它们已安装在任何您想开发或部署应用程序的机器上,并确保操作系统知道这些语言。

$ locale -a

如果某个区域设置尚未安装,您可以像以下示例中的 es-AR 区域设置一样添加它

$ sudo locale-gen es_AR
$ sudo locale-gen es_AR.UTF-8
$ sudo update-locale
$ sudo service apache2 restart

注意:在类 Unix 操作系统上,安装过程中使用的区域代码必须使用下划线。

创建新的实例

为了创建 I18n 类的实例,只需提供您支持的区域设置集。唯一的特殊条目是第一个区域设置,如果没有更好的匹配,它也作为默认区域设置。

$i18n = new \Delight\I18n\I18n([
    \Delight\I18n\Codes::EN_US,
    \Delight\I18n\Codes::DA_DK,
    \Delight\I18n\Codes::ES,
    \Delight\I18n\Codes::ES_AR,
    \Delight\I18n\Codes::KO,
    \Delight\I18n\Codes::KO_KR,
    \Delight\I18n\Codes::RU_RU,
    \Delight\I18n\Codes::SW
]);

翻译文件的目录和文件名

您的翻译文件将稍后存储在以下位置

locale/<LOCALE_CODE>/LC_MESSAGES/messages.po

例如,可以使用 es-ES 本地化

locale/es_ES/LC_MESSAGES/messages.po

如果您需要更改 locale 目录的路径或希望使用该目录的另一个名称,只需明确指定其路径即可

$i18n->setDirectory(__DIR__ . '/../translations');

LC_MESSAGES 目录中的文件名,即 messages.po,是带有 PO(可移植对象)文件扩展名的应用程序模块的名称。通常不需要更改,但如果您仍然想这样做,只需调用以下方法

$i18n->setModule('messages');

注意:在类 Unix 操作系统中,目录名称中使用的区域设置代码必须使用下划线,而在 Windows 上,代码必须使用连字符。

激活用户正确的区域设置

自动

选择最适合用户的最简单方法是让此库根据各种信号和选项自动决定

$i18n->setLocaleAutomatically();

它将根据以下因素(按此顺序)进行检查和决定

  1. 带有区域设置代码的子域(例如 da-DK.example.com
  2. 带有区域设置代码的路径前缀(例如 http://www.example.com/pt-BR/welcome.html
  3. 带有区域设置代码的查询字符串
    1. locale 参数
    2. language 参数
    3. lang 参数
    4. lc 参数
  4. 通过 I18n#setSessionField 定义的会话字段(例如 $i18n->setSessionField('locale');),例如 $i18n->setSessionField('locale');
  5. 通过 I18n#setCookieName 定义的 Cookie(例如 $i18n->setCookieName('lc');),通过 I18n#setCookieLifetime 可选地定义生存周期(例如 $i18n->setCookieLifetime(60 * 60 * 24);),其中 null 的值表示 Cookie 将在当前浏览器会话结束时过期
  6. HTTP 请求头 Accept-Language(例如 en-US,en;q=0.5

通常您会选择其中一个选项来存储和传输您的区域设置代码,其他因素(特别是最后一个)作为后备选项。前三个选项(以及最后一个)可能在搜索引擎优化(SEO)和缓存方面提供优势。

手动

当然,您也可以手动指定用户的区域设置

try {
    $i18n->setLocaleManually('es-AR');
}
catch (\Delight\I18n\Throwable\LocaleNotSupportedException $e) {
    die('The locale requested by the user is not supported');
}

启用翻译别名

在您的应用程序代码中设置以下别名以简化您与此库的工作,使您的代码更易于阅读,并启用对包含的工具和 GNU gettext 其他工具的支持

function _f($text, ...$replacements) { global $i18n; return $i18n->translateFormatted($text, ...$replacements); }

function _fe($text, ...$replacements) { global $i18n; return $i18n->translateFormattedExtended($text, ...$replacements); }

function _p($text, $alternative, $count) { global $i18n; return $i18n->translatePlural($text, $alternative, $count); }

function _pf($text, $alternative, $count, ...$replacements) { global $i18n; return $i18n->translatePluralFormatted($text, $alternative, $count, ...$replacements); }

function _pfe($text, $alternative, $count, ...$replacements) { global $i18n; return $i18n->translatePluralFormattedExtended($text, $alternative, $count, ...$replacements); }

function _c($text, $context) { global $i18n; return $i18n->translateWithContext($text, $context); }

function _m($text) { global $i18n; return $i18n->markForTranslation($text); }

如果包含全局 I18n 实例的变量不是命名为 $i18n,则当然必须相应地调整上面代码片段中的每个 $i18n 出现的位置。

识别、标记和格式化可翻译字符串

为了国际化您的代码库,您必须识别并标记可翻译的字符串,并使用更复杂的字符串进行格式化。然后,这些标记的字符串可以自动提取出来,在代码外部进行翻译,并在运行时由该库再次插入。

在标记字符串以进行翻译时,通常应遵循以下简单规则

  • 尽可能使用最大的文本单元。这可能是一个单词(例如按钮上的“保存”),几个单词(例如标题中的“创建新账户”),或完整的句子(例如“您的账户已创建。”)。
  • 尽可能将整个句子视为原子单元,除非绝对必要,否则不要从多个已翻译的单词或部分中组成句子。
  • 使用专用函数和方法进行字符串格式化,而不是求助于字符串连接或字符串插值。
  • 使用专用函数和方法处理单数和复数形式,这些函数和方法甚至适用于具有复杂复数规则的语言,这些规则并不总是像英语的二进制规则那样简单。

基本字符串

将用户界面的句子、短语和标签包裹在 _ 函数中

_('Welcome to our online store!');
// Welcome to our online store!
_('Create account');
// Create account
_('You have been successfully logged out.');
// You have been successfully logged out.

带格式的字符串

将用户界面的句子、短语和标签包裹在 _f 函数中

_f('This is %1$s.', 'Bob');
// This is Bob.
_f('This is %1$d.', 3);
// This is 3.
_f('This is %1$05d.', 3);
// This is 00003.
_f('This is %1$ 5d.', 3);
// This is     3.
// This is ␣␣␣␣3.
_f('This is %1$+d.', 3);
// This is +3.
_f('This is %1$+06d.', 3);
// This is +00003.
_f('This is %1$+ 6d.', 3);
// This is     +3.
// This is ␣␣␣␣+3.
_f('This is %1$f.', 3.14);
// This is 3.140000.
_f('This is %1$012f.', 3.14);
// This is 00003.140000.
_f('This is %1$010.4f.', 3.14);
// This is 00003.1400.
_f('This is %1$ 12f.', 3.14);
// This is     3.140000.
// This is ␣␣␣␣3.140000.
_f('This is %1$ 10.4f.', 3.14);
// This is     3.1400.
// This is ␣␣␣␣3.1400.
_f('This is %1$+f.', 3.14);
// This is +3.140000.
_f('This is %1$+013f.', 3.14);
// This is +00003.140000.
_f('This is %1$+011.4f.', 3.14);
// This is +00003.1400.
_f('This is %1$+ 13f.', 3.14);
// This is     +3.140000.
// This is ␣␣␣␣+3.140000.
_f('This is %1$+ 11.4f.', 3.14);
// This is     +3.1400.
// This is ␣␣␣␣+3.1400.
_f('Hello %s!', 'Jane');
// Hello Jane!
_f('%1$s is %2$d years old.', 'John', 30);
// John is 30 years old.

注意:这使用的是来自 C 语言(以及 PHP)的“printf”格式字符串语法。为了将百分号(用作字面量)转义,只需将其加倍,例如 50 %%

注意:当您的格式字符串包含多个占位符和替换项时,请始终对占位符进行编号,以避免歧义,并在翻译过程中提供灵活性。例如,不要使用%s is from %s,而是使用%1$s is from %2$s

扩展格式的字符串

将您的用户界面的句子、短语和标签包裹在_fe函数中

_fe('This is {0}.', 'Bob');
// This is Bob.
_fe('This is {0, number}.', 1003.14);
// This is 1,003.14.
_fe('This is {0, number, percent}.', 0.42);
// This is 42%.
_fe('This is {0, date}.', -14182916);
// This is Jul 20, 1969.
_fe('This is {0, date, short}.', -14182916);
// This is 7/20/69.
_fe('This is {0, date, medium}.', -14182916);
// This is Jul 20, 1969.
_fe('This is {0, date, long}.', -14182916);
// This is July 20, 1969.
_fe('This is {0, date, full}.', -14182916);
// This is Sunday, July 20, 1969.
_fe('This is {0, time}.', -14182916);
// This is 1:18:04 PM.
_fe('This is {0, time, short}.', -14182916);
// This is 1:18 PM.
_fe('This is {0, time, medium}.', -14182916);
// This is 1:18:04 PM.
_fe('This is {0, time, long}.', -14182916);
// This is 1:18:04 PM GMT-7.
_fe('This is {0, time, full}.', -14182916);
// This is 1:18:04 PM GMT-07:00.
_fe('This is {0, spellout}.', 314159);
// This is three hundred fourteen thousand one hundred fifty-nine.
_fe('This is {0, ordinal}.', 314159);
// This is 314,159th.
_fe('Hello {0}!', 'Jane');
// Hello Jane!
_fe('{0} is {1, number} years old.', 'John', 30);
// John is 30 years old.

注意:这使用的是ICU的“MessageFormat”语法。为了将大括号(用作文本)转义,请用单引号将其括起来,如'{''}'。为了将单引号(用作文本)转义,只需将其加倍,如it''s。如果您在PHP中使用单引号作为字符串文本,还必须使用反斜杠转义插入的单引号,如\'{\'\'}\'it\'\'s

单数和复数形式

将您的用户界面的句子、短语和标签包裹在_p函数中

_p('cat', 'cats', 1);
// cat
_p('cat', 'cats', 2);
// cats
_p('cat', 'cats', 3);
// cats
_p('The file has been saved.', 'The files have been saved.', 1);
// The file has been saved.
_p('The file has been saved.', 'The files have been saved.', 2);
// The files have been saved.
_p('The file has been saved.', 'The files have been saved.', 3);
// The files have been saved.

带格式的单数和复数形式

将您的用户界面的句子、短语和标签包裹在_pf函数中

_pf('There is %d monkey.', 'There are %d monkeys.', 0);
// There are 0 monkeys.
_pf('There is %d monkey.', 'There are %d monkeys.', 1);
// There is 1 monkey.
_pf('There is %d monkey.', 'There are %d monkeys.', 2);
// There are 2 monkeys.
_pf('There is %1$d monkey in %2$s.', 'There are %1$d monkeys in %2$s.', 3, 'Anytown');
// There are 3 monkeys in Anytown.
_pf('You have %d new message', 'You have %d new messages', 0);
// You have 0 new messages
_pf('You have %d new message', 'You have %d new messages', 1);
// You have 1 new message
_pf('You have %d new message', 'You have %d new messages', 32);
// You have 32 new messages

注意:这使用的是来自 C 语言(以及 PHP)的“printf”格式字符串语法。为了将百分号(用作字面量)转义,只需将其加倍,例如 50 %%

扩展格式的单数和复数形式

将您的用户界面的句子、短语和标签包裹在_pfe函数中

_pfe('There is {0, number} monkey.', 'There are {0, number} monkeys.', 0);
// There are 0 monkeys.
_pfe('There is {0, number} monkey.', 'There are {0, number} monkeys.', 1);
// There is 1 monkey.
_pfe('There is {0, number} monkey.', 'There are {0, number} monkeys.', 2);
// There are 2 monkeys.
_pfe('There is {0, number} monkey in {1}.', 'There are {0, number} monkeys in {1}.', 3, 'Anytown');
// There are 3 monkeys in Anytown.
_pfe('You have {0, number} new message', 'You have {0, number} new messages', 0);
// You have 0 new messages
_pfe('You have {0, number} new message', 'You have {0, number} new messages', 1);
// You have 1 new message
_pfe('You have {0, number} new message', 'You have {0, number} new messages', 32);
// You have 32 new messages

注意:这使用的是ICU的“MessageFormat”语法。为了将大括号(用作文本)转义,请用单引号将其括起来,如'{''}'。为了将单引号(用作文本)转义,只需将其加倍,如it''s。如果您在PHP中使用单引号作为字符串文本,还必须使用反斜杠转义插入的单引号,如\'{\'\'}\'it\'\'s

带上下文的字符串

将您的用户界面的句子、短语和标签包裹在_c函数中

_c('Order', 'sorting');
// or
_c('Order', 'purchase');
// or
_c('Order', 'mathematics');
// or
_c('Order', 'classification');
_c('Address:', 'location');
// or
_c('Address:', 'www');
// or
_c('Address:', 'email');
// or
_c('Address:', 'letter');
// or
_c('Address:', 'speech');

标记为稍后翻译的字符串

将您的用户界面的句子、短语和标签包裹在_m函数中。这是一个无操作指令,即(初看)它不起作用。但它标记了包裹的文本以便稍后翻译。如果文本不应该立即翻译,而将在稍后从变量中翻译,通常是在尽可能晚的时刻,这很有用。

_m('User');
// User

此返回值可以插入到您的数据库中,例如,它将始终使用源代码中的原始字符串。稍后,您可以使用以下调用将字符串从变量中翻译

$text = 'User';
_($text);
// User

提取和更新可翻译字符串

要提取PHP文件中的所有可翻译字符串,您可以使用内置工具执行此任务

# For the `mr-IN` locale, with the default directory, and with the default domain
$ bash ./i18n.sh mr-IN
# For the `sq-MK` locale, with the directory 'translations', and with the default domain
$ bash ./i18n.sh sq-MK translations
# For the `yo-NG` locale, with the default directory, and with the domain 'plugin'
$ bash ./i18n.sh yo-NG "" plugin

这将为指定的语言创建或更新一个PO(可移植对象)文件,然后您可以翻译它、与您的翻译团队合作分享,或发送给外部翻译人员。

如果您只需要一个包含所有提取字符串的通用POT(可移植对象模板)文件,而不针对特定语言,只需省略带有区域设置代码的参数(或将它设置为空字符串)。

# With the default directory and with the default domain
$ bash ./i18n.sh
# With the directory 'translations' and with the default domain
$ bash ./i18n.sh "" translations
# With the default directory and with the domain 'plugin'
$ bash ./i18n.sh "" "" plugin

翻译提取的字符串

无论实际翻译提取字符串的是您、您的翻译团队还是外部翻译人员,负责人员将需要他们语言的PO(可移植对象)文件,或者在某些情况下,需要通用POT(可移植对象模板)文件。

只需打开相关文件,搜索以下带有msgstr ""的字符串。这些是需要您进一步处理的带有空翻译的字符串。此外,任何带有#, fuzzy的字符串之前都有过翻译,但源代码中的原始字符串已更改,因此必须审查翻译(并移除“fuzzy”标志或注释)。

将翻译导出为二进制格式

在您完成翻译并保存语言PO(可移植对象)文件后,您需要再次运行“提取和更新可翻译字符串”中的命令,以便将这些翻译导出为二进制格式。

然后,它们将与您的PO(可移植对象)文件一起存储在MO(机器对象)文件中,准备自动替换原始字符串。

检索活动区域设置

$i18n->getLocale();
// en-US
$i18n->getSystemLocale();
// en_US.utf8

关于区域设置的信息

当前语言的区域设置名称

$i18n->getLocaleName();
// English (United States)
$i18n->getLocaleName('fr-BE');
// French (Belgium)
\Delight\I18n\Locale::toName('nb-NO');
// Norwegian Bokmål (Norway)

区域设置的本地名称

$i18n->getNativeLocaleName();
// English (United States)
$i18n->getNativeLocaleName('fr-BE');
// français (Belgique)
\Delight\I18n\Locale::toNativeName('nb-NO');
// norsk bokmål (Norge)

区域设置的英语名称

\Delight\I18n\Locale::toEnglishName('nb-NO');
// Norwegian Bokmål (Norway)

当前语言的名称

$i18n->getLanguageName();
// English
$i18n->getLanguageName('fr-BE');
// French
\Delight\I18n\Locale::toLanguageName('nb-NO');
// Norwegian Bokmål

语言的本地名称

$i18n->getNativeLanguageName();
// English
$i18n->getNativeLanguageName('fr-BE');
// français
\Delight\I18n\Locale::toNativeLanguageName('nb-NO');
// norsk bokmål

语言的英语名称

\Delight\I18n\Locale::toEnglishLanguageName('nb-NO');
// Norwegian Bokmål

当前语言的脚本名称

\Delight\I18n\Locale::toScriptName('nb-Latn-NO');
// Latin

脚本的本地名称

\Delight\I18n\Locale::toNativeScriptName('nb-Latn-NO');
// latinsk

脚本的英语名称

\Delight\I18n\Locale::toEnglishScriptName('nb-Latn-NO');
// Latin

当前语言的区域名称

\Delight\I18n\Locale::toRegionName('nb-NO');
// Norway

区域的本地名称

\Delight\I18n\Locale::toNativeRegionName('nb-NO');
// Norge

区域的英语名称

\Delight\I18n\Locale::toEnglishRegionName('nb-NO');
// Norway

语言代码

\Delight\I18n\Locale::toLanguageCode('nb-Latn-NO');
// nb

脚本代码

\Delight\I18n\Locale::toScriptCode('nb-Latn-NO');
// Latn

区域代码

\Delight\I18n\Locale::toRegionCode('nb-Latn-NO');
// NO

文本的方向性

\Delight\I18n\Locale::isRtl('ur-PK');
// true
\Delight\I18n\Locale::isLtr('ln-CD');
// true

控制区域设置的查找和比较的宽松度

当使用I18n#setLocaleAutomatically自动确定并激活用户的正确区域时,您可以控制要考虑的类似或相关区域。因此,您可以控制区域查找和比较的方式。

如果默认行为不适合您,只需为 I18n#setLocaleAutomatically 方法提供可选的第一个参数,该参数名为 $leniency。下表列出了匹配所涉及的两个区域代码所需的最小容错值。

故障排除

  • 翻译通常会被缓存,因此可能需要重新启动网络服务器才能使任何更改生效。

贡献

欢迎所有贡献!如果您想贡献,请先创建一个问题,以便讨论您的功能、问题或疑问。

许可

本项目遵循 MIT 许可证 的条款。