Lors d’un sprint de version, plus d’une dizaine de nouveaux libellés d’interface ont été ajoutés à la branche de développement. L’archivage s’est terminé normalement, mais les testeurs ont constaté des retours à l’anglais et des boutons vides après avoir changé de langue. Le problème ne venait pas du compilateur : le pipeline n’avait jamais traité l’intégrité des localisations comme un contrôle susceptible de faire échouer la tâche. Sur un Mac cloud utilisé en continu, l’approche la plus efficace ne consiste pas à attendre une vérification manuelle de chaque écran, mais à contrôler directement les fichiers .xcstrings avant chaque build.
Définir précisément ce que le contrôle doit bloquer
Un String Catalog est un fichier JSON, mais le fait qu’il puisse être analysé ne signifie pas qu’il soit prêt à être livré. Un contrôle utile doit au minimum détecter trois catégories de problèmes : l’absence d’une langue cible requise par le projet, un état de traduction différent de translated et une chaîne finale vide. Les pluriels, les variantes par appareil et d’autres contenus figurent également sous variations. Lire uniquement le premier niveau de stringUnit laisserait donc passer certaines erreurs.
Les critères d’acceptation doivent être enregistrés dans le dépôt, et non conservés dans une configuration temporaire du nœud de build. Si la langue source du projet est l’anglais et que le chinois simplifié, le japonais et le français sont actuellement requis, les langues cibles peuvent être transmises au script sous forme de paramètres.
| Élément contrôlé | Condition d’échec | Traitement |
|---|---|---|
| Langue cible | La clé correspondante est absente de localizations |
Bloquer le build |
| État de traduction | Au moins un nœud terminal n’est pas translated |
Renvoyer la clé et la langue concernées |
| Contenu de la chaîne | value est vide ou ne contient que des espaces |
Bloquer le build |
| Contenu non traduisible | shouldTranslate vaut false |
L’ignorer explicitement |
Ce contrôle vérifie uniquement que les ressources de localisation du catalogue sont complètes. Il ne remplace pas la validation manuelle des troncatures dans l’interface, des paramètres dynamiques ni de l’exactitude sémantique.
Écrire un validateur sans dépendance externe
Enregistrez le script sous Scripts/check_xcstrings.py. Il dépend uniquement de l’environnement Python fourni avec macOS. Si l’équipe utilise un chemin Python dédié et stable, celui-ci doit être indiqué explicitement dans la configuration de build afin d’éviter toute différence de PATH entre le shell interactif et la 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)
Exécutez la commande suivante à la racine du dépôt :
python3 Scripts/check_xcstrings.py . \
--locale zh-Hans \
--locale ja \
--locale fr
Lorsque le script se termine avec un code différent de zéro, n’importe quel orchestrateur de tâches courant peut interrompre les étapes suivantes. La sortie indique le fichier, la clé de chaîne, la langue et l’index du nœud terminal. La personne chargée de la correction n’a donc pas besoin de parcourir au préalable l’intégralité du journal de build.
Intégrer le contrôle au pipeline du Mac cloud avant la compilation
Il est recommandé d’exécuter le contrôle après la résolution des dépendances, mais avant xcodebuild build ou archive. La préparation des dépendances permet de vérifier que tout le contenu du dépôt est bien disponible sur disque. Le contrôle anticipé évite ensuite de lancer la compilation, les tests et l’archivage pour un commit manifestement non conforme.
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 est essentiel. Si la sortie est ensuite transmise à tee, son absence peut conduire le pipeline à ne voir que la réussite de la dernière commande et à ignorer l’échec du validateur. Le répertoire des rapports doit également être recréé au démarrage de la tâche afin qu’un fichier laissé par une exécution précédente sur le Mac cloud ne soit pas interprété comme le résultat actuel.
Ajouter une vérification humaine à partir des fichiers exportés
L’analyse directe des fichiers .xcstrings convient à un contrôle automatisé. Avant une livraison, il faut toutefois exporter les paquets de localisation afin de vérifier que la langue de développement, les commentaires et le contexte sont complets. Pour les langues cibles, exécutez :
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
Si l’export échoue, ne relancez pas immédiatement la commande en écrasant les éléments utiles au diagnostic. Conservez d’abord l’intégralité de la sortie d’erreur standard, puis vérifiez le scheme, le chemin du projet et l’identifiant de la langue cible. Le code de langue doit correspondre exactement à la clé du Catalog. Par exemple, zh-Hans et zh-Hant sont deux cibles distinctes et ne peuvent pas être remplacées par un préfixe approximatif.
Les fichiers .xcloc exportés peuvent être confiés au responsable de la localisation pour un contrôle par échantillonnage. En revanche, il est déconseillé de placer ces artefacts de build dans la branche principale. Le dépôt doit contenir les fichiers .xcstrings sources, le script de contrôle et la liste des langues cibles, tandis que le répertoire d’export reste un artefact propre à chaque tâche.
Traiter les faux positifs courants et valider le processus
La première catégorie de faux positifs concerne les chaînes techniques qui n’ont pas à être traduites. Il ne faut pas les ignorer arbitrairement dans le script à partir d’un préfixe de clé. Définissez explicitement shouldTranslate: false dans le Catalog afin que la règle reste attachée à la ressource elle-même. La deuxième catégorie apparaît lorsqu’une seule branche de pluriel, par exemple one, a été traduite alors que other a été oubliée. La lecture récursive de chaque stringUnit permet de localiser précisément ce cas.
La troisième catégorie survient lorsqu’un développeur ajoute une langue sans mettre à jour les paramètres de la CI. La liste des langues cibles doit idéalement avoir une seule source, par exemple un tableau dans le script du projet ou une variable du pipeline, afin d’éviter que plusieurs tâches en maintiennent chacune une copie. La quatrième catégorie concerne les espaces, les sauts de ligne ou les placeholders considérés à tort comme une traduction valide. Le script actuel rejette les chaînes composées uniquement d’espaces. Pour les paramètres de format tels que %@ et %d, il est également possible de comparer les ensembles de placeholders de la chaîne source et de la chaîne cible. Il faut toutefois gérer d’abord les séquences d’échappement et les paramètres positionnels avant de rendre ce contrôle bloquant.
Avant la fusion, effectuez la validation dans l’ordre suivant :
- Supprimez volontairement une langue cible et vérifiez que la tâche échoue.
- Remplacez une traduction par une espace et vérifiez que le rapport indique la clé exacte.
- Créez une branche de pluriel incomplète et vérifiez que l’analyse récursive la détecte.
- Restaurez les ressources, puis relancez la tâche pour confirmer que l’ancien rapport n’affecte pas le résultat.
- Exécutez enfin un build en configuration Release afin de vérifier que le contrôle et la tâche de production utilisent le même contenu extrait du dépôt.
Le résultat n’est pas un simple scan ponctuel des traductions, mais une contrainte d’ingénierie reproductible et fiable sur un Mac cloud : les ressources incomplètes provoquent un échec au plus tôt, et la compilation, les tests ainsi que l’archivage ne commencent qu’après leur correction.
Questions fréquentes
À quel moment exécuter le contrôle String Catalog dans la CI ?
Exécutez-le après la préparation des dépendances, mais avant la compilation et l’archivage. Une erreur de localisation interrompt ainsi le travail avant les étapes les plus coûteuses.
Vérifier uniquement l’état translated est-il suffisant ?
Non. Il faut aussi vérifier la présence de chaque langue, les valeurs vides et tous les stringUnit imbriqués dans les variantes, notamment celles des formes plurielles.
Exécutez votre prochaine compilation sur un Mac dans le cloud.
Comparez trois configurations Apple Silicon et choisissez, parmi cinq nœuds internationaux, la région adaptée à votre flux de travail actuel.