Ein iOS-Layout-Gate mit Pseudolokalisierung auf einem Cloud-Mac aufbauen

Ein iOS-Layout-Gate mit Pseudolokalisierung auf einem Cloud-Mac aufbauen

Ein englischer Buttontext besteht auf dem Entwicklungsrechner vielleicht nur aus sechs Buchstaben, kann auf Deutsch aber eine ganze Zeile füllen. In einer Benutzeroberfläche mit Schreibrichtung von rechts nach links zeigt selbst der Zurück-Button möglicherweise weiterhin in die falsche Richtung. Werden solche Probleme erst nach Abschluss aller Übersetzungen entdeckt, wirken sich die Korrekturen gleichzeitig auf Layout, Tests und Veröffentlichungstermin aus. Robuster ist es, Zeichenfolgen bei jedem Merge-Check auf einem Cloud-Mac gezielt zu verlängern, die Layoutrichtung umzukehren und nicht sichtbare oder nicht bedienbare Steuerelemente direkt als Fehler zu werten.

Zuerst festlegen, was das Gate erkennen soll

Bei der Pseudolokalisierung geht es nicht darum, die Benutzeroberfläche durch schwer lesbare Zeichen zu ersetzen. Stattdessen werden Layoutrisiken mit kontrollierten Transformationen sichtbar gemacht. Dafür sollten zunächst zwei Modi eingerichtet werden:

Das Gate sollte sich auf vier Problemklassen konzentrieren: Der sichtbare Bereich eines Textelements ist deutlich zu klein; eine primäre Schaltfläche fehlt, ist nicht anklickbar oder liegt außerhalb des Fensters; die Oberfläche enthält hart codierte Texte, die den Lokalisierungsmechanismus umgehen; Navigation, horizontale Listen oder Richtungssymbole passen sich nicht an RTL an.

Pseudolokalisierung eignet sich dazu, strukturelle Probleme aufzudecken. Sie bewertet nicht die sprachliche Richtigkeit einer Übersetzung. Eine Abnahme mit echten Zielsprachen bleibt erforderlich, sollte aber nicht die Kosten für das Erkennen grundlegender Fehler wie fest definierter Breiten tragen.

Eine kontrollierbare Transformationsschicht für Zeichenfolgen aufbauen

Die Tests sollten nicht die Ansichtshierarchie durchlaufen und Beschriftungen nachträglich verändern. Dadurch würde der tatsächliche Abrufpfad für Zeichenfolgen umgangen, und hart codierte Texte blieben unentdeckt. Zuverlässiger ist es, den bestehenden Lokalisierungszugriff des Projekts um einen Transformer zu ergänzen, der ausschließlich in Debug-Builds aktiv ist.

enum PseudoMode: String {
    case expanded
    case rtl
}

#if DEBUG
func pseudoTransform(_ value: String, mode: PseudoMode?) -> String {
    guard let mode else { return value }

    switch mode {
    case .expanded:
        let padding = String(repeating: "·", count: max(2, value.count * 2 / 5))
        return "[\(value)\(padding)]"
    case .rtl:
        return "⁧\(value)⁩"
    }
}
#else
func pseudoTransform(_ value: String, mode: PseudoMode?) -> String {
    value
}
#endif

Beim Start liest die App PSEUDO_LOCALE ein und akzeptiert nur ausdrücklich definierte Enum-Werte. Alle für Benutzer sichtbaren Zeichenfolgen durchlaufen einen einheitlichen Zugriff, beispielsweise L10n.text("checkout.confirm"), und werden anschließend an den Transformer übergeben. Fehlen bei einer Überschrift die Begrenzungsmarkierungen, lässt sich daraus schließen, dass sie die Lokalisierungsschicht umgeht.

Im RTL-Modus muss außerdem die semantische Richtung in der Root-View der App gesetzt werden. Unter UIKit kann für das Testfenster semanticContentAttribute = .forceRightToLeft festgelegt werden; unter SwiftUI lässt sich layoutDirection in die Root-View injizieren. Bildinhalte dürfen dabei nicht gespiegelt werden. Auch Zahlen, Pfade und Codefragmente sollten nicht zwangsweise umgekehrt werden.

Sichtbarkeit und abgeschnittene Inhalte mit UI-Tests prüfen

Die Tests müssen nicht jedes Pixel vergleichen. Zunächst sollten zentrale Abläufe wie die Startseite nach der Anmeldung, Listen, Detailansichten, Formulare und Fehlerzustände abgedeckt werden. Primäre Steuerelemente benötigen dafür stabile Accessibility-Identifier. Der Testprozess startet den jeweiligen Modus über eine Umgebungsvariable:

func launchApp(mode: String) -> XCUIApplication {
    let app = XCUIApplication()
    app.launchEnvironment["PSEUDO_LOCALE"] = mode
    app.launchArguments += ["-UI_TESTING", "1"]
    app.launch()
    return app
}

func testExpandedCheckoutLayout() {
    let app = launchApp(mode: "expanded")
    let confirm = app.buttons["checkout.confirm"]

    XCTAssertTrue(confirm.waitForExistence(timeout: 8))
    XCTAssertTrue(confirm.isHittable)
    XCTAssertFalse(confirm.frame.isEmpty)
    XCTAssertTrue(app.windows.firstMatch.frame.contains(confirm.frame))
}

isHittable ist aussagekräftiger als ein einfacher exists-Check: Eine Schaltfläche kann im Accessibility-Baum vorhanden sein, obwohl sie von einem Overlay verdeckt wird oder außerhalb des Bildschirms liegt. Bei Beschriftungen lässt sich der Frame über den Identifier abrufen und darauf prüfen, ob er den zugehörigen Container überschreitet. OCR sollte nicht verwendet werden, um abgeschnittenen Text zu erraten. Stattdessen sollten wichtige Komponenten in Debug-Builds bevorzugt die Differenz zwischen intrinsicContentSize und dem tatsächlichen Frame bereitstellen.

Den Prüfumfang als Checkliste abbilden

SzenarioPrüfung im Modus expandedPrüfung im RTL-ModusFehlernachweis
NavigationsleisteTitel und Aktionen überlappen sich nichtZurück-Richtung und Reihenfolge sind korrektAnsichtshierarchie, Frames der Steuerelemente
FormularBeschriftungen können umbrechen, Fehlermeldungen sind vollständigBeschriftungen und Eingabefelder liegen auf derselben AchseFeld-Identifier, aktueller Wert
ListeLange Titel verdrängen die primäre Aktion nichtAnhänge und Pfeile sind korrekt positioniertIndex der fehlerhaften Zeile, Screenshot
OverlaySchaltflächen sind vollständig sichtbar und anklickbarDie Reihenfolge der Aktionen entspricht ihrer SemantikFrame des Overlays, Elementbaum

Die Ausführungsumgebung auf dem Cloud-Mac organisieren

Für jeden Auftrag sollte ein eigener Satz von Simulatorgeräten angelegt werden, damit parallele Jobs keinen gemeinsamen Startzustand verwenden. Das Verzeichnis des Gerätesatzes gehört in das temporäre Verzeichnis des jeweiligen Auftrags und muss nach Abschluss unabhängig von Erfolg oder Fehlschlag bereinigt werden. Vor dem Test sind Xcode-Pfad, Simulatormodell und Systemversion festzulegen und im Bericht zu dokumentieren.

set -euo pipefail

export DEVELOPER_DIR="/Applications/Xcode.app/Contents/Developer"
export PSEUDO_LOCALE="${PSEUDO_LOCALE:-expanded}"
RESULT_DIR="$PWD/TestResults/$PSEUDO_LOCALE"
mkdir -p "$RESULT_DIR"

xcodebuild test \
  -workspace App.xcworkspace \
  -scheme AppUITests \
  -destination "platform=iOS Simulator,name=iPhone 16,OS=latest" \
  -resultBundlePath "$RESULT_DIR/LayoutTests.xcresult" \
  PSEUDO_LOCALE="$PSEUDO_LOCALE"

expanded und rtl sollten als zwei separate Jobs ausgeführt werden, statt während derselben Simulatorsitzung zwischen ihnen umzuschalten. Dadurch lässt sich ein Fehler eindeutig einem Modus zuordnen, und Wiederholungen übernehmen nicht versehentlich die Layoutrichtung des vorherigen Laufs. Werden auf dem Cloud-Mac mehrere Jobs gleichzeitig ausgeführt, benötigt jeder Job außerdem einen eigenen DerivedData-Pfad, damit sich Testergebnisse nicht gegenseitig überschreiben.

Häufige Fehlalarme und echte Defekte behandeln

Die häufigsten Fehlalarme entstehen durch noch laufende Animationen, nicht geschlossene Systemdialoge oder Testdaten mit schwankender Länge. Die Lösung besteht nicht darin, eine einheitliche Wartezeit einzubauen, sondern auf konkrete Zustände zu warten: Der Ladeindikator ist verschwunden, das Zielelement wird angezeigt und die Anzahl der Fenster ist stabil. Auch die Testdaten müssen fest vorgegeben sein, insbesondere Benutzernamen, Datumswerte und serverseitige Fehlermeldungen.

Echte Fehler treten meist bei festen Höhen, einzeiligen Beschriftungen, manuell berechneten Breiten sowie Komponenten auf, deren Reihenfolge nur durch die visuelle Position ausgedrückt wird. Bei der Korrektur sollten vorrangig Einschränkungen entfernt werden, statt Sonderfälle für einzelne Sprachen einzubauen. Nachdem Zeilenumbrüche in Schaltflächentiteln zugelassen wurden, muss erneut geprüft werden, ob die anklickbare Fläche den vollständigen Text abdeckt.

Bei einem Fehler sollten mindestens die folgenden Inhalte archiviert werden:

  1. Aktueller Pseudolokalisierungsmodus sowie Xcode- und Simulatorversion.
  2. Identifier, Frame und Label des fehlerhaften Steuerelements sowie die Angabe, ob es anklickbar ist.
  3. Accessibility-Hierarchie der aktuellen Seite.
  4. Zugehöriges xcresult und Screenshot zum Fehlerzeitpunkt.
  5. Feste Testdaten, die zum Reproduzieren der Seite erforderlich sind.

Merge-Schwellenwerte festlegen und Leaks verhindern

Das Gate sollte Probleme blockieren, die den Abschluss einer Aufgabe verhindern: Eine primäre Aktion ist unsichtbar oder nicht anklickbar, abgeschnittener Text verliert seine Bedeutung, die RTL-Reihenfolge ist falsch oder einem interaktiven Steuerelement fehlt eine zugängliche Bezeichnung. Geringfügige Änderungen der Abstände können zur späteren Prüfung erfasst werden; nicht jede Pixelabweichung muss einen Merge blockieren.

Abschließend ist eine zusätzliche Release-Prüfung erforderlich: Build-Einstellungen und App-Ressourcen werden daraufhin durchsucht, ob sie ausschließlich für Tests vorgesehene Pseudolokalisierungsdateien enthalten. Außerdem muss verifiziert werden, dass Produktions-Builds PSEUDO_LOCALE ignorieren. Parallel dazu sollte im Normalmodus eine Reihe von Smoke-Tests ausgeführt werden, damit die Transformationsschicht nicht selbst Zeichenfolgen verändert.

Der Wert dieses Ablaufs besteht nicht darin, eine ungewöhnlich aussehende Benutzeroberfläche zu erzeugen. Er macht aus manuellen Stichproben mehrsprachiger Layouts kurz vor der Veröffentlichung eine wiederholbare technische Vorgabe für jede Codeänderung. Zuerst werden die wichtigsten Abläufe abgedeckt, danach schrittweise Fehlerzustände und selten verwendete Overlays. So bleibt das Gate stabil und wartbar.

Häufig gestellte Fragen

Ersetzt Pseudolokalisierung die Prüfung mit echten Sprachen?

Nein. Sie findet feste Breiten, unübersetzte Texte und RTL-Fehler früh, doch die Endabnahme sollte mindestens eine textreiche Sprache und eine RTL-Sprache umfassen.

Wie bleibt der Pseudolokalisierungs-Code aus dem Release?

Der Transformator wird nur unter DEBUG kompiliert. Ein Release-Build muss fehlschlagen, wenn PSEUDO_LOCALE oder ausschließlich für Tests gedachte Ressourcen enthalten sind.

Welche Layoutfehler sollten einen Merge blockieren?

Nicht sichtbare oder nicht bedienbare Hauptaktionen, sinnentstellende Kürzungen, eine falsche RTL-Reihenfolge und fehlende Accessibility-Beschriftungen müssen den Merge blockieren.

Dedizierter physischer Knoten

Den nächsten Build auf einen Mac mini in der Cloud verlagern

Wählen Sie Konfiguration, Abrechnungszeitraum und Knotenregion und führen Sie Entwicklungs-, Build- und Automatisierungsaufgaben auf einem dedizierten physischen Rechner aus.

Mac mini mieten