Files
houseplan-card/demo/smoke_entry_stale.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

57 lines
2.6 KiB
JavaScript

// #353 AC3b: a proxy-cached entry `houseplan-card.js` that survived an update
// points at a main chunk the manifest-gated server no longer serves. Before
// the fix the static re-export aborted the whole module and the card died
// silently; now the entry defines a fallback element with a human message,
// and `await import(entry)` + `createElement(...).setConfig(...)` stays valid.
import { readFileSync } from 'node:fs';
import { chromium } from 'playwright';
import { check, checkAll } from './serve.mjs';
const entry = readFileSync('dist/houseplan-card.js');
const page404 = `<!doctype html><meta charset="utf-8"><body><script type="module">
await import('/assets/houseplan-card.js');
const card = document.createElement('houseplan-card');
card.setConfig({ type: 'custom:houseplan-card' });
document.body.appendChild(card);
window.__done = true;
<\/script></body>`;
const run = async (locale, expected) => {
const browser = await chromium.launch({ args: ['--no-sandbox'] });
const page = await (await browser.newContext({ locale })).newPage();
let pageErrors = 0;
page.on('pageerror', (error) => { pageErrors++; console.log('EXC', error.message); });
await page.route('**/*', (route) => {
const path = new URL(route.request().url()).pathname;
if (path === '/stale.html') {
return route.fulfill({ status: 200, contentType: 'text/html', body: page404 });
}
if (path === '/assets/houseplan-card.js') {
return route.fulfill({ status: 200, contentType: 'text/javascript', body: entry });
}
// Every hashed chunk of the cached build is gone after the update.
return route.fulfill({ status: 404, body: 'nf' });
});
await page.goto('http://demo.local/stale.html', { waitUntil: 'domcontentloaded' });
await page.waitForFunction(() => window.__done === true, { timeout: 9000 });
const text = await page.locator('houseplan-card').textContent();
await browser.close();
return { text, pageErrors };
};
const en = await run('en-US');
const ru = await run('ru-RU');
const fr = await run('fr-FR');
const out = {
entrySurvivesMissingChunk: en.pageErrors === 0 && ru.pageErrors === 0 && fr.pageErrors === 0,
englishMessageVisible: en.text.includes('House Plan was updated')
&& en.text.includes('reload the page'),
russianMessageVisible: ru.text.includes('House Plan обновился')
&& ru.text.includes('перезагрузите страницу'),
frenchMessageVisible: fr.text.includes('House Plan a été mis à jour')
&& fr.text.includes('recharger la page'),
};
checkAll(out);
check('smoke_entry_stale', Object.values(out).every(Boolean));
if (Object.values(out).every(Boolean)) console.log('OK');