dolibarr  19.0.0-dev
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  public $origlang;
45  public $error;
46  public $errors = array();
47 
48 
55  public function __construct($dir, $conf)
56  {
57  if (!empty($conf->file->character_set_client)) {
58  $this->charset_output = $conf->file->character_set_client; // If charset output is forced
59  }
60  if ($dir) {
61  $this->dir = array($dir);
62  } else {
63  $this->dir = $conf->file->dol_document_root;
64  }
65  }
66 
67 
74  public function setDefaultLang($srclang = 'en_US')
75  {
76  global $conf;
77 
78  //dol_syslog(get_class($this)."::setDefaultLang srclang=".$srclang,LOG_DEBUG);
79 
80  // If a module ask to force a priority on langs directories (to use its own lang files)
81  if (!empty($conf->global->MAIN_FORCELANGDIR)) {
82  $more = array();
83  $i = 0;
84  foreach ($conf->file->dol_document_root as $dir) {
85  $newdir = $dir . $conf->global->MAIN_FORCELANGDIR; // For example $conf->global->MAIN_FORCELANGDIR is '/mymodule' meaning we search files into '/mymodule/langs/xx_XX'
86  if (!in_array($newdir, $this->dir)) {
87  $more['module_' . $i] = $newdir;
88  $i++; // We add the forced dir into the array $more. Just after, we add entries into $more to list of lang dir $this->dir.
89  }
90  }
91  $this->dir = array_merge($more, $this->dir); // Forced dir ($more) are before standard dirs ($this->dir)
92  }
93 
94  $this->origlang = $srclang;
95 
96  if (empty($srclang) || $srclang == 'auto') {
97  // $_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
98  $langpref = empty($_SERVER['HTTP_ACCEPT_LANGUAGE']) ? '' : $_SERVER['HTTP_ACCEPT_LANGUAGE'];
99  $langpref = preg_replace("/;([^,]*)/i", "", $langpref); // Remove the 'q=x.y,' part
100  $langpref = str_replace("-", "_", $langpref);
101  $langlist = preg_split("/[;,]/", $langpref);
102  $codetouse = preg_replace('/[^_a-zA-Z]/', '', $langlist[0]);
103  } else {
104  $codetouse = $srclang;
105  }
106 
107  // We redefine $srclang
108  $langpart = explode("_", $codetouse);
109  //print "Short code before _ : ".$langpart[0].' / Short code after _ : '.$langpart[1].'<br>';
110  if (!empty($langpart[1])) { // If it's for a codetouse that is a long code xx_YY
111  // Array force long code from first part, even if long code is defined
112  $longforshort = array('ar' => 'ar_SA');
113  $longforshortexcep = array('ar_EG');
114  if (isset($longforshort[strtolower($langpart[0])]) && !in_array($codetouse, $longforshortexcep)) {
115  $srclang = $longforshort[strtolower($langpart[0])];
116  } elseif (!is_numeric($langpart[1])) { // Second part YY may be a numeric with some Chrome browser
117  $srclang = strtolower($langpart[0]) . "_" . strtoupper($langpart[1]);
118  $longforlong = array('no_nb' => 'nb_NO');
119  if (isset($longforlong[strtolower($srclang)])) {
120  $srclang = $longforlong[strtolower($srclang)];
121  }
122  } else {
123  $srclang = strtolower($langpart[0]) . "_" . strtoupper($langpart[0]);
124  }
125  } else { // If it's for a codetouse that is a short code xx
126  // Array to convert short lang code into long code.
127  $longforshort = array(
128  'am' => 'am_ET', 'ar' => 'ar_SA', 'bn' => 'bn_DB', 'el' => 'el_GR', 'ca' => 'ca_ES', 'cs' => 'cs_CZ', 'en' => 'en_US', 'fa' => 'fa_IR',
129  'gl' => 'gl_ES', 'he' => 'he_IL', 'hi' => 'hi_IN', 'ja' => 'ja_JP',
130  'ka' => 'ka_GE', 'km' => 'km_KH', 'kn' => 'kn_IN', 'ko' => 'ko_KR', 'lo' => 'lo_LA', 'nb' => 'nb_NO', 'no' => 'nb_NO', 'ne' => 'ne_NP',
131  'sl' => 'sl_SI', 'sq' => 'sq_AL', 'sr' => 'sr_RS', 'sv' => 'sv_SE', 'uk' => 'uk_UA', 'vi' => 'vi_VN', 'zh' => 'zh_CN'
132  );
133  if (isset($longforshort[strtolower($langpart[0])])) {
134  $srclang = $longforshort[strtolower($langpart[0])];
135  } elseif (!empty($langpart[0])) {
136  $srclang = strtolower($langpart[0]) . "_" . strtoupper($langpart[0]);
137  } else {
138  $srclang = 'en_US';
139  }
140  }
141 
142  $this->defaultlang = $srclang;
143  $this->shortlang = substr($srclang, 0, 2);
144  //print 'this->defaultlang='.$this->defaultlang;
145  }
146 
147 
155  public function getDefaultLang($mode = 0)
156  {
157  if (empty($mode)) {
158  return $this->defaultlang;
159  } else {
160  return substr($this->defaultlang, 0, 2);
161  }
162  }
163 
164 
171  public function loadLangs($domains)
172  {
173  $loaded = 0;
174  foreach ($domains as $domain) {
175  $result = $this->load($domain);
176  if ($result > 0) {
177  $loaded = $result;
178  } elseif ($result < 0) {
179  return $result;
180  }
181  }
182  return $loaded;
183  }
184 
206  public function load($domain, $alt = 0, $stopafterdirection = 0, $forcelangdir = '', $loadfromfileonly = 0, $forceloadifalreadynotfound = 0)
207  {
208  global $conf, $db;
209 
210  //dol_syslog("Translate::Load Start domain=".$domain." alt=".$alt." forcelangdir=".$forcelangdir." this->defaultlang=".$this->defaultlang);
211 
212  // Check parameters
213  if (empty($domain)) {
214  dol_print_error('', get_class($this) . "::Load ErrorWrongParameters");
215  return -1;
216  }
217  if ($this->defaultlang === 'none_NONE') {
218  return 0; // Special language code to not translate keys
219  }
220 
221 
222  // Load $this->tab_translate[] from database
223  if (empty($loadfromfileonly) && count($this->tab_translate) == 0) {
224  $this->loadFromDatabase($db); // No translation was never loaded yet, so we load database.
225  }
226 
227 
228  $newdomain = $domain;
229  $modulename = '';
230 
231  // Search if a module directory name is provided into lang file name
232  $regs = array();
233  if (preg_match('/^([^@]+)@([^@]+)$/i', $domain, $regs)) {
234  $newdomain = $regs[1];
235  $modulename = $regs[2];
236  }
237 
238  // Check cache
239  if (
240  !empty($this->_tab_loaded[$newdomain])
241  && ($this->_tab_loaded[$newdomain] != 2 || empty($forceloadifalreadynotfound))
242  ) { // File already loaded and found and not forced for this domain
243  //dol_syslog("Translate::Load already loaded for newdomain=".$newdomain);
244  return 0;
245  }
246 
247  $fileread = 0;
248  $langofdir = (empty($forcelangdir) ? $this->defaultlang : $forcelangdir);
249 
250  // Redefine alt
251  $langarray = explode('_', $langofdir);
252  if ($alt < 1 && isset($langarray[1]) && (strtolower($langarray[0]) == strtolower($langarray[1]) || in_array(strtolower($langofdir), array('el_gr')))) {
253  $alt = 1;
254  }
255  if ($alt < 2 && strtolower($langofdir) == 'en_us') {
256  $alt = 2;
257  }
258 
259  if (empty($langofdir)) { // This may occurs when load is called without setting the language and without providing a value for forcelangdir
260  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);
261  return -1;
262  }
263 
264  foreach ($this->dir as $searchdir) {
265  // Directory of translation files
266  $file_lang = $searchdir . ($modulename ? '/' . $modulename : '') . "/langs/" . $langofdir . "/" . $newdomain . ".lang";
267  $file_lang_osencoded = dol_osencode($file_lang);
268 
269  $filelangexists = is_file($file_lang_osencoded);
270 
271  //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);
272  //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";
273 
274  if ($filelangexists) {
275  // TODO Move cache read out of loop on dirs or at least filelangexists
276  $found = false;
277 
278  // Enable caching of lang file in memory (not by default)
279  $usecachekey = '';
280  // Using a memcached server
281  if (isModEnabled('memcached') && !empty($conf->global->MEMCACHED_SERVER)) {
282  $usecachekey = $newdomain . '_' . $langofdir . '_' . md5($file_lang); // Should not contains special chars
283  } elseif (isset($conf->global->MAIN_OPTIMIZE_SPEED) && ($conf->global->MAIN_OPTIMIZE_SPEED & 0x02)) {
284  // Using cache with shmop. Speed gain: 40ms - Memory overusage: 200ko (Size of session cache file)
285  $usecachekey = $newdomain;
286  }
287 
288  if ($usecachekey) {
289  //dol_syslog('Translate::Load we will cache result into usecachekey '.$usecachekey);
290  //global $aaa; $aaa+=1;
291  //print $aaa." ".$usecachekey."\n";
292  require_once DOL_DOCUMENT_ROOT . '/core/lib/memory.lib.php';
293  $tmparray = dol_getcache($usecachekey);
294  if (is_array($tmparray) && count($tmparray)) {
295  $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.
296  //print $newdomain."\n";
297  //var_dump($this->tab_translate);
298  if ($alt == 2) {
299  $fileread = 1;
300  }
301  $found = true; // Found in dolibarr PHP cache
302  }
303  }
304 
305  if (!$found) {
306  if ($fp = @fopen($file_lang, "rt")) {
307  if ($usecachekey) {
308  $tabtranslatedomain = array(); // To save lang content in cache
309  }
310 
316  while ($line = fscanf($fp, "%[^= ]%*[ =]%[^\n\r]")) {
317  if (isset($line[1])) {
318  list($key, $value) = $line;
319  //if ($domain == 'orders') print "Domain=$domain, found a string for $tab[0] with value $tab[1]. Currently in cache ".$this->tab_translate[$key]."<br>";
320  //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>";
321  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)
322  if ($key == 'DIRECTION') { // This is to declare direction of language
323  if ($alt < 2 || empty($this->tab_translate[$key])) { // We load direction only for primary files or if not yet loaded
324  $this->tab_translate[$key] = $value;
325  if ($stopafterdirection) {
326  break; // We do not save tab if we stop after DIRECTION
327  } elseif ($usecachekey) {
328  $tabtranslatedomain[$key] = $value;
329  }
330  }
331  } elseif ($key[0] == '#') {
332  continue;
333  } else {
334  // Convert some strings: Parse and render carriage returns. Also, change '\\s' into '\s' because transifex sync pull the string '\s' into string '\\s'
335  $this->tab_translate[$key] = str_replace(array('\\n', '\\\\s'), array("\n", '\s'), $value);
336  if ($usecachekey) {
337  $tabtranslatedomain[$key] = $value;
338  } // To save lang content in cache
339  }
340  }
341  }
342  }
343  fclose($fp);
344  $fileread = 1;
345 
346  // TODO Move cache write out of loop on dirs
347  // To save lang content for usecachekey into cache
348  if ($usecachekey && count($tabtranslatedomain)) {
349  $ressetcache = dol_setcache($usecachekey, $tabtranslatedomain);
350  if ($ressetcache < 0) {
351  $error = 'Failed to set cache for usecachekey=' . $usecachekey . ' result=' . $ressetcache;
352  dol_syslog($error, LOG_ERR);
353  }
354  }
355 
356  if (empty($conf->global->MAIN_FORCELANGDIR)) {
357  break; // Break loop on each root dir. If a module has forced dir, we do not stop loop.
358  }
359  }
360  }
361  }
362  }
363 
364  // Now we complete with next file (fr_CA->fr_FR, es_MX->ex_ES, ...)
365  if ($alt == 0) {
366  // This function MUST NOT contains call to syslog
367  //dol_syslog("Translate::Load loading alternate translation file (to complete ".$this->defaultlang."/".$newdomain.".lang file)", LOG_DEBUG);
368  $langofdir = strtolower($langarray[0]) . '_' . strtoupper($langarray[0]);
369  if ($langofdir == 'el_EL') {
370  $langofdir = 'el_GR'; // main parent for el_CY is not 'el_EL' but 'el_GR'
371  }
372  if ($langofdir == 'ar_AR') {
373  $langofdir = 'ar_SA'; // main parent for ar_EG is not 'ar_AR' but 'ar_SA'
374  }
375  $this->load($domain, $alt + 1, $stopafterdirection, $langofdir);
376  }
377 
378  // Now we complete with reference file (en_US)
379  if ($alt == 1) {
380  // This function MUST NOT contains call to syslog
381  //dol_syslog("Translate::Load loading alternate translation file (to complete ".$this->defaultlang."/".$newdomain.".lang file)", LOG_DEBUG);
382  $langofdir = 'en_US';
383  $this->load($domain, $alt + 1, $stopafterdirection, $langofdir);
384  }
385 
386  // We are in the pass of the reference file. No more files to scan to complete.
387  if ($alt == 2) {
388  if ($fileread) {
389  $this->_tab_loaded[$newdomain] = 1; // Set domain file as found so loaded
390  }
391 
392  if (empty($this->_tab_loaded[$newdomain])) {
393  $this->_tab_loaded[$newdomain] = 2; // Set this file as not found
394  }
395  }
396 
397  // This part is deprecated and replaced with table llx_overwrite_trans
398  // Kept for backward compatibility.
399  if (empty($loadfromfileonly)) {
400  $overwritekey = 'MAIN_OVERWRITE_TRANS_' . $this->defaultlang;
401  if (!empty($conf->global->$overwritekey)) { // Overwrite translation with key1:newstring1,key2:newstring2
402  // Overwrite translation with param MAIN_OVERWRITE_TRANS_xx_XX
403  $tmparray = explode(',', $conf->global->$overwritekey);
404  foreach ($tmparray as $tmp) {
405  $tmparray2 = explode(':', $tmp);
406  if (!empty($tmparray2[1])) {
407  $this->tab_translate[$tmparray2[0]] = $tmparray2[1];
408  }
409  }
410  }
411  }
412 
413  // Check to be sure that SeparatorDecimal differs from SeparatorThousand
414  if (
415  !empty($this->tab_translate["SeparatorDecimal"]) && !empty($this->tab_translate["SeparatorThousand"])
416  && $this->tab_translate["SeparatorDecimal"] == $this->tab_translate["SeparatorThousand"]
417  ) {
418  $this->tab_translate["SeparatorThousand"] = '';
419  }
420 
421  return 1;
422  }
423 
436  public function loadFromDatabase($db)
437  {
438  global $conf;
439 
440  $domain = 'database';
441 
442  // Check parameters
443  if (empty($db)) {
444  return 0; // Database handler can't be used
445  }
446 
447  //dol_syslog("Translate::Load Start domain=".$domain." alt=".$alt." forcelangdir=".$forcelangdir." this->defaultlang=".$this->defaultlang);
448 
449  $newdomain = $domain;
450 
451  // Check cache
452  if (!empty($this->_tab_loaded[$newdomain])) { // File already loaded for this domain 'database'
453  //dol_syslog("Translate::Load already loaded for newdomain=".$newdomain);
454  return 0;
455  }
456 
457  $this->_tab_loaded[$newdomain] = 1; // We want to be sure this function is called once only for domain 'database'
458 
459  $fileread = 0;
460  $langofdir = $this->defaultlang;
461 
462  if (empty($langofdir)) { // This may occurs when load is called without setting the language and without providing a value for forcelangdir
463  dol_syslog("Error: " . get_class($this) . "::loadFromDatabase was called but language was not set yet with langs->setDefaultLang(). Nothing will be loaded.", LOG_WARNING);
464  return -1;
465  }
466 
467  // TODO Move cache read out of loop on dirs or at least filelangexists
468  $found = false;
469 
470  // Enable caching of lang file in memory (not by default)
471  $usecachekey = '';
472  // Using a memcached server
473  if (isModEnabled('memcached') && !empty($conf->global->MEMCACHED_SERVER)) {
474  $usecachekey = $newdomain . '_' . $langofdir; // Should not contains special chars
475  } elseif (isset($conf->global->MAIN_OPTIMIZE_SPEED) && ($conf->global->MAIN_OPTIMIZE_SPEED & 0x02)) {
476  // Using cache with shmop. Speed gain: 40ms - Memory overusage: 200ko (Size of session cache file)
477  $usecachekey = $newdomain;
478  }
479 
480  if ($usecachekey) {
481  //dol_syslog('Translate::Load we will cache result into usecachekey '.$usecachekey);
482  //global $aaa; $aaa+=1;
483  //print $aaa." ".$usecachekey."\n";
484  require_once DOL_DOCUMENT_ROOT . '/core/lib/memory.lib.php';
485  $tmparray = dol_getcache($usecachekey);
486  if (is_array($tmparray) && count($tmparray)) {
487  $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.
488  //print $newdomain."\n";
489  //var_dump($this->tab_translate);
490  $fileread = 1;
491  $found = true; // Found in dolibarr PHP cache
492  }
493  }
494 
495  if (!$found && !empty($conf->global->MAIN_ENABLE_OVERWRITE_TRANSLATION)) {
496  // Overwrite translation with database read
497  $sql = "SELECT transkey, transvalue FROM " . $db->prefix() . "overwrite_trans where lang='" . $db->escape($this->defaultlang) . "' OR lang IS NULL";
498  $sql .= " AND entity IN (0, " . getEntity('overwrite_trans') . ")";
499  $sql .= $db->order("lang", "DESC");
500  $resql = $db->query($sql);
501 
502  if ($resql) {
503  $num = $db->num_rows($resql);
504  if ($num) {
505  if ($usecachekey) {
506  $tabtranslatedomain = array(); // To save lang content in cache
507  }
508 
509  $i = 0;
510  while ($i < $num) { // Ex: Need 225ms for all fgets on all lang file for Third party page. Same speed than file_get_contents
511  $obj = $db->fetch_object($resql);
512 
513  $key = $obj->transkey;
514  $value = $obj->transvalue;
515 
516  //print "Domain=$domain, found a string for $tab[0] with value $tab[1]<br>";
517  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)
518  // Convert some strings: Parse and render carriage returns. Also, change '\\s' int '\s' because transifex sync pull the string '\s' into string '\\s'
519  $this->tab_translate[$key] = str_replace(array('\\n', '\\\\s'), array("\n", '\s'), $value);
520 
521  if ($usecachekey) {
522  $tabtranslatedomain[$key] = $value; // To save lang content in cache
523  }
524  }
525 
526  $i++;
527  }
528 
529  $fileread = 1;
530 
531  // TODO Move cache write out of loop on dirs
532  // To save lang content for usecachekey into cache
533  if ($usecachekey && count($tabtranslatedomain)) {
534  $ressetcache = dol_setcache($usecachekey, $tabtranslatedomain);
535  if ($ressetcache < 0) {
536  $error = 'Failed to set cache for usecachekey=' . $usecachekey . ' result=' . $ressetcache;
537  dol_syslog($error, LOG_ERR);
538  }
539  }
540  }
541  } else {
542  dol_print_error($db);
543  }
544  }
545 
546  if ($fileread) {
547  $this->_tab_loaded[$newdomain] = 1; // Set domain file as loaded
548  }
549 
550  if (empty($this->_tab_loaded[$newdomain])) {
551  $this->_tab_loaded[$newdomain] = 2; // Mark this case as not found (no lines found for language)
552  }
553 
554  return 1;
555  }
556 
563  public function isLoaded($domain)
564  {
565  return $this->_tab_loaded[$domain];
566  }
567 
579  private function getTradFromKey($key)
580  {
581  global $conf, $db;
582 
583  if (!is_string($key)) {
584  //xdebug_print_function_stack('ErrorBadValueForParamNotAString');
585  return 'ErrorBadValueForParamNotAString'; // Avoid multiple errors with code not using function correctly.
586  }
587 
588  $newstr = $key;
589  $reg = array();
590  if (preg_match('/^Civility([0-9A-Z]+)$/i', $key, $reg)) {
591  $newstr = $this->getLabelFromKey($db, $reg[1], 'c_civility', 'code', 'label');
592  } elseif (preg_match('/^Currency([A-Z][A-Z][A-Z])$/i', $key, $reg)) {
593  $newstr = $this->getLabelFromKey($db, $reg[1], 'c_currencies', 'code_iso', 'label');
594  } elseif (preg_match('/^SendingMethod([0-9A-Z]+)$/i', $key, $reg)) {
595  $newstr = $this->getLabelFromKey($db, $reg[1], 'c_shipment_mode', 'code', 'libelle');
596  } elseif (preg_match('/^PaymentType(?:Short)?([0-9A-Z]+)$/i', $key, $reg)) {
597  $newstr = $this->getLabelFromKey($db, $reg[1], 'c_paiement', 'code', 'libelle', '', 1);
598  } elseif (preg_match('/^OppStatus([0-9A-Z]+)$/i', $key, $reg)) {
599  $newstr = $this->getLabelFromKey($db, $reg[1], 'c_lead_status', 'code', 'label');
600  } elseif (preg_match('/^OrderSource([0-9A-Z]+)$/i', $key, $reg)) {
601  // TODO OrderSourceX must be replaced with content of table llx_c_input_reason or llx_c_input_method
602  //$newstr=$this->getLabelFromKey($db,$reg[1],'llx_c_input_reason','code','label');
603  }
604 
605  /* Disabled. There is too many cases where translation of $newstr is not defined is normal (like when output with setEventMessage an already translated string)
606  if (getDolGlobalInt('MAIN_FEATURES_LEVEL') >= 2)
607  {
608  dol_syslog(__METHOD__." MAIN_FEATURES_LEVEL=DEVELOP: missing translation for key '".$newstr."' in ".$_SERVER["PHP_SELF"], LOG_DEBUG);
609  }*/
610 
611  return $newstr;
612  }
613 
614 
628  public function trans($key, $param1 = '', $param2 = '', $param3 = '', $param4 = '', $maxsize = 0)
629  {
630  global $conf;
631 
632  if (!empty($this->tab_translate[$key])) { // Translation is available
633  $str = $this->tab_translate[$key];
634 
635  // Make some string replacement after translation
636  $replacekey = 'MAIN_REPLACE_TRANS_' . $this->defaultlang;
637  if (!empty($conf->global->$replacekey)) { // Replacement translation variable with string1:newstring1;string2:newstring2
638  $tmparray = explode(';', $conf->global->$replacekey);
639  foreach ($tmparray as $tmp) {
640  $tmparray2 = explode(':', $tmp);
641  $str = preg_replace('/' . preg_quote($tmparray2[0]) . '/', $tmparray2[1], $str);
642  }
643  }
644 
645  // We replace some HTML tags by __xx__ to avoid having them encoded by htmlentities because
646  // we want to keep '"' '<b>' '</b>' '<strong' '</strong>' '<a ' '</a>' '<br>' '< ' '<span' '</span>' that are reliable HTML tags inside translation strings.
647  $str = str_replace(
648  array('"', '<b>', '</b>', '<u>', '</u>', '<i', '</i>', '<center>', '</center>', '<strong>', '</strong>', '<a ', '</a>', '<br>', '<span', '</span>', '< ', '>'), // We accept '< ' but not '<'. We can accept however '>'
649  array('__quot__', '__tagb__', '__tagbend__', '__tagu__', '__taguend__', '__tagi__', '__tagiend__', '__tagcenter__', '__tagcenterend__', '__tagb__', '__tagbend__', '__taga__', '__tagaend__', '__tagbr__', '__tagspan__', '__tagspanend__', '__ltspace__', '__gt__'),
650  $str
651  );
652 
653  if (strpos($key, 'Format') !== 0) {
654  try {
655  $str = sprintf($str, $param1, $param2, $param3, $param4); // Replace %s and %d except for FormatXXX strings.
656  } catch (Exception $e) {
657  // No exception managed
658  }
659  }
660 
661  // Crypt string into HTML
662  $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
663 
664  // Restore reliable HTML tags into original translation string
665  $str = str_replace(
666  array('__quot__', '__tagb__', '__tagbend__', '__tagu__', '__taguend__', '__tagi__', '__tagiend__', '__tagcenter__', '__tagcenterend__', '__taga__', '__tagaend__', '__tagbr__', '__tagspan__', '__tagspanend__', '__ltspace__', '__gt__'),
667  array('"', '<b>', '</b>', '<u>', '</u>', '<i', '</i>', '<center>', '</center>', '<a ', '</a>', '<br>', '<span', '</span>', '< ', '>'),
668  $str
669  );
670 
671  if ($maxsize) {
672  $str = dol_trunc($str, $maxsize);
673  }
674 
675  return $str;
676  } else { // Translation is not available
677  //if ($key[0] == '$') { return dol_eval($key, 1, 1, '1'); }
678  return $this->getTradFromKey($key);
679  }
680  }
681 
682 
697  public function transnoentities($key, $param1 = '', $param2 = '', $param3 = '', $param4 = '', $param5 = '')
698  {
699  return $this->convToOutputCharset($this->transnoentitiesnoconv($key, $param1, $param2, $param3, $param4, $param5));
700  }
701 
702 
718  public function transnoentitiesnoconv($key, $param1 = '', $param2 = '', $param3 = '', $param4 = '', $param5 = '')
719  {
720  global $conf;
721 
722  if (!empty($this->tab_translate[$key])) { // Translation is available
723  $str = $this->tab_translate[$key];
724 
725  // Make some string replacement after translation
726  $replacekey = 'MAIN_REPLACE_TRANS_' . $this->defaultlang;
727  if (!empty($conf->global->$replacekey)) { // Replacement translation variable with string1:newstring1;string2:newstring2
728  $tmparray = explode(';', $conf->global->$replacekey);
729  foreach ($tmparray as $tmp) {
730  $tmparray2 = explode(':', $tmp);
731  $str = preg_replace('/' . preg_quote($tmparray2[0]) . '/', $tmparray2[1], $str);
732  }
733  }
734 
735  if (!preg_match('/^Format/', $key)) {
736  //print $str;
737  $str = sprintf($str, $param1, $param2, $param3, $param4, $param5); // Replace %s and %d except for FormatXXX strings.
738  }
739 
740  return $str;
741  } else {
742  /*if ($key[0] == '$') {
743  return dol_eval($key, 1, 1, '1');
744  }*/
745  return $this->getTradFromKey($key);
746  }
747  }
748 
749 
758  public function transcountry($str, $countrycode)
759  {
760  if (!empty($this->tab_translate["$str$countrycode"])) {
761  return $this->trans("$str$countrycode");
762  } else {
763  return $this->trans($str);
764  }
765  }
766 
767 
776  public function transcountrynoentities($str, $countrycode)
777  {
778  if (!empty($this->tab_translate["$str$countrycode"])) {
779  return $this->transnoentities("$str$countrycode");
780  } else {
781  return $this->transnoentities($str);
782  }
783  }
784 
785 
793  public function convToOutputCharset($str, $pagecodefrom = 'UTF-8')
794  {
795  if ($pagecodefrom == 'ISO-8859-1' && $this->charset_output == 'UTF-8') {
796  $str = utf8_encode($str);
797  }
798  if ($pagecodefrom == 'UTF-8' && $this->charset_output == 'ISO-8859-1') {
799  $str = utf8_decode(str_replace('€', chr(128), $str));
800  // TODO Replace with iconv("UTF-8", "ISO-8859-1", str_replace('€', chr(128), $str)); ?
801  }
802  return $str;
803  }
804 
805 
806  // phpcs:disable PEAR.NamingConventions.ValidFunctionName.ScopeNotCamelCaps
816  public function get_available_languages($langdir = DOL_DOCUMENT_ROOT, $maxlength = 0, $usecode = 0, $mainlangonly = 0)
817  {
818  // phpcs:enable
819  global $conf;
820 
821  $this->load("languages");
822 
823  // We scan directory langs to detect available languages
824  $handle = opendir($langdir . "/langs");
825  $langs_available = array();
826  while ($dir = trim(readdir($handle))) {
827  $regs = array();
828  if (preg_match('/^([a-z]+)_([A-Z]+)/i', $dir, $regs)) {
829  // We must keep only main languages
830  if ($mainlangonly) {
831  $arrayofspecialmainlanguages = array(
832  'en' => 'en_US',
833  'am' => 'am_ET',
834  'ar' => 'ar_SA',
835  'bn' => 'bn_DB',
836  'bs' => 'bs_BA',
837  'ca' => 'ca_ES',
838  'cs' => 'cs_CZ',
839  'da' => 'da_DK',
840  'et' => 'et_EE',
841  'el' => 'el_GR',
842  'eu' => 'eu_ES',
843  'fa' => 'fa_IR',
844  'he' => 'he_IL',
845  'ka' => 'ka_GE',
846  'km' => 'km_KH',
847  'kn' => 'kn_IN',
848  'ko' => 'ko_KR',
849  'ja' => 'ja_JP',
850  'lo' => 'lo_LA',
851  'nb' => 'nb_NO',
852  'sq' => 'sq_AL',
853  'sr' => 'sr_RS',
854  'sv' => 'sv_SE',
855  'sl' => 'sl_SI',
856  'uk' => 'uk_UA',
857  'vi' => 'vi_VN',
858  'zh' => 'zh_CN'
859  );
860  if (strtolower($regs[1]) != strtolower($regs[2]) && !in_array($dir, $arrayofspecialmainlanguages)) {
861  continue;
862  }
863  }
864  // We must keep only languages into MAIN_LANGUAGES_ALLOWED
865  if (!empty($conf->global->MAIN_LANGUAGES_ALLOWED) && !in_array($dir, explode(',', $conf->global->MAIN_LANGUAGES_ALLOWED))) {
866  continue;
867  }
868 
869  if ($usecode == 2) {
870  $langs_available[$dir] = $dir;
871  }
872 
873  if ($usecode == 1 || !empty($conf->global->MAIN_SHOW_LANGUAGE_CODE)) {
874  $langs_available[$dir] = $dir . ': ' . dol_trunc($this->trans('Language_' . $dir), $maxlength);
875  } else {
876  $langs_available[$dir] = $this->trans('Language_' . $dir);
877  }
878  if ($mainlangonly) {
879  $langs_available[$dir] = str_replace(' (United States)', '', $langs_available[$dir]);
880  }
881  }
882  }
883  return $langs_available;
884  }
885 
886 
887  // phpcs:disable PEAR.NamingConventions.ValidFunctionName.ScopeNotCamelCaps
895  public function file_exists($filename, $searchalt = 0)
896  {
897  // phpcs:enable
898  // Test si fichier dans repertoire de la langue
899  foreach ($this->dir as $searchdir) {
900  if (is_readable(dol_osencode($searchdir . "/langs/" . $this->defaultlang . "/" . $filename))) {
901  return true;
902  }
903 
904  if ($searchalt) {
905  // Test si fichier dans repertoire de la langue alternative
906  if ($this->defaultlang != "en_US") {
907  $filenamealt = $searchdir . "/langs/en_US/" . $filename;
908  }
909  //else $filenamealt = $searchdir."/langs/fr_FR/".$filename;
910  if (is_readable(dol_osencode($filenamealt))) {
911  return true;
912  }
913  }
914  }
915 
916  return false;
917  }
918 
919 
931  public function getLabelFromNumber($number, $isamount = '')
932  {
933  global $conf;
934 
935  $newnumber = $number;
936 
937  $dirsubstitutions = array_merge(array(), $conf->modules_parts['substitutions']);
938  foreach ($dirsubstitutions as $reldir) {
939  $dir = dol_buildpath($reldir, 0);
940  $newdir = dol_osencode($dir);
941 
942  // Check if directory exists
943  if (!is_dir($newdir)) {
944  continue; // We must not use dol_is_dir here, function may not be loaded
945  }
946 
947  $fonc = 'numberwords';
948  if (file_exists($newdir . '/functions_' . $fonc . '.lib.php')) {
949  include_once $newdir . '/functions_' . $fonc . '.lib.php';
950  if (function_exists('numberwords_getLabelFromNumber')) {
951  $newnumber = numberwords_getLabelFromNumber($this, $number, $isamount);
952  break;
953  }
954  }
955  }
956 
957  return $newnumber;
958  }
959 
960 
976  public function getLabelFromKey($db, $key, $tablename, $fieldkey, $fieldlabel, $keyforselect = '', $filteronentity = 0)
977  {
978  // If key empty
979  if ($key == '') {
980  return '';
981  }
982  // Test should be useless because the 3 variables are never set from user input but we keep it in case of.
983  if (preg_match('/[^0-9A-Z_]/i', $tablename) || preg_match('/[^0-9A-Z_]/i', $fieldkey) || preg_match('/[^0-9A-Z_]/i', $fieldlabel)) {
984  $this->error = 'Bad value for parameter tablename, fieldkey or fieldlabel';
985  return -1;
986  }
987 
988  //print 'param: '.$key.'-'.$keydatabase.'-'.$this->trans($key); exit;
989 
990  // Check if a translation is available (Note: this can call getTradFromKey that can call getLabelFromKey)
991  $tmp = $this->transnoentitiesnoconv($key);
992  if ($tmp != $key && $tmp != 'ErrorBadValueForParamNotAString') {
993  return $tmp; // Found in language array
994  }
995 
996  // Check in cache
997  if (isset($this->cache_labels[$tablename][$key])) { // Can be defined to 0 or ''
998  return $this->cache_labels[$tablename][$key]; // Found in cache
999  }
1000 
1001  // Not found in loaded language file nor in cache. So we will take the label into database.
1002  $sql = "SELECT " . $fieldlabel . " as label";
1003  $sql .= " FROM " . $db->prefix() . $tablename;
1004  $sql .= " WHERE " . $fieldkey . " = '" . $db->escape($keyforselect ? $keyforselect : $key) . "'";
1005  if ($filteronentity) {
1006  $sql .= " AND entity IN (" . getEntity($tablename) . ')';
1007  }
1008  dol_syslog(get_class($this) . '::getLabelFromKey', LOG_DEBUG);
1009  $resql = $db->query($sql);
1010  if ($resql) {
1011  $obj = $db->fetch_object($resql);
1012  if ($obj) {
1013  $this->cache_labels[$tablename][$key] = $obj->label;
1014  } else {
1015  $this->cache_labels[$tablename][$key] = $key;
1016  }
1017 
1018  $db->free($resql);
1019  return $this->cache_labels[$tablename][$key];
1020  } else {
1021  $this->error = $db->lasterror();
1022  return -1;
1023  }
1024  }
1025 
1026 
1036  public function getCurrencyAmount($currency_code, $amount)
1037  {
1038  $symbol = $this->getCurrencySymbol($currency_code);
1039 
1040  if (in_array($currency_code, array('USD'))) {
1041  return $symbol . $amount;
1042  } else {
1043  return $amount . $symbol;
1044  }
1045  }
1046 
1055  public function getCurrencySymbol($currency_code, $forceloadall = 0)
1056  {
1057  $currency_sign = ''; // By default return iso code
1058 
1059  if (function_exists("mb_convert_encoding")) {
1060  $this->loadCacheCurrencies($forceloadall ? '' : $currency_code);
1061 
1062  if (isset($this->cache_currencies[$currency_code]) && !empty($this->cache_currencies[$currency_code]['unicode']) && is_array($this->cache_currencies[$currency_code]['unicode'])) {
1063  foreach ($this->cache_currencies[$currency_code]['unicode'] as $unicode) {
1064  $currency_sign .= mb_convert_encoding("&#" . $unicode . ";", "UTF-8", 'HTML-ENTITIES');
1065  }
1066  }
1067  }
1068 
1069  return ($currency_sign ? $currency_sign : $currency_code);
1070  }
1071 
1078  public function loadCacheCurrencies($currency_code)
1079  {
1080  global $db;
1081 
1082  if ($this->cache_currencies_all_loaded) {
1083  return 0; // Cache already loaded for all
1084  }
1085  if (!empty($currency_code) && isset($this->cache_currencies[$currency_code])) {
1086  return 0; // Cache already loaded for the currency
1087  }
1088 
1089  $sql = "SELECT code_iso, label, unicode";
1090  $sql .= " FROM " . $db->prefix() . "c_currencies";
1091  $sql .= " WHERE active = 1";
1092  if (!empty($currency_code)) {
1093  $sql .= " AND code_iso = '" . $db->escape($currency_code) . "'";
1094  }
1095  //$sql.= " ORDER BY code_iso ASC"; // Not required, a sort is done later
1096 
1097  dol_syslog(get_class($this) . '::loadCacheCurrencies', LOG_DEBUG);
1098  $resql = $db->query($sql);
1099  if ($resql) {
1100  $this->load("dict");
1101  $label = array();
1102  if (!empty($currency_code)) {
1103  foreach ($this->cache_currencies as $key => $val) {
1104  $label[$key] = $val['label']; // Label in already loaded cache
1105  }
1106  }
1107 
1108  $num = $db->num_rows($resql);
1109  $i = 0;
1110  while ($i < $num) {
1111  $obj = $db->fetch_object($resql);
1112  if ($obj) {
1113  // If a translation exists, we use it lese we use the default label
1114  $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 : ''));
1115  $this->cache_currencies[$obj->code_iso]['unicode'] = (array) json_decode((empty($obj->unicode) ? '' : $obj->unicode), true);
1116  $label[$obj->code_iso] = $this->cache_currencies[$obj->code_iso]['label'];
1117  }
1118  $i++;
1119  }
1120  if (empty($currency_code)) {
1121  $this->cache_currencies_all_loaded = true;
1122  }
1123  //print count($label).' '.count($this->cache_currencies);
1124 
1125  // Resort cache
1126  array_multisort($label, SORT_ASC, $this->cache_currencies);
1127  //var_dump($this->cache_currencies); $this->cache_currencies is now sorted onto label
1128  return $num;
1129  } else {
1130  dol_print_error($db);
1131  return -1;
1132  }
1133  }
1134 
1135  // phpcs:disable PEAR.NamingConventions.ValidFunctionName.ScopeNotCamelCaps
1143  {
1144  // phpcs:enable
1145  $substitutionarray = array();
1146 
1147  foreach ($this->tab_translate as $code => $label) {
1148  $substitutionarray['lang_' . $code] = $label;
1149  $substitutionarray['__(' . $code . ')__'] = $label;
1150  }
1151 
1152  return $substitutionarray;
1153  }
1154 }
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') && $user->hasRight('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') && $user->hasRight('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)) $sql
Social contributions to pay.
Definition: index.php:746
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.
isModEnabled($module)
Is Dolibarr module enabled.
dol_syslog($message, $level=LOG_INFO, $ident=0, $suffixinfilename='', $restricttologhandler='', $logcontext=null)
Write log message into outputs.
getEntity($element, $shared=1, $currentobject=null)
Get list of entity id to use.
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