OSDN Git Service

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