Audits · août 2026Audit i18n — 2026-08-12

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éfautAmpleurStatut
ASurfaces jamais branchées sur useTranslations2281 strings / 390 fichiersOuvert — chantier à arbitrer
BValeurs laissées en anglais dans de / es / it / pt431 clésCorrigé 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

SurfaceStringsFichiersVisible par l'utilisateur ?
SCAN-TRACKING67638Oui — /scan/tracking, funnel public
ADMIN56999Non — panel interne
DASHBOARD38484Oui — tout le produit connecté
FEATURES28469Oui — chat, workflow, store-browser
SHARED11034Oui — credits, marketplace, sell
HUB1068Oui — /marketplace, /intelligence
EMAIL4921Oui — emails transactionnels
MARKETING348Oui — régression sur surface traduite
PATTERNS2816Partiel
SHELLS153Oui — témoignages home
COMPONENTS126Partiel
OAUTH101Oui — écran de consentement
MINIMAL43Oui

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.

FichierStrings
src/app/(marketing)/intelligence/radar/page.tsx12
src/app/(marketing)/intelligence/transparency/page.tsx11
src/components/shells/app-shell/home-sections/testimonials-section.tsx9
src/app/(marketing)/sellers/[id]/page.tsx6
src/components/shells/platform/platform-center.tsx5
src/app/oauth/authorize/page.tsx10
src/app/(marketing)/error.tsx, not-found.tsx, marketplace/error.tsx3
src/app/(marketing)/pricing/_components/{model-pricing-cards,plan-comparison}.tsx2

~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)

FichierStrings
src/components/hub/intel-detail-view.tsx49
src/components/hub/tool-detail-view.tsx21
src/components/hub/marketplace.tsx14
src/components/hub/listings.tsx11
src/components/hub/intel-listings.tsx6
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.

LocaleAvantCorrigéesAprès
de18913434
es13612833
it16313432
pt463530

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érateur de/es/it la 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.