|
11 | 11 |
|
12 | 12 | // 页面标题和副标题保存语言键,具体文本由后端加载的翻译包提供。 |
13 | 13 | const viewCopy = { |
14 | | - "new-task": ["nav.new_task", "page.new_task"], |
15 | | - monitor: ["nav.monitor", "monitor.description"], |
16 | | - queue: ["nav.queue", "page.queue"], |
17 | | - history: ["nav.history", "history.query_id"], |
18 | | - logs: ["nav.logs", "page.logs"], |
19 | | - settings: ["nav.settings", "page.settings"], |
20 | | - about: ["nav.about", ""], |
| 14 | + "new-task": {title: "nav.new_task", meta: "page.new_task"}, |
| 15 | + monitor: {title: "nav.monitor", meta: "monitor.description"}, |
| 16 | + queue: {title: "nav.queue", meta: "page.queue"}, |
| 17 | + history: {title: "nav.history", meta: "history.query_id"}, |
| 18 | + logs: {title: "nav.logs", meta: "page.logs"}, |
| 19 | + settings: {title: "nav.settings", meta: "page.settings"}, |
| 20 | + about: {title: "nav.about"}, |
21 | 21 | }; |
22 | 22 |
|
23 | 23 | let uiTranslations = Object.create(null); |
| 24 | + let translatedUiInitialized = false; |
24 | 25 |
|
25 | 26 | function translateText(key) { |
26 | | - // 翻译包尚未加载时返回空字符串,避免将语言键直接呈现给用户。 |
27 | | - return uiTranslations[key] || ""; |
| 27 | + return uiTranslations[key]; |
28 | 28 | } |
29 | 29 |
|
30 | 30 | function formatTranslated(key, values = []) { |
|
34 | 34 | )); |
35 | 35 | } |
36 | 36 |
|
37 | | - function resolveText(value) { |
38 | | - // 动态消息通常传入语言键;后端返回的错误文本不属于语言包时保留原文。 |
39 | | - return uiTranslations[value] ?? value; |
40 | | - } |
41 | | - |
42 | 37 | function applyStaticTranslations() { |
43 | 38 | // 根据当前语言包更新 HTML 中标记 data-i18n 的文本及属性。 |
44 | 39 | document.querySelectorAll("[data-i18n]").forEach((element) => { |
|
65 | 60 | toast.className = `toast ${type}`; |
66 | 61 | toast.innerHTML = ` |
67 | 62 | <span class="toast-icon">${icon(type === "warning" ? "info" : "check")}</span> |
68 | | - <div><strong>${resolveText(title)}</strong><span>${resolveText(message)}</span></div> |
| 63 | + <div><strong>${title}</strong><span>${message}</span></div> |
69 | 64 | `; |
70 | 65 | toastRegion.appendChild(toast); |
71 | 66 | window.setTimeout(() => { |
|
81 | 76 |
|
82 | 77 | let monitorMode = false; |
83 | 78 |
|
| 79 | + function updateViewHeader(name) { |
| 80 | + const copy = viewCopy[name]; |
| 81 | + const metaKey = copy.meta; |
| 82 | + viewTitle.textContent = translateText(copy.title); |
| 83 | + viewMeta.textContent = metaKey ? translateText(metaKey) : ""; |
| 84 | + } |
| 85 | + |
84 | 86 | function setView(name, options = {}) { |
85 | 87 | // 监听运行期间锁定其他页面,避免在监听模式下修改配置或创建冲突任务。 |
86 | 88 | if (monitorMode && name !== "monitor" && !options.force) { |
87 | | - showToast("toast.exit_monitor_first", "toast.monitor_switch_locked", "warning"); |
| 89 | + showToast( |
| 90 | + translateText("toast.exit_monitor_first"), translateText("toast.monitor_switch_locked"), "warning"); |
88 | 91 | return; |
89 | 92 | } |
90 | 93 | const page = document.querySelector(`.view[data-page="${name}"]`); |
|
93 | 96 | document.querySelectorAll(".view").forEach((item) => item.classList.toggle("active", item === page)); |
94 | 97 | document.querySelectorAll(".nav-item") |
95 | 98 | .forEach((item) => item.classList.toggle("active", item.dataset.view === name)); |
96 | | - [viewTitle.textContent, viewMeta.textContent] = viewCopy[name].map(translateText); |
| 99 | + updateViewHeader(name); |
97 | 100 | if (window.location.hash !== `#${name}`) { |
98 | 101 | window.history.replaceState(null, "", `#${name}`); |
99 | 102 | } |
|
129 | 132 | if (document.querySelector(".view.active")?.dataset.page === "monitor") { |
130 | 133 | viewMeta.textContent = active |
131 | 134 | ? translateText("monitor.reading") |
132 | | - : translateText(viewCopy.monitor[1]); |
| 135 | + : translateText(viewCopy.monitor.meta); |
133 | 136 | } |
134 | 137 | monitorState.classList.toggle("inactive", !active); |
135 | 138 | monitorState.innerHTML = `<i></i><span>${translateText(active ? "monitor.active" : "monitor.inactive")}</span>`; |
|
380 | 383 | historyEnabled = Boolean(enabled); |
381 | 384 | historyView.classList.toggle("is-disabled", !historyEnabled); |
382 | 385 | historySearch.disabled = !historyEnabled; |
383 | | - viewCopy.history[1] = historyEnabled |
384 | | - ? "history.query_id" |
385 | | - : "history.query_disabled"; |
386 | | - if (document.querySelector(".view.active") === historyView) viewMeta.textContent = translateText( |
387 | | - viewCopy.history[1]); |
388 | 386 | if (!historyEnabled) { |
389 | 387 | historyRequestId += 1; |
390 | 388 | historyBody.replaceChildren(); |
|
447 | 445 | if (requestId !== historyRequestId) return; |
448 | 446 | renderNativeHistoryPage(result); |
449 | 447 | } catch (error) { |
450 | | - if (requestId === historyRequestId) showToast("history.read_failed", String(error), "warning"); |
| 448 | + if (requestId === historyRequestId) showToast( |
| 449 | + translateText("history.read_failed"), String(error), "warning"); |
451 | 450 | } |
452 | 451 | } |
453 | 452 |
|
|
666 | 665 | }); |
667 | 666 | }); |
668 | 667 |
|
669 | | - applyNameFormat(""); |
670 | | - |
671 | | - refreshQueue(); |
672 | | - refreshMonitorQueue(); |
673 | | - updateRecordSelection(); |
674 | | - renderMonitorState(false); |
675 | | - const initialView = window.location.hash.slice(1); |
676 | | - if (viewCopy[initialView]) setView(initialView); |
| 668 | + function initializeTranslatedUi() { |
| 669 | + refreshQueue(); |
| 670 | + refreshMonitorQueue(); |
| 671 | + updateRecordSelection(); |
| 672 | + renderMonitorState(monitorMode); |
| 673 | + if (!translatedUiInitialized) { |
| 674 | + const initialView = window.location.hash.slice(1); |
| 675 | + if (viewCopy[initialView]) setView(initialView); |
| 676 | + translatedUiInitialized = true; |
| 677 | + } |
| 678 | + } |
677 | 679 |
|
678 | 680 | // PyWebView 负责所有数据变更,DOM 仅渲染快照;以下变量用于控制轮询和请求去重。 |
679 | 681 | let nativeApi = null; |
|
736 | 738 | stateLine.className = "queue-state-line"; |
737 | 739 | const state = document.createElement("span"); |
738 | 740 | state.className = `status-text ${statusClass(task.state)}`; |
739 | | - state.textContent = translateText(statusLabels[task.state] || task.state); |
| 741 | + state.textContent = translateText(statusLabels[task.state]); |
740 | 742 | const message = document.createElement("span"); |
741 | | - message.textContent = task.error || translateText(statusDetails[task.state] || ""); |
| 743 | + message.textContent = task.error ? task.error : translateText(statusDetails[task.state]); |
742 | 744 | stateLine.append(state, message); |
743 | 745 | detail.append(link, stateLine); |
744 | 746 | item.append(symbol, detail); |
|
772 | 774 | state.className = `status-text ${statusClass(task.state)}`; |
773 | 775 | if (task.state === "success") state.innerHTML = icon("check"); |
774 | 776 | else state.append(document.createElement("i")); |
775 | | - state.append(document.createTextNode(translateText(statusLabels[task.state] || task.state))); |
| 777 | + state.append(document.createTextNode(translateText(statusLabels[task.state]))); |
776 | 778 | item.append(symbol, primary, state); |
777 | 779 | return item; |
778 | 780 | } |
|
900 | 902 | applyNameFormat(nameFormat); |
901 | 903 | const activeView = document.querySelector(".view.active")?.dataset.page; |
902 | 904 | if (viewCopy[activeView]) { |
903 | | - [viewTitle.textContent, viewMeta.textContent] = viewCopy[activeView].map(translateText); |
| 905 | + updateViewHeader(activeView); |
904 | 906 | } |
| 907 | + initializeTranslatedUi(); |
905 | 908 | } |
906 | 909 |
|
907 | 910 | function collectNativeSettings() { |
|
956 | 959 | // 关于页的版本、发布类型和仓库信息均来自后端常量,避免在 HTML 中固化。 |
957 | 960 | const version = String(about.version); |
958 | 961 | const release = String(about.release); |
959 | | - const displayVersion = [version, release].filter(Boolean).join(" "); |
960 | | - document.getElementById("aboutVersion").textContent = displayVersion; |
| 962 | + document.getElementById("aboutVersion").textContent = [version, release].filter(Boolean).join(" "); |
961 | 963 | document.getElementById("aboutAuthor").textContent = about.author; |
962 | 964 | document.getElementById("aboutLicense").textContent = about.license; |
963 | 965 | document.getElementById("aboutRepository").textContent = String(about.repository).replace(/^https?:\/\//, ""); |
|
990 | 992 | applyNativeState(await nativeApi.get_state()); |
991 | 993 | return true; |
992 | 994 | } catch (error) { |
993 | | - showToast("update.failed", String(error), "warning"); |
| 995 | + showToast(translateText("update.failed"), String(error), "warning"); |
994 | 996 | return false; |
995 | 997 | } finally { |
996 | 998 | bridgeRefreshBusy = false; |
|
1004 | 1006 | await refreshNativeState(); |
1005 | 1007 | } catch (error) { |
1006 | 1008 | const message = error instanceof Error ? error.message : String(error); |
1007 | | - showToast(message || translateText("toast.operation_failed"), "", "warning"); |
| 1009 | + showToast(message, "", "warning"); |
1008 | 1010 | } |
1009 | 1011 | } |
1010 | 1012 |
|
|
1031 | 1033 | startStatePolling(); |
1032 | 1034 | } catch (error) { |
1033 | 1035 | const message = error instanceof Error ? error.message : String(error); |
1034 | | - showToast(message || translateText("toast.operation_failed"), "", "warning"); |
| 1036 | + showToast(message, "", "warning"); |
1035 | 1037 | } |
1036 | 1038 | })(); |
1037 | 1039 | }); |
|
1042 | 1044 | await nativeApi.decline_disclaimer(); |
1043 | 1045 | } catch (error) { |
1044 | 1046 | const message = error instanceof Error ? error.message : String(error); |
1045 | | - showToast(message || translateText("toast.operation_failed"), "", "warning"); |
| 1047 | + showToast(message, "", "warning"); |
1046 | 1048 | } |
1047 | 1049 | })(); |
1048 | 1050 | }); |
|
1060 | 1062 | event.stopImmediatePropagation(); |
1061 | 1063 | void runNativeAction(async () => { |
1062 | 1064 | const cancelled = await nativeApi.cancel_task(taskId); |
1063 | | - if (!cancelled) showToast("toast.cannot_cancel", "toast.started_task", "warning"); |
| 1065 | + if (!cancelled) showToast( |
| 1066 | + translateText("toast.cannot_cancel"), translateText("toast.started_task"), "warning"); |
1064 | 1067 | }); |
1065 | 1068 | return; |
1066 | 1069 | } |
|
1104 | 1107 | if (!result.ok) throw new Error(result.error); |
1105 | 1108 | settingsLoaded = false; |
1106 | 1109 | await refreshUiTranslations(); |
1107 | | - showToast("settings.saved", "settings.reloaded"); |
| 1110 | + showToast(translateText("settings.saved"), translateText("settings.reloaded")); |
1108 | 1111 | }); |
1109 | 1112 | } else if (id === "discardSettings") { |
1110 | 1113 | event.preventDefault(); |
1111 | 1114 | event.stopImmediatePropagation(); |
1112 | 1115 | void runNativeAction(async () => { |
1113 | 1116 | settingsLoaded = false; |
1114 | 1117 | applyNativeSettings(await nativeApi.get_settings()); |
1115 | | - showToast("settings.discarded"); |
| 1118 | + showToast(translateText("settings.discarded")); |
1116 | 1119 | }); |
1117 | 1120 | } else if (id === "openRepository") { |
1118 | 1121 | event.preventDefault(); |
|
1130 | 1133 | void (async () => { |
1131 | 1134 | try { |
1132 | 1135 | const result = await nativeApi.check_update(); |
1133 | | - if (result.status !== "ok") throw new Error(result.message); |
| 1136 | + if (result.status !== "ok") { |
| 1137 | + setUpdateResult(result.message, "warning"); |
| 1138 | + showToast(translateText("update.failed"), result.message, "warning"); |
| 1139 | + return; |
| 1140 | + } |
1134 | 1141 | const updateAvailable = ["update_available", "stable_available"].includes(result.kind); |
1135 | 1142 | const tone = updateAvailable ? "warning" : "success"; |
1136 | 1143 | const title = result.title; |
|
1139 | 1146 | } catch (error) { |
1140 | 1147 | const message = error?.message || String(error); |
1141 | 1148 | setUpdateResult(`${translateText("update.failed")}: ${message}`, "error"); |
1142 | | - showToast("update.failed", message, "warning"); |
| 1149 | + showToast(translateText("update.failed"), message, "warning"); |
1143 | 1150 | } finally { |
1144 | 1151 | setUpdateChecking(false); |
1145 | 1152 | } |
|
1162 | 1169 | startStatePolling(); |
1163 | 1170 | })().catch((error) => { |
1164 | 1171 | startupScreen.hidden = true; |
1165 | | - showToast("toast.language_load_failed", String(error), "warning"); |
| 1172 | + showToast(translateText("toast.language_load_failed"), String(error), "warning"); |
1166 | 1173 | }); |
1167 | 1174 | } |
1168 | 1175 |
|
|
0 commit comments