📜 ⬆️ ⬇️

Yii2-advanced: Making internationalization with a source in Redis

In Yii2, it is possible to implement three options for internationalization:

  1. File with an array, type: key => translation (flexible)
    Help Link:

    Create a configuration file for the collector


    Execute the console command
    php ./yii message/config @common/config/i18n.php //   php ./yii message/config-template @common/config/i18n.php 

    ps For windows php yii environment ... Work with the console should be configured ie necessary components must be connected.

    The file /common/config/i18n.php should be created:
     return [ 'sourcePath' => __DIR__. '..' . DIRECTORY_SEPARATOR . '..' . DIRECTORY_SEPARATOR . '..' . DIRECTORY_SEPARATOR, 'languages' => ['pt-PT','ru-RU','uk-UA','en-US'], //      'translator' => 'Yii::t', 'sort' => false, 'removeUnused' => false, 'only' => ['*.php'],//       'except' => [//   '.svn', '.git', '.gitignore', '.gitkeep', '.hgignore', '.hgkeep', '/messages', '/vendor', ], 'format' => 'php',//    'messagePath' => __DIR__ . DIRECTORY_SEPARATOR . '..' . DIRECTORY_SEPARATOR . 'messages', 'overwrite' => true, ]; 

    ')
    Create a common / messages folder to store translations!

    Add the i18n component


    In the common / main.php configuration file
     'language'=> 'pt-PT',//  'sourceLanguage' => 'uk-UA',//  'components' => [ ... 'i18n' => [ 'translations' => [ 'frontend*' => [ 'class' => 'yii\i18n\PhpMessageSource', 'basePath' => '@common/messages', /* fileMap ,       .        'fileMap' => [ 'app' => 'app.php', 'app/error' => 'error.php', ],*/ ], 'backend*' => [ 'class' => 'yii\i18n\PhpMessageSource', 'basePath' => '@common/messages', ], //       *     '*' => [ 'class' => 'yii\i18n\PhpMessageSource', 'basePath' => '@common/messages', 'on missingTranslation' => ['common\components\TranslationEventHandler', 'handleMissingTranslation']//     ], ], ], .... 


    What should be in the submissions


    Examples from the Yii :: t () documentation
      //     /        frontend/home.php echo \Yii::t('frontend/home', 'key home'); echo \Yii::t('frontend/login', 'key login'); 

    It is better if the category names contain the name of the page on which they will be used. When searching for a translation, Yii samples the entire category.

    Handler not found translations


      common\components\TranslationEventHandler.php namespace common\components; use yii\i18n\MissingTranslationEvent; class TranslationEventHandler { public static function handleMissingTranslation(MissingTranslationEvent $event) { //     $event->translatedMessage = "@MISSING: {$event->category}.{$event->message} FOR LANGUAGE {$event->language} @"; //    } } 


    Starting the construction of keys


    php yii message/extract @common/config/i18n.php
    Then files should be created:
    • common \ messages \ ru-RU \ frontend \ home.php
    • common \ messages \ uk-UA \ frontend \ home.php
    • common \ messages \ en-US \ frontend \ home.php
    • common \ messages \ ru-RU \ frontend \ login.php
    • common \ messages \ uk-UA \ frontend \ login.php
    • common \ messages \ en-US \ frontend \ login.php
    • ....

    The folder structure depends on the category name. This one is based on frontend / login and frontend \ home
    If we used this type of category (keys) Yii::t('frontend','home')
    then all one file for each language would be created (common \ messages \ ru-RU \ frontend.php) in which there would be all translations and to translate one view, Yii would load all translations that we don’t need on this page.

    Files have the following form:
     return [ 'key home' => '', 'key login' => '' ]; 

    We will translate ourselves already.

    If you create the file views / site / ru-RU / index.php and the default language is language = ru-RU, then this view will first be rendered.


  2. File with the extension .po, .mo binary (need compiler, fast)
    Help Link:

    Creating a configuration file


    Team php yii message/config @common/config/i18n.php
    get the file common / config / i18n.php
     return [ 'color' => null, 'interactive' => true, 'sourcePath' => __DIR__. '..' . DIRECTORY_SEPARATOR . '..' . DIRECTORY_SEPARATOR . '..' . DIRECTORY_SEPARATOR, 'languages' => ['pt-PT','ru-RU','uk-UA','en-US'], 'translator' => 'Yii::t', 'sort' => false, 'removeUnused' => false, 'only' => ['*.php'], 'except' => [ '.svn', '.git', '.gitignore', '.gitkeep', '.hgignore', '.hgkeep', '/messages', '/vendor', ], 'format' => 'po', 'messagePath' => __DIR__ . DIRECTORY_SEPARATOR . '..' . DIRECTORY_SEPARATOR . 'messages', 'overwrite' => true, ]; 

    It is the same as in the case of the php format. It differs only in the format :) 'format' => 'po'

    Create a common / messages folder to store translations!

    The difference of this format from the php format is only in the fact that after collecting keys from an application using the php yii message/extract @common/config/i18n.php command, php yii message/extract @common/config/i18n.php in the common \ messages folder create files with the .so extension (frontend.so) that should be compiled by the program , for example, poedit windows and simply saving it will create binary files with the extension .so.


  3. Mysql database, two tables for keys and translations
    Help Link:

    Create a configuration file common / config / i18n.php


    Having executed the command
    php yii message/config @common/config/i18n.php
    Create such a file common / config / i18n.php:
     return [ 'color' => null, 'interactive' => true, 'help' => null, 'sourcePath' => __DIR__. '..' . DIRECTORY_SEPARATOR . '..' . DIRECTORY_SEPARATOR . '..' . DIRECTORY_SEPARATOR, 'messagePath' => __DIR__ . DIRECTORY_SEPARATOR . '..' . DIRECTORY_SEPARATOR . 'messages', 'languages' => ['pt-PT','ru-RU','uk-UA','en-US'],//       'translator' => 'Yii::t', 'sort' => false, 'overwrite' => true, 'removeUnused' => false, 'markUnused' => true, 'except' => [ '.svn', '.git', '.gitignore', '.gitkeep', '.hgignore', '.hgkeep', '/messages', '/BaseYii.php', ], 'only' => [ '*.php', ], 'format' => 'db', 'db' => 'db', 'sourceMessageTable' => '{{%source_message}}',//      'messageTable' => '{{%message}}',//     'ignoreCategories' => ['yii'],//   ]; 


    Add a component


    Examples of the component are in the version with php format
    In the common / main.php configuration file
     'language'=> 'ru-RU',//  'sourceLanguage' => 'uk-UA',//  'components' => [ ... 'i18n' => [ 'translations' => [ // * -   '*' => [ 'class' => 'yii\i18n\DbMessageSource', 'on missingTranslation' => ['common\components\TranslationEventHandler', 'handleMissingTranslation'] ], ], ], .... 


    the handler for not found translations common \ components \ TranslationEventHandler is also in the example with the php format

    In the views, there should be calls to the method of the Yii :: t () component see. Help Link
    For example:
     echo \Yii::t('frontend/home', 'key home') ; echo \Yii::t('frontend/login', 'key login') ; 


    Execute migrate


    To store translations, execute the table creation script.
    php yii migrate --migrationPath=@yii/i18n/migrations
    Two tables will be created:
     --  CREATE TABLE IF NOT EXISTS `source_message` ( `id` int(11) NOT NULL AUTO_INCREMENT PRIMARY KEY, `category` varchar(255) COLLATE utf8_unicode_ci DEFAULT NULL, `message` text COLLATE utf8_unicode_ci, KEY `idx_source_message_category` (`category`) ) ENGINE=InnoDB DEFAULT CHARSET=utf8 COLLATE=utf8_unicode_ci; --  CREATE TABLE IF NOT EXISTS `message` ( `id` int(11) NOT NULL PRIMARY KEY, `language` varchar(16) COLLATE utf8_unicode_ci NOT NULL, `translation` text COLLATE utf8_unicode_ci, KEY `idx_message_language` (`language`), CONSTRAINT `fk_message_source_message` FOREIGN KEY (`id`) REFERENCES `source_message` (`id`) ON DELETE CASCADE ) ENGINE=InnoDB DEFAULT CHARSET=utf8 COLLATE=utf8_unicode_ci; 


    Now you can run the console command to collect all categories and save them to the database
    php yii message/extract @common/config/i18n.php

You can also store translations in Mongo:

But the key collection will have to write yourself.
yii \ mongodb \ i18n \ MongoDbMessageSource
Similarly…
I18n component
 return [ 'language'=> 'uk-UA',//'ru-RU', //.... 'components' => [ // ... 'i18n' => [ 'translations' => [ '*' => [ 'class' => 'yii\mongodb\i18n\MongoDbMessageSource' ] ] ], ] ]; 


Create a collection:

 $messages=Yii::$app->mongodb->getCollection('messages'); $messages->insert([ "_id"=>1, "category"=>"frontend/index", "language"=>"uk", "messages"=>["Hello world!"=>"і Welt!","Hello"=>"і"] ]); $messages->insert([ "_id"=>2, "category"=>"frontend/index", "language"=>"ru", "messages"=>["Hello world!"=>" Welt!","Hello"=>""] ]); 


we receive such documents:
 { "_id": 1, "category": "frontend/index", "language": "uk", "messages": { "Hello world!": "і Welt!", "Hello": "і" } }, { "_id": 2, "category": "frontend/index", "language": "ru", "messages": { "Hello world!": "Hallo Welt!", "Hello": "" } } 



And when calling a transfer

echo \Yii::t('frontend/index', 'Hello');
echo \Yii::t('frontend/index', 'Hello world!');

work out a sample from the collection of messages by language 'language'=> 'uk-UA'
and categories "category": "frontend/index" Getting all the category keys at once, it makes sense to determine the category that uses all translations to the maximum.


Or your own version, taking as a basis the storage of transfers in the database but with your own management (key generation, transfers and their storage).

Main



What are the advantages:



Creating a configuration file i18n.php


Let's start by creating a key collector configuration file with this console command:

 php yii message/config @common/config/i18n.php 

After this console command, the i18n.php file will appear in common / config / or simply create it like this:

common / config / i18n.php
  return [ 'color' => null, 'interactive' => true, 'help' => null, 'sourcePath' => __DIR__. '..' . DIRECTORY_SEPARATOR . '..' . DIRECTORY_SEPARATOR . '..' . DIRECTORY_SEPARATOR, 'languages' => ['ru-RU','uk-UA','en-US'],//    'translator' => 'Yii::t', 'sort' => false, 'overwrite' => true, 'removeUnused' => false, 'markUnused' => true, 'except' => [ '.svn', '.git', '.gitignore', '.gitkeep', '.hgignore', '.hgkeep', '/messages', '/BaseYii.php', ], 'only' => [ '*.php', ], 'format' => 'db', 'db' => 'db', //'messageTable' => '{{%message}}', //       gr_dictionary 'sourceMessageTable' => '{{%gr_dictionary}}',//   'ignoreCategories' => ['yii'], ]; 

Creating tables in MySQL


Next, create three tables for the main storage of all languages ​​with translations:

gr_language (languages)
 CREATE TABLE IF NOT EXISTS `gr_language` ( `id` smallint(5) unsigned NOT NULL AUTO_INCREMENT PRIMARY KEY, `code_lang` varchar(255) NOT NULL, `local` varchar(255) NOT NULL, `name` varchar(255) NOT NULL, `status` tinyint(4) NOT NULL DEFAULT '1', UNIQUE KEY `code_lang` (`code_lang`), UNIQUE KEY `local` (`local`) ) ENGINE=InnoDB DEFAULT CHARSET=utf8; INSERT INTO `gr_language` (`id`, `code_lang`, `local`, `name`, `status`) VALUES (1, 'en', 'en-US', 'English', 1), (2, 'ru', 'ru-RU', '', 1), (3, 'uk', 'uk-UA', 'ї', 1); 


gr_dictionary_keys (keys)
  --    CREATE TABLE IF NOT EXISTS `gr_dictionary_keys` ( `id` int(10) unsigned NOT NULL AUTO_INCREMENT PRIMARY KEY, `key` varchar(250) NOT NULL, UNIQUE KEY `id` (`id`), KEY `key` (`key`) ) ENGINE=InnoDB DEFAULT CHARSET=utf8; 


gr_dictionary (translations)
 --    CREATE TABLE IF NOT EXISTS `gr_dictionary` ( `id` int(11) NOT NULL AUTO_INCREMENT PRIMARY KEY, `language_id` smallint(5) unsigned NOT NULL, `key` int(10) unsigned NOT NULL, `value` varchar(255) NOT NULL COMMENT '', `translator` text NOT NULL COMMENT '', `type` set('w','m') DEFAULT NULL COMMENT 'w/m /', `status` tinyint(4) NOT NULL DEFAULT '1', CONSTRAINT `gr_dictionary_ibfk_1` FOREIGN KEY (`language_id`) REFERENCES `gr_language` (`id`) ON DELETE CASCADE ON UPDATE CASCADE, CONSTRAINT `gr_dictionary_ibfk_2` FOREIGN KEY (`key`) REFERENCES `gr_dictionary_keys` (`id`) ON DELETE CASCADE ON UPDATE CASCADE, UNIQUE KEY `language_id` (`language_id`,`key`,`type`), KEY `code_lang` (`language_id`), KEY `type` (`type`) ) ENGINE=InnoDB DEFAULT CHARSET=utf8; 


Redefine the console controller


Now, before collecting the keys, override the console controller \ yii \ console \ controllers \ MessageController which is responsible for collecting all the keys. To do this, create your controller which is inherited from it.

Create a file console \ controllers \ Message2Controller.php of this type:

console \ controllers \ Message2Controller.php
 namespace console\controllers; use Yii; use yii\console\Exception; class Message2Controller extends \yii\console\controllers\MessageController { /** * Saves messages to database * * @param array $messages     [[]=>[[],[...]] ,... ] * @param \yii\db\Connection $db * @param string $sourceMessageTable     * @param string $messageTable   * @param boolean $removeUnused * @param array $languages    languages  i18n.php ['ru-RU',...] * @param boolean $markUnused */ protected function saveMessagesToDb($messages, $db, $sourceMessageTable, $messageTable, $removeUnused, $languages, $markUnused) { try{ $pr_iskey=Yii::$app->db->createCommand("SELECT `id` FROM `gr_dictionary_keys` WHERE `key`=:key"); $pr_inskey=Yii::$app->db->createCommand("INSERT INTO `gr_dictionary_keys`( `key`) VALUES (:key)"); $pr_delkey=Yii::$app->db->createCommand("DELETE FROM `gr_dictionary_keys` WHERE `id`=:id"); $id_lang=[]; $pr_l=Yii::$app->db->createCommand("SELECT SQL_NO_CACHE id FROM gr_language WHERE local=:local LIMIT 1"); foreach ($languages as $language) { if(!isset($id_lang[$language])){ $id_language=(int)$pr_l->bindValue(":local", $language,2)->queryScalar(); if(empty($id_language)){ continue; // throw new Exception("Unknow lang type $language"); } $id_lang[$language]=(int)$id_language; } } if(empty($id_lang))throw new Exception("empty lang"); //ALTER TABLE `yii2advanced`.`gr_dictionary` ADD UNIQUE (`language_id`, `key`, `type`); $pr_d=Yii::$app->db->createCommand("INSERT IGNORE INTO `gr_dictionary`( `language_id`, `key`, `value`, `type`) VALUES (:language_id,:key,:value,:type)"); foreach ($messages as $category => $msgs){ list($type,$key)=explode(":", $category); if(empty($id=$pr_iskey->bindValue(":key", $key,2)->queryScalar())){ $pr_inskey->bindValue(":key", $key,2)->execute(); $id=Yii::$app->db->lastInsertID; } foreach ($id_lang as $id_language) { $pr_d->bindValue(":language_id", $id_language,1) ->bindValue(":key", $id,1) ->bindValue(":value", $msgs[0],2) ->bindValue(":type", $type,2) ->execute(); } } //     status=1 (   ) $query=Yii::$app->db->createCommand("SELECT SQL_NO_CACHE dk.`id`,CONCAT(d.`type`,':',dk.`key`) as 'key_' FROM `gr_dictionary` d,gr_dictionary_keys dk WHERE d.`key`=dk.id AND status=1")->query(); //$pr_del=Yii::$app->db->createCommand("DELETE FROM `gr_dictionary` WHERE `key`=:key"); while(($data=$query->read())!=false){ if(array_key_exists($data['key_'], $messages)===false){ //$pr_del->bindValue(":key", $data['id'],1)->execute(); $pr_delkey->bindValue(":id", $data['id'],1)->execute(); } } Yii::$app->db->createCommand("ALTER TABLE gr_dictionary AUTO_INCREMENT = 1;")->execute(); }catch (\Exception $e){ //   } } } 



The point here is that we need only one method saveMessagesToDb, which fills the gr_dictionary table from the common / config / i18n.php configuration file

 'sourceMessageTable' => '{{%gr_dictionary}}' 

collected keys from our site, which we previously called via Yii :: t () . But you can also use another table, here we already decide how we feel better. Added the deletion of keys, and with them the transfers by reference to a foreign key if this key is no longer used on the site.

Now we can call the collection of keys by the team to our controller:

 php yii message2/extract @common/config/i18n.php 

As a result, two tables should be filled (gr_dictionary and gr_dictionary_keys). An entry for translation will be created for each language from the gr_language table.

Add components i18n


Next, add the common \ config \ main.php configuration file to the i18n component:

common \ config \ main.php
  ... 'language'=> 'ru-RU', 'sourceLanguage' => 'en-US', 'components' => [ 'i18n' => [ 'translations' => [ '*' => [ 'class' => 'common\models\AltDbMessageSource', //   yii\i18n\DbMessageSource ], ], ], 'lng' => [ 'class' => '\common\components\LanguageExtension', ], ... 


  1. The Yii i18n component will work on calls to Yii :: $ app-> t (). The class responsible for this is yii \ i18n \ DbMessageSource but we will override it with common \ models \ AltDbMessageSource.

  2. The lng component is our class \ common \ components \ LanguageExtension responsible for working with Redis

Override yii \ i18n \ DbMessageSource


The class responsible for the translation, we implement in our

yii \ i18n \ DbMessageSource
 namespace common\models; use Yii; class AltDbMessageSource extends \yii\i18n\MessageSource { public $sourceLanguage; public function init() { parent::init(); if ($this->sourceLanguage === null) { $this->sourceLanguage = Yii::$app->sourceLanguage; } } protected function translateMessage($category, $message, $language) { /*    (w:key)         lng    w  m    ,          . */ list($type,) = explode(":",$category); return Yii::$app->lng->$type($category); } public function translate($category, $message, $language) { if ( $language !== $this->sourceLanguage ) { return $this->translateMessage($category, $message, $language); } else { return false; } } } 


The translateMessage method is called when we call Yii :: t ('category', 'value'). Here it is important how we are going to organize the appearance of the key. It is possible through the separator : with the help of which folders with hierarchy will be created in Redis, which gives clarity. For example: such keys
Yii::t('ru-RU:type:uniq_view','') will look in RedisAdmin like this:

  • ru-RU:
    • type:
      • uniq_view: value
      • uniq_view: value
      • uniq_view: value

What will allow to do with Redis such selections:
 $redis->executeCommand("KEYS",["KEY" => $lang_key.":".$type.":*"]); $redis->executeCommand("KEYS",["KEY" => $lang_key.":*"]); 

The language key ru-RU and others will be added at the time of filling Redis in the component \ common \ components \ LanguageExtension.

Let's write the component \ common \ components \ LanguageExtension


The component is needed to receive a translation by key from Redis or an array if Redis has fallen off.

common \ components \ LanguageExtension
 namespace common\components\extensions; use Yii; use common\components\exceptions\LanguageException; use yii\db\Exception; use PDO; /** * Class LanguageExtension * @package common\components\extensions * : *   *   redis */ class LanguageExtension extends \yii\base\Object { private $language; //   -   ru private $w = []; //   private $m = []; //   private $storageConnection; //     private $storageStatus; //     private $numbDb; //  redis private $default_key; //    private $expire; public function __construct() { try{ $this->expire = Yii::$app->params['secretKeyExpire']??(60 * 60 * 60); $this->language = \Yii::$app->language; $language=LanguageExtension::currentLang(); if(!empty($language)){ if($this->idKeyLang($language)) { $this->language= $language; } } $this->numbDb=Yii::$app->params['redisLangDB']??11; $this->storageStatus = false; $this->default_key= $this->language.":index"; $this->storageConnection = new \yii\redis\Connection([ 'hostname' => Yii::$app->params['redisHost'], // 'password' => '', 'port' => 6379, 'database' => $this->numbDb, ]); if(empty($this->language)) throw new LanguageException("not default language",0); $this->init(); }catch ( LanguageException $event){ // echo $event->getMessage(); }catch ( \yii\db\Exception $event){ $this->init(); }catch (\Exception $event){ // echo $event->getMessage(); } } public function __destruct() { try{ if($this->storageConnection->isActive) $this->storageConnection->close(); }catch (\Exception $event){ } } /** *  . *  .      . *     . */ public function init(){ try{ $this->storageConnection->open(); //     redis if(!$this->isFullData()){ //   mysql    redis $this->loadRedis(); } $this->storageStatus = true; } catch ( \yii\db\Exception $event) { $this->storageStatus = false; //  . //     $w  $m     . $this->loadVariable(); } } public static function currentLang(){ try{ $language = isset($_COOKIE['userLang']) ? $_COOKIE['userLang'] : null; if(!$language && Yii::$app->session->has('userLang')) { $language = Yii::$app->session('userLang'); } if(empty($language))$language=\Yii::$app->language; return $language; } catch (\Exception $e){ print_r($e->getMessage());exit; } } private function idKeyLang(string $key){ if(!empty($key)){ return Yii::$app->db->createCommand("SELECT `id` FROM `gr_language` WHERE local=:local") ->bindValue(":local", $key,PDO::PARAM_STR) ->queryScalar(); } return false; } /** * @param string $type * @param string $key * @return string *   */ private function getKeyMD5(string $type,string $key):string { return $this->language.":".$type.":".md5($key); } /** * @return bool *     */ private function loadVariable():bool{ try{ //  . //     $w  $m     . $language_id=$this->idKeyLang($this->language); $res=\Yii::$app->db->createCommand("SELECT d.`type`,d.`value`,d.`translator`, dk.`key` FROM `gr_dictionary` d,gr_dictionary_keys dk WHERE d.`key`=dk.id AND d.language_id=:language_id") ->bindValue(":language_id", $language_id,PDO::PARAM_INT)->query(); $this->w=$this->m=[]; while(($data=$res->read())!=false){ if(method_exists($this, $data['type'])){ $this->{$data['type']}[$this->getKeyMD5($data['type'],$data['key'])]=$data['translator']; } } return true; }catch (\Exception $event){ echo $event->getLine()."|".$event->getMessage();exit; return false; } } /** * @return bool *    redis  mysql ( ) */ private function loadRedis():bool{ try{ $language_id=$this->idKeyLang($this->language); $res=\Yii::$app->db->createCommand("SELECT d.`type`,dk.`key`, d.`value`,d.`translator` FROM `gr_dictionary` d,gr_dictionary_keys dk WHERE d.`key`=dk.id AND d.language_id=:language_id") ->bindValue(":language_id", $language_id,PDO::PARAM_INT)->query(); $this->storageConnection->executeCommand('SETEX', [ "KEY" => $this->default_key,"SECONDS"=>$this->expire,"VALUE"=> "1"]); while(($data=$res->read())!=false){ $this->storageConnection->executeCommand('SETEX', ["KEY" => $this->getKeyMD5($data['type'],$data['key']),"SECONDS"=>$this->expire,"VALUE"=> $data['translator']]); } if(empty($this->storageConnection->executeCommand('LASTSAVE', [] ))) $this->storageConnection->executeCommand('BGSAVE', [] ); return true; }catch (\Exception $event){ echo $event->getMessage();exit; } } /** *  Redis */ public function flushdb(){ try{ if($this->storageConnection->isActive) $this->storageConnection->executeCommand('FLUSHDB'); else { $this->w=[]; $this->m=[]; } }catch (\Exception $event){ } } /** * @return bool *    redis         */ private function isFullData():bool { try{ $res= $this->storageConnection->executeCommand('INFO', [ ] ); preg_match("/.*db$this->numbDb:keys=([\d])*.*?/uis",$res,$arr); if(isset($arr[1]) && $arr[1]>1){ return $this->exists($this->default_key); } return false; }catch (\Exception $event){ echo $event->getMessage(); return false; } } /** * @param string $key * @return string *         */ public function w(string $key) : string { return $this->getKeyValue($key, 'w'); } /** * @param string $key * @return string *         */ public function m(string $key) : string { return $this->getKeyValue($key, 'm'); } /** * @param string $key * @param string $type * @return string *    *  .      -. */ private function getKeyValue ( string &$key, string $type ) : string { try{ if(!$key=trim($key)) throw new LanguageException("Error dictionary ".addslashes($key).". The ".addslashes($key)." can not be empty or contain only whitespace.", 777001); if($this->storageStatus) $value = $this->storageConnection->executeCommand("GET",["KEY" =>$this->getKeyMD5($type,$key)]); else{ $value = @$this->$type[$this->getKeyMD5($type,$key)]; } /*   if(!$value){ if ($this->hasEventHandlers(\yii\i18n\MessageSource::EVENT_MISSING_TRANSLATION)) { $event = new \yii\i18n\MissingTranslationEvent([ 'category' => $key, 'message' => $key, 'language' => $this->language, ]); $this->trigger(\yii\i18n\MessageSource::EVENT_MISSING_TRANSLATION, $event); } }*/ return $value ? $value : $key; }catch (\Exception $event){ return $key; } } /** * @param $key * @return bool *   */ public function del($key):bool{ try{ if($this->storageConnection->isActive){ return $this->storageConnection->executeCommand("DEL",["KEY" =>$key]);// keys test:1:v }else{ list($lang_key,$type,$key_)= explode(":", $key); if(method_exists($this, $type) && isset($this->$type[$key_])){ unset($this->$type[$key_]); return true; } return false; } }catch (\Exception $event){ return false; } } /** * @param string $lang_key * @param null $type * @return bool *         */ public function delAll(string $lang_key,$type=null){ try{ if($this->storageConnection->isActive){ $keys= $this->keys($lang_key,$type); if(!empty($keys)){ foreach ($keys as $key){ $this->del($key); } if($type==null) $this->del($lang_key.":index"); } }else{ $this->w=$this->m=[]; return true; } }catch (\Exception $event){ return false; } } /** * @param $type * @param $key * @return array *     */ public function keys(string $lang_key,$type=null):array{ try{ if($this->storageConnection->isActive){ if($type!=null) return $this->storageConnection->executeCommand("KEYS",["KEY" => $lang_key.":".$type.":*"]); else return $this->storageConnection->executeCommand("KEYS",["KEY" => $lang_key.":*"]); }else{ if($type!=null){ return $this->w+$this->m; }else{ if(method_exists($this, $type))return $this->$type; } return []; } }catch (\Exception $event){ return []; } } /** * @param $type * @param $key * @return bool *    */ public function exists($key):bool{ try{ if($this->storageConnection->isActive){ return $this->storageConnection->executeCommand("EXISTS",["KEY" =>$key]); }else{ // return (method_exists($this, $type) && isset($this->$type[$key])); list($lang_key,$type,$key_)= explode(":", $key); if(method_exists($this, $type))return isset($this->$type[$key_]); return false; } return false; }catch (\Exception $event){ return false; } } } 


The essence


Storing the entire language dictionary by default Yii::$app->language value, if there is no COOKIE data, in Redis or in the array if Redis did not work, according to the type of the value $this->w[] ,$this->m[] . , .

How does he work


Redis. , , MySQL.

, , ru-RU:index .

, MySQL 4 . ru-RU , ? Redis MySQL ru-RU , en-US, Redis , .



 $redis->executeCommand('SETEX', ["KEY" => $this->getKeyMD5($data['type'],$data['key']),"SECONDS"=>$this->expire,"VALUE"=> $data['translator']]); 


frontend\widgets\WLang
 namespace frontend\widgets\WLang; use frontend\models\Lang; use Yii; use PDO; class WLang extends \yii\bootstrap\Widget { public function init(){} public function run() { return $this->render('index', [ 'current' => \common\components\LanguageExtension::currentLang(), 'default' => \Yii::$app->db->createCommand("SELECT `id`, `code_lang`, `local`, `name`, `default_lang`, `status` FROM `gr_language` WHERE local=:local LIMIT 1")->bindValue(":local",\common\components\LanguageExtension::currentLang(),PDO::PARAM_STR)->queryOne(PDO::FETCH_OBJ), 'langs' =>\Yii::$app->db->createCommand("SELECT `id`, `code_lang`, `local`, `name`, `status` FROM `gr_language` WHERE 1")->queryAll(PDO::FETCH_OBJ), ]); } } <div> <?php use yii\helpers\Html; ?> <script> function click_(el) { var lang = $(el).attr('data-lang'); var date = new Date; date.setDate(date.getDate() + 1); document.cookie = "userLang=" + lang + "; path=/; expires=" + date.toUTCString() + ";"; location.reload(); } </script> <div id="lang"> <span id="current-lang"> <span class="show-more-lang" >  <?= $current;?></span> </span> <ul id="langs"> <?php foreach ($langs as $lang):?> <li class="item-lang"><a href="#" onclick="click_(this)" data-lang="<?=$lang->local?>"><?=$lang->code_lang?></a></li> <?php endforeach;?> </ul> </div> </div> 


The whole point of the widget is to display all available languages ​​and install COOKIE data.

Further, when more translations are added or deleted on the pages of our application, we simply call for the collection of keys and enter their values ​​into MySQL.

good luck, Jekshmek

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


All Articles