Source for file Ethna_I18N.php

Documentation is available at Ethna_I18N.php

  1. <?php
  2. // vim: foldmethod=marker
  3. /**
  4.  *  Ethna_I18N.php
  5.  *
  6.  *  @author     Masaki Fujimoto <fujimoto@php.net>
  7.  *  @license    http://www.opensource.org/licenses/bsd-license.php The BSD License
  8.  *  @package    Ethna
  9.  *  @version    $Id: a4970c99c631424c24bc55392c1186a3cb033a02 $
  10.  */
  11.  
  12. // {{{  mbstring enabled check
  13. function mb_enabled()
  14. {
  15.     return (extension_loaded('mbstring')) ? true : false;
  16. }
  17. // }}}
  18.  
  19. // {{{ I18N shortcut
  20. /**
  21.  *  メッセージカタログからロケールに適合するメッセージを取得します。
  22.  *  Ethna_I18N#get のショートカットです。
  23.  *
  24.  *  @access public
  25.  *  @param  string  $message    メッセージ
  26.  *  @return string  ロケールに適合するメッセージ
  27.  *  @see    Ethna_I18N#get
  28.  */
  29. function _et($message) 
  30. {
  31.     $ctl =& Ethna_Controller::getInstance();
  32.     $i18n =& $ctl->getI18N();
  33.     return $i18n->get($message);
  34. }
  35. // }}}
  36.  
  37. // {{{ Ethna_I18N
  38. /**
  39.  *  i18n関連の処理を行うクラス
  40.  *
  41.  *  @author     Masaki Fujimoto <fujimoto@php.net>
  42.  *  @access     public
  43.  *  @package    Ethna
  44.  */
  45. class Ethna_I18N
  46. {
  47.     /**#@+
  48.      *  @access private
  49.      */
  50.  
  51.     /** @var    Ethna_Controller  コントローラーオブジェクト  */
  52.     var $ctl;
  53.  
  54.     /** @var    bool    gettextフラグ */
  55.     var $use_gettext;
  56.  
  57.     /** @var    string  ロケール */
  58.     var $locale;
  59.  
  60.     /** @var    string  プロジェクトのロケールディレクトリ */
  61.     var $locale_dir;
  62.  
  63.     /** @var    string  アプリケーションID */
  64.     var $appid;
  65.  
  66.     /** @var    string  システム側エンコーディング */
  67.     var $systemencoding;
  68.  
  69.     /** @var    string  クライアント側エンコーディング */
  70.     var $clientencoding;
  71.  
  72.     /** @var    mixed   Ethna独自のメッセージカタログ */
  73.     var $messages;
  74.  
  75.     /** @var    mixed   ロガーオブジェクト */
  76.     var $logger;
  77.  
  78.     /**#@-*/
  79.  
  80.     /**
  81.      *  Ethna_I18Nクラスのコンストラクタ
  82.      *
  83.      *  @access public
  84.      *  @param  string  $locale_dir プロジェクトのロケールディレクトリ
  85.      *  @param  string  $appid      アプリケーションID
  86.      */
  87.     function Ethna_I18N($locale_dir, $appid)
  88.     {
  89.         $this->locale_dir = $locale_dir;
  90.         $this->appid = $appid;
  91.  
  92.         $this->ctl =& Ethna_Controller::getInstance();
  93.         $config =& $this->ctl->getConfig();
  94.         $this->logger =& $this->ctl->getLogger();
  95.         $this->use_gettext = $config->get('use_gettext') ? true : false;
  96.  
  97.         //    gettext load check. 
  98.         if ($this->use_gettext === true
  99.          && !extension_loaded("gettext")) {
  100.             $this->logger->log(LOG_WARNING,
  101.                 "You specify to use gettext in ${appid}/etc/${appid}-ini.php, "
  102.               . "but gettext extension was not installed !!!"
  103.             );
  104.         }
  105.  
  106.         $this->messages = false;  //  not initialized yet.
  107.     }
  108.     
  109.     /**
  110.      *  タイムゾーンを設定する PHP 5.1.0 以前では
  111.      *  無意味なので呼ぶ必要がありません。
  112.      *
  113.      *  @access public
  114.      *  @param  string  $timezone       タイムゾーン名(e.x Asia/Tokyo)
  115.      *  @see    http://www.php.net/manual/ja/timezones.php
  116.      *  @static
  117.      */
  118.     function setTimeZone($timezone = 'UTC')
  119.     {
  120.         //   date.timezone 設定は PHP 5.1.0 以降でのみ
  121.         //   利用可能
  122.         ini_set('date.timezone', $timezone);
  123.     } 
  124.  
  125.     /**
  126.      *  ロケール、言語設定を設定する
  127.      *
  128.      *  @access public
  129.      *  @param  string  $locale         ロケール名(e.x ja_JP, en_US 等)
  130.      *                                   (ll_cc の形式。ll = 言語コード cc = 国コード)
  131.      *  @param  string  $systemencoding システムエンコーディング名
  132.      *  @param  string  $clientencoding クライアントエンコーディング名
  133.      *                                   (=テンプレートのエンコーディングと考えてよい)
  134.      *  @see    http://www.gnu.org/software/gettext/manual/html_node/Locale-Names.html
  135.      */
  136.     function setLanguage($locale, $systemencoding = null, $clientencoding = null)
  137.     {
  138.         setlocale(LC_ALL, $locale . ($systemencoding !== null ? "." . $systemencoding : ""));
  139.  
  140.         if ($this->use_gettext) {
  141.             bind_textdomain_codeset($locale, $clientencoding);
  142.             bindtextdomain($locale, $this->locale_dir);
  143.             textdomain($locale);
  144.         }
  145.  
  146.         $this->locale = $locale;
  147.         $this->systemencoding = $systemencoding;
  148.         $this->clientencoding = $clientencoding;
  149.  
  150.         //  強制的にメッセージカタログ再生成
  151.         if (!$this->use_gettext) {
  152.             $this->messages = $this->_makeEthnaMsgCatalog();
  153.         }
  154.     }
  155.  
  156.     /**
  157.      *  メッセージカタログからロケールに適合するメッセージを取得する
  158.      *
  159.      *  @access public
  160.      *  @param  string  $msg    メッセージ
  161.      *  @return string  ロケールに適合するメッセージ
  162.      */
  163.     function get($msg)
  164.     {
  165.  
  166.         if ($this->use_gettext) {
  167.  
  168.             //
  169.             //    gettext から返されるメッセージは、
  170.             //    [appid]/locale/[locale_name]/LC_MESSAGES/[locale].mo から
  171.             //    返される。エンコーディング変換はgettext任せである
  172.             //
  173.             return gettext($msg);
  174.  
  175.         } else {
  176.  
  177.             //
  178.             //  初期化されてない場合は、
  179.             //  Ethna独自のメッセージカタログを初期化
  180.             //
  181.             if ($this->messages === false) {
  182.                 $this->messages = $this->_makeEthnaMsgCatalog();
  183.             }
  184.  
  185.             //
  186.             //  Ethna独自のメッセージは、
  187.             //  [appid]/locale/[locale_name]/LC_MESSAGES/*.ini から
  188.             //  返される。
  189.             //
  190.             if (isset($this->messages[$msg]) && !empty($this->messages[$msg])) {
  191.  
  192.                 $ret_message = $this->messages[$msg];
  193.  
  194.                 //
  195.                 //  convert message in case $client_encoding
  196.                 //  setting IS NOT UTF-8.
  197.                 //
  198.                 //  @see Ethna_Controller#_getDefaultLanguage
  199.                 // 
  200.                 if (strcasecmp($this->clientencoding, 'UTF-8') !== 0) {
  201.                     return mb_convert_encoding($ret_message, $this->clientencoding, 'UTF-8');
  202.                 }
  203.  
  204.                 return $ret_message;
  205.             }
  206.  
  207.         }
  208.  
  209.         return $msg;
  210.     }
  211.  
  212.     /**
  213.      *  Ethna独自のメッセージカタログを読み込んで生成する
  214.      *
  215.      *  1. [appid]/locale/[locale_name]/LC_MESSAGES/*.ini
  216.      *     からメッセージを読み込む。
  217.      *  2. Ethnaが吐くメッセージカタログファイル名は ethna_sysmsg.ini とし、
  218.      *     skel化して ETHNA_HOME/skel/locale/[locale_name]/ethna_sysmsg.ini に置く
  219.      *  3. "ethna i18n" コマンドでは、1. のファイルとプロジェクトファイル
  220.      *     内の _et('xxxx') を全て走査し、メッセージカタログを作る。gettext を利用
  221.      *     するのであれば、potファイルを生成する。
  222.      *  4. ethna_sysmsg.ini は単純な ini ファイル形式とし、
  223.      *     "msgid" = "translation" の形式とする。エンコーディングは一律 UTF-8
  224.      * 
  225.      *  @access  private
  226.      *  @return  array     読み込んだメッセージカタログ。失敗した場合は空の配列
  227.      */
  228.     function _makeEthnaMsgCatalog()
  229.     {
  230.         $ret_messages = array();
  231.  
  232.         //    Ethna_I18N#setLanguage を呼び出さず
  233.         //    このメソッドを呼び出すと、ロケール名が空になる
  234.         //    その場合は Ethna_Controller の設定を補う
  235.         if (empty($this->locale)) {
  236.             list($this->locale, $sys_enc, $cli_enc) = $this->ctl->getLanguage();
  237.         }
  238.  
  239.         //    ロケールディレクトリが存在しない場合は、E_NOTICEを出し、
  240.         //    デフォルトの skelton ファイルを使う
  241.         $msg_dir = sprintf("%s/%s/LC_MESSAGES", $this->locale_dir, $this->locale);
  242.         if (!file_exists($msg_dir)) {
  243.             //   use skelton.
  244.             $this->logger->log(LOG_NOTICE,
  245.                                "Message directory was not found!! : $msg_dir,"
  246.                              . " Use skelton file Instead"); 
  247.             $msg_dir = sprintf("%s/skel/locale/%s", ETHNA_BASE, $this->locale);
  248.             if (!file_exists($msg_dir)) {  // last fallback.
  249.                 $msg_dir = sprintf("%s/skel/locale", ETHNA_BASE);
  250.             }
  251.         }
  252.                      
  253.         //  localeディレクトリ内のファイルを読み込み、parseする
  254.         $msg_dh = opendir($msg_dir);
  255.         while (($file = readdir($msg_dh)) !== false) {
  256.             if (is_dir($file) || !preg_match("/[A-Za-z0-9\-_]+\.ini$/", $file)) {
  257.                 continue;
  258.             }
  259.             $msg_file = sprintf("%s/%s", $msg_dir, $file);
  260.             $messages = $this->parseEthnaMsgCatalog($msg_file);
  261.             $ret_messages = array_merge($ret_messages, $messages);
  262.         }
  263.         
  264.         return $ret_messages;
  265.     }
  266.  
  267.     /**
  268.      *  Ethna独自のメッセージカタログをparseする
  269.      *
  270.      *  @access  public
  271.      *  @param   string    メッセージカタログファイル名
  272.      *  @return  array     読み込んだメッセージカタログ。失敗した場合は空の配列
  273.      */
  274.     function parseEthnaMsgCatalog($file)
  275.     {
  276.         $messages = array();
  277.  
  278.         //
  279.         //    ファイルフォーマットは ini ファイルライクだが、
  280.         //    parse_ini_file 関数は使わない。
  281.         //
  282.         //    キーに含められないキーワードや文字があるため。
  283.         //    e.x yes, no {}|&~![() 等
  284.         //    @see http://www.php.net/manual/en/function.parse-ini-file.php
  285.         //
  286.         $contents = file($file);
  287.         if ($contents === false) {
  288.             return $messages;
  289.         }
  290.  
  291.         $quote = 0;                   // ダブルクオートの数 
  292.         $in_translation_line = false; // 翻訳行をパース中か否か
  293.         $before_is_quote = false;     // 直前の文字がクォート文字(\)か否か
  294.         $equal_op = 0;                // 等値演算子の数
  295.         $is_end = false;              // 終了フラグ
  296.         $msgid = $msgstr = '';
  297.  
  298.         foreach ($contents as $idx => $line) {
  299.  
  300.             //  コメント行または空行は無視する。
  301.             //  ホワイトスペースを除いた上で、それと看做される行も無視する
  302.             $ltrimed_line = ltrim($line);
  303.             if ($in_translation_line == false
  304.             && (strpos($ltrimed_line, ';') === 0 || preg_match('/^$/', $ltrimed_line))) {
  305.                 continue;
  306.             }
  307.  
  308.             //    1文字ずつ、ダブルクォートの数
  309.             //    を基準にしてパースする
  310.             $length = strlen($line);
  311.             for ($pos = 0; $pos < $length; $pos++) {
  312.  
  313.                 //    特別な文字で分岐
  314.                 switch ($line[$pos]) {
  315.                     case '"':
  316.                         if ($in_translation_line == false && $pos == 0) {
  317.                             $in_translation_line = true;  // 翻訳行開始
  318.                         }
  319.                         if (!$before_is_quote) {
  320.                             $quote++;
  321.                             continue 2;  // switch 文を抜けるのではなく、
  322.                                          // for文に戻る = 次の文字へ
  323.                         }
  324.                         //  クォートされた「"」
  325.                         $before_is_quote = false;         
  326.                         break; 
  327.                     case '=':
  328.                         //  等値演算子は文法的にvalidかどうかを確
  329.                         //  認する手段でしかない 
  330.                         if ($quote == 2) {
  331.                             $equal_op++;
  332.                         }
  333.                     case '\\': // backslash
  334.                         //   クォート用のバックスラッシュと看做す
  335.                         if ($quote == 1 || $quote == 3) {
  336.                             $before_is_quote = true;
  337.                         }
  338.                         break;
  339.                     default:
  340.                         if ($before_is_quote) {
  341.                             $before_is_quote = false;
  342.                         }
  343.                         if ($quote == 4) {
  344.                             $is_end = true;   
  345.                         }
  346.                 }
  347.  
  348.                 if ($is_end == true) {
  349.                     $in_translation_line = false;  //  翻訳行終了
  350.                     break;
  351.                 }
  352.  
  353.                 //  パース済みの文字列を追加
  354.                 if ($quote == 1) {
  355.                     $msgid .= $line[$pos];
  356.                 }
  357.                 if ($quote == 3) {
  358.                     $msgstr .= $line[$pos];
  359.                 }
  360.             }
  361.         
  362.             //  一行分のパース終了
  363.             if ($is_end == false) {
  364.                 //  翻訳行がまだ終わってない場合次の行へ
  365.                 continue;
  366.             } elseif ($equal_op != 1 || $quote != 4) { 
  367.                 //  終わっているが、valid な翻訳行でない場合
  368.                 $this->logger->log(LOG_WARNING,
  369.                                    "invalid message catalog in {$file}, line " . ($idx + 1)
  370.                 );
  371.                 continue; 
  372.             } 
  373.  
  374.             //  カタログに追加
  375.             $msgid = preg_replace('#\\\"#', '"', $msgid);
  376.             $msgstr = preg_replace('#\\\"#', '"', $msgstr);
  377.             $messages[$msgid] = $msgstr; 
  378.  
  379.             //  パース用変数をリセット
  380.             $quote = 0;                   
  381.             $in_translation_line = false;
  382.             $before_is_quote = false;
  383.             $equal_op = 0;
  384.             $is_end = false;
  385.             $msgid = $msgstr = '';
  386.         }
  387.         
  388.         return $messages;
  389.     }
  390. }
  391. // }}}
  392.  
  393. ?>

Documentation generated on Fri, 11 Nov 2011 03:59:46 +0900 by phpDocumentor 1.4.3