OSDN Git Service

MERGE:リビジョン1828をマージ
[nucleus-jp/nucleus-next.git] / nucleus / libs / i18n.php
1 <?php\r
2 /**\r
3  * i18n class for Nucleus CMS\r
4  * written by Takashi Sakamoto as of Feb 03, 2012\r
5  * \r
6  * This includes wrapper functions of iconv and mbstring\r
7  * for multibyte processing and includes parameters related to locale.\r
8  * \r
9  * This program is free software; you can redistribute it and/or\r
10  * modify it under the terms of the GNU General Public License\r
11  * as published by the Free Software Foundation; either version 2\r
12  * of the License, or (at your option) any later version.\r
13  * (see nucleus/documentation/index.html#license for more info)\r
14  *\r
15  * @license http://nucleuscms.org/license.txt GNU General Public License\r
16  * @copyright Copyright (C) 2002-2011 The Nucleus Group\r
17  * @version $Id: i18n.php 1678 2012-02-26 07:31:36Z sakamocchi $\r
18  */\r
19 class i18n\r
20 {\r
21         static private $mode = FALSE;\r
22         \r
23         static private $current_charset = '';\r
24         static private $current_language = '';\r
25         static private $current_script = '';\r
26         static private $current_region = '';\r
27         \r
28         static private $locale_list = array();\r
29         static private $timezone = 'UTC';\r
30         \r
31         static private $forced_charset = '';\r
32         static private $forced_language = '';\r
33         static private $forced_script = '';\r
34         static private $forced_region = '';\r
35         \r
36         /**\r
37          * i18n::init\r
38          * Initializing i18n class\r
39          * \r
40          * @static\r
41          * @param       string  $charset        character set\r
42          * @return      boolean \r
43          */\r
44         static public function init($charset, $dir)\r
45         {\r
46                 /* i18n is already initialized */\r
47                 if ( self::$mode )\r
48                 {\r
49                         return TRUE;\r
50                 }\r
51                 \r
52                 /* make locale list in this Nucleus CMS */\r
53                 if ( ($handle = opendir($dir)) === FALSE )\r
54                 {\r
55                         return FALSE;\r
56                 }\r
57                 while ($filename = readdir($handle))\r
58                 {\r
59                         if (preg_match("#^(.+_.+_.+)\.{$charset}\.php$#", $filename, $matches) )\r
60                         {\r
61                                 if ( !in_array($matches[1], self::$locale_list) )\r
62                                 {\r
63                                         self::$locale_list[] = $matches[1];\r
64                                 }\r
65                         }\r
66                 }\r
67                 closedir($handle);\r
68                 \r
69                 /* set i18n backend and validate character set */\r
70                 if ( extension_loaded('iconv') )\r
71                 {\r
72                         /* this is just for checking the charset. */\r
73                         if ( iconv_set_encoding('internal_encoding', $charset)\r
74                          && iconv_set_encoding('output_encoding', $charset)\r
75                          && iconv_set_encoding('internal_encoding', $charset) )\r
76                         {\r
77                                 self::$current_charset = $charset;\r
78                                 self::$mode = 'iconv';\r
79                         }\r
80                 }\r
81                 else if ( extension_loaded('mbstring') )\r
82                 {\r
83                         /* this is just for checking the charset. */\r
84                         if ( mb_http_output($charset)\r
85                          && mb_internal_encoding($charset)\r
86                          && mb_regex_encoding($charset) )\r
87                         {\r
88                                 self::$current_charset = $charset;\r
89                                 self::$mode = 'mbstring';\r
90                         }\r
91                 }\r
92                 \r
93                 return TRUE;\r
94         }\r
95         \r
96         /**\r
97          * i18n::get_available_locale_list\r
98          * return available locale list with current charset\r
99          * \r
100          * @static\r
101          * @param       void\r
102          * @return      array   available locale list\r
103          */\r
104         static public function get_available_locale_list()\r
105         {\r
106                 return self::$locale_list;\r
107         }\r
108         \r
109         /**\r
110          * i18n::get_current_charset\r
111          * return current charset\r
112          * \r
113          * @static\r
114          * @param       void\r
115          * @return      string  $charset        current character set\r
116          */\r
117         static public function get_current_charset()\r
118         {\r
119                 return self::$current_charset;\r
120         }\r
121         \r
122         /**\r
123          * i18n::set_locale\r
124          * Set current locale\r
125          * \r
126          * NOTE:\r
127          * naming rule is "$language_$script_$region.$charset.php", refer to RFC 5646.\r
128          * @link http://www.ietf.org/rfc/rfc5646.txt\r
129          * @see 2.  The Language Tag\r
130          * \r
131          * @static\r
132          * @param       string  $locale\r
133          * @return      bool    TRUE/FALSE\r
134          * \r
135          */\r
136         static public function set_current_locale($locale)\r
137         {\r
138                 if ( preg_match('#^(.+)_(.+)_(.+)$#', $locale, $match) )\r
139                 {\r
140                         self::$current_language = $match[1];\r
141                         self::$current_script   = $match[2];\r
142                         self::$current_region   = $match[3];\r
143                         return TRUE;\r
144                 }\r
145                 return FALSE;\r
146         }\r
147         \r
148         /**\r
149          * i18n::get_locale\r
150          * Get current locale\r
151          * \r
152          * @static\r
153          * @param       void\r
154          * @return      $locale\r
155          */\r
156         static public function get_current_locale()\r
157         {\r
158                 $elements = array(self::$current_language, self::$current_script, self::$current_region);\r
159                 return implode('_', $elements);\r
160         }\r
161         \r
162         /**\r
163          * i18n::set_forced_locale()\r
164          * Set forced locale\r
165          * \r
166          * @static\r
167          * @param       string  $forced_locale\r
168          * @return      bool    TRUE/FALSE\r
169          * \r
170          */\r
171         static public function set_forced_locale($forced_locale)\r
172         {\r
173                 if ( preg_match('#^(.+)_(.+)_(.+)$#', $forced_locale, $match) )\r
174                 {\r
175                         self::$forced_language  = $match[1];\r
176                         self::$forced_script    = $match[2];\r
177                         self::$forced_region    = $match[3];\r
178                         return TRUE;\r
179                 }\r
180                 return FALSE;\r
181         }\r
182         \r
183         /**\r
184          * i18n::get_forced_locale\r
185          * Get forced locale\r
186          * \r
187          * @static\r
188          * @param       void\r
189          * @return      $forced_locale\r
190          */\r
191         static public function get_forced_locale()\r
192         {\r
193                 if ( !self::$forced_language )\r
194                 {\r
195                         return '';\r
196                 }\r
197                 \r
198                 $elements = array(self::$forced_language, self::$forced_script, self::$forced_region);\r
199                 return implode('_', $elements);\r
200         }\r
201         \r
202         /**\r
203          * i18n::set_forced_charset\r
204          * return forced charset\r
205          * \r
206          * @static\r
207          * @param       void    $charset        forced character set\r
208          * @return      void\r
209          */\r
210         static public function set_forced_charset($forced_charset)\r
211         {\r
212                 self::$forced_charset = $forced_charset;\r
213                 return;\r
214         }\r
215         \r
216         /**\r
217          * i18n::get_forced_charset\r
218          * return forced charset\r
219          * \r
220          * @static\r
221          * @param       void\r
222          * @return      string  $charset        forced character set\r
223          */\r
224         static public function get_forced_charset()\r
225         {\r
226                 return self::$forced_charset;\r
227         }\r
228         \r
229         /**\r
230          * i18n::confirm_default_date_timezone\r
231          * to avoid E_NOTICE or E_WARNING generated when every calling to a date/time function.\r
232          * \r
233          * NOTE:\r
234          * Some private servers are lack of its timezone setting\r
235          * http://www.php.net/manual/en/function.date-default-timezone-set.php\r
236          * \r
237          * @static\r
238          * @param       void\r
239          * @return      void\r
240          */\r
241         static public function confirm_default_date_timezone()\r
242         {\r
243                 if ( function_exists('date_default_timezone_get') \r
244                  && FALSE !== ($timezone = @date_default_timezone_get()))\r
245                 {\r
246                         self::$timezone = $timezone;\r
247                 }\r
248                 if (function_exists('date_default_timezone_set')) {\r
249                          @date_default_timezone_set(self::$timezone);\r
250                 }\r
251                 return;\r
252         }\r
253         \r
254         /**\r
255          * i18n::get_current_date_timezone()\r
256          * get current timezone\r
257          * \r
258          * @static\r
259          * @param       void\r
260          * @return      $timezone\r
261          */\r
262         static public function get_date_timezone()\r
263         {\r
264                 return self::$timezone;\r
265         }\r
266         \r
267         /**\r
268          * i18n::convert\r
269          * character set converter\r
270          * \r
271          * @static\r
272          * @param       string  $string target string binary\r
273          * @param       string  $from   original character set encoding\r
274          * @param       string  $to     target character set encoding\r
275          * @return      string  converted string\r
276          */\r
277         static public function convert($string, $from, $to='')\r
278         {\r
279                 if ( $to == '' )\r
280                 {\r
281                         $to = self::$current_charset;\r
282                 }\r
283                 \r
284                 if ( $from == $to )\r
285                 {\r
286                         /* do nothing */\r
287                 }\r
288                 else if ( self::$mode == 'iconv' )\r
289                 {\r
290                         $string = iconv($from, $to.'//TRANSLIT', $string);\r
291                 }\r
292                 else if ( self::$mode == 'mbstring' )\r
293                 {\r
294                         $string = mb_convert_encoding($string, $to, $from);\r
295                 }\r
296                 return (string) $string;\r
297         }\r
298         \r
299         /**\r
300          * i18n::convert_handler\r
301          * callable handler for character set converter\r
302          * \r
303          * @static\r
304          * @param       string  $string target string binary\r
305          * @return      void\r
306          */\r
307         static public function convert_handler($string)\r
308         {\r
309                 return self::convert($string, self::$current_charset, self::$forced_charset);\r
310         }\r
311         \r
312         /**\r
313          * i18n::convert_array\r
314          * recursively converting array\r
315          * \r
316          * @static\r
317          * @param       array   $array  array to convert\r
318          * @return      void\r
319          */\r
320         static public function convert_array($array, $from, $to='')\r
321         {\r
322                 if ( !is_array($array) )\r
323                 {\r
324                         $array = self::convert($array, $from, $to);\r
325                 }\r
326                 else\r
327                 {\r
328                         foreach ( $array as $key => $value )\r
329                         {\r
330                                 if ( !is_array($value) )\r
331                                 {\r
332                                         $array[$key] = self::convert($value, $from, $to);\r
333                                 }\r
334                                 else\r
335                                 {\r
336                                         self::convert_array($array[$key]);\r
337                                 }\r
338                         }\r
339                 }\r
340                 \r
341                 return $array;\r
342         }\r
343         \r
344         /**\r
345          * i18n::strlen\r
346          * strlen wrapper\r
347          * \r
348          * @static\r
349          * @param       string  $string target string\r
350          * @return      integer the number of letters\r
351          */\r
352         static public function strlen($string)\r
353         {\r
354                 $length = 0;\r
355                 if ( self::$mode == 'iconv' )\r
356                 {\r
357                         $length = iconv_strlen($string, self::$current_charset);\r
358                 }\r
359                 else if ( self::$mode == 'mbstring' )\r
360                 {\r
361                         $length = mb_strlen($string, self::$current_charset);\r
362                 }\r
363                 else\r
364                 {\r
365                         $length = strlen($string);\r
366                 }\r
367                 return (integer) $length;\r
368         }\r
369         \r
370         /**\r
371          * i18n::strpos\r
372          * strpos wrapper\r
373          * \r
374          * @static\r
375          * @param       string  $haystack       string to search\r
376          * @param       string  $needle string for search\r
377          * @param       integer $offset the position from which the search should be performed. \r
378          * @return      integer/FALSE   the numeric position of the first occurrence of needle in haystack\r
379          */\r
380         static public function strpos($haystack, $needle, $offset=0)\r
381         {\r
382                 $position = 0;\r
383                 if ( self::$mode == 'iconv' )\r
384                 {\r
385                         $position = iconv_strpos($haystack, $needle, $offset, self::$current_charset);\r
386                 }\r
387                 else if ( self::$mode == 'mbstring' )\r
388                 {\r
389                         $position = mb_strpos($haystack, $needle, $offset, self::$current_charset);\r
390                 }\r
391                 else\r
392                 {\r
393                         $position = strpos($haystack, $needle, $offset);\r
394                 }\r
395                 \r
396                 if ( $position !== FALSE)\r
397                 {\r
398                         $position = (integer) $position;\r
399                 }\r
400                 return $position;\r
401         }\r
402         \r
403         /**\r
404          * i18n::strrpos\r
405          * strrpos wrapper\r
406          * \r
407          * @static\r
408          * @param       string  $haystack       string to search\r
409          * @param       string  $needle string for search\r
410          * @return      integer/FALSE   the numeric position of the last occurrence of needle in haystack\r
411          */\r
412         static public function strrpos ($haystack, $needle)\r
413         {\r
414                 $position = 0;\r
415                 if ( self::$mode == 'iconv' )\r
416                 {\r
417                         $position = iconv_strrpos($haystack, $needle, self::$current_charset);\r
418                 }\r
419                 else if ( self::$mode == 'mbstring' )\r
420                 {\r
421                         $position = mb_strrpos($haystack, $needle, 0, self::$current_charset);\r
422                 }\r
423                 else\r
424                 {\r
425                         $position = strrpos($haystack, $needle, 0);\r
426                 }\r
427                 \r
428                 if ( $position !== FALSE)\r
429                 {\r
430                         $position = (integer) $position;\r
431                 }\r
432                 return $position;\r
433         }\r
434         \r
435         /**\r
436          * i18n::substr\r
437          * substr wrapper\r
438          * \r
439          * @static\r
440          * @param       string  $string string to be cut\r
441          * @param       string  $start  the position of starting\r
442          * @param       integer $length the length to be cut\r
443          * @return      string  the extracted part of string\r
444          */\r
445         static public function substr($string, $start, $length=0)\r
446         {\r
447                 $return = '';\r
448                 if ( self::$mode == 'iconv' )\r
449                 {\r
450                         $return = iconv_substr($string, $start, $length, self::$current_charset);\r
451                 }\r
452                 else if ( self::$mode == 'mbstring' )\r
453                 {\r
454                         $return = mb_substr($string, $start, $length, self::$current_charset);\r
455                 }\r
456                 else\r
457                 {\r
458                         $return = strrpos($string, $start, $length);\r
459                 }\r
460                 return (string) $return;\r
461         }\r
462         \r
463         /**\r
464          * i18n::strftime\r
465          * strftime function based on multibyte processing\r
466          * \r
467          * @static\r
468          * @param       string  $format format with singlebyte or multibyte\r
469          * @param       timestamp       $timestamp      UNIX timestamp\r
470          * @return      string  formatted timestamp\r
471          */\r
472         static public function strftime($format, $timestamp='')\r
473         {\r
474                 return preg_replace_callback('/(%[a-z%])/i',\r
475                         create_function('$matches', 'return strftime($matches[1], ' . intval($timestamp) . ');'),\r
476                         $format\r
477                 );\r
478         }\r
479         \r
480         /**\r
481          * i18n::formatted_datetime()\r
482          * return formatted datetime string\r
483          * \r
484          * Date and Time Formats\r
485          * @link        http://www.w3.org/TR/NOTE-datetime\r
486          * \r
487          * Working with Time Zones\r
488          * @link        http://www.w3.org/TR/timezone/\r
489          * \r
490          * @param       String  $format time expression format\r
491          * @param       String  $timestamp      UNIX timestamp\r
492          * @param       Integer $offset timestamp offset\r
493          * @return      String  formatted datetime\r
494          */\r
495         static public function formatted_datetime($format, $timestamp, $offset=0)\r
496         {\r
497                 $suffix = '';\r
498                 $string = '';\r
499                 \r
500                 switch ( $format )\r
501                 {\r
502                         case 'mysql':\r
503                                 /*\r
504                                  * MySQL 5.0 Reference Manual\r
505                                  *  10.3.1. The DATE, DATETIME, and TIMESTAMP Types\r
506                                  *   http://dev.mysql.com/doc/refman/5.0/en/datetime.html\r
507                                  */\r
508                                 $timestamp += $offset;\r
509                                 $format = '%Y-%m-%d %H:%M:%S';\r
510                                 $suffix ='';\r
511                                 break;\r
512                         case 'rfc822':\r
513                                 /*\r
514                                  * RFC 822: STANDARD FOR THE FORMAT OF ARPA INTERNET TEXT MESSAGES\r
515                                  *  5.  DATE AND TIME SPECIFICATION\r
516                                  *   http://www.ietf.org/rfc/rfc0822.txt\r
517                                  */\r
518                                 $format = '%a, %d %m %y %H:%M:%S ';\r
519                                 if ( $offset < 0 )\r
520                                 {\r
521                                         $suffix = '-';\r
522                                         $offset = -$offset;\r
523                                 }\r
524                                 else\r
525                                 {\r
526                                         $suffix = '+';\r
527                                 }\r
528                                 \r
529                                 $suffix .= sprintf("%02d%02d", floor($offset / 3600), round(($offset % 3600) / 60) );\r
530                                 break;\r
531                         case 'rfc822GMT':\r
532                                 /*\r
533                                  * RFC 822: STANDARD FOR THE FORMAT OF ARPA INTERNET TEXT MESSAGES\r
534                                  *  5.  DATE AND TIME SPECIFICATION\r
535                                  *   http://www.ietf.org/rfc/rfc0822.txt\r
536                                  */\r
537                                 $format = '%a, %d %m %y %H:%M:%S ';\r
538                                 $timestamp -= $offset;\r
539                                 $suffix = 'GMT';\r
540                                 break;\r
541                         case 'iso8601':\r
542                         case 'rfc3339':\r
543                                 /*\r
544                                  * RFC3339: Date and Time on the Internet: Timestamps\r
545                                  *  5. Date and Time format\r
546                                  *   http://www.ietf.org/rfc/rfc3339.txt\r
547                                  */\r
548                                 $format = '%Y-%m-%dT%H:%M:%S';\r
549                                 if ( $offset < 0 )\r
550                                 {\r
551                                         $suffix = '-';\r
552                                         $offset = -$offset;\r
553                                 }\r
554                                 else\r
555                                 {\r
556                                         $suffix = '+';\r
557                                 }\r
558                                 $suffix .= sprintf("%02d:%02d", floor($offset / 3600), round(($offset % 3600) / 60) );\r
559                                 break;\r
560                         case 'utc':\r
561                         case 'iso8601UTC':\r
562                         case 'rfc3339UTC':\r
563                                 /*\r
564                                  * RFC3339: Date and Time on the Internet: Timestamps\r
565                                  *  5. Date and Time format\r
566                                  *   http://www.ietf.org/rfc/rfc3339.txt\r
567                                  */\r
568                                 $timestamp -= $offset;\r
569                                 $format = '%Y-%m-%dT%H:%M:%SZ';\r
570                                 $suffix = '';\r
571                                 break;\r
572                         case '':\r
573                                 $format = '%X %x';\r
574                                 $offset = '';\r
575                                 break;\r
576                         default:\r
577                                 $suffix = '';\r
578                                 break;\r
579                 }\r
580                 return i18n::strftime($format, $timestamp) . $suffix;\r
581         }\r
582         \r
583         /**\r
584          * i18n::convert_locale_to_old_language_file_name()\r
585          * NOTE: this should be obsoleted near future.\r
586          * \r
587          * @static\r
588          * @param       string  $target_locale  locale name as language_script_region\r
589          * @return      string  old translation file name\r
590          */\r
591         static public function convert_locale_to_old_language_file_name($target_locale)\r
592         {\r
593                 $target_language = '';\r
594                 foreach ( self::$lang_refs as $language => $locale )\r
595                 {\r
596                         if ( preg_match('#-#', $language) )\r
597                         {\r
598                                 if ( $target_locale . '.' . self::$current_charset == $locale )\r
599                                 {\r
600                                         $target_language = $language;\r
601                                         break;\r
602                                 }\r
603                         }\r
604                         else if ( $target_locale == $locale )\r
605                         {\r
606                                 $target_language = $language;\r
607                         }\r
608                 }\r
609                 return $target_language;\r
610         }\r
611         \r
612         /**\r
613          * i18n::convert_old_language_file_name_to_locale()\r
614          * NOTE: this should be obsoleted near future.\r
615          * \r
616          * @static\r
617          * @param       string  $target_language        old translation file name\r
618          * @return      string  locale name as language_script_region\r
619          */\r
620         static public function convert_old_language_file_name_to_locale($target_language)\r
621         {\r
622                 $target_locale = '';\r
623                 foreach ( self::$lang_refs as $language => $locale )\r
624                 {\r
625                         if ( $target_language == $language )\r
626                         {\r
627                                 if ( preg_match('#^(.+)\.(.+)$#', $locale, $match) )\r
628                                 {\r
629                                         $target_locale = $match[1];\r
630                                 }\r
631                                 else\r
632                                 {\r
633                                         $target_locale = $locale;\r
634                                 }\r
635                                 break;\r
636                         }\r
637                 }\r
638                 return $target_locale;\r
639         }\r
640         \r
641         /**\r
642          * i18n::$lang_refs\r
643          * reference table to convert old and new way to name translation files.\r
644          * NOTE: this should be obsoleted as soon as possible.\r
645          * \r
646          * @static\r
647          */\r
648         static private $lang_refs = array(\r
649                 "english"               => "en_Latn_US",\r
650                 "english-utf8"  => "en_Latn_US.UTF-8",\r
651                 "bulgarian"     => "bg_Cyrl_BG",\r
652                 "finnish"               => "fi_Latn_FU",\r
653                 "catalan"               => "ca_Latn_ES",\r
654                 "french"                => "fr_Latn_FR",\r
655                 "russian"               => "ru_Cyrl_RU",\r
656                 "chinese"               => "zh_Hans_CN",\r
657                 "simchinese"    => "zh_Hans_CN",\r
658                 "chineseb5"     => "zh_Hant_TW",\r
659                 "traditional_chinese"   =>      "zh_Hant_TW",\r
660                 "galego"                => "gl_Latn_ES",\r
661                 "german"                => "de_Latn_DE",\r
662                 "korean-utf"    => "ko_Kore_KR.UTF-8",\r
663                 "korean-euc-kr" => "ko_Kore_KR.EUC-KR",\r
664                 "slovak"                => "sk_Latn_SK",\r
665                 "czech"         => "cs_Latn_CZ",\r
666                 "hungarian"     => "hu_Latn_HU",\r
667                 "latvian"               => "lv_Latn_LV",\r
668                 "nederlands"    => "nl_Latn_NL",\r
669                 "italiano"              => "it_Latn_IT",\r
670                 "persian"               => "fa_Arab_IR",\r
671                 "spanish"               => "es_Latn_ES",\r
672                 "spanish-utf8"  => "es_Latn_ES.UTF-8",\r
673                 "japanese-euc"  => "ja_Jpan_JP.EUC-JP",\r
674                 "japanese-utf8" => "ja_Jpan_JP.UTF-8",\r
675                 "portuguese_brazil"     => "pt_Latn_BR"\r
676         );\r
677 }\r