Dépôt officiel du core SPIP * Copie possible par svn sur svn://trac.rezo.net/spip * Les svn:externals sont présent dans https://git.spip.net/SPIP/[nom du plugin dist] https://www.spip.net
You can not select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
 
 
 
 

354 lines
11 KiB

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