From 24e3586aaa987da8a12c96694ea9fe58ad2e547a Mon Sep 17 00:00:00 2001 From: w287346141 <287346141@qq.com> Date: Sun, 24 May 2026 08:03:49 +0800 Subject: [PATCH 1/2] Localize setup preset descriptions --- src/cli/ui/Wizard.tsx | 7 +++---- src/i18n/EN.ts | 14 ++++++++++++++ src/i18n/types.ts | 1 + src/i18n/zh-CN.ts | 14 ++++++++++++++ tests/wizard.test.tsx | 20 +++++++++++++++++++- 5 files changed, 51 insertions(+), 5 deletions(-) diff --git a/src/cli/ui/Wizard.tsx b/src/cli/ui/Wizard.tsx index 9706f8b..101330a 100644 --- a/src/cli/ui/Wizard.tsx +++ b/src/cli/ui/Wizard.tsx @@ -36,7 +36,6 @@ import { import type { LanguageCode } from "../../i18n/types.js"; import { type CatalogEntry, MCP_CATALOG } from "../../mcp/catalog.js"; import { MultiSelect, type SelectItem, SingleSelect } from "./Select.js"; -import { PRESET_DESCRIPTIONS } from "./presets.js"; import { ThemeProvider, useTheme } from "./theme/context.js"; import { type ThemeName, listThemeNames } from "./theme/tokens.js"; @@ -705,11 +704,11 @@ function SummaryLine({ label, value }: { label: string; value: string }) { ); } -function presetItems(): SelectItem[] { +export function presetItems(): SelectItem[] { return (["auto", "flash", "pro"] as const).map((name) => ({ value: name as PresetName, - label: `${name} — ${PRESET_DESCRIPTIONS[name].headline}`, - hint: PRESET_DESCRIPTIONS[name].cost, + label: `${name} — ${t(`wizard.presetDescriptions.${name}.headline`)}`, + hint: t(`wizard.presetDescriptions.${name}.cost`), })); } diff --git a/src/i18n/EN.ts b/src/i18n/EN.ts index 0ac9539..c1c849f 100644 --- a/src/i18n/EN.ts +++ b/src/i18n/EN.ts @@ -451,6 +451,20 @@ export const EN: TranslationSchema = { "github-light": "GitHub light", "high-contrast": "Accessibility", }, + presetDescriptions: { + auto: { + headline: "flash → pro on hard turns", + cost: "default · ~96% turns stay on flash · pro kicks in only when needed", + }, + flash: { + headline: "v4-flash always", + cost: "cheapest · predictable · /pro still works for a one-turn bump", + }, + pro: { + headline: "v4-pro always", + cost: "~3× flash (5/31 discount) / ~12× full price · for hard multi-turn work", + }, + }, reviewLabelTheme: "Theme", presetTitle: "Pick a preset", mcpTitle: "Which MCP servers should Carbon Code wire up for you?", diff --git a/src/i18n/types.ts b/src/i18n/types.ts index 8282c0a..afc06a0 100644 --- a/src/i18n/types.ts +++ b/src/i18n/types.ts @@ -306,6 +306,7 @@ export interface TranslationSchema { themeSampleHeading: string; themeFooter: string; themeCaption: Record; + presetDescriptions: Record<"auto" | "flash" | "pro", { headline: string; cost: string }>; reviewTitle: string; reviewLabelApiKey: string; reviewLabelLanguage: string; diff --git a/src/i18n/zh-CN.ts b/src/i18n/zh-CN.ts index 7afc2e3..bd4b7c0 100644 --- a/src/i18n/zh-CN.ts +++ b/src/i18n/zh-CN.ts @@ -438,6 +438,20 @@ export const zhCN: TranslationSchema = { "github-light": "GitHub 浅色", "high-contrast": "高对比度(无障碍)", }, + presetDescriptions: { + auto: { + headline: "困难轮次从 flash 升级到 pro", + cost: "默认 · 大多数轮次使用 flash · 需要时才启用 pro", + }, + flash: { + headline: "始终使用 v4-flash", + cost: "最便宜 · 可预测 · 仍可用 /pro 临时提升一轮", + }, + pro: { + headline: "始终使用 v4-pro", + cost: "约 3 倍 flash(5/31 折扣)/ 原价约 12 倍 · 适合困难的多轮工作", + }, + }, reviewLabelTheme: "主题", presetTitle: "选择预设", mcpTitle: "Carbon Code 要为你接入哪些 MCP 服务器?", diff --git a/tests/wizard.test.tsx b/tests/wizard.test.tsx index 552e2b4..1b2ff16 100644 --- a/tests/wizard.test.tsx +++ b/tests/wizard.test.tsx @@ -3,7 +3,7 @@ import { render } from "ink-testing-library"; import React from "react"; import { afterEach, describe, expect, it } from "vitest"; -import { Wizard, buildSpec, validateDeepSeekApiKey } from "../src/cli/ui/Wizard.js"; +import { Wizard, buildSpec, presetItems, validateDeepSeekApiKey } from "../src/cli/ui/Wizard.js"; import { setLanguageRuntime } from "../src/i18n/index.js"; import { parseMcpSpec } from "../src/mcp/spec.js"; @@ -66,6 +66,24 @@ describe("Wizard — first-launch language picker", () => { }); }); +describe("Wizard — localized preset descriptions", () => { + afterEach(() => { + setLanguageRuntime("EN"); + }); + + it("shows preset descriptions in zh-CN when runtime language is Simplified Chinese", () => { + setLanguageRuntime("zh-CN"); + const items = presetItems(); + + expect(items.map((item) => item.label)).toEqual([ + "auto — 困难轮次从 flash 升级到 pro", + "flash — 始终使用 v4-flash", + "pro — 始终使用 v4-pro", + ]); + expect(items.map((item) => item.hint).join("\n")).not.toContain("hard turns"); + }); +}); + describe("Wizard API-key validation", () => { it("accepts a key when DeepSeek auth check succeeds", async () => { const fetcher = async () => new Response(JSON.stringify({ data: [] }), { status: 200 }); From b00d8622f5690d06a32ddc785157baa00c284e0b Mon Sep 17 00:00:00 2001 From: w287346141 <287346141@qq.com> Date: Sun, 24 May 2026 08:23:18 +0800 Subject: [PATCH 2/2] Localize setup MCP catalog descriptions --- src/cli/ui/Wizard.tsx | 22 ++++++++++++++++------ src/i18n/EN.ts | 21 +++++++++++++++++++++ src/i18n/types.ts | 4 ++++ src/i18n/zh-CN.ts | 21 +++++++++++++++++++++ tests/wizard.test.tsx | 30 +++++++++++++++++++++++++++++- 5 files changed, 91 insertions(+), 7 deletions(-) diff --git a/src/cli/ui/Wizard.tsx b/src/cli/ui/Wizard.tsx index 101330a..7e310d3 100644 --- a/src/cli/ui/Wizard.tsx +++ b/src/cli/ui/Wizard.tsx @@ -596,10 +596,10 @@ function McpArgsStep({ return ( - {entry.summary} - {entry.note ? ( + {mcpCatalogSummary(entry)} + {mcpCatalogNote(entry) ? ( - {entry.note} + {mcpCatalogNote(entry)} ) : null} @@ -712,11 +712,12 @@ export function presetItems(): SelectItem[] { })); } -function mcpItems(): SelectItem[] { +export function mcpItems(): SelectItem[] { return MCP_CATALOG.map((entry) => { - const hintParts: string[] = [entry.summary]; + const hintParts: string[] = [mcpCatalogSummary(entry)]; if (entry.userArgs) hintParts.push(t("wizard.mcpUserArgsHint", { arg: entry.userArgs })); - if (entry.note) hintParts.push(entry.note); + const note = mcpCatalogNote(entry); + if (note) hintParts.push(note); return { value: entry.name, label: entry.name, @@ -725,6 +726,15 @@ function mcpItems(): SelectItem[] { }); } +function mcpCatalogSummary(entry: CatalogEntry): string { + return t(`wizard.mcpCatalog.${entry.name}.summary`); +} + +function mcpCatalogNote(entry: CatalogEntry): string | undefined { + if (!entry.note) return undefined; + return t(`wizard.mcpCatalog.${entry.name}.note`); +} + function placeholderFor(entry: CatalogEntry): string { if (entry.name === "filesystem") return "e.g. /tmp/carboncode-sandbox"; if (entry.name === "sqlite") return "e.g. ./notes.sqlite"; diff --git a/src/i18n/EN.ts b/src/i18n/EN.ts index c1c849f..1ffc769 100644 --- a/src/i18n/EN.ts +++ b/src/i18n/EN.ts @@ -465,6 +465,27 @@ export const EN: TranslationSchema = { cost: "~3× flash (5/31 discount) / ~12× full price · for hard multi-turn work", }, }, + mcpCatalog: { + filesystem: { + summary: "read/write/search files inside a sandboxed directory", + note: "the directory is a hard sandbox — the server refuses access outside it", + }, + memory: { + summary: "persistent key-value memory across sessions", + }, + github: { + summary: "read issues, PRs, code search (needs GITHUB_PERSONAL_ACCESS_TOKEN)", + note: "set GITHUB_PERSONAL_ACCESS_TOKEN in your env before spawning", + }, + puppeteer: { + summary: "browser automation — take screenshots, click, type", + note: "downloads Chromium on first run (~200 MB)", + }, + everything: { + summary: "official test server — exercises every MCP feature", + note: "useful for debugging your Carbon Code setup", + }, + }, reviewLabelTheme: "Theme", presetTitle: "Pick a preset", mcpTitle: "Which MCP servers should Carbon Code wire up for you?", diff --git a/src/i18n/types.ts b/src/i18n/types.ts index afc06a0..8da2faa 100644 --- a/src/i18n/types.ts +++ b/src/i18n/types.ts @@ -307,6 +307,10 @@ export interface TranslationSchema { themeFooter: string; themeCaption: Record; presetDescriptions: Record<"auto" | "flash" | "pro", { headline: string; cost: string }>; + mcpCatalog: Record< + "filesystem" | "memory" | "github" | "puppeteer" | "everything", + { summary: string; note?: string } + >; reviewTitle: string; reviewLabelApiKey: string; reviewLabelLanguage: string; diff --git a/src/i18n/zh-CN.ts b/src/i18n/zh-CN.ts index bd4b7c0..ad0a118 100644 --- a/src/i18n/zh-CN.ts +++ b/src/i18n/zh-CN.ts @@ -452,6 +452,27 @@ export const zhCN: TranslationSchema = { cost: "约 3 倍 flash(5/31 折扣)/ 原价约 12 倍 · 适合困难的多轮工作", }, }, + mcpCatalog: { + filesystem: { + summary: "在沙箱目录内读取、写入和搜索文件", + note: "该目录是严格沙箱 — 服务器会拒绝访问目录外的路径", + }, + memory: { + summary: "跨会话保存持久化键值记忆", + }, + github: { + summary: "读取 issues、PR 和代码搜索(需要 GITHUB_PERSONAL_ACCESS_TOKEN)", + note: "启动前请在环境变量中设置 GITHUB_PERSONAL_ACCESS_TOKEN", + }, + puppeteer: { + summary: "浏览器自动化 — 截图、点击、输入", + note: "首次运行会下载 Chromium(约 200 MB)", + }, + everything: { + summary: "官方测试服务器 — 覆盖所有 MCP 功能", + note: "适合调试 Carbon Code 设置", + }, + }, reviewLabelTheme: "主题", presetTitle: "选择预设", mcpTitle: "Carbon Code 要为你接入哪些 MCP 服务器?", diff --git a/tests/wizard.test.tsx b/tests/wizard.test.tsx index 1b2ff16..2378b1a 100644 --- a/tests/wizard.test.tsx +++ b/tests/wizard.test.tsx @@ -3,7 +3,13 @@ import { render } from "ink-testing-library"; import React from "react"; import { afterEach, describe, expect, it } from "vitest"; -import { Wizard, buildSpec, presetItems, validateDeepSeekApiKey } from "../src/cli/ui/Wizard.js"; +import { + Wizard, + buildSpec, + mcpItems, + presetItems, + validateDeepSeekApiKey, +} from "../src/cli/ui/Wizard.js"; import { setLanguageRuntime } from "../src/i18n/index.js"; import { parseMcpSpec } from "../src/mcp/spec.js"; @@ -84,6 +90,28 @@ describe("Wizard — localized preset descriptions", () => { }); }); +describe("Wizard — localized MCP catalog descriptions", () => { + afterEach(() => { + setLanguageRuntime("EN"); + }); + + it("shows MCP catalog summaries and notes in zh-CN", () => { + setLanguageRuntime("zh-CN"); + const items = mcpItems(); + const filesystem = items.find((item) => item.value === "filesystem"); + const github = items.find((item) => item.value === "github"); + + expect(filesystem?.hint).toContain("在沙箱目录内读取、写入和搜索文件"); + expect(filesystem?.hint).toContain("需要你提供 "); + expect(filesystem?.hint).toContain("服务器会拒绝访问目录外的路径"); + expect(github?.hint).toContain("读取 issues、PR 和代码搜索"); + expect(github?.hint).toContain("启动前请在环境变量中设置 GITHUB_PERSONAL_ACCESS_TOKEN"); + expect(items.map((item) => item.hint).join("\n")).not.toContain( + "read/write/search files inside a sandboxed directory", + ); + }); +}); + describe("Wizard API-key validation", () => { it("accepts a key when DeepSeek auth check succeeds", async () => { const fetcher = async () => new Response(JSON.stringify({ data: [] }), { status: 200 });