userfrosting / i18n
UserFrosting 的国际化模块
Requires
- php: >=7.1
- twig/twig: ^2.11
- userfrosting/support: ~4.5.0
Requires (Dev)
- friendsofphp/php-cs-fixer: ^2.13
- mockery/mockery: ^1.2
- phpunit/phpunit: ^7.5 | ^8.5
- victorjonsson/markdowndocs: ^1.3
README
分支 | 构建 | 覆盖率 | 风格 |
---|---|---|---|
master | |||
develop | |
路易斯·夏雷特(Louis Charette)和亚历山大·维斯曼(Alexander Weissman),2016-2019
I18n 模块处理 UserFrosting 的翻译任务。
翻译器使用一个字典,该字典本身与一个区域相关联。系统的基本结构可以表示如下
|------------|
| Locale |
|------------|
|
V
|------------| |---------| |-------------|
| Dictionary | <-- | Locator | <-- | Definitions |
|------------| |---------| |-------------|
|
V
|------------|
| Translator |
|------------|
区域 知道有关区域的所有信息。区域名称、作者、复数规则等。
字典 与特定区域相关联。其目的是返回一个数据矩阵,该矩阵由所有区域共享的键(称为“消息键”)和翻译的字符串(称为“本地化消息”)组成。该系统使用 KEY
和 VALUE
系统,可以存储在标准的 PHP 文件中。
翻译器 使用字典中的数据执行实际翻译,即找到正确的键并用指定的值替换占位符。
目录
文档
基本用法
步骤 1 - 设置语言文件。
语言文件返回一个将 消息键 映射到 本地化消息 的数组。消息可以可选地具有占位符。例如
locale/en_US/main.php
return array(
//LANGUAGE_KEY => Localized message
"ACCOUNT_SPECIFY_USERNAME" => "Please enter your user name.",
"ACCOUNT_SPECIFY_DISPLAY_NAME" => "Please enter your display name.",
"ACCOUNT_USER_CHAR_LIMIT" => "Your user name must be between {{min}} and {{max}} characters in length."
);
locale/es_ES/main.php
return array(
//MESSAGE_KEY => Localized message
"ACCOUNT_SPECIFY_USERNAME" => "Introduce tu nombre de usuario.",
"ACCOUNT_SPECIFY_DISPLAY_NAME" => "Introduce tu nombre público.",
"ACCOUNT_USER_CHAR_LIMIT" => "Tu nombre de usuario debe estar entre {{min}} y {{max}} caracteres de longitud."
);
步骤 2 - 设置区域
Locale
类将加载指定区域的全部 配置值。您只需指定区域标识符作为字符串即可。
$locale = new Locale('en_US');
这将加载所有英语(en_US
)区域配置。当传递给字典时,这将加载所有英语定义。
步骤 3 - 设置 UniformResourceLocator 和字典
Dictionary
将加载指定区域的全部定义。为此,字典将构建位于区域标识符目录(例如,en_US
)中的文件列表,这些文件位于指定的定位器路径。将使用 UniformResourceLocator 获取文件列表。
首先,您需要设置定位器。有关更多信息,请参阅UniformResourceLocator 文档。
// Set up a locator class
$locator = new UniformResourceLocator(__DIR__);
$locator->registerStream('locale');
$locator->registerLocation('core');
// Register the `__DIR__/core/locale/` path
有了定位器和区域,我们现在可以创建字典实例。
$dictionary = new Dictionary($locale, $this->locator);
步骤 4 - 初始化一个 Translator
对象
现在可以使用词典启动翻译器。区域设置将从词典继承。
// Create the Translator object
$translator = new Translator($dictionary);
步骤 5 - 进行翻译!
echo $translator->translate("ACCOUNT_USER_CHAR_LIMIT", [
"min" => 4,
"max" => 200
]);
// Returns "Tu nombre de usuario debe estar entre 4 y 200 caracteres de longitud."
总结
$locator = new UniformResourceLocator(__DIR__);
$locator->addPath('locale', '', 'locale');
$locale = new Locale('en_US');
$dictionary = new Dictionary($locale, $locator);
$translator = new Translator($dictionary);
echo $translator->translate("ACCOUNT_USER_CHAR_LIMIT", [
"min" => 4,
"max" => 200
]);
区域配置文件
每个区域都有自己的配置文件。这些选项需要保存到位于区域文件夹中的 locale.yaml
文件中,可以通过 locale://en_US/locale.yaml
URI 访问。
配置文件可以包含多个选项。例如:
name: French Canadian
regional: Français Canadien
authors:
- Foo Bar
- Bar Foo
plural_rule: 2
parents:
- fr_FR
配置值
名称
区域的名称。应该是名称的英文版本。
地区
区域的本地化名称。例如,对于法语区域,区域在法语中的名称。
作者
该区域的作者列表。
复数规则
与区域关联的复数规则编号。有关详细信息,请参阅复数化。
父级
此区域的父区域列表。将加载父区域的所有数据,包括所有词典定义。
例如,如果 fr_CA
区域的父区域为 fr_FR
,则所有配置和所有未在 CA
翻译中找到的键将回退到 FR
。如果 fr_FR
区域本身以 en_US
作为父区域,则未在 CA
和 FR
中找到的所有键将回退到英文键。
建议所有区域至少以 en_US
作为顶级父区域,因此您区域中的未定义键将回退到英文版本。
复数化
复数系统允许轻松地对字符串进行复数化。整个系统基于 Mozilla 复数规则。对于给定语言,有一个关于如何根据修饰词的数量更改单词的语法规则。不同的语言可能有不同的规则。例如,在英语中你说 no cars
(注意复数 cars
),而在法语中你说 Aucune voiture
(注意单数 voiture
)。
与特定语言关联的规则(见上文链接)定义在区域配置元数据中。因此,对于 english
区域,你应该找到 plural_rule: 1
,在 french
文件中则是 "plural_rule: 2
。
具有复数形式的字符串定义为具有规则作为键的子数组。正确的复数形式由作为 translate
函数第二个参数传递的复数值确定。
"HUNGRY_CATS" => [
0 => "hungry cats",
1 => "hungry cat",
2 => "hungry cats",
]
echo $translator->translate("HUNGRY_CATS", 0); // Return "hungry cats"
echo $translator->translate("HUNGRY_CATS", 1); // Return "hungry cat"
echo $translator->translate("HUNGRY_CATS", 2); // Return "hungry cats"
echo $translator->translate("HUNGRY_CATS", 5); // Return "hungry cats"
用于选择正确形式的复数值默认定义在 plural
占位符中。这意味着 $translator->translate("HUNGRY_CATS", 5)
等同于 $translator->translate("HUNGRY_CATS", ['plural' => 5])
。可以在字符串定义中使用 plural
占位符。请注意,在这种情况下,建议使用 X_
前缀来指示将显示复数。
"X_HUNGRY_CATS" => [
0 => "No hungry cats",
1 => "{{plural}} hungry cat",
2 => "{{plural}} hungry cats",
]
echo $translator->translate("X_HUNGRY_CATS", 0); // Return "No hungry cats"
echo $translator->translate("X_HUNGRY_CATS", 1); // Return "1 hungry cat"
echo $translator->translate("X_HUNGRY_CATS", 2); // Return "2 hungry cats"
echo $translator->translate("X_HUNGRY_CATS", 5); // Return "5 hungry cats"
echo $translator->translate("X_HUNGRY_CATS", ['plural': 5]); // Return "5 hungry cats" (equivalent to the previous one)
在此示例中,您可以看到 0
被用作特殊规则,显示 No hungry cats
而不是 0 hungry cats
以创建更友好的字符串。请注意,可以使用 handles 来覆盖 plural
占位符。
当 translate
函数的第一个参数指向语言定义文件中的复数键,并且第二个参数省略时,默认复数值将为 1
,除非定义了 @TRANSLATION
键(见handles)。在上面的示例中,$translator->translate("X_HUNGRY_CATS", 1)
等同于 $translator->translate("X_HUNGRY_CATS")
。
带占位符的复数值
如果您有多个占位符,则必须将复数值传递给占位符(没有快捷方式)。
"X_EMOTION_CATS" => [
0 => "No {{emotion}} cats",
1 => "One {{emotion}} cat",
2 => "{{plural}} {{emotion}} cats",
]
echo $translator->translate("X_EMOTION_CATS", ['plural': 2, 'emotion': 'hungry']); // Return "2 hungry cats"
echo $translator->translate("X_EMOTION_CATS", ['plural': 5, 'emotion': 'angry']); // Return "5 angry cats"
字符串中的多个复数
如果本地化字符串包含多个复数形式,例如 1位访客和4位朋友目前在线
,可以通过将 ONLINE_GUEST
和 ONLINE_FRIEND
键嵌套到 ONLINE_USERS
中来应用复数规则到 guest
和 friends
"ONLINE_GUEST" => [
0 => "0 guests",
1 => "1 guest",
2 => "{{plural}} guests"
],
"ONLINE_FRIEND" => [
0 => "0 friends",
1 => "1 friend",
2 => "{{plural}} friends"
],
"ONLINE_USERS" => "{{guest}} and {{friend}} currently online",
[...]
$online_guest => $translator->translate("ONLINE_GUEST", 1);
$online_friend => $translator->translate("ONLINE_FRIEND", 4);
echo $translator->translate("ONLINE_USERS", ["guest" => $online_guest, "friend" => $online_friend]); // Returns "1 guest and 4 friends currently online"
注意,在面临使用多个子字符串或复数形式的冗长句子时,可以使用嵌套翻译,但在可能的情况下应避免。应该优先考虑更短或多个句子。在这种情况下,特殊的 处理程序 也可以很有用。
数字是规则,而不是限制!
非常重要:语言文件中定义的 数字 与复数值无关,而是与 复数规则 有关。因此,这是完全错误的。
"X_HUNGRY_CATS" => [
0 => "No hungry cats",
1 => "One hungry cat",
2 => "{{plural}} hungry cats",
5 => "A lot of hungry cats"
]
echo $translator->translate("X_HUNGRY_CATS", 2); // Return "2 hungry cats"
echo $translator->translate("X_HUNGRY_CATS", 5); // Return "5 hungry cats", NOT "A lot of hungry cats"!
关于复数化的一点...
在某些情况下,直接访问复数值可能更快、更简单。例如,当字符串始终是复数时。考虑以下示例
"COLOR" => [
0 => "colors",
1 => "color",
2 => "colors"
],
"COLORS" => "Colors",
在这个例子中,$translator->translate("COLOR", 2);
和 $translator->translate("COLORS");
将返回相同的值。这在英语中是正确的,但并不一定适用于所有语言。对于没有任何复数形式定义的语言,它们将定义类似 "COLOR" => "Color"
和 "COLORS" => "Color"
的内容,而一些语言将拥有更复杂的规则。这就是为什么在计划翻译到多种语言时,最好避免像 COLORS
这样的键。这也适用于不同的语言中可能不同的 0
值,但也可以根据要显示的消息不同地处理(例如:使用 无颜色
而不是 0 个颜色
)。
子键
可以在语言文件中定义子键以简化列表的导航或区分具有常见键的两个项目。例如
return [
"COLOR" => [
"BLACK" => "black",
"RED" => "red",
"WHITE" => "white"
]
];
可以使用 点语法 访问子键。因此,$translator->translate('COLOR.BLACK')
将返回 black
。当多个 主键 共享相同的子键时,子键也非常有用。
return [
"METHOD_A" => [
"TITLE" => "Scénario A",
"DESCRIPTION" => "..."
],
"METHOD_B" => [
"TITLE" => "Scénario B",
"DESCRIPTION" => "..."
]
];
$method = Method->get(); // return $method = "METHOD_A";
echo $translator->translate("$method.TITLE"); // Print "Scénario A"
当然,子键和复数规则可以同时存在于同一个主键中。
"COLOR" => [
//Substrings
"BLACK" => "black",
"RED" => "red",
"WHITE" => "white",
//Plurals
1 => "color",
2 => "colors"
]
处理
可以在语言文件中定义一些特殊处理程序以修改翻译器的默认行为。这些处理程序使用 @
前缀。
@TRANSLATION
如果您想为顶级键提供值,可以使用 @TRANSLATION
(处理程序)[#handles],它将创建一个别名 TOP_KEY
并将其指向 TOP_KEY.@TRANSLATION
。
return [
"ACCOUNT" => [
"@TRANSLATION" => "Account",
"ALT" => "Profile"
]
];
$translator->translate('ACCOUNT') //Return "Account"
$translator->translate('ACCOUNT.@TRANSLATION') //Return "Account"
$translator->translate('ACCOUNT.ALT'); //Return "Profile"
注意:当与复数规则一起使用 @TRANSLATION
时,省略 translate
函数的第二个参数将改变结果。1
不会作为复数值来确定我们选择哪个规则。将返回 @TRANSLATION
值。例如
"X_HUNGRY_CATS" => [
"@TRANSLATION => "Hungry cats",
0 => "No hungry cats",
1 => "{{plural}} hungry cat",
2 => "{{plural}} hungry cats",
]
如上定义的 @TRANSLATION
,$translator->translate("X_HUNGRY_CATS");
将返回 Hungry cats
。移除 @TRANSLATION
处理程序后,相同的 $translator->translate("X_HUNGRY_CATS");
现在将返回 1 hungry cat
。
@PLURAL
可以通过语言文件中的 @PLURAL
处理程序覆盖默认的 plural
占位符。如果将现有数组传递给翻译函数,这可能很有用。
"NB_HUNGRY_CATS" => [
"@PLURAL" => "nb",
0 => "No hungry cats",
1 => "One hungry cat",
2 => "{{nb}} hungry cats",
]
echo $translator->translate("NB_HUNGRY_CATS", 2); // Return "2 hungry cats"
echo $translator->translate("NB_HUNGRY_CATS", ['nb': 5]); // Return "5 hungry cats"
&
占位符
当翻译文件中的占位符名称以 &
字符开头或占位符的值以相同的 &
字符开头时,这告诉翻译器直接用映射到该消息键的消息(如果存在)替换占位符。请注意,这是大小写敏感的,并且与其他处理程序一样,所有在主要翻译函数中定义的占位符都传递给所有子翻译。这在您不想在同一个语言文件中重复翻译相同的单词或使用具有复数值的复杂翻译时非常有用。在使用复数时要小心,因为复数值将传递给所有子翻译,可能会引起冲突(参见 复杂翻译的示例)。
示例
"MY_CATS" => [
1 => "my cat",
2 => "my {{plural}} cats"
];
"I_LOVE_MY_CATS" => "I love {{&MY_CATS}}";
$translator->translate('I_LOVE_MY_CATS', 3); //Return "I love my 3 cats"
在这个例子中,{{&MY_CATS}}
会被替换为MY_CATS
,由于有3只猫,所以选择了第2条规则。因此,字符串变为I love my {{plural}} cats
,然后变为I love my 3 cats
。
注意:由于这是翻译器处理的最后一件事情,这种行为可以被函数调用覆盖。
$translator->translate('I_LOVE_MY_CATS', ["plural" => 3, "&MY_CATS" => "my 3 dogs"); //Return "I love my 3 dogs"
由于其他占位符,包括复数值也会传递到子翻译中,这对于像法语这样的语言很有用,其中形容词也可以是“可复数的”。考虑这个句子:I have 3 white catS
。在法语中,我们会说J'ai 3 chatS blancS
。注意颜色blanc
上的S
吗?一个开发者可能会在英语环境中做这个。
$colorString = $translator->translate('COLOR.WHITE');
echo $translator->translate('MY_CATS', ["plural" => 3, "color" => $colorString);
虽然在英语中这会起作用,因为颜色不是“可复数的”,但在法语中就不会。我们最终会得到J'ai 3 chatS blanc
(颜色上没有S
)。我们需要的是调用翻译并使用&
前缀将颜色键作为占位符传递的PHP代码:$translator->translate('MY_CATS', ["plural" => 3, "color" => "&COLOR.WHITE"]);
。在这种情况下,两种语言的翻译文件如下
英语
"COLOR" => [
"RED" => "red",
"WHITE" => "white",
[...]
];
"MY_CATS" => [
0 => "I have no cats",
1 => "I have a {{color}} cat",
2 => "I have {{plural}} {{color}} cats"
];
法语
"COLOR" => [
"RED" => [
1 => "rouge",
2 => "rouges"
"WHITE" => [
1 => "blanc",
2 =. "blancs"
].
[...]
];
"MY_CATS" => [
0 => "I have no cats",
1 => "I have a {{color}} cat",
2 => "I have {{plural}} {{color}} cats"
];
由于占位符(["plural" => 3, "color" => "&COLOR.WHITE"]
)将在翻译COLOR.WHITE
键时传递给翻译函数,所以法语的正确复数形式将被返回,得到J'ai 3 chatS blancS
。即使没有任何复数值,使用这种方法定义两个翻译函数也要短得多。
$translator->translate('MY_CATS', ["color" => "&COLOR.WHITE"]);
与
$colorString = $translator->translate('COLOR.WHITE');
echo $translator->translate('MY_CATS', ["color" => $colorString);
最后,如果子翻译键在翻译文件中缺失,我们简单地得到未翻译的占位符(这在某些情况下可能是你想要的):I have 3 COLOR.WHITE cats
。
复杂翻译示例
语言文件
return [
"COMPLEX_STRING" => "There's {{&X_CHILD}} and {{&X_ADULT}} in the {{color}} {{&CAR.FULL_MODEL}}",
"X_CHILD" => [
"@PLURAL" => "nb_child",
0 => "no children",
1 => "a child",
2 => "{{plural}} children",
],
"X_ADULT" => [
"@PLURAL" => "nb_adult",
0 => "no adults",
1 => "an adult",
2 => "{{nb_adult}} adults",
],
"CAR" => [
"FULL_MODEL" => "{{make}} {{model}} {{year}}"
],
"COLOR" => [
"BLACK" => "black",
"RED" => "red",
"WHITE" => "white"
]
];
翻译函数
$carMake = "Honda";
echo $translator->translate("COMPLEX_STRING", [
"nb_child" => 1,
"nb_adult" => 0,
"color" => "&COLOR.WHITE",
"make" => $carMake,
"model" => "Civic",
"year" => 1993
]);
结果
There's a child and no adults in the white Honda Civic 1993
风格指南
测试
API 文档
构建文档
vendor/bin/phpdoc-md generate src/ > docs/README.md