Files
OpenTerminal/website/assets/i18n-apply.js
T
Bill 2027fa3310
Deploy website to GitHub Pages / deploy (push) Waiting to run
CI / typecheck + test + build (windows) (push) Canceled after 0s
fix(updater,i18n): 对抗性审查修复(1中 + 3低)
- [中] 手动检查不再丢弃「已下载」事实:runCheck('manual') 原来无条件清
  抑制记录,导致下载完成后点「检查更新」会把关于页从「重启安装」退回
  「下载」(包已在磁盘)。改为保留 downloaded 记录,只清 announcement
  记账;测试 §19 补 manual-check 保持 downloaded 的口径(153 断言)
- website 首帧脚本:?lang= 的 decodeURIComponent 包 try/catch(畸形转义
  会在 IIFE 内抛 URIError,跳过 html[lang] 与防闪标记,指定语言被静默
  忽略);zh-Hant-TW/zh-Hant-HK/zh-Hant 按 BCP-47 语义判为繁体
  (normalize 与 i18n-apply.js 双处同步)
- ZmodemOffers 是 App 兄弟节点,语言切换时父级 children bailout 不重渲染:
  订阅 language 并以 tFor 显式翻译(事件回调内的 t() 保留)
- ROADMAP 补录对抗性审查推迟项(周期检查可被设置写入推迟、启动开关
  即时生效语义、下载无整体超时、--channel-only/--skip-gitea 矛盾)
2026-10-10 16:51:10 +08:00

131 lines
4.5 KiB
JavaScript
Raw Permalink Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
/* OpenTerminal 官网语言层
选定语言的优先级(与 index.html 头部内联脚本完全一致):
1. URL 参数 ?lang=xx
2. localStorage['openterminal.lang']
3. navigator.language(s) 前缀匹配
4. 默认 zh-CN
index.html 头部内联脚本先按同一优先级把 <html lang> 定好(首帧防闪),
本文件直接读 document.documentElement.lang 作为唯一事实源,不重复判定。
应用规则:
- [data-i18n="k"] → textContent
- [data-i18n-html="k"] → innerHTML(字典是本地静态文件,非用户输入)
- [data-i18n-attr="alt:k;title:k2"] → 逐对写属性,分号分隔
缺键回落到 zh-CN;仍缺则保持 HTML 里的原文,不会把页面写空。
语言切换后派发 document 事件 'openterminal:langchange'(detail.lang),
页面尾部的内联脚本据此重刷主题按钮文案。 */
(function () {
'use strict';
var DICT = window.OPENTERMINAL_I18N || {};
var STORAGE_KEY = 'openterminal.lang';
var FALLBACK = 'zh-CN';
var SUPPORTED = ['zh-CN', 'zh-TW', 'en', 'ja'];
function normalize(tag) {
if (!tag) return null;
var t = String(tag).replace(/_/g, '-');
if (/^zh-?(tw|hk|mo)/i.test(t)) return 'zh-TW';
// zh-Hant-* is Traditional by definition (kept in sync with the head script).
if (/^zh-hant/i.test(t)) return 'zh-TW';
if (/^zh/i.test(t)) return 'zh-CN';
if (/^ja/i.test(t)) return 'ja';
if (/^en/i.test(t)) return 'en';
return null;
}
function tableOf(lang) {
return DICT[lang] || DICT[FALLBACK] || {};
}
function lookup(lang, key) {
var table = tableOf(lang);
if (Object.prototype.hasOwnProperty.call(table, key)) return table[key];
var fb = DICT[FALLBACK] || {};
if (Object.prototype.hasOwnProperty.call(fb, key)) return fb[key];
return null;
}
function applyAttrs(lang) {
var nodes = document.querySelectorAll('[data-i18n-attr]');
for (var i = 0; i < nodes.length; i++) {
var pairs = nodes[i].getAttribute('data-i18n-attr').split(';');
for (var j = 0; j < pairs.length; j++) {
var parts = pairs[j].split(':');
if (parts.length < 2) continue;
var attr = parts[0].trim();
var key = parts.slice(1).join(':').trim();
var val = lookup(lang, key);
if (attr && val !== null) nodes[i].setAttribute(attr, val);
}
}
}
function applyText(lang) {
var nodes = document.querySelectorAll('[data-i18n]');
for (var i = 0; i < nodes.length; i++) {
var val = lookup(lang, nodes[i].getAttribute('data-i18n'));
if (val !== null) nodes[i].textContent = val;
}
}
function applyHtml(lang) {
var nodes = document.querySelectorAll('[data-i18n-html]');
for (var i = 0; i < nodes.length; i++) {
var val = lookup(lang, nodes[i].getAttribute('data-i18n-html'));
if (val !== null) nodes[i].innerHTML = val;
}
}
function clearPending() {
document.documentElement.removeAttribute('data-i18n-pending');
}
function apply(lang, announce) {
var target = normalize(lang) || FALLBACK;
try {
document.documentElement.lang = target;
applyText(target);
applyHtml(target);
applyAttrs(target);
} finally {
// 任何异常都不能让 body 一直藏在 visibility:hidden 后面
clearPending();
}
var sel = document.getElementById('lang-select');
if (sel && sel.value !== target) sel.value = target;
if (announce) {
try {
document.dispatchEvent(new CustomEvent('openterminal:langchange', { detail: { lang: target } }));
} catch (e) {
var ev = document.createEvent('Event');
ev.initEvent('openterminal:langchange', true, true);
ev.detail = { lang: target };
document.dispatchEvent(ev);
}
}
return target;
}
// 头部内联脚本已经定好语言;这里只是把它落到 DOM 上
var current = apply(document.documentElement.lang || FALLBACK, false);
var select = document.getElementById('lang-select');
if (select) {
select.value = current;
select.addEventListener('change', function () {
var next = normalize(select.value) || FALLBACK;
try { localStorage.setItem(STORAGE_KEY, next); } catch (e) {}
current = apply(next, true);
});
}
window.OPENTERMINAL_SET_LANG = function (lang) {
current = apply(lang, true);
try { localStorage.setItem(STORAGE_KEY, current); } catch (e) {}
return current;
};
window.OPENTERMINAL_I18N_SUPPORTED = SUPPORTED.slice();
})();