diff --git a/ecrire/inc/filtres.php b/ecrire/inc/filtres.php
index d30ee6dbbd42a5ae2f51f600a037c5e6058c48d5..2470b8ff0d0c777a6f3a9de0646fbfd600d4c9e1 100644
--- a/ecrire/inc/filtres.php
+++ b/ecrire/inc/filtres.php
@@ -1532,6 +1532,83 @@ function post_autobr($texte, $delim = "\n_ ") {
 }
 
 
+/**
+ * Expression régulière pour obtenir le contenu des extraits idiomes `<:module:cle:>`
+ *
+ * @var string
+ */
+define('_EXTRAIRE_IDIOME', '@<:(?:([a-z0-9_]+):)?([a-z0-9_]+):>@isS');
+
+/**
+ * Extrait une langue des extraits idiomes (`<:module:cle_de_langue:>`)
+ *
+ * Retrouve les balises `<:cle_de_langue:>` d'un texte et remplace son contenu
+ * par l'extrait correspondant à la langue demandée (si possible), sinon dans la
+ * langue par défaut du site.
+ *
+ * Ne pas mettre de span@lang=fr si on est déjà en fr.
+ *
+ * @filtre
+ * @uses inc_traduire_dist()
+ * @uses code_echappement()
+ * @uses echappe_retour()
+ *
+ * @param string $letexte
+ * @param string $lang
+ *     Langue à retrouver (si vide, utilise la langue en cours).
+ * @param array $options Options {
+ * @type bool $echappe_span
+ *         True pour échapper les balises span (false par défaut)
+ * @type string $lang_defaut
+ *         Code de langue : permet de définir la langue utilisée par défaut,
+ *         en cas d'absence de traduction dans la langue demandée.
+ *         Par défaut la langue du site.
+ *         Indiquer 'aucune' pour ne pas retourner de texte si la langue
+ *         exacte n'a pas été trouvée.
+ * }
+ * @return string
+ **/
+function extraire_idiome($letexte, $lang = null, $options = array()) {
+	static $traduire = false;
+	if ($letexte
+		and preg_match_all(_EXTRAIRE_IDIOME, $letexte, $regs, PREG_SET_ORDER)
+	) {
+		if (!$traduire) {
+			$traduire = charger_fonction('traduire', 'inc');
+			include_spip('inc/lang');
+		}
+		if (!$lang) {
+			$lang = $GLOBALS['spip_lang'];
+		}
+		// Compatibilité avec le prototype de fonction précédente qui utilisait un boolean
+		if (is_bool($options)) {
+			$options = array('echappe_span' => $options);
+		}
+		if (!isset($options['echappe_span'])) {
+			$options = array_merge($options, array('echappe_span' => false));
+		}
+
+		foreach ($regs as $reg) {
+			$cle = ($reg[1] ? $reg[1] . ':' : '') . $reg[2];
+			$trad = $traduire($cle, $lang);
+			#$l = $lang; // TODO: aucun moyen de connaître la langue de retour de la traduction actuellement
+			// si pas de traduction, on laissera l'écriture de l'idiome entier dans le texte.
+			if (strlen($trad)) {
+				$trad = code_echappement($trad, 'idiome', false);
+				#$trad = str_replace("'", '"', inserer_attribut($trad, 'lang', $l));
+				#if (lang_dir($l) !== lang_dir($lang)) {
+				#	$trad = str_replace("'", '"', inserer_attribut($trad, 'dir', lang_dir($l)));
+				#}
+				if (!$options['echappe_span']) {
+					$trad = echappe_retour($trad, 'idiome');
+				}
+				$letexte = str_replace($reg[0], $trad, $letexte);
+			}
+		}
+	}
+	return $letexte;
+}
+
 /**
  * Expression régulière pour obtenir le contenu des extraits polyglottes `<multi>`
  *
diff --git a/ecrire/inc/utils.php b/ecrire/inc/utils.php
index 19b63e4fad64ea59e7fbb4eae24083dfbf81c7fc..6482700614f51d3dc27409110b686863d0af09ba 100644
--- a/ecrire/inc/utils.php
+++ b/ecrire/inc/utils.php
@@ -1036,8 +1036,13 @@ function queue_sleep_time_to_next_job($force = null) {
 }
 
 
-// transformation XML des "&" en "&amp;"
-// http://code.spip.net/@quote_amp
+/**
+ * Transformation XML des `&` en `&amp;`
+ * 
+ * @pipeline post_typo
+ * @param string $u
+ * @return string
+ */
 function quote_amp($u) {
 	return preg_replace(
 		"/&(?![a-z]{0,4}\w{2,3};|#x?[0-9a-f]{2,6};)/i",