ENGINEERING-NOTIZ

Eine String-Catalog-Prüfung auf dem Cloud-Mac einrichten

Eine String-Catalog-Prüfung auf dem Cloud-Mac einrichten

Während eines Release-Sprints wurden im Entwicklungsbranch mehr als zehn neue UI-Texte ergänzt. Der Archivierungsjob lief ohne Fehler durch, doch nach einem Sprachwechsel sahen die Tester englische Fallbacks und leere Schaltflächen. Die Ursache lag nicht beim Compiler. Vielmehr hatte die Pipeline die Vollständigkeit der Lokalisierung nie als Prüfung behandelt, die einen Job fehlschlagen lassen kann. Bei einem dauerhaft betriebenen Cloud-Mac ist es am wirkungsvollsten, nicht auf eine manuelle Kontrolle sämtlicher Ansichten zu warten, sondern die .xcstrings-Dateien vor jedem Build direkt zu prüfen.

Festlegen, was die Prüfung blockieren soll

Ein String Catalog ist eine JSON-Datei. Dass sie sich parsen lässt, bedeutet jedoch nicht, dass sie auslieferungsbereit ist. Eine praxistaugliche Prüfung sollte mindestens drei Problemklassen erkennen: Eine vom Projekt geforderte Zielsprache fehlt, der Übersetzungsstatus lautet nicht translated oder die endgültige Zeichenfolge ist leer. Pluralformen, gerätespezifische Varianten und weitere Inhalte befinden sich außerdem unter variations. Wer nur stringUnit auf der ersten Ebene ausliest, übersieht daher Fehler.

Die Abnahmekriterien sollten im Repository hinterlegt werden und nicht in einer temporären Konfiguration des Build-Knotens verbleiben. Angenommen, die Ausgangssprache des Projekts ist Englisch und derzeit werden vereinfachtes Chinesisch, Japanisch und Französisch verlangt. Dann lassen sich die Zielsprachen als Parameter an das Skript übergeben.

Prüfpunkt Fehlerbedingung Behandlung
Zielsprache Der zugehörige Schlüssel fehlt in localizations Build blockieren
Übersetzungsstatus Mindestens ein Blattknoten ist nicht translated Konkreten Schlüssel und Sprache ausgeben
Zeichenfolgeninhalt value ist leer oder enthält nur Leerraum Build blockieren
Nicht zu übersetzen shouldTranslate ist false Explizit überspringen

Die Prüfung stellt nur fest, ob die Lokalisierungsressourcen im Katalog vollständig sind. Sie ersetzt nicht die manuelle Abnahme von abgeschnittenen UI-Texten, dynamischen Parametern und der inhaltlichen Richtigkeit.

Einen Validator ohne externe Abhängigkeiten schreiben

Speichern Sie das Skript unter Scripts/check_xcstrings.py. Es benötigt lediglich die in macOS enthaltene Python-Laufzeit. Falls das Team einen festen, separaten Python-Pfad verwendet, sollte dieser in der Build-Konfiguration ausdrücklich angegeben werden. So entstehen keine Unterschiede zwischen dem PATH der interaktiven Shell und dem der CI.

#!/usr/bin/env python3
import argparse
import json
import pathlib
import sys

def units(node):
    found = []
    if isinstance(node, dict):
        unit = node.get("stringUnit")
        if isinstance(unit, dict):
            found.append(unit)
        for key, value in node.items():
            if key != "stringUnit":
                found.extend(units(value))
    elif isinstance(node, list):
        for value in node:
            found.extend(units(value))
    return found

parser = argparse.ArgumentParser()
parser.add_argument("root")
parser.add_argument("--locale", action="append", required=True)
args = parser.parse_args()

failures = []
files = sorted(pathlib.Path(args.root).rglob("*.xcstrings"))

if not files:
    failures.append("no .xcstrings files found")

for path in files:
    with path.open(encoding="utf-8") as handle:
        catalog = json.load(handle)

    for key, entry in catalog.get("strings", {}).items():
        if entry.get("shouldTranslate") is False:
            continue

        localizations = entry.get("localizations", {})
        for locale in args.locale:
            localized = localizations.get(locale)
            if localized is None:
                failures.append(f"{path}:{key}:{locale}:missing locale")
                continue

            leaves = units(localized)
            if not leaves:
                failures.append(f"{path}:{key}:{locale}:missing stringUnit")
                continue

            for index, unit in enumerate(leaves):
                state = unit.get("state")
                value = unit.get("value", "")
                if state != "translated":
                    failures.append(
                        f"{path}:{key}:{locale}:{index}:state={state}"
                    )
                if not value.strip():
                    failures.append(
                        f"{path}:{key}:{locale}:{index}:empty value"
                    )

for failure in failures:
    print(failure, file=sys.stderr)

sys.exit(1 if failures else 0)

Führen Sie im Stammverzeichnis des Repositorys folgenden Befehl aus:

python3 Scripts/check_xcstrings.py . \
  --locale zh-Hans \
  --locale ja \
  --locale fr

Beendet sich das Skript mit einem Status ungleich null, kann jeder gängige Task-Orchestrator die nachfolgenden Schritte abbrechen. Die Ausgabe enthält Datei, String-Schlüssel, Sprache und Index des Blattknotens. Wer den Fehler behebt, muss daher nicht zuerst das vollständige Build-Protokoll durchsuchen.

Die Prüfung vor der Kompilierung in den Cloud-Mac-Ablauf einbinden

Die Prüfung sollte nach der Auflösung der Abhängigkeiten, aber vor xcodebuild build oder archive ausgeführt werden. Durch die Vorbereitung der Abhängigkeiten lässt sich sicherstellen, dass der gesamte Inhalt des Repositorys auf dem Datenträger vorliegt. Die frühzeitige Prüfung verhindert zugleich, dass für einen offensichtlich ungeeigneten Commit noch Kompilierung, Tests und Archivierung ausgeführt werden.

set -euo pipefail

mkdir -p build/reports
python3 Scripts/check_xcstrings.py Sources \
  --locale zh-Hans \
  --locale ja \
  --locale fr \
  2> build/reports/localization-errors.txt

xcodebuild \
  -project App.xcodeproj \
  -scheme App \
  -configuration Release \
  -destination 'generic/platform=iOS' \
  build

set -o pipefail ist besonders wichtig. Wird die Ausgabe später an tee weitergeleitet, sieht die Pipeline ohne diese Option möglicherweise nur den Erfolg des letzten Befehls und verschluckt den Fehlerstatus des Validators. Auch das Berichtsverzeichnis sollte zu Beginn des Jobs neu erstellt werden. Andernfalls könnte eine Datei aus einem früheren Lauf auf dem Cloud-Mac fälschlich als aktuelles Ergebnis gelten.

Die exportierten Ergebnisse zusätzlich manuell prüfen

Das direkte Parsen der .xcstrings-Dateien eignet sich für eine automatisierte Schranke. Vor der Veröffentlichung sollten jedoch zusätzlich die Lokalisierungspakete exportiert werden, um die Vollständigkeit der Entwicklungssprache, Kommentare und Kontextinformationen zu kontrollieren. Für die Zielsprachen können Sie Folgendes ausführen:

rm -rf build/xcloc
mkdir -p build/xcloc

xcodebuild -exportLocalizations \
  -project App.xcodeproj \
  -localizationPath build/xcloc \
  -exportLanguage zh-Hans

xcodebuild -exportLocalizations \
  -project App.xcodeproj \
  -localizationPath build/xcloc \
  -exportLanguage ja

Schlägt der Export fehl, sollte er nicht sofort erneut ausgeführt und damit der vorliegende Zustand überschrieben werden. Sichern Sie zunächst die vollständige Standardfehlerausgabe und prüfen Sie anschließend Scheme, Projektpfad und Kennung der Zielsprache. Der Sprachcode muss exakt mit dem Schlüssel im Catalog übereinstimmen. So sind zh-Hans und zh-Hant zwei eigenständige Ziele und dürfen nicht durch ein unscharfes Präfix ersetzt werden.

Die exportierten .xcloc-Dateien eignen sich für Stichproben durch die für Lokalisierung verantwortliche Person. Es empfiehlt sich jedoch nicht, diese Build-Artefakte in den Hauptbranch einzuchecken. Im Repository sollten die ursprünglichen .xcstrings-Dateien, das Prüfsystem und die Liste der Zielsprachen liegen. Das Exportverzeichnis bleibt dagegen ein Artefakt des jeweiligen Jobs.

Häufige Fehlalarme behandeln und die Abnahme abschließen

Die erste Art von Fehlalarm entsteht durch technische Zeichenfolgen, die nicht übersetzt werden sollen. Solche Einträge sollten im Skript nicht willkürlich anhand eines Schlüsselpräfixes ignoriert werden. Setzen Sie stattdessen im Catalog ausdrücklich shouldTranslate: false, damit die Regel an die Ressource selbst gebunden bleibt. Die zweite Art tritt auf, wenn bei einer Pluralform nur one, nicht aber other übersetzt wurde. Durch das rekursive Auslesen jedes stringUnit lässt sich die fehlende Variante genau lokalisieren.

Die dritte Art entsteht, wenn Entwickler eine neue Sprache hinzufügen, ohne die CI-Parameter zu aktualisieren. Die Liste der Zielsprachen sollte möglichst nur eine einzige Quelle haben, etwa ein Array im Projektskript oder eine Pipeline-Variable. So müssen nicht mehrere Jobs jeweils eine eigene Kopie pflegen. Die vierte Art betrifft Leerzeichen, Zeilenumbrüche oder Platzhalter, die fälschlich als gültige Übersetzung gelten. Das aktuelle Skript lehnt reinen Leerraum ab. Bei Formatparametern wie %@ und %d kann zusätzlich die Menge der Platzhalter in Ausgangs- und Zielzeichenfolge verglichen werden. Bevor diese Prüfung blockierend eingesetzt wird, müssen jedoch Escape-Sequenzen und Positionsparameter abgedeckt sein.

Führen Sie die Abnahme vor dem Zusammenführen in dieser Reihenfolge durch:

  1. Entfernen Sie absichtlich eine Zielsprache und prüfen Sie, ob der Job fehlschlägt.
  2. Ersetzen Sie eine Übersetzung durch ein Leerzeichen und prüfen Sie, ob der Bericht den konkreten Schlüssel nennt.
  3. Erzeugen Sie einen unvollständigen Pluralzweig und prüfen Sie, ob die rekursive Kontrolle ihn erkennt.
  4. Stellen Sie die Ressourcen wieder her, führen Sie den Job erneut aus und vergewissern Sie sich, dass der alte Bericht das Ergebnis nicht beeinflusst.
  5. Führen Sie abschließend einen Build mit der Release-Konfiguration aus und prüfen Sie, ob die Schranke und der reguläre Job denselben ausgecheckten Inhalt verwenden.

Das Ergebnis ist kein einmaliger Übersetzungsscan, sondern eine auf dem Cloud-Mac zuverlässig reproduzierbare technische Vorgabe: Unvollständige Ressourcen führen möglichst früh zum Abbruch. Erst nach ihrer Korrektur beginnen Kompilierung, Tests und Archivierung.

Häufig gestellte Fragen

An welcher Stelle der CI sollte die String-Catalog-Prüfung laufen?

Sie sollte nach dem Vorbereiten der Abhängigkeiten, aber vor Kompilierung und Archivierung laufen. Fehler stoppen die Pipeline damit vor den rechenintensiven Schritten.

Reicht es aus, nur den Status translated zu prüfen?

Nein. Zusätzlich müssen vorhandene Zielsprachen, leere Werte und alle verschachtelten stringUnit-Einträge in Varianten wie Pluralformen geprüft werden.

Exklusiver physischer Knoten

Führen Sie Ihren nächsten Build auf einem Mac in der Cloud aus.

Vergleichen Sie drei Apple-Silicon-Konfigurationen und wählen Sie aus fünf internationalen Knoten die Region, die zu Ihrem aktuellen Workflow passt.

Mietmodell auswählen