check-l10n

Par divinevideo · divine-mobile

À exécuter avant de pousser une PR ayant touché l'UI, afin de détecter les clés non traduites dans l'un des 16 locales non anglophones et de repérer les chaînes en anglais visibles par l'utilisateur qui contournent `context.l10n`. Présente les résultats sous forme de checklist ; refuse de déclarer le code propre tant que tous les problèmes n'ont pas été résolus ou explicitement ignorés. Invoquer avec `/check-l10n`.

npx skills add https://github.com/divinevideo/divine-mobile --skill check-l10n

Vérifier la Skill L10n

Objectif

Détecter les deux façons dont la localisation se casse dans ce repo avant que les utilisateurs ne voient de l'anglais dans une build non-anglaise :

  1. Clés non traduites. Une clé ajoutée à app_en.arb mais jamais traduite dans l'une des 16 locales non-anglaises (ar, am, bg, de, es, fr, id, it, ja, ko, nl, pl, pt, ro, sv, tr).
  2. Anglais en dur visible à l'utilisateur. Des chaînes rendues directement à l'utilisateur depuis le code widget sans passer par context.l10n.<key>. Elles n'apparaissent jamais dans les fichiers .arb car elles n'ont jamais été extraites, donc aucune quantité de travail de traduction ne les corrige.

Exécutez ceci avant chaque push de PR qui touche mobile/lib/.

Flux de travail

Étape 1 : Déterminer le périmètre

Si l'utilisateur a passé des chemins après /check-l10n, analysez-les. Sinon, analysez l'union des changements en staging + unstaging par rapport à l'arborescence de travail.

# Par défaut : fichiers modifiés
git -C mobile status --porcelain | awk '{print $NF}' | grep '\.dart$'

# Ou explicite : chemins en arguments

Étape 2 : Exécuter le test de cohérence arb (s'il existe)

cd mobile && flutter test test/l10n/arb_consistency_test.dart

Ce test compare tous les fichiers .arb par rapport à app_en.arb et échoue si une locale manque des clés qui ne sont pas sur la liste d'exemption explicite _knownUntranslatedDebt.

Si le fichier de test n'existe pas sur cette branche, ignorez cette étape et fiez-vous entièrement à l'étape 3 plus la vérification en ligne ci-dessous. Notez dans le rapport que la cohérence arb n'a pas été vérifiée.

Repli en ligne quand le test n'existe pas

Si mobile/test/l10n/arb_consistency_test.dart est absent, faites la vérification équivalente manuellement :

cd mobile/lib/l10n
python3 - <<'PY'
import json, glob
en = json.load(open('app_en.arb'))
en_keys = {k for k in en if not k.startswith('@') and k != '@@locale'}
for f in sorted(glob.glob('app_*.arb')):
    if f == 'app_en.arb':
        continue
    other = json.load(open(f))
    other_keys = {k for k in other if not k.startswith('@') and k != '@@locale'}
    missing = en_keys - other_keys
    if missing:
        print(f"{f}: {len(missing)} missing key(s)")
        for k in sorted(missing)[:20]:
            print(f"  - {k}")
PY

Étape 3 : Analyser l'anglais en dur dans les fichiers modifiés

python3 .claude/skills/check-l10n/scan_strings.py

Ou avec des chemins explicites :

python3 .claude/skills/check-l10n/scan_strings.py mobile/lib/screens/auth/foo.dart

Le scanner émet une ligne par candidat, au format <path>:<line>:<col> [<rule>] '<literal>'. Le code de sortie est 1 quand il y a des trouvailles, 0 sinon.

Le scanner n'inspecte que les fichiers sous mobile/lib/. Il exclut les fichiers générés (*.g.dart, *.freezed.dart, *.mocks.dart), le répertoire l10n/, et tout arborescence test/ ou integration_test/. Il saute également les lignes à l'intérieur de Log.*(), developer.log(), print(), assert(), throw <Type>Exception(), et les constantes de noms de routes — ces littérales ne sont pas visibles par l'utilisateur.

Étape 4 : Signaler comme une checklist

Produisez une section par catégorie. Utilisez la sortie littérale des outils sous-jacents plutôt que de paraphraser — l'utilisateur doit pouvoir copier un chemin et sauter directement à la ligne.

## Localization check — <branch>

### 1. ARB consistency
- ✅ All 17 locales have every key in app_en.arb
  (or)
- ❌ app_de.arb missing 7 keys: authConfirmPasswordLabel, ...
  (or)
- ⚠️  arb_consistency_test.dart not present on this branch — used inline
     fallback. Verify before merge.

### 2. Hardcoded English in changed UI files
- ✅ No likely user-visible English literals found.
  (or)
- ❌ 5 candidate(s):
  mobile/lib/screens/auth/login_options_screen.dart
    L147:26  [Text-literal]  'Amber app is not installed'
    L316:27  [label-arg]  'Sign in'
  ...

Terminez par l'un de :

  • OK to push — les deux vérifications sont passées.
  • Do not push — lister les actions requises.
  • ⚠️ Push with caveat — seulement après que l'utilisateur ait explicitement renoncé à une trouvaille, en documentant pourquoi dans le rapport.

Corriger les trouvailles

Clés non traduites

Si la locale manquante en est une que nous livrons à des locuteurs natifs (l'utilisateur peut confirmer la liste de lancement actuelle), traduisez. Sinon, ajoutez la clé à l'ensemble _knownUntranslatedDebt dans mobile/test/l10n/arb_consistency_test.dart avec un commentaire nommant les locales qui ont encore besoin d'une passe. N'étendez pas silencieusement l'ensemble de dettes — il devrait toujours être révisable comme « la liste des trucs qui ne sont pas traduits, exprès ».

Anglais en dur

Chaque trouvaille a trois résolutions, dans l'ordre de préférence :

  1. Ajouter une clé l10n à mobile/lib/l10n/app_en.arb, puis router le widget à travers context.l10n.<key>. Si la valeur existe déjà sous un nom légèrement différent, réutilisez-la au lieu de créer un doublon.
  2. Marquer comme non visible pour l'utilisateur. Si le littéral ne rejoint vraiment pas l'utilisateur (par ex., un widget debug-only, un flag développeur-only, un identificateur de test sémantique), considérez si le scanner a besoin d'un motif de ligne de saut supplémentaire. Une règle de saut doit être justifiée par au moins 3 exemples distincts ; les cas isolés ne valent pas l'effort de maintenance regex.
  3. Renoncer avec raison. Les chaînes de marque qui NE DOIVENT JAMAIS être traduites ("OpenVine", "Divine") sont de l'anglais en dur légitime. Notez la renonciation dans la description de la PR plutôt que de réduire au silence le scanner — les futurs lecteurs doivent pouvoir voir pourquoi cette trouvaille a été acceptée.

Significations courantes des règles

Règle Détecte
Text-literal Text('Foo') et const Text("Bar")
AppBar-title-Text title: Text('Foo') (spécialisation de Text-literal)
label-arg label: 'Foo' paramètre nommé à n'importe quel widget
title-arg title: 'Foo' paramètre nommé à n'importe quel widget
hintText-arg hintText: 'Foo' (champs de formulaire)
helperText-arg helperText: 'Foo' (champs de formulaire)
tooltip-arg / Tooltip-message texte d'infobulle
semanticLabel-arg / semanticsLabel-arg étiquettes d'accessibilité
user-message-call premier argument positionnel d'une méthode dont le nom inclut Error/Message/Snackbar/Toast/Dialog/Banner/Notification

Limitations

  • Le scanner est basé sur des expressions régulières et manquera le code fortement templé (string builders, .padLeft(...), constructions '$prefix - $suffix'). Traitez une exécution propre comme « aucune fuite évidente », pas « toutes les fuites exclues ».
  • Les chaînes de marque ("Divine", "OpenVine", "Vine") déclencheront parfois l'heuristique de visibilité utilisateur. Renoncez-y dans la description de la PR plutôt que de tenter de les réduire au silence dans le scanner.
  • Le scanner signale seulement les chaînes commençant par une majuscule et contenant un espace — les copies UI purement minuscules ou monosyllabiques ("ok", "submit") ne seront pas détectées. C'est un compromis délibéré pour le signal-bruit ; l'examen manuel reste nécessaire pour les étiquettes courtes.

Skills similaires