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.

Un placeholder est la partie d’une chaîne traduisible qui n’est pas traduisible. Chaque framework a inventé sa propre notation, sans qu’aucun ne s’accorde avec les autres, si bien qu’un produit typique se retrouve à porter trois ou quatre syntaxes à la fois — une pour l’application mobile, une pour le front-end web, une pour le backend, une pour un modèle d’e-mail.

Cette page rassemble le tableau de référence de toutes ces syntaxes, suivi des règles qui gardent les placeholders intacts pendant la traduction.

L’aide-mémoire

Syntaxe Nom Où vous la rencontrerez Exemple
%s %d %f spécificateurs de conversion printf C, gettext, PHP, Python, Java, Go, Android, Ruby Welcome back, %s
%1$s %2$d printf, positionnel par index Android (obligatoire avec 2 arguments ou plus), Java, PHP, gettext %1$s added %2$s
%@ spécificateur d’objet Objective-C iOS, macOS, fichiers .strings Hello, %@
%1$@ Objective-C, positionnel iOS avec plusieurs arguments %1$@ invited %2$@
%{name} interpolation nommée Ruby Rails I18n, fichiers YAML Ruby Hello, %{name}
%<count>d Ruby nommé, avec un spécificateur de format Rails I18n quand la valeur doit être formatée %<count>05d
%(name)s printf nommé Python formatage % Python, Django Hello, %(name)s
{name} Python str.format, .NET, ICU, Java str.format, f-strings, ICU MessageFormat Hello, {name}
{0} {1} Positionnel par index Java MessageFormat, .NET, C# {0} invited {1}
{0,number,integer} Java MessageFormat avec un type de format resource bundles Java {0,number,currency}
{{name}} interpolation de type Mustache Handlebars, Mustache, Angular, i18next, Vue Hello, {{name}}
{{{name}}} Mustache, non échappé sortie HTML brute Handlebars/Mustache {{{html_body}}}
%1 %2 marqueurs d’argument Qt fichiers .ts de Qt Linguist %1 of %2 files
$1 $2 rétro-référence de type regex Certaines bibliothèques JS, messages d’extensions Chrome Hello $1
<xliff:g id="name">%s</xliff:g> annotation de placeholder Android strings.xml encapsule un %s avec un nom et un exemple
# valeur plurielle ICU Uniquement à l’intérieur d’une branche plural ICU {n, plural, other {# files}}
$t(key) imbrication i18next i18next $t(common.appName)
${name} littéral de template / style shell chaînes de template JS, certains outils YAML `Hello ${name}`

printf, plus en détail

La famille printf est de loin la plus courante, et la lettre n’est pas décorative — elle déclare un type, et le runtime échouera si l’argument ne correspond pas :

Spécificateur Type Remarques
%s chaîne En Objective-C, cela signifie une chaîne C ; utilisez %@ pour un NSString
%d %i entier signé
%u entier non signé
%f flottant %.2f fixe deux décimales
%x %X hexadécimal
%% un signe pourcentage littéral L’échappement le plus souvent oublié
%@ objet Objective-C description est appelé sur l’objet

%% mérite sa propre ligne. "50% off" dans une chaîne parsée par printf est un bug : le % est lu comme le début d’un spécificateur. Il faut écrire "50%% off".

Ce qui casse vraiment

Réordonnancement. En anglais, on dit « %s added %s to the project ». L’allemand, le japonais et le turc peuvent avoir besoin d’inverser ces deux noms. Avec un %s nu, il n’y a aucun moyen d’exprimer cela — les arguments sont consommés dans leur ordre d’apparition. La forme numérotée %1$s corrige le problème, et les placeholders nommés le corrigent encore mieux.

Ressaisie. Les traducteurs travaillent dans un champ de texte. Un placeholder est une suite de signes de ponctuation qui n’a aucun sens dans leur langue, et elle se fait retaper, autocorriger, transformer en guillemets typographiques, voire traduire. %{count} devient %{compte}. {{name}} devient {{nom}}. Les deux ont l’air corrects, et aucun des deux ne se substitue.

Espaces et casse. %{ name } n’est pas %{name} en Ruby. {{Name}} n’est pas {{name}} en Handlebars. Rien ne vous prévient.

Incompatibilité de type. Inversez %d et %s dans une chaîne de format Java et vous obtenez une IllegalFormatConversionException à l’exécution. Faites-le en Objective-C et vous obtenez un comportement indéfini qui lit au-delà de l’argument.

Concaténation. Le plus subtil des cas, et c’est un bug côté source plutôt qu’un bug de traduction : construire une phrase à partir d’un fragment traduit plus une valeur formatée suppose que l’ordre du fragment tient dans toutes les langues. Ce n’est pas le cas. Mettez la phrase entière dans une seule chaîne, avec un placeholder à l’intérieur.

Les règles qui gardent les placeholders en vie

  1. Nommez-les partout où le format le permet. %{recipient} indique au traducteur ce qui apparaîtra à cet endroit ; %s ne lui dit rien, et {0} lui dit encore moins. Les placeholders nommés survivent aussi au réordonnancement, gratuitement.
  2. Ne découpez jamais une phrase entre plusieurs chaînes. Une phrase, un segment, avec les placeholders à l’intérieur.
  3. Donnez du contexte pour la valeur. « %d » peut être un nombre, une année ou un pourcentage, et la grammaire environnante diffère selon le cas. Un commentaire de développeur sur le segment coûte quelques secondes et évite toute une catégorie de mauvaises traductions.
  4. Utilisez la forme positionnelle dès qu’il y a deux arguments, même si la langue source n’en a pas besoin. C’est la seule chose qui rend le réordonnancement possible en aval.
  5. Validez mécaniquement, dans l’éditeur. Comparer l’ensemble des placeholders de la source à ceux de la cible est une vérification exacte et peu coûteuse — et c’est la seule qui détecte de façon fiable un placeholder traduit, car une personne qui lit pour le sens voit un mot français correct plutôt qu’une variable cassée.

Une phrase, plusieurs syntaxes

La même phrase dans un vrai produit multiplateforme :

text
Android   strings.xml     %1$s shared %2$d files
iOS       .strings        %1$@ shared %2$ld files
Rails     en.yml          %{user} shared %{count} files
React     i18next JSON    {{user}} shared {{count}} files
Java      .properties     {0} shared {1} files
ICU       any             {user} shared {count, plural, one {# file} other {# files}}

Six écritures, une seule phrase. Pour un traducteur, ce sont six tâches sans rapport entre elles, et pour une mémoire de traduction fondée sur la correspondance exacte de chaînes, ce sont six entrées sans rapport — la traduction japonaise faite pour Android est invisible quand la même phrase revient dans le fichier Rails.

C’est le problème que résout la mémoire de traduction normalisée de WebTranslateIt. Avant la comparaison, les placeholders sont retirés et les entités HTML décodées, si bien que les six variantes ci-dessus se normalisent vers le même texte et correspondent entre elles. Quand une correspondance est réutilisée, les placeholders sont réadaptés dans la syntaxe utilisée par le projet de destination — un %{user} Rails devient un %1$@ iOS à l’arrivée. Les correspondances normalisées sont identifiées comme telles et classées en dessous des correspondances exactes, si bien qu’une correspondance exacte l’emporte toujours ; la fonctionnalité ne nécessite aucune configuration, et la mémoire de traduction existante a été rétro-intégrée dans l’index.

Empêcher les placeholders d’être cassés dès le départ

La réutilisation ne résout que la moitié du problème. L’autre moitié, c’est que le placeholder survive tout court à l’aller-retour — et c’est le rôle de la validation au moment de la traduction, pas d’une étape de build après coup.

WebTranslateIt valide chaque traduction au moment où elle est enregistrée, en comparant l’ensemble des placeholders de la cible à celui de la source. Il signale quatre types d’erreurs distincts :

  • La source ne contient aucun placeholder, mais la traduction en a introduit un.
  • La source contient un placeholder et la traduction n’en a aucun.
  • La traduction contient des placeholders absents de la source — la signature d’un nom de placeholder traduit, %{name} devenant %{nom}.
  • Un placeholder est incorrect dans l’une des formes d’un segment pluriel, ce qui est facile à manquer car les autres formes ont l’air correctes.

Il reconnaît les syntaxes présentées sur cette page plutôt qu’un seul style maison — la famille printf, y compris les formes positionnelles et complétées (%s, %d, %1$s, %02d, %,d), le %@ d’Objective-C, le %{variable} de Ruby, les formes à accolade simple et double, les balises ERB (<%= @variable %>), l’imbrication i18next ($t(...)), et les conventions à délimiteurs comme @variable@ et {!1}. Cette étendue est précisément le but : un validateur qui ne connaît qu’une seule syntaxe est inutile sur une base de code qui en porte quatre.

Quand quelque chose échoue, l’éditeur ne se contente pas de refuser l’enregistrement. Il affiche la traduction avec chaque problème surligné dans sa propre couleur, accompagnée d’une liste des problèmes ; survoler un problème met en évidence le texte correspondant, et inversement, si bien que le traducteur voit exactement quelle séquence de caractères est fautive plutôt que de simplement se voir dire que la chaîne est invalide. Cette distinction compte quand la différence entre correct et incorrect tient à %{name} contre %{nom}.

À partir de là, le traducteur a trois options : corriger manuellement, ignorer l’avertissement s’il est sûr qu’il s’agit d’un faux positif, ou utiliser AutoCorrect — qui applique d’abord des corrections déterministes, restaurant les placeholders déformés dans la forme utilisée par la source, puis fait appel à l’IA pour les problèmes qui demandent un jugement contextuel.

La raison de faire cela dans l’éditeur plutôt qu’en CI, c’est que la personne capable de corriger correctement un placeholder déformé est le traducteur, et qu’il n’est disponible que quelques secondes. Un échec de build deux jours plus tard signifie rouvrir la tâche, expliquer le problème à travers une barrière de langue, et attendre — pour une erreur qui n’a qu’une seule bonne réponse.

Référence rapide par écosystème

Plateforme Syntaxe principale Également rencontré
Rails / Ruby %{name} %<name>s, %s
Django / Python %(name)s {name}, %s
Laravel / PHP :name %s, %1$s
Symfony / PHP %name% ICU via le domaine intl-icu
Java {0} %s, %1$s
.NET / C# {0} {name} dans les chaînes interpolées
Android %1$s wrappers <xliff:g>
iOS / Swift %@, %1$@ %d, %ld
React (i18next) {{name}} $t(key) pour l’imbrication
React (FormatJS) {name} ICU MessageFormat complet
Vue {name} {{ }} dans les templates, liaison @:key
Angular {{name}} ICU dans les messages $localize
Go %s, %v {{.Name}} dans les templates
Flutter {name} ICU complet via intl

Quand un projet couvre plusieurs lignes de ce tableau — et c’est le cas de la plupart — les placeholders ne sont pas un détail. Ils sont ce qui a le plus de chances d’être silencieusement faux dans la langue que vous ne savez pas lire.

Frequently asked questions

Que signifie %s dans un fichier de traduction ?
%s est un spécificateur de conversion printf qui signifie « insérer une chaîne ici ». Il vient du C et apparaît dans gettext, Java, Python, PHP, Android et Go. La lettre définit le type : %s pour une chaîne, %d pour un entier, %f pour un flottant. Il est positionnel — le premier %s prend le premier argument — c’est pourquoi les traductions qui ont besoin d’un ordre des mots différent utilisent plutôt la forme numérotée %1$s.
Quelle est la différence entre {name} et {{name}} ?
Les accolades simples sont utilisées par ICU MessageFormat, Java MessageFormat, Python str.format et .NET. Les doubles accolades sont utilisées par Mustache, Handlebars, l’interpolation Angular et i18next. Ce sont des syntaxes sans rapport qui se ressemblent par coïncidence, si bien qu’une valeur copiée d’un système vers l’autre s’affichera comme du texte littéral au lieu d’être substituée.
Pourquoi %1$s apparaît-il à la place de %s ?
Le numéro rend le placeholder positionnel par index plutôt que par ordre d’apparition, ce qui permet à une traduction de réordonner les arguments. Les langues n’ont pas toutes le même ordre des mots, et une phrase comme « %s added %s to the project » peut nécessiter d’inverser ses deux noms. Sans l’index, il n’y a aucun moyen d’exprimer cela. Android exige la forme numérotée dès qu’une chaîne comporte plus d’un argument.
Que se passe-t-il si un traducteur supprime un placeholder ?
Cela dépend du runtime : un argument manquant peut s’afficher en blanc, imprimer le placeholder brut, ou provoquer un crash avec une exception de formatage. Java et Objective-C sont les plus sévères — un spécificateur de format erroné en Objective-C lit la mémoire adjacente. C’est pourquoi la validation des placeholders doit se faire dans l’outil de traduction, là où la personne capable de la corriger a encore la chaîne sous les yeux.

Keep reading

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.