feat(i18n): localize configure command
This commit is contained in:
parent
badf499e2b
commit
5f6dc2d89b
@ -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,
|
||||
});
|
||||
}
|
||||
|
||||
@ -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);
|
||||
|
||||
@ -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,
|
||||
);
|
||||
|
||||
@ -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);
|
||||
|
||||
@ -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<ConfigureSectionChoice> {
|
||||
return guardCancel(
|
||||
await select<ConfigureSectionChoice>({
|
||||
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<ChannelsWizardMode> {
|
||||
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);
|
||||
|
||||
@ -6,7 +6,7 @@ const locales: Record<string, any> = {
|
||||
"zh-CN": zhCN,
|
||||
};
|
||||
|
||||
export function t(key: string): string {
|
||||
export function t(key: string, args?: Record<string, string | number>): 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;
|
||||
}
|
||||
|
||||
@ -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: "配置已更新。",
|
||||
}
|
||||
|
||||
Loading…
Reference in New Issue
Block a user