2025-06-03 06:50:19 +08:00
|
|
|
|
/**
|
|
|
|
|
* 國際化(i18n)模組
|
|
|
|
|
* =================
|
|
|
|
|
*
|
|
|
|
|
* 處理多語言支援和界面文字翻譯
|
|
|
|
|
* 從後端 /api/translations 載入翻譯數據
|
|
|
|
|
*/
|
|
|
|
|
|
|
|
|
|
class I18nManager {
|
|
|
|
|
constructor() {
|
|
|
|
|
this.currentLanguage = 'zh-TW';
|
|
|
|
|
this.translations = {};
|
|
|
|
|
this.loadingPromise = null;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
async init() {
|
|
|
|
|
// 從 localStorage 載入語言偏好
|
|
|
|
|
const savedLanguage = localStorage.getItem('language');
|
|
|
|
|
if (savedLanguage) {
|
|
|
|
|
this.currentLanguage = savedLanguage;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
// 載入翻譯數據
|
|
|
|
|
await this.loadTranslations();
|
|
|
|
|
|
|
|
|
|
// 應用翻譯
|
|
|
|
|
this.applyTranslations();
|
|
|
|
|
|
|
|
|
|
// 設置語言選擇器
|
|
|
|
|
this.setupLanguageSelectors();
|
|
|
|
|
|
|
|
|
|
// 延遲一點再更新動態內容,確保應用程式已初始化
|
|
|
|
|
setTimeout(() => {
|
|
|
|
|
this.updateDynamicContent();
|
|
|
|
|
}, 100);
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
async loadTranslations() {
|
|
|
|
|
if (this.loadingPromise) {
|
|
|
|
|
return this.loadingPromise;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
this.loadingPromise = fetch('/api/translations')
|
|
|
|
|
.then(response => response.json())
|
|
|
|
|
.then(data => {
|
|
|
|
|
this.translations = data;
|
|
|
|
|
console.log('翻譯數據載入完成:', Object.keys(this.translations));
|
|
|
|
|
|
|
|
|
|
// 檢查當前語言是否有翻譯數據
|
|
|
|
|
if (!this.translations[this.currentLanguage] || Object.keys(this.translations[this.currentLanguage]).length === 0) {
|
|
|
|
|
console.warn(`當前語言 ${this.currentLanguage} 沒有翻譯數據,回退到 zh-TW`);
|
|
|
|
|
this.currentLanguage = 'zh-TW';
|
|
|
|
|
}
|
|
|
|
|
})
|
|
|
|
|
.catch(error => {
|
|
|
|
|
console.error('載入翻譯數據失敗:', error);
|
|
|
|
|
// 使用最小的回退翻譯
|
|
|
|
|
this.translations = this.getMinimalFallbackTranslations();
|
|
|
|
|
});
|
|
|
|
|
|
|
|
|
|
return this.loadingPromise;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
getMinimalFallbackTranslations() {
|
|
|
|
|
// 最小的回退翻譯,只包含關鍵項目
|
|
|
|
|
return {
|
|
|
|
|
'zh-TW': {
|
|
|
|
|
'app': {
|
2025-06-03 20:59:45 +08:00
|
|
|
|
'title': 'MCP Feedback Enhanced',
|
2025-06-03 06:50:19 +08:00
|
|
|
|
'projectDirectory': '專案目錄'
|
|
|
|
|
},
|
|
|
|
|
'tabs': {
|
|
|
|
|
'feedback': '💬 回饋',
|
|
|
|
|
'summary': '📋 AI 摘要',
|
|
|
|
|
'command': '⚡ 命令',
|
|
|
|
|
'settings': '⚙️ 設定'
|
|
|
|
|
},
|
|
|
|
|
'buttons': {
|
|
|
|
|
'cancel': '❌ 取消',
|
|
|
|
|
'submit': '✅ 提交回饋'
|
|
|
|
|
},
|
|
|
|
|
'settings': {
|
|
|
|
|
'language': '語言'
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
};
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
// 支援巢狀鍵值的翻譯函數
|
|
|
|
|
t(key, defaultValue = '') {
|
|
|
|
|
const langData = this.translations[this.currentLanguage] || {};
|
|
|
|
|
return this.getNestedValue(langData, key) || defaultValue || key;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
getNestedValue(obj, path) {
|
|
|
|
|
return path.split('.').reduce((current, key) => {
|
|
|
|
|
return current && current[key] !== undefined ? current[key] : null;
|
|
|
|
|
}, obj);
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
setLanguage(language) {
|
|
|
|
|
if (this.translations[language]) {
|
|
|
|
|
this.currentLanguage = language;
|
|
|
|
|
localStorage.setItem('language', language);
|
|
|
|
|
this.applyTranslations();
|
|
|
|
|
|
|
|
|
|
// 更新語言選擇器(只更新設定頁面的)
|
|
|
|
|
const selector = document.getElementById('settingsLanguageSelect');
|
|
|
|
|
if (selector) {
|
|
|
|
|
selector.value = language;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
// 更新 HTML lang 屬性
|
|
|
|
|
document.documentElement.lang = language;
|
|
|
|
|
|
|
|
|
|
console.log('語言已切換到:', language);
|
|
|
|
|
} else {
|
|
|
|
|
console.warn('不支援的語言:', language);
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
applyTranslations() {
|
|
|
|
|
// 翻譯所有有 data-i18n 屬性的元素
|
|
|
|
|
const elements = document.querySelectorAll('[data-i18n]');
|
|
|
|
|
elements.forEach(element => {
|
|
|
|
|
const key = element.getAttribute('data-i18n');
|
|
|
|
|
const translation = this.t(key);
|
|
|
|
|
if (translation && translation !== key) {
|
|
|
|
|
element.textContent = translation;
|
|
|
|
|
}
|
|
|
|
|
});
|
|
|
|
|
|
|
|
|
|
// 翻譯有 data-i18n-placeholder 屬性的元素
|
|
|
|
|
const placeholderElements = document.querySelectorAll('[data-i18n-placeholder]');
|
|
|
|
|
placeholderElements.forEach(element => {
|
|
|
|
|
const key = element.getAttribute('data-i18n-placeholder');
|
|
|
|
|
const translation = this.t(key);
|
|
|
|
|
if (translation && translation !== key) {
|
|
|
|
|
element.placeholder = translation;
|
|
|
|
|
}
|
|
|
|
|
});
|
|
|
|
|
|
|
|
|
|
// 更新動態內容
|
|
|
|
|
this.updateDynamicContent();
|
|
|
|
|
|
|
|
|
|
console.log('翻譯已應用:', this.currentLanguage);
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
updateDynamicContent() {
|
|
|
|
|
// 只更新終端歡迎信息,不要覆蓋 AI 摘要
|
|
|
|
|
this.updateTerminalWelcome();
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
updateTerminalWelcome() {
|
|
|
|
|
const commandOutput = document.getElementById('commandOutput');
|
|
|
|
|
if (commandOutput && window.feedbackApp) {
|
|
|
|
|
const welcomeTemplate = this.t('dynamic.terminalWelcome');
|
|
|
|
|
if (welcomeTemplate && welcomeTemplate !== 'dynamic.terminalWelcome') {
|
|
|
|
|
const welcomeMessage = welcomeTemplate.replace('{sessionId}', window.feedbackApp.sessionId || 'unknown');
|
|
|
|
|
commandOutput.textContent = welcomeMessage;
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
setupLanguageSelectors() {
|
|
|
|
|
// 舊版下拉選擇器(兼容性保留)
|
|
|
|
|
const selector = document.getElementById('settingsLanguageSelect');
|
|
|
|
|
if (selector) {
|
|
|
|
|
// 設置當前值
|
|
|
|
|
selector.value = this.currentLanguage;
|
|
|
|
|
|
|
|
|
|
// 添加事件監聽器
|
|
|
|
|
selector.addEventListener('change', (e) => {
|
|
|
|
|
this.setLanguage(e.target.value);
|
|
|
|
|
});
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
// 新版現代化語言選擇器
|
|
|
|
|
const languageOptions = document.querySelectorAll('.language-option');
|
|
|
|
|
if (languageOptions.length > 0) {
|
|
|
|
|
// 設置當前語言的活躍狀態
|
|
|
|
|
languageOptions.forEach(option => {
|
|
|
|
|
const lang = option.getAttribute('data-lang');
|
|
|
|
|
if (lang === this.currentLanguage) {
|
|
|
|
|
option.classList.add('active');
|
|
|
|
|
} else {
|
|
|
|
|
option.classList.remove('active');
|
|
|
|
|
}
|
|
|
|
|
});
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
getCurrentLanguage() {
|
|
|
|
|
return this.currentLanguage;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
getAvailableLanguages() {
|
|
|
|
|
return Object.keys(this.translations);
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
// 創建全域實例
|
|
|
|
|
window.i18nManager = new I18nManager();
|