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éterminer les deux façons dont la localisation se brise 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 codé en dur et visible par l'utilisateur. Des chaînes rendues directement à l'utilisateur à partir du code widget sans passer par context.l10n.<key>. Elles ne figurent 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/.

Workflow

Étape 1 : Déterminer le scope

Si l'utilisateur a passé des chemins après /check-l10n, scannez ceux-ci. Sinon, scannez l'union des changements indexés + non indexés par rapport à l'arbre de travail.

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

# Ou explicite : chemins en argument

Étape 2 : Exécuter le test de cohérence des arb (s'il est présent)

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

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

Si le fichier test n'existe pas sur cette branche, passez 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 des arb n'a pas été vérifiée.

Fallback en ligne quand le test n'existe pas

Si mobile/test/l10n/arb_consistency_test.dart est manquant, effectuez 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 : Scanner pour l'anglais codé en dur dans les fichiers modifiés

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

Ou avec des chemins explicites :

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

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

Le scanner inspecte uniquement 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 arbre test/ ou integration_test/. Il ignore également les lignes à l'intérieur de Log.*(), developer.log(), print(), assert(), throw <Type>Exception(), et les constantes de noms de routes — ces littéraux ne sont pas visibles par l'utilisateur.

Étape 4 : Reporter sous forme de checklist

Affichez 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 des énoncés suivants :

  • 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 lève explicitement un résultat, en documentant pourquoi dans le rapport.

Corriger les résultats

Clés non traduites

Si la locale manquante figure parmi celles que nous envoyons aux 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 relecture. N'étendez pas silencieusement l'ensemble de la dette — il devrait toujours être examinable comme « la liste des trucs qui ne sont pas traduits, à dessein ».

Anglais codé en dur

Chaque résultat 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 via 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 par l'utilisateur. Si le littéral n'atteint vraiment pas l'utilisateur (par exemple, un widget debug-only, un drapeau developer-only, un identifiant de test sémantique), considérez si le scanner a besoin d'une 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 la peine de la maintenance regex.
  3. Lever avec raison. Les chaînes de marque qui NE DOIVENT JAMAIS être traduites (« OpenVine », « Divine ») sont un anglais codé en dur légitime. Notez la levée dans la description de la PR plutôt que de réduire au silence le scanner — les futurs lecteurs doivent pouvoir voir pourquoi ce résultat a été accepté.

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é de tout widget
title-arg title: 'Foo' paramètre nommé de tout widget
hintText-arg hintText: 'Foo' (champs de formulaire)
helperText-arg helperText: 'Foo' (champs de formulaire)
tooltip-arg / Tooltip-message texte de tooltip
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 regex et manquera le code fortement templaterisé (constructeurs de chaînes, .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. Levez-les dans la description de la PR plutôt que d'essayer de les réduire au silence dans le scanner.
  • Le scanner ne signale que les chaînes commençant par une lettre majuscule et contenant un espace — le texte d'interface utilisateur purement minuscule ou composé d'un seul mot (« ok », « submit ») ne sera pas attrapé. C'est un choix délibéré pour le rapport signal-bruit ; un examen manuel reste nécessaire pour les étiquettes courtes.

Skills similaires