Данный функционал является частью стандартного модуля php-intl, то есть доступен «из коробки» и реализуется с помощью класса MessageFormatter. По сути, это такой printf() на стероидах, с добавлением оператора ветвления и некоторых других возможностей.
Установка модуля intl не должна вызывать сложностей, это стандартное sudo apt install php-intl, либо раскомментировать extension=intl в php.ini под Windows.
Основы
В самом базовом варианте форматтер используется для простой замены плейсхолдеров:
echo msgfmt_format_message("ru", "Привет, {0}!", ['Вася']); echo msgfmt_format_message("ru", "Привет, {name}!", ['name' => 'Вася']);
Как видно, поддерживаются как нумерованные, так и именованные плейсхолдеры.
Но, разумеется, ради таких простых замен не стоило и затеваться, в этом случае проще использовать обычный sprintf(). ICU message formatter используется из-за развитых возможностей форматирования, и — в частности — с учётом локали:
echo msgfmt_format_message( "ru", "Сегодня {0, date, long} или, коротко, {0, date, short}", [new DateTime()] ); // выведет: // Сегодня 7 июня 2023 г. или, коротко, 07.06.23
На этом примере видно, что плейсхолдер может содержать от одного до трех аргументов, и в общем виде вызывается, как
{argNameOrNumber, argType, argStyle}
где argNameOrNumber — это имя или номер плейсхолдера, argType — его тип, и argStyle — дополнительные настройки.
Использование
Перед началом работы зададим режим выброса исключений, чтобы сразу видеть ошибки, которые неизбежно будут возникать при экспериментах с форматами:
ini_set('intl.use_exceptions', true);
Для получения готовых сообщений можно либо сначала создать формат и затем применить его, либо сделать все в одном вызове. Также можно использовать как объектный, так и процедурный интерфейсы. Я буду использовать в статье функцию msgfmt_format_message(), чтобы код примеров был короче и не вызывал горизонтальный скроллинг.
Типы
К сожалению, внятной документации по этой библиотеке я не нашел. Приходится собирать по крохам и экспериментировать методом тыка, выясняя, как примеры из официальной документации применить к РНР.
К примеру, список из 10 основных типов я взял в статье по Yii::t(), ссылка на которую дана выше. Первые 4 из них требуют обязательного наличия всех трех параметров, а остальные позволяют обойтись двумя.
plural select selectordinal choice (объявлен устаревшим) number date time spellout ordinal duration
Дополнительные настройки
Дополнительные настройки, насколько я понял, делятся на два типа: обычные и начинающиеся с двойного двоеточия, которые называются скелетонами. Обычных настроек всегда немного, например для date и time поддерживаются четыре: short, medium, long, full, а для number — три: integer, currency, percent. Но вот скелетонов может быть сколько угодно, причем они могут быть составными, как в последнем примере (group-off отключает разбивку по тысячам):
echo msgfmt_format_message('ru', '{0, number, percent}', [.50]),"\n"; // 50 % echo msgfmt_format_message('ru', '{0, number, ::currency/RUR}', [9999.99]),"\n"; // 9 999,99 р. echo msgfmt_format_message('ru', '{0, number, ::unit/kilogram group-off}', [1000]); // 1000 кг.
К слову, отдельные числа можно форматировать и с помощью другого класса этого же модуля, NumberFormatter:
echo (new NumberFormatter('@numbers=roman', NumberFormatter::DECIMAL)) ->format(date('Y')); // MMXXIII
Интересным типом, относящимся к форматированию чисел, является spellout, который выводит число прописью:
echo msgfmt_format_message('ru', '{0, spellout}', [100500]); // сто тысяч пятьсот
Ветвление
Но самой, пожалуй, интересной возможностью форматтера является встроенный оператор ветвления (аналогичный конструкции switch), который представлен в нескольких вариантах:
-
plural, для образования множественного числа -
select, обычно используется для склонения по родам -
selectordinalдля перечислений (поддержка русского языка ограничена)
В шаблонах игнорируются пробельные символы, и благодаря этому их можно форматировать для удобства восприятия сложных конструкций:
$message = 'Всего {files, plural, one{загружен} other{загружено} } {files, number, ::group-off} {files, plural, one{файл} few{файла} many{файлов} other{файла} }'; echo msgfmt_format_message('ru', $message, ['files' => 9991]),"\n"; // Всего загружен 9991 файл
$message = '{host} {host_gender, select, female{будет счастлива} male{будет счастлив} neuter{будет счастливо} other{будут счастливы} } видеть вас на {event}, {event_gender, select, female {которая состоится} male{который состоится} neuter{которое состоится} other{которые состоятся} } {date, date} в {date, time, short} {place} по адресу {address}.'; $data = [ 'host' => 'Мария Ивановна Заподдубная', 'host_gender' => 'female', 'event_gender' => 'neuter', 'event' => 'праздновании дня рождения', 'date' => new DateTime('2023-05-01 18:00'), 'place' => 'в кафе "Голубка"', 'address' => 'улица Садовая, дом 2', ]; echo msgfmt_format_message('ru', $message, $data),"\n";
выведет
Мария Ивановна Заподдубная будет счастлива видеть вас на праздновании дня рождения, которое состоится 1 мая 2023 г. в 18:00 в кафе "Голубка" по адресу улица Садовая, дом 2.
Соответственно, с другими данными
$data = [ 'host' => 'Полковник Васин', 'host_gender' => 'male', 'event_gender' => 'other', 'event' => 'посиделках', 'date' => new DateTime('2023-05-01 18:00'), 'place' => 'у него дома', 'address' => 'Коммунистический тупик, дом 2', ];
выведет
Полковник Васин будет счастлив видеть вас на посиделках, которые состоятся 1 мая 2023 г. в 18:00 у него дома по адресу Коммунистический тупик, дом 2.
Также поддерживаются вложенные конструкции, например в каждую ветку select можно добавить свой plural, но если честно, то я не смог придумать жизненный пример, и поэтому просто скопирую из документации:
Вложенные конструкции
{gender_of_host, select, female { {num_guests, plural, offset:1 =0 {{host} does not give a party.} =1 {{host} invites {guest} to her party.} =2 {{host} invites {guest} and one other person to her party.} other {{host} invites {guest} and # other people to her party.}}} male { {num_guests, plural, offset:1 =0 {{host} does not give a party.} =1 {{host} invites {guest} to his party.} =2 {{host} invites {guest} and one other person to his party.} other {{host} invites {guest} and # other people to his party.}}} other { {num_guests, plural, offset:1 =0 {{host} does not give a party.} =1 {{host} invites {guest} to their party.} =2 {{host} invites {guest} and one other person to their party.} other {{host} invites {guest} and # other people to their party.}}}}
Экранирование
Сделано довольно занятно, одинарными кавычками. Любой текст, заключенный в них, интерпретируется, как есть. Чтобы экранировать саму кавычку, её надо удвоить:
echo msgfmt_format_message("ru", "Как есть '{0, date}' O''Neal '{time} {1}'", []);
Заключение
Как видно из приведенных выше примеров, данный механизм позволяет легко решать стандартные задачи, связанные со склонениями и перечислениями в русском (и других языках), а также форматировать текст с учётом локали, причем без подключения дополнительных библиотек. Некоторым минусом является отсутствие примеров конкретно для РНР, но я надеюсь что данная публикация немного восполнит этот пробел.
Если у вас есть опыт использования данной библиотеки, то прошу поделиться им в комментариях. В частности, очень хочется научиться выводить римские цифры не отдельным классом, а прямо через MessageFormatter.
ссылка на оригинал статьи https://habr.com/ru/articles/740788/
Добавить комментарий