diff --git a/CHANGELOG.md b/CHANGELOG.md index 9b41fd7719f..ec23b6d2370 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -18,6 +18,7 @@ Docs: https://docs.openclaw.ai - Android/Nodes: add `camera.list`, `device.permissions`, `device.health`, and `notifications.actions` (`open`/`dismiss`/`reply`) on Android nodes, plus first-class node-tool actions for the new device/notification commands. (#28260) Thanks @obviyus. - Android/Gateway capability refresh: add live Android capability integration coverage and node canvas capability refresh wiring, plus runtime hardening for A2UI readiness retries, scoped canvas URL normalization, debug diagnostics JSON, and JavaScript MIME delivery. (#28388) Thanks @obviyus. - Docs/Contributing: add Nimrod Gutman to the maintainer roster in `CONTRIBUTING.md`. (#27840) Thanks @ngutman. +- Web UI/i18n: add German (`de`) locale support and auto-render language options from supported locale constants in Overview settings. (#28495) thanks @dsantoreis. ### Fixes diff --git a/ui/src/i18n/lib/translate.ts b/ui/src/i18n/lib/translate.ts index 0a03226ff42..ef39ce54796 100644 --- a/ui/src/i18n/lib/translate.ts +++ b/ui/src/i18n/lib/translate.ts @@ -3,7 +3,7 @@ import type { Locale, TranslationMap } from "./types.ts"; type Subscriber = (locale: Locale) => void; -export const SUPPORTED_LOCALES: ReadonlyArray = ["en", "zh-CN", "zh-TW", "pt-BR"]; +export const SUPPORTED_LOCALES: ReadonlyArray = ["en", "zh-CN", "zh-TW", "pt-BR", "de"]; export function isSupportedLocale(value: string | null | undefined): value is Locale { return value !== null && value !== undefined && SUPPORTED_LOCALES.includes(value as Locale); @@ -30,6 +30,9 @@ class I18nManager { if (navLang.startsWith("pt")) { return "pt-BR"; } + if (navLang.startsWith("de")) { + return "de"; + } return "en"; } @@ -64,6 +67,8 @@ class I18nManager { module = await import("../locales/zh-TW.ts"); } else if (locale === "pt-BR") { module = await import("../locales/pt-BR.ts"); + } else if (locale === "de") { + module = await import("../locales/de.ts"); } else { return; } diff --git a/ui/src/i18n/lib/types.ts b/ui/src/i18n/lib/types.ts index 3fefa42bf59..9578d0ff7a9 100644 --- a/ui/src/i18n/lib/types.ts +++ b/ui/src/i18n/lib/types.ts @@ -1,6 +1,6 @@ export type TranslationMap = { [key: string]: string | TranslationMap }; -export type Locale = "en" | "zh-CN" | "zh-TW" | "pt-BR"; +export type Locale = "en" | "zh-CN" | "zh-TW" | "pt-BR" | "de"; export interface I18nConfig { locale: Locale; diff --git a/ui/src/i18n/locales/de.ts b/ui/src/i18n/locales/de.ts new file mode 100644 index 00000000000..ed853e4e6c1 --- /dev/null +++ b/ui/src/i18n/locales/de.ts @@ -0,0 +1,126 @@ +import type { TranslationMap } from "../lib/types.ts"; + +export const de: TranslationMap = { + common: { + version: "Version", + health: "Status", + ok: "OK", + offline: "Offline", + connect: "Verbinden", + refresh: "Aktualisieren", + enabled: "Aktiviert", + disabled: "Deaktiviert", + na: "k. A.", + docs: "Dokumentation", + resources: "Ressourcen", + }, + nav: { + chat: "Chat", + control: "Steuerung", + agent: "Agent", + settings: "Einstellungen", + expand: "Seitenleiste ausklappen", + collapse: "Seitenleiste einklappen", + }, + tabs: { + agents: "Agenten", + overview: "Übersicht", + channels: "Kanäle", + instances: "Instanzen", + sessions: "Sitzungen", + usage: "Nutzung", + cron: "Cron-Aufgaben", + skills: "Fähigkeiten", + nodes: "Geräte", + chat: "Chat", + config: "Konfiguration", + debug: "Debug", + logs: "Protokolle", + }, + subtitles: { + agents: "Agent-Arbeitsbereiche, Tools und Identitäten verwalten.", + overview: "Gateway-Status, Einstiegspunkte und eine schnelle Zustandsprüfung.", + channels: "Kanäle und Einstellungen verwalten.", + instances: "Präsenzsignale von verbundenen Clients und Geräten.", + sessions: "Aktive Sitzungen inspizieren und Standardeinstellungen pro Sitzung anpassen.", + usage: "API-Nutzung und Kosten überwachen.", + cron: "Aufweckzeiten und wiederkehrende Agent-Läufe planen.", + skills: "Skill-Verfügbarkeit und API-Schlüsselinjektion verwalten.", + nodes: "Gekoppelte Geräte, Fähigkeiten und Befehlsfreigabe.", + chat: "Direkte Gateway-Chat-Sitzung für schnelle Eingriffe.", + config: "~/.openclaw/openclaw.json sicher bearbeiten.", + debug: "Gateway-Snapshots, Ereignisse und manuelle RPC-Aufrufe.", + logs: "Live-Verfolgung der Gateway-Protokolldateien.", + }, + overview: { + access: { + title: "Gateway-Zugang", + subtitle: "Wo sich das Dashboard verbindet und wie es sich authentifiziert.", + wsUrl: "WebSocket-URL", + token: "Gateway-Token", + password: "Passwort (nicht gespeichert)", + sessionKey: "Standard-Sitzungsschlüssel", + language: "Sprache", + connectHint: "Klicken Sie auf Verbinden, um Verbindungsänderungen anzuwenden.", + trustedProxy: "Authentifiziert über vertrauenswürdigen Proxy.", + }, + snapshot: { + title: "Snapshot", + subtitle: "Neueste Gateway-Handshake-Informationen.", + status: "Status", + uptime: "Betriebszeit", + tickInterval: "Tick-Intervall", + lastChannelsRefresh: "Letzte Kanalaktualisierung", + channelsHint: + "Verwenden Sie Kanäle, um WhatsApp, Telegram, Discord, Signal oder iMessage zu verknüpfen.", + }, + stats: { + instances: "Instanzen", + instancesHint: "Präsenzsignale in den letzten 5 Minuten.", + sessions: "Sitzungen", + sessionsHint: "Letzte vom Gateway verfolgte Sitzungsschlüssel.", + cron: "Cron", + cronNext: "Nächste Ausführung {time}", + }, + notes: { + title: "Notizen", + subtitle: "Kurze Hinweise für Remote-Steuerung.", + tailscaleTitle: "Tailscale Serve", + tailscaleText: + "Bevorzugen Sie den Serve-Modus, um das Gateway auf Loopback mit Tailnet-Auth zu halten.", + sessionTitle: "Sitzungshygiene", + sessionText: "Verwenden Sie /new oder sessions.patch, um den Kontext zurückzusetzen.", + cronTitle: "Cron-Erinnerungen", + cronText: "Verwenden Sie isolierte Sitzungen für wiederkehrende Läufe.", + }, + auth: { + required: + "Dieses Gateway erfordert Authentifizierung. Fügen Sie ein Token oder Passwort hinzu und klicken Sie auf Verbinden.", + failed: + "Authentifizierung fehlgeschlagen. Kopieren Sie erneut eine URL mit Token über {command}, oder aktualisieren Sie das Token und klicken Sie auf Verbinden.", + }, + pairing: { + hint: "Dieses Gerät benötigt eine Pairing-Freigabe vom Gateway-Host.", + mobileHint: + "Auf dem Mobilgerät? Kopieren Sie die vollständige URL (einschließlich #token=...) von openclaw dashboard --no-open auf Ihrem Desktop.", + }, + insecure: { + hint: "Diese Seite ist HTTP, daher blockiert der Browser die Geräteidentifikation. Verwenden Sie HTTPS (Tailscale Serve) oder öffnen Sie {url} auf dem Gateway-Host.", + stayHttp: "Wenn Sie bei HTTP bleiben müssen, setzen Sie {config} (nur Token).", + }, + }, + chat: { + disconnected: "Verbindung zum Gateway getrennt.", + refreshTitle: "Chat-Daten aktualisieren", + thinkingToggle: "Ausgabe des Assistenten ein-/ausblenden", + focusToggle: "Fokusmodus ein-/ausschalten (Seitenleiste + Kopfzeile ausblenden)", + onboardingDisabled: "Während der Einrichtung deaktiviert", + }, + languages: { + en: "English", + zhCN: "简体中文 (Vereinfachtes Chinesisch)", + zhTW: "繁體中文 (Traditionelles Chinesisch)", + ptBR: "Português (Brasilianisches Portugiesisch)", + de: "Deutsch", + }, +}; diff --git a/ui/src/i18n/locales/en.ts b/ui/src/i18n/locales/en.ts index dfba6d21fa8..1f790d7fb93 100644 --- a/ui/src/i18n/locales/en.ts +++ b/ui/src/i18n/locales/en.ts @@ -118,5 +118,6 @@ export const en: TranslationMap = { zhCN: "简体中文 (Simplified Chinese)", zhTW: "繁體中文 (Traditional Chinese)", ptBR: "Português (Brazilian Portuguese)", + de: "Deutsch (German)", }, }; diff --git a/ui/src/i18n/locales/pt-BR.ts b/ui/src/i18n/locales/pt-BR.ts index d7cb780bb5f..1c101272c50 100644 --- a/ui/src/i18n/locales/pt-BR.ts +++ b/ui/src/i18n/locales/pt-BR.ts @@ -120,5 +120,6 @@ export const pt_BR: TranslationMap = { zhCN: "简体中文 (Chinês Simplificado)", zhTW: "繁體中文 (Chinês Tradicional)", ptBR: "Português (Português Brasileiro)", + de: "Deutsch (Alemão)", }, }; diff --git a/ui/src/i18n/locales/zh-CN.ts b/ui/src/i18n/locales/zh-CN.ts index f6c7ce38c85..07ff0872701 100644 --- a/ui/src/i18n/locales/zh-CN.ts +++ b/ui/src/i18n/locales/zh-CN.ts @@ -117,5 +117,6 @@ export const zh_CN: TranslationMap = { zhCN: "简体中文 (简体中文)", zhTW: "繁體中文 (繁体中文)", ptBR: "Português (巴西葡萄牙语)", + de: "Deutsch (德语)", }, }; diff --git a/ui/src/i18n/locales/zh-TW.ts b/ui/src/i18n/locales/zh-TW.ts index 52f39b92398..8e60f3c917e 100644 --- a/ui/src/i18n/locales/zh-TW.ts +++ b/ui/src/i18n/locales/zh-TW.ts @@ -117,5 +117,6 @@ export const zh_TW: TranslationMap = { zhCN: "简体中文 (簡體中文)", zhTW: "繁體中文 (繁體中文)", ptBR: "Português (巴西葡萄牙語)", + de: "Deutsch (德語)", }, }; diff --git a/ui/src/ui/views/overview.ts b/ui/src/ui/views/overview.ts index 3c341df473b..b559fba4dab 100644 --- a/ui/src/ui/views/overview.ts +++ b/ui/src/ui/views/overview.ts @@ -1,6 +1,6 @@ import { html } from "lit"; import { ConnectErrorDetailCodes } from "../../../../src/gateway/protocol/connect-error-details.js"; -import { t, i18n, type Locale } from "../../i18n/index.ts"; +import { t, i18n, SUPPORTED_LOCALES, type Locale } from "../../i18n/index.ts"; import { buildExternalLinkRel, EXTERNAL_LINK_TARGET } from "../external-link.ts"; import { formatRelativeTimestamp, formatDurationHuman } from "../format.ts"; import type { GatewayHelloOk } from "../gateway.ts"; @@ -259,10 +259,10 @@ export function renderOverview(props: OverviewProps) { props.onSettingsChange({ ...props.settings, locale: v }); }} > - - - - + ${SUPPORTED_LOCALES.map((loc) => { + const key = loc.replace(/-([a-zA-Z])/g, (_, c) => c.toUpperCase()); + return html``; + })}