📖 更新 README.md 和 README.zh-TW.md,新增多語言支援功能介紹及版本歷史,並更新介面截圖。新增 i18n.py 模組以支援多語系,並重構相關檔案結構以提升可維護性。

This commit is contained in:
Minidoracat 2025-05-31 06:53:35 +08:00
parent a35fec2a5b
commit c470f686a9
18 changed files with 2913 additions and 1114 deletions

View File

@ -1,4 +1,4 @@
# Interactive Feedback MCP # Interactive Feedback MCP
**🌐 Language / 語言切換:** **English** | [繁體中文](README.zh-TW.md) **🌐 Language / 語言切換:** **English** | [繁體中文](README.zh-TW.md)
@ -31,13 +31,20 @@ A simple [MCP server](https://modelcontextprotocol.io/) for implementing human-i
- **Error Handling**: Enhanced error handling ensuring stable program operation - **Error Handling**: Enhanced error handling ensuring stable program operation
- **Output Isolation**: Strict isolation of debug output from MCP communication - **Output Isolation**: Strict isolation of debug output from MCP communication
### 🌏 Multi-language Support
- **Full Internationalization**: Complete multi-language support with structured JSON translation files
- **Supported Languages**: Traditional Chinese, English, Simplified Chinese
- **Smart Detection**: Automatic language detection based on system locale
- **Easy Extension**: Simple JSON-based translation system for adding new languages
- **Legacy Compatibility**: Fully backward compatible with existing code
## 🖥️ Interface Preview ## 🖥️ Interface Preview
### Qt GUI Interface (Local Environment) ### Qt GUI Interface (Local Environment)
![Qt GUI Interface](docs/images/qt-gui-interface.png) ![Qt GUI Interface - English](docs/images/gui-en.png)
### Web UI Interface (SSH Remote Environment) ### Web UI Interface (SSH Remote Environment)
![Web UI Interface](docs/images/web-ui-interface.jpeg) ![Web UI Interface - English](docs/images/web-en.png)
Both interfaces support: Both interfaces support:
- 💬 Text feedback input - 💬 Text feedback input
@ -45,6 +52,7 @@ Both interfaces support:
- ⚡ Real-time command execution - ⚡ Real-time command execution
- 🎨 Modern dark theme - 🎨 Modern dark theme
- 📱 Responsive design (Web UI) - 📱 Responsive design (Web UI)
- 🌐 Multi-language support (Traditional Chinese, English, Simplified Chinese)
## 🎯 Why Use This Tool? ## 🎯 Why Use This Tool?
@ -205,6 +213,46 @@ uvx mcp-feedback-enhanced@latest version
MCP_DEBUG=true uvx mcp-feedback-enhanced@latest test MCP_DEBUG=true uvx mcp-feedback-enhanced@latest test
``` ```
## 🆕 Version History
### v2.0.9 - Multi-language Architecture Enhancement (Latest)
- 🌏 **Complete Multi-language Architecture Restructuring**: Migrated from embedded translations to structured JSON-based system
- 📁 **Organized Language Files**: Separated language files into `src/mcp_feedback_enhanced/locales/` directory structure
- 🔧 **Enhanced Internationalization**: Dynamic loading with nested key structure and browser language detection
- 📚 **Comprehensive Documentation**: Added detailed README for translation contributors with examples and guidelines
- 🔄 **Backward Compatibility**: Maintained full compatibility with existing code while enabling modern features
- 🖼️ **Interface Screenshots Update**: Added comprehensive screenshots showcasing both English and Traditional Chinese interfaces
- 📝 **Documentation Enhancement**: Updated README files with multi-language screenshots and feature descriptions
### v2.0.7 - Stability and Performance Improvements
- 🛡️ **Enhanced Error Handling**: Improved stability and error recovery mechanisms
- 🚀 **Performance Optimizations**: Faster startup times and improved resource management
- 🔧 **Bug Fixes**: Various minor fixes and improvements
- 📦 **Package Optimization**: Better dependency management and build process
- 🎨 **Visual Improvements**: Enhanced button visibility and color consistency
- 🖱️ **Interaction Fixes**: Improved drag-and-drop indicators and empty state hints
- 📱 **Responsive Design**: Better layout adaptation for different screen sizes
- 🌐 **Language Switching**: Fixed Qt GUI language switching with proper checkmarks
- 🛡️ **Complete Chinese Character Encoding Fix**: Perfect Chinese character display support
- 🔧 **JSON Parsing Error Resolution**: Fixed MCP client "Unexpected token" errors
- 🎛️ **Controllable Debug Mode**: Debug output control via `MCP_DEBUG` environment variable
- 🖼️ **Enhanced Image Support**: Improved image processing and Base64 encoding
- 🚀 **Output Isolation**: Strict separation of debug output from MCP communication
- 📦 **Package Optimization**: Improved uvx installation experience and dependency management
### v2.0.0 - Web UI and Remote Support
- ✅ **Web UI Interface**: Added Web UI support for SSH remote development environments
- ✅ **Automatic Environment Detection**: Smart interface selection based on environment
- ✅ **WebSocket Real-time Communication**: Live command execution and feedback
- ✅ **Modern Dark Theme**: Beautiful dark theme with responsive design
- ✅ **Persistent Testing Mode**: Enhanced testing capabilities
### v1.0.0 - Foundation (Original Author)
- ✅ **Qt GUI Interface**: Native desktop interface
- ✅ **Command Execution**: Real-time command execution and output
- ✅ **MCP Protocol Support**: Full Model Context Protocol implementation
- ✅ **Cross-platform Support**: Windows, macOS, and Linux compatibility
## 🛠️ Environment Variables ## 🛠️ Environment Variables
### Core Environment Variables ### Core Environment Variables

View File

@ -31,13 +31,20 @@
- **錯誤處理**:強化錯誤處理,確保程序穩定運行 - **錯誤處理**:強化錯誤處理,確保程序穩定運行
- **輸出隔離**:嚴格隔離調試輸出與 MCP 通信 - **輸出隔離**:嚴格隔離調試輸出與 MCP 通信
### 🌏 多語言支援
- **完整國際化**:採用結構化 JSON 翻譯檔案的完整多語言支援
- **支援語言**:繁體中文、英文、簡體中文
- **智能偵測**:根據系統語言環境自動偵測
- **易於擴充**:基於 JSON 的簡單翻譯系統,方便新增語言
- **向後兼容**:與現有程式碼完全向後兼容
## 🖥️ 介面預覽 ## 🖥️ 介面預覽
### Qt GUI 介面(本地環境) ### Qt GUI 介面(本地環境)
![Qt GUI Interface](docs/images/qt-gui-interface.png) ![Qt GUI Interface - Traditional Chinese](docs/images/gui-zh-tw.png)
### Web UI 介面SSH Remote 環境) ### Web UI 介面SSH Remote 環境)
![Web UI Interface](docs/images/web-ui-interface.jpeg) ![Web UI Interface - Traditional Chinese](docs/images/web-zh-tw.png)
兩種介面都支援: 兩種介面都支援:
- 💬 文字回饋輸入 - 💬 文字回饋輸入
@ -45,6 +52,7 @@
- ⚡ 即時命令執行 - ⚡ 即時命令執行
- 🎨 現代化深色主題 - 🎨 現代化深色主題
- 📱 響應式設計Web UI - 📱 響應式設計Web UI
- 🌐 多語言支援(繁體中文、英文、簡體中文)
## 🎯 為什麼使用這個工具? ## 🎯 為什麼使用這個工具?
@ -406,7 +414,24 @@ AI 助手會如此調用 `mcp-feedback-enhanced` 工具:
## 🆕 版本更新 ## 🆕 版本更新
### v2.0.3 - 穩定性改善 ### v2.0.9 - 多語言架構重構(最新版)
- 🌏 **完整多語言架構重構**:從嵌入式翻譯遷移到結構化 JSON 基礎系統
- 📁 **語言檔案組織化**:將語言檔案分離到 `src/mcp_feedback_enhanced/locales/` 目錄結構
- 🔧 **增強國際化功能**:動態載入與嵌套鍵值結構,支援瀏覽器語言偵測
- 📚 **完整文件說明**:為翻譯貢獻者新增詳細 README包含範例和指南
- 🔄 **向後兼容性**:在啟用現代功能的同時保持與現有程式碼的完全相容性
- 🖼️ **介面截圖更新**:新增完整截圖展示英文和繁體中文介面
- 📝 **文件強化**:更新 README 檔案,加入多語言截圖和功能說明
- 🛡️ **增強錯誤處理**:改善穩定性和錯誤恢復機制
- 🚀 **效能優化**:更快的啟動時間和改善的資源管理
- 🔧 **錯誤修復**:各種小型修復和改善
- 📦 **套件優化**:更好的相依性管理和建置流程
- 🎨 **視覺改善**:增強按鈕可見性和顏色一致性
- 🖱️ **互動修復**:改善拖拽指示器和空狀態提示
- 📱 **響應式設計**:更好的不同螢幕尺寸適應
- 🌐 **語言切換**:修復 Qt GUI 語言切換,加入適當的勾選標記
### v2.0.3 - 編碼與通訊修復
- 🛡️ **完全修復中文字符編碼問題**:支援完美的中文顯示 - 🛡️ **完全修復中文字符編碼問題**:支援完美的中文顯示
- 🔧 **解決 JSON 解析錯誤**:修復 MCP 客戶端的 "Unexpected token" 錯誤 - 🔧 **解決 JSON 解析錯誤**:修復 MCP 客戶端的 "Unexpected token" 錯誤
- 🎛️ **可控調試模式**:透過 `MCP_DEBUG` 環境變數控制調試輸出 - 🎛️ **可控調試模式**:透過 `MCP_DEBUG` 環境變數控制調試輸出
@ -414,19 +439,18 @@ AI 助手會如此調用 `mcp-feedback-enhanced` 工具:
- 🚀 **輸出隔離**:嚴格分離調試輸出與 MCP 通信 - 🚀 **輸出隔離**:嚴格分離調試輸出與 MCP 通信
- 📦 **套件優化**:改善 uvx 安裝體驗和依賴管理 - 📦 **套件優化**:改善 uvx 安裝體驗和依賴管理
### v2.0 - Web UI 支援 ### v2.0.0 - Web UI 與遠端支援
- ✅ 新增 Web UI 介面支援 SSH remote 開發 - ✅ **Web UI 介面**:新增 Web UI 支援 SSH remote 開發環境
- ✅ 自動環境檢測和介面選擇 - ✅ **自動環境檢測**:根據環境智能選擇介面
- ✅ WebSocket 即時通訊 - ✅ **WebSocket 即時通訊**:即時命令執行和回饋
- ✅ 現代化深色主題 - ✅ **現代化深色主題**:美觀的深色主題與響應式設計
- ✅ 響應式設計支援 - ✅ **持久化測試模式**:增強的測試功能
- ✅ 持久化測試模式
### v1.0 - 基礎版本(原作者) ### v1.0.0 - 基礎版本(原作者)
- ✅ Qt GUI 介面 - ✅ **Qt GUI 介面**:原生桌面介面
- ✅ 命令執行功能 - ✅ **命令執行**:即時命令執行和輸出
- ✅ MCP 協議支援 - ✅ **MCP 協議支援**:完整的模型上下文協議實作
- ✅ 多平台支援 - ✅ **跨平台支援**Windows、macOS 和 Linux 相容性
## 🐛 故障排除 ## 🐛 故障排除

BIN
docs/images/gui-en.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 40 KiB

BIN
docs/images/gui-zh-tw.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 46 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 40 KiB

BIN
docs/images/web-en.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 88 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 222 KiB

BIN
docs/images/web-zh-tw.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 86 KiB

View File

@ -6,10 +6,12 @@
基於 PySide6 的圖形用戶介面提供直觀的回饋收集功能 基於 PySide6 的圖形用戶介面提供直觀的回饋收集功能
支援文字輸入圖片上傳命令執行等功能 支援文字輸入圖片上傳命令執行等功能
新增多語系支援繁體中文英文簡體中文
作者: Fábio Ferreira 作者: Fábio Ferreira
靈感來源: dotcursorrules.com 靈感來源: dotcursorrules.com
增強功能: 圖片支援和現代化界面設計 增強功能: 圖片支援和現代化界面設計
多語系支援: Minidoracat
""" """
import os import os
@ -25,10 +27,13 @@ from PySide6.QtWidgets import (
QApplication, QMainWindow, QWidget, QVBoxLayout, QHBoxLayout, QApplication, QMainWindow, QWidget, QVBoxLayout, QHBoxLayout,
QLabel, QLineEdit, QPushButton, QTextEdit, QGroupBox, QLabel, QLineEdit, QPushButton, QTextEdit, QGroupBox,
QScrollArea, QFrame, QGridLayout, QFileDialog, QMessageBox, QScrollArea, QFrame, QGridLayout, QFileDialog, QMessageBox,
QTabWidget, QSizePolicy QTabWidget, QSizePolicy, QComboBox, QMenuBar, QMenu
) )
from PySide6.QtCore import Qt, Signal, QTimer from PySide6.QtCore import Qt, Signal, QTimer
from PySide6.QtGui import QFont, QPixmap, QDragEnterEvent, QDropEvent, QKeySequence, QShortcut from PySide6.QtGui import QFont, QPixmap, QDragEnterEvent, QDropEvent, QKeySequence, QShortcut, QAction
# 導入多語系支援
from .i18n import t, get_i18n_manager
# ===== 調試日誌函數 ===== # ===== 調試日誌函數 =====
def debug_log(message: str) -> None: def debug_log(message: str) -> None:
@ -115,22 +120,25 @@ class ImagePreviewWidget(QLabel):
self.delete_button.setStyleSheet(""" self.delete_button.setStyleSheet("""
QPushButton { QPushButton {
background-color: #f44336; background-color: #f44336;
color: white; color: #ffffff;
border: none; border: none;
border-radius: 10px; border-radius: 10px;
font-weight: bold; font-weight: bold;
font-size: 12px; font-size: 14px;
}
QPushButton:hover {
background-color: #d32f2f;
color: #ffffff;
} }
QPushButton:hover { background-color: #d32f2f; }
""") """)
self.delete_button.clicked.connect(self._on_delete_clicked) self.delete_button.clicked.connect(self._on_delete_clicked)
self.delete_button.setToolTip("刪除圖片") self.delete_button.setToolTip(t('images_clear'))
def _on_delete_clicked(self) -> None: def _on_delete_clicked(self) -> None:
"""處理刪除按鈕點擊事件""" """處理刪除按鈕點擊事件"""
reply = QMessageBox.question( reply = QMessageBox.question(
self, '確認刪除', self, t('images_delete_title'),
f'確定要移除圖片 "{os.path.basename(self.image_path)}" 嗎?', t('images_delete_confirm', filename=os.path.basename(self.image_path)),
QMessageBox.Yes | QMessageBox.No, QMessageBox.Yes | QMessageBox.No,
QMessageBox.No QMessageBox.No
) )
@ -158,10 +166,10 @@ class ImageUploadWidget(QWidget):
layout.setContentsMargins(12, 8, 12, 8) layout.setContentsMargins(12, 8, 12, 8)
# 標題 # 標題
title = QLabel("🖼️ 圖片附件(可選)") self.title = QLabel(t('images_title'))
title.setFont(QFont("", 10, QFont.Bold)) self.title.setFont(QFont("", 10, QFont.Bold))
title.setStyleSheet("color: #007acc; margin: 1px 0;") self.title.setStyleSheet("color: #007acc; margin: 1px 0;")
layout.addWidget(title) layout.addWidget(self.title)
# 操作按鈕 # 操作按鈕
self._create_buttons(layout) self._create_buttons(layout)
@ -170,7 +178,7 @@ class ImageUploadWidget(QWidget):
self._create_drop_zone(layout) self._create_drop_zone(layout)
# 狀態標籤 - 移到預覽區域前面 # 狀態標籤 - 移到預覽區域前面
self.status_label = QLabel("已選擇 0 張圖片") self.status_label = QLabel(t('images_status', count=0))
self.status_label.setStyleSheet("color: #9e9e9e; font-size: 10px; margin: 5px 0;") self.status_label.setStyleSheet("color: #9e9e9e; font-size: 10px; margin: 5px 0;")
layout.addWidget(self.status_label) layout.addWidget(self.status_label)
@ -182,15 +190,15 @@ class ImageUploadWidget(QWidget):
button_layout = QHBoxLayout() button_layout = QHBoxLayout()
# 選擇文件按鈕 # 選擇文件按鈕
self.file_button = QPushButton("📁 選擇文件") self.file_button = QPushButton(t('btn_select_files'))
self.file_button.clicked.connect(self.select_files) self.file_button.clicked.connect(self.select_files)
# 剪貼板按鈕 # 剪貼板按鈕
self.paste_button = QPushButton("📋 剪貼板") self.paste_button = QPushButton(t('btn_paste_clipboard'))
self.paste_button.clicked.connect(self.paste_from_clipboard) self.paste_button.clicked.connect(self.paste_from_clipboard)
# 清除按鈕 # 清除按鈕
self.clear_button = QPushButton("❌ 清除") self.clear_button = QPushButton(t('btn_clear_all'))
self.clear_button.clicked.connect(self.clear_all_images) self.clear_button.clicked.connect(self.clear_all_images)
# 設置按鈕樣式 # 設置按鈕樣式
@ -203,11 +211,39 @@ class ImageUploadWidget(QWidget):
font-weight: bold; font-weight: bold;
font-size: 11px; font-size: 11px;
} }
QPushButton:hover {
opacity: 0.8;
}
""" """
self.file_button.setStyleSheet(button_style + "QPushButton { background-color: #0e639c; }") self.file_button.setStyleSheet(button_style + """
self.paste_button.setStyleSheet(button_style + "QPushButton { background-color: #4caf50; }") QPushButton {
self.clear_button.setStyleSheet(button_style + "QPushButton { background-color: #f44336; }") background-color: #0e639c;
}
QPushButton:hover {
background-color: #005a9e;
}
""")
self.paste_button.setStyleSheet(button_style + """
QPushButton {
background-color: #4caf50;
}
QPushButton:hover {
background-color: #45a049;
}
""")
self.clear_button.setStyleSheet(button_style + """
QPushButton {
background-color: #f44336;
color: #ffffff;
}
QPushButton:hover {
background-color: #d32f2f;
color: #ffffff;
}
""")
button_layout.addWidget(self.file_button) button_layout.addWidget(self.file_button)
button_layout.addWidget(self.paste_button) button_layout.addWidget(self.paste_button)
@ -218,7 +254,7 @@ class ImageUploadWidget(QWidget):
def _create_drop_zone(self, layout: QVBoxLayout) -> None: def _create_drop_zone(self, layout: QVBoxLayout) -> None:
"""創建拖拽區域""" """創建拖拽區域"""
self.drop_zone = QLabel("🎯 拖拽圖片到這裡 (PNG、JPG、JPEG、GIF、BMP、WebP)") self.drop_zone = QLabel(t('images_drag_hint'))
self.drop_zone.setFixedHeight(50) self.drop_zone.setFixedHeight(50)
self.drop_zone.setAlignment(Qt.AlignCenter) self.drop_zone.setAlignment(Qt.AlignCenter)
self.drop_zone.setStyleSheet(""" self.drop_zone.setStyleSheet("""
@ -260,9 +296,9 @@ class ImageUploadWidget(QWidget):
"""選擇文件對話框""" """選擇文件對話框"""
files, _ = QFileDialog.getOpenFileNames( files, _ = QFileDialog.getOpenFileNames(
self, self,
"選擇圖片文件", t('images_select'),
"", "",
"圖片文件 (*.png *.jpg *.jpeg *.gif *.bmp *.webp);;所有文件 (*)" "Image files (*.png *.jpg *.jpeg *.gif *.bmp *.webp);;All files (*)"
) )
if files: if files:
self._add_images(files) self._add_images(files)
@ -469,21 +505,19 @@ class ImageUploadWidget(QWidget):
"""更新狀態標籤""" """更新狀態標籤"""
count = len(self.images) count = len(self.images)
if count == 0: if count == 0:
self.status_label.setText("已選擇 0 張圖片") self.status_label.setText(t('images_status', count=0))
else: else:
total_size = sum(img["size"] for img in self.images.values()) total_size = sum(img["size"] for img in self.images.values())
# 智能單位顯示 # 格式化文件大小
if total_size < 1024: if total_size > 1024 * 1024: # MB
size_str = f"{total_size} B"
elif total_size < 1024 * 1024:
size_kb = total_size / 1024
size_str = f"{size_kb:.1f} KB"
else:
size_mb = total_size / (1024 * 1024) size_mb = total_size / (1024 * 1024)
size_str = f"{size_mb:.1f} MB" size_str = f"{size_mb:.1f} MB"
else: # KB
size_kb = total_size / 1024
size_str = f"{size_kb:.1f} KB"
self.status_label.setText(f"已選擇 {count} 張圖片 (總計 {size_str})") self.status_label.setText(t('images_status_with_size', count=count, size=size_str))
# 詳細調試信息 # 詳細調試信息
debug_log(f"=== 圖片狀態更新 ===") debug_log(f"=== 圖片狀態更新 ===")
@ -579,197 +613,332 @@ class ImageUploadWidget(QWidget):
debug_log(f"清理了 {cleaned_count} 個舊的臨時文件") debug_log(f"清理了 {cleaned_count} 個舊的臨時文件")
except Exception as e: except Exception as e:
debug_log(f"臨時文件清理過程出錯: {e}") debug_log(f"臨時文件清理過程出錯: {e}")
def update_texts(self) -> None:
"""更新界面文字(用於語言切換)"""
# 更新標題
if hasattr(self, 'title'):
self.title.setText(t('images_title'))
# 更新按鈕文字
if hasattr(self, 'file_button'):
self.file_button.setText(t('btn_select_files'))
if hasattr(self, 'paste_button'):
self.paste_button.setText(t('btn_paste_clipboard'))
if hasattr(self, 'clear_button'):
self.clear_button.setText(t('btn_clear_all'))
# 更新拖拽區域文字
if hasattr(self, 'drop_zone'):
self.drop_zone.setText(t('images_drag_hint'))
# 更新狀態文字
self._update_status()
# ===== 主要回饋介面 ===== # ===== 主要回饋介面 =====
class FeedbackWindow(QMainWindow): class FeedbackWindow(QMainWindow):
"""主要的回饋收集視窗""" """回饋收集主窗口"""
language_changed = Signal()
def __init__(self, project_dir: str, summary: str): def __init__(self, project_dir: str, summary: str):
super().__init__() super().__init__()
self.project_dir = project_dir self.project_dir = project_dir
self.summary = summary self.summary = summary
self.result: Optional[FeedbackResult] = None self.result = None
self.process: Optional[subprocess.Popen] = None self.command_process = None
self.accepted = False self.i18n = get_i18n_manager()
self._setup_ui() self._setup_ui()
self._apply_dark_style() self._apply_dark_style()
# 連接語言變更信號
self.language_changed.connect(self._refresh_ui_texts)
def _setup_ui(self) -> None: def _setup_ui(self) -> None:
"""設置用戶介面""" """設置用戶介面"""
self.setWindowTitle("互動式回饋收集") self.setWindowTitle(t('app_title'))
self.setMinimumSize(800, 600) self.setMinimumSize(900, 700)
self.resize(1000, 800)
# 主要元件 # 創建菜單欄
self._create_menu_bar()
# 中央元件
central_widget = QWidget() central_widget = QWidget()
self.setCentralWidget(central_widget) self.setCentralWidget(central_widget)
layout = QVBoxLayout(central_widget)
layout.setSpacing(8)
layout.setContentsMargins(16, 8, 16, 12)
# 主要佈局 # AI 工作摘要區域
main_layout = QVBoxLayout(central_widget) self._create_summary_section(layout)
main_layout.setSpacing(10)
main_layout.setContentsMargins(10, 10, 10, 10)
# AI 工作摘要(適度參與拉伸) # 分頁區域
self._create_summary_section(main_layout) self._create_tabs(layout)
# 分頁標籤(主要工作區域) # 操作按鈕
self._create_tabs(main_layout) self._create_action_buttons(layout)
# 操作按鈕(固定大小)
self._create_action_buttons(main_layout)
# 設置比例拉伸摘要區域佔1份分頁區域佔3份按鈕不拉伸
summary_widget = main_layout.itemAt(0).widget() # 摘要區域
main_layout.setStretchFactor(summary_widget, 1) # 適度拉伸
main_layout.setStretchFactor(self.tabs, 3) # 主要拉伸區域
def _create_menu_bar(self) -> None:
"""創建菜單欄"""
menubar = self.menuBar()
# 語言菜單
self.language_menu = menubar.addMenu(t('language_selector'))
self.language_actions = {}
# 添加語言選項
for lang_code in self.i18n.get_supported_languages():
action = QAction(self.i18n.get_language_display_name(lang_code), self)
action.setCheckable(True)
action.setChecked(lang_code == self.i18n.get_current_language())
action.triggered.connect(lambda checked, lang=lang_code: self._change_language(lang))
self.language_menu.addAction(action)
self.language_actions[lang_code] = action
def _change_language(self, language: str) -> None:
"""更改語言"""
if self.i18n.set_language(language):
# 更新所有菜單項目的勾選狀態
for lang_code, action in self.language_actions.items():
action.setChecked(lang_code == language)
# 發送語言變更信號
self.language_changed.emit()
def _refresh_ui_texts(self) -> None:
"""刷新界面文字"""
# 更新窗口標題
self.setWindowTitle(t('app_title'))
# 更新菜單文字
self._update_menu_texts()
# 更新標籤和按鈕文字
self._update_widget_texts()
# 更新圖片上傳元件的文字
self._update_image_upload_texts()
def _update_menu_texts(self) -> None:
"""更新菜單文字"""
# 更新語言菜單標題
self.language_menu.setTitle(t('language_selector'))
# 更新語言選項文字
for lang_code, action in self.language_actions.items():
action.setText(self.i18n.get_language_display_name(lang_code))
def _update_widget_texts(self) -> None:
"""更新元件文字"""
# 更新摘要標題
if hasattr(self, 'summary_title'):
self.summary_title.setText(t('ai_summary'))
# 更新專案目錄標籤
if hasattr(self, 'project_label'):
self.project_label.setText(f"{t('project_directory')}: {self.project_dir}")
# 更新分頁標籤
if hasattr(self, 'tab_widget'):
self.tab_widget.setTabText(0, t('feedback_tab'))
self.tab_widget.setTabText(1, t('command_tab'))
# 更新按鈕文字
if hasattr(self, 'submit_button'):
self.submit_button.setText(t('btn_submit_feedback'))
if hasattr(self, 'cancel_button'):
self.cancel_button.setText(t('btn_cancel'))
if hasattr(self, 'run_button'):
self.run_button.setText(t('btn_run_command'))
# 更新回饋區域標籤
if hasattr(self, 'feedback_title'):
self.feedback_title.setText(t('feedback_title'))
if hasattr(self, 'feedback_description'):
self.feedback_description.setText(t('feedback_description'))
if hasattr(self, 'feedback_input'):
self.feedback_input.setPlaceholderText(t('feedback_placeholder'))
# 更新命令區域標籤
if hasattr(self, 'command_title'):
self.command_title.setText(t('command_title'))
if hasattr(self, 'command_description'):
self.command_description.setText(t('command_description'))
if hasattr(self, 'command_input'):
self.command_input.setPlaceholderText(t('command_placeholder'))
if hasattr(self, 'output_title'):
self.output_title.setText(t('command_output'))
def _update_image_upload_texts(self) -> None:
"""更新圖片上傳元件的文字"""
if hasattr(self, 'image_upload'):
self.image_upload.update_texts()
def _create_summary_section(self, layout: QVBoxLayout) -> None: def _create_summary_section(self, layout: QVBoxLayout) -> None:
"""創建 AI 工作摘要區域""" """創建 AI 工作摘要區域"""
summary_group = QGroupBox("📋 AI 工作摘要") summary_group = QGroupBox()
summary_group.setTitle("")
summary_group.setMaximumHeight(200)
summary_layout = QVBoxLayout(summary_group) summary_layout = QVBoxLayout(summary_group)
summary_layout.setSpacing(8)
summary_layout.setContentsMargins(12, 8, 12, 12)
self.summary_text = QTextEdit() # 標題與項目信息
self.summary_text.setPlainText(self.summary) header_layout = QHBoxLayout()
# 設置合理的高度範圍,允許適度拉伸
self.summary_text.setMinimumHeight(80)
self.summary_text.setMaximumHeight(250) # 增加最大高度,允許更多拉伸
self.summary_text.setReadOnly(True)
self.summary_text.setStyleSheet("background-color: #2d2d30; border: 1px solid #464647;")
# 設置大小策略:允許適度垂直擴展 # AI 工作摘要標題
self.summary_text.setSizePolicy(QSizePolicy.Expanding, QSizePolicy.Expanding) self.summary_title = QLabel(t('ai_summary'))
self.summary_title.setFont(QFont("", 12, QFont.Bold))
self.summary_title.setStyleSheet("color: #007acc; margin-bottom: 5px;")
header_layout.addWidget(self.summary_title)
# 設置群組框的大小策略:允許適度擴展 header_layout.addStretch()
summary_group.setSizePolicy(QSizePolicy.Expanding, QSizePolicy.Expanding)
# 專案目錄信息
self.project_label = QLabel(f"{t('project_directory')}: {self.project_dir}")
self.project_label.setStyleSheet("color: #9e9e9e; font-size: 11px;")
header_layout.addWidget(self.project_label)
summary_layout.addLayout(header_layout)
# 摘要內容(可滾動的文本區域)
summary_text = QTextEdit()
summary_text.setPlainText(self.summary)
summary_text.setReadOnly(True)
summary_text.setMaximumHeight(120)
summary_layout.addWidget(summary_text)
summary_layout.addWidget(self.summary_text)
layout.addWidget(summary_group) layout.addWidget(summary_group)
def _create_tabs(self, layout: QVBoxLayout) -> None: def _create_tabs(self, layout: QVBoxLayout) -> None:
"""創建分頁標籤""" """創建分頁標籤"""
self.tabs = QTabWidget() self.tab_widget = QTabWidget()
# 設置分頁標籤的大小策略,確保能夠獲得主要空間
self.tabs.setSizePolicy(QSizePolicy.Expanding, QSizePolicy.Expanding)
# 回饋分頁 # 回饋分頁
self._create_feedback_tab() self._create_feedback_tab()
# 命令分頁 # 命令分頁
self._create_command_tab() self._create_command_tab()
layout.addWidget(self.tabs) layout.addWidget(self.tab_widget)
def _create_feedback_tab(self) -> None: def _create_feedback_tab(self) -> None:
"""創建回饋分頁""" """創建回饋分頁"""
feedback_widget = QWidget() feedback_widget = QWidget()
feedback_layout = QVBoxLayout(feedback_widget) layout = QVBoxLayout(feedback_widget)
layout.setSpacing(8)
layout.setContentsMargins(12, 12, 12, 12)
# 文字回饋區域 # 回饋輸入區域
feedback_group = QGroupBox("💬 您的回饋") feedback_group = QGroupBox()
feedback_group_layout = QVBoxLayout(feedback_group) feedback_layout = QVBoxLayout(feedback_group)
feedback_layout.setSpacing(8)
feedback_layout.setContentsMargins(12, 8, 12, 12)
self.feedback_text = QTextEdit() # 回饋標題和說明
self.feedback_text.setPlaceholderText("請在這裡輸入您的回饋、建議或問題...\n\n💡 小提示:按 Ctrl+Enter 可快速提交回饋") self.feedback_title = QLabel(t('feedback_title'))
self.feedback_text.setMinimumHeight(150) self.feedback_title.setFont(QFont("", 11, QFont.Bold))
# 確保文字輸入區域能夠擴展 self.feedback_title.setStyleSheet("color: #007acc; margin-bottom: 5px;")
self.feedback_text.setSizePolicy(QSizePolicy.Expanding, QSizePolicy.Expanding) feedback_layout.addWidget(self.feedback_title)
# 添加快捷鍵支援 # 說明文字
submit_shortcut = QShortcut(QKeySequence("Ctrl+Return"), self.feedback_text) self.feedback_description = QLabel(t('feedback_description'))
submit_shortcut.activated.connect(self._submit_feedback) self.feedback_description.setStyleSheet("color: #9e9e9e; font-size: 10px; margin-bottom: 8px;")
self.feedback_description.setWordWrap(True)
feedback_layout.addWidget(self.feedback_description)
feedback_group_layout.addWidget(self.feedback_text) # 文字輸入框
feedback_layout.addWidget(feedback_group) self.feedback_input = QTextEdit()
self.feedback_input.setPlaceholderText(t('feedback_placeholder'))
self.feedback_input.setMinimumHeight(120)
feedback_layout.addWidget(self.feedback_input)
# 圖片上傳區域(允許適度拉伸) layout.addWidget(feedback_group, stretch=2) # 給更多空間
# 圖片上傳區域
self.image_upload = ImageUploadWidget() self.image_upload = ImageUploadWidget()
self.image_upload.setSizePolicy(QSizePolicy.Expanding, QSizePolicy.Expanding) layout.addWidget(self.image_upload, stretch=3) # 給圖片區域更多空間
self.image_upload.setMinimumHeight(200) # 設置最小高度
self.image_upload.setMaximumHeight(400) # 增加最大高度限制
feedback_layout.addWidget(self.image_upload)
# 設置比例拉伸文字區域佔2份圖片區域佔1份 self.tab_widget.addTab(feedback_widget, t('feedback_tab'))
feedback_layout.setStretchFactor(feedback_group, 2)
feedback_layout.setStretchFactor(self.image_upload, 1)
self.tabs.addTab(feedback_widget, "💬 回饋")
def _create_command_tab(self) -> None: def _create_command_tab(self) -> None:
"""創建命令分頁""" """創建命令分頁"""
command_widget = QWidget() command_widget = QWidget()
command_layout = QVBoxLayout(command_widget) layout = QVBoxLayout(command_widget)
layout.setSpacing(8)
layout.setContentsMargins(12, 12, 12, 12)
# 命令輸入區域 # 命令輸入區域
command_group = QGroupBox("⚡ 命令執行") command_group = QGroupBox()
command_group_layout = QVBoxLayout(command_group) command_layout = QVBoxLayout(command_group)
command_layout.setSpacing(8)
command_layout.setContentsMargins(12, 8, 12, 12)
# 命令輸入 # 命令標題
cmd_input_layout = QHBoxLayout() self.command_title = QLabel(t('command_title'))
self.command_title.setFont(QFont("", 11, QFont.Bold))
self.command_title.setStyleSheet("color: #007acc; margin-bottom: 5px;")
command_layout.addWidget(self.command_title)
# 說明文字
self.command_description = QLabel(t('command_description'))
self.command_description.setStyleSheet("color: #9e9e9e; font-size: 10px; margin-bottom: 8px;")
self.command_description.setWordWrap(True)
command_layout.addWidget(self.command_description)
# 命令輸入和執行按鈕
input_layout = QHBoxLayout()
self.command_input = QLineEdit() self.command_input = QLineEdit()
self.command_input.setPlaceholderText("輸入要執行的命令...") self.command_input.setPlaceholderText(t('command_placeholder'))
self.command_input.returnPressed.connect(self._run_command) self.command_input.returnPressed.connect(self._run_command)
input_layout.addWidget(self.command_input)
self.run_button = QPushButton("▶️ 執行") self.run_button = QPushButton(t('btn_run_command'))
self.run_button.clicked.connect(self._run_command) self.run_button.clicked.connect(self._run_command)
self.run_button.setFixedWidth(100)
input_layout.addWidget(self.run_button)
cmd_input_layout.addWidget(self.command_input) command_layout.addLayout(input_layout)
cmd_input_layout.addWidget(self.run_button) layout.addWidget(command_group)
command_group_layout.addLayout(cmd_input_layout)
# 命令輸出區域
output_group = QGroupBox()
output_layout = QVBoxLayout(output_group)
output_layout.setSpacing(8)
output_layout.setContentsMargins(12, 8, 12, 12)
self.output_title = QLabel(t('command_output'))
self.output_title.setFont(QFont("", 11, QFont.Bold))
self.output_title.setStyleSheet("color: #007acc; margin-bottom: 5px;")
output_layout.addWidget(self.output_title)
# 命令輸出
self.command_output = QTextEdit() self.command_output = QTextEdit()
self.command_output.setReadOnly(True) self.command_output.setReadOnly(True)
self.command_output.setMinimumHeight(200) self.command_output.setFont(QFont("Consolas", 9))
self.command_output.setStyleSheet("background-color: #1e1e1e; color: #ffffff; font-family: 'Consolas', 'Monaco', 'Courier New', monospace;") output_layout.addWidget(self.command_output)
# 確保命令輸出區域能夠擴展
self.command_output.setSizePolicy(QSizePolicy.Expanding, QSizePolicy.Expanding)
command_group_layout.addWidget(self.command_output)
# 設置群組框的大小策略 layout.addWidget(output_group, stretch=1)
command_group.setSizePolicy(QSizePolicy.Expanding, QSizePolicy.Expanding)
command_layout.addWidget(command_group) self.tab_widget.addTab(command_widget, t('command_tab'))
self.tabs.addTab(command_widget, "⚡ 命令")
def _create_action_buttons(self, layout: QVBoxLayout) -> None: def _create_action_buttons(self, layout: QVBoxLayout) -> None:
"""創建操作按鈕""" """創建操作按鈕"""
button_layout = QHBoxLayout() button_layout = QHBoxLayout()
self.submit_button = QPushButton("✅ 提交回饋")
self.submit_button.clicked.connect(self._submit_feedback)
self.submit_button.setStyleSheet("""
QPushButton {
background-color: #4caf50;
color: white;
border: none;
padding: 10px 20px;
border-radius: 6px;
font-weight: bold;
font-size: 14px;
}
QPushButton:hover { background-color: #45a049; }
""")
self.cancel_button = QPushButton("❌ 取消")
self.cancel_button.clicked.connect(self._cancel_feedback)
self.cancel_button.setStyleSheet("""
QPushButton {
background-color: #f44336;
color: white;
border: none;
padding: 10px 20px;
border-radius: 6px;
font-weight: bold;
font-size: 14px;
}
QPushButton:hover { background-color: #d32f2f; }
""")
button_layout.addStretch() button_layout.addStretch()
# 取消按鈕
self.cancel_button = QPushButton(t('btn_cancel'))
self.cancel_button.clicked.connect(self._cancel_feedback)
self.cancel_button.setFixedSize(120, 35)
button_layout.addWidget(self.cancel_button) button_layout.addWidget(self.cancel_button)
# 提交按鈕
self.submit_button = QPushButton(t('btn_submit_feedback'))
self.submit_button.clicked.connect(self._submit_feedback)
self.submit_button.setFixedSize(140, 35)
self.submit_button.setDefault(True)
button_layout.addWidget(self.submit_button) button_layout.addWidget(self.submit_button)
layout.addLayout(button_layout) layout.addLayout(button_layout)
@ -833,7 +1002,7 @@ class FeedbackWindow(QMainWindow):
try: try:
# 在專案目錄中執行命令 # 在專案目錄中執行命令
self.process = subprocess.Popen( self.command_process = subprocess.Popen(
command, command,
shell=True, shell=True,
cwd=self.project_dir, cwd=self.project_dir,
@ -854,9 +1023,9 @@ class FeedbackWindow(QMainWindow):
def _read_command_output(self) -> None: def _read_command_output(self) -> None:
"""讀取命令輸出""" """讀取命令輸出"""
if self.process and self.process.poll() is None: if self.command_process and self.command_process.poll() is None:
try: try:
output = self.process.stdout.readline() output = self.command_process.stdout.readline()
if output: if output:
self.command_output.insertPlainText(output) self.command_output.insertPlainText(output)
# 自動滾動到底部 # 自動滾動到底部
@ -869,32 +1038,30 @@ class FeedbackWindow(QMainWindow):
# 進程結束 # 進程結束
if hasattr(self, 'timer'): if hasattr(self, 'timer'):
self.timer.stop() self.timer.stop()
if self.process: if self.command_process:
return_code = self.process.returncode return_code = self.command_process.returncode
self.command_output.append(f"\n進程結束,返回碼: {return_code}\n") self.command_output.append(f"\n進程結束,返回碼: {return_code}\n")
def _submit_feedback(self) -> None: def _submit_feedback(self) -> None:
"""提交回饋""" """提交回饋"""
self.result = { self.result = {
"interactive_feedback": self.feedback_text.toPlainText(), "interactive_feedback": self.feedback_input.toPlainText(),
"command_logs": self.command_output.toPlainText(), "command_logs": self.command_output.toPlainText(),
"images": self.image_upload.get_images_data() "images": self.image_upload.get_images_data()
} }
self.accepted = True
self.close() self.close()
def _cancel_feedback(self) -> None: def _cancel_feedback(self) -> None:
"""取消回饋""" """取消回饋"""
self.accepted = False
self.close() self.close()
def closeEvent(self, event) -> None: def closeEvent(self, event) -> None:
"""處理視窗關閉事件""" """處理視窗關閉事件"""
if hasattr(self, 'timer'): if hasattr(self, 'timer'):
self.timer.stop() self.timer.stop()
if self.process: if self.command_process:
try: try:
self.process.terminate() self.command_process.terminate()
except: except:
pass pass
@ -945,7 +1112,7 @@ def feedback_ui(project_directory: str, summary: str) -> Optional[FeedbackResult
app.exec() app.exec()
# 返回結果 # 返回結果
if window.accepted: if window.result:
return window.result return window.result
else: else:
return None return None

View File

@ -0,0 +1,346 @@
#!/usr/bin/env python3
# -*- coding: utf-8 -*-
"""
國際化支援模組
===============
提供統一的多語系支援功能支援繁體中文英文等語言
自動偵測系統語言並提供語言切換功能
新架構
- 使用分離的 JSON 翻譯檔案
- 支援巢狀翻譯鍵值
- 元資料支援
- 易於擴充新語言
作者: Minidoracat
"""
import os
import sys
import locale
import json
from typing import Dict, Any, Optional, Union
from pathlib import Path
class I18nManager:
"""國際化管理器 - 新架構版本"""
def __init__(self):
self._current_language = None
self._translations = {}
self._supported_languages = ['zh-TW', 'en', 'zh-CN']
self._fallback_language = 'en'
self._config_file = self._get_config_file_path()
self._locales_dir = Path(__file__).parent / "locales"
# 載入翻譯
self._load_all_translations()
# 設定語言
self._current_language = self._detect_language()
def _get_config_file_path(self) -> Path:
"""獲取配置文件路徑"""
config_dir = Path.home() / ".config" / "mcp-feedback-enhanced"
config_dir.mkdir(parents=True, exist_ok=True)
return config_dir / "language.json"
def _load_all_translations(self) -> None:
"""載入所有語言的翻譯檔案"""
self._translations = {}
for lang_code in self._supported_languages:
lang_dir = self._locales_dir / lang_code
translation_file = lang_dir / "translations.json"
if translation_file.exists():
try:
with open(translation_file, 'r', encoding='utf-8') as f:
data = json.load(f)
self._translations[lang_code] = data
print(f"[I18N] 成功載入語言 {lang_code}: {data.get('meta', {}).get('displayName', lang_code)}")
except Exception as e:
print(f"[I18N] 載入語言檔案失敗 {lang_code}: {e}")
# 如果載入失敗,使用空的翻譯
self._translations[lang_code] = {}
else:
print(f"[I18N] 找不到語言檔案: {translation_file}")
self._translations[lang_code] = {}
def _detect_language(self) -> str:
"""自動偵測語言"""
# 1. 優先使用用戶保存的語言設定
saved_lang = self._load_saved_language()
if saved_lang and saved_lang in self._supported_languages:
return saved_lang
# 2. 檢查環境變數
env_lang = os.getenv('MCP_LANGUAGE', '').strip()
if env_lang and env_lang in self._supported_languages:
return env_lang
# 3. 自動偵測系統語言
try:
# 獲取系統語言
system_locale = locale.getdefaultlocale()[0]
if system_locale:
if system_locale.startswith('zh_TW') or system_locale.startswith('zh_Hant'):
return 'zh-TW'
elif system_locale.startswith('zh_CN') or system_locale.startswith('zh_Hans'):
return 'zh-CN'
elif system_locale.startswith('en'):
return 'en'
except Exception:
pass
# 4. 回退到默認語言
return self._fallback_language
def _load_saved_language(self) -> Optional[str]:
"""載入保存的語言設定"""
try:
if self._config_file.exists():
with open(self._config_file, 'r', encoding='utf-8') as f:
config = json.load(f)
return config.get('language')
except Exception:
pass
return None
def save_language(self, language: str) -> None:
"""保存語言設定"""
try:
config = {'language': language}
with open(self._config_file, 'w', encoding='utf-8') as f:
json.dump(config, f, ensure_ascii=False, indent=2)
except Exception:
pass
def get_current_language(self) -> str:
"""獲取當前語言"""
return self._current_language
def set_language(self, language: str) -> bool:
"""設定語言"""
if language in self._supported_languages:
self._current_language = language
self.save_language(language)
return True
return False
def get_supported_languages(self) -> list:
"""獲取支援的語言列表"""
return self._supported_languages.copy()
def get_language_info(self, language_code: str) -> Dict[str, Any]:
"""獲取語言的元資料信息"""
if language_code in self._translations:
return self._translations[language_code].get('meta', {})
return {}
def _get_nested_value(self, data: Dict[str, Any], key_path: str) -> Optional[str]:
"""從巢狀字典中獲取值,支援點分隔的鍵路徑"""
keys = key_path.split('.')
current = data
for key in keys:
if isinstance(current, dict) and key in current:
current = current[key]
else:
return None
return current if isinstance(current, str) else None
def t(self, key: str, **kwargs) -> str:
"""
翻譯函數 - 支援新舊兩種鍵值格式
新格式: 'buttons.submit' -> data['buttons']['submit']
舊格式: 'btn_submit_feedback' -> 兼容舊的鍵值
"""
# 獲取當前語言的翻譯
current_translations = self._translations.get(self._current_language, {})
# 嘗試新格式(巢狀鍵)
text = self._get_nested_value(current_translations, key)
# 如果沒有找到,嘗試舊格式的兼容映射
if text is None:
text = self._get_legacy_translation(current_translations, key)
# 如果還是沒有找到,嘗試使用回退語言
if text is None:
fallback_translations = self._translations.get(self._fallback_language, {})
text = self._get_nested_value(fallback_translations, key)
if text is None:
text = self._get_legacy_translation(fallback_translations, key)
# 最後回退到鍵本身
if text is None:
text = key
# 處理格式化參數
if kwargs:
try:
text = text.format(**kwargs)
except (KeyError, ValueError):
pass
return text
def _get_legacy_translation(self, translations: Dict[str, Any], key: str) -> Optional[str]:
"""獲取舊格式翻譯的兼容方法"""
# 舊鍵到新鍵的映射
legacy_mapping = {
# 應用程式
'app_title': 'app.title',
'project_directory': 'app.projectDirectory',
'language': 'app.language',
'settings': 'app.settings',
# 分頁
'feedback_tab': 'tabs.feedback',
'command_tab': 'tabs.command',
'images_tab': 'tabs.images',
# 回饋
'feedback_title': 'feedback.title',
'feedback_description': 'feedback.description',
'feedback_placeholder': 'feedback.placeholder',
# 命令
'command_title': 'command.title',
'command_description': 'command.description',
'command_placeholder': 'command.placeholder',
'command_output': 'command.output',
# 圖片
'images_title': 'images.title',
'images_select': 'images.select',
'images_paste': 'images.paste',
'images_clear': 'images.clear',
'images_status': 'images.status',
'images_status_with_size': 'images.statusWithSize',
'images_drag_hint': 'images.dragHint',
'images_delete_confirm': 'images.deleteConfirm',
'images_delete_title': 'images.deleteTitle',
'images_size_warning': 'images.sizeWarning',
'images_format_error': 'images.formatError',
# 按鈕
'submit': 'buttons.submit',
'cancel': 'buttons.cancel',
'close': 'buttons.close',
'clear': 'buttons.clear',
'btn_submit_feedback': 'buttons.submitFeedback',
'btn_cancel': 'buttons.cancel',
'btn_select_files': 'buttons.selectFiles',
'btn_paste_clipboard': 'buttons.pasteClipboard',
'btn_clear_all': 'buttons.clearAll',
'btn_run_command': 'buttons.runCommand',
# 狀態
'feedback_submitted': 'status.feedbackSubmitted',
'feedback_cancelled': 'status.feedbackCancelled',
'timeout_message': 'status.timeoutMessage',
'error_occurred': 'status.errorOccurred',
'loading': 'status.loading',
'connecting': 'status.connecting',
'connected': 'status.connected',
'disconnected': 'status.disconnected',
'uploading': 'status.uploading',
'upload_success': 'status.uploadSuccess',
'upload_failed': 'status.uploadFailed',
'command_running': 'status.commandRunning',
'command_finished': 'status.commandFinished',
'paste_success': 'status.pasteSuccess',
'paste_failed': 'status.pasteFailed',
'invalid_file_type': 'status.invalidFileType',
'file_too_large': 'status.fileTooLarge',
# 其他
'ai_summary': 'aiSummary',
'language_selector': 'languageSelector',
'language_zh_tw': 'languageNames.zhTw',
'language_en': 'languageNames.en',
'language_zh_cn': 'languageNames.zhCn',
}
# 檢查是否有對應的新鍵
new_key = legacy_mapping.get(key)
if new_key:
return self._get_nested_value(translations, new_key)
return None
def get_language_display_name(self, language_code: str) -> str:
"""獲取語言的顯示名稱"""
# 從當前語言的翻譯中獲取
lang_key = f"languageNames.{language_code.replace('-', '').lower()}"
if language_code == 'zh-TW':
lang_key = 'languageNames.zhTw'
elif language_code == 'zh-CN':
lang_key = 'languageNames.zhCn'
elif language_code == 'en':
lang_key = 'languageNames.en'
display_name = self.t(lang_key)
if display_name != lang_key: # 如果找到了翻譯
return display_name
# 回退到元資料中的顯示名稱
meta = self.get_language_info(language_code)
return meta.get('displayName', language_code)
def reload_translations(self) -> None:
"""重新載入所有翻譯檔案(開發時使用)"""
self._load_all_translations()
def add_language(self, language_code: str, translation_file_path: str) -> bool:
"""動態添加新語言支援"""
try:
translation_file = Path(translation_file_path)
if not translation_file.exists():
return False
with open(translation_file, 'r', encoding='utf-8') as f:
data = json.load(f)
self._translations[language_code] = data
if language_code not in self._supported_languages:
self._supported_languages.append(language_code)
print(f"[I18N] 成功添加語言 {language_code}: {data.get('meta', {}).get('displayName', language_code)}")
return True
except Exception as e:
print(f"[I18N] 添加語言失敗 {language_code}: {e}")
return False
# 全域的國際化管理器實例
_i18n_manager = None
def get_i18n_manager() -> I18nManager:
"""獲取全域的國際化管理器實例"""
global _i18n_manager
if _i18n_manager is None:
_i18n_manager = I18nManager()
return _i18n_manager
def t(key: str, **kwargs) -> str:
"""便捷的翻譯函數"""
return get_i18n_manager().t(key, **kwargs)
def set_language(language: str) -> bool:
"""設定語言"""
return get_i18n_manager().set_language(language)
def get_current_language() -> str:
"""獲取當前語言"""
return get_i18n_manager().get_current_language()
def reload_translations() -> None:
"""重新載入翻譯(開發用)"""
get_i18n_manager().reload_translations()

View File

@ -0,0 +1,172 @@
# 多語系檔案結構說明
## 📁 檔案結構
```
locales/
├── README.md # 此說明檔案
├── zh-TW/ # 繁體中文
│ └── translations.json
├── en/ # 英文
│ └── translations.json
└── zh-CN/ # 簡體中文
└── translations.json
```
## 🌐 翻譯檔案格式
每個語言的 `translations.json` 檔案都包含以下結構:
### 1. 元資料區塊 (meta)
```json
{
"meta": {
"language": "zh-TW",
"displayName": "繁體中文",
"author": "作者名稱",
"version": "1.0.0",
"lastUpdate": "2025-01-31"
}
}
```
### 2. 應用程式區塊 (app)
```json
{
"app": {
"title": "應用程式標題",
"projectDirectory": "專案目錄",
"language": "語言",
"settings": "設定"
}
}
```
### 3. 分頁區塊 (tabs)
```json
{
"tabs": {
"feedback": "💬 回饋",
"command": "⚡ 命令",
"images": "🖼️ 圖片"
}
}
```
### 4. 其他功能區塊
- `feedback`: 回饋相關文字
- `command`: 命令執行相關文字
- `images`: 圖片上傳相關文字
- `buttons`: 按鈕文字
- `status`: 狀態訊息
- `aiSummary`: AI 摘要標題
- `languageSelector`: 語言選擇器標題
- `languageNames`: 語言顯示名稱
## 🔧 新增新語言步驟
### 1. 建立語言目錄
```bash
mkdir src/mcp_feedback_enhanced/locales/[語言代碼]
```
### 2. 複製翻譯檔案
```bash
cp src/mcp_feedback_enhanced/locales/en/translations.json \
src/mcp_feedback_enhanced/locales/[語言代碼]/translations.json
```
### 3. 修改元資料
```json
{
"meta": {
"language": "[語言代碼]",
"displayName": "[語言顯示名稱]",
"author": "[翻譯者姓名]",
"version": "1.0.0",
"lastUpdate": "[日期]"
}
}
```
### 4. 翻譯內容
逐一翻譯各個區塊的內容,保持 JSON 結構不變。
### 5. 註冊新語言
`i18n.py` 中將新語言代碼加入支援列表:
```python
self._supported_languages = ['zh-TW', 'en', 'zh-CN', '[新語言代碼]']
```
`i18n.js` 中也要加入:
```javascript
this.supportedLanguages = ['zh-TW', 'en', 'zh-CN', '[新語言代碼]'];
```
## 🎯 使用方式
### Python 後端
```python
from .i18n import t
# 新格式(建議)
title = t('app.title')
button_text = t('buttons.submitFeedback')
# 舊格式(兼容)
title = t('app_title')
button_text = t('btn_submit_feedback')
```
### JavaScript 前端
```javascript
// 新格式(建議)
const title = t('app.title');
const buttonText = t('buttons.submitFeedback');
// 舊格式(兼容)
const title = t('app_title');
const buttonText = t('btn_submit_feedback');
```
## 📋 翻譯檢查清單
建議在新增或修改翻譯時檢查:
- [ ] JSON 格式正確,沒有語法錯誤
- [ ] 所有必要的鍵值都存在
- [ ] 佔位符 `{param}` 格式正確
- [ ] 特殊字符和 Emoji 顯示正常
- [ ] 文字長度適合 UI 顯示
- [ ] 語言顯示名稱在 `languageNames` 中正確設定
## 🔄 向後兼容
新的多語系系統完全向後兼容舊的鍵值格式:
| 舊格式 | 新格式 |
|--------|--------|
| `app_title` | `app.title` |
| `btn_submit_feedback` | `buttons.submitFeedback` |
| `images_status` | `images.status` |
| `command_output` | `command.output` |
## 🚀 優勢特色
1. **結構化組織**:按功能區域分組,易於維護
2. **元資料支援**:包含版本、作者等資訊
3. **巢狀鍵值**:更清晰的命名空間
4. **動態載入**:前端支援從 API 載入翻譯
5. **向後兼容**:舊程式碼無需修改
6. **易於擴充**:新增語言非常簡單
## 📝 貢獻指南
歡迎貢獻新的語言翻譯:
1. Fork 專案
2. 按照上述步驟新增語言
3. 測試翻譯是否正確顯示
4. 提交 Pull Request
需要幫助可以參考現有的翻譯檔案作為範本。

View File

@ -0,0 +1,81 @@
{
"meta": {
"language": "en",
"displayName": "English",
"author": "Minidoracat",
"version": "1.0.0",
"lastUpdate": "2025-01-31"
},
"app": {
"title": "Interactive Feedback Collection",
"projectDirectory": "Project Directory",
"language": "Language",
"settings": "Settings"
},
"tabs": {
"feedback": "💬 Feedback",
"command": "⚡ Command",
"images": "🖼️ Images"
},
"feedback": {
"title": "Your Feedback",
"description": "Please describe your thoughts, suggestions, or changes needed for the AI work results.",
"placeholder": "Please enter your feedback, suggestions, or questions here...\n\n💡 Tip: Press Ctrl+Enter to submit quickly"
},
"command": {
"title": "Command Execution",
"description": "You can execute commands to verify results or gather more information.",
"placeholder": "Enter command to execute...",
"output": "Command Output"
},
"images": {
"title": "🖼️ Image Attachments (Optional)",
"select": "Select Files",
"paste": "Clipboard",
"clear": "Clear",
"status": "{count} images selected",
"statusWithSize": "{count} images selected (Total {size})",
"dragHint": "🎯 Drag images here (PNG, JPG, JPEG, GIF, BMP, WebP)",
"deleteConfirm": "Are you sure you want to remove image \"{filename}\"?",
"deleteTitle": "Confirm Delete",
"sizeWarning": "Image file size cannot exceed 1MB",
"formatError": "Unsupported image format"
},
"buttons": {
"submit": "Submit Feedback",
"cancel": "Cancel",
"close": "Close",
"clear": "Clear",
"submitFeedback": "✅ Submit Feedback",
"selectFiles": "📁 Select Files",
"pasteClipboard": "📋 Clipboard",
"clearAll": "✕ Clear",
"runCommand": "▶️ Run"
},
"status": {
"feedbackSubmitted": "Feedback submitted successfully!",
"feedbackCancelled": "Feedback cancelled.",
"timeoutMessage": "Feedback timeout",
"errorOccurred": "Error occurred",
"loading": "Loading...",
"connecting": "Connecting...",
"connected": "Connected",
"disconnected": "Disconnected",
"uploading": "Uploading...",
"uploadSuccess": "Upload successful",
"uploadFailed": "Upload failed",
"commandRunning": "Command running...",
"commandFinished": "Command finished",
"pasteSuccess": "Image pasted from clipboard",
"pasteFailed": "Failed to get image from clipboard",
"invalidFileType": "Unsupported file type",
"fileTooLarge": "File too large (max 1MB)"
},
"aiSummary": "AI Work Summary",
"languageSelector": "🌐 Language",
"languageNames": {
"zhTw": "繁體中文",
"en": "English",
"zhCn": "简体中文"
}
}

View File

@ -0,0 +1,81 @@
{
"meta": {
"language": "zh-CN",
"displayName": "简体中文",
"author": "Minidoracat",
"version": "1.0.0",
"lastUpdate": "2025-01-31"
},
"app": {
"title": "交互式反馈收集",
"projectDirectory": "项目目录",
"language": "语言",
"settings": "设置"
},
"tabs": {
"feedback": "💬 反馈",
"command": "⚡ 命令",
"images": "🖼️ 图片"
},
"feedback": {
"title": "您的反馈",
"description": "请描述您对 AI 工作结果的想法、建议或需要修改的地方。",
"placeholder": "请在这里输入您的反馈、建议或问题...\n\n💡 小提示:按 Ctrl+Enter 可快速提交反馈"
},
"command": {
"title": "命令执行",
"description": "您可以执行命令来验证结果或收集更多信息。",
"placeholder": "输入要执行的命令...",
"output": "命令输出"
},
"images": {
"title": "🖼️ 图片附件(可选)",
"select": "选择文件",
"paste": "剪贴板",
"clear": "清除",
"status": "已选择 {count} 张图片",
"statusWithSize": "已选择 {count} 张图片 (总计 {size})",
"dragHint": "🎯 拖拽图片到这里 (PNG、JPG、JPEG、GIF、BMP、WebP)",
"deleteConfirm": "确定要移除图片 \"{filename}\" 吗?",
"deleteTitle": "确认删除",
"sizeWarning": "图片文件大小不能超过 1MB",
"formatError": "不支持的图片格式"
},
"buttons": {
"submit": "提交反馈",
"cancel": "取消",
"close": "关闭",
"clear": "清除",
"submitFeedback": "✅ 提交反馈",
"selectFiles": "📁 选择文件",
"pasteClipboard": "📋 剪贴板",
"clearAll": "✕ 清除",
"runCommand": "▶️ 执行"
},
"status": {
"feedbackSubmitted": "反馈已成功提交!",
"feedbackCancelled": "已取消反馈。",
"timeoutMessage": "等待反馈超时",
"errorOccurred": "发生错误",
"loading": "加载中...",
"connecting": "连接中...",
"connected": "已连接",
"disconnected": "连接中断",
"uploading": "上传中...",
"uploadSuccess": "上传成功",
"uploadFailed": "上传失败",
"commandRunning": "命令执行中...",
"commandFinished": "命令执行完成",
"pasteSuccess": "已从剪贴板粘贴图片",
"pasteFailed": "无法从剪贴板获取图片",
"invalidFileType": "不支持的文件类型",
"fileTooLarge": "文件过大(最大 1MB"
},
"aiSummary": "AI 工作摘要",
"languageSelector": "🌐 语言选择",
"languageNames": {
"zhTw": "繁體中文",
"en": "English",
"zhCn": "简体中文"
}
}

View File

@ -0,0 +1,81 @@
{
"meta": {
"language": "zh-TW",
"displayName": "繁體中文",
"author": "Minidoracat",
"version": "1.0.0",
"lastUpdate": "2025-01-31"
},
"app": {
"title": "互動式回饋收集",
"projectDirectory": "專案目錄",
"language": "語言",
"settings": "設定"
},
"tabs": {
"feedback": "💬 回饋",
"command": "⚡ 命令",
"images": "🖼️ 圖片"
},
"feedback": {
"title": "您的回饋",
"description": "請描述您對 AI 工作結果的想法、建議或需要修改的地方。",
"placeholder": "請在這裡輸入您的回饋、建議或問題...\n\n💡 小提示:按 Ctrl+Enter 可快速提交回饋"
},
"command": {
"title": "命令執行",
"description": "您可以執行命令來驗證結果或收集更多資訊。",
"placeholder": "輸入要執行的命令...",
"output": "命令輸出"
},
"images": {
"title": "🖼️ 圖片附件(可選)",
"select": "選擇文件",
"paste": "剪貼板",
"clear": "清除",
"status": "已選擇 {count} 張圖片",
"statusWithSize": "已選擇 {count} 張圖片 (總計 {size})",
"dragHint": "🎯 拖拽圖片到這裡 (PNG、JPG、JPEG、GIF、BMP、WebP)",
"deleteConfirm": "確定要移除圖片 \"{filename}\" 嗎?",
"deleteTitle": "確認刪除",
"sizeWarning": "圖片文件大小不能超過 1MB",
"formatError": "不支援的圖片格式"
},
"buttons": {
"submit": "提交回饋",
"cancel": "取消",
"close": "關閉",
"clear": "清除",
"submitFeedback": "✅ 提交回饋",
"selectFiles": "📁 選擇文件",
"pasteClipboard": "📋 剪貼板",
"clearAll": "✕ 清除",
"runCommand": "▶️ 執行"
},
"status": {
"feedbackSubmitted": "回饋已成功提交!",
"feedbackCancelled": "已取消回饋。",
"timeoutMessage": "等待回饋超時",
"errorOccurred": "發生錯誤",
"loading": "載入中...",
"connecting": "連接中...",
"connected": "已連接",
"disconnected": "連接中斷",
"uploading": "上傳中...",
"uploadSuccess": "上傳成功",
"uploadFailed": "上傳失敗",
"commandRunning": "命令執行中...",
"commandFinished": "命令執行完成",
"pasteSuccess": "已從剪貼板貼上圖片",
"pasteFailed": "無法從剪貼板獲取圖片",
"invalidFileType": "不支援的文件類型",
"fileTooLarge": "文件過大(最大 1MB"
},
"aiSummary": "AI 工作摘要",
"languageSelector": "🌐 語言",
"languageNames": {
"zhTw": "繁體中文",
"en": "English",
"zhCn": "简体中文"
}
}

View File

@ -0,0 +1,472 @@
/**
* 前端國際化支援 - 新架構版本
* =============================
*
* 提供 Web UI 的多語系支援支援繁體中文英文簡體中文
* 新特性
* - 支援從 API 動態載入翻譯檔案
* - 巢狀翻譯鍵值支援
* - 舊格式兼容
* - 自動偵測瀏覽器語言
*/
class I18nManager {
constructor() {
this.currentLanguage = null;
this.translations = {};
this.supportedLanguages = ['zh-TW', 'en', 'zh-CN'];
this.fallbackLanguage = 'en';
this.isLoaded = false;
// 內嵌的備用翻譯(防止 API 載入失敗)
this.fallbackTranslations = this._getEmbeddedTranslations();
// 初始化語言設定
this.currentLanguage = this.detectLanguage();
}
/**
* 獲取內嵌的備用翻譯
*/
_getEmbeddedTranslations() {
return {
'zh-TW': {
app: {
title: 'Interactive Feedback MCP',
projectDirectory: '專案目錄',
language: '語言'
},
tabs: {
feedback: '💬 回饋',
command: '⚡ 命令'
},
feedback: {
title: '💬 您的回饋',
description: '請在這裡輸入您的回饋、建議或問題。您的意見將幫助 AI 更好地理解您的需求。',
placeholder: '請在這裡輸入您的回饋、建議或問題...\n\n💡 小提示:按 Ctrl+Enter 可快速提交回饋'
},
command: {
title: '⚡ 命令執行',
description: '您可以在此執行系統命令來驗證結果或獲取更多資訊。',
placeholder: '輸入要執行的命令...',
output: '命令輸出'
},
images: {
title: '🖼️ 圖片附件(可選)',
status: '已選擇 {count} 張圖片',
statusWithSize: '已選擇 {count} 張圖片 (總計 {size})',
dragHint: '🎯 拖拽圖片到這裡 (PNG、JPG、JPEG、GIF、BMP、WebP)',
deleteConfirm: '確定要移除圖片 "{filename}" 嗎?',
deleteTitle: '確認刪除'
},
buttons: {
selectFiles: '📁 選擇文件',
pasteClipboard: '📋 剪貼板',
clearAll: '✕ 清除',
runCommand: '▶️ 執行',
submitFeedback: '✅ 提交回饋',
cancel: '❌ 取消'
},
status: {
uploading: '上傳中...',
uploadSuccess: '上傳成功',
uploadFailed: '上傳失敗',
commandRunning: '命令執行中...',
commandFinished: '命令執行完成',
pasteSuccess: '已從剪貼板貼上圖片',
pasteFailed: '無法從剪貼板獲取圖片',
invalidFileType: '不支援的文件類型',
fileTooLarge: '文件過大(最大 1MB'
},
aiSummary: '📋 AI 工作摘要',
languageSelector: '🌐 語言選擇',
languageNames: {
zhTw: '繁體中文',
en: 'English',
zhCn: '简体中文'
}
},
'en': {
app: {
title: 'Interactive Feedback MCP',
projectDirectory: 'Project Directory',
language: 'Language'
},
tabs: {
feedback: '💬 Feedback',
command: '⚡ Commands'
},
feedback: {
title: '💬 Your Feedback',
description: 'Please enter your feedback, suggestions, or questions here. Your input helps AI better understand your needs.',
placeholder: 'Please enter your feedback, suggestions, or questions here...\n\n💡 Tip: Press Ctrl+Enter to submit quickly'
},
command: {
title: '⚡ Command Execution',
description: 'You can execute system commands here to verify results or get additional information.',
placeholder: 'Enter command to execute...',
output: 'Command Output'
},
images: {
title: '🖼️ Image Attachments (Optional)',
status: '{count} images selected',
statusWithSize: '{count} images selected (Total {size})',
dragHint: '🎯 Drag images here (PNG, JPG, JPEG, GIF, BMP, WebP)',
deleteConfirm: 'Are you sure you want to remove image "{filename}"?',
deleteTitle: 'Confirm Delete'
},
buttons: {
selectFiles: '📁 Select Files',
pasteClipboard: '📋 Clipboard',
clearAll: '✕ Clear',
runCommand: '▶️ Run',
submitFeedback: '✅ Submit Feedback',
cancel: '❌ Cancel'
},
status: {
uploading: 'Uploading...',
uploadSuccess: 'Upload successful',
uploadFailed: 'Upload failed',
commandRunning: 'Command running...',
commandFinished: 'Command finished',
pasteSuccess: 'Image pasted from clipboard',
pasteFailed: 'Failed to get image from clipboard',
invalidFileType: 'Unsupported file type',
fileTooLarge: 'File too large (max 1MB)'
},
aiSummary: '📋 AI Work Summary',
languageSelector: '🌐 Language',
languageNames: {
zhTw: '繁體中文',
en: 'English',
zhCn: '简体中文'
}
},
'zh-CN': {
app: {
title: 'Interactive Feedback MCP',
projectDirectory: '项目目录',
language: '语言'
},
tabs: {
feedback: '💬 反馈',
command: '⚡ 命令'
},
feedback: {
title: '💬 您的反馈',
description: '请在这里输入您的反馈、建议或问题。您的意见将帮助 AI 更好地理解您的需求。',
placeholder: '请在这里输入您的反馈、建议或问题...\n\n💡 小提示:按 Ctrl+Enter 可快速提交反馈'
},
command: {
title: '⚡ 命令执行',
description: '您可以在此执行系统命令来验证结果或获取更多信息。',
placeholder: '输入要执行的命令...',
output: '命令输出'
},
images: {
title: '🖼️ 图片附件(可选)',
status: '已选择 {count} 张图片',
statusWithSize: '已选择 {count} 张图片 (总计 {size})',
dragHint: '🎯 拖拽图片到这里 (PNG、JPG、JPEG、GIF、BMP、WebP)',
deleteConfirm: '确定要移除图片 "{filename}" 吗?',
deleteTitle: '确认删除'
},
buttons: {
selectFiles: '📁 选择文件',
pasteClipboard: '📋 剪贴板',
clearAll: '✕ 清除',
runCommand: '▶️ 执行',
submitFeedback: '✅ 提交反馈',
cancel: '❌ 取消'
},
status: {
uploading: '上传中...',
uploadSuccess: '上传成功',
uploadFailed: '上传失败',
commandRunning: '命令执行中...',
commandFinished: '命令执行完成',
pasteSuccess: '已从剪贴板粘贴图片',
pasteFailed: '无法从剪贴板获取图片',
invalidFileType: '不支持的文件类型',
fileTooLarge: '文件过大(最大 1MB'
},
aiSummary: '📋 AI 工作摘要',
languageSelector: '🌐 语言选择',
languageNames: {
zhTw: '繁體中文',
en: 'English',
zhCn: '简体中文'
}
}
};
}
/**
* API 載入翻譯檔案
*/
async loadTranslations() {
try {
// 嘗試從 API 載入翻譯
const response = await fetch('/api/translations');
if (response.ok) {
const data = await response.json();
this.translations = data;
this.isLoaded = true;
console.log('[I18N] 成功從 API 載入翻譯');
return true;
}
} catch (error) {
console.warn('[I18N] 無法從 API 載入翻譯,使用內嵌翻譯:', error);
}
// 使用內嵌翻譯作為備用
this.translations = this.fallbackTranslations;
this.isLoaded = true;
console.log('[I18N] 使用內嵌翻譯');
return false;
}
/**
* 自動偵測語言
*/
detectLanguage() {
// 1. 先檢查 localStorage
const savedLang = localStorage.getItem('mcp-feedback-language');
if (savedLang && this.supportedLanguages.includes(savedLang)) {
return savedLang;
}
// 2. 檢查瀏覽器語言設定
const browserLang = navigator.language || navigator.userLanguage;
// 映射常見的語言代碼
const langMap = {
'zh-TW': 'zh-TW',
'zh-HK': 'zh-TW',
'zh-MO': 'zh-TW',
'zh-CN': 'zh-CN',
'zh-SG': 'zh-CN',
'zh': 'zh-TW', // 默認繁體中文
'en': 'en',
'en-US': 'en',
'en-GB': 'en',
'en-AU': 'en',
'en-CA': 'en'
};
if (langMap[browserLang]) {
return langMap[browserLang];
}
// 3. 檢查語言前綴
const prefix = browserLang.split('-')[0];
if (langMap[prefix]) {
return langMap[prefix];
}
// 4. 回退到默認語言
return this.fallbackLanguage;
}
/**
* 設定語言
*/
setLanguage(language) {
if (!this.supportedLanguages.includes(language)) {
console.warn(`Unsupported language: ${language}`);
return false;
}
this.currentLanguage = language;
localStorage.setItem('mcp-feedback-language', language);
// 觸發語言變更事件
document.dispatchEvent(new CustomEvent('languageChanged', {
detail: { language: language }
}));
return true;
}
/**
* 從巢狀物件中獲取值
*/
_getNestedValue(obj, path) {
return path.split('.').reduce((current, key) => {
return current && current[key] !== undefined ? current[key] : null;
}, obj);
}
/**
* 舊鍵到新鍵的映射
*/
_getLegacyMapping() {
return {
// 應用程式標題
'app_title': 'app.title',
'project_directory': 'app.projectDirectory',
'language_selector': 'languageSelector',
// 語言名稱
'lang_zh_tw': 'languageNames.zhTw',
'lang_en': 'languageNames.en',
'lang_zh_cn': 'languageNames.zhCn',
// AI 摘要區域
'ai_summary': 'aiSummary',
// 分頁標籤
'feedback_tab': 'tabs.feedback',
'command_tab': 'tabs.command',
// 回饋區域
'feedback_title': 'feedback.title',
'feedback_description': 'feedback.description',
'feedback_placeholder': 'feedback.placeholder',
// 命令區域
'command_title': 'command.title',
'command_description': 'command.description',
'command_placeholder': 'command.placeholder',
'command_output': 'command.output',
// 圖片區域
'images_title': 'images.title',
'images_select': 'images.select',
'images_paste': 'images.paste',
'images_clear': 'images.clear',
'images_status': 'images.status',
'images_status_with_size': 'images.statusWithSize',
'images_drag_hint': 'images.dragHint',
'images_delete_confirm': 'images.deleteConfirm',
'images_delete_title': 'images.deleteTitle',
'images_size_warning': 'images.sizeWarning',
'images_format_error': 'images.formatError',
// 按鈕
'btn_select_files': 'buttons.selectFiles',
'btn_paste_clipboard': 'buttons.pasteClipboard',
'btn_clear_all': 'buttons.clearAll',
'btn_run_command': 'buttons.runCommand',
'btn_submit_feedback': 'buttons.submitFeedback',
'btn_cancel': 'buttons.cancel',
// 狀態消息
'uploading': 'status.uploading',
'upload_success': 'status.uploadSuccess',
'upload_failed': 'status.uploadFailed',
'command_running': 'status.commandRunning',
'command_finished': 'status.commandFinished',
'paste_success': 'status.pasteSuccess',
'paste_failed': 'status.pasteFailed',
'invalid_file_type': 'status.invalidFileType',
'file_too_large': 'status.fileTooLarge'
};
}
/**
* 獲取翻譯文字
*/
t(key, params = {}) {
// 確保翻譯已載入
if (!this.isLoaded) {
// 如果還沒載入,先嘗試從備用翻譯獲取
this.translations = this.fallbackTranslations;
}
const currentTranslations = this.translations[this.currentLanguage] || {};
// 嘗試新格式(巢狀鍵)
let translation = this._getNestedValue(currentTranslations, key);
// 如果沒有找到,嘗試舊格式映射
if (translation === null) {
const legacyMapping = this._getLegacyMapping();
const newKey = legacyMapping[key];
if (newKey) {
translation = this._getNestedValue(currentTranslations, newKey);
}
}
// 如果還是沒有找到,嘗試回退語言
if (translation === null) {
const fallbackTranslations = this.translations[this.fallbackLanguage] || {};
translation = this._getNestedValue(fallbackTranslations, key);
if (translation === null) {
const legacyMapping = this._getLegacyMapping();
const newKey = legacyMapping[key];
if (newKey) {
translation = this._getNestedValue(fallbackTranslations, newKey);
}
}
}
// 最後回退到鍵本身
if (translation === null) {
translation = key;
}
// 替換參數
if (typeof translation === 'string') {
translation = translation.replace(/{(\w+)}/g, (match, param) => {
return params[param] !== undefined ? params[param] : match;
});
}
return translation;
}
/**
* 獲取語言顯示名稱
*/
getLanguageDisplayName(languageCode) {
const key = `languageNames.${languageCode.toLowerCase().replace('-', '')}`;
if (languageCode === 'zh-TW') {
return this.t('languageNames.zhTw');
} else if (languageCode === 'zh-CN') {
return this.t('languageNames.zhCn');
} else if (languageCode === 'en') {
return this.t('languageNames.en');
}
return this.t(key);
}
/**
* 獲取當前語言
*/
getCurrentLanguage() {
return this.currentLanguage;
}
/**
* 獲取支援的語言列表
*/
getSupportedLanguages() {
return [...this.supportedLanguages];
}
/**
* 初始化載入翻譯
*/
async init() {
await this.loadTranslations();
return this.isLoaded;
}
}
// 創建全域實例
window.i18n = new I18nManager();
// 翻譯函數的全域快捷方式
window.t = function(key, params = {}) {
return window.i18n.t(key, params);
};
// 初始化函數
window.initI18n = async function() {
await window.i18n.init();
return window.i18n.isLoaded;
};

File diff suppressed because it is too large Load Diff

View File

@ -7,7 +7,7 @@
基於 FastAPI Web 用戶介面專為 SSH 遠端開發環境設計 基於 FastAPI Web 用戶介面專為 SSH 遠端開發環境設計
支援文字輸入圖片上傳命令執行等功能 支援文字輸入圖片上傳命令執行等功能
作者: Fábio Ferreira 作者: Minidoracat
靈感來源: dotcursorrules.com 靈感來源: dotcursorrules.com
增強功能: 圖片支援和現代化界面設計 增強功能: 圖片支援和現代化界面設計
""" """

742
uv.lock generated

File diff suppressed because it is too large Load Diff