pinga/locale
PHP 的国际化与本地化
Requires
- php: >=8.1.0
- ext-gettext: *
- ext-intl: *
This package is auto-updated.
Last update: 2024-09-10 11:44:59 UTC
README
PHP 的国际化与本地化
为您的应用程序提供多语言支持,以适应不同国家的用户,满足不同的格式和约定。基于优秀的 delight-im/PHP-I18N。
需求
- PHP 8.1+
- GNU gettext 扩展 (
gettext
) - 国际化扩展 (
intl
)
- GNU gettext 扩展 (
注意:在 Windows 上,您可能需要使用非线程安全 (NTS) 版本的 PHP。
macOS:使用打包的 bash 脚本生成 po 和 mo 文件,请安装 gnu-sed。
$ brew install gnu-sed
$ PATH="$(brew --prefix)/opt/gnu-sed/libexec/gnubin:$PATH"
安装
-
通过 Composer 包含库 [?]
$ composer require pinga/locale
-
将 i18n bash 脚本复制到项目根目录
$ cp vendor/pinga/locale/i18n.sh .
-
包含 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::UK_UA, \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();
它将根据以下因素(按此顺序)进行检查和决定
-
子域(带区域设置代码)(例如
da-DK.example.com
)注意:子域中的区域设置代码不区分大小写,即
da-dk
也一样,您可以省略地区或脚本名称,即仅da
就足够了。 -
路径前缀(带区域设置代码)(例如
http://www.example.com/pt-BR/welcome.html
)注意:路径前缀中的区域设置代码不区分大小写,即
pt-br
也一样,您可以省略地区或脚本名称,即仅pt
就足够了。 -
查询字符串(带区域设置代码)
- the
locale
参数 - the
language
参数 - the
lang
参数 - the
lc
参数
- the
-
会话字段 通过
I18n#setSessionField
定义(例如$i18n->setSessionField('locale');
) -
Cookie 通过
I18n#setCookieName
定义(例如$i18n->setCookieName('lc');
),可选的生存期通过I18n#setCookieLifetime
定义(例如$i18n->setCookieLifetime(60 * 60 * 24);
),其中null
的值表示 Cookie 将在当前浏览器会话结束时过期 -
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, with the default domain, and with fuzzy matching
$ bash ./i18n.sh mr-IN
# For the `sq-MK` locale, with the directory 'translations', with the default domain, and with fuzzy matching
$ bash ./i18n.sh sq-MK translations
# For the `yo-NG` locale, with the default directory, with the domain 'plugin', and with fuzzy matching $ bash ./i18n.sh yo-NG "" plugin
# For the `fr-FR` locale, with the default directory, with the default domain, and without fuzzy matching $ bash ./i18n.sh fr-FR "" "" nofuzzy
这为指定的语言创建或更新一个PO(可移植对象)文件,然后您可以翻译它,与您的翻译团队合作,或将它发送给外部翻译人员。
如果您只需要一个通用的POT(可移植对象模板)文件,其中包含所有提取的字符串,但不是特定于某种语言,则只需省略带有区域代码的参数(或将它设置为空字符串)。
# With the default directory, with the default domain, and with fuzzy matching
$ bash ./i18n.sh
# With the directory 'translations', with the default domain, and with fuzzy matching $ bash ./i18n.sh "" translations
# With the default directory, with the domain 'plugin', and with fuzzy matching $ bash ./i18n.sh "" "" plugin
# With the default directory, with the default domain, and without fuzzy matching $ bash ./i18n.sh "" "" "" nofuzzy
翻译提取的字符串
无论谁处理提取字符串的实际任务,无论是您、您的翻译团队还是外部翻译人员,负责人员都需要他们语言的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 许可证。