PageRenderTime 41ms CodeModel.GetById 13ms RepoModel.GetById 0ms app.codeStats 0ms

/htdocs/core/class/translatestandalone.class.php

https://bitbucket.org/speedealing/speedealing
PHP | 500 lines | 263 code | 62 blank | 175 comment | 101 complexity | 0a0a197e8cc2d1cfeb286b136f9f4d23 MD5 | raw file
Possible License(s): LGPL-3.0, LGPL-2.1, GPL-3.0, MIT
  1. <?php
  2. /* Copyright (C) 2001 Eric Seigne <erics@rycks.com>
  3. * Copyright (C) 2004-2012 Destailleur Laurent <eldy@users.sourceforge.net>
  4. * Copyright (C) 2005-2013 Regis Houssin <regis.houssin@capnetworks.com>
  5. *
  6. * This program is free software; you can redistribute it and/or modify
  7. * it under the terms of the GNU General Public License as published by
  8. * the Free Software Foundation; either version 3 of the License, or
  9. * (at your option) any later version.
  10. *
  11. * This program is distributed in the hope that it will be useful,
  12. * but WITHOUT ANY WARRANTY; without even the implied warranty of
  13. * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
  14. * GNU General Public License for more details.
  15. *
  16. * You should have received a copy of the GNU General Public License
  17. * along with this program. If not, see <http://www.gnu.org/licenses/>.
  18. */
  19. /**
  20. * \file htdocs/core/class/translatestandalone.class.php
  21. * \ingroup core
  22. * \brief File for TanslateStandalone class
  23. */
  24. /**
  25. * Class to manage standalone translations
  26. */
  27. class TranslateStandalone {
  28. var $dir; // Directories that contains /langs subdirectory
  29. var $defaultlang; // Current language for current user
  30. var $direction = 'ltr'; // Left to right or Right to left
  31. var $charset_inputfile = array(); // To store charset encoding used for language
  32. var $charset_output = 'UTF-8'; // Codage used by "trans" method outputs
  33. var $tab_translate = array(); // Array of all translations key=>value
  34. private $_tab_loaded = array(); // Array to store result after loading each language file
  35. /**
  36. * Constructor
  37. *
  38. * @param string $dir Force language directory.
  39. */
  40. function __construct($dir = null) {
  41. global $conf;
  42. if (!empty($conf->file->character_set_client))
  43. $this->charset_output = $conf->file->character_set_client; // If charset output is forced
  44. if (!empty($dir))
  45. $this->dir = array($dir);
  46. else
  47. $this->dir = $conf->file->dol_document_root;
  48. }
  49. /**
  50. * Set accessor for this->defaultlang
  51. *
  52. * @param string $srclang Language to use
  53. * @return void
  54. */
  55. function setDefaultLang($srclang = 'en_US') {
  56. global $conf;
  57. //dol_syslog(get_class($this)."::setDefaultLang srclang=".$srclang,LOG_DEBUG);
  58. // If a module ask to force a priority on langs directories (to use its own lang files)
  59. if (!empty($conf->global->MAIN_FORCELANGDIR)) {
  60. $more = array();
  61. $i = 0;
  62. foreach ($conf->file->dol_document_root as $dir) {
  63. $newdir = $dir . $conf->global->MAIN_FORCELANGDIR; // For example $conf->global->MAIN_FORCELANGDIR is '/mymodule' meaning we search files into '/mymodule/langs/xx_XX'
  64. if (!in_array($newdir, $this->dir)) {
  65. $more['module_' . $i] = $newdir;
  66. $i++; // We add the forced dir into the array $more. Just after, we add entries into $more to list of lang dir $this->dir.
  67. }
  68. }
  69. $this->dir = array_merge($more, $this->dir); // Forced dir ($more) are before standard dirs ($this->dir)
  70. }
  71. $this->origlang = $srclang;
  72. if (empty($srclang) || $srclang == 'auto') {
  73. $langpref = empty($_SERVER['HTTP_ACCEPT_LANGUAGE']) ? '' : $_SERVER['HTTP_ACCEPT_LANGUAGE'];
  74. $langpref = preg_replace("/;([^,]*)/i", "", $langpref);
  75. $langpref = str_replace("-", "_", $langpref);
  76. $langlist = preg_split("/[;,]/", $langpref);
  77. $codetouse = $langlist[0];
  78. }
  79. else
  80. $codetouse = $srclang;
  81. // We redefine $srclang
  82. $langpart = explode("_", $codetouse);
  83. //print "Short before _ : ".$langpart[0].'/ Short after _ : '.$langpart[1].'<br>';
  84. if (!empty($langpart[1])) { // If it's for a codetouse that is a long code xx_YY
  85. // Array force long code from first part, even if long code is defined
  86. $longforshort = array('ar' => 'ar_SA');
  87. if (isset($longforshort[strtolower($langpart[0])]))
  88. $srclang = $longforshort[strtolower($langpart[0])];
  89. else {
  90. $srclang = strtolower($langpart[0]) . "_" . strtoupper($langpart[1]);
  91. $longforlong = array('no_nb' => 'nb_NO');
  92. if (isset($longforlong[strtolower($srclang)]))
  93. $srclang = $longforlong[strtolower($srclang)];
  94. }
  95. }
  96. else { // If it's for a codetouse that is a short code xx
  97. // Array to convert short lang code into long code.
  98. $longforshort = array('ar' => 'ar_SA', 'el' => 'el_GR', 'ca' => 'ca_ES', 'en' => 'en_US', 'nb' => 'nb_NO', 'no' => 'nb_NO');
  99. if (isset($longforshort[strtolower($langpart[0])]))
  100. $srclang = $longforshort[strtolower($langpart[0])];
  101. else if (!empty($langpart[0]))
  102. $srclang = strtolower($langpart[0]) . "_" . strtoupper($langpart[0]);
  103. else
  104. $srclang = 'en_US';
  105. }
  106. $this->defaultlang = $srclang;
  107. //print 'this->defaultlang='.$this->defaultlang;
  108. }
  109. /**
  110. * Return active language code for current user
  111. * It's an accessor for this->defaultlang
  112. *
  113. * @param int $mode 0=Long language code, 1=Short language code
  114. * @return string Language code used (en_US, en_AU, fr_FR, ...)
  115. */
  116. function getDefaultLang($mode = 0) {
  117. if (empty($mode))
  118. return $this->defaultlang;
  119. else
  120. return substr($this->defaultlang, 0, 2);
  121. }
  122. /**
  123. * Load translation key-value for a particular file, into a memory array.
  124. * If data for file already loaded, do nothing.
  125. * All data in translation array are stored in UTF-8 format.
  126. * tab_loaded is completed with $domain key.
  127. * Value for hash are: 1:Loaded from disk, 2:Not found, 3:Loaded from cache
  128. *
  129. * @param string $domain File name to load (.lang file). Must be "file" or "file@module" for module language files:
  130. * If $domain is "file@module" instead of "file" then we look for module lang file
  131. * in htdocs/custom/modules/mymodule/langs/code_CODE/file.lang
  132. * then in htdocs/module/langs/code_CODE/file.lang instead of htdocs/langs/code_CODE/file.lang
  133. * @param string $alt 0 (try xx_ZZ then 1), 1 (try xx_XX then 2), 2 (try en_US or fr_FR or es_ES)
  134. * @param int $stopafterdirection Stop when the DIRECTION tag is found (optimize speed)
  135. * @param int $forcelangdir To force a different lang directory
  136. * @return int <0 if KO, 0 if already loaded, >0 if OK
  137. */
  138. function Load($domain, $alt = 0, $stopafterdirection = 0, $forcelangdir = '') {
  139. global $conf;
  140. // Check parameters
  141. if (empty($domain)) {
  142. dol_print_error('', get_class($this) . "::Load ErrorWrongParameters");
  143. exit;
  144. }
  145. if ($this->defaultlang == 'none_NONE')
  146. return; // Special language code to not translate keys
  147. //dol_syslog("Translate::Load Start domain=".$domain." alt=".$alt." forcelangdir=".$forcelangdir." this->defaultlang=".$this->defaultlang);
  148. $newdomain = $domain;
  149. $modulename = '';
  150. // Search if a module directory name is provided into lang file name
  151. if (preg_match('/^([^@]+)@([^@]+)$/i', $domain, $regs)) {
  152. $newdomain = $regs[1];
  153. $modulename = $regs[2];
  154. }
  155. // Check cache
  156. if (!empty($this->_tab_loaded[$newdomain])) { // File already loaded for this domain
  157. //dol_syslog("Translate::Load already loaded for newdomain=".$newdomain);
  158. return 0;
  159. }
  160. $fileread = 0;
  161. $langofdir = (empty($forcelangdir) ? $this->defaultlang : $forcelangdir);
  162. // Redefine alt
  163. $langarray = explode('_', $langofdir);
  164. if ($alt < 1 && strtolower($langarray[0]) == strtolower($langarray[1]))
  165. $alt = 1;
  166. if ($alt < 2 && (strtolower($langofdir) == 'en_us' || strtolower($langofdir) == 'fr_fr' || strtolower($langofdir) == 'es_es'))
  167. $alt = 2;
  168. foreach ($this->dir as $keydir => $searchdir) {
  169. // Directory of translation files
  170. $file_lang = $searchdir . ($modulename ? '/' . $modulename : '') . "/langs/" . $langofdir . "/" . $newdomain . ".lang.php";
  171. $file_lang_osencoded = dol_osencode($file_lang);
  172. $filelangexists = is_file($file_lang_osencoded);
  173. //dol_syslog('Translate::Load Try to read for alt='.$alt.' langofdir='.$langofdir.' file_lang='.$file_lang." => filelangexists=".$filelangexists);
  174. if ($filelangexists) {
  175. // TODO Move cache read out of loop on dirs
  176. $found = false;
  177. // Enable caching of lang file in memory (not by default)
  178. $usecachekey = '';
  179. // Using a memcached server
  180. $usecachekey = $newdomain . '_' . $langofdir . '_' . md5($file_lang); // Should not contains special chars
  181. // Using cache with shmop. Speed gain: 40ms - Memory overusage: 200ko (Size of session cache file)
  182. //dol_syslog('Translate::Load we will cache result into usecachekey '.$usecachekey);
  183. if (!empty($conf->Couchdb->name)) {
  184. require_once DOL_DOCUMENT_ROOT . '/core/lib/memory.lib.php';
  185. $tmparray = dol_getcache($usecachekey);
  186. if (is_array($tmparray) && count($tmparray)) {
  187. $this->tab_translate = array_merge($tmparray, $this->tab_translate); // Already found values tab_translate overwrites duplicates
  188. //print $newdomain."\n";
  189. //var_dump($this->tab_translate);
  190. if ($alt == 2)
  191. $fileread = 1;
  192. $found = true; // Found in dolibarr PHP cache
  193. }
  194. }
  195. if (!$found) {
  196. include $file_lang;
  197. if (!empty($usecachekey))
  198. $tabtranslatedomain = array(); // To save lang content in cache
  199. foreach ($$newdomain as $key => $value) {
  200. if ((!empty($conf->global->MAIN_USE_CUSTOM_TRANSLATION) || empty($this->tab_translate[$key])) && !empty($value)) { // If data was already found, we must not enter here, even if MAIN_FORCELANGDIR is set (MAIN_FORCELANGDIR is to replace lang dir, not to overwrite)
  201. $value = trim(preg_replace('/\\n/', "\n", $value));
  202. if ($key == 'DIRECTION') { // This is to declare direction of language
  203. if ($alt < 2 || empty($this->tab_translate[$key])) { // We load direction only for primary files or if not yet loaded
  204. $this->tab_translate[$key] = $value;
  205. if ($stopafterdirection)
  206. break; // We do not save tab if we stop after DIRECTION
  207. else if (!empty($usecachekey))
  208. $tabtranslatedomain[$key] = $value;
  209. }
  210. }
  211. else {
  212. $this->tab_translate[$key] = $value;
  213. if (!empty($usecachekey))
  214. $tabtranslatedomain[$key] = $value; // To save lang content in cache
  215. }
  216. }
  217. }
  218. $fileread = 1;
  219. // TODO Move cache write out of loop on dirs
  220. // To save lang content for usecachekey into cache
  221. if (!empty($conf->Couchdb->name) && !empty($usecachekey) && count($tabtranslatedomain)) {
  222. $ressetcache = dol_setcache($usecachekey, $tabtranslatedomain);
  223. if ($ressetcache < 0) {
  224. $error = 'Failed to set cache for usecachekey=' . $usecachekey . ' result=' . $ressetcache;
  225. dol_syslog($error, LOG_ERR);
  226. }
  227. }
  228. if (empty($conf->global->MAIN_FORCELANGDIR) && empty($conf->global->MAIN_USE_CUSTOM_TRANSLATION))
  229. break; // Break loop on each root dir. If a module has forced dir, we do not stop loop.
  230. }
  231. }
  232. }
  233. // Now we complete with next file
  234. if ($alt == 0) {
  235. // This function MUST NOT contains call to syslog
  236. //dol_syslog("Translate::Load loading alternate translation file (to complete ".$this->defaultlang."/".$newdomain.".lang file)", LOG_DEBUG);
  237. $langofdir = strtolower($langarray[0]) . '_' . strtoupper($langarray[0]);
  238. $this->load($domain, $alt + 1, $stopafterdirection, $langofdir);
  239. }
  240. // Now we complete with reference en_US/fr_FR/es_ES file
  241. if ($alt == 1) {
  242. // This function MUST NOT contains call to syslog
  243. //dol_syslog("Translate::Load loading alternate translation file (to complete ".$this->defaultlang."/".$newdomain.".lang file)", LOG_DEBUG);
  244. $langofdir = 'en_US';
  245. if (preg_match('/^fr/i', $langarray[0]))
  246. $langofdir = 'fr_FR';
  247. if (preg_match('/^es/i', $langarray[0]))
  248. $langofdir = 'es_ES';
  249. $this->load($domain, $alt + 1, $stopafterdirection, $langofdir);
  250. }
  251. if ($alt == 2) {
  252. if ($fileread)
  253. $this->_tab_loaded[$newdomain] = 1; // Set domain file as loaded
  254. if (empty($this->_tab_loaded[$newdomain]))
  255. $this->_tab_loaded[$newdomain] = 2; // Marque ce fichier comme non trouve
  256. }
  257. // Check to be sure that SeparatorDecimal differs from SeparatorThousand
  258. if (!empty($this->tab_translate["SeparatorDecimal"]) && !empty($this->tab_translate["SeparatorThousand"])
  259. && $this->tab_translate["SeparatorDecimal"] == $this->tab_translate["SeparatorThousand"])
  260. $this->tab_translate["SeparatorThousand"] = '';
  261. return 1;
  262. }
  263. /**
  264. * Return text translated of text received as parameter (and encode it into HTML)
  265. * Si il n'y a pas de correspondance pour ce texte, on cherche dans fichier alternatif
  266. * et si toujours pas trouve, il est retourne tel quel
  267. * Les parametres de cette methode peuvent contenir de balises HTML.
  268. *
  269. * @param string $key Key to translate
  270. * @param string $param1 chaine de param1
  271. * @param string $param2 chaine de param2
  272. * @param string $param3 chaine de param3
  273. * @param string $param4 chaine de param4
  274. * @param int $maxsize Max length of text
  275. * @return string Translated string (encoded into HTML entities and UTF8)
  276. */
  277. function trans($key, $param1 = '', $param2 = '', $param3 = '', $param4 = '', $maxsize = 0) {
  278. global $conf;
  279. if (!empty($this->tab_translate[$key])) { // Translation is available
  280. $str = $this->tab_translate[$key];
  281. if (!preg_match('/^Format/', $key))
  282. $str = sprintf($str, $param1, $param2, $param3, $param4); // Replace %s and %d except for FormatXXX strings.
  283. if ($maxsize)
  284. $str = dol_trunc($str, $maxsize);
  285. // We replace some HTML tags by __xx__ to avoid having them encoded by htmlentities
  286. $str = str_replace(array('<', '>', '"',), array('__lt__', '__gt__', '__quot__'), $str);
  287. // Crypt string into HTML
  288. //$str = htmlentities($str, ENT_QUOTES, $this->charset_output);
  289. // Restore HTML tags
  290. $str = str_replace(array('__lt__', '__gt__', '__quot__'), array('<', '>', '"',), $str);
  291. // Overwrite translation
  292. if (!empty($conf->global->MAIN_OVERWRITE_TRANS)) { // Overwrite translation with string1:newstring1,string2:newstring2
  293. $tmparray = explode(',', $conf->global->MAIN_OVERWRITE_TRANS);
  294. foreach ($tmparray as $tmp) {
  295. $tmparray2 = explode(':', $tmp);
  296. if ($tmparray2[0] == $str) {
  297. $str = $tmparray2[1];
  298. break;
  299. }
  300. }
  301. }
  302. return $str;
  303. } else { // Translation is not available
  304. return $key;
  305. }
  306. }
  307. /**
  308. * Return translated value of a text string
  309. * Si il n'y a pas de correspondance pour ce texte, on cherche dans fichier alternatif
  310. * et si toujours pas trouve, il est retourne tel quel.
  311. * Parameters of this method must not contains any HTML tags.
  312. *
  313. * @param string $key Key to translate
  314. * @param string $param1 chaine de param1
  315. * @param string $param2 chaine de param2
  316. * @param string $param3 chaine de param3
  317. * @param string $param4 chaine de param4
  318. * @return string Translated string (encoded into UTF8)
  319. */
  320. function transnoentities($key, $param1 = '', $param2 = '', $param3 = '', $param4 = '') {
  321. return $this->convToOutputCharset($this->transnoentitiesnoconv($key, $param1, $param2, $param3, $param4));
  322. }
  323. /**
  324. * Return translated value of a text string
  325. * Si il n'y a pas de correspondance pour ce texte, on cherche dans fichier alternatif
  326. * et si toujours pas trouve, il est retourne tel quel.
  327. * No convert to encoding charset of lang object is done.
  328. * Parameters of this method must not contains any HTML tags.
  329. *
  330. * @param string $key Key to translate
  331. * @param string $param1 chaine de param1
  332. * @param string $param2 chaine de param2
  333. * @param string $param3 chaine de param3
  334. * @param string $param4 chaine de param4
  335. * @return string Translated string
  336. */
  337. function transnoentitiesnoconv($key, $param1 = '', $param2 = '', $param3 = '', $param4 = '') {
  338. if (!empty($this->tab_translate[$key])) { // Translation is available
  339. $str = $this->tab_translate[$key];
  340. if (!preg_match('/^Format/', $key))
  341. $str = sprintf($str, $param1, $param2, $param3, $param4); // Replace %s and %d except for FormatXXX strings.
  342. }
  343. else {
  344. $str = $key;
  345. }
  346. return $str;
  347. }
  348. /**
  349. * Return translation of a key depending on country
  350. *
  351. * @param string $str string root to translate
  352. * @param string $countrycode country code (FR, ...)
  353. * @return string translated string
  354. */
  355. function transcountry($str, $countrycode) {
  356. if ($this->tab_translate["$str$countrycode"])
  357. return $this->trans("$str$countrycode");
  358. else
  359. return $this->trans($str);
  360. }
  361. /**
  362. * Retourne la version traduite du texte passe en parametre complete du code pays
  363. *
  364. * @param string $str string root to translate
  365. * @param string $countrycode country code (FR, ...)
  366. * @return string translated string
  367. */
  368. function transcountrynoentities($str, $countrycode) {
  369. if ($this->tab_translate["$str$countrycode"])
  370. return $this->transnoentities("$str$countrycode");
  371. else
  372. return $this->transnoentities($str);
  373. }
  374. /**
  375. * Convert a string into output charset (this->charset_output that should be defined to conf->file->character_set_client)
  376. *
  377. * @param string $str String to convert
  378. * @param string $pagecodefrom Page code of src string
  379. * @return string Converted string
  380. */
  381. function convToOutputCharset($str, $pagecodefrom = 'UTF-8') {
  382. if ($pagecodefrom == 'ISO-8859-1' && $this->charset_output == 'UTF-8')
  383. $str = utf8_encode($str);
  384. if ($pagecodefrom == 'UTF-8' && $this->charset_output == 'ISO-8859-1')
  385. $str = utf8_decode(str_replace('€', chr(128), $str));
  386. return $str;
  387. }
  388. /**
  389. * Return list of all available languages
  390. *
  391. * @param string $langdir Directory to scan
  392. * @param string $maxlength Max length for each value in combo box (will be truncated)
  393. * @param int $usecode Show code instead of country name for language variant
  394. * @return array List of languages
  395. */
  396. function get_available_languages($langdir = DOL_DOCUMENT_ROOT, $maxlength = 0, $usecode = 0) {
  397. global $conf;
  398. // We scan directory langs to detect available languages
  399. $handle = opendir($langdir . "/langs");
  400. $langs_available = array();
  401. while ($dir = trim(readdir($handle))) {
  402. if (preg_match('/^[a-z]+_[A-Z]+/i', $dir)) {
  403. $this->load("languages");
  404. if ($usecode || !empty($conf->global->MAIN_SHOW_LANGUAGE_CODE)) {
  405. $langs_available[$dir] = $dir . ': ' . dol_trunc($this->trans('Language_' . $dir), $maxlength);
  406. } else {
  407. $langs_available[$dir] = $this->trans('Language_' . $dir);
  408. }
  409. }
  410. }
  411. return $langs_available;
  412. }
  413. /**
  414. * Return if a filename $filename exists for current language (or alternate language)
  415. *
  416. * @param string $filename Language filename to search
  417. * @param string $searchalt Search also alernate language file
  418. * @return boolean true if exists and readable
  419. */
  420. function file_exists($filename, $searchalt = 0) {
  421. // Test si fichier dans repertoire de la langue
  422. foreach ($this->dir as $searchdir) {
  423. if (is_readable(dol_osencode($searchdir . "/langs/" . $this->defaultlang . "/" . $filename)))
  424. return true;
  425. if ($searchalt) {
  426. // Test si fichier dans repertoire de la langue alternative
  427. if ($this->defaultlang != "en_US")
  428. $filenamealt = $searchdir . "/langs/en_US/" . $filename;
  429. else
  430. $filenamealt = $searchdir . "/langs/fr_FR/" . $filename;
  431. if (is_readable(dol_osencode($filenamealt)))
  432. return true;
  433. }
  434. }
  435. return false;
  436. }
  437. }
  438. ?>