i18n: Englisch als Hauptsprache, Sprachpakete fuer Desktop und WebUI (#661)

Umbau durch Gemini Flash, Durchsicht und Nacharbeit durch Claude.

Umfang:
- Desktop: LocalizationManager (JSON-Pakete, Rueckfall auf Englisch,
  Umschalten zur Laufzeit), Localize-Markup-Erweiterung, Sprachwahl in den
  Einstellungen (AppSettings.Language, Vorgabe "en"); 317 Schluessel je Sprache
- WebUI: alle Ansichten auf t()/data-i18n, 656 Schluessel je Sprache
- Agenten-Werkzeuge: Beschreibungen, Schemata und Meldungen auf Englisch
- Tests an die englischen Meldungen angepasst, keine Pruefung abgeschwaecht

Nacharbeit bei der Durchsicht:
- ~25 Kommentarbloecke mit Begruendungen waren ersatzlos entfallen, darunter
  sicherheitsrelevante (NTFS-Datenstrom beim shared:-Praefix, Bremse gegen
  Agentenschleifen, doppelter POST beim Anlegen einer Instanz). Wieder
  eingesetzt: auf Englisch, wo die Datei umgestellt ist, sonst im Wortlaut.
- Drei WebUI-Schluessel fehlten (logs.paused, instances.instanceOfflineError,
  instances.noInstanceActiveError). Weil t() bei fehlendem Schluessel den
  Schluessel selbst liefert, griff der "|| err.message"-Rueckfall nie — die
  Oberflaeche haette den Schluesselnamen angezeigt.

Geprueft: Schluesselgleichheit en/de, Platzhalter je Schluessel, jeder im
Code benutzte Schluessel vorhanden, node --check fuer alle Module,
1.220 Tests gruen.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
This commit is contained in:
Richard
2026-09-24 09:43:51 +02:00
co-authored by Claude Opus 5.5
parent 69718122f3
commit ad71682c2d
97 changed files with 4103 additions and 1928 deletions
+23 -10
View File
@@ -30,7 +30,7 @@ import { rendereInstanzen, stoppePolling as stoppeInstanzenPolling } from "./vie
import { rendereEinstellungen } from "./views/settings.js";
import { rendereSicherung } from "./views/backup.js";
import { rendereAgentenChats, trenneAgentenChatsStream } from "./views/agent-chats.js";
import { loadLanguage, translateDocument } from "./modules/i18n.js";
import { loadLanguage, translateDocument, setLanguage, currentLanguage, t } from "./modules/i18n.js";
import { renderUsageRuns } from "./views/usage-runs.js";
import { renderUsageTimeline } from "./views/usage-timeline.js";
import { renderRunDetail } from "./views/run-detail.js";
@@ -112,6 +112,18 @@ document.querySelectorAll(".nav-link").forEach((link) => {
});
});
// Sprache umschalten
const spracheSchalter = document.getElementById("sprache-schalter");
spracheSchalter?.addEventListener("change", async (e) => {
await setLanguage(e.target.value);
});
window.addEventListener("clawd:language-changed", () => {
if (spracheSchalter) spracheSchalter.value = currentLanguage();
const hash = window.location.hash || "#/chat";
navigiere(hash);
});
// Farbschema umschalten
themeSchalter?.addEventListener("click", () => {
wechsleTheme();
@@ -145,10 +157,10 @@ beiVerbindungsStatus((istOffen) => {
if (!verbindungPunkt) return;
if (istOffen) {
verbindungPunkt.classList.add("offen");
verbindungPunkt.title = "Ereignisstrom: verbunden";
verbindungPunkt.title = t("header.eventStreamConnected");
} else {
verbindungPunkt.classList.remove("offen");
verbindungPunkt.title = "Ereignisstrom: getrennt";
verbindungPunkt.title = t("header.eventStreamDisconnected");
}
});
@@ -190,7 +202,7 @@ function aktualisiereInstanzPlakette() {
kopfInstanzStatus.replaceWith(neuePlakette);
kopfInstanzStatus = neuePlakette;
} else {
const text = laufende.length > 1 ? `${laufende.length} laufen` : "Keine aktiv";
const text = laufende.length > 1 ? t("header.multipleRunning", { count: laufende.length }) : t("header.noneActive");
const art = laufende.length > 1 ? "warnung" : "neutral";
const neuePlakette = erstellePlakette(text, art);
neuePlakette.id = "kopf-instanz-status";
@@ -203,10 +215,10 @@ function aktualisiereInstanzPlakette() {
if (aktive && aktive.zustand !== "Running") {
startBtn.hidden = false;
startBtn.disabled = false;
startBtn.textContent = "Starten";
startBtn.textContent = t("header.startInstance");
startBtn.onclick = async () => {
startBtn.disabled = true;
startBtn.textContent = "Startet…";
startBtn.textContent = t("header.starting");
try {
const res = await api(`/verwaltung/instanzen/${encodeURIComponent(aktive.id)}/start`, {
method: "POST"
@@ -223,7 +235,7 @@ function aktualisiereInstanzPlakette() {
zeigeMeldung(`Start fehlgeschlagen: ${err.message}`, "gefahr");
}
startBtn.disabled = false;
startBtn.textContent = "Starten";
startBtn.textContent = t("header.startInstance");
}
};
} else {
@@ -266,7 +278,7 @@ async function aktualisiereInstanzenListe(vorhandene = null) {
kopfInstanzSelect.replaceChildren();
const placeholderOpt = erstelleElement("option", {
value: "",
text: bekannteInstanzen.length === 0 ? "Keine Instanzen" : "— Instanz wählen —"
text: bekannteInstanzen.length === 0 ? t("header.noInstances") : t("header.selectInstancePlaceholder")
});
kopfInstanzSelect.append(placeholderOpt);
@@ -304,9 +316,9 @@ async function aktualisiereKopfStatus() {
} catch (err) {
if (kopfOpenRouter) {
if (err.instanzUnerreichbar) {
kopfOpenRouter.textContent = "Instanz offline (502)";
kopfOpenRouter.textContent = t("header.instanceOffline");
} else if (err.instanzKonflikt) {
kopfOpenRouter.textContent = "Instanz wählen (409)";
kopfOpenRouter.textContent = t("header.selectInstancePrompt");
} else {
kopfOpenRouter.textContent = "";
}
@@ -366,6 +378,7 @@ async function starteHauptAnwendung(instanzen) {
// #661: language packs before the first render
await loadLanguage();
translateDocument();
if (spracheSchalter) spracheSchalter.value = currentLanguage();
// Router initialisieren
initialisiereRouter();