Yii 2 - Интернационализация проекта (мультиязычность)

2016-08-12
Yii

Рассмотрим пример интернационализации сайта на Yii 2 на двух языках: ru и en.

В Yii 2 уже встроен компонент отвечающий за интернационализацию, называется он i18n. Для того что бы можно было начать им пользоваться, достаточно добавить его в конфигурацию приложения в раздел components. Также, для полноценной работы этого компонента, требуется PHP расширение intl.

В данной статье будет рассмотрен пример интернационализации сайта на Yii 2 на двух языках: ru и en.

Существуют два варианта записей идентификаторов языков:

  • сокращенные, например ru, en;
  • полные, например: ru-RU, en-US.

Мы будем использовать сокращенный вариант (ru и en), т.к. его вполне достаточно для требуемой функциональности и он более лаконично воспринимается визуально.

В качестве кодовой базы можно использовать как уже готовый сайт на Yii 2, так и basic шаблон предоставляемый фреймворком. Как установить basic шаблон можно прочитать в этой статье.

Задача

Сформулируем требования, чего необходимо добиться:

  • переключение сайта между двумя языками: ru и en;
  • отображение языка в адресной строке в виде http://site.com/en/;
  • автоматическое перенаправление пользователя на наиболее подходящий для него язык, если он перешел на сайт без указания языка;
  • хранение переводов должно осуществляется в PHP файлах в виде массивов;
  • переводы статических страниц должны содержаться в отдельных представлениях для разных языков.

Конфигурация

Начинаем реализацию с прописывания требуемых для этого конфигураций в файл \config\web.php:

[
    ...
    'sourceLanguage' => 'en',
    'language' => 'en',
    ...
    'components' => [
        ...
        'urlManager' => [
            'enablePrettyUrl' => true,
            'showScriptName' => false,
            'rules' => [
            ],
        ],
        ...
        'i18n' => [
            'translations' => [
                'common*' => [
                    'class' => 'yii\i18n\PhpMessageSource',
                    'basePath' => '@app/messages',
                ],
            ],
        ],
        ...
    ]
]

В этой конфигурации:

sourceLanguage - исходный язык сообщений, т.е. тот который будет использован в виде ключей переводов, в данном случае en.

language - язык по умолчанию, на который будет перенаправлен пользователь в случае невозможности определения для него наиболее подходящего языка на основе данных предоставляемых его браузером.

components -> urlManager - в данной секции включаем ЧПУ, для более наглядного отображения URL'ов.

components -> i18n - эта секция предназначена для конфигурирования компонента интернационализации. Ключ массива common* - означает категорию к которой будут относиться переводы из соответствующего файла common.php. Название категории можно указать и любое другое, или например сделать несколько категорий, но для примера обойдемся одной. В секции 'class' => 'yii\i18n\PhpMessageSource' необходимо указать как именно мы будем хранить сообщения, в данном случае указано что в виде PHP файлов (в виде ассоциативных массивов). Кроме yii\i18n\PhpMessageSource существуют и другие варианты, например сообщения можно хранить в БД. В секции 'basePath' => '@app/messages' указан путь по которому будут располагаться PHP файлы с переводами.

Файл со списком переводов

Теперь необходимо создать PHP файл, который будет содержать в себе ассоциативный массив с переводами.

При конфигурировании компонента i18n мы указали:

...
'translations' => [
    'common*' => [
        'class' => 'yii\i18n\PhpMessageSource',
        'basePath' => '@app/messages',
    ],
],
...

Это означает что файл с переводами должен иметь такое же имя, как и имя категории, т.е. common.php. Храниться он должен по такому пути \messages\ru\common.php.

Для нашего примера с двумя языками (ru и en), достаточно создать один файл с переводами только для русского языка, т.к. переводы английского языка будут в виде ключей для всех остальных переводов. Т.е. везде по сайту будут английские сообщения, при переключении на русский язык, фреймворк будет их подменять на русские.

Например если бы мы хотели добавить какой либо дополнительный язык, например de, то кроме файла \messages\ru\common.php нам нужно было бы еще создать аналогичный для немецкого языка \messages\de\common.php. Но в нашем примере будет только один файл \messages\ru\common.php, создадим его, пока с пустым массивом:

<?php

return [];

Вносим переводы

Теперь необходимо внести переводы по коду сайта и также добавить их в файл \messages\ru\common.php.

Для вывода переводов в Yii 2 существует специальный метод Yii::t(). Пример использования:

<?= Yii::t('common', 'Example text...') ?>

В первом аргументе указываем категорию, у нас она одна - common. Во втором аргументе пишем английский текст в том виде как он должен отображаться на сайте.

После этого необходимо добавить русский перевод в файл \messages\ru\common.php:

return [
    ...
    'Example text...' => 'Пример текста...',
    ...
];

В качестве ключа используем английский перевод, а в качестве значения русский.

Необходимо проделать такие действия по всему коду, т.е. все отображаемые тексты позаменять на английские, причем выводить их надо с помощью Yii::t(). И также добавить для всех английских текстов переводы в файл \messages\ru\common.php.

Переключение языков

У пользователя должна быть возможность как то переключаться между языками. Для этого необходимо отображать ссылки для переходя на русский или английский язык. Для этой цели создадим специальный скрипт представления \views\layouts\main\select-language.php:

<?php

use yii\bootstrap\Html;

if(\Yii::$app->language == 'ru'):
    echo Html::a('Go to English', array_merge(
        \Yii::$app->request->get(),
        [\Yii::$app->controller->route, 'language' => 'en']
    ));
else:
    echo Html::a('Перейти на русский', array_merge(
        \Yii::$app->request->get(),
        [\Yii::$app->controller->route, 'language' => 'ru']
    ));
endif;

Далее необходимо подключить этот скрипт в главном шаблоне представления \views\layouts\main.php, для этого следующую строку:

<p class="pull-left">&copy; My Company <?= date('Y') ?></p>

нужно заменить на:

<p class="pull-left">&copy; My Company <?= date('Y') ?> | <?= $this->render('main/select-language') ?></p>

В этой строке мы добавили рендеринг скрипта представления для переключения языков, благодаря ему в футере будет отображаться ссылка для перехода на другой язык. Выглядеть это будет примерно так:

Yii 2 - Интернационализация проекта

Использование стороннего UrlManager

Теперь нам необходимо сделать так, что бы в строке адреса отображался текущий язык, например http://site.com/en. К сожалению Yii 2 не предоставляет для этого каких либо встроенных возможностей, и даже сделать это какими то адекватными обходными путями не получиться, или по крайней мере сложно. Самым "правильным" выходом в этой ситуации будет переопределение стандартного класса yii\web\UrlManager на свой собственный, в котором будут заменены некоторые методы. Эти замененные методы должны брать на себя реализацию отображения языка в строке адреса.

Вообще замена стандартных классов фреймворка на сторонние, лично на мой взгляд это хард-код. Но судя по всему в рамках фреймворка Yii 2 это к сожалению нормальная практика.

Писать собственную реализацию класса UrlManager мы не будем, т.к. данный "велосипед" уже создан и его можно просто подключить через Composer, что мы и сделаем. Переходим в консоле в корневую папку проекта, и выполняем команду (тут имеется ввиду что Composer установлен глобально):

$ composer require codemix/yii2-localeurls

Пакет codemix/yii2-localeurls дает нам следующие возможности:

  • отображение текущего языка в адресной строке;
  • перенаправление пользователя на наиболее подходящий для него язык на основе данных отправляемых его браузером.

Далее нам нужно переконфигурировать компонент urlManager так, что бы он использовал не стандартный класс фреймворка, а только что подключенный. Для этого в файле \config\web.php, в секцию componets -> urlManager добавляем следующие три строки:

[
    ...
    'components' => [
        ...
        'urlManager' => [
            ...
            'class' => 'codemix\localeurls\UrlManager',
            'languages' => ['ru', 'en'],
            'enableDefaultLanguageUrlCode' => true,
            ...
        ],
        ...
    ]
    ...
]

Немного объяснений по данной конфигурации:

'class' => 'codemix\localeurls\UrlManager' - указываем фреймворку что бы вместо стандартного класса UrlManager был использован тот который мы только что подключили через Composer.

'languages' => ['ru', 'en'] - на какие языки переведен проект.

'enableDefaultLanguageUrlCode' => true - этот флаг означает что мы хотим что бы текущий язык отображался в адресной строке всегда, даже если пользователь просто находиться на главной странице (т.е. адрес будет вида http://site.com/en). При желании этот флаг можно и не указывать, по умолчанию он будет иметь значение false, что означает что на главной странице язык в адресной строке отображаться не будет (т.е. адрес будет вида http://site.com).

Интернационализация статических страниц

В Yii 2 есть встроенный класс \yii\web\ViewAction, который предназначен для реализации статических страниц.

Но по умолчанию интернационализацию он не поддерживает.

Суть в том, что статические страницы имеют большой текст, который не удобно вставлять в файлы переводов (т.е. в нашем случае в файл \messages\ru\common.php). Переводы для статических страниц лучше всего хранить в отдельных файлах - в разных скриптах преставлений которые будут соответствовать разным языкам.

Реализуем интернационализированные статические страницы, для этого в файле \controllers\SiteController.php, в методе actions() необходимо добавить следующую секцию:

public function actions()
{
    return [
        ...
        'page' => [
            'class' => \yii\web\ViewAction::className(),
            'viewPrefix' => 'pages/' . \Yii::$app->language
        ]
        ...
    ];
}

В этой секции и находиться основной код который позволяет интернационализировать статические страницы, это 'viewPrefix' => 'pages/' . \Yii::$app->language. В параметре viewPrefix мы указываем по какому пути у нас будут находиться скрипты представлений для статических страниц, но в нашем случае мы еще и подставляем в этот путь текущий язык \Yii::$app->language.

Далее создадим два файла представления, которые будут соответствовать одной статической странице, но каждый будет для своего языка. Для английского \views\site\pages\en\some.php должен содержать текст: EN static page.... Для русского \views\site\pages\ru\some.php должен содержать текст Русская статическая страница....

Теперь настроим ЧПУ для наглядного отображения адресов статических страниц, для этого в конфиге \config\web.php в секцию urlManager -> rules добавим правило:

'urlManager' => [
    ...
    'rules' => [
        'page/<view:[a-zA-Z0-9-]+>' => 'site/page',
    ],
    ...
],

На этом, реализация статических страниц закончена, можно проверить, попробовать открыть два адреса в браузере:

  • http://site.com/ru/page/some - откроется русская статическая страница;
  • http://site.com/en/page/some - английская.

Заключение

В целом реализация интернационализации в Yii 2 не представляет ничего сложного, особенно если использовать сторонний компонент codemix/yii2-localeurls.

Задача, которая была описана в начале статьи полностью выполнена:

  • переключение сайта между двумя языками происходит по ссылке которая расположена в футере;
  • текущий язык отображается в адресной строке в виде http://site.com/en/;
  • если пользователь перешел на сайт не указав язык (т.е. http://site.com), то он будет перенаправлен на наиболее подходящий язык (т.е. например на http://site.com/en);
  • хранение переводов осуществляется в PHP файлах в виде ассоциативных массивов;
  • переводы статических страниц хранятся в отдельных представлениях для разных языков.