本地化
处理区域设置
CodeIgniter 提供了多种工具来帮助你对应用程序进行不同语言的本地化。虽然完整的应用程序本地化是一个复杂的主题,但替换应用程序中不同支持语言的字符串非常简单。
配置区域设置
设置默认区域设置
每个站点都有一个默认的操作语言/区域设置。这可以在 app/Config/App.php 中设置:
<?php
namespace Config;
use CodeIgniter\Config\BaseConfig;
class App extends BaseConfig
{
// ...
public string $defaultLocale = 'en';
// ...
}
该值可以是应用程序用于管理文本字符串和其他格式的任何字符串。建议使用 BCP 47 语言代码。这会生成像 en-US(美式英语)或 fr-FR(法语/法国)这样的语言代码。在 W3C 的网站 上可以找到更易读的介绍。
如果找不到完全匹配项,系统会智能回退到更通用的语言代码。如果区域设置代码设置为 en-US
,而我们只设置了 en
的语言文件,则将使用这些文件,因为更具体的 en-US
不存在。但是,如果 app/Language/en-US 目录中存在语言目录,则将优先使用该目录。
区域检测
重要
区域检测仅适用于使用 IncomingRequest 类的基于 Web 的请求。命令行请求将不具备这些功能。
有两种支持的方法可以在请求期间检测正确的区域设置。
如果需要直接设置区域设置,请参阅 设置当前区域设置。
自 v4.4.0 起,添加了 IncomingRequest::setValidLocales()
来设置(和重置)从 Config\App::$supportedLocales
设置的有效区域。
内容协商
你可以通过 app/Config/App.php 中的两个附加设置来配置自动内容协商。第一个值告诉 Request 类我们确实希望协商区域设置,因此只需将其设置为 true:
<?php
namespace Config;
use CodeIgniter\Config\BaseConfig;
class App extends BaseConfig
{
// ...
public bool $negotiateLocale = true;
// ...
}
启用此功能后,系统将根据你在 $supportLocales
中定义的区域设置数组自动协商正确的语言。如果在你支持的语言与请求的语言之间找不到匹配项,则将使用 $supportedLocales
中的第一个项。在以下示例中,如果找不到匹配项,将使用 en
区域设置:
<?php
namespace Config;
use CodeIgniter\Config\BaseConfig;
class App extends BaseConfig
{
// ...
public array $supportedLocales = ['en', 'es', 'fr-FR'];
// ...
}
在路由中
第二种方法使用自定义占位符来检测所需区域设置并在请求中设置。占位符 {locale}
可以作为段放置在路由中。如果存在,匹配段的内容将是你的区域设置:
$routes->get('{locale}/books', 'App\Books::index');
在此示例中,如果用户尝试访问 http://example.com/fr/books,则区域设置将设置为 fr
,前提是它被配置为有效区域设置。
如果该值与 app/Config/App.php 中 $supportedLocales
定义的有效区域设置不匹配,则将使用默认区域设置,除非你设置为仅使用 App 配置文件中定义的受支持区域设置:
$routes->useSupportedLocalesOnly(true);
备注
自 v4.3.0 起可以使用 useSupportedLocalesOnly()
方法。
设置当前区域设置
IncomingRequest 区域设置
如果要直接设置区域设置,可以使用 IncomingRequest 类 中的 setLocale()
方法:
/** @var \CodeIgniter\HTTP\IncomingRequest $request */
$request->setLocale('ja');
在设置区域设置之前,必须设置有效区域。因为任何尝试设置无效区域设置的操作都将导致设置 默认区域设置。
默认情况下,有效区域在 app/Config/App.php 的 Config\App::$supportedLocales
中定义:
<?php
namespace Config;
use CodeIgniter\Config\BaseConfig;
class App extends BaseConfig
{
// ...
public array $supportedLocales = ['en', 'es', 'fr-FR'];
// ...
}
备注
自 v4.4.0 起,添加了 IncomingRequest::setValidLocales()
来设置(和重置)有效区域。如果要动态更改有效区域,请使用此方法。
语言区域设置
lang()
函数中使用的 Language
类也具有当前区域设置。该设置在实例化期间设置为 IncommingRequest
区域设置。
如果要在实例化语言类后更改区域设置,请使用 Language::setLocale()
方法。
/** @var \CodeIgniter\Language\Language $lang */
$lang = service('language');
$lang->setLocale('ja');
获取当前区域设置
始终可以通过 getLocale()
方法从 IncomingRequest 对象获取当前区域设置。如果你的控制器继承自 CodeIgniter\Controller
,则可以通过 $this->request
获取:
<?php
namespace App\Controllers;
class UserController extends BaseController
{
public function index()
{
$locale = $this->request->getLocale();
}
}
或者,你可以使用 Services 类 来获取当前请求:
$locale = service('request')->getLocale();
语言本地化
创建语言文件
语言字符串存储在 app/Language 目录中,每个支持的语言(区域设置)都有一个子目录:
app/
Language/
en/
App.php
fr/
App.php
备注
语言文件没有命名空间。
语言没有必须遵循的特定命名约定。文件应逻辑命名以描述其包含的内容类型。例如,假设你要创建一个包含错误消息的文件。你可以简单地将其命名为:Errors.php。
在文件中,你将返回一个数组,其中数组中的每个元素都有一个语言键,并可以返回字符串:
<?php
return [
'languageKey' => 'The actual message to be shown.',
];
备注
不能在语言键的开头和结尾使用点(.
)。
它还支持嵌套定义:
<?php
return [
'languageKey' => [
'nested' => [
'key' => 'The actual message to be shown.',
],
],
];
<?php
return [
'errorEmailMissing' => 'You must submit an email address',
'errorURLMissing' => 'You must submit a URL',
'errorUsernameMissing' => 'You must submit a username',
'nested' => [
'error' => [
'message' => 'A specific error message',
],
],
];
基本用法
你可以使用 lang()
辅助函数通过传递文件名和语言键作为第一个参数(用句点 .
分隔)从任何语言文件中检索文本。
例如,要从 Errors.php 语言文件加载 errorEmailMissing
字符串,可以执行以下操作:
echo lang('Errors.errorEmailMissing');
对于嵌套定义,可以执行以下操作:
echo lang('Errors.nested.error.message');
如果请求的语言键在当前区域设置的文件中不存在(在 语言回退 之后),将原样返回字符串。在此示例中,如果不存在,它将返回 Errors.errorEmailMissing
或 Errors.nested.error.message
。
替换参数
你可以将值的数组作为第二个参数传递给 lang()
函数,以替换语言字符串中的占位符。这允许进行非常简单的数字翻译和格式化:
<?php
// The language file, Tests.php:
return [
'apples' => 'I have {0, number} apples.',
'men' => 'The top {1, number} men out-performed the remaining {0, number}',
'namedApples' => 'I have {number_apples, number, integer} apples.',
];
// Displays "I have 3 apples."
echo lang('Tests.apples', [3]);
占位符中的第一个项对应于数组中的项索引(如果是数字):
// Displays "The top 23 men out-performed the remaining 20"
echo lang('Tests.men', [20, 23]);
你也可以使用命名键以便更清晰:
// Displays "I have 3 apples."
echo lang('Tests.namedApples', ['number_apples' => 3]);
显然,你可以做的不仅仅是数字替换。根据底层库的 官方 ICU 文档,可以替换以下类型的数据:
数字 - 整数、货币、百分比
日期 - 短、中、长、完整
时间 - 短、中、长、完整
拼写 - 拼出数字(例如,34 变为三十四)
序数
持续时间
以下是一些示例:
<?php
// The language file, Tests.php
return [
'shortTime' => 'The time is now {0, time, short}.',
'mediumTime' => 'The time is now {0, time, medium}.',
'longTime' => 'The time is now {0, time, long}.',
'fullTime' => 'The time is now {0, time, full}.',
'shortDate' => 'The date is now {0, date, short}.',
'mediumDate' => 'The date is now {0, date, medium}.',
'longDate' => 'The date is now {0, date, long}.',
'fullDate' => 'The date is now {0, date, full}.',
'spelledOut' => '34 is {0, spellout}',
'ordinal' => 'The ordinal is {0, ordinal}',
'duration' => 'It has been {0, duration}',
];
// Displays "The time is now 11:18 PM"
echo lang('Tests.shortTime', [time()]);
// Displays "The time is now 11:18:50 PM"
echo lang('Tests.mediumTime', [time()]);
// Displays "The time is now 11:19:09 PM CDT"
echo lang('Tests.longTime', [time()]);
// Displays "The time is now 11:19:26 PM Central Daylight Time"
echo lang('Tests.fullTime', [time()]);
// Displays "The date is now 8/14/16"
echo lang('Tests.shortDate', [time()]);
// Displays "The date is now Aug 14, 2016"
echo lang('Tests.mediumDate', [time()]);
// Displays "The date is now August 14, 2016"
echo lang('Tests.longDate', [time()]);
// Displays "The date is now Sunday, August 14, 2016"
echo lang('Tests.fullDate', [time()]);
// Displays "34 is thirty-four"
echo lang('Tests.spelledOut', [34]);
// Displays "It has been 408,676:24:35"
echo lang('Tests.ordinal', [time()]);
你应该阅读 MessageFormatter 类和底层 ICU 格式化的文档,以更好地了解其功能,例如执行条件替换、复数化等。前面提供的两个链接将让你很好地了解可用选项。
指定区域设置
要指定用于替换参数的不同区域设置,可以将区域设置作为第三个参数传递给 lang()
函数。
<?php
// Displays "The time is now 23:21:28 GMT-5"
echo lang('Test.longTime', [time()], 'ru-RU');
// Displays "£7.41"
echo lang('{price, number, currency}', ['price' => 7.41], 'en-GB');
// Displays "$7.41"
echo lang('{price, number, currency}', ['price' => 7.41], 'en-US');
如果要更改当前区域设置,请参阅 语言区域设置。
嵌套数组
语言文件还允许使用嵌套数组以便更轻松地处理列表等。
<?php
// Language/en/Fruit.php
return [
'list' => [
'Apples',
'Bananas',
'Grapes',
'Lemons',
'Oranges',
'Strawberries',
],
];
// Displays "Apples, Bananas, Grapes, Lemons, Oranges, Strawberries"
echo implode(', ', lang('Fruit.list'));
语言回退
如果你为某个区域设置(例如 Language/en/App.php)提供了一组消息,则可以为该区域设置添加语言变体,每个变体位于自己的文件夹中,例如 Language/en-US/App.php。
你只需为该区域变体本地化不同的消息提供值。任何缺失的消息定义将自动从主区域设置中提取。
更好的是,本地化可以一直回退到英语(en),以防框架添加了新消息而你尚未有机会为你的区域设置翻译它们。
因此,如果你使用区域设置 fr-CA
,则将首先在 Language/fr-CA 目录中查找本地化消息,然后在 Language/fr 目录中查找,最后在 Language/en 目录中查找。
系统消息翻译
我们在 自己的仓库 中提供了一套 “官方” 系统消息翻译。
你可以下载该仓库,并将其 Language 文件夹复制到你的 app 文件夹中。由于 App
命名空间映射到你的 app 文件夹,因此合并的翻译将自动被识别。
或者,更好的做法是在项目中运行以下命令:
composer require codeigniter4/translations
由于翻译文件夹被正确映射,翻译后的消息将自动被识别。
覆盖系统消息翻译
框架提供 系统消息翻译,你安装的包也可能提供消息翻译。
如果要覆盖某些语言消息,请在 app/Language 目录中创建语言文件。然后,在文件中仅返回要覆盖的数组。
通过命令生成翻译文件
在 4.5.0 版本加入.
你可以自动生成和更新 app 文件夹中的翻译文件。该命令将搜索 lang()
函数的使用,通过定义 Config\App
中的 defaultLocale
区域设置来合并 app/Language 中的当前翻译键。操作完成后,你需要自行翻译语言键。该命令通常能够识别嵌套键 File.array.nested.text
。先前保存的键不会更改。
php spark lang:find
<?php
// Controllers/Translation/Lang.php
$message = lang('Text.info.success');
$message2 = lang('Text.paragraph');
// The following will be saved in Language/en/Text.php
return [
'info' => [
'success' => 'Text.info.success',
],
'paragraph' => 'Text.paragraph',
];
备注
扫描文件夹时,将跳过 app/Language。
生成的翻译文件很可能不符合你的编码标准。建议进行格式化。例如,如果安装了 php-cs-fixer
,则运行 vendor/bin/php-cs-fixer fix ./app/Language
。
在更新之前,可以预览命令找到的翻译:
php spark lang:find --verbose --show-new
--verbose
的详细输出还会显示无效键的列表。例如:
...
Files found: 10
New translates found: 30
Bad translates found: 5
+------------------------+---------------------------------+
| Bad Key | Filepath |
+------------------------+---------------------------------+
| ..invalid_nested_key.. | app/Controllers/Translation.php |
| .invalid_key | app/Controllers/Translation.php |
| TranslationBad | app/Controllers/Translation.php |
| TranslationBad. | app/Controllers/Translation.php |
| TranslationBad... | app/Controllers/Translation.php |
+------------------------+---------------------------------+
All operations done!
为了更精确地搜索,请指定要扫描的区域设置或目录。
php spark lang:find --dir Controllers/Translation --locale en --show-new
可以通过运行命令获取详细信息:
php spark lang:find --help
通过命令同步翻译文件
在 4.6.0 版本加入.
当你完成当前语言的翻译后,可能需要为另一种语言创建文件。你可以使用 spark lang:find
命令来帮助完成此操作。但是,它可能无法检测到所有翻译,特别是那些具有动态设置参数的翻译,例如 lang('App.status.' . $key, ['payload' => 'John'], 'en')
。
为了确保不遗漏任何翻译,最好复制已完成的语言文件并手动翻译它们。这种方法可以保留命令可能遗漏的任何唯一键。
只需执行:
// 指定新/更新翻译的区域设置
php spark lang:sync --target ru
// 或设置原始区域设置
php spark lang:sync --locale en --target ru
结果你将获得包含翻译键的文件。如果目标区域设置中存在重复键,则会保存这些键。
警告
新翻译中不匹配的键将被删除!