From 517a17d974ee96bda231ad872211a0c87c17b8d9 Mon Sep 17 00:00:00 2001 From: youzini Date: Fri, 5 Jun 2026 10:50:51 +0000 Subject: [PATCH] feat(i18n): localize panel shell controls --- i18n/en-US.js | 14 ++++++++++++++ i18n/zh-CN.js | 14 ++++++++++++++ tests/panel-bridge.mjs | 34 +++++++++++++++++++++++++++++++--- ui/panel-bridge.js | 2 +- ui/panel.html | 26 ++++++++++++++++++++++++-- ui/panel.js | 28 ++++++++++++++++++++-------- 6 files changed, 104 insertions(+), 14 deletions(-) diff --git a/i18n/en-US.js b/i18n/en-US.js index 9ba1233..987ccc0 100644 --- a/i18n/en-US.js +++ b/i18n/en-US.js @@ -5,6 +5,7 @@ export default { "common.appName": "ST-BME", "common.cancel": "Cancel", + "common.clauseSeparator": "; ", "common.close": "Close", "common.confirm": "Confirm", "common.delete": "Delete", @@ -45,6 +46,11 @@ export default { "panel.entry.menuLabel": "Memory Graph", "panel.entry.openPanelAction": "Open Panel", "panel.entry.openFailed": "Memory Graph panel failed to load. Check the console for details.", + "panel.entry.preloadFailed": "Memory Graph panel preloading failed. Try opening it from the menu again later.", + "panel.appearance.locale.subtitle": "Only changes the BME frontend UI. It does not translate chat content, memory nodes, or prompts.", + "panel.appearance.locale.title": "Interface Language", + "panel.appearance.theme.subtitle": "Choose the panel style that best fits the current story mood and reading habits.", + "panel.appearance.theme.title": "Panel Theme", "panel.title": "ST-BME Memory Graph", "panel.header.fabToggleTitle": "Show/Hide FAB", "panel.header.themePickerTitle": "Switch Theme", @@ -214,4 +220,12 @@ export default { "status.initial.recall.detail": "Recall has not run yet", "status.initial.runtime.detail": "Ready", "status.initial.vector.detail": "Vector tasks have not run yet", + + "llm.providerHelp.auto": "Leave blank to reuse the current chat model. BME can detect OpenAI-compatible endpoints, Anthropic Claude, and Google AI Studio / Gemini; full endpoints are normalized into reusable base URLs.", + "llm.providerHelp.baseUrl": "Base URL: {baseUrl}", + "llm.providerHelp.customProvider": "Provider not recognized; treating it as a custom OpenAI-compatible endpoint", + "llm.providerHelp.knownProvider": "Detected provider: {provider}", + "llm.providerHelp.modelFetchUnsupported": "This provider cannot fetch models automatically yet; enter the model name manually.", + "llm.providerHelp.normalizedUrl": "Normalized URL: {apiUrl}", + "llm.providerHelp.transport": "Transport: {transport}", }; diff --git a/i18n/zh-CN.js b/i18n/zh-CN.js index 93c0828..1a67817 100644 --- a/i18n/zh-CN.js +++ b/i18n/zh-CN.js @@ -5,6 +5,7 @@ export default { "common.appName": "ST-BME", "common.cancel": "取消", + "common.clauseSeparator": ";", "common.close": "关闭", "common.confirm": "确认", "common.delete": "删除", @@ -45,6 +46,11 @@ export default { "panel.entry.menuLabel": "记忆图谱", "panel.entry.openPanelAction": "打开面板", "panel.entry.openFailed": "记忆图谱面板加载失败,请查看控制台报错", + "panel.entry.preloadFailed": "记忆图谱面板预加载失败,可稍后重试点击菜单", + "panel.appearance.locale.subtitle": "只切换 BME 前端界面,不翻译聊天内容、记忆节点或提示词。", + "panel.appearance.locale.title": "界面语言", + "panel.appearance.theme.subtitle": "选择最适合当前故事氛围和阅读习惯的面板风格。", + "panel.appearance.theme.title": "面板主题", "panel.title": "ST-BME 记忆图谱", "panel.header.fabToggleTitle": "显示/隐藏悬浮球", "panel.header.themePickerTitle": "切换主题", @@ -214,4 +220,12 @@ export default { "status.initial.recall.detail": "尚未执行召回", "status.initial.runtime.detail": "准备就绪", "status.initial.vector.detail": "尚未执行向量任务", + + "llm.providerHelp.auto": "留空时复用当前聊天模型。支持自动识别 OpenAI 兼容渠道、Anthropic Claude、Google AI Studio / Gemini;填写完整 endpoint 时会自动规整为可复用的 base URL。", + "llm.providerHelp.baseUrl": "基础地址:{baseUrl}", + "llm.providerHelp.customProvider": "未识别为特定渠道,将按自定义 OpenAI 兼容接口处理", + "llm.providerHelp.knownProvider": "已识别渠道:{provider}", + "llm.providerHelp.modelFetchUnsupported": "该渠道暂不支持自动拉取模型,请手动填写模型名", + "llm.providerHelp.normalizedUrl": "规范化地址:{apiUrl}", + "llm.providerHelp.transport": "请求通道:{transport}", }; diff --git a/tests/panel-bridge.mjs b/tests/panel-bridge.mjs index 94a4eda..c75c435 100644 --- a/tests/panel-bridge.mjs +++ b/tests/panel-bridge.mjs @@ -132,15 +132,23 @@ function buildDocument({ includeExtensionsMenu = true } = {}) { return document; } -function buildRuntime(document) { +function buildRuntime(document, initialSettings = {}) { + let settings = { ...initialSettings }; const calls = { opened: 0, hidden: [], css: [] }; - const panelModule = { openPanel: () => { calls.opened += 1; } }; + const panelModule = { + openPanel: () => { calls.opened += 1; }, + updatePanelLocale: (localeMode) => { calls.updatedLocale = localeMode; }, + }; return { calls, console, document, getPanelModule: () => panelModule, - getSettings: () => ({}), + getSettings: () => settings, + updateSettings: (patch) => { + settings = { ...settings, ...patch }; + return settings; + }, $: (selector) => ({ hide: () => calls.hidden.push(selector), css: (name, value) => calls.css.push([selector, name, value]), @@ -168,6 +176,26 @@ const { initializePanelBridgeController } = await import("../ui/panel-bridge.js" assert.ok(runtime.calls.hidden.includes("#extensionsMenu"), "magic-wand entry closes extensions menu"); } +{ + const document = buildDocument(); + const runtime = buildRuntime(document, { uiLocale: "en-US" }); + + await initializePanelBridgeController(runtime); + const optionsEntry = document.getElementById("option_st_bme_panel"); + const wandEntry = document.getElementById("st_bme_extensions_menu_entry"); + const fab = document.getElementById("bme-floating-ball"); + + assert.match(optionsEntry.innerHTML, /Memory Graph/, "legacy menu entry follows English locale"); + assert.match(wandEntry.innerHTML, /Memory Graph/, "magic-wand entry follows English locale"); + assert.match(fab.innerHTML, /BME Memory Graph/, "floating bootstrap follows English locale"); + + runtime.updateSettings({ uiLocale: "zh-CN" }); + await initializePanelBridgeController(runtime); + assert.match(optionsEntry.innerHTML, /记忆图谱/, "legacy menu entry refreshes when locale changes"); + assert.match(wandEntry.innerHTML, /记忆图谱/, "magic-wand entry refreshes when locale changes"); + assert.match(fab.innerHTML, /BME 记忆图谱/, "floating bootstrap refreshes when locale changes"); +} + { const document = buildDocument({ includeExtensionsMenu: false }); const runtime = buildRuntime(document); diff --git a/ui/panel-bridge.js b/ui/panel-bridge.js index 9d3dfda..f341b28 100644 --- a/ui/panel-bridge.js +++ b/ui/panel-bridge.js @@ -252,6 +252,6 @@ export async function initializePanelBridgeController(runtime) { "[ST-BME] 操控面板加载失败(核心功能不受影响):", panelError, ); - globalThis.toastr?.error?.("记忆图谱面板预加载失败,可稍后重试点击菜单", "ST-BME"); + globalThis.toastr?.error?.(t("panel.entry.preloadFailed"), "ST-BME"); } } diff --git a/ui/panel.html b/ui/panel.html index bb1c118..95f4c5a 100644 --- a/ui/panel.html +++ b/ui/panel.html @@ -3235,8 +3235,30 @@
-
面板主题
-
+
界面语言
+
+ 只切换 BME 前端界面,不翻译聊天内容、记忆节点或提示词。 +
+
+
+
+ + +
+ 只影响 ST-BME 面板、提示和状态文案,不会翻译聊天内容、记忆节点或提示词。 +
+
+
+ +
+
+
+
面板主题
+
选择最适合当前故事氛围和阅读习惯的面板风格。
diff --git a/ui/panel.js b/ui/panel.js index 565808e..5b885b5 100644 --- a/ui/panel.js +++ b/ui/panel.js @@ -93,8 +93,7 @@ function _refreshMemoryLlmProviderHelp(urlValue = null) { ).trim(); if (!rawUrl) { - helpEl.textContent = - "留空时复用当前聊天模型。支持自动识别 OpenAI 兼容渠道、Anthropic Claude、Google AI Studio / Gemini;填写完整 endpoint 时会自动规整为可复用的 base URL。"; + helpEl.textContent = t("llm.providerHelp.auto"); return; } @@ -102,24 +101,26 @@ function _refreshMemoryLlmProviderHelp(urlValue = null) { const parts = []; if (resolved.isKnownProvider) { - parts.push(`已识别渠道:${resolved.providerLabel || resolved.providerId || "未知渠道"}`); + parts.push(t("llm.providerHelp.knownProvider", { + provider: resolved.providerLabel || resolved.providerId || t("common.unknown"), + })); } else { - parts.push("未识别为特定渠道,将按自定义 OpenAI 兼容接口处理"); + parts.push(t("llm.providerHelp.customProvider")); } if (resolved.transportLabel) { - parts.push(`请求通道:${resolved.transportLabel}`); + parts.push(t("llm.providerHelp.transport", { transport: resolved.transportLabel })); } if (resolved.apiUrl && resolved.apiUrl !== rawUrl) { - parts.push(`规范化地址:${resolved.apiUrl}`); + parts.push(t("llm.providerHelp.normalizedUrl", { apiUrl: resolved.apiUrl })); } if (resolved.supportsModelFetch !== true) { - parts.push("该渠道暂不支持自动拉取模型,请手动填写模型名"); + parts.push(t("llm.providerHelp.modelFetchUnsupported")); } - helpEl.textContent = parts.join(";"); + helpEl.textContent = parts.join(t("common.clauseSeparator")); } function getDefaultPrompts() { @@ -7895,6 +7896,10 @@ function _refreshConfigTab() { "bme-setting-recall-card-user-input-display-mode", settings.recallCardUserInputDisplayMode ?? "beautify_only", ); + _setInputValue( + "bme-setting-ui-locale", + settings.uiLocale || "auto", + ); _setInputValue( "bme-setting-notice-display-mode", settings.noticeDisplayMode ?? "normal", @@ -8227,6 +8232,13 @@ function _bindConfigControls() { bindCheckbox("bme-setting-debug-logging-enabled", (checked) => { _patchSettings({ debugLoggingEnabled: checked }); }); + const uiLocaleEl = document.getElementById("bme-setting-ui-locale"); + if (uiLocaleEl && uiLocaleEl.dataset.bmeBound !== "true") { + uiLocaleEl.addEventListener("change", () => { + _patchSettings({ uiLocale: uiLocaleEl.value || "auto" }); + }); + uiLocaleEl.dataset.bmeBound = "true"; + } bindCheckbox("bme-setting-graph-native-force-disable", (checked) => { _patchSettings({ graphNativeForceDisable: checked }); });