diff --git a/src/ClawdDotNet.Manager.Web/wwwroot/app.js b/src/ClawdDotNet.Manager.Web/wwwroot/app.js index 624b35c..a88e497 100644 --- a/src/ClawdDotNet.Manager.Web/wwwroot/app.js +++ b/src/ClawdDotNet.Manager.Web/wwwroot/app.js @@ -233,16 +233,16 @@ function aktualisiereInstanzPlakette() { const res = await api(`/verwaltung/instanzen/${encodeURIComponent(aktive.id)}/start`, { method: "POST" }); - zeigeMeldung(res?.meldung || `Instanz '${aktive.name}' gestartet.`, "erfolg"); + zeigeMeldung(res?.meldung || t("header.instanceStarted", { name: aktive.name }), "erfolg"); await aktualisiereInstanzenListe(); if (window.location.hash) { window.dispatchEvent(new HashChangeEvent("hashchange")); } } catch (err) { if (err.lizenzFehlt) { - zeigeMeldung("Start abgelehnt: Ohne gültige Lizenz startet keine Instanz (HTTP 402).", "gefahr", 6000); + zeigeMeldung(t("header.startNoLicense"), "gefahr", 6000); } else { - zeigeMeldung(`Start fehlgeschlagen: ${err.message}`, "gefahr"); + zeigeMeldung(t("header.startFailed", { error: err.message }), "gefahr"); } startBtn.disabled = false; startBtn.textContent = t("header.startInstance"); @@ -319,9 +319,11 @@ async function aktualisiereKopfStatus() { try { const status = await api("/status"); if (kopfOpenRouter && status) { + // #764: The field is apiReachable (StatusDto); isApiReachable never existed, so without + // credits text this always said "offline". kopfOpenRouter.textContent = status.creditsText - ? `OpenRouter: ${status.creditsText}` - : (status.isApiReachable ? "OpenRouter: OK" : "OpenRouter: offline"); + ? t("header.openRouterCredits", { credits: status.creditsText }) + : (status.apiReachable ? t("header.openRouterOk") : t("header.openRouterOffline")); } } catch (err) { if (kopfOpenRouter) { diff --git a/src/ClawdDotNet.Manager.Web/wwwroot/i18n/de.json b/src/ClawdDotNet.Manager.Web/wwwroot/i18n/de.json index eeaf256..c154c3e 100644 --- a/src/ClawdDotNet.Manager.Web/wwwroot/i18n/de.json +++ b/src/ClawdDotNet.Manager.Web/wwwroot/i18n/de.json @@ -669,6 +669,59 @@ "logs.empty": "Noch keine Protokolleinträge vorhanden.", "settings.title": "Einstellungen & Systemarchitektur", + "common.error": "Fehler", + "common.errorWith": "Fehler: {error}", + "header.instanceStarted": "Instanz '{name}' gestartet.", + "header.startNoLicense": "Start abgelehnt: Ohne gültige Lizenz startet keine Instanz (HTTP 402).", + "header.startFailed": "Start fehlgeschlagen: {error}", + "header.openRouterCredits": "OpenRouter: {credits}", + "header.openRouterOk": "OpenRouter: OK", + "header.openRouterOffline": "OpenRouter: offline", + "login.tokenRejected": "Kein gültiges Token. Bitte neu anmelden.", + "backup.loadFailed": "Sicherungen konnten nicht geladen werden: {error}", + "backup.ftpLoadFailed": "Einstellungen nicht ladbar: {error}", + "info.agentEngine": "Agent-Engine", + "info.scheduler": "Scanner / Planer", + "info.apiOk": "OK", + "info.apiOffline": "Offline", + "info.credits": "Guthaben", + "instances.starting": "'{name}' wird gestartet…", + "instances.stopTitle": "Instanz anhalten", + "instances.stopExplanation": "Das Anhalten von '{name}' fährt alle laufenden Agenten und Prozesse geordnet herunter.", + "instances.instance": "Instanz", + "instances.confirmLocked": "Bestätigung gesperrt: {seconds}s…", + "instances.confirmUnlocked": "Entsperrt — bereit zur Bestätigung.", + "instances.stopping": "Wird angehalten…", + "instances.stopped": "'{name}' angehalten.", + "instances.stopFailed": "Anhalten fehlgeschlagen: {error}", + "logs.level.debug": "Debug", + "logs.level.info": "Info", + "logs.level.warning": "Warnung", + "logs.level.error": "Fehler", + "logs.level.critical": "Kritisch", + "logs.live": "Live", + "services.enabledMsg": "'{name}' aktiviert.", + "services.disabledMsg": "'{name}' deaktiviert.", + "services.deleteConfirm": "Dienst '{name}' ({id}) löschen?", + "services.deleted": "Dienst '{name}' gelöscht.", + "services.created": "Dienst '{name}' angelegt.", + "services.namePlaceholder": "z. B. WebHookService", + "services.typePlaceholder": "z. B. HTTP, TCP, Socket", + "services.portPlaceholder": "z. B. 8081 (optional)", + "tasks.loadError": "Fehler im Taskboard", + "tasks.untitled": "Aufgabe {id}", + "tasks.assignNewRun": "{name} (neuer Lauf)", + "tasks.assignChatHistory": "{name} (Chatverlauf)", + "tasks.assignHuman": "Mensch (kein Agent)", + "tasks.chatHistoryNotice": "⚠ Wiederkehrende Aufgaben im Chatverlauf lassen Kontext und Kosten wachsen.", + "tasks.scheduleValue": "Wert:", + "tasks.timezone": "Zeitzone:", + "tasks.acceptance": "Abnahmekriterien:", + "agents.modelPlaceholder": "z. B. anthropic/claude-sonnet-4-5", + "org.unitNamePlaceholder": "z. B. Marketing", + "org.workspaceNamePlaceholder": "z. B. Artikelstamm", + "org.protectedPathsPlaceholder": "z. B. prices, archive", + "org.votersPlaceholder": "buchhaltung\nlogistik\nmarketing", "settings.updateCard": "Update", "settings.updateInstalled": "Installiert", "settings.updateLatest": "Neueste", diff --git a/src/ClawdDotNet.Manager.Web/wwwroot/i18n/en.json b/src/ClawdDotNet.Manager.Web/wwwroot/i18n/en.json index 8be69d6..0e5586f 100644 --- a/src/ClawdDotNet.Manager.Web/wwwroot/i18n/en.json +++ b/src/ClawdDotNet.Manager.Web/wwwroot/i18n/en.json @@ -669,6 +669,59 @@ "logs.empty": "No log messages yet.", "settings.title": "Settings & System Architecture", + "common.error": "Error", + "common.errorWith": "Error: {error}", + "header.instanceStarted": "Instance '{name}' started.", + "header.startNoLicense": "Start refused: without a valid license no instance starts (HTTP 402).", + "header.startFailed": "Start failed: {error}", + "header.openRouterCredits": "OpenRouter: {credits}", + "header.openRouterOk": "OpenRouter: OK", + "header.openRouterOffline": "OpenRouter: offline", + "login.tokenRejected": "No valid token. Please sign in again.", + "backup.loadFailed": "Backups could not be loaded: {error}", + "backup.ftpLoadFailed": "Settings could not be loaded: {error}", + "info.agentEngine": "Agent engine", + "info.scheduler": "Scanner / scheduler", + "info.apiOk": "OK", + "info.apiOffline": "Offline", + "info.credits": "Credits", + "instances.starting": "Starting '{name}'…", + "instances.stopTitle": "Stop instance", + "instances.stopExplanation": "Stopping instance '{name}' winds down all running agents and processes in order.", + "instances.instance": "Instance", + "instances.confirmLocked": "Confirmation locked: {seconds}s…", + "instances.confirmUnlocked": "Unlocked — ready to confirm.", + "instances.stopping": "Stopping…", + "instances.stopped": "'{name}' stopped.", + "instances.stopFailed": "Stop failed: {error}", + "logs.level.debug": "Debug", + "logs.level.info": "Info", + "logs.level.warning": "Warning", + "logs.level.error": "Error", + "logs.level.critical": "Critical", + "logs.live": "Live", + "services.enabledMsg": "'{name}' enabled.", + "services.disabledMsg": "'{name}' disabled.", + "services.deleteConfirm": "Delete service '{name}' ({id})?", + "services.deleted": "Service '{name}' deleted.", + "services.created": "Service '{name}' created.", + "services.namePlaceholder": "e.g. WebHookService", + "services.typePlaceholder": "e.g. HTTP, TCP, Socket", + "services.portPlaceholder": "e.g. 8081 (optional)", + "tasks.loadError": "Taskboard error", + "tasks.untitled": "Task {id}", + "tasks.assignNewRun": "{name} (new run)", + "tasks.assignChatHistory": "{name} (chat history)", + "tasks.assignHuman": "Human (no agent)", + "tasks.chatHistoryNotice": "⚠ Recurring tasks in the chat history make the context and the costs grow.", + "tasks.scheduleValue": "Value:", + "tasks.timezone": "Time zone:", + "tasks.acceptance": "Acceptance criteria:", + "agents.modelPlaceholder": "e.g. anthropic/claude-sonnet-4-5", + "org.unitNamePlaceholder": "e.g. Marketing", + "org.workspaceNamePlaceholder": "e.g. Article master", + "org.protectedPathsPlaceholder": "e.g. prices, archive", + "org.votersPlaceholder": "accounting\nlogistics\nmarketing", "settings.updateCard": "Update", "settings.updateInstalled": "Installed", "settings.updateLatest": "Latest", diff --git a/src/ClawdDotNet.Manager.Web/wwwroot/modules/api.js b/src/ClawdDotNet.Manager.Web/wwwroot/modules/api.js index 2e8f0de..1c525d9 100644 --- a/src/ClawdDotNet.Manager.Web/wwwroot/modules/api.js +++ b/src/ClawdDotNet.Manager.Web/wwwroot/modules/api.js @@ -2,6 +2,8 @@ // // Verwaltet das Zugangstoken im sessionStorage und kapselt fetch-Aufrufe. +import { t } from "./i18n.js"; + export const TOKEN_SCHLUESSEL = "clawd.token"; export const INSTANZ_SCHLUESSEL = "clawd.instanz"; @@ -145,7 +147,7 @@ export async function api(pfad, optionen = {}) { if (antwort.status === 401) { meldeUnberechtigt(); - const fehler = new Error("Kein gültiges Token. Bitte neu anmelden."); + const fehler = new Error(t("login.tokenRejected")); fehler.abgelehnt = true; fehler.status = 401; throw fehler; diff --git a/src/ClawdDotNet.Manager.Web/wwwroot/modules/dom.js b/src/ClawdDotNet.Manager.Web/wwwroot/modules/dom.js index 243c010..e61c7d1 100644 --- a/src/ClawdDotNet.Manager.Web/wwwroot/modules/dom.js +++ b/src/ClawdDotNet.Manager.Web/wwwroot/modules/dom.js @@ -2,7 +2,7 @@ // // Guide rule 3.1: foreign text is never HTML. No innerHTML anywhere in the tree. -import { t } from "./i18n.js"; +import { t, currentLanguage } from "./i18n.js"; const SVG_NS = "http://www.w3.org/2000/svg"; @@ -256,11 +256,19 @@ export function erstelleModal(titel, inhalt, aktionen = [], optionen = {}) { * @param {string|Date|number|null} wert * @returns {string} */ +/** + * #764: The locale of the active language. Dates were always formatted "de-DE", also in the + * English interface. en-GB rather than en-US: day before month, like the German format. + */ +function locale() { + return currentLanguage() === "de" ? "de-DE" : "en-GB"; +} + export function formatiereZeit(wert) { if (!wert) return "—"; const d = wert instanceof Date ? wert : new Date(wert); if (isNaN(d.getTime())) return String(wert); - return d.toLocaleTimeString("de-DE", { hour: "2-digit", minute: "2-digit", second: "2-digit" }); + return d.toLocaleTimeString(locale(), { hour: "2-digit", minute: "2-digit", second: "2-digit" }); } /** @@ -272,7 +280,7 @@ export function formatiereDatumZeit(wert) { if (!wert) return "—"; const d = wert instanceof Date ? wert : new Date(wert); if (isNaN(d.getTime())) return String(wert); - return d.toLocaleString("de-DE", { + return d.toLocaleString(locale(), { day: "2-digit", month: "2-digit", year: "numeric", diff --git a/src/ClawdDotNet.Manager.Web/wwwroot/views/agents.js b/src/ClawdDotNet.Manager.Web/wwwroot/views/agents.js index 993d19d..f22be9f 100644 --- a/src/ClawdDotNet.Manager.Web/wwwroot/views/agents.js +++ b/src/ClawdDotNet.Manager.Web/wwwroot/views/agents.js @@ -389,7 +389,7 @@ function oeffneNeuerAgentDialog() { const modellFeld = erstelleElement("input", { type: "text", required: true, - placeholder: "z.B. anthropic/claude-3.5-sonnet", + placeholder: t("agents.modelPlaceholder"), value: "anthropic/claude-3.5-sonnet", id: "neu-agent-modell" }); diff --git a/src/ClawdDotNet.Manager.Web/wwwroot/views/backup.js b/src/ClawdDotNet.Manager.Web/wwwroot/views/backup.js index b3f281b..3ea7877 100644 --- a/src/ClawdDotNet.Manager.Web/wwwroot/views/backup.js +++ b/src/ClawdDotNet.Manager.Web/wwwroot/views/backup.js @@ -187,8 +187,8 @@ export async function rendereSicherung() { inhaltBereich.replaceChildren(erstelleKarte(t("backup.existingBackups", { count: backups.length }), [tabelle])); } catch (err) { inhaltBereich.replaceChildren( - erstelleKarte("Fehler", [ - erstelleElement("p", { klasse: "fehler-meldung", text: `Sicherungen konnten nicht geladen werden: ${err.message}` }) + erstelleKarte(t("common.error"), [ + erstelleElement("p", { klasse: "fehler-meldung", text: t("backup.loadFailed", { error: err.message }) }) ]) ); } @@ -426,7 +426,7 @@ async function rendereFtp(bereich) { stand = await api("/verwaltung/sicherung/ftp"); } catch (err) { bereich.replaceChildren(erstelleKarte(t("backup.ftpTitle"), [ - erstelleElement("p", { klasse: "fehler-meldung", text: `Einstellungen nicht ladbar: ${err.message}` }) + erstelleElement("p", { klasse: "fehler-meldung", text: t("backup.ftpLoadFailed", { error: err.message }) }) ])); return; } diff --git a/src/ClawdDotNet.Manager.Web/wwwroot/views/chat.js b/src/ClawdDotNet.Manager.Web/wwwroot/views/chat.js index 6b27764..9bdfbc9 100644 --- a/src/ClawdDotNet.Manager.Web/wwwroot/views/chat.js +++ b/src/ClawdDotNet.Manager.Web/wwwroot/views/chat.js @@ -212,7 +212,7 @@ async function ladeAgenten(gewuenschteId = null) { agentenSelector.value = zielId; await waehleAgent(zielId); } catch (err) { - zeigeMeldung(`Fehler: ${err.message}`, "gefahr"); + zeigeMeldung(t("common.errorWith", { error: err.message }), "gefahr"); } } diff --git a/src/ClawdDotNet.Manager.Web/wwwroot/views/info.js b/src/ClawdDotNet.Manager.Web/wwwroot/views/info.js index 30330c1..0eeaf7e 100644 --- a/src/ClawdDotNet.Manager.Web/wwwroot/views/info.js +++ b/src/ClawdDotNet.Manager.Web/wwwroot/views/info.js @@ -64,9 +64,9 @@ export async function rendereInfo() { // 2. Teilsysteme const dlSysteme = erstelleElement("dl", { klasse: "datenliste" }); dlSysteme.append( - erstelleElement("dt", { text: "Agent Engine" }), + erstelleElement("dt", { text: t("info.agentEngine") }), erstelleElement("dd", {}, erstellePlakette(info.engineActive ? (t("status.running")) : (t("status.stopped")), info.engineActive ? "erfolg" : "neutral")), - erstelleElement("dt", { text: "Scanner / Scheduler" }), + erstelleElement("dt", { text: t("info.scheduler") }), erstelleElement("dd", {}, erstellePlakette(info.schedulerActive ? (t("status.running")) : (t("status.stopped")), info.schedulerActive ? "erfolg" : "neutral")), erstelleElement("dt", { text: t("nav.staging") }), erstelleElement("dd", {}, erstellePlakette(info.stagingActive ? (t("status.running")) : (t("status.stopped")), info.stagingActive ? "erfolg" : "neutral")), @@ -81,10 +81,10 @@ export async function rendereInfo() { if (status) { dlStatus.append( erstelleElement("dt", { text: "API" }), - erstelleElement("dd", {}, erstellePlakette(status.isApiReachable ? "OK" : "Offline", status.isApiReachable ? "erfolg" : "gefahr")), + erstelleElement("dd", {}, erstellePlakette(status.apiReachable ? t("info.apiOk") : t("info.apiOffline"), status.apiReachable ? "erfolg" : "gefahr")), erstelleElement("dt", { text: t("common.status") }), erstelleElement("dd", { text: status.statusText || "—" }), - erstelleElement("dt", { text: "Credits" }), + erstelleElement("dt", { text: t("info.credits") }), erstelleElement("dd", {}, erstelleElement("strong", { text: status.creditsText || "—" })) ); } else { diff --git a/src/ClawdDotNet.Manager.Web/wwwroot/views/instances.js b/src/ClawdDotNet.Manager.Web/wwwroot/views/instances.js index b582b16..68fd13b 100644 --- a/src/ClawdDotNet.Manager.Web/wwwroot/views/instances.js +++ b/src/ClawdDotNet.Manager.Web/wwwroot/views/instances.js @@ -188,7 +188,7 @@ async function ladeInstanzen(istHintergrund = false) { } catch (err) { if (!istHintergrund) { raster.replaceChildren( - erstelleElement("div", { klasse: "fehler-zentriert", text: `Error: ${err.message}` }) + erstelleElement("div", { klasse: "fehler-zentriert", text: t("common.errorWith", { error: err.message }) }) ); } } @@ -199,16 +199,16 @@ async function starteInstanz(instanz) { const antwort = await api(`/verwaltung/instanzen/${encodeURIComponent(instanz.id)}/start`, { method: "POST" }); - zeigeMeldung(antwort?.meldung || `Starting '${instanz.name}'…`, "erfolg"); + zeigeMeldung(antwort?.meldung || t("instances.starting", { name: instanz.name }), "erfolg"); if (!holeAktiveInstanz()) { setzeAktiveInstanz(instanz.id); } await ladeInstanzen(); } catch (err) { if (err.lizenzFehlt) { - zeigeMeldung("License required (HTTP 402).", "gefahr", 6000); + zeigeMeldung(t("header.startNoLicense"), "gefahr", 6000); } else { - zeigeMeldung(`Failed to start: ${err.message}`, "gefahr"); + zeigeMeldung(t("header.startFailed", { error: err.message }), "gefahr"); } } } @@ -228,16 +228,16 @@ function oeffneSicherheitsStoppDialog(instanz) { "h3", {}, erstelleIcon(PFADE.ausrufezeichen, 18), - erstelleElement("span", { text: " Stop Instance" }) + erstelleElement("span", { text: ` ${t("instances.stopTitle")}` }) ); const boxText1 = erstelleElement("p", { - text: `Stopping instance '${instanz.name}' will gracefully shut down all active agents and processes.` + text: t("instances.stopExplanation", { name: instanz.name }) }); sicherheitsBox.append(boxTitel, boxText1); const dl = erstelleElement("dl", { klasse: "datenliste" }); dl.append( - erstelleElement("dt", { text: "Instance" }), + erstelleElement("dt", { text: t("instances.instance") }), erstelleElement("dd", {}, erstelleElement("strong", { text: instanz.name })), erstelleElement("dt", { text: "ID" }), erstelleElement("dd", {}, erstelleElement("code", { text: instanz.id })), @@ -247,7 +247,7 @@ function oeffneSicherheitsStoppDialog(instanz) { const countdownBox = erstelleElement("div", { klasse: "sicherheits-countdown", - text: `Confirmation locked: ${verbleibend}s…` + text: t("instances.confirmLocked", { seconds: verbleibend }) }); inhalt.append(sicherheitsBox, dl, countdownBox); @@ -287,7 +287,7 @@ function oeffneSicherheitsStoppDialog(instanz) { timerId = setInterval(() => { verbleibend--; if (verbleibend > 0) { - countdownBox.textContent = `Confirmation locked: ${verbleibend}s…`; + countdownBox.textContent = t("instances.confirmLocked", { seconds: verbleibend }); const textSpan = anhaltenBtn.querySelector("span"); if (textSpan) textSpan.textContent = ` ${t("instances.stop")} (${verbleibend}s)`; } else { @@ -297,7 +297,7 @@ function oeffneSicherheitsStoppDialog(instanz) { const textSpan = anhaltenBtn.querySelector("span"); if (textSpan) textSpan.textContent = ` ${t("instances.stop")}`; countdownBox.className = "sicherheits-countdown abgelaufen"; - countdownBox.textContent = "Unlocked — confirmation ready."; + countdownBox.textContent = t("instances.confirmUnlocked"); } }, 1000); @@ -305,17 +305,17 @@ function oeffneSicherheitsStoppDialog(instanz) { if (verbleibend > 0) return; anhaltenBtn.disabled = true; const textSpan = anhaltenBtn.querySelector("span"); - if (textSpan) textSpan.textContent = " Stopping…"; + if (textSpan) textSpan.textContent = ` ${t("instances.stopping")}`; try { const antwort = await api(`/verwaltung/instanzen/${encodeURIComponent(instanz.id)}/stop`, { method: "POST" }); modal.schliessen(); - zeigeMeldung(antwort?.meldung || `'${instanz.name}' stopped.`, "info"); + zeigeMeldung(antwort?.meldung || t("instances.stopped", { name: instanz.name }), "info"); await ladeInstanzen(); } catch (err) { - zeigeMeldung(`Stop failed: ${err.message}`, "gefahr"); + zeigeMeldung(t("instances.stopFailed", { error: err.message }), "gefahr"); anhaltenBtn.disabled = false; if (textSpan) textSpan.textContent = ` ${t("instances.stop")}`; } diff --git a/src/ClawdDotNet.Manager.Web/wwwroot/views/logs.js b/src/ClawdDotNet.Manager.Web/wwwroot/views/logs.js index c36cfb7..a395b28 100644 --- a/src/ClawdDotNet.Manager.Web/wwwroot/views/logs.js +++ b/src/ClawdDotNet.Manager.Web/wwwroot/views/logs.js @@ -70,11 +70,11 @@ export async function rendereProtokoll() { }); const levelOptionen = [ ["All", t("common.all")], - ["Debug", "Debug"], - ["Information", "Info"], - ["Warning", "Warning"], - ["Error", "Error"], - ["Critical", "Critical"] + ["Debug", t("logs.level.debug")], + ["Information", t("logs.level.info")], + ["Warning", t("logs.level.warning")], + ["Error", t("logs.level.error")], + ["Critical", t("logs.level.critical")] ]; for (const [val, label] of levelOptionen) { levelSelect.append(erstelleElement("option", { value: val, text: label })); @@ -252,7 +252,7 @@ function togglePause() { function aktualisiereStatus(offen, text = null) { if (!streamStatusPlakette) return; - const statusT = text || (offen ? "Live" : "—"); + const statusT = text || (offen ? t("logs.live") : "—"); const art = offen ? "erfolg" : "neutral"; const neuePlakette = erstellePlakette(statusT, art); streamStatusPlakette.replaceWith(neuePlakette); diff --git a/src/ClawdDotNet.Manager.Web/wwwroot/views/organisation.js b/src/ClawdDotNet.Manager.Web/wwwroot/views/organisation.js index fd932ff..e9edb1e 100644 --- a/src/ClawdDotNet.Manager.Web/wwwroot/views/organisation.js +++ b/src/ClawdDotNet.Manager.Web/wwwroot/views/organisation.js @@ -270,7 +270,7 @@ function select(sel) { function openCreateUnit(parentId = null) { const id = erstelleElement("input", { type: "text", placeholder: "marketing", required: true }); - const name = erstelleElement("input", { type: "text", placeholder: "Marketing", required: true }); + const name = erstelleElement("input", { type: "text", placeholder: t("org.unitNamePlaceholder"), required: true }); const parent = unitOptions(erstelleElement("select"), parentId); const goal = erstelleElement("textarea", { rows: 2 }); @@ -668,7 +668,7 @@ function votesCard(unit) { const current = unit.votes; const ops = erstelleElement("textarea", { rows: 2, placeholder: "Database.insert" }); ops.value = (current?.operations ?? []).join("\n"); - const voters = erstelleElement("textarea", { rows: 3, placeholder: "accounting\nlogistics\nmarketing" }); + const voters = erstelleElement("textarea", { rows: 3, placeholder: t("org.votersPlaceholder") }); voters.value = (current?.voters ?? []).join("\n"); const number = (value, min) => erstelleElement("input", { type: "number", min: String(min), step: "1", value: String(value) }); const approveAt = number(current?.approveAt ?? 3, 1); @@ -944,7 +944,7 @@ function unitWorkspaceCard(unit) { function openCreateWorkspace() { const id = erstelleElement("input", { type: "text", placeholder: "articles", required: true }); - const name = erstelleElement("input", { type: "text", placeholder: "Article master", required: true }); + const name = erstelleElement("input", { type: "text", placeholder: t("org.workspaceNamePlaceholder"), required: true }); const purpose = erstelleElement("textarea", { rows: 2 }); const owner = unitOptions(erstelleElement("select"), null, new Set(), t("org.noOwner")); @@ -982,7 +982,7 @@ function workspacePanel(workspace) { const name = erstelleElement("input", { type: "text", value: workspace.name }); const purpose = erstelleElement("textarea", { rows: 2 }); purpose.value = workspace.purpose ?? ""; - const protectedPaths = erstelleElement("input", { type: "text", value: workspace.protectedPaths.join(", "), placeholder: "prices, archive" }); + const protectedPaths = erstelleElement("input", { type: "text", value: workspace.protectedPaths.join(", "), placeholder: t("org.protectedPathsPlaceholder") }); const general = erstelleKarte(workspace.name, [ erstelleElement("p", { klasse: "gedaempft text-klein", diff --git a/src/ClawdDotNet.Manager.Web/wwwroot/views/placeholders.js b/src/ClawdDotNet.Manager.Web/wwwroot/views/placeholders.js deleted file mode 100644 index fb5a05c..0000000 --- a/src/ClawdDotNet.Manager.Web/wwwroot/views/placeholders.js +++ /dev/null @@ -1,52 +0,0 @@ -// ClawdDotNet — Platzhalter-Ansichten für noch nicht freigegebene Kern-Endpunkte -// -// Leitfaden Abschnitt 7: Sicherung, Einstellungen und Agenten-Chats (Analyse). - -import { erstelleElement, erstelleKarte, erstelleIcon, PFADE } from "../modules/dom.js"; - -export function rendereSicherung() { - const container = document.getElementById("ansicht-inhalt"); - if (!container) return; - container.replaceChildren(); - - const layout = erstelleElement("div", { klasse: "platzhalter-ansicht" }); - layout.append(erstelleElement("h1", { text: "Datensicherung & Wiederherstellung" })); - - const karte = erstelleKarte("In Vorbereitung", [ - erstelleElement("p", { - klasse: "gedaempft", - text: "Die API-Endpunkte für die automatisierte Sicherung und Rücksicherung über das WebUI befinden sich in Arbeit." - }), - erstelleElement("p", { - klasse: "hinweis", - text: "Bitte verwende für die Sicherung von Instanzen aktuell die Desktop-Oberfläche von ClawdDotNet." - }) - ]); - - layout.append(karte); - container.append(layout); -} - - -export function rendereAgentenChats() { - const container = document.getElementById("ansicht-inhalt"); - if (!container) return; - container.replaceChildren(); - - const layout = erstelleElement("div", { klasse: "platzhalter-ansicht" }); - layout.append(erstelleElement("h1", { text: "Agenten-Kommunikation" })); - - const karte = erstelleKarte("Analyse: Interne Agenten-Chats", [ - erstelleElement("p", { - klasse: "gedaempft", - text: "Die Protokollierung und Auswertung der direkten Kommunikation zwischen zwei Agenten (A2A) wird mit Vorhaben #208 bereitgestellt." - }), - erstelleElement("p", { - klasse: "hinweis", - text: "Direkte Chats mit Agenten können im Bereich 'Chat' geführt werden." - }) - ]); - - layout.append(karte); - container.append(layout); -} diff --git a/src/ClawdDotNet.Manager.Web/wwwroot/views/services.js b/src/ClawdDotNet.Manager.Web/wwwroot/views/services.js index 82f1c89..0920f85 100644 --- a/src/ClawdDotNet.Manager.Web/wwwroot/views/services.js +++ b/src/ClawdDotNet.Manager.Web/wwwroot/views/services.js @@ -111,10 +111,10 @@ async function ladeDienste() { method: "PUT" }); s.enabled = aktiv; - zeigeMeldung(`'${s.name}' ${aktiv ? "enabled" : "disabled"}.`, "info"); + zeigeMeldung(t(aktiv ? "services.enabledMsg" : "services.disabledMsg", { name: s.name }), "info"); } catch (err) { e.target.checked = !aktiv; - zeigeMeldung(`Error: ${err.message}`, "gefahr"); + zeigeMeldung(t("common.errorWith", { error: err.message }), "gefahr"); } } }); @@ -152,14 +152,14 @@ async function ladeDienste() { } async function loescheDienst(s) { - if (!confirm(`Delete service '${s.name}' (${s.id})?`)) return; + if (!confirm(t("services.deleteConfirm", { name: s.name, id: s.id }))) return; try { await api(`/services/${encodeURIComponent(s.id)}`, { method: "DELETE" }); - zeigeMeldung(`Service '${s.name}' deleted.`, "info"); + zeigeMeldung(t("services.deleted", { name: s.name }), "info"); await ladeDienste(); } catch (err) { - zeigeMeldung(`Failed: ${err.message}`, "gefahr"); + zeigeMeldung(t("common.errorWith", { error: err.message }), "gefahr"); } } @@ -169,20 +169,20 @@ function oeffneNeuerDienstDialog() { const nameFeld = erstelleElement("input", { type: "text", required: true, - placeholder: "e.g. WebHookService" + placeholder: t("services.namePlaceholder") }); const typFeld = erstelleElement("input", { type: "text", required: true, - placeholder: "e.g. HTTP, TCP, Socket" + placeholder: t("services.typePlaceholder") }); const portFeld = erstelleElement("input", { type: "number", min: "1", max: "65535", - placeholder: "e.g. 8081 (optional)" + placeholder: t("services.portPlaceholder") }); const descFeld = erstelleElement("textarea", { @@ -231,10 +231,10 @@ function oeffneNeuerDienstDialog() { } }); modal.schliessen(); - zeigeMeldung(`Service '${nameFeld.value.trim()}' created.`, "erfolg"); + zeigeMeldung(t("services.created", { name: nameFeld.value.trim() }), "erfolg"); await ladeDienste(); } catch (err) { - zeigeMeldung(`Error: ${err.message}`, "gefahr"); + zeigeMeldung(t("common.errorWith", { error: err.message }), "gefahr"); } finally { anlegenBtn.disabled = false; } diff --git a/src/ClawdDotNet.Manager.Web/wwwroot/views/tasks.js b/src/ClawdDotNet.Manager.Web/wwwroot/views/tasks.js index 4630c65..3663095 100644 --- a/src/ClawdDotNet.Manager.Web/wwwroot/views/tasks.js +++ b/src/ClawdDotNet.Manager.Web/wwwroot/views/tasks.js @@ -155,7 +155,7 @@ async function ladeTaskboard() { container.append(raster); } catch (err) { container.replaceChildren( - erstelleKarte("Taskboard Error", [ + erstelleKarte(t("tasks.loadError"), [ erstelleElement("p", { klasse: "gedaempft", text: err.message }) ]) ); @@ -343,7 +343,7 @@ function erstelleAufgabenKarte(task) { ); zeilen.push(aktionen); - return erstelleKarte(task.title || `Task ${task.id}`, zeilen); + return erstelleKarte(task.title || t("tasks.untitled", { id: task.id }), zeilen); } async function fuehreAus(task) { @@ -352,7 +352,7 @@ async function fuehreAus(task) { zeigeMeldung(res?.message || t("tasks.runTriggered"), "erfolg"); setTimeout(ladeTaskboard, 1500); } catch (err) { - zeigeMeldung(`Error: ${err.message}`, "gefahr"); + zeigeMeldung(t("common.errorWith", { error: err.message }), "gefahr"); } } @@ -362,7 +362,7 @@ async function setzeAktiv(task, aktiv) { zeigeMeldung(res?.message || (aktiv ? t("tasks.resume") : t("tasks.pause")), "info"); await ladeTaskboard(); } catch (err) { - zeigeMeldung(`Error: ${err.message}`, "gefahr"); + zeigeMeldung(t("common.errorWith", { error: err.message }), "gefahr"); } } @@ -384,11 +384,11 @@ async function oeffneAufgabenDialog(task) { const zuordnung = erstelleElement("select", { required: true }); for (const a of agenten) { zuordnung.append( - erstelleElement("option", { value: `@new:${a.agentId}`, text: `${a.displayName} (New Run)` }), - erstelleElement("option", { value: `@${a.agentId}`, text: `${a.displayName} (Chat History)` }) + erstelleElement("option", { value: `@new:${a.agentId}`, text: t("tasks.assignNewRun", { name: a.displayName }) }), + erstelleElement("option", { value: `@${a.agentId}`, text: t("tasks.assignChatHistory", { name: a.displayName }) }) ); } - zuordnung.append(erstelleElement("option", { value: "@human", text: "Human (No Agent)" })); + zuordnung.append(erstelleElement("option", { value: "@human", text: t("tasks.assignHuman") })); if (task?.assignee && ![...zuordnung.options].some((o) => o.value === task.assignee)) { zuordnung.append(erstelleElement("option", { value: task.assignee, text: task.assignee })); } @@ -397,7 +397,7 @@ async function oeffneAufgabenDialog(task) { const terminArt = erstelleElement("select"); terminArt.append( erstelleElement("option", { value: "", text: t("tasks.scheduleNone") }), - erstelleElement("option", { value: "cron", text: "Cron" }), + erstelleElement("option", { value: "cron", text: t("tasks.scheduleCron") }), erstelleElement("option", { value: "every", text: t("tasks.scheduleInterval") }), erstelleElement("option", { value: "at", text: t("tasks.scheduleAt") }) ); @@ -439,7 +439,7 @@ async function oeffneAufgabenDialog(task) { const wiederkehrend = terminArt.value === "cron" || terminArt.value === "every"; const imVerlauf = zuordnung.value.startsWith("@") && !zuordnung.value.startsWith("@new") && zuordnung.value !== "@human"; hinweis.hidden = !(wiederkehrend && imVerlauf); - hinweis.textContent = "⚠ Notice: Running recurring tasks in chat history increases context and costs."; + hinweis.textContent = t("tasks.chatHistoryNotice"); const ohneTermin = terminArt.value === ""; terminWert.disabled = ohneTermin; @@ -459,9 +459,9 @@ async function oeffneAufgabenDialog(task) { erstelleElement("label", { text: t("tasks.taskMessage") + ":" }), beschreibung, erstelleElement("label", { text: t("tasks.agent") + ":" }), zuordnung, erstelleElement("label", { text: t("tasks.scheduleType") + ":" }), terminArt, - erstelleElement("label", { text: "Value:" }), terminWert, - erstelleElement("label", { text: "Timezone:" }), zeitzone, - erstelleElement("label", { text: "Acceptance Criteria:" }), abnahme, + erstelleElement("label", { text: t("tasks.scheduleValue") }), terminWert, + erstelleElement("label", { text: t("tasks.timezone") }), zeitzone, + erstelleElement("label", { text: t("tasks.acceptance") }), abnahme, erstelleElement("label", { text: t("tasks.parent") + ":" }), eltern, erstelleElement("label", { text: t("tasks.fillBy") + ":", title: t("tasks.fillByHint") }), fuellBis, erstelleElement("label", { text: t("tasks.estimateUsd") + ":" }), schaetzung, @@ -513,7 +513,7 @@ async function oeffneAufgabenDialog(task) { zeigeMeldung(text, "erfolg"); await ladeTaskboard(); } catch (err) { - zeigeMeldung(`Error: ${err.message}`, "gefahr"); + zeigeMeldung(t("common.errorWith", { error: err.message }), "gefahr"); } finally { speichern.disabled = false; }