OSDN Git Service

- reimplemented ethna specific message catalog processing routine.
authormumumu-org <mumumu-org@2ef88817-412d-0410-a32c-8029a115e976>
Tue, 17 Jun 2008 15:53:12 +0000 (15:53 +0000)
committermumumu-org <mumumu-org@2ef88817-412d-0410-a32c-8029a115e976>
Tue, 17 Jun 2008 15:53:12 +0000 (15:53 +0000)
CHANGES
class/Ethna_I18N.php
test/Ethna_I18N_Test.php [new file with mode: 0644]

diff --git a/CHANGES b/CHANGES
index 5850aa8..f43a733 100644 (file)
--- a/CHANGES
+++ b/CHANGES
@@ -30,6 +30,9 @@
 --- 2.3.5までのコードで gettext を利用している場合は、設定が明示的に必要です。
 -- "ethna add-project" コマンドに [-l|locale] オプションを追加
 -- スケルトンの日本語コメントをすべてASCIIに変更(好みのエンコーディングで編集できるようにするため)
+-- gettextを使わない場合向けに、Ethna独自のメッセージカタログを実装
+--- ini ファイルライクなフォーマットで msgid と翻訳を格納する方式
+--- Ethna_I18N#setLanguage で出力ロケールの切り替えも可能
 - [Breaking B.C] レンタルサーバを考慮して、[appid]_Controllerの include_path を、[appid]/lib を優先するように変更
 -- include_path の順番に依存するコードは少ないとは思いますが、移行の際は注意すべきです。
 - "ethna add-project" コマンドに [-s|skeldir] オプションを追加
index dbbcde5..6135907 100644 (file)
@@ -62,6 +62,9 @@ class Ethna_I18N
      *  @access private
      */
 
+    /** @var    Ethna_Controller  コントローラーオブジェクト  */
+    var $ctl;
+
     /** @var    bool    gettextフラグ */
     var $use_gettext;
 
@@ -100,9 +103,9 @@ class Ethna_I18N
         $this->locale_dir = $locale_dir;
         $this->appid = strtoupper($appid);
 
-        $ctl =& Ethna_Controller::getInstance();
-        $config =& $ctl->getConfig();
-        $this->logger =& $ctl->getLogger();
+        $this->ctl =& Ethna_Controller::getInstance();
+        $config =& $this->ctl->getConfig();
+        $this->logger =& $this->ctl->getLogger();
         $this->use_gettext = $config->get('use_gettext') ? true : false;
 
         //    gettext load check. 
@@ -139,6 +142,11 @@ class Ethna_I18N
         $this->locale = $locale;
         $this->systemencoding = $systemencoding;
         $this->clientencoding = $clientencoding;
+
+        //  強制的にメッセージカタログ再生成
+        if (!$this->use_gettext) {
+            $this->messages = $this->_makeEthnaMsgCatalog();
+        }
     }
 
     /**
@@ -162,11 +170,11 @@ class Ethna_I18N
         } else {
 
             //
+            //  初期化されてない場合は、
             //  Ethna独自のメッセージカタログを初期化
-            //  この処理ははじめに使用するときまで遅延させる
             //
             if ($this->messages === false) {
-                $this->messages = $this->_getEthnaMsgCatalog();
+                $this->messages = $this->_makeEthnaMsgCatalog();
             }
 
             //
@@ -187,33 +195,52 @@ class Ethna_I18N
      *  Ethna独自のメッセージカタログを読み込んで生成する
      *
      *  1. [appid]/locale/[locale_name]/LC_MESSAGES/*.ini
-     *     ã\81\8bã\82\89ã\83¡ã\83\83ã\82»ã\83¼ã\82¸ã\82\92読ã\81¿è¾¼ã\81¿ã\80\81$this->messages ã\82\92å\88\9dæ\9c\9få\8c\96ã\81\99ã\82\8b
+     *     ã\81\8bã\82\89ã\83¡ã\83\83ã\82»ã\83¼ã\82¸ã\82\92読ã\81¿è¾¼ã\82\80ã\80\82
      *  2. Ethnaが吐くメッセージカタログファイル名は ethna_sysmsg.ini とし、
      *     skel化して ETHNA_HOME/skel/locale/[locale_name]/ethna_sysmsg.ini に置く
      *  3. "ethna i18n" コマンドでは、1. のファイルとプロジェクトファイル
      *     内の _et('xxxx') を全て走査し、メッセージカタログを作る。gettext を利用
      *     するのであれば、potファイルを生成する。
      *  4. ethna_sysmsg.ini は単純な ini ファイル形式とし、
-     *     msgid = translation の形式とする。エンコーディングは一律 UTF-8
+     *     "msgid" = "translation" の形式とする。エンコーディングは一律 UTF-8
      * 
      *  @access  private 
      *  @return  array     読み込んだメッセージカタログ。失敗した場合は空の配列 
      */
-    function _getEthnaMsgCatalog()
+    function _makeEthnaMsgCatalog()
     {
+        if ($this->messages !== false) {
+            return; 
+        }
+
         $ret_messages = array();
 
+        //    Ethna_I18N#setLanguage を呼び出さず
+        //    このメソッドを呼び出すと、ロケール名が空になる
+        //    その場合は Ethna_Controller の設定を補う
+        if (empty($this->locale)) {
+            list($this->locale, $sys_enc, $cli_enc) = $this->ctl->getLanguage();
+        }
+
+        //    ロケールディレクトリが存在しない場合は、E_NOTICEを出し、
+        //    デフォルトの skelton ファイルを使う
         $msg_dir = sprintf("%s/%s/LC_MESSAGES", $this->locale_dir, $this->locale);
         if (!file_exists($msg_dir)) {
-            $this->logger->log(LOG_WARNING, "Message directory was not found!! : $msg_dir"); 
-            return $ret_messages;
+            //   use skelton.
+            $this->logger->log(LOG_NOTICE,
+                               "Message directory was not found!! : $msg_dir,"
+                             . " Use skelton file Instead"); 
+            $msg_dir = sprintf("%s/skel/locale/%s", ETHNA_BASE, $this->locale);
         }
                      
         $msg_dh = opendir($msg_dir);
 
-        //    ini ファイル形式だが、parse_ini_file 関数は使わない
-        //    特殊な文字が多すぎる上、parseの仕方もバージョンによって
-        //    微妙に違ったりするため。
+        //    ファイルフォーマットは ini ファイルライクだが、
+        //    parse_ini_file 関数は使わない。
+        //
+        //    キーに含められないキーワードや文字があるため。
+        //    e.x yes, no {}|&~![() 等
+        //    @see http://www.php.net/manual/en/function.parse-ini-file.php
         while (($file = readdir($msg_dh)) !== false) {
             if (is_dir($file) || !preg_match("/[A-Za-z0-9\-_]+\.ini$/", $file)) {
                 continue;
@@ -221,22 +248,81 @@ class Ethna_I18N
             $msg_file = sprintf("%s/%s", $msg_dir, $file);
             $contents = file($msg_file);
             foreach ($contents as $idx => $line) {
-                if (strpos($line, ';') === 0 || strpos($line, '=') === false) {
+
+                //  コメント、もしくは不正な行は無視する
+                trim($line);
+                if (strpos($line, ';') === 0 || strpos($line, '=') === false || $line[0] != '"') {
                     continue;
                 }
-                $catalog = array();
-                if (preg_match('/^"(.+?)"\s*=\s*"(.*?)".*$/', $line, $catalog)) {
-                    $msgid = $catalog[1];
-                    $msgstr = $catalog[2];
-                    $ret_messages[$msgid] = $msgstr;
-                } else {
+
+                $quote = 0;                // ダブルクオートの数
+                $before_is_quote = false;  // 直前の文字がダブルクォートか否か
+                $equal_op = 0;             // 等値演算子の数
+                $is_end = false;           // 終了フラグ
+                $length = strlen($line);
+                $msgid = $msgstr = '';
+
+                //    1文字ずつ、ダブルクォートの数
+                //    を基準にしてパースする
+                for ($pos = 0; $pos < $length; $pos++) {
+            
+                    //    特別な文字で分岐
+                    switch ($line[$pos]) {
+                        case '"':
+                            if (!$before_is_quote) {
+                                $quote++;
+                                continue 2;  // switch 文を抜けるのではなく、
+                                             // for文に戻る。
+                            }
+                            $before_is_quote = false;
+            
+                            //  ダブルクォートが4つに達した時点で終了
+                            if ($quote == 4) {
+                                $is_end = true;   
+                            }
+                            break; 
+                        case '=':
+                            //  等値演算子は文法的にvalidかどうかを確
+                            //  認する手段でしかない 
+                            if ($quote == 2) {
+                                $equal_op++;
+                            }
+                        case '\\': // backslash
+                            if ($quote == 1 || $quote == 3) {
+                                $before_is_quote = true;
+                            }
+                            break;
+                        default:
+                            if ($before_is_quote) {
+                                $before_is_quote = false;
+                            }
+                    }
+
+                    if ($is_end == true) {
+                        break;
+                    }
+
+                    //  パース済みの文字列を追加
+                    if ($quote == 1) {
+                        $msgid .= $line[$pos];
+                    }
+                    if ($quote == 3) {
+                        $msgstr .= $line[$pos];
+                    }
+                }
+            
+                //  valid な行かチェック
+                if ($equal_op != 1 || $quote != 4) {
                     $this->logger->log(LOG_WARNING,
                                        "invalid message catalog in {$file}, line " . ($idx + 1)
-                    ); 
-                }
+                    );
+                    continue; 
+                } 
+
+                $ret_messages[$msgid] = $msgstr; 
             }
         }
+        
         return $ret_messages;
     }
 }
diff --git a/test/Ethna_I18N_Test.php b/test/Ethna_I18N_Test.php
new file mode 100644 (file)
index 0000000..c8a2144
--- /dev/null
@@ -0,0 +1,68 @@
+<?php
+// vim: foldmethod=marker
+/**
+ *  Ethna_I18N_Test.php
+ *
+ *  @author     Yoshinari Takaoka <takaoka@beatcraft.com>
+ *  @version    $Id$
+ */
+
+//{{{    Ethna_I18N_Test
+/**
+ *  Test Case For Ethna_I18N class
+ *
+ *  @access public
+ */
+class Ethna_I18N_Test extends Ethna_UnitTestBase
+{
+    var $i18n;
+
+    function setUp()
+    {
+        $ctl =& Ethna_Controller::getInstance();
+        $this->i18n = $ctl->getI18N();
+    }
+
+    // {{{  test_get_ja_JP
+    function test_get_ja_JP()
+    {
+        //  デフォルトは日本語のメッセージが返ってくる
+        $this->assertEqual($this->i18n->get('Backend'), 'バックエンド');
+        $this->assertEqual($this->i18n->get('Could not write uploaded file to disk.'), 'ディスクへの書き込みに失敗しました。');
+        $this->assertEqual($this->i18n->get('Filter(%d)'), 'フィルタ(%d)');
+        $this->assertEqual($this->i18n->get('Heisei'), '平成');
+        $this->assertEqual($this->i18n->get('%Y/%m/%d %H:%M:%S'), '%Y年%m月%d日 %H時%M分%S秒');
+
+        //  カタログにないメッセージはそのまま返ってくる 
+        $this->assertEqual($this->i18n->get('foo'), 'foo');
+        $this->assertEqual($this->i18n->get('www.example.com'), 'www.example.com');
+    }
+    // }}}
+
+    // {{{  test_get_fallback_locale
+    function test_get_fallback_locale()
+    {
+        //  ロケール切り替え
+        $this->i18n->setLanguage('en_US', 'ASCII', 'ASCII');
+
+        //  メッセージカタログファイルがないロケールの場合は、
+        //  skel/locale/ethna_sysmsg.ini にあるメッセージが返ってくる
+        $this->assertEqual($this->i18n->get('Backend'), 'Backend');
+        $this->assertEqual($this->i18n->get('Could not write uploaded file to disk.'),
+                           'Could not write uploaded file to disk.'
+        );
+        $this->assertEqual($this->i18n->get('Filter(%d)'), 'Filter(%d)');
+        $this->assertEqual($this->i18n->get('Heisei'), 'Heisei');
+        $this->assertEqual($this->i18n->get('%Y/%m/%d %H:%M:%S'),
+                           '%Y/%m/%d %H:%M:%S'
+        );
+
+        //  カタログにないメッセージはそのまま返ってくる 
+        $this->assertEqual($this->i18n->get('foo'), 'foo');
+        $this->assertEqual($this->i18n->get('www.example.com'), 'www.example.com');
+    }
+    // }}}
+}
+// }}}
+
+?>