mirror of
https://github.com/Minidoracat/mcp-feedback-enhanced.git
synced 2025-07-27 10:42:25 +08:00
📖 更新 README.md 和 README.zh-TW.md,新增多語言支援功能介紹及版本歷史,並更新介面截圖。新增 i18n.py 模組以支援多語系,並重構相關檔案結構以提升可維護性。
This commit is contained in:
parent
a35fec2a5b
commit
c470f686a9
52
README.md
52
README.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
|
||||
- **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
|
||||
|
||||
### Qt GUI Interface (Local Environment)
|
||||

|
||||

|
||||
|
||||
### Web UI Interface (SSH Remote Environment)
|
||||

|
||||

|
||||
|
||||
Both interfaces support:
|
||||
- 💬 Text feedback input
|
||||
@ -45,6 +52,7 @@ Both interfaces support:
|
||||
- ⚡ Real-time command execution
|
||||
- 🎨 Modern dark theme
|
||||
- 📱 Responsive design (Web UI)
|
||||
- 🌐 Multi-language support (Traditional Chinese, English, Simplified Chinese)
|
||||
|
||||
## 🎯 Why Use This Tool?
|
||||
|
||||
@ -205,6 +213,46 @@ uvx mcp-feedback-enhanced@latest version
|
||||
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
|
||||
|
||||
### Core Environment Variables
|
||||
|
@ -31,13 +31,20 @@
|
||||
- **錯誤處理**:強化錯誤處理,確保程序穩定運行
|
||||
- **輸出隔離**:嚴格隔離調試輸出與 MCP 通信
|
||||
|
||||
### 🌏 多語言支援
|
||||
- **完整國際化**:採用結構化 JSON 翻譯檔案的完整多語言支援
|
||||
- **支援語言**:繁體中文、英文、簡體中文
|
||||
- **智能偵測**:根據系統語言環境自動偵測
|
||||
- **易於擴充**:基於 JSON 的簡單翻譯系統,方便新增語言
|
||||
- **向後兼容**:與現有程式碼完全向後兼容
|
||||
|
||||
## 🖥️ 介面預覽
|
||||
|
||||
### Qt GUI 介面(本地環境)
|
||||

|
||||

|
||||
|
||||
### Web UI 介面(SSH Remote 環境)
|
||||

|
||||

|
||||
|
||||
兩種介面都支援:
|
||||
- 💬 文字回饋輸入
|
||||
@ -45,6 +52,7 @@
|
||||
- ⚡ 即時命令執行
|
||||
- 🎨 現代化深色主題
|
||||
- 📱 響應式設計(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" 錯誤
|
||||
- 🎛️ **可控調試模式**:透過 `MCP_DEBUG` 環境變數控制調試輸出
|
||||
@ -414,19 +439,18 @@ AI 助手會如此調用 `mcp-feedback-enhanced` 工具:
|
||||
- 🚀 **輸出隔離**:嚴格分離調試輸出與 MCP 通信
|
||||
- 📦 **套件優化**:改善 uvx 安裝體驗和依賴管理
|
||||
|
||||
### v2.0 - Web UI 支援
|
||||
- ✅ 新增 Web UI 介面支援 SSH remote 開發
|
||||
- ✅ 自動環境檢測和介面選擇
|
||||
- ✅ WebSocket 即時通訊
|
||||
- ✅ 現代化深色主題
|
||||
- ✅ 響應式設計支援
|
||||
- ✅ 持久化測試模式
|
||||
### v2.0.0 - Web UI 與遠端支援
|
||||
- ✅ **Web UI 介面**:新增 Web UI 支援 SSH remote 開發環境
|
||||
- ✅ **自動環境檢測**:根據環境智能選擇介面
|
||||
- ✅ **WebSocket 即時通訊**:即時命令執行和回饋
|
||||
- ✅ **現代化深色主題**:美觀的深色主題與響應式設計
|
||||
- ✅ **持久化測試模式**:增強的測試功能
|
||||
|
||||
### v1.0 - 基礎版本(原作者)
|
||||
- ✅ Qt GUI 介面
|
||||
- ✅ 命令執行功能
|
||||
- ✅ MCP 協議支援
|
||||
- ✅ 多平台支援
|
||||
### v1.0.0 - 基礎版本(原作者)
|
||||
- ✅ **Qt GUI 介面**:原生桌面介面
|
||||
- ✅ **命令執行**:即時命令執行和輸出
|
||||
- ✅ **MCP 協議支援**:完整的模型上下文協議實作
|
||||
- ✅ **跨平台支援**:Windows、macOS 和 Linux 相容性
|
||||
|
||||
## 🐛 故障排除
|
||||
|
||||
|
BIN
docs/images/gui-en.png
Normal file
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
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
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
BIN
docs/images/web-zh-tw.png
Normal file
Binary file not shown.
After Width: | Height: | Size: 86 KiB |
@ -6,10 +6,12 @@
|
||||
|
||||
基於 PySide6 的圖形用戶介面,提供直觀的回饋收集功能。
|
||||
支援文字輸入、圖片上傳、命令執行等功能。
|
||||
新增多語系支援(繁體中文、英文、簡體中文)。
|
||||
|
||||
作者: Fábio Ferreira
|
||||
靈感來源: dotcursorrules.com
|
||||
增強功能: 圖片支援和現代化界面設計
|
||||
多語系支援: Minidoracat
|
||||
"""
|
||||
|
||||
import os
|
||||
@ -25,10 +27,13 @@ from PySide6.QtWidgets import (
|
||||
QApplication, QMainWindow, QWidget, QVBoxLayout, QHBoxLayout,
|
||||
QLabel, QLineEdit, QPushButton, QTextEdit, QGroupBox,
|
||||
QScrollArea, QFrame, QGridLayout, QFileDialog, QMessageBox,
|
||||
QTabWidget, QSizePolicy
|
||||
QTabWidget, QSizePolicy, QComboBox, QMenuBar, QMenu
|
||||
)
|
||||
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:
|
||||
@ -115,22 +120,25 @@ class ImagePreviewWidget(QLabel):
|
||||
self.delete_button.setStyleSheet("""
|
||||
QPushButton {
|
||||
background-color: #f44336;
|
||||
color: white;
|
||||
color: #ffffff;
|
||||
border: none;
|
||||
border-radius: 10px;
|
||||
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.setToolTip("刪除圖片")
|
||||
self.delete_button.setToolTip(t('images_clear'))
|
||||
|
||||
def _on_delete_clicked(self) -> None:
|
||||
"""處理刪除按鈕點擊事件"""
|
||||
reply = QMessageBox.question(
|
||||
self, '確認刪除',
|
||||
f'確定要移除圖片 "{os.path.basename(self.image_path)}" 嗎?',
|
||||
self, t('images_delete_title'),
|
||||
t('images_delete_confirm', filename=os.path.basename(self.image_path)),
|
||||
QMessageBox.Yes | QMessageBox.No,
|
||||
QMessageBox.No
|
||||
)
|
||||
@ -158,10 +166,10 @@ class ImageUploadWidget(QWidget):
|
||||
layout.setContentsMargins(12, 8, 12, 8)
|
||||
|
||||
# 標題
|
||||
title = QLabel("🖼️ 圖片附件(可選)")
|
||||
title.setFont(QFont("", 10, QFont.Bold))
|
||||
title.setStyleSheet("color: #007acc; margin: 1px 0;")
|
||||
layout.addWidget(title)
|
||||
self.title = QLabel(t('images_title'))
|
||||
self.title.setFont(QFont("", 10, QFont.Bold))
|
||||
self.title.setStyleSheet("color: #007acc; margin: 1px 0;")
|
||||
layout.addWidget(self.title)
|
||||
|
||||
# 操作按鈕
|
||||
self._create_buttons(layout)
|
||||
@ -170,7 +178,7 @@ class ImageUploadWidget(QWidget):
|
||||
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;")
|
||||
layout.addWidget(self.status_label)
|
||||
|
||||
@ -182,15 +190,15 @@ class ImageUploadWidget(QWidget):
|
||||
button_layout = QHBoxLayout()
|
||||
|
||||
# 選擇文件按鈕
|
||||
self.file_button = QPushButton("📁 選擇文件")
|
||||
self.file_button = QPushButton(t('btn_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.clear_button = QPushButton("❌ 清除")
|
||||
self.clear_button = QPushButton(t('btn_clear_all'))
|
||||
self.clear_button.clicked.connect(self.clear_all_images)
|
||||
|
||||
# 設置按鈕樣式
|
||||
@ -203,11 +211,39 @@ class ImageUploadWidget(QWidget):
|
||||
font-weight: bold;
|
||||
font-size: 11px;
|
||||
}
|
||||
QPushButton:hover {
|
||||
opacity: 0.8;
|
||||
}
|
||||
"""
|
||||
|
||||
self.file_button.setStyleSheet(button_style + "QPushButton { background-color: #0e639c; }")
|
||||
self.paste_button.setStyleSheet(button_style + "QPushButton { background-color: #4caf50; }")
|
||||
self.clear_button.setStyleSheet(button_style + "QPushButton { background-color: #f44336; }")
|
||||
self.file_button.setStyleSheet(button_style + """
|
||||
QPushButton {
|
||||
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.paste_button)
|
||||
@ -218,7 +254,7 @@ class ImageUploadWidget(QWidget):
|
||||
|
||||
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.setAlignment(Qt.AlignCenter)
|
||||
self.drop_zone.setStyleSheet("""
|
||||
@ -260,9 +296,9 @@ class ImageUploadWidget(QWidget):
|
||||
"""選擇文件對話框"""
|
||||
files, _ = QFileDialog.getOpenFileNames(
|
||||
self,
|
||||
"選擇圖片文件",
|
||||
t('images_select'),
|
||||
"",
|
||||
"圖片文件 (*.png *.jpg *.jpeg *.gif *.bmp *.webp);;所有文件 (*)"
|
||||
"Image files (*.png *.jpg *.jpeg *.gif *.bmp *.webp);;All files (*)"
|
||||
)
|
||||
if files:
|
||||
self._add_images(files)
|
||||
@ -469,21 +505,19 @@ class ImageUploadWidget(QWidget):
|
||||
"""更新狀態標籤"""
|
||||
count = len(self.images)
|
||||
if count == 0:
|
||||
self.status_label.setText("已選擇 0 張圖片")
|
||||
self.status_label.setText(t('images_status', count=0))
|
||||
else:
|
||||
total_size = sum(img["size"] for img in self.images.values())
|
||||
|
||||
# 智能單位顯示
|
||||
if total_size < 1024:
|
||||
size_str = f"{total_size} B"
|
||||
elif total_size < 1024 * 1024:
|
||||
size_kb = total_size / 1024
|
||||
size_str = f"{size_kb:.1f} KB"
|
||||
else:
|
||||
# 格式化文件大小
|
||||
if total_size > 1024 * 1024: # MB
|
||||
size_mb = total_size / (1024 * 1024)
|
||||
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"=== 圖片狀態更新 ===")
|
||||
@ -580,78 +614,207 @@ class ImageUploadWidget(QWidget):
|
||||
except Exception as 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):
|
||||
"""主要的回饋收集視窗"""
|
||||
"""回饋收集主窗口"""
|
||||
language_changed = Signal()
|
||||
|
||||
def __init__(self, project_dir: str, summary: str):
|
||||
super().__init__()
|
||||
self.project_dir = project_dir
|
||||
self.summary = summary
|
||||
self.result: Optional[FeedbackResult] = None
|
||||
self.process: Optional[subprocess.Popen] = None
|
||||
self.accepted = False
|
||||
self.result = None
|
||||
self.command_process = None
|
||||
self.i18n = get_i18n_manager()
|
||||
|
||||
self._setup_ui()
|
||||
self._apply_dark_style()
|
||||
|
||||
# 連接語言變更信號
|
||||
self.language_changed.connect(self._refresh_ui_texts)
|
||||
|
||||
def _setup_ui(self) -> None:
|
||||
"""設置用戶介面"""
|
||||
self.setWindowTitle("互動式回饋收集")
|
||||
self.setMinimumSize(800, 600)
|
||||
self.setWindowTitle(t('app_title'))
|
||||
self.setMinimumSize(900, 700)
|
||||
self.resize(1000, 800)
|
||||
|
||||
# 主要元件
|
||||
# 創建菜單欄
|
||||
self._create_menu_bar()
|
||||
|
||||
# 中央元件
|
||||
central_widget = QWidget()
|
||||
self.setCentralWidget(central_widget)
|
||||
layout = QVBoxLayout(central_widget)
|
||||
layout.setSpacing(8)
|
||||
layout.setContentsMargins(16, 8, 16, 12)
|
||||
|
||||
# 主要佈局
|
||||
main_layout = QVBoxLayout(central_widget)
|
||||
main_layout.setSpacing(10)
|
||||
main_layout.setContentsMargins(10, 10, 10, 10)
|
||||
# AI 工作摘要區域
|
||||
self._create_summary_section(layout)
|
||||
|
||||
# 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)
|
||||
def _create_menu_bar(self) -> None:
|
||||
"""創建菜單欄"""
|
||||
menubar = self.menuBar()
|
||||
|
||||
# 設置比例拉伸:摘要區域佔1份,分頁區域佔3份,按鈕不拉伸
|
||||
summary_widget = main_layout.itemAt(0).widget() # 摘要區域
|
||||
main_layout.setStretchFactor(summary_widget, 1) # 適度拉伸
|
||||
main_layout.setStretchFactor(self.tabs, 3) # 主要拉伸區域
|
||||
# 語言菜單
|
||||
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:
|
||||
"""創建 AI 工作摘要區域"""
|
||||
summary_group = QGroupBox("📋 AI 工作摘要")
|
||||
summary_group = QGroupBox()
|
||||
summary_group.setTitle("")
|
||||
summary_group.setMaximumHeight(200)
|
||||
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)
|
||||
# 設置合理的高度範圍,允許適度拉伸
|
||||
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;")
|
||||
# 標題與項目信息
|
||||
header_layout = QHBoxLayout()
|
||||
|
||||
# 設置大小策略:允許適度垂直擴展
|
||||
self.summary_text.setSizePolicy(QSizePolicy.Expanding, QSizePolicy.Expanding)
|
||||
# AI 工作摘要標題
|
||||
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)
|
||||
|
||||
# 設置群組框的大小策略:允許適度擴展
|
||||
summary_group.setSizePolicy(QSizePolicy.Expanding, QSizePolicy.Expanding)
|
||||
header_layout.addStretch()
|
||||
|
||||
# 專案目錄信息
|
||||
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)
|
||||
|
||||
def _create_tabs(self, layout: QVBoxLayout) -> None:
|
||||
"""創建分頁標籤"""
|
||||
self.tabs = QTabWidget()
|
||||
|
||||
# 設置分頁標籤的大小策略,確保能夠獲得主要空間
|
||||
self.tabs.setSizePolicy(QSizePolicy.Expanding, QSizePolicy.Expanding)
|
||||
self.tab_widget = QTabWidget()
|
||||
|
||||
# 回饋分頁
|
||||
self._create_feedback_tab()
|
||||
@ -659,117 +822,123 @@ class FeedbackWindow(QMainWindow):
|
||||
# 命令分頁
|
||||
self._create_command_tab()
|
||||
|
||||
layout.addWidget(self.tabs)
|
||||
layout.addWidget(self.tab_widget)
|
||||
|
||||
def _create_feedback_tab(self) -> None:
|
||||
"""創建回饋分頁"""
|
||||
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_layout = QVBoxLayout(feedback_group)
|
||||
# 回饋輸入區域
|
||||
feedback_group = QGroupBox()
|
||||
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_text.setMinimumHeight(150)
|
||||
# 確保文字輸入區域能夠擴展
|
||||
self.feedback_text.setSizePolicy(QSizePolicy.Expanding, QSizePolicy.Expanding)
|
||||
# 回饋標題和說明
|
||||
self.feedback_title = QLabel(t('feedback_title'))
|
||||
self.feedback_title.setFont(QFont("", 11, QFont.Bold))
|
||||
self.feedback_title.setStyleSheet("color: #007acc; margin-bottom: 5px;")
|
||||
feedback_layout.addWidget(self.feedback_title)
|
||||
|
||||
# 添加快捷鍵支援
|
||||
submit_shortcut = QShortcut(QKeySequence("Ctrl+Return"), self.feedback_text)
|
||||
submit_shortcut.activated.connect(self._submit_feedback)
|
||||
# 說明文字
|
||||
self.feedback_description = QLabel(t('feedback_description'))
|
||||
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.setSizePolicy(QSizePolicy.Expanding, QSizePolicy.Expanding)
|
||||
self.image_upload.setMinimumHeight(200) # 設置最小高度
|
||||
self.image_upload.setMaximumHeight(400) # 增加最大高度限制
|
||||
feedback_layout.addWidget(self.image_upload)
|
||||
layout.addWidget(self.image_upload, stretch=3) # 給圖片區域更多空間
|
||||
|
||||
# 設置比例拉伸:文字區域佔2份,圖片區域佔1份
|
||||
feedback_layout.setStretchFactor(feedback_group, 2)
|
||||
feedback_layout.setStretchFactor(self.image_upload, 1)
|
||||
|
||||
self.tabs.addTab(feedback_widget, "💬 回饋")
|
||||
self.tab_widget.addTab(feedback_widget, t('feedback_tab'))
|
||||
|
||||
def _create_command_tab(self) -> None:
|
||||
"""創建命令分頁"""
|
||||
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_layout = QVBoxLayout(command_group)
|
||||
command_group = QGroupBox()
|
||||
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.setPlaceholderText("輸入要執行的命令...")
|
||||
self.command_input.setPlaceholderText(t('command_placeholder'))
|
||||
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.setFixedWidth(100)
|
||||
input_layout.addWidget(self.run_button)
|
||||
|
||||
cmd_input_layout.addWidget(self.command_input)
|
||||
cmd_input_layout.addWidget(self.run_button)
|
||||
command_group_layout.addLayout(cmd_input_layout)
|
||||
command_layout.addLayout(input_layout)
|
||||
layout.addWidget(command_group)
|
||||
|
||||
# 命令輸出區域
|
||||
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.setReadOnly(True)
|
||||
self.command_output.setMinimumHeight(200)
|
||||
self.command_output.setStyleSheet("background-color: #1e1e1e; color: #ffffff; font-family: 'Consolas', 'Monaco', 'Courier New', monospace;")
|
||||
# 確保命令輸出區域能夠擴展
|
||||
self.command_output.setSizePolicy(QSizePolicy.Expanding, QSizePolicy.Expanding)
|
||||
command_group_layout.addWidget(self.command_output)
|
||||
self.command_output.setFont(QFont("Consolas", 9))
|
||||
output_layout.addWidget(self.command_output)
|
||||
|
||||
# 設置群組框的大小策略
|
||||
command_group.setSizePolicy(QSizePolicy.Expanding, QSizePolicy.Expanding)
|
||||
layout.addWidget(output_group, stretch=1)
|
||||
|
||||
command_layout.addWidget(command_group)
|
||||
|
||||
self.tabs.addTab(command_widget, "⚡ 命令")
|
||||
self.tab_widget.addTab(command_widget, t('command_tab'))
|
||||
|
||||
def _create_action_buttons(self, layout: QVBoxLayout) -> None:
|
||||
"""創建操作按鈕"""
|
||||
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()
|
||||
|
||||
# 取消按鈕
|
||||
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)
|
||||
|
||||
# 提交按鈕
|
||||
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)
|
||||
|
||||
layout.addLayout(button_layout)
|
||||
@ -833,7 +1002,7 @@ class FeedbackWindow(QMainWindow):
|
||||
|
||||
try:
|
||||
# 在專案目錄中執行命令
|
||||
self.process = subprocess.Popen(
|
||||
self.command_process = subprocess.Popen(
|
||||
command,
|
||||
shell=True,
|
||||
cwd=self.project_dir,
|
||||
@ -854,9 +1023,9 @@ class FeedbackWindow(QMainWindow):
|
||||
|
||||
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:
|
||||
output = self.process.stdout.readline()
|
||||
output = self.command_process.stdout.readline()
|
||||
if output:
|
||||
self.command_output.insertPlainText(output)
|
||||
# 自動滾動到底部
|
||||
@ -869,32 +1038,30 @@ class FeedbackWindow(QMainWindow):
|
||||
# 進程結束
|
||||
if hasattr(self, 'timer'):
|
||||
self.timer.stop()
|
||||
if self.process:
|
||||
return_code = self.process.returncode
|
||||
if self.command_process:
|
||||
return_code = self.command_process.returncode
|
||||
self.command_output.append(f"\n進程結束,返回碼: {return_code}\n")
|
||||
|
||||
def _submit_feedback(self) -> None:
|
||||
"""提交回饋"""
|
||||
self.result = {
|
||||
"interactive_feedback": self.feedback_text.toPlainText(),
|
||||
"interactive_feedback": self.feedback_input.toPlainText(),
|
||||
"command_logs": self.command_output.toPlainText(),
|
||||
"images": self.image_upload.get_images_data()
|
||||
}
|
||||
self.accepted = True
|
||||
self.close()
|
||||
|
||||
def _cancel_feedback(self) -> None:
|
||||
"""取消回饋"""
|
||||
self.accepted = False
|
||||
self.close()
|
||||
|
||||
def closeEvent(self, event) -> None:
|
||||
"""處理視窗關閉事件"""
|
||||
if hasattr(self, 'timer'):
|
||||
self.timer.stop()
|
||||
if self.process:
|
||||
if self.command_process:
|
||||
try:
|
||||
self.process.terminate()
|
||||
self.command_process.terminate()
|
||||
except:
|
||||
pass
|
||||
|
||||
@ -945,7 +1112,7 @@ def feedback_ui(project_directory: str, summary: str) -> Optional[FeedbackResult
|
||||
app.exec()
|
||||
|
||||
# 返回結果
|
||||
if window.accepted:
|
||||
if window.result:
|
||||
return window.result
|
||||
else:
|
||||
return None
|
||||
|
346
src/mcp_feedback_enhanced/i18n.py
Normal file
346
src/mcp_feedback_enhanced/i18n.py
Normal 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()
|
172
src/mcp_feedback_enhanced/locales/README.md
Normal file
172
src/mcp_feedback_enhanced/locales/README.md
Normal 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
|
||||
|
||||
需要幫助可以參考現有的翻譯檔案作為範本。
|
81
src/mcp_feedback_enhanced/locales/en/translations.json
Normal file
81
src/mcp_feedback_enhanced/locales/en/translations.json
Normal 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": "简体中文"
|
||||
}
|
||||
}
|
81
src/mcp_feedback_enhanced/locales/zh-CN/translations.json
Normal file
81
src/mcp_feedback_enhanced/locales/zh-CN/translations.json
Normal 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": "简体中文"
|
||||
}
|
||||
}
|
81
src/mcp_feedback_enhanced/locales/zh-TW/translations.json
Normal file
81
src/mcp_feedback_enhanced/locales/zh-TW/translations.json
Normal 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": "简体中文"
|
||||
}
|
||||
}
|
472
src/mcp_feedback_enhanced/static/i18n.js
Normal file
472
src/mcp_feedback_enhanced/static/i18n.js
Normal 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
@ -7,7 +7,7 @@
|
||||
基於 FastAPI 的 Web 用戶介面,專為 SSH 遠端開發環境設計。
|
||||
支援文字輸入、圖片上傳、命令執行等功能。
|
||||
|
||||
作者: Fábio Ferreira
|
||||
作者: Minidoracat
|
||||
靈感來源: dotcursorrules.com
|
||||
增強功能: 圖片支援和現代化界面設計
|
||||
"""
|
||||
|
Loading…
x
Reference in New Issue
Block a user