[Из песочницы] Yii2 и организация мультиязычности

Долгожданный релиз Yii 2.0 Beta дал стимул многим разработчикам, использующих Yii, к переходу на вторую версию фреймворка. Разработчики фреймворка указали, что постараются не трогать обратную совместимость и в основном будут сосредоточены над исправлением ошибок и заканчивать документацию. Это дает еще больший импульс к использованию Yii2 в реальных проектах.Мы решили не отставать от новшеств и выбрали именно вторую версию замечательного фреймворка Yii. При разработке проекта возникла необходимость в организации мультиязычности на сайте.

Постановка задачи1. Количество языков неограниченно.2. URL сайта представлены как ЧПУ и SEO оптимизированы. Ссылки вида: example.com/en/mypageexample.com/ru/mypageexample.com/de/mypage3. Минимальные изменения в работе с фреймворком. Ресурс по ссылке example.com/mypage должен отдаваться на языке, установленным по умолчанию. Правила роутинга не должны изменяться в зависимости от количества языков.Хранение языков Исходя из того, что количество языков неограниченно и должен указываться язык по умолчанию, то было решено хранить этот список в отдельной таблице БД. Создаем таблицу lang с такими полями: id — идентификатор языкаurl — буквенный идентификатор языка для отображения в URL (ru, en, de,…)local — язык (локаль) пользователяname — название (English, Русский,…)default — флаг, указывающий на язык по умолчанию (1 — язык по умолчанию)date_update — дата обновления (в unixtimestamp)date_create — дата создания (в unixtimestamp) CREATE TABLE IF NOT EXISTS `lang` ( `id` int (11) NOT NULL AUTO_INCREMENT, `url` varchar (255) NOT NULL, `local` varchar (255) NOT NULL, `name` varchar (255) NOT NULL, `default` smallint (6) NOT NULL DEFAULT '0', `date_update` int (11) NOT NULL, `date_create` int (11) NOT NULL, PRIMARY KEY (`id`) ) ENGINE=InnoDB DEFAULT CHARSET=utf8 AUTO_INCREMENT=1; И вносим в таблицу два языка, учитывая что один должен быть из значением default=1:

INSERT INTO `lang` (`url`, `local`, `name`, `default`, `date_update`, `date_create`) VALUES ('en', 'en-EN', 'English', 0, UNIX_TIMESTAMP (), UNIX_TIMESTAMP ()), ('ru', 'ru-RU', 'Русский', 1, UNIX_TIMESTAMP (), UNIX_TIMESTAMP ()); Или cоздаем миграцию, выполнив команду php yii migrate/create lang. В созданный файл вставляем:

public function safeUp () { $tableOptions = null; if ($this→db→driverName === 'mysql') { $tableOptions = 'CHARACTER SET utf8 COLLATE utf8_general_ci ENGINE=InnoDB'; }

$this→createTable ('{{%lang}}', [ 'id' => Schema: TYPE_PK, 'url' => Schema: TYPE_STRING. '(255) NOT NULL', 'local' => Schema: TYPE_STRING. '(255) NOT NULL', 'name' => Schema: TYPE_STRING. '(255) NOT NULL', 'default' => Schema: TYPE_SMALLINT. ' NOT NULL DEFAULT 0', 'date_update' => Schema: TYPE_INTEGER. ' NOT NULL', 'date_create' => Schema: TYPE_INTEGER. ' NOT NULL', ], $tableOptions);

$this→batchInsert ('lang', ['url', 'local', 'name', 'default', 'date_update', 'date_create'], [ ['en', 'en-EN', 'English', 0, time (), time ()], ['ru', 'ru-RU', 'Русский', 1, time (), time ()], ]); }

public function safeDown () { $this→dropTable ('{{%lang}}'); } Применяем миграцию командой php yii migrate.

Модель языка С помощью gii создаем модель Lang и генерируем CRUD.В модель добавляем поведение для автоматического обновления даты при редактировании и создании записи в таблице lang: public function behaviors () { return [ 'timestamp' => [ 'class' => 'yii\behaviors\TimestampBehavior', 'attributes' => [ \yii\db\ActiveRecord: EVENT_BEFORE_INSERT => ['date_create', 'date_update'], \yii\db\ActiveRecord: EVENT_BEFORE_UPDATE => ['date_update'], ], ], ]; } Так же добавим вспомогательные методы для работы с объектом языка в модель Lang:

//Переменная, для хранения текущего объекта языка static $current = null;

//Получение текущего объекта языка static function getCurrent () { if (self::$current === null){ self::$current = self: getDefaultLang (); } return self::$current; }

//Установка текущего объекта языка и локаль пользователя static function setCurrent ($url = null) { $language = self: getLangByUrl ($url); self::$current = ($language === null) ? self: getDefaultLang () : $language; Yii::$app→language = self::$current→local; }

//Получения объекта языка по умолчанию static function getDefaultLang () { return Lang: find ()→where ('`default` = : default', [': default' => 1])→one (); }

//Получения объекта языка по буквенному идентификатору static function getLangByUrl ($url = null) { if ($url === null) { return null; } else { $language = Lang: find ()→where ('url = : url', [': url' => $url])→one (); if ($language === null) { return null; }else{ return $language; } } } Формирование URL Менеджер URL (urlManager) — встроенный компонент приложения для создания URL-адресов. Через этот компонент создаются все URL в приложении. Для добавления префикса буквенного идентификатора языка в URL достаточно переопределить метод createUrl класса UrlManager и в конфигурации приложения указать используемый менеджер URL.В блок components конфигурационного файла config/main.php добавляем:

'urlManager' => [ 'enablePrettyUrl' => true, 'showScriptName' => false, 'class'=>'frontend\components\LangUrlManager', 'rules'=>[ '/' => 'site/index', '//*'=>'/', ] ], Создаем файл components/LangUrlManager.php и переопределяем createUrl:

use yii\web\UrlManager; use frontend\models\Lang;

class LangUrlManager extends UrlManager { public function createUrl ($params) { if (isset ($params['lang_id'])){ //Если указан идентификатор языка, то делаем попытку найти язык в БД, //иначе работаем с языком по умолчанию $lang = Lang: findOne ($params['lang_id']); if ($lang === null){ $lang = Lang: getDefaultLang (); } unset ($params['lang_id']); } else { //Если не указан параметр языка, то работаем с текущим языком $lang = Lang: getCurrent (); } //Получаем сформированный URL (без префикса идентификатора языка) $url = parent: createUrl ($params); //Добавляем к URL префикс — буквенный идентификатор языка if ($url == '/'){ return '/'.$lang→url; }else{ return '/'.$lang→url.$url; } } } Определения языка Информация о идентификаторе языка храниться только в URL. Соответственно определить текущий язык можно лишь путем парсинга URL. Для этого переопределим метод resolveRequestUri класса Request и в конфигурационном файле приложения укажем используемый компоненте request. Метод resolveRequestUri — возвращает текущий относительный URL.Что бы не переписывать rules в UrlManager с учетом буквенного идентификатора языка, его (буквенный идентификатор) можно убрать с URL, установить текущий язык через Lang: setCurrent и возвращать URL, но уже без префикса языка.

Создаем файл components/LangRequest.php и переопределяем resolveRequestUri:

use Yii; use yii\web\Request; use frontend\models\Lang;

class LangRequest extends Request { protected function resolveRequestUri () { $lang_prefix = null; $requestUri = parent: resolveRequestUri (); $requestUriToList = explode ('/', $requestUri); $lang_url = isset ($requestUriToList[1]) ? $requestUriToList[1] : null;

Lang: setCurrent ($lang_url);

if ($lang_url!== null && $lang_url === Lang: getCurrent ()→url && strpos ($requestUri, Lang: getCurrent ()→url) === 1) { $requestUri = substr ($requestUri, strlen (Lang: getCurrent ()→url)+1); } return $requestUri; } } В блок components конфигурационного файла config/main.php добавляем:

'request' => [ 'class' => 'frontend\components\LangRequest' ], Интернационализация приложения В переводе приложения участвуют два языка: язык приложения ($language) — язык пользователя, который работает с приложением; исходный язык приложения ($sourceLanguage) — язык, который используется в исходном коде приложения. По умолчанию $sourceLanguage = 'en'.Перевод сообщений осуществляется с помощью метода Yii: t ($category, $message, $params = [], $language = null).

Установим значения по умолчанию $sourceLanguage='en' и $language='ru-RU' в конфигурационном файле. Значение $language — устанавливается заново (метод Lang: setCurrent, строчка Yii::$app→language = self::$current→local;) при разборе URL в LangRequest: resolveRequestUri, то есть при каждом HTTP запросе.

Переводы сообщений будем хранить в директории messages. Для каждого языка своя директория (message/ru и message/en), в которой хранится переводы по категориям.

В блок components конфигурационного файла config/main.php добавляем:

'language'=>'ru-RU', 'i18n' => [ 'translations' => [ '*' => [ 'class' => 'yii\i18n\PhpMessageSource', 'basePath' => '@frontend/messages', 'sourceLanguage' => 'en', 'fileMap' => [ //'main' => 'main.php', ], ], ], ], Более подробную информацию можно найти здесь.

Виджет переключения языков Создаем frontend/widgets/Lang.php:

class Lang extends \yii\bootstrap\Widget { public function init (){}

public function run () { return $this→render ('lang/view', [ 'current' => Lang: getCurrent (), 'langs' => Lang: find ()→where ('id!= : current_id', [': current_id' => Lang: getCurrent ()→id])→all (), ]); } } И отображение frontend/widgets/views/lang/view.php:

name;?>
  • name, '/'.$lang→url.Yii::$app→getRequest ()→getUrl ()) ?>
Вывод виджета:

© Habrahabr.ru