Sur une machine de développement, le libellé anglais d’un bouton peut ne compter que six lettres, puis occuper une ligne entière une fois traduit en allemand. Dans une interface de droite à gauche, le bouton de retour placé à gauche peut aussi continuer à pointer dans la mauvaise direction. Si ces problèmes ne sont découverts qu’une fois toutes les traductions terminées, leur correction affectera simultanément la mise en page, les tests et le calendrier de livraison. Une approche plus fiable consiste à allonger volontairement les chaînes, à inverser le sens de la mise en page lors de chaque contrôle de fusion sur un Mac cloud, puis à faire échouer le test dès qu’un contrôle devient invisible ou inutilisable.
Définir d’abord ce que le contrôle doit détecter
La pseudolocalisation ne consiste pas à remplacer l’interface par des caractères illisibles. Elle applique des transformations maîtrisées afin d’amplifier les risques de mise en page. Commencez par définir deux modes :
expanded: conserver le texte d’origine, ajouter des marqueurs de chaque côté et allonger les chaînes d’environ 40 %.rtl: conserver un contenu reconnaissable tout en activant une direction sémantique de droite à gauche afin de vérifier l’ordre des éléments et les icônes.- Mode normal : servir de groupe témoin pour confirmer que le code d’assistance réservé aux tests ne modifie pas le comportement du produit.
Le contrôle doit se concentrer sur quatre catégories de problèmes : une zone visible manifestement insuffisante pour les contrôles textuels ; des boutons principaux absents, impossibles à toucher ou situés hors de la fenêtre ; des libellés d’interface codés en dur qui contournent le point d’entrée de localisation ; et des éléments de navigation, listes horizontales ou icônes directionnelles qui ne s’adaptent pas au mode RTL.
La pseudolocalisation permet de révéler les problèmes structurels, mais pas d’évaluer l’exactitude d’une traduction. Une validation dans les langues réelles reste indispensable, mais elle ne devrait pas avoir à détecter des défauts élémentaires tels que des largeurs fixes.
Créer une couche maîtrisée de transformation des chaînes
Ne parcourez pas la hiérarchie des vues dans les tests pour modifier les libellés. Cette méthode contourne le véritable chemin d’accès aux chaînes de l’application et ne permet pas de repérer les textes codés en dur. Il est plus fiable d’ajouter au point d’entrée de localisation existant du projet un transformateur actif uniquement dans les builds de débogage.
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
Au démarrage de l’application, lisez PSEUDO_LOCALE et n’acceptez que des valeurs explicitement définies dans l’énumération. Toutes les chaînes destinées à l’utilisateur doivent passer par un point d’entrée commun, par exemple L10n.text("checkout.confirm"), avant d’être transmises au transformateur. Si un titre s’affiche sans marqueurs de délimitation, cela indique qu’il a contourné la couche de localisation.
Le mode RTL doit également définir la direction sémantique sur la vue racine de l’application. Avec UIKit, définissez semanticContentAttribute = .forceRightToLeft sur la fenêtre de test. Avec SwiftUI, injectez layoutDirection dans la vue racine. N’inversez pas le contenu des images et ne forcez pas l’inversion des nombres, des chemins ou des extraits de code.
Vérifier la visibilité et la troncature avec des tests d’interface
Les tests n’ont pas besoin de comparer chaque pixel. Commencez par couvrir les parcours essentiels, notamment l’écran d’accueil après connexion, les listes, les vues détaillées, les formulaires et les états d’erreur. Attribuez également des identifiants d’accessibilité stables aux principaux contrôles. Le processus de test lance l’application dans le mode voulu au moyen d’une variable d’environnement :
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 est plus utile qu’un simple contrôle de exists : un bouton peut être présent dans l’arbre d’accessibilité tout en étant masqué par une fenêtre superposée ou placé hors de l’écran. Pour les contrôles de type libellé, utilisez leur identifiant afin d’obtenir leur frame et vérifiez qu’ils ne dépassent pas de leur conteneur. N’utilisez pas l’OCR pour tenter de déterminer si le texte est tronqué. Dans les builds de débogage, exposez plutôt, pour les composants essentiels, l’écart entre intrinsicContentSize et la frame réelle.
Transformer le périmètre de contrôle en liste de vérification
| Scénario | Contrôles expanded | Contrôles RTL | Preuves d’échec |
|---|---|---|---|
| Barre de navigation | Le titre et les actions ne se chevauchent pas | La direction du retour et l’ordre sont corrects | Hiérarchie de l’interface, frame du contrôle |
| Formulaire | Les libellés peuvent passer à la ligne et les messages d’erreur sont complets | Les libellés et les champs de saisie suivent le même axe | Identifiant du champ, valeur actuelle |
| Liste | Les titres longs ne repoussent pas l’action principale | Les pièces jointes et les flèches sont correctement positionnées | Index de la ligne en échec, capture d’écran |
| Fenêtre superposée | Les boutons sont complets et utilisables | L’ordre des actions respecte la sémantique | Frame de la fenêtre, arbre des éléments |
Organiser l’environnement d’exécution sur un Mac cloud
Créez un ensemble d’appareils de simulateur indépendant pour chaque tâche afin d’éviter que des jobs simultanés ne partagent leur état de démarrage. Placez le répertoire de cet ensemble dans le dossier temporaire de la tâche, puis nettoyez-le à la fin, que le job réussisse ou échoue. Avant les tests, fixez le chemin de Xcode, le modèle de simulateur et la version du système, puis consignez ces valeurs dans le rapport.
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"
Exécutez expanded et rtl sous forme de deux tâches indépendantes au lieu de changer de mode au sein d’une même session de simulateur. Chaque échec peut ainsi être attribué sans ambiguïté, et une nouvelle exécution n’hérite pas du mode directionnel précédent. Si plusieurs jobs s’exécutent simultanément sur le Mac cloud, chacun doit également utiliser son propre chemin DerivedData afin d’éviter l’écrasement mutuel des artefacts de test.
Traiter les faux positifs courants et les défauts réels
Les faux positifs les plus fréquents surviennent lorsque les animations ne sont pas terminées, que des fenêtres système restent ouvertes ou que la longueur des données de test varie. La solution ne consiste pas à ajouter un délai d’attente uniforme, mais à attendre des états explicites : disparition de l’indicateur de chargement, apparition de l’élément cible ou stabilisation du nombre de fenêtres. Les données de test doivent elles aussi être fixes, notamment les noms d’utilisateur, les dates et les messages d’erreur renvoyés par le serveur.
Les défauts réels concernent généralement les hauteurs fixes, les libellés limités à une seule ligne, les largeurs calculées manuellement et les composants qui expriment l’ordre uniquement par leur position visuelle. Lors de la correction, supprimez en priorité les contraintes au lieu d’ajouter des exceptions propres à une langue. Après avoir autorisé le retour à la ligne du titre d’un bouton, vérifiez de nouveau que sa zone tactile couvre l’intégralité du texte.
Lorsqu’un échec est détecté, archivez au minimum les éléments suivants :
- Le mode de pseudolangue actif ainsi que les versions de Xcode et du simulateur.
- L’identifier, la frame, le label et l’état cliquable du contrôle en échec.
- La hiérarchie d’accessibilité de la page affichée.
- Le fichier
xcresultcorrespondant et une capture d’écran prise au moment de l’échec. - Les données de test fixes nécessaires pour reproduire cette page.
Définir les seuils de fusion et empêcher toute fuite
Le contrôle doit bloquer les problèmes qui empêchent l’utilisateur d’accomplir une tâche : action principale invisible ou inutilisable, texte tronqué au point de perdre son sens, ordre RTL incorrect et contrôle interactif dépourvu de nom accessible. Les légères variations d’espacement peuvent être consignées pour vérification ultérieure ; toutes les différences au pixel près ne doivent pas nécessairement bloquer une fusion.
Ajoutez enfin un contrôle de validation Release : analysez les réglages de build et les ressources de l’application pour confirmer l’absence de fichiers de pseudolangue réservés aux tests, puis vérifiez que les builds de production ignorent PSEUDO_LOCALE. Exécutez également un ensemble de tests de fumée en mode normal afin de vous assurer que la couche de transformation ne modifie pas elle-même les chaînes.
L’intérêt de ce processus n’est pas de produire une interface à l’apparence étrange. Il transforme le contrôle manuel ponctuel des mises en page multilingues avant livraison en une contrainte d’ingénierie reproductible à chaque modification du code. Couvrez d’abord les parcours principaux, puis ajoutez progressivement les états d’erreur et les fenêtres rarement affichées afin que le contrôle reste stable et facile à maintenir.
Questions fréquentes
La pseudolocalisation remplace-t-elle une validation en langues réelles ?
Non. Elle détecte vite les largeurs fixes, les chaînes non traduites et les défauts RTL, mais la validation finale doit inclure une langue verbeuse et une langue RTL.
Comment exclure le code de pseudolocalisation de la version publiée ?
Compilez le transformateur uniquement en DEBUG et faites échouer la version Release si elle contient la variable PSEUDO_LOCALE ou des ressources réservées aux tests.
Quels défauts doivent bloquer une fusion ?
Une action principale masquée ou inutilisable, une troncature qui change le sens, un ordre RTL incorrect ou une étiquette d’accessibilité absente doivent bloquer la fusion.
Placez votre prochaine compilation dans le cloud sur un Mac mini
Choisissez la configuration, la durée de facturation et la région du nœud pour exécuter vos tâches de développement, de compilation et d’automatisation sur une machine physique dédiée.