dolibarr  17.0.4
translate.class.php
Go to the documentation of this file.
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@inodbox.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 <https://www.gnu.org/licenses/>.
18  */
19 
30 class Translate
31 {
32  public $dir; // Directories that contains /langs subdirectory
33 
34  public $defaultlang; // Current language for current user
35  public $shortlang; // Short language for current user
36  public $charset_output = 'UTF-8'; // Codage used by "trans" method outputs
37 
38  public $tab_translate = array(); // Array of all translations key=>value
39  private $_tab_loaded = array(); // Array to store result after loading each language file
40 
41  public $cache_labels = array(); // Cache for labels return by getLabelFromKey method
42  public $cache_currencies = array(); // Cache to store currency symbols
43  private $cache_currencies_all_loaded = false;
44 
45 
52  public function __construct($dir, $conf)
53  {
54  if (!empty($conf->file->character_set_client)) {
55  $this->charset_output = $conf->file->character_set_client; // If charset output is forced
56  }
57  if ($dir) {
58  $this->dir = array($dir);
59  } else {
60  $this->dir = $conf->file->dol_document_root;
61  }
62  }
63 
64 
71  public function setDefaultLang($srclang = 'en_US')
72  {
73  global $conf;
74 
75  //dol_syslog(get_class($this)."::setDefaultLang srclang=".$srclang,LOG_DEBUG);
76 
77  // If a module ask to force a priority on langs directories (to use its own lang files)
78  if (!empty($conf->global->MAIN_FORCELANGDIR)) {
79  $more = array();
80  $i = 0;
81  foreach ($conf->file->dol_document_root as $dir) {
82  $newdir = $dir.$conf->global->MAIN_FORCELANGDIR; // For example $conf->global->MAIN_FORCELANGDIR is '/mymodule' meaning we search files into '/mymodule/langs/xx_XX'
83  if (!in_array($newdir, $this->dir)) {
84  $more['module_'.$i] = $newdir;
85  $i++; // We add the forced dir into the array $more. Just after, we add entries into $more to list of lang dir $this->dir.
86  }
87  }
88  $this->dir = array_merge($more, $this->dir); // Forced dir ($more) are before standard dirs ($this->dir)
89  }
90 
91  $this->origlang = $srclang;
92 
93  if (empty($srclang) || $srclang == 'auto') {
94  // $_SERVER['HTTP_ACCEPT_LANGUAGE'] can be 'fr-FR,fr;q=0.9,en-US;q=0.8,en;q=0.7,it;q=0.6' but can contains also malicious content
95  $langpref = empty($_SERVER['HTTP_ACCEPT_LANGUAGE']) ? '' : $_SERVER['HTTP_ACCEPT_LANGUAGE'];
96  $langpref = preg_replace("/;([^,]*)/i", "", $langpref); // Remove the 'q=x.y,' part
97  $langpref = str_replace("-", "_", $langpref);
98  $langlist = preg_split("/[;,]/", $langpref);
99  $codetouse = preg_replace('/[^_a-zA-Z]/', '', $langlist[0]);
100  } else {
101  $codetouse = $srclang;
102  }
103 
104  // We redefine $srclang
105  $langpart = explode("_", $codetouse);
106  //print "Short code before _ : ".$langpart[0].' / Short code after _ : '.$langpart[1].'<br>';
107  if (!empty($langpart[1])) { // If it's for a codetouse that is a long code xx_YY
108  // Array force long code from first part, even if long code is defined
109  $longforshort = array('ar'=>'ar_SA');
110  $longforshortexcep = array('ar_EG');
111  if (isset($longforshort[strtolower($langpart[0])]) && !in_array($codetouse, $longforshortexcep)) {
112  $srclang = $longforshort[strtolower($langpart[0])];
113  } elseif (!is_numeric($langpart[1])) { // Second part YY may be a numeric with some Chrome browser
114  $srclang = strtolower($langpart[0])."_".strtoupper($langpart[1]);
115  $longforlong = array('no_nb'=>'nb_NO');
116  if (isset($longforlong[strtolower($srclang)])) {
117  $srclang = $longforlong[strtolower($srclang)];
118  }
119  } else {
120  $srclang = strtolower($langpart[0])."_".strtoupper($langpart[0]);
121  }
122  } else { // If it's for a codetouse that is a short code xx
123  // Array to convert short lang code into long code.
124  $longforshort = array(
125  'am'=>'am_ET', 'ar'=>'ar_SA', 'bn'=>'bn_DB', 'el'=>'el_GR', 'ca'=>'ca_ES', 'cs'=>'cs_CZ', 'en'=>'en_US', 'fa'=>'fa_IR',
126  'gl'=>'gl_ES', 'he'=>'he_IL', 'hi'=>'hi_IN', 'ja'=>'ja_JP',
127  'ka'=>'ka_GE', 'km'=>'km_KH', 'kn'=>'kn_IN', 'ko'=>'ko_KR', 'lo'=>'lo_LA', 'nb'=>'nb_NO', 'no'=>'nb_NO', 'ne'=>'ne_NP',
128  'sl'=>'sl_SI', 'sq'=>'sq_AL', 'sr'=>'sr_RS', 'sv'=>'sv_SE', 'uk'=>'uk_UA', 'vi'=>'vi_VN', 'zh'=>'zh_CN'
129  );
130  if (isset($longforshort[strtolower($langpart[0])])) {
131  $srclang = $longforshort[strtolower($langpart[0])];
132  } elseif (!empty($langpart[0])) {
133  $srclang = strtolower($langpart[0])."_".strtoupper($langpart[0]);
134  } else {
135  $srclang = 'en_US';
136  }
137  }
138 
139  $this->defaultlang = $srclang;
140  $this->shortlang = substr($srclang, 0, 2);
141  //print 'this->defaultlang='.$this->defaultlang;
142  }
143 
144 
152  public function getDefaultLang($mode = 0)
153  {
154  if (empty($mode)) {
155  return $this->defaultlang;
156  } else {
157  return substr($this->defaultlang, 0, 2);
158  }
159  }
160 
161 
168  public function loadLangs($domains)
169  {
170  foreach ($domains as $domain) {
171  $this->load($domain);
172  }
173  }
174 
196  public function load($domain, $alt = 0, $stopafterdirection = 0, $forcelangdir = '', $loadfromfileonly = 0, $forceloadifalreadynotfound = 0)
197  {
198  global $conf, $db;
199 
200  //dol_syslog("Translate::Load Start domain=".$domain." alt=".$alt." forcelangdir=".$forcelangdir." this->defaultlang=".$this->defaultlang);
201 
202  // Check parameters
203  if (empty($domain)) {
204  dol_print_error('', get_class($this)."::Load ErrorWrongParameters");
205  return -1;
206  }
207  if ($this->defaultlang === 'none_NONE') {
208  return 0; // Special language code to not translate keys
209  }
210 
211 
212  // Load $this->tab_translate[] from database
213  if (empty($loadfromfileonly) && count($this->tab_translate) == 0) {
214  $this->loadFromDatabase($db); // No translation was never loaded yet, so we load database.
215  }
216 
217 
218  $newdomain = $domain;
219  $modulename = '';
220 
221  // Search if a module directory name is provided into lang file name
222  $regs = array();
223  if (preg_match('/^([^@]+)@([^@]+)$/i', $domain, $regs)) {
224  $newdomain = $regs[1];
225  $modulename = $regs[2];
226  }
227 
228  // Check cache
229  if (!empty($this->_tab_loaded[$newdomain])
230  && ($this->_tab_loaded[$newdomain] != 2 || empty($forceloadifalreadynotfound))) { // File already loaded and found and not forced for this domain
231  //dol_syslog("Translate::Load already loaded for newdomain=".$newdomain);
232  return 0;
233  }
234 
235  $fileread = 0;
236  $langofdir = (empty($forcelangdir) ? $this->defaultlang : $forcelangdir);
237 
238  // Redefine alt
239  $langarray = explode('_', $langofdir);
240  if ($alt < 1 && isset($langarray[1]) && (strtolower($langarray[0]) == strtolower($langarray[1]) || in_array(strtolower($langofdir), array('el_gr')))) {
241  $alt = 1;
242  }
243  if ($alt < 2 && strtolower($langofdir) == 'en_us') {
244  $alt = 2;
245  }
246 
247  if (empty($langofdir)) { // This may occurs when load is called without setting the language and without providing a value for forcelangdir
248  dol_syslog("Error: ".get_class($this)."::load was called for domain=".$domain." but language was not set yet with langs->setDefaultLang(). Nothing will be loaded.", LOG_WARNING);
249  return -1;
250  }
251 
252  foreach ($this->dir as $searchdir) {
253  // Directory of translation files
254  $file_lang = $searchdir.($modulename ? '/'.$modulename : '')."/langs/".$langofdir."/".$newdomain.".lang";
255  $file_lang_osencoded = dol_osencode($file_lang);
256 
257  $filelangexists = is_file($file_lang_osencoded);
258 
259  //dol_syslog(get_class($this).'::Load Try to read for alt='.$alt.' langofdir='.$langofdir.' domain='.$domain.' newdomain='.$newdomain.' modulename='.$modulename.' file_lang='.$file_lang." => filelangexists=".$filelangexists);
260  //print 'Try to read for alt='.$alt.' langofdir='.$langofdir.' domain='.$domain.' newdomain='.$newdomain.' modulename='.$modulename.' this->_tab_loaded[newdomain]='.$this->_tab_loaded[$newdomain].' file_lang='.$file_lang." => filelangexists=".$filelangexists."\n";
261 
262  if ($filelangexists) {
263  // TODO Move cache read out of loop on dirs or at least filelangexists
264  $found = false;
265 
266  // Enable caching of lang file in memory (not by default)
267  $usecachekey = '';
268  // Using a memcached server
269  if (!empty($conf->memcached->enabled) && !empty($conf->global->MEMCACHED_SERVER)) {
270  $usecachekey = $newdomain.'_'.$langofdir.'_'.md5($file_lang); // Should not contains special chars
271  } elseif (isset($conf->global->MAIN_OPTIMIZE_SPEED) && ($conf->global->MAIN_OPTIMIZE_SPEED & 0x02)) {
272  // Using cache with shmop. Speed gain: 40ms - Memory overusage: 200ko (Size of session cache file)
273  $usecachekey = $newdomain;
274  }
275 
276  if ($usecachekey) {
277  //dol_syslog('Translate::Load we will cache result into usecachekey '.$usecachekey);
278  //global $aaa; $aaa+=1;
279  //print $aaa." ".$usecachekey."\n";
280  require_once DOL_DOCUMENT_ROOT.'/core/lib/memory.lib.php';
281  $tmparray = dol_getcache($usecachekey);
282  if (is_array($tmparray) && count($tmparray)) {
283  $this->tab_translate += $tmparray; // Faster than array_merge($tmparray,$this->tab_translate). Note: If a value already exists into tab_translate, value into tmparaay is not added.
284  //print $newdomain."\n";
285  //var_dump($this->tab_translate);
286  if ($alt == 2) {
287  $fileread = 1;
288  }
289  $found = true; // Found in dolibarr PHP cache
290  }
291  }
292 
293  if (!$found) {
294  if ($fp = @fopen($file_lang, "rt")) {
295  if ($usecachekey) {
296  $tabtranslatedomain = array(); // To save lang content in cache
297  }
298 
304  while ($line = fscanf($fp, "%[^= ]%*[ =]%[^\n\r]")) {
305  if (isset($line[1])) {
306  list($key, $value) = $line;
307  //if ($domain == 'orders') print "Domain=$domain, found a string for $tab[0] with value $tab[1]. Currently in cache ".$this->tab_translate[$key]."<br>";
308  //if ($key == 'Order') print "Domain=$domain, found a string for key=$key=$tab[0] with value $tab[1]. Currently in cache ".$this->tab_translate[$key]."<br>";
309  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)
310  if ($key == 'DIRECTION') { // This is to declare direction of language
311  if ($alt < 2 || empty($this->tab_translate[$key])) { // We load direction only for primary files or if not yet loaded
312  $this->tab_translate[$key] = $value;
313  if ($stopafterdirection) {
314  break; // We do not save tab if we stop after DIRECTION
315  } elseif ($usecachekey) {
316  $tabtranslatedomain[$key] = $value;
317  }
318  }
319  } elseif ($key[0] == '#') {
320  continue;
321  } else {
322  // Convert some strings: Parse and render carriage returns. Also, change '\\s' into '\s' because transifex sync pull the string '\s' into string '\\s'
323  $this->tab_translate[$key] = str_replace(array('\\n', '\\\\s'), array("\n", '\s'), $value);
324  if ($usecachekey) {
325  $tabtranslatedomain[$key] = $value;
326  } // To save lang content in cache
327  }
328  }
329  }
330  }
331  fclose($fp);
332  $fileread = 1;
333 
334  // TODO Move cache write out of loop on dirs
335  // To save lang content for usecachekey into cache
336  if ($usecachekey && count($tabtranslatedomain)) {
337  $ressetcache = dol_setcache($usecachekey, $tabtranslatedomain);
338  if ($ressetcache < 0) {
339  $error = 'Failed to set cache for usecachekey='.$usecachekey.' result='.$ressetcache;
340  dol_syslog($error, LOG_ERR);
341  }
342  }
343 
344  if (empty($conf->global->MAIN_FORCELANGDIR)) {
345  break; // Break loop on each root dir. If a module has forced dir, we do not stop loop.
346  }
347  }
348  }
349  }
350  }
351 
352  // Now we complete with next file (fr_CA->fr_FR, es_MX->ex_ES, ...)
353  if ($alt == 0) {
354  // This function MUST NOT contains call to syslog
355  //dol_syslog("Translate::Load loading alternate translation file (to complete ".$this->defaultlang."/".$newdomain.".lang file)", LOG_DEBUG);
356  $langofdir = strtolower($langarray[0]).'_'.strtoupper($langarray[0]);
357  if ($langofdir == 'el_EL') {
358  $langofdir = 'el_GR'; // main parent for el_CY is not 'el_EL' but 'el_GR'
359  }
360  if ($langofdir == 'ar_AR') {
361  $langofdir = 'ar_SA'; // main parent for ar_EG is not 'ar_AR' but 'ar_SA'
362  }
363  $this->load($domain, $alt + 1, $stopafterdirection, $langofdir);
364  }
365 
366  // Now we complete with reference file (en_US)
367  if ($alt == 1) {
368  // This function MUST NOT contains call to syslog
369  //dol_syslog("Translate::Load loading alternate translation file (to complete ".$this->defaultlang."/".$newdomain.".lang file)", LOG_DEBUG);
370  $langofdir = 'en_US';
371  $this->load($domain, $alt + 1, $stopafterdirection, $langofdir);
372  }
373 
374  // We are in the pass of the reference file. No more files to scan to complete.
375  if ($alt == 2) {
376  if ($fileread) {
377  $this->_tab_loaded[$newdomain] = 1; // Set domain file as found so loaded
378  }
379 
380  if (empty($this->_tab_loaded[$newdomain])) {
381  $this->_tab_loaded[$newdomain] = 2; // Set this file as not found
382  }
383  }
384 
385  // This part is deprecated and replaced with table llx_overwrite_trans
386  // Kept for backward compatibility.
387  if (empty($loadfromfileonly)) {
388  $overwritekey = 'MAIN_OVERWRITE_TRANS_'.$this->defaultlang;
389  if (!empty($conf->global->$overwritekey)) { // Overwrite translation with key1:newstring1,key2:newstring2
390  // Overwrite translation with param MAIN_OVERWRITE_TRANS_xx_XX
391  $tmparray = explode(',', $conf->global->$overwritekey);
392  foreach ($tmparray as $tmp) {
393  $tmparray2 = explode(':', $tmp);
394  if (!empty($tmparray2[1])) {
395  $this->tab_translate[$tmparray2[0]] = $tmparray2[1];
396  }
397  }
398  }
399  }
400 
401  // Check to be sure that SeparatorDecimal differs from SeparatorThousand
402  if (!empty($this->tab_translate["SeparatorDecimal"]) && !empty($this->tab_translate["SeparatorThousand"])
403  && $this->tab_translate["SeparatorDecimal"] == $this->tab_translate["SeparatorThousand"]) {
404  $this->tab_translate["SeparatorThousand"] = '';
405  }
406 
407  return 1;
408  }
409 
422  public function loadFromDatabase($db)
423  {
424  global $conf;
425 
426  $domain = 'database';
427 
428  // Check parameters
429  if (empty($db)) {
430  return 0; // Database handler can't be used
431  }
432 
433  //dol_syslog("Translate::Load Start domain=".$domain." alt=".$alt." forcelangdir=".$forcelangdir." this->defaultlang=".$this->defaultlang);
434 
435  $newdomain = $domain;
436 
437  // Check cache
438  if (!empty($this->_tab_loaded[$newdomain])) { // File already loaded for this domain 'database'
439  //dol_syslog("Translate::Load already loaded for newdomain=".$newdomain);
440  return 0;
441  }
442 
443  $this->_tab_loaded[$newdomain] = 1; // We want to be sure this function is called once only for domain 'database'
444 
445  $fileread = 0;
446  $langofdir = $this->defaultlang;
447 
448  if (empty($langofdir)) { // This may occurs when load is called without setting the language and without providing a value for forcelangdir
449  dol_syslog("Error: ".get_class($this)."::loadFromDatabase was called but language was not set yet with langs->setDefaultLang(). Nothing will be loaded.", LOG_WARNING);
450  return -1;
451  }
452 
453  // TODO Move cache read out of loop on dirs or at least filelangexists
454  $found = false;
455 
456  // Enable caching of lang file in memory (not by default)
457  $usecachekey = '';
458  // Using a memcached server
459  if (!empty($conf->memcached->enabled) && !empty($conf->global->MEMCACHED_SERVER)) {
460  $usecachekey = $newdomain.'_'.$langofdir; // Should not contains special chars
461  } elseif (isset($conf->global->MAIN_OPTIMIZE_SPEED) && ($conf->global->MAIN_OPTIMIZE_SPEED & 0x02)) {
462  // Using cache with shmop. Speed gain: 40ms - Memory overusage: 200ko (Size of session cache file)
463  $usecachekey = $newdomain;
464  }
465 
466  if ($usecachekey) {
467  //dol_syslog('Translate::Load we will cache result into usecachekey '.$usecachekey);
468  //global $aaa; $aaa+=1;
469  //print $aaa." ".$usecachekey."\n";
470  require_once DOL_DOCUMENT_ROOT.'/core/lib/memory.lib.php';
471  $tmparray = dol_getcache($usecachekey);
472  if (is_array($tmparray) && count($tmparray)) {
473  $this->tab_translate += $tmparray; // Faster than array_merge($tmparray,$this->tab_translate). Note: If a value already exists into tab_translate, value into tmparaay is not added.
474  //print $newdomain."\n";
475  //var_dump($this->tab_translate);
476  $fileread = 1;
477  $found = true; // Found in dolibarr PHP cache
478  }
479  }
480 
481  if (!$found && !empty($conf->global->MAIN_ENABLE_OVERWRITE_TRANSLATION)) {
482  // Overwrite translation with database read
483  $sql = "SELECT transkey, transvalue FROM ".$db->prefix()."overwrite_trans where (lang='".$db->escape($this->defaultlang)."' OR lang IS NULL)";
484  $sql .= " AND entity IN (0, ".getEntity('overwrite_trans').")";
485  $sql .= $db->order("lang", "DESC");
486  $resql = $db->query($sql);
487 
488  if ($resql) {
489  $num = $db->num_rows($resql);
490  if ($num) {
491  if ($usecachekey) {
492  $tabtranslatedomain = array(); // To save lang content in cache
493  }
494 
495  $i = 0;
496  while ($i < $num) { // Ex: Need 225ms for all fgets on all lang file for Third party page. Same speed than file_get_contents
497  $obj = $db->fetch_object($resql);
498 
499  $key = $obj->transkey;
500  $value = $obj->transvalue;
501 
502  //print "Domain=$domain, found a string for $tab[0] with value $tab[1]<br>";
503  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)
504  // Convert some strings: Parse and render carriage returns. Also, change '\\s' int '\s' because transifex sync pull the string '\s' into string '\\s'
505  $this->tab_translate[$key] = str_replace(array('\\n', '\\\\s'), array("\n", '\s'), $value);
506 
507  if ($usecachekey) {
508  $tabtranslatedomain[$key] = $value; // To save lang content in cache
509  }
510  }
511 
512  $i++;
513  }
514 
515  $fileread = 1;
516 
517  // TODO Move cache write out of loop on dirs
518  // To save lang content for usecachekey into cache
519  if ($usecachekey && count($tabtranslatedomain)) {
520  $ressetcache = dol_setcache($usecachekey, $tabtranslatedomain);
521  if ($ressetcache < 0) {
522  $error = 'Failed to set cache for usecachekey='.$usecachekey.' result='.$ressetcache;
523  dol_syslog($error, LOG_ERR);
524  }
525  }
526  }
527  } else {
528  dol_print_error($db);
529  }
530  }
531 
532  if ($fileread) {
533  $this->_tab_loaded[$newdomain] = 1; // Set domain file as loaded
534  }
535 
536  if (empty($this->_tab_loaded[$newdomain])) {
537  $this->_tab_loaded[$newdomain] = 2; // Mark this case as not found (no lines found for language)
538  }
539 
540  return 1;
541  }
542 
549  public function isLoaded($domain)
550  {
551  return $this->_tab_loaded[$domain];
552  }
553 
565  private function getTradFromKey($key)
566  {
567  global $conf, $db;
568 
569  if (!is_string($key)) {
570  //xdebug_print_function_stack('ErrorBadValueForParamNotAString');
571  return 'ErrorBadValueForParamNotAString'; // Avoid multiple errors with code not using function correctly.
572  }
573 
574  $newstr = $key;
575  $reg = array();
576  if (preg_match('/^Civility([0-9A-Z]+)$/i', $key, $reg)) {
577  $newstr = $this->getLabelFromKey($db, $reg[1], 'c_civility', 'code', 'label');
578  } elseif (preg_match('/^Currency([A-Z][A-Z][A-Z])$/i', $key, $reg)) {
579  $newstr = $this->getLabelFromKey($db, $reg[1], 'c_currencies', 'code_iso', 'label');
580  } elseif (preg_match('/^SendingMethod([0-9A-Z]+)$/i', $key, $reg)) {
581  $newstr = $this->getLabelFromKey($db, $reg[1], 'c_shipment_mode', 'code', 'libelle');
582  } elseif (preg_match('/^PaymentType(?:Short)?([0-9A-Z]+)$/i', $key, $reg)) {
583  $newstr = $this->getLabelFromKey($db, $reg[1], 'c_paiement', 'code', 'libelle', '', 1);
584  } elseif (preg_match('/^OppStatus([0-9A-Z]+)$/i', $key, $reg)) {
585  $newstr = $this->getLabelFromKey($db, $reg[1], 'c_lead_status', 'code', 'label');
586  } elseif (preg_match('/^OrderSource([0-9A-Z]+)$/i', $key, $reg)) {
587  // TODO OrderSourceX must be replaced with content of table llx_c_input_reason or llx_c_input_method
588  //$newstr=$this->getLabelFromKey($db,$reg[1],'llx_c_input_reason','code','label');
589  }
590 
591  /* Disabled. There is too many cases where translation of $newstr is not defined is normal (like when output with setEventMessage an already translated string)
592  if (getDolGlobalInt('MAIN_FEATURES_LEVEL') >= 2)
593  {
594  dol_syslog(__METHOD__." MAIN_FEATURES_LEVEL=DEVELOP: missing translation for key '".$newstr."' in ".$_SERVER["PHP_SELF"], LOG_DEBUG);
595  }*/
596 
597  return $newstr;
598  }
599 
600 
614  public function trans($key, $param1 = '', $param2 = '', $param3 = '', $param4 = '', $maxsize = 0)
615  {
616  global $conf;
617 
618  if (!empty($this->tab_translate[$key])) { // Translation is available
619  $str = $this->tab_translate[$key];
620 
621  // Make some string replacement after translation
622  $replacekey = 'MAIN_REPLACE_TRANS_'.$this->defaultlang;
623  if (!empty($conf->global->$replacekey)) { // Replacement translation variable with string1:newstring1;string2:newstring2
624  $tmparray = explode(';', $conf->global->$replacekey);
625  foreach ($tmparray as $tmp) {
626  $tmparray2 = explode(':', $tmp);
627  $str = preg_replace('/'.preg_quote($tmparray2[0]).'/', $tmparray2[1], $str);
628  }
629  }
630 
631  // We replace some HTML tags by __xx__ to avoid having them encoded by htmlentities because
632  // we want to keep '"' '<b>' '</b>' '<strong' '</strong>' '<a ' '</a>' '<br>' '< ' '<span' '</span>' that are reliable HTML tags inside translation strings.
633  $str = str_replace(
634  array('"', '<b>', '</b>', '<u>', '</u>', '<i', '</i>', '<center>', '</center>', '<strong>', '</strong>', '<a ', '</a>', '<br>', '<span', '</span>', '< ', '>'), // We accept '< ' but not '<'. We can accept however '>'
635  array('__quot__', '__tagb__', '__tagbend__', '__tagu__', '__taguend__', '__tagi__', '__tagiend__', '__tagcenter__', '__tagcenterend__', '__tagb__', '__tagbend__', '__taga__', '__tagaend__', '__tagbr__', '__tagspan__', '__tagspanend__', '__ltspace__', '__gt__'),
636  $str
637  );
638 
639  if (strpos($key, 'Format') !== 0) {
640  try {
641  $str = sprintf($str, $param1, $param2, $param3, $param4); // Replace %s and %d except for FormatXXX strings.
642  } catch (Exception $e) {
643  // No exception managed
644  }
645  }
646 
647  // Crypt string into HTML
648  $str = htmlentities($str, ENT_COMPAT, $this->charset_output); // Do not convert simple quotes in translation (strings in html are embraced by "). Use dol_escape_htmltag around text in HTML content
649 
650  // Restore reliable HTML tags into original translation string
651  $str = str_replace(
652  array('__quot__', '__tagb__', '__tagbend__', '__tagu__', '__taguend__', '__tagi__', '__tagiend__', '__tagcenter__', '__tagcenterend__', '__taga__', '__tagaend__', '__tagbr__', '__tagspan__', '__tagspanend__', '__ltspace__', '__gt__'),
653  array('"', '<b>', '</b>', '<u>', '</u>', '<i', '</i>', '<center>', '</center>', '<a ', '</a>', '<br>', '<span', '</span>', '< ', '>'),
654  $str
655  );
656 
657  if ($maxsize) {
658  $str = dol_trunc($str, $maxsize);
659  }
660 
661  return $str;
662  } else { // Translation is not available
663  //if ($key[0] == '$') { return dol_eval($key, 1, 1, '1'); }
664  return $this->getTradFromKey($key);
665  }
666  }
667 
668 
683  public function transnoentities($key, $param1 = '', $param2 = '', $param3 = '', $param4 = '', $param5 = '')
684  {
685  return $this->convToOutputCharset($this->transnoentitiesnoconv($key, $param1, $param2, $param3, $param4, $param5));
686  }
687 
688 
704  public function transnoentitiesnoconv($key, $param1 = '', $param2 = '', $param3 = '', $param4 = '', $param5 = '')
705  {
706  global $conf;
707 
708  if (!empty($this->tab_translate[$key])) { // Translation is available
709  $str = $this->tab_translate[$key];
710 
711  // Make some string replacement after translation
712  $replacekey = 'MAIN_REPLACE_TRANS_'.$this->defaultlang;
713  if (!empty($conf->global->$replacekey)) { // Replacement translation variable with string1:newstring1;string2:newstring2
714  $tmparray = explode(';', $conf->global->$replacekey);
715  foreach ($tmparray as $tmp) {
716  $tmparray2 = explode(':', $tmp);
717  $str = preg_replace('/'.preg_quote($tmparray2[0]).'/', $tmparray2[1], $str);
718  }
719  }
720 
721  if (!preg_match('/^Format/', $key)) {
722  //print $str;
723  $str = sprintf($str, $param1, $param2, $param3, $param4, $param5); // Replace %s and %d except for FormatXXX strings.
724  }
725 
726  return $str;
727  } else {
728  /*if ($key[0] == '$') {
729  return dol_eval($key, 1, 1, '1');
730  }*/
731  return $this->getTradFromKey($key);
732  }
733  }
734 
735 
744  public function transcountry($str, $countrycode)
745  {
746  if (!empty($this->tab_translate["$str$countrycode"])) {
747  return $this->trans("$str$countrycode");
748  } else {
749  return $this->trans($str);
750  }
751  }
752 
753 
762  public function transcountrynoentities($str, $countrycode)
763  {
764  if (!empty($this->tab_translate["$str$countrycode"])) {
765  return $this->transnoentities("$str$countrycode");
766  } else {
767  return $this->transnoentities($str);
768  }
769  }
770 
771 
779  public function convToOutputCharset($str, $pagecodefrom = 'UTF-8')
780  {
781  if ($pagecodefrom == 'ISO-8859-1' && $this->charset_output == 'UTF-8') {
782  $str = utf8_encode($str);
783  }
784  if ($pagecodefrom == 'UTF-8' && $this->charset_output == 'ISO-8859-1') {
785  $str = utf8_decode(str_replace('€', chr(128), $str));
786  }
787  return $str;
788  }
789 
790 
791  // phpcs:disable PEAR.NamingConventions.ValidFunctionName.ScopeNotCamelCaps
801  public function get_available_languages($langdir = DOL_DOCUMENT_ROOT, $maxlength = 0, $usecode = 0, $mainlangonly = 0)
802  {
803  // phpcs:enable
804  global $conf;
805 
806  $this->load("languages");
807 
808  // We scan directory langs to detect available languages
809  $handle = opendir($langdir."/langs");
810  $langs_available = array();
811  while ($dir = trim(readdir($handle))) {
812  $regs = array();
813  if (preg_match('/^([a-z]+)_([A-Z]+)/i', $dir, $regs)) {
814  // We must keep only main languages
815  if ($mainlangonly) {
816  $arrayofspecialmainlanguages = array(
817  'en'=>'en_US',
818  'am'=>'am_ET',
819  'ar'=>'ar_SA',
820  'bn'=>'bn_DB',
821  'bs'=>'bs_BA',
822  'ca'=>'ca_ES',
823  'cs'=>'cs_CZ',
824  'da'=>'da_DK',
825  'et'=>'et_EE',
826  'el'=>'el_GR',
827  'eu'=>'eu_ES',
828  'fa'=>'fa_IR',
829  'he'=>'he_IL',
830  'ka'=>'ka_GE',
831  'km'=>'km_KH',
832  'kn'=>'kn_IN',
833  'ko'=>'ko_KR',
834  'ja'=>'ja_JP',
835  'lo'=>'lo_LA',
836  'nb'=>'nb_NO',
837  'sq'=>'sq_AL',
838  'sr'=>'sr_RS',
839  'sv'=>'sv_SE',
840  'sl'=>'sl_SI',
841  'uk'=>'uk_UA',
842  'vi'=>'vi_VN',
843  'zh'=>'zh_CN'
844  );
845  if (strtolower($regs[1]) != strtolower($regs[2]) && !in_array($dir, $arrayofspecialmainlanguages)) {
846  continue;
847  }
848  }
849  // We must keep only languages into MAIN_LANGUAGES_ALLOWED
850  if (!empty($conf->global->MAIN_LANGUAGES_ALLOWED) && !in_array($dir, explode(',', $conf->global->MAIN_LANGUAGES_ALLOWED))) {
851  continue;
852  }
853 
854  if ($usecode == 2) {
855  $langs_available[$dir] = $dir;
856  }
857 
858  if ($usecode == 1 || !empty($conf->global->MAIN_SHOW_LANGUAGE_CODE)) {
859  $langs_available[$dir] = $dir.': '.dol_trunc($this->trans('Language_'.$dir), $maxlength);
860  } else {
861  $langs_available[$dir] = $this->trans('Language_'.$dir);
862  }
863  if ($mainlangonly) {
864  $langs_available[$dir] = str_replace(' (United States)', '', $langs_available[$dir]);
865  }
866  }
867  }
868  return $langs_available;
869  }
870 
871 
872  // phpcs:disable PEAR.NamingConventions.ValidFunctionName.ScopeNotCamelCaps
880  public function file_exists($filename, $searchalt = 0)
881  {
882  // phpcs:enable
883  // Test si fichier dans repertoire de la langue
884  foreach ($this->dir as $searchdir) {
885  if (is_readable(dol_osencode($searchdir."/langs/".$this->defaultlang."/".$filename))) {
886  return true;
887  }
888 
889  if ($searchalt) {
890  // Test si fichier dans repertoire de la langue alternative
891  if ($this->defaultlang != "en_US") {
892  $filenamealt = $searchdir."/langs/en_US/".$filename;
893  }
894  //else $filenamealt = $searchdir."/langs/fr_FR/".$filename;
895  if (is_readable(dol_osencode($filenamealt))) {
896  return true;
897  }
898  }
899  }
900 
901  return false;
902  }
903 
904 
916  public function getLabelFromNumber($number, $isamount = '')
917  {
918  global $conf;
919 
920  $newnumber = $number;
921 
922  $dirsubstitutions = array_merge(array(), $conf->modules_parts['substitutions']);
923  foreach ($dirsubstitutions as $reldir) {
924  $dir = dol_buildpath($reldir, 0);
925  $newdir = dol_osencode($dir);
926 
927  // Check if directory exists
928  if (!is_dir($newdir)) {
929  continue; // We must not use dol_is_dir here, function may not be loaded
930  }
931 
932  $fonc = 'numberwords';
933  if (file_exists($newdir.'/functions_'.$fonc.'.lib.php')) {
934  include_once $newdir.'/functions_'.$fonc.'.lib.php';
935  $newnumber = numberwords_getLabelFromNumber($this, $number, $isamount);
936  break;
937  }
938  }
939 
940  return $newnumber;
941  }
942 
943 
959  public function getLabelFromKey($db, $key, $tablename, $fieldkey, $fieldlabel, $keyforselect = '', $filteronentity = 0)
960  {
961  // If key empty
962  if ($key == '') {
963  return '';
964  }
965  // Test should be useless because the 3 variables are never set from user input but we keep it in case of.
966  if (preg_match('/[^0-9A-Z_]/i', $tablename) || preg_match('/[^0-9A-Z_]/i', $fieldkey) || preg_match('/[^0-9A-Z_]/i', $fieldlabel)) {
967  $this->error = 'Bad value for parameter tablename, fieldkey or fieldlabel';
968  return -1;
969  }
970 
971  //print 'param: '.$key.'-'.$keydatabase.'-'.$this->trans($key); exit;
972 
973  // Check if a translation is available (Note: this can call getTradFromKey that can call getLabelFromKey)
974  $tmp = $this->transnoentitiesnoconv($key);
975  if ($tmp != $key && $tmp != 'ErrorBadValueForParamNotAString') {
976  return $tmp; // Found in language array
977  }
978 
979  // Check in cache
980  if (isset($this->cache_labels[$tablename][$key])) { // Can be defined to 0 or ''
981  return $this->cache_labels[$tablename][$key]; // Found in cache
982  }
983 
984  // Not found in loaded language file nor in cache. So we will take the label into database.
985  $sql = "SELECT ".$fieldlabel." as label";
986  $sql .= " FROM ".$db->prefix().$tablename;
987  $sql .= " WHERE ".$fieldkey." = '".$db->escape($keyforselect ? $keyforselect : $key)."'";
988  if ($filteronentity) {
989  $sql .= " AND entity IN (".getEntity($tablename).')';
990  }
991  dol_syslog(get_class($this).'::getLabelFromKey', LOG_DEBUG);
992  $resql = $db->query($sql);
993  if ($resql) {
994  $obj = $db->fetch_object($resql);
995  if ($obj) {
996  $this->cache_labels[$tablename][$key] = $obj->label;
997  } else {
998  $this->cache_labels[$tablename][$key] = $key;
999  }
1000 
1001  $db->free($resql);
1002  return $this->cache_labels[$tablename][$key];
1003  } else {
1004  $this->error = $db->lasterror();
1005  return -1;
1006  }
1007  }
1008 
1009 
1019  public function getCurrencyAmount($currency_code, $amount)
1020  {
1021  $symbol = $this->getCurrencySymbol($currency_code);
1022 
1023  if (in_array($currency_code, array('USD'))) {
1024  return $symbol.$amount;
1025  } else {
1026  return $amount.$symbol;
1027  }
1028  }
1029 
1038  public function getCurrencySymbol($currency_code, $forceloadall = 0)
1039  {
1040  $currency_sign = ''; // By default return iso code
1041 
1042  if (function_exists("mb_convert_encoding")) {
1043  $this->loadCacheCurrencies($forceloadall ? '' : $currency_code);
1044 
1045  if (isset($this->cache_currencies[$currency_code]) && !empty($this->cache_currencies[$currency_code]['unicode']) && is_array($this->cache_currencies[$currency_code]['unicode'])) {
1046  foreach ($this->cache_currencies[$currency_code]['unicode'] as $unicode) {
1047  $currency_sign .= mb_convert_encoding("&#{$unicode};", "UTF-8", 'HTML-ENTITIES');
1048  }
1049  }
1050  }
1051 
1052  return ($currency_sign ? $currency_sign : $currency_code);
1053  }
1054 
1061  public function loadCacheCurrencies($currency_code)
1062  {
1063  global $db;
1064 
1065  if ($this->cache_currencies_all_loaded) {
1066  return 0; // Cache already loaded for all
1067  }
1068  if (!empty($currency_code) && isset($this->cache_currencies[$currency_code])) {
1069  return 0; // Cache already loaded for the currency
1070  }
1071 
1072  $sql = "SELECT code_iso, label, unicode";
1073  $sql .= " FROM ".$db->prefix()."c_currencies";
1074  $sql .= " WHERE active = 1";
1075  if (!empty($currency_code)) {
1076  $sql .= " AND code_iso = '".$db->escape($currency_code)."'";
1077  }
1078  //$sql.= " ORDER BY code_iso ASC"; // Not required, a sort is done later
1079 
1080  dol_syslog(get_class($this).'::loadCacheCurrencies', LOG_DEBUG);
1081  $resql = $db->query($sql);
1082  if ($resql) {
1083  $this->load("dict");
1084  $label = array();
1085  if (!empty($currency_code)) {
1086  foreach ($this->cache_currencies as $key => $val) {
1087  $label[$key] = $val['label']; // Label in already loaded cache
1088  }
1089  }
1090 
1091  $num = $db->num_rows($resql);
1092  $i = 0;
1093  while ($i < $num) {
1094  $obj = $db->fetch_object($resql);
1095  if ($obj) {
1096  // If a translation exists, we use it lese we use the default label
1097  $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 : ''));
1098  $this->cache_currencies[$obj->code_iso]['unicode'] = (array) json_decode((empty($obj->unicode) ? '' : $obj->unicode), true);
1099  $label[$obj->code_iso] = $this->cache_currencies[$obj->code_iso]['label'];
1100  }
1101  $i++;
1102  }
1103  if (empty($currency_code)) {
1104  $this->cache_currencies_all_loaded = true;
1105  }
1106  //print count($label).' '.count($this->cache_currencies);
1107 
1108  // Resort cache
1109  array_multisort($label, SORT_ASC, $this->cache_currencies);
1110  //var_dump($this->cache_currencies); $this->cache_currencies is now sorted onto label
1111  return $num;
1112  } else {
1113  dol_print_error($db);
1114  return -1;
1115  }
1116  }
1117 
1118  // phpcs:disable PEAR.NamingConventions.ValidFunctionName.ScopeNotCamelCaps
1126  {
1127  // phpcs:enable
1128  $substitutionarray = array();
1129 
1130  foreach ($this->tab_translate as $code => $label) {
1131  $substitutionarray['lang_'.$code] = $label;
1132  $substitutionarray['__('.$code.')__'] = $label;
1133  }
1134 
1135  return $substitutionarray;
1136  }
1137 }
Class to manage translations.
transnoentities($key, $param1='', $param2='', $param3='', $param4='', $param5='')
Return translated value of a text string If there is no match for this text, we look in alternative f...
transnoentitiesnoconv($key, $param1='', $param2='', $param3='', $param4='', $param5='')
Return translated value of a text string If there is no match for this text, we look in alternative f...
getTradFromKey($key)
Return translated value of key for special keys ("Currency...", "Civility...", ......
getLabelFromNumber($number, $isamount='')
Return full text translated to language label for a key.
getCurrencyAmount($currency_code, $amount)
Return a currency code into its symbol.
get_translations_for_substitutions()
Return an array with content of all loaded translation keys (found into this->tab_translate) so we ge...
getLabelFromKey($db, $key, $tablename, $fieldkey, $fieldlabel, $keyforselect='', $filteronentity=0)
Return a label for a key.
get_available_languages($langdir=DOL_DOCUMENT_ROOT, $maxlength=0, $usecode=0, $mainlangonly=0)
Return list of all available languages.
loadCacheCurrencies($currency_code)
Load into the cache this->cache_currencies, all currencies.
setDefaultLang($srclang='en_US')
Set accessor for this->defaultlang.
file_exists($filename, $searchalt=0)
Return if a filename $filename exists for current language (or alternate language)
transcountry($str, $countrycode)
Return translation of a key depending on country.
isLoaded($domain)
Get information with result of loading data for domain.
load($domain, $alt=0, $stopafterdirection=0, $forcelangdir='', $loadfromfileonly=0, $forceloadifalreadynotfound=0)
Load translation key-value for a particular file, into a memory array.
trans($key, $param1='', $param2='', $param3='', $param4='', $maxsize=0)
Return text translated of text received as parameter (and encode it into HTML) If there is no match f...
transcountrynoentities($str, $countrycode)
Retourne la version traduite du texte passe en parametre complete du code pays.
getCurrencySymbol($currency_code, $forceloadall=0)
Return a currency code into its symbol.
loadLangs($domains)
Load translation files.
convToOutputCharset($str, $pagecodefrom='UTF-8')
Convert a string into output charset (this->charset_output that should be defined to conf->file->char...
loadFromDatabase($db)
Load translation key-value from database into a memory array.
getDefaultLang($mode=0)
Return active language code for current user It's an accessor for this->defaultlang.
__construct($dir, $conf)
Constructor.
if(isModEnabled('facture') &&!empty($user->rights->facture->lire)) if((isModEnabled('fournisseur') &&empty($conf->global->MAIN_USE_NEW_SUPPLIERMOD) && $user->hasRight("fournisseur", "facture", "lire"))||(isModEnabled('supplier_invoice') && $user->hasRight("supplier_invoice", "lire"))) if(isModEnabled('don') &&!empty($user->rights->don->lire)) if(isModEnabled('tax') &&!empty($user->rights->tax->charges->lire)) if(isModEnabled('facture') &&isModEnabled('commande') && $user->hasRight("commande", "lire") &&empty($conf->global->WORKFLOW_DISABLE_CREATE_INVOICE_FROM_ORDER)) $resql
Social contributions to pay.
Definition: index.php:745
dol_osencode($str)
Return a string encoded into OS filesystem encoding.
dol_print_error($db='', $error='', $errors=null)
Displays error message system with all the information to facilitate the diagnosis and the escalation...
dol_buildpath($path, $type=0, $returnemptyifnotfound=0)
Return path of url or filesystem.
dol_trunc($string, $size=40, $trunc='right', $stringencoding='UTF-8', $nodot=0, $display=0)
Truncate a string to a particular length adding '…' if string larger than length.
dol_syslog($message, $level=LOG_INFO, $ident=0, $suffixinfilename='', $restricttologhandler='', $logcontext=null)
Write log message into outputs.
dol_setcache($memoryid, $data, $expire=0)
Save data into a memory area shared by all users, all sessions on server.
Definition: memory.lib.php:68
dol_getcache($memoryid)
Read a memory area shared by all users, all sessions on server.
Definition: memory.lib.php:140