Source for file I18N.php

Documentation is available at I18N.php

  1. <?php
  2. // vim: foldmethod=marker
  3. /**
  4.  *  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: 225716b0a8637905a234e3fc05555f45432e19e9 $
  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.     /** @protected    Ethna_Controller  コントローラーオブジェクト  */
  52.     protected $ctl;
  53.  
  54.     /** @protected    bool    gettextフラグ */
  55.     protected $use_gettext;
  56.  
  57.     /** @protected    string  ロケール */
  58.     protected $locale;
  59.  
  60.     /** @protected    string  プロジェクトのロケールディレクトリ */
  61.     protected $locale_dir;
  62.  
  63.     /** @protected    string  アプリケーションID */
  64.     protected $appid;
  65.  
  66.     /** @protected    string  システム側エンコーディング */
  67.     protected $systemencoding;
  68.  
  69.     /** @protected    string  クライアント側エンコーディング */
  70.     protected $clientencoding;
  71.  
  72.     /** @protected    mixed   Ethna独自のメッセージカタログ */
  73.     protected $messages;
  74.  
  75.     /** @protected    mixed   ロガーオブジェクト */
  76.     protected $logger;
  77.  
  78.     /**#@-*/
  79.  
  80.     /**
  81.      *  Ethna_I18Nクラスのコンストラクタ
  82.      *
  83.      *  @access public
  84.      *  @param  string  $locale_dir プロジェクトのロケールディレクトリ
  85.      *  @param  string  $appid      アプリケーションID
  86.      */
  87.     public function __construct($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.     public static 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.     public 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.     public 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.     private 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.     public 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';'=== || 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 == || $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 != || $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. // }}}

Documentation generated on Fri, 11 Nov 2011 03:58:17 +0900 by phpDocumentor 1.4.3