From 5f6dc2d89bdf5d99818ee21a151fa37d80233f0c Mon Sep 17 00:00:00 2001 From: "Xu, Jingrong" Date: Fri, 30 Jan 2026 19:51:47 +0800 Subject: [PATCH] feat(i18n): localize configure command --- src/commands/configure.daemon.ts | 31 +++++----- src/commands/configure.gateway-auth.ts | 3 +- src/commands/configure.gateway.ts | 71 ++++++++++----------- src/commands/configure.shared.ts | 43 ++++++------- src/commands/configure.wizard.ts | 69 ++++++++++----------- src/wizard/i18n.ts | 10 ++- src/wizard/locales/zh-CN.ts | 86 ++++++++++++++++++++++++++ 7 files changed, 200 insertions(+), 113 deletions(-) diff --git a/src/commands/configure.daemon.ts b/src/commands/configure.daemon.ts index c0431c9f1..84ff08f27 100644 --- a/src/commands/configure.daemon.ts +++ b/src/commands/configure.daemon.ts @@ -12,6 +12,7 @@ import { import { guardCancel } from "./onboard-helpers.js"; import { ensureSystemdUserLingerInteractive } from "./systemd-linger.js"; import { loadConfig } from "../config/config.js"; +import { t } from "../wizard/i18n.js"; export async function maybeInstallDaemon(params: { runtime: RuntimeEnv; @@ -27,11 +28,11 @@ export async function maybeInstallDaemon(params: { if (loaded) { const action = guardCancel( await select({ - message: "Gateway service already installed", + message: t("configure.daemon.alreadyInstalled"), options: [ - { value: "restart", label: "Restart" }, - { value: "reinstall", label: "Reinstall" }, - { value: "skip", label: "Skip" }, + { value: "restart", label: t("configure.daemon.restart") }, + { value: "reinstall", label: t("configure.daemon.reinstall") }, + { value: "skip", label: t("configure.daemon.skip") }, ], }), params.runtime, @@ -40,12 +41,12 @@ export async function maybeInstallDaemon(params: { await withProgress( { label: "Gateway service", indeterminate: true, delayMs: 0 }, async (progress) => { - progress.setLabel("Restarting Gateway service…"); + progress.setLabel(t("configure.daemon.restarting")); await service.restart({ env: process.env, stdout: process.stdout, }); - progress.setLabel("Gateway service restarted."); + progress.setLabel(t("configure.daemon.restarted")); }, ); shouldCheckLinger = true; @@ -56,9 +57,9 @@ export async function maybeInstallDaemon(params: { await withProgress( { label: "Gateway service", indeterminate: true, delayMs: 0 }, async (progress) => { - progress.setLabel("Uninstalling Gateway service…"); + progress.setLabel(t("configure.daemon.uninstalling")); await service.uninstall({ env: process.env, stdout: process.stdout }); - progress.setLabel("Gateway service uninstalled."); + progress.setLabel(t("configure.daemon.uninstalled")); }, ); } @@ -72,7 +73,7 @@ export async function maybeInstallDaemon(params: { } else { daemonRuntime = guardCancel( await select({ - message: "Gateway service runtime", + message: t("configure.daemon.selectRuntime"), options: GATEWAY_DAEMON_RUNTIME_OPTIONS, initialValue: DEFAULT_GATEWAY_DAEMON_RUNTIME, }), @@ -83,7 +84,7 @@ export async function maybeInstallDaemon(params: { await withProgress( { label: "Gateway service", indeterminate: true, delayMs: 0 }, async (progress) => { - progress.setLabel("Preparing Gateway service…"); + progress.setLabel(t("configure.daemon.preparing")); const cfg = loadConfig(); const { programArguments, workingDirectory, environment } = await buildGatewayInstallPlan({ @@ -95,7 +96,7 @@ export async function maybeInstallDaemon(params: { config: cfg, }); - progress.setLabel("Installing Gateway service…"); + progress.setLabel(t("configure.daemon.installing")); try { await service.install({ env: process.env, @@ -104,15 +105,15 @@ export async function maybeInstallDaemon(params: { workingDirectory, environment, }); - progress.setLabel("Gateway service installed."); + progress.setLabel(t("configure.daemon.installed")); } catch (err) { installError = err instanceof Error ? err.message : String(err); - progress.setLabel("Gateway service install failed."); + progress.setLabel(t("configure.daemon.installFailed")); } }, ); if (installError) { - note("Gateway service install failed: " + installError, "Gateway"); + note(t("configure.daemon.installFailedNote") + installError, "Gateway"); note(gatewayInstallErrorHint(), "Gateway"); return; } @@ -127,7 +128,7 @@ export async function maybeInstallDaemon(params: { note, }, reason: - "Linux installs use a systemd user service. Without lingering, systemd stops the user session on logout/idle and kills the Gateway.", + t("configure.daemon.lingerReason"), requireConfirm: true, }); } diff --git a/src/commands/configure.gateway-auth.ts b/src/commands/configure.gateway-auth.ts index 508ed6cf0..88b4c04bf 100644 --- a/src/commands/configure.gateway-auth.ts +++ b/src/commands/configure.gateway-auth.ts @@ -11,6 +11,7 @@ import { promptDefaultModel, promptModelAllowlist, } from "./model-picker.js"; +import { t } from "../wizard/i18n.js"; type GatewayAuthChoice = "token" | "password"; @@ -80,7 +81,7 @@ export async function promptAuthConfig( prompter, allowedKeys: anthropicOAuth ? ANTHROPIC_OAUTH_MODEL_KEYS : undefined, initialSelections: anthropicOAuth ? ["anthropic/claude-opus-4-5"] : undefined, - message: anthropicOAuth ? "Anthropic OAuth models" : undefined, + message: anthropicOAuth ? t("configure.auth.anthropicOAuthModels") : undefined, }); if (allowlistSelection.models) { next = applyModelAllowlist(next, allowlistSelection.models); diff --git a/src/commands/configure.gateway.ts b/src/commands/configure.gateway.ts index 9b66a7b34..8b209b49b 100644 --- a/src/commands/configure.gateway.ts +++ b/src/commands/configure.gateway.ts @@ -4,6 +4,7 @@ import { findTailscaleBinary } from "../infra/tailscale.js"; import type { RuntimeEnv } from "../runtime.js"; import { note } from "../terminal/note.js"; import { buildGatewayAuthConfig } from "./configure.gateway-auth.js"; +import { t } from "../wizard/i18n.js"; import { confirm, select, text } from "./configure.shared.js"; import { guardCancel, randomToken } from "./onboard-helpers.js"; @@ -19,9 +20,9 @@ export async function promptGatewayConfig( }> { const portRaw = guardCancel( await text({ - message: "Gateway port", + message: t("onboarding.gatewayConfig.port"), initialValue: String(resolveGatewayPort(cfg)), - validate: (value) => (Number.isFinite(Number(value)) ? undefined : "Invalid port"), + validate: (value) => (Number.isFinite(Number(value)) ? undefined : t("onboarding.gatewayConfig.invalidPort")), }), runtime, ); @@ -29,31 +30,31 @@ export async function promptGatewayConfig( let bind = guardCancel( await select({ - message: "Gateway bind mode", + message: t("onboarding.gatewayConfig.bind"), options: [ { value: "loopback", - label: "Loopback (Local only)", - hint: "Bind to 127.0.0.1 - secure, local-only access", + label: t("onboarding.gateway.bindLoopback"), + hint: t("onboarding.gatewayConfig.bindLoopbackHint") || "Bind to 127.0.0.1 - secure, local-only access", }, { value: "tailnet", - label: "Tailnet (Tailscale IP)", - hint: "Bind to your Tailscale IP only (100.x.x.x)", + label: t("onboarding.gateway.bindTailnet"), + hint: t("onboarding.gatewayConfig.bindTailnetHint") || "Bind to your Tailscale IP only (100.x.x.x)", }, { value: "auto", - label: "Auto (Loopback → LAN)", + label: t("onboarding.gateway.bindAuto"), hint: "Prefer loopback; fall back to all interfaces if unavailable", }, { value: "lan", - label: "LAN (All interfaces)", - hint: "Bind to 0.0.0.0 - accessible from anywhere on your network", + label: t("onboarding.gateway.bindLan"), + hint: t("onboarding.gatewayConfig.bindLanHint") || "Bind to 0.0.0.0 - accessible from anywhere on your network", }, { value: "custom", - label: "Custom IP", + label: t("onboarding.gateway.bindCustom"), hint: "Specify a specific IP address, with 0.0.0.0 fallback if unavailable", }, ], @@ -65,13 +66,13 @@ export async function promptGatewayConfig( if (bind === "custom") { const input = guardCancel( await text({ - message: "Custom IP address", + message: t("onboarding.gatewayConfig.customIp"), placeholder: "192.168.1.100", validate: (value) => { - if (!value) return "IP address is required for custom bind mode"; + if (!value) return t("onboarding.gatewayConfig.customIpRequired"); const trimmed = value.trim(); const parts = trimmed.split("."); - if (parts.length !== 4) return "Invalid IPv4 address (e.g., 192.168.1.100)"; + if (parts.length !== 4) return t("onboarding.gatewayConfig.invalidIp") + " (e.g., 192.168.1.100)"; if ( parts.every((part) => { const n = parseInt(part, 10); @@ -79,7 +80,7 @@ export async function promptGatewayConfig( }) ) return undefined; - return "Invalid IPv4 address (each octet must be 0-255)"; + return t("onboarding.gatewayConfig.invalidIpOctet"); }, }), runtime, @@ -89,10 +90,10 @@ export async function promptGatewayConfig( let authMode = guardCancel( await select({ - message: "Gateway auth", + message: t("onboarding.gatewayConfig.auth"), options: [ - { value: "token", label: "Token", hint: "Recommended default" }, - { value: "password", label: "Password" }, + { value: "token", label: t("onboarding.gatewayConfig.authToken"), hint: t("onboarding.gatewayConfig.authTokenHint") }, + { value: "password", label: t("onboarding.gatewayConfig.authPassword") }, ], initialValue: "token", }), @@ -101,18 +102,18 @@ export async function promptGatewayConfig( const tailscaleMode = guardCancel( await select({ - message: "Tailscale exposure", + message: t("onboarding.gatewayConfig.tsExposure"), options: [ - { value: "off", label: "Off", hint: "No Tailscale exposure" }, + { value: "off", label: t("onboarding.gatewayConfig.tsOff"), hint: t("onboarding.gatewayConfig.tsOffHint") }, { value: "serve", - label: "Serve", - hint: "Private HTTPS for your tailnet (devices on Tailscale)", + label: t("onboarding.gatewayConfig.tsServe"), + hint: t("onboarding.gatewayConfig.tsServeHint"), }, { value: "funnel", - label: "Funnel", - hint: "Public HTTPS via Tailscale Funnel (internet)", + label: t("onboarding.gatewayConfig.tsFunnel"), + hint: t("onboarding.gatewayConfig.tsFunnelHint"), }, ], }), @@ -124,14 +125,8 @@ export async function promptGatewayConfig( const tailscaleBin = await findTailscaleBinary(); if (!tailscaleBin) { note( - [ - "Tailscale binary not found in PATH or /Applications.", - "Ensure Tailscale is installed from:", - " https://tailscale.com/download/mac", - "", - "You can continue setup, but serve/funnel will fail at runtime.", - ].join("\n"), - "Tailscale Warning", + t("onboarding.gatewayConfig.tsNotFound"), + t("onboarding.gatewayConfig.tsWarningTitle"), ); } } @@ -147,7 +142,7 @@ export async function promptGatewayConfig( tailscaleResetOnExit = Boolean( guardCancel( await confirm({ - message: "Reset Tailscale serve/funnel on exit?", + message: t("onboarding.gatewayConfig.tsResetConfirm"), initialValue: false, }), runtime, @@ -156,12 +151,12 @@ export async function promptGatewayConfig( } if (tailscaleMode !== "off" && bind !== "loopback") { - note("Tailscale requires bind=loopback. Adjusting bind to loopback.", "Note"); + note(t("onboarding.gatewayConfig.tsAdjustBind"), "Note"); bind = "loopback"; } if (tailscaleMode === "funnel" && authMode !== "password") { - note("Tailscale funnel requires password auth.", "Note"); + note(t("onboarding.gatewayConfig.tsFunnelAuth"), "Note"); authMode = "password"; } @@ -172,7 +167,7 @@ export async function promptGatewayConfig( if (authMode === "token") { const tokenInput = guardCancel( await text({ - message: "Gateway token (blank to generate)", + message: t("onboarding.gatewayConfig.tokenPlaceholder"), initialValue: randomToken(), }), runtime, @@ -183,8 +178,8 @@ export async function promptGatewayConfig( if (authMode === "password") { const password = guardCancel( await text({ - message: "Gateway password", - validate: (value) => (value?.trim() ? undefined : "Required"), + message: t("onboarding.gatewayConfig.passwordLabel"), + validate: (value) => (value?.trim() ? undefined : t("onboarding.gatewayConfig.passwordRequired")), }), runtime, ); diff --git a/src/commands/configure.shared.ts b/src/commands/configure.shared.ts index bc89529d8..35002a745 100644 --- a/src/commands/configure.shared.ts +++ b/src/commands/configure.shared.ts @@ -7,6 +7,7 @@ import { } from "@clack/prompts"; import { stylePromptHint, stylePromptMessage, stylePromptTitle } from "../terminal/prompt-style.js"; +import { t } from "../wizard/i18n.js"; export const CONFIGURE_WIZARD_SECTIONS = [ "workspace", @@ -33,27 +34,27 @@ export const CONFIGURE_SECTION_OPTIONS: Array<{ label: string; hint: string; }> = [ - { value: "workspace", label: "Workspace", hint: "Set workspace + sessions" }, - { value: "model", label: "Model", hint: "Pick provider + credentials" }, - { value: "web", label: "Web tools", hint: "Configure Brave search + fetch" }, - { value: "gateway", label: "Gateway", hint: "Port, bind, auth, tailscale" }, - { - value: "daemon", - label: "Daemon", - hint: "Install/manage the background service", - }, - { - value: "channels", - label: "Channels", - hint: "Link WhatsApp/Telegram/etc and defaults", - }, - { value: "skills", label: "Skills", hint: "Install/enable workspace skills" }, - { - value: "health", - label: "Health check", - hint: "Run gateway + channel checks", - }, -]; + { value: "workspace", label: t("configure.sections.workspace"), hint: t("configure.sections.workspaceHint") }, + { value: "model", label: t("configure.sections.model"), hint: t("configure.sections.modelHint") }, + { value: "web", label: t("configure.sections.web"), hint: t("configure.sections.webHint") }, + { value: "gateway", label: t("configure.sections.gateway"), hint: t("configure.sections.gatewayHint") }, + { + value: "daemon", + label: t("configure.sections.daemon"), + hint: t("configure.sections.daemonHint"), + }, + { + value: "channels", + label: t("configure.sections.channels"), + hint: t("configure.sections.channelsHint"), + }, + { value: "skills", label: t("configure.sections.skills"), hint: t("configure.sections.skillsHint") }, + { + value: "health", + label: t("configure.sections.health"), + hint: t("configure.sections.healthHint"), + }, + ]; export const intro = (message: string) => clackIntro(stylePromptTitle(message) ?? message); export const outro = (message: string) => clackOutro(stylePromptTitle(message) ?? message); diff --git a/src/commands/configure.wizard.ts b/src/commands/configure.wizard.ts index 505fb7760..23159bc22 100644 --- a/src/commands/configure.wizard.ts +++ b/src/commands/configure.wizard.ts @@ -9,6 +9,7 @@ import { note } from "../terminal/note.js"; import { resolveUserPath } from "../utils.js"; import { createClackPrompter } from "../wizard/clack-prompter.js"; import { WizardCancelledError } from "../wizard/prompts.js"; +import { t } from "../wizard/i18n.js"; import { removeChannelConfigWizard } from "./configure.channels.js"; import { maybeInstallDaemon } from "./configure.daemon.js"; import { promptGatewayConfig } from "./configure.gateway.js"; @@ -51,13 +52,13 @@ async function promptConfigureSection( ): Promise { return guardCancel( await select({ - message: "Select sections to configure", + message: t("configure.sections.title"), options: [ ...CONFIGURE_SECTION_OPTIONS, { value: "__continue", - label: "Continue", - hint: hasSelection ? "Done" : "Skip for now", + label: t("configure.sections.continue"), + hint: hasSelection ? t("configure.sections.continueHint") : t("configure.sections.skipHint"), }, ], initialValue: CONFIGURE_SECTION_OPTIONS[0]?.value, @@ -69,17 +70,17 @@ async function promptConfigureSection( async function promptChannelMode(runtime: RuntimeEnv): Promise { return guardCancel( await select({ - message: "Channels", + message: t("configure.sections.channels"), options: [ { value: "configure", - label: "Configure/link", - hint: "Add/update channels; disable unselected accounts", + label: t("configure.channels.configure"), + hint: t("configure.channels.configureHint"), }, { value: "remove", - label: "Remove channel config", - hint: "Delete channel tokens/settings from openclaw.json", + label: t("configure.channels.remove"), + hint: t("configure.channels.removeHint"), }, ], initialValue: "configure", @@ -107,7 +108,7 @@ async function promptWebToolsConfig( const enableSearch = guardCancel( await confirm({ - message: "Enable web_search (Brave Search)?", + message: t("configure.web.enableSearch"), initialValue: existingSearch?.enabled ?? hasSearchKey, }), runtime, @@ -122,9 +123,9 @@ async function promptWebToolsConfig( const keyInput = guardCancel( await text({ message: hasSearchKey - ? "Brave Search API key (leave blank to keep current or use BRAVE_API_KEY)" - : "Brave Search API key (paste it here; leave blank to use BRAVE_API_KEY)", - placeholder: hasSearchKey ? "Leave blank to keep current" : "BSA...", + ? t("configure.web.keyPrompt") + : t("configure.web.keyPromptEmpty"), + placeholder: hasSearchKey ? t("configure.web.placeholderKey") : t("configure.web.placeholderKeyEmpty"), }), runtime, ); @@ -133,19 +134,15 @@ async function promptWebToolsConfig( nextSearch = { ...nextSearch, apiKey: key }; } else if (!hasSearchKey) { note( - [ - "No key stored yet, so web_search will stay unavailable.", - "Store a key here or set BRAVE_API_KEY in the Gateway environment.", - "Docs: https://docs.openclaw.ai/tools/web", - ].join("\n"), - "Web search", + t("configure.web.noKeyWarning"), + t("configure.sections.web"), ); } } const enableFetch = guardCancel( await confirm({ - message: "Enable web_fetch (keyless HTTP fetch)?", + message: t("configure.web.enableFetch"), initialValue: existingFetch?.enabled ?? true, }), runtime, @@ -175,7 +172,7 @@ export async function runConfigureWizard( ) { try { printWizardHeader(runtime); - intro(opts.command === "update" ? "OpenClaw update wizard" : "OpenClaw configure"); + intro(opts.command === "update" ? t("configure.updateTitle") : t("configure.title")); const prompter = createClackPrompter(); const snapshot = await readConfigFileSnapshot(); @@ -212,30 +209,30 @@ export async function runConfigureWizard( const remoteUrl = baseConfig.gateway?.remote?.url?.trim() ?? ""; const remoteProbe = remoteUrl ? await probeGatewayReachable({ - url: remoteUrl, - token: baseConfig.gateway?.remote?.token, - }) + url: remoteUrl, + token: baseConfig.gateway?.remote?.token, + }) : null; const mode = guardCancel( await select({ - message: "Where will the Gateway run?", + message: t("configure.gateway.modeSelect"), options: [ { value: "local", - label: "Local (this machine)", + label: t("configure.gateway.local"), hint: localProbe.ok - ? `Gateway reachable (${localUrl})` - : `No gateway detected (${localUrl})`, + ? t("configure.gateway.localHintReachable", { url: localUrl }) + : t("configure.gateway.localHintMissing", { url: localUrl }), }, { value: "remote", - label: "Remote (info-only)", + label: t("configure.gateway.remote"), hint: !remoteUrl - ? "No remote URL configured yet" + ? t("configure.gateway.remoteHintNoUrl") : remoteProbe?.ok - ? `Gateway reachable (${remoteUrl})` - : `Configured but unreachable (${remoteUrl})`, + ? t("configure.gateway.remoteHintReachable", { url: remoteUrl }) + : t("configure.gateway.remoteHintConfigured", { url: remoteUrl }), }, ], }), @@ -250,7 +247,7 @@ export async function runConfigureWizard( }); await writeConfigFile(remoteConfig); logConfigUpdated(runtime); - outro("Remote gateway configured."); + outro(t("configure.gateway.remoteConfigured")); return; } @@ -288,7 +285,7 @@ export async function runConfigureWizard( if (opts.sections) { const selected = opts.sections; if (!selected || selected.length === 0) { - outro("No changes selected."); + outro(t("configure.gateway.noChanges")); return; } @@ -530,10 +527,10 @@ export async function runConfigureWizard( if (!ranSection) { if (didSetGatewayMode) { await persistConfig(); - outro("Gateway mode set to local."); + outro(t("configure.gateway.modeSetLocal")); return; } - outro("No changes selected."); + outro(t("configure.gateway.noChanges")); return; } } @@ -582,7 +579,7 @@ export async function runConfigureWizard( "Control UI", ); - outro("Configure complete."); + outro(t("configure.gateway.configureComplete")); } catch (err) { if (err instanceof WizardCancelledError) { runtime.exit(0); diff --git a/src/wizard/i18n.ts b/src/wizard/i18n.ts index c4472b6fd..adf64f3d2 100644 --- a/src/wizard/i18n.ts +++ b/src/wizard/i18n.ts @@ -6,7 +6,7 @@ const locales: Record = { "zh-CN": zhCN, }; -export function t(key: string): string { +export function t(key: string, args?: Record): string { const keys = key.split("."); let value = locales[currentLocale]; for (const k of keys) { @@ -16,5 +16,11 @@ export function t(key: string): string { return key; } } - return typeof value === "string" ? value : key; + let str = typeof value === "string" ? value : key; + if (args) { + for (const [k, v] of Object.entries(args)) { + str = str.replace(new RegExp(`{${k}}`, "g"), String(v)); + } + } + return str; } diff --git a/src/wizard/locales/zh-CN.ts b/src/wizard/locales/zh-CN.ts index fcd8d13fe..f436f23fd 100644 --- a/src/wizard/locales/zh-CN.ts +++ b/src/wizard/locales/zh-CN.ts @@ -209,6 +209,92 @@ export const zhCN = { passwordRequired: "必须填写密码", } }, + configure: { + title: "OpenClaw 配置", + updateTitle: "OpenClaw 更新向导", + auth: { + anthropicOAuthModels: "Anthropic OAuth 模型", + }, + sections: { + title: "选择要配置的部分", + continue: "继续", + continueHint: "完成", + skipHint: "暂时跳过", + workspace: "工作区 (Workspace)", + workspaceHint: "设置工作区 + 会话", + model: "模型 (Model)", + modelHint: "选择提供商 + 凭证", + web: "Web 工具", + webHint: "配置 Brave 搜索 + 用于抓取", + gateway: "网关 (Gateway)", + gatewayHint: "端口、绑定、认证、Tailscale", + daemon: "后台服务 (Daemon)", + daemonHint: "安装/管理后台服务", + channels: "渠道 (Channels)", + channelsHint: "连接 WhatsApp/Telegram 等", + skills: "技能 (Skills)", + skillsHint: "安装/启用工作区技能", + health: "健康检查 (Health check)", + healthHint: "运行网关 + 渠道检查", + }, + gateway: { + modeSelect: "网关将在哪里运行?", + local: "本地 (这台机器)", + localHintReachable: "网关可达 ({url})", + localHintMissing: "未检测到网关 ({url})", + remote: "远程 (仅信息)", + remoteHintNoUrl: "尚未配置远程 URL", + remoteHintReachable: "网关可达 ({url})", + remoteHintConfigured: "已配置但无法连接 ({url})", + remoteConfigured: "远程网关已配置。", + modeSetLocal: "网关模式已设置为本地。", + noChanges: "未选择任何更改。", + configureComplete: "配置完成。", + }, + channels: { + modeTitle: "渠道配置模式", + configure: "配置/连接", + configureHint: "添加/更新渠道;禁用未选中的账户", + remove: "移除渠道配置", + removeHint: "从 openclaw.json 中删除渠道令牌/设置", + }, + web: { + title: "网页搜索", + desc: [ + "网页搜索允许您的 Agent 使用 `web_search` 工具在线查找信息。", + "它需要 Brave Search API 密钥(您可以将其存储在配置中或在网关环境中设置 BRAVE_API_KEY)。", + "文档: https://docs.openclaw.ai/tools/web", + ].join("\n"), + enableSearch: "启用 web_search (Brave Search)?", + keyPrompt: "Brave Search API 密钥 (留空保留当前值或使用 BRAVE_API_KEY)", + keyPromptEmpty: "Brave Search API 密钥 (粘贴到这里;留空使用 BRAVE_API_KEY)", + placeholderKey: "留空以保留当前设置", + placeholderKeyEmpty: "BSA...", + noKeyWarning: [ + "尚未存储密钥,因此 web_search 将不可用。", + "请在此处存储密钥或在网关环境中设置 BRAVE_API_KEY。", + "文档: https://docs.openclaw.ai/tools/web", + ].join("\n"), + enableFetch: "启用 web_fetch (无密钥 HTTP 抓取)?", + }, + daemon: { + alreadyInstalled: "网关服务已安装", + restart: "重启 (Restart)", + reinstall: "重新安装 (Reinstall)", + skip: "跳过 (Skip)", + restarting: "正在重启网关服务…", + restarted: "网关服务已重启。", + uninstalling: "正在卸载网关服务…", + uninstalled: "网关服务已卸载。", + selectRuntime: "网关服务运行时", + preparing: "正在准备网关服务…", + installing: "正在安装网关服务…", + installed: "网关服务已安装。", + installFailed: "网关服务安装失败。", + installFailedNote: "网关服务安装失败: ", + lingerReason: "Linux 安装使用 systemd 用户服务。如果不启用 lingering,systemd 会在注销/空闲时停止用户会话并终止网关。", + } + }, common: { configUpdated: "配置已更新。", }