Yii2 и организация мультиязычности

от автора

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

Мы решили не отставать от новшеств и выбрали именно вторую версию замечательного фреймворка Yii. При разработке проекта возникла необходимость в организации мультиязычности на сайте.

Постановка задачи

1. Количество языков неограниченно.
2. URL сайта представлены как ЧПУ и SEO оптимизированы. Ссылки вида:
example.com/en/mypage
example.com/ru/mypage
example.com/de/mypage
3. Минимальные изменения в работе с фреймворком. Ресурс по ссылке 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',         '<controller:\w+>/<action:\w+>/*'=>'<controller>/<action>',     ] ], 

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

<?php namespace frontend\components;  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:

<?php namespace frontend\components;  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:

<?php namespace frontend\widgets; use frontend\models\Lang;  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:

<?php use yii\helpers\Html; ?> <div id="lang">     <span id="current-lang">         <?= $current->name;?> <span class="show-more-lang">▼</span>     </span>     <ul id="langs">         <?php foreach ($langs as $lang):?>             <li class="item-lang">                 <?= Html::a($lang->name, '/'.$lang->url.Yii::$app->getRequest()->getUrl()) ?>             </li>         <?php endforeach;?>     </ul> </div> 

Вывод виджета:

<?php use frontend\widgets\Lang; ?> ... <?= Lang::widget();?> 

ссылка на оригинал статьи http://habrahabr.ru/post/226931/


Комментарии

Добавить комментарий

Ваш адрес email не будет опубликован. Обязательные поля помечены *