Add files via upload

This commit is contained in:
公明
2026-03-09 02:06:39 +08:00
committed by GitHub
parent 8a2177ffab
commit 7b1487383f
16 changed files with 3628 additions and 971 deletions
+202
View File
@@ -0,0 +1,202 @@
// 前端国际化初始化(基于 i18next 浏览器版本)
(function () {
const DEFAULT_LANG = 'zh-CN';
const STORAGE_KEY = 'csai_lang';
const RESOURCES_PREFIX = '/static/i18n';
const loadedLangs = {};
function detectInitialLang() {
try {
const stored = localStorage.getItem(STORAGE_KEY);
if (stored) {
return stored;
}
} catch (e) {
console.warn('无法读取语言设置:', e);
}
const navLang = (navigator.language || navigator.userLanguage || '').toLowerCase();
if (navLang.startsWith('zh')) {
return 'zh-CN';
}
if (navLang.startsWith('en')) {
return 'en-US';
}
return DEFAULT_LANG;
}
async function loadLanguageResources(lang) {
if (loadedLangs[lang]) {
return;
}
try {
const resp = await fetch(RESOURCES_PREFIX + '/' + lang + '.json', {
cache: 'no-cache'
});
if (!resp.ok) {
console.warn('加载语言包失败:', lang, resp.status);
return;
}
const data = await resp.json();
if (typeof i18next !== 'undefined') {
i18next.addResourceBundle(lang, 'translation', data, true, true);
}
loadedLangs[lang] = true;
} catch (e) {
console.error('加载语言包异常:', lang, e);
}
}
function applyTranslations(root) {
if (typeof i18next === 'undefined') return;
const container = root || document;
if (!container) return;
const elements = container.querySelectorAll('[data-i18n]');
elements.forEach(function (el) {
const key = el.getAttribute('data-i18n');
if (!key) return;
const skipText = el.getAttribute('data-i18n-skip-text') === 'true';
const isFormControl = (el.tagName === 'INPUT' || el.tagName === 'TEXTAREA');
const attrList = el.getAttribute('data-i18n-attr');
const text = i18next.t(key);
// 仅当未使用 data-i18n-attr 时才替换元素文本内容(否则会覆盖卡片内的数字、子节点等)
// input/textarea:永不设置 textContent(会变成 value),只更新属性
if (!attrList && !skipText && !isFormControl && text && typeof text === 'string') {
el.textContent = text;
}
if (attrList) {
attrList.split(',').map(function (s) { return s.trim(); }).forEach(function (attr) {
if (!attr) return;
if (text && typeof text === 'string') {
el.setAttribute(attr, text);
}
});
}
});
// 对话输入框:若 value 与 placeholder 相同,清空 value 以便正确显示占位提示
try {
const chatInput = document.getElementById('chat-input');
if (chatInput && chatInput.tagName === 'TEXTAREA') {
const ph = (chatInput.getAttribute('placeholder') || '').trim();
if (ph && chatInput.value.trim() === ph) {
chatInput.value = '';
}
}
} catch (e) { /* ignore */ }
// 更新 html lang 属性
try {
if (document && document.documentElement) {
document.documentElement.lang = i18next.language || DEFAULT_LANG;
}
} catch (e) {
// ignore
}
}
function updateLangLabel() {
const label = document.getElementById('current-lang-label');
if (!label || typeof i18next === 'undefined') return;
const lang = (i18next.language || DEFAULT_LANG).toLowerCase();
if (lang.indexOf('zh') === 0) {
label.textContent = '中文';
} else {
label.textContent = 'English';
}
}
function closeLangDropdown() {
const dropdown = document.getElementById('lang-dropdown');
if (dropdown) {
dropdown.style.display = 'none';
}
}
function handleGlobalClickForLangDropdown(ev) {
const dropdown = document.getElementById('lang-dropdown');
const btn = document.querySelector('.lang-switcher-btn');
if (!dropdown || dropdown.style.display !== 'block') return;
const target = ev.target;
if (btn && btn.contains(target)) {
return;
}
if (!dropdown.contains(target)) {
closeLangDropdown();
}
}
async function changeLanguage(lang) {
if (typeof i18next === 'undefined') return;
const current = i18next.language || DEFAULT_LANG;
if (lang === current) return;
await loadLanguageResources(lang);
await i18next.changeLanguage(lang);
try {
localStorage.setItem(STORAGE_KEY, lang);
} catch (e) {
console.warn('无法保存语言设置:', e);
}
applyTranslations(document);
updateLangLabel();
try {
document.dispatchEvent(new CustomEvent('languagechange', { detail: { lang: lang } }));
} catch (e) { /* ignore */ }
}
async function initI18n() {
if (typeof i18next === 'undefined') {
console.warn('i18next 未加载,跳过前端国际化初始化');
return;
}
const initialLang = detectInitialLang();
await i18next.init({
lng: initialLang,
fallbackLng: DEFAULT_LANG,
debug: false,
resources: {}
});
await loadLanguageResources(initialLang);
applyTranslations(document);
updateLangLabel();
// 导出全局函数供其他脚本调用(支持插值参数,如 _t('key', { count: 2 })
window.t = function (key, opts) {
if (typeof i18next === 'undefined') return key;
return i18next.t(key, opts);
};
window.changeLanguage = changeLanguage;
window.applyTranslations = applyTranslations;
// 语言切换下拉支持
window.toggleLangDropdown = function () {
const dropdown = document.getElementById('lang-dropdown');
if (!dropdown) return;
if (dropdown.style.display === 'block') {
dropdown.style.display = 'none';
} else {
dropdown.style.display = 'block';
}
};
window.onLanguageSelect = function (lang) {
changeLanguage(lang);
closeLangDropdown();
};
document.addEventListener('click', handleGlobalClickForLangDropdown);
}
document.addEventListener('DOMContentLoaded', function () {
// i18n 初始化在 DOM Ready 后执行
initI18n().catch(function (e) {
console.error('初始化国际化失败:', e);
});
});
})();