ryhmrt’s blog

意識低い系プログラマの雑記

CakePHP3のメッセージ定義でJavaScriptを多国語化

フィリピンでシステムを作って日本に納めるために、英語版を作って日本語化するというアプローチを取っているのだけれど、React.jsをシステムに取り入れたところJavaScriptローカライズする必要が生じて、えいやっとやってみました。

これで CakePHP と同じように __('Hoge') という感じでローカライズできるようになります。

同僚からは笑われましたが、ローカライズはお前の担当だから好きにしろとのことなのでこいつを正式採用することにします。

無駄に力を入れて、メッセージ定義ファイルから生成したJavaScriptをキャッシュしているのが個人的なネタポイントです。

ちなみに、一般公開するページと管理者用のページを分けていたりする場合は、メッセージの内容で中身を推測されてよろしくない事態が発生するかもしれないので、そんなときはメッセージ定義を分けるとかすると良いと思ったり。

app/src/Controller/JsController.php

以下のファイルを作成。

<?php
namespace App\Controller;

use App\Model\Table\StudentsTable;
use Cake\Cache\Cache;
use Cake\I18n\I18n;
use Cake\I18n\MessagesFileLoader;

class JsController extends AppController {

    public function initialize() {
        parent::initialize();
    }

    public function i18n() {
        $this->autoRender = false ;
        $this->response->type('js');
        echo $this->_getI18nJs(I18n::locale());
        echo "\n";
    }

    private function _getI18nJs($locale) {
        $cacher = Cache::engine('_cake_core_');
        $key = "js.i18n.$locale";
        $js = $cacher->read($key);
        if (!$js) {
            $js = $this->_generateI18nJs($locale);
            $cacher->write($key, $js);
        }
        return $js;
    }

    private function _generateI18nJs($locale) {
        $messages = $this->_readLocaleMessages($locale);
        $messages = array_filter($messages, function($message){ return !empty($message); });
        $messages = array_map(function($key, $message){
                return  "'" . preg_replace("/([\\\\'])/", "\\$1", $key) . "':'" . preg_replace("/([\\\\'])/", "\\$1", $message) . "'";
            }, array_keys($messages), $messages);
        return '__ = (function(messages){return function(key){ return messages[key] ? messages[key] : key; }})({' . implode(',', $messages) . '});';
    }

    private function _readLocaleMessages($locale) {
        $loader = new MessagesFileLoader('default', $locale);
        $package = $loader->__invoke();
        return $package->getMessages();
    }

}
?>

app/config/routes.php

以下の記述を追加。

Router::scope('/js', function($routes) {
  $routes->extensions(['json', 'js']);
  $routes->connect('/:action', ['controller' => 'Js', 'action' => '(:action)']);
});

app/src/Template/Layout/default.ctp (もしくは他のレイアウトファイル)

以下の記述を追加。

<?php echo $this->Html->script('i18n.js'); ?>