mirror of
https://github.com/Ed1s0nZ/CyberStrikeAI.git
synced 2026-04-21 18:26:38 +02:00
203 lines
6.9 KiB
JavaScript
203 lines
6.9 KiB
JavaScript
// 前端国际化初始化(基于 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);
|
||
});
|
||
});
|
||
})();
|
||
|