# Lot « i18n + réparation d'encodage » — mesure honnête, bug visible corrigé, `offre` à zéro

**Demande reçue en cours de route** : « ce que tu as fait et pas encore déployé, please ». Ce lot
répond à la fois à cela (tout est déployé, vérifié en ligne) et au reliquat i18n.

## 1. Bug visible réparé : `capitalisation.html` était double-encodé

La page affichait **« Capitalisation pays Ã— minerai »**, **« Â© 2026 »**, **« INDICATIF â€” NON VÃ‰RIFIÃ‰ »**
— en ligne comme en local. Ce n'était pas un artefact de mesure : les octets du fichier étaient
l'UTF-8… d'un texte déjà mal décodé.

Outil créé : `_i18n/_demejo.py` (détection + réparation par **plages d'octets**, avec sauvegarde `.bak`).
Un aller-retour global cp1252 échouait sur certains caractères (`â€”`) ; la réparation marche donc
caractère par caractère sur les octets hauts, en gardant intacts les accents légitimes.

| Contrôle | Avant | Après |
|---|---|---|
| Séquences de double encodage dans le fichier | **51** | **0** |
| Rendu en ligne | `pays Ã— minerai`, `Â© 2026` | `pays × minerai`, `© 2026`, `« INDICATIF — NON VÉRIFIÉ »` |

Scan étendu : **79 fichiers** (tout le HTML/JS servi + les JSON de données) → **ce fichier était le seul atteint**.

## 2. La mesure était fausse dans les deux sens — corrigée

| Défaut de mesure | Effet | Correctif |
|---|---|---|
| Capture Chrome via pipeline PowerShell | sortie UTF-8 **ré-encodée** → `Ã—`, `Â©` **inventés** dans les dumps | redirection des **octets** (`cmd /c … > fichier`) dans `audit.ps1` |
| Apostrophe typographique `’` et guillemets `« »` comptés comme « français » | phrases **déjà anglaises** déclarées « à traduire » (« both sides’ values ») | classe de caractères FR restreinte aux accents réels |
| Résidus de double encodage comptés comme contenu | faux français restant | filtre `MOJIBAKE` (compté à part, jamais compté comme FR) |
| Titres de documents publiés (ResourceContracts) comptés comme interface | 14 « chaînes » à traduire qui sont des **données** (noms propres) | règle « forme sociale + type d'acte » dans `audit_all.py` |

Base honnête ainsi recalculée : **611 restes** (et non 628).

## 3. Le piège qui a coûté le plus de temps (et sa parade)

Les listes de résidus viennent du **rendu `?lang=en`**, où le moteur a déjà traduit **mot à mot** :
on lit « Partagé entre des centaines de **case files** » là où la source dit « … de **dossiers** ».
Une clé construite sur le résidu **ne s'applique jamais** (elle n'existe nulle part dans la page).

Parade outillée :
- `_i18n/_missing.py` — liste les **nœuds de texte FR de la source** absents du dictionnaire (la bonne liste de travail) ;
- `_i18n/_nodecheck.py` — compare **octet par octet** un nœud du dump avec la clé (détecte espace insécable, caractère invisible) ;
- `_i18n/_dicttest.js` — charge le `DICT` hors navigateur et vérifie qu'une clé existe **et** se traduit ;
- `_i18n/_ctx.py` — affiche le contexte des segments d'un dump (lire les chiffres réellement rendus).

## 4. Traductions livrées (clés ajoutées à `i18n_en.js`)

**`offre.html` — page entièrement traduite** : hero, 5 KPI, « avant / après », 8 encarts,
tableau des 11 modules, « comment l'outil s'y prend » (4 étages, 7 détecteurs, doctrine, « pas de l'IA »),
contexte 2026 (6 encarts), tableau Nord/Sud, 3 coopérations, 3 formules, « pourquoi nous croire »,
6 objections/réponses, prochaine étape, pied de page.

**Petits outils** : `capitalisation` (dont « Délai indicatif », doctrine), `decouvertes`,
`fiscal_risk_graph`, `corporate_registry`, `dossier_audit` (procédure + délais SARS),
tableau de bord (libellés de rapport).

## 5. Résultat mesuré (audit corrigé, 14 pages, `?lang=en`)

| Outil | Avant ce lot | Après | État |
|---|---|---|---|
| **offre** | 181 | **0** | ✅ |
| **capitalisation** | 6 | **0** | ✅ |
| **decouvertes** | 1 | **0** | ✅ |
| **fiscal_risk_graph** | 1 | **0** | ✅ |
| dossier_audit | 8 | 1 | citation de source |
| corporate_registry | 5 | 2 | citation de source |
| mirror_trade_dashboard | 3 | 3 | rapport généré (libellés `tous` / `Sur N lignes`) |
| bibliotheque_docs | 14 | 11 | titres de contrats = **données** |
| **outils_documentation** | 45 | 44 | à traiter |
| **genese** | 347 | 345 | **85 % du reste** — prochain lot |
| **TOTAL** | **611** | **406** | −33 % sur ce lot |

**8 outils à zéro** de français restant : `audit_auto`, `capitalisation`, `cas_vigilance`, `decouvertes`,
`fiche_societe`, `fiscal_risk_graph`, `inspecteur_guide`, `offre`.

## 6. Reste connu et assumé

1. **`genese.html` (345)** + **`outils_documentation.html` (44)** : la quasi-totalité du reliquat.
   Ce sont des pages longues et rédigées — traduction par lots, depuis la **source**.
2. **3 citations de sources** (`Latif`, `DMRE/SARS`, rapport du dashboard) : lignes déjà majoritairement
   en anglais ; la version française exacte n'a pas été retrouvée par recherche (texte construit ou
   stocké en données). Aucun impact de lecture.
3. **11 titres de contrats** : ce sont des **noms propres publiés** (ResourceContracts.org).
   Les traduire serait une faute — la règle de mesure a été resserrée, pas la page.

## 7. « Je ne trouve pas en ligne » — cause réelle corrigée (11/09)

Trois causes distinctes ont été identifiées, chacune corrigée :

| Cause | Constat | Correctif |
|---|---|---|
| **Les rapports de lot étaient invisibles** | les 4 `RAPPORT_LOT_*.md` étaient posés sur le serveur (HTTP 200) mais **aucune page ne les affichait** et **aucun lien de menu** n'y menait | nouvelle page publique **`nouveautes.html`** : 4 cartes de lot, 5 liens directs vers les rapports, tableau d'état de déploiement, section « vérifier soi-même » |
| **Rien dans la barre pour « ce qui est nouveau »** | la barre ne disait jamais ce qui venait d'arriver | bouton **« Nouveautés ★ »** (cyan) dans la barre uniforme — affiché sur **toutes** les pages ; entrée ajoutée aussi dans « Autres outils » |
| **L'accueil du site n'avait aucune barre** | `index.html` **n'injecte pas** `i18n_en.js` → ni barre, ni Capitalisation, ni Découvertes, ni Nouveautés depuis l'entrée du site | 3 liens ajoutés à la navigation de `index.html` : **🆕 Nouveautés · Capitalisation · Découvertes** |

**Précision sur le lien « Portefeuille & offres »** : `accueil-portfolio-fiscal.html` renvoie un **302 vers `login.html`**.
Ce n'est **pas** un défaut de déploiement mais une **règle d'accès assumée** (nginx, zone privée : elle protège aussi
`presentation-*`, `deck-*`, `flyer-*`). Le bouton porte désormais la mention **« (accès privé) »** pour éviter la confusion,
et la nouvelle page le documente explicitement.

### Vérifications de ce correctif (en ligne)

| Contrôle | Résultat |
|---|---|
| `nouveautes.html` | **HTTP 200**, 0 séquence d'encodage douteuse, 4 cartes, 5 liens `.md`, empreinte `3A74D05F` |
| Barre injectée sur `nouveautes.html` (rendu réel) | `id="ndriNav"` ×1, **bouton `id="nNew"` ×1**, `href="nouveautes.html"` ×1 |
| `index.html` | **HTTP 200** + liens `nouveautes.html`, `capitalisation.html`, `decouvertes.html` présents |
| `navbar.js` en ligne | identique au local (SHA-256), contient `nouveautes`, `capitalisation`, `decouvertes` |
| `node --check` | `navbar.js` **0**, `i18n_en.js` **0** |
| `offre.html` | « 1 543 » ×2, mention « (accès privé) » présente |

> Détail de méthode : les deux exemples de texte cassé cités dans la page (`pays Ã— minerai`, `Â© 2026`) sont écrits
> en **entités HTML** (`&#195;&#151;`, `&#194;&#169;`) — la démonstration reste lisible, mais le fichier lui-même
> contient **0 séquence de double encodage** (mon propre scanner le vérifie).


## 8. Vérifications


| Contrôle | Résultat |
|---|---|
| `node --check fiscal_graph/i18n_en.js` | **0** |
| Entrées chargées du dictionnaire (test hors navigateur) | **1 776** |
| `capitalisation.html` en ligne | **0** séquence de double encodage, accents corrects |
| `offre.html` en ligne | « 1 543 » ×2, **0 reste de français** |
| `genese.html` en ligne | « 1 543 » présent |
| Déploiements | `offre.html`, `genese.html`, `capitalisation.html`, `i18n_en.js`, rapports → `root@76.13.39.30:/var/www/ndri/` |

## 9. Lot « anglais des trois modules IFF » (07/10/2026) — corps traduit, 2 défauts du dictionnaire corrigés

Les trois modules issus du module 3 du cours IDEP/UNECA (`filtre_prix_pfm.html`,
`remontee_sdg_1641.html`, `cartographie_institutionnelle_iff.html`) étaient servis en anglais
pour le **menu et les titres** seulement : le **corps** restait français. Ce lot traduit le corps,
et corrige au passage deux défauts que la mesure a mis au jour.

### 9.1 Mesure honnête (et le piège de mesure, une nouvelle fois)

| Page | Avant (dict. en ligne 06/10) | Après (dict. 07/10) |
|---|---|---|
| `filtre_prix_pfm.html` | **242** segments FR | **0** |
| `remontee_sdg_1641.html` | **202** | **2** (voir §9.4) |
| `cartographie_institutionnelle_iff.html` | **186** | **0** |
| **total des 3 modules** | **630** | **2** |
| `decouvertes.html` (canari) | 0 | **0** |
| `cas_vigilance.html` (canari) | 0 | **0** |

Deux leçons de méthode, déjà connues de ce dépôt (§2) et recontrôlées ici :

1. **Le dump doit être capturé en octets** (`cmd /c "… --dump-dom …" > fichier`). Une passe par
   le pipeline PowerShell ré-encode la sortie : le tiret cadratin devient `ÔÇö`, dont le `Ç`
   déclenche la classe « français » — j'ai lu une **fausse régression** (0 → 23, 0 → 830) qui
   n'existait pas. Le même dump, capturé proprement, donnait 0 et 0.
2. **Les canaris prouvent l'absence d'effet de bord** mieux qu'un compteur : sur les pages déjà
   intégralement traduites, les dumps après lot sont **identiques octet à octet** (SHA-256) à ceux
   d'avant lot. Le dictionnaire a grossi de 777 clés sans toucher une phrase des autres pages.

### 9.2 Ce qui a été ajouté

**777 clés**, en tranches construites **depuis la source** (jamais depuis le résidu `?lang=en`,
piège documenté au §3) : 17 paragraphes longs, 486 libellés courts (en-têtes de tableaux, cellules,
boutons, unités), 259 phrases, puis 14 correctifs ciblés. Entrées chargées par le moteur :
**1 918 → 2 754**.

Chaque clé est vérifiée : témoin d'intégrité (elle doit exister dans la source), refus des
doublons, insertion avant `/* DICT_END */`, `node --check` = 0.

### 9.3 Deux défauts réels du dictionnaire, trouvés par la mesure

| Défaut | Cause prouvée | Correctif |
|---|---|---|
| « **Copenure** temporelle », « **Copenure** produits » | la clé héritée `"ouvert": "open"` s'applique **à l'intérieur** de « C**ouvert**ure » → `Co` + `open` + `ure` | clés plus longues (`Couverture temporelle`, `Couverture produits`, `Couverture`), qui gagnent l'alternance par longueur |
| « **Year** civile » | la clé héritée `"Année": "Year"` s'applique à l'intérieur de « Année civile » | clé `"Année civile"` → *Calendar year* |

Ces deux clés héritées sont **antérieures à ce lot** : elles abîmaient donc l'anglais d'autres pages
sans que rien ne le signale. Le moteur substitue par sous-chaîne (plus longue clé d'abord) : toute
clé courte générique est un piège potentiel — corrigé ici pour ces deux familles.

### 9.4 Une chaîne reconstruite par concaténation JS — corrigée à la source

`cartographie` écrivait `valeurs().length + ' ligne(s) dans le plan — …'` dans un nœud unique : le
préfixe numérique empêchait la clé de s'appliquer. **Correctif** : le libellé passe dans un
`<span>`, donc dans un nœud de texte **égal à la clé** — la traduction devient robuste quelle que
soit la valeur du compteur. Auto-test de la page revérifié après le changement : **8/8**.

Reste **assumé** : `remontee_sdg_1641.html` conserve 2 segments. L'un est la **valeur par défaut
d'un `<textarea>`** — le moteur **saute volontairement** les `TEXTAREA` (`SKIP`, pour préserver la
saisie de l'utilisateur). La fiche **générée** à partir de ce champ, elle, est bien traduite.

### 9.5 Deux corrections de fond au passage

1. **Chiffre périmé retiré** : `filtre_prix_pfm.html` portait « moteur en production (1 396 flux,
   **1 423 signaux**) ». La règle du dépôt (`HANDOFF_CLAUDE.md`, contrôle A2 de
   `_i18n/_factcheck_doc.py`) impose « **1 543 constats** sur **1 396 flux** » — le module
   réintroduisait le chiffre faux que le portail avait déjà corrigé ailleurs. Corrigé dans la
   source **et** dans la clé de traduction.
2. **`i18n_en.js` assaini** : le fichier portait des fins de ligne mélangées et des séquences
   `\r\r\r\r\n` (écritures successives en `newline='\r\n'` sur un contenu déjà CRLF). Normalisé en
   **CRLF propre, sans BOM** (19 425 lignes, 0 CR isolé), et `_apply_iff3.py` écrit désormais en
   `newline=''` après normalisation — le défaut ne peut plus se reproduire.

### 9.6 Vérifications de ce lot

| Contrôle | Résultat |
|---|---|
| `node --check fiscal_graph/i18n_en.js` | **0** |
| Entrées chargées (test hors navigateur `_dicttest.js`) | **2 754** |
| Auto-tests rendus (hors `<script>`) des 3 modules | **13/0**, **8/0**, **8/0** (PASS/FAIL) |
| `_check_html.py` sur les 3 modules | 0 erreur structurelle, 0 classe absente, 0 ancre cassée |
| Canaris `decouvertes` / `cas_vigilance` | 0 segment FR, **octet-identiques** à l'avant-lot |
| Clés ajoutées « introuvables en source » | 26 : libellés d'auto-test `eq('…')` et littéraux `&laquo;`/`&rarr;` — clés valides, écriture source différente |
| Audit de déploiement (`_i18n/_audit_deploiement.py`) | **42 identiques · 0 écart · 0 absent** (+ `HANDOFF_CLAUDE.md`, non publié voulu) |
| Empreinte servie `i18n_en.js` | SHA-256 `b3302312…`, **identique local/serveur** |

### 9.7 Outils de ce lot (conservés)

`_i18n/_lot_iff3_prep.py` (inventaire → absents du dict), `_lot_iff3_split.py` (tranches),
`_apply_iff3.py` (applique un TSV : témoin + doublons + insertion, CRLF propre),
`_verif_temoins.py` (témoin avec décodage d'entités et d'échappements JS), `_count_dict.py`
(comptage + `_dicttest.js`), `_verif_finale.py` (auto-tests rendus + résidus + diff des canaris),
`_audit_fragments.py` (clés courtes génériques déjà présentes), `_fix_crlf_i18n.py` (fins de ligne),
`_range_entetes.py` (dédoublonnage des en-têtes de lot). Les sondes jetables (`_diag2/3/4/5`,
`_probe.js`) ont été retirées après usage.

> Rappel de la règle de mesure adoptée ici : **la liste de travail est la source**, pas le rendu.
> Corollaire : quand une clé ne s'applique pas, on vérifie **caractère par caractère** (espace
> insécable, tiret, apostrophe) avant d'accuser le moteur.


---

## 10. Lot « positionnement IFF » (07/10/2026) — corriger un fait devenu faux, publier la preuve

Le lot §9 avait traduit les trois modules. Celui-ci ferme la boucle de **positionnement** : ce qui
était écrit **avant** que les modules existent est corrigé, une critique écrite devient une preuve
publiée, et le dossier reçoit une **adresse citable**.

### 10.1 Un fait devenu faux, corrigé dans les deux sources

Le module 3 (§5, « Traduction pour nous ») affirmait : « Il **n'est pas** l'étape 2 : nous ne
cartographions pas les agences ». C'était vrai jusqu'au 07/10/2026. Le module *Groupe technique*
cartographie désormais les agences et leurs sources, ouvre la création d'un TWG et génère le plan de
mesure exportable. La phrase est corrigée **dans la source Markdown**
(`MODULE3_MESURER_ET_CE_QUIL_CHANGE.md`) **et** dans l'onglet « Module 3 » de `cours_iff_idep.html`.
Le §8 de la même page reçoit l'**état d'exécution des six décisions** : les décisions 2, 3 et 5 sont
livrées, chacune avec son lien.

### 10.2 La critique devient une preuve : §11 de la page méthode

`outils_documentation.html` reçoit une **section 11** (ancre `#pcm`, lien ajouté au sommaire) qui
publie la **conformité du moteur aux dix étapes officielles de la PCM+**, verdicts nommés, lacunes
comprises (étapes 4 et 6 à 8 non faites, étape 9 remplacée par un seuil explicite ; test CIF de
6 % : 738 → 735 constats, −0,4 %). C'était la **décision n° 2** du module 3 : « publier le tableau
de conformité — une critique devient une preuve ».

### 10.3 Une adresse citable : `positionnement_iff.html`

Nouvelle page d'**une seule lecture**, faite pour être citée : ce que le dispositif est, les **six
étapes** du processus officiel et ce qu'il y fait, sa conformité à la PCM+, les **deux mesures
négatives** que personne d'autre ne publie (1 082 constats sur 1 543 sans prix de référence ;
43 portails testés, 2 exploitables ; 316 filiales, zéro correspondance de nom) et **ce qu'il ne fait
pas**. Elle est générée par le script de lot, **réutilise le `<style>` de la page méthode tel quel**
(aucune dérive visuelle) et entre au bandeau (`fiscal_graph/navbar.js`). Le journal
(`nouveautes.html`) reçoit sa carte.

### 10.4 Anglais

Les textes introduits reçoivent **98 paires** anglaises dans `fiscal_graph/i18n_en.js` (bloc « lot
positionnement IFF » : **2 754 → 2 849** entrées chargées ; 3 paires recouvrent des clés déjà
connues). Les cellules du §11 et de la page neuve sont écrites en **nœuds de texte uniques** (aucune
balise interne) : la traduction est exacte, jamais fragmentaire. Le `<title>` de la page neuve est
traduit comme le reste.

### 10.5 Mesure (protocole inchangé : dumps `--dump-dom` en octets, pages servies en `?lang=en`)

| Page | Avant | Après | Lecture |
|---|---|---|---|
| `outils_documentation` | 80 segments FR | **79** | le §11 (37 nœuds) n'ajoute **aucun** résidu |
| `cours_iff_idep` | 1 977 | **1 963** | −14 : les clés traduisent aussi des fragments déjà présents |
| `positionnement_iff` (neuve) | — | **0** | page neuve entièrement traduisible dès la publication |
| `decouvertes` (témoin) | 0 | 0 | **1 seul bloc** d'écart : l'entrée de bandeau ajoutée |
| `cas_vigilance` (témoin) | 0 | 0 | **1 seul bloc** d'écart : le même |

Le diff des témoins est **instruit**, pas constaté : `difflib` ne relève qu'**une insertion**,
`positionnement_iff.html">Positioning (citable page)</a>` — l'entrée de bandeau, rendue **en
anglais**. Aucun autre caractère ne bouge.

### 10.6 Vérifications de ce lot

| Contrôle | Résultat |
|---|---|
| `python _i18n/_lot_positionnement.py` (idempotent) | **98** clés, toutes présentes telles quelles dans le texte introduit |
| `node --check` sur `i18n_en.js` et `navbar.js` | **0** / **0** |
| Entrées chargées (`_dicttest.js`) | **2 849** |
| `_i18n/_verif_cours_iff.py` (onglets ↔ panneaux, ancres) | appariement OK |
| `_check_html.py` sur les 4 pages touchées | **0** erreur structurelle, 0 classe absente, **0 ancre cassée** |
| Équilibre `<div>` (contrôle interne du lot) | 38/38 · 33/33 · 14/14 · journal 22 → 23 cartes |
| Fins de ligne | MD/journal/`i18n_en.js`/`navbar.js` en CRLF, `cours_iff_idep.html`/`positionnement_iff.html` en LF, **aucun BOM** |
| SHA-256 local ↔ serveur (7 fichiers déployés) | **identiques** |
| Audit de déploiement (`_i18n/_audit_deploiement.py`) | **43 identiques · 0 écart · 0 absent** |

### 10.7 Outils de ce lot (conservés)

`_i18n/_lot_positionnement.py` (six gestes gardés par des ancres uniques, **plus la génération de
la page citable** : relancer le script régénère la page et le bloc anglais sans jamais doubler une
insertion), `_i18n/_mesure_positionnement.py` (mesure avant/après en lecture seule) et
`_i18n/_maj_rapport_pos.py` (append de cette section). La page `positionnement_iff.html` entre dans
l'audit de déploiement et dans la liste de mesure (`_i18n/audit.ps1`).

> **Le fait de positionnement à retenir.** Le dispositif exécute **quatre des six étapes** du
> processus institutionnel du cours (2, 3, 5 et 6) et la **méthode n° 1** (PCM+), avec ses écarts
> publiés. Ce qui restait une critique écrite est devenu, le même jour, une preuve liée — et une
> adresse citable.
