# Plan d'internationalisation (i18n) — FailDaily

Rédigé le : 2026-03-07  
État actuel : **zéro i18n** — tout le projet est hardcodé en français.  
Langues cibles initiales : **fr** (défaut), **en**  
(extensible à d'autres langues sans refonte si le plan est bien suivi)

---

## Table des matières

1. [Choix technique : JSON vs BDD](#1-choix-technique--json-vs-bdd)
2. [Architecture retenue](#2-architecture-retenue)
3. [Inventaire complet des fichiers à traduire](#3-inventaire-complet-des-fichiers-à-traduire)
   - 3.1 Frontend — Templates HTML
   - 3.2 Frontend — Services (messages TS)
   - 3.3 Frontend — Pages (toasts, alerts, erreurs)
   - 3.4 Backend — Messages API (JSON responses)
   - 3.5 Backend — Emails transactionnels
   - 3.6 Backend — Page HTML consent parental
   - 3.7 Base de données — Contenu éditorial
4. [Périmètre exclu ou secondaire](#4-périmètre-exclu-ou-secondaire)
5. [Roadmap détaillée étape par étape](#5-roadmap-détaillée-étape-par-étape)
6. [Structure des fichiers JSON de traduction](#6-structure-des-fichiers-json-de-traduction)
7. [Conventions et règles de nommage des clés](#7-conventions-et-règles-de-nommage-des-clés)
8. [Gestion des langues dans Angular/Ionic](#8-gestion-des-langues-dans-angularionic)
9. [Gestion des langues côté backend](#9-gestion-des-langues-côté-backend)
10. [Détection automatique de la langue](#10-détection-automatique-de-la-langue)
11. [Tests et validation](#11-tests-et-validation)
12. [Risques et points de vigilance](#12-risques-et-points-de-vigilance)

---

## 1. Choix technique : JSON vs BDD

### Comparatif

| Critère | Fichiers JSON | Base de données |
|---|---|---|
| Simplicité de mise en œuvre | ✅ Très simple | ❌ Complexité accrue |
| Performance | ✅ Chargé en mémoire, zéro requête | ❌ Requête DB par affichage |
| Déploiement | ✅ Versionnés avec le code (git) | ❌ Migrations nécessaires |
| Traductions par des non-devs | ✅ Fichiers JSON lisibles | ✅ Interface admin possible |
| Contenu dynamique (légal, badges) | ❌ Statique → rebuild | ✅ Éditables sans redéploiement |
| App mobile offline (Capacitor) | ✅ Fonctionne offline | ❌ Nécessite un réseau |
| Rechargement à chaud (ngx-translate) | ✅ Lazy loading par langue | ❌ Non applicable |
| Audit / historique | ✅ Git log | ❌ Requiert versioning custom |

### Décision : **JSON pour le frontend, JSON pour les emails backend**

**Raisonnement :**

- L'app est une **SPA mobile/web Ionic + Capacitor** → le contenu doit être disponible offline.
- Les traductions UI sont des chaînes statiques qui évoluent avec le code → le versioning git est l'outil naturel.
- La bibliothèque **`@ngx-translate/core`** est la référence éprouvée pour Angular/Ionic, avec lazy-loading par locale.
- Les emails transactionnels (reset password, consent parental, support) sont des templates Node.js → des fichiers JSON de traduction côté backend suffisent.

**Exception : contenu éditorial en BDD**

Deux types de contenu sont déjà stockés en base de données et doivent y rester traduits :
- **Documents légaux** (`legal_documents` table) → ajouter colonne `lang` ou table `legal_document_translations`
- **Descriptions de badges** (`badges` table) → ajouter colonne `name_en`, `description_en` ou table `badge_translations`

---

## 2. Architecture retenue

```
frontend/
  src/
    assets/
      i18n/
        fr.json          ← traductions françaises (langue par défaut)
        en.json          ← traductions anglaises
    app/
      app.module.ts      ← import TranslateModule + HttpLoaderFactory
      app.component.ts   ← détection + initialisation de la langue

backend-api/
  src/
    i18n/
      fr.json            ← messages API + emails en français
      en.json            ← messages API + emails en anglais
    utils/
      i18n.js            ← helper getLang(req) + t(lang, key, params)
```

**Bibliothèques frontend :**
- `@ngx-translate/core` — moteur de traduction Angular
- `@ngx-translate/http-loader` — chargement des JSON via HTTP (lazy loading par locale)

**Côté backend :**
- Pas de bibliothèque externe — un simple helper `t(lang, key)` qui lit les JSON en mémoire suffit.
- La langue est détectée depuis le header `Accept-Language` ou un paramètre `?lang=fr`.

---

## 3. Inventaire complet des fichiers à traduire

### 3.1 Frontend — Templates HTML (31 fichiers)

Chaque fichier `.html` contient des libellés hardcodés à remplacer par le pipe `| translate`.

| Fichier | Contenu traduit | Volume estimé |
|---|---|---|
| `pages/tabs/tabs.page.html` | Labels nav : Accueil, Ajouter, Badges, Messages, Profil | ~5 clés |
| `pages/auth/login.page.html` | Email, Mot de passe, Se connecter, Mot de passe oublié, placeholder | ~8 clés |
| `pages/auth/register.page.html` | Pseudo, Email, Mot de passe, Confirmer, CGU, placeholder, bouton S'inscrire | ~20 clés |
| `pages/auth/forgot-password.page.html` | Titre, instructions, bouton Réinitialiser | ~6 clés |
| `pages/reset-password/reset-password.page.html` | Nouveau mot de passe, Confirmer, bouton | ~6 clés |
| `pages/verify-email/verify-email.page.html` | Message de confirmation email | ~4 clés |
| `pages/profile/profile.page.html` | Profil, Mes fails, Abonnés, Abonnements, Support, placeholder support | ~25 clés |
| `pages/edit-profile/edit-profile.page.html` | Bio, pseudo, bouton Sauvegarder | ~10 clés |
| `pages/change-photo/change-photo.page.html` | Changer photo, Galerie, Caméra, Supprimer | ~8 clés |
| `pages/post-fail/post-fail.page.html` | Titre, Description, Catégorie, Publier, Privé/Public | ~15 clés |
| `pages/my-fails/my-fails.page.html` | Mes fails, Aucun fail, bouton créer | ~8 clés |
| `pages/badges/badges.page.html` | Badges, Aucun badge, descriptions | ~10 clés |
| `pages/messages/messages.page.html` | Messages, Aucun message, Nouveau | ~6 clés |
| `pages/messages/conversation/conversation.page.html` | Envoyer, placeholder Message… | ~5 clés |
| `pages/legal/legal.page.html` | Mentions légales, liste des documents | ~10 clés |
| `pages/legal-document/legal-document.page.html` | Chargement, titre dynamique | ~3 clés |
| `pages/privacy-settings/privacy-settings.page.html` | Confidentialité, options, boutons | ~20 clés |
| `pages/sessions/sessions.page.html` | Sessions actives, Déconnecter, En cours | ~10 clés |
| `pages/share/share.page.html` | Partager, Copier le lien | ~5 clés |
| `pages/user-profile/user-profile.page.html` | Profil utilisateur, Suivre, Abonnés | ~12 clés |
| `pages/admin/admin.page.html` | Tableau de bord admin, labels colonnes | ~30 clés |
| `pages/admin/logs/admin-logs.page.html` | Logs, Filtrer, Exporter | ~20 clés |
| `pages/admin/moderation/moderation.page.html` | Modération, Signalements, Actions | ~25 clés |
| `pages/admin/moderation-settings/admin-moderation.page.html` | Paramètres modération | ~15 clés |
| `pages/debug/debug.page.html` | Debug (page interne, peut rester en FR) | ~5 clés |
| `home/home.page.html` | Page d'accueil, feed, textes vides | ~10 clés |
| `components/fail-card/fail-card.component.html` | Réactions, Commenter, Signaler, il y a X temps | ~15 clés |
| `components/comments-thread/comments-thread.component.html` | Commentaires, Répondre, Supprimer | ~10 clés |
| `components/auth-required-modal/auth-required-modal.component.html` | Modal connexion requise | ~8 clés |
| `components/legal-consent-modal/legal-consent-modal.component.html` | Consentement, J'accepte | ~10 clés |
| `components/avatar-selector/avatar-selector.component.html` | Choisir avatar | ~5 clés |
| `app/app.component.html` | Structure globale | ~3 clés |

**Total estimé : ~320 clés de traduction HTML**

---

### 3.2 Frontend — Services TypeScript (messages inline)

Ces fichiers contiennent des chaînes françaises hardcodées dans le code TS.

| Fichier | Type de contenu | Priorité |
|---|---|---|
| `services/auth.service.ts` | Messages de validation (pseudo trop court, déjà pris, erreur connexion…) | Haute |
| `services/auth.service.ts` | Messages de retour inscription/login | Haute |
| `services/registration.service.ts` | Messages flow inscription mineur | Haute |
| `services/registration-wizard.service.ts` | Étapes wizard, erreurs | Haute |
| `services/fail.service.ts` | Erreurs chargement fails | Moyenne |
| `services/comment.service.ts` | Erreurs commentaires | Moyenne |
| `services/follow.service.ts` | Messages suivi/abonnement | Moyenne |
| `services/consent.service.ts` | Texte email consentement parental (placeholder) | Haute |
| `services/badge.service.ts` | Descriptions badges | Basse |
| `services/notification.service.ts` | Messages de notification push | Moyenne |
| `services/moderation.service.ts` | Messages modération | Basse |
| `directives/auth-action.directive.ts` | 'Connexion requise pour cette action' | Haute |

**Approche :** Injecter `TranslateService` dans les services et remplacer les strings hardcodées par `this.translate.instant('clé')`.

---

### 3.3 Frontend — Pages TypeScript (toasts, alerts, confirms)

Ces fichiers font appel à `ToastController`, `AlertController` ou `ModalController` avec du texte français.

| Fichier | Contenu | Priorité |
|---|---|---|
| `pages/auth/login.page.ts` | Toast erreur connexion | Haute |
| `pages/auth/register.page.ts` | Toasts inscription, gestion parental | Haute |
| `pages/auth/forgot-password.page.ts` | Toast email envoyé / erreur | Haute |
| `pages/reset-password/reset-password.page.ts` | Toast succès / erreur | Haute |
| `pages/profile/profile.page.ts` | Toasts support, suppression compte | Haute |
| `pages/edit-profile/edit-profile.page.ts` | Toast sauvegarde profil | Haute |
| `pages/change-photo/change-photo.page.ts` | Toast photo mise à jour | Haute |
| `pages/post-fail/post-fail.page.ts` | Toast publication fail | Haute |
| `pages/privacy-settings/privacy-settings.page.ts` | Toast paramètres, confirm suppression | Haute |
| `pages/sessions/sessions.page.ts` | Toast déconnexion session | Moyenne |
| `pages/messages/messages.page.ts` | Toast message envoyé / erreur | Moyenne |
| `pages/messages/conversation/conversation.page.ts` | Formatage date en fr-FR → dynamique | Haute |
| `pages/user-profile/user-profile.page.ts` | Toast follow/unfollow | Moyenne |
| `pages/admin/admin.page.ts` | Toasts admin, formatage dates | Basse |
| `pages/admin/logs/admin-logs.page.ts` | Formatage dates fr-FR | Basse |
| `pages/badges/badges.page.ts` | Notification badge débloqué | Moyenne |
| `pages/my-fails/my-fails.page.ts` | Toast suppression fail | Haute |

**Note :** Les `toLocaleString('fr-FR')` et `toLocaleDateString('fr-FR')` hardcodés devront utiliser la locale active.

---

### 3.4 Backend — Messages API (réponses JSON)

Les `res.json({ message: '...' })` sont renvoyés au frontend et parfois affichés directement à l'utilisateur.

| Fichier route/controller | Messages à traduire | Priorité |
|---|---|---|
| `middleware/auth.js` | "Token d'accès requis", "Session invalide", "Token expiré ou invalide" | Haute |
| `controllers/authController.js` | Erreurs login, inscription, reset password | Haute |
| `controllers/ageVerificationController.js` | Refus d'accès par âge | Haute |
| `controllers/failsController.js` | Messages création/suppression fail | Haute |
| `controllers/commentsController.js` | Messages commentaires | Haute |
| `controllers/reactionsController.js` | Messages réactions | Moyenne |
| `controllers/messagesController.js` | Messages chat | Moyenne |
| `controllers/moderationSettingsController.js` | Messages paramètres modération | Basse |
| `routes/follows.js` | "Impossible de se suivre soi-même", erreurs follow | Haute |
| `routes/registration.js` | Flow inscription, erreurs | Haute |
| `routes/notifications.js` | Erreurs notifications | Moyenne |
| `routes/support.js` | Labels type de support | Basse |
| `routes/users.js` | Messages profil | Haute |
| `routes/legal.js` | Messages documents légaux | Moyenne |
| `routes/upload.js` | Erreurs upload | Moyenne |
| `routes/badges.js` | Messages badges | Basse |
| `routes/push.js` | Erreurs push | Basse |
| `routes/share.js` | Messages partage | Basse |
| `routes/consents.js` | Messages consentement | Haute |
| `routes/privacySettings.js` | Messages confidentialité | Haute |
| `routes/reactions.js` | Messages réactions | Basse |
| `routes/admin.js` | Messages admin | Basse |

**Approche :** Détecter la langue depuis `req.headers['accept-language']` ou `req.query.lang`, puis utiliser `t(lang, 'clé')` dans les réponses.

**Attention :** Tous les messages API ne sont pas forcément visibles par l'utilisateur final (logs serveur, codes d'erreur internes). Séparer les **messages user-facing** des messages de log.

---

### 3.5 Backend — Emails transactionnels

Les emails sont des templates HTML/text hardcodés en français.

| Fichier | Email concerné | Contenu à traduire |
|---|---|---|
| `utils/mailer.js` | Reset password | Sujet, corps texte, lien | 
| `utils/mailer.js` | `sendParentConsentEmail()` | Sujet, corps consentement parental |
| `routes/registration.js` | Email confirmation d'inscription | Sujet, corps HTML |
| `routes/support.js` | Email de support interne | typeLabels, contenu HTML du template |

**Approche :** Extraire chaque template email dans `backend-api/src/i18n/emails/fr/` et `en/` sous forme de fichiers JSON ou de modules JS avec les chaînes. La langue utilisée = langue communiquée par le frontend lors de l'action (header `Accept-Language` ou champ `lang` dans le body de la requête).

---

### 3.6 Backend — Page HTML consent parental

| Fichier | Description |
|---|---|
| `routes/registration.js` → `parentConsentPageTemplate()` | Page HTML servie directement par Express pour la confirmation du consentement parental. Contient des textes FR hardcodés dans les strings template literals. |

Cette page est unique : elle n'est pas rendue par Angular mais par Express directement. Elle doit être traduite via le même système `t(lang, 'clé')` backend. La langue peut être passée en query param dans l'URL de consentement (ex: `?lang=fr`).

---

### 3.7 Base de données — Contenu éditorial

Deux tables contiennent du contenu traduit qui doit rester en BDD.

#### Table `legal_documents`

Actuellement : une rangée par type de document, langue unique (FR).  
**Solution :** Ajouter une colonne `lang VARCHAR(5) DEFAULT 'fr'` et une contrainte unique sur `(slug, lang)`.

Documents concernés (6 slugs) :
- `legal-notice`
- `terms-of-service`
- `privacy-policy`
- `moderation-charter`
- `help-resources`
- `age-restrictions`

Migration SQL :
```sql
ALTER TABLE legal_documents ADD COLUMN lang VARCHAR(5) NOT NULL DEFAULT 'fr';
ALTER TABLE legal_documents DROP INDEX IF EXISTS legal_documents_document_type_unique;
ALTER TABLE legal_documents ADD UNIQUE KEY uq_legal_slug_lang (slug, lang);
```

Route backend à mettre à jour : `GET /api/legal/:slug` → accepter `?lang=en` et servir la bonne version.

#### Table `badges`

Actuellement : `name` et `description` en français.  
**Solution :** Ajouter colonnes `name_en VARCHAR(100)` et `description_en TEXT`.  
Alternative plus propre (si beaucoup de langues) : table `badge_translations(badge_id, lang, name, description)`.

Pour une roadmap à 2 langues, les colonnes suffisent. Pour 3+ langues, préférer la table de traductions.

Migration SQL :
```sql
ALTER TABLE badges ADD COLUMN name_en VARCHAR(100) DEFAULT NULL;
ALTER TABLE badges ADD COLUMN description_en TEXT DEFAULT NULL;
```

---

## 4. Périmètre exclu ou secondaire

| Élément | Raison de l'exclusion |
|---|---|
| `pages/debug/debug.page.html` | Page interne dev uniquement |
| Messages `console.error` / `console.log` | Pas visibles par les utilisateurs |
| Logs stockés en BDD (`logs` table) | Logs techniques, rester en FR |
| Pages admin (priorité basse) | Utilisateurs internes, uniquement FR acceptable |
| Commentaires de code | Non pertinent |
| Noms de variables et fonctions | Non pertinent |

---

## 5. Roadmap détaillée étape par étape

### Phase 1 — Setup infrastructure frontend (1–2 jours)

**Étape 1.1 — Installation de @ngx-translate**
```bash
pnpm -C frontend add @ngx-translate/core @ngx-translate/http-loader
```

**Étape 1.2 — Configuration dans `app.config.ts` ou `app.module.ts`**

Ajouter `TranslateModule.forRoot()` avec `HttpLoaderFactory` pointant vers `assets/i18n/`.

**Étape 1.3 — Créer les fichiers JSON de base**
```
frontend/src/assets/i18n/fr.json
frontend/src/assets/i18n/en.json
```

Structure initiale : JSON vide `{}` à remplir au fur et à mesure.

**Étape 1.4 — Créer le service `LanguageService`**

Service Angular qui :
- Lit la langue sauvegardée (`localStorage['lang']`)
- À défaut, détecte depuis `navigator.language`
- Appelle `translate.use(lang)` pour charger le bon JSON
- Expose `setLanguage(lang: string)` pour le sélecteur de langue

**Étape 1.5 — Initialisation dans `app.component.ts`**

Appeler `languageService.init()` au démarrage de l'app.

---

### Phase 2 — Migration des templates HTML (3–4 jours)

**Étape 2.1 — Templates prioritaires (auth, navigation)**

Ordre de priorité :
1. `tabs.page.html` — Navigation principale
2. `auth/login.page.html` — Connexion
3. `auth/register.page.html` — Inscription
4. `auth/forgot-password.page.html`
5. `reset-password/reset-password.page.html`

Remplacement standard :
```html
<!-- Avant -->
<ion-label>Accueil</ion-label>

<!-- Après -->
<ion-label>{{ 'nav.home' | translate }}</ion-label>
```

Pour les `placeholder` :
```html
<!-- Avant -->
placeholder="votre@email.com"

<!-- Après -->
[placeholder]="'auth.email_placeholder' | translate"
```

**Étape 2.2 — Templates secondaires (profil, fails, messages, badges)**

Même approche, fichiers listés en §3.1.

**Étape 2.3 — Composants réutilisables**

`fail-card`, `comments-thread`, `auth-required-modal`, `legal-consent-modal`, `avatar-selector`.

**Étape 2.4 — Pages admin (optionnel)**

Basse priorité, peut rester en FR pour la V1.

---

### Phase 3 — Migration des services et pages TypeScript (2–3 jours)

**Étape 3.1 — Injection de `TranslateService` dans les services**

Pour les services Angular, injecter `TranslateService` et remplacer :
```typescript
// Avant
throw new Error('Le pseudo doit contenir au moins 3 caractères');

// Après
const msg = this.translate.instant('validation.username_too_short');
throw new Error(msg);
```

**Étape 3.2 — Toasts et alerts dans les pages**

```typescript
// Avant
const toast = await this.toastController.create({
  message: 'Profil mis à jour avec succès',
  duration: 2000
});

// Après
const toast = await this.toastController.create({
  message: this.translate.instant('profile.save_success'),
  duration: 2000
});
```

**Étape 3.3 — Formatage des dates selon la locale**

Remplacer les `toLocaleDateString('fr-FR', ...)` hardcodés par une utilisation de la locale active :
```typescript
// Avant
return date.toLocaleDateString('fr-FR', { weekday: 'long', day: 'numeric', month: 'long' });

// Après
const locale = this.languageService.currentLang; // 'fr' ou 'en'
return date.toLocaleDateString(locale, { weekday: 'long', day: 'numeric', month: 'long' });
```

---

### Phase 4 — Setup infrastructure backend (1 jour)

**Étape 4.1 — Créer le helper i18n backend**

Fichier : `backend-api/src/utils/i18n.js`
```javascript
const fr = require('../i18n/fr.json');
const en = require('../i18n/en.json');
const catalogs = { fr, en };

function getLang(req) {
  const ql = req.query?.lang;
  if (ql && catalogs[ql]) return ql;
  const ah = req.headers?.['accept-language'] || '';
  const detected = ah.split(',')[0]?.split('-')[0]?.toLowerCase();
  return catalogs[detected] ? detected : 'fr';
}

function t(lang, key, params = {}) {
  const catalog = catalogs[lang] || catalogs['fr'];
  const parts = key.split('.');
  let val = catalog;
  for (const p of parts) val = val?.[p];
  if (typeof val !== 'string') return key;
  return val.replace(/\{\{(\w+)\}\}/g, (_, k) => params[k] ?? `{{${k}}}`);
}

module.exports = { getLang, t };
```

**Étape 4.2 — Créer les fichiers JSON backend**
```
backend-api/src/i18n/fr.json
backend-api/src/i18n/en.json
```

**Étape 4.3 — Créer les templates emails traduits**

Pour chaque email (reset password, consent parental, confirmation inscription), extraire le corps HTML/text dans des fonctions paramétrées qui acceptent `lang`.

---

### Phase 5 — Migration messages backend (2–3 jours)

**Étape 5.1 — Middleware auth**

`backend-api/src/middleware/auth.js` — 4 messages user-facing.

**Étape 5.2 — Controllers (auth, fails, comments, reactions)**

Fichiers listés en §3.4, en commençant par `authController.js` (le plus critique).

**Étape 5.3 — Routes (follows, registration, users)**

**Étape 5.4 — Emails transactionnels**

Refactorer `utils/mailer.js` pour accepter un paramètre `lang` et charger le bon template.

**Étape 5.5 — Page HTML consent parental**

Passer `?lang=fr` dans l'URL de consent et utiliser `t(lang, ...)` dans `parentConsentPageTemplate()`.

---

### Phase 6 — Migrations base de données (1 jour)

**Étape 6.1 — Migration `legal_documents`**

Écrire et tester la migration SQL (§3.7).  
Créer les versions EN des 6 documents légaux.  
Mettre à jour la route `GET /api/legal/:slug` pour accepter `?lang=`.  
Mettre à jour `LegalService` Angular pour passer la langue courante.

**Étape 6.2 — Migration `badges`**

Ajouter colonnes `name_en` et `description_en`.  
Remplir les traductions pour les badges existants.  
Mettre à jour la route `GET /api/badges` pour servir le bon champ selon la langue.

---

### Phase 7 — Sélecteur de langue dans l'UI (1 jour)

**Étape 7.1 — Composant sélecteur de langue**

Ajouter un sélecteur (select ou ion-segment) dans :
- Page Profil (`profile.page.html`) → préférence persistante
- Settings ou menu utilisateur

**Étape 7.2 — Persistance**

Sauvegarder le choix dans `localStorage` et dans les `privacy_settings` ou `users.lang_preference` (si colonne ajoutée).

---

### Phase 8 — Tests et validation (1–2 jours)

Voir §11 pour le détail des tests.

---

### Phase 9 — Regenerer le dump BDD (à chaque migration SQL)

Après chaque migration SQL jouée en développement local :
```powershell
pwsh -File scripts/dump-bdd-local.ps1
git add backend-api/migrations/faildaily_bdd.sql
git commit -m "chore: update db dump after i18n migration"
```

---

## 6. Structure des fichiers JSON de traduction

### Frontend — `fr.json` (structure de référence)

```json
{
  "nav": {
    "home": "Accueil",
    "add_fail": "Ajouter",
    "badges": "Badges",
    "messages": "Messages",
    "profile": "Profil"
  },
  "auth": {
    "login": {
      "title": "Connexion",
      "email_placeholder": "votre@email.com",
      "password_placeholder": "••••••••",
      "submit": "Se connecter",
      "forgot_password": "Mot de passe oublié ?",
      "no_account": "Pas encore de compte ?",
      "register_link": "S'inscrire"
    },
    "register": {
      "title": "Créer un compte",
      "username_placeholder": "Comment vous appeler ?",
      "submit": "S'inscrire",
      "already_account": "Déjà un compte ?",
      "login_link": "Se connecter"
    },
    "forgot_password": {
      "title": "Mot de passe oublié",
      "instructions": "Entrez votre email pour recevoir un lien de réinitialisation",
      "submit": "Envoyer"
    },
    "errors": {
      "invalid_credentials": "Email ou mot de passe incorrect",
      "account_disabled": "Compte désactivé",
      "unknown": "Erreur de connexion inconnue"
    }
  },
  "validation": {
    "username_too_short": "Le pseudo doit contenir au moins 3 caractères",
    "username_too_long": "Le pseudo ne peut pas dépasser 30 caractères",
    "username_available": "✅ Ce pseudo est disponible",
    "username_taken": "❌ Ce pseudo est déjà pris. Suggestion: {{suggestion}}",
    "username_taken_no_suggest": "❌ Ce pseudo est déjà pris",
    "username_check_error": "Erreur lors de la vérification du pseudo"
  },
  "fail": {
    "post": {
      "title_placeholder": "Titre de ton fail...",
      "description_placeholder": "Décris ton fail...",
      "submit": "Publier",
      "visibility_public": "Public",
      "visibility_private": "Privé",
      "success": "Fail publié !",
      "error": "Erreur lors de la publication"
    },
    "card": {
      "comment": "Commenter",
      "share": "Partager",
      "report": "Signaler",
      "ago": "il y a {{time}}"
    }
  },
  "profile": {
    "tabs": {
      "fails": "Mes fails",
      "followers": "Abonnés",
      "following": "Abonnements"
    },
    "edit": {
      "save_success": "Profil mis à jour",
      "save_error": "Erreur lors de la sauvegarde"
    }
  },
  "messages": {
    "input_placeholder": "Message…",
    "send_error": "Erreur lors de l'envoi"
  },
  "sessions": {
    "title": "Sessions actives",
    "current": "En cours",
    "disconnect": "Déconnecter",
    "disconnect_success": "Session déconnectée"
  },
  "privacy": {
    "title": "Confidentialité",
    "save_success": "Paramètres enregistrés",
    "delete_account_confirm": "Supprimer votre compte ? Cette action est irréversible."
  },
  "legal": {
    "title": "Mentions légales",
    "loading": "Chargement..."
  },
  "badges": {
    "title": "Badges",
    "none": "Aucun badge pour l'instant",
    "unlocked": "Badge débloqué : {{name}}"
  },
  "common": {
    "loading": "Chargement...",
    "error": "Une erreur est survenue",
    "retry": "Réessayer",
    "cancel": "Annuler",
    "confirm": "Confirmer",
    "save": "Sauvegarder",
    "delete": "Supprimer",
    "close": "Fermer",
    "back": "Retour",
    "yes": "Oui",
    "no": "Non"
  },
  "auth_required": {
    "title": "Connexion requise",
    "message": "Connexion requise pour cette action",
    "login_button": "Se connecter",
    "register_button": "S'inscrire"
  }
}
```

### Backend — `fr.json` (structure de référence)

```json
{
  "auth": {
    "token_required": "Token d'accès requis",
    "session_invalid": "Session invalide, veuillez vous reconnecter",
    "token_expired": "Token expiré ou invalide",
    "token_invalid": "Token invalide",
    "reset_link_sent": "Si cet email existe, un lien de réinitialisation a été envoyé",
    "reset_error": "Erreur lors de la demande de réinitialisation",
    "reset_confirmed": "Mot de passe réinitialisé avec succès",
    "reset_confirm_error": "Erreur lors de la confirmation de réinitialisation"
  },
  "follows": {
    "self_follow": "Impossible de se suivre soi-même",
    "unauthorized": "Action non autorisée",
    "created": "Suivi créé",
    "deleted": "Suivi supprimé",
    "error_create": "Erreur création follow",
    "error_delete": "Erreur suppression follow",
    "error_read": "Erreur lecture follow",
    "error_feed": "Erreur chargement du feed"
  },
  "errors": {
    "internal": "Erreur interne",
    "not_found": "Ressource introuvable",
    "forbidden": "Action non autorisée",
    "bad_request": "Requête invalide"
  },
  "emails": {
    "reset_password": {
      "subject": "🔐 Réinitialisation de votre mot de passe - FailDaily",
      "body": "Bonjour,\n\nVous avez demandé à réinitialiser votre mot de passe.\n\nCliquez sur ce lien pour définir un nouveau mot de passe (valide 1h) :\n{{link}}\n\nSi vous n'êtes pas à l'origine de cette demande, ignorez cet email."
    },
    "parent_consent": {
      "subject": "Consentement parental requis - FailDaily",
      "body": "Bonjour,\n\nVotre enfant {{child_name}} souhaite créer un compte FailDaily.\n\nCliquez sur ce lien pour donner votre consentement :\n{{link}}"
    }
  }
}
```

---

## 7. Conventions et règles de nommage des clés

- **Hiérarchie dot-notation** : `page.section.element` (ex: `auth.login.submit`)
- **Snake_case** pour les noms de clés
- **Pas de majuscules** dans les clés (sauf acronymes si nécessaire)
- **Namespacing par page** pour éviter les collisions : `profile.`, `fail.`, `badges.`, etc.
- **Namespace `common`** pour les textes réutilisés partout : `common.loading`, `common.error`, etc.
- **Namespace `validation`** pour les messages de validation formulaire
- **Namespace `errors`** dans chaque section pour les erreurs spécifiques
- **Paramètres interpolés** : utiliser `{{nomDuParam}}` (compatible ngx-translate et le helper backend)
- **Éviter les duplications** : si un texte est identique dans plusieurs pages, le mettre dans `common`

---

## 8. Gestion des langues dans Angular/Ionic

### Installation

```bash
pnpm -C frontend add @ngx-translate/core @ngx-translate/http-loader
```

### Configuration (app.config.ts)

```typescript
import { TranslateModule, TranslateLoader } from '@ngx-translate/core';
import { TranslateHttpLoader } from '@ngx-translate/http-loader';
import { HttpClient } from '@angular/common/http';

export function HttpLoaderFactory(http: HttpClient) {
  return new TranslateHttpLoader(http, './assets/i18n/', '.json');
}

// Dans providers :
importProvidersFrom(
  TranslateModule.forRoot({
    defaultLanguage: 'fr',
    loader: {
      provide: TranslateLoader,
      useFactory: HttpLoaderFactory,
      deps: [HttpClient]
    }
  })
)
```

### Dans les templates HTML

```html
<!-- Texte simple -->
{{ 'clé.de.traduction' | translate }}

<!-- Attribut -->
[placeholder]="'clé' | translate"

<!-- Avec paramètre -->
{{ 'validation.username_taken' | translate: { suggestion: suggestedName } }}
```

### Dans les composants/services TypeScript

```typescript
constructor(private translate: TranslateService) {}

// Synchrone (si le JSON est déjà chargé)
const msg = this.translate.instant('clé');

// Asynchrone (si besoin d'attendre le chargement)
this.translate.get('clé').subscribe(msg => { ... });
```

### Sélecteur de langue (LanguageService)

```typescript
@Injectable({ providedIn: 'root' })
export class LanguageService {
  private readonly LANG_KEY = 'lang';
  
  constructor(private translate: TranslateService) {}

  init() {
    const saved = localStorage.getItem(this.LANG_KEY);
    const browserLang = navigator.language.split('-')[0];
    const lang = saved || (this.isSupportedLang(browserLang) ? browserLang : 'fr');
    this.translate.use(lang);
  }

  setLanguage(lang: string) {
    if (this.isSupportedLang(lang)) {
      this.translate.use(lang);
      localStorage.setItem(this.LANG_KEY, lang);
    }
  }

  get currentLang(): string {
    return this.translate.currentLang || 'fr';
  }

  private isSupportedLang(lang: string): boolean {
    return ['fr', 'en'].includes(lang);
  }
}
```

---

## 9. Gestion des langues côté backend

### Détection de la langue

Le helper `getLang(req)` (défini en §5, étape 4.1) lit dans l'ordre :
1. `req.query.lang` (ex: `GET /api/legal/terms-of-service?lang=en`)
2. Header `Accept-Language` (ex: `Accept-Language: en-US,en;q=0.9`)
3. Défaut : `fr`

### Passage de la langue depuis le frontend

Dans `LegalService` Angular, passer la langue courante :
```typescript
getLegalDocument(slug: string): Observable<LegalDocument> {
  const lang = this.languageService.currentLang;
  return this.http.get<LegalDocument>(`/api/legal/${slug}?lang=${lang}`);
}
```

Pour les appels POST/PUT, ajouter un header :
```typescript
const headers = new HttpHeaders({ 'Accept-Language': this.languageService.currentLang });
```

Le backend peut alors lire ce header via le helper `getLang(req)`.

### Emails : passer la langue comme paramètre

L'action déclenchant un email (reset password, etc.) se fait depuis le frontend. Le frontend doit passer sa langue dans la requête (query param ou body `lang`). Le backend utilisera cette langue pour générer l'email.

---

## 10. Détection automatique de la langue

Ordre de priorité de détection (côté frontend) :

1. Langue sauvegardée en `localStorage` (choix explicite utilisateur)
2. Langue du navigateur (`navigator.language`)
3. Langue par défaut : `fr`

Côté backend, même logique via les headers HTTP.

**Pièges à éviter :**
- `navigator.language` retourne `'fr-FR'` → toujours prendre les 2 premiers chars.
- Ne pas basculer automatiquement si l'utilisateur a fait un choix explicite.
- Pour Capacitor (app mobile), `navigator.language` fonctionne normalement.

---

## 11. Tests et validation

### Frontend

**Tests manuels :**
- Changer la langue → vérifier que tous les textes de la page courante se mettent à jour
- Recharger la page → vérifier que la langue mémorisée est restaurée
- Tester chaque page listée en §3.1 dans les 2 langues
- Vérifier les placeholders, attributs `aria-label`, `title`
- Tester les toasts et alerts dans les 2 langues

**Tests automatisés (Cypress — `e2e/`) :**
- Ajouter des tests de smoke pour vérifier qu'aucune clé manquante (`[MissingTranslation]`) n'apparaît en prod
- Tester le changement de langue depuis l'UI

**Validation des fichiers JSON :**
```bash
# Vérifier la syntaxe JSON
cat frontend/src/assets/i18n/fr.json | python3 -m json.tool
cat frontend/src/assets/i18n/en.json | python3 -m json.tool

# Vérifier les clés manquantes en EN vs FR
node -e "
  const fr = require('./frontend/src/assets/i18n/fr.json');
  const en = require('./frontend/src/assets/i18n/en.json');
  // script de diff récursif des clés
"
```

### Backend

**Tests manuels :**
- Appeler les routes avec `?lang=en` et `?lang=fr` → vérifier les messages retournés
- Appeler sans lang → vérifier le fallback vers `fr`
- Tester les emails en changeant la langue dans la requête

**Smoke tests existants :**
```bash
NODE_ENV=test DB_DISABLED=true JWT_SECRET=test_jwt_secret_ci \
SMTP_PASS=fake OPENAI_API_KEY=fake LOGS_DB_PASSWORD=fake DB_PASSWORD=fake \
pnpm -C backend-api test:smoke
```

---

## 12. Risques et points de vigilance

| Risque | Impact | Mitigation |
|---|---|---|
| Clé manquante dans `en.json` → ngx-translate affiche la clé brute | UX dégradée | Activer `missingTranslationHandler` pour logguer les clés manquantes. Configurer le fallback vers `fr`. |
| Messages backend non traduits affichés au frontend | UX incohérente | Prioriser les messages user-facing dans la phase 5. Les logs internes peuvent rester en FR. |
| Emails envoyés dans la mauvaise langue | Mauvaise expérience | Toujours passer `lang` dans les requêtes déclenchant un email. |
| Documents légaux non disponibles en EN en BDD | Page légale cassée en EN | Créer les versions EN avant de mettre en prod le sélecteur de langue. |
| `translate.instant()` appelé avant le chargement du JSON | Retourne la clé brute | Initialiser la langue dans `APP_INITIALIZER` pour s'assurer que le JSON est chargé avant le rendu. |
| Capacitor app rebuild nécessaire après ajout de fichiers JSON | Délai de déploiement mobile | Prévoir un rebuild Capacitor à chaque modification des fichiers i18n. |
| Injection SQL ou XSS via les clés traduites | Sécurité | Ne jamais eval/innerHTML les strings traduites. Utiliser uniquement le pipe `translate` Angular (safe by DomSanitizer). |
| Dump BDD désynchronisé après migrations i18n | Incohérence schema | Régénérer et commiter le dump après chaque migration SQL i18n. |

---

## Résumé synthétique

| Périmètre | Méthode | Fichiers principaux |
|---|---|---|
| UI Angular/Ionic (templates, toasts, services) | **JSON** (`@ngx-translate`) | `assets/i18n/fr.json`, `en.json` |
| Messages API backend | **JSON** (helper `i18n.js`) | `src/i18n/fr.json`, `en.json` |
| Emails transactionnels | **JSON** (templates dans i18n) | `src/i18n/fr.json` sections `emails.*` |
| Page consent parental HTML | **JSON** (même helper backend) | idem backend |
| Documents légaux | **BDD** (colonne `lang`) | Table `legal_documents` |
| Badges | **BDD** (colonnes `name_en`, `description_en`) | Table `badges` |

**Total fichiers frontend à modifier :** ~45 fichiers (31 HTML + 14 TS)  
**Total fichiers backend à modifier :** ~25 fichiers (routes + controllers + utils)  
**Nouvelles clés de traduction estimées :** ~400–500 clés  
**Durée totale estimée :** 2–3 semaines de travail effectif

