2007年12月27日木曜日

PHPTALの使い方 PHPスクリプトの記述

●PHPスクリプト上での使い方
   ・定数
    PHPTAL_VERSION
        利用されているPHPTALのバージョン
        
    PHPTAL_PHP_CODE_DESTINATION
        PHPTALの一時ファイル置き場、PHPTAL.phpを読み込む前に設定すれば変更可能。
        デフォルトでは [ /tmp ] になる。
    
    PHPTAL_FORCE_REPARSE
        一時ファイルを無視して強制的にテンプレートファイルをパースするかどうかの設定。
       PHPTAL.phpを読み込む前に true にすれば、設定可能。デフォルトは false
    
    PHPTAL_TEMPLATE_REPOSITORY
        テンプレートファイル置き場のデフォルト指定。標準では指定なし。
    
    ・フィルター
        prefilterとpostfilterの二種類がある。
        
        -prefilter  : テンプレートファイルをパースする前に操作するフィルタ。
                      直後にパースすることになるため、XML整形式でなくなるような操作をするとエラーになる。
                      登録の仕方 $phptal->setPreFilter(new Prefilter());
                     
        -postfilter : テンプレートファイルをパースし、TAL式などすべて実行後に操作するフィルタ。
                      prefilterと違いパースエラーの心配はない。
                      登録の仕方 $phptal->setPostFilter(new Postfilter());
        
        なお、共通する制限として、setPre(Post)Filterで設定できるものはひとつのみになるため、
        複数のフィルターを使いたい場合は同一のフィルターに複数の処理を記載するか、以下のような
        クラスを用意するなどの対応が必要
        
                require_once 'PHPTAL_Filter.php';
        
        /**
         * PHPTALにてフィルターを複数使えるようにするフィルター
         */
        class FilterChain implements PHPTAL_Filter {
            private $_filters = array();
        
            public function add(PHPTAL_Filter $filter){
                $this->_filters[] = $filter;
            }
        
            public function filter($source){
                foreach ($this->_filters as $filter){
                    $source = $filter->filter($source);
                }
                return $source;
            }
        }
        
        
    ・トリガー
        登録しているトリガーのID を phptal:id に設定すると、そのIDに対して、トリガーを呼び出す。
        トリガーは start() → end()の順で呼び出され、start() で return される値により、タグの動作が変わる。
        -return self::SKIPTAG  :  そのタグ自身、要素などは出力せずに end() を実行する。
        -return self::PROCEED  :  そのタグ自身、要素などを出力した後に end() を実行する。
        return しなかった場合は SKIPTAG と同等の動きをする。
        
        
    ・TranslationService
        標準の PHPTAL_GetTextTranslator とは別に独自の文字列置換用クラスを実装することが可能。
        以下の4つのメソッドを実装しなくてはならない。
        
        - method setLanguage(...)
        使用言語を設定するメソッド。
        func_get_args() で 引数を取得するようにし、最初に設定されている言語が標準で利用される。
        
        - method useDomain($domain)
        ドメインを指定するメソッド。アプリケーションごとの言語設定の保存先を決定していれば、その中で
        開くドメインが決定される。
        
        - method setVar($key,$value)
        このメソッドは i18:name をコールした際に、最後に呼ばれるので、文章に反映される。
        
        - method translate($key)
        実際に文字列を置き換えるメソッド。 
        

                require_once 'PHPTAL/TranslationService.php';
        
        class MyTranslator implements PHPTAL_TranslationService {
        
            private $_currentDomain;
            private $_domains = array();
            private $_currentLang;
            private $_context = array();
            
            public function setLanguage(){
                $langs = func_get_args();
                foreach ($langs as $lang){
                    // if $lang known use it and stop the loop
                    $this->_currentLang = $lang;
                    return;
                }
            }

            public function useDomain($domain){
                if (!array_key_exists($domain, $this->_domains)){
                    $file = "domains/$this->_currentLang/$domain.php";
                    $this->_domains[$domain] = include($file);
                }
                $this->_currentDomain = $this->_domains[$domain];
            }
            
            public function setVar($key, $value){
                $this->_context[$key] = $value;
            }
            
            public function translate($key){
                $value = $this->_currentDomain[$key];
                
                // interpolate ${myvar} using context associative array
                while (preg_match('/\${(.*?)\}/sm', $value, $m)){
                    list($src,$var) = $m;
                    if (!array_key_exists($var, $this->_context)){
                        $err = sprintf('Interpolation error, var "%s" not set', 
                                       $var);
                        throw new Exception($err);
                    }
                    $value = str_replace($src, $this->_context[$var], $value);
                }
                
                return $value;
            }
        }
        ?>
       
    ・PHPTAL_GetTextTranslator
    gettextはPHPでGNU Gettextをサポートさせているときに利用可能な、標準のGNU国際化/翻訳システム。
    簡単に利用できるが、gettext関連の関数ををインストールしないといけないのが難点。
    
    http://php.mirror.camelnetwork.com/manual/ja/function.gettext.php

        - gettext のインストール
        - 「--with-gettext[=DIR]」をつけてPHPを再インストール
        - gettext が利用できるかは以下で確認可能
        if (function_exists("gettext")){
            echo "gettext is supported\n";
        } else {
            echo "gettext is not installed\n";
        }
        
        -ディレクトリ構成
       こんな感じの予定。translationsじゃなくてlocalのほうがいいか・・・?
        views
        `-- translations
            |-- en_US
            |   `-- LC_MESSAGES
            |       |-- zfs.mo
            |       `-- zfs.po
            |-- ja_JP
            |   `-- LC_MESSAGES
            |       |-- messages.mo
            |       |-- zfs.mo
            |       `-- zfs.po
            `-- zfs.pot
        
        - pot ファイルの作成
        多言語対応するための基本となるファイル。phpに直接記載していれば、xgettext コマンドでとってこれる。
        今回はPHPTALに記述されるので自動抽出ができない為、手動で作成。以下のような感じ。
        !!!このファイルを言語関連の基本ファイルとし、msgid の追記はこのファイルのみに行う。!!!
        ※"Content-Transfer-Encoding: 8bit\n"までの部分は xgettext に --force-po オプションで自動生成
        
        # SOME DESCRIPTIVE TITLE.
        # Copyright (C) YEAR THE PACKAGE'S COPYRIGHT HOLDER
        # This file is distributed under the same license as the PACKAGE package.
        # FIRST AUTHOR , YEAR.
        #
        #, fuzzy
        msgid ""
        msgstr ""
        "Project-Id-Version: PACKAGE VERSION\n"
        "Report-Msgid-Bugs-To: \n"
        "POT-Creation-Date: 2007-12-27 01:00+0900\n"
        "PO-Revision-Date: YEAR-MO-DA HO:MI+ZONE\n"
        "Last-Translator: FULL NAME \n"
        "Language-Team: LANGUAGE \n"
        "MIME-Version: 1.0\n"
        "Content-Type: text/plain; charset=CHARSET\n"
        "Content-Transfer-Encoding: 8bit\n"
        
        msgid "Hello World"
        msgstr ""
        
        msgid "Welcome"
        msgstr ""
        
        msgid "null"
        msgstr ""
        
        
        
        TODO:TAL形式から何らかの形式に変換して、自動で pot ファイルを作成できるようにする。
        
        - po ファイルの作成 & マージ
        一番最初は差分などないので、zfs.pot を 翻訳したい言語のディレクトリにコピーする。
        
        % cp zfs.pot ./ja_JP/LC_MESSAGES/zfs.po
        
        コピーしたら、そのファイルを編集する。こんな感じ>>> msgstr "ここに翻訳を書く"
        
        zfs.pot に文章を追加したら、差分が発生してしまい、コピーして対応するには手間が
        かかりすぎるので、マージを行う。※ 第一引数と第二引数の順番に注意!
        
        % msgmerge ja_JP/LC_MESSAGES/zfs.po zfs.pot -o ja_JP/LC_MESSAGES/zfs.po
        
        これで、差分を反映してくれる。差分は msgid に値が入っているもののみ反映。
        また、pot 側で削除した場合、翻訳済みのものはコメントアウト、未翻訳の場合は削除される。
        
        # SOME DESCRIPTIVE TITLE.
        # Copyright (C) YEAR THE PACKAGE'S COPYRIGHT HOLDER
        # This file is distributed under the same license as the PACKAGE package.
        # FIRST AUTHOR , YEAR.
        #
        #, fuzzy
        msgid ""
        msgstr ""
        "Project-Id-Version: PACKAGE VERSION\n"
        "Report-Msgid-Bugs-To: \n"
        "POT-Creation-Date: 2007-12-27 01:00+0900\n"
        "PO-Revision-Date: 2007-12-26 22:05+0900\n"
        "Last-Translator: FULL NAME \n"
        "Language-Team: LANGUAGE \n"
        "MIME-Version: 1.0\n"
        "Content-Type: text/plain; charset=t=UTF-8\n"
        "Content-Transfer-Encoding: 8bit\n"
        
        #, fuzzy
        msgid "Hello World"
        msgstr "はろーわーるど"
        
        msgid "Welcome"
        msgstr "ようこそ"
        
        msgid "null"
        msgstr ""
        
        また、差分を反映した際、幾つ未翻訳や変更点があるかなどを確認したいときには、
        以下のコマンドで確認できる
        
        % msgfmt --verbose ja_JP/LC_MESSAGES/zfs.po
        1 個の翻訳メッセージ, 1 個の翻訳があいまいです, 1 個の未訳のメッセージ.
        
        結果の説明
        ・翻訳メッセージ -> そのまま文字通り。
        ・翻訳があいまい -> pot ファイル側で変更があった場合、po 側に変更があったことを通知するために
                            変更があった msgid の前の行に #, fuzzy と追記される。これの数が表示される。
                            つまり、翻訳済みだが変更があったため要確認 な状態。
        ・未訳のメッセージ -> 新規に追加された msgid の分 msgstr が空の状態。
        
        - mo ファイルの作成
        プログラム側で扱うバイナリ形式に変換する
        
        % cd ./ja_JP/LC_MESSAGES/
        % msgfmt zfs.po [-f] -o zfs.mo
        ※ -f オプションはfuzzyを含むかどうかの指定
        
        - View への組み込み
        
        $tr = new PHPTAL_GetTextTranslator();
        $tr->setLanguage('ja_JP.UTF-8', 'ja_JP');
        $tr->addDomain('zfs', '/path/to/app/views/translations/');
        $tr->useDomain('zfs');
        
        $view = new ZFS_View();
        $view->setTranslator($tr);