📜 ⬆️ ⬇️

Yii2 and multilingual organization

The long-awaited release of Yii 2.0 Beta gave an impetus to many developers using Yii to switch to the second version of the framework. The developers of the framework indicated that they would try not to touch backward compatibility and would mostly concentrate on correcting errors and complete the documentation. This gives even greater impetus to the use of Yii2 in real projects.

We decided to keep up with the innovations and chose the second version of the wonderful framework Yii. When developing the project, it became necessary to organize multilingualism on the site.

Formulation of the problem


1. The number of languages ​​is unlimited.
2. Website URLs are presented as CNC and SEO optimized. View links:
example.com/en/mypage
example.com/ru/mypage
example.com/de/mypage
3. Minimal changes in the work with the framework. The resource for the link example.com/mypage should be given in the default language. Routing rules should not change depending on the number of languages.

Language storage


Assuming that the number of languages ​​is unlimited and the default language must be indicated, it was decided to store this list in a separate database table. Create a lang table with the following fields:
id - language identifier
url - the letter identifier of the language to display in the URL (ru, en, de, ...)
local - user language (locale)
name - name (English, Russian, ...)
default - flag indicating the default language (1 - default language)
date_update - update date (in unixtimestamp)
date_create - creation date (in 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 ; 


And we add two languages ​​to the table, considering that one must be from the value 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()); 


Or create a migration by running the php command yii migrate / create lang. Insert into the created file:

 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}}'); } 


Apply migration with the php yii migrate command.

Language model


Using gii, create a Lang model and generate a CRUD.
We add behavior to the model to automatically update the date when editing and creating an entry in the lang table:

 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'], ], ], ]; } 


Also add helper methods for working with a language object in the Lang model:

 //,      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 Formation


URL Manager (urlManager) is an embedded component of the application for creating URLs. Through this component all URLs are created in the application. To add a prefix of the alphabetic language identifier to the URL, simply override the createUrl method of the UrlManager class and specify the URL manager to be used in the application configuration.

In the components block of the config / main.php configuration file, add:

 'urlManager' => [ 'enablePrettyUrl' => true, 'showScriptName' => false, 'class'=>'frontend\components\LangUrlManager', 'rules'=>[ '/' => 'site/index', '<controller:\w+>/<action:\w+>/*'=>'<controller>/<action>', ] ], 


Create the components / LangUrlManager.php file and override 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 = parent::createUrl($params); return $url == '/' ? '/'.$lang->url : '/'.$lang->url.$url; } } 


Language definitions


Information about the language identifier is stored only in the URL. Accordingly, the current language can only be determined by parsing the URL. To do this, override the method RequestPathInfo's class and specify in the application's configuration file we specify the request component used. Method resolvePathInfo - returns part of the URL ($ pathInfo) to the "?" and after the login script.

Not to rewrite the rules in UrlManager, taking into account the alphabetic identifier of the language, its (alphabetic identifier) ​​can be removed from $ pathInfo, set the current language via Lang :: setCurrent and return $ pathInfo, but without the language prefix.

Create the components / LangRequest.php file and override the resolvePathInfo:

 <?php namespace frontend\components; use Yii; use yii\web\Request; use frontend\models\Lang; class LangRequest extends Request { private $_lang_url; public function getLangUrl() { if ($this->_lang_url === null) { $this->_lang_url = $this->getUrl(); $url_list = explode('/', $this->_lang_url); $lang_url = isset($url_list[1]) ? $url_list[1] : null; Lang::setCurrent($lang_url); if( $lang_url !== null && $lang_url === Lang::getCurrent()->url && strpos($this->_lang_url, Lang::getCurrent()->url) === 1 ) { $this->_lang_url = substr($this->_lang_url, strlen(Lang::getCurrent()->url)+1); } } return $this->_lang_url; } protected function resolvePathInfo() { $pathInfo = $this->getLangUrl(); if (($pos = strpos($pathInfo, '?')) !== false) { $pathInfo = substr($pathInfo, 0, $pos); } $pathInfo = urldecode($pathInfo); // try to encode in UTF8 if not so // http://w3.org/International/questions/qa-forms-utf-8.html if (!preg_match('%^(?: [\x09\x0A\x0D\x20-\x7E] # ASCII | [\xC2-\xDF][\x80-\xBF] # non-overlong 2-byte | \xE0[\xA0-\xBF][\x80-\xBF] # excluding overlongs | [\xE1-\xEC\xEE\xEF][\x80-\xBF]{2} # straight 3-byte | \xED[\x80-\x9F][\x80-\xBF] # excluding surrogates | \xF0[\x90-\xBF][\x80-\xBF]{2} # planes 1-3 | [\xF1-\xF3][\x80-\xBF]{3} # planes 4-15 | \xF4[\x80-\x8F][\x80-\xBF]{2} # plane 16 )*$%xs', $pathInfo) ) { $pathInfo = utf8_encode($pathInfo); } $scriptUrl = $this->getScriptUrl(); $baseUrl = $this->getBaseUrl(); if (strpos($pathInfo, $scriptUrl) === 0) { $pathInfo = substr($pathInfo, strlen($scriptUrl)); } elseif ($baseUrl === '' || strpos($pathInfo, $baseUrl) === 0) { $pathInfo = substr($pathInfo, strlen($baseUrl)); } elseif (isset($_SERVER['PHP_SELF']) && strpos($_SERVER['PHP_SELF'], $scriptUrl) === 0) { $pathInfo = substr($_SERVER['PHP_SELF'], strlen($scriptUrl)); } else { throw new InvalidConfigException('Unable to determine the path info of the current request.'); } if (isset($pathInfo[0]) && $pathInfo[0] === '/') { $pathInfo = substr($pathInfo, 1); } return (string) $pathInfo; } } 


In the components block of the config / main.php configuration file, add:

 'request' => [ 'class' => 'frontend\components\LangRequest' ], 


Application internationalization


Two languages ​​are involved in the translation of the application:
application language ($ language) - the language of the user who works with the application;
the source language of the application ($ sourceLanguage) is the language used in the source code of the application. By default, $ sourceLanguage = 'en'.

Messages are translated using the Yii :: t method ($ category, $ message, $ params = [], $ language = null) .

Set the default values ​​$ sourceLanguage = 'en' and $ language = 'ru-RU' in the configuration file. The value of $ language is set again (the Lang :: setCurrent method, the line Yii :: $ app-> language = self :: $ current-> local;) when determining $ pathInfo in LangRequest :: resolvePathInfo, that is, on each HTTP request.

Translations of messages will be stored in the messages directory. Each language has its own directory (message / ru and message / en), which stores translations by category.

In the components block of the config / main.php configuration file, add:

 'language'=>'ru-RU', 'i18n' => [ 'translations' => [ '*' => [ 'class' => 'yii\i18n\PhpMessageSource', 'basePath' => '@frontend/messages', 'sourceLanguage' => 'en', 'fileMap' => [ //'main' => 'main.php', ], ], ], ], 


More information can be found here .

Language Switching Widget


Create frontend / widgets / Lang.php:

 <?php namespace frontend\widgets; use frontend\models\Lang; class WLang 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(), ]); } } 


And the frontend / widgets / views / lang / view.php display:

 <?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()->getLangUrl()) ?> </li> <?php endforeach;?> </ul> </div> 


Widget output:

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

Source: https://habr.com/ru/post/226931/


All Articles