Umsetzung des "Website Masterplan" (16 Seiten) unter /webdesign. Zugangsschutz mit EIGENER Schranke (server/webdesign-gate.js) statt gate.js: gate.js laesst seit dem oeffentlichen Start am 21.08.2026 jeden durch, weil die Pruefung auf SITE_PUBLIC_LAUNCH_AT als allererste Zeile steht. Haette man /webdesign dahintergehaengt, waere der ausdruecklich nicht-oeffentliche Bereich inklusive Preisen und spaeteren Kundendaten ab der ersten Sekunde fuer jeden lesbar gewesen. Eigenes Sitzungs-Cookie, bereich="webdesign" im Token, damit ein gueltiges Universe-Cookie hier NICHT gilt. 37/37 Tests. Sieben oeffentliche Seiten in fuenf Sprachen (de, de-CH mit echtem Dialekt, en, fr, pt). Preise, Zeitrahmen, Paketnamen und die 30-%-Regel stehen an genau EINER Stelle in wd-core.js -- der Masterplan verlangt "ueberall widerspruchsfrei", und vier Kopien laufen bei der ersten Preisaenderung auseinander. Als App installierbar auf Handy und PC. Der Service Worker speichert bewusst KEINE HTML-Seite zwischen: nach dem Abmelden wuerden sonst geschuetzte Seiten weiter ausgeliefert, ohne dass der Server je gefragt wird. 18/18 Tests. Handy-Abnahme ueber alle Seiten in fuenf Breiten (320-1440) und fuenf Sprachen: 40/40. Der Test fand 35 echte Fehler (Touch-Ziele unter 44px), behoben im Designsystem statt einzeln pro Seite. PayPal (Wunsch 22.08.2026 "sofort auf meinem paypal"): Orders API mit intent=CAPTURE, also sofortiger Einzug statt blosser Reservierung. Gebuehr und Nettobetrag getrennt gespeichert. Betraege durchgehend als Ganzzahl in Cent. Gefaelschte Webhooks werden abgewiesen. Fail closed solange Zugangsdaten fehlen. 28/28 Tests gegen einen nachgebauten PayPal-Server. Datenbank: 15 Tabellen mit Praefix wd_, fachlich vollstaendig vom Universe getrennt. Co-Authored-By: Claude Opus 5 <[email protected]>
105 lines
4.3 KiB
JavaScript
105 lines
4.3 KiB
JavaScript
/* 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(`</${tag}>`, "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);
|