[SPIP] v3.2.1-->v3.2.2
[lhc/web/www.git] / www / ecrire / inc / traduire.php
1 <?php
2
3 /***************************************************************************\
4 * SPIP, Systeme de publication pour l'internet *
5 * *
6 * Copyright (c) 2001-2019 *
7 * Arnaud Martin, Antoine Pitrou, Philippe Riviere, Emmanuel Saint-James *
8 * *
9 * Ce programme est un logiciel libre distribue sous licence GNU/GPL. *
10 * Pour plus de details voir le fichier COPYING.txt ou l'aide en ligne. *
11 \***************************************************************************/
12
13 /**
14 * Outils pour la traduction et recherche de traductions
15 *
16 * @package SPIP\Core\Traductions
17 **/
18
19 if (!defined('_ECRIRE_INC_VERSION')) {
20 return;
21 }
22
23 /**
24 * Rechercher tous les lang/file dans le path
25 * qui seront ensuite chargés dans l'ordre du path
26 *
27 * Version dédiée et optimisée pour cet usage de find_in_path
28 *
29 * @see find_in_path()
30 *
31 * @staticvar array $dirs
32 *
33 * @param string $file
34 * Nom du fichier cherché, tel que `mots_fr.php`
35 * @param string $dirname
36 * Nom du répertoire de recherche
37 * @return array
38 * Liste des fichiers de langue trouvés, dans l'ordre des chemins
39 */
40 function find_langs_in_path($file, $dirname = 'lang') {
41 static $dirs = array();
42 $liste = array();
43 foreach (creer_chemin() as $dir) {
44 if (!isset($dirs[$a = $dir . $dirname])) {
45 $dirs[$a] = (is_dir($a) || !$a);
46 }
47 if ($dirs[$a]) {
48 if (is_readable($a .= $file)) {
49 $liste[] = $a;
50 }
51 }
52 }
53
54 return array_reverse($liste);
55 }
56
57 /**
58 * Recherche le ou les fichiers de langue d'un module de langue
59 *
60 * @param string $module
61 * Nom du module de langue, tel que `mots` ou `ecrire`
62 * @param string $lang
63 * Langue dont on veut obtenir les traductions.
64 * Paramètre optionnel uniquement si le module est `local`
65 * @return array
66 * Liste des fichiers touvés pour ce module et cette langue.
67 **/
68 function chercher_module_lang($module, $lang = '') {
69 if ($lang) {
70 $lang = '_' . $lang;
71 }
72
73 // 1) dans un repertoire nomme lang/ se trouvant sur le chemin
74 if ($f = ($module == 'local'
75 ? find_in_path($module . $lang . '.php', 'lang/')
76 : find_langs_in_path($module . $lang . '.php', 'lang/'))
77 ) {
78 return is_array($f) ? $f : array($f);
79 }
80
81 // 2) directement dans le chemin (old style, uniquement pour local)
82 return (($module == 'local') or strpos($module, '/'))
83 ? (($f = find_in_path($module . $lang . '.php')) ? array($f) : false)
84 : false;
85 }
86
87 /**
88 * Charge en mémoire les couples cle/traduction d'un module de langue
89 * et une langue donnée
90 *
91 * Interprête un fichier de langue pour le module et la langue désignée
92 * s'il existe, et sinon se rabat soit sur la langue principale du site
93 * (définie par la meta `langue_site`), soit sur le français.
94 *
95 * Définit la globale `idx_lang` qui sert à la lecture du fichier de langue
96 * (include) et aux surcharges via `surcharger_langue()`
97 *
98 * @uses chercher_module_lang()
99 * @uses surcharger_langue()
100 *
101 * @param string $lang Code de langue
102 * @param string $module Nom du module de langue
103 * @return string Langue du module chargé, sinon chaîne vide.
104 **/
105 function charger_langue($lang, $module = 'spip') {
106 $var = 'i18n_' . $module . '_' . $lang;
107 if ($lang and $fichiers_lang = chercher_module_lang($module, $lang)) {
108 $GLOBALS['idx_lang'] = $var;
109 include(array_shift($fichiers_lang));
110 surcharger_langue($fichiers_lang);
111 $GLOBALS['lang_' . $var] = $lang;
112 } else {
113 // si le fichier de langue du module n'existe pas, on se rabat sur
114 // la langue par defaut du site -- et au pire sur le francais, qui
115 // *par definition* doit exister, et on copie le tableau dans la
116 // var liee a la langue
117 $l = $GLOBALS['meta']['langue_site'];
118 if (!$fichiers_lang = chercher_module_lang($module, $l)) {
119 $l = _LANGUE_PAR_DEFAUT;
120 $fichiers_lang = chercher_module_lang($module, $l);
121 }
122
123 if ($fichiers_lang) {
124 $GLOBALS['idx_lang'] = 'i18n_' . $module . '_' . $l;
125 include(array_shift($fichiers_lang));
126 surcharger_langue($fichiers_lang);
127 $GLOBALS[$var] = &$GLOBALS['i18n_' . $module . '_' . $l];
128 $GLOBALS['lang_' . $var] = $l;
129 #spip_log("module de langue : ${module}_$l.php");
130 }
131 }
132 }
133
134 /**
135 * Surcharger le fichier de langue courant avec un ou plusieurs autres
136 *
137 * Charge chaque fichier de langue dont les chemins sont transmis et
138 * surcharge les infos de cette langue/module déjà connues par les nouvelles
139 * données chargées. Seule les clés nouvelles ou modifiées par la
140 * surcharge sont impactées (les clés non présentes dans la surcharge
141 * ne sont pas supprimées !).
142 *
143 * La fonction suppose la présence de la globale `idx_lang` indiquant
144 * la destination des couples de traduction, de la forme
145 * `i18n_${module}_${lang}`
146 *
147 * @param array $fichiers
148 * Liste des chemins de fichiers de langue à surcharger.
149 **/
150 function surcharger_langue($fichiers) {
151 static $surcharges = array();
152 if (!isset($GLOBALS['idx_lang'])) {
153 return;
154 }
155
156 if (!is_array($fichiers)) {
157 $fichiers = array($fichiers);
158 }
159 if (!count($fichiers)) {
160 return;
161 }
162 foreach ($fichiers as $fichier) {
163 if (!isset($surcharges[$fichier])) {
164 $idx_lang_normal = $GLOBALS['idx_lang'];
165 $GLOBALS['idx_lang'] = $GLOBALS['idx_lang'] . '@temporaire';
166 include($fichier);
167 $surcharges[$fichier] = $GLOBALS[$GLOBALS['idx_lang']];
168 unset($GLOBALS[$GLOBALS['idx_lang']]);
169 $GLOBALS['idx_lang'] = $idx_lang_normal;
170 }
171 if (is_array($surcharges[$fichier])) {
172 $GLOBALS[$GLOBALS['idx_lang']] = array_merge(
173 (isset($GLOBALS[$GLOBALS['idx_lang']]) ? (array)$GLOBALS[$GLOBALS['idx_lang']] : array()),
174 $surcharges[$fichier]
175 );
176 }
177 }
178 }
179
180
181
182 class SPIP_Traductions_Description {
183 /** @var string code de langue (hors module) */
184 public $code;
185 /** @var string nom du module de langue */
186 public $module;
187 /** @var string langue de la traduction */
188 public $langue;
189 /** @var string traduction */
190 public $texte;
191 /** @var string var mode particulier appliqué ? */
192 public $mode;
193 /** @var bool Corrections des textes appliqué ? */
194 public $corrections = false;
195 }
196
197
198 /**
199 * Traduire une chaine internationalisée
200 *
201 * Lorsque la langue demandée n'a pas de traduction pour la clé de langue
202 * transmise, la fonction cherche alors la traduction dans la langue
203 * principale du site (défini par la meta `langue_site`), puis, à défaut
204 * dans la langue française.
205 *
206 * Les traductions sont cherchées dans les modules de langue indiqués.
207 * Par exemple le module `mots` dans la clé `mots:titre_mot`, pour une
208 * traduction `es` (espagnol) provoquera une recherche dans tous les fichiers
209 * `lang\mots_es.php`.
210 *
211 * Des surcharges locales peuvent être présentes également
212 * dans les fichiers `lang/local_es.php` ou `lang/local.php`
213 *
214 * @note
215 * Les couples clé/traductions déjà connus sont sauvés en interne
216 * dans les globales `i18n_${module}_${lang}` tel que `i18n_mots_es`
217 * et sont également sauvés dans la variable statique `deja_vu`
218 * de cette fonction.
219 *
220 * @uses charger_langue()
221 * @uses chercher_module_lang()
222 * @uses surcharger_langue()
223 *
224 * @param string $ori
225 * Clé de traduction, tel que `bouton_enregistrer` ou `mots:titre_mot`
226 * @param string $lang
227 * Code de langue, la traduction doit se faire si possible dans cette langue
228 * @param bool $raw
229 * - false : retourne le texte (par défaut)
230 * - true : retourne une description de la chaine de langue (module, texte, langue)
231 * @return string|array
232 * - string : Traduction demandée. Chaîne vide si aucune traduction trouvée.
233 * - SPIP_Traductions_Description : traduction et description (texte, module, langue)
234 **/
235 function inc_traduire_dist($ori, $lang, $raw = false) {
236 static $deja_vu = array();
237 static $local = array();
238
239 if (isset($deja_vu[$lang][$ori]) and (_request('var_mode') != 'traduction')) {
240 return $raw ? $deja_vu[$lang][$ori] : $deja_vu[$lang][$ori]->texte;
241 }
242
243 // modules demandes explicitement <xxx|yyy|zzz:code> cf MODULES_IDIOMES
244 if (strpos($ori, ':')) {
245 list($modules, $code) = explode(':', $ori, 2);
246 $modules = explode('|', $modules);
247 $ori_complet = $ori;
248 } else {
249 $modules = array('spip', 'ecrire');
250 $code = $ori;
251 $ori_complet = implode('|', $modules) . ':' . $ori;
252 }
253
254 $desc = new SPIP_Traductions_Description();
255
256 // parcourir tous les modules jusqu'a ce qu'on trouve
257 foreach ($modules as $module) {
258 $var = "i18n_" . $module . "_" . $lang;
259
260 if (empty($GLOBALS[$var])) {
261 charger_langue($lang, $module);
262 // surcharges persos -- on cherche
263 // (lang/)local_xx.php et/ou (lang/)local.php ...
264 if (!isset($local['local_' . $lang])) {
265 // redéfinir la langue en cours pour les surcharges (chercher_langue a pu le changer)
266 $GLOBALS['idx_lang'] = $var;
267 // ... (lang/)local_xx.php
268 $local['local_' . $lang] = chercher_module_lang('local', $lang);
269 }
270 if ($local['local_' . $lang]) {
271 surcharger_langue($local['local_' . $lang]);
272 }
273 // ... puis (lang/)local.php
274 if (!isset($local['local'])) {
275 $local['local'] = chercher_module_lang('local');
276 }
277 if ($local['local']) {
278 surcharger_langue($local['local']);
279 }
280 }
281
282 if (isset($GLOBALS[$var][$code])) {
283 $desc->code = $code;
284 $desc->module = $module;
285 $desc->langue = $GLOBALS['lang_' . $var];
286 $desc->texte = $GLOBALS[$var][$code];
287 break;
288 }
289 }
290
291 if (!$desc->corrections) {
292 $desc->corrections = true;
293 // Retour aux sources si la chaine est absente dans la langue cible ;
294 // on essaie d'abord la langue du site, puis a defaut la langue fr
295 if (!strlen($desc->texte) and $lang !== _LANGUE_PAR_DEFAUT) {
296 if ($lang !== $GLOBALS['meta']['langue_site']) {
297 $desc = inc_traduire_dist($ori, $GLOBALS['meta']['langue_site'], true);
298 } else {
299 $desc = inc_traduire_dist($ori, _LANGUE_PAR_DEFAUT, true);
300 }
301 }
302
303 // Supprimer la mention <NEW> ou <MODIF>
304 if (substr($desc->texte, 0, 1) === '<') {
305 $desc->texte = str_replace(array('<NEW>', '<MODIF>'), array(), $desc->texte);
306 }
307
308 // Si on n'est pas en utf-8, la chaine peut l'etre...
309 // le cas echeant on la convertit en entites html &#xxx;
310 if ((!isset($GLOBALS['meta']['charset']) or $GLOBALS['meta']['charset'] !== 'utf-8')
311 and preg_match(',[\x7f-\xff],S', $desc->texte)
312 ) {
313 include_spip('inc/charsets');
314 $desc->texte = charset2unicode($desc->texte, 'utf-8');
315 }
316 }
317
318 if (_request('var_mode') == 'traduction') {
319 $desc = definir_details_traduction($desc, $ori_complet);
320 } else {
321 $deja_vu[$lang][$ori] = $desc;
322 }
323
324 return $raw ? $desc : $desc->texte;
325 }
326
327 /**
328 * Modifie le texte de traduction pour indiquer des éléments
329 * servant au debug de celles-ci. (pour var_mode=traduction)
330 *
331 * @param SPIP_Traductions_Description $desc
332 * @param string $modules Les modules qui étaient demandés
333 * @return SPIP_Traductions_Description
334 */
335 function definir_details_traduction($desc, $modules) {
336 if (!$desc->mode and $desc->texte) {
337 // ne pas modifier 2 fois l'affichage
338 $desc->mode = 'traduction';
339 $classe = 'debug-traduction' . ($desc->module == 'ecrire' ? '-prive' : '');
340 $desc->texte = '<span '
341 . 'lang=' . $desc->langue
342 . ' class=' . $classe
343 . ' title=' . $modules . '(' . $desc->langue . ')>'
344 . $desc->texte
345 . '</span>';
346 $desc->texte = str_replace(
347 array("$desc->module:", "$desc->module|"),
348 array("*$desc->module*:", "*$desc->module*|"),
349 $desc->texte
350 );
351 }
352 return $desc;
353 }