Files
houseplan-card/demo/smoke_french_locale.mjs
T
Codex 89fa8ca588 feat: French localization — community contribution by @OUARZA (#371)
The complete French dictionary contributed in #371 (1026 keys, zero empty
values, zero placeholder mismatches) lands as the second lazy locale on the
fr.ts + one static entry. The contributor's snapshot predated this week's
keys, so the 121 additions (device inbox #29, backdrop guard #39, junction
limits #331, lazy-editor toasts #353/#354, furniture #159) are translated
in this commit and flagged in the issue for the author's review; 21 stale
pre-#62 keys are dropped; key order follows en.json. The HA integration
translations file ships as contributed (full parity).

Wiring: loadFrench + __HOUSEPLAN_FR_RETRY_ASSET__ with the same 1/1
replacement guarantee as German; locale roles and localeRoots generalise to
(de|fr); the stale-entry fallback panel (#353) gains its French branch —
the r1 reviewer caught that this second hardcoded language list would have
silently degraded French to English on a cached entry. French profiles
(fr, fr-FR, fr-CA, fr-BE, fr-CH) select automatically.

Proofs: the registry-driven parity suite covers fr by construction
(1128/1128 keys); French analogues of both German-personal tests (product
glossary + non-translation scan with a reviewed equal-to-English
allow-list of 22 legitimate homographs); smoke_french_locale — fr-CA
profile commits French from the real bundle, one lazy chunk per page,
initial graph free of fr; smoke_entry_stale gains the French run; a
registry mutant drops the fr entry and is killed by the smoke. Initial
view: +0.5 KB (registry entry + fallback branch); the 23 KB dictionary is
lazy.

Issue: #371
User-Visible: yes
2026-08-29 13:39:21 +03:00

40 lines
2.0 KiB
JavaScript

// #371: French is the second lazy locale — community-contributed dictionary
// (@OUARZA). This smoke pins the fr-specific wiring only: automatic selection
// from an fr-CA profile, one lazy chunk per page, and the initial graph free
// of French. The generic failure machinery (retry, fallback, render gate,
// failure toast) is locale-agnostic and stays proven by smoke_german_locale.
import { readFileSync } from 'node:fs';
import { launchColdView, checkAll, finish } from './serve.mjs';
const manifest = JSON.parse(readFileSync('dist/houseplan-assets.json', 'utf8'));
const frPath = manifest.files.map((f) => f.path).find((p) => /(?:^|\/)fr-[^/]+\.js$/.test(p));
if (!frPath) throw new Error('French locale chunk is absent from the bundle manifest');
const frName = frPath.split('/').at(-1);
const { page, browser } = await launchColdView();
const requested = [];
page.on('request', (r) => requested.push(new URL(r.url()).pathname));
const out = await page.evaluate(async (frChunk) => {
const card = window.__card;
const out = {};
const waitFor = async (predicate, timeout = 8000) => {
const started = Date.now();
while (!predicate() && Date.now() - started < timeout) {
await new Promise((resolve) => setTimeout(resolve, 40));
}
return !!predicate();
};
// профиль HA fr-CA → французский, без явного выбора
card.hass = { ...card.hass, locale: { ...card.hass.locale, language: 'fr-CA' } };
card.requestUpdate();
out.frenchCommits = await waitFor(() => card._t('btn.save') === 'Enregistrer');
out.frenchTitleGlossary = card._t('space.header') === 'Espace';
out.langAttrIsFr = await waitFor(() => card.getAttribute('lang') === 'fr');
return out;
}, frName);
out.frChunkRequestedOnce = requested.filter((p) => p.endsWith(`/${frName}`)).length === 1;
out.initialGraphHasNoFrench = !manifest.initialViewFiles.some((p) => /(?:^|\/)fr-[^/]+\.js$/.test(p))
&& manifest.lazyLocaleFiles.some((p) => /(?:^|\/)fr-[^/]+\.js$/.test(p));
checkAll(out);
await finish(browser, out);