spark-server/health-web/assets/site.js
DESKTOP-L8OUL9F\gfl0034 1bbf3b97c7 feat(web): 为 Sleep Heart 官网补上多语言切换
已部署站点需要按访客语言展示导航与协议正文,因此把文案抽到语言包并加上语言选择。

Co-authored-by: Cursor <cursoragent@cursor.com>
2026-10-12 01:46:06 +08:00

167 lines
6.0 KiB
JavaScript

(function () {
var CODES = ["zh-Hans", "zh-Hant", "en", "ja", "ko", "es", "fr", "de", "pt", "ru", "ar", "th", "vi", "id", "it"];
var NAMES = {
"zh-Hans": "简体中文",
"zh-Hant": "繁體中文",
en: "English",
ja: "日本語",
ko: "한국어",
es: "Español",
fr: "Français",
de: "Deutsch",
pt: "Português",
ru: "Русский",
ar: "العربية",
th: "ไทย",
vi: "Tiếng Việt",
id: "Bahasa Indonesia",
it: "Italiano"
};
var STORAGE = "sleepheart-lang";
function match(lang) {
var l = (lang || "").toLowerCase().replace(/_/g, "-");
if (l.indexOf("zh") === 0) {
if (l.indexOf("tw") >= 0 || l.indexOf("hk") >= 0 || l.indexOf("mo") >= 0 || l.indexOf("hant") >= 0) return "zh-Hant";
return "zh-Hans";
}
var base = l.split("-")[0];
return CODES.indexOf(base) >= 0 ? base : "";
}
function preferred() {
var saved = "";
try { saved = localStorage.getItem(STORAGE) || ""; } catch (e) {}
if (CODES.indexOf(saved) >= 0) return saved;
var list = navigator.languages || [navigator.language || ""];
for (var i = 0; i < list.length; i++) {
var hit = match(list[i]);
if (hit) return hit;
}
return "zh-Hans";
}
function esc(s) {
return String(s).replace(/[&<>"']/g, function (c) {
return { "&": "&amp;", "<": "&lt;", ">": "&gt;", '"': "&quot;", "'": "&#39;" }[c];
});
}
function repl(s, a, b) { return String(s).split(a).join(b); }
function linkify(s, ui) {
var html = esc(s);
html = repl(html, "{email}", '<a href="mailto:asd272940236@foxmail.com">asd272940236@foxmail.com</a>');
html = repl(html, "{home}", '<a href="/">' + esc(ui.backHome) + "</a>");
html = repl(html, "{delete}", '<a href="/delete">' + esc(ui.navDelete) + "</a>");
html = repl(html, "{privacyDoc}", '<a href="/privacy">' + esc(ui.privacyDoc) + "</a>");
html = repl(html, "{privacy}", '<a href="/privacy">' + esc(ui.navPrivacy) + "</a>");
html = repl(html, "{terms}", '<a href="/terms">' + esc(ui.navTerms) + "</a>");
return html;
}
function renderDoc(blocks, ui) {
var html = "";
var list = "";
function closeList() {
if (!list) return;
html += list === "ol" ? "</ol>" : "</ul>";
list = "";
}
blocks.forEach(function (block) {
var tag = block[0];
var inner = linkify(block[1], ui);
if (tag === "step" || tag === "li") {
var kind = tag === "step" ? "ol" : "ul";
if (list && list !== kind) closeList();
if (!list) {
html += kind === "ol" ? '<ol class="steps">' : "<ul>";
list = kind;
}
html += "<li>" + inner + "</li>";
return;
}
closeList();
if (tag === "meta") html += '<p class="meta">' + inner + "</p>";
else if (tag === "lead") html += '<p class="lead">' + inner + "</p>";
else if (tag === "note") html += '<p class="note">' + inner + "</p>";
else html += "<" + tag + ">" + inner + "</" + tag + ">";
});
closeList();
return html;
}
function apply(pack) {
var page = document.body.getAttribute("data-page") || "home";
var ui = pack.ui;
document.documentElement.lang = pack.htmlLang;
document.documentElement.dir = pack.dir || "ltr";
if (pack.titles && pack.titles[page]) document.title = pack.titles[page];
var desc = document.querySelector('meta[name="description"]');
if (desc && pack.desc && pack.desc[page]) desc.setAttribute("content", pack.desc[page]);
document.querySelectorAll("[data-i18n]").forEach(function (el) {
var key = el.getAttribute("data-i18n");
if (ui[key] == null) return;
if (el.getAttribute("data-i18n-mode") === "html") el.innerHTML = linkify(ui[key], ui);
else el.textContent = ui[key];
});
document.querySelectorAll("[data-i18n-alt]").forEach(function (el) {
var key = el.getAttribute("data-i18n-alt");
if (ui[key] != null) el.setAttribute("alt", ui[key]);
});
var doc = document.getElementById("doc");
if (doc && pack[page]) doc.innerHTML = renderDoc(pack[page], ui);
var select = document.getElementById("lang-select");
if (select) {
select.value = pack.code;
select.setAttribute("aria-label", ui.langLabel);
}
var label = document.querySelector(".lang-label span");
if (label) label.textContent = ui.langLabel;
var nav = document.querySelector(".nav");
if (nav) nav.setAttribute("aria-label", ui.navAria);
var foot = document.querySelector(".foot-links");
if (foot) foot.setAttribute("aria-label", ui.footAria);
}
function load(code) {
if (window.SH_PACKS && window.SH_PACKS[code]) return Promise.resolve(window.SH_PACKS[code]);
return new Promise(function (resolve, reject) {
var s = document.createElement("script");
s.src = "/assets/i18n/" + code + ".js?v=20261011lang";
s.onload = function () {
if (window.SH_PACKS && window.SH_PACKS[code]) resolve(window.SH_PACKS[code]);
else reject(new Error("missing pack"));
};
s.onerror = function () { reject(new Error("load")); };
document.head.appendChild(s);
});
}
function boot() {
var select = document.getElementById("lang-select");
if (select && !select.options.length) {
CODES.forEach(function (code) {
var opt = document.createElement("option");
opt.value = code;
opt.textContent = NAMES[code];
select.appendChild(opt);
});
select.addEventListener("change", function () {
var code = select.value;
try { localStorage.setItem(STORAGE, code); } catch (e) {}
load(code).then(apply).catch(function () {
if (window.SH_PACKS && window.SH_PACKS["zh-Hans"]) apply(window.SH_PACKS["zh-Hans"]);
});
});
}
var code = preferred();
load(code).then(apply).catch(function () {
if (window.SH_PACKS && window.SH_PACKS["zh-Hans"]) apply(window.SH_PACKS["zh-Hans"]);
});
}
if (document.readyState === "loading") document.addEventListener("DOMContentLoaded", boot);
else boot();
})();