Локализация¶
Namespace Keel\I18n. Translator над PHP-файлами сообщений с плейсхолдерами и формами
множественного числа, LocaleMiddleware, выбирающий локаль запроса, Locale, внедряемая в
хэндлеры, и фильтр trans для Twig и Latte.
// config/i18n.php
return static fn(string $projectDir): I18nConfig => new I18nConfig(
path: $projectDir . '/translations', // <path>/<locale>/<domain>.php
locales: ['en', 'ru', 'pt-BR'],
defaultLocale: 'en',
fromPath: true, // /ru/about
sessionKey: 'locale', // после SessionMiddleware
acceptLanguage: true,
);
// config/services.php
return new ServicesConfig([I18nProvider::class]);
// config/http.php
return new HttpConfig(middleware: [SessionMiddleware::class, LocaleMiddleware::class]);
// translations/en/messages.php
return [
'hello' => 'Hello, {name}!',
'cart' => ['items' => '{count} item|{count} items'],
];
// translations/ru/messages.php
return [
'hello' => 'Привет, {name}!',
'cart' => ['items' => '{count} товар|{count} товара|{count} товаров'],
];
#[Get('/cart')]
public function cart(Translator $t, Locale $locale): array
{
return ['title' => $t->trans('cart.items', ['count' => 3]), 'locale' => $locale->value]; // "3 товара", "ru"
}
Сообщения¶
Файл сообщений возвращает вложенный массив; ключи — пути через точку (cart.items), плоский
ключ с точками побеждает путь. {placeholders} подставляются из параметров. Сообщение с |
перечисляет формы числа в порядке CLDR (one|other для английского, one|few|many для
русского, украинского, польского; one|few|other для чешского и так далее), а параметр
count выбирает форму по правилу языка. С загруженным ext-intl ICU-шаблон
({count, plural, one {# item} other {# items}}) форматирует MessageFormatter. Отсутствующее
сообщение возвращается как свой ключ, так что до появления перевода ничего не ломается; has()
подскажет. Аргумент domain называет другой файл (errors.php); locale перекрывает текущую.
Сообщение, которого нет в локали запроса, ищется в fallbackLocale, иначе в локали по
умолчанию. Файлы загружаются один раз на воркер и остаются в памяти; текущая локаль — на запрос:
переводчик Resettable, а LocaleMiddleware выставляет её (LocaleAware). Поэтому перевод
внутри сервиса — это локаль запроса без лишней обвязки.
Выбор локали¶
LocaleMiddleware перебирает включённые в конфиге источники в таком порядке и берёт первый,
который называет поддерживаемую локаль: ведущий сегмент пути (fromPath, /ru/about),
query-параметр (queryParam), ключ сессии (sessionKey, поэтому SessionMiddleware идёт
раньше), cookie (cookie), заголовок Accept-Language (acceptLanguage, с учётом q и
сопоставлением en-GB → en или pt → pt-BR, когда поддерживаются только они). Если ничего
не подошло — defaultLocale. Локаль становится атрибутом запроса Locale (value,
language(), region()) и заголовком ответа Content-Language; согласованная по заголовку
локаль добавляет Vary: Accept-Language. Теги приводятся к каноническому виду (en_us → en-US).
Сообщения валидации (must not be blank, ...) не переводятся: errors API предназначены
разработчикам, а HTML-формы рисуют свои сообщения по путям полей. Правила с аргументом
message принимают переведённый текст.
Шаблоны¶
Если зарегистрирован Translator, TwigProvider и LatteProvider добавляют trans как
фильтр и функцию, а также locale():
{{ 'hello'|trans({name: user.name}) }} {{ trans('cart.items', {count: cart.count}) }} [{{ locale() }}]
{='hello'|trans: ['name' => $user->name]} {trans('cart.items', ['count' => $cart->count])} [{locale()}]