414 lines
16 KiB
PHP
414 lines
16 KiB
PHP
<?php
|
|
|
|
/**
|
|
* DomFramework
|
|
* @package domframework
|
|
* @author Dominique Fournier <dominique@fournier38.fr>
|
|
* @license BSD
|
|
*/
|
|
|
|
namespace Domframework;
|
|
|
|
/**
|
|
* 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)
|
|
*/
|
|
public function languageSelection($repLocale = "./locale", $languageCode = "fr_FR")
|
|
{
|
|
$arrAccept = [];
|
|
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
|
|
*/
|
|
public 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, true)) {
|
|
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
|
|
*/
|
|
public 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
|
|
*/
|
|
public 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
|
|
*/
|
|
public 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
|
|
*/
|
|
public 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,
|
|
__DIR__ . "/../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);
|
|
}
|
|
}
|