1: <?php
2: // declare(encoding='UTF-8');
3: /**
4: * I18n permet de traduire une application à partir de données stockées dans des fichiers ini.
5: * Si vous souhaitez utiliser le fonctionnement par défaut vous devrez :
6: * - déposer les fichiers ini dans le dossier définit par la variable de config "chemin_i18n".
7: * - nommer les fichiers selon la forme "locale.ini" (Ex.: fr.ini ou fr_CH.ini ).
8: *
9: * Elle offre l'accès en lecture seule aux paramètres des fichiers ini.
10: * C'est une Singleton. Une seule classe de traduction peut être instanciée par Application.
11: *
12: * @category PHP 5.2
13: * @package Framework
14: * @author Jean-Pascal MILCENT <jpm@tela-botanica.org>
15: * @copyright Copyright (c) 2010, Tela Botanica (accueil@tela-botanica.org)
16: * @license http://www.cecill.info/licences/Licence_CeCILL_V2-fr.txt Licence CECILL
17: * @license http://www.gnu.org/licenses/gpl.html Licence GNU-GPL
18: * @since 0.3
19: * @version $Id: I18n.php 330 2011-02-24 18:03:07Z jpm $
20: * @link /doc/framework/
21: */
22: class I18n {
23: /** Format de traduction utilisant les fichier .ini */
24: const FORMAT_INI = '.ini';
25:
26: /** Instance de la classe pointant sur elle même (pour le pattern singleton) */
27: private static $instance = null;
28:
29: /** Fichiers de traduction disponibles. */
30: private static $traductions = array();
31:
32: /** Langue courrante utilisée par l'application. */
33: private static $langue = null;
34:
35: /** Tableau des noms des paramètres à définir dans le fichier de config car obligatoirement nécessaire à cette classe.*/
36: private static $parametres_obligatoires = array('chemin_i18n', 'i18n_url_parametre', 'i18n_langue_defaut', 'debogage');
37:
38: private function __construct() {
39: Config::verifierPresenceParametres(self::$parametres_obligatoires);
40: self::trouverLangue();
41: }
42:
43: /**
44: * Accesseur pour la valeur d'une traduction
45: * @param string $param le nom du paramètre
46: * @return string la valeur du paramètre
47: */
48: public static function get($identifiant, $langue = null) {
49: self::verifierCreationInstance();
50: $texte = '';
51:
52: // Récupération de la langue actuellement demandée
53: $langue_a_charger = self::$langue;
54: if (!is_null($langue)) {
55: $langue_a_charger = $langue;
56: }
57:
58: if (!isset(self::$traductions[$langue_a_charger])) {
59: // Tentative de chargement du fichier de traduction
60: $chargement = self::charger($langue_a_charger);
61: if ($chargement === false) {
62: $m = "Le fichier d'i18n pour la langue '$langue_a_charger' demandée n'a pas été trouvé.";
63: self::ajouterErreur($m);
64: }
65: }
66:
67: // Recherche de la langue dans le tableau des traductions
68: if (isset(self::$traductions[$langue_a_charger]) && self::$traductions[$langue_a_charger] !== false) {
69: // Recherche de la traduction demandée
70: $valeur = self::getValeur($identifiant, self::$traductions[$langue_a_charger]);
71: if ($valeur !== false) {
72: $texte = $valeur;
73: } else {
74: $m = "Le traduction n'existe pas pour l'identifiant '$identifiant' demandé.";
75: self::ajouterErreur($m);
76: }
77: }
78:
79: return $texte;
80: }
81:
82: /**
83: * Charge un fichier ini dans le tableau des paramètres de l'appli
84: * @param string $fichier_ini le nom du fichier à charger
85: * @return boolean true, si le fichier a été trouvé et correctement chargé, sinon false.
86: */
87: public static function charger($langue, $fichier = null, $format = self::FORMAT_INI) {
88: self::verifierCreationInstance();
89: $ok = false;
90:
91: // Création du chemin vers le fichier de traduction par défaut
92: if (is_null($fichier)) {
93: $fichier = Config::get('chemin_i18n').$langue.$format;
94: }
95:
96: // Chargement
97: if ($format == self::FORMAT_INI) {
98: $ok = self::chargerFichierIni($fichier, $langue);
99: } else {
100: $m = "Le format '$format' de fichier de traduction n'est pas pris en compte par le Framework.";
101: self::ajouterErreur($m);
102: }
103:
104: return $ok;
105: }
106:
107: /**
108: * Définit la langue utiliser pour rechercher une traduction.
109: * @param string $fichier_ini le nom du fichier à charger
110: * @return array le fichier ini parsé
111: */
112: public static function setLangue($langue) {
113: self::verifierCreationInstance();
114: self::$langue = $langue;
115: }
116:
117: /**
118: * Renvoie la valeur demandé grâce une chaine de paramètres
119: * @param string $param la chaine identifiante
120: * @param array $i18n le tableau de traductions
121: * @return mixed la valeur correspondante à la chaine identifiante si elle est trouvée, sinon false.
122: */
123: private static function getValeur($param, $i18n) {
124: if ($param === null) {
125: return false;
126: } else {
127: if (isset($i18n[$param])) {
128: return $i18n[$param];
129: } else if (strpos($param, '.') !== false) {
130: $pieces = explode('.', $param, 2);
131: if (strlen($pieces[0]) && strlen($pieces[1])) {
132: if (isset($i18n[$pieces[0]])) {
133: if (is_array($i18n[$pieces[0]])) {
134: return self::getValeur($pieces[1], $i18n[$pieces[0]]);
135: }
136: }
137: }
138: } else {
139: return false;
140: }
141: }
142: }
143:
144: /**
145: * Parse le fichier ini donné en paramètre
146: * @param string $fichier_ini nom du fichier ini à parser
147: * @param string $langue la langue correspondant au fichier
148: * @return boolean true si le chargement c'est bien passé, sinon false.
149: */
150: private static function chargerFichierIni($fichier_ini, $langue) {
151: self::$traductions[$langue] = false;
152: if (file_exists($fichier_ini)) {
153: $ini = parse_ini_file($fichier_ini, true);
154: $ini = self::analyserTableauIni($ini);
155: self::$traductions[$langue] = $ini;
156: }
157: return (self::$traductions[$langue] === false) ? false : true;
158: }
159:
160: /**
161: * Analyse un tableau de traductions pour évaluer les clés.
162: * @param array $i18n le tableau de traductions
163: * @return array le tableau analysé et modifié si nécessaire.
164: */
165: private static function analyserTableauIni($i18n = array()) {
166: //ATTENTION : il est important de passer la valeur par référence car nous la modifions dynamiquement dans la boucle
167: foreach ($i18n as $cle => &$valeur) {
168: if (is_array($valeur)) {
169: $i18n[$cle] = self::analyserTableauIni($valeur);
170: } else {
171: $i18n = self::evaluerCle($i18n, $cle, $valeur);
172: }
173: }
174: return $i18n;
175: }
176:
177: /**
178: * Dans le cas des chaines de traduction à sous clé (ex.: cle.souscle), cette méthode
179: * évalue les valeurs correspondantes et créée les sous tableaux associés.
180: * @param array $i18n tableau de traductions (par référence)
181: * @param string $cle la cle dans le tableau
182: * @param string $valeur la valeur à affecter
183: */
184: private static function evaluerCle($i18n, $cle, $valeur) {
185: if (strpos($cle, '.') !== false) {
186: unset($i18n[$cle]);
187: $pieces = explode('.', $cle, 2);
188: if (strlen($pieces[0]) && strlen($pieces[1])) {
189: if (isset($i18n[$pieces[0]]) && !is_array($i18n[$pieces[0]])) {
190: $m = "Ne peut pas créer de sous-clé pour '{$pieces[0]}' car la clé existe déjà";
191: trigger_error($m, E_USER_WARNING);
192: } else {
193: $i18n[$pieces[0]][$pieces[1]] = $valeur;
194: $i18n[$pieces[0]] = self::evaluerCle($i18n[$pieces[0]], $pieces[1], $valeur);
195: }
196: } else {
197: $m = "Clé invalide '$cle'";
198: trigger_error($m, E_USER_WARNING);
199: }
200: } else {
201: $i18n[$cle] = $valeur;
202: }
203: return $i18n;
204: }
205:
206: /**
207: * Cherche l'information sur la langue demandée par l'application
208: */
209: private static function trouverLangue() {
210: if (isset($_GET[Config::get('i18n_url_parametre')])) {
211: self::$langue = $_GET[Config::get('i18n_url_parametre')];
212: } else {
213: self::$langue = Config::get('i18n_langue_defaut');
214: }
215: }
216:
217: /**
218: * Vérifie si l'instance de classe à été crée, si non la crée
219: */
220: private static function verifierCreationInstance() {
221: if (empty(self::$instance)) {
222: self::$instance = new I18n();
223: }
224: }
225:
226: /**
227: * Ajouter une message d'erreur
228: */
229: private static function ajouterErreur($m, $e = E_USER_WARNING) {
230: if (Config::get('debogage') === true) {
231: trigger_error($m, $e);
232: }
233: }
234: }
235: ?>