La plupart des syntaxes de placeholders ne font que substituer : vous leur donnez une valeur et elles la déposent dans un emplacement. ICU MessageFormat fait quelque chose de différent — il permet à la structure de la phrase de dépendre de la valeur. Une seule chaîne, et la forme du pluriel, le pronom genré et le formatage du nombre se résolvent tous au moment du rendu, selon ce dont la langue cible a besoin.
Cette puissance est la raison de son existence, et la raison pour laquelle il est facile de s’y tromper. Ce guide couvre la syntaxe que vous utiliserez réellement, la règle d’échappement qui piège tout le monde, et les erreurs précises qui survivent à la relecture et atteignent la production.
La forme d’un message
Dans sa forme la plus simple, un argument entre accolades :
Bonjour, {name} !
Ajoutez un type après une virgule et l’argument est formaté plutôt qu’interpolé :
Vous avez {count, number} messages non lus.
Dernière synchro {when, date, medium} à {when, time, short}.
{amount, number, ::currency/EUR} facturés aujourd’hui.
Le préfixe :: introduit un squelette — une description compacte du format, résolue par locale. ::currency/EUR affiche €1,234.50 en anglais et 1 234,50 € en français sans que vous n’ayez à écrire l’un ou l’autre.
Ajoutez un style entre accolades et vous obtenez du branchement. Les deux que vous utiliserez constamment sont plural et select.
Les pluriels
{count, plural,
one {Vous avez # message non lu}
other {Vous avez # messages non lus}
}
Trois choses se produisent :
countest comparé aux règles de pluriel CLDR de la langue cible pour choisir une catégorie.- Les noms de catégories —
zero,one,two,few,many,other— sont ceux de CLDR. Ce sont des étiquettes pour des ensembles de nombres, pas des quantités ;oneen français couvre 0 aussi bien que 1. #est remplacé par la valeur formatée decount, localisée. Pas{count}—#, qui applique le formatage numérique de la locale.
other est obligatoire. Un message qui ne l’a pas ne compilera pas.
Vous pouvez aussi cibler un nombre exact, qui prime sur la catégorie :
{count, plural,
=0 {Aucun message}
one {# message}
other {# messages}
}
Utilisez =0 quand le cas vide a besoin d’un texte différent, pas simplement d’une forme de pluriel différente. « Aucun message » est un meilleur état vide que « 0 message », et dans une langue où one couvre zéro, c’est le seul moyen de l’exprimer.
La conséquence critique pour la traduction : le nombre de branches est une propriété de la langue cible. L’anglais vous donne one et other ; le polonais a besoin de one, few, many, other ; l’arabe a besoin des six. Un traducteur qui travaille à partir d’une source anglaise à deux branches doit produire une cible à quatre ou six branches, et tout outillage qui copie la structure source produit des messages cassés.
La sélection par genre ou autre chose
select compare un argument de type chaîne à des clés fixes :
{gender, select,
female {Elle a mis à jour son profil}
male {Il a mis à jour son profil}
other {Cette personne a mis à jour son profil}
}
other est requis ici aussi, et ce n’est pas un synonyme de « inconnu » — traitez-le comme le cas qui doit rester correct quand la valeur est absente, inattendue, ou d’un genre que votre liste de clés n’énumère pas.
select ne se limite pas au genre. C’est un aiguillage de chaînes général, utile pour les formules d’abonnement, les types d’entités, ou toute branche où les alternatives sont des phrases véritablement différentes plutôt qu’un nom substitué.
Les ordinaux
{place, selectordinal,
one {#st}
two {#nd}
few {#rd}
other {#th}
}
selectordinal utilise les règles ordinales de la langue, un jeu de données CLDR distinct des règles cardinales. L’anglais a besoin de quatre catégories ordinales et de deux seulement au cardinal — un rappel utile que « combien de formes de pluriel cette langue a-t-elle » a deux réponses différentes selon la question posée.
L’imbrication
Les branches contiennent un texte de message arbitraire, y compris d’autres arguments et d’autres branches :
{count, plural,
=0 {{name} n’a partagé aucun fichier pour l’instant}
one {{name} a partagé # fichier avec {recipients, plural,
one {# personne}
other {# personnes}
}}
other {{name} a partagé # fichiers avec {recipients, plural,
one {# personne}
other {# personnes}
}}
}
C’est légal, et c’est aussi là que la lisibilité s’effondre. Une limite pratique : imbriquez un seul niveau, et si vous avez besoin de deux, demandez-vous d’abord si la phrase devrait être découpée ou restructurée. Chaque niveau d’imbrication multiplie le nombre de branches que le traducteur doit remplir, et dans une langue à six formes, un pluriel doublement imbriqué représente 36 cellules.
Notez que le # intérieur, dans un pluriel imbriqué, se réfère à l’argument du pluriel le plus intérieur. Si vous avez besoin de celui de l’extérieur, nommez-le explicitement : {count}.
L’échappement — la règle que tout le monde rate
Les accolades et apostrophes littérales ont besoin d’être échappées, et le mécanisme utilise les guillemets simples :
| Vous voulez | Vous écrivez | Remarques |
|---|---|---|
{ littérale |
'{' |
La paire de guillemets échappe l’accolade |
} littérale |
'}' |
|
' littérale |
'' |
Deux apostrophes, pas une barre oblique inverse |
{ dans un littéral plus long |
'{not an argument}' |
Une paire de guillemets peut couvrir toute une séquence |
Le mode d’échec est précis et vicieux : une seule apostrophe non appariée ouvre une section entre guillemets qui court jusqu’à la fin du message ou jusqu’à la prochaine apostrophe. Les traductions françaises et italiennes sont pleines d’apostrophes — l'utilisateur, dell'account — donc un traducteur qui écrit naturellement peut désactiver silencieusement chaque placeholder après la première élision.
✗ Cassé : L'utilisateur {name} a {count} fichiers
→ tout ce qui suit L' est traité comme du texte littéral
✓ Correct : L''utilisateur {name} a {count} fichiers
Certaines implémentations ne traitent une apostrophe comme un échappement que lorsqu’elle précède {, } ou #, ce qui rend le bug dépendant de la locale et de la bibliothèque — il fonctionne dans un moteur de rendu et casse dans un autre. Doubler l’apostrophe est correct partout.
Ce que les traducteurs ratent vraiment
Quatre modes d’échec expliquent la quasi-totalité des messages ICU cassés, et un seul est réellement la faute du traducteur :
- Traduire les mots-clés. Les mots de la syntaxe font partie de la syntaxe, pas du texte. Un traducteur qui voit
plural,one,otherdans un champ de texte suppose raisonnablement que ce sont des mots.{count, pluriel, un {# article} autre {# articles}}est un message qui ne compile plus — et c’est une chose tout à fait compréhensible à avoir écrite. - Des catégories de pluriel manquantes. La source a deux branches, la langue cible a besoin de quatre, et deux ne sont jamais écrites.
- La perte du
#. Il se lit comme de la ponctuation, donc il est supprimé ou remplacé par un chiffre littéral. - Des apostrophes non échappées, comme ci-dessus.
Aucune de ces erreurs n’est détectable en lisant la traduction, car un message ICU cassé ressemble à un texte raisonnable. Elles sont détectables en le parsant, ce qui est l’argument en faveur de la validation des messages dans l’outil de traduction plutôt qu’au moment du build — la personne qui peut corriger un mot-clé mal traduit, c’est le traducteur, et il est déjà passé à autre chose quand la CI échoue.
WebTranslateIt parse les messages ICU au moment de leur enregistrement : il détecte les mots-clés mal traduits dans 12 langues et propose de les corriger automatiquement, signale les catégories de pluriel manquantes ou en trop par rapport aux règles CLDR de la langue cible, et remonte en ligne les erreurs de syntaxe, les incompatibilités de type et les références # manquantes. Ses moteurs de traduction automatique Mistral et Gemini sont eux aussi conscients d’ICU : traduire un message anglais à deux formes vers le polonais l’étend aux quatre formes que requiert le polonais, en ne traduisant que le texte lisible et en laissant la structure intacte.
Où ICU MessageFormat est pris en charge
| Plateforme | Prise en charge |
|---|---|
| Java | com.ibm.icu.text.MessageFormat (ICU4J). Le java.text.MessageFormat natif du JDK est un sous-ensemble bien plus ancien et incompatible |
| C / C++ | ICU4C |
| JavaScript | intl-messageformat / FormatJS, messageformat.js ; Intl.MessageFormat est sur la voie de la normalisation |
| React | react-intl (FormatJS) |
| Vue | vue-i18n avec le compilateur de messages ICU |
| Ruby | via les gems twitter_cldr ou message_format ; Rails I18n ne le prend pas en charge nativement |
| Python | PyICU, ou babel pour le sous-ensemble formatage |
| Android | Pas natif — ressources plurals uniquement |
| iOS | Pas natif — .stringsdict uniquement |
Les deux dernières lignes expliquent pourquoi les équipes multiplateformes adoptent souvent une bibliothèque ICU sur mobile plutôt que le format natif de la plateforme : c’est le seul moyen de conserver une seule syntaxe de message, et donc une seule mémoire de traduction, sur le web et le mobile.
Un guide de style qui fonctionne
- Nommez vos arguments.
{count}survit à une réorganisation et se lit dans un éditeur de traduction ;{0}non. - Mettez toute la phrase dans un seul message. Concatener un fragment traduit à un nombre formaté va à l’encontre du principe même.
- Fournissez toujours
other, et faites en sorte qu’il reste correct pour les valeurs inattendues. - Utilisez
=0pour les états vides qui ont besoin d’un texte différent, pas simplement d’une forme de pluriel différente. - Doublez chaque apostrophe dans le texte source, pour que les traducteurs héritent de la convention plutôt que de la découvrir.
- Validez à l’enregistrement, pas au moment du build.
Frequently asked questions
- Qu’est-ce que ICU MessageFormat ?
- ICU MessageFormat est une syntaxe pour écrire une seule chaîne traduisible qui s’adapte à ses arguments — en choisissant une forme de pluriel, en sélectionnant selon le genre, et en formatant les nombres et les dates selon la locale. Elle vient du projet International Components for Unicode et est implémentée dans ICU4J, ICU4C, FormatJS, messageformat.js et la plupart des bibliothèques i18n modernes.
- Comment échapper une accolade dans ICU MessageFormat ?
- Entourez le texte littéral de guillemets simples : '{' produit une accolade ouvrante littérale. Pour produire une apostrophe littérale, doublez-la : ''. C’est la source de confusion la plus fréquente, car dans les textes en français et en italien les apostrophes sont partout, et une apostrophe non appariée avale silencieusement le reste du message.
- Quelle est la différence entre plural et selectordinal ?
- plural utilise les règles cardinales d’une langue — un fichier, deux fichiers. selectordinal utilise ses règles ordinales — 1er, 2e, 3e. Les catégories se ressemblent, mais les ensembles qu’elles recouvrent sont différents : l’anglais a besoin de deux formes cardinales et de quatre formes ordinales.
- ICU MessageFormat fonctionne-t-il avec Android et iOS ?
- Pas nativement. Android utilise sa propre ressource plurals et iOS utilise .stringsdict, qui couvrent tous les deux les pluriels mais pas l’imbrication, la sélection et le formatage en ligne qu’offre ICU. Les équipes qui veulent une seule syntaxe de message sur le web, Android et iOS adoptent généralement une bibliothèque ICU sur chaque plateforme plutôt que le format natif de la plateforme.
Keep reading
-
Règles de pluriel par langue : le guide CLDR complet
Un tableau de référence des catégories de pluriel CLDR pour 163 langues, plus pourquoi one ne veut pas dire 1 et comment chaque framework i18n attend que vous écriviez les règles.
-
Aide-mémoire des formats de placeholders : %s, %@, %{name}, {{name}}, {0}
Toutes les syntaxes de placeholder que vous rencontrerez, quel langage ou framework les utilise, et ce qui casse quand un traducteur en retape une à la main.
-
Les validations de traduction dans WebTranslateIt (documentation)
Les contrôles qui interceptent un message ICU cassé avant qu’il n’atteigne la production.
Translate your app without the spreadsheet round-trip
WebTranslateIt reads the file formats and placeholder syntax described on this page, validates them as translators work, and syncs the results straight back into your repository.