%@, %1$@, %lld: Formatbezeichner, die beim Übersetzen kaputtgehen

Aktualisiert am · LocalePilot

Ein iOS-String mit einer Variable enthält ein %@ oder ein %lld. Der Übersetzer muss es behalten, an der richtigen Stelle und mit dem richtigen Typ. Hier sind die Fehler, die immer wiederkehren, darunter einer, der Deutsch und Französisch betrifft, und wie Sie sie erkennen.

Wenn es schnell gehen soll: Prüfen Sie die Platzhalter einer Übersetzung direkt im Browser, kostenlos, ohne Anmeldung und ohne Upload.

Woher das % kommt

In einer iOS-App wird ein lokalisierter String mit einer Variable zu einem Format-String. In SwiftUI geschieht das implizit:

Text("\(count) items left")                  // key: "%lld items left"
Text("Welcome back, \(name)!")               // key: "Welcome back, %@!"
String(localized: "Total: \(price) €")       // price is a Double: "Total: %lf €"

Ein Int ergibt %lld, ein String ergibt %@, ein Double ergibt %lf. Mit der älteren API schreiben Sie das Format selbst: String(format: NSLocalizedString("items_left", comment: ""), count), und die .strings-Datei enthält %d oder %ld.

So oder so bekommt der Übersetzer Text mit %-Zeichen mittendrin, und genau dort geht etwas kaputt.

Drei Arten, eine Übersetzung kaputtzumachen

Den Bezeichner weglassen. "Invitez à modifier cette liste" statt "Invitez %@ à modifier cette liste". Nichts stürzt ab: Das Argument wird einfach ignoriert, und der Name der Person verschwindet vom Bildschirm. Das ist der häufigste und unauffälligste Fehler.

Den Typ ändern. %lld durch %@ ersetzen oder umgekehrt. Das Format liest das Argument dann mit dem falschen Typ: im besten Fall eine unsinnige Zahl, im schlimmsten Fall ein Absturz, weil %@ ein Objekt erwartet, wo es eine Ganzzahl bekommt.

Die Reihenfolge stillschweigend ändern. "%@ shared %@" wird in einer Sprache mit anderer Wortstellung zu einem Satz, in dem die beiden Namen vertauscht sind. Die Syntax stimmt, der Sinn nicht.

Mit Positionen umstellen

Um die Reihenfolge zu ändern, nummerieren Sie die Argumente: %1$@ ist das erste Argument, %2$@ das zweite, egal wo sie im Satz stehen.

/* source */      "%@ shared %@ with you" = "%1$@ shared %2$@ with you";
/* French */      "%@ shared %@ with you" = "%1$@ vous a partagé %2$@";
/* other order */ "%@ shared %@ with you" = "%2$@ : partagé par %1$@";

Eine Regel zum Merken: Innerhalb eines Strings wird entweder alles nummeriert oder nichts. %1$@ und %@ zu mischen ist undefiniertes Verhalten.

Eine Falle im Französischen (und im Deutschen): „20 % Rabatt“

Wenn Sie auf Französisch oder Deutsch veröffentlichen, sollten Sie diese kennen. Beide Typografien setzen ein Leerzeichen vor das Prozentzeichen: „20 % de remise“, „20 % Rabatt“. In einem Format-String ist das ein Problem, denn % d (Prozent, Leerzeichen, d) ist ein gültiger Bezeichner: Das Leerzeichen ist ein printf-Flag („positive Zahlen mit einem Leerzeichen auffüllen“) und d die Ganzzahl-Konvertierung. Der Satz

"Profitez de %lld % de remise"

enthält also zwei Bezeichner, %lld und % d, für ein einziges Argument. Der zweite liest einen Wert, den es nicht gibt. Die korrekte Form verdoppelt das Zeichen:

"Profitez de %lld %% de remise"

Im Deutschen ist % R zwar kein gültiger Bezeichner, aber auch dort gilt: In einem String mit Variablen schreiben Sie %%. Das betrifft nur Strings, die durch ein Format laufen, also Strings mit mindestens einer Variable. Ein festes „20 % Rabatt“ ohne Argument wird so angezeigt, wie es geschrieben ist. Übersetzungen ändern sich aber: An dem Tag, an dem jemand diesem Satz eine Variable hinzufügt, wird das einzelne % zum Fehler.

Pluralformen

Für „1 Artikel / 3 Artikel“ schreiben Sie nicht zwei Schlüssel und ein if in Ihren Code. In einem String Catalog bekommt der Schlüssel %lld items left Pluralvarianten (one, other, je nach Sprache zusätzlich few oder many), jede mit ihrem eigenen %lld. Hat ein Satz zwei Zahlen, die unabhängig voneinander variieren, nutzt Xcode Substitutionen (%#@items@): ein seltenerer Fall, der in Xcode bearbeitet wird.

Vor dem Release prüfen

Die Grundprüfung ist mechanisch: Für jede Übersetzung muss die Liste der Bezeichner (Typ und Position) mit dem Ausgangstext übereinstimmen. Genau das macht die QA von LocalePilot bei .strings- und .xcstrings-Dateien: Sie erkennt %@, %1$@, %d, %ld, %lld, %.2f, %u, %i und %s, behandelt %% als Text und markiert vor dem Export einen fehlenden oder überzähligen Bezeichner. Seltene Konvertierungen wie %e oder %x ignoriert sie bewusst, damit „20%en“ nicht für einen Fehler gehalten wird. Den Fall „% d“ oben müssen Sie selbst prüfen: Suchen Sie in Ihren französischen und deutschen Übersetzungen nach einem %, auf das ein Leerzeichen folgt.

Häufige Fragen

Was ist der Unterschied zwischen %d, %ld und %lld?

Die Größe der erwarteten Ganzzahl. SwiftUI verwendet %lld für ein interpoliertes Int; in einer handgeschriebenen .strings-Datei behalten Sie den Bezeichner des Ausgangstexts bei.

Wie schreibe ich ein %-Zeichen in einem übersetzten String?

In einem String mit Variablen verdoppeln Sie es: %%. Sonst werden „% d“ oder „% s“ womöglich als Bezeichner gelesen.

Darf ein Übersetzer die Reihenfolge der Variablen ändern?

Ja, mit Positionen: %2$@, dann %1$@. Dann muss aber der ganze String nummeriert sein.