*/ /** Language class : change the messages */ class language { // Language.php // Use Gettext, so the locales must be available in the system // Check the output of the command 'locale -a' : // en_GB.utf8 // en_US.utf8 // [...] // fr_FR.utf8 // The directories must be of this format but with a UTF8 in capital ! // Example : ./locale/en_US.UTF8/LC_MESSAGES/programme.mo // The only available codeset is UTF8 // The languages are always in the format fr_FR (without the codeset) /** Language cache directory */ public $cacheDir = "data/locale"; /** Choose the best language in the browser list and which is available in * locale path * @param string|null $repLocale Directory where are stored the translations * @param string|null $languageCode The coding langugage of the soft * @return string The choosed locale whithout charset (like fr_FR) */ function languageSelection ($repLocale = "./locale", $languageCode = "fr_FR") { $arrAccept = array (); if (isset ($_SERVER["HTTP_ACCEPT_LANGUAGE"])) { // Analyse de HTTP_ACCEPT_LANGUAGE // HTTP_ACCEPT_LANGUAGE est de la forme : fr,en;q=0.7,en-us;q=0.3 // Recuperation de la liste des languages souhaitees $arrAccept = explode(",", strtolower ($_SERVER["HTTP_ACCEPT_LANGUAGE"])); // Pre-traitement des choix de l'utilisateur foreach ($arrAccept as $key=>$value) { // Suppression des poids (l'ordre est donne dans la pile) if ( ($pos = strpos ($value, ";")) !== FALSE) { $arrAccept[$key] = substr ($value,0,$pos); } // Si la language proposee est du style en-us, convertit en en-US if ( ($pos = strpos ($value, "-")) !== FALSE) { $arrAccept[$key] = substr ($arrAccept[$key], 0, $pos). strtoupper (substr ($arrAccept[$key], $pos)); } // Remplacement des tirets par des soulignes $arrAccept[$key] = str_replace ("-", "_", $arrAccept[$key]); } } if (isset ($_SERVER["LC_MESSAGES"])) { // La ligne ci-dessous permet de récupérer la language sans le codeset si // il est fourni en_US.UTF8 -> en_US @list ($languageCodetmp, $codeset) = explode (".", $_SERVER["LC_MESSAGES"]); $arrAccept[] = $languageCodetmp; } if (isset ($_SERVER["LANG"])) { // La ligne ci-dessous permet de récupérer la language sans le codeset si // il est fourni en_US.UTF8 -> en_US @list ($languageCodetmp, $codeset) = explode (".", $_SERVER["LANG"]); $arrAccept[] = $languageCodetmp; } // Si l'utilisateur n'a defini aucune language, on met la language par // defaut if (empty ($arrAccept)) $arrAccept[] = $languageCode; // Le tableau $arrAccept est trié par priorité de language souhaité par // l'utilisateur (0=>le plus important) // Recherche des languages disponibles dans le repertoire $repLocale $arrLanguageAvailable = $this->languageTraductionsList ($repLocale); $arrLanguageAvailable[] = $languageCode; $languageCode = ""; // Analyse pour donner la meilleure language possible foreach ($arrAccept as $value) { // Regarde si un repertoire existe avec la language proposee. // Recherche insensible à la casse, retourne le nom du fichier avec la // casse $val2 = strtolower ($value); foreach ($arrLanguageAvailable as $val) { $val3 = strtolower ($val); if ($val2 === $val3) { $languageCode = $val; break; } } // Regarde si un repertoire existe avec language en tant que base // (l'utilisateur demande fr et on a fr_FR.utf-8) foreach ($arrLanguageAvailable as $languageCodeAvailable) { if ($value === substr ($languageCodeAvailable, 0, strlen ($value))) { $languageCode = $languageCodeAvailable; break; } } // On a trouvé : on arrête de chercher if ($languageCode !== "") break; } // Si on n'a toujours pas trouve de language, c'est que la language par // defaut proposee en tete de fichier est inconnue : on met la language "C" // et le code s'affiche selon la programmation if ($languageCode === "") $languageCode = "C"; return ($languageCode); } /** Return the language recorded in the Cookie. Check if this language is * @param string $cookieName The cookie name * @param string|null $repLocale The directory use to store the locale files * allowed * @return string The language allowed or FALSE */ function languageCookie ($cookieName, $repLocale = "./locale") { if (!isset ($_COOKIE[$cookieName])) return FALSE; $listeTranslations = $this->languageTraductionsList ($repLocale); if ($listeTranslations === FALSE) return FALSE; if (in_array ($_COOKIE[$cookieName], $listeTranslations)) return $_COOKIE[$cookieName]; return FALSE; } /** Set the cookie with a TTL of one month * @param string $cookieName The name of the cookie * @param string $languageCode Language to store * @param string $sitepath The site path */ function languageCookieSet ($cookieName, $languageCode, $sitepath) { @setcookie ($cookieName, $languageCode, time()+60*60*24*30, $sitepath); } /** Return an array with all the languages available in the $repLocale dir * The languages are in the format 'en_US' without the codeset. * @param string|null $repLocale The directory use to store the locale files * Return FALSE if there is an error */ function languageTraductionsList ($repLocale = "./locale") { if (! is_dir ($repLocale) || ! is_readable ($repLocale)) return FALSE; $list = glob ("$repLocale/*"); foreach ($list as $key=>$val) { $val = basename ($val); $pos = strpos ($val, "."); if ($pos === FALSE) $list[$key] = $val; else $list[$key] = substr ($val, 0, $pos); } sort ($list); return $list; } /** Return the full text of the category * Return false if it doesn't exists * @param string $category The category to analyze */ private function languageCategoryText ($category) { $categories[LC_ALL] = "LC_ALL"; $categories[LC_COLLATE] = "LC_COLLATE"; $categories[LC_CTYPE] = "LC_CTYPE"; $categories[LC_MONETARY] = "LC_MONETARY"; $categories[LC_NUMERIC] = "LC_NUMERIC"; $categories[LC_TIME] = "LC_TIME"; $categories[LC_MESSAGES] = "LC_MESSAGES"; if (! isset ($categories[$category])) return FALSE; return $categories[$category]; } /** This function manage the cache of $package.mo files as Apache cache them * It return the directory to use * @param string $languageCode Language with format "fr_FR" * @param string|null $package The package name of the soft ($package.mo * file). "messages" by default * @param string|null $category The folder name LC_MESSAGES by default * @param string|null $repLocale The folder where all the locales are stored */ function languageCache ($languageCode, $package="messages", $category=LC_MESSAGES, $repLocale = "./locale") { // Apache cache le fichier messages.mo jusqu'au prochain redémarrage. L'idée // est de créer un fichier temporaire basé sur le fichier normal. Du coup, // si on change le fichier temporaire, Apache recharge le cache et donne les // dernières traductions. // Cette fonction gère le cache et renvoie le nom du fichier temporaire // La ligne ci-dessous permet de récupérer la language sans le codeset si // il est fourni en_US.UTF8 -> en_US if (($pos = strpos ($languageCode, ".")) !== FALSE) list ($languageCode, $codeset) = explode (".", $languageCode); $codeset = "UTF8"; // SANS TIRET ET EN MAJSUCULES!!! // -> Le répertoire de données doit être fr_FR.UTF8 $category = $this->languageCategoryText ($category); $temporaries = glob ("$repLocale/$languageCode.$codeset/$category/*-*.mo"); $moFile = "$repLocale/$languageCode.$codeset/$category/$package.mo"; if (! file_exists ($moFile)) return ""; $linkBase = $this->cacheDir."/".filemtime($moFile)."/"; $linkEnd = "$languageCode.$codeset/$category/$package.mo"; $link = $linkBase.$linkEnd; clearstatcache (false, $moFile); // Manage the cache directory if (! file_exists (dirname ($link))) { // Try to create the cache dir. If there is an error, return the official // moFile. Apache will need to be restarted @mkdir (dirname ($link), 0777, true); } if (is_dir (dirname ($link)) && is_writeable (dirname ($link)) && is_readable (dirname ($link))) { // Manage the cache file if (! file_exists ($link) || ! is_readable ($link) || filemtime ($moFile) > filemtime ($link)) { // Do not remove immediately the old files : they can be used by Apache $files = glob ($this->cacheDir."/*/".$linkEnd); foreach ($files as $file) { unlink ($file); // Remove the empty dirs. If not empty, do not display an error @rmdir (dirname ($file)); @rmdir (dirname (dirname ($file))); @rmdir (dirname (dirname (dirname ($file)))); } copy ($moFile, $link); chmod ($link, 0666); } if (filemtime ($moFile) <= filemtime ($link)) { return $link; } } return $moFile; } /** Start the Gettext support with the cached file .mo provided as parameter * @param string $moFile The .mo file * @param string $languageCode The language code to use * @param integer $category the LC_ type to use * @param string|null $repLocale The locale directory. Use "./locale" if not * provided */ function languageActivation ($moFile, $languageCode, $category = LC_MESSAGES, $repLocale = "./locale") { // Active le support Gettext pour le fichier de language .mo caché fourni en // paramètre. // fichierMo : fichier messages-1354015006.mo // On ne vérifie pas la language fournie, elle doit correspondre à un // fichier de language (qui peut être récupéré par languageSelection ou // languageTraductionsList // Language doit être fourni sans codeset (fr_FR et PAS fr_FR.UTF8) if ($languageCode !== "C" && $moFile !== "") { $package = substr (basename ($moFile) , 0, -3); // La ligne ci-dessous permet de récupérer la language sans le codeset si // il est fourni en_US.UTF8 -> en_US if (($pos = strpos ($languageCode, ".")) !== FALSE) list ($languageCode, $codeset) = explode (".", $languageCode); $codeset = "UTF8"; // SANS TIRET ET EN MAJSUCULES!!! // -> Le répertoire de données doit être fr_FR.UTF8 putenv ('LANG='.$languageCode.'.'.$codeset); putenv ('LANGUAGE='.$languageCode.'.'.$codeset); $GLOBALS["domframework"]["lang"] = $languageCode; bind_textdomain_codeset ($package, "utf-8"); bindtextdomain ($package, $repLocale); textdomain ($package); $rc = setlocale (LC_MESSAGES, $languageCode.'.'.$codeset); if ($rc === FALSE) { // Language non disponible sur le système // La liste des languages est affichée par 'locale -a' return FALSE; } $rc = setlocale (LC_TIME, $languageCode.'.'.$codeset); return TRUE; } return FALSE; } /** The complete stack of language selection @param string $package The package name (package.(po|mo) files) @param string $languageCookie The name of the cookie saved in the browser @param string $forcedLanguage The name of a forced language @return string The language with format fr_FR to be used */ public function activeLanguage ($package, $languageCookie, $forcedLanguage = null) { // Prefered language in the browser $langNav = $this->languageSelection ("./locale", "fr_FR"); // Language defined in the cookie $langCookie = $this->languageCookie ($languageCookie); if ($forcedLanguage !== null) $languageCode = $forcedLanguage; elseif ($langCookie !== FALSE) { $languageCode = $langCookie; // Update the already set cookie $this->languageCookieSet ($languageCookie, $languageCode, "/"); } else $languageCode = $langNav; // Cache the domframework's .mo file too $dfFile = $this->languageCache ($languageCode, "domframework", LC_MESSAGES, dirname (__FILE__)."/locale"); $dfDir = dirname (dirname (dirname ($dfFile))); $this->languageActivation ($dfFile, $languageCode, LC_MESSAGES, $dfDir); $languageCodeFichier = $this->languageCache ($languageCode, $package); $languageCodeDir = dirname (dirname (dirname ($languageCodeFichier))); $this->languageActivation ($languageCodeFichier, $languageCode, LC_MESSAGES, $languageCodeDir); return $languageCode; } /** Return the language name from the language Ex. : $languageCode=fr_FR, return France @param string $languageCode Language with format "fr_FR" @return string Then language name */ public function languageName ($languageCode) { switch ($languageCode) { case "fr_FR": return dgettext ("domframework", "French"); case "en_US": return dgettext ("domframework", "English (US)"); case "en_GB": return dgettext ("domframework", "English (GB)"); default: throw new \Exception ("No language available for '$languageCode'", 500); } } /** Return the language subtag for the language * http://www.iana.org/assignments/language-subtag-registry/ * language-subtag-registry * @param string $languageCode The language code to convert */ public function languageSubTag ($languageCode) { return str_replace ("_", "-", $languageCode); } }