Skip to content
Merged
Changes from 1 commit
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
@@ -0,0 +1,95 @@
# Virtues mindmap `.content.svg` FR-frozen — mechanism traced, root cause & fix path

**Author**: po-2024 (worker) · **Date**: 2026-06-25 · **Base**: master `bef3bc6c` (release-frozen)
**Scope**: read-only code investigation. **0 write under `Cards/`** (release freeze). master stays `bef3bc6c`.
**Related**: release dossier #591 §3.2 (gap documented as "deferred, non-blocking v0.9.0"); cross-verify #599 (orthogonality); memory `virtues-mindmap-content-svg-fr-frozen`.

---

## TL;DR

1. **The gap is real and verified**: Virtues `.content.svg` are FR-frozen (po-2023 observed `Argument valable` / `Échange enrichissant` identical fr=zh=ar), while Fallacies `.content.svg` are localized across 8 languages. **Non-blocking v0.9.0** — deferred to jsboige's GO (post-release).
2. **The localization mechanism is now TRACED** (was "untraced" as of the #591 dossier). It is **upstream of the render engine** — neither `TitleFunc` nor `ProcessSvgFilesAsync` applies the language. It is **expression rewriting** via reflection.
3. **Root cause is 2-layer** and fully bounded:
- **Layer B (config)** — the Virtues localization table is a stale stub (translates only the tree-root literal `"Vertus"`, 4 langs).
- **Layer A (entity)** — `Virtue.cs` has no `TitleAr/TitleFa/TitleZh` properties (the ar/fa/zh CSV columns added by #590/#595 are invisible to the mindmap pipeline).
4. **The fix is bounded** (3 steps, mirrors the Fallacies path) but **gated on jsboige** (post-release). One non-obvious prerequisite: the Virtues `DefaultTitleExpression` must change from `{item.Text}` to `{item.TitleFr}` before a config-only fix can fire — otherwise `Replace("TitleFr", ...)` is a silent no-op.

---

## The mechanism — how Fallacies mindmaps get localized (traced)

Localization is **expression rewriting in-place**, applied **before** the `.mm`/SVG render:

1. `ParallelDocumentCreatorConfigBase.ProcessDocumentAsync` ([ParallelDocumentCreatorConfigBase.cs:58-62](../../Generation/Converters/Argumentum.AssetConverter/ParallelDocumentCreatorConfigBase.cs#L58-L62)) — for each target language, **clones** the document config (`currentTranslatedMap = mindMap.Clone()`), then applies **every** entry of `LocalizationConfig.MindMapLocalization` via `DocumentLocalization.DoReflectionTranslate(clone, lang)`.

2. `DocumentLocalization.DoReflectionTranslate` ([DocumentLocalization.cs:37-54](../../Generation/Converters/Argumentum.AssetConverter/WebBasedGenerator/Localization/DocumentLocalization.cs#L37-L54)) — reflects over the config's `string` properties whose name is in the entry's `TargetProperties`. For each: reads the current value, runs `DoStaticConversions(value, lang)`, writes it back.

3. `DoStaticConversions` ([DocumentLocalization.cs:16-31](../../Generation/Converters/Argumentum.AssetConverter/WebBasedGenerator/Localization/DocumentLocalization.cs#L16-L31)) — **naive `template.Replace(sourceText, destText)`** per conversion, filtered by language. No token-boundary awareness (same fragility class as #477 card-set mapping, #488 CSV contract).

**Net effect**: the config's *expression strings* are rewritten per language. E.g. Fallacies `TitleExpression = "{fallacy.TextFr}"` becomes `"{fallacy.TextZh}"` for `zh`. The interpolation at render time (`TitleFunc`) then produces localized text. The `.content.svg` is rebuilt from the in-memory Freemind model (`ProcessSvgFilesAsync`), which carries the already-localized expression.

The 4 production `MindMapLocalization` entries ([AssetConverterConfig.cs:207-261](../../Generation/Converters/Argumentum.AssetConverter/AssetConverterConfig.cs#L207-L261)):

| # | Targets | Does | Localizes Fallacies? | Localizes Virtues? |
|---|---|---|---|---|
| 1 | `FallacyMindMapDocumentConfig.{Title,Card,Description,Example,Link}Expression` | `TextFr→TextZh` (+6 langs), `DescFr→…`, `ExampleFr→…`, `LinkFrFallback→…` | ✅ **yes (text)** | n/a |
| 2 | `FallacyMindMapDocumentConfig.{Famille,SousFamille,SoussousFamille}Expression` | `Soussousfamille→SubsubfamilyZh`, etc. (most-specific-first ordering) | ✅ **yes (hierarchy)** | n/a |
| 3 | `VirtueMindMapDocumentConfig.TitleExpression` | `"Vertus"→"Virtues"/"Dobrodeteli"/"Virtudes"` (4 langs, **tree root only**) | n/a | ❌ **no (stub)** |
| 4 | `DocumentConfig.DocumentName` | `_fr.→_zh.` | filename only | filename only |

---

## Root cause — why Virtues stay FR (2 layers, both verified on `bef3bc6c`)

### Layer B — the Virtues conversion table is a stale stub (entry #3)

`AssetConverterConfig.cs:242-250` entry #3 targets `VirtueMindMapDocumentConfig.TitleExpression`, but its only `StaticConversion` is `("Vertus", [en→Virtues, ru→Dobrodeteli, pt→Virtudes, es→Virtudes])` — the **tree-root literal**, 4 languages only. The comment at l.241 reads *"data is FR-only, only tree root name changes"*.

That design intent is **stale**: the Virtues CSV is now translated ×7 langs (via #590 Phase 1 + #595/#583 harmonization). But the config was never updated to consume it.

Consequence: `VirtueMindMapDocumentConfig.DefaultTitleExpression = @"{item.Text}"` ([VirtueMindMapDocumentConfig.cs:37](../../Generation/Converters/Argumentum.AssetConverter/Mindmapper/VirtueMindMapDocumentConfig.cs#L37)). `"{item.Text}".Replace("Vertus", "Virtues")` is a **no-op** (the substring `"Vertus"` is not present) → the expression stays `{item.Text}` = `Virtue.Text` = `TitleFr` = **FR for all 8 languages**.

### Layer A — `Virtue` has no ar/fa/zh title properties (data exists, entity doesn't map it)

[Virtue.cs:25-68](../../Generation/Converters/Argumentum.AssetConverter/Entities/Virtue.cs#L25-L68) maps only `TitleFr/En/Ru/Pt/Es` (gap between l.68 and l.73 — no `TitleAr/TitleFa/TitleZh`). Compare `Fallacy.cs:134/146/158` which has `TextAr/TextFa/TextZh` (+ ClassMap at `:250/257/264`).

So even if Layer B were fixed, the ar/fa/zh CSV columns are **invisible to the mindmap pipeline** — there is no entity property to bind `{item.TitleZh}` to.

**Verified (2026-06-25)**: the **CSV data exists**. `Cards/Fallacies/Argumentum Virtues - Taxonomy.csv` carries `title_ar`/`title_zh`/`title_fa` (cols 39/51/57), plus `subfamily_{ar,zh,fa}`, `subsubfamily_{ar,zh,fa}`, `description_*`, `remark_*`, `family_*`, `link_*` for all three. So the gap is **purely the entity mapping**, not a missing-data problem — no upstream data task.

---

## Fix path (post-release, jsboige-gated — 3 bounded steps, mirrors Fallacies)

> ⚠️ **Gated**: touch the mindmap post-processing = judgment call (per ai-01 17:16 / po-2023 16:20, No-Pendulum). Do **not** code before jsboige's GO. The localization *content* exists in CSV; this is about making the mindmap pipeline consume it.

1. **Entity** ([Virtue.cs](../../Generation/Converters/Argumentum.AssetConverter/Entities/Virtue.cs)): add `TitleAr/TitleFa/TitleZh` (+ `SubfamilyAr/Fa/Zh`, `SubsubfamilyAr/Fa/Zh` for the hierarchy nodes) + `ClassMap` entries `.Optional()` mirroring `Fallacy.cs:250/257/264`. ✅ **CSV data verified present** (Layer A note above: `title_ar/zh/fa` + `subfamily_*`/`subsubfamily_*` exist in `Argumentum Virtues - Taxonomy.csv`) — no upstream data task, pure entity-mapping work.

2. **Expression** ([VirtueMindMapDocumentConfig.cs:37](../../Generation/Converters/Argumentum.AssetConverter/Mindmapper/VirtueMindMapDocumentConfig.cs#L37)): change `DefaultTitleExpression` from `{item.Text}` → **`{item.TitleFr}`** (parallel to Fallacies' `{fallacy.TextFr}`). **This is the non-obvious prerequisite**: `DoStaticConversions` is plain `Replace`, so `Replace("TitleFr","TitleZh")` only fires if the literal `TitleFr` is *in* the expression. `{item.Text}` contains no such literal → a config-only fix (step 3 alone) would be a **silent no-op**, repeating the SUPPOSÉ-error class. Changing the expression first is what unblocks the conversion.

3. **Config** ([AssetConverterConfig.cs:242-250](../../Generation/Converters/Argumentum.AssetConverter/AssetConverterConfig.cs#L242-L250)): replace the root-only entry #3 with an entry mirroring Fallacy text (#1) — target `VirtueMindMapDocumentConfig.TitleExpression` with `StaticConversions ("TitleFr",[en→TitleEn, ru→TitleRu, pt→TitlePt, es→TitleEs, ar→TitleAr, fa→TitleFa, zh→TitleZh])` — plus a hierarchy entry mirroring #2 for the family/subfamily/subsubfamily nodes.

### Validation

- **Regen 8-lang with clobbered harvests** (cf `regen-success-without-clobber-is-stale-trap`: exit 0 + identical count ≠ fresh content — must clobber `ImageHelper.cs:100` skips).
- Verify `Argumentation_Virtues_zh.content.svg` contains CJK glyphs, not `Échange enrichissant`; `…_ar.content.svg` RTL Arabic, not `Argument valable`.
- **Add a Virtues mirror of `MindMapLocalizationRegressionTests.cs`** — the existing regression suite covers Fallacies only (its own docstring, l.40-43, lists the 4 prod entries but the test asserts Fallacy expressions). Without a Virtues test, a future regression here is silent.

---

## Orthogonality note (vs #599 / #568)

This gap is **upstream of the render engine**. Whether the `.mm` is rendered by **Batik (FreeMind)** or **Freeplane (#599 opt-in)**, the node `TEXT` carries the same `{item.Text}` = FR for Virtues — the engine renders what it is given. The Freeplane breakthrough-refutation (#568 §3.8) and this finding are independent. (Cross-verify ACK #599 `4802039001`, po-2023.)

---

## Reproducibility

Read-only on master `bef3bc6c`. No code run. All line references verified against current source. Re-verify with:
- `grep -n "DefaultTitleExpression" Generation/Converters/Argumentum.AssetConverter/Mindmapper/VirtueMindMapDocumentConfig.cs`
- `grep -n "TitleAr\|TitleFa\|TitleZh" Generation/Converters/Argumentum.AssetConverter/Entities/Virtue.cs` → expect **no matches** (the gap)

---

*This doc converts the #591 §3.2 "deferred, mechanism untraced" finding into a "traced, bounded fix path" — so jsboige's post-release decision is informed, not open-ended.*
Loading