Files
dogfather-universe/server/pruefe-webdesign-i18n.mjs
T
DogFatherGitandClaude Opus 5 4b3ec450d5 Webdesign-Bereich: Fundament, oeffentliche Seiten, Zugangsschutz, PayPal
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]>
2026-08-22 18:06:52 +02:00

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);