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
54
README.md
54
README.md
@ -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)
|
||||||

|

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

|

|
||||||
|
|
||||||
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
|
||||||
|
@ -31,13 +31,20 @@
|
|||||||
- **錯誤處理**:強化錯誤處理,確保程序穩定運行
|
- **錯誤處理**:強化錯誤處理,確保程序穩定運行
|
||||||
- **輸出隔離**:嚴格隔離調試輸出與 MCP 通信
|
- **輸出隔離**:嚴格隔離調試輸出與 MCP 通信
|
||||||
|
|
||||||
|
### 🌏 多語言支援
|
||||||
|
- **完整國際化**:採用結構化 JSON 翻譯檔案的完整多語言支援
|
||||||
|
- **支援語言**:繁體中文、英文、簡體中文
|
||||||
|
- **智能偵測**:根據系統語言環境自動偵測
|
||||||
|
- **易於擴充**:基於 JSON 的簡單翻譯系統,方便新增語言
|
||||||
|
- **向後兼容**:與現有程式碼完全向後兼容
|
||||||
|
|
||||||
## 🖥️ 介面預覽
|
## 🖥️ 介面預覽
|
||||||
|
|
||||||
### Qt GUI 介面(本地環境)
|
### Qt GUI 介面(本地環境)
|
||||||

|

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

|

|
||||||
|
|
||||||
兩種介面都支援:
|
兩種介面都支援:
|
||||||
- 💬 文字回饋輸入
|
- 💬 文字回饋輸入
|
||||||
@ -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
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 的圖形用戶介面,提供直觀的回饋收集功能。
|
基於 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
|
||||||
|
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 遠端開發環境設計。
|
基於 FastAPI 的 Web 用戶介面,專為 SSH 遠端開發環境設計。
|
||||||
支援文字輸入、圖片上傳、命令執行等功能。
|
支援文字輸入、圖片上傳、命令執行等功能。
|
||||||
|
|
||||||
作者: Fábio Ferreira
|
作者: Minidoracat
|
||||||
靈感來源: dotcursorrules.com
|
靈感來源: dotcursorrules.com
|
||||||
增強功能: 圖片支援和現代化界面設計
|
增強功能: 圖片支援和現代化界面設計
|
||||||
"""
|
"""
|
||||||
|
Loading…
x
Reference in New Issue
Block a user