Ми}{алы4

Блог хеллоуворлдщика

Многоязычность или мультиязычность в PHP

03.03.2019 php, icu, intl, i18n

Многие сталкиваются с такой проблемой, как поиск подходящего решения в решение вопроса с мультиязычностью и переводами в целом. Мало кто знает, но в PHP есть такое расширение Intl, которое из коробки поддерживает различные фичи связанные с переводами, форматированием и прочими плюхами полезными не только для мультиязычных проектов, но и одноязычных тоже. Все это называется - интернационализация. И сегодня я хочу осветить такой вовсе не странный и весьма простой вопрос с переводами. Дак, как же все-таки сделать это просто, быстро и безболезненно? Читайте дальше! =)

В первую очередь, что необходимо сделать, это убедиться, что у вас установлено расширение Intl с PHP,

$  php -m | grep intl

(если вывод будет пустой, то значит расширения этого у вас нет и его следует установить),

да и вообще "покурить" документацию к нему. Расширение Intl реализовано на базе ICU, а значит, все, что справедливо для ICU справедливо и для нас.

Перейдем к коду.

Подготовим наш конфигурационный файл с сообщениями:

// config/messages.php
<?php declare(strict_types=1);

return [
	'User' => [
		'ru-RU' => [
			'SAY_HELLO' =>  'Привет, {0}!',
			'NUMBER_OF_APPLE' => 'У меня {0, plural, one{# яблоко} few{# яблока} many{# яблок} other{# яблока}}',
		],
		'en-US' => [
			'SAY_HELLO' =>  'Hello, {0}!',
			'NUMBER_OF_APPLE' => 'I have {0, plural, one{# apple} other{# apples}}',
		],
	],
];

И сделаем интерфейс от которого мы позже можем плясать и без проблемно заменять реализации в проекте (привет SOLID):

<?php declare(strict_types=1);

namespace I18n;

interface TranslatorInterface
{
    public function t(string $message, string $domain, ...$params): string;
}

Здесь все просто: первый аргумент - сообщение, второй - группа сообщений, третий - дор. параметры для сообщения, если есть.

А сейчас реализуем сам транслятор сообщений:


<?php declare(strict_types=1);

namespace I18n;

use MessageFormatter;

class FsTranslator implements TranslatorInterface
{
    /**
     * @var array
     */
    private $messages;
    /**
     * @var string
     */
    private $locale;

    public function __construct(array $messages, string $locale)
    {
        $this->messages = $messages;
        $this->locale = $locale;
    }

    public function t(string $message, string $domain, ...$params): string
    {
        if (isset($this->messages[$domain][$this->locale][$message])) {
            $message = $this->messages[$domain][$this->locale][$message];
        }

        return (new MessageFormatter($this->locale, $message))->format($params);
    }
}

В классе FsTranslator мы использовали MessageFormatter из Intl, документация к нему здесь.

Использование:


// Russian
$translator = new \I18n\FsTranslator(require __DIR__ . '/config/messages.php', 'ru-RU');

$translator->t('SAY_HELLO', 'User', 'Иван'); // Привет, Иван!

$translator->t('NUMBER_OF_APPLE', 'User', 1); // У меня 1 яблоко
$translator->t('NUMBER_OF_APPLE', 'User', 2); // У меня 2 яблока
$translator->t('NUMBER_OF_APPLE', 'User', 3); // У меня 3 яблока
$translator->t('NUMBER_OF_APPLE', 'User', 5); // У меня 5 яблок
$translator->t('NUMBER_OF_APPLE', 'User', 1.5); // У меня 1,5 яблока

// English
$translator = new \I18n\FsTranslator(require __DIR__ . '/config/messages.php', 'en-US');

echo $translator->t('SAY_HELLO', 'User', 'Ivan'); // Hello, Ivan!

$translator->t('NUMBER_OF_APPLE', 'User', 1); // I have 1 apple
$translator->t('NUMBER_OF_APPLE', 'User', 2); // I have 2 apples
$translator->t('NUMBER_OF_APPLE', 'User', 3); // I have 3 apples
$translator->t('NUMBER_OF_APPLE', 'User', 5); // I have 5 apples
$translator->t('NUMBER_OF_APPLE', 'User', 1.5); // I have 1,5 apples

Вот и все. Это, конечно, не все возможности. Все я, к сожалению, охватить не могу. Поэтому, читайте документацию к ICU. =)

Полезные ссылки: