* Copyright (C) 2004-2010 Destailleur Laurent * Copyright (C) 2005-2010 Regis Houssin * * This program is free software; you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by * the Free Software Foundation; either version 2 of the License, or * any later version. * * This program is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. * * You should have received a copy of the GNU General Public License * along with this program; if not, write to the Free Software * Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA. */ /** * \file htdocs/core/class/translate.class.php * \ingroup core * \brief File for Tanslate class * \author Eric Seigne * \author Laurent Destailleur * \version $Id$ */ /** * \class Translate * \brief Class to manage translations */ class Translate { var $dir; // Directories that contains /langs subdirectory var $defaultlang; // Langue courante en vigueur de l'utilisateur var $direction = 'ltr'; // Left to right or Right to left var $tab_translate=array(); // Tableau des traductions var $tab_loaded=array(); // Array to store result after loading each language file var $cache_labels=array(); // Cache for labels var $charset_inputfile=array(); // To store charset encoding used for language var $charset_output='UTF-8'; // Codage used by "trans" method outputs /** * Constructor * @param dir Force directory that contains /langs subdirectory (value is sometine '..' like into install/* pages * or support/* pages). * @param conf Object with Dolibarr configuration */ function Translate($dir = "",$conf) { if (! empty($conf->file->character_set_client)) $this->charset_output=$conf->file->character_set_client; // If charset output is forced if ($dir) $this->dir=array($dir); else $this->dir=$conf->file->dol_document_root; } /** * Set accessor for this->defaultlang * @param srclang Language to use */ function setDefaultLang($srclang='fr_FR') { global $conf; //dol_syslog("Translate::setDefaultLang srclang=".$srclang,LOG_DEBUG); // If a module ask to force a priority on langs directories (to use its own lang files) if (! empty($conf->global->MAIN_FORCELANGDIR)) { $more=array(); foreach($conf->file->dol_document_root as $dir) { $newdir=$dir.$conf->global->MAIN_FORCELANGDIR; if (! in_array($newdir,$this->dir)) $more[]=$newdir; } $this->dir=array_merge($more,$this->dir); } $this->origlang=$srclang; if (empty($srclang) || $srclang == 'auto') { $langpref=$_SERVER['HTTP_ACCEPT_LANGUAGE']; $langpref=preg_replace("/;([^,]*)/i","",$langpref); $langpref=str_replace("-","_",$langpref); $langlist=preg_split("/[;,]/",$langpref); $codetouse=$langlist[0]; } else $codetouse=$srclang; // We redefine $srclang $langpart=explode("_",$codetouse); //print "Short before _ : ".$langpart[0].'/ Short after _ : '.$langpart[1].'
'; if (isset($langpart[1])) // If it's for a codetouse that is a long code xx_YY { // Array force long code from first part, even if long code is defined $longforshort=array('ar'=>'ar_SA'); if (isset($longforshort[strtolower($langpart[0])])) $srclang=$longforshort[strtolower($langpart[0])]; else { $srclang=strtolower($langpart[0])."_".strtoupper($langpart[1]); $longforlong=array('no_nb'=>'nb_NO'); if (isset($longforlong[strtolower($srclang)])) $srclang=$longforlong[strtolower($srclang)]; } } else { // If it's for a codetouse that is a short code xx // Array to convert short lang code into long code. $longforshort=array('ar'=>'ar_SA', 'el'=>'el_GR', 'ca'=>'ca_ES', 'en'=>'en_US', 'nb'=>'nb_NO', 'no'=>'nb_NO'); if (isset($longforshort[strtolower($langpart[0])])) $srclang=$longforshort[strtolower($langpart[0])]; else $srclang=strtolower($langpart[0])."_".strtoupper($langpart[0]); } $this->defaultlang=$srclang; //print $this->defaultlang; } /** * Return active language code for current user * It's an accessor for this->defaultlang * @param mode 0=Long language code, 1=Short language code * @return string Language code used (en_US, en_AU, fr_FR, ...) */ function getDefaultLang($mode=0) { if (empty($mode)) return $this->defaultlang; else return substr($this->defaultlang,0,2); } /** * Load translation key-value for a particular file, into a memory array. * If data for file already loaded, do nothing. * All data in translation array are stored in UTF-8 format. * tab_loaded is completed with $domain key. * Value for hash are: 1:Loaded from disk, 2:Not found, 3:Loaded from cache * @param domain File name to load (.lang file). Use file@module if file is in a module directory. * @param alt 0 (try xx_ZZ then 1), 1 (try xx_XX then 2), 2 (try en_US or fr_FR or es_ES) * @param stopafterdirection Stop when the DIRECTION tag is found (optimize) * @param forcelangdir To force a lang directory * @return int <0 if KO, 0 if already loaded, >0 if OK */ function Load($domain,$alt=0,$stopafterdirection=0,$forcelangdir='') { global $conf; // Check parameters if (empty($domain)) { dol_print_error('',"Translate::Load ErrorWrongParameters"); exit; } //dol_syslog("Translate::Load Start domain=".$domain." alt=".$alt." forcelangdir=".$forcelangdir." this->defaultlang=".$this->defaultlang); $newdomain = $domain; $modulename = ''; // Search if module directory name is different of lang file name if (preg_match('/^([^@]+)?@([^@]+)$/i',$domain,$regs)) { $newdomain = (!empty($regs[1])?$regs[1]:$regs[2]); $modulename = (!empty($regs[1])?$regs[2]:''); } // Check cache if (! empty($this->tab_loaded[$newdomain])) // File already loaded for this domain { //dol_syslog("Translate::Load already loaded for newdomain=".$newdomain); return 0; } $fileread=0; $langofdir=(empty($forcelangdir)?$this->defaultlang:$forcelangdir); // Redefine alt $langarray=explode('_',$langofdir); if ($alt < 1 && strtolower($langarray[0]) == strtolower($langarray[1])) $alt=1; if ($alt < 2 && (strtolower($langofdir) == 'en_us' || strtolower($langofdir) == 'fr_fr' || strtolower($langofdir) == 'es_es')) $alt=2; foreach($this->dir as $searchdir) { // If $domain is "file@module" instead of "file" then we look for module lang file // in htdocs/custom/modules/mymodule/langs/code_CODE/file.lang // and in htdocs/mymodule/langs/code_CODE/file.lang for backward compatibility // instead of file htdocs/langs/code_CODE/filelang if (preg_match('/@/',$domain)) $searchdir = $searchdir."/".(!empty($modulename)?$modulename:$newdomain)."/langs"; else $searchdir=$searchdir."/langs"; // Directory of translation files $scandir = $searchdir."/".$langofdir; $file_lang = $scandir . "/".$newdomain.".lang"; $file_lang_osencoded=dol_osencode($file_lang); $filelangexists=is_file($file_lang_osencoded); //dol_syslog('Translate::Load Try to read for alt='.$alt.' langofdir='.$langofdir.' file_lang='.$file_lang." => ".$filelangexists); if ($filelangexists) { $found=false; // Enable cache of lang file in memory (not by default) $usecachekey=''; // Using a memcached server if (! empty($conf->memcached->enabled) && ! empty($conf->global->MEMCACHED_SERVER)) { $usecachekey=$newdomain.'_'.$langofdir; } // Using cache with shmop. Speed gain: 40ms - Memory overusage: 200ko (Size of session cache file) else if (isset($conf->global->MAIN_OPTIMIZE_SPEED) && ($conf->global->MAIN_OPTIMIZE_SPEED & 0x02)) { $usecachekey=$newdomain; } if ($usecachekey) { require_once(DOL_DOCUMENT_ROOT ."/lib/memory.lib.php"); $tmparray=dol_getcache($usecachekey); if (is_array($tmparray) && sizeof($tmparray)) { $this->tab_translate=array_merge($tmparray,$this->tab_translate); // Already found values tab_translate overwrites duplicates //print $newdomain."\n"; //var_dump($this->tab_translate); if ($alt == 2) $fileread=1; $found=true; // Found in dolibarr PHP cache } } if (! $found) { if ($fp = @fopen($file_lang,"rt")) { if ($usecachekey) $tabtranslatedomain=array(); // To save lang content in cache 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 { if ($line[0] != "\n" && $line[0] != " " && $line[0] != "#") { $tab=explode('=',$line,2); $key=trim($tab[0]); //print "Domain=$domain, found a string for $tab[0] with value $tab[1]
"; if (empty($this->tab_translate[$key]) && isset($tab[1])) { $value=trim(preg_replace('/\\n/',"\n",$tab[1])); if ($key == 'CHARSET') // This is to declare in which charset files are encoded { $this->charset_inputfile[$newdomain]=strtoupper($value); //print 'File '.$file_lang.' is declared to have format '.$this->charset_inputfile[$newdomain].'
'; } elseif ($key == 'DIRECTION') // This is to declare direction of language { if ($alt < 2 || empty($this->tab_translate[$key])) // We load direction only for primary files or if not yer load { $this->tab_translate[$key]=$value; if ($stopafterdirection) break; // We do not save tab if we stop after DIRECTION else if ($usecachekey) $tabtranslatedomain[$key]=$value; } } else { // On stocke toujours dans le tableau Tab en UTF-8 if (empty($this->charset_inputfile[$newdomain]) || $this->charset_inputfile[$newdomain] == 'ISO-8859-1') $value=utf8_encode($value); //print 'XX'.$key; $this->tab_translate[$key]=$value; if ($usecachekey) $tabtranslatedomain[$key]=$value; // To save lang content in cache } } } } fclose($fp); $fileread=1; // To save lang content for usecachekey into cache if ($usecachekey && sizeof($tabtranslatedomain)) { require_once(DOL_DOCUMENT_ROOT ."/lib/memory.lib.php"); $size=dol_setcache($usecachekey,$tabtranslatedomain); } //exit; break; // Break loop on each root dir } } } } // Now we complete with next file if ($alt == 0) { // This function MUST NOT contains call to syslog //dol_syslog("Translate::Load loading alternate translation file (to complete ".$this->defaultlang."/".$newdomain.".lang file)", LOG_DEBUG); $langofdir=strtolower($langarray[0]).'_'.strtoupper($langarray[0]); $this->load($domain,$alt+1,$stopafterdirection,$langofdir); } // Now we complete with reference en_US/fr_FR/es_ES file if ($alt == 1) { // This function MUST NOT contains call to syslog //dol_syslog("Translate::Load loading alternate translation file (to complete ".$this->defaultlang."/".$newdomain.".lang file)", LOG_DEBUG); $langofdir='en_US'; if (preg_match('/^fr/i',$langarray[0])) $langofdir='fr_FR'; if (preg_match('/^es/i',$langarray[0])) $langofdir='es_ES'; $this->load($domain,$alt+1,$stopafterdirection,$langofdir); } if ($alt == 2) { if ($fileread) $this->tab_loaded[$newdomain]=1; // Set domain file as loaded if (empty($this->tab_loaded[$newdomain])) $this->tab_loaded[$newdomain]=2; // Marque ce fichier comme non trouve } // Check to be sure that SeparatorDecimal differs from SeparatorThousand if (! empty($this->tab_translate["SeparatorDecimal"]) && ! empty($this->tab_translate["SeparatorThousand"]) && $this->tab_translate["SeparatorDecimal"] == $this->tab_translate["SeparatorThousand"]) $this->tab_translate["SeparatorThousand"]=''; return 1; } /** * Return translated value of key. Search in lang file, then into database. * If not found, return key. * WARNING: To avoid infinite loop (getLabelFromKey->transnoentities->getTradFromKey), getLabelFromKey must * not be called with same value than input. * * @param key * @return string */ function getTradFromKey($key) { global $db; $newstr=$key; if (preg_match('/^CurrencySing([A-Z][A-Z][A-Z])$/i',$key,$reg)) { $newstr=$this->getLabelFromKey($db,$reg[1],'c_currencies','code_iso','labelsing'); } else if (preg_match('/^Currency([A-Z][A-Z][A-Z])$/i',$key,$reg)) { $newstr=$this->getLabelFromKey($db,$reg[1],'c_currencies','code_iso','label'); } else if (preg_match('/^SendingMethod([0-9A-Z]+)$/i',$key,$reg)) { $newstr=$this->getLabelFromKey($db,$reg[1],'c_shipment_mode','code','libelle'); } else if (preg_match('/^PaymentTypeShort([0-9A-Z]+)$/i',$key,$reg)) { $newstr=$this->getLabelFromKey($db,$reg[1],'c_paiement','code','libelle'); } else if (preg_match('/^Civility([0-9A-Z]+)$/i',$key,$reg)) { $newstr=$this->getLabelFromKey($db,$reg[1],'c_civilite','code','civilite'); } else if (preg_match('/^OrderSource([0-9A-Z]+)$/i',$key,$reg)) { // TODO Add a table for OrderSourceX //$newstr=$this->getLabelFromKey($db,$reg[1],'c_ordersource','code','label'); } return $newstr; } /** * Return text translated of text received as parameter (and encode it into HTML) * Si il n'y a pas de correspondance pour ce texte, on cherche dans fichier alternatif * et si toujours pas trouve, il est retourne tel quel * Les parametres de cette methode peuvent contenir de balises HTML. * @param key cle de chaine a traduire * @param param1 chaine de param1 * @param param2 chaine de param2 * @param param3 chaine de param3 * @param param4 chaine de param4 * @param maxsize taille max * @return string Chaine traduite et code en HTML */ function trans($key, $param1='', $param2='', $param3='', $param4='', $maxsize=0) { if (! empty($this->tab_translate[$key])) // Translation is available { $str=$this->tab_translate[$key]; if (! preg_match('/^Format/',$key)) $str=sprintf($str,$param1,$param2,$param3,$param4); // Replace %s and %d except for FormatXXX strings. if ($maxsize) $str=dol_trunc($str,$maxsize); // We replace some HTML tags by __xx__ to avoid having them encoded by htmlentities $str=str_replace(array('<','>','"',),array('__lt__','__gt__','__quot__'),$str); $str=$this->convToOutputCharset($str); // Convert string to $this->charset_output // Crypt string into HTML // $str est une chaine stockee en memoire au format $this->charset_output $str=htmlentities($str,ENT_QUOTES,$this->charset_output); // Restore HTML tags $str=str_replace(array('__lt__','__gt__','__quot__'),array('<','>','"',),$str); return $str; } else // Translation is not available { $str=$this->getTradFromKey($key); return $this->convToOutputCharset($str); } } /** * Return translated value of a text string * Si il n'y a pas de correspondance pour ce texte, on cherche dans fichier alternatif * et si toujours pas trouve, il est retourne tel quel. * Parameters of this method must not contains any HTML tags. * @param key key of string to translate * @param param1 chaine de param1 * @param param2 chaine de param2 * @param param3 chaine de param3 * @param param4 chaine de param4 * @return string chaine traduite */ function transnoentities($key, $param1='', $param2='', $param3='', $param4='') { if (! empty($this->tab_translate[$key])) { // Si la traduction est disponible $newstr=sprintf($this->tab_translate[$key],$param1,$param2,$param3,$param4); } else { $newstr=$this->getTradFromKey($key); } return $this->convToOutputCharset($newstr); } /** * Return translated value of a text string * Si il n'y a pas de correspondance pour ce texte, on cherche dans fichier alternatif * et si toujours pas trouve, il est retourne tel quel. * No convert to encoding charset of lang object is done. * Parameters of this method must not contains any HTML tags. * @param key key of string to translate * @param param1 chaine de param1 * @param param2 chaine de param1 * @param param3 chaine de param1 * @param param4 chaine de param1 * @return string chaine traduite */ function transnoentitiesnoconv($key, $param1='', $param2='', $param3='', $param4='') { if (! empty($this->tab_translate[$key])) { // Si la traduction est disponible $newstr=sprintf($this->tab_translate[$key],$param1,$param2,$param3,$param4); } else { $newstr=$this->getTradFromKey($key); } return $newstr; } /** * Return translation of a key depending on country * @param str string root to translate * @param countrycode country code (FR, ...) * @return string translated string */ function transcountry($str, $countrycode) { if ($this->tab_translate["$str$countrycode"]) return $this->trans("$str$countrycode"); else return $this->trans($str); } /** * Retourne la version traduite du texte passe en parametre complete du code pays * @param str string root to translate * @param countrycode country code (FR, ...) * @return string translated string */ function transcountrynoentities($str, $countrycode) { if ($this->tab_translate["$str$countrycode"]) return $this->transnoentities("$str$countrycode"); else return $this->transnoentities($str); } /** * Convert a string into output charset (this->charset_output that should be defined to conf->file->character_set_client) * @param str String to convert * @param pagecodefrom Page code of src string * @return string Converted string */ function convToOutputCharset($str,$pagecodefrom='UTF-8') { if ($pagecodefrom == 'ISO-8859-1' && $this->charset_output == 'UTF-8') $str=utf8_encode($str); if ($pagecodefrom == 'UTF-8' && $this->charset_output == 'ISO-8859-1') $str=utf8_decode(str_replace('€',chr(128),$str)); return $str; } /** * Return list of all available languages * @param langdir Directory to scan * @param maxlength Max length for each value in combo box (will be truncated) * @return array List of languages */ function get_available_languages($langdir=DOL_DOCUMENT_ROOT,$maxlength=0) { global $conf; // We scan directory langs to detect available languages $handle=opendir($langdir."/langs"); $langs_available=array(); while ($dir = trim(readdir($handle))) { if (preg_match('/^[a-z]+_[A-Z]+/i',$dir)) { $this->load("languages"); if (isset($conf->global->MAIN_SHOW_LANGUAGE_CODE) && $conf->global->MAIN_SHOW_LANGUAGE_CODE) { $langs_available[$dir] = $dir.': '.dol_trunc($this->trans('Language_'.$dir),$maxlength); } else { $langs_available[$dir] = $this->trans('Language_'.$dir); } } } return $langs_available; } /** * Return if a filename $filename exists for current language (or alternate language) * @param filename Language filename to search * @param searchalt Search also alernate language file * @return boolean true if exists and readable */ function file_exists($filename,$searchalt=0) { // Test si fichier dans repertoire de la langue foreach($this->dir as $searchdir) { if (is_readable(dol_osencode($searchdir."/langs/".$this->defaultlang."/".$filename))) return true; if ($searchalt) { // Test si fichier dans repertoire de la langue alternative if ($this->defaultlang != "en_US") $filenamealt = $searchdir."/langs/en_US/".$filename; else $filenamealt = $searchdir."/langs/fr_FR/".$filename; if (is_readable(dol_osencode($filenamealt))) return true; } } return false; } /** * Return full text translated to language label for a key. Store key-label in a cache. * This function need module "numberwords" to be installed. If not it will return * same number (this module is not provided by default as it use non GPL source code). * @param number Number to encode in full text * @param isamount 1=It's an amount, 0=it's just a number * @return string Label translated in UTF8 (but without entities) * 10 if setDefaultLang was en_US => ten * 123 if setDefaultLang was fr_FR => cent vingt trois */ function getLabelFromNumber($number,$isamount=0) { global $conf; /* $outlang=$this->defaultlang; // Output language we want $outlangarray=explode('_',$outlang,2); // If lang is xx_XX, then we use xx if (strtolower($outlangarray[0]) == strtolower($outlangarray[1])) $outlang=$outlangarray[0]; */ $newnumber=$number; foreach ($conf->file->dol_document_root as $dirroot) { $dir=$dirroot."/includes/modules/substitutions"; $fonc='numberwords'; if (file_exists($dir.'/functions_'.$fonc.'.lib.php')) { include_once($dir.'/functions_'.$fonc.'.lib.php'); $newnumber=numberwords_getLabelFromNumber($this,$number,$isamount); break; } } return $newnumber; } /** * Return a label for a key. Store key-label into cache variable $this->cache_labels to save SQL requests to get labels. * @param db Database handler * @param key Key to get label (key in language file) * @param tablename Table name without prefix * @param fieldkey Field for key * @param fieldlabel Field for label * @return string Label in UTF8 (but without entities) * @remarks This function can be used to get label in database but more often to get code from key id. */ function getLabelFromKey($db,$key,$tablename,$fieldkey,$fieldlabel) { // If key empty if ($key == '') return ''; // Check in language array if ($this->transnoentities($key) != $key) { return $this->transnoentities($key); // Found in language array } // Check in cache if (isset($this->cache_labels[$tablename][$key])) // Can be defined to 0 or '' { return $this->cache_labels[$tablename][$key]; // Found in cache } $sql = "SELECT ".$fieldlabel." as label"; $sql.= " FROM ".MAIN_DB_PREFIX.$tablename; $sql.= " WHERE ".$fieldkey." = '".$key."'"; dol_syslog('Translate::getLabelFromKey sql='.$sql,LOG_DEBUG); $resql = $db->query($sql); if ($resql) { $obj = $db->fetch_object($resql); if ($obj) $this->cache_labels[$tablename][$key]=$obj->label; else $this->cache_labels[$tablename][$key]=$key; $db->free($resql); return $this->cache_labels[$tablename][$key]; } else { $this->error=$db->lasterror(); dol_syslog("Translate::getLabelFromKey error=".$this->error,LOG_ERR); return -1; } } } ?>