3 * i18n class for Nucleus CMS
\r
4 * written by Takashi Sakamoto as of Feb 03, 2012
\r
6 * This includes wrapper functions of iconv and mbstring
\r
7 * for multibyte processing and includes parameters related to locale.
\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
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
21 static private $mode = FALSE;
\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
28 static private $locale_list = array();
\r
29 static private $timezone = 'UTC';
\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
38 * Initializing i18n class
\r
41 * @param string $charset character set
\r
44 static public function init($charset, $dir)
\r
46 /* i18n is already initialized */
\r
52 /* make locale list in this Nucleus CMS */
\r
53 if ( ($handle = opendir($dir)) === FALSE )
\r
57 while ($filename = readdir($handle))
\r
59 if (preg_match("#^(.+_.+_.+)\.{$charset}\.php$#", $filename, $matches) )
\r
61 if ( !in_array($matches[1], self::$locale_list) )
\r
63 self::$locale_list[] = $matches[1];
\r
69 /* set i18n backend and validate character set */
\r
70 if ( extension_loaded('iconv') )
\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
77 self::$current_charset = $charset;
\r
78 self::$mode = 'iconv';
\r
81 else if ( extension_loaded('mbstring') )
\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
88 self::$current_charset = $charset;
\r
89 self::$mode = 'mbstring';
\r
97 * i18n::get_available_locale_list
\r
98 * return available locale list with current charset
\r
102 * @return array available locale list
\r
104 static public function get_available_locale_list()
\r
106 return self::$locale_list;
\r
110 * i18n::get_current_charset
\r
111 * return current charset
\r
115 * @return string $charset current character set
\r
117 static public function get_current_charset()
\r
119 return self::$current_charset;
\r
124 * Set current locale
\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
132 * @param string $locale
\r
133 * @return bool TRUE/FALSE
\r
136 static public function set_current_locale($locale)
\r
138 if ( preg_match('#^(.+)_(.+)_(.+)$#', $locale, $match) )
\r
140 self::$current_language = $match[1];
\r
141 self::$current_script = $match[2];
\r
142 self::$current_region = $match[3];
\r
150 * Get current locale
\r
156 static public function get_current_locale()
\r
158 $elements = array(self::$current_language, self::$current_script, self::$current_region);
\r
159 return implode('_', $elements);
\r
163 * i18n::set_forced_locale()
\r
164 * Set forced locale
\r
167 * @param string $forced_locale
\r
168 * @return bool TRUE/FALSE
\r
171 static public function set_forced_locale($forced_locale)
\r
173 if ( preg_match('#^(.+)_(.+)_(.+)$#', $forced_locale, $match) )
\r
175 self::$forced_language = $match[1];
\r
176 self::$forced_script = $match[2];
\r
177 self::$forced_region = $match[3];
\r
184 * i18n::get_forced_locale
\r
185 * Get forced locale
\r
189 * @return $forced_locale
\r
191 static public function get_forced_locale()
\r
193 if ( !self::$forced_language )
\r
198 $elements = array(self::$forced_language, self::$forced_script, self::$forced_region);
\r
199 return implode('_', $elements);
\r
203 * i18n::set_forced_charset
\r
204 * return forced charset
\r
207 * @param void $charset forced character set
\r
210 static public function set_forced_charset($forced_charset)
\r
212 self::$forced_charset = $forced_charset;
\r
217 * i18n::get_forced_charset
\r
218 * return forced charset
\r
222 * @return string $charset forced character set
\r
224 static public function get_forced_charset()
\r
226 return self::$forced_charset;
\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
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
241 static public function confirm_default_date_timezone()
\r
243 if ( function_exists('date_default_timezone_get')
\r
244 && FALSE !== ($timezone = @date_default_timezone_get()))
\r
246 self::$timezone = $timezone;
\r
248 if (function_exists('date_default_timezone_set')) {
\r
249 @date_default_timezone_set(self::$timezone);
\r
255 * i18n::get_current_date_timezone()
\r
256 * get current timezone
\r
260 * @return $timezone
\r
262 static public function get_date_timezone()
\r
264 return self::$timezone;
\r
269 * character set converter
\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
277 static public function convert($string, $from, $to='')
\r
281 $to = self::$current_charset;
\r
284 if ( $from == $to )
\r
288 else if ( self::$mode == 'iconv' )
\r
290 $string = iconv($from, $to.'//TRANSLIT', $string);
\r
292 else if ( self::$mode == 'mbstring' )
\r
294 $string = mb_convert_encoding($string, $to, $from);
\r
296 return (string) $string;
\r
300 * i18n::convert_handler
\r
301 * callable handler for character set converter
\r
304 * @param string $string target string binary
\r
307 static public function convert_handler($string)
\r
309 return self::convert($string, self::$current_charset, self::$forced_charset);
\r
313 * i18n::convert_array
\r
314 * recursively converting array
\r
317 * @param array $array array to convert
\r
320 static public function convert_array($array, $from, $to='')
\r
322 if ( !is_array($array) )
\r
324 $array = self::convert($array, $from, $to);
\r
328 foreach ( $array as $key => $value )
\r
330 if ( !is_array($value) )
\r
332 $array[$key] = self::convert($value, $from, $to);
\r
336 self::convert_array($array[$key]);
\r
349 * @param string $string target string
\r
350 * @return integer the number of letters
\r
352 static public function strlen($string)
\r
355 if ( self::$mode == 'iconv' )
\r
357 $length = iconv_strlen($string, self::$current_charset);
\r
359 else if ( self::$mode == 'mbstring' )
\r
361 $length = mb_strlen($string, self::$current_charset);
\r
365 $length = strlen($string);
\r
367 return (integer) $length;
\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
380 static public function strpos($haystack, $needle, $offset=0)
\r
383 if ( self::$mode == 'iconv' )
\r
385 $position = iconv_strpos($haystack, $needle, $offset, self::$current_charset);
\r
387 else if ( self::$mode == 'mbstring' )
\r
389 $position = mb_strpos($haystack, $needle, $offset, self::$current_charset);
\r
393 $position = strpos($haystack, $needle, $offset);
\r
396 if ( $position !== FALSE)
\r
398 $position = (integer) $position;
\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
412 static public function strrpos ($haystack, $needle)
\r
415 if ( self::$mode == 'iconv' )
\r
417 $position = iconv_strrpos($haystack, $needle, self::$current_charset);
\r
419 else if ( self::$mode == 'mbstring' )
\r
421 $position = mb_strrpos($haystack, $needle, 0, self::$current_charset);
\r
425 $position = strrpos($haystack, $needle, 0);
\r
428 if ( $position !== FALSE)
\r
430 $position = (integer) $position;
\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
445 static public function substr($string, $start, $length=0)
\r
448 if ( self::$mode == 'iconv' )
\r
450 $return = iconv_substr($string, $start, $length, self::$current_charset);
\r
452 else if ( self::$mode == 'mbstring' )
\r
454 $return = mb_substr($string, $start, $length, self::$current_charset);
\r
458 $return = strrpos($string, $start, $length);
\r
460 return (string) $return;
\r
465 * strftime function based on multibyte processing
\r
468 * @param string $format format with singlebyte or multibyte
\r
469 * @param timestamp $timestamp UNIX timestamp
\r
470 * @return string formatted timestamp
\r
472 static public function strftime($format, $timestamp='')
\r
474 return preg_replace_callback('/(%[a-z%])/i',
\r
475 create_function('$matches', 'return strftime($matches[1], ' . intval($timestamp) . ');'),
\r
481 * i18n::formatted_datetime()
\r
482 * return formatted datetime string
\r
484 * Date and Time Formats
\r
485 * @link http://www.w3.org/TR/NOTE-datetime
\r
487 * Working with Time Zones
\r
488 * @link http://www.w3.org/TR/timezone/
\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
495 static public function formatted_datetime($format, $timestamp, $offset=0)
\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
508 $timestamp += $offset;
\r
509 $format = '%Y-%m-%d %H:%M:%S';
\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
518 $format = '%a, %d %m %y %H:%M:%S ';
\r
522 $offset = -$offset;
\r
529 $suffix .= sprintf("%02d%02d", floor($offset / 3600), round(($offset % 3600) / 60) );
\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
537 $format = '%a, %d %m %y %H:%M:%S ';
\r
538 $timestamp -= $offset;
\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
548 $format = '%Y-%m-%dT%H:%M:%S';
\r
552 $offset = -$offset;
\r
558 $suffix .= sprintf("%02d:%02d", floor($offset / 3600), round(($offset % 3600) / 60) );
\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
568 $timestamp -= $offset;
\r
569 $format = '%Y-%m-%dT%H:%M:%SZ';
\r
580 return i18n::strftime($format, $timestamp) . $suffix;
\r
584 * i18n::convert_locale_to_old_language_file_name()
\r
585 * NOTE: this should be obsoleted near future.
\r
588 * @param string $target_locale locale name as language_script_region
\r
589 * @return string old translation file name
\r
591 static public function convert_locale_to_old_language_file_name($target_locale)
\r
593 $target_language = '';
\r
594 foreach ( self::$lang_refs as $language => $locale )
\r
596 if ( preg_match('#-#', $language) )
\r
598 if ( $target_locale . '.' . self::$current_charset == $locale )
\r
600 $target_language = $language;
\r
604 else if ( $target_locale == $locale )
\r
606 $target_language = $language;
\r
609 return $target_language;
\r
613 * i18n::convert_old_language_file_name_to_locale()
\r
614 * NOTE: this should be obsoleted near future.
\r
617 * @param string $target_language old translation file name
\r
618 * @return string locale name as language_script_region
\r
620 static public function convert_old_language_file_name_to_locale($target_language)
\r
622 $target_locale = '';
\r
623 foreach ( self::$lang_refs as $language => $locale )
\r
625 if ( $target_language == $language )
\r
627 if ( preg_match('#^(.+)\.(.+)$#', $locale, $match) )
\r
629 $target_locale = $match[1];
\r
633 $target_locale = $locale;
\r
638 return $target_locale;
\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
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