This commit is contained in:
前田信文 2026-01-30 06:49:50 +08:00 committed by GitHub
commit bb8c4ae7ab
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
4 changed files with 232 additions and 56 deletions

View File

@ -23,6 +23,30 @@ If you want a personal, single-user assistant that feels local, fast, and always
[Website](https://molt.bot) · [Docs](https://docs.molt.bot) · [Getting Started](https://docs.molt.bot/start/getting-started) · [Updating](https://docs.molt.bot/install/updating) · [Showcase](https://docs.molt.bot/start/showcase) · [FAQ](https://docs.molt.bot/start/faq) · [Wizard](https://docs.molt.bot/start/wizard) · [Nix](https://github.com/moltbot/nix-clawdbot) · [Docker](https://docs.molt.bot/install/docker) · [Discord](https://discord.gg/clawd)
## 繁體中文zh-TW
此 fork 目標是把 **CLI 安裝/初始化流程(`moltbot onboard` / wizard** 加入繁體中文介面。
### 啟用繁中介面
macOS / Linuxzsh/bash
```bash
export MOLTBOT_LANG=zh-TW
moltbot onboard --install-daemon
```
WindowsPowerShell
```powershell
$env:MOLTBOT_LANG = "zh-TW"
moltbot onboard --install-daemon
```
也支援從系統語系自動判斷(例如 `LANG=zh_TW.UTF-8`)。
---
Preferred setup: run the onboarding wizard (`moltbot onboard`). It walks through gateway, workspace, channels, and skills. The CLI wizard is the recommended path and works on **macOS, Linux, and Windows (via WSL2; strongly recommended)**.
Works with npm, pnpm, or bun.
New install? Start here: [Getting started](https://docs.molt.bot/start/getting-started)

View File

@ -61,11 +61,13 @@ export async function onboardCommand(opts: OnboardOptions, runtime: RuntimeEnv =
}
if (process.platform === "win32") {
const { resolveLocaleFromEnv, t } = await import("../i18n/i18n.js");
const locale = resolveLocaleFromEnv();
runtime.log(
[
"Windows detected.",
"WSL2 is strongly recommended; native Windows is untested and more problematic.",
"Guide: https://docs.molt.bot/windows",
t(locale, "windows.detected"),
t(locale, "windows.recommend"),
t(locale, "windows.guide"),
].join("\n"),
);
}

116
src/i18n/i18n.ts Normal file
View File

@ -0,0 +1,116 @@
export type MoltbotLocale = "en" | "zh-TW";
export function resolveLocaleFromEnv(env: NodeJS.ProcessEnv = process.env): MoltbotLocale {
const raw = (env.MOLTBOT_LANG ?? env.CLAWDBOT_LANG ?? env.LANG ?? env.LC_ALL ?? "").trim();
const norm = raw.replace("_", "-");
if (!norm) return "en";
// Common forms:
// - zh_TW.UTF-8
// - zh-TW
// - zh-Hant
// - zh-Hant-TW
const lower = norm.toLowerCase();
if (lower.startsWith("zh")) {
if (lower.includes("tw") || lower.includes("hant")) return "zh-TW";
// If user explicitly asked for Chinese, default to Traditional.
return "zh-TW";
}
return "en";
}
const dict: Record<MoltbotLocale, Record<string, string>> = {
en: {
"onboard.intro": "Moltbot onboarding",
"onboard.mode": "Onboarding mode",
"onboard.mode.quickstart": "QuickStart",
"onboard.mode.manual": "Manual",
"onboard.quickstart.title": "QuickStart",
"onboard.quickstart.keepExisting": "Keeping your current gateway settings:",
"onboard.quickstart.direct": "Direct to chat channels.",
"onboard.whatToSetup": "What do you want to set up?",
"onboard.localGateway": "Local gateway (this machine)",
"onboard.remoteGateway": "Remote gateway (info-only)",
"onboard.configExisting": "Existing config detected",
"onboard.configInvalid": "Invalid config",
"onboard.configIssues": "Config issues",
"onboard.configHandling": "Config handling",
"onboard.config.keep": "Use existing values",
"onboard.config.modify": "Update values",
"onboard.config.reset": "Reset",
"onboard.resetScope": "Reset scope",
"onboard.reset.config": "Config only",
"onboard.reset.configCredsSessions": "Config + creds + sessions",
"onboard.reset.full": "Full reset (config + creds + sessions + workspace)",
"onboard.workspaceDir": "Workspace directory",
"security.title": "Security",
"security.noteTitle": "Security warning — please read.",
"security.confirm": "I understand this is powerful and inherently risky. Continue?",
"windows.detected": "Windows detected.",
"windows.recommend": "WSL2 is strongly recommended; native Windows is untested and more problematic.",
"windows.guide": "Guide: https://docs.molt.bot/windows",
"onboard.skipChannels": "Skipping channel setup.",
"onboard.channelsTitle": "Channels",
"onboard.skipSkills": "Skipping skills setup.",
"onboard.skillsTitle": "Skills",
"onboard.remoteConfigured": "Remote gateway configured.",
},
"zh-TW": {
"onboard.intro": "Moltbot 初始設定Onboarding",
"onboard.mode": "安裝/初始化模式",
"onboard.mode.quickstart": "快速開始QuickStart",
"onboard.mode.manual": "手動設定Manual",
"onboard.quickstart.title": "快速開始QuickStart",
"onboard.quickstart.keepExisting": "保留你目前的 Gateway 設定:",
"onboard.quickstart.direct": "直接導向聊天管道設定。",
"onboard.whatToSetup": "你想要設定哪一種?",
"onboard.localGateway": "本機 Gateway這台機器",
"onboard.remoteGateway": "遠端 Gateway僅寫入設定/不做本機安裝)",
"onboard.configExisting": "偵測到既有設定",
"onboard.configInvalid": "設定檔不合法",
"onboard.configIssues": "設定問題",
"onboard.configHandling": "設定檔處理方式",
"onboard.config.keep": "沿用既有值",
"onboard.config.modify": "更新設定",
"onboard.config.reset": "重置",
"onboard.resetScope": "重置範圍",
"onboard.reset.config": "只重置設定檔",
"onboard.reset.configCredsSessions": "設定檔 + 憑證 + sessions",
"onboard.reset.full": "完整重置(設定檔 + 憑證 + sessions + workspace",
"onboard.workspaceDir": "Workspace 目錄",
"security.title": "安全性",
"security.noteTitle": "安全警告 — 請務必先閱讀",
"security.confirm": "我了解這很強大且有風險,仍要繼續嗎?",
"windows.detected": "偵測到 Windows。",
"windows.recommend": "強烈建議使用 WSL2原生 Windows 尚未完整測試,問題也比較多。",
"windows.guide": "指南https://docs.molt.bot/windows",
"onboard.skipChannels": "略過聊天管道設定。",
"onboard.channelsTitle": "聊天管道Channels",
"onboard.skipSkills": "略過技能Skills安裝。",
"onboard.skillsTitle": "技能Skills",
"onboard.remoteConfigured": "已完成遠端 Gateway 設定。",
},
};
export function t(locale: MoltbotLocale, key: string): string {
return dict[locale]?.[key] ?? dict.en[key] ?? key;
}

View File

@ -38,6 +38,7 @@ import { logConfigUpdated } from "../config/logging.js";
import type { RuntimeEnv } from "../runtime.js";
import { defaultRuntime } from "../runtime.js";
import { resolveUserPath } from "../utils.js";
import { resolveLocaleFromEnv, t } from "../i18n/i18n.js";
import { finalizeOnboardingWizard } from "./onboarding.finalize.js";
import { configureGatewayForOnboarding } from "./onboarding.gateway-config.js";
import type { QuickstartGatewayDefaults, WizardFlow } from "./onboarding.types.js";
@ -49,34 +50,61 @@ async function requireRiskAcknowledgement(params: {
}) {
if (params.opts.acceptRisk === true) return;
const locale = resolveLocaleFromEnv();
const securityBodyEn = [
"Security warning — please read.",
"",
"Moltbot is a hobby project and still in beta. Expect sharp edges.",
"This bot can read files and run actions if tools are enabled.",
"A bad prompt can trick it into doing unsafe things.",
"",
"If youre not comfortable with basic security and access control, dont run Moltbot.",
"Ask someone experienced to help before enabling tools or exposing it to the internet.",
"",
"Recommended baseline:",
"- Pairing/allowlists + mention gating.",
"- Sandbox + least-privilege tools.",
"- Keep secrets out of the agents reachable filesystem.",
"- Use the strongest available model for any bot with tools or untrusted inboxes.",
"",
"Run regularly:",
"moltbot security audit --deep",
"moltbot security audit --fix",
"",
"Must read: https://docs.molt.bot/gateway/security",
];
const securityBodyZhTw = [
"安全警告 — 請務必先閱讀。",
"",
"Moltbot 是一個興趣專案,目前仍在 beta請預期會有一些粗糙邊角。",
"當你啟用工具tools這個 bot 可能具備讀檔/執行動作的能力。",
"不良提示prompt可能誘導它做出不安全的操作。",
"",
"如果你不熟悉基本資安與存取控制,建議不要直接在生產環境跑 Moltbot。",
"在啟用工具或把它暴露到網路前,請找有經驗的人協助檢視設定。",
"",
"建議底線baseline",
"- Pairing/allowlists + mention gating配對/白名單 + 只回應@提及)",
"- Sandbox + 最小權限工具",
"- 不要把機密放在 agent 可讀到的檔案系統",
"- 有工具或會讀不受信任訊息時,請用你能用到的最強模型",
"",
"建議定期執行:",
"moltbot security audit --deep",
"moltbot security audit --fix",
"",
"必讀https://docs.molt.bot/gateway/security",
];
await params.prompter.note(
[
"Security warning — please read.",
"",
"Moltbot is a hobby project and still in beta. Expect sharp edges.",
"This bot can read files and run actions if tools are enabled.",
"A bad prompt can trick it into doing unsafe things.",
"",
"If youre not comfortable with basic security and access control, dont run Moltbot.",
"Ask someone experienced to help before enabling tools or exposing it to the internet.",
"",
"Recommended baseline:",
"- Pairing/allowlists + mention gating.",
"- Sandbox + least-privilege tools.",
"- Keep secrets out of the agents reachable filesystem.",
"- Use the strongest available model for any bot with tools or untrusted inboxes.",
"",
"Run regularly:",
"moltbot security audit --deep",
"moltbot security audit --fix",
"",
"Must read: https://docs.molt.bot/gateway/security",
].join("\n"),
"Security",
(locale === "zh-TW" ? securityBodyZhTw : securityBodyEn).join("\n"),
t(locale, "security.title"),
);
const ok = await params.prompter.confirm({
message: "I understand this is powerful and inherently risky. Continue?",
message: t(locale, "security.confirm"),
initialValue: false,
});
if (!ok) {
@ -89,23 +117,27 @@ export async function runOnboardingWizard(
runtime: RuntimeEnv = defaultRuntime,
prompter: WizardPrompter,
) {
const locale = resolveLocaleFromEnv();
printWizardHeader(runtime);
await prompter.intro("Moltbot onboarding");
await prompter.intro(t(locale, "onboard.intro"));
await requireRiskAcknowledgement({ opts, prompter });
const snapshot = await readConfigFileSnapshot();
let baseConfig: MoltbotConfig = snapshot.valid ? snapshot.config : {};
if (snapshot.exists && !snapshot.valid) {
await prompter.note(summarizeExistingConfig(baseConfig), "Invalid config");
await prompter.note(summarizeExistingConfig(baseConfig), t(locale, "onboard.configInvalid"));
if (snapshot.issues.length > 0) {
await prompter.note(
[
...snapshot.issues.map((iss) => `- ${iss.path}: ${iss.message}`),
"",
"Docs: https://docs.molt.bot/gateway/configuration",
locale === "zh-TW"
? "文件https://docs.molt.bot/gateway/configuration"
: "Docs: https://docs.molt.bot/gateway/configuration",
].join("\n"),
"Config issues",
t(locale, "onboard.configIssues"),
);
}
await prompter.outro(
@ -135,47 +167,49 @@ export async function runOnboardingWizard(
let flow: WizardFlow =
explicitFlow ??
((await prompter.select({
message: "Onboarding mode",
message: t(locale, "onboard.mode"),
options: [
{ value: "quickstart", label: "QuickStart", hint: quickstartHint },
{ value: "advanced", label: "Manual", hint: manualHint },
{ value: "quickstart", label: t(locale, "onboard.mode.quickstart"), hint: quickstartHint },
{ value: "advanced", label: t(locale, "onboard.mode.manual"), hint: manualHint },
],
initialValue: "quickstart",
})) as "quickstart" | "advanced");
if (opts.mode === "remote" && flow === "quickstart") {
await prompter.note(
"QuickStart only supports local gateways. Switching to Manual mode.",
"QuickStart",
locale === "zh-TW"
? "快速開始QuickStart只支援本機 Gateway將切換為手動模式Manual。"
: "QuickStart only supports local gateways. Switching to Manual mode.",
t(locale, "onboard.quickstart.title"),
);
flow = "advanced";
}
if (snapshot.exists) {
await prompter.note(summarizeExistingConfig(baseConfig), "Existing config detected");
await prompter.note(summarizeExistingConfig(baseConfig), t(locale, "onboard.configExisting"));
const action = (await prompter.select({
message: "Config handling",
message: t(locale, "onboard.configHandling"),
options: [
{ value: "keep", label: "Use existing values" },
{ value: "modify", label: "Update values" },
{ value: "reset", label: "Reset" },
{ value: "keep", label: t(locale, "onboard.config.keep") },
{ value: "modify", label: t(locale, "onboard.config.modify") },
{ value: "reset", label: t(locale, "onboard.config.reset") },
],
})) as "keep" | "modify" | "reset";
if (action === "reset") {
const workspaceDefault = baseConfig.agents?.defaults?.workspace ?? DEFAULT_WORKSPACE;
const resetScope = (await prompter.select({
message: "Reset scope",
message: t(locale, "onboard.resetScope"),
options: [
{ value: "config", label: "Config only" },
{ value: "config", label: t(locale, "onboard.reset.config") },
{
value: "config+creds+sessions",
label: "Config + creds + sessions",
label: t(locale, "onboard.reset.configCredsSessions"),
},
{
value: "full",
label: "Full reset (config + creds + sessions + workspace)",
label: t(locale, "onboard.reset.full"),
},
],
})) as ResetScope;
@ -254,7 +288,7 @@ export async function runOnboardingWizard(
};
const quickstartLines = quickstartGateway.hasExisting
? [
"Keeping your current gateway settings:",
t(locale, "onboard.quickstart.keepExisting"),
`Gateway port: ${quickstartGateway.port}`,
`Gateway bind: ${formatBind(quickstartGateway.bind)}`,
...(quickstartGateway.bind === "custom" && quickstartGateway.customBindHost
@ -262,16 +296,16 @@ export async function runOnboardingWizard(
: []),
`Gateway auth: ${formatAuth(quickstartGateway.authMode)}`,
`Tailscale exposure: ${formatTailscale(quickstartGateway.tailscaleMode)}`,
"Direct to chat channels.",
t(locale, "onboard.quickstart.direct"),
]
: [
`Gateway port: ${DEFAULT_GATEWAY_PORT}`,
"Gateway bind: Loopback (127.0.0.1)",
"Gateway auth: Token (default)",
"Tailscale exposure: Off",
"Direct to chat channels.",
t(locale, "onboard.quickstart.direct"),
];
await prompter.note(quickstartLines.join("\n"), "QuickStart");
await prompter.note(quickstartLines.join("\n"), t(locale, "onboard.quickstart.title"));
}
const localPort = resolveGatewayPort(baseConfig);
@ -294,18 +328,18 @@ export async function runOnboardingWizard(
(flow === "quickstart"
? "local"
: ((await prompter.select({
message: "What do you want to set up?",
message: t(locale, "onboard.whatToSetup"),
options: [
{
value: "local",
label: "Local gateway (this machine)",
label: t(locale, "onboard.localGateway"),
hint: localProbe.ok
? `Gateway reachable (${localUrl})`
: `No gateway detected (${localUrl})`,
},
{
value: "remote",
label: "Remote gateway (info-only)",
label: t(locale, "onboard.remoteGateway"),
hint: !remoteUrl
? "No remote URL configured yet"
: remoteProbe?.ok
@ -320,7 +354,7 @@ export async function runOnboardingWizard(
nextConfig = applyWizardMetadata(nextConfig, { command: "onboard", mode });
await writeConfigFile(nextConfig);
logConfigUpdated(runtime);
await prompter.outro("Remote gateway configured.");
await prompter.outro(t(locale, "onboard.remoteConfigured"));
return;
}
@ -329,7 +363,7 @@ export async function runOnboardingWizard(
(flow === "quickstart"
? (baseConfig.agents?.defaults?.workspace ?? DEFAULT_WORKSPACE)
: await prompter.text({
message: "Workspace directory",
message: t(locale, "onboard.workspaceDir"),
initialValue: baseConfig.agents?.defaults?.workspace ?? DEFAULT_WORKSPACE,
}));
@ -403,7 +437,7 @@ export async function runOnboardingWizard(
const settings = gateway.settings;
if (opts.skipChannels ?? opts.skipProviders) {
await prompter.note("Skipping channel setup.", "Channels");
await prompter.note(t(locale, "onboard.skipChannels"), t(locale, "onboard.channelsTitle"));
} else {
const quickstartAllowFromChannels =
flow === "quickstart"
@ -427,7 +461,7 @@ export async function runOnboardingWizard(
});
if (opts.skipSkills) {
await prompter.note("Skipping skills setup.", "Skills");
await prompter.note(t(locale, "onboard.skipSkills"), t(locale, "onboard.skillsTitle"));
} else {
nextConfig = await setupSkills(nextConfig, workspaceDir, runtime, prompter);
}