diff --git a/scripts/validate_message_codes.py b/scripts/validate_message_codes.py new file mode 100644 index 0000000..2998055 --- /dev/null +++ b/scripts/validate_message_codes.py @@ -0,0 +1,202 @@ +#!/usr/bin/env python3 +""" +訊息代碼驗證腳本 + +驗證後端訊息代碼、前端常量和翻譯文件的一致性。 +確保所有訊息代碼都有對應的定義和翻譯。 + +使用方式: + python scripts/validate_message_codes.py +""" + +import json +import re +import sys +from pathlib import Path + + +def extract_backend_codes(): + """從後端 Python 文件中提取所有訊息代碼""" + codes = set() + + # 讀取 MessageCodes 類別 + message_codes_file = Path( + "src/mcp_feedback_enhanced/web/constants/message_codes.py" + ) + if message_codes_file.exists(): + content = message_codes_file.read_text(encoding="utf-8") + # 匹配形如 SESSION_FEEDBACK_SUBMITTED = "session.feedbackSubmitted" + pattern = r'([A-Z_]+)\s*=\s*"([^"]+)"' + matches = re.findall(pattern, content) + for constant_name, code in matches: + codes.add(code) + + return codes + + +def extract_frontend_codes(): + """從前端 JavaScript 文件中提取所有訊息代碼""" + codes = set() + + # 讀取 message-codes.js + message_codes_js = Path( + "src/mcp_feedback_enhanced/web/static/js/modules/constants/message-codes.js" + ) + if message_codes_js.exists(): + content = message_codes_js.read_text(encoding="utf-8") + # 匹配形如 FEEDBACK_SUBMITTED: 'session.feedbackSubmitted' + pattern = r'[A-Z_]+:\s*[\'"]([^\'"]+)[\'"]' + matches = re.findall(pattern, content) + codes.update(matches) + + # 讀取 utils.js 中的 fallback 訊息 + utils_js = Path("src/mcp_feedback_enhanced/web/static/js/modules/utils.js") + if utils_js.exists(): + content = utils_js.read_text(encoding="utf-8") + # 匹配 fallbackMessages 物件中的 key + fallback_section = re.search( + r"fallbackMessages\s*=\s*\{([^}]+)\}", content, re.DOTALL + ) + if fallback_section: + pattern = r'[\'"]([^\'"]+)[\'"]:\s*[\'"][^\'"]+[\'"]' + matches = re.findall(pattern, fallback_section.group(1)) + codes.update(matches) + + return codes + + +def extract_translation_keys(locale="zh-TW"): + """從翻譯文件中提取所有 key""" + keys = set() + + translation_file = Path( + f"src/mcp_feedback_enhanced/web/locales/{locale}/translation.json" + ) + if translation_file.exists(): + try: + data = json.loads(translation_file.read_text(encoding="utf-8")) + + def extract_keys_recursive(obj, prefix=""): + """遞迴提取所有 key""" + if isinstance(obj, dict): + for key, value in obj.items(): + full_key = f"{prefix}.{key}" if prefix else key + if isinstance(value, dict): + extract_keys_recursive(value, full_key) + else: + keys.add(full_key) + + extract_keys_recursive(data) + except json.JSONDecodeError as e: + print(f"❌ 無法解析翻譯文件 {translation_file}: {e}") + + return keys + + +def validate_message_codes(): + """執行驗證""" + print("🔍 開始驗證訊息代碼一致性...\n") + + # 提取所有代碼 + backend_codes = extract_backend_codes() + frontend_codes = extract_frontend_codes() + + # 提取所有語言的翻譯 key + locales = ["zh-TW", "en", "zh-CN"] + translation_keys = {} + for locale in locales: + translation_keys[locale] = extract_translation_keys(locale) + + # 統計資訊 + print("📊 統計資訊:") + print(f" - 後端訊息代碼數量: {len(backend_codes)}") + print(f" - 前端訊息代碼數量: {len(frontend_codes)}") + for locale in locales: + print(f" - {locale} 翻譯 key 數量: {len(translation_keys[locale])}") + print() + + # 驗證後端代碼是否都有前端定義 + print("🔍 檢查後端代碼是否都有前端定義...") + missing_in_frontend = backend_codes - frontend_codes + if missing_in_frontend: + print("❌ 以下後端代碼在前端沒有定義:") + for code in sorted(missing_in_frontend): + print(f" - {code}") + else: + print("✅ 所有後端代碼都有前端定義") + print() + + # 驗證前端代碼是否都有翻譯 + print("🔍 檢查前端代碼是否都有翻譯...") + all_frontend_codes = backend_codes | frontend_codes + + for locale in locales: + print(f"\n 檢查 {locale} 翻譯:") + missing_translations = set() + + for code in all_frontend_codes: + if code not in translation_keys[locale]: + missing_translations.add(code) + + if missing_translations: + print(" ❌ 缺少以下翻譯:") + for code in sorted(missing_translations): + print(f" - {code}") + else: + print(" ✅ 所有代碼都有翻譯") + + # 檢查是否有多餘的翻譯 + print("\n🔍 檢查是否有多餘的翻譯...") + for locale in locales: + # 過濾掉非訊息代碼的 key(如 buttons, labels 等) + message_keys = { + k + for k in translation_keys[locale] + if any( + k.startswith(prefix) + for prefix in [ + "system.", + "session.", + "settings.", + "error.", + "command.", + "file.", + "prompt.", + "notification.", + ] + ) + } + + extra_translations = message_keys - all_frontend_codes + if extra_translations: + print(f"\n {locale} 有多餘的翻譯:") + for key in sorted(extra_translations): + print(f" - {key}") + + print("\n✅ 驗證完成!") + + # 返回是否有錯誤 + return len(missing_in_frontend) == 0 and all( + len( + [ + code + for code in all_frontend_codes + if code not in translation_keys[locale] + ] + ) + == 0 + for locale in locales + ) + + +if __name__ == "__main__": + # 切換到專案根目錄 + script_dir = Path(__file__).parent + project_root = script_dir.parent + import os + + os.chdir(project_root) + + # 執行驗證 + success = validate_message_codes() + sys.exit(0 if success else 1) diff --git a/src/mcp_feedback_enhanced/web/constants/__init__.py b/src/mcp_feedback_enhanced/web/constants/__init__.py new file mode 100644 index 0000000..550b401 --- /dev/null +++ b/src/mcp_feedback_enhanced/web/constants/__init__.py @@ -0,0 +1,8 @@ +""" +Web 常量模組 +""" + +from .message_codes import MessageCodes, get_message_code + + +__all__ = ["MessageCodes", "get_message_code"] diff --git a/src/mcp_feedback_enhanced/web/constants/message_codes.py b/src/mcp_feedback_enhanced/web/constants/message_codes.py new file mode 100644 index 0000000..28df091 --- /dev/null +++ b/src/mcp_feedback_enhanced/web/constants/message_codes.py @@ -0,0 +1,173 @@ +""" +統一的訊息代碼定義 + +這個模組定義了所有後端使用的訊息代碼常量。 +前端會根據這些代碼顯示對應的本地化訊息。 + +使用方式: + from ..constants import MessageCodes, get_message_code + + # 使用常量 + code = MessageCodes.SESSION_FEEDBACK_SUBMITTED + + # 或使用輔助函數 + code = get_message_code("SESSION_FEEDBACK_SUBMITTED") +""" + + +class MessageCodes: + """訊息代碼常量類""" + + # ========== 系統相關 ========== + SYSTEM_CONNECTION_ESTABLISHED = "system.connectionEstablished" + SYSTEM_CONNECTION_LOST = "system.connectionLost" + SYSTEM_CONNECTION_RECONNECTING = "system.connectionReconnecting" + SYSTEM_CONNECTION_RECONNECTED = "system.connectionReconnected" + SYSTEM_CONNECTION_FAILED = "system.connectionFailed" + SYSTEM_WEBSOCKET_ERROR = "system.websocketError" + SYSTEM_WEBSOCKET_READY = "system.websocketReady" + SYSTEM_MEMORY_PRESSURE = "system.memoryPressure" + SYSTEM_SHUTDOWN = "system.shutdown" + SYSTEM_PROCESS_KILLED = "system.processKilled" + SYSTEM_HEARTBEAT_STOPPED = "system.heartbeatStopped" + + # ========== 會話相關 ========== + SESSION_NO_ACTIVE = "session.noActiveSession" + SESSION_CREATED = "session.created" + SESSION_UPDATED = "session.updated" + SESSION_EXPIRED = "session.expired" + SESSION_TIMEOUT = "session.timeout" + SESSION_CLEANED = "session.cleaned" + SESSION_FEEDBACK_SUBMITTED = "session.feedbackSubmitted" + SESSION_USER_MESSAGE_RECORDED = "session.userMessageRecorded" + SESSION_HISTORY_SAVED = "session.historySaved" + SESSION_HISTORY_LOADED = "session.historyLoaded" + SESSION_MANUAL_CLEANUP = "session.manualCleanup" + SESSION_ERROR_CLEANUP = "session.errorCleanup" + + # ========== 設定相關 ========== + SETTINGS_SAVED = "settingsAPI.saved" + SETTINGS_LOADED = "settingsAPI.loaded" + SETTINGS_CLEARED = "settingsAPI.cleared" + SETTINGS_SAVE_FAILED = "settingsAPI.saveFailed" + SETTINGS_LOAD_FAILED = "settingsAPI.loadFailed" + SETTINGS_CLEAR_FAILED = "settingsAPI.clearFailed" + SETTINGS_SET_FAILED = "settingsAPI.setFailed" + SETTINGS_INVALID_VALUE = "settingsAPI.invalidValue" + SETTINGS_LOG_LEVEL_UPDATED = "settingsAPI.logLevelUpdated" + SETTINGS_INVALID_LOG_LEVEL = "settingsAPI.invalidLogLevel" + + # ========== 命令執行相關 ========== + COMMAND_EXECUTING = "commandStatus.executing" + COMMAND_COMPLETED = "commandStatus.completed" + COMMAND_FAILED = "commandStatus.failed" + COMMAND_INVALID = "commandStatus.invalid" + COMMAND_OUTPUT_RECEIVED = "commandStatus.outputReceived" + COMMAND_ERROR = "commandStatus.error" + + # ========== 錯誤相關 ========== + ERROR_GENERIC = "error.generic" + ERROR_NETWORK = "error.network" + ERROR_SERVER = "error.server" + ERROR_TIMEOUT = "error.timeout" + ERROR_INVALID_INPUT = "error.invalidInput" + ERROR_OPERATION_FAILED = "error.operationFailed" + ERROR_USER_MESSAGE_FAILED = "error.userMessageFailed" + ERROR_GET_SESSIONS_FAILED = "error.getSessionsFailed" + ERROR_GET_LOG_LEVEL_FAILED = "error.getLogLevelFailed" + ERROR_RESOURCE_CLEANUP = "error.resourceCleanup" + ERROR_PROCESSING = "error.processing" + + # ========== 檔案相關 ========== + FILE_UPLOAD_SUCCESS = "file.uploadSuccess" + FILE_UPLOAD_FAILED = "file.uploadFailed" + FILE_SIZE_TOO_LARGE = "file.sizeTooLarge" + FILE_TYPE_NOT_SUPPORTED = "file.typeNotSupported" + FILE_PROCESSING = "file.processing" + FILE_REMOVED = "file.removed" + + # ========== 提示詞相關 ========== + PROMPT_SAVED = "prompt.saved" + PROMPT_DELETED = "prompt.deleted" + PROMPT_APPLIED = "prompt.applied" + PROMPT_IMPORT_SUCCESS = "prompt.importSuccess" + PROMPT_IMPORT_FAILED = "prompt.importFailed" + PROMPT_EXPORT_SUCCESS = "prompt.exportSuccess" + PROMPT_VALIDATION_FAILED = "prompt.validationFailed" + + +# 向後兼容的映射表(從舊的 key 到新的常量名稱) +LEGACY_KEY_MAPPING = { + # feedback_session.py 的舊 key + "FEEDBACK_SUBMITTED": "SESSION_FEEDBACK_SUBMITTED", + "SESSION_CLEANUP": "SESSION_CLEANED", + "TIMEOUT_CLEANUP": "SESSION_TIMEOUT", + "EXPIRED_CLEANUP": "SESSION_EXPIRED", + "MEMORY_PRESSURE_CLEANUP": "SYSTEM_MEMORY_PRESSURE", + "MANUAL_CLEANUP": "SESSION_MANUAL_CLEANUP", + "ERROR_CLEANUP": "SESSION_ERROR_CLEANUP", + "SHUTDOWN_CLEANUP": "SYSTEM_SHUTDOWN", + "COMMAND_EXECUTING": "COMMAND_EXECUTING", + "COMMAND_COMPLETED": "COMMAND_COMPLETED", + "COMMAND_FAILED": "COMMAND_FAILED", + "COMMAND_INVALID": "COMMAND_INVALID", + "COMMAND_ERROR": "COMMAND_ERROR", + "PROCESS_KILLED": "SYSTEM_PROCESS_KILLED", + "RESOURCE_CLEANUP_ERROR": "ERROR_RESOURCE_CLEANUP", + "HEARTBEAT_STOPPED": "SYSTEM_HEARTBEAT_STOPPED", + "PROCESSING_ERROR": "ERROR_PROCESSING", + "WEBSOCKET_READY": "SYSTEM_WEBSOCKET_READY", + # main_routes.py 的舊 key + "no_active_session": "SESSION_NO_ACTIVE", + "websocket_connected": "SYSTEM_CONNECTION_ESTABLISHED", + "new_session_created": "SESSION_CREATED", + "user_message_recorded": "SESSION_USER_MESSAGE_RECORDED", + "add_user_message_failed": "ERROR_USER_MESSAGE_FAILED", + "settings_saved": "SETTINGS_SAVED", + "save_failed": "SETTINGS_SAVE_FAILED", + "load_failed": "SETTINGS_LOAD_FAILED", + "settings_cleared": "SETTINGS_CLEARED", + "clear_failed": "SETTINGS_CLEAR_FAILED", + "session_history_saved": "SESSION_HISTORY_SAVED", + "get_sessions_failed": "ERROR_GET_SESSIONS_FAILED", + "get_log_level_failed": "ERROR_GET_LOG_LEVEL_FAILED", + "invalid_log_level": "SETTINGS_INVALID_LOG_LEVEL", + "log_level_updated": "SETTINGS_LOG_LEVEL_UPDATED", + "set_failed": "SETTINGS_SET_FAILED", +} + + +def get_message_code(key: str) -> str: + """ + 獲取訊息代碼 + + 支援三種輸入方式: + 1. 直接使用常量名稱:get_message_code("SESSION_FEEDBACK_SUBMITTED") + 2. 使用舊的 key(向後兼容):get_message_code("FEEDBACK_SUBMITTED") + 3. 使用小寫的舊 key(向後兼容):get_message_code("feedback_submitted") + + Args: + key: 訊息 key 或常量名稱 + + Returns: + 訊息代碼字串(例如:"session.feedbackSubmitted") + """ + # 嘗試直接從 MessageCodes 獲取 + if hasattr(MessageCodes, key): + return str(getattr(MessageCodes, key)) + + # 嘗試從映射表獲取(支援大寫和小寫) + upper_key = key.upper() + if upper_key in LEGACY_KEY_MAPPING: + constant_name = LEGACY_KEY_MAPPING[upper_key] + if hasattr(MessageCodes, constant_name): + return str(getattr(MessageCodes, constant_name)) + + # 如果是小寫的 key,也嘗試映射 + if key in LEGACY_KEY_MAPPING: + constant_name = LEGACY_KEY_MAPPING[key] + if hasattr(MessageCodes, constant_name): + return str(getattr(MessageCodes, constant_name)) + + # 如果都找不到,返回一個預設格式 + return f"unknown.{key}" diff --git a/src/mcp_feedback_enhanced/web/locales/en/translation.json b/src/mcp_feedback_enhanced/web/locales/en/translation.json index fbfb975..55454cf 100644 --- a/src/mcp_feedback_enhanced/web/locales/en/translation.json +++ b/src/mcp_feedback_enhanced/web/locales/en/translation.json @@ -50,7 +50,8 @@ "sendFailed": "Send failed, please retry", "noContent": "No content to copy", "copySuccess": "Content copied to clipboard", - "copyFailed": "Failed to copy" + "copyFailed": "Failed to copy", + "provideTextOrImage": "Please provide feedback text or upload an image" }, "summary": { "title": "📋 AI Work Summary", @@ -96,7 +97,7 @@ "summaryTitle": "📋 AI Work Summary", "feedbackTitle": "💬 Provide Feedback" }, - "settings": { + "settingsUI": { "title": "⚙️ Settings", "language": "🌍 Language", "currentLanguage": "Current Language", @@ -195,7 +196,7 @@ "upload": "Upload", "download": "Download" }, - "session": { + "sessionTimeout": { "timeout": "⏰ Session has timed out, interface will close automatically", "timeoutWarning": "Session is about to timeout", "timeoutDescription": "Due to prolonged inactivity, the session has timed out. The interface will automatically close in 3 seconds.", @@ -261,7 +262,10 @@ "clearSuccess": "Session history cleared successfully", "clearTitle": "Clear all session history records", "description": "Manage locally stored session history records, including retention period settings and data export functionality", - "exportDescription": "Export or clear locally stored session history records" + "exportDescription": "Export or clear locally stored session history records", + "exportFailed": "Export failed: {error}", + "clearFailed": "Clear failed: {error}", + "clearFailedGeneric": "Clear failed" }, "retention": { "24hours": "24 hours", @@ -296,6 +300,14 @@ "clearAllTitle": "Clear all user message records from all sessions", "confirmClearAll": "Are you sure you want to clear all user message records from all sessions? This action cannot be undone.", "clearSuccess": "User message records cleared successfully" + }, + "noActiveSession": "No active session data", + "sessionNotFound": "Session data not found", + "currentSession": { + "noData": "No current session data", + "noUserMessages": "Current session has no user message records", + "copyFailed": "Copy failed, please try again", + "dataManagerNotInit": "Data manager not initialized" } }, "connectionMonitor": { @@ -440,7 +452,8 @@ "countdownLabel": "Submit Countdown", "pauseCountdown": "Pause Countdown", "resumeCountdown": "Resume Countdown", - "paused": "Auto submit paused" + "paused": "Auto submit paused", + "autoCommitNoPrompt": "Please select a prompt as auto-submit content first" }, "audio": { "notification": { @@ -514,5 +527,95 @@ "permissionRequired": "Please grant notification permission first", "criticalTitle": "MCP Feedback - Warning" } + }, + "system": { + "connectionEstablished": "WebSocket connection established", + "connectionLost": "WebSocket connection lost", + "connectionReconnecting": "Reconnecting...", + "connectionReconnected": "Reconnected", + "connectionFailed": "Connection failed", + "websocketError": "WebSocket error", + "memoryPressure": "Memory pressure cleanup", + "shutdown": "System shutdown", + "processKilled": "Process killed", + "heartbeatStopped": "Heartbeat stopped", + "websocketReady": "WebSocket ready" + }, + "session": { + "noActiveSession": "No active session", + "created": "New MCP session created, page will refresh automatically", + "updated": "Session updated", + "expired": "Session expired", + "timeout": "Session timed out", + "cleaned": "Session cleaned", + "feedbackSubmitted": "Feedback submitted successfully", + "userMessageRecorded": "User message recorded", + "historySaved": "Session history saved ({{count}} sessions)", + "historyLoaded": "Session history loaded" + }, + "settingsAPI": { + "saved": "Settings saved", + "loaded": "Settings loaded", + "cleared": "Settings cleared", + "saveFailed": "Save failed", + "loadFailed": "Load failed", + "clearFailed": "Clear failed", + "setFailed": "Set failed", + "invalidValue": "Invalid setting value", + "logLevelUpdated": "Log level updated", + "invalidLogLevel": "Invalid log level, must be one of DEBUG, INFO, WARN, ERROR" + }, + "file": { + "uploadSuccess": "File uploaded successfully", + "uploadFailed": "File upload failed", + "sizeTooLarge": "File size exceeds limit", + "typeNotSupported": "File type not supported", + "processing": "Processing file...", + "removed": "File removed" + }, + "prompt": { + "saved": "Prompt saved", + "deleted": "Prompt deleted", + "applied": "Prompt applied: {{name}}", + "importSuccess": "Prompts imported successfully", + "importFailed": "Prompts import failed", + "exportSuccess": "Prompts exported successfully", + "validationFailed": "Prompt validation failed" + }, + "error": { + "generic": "An error occurred: {{error}}", + "network": "Network error", + "server": "Server error", + "timeout": "Operation timed out", + "invalidInput": "Invalid input", + "operationFailed": "Operation failed", + "userMessageFailed": "Failed to add user message", + "getSessionsFailed": "Failed to get sessions", + "getLogLevelFailed": "Failed to get log level", + "command": "Command execution error", + "resourceCleanup": "Resource cleanup error", + "processing": "Processing error" + }, + "commandStatus": { + "executing": "Executing command...", + "completed": "Command completed", + "failed": "Command failed", + "outputReceived": "Output received", + "invalid": "Invalid command", + "error": "Command execution error" + }, + "utils": { + "copySuccess": "Copied to clipboard", + "copyError": "Copy failed" + }, + "fileUpload": { + "fileSizeExceeded": "Image size exceeds limit ({limit}): {filename}", + "maxFilesExceeded": "Maximum {maxFiles} files can be uploaded", + "processingFailed": "File processing failed, please retry" + }, + "aria": { + "toggleAutoSubmit": "Toggle auto submit", + "toggleNotification": "Toggle notification", + "toggleAudioNotification": "Toggle audio notification" } } diff --git a/src/mcp_feedback_enhanced/web/locales/zh-CN/translation.json b/src/mcp_feedback_enhanced/web/locales/zh-CN/translation.json index ae68858..c4b90a4 100644 --- a/src/mcp_feedback_enhanced/web/locales/zh-CN/translation.json +++ b/src/mcp_feedback_enhanced/web/locales/zh-CN/translation.json @@ -50,7 +50,8 @@ "sendFailed": "发送失败,请重试", "noContent": "没有可复制的内容", "copySuccess": "内容已复制到剪贴板", - "copyFailed": "复制失败" + "copyFailed": "复制失败", + "provideTextOrImage": "请提供反馈文字或上传图片" }, "summary": { "title": "📋 AI 工作摘要", @@ -96,7 +97,7 @@ "summaryTitle": "📋 AI 工作摘要", "feedbackTitle": "💬 提供反馈" }, - "settings": { + "settingsUI": { "title": "⚙️ 设定", "language": "🌍 语言", "currentLanguage": "当前语言", @@ -121,7 +122,8 @@ "resetError": "重置设定时发生错误", "timeout": "连线逾时 (秒)", "autorefresh": "自动重新整理", - "debug": "除错模式" + "debug": "除错模式", + "autoCommitNoPrompt": "请先选择一个提示词作为自动提交内容" }, "languages": { "zh-TW": "繁體中文", @@ -195,7 +197,7 @@ "upload": "上传", "download": "下载" }, - "session": { + "sessionTimeout": { "timeout": "⏰ 会话已超时,界面将自动关闭", "timeoutWarning": "会话即将超时", "timeoutDescription": "由于长时间无响应,会话已超时。界面将在 3 秒后自动关闭。", @@ -261,7 +263,10 @@ "clearSuccess": "会话历史已清空", "clearTitle": "清空所有会话历史记录", "description": "管理本地存储的会话历史记录,包括保存期限设定和数据导出功能", - "exportDescription": "导出或清空本地存储的会话历史记录" + "exportDescription": "导出或清空本地存储的会话历史记录", + "exportFailed": "导出失败: {error}", + "clearFailed": "清空失败: {error}", + "clearFailedGeneric": "清空失败" }, "retention": { "24hours": "24 小时", @@ -296,6 +301,14 @@ "clearAllTitle": "清空所有会话的用户消息记录", "confirmClearAll": "确定要清空所有会话的用户消息记录吗?此操作无法撤销。", "clearSuccess": "用户消息记录已清空" + }, + "noActiveSession": "目前没有活跃的会话数据", + "sessionNotFound": "找不到会话资料", + "currentSession": { + "noData": "没有当前会话数据", + "noUserMessages": "当前会话没有用户消息记录", + "copyFailed": "复制失败,请重试", + "dataManagerNotInit": "数据管理器未初始化" } }, "connectionMonitor": { @@ -513,5 +526,82 @@ "permissionRequired": "请先授权通知权限", "criticalTitle": "MCP Feedback - 警告" } + }, + "system": { + "connectionEstablished": "WebSocket 连接已建立", + "connectionLost": "WebSocket 连接已断开", + "connectionReconnecting": "正在重新连接...", + "connectionReconnected": "已重新连接", + "connectionFailed": "连接失败", + "websocketError": "WebSocket 错误" + }, + "session": { + "noActiveSession": "没有活跃会话", + "created": "新的 MCP 会话已创建,页面将自动刷新", + "updated": "会话已更新", + "expired": "会话已过期", + "timeout": "会话已超时", + "cleaned": "会话已清理", + "feedbackSubmitted": "反馈已成功提交", + "userMessageRecorded": "用户消息已记录", + "historySaved": "会话历史已保存({{count}} 个会话)", + "historyLoaded": "会话历史已载入" + }, + "settingsAPI": { + "saved": "设置已保存", + "loaded": "设置已载入", + "cleared": "设置已清除", + "saveFailed": "保存失败", + "loadFailed": "加载失败", + "clearFailed": "清除失败", + "invalidValue": "无效的设置值", + "logLevelUpdated": "日志等级已更新", + "invalidLogLevel": "无效的日志等级,必须是 DEBUG, INFO, WARN, ERROR 之一" + }, + "file": { + "uploadSuccess": "文件上传成功", + "uploadFailed": "文件上传失败", + "sizeTooLarge": "文件大小超过限制", + "typeNotSupported": "不支持的文件类型", + "processing": "正在处理文件...", + "removed": "文件已移除" + }, + "prompt": { + "saved": "提示词已保存", + "deleted": "提示词已删除", + "applied": "已套用提示词:{{name}}", + "importSuccess": "提示词导入成功", + "importFailed": "提示词导入失败", + "exportSuccess": "提示词导出成功", + "validationFailed": "提示词验证失败" + }, + "error": { + "generic": "发生错误:{{error}}", + "network": "网络错误", + "server": "服务器错误", + "timeout": "操作超时", + "invalidInput": "输入无效", + "operationFailed": "操作失败" + }, + "commandStatus": { + "executing": "正在执行命令...", + "completed": "命令执行完成", + "failed": "命令执行失败", + "outputReceived": "已接收输出", + "invalid": "无效的命令" + }, + "utils": { + "copySuccess": "已复制到剪贴板", + "copyError": "复制失败" + }, + "fileUpload": { + "fileSizeExceeded": "图片大小超过限制 ({limit}): {filename}", + "maxFilesExceeded": "最多只能上传 {maxFiles} 个文件", + "processingFailed": "文件处理失败,请重试" + }, + "aria": { + "toggleAutoSubmit": "切换自动提交", + "toggleNotification": "切换通知", + "toggleAudioNotification": "切换音效通知" } } diff --git a/src/mcp_feedback_enhanced/web/locales/zh-TW/translation.json b/src/mcp_feedback_enhanced/web/locales/zh-TW/translation.json index 0d8073b..694a433 100644 --- a/src/mcp_feedback_enhanced/web/locales/zh-TW/translation.json +++ b/src/mcp_feedback_enhanced/web/locales/zh-TW/translation.json @@ -55,7 +55,8 @@ "sendFailed": "發送失敗,請重試", "noContent": "沒有可複製的內容", "copySuccess": "內容已複製到剪貼板", - "copyFailed": "複製失敗" + "copyFailed": "複製失敗", + "provideTextOrImage": "請提供回饋文字或上傳圖片" }, "summary": { "title": "📋 AI 工作摘要", @@ -101,7 +102,7 @@ "summaryTitle": "📋 AI 工作摘要", "feedbackTitle": "💬 提供回饋" }, - "settings": { + "settingsUI": { "title": "⚙️ 設定", "language": "🌍 語言", "currentLanguage": "當前語言", @@ -126,7 +127,8 @@ "resetError": "重置設定時發生錯誤", "timeout": "連線逾時 (秒)", "autorefresh": "自動重新整理", - "debug": "除錯模式" + "debug": "除錯模式", + "autoCommitNoPrompt": "請先選擇一個提示詞作為自動提交內容" }, "languages": { "zh-TW": "繁體中文", @@ -200,7 +202,7 @@ "upload": "上傳", "download": "下載" }, - "session": { + "sessionTimeout": { "timeout": "⏰ 會話已超時,介面將自動關閉", "timeoutWarning": "會話即將超時", "timeoutDescription": "由於長時間無回應,會話已超時。介面將在 3 秒後自動關閉。", @@ -266,7 +268,10 @@ "clearSuccess": "會話歷史已清空", "clearTitle": "清空所有會話歷史記錄", "description": "管理本地儲存的會話歷史記錄,包括保存期限設定和資料匯出功能", - "exportDescription": "匯出或清空本地儲存的會話歷史記錄" + "exportDescription": "匯出或清空本地儲存的會話歷史記錄", + "exportFailed": "匯出失敗: {error}", + "clearFailed": "清空失敗: {error}", + "clearFailedGeneric": "清空失敗" }, "retention": { "24hours": "24 小時", @@ -301,6 +306,14 @@ "clearAllTitle": "清空所有會話的用戶訊息記錄", "confirmClearAll": "確定要清空所有會話的用戶訊息記錄嗎?此操作無法復原。", "clearSuccess": "用戶訊息記錄已清空" + }, + "noActiveSession": "目前沒有活躍的會話數據", + "sessionNotFound": "找不到會話資料", + "currentSession": { + "noData": "沒有當前會話數據", + "noUserMessages": "當前會話沒有用戶消息記錄", + "copyFailed": "複製失敗,請重試", + "dataManagerNotInit": "數據管理器未初始化" } }, "connectionMonitor": { @@ -518,5 +531,95 @@ "permissionRequired": "請先授權通知權限", "criticalTitle": "MCP Feedback - 警告" } + }, + "system": { + "connectionEstablished": "WebSocket 連接已建立", + "connectionLost": "WebSocket 連接已斷開", + "connectionReconnecting": "正在重新連接...", + "connectionReconnected": "已重新連接", + "connectionFailed": "連接失敗", + "websocketError": "WebSocket 錯誤", + "memoryPressure": "記憶體壓力清理", + "shutdown": "系統關閉", + "processKilled": "進程已終止", + "heartbeatStopped": "心跳已停止", + "websocketReady": "WebSocket 已就緒" + }, + "session": { + "noActiveSession": "沒有活躍會話", + "created": "新的 MCP 會話已創建,頁面將自動刷新", + "updated": "會話已更新", + "expired": "會話已過期", + "timeout": "會話已超時", + "cleaned": "會話已清理", + "feedbackSubmitted": "反饋已成功提交", + "userMessageRecorded": "用戶消息已記錄", + "historySaved": "會話歷史已保存({{count}} 個會話)", + "historyLoaded": "會話歷史已載入" + }, + "settingsAPI": { + "saved": "設定已保存", + "loaded": "設定已載入", + "cleared": "設定已清除", + "saveFailed": "保存失敗", + "loadFailed": "載入失敗", + "clearFailed": "清除失敗", + "setFailed": "設定失敗", + "invalidValue": "無效的設定值", + "logLevelUpdated": "日誌等級已更新", + "invalidLogLevel": "無效的日誌等級,必須是 DEBUG, INFO, WARN, ERROR 之一" + }, + "file": { + "uploadSuccess": "檔案上傳成功", + "uploadFailed": "檔案上傳失敗", + "sizeTooLarge": "檔案大小超過限制", + "typeNotSupported": "不支援的檔案類型", + "processing": "正在處理檔案...", + "removed": "檔案已移除" + }, + "prompt": { + "saved": "提示詞已保存", + "deleted": "提示詞已刪除", + "applied": "已套用提示詞:{{name}}", + "importSuccess": "提示詞匯入成功", + "importFailed": "提示詞匯入失敗", + "exportSuccess": "提示詞匯出成功", + "validationFailed": "提示詞驗證失敗" + }, + "error": { + "generic": "發生錯誤:{{error}}", + "network": "網絡錯誤", + "server": "伺服器錯誤", + "timeout": "操作超時", + "invalidInput": "輸入無效", + "operationFailed": "操作失敗", + "userMessageFailed": "添加用戶消息失敗", + "getSessionsFailed": "獲取會話列表失敗", + "getLogLevelFailed": "獲取日誌等級失敗", + "command": "命令執行錯誤", + "resourceCleanup": "資源清理錯誤", + "processing": "處理過程錯誤" + }, + "commandStatus": { + "executing": "正在執行命令...", + "completed": "命令執行完成", + "failed": "命令執行失敗", + "outputReceived": "已接收輸出", + "invalid": "無效的命令", + "error": "命令執行錯誤" + }, + "utils": { + "copySuccess": "已複製到剪貼板", + "copyError": "複製失敗" + }, + "fileUpload": { + "fileSizeExceeded": "圖片大小超過限制 ({limit}): {filename}", + "maxFilesExceeded": "最多只能上傳 {maxFiles} 個檔案", + "processingFailed": "檔案處理失敗,請重試" + }, + "aria": { + "toggleAutoSubmit": "切換自動提交", + "toggleNotification": "切換通知", + "toggleAudioNotification": "切換音效通知" } } diff --git a/src/mcp_feedback_enhanced/web/main.py b/src/mcp_feedback_enhanced/web/main.py index 90111c2..5e3161b 100644 --- a/src/mcp_feedback_enhanced/web/main.py +++ b/src/mcp_feedback_enhanced/web/main.py @@ -23,7 +23,6 @@ from fastapi.staticfiles import StaticFiles from fastapi.templating import Jinja2Templates from ..debug import web_debug_log as debug_log -from ..i18n import get_i18n_manager from ..utils.error_handler import ErrorHandler, ErrorType from ..utils.memory_monitor import get_memory_monitor from .models import CleanupReason, SessionStatus, WebFeedbackSession @@ -137,7 +136,7 @@ class WebUIManager: def _init_basic_components(self): """同步初始化基本組件""" # 基本組件初始化(必須同步) - self.i18n = get_i18n_manager() + # 移除 i18n 管理器,因為翻譯已移至前端 # 設置靜態文件和模板(必須同步) self._setup_static_files() @@ -181,9 +180,8 @@ class WebUIManager: def preload_i18n(): try: - # 觸發翻譯載入(如果尚未載入) - self.i18n.get_supported_languages() - debug_log("I18N 資源預載入完成") + # I18N 在前端處理,這裡只記錄預載入完成 + debug_log("I18N 資源預載入完成(前端處理)") return True except Exception as e: debug_log(f"I18N 資源預載入失敗: {e}") @@ -733,7 +731,7 @@ class WebUIManager: refresh_message = { "type": "session_updated", "action": "new_session_created", - "message": "新的 MCP 會話已創建,頁面將自動刷新", + "messageCode": "session.created", "session_info": { "session_id": self.current_session.session_id, "project_directory": self.current_session.project_directory, diff --git a/src/mcp_feedback_enhanced/web/models/feedback_session.py b/src/mcp_feedback_enhanced/web/models/feedback_session.py index d1ac589..19de970 100644 --- a/src/mcp_feedback_enhanced/web/models/feedback_session.py +++ b/src/mcp_feedback_enhanced/web/models/feedback_session.py @@ -26,6 +26,7 @@ from fastapi import WebSocket from ...debug import web_debug_log as debug_log from ...utils.error_handler import ErrorHandler, ErrorType from ...utils.resource_manager import get_resource_manager, register_process +from ..constants import get_message_code class SessionStatus(Enum): @@ -63,6 +64,9 @@ SUPPORTED_IMAGE_TYPES = { } TEMP_DIR = Path.home() / ".cache" / "interactive-feedback-mcp-web" +# 訊息代碼現在從統一的常量文件導入 +# 使用 get_message_code 函數來獲取訊息代碼 + def _safe_parse_command(command: str) -> list[str]: """ @@ -135,6 +139,7 @@ class WebFeedbackSession: self.command_logs: list[str] = [] self.user_messages: list[dict] = [] # 用戶消息記錄 self._cleanup_done = False # 防止重複清理 + # 移除語言設定,改由前端處理 # 新增:會話狀態管理 self.status = SessionStatus.WAITING @@ -176,6 +181,18 @@ class WebFeedbackSession: f"會話 {self.session_id} 初始化完成,自動清理延遲: {auto_cleanup_delay}秒,最大空閒: {max_idle_time}秒" ) + def get_message_code(self, key: str) -> str: + """ + 獲取訊息代碼 + + Args: + key: 訊息 key + + Returns: + 訊息代碼(用於前端 i18n) + """ + return get_message_code(key) + def next_step(self, message: str | None = None) -> bool: """進入下一個狀態 - 單向流轉,不可倒退""" old_status = self.status @@ -484,8 +501,9 @@ class WebFeedbackSession: try: await self.websocket.send_json( { - "type": "feedback_received", - "message": "反饋已成功提交", + "type": "notification", + "code": self.get_message_code("FEEDBACK_SUBMITTED"), + "severity": "success", "status": self.status.value, } ) @@ -738,21 +756,24 @@ class WebFeedbackSession: # 2. 關閉 WebSocket 連接 if self.websocket: try: - # 根據清理原因發送不同的通知消息 - message_map = { - CleanupReason.TIMEOUT: "會話已超時,介面將自動關閉", - CleanupReason.EXPIRED: "會話已過期,介面將自動關閉", - CleanupReason.MEMORY_PRESSURE: "系統內存不足,會話將被清理", - CleanupReason.MANUAL: "會話已被手動清理", - CleanupReason.ERROR: "會話發生錯誤,將被清理", - CleanupReason.SHUTDOWN: "系統正在關閉,會話將被清理", + # 根據清理原因獲取訊息代碼 + code_key_map = { + CleanupReason.TIMEOUT: "TIMEOUT_CLEANUP", + CleanupReason.EXPIRED: "EXPIRED_CLEANUP", + CleanupReason.MEMORY_PRESSURE: "MEMORY_PRESSURE_CLEANUP", + CleanupReason.MANUAL: "MANUAL_CLEANUP", + CleanupReason.ERROR: "ERROR_CLEANUP", + CleanupReason.SHUTDOWN: "SHUTDOWN_CLEANUP", } + code_key = code_key_map.get(reason, "SESSION_CLEANUP") + await self.websocket.send_json( { - "type": "session_cleanup", + "type": "notification", + "code": self.get_message_code(code_key), + "severity": "warning", "reason": reason.value, - "message": message_map.get(reason, "會話將被清理"), } ) await asyncio.sleep(0.1) # 給前端一點時間處理消息 diff --git a/src/mcp_feedback_enhanced/web/routes/main_routes.py b/src/mcp_feedback_enhanced/web/routes/main_routes.py index 2e7ebdd..c3b56e4 100644 --- a/src/mcp_feedback_enhanced/web/routes/main_routes.py +++ b/src/mcp_feedback_enhanced/web/routes/main_routes.py @@ -16,6 +16,7 @@ from fastapi.responses import HTMLResponse, JSONResponse from ... import __version__ from ...debug import web_debug_log as debug_log +from ..constants import get_message_code as get_msg_code if TYPE_CHECKING: @@ -44,6 +45,11 @@ def load_user_layout_settings() -> str: return "combined-vertical" +# 使用統一的訊息代碼系統 +# 從 ..constants 導入的 get_msg_code 函數會處理所有訊息代碼 +# 舊的 key 會自動映射到新的常量 + + def setup_routes(manager: "WebUIManager"): """設置路由""" @@ -79,7 +85,6 @@ def setup_routes(manager: "WebUIManager"): "version": __version__, "has_session": True, "layout_mode": layout_mode, - "i18n": manager.i18n, }, ) @@ -113,16 +118,23 @@ def setup_routes(manager: "WebUIManager"): return JSONResponse(content=translations) @manager.app.get("/api/session-status") - async def get_session_status(): + async def get_session_status(request: Request): """獲取當前會話狀態""" current_session = manager.get_current_session() + # 從請求頭獲取客戶端語言 + lang = ( + request.headers.get("Accept-Language", "zh-TW").split(",")[0].split("-")[0] + ) + if lang == "zh": + lang = "zh-TW" + if not current_session: return JSONResponse( content={ "has_session": False, "status": "no_session", - "message": "沒有活躍會話", + "messageCode": get_msg_code("no_active_session"), } ) @@ -139,12 +151,20 @@ def setup_routes(manager: "WebUIManager"): ) @manager.app.get("/api/current-session") - async def get_current_session(): + async def get_current_session(request: Request): """獲取當前會話詳細信息""" current_session = manager.get_current_session() + # 從查詢參數獲取語言,如果沒有則從會話獲取,最後使用默認值 + if not current_session: - return JSONResponse(status_code=404, content={"error": "沒有活躍會話"}) + return JSONResponse( + status_code=404, + content={ + "error": "No active session", + "messageCode": get_msg_code("no_active_session"), + }, + ) return JSONResponse( content={ @@ -158,8 +178,9 @@ def setup_routes(manager: "WebUIManager"): ) @manager.app.get("/api/all-sessions") - async def get_all_sessions(): + async def get_all_sessions(request: Request): """獲取所有會話的實時狀態""" + try: sessions_data = [] @@ -189,44 +210,65 @@ def setup_routes(manager: "WebUIManager"): except Exception as e: debug_log(f"獲取所有會話狀態失敗: {e}") return JSONResponse( - status_code=500, content={"error": f"獲取會話狀態失敗: {e!s}"} + status_code=500, + content={ + "error": f"Failed to get sessions: {e!s}", + "messageCode": get_msg_code("get_sessions_failed"), + }, ) @manager.app.post("/api/add-user-message") async def add_user_message(request: Request): """添加用戶消息到當前會話""" + try: data = await request.json() current_session = manager.get_current_session() if not current_session: - return JSONResponse(status_code=404, content={"error": "沒有活躍會話"}) + return JSONResponse( + status_code=404, + content={ + "error": "No active session", + "messageCode": get_msg_code("no_active_session"), + }, + ) # 添加用戶消息到會話 current_session.add_user_message(data) debug_log(f"用戶消息已添加到會話 {current_session.session_id}") return JSONResponse( - content={"status": "success", "message": "用戶消息已記錄"} + content={ + "status": "success", + "messageCode": get_msg_code("user_message_recorded"), + } ) except Exception as e: debug_log(f"添加用戶消息失敗: {e}") return JSONResponse( - status_code=500, content={"error": f"添加用戶消息失敗: {e!s}"} + status_code=500, + content={ + "error": f"Failed to add user message: {e!s}", + "messageCode": get_msg_code("add_user_message_failed"), + }, ) @manager.app.websocket("/ws") - async def websocket_endpoint(websocket: WebSocket): + async def websocket_endpoint(websocket: WebSocket, lang: str = "zh-TW"): """WebSocket 端點 - 重構後移除 session_id 依賴""" # 獲取當前活躍會話 session = manager.get_current_session() if not session: - await websocket.close(code=4004, reason="沒有活躍會話") + await websocket.close(code=4004, reason="No active session") return await websocket.accept() + # 語言由前端處理,不需要在後端設置 + debug_log(f"WebSocket 連接建立,語言由前端處理: {lang}") + # 檢查會話是否已有 WebSocket 連接 if session.websocket and session.websocket != websocket: debug_log("會話已有 WebSocket 連接,替換為新連接") @@ -237,7 +279,10 @@ def setup_routes(manager: "WebUIManager"): # 發送連接成功消息 try: await websocket.send_json( - {"type": "connection_established", "message": "WebSocket 連接已建立"} + { + "type": "connection_established", + "messageCode": get_msg_code("websocket_connected"), + } ) # 檢查是否有待發送的會話更新 @@ -247,7 +292,7 @@ def setup_routes(manager: "WebUIManager"): { "type": "session_updated", "action": "new_session_created", - "message": "新會話已創建,正在更新頁面內容", + "messageCode": get_msg_code("new_session_created"), "session_info": { "project_directory": session.project_directory, "summary": session.summary, @@ -296,6 +341,7 @@ def setup_routes(manager: "WebUIManager"): @manager.app.post("/api/save-settings") async def save_settings(request: Request): """保存設定到檔案""" + try: data = await request.json() @@ -310,18 +356,28 @@ def setup_routes(manager: "WebUIManager"): debug_log(f"設定已保存到: {settings_file}") - return JSONResponse(content={"status": "success", "message": "設定已保存"}) + return JSONResponse( + content={ + "status": "success", + "messageCode": get_msg_code("settings_saved"), + } + ) except Exception as e: debug_log(f"保存設定失敗: {e}") return JSONResponse( status_code=500, - content={"status": "error", "message": f"保存失敗: {e!s}"}, + content={ + "status": "error", + "message": f"Save failed: {e!s}", + "messageCode": get_msg_code("save_failed"), + }, ) @manager.app.get("/api/load-settings") - async def load_settings(): + async def load_settings(request: Request): """從檔案載入設定""" + try: # 使用統一的設定檔案路徑 config_dir = Path.home() / ".config" / "mcp-feedback-enhanced" @@ -340,12 +396,17 @@ def setup_routes(manager: "WebUIManager"): debug_log(f"載入設定失敗: {e}") return JSONResponse( status_code=500, - content={"status": "error", "message": f"載入失敗: {e!s}"}, + content={ + "status": "error", + "message": f"Load failed: {e!s}", + "messageCode": get_msg_code("load_failed"), + }, ) @manager.app.post("/api/clear-settings") - async def clear_settings(): + async def clear_settings(request: Request): """清除設定檔案""" + try: # 使用統一的設定檔案路徑 config_dir = Path.home() / ".config" / "mcp-feedback-enhanced" @@ -357,18 +418,28 @@ def setup_routes(manager: "WebUIManager"): else: debug_log("設定檔案不存在,無需刪除") - return JSONResponse(content={"status": "success", "message": "設定已清除"}) + return JSONResponse( + content={ + "status": "success", + "messageCode": get_msg_code("settings_cleared"), + } + ) except Exception as e: debug_log(f"清除設定失敗: {e}") return JSONResponse( status_code=500, - content={"status": "error", "message": f"清除失敗: {e!s}"}, + content={ + "status": "error", + "message": f"Clear failed: {e!s}", + "messageCode": get_msg_code("clear_failed"), + }, ) @manager.app.get("/api/load-session-history") - async def load_session_history(): + async def load_session_history(request: Request): """從檔案載入會話歷史""" + try: # 使用統一的設定檔案路徑 config_dir = Path.home() / ".config" / "mcp-feedback-enhanced" @@ -402,12 +473,17 @@ def setup_routes(manager: "WebUIManager"): debug_log(f"載入會話歷史失敗: {e}") return JSONResponse( status_code=500, - content={"status": "error", "message": f"載入失敗: {e!s}"}, + content={ + "status": "error", + "message": f"Load failed: {e!s}", + "messageCode": get_msg_code("load_failed"), + }, ) @manager.app.post("/api/save-session-history") async def save_session_history(request: Request): """保存會話歷史到檔案""" + try: data = await request.json() @@ -435,7 +511,8 @@ def setup_routes(manager: "WebUIManager"): return JSONResponse( content={ "status": "success", - "message": f"會話歷史已保存({session_count} 個會話)", + "messageCode": get_msg_code("session_history_saved"), + "params": {"count": session_count}, } ) @@ -443,12 +520,17 @@ def setup_routes(manager: "WebUIManager"): debug_log(f"保存會話歷史失敗: {e}") return JSONResponse( status_code=500, - content={"status": "error", "message": f"保存失敗: {e!s}"}, + content={ + "status": "error", + "message": f"Save failed: {e!s}", + "messageCode": get_msg_code("save_failed"), + }, ) @manager.app.get("/api/log-level") - async def get_log_level(): + async def get_log_level(request: Request): """獲取日誌等級設定""" + try: # 使用統一的設定檔案路徑 config_dir = Path.home() / ".config" / "mcp-feedback-enhanced" @@ -470,12 +552,16 @@ def setup_routes(manager: "WebUIManager"): debug_log(f"獲取日誌等級失敗: {e}") return JSONResponse( status_code=500, - content={"error": f"獲取日誌等級失敗: {e!s}"}, + content={ + "error": f"Failed to get log level: {e!s}", + "messageCode": get_msg_code("get_log_level_failed"), + }, ) @manager.app.post("/api/log-level") async def set_log_level(request: Request): """設定日誌等級""" + try: data = await request.json() log_level = data.get("logLevel") @@ -484,7 +570,8 @@ def setup_routes(manager: "WebUIManager"): return JSONResponse( status_code=400, content={ - "error": "無效的日誌等級,必須是 DEBUG, INFO, WARN, ERROR 之一" + "error": "Invalid log level", + "messageCode": get_msg_code("invalid_log_level"), }, ) @@ -512,7 +599,7 @@ def setup_routes(manager: "WebUIManager"): content={ "status": "success", "logLevel": log_level, - "message": "日誌等級已更新", + "messageCode": get_msg_code("log_level_updated"), } ) @@ -520,7 +607,11 @@ def setup_routes(manager: "WebUIManager"): debug_log(f"設定日誌等級失敗: {e}") return JSONResponse( status_code=500, - content={"status": "error", "message": f"設定失敗: {e!s}"}, + content={ + "status": "error", + "message": f"Set failed: {e!s}", + "messageCode": get_msg_code("set_failed"), + }, ) diff --git a/src/mcp_feedback_enhanced/web/static/js/app.js b/src/mcp_feedback_enhanced/web/static/js/app.js index 55f098c..1d07f3b 100644 --- a/src/mcp_feedback_enhanced/web/static/js/app.js +++ b/src/mcp_feedback_enhanced/web/static/js/app.js @@ -670,6 +670,11 @@ break; case 'session_updated': console.log('🔄 收到會話更新訊息:', data.session_info); + // 處理訊息代碼 + if (data.messageCode && window.i18nManager) { + const message = window.i18nManager.t(data.messageCode); + window.MCPFeedback.Utils.showMessage(message, window.MCPFeedback.Utils.CONSTANTS.MESSAGE_SUCCESS); + } this._originalHandleSessionUpdated(data); break; case 'desktop_close_request': @@ -712,9 +717,20 @@ this.uiManager.setFeedbackState(window.MCPFeedback.Utils.CONSTANTS.FEEDBACK_SUBMITTED); this.uiManager.setLastSubmissionTime(Date.now()); + // 停止自動提交計時器(如果正在運行) + if (this.autoSubmitManager && this.autoSubmitManager.isEnabled) { + console.log('⏸️ 反饋已成功提交,停止自動提交倒數計時器'); + this.autoSubmitManager.stop(); + } + // 顯示成功訊息 - const successMessage = window.i18nManager ? window.i18nManager.t('feedback.submitSuccess') : '回饋提交成功!'; - window.MCPFeedback.Utils.showMessage(data.message || successMessage, window.MCPFeedback.Utils.CONSTANTS.MESSAGE_SUCCESS); + if (data.messageCode && window.i18nManager) { + const message = window.i18nManager.t(data.messageCode, data.params); + window.MCPFeedback.Utils.showMessage(message, window.MCPFeedback.Utils.CONSTANTS.MESSAGE_SUCCESS); + } else { + const successMessage = window.i18nManager ? window.i18nManager.t('feedback.submitSuccess') : '回饋提交成功!'; + window.MCPFeedback.Utils.showMessage(data.message || successMessage, window.MCPFeedback.Utils.CONSTANTS.MESSAGE_SUCCESS); + } // 更新 AI 摘要區域顯示「已送出反饋」狀態 const submittedMessage = window.i18nManager ? window.i18nManager.t('feedback.submittedWaiting') : '已送出反饋,等待下次 MCP 調用...'; @@ -793,8 +809,11 @@ } // 顯示新會話通知 + const defaultMessage = window.i18nManager ? + window.i18nManager.t('session.created') : + 'New MCP session created, page will refresh automatically'; window.MCPFeedback.Utils.showMessage( - data.message || '新的 MCP 會話已創建,正在更新內容...', + data.message || defaultMessage, window.MCPFeedback.Utils.CONSTANTS.MESSAGE_SUCCESS ); @@ -1101,7 +1120,10 @@ const images = this.imageHandler ? this.imageHandler.getImages() : []; if (!feedback && images.length === 0) { - window.MCPFeedback.Utils.showMessage('請提供回饋文字或上傳圖片', window.MCPFeedback.Utils.CONSTANTS.MESSAGE_WARNING); + const message = window.i18nManager ? + window.i18nManager.t('feedback.provideTextOrImage', '請提供回饋文字或上傳圖片') : + '請提供回饋文字或上傳圖片'; + window.MCPFeedback.Utils.showMessage(message, window.MCPFeedback.Utils.CONSTANTS.MESSAGE_WARNING); return null; } @@ -1130,6 +1152,12 @@ this.uiManager.setFeedbackState(window.MCPFeedback.Utils.CONSTANTS.FEEDBACK_PROCESSING); } + // 停止自動提交計時器(如果正在運行) + if (this.autoSubmitManager && this.autoSubmitManager.isEnabled) { + console.log('⏸️ 手動提交反饋,停止自動提交倒數計時器'); + this.autoSubmitManager.stop(); + } + // 3. 發送回饋到 AI 助手 const success = this.webSocketManager.send({ type: 'submit_feedback', diff --git a/src/mcp_feedback_enhanced/web/static/js/i18n.js b/src/mcp_feedback_enhanced/web/static/js/i18n.js index e723ee5..2b5d754 100644 --- a/src/mcp_feedback_enhanced/web/static/js/i18n.js +++ b/src/mcp_feedback_enhanced/web/static/js/i18n.js @@ -8,10 +8,40 @@ class I18nManager { constructor() { - this.currentLanguage = 'zh-CN'; + this.currentLanguage = this.getDefaultLanguage(); this.translations = {}; this.loadingPromise = null; } + + getDefaultLanguage() { + // 1. 先檢查本地儲存的設定 + const savedLanguage = localStorage.getItem('language'); + if (savedLanguage && ['zh-TW', 'zh-CN', 'en'].includes(savedLanguage)) { + console.log('🌐 使用儲存的語言設定:', savedLanguage); + return savedLanguage; + } + + // 2. 檢查瀏覽器語言 + const browserLang = navigator.language || navigator.userLanguage; + console.log('🌐 瀏覽器語言:', browserLang); + + if (browserLang.startsWith('zh-TW') || browserLang.includes('Hant')) { + console.log('🌐 偵測到繁體中文環境'); + return 'zh-TW'; + } + if (browserLang.startsWith('zh') || browserLang.includes('Hans')) { + console.log('🌐 偵測到簡體中文環境'); + return 'zh-CN'; + } + if (browserLang.startsWith('en')) { + console.log('🌐 偵測到英文環境'); + return 'en'; + } + + // 3. 預設使用繁體中文 + console.log('🌐 使用預設語言: zh-TW'); + return 'zh-TW'; + } async init() { console.log(`i18nManager 使用預設語言: ${this.currentLanguage}`); @@ -163,6 +193,16 @@ class I18nManager { } }); + // 翻譯有 data-i18n-aria-label 屬性的元素 + const ariaLabelElements = document.querySelectorAll('[data-i18n-aria-label]'); + ariaLabelElements.forEach(element => { + const key = element.getAttribute('data-i18n-aria-label'); + const translation = this.t(key); + if (translation && translation !== key) { + element.setAttribute('aria-label', translation); + } + }); + // 更新動態內容 this.updateDynamicContent(); @@ -290,31 +330,18 @@ class I18nManager { // 設定頁籤的下拉選擇器 const selector = document.getElementById('settingsLanguageSelect'); if (selector) { - // 設置當前值 + // 只設置當前值,不綁定事件(讓 SettingsManager 統一處理) selector.value = this.currentLanguage; console.log(`🔧 setupLanguageSelectors: 設置 select.value = ${this.currentLanguage}`); - - // 移除舊的事件監聽器(如果存在) - if (selector._i18nChangeHandler) { - selector.removeEventListener('change', selector._i18nChangeHandler); - } - - // 添加新的事件監聽器 - 通過 SettingsManager 統一管理 - selector._i18nChangeHandler = (e) => { - console.log(`🔄 i18n select change event: ${e.target.value}`); - if (window.feedbackApp && window.feedbackApp.settingsManager) { - window.feedbackApp.settingsManager.set('language', e.target.value); - } else { - this.setLanguage(e.target.value); - } - }; - selector.addEventListener('change', selector._i18nChangeHandler); + + // 不再綁定事件監聽器,避免與 SettingsManager 衝突 + // 事件處理完全交由 SettingsManager 負責 } // 新版現代化語言選擇器 const languageOptions = document.querySelectorAll('.language-option'); if (languageOptions.length > 0) { - // 設置當前語言的活躍狀態和點擊事件 + // 只設置當前語言的活躍狀態,不綁定事件 languageOptions.forEach(option => { const lang = option.getAttribute('data-lang'); if (lang === this.currentLanguage) { @@ -322,20 +349,8 @@ class I18nManager { } else { option.classList.remove('active'); } - - // 移除舊的事件監聽器(如果存在) - option.removeEventListener('click', option._languageClickHandler); - - // 添加新的點擊事件監聽器 - 通過 SettingsManager 統一管理 - option._languageClickHandler = () => { - if (window.feedbackApp && window.feedbackApp.settingsManager) { - window.feedbackApp.settingsManager.set('language', lang); - } else { - this.setLanguage(lang); - } - }; - option.addEventListener('click', option._languageClickHandler); }); + // 事件監聽器由 SettingsManager 統一處理,避免重複綁定 } } diff --git a/src/mcp_feedback_enhanced/web/static/js/modules/audio/audio-manager.js b/src/mcp_feedback_enhanced/web/static/js/modules/audio/audio-manager.js index dd6f988..2112f92 100644 --- a/src/mcp_feedback_enhanced/web/static/js/modules/audio/audio-manager.js +++ b/src/mcp_feedback_enhanced/web/static/js/modules/audio/audio-manager.js @@ -591,7 +591,7 @@ // 可以在這裡添加 UI 通知邏輯 if (window.MCPFeedback && window.MCPFeedback.Utils && window.MCPFeedback.Utils.showMessage) { const message = window.i18nManager ? - window.i18nManager.t('audio.autoplayBlocked', '瀏覽器阻止音效自動播放,請點擊頁面以啟用音效通知') : + window.i18nManager.t('notification.autoplayBlocked', '瀏覽器阻止音效自動播放,請點擊頁面以啟用音效通知') : '瀏覽器阻止音效自動播放,請點擊頁面以啟用音效通知'; window.MCPFeedback.Utils.showMessage(message, 'info'); } diff --git a/src/mcp_feedback_enhanced/web/static/js/modules/audio/audio-settings-ui.js b/src/mcp_feedback_enhanced/web/static/js/modules/audio/audio-settings-ui.js index 587a95d..cb10e17 100644 --- a/src/mcp_feedback_enhanced/web/static/js/modules/audio/audio-settings-ui.js +++ b/src/mcp_feedback_enhanced/web/static/js/modules/audio/audio-settings-ui.js @@ -85,13 +85,11 @@
-
啟用音效通知
-
- 啟用後將在有新會話更新時播放音效通知 -
+
+
-
@@ -668,6 +666,16 @@ } }); + // 對有 data-i18n-aria-label 屬性的元素應用翻譯 + const ariaLabelElements = this.container.querySelectorAll('[data-i18n-aria-label]'); + ariaLabelElements.forEach(element => { + const key = element.getAttribute('data-i18n-aria-label'); + const translation = this.t(key); + if (translation && translation !== key) { + element.setAttribute('aria-label', translation); + } + }); + console.log('🌐 AudioSettingsUI 初始翻譯已應用'); }; diff --git a/src/mcp_feedback_enhanced/web/static/js/modules/constants/message-codes.js b/src/mcp_feedback_enhanced/web/static/js/modules/constants/message-codes.js new file mode 100644 index 0000000..daf2937 --- /dev/null +++ b/src/mcp_feedback_enhanced/web/static/js/modules/constants/message-codes.js @@ -0,0 +1,168 @@ +/** + * MCP Feedback Enhanced - 訊息代碼常量 + * ==================================== + * + * 定義所有系統訊息的標準代碼,用於國際化支援 + */ + +(function() { + 'use strict'; + + // 確保命名空間存在 + window.MCPFeedback = window.MCPFeedback || {}; + window.MCPFeedback.Constants = window.MCPFeedback.Constants || {}; + + /** + * 訊息代碼枚舉 + * 所有系統訊息都應該使用這些代碼,而非硬編碼字串 + */ + const MessageCodes = { + // 系統狀態訊息 + SYSTEM: { + CONNECTION_ESTABLISHED: 'system.connectionEstablished', + CONNECTION_LOST: 'system.connectionLost', + CONNECTION_RECONNECTING: 'system.connectionReconnecting', + CONNECTION_RECONNECTED: 'system.connectionReconnected', + CONNECTION_FAILED: 'system.connectionFailed', + WEBSOCKET_ERROR: 'system.websocketError' + }, + + // 會話相關訊息 + SESSION: { + NO_ACTIVE_SESSION: 'session.noActiveSession', + SESSION_CREATED: 'session.created', + SESSION_UPDATED: 'session.updated', + SESSION_EXPIRED: 'session.expired', + SESSION_TIMEOUT: 'session.timeout', + SESSION_CLEANED: 'session.cleaned', + FEEDBACK_SUBMITTED: 'session.feedbackSubmitted', + USER_MESSAGE_RECORDED: 'session.userMessageRecorded', + HISTORY_SAVED: 'session.historySaved', + HISTORY_LOADED: 'session.historyLoaded', + MANUAL_CLEANUP: 'session.manualCleanup', + ERROR_CLEANUP: 'session.errorCleanup' + }, + + // 設定相關訊息 + SETTINGS: { + SAVED: 'settings.saved', + LOADED: 'settings.loaded', + CLEARED: 'settings.cleared', + SAVE_FAILED: 'settings.saveFailed', + LOAD_FAILED: 'settings.loadFailed', + CLEAR_FAILED: 'settings.clearFailed', + INVALID_VALUE: 'settings.invalidValue', + LOG_LEVEL_UPDATED: 'settings.logLevelUpdated', + INVALID_LOG_LEVEL: 'settings.invalidLogLevel' + }, + + // 通知相關訊息 + NOTIFICATION: { + AUTOPLAY_BLOCKED: 'notification.autoplayBlocked', + PERMISSION_DENIED: 'notification.permissionDenied', + PERMISSION_GRANTED: 'notification.permissionGranted', + TEST_SENT: 'notification.testSent', + SOUND_ENABLED: 'notification.soundEnabled', + SOUND_DISABLED: 'notification.soundDisabled' + }, + + // 檔案上傳訊息 + FILE: { + UPLOAD_SUCCESS: 'file.uploadSuccess', + UPLOAD_FAILED: 'file.uploadFailed', + SIZE_TOO_LARGE: 'file.sizeTooLarge', + TYPE_NOT_SUPPORTED: 'file.typeNotSupported', + PROCESSING: 'file.processing', + REMOVED: 'file.removed' + }, + + // 提示詞相關訊息 + PROMPT: { + SAVED: 'prompt.saved', + DELETED: 'prompt.deleted', + APPLIED: 'prompt.applied', + IMPORT_SUCCESS: 'prompt.importSuccess', + IMPORT_FAILED: 'prompt.importFailed', + EXPORT_SUCCESS: 'prompt.exportSuccess', + VALIDATION_FAILED: 'prompt.validationFailed' + }, + + // 錯誤訊息 + ERROR: { + GENERIC: 'error.generic', + NETWORK: 'error.network', + SERVER: 'error.server', + TIMEOUT: 'error.timeout', + INVALID_INPUT: 'error.invalidInput', + OPERATION_FAILED: 'error.operationFailed' + }, + + // 命令執行訊息 + COMMAND: { + EXECUTING: 'commandStatus.executing', + COMPLETED: 'commandStatus.completed', + FAILED: 'commandStatus.failed', + OUTPUT_RECEIVED: 'commandStatus.outputReceived', + INVALID_COMMAND: 'commandStatus.invalid', + ERROR: 'commandStatus.error' + } + }; + + /** + * 訊息嚴重程度 + */ + const MessageSeverity = { + INFO: 'info', + SUCCESS: 'success', + WARNING: 'warning', + ERROR: 'error' + }; + + /** + * 建立標準訊息物件 + * @param {string} code - 訊息代碼 + * @param {Object} params - 動態參數 + * @param {string} severity - 嚴重程度 + * @returns {Object} 標準訊息物件 + */ + function createMessage(code, params = {}, severity = MessageSeverity.INFO) { + return { + type: 'notification', + code: code, + params: params, + severity: severity, + timestamp: Date.now() + }; + } + + /** + * 快捷方法:建立成功訊息 + */ + function createSuccessMessage(code, params = {}) { + return createMessage(code, params, MessageSeverity.SUCCESS); + } + + /** + * 快捷方法:建立錯誤訊息 + */ + function createErrorMessage(code, params = {}) { + return createMessage(code, params, MessageSeverity.ERROR); + } + + /** + * 快捷方法:建立警告訊息 + */ + function createWarningMessage(code, params = {}) { + return createMessage(code, params, MessageSeverity.WARNING); + } + + // 匯出到全域命名空間 + window.MCPFeedback.Constants.MessageCodes = MessageCodes; + window.MCPFeedback.Constants.MessageSeverity = MessageSeverity; + window.MCPFeedback.Constants.createMessage = createMessage; + window.MCPFeedback.Constants.createSuccessMessage = createSuccessMessage; + window.MCPFeedback.Constants.createErrorMessage = createErrorMessage; + window.MCPFeedback.Constants.createWarningMessage = createWarningMessage; + + console.log('📋 訊息代碼常量載入完成'); +})(); \ No newline at end of file diff --git a/src/mcp_feedback_enhanced/web/static/js/modules/file-upload-manager.js b/src/mcp_feedback_enhanced/web/static/js/modules/file-upload-manager.js index a7b3159..c765286 100644 --- a/src/mcp_feedback_enhanced/web/static/js/modules/file-upload-manager.js +++ b/src/mcp_feedback_enhanced/web/static/js/modules/file-upload-manager.js @@ -289,14 +289,23 @@ if (this.maxFileSize > 0 && file.size > this.maxFileSize) { const sizeLimit = this.formatFileSize(this.maxFileSize); console.warn('⚠️ 檔案過大:', file.name, '超過限制', sizeLimit); - this.showMessage('圖片大小超過限制 (' + sizeLimit + '): ' + file.name, 'warning'); + const message = window.i18nManager ? + window.i18nManager.t('fileUpload.fileSizeExceeded', { + limit: sizeLimit, + filename: file.name + }) : + '圖片大小超過限制 (' + sizeLimit + '): ' + file.name; + this.showMessage(message, 'warning'); continue; } // 檢查檔案數量限制 if (this.files.length + validFiles.length >= this.maxFiles) { console.warn('⚠️ 檔案數量超過限制:', this.maxFiles); - this.showMessage('最多只能上傳 ' + this.maxFiles + ' 個檔案', 'warning'); + const message = window.i18nManager ? + window.i18nManager.t('fileUpload.maxFilesExceeded', { maxFiles: this.maxFiles }) : + '最多只能上傳 ' + this.maxFiles + ' 個檔案'; + this.showMessage(message, 'warning'); break; } @@ -340,7 +349,10 @@ }) .catch(function(error) { console.error('❌ 檔案處理失敗:', error); - self.showMessage('檔案處理失敗,請重試', 'error'); + const message = window.i18nManager ? + window.i18nManager.t('fileUpload.processingFailed', '檔案處理失敗,請重試') : + '檔案處理失敗,請重試'; + self.showMessage(message, 'error'); }); }; diff --git a/src/mcp_feedback_enhanced/web/static/js/modules/logger.js b/src/mcp_feedback_enhanced/web/static/js/modules/logger.js index 4ea9dc6..8af2ed9 100644 --- a/src/mcp_feedback_enhanced/web/static/js/modules/logger.js +++ b/src/mcp_feedback_enhanced/web/static/js/modules/logger.js @@ -322,7 +322,8 @@ // 從 API 載入日誌等級 function loadLogLevelFromAPI() { - fetch('/api/log-level') + const lang = window.i18nManager ? window.i18nManager.getCurrentLanguage() : 'zh-TW'; + fetch('/api/log-level?lang=' + lang) .then(function(response) { if (response.ok) { return response.json(); @@ -343,7 +344,8 @@ // 保存日誌等級到 API function saveLogLevelToAPI(logLevel) { - fetch('/api/log-level', { + const lang = window.i18nManager ? window.i18nManager.getCurrentLanguage() : 'zh-TW'; + fetch('/api/log-level?lang=' + lang, { method: 'POST', headers: { 'Content-Type': 'application/json', @@ -360,6 +362,11 @@ }) .then(function(data) { console.log('📋 日誌等級已保存:', data.logLevel); + // 處理訊息代碼 + if (data.messageCode && window.i18nManager) { + const message = window.i18nManager.t(data.messageCode, data.params); + console.log('伺服器回應:', message); + } }) .catch(function(error) { console.warn('⚠️ 保存日誌等級失敗:', error); diff --git a/src/mcp_feedback_enhanced/web/static/js/modules/notification/notification-settings.js b/src/mcp_feedback_enhanced/web/static/js/modules/notification/notification-settings.js index 9656cb5..099f54c 100644 --- a/src/mcp_feedback_enhanced/web/static/js/modules/notification/notification-settings.js +++ b/src/mcp_feedback_enhanced/web/static/js/modules/notification/notification-settings.js @@ -55,6 +55,11 @@ this.setupEventListeners(); this.updateUI(); + // 應用翻譯到動態生成的內容 + if (window.i18nManager) { + window.i18nManager.applyTranslations(); + } + console.log('✅ NotificationSettings 初始化完成'); }; @@ -66,17 +71,15 @@
-
瀏覽器通知
-
- 新會話建立時通知(僅在背景執行時) -
+
+
-
@@ -85,25 +88,25 @@ @@ -111,12 +114,12 @@ @@ -261,7 +264,7 @@ const settings = this.notificationManager.getSettings(); if (!settings.browserSupported) { - this.statusDiv.innerHTML = `⚠️ 您的瀏覽器不支援通知功能`; + this.statusDiv.innerHTML = ``; this.statusDiv.className = 'permission-status status-unsupported'; this.toggle.disabled = true; return; diff --git a/src/mcp_feedback_enhanced/web/static/js/modules/session-manager.js b/src/mcp_feedback_enhanced/web/static/js/modules/session-manager.js index d8a2c6b..cb3f115 100644 --- a/src/mcp_feedback_enhanced/web/static/js/modules/session-manager.js +++ b/src/mcp_feedback_enhanced/web/static/js/modules/session-manager.js @@ -370,7 +370,10 @@ const currentSession = this.dataManager.getCurrentSession(); if (!currentSession) { - this.showMessage('目前沒有活躍的會話數據', 'warning'); + const message = window.i18nManager ? + window.i18nManager.t('sessionHistory.noActiveSession', '目前沒有活躍的會話數據') : + '目前沒有活躍的會話數據'; + this.showMessage(message, 'warning'); return; } @@ -390,7 +393,10 @@ if (sessionData) { this.detailsModal.showSessionDetails(sessionData); } else { - this.showMessage('找不到會話資料', 'error'); + const message = window.i18nManager ? + window.i18nManager.t('sessionHistory.sessionNotFound', '找不到會話資料') : + '找不到會話資料'; + this.showMessage(message, 'error'); } }; @@ -595,7 +601,10 @@ } catch (error) { console.error('📋 匯出會話歷史失敗:', error); if (window.MCPFeedback && window.MCPFeedback.Utils && window.MCPFeedback.Utils.showMessage) { - window.MCPFeedback.Utils.showMessage('匯出失敗: ' + error.message, 'error'); + const message = window.i18nManager ? + window.i18nManager.t('sessionHistory.management.exportFailed', { error: error.message }) : + '匯出失敗: ' + error.message; + window.MCPFeedback.Utils.showMessage(message, 'error'); } } }; @@ -623,7 +632,10 @@ } catch (error) { console.error('📋 匯出單一會話失敗:', error); if (window.MCPFeedback && window.MCPFeedback.Utils && window.MCPFeedback.Utils.showMessage) { - window.MCPFeedback.Utils.showMessage('匯出失敗: ' + error.message, 'error'); + const message = window.i18nManager ? + window.i18nManager.t('sessionHistory.management.exportFailed', { error: error.message }) : + '匯出失敗: ' + error.message; + window.MCPFeedback.Utils.showMessage(message, 'error'); } } }; @@ -659,7 +671,10 @@ } catch (error) { console.error('📋 清空會話歷史失敗:', error); if (window.MCPFeedback && window.MCPFeedback.Utils && window.MCPFeedback.Utils.showMessage) { - window.MCPFeedback.Utils.showMessage('清空失敗: ' + error.message, 'error'); + const errorMessage = window.i18nManager ? + window.i18nManager.t('sessionHistory.management.clearFailed', { error: error.message }) : + '清空失敗: ' + error.message; + window.MCPFeedback.Utils.showMessage(errorMessage, 'error'); } } }; @@ -690,11 +705,17 @@ '用戶訊息記錄已清空'; this.showMessage(successMessage, 'success'); } else { - this.showMessage('清空失敗', 'error'); + const errorMessage = window.i18nManager ? + window.i18nManager.t('sessionHistory.management.clearFailedGeneric', '清空失敗') : + '清空失敗'; + this.showMessage(errorMessage, 'error'); } } catch (error) { console.error('📋 清空用戶訊息記錄失敗:', error); - this.showMessage('清空失敗: ' + error.message, 'error'); + const errorMessage = window.i18nManager ? + window.i18nManager.t('sessionHistory.management.clearFailed', { error: error.message }) : + '清空失敗: ' + error.message; + this.showMessage(errorMessage, 'error'); } }; @@ -752,15 +773,24 @@ try { const currentSession = this.dataManager.getCurrentSession(); if (!currentSession) { - this.showMessage('没有当前会话数据', 'error'); + const message = window.i18nManager ? + window.i18nManager.t('sessionHistory.currentSession.noData', '沒有當前會話數據') : + '沒有當前會話數據'; + this.showMessage(message, 'error'); return; } const content = this.formatCurrentSessionContent(currentSession); - this.copyToClipboard(content, '当前会话内容已复制到剪贴板'); + const successMessage = window.i18nManager ? + window.i18nManager.t('sessionHistory.currentSession.copySuccess', '當前會話內容已複製到剪貼板') : + '當前會話內容已複製到剪貼板'; + this.copyToClipboard(content, successMessage); } catch (error) { console.error('复制当前会话内容失败:', error); - this.showMessage('复制失败,请重试', 'error'); + const message = window.i18nManager ? + window.i18nManager.t('sessionHistory.currentSession.copyFailed', '複製失敗,請重試') : + '複製失敗,請重試'; + this.showMessage(message, 'error'); } }; @@ -774,7 +804,10 @@ try { if (!this.dataManager) { console.log('📝 dataManager 不存在,尝试其他方式获取数据'); - this.showMessage('数据管理器未初始化', 'error'); + const message = window.i18nManager ? + window.i18nManager.t('sessionHistory.currentSession.dataManagerNotInit', '數據管理器未初始化') : + '數據管理器未初始化'; + this.showMessage(message, 'error'); return; } @@ -783,7 +816,10 @@ if (!currentSession) { console.log('📝 没有当前会话数据'); - this.showMessage('当前会话没有数据', 'warning'); + const message = window.i18nManager ? + window.i18nManager.t('sessionHistory.currentSession.noData', '當前會話沒有數據') : + '當前會話沒有數據'; + this.showMessage(message, 'warning'); return; } @@ -792,7 +828,10 @@ if (!currentSession.user_messages || currentSession.user_messages.length === 0) { console.log('📝 没有用户消息记录'); - this.showMessage('当前会话没有用户消息记录', 'warning'); + const message = window.i18nManager ? + window.i18nManager.t('sessionHistory.currentSession.noUserMessages', '當前會話沒有用戶消息記錄') : + '當前會話沒有用戶消息記錄'; + this.showMessage(message, 'warning'); return; } @@ -804,11 +843,17 @@ console.log('📝 格式化后的内容长度:', content.length); console.log('📝 格式化后的内容预览:', content.substring(0, 200)); - this.copyToClipboard(content, '当前用户内容已复制到剪贴板'); + const successMessage = window.i18nManager ? + window.i18nManager.t('sessionHistory.currentSession.userContentCopySuccess', '當前用戶內容已複製到剪貼板') : + '當前用戶內容已複製到剪貼板'; + this.copyToClipboard(content, successMessage); } catch (error) { console.error('📝 复制当前用户内容失败:', error); console.error('📝 错误堆栈:', error.stack); - this.showMessage('复制失败,请重试', 'error'); + const message = window.i18nManager ? + window.i18nManager.t('sessionHistory.currentSession.copyFailed', '複製失敗,請重試') : + '複製失敗,請重試'; + this.showMessage(message, 'error'); } }; @@ -959,7 +1004,10 @@ this.showMessage(successMessage, 'success'); } catch (err) { console.error('降级复制失败:', err); - this.showMessage('复制失败,请手动复制', 'error'); + const message = window.i18nManager ? + window.i18nManager.t('sessionHistory.currentSession.copyFailedManual', '複製失敗,請手動複製') : + '複製失敗,請手動複製'; + this.showMessage(message, 'error'); } finally { document.body.removeChild(textArea); } diff --git a/src/mcp_feedback_enhanced/web/static/js/modules/session/session-data-manager.js b/src/mcp_feedback_enhanced/web/static/js/modules/session/session-data-manager.js index 61b1906..e14bec1 100644 --- a/src/mcp_feedback_enhanced/web/static/js/modules/session/session-data-manager.js +++ b/src/mcp_feedback_enhanced/web/static/js/modules/session/session-data-manager.js @@ -327,7 +327,8 @@ * 發送用戶消息到服務器端 */ SessionDataManager.prototype.sendUserMessageToServer = function(userMessage) { - fetch('/api/add-user-message', { + const lang = window.i18nManager ? window.i18nManager.getCurrentLanguage() : 'zh-TW'; + fetch('/api/add-user-message?lang=' + lang, { method: 'POST', headers: { 'Content-Type': 'application/json' @@ -633,7 +634,8 @@ const self = this; // 首先嘗試獲取實時會話狀態 - fetch('/api/all-sessions') + const lang = window.i18nManager ? window.i18nManager.getCurrentLanguage() : 'zh-TW'; + fetch('/api/all-sessions?lang=' + lang) .then(function(response) { if (response.ok) { return response.json(); @@ -677,7 +679,8 @@ SessionDataManager.prototype.loadFromHistoryFile = function() { const self = this; - fetch('/api/load-session-history') + const lang = window.i18nManager ? window.i18nManager.getCurrentLanguage() : 'zh-TW'; + fetch('/api/load-session-history?lang=' + lang) .then(function(response) { if (response.ok) { return response.json(); @@ -775,7 +778,8 @@ lastCleanup: TimeUtils.getCurrentTimestamp() }; - fetch('/api/save-session-history', { + const lang = window.i18nManager ? window.i18nManager.getCurrentLanguage() : 'zh-TW'; + fetch('/api/save-session-history?lang=' + lang, { method: 'POST', headers: { 'Content-Type': 'application/json', @@ -791,7 +795,12 @@ } }) .then(function(result) { - console.log('📊 會話快照保存回應:', result.message); + if (result.messageCode && window.i18nManager) { + const message = window.i18nManager.t(result.messageCode, result.params); + console.log('📊 會話快照保存回應:', message); + } else { + console.log('📊 會話快照保存回應:', result.message); + } }) .catch(function(error) { console.error('📊 保存會話快照到伺服器失敗:', error); @@ -807,7 +816,8 @@ lastCleanup: TimeUtils.getCurrentTimestamp() }; - fetch('/api/save-session-history', { + const lang = window.i18nManager ? window.i18nManager.getCurrentLanguage() : 'zh-TW'; + fetch('/api/save-session-history?lang=' + lang, { method: 'POST', headers: { 'Content-Type': 'application/json', @@ -823,7 +833,12 @@ } }) .then(function(result) { - console.log('📊 伺服器保存回應:', result.message); + if (result.messageCode && window.i18nManager) { + const message = window.i18nManager.t(result.messageCode, result.params); + console.log('📊 伺服器保存回應:', message); + } else { + console.log('📊 伺服器保存回應:', result.message); + } }) .catch(function(error) { console.error('📊 保存會話歷史到伺服器失敗:', error); diff --git a/src/mcp_feedback_enhanced/web/static/js/modules/settings-manager.js b/src/mcp_feedback_enhanced/web/static/js/modules/settings-manager.js index ce26c06..5077116 100644 --- a/src/mcp_feedback_enhanced/web/static/js/modules/settings-manager.js +++ b/src/mcp_feedback_enhanced/web/static/js/modules/settings-manager.js @@ -23,11 +23,14 @@ function SettingsManager(options) { options = options || {}; + // 從 i18nManager 獲取當前語言作為預設值 + const defaultLanguage = window.i18nManager ? window.i18nManager.getCurrentLanguage() : 'zh-TW'; + // 預設設定 this.defaultSettings = { layoutMode: 'combined-vertical', autoClose: false, - language: 'zh-TW', + language: defaultLanguage, // 使用 i18nManager 的當前語言 imageSizeLimit: 0, enableBase64Detail: false, // 移除 activeTab - 頁籤切換無需持久化 @@ -80,6 +83,17 @@ console.log('沒有找到設定,使用預設值'); self.currentSettings = Utils.deepClone(self.defaultSettings); } + + // 同步語言設定到 i18nManager + if (self.currentSettings.language && window.i18nManager) { + const currentI18nLanguage = window.i18nManager.getCurrentLanguage(); + if (self.currentSettings.language !== currentI18nLanguage) { + console.log('🔧 SettingsManager.loadSettings: 同步語言設定到 i18nManager'); + console.log(' 從:', currentI18nLanguage, '到:', self.currentSettings.language); + window.i18nManager.setLanguage(self.currentSettings.language); + } + } + resolve(self.currentSettings); }) .catch(function(error) { @@ -94,7 +108,8 @@ * 從伺服器載入設定 */ SettingsManager.prototype.loadFromServer = function() { - return fetch('/api/load-settings') + const lang = window.i18nManager ? window.i18nManager.getCurrentLanguage() : 'zh-TW'; + return fetch('/api/load-settings?lang=' + lang) .then(function(response) { if (response.ok) { return response.json(); @@ -146,7 +161,8 @@ SettingsManager.prototype._performServerSave = function() { const self = this; - fetch('/api/save-settings', { + const lang = window.i18nManager ? window.i18nManager.getCurrentLanguage() : 'zh-TW'; + fetch('/api/save-settings?lang=' + lang, { method: 'POST', headers: { 'Content-Type': 'application/json', @@ -154,10 +170,18 @@ body: JSON.stringify(self.currentSettings) }) .then(function(response) { - if (response.ok) { + return response.json(); + }) + .then(function(data) { + if (data.status === 'success') { console.log('設定已即時同步到伺服器端'); + // 處理訊息代碼 + if (data.messageCode && window.i18nManager) { + const message = window.i18nManager.t(data.messageCode, data.params); + console.log('伺服器回應:', message); + } } else { - console.warn('同步設定到伺服器端失敗:', response.status); + console.warn('同步設定到伺服器端失敗:', data); } }) .catch(function(error) { @@ -239,15 +263,12 @@ * 處理語言變更 */ SettingsManager.prototype.handleLanguageChange = function(newLanguage) { - console.log('語言設定變更: ' + newLanguage); + console.log('🔄 SettingsManager.handleLanguageChange: ' + newLanguage); // 通知國際化系統(統一由 SettingsManager 管理) if (window.i18nManager) { - // 直接設定語言,不觸發 i18nManager 的保存邏輯 - window.i18nManager.currentLanguage = newLanguage; - window.i18nManager.applyTranslations(); - window.i18nManager.setupLanguageSelectors(); - document.documentElement.lang = newLanguage; + // 使用 setLanguage 方法確保正確更新 + window.i18nManager.setLanguage(newLanguage); } // 延遲更新動態文字,確保 i18n 已經載入新語言 @@ -688,7 +709,10 @@ try { // 如果要啟用自動提交,檢查是否已選擇提示詞 if (newValue && (!currentPromptId || currentPromptId === '')) { - Utils.showMessage('請先選擇一個提示詞作為自動提交內容', Utils.CONSTANTS.MESSAGE_WARNING); + const message = window.i18nManager ? + window.i18nManager.t('settingsUI.autoCommitNoPrompt', '請先選擇一個提示詞作為自動提交內容') : + '請先選擇一個提示詞作為自動提交內容'; + Utils.showMessage(message, Utils.CONSTANTS.MESSAGE_WARNING); return; } diff --git a/src/mcp_feedback_enhanced/web/static/js/modules/ui-manager.js b/src/mcp_feedback_enhanced/web/static/js/modules/ui-manager.js index 534b55d..d32c052 100644 --- a/src/mcp_feedback_enhanced/web/static/js/modules/ui-manager.js +++ b/src/mcp_feedback_enhanced/web/static/js/modules/ui-manager.js @@ -494,7 +494,9 @@ feedbackInput.value = ''; console.log('📝 已清空文字內容'); } - feedbackInput.disabled = false; + // 只有在等待狀態才啟用輸入框 + const canInput = this.feedbackState === Utils.CONSTANTS.FEEDBACK_WAITING; + feedbackInput.disabled = !canInput; } // 重新啟用提交按鈕 diff --git a/src/mcp_feedback_enhanced/web/static/js/modules/utils.js b/src/mcp_feedback_enhanced/web/static/js/modules/utils.js index 00ef3ee..33baf5d 100644 --- a/src/mcp_feedback_enhanced/web/static/js/modules/utils.js +++ b/src/mcp_feedback_enhanced/web/static/js/modules/utils.js @@ -169,8 +169,12 @@ * @returns {Promise} 複製是否成功 */ copyToClipboard: function(text, successMessage, errorMessage) { - successMessage = successMessage || '已複製到剪貼板'; - errorMessage = errorMessage || '複製失敗'; + successMessage = successMessage || (window.i18nManager ? + window.i18nManager.t('utils.copySuccess', '已複製到剪貼板') : + '已複製到剪貼板'); + errorMessage = errorMessage || (window.i18nManager ? + window.i18nManager.t('utils.copyError', '複製失敗') : + '複製失敗'); return new Promise(function(resolve) { if (navigator.clipboard && navigator.clipboard.writeText) { @@ -262,7 +266,115 @@ * @param {string} type - 訊息類型 (success, error, warning, info) * @param {number} duration - 顯示時間(毫秒) */ - showMessage: function(message, type, duration) { + showMessage: function(messageOrCode, type, duration) { + // 處理訊息代碼物件 + let actualMessage = messageOrCode; + let actualType = type || 'info'; + + if (typeof messageOrCode === 'object' && messageOrCode.code) { + // 使用 i18n 系統翻譯訊息代碼 + if (window.i18nManager) { + actualMessage = window.i18nManager.t(messageOrCode.code, messageOrCode.params); + } else { + // 改善 fallback 機制:提供基本的英文訊息 + actualMessage = this.getFallbackMessage(messageOrCode.code, messageOrCode.params); + } + // 使用訊息物件中的嚴重程度 + actualType = messageOrCode.severity || type || 'info'; + } + + // 呼叫內部顯示方法 + return this._displayMessage(actualMessage, actualType, duration); + }, + + /** + * 獲取 fallback 訊息 + * 當 i18n 系統尚未載入時使用 + * @param {string} code - 訊息代碼 + * @param {Object} params - 參數 + * @returns {string} fallback 訊息 + */ + getFallbackMessage: function(code, params) { + // 基本的 fallback 訊息對照表 + const fallbackMessages = { + // 系統相關 + 'system.connectionEstablished': 'WebSocket connection established', + 'system.connectionLost': 'WebSocket connection lost', + 'system.connectionReconnecting': 'Reconnecting...', + 'system.connectionReconnected': 'Reconnected', + 'system.connectionFailed': 'Connection failed', + 'system.websocketError': 'WebSocket error', + 'system.websocketReady': 'WebSocket ready', + 'system.memoryPressure': 'Memory pressure cleanup', + 'system.shutdown': 'System shutdown', + 'system.processKilled': 'Process killed', + 'system.heartbeatStopped': 'Heartbeat stopped', + + // 會話相關 + 'session.noActiveSession': 'No active session', + 'session.created': 'New session created', + 'session.updated': 'Session updated', + 'session.expired': 'Session expired', + 'session.timeout': 'Session timed out', + 'session.cleaned': 'Session cleaned', + 'session.feedbackSubmitted': 'Feedback submitted successfully', + 'session.userMessageRecorded': 'User message recorded', + 'session.historySaved': 'Session history saved', + 'session.historyLoaded': 'Session history loaded', + + // 設定相關 + 'settings.saved': 'Settings saved', + 'settings.loaded': 'Settings loaded', + 'settings.cleared': 'Settings cleared', + 'settings.saveFailed': 'Save failed', + 'settings.loadFailed': 'Load failed', + 'settings.clearFailed': 'Clear failed', + 'settings.setFailed': 'Set failed', + 'settings.logLevelUpdated': 'Log level updated', + 'settings.invalidLogLevel': 'Invalid log level', + + // 錯誤相關 + 'error.generic': 'An error occurred', + 'error.userMessageFailed': 'Failed to add user message', + 'error.getSessionsFailed': 'Failed to get sessions', + 'error.getLogLevelFailed': 'Failed to get log level', + 'error.command': 'Command execution error', + 'error.resourceCleanup': 'Resource cleanup error', + 'error.processing': 'Processing error', + + // 通知相關 + 'notification.autoplayBlocked': 'Browser blocked autoplay, click page to enable sound', + + // 預設訊息 + 'default': 'System message' + }; + + // 嘗試獲取對應的 fallback 訊息 + let message = fallbackMessages[code] || fallbackMessages['default']; + + // 處理參數替換(簡單版本) + if (params && typeof params === 'object') { + for (const key in params) { + if (params.hasOwnProperty(key)) { + const placeholder = '{{' + key + '}}'; + message = message.replace(placeholder, params[key]); + } + } + } + + // 在開發模式下顯示警告 + if (window.location.hostname === 'localhost' || window.location.hostname === '127.0.0.1') { + console.warn('[i18n] Fallback message used for:', code, '→', message); + } + + return message; + }, + + /** + * 內部方法:實際顯示訊息 + * @private + */ + _displayMessage: function(message, type, duration) { type = type || 'info'; duration = duration || 3000; diff --git a/src/mcp_feedback_enhanced/web/static/js/modules/websocket-manager.js b/src/mcp_feedback_enhanced/web/static/js/modules/websocket-manager.js index 703062e..c48d8c0 100644 --- a/src/mcp_feedback_enhanced/web/static/js/modules/websocket-manager.js +++ b/src/mcp_feedback_enhanced/web/static/js/modules/websocket-manager.js @@ -74,7 +74,10 @@ this.websocket = null; } - this.websocket = new WebSocket(wsUrl); + // 添加語言參數到 WebSocket URL + const language = window.i18nManager ? window.i18nManager.getCurrentLanguage() : 'zh-TW'; + const wsUrlWithLang = wsUrl + (wsUrl.includes('?') ? '&' : '?') + 'lang=' + language; + this.websocket = new WebSocket(wsUrlWithLang); this.setupWebSocketEvents(); } catch (error) { @@ -261,6 +264,11 @@ console.log('WebSocket 連接確認'); this.connectionReady = true; this.handleConnectionReady(); + // 處理訊息代碼 + if (data.messageCode && window.i18nManager) { + const message = window.i18nManager.t(data.messageCode); + Utils.showMessage(message, Utils.CONSTANTS.MESSAGE_SUCCESS); + } break; case 'heartbeat_response': this.handleHeartbeatResponse(); diff --git a/src/mcp_feedback_enhanced/web/templates/feedback.html b/src/mcp_feedback_enhanced/web/templates/feedback.html index 18783df..eec4258 100644 --- a/src/mcp_feedback_enhanced/web/templates/feedback.html +++ b/src/mcp_feedback_enhanced/web/templates/feedback.html @@ -693,13 +693,13 @@
-

🎨 介面設定

+

🎨 介面設定

-
界面佈局模式
-
+
界面佈局模式
+
選擇 AI 摘要和回饋輸入的顯示方式
@@ -707,15 +707,15 @@
@@ -729,13 +729,13 @@
-

🌍 語言設定

+

🌍 語言設定

-
當前語言
-
+
當前語言
+
選擇界面顯示語言
@@ -804,7 +804,7 @@
-
@@ -962,18 +962,18 @@
-

🔧 進階設定

+

🔧 進階設定

-
重置設定
-
+
重置設定
+
清除所有已保存的設定,恢復到預設狀態