在Yii 2中,官方的頁面多語言解決方案有兩個:php
方案1,使用Yii::t()函數,在頁面中須要輸出文字的地方,使用相似以下代碼:html
<?= Yii::t(‘views/login’, ‘hello’)?>
這樣作的後果是頁面上大量充斥着相似的代碼,致使頁面可讀性不好,並且對於同一個頁面來講,Yii::t()函數的第一個參數基本上都是同樣的,看到這些重複代碼,也是心塞。我曾經在項目中採用這種方式實現多語言,一個簡單的登陸頁面都能寫到心煩的要命。前端
方案2,爲指定語言作一個專門的視圖,假設你有個頁面是英文的,想再作箇中文頁面,但是中英文頁面佈局等相差很大,不是簡單的翻譯文字,那麼在Yii 2中,能夠在該頁面的目錄下,再創建一個zh-CN目錄,而後在這個目錄下創建一個同名的視圖文件,頁面內容用中文實現便可。這個我會專門再有文章說明如何實現。後端
若是中英文頁面佈局基本同樣,只是文字有變化,那麼建議仍是不要用方案2了,寧肯用方案1下降可讀性,不然一旦頁面內容有修改,兩個頁面之間的內容同步會搞到你懷疑人生。app
總之,不論是方案1仍是2,我都不喜歡,想要尋找一種簡潔明瞭的頁面多語言方案,頁面看起來乾淨清爽,又不須要爲每一個語言作單獨的頁面。ide
那麼怎樣才能作到呢,我從Mustache中找到了實現的方案,假設下面是一個視圖的代碼:函數
<h3>{{基本信息}}</h3> <table class="table table-bordered"> <tbody> <tr> <td class="base-label active">{{下載隊列}}</td> <td class="base-label active">{{等待隊列}}</td> </tr> <tr> <td class="base-label active">{{已安裝}}</td> <td class="base-label active">{{當前GameInfo}}</td> </tr> <tr> <td class="base-label active">{{剩餘電量}}</td> <td class="base-label active">{{是否容許OTA}}</td> </tr> </tbody> </table>
若是把想要多語言顯示的文字用Mustache的變量符號給括起來,而後假設上面的內容已經存到一個字符串$content裏,那麼在Action中,能夠用Mustache將其輸出爲英文:佈局
$content = $this->render('mypage', $params); $m = new Mustache_Engine(); $content = $m->render($content, [ '基本信息' => 'Base Information', '下載隊列' => 'Downloading', '等待隊列' => 'Waiting', '已安裝' => 'Installed', '當前GameInfo' => 'GameInfo', '剩餘電量' => 'Battery Level', '是否容許OTA' => 'Is can OTA', ]); return $content;
這裏的要點是先用Yii的render函數,獲得要輸出頁面的字符串,而後使用Mustache,將指定的文字轉換爲英文,最後經過return輸出。this
上面這段代碼就是使用Mustache實現頁面多語言的核心思想,首先看頁面的代碼,徹底沒有任何PHP的代碼,都是標準的HTML元素,頁面看起來很是的乾淨清爽,前端開發人員能夠直接用這個頁面作前端的各類效果,完美實現先後端開發的解耦。翻譯
固然,上面展現的是核心的思想,可是要實際使用,仍是須要進一步的完善。
首先,翻譯的文字其實不適合放到Action的代碼裏,這樣很差維護,應該按照Yii 2的設計思想,放到messages目錄下,爲指定語言創建messages文件,相似以下:
return [ 'device/views/deviceLog/mypage => [ '基本信息' => 'Base Information', '下載隊列' => 'Downloading', '等待隊列' => 'Waiting', '已安裝' => 'Installed', '當前GameInfo' => 'GameInfo', '剩餘電量' => 'Battery Level', '是否容許OTA' => 'Is can OTA', ], ];
使用視圖的路徑做爲鍵值,方便爲每一個頁面肯定翻譯的內容。
其次,作一個本身的Controller的基類,重載render函數:
public function render($view, $params = []) { $content = parent::render($view, $params); $path = $this->getViewPath() . '/' . $view; $list = EonI18nUtils::getMsgs($path); if (empty($list)) { return $content; } $m = new Mustache_Engine([ 'delimiters' => '## ##', ]); $content = $m->render($content, $list); return $content; }
這裏,先調用父類的render函數,獲得正常輸出的視圖字符串,再調用EonI18nUtils::getMsgs(),根據視圖文件的路徑,獲得該頁面的多語言鍵值對,而後建立Mustache對象,將視圖字符串中的指定鍵值修改成翻譯後的文字。
爲了保證頁面上基於JavaScript的Mustache可使用,這裏將Mustache的鍵值標籤由{{}}改成####,頁面代碼相似以下:
<h3>##基本信息##</h3> <table class="table table-bordered"> <tbody> <tr> <td class="base-label active">##下載隊列##</td> <td class="base-val">{{downloading}}</td> <td class="base-label active">##等待隊列##</td> <td class="base-val">{{waitting}}</td> </tr> <tr> <td class="base-label active">##已安裝##</td> <td class="base-val">{{installed}}</td> <td class="base-label active">##當前GameInfo##</td> <td class="base-val">{{gameinfo}}</td> </tr> <tr> <td class="base-label active">##剩餘電量##</td> <td class="base-val">{{battery_level}}%</td> <td class="base-label active">##是否容許OTA##</td> <td class="base-val">{{allowOTA}}</td> </tr> </tbody> </table>
這樣,上面代碼中用##括起來的文字會被翻譯,而用{{}}括起來的,則由頁面上的JS代碼使用Mustache方案替換文字。
EonI18nUtils::getMsgs()的參考代碼以下:
public static function getMsgs($category, $lang = null) { $category = str_replace('\\', '/', $category); $arr = explode("/", $category); $arr = array_slice($arr, count($arr) - 4); $category = implode("/", $arr); $messageSource = \Yii::$app->getI18n()->getMessageSource('messages'); $list = $messageSource->getMsgList($category, $lang); return $list; }
getMsgList()代碼相似以下:
public function getMsgList($langPath, $language) { $language = $this->getLanguage($language); if (!isset($this->_messages[$language])) { $this->_messages[$language] = $this->loadMessages('messages', $language); } if (isset($this->_messages[$language][$langPath])) { return $this->_messages[$language][$langPath]; } return false; }