PageRenderTime 54ms CodeModel.GetById 24ms RepoModel.GetById 1ms app.codeStats 0ms

/classes/Translate.php

https://github.com/fracmak/mythweb
PHP | 349 lines | 206 code | 30 blank | 113 comment | 42 complexity | c090073fce3a378c2dd4f1d7db14847a MD5 | raw file
  1. <?php
  2. /**
  3. *
  4. *
  5. * @url $URL$
  6. * @date $Date$
  7. * @version $Revision$
  8. * @author $Author$
  9. * @license GPL
  10. *
  11. * @package MythWeb
  12. * @subpackage Classes
  13. *
  14. /**/
  15. class Translate extends MythBase {
  16. // Cache for 24 hours
  17. public $cacheLifetime = 86400;
  18. // Define the languages that mythweb can translate into. Each hash entry should
  19. // point to an array containing first the user-visible name of the language, the
  20. // php locale for printing and finally the matching language and charset codes.
  21. public static $Languages = array(
  22. 'Catalan' => array('Catal&agrave;', 'ca_ES.UTF-8', 'ca_ES'),
  23. 'Dutch' => array('Nederlands', 'nl_NL', array('nl_NL', 'nl_BE')),
  24. 'English' => array('English', 'en_US.UTF-8', 'en_US.ISO-8859-1'),
  25. 'English_CA' => array('English_CA', 'en_CA.UTF-8', 'en_CA.ISO-8859-1'),
  26. 'English_GB' => array('English_GB', 'en_GB.UTF-8', 'en_GB.ISO-8859-1'),
  27. 'French' => array('Fran&ccedil;ais', 'fr_FR.UTF-8', 'fr_FR'),
  28. 'French_CA' => array('Fran&ccedil;ais_CA', 'fr_CA.UTF-8', 'fr_CA'),
  29. 'Japanese' => array('Japanese', 'ja_JP.UTF-8', 'ja_JP'),
  30. 'Spanish' => array('Espa&ntilde;ol', 'es_ES.UTF-8', 'es'),
  31. 'Spanish' => array('Espa&ntilde;ol_ES', 'es_ES.UTF-8', 'es_ES'),
  32. 'Swedish' => array('Svenska', 'sv_SE.UTF-8', 'sv_SE'),
  33. 'Danish' => array('Dansk', 'da_DK.UTF-8', 'da_DK'),
  34. 'German' => array('Deutsch', 'de_DE', 'de_DE'),
  35. 'Slovenian' => array('Slovensko', 'si_SI', 'sl_SI'),
  36. 'Finnish' => array('Suomi', 'fi_FI.UTF-8', 'fi_FI'),
  37. 'Czech' => array('Czech', 'cs_CZ.UTF-8', 'cs_CZ.UTF-8'),
  38. 'Polish' => array('Polski', 'pl_PL.UTF-8', 'pl_PL'),
  39. 'Hungarian' => array('Magyar', 'hu_HU.UTF-8', 'hu_HU'),
  40. 'Norwegian_NB' => array('Norsk Bokm&aring;l', 'nb_NO.UTF-8', array('nb_NO', 'nn_NO', 'se_NO', 'no_NO')),
  41. );
  42. private $currentLanguage = '';
  43. private $translations = array();
  44. public function __construct($language = null) {
  45. if (!is_null($language))
  46. $_SESSION['language'] = $language;
  47. // Need to detect the language?
  48. if (empty($_SESSION['language']))
  49. $_SESSION['language'] = self::get_browser_lang();
  50. if (empty($_SESSION['language']))
  51. $_SESSION['language'] = 'English';
  52. // Set the locale
  53. setlocale(LC_ALL, self::$Languages[$_SESSION['language']][1]);
  54. $this->load_translation();
  55. }
  56. public function __wakeup() {
  57. $this->__construct();
  58. // Note, we don't need to call the parent wakeup here, as we're already a cache hit and we don't
  59. // need to recache ourself. It's pointless, so just resetup the browser info and call it good.
  60. }
  61. /**
  62. * Returns $str, translated appropriately
  63. /**/
  64. public function string($str /* [, arg1, arg2, argN] */ ) {
  65. // No string?
  66. if (!$str)
  67. return '';
  68. // No translation for this string?
  69. if (!isset($this->translations[$str]) && !(int)($str) && $str != '0')
  70. return "!!NoTrans: $str!!";
  71. // Parse out anything passed in as an array (usually from tn())
  72. $args = array();
  73. if (func_num_args() > 1) {
  74. $a = func_get_args();
  75. array_shift($a); // shift off $str, we don't need it here
  76. foreach ($a as $arg) {
  77. if (is_array($arg)) {
  78. foreach ($arg as $arg2)
  79. $args[] = $arg2;
  80. }
  81. else
  82. $args[] = $arg;
  83. }
  84. }
  85. // Pull in the translated string (or default to $str if the translated string is '')
  86. if ($this->translations[$str])
  87. $str = $this->translations[$str];
  88. // Nothing extra to print
  89. if (count($args) < 1)
  90. return $str;
  91. // Otherwise, parse in replacement strings as needed
  92. foreach ($args as $i => $arg) {
  93. $str = preg_replace('/\\$'.($i+1).'\b/',
  94. str_replace('$', '~~$~~', $arg), // Replace $ with ~~$~~ so sub-args don't get reinterpreted
  95. $str
  96. );
  97. }
  98. $str = str_replace('~~$~~', '$', $str); // re-convert any ~~$~~ sequences
  99. return $str;
  100. }
  101. /**
  102. * return different translated strings based on the numerical value of int.
  103. * an optional array of string arguments can be included and will be passed
  104. * to t() for interpretation. If no array is passed in, a default will be
  105. * created using the value of t(int).
  106. /**/
  107. public function number(/* string1, string2, stringN, int [, array-of-args] */) {
  108. // Flatten arrays
  109. $ab = func_get_args();
  110. $a[] = $ab[0];
  111. foreach ($ab[1] as $b)
  112. $a[] = $b;
  113. // Array of arguments?
  114. if (is_array($a[count($a)-1]))
  115. $args = array_pop($a);
  116. // Pull off the int
  117. $int = intVal(array_pop($a));
  118. // Default parameters to $int
  119. if (!isset($args))
  120. $args = array(t($int));
  121. // Return the appropriate translated string
  122. if ($a[$int-1])
  123. return $this->string($a[$int-1], $args);
  124. return $this->string($a[count($a)-1], $args);
  125. }
  126. /**
  127. * Load a translation file into the global translation array
  128. *
  129. * @param string $path The path to the translation file
  130. /**/
  131. public function load_translation($language = null) {
  132. if (is_null($language))
  133. $language = $_SESSION['language'];
  134. // Already loaded?
  135. if ($language == $this->currentLanguage)
  136. return;
  137. $this->translations = array();
  138. // Wipe any existing cache
  139. Cache::clear();
  140. // Load the primary language file, or English if the other doesn't exist.
  141. if (file_exists(modules_path.'/_shared/lang/'.$language.'.lang'))
  142. $path = modules_path.'/_shared/lang/'.$language.'.lang';
  143. else
  144. $path = modules_path.'/_shared/lang/English.lang';
  145. // Load a module override translation if one exists.
  146. if (file_exists(modules_path.'/'.module.'/lang/'.$language.'.lang'))
  147. $path = modules_path.'/'.module.'/lang/'.$language.'.lang';
  148. elseif (file_exists(modules_path.'/'.module.'/lang/English.lang'))
  149. $path = modules_path.'/'.module.'/lang/English.lang';
  150. $file = @file_get_contents($path);
  151. // Error?
  152. if ($file === false) {
  153. trigger_error("Failed to open translation file: $path", ERROR);
  154. return false;
  155. }
  156. // Parse the file
  157. foreach (preg_split('/\n(?=\S)/', $file) as $group) {
  158. preg_match('/^([^\n]+?)\s*(?:$|\s*\n(\s+)(.+)$)/s', $group, $match);
  159. $indent = '';
  160. $trans = '';
  161. if (count($match) == 2)
  162. list($match, $key) = $match;
  163. else
  164. list($match, $key, $indent, $trans) = $match;
  165. // Cleanup
  166. $trans = trim(str_replace("\n$indent", "\n", $trans));
  167. $key = trim($key);
  168. if (preg_match('/^["\']/', $key))
  169. $key = preg_replace('/^(["\'])(.+)\\1$/', '$2', $key);
  170. // Store
  171. if ($trans || empty($this->translations[$key]))
  172. $this->translations[$key] = $trans;
  173. }
  174. // No language array defined?
  175. if (!is_array($this->translations) || !count($this->translations))
  176. trigger_error('No language strings defined.', FATAL);
  177. // Generate the date formats
  178. $session = array();
  179. $session['date_statusbar'] = $this->string('generic_date').', '.$this->string('generic_time');
  180. $session['date_scheduled'] = $this->string('generic_date').' ('.$this->string('generic_time').')';
  181. $session['date_scheduled_popup'] = $this->string('generic_date');
  182. $session['date_recorded'] = $this->string('generic_date').' ('.$this->string('generic_time').')';
  183. $session['date_search'] = $this->string('generic_date').', '.$this->string('generic_time');
  184. $session['date_listing_key'] = $this->string('generic_date').', '.$this->string('generic_time');
  185. $session['date_listing_jump'] = $this->string('generic_date');
  186. $session['date_channel_jump'] = $this->string('generic_date');
  187. $session['date_job_status'] = $this->string('generic_date').', '.$this->string('generic_time');
  188. $session['time_format'] = $this->string('generic_time');
  189. foreach ($session as $key => $value)
  190. if (!isset($_SESSION[$key]) || $_SESSION[$key] == '')
  191. $_SESSION[$key] = $value;
  192. unset($session);
  193. $this->currentLanguage = $language;
  194. }
  195. /**
  196. * The get_browser_lang function is modified from Wouter Verhelst's source.
  197. * Relevant documentation from his code is included below.
  198. *
  199. * accept-to-gettext.inc -- convert information in 'Accept-*' headers to
  200. * gettext language identifiers.
  201. * Copyright (c) 2003, Wouter Verhelst <wouter@debian.org>
  202. *
  203. * This program is free software; you can redistribute it and/or modify
  204. * it under the terms of the GNU General Public License as published by
  205. * the Free Software Foundation; either version 2 of the License, or
  206. * (at your option) any later version.
  207. *
  208. * This program is distributed in the hope that it will be useful,
  209. * but WITHOUT ANY WARRANTY; without even the implied warranty of
  210. * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
  211. * GNU General Public License for more details.
  212. *
  213. * You should have received a copy of the GNU General Public License
  214. * along with this program; if not, write to the Free Software
  215. * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
  216. *
  217. * Assumptions made:
  218. * * Charset encodings are written the same way as the Accept-Charset
  219. * HTTP header specifies them (RFC2616), except that they're parsed
  220. * case-insensitive.
  221. * * Country codes and language codes are the same in both gettext and
  222. * the Accept-Language syntax (except for the case differences, which
  223. * are dealt with easily). If not, some input may be ignored.
  224. * * The language is more important than the charset; i.e., if the
  225. * following is given:
  226. *
  227. * Accept-Language: nl-be, nl;q=0.8, en-us;q=0.5, en;q=0.3
  228. * Accept-Charset: ISO-8859-15, utf-8;q=0.5
  229. *
  230. * And the supplied parameter contains (amongst others) nl_BE.UTF-8
  231. * and nl.ISO-8859-15, then nl_BE.UTF-8 will be picked.
  232. *
  233. /**/
  234. private static function get_browser_lang() {
  235. // default to "everything is acceptable", as RFC2616 specifies
  236. $alparts = explode(',', $_SERVER["HTTP_ACCEPT_LANGUAGE"] ? $_SERVER["HTTP_ACCEPT_LANGUAGE"] : '*');
  237. $acparts = explode(',', $_SERVER["HTTP_ACCEPT_CHARSET"] ? $_SERVER["HTTP_ACCEPT_CHARSET"] : '*');
  238. // Parse the contents of the Accept-Language header.
  239. $alscores = array();
  240. foreach($alparts as $part) {
  241. $part = trim($part);
  242. if (strstr($part, ';')) {
  243. $lang = explode(';', $part);
  244. $score = explode('=', $lang[1]);
  245. $alscores[$lang[0]] = $score[1];
  246. }
  247. else
  248. $alscores[$part] = 1;
  249. }
  250. // Do the same for the Accept-Charset header. */
  251. /* RFC2616: ``If no '*' is present in an Accept-Charset field, then
  252. * all character sets not explicitly mentioned get a quality value of
  253. * 0, except for ISO-8859-1, which gets a quality value of 1 if not
  254. * explicitly mentioned.''
  255. *
  256. * Making it 2 for the time being, so that we can distinguish between
  257. * "not specified" and "specified as 1" later on.
  258. */
  259. $acscores = array('ISO-8859-1' => 2);
  260. foreach($acparts as $part) {
  261. $part = trim($part);
  262. if (strstr($part, ';')) {
  263. $cs = explode(';', $part);
  264. $score = explode('=', $cs[1]);
  265. $acscores[strtoupper($cs[0])]=$score[1];
  266. }
  267. else
  268. $acscores[strtoupper($part)]=1;
  269. }
  270. if ($acscores['ISO-8859-1'] == 2)
  271. $acscores['ISO-8859-1'] = $acscores['*'] ? $acscores['*'] : 1;
  272. /*
  273. * Loop through the available languages/encodings, and pick the one
  274. * with the highest score, excluding the ones with a charset the user
  275. * did not include.
  276. */
  277. $curlscore = 0;
  278. $curcscore = 0;
  279. $curlang = null;
  280. foreach(self::$Languages as $lang => $details) {
  281. $encodings = is_array($details[2]) ? $details[2] : array($details[2]);
  282. if (empty($encodings))
  283. continue;
  284. foreach ($encodings as $encoding) {
  285. if (empty($encoding))
  286. continue;
  287. $tmp = @explode('.', str_replace('_', '-', $encoding));
  288. $allang = strtolower($tmp[0]);
  289. $cs = strtoupper($tmp[1]);
  290. $noct = explode('-', $allang);
  291. $testvals = array(
  292. array($alscores[$allang], $acscores[$cs]),
  293. array($alscores[$noct[0]], $acscores[$cs]),
  294. array($alscores[$allang], $acscores['*']),
  295. array($alscores[$noct[0]], $acscores['*']),
  296. array($alscores['*'], $acscores[$cs]),
  297. array($alscores['*'], $acscores['*'])
  298. );
  299. // Scan through the possible test-match values until we find a valid set
  300. foreach($testvals as $tval) {
  301. if(!isset($tval[0]) || !isset($tval[1]))
  302. continue;
  303. if($curlscore < $tval[0]) {
  304. $curlscore = $tval[0];
  305. $curcscore = $tval[1];
  306. $curlang = $lang;
  307. }
  308. elseif ($curlscore == $tval[0] && $curcscore < $tval[1]) {
  309. $curcscore = $tval[1];
  310. $curlang = $lang;
  311. }
  312. break;
  313. }
  314. }
  315. }
  316. // Return the language we found
  317. return $curlang;
  318. }
  319. }