diff --git a/ecrire/inc_filtres.php3 b/ecrire/inc_filtres.php3
index 93554d44a1560063fe31e02c2df6d058fa514978..199f380210e435f0d4739c561371653a56f369b2 100644
--- a/ecrire/inc_filtres.php3
+++ b/ecrire/inc_filtres.php3
@@ -810,85 +810,32 @@ function post_autobr($texte, $delim="\n_ ") {
 }
 
 
-//
-// Gestion des blocs multilingues
-//
-
-// renvoie la traduction d'un bloc multi dans la langue demandee
-function multi_trad ($lang, $trads) {
-	// si la traduction existe, genial
-	if (isset($trads[$lang])) {
-		$retour = $trads[$lang];
-
-	}	// cas des langues xx_yy
-	else if (ereg('^([a-z]+)_', $lang, $regs) AND isset($trads[$regs[1]])) {
-		$retour = $trads[$regs[1]];
-
-	}	// sinon, renvoyer la premiere du tableau
-		// remarque : on pourrait aussi appeler un service de traduction externe
-		// ou permettre de choisir une langue "plus proche",
-		// par exemple le francais pour l'espagnol, l'anglais pour l'allemand, etc.
-	else {
-		list (,$trad) = each($trads);
-		$retour = $trad;
-	}
-
-
-	// dans l'espace prive, mettre un popup multi
-	if (!_DIR_RESTREINT) {
-		$retour = ajoute_popup_multi($lang, $trads, $retour);
-	}
-
-	return $retour;
-}
 
-// analyse un bloc multi
-function extraire_trad ($langue_demandee, $bloc) {
-	$lang = '';
+// les ci-devant blocs multi sont maintenant traites en amont.
+// garder cette fonction pour de vielles contrib qui l'appelleraient
 
-	while (preg_match("/^(.*?)[\{\[]([a-z_]+)[\}\]]/si", $bloc, $regs)) {
-		$texte = trim($regs[1]);
-		if ($texte OR $lang)
-			$trads[$lang] = $texte;
-		$bloc = substr($bloc, strlen($regs[0]));
-		$lang = $regs[2];
-	}
-	$trads[$lang] = $bloc;
-
-	// faire la traduction avec ces donnees
-	return multi_trad($langue_demandee, $trads);
-}
-
-// repere les blocs multi dans un texte et extrait le bon
-function extraire_multi ($letexte) {
-	global $flag_pcre;
+function extraire_multi ($letexte) {return $letexte;}
 
-	if (strpos($letexte, '<multi>') === false) return $letexte; // perf
-	if ($flag_pcre AND preg_match_all("@<multi>(.*?)</multi>@s", $letexte, $regs, PREG_SET_ORDER)) {
-		while (list(,$reg) = each ($regs)) {
-			$letexte = str_replace($reg[0], extraire_trad($GLOBALS['spip_lang'], $reg[1]), $letexte);
-		}
-	}
-	return $letexte;
-}
+//
+// Selection dans un tableau dont les index sont des noms de langues
+// de la valeur associee a la langue en cours (ci-devant bloc multi)
+//
 
-// popup des blocs multi dans l'espace prive (a ameliorer)
-function ajoute_popup_multi($langue_demandee, $trads, $texte) {
-	static $num_multi=0;
-	global $multi_popup;
-	while (list($lang,$bloc) = each($trads)) {
-		if ($lang != $langue_demandee)
-			$survol .= "[$lang] ".supprimer_tags(couper($bloc,20))."\n";
-		$texte_popup .= "<br /><b>".traduire_nom_langue($lang)."</b> ".ereg_replace("\n+","<br />", supprimer_tags(couper(propre($bloc),200)));
-	}
+function multi_trad ($trads) {
+	global  $spip_lang; 
 
-	if ($survol) {
-		$num_multi ++;
-		$texte .= " <img src=\"" . _DIR_IMG_PACK . "langues-modif-12.gif\" alt=\"(multi)\" title=\"$survol\" height=\"12\" width=\"12\" border=\"0\" onclick=\"return openmulti($num_multi)\" />";
-		$multi_popup .= "textes_multi[$num_multi] = '".addslashes($texte_popup)."';\n";
-	}
+	if (isset($trads[$spip_lang])) {
+		return $trads[$spip_lang];
 
-	return $texte;
+	}	// cas des langues xx_yy
+	else if (ereg('^([a-z]+)_', $spip_lang, $regs) AND isset($trads[$regs[1]])) {
+		return $trads[$regs[1]];
+	}	
+	// sinon, renvoyer la premiere du tableau
+	// remarque : on pourrait aussi appeler un service de traduction externe
+	// ou permettre de choisir une langue "plus proche",
+	// par exemple le francais pour l'espagnol, l'anglais pour l'allemand, etc.
+	else  return array_shift($trads);
 }
 
 // Raccourci ancre [#ancre<-]
diff --git a/ecrire/inc_texte.php3 b/ecrire/inc_texte.php3
index 63b0bf75bca2c96d6f044b27f964a0f6cdd97fdd..6b25c6bf20734d97d8a47de543225135c8b09b0b 100644
--- a/ecrire/inc_texte.php3
+++ b/ecrire/inc_texte.php3
@@ -68,7 +68,6 @@ function nettoyer_chapo($chapo){
 // points d'entree de pre- et post-traitement pour propre() et typo()
 function spip_avant_propre ($letexte) {
 	$letexte = avant_propre_ancres($letexte);
-	$letexte = extraire_multi($letexte);
 
 	if (function_exists('avant_propre'))
 		$letexte = avant_propre($letexte);
@@ -84,7 +83,6 @@ function spip_apres_propre ($letexte) {
 }
 
 function spip_avant_typo ($letexte) {
-	$letexte = extraire_multi($letexte);
 	$letexte = avant_typo_smallcaps($letexte);
 
 	if (function_exists('avant_typo'))
@@ -365,7 +363,7 @@ function couper($texte, $taille=50) {
 
 // prendre <intro>...</intro> sinon couper a la longueur demandee
 function couper_intro($texte, $long) {
-	$texte = extraire_multi(eregi_replace("(</?)intro>", "\\1intro>", $texte)); // minuscules
+	$texte = (eregi_replace("(</?)intro>", "\\1intro>", $texte)); // minuscules
 	while ($fin = strpos($texte, "</intro>")) {
 		$zone = substr($texte, 0, $fin);
 		$texte = substr($texte, $fin + strlen("</intro>"));
diff --git a/inc-compilo-api.php3 b/inc-compilo-api.php3
index 8b1564fd1016dea8a4e676f6955439adab29e777..30c0e93255fc43133e252fdb85336bf6108ed43f 100644
--- a/inc-compilo-api.php3
+++ b/inc-compilo-api.php3
@@ -16,7 +16,7 @@ if (defined("_INC_COMPILO_API")) return;
 define("_INC_COMPILO_API", "1");
 
 
-// Definition des classes Boucle, Texte, Inclure, Champ
+// Definition des noeuds de l'arbre de syntaxe abstraite
 
 class Texte {
 	var $type = 'texte';
@@ -73,7 +73,7 @@ class Champ {
 	var $nom_champ;
 	var $nom_boucle= ''; // seulement si boucle explicite
 	var $cond_avant, $cond_apres; // tableaux d'objets
-	var $fonctions;  // filtre explicites
+	var $fonctions = array();  // filtre explicites
 	var $etoile;
 	// champs pour la production de code
 	var $id_boucle;
@@ -90,6 +90,24 @@ class Champ {
 }
 
 
+class Idiome {
+	var $type = 'idiome';
+	var $chaine = ""; // la chaine a traduire
+	var $module = ""; // son module de definition
+	var $fonctions = array(); // les filtres a appliquer au resultat
+	// champs pour la production de code, cf ci-dessus
+	var $id_boucle;
+	var $boucles;
+	var $type_requete;
+	var $code;
+	var $statut;
+	var $descr = array();
+}
+
+class Polyglotte {
+	var $type = 'polyglotte';
+	var $traductions = array(); // les textes ou choisir
+}
 //
 // Globales de description de la base
 
diff --git a/inc-compilo-index.php3 b/inc-compilo-index.php3
index b18362708136e4c00076b83140835b0306304f53..2bc9b7aab11d3d15a91219404555035be006459a 100644
--- a/inc-compilo-index.php3
+++ b/inc-compilo-index.php3
@@ -288,25 +288,17 @@ function filtres_arglist($args, $p, $sep) {
 		else
 			ereg('^([[:space:]]*)([^,]*),?(.*)$', $args, $regs);
 
-		// $quote differencie {#ID_ARTICLE} et {'#ID_ARTICLE'}
-		$quote = trim($regs[1]);	// valeur = ", ', ou vide
-		$arg = $regs[2];			// le premier argument
-		$args = $regs[3];			// ceux qui restent
-		if ($quote)
+		$arg = $regs[2];		// le premier argument
+		$args = $regs[3];		// ceux qui restent
+		//  difference {#ID_ARTICLE} et {'#ID_ARTICLE'}
+		if ($regs[1]) // valeur = ", ', ou vide
 			$arg = "'" . texte_script($arg) . "'";
 		else {
-			$arg = trim($arg);
-			if ($arg[0] =='$')
-				$arg = '$Pile[0][\'' . substr($arg,1) . "']";
-			else if ($arg[0] =='<')
-				$arg = calculer_texte($arg, $p->id_boucle, $p->boucles);
-			else if (ereg("^" . NOM_DE_CHAMP ."(.*)$", $arg, $r2)) {
-				$p->nom_boucle = $r2[2];
-				$p->nom_champ = $r2[3];
-				$p->fonctions = phraser_filtres($r2[5]);
-				$arg = calculer_champ($p);
-			} else if (!is_numeric($arg))
-				$arg = "'" . texte_script($arg) . "'";
+		  // Appel recursif de calculer_liste sur des cas restreints
+		  $arg = calculer_liste(phraser_champs(trim($arg), array()),
+					$p->descr,
+					$p->boucles,
+					$p->id_boucle);
 		}
 
 		$arglist .= $sep . $arg;
diff --git a/inc-compilo.php3 b/inc-compilo.php3
index d7f339908f9509c099ab10ed726a607cfcf86e04..d9ffecf8db977b4e1c785908635eff79c7b0e3be 100644
--- a/inc-compilo.php3
+++ b/inc-compilo.php3
@@ -90,51 +90,6 @@ function calculer_inclure($fichier, $params, $id_boucle, &$boucles) {
 		"\n?'." . "'>'";
 }
 
-
-//
-// Traite une partie "texte" d'un squelette (c'est-a-dire tout element
-// qui ne contient ni balise, ni boucle, ni <INCLURE()> ; le transforme
-// en une EXPRESSION php (qui peut etre l'argument d'un Return ou la
-// partie droite d'une affectation). Ici sont analyses les elements
-// multilingues des squelettes : <:xxx:> et <multi>[fr]coucou</multi>
-//
-function calculer_texte($texte, $id_boucle, &$boucles) {
-	//
-	// Les elements multilingues
-	//
-	$code = "'".ereg_replace("([\\\\'])", "\\\\1", $texte)."'";
-
-	// bloc multi
-	if (eregi('<multi>', $texte)) {
-		$ouvre_multi = 'extraire_multi(';
-		$ferme_multi = ')';
-	} else {
-		$ouvre_multi = $ferme_multi = '';
-	}
-
-	// Reperer les balises de traduction <:toto:>
-	while (eregi("<:(([a-z0-9_]+):)?([a-z0-9_]+)(\|[^>]*)?:>", $code, $match)) {
-		//
-		// Traiter la balise de traduction multilingue
-		//
-		$chaine = strtolower($match[3]);
-		if (!($module = $match[2]))
-			// ordre standard des modules a explorer
-			$module = 'public/spip/ecrire';
-		$c = new Champ;
-		$c->code = "_T('$module:$chaine')";
-		$c->fonctions = phraser_filtres(substr($match[4],1));
-		$c->id_boucle = $id_boucle;
-		$c->boucles = &$boucles;
-		$c->statut = 'php'; // ne pas manger les espaces avec trim()
-		$c = applique_filtres($c);
-		$code = str_replace($match[0], "'$ferme_multi.$c.$ouvre_multi'", $code);
-	}
-
-	return $ouvre_multi . $code . $ferme_multi;
-}
-
-
 //
 // calculer_boucle() produit le corps PHP d'une boucle Spip 
 // (sauf les recursives)
@@ -424,6 +379,7 @@ function calculer_parties($partie, $mode_partie, $total_parties, $id_boucle) {
 // Production du code PHP a partir de la sequence livree par le phraseur
 // $boucles est passe par reference pour affectation par index_pile.
 // Retourne une expression PHP,
+// (qui sera argument d'un Return ou la partie droite d'une affectation).
 
 function calculer_liste($tableau, $descr, &$boucles, $id_boucle='', $niv=1) {
 	if (!$tableau) return "''";
@@ -437,13 +393,32 @@ function calculer_liste($tableau, $descr, &$boucles, $id_boucle='', $niv=1) {
 		switch($p->type) {
 		// texte seul
 		case 'texte':
-			$code = calculer_texte($p->texte, $id_boucle, $boucles);
+			$code = "'".ereg_replace("([\\\\'])", "\\\\1", $p->texte)."'";
+
 			$commentaire='';
 			$avant='';
 			$apres='';
 			$altern = "''";
 			break;
 
+		case 'polyglotte':
+			$code = "";
+			foreach($p->traductions as $k => $v) {
+			  $code .= ",'" .
+			    ereg_replace("([\\\\'])", "\\\\1", $k) .
+			    "' => '" .
+			    ereg_replace("([\\\\'])", "\\\\1", $v) .
+			    "'";
+			}
+			$code = "multi_trad(array(" .
+			  substr($code,1) .
+			  "))";
+			$commentaire= '';
+			$avant='';
+			$apres='';
+			$altern = "''";
+			break;
+
 		// inclure
 		case 'include':
 			$code= calculer_inclure($p->fichier,
@@ -473,7 +448,18 @@ function calculer_liste($tableau, $descr, &$boucles, $id_boucle='', $niv=1) {
 				$newdescr, $boucles, $id_boucle, $niv+1);
 			break;
 
-		// balise SPIP
+		case 'idiome':
+			$p->code = "_T('" . $p->module . ":" .$p->chaine. "')";
+			$p->id_boucle = $id_boucle;
+			$p->boucles = &$boucles;
+			$p->statut = 'php'; // ne pas manger les espaces avec trim()
+			$commentaire = "";
+			$code = applique_filtres($p);
+			$avant='';
+			$apres='';
+			$altern = "''";
+			break;
+
 		default: 
 
 			// cette structure pourrait etre completee des le phrase' (a faire)
diff --git a/inc-html-squel.php3 b/inc-html-squel.php3
index 414188dd7d5034b94ad2aefe5dc1d73601f4d8d2..bd643ac3e37d21b1d3894023de6bf609807adc7b 100644
--- a/inc-html-squel.php3
+++ b/inc-html-squel.php3
@@ -51,7 +51,7 @@ function phraser_inclure($texte, $result) {
 		$debut = substr($texte, 0, $p);
 		$texte = substr($fin, strlen($s));
 
-		if ($debut) $result = phraser_champs($debut, $result);
+		if ($debut) $result = phraser_idiomes($debut, $result);
 
 		$champ = new Inclure;
 		$champ->fichier = $match[1];
@@ -70,20 +70,64 @@ function phraser_inclure($texte, $result) {
 		$result[] = $champ;
 	}
 
-	return (!$texte ? $result : phraser_champs($texte, $result));
+	return (!$texte ? $result : phraser_idiomes($texte, $result));
 }
 
-function phraser_champs($texte,$result) {
-	while (ereg(NOM_DE_CHAMP . '(.*)$', $texte, $regs)) {
-	  $p = strpos($texte, $regs[0]);
-
-	  if ($regs[4] || (strpos($regs[5][0], "[0-9]") === false)) {
+function phraser_polyglotte($texte,$result) {
+	while (eregi('<multi>([^<]*)</multi>', $texte, $match)) {
+		$p = strpos($texte, $match[0]);
 		if ($p) {
 			$champ = new Texte;
 			$champ->texte = (substr($texte, 0, $p));
 			$result[] = $champ;
 		}
-		  
+
+		$champ = new Polyglotte;
+		$lang = '';
+		$bloc = $match[1];
+		$texte = substr($texte,$p+strlen($match[0]));
+		while (preg_match("/^[[:space:]]*([^[{]*)[[:space:]]*[\{\[]([a-z_]+)[\}\]](.*)$/si", $bloc, $regs)) {
+		  $trad = $regs[1];
+		  if ($trad OR $lang) 
+			$champ->traductions[$lang] = $trad;
+		  $lang = $regs[2];
+		  $bloc = $regs[3];
+		}
+		$champ->traductions[$lang] = $bloc;
+		$result[] = $champ;
+	}
+	if ($texte) {
+			$champ = new Texte;
+			$champ->texte = $texte;
+			$result[] = $champ;
+	}
+	return $result;
+}
+
+
+function phraser_idiomes($texte,$result) {
+	// Reperer les balises de traduction <:toto:>
+	while (eregi("<:(([a-z0-9_]+):)?([a-z0-9_]+)(\|[^>]*)?:>", $texte, $match)) {
+		$p = strpos($texte, $match[0]);
+		if ($p) $result = phraser_champs(substr($texte, 0, $p),$result);
+		$champ = new Idiome;
+		$champ->chaine = strtolower($match[3]);
+		$champ->module = $match[2] ? $match[2] : 'public/spip/ecrire';
+		$champ->fonctions = phraser_filtres(substr($match[4],1));
+		$texte = substr($texte,$p+strlen($match[0]));
+		$result[] = $champ;
+	}
+	if ($texte)  $result = phraser_champs($texte,$result);
+	return $result;
+}
+
+function phraser_champs($texte,$result) {
+	while (ereg(NOM_DE_CHAMP . '(.*)$', $texte, $regs)) {
+	  $p = strpos($texte, $regs[0]);
+
+	  if ($regs[4] || (strpos($regs[5][0], "[0-9]") === false)) {
+		if ($p)
+			$result = phraser_polyglotte(substr($texte, 0, $p), $result);
 		$champ = new Champ;
 		$champ->nom_boucle = $regs[2];
 		$champ->nom_champ = $regs[3];
@@ -92,18 +136,11 @@ function phraser_champs($texte,$result) {
 		$result[] = $champ;
 	  } else {
 	    // faux champ
-	    $champ = new Texte;
-	    $champ->texte = (substr($texte, 0, $p+1));
-	    $result[] = $champ;
+	    $result = phraser_polyglotte (substr($texte, 0, $p+1), $result);
 	    $texte = (substr($texte, $p+1));
 	  }
 	}
-	if ($texte) {
-	  
-		$champ = new Texte;
-		$champ->texte = $texte;
-		$result[] = $champ;
-	}
+	if ($texte) $result = phraser_polyglotte($texte, $result);
 	return $result;
 }
 
@@ -120,13 +157,12 @@ function phraser_champs_etendus($texte, $result) {
 	return array_merge($result, phraser_champs_interieurs($texte, $sep, array()));
 }
 
-
 function phraser_filtres($fonctions) {
   $r = array();
   if ($fonctions) {
     $fonctions = explode('|', ereg_replace("^\|", "", $fonctions));
     foreach($fonctions as $f) {
-      ereg("^([^{]*)?(.*)$",$f,$m);
+      ereg("^([^{]*)(.*)$",$f,$m);
       $arg = $m[2];
       $fonc = $m[1];
       $x = ereg("^\{(.*)\} *$",$arg,$m);
@@ -145,7 +181,6 @@ function phraser_champs_exterieurs($debut, $sep, $nested) {
 	    $debut = $m[2];
 	}
 	return (!$debut ?  $res : phraser_inclure($debut, $res));
-
 }
 
 function phraser_champs_interieurs($texte, $sep, $result) {
diff --git a/inc-urls-propres.php3 b/inc-urls-propres.php3
index 0a28a93bc9055048b95327eaecc1d1f07579a015..0975f428ef6f4714eed1f221786d4a71fd101442 100644
--- a/inc-urls-propres.php3
+++ b/inc-urls-propres.php3
@@ -73,9 +73,7 @@ function _generer_url_propre($type, $id_objet) {
 	// Sinon, creer l'URL
 	include_ecrire("inc_filtres.php3");
 	include_ecrire("inc_charsets.php3");
-	$url = translitteration(corriger_caracteres(
-		supprimer_tags(supprimer_numero(extraire_multi($row['titre'])))
-		));
+	$url = translitteration(corriger_caracteres(supprimer_tags(supprimer_numero($row['titre']))));
 	$url = @preg_replace(',[[:punct:][:space:]]+,u', ' ', $url);
 	// S'il reste des caracteres non latins, utiliser l'id a la place
 	if (preg_match(",[^a-zA-Z0-9 ],", $url)) {