website: v1.0.25 — 四语言站点 + 更新下载与日志
- 单页 + 语言层:assets/i18n.js(147 键 × zh-CN/zh-TW/en/ja)+ i18n-apply.js(data-i18n / data-i18n-html / data-i18n-attr 三种挂键方式, 首帧前定语言,?lang= → localStorage → navigator → zh-CN) - 导航栏新增语言切换;html[lang]、title、meta description / og:* 同步 - 版本号/下载直链 → v1.0.25;近期更新补 1.0.25 / 1.0.24,裁到 5 条 - style.css 仅追加(语言选择器样式 + 首帧防闪),既有规则零改动 - README 补语言层说明与发版清单
This commit is contained in:
1 parent
7a1b11dad7
commit
b89f9c6c95
5 files changed
+1229
-149
No files matched your search
@@ -0,0 +1,128 @@
|
||||
/* 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';
|
||||
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();
|
||||
})();
|
||||
Reference in new issue
Block a user