/* Prüft für jede Webdesign-Seite: 1. jeder data-i18n-Schlüssel existiert im Wörterbuch 2. jeder Schlüssel hat ALLE fünf Sprachen 3. keine Überschrift h1-h4 ohne data-i18n (der dokumentierte Bug-Klassiker aus DogiHompage/CLAUDE.md) 4. keine unbenutzten Schlüssel (Hinweis, kein Fehler) Aufruf: node tmp-i18n-check.mjs */ import { readFileSync, existsSync, readdirSync } from "node:fs"; import { chdir } from "node:process"; import { dirname, join } from "node:path"; import { fileURLToPath } from "node:url"; /* Immer vom Projektordner aus arbeiten, egal von wo aufgerufen. */ chdir(join(dirname(fileURLToPath(import.meta.url)), "..")); const SPRACHEN = ["de", "de-CH", "en", "fr", "pt"]; const KERN = "assets/js/wd-core.js"; /* Schlüssel, die wd-core.js selbst mitbringt (Navigation, Fußzeile, Fehlermeldungen) — die dürfen in jeder Seite benutzt werden. */ const kernQuelle = readFileSync(KERN, "utf8"); const kernSchluessel = new Set( [...kernQuelle.matchAll(/^\s{4}([a-z0-9_]+):\s*\{/gm)].map((m) => m[1]) ); let fehler = 0; const seiten = existsSync("webdesign") ? readdirSync("webdesign").filter((f) => f.endsWith(".html")) : []; for (const datei of seiten) { const name = datei.replace(/\.html$/, ""); const html = readFileSync(`webdesign/${datei}`, "utf8"); const i18nDatei = `assets/js/i18n-wd-${name}.js`; console.log(`\n--- ${datei} ---`); if (!existsSync(i18nDatei)) { console.log(` FEHLER: ${i18nDatei} fehlt`); fehler++; continue; } // Wörterbuch laden (setzt window.I18N_WD) global.window = {}; // eslint-disable-next-line no-eval eval(readFileSync(i18nDatei, "utf8")); const woerterbuch = global.window.I18N_WD || {}; // --- 1+2: benutzte Schlüssel prüfen --- const benutzt = new Set(); for (const m of html.matchAll(/data-i18n="([^"]+)"/g)) benutzt.add(m[1]); for (const m of html.matchAll(/data-i18n-attr="([^"]+)"/g)) { m[1].split(";").forEach((teil) => { const pos = teil.indexOf(":"); if (pos > 0) benutzt.add(teil.slice(pos + 1).trim()); }); } const titelKey = (html.match(/data-i18n-titel="([^"]+)"/) || [])[1]; if (titelKey) benutzt.add(titelKey); // Schlüssel, die nur im Seitenskript per WD.t(...) geholt werden for (const m of html.matchAll(/WD\.t\("([a-z0-9_]+)"\)/g)) benutzt.add(m[1]); const fehlend = []; const unvollstaendig = []; for (const k of benutzt) { const eintrag = woerterbuch[k]; if (!eintrag) { if (!kernSchluessel.has(k)) fehlend.push(k); continue; } for (const s of SPRACHEN) if (!eintrag[s]) unvollstaendig.push(`${k}:${s}`); } // --- 3: Überschriften ohne data-i18n --- const ohneI18n = []; for (const m of html.matchAll(/<(h[1-4])\b([^>]*)>([\s\S]*?)<\/\1>/g)) { if (m[2].includes("data-i18n")) continue; const text = m[3].replace(/<[^>]*>/g, "").trim(); if (!text) continue; // rein dekorativ if (/^\$\{/.test(text)) continue; // dynamisch aus JS ohneI18n.push(`${m[1]}: ${text.slice(0, 60)}`); } // --- 4: unbenutzte Schlüssel (nur Hinweis) --- const unbenutzt = Object.keys(woerterbuch).filter((k) => !benutzt.has(k)); console.log(` Schluessel im HTML: ${benutzt.size}, im Woerterbuch: ${Object.keys(woerterbuch).length}`); if (fehlend.length) { console.log(` FEHLENDE Schluessel: ${fehlend.join(", ")}`); fehler += fehlend.length; } if (unvollstaendig.length) { console.log(` FEHLENDE Sprachen: ${unvollstaendig.join(", ")}`); fehler += unvollstaendig.length; } if (ohneI18n.length) { console.log(` Ueberschrift ohne data-i18n:\n ${ohneI18n.join("\n ")}`); fehler += ohneI18n.length; } if (unbenutzt.length) console.log(` Hinweis, unbenutzt: ${unbenutzt.join(", ")}`); if (!fehlend.length && !unvollstaendig.length && !ohneI18n.length) console.log(" alles vollstaendig"); // --- Tag-Balance grob prüfen --- for (const tag of ["div", "section", "main", "article", "details"]) { const auf = (html.match(new RegExp(`<${tag}\\b`, "g")) || []).length; const zu = (html.match(new RegExp(``, "g")) || []).length; if (auf !== zu) { console.log(` TAG-UNGLEICHGEWICHT <${tag}>: ${auf} offen, ${zu} geschlossen`); fehler++; } } } console.log(`\n=== ${fehler === 0 ? "Keine Fehler" : fehler + " Fehler"} ===`); process.exit(fehler === 0 ? 0 : 1);