add: 添加 i18n 翻译
This commit is contained in:
+53
-10
@@ -1,7 +1,9 @@
|
||||
// app.js — entry point. Routes hash changes to view renderers and
|
||||
// keeps the top-nav active state in sync.
|
||||
// app.js — entry point. Routes hash changes to view renderers, keeps
|
||||
// the top-nav active state in sync, and re-renders chrome strings when
|
||||
// the locale changes.
|
||||
|
||||
import { onRoute } from "./state.js";
|
||||
import { t, currentLocale, setLocale, onLocale } from "./i18n.js";
|
||||
import { renderRemotes } from "./views/remotes.js";
|
||||
import { renderBrowse } from "./views/browser.js";
|
||||
import { renderJobs, renderNewJob, stopJobPolling } from "./views/jobs.js";
|
||||
@@ -19,12 +21,13 @@ const views = {
|
||||
"configure-edit": renderConfigureEdit,
|
||||
};
|
||||
|
||||
let currentRoute = { name: "remotes", params: {} };
|
||||
|
||||
function setActiveNav(routeName) {
|
||||
const links = document.querySelectorAll("#nav-links a");
|
||||
for (const a of links) {
|
||||
const target = a.dataset.route;
|
||||
let isActive = target === routeName;
|
||||
// "Configure" is active on both configure-new and configure-edit
|
||||
if (target === "configure" && routeName.startsWith("configure-")) {
|
||||
isActive = true;
|
||||
}
|
||||
@@ -32,26 +35,66 @@ function setActiveNav(routeName) {
|
||||
}
|
||||
}
|
||||
|
||||
onRoute(async (route) => {
|
||||
function applyChromeStrings() {
|
||||
// nav links
|
||||
document.querySelectorAll("#nav-links a").forEach((a) => {
|
||||
const key = a.dataset.route === "configure" ? "nav.configure" : `nav.${a.dataset.route}`;
|
||||
a.textContent = t(key);
|
||||
});
|
||||
const newJobBtn = document.querySelector(".nav-new-job");
|
||||
if (newJobBtn) newJobBtn.textContent = t("nav.new_job");
|
||||
const lt = document.getElementById("loading-title");
|
||||
if (lt) lt.textContent = t("loading.title");
|
||||
const lb = document.getElementById("loading-body");
|
||||
if (lb) lb.innerHTML = t("loading.connecting");
|
||||
// Anything with data-i18n attribute (footer)
|
||||
document.querySelectorAll("[data-i18n]").forEach((el) => {
|
||||
el.textContent = t(el.dataset.i18n);
|
||||
});
|
||||
// lang buttons
|
||||
const locale = currentLocale();
|
||||
document.querySelectorAll(".lang-btn").forEach((btn) => {
|
||||
btn.classList.toggle("active", btn.dataset.lang === locale);
|
||||
});
|
||||
}
|
||||
|
||||
function wireLangSwitcher() {
|
||||
document.querySelectorAll(".lang-btn").forEach((btn) => {
|
||||
btn.addEventListener("click", () => setLocale(btn.dataset.lang));
|
||||
});
|
||||
}
|
||||
|
||||
onRoute((route) => {
|
||||
currentRoute = route;
|
||||
setActiveNav(route.name);
|
||||
|
||||
// Stop jobs polling when leaving the jobs view
|
||||
if (route.name !== "jobs") {
|
||||
stopJobPolling();
|
||||
}
|
||||
|
||||
const renderer = views[route.name] || renderRemotes;
|
||||
try {
|
||||
await renderer(route.params);
|
||||
} catch (e) {
|
||||
renderer(route.params).catch((e) => {
|
||||
console.error("view error", e);
|
||||
const app = document.getElementById("app");
|
||||
if (app) {
|
||||
app.innerHTML = `<div class="empty"><h3>Something went wrong</h3><p>${escapeHtml(e.message)}</p></div>`;
|
||||
app.innerHTML = `<div class="empty"><h3>${t("error.title")}</h3><p>${escapeHtml(e.message)}</p></div>`;
|
||||
}
|
||||
}
|
||||
});
|
||||
});
|
||||
|
||||
// Re-render everything on locale change so views pick up new strings.
|
||||
onLocale(() => {
|
||||
applyChromeStrings();
|
||||
const renderer = views[currentRoute.name] || renderRemotes;
|
||||
renderer(currentRoute.params).catch((e) => {
|
||||
console.error("view error on locale change", e);
|
||||
});
|
||||
});
|
||||
|
||||
// Initial chrome paint + switcher wiring.
|
||||
applyChromeStrings();
|
||||
wireLangSwitcher();
|
||||
|
||||
function escapeHtml(s) {
|
||||
return String(s)
|
||||
.replace(/&/g, "&")
|
||||
|
||||
Reference in New Issue
Block a user