mirror of
https://github.com/Ed1s0nZ/CyberStrikeAI.git
synced 2026-07-09 22:08:52 +02:00
Add files via upload
This commit is contained in:
@@ -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);
|
||||
});
|
||||
});
|
||||
})();
|
||||
|
||||
Reference in New Issue
Block a user