Skip to main content

Documentation Index

Fetch the complete documentation index at: https://snakysec.mintlify.app/llms.txt

Use this file to discover all available pages before exploring further.

Convention i18n — SnakySec Platform

Pattern P5 (UX_AUDIT_RECOMMENDATIONS.md) + REC-CRIT-01. Cible : 0 string utilisateur hardcodé hors messages/fr.json + messages/en.json. Outil : platform/scripts/check-hardcoded-strings.mjs — gate CI obligatoire.

Règle d’or

Aucun string visible par l’utilisateur final ne doit être hardcodé dans .tsx. Toute chaîne utilisateur → messages/fr.json + messages/en.json → consommée via useTranslations() (Client Component) ou getTranslations() (Server Component) ou <Trans>.

Stack i18n

  • Lib : next-intl (déjà installée, configurée dans platform/src/i18n/).
  • Locales supportées : fr (par défaut) + en.
  • Détection locale : header Accept-Language ou cookie NEXT_LOCALE.
  • Source de vérité terminologique : docs/conventions/glossary.md.

Patterns d’usage

Server Component (async, app/**/page.tsx)

import { getTranslations } from "next-intl/server";

export default async function MyPage() {
  const t = await getTranslations("dashboard");
  return <h1>{t("title")}</h1>;
}

Client Component ("use client")

"use client";
import { useTranslations } from "next-intl";

export function MyComponent() {
  const t = useTranslations("clients");
  return <button>{t("addClient")}</button>;
}

Avec interpolation

// fr.json: { "audits": { "lastRun": "Dernier audit il y a {duration}" } }
const t = useTranslations("audits");
return <p>{t("lastRun", { duration: "2h" })}</p>;

Avec mise en forme (rich text)

// fr.json: { "warning": "Veuillez <bold>confirmer</bold> votre action." }
const t = useTranslations("common");
return t.rich("warning", { bold: (chunks) => <strong>{chunks}</strong> });

Pluriels

// fr.json: { "controls": { "count": "{count, plural, =0 {Aucun contrôle} one {# contrôle} other {# contrôles}}" } }
const t = useTranslations("controls");
return <p>{t("count", { count: 220 })}</p>;

Namespace conventions

NamespaceUsage
commonBoutons / actions universels (Save, Cancel, Edit, Delete, Sign Out)
navItems de navigation sidebar/header
dashboardPage /dashboard
clientsListe + cards /dashboard/clients
clientDetail/dashboard/clients/[id]
auditDetail/dashboard/audits/[id]
auditsListe /dashboard/audits
portalPages /portal/*
onboardingWizard onboarding
headerComposant Header
sidebarSidebar
auditActionsActions (Cancel/Retry/Sync/Pipeline/PDF/Excel/HTML/Delete)
settings/dashboard/settings/*
integrations/dashboard/integrations
alerts/dashboard/alerts
errorsMessages d’erreur (forbidden, not_found, expired_session, etc.)
formsLabels + placeholders + descriptions des forms
timeFormatage temporel (“il y a 2h”, “dans 3 jours”, “Today”, “Yesterday”)
severitySévérités (Critical/High/Medium/Low/Informational)
statusStatuts contrôle (compliant/finding/manual/…) — voir glossary.md
dictAxes DICT (Disponibilité/Intégrité/Confidentialité/Traçabilité)

Outil CI : check-hardcoded-strings.mjs

Usage local

cd platform/

# Audit complet (tous fichiers .tsx sous src/app + src/components)
npm run i18n:check

# JSON output (machine-readable)
npm run i18n:check -- --json > i18n-report.json

# Fail-on-found (gate CI)
npm run i18n:check -- --fail

# Cibler un fichier précis
node scripts/check-hardcoded-strings.mjs --paths src/components/sidebar.tsx

# Mode strict : flag aussi les strings 1 mot (dur — beaucoup de bruit)
node scripts/check-hardcoded-strings.mjs --strict-single-word

Règles de détection

Le script flag :
  • JSX text : <p>Hello world</p> (texte direct dans JSX)
  • JSX expression string : <p>{"Hello world"}</p>
  • JSX attribute string : <input placeholder="Search clients" /> pour les attrs label, title, placeholder, description, alt, message, tooltip, headerText
  • Toute chaîne ≥ 2 mots, hors exceptions
Le script ignore :
  • Strings dans t(), useTranslations(), getTranslations(), <Trans>, t.rich(), t.raw()
  • Attributs techniques : className, href, key, id, role, type, name, src, htmlFor, value, data-*, aria-*
  • Strings d’1 mot (sauf --strict-single-word)
  • Strings code-like : snake_case, camelCase, kebab-case, PascalCase, URLs, paths
  • Marques + termes techniques (CIS, SCuBA, Microsoft 365, etc.) — voir DEFAULT_IGNORE dans le script
  • Symboles UI : , , , ,

Configuration locale projet

Fichier : platform/scripts/i18n-ignore.json
{
  "brands": [],          // Ajouts au DEFAULT_IGNORE.brands
  "technical": [],       // Ajouts au DEFAULT_IGNORE.technical
  "exactMatches": [
    "MSSP_ADMIN",        // Strings exacts à exempter (case-sensitive)
    "ANALYST",
    "CLIENT_USER"
  ],
  "regexPatterns": [],   // Regex strings → strings matchant exemptés
  "fileOverrides": {     // Per-file exceptions (legacy)
    "platform/src/components/legacy.tsx": ["temporary string"]
  }
}
Privilégier la migration plutôt que d’ajouter à fileOverrides. Les overrides sont des dette technique explicite à éliminer.

Intégration CI (GitLab)

Ajouter dans .gitlab-ci.yml :
i18n:check:
  stage: lint
  image: node:22-alpine
  script:
    - cd platform && npm ci
    - npm run i18n:check -- --fail
  only:
    - merge_requests
    - main
Pour V1 : exécuter localement avant chaque commit. Intégration CI bloquante = post-migration complète des 430 strings.

Workflow de migration (REC-CRIT-01)

Phase 0 — Inventaire (✓ fait)

cd platform/
npm run i18n:check -- --json > i18n-report.json
Snapshot baseline : 430 occurrences dans 78 fichiers (au 2026-04-29).

Phase 1 — Migration par groupes (Sprint 1 UX)

Ordre recommandé pour minimiser les conflits :
  1. Composants nav : sidebar.tsx, portal-sidebar.tsx, header.tsx, breadcrumb.tsx (~30 strings)
  2. Filtres globaux : dashboard-filters.tsx, audits/audit-filters.tsx (~15 strings)
  3. Settings hub : settings/page.tsx, settings/users/page.tsx, settings/audit-log/audit-log-content.tsx (~70 strings)
  4. Forms : clients/[id]/edit/page.tsx, clients/[id]/secrets/*, clients/new/page.tsx (~80 strings)
  5. Audit detail : audits/[id]/page.tsx, audit/control-results-table.tsx, audit-actions.tsx (~50 strings)
  6. Portal pages : portal/audits/[id]/page.tsx, portal/findings/page.tsx, portal/documents/page.tsx, portal/remediation/page.tsx (~40 strings)
  7. Composants spécialisés : tampering-banner.tsx, trajectory-dashboard.tsx, cron-builder.tsx, etc. (~145 restants)
Pour chaque fichier :
  1. Lire les findings du script CI
  2. Choisir le namespace (cf tableau ci-dessus)
  3. Ajouter les clés à messages/fr.json + messages/en.json
  4. Remplacer les hardcoded par t("...") / getTranslations("...") / <Trans>
  5. Re-tester le composant en FR + EN
  6. Re-runner le script CI sur le fichier : node scripts/check-hardcoded-strings.mjs --paths <file>
  7. Commit incrémental

Phase 2 — Activation gate CI

Quand i18n-report.json ≤ 50 occurrences résiduelles toutes catégorisées dans fileOverrides, activer le --fail en CI bloquante.

Cas particuliers

Strings dynamiques depuis API/DB

Les valeurs venant de la DB (nom de client, slug, message d’erreur Graph) ne passent pas par i18n. Ne pas les flagger — le script les ignore naturellement (interpolation).
// ✅ OK — value dynamic from DB
return <p>{client.name}</p>;

// ❌ Mauvais — wrapper hardcoded
return <p>Client: {client.name}</p>;

// ✅ OK — wrapper i18n
return <p>{t("clientLabel", { name: client.name })}</p>;

Strings d’erreur backend

Pour les messages d’erreur API → i18n côté client uniquement. Le serveur renvoie un code (forbidden, not_found, expired_session), le client traduit.
// API
return NextResponse.json({ error: { code: "forbidden" } }, { status: 403 });

// Client
const t = useTranslations("errors");
toast({ description: t(error.code) });

Strings dans Markdown

Les fichiers Markdown (DR Runbook, GRC Documents) restent en français/anglais selon le contexte de génération. Pas de migration i18n sur Markdown — c’est du contenu éditorial.

Strings dans les rapports PDF/DOCX

Les générateurs GRC dans platform/src/lib/grc/ utilisent leur propre système de templates bilingues (generate-grc-pdf.ts accepte locale: "fr" | "en"). Voir platform/src/lib/grc/types.ts.

Glossaire et terminologie

Les libellés migrés doivent respecter le glossaire :
  • “Findings” → “Écarts” en FR (cf glossary.md, arbitrage A4)
  • “Trigger / Launch” → “Lancer” en FR
  • “Logout” → “Se déconnecter” en FR
  • “Voir comme ce client” → “Prévisualiser le portail” en FR (action à effet de bord)

Références