Files
dogfather-universe/pruef-rechtliches.mjs
DogFatherGitandClaude Opus 5 2e2172202e Rechtstexte in fuenf Sprachen
Impressum, AGB, Widerrufsbelehrung und Datenschutzerklaerung gibt es
jetzt auf Deutsch, Schweizer Hochdeutsch, Englisch, Franzoesisch und
Portugiesisch. 95 Textbloecke, rund 13.000 Zeichen je Sprache.

DIE MASSGEBLICHKEITSKLAUSEL STEHT IN JEDER SPRACHE

Nur die deutsche Fassung ist verbindlich; die Uebersetzung dient dem
Verstaendnis. Ohne diesen Hinweis waere die Uebersetzung ein Risiko --
ein Kunde koennte sich auf die franzoesische Fassung berufen. Der
Hinweis stand schon auf der Seite, aber selbst nur auf Deutsch: Genau
die Leute, fuer die er gedacht ist, konnten ihn nicht lesen.

WIDERRUFSBELEHRUNG UND MUSTERFORMULAR SIND AMTLICH

Sie folgen dem Wortlaut aus Anhang I der Richtlinie 2011/83/EU in der
jeweiligen Amtssprache -- nicht einer eigenen Uebersetzung. Der
Gesetzgeber hat diese Saetze selbst formuliert; wer sie neu uebersetzt,
verliert den Schutz der Musterbelehrung. Angepasst ist nur die Person:
Die Muster sprechen von "wir/uns", hier schreibt ein Einzelunternehmer
"ich/mir" -- diese Anpassung sieht das Muster ausdruecklich vor.

SCHWEIZERDEUTSCH GIBT ES HIER BEWUSST NICHT

Auf allen anderen Seiten spricht "de-CH" echten Dialekt. In
Rechtstexten waere das unserioes -- es gibt keine Widerrufsbelehrung auf
Mundart. Die Schweiz schreibt Hochdeutsch, nur ohne Eszett. Genau das
erzeugt eine Zeile Code aus dem deutschen Text, statt 95 von Hand
gepflegter Doppeleintraege, die beim naechsten Aendern auseinanderlaufen
wuerden.

DER FEHLER, DER FAST LIVE GEGANGEN WAERE

Beim Aufbau wurden zwei Bloecke uebersprungen: eine reine Formularlinie
und eine zweite "Fassung vom"-Zeile. Ab dort war jede Schluesselnummer
um eins verschoben.

Im Musterformular haette dadurch ueber jedem Feld die falsche
Beschriftung gestanden -- "Datum" wo "Name" hingehoert. Und in fuenf
Sprachen waere es niemandem aufgefallen, denn jede Sprache war ja
vollstaendig gefuellt: Eine Vollstaendigkeitspruefung ist gegen diese
Art Fehler wehrlos.

Die Schluessel sind deshalb jetzt ueber den TEXT zugeordnet, nicht ueber
eine laufende Nummer. Ein eigener Durchlauf vergleicht jeden deutschen
Text im Woerterbuch mit dem an derselben Stelle im HTML.

WAS DER NEUE DURCHLAUF SONST PRUEFT

- Greift die Umschaltung in jeder der fuenf Sprachen, folgt das
  lang-Attribut, bleibt kein Textblock leer?
- Gegenprobe, dass die Umschaltung ueberhaupt etwas tut: Englisch,
  Franzoesisch und Portugiesisch MUESSEN sich vom Deutschen
  unterscheiden. Ohne diese Probe waere ein Woerterbuch, das ueberall
  denselben deutschen Text zurueckgibt, gruen durchgelaufen.
- Kein Eszett in der Schweizer Fassung, sonst wortgleich.
- Die Massgeblichkeitsklausel nennt in jeder Sprache die deutsche
  Fassung.

Geprueft: 1303 Pruefungen gruen (Browser 737, Server 566), i18n ohne
Fehler, WCAG ohne Fundstellen.

Co-Authored-By: Claude Opus 5 <[email protected]>
2026-08-25 22:28:52 +02:00

160 lines
6.5 KiB
JavaScript

/* =====================================================================
Prueft die Rechtstexte in fuenf Sprachen.
Der wichtigste Test hier ist NICHT "ist alles uebersetzt", sondern:
Steht unter jedem Schluessel auch der Text, der im HTML an dieser
Stelle steht?
Beim Bauen waren die Schluessel ab einer Stelle um eins verschoben,
weil zwei Bloecke uebersprungen worden waren. Im Musterformular haette
dadurch ueber jedem Feld die falsche Beschriftung gestanden -- "Datum"
wo "Name" hingehoert. In fuenf Sprachen waere das niemandem
aufgefallen, denn jede Sprache war ja vollstaendig gefuellt.
Gegen diese Art Fehler ist eine Vollstaendigkeitspruefung wehrlos.
Deshalb wird hier der deutsche Text im Woerterbuch mit dem im HTML
verglichen -- Zeichen fuer Zeichen.
===================================================================== */
import { chromium } from "playwright";
import http from "http";
import fs from "fs";
import path from "path";
const TYP = {
".html": "text/html", ".css": "text/css", ".js": "text/javascript",
".png": "image/png", ".svg": "image/svg+xml", ".webp": "image/webp",
".json": "application/json", ".ico": "image/x-icon", ".jpg": "image/jpeg",
};
const srv = http.createServer((q, r) => {
const p = path.join(process.cwd(), decodeURIComponent(q.url.split("?")[0]));
if (fs.existsSync(p) && !p.endsWith("/")) {
r.writeHead(200, { "content-type": TYP[path.extname(p)] || "text/plain" });
return r.end(fs.readFileSync(p));
}
r.writeHead(404);
r.end("");
});
let ok = 0, fehl = 0;
const pruefe = (name, gut, info) => {
if (gut) { ok++; console.log(" ok " + name + (info ? " -> " + info : "")); }
else { fehl++; console.log(" FEHL " + name + (info ? " -> " + info : "")); }
};
const SPRACHEN = ["de", "de-CH", "en", "fr", "pt"];
srv.listen(4406, async () => {
const browser = await chromium.launch();
const seite = await browser.newPage({ viewport: { width: 1440, height: 900 } });
const skriptfehler = [];
seite.on("pageerror", (e) => skriptfehler.push(String(e).slice(0, 140)));
await seite.goto("http://127.0.0.1:4406/webdesign/rechtliches.html");
await seite.waitForTimeout(1600);
console.log("1. LIEGT UNTER JEDEM SCHLUESSEL DER RICHTIGE TEXT?");
const versatz = await seite.evaluate(() => {
const W = window.I18N_WD || {};
/* HTML-Entitaeten aufloesen, bevor verglichen wird.
Im Woerterbuch steht "&amp;", im Browser liest textContent daraus
ein "&". Ohne dieses Aufloesen meldet der Test eine Abweichung,
wo in Wahrheit derselbe Text steht -- genau bei der Ueberschrift
"Impressum, AGB & Datenschutz". */
const entschluesseln = (t) => {
const hilf = document.createElement("textarea");
hilf.innerHTML = t;
return hilf.value;
};
const sauber = (t) => entschluesseln(t).replace(/\s+/g, " ").trim();
const raus = [];
for (const el of document.querySelectorAll("[data-i18n^='rl_']")) {
const k = el.getAttribute("data-i18n");
if (!W[k]) { raus.push({ k, grund: "kein Eintrag", html: sauber(el.textContent).slice(0, 50) }); continue; }
const imBuch = sauber(W[k].de.replace(/<[^>]*>/g, " "));
const imHtml = sauber(el.textContent);
if (imBuch.slice(0, 45) !== imHtml.slice(0, 45)) {
raus.push({ k, grund: "Text weicht ab", html: imHtml.slice(0, 45), buch: imBuch.slice(0, 45) });
}
}
return raus;
});
pruefe("jeder Schluessel traegt seinen eigenen Text",
versatz.length === 0,
versatz.length ? versatz.slice(0, 3).map((v) => v.k + " (" + v.grund + ")").join(", ")
: "alle 95 stimmen");
console.log("\n2. GREIFT DIE UEBERSETZUNG IN JEDER SPRACHE?");
const proben = { rl_001: "Titel", rl_003: "Sprachhinweis", rl_047: "Widerrufsrecht" };
const gesehen = {};
for (const spr of SPRACHEN) {
const r = await seite.evaluate((p) => {
window.WD.setSprache(p);
const hole = (k) => document.querySelector(`[data-i18n="${k}"]`)?.textContent.trim() || "";
return {
lang: document.documentElement.lang,
rl_001: hole("rl_001"),
rl_003: hole("rl_003"),
rl_047: hole("rl_047"),
leer: [...document.querySelectorAll("[data-i18n^='rl_']")]
.filter((el) => !el.textContent.trim()).length,
};
}, spr);
gesehen[spr] = r;
pruefe(` ${spr}: lang-Attribut folgt`, r.lang === spr, r.lang);
pruefe(` ${spr}: kein Textblock ist leer`, r.leer === 0, r.leer + " leer");
}
/* Gegenprobe: Der Test darf nicht gruen sein, weil die Umschaltung
gar nichts tut. Die drei Fremdsprachen MUESSEN sich vom Deutschen
unterscheiden. */
for (const spr of ["en", "fr", "pt"]) {
pruefe(` ${spr} unterscheidet sich wirklich vom Deutschen`,
gesehen[spr].rl_003 !== gesehen.de.rl_003,
gesehen[spr].rl_003.slice(0, 38));
}
/* de-CH ist bei Rechtstexten ABSICHTLICH kein Dialekt, sondern
Hochdeutsch in Schweizer Rechtschreibung -- also identisch bis auf
das Eszett. Eine Widerrufsbelehrung auf Mundart waere unserioes. */
console.log("\n3. SCHWEIZ: HOCHDEUTSCH OHNE ESZETT, KEIN DIALEKT");
const ch = await seite.evaluate(() => {
window.WD.setSprache("de-CH");
const W = window.I18N_WD;
let mitEszett = 0, abweichend = 0;
for (const v of Object.values(W)) {
if (/ß/.test(v["de-CH"])) mitEszett++;
if (v["de-CH"] !== v.de.replace(/ß/g, "ss")) abweichend++;
}
return { mitEszett, abweichend };
});
pruefe("kein Eszett in der Schweizer Fassung", ch.mitEszett === 0, ch.mitEszett + " Stellen");
pruefe("sonst wortgleich mit dem Deutschen", ch.abweichend === 0, ch.abweichend + " Abweichungen");
console.log("\n4. DIE MASSGEBLICHKEITSKLAUSEL STEHT IN JEDER SPRACHE");
/* Ohne diesen Hinweis waere die Uebersetzung ein Risiko: Ein Kunde
koennte sich auf die franzoesische Fassung berufen. */
const klausel = await seite.evaluate(() => {
const W = window.I18N_WD;
const muster = {
de: /deutsch/i, "de-CH": /deutsch/i, en: /german/i,
fr: /allemand/i, pt: /alem/i,
};
const raus = {};
for (const [spr, m] of Object.entries(muster)) {
raus[spr] = m.test(W.rl_003[spr]) && m.test(W.rl_004[spr]);
}
return raus;
});
for (const [spr, gut] of Object.entries(klausel)) {
pruefe(` ${spr}: nennt die deutsche Fassung als verbindlich`, gut);
}
pruefe("kein Skriptfehler", skriptfehler.length === 0, skriptfehler[0] || "");
console.log(`\n===== ${ok} bestanden, ${fehl} fehlgeschlagen =====`);
await browser.close();
srv.close();
process.exit(fehl ? 1 : 0);
});