diff --git a/README.md b/README.md
index 963ed6f..245cafc 100644
--- a/README.md
+++ b/README.md
@@ -109,6 +109,83 @@ Here's an example of how the AI assistant would call the `interactive_feedback`
```
+## Language Support
+
+The Interactive Feedback MCP supports multiple languages through a flexible JSON-based configuration system. Languages are automatically detected from the `languages/` directory.
+
+### Available Languages
+
+| Language Code | Language Name | File |
+|---------------|---------------|------|
+| `en` | English | `languages/en.json` |
+| `zh` | 中文 (Chinese) | `languages/zh.json` |
+| `fr` | Français (French) | `languages/fr.json` |
+
+### Language Configuration Structure
+
+Each language file follows this JSON structure:
+
+```json
+{
+ "name": "Language Display Name",
+ "window_title": "Window title text",
+ "show_command_section": "Show Command Section",
+ "hide_command_section": "Hide Command Section",
+ "command": "Command",
+ "working_directory": "Working directory",
+ "run": "&Run",
+ "stop": "Sto&p",
+ "execute_automatically": "Execute automatically on next run",
+ "save_configuration": "&Save Configuration",
+ "console": "Console",
+ "clear": "&Clear",
+ "feedback": "Feedback",
+ "feedback_placeholder": "Enter your feedback here (Ctrl+Enter to submit)",
+ "send_feedback": "&Send Feedback (Ctrl+Enter)",
+ "contact_info": "Contact information HTML",
+ "configuration_saved": "Configuration saved for this project.\n",
+ "error_running_command": "Error running command: {error}\n",
+ "command_prompt": "$ {command}\n",
+ "language": "Language",
+ "language_changed": "Language changed message.\n",
+ "please_enter_command": "Please enter a command to run\n",
+ "argparse_description": "Run the feedback UI",
+ "argparse_project_directory_help": "The project directory to run the command in",
+ "argparse_prompt_help": "The prompt to show to the user",
+ "argparse_output_file_help": "Path to save the feedback result as JSON",
+ "default_prompt": "I implemented the changes you requested.",
+ "logs_collected": "Logs collected",
+ "feedback_received": "Feedback received",
+ "argparse_language_help": "Interface language, automatically detects available languages"
+}
+```
+
+### Adding New Languages
+
+To add support for a new language:
+
+1. Create a new JSON file in the `languages/` directory (e.g., `languages/es.json` for Spanish)
+2. Include the `name` field with the language's display name
+3. Translate all the text keys according to the structure above
+4. The language will be automatically detected and available in the language selector
+
+### Usage
+
+You can specify the language when running the application:
+
+```bash
+# Use English (default)
+python feedback_ui.py --language en
+
+# Use Chinese
+python feedback_ui.py --language zh
+
+# Use French
+python feedback_ui.py --language fr
+```
+
+The language preference is automatically saved and restored between sessions.
+
## Acknowledgements & Contact
If you find this Interactive Feedback MCP useful, the best way to show appreciation is by following Fábio Ferreira on [X @fabiomlferreira](https://x.com/fabiomlferreira).
diff --git a/feedback_ui.py b/feedback_ui.py
index c71e951..f51a3c6 100644
--- a/feedback_ui.py
+++ b/feedback_ui.py
@@ -13,11 +13,13 @@
from PySide6.QtWidgets import (
QApplication, QMainWindow, QWidget, QVBoxLayout, QHBoxLayout,
- QLabel, QLineEdit, QPushButton, QCheckBox, QTextEdit, QGroupBox
+ QLabel, QLineEdit, QPushButton, QCheckBox, QTextEdit, QGroupBox, QComboBox
)
from PySide6.QtCore import Qt, Signal, QObject, QTimer, QSettings
from PySide6.QtGui import QTextCursor, QIcon, QKeyEvent, QFont, QFontDatabase, QPalette, QColor
+from language_manager import get_language_manager, get_text
+
class FeedbackResult(TypedDict):
command_logs: str
interactive_feedback: str
@@ -221,14 +223,18 @@ def __init__(self, project_directory: str, prompt: str):
self.log_signals = LogSignals()
self.log_signals.append_log.connect(self._append_log)
- self.setWindowTitle("Interactive Feedback MCP")
+ self.language_manager = get_language_manager()
+
+ self.settings = QSettings("InteractiveFeedbackMCP", "InteractiveFeedbackMCP")
+ saved_language = self.settings.value("language", "en", type=str)
+ self.language_manager.set_language(saved_language)
+
+ self.setWindowTitle(get_text("window_title"))
script_dir = os.path.dirname(os.path.abspath(__file__))
icon_path = os.path.join(script_dir, "images", "feedback.png")
self.setWindowIcon(QIcon(icon_path))
self.setWindowFlags(self.windowFlags() | Qt.WindowStaysOnTopHint)
-
- self.settings = QSettings("InteractiveFeedbackMCP", "InteractiveFeedbackMCP")
-
+
# Load general UI settings for the main window (geometry, state)
self.settings.beginGroup("MainWindow_General")
geometry = self.settings.value("geometry")
@@ -244,7 +250,7 @@ def __init__(self, project_directory: str, prompt: str):
if state:
self.restoreState(state)
self.settings.endGroup() # End "MainWindow_General" group
-
+
# Load project-specific settings (command, auto-execute, command section visibility)
self.project_group_name = get_project_settings_group(self.project_directory)
self.settings.beginGroup(self.project_group_name)
@@ -252,7 +258,7 @@ def __init__(self, project_directory: str, prompt: str):
loaded_execute_auto = self.settings.value("execute_automatically", False, type=bool)
command_section_visible = self.settings.value("commandSectionVisible", False, type=bool)
self.settings.endGroup() # End project-specific group
-
+
self.config: FeedbackConfig = {
"run_command": loaded_run_command,
"execute_automatically": loaded_execute_auto
@@ -263,9 +269,9 @@ def __init__(self, project_directory: str, prompt: str):
# Set command section visibility AFTER _create_ui has created relevant widgets
self.command_group.setVisible(command_section_visible)
if command_section_visible:
- self.toggle_command_button.setText("Hide Command Section")
+ self.toggle_command_button.setText(get_text("hide_command_section"))
else:
- self.toggle_command_button.setText("Show Command Section")
+ self.toggle_command_button.setText(get_text("show_command_section"))
set_dark_title_bar(self, True)
@@ -286,19 +292,45 @@ def _create_ui(self):
self.setCentralWidget(central_widget)
layout = QVBoxLayout(central_widget)
+ # Language selection
+ language_layout = QHBoxLayout()
+ self.language_label = QLabel(get_text("language") + ":")
+ self.language_label.setObjectName("language_label")
+ self.language_combo = QComboBox()
+ self.language_combo.setObjectName("language_combo")
+ available_languages = self.language_manager.get_available_languages()
+ for code, name in available_languages.items():
+ self.language_combo.addItem(name, code)
+
+ # Set current language in combo box
+ current_lang = self.language_manager.get_current_language()
+ for i in range(self.language_combo.count()):
+ if self.language_combo.itemData(i) == current_lang:
+ self.language_combo.setCurrentIndex(i)
+ break
+
+ self.language_combo.currentTextChanged.connect(self._on_language_changed)
+ language_layout.addWidget(self.language_label)
+ language_layout.addWidget(self.language_combo)
+ language_layout.addStretch()
+ layout.addLayout(language_layout)
+
# Toggle Command Section Button
- self.toggle_command_button = QPushButton("Show Command Section")
+ self.toggle_command_button = QPushButton(get_text("show_command_section"))
+ self.toggle_command_button.setObjectName("toggle_command_button")
self.toggle_command_button.clicked.connect(self._toggle_command_section)
layout.addWidget(self.toggle_command_button)
# Command section
- self.command_group = QGroupBox("Command")
+ self.command_group = QGroupBox(get_text("command"))
+ self.command_group.setObjectName("command_group")
command_layout = QVBoxLayout(self.command_group)
# Working directory label
formatted_path = self._format_windows_path(self.project_directory)
- working_dir_label = QLabel(f"Working directory: {formatted_path}")
- command_layout.addWidget(working_dir_label)
+ self.working_dir_label = QLabel(f"{get_text('working_directory')}: {formatted_path}")
+ self.working_dir_label.setObjectName("working_dir_label")
+ command_layout.addWidget(self.working_dir_label)
# Command input row
command_input_layout = QHBoxLayout()
@@ -306,7 +338,8 @@ def _create_ui(self):
self.command_entry.setText(self.config["run_command"])
self.command_entry.returnPressed.connect(self._run_command)
self.command_entry.textChanged.connect(self._update_config)
- self.run_button = QPushButton("&Run")
+ self.run_button = QPushButton(get_text("run"))
+ self.run_button.setObjectName("run_button")
self.run_button.clicked.connect(self._run_command)
command_input_layout.addWidget(self.command_entry)
@@ -315,22 +348,25 @@ def _create_ui(self):
# Auto-execute and save config row
auto_layout = QHBoxLayout()
- self.auto_check = QCheckBox("Execute automatically on next run")
+ self.auto_check = QCheckBox(get_text("execute_automatically"))
+ self.auto_check.setObjectName("auto_check")
self.auto_check.setChecked(self.config.get("execute_automatically", False))
self.auto_check.stateChanged.connect(self._update_config)
- save_button = QPushButton("&Save Configuration")
- save_button.clicked.connect(self._save_config)
+ self.save_button = QPushButton(get_text("save_configuration"))
+ self.save_button.setObjectName("save_button")
+ self.save_button.clicked.connect(self._save_config)
auto_layout.addWidget(self.auto_check)
auto_layout.addStretch()
- auto_layout.addWidget(save_button)
+ auto_layout.addWidget(self.save_button)
command_layout.addLayout(auto_layout)
# Console section (now part of command_group)
- console_group = QGroupBox("Console")
- console_layout_internal = QVBoxLayout(console_group)
- console_group.setMinimumHeight(200)
+ self.console_group = QGroupBox(get_text("console"))
+ self.console_group.setObjectName("console_group")
+ console_layout_internal = QVBoxLayout(self.console_group)
+ self.console_group.setMinimumHeight(200)
# Log text area
self.log_text = QTextEdit()
@@ -342,66 +378,77 @@ def _create_ui(self):
# Clear button
button_layout = QHBoxLayout()
- self.clear_button = QPushButton("&Clear")
+ self.clear_button = QPushButton(get_text("clear"))
+ self.clear_button.setObjectName("clear_button")
self.clear_button.clicked.connect(self.clear_logs)
button_layout.addStretch()
button_layout.addWidget(self.clear_button)
console_layout_internal.addLayout(button_layout)
-
- command_layout.addWidget(console_group)
- self.command_group.setVisible(False)
+ command_layout.addWidget(self.console_group)
+
+ self.command_group.setVisible(False)
layout.addWidget(self.command_group)
# Feedback section with adjusted height
- self.feedback_group = QGroupBox("Feedback")
+ self.feedback_group = QGroupBox(get_text("feedback"))
+ self.feedback_group.setObjectName("feedback_group")
feedback_layout = QVBoxLayout(self.feedback_group)
# Short description label (from self.prompt)
- self.description_label = QLabel(self.prompt)
+ display_prompt = self.prompt
+ all_default_prompts = self.language_manager.get_all_default_prompts()
+ if self.prompt in all_default_prompts:
+ display_prompt = get_text("default_prompt")
+
+ self.description_label = QLabel(display_prompt)
+ self.description_label.setObjectName("description_label")
self.description_label.setWordWrap(True)
feedback_layout.addWidget(self.description_label)
self.feedback_text = FeedbackTextEdit()
+ self.feedback_text.setObjectName("feedback_text")
font_metrics = self.feedback_text.fontMetrics()
row_height = font_metrics.height()
# Calculate height for 5 lines + some padding for margins
padding = self.feedback_text.contentsMargins().top() + self.feedback_text.contentsMargins().bottom() + 5 # 5 is extra vertical padding
self.feedback_text.setMinimumHeight(5 * row_height + padding)
- self.feedback_text.setPlaceholderText("Enter your feedback here (Ctrl+Enter to submit)")
- submit_button = QPushButton("&Send Feedback (Ctrl+Enter)")
- submit_button.clicked.connect(self._submit_feedback)
+ self.feedback_text.setPlaceholderText(get_text("feedback_placeholder"))
+ self.submit_button = QPushButton(get_text("send_feedback"))
+ self.submit_button.setObjectName("submit_button")
+ self.submit_button.clicked.connect(self._submit_feedback)
feedback_layout.addWidget(self.feedback_text)
- feedback_layout.addWidget(submit_button)
+ feedback_layout.addWidget(self.submit_button)
# Set minimum height for feedback_group to accommodate its contents
# This will be based on the description label and the 5-line feedback_text
- self.feedback_group.setMinimumHeight(self.description_label.sizeHint().height() + self.feedback_text.minimumHeight() + submit_button.sizeHint().height() + feedback_layout.spacing() * 2 + feedback_layout.contentsMargins().top() + feedback_layout.contentsMargins().bottom() + 10) # 10 for extra padding
+ self.feedback_group.setMinimumHeight(self.description_label.sizeHint().height() + self.feedback_text.minimumHeight() + self.submit_button.sizeHint().height() + feedback_layout.spacing() * 2 + feedback_layout.contentsMargins().top() + feedback_layout.contentsMargins().bottom() + 10) # 10 for extra padding
# Add widgets in a specific order
layout.addWidget(self.feedback_group)
# Credits/Contact Label
- contact_label = QLabel('Need to improve? Contact Fábio Ferreira on X.com or visit dotcursorrules.com')
- contact_label.setOpenExternalLinks(True)
- contact_label.setAlignment(Qt.AlignCenter)
+ self.contact_label = QLabel(get_text("contact_info"))
+ self.contact_label.setObjectName("contact_label")
+ self.contact_label.setOpenExternalLinks(True)
+ self.contact_label.setAlignment(Qt.AlignCenter)
# Optionally, make font a bit smaller and less prominent
# contact_label_font = contact_label.font()
# contact_label_font.setPointSize(contact_label_font.pointSize() - 1)
# contact_label.setFont(contact_label_font)
- contact_label.setStyleSheet("font-size: 9pt; color: #cccccc;") # Light gray for dark theme
- layout.addWidget(contact_label)
+ self.contact_label.setStyleSheet("font-size: 9pt; color: #cccccc;") # Light gray for dark theme
+ layout.addWidget(self.contact_label)
def _toggle_command_section(self):
is_visible = self.command_group.isVisible()
self.command_group.setVisible(not is_visible)
if not is_visible:
- self.toggle_command_button.setText("Hide Command Section")
+ self.toggle_command_button.setText(get_text("hide_command_section"))
else:
- self.toggle_command_button.setText("Show Command Section")
-
+ self.toggle_command_button.setText(get_text("show_command_section"))
+
# Immediately save the visibility state for this project
self.settings.beginGroup(self.project_group_name)
self.settings.setValue("commandSectionVisible", self.command_group.isVisible())
@@ -411,7 +458,7 @@ def _toggle_command_section(self):
new_height = self.centralWidget().sizeHint().height()
if self.command_group.isVisible() and self.command_group.layout().sizeHint().height() > 0 :
# if command group became visible and has content, ensure enough height
- min_content_height = self.command_group.layout().sizeHint().height() + self.feedback_group.minimumHeight() + self.toggle_command_button.height() + layout().spacing() * 2
+ min_content_height = self.command_group.layout().sizeHint().height() + self.feedback_group.minimumHeight() + self.toggle_command_button.height() + self.centralWidget().layout().spacing() * 2
new_height = max(new_height, min_content_height)
current_width = self.width()
@@ -432,8 +479,8 @@ def _check_process_status(self):
if self.process and self.process.poll() is not None:
# Process has terminated
exit_code = self.process.poll()
- self._append_log(f"\nProcess exited with code {exit_code}\n")
- self.run_button.setText("&Run")
+ self._append_log(f"\n{get_text('process_exited', exit_code=exit_code)}\n")
+ self.run_button.setText(get_text("run"))
self.process = None
self.activateWindow()
self.feedback_text.setFocus()
@@ -442,7 +489,7 @@ def _run_command(self):
if self.process:
kill_tree(self.process)
self.process = None
- self.run_button.setText("&Run")
+ self.run_button.setText(get_text("run"))
return
# Clear the log buffer but keep UI logs visible
@@ -450,11 +497,11 @@ def _run_command(self):
command = self.command_entry.text()
if not command:
- self._append_log("Please enter a command to run\n")
+ self._append_log(get_text("please_enter_command"))
return
- self._append_log(f"$ {command}\n")
- self.run_button.setText("Sto&p")
+ self._append_log(get_text("command_prompt", command=command))
+ self.run_button.setText(get_text("stop"))
try:
self.process = subprocess.Popen(
@@ -493,8 +540,8 @@ def read_output(pipe):
self.status_timer.start(100) # Check every 100ms
except Exception as e:
- self._append_log(f"Error running command: {str(e)}\n")
- self.run_button.setText("&Run")
+ self._append_log(get_text("error_running_command", error=str(e)))
+ self.run_button.setText(get_text("run"))
def _submit_feedback(self):
self.feedback_result = FeedbackResult(
@@ -513,7 +560,89 @@ def _save_config(self):
self.settings.setValue("run_command", self.config["run_command"])
self.settings.setValue("execute_automatically", self.config["execute_automatically"])
self.settings.endGroup()
- self._append_log("Configuration saved for this project.\n")
+ self._append_log(get_text("configuration_saved"))
+
+ def _on_language_changed(self):
+ """Change language and update UI texts"""
+ selected_index = self.language_combo.currentIndex()
+ if selected_index >= 0:
+ language_code = self.language_combo.itemData(selected_index)
+ if language_code and self.language_manager.set_language(language_code):
+ # Save language preference to settings
+ self.settings.setValue("language", language_code)
+
+ # Update UI texts
+ self._update_ui_texts()
+
+ # Record language change
+ self._append_log(get_text("language_changed"))
+
+ def _update_ui_texts(self):
+ """Update UI texts to the current language"""
+ # Update window title
+ self.setWindowTitle(get_text("window_title"))
+
+ # Initialize language manager
+ widget_text_mapping = {
+ 'language_label': ('setText', lambda: get_text("language") + ":"),
+ 'command_group': ('setTitle', 'command'),
+ 'feedback_group': ('setTitle', 'feedback'),
+ 'console_group': ('setTitle', 'console'),
+ 'auto_check': ('setText', 'execute_automatically'),
+ 'clear_button': ('setText', 'clear'),
+ 'save_button': ('setText', 'save_configuration'),
+ 'submit_button': ('setText', 'send_feedback'),
+ 'contact_label': ('setText', 'contact_info'),
+ 'working_dir_label': ('setText', lambda: f"{get_text('working_directory')}: {self._format_windows_path(self.project_directory)}"),
+ 'feedback_text': ('setPlaceholderText', 'feedback_placeholder'),
+ }
+
+ # Update UI texts based on the mapping
+ for widget_name, (method_name, text_key) in widget_text_mapping.items():
+ if hasattr(self, widget_name):
+ widget = getattr(self, widget_name)
+ if widget and hasattr(widget, method_name):
+ if callable(text_key):
+ text = text_key()
+ else:
+ text = get_text(text_key)
+
+ getattr(widget, method_name)(text)
+
+ if hasattr(self, 'toggle_command_button'):
+ if self.command_group.isVisible():
+ self.toggle_command_button.setText(get_text("hide_command_section"))
+ else:
+ self.toggle_command_button.setText(get_text("show_command_section"))
+
+ if hasattr(self, 'run_button'):
+ if self.process:
+ self.run_button.setText(get_text("stop"))
+ else:
+ self.run_button.setText(get_text("run"))
+
+ if hasattr(self, 'language_combo'):
+ current_lang = self.language_manager.get_current_language()
+ available_languages = self.language_manager.get_available_languages()
+
+ self.language_combo.currentTextChanged.disconnect()
+ self.language_combo.clear()
+ for code, name in available_languages.items():
+ self.language_combo.addItem(name, code)
+
+ for i in range(self.language_combo.count()):
+ if self.language_combo.itemData(i) == current_lang:
+ self.language_combo.setCurrentIndex(i)
+ break
+
+ self.language_combo.currentTextChanged.connect(self._on_language_changed)
+
+ if hasattr(self, 'description_label') and self.description_label:
+ current_text = self.description_label.text()
+ all_default_prompts = self.language_manager.get_all_default_prompts()
+
+ if (self.prompt in all_default_prompts or current_text in all_default_prompts):
+ self.description_label.setText(get_text("default_prompt"))
def closeEvent(self, event):
# Save general UI settings for the main window (geometry, state)
@@ -568,14 +697,30 @@ def feedback_ui(project_directory: str, prompt: str, output_file: Optional[str]
return result
if __name__ == "__main__":
- parser = argparse.ArgumentParser(description="Run the feedback UI")
- parser.add_argument("--project-directory", default=os.getcwd(), help="The project directory to run the command in")
- parser.add_argument("--prompt", default="I implemented the changes you requested.", help="The prompt to show to the user")
- parser.add_argument("--output-file", help="Path to save the feedback result as JSON")
+ lang_manager = get_language_manager() # This is necessary to initialize the language manager
+
+ available_languages = list(lang_manager.get_available_languages().keys())
+ default_language = available_languages[0] if available_languages else "en"
+
+ # First check for --language argument
+ import sys
+ if "--language" in sys.argv:
+ lang_index = sys.argv.index("--language")
+ if lang_index + 1 < len(sys.argv):
+ requested_lang = sys.argv[lang_index + 1]
+ if requested_lang in available_languages:
+ lang_manager.set_language(requested_lang)
+
+ parser = argparse.ArgumentParser(description=get_text("argparse_description"))
+ parser.add_argument("--project-directory", default=os.getcwd(), help=get_text("argparse_project_directory_help"))
+ parser.add_argument("--prompt", default=get_text("default_prompt"), help=get_text("argparse_prompt_help"))
+ parser.add_argument("--output-file", help=get_text("argparse_output_file_help"))
+ parser.add_argument("--language", choices=available_languages, default=default_language, help=get_text("argparse_language_help"))
args = parser.parse_args()
+ lang_manager.set_language(args.language)
result = feedback_ui(args.project_directory, args.prompt, args.output_file)
if result:
- print(f"\nLogs collected: \n{result['logs']}")
- print(f"\nFeedback received:\n{result['interactive_feedback']}")
+ print(f"\n{get_text('logs_collected')}: \n{result['logs']}")
+ print(f"\n{get_text('feedback_received')}:\n{result['interactive_feedback']}")
sys.exit(0)
diff --git a/language_manager.py b/language_manager.py
new file mode 100644
index 0000000..c9c0c2c
--- /dev/null
+++ b/language_manager.py
@@ -0,0 +1,129 @@
+# Language Manager for Interactive Feedback MCP
+# Handles loading and managing language configurations
+
+import os
+import json
+import glob
+from typing import Dict, Optional
+
+
+class LanguageManager:
+ """Class for managing multiple language configurations"""
+
+ def __init__(self, default_language: str = "en"):
+ self.current_language = default_language
+ self.languages: Dict[str, Dict[str, str]] = {}
+ self.available_languages: list[str] = []
+ self._load_languages()
+
+ def _load_languages(self):
+ """Scan and load all available language configuration files"""
+ script_dir = os.path.dirname(os.path.abspath(__file__))
+ languages_dir = os.path.join(script_dir, "languages")
+
+ if not os.path.exists(languages_dir):
+ print(f"Warning: Languages directory {languages_dir} not found")
+ return
+
+ # Get language files
+ json_files = glob.glob(os.path.join(languages_dir, "*.json"))
+
+ for lang_file in json_files:
+ # Get the language code from the file name
+ lang_code = os.path.splitext(os.path.basename(lang_file))[0]
+
+ try:
+ with open(lang_file, 'r', encoding='utf-8') as f:
+ lang_data = json.load(f)
+
+ # Verify that the language file contains the necessary fields
+ if 'name' not in lang_data:
+ print(f"Warning: Language file {lang_file} missing 'name' field")
+ continue
+
+ self.languages[lang_code] = lang_data
+ self.available_languages.append(lang_code)
+
+ except FileNotFoundError:
+ print(f"Warning: Language file {lang_file} not found")
+ except json.JSONDecodeError as e:
+ print(f"Warning: Error parsing language file {lang_file}: {e}")
+
+ # Ensure there is a default language
+ if self.current_language not in self.available_languages and self.available_languages:
+ self.current_language = self.available_languages[0]
+ elif not self.available_languages:
+ print("Warning: No valid language files found")
+
+ def set_language(self, language_code: str) -> bool:
+ """Set the current language"""
+ if language_code in self.languages:
+ self.current_language = language_code
+ return True
+ return False
+
+ def get_text(self, key: str, **kwargs) -> str:
+ """Get the text for a given key, supporting formatting arguments"""
+ if self.current_language not in self.languages:
+ # If the current language is not available, fallback to English
+ self.current_language = "en"
+
+ text = self.languages.get(self.current_language, {}).get(key, key)
+
+ # If the key is not found in the current language, try to get it from English
+ if text == key and self.current_language != "en":
+ text = self.languages.get("en", {}).get(key, key)
+
+ # Apply formatting arguments
+ if kwargs:
+ try:
+ text = text.format(**kwargs)
+ except (KeyError, ValueError):
+ # If formatting fails, return the original text
+ pass
+
+ return text
+
+ def get_current_language(self) -> str:
+ """Get the current language code"""
+ return self.current_language
+
+ def get_available_languages(self) -> Dict[str, str]:
+ """Retrieve the list of available languages and return the mapping between language codes and display names"""
+ result = {}
+ for lang_code in self.available_languages:
+ if lang_code in self.languages and 'name' in self.languages[lang_code]:
+ result[lang_code] = self.languages[lang_code]['name']
+ else:
+ result[lang_code] = lang_code
+ return result
+
+ def get_all_default_prompts(self) -> list[str]:
+ """Get all default prompt variants for all languages"""
+ default_prompts = []
+ for lang_code in self.available_languages:
+ if lang_code in self.languages and 'default_prompt' in self.languages[lang_code]:
+ prompt = self.languages[lang_code]['default_prompt']
+ if prompt not in default_prompts:
+ default_prompts.append(prompt)
+ return default_prompts
+
+
+_language_manager = None
+
+def get_language_manager() -> LanguageManager:
+ """Get a global language manager instance"""
+ global _language_manager
+ if _language_manager is None:
+ _language_manager = LanguageManager()
+ return _language_manager
+
+
+def set_global_language(language_code: str) -> bool:
+ """Set global language"""
+ return get_language_manager().set_language(language_code)
+
+
+def get_text(key: str, **kwargs) -> str:
+ """Convenient function for obtaining text"""
+ return get_language_manager().get_text(key, **kwargs)
diff --git a/languages/en.json b/languages/en.json
new file mode 100644
index 0000000..88a34dd
--- /dev/null
+++ b/languages/en.json
@@ -0,0 +1,33 @@
+{
+ "name": "English",
+ "window_title": "Interactive Feedback MCP",
+ "show_command_section": "Show Command Section",
+ "hide_command_section": "Hide Command Section",
+ "command": "Command",
+ "working_directory": "Working directory",
+ "run": "&Run",
+ "stop": "Sto&p",
+ "execute_automatically": "Execute automatically on next run",
+ "save_configuration": "&Save Configuration",
+ "console": "Console",
+ "clear": "&Clear",
+ "feedback": "Feedback",
+ "feedback_placeholder": "Enter your feedback here (Ctrl+Enter to submit)",
+ "send_feedback": "&Send Feedback (Ctrl+Enter)",
+ "contact_info": "Need to improve? Contact Fábio Ferreira on X.com or visit dotcursorrules.com",
+ "configuration_saved": "Configuration saved for this project.\n",
+ "error_running_command": "Error running command: {error}\n",
+ "command_prompt": "$ {command}\n",
+ "language": "Language",
+ "language_changed": "Language changed to English.\n",
+ "please_enter_command": "Please enter a command to run\n",
+ "argparse_description": "Run the feedback UI",
+ "argparse_project_directory_help": "The project directory to run the command in",
+ "argparse_prompt_help": "The prompt to show to the user",
+ "argparse_output_file_help": "Path to save the feedback result as JSON",
+ "default_prompt": "I implemented the changes you requested.",
+ "logs_collected": "Logs collected",
+ "feedback_received": "Feedback received",
+ "argparse_language_help": "Interface language, automatically detects available languages",
+ "process_exited": "Process exited with code {exit_code}"
+}
diff --git a/languages/fr.json b/languages/fr.json
new file mode 100644
index 0000000..e1642e1
--- /dev/null
+++ b/languages/fr.json
@@ -0,0 +1,33 @@
+{
+ "name": "Français",
+ "window_title": "Interface de Retour Interactif MCP",
+ "show_command_section": "Afficher la Section Commande",
+ "hide_command_section": "Masquer la Section Commande",
+ "command": "Commande",
+ "working_directory": "Répertoire de travail",
+ "run": "&Exécuter",
+ "stop": "&Arrêter",
+ "execute_automatically": "Exécuter automatiquement au prochain lancement",
+ "save_configuration": "&Sauvegarder la Configuration",
+ "console": "Console",
+ "clear": "&Effacer",
+ "feedback": "Retour",
+ "feedback_placeholder": "Entrez votre retour ici (Ctrl+Entrée pour soumettre)",
+ "send_feedback": "&Envoyer le Retour (Ctrl+Entrée)",
+ "contact_info": "Besoin d'amélioration ? Contactez Fábio Ferreira sur X.com ou visitez dotcursorrules.com",
+ "configuration_saved": "Configuration sauvegardée pour ce projet.\n",
+ "error_running_command": "Erreur lors de l'exécution de la commande : {error}\n",
+ "command_prompt": "$ {command}\n",
+ "language": "Langue",
+ "language_changed": "Langue changée en français.\n",
+ "please_enter_command": "Veuillez entrer une commande à exécuter\n",
+ "argparse_description": "Lancer l'interface de retour",
+ "argparse_project_directory_help": "Le répertoire du projet dans lequel exécuter la commande",
+ "argparse_prompt_help": "Le message à afficher à l'utilisateur",
+ "argparse_output_file_help": "Chemin pour sauvegarder le résultat du retour en JSON",
+ "default_prompt": "J'ai implémenté les changements que vous avez demandés.",
+ "logs_collected": "Journaux collectés",
+ "feedback_received": "Retour reçu",
+ "argparse_language_help": "Langue de l'interface, détecte automatiquement les langues disponibles",
+ "process_exited": "Le processus s'est terminé avec le code {exit_code}"
+}
diff --git a/languages/zh.json b/languages/zh.json
new file mode 100644
index 0000000..8eb1c01
--- /dev/null
+++ b/languages/zh.json
@@ -0,0 +1,33 @@
+{
+ "name": "中文",
+ "window_title": "交互式反馈 MCP",
+ "show_command_section": "显示命令区域",
+ "hide_command_section": "隐藏命令区域",
+ "command": "命令",
+ "working_directory": "工作目录",
+ "run": "运行",
+ "stop": "停止",
+ "execute_automatically": "下次运行时自动执行",
+ "save_configuration": "保存配置",
+ "console": "控制台",
+ "clear": "清除",
+ "feedback": "反馈",
+ "feedback_placeholder": "在此输入您的反馈",
+ "send_feedback": "发送反馈",
+ "contact_info": "需要改进?联系 Fábio Ferreira 在 X.com 或访问 dotcursorrules.com",
+ "configuration_saved": "此项目的配置已保存。\n",
+ "error_running_command": "运行命令时出错: {error}\n",
+ "command_prompt": "$ {command}\n",
+ "language": "语言",
+ "language_changed": "语言已更改为中文。\n",
+ "please_enter_command": "请输入要运行的命令\n",
+ "argparse_description": "运行反馈界面",
+ "argparse_project_directory_help": "运行命令的项目目录",
+ "argparse_prompt_help": "显示给用户的提示信息",
+ "argparse_output_file_help": "保存反馈结果为JSON的路径",
+ "default_prompt": "我已实现您请求的更改。",
+ "logs_collected": "收集的日志",
+ "feedback_received": "收到的反馈",
+ "argparse_language_help": "界面语言,自动检测可用语言",
+ "process_exited": "进程已退出,退出代码 {exit_code}"
+}