7.1 KiB
BILINGUAL-REPORT.md — EN/FR for https://retire.krisforbes.ca
Executed end-to-end per BILINGUAL-PLAN.md (Stages 0–6). Live at
https://retire.krisforbes.ca/ — every user-facing string is bilingual, with a persisted
EN | FR header toggle, locale-correct Intl.NumberFormat numbers, and lang carried in
share links. The engine (site/engine/*.js) is untouched.
What shipped
| Piece | File | Notes |
|---|---|---|
| i18n scaffold | site/i18n.js (new, 558 lines) |
I18N dict (EN + full FR mirror), t(key, vars) with dot-path lookup, fr→en→key fallback and {name} interpolation, getLang/setLang/detectLang/applyI18n/wireToggle, locale formatters fmtMoney / fmtMoneyK / fmtInt / fmtPct / fmtMoney2. |
| Header toggle | site/index.html, site/style.css |
`EN |
| App copy | site/app.js |
1:1 faithful port: all dynamic strings (14/17 steps, panel + chips + insight, chart titles/legends/annotations, verdict, gap cards, 11-col table, what-ifs, vision captions, validation errors, share/print/restart, disclaimer, privacy) via t(); all geometry, ids, engine field names and control flow byte-identical to baseline. |
| Language state | site/app.js + site/i18n.js |
LS retire-planner-v1 = {a, step, screen, lang} (lang merged read-modify-write); share payload #p={a,step,lang}; load precedence payload lang → saved lang → navigator.language fr* → en; legacy payloads (no lang) open EN. Toggle re-renders the current screen live (__onLangChange). |
| FR typography | site/i18n.js frNorm() |
U+2019 apostrophes, NBSP before : ; ! ? %, NBSP inside « ». |
| Numbers | formatters in site/i18n.js |
EN $959,835 / $523K / $1.05M / 3.5%; FR 959 835 $ / 523 k$ / 1,05 M$ / 3,5 % (fr-CA Intl compact emits 523 k $, so FR compact is hand-rolled to the plan's glossary form). All chart axis/legend/annotation text and table cells go through the formatters. |
Glossary followed: REER, FERR, CELI, RPC, SV, SRG, ménage, conjoint, fractionnement du revenu de pension, récupération de la SV, RevenuQuébec.ca, glissez →, barèmes 2026, k$/M$ compacts. FR chart bar labels kept concise (e.g. g4 scenario names shortened) so they fit the viewBox.
Tests (all from workspace root, all green pre-deploy)
node tests/engine.test.js→ RESULT: 36 pass, 0 fail (engine untouched).node tests/i18n.test.js→ I18N-OK (48 checks): deep key-set parity en↔fr, no empty values,{placeholder}set equality for every key, everyt()key used in app.js exists in both dicts, formatter exact outputs ($959,835/959 835 $,$523K/523 k$,$1.05M/1,05 M$,5%/5 %,3.5%/3,5 %,−$32,962/−32 962 $,$1,507.65/1 507,65 $), FR typography (U+2019, NBSP),applyI18nover the realindex.html(FR title Planificateur de retraite…, h1, meta description,documentElement.lang,aria-pressed), lang persistence merge, load precedence (payload > saved > detect; legacy payload → en).node tests/ui.smoke.js→ SMOKE-OK (56 checks): full begin → province → 14 steps → results → what-if walk run twice, EN and FR, asserting locale strings (step titles, validation message, panel, verdict, chart titles, legend, table header, year summary, stamptax rates 2026 · CPP max $1,507.65vsbarèmes 2026 · RPC max. 1 507,65 $), plusLS.lang === 'fr'after the FR run.
EN regression (baseline vs deployed, live site)
audit/bilingual-en-regression/ (fresh EN runs) vs audit/bilingual-en-baseline/:
- Geometry identical: panel 374×218 top 618, chips 344×158, step body, btn-next,
g1 svg 560×236, table
.tbl-wrapclientW 326 (scrollW 518, horizontal-scroll pattern unchanged), vision 356×250 — at 390/360/320. - Pixel diff (thresholded, per screenshot): 0.9–4.1 % of pixels — the differences are
the header EN|FR toggle and the reformatted numerals (
$523K,2 %-style compact/trim allowed by the plan's formatter spec). Header height 55→57 CSS px (toggle button); full-page doc height +6 px. No element moved; content layout below the header identical. - Desktop (≥861px): toggle is the only header addition; grid/content layout untouched
(
desktop-results-en-topdiff evidence inaudit/bilingual-fr/).
FR mobile audit — 320/360/390 + desktop 1280 (audit/bilingual-fr/, 23 PNGs + geo.json)
98/98 programmatic checks pass (audit/bilingual_audit.py → FR-AUDIT-OK):
- No horizontal document overflow at any viewport:
max(documentElement, body).scrollWidth == innerWidth(320/360/390/1280, intro, province grid, income input, slider, sheet-open, results top/charts/table). - Chart text fits the viewBox: every
<text>in every#g1–#g4SVG measured withgetBBox(); 0 violations on every device+screen (FR labels kept concise, e.g. g4 scenarios,L'évolution de votre épargne). - Sheet/chips: panel visible on every quiz step, chips font 11px, panel height 235px (33.5 vh at 320) and 291px (41.5 vh) open — live
--sheet-hsync intact. - CTA: quiz Suivant → in viewport on every step (sticky foot), intro CTA rendered, share CTA on results (full-page shots).
- Table: 11 headers in DOM, 9 visible at ≤560px (non-reg + GIS columns hidden by the pre-existing "Fix 5" mobile design), 13px header font,
.tbl-wrapscrolls horizontally (clientW 256/296/326 < scrollW 549) — nothing clipped. - FR rendering proof: RPC/REER/CELI/retraite token counts per screen (5 intro, 2 quiz steps, 8–10 results); desktop EN control shot shows 0 FR tokens.
Deploy + off-LAN verification
bash scripts/deploy.sh→ DEPLOY-OK; servedindex.htmlmarkerretire-planner v1grep = 1; servedi18n.jscontains FR strings;index.htmlreferencesi18n.js.- Off-LAN (
audit/offlan_check.pyvia Midhurst troublebox, result inaudit/bilingual-geo-check.json): 200 + marker +lang-toggle+i18n.jsscript + FR strings (OFFLAN-OK, vantage trouble@100.96.20.61).
FR notes / accepted deltas
- Header brand uses ellipsis at 320px (Planificateur de retraite… with the EN Retire Planner kept short) so the toggle + restart stay on one row.
- Legacy share links (payloads without
lang) open in EN — deterministic, never garbled. - FR verdict/stamp strings use non-breaking narrow spaces per fr-CA Intl (
1 507,65 $); tests normalize U+202F/U+00A0 for assertions. - EN micro-deltas mandated by the formatter spec: compact
$523K(was$523k), trailing-zero trim onpct2(2 %-class output),to {le}in the verdict line (identical at the default life expectancy).
Files changed
site/i18n.js (new) · site/app.js · site/index.html · site/style.css ·
tests/i18n.test.js (new) · tests/ui.smoke.js · audit/bilingual_audit.py (new) ·
audit/offlan_check.py (new) · audit/bilingual-en-baseline/ · audit/bilingual-en-regression/ ·
audit/bilingual-fr/ · audit/bilingual-geo-check.json · BILINGUAL-PLAN.md · .gitignore.
git diff --stat site/engine/ → empty: engine untouched, 36/36.