%@, %1$@, %lld : les format specifiers qui cassent en traduction

Mis à jour le · GLabs325

Une chaîne iOS avec une variable contient un %@ ou un %lld. Le traducteur doit le garder, au bon endroit et du bon type. Les erreurs qui reviennent le plus souvent, dont une propre au français, et comment les repérer.

D'où viennent ces %

Dans une app iOS, une chaîne localisée avec une variable devient une chaîne de format. En SwiftUI, c'est implicite :

Text("\(count) items left")                  // clé : "%lld items left"
Text("Welcome back, \(name)!")               // clé : "Welcome back, %@!"
String(localized: "Total: \(price) €")       // price est un Double : "Total: %lf €"

Un Int donne %lld, une String donne %@, un Double donne %lf. Avec l'ancienne API, on écrit le format soi-même : String(format: NSLocalizedString("items_left", comment: ""), count), et le .strings contient %d ou %ld.

Dans les deux cas, le traducteur reçoit un texte avec des % au milieu, et c'est là que les choses cassent.

Trois façons de casser une traduction

Supprimer le specifier. "Invitez à modifier cette liste" au lieu de "Invitez %@ à modifier cette liste". Rien ne plante : l'argument est simplement ignoré, et le nom de la personne disparaît de l'écran. C'est l'erreur la plus fréquente et la plus discrète.

Changer le type. Remplacer %lld par %@, ou l'inverse. Le format lit alors l'argument comme un autre type : au mieux un nombre absurde, au pire un plantage, puisque %@ attend un objet là où on lui passe un entier.

Changer l'ordre sans le dire. "%@ a partagé %@" devient, dans une langue où l'ordre des mots s'inverse, une phrase où les deux noms sont intervertis. La syntaxe est correcte, le sens est faux.

Réordonner avec les positions

Pour changer l'ordre, on numérote : %1$@ est le premier argument, %2$@ le deuxième, quelle que soit leur place dans la phrase.

/* source */      "%@ shared %@ with you" = "%1$@ shared %2$@ with you";
/* traduction */  "%@ shared %@ with you" = "%1$@ vous a partagé %2$@";
/* autre ordre */ "%@ shared %@ with you" = "%2$@ : partagé par %1$@";

Une règle à retenir : dans une même chaîne, tout est numéroté ou rien ne l'est. Mélanger %1$@ et %@ donne un comportement indéfini.

Le piège français : « 20 % de remise »

En typographie française, on écrit « 20 % de remise », avec une espace avant le signe. Dans une chaîne de format, c'est un problème : % d (pourcent, espace, d) est un specifier valide. L'espace est un drapeau printf (« mettre une espace devant les nombres positifs ») et d la conversion entière. La phrase

"Profitez de %lld % de remise"

contient donc deux specifiers, %lld et % d, pour un seul argument. Le second lit une valeur qui n'existe pas. La bonne écriture double le signe :

"Profitez de %lld %% de remise"

Le piège ne concerne que les chaînes qui passent par un format, c'est-à-dire celles qui ont au moins une variable. « 20 % de remise » en texte fixe, sans argument, s'affiche tel quel. Mais une traduction évolue : le jour où quelqu'un ajoute une variable à la phrase, le % seul devient un bug.

Les pluriels

Pour « 1 article / 3 articles », n'écrivez pas deux clés et un if dans le code. Dans un String Catalog, la clé %lld items left reçoit des variantes de pluriel (one, other, et few ou many selon les langues), chacune avec son propre %lld. Quand une phrase contient deux nombres qui varient chacun, Xcode utilise des substitutions (%#@items@) : un cas plus rare, à éditer dans Xcode.

Vérifier avant de livrer

La vérification de base est mécanique : pour chaque traduction, la liste des specifiers (type et position) doit correspondre à celle de la source. C'est ce que fait la QA de LocalePilot sur les fichiers .strings et .xcstrings : elle reconnaît %@, %1$@, %d, %ld, %lld, %.2f, %u, %i et %s, traite %% comme du texte, et signale un specifier manquant ou en trop avant l'export. Elle ignore volontairement les conversions rares comme %e ou %x, justement pour ne pas prendre « 20%en » pour une erreur. Le cas « % d » du paragraphe précédent, lui, se repère à la relecture : cherchez les % suivis d'une espace dans vos traductions françaises.

Questions fréquentes

Quelle différence entre %d, %ld et %lld ?

La taille de l'entier attendu. SwiftUI utilise %lld pour un Int interpolé ; dans un .strings écrit à la main, gardez le specifier de la source.

Comment écrire un signe % dans une chaîne traduite ?

Dans une chaîne qui contient des variables, doublez-le : %%. Sinon, « % d » ou « % s » peuvent être lus comme des specifiers.

Un traducteur peut-il changer l'ordre des variables ?

Oui, avec les positions : %2$@ puis %1$@. Mais toute la chaîne doit alors être numérotée.

Autres guides