From 24f64eaf145ce3f7376a641e6808f497d677129b Mon Sep 17 00:00:00 2001 From: huangruiteng Date: Thu, 1 Oct 2026 22:22:38 +0800 Subject: [PATCH 1/2] fix(workspace): localize the local source and status-only route labels MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit The English Workspace showed the local control-plane source as 本机 and the status-only chat route as 仅查状态 because both labels were hard-coded. Render them through the existing workspace dictionary; Chinese text is unchanged. The locale browser smoke now asserts both directions. Signed-off-by: huangruiteng --- .../dashboard/smoke/workspace-locale-browser-smoke.mjs | 4 ++++ .../dashboard/src/features/personal-workspace/i18n.tsx | 4 ++++ .../features/personal-workspace/status-source-switcher.tsx | 2 +- apps/presentation/dashboard/src/views/dashboard-page.tsx | 4 ++-- 4 files changed, 11 insertions(+), 3 deletions(-) diff --git a/apps/presentation/dashboard/smoke/workspace-locale-browser-smoke.mjs b/apps/presentation/dashboard/smoke/workspace-locale-browser-smoke.mjs index 4d5064ef18..f7d4f7be0c 100644 --- a/apps/presentation/dashboard/smoke/workspace-locale-browser-smoke.mjs +++ b/apps/presentation/dashboard/smoke/workspace-locale-browser-smoke.mjs @@ -67,6 +67,9 @@ export async function verifyWorkspaceLocales({ browser, url, installApi, outputD await installApi(page); await page.goto(url, { waitUntil: "networkidle" }); await page.getByTestId("personal-goal-home").waitFor(); + assert.match(await page.getByRole("region", { name: "Control plane source" }).innerText(), /This machine/); + const englishText = await page.locator("body").innerText(); + for (const chinese of ["本机", "仅查状态"]) assert.ok(!englishText.includes(chinese), `English Workspace shows ${chinese}`); await page.screenshot({ path: resolve(outputDir, "locale-default-english.png") }); await page.setViewportSize({ width: 390, height: 844 }); await page.locator(".personal-channel-scroll").evaluate((element) => { element.scrollTop = 0; }); @@ -86,6 +89,7 @@ export async function verifyWorkspaceLocales({ browser, url, installApi, outputD await page.reload({ waitUntil: "networkidle" }); await page.getByTestId("personal-goal-home").waitFor(); assert.equal(await page.locator("html").getAttribute("lang"), "zh-CN"); + assert.match(await page.getByRole("region", { name: "控制面来源" }).innerText(), /本机/); await page.screenshot({ path: resolve(outputDir, "locale-saved-chinese.png") }); await page.getByRole("button", { name: "设置", exact: true }).click(); await page.getByRole("button", { name: /语言/ }).click(); diff --git a/apps/presentation/dashboard/src/features/personal-workspace/i18n.tsx b/apps/presentation/dashboard/src/features/personal-workspace/i18n.tsx index bde6651f32..f3c9aff342 100644 --- a/apps/presentation/dashboard/src/features/personal-workspace/i18n.tsx +++ b/apps/presentation/dashboard/src/features/personal-workspace/i18n.tsx @@ -416,6 +416,7 @@ const en = { "header.goalView": "Goal view", "header.live": "Live", "header.manager": "LoopX Manager", + "header.statusOnlyAgent": "Status only", "header.managerModelSettings": "Change steward model and reasoning effort", "header.managerDescription": "Your personal workspace across Goals", "header.managerRuntime": "{profile} · {sandbox}", @@ -843,6 +844,7 @@ const en = { "source.hostPlaceholder": "Enter or search for a Host", "source.invalid": "The SSH source settings are invalid.", "source.loadingHosts": "Loading…", + "source.local": "This machine", "source.localInteractive": "Local interactive", "source.localPort": "Local port", "source.manual": "Manual URL", @@ -1628,6 +1630,7 @@ const zhCN: Record = { "header.goalView": "Goal 视图", "header.live": "实时", "header.manager": "LoopX 管家", + "header.statusOnlyAgent": "仅查状态", "header.managerModelSettings": "调整管家模型与思考深度", "header.managerDescription": "跨 Goal 的个人工作入口", "header.managerRuntime": "{profile} · {sandbox}", @@ -2055,6 +2058,7 @@ const zhCN: Record = { "source.hostPlaceholder": "输入或搜索 Host", "source.invalid": "SSH 来源参数无效。", "source.loadingHosts": "正在读取…", + "source.local": "本机", "source.localInteractive": "本机交互", "source.localPort": "本地端口", "source.manual": "手动 URL", diff --git a/apps/presentation/dashboard/src/features/personal-workspace/status-source-switcher.tsx b/apps/presentation/dashboard/src/features/personal-workspace/status-source-switcher.tsx index 5596a4f1cb..51e306cff8 100644 --- a/apps/presentation/dashboard/src/features/personal-workspace/status-source-switcher.tsx +++ b/apps/presentation/dashboard/src/features/personal-workspace/status-source-switcher.tsx @@ -49,7 +49,7 @@ export function StatusSourceSwitcher({ const [statusUrl, setStatusUrl] = useState(""); const quickAddPrefix = "configured:"; const sourceOptions = [ - ...sources.map((source) => ({ label: source.label, value: source.id })), + ...sources.map((source) => ({ label: source.kind === "local" ? t("source.local") : source.label, value: source.id })), ...configuredHosts .filter((host) => !sources.some((source) => source.label === host.alias)) .map((host) => ({ diff --git a/apps/presentation/dashboard/src/views/dashboard-page.tsx b/apps/presentation/dashboard/src/views/dashboard-page.tsx index 627dca6c39..e19066e5df 100644 --- a/apps/presentation/dashboard/src/views/dashboard-page.tsx +++ b/apps/presentation/dashboard/src/views/dashboard-page.tsx @@ -1377,7 +1377,7 @@ function PersonalGoalHome({ capability: "不调用模型", adapterKind: "status_projection", interrupt: false, - label: "仅查状态", + label: t("header.statusOnlyAgent"), resume: true, statusLabel: "只读", streaming: false, @@ -2171,7 +2171,7 @@ function PersonalGoalHome({ const answer = personalManagerSnapshot(targetQuestionModel); const usesStatusOnlyRoute = selectedRoute.agentId === "status-only"; const answerMessageId = appendManagerAssistantMessage(targetContextId, { - agentLabel: usesStatusOnlyRoute ? "仅查状态" : "LoopX 管家", + agentLabel: usesStatusOnlyRoute ? t("header.statusOnlyAgent") : t("header.manager"), lines: answer.lines.slice(0, 3), sourceLabel: usesStatusOnlyRoute ? "LoopX 状态投影 · 仅查状态" : "LoopX 状态投影", text: answer.text, From 94930e2f1982af157635f540a388dcb29aff8fac Mon Sep 17 00:00:00 2001 From: huangruiteng Date: Fri, 2 Oct 2026 02:14:35 +0800 Subject: [PATCH 2/2] fix(app): localize status-only route description and response identity Signed-off-by: huangruiteng --- .../smoke/workspace-locale-browser-smoke.mjs | 11 ++++++++++- .../src/features/personal-workspace/i18n.tsx | 4 ++++ .../dashboard/src/views/dashboard-page.tsx | 6 +++--- 3 files changed, 17 insertions(+), 4 deletions(-) diff --git a/apps/presentation/dashboard/smoke/workspace-locale-browser-smoke.mjs b/apps/presentation/dashboard/smoke/workspace-locale-browser-smoke.mjs index f7d4f7be0c..a996614110 100644 --- a/apps/presentation/dashboard/smoke/workspace-locale-browser-smoke.mjs +++ b/apps/presentation/dashboard/smoke/workspace-locale-browser-smoke.mjs @@ -64,13 +64,22 @@ export async function verifyWorkspaceLocales({ browser, url, installApi, outputD const page = await browser.newPage({ locale: "en-US", viewport: { width: 1440, height: 960 } }); try { - await installApi(page); + const api = await installApi(page); await page.goto(url, { waitUntil: "networkidle" }); await page.getByTestId("personal-goal-home").waitFor(); assert.match(await page.getByRole("region", { name: "Control plane source" }).innerText(), /This machine/); const englishText = await page.locator("body").innerText(); for (const chinese of ["本机", "仅查状态"]) assert.ok(!englishText.includes(chinese), `English Workspace shows ${chinese}`); await page.screenshot({ path: resolve(outputDir, "locale-default-english.png") }); + await page.getByRole("combobox", { name: "Select chat runtime", exact: true }).click(); + const statusOnly = page.getByRole("option", { name: "Status only", exact: true }); + assert.ok(await statusOnly.isVisible(), "English Runtime picker shows the translated status-only route"); + await statusOnly.click(); + await page.getByLabel("Send a message to LoopX").fill("Show the current state."); + await page.getByRole("button", { name: "Send", exact: true }).click(); + await page.locator(".personal-manager-conversation-messages article.is-assistant > strong").getByText("Status only", { exact: true }).waitFor(); + assert.equal(api.turnRequests.length, 0, "Localizing the status-only route must not start an Agent Turn"); + assert.ok(!(await page.locator("body").innerText()).includes("LoopX 状态投影"), "The status-only reply source follows the selected language"); await page.setViewportSize({ width: 390, height: 844 }); await page.locator(".personal-channel-scroll").evaluate((element) => { element.scrollTop = 0; }); const clippedStats = await page.locator(".personal-digest-stats span").evaluateAll((stats) => diff --git a/apps/presentation/dashboard/src/features/personal-workspace/i18n.tsx b/apps/presentation/dashboard/src/features/personal-workspace/i18n.tsx index c2c1964a91..26aa0eebb7 100644 --- a/apps/presentation/dashboard/src/features/personal-workspace/i18n.tsx +++ b/apps/presentation/dashboard/src/features/personal-workspace/i18n.tsx @@ -429,6 +429,8 @@ const en = { "header.live": "Live", "header.manager": "LoopX Manager", "header.statusOnlyAgent": "Status only", + "header.statusOnlyDescription": "No model calls", + "header.statusProjection": "LoopX state projection", "header.managerModelSettings": "Change steward model and reasoning effort", "header.managerDescription": "Your personal workspace across Goals", "header.managerRuntime": "{profile} · {sandbox}", @@ -1655,6 +1657,8 @@ const zhCN: Record = { "header.live": "实时", "header.manager": "LoopX 管家", "header.statusOnlyAgent": "仅查状态", + "header.statusOnlyDescription": "不调用模型", + "header.statusProjection": "LoopX 状态投影", "header.managerModelSettings": "调整管家模型与思考深度", "header.managerDescription": "跨 Goal 的个人工作入口", "header.managerRuntime": "{profile} · {sandbox}", diff --git a/apps/presentation/dashboard/src/views/dashboard-page.tsx b/apps/presentation/dashboard/src/views/dashboard-page.tsx index e3d41f83f4..89c4ed55f8 100644 --- a/apps/presentation/dashboard/src/views/dashboard-page.tsx +++ b/apps/presentation/dashboard/src/views/dashboard-page.tsx @@ -1340,12 +1340,12 @@ function PersonalGoalHome({ { agentId: "status-only", available: true, - capability: "不调用模型", + capability: t("header.statusOnlyDescription"), adapterKind: "status_projection", interrupt: false, label: t("header.statusOnlyAgent"), resume: true, - statusLabel: "只读", + statusLabel: t("common.readOnly"), streaming: false, toolCalls: false, trustScope: "read_only", @@ -2215,7 +2215,7 @@ function PersonalGoalHome({ const answerMessageId = appendManagerAssistantMessage(targetContextId, { agentLabel: usesStatusOnlyRoute ? t("header.statusOnlyAgent") : t("header.manager"), lines: answer.lines.slice(0, 3), - sourceLabel: usesStatusOnlyRoute ? "LoopX 状态投影 · 仅查状态" : "LoopX 状态投影", + sourceLabel: usesStatusOnlyRoute ? `${t("header.statusProjection")} · ${t("header.statusOnlyAgent")}` : t("header.statusProjection"), text: answer.text, }); void recordProjectionExchange({