diff --git a/language.php b/language.php new file mode 100644 index 0000000..f6582b9 --- /dev/null +++ b/language.php @@ -0,0 +1,323 @@ + */ + +/** 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) + + /** 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 $language The coding langugage of the soft + @return string The choosed locale whithout charset (like fr_FR) */ + function languageSelection ($repLocale = "./locale", $language = "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 ($languagetmp, $codeset) = explode (".", $_SERVER["LC_MESSAGES"]); + $arrAccept[] = $languagetmp; + } + 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 ($languagetmp, $codeset) = explode (".", $_SERVER["LANG"]); + $arrAccept[] = $languagetmp; + } + + // Si l'utilisateur n'a defini aucune language, on met la language par defaut + if (empty ($arrAccept)) + $arrAccept[] = $language; + // 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[] = $language; + + $language = ""; + // 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) + { + $language = $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 $languageAvailable) + { + if ($value === substr ($languageAvailable, 0, strlen ($value))) + { + $language = $languageAvailable; + break; + } + } + + // On a trouvé : on arrête de chercher + if ($language !== "") + 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 ($language === "") + $language = "C"; + + return ($language); + } + + /** Return the language recorded in the Cookie. Check if this language is + 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 $language Language to store + @param string $sitepath The site path */ + function languageCookieSet ($cookieName, $language, $sitepath) + { + @setcookie ($cookieName, $language, 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. + 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; + } + + private function languageCategoryText ($category) + { + // Renvoie en texte la catégorie fournie en entier + // Renvoie FALSE si elle n'existe pas + $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 $moFile to use + @param string $language 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 ($language, $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 ($language, ".")) !== FALSE) + list ($language, $codeset) = explode (".", $language); + $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/$language.$codeset/$category/*-*.mo"); + $moFile = "$repLocale/$language.$codeset/$category/$package.mo"; + $link = "$repLocale/$language.$codeset/$category/$package-".time(). + ".mo"; + if (! file_exists ($moFile)) + { + return ""; + } + clearstatcache (false, $moFile); + return $moFile; +/* if (! is_writeable (dirname ($moFile))) + { + // Si le répertoire est protégé en écriture, on donne le fichier original. + // On devra relancer Apache pour prendre en compte une modification + return $moFile; + } + if (count ($temporaries) > 1) + { + // Too much links are available : delete all and recreate it + foreach ($temporaries as $temp) + { + unlink ($temp); + } + $temporaries = array (); + } + if (empty ($temporaries)) + { + // Create the cache + copy ($moFile, $link); + } + else + { + // The cache exists. Is it up to date ? + $cache = $temporaries[0]; + if (filemtime ($cache) < filemtime ($moFile)) + { + // Obsolete cache : recreate it + unlink ($cache); + copy ($moFile, $link); + } + else + { + // Up to date cache + $link = $cache; + } + } + return $link;*/ + } + + function languageActivation ($moFile, $language, $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 ($language !== "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 ($language, ".")) !== FALSE) + list ($language, $codeset) = explode (".", $language); + $codeset = "UTF8"; // SANS TIRET ET EN MAJSUCULES!!! + // -> Le répertoire de données doit être fr_FR.UTF8 + putenv ('LANG='.$language.'.'.$codeset); + putenv ('LANGUAGE='.$language.'.'.$codeset); + + bind_textdomain_codeset ($package, "utf-8"); + bindtextdomain ($package, $repLocale); + textdomain ($package); + + $rc = setlocale (LC_MESSAGES, $language.'.'.$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, $language.'.'.$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 + @return string The language with format fr_FR to be used */ + public function activeLanguage ($package, $languageCookie) + { + // Prefered language in the browser + $langNav = $this->languageSelection ("./locale", "fr_FR"); + // Language defined in the cookie + $langCookie = $this->languageCookie ($languageCookie); + if ($langCookie !== FALSE) + $language = $langCookie; + else + $language = $langNav; + $this->languageCookieSet ($languageCookie, $language, "/"); + // Cache the domframework's .mo file too + $dfFile = $this->languageCache ($language, "domframework", LC_MESSAGES, + dirname (__FILE__)."/locale"); + $this->languageActivation ($dfFile, $language, LC_MESSAGES, + dirname (__FILE__)."/locale"); + $languageFichier = $this->languageCache ($language, $package); + $this->languageActivation ($languageFichier, $language); + + return $language; + } +}