translate.class.php 37 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665666667668669670671672673674675676677678679680681682683684685686687688689690691692693694695696697698699700701702703704705706707708709710711712713714715716717718719720721722723724725726727728729730731732733734735736737738739740741742743744745746747748749750751752753754755756757758759760761762763764765766767768769770771772773774775776777778779780781782783784785786787788789790791792793794795796797798799800801802803804805806807808809810811812813814815816817818819820821822823824825826827828829830831832833834835836837838839840841842843844845846847848849850851852853854855856857858859860861862863864865866867868869870871872873874875876877878879880881882883884885886887888889890891892893894895896897898899900901902903904905906907908909910911912913914915916917918919920921922923924925926927928929930931932933934935936937938939940941942943944945946947948949950951952953954955956957958959960961962963964965966967968969970971972973974975976977978979980981982983984985986987988989990991992993994995996997998999100010011002100310041005
  1. <?php
  2. /* Copyright (C) 2001 Eric Seigne <erics@rycks.com>
  3. * Copyright (C) 2004-2015 Destailleur Laurent <eldy@users.sourceforge.net>
  4. * Copyright (C) 2005-2010 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. * 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/translate.class.php
  21. * \ingroup core
  22. * \brief File for Tanslate class
  23. */
  24. /**
  25. * Class to manage translations
  26. */
  27. class Translate
  28. {
  29. var $dir; // Directories that contains /langs subdirectory
  30. var $defaultlang; // Current language for current user
  31. var $direction = 'ltr'; // Left to right or Right to left
  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. var $cache_labels=array(); // Cache for labels return by getLabelFromKey method
  36. var $cache_currencies=array(); // Cache to store currency symbols
  37. /**
  38. * Constructor
  39. *
  40. * @param string $dir Force directory that contains /langs subdirectory (value is sometine '..' like into install/* pages or support/* pages).
  41. * @param Conf $conf Object with Dolibarr configuration
  42. */
  43. function __construct($dir,$conf)
  44. {
  45. if (! empty($conf->file->character_set_client)) $this->charset_output=$conf->file->character_set_client; // If charset output is forced
  46. if ($dir) $this->dir=array($dir);
  47. else $this->dir=$conf->file->dol_document_root;
  48. }
  49. /**
  50. * Set accessor for this->defaultlang
  51. *
  52. * @param string $srclang Language to use. If '' or 'auto', we use browser lang.
  53. * @return void
  54. */
  55. function setDefaultLang($srclang='en_US')
  56. {
  57. global $conf;
  58. //dol_syslog(get_class($this)."::setDefaultLang srclang=".$srclang,LOG_DEBUG);
  59. // If a module ask to force a priority on langs directories (to use its own lang files)
  60. if (! empty($conf->global->MAIN_FORCELANGDIR))
  61. {
  62. $more=array();
  63. $i=0;
  64. foreach($conf->file->dol_document_root as $dir)
  65. {
  66. $newdir=$dir.$conf->global->MAIN_FORCELANGDIR; // For example $conf->global->MAIN_FORCELANGDIR is '/mymodule' meaning we search files into '/mymodule/langs/xx_XX'
  67. if (! in_array($newdir,$this->dir))
  68. {
  69. $more['module_'.$i]=$newdir; $i++; // We add the forced dir into the array $more. Just after, we add entries into $more to list of lang dir $this->dir.
  70. }
  71. }
  72. $this->dir=array_merge($more,$this->dir); // Forced dir ($more) are before standard dirs ($this->dir)
  73. }
  74. $this->origlang=$srclang;
  75. if (empty($srclang) || $srclang == 'auto')
  76. {
  77. $langpref=empty($_SERVER['HTTP_ACCEPT_LANGUAGE'])?'':$_SERVER['HTTP_ACCEPT_LANGUAGE'];
  78. $langpref=preg_replace("/;([^,]*)/i","",$langpref);
  79. $langpref=str_replace("-","_",$langpref);
  80. $langlist=preg_split("/[;,]/",$langpref);
  81. $codetouse=$langlist[0];
  82. }
  83. else $codetouse=$srclang;
  84. // We redefine $srclang
  85. $langpart=explode("_",$codetouse);
  86. //print "Short before _ : ".$langpart[0].'/ Short after _ : '.$langpart[1].'<br>';
  87. if (! empty($langpart[1])) // If it's for a codetouse that is a long code xx_YY
  88. {
  89. // Array force long code from first part, even if long code is defined
  90. $longforshort=array('ar'=>'ar_SA');
  91. if (isset($longforshort[strtolower($langpart[0])])) $srclang=$longforshort[strtolower($langpart[0])];
  92. else if (! is_numeric($langpart[1])) { // Second part YY may be a numeric with some Chrome browser
  93. $srclang=strtolower($langpart[0])."_".strtoupper($langpart[1]);
  94. $longforlong=array('no_nb'=>'nb_NO');
  95. if (isset($longforlong[strtolower($srclang)])) $srclang=$longforlong[strtolower($srclang)];
  96. }
  97. else $srclang=strtolower($langpart[0])."_".strtoupper($langpart[0]);
  98. }
  99. else { // If it's for a codetouse that is a short code xx
  100. // Array to convert short lang code into long code.
  101. $longforshort=array('ar'=>'ar_SA', 'el'=>'el_GR', 'ca'=>'ca_ES', 'en'=>'en_US', 'nb'=>'nb_NO', 'no'=>'nb_NO');
  102. if (isset($longforshort[strtolower($langpart[0])])) $srclang=$longforshort[strtolower($langpart[0])];
  103. else if (! empty($langpart[0])) $srclang=strtolower($langpart[0])."_".strtoupper($langpart[0]);
  104. else $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. {
  118. if (empty($mode)) return $this->defaultlang;
  119. else return substr($this->defaultlang,0,2);
  120. }
  121. /**
  122. * Load translation key-value for a particular file, into a memory array.
  123. * If data for file already loaded, do nothing.
  124. * All data in translation array are stored in UTF-8 format.
  125. * tab_loaded is completed with $domain key.
  126. * rule "we keep first entry found with we keep last entry found" so it is probably not what you want to do.
  127. *
  128. * Value for hash are: 1:Loaded from disk, 2:Not found, 3:Loaded from cache
  129. *
  130. * @param string $domain File name to load (.lang file). Must be "file" or "file@module" for module language files:
  131. * If $domain is "file@module" instead of "file" then we look for module lang file
  132. * in htdocs/custom/modules/mymodule/langs/code_CODE/file.lang
  133. * then in htdocs/module/langs/code_CODE/file.lang instead of htdocs/langs/code_CODE/file.lang
  134. * @param integer $alt 0 (try xx_ZZ then 1), 1 (try xx_XX then 2), 2 (try en_US)
  135. * @param int $stopafterdirection Stop when the DIRECTION tag is found (optimize speed)
  136. * @param int $forcelangdir To force a different lang directory
  137. * @return int <0 if KO, 0 if already loaded or loading not required, >0 if OK
  138. */
  139. function load($domain,$alt=0,$stopafterdirection=0,$forcelangdir='')
  140. {
  141. global $conf,$db;
  142. if (count($this->tab_translate) == 0) $this->loadFromDatabase($db); // Nothing was loaded yet, so we load database.
  143. // Check parameters
  144. if (empty($domain))
  145. {
  146. dol_print_error('',get_class($this)."::Load ErrorWrongParameters");
  147. return -1;
  148. }
  149. if ($this->defaultlang == 'none_NONE') return 0; // Special language code to not translate keys
  150. //dol_syslog("Translate::Load Start domain=".$domain." alt=".$alt." forcelangdir=".$forcelangdir." this->defaultlang=".$this->defaultlang);
  151. $newdomain = $domain;
  152. $modulename = '';
  153. // Search if a module directory name is provided into lang file name
  154. if (preg_match('/^([^@]+)@([^@]+)$/i',$domain,$regs))
  155. {
  156. $newdomain = $regs[1];
  157. $modulename = $regs[2];
  158. }
  159. // Check cache
  160. if (! empty($this->_tab_loaded[$newdomain])) // File already loaded for this domain
  161. {
  162. //dol_syslog("Translate::Load already loaded for newdomain=".$newdomain);
  163. return 0;
  164. }
  165. $fileread=0;
  166. $langofdir=(empty($forcelangdir)?$this->defaultlang:$forcelangdir);
  167. // Redefine alt
  168. $langarray=explode('_',$langofdir);
  169. if ($alt < 1 && isset($langarray[1]) && strtolower($langarray[0]) == strtolower($langarray[1])) $alt=1;
  170. if ($alt < 2 && strtolower($langofdir) == 'en_us') $alt=2;
  171. if (empty($langofdir)) // This may occurs when load is called without setting the language and without providing a value for forcelangdir
  172. {
  173. dol_syslog("Error: ".get_class($this)."::Load was called but language was not set yet with langs->setDefaultLang(). Nothing will be loaded.", LOG_WARNING);
  174. return -1;
  175. }
  176. foreach($this->dir as $keydir => $searchdir)
  177. {
  178. // Directory of translation files
  179. $file_lang = $searchdir.($modulename?'/'.$modulename:'')."/langs/".$langofdir."/".$newdomain.".lang";
  180. $file_lang_osencoded=dol_osencode($file_lang);
  181. $filelangexists=is_file($file_lang_osencoded);
  182. //dol_syslog(get_class($this).'::Load Try to read for alt='.$alt.' langofdir='.$langofdir.' newdomain='.$domain.' modulename='.$modulename.' file_lang='.$file_lang." => filelangexists=".$filelangexists);
  183. if ($filelangexists)
  184. {
  185. // TODO Move cache read out of loop on dirs or at least filelangexists
  186. $found=false;
  187. // Enable caching of lang file in memory (not by default)
  188. $usecachekey='';
  189. // Using a memcached server
  190. if (! empty($conf->memcached->enabled) && ! empty($conf->global->MEMCACHED_SERVER))
  191. {
  192. $usecachekey=$newdomain.'_'.$langofdir.'_'.md5($file_lang); // Should not contains special chars
  193. }
  194. // Using cache with shmop. Speed gain: 40ms - Memory overusage: 200ko (Size of session cache file)
  195. else if (isset($conf->global->MAIN_OPTIMIZE_SPEED) && ($conf->global->MAIN_OPTIMIZE_SPEED & 0x02))
  196. {
  197. $usecachekey=$newdomain;
  198. }
  199. if ($usecachekey)
  200. {
  201. //dol_syslog('Translate::Load we will cache result into usecachekey '.$usecachekey);
  202. //global $aaa; $aaa+=1;
  203. //print $aaa." ".$usecachekey."\n";
  204. require_once DOL_DOCUMENT_ROOT .'/core/lib/memory.lib.php';
  205. $tmparray=dol_getcache($usecachekey);
  206. if (is_array($tmparray) && count($tmparray))
  207. {
  208. $this->tab_translate+=$tmparray; // Faster than array_merge($tmparray,$this->tab_translate). Note: If a valuer already exists into tab_translate, value into tmparaay is not added.
  209. //print $newdomain."\n";
  210. //var_dump($this->tab_translate);
  211. if ($alt == 2) $fileread=1;
  212. $found=true; // Found in dolibarr PHP cache
  213. }
  214. }
  215. if (! $found)
  216. {
  217. if ($fp = @fopen($file_lang,"rt"))
  218. {
  219. if ($usecachekey) $tabtranslatedomain=array(); // To save lang content in cache
  220. while ($line = fgets($fp,4096)) // Ex: Need 225ms for all fgets on all lang file for Third party page. Same speed than file_get_contents
  221. {
  222. if ($line[0] != "\n" && $line[0] != " " && $line[0] != "#")
  223. {
  224. $tab=explode('=',$line,2);
  225. $key=trim($tab[0]);
  226. //print "Domain=$domain, found a string for $tab[0] with value $tab[1]<br>";
  227. if (empty($this->tab_translate[$key]) && isset($tab[1])) // If translation was already found, we must not continue, even if MAIN_FORCELANGDIR is set (MAIN_FORCELANGDIR is to replace lang dir, not to overwrite entries)
  228. {
  229. $value=trim(preg_replace('/\\n/',"\n",$tab[1]));
  230. if ($key == 'DIRECTION') // This is to declare direction of language
  231. {
  232. if ($alt < 2 || empty($this->tab_translate[$key])) // We load direction only for primary files or if not yet loaded
  233. {
  234. $this->tab_translate[$key]=$value;
  235. if ($stopafterdirection) break; // We do not save tab if we stop after DIRECTION
  236. else if ($usecachekey) $tabtranslatedomain[$key]=$value;
  237. }
  238. }
  239. else
  240. {
  241. $this->tab_translate[$key]=$value;
  242. if ($usecachekey) $tabtranslatedomain[$key]=$value; // To save lang content in cache
  243. }
  244. }
  245. }
  246. }
  247. fclose($fp);
  248. $fileread=1;
  249. // TODO Move cache write out of loop on dirs
  250. // To save lang content for usecachekey into cache
  251. if ($usecachekey && count($tabtranslatedomain))
  252. {
  253. $ressetcache=dol_setcache($usecachekey,$tabtranslatedomain);
  254. if ($ressetcache < 0)
  255. {
  256. $error='Failed to set cache for usecachekey='.$usecachekey.' result='.$ressetcache;
  257. dol_syslog($error, LOG_ERR);
  258. }
  259. }
  260. if (empty($conf->global->MAIN_FORCELANGDIR)) break; // Break loop on each root dir. If a module has forced dir, we do not stop loop.
  261. }
  262. }
  263. }
  264. }
  265. // Now we complete with next file
  266. if ($alt == 0)
  267. {
  268. // This function MUST NOT contains call to syslog
  269. //dol_syslog("Translate::Load loading alternate translation file (to complete ".$this->defaultlang."/".$newdomain.".lang file)", LOG_DEBUG);
  270. $langofdir=strtolower($langarray[0]).'_'.strtoupper($langarray[0]);
  271. $this->load($domain,$alt+1,$stopafterdirection,$langofdir);
  272. }
  273. // Now we complete with reference en_US/fr_FR/es_ES file
  274. if ($alt == 1)
  275. {
  276. // This function MUST NOT contains call to syslog
  277. //dol_syslog("Translate::Load loading alternate translation file (to complete ".$this->defaultlang."/".$newdomain.".lang file)", LOG_DEBUG);
  278. $langofdir='en_US';
  279. //if (preg_match('/^fr/i',$langarray[0])) $langofdir='fr_FR';
  280. //if (preg_match('/^es/i',$langarray[0])) $langofdir='es_ES';
  281. $this->load($domain,$alt+1,$stopafterdirection,$langofdir);
  282. }
  283. if ($alt == 2)
  284. {
  285. if ($fileread) $this->_tab_loaded[$newdomain]=1; // Set domain file as loaded
  286. if (empty($this->_tab_loaded[$newdomain])) $this->_tab_loaded[$newdomain]=2; // Marque ce fichier comme non trouve
  287. }
  288. // This part is deprecated and replaced with table llx_overwrite_trans
  289. // Kept for backward compatibility.
  290. $overwritekey='MAIN_OVERWRITE_TRANS_'.$this->defaultlang;
  291. if (! empty($conf->global->$overwritekey)) // Overwrite translation with key1:newstring1,key2:newstring2
  292. {
  293. // Overwrite translation with param MAIN_OVERWRITE_TRANS_xx_XX
  294. $tmparray=explode(',', $conf->global->$overwritekey);
  295. foreach($tmparray as $tmp)
  296. {
  297. $tmparray2=explode(':',$tmp);
  298. if (! empty($tmparray2[1])) $this->tab_translate[$tmparray2[0]]=$tmparray2[1];
  299. }
  300. }
  301. // Check to be sure that SeparatorDecimal differs from SeparatorThousand
  302. if (! empty($this->tab_translate["SeparatorDecimal"]) && ! empty($this->tab_translate["SeparatorThousand"])
  303. && $this->tab_translate["SeparatorDecimal"] == $this->tab_translate["SeparatorThousand"]) $this->tab_translate["SeparatorThousand"]='';
  304. return 1;
  305. }
  306. /**
  307. * Load translation key-value from database into a memory array.
  308. * If data already loaded, do nothing.
  309. * All data in translation array are stored in UTF-8 format.
  310. * tab_loaded is completed with $domain key.
  311. * rule "we keep first entry found with we keep last entry found" so it is probably not what you want to do.
  312. *
  313. * Value for hash are: 1:Loaded from disk, 2:Not found, 3:Loaded from cache
  314. *
  315. * @param Database $db Database handler
  316. * @return int <0 if KO, 0 if already loaded or loading not required, >0 if OK
  317. */
  318. function loadFromDatabase($db)
  319. {
  320. global $conf;
  321. $domain='database';
  322. if ($this->defaultlang == 'none_NONE') return 0; // Special language code to not translate keys
  323. // Check parameters
  324. if (empty($db)) return 0; // Database handler can't be used
  325. //dol_syslog("Translate::Load Start domain=".$domain." alt=".$alt." forcelangdir=".$forcelangdir." this->defaultlang=".$this->defaultlang);
  326. $newdomain = $domain;
  327. $modulename = '';
  328. // Check cache
  329. if (! empty($this->_tab_loaded[$newdomain])) // File already loaded for this domain
  330. {
  331. //dol_syslog("Translate::Load already loaded for newdomain=".$newdomain);
  332. return 0;
  333. }
  334. $this->_tab_loaded[$newdomain] = 1; // We want to be sure this function is called once only.
  335. $fileread=0;
  336. $langofdir=(empty($forcelangdir)?$this->defaultlang:$forcelangdir);
  337. // Redefine alt
  338. $alt=2;
  339. if (empty($langofdir)) // This may occurs when load is called without setting the language and without providing a value for forcelangdir
  340. {
  341. dol_syslog("Error: ".get_class($this)."::Load was called but language was not set yet with langs->setDefaultLang(). Nothing will be loaded.", LOG_WARNING);
  342. return -1;
  343. }
  344. // TODO Move cache read out of loop on dirs or at least filelangexists
  345. $found=false;
  346. // Enable caching of lang file in memory (not by default)
  347. $usecachekey='';
  348. // Using a memcached server
  349. if (! empty($conf->memcached->enabled) && ! empty($conf->global->MEMCACHED_SERVER))
  350. {
  351. $usecachekey=$newdomain.'_'.$langofdir.'_'.md5($file_lang); // Should not contains special chars
  352. }
  353. // Using cache with shmop. Speed gain: 40ms - Memory overusage: 200ko (Size of session cache file)
  354. else if (isset($conf->global->MAIN_OPTIMIZE_SPEED) && ($conf->global->MAIN_OPTIMIZE_SPEED & 0x02))
  355. {
  356. $usecachekey=$newdomain;
  357. }
  358. if ($usecachekey)
  359. {
  360. //dol_syslog('Translate::Load we will cache result into usecachekey '.$usecachekey);
  361. //global $aaa; $aaa+=1;
  362. //print $aaa." ".$usecachekey."\n";
  363. require_once DOL_DOCUMENT_ROOT .'/core/lib/memory.lib.php';
  364. $tmparray=dol_getcache($usecachekey);
  365. if (is_array($tmparray) && count($tmparray))
  366. {
  367. $this->tab_translate+=$tmparray; // Faster than array_merge($tmparray,$this->tab_translate). Note: If a valuer already exists into tab_translate, value into tmparaay is not added.
  368. //print $newdomain."\n";
  369. //var_dump($this->tab_translate);
  370. if ($alt == 2) $fileread=1;
  371. $found=true; // Found in dolibarr PHP cache
  372. }
  373. }
  374. if (! $found)
  375. {
  376. // Overwrite translation with database read
  377. $sql="SELECT transkey, transvalue FROM ".MAIN_DB_PREFIX."overwrite_trans where lang='".$this->defaultlang."'";
  378. $resql=$db->query($sql);
  379. if ($resql)
  380. {
  381. $num = $db->num_rows($resql);
  382. if ($num)
  383. {
  384. if ($usecachekey) $tabtranslatedomain=array(); // To save lang content in cache
  385. $i = 0;
  386. while ($i < $num) // Ex: Need 225ms for all fgets on all lang file for Third party page. Same speed than file_get_contents
  387. {
  388. $obj=$db->fetch_object($resql);
  389. $key=$obj->transkey;
  390. $value=$obj->transvalue;
  391. //print "Domain=$domain, found a string for $tab[0] with value $tab[1]<br>";
  392. if (empty($this->tab_translate[$key])) // If translation was already found, we must not continue, even if MAIN_FORCELANGDIR is set (MAIN_FORCELANGDIR is to replace lang dir, not to overwrite entries)
  393. {
  394. $value=trim(preg_replace('/\\n/',"\n",$value));
  395. $this->tab_translate[$key]=$value;
  396. if ($usecachekey) $tabtranslatedomain[$key]=$value; // To save lang content in cache
  397. }
  398. $i++;
  399. }
  400. $fileread=1;
  401. // TODO Move cache write out of loop on dirs
  402. // To save lang content for usecachekey into cache
  403. if ($usecachekey && count($tabtranslatedomain))
  404. {
  405. $ressetcache=dol_setcache($usecachekey,$tabtranslatedomain);
  406. if ($ressetcache < 0)
  407. {
  408. $error='Failed to set cache for usecachekey='.$usecachekey.' result='.$ressetcache;
  409. dol_syslog($error, LOG_ERR);
  410. }
  411. }
  412. }
  413. }
  414. else
  415. {
  416. dol_print_error($db);
  417. }
  418. }
  419. if ($alt == 2)
  420. {
  421. if ($fileread) $this->_tab_loaded[$newdomain]=1; // Set domain file as loaded
  422. if (empty($this->_tab_loaded[$newdomain])) $this->_tab_loaded[$newdomain]=2; // Marque ce cas comme non trouve (no lines found for language)
  423. }
  424. // Check to be sure that SeparatorDecimal differs from SeparatorThousand
  425. if (! empty($this->tab_translate["SeparatorDecimal"]) && ! empty($this->tab_translate["SeparatorThousand"])
  426. && $this->tab_translate["SeparatorDecimal"] == $this->tab_translate["SeparatorThousand"]) $this->tab_translate["SeparatorThousand"]='';
  427. return 1;
  428. }
  429. /**
  430. * Return translated value of key for special keys ("Currency...", "Civility...", ...).
  431. * Search in lang file, then into database. Key must be any complete entry into lang file: CurrencyEUR, ...
  432. * If not found, return key.
  433. * The string return is not formated (translated with transnoentitiesnoconv)
  434. * NOTE: To avoid infinite loop (getLabelFromKey->transnoentities->getTradFromKey), if you modify this function,
  435. * check that getLabelFromKey is not called with same value than input.
  436. *
  437. * @param string $key Key to translate
  438. * @return string Translated string (translated with transnoentitiesnoconv)
  439. */
  440. private function getTradFromKey($key)
  441. {
  442. global $db;
  443. if (! is_string($key)) return 'ErrorBadValueForParamNotAString'; // Avoid multiple errors with code not using function correctly.
  444. $newstr=$key;
  445. if (preg_match('/^Civility([0-9A-Z]+)$/i',$key,$reg))
  446. {
  447. $newstr=$this->getLabelFromKey($db,$reg[1],'c_civility','code','label');
  448. }
  449. elseif (preg_match('/^Currency([A-Z][A-Z][A-Z])$/i',$key,$reg))
  450. {
  451. $newstr=$this->getLabelFromKey($db,$reg[1],'c_currencies','code_iso','label');
  452. }
  453. elseif (preg_match('/^SendingMethod([0-9A-Z]+)$/i',$key,$reg))
  454. {
  455. $newstr=$this->getLabelFromKey($db,$reg[1],'c_shipment_mode','code','libelle');
  456. }
  457. elseif (preg_match('/^PaymentTypeShort([0-9A-Z]+)$/i',$key,$reg))
  458. {
  459. $newstr=$this->getLabelFromKey($db,$reg[1],'c_paiement','code','libelle');
  460. }
  461. elseif (preg_match('/^OppStatusShort([0-9A-Z]+)$/i',$key,$reg))
  462. {
  463. $newstr=$this->getLabelFromKey($db,$reg[1],'c_lead_status','code','label');
  464. }
  465. elseif (preg_match('/^OppStatus([0-9A-Z]+)$/i',$key,$reg))
  466. {
  467. $newstr=$this->getLabelFromKey($db,$reg[1],'c_lead_status','code','label');
  468. }
  469. elseif (preg_match('/^OrderSource([0-9A-Z]+)$/i',$key,$reg))
  470. {
  471. // TODO OrderSourceX must be replaced with content of table llx_c_input_reason or llx_c_input_method
  472. //$newstr=$this->getLabelFromKey($db,$reg[1],'c_ordersource','code','label');
  473. }
  474. return $newstr;
  475. }
  476. /**
  477. * Return text translated of text received as parameter (and encode it into HTML)
  478. * Si il n'y a pas de correspondance pour ce texte, on cherche dans fichier alternatif
  479. * et si toujours pas trouve, il est retourne tel quel
  480. * Les parametres de cette methode peuvent contenir de balises HTML.
  481. *
  482. * @param string $key Key to translate
  483. * @param string $param1 chaine de param1
  484. * @param string $param2 chaine de param2
  485. * @param string $param3 chaine de param3
  486. * @param string $param4 chaine de param4
  487. * @param int $maxsize Max length of text
  488. * @return string Translated string (encoded into HTML entities and UTF8)
  489. */
  490. function trans($key, $param1='', $param2='', $param3='', $param4='', $maxsize=0)
  491. {
  492. global $conf;
  493. if (! empty($this->tab_translate[$key])) // Translation is available
  494. {
  495. $str=$this->tab_translate[$key];
  496. // Make some string replacement after translation
  497. $replacekey='MAIN_REPLACE_TRANS_'.$this->defaultlang;
  498. if (! empty($conf->global->$replacekey)) // Replacement translation variable with string1:newstring1;string2:newstring2
  499. {
  500. $tmparray=explode(';', $conf->global->$replacekey);
  501. foreach($tmparray as $tmp)
  502. {
  503. $tmparray2=explode(':',$tmp);
  504. $str=preg_replace('/'.preg_quote($tmparray2[0]).'/',$tmparray2[1],$str);
  505. }
  506. }
  507. if (! preg_match('/^Format/',$key))
  508. {
  509. //print $str;
  510. $str=sprintf($str,$param1,$param2,$param3,$param4); // Replace %s and %d except for FormatXXX strings.
  511. }
  512. if ($maxsize) $str=dol_trunc($str,$maxsize);
  513. // We replace some HTML tags by __xx__ to avoid having them encoded by htmlentities
  514. $str=str_replace(array('<','>','"',),array('__lt__','__gt__','__quot__'),$str);
  515. // Crypt string into HTML
  516. $str=htmlentities($str,ENT_QUOTES,$this->charset_output);
  517. // Restore HTML tags
  518. $str=str_replace(array('__lt__','__gt__','__quot__'),array('<','>','"',),$str);
  519. return $str;
  520. }
  521. else // Translation is not available
  522. {
  523. if ($key[0] == '$') { return dol_eval($key,1); }
  524. return $this->getTradFromKey($key);
  525. }
  526. }
  527. /**
  528. * Return translated value of a text string
  529. * Si il n'y a pas de correspondance pour ce texte, on cherche dans fichier alternatif
  530. * et si toujours pas trouve, il est retourne tel quel.
  531. * Parameters of this method must not contains any HTML tags.
  532. *
  533. * @param string $key Key to translate
  534. * @param string $param1 chaine de param1
  535. * @param string $param2 chaine de param2
  536. * @param string $param3 chaine de param3
  537. * @param string $param4 chaine de param4
  538. * @return string Translated string (encoded into UTF8)
  539. */
  540. function transnoentities($key, $param1='', $param2='', $param3='', $param4='')
  541. {
  542. return $this->convToOutputCharset($this->transnoentitiesnoconv($key, $param1, $param2, $param3, $param4));
  543. }
  544. /**
  545. * Return translated value of a text string
  546. * Si il n'y a pas de correspondance pour ce texte, on cherche dans fichier alternatif
  547. * et si toujours pas trouve, il est retourne tel quel.
  548. * No convert to encoding charset of lang object is done.
  549. * Parameters of this method must not contains any HTML tags.
  550. *
  551. * @param string $key Key to translate
  552. * @param string $param1 chaine de param1
  553. * @param string $param2 chaine de param2
  554. * @param string $param3 chaine de param3
  555. * @param string $param4 chaine de param4
  556. * @return string Translated string
  557. */
  558. function transnoentitiesnoconv($key, $param1='', $param2='', $param3='', $param4='')
  559. {
  560. global $conf;
  561. if (! empty($this->tab_translate[$key])) // Translation is available
  562. {
  563. $str=$this->tab_translate[$key];
  564. // Make some string replacement after translation
  565. $replacekey='MAIN_REPLACE_TRANS_'.$this->defaultlang;
  566. if (! empty($conf->global->$replacekey)) // Replacement translation variable with string1:newstring1;string2:newstring2
  567. {
  568. $tmparray=explode(';', $conf->global->$replacekey);
  569. foreach($tmparray as $tmp)
  570. {
  571. $tmparray2=explode(':',$tmp);
  572. $str=preg_replace('/'.preg_quote($tmparray2[0]).'/',$tmparray2[1],$str);
  573. }
  574. }
  575. if (! preg_match('/^Format/',$key))
  576. {
  577. //print $str;
  578. $str=sprintf($str,$param1,$param2,$param3,$param4); // Replace %s and %d except for FormatXXX strings.
  579. }
  580. return $str;
  581. }
  582. else
  583. {
  584. if ($key[0] == '$') { return dol_eval($key,1); }
  585. return $this->getTradFromKey($key);
  586. }
  587. }
  588. /**
  589. * Return translation of a key depending on country
  590. *
  591. * @param string $str string root to translate
  592. * @param string $countrycode country code (FR, ...)
  593. * @return string translated string
  594. */
  595. function transcountry($str, $countrycode)
  596. {
  597. if ($this->tab_translate["$str$countrycode"]) return $this->trans("$str$countrycode");
  598. else return $this->trans($str);
  599. }
  600. /**
  601. * Retourne la version traduite du texte passe en parametre complete du code pays
  602. *
  603. * @param string $str string root to translate
  604. * @param string $countrycode country code (FR, ...)
  605. * @return string translated string
  606. */
  607. function transcountrynoentities($str, $countrycode)
  608. {
  609. if ($this->tab_translate["$str$countrycode"]) return $this->transnoentities("$str$countrycode");
  610. else return $this->transnoentities($str);
  611. }
  612. /**
  613. * Convert a string into output charset (this->charset_output that should be defined to conf->file->character_set_client)
  614. *
  615. * @param string $str String to convert
  616. * @param string $pagecodefrom Page code of src string
  617. * @return string Converted string
  618. */
  619. function convToOutputCharset($str,$pagecodefrom='UTF-8')
  620. {
  621. if ($pagecodefrom == 'ISO-8859-1' && $this->charset_output == 'UTF-8') $str=utf8_encode($str);
  622. if ($pagecodefrom == 'UTF-8' && $this->charset_output == 'ISO-8859-1') $str=utf8_decode(str_replace('€',chr(128),$str));
  623. return $str;
  624. }
  625. /**
  626. * Return list of all available languages
  627. *
  628. * @param string $langdir Directory to scan
  629. * @param integer $maxlength Max length for each value in combo box (will be truncated)
  630. * @param int $usecode 1=Show code instead of country name for language variant, 2=Show only code
  631. * @return array List of languages
  632. */
  633. function get_available_languages($langdir=DOL_DOCUMENT_ROOT,$maxlength=0,$usecode=0)
  634. {
  635. global $conf;
  636. // We scan directory langs to detect available languages
  637. $handle=opendir($langdir."/langs");
  638. $langs_available=array();
  639. while ($dir = trim(readdir($handle)))
  640. {
  641. if (preg_match('/^[a-z]+_[A-Z]+/i',$dir))
  642. {
  643. $this->load("languages");
  644. if ($usecode == 2)
  645. {
  646. $langs_available[$dir] = $dir;
  647. }
  648. if ($usecode == 1 || ! empty($conf->global->MAIN_SHOW_LANGUAGE_CODE))
  649. {
  650. $langs_available[$dir] = $dir.': '.dol_trunc($this->trans('Language_'.$dir),$maxlength);
  651. }
  652. else
  653. {
  654. $langs_available[$dir] = $this->trans('Language_'.$dir);
  655. }
  656. }
  657. }
  658. return $langs_available;
  659. }
  660. /**
  661. * Return if a filename $filename exists for current language (or alternate language)
  662. *
  663. * @param string $filename Language filename to search
  664. * @param integer $searchalt Search also alernate language file
  665. * @return boolean true if exists and readable
  666. */
  667. function file_exists($filename,$searchalt=0)
  668. {
  669. // Test si fichier dans repertoire de la langue
  670. foreach($this->dir as $searchdir)
  671. {
  672. if (is_readable(dol_osencode($searchdir."/langs/".$this->defaultlang."/".$filename))) return true;
  673. if ($searchalt)
  674. {
  675. // Test si fichier dans repertoire de la langue alternative
  676. if ($this->defaultlang != "en_US") $filenamealt = $searchdir."/langs/en_US/".$filename;
  677. //else $filenamealt = $searchdir."/langs/fr_FR/".$filename;
  678. if (is_readable(dol_osencode($filenamealt))) return true;
  679. }
  680. }
  681. return false;
  682. }
  683. /**
  684. * Return full text translated to language label for a key. Store key-label in a cache.
  685. * This function need module "numberwords" to be installed. If not it will return
  686. * same number (this module is not provided by default as it use non GPL source code).
  687. *
  688. * @param int $number Number to encode in full text
  689. * @param int $isamount 1=It's an amount, 0=it's just a number
  690. * @return string Label translated in UTF8 (but without entities)
  691. * 10 if setDefaultLang was en_US => ten
  692. * 123 if setDefaultLang was fr_FR => cent vingt trois
  693. */
  694. function getLabelFromNumber($number,$isamount=0)
  695. {
  696. global $conf;
  697. $newnumber=$number;
  698. $dirsubstitutions=array_merge(array(),$conf->modules_parts['substitutions']);
  699. foreach($dirsubstitutions as $reldir)
  700. {
  701. $dir=dol_buildpath($reldir,0);
  702. $newdir=dol_osencode($dir);
  703. // Check if directory exists
  704. if (! is_dir($newdir)) continue; // We must not use dol_is_dir here, function may not be loaded
  705. $fonc='numberwords';
  706. if (file_exists($newdir.'/functions_'.$fonc.'.lib.php'))
  707. {
  708. include_once $newdir.'/functions_'.$fonc.'.lib.php';
  709. $newnumber=numberwords_getLabelFromNumber($this,$number,$isamount);
  710. break;
  711. }
  712. }
  713. return $newnumber;
  714. }
  715. /**
  716. * Return a label for a key.
  717. * Search into translation array, then into cache, then if still not found, search into database.
  718. * Store key-label found into cache variable $this->cache_labels to save SQL requests to get labels.
  719. *
  720. * @param DoliDB $db Database handler
  721. * @param string $key Translation key to get label (key in language file)
  722. * @param string $tablename Table name without prefix
  723. * @param string $fieldkey Field for key
  724. * @param string $fieldlabel Field for label
  725. * @param string $keyforselect Use another value than the translation key for the where into select
  726. * @return string Label in UTF8 (but without entities)
  727. * @see dol_getIdFromCode
  728. */
  729. function getLabelFromKey($db,$key,$tablename,$fieldkey,$fieldlabel,$keyforselect='')
  730. {
  731. // If key empty
  732. if ($key == '') return '';
  733. //print 'param: '.$key.'-'.$keydatabase.'-'.$this->trans($key); exit;
  734. // Check if a translation is available (this can call getTradFromKey)
  735. $tmp=$this->transnoentitiesnoconv($key);
  736. if ($tmp != $key && $tmp != 'ErrorBadValueForParamNotAString')
  737. {
  738. return $tmp; // Found in language array
  739. }
  740. // Check in cache
  741. if (isset($this->cache_labels[$tablename][$key])) // Can be defined to 0 or ''
  742. {
  743. return $this->cache_labels[$tablename][$key]; // Found in cache
  744. }
  745. $sql = "SELECT ".$fieldlabel." as label";
  746. $sql.= " FROM ".MAIN_DB_PREFIX.$tablename;
  747. $sql.= " WHERE ".$fieldkey." = '".($keyforselect?$keyforselect:$key)."'";
  748. dol_syslog(get_class($this).'::getLabelFromKey', LOG_DEBUG);
  749. $resql = $db->query($sql);
  750. if ($resql)
  751. {
  752. $obj = $db->fetch_object($resql);
  753. if ($obj) $this->cache_labels[$tablename][$key]=$obj->label;
  754. else $this->cache_labels[$tablename][$key]=$key;
  755. $db->free($resql);
  756. return $this->cache_labels[$tablename][$key];
  757. }
  758. else
  759. {
  760. $this->error=$db->lasterror();
  761. return -1;
  762. }
  763. }
  764. /**
  765. * Return a currency code into its symbol
  766. *
  767. * @param string $currency_code Currency Code
  768. * @param string $amount If not '', show currency + amount according to langs ($10, 10€).
  769. * @return string Amount + Currency symbol encoded into UTF8
  770. * @deprecated Use method price to output a price
  771. * @see price()
  772. */
  773. function getCurrencyAmount($currency_code, $amount)
  774. {
  775. $symbol=$this->getCurrencySymbol($currency_code);
  776. if (in_array($currency_code, array('USD'))) return $symbol.$amount;
  777. else return $amount.$symbol;
  778. }
  779. /**
  780. * Return a currency code into its symbol.
  781. * If mb_convert_encoding is not available, return currency code.
  782. *
  783. * @param string $currency_code Currency code
  784. * @param integer $forceloadall 1=Force to load all currencies into cache. We know we need to use all of them. By default read and cache only required currency.
  785. * @return string Currency symbol encoded into UTF8
  786. */
  787. function getCurrencySymbol($currency_code, $forceloadall=0)
  788. {
  789. $currency_sign = ''; // By default return iso code
  790. if (function_exists("mb_convert_encoding"))
  791. {
  792. $this->loadCacheCurrencies($forceloadall?'':$currency_code);
  793. if (isset($this->cache_currencies[$currency_code]) && ! empty($this->cache_currencies[$currency_code]['unicode']) && is_array($this->cache_currencies[$currency_code]['unicode']))
  794. {
  795. foreach($this->cache_currencies[$currency_code]['unicode'] as $unicode)
  796. {
  797. $currency_sign .= mb_convert_encoding("&#{$unicode};", "UTF-8", 'HTML-ENTITIES');
  798. }
  799. }
  800. }
  801. return ($currency_sign?$currency_sign:$currency_code);
  802. }
  803. /**
  804. * Load into the cache this->cache_currencies, all currencies
  805. *
  806. * @param string $currency_code Get only currency. Get all if ''.
  807. * @return int Nb of loaded lines, 0 if already loaded, <0 if KO
  808. */
  809. public function loadCacheCurrencies($currency_code)
  810. {
  811. global $db;
  812. if (! empty($currency_code) && isset($this->cache_currencies[$currency_code])) return 0; // Value already into cache
  813. $sql = "SELECT code_iso, label, unicode";
  814. $sql.= " FROM ".MAIN_DB_PREFIX."c_currencies";
  815. $sql.= " WHERE active = 1";
  816. if (! empty($currency_code)) $sql.=" AND code_iso = '".$currency_code."'";
  817. //$sql.= " ORDER BY code_iso ASC"; // Not required, a sort is done later
  818. dol_syslog(get_class($this).'::loadCacheCurrencies', LOG_DEBUG);
  819. $resql = $db->query($sql);
  820. if ($resql)
  821. {
  822. $this->load("dict");
  823. $label=array();
  824. foreach($this->cache_currencies as $key => $val) $label[$key]=$val['label'];
  825. $num = $db->num_rows($resql);
  826. $i = 0;
  827. while ($i < $num)
  828. {
  829. $obj = $db->fetch_object($resql);
  830. // Si traduction existe, on l'utilise, sinon on prend le libelle par defaut
  831. $this->cache_currencies[$obj->code_iso]['label'] = ($obj->code_iso && $this->trans("Currency".$obj->code_iso)!="Currency".$obj->code_iso?$this->trans("Currency".$obj->code_iso):($obj->label!='-'?$obj->label:''));
  832. $this->cache_currencies[$obj->code_iso]['unicode'] = (array) json_decode($obj->unicode, true);
  833. $label[$obj->code_iso] = $this->cache_currencies[$obj->code_iso]['label'];
  834. $i++;
  835. }
  836. //print count($label).' '.count($this->cache_currencies);
  837. array_multisort($label, SORT_ASC, $this->cache_currencies);
  838. //var_dump($this->cache_currencies); $this->cache_currencies is now sorted onto label
  839. return $num;
  840. }
  841. else
  842. {
  843. dol_print_error($db);
  844. return -1;
  845. }
  846. }
  847. /**
  848. * Return an array with content of all loaded translation keys (found into this->tab_translate) so
  849. * we get a substitution array we can use for substitutions (for mail or ODT generation for example)
  850. *
  851. * @return array Array of translation keys lang_key => string_translation_loaded
  852. */
  853. function get_translations_for_substitutions()
  854. {
  855. $substitutionarray = array();
  856. foreach($this->tab_translate as $code => $label) {
  857. $substitutionarray['lang_'.$code] = $label;
  858. }
  859. return $substitutionarray;
  860. }
  861. }