Audit i18n — 2026-08-12
Réponse à : « quand je change de langue, certaines pages / sections / blocs ne sont pas traduits ». Audit reproductible via pnpm i18n:audit (scripts/i18n-audit.mjs) — chaque chiffre ci-dessous est régénérable.
Réponse à : « quand je change de langue, certaines pages / sections / blocs ne sont pas traduits ». Audit reproductible via
pnpm i18n:audit(scripts/i18n-audit.mjs) — chaque chiffre ci-dessous est régénérable.
Verdict
Le problème n'est pas un catalogue incomplet : les 6 locales exposent
exactement les 2213 mêmes clés, zéro clé manquante, zéro clé en trop. Aucun
MISSING_MESSAGE next-intl n'est possible.
Le problème est que l'i18n s'arrête à la couche marketing publique. Tout ce qui est derrière l'auth, plus le tunnel scan tracking et le hub, n'a jamais été externalisé : le texte est écrit en dur dans le JSX. Changer de langue ne peut donc rien y faire, il n'y a pas de clé à résoudre.
2281 strings en dur dans 390 fichiers.
Deux défauts distincts, à traiter séparément :
| # | Défaut | Ampleur | Statut |
|---|---|---|---|
| A | Surfaces jamais branchées sur useTranslations | 2281 strings / 390 fichiers | Ouvert — chantier à arbitrer |
| B | Valeurs laissées en anglais dans de / es / it / pt | 431 clés | Corrigé dans cette PR |
A — Couverture t() par surface
DASHBOARD ···················· 0% (0/140 fichiers)
ADMIN ···················· 0% (0/155 fichiers)
SCAN-TRACKING ···················· 0% (0/55 fichiers)
OAUTH ···················· 0% (0/1 fichiers)
HUB ···················· 0% (0/11 fichiers)
EMAIL ···················· 0% (0/24 fichiers)
COMPONENTS ···················· 1% (1/71 fichiers)
FEATURES █··················· 4% (6/134 fichiers)
SHARED ████················ 20% (19/95 fichiers)
PATTERNS █████··············· 24% (19/78 fichiers)
MINIMAL █████··············· 25% (4/16 fichiers)
SHELLS ████████············ 42% (11/26 fichiers)
MARKETING ███████████████····· 77% (65/84 fichiers)
Le catalogue le confirme : les 34 namespaces de en.json couvrent home,
marketingPages, pricing, auth, onboarding, nav, status, legal,
agents, sellStore, chat… Aucun namespace dashboard, account,
billing, settings, credits, hub, intelligence ou scan. Ces
surfaces n'ont jamais été prévues pour la traduction.
Répartition des strings en dur
| Surface | Strings | Fichiers | Visible par l'utilisateur ? |
|---|---|---|---|
| SCAN-TRACKING | 676 | 38 | Oui — /scan/tracking, funnel public |
| ADMIN | 569 | 99 | Non — panel interne |
| DASHBOARD | 384 | 84 | Oui — tout le produit connecté |
| FEATURES | 284 | 69 | Oui — chat, workflow, store-browser |
| SHARED | 110 | 34 | Oui — credits, marketplace, sell |
| HUB | 106 | 8 | Oui — /marketplace, /intelligence |
| 49 | 21 | Oui — emails transactionnels | |
| MARKETING | 34 | 8 | Oui — régression sur surface traduite |
| PATTERNS | 28 | 16 | Partiel |
| SHELLS | 15 | 3 | Oui — témoignages home |
| COMPONENTS | 12 | 6 | Partiel |
| OAUTH | 10 | 1 | Oui — écran de consentement |
| MINIMAL | 4 | 3 | Oui |
Priorité 1 — surfaces publiques déjà traduites à 77 %
Ce sont des trous dans un mur par ailleurs fini. Peu de strings, effet immédiat, aucune décision d'architecture à prendre.
| Fichier | Strings |
|---|---|
src/app/(marketing)/intelligence/radar/page.tsx | 12 |
src/app/(marketing)/intelligence/transparency/page.tsx | 11 |
src/components/shells/app-shell/home-sections/testimonials-section.tsx | 9 |
src/app/(marketing)/sellers/[id]/page.tsx | 6 |
src/components/shells/platform/platform-center.tsx | 5 |
src/app/oauth/authorize/page.tsx | 10 |
src/app/(marketing)/error.tsx, not-found.tsx, marketplace/error.tsx | 3 |
src/app/(marketing)/pricing/_components/{model-pricing-cards,plan-comparison}.tsx | 2 |
~58 strings → couvre les pages publiques que l'utilisateur voit en premier
en changeant de langue. Nouveau namespace non nécessaire pour la plupart :
marketingPages et home existent déjà.
Priorité 2 — hub public (marketplace + intelligence)
| Fichier | Strings |
|---|---|
src/components/hub/intel-detail-view.tsx | 49 |
src/components/hub/tool-detail-view.tsx | 21 |
src/components/hub/marketplace.tsx | 14 |
src/components/hub/listings.tsx | 11 |
src/components/hub/intel-listings.tsx | 6 |
autres (listing-table, hub, use-save) | 5 |
106 strings, 8 fichiers. Surface publique indexée, à fort trafic. Demande un
namespace hub.
Priorité 3 — tunnel scan tracking
676 strings, mais ~564 concentrées dans 8 fichiers de guides :
guide-cookiebot.tsx 95 guide-addingwell.tsx 67
guide-gtm.tsx 85 guide-ga4.tsx 51
guide-gads-capi.tsx 78 guide-klaviyo.tsx 40
guide-mads-capi.tsx 77 guide-shopify.tsx 71
Ce sont des guides d'intégration pas-à-pas : de la documentation, pas de
l'UI. Décision à prendre : les externaliser en clés (lourd, ~560 clés × 6) ou
les basculer en MDX sous content/ avec une variante par locale. La seconde
piste est probablement la bonne, et c'est la raison pour laquelle je ne l'ai pas
tranchée seul.
Priorité 4 — dashboard connecté
384 strings / 84 fichiers, sans aucun namespace existant. Chantier le plus lourd
et le plus structurant : il faut d'abord définir l'arborescence de namespaces
(dashboard.billing, dashboard.settings, account.*, …) avant d'écrire la
moindre clé. Têtes de liste :
account/settings/authentication/page.tsx 20
[orgSlug]/[storeSlug]/workspace/workspace-client 17
[orgSlug]/~/listings/[id]/edit/edit-form.tsx 17
[orgSlug]/~/settings/page.tsx 13
[orgSlug]/[storeSlug]/settings/.../whatsapp 12
account/settings/payouts/page.tsx 12
Hors périmètre — ADMIN (569 strings)
Panel interne opérateur. Le traduire n'a aucune valeur produit ; je recommande de l'exclure explicitement plutôt que de le laisser traîner dans les compteurs.
Cas à part : emails transactionnels (49 strings, 24 fichiers)
src/modules/email/templates/* ne peut pas utiliser useTranslations (rendu
hors requête). Il faut passer la locale du destinataire explicitement et charger
le catalogue via createTranslator. Chantier indépendant des autres.
B — Valeurs laissées en anglais dans le catalogue : corrigé
Clés présentes mais identiques à l'anglais alors que le français les
traduit : donc de vrais oublis, pas des noms propres. Le critère « traduit en
fr » sert de preuve de traductibilité et élimine les faux positifs.
| Locale | Avant | Corrigées | Après |
|---|---|---|---|
de | 189 | 134 | 34 |
es | 136 | 128 | 33 |
it | 163 | 134 | 32 |
pt | 46 | 35 | 30 |
Les quatre locales retombent au niveau plancher du français (36), qui ne contient plus que des noms propres légitimes : « Christopher Lasgi », « @Atlas MCP », « AI Teams », « Super Admin », « Shopify Plus · EU ».
Namespaces les plus touchés :
sellStore.modal.*(25 clés × 3 locales) : la modale « vendre mon store », un funnel de conversion public. Un opérateurde/es/itla voyait intégralement en anglais.chat.elements.*(26 clés × 3 locales) : l'activity feed d'@Atlas, le pied de message (coût, modèle, latence), la navigation entre versions de réponse. Visible à chaque tour de conversation.feedback.form.*,roadmap.statuses.*,agents.profile.*,nav.*,home.*: libellés isolés.
Les valeurs non traduites volontairement sont des emprunts établis dans la langue
cible (Dashboard, Community, Events, Tutorials, Themes, Traffic,
Live, Offline, Upgrade en allemand ; Merchandising partout), des noms de
produit (Claude Skills, @Atlas Marketplace) ou des tokens non-linguistiques
(+30%, e.g. 8000, TL;DR).
Outillage
pnpm i18n:audit # rapport complet
pnpm i18n:audit --strings # + détail des strings par fichier
pnpm i18n:audit --area=DASHBOARD # une surface, strings détaillées
pnpm i18n:audit --json # sortie machine
Le script sort en code 1 si la parité du catalogue est rompue : branchable en CI pour empêcher toute régression du défaut B. Les strings en dur ne font pas échouer le script : c'est un inventaire de dette, pas une régression.
Limites de la détection
Heuristique sur le JSX, pas d'analyse AST. Elle relève les nœuds texte
(>Texte<) et une liste d'attributs porteurs de texte (placeholder, title,
label, aria-label, alt, description, …). Elle rate le texte construit
dans des variables ou des tableaux de config, et compte quelques faux positifs
(fragments de phrases coupés par du JSX inline). Les galeries de composants du
design system sont exclues. Les totaux sont donc un plancher, pas un compte
exact.