在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; }