PageRenderTime 47ms CodeModel.GetById 17ms RepoModel.GetById 0ms app.codeStats 0ms

/system/classes/habarilocale.php

https://github.com/HabariMag/habarimag-old
PHP | 437 lines | 243 code | 46 blank | 148 comment | 38 complexity | e8eb82f5bfc03df9a238360cdd597b37 MD5 | raw file
Possible License(s): Apache-2.0
  1. <?php
  2. /**
  3. * @package Habari
  4. *
  5. */
  6. /**
  7. * Habari Locale Class
  8. *
  9. * Provides translation services.
  10. *
  11. */
  12. class HabariLocale
  13. {
  14. private static $uselocale = false;
  15. private static $messages = array();
  16. private static $plural_function;
  17. private static $locale;
  18. /**
  19. * Sets the locale for Habari.
  20. *
  21. * @param string $locale A language code like 'en' or 'en-us' or 'x-klingon', will be lowercased
  22. */
  23. public static function set( $locale = null )
  24. {
  25. if ( $locale == null ) {
  26. return;
  27. }
  28. self::$locale = strtolower( $locale );
  29. self::$uselocale = self::load_domain( 'habari' );
  30. }
  31. /**
  32. * Set system locale.
  33. *
  34. * The problem is that every platform has its own way to designate a locale,
  35. * so for German you could have 'de', 'de_DE', 'de_DE.UTF-8', 'de_DE.UTF-8@euro'
  36. * (Linux) or 'DEU' (Windows), etc.
  37. *
  38. * @todo: This setting should probably be stored in the language files.
  39. *
  40. * @param string... $locale The locale(s) to set. They will be tried in order.
  41. * @return string the locale that was picked, or false if an error occurred
  42. */
  43. public static function set_system_locale()
  44. {
  45. if ( func_num_args() == 0 ) return;
  46. $args = func_get_args();
  47. array_unshift( $args, LC_ALL );
  48. return call_user_func_array( 'setlocale', $args );
  49. }
  50. /**
  51. * Load translations for a given domain and base directory for a pluggable object.
  52. * Translations are stored in gettext-style .mo files.
  53. * The internal workings of the file format are not entirely meant to be understood.
  54. *
  55. * @link http://www.gnu.org/software/gettext/manual/html_node/gettext_136.html GNU Gettext Manual: Description of the MO file format
  56. * @param string $domain the domain to load
  57. * @param string $base_dir the base directory in which to find the translation files
  58. * @return boolean true if data was successfully loaded, false otherwise
  59. */
  60. public static function load_pluggable_domain( $domain, $base_dir )
  61. {
  62. $file = $base_dir . '/locale/' . self::$locale . '/LC_MESSAGES/' . $domain . '.mo';
  63. return self::load_file( $domain, $file );
  64. }
  65. /**
  66. * Load translations for a given domain.
  67. * Translations are stored in gettext-style .mo files.
  68. * The internal workings of the file format are not entirely meant to be understood.
  69. *
  70. * @link http://www.gnu.org/software/gettext/manual/html_node/gettext_136.html GNU Gettext Manual: Description of the MO file format
  71. * @param string $domain the domain to load
  72. * @return boolean true if data was successfully loaded, false otherwise
  73. */
  74. private static function load_domain( $domain )
  75. {
  76. $file_end = self::$locale . '/LC_MESSAGES/' . $domain . '.mo';
  77. if ( file_exists( Site::get_dir( 'config' ) . '/locale/' . $file_end ) ) {
  78. $file = Site::get_dir( 'config' ) . '/locale/' . $file_end;
  79. }
  80. else if ( file_exists( HABARI_PATH . '/user/locale/' . $file_end ) ) {
  81. $file = HABARI_PATH . '/user/locale/' . $file_end;
  82. }
  83. else if ( file_exists( HABARI_PATH . '/3rdparty/locale/' . $file_end ) ) {
  84. $file = HABARI_PATH . '/3rdparty/locale/' . $file_end;
  85. }
  86. else {
  87. $file = HABARI_PATH . '/system/locale/' . $file_end;
  88. }
  89. return self::load_file( $domain, $file );
  90. }
  91. /**
  92. * function list_all
  93. * Retrieves an array of the Habari locales that are installed
  94. *
  95. * @return array. An array of Habari locales in the installation
  96. */
  97. public static function list_all()
  98. {
  99. $localedirs = array( HABARI_PATH . '/system/locale/', HABARI_PATH . '/3rdparty/locale/', HABARI_PATH . '/user/locale/' );
  100. if ( Site::CONFIG_LOCAL != Site::$config_type ) {
  101. // include site-specific locales
  102. $localedirs[] = Site::get_dir( 'config' ) . '/locale/';
  103. }
  104. $dirs = array();
  105. foreach ( $localedirs as $localedir ) {
  106. if ( file_exists( $localedir ) ) {
  107. $dirs = array_merge( $dirs, Utils::glob( $localedir . '*', GLOB_ONLYDIR | GLOB_MARK ) );
  108. }
  109. }
  110. $dirs = array_filter( $dirs, create_function( '$a', 'return file_exists($a . "LC_MESSAGES/habari.mo");' ) );
  111. $locales = array_map( 'basename', $dirs );
  112. ksort( $locales );
  113. return $locales;
  114. }
  115. /**
  116. * Load translations from a given file.
  117. *
  118. * @param string $domain the domain to load the data into
  119. * @param string $file the file name
  120. * @return boolean true if data was successfully loaded, false otherwise
  121. */
  122. private static function load_file( $domain, $file )
  123. {
  124. if ( ! file_exists( $file ) ) {
  125. Error::raise( sprintf( _t( 'No translations found for locale %s, domain %s!' ), self::$locale, $domain ) );
  126. return false;
  127. }
  128. if ( filesize( $file ) < 24 ) {
  129. Error::raise( sprintf( _t( 'Invalid .MO file for locale %s, domain %s!' ), self::$locale, $domain ) );
  130. return false;
  131. }
  132. $fp = fopen( $file, 'rb' );
  133. $data = fread( $fp, filesize( $file ) );
  134. fclose( $fp );
  135. // determine endianness
  136. $little_endian = true;
  137. list(,$magic) = unpack( 'V1', substr( $data, 0, 4 ) );
  138. switch ( $magic & 0xFFFFFFFF ) {
  139. case (int)0x950412de:
  140. $little_endian = true;
  141. break;
  142. case (int)0xde120495:
  143. $little_endian = false;
  144. break;
  145. default:
  146. Error::raise( sprintf( _t( 'Invalid magic number 0x%08x in %s!' ), $magic, $file ) );
  147. return false;
  148. }
  149. $revision = substr( $data, 4, 4 );
  150. if ( $revision != 0 ) {
  151. Error::raise( sprintf( _t( 'Unknown revision number %d in %s!' ), $revision, $file ) );
  152. return false;
  153. }
  154. $l = $little_endian ? 'V' : 'N';
  155. if ( $data && strlen( $data ) >= 20 ) {
  156. $header = substr( $data, 8, 12 );
  157. $header = unpack( "{$l}1msgcount/{$l}1msgblock/{$l}1transblock", $header );
  158. if ( $header['msgblock'] + ($header['msgcount'] - 1 ) * 8 > filesize( $file ) ) {
  159. Error::raise( sprintf( _t( 'Message count (%d) out of bounds in %s!' ), $header['msgcount'], $file ) );
  160. return false;
  161. }
  162. $lo = "{$l}1length/{$l}1offset";
  163. for ( $msgindex = 0; $msgindex < $header['msgcount']; $msgindex++ ) {
  164. $msginfo = unpack( $lo, substr( $data, $header['msgblock'] + $msgindex * 8, 8 ) );
  165. $msgids = explode( "\0", substr( $data, $msginfo['offset'], $msginfo['length'] ) );
  166. $transinfo = unpack( $lo, substr( $data, $header['transblock'] + $msgindex * 8, 8 ) );
  167. $transids = explode( "\0", substr( $data, $transinfo['offset'], $transinfo['length'] ) );
  168. self::$messages[$domain][$msgids[0]] = array(
  169. $msgids,
  170. $transids,
  171. );
  172. }
  173. }
  174. // setup plural functionality
  175. self::$plural_function = self::get_plural_function( self::$messages[$domain][''][1][0] );
  176. // only use locale if we actually read something
  177. return ( count( self::$messages ) > 0 );
  178. }
  179. private static function get_plural_function( $header )
  180. {
  181. if ( preg_match( '/plural-forms: (.*?)$/i', $header, $matches ) && preg_match( '/^\s*nplurals\s*=\s*(\d+)\s*;\s*plural=(.*)$/u', $matches[1], $matches ) ) {
  182. // sanitize
  183. $nplurals = preg_replace( '/[^0-9]/', '', $matches[1] );
  184. $plural = preg_replace( '#[^n0-9:\(\)\?\|\&=!<>+*/\%-]#', '', $matches[2] );
  185. $body = str_replace(
  186. array( 'plural', 'n', '$n$plurals', ),
  187. array( '$plural', '$n', '$nplurals', ),
  188. 'nplurals='. $nplurals . '; plural=' . $plural
  189. );
  190. // add parens
  191. // important since PHP's ternary evaluates from left to right
  192. $body .= ';';
  193. $res = '';
  194. $p = 0;
  195. for ( $i = 0; $i < strlen( $body ); $i++ ) {
  196. $ch = $body[$i];
  197. switch ( $ch ) {
  198. case '?':
  199. $res .= ' ? (';
  200. $p++;
  201. break;
  202. case ':':
  203. $res .= ') : (';
  204. break;
  205. case ';':
  206. $res .= str_repeat( ')', $p ) . ';';
  207. $p = 0;
  208. break;
  209. default:
  210. $res .= $ch;
  211. }
  212. }
  213. $body = $res . 'return ($plural>=$nplurals?$nplurals-1:$plural);';
  214. $fn = create_function(
  215. '$n',
  216. $body
  217. );
  218. }
  219. else {
  220. // default: one plural form for all cases but n==1 (english)
  221. $fn = create_function(
  222. '$n',
  223. '$nplurals=2;$plural=($n==1?0:1);return ($plural>=$nplurals?$nplurals-1:$plural);'
  224. );
  225. }
  226. return $fn;
  227. }
  228. /**
  229. * DO NOT USE THIS FUNCTION.
  230. * This function is only to be used by the test case for the Locale class!
  231. */
  232. public static function __run_plural_test( $header )
  233. {
  234. $fn = self::get_plural_function( $header );
  235. $res = '';
  236. for ( $n = 0; $n < 200; $n++ ) {
  237. $res .= $fn( $n );
  238. }
  239. return $res;
  240. }
  241. /**
  242. * DO NOT USE THIS FUNCTION.
  243. * This function is only to be used by the test case for the Locale class!
  244. */
  245. public static function __run_loadfile_test( $filename )
  246. {
  247. return self::load_file( 'test', $filename );
  248. }
  249. /**
  250. * Echo a version of the string translated into the current locale
  251. * @param string $text The text to echo translated
  252. * @param string $domain (optional) The domain to search for the message
  253. */
  254. public static function _e()
  255. {
  256. $args = func_get_args();
  257. echo call_user_func_array( array( 'HabariLocale', '_t' ), $args );
  258. }
  259. /**
  260. * Return a version of the string translated into the current locale
  261. *
  262. * @param string $text The text to echo translated
  263. * @param string $domain (optional) The domain to search for the message
  264. * @return string The translated string
  265. */
  266. public static function _t( $text, $args = array(), $domain = 'habari' )
  267. {
  268. if ( is_string( $args ) ) {
  269. $domain = $args;
  270. }
  271. if ( isset( self::$messages[$domain][$text] ) ) {
  272. $t = self::$messages[$domain][$text][1][0];
  273. }
  274. else {
  275. $t = $text;
  276. }
  277. if ( !empty( $args ) && is_array( $args ) ) {
  278. array_unshift( $args, $t );
  279. $t = call_user_func_array( 'sprintf', $args );
  280. }
  281. return $t;
  282. }
  283. /**
  284. * Given a string translated into the current locale, return the untranslated string.
  285. *
  286. * @param string $text The translated string
  287. * @param string $domain (optional) The domain to search for the message
  288. * @return string The untranslated string
  289. */
  290. public static function _u( $text, $domain = 'habari' )
  291. {
  292. $t = $text;
  293. foreach ( self::$messages[$domain] as $msg ) {
  294. if ( $text == $msg[1][0] ) {
  295. $t = $msg[0][0];
  296. break;
  297. }
  298. }
  299. return $t;
  300. }
  301. /**
  302. * Echo singular or plural version of the string, translated into the current locale, based on the count provided
  303. *
  304. * @param string $singular The singular form
  305. * @param string $plural The plural form
  306. * @param string $count The count
  307. * @param string $domain (optional) The domain to search for the message
  308. */
  309. public static function _ne( $singular, $plural, $count, $domain = 'habari' )
  310. {
  311. echo self::_n( $singular, $plural, $count, $domain );
  312. }
  313. /**
  314. * Return a singular or plural string translated into the current locale based on the count provided
  315. *
  316. * @param string $singular The singular form
  317. * @param string $plural The plural form
  318. * @param string $count The count
  319. * @param string $domain (optional) The domain to search for the message
  320. * @return string The appropriately translated string
  321. */
  322. public static function _n( $singular, $plural, $count, $domain = 'habari' )
  323. {
  324. if ( isset( self::$messages[$domain][$singular] ) ) {
  325. // XXX workaround, but direct calling doesn't work
  326. $fn = self::$plural_function;
  327. $n = $fn( $count );
  328. if ( isset( self::$messages[$domain][$singular][1][$n] ) ) {
  329. return self::$messages[$domain][$singular][1][$n];
  330. }
  331. }
  332. // fall-through else for both cases
  333. return ( $count == 1 ? $singular : $plural );
  334. }
  335. }
  336. /**
  337. * Echo a version of the string translated into the current locale, alias for HabariLocale::_e()
  338. *
  339. * @param string $text The text to translate
  340. */
  341. function _e( $text, $args = array(), $domain = 'habari' )
  342. {
  343. return HabariLocale::_e( $text, $args, $domain );
  344. }
  345. /**
  346. * function _ne
  347. * Echo singular or plural version of the string, translated into the current locale, based on the count provided,
  348. * alias for HabariLocale::_ne()
  349. * @param string $singular The singular form
  350. * @param string $plural The plural form
  351. * @param string $count The count
  352. */
  353. function _ne( $singular, $plural, $count, $domain = 'habari' )
  354. {
  355. return HabariLocale::_ne( $singular, $plural, $count, $domain );
  356. }
  357. /**
  358. * Return a version of the string translated into the current locale, alias for HabariLocale::_t()
  359. *
  360. * @param string $text The text to translate
  361. * @return string The translated string
  362. */
  363. function _t( $text, $args = array(), $domain = 'habari' )
  364. {
  365. return HabariLocale::_t( $text, $args, $domain );
  366. }
  367. /**
  368. * Return a singular or plural string translated into the current locale based on the count provided
  369. *
  370. * @param string $singular The singular form
  371. * @param string $plural The plural form
  372. * @param string $count The count
  373. * @return string The appropriately translated string
  374. */
  375. function _n( $singular, $plural, $count, $domain = 'habari' )
  376. {
  377. return HabariLocale::_n( $singular, $plural, $count, $domain );
  378. }
  379. /**
  380. * Given a string translated into the current locale, return the untranslated version of the string.
  381. * Alias for HabariLocale::_u()
  382. *
  383. * @param string $text The translated string
  384. * @param string $domain (optional) The domain to search for the message
  385. * @return string The untranslated string
  386. */
  387. function _u( $text, $domain = 'habari' )
  388. {
  389. return HabariLocale::_u( $text, $domain );
  390. }
  391. ?>