From 47e3af1b4d31adde6d4945ad4332ce1c7be8ce33 Mon Sep 17 00:00:00 2001 From: Hongyu Shi Date: Sun, 19 Oct 2025 17:47:38 +0800 Subject: [PATCH 01/19] =?UTF-8?q?fix(i18n):=20=E4=BC=98=E5=8C=96=20bash=20?= =?UTF-8?q?=E5=85=BC=E5=AE=B9=E6=80=A7?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Signed-off-by: Hongyu Shi --- scripts/tools/i18n-manager.sh | 41 ++++++++++++++++++++++++----------- 1 file changed, 28 insertions(+), 13 deletions(-) diff --git a/scripts/tools/i18n-manager.sh b/scripts/tools/i18n-manager.sh index 73f6be3..0aba948 100755 --- a/scripts/tools/i18n-manager.sh +++ b/scripts/tools/i18n-manager.sh @@ -2,6 +2,7 @@ # 国际化翻译管理脚本 set -e +set -o pipefail SCRIPT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)" PROJECT_ROOT="$(cd "$SCRIPT_DIR/../.." && pwd)" @@ -79,13 +80,13 @@ extract() { exit 1 fi - file_count=$(echo "$python_files" | wc -l | tr -d ' ') + file_count=$(echo "$python_files" | wc -l | sed 's/^[[:space:]]*//') echo " Found $file_count Python files" echo " Output file: $POT_FILE" # 使用 xgettext 提取字符串(使用相对路径) # shellcheck disable=SC2086 - xgettext \ + if xgettext \ --language=Python \ --keyword=_ \ --keyword=_n:1,2 \ @@ -96,9 +97,7 @@ extract() { --msgid-bugs-address=contact@openeuler.org \ --copyright-holder="openEuler Intelligence Project" \ --add-comments=Translators \ - $python_files - - if [ $? -eq 0 ]; then + $python_files; then print_green "✅ Successfully extracted strings to messages.pot" else print_red "❌ Failed to extract strings" @@ -137,7 +136,7 @@ update() { echo " Updating $locale_name..." if msgmerge --update --backup=none "$po_file" "$POT_FILE" 2>/dev/null; then echo " ✅ Updated $locale_name" - ((updated++)) + updated=$((updated + 1)) else print_yellow " ⚠️ Failed to update $locale_name" fi @@ -163,6 +162,7 @@ compile() { check_gettext compiled=0 + failed=0 # 遍历所有语言目录 for locale_path in "$LOCALE_DIR"/*; do @@ -180,20 +180,35 @@ compile() { fi echo " Compiling $locale_name..." - if msgfmt -o "$mo_file" "$po_file" 2>/dev/null; then + # 临时禁用 set -e 和 set -o pipefail 以捕获错误但继续执行 + set +e + set +o pipefail + error_output=$(msgfmt -o "$mo_file" "$po_file" 2>&1) + msgfmt_status=$? + set -e + set -o pipefail + + if [ "$msgfmt_status" -eq 0 ]; then echo " ✅ Compiled $locale_name" - ((compiled++)) + compiled=$((compiled + 1)) else print_yellow " ⚠️ Failed to compile $locale_name" + echo " Error: $error_output" + failed=$((failed + 1)) fi done - if [ $compiled -gt 0 ]; then - echo "" + echo "" + if [ "$compiled" -gt 0 ]; then print_green "✅ Successfully compiled $compiled translation file(s)" - else - echo "" - print_yellow "⚠️ No translation files were compiled" + fi + + if [ "$failed" -gt 0 ]; then + print_yellow "⚠️ Failed to compile $failed translation file(s)" + fi + + if [ "$compiled" -eq 0 ] && [ "$failed" -eq 0 ]; then + print_yellow "⚠️ No translation files found to compile" fi } -- Gitee From fc1492290ddf1ee63883626668dd13272952f2a5 Mon Sep 17 00:00:00 2001 From: Hongyu Shi Date: Tue, 21 Oct 2025 11:21:13 +0800 Subject: [PATCH 02/19] =?UTF-8?q?chore(i18n):=20=E6=9B=B4=E6=96=B0=20Tools?= =?UTF-8?q?=20=E5=9B=BD=E9=99=85=E5=8C=96=20(Part=202)?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Signed-off-by: Hongyu Shi --- .../locales/en_US/LC_MESSAGES/messages.po | 326 ++++++++++++++++- .../locales/zh_CN/LC_MESSAGES/messages.po | 327 +++++++++++++++++- 2 files changed, 646 insertions(+), 7 deletions(-) diff --git a/src/i18n/locales/en_US/LC_MESSAGES/messages.po b/src/i18n/locales/en_US/LC_MESSAGES/messages.po index 9502e02..8e5ecdf 100644 --- a/src/i18n/locales/en_US/LC_MESSAGES/messages.po +++ b/src/i18n/locales/en_US/LC_MESSAGES/messages.po @@ -6,7 +6,7 @@ msgid "" msgstr "" "Project-Id-Version: oi-cli\n" "Report-Msgid-Bugs-To: contact@openeuler.org\n" -"POT-Creation-Date: 2025-10-21 10:23+0800\n" +"POT-Creation-Date: 2025-10-21 10:54+0800\n" "PO-Revision-Date: 2025-10-20 19:28+0800\n" "Last-Translator: openEuler Intelligence Team\n" "Language-Team: English\n" @@ -154,7 +154,7 @@ msgstr "Toggle Focus" #: src/app/tui.py:255 #, python-brace-format -msgid "Intelligent CLI Tool {version}" +msgid "Intelligent CLI Assistant {version}" msgstr "Command Line Tool {version}" #: src/app/tui.py:371 @@ -266,7 +266,8 @@ msgstr "Press any key to close" #: src/app/dialogs/agent.py:60 src/app/dialogs/agent.py:130 #: src/app/dialogs/agent.py:185 src/app/dialogs/agent.py:205 -#: src/app/dialogs/agent.py:208 +#: src/app/dialogs/agent.py:208 src/tool/oi_select_agent.py:67 +#: src/tool/oi_select_agent.py:94 msgid "智能问答" msgstr "Smart Chat" @@ -474,3 +475,322 @@ msgstr "✗ Unsupported language: {locale}\n" #: src/main.py:228 msgid "Fatal error in Intelligent Shell application" msgstr "Fatal error in Intelligent Shell application" + +#: src/tool/oi_select_agent.py:29 +msgid "退出" +msgstr "Exit" + +#: src/tool/oi_select_agent.py:123 +#, python-brace-format +msgid "✓ 默认智能体已设置为: {name}\n" +msgstr "✓ Default agent set to: {name}\n" + +#: src/tool/oi_select_agent.py:125 +#, python-brace-format +msgid " App ID: {app_id}\n" +msgstr " App ID: {app_id}\n" + +#: src/tool/oi_select_agent.py:127 +msgid " 已设置为智能问答模式(无智能体)\n" +msgstr " Set to Smart Q&A mode (no agent)\n" + +#: src/tool/oi_select_agent.py:129 +msgid "已取消选择\n" +msgstr "Selection cancelled\n" + +#: src/tool/oi_select_agent.py:143 +msgid "错误: 智能体功能需要使用 openEuler Intelligence 后端\n" +msgstr "Error: Agent functionality requires openEuler Intelligence backend\n" + +#: src/tool/oi_select_agent.py:144 +msgid "请先运行以下命令切换后端:\n" +msgstr "Please run the following command to switch backend:\n" + +#: src/tool/oi_select_agent.py:145 +msgid "" +" oi # 然后按下 Ctrl+S 进入设置界面切换到 openEuler Intelligence 后端\n" +msgstr "" +" oi # Then press Ctrl+S to enter settings and switch to openEuler " +"Intelligence backend\n" + +#: src/tool/oi_select_agent.py:163 +#, python-brace-format +msgid "错误: {error}\n" +msgstr "Error: {error}\n" + +#: src/tool/oi_llm_config.py:77 +msgid "需要管理员权限才能修改 openEuler Intelligence 配置文件" +msgstr "Administrator privileges are required to modify openEuler Intelligence configuration files" + +#: src/tool/oi_llm_config.py:84 +#, python-brace-format +msgid "配置文件不存在: {path}" +msgstr "Configuration file does not exist: {path}" + +#: src/tool/oi_llm_config.py:85 +msgid "请先运行 '(sudo) oi --init' 部署后端服务" +msgstr "Please run '(sudo) oi --init' first to deploy backend services" + +#: src/tool/oi_llm_config.py:89 src/tool/oi_llm_config.py:93 +#, python-brace-format +msgid "配置文件不可写: {path}" +msgstr "Configuration file is not writable: {path}" + +#: src/tool/oi_llm_config.py:96 +#, python-brace-format +msgid "访问配置文件时权限不足: {error}" +msgstr "Insufficient permissions when accessing configuration file: {error}" + +#: src/tool/oi_llm_config.py:98 +#, python-brace-format +msgid "访问配置文件时发生错误: {error}" +msgstr "Error occurred when accessing configuration file: {error}" + +#: src/tool/oi_llm_config.py:127 +#, python-brace-format +msgid "权限不足:无法访问配置文件 {filename},请以管理员身份运行" +msgstr "Insufficient permissions: Cannot access configuration file {filename}, please run as administrator" + +#: src/tool/validators.py:135 src/tool/validators.py:584 +#: src/tool/validators.py:647 +#, python-brace-format +msgid "连接超时 - 无法在 {timeout} 秒内连接到 {endpoint}" +msgstr "Connection timeout - Unable to connect to {endpoint} within {timeout} seconds" + +#: src/tool/validators.py:140 +#, python-brace-format +msgid "LLM 配置验证失败: {error}" +msgstr "LLM configuration validation failed: {error}" + +#: src/tool/validators.py:144 +msgid "LLM 配置验证成功" +msgstr "LLM configuration validation successful" + +#: src/tool/validators.py:146 +#, python-brace-format +msgid " - 支持工具调用,类型: {func_type}" +msgstr " - Tool calling supported, type: {func_type}" + +#: src/tool/validators.py:148 +msgid " - 不支持工具调用" +msgstr " - Tool calling not supported" + +#: src/tool/validators.py:201 +msgid "无法连接到 Embedding 模型服务。" +msgstr "Unable to connect to Embedding model service." + +#: src/tool/validators.py:241 +msgid "基本对话测试失败" +msgstr "Basic chat test failed" + +#: src/tool/validators.py:244 +msgid "基本对话功能正常" +msgstr "Basic chat functionality is working" + +#: src/tool/validators.py:246 +msgid "对话响应为空" +msgstr "Chat response is empty" + +#: src/tool/validators.py:318 +msgid "不支持任何 function_call 格式" +msgstr "No function_call format is supported" + +#: src/tool/validators.py:353 +#, python-brace-format +msgid "tools 格式测试失败: {error}" +msgstr "tools format test failed: {error}" + +#: src/tool/validators.py:358 +msgid "支持 tools 格式的 function_call" +msgstr "tools format function_call is supported" + +#: src/tool/validators.py:360 +msgid "不支持工具调用功能" +msgstr "Tool calling functionality is not supported" + +#: src/tool/validators.py:401 +#, python-brace-format +msgid "structured_output 格式测试失败: {error}" +msgstr "structured_output format test failed: {error}" + +#: src/tool/validators.py:409 +msgid "structured_output 响应不是有效 JSON" +msgstr "structured_output response is not valid JSON" + +#: src/tool/validators.py:411 +msgid "支持 structured_output 格式" +msgstr "structured_output format is supported" + +#: src/tool/validators.py:413 +msgid "structured_output 响应为空" +msgstr "structured_output response is empty" + +#: src/tool/validators.py:439 +#, python-brace-format +msgid "json_mode 格式测试失败: {error}" +msgstr "json_mode format test failed: {error}" + +#: src/tool/validators.py:447 +msgid "json_mode 响应不是有效 JSON" +msgstr "json_mode response is not valid JSON" + +#: src/tool/validators.py:449 +msgid "支持 json_mode 格式" +msgstr "json_mode format is supported" + +#: src/tool/validators.py:451 +msgid "json_mode 响应为空" +msgstr "json_mode response is empty" + +#: src/tool/validators.py:499 +msgid "支持 vLLM 结构化输出(部分支持)" +msgstr "vLLM structured output is supported (partial support)" + +#: src/tool/validators.py:504 +#, python-brace-format +msgid "不支持 vLLM guided_json 格式: {error}" +msgstr "vLLM guided_json format is not supported: {error}" + +#: src/tool/validators.py:508 +msgid "vLLM guided_json 响应无效" +msgstr "vLLM guided_json response is invalid" + +#: src/tool/validators.py:555 +msgid "支持 Ollama function_call 格式" +msgstr "Ollama function_call format is supported" + +#: src/tool/validators.py:558 +#, python-brace-format +msgid "不支持 Ollama function_call 格式: {error}" +msgstr "Ollama function_call format is not supported: {error}" + +#: src/tool/validators.py:561 +msgid "Ollama function_call 响应无效" +msgstr "Ollama function_call response is invalid" + +#: src/tool/validators.py:589 +#, python-brace-format +msgid "OpenAI Embedding 配置验证失败: {error}" +msgstr "OpenAI Embedding configuration validation failed: {error}" + +#: src/tool/validators.py:598 +#, python-brace-format +msgid "OpenAI Embedding 配置验证成功 - 维度: {dimension}" +msgstr "OpenAI Embedding configuration validation successful - Dimension: {dimension}" + +#: src/tool/validators.py:606 +msgid "OpenAI Embedding 响应为空" +msgstr "OpenAI Embedding response is empty" + +#: src/tool/validators.py:634 +#, python-brace-format +msgid "MindIE Embedding 配置验证成功 - 维度: {dimension}" +msgstr "MindIE Embedding configuration validation successful - Dimension: {dimension}" + +#: src/tool/validators.py:644 +msgid "MindIE Embedding 响应格式不正确" +msgstr "MindIE Embedding response format is incorrect" + +#: src/tool/validators.py:652 +#, python-brace-format +msgid "MindIE Embedding 配置验证失败: {error}" +msgstr "MindIE Embedding configuration validation failed: {error}" + +#: src/tool/validators.py:674 +msgid "服务 URL 必须以 http:// 或 https:// 开头" +msgstr "Service URL must start with http:// or https://" + +#: src/tool/validators.py:685 +msgid "访问令牌格式无效" +msgstr "Access token format is invalid" + +#: src/tool/validators.py:710 +msgid "服务返回的数据格式不正确" +msgstr "Service returned data in incorrect format" + +#: src/tool/validators.py:716 +msgid "连接成功" +msgstr "Connection successful" + +#: src/tool/validators.py:718 +#, python-brace-format +msgid "服务返回错误代码: {code}" +msgstr "Service returned error code: {code}" + +#: src/tool/validators.py:721 +msgid "无法连接到服务,请检查 URL 和网络连接" +msgstr "Unable to connect to service, please check URL and network connection" + +#: src/tool/validators.py:723 +msgid "连接超时,请检查网络连接或服务状态" +msgstr "Connection timeout, please check network connection or service status" + +#: src/tool/validators.py:726 +#, python-brace-format +msgid "连接验证失败: {error}" +msgstr "Connection validation failed: {error}" + +#: src/tool/validators.py:732 +msgid "访问令牌无效或已过期" +msgstr "Access token is invalid or expired" + +#: src/tool/validators.py:733 +msgid "访问权限不足" +msgstr "Insufficient access permissions" + +#: src/tool/validators.py:734 +msgid "API 接口不存在,请检查服务版本" +msgstr "API interface does not exist, please check service version" + +#: src/tool/validators.py:737 +#, python-brace-format +msgid "服务响应异常,状态码: {status_code}" +msgstr "Service response error, status code: {status_code}" + +#: src/tool/command_processor.py:56 +msgid "请输入有效命令或问题。" +msgstr "Please enter a valid command or question." + +#: src/tool/command_processor.py:75 +msgid "检测到不安全命令,已阻止执行。" +msgstr "Unsafe command detected, execution blocked." + +#: src/tool/command_processor.py:135 +msgid "[命令启动失败] 无法创建子进程" +msgstr "[Command start failed] Unable to create subprocess" + +#: src/tool/command_processor.py:136 +#, python-brace-format +msgid "无法启动命令 '{command}',请分析可能原因并给出解决建议。" +msgstr "Unable to start command '{command}', please analyze possible causes and provide solutions." + +#: src/tool/command_processor.py:165 +#, python-brace-format +msgid "" +"\n" +"[命令完成] 退出码: {returncode}" +msgstr "" +"\n" +"[Command completed] Exit code: {returncode}" + +#: src/tool/command_processor.py:183 +#, python-brace-format +msgid "[命令失败] 退出码: {returncode}" +msgstr "[Command failed] Exit code: {returncode}" + +#: src/tool/command_processor.py:188 +#, python-brace-format +msgid "" +"命令 '{command}' 以非零状态 {returncode} 退出。\n" +"标准错误输出如下:\n" +"{stderr_text}\n" +"请分析原因并提供解决建议。" +msgstr "" +"Command '{command}' exited with non-zero status {returncode}.\n" +"Standard error output:\n" +"{stderr_text}\n" +"Please analyze the cause and provide solutions." + +#: src/tool/command_processor.py:206 +msgid "读取 stderr 失败" +msgstr "Failed to read stderr" diff --git a/src/i18n/locales/zh_CN/LC_MESSAGES/messages.po b/src/i18n/locales/zh_CN/LC_MESSAGES/messages.po index ed9d147..c0926a4 100644 --- a/src/i18n/locales/zh_CN/LC_MESSAGES/messages.po +++ b/src/i18n/locales/zh_CN/LC_MESSAGES/messages.po @@ -6,7 +6,7 @@ msgid "" msgstr "" "Project-Id-Version: oi-cli\n" "Report-Msgid-Bugs-To: contact@openeuler.org\n" -"POT-Creation-Date: 2025-10-21 10:23+0800\n" +"POT-Creation-Date: 2025-10-21 10:54+0800\n" "PO-Revision-Date: 2025-10-21 09:40+0800\n" "Last-Translator: openEuler Intelligence Team\n" "Language-Team: Chinese (Simplified)\n" @@ -154,8 +154,8 @@ msgstr "切换焦点" #: src/app/tui.py:255 #, python-brace-format -msgid "Intelligent CLI Tool {version}" -msgstr "智能命令行工具 {version}" +msgid "Intelligent CLI Assistant {version}" +msgstr "智能命令行助手 {version}" #: src/app/tui.py:371 msgid "[Cancelled]" @@ -265,7 +265,8 @@ msgstr "按任意键关闭" #: src/app/dialogs/agent.py:60 src/app/dialogs/agent.py:130 #: src/app/dialogs/agent.py:185 src/app/dialogs/agent.py:205 -#: src/app/dialogs/agent.py:208 +#: src/app/dialogs/agent.py:208 src/tool/oi_select_agent.py:67 +#: src/tool/oi_select_agent.py:94 msgid "智能问答" msgstr "智能问答" @@ -469,3 +470,321 @@ msgstr "✗ 不支持的语言: {locale}\n" #: src/main.py:228 msgid "Fatal error in Intelligent Shell application" msgstr "智能 Shell 应用发生致命错误" + +#: src/tool/oi_select_agent.py:29 +msgid "退出" +msgstr "退出" + +#: src/tool/oi_select_agent.py:123 +#, python-brace-format +msgid "✓ 默认智能体已设置为: {name}\n" +msgstr "✓ 默认智能体已设置为: {name}\n" + +#: src/tool/oi_select_agent.py:125 +#, python-brace-format +msgid " App ID: {app_id}\n" +msgstr " App ID: {app_id}\n" + +#: src/tool/oi_select_agent.py:127 +msgid " 已设置为智能问答模式(无智能体)\n" +msgstr " 已设置为智能问答模式(无智能体)\n" + +#: src/tool/oi_select_agent.py:129 +msgid "已取消选择\n" +msgstr "已取消选择\n" + +#: src/tool/oi_select_agent.py:143 +msgid "错误: 智能体功能需要使用 openEuler Intelligence 后端\n" +msgstr "错误: 智能体功能需要使用 openEuler Intelligence 后端\n" + +#: src/tool/oi_select_agent.py:144 +msgid "请先运行以下命令切换后端:\n" +msgstr "请先运行以下命令切换后端:\n" + +#: src/tool/oi_select_agent.py:145 +msgid "" +" oi # 然后按下 Ctrl+S 进入设置界面切换到 openEuler Intelligence 后端\n" +msgstr "" +" oi # 然后按下 Ctrl+S 进入设置界面切换到 openEuler Intelligence 后端\n" + +#: src/tool/oi_select_agent.py:163 +#, python-brace-format +msgid "错误: {error}\n" +msgstr "错误: {error}\n" + +#: src/tool/oi_llm_config.py:77 +msgid "需要管理员权限才能修改 openEuler Intelligence 配置文件" +msgstr "需要管理员权限才能修改 openEuler Intelligence 配置文件" + +#: src/tool/oi_llm_config.py:84 +#, python-brace-format +msgid "配置文件不存在: {path}" +msgstr "配置文件不存在: {path}" + +#: src/tool/oi_llm_config.py:85 +msgid "请先运行 '(sudo) oi --init' 部署后端服务" +msgstr "请先运行 '(sudo) oi --init' 部署后端服务" + +#: src/tool/oi_llm_config.py:89 src/tool/oi_llm_config.py:93 +#, python-brace-format +msgid "配置文件不可写: {path}" +msgstr "配置文件不可写: {path}" + +#: src/tool/oi_llm_config.py:96 +#, python-brace-format +msgid "访问配置文件时权限不足: {error}" +msgstr "访问配置文件时权限不足: {error}" + +#: src/tool/oi_llm_config.py:98 +#, python-brace-format +msgid "访问配置文件时发生错误: {error}" +msgstr "访问配置文件时发生错误: {error}" + +#: src/tool/oi_llm_config.py:127 +#, python-brace-format +msgid "权限不足:无法访问配置文件 {filename},请以管理员身份运行" +msgstr "权限不足:无法访问配置文件 {filename},请以管理员身份运行" + +#: src/tool/validators.py:135 src/tool/validators.py:584 +#: src/tool/validators.py:647 +#, python-brace-format +msgid "连接超时 - 无法在 {timeout} 秒内连接到 {endpoint}" +msgstr "连接超时 - 无法在 {timeout} 秒内连接到 {endpoint}" + +#: src/tool/validators.py:140 +#, python-brace-format +msgid "LLM 配置验证失败: {error}" +msgstr "LLM 配置验证失败: {error}" + +#: src/tool/validators.py:144 +msgid "LLM 配置验证成功" +msgstr "LLM 配置验证成功" + +#: src/tool/validators.py:146 +#, python-brace-format +msgid " - 支持工具调用,类型: {func_type}" +msgstr " - 支持工具调用,类型: {func_type}" + +#: src/tool/validators.py:148 +msgid " - 不支持工具调用" +msgstr " - 不支持工具调用" + +#: src/tool/validators.py:201 +msgid "无法连接到 Embedding 模型服务。" +msgstr "无法连接到 Embedding 模型服务。" + +#: src/tool/validators.py:241 +msgid "基本对话测试失败" +msgstr "基本对话测试失败" + +#: src/tool/validators.py:244 +msgid "基本对话功能正常" +msgstr "基本对话功能正常" + +#: src/tool/validators.py:246 +msgid "对话响应为空" +msgstr "对话响应为空" + +#: src/tool/validators.py:318 +msgid "不支持任何 function_call 格式" +msgstr "不支持任何 function_call 格式" + +#: src/tool/validators.py:353 +#, python-brace-format +msgid "tools 格式测试失败: {error}" +msgstr "tools 格式测试失败: {error}" + +#: src/tool/validators.py:358 +msgid "支持 tools 格式的 function_call" +msgstr "支持 tools 格式的 function_call" + +#: src/tool/validators.py:360 +msgid "不支持工具调用功能" +msgstr "不支持工具调用功能" + +#: src/tool/validators.py:401 +#, python-brace-format +msgid "structured_output 格式测试失败: {error}" +msgstr "structured_output 格式测试失败: {error}" + +#: src/tool/validators.py:409 +msgid "structured_output 响应不是有效 JSON" +msgstr "structured_output 响应不是有效 JSON" + +#: src/tool/validators.py:411 +msgid "支持 structured_output 格式" +msgstr "支持 structured_output 格式" + +#: src/tool/validators.py:413 +msgid "structured_output 响应为空" +msgstr "structured_output 响应为空" + +#: src/tool/validators.py:439 +#, python-brace-format +msgid "json_mode 格式测试失败: {error}" +msgstr "json_mode 格式测试失败: {error}" + +#: src/tool/validators.py:447 +msgid "json_mode 响应不是有效 JSON" +msgstr "json_mode 响应不是有效 JSON" + +#: src/tool/validators.py:449 +msgid "支持 json_mode 格式" +msgstr "支持 json_mode 格式" + +#: src/tool/validators.py:451 +msgid "json_mode 响应为空" +msgstr "json_mode 响应为空" + +#: src/tool/validators.py:499 +msgid "支持 vLLM 结构化输出(部分支持)" +msgstr "支持 vLLM 结构化输出(部分支持)" + +#: src/tool/validators.py:504 +#, python-brace-format +msgid "不支持 vLLM guided_json 格式: {error}" +msgstr "不支持 vLLM guided_json 格式: {error}" + +#: src/tool/validators.py:508 +msgid "vLLM guided_json 响应无效" +msgstr "vLLM guided_json 响应无效" + +#: src/tool/validators.py:555 +msgid "支持 Ollama function_call 格式" +msgstr "支持 Ollama function_call 格式" + +#: src/tool/validators.py:558 +#, python-brace-format +msgid "不支持 Ollama function_call 格式: {error}" +msgstr "不支持 Ollama function_call 格式: {error}" + +#: src/tool/validators.py:561 +msgid "Ollama function_call 响应无效" +msgstr "Ollama function_call 响应无效" + +#: src/tool/validators.py:589 +#, python-brace-format +msgid "OpenAI Embedding 配置验证失败: {error}" +msgstr "OpenAI Embedding 配置验证失败: {error}" + +#: src/tool/validators.py:598 +#, python-brace-format +msgid "OpenAI Embedding 配置验证成功 - 维度: {dimension}" +msgstr "OpenAI Embedding 配置验证成功 - 维度: {dimension}" + +#: src/tool/validators.py:606 +msgid "OpenAI Embedding 响应为空" +msgstr "OpenAI Embedding 响应为空" + +#: src/tool/validators.py:634 +#, python-brace-format +msgid "MindIE Embedding 配置验证成功 - 维度: {dimension}" +msgstr "MindIE Embedding 配置验证成功 - 维度: {dimension}" + +#: src/tool/validators.py:644 +msgid "MindIE Embedding 响应格式不正确" +msgstr "MindIE Embedding 响应格式不正确" + +#: src/tool/validators.py:652 +#, python-brace-format +msgid "MindIE Embedding 配置验证失败: {error}" +msgstr "MindIE Embedding 配置验证失败: {error}" + +#: src/tool/validators.py:674 +msgid "服务 URL 必须以 http:// 或 https:// 开头" +msgstr "服务 URL 必须以 http:// 或 https:// 开头" + +#: src/tool/validators.py:685 +msgid "访问令牌格式无效" +msgstr "访问令牌格式无效" + +#: src/tool/validators.py:710 +msgid "服务返回的数据格式不正确" +msgstr "服务返回的数据格式不正确" + +#: src/tool/validators.py:716 +msgid "连接成功" +msgstr "连接成功" + +#: src/tool/validators.py:718 +#, python-brace-format +msgid "服务返回错误代码: {code}" +msgstr "服务返回错误代码: {code}" + +#: src/tool/validators.py:721 +msgid "无法连接到服务,请检查 URL 和网络连接" +msgstr "无法连接到服务,请检查 URL 和网络连接" + +#: src/tool/validators.py:723 +msgid "连接超时,请检查网络连接或服务状态" +msgstr "连接超时,请检查网络连接或服务状态" + +#: src/tool/validators.py:726 +#, python-brace-format +msgid "连接验证失败: {error}" +msgstr "连接验证失败: {error}" + +#: src/tool/validators.py:732 +msgid "访问令牌无效或已过期" +msgstr "访问令牌无效或已过期" + +#: src/tool/validators.py:733 +msgid "访问权限不足" +msgstr "访问权限不足" + +#: src/tool/validators.py:734 +msgid "API 接口不存在,请检查服务版本" +msgstr "API 接口不存在,请检查服务版本" + +#: src/tool/validators.py:737 +#, python-brace-format +msgid "服务响应异常,状态码: {status_code}" +msgstr "服务响应异常,状态码: {status_code}" + +#: src/tool/command_processor.py:56 +msgid "请输入有效命令或问题。" +msgstr "请输入有效命令或问题。" + +#: src/tool/command_processor.py:75 +msgid "检测到不安全命令,已阻止执行。" +msgstr "检测到不安全命令,已阻止执行。" + +#: src/tool/command_processor.py:135 +msgid "[命令启动失败] 无法创建子进程" +msgstr "[命令启动失败] 无法创建子进程" + +#: src/tool/command_processor.py:136 +#, python-brace-format +msgid "无法启动命令 '{command}',请分析可能原因并给出解决建议。" +msgstr "无法启动命令 '{command}',请分析可能原因并给出解决建议。" + +#: src/tool/command_processor.py:165 +#, python-brace-format +msgid "" +"\n" +"[命令完成] 退出码: {returncode}" +msgstr "" +"\n" +"[命令完成] 退出码: {returncode}" + +#: src/tool/command_processor.py:183 +#, python-brace-format +msgid "[命令失败] 退出码: {returncode}" +msgstr "[命令失败] 退出码: {returncode}" + +#: src/tool/command_processor.py:188 +#, python-brace-format +msgid "" +"命令 '{command}' 以非零状态 {returncode} 退出。\n" +"标准错误输出如下:\n" +"{stderr_text}\n" +"请分析原因并提供解决建议。" +msgstr "" +"命令 '{command}' 以非零状态 {returncode} 退出。\n" +"标准错误输出如下:\n" +"{stderr_text}\n" +"请分析原因并提供解决建议。" + +#: src/tool/command_processor.py:206 +msgid "读取 stderr 失败" +msgstr "读取 stderr 失败" -- Gitee From bd8a07043d1d2a64d35dabffc8f7d5fdb48daf8f Mon Sep 17 00:00:00 2001 From: Hongyu Shi Date: Sun, 19 Oct 2025 17:57:08 +0800 Subject: [PATCH 03/19] =?UTF-8?q?chore(i18n):=20=E6=B7=BB=E5=8A=A0?= =?UTF-8?q?=E7=BC=96=E8=AF=91=E5=90=8E=E7=9A=84=E5=9B=BD=E9=99=85=E5=8C=96?= =?UTF-8?q?=E6=96=87=E4=BB=B6=E5=88=B0=20.gitignore?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Signed-off-by: Hongyu Shi --- .gitignore | 3 +++ 1 file changed, 3 insertions(+) diff --git a/.gitignore b/.gitignore index 450a7e5..95f1987 100644 --- a/.gitignore +++ b/.gitignore @@ -20,3 +20,6 @@ # Cython *.c + +# Compiled i18n files +**/*.mo -- Gitee From 1bc8c9297f6cd29f1d3f7a6af11e0f64a2974eb7 Mon Sep 17 00:00:00 2001 From: Hongyu Shi Date: Tue, 21 Oct 2025 14:18:00 +0800 Subject: [PATCH 04/19] =?UTF-8?q?fix(tui):=20=E7=A7=BB=E9=99=A4=E9=80=80?= =?UTF-8?q?=E5=87=BA=E4=BA=8B=E4=BB=B6=E7=AD=89=E5=BE=85=E8=B6=85=E6=97=B6?= =?UTF-8?q?=EF=BC=8C=E6=94=B9=E4=B8=BA=E6=97=A0=E9=99=90=E7=AD=89=E5=BE=85?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Signed-off-by: Hongyu Shi --- src/app/tui.py | 6 ++---- 1 file changed, 2 insertions(+), 4 deletions(-) diff --git a/src/app/tui.py b/src/app/tui.py index c8f4532..292662c 100644 --- a/src/app/tui.py +++ b/src/app/tui.py @@ -1380,11 +1380,9 @@ class IntelligentTerminal(App): # 启动监控任务 monitor_task = asyncio.create_task(monitor_screen_stack()) - # 等待退出事件或超时(5分钟) + # 等待退出事件 try: - await asyncio.wait_for(exit_event.wait(), timeout=300.0) - except TimeoutError: - self.logger.warning("等待设置页面退出超时") + await exit_event.wait() finally: # 取消监控任务 if not monitor_task.done(): -- Gitee From a6398e1b76b4f97bb4472dad2c046e5b851ff17b Mon Sep 17 00:00:00 2001 From: Hongyu Shi Date: Sun, 19 Oct 2025 18:06:58 +0800 Subject: [PATCH 05/19] =?UTF-8?q?fix(i18n):=20=E4=BF=AE=E5=A4=8D=E8=87=AA?= =?UTF-8?q?=E5=8A=A8=E6=A3=80=E6=B5=8B=E7=B3=BB=E7=BB=9F=E8=AF=AD=E8=A8=80?= =?UTF-8?q?=E5=B9=B6=E6=9B=B4=E6=96=B0=E9=85=8D=E7=BD=AE?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Signed-off-by: Hongyu Shi --- src/config/model.py | 4 ++-- src/i18n/manager.py | 2 ++ src/main.py | 16 +++++++++++++--- 3 files changed, 17 insertions(+), 5 deletions(-) diff --git a/src/config/model.py b/src/config/model.py index aef1e7b..ab986c4 100644 --- a/src/config/model.py +++ b/src/config/model.py @@ -84,7 +84,7 @@ class ConfigModel: openai: OpenAIConfig = field(default_factory=OpenAIConfig) eulerintelli: HermesConfig = field(default_factory=HermesConfig) log_level: LogLevel = field(default=LogLevel.DEBUG) - locale: str = field(default="en_US") # 默认语言为英语 + locale: str = field(default="") # 空字符串表示自动检测系统语言 @classmethod def from_dict(cls, d: dict) -> "ConfigModel": @@ -115,7 +115,7 @@ class ConfigModel: openai=OpenAIConfig.from_dict(d.get("openai", {})), eulerintelli=HermesConfig.from_dict(d.get("eulerintelli", {})), log_level=log_level, - locale=d.get("locale", "en_US"), + locale=d.get("locale", ""), # 空字符串表示自动检测 ) def to_dict(self) -> dict: diff --git a/src/i18n/manager.py b/src/i18n/manager.py index b4d6828..7a04747 100644 --- a/src/i18n/manager.py +++ b/src/i18n/manager.py @@ -95,6 +95,8 @@ class I18nManager: if system_locale: # 标准化语言代码 (如 zh_CN.UTF-8 -> zh_CN) locale_code = system_locale.split(".")[0] + if locale_code.startswith("zh"): + locale_code = "zh_CN" if locale_code in SUPPORTED_LOCALES: return locale_code except (ValueError, TypeError, locale.Error): diff --git a/src/main.py b/src/main.py index 9d0378f..8585767 100644 --- a/src/main.py +++ b/src/main.py @@ -9,7 +9,7 @@ from __version__ import __version__ from app.tui import IntelligentTerminal from config.manager import ConfigManager from config.model import LogLevel -from i18n.manager import _, get_supported_locales, init_i18n, set_locale +from i18n.manager import _, get_locale, get_supported_locales, init_i18n, set_locale from log.manager import ( cleanup_empty_logs, disable_console_output, @@ -168,8 +168,18 @@ def main() -> None: # 首先初始化配置管理器 config_manager = ConfigManager() - # 初始化国际化系统 - 使用配置中的语言设置 - init_i18n(config_manager.get_locale()) + # 初始化国际化系统 + # 如果配置中没有设置语言(空字符串),则自动检测系统语言 + configured_locale = config_manager.get_locale() + if configured_locale: + # 使用用户配置的语言 + init_i18n(configured_locale) + else: + # 自动检测系统语言 + init_i18n(None) + # 保存检测到的语言到配置中 + detected_locale = get_locale() + config_manager.set_locale(detected_locale) # 解析命令行参数(需要在初始化 i18n 后进行,以支持翻译) args = parse_args() -- Gitee From fd0f7c1343ced2dcdf7083578ec8c148d97e5fe9 Mon Sep 17 00:00:00 2001 From: Hongyu Shi Date: Tue, 21 Oct 2025 10:13:19 +0800 Subject: [PATCH 06/19] =?UTF-8?q?feat(config):=20=E6=B7=BB=E5=8A=A0?= =?UTF-8?q?=E7=94=A8=E6=88=B7=E9=85=8D=E7=BD=AE=E5=88=B7=E6=96=B0=E5=8A=9F?= =?UTF-8?q?=E8=83=BD=EF=BC=8C=E7=A1=AE=E4=BF=9D=E9=83=A8=E7=BD=B2=E5=90=8E?= =?UTF-8?q?=E5=BA=94=E7=94=A8=E5=85=A8=E5=B1=80=E6=A8=A1=E6=9D=BF=E9=85=8D?= =?UTF-8?q?=E7=BD=AE?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Signed-off-by: Hongyu Shi --- src/tool/oi_backend_init.py | 50 +++++++++++++++++++++++++++++++++++++ 1 file changed, 50 insertions(+) diff --git a/src/tool/oi_backend_init.py b/src/tool/oi_backend_init.py index 551d91d..af07b37 100644 --- a/src/tool/oi_backend_init.py +++ b/src/tool/oi_backend_init.py @@ -2,6 +2,7 @@ from __future__ import annotations +import json from pathlib import Path from textual.app import App @@ -47,6 +48,10 @@ def backend_init() -> None: app = DeploymentApp() result = app.run() logger.info("部署结果: %s", result) + + # 部署完成后,强制从全局模板刷新当前用户的配置 + _refresh_user_config_from_template() + except KeyboardInterrupt: logger.warning("用户中断部署") except ImportError: @@ -56,3 +61,48 @@ def backend_init() -> None: except Exception: logger.exception("未预期的错误") raise + + +def _refresh_user_config_from_template() -> None: + """ + 部署完成后强制从全局模板刷新当前用户的配置 + + 确保部署时创建的全局模板配置能够应用到当前用户 + """ + logger = get_logger(__name__) + + try: + # 检查全局模板是否存在 + if not ConfigManager.GLOBAL_CONFIG_PATH.exists(): + logger.warning("全局配置模板不存在,跳过配置刷新") + return + + # 确保用户配置目录存在 + ConfigManager.USER_CONFIG_PATH.parent.mkdir(parents=True, exist_ok=True) + + # 从全局模板读取配置 + with ConfigManager.GLOBAL_CONFIG_PATH.open(encoding="utf-8") as f: + template_config = json.load(f) + + # 如果用户配置已存在,保留用户的个性化设置(如 locale) + user_config = template_config.copy() + if ConfigManager.USER_CONFIG_PATH.exists(): + try: + with ConfigManager.USER_CONFIG_PATH.open(encoding="utf-8") as f: + existing_config = json.load(f) + # 保留用户的语言设置 + if "locale" in existing_config: + user_config["locale"] = existing_config["locale"] + except (json.JSONDecodeError, OSError): + logger.warning("读取现有用户配置失败,将使用模板配置") + + # 写入用户配置 + with ConfigManager.USER_CONFIG_PATH.open("w", encoding="utf-8") as f: + json.dump(user_config, f, indent=4, ensure_ascii=False) + + logger.info("已从全局模板刷新当前用户配置: %s", ConfigManager.USER_CONFIG_PATH) + + except (OSError, json.JSONDecodeError): + logger.exception("从全局模板刷新用户配置失败") + except Exception: + logger.exception("刷新用户配置时发生未预期的错误") -- Gitee From ca3783f2740c0fe91ebd12cf0ad1a49249ae5e43 Mon Sep 17 00:00:00 2001 From: Hongyu Shi Date: Tue, 21 Oct 2025 14:18:16 +0800 Subject: [PATCH 07/19] =?UTF-8?q?fix(i18n):=20=E5=BB=B6=E8=BF=9F=E5=AF=BC?= =?UTF-8?q?=E5=85=A5=20IntelligentTerminal=EF=BC=8C=E7=A1=AE=E4=BF=9D?= =?UTF-8?q?=E5=9C=A8=20i18n=20=E5=88=9D=E5=A7=8B=E5=8C=96=E4=B9=8B?= =?UTF-8?q?=E5=90=8E?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Signed-off-by: Hongyu Shi --- src/main.py | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/src/main.py b/src/main.py index 8585767..bc2dd29 100644 --- a/src/main.py +++ b/src/main.py @@ -6,7 +6,6 @@ import atexit import sys from __version__ import __version__ -from app.tui import IntelligentTerminal from config.manager import ConfigManager from config.model import LogLevel from i18n.manager import _, get_locale, get_supported_locales, init_i18n, set_locale @@ -222,6 +221,9 @@ def main() -> None: logger = get_logger(__name__) try: + # 延迟导入 IntelligentTerminal,确保在 i18n 初始化之后 + from app.tui import IntelligentTerminal # noqa: PLC0415 + app = IntelligentTerminal() app.run() except Exception: -- Gitee From 6ae5eaef6485095d6a41da0f089c681076cac8b6 Mon Sep 17 00:00:00 2001 From: Hongyu Shi Date: Fri, 17 Oct 2025 18:00:55 +0800 Subject: [PATCH 08/19] =?UTF-8?q?feat(i18n):=20=E6=B7=BB=E5=8A=A0=E5=9B=BD?= =?UTF-8?q?=E9=99=85=E5=8C=96=E7=BF=BB=E8=AF=91=E6=96=87=E4=BB=B6=E7=BC=96?= =?UTF-8?q?=E8=AF=91=E5=92=8C=E6=89=93=E5=8C=85=E6=94=AF=E6=8C=81?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Signed-off-by: Hongyu Shi --- distribution/linux/euler-copilot-shell.spec | 3 +++ ...00\345\217\221\346\214\207\345\215\227.md" | 25 +++++++++++++++++++ oi-cli.spec | 3 +++ 3 files changed, 31 insertions(+) diff --git a/distribution/linux/euler-copilot-shell.spec b/distribution/linux/euler-copilot-shell.spec index d65f525..49801d5 100644 --- a/distribution/linux/euler-copilot-shell.spec +++ b/distribution/linux/euler-copilot-shell.spec @@ -59,6 +59,9 @@ uv pip install . # 安装 PyInstaller(通过 uv 保证环境一致) uv pip install pyinstaller +# 编译国际化翻译文件 +./scripts/tools/i18n-manager.sh compile + # 使用虚拟环境中的 PyInstaller 创建单一可执行文件 pyinstaller --noconfirm \ --distpath dist \ diff --git "a/docs/development/\345\233\275\351\231\205\345\214\226\345\274\200\345\217\221\346\214\207\345\215\227.md" "b/docs/development/\345\233\275\351\231\205\345\214\226\345\274\200\345\217\221\346\214\207\345\215\227.md" index c2ca8b5..6c545f2 100644 --- "a/docs/development/\345\233\275\351\231\205\345\214\226\345\274\200\345\217\221\346\214\207\345\215\227.md" +++ "b/docs/development/\345\233\275\351\231\205\345\214\226\345\274\200\345\217\221\346\214\207\345\215\227.md" @@ -143,6 +143,31 @@ msgstr[0] "找到 {n} 个文件" 这会依次执行提取、更新和编译操作。 +### PyInstaller 打包支持 + +项目使用 PyInstaller 构建单文件可执行程序时,国际化翻译文件会自动打包。 + +**配置文件**: `oi-cli.spec` + +```python +# 数据文件 +added_files = [ + (str(src_dir / "app" / "css" / "styles.tcss"), "app/css"), + # 国际化翻译文件 + (str(src_dir / "i18n" / "locales" / "en_US" / "LC_MESSAGES" / "messages.mo"), "i18n/locales/en_US/LC_MESSAGES"), + (str(src_dir / "i18n" / "locales" / "zh_CN" / "LC_MESSAGES" / "messages.mo"), "i18n/locales/zh_CN/LC_MESSAGES"), +] +``` + +**构建流程**: + +1. 编译翻译文件: `./scripts/tools/i18n-manager.sh compile` +2. 打包应用: `pyinstaller oi-cli.spec` + +打包后的可执行文件会包含所有翻译,无需额外配置即可使用国际化功能。 + +**添加新语言时**: 记得在 `oi-cli.spec` 中添加对应的 `.mo` 文件路径。 + ### 添加新语言 要添加对新语言的支持: diff --git a/oi-cli.spec b/oi-cli.spec index 955e7c8..b1ad230 100644 --- a/oi-cli.spec +++ b/oi-cli.spec @@ -37,6 +37,9 @@ hidden_imports = [ # 数据文件 added_files = [ (str(src_dir / "app" / "css" / "styles.tcss"), "app/css"), + # 国际化翻译文件 + (str(src_dir / "i18n" / "locales" / "en_US" / "LC_MESSAGES" / "messages.mo"), "i18n/locales/en_US/LC_MESSAGES"), + (str(src_dir / "i18n" / "locales" / "zh_CN" / "LC_MESSAGES" / "messages.mo"), "i18n/locales/zh_CN/LC_MESSAGES"), ] a = Analysis( -- Gitee From 5251b513d2993566215404578ec3e0f03084b6e9 Mon Sep 17 00:00:00 2001 From: Hongyu Shi Date: Mon, 20 Oct 2025 19:32:14 +0800 Subject: [PATCH 09/19] =?UTF-8?q?chore(i18n):=20=E7=BF=BB=E8=AF=91?= =?UTF-8?q?=E4=B8=BB=E7=95=8C=E9=9D=A2=20TUI?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Signed-off-by: Hongyu Shi --- src/app/tui.py | 74 +++++----- .../locales/en_US/LC_MESSAGES/messages.po | 131 +++++++++++++++++- src/i18n/locales/messages.pot | 127 ++++++++++++++++- .../locales/zh_CN/LC_MESSAGES/messages.po | 130 ++++++++++++++++- 4 files changed, 415 insertions(+), 47 deletions(-) diff --git a/src/app/tui.py b/src/app/tui.py index a13d38a..8b20da5 100644 --- a/src/app/tui.py +++ b/src/app/tui.py @@ -30,6 +30,7 @@ from backend.hermes.mcp_helpers import ( ) from config import ConfigManager from config.model import Backend +from i18n.manager import _ from log.manager import get_logger, log_exception from tool.command_processor import process_command from tool.validators import APIValidator, validate_oi_connection @@ -213,7 +214,7 @@ class CommandInput(Input): def __init__(self) -> None: """初始化命令输入组件""" - super().__init__(placeholder="输入命令或问题...", id="command-input") + super().__init__(placeholder=_("Enter command or question..."), id="command-input") class IntelligentTerminal(App): @@ -222,12 +223,12 @@ class IntelligentTerminal(App): CSS_PATH = "css/styles.tcss" BINDINGS: ClassVar[list[BindingType]] = [ - Binding(key="ctrl+q", action="request_quit", description="退出"), - Binding(key="ctrl+s", action="settings", description="设置"), - Binding(key="ctrl+r", action="reset_conversation", description="重置对话"), - Binding(key="ctrl+t", action="choose_agent", description="选择智能体"), - Binding(key="ctrl+c", action="cancel", description="取消", priority=True), - Binding(key="tab", action="toggle_focus", description="切换焦点"), + Binding(key="ctrl+q", action="request_quit", description=_("Quit")), + Binding(key="ctrl+s", action="settings", description=_("Settings")), + Binding(key="ctrl+r", action="reset_conversation", description=_("Reset")), + Binding(key="ctrl+t", action="choose_agent", description=_("Agent")), + Binding(key="ctrl+c", action="cancel", description=_("Cancel"), priority=True), + Binding(key="tab", action="toggle_focus", description=_("Focus")), ] class SwitchToMCPConfirm(Message): @@ -251,7 +252,7 @@ class IntelligentTerminal(App): super().__init__() # 设置应用标题 self.title = "openEuler Intelligence" - self.sub_title = f"智能命令行工具 {__version__}" + self.sub_title = _("Intelligent CLI Tool {version}").format(version=__version__) self.config_manager = ConfigManager() self.processing: bool = False # 添加保存任务的集合到类属性 @@ -367,7 +368,7 @@ class IntelligentTerminal(App): if interrupted_count > 0: # 显示中断消息 output_container = self.query_one("#output-container") - interrupt_line = OutputLine("[已取消]") + interrupt_line = OutputLine(_("[Cancelled]")) output_container.mount(interrupt_line) # 异步滚动到底部 scroll_task = asyncio.create_task(self._scroll_to_end()) @@ -571,7 +572,10 @@ class IntelligentTerminal(App): # 如果没有收到任何内容且应用仍在运行,显示错误信息 if not received_any_content and hasattr(self, "is_running") and self.is_running: output_container.mount( - OutputLine("没有收到响应,请检查网络连接或稍后重试", command=False), + OutputLine( + _("No response received, please check network connection or try again later"), + command=False, + ), ) except asyncio.CancelledError: @@ -677,14 +681,14 @@ class IntelligentTerminal(App): """检查各种超时条件,返回是否应该中断处理""" # 检查总体超时 if current_time - stream_state["start_time"] > stream_state["timeout_seconds"]: - output_container.mount(OutputLine("请求超时,已停止处理", command=False)) + output_container.mount(OutputLine(_("Request timeout, processing stopped"), command=False)) return True # 检查无内容超时 received_any_content = stream_state["received_any_content"] time_since_last_content = current_time - stream_state["last_content_time"] if received_any_content and time_since_last_content > stream_state["no_content_timeout"]: - output_container.mount(OutputLine("长时间无响应,已停止处理", command=False)) + output_container.mount(OutputLine(_("No response for a long time, processing stopped"), command=False)) return True return False @@ -712,7 +716,7 @@ class IntelligentTerminal(App): ) # 检查是否是 MCP 消息处理(返回值为 None 表示是 MCP 消息) - tool_name, _ = extract_mcp_tag(content) + tool_name, _cleaned_content = extract_mcp_tag(content) is_mcp_detected = processed_line is None and tool_name is not None # 只有当返回值不为None时才更新current_line @@ -737,7 +741,7 @@ class IntelligentTerminal(App): """处理超时错误""" self.logger.warning("Command stream timed out") if hasattr(self, "is_running") and self.is_running: - output_container.mount(OutputLine("请求超时,请稍后重试", command=False)) + output_container.mount(OutputLine(_("Request timeout, please try again later"), command=False)) return stream_state["received_any_content"] def _handle_cancelled_error(self, output_container: Container, stream_state: dict) -> bool: @@ -881,13 +885,13 @@ class IntelligentTerminal(App): # 处理 HermesAPIError 特殊情况 if hasattr(error, "status_code") and hasattr(error, "message"): if error.status_code == 500: # type: ignore[attr-defined] # noqa: PLR2004 - return f"服务端错误: {error.message}" # type: ignore[attr-defined] + return _("Server error: {message}").format(message=error.message) # type: ignore[attr-defined] if error.status_code >= 400: # type: ignore[attr-defined] # noqa: PLR2004 - return f"请求失败: {error.message}" # type: ignore[attr-defined] + return _("Request failed: {message}").format(message=error.message) # type: ignore[attr-defined] # 定义错误匹配规则和对应的用户友好消息 error_patterns = { - "网络连接异常中断,请检查网络连接后重试": [ + _("Network connection interrupted, please check network and try again"): [ "remoteprotocolerror", "server disconnected", "peer closed connection", @@ -895,11 +899,11 @@ class IntelligentTerminal(App): "connection refused", "broken pipe", ], - "请求超时,请稍后重试": [ + _("Request timeout, please try again later"): [ "timeout", "timed out", ], - "网络连接错误,请检查网络后重试": [ + _("Network connection error, please check network and try again"): [ "network", "connection", "unreachable", @@ -908,19 +912,19 @@ class IntelligentTerminal(App): "httperror", "requestserror", ], - "服务端响应异常,请稍后重试": [ + _("Server response error, please try again later"): [ "http", "status", "response", ], - "数据格式错误,请稍后重试": [ + _("Data format error, please try again later"): [ "json", "decode", "parse", "invalid", "malformed", ], - "认证失败,请检查配置": [ + _("Authentication failed, please check configuration"): [ "auth", "unauthorized", "forbidden", @@ -942,9 +946,9 @@ class IntelligentTerminal(App): "requesterror", ] ): - return "服务端响应异常,请稍后重试" + return _("Server response error, please try again later") - return f"处理命令时出错: {error!s}" + return _("Error processing command: {error}").format(error=str(error)) def _display_error_in_ui(self, error: BaseException) -> None: """在UI界面显示错误信息""" @@ -1163,10 +1167,10 @@ class IntelligentTerminal(App): ) if not success: # 如果没有收到任何响应内容,显示默认消息 - output_container.mount(OutputLine("💡 MCP 响应已发送")) + output_container.mount(OutputLine(_("💡 MCP response sent"))) else: self.logger.error("当前客户端不支持 MCP 响应功能") - output_container.mount(OutputLine("❌ 当前客户端不支持 MCP 响应功能")) + output_container.mount(OutputLine(_("❌ Current client does not support MCP response"))) except Exception as e: self.logger.exception("发送 MCP 响应失败") @@ -1174,7 +1178,9 @@ class IntelligentTerminal(App): if output_container is not None: try: error_message = self._format_error_message(e) - output_container.mount(OutputLine(f"❌ 发送 MCP 响应失败: {error_message}")) + output_container.mount( + OutputLine(_("❌ Failed to send MCP response: {error}").format(error=error_message)), + ) except Exception: # 如果连显示错误信息都失败了,至少记录日志 self.logger.exception("无法显示错误信息") @@ -1193,7 +1199,7 @@ class IntelligentTerminal(App): """处理 MCP 响应的流式回复""" if not isinstance(llm_client, HermesChatClient): self.logger.error("当前客户端不支持 MCP 响应功能") - output_container.mount(OutputLine("❌ 当前客户端不支持 MCP 响应功能")) + output_container.mount(OutputLine(_("❌ Current client does not support MCP response"))) return False # 使用统一的流状态管理,与 _handle_command_stream 保持一致 @@ -1219,7 +1225,7 @@ class IntelligentTerminal(App): break # 判断是否为 LLM 输出内容 - tool_name, _ = extract_mcp_tag(content) + tool_name, _cleaned_content = extract_mcp_tag(content) is_llm_output = tool_name is None # 处理内容 @@ -1239,10 +1245,12 @@ class IntelligentTerminal(App): return await asyncio.wait_for(_process_stream(), timeout=timeout_seconds) except TimeoutError: - output_container.mount(OutputLine(f"⏱️ MCP 响应超时 ({timeout_seconds}秒)")) + output_container.mount( + OutputLine(_("⏱️ MCP response timeout ({seconds} seconds)").format(seconds=timeout_seconds)), + ) return stream_state["received_any_content"] except asyncio.CancelledError: - output_container.mount(OutputLine("🚫 MCP 响应被取消")) + output_container.mount(OutputLine(_("🚫 MCP response cancelled"))) raise def _get_initial_agent(self) -> tuple[str, str]: @@ -1325,8 +1333,8 @@ class IntelligentTerminal(App): def _show_config_validation_notification(self) -> None: """显示配置验证失败的通知""" self.notify( - "后端配置验证失败,请检查并修改配置", - title="配置错误", + _("Backend configuration validation failed, please check and modify"), + title=_("Configuration Error"), severity="error", timeout=1, ) diff --git a/src/i18n/locales/en_US/LC_MESSAGES/messages.po b/src/i18n/locales/en_US/LC_MESSAGES/messages.po index 991491c..61e8480 100644 --- a/src/i18n/locales/en_US/LC_MESSAGES/messages.po +++ b/src/i18n/locales/en_US/LC_MESSAGES/messages.po @@ -6,8 +6,8 @@ msgid "" msgstr "" "Project-Id-Version: smart-shell\n" "Report-Msgid-Bugs-To: contact@openeuler.org\n" -"POT-Creation-Date: 2025-10-17 17:39+0800\n" -"PO-Revision-Date: 2025-01-17 00:00+0000\n" +"POT-Creation-Date: 2025-10-20 17:44+0800\n" +"PO-Revision-Date: 2025-10-20 19:28+0800\n" "Last-Translator: openEuler Intelligence Team\n" "Language-Team: English\n" "Language: en_US\n" @@ -15,6 +15,127 @@ msgstr "" "Content-Type: text/plain; charset=UTF-8\n" "Content-Transfer-Encoding: 8bit\n" "Plural-Forms: nplurals=2; plural=(n != 1);\n" +"X-Generator: Poedit 3.8\n" + +#: src/app/tui.py:217 +msgid "Enter command or question..." +msgstr "Enter question or command..." + +#: src/app/tui.py:226 +msgid "Quit" +msgstr "Quit" + +#: src/app/tui.py:227 +msgid "Settings" +msgstr "Settings" + +#: src/app/tui.py:228 +msgid "Reset" +msgstr "Reset Conversation" + +#: src/app/tui.py:229 +msgid "Agent" +msgstr "Select Agent" + +#: src/app/tui.py:230 +msgid "Cancel" +msgstr "Cancel" + +#: src/app/tui.py:231 +msgid "Focus" +msgstr "Toggle Focus" + +#: src/app/tui.py:255 +#, python-brace-format +msgid "Intelligent CLI Tool {version}" +msgstr "Command Line Tool {version}" + +#: src/app/tui.py:371 +msgid "[Cancelled]" +msgstr "[Cancelled]" + +#: src/app/tui.py:576 +msgid "" +"No response received, please check network connection or try again later" +msgstr "" +"No response received, please check network connection or try again later" + +#: src/app/tui.py:684 +msgid "Request timeout, processing stopped" +msgstr "Request timeout, processing stopped" + +#: src/app/tui.py:691 +msgid "No response for a long time, processing stopped" +msgstr "No response for a long time, processing stopped" + +#: src/app/tui.py:744 src/app/tui.py:902 +msgid "Request timeout, please try again later" +msgstr "Request timeout, please try again later" + +#: src/app/tui.py:888 +#, python-brace-format +msgid "Server error: {message}" +msgstr "Server error: {message}" + +#: src/app/tui.py:890 +#, python-brace-format +msgid "Request failed: {message}" +msgstr "Request failed: {message}" + +#: src/app/tui.py:894 +msgid "Network connection interrupted, please check network and try again" +msgstr "Network connection interrupted, please check network and try again" + +#: src/app/tui.py:906 +msgid "Network connection error, please check network and try again" +msgstr "Network connection error, please check network and try again" + +#: src/app/tui.py:915 src/app/tui.py:949 +msgid "Server response error, please try again later" +msgstr "Server response error, please try again later" + +#: src/app/tui.py:920 +msgid "Data format error, please try again later" +msgstr "Data format error, please try again later" + +#: src/app/tui.py:927 +msgid "Authentication failed, please check configuration" +msgstr "Authentication failed, please check configuration" + +#: src/app/tui.py:951 +#, python-brace-format +msgid "Error processing command: {error}" +msgstr "Processing command failed with error: {error}" + +#: src/app/tui.py:1170 +msgid "💡 MCP response sent" +msgstr "💡 MCP response sent" + +#: src/app/tui.py:1173 src/app/tui.py:1202 +msgid "❌ Current client does not support MCP response" +msgstr "❌ Current client does not support MCP response" + +#: src/app/tui.py:1182 +#, python-brace-format +msgid "❌ Failed to send MCP response: {error}" +msgstr "❌ Failed to send MCP response: {error}" + +#: src/app/tui.py:1249 +#, python-brace-format +msgid "⏱️ MCP response timeout ({seconds} seconds)" +msgstr "⏱️ MCP response timeout ({seconds} seconds)" + +#: src/app/tui.py:1253 +msgid "🚫 MCP response cancelled" +msgstr "🚫 MCP response cancelled" + +#: src/app/tui.py:1336 +msgid "Backend configuration validation failed, please check and modify" +msgstr "Backend configuration validation failed, please check and modify" + +#: src/app/tui.py:1337 +msgid "Configuration Error" +msgstr "Configuration Error" #: src/main.py:28 msgid "openEuler Intelligence - Intelligent command-line tool" @@ -157,16 +278,16 @@ msgstr "✓ Log level successfully set to: {level}\n" msgid "✓ Logging system initialized\n" msgstr "✓ Logging system initialized\n" -#: src/main.py:181 +#: src/main.py:191 #, python-brace-format msgid "✓ Language set to: {locale}\n" msgstr "✓ Language set to: {locale}\n" -#: src/main.py:183 +#: src/main.py:193 #, python-brace-format msgid "✗ Unsupported language: {locale}\n" msgstr "✗ Unsupported language: {locale}\n" -#: src/main.py:218 +#: src/main.py:228 msgid "Fatal error in Intelligent Shell application" msgstr "Fatal error in Intelligent Shell application" diff --git a/src/i18n/locales/messages.pot b/src/i18n/locales/messages.pot index 3545d8e..a56ed25 100644 --- a/src/i18n/locales/messages.pot +++ b/src/i18n/locales/messages.pot @@ -8,7 +8,7 @@ msgid "" msgstr "" "Project-Id-Version: smart-shell 0.10.2\n" "Report-Msgid-Bugs-To: contact@openeuler.org\n" -"POT-Creation-Date: 2025-10-17 17:39+0800\n" +"POT-Creation-Date: 2025-10-20 17:44+0800\n" "PO-Revision-Date: YEAR-MO-DA HO:MI+ZONE\n" "Last-Translator: FULL NAME \n" "Language-Team: LANGUAGE \n" @@ -17,6 +17,125 @@ msgstr "" "Content-Type: text/plain; charset=UTF-8\n" "Content-Transfer-Encoding: 8bit\n" +#: src/app/tui.py:217 +msgid "Enter command or question..." +msgstr "" + +#: src/app/tui.py:226 +msgid "Quit" +msgstr "" + +#: src/app/tui.py:227 +msgid "Settings" +msgstr "" + +#: src/app/tui.py:228 +msgid "Reset" +msgstr "" + +#: src/app/tui.py:229 +msgid "Agent" +msgstr "" + +#: src/app/tui.py:230 +msgid "Cancel" +msgstr "" + +#: src/app/tui.py:231 +msgid "Focus" +msgstr "" + +#: src/app/tui.py:255 +#, python-brace-format +msgid "Intelligent CLI Tool {version}" +msgstr "" + +#: src/app/tui.py:371 +msgid "[Cancelled]" +msgstr "" + +#: src/app/tui.py:576 +msgid "" +"No response received, please check network connection or try again later" +msgstr "" + +#: src/app/tui.py:684 +msgid "Request timeout, processing stopped" +msgstr "" + +#: src/app/tui.py:691 +msgid "No response for a long time, processing stopped" +msgstr "" + +#: src/app/tui.py:744 src/app/tui.py:902 +msgid "Request timeout, please try again later" +msgstr "" + +#: src/app/tui.py:888 +#, python-brace-format +msgid "Server error: {message}" +msgstr "" + +#: src/app/tui.py:890 +#, python-brace-format +msgid "Request failed: {message}" +msgstr "" + +#: src/app/tui.py:894 +msgid "Network connection interrupted, please check network and try again" +msgstr "" + +#: src/app/tui.py:906 +msgid "Network connection error, please check network and try again" +msgstr "" + +#: src/app/tui.py:915 src/app/tui.py:949 +msgid "Server response error, please try again later" +msgstr "" + +#: src/app/tui.py:920 +msgid "Data format error, please try again later" +msgstr "" + +#: src/app/tui.py:927 +msgid "Authentication failed, please check configuration" +msgstr "" + +#: src/app/tui.py:951 +#, python-brace-format +msgid "Error processing command: {error}" +msgstr "" + +#: src/app/tui.py:1170 +msgid "💡 MCP response sent" +msgstr "" + +#: src/app/tui.py:1173 src/app/tui.py:1202 +msgid "❌ Current client does not support MCP response" +msgstr "" + +#: src/app/tui.py:1182 +#, python-brace-format +msgid "❌ Failed to send MCP response: {error}" +msgstr "" + +#: src/app/tui.py:1249 +#, python-brace-format +msgid "⏱️ MCP response timeout ({seconds} seconds)" +msgstr "" + +#: src/app/tui.py:1253 +msgid "🚫 MCP response cancelled" +msgstr "" + +#: src/app/tui.py:1336 +msgid "Backend configuration validation failed, please check and modify" +msgstr "" + +#: src/app/tui.py:1337 +msgid "Configuration Error" +msgstr "" + #: src/main.py:28 msgid "openEuler Intelligence - Intelligent command-line tool" msgstr "" @@ -148,16 +267,16 @@ msgstr "" msgid "✓ Logging system initialized\n" msgstr "" -#: src/main.py:181 +#: src/main.py:191 #, python-brace-format msgid "✓ Language set to: {locale}\n" msgstr "" -#: src/main.py:183 +#: src/main.py:193 #, python-brace-format msgid "✗ Unsupported language: {locale}\n" msgstr "" -#: src/main.py:218 +#: src/main.py:228 msgid "Fatal error in Intelligent Shell application" msgstr "" diff --git a/src/i18n/locales/zh_CN/LC_MESSAGES/messages.po b/src/i18n/locales/zh_CN/LC_MESSAGES/messages.po index a52bf85..0a1900c 100644 --- a/src/i18n/locales/zh_CN/LC_MESSAGES/messages.po +++ b/src/i18n/locales/zh_CN/LC_MESSAGES/messages.po @@ -6,8 +6,8 @@ msgid "" msgstr "" "Project-Id-Version: smart-shell\n" "Report-Msgid-Bugs-To: contact@openeuler.org\n" -"POT-Creation-Date: 2025-10-17 17:39+0800\n" -"PO-Revision-Date: 2025-01-17 00:00+0000\n" +"POT-Creation-Date: 2025-10-20 17:44+0800\n" +"PO-Revision-Date: 2025-10-20 19:12+0800\n" "Last-Translator: openEuler Intelligence Team\n" "Language-Team: Chinese (Simplified)\n" "Language: zh_CN\n" @@ -15,6 +15,126 @@ msgstr "" "Content-Type: text/plain; charset=UTF-8\n" "Content-Transfer-Encoding: 8bit\n" "Plural-Forms: nplurals=1; plural=0;\n" +"X-Generator: Poedit 3.8\n" + +#: src/app/tui.py:217 +msgid "Enter command or question..." +msgstr "输入命令或问题..." + +#: src/app/tui.py:226 +msgid "Quit" +msgstr "退出" + +#: src/app/tui.py:227 +msgid "Settings" +msgstr "设置" + +#: src/app/tui.py:228 +msgid "Reset" +msgstr "重置对话" + +#: src/app/tui.py:229 +msgid "Agent" +msgstr "选择智能体" + +#: src/app/tui.py:230 +msgid "Cancel" +msgstr "取消" + +#: src/app/tui.py:231 +msgid "Focus" +msgstr "切换焦点" + +#: src/app/tui.py:255 +#, python-brace-format +msgid "Intelligent CLI Tool {version}" +msgstr "智能命令行工具 {version}" + +#: src/app/tui.py:371 +msgid "[Cancelled]" +msgstr "[已取消]" + +#: src/app/tui.py:576 +msgid "" +"No response received, please check network connection or try again later" +msgstr "没有收到响应,请检查网络连接或稍后重试" + +#: src/app/tui.py:684 +msgid "Request timeout, processing stopped" +msgstr "请求超时,已停止处理" + +#: src/app/tui.py:691 +msgid "No response for a long time, processing stopped" +msgstr "长时间无响应,已停止处理" + +#: src/app/tui.py:744 src/app/tui.py:902 +msgid "Request timeout, please try again later" +msgstr "请求超时,请稍后重试" + +#: src/app/tui.py:888 +#, python-brace-format +msgid "Server error: {message}" +msgstr "服务端错误: {message}" + +#: src/app/tui.py:890 +#, python-brace-format +msgid "Request failed: {message}" +msgstr "请求失败: {message}" + +#: src/app/tui.py:894 +msgid "Network connection interrupted, please check network and try again" +msgstr "网络连接异常中断,请检查网络连接后重试" + +#: src/app/tui.py:906 +msgid "Network connection error, please check network and try again" +msgstr "网络连接错误,请检查网络后重试" + +#: src/app/tui.py:915 src/app/tui.py:949 +msgid "Server response error, please try again later" +msgstr "服务端响应异常,请稍后重试" + +#: src/app/tui.py:920 +msgid "Data format error, please try again later" +msgstr "数据格式错误,请稍后重试" + +#: src/app/tui.py:927 +msgid "Authentication failed, please check configuration" +msgstr "认证失败,请检查配置" + +#: src/app/tui.py:951 +#, python-brace-format +msgid "Error processing command: {error}" +msgstr "处理命令时出错: {error}" + +#: src/app/tui.py:1170 +msgid "💡 MCP response sent" +msgstr "💡 MCP 响应已发送" + +#: src/app/tui.py:1173 src/app/tui.py:1202 +msgid "❌ Current client does not support MCP response" +msgstr "❌ 当前客户端不支持 MCP 响应功能" + +#: src/app/tui.py:1182 +#, python-brace-format +msgid "❌ Failed to send MCP response: {error}" +msgstr "❌ 发送 MCP 响应失败: {error}" + +#: src/app/tui.py:1249 +#, python-brace-format +msgid "⏱️ MCP response timeout ({seconds} seconds)" +msgstr "⏱️ MCP 响应超时 ({seconds}秒)" + +#: src/app/tui.py:1253 +msgid "🚫 MCP response cancelled" +msgstr "🚫 MCP 响应被取消" + +#: src/app/tui.py:1336 +msgid "Backend configuration validation failed, please check and modify" +msgstr "后端配置验证失败,请检查并修改配置" + +#: src/app/tui.py:1337 +msgid "Configuration Error" +msgstr "配置错误" #: src/main.py:28 msgid "openEuler Intelligence - Intelligent command-line tool" @@ -155,16 +275,16 @@ msgstr "✓ 日志级别已成功设置为: {level}\n" msgid "✓ Logging system initialized\n" msgstr "✓ 日志系统初始化完成\n" -#: src/main.py:181 +#: src/main.py:191 #, python-brace-format msgid "✓ Language set to: {locale}\n" msgstr "✓ 语言已设置为: {locale}\n" -#: src/main.py:183 +#: src/main.py:193 #, python-brace-format msgid "✗ Unsupported language: {locale}\n" msgstr "✗ 不支持的语言: {locale}\n" -#: src/main.py:218 +#: src/main.py:228 msgid "Fatal error in Intelligent Shell application" msgstr "智能 Shell 应用发生致命错误" -- Gitee From 8d60b4a42b7e9c1e78e997f6267bf542c944a85a Mon Sep 17 00:00:00 2001 From: Hongyu Shi Date: Tue, 21 Oct 2025 10:34:01 +0800 Subject: [PATCH 10/19] =?UTF-8?q?feat(i18n):=20=E6=9B=B4=E6=96=B0=20MCP=20?= =?UTF-8?q?=E7=8A=B6=E6=80=81=E6=96=87=E6=9C=AC=E7=89=87=E6=AE=B5=E4=B8=BA?= =?UTF-8?q?=E5=8A=A8=E6=80=81=E7=BF=BB=E8=AF=91=EF=BC=8C=E6=B7=BB=E5=8A=A0?= =?UTF-8?q?=E5=B7=A5=E5=85=B7=E5=88=9D=E5=A7=8B=E5=8C=96=E5=8F=8A=E6=89=A7?= =?UTF-8?q?=E8=A1=8C=E7=8A=B6=E6=80=81=E7=9A=84=E5=9B=BD=E9=99=85=E5=8C=96?= =?UTF-8?q?=E6=94=AF=E6=8C=81?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Signed-off-by: Hongyu Shi --- src/backend/hermes/client.py | 8 +- src/backend/hermes/mcp_helpers.py | 215 ++++++++++++------ .../locales/en_US/LC_MESSAGES/messages.po | 56 ++++- src/i18n/locales/messages.pot | 50 +++- .../locales/zh_CN/LC_MESSAGES/messages.po | 52 ++++- 5 files changed, 298 insertions(+), 83 deletions(-) diff --git a/src/backend/hermes/client.py b/src/backend/hermes/client.py index 49196f6..c13ab83 100644 --- a/src/backend/hermes/client.py +++ b/src/backend/hermes/client.py @@ -10,6 +10,7 @@ from urllib.parse import urljoin import httpx from backend.base import LLMClientBase +from i18n.manager import get_locale from log.manager import get_logger, log_exception from .constants import HTTP_OK @@ -146,12 +147,17 @@ class HermesChatClient(LLMClientBase): # 创建聊天请求 app = HermesApp(self.current_agent_id) + + # 根据当前语言环境设置语言参数 + current_locale = get_locale() + language = "zh" if current_locale.startswith("zh") else "en" + request = HermesChatRequest( app=app, conversation_id=conversation_id, question=prompt, features=HermesFeatures(), - language="zh", + language=language, ) # 直接传递异常,不在这里处理 diff --git a/src/backend/hermes/mcp_helpers.py b/src/backend/hermes/mcp_helpers.py index a76a9af..f43b333 100644 --- a/src/backend/hermes/mcp_helpers.py +++ b/src/backend/hermes/mcp_helpers.py @@ -9,6 +9,8 @@ from __future__ import annotations import re from typing import ClassVar +from i18n.manager import _ + # MCP 状态标记 class MCPTags: @@ -36,73 +38,153 @@ class MCPEmojis: class MCPTextFragments: """MCP 状态文本片段常量""" - INIT_TOOL = "正在初始化工具" - TOOL_WORD = "工具" - EXECUTING = "正在执行..." - COMPLETED = "执行完成" - CANCELLED = "已取消" - FAILED = "执行失败" - WAITING_CONFIRM = "**等待用户确认执行工具**" - WAITING_PARAM = "**等待用户输入参数**" + @staticmethod + def init_tool() -> str: + """正在初始化工具""" + return _("正在初始化工具") + + @staticmethod + def tool_word() -> str: + """工具""" + return _("工具") + + @staticmethod + def executing() -> str: + """正在执行...""" + return _("正在执行...") + + @staticmethod + def completed() -> str: + """执行完成""" + return _("执行完成") + + @staticmethod + def cancelled() -> str: + """已取消""" + return _("已取消") + + @staticmethod + def failed() -> str: + """执行失败""" + return _("执行失败") + + @staticmethod + def waiting_confirm() -> str: + """等待用户确认执行工具""" + return _("**等待用户确认执行工具**") + + @staticmethod + def waiting_param() -> str: + """等待用户输入参数""" + return _("**等待用户输入参数**") # MCP 完整状态消息模板 class MCPMessageTemplates: """MCP 状态消息模板常量""" - # 基础状态指示符(用于识别) - INIT_INDICATOR = f"{MCPEmojis.INIT} {MCPTextFragments.INIT_TOOL}" - INPUT_INDICATOR = f"{MCPEmojis.INPUT} {MCPTextFragments.TOOL_WORD}" - EXECUTING_INDICATOR = MCPTextFragments.EXECUTING - OUTPUT_INDICATOR = f"{MCPEmojis.OUTPUT} {MCPTextFragments.TOOL_WORD}" - COMPLETED_INDICATOR = MCPTextFragments.COMPLETED - CANCEL_INDICATOR = f"{MCPEmojis.CANCEL} {MCPTextFragments.TOOL_WORD}" - CANCELLED_INDICATOR = MCPTextFragments.CANCELLED - ERROR_INDICATOR = f"{MCPEmojis.ERROR} {MCPTextFragments.TOOL_WORD}" - FAILED_INDICATOR = MCPTextFragments.FAILED - WAITING_START_INDICATOR = f"{MCPEmojis.WAITING_START} {MCPTextFragments.WAITING_CONFIRM}" - WAITING_PARAM_INDICATOR = f"{MCPEmojis.WAITING_PARAM} {MCPTextFragments.WAITING_PARAM}" + # 基础状态指示符(用于识别)- 使用函数动态生成 + @staticmethod + def init_indicator() -> str: + """初始化指示符""" + return f"{MCPEmojis.INIT} {MCPTextFragments.init_tool()}" + + @staticmethod + def input_indicator() -> str: + """输入指示符""" + return f"{MCPEmojis.INPUT} {MCPTextFragments.tool_word()}" + + @staticmethod + def executing_indicator() -> str: + """执行中指示符""" + return MCPTextFragments.executing() + + @staticmethod + def output_indicator() -> str: + """输出指示符""" + return f"{MCPEmojis.OUTPUT} {MCPTextFragments.tool_word()}" + + @staticmethod + def completed_indicator() -> str: + """完成指示符""" + return MCPTextFragments.completed() + + @staticmethod + def cancel_indicator() -> str: + """取消指示符""" + return f"{MCPEmojis.CANCEL} {MCPTextFragments.tool_word()}" + + @staticmethod + def cancelled_indicator() -> str: + """已取消指示符""" + return MCPTextFragments.cancelled() + + @staticmethod + def error_indicator() -> str: + """错误指示符""" + return f"{MCPEmojis.ERROR} {MCPTextFragments.tool_word()}" + + @staticmethod + def failed_indicator() -> str: + """失败指示符""" + return MCPTextFragments.failed() + + @staticmethod + def waiting_start_indicator() -> str: + """等待确认指示符""" + return f"{MCPEmojis.WAITING_START} {MCPTextFragments.waiting_confirm()}" + + @staticmethod + def waiting_param_indicator() -> str: + """等待参数指示符""" + return f"{MCPEmojis.WAITING_PARAM} {MCPTextFragments.waiting_param()}" # 完整状态消息模板(用于生成) @staticmethod def init_message(tool_name: str) -> str: """生成工具初始化消息""" - return f"\n{MCPEmojis.INIT} {MCPTextFragments.INIT_TOOL}: `{tool_name}`\n" + return f"\n{MCPEmojis.INIT} {MCPTextFragments.init_tool()}: `{tool_name}`\n" @staticmethod def input_message(tool_name: str) -> str: """生成工具执行中消息""" - return f"\n{MCPEmojis.INPUT} {MCPTextFragments.TOOL_WORD} `{tool_name}` {MCPTextFragments.EXECUTING}\n" + return f"\n{MCPEmojis.INPUT} {MCPTextFragments.tool_word()} `{tool_name}` {MCPTextFragments.executing()}\n" @staticmethod def output_message(tool_name: str) -> str: """生成工具执行完成消息""" - return f"\n{MCPEmojis.OUTPUT} {MCPTextFragments.TOOL_WORD} `{tool_name}` {MCPTextFragments.COMPLETED}\n" + return f"\n{MCPEmojis.OUTPUT} {MCPTextFragments.tool_word()} `{tool_name}` {MCPTextFragments.completed()}\n" @staticmethod def cancel_message(tool_name: str) -> str: """生成工具取消消息""" - return f"\n{MCPEmojis.CANCEL} {MCPTextFragments.TOOL_WORD} `{tool_name}` {MCPTextFragments.CANCELLED}\n" + return f"\n{MCPEmojis.CANCEL} {MCPTextFragments.tool_word()} `{tool_name}` {MCPTextFragments.cancelled()}\n" @staticmethod def error_message(tool_name: str) -> str: """生成工具执行失败消息""" - return f"\n{MCPEmojis.ERROR} {MCPTextFragments.TOOL_WORD} `{tool_name}` {MCPTextFragments.FAILED}\n" + return f"\n{MCPEmojis.ERROR} {MCPTextFragments.tool_word()} `{tool_name}` {MCPTextFragments.failed()}\n" @staticmethod def waiting_start_message(tool_name: str, risk_info: str, reason: str) -> str: """生成等待用户确认消息""" + tool_name_label = _("名称") + explanation_label = _("说明") return ( - f"\n{MCPEmojis.WAITING_START} {MCPTextFragments.WAITING_CONFIRM}\n\n" - f"{MCPEmojis.INIT} {MCPTextFragments.TOOL_WORD}名称: `{tool_name}` {risk_info}\n\n💭 说明: {reason}\n" + f"\n{MCPEmojis.WAITING_START} {MCPTextFragments.waiting_confirm()}\n\n" + f"{MCPEmojis.INIT} {MCPTextFragments.tool_word()}{tool_name_label}: " + f"`{tool_name}` {risk_info}\n\n💭 {explanation_label}: {reason}\n" ) @staticmethod def waiting_param_message(tool_name: str, message_content: str) -> str: """生成等待参数输入消息""" + tool_name_label = _("名称") + explanation_label = _("说明") return ( - f"\n{MCPEmojis.WAITING_PARAM} {MCPTextFragments.WAITING_PARAM}\n\n" - f"{MCPEmojis.INIT} {MCPTextFragments.TOOL_WORD}名称: `{tool_name}`\n\n💭 说明: {message_content}\n" + f"\n{MCPEmojis.WAITING_PARAM} {MCPTextFragments.waiting_param()}\n\n" + f"{MCPEmojis.INIT} {MCPTextFragments.tool_word()}{tool_name_label}: " + f"`{tool_name}`\n\n💭 {explanation_label}: {message_content}\n" ) @@ -110,30 +192,36 @@ class MCPMessageTemplates: class MCPIndicators: """MCP 状态指示符列表常量""" - # 所有状态指示符(用于通用检测) - ALL_INDICATORS: ClassVar[list[str]] = [ - MCPMessageTemplates.INIT_INDICATOR, - MCPMessageTemplates.INPUT_INDICATOR, - MCPMessageTemplates.EXECUTING_INDICATOR, - MCPMessageTemplates.WAITING_START_INDICATOR, - MCPMessageTemplates.WAITING_PARAM_INDICATOR, - MCPMessageTemplates.OUTPUT_INDICATOR, - MCPMessageTemplates.COMPLETED_INDICATOR, - MCPMessageTemplates.CANCEL_INDICATOR, - MCPMessageTemplates.CANCELLED_INDICATOR, - MCPMessageTemplates.ERROR_INDICATOR, - MCPMessageTemplates.FAILED_INDICATOR, - ] + # 所有状态指示符(用于通用检测)- 使用函数动态生成 + @staticmethod + def all_indicators() -> list[str]: + """获取所有状态指示符""" + return [ + MCPMessageTemplates.init_indicator(), + MCPMessageTemplates.input_indicator(), + MCPMessageTemplates.executing_indicator(), + MCPMessageTemplates.waiting_start_indicator(), + MCPMessageTemplates.waiting_param_indicator(), + MCPMessageTemplates.output_indicator(), + MCPMessageTemplates.completed_indicator(), + MCPMessageTemplates.cancel_indicator(), + MCPMessageTemplates.cancelled_indicator(), + MCPMessageTemplates.error_indicator(), + MCPMessageTemplates.failed_indicator(), + ] # 最终状态指示符(用于检测工具执行结束) - FINAL_INDICATORS: ClassVar[list[str]] = [ - MCPMessageTemplates.OUTPUT_INDICATOR, - MCPMessageTemplates.COMPLETED_INDICATOR, - MCPMessageTemplates.CANCEL_INDICATOR, - MCPMessageTemplates.CANCELLED_INDICATOR, - MCPMessageTemplates.ERROR_INDICATOR, - MCPMessageTemplates.FAILED_INDICATOR, - ] + @staticmethod + def final_indicators() -> list[str]: + """获取最终状态指示符""" + return [ + MCPMessageTemplates.output_indicator(), + MCPMessageTemplates.completed_indicator(), + MCPMessageTemplates.cancel_indicator(), + MCPMessageTemplates.cancelled_indicator(), + MCPMessageTemplates.error_indicator(), + MCPMessageTemplates.failed_indicator(), + ] # 进度状态指示符(用于UI快速检测) PROGRESS_INDICATORS: ClassVar[list[str]] = [ @@ -190,18 +278,17 @@ class MCPRiskLevels: HIGH = "high" UNKNOWN = "unknown" - # 风险级别显示映射 - RISK_DISPLAY_MAP: ClassVar[dict[str, str]] = { - LOW: "🟢 低风险", - MEDIUM: "🟡 中等风险", - HIGH: "🔴 高风险", - UNKNOWN: "⚪ 风险等级未知", - } - + # 风险级别显示映射 - 使用函数动态生成 @classmethod def get_risk_display(cls, risk_level: str) -> str: """获取风险级别的显示文本""" - return cls.RISK_DISPLAY_MAP.get(risk_level, cls.RISK_DISPLAY_MAP[cls.UNKNOWN]) + risk_display_map = { + cls.LOW: f"🟢 {_('低风险')}", + cls.MEDIUM: f"🟡 {_('中等风险')}", + cls.HIGH: f"🔴 {_('高风险')}", + cls.UNKNOWN: f"⚪ {_('未知风险')}", + } + return risk_display_map.get(risk_level, risk_display_map[cls.UNKNOWN]) # 工具函数 @@ -212,12 +299,12 @@ def is_mcp_message(content: str) -> bool: return True # 检查是否包含任何 MCP 状态指示符 - return any(indicator in content for indicator in MCPIndicators.ALL_INDICATORS) + return any(indicator in content for indicator in MCPIndicators.all_indicators()) def is_final_mcp_message(content: str) -> bool: """检查内容是否为最终状态的 MCP 消息""" - return any(indicator in content for indicator in MCPIndicators.FINAL_INDICATORS) + return any(indicator in content for indicator in MCPIndicators.final_indicators()) def extract_mcp_tag(content: str) -> tuple[str | None, str]: @@ -268,6 +355,6 @@ def format_tool_message(tool_name: str, status: str, *, use_emoji: bool = True) } if use_emoji and status in emoji_map: - return f"{emoji_map[status]} {MCPTextFragments.TOOL_WORD} `{tool_name}` {status}" + return f"{emoji_map[status]} {MCPTextFragments.tool_word()} `{tool_name}` {status}" - return f"{MCPTextFragments.TOOL_WORD} `{tool_name}` {status}" + return f"{MCPTextFragments.tool_word()} `{tool_name}` {status}" diff --git a/src/i18n/locales/en_US/LC_MESSAGES/messages.po b/src/i18n/locales/en_US/LC_MESSAGES/messages.po index f306571..9502e02 100644 --- a/src/i18n/locales/en_US/LC_MESSAGES/messages.po +++ b/src/i18n/locales/en_US/LC_MESSAGES/messages.po @@ -6,7 +6,7 @@ msgid "" msgstr "" "Project-Id-Version: oi-cli\n" "Report-Msgid-Bugs-To: contact@openeuler.org\n" -"POT-Creation-Date: 2025-10-21 09:54+0800\n" +"POT-Creation-Date: 2025-10-21 10:23+0800\n" "PO-Revision-Date: 2025-10-20 19:28+0800\n" "Last-Translator: openEuler Intelligence Team\n" "Language-Team: English\n" @@ -21,19 +21,19 @@ msgstr "" msgid "需要用户确认是否执行此工具" msgstr "User confirmation required to execute this tool" -#: src/app/mcp_widgets.py:51 +#: src/app/mcp_widgets.py:51 src/backend/hermes/mcp_helpers.py:286 msgid "低风险" msgstr "Low risk" -#: src/app/mcp_widgets.py:52 +#: src/app/mcp_widgets.py:52 src/backend/hermes/mcp_helpers.py:287 msgid "中等风险" msgstr "Medium risk" -#: src/app/mcp_widgets.py:53 +#: src/app/mcp_widgets.py:53 src/backend/hermes/mcp_helpers.py:288 msgid "高风险" msgstr "High risk" -#: src/app/mcp_widgets.py:54 +#: src/app/mcp_widgets.py:54 src/backend/hermes/mcp_helpers.py:289 msgid "未知风险" msgstr "Unknown risk" @@ -276,11 +276,53 @@ msgstr "OS Smart Assistant" #: src/app/dialogs/agent.py:115 msgid "使用上下键选择,回车确认,ESC取消 | ✓ 表示当前选中" -msgstr "Use arrow keys to select, Enter to confirm, ESC to cancel | ✓ indicates current selection" +msgstr "" +"Use arrow keys to select, Enter to confirm, ESC to cancel | ✓ indicates " +"current selection" + +#: src/backend/hermes/mcp_helpers.py:44 +msgid "正在初始化工具" +msgstr "Initializing tool" + +#: src/backend/hermes/mcp_helpers.py:49 +msgid "工具" +msgstr "Tool" + +#: src/backend/hermes/mcp_helpers.py:54 +msgid "正在执行..." +msgstr "Executing..." + +#: src/backend/hermes/mcp_helpers.py:59 +msgid "执行完成" +msgstr "Completed" + +#: src/backend/hermes/mcp_helpers.py:64 +msgid "已取消" +msgstr "Cancelled" + +#: src/backend/hermes/mcp_helpers.py:69 +msgid "执行失败" +msgstr "Failed" + +#: src/backend/hermes/mcp_helpers.py:74 +msgid "**等待用户确认执行工具**" +msgstr "**Waiting for user confirmation to execute tool**" + +#: src/backend/hermes/mcp_helpers.py:79 +msgid "**等待用户输入参数**" +msgstr "**Waiting for user to input parameters**" + +#: src/backend/hermes/mcp_helpers.py:171 src/backend/hermes/mcp_helpers.py:182 +msgid "名称" +msgstr "Name" + +#: src/backend/hermes/mcp_helpers.py:172 src/backend/hermes/mcp_helpers.py:183 +msgid "说明" +msgstr "Description" #: src/main.py:28 msgid "openEuler Intelligence - Intelligent command-line tool" -msgstr "openEuler Intelligence - Intelligent command-line tool" +msgstr "openEuler Intelligence - Command-line Client" #: src/main.py:29 msgid "" diff --git a/src/i18n/locales/messages.pot b/src/i18n/locales/messages.pot index 19f1cd4..0f5f27b 100644 --- a/src/i18n/locales/messages.pot +++ b/src/i18n/locales/messages.pot @@ -8,7 +8,7 @@ msgid "" msgstr "" "Project-Id-Version: oi-cli 0.10.2\n" "Report-Msgid-Bugs-To: contact@openeuler.org\n" -"POT-Creation-Date: 2025-10-21 09:54+0800\n" +"POT-Creation-Date: 2025-10-21 10:23+0800\n" "PO-Revision-Date: YEAR-MO-DA HO:MI+ZONE\n" "Last-Translator: FULL NAME \n" "Language-Team: LANGUAGE \n" @@ -21,19 +21,19 @@ msgstr "" msgid "需要用户确认是否执行此工具" msgstr "" -#: src/app/mcp_widgets.py:51 +#: src/app/mcp_widgets.py:51 src/backend/hermes/mcp_helpers.py:286 msgid "低风险" msgstr "" -#: src/app/mcp_widgets.py:52 +#: src/app/mcp_widgets.py:52 src/backend/hermes/mcp_helpers.py:287 msgid "中等风险" msgstr "" -#: src/app/mcp_widgets.py:53 +#: src/app/mcp_widgets.py:53 src/backend/hermes/mcp_helpers.py:288 msgid "高风险" msgstr "" -#: src/app/mcp_widgets.py:54 +#: src/app/mcp_widgets.py:54 src/backend/hermes/mcp_helpers.py:289 msgid "未知风险" msgstr "" @@ -277,6 +277,46 @@ msgstr "" msgid "使用上下键选择,回车确认,ESC取消 | ✓ 表示当前选中" msgstr "" +#: src/backend/hermes/mcp_helpers.py:44 +msgid "正在初始化工具" +msgstr "" + +#: src/backend/hermes/mcp_helpers.py:49 +msgid "工具" +msgstr "" + +#: src/backend/hermes/mcp_helpers.py:54 +msgid "正在执行..." +msgstr "" + +#: src/backend/hermes/mcp_helpers.py:59 +msgid "执行完成" +msgstr "" + +#: src/backend/hermes/mcp_helpers.py:64 +msgid "已取消" +msgstr "" + +#: src/backend/hermes/mcp_helpers.py:69 +msgid "执行失败" +msgstr "" + +#: src/backend/hermes/mcp_helpers.py:74 +msgid "**等待用户确认执行工具**" +msgstr "" + +#: src/backend/hermes/mcp_helpers.py:79 +msgid "**等待用户输入参数**" +msgstr "" + +#: src/backend/hermes/mcp_helpers.py:171 src/backend/hermes/mcp_helpers.py:182 +msgid "名称" +msgstr "" + +#: src/backend/hermes/mcp_helpers.py:172 src/backend/hermes/mcp_helpers.py:183 +msgid "说明" +msgstr "" + #: src/main.py:28 msgid "openEuler Intelligence - Intelligent command-line tool" msgstr "" diff --git a/src/i18n/locales/zh_CN/LC_MESSAGES/messages.po b/src/i18n/locales/zh_CN/LC_MESSAGES/messages.po index ea8ef32..ed9d147 100644 --- a/src/i18n/locales/zh_CN/LC_MESSAGES/messages.po +++ b/src/i18n/locales/zh_CN/LC_MESSAGES/messages.po @@ -6,7 +6,7 @@ msgid "" msgstr "" "Project-Id-Version: oi-cli\n" "Report-Msgid-Bugs-To: contact@openeuler.org\n" -"POT-Creation-Date: 2025-10-21 09:54+0800\n" +"POT-Creation-Date: 2025-10-21 10:23+0800\n" "PO-Revision-Date: 2025-10-21 09:40+0800\n" "Last-Translator: openEuler Intelligence Team\n" "Language-Team: Chinese (Simplified)\n" @@ -21,19 +21,19 @@ msgstr "" msgid "需要用户确认是否执行此工具" msgstr "需要用户确认是否执行此工具" -#: src/app/mcp_widgets.py:51 +#: src/app/mcp_widgets.py:51 src/backend/hermes/mcp_helpers.py:286 msgid "低风险" msgstr "低风险" -#: src/app/mcp_widgets.py:52 +#: src/app/mcp_widgets.py:52 src/backend/hermes/mcp_helpers.py:287 msgid "中等风险" msgstr "中等风险" -#: src/app/mcp_widgets.py:53 +#: src/app/mcp_widgets.py:53 src/backend/hermes/mcp_helpers.py:288 msgid "高风险" msgstr "高风险" -#: src/app/mcp_widgets.py:54 +#: src/app/mcp_widgets.py:54 src/backend/hermes/mcp_helpers.py:289 msgid "未知风险" msgstr "未知风险" @@ -277,9 +277,49 @@ msgstr "OS 智能助手" msgid "使用上下键选择,回车确认,ESC取消 | ✓ 表示当前选中" msgstr "使用上下键选择,回车确认,ESC取消 | ✓ 表示当前选中" +#: src/backend/hermes/mcp_helpers.py:44 +msgid "正在初始化工具" +msgstr "正在初始化工具" + +#: src/backend/hermes/mcp_helpers.py:49 +msgid "工具" +msgstr "工具" + +#: src/backend/hermes/mcp_helpers.py:54 +msgid "正在执行..." +msgstr "正在执行..." + +#: src/backend/hermes/mcp_helpers.py:59 +msgid "执行完成" +msgstr "执行完成" + +#: src/backend/hermes/mcp_helpers.py:64 +msgid "已取消" +msgstr "已取消" + +#: src/backend/hermes/mcp_helpers.py:69 +msgid "执行失败" +msgstr "执行失败" + +#: src/backend/hermes/mcp_helpers.py:74 +msgid "**等待用户确认执行工具**" +msgstr "**等待用户确认执行工具**" + +#: src/backend/hermes/mcp_helpers.py:79 +msgid "**等待用户输入参数**" +msgstr "**等待用户输入参数**" + +#: src/backend/hermes/mcp_helpers.py:171 src/backend/hermes/mcp_helpers.py:182 +msgid "名称" +msgstr "名称" + +#: src/backend/hermes/mcp_helpers.py:172 src/backend/hermes/mcp_helpers.py:183 +msgid "说明" +msgstr "说明" + #: src/main.py:28 msgid "openEuler Intelligence - Intelligent command-line tool" -msgstr "openEuler Intelligence - 智能命令行工具" +msgstr "openEuler Intelligence - 智能命令行助手" #: src/main.py:29 msgid "" -- Gitee From 45c2c67237a675bec09fcdbdf0723106bb88d511 Mon Sep 17 00:00:00 2001 From: Hongyu Shi Date: Tue, 21 Oct 2025 14:33:55 +0800 Subject: [PATCH 11/19] =?UTF-8?q?fix(i18n):=20=E4=BC=98=E5=8C=96=E9=BB=98?= =?UTF-8?q?=E8=AE=A4=E8=AF=AD=E8=A8=80=E6=A3=80=E6=B5=8B=E9=80=BB=E8=BE=91?= =?UTF-8?q?=EF=BC=8C=E5=88=9D=E5=A7=8B=E4=BD=BF=E7=94=A8=E7=B3=BB=E7=BB=9F?= =?UTF-8?q?=E9=BB=98=E8=AE=A4=E8=AF=AD=E8=A8=80?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Signed-off-by: Hongyu Shi --- src/i18n/manager.py | 55 +++++++++++++++++++++++++++++---------------- 1 file changed, 36 insertions(+), 19 deletions(-) diff --git a/src/i18n/manager.py b/src/i18n/manager.py index 7a04747..269c7c8 100644 --- a/src/i18n/manager.py +++ b/src/i18n/manager.py @@ -11,8 +11,40 @@ SUPPORTED_LOCALES = { "zh_CN": "简体中文", } -# 默认语言 - 英语 -DEFAULT_LOCALE = "en_US" +# 备用语言(当系统语言无法检测时使用) +FALLBACK_LOCALE = "en_US" + + +def _detect_default_locale() -> str: + """ + 检测默认语言环境(在模块加载时调用) + + Returns: + 检测到的语言代码,如果不支持则返回备用语言(英语) + + """ + try: + # 获取系统语言设置 + system_locale, _ = locale.getdefaultlocale() + if system_locale: + # 标准化语言代码 (如 zh_CN.UTF-8 -> zh_CN) + locale_code = system_locale.split(".")[0] + if locale_code.startswith("zh"): + locale_code = "zh_CN" + if locale_code.startswith("en"): + locale_code = "en_US" + if locale_code in SUPPORTED_LOCALES: + return locale_code + except (ValueError, TypeError, locale.Error): + # 捕获可能的 locale 相关异常 + pass + + # 无法检测或不支持时,返回备用语言 + return FALLBACK_LOCALE + + +# 默认语言 - 根据系统语言自动检测 +DEFAULT_LOCALE = _detect_default_locale() class I18nManager: @@ -86,25 +118,10 @@ class I18nManager: 检测系统语言环境 Returns: - 检测到的语言代码,如果不支持则返回默认语言(英语) + 检测到的语言代码,如果不支持则返回默认语言 """ - try: - # 获取系统语言设置 - system_locale, _ = locale.getdefaultlocale() - if system_locale: - # 标准化语言代码 (如 zh_CN.UTF-8 -> zh_CN) - locale_code = system_locale.split(".")[0] - if locale_code.startswith("zh"): - locale_code = "zh_CN" - if locale_code in SUPPORTED_LOCALES: - return locale_code - except (ValueError, TypeError, locale.Error): - # 捕获可能的 locale 相关异常 - pass - - # 无法检测或不支持时,返回默认语言 - return DEFAULT_LOCALE + return _detect_default_locale() def translate(self, message: str, **kwargs: str | float) -> str: """ -- Gitee From 223764a36b26c23fc37fbdb61e8961039d224b68 Mon Sep 17 00:00:00 2001 From: Hongyu Shi Date: Fri, 17 Oct 2025 17:47:09 +0800 Subject: [PATCH 12/19] =?UTF-8?q?feat:=20=E5=88=9D=E6=AD=A5=E6=94=AF?= =?UTF-8?q?=E6=8C=81=E5=9B=BD=E9=99=85=E5=8C=96?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Signed-off-by: Hongyu Shi --- pyproject.toml | 10 +- scripts/tools/i18n-manager.sh | 244 ++++++++++++++++++++++++++++++++++ src/config/manager.py | 9 ++ src/config/model.py | 3 + src/i18n/__init__.py | 23 ++++ src/i18n/manager.py | 204 ++++++++++++++++++++++++++++ src/main.py | 99 +++++++++----- 7 files changed, 559 insertions(+), 33 deletions(-) create mode 100755 scripts/tools/i18n-manager.sh create mode 100644 src/i18n/__init__.py create mode 100644 src/i18n/manager.py diff --git a/pyproject.toml b/pyproject.toml index a65febe..37dc1c7 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -40,7 +40,14 @@ allow-direct-references = true exclude = ["*.pyc", "__pycache__", ".DS_Store"] [tool.hatch.build.targets.wheel] -packages = ["src/app", "src/backend", "src/config", "src/log", "src/tool"] +packages = [ + "src/app", + "src/backend", + "src/config", + "src/i18n", + "src/log", + "src/tool", +] [tool.hatch.build.targets.wheel.force-include] "src/main.py" = "main.py" @@ -48,6 +55,7 @@ packages = ["src/app", "src/backend", "src/config", "src/log", "src/tool"] [tool.hatch.build.targets.wheel.package-data] app = ["css/*.tcss"] +i18n = ["locales/*/LC_MESSAGES/*.mo", "locales/*.pot"] [tool.hatch.build.targets.sdist] include = ["LICENSE", "MANIFEST.in", "README.md", "requirements.txt", "src"] diff --git a/scripts/tools/i18n-manager.sh b/scripts/tools/i18n-manager.sh new file mode 100755 index 0000000..73f6be3 --- /dev/null +++ b/scripts/tools/i18n-manager.sh @@ -0,0 +1,244 @@ +#!/usr/bin/env bash +# 国际化翻译管理脚本 + +set -e + +SCRIPT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)" +PROJECT_ROOT="$(cd "$SCRIPT_DIR/../.." && pwd)" +SRC_DIR="$PROJECT_ROOT/src" +LOCALE_DIR="$SRC_DIR/i18n/locales" +POT_FILE="$LOCALE_DIR/messages.pot" + +# 颜色输出函数(使用 printf 确保兼容性) +print_blue() { + printf "\033[0;34m%s\033[0m\n" "$1" +} + +print_green() { + printf "\033[0;32m%s\033[0m\n" "$1" +} + +print_yellow() { + printf "\033[1;33m%s\033[0m\n" "$1" +} + +print_red() { + printf "\033[0;31m%s\033[0m\n" "$1" +} + +# 检查 gettext 工具是否安装 +check_gettext() { + if ! command -v xgettext &>/dev/null; then + print_red "❌ xgettext command not found. Please install gettext tools:" + echo " macOS: brew install gettext" + echo " Ubuntu/Debian: sudo apt-get install gettext" + echo " Fedora/RHEL/openEuler: sudo dnf install gettext" + exit 1 + fi +} + +# 显示帮助信息 +show_help() { + print_blue "国际化翻译管理工具" + echo "" + echo "使用方法:" + echo " $0 " + echo "" + echo "命令:" + print_green " extract" + echo " 从源代码提取可翻译字符串到模板文件" + print_green " update" + echo " 更新所有语言的翻译文件" + print_green " compile" + echo " 编译翻译文件为二进制格式" + print_green " all" + echo " 执行完整流程 (extract -> update -> compile)" + print_green " help" + echo " 显示此帮助信息" + echo "" + echo "示例:" + echo " $0 extract # 提取可翻译字符串" + echo " $0 compile # 编译翻译文件" + echo " $0 all # 完整翻译工作流" + echo "" + echo "更多信息请参考: docs/development/国际化开发指南.md" +} + +# 提取可翻译字符串 +extract() { + print_blue "🔍 提取可翻译字符串..." + + check_gettext + + # 查找所有 Python 源文件(使用相对路径) + cd "$PROJECT_ROOT" + python_files=$(find src -name "*.py" -type f) + + if [ -z "$python_files" ]; then + print_red "❌ No Python files found in src directory" + exit 1 + fi + + file_count=$(echo "$python_files" | wc -l | tr -d ' ') + echo " Found $file_count Python files" + echo " Output file: $POT_FILE" + + # 使用 xgettext 提取字符串(使用相对路径) + # shellcheck disable=SC2086 + xgettext \ + --language=Python \ + --keyword=_ \ + --keyword=_n:1,2 \ + --output="$POT_FILE" \ + --from-code=UTF-8 \ + --package-name=smart-shell \ + --package-version=0.10.2 \ + --msgid-bugs-address=contact@openeuler.org \ + --copyright-holder="openEuler Intelligence Project" \ + --add-comments=Translators \ + $python_files + + if [ $? -eq 0 ]; then + print_green "✅ Successfully extracted strings to messages.pot" + else + print_red "❌ Failed to extract strings" + exit 1 + fi +} + +# 更新翻译文件 +update() { + print_blue "🔄 更新翻译文件..." + + check_gettext + + if [ ! -f "$POT_FILE" ]; then + print_red "❌ Template file messages.pot not found" + echo " Please run: $0 extract first" + exit 1 + fi + + updated=0 + + # 遍历所有语言目录 + for locale_path in "$LOCALE_DIR"/*; do + if [ ! -d "$locale_path" ]; then + continue + fi + + locale_name=$(basename "$locale_path") + po_file="$locale_path/LC_MESSAGES/messages.po" + + if [ ! -f "$po_file" ]; then + print_yellow "⚠️ Skipping $locale_name: PO file not found" + continue + fi + + echo " Updating $locale_name..." + if msgmerge --update --backup=none "$po_file" "$POT_FILE" 2>/dev/null; then + echo " ✅ Updated $locale_name" + ((updated++)) + else + print_yellow " ⚠️ Failed to update $locale_name" + fi + done + + if [ $updated -gt 0 ]; then + echo "" + print_green "✅ Successfully updated $updated translation file(s)" + echo "" + print_yellow "📝 Next steps:" + echo " 1. Edit the .po files to add/update translations" + echo " 2. Run: $0 compile to compile translations" + else + echo "" + print_yellow "⚠️ No translation files were updated" + fi +} + +# 编译翻译文件 +compile() { + print_blue "⚙️ 编译翻译文件..." + + check_gettext + + compiled=0 + + # 遍历所有语言目录 + for locale_path in "$LOCALE_DIR"/*; do + if [ ! -d "$locale_path" ]; then + continue + fi + + locale_name=$(basename "$locale_path") + po_file="$locale_path/LC_MESSAGES/messages.po" + mo_file="$locale_path/LC_MESSAGES/messages.mo" + + if [ ! -f "$po_file" ]; then + print_yellow "⚠️ Skipping $locale_name: PO file not found" + continue + fi + + echo " Compiling $locale_name..." + if msgfmt -o "$mo_file" "$po_file" 2>/dev/null; then + echo " ✅ Compiled $locale_name" + ((compiled++)) + else + print_yellow " ⚠️ Failed to compile $locale_name" + fi + done + + if [ $compiled -gt 0 ]; then + echo "" + print_green "✅ Successfully compiled $compiled translation file(s)" + else + echo "" + print_yellow "⚠️ No translation files were compiled" + fi +} + +# 执行完整流程 +all() { + extract + echo "" + update + echo "" + compile + echo "" + print_green "✅ 翻译工作流完成!" + echo "" + print_yellow "📝 下一步:" + echo " 1. 编辑 .po 文件添加或更新翻译" + echo " 2. 重新运行 '$0 compile' 编译翻译" + echo " 3. 运行 'oi --locale zh_CN' 测试中文" + echo " 4. 运行 'oi --locale en_US' 测试英文" +} + +# 主函数 +main() { + case "${1:-help}" in + extract) + extract + ;; + update) + update + ;; + compile) + compile + ;; + all) + all + ;; + help | --help | -h) + show_help + ;; + *) + print_red "❌ 未知命令: $1" + echo "" + show_help + exit 1 + ;; + esac +} + +main "$@" diff --git a/src/config/manager.py b/src/config/manager.py index e6ef9c0..ba45454 100644 --- a/src/config/manager.py +++ b/src/config/manager.py @@ -204,6 +204,15 @@ class ConfigManager: self.data.eulerintelli.default_app = app_id self._save_settings() + def get_locale(self) -> str: + """获取当前语言环境""" + return self.data.locale + + def set_locale(self, locale_code: str) -> None: + """更新语言环境并保存""" + self.data.locale = locale_code + self._save_settings() + def validate_and_update_config(self) -> bool: """ 检查配置文件完整性并更新缺失字段 diff --git a/src/config/model.py b/src/config/model.py index f8df042..aef1e7b 100644 --- a/src/config/model.py +++ b/src/config/model.py @@ -84,6 +84,7 @@ class ConfigModel: openai: OpenAIConfig = field(default_factory=OpenAIConfig) eulerintelli: HermesConfig = field(default_factory=HermesConfig) log_level: LogLevel = field(default=LogLevel.DEBUG) + locale: str = field(default="en_US") # 默认语言为英语 @classmethod def from_dict(cls, d: dict) -> "ConfigModel": @@ -114,6 +115,7 @@ class ConfigModel: openai=OpenAIConfig.from_dict(d.get("openai", {})), eulerintelli=HermesConfig.from_dict(d.get("eulerintelli", {})), log_level=log_level, + locale=d.get("locale", "en_US"), ) def to_dict(self) -> dict: @@ -123,4 +125,5 @@ class ConfigModel: "openai": self.openai.to_dict(), "eulerintelli": self.eulerintelli.to_dict(), "log_level": self.log_level.value, + "locale": self.locale, } diff --git a/src/i18n/__init__.py b/src/i18n/__init__.py new file mode 100644 index 0000000..dd0631b --- /dev/null +++ b/src/i18n/__init__.py @@ -0,0 +1,23 @@ +"""国际化支持模块""" + +from i18n.manager import ( + DEFAULT_LOCALE, + SUPPORTED_LOCALES, + _, + _n, + get_locale, + get_supported_locales, + init_i18n, + set_locale, +) + +__all__ = [ + "DEFAULT_LOCALE", + "SUPPORTED_LOCALES", + "_", + "_n", + "get_locale", + "get_supported_locales", + "init_i18n", + "set_locale", +] diff --git a/src/i18n/manager.py b/src/i18n/manager.py new file mode 100644 index 0000000..b4d6828 --- /dev/null +++ b/src/i18n/manager.py @@ -0,0 +1,204 @@ +"""国际化管理模块""" + +import gettext +import locale +from pathlib import Path +from typing import ClassVar + +# 支持的语言列表 +SUPPORTED_LOCALES = { + "en_US": "English", + "zh_CN": "简体中文", +} + +# 默认语言 - 英语 +DEFAULT_LOCALE = "en_US" + + +class I18nManager: + """国际化管理器""" + + _instance: ClassVar["I18nManager | None"] = None + _current_locale: str = DEFAULT_LOCALE + _translations: ClassVar[dict[str, gettext.GNUTranslations | gettext.NullTranslations]] = {} + + def __new__(cls) -> "I18nManager": + """单例模式""" + if cls._instance is None: + cls._instance = super().__new__(cls) + return cls._instance + + def __init__(self) -> None: + """初始化国际化管理器""" + if not hasattr(self, "_initialized"): + self._locale_dir = Path(__file__).parent / "locales" + self._domain = "messages" + self._load_all_translations() + self._initialized = True + + def _load_all_translations(self) -> None: + """预加载所有支持的翻译""" + for locale_code in SUPPORTED_LOCALES: + try: + translation = gettext.translation( + self._domain, + localedir=str(self._locale_dir), + languages=[locale_code], + fallback=False, + ) + self._translations[locale_code] = translation + except FileNotFoundError: + # 如果翻译文件不存在,使用空翻译(返回原始文本) + self._translations[locale_code] = gettext.NullTranslations() + + def set_locale(self, locale_code: str) -> bool: + """ + 设置当前语言环境 + + Args: + locale_code: 语言代码,如 'zh_CN', 'en_US' + + Returns: + 是否设置成功 + + """ + if locale_code not in SUPPORTED_LOCALES: + return False + + self._current_locale = locale_code + + # 安装全局翻译函数 + if locale_code in self._translations: + self._translations[locale_code].install() + + return True + + def get_locale(self) -> str: + """获取当前语言环境""" + return self._current_locale + + def get_supported_locales(self) -> dict[str, str]: + """获取支持的语言列表""" + return SUPPORTED_LOCALES.copy() + + def detect_system_locale(self) -> str: + """ + 检测系统语言环境 + + Returns: + 检测到的语言代码,如果不支持则返回默认语言(英语) + + """ + try: + # 获取系统语言设置 + system_locale, _ = locale.getdefaultlocale() + if system_locale: + # 标准化语言代码 (如 zh_CN.UTF-8 -> zh_CN) + locale_code = system_locale.split(".")[0] + if locale_code in SUPPORTED_LOCALES: + return locale_code + except (ValueError, TypeError, locale.Error): + # 捕获可能的 locale 相关异常 + pass + + # 无法检测或不支持时,返回默认语言 + return DEFAULT_LOCALE + + def translate(self, message: str, **kwargs: str | float) -> str: + """ + 翻译消息 + + Args: + message: 要翻译的消息 + **kwargs: 格式化参数 + + Returns: + 翻译后的消息 + + """ + translation = self._translations.get( + self._current_locale, + gettext.NullTranslations(), + ) + translated = translation.gettext(message) + + # 支持格式化参数 + if kwargs: + translated = translated.format(**kwargs) + + return translated + + def translate_plural( + self, + singular: str, + plural: str, + n: int, + **kwargs: str | float, + ) -> str: + """ + 翻译复数形式 + + Args: + singular: 单数形式 + plural: 复数形式 + n: 数量 + **kwargs: 格式化参数 + + Returns: + 翻译后的消息 + + """ + translation = self._translations.get( + self._current_locale, + gettext.NullTranslations(), + ) + translated = translation.ngettext(singular, plural, n) + + if kwargs: + translated = translated.format(n=n, **kwargs) + + return translated + + +# 全局实例 +_i18n_manager = I18nManager() + + +def init_i18n(locale_code: str | None = None) -> None: + """ + 初始化国际化系统 + + Args: + locale_code: 语言代码,如果为 None 则自动检测系统语言 + + """ + if locale_code is None: + locale_code = _i18n_manager.detect_system_locale() + + _i18n_manager.set_locale(locale_code) + + +def set_locale(locale_code: str) -> bool: + """设置当前语言环境""" + return _i18n_manager.set_locale(locale_code) + + +def get_locale() -> str: + """获取当前语言环境""" + return _i18n_manager.get_locale() + + +def get_supported_locales() -> dict[str, str]: + """获取支持的语言列表""" + return _i18n_manager.get_supported_locales() + + +# 便捷的翻译函数 +def _(message: str, **kwargs: str | float) -> str: + """翻译消息的快捷函数""" + return _i18n_manager.translate(message, **kwargs) + + +def _n(singular: str, plural: str, n: int, **kwargs: str | float) -> str: + """翻译复数形式的快捷函数""" + return _i18n_manager.translate_plural(singular, plural, n, **kwargs) diff --git a/src/main.py b/src/main.py index c228c4f..9d0378f 100644 --- a/src/main.py +++ b/src/main.py @@ -9,6 +9,7 @@ from __version__ import __version__ from app.tui import IntelligentTerminal from config.manager import ConfigManager from config.model import LogLevel +from i18n.manager import _, get_supported_locales, init_i18n, set_locale from log.manager import ( cleanup_empty_logs, disable_console_output, @@ -24,76 +25,96 @@ def parse_args() -> argparse.Namespace: """解析命令行参数""" parser = argparse.ArgumentParser( prog="oi", - description="openEuler Intelligence - 智能命令行工具", - epilog=""" -更多信息和使用文档请访问: + description=_("openEuler Intelligence - Intelligent command-line tool"), + epilog=_(""" +For more information and documentation, please visit: https://gitee.com/openeuler/euler-copilot-shell/tree/master/docs - """, + """), formatter_class=argparse.RawTextHelpFormatter, add_help=False, ) # 通用选项组 general_group = parser.add_argument_group( - "通用选项", - "显示帮助信息和版本信息", + _("General Options"), + _("Show help and version information"), ) general_group.add_argument( "-h", "--help", action="help", - help="显示此帮助信息并退出", + help=_("Show this help message and exit"), ) general_group.add_argument( "-V", "--version", action="version", version=f"%(prog)s {__version__}", - help="显示程序版本号并退出", + help=_("Show program version number and exit"), ) # 后端配置选项组 backend_group = parser.add_argument_group( - "后端配置选项", - "用于初始化和配置 openEuler Intelligence 后端服务", + _("Backend Configuration Options"), + _("For initializing and configuring openEuler Intelligence backend services"), ) backend_group.add_argument( "--init", action="store_true", - help="初始化 openEuler Intelligence 后端\n * 初始化操作需要管理员权限和网络连接", + help=_( + "Initialize openEuler Intelligence backend\n" + " * Initialization requires administrator privileges and network connection", + ), ) backend_group.add_argument( "--llm-config", action="store_true", - help="更改 openEuler Intelligence 大模型设置(需要有效的本地后端服务)\n * 配置编辑操作需要管理员权限", + help=_( + "Change openEuler Intelligence LLM settings (requires valid local backend service)\n" + " * Configuration editing requires administrator privileges", + ), ) # 应用配置选项组 app_group = parser.add_argument_group( - "应用配置选项", - "用于配置应用前端行为和偏好设置", + _("Application Configuration Options"), + _("For configuring application frontend behavior and preferences"), ) app_group.add_argument( "--agent", action="store_true", - help="选择默认智能体", + help=_("Select default agent"), + ) + + # 语言设置选项组 + i18n_group = parser.add_argument_group( + _("Language Settings"), + _("For configuring application display language"), + ) + locale_choices = list(get_supported_locales().keys()) + locale_names = ", ".join(f"{k} ({v})" for k, v in get_supported_locales().items()) + i18n_group.add_argument( + "--locale", + choices=locale_choices, + metavar="LOCALE", + help=_("Set display language (available: {locales})").format(locales=locale_names), ) # 日志管理选项组 log_group = parser.add_argument_group( - "日志管理选项", - "用于查看和配置日志输出", + _("Log Management Options"), + _("For viewing and configuring log output"), ) log_group.add_argument( "--logs", action="store_true", - help="显示最新的日志内容(最多1000行)", + help=_("Show latest log content (up to 1000 lines)"), ) log_group.add_argument( "--log-level", choices=["DEBUG", "INFO", "WARNING", "ERROR"], metavar="LEVEL", - help="设置日志级别 (可选: DEBUG, INFO, WARNING, ERROR)", + help=_("Set log level (available: DEBUG, INFO, WARNING, ERROR)"), ) # 注册清理函数,确保在程序异常退出时也能清理空日志文件 @@ -116,14 +137,14 @@ def show_logs() -> None: # 直接输出到标准输出,保持原有的日志格式 sys.stdout.write(line.rstrip() + "\n") except (OSError, RuntimeError) as e: - sys.stderr.write(f"获取日志失败: {e}\n") + sys.stderr.write(_("Failed to retrieve logs: {error}\n").format(error=e)) sys.exit(1) def set_log_level(config_manager: ConfigManager, level: str) -> None: """设置日志级别""" if level not in LogLevel.__members__: - sys.stderr.write(f"无效的日志级别: {level}\n") + sys.stderr.write(_("Invalid log level: {level}\n").format(level=level)) sys.exit(1) config_manager.set_log_level(LogLevel(level)) @@ -132,20 +153,37 @@ def set_log_level(config_manager: ConfigManager, level: str) -> None: enable_console_output() # 启用控制台输出以显示验证信息 logger = get_logger(__name__) - logger.info("日志级别已设置为: %s", level) - logger.debug("这是一条 DEBUG 级别的测试消息") - logger.info("这是一条 INFO 级别的测试消息") - logger.warning("这是一条 WARNING 级别的测试消息") - logger.error("这是一条 ERROR 级别的测试消息") + logger.info(_("Log level has been set to: %s"), level) + logger.debug(_("This is a DEBUG level test message")) + logger.info(_("This is an INFO level test message")) + logger.warning(_("This is a WARNING level test message")) + logger.error(_("This is an ERROR level test message")) - sys.stdout.write(f"✓ 日志级别已成功设置为: {level}\n") - sys.stdout.write("✓ 日志系统初始化完成\n") + sys.stdout.write(_("✓ Log level successfully set to: {level}\n").format(level=level)) + sys.stdout.write(_("✓ Logging system initialized\n")) def main() -> None: """主函数""" + # 首先初始化配置管理器 + config_manager = ConfigManager() + + # 初始化国际化系统 - 使用配置中的语言设置 + init_i18n(config_manager.get_locale()) + + # 解析命令行参数(需要在初始化 i18n 后进行,以支持翻译) args = parse_args() + # 处理语言设置参数 + if args.locale: + if set_locale(args.locale): + config_manager.set_locale(args.locale) + sys.stdout.write(_("✓ Language set to: {locale}\n").format(locale=args.locale)) + else: + sys.stderr.write(_("✗ Unsupported language: {locale}\n").format(locale=args.locale)) + sys.exit(1) + return + if args.logs: show_logs() return @@ -162,9 +200,6 @@ def main() -> None: llm_config() return - # 初始化配置和日志系统 - config_manager = ConfigManager() - # 处理命令行参数设置的日志级别 if args.log_level: set_log_level(config_manager, args.log_level) @@ -180,7 +215,7 @@ def main() -> None: app = IntelligentTerminal() app.run() except Exception: - logger.exception("智能 Shell 应用发生致命错误") + logger.exception(_("Fatal error in Intelligent Shell application")) raise -- Gitee From 0b5c93d82e0f23b394acb95972d3bbcf98b14643 Mon Sep 17 00:00:00 2001 From: Hongyu Shi Date: Fri, 17 Oct 2025 17:53:05 +0800 Subject: [PATCH 13/19] =?UTF-8?q?chore(i18n):=20=E6=B7=BB=E5=8A=A0?= =?UTF-8?q?=E5=9B=BD=E9=99=85=E5=8C=96=E6=94=AF=E6=8C=81=E6=96=87=E6=A1=A3?= =?UTF-8?q?=E5=92=8C=E5=BF=AB=E9=80=9F=E5=85=A5=E9=97=A8=E6=8C=87=E5=8D=97?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Signed-off-by: Hongyu Shi --- README.md | 38 ++ ...00\345\217\221\346\214\207\345\215\227.md" | 430 ++++++++++++++++++ ...53\351\200\237\345\205\245\351\227\250.md" | 79 ++++ 3 files changed, 547 insertions(+) create mode 100644 "docs/development/\345\233\275\351\231\205\345\214\226\345\274\200\345\217\221\346\214\207\345\215\227.md" create mode 100644 "docs/development/\345\233\275\351\231\205\345\214\226\345\277\253\351\200\237\345\205\245\351\227\250.md" diff --git a/README.md b/README.md index e586009..2568109 100644 --- a/README.md +++ b/README.md @@ -179,6 +179,44 @@ oi --agent 3. 如果系统配置文件不存在或权限不足,工具会显示相应错误信息并退出; 4. 建议在修改配置前备份原有的配置文件。 +## 国际化支持 + +应用程序内置多语言支持,提供英文和中文界面。 + +### 支持的语言 + +- **English (en_US)** - 默认语言 +- **简体中文 (zh_CN)** + +### 切换语言 + +```sh +# 切换到中文 +oi --locale zh_CN + +# 切换到英文 +oi --locale en_US +``` + +语言设置会自动保存,下次启动时生效。 + +### 语言自动检测 + +应用启动时会按以下优先级确定显示语言: + +1. 用户配置文件中的语言设置 +2. 系统环境变量(`LANG`, `LC_ALL` 等) +3. 默认语言(英语) + +如果系统语言为中文,首次运行时会自动使用中文界面。 + +### 开发者文档 + +如需为应用程序添加新的翻译或了解国际化实现细节,请参考: + +- [国际化快速入门](./docs/development/国际化快速入门.md) +- [国际化开发指南](./docs/development/国际化开发指南.md) + ## 配置说明 应用程序支持两种后端配置,配置文件会自动保存在 `~/.config/eulerintelli/smart-shell.json`: diff --git "a/docs/development/\345\233\275\351\231\205\345\214\226\345\274\200\345\217\221\346\214\207\345\215\227.md" "b/docs/development/\345\233\275\351\231\205\345\214\226\345\274\200\345\217\221\346\214\207\345\215\227.md" new file mode 100644 index 0000000..c2ca8b5 --- /dev/null +++ "b/docs/development/\345\233\275\351\231\205\345\214\226\345\274\200\345\217\221\346\214\207\345\215\227.md" @@ -0,0 +1,430 @@ +# 国际化 (i18n) 开发指南 + +## 概述 + +本项目使用 Python 标准库 `gettext` 实现国际化功能,支持多语言界面。目前支持的语言: + +- **English (en_US)** - 默认语言 +- **简体中文 (zh_CN)** + +## 架构设计 + +### 目录结构 + +```text +src/i18n/ +├── __init__.py # 模块导出 +├── manager.py # 国际化管理器 +├── tools.py # 翻译工具脚本 +└── locales/ # 翻译文件目录 + ├── messages.pot # 翻译模板文件 + ├── en_US/ + │ └── LC_MESSAGES/ + │ ├── messages.po # 英文翻译源文件 + │ └── messages.mo # 英文翻译二进制文件 + └── zh_CN/ + └── LC_MESSAGES/ + ├── messages.po # 中文翻译源文件 + └── messages.mo # 中文翻译二进制文件 +``` + +### 核心组件 + +1. **I18nManager**: 单例模式的国际化管理器,负责: + - 加载和管理翻译文件 + - 检测系统语言环境 + - 提供翻译函数接口 + +2. **翻译工具**: 提供命令行工具用于: + - 提取源代码中的可翻译字符串 + - 更新翻译文件 + - 编译翻译文件为二进制格式 + +## 使用方法 + +### 在代码中使用翻译 + +#### 1. 导入翻译函数 + +```python +from i18n.manager import _ +``` + +#### 2. 标记需要翻译的字符串 + +```python +# 简单字符串翻译 +message = _("Hello, world!") + +# 带参数的字符串翻译 +greeting = _("Hello, {name}!").format(name="Alice") + +# 多行字符串翻译 +help_text = _( + "This is a long help text\n" + "that spans multiple lines." +) +``` + +#### 3. 复数形式翻译 + +```python +from i18n.manager import _n + +# 根据数量选择单复数形式 +message = _n( + "Found {n} file", + "Found {n} files", + file_count +).format(n=file_count) +``` + +### 翻译工作流程 + +#### 1. 提取可翻译字符串 + +当你添加或修改了代码中的可翻译字符串后,运行: + +```bash +./scripts/tools/i18n-manager.sh extract +``` + +这会扫描 `src/` 目录下的所有 Python 文件,提取标记为可翻译的字符串到 `src/i18n/locales/messages.pot`。 + +#### 2. 更新翻译文件 + +将新提取的字符串合并到现有的翻译文件: + +```bash +./scripts/tools/i18n-manager.sh update +``` + +这会更新所有语言的 `.po` 文件,添加新的字符串,保留已有的翻译。 + +#### 3. 编辑翻译文件 + +使用文本编辑器或专业的 PO 编辑器(如 Poedit)编辑翻译文件: + +```bash +# 编辑中文翻译 +vim src/i18n/locales/zh_CN/LC_MESSAGES/messages.po +``` + +翻译文件格式示例: + +```po +#: src/main.py +msgid "Hello, world!" +msgstr "你好,世界!" + +#: src/main.py +msgid "Found {n} file" +msgid_plural "Found {n} files" +msgstr[0] "找到 {n} 个文件" +``` + +#### 4. 编译翻译文件 + +将 `.po` 文件编译为二进制 `.mo` 文件: + +```bash +./scripts/tools/i18n-manager.sh compile +``` + +编译后的 `.mo` 文件会被应用程序加载和使用。 + +#### 完整流程(推荐) + +你也可以一次执行所有步骤: + +```bash +./scripts/tools/i18n-manager.sh all +``` + +这会依次执行提取、更新和编译操作。 + +### 添加新语言 + +要添加对新语言的支持: + +1. **在管理器中注册语言** + + 编辑 `src/i18n/manager.py`,在 `SUPPORTED_LOCALES` 中添加新语言: + + ```python + SUPPORTED_LOCALES = { + "en_US": "English", + "zh_CN": "简体中文", + "ja_JP": "日本語", # 新增日语 + } + ``` + +2. **创建翻译文件目录** + + ```bash + mkdir -p src/i18n/locales/ja_JP/LC_MESSAGES + ``` + +3. **初始化翻译文件** + + ```bash + # 从模板创建新的 PO 文件 + msginit -i src/i18n/locales/messages.pot \ + -o src/i18n/locales/ja_JP/LC_MESSAGES/messages.po \ + -l ja_JP + ``` + +4. **翻译并编译** + + 编辑 `messages.po` 文件添加翻译,然后运行: + + ```bash + ./scripts/tools/i18n-manager.sh compile + ``` + +## 配置管理 + +### 语言设置持久化 + +用户选择的语言会保存在配置文件中: + +```json +{ + "locale": "zh_CN" +} +``` + +配置文件位置: + +- 用户配置: `~/.config/eulerintelli/smart-shell.json` +- 全局配置: `/etc/openEuler-Intelligence/smart-shell-template.json` + +### 命令行设置语言 + +```bash +# 设置为中文 +oi --locale zh_CN + +# 设置为英文 +oi --locale en_US + +# 查看帮助(会使用当前配置的语言) +oi --help +``` + +### 语言自动检测 + +应用启动时会按以下优先级确定语言: + +1. 用户配置文件中的设置 +2. 系统环境变量(`LANG`, `LC_ALL` 等) +3. 默认语言(英语) + +## 最佳实践 + +### 1. 字符串提取 + +✅ **推荐做法:** + +```python +# 使用 _() 函数包裹需要翻译的字符串 +message = _("Operation completed successfully") + +# 将参数化的内容单独提取 +name = user.name +greeting = _("Welcome, {name}!").format(name=name) +``` + +❌ **避免:** + +```python +# 不要在 _() 中进行复杂计算 +message = _(f"User {user.name} logged in") # 这样无法提取 + +# 不要翻译动态内容 +message = _(user_input) # 用户输入不应翻译 +``` + +### 2. 格式化字符串 + +使用命名参数而非位置参数: + +✅ **推荐:** + +```python +_("Found {count} items in {directory}").format( + count=item_count, + directory=dir_name +) +``` + +❌ **避免:** + +```python +_("Found {} items in {}").format(item_count, dir_name) +# 不同语言的语序可能不同 +``` + +### 3. 复数形式 + +对于涉及数量的字符串,使用复数形式: + +✅ **推荐:** + +```python +message = _n( + "{n} file deleted", + "{n} files deleted", + count +).format(n=count) +``` + +❌ **避免:** + +```python +# 不要用条件判断构造复数形式 +if count == 1: + message = _("1 file deleted") +else: + message = _("{n} files deleted").format(n=count) +``` + +### 4. 上下文信息 + +为翻译人员提供充足的上下文: + +```python +# 在代码中添加注释,说明字符串的使用场景 +# Translators: This message is shown when the user successfully logs in +message = _("Login successful") +``` + +### 5. 避免拼接字符串 + +❌ **避免:** + +```python +# 不同语言的语序不同,拼接会导致问题 +message = _("Hello") + ", " + username + "!" +``` + +✅ **推荐:** + +```python +message = _("Hello, {name}!").format(name=username) +``` + +## 测试 + +### 测试不同语言 + +```bash +# 测试英文 +oi --locale en_US +oi --help + +# 测试中文 +oi --locale zh_CN +oi --help + +# 测试语言切换 +oi --locale zh_CN +oi --help # 应显示中文 + +oi --locale en_US +oi --help # 应显示英文 +``` + +### 验证翻译完整性 + +```bash +# 检查未翻译的字符串 +msgfmt --check --statistics src/i18n/locales/zh_CN/LC_MESSAGES/messages.po +``` + +## 常见问题 + +### 1. 翻译不生效 + +**原因:** 可能是 `.mo` 文件未编译或过时 + +**解决:** + +```bash +./scripts/tools/i18n-manager.sh compile +``` + +### 2. 新字符串未出现在 PO 文件中 + +**原因:** 未运行提取和更新命令 + +**解决:** + +```bash +./scripts/tools/i18n-manager.sh extract +./scripts/tools/i18n-manager.sh update +``` + +### 3. 系统语言检测不准确 + +**原因:** 系统环境变量未正确设置 + +**解决:** 手动设置语言 + +```bash +oi --locale zh_CN +``` + +### 4. 工具命令找不到 + +**原因:** gettext 工具未安装 + +**解决:** + +```bash +# macOS +brew install gettext + +# Ubuntu/Debian +sudo apt-get install gettext + +# Fedora/RHEL/openEuler +sudo dnf install gettext +``` + +## 开发工具 + +### 推荐的 PO 编辑器 + +- **Poedit**: (跨平台,图形界面) +- **Lokalize**: KDE 的翻译工具 +- **Gtranslator**: GNOME 的翻译工具 +- **VS Code**: 使用 gettext 扩展 + +### 在线翻译资源 + +- Google Translate API +- DeepL API +- 机器翻译仅作参考,最终需要人工校对 + +## 参考资料 + +- [Python gettext 官方文档](https://docs.python.org/3/library/gettext.html) +- GNU gettext 手册:可通过命令 `info gettext` 查看完整文档 +- PO 文件格式:可通过命令 `man msgfmt` 或 `info gettext` 查看详细说明 + +## 贡献翻译 + +欢迎贡献新的语言翻译或改进现有翻译! + +1. Fork 项目仓库 +2. 按照"添加新语言"步骤添加翻译 +3. 测试翻译效果 +4. 提交 Pull Request + +翻译时请注意: + +- 保持术语一致性 +- 尊重目标语言的习惯用法 +- 保留格式化占位符(如 `{name}`) +- 测试不同长度的翻译文本在界面上的显示效果 diff --git "a/docs/development/\345\233\275\351\231\205\345\214\226\345\277\253\351\200\237\345\205\245\351\227\250.md" "b/docs/development/\345\233\275\351\231\205\345\214\226\345\277\253\351\200\237\345\205\245\351\227\250.md" new file mode 100644 index 0000000..3d8459d --- /dev/null +++ "b/docs/development/\345\233\275\351\231\205\345\214\226\345\277\253\351\200\237\345\205\245\351\227\250.md" @@ -0,0 +1,79 @@ +# 国际化快速入门 + +## 用户使用 + +### 查看支持的语言 + +```bash +oi --help +``` + +在 "Language Settings" 部分可以看到支持的语言列表。 + +### 切换语言 + +```bash +# 切换到中文 +oi --locale zh_CN + +# 切换到英文 +oi --locale en_US +``` + +语言设置会被保存,下次启动时自动使用。 + +### 查看帮助(当前语言) + +```bash +oi --help +``` + +## 开发者使用 + +### 在代码中添加翻译 + +```python +from i18n.manager import _ + +# 简单翻译 +message = _("Hello, world!") + +# 带参数的翻译 +greeting = _("Hello, {name}!").format(name=username) +``` + +### 翻译工作流 + +```bash +# 完整流程(推荐) +./scripts/tools/i18n-manager.sh all + +# 或分步执行 +./scripts/tools/i18n-manager.sh extract # 提取字符串 +./scripts/tools/i18n-manager.sh update # 更新翻译文件 +# 编辑 .po 文件... +./scripts/tools/i18n-manager.sh compile # 编译翻译 +``` + +### 测试翻译 + +```bash +# 测试中文 +oi --locale zh_CN +oi --help + +# 测试英文 +oi --locale en_US +oi --help +``` + +## 详细文档 + +完整的开发指南请参考:[docs/development/国际化开发指南.md](./docs/development/国际化开发指南.md) + +包括: + +- 架构设计详解 +- 添加新语言的步骤 +- 最佳实践和注意事项 +- 常见问题解决方案 -- Gitee From 79bca35e78cdcd6df98d2845c0db50b62557941c Mon Sep 17 00:00:00 2001 From: Hongyu Shi Date: Fri, 17 Oct 2025 18:05:16 +0800 Subject: [PATCH 14/19] =?UTF-8?q?chore(i18n):=20=E6=B7=BB=E5=8A=A0?= =?UTF-8?q?=E8=8B=B1=E6=96=87=E5=92=8C=E7=AE=80=E4=BD=93=E4=B8=AD=E6=96=87?= =?UTF-8?q?=E7=BF=BB=E8=AF=91=E6=96=87=E4=BB=B6?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Signed-off-by: Hongyu Shi --- .../locales/en_US/LC_MESSAGES/messages.po | 172 ++++++++++++++++++ src/i18n/locales/messages.pot | 163 +++++++++++++++++ .../locales/zh_CN/LC_MESSAGES/messages.po | 170 +++++++++++++++++ 3 files changed, 505 insertions(+) create mode 100644 src/i18n/locales/en_US/LC_MESSAGES/messages.po create mode 100644 src/i18n/locales/messages.pot create mode 100644 src/i18n/locales/zh_CN/LC_MESSAGES/messages.po diff --git a/src/i18n/locales/en_US/LC_MESSAGES/messages.po b/src/i18n/locales/en_US/LC_MESSAGES/messages.po new file mode 100644 index 0000000..991491c --- /dev/null +++ b/src/i18n/locales/en_US/LC_MESSAGES/messages.po @@ -0,0 +1,172 @@ +# English translations for smart-shell package. +# Copyright (C) 2025 openEuler Intelligence Project +# This file is distributed under the same license as the smart-shell package. +# +msgid "" +msgstr "" +"Project-Id-Version: smart-shell\n" +"Report-Msgid-Bugs-To: contact@openeuler.org\n" +"POT-Creation-Date: 2025-10-17 17:39+0800\n" +"PO-Revision-Date: 2025-01-17 00:00+0000\n" +"Last-Translator: openEuler Intelligence Team\n" +"Language-Team: English\n" +"Language: en_US\n" +"MIME-Version: 1.0\n" +"Content-Type: text/plain; charset=UTF-8\n" +"Content-Transfer-Encoding: 8bit\n" +"Plural-Forms: nplurals=2; plural=(n != 1);\n" + +#: src/main.py:28 +msgid "openEuler Intelligence - Intelligent command-line tool" +msgstr "openEuler Intelligence - Intelligent command-line tool" + +#: src/main.py:29 +msgid "" +"\n" +"For more information and documentation, please visit:\n" +" https://gitee.com/openeuler/euler-copilot-shell/tree/master/docs\n" +" " +msgstr "" +"\n" +"For more information and documentation, please visit:\n" +" https://gitee.com/openeuler/euler-copilot-shell/tree/master/docs\n" +" " + +#: src/main.py:39 +msgid "General Options" +msgstr "General Options" + +#: src/main.py:40 +msgid "Show help and version information" +msgstr "Show help and version information" + +#: src/main.py:46 +msgid "Show this help message and exit" +msgstr "Show this help message and exit" + +#: src/main.py:53 +msgid "Show program version number and exit" +msgstr "Show program version number and exit" + +#: src/main.py:58 +msgid "Backend Configuration Options" +msgstr "Backend Configuration Options" + +#: src/main.py:59 +msgid "" +"For initializing and configuring openEuler Intelligence backend services" +msgstr "" +"For initializing and configuring openEuler Intelligence backend services" + +#: src/main.py:65 +msgid "" +"Initialize openEuler Intelligence backend\n" +" * Initialization requires administrator privileges and network connection" +msgstr "" +"Initialize openEuler Intelligence backend\n" +" * Initialization requires administrator privileges and network connection" + +#: src/main.py:73 +msgid "" +"Change openEuler Intelligence LLM settings (requires valid local backend " +"service)\n" +" * Configuration editing requires administrator privileges" +msgstr "" +"Change openEuler Intelligence LLM settings (requires valid local backend " +"service)\n" +" * Configuration editing requires administrator privileges" + +#: src/main.py:80 +msgid "Application Configuration Options" +msgstr "Application Configuration Options" + +#: src/main.py:81 +msgid "For configuring application frontend behavior and preferences" +msgstr "For configuring application frontend behavior and preferences" + +#: src/main.py:86 +msgid "Select default agent" +msgstr "Select default agent" + +#: src/main.py:91 +msgid "Language Settings" +msgstr "Language Settings" + +#: src/main.py:92 +msgid "For configuring application display language" +msgstr "For configuring application display language" + +#: src/main.py:100 +#, python-brace-format +msgid "Set display language (available: {locales})" +msgstr "Set display language (available: {locales})" + +#: src/main.py:105 +msgid "Log Management Options" +msgstr "Log Management Options" + +#: src/main.py:106 +msgid "For viewing and configuring log output" +msgstr "For viewing and configuring log output" + +#: src/main.py:111 +msgid "Show latest log content (up to 1000 lines)" +msgstr "Show latest log content (up to 1000 lines)" + +#: src/main.py:117 +msgid "Set log level (available: DEBUG, INFO, WARNING, ERROR)" +msgstr "Set log level (available: DEBUG, INFO, WARNING, ERROR)" + +#: src/main.py:140 +#, python-brace-format +msgid "Failed to retrieve logs: {error}\n" +msgstr "Failed to retrieve logs: {error}\n" + +#: src/main.py:147 +#, python-brace-format +msgid "Invalid log level: {level}\n" +msgstr "Invalid log level: {level}\n" + +#: src/main.py:156 +#, python-format +msgid "Log level has been set to: %s" +msgstr "Log level has been set to: %s" + +#: src/main.py:157 +msgid "This is a DEBUG level test message" +msgstr "This is a DEBUG level test message" + +#: src/main.py:158 +msgid "This is an INFO level test message" +msgstr "This is an INFO level test message" + +#: src/main.py:159 +msgid "This is a WARNING level test message" +msgstr "This is a WARNING level test message" + +#: src/main.py:160 +msgid "This is an ERROR level test message" +msgstr "This is an ERROR level test message" + +#: src/main.py:162 +#, python-brace-format +msgid "✓ Log level successfully set to: {level}\n" +msgstr "✓ Log level successfully set to: {level}\n" + +#: src/main.py:163 +msgid "✓ Logging system initialized\n" +msgstr "✓ Logging system initialized\n" + +#: src/main.py:181 +#, python-brace-format +msgid "✓ Language set to: {locale}\n" +msgstr "✓ Language set to: {locale}\n" + +#: src/main.py:183 +#, python-brace-format +msgid "✗ Unsupported language: {locale}\n" +msgstr "✗ Unsupported language: {locale}\n" + +#: src/main.py:218 +msgid "Fatal error in Intelligent Shell application" +msgstr "Fatal error in Intelligent Shell application" diff --git a/src/i18n/locales/messages.pot b/src/i18n/locales/messages.pot new file mode 100644 index 0000000..3545d8e --- /dev/null +++ b/src/i18n/locales/messages.pot @@ -0,0 +1,163 @@ +# SOME DESCRIPTIVE TITLE. +# Copyright (C) YEAR openEuler Intelligence Project +# This file is distributed under the same license as the smart-shell package. +# FIRST AUTHOR , YEAR. +# +#, fuzzy +msgid "" +msgstr "" +"Project-Id-Version: smart-shell 0.10.2\n" +"Report-Msgid-Bugs-To: contact@openeuler.org\n" +"POT-Creation-Date: 2025-10-17 17:39+0800\n" +"PO-Revision-Date: YEAR-MO-DA HO:MI+ZONE\n" +"Last-Translator: FULL NAME \n" +"Language-Team: LANGUAGE \n" +"Language: \n" +"MIME-Version: 1.0\n" +"Content-Type: text/plain; charset=UTF-8\n" +"Content-Transfer-Encoding: 8bit\n" + +#: src/main.py:28 +msgid "openEuler Intelligence - Intelligent command-line tool" +msgstr "" + +#: src/main.py:29 +msgid "" +"\n" +"For more information and documentation, please visit:\n" +" https://gitee.com/openeuler/euler-copilot-shell/tree/master/docs\n" +" " +msgstr "" + +#: src/main.py:39 +msgid "General Options" +msgstr "" + +#: src/main.py:40 +msgid "Show help and version information" +msgstr "" + +#: src/main.py:46 +msgid "Show this help message and exit" +msgstr "" + +#: src/main.py:53 +msgid "Show program version number and exit" +msgstr "" + +#: src/main.py:58 +msgid "Backend Configuration Options" +msgstr "" + +#: src/main.py:59 +msgid "" +"For initializing and configuring openEuler Intelligence backend services" +msgstr "" + +#: src/main.py:65 +msgid "" +"Initialize openEuler Intelligence backend\n" +" * Initialization requires administrator privileges and network connection" +msgstr "" + +#: src/main.py:73 +msgid "" +"Change openEuler Intelligence LLM settings (requires valid local backend " +"service)\n" +" * Configuration editing requires administrator privileges" +msgstr "" + +#: src/main.py:80 +msgid "Application Configuration Options" +msgstr "" + +#: src/main.py:81 +msgid "For configuring application frontend behavior and preferences" +msgstr "" + +#: src/main.py:86 +msgid "Select default agent" +msgstr "" + +#: src/main.py:91 +msgid "Language Settings" +msgstr "" + +#: src/main.py:92 +msgid "For configuring application display language" +msgstr "" + +#: src/main.py:100 +#, python-brace-format +msgid "Set display language (available: {locales})" +msgstr "" + +#: src/main.py:105 +msgid "Log Management Options" +msgstr "" + +#: src/main.py:106 +msgid "For viewing and configuring log output" +msgstr "" + +#: src/main.py:111 +msgid "Show latest log content (up to 1000 lines)" +msgstr "" + +#: src/main.py:117 +msgid "Set log level (available: DEBUG, INFO, WARNING, ERROR)" +msgstr "" + +#: src/main.py:140 +#, python-brace-format +msgid "Failed to retrieve logs: {error}\n" +msgstr "" + +#: src/main.py:147 +#, python-brace-format +msgid "Invalid log level: {level}\n" +msgstr "" + +#: src/main.py:156 +#, python-format +msgid "Log level has been set to: %s" +msgstr "" + +#: src/main.py:157 +msgid "This is a DEBUG level test message" +msgstr "" + +#: src/main.py:158 +msgid "This is an INFO level test message" +msgstr "" + +#: src/main.py:159 +msgid "This is a WARNING level test message" +msgstr "" + +#: src/main.py:160 +msgid "This is an ERROR level test message" +msgstr "" + +#: src/main.py:162 +#, python-brace-format +msgid "✓ Log level successfully set to: {level}\n" +msgstr "" + +#: src/main.py:163 +msgid "✓ Logging system initialized\n" +msgstr "" + +#: src/main.py:181 +#, python-brace-format +msgid "✓ Language set to: {locale}\n" +msgstr "" + +#: src/main.py:183 +#, python-brace-format +msgid "✗ Unsupported language: {locale}\n" +msgstr "" + +#: src/main.py:218 +msgid "Fatal error in Intelligent Shell application" +msgstr "" diff --git a/src/i18n/locales/zh_CN/LC_MESSAGES/messages.po b/src/i18n/locales/zh_CN/LC_MESSAGES/messages.po new file mode 100644 index 0000000..a52bf85 --- /dev/null +++ b/src/i18n/locales/zh_CN/LC_MESSAGES/messages.po @@ -0,0 +1,170 @@ +# Simplified Chinese translations for smart-shell package. +# Copyright (C) 2025 openEuler Intelligence Project +# This file is distributed under the same license as the smart-shell package. +# +msgid "" +msgstr "" +"Project-Id-Version: smart-shell\n" +"Report-Msgid-Bugs-To: contact@openeuler.org\n" +"POT-Creation-Date: 2025-10-17 17:39+0800\n" +"PO-Revision-Date: 2025-01-17 00:00+0000\n" +"Last-Translator: openEuler Intelligence Team\n" +"Language-Team: Chinese (Simplified)\n" +"Language: zh_CN\n" +"MIME-Version: 1.0\n" +"Content-Type: text/plain; charset=UTF-8\n" +"Content-Transfer-Encoding: 8bit\n" +"Plural-Forms: nplurals=1; plural=0;\n" + +#: src/main.py:28 +msgid "openEuler Intelligence - Intelligent command-line tool" +msgstr "openEuler Intelligence - 智能命令行工具" + +#: src/main.py:29 +msgid "" +"\n" +"For more information and documentation, please visit:\n" +" https://gitee.com/openeuler/euler-copilot-shell/tree/master/docs\n" +" " +msgstr "" +"\n" +"更多信息和使用文档请访问:\n" +" https://gitee.com/openeuler/euler-copilot-shell/tree/master/docs\n" +" " + +#: src/main.py:39 +msgid "General Options" +msgstr "通用选项" + +#: src/main.py:40 +msgid "Show help and version information" +msgstr "显示帮助信息和版本信息" + +#: src/main.py:46 +msgid "Show this help message and exit" +msgstr "显示此帮助信息并退出" + +#: src/main.py:53 +msgid "Show program version number and exit" +msgstr "显示程序版本号并退出" + +#: src/main.py:58 +msgid "Backend Configuration Options" +msgstr "后端配置选项" + +#: src/main.py:59 +msgid "" +"For initializing and configuring openEuler Intelligence backend services" +msgstr "用于初始化和配置 openEuler Intelligence 后端服务" + +#: src/main.py:65 +msgid "" +"Initialize openEuler Intelligence backend\n" +" * Initialization requires administrator privileges and network connection" +msgstr "" +"初始化 openEuler Intelligence 后端\n" +" * 初始化操作需要管理员权限和网络连接" + +#: src/main.py:73 +msgid "" +"Change openEuler Intelligence LLM settings (requires valid local backend " +"service)\n" +" * Configuration editing requires administrator privileges" +msgstr "" +"更改 openEuler Intelligence 大模型设置(需要有效的本地后端服务)\n" +" * 配置编辑操作需要管理员权限" + +#: src/main.py:80 +msgid "Application Configuration Options" +msgstr "应用配置选项" + +#: src/main.py:81 +msgid "For configuring application frontend behavior and preferences" +msgstr "用于配置应用前端行为和偏好设置" + +#: src/main.py:86 +msgid "Select default agent" +msgstr "选择默认智能体" + +#: src/main.py:91 +msgid "Language Settings" +msgstr "语言设置" + +#: src/main.py:92 +msgid "For configuring application display language" +msgstr "用于配置应用显示语言" + +#: src/main.py:100 +#, python-brace-format +msgid "Set display language (available: {locales})" +msgstr "设置显示语言 (可选: {locales})" + +#: src/main.py:105 +msgid "Log Management Options" +msgstr "日志管理选项" + +#: src/main.py:106 +msgid "For viewing and configuring log output" +msgstr "用于查看和配置日志输出" + +#: src/main.py:111 +msgid "Show latest log content (up to 1000 lines)" +msgstr "显示最新的日志内容(最多1000行)" + +#: src/main.py:117 +msgid "Set log level (available: DEBUG, INFO, WARNING, ERROR)" +msgstr "设置日志级别 (可选: DEBUG, INFO, WARNING, ERROR)" + +#: src/main.py:140 +#, python-brace-format +msgid "Failed to retrieve logs: {error}\n" +msgstr "获取日志失败: {error}\n" + +#: src/main.py:147 +#, python-brace-format +msgid "Invalid log level: {level}\n" +msgstr "无效的日志级别: {level}\n" + +#: src/main.py:156 +#, python-format +msgid "Log level has been set to: %s" +msgstr "日志级别已设置为: %s" + +#: src/main.py:157 +msgid "This is a DEBUG level test message" +msgstr "这是一条 DEBUG 级别的测试消息" + +#: src/main.py:158 +msgid "This is an INFO level test message" +msgstr "这是一条 INFO 级别的测试消息" + +#: src/main.py:159 +msgid "This is a WARNING level test message" +msgstr "这是一条 WARNING 级别的测试消息" + +#: src/main.py:160 +msgid "This is an ERROR level test message" +msgstr "这是一条 ERROR 级别的测试消息" + +#: src/main.py:162 +#, python-brace-format +msgid "✓ Log level successfully set to: {level}\n" +msgstr "✓ 日志级别已成功设置为: {level}\n" + +#: src/main.py:163 +msgid "✓ Logging system initialized\n" +msgstr "✓ 日志系统初始化完成\n" + +#: src/main.py:181 +#, python-brace-format +msgid "✓ Language set to: {locale}\n" +msgstr "✓ 语言已设置为: {locale}\n" + +#: src/main.py:183 +#, python-brace-format +msgid "✗ Unsupported language: {locale}\n" +msgstr "✗ 不支持的语言: {locale}\n" + +#: src/main.py:218 +msgid "Fatal error in Intelligent Shell application" +msgstr "智能 Shell 应用发生致命错误" -- Gitee From be0f94aec51dedecc4a9c39ce7a66cc488232c0c Mon Sep 17 00:00:00 2001 From: Hongyu Shi Date: Tue, 21 Oct 2025 10:10:43 +0800 Subject: [PATCH 15/19] =?UTF-8?q?chore(i18n):=20=E6=B7=BB=E5=8A=A0=20MCP?= =?UTF-8?q?=20=E4=BA=A4=E4=BA=92=E7=BB=84=E4=BB=B6=20&=20=E8=AE=BE?= =?UTF-8?q?=E7=BD=AE?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Signed-off-by: Hongyu Shi --- scripts/tools/i18n-manager.sh | 2 +- src/app/dialogs/agent.py | 22 +-- src/app/dialogs/common.py | 8 +- src/app/mcp_widgets.py | 28 ++-- src/app/settings.py | 49 +++--- .../locales/en_US/LC_MESSAGES/messages.po | 149 ++++++++++++++++- src/i18n/locales/messages.pot | 147 ++++++++++++++++- .../locales/zh_CN/LC_MESSAGES/messages.po | 151 +++++++++++++++++- 8 files changed, 492 insertions(+), 64 deletions(-) diff --git a/scripts/tools/i18n-manager.sh b/scripts/tools/i18n-manager.sh index 0aba948..ef38b42 100755 --- a/scripts/tools/i18n-manager.sh +++ b/scripts/tools/i18n-manager.sh @@ -92,7 +92,7 @@ extract() { --keyword=_n:1,2 \ --output="$POT_FILE" \ --from-code=UTF-8 \ - --package-name=smart-shell \ + --package-name=oi-cli \ --package-version=0.10.2 \ --msgid-bugs-address=contact@openeuler.org \ --copyright-holder="openEuler Intelligence Project" \ diff --git a/src/app/dialogs/agent.py b/src/app/dialogs/agent.py index c000d27..b1cd115 100644 --- a/src/app/dialogs/agent.py +++ b/src/app/dialogs/agent.py @@ -14,6 +14,8 @@ from textual.containers import Container from textual.screen import ModalScreen from textual.widgets import Label, Static +from i18n.manager import _ + class BackendRequiredDialog(ModalScreen): """后端要求提示对话框""" @@ -22,9 +24,9 @@ class BackendRequiredDialog(ModalScreen): """构建后端要求提示对话框""" yield Container( Container( - Label("智能体功能提示", id="backend-dialog-title"), - Label("请选择 openEuler Intelligence 后端来使用智能体功能", id="backend-dialog-text"), - Label("按任意键关闭", id="backend-dialog-help"), + Label(_("智能体功能提示"), id="backend-dialog-title"), + Label(_("请选择 openEuler Intelligence 后端来使用智能体功能"), id="backend-dialog-text"), + Label(_("按任意键关闭"), id="backend-dialog-help"), id="backend-dialog", ), id="backend-dialog-screen", @@ -55,7 +57,7 @@ class AgentSelectionDialog(ModalScreen): """ super().__init__() - self.current_agent = current_agent or ("", "智能问答") + self.current_agent = current_agent or ("", _("智能问答")) self.callback = callback # 重新排序智能体列表:智能问答永远第一,当前智能体(如果不是智能问答)排第二 @@ -108,9 +110,9 @@ class AgentSelectionDialog(ModalScreen): yield Container( Container( - Label("OS 智能助手", id="agent-dialog-title"), + Label(_("OS 智能助手"), id="agent-dialog-title"), agent_content, - Label("使用上下键选择,回车确认,ESC取消 | ✓ 表示当前选中", id="agent-dialog-help"), + Label(_("使用上下键选择,回车确认,ESC取消 | ✓ 表示当前选中"), id="agent-dialog-help"), id="agent-dialog", ), id="agent-dialog-screen", @@ -125,7 +127,7 @@ class AgentSelectionDialog(ModalScreen): if self.agents and 0 <= self.selected_index < len(self.agents): selected_agent = self.agents[self.selected_index] else: - selected_agent = ("", "智能问答") + selected_agent = ("", _("智能问答")) self.callback(selected_agent) self.app.pop_screen() elif event.key == "up" and self.selected_index > 0: @@ -180,7 +182,7 @@ class AgentSelectionDialog(ModalScreen): # 如果没有智能体,添加默认选项 if not agent_text_lines: - agent_text_lines.append("[white on green]► ✓ 智能问答[/white on green]") + agent_text_lines.append(f"[white on green]► ✓ {_('智能问答')}[/white on green]") # 更新 Static 组件的内容 try: @@ -200,10 +202,10 @@ class AgentSelectionDialog(ModalScreen): 3. 其他智能体保持原有顺序 """ if not agents: - return [("", "智能问答")] + return [("", _("智能问答"))] # 查找智能问答和当前智能体 - default_qa = ("", "智能问答") + default_qa = ("", _("智能问答")) current_agent = self.current_agent reordered = [] diff --git a/src/app/dialogs/common.py b/src/app/dialogs/common.py index 9958917..99049e2 100644 --- a/src/app/dialogs/common.py +++ b/src/app/dialogs/common.py @@ -12,6 +12,8 @@ from textual.containers import Container, Horizontal from textual.screen import ModalScreen from textual.widgets import Button, Label +from i18n.manager import _ + class ExitDialog(ModalScreen): """退出确认对话框""" @@ -20,10 +22,10 @@ class ExitDialog(ModalScreen): """构建退出确认对话框""" yield Container( Container( - Label("确认退出吗?", id="dialog-text"), + Label(_("确认退出吗?"), id="dialog-text"), Horizontal( - Button("取消", classes="dialog-button", id="cancel"), - Button("确认", classes="dialog-button", id="confirm"), + Button(_("取消"), classes="dialog-button", id="cancel"), + Button(_("确认"), classes="dialog-button", id="confirm"), id="dialog-buttons", ), id="exit-dialog", diff --git a/src/app/mcp_widgets.py b/src/app/mcp_widgets.py index 12cdd38..7fb5d27 100644 --- a/src/app/mcp_widgets.py +++ b/src/app/mcp_widgets.py @@ -10,6 +10,8 @@ from textual.containers import Container, Horizontal, Vertical from textual.message import Message from textual.widgets import Button, Input, Static +from i18n.manager import _ + if TYPE_CHECKING: from textual.app import ComposeResult @@ -42,14 +44,14 @@ class MCPConfirmWidget(Container): step_name = self.event.get_step_name() content = self.event.get_content() risk = content.get("risk", "unknown") - reason = content.get("reason", "需要用户确认是否执行此工具") + reason = content.get("reason", _("需要用户确认是否执行此工具")) # 风险级别文本和图标 risk_info = { - "low": ("🟢", "低风险"), - "medium": ("🟡", "中等风险"), - "high": ("🔴", "高风险"), - }.get(risk, ("⚪", "未知风险")) + "low": ("🟢", _("低风险")), + "medium": ("🟡", _("中等风险")), + "high": ("🔴", _("高风险")), + }.get(risk, ("⚪", _("未知风险"))) risk_icon, risk_text = risk_info @@ -76,8 +78,8 @@ class MCPConfirmWidget(Container): ) # 确保按钮始终显示 with Horizontal(classes="confirm-buttons"): - yield Button("✓ 确认", variant="success", id="mcp-confirm-yes") - yield Button("✗ 取消", variant="error", id="mcp-confirm-no") + yield Button(_("✓ 确认"), variant="success", id="mcp-confirm-yes") + yield Button(_("✗ 取消"), variant="error", id="mcp-confirm-no") @on(Button.Pressed, "#mcp-confirm-yes") def confirm_execution(self) -> None: @@ -159,12 +161,12 @@ class MCPParameterWidget(Container): """构建参数输入界面""" step_name = self.event.get_step_name() content = self.event.get_content() - message = content.get("message", "需要补充参数") + message = content.get("message", _("需要补充参数")) params = content.get("params", {}) with Vertical(classes="mcp-content"): # 紧凑的参数输入标题 - yield Static("📝 参数输入", classes="param-header", markup=False) + yield Static(_("📝 参数输入"), classes="param-header", markup=False) yield Static(f"🔧 {step_name}", classes="param-tool", markup=False) # 显示说明文字,超长时显示省略号 if len(message) > MAX_DISPLAY_LENGTH: @@ -176,7 +178,7 @@ class MCPParameterWidget(Container): for param_name, param_value in params.items(): if param_value is None or param_value == "": param_input = Input( - placeholder=f"请输入 {param_name}", + placeholder=_("请输入 {param_name}").format(param_name=param_name), id=f"param_{param_name}", classes="param-input-compact", ) @@ -186,7 +188,7 @@ class MCPParameterWidget(Container): # 简化的补充说明输入 if params: # 只有在有其他参数时才显示补充说明 description_input = Input( - placeholder="补充说明(可选)", + placeholder=_("补充说明(可选)"), id="param_description", classes="param-input-compact", ) @@ -195,8 +197,8 @@ class MCPParameterWidget(Container): # 紧凑的按钮行 with Horizontal(classes="param-buttons"): - yield Button("✓ 提交", variant="success", id="mcp-param-submit") - yield Button("✗ 取消", variant="error", id="mcp-param-cancel") + yield Button(_("✓ 提交"), variant="success", id="mcp-param-submit") + yield Button(_("✗ 取消"), variant="error", id="mcp-param-cancel") @on(Button.Pressed, "#mcp-param-submit") def submit_parameters(self) -> None: diff --git a/src/app/settings.py b/src/app/settings.py index caf448e..4d2ec66 100644 --- a/src/app/settings.py +++ b/src/app/settings.py @@ -15,6 +15,7 @@ from app.dialogs import ExitDialog from backend.hermes import HermesChatClient from backend.openai import OpenAIClient from config import Backend, ConfigManager +from i18n.manager import _ from log import get_logger from tool.validators import APIValidator, validate_oi_connection @@ -59,10 +60,10 @@ class SettingsScreen(ModalScreen): """构建设置页面""" yield Container( Container( - Label("设置", id="settings-title"), + Label(_("设置"), id="settings-title"), # 后端选择 Horizontal( - Label("后端:", classes="settings-label"), + Label(_("后端:"), classes="settings-label"), Button( f"{self.backend.get_display_name()}", id="backend-btn", @@ -72,7 +73,7 @@ class SettingsScreen(ModalScreen): ), # Base URL 输入 Horizontal( - Label("Base URL:", classes="settings-label"), + Label(_("Base URL:"), classes="settings-label"), Input( value=self.config_manager.get_base_url() if self.backend == Backend.OPENAI @@ -84,14 +85,14 @@ class SettingsScreen(ModalScreen): ), # API Key 输入 Horizontal( - Label("API Key:", classes="settings-label"), + Label(_("API Key:"), classes="settings-label"), Input( value=self.config_manager.get_api_key() if self.backend == Backend.OPENAI else self.config_manager.get_eulerintelli_key(), classes="settings-input", id="api-key", - placeholder="API 访问密钥,可选", + placeholder=_("API 访问密钥,可选"), ), classes="settings-option", ), @@ -99,12 +100,12 @@ class SettingsScreen(ModalScreen): *( [ Horizontal( - Label("模型:", classes="settings-label"), + Label(_("模型:"), classes="settings-label"), Input( value=self.selected_model, classes="settings-input", id="model-input", - placeholder="模型名称,可选", + placeholder=_("模型名称,可选"), ), id="model-section", classes="settings-option", @@ -113,9 +114,9 @@ class SettingsScreen(ModalScreen): if self.backend == Backend.OPENAI else [ Horizontal( - Label("MCP 工具授权:", classes="settings-label"), + Label(_("MCP 工具授权:"), classes="settings-label"), Button( - "自动执行" if self.auto_execute_status else "手动确认", + _("自动执行") if self.auto_execute_status else _("手动确认"), id="mcp-btn", classes="settings-button", disabled=not self.mcp_status_loaded, @@ -129,8 +130,8 @@ class SettingsScreen(ModalScreen): Static("", id="spacer"), # 操作按钮 Horizontal( - Button("保存", id="save-btn", variant="primary"), - Button("取消", id="cancel-btn", variant="default"), + Button(_("保存"), id="save-btn", variant="primary"), + Button(_("取消"), id="cancel-btn", variant="default"), id="action-buttons", classes="settings-option", ), @@ -170,14 +171,14 @@ class SettingsScreen(ModalScreen): # 更新 MCP 按钮文本和状态 mcp_btn = self.query_one("#mcp-btn", Button) - mcp_btn.label = "自动执行" if self.auto_execute_status else "手动确认" + mcp_btn.label = _("自动执行") if self.auto_execute_status else _("手动确认") mcp_btn.disabled = not self.mcp_status_loaded except (OSError, ValueError, RuntimeError): self.auto_execute_status = False self.mcp_status_loaded = False mcp_btn = self.query_one("#mcp-btn", Button) - mcp_btn.label = "手动确认" + mcp_btn.label = _("手动确认") mcp_btn.disabled = True @on(Input.Changed, "#base-url, #api-key, #model-input") @@ -235,12 +236,12 @@ class SettingsScreen(ModalScreen): container = self.query_one("#settings-container") spacer = self.query_one("#spacer") model_section = Horizontal( - Label("模型:", classes="settings-label"), + Label(_("模型:"), classes="settings-label"), Input( value=self.selected_model, classes="settings-input", id="model-input", - placeholder="模型名称,可选", + placeholder=_("模型名称,可选"), ), id="model-section", classes="settings-option", @@ -268,9 +269,9 @@ class SettingsScreen(ModalScreen): container = self.query_one("#settings-container") spacer = self.query_one("#spacer") mcp_section = Horizontal( - Label("MCP 工具授权:", classes="settings-label"), + Label(_("MCP 工具授权:"), classes="settings-label"), Button( - "自动执行" if self.auto_execute_status else "手动确认", + _("自动执行") if self.auto_execute_status else _("手动确认"), id="mcp-btn", classes="settings-button", disabled=not self.mcp_status_loaded, @@ -433,7 +434,7 @@ class SettingsScreen(ModalScreen): if not base_url: self.is_validated = False - self.validation_message = "Base URL 不能为空" + self.validation_message = _("Base URL 不能为空") self._update_save_button_state() return @@ -447,7 +448,7 @@ class SettingsScreen(ModalScreen): model = self.selected_model # 验证 OpenAI 配置(模型和 API Key 都可以为空) - valid, message, _ = await self.validator.validate_llm_config( + valid, message, _additional_info = await self.validator.validate_llm_config( endpoint=base_url, api_key=api_key, model=model, @@ -530,7 +531,7 @@ class SettingsScreen(ModalScreen): # 先禁用按钮防止重复点击 mcp_btn = self.query_one("#mcp-btn", Button) mcp_btn.disabled = True - mcp_btn.label = "切换中..." + mcp_btn.label = _("切换中...") # 根据当前状态调用相应的方法 if self.auto_execute_status: @@ -545,13 +546,11 @@ class SettingsScreen(ModalScreen): self.auto_execute_status = await self.llm_client.get_auto_execute_status() # type: ignore[attr-defined] # 更新按钮状态 - mcp_btn.label = "自动执行" if self.auto_execute_status else "手动确认" + mcp_btn.label = _("自动执行") if self.auto_execute_status else _("手动确认") mcp_btn.disabled = False - except (OSError, ValueError, RuntimeError) as e: + except (OSError, ValueError, RuntimeError): # 发生错误时恢复按钮状态 mcp_btn = self.query_one("#mcp-btn", Button) - mcp_btn.label = "自动执行" if self.auto_execute_status else "手动确认" + mcp_btn.label = _("自动执行") if self.auto_execute_status else _("手动确认") mcp_btn.disabled = False - # 可以考虑显示错误消息 - self.notify(f"切换 MCP 工具授权模式失败: {e!s}", severity="error") diff --git a/src/i18n/locales/en_US/LC_MESSAGES/messages.po b/src/i18n/locales/en_US/LC_MESSAGES/messages.po index 61e8480..f306571 100644 --- a/src/i18n/locales/en_US/LC_MESSAGES/messages.po +++ b/src/i18n/locales/en_US/LC_MESSAGES/messages.po @@ -1,12 +1,12 @@ -# English translations for smart-shell package. +# English translations for oi-cli package. # Copyright (C) 2025 openEuler Intelligence Project -# This file is distributed under the same license as the smart-shell package. +# This file is distributed under the same license as the oi-cli package. # msgid "" msgstr "" -"Project-Id-Version: smart-shell\n" +"Project-Id-Version: oi-cli\n" "Report-Msgid-Bugs-To: contact@openeuler.org\n" -"POT-Creation-Date: 2025-10-20 17:44+0800\n" +"POT-Creation-Date: 2025-10-21 09:54+0800\n" "PO-Revision-Date: 2025-10-20 19:28+0800\n" "Last-Translator: openEuler Intelligence Team\n" "Language-Team: English\n" @@ -17,6 +17,113 @@ msgstr "" "Plural-Forms: nplurals=2; plural=(n != 1);\n" "X-Generator: Poedit 3.8\n" +#: src/app/mcp_widgets.py:47 +msgid "需要用户确认是否执行此工具" +msgstr "User confirmation required to execute this tool" + +#: src/app/mcp_widgets.py:51 +msgid "低风险" +msgstr "Low risk" + +#: src/app/mcp_widgets.py:52 +msgid "中等风险" +msgstr "Medium risk" + +#: src/app/mcp_widgets.py:53 +msgid "高风险" +msgstr "High risk" + +#: src/app/mcp_widgets.py:54 +msgid "未知风险" +msgstr "Unknown risk" + +#: src/app/mcp_widgets.py:81 +msgid "✓ 确认" +msgstr "✓ Confirm" + +#: src/app/mcp_widgets.py:82 src/app/mcp_widgets.py:201 +msgid "✗ 取消" +msgstr "✗ Cancel" + +#: src/app/mcp_widgets.py:164 +msgid "需要补充参数" +msgstr "Parameters required" + +#: src/app/mcp_widgets.py:169 +msgid "📝 参数输入" +msgstr "📝 Parameter Input" + +#: src/app/mcp_widgets.py:181 +#, python-brace-format +msgid "请输入 {param_name}" +msgstr "Please enter {param_name}" + +#: src/app/mcp_widgets.py:191 +msgid "补充说明(可选)" +msgstr "Additional notes (optional)" + +#: src/app/mcp_widgets.py:200 +msgid "✓ 提交" +msgstr "✓ Submit" + +#: src/app/settings.py:63 +msgid "设置" +msgstr "Settings" + +#: src/app/settings.py:66 +msgid "后端:" +msgstr "Backend:" + +#: src/app/settings.py:76 +msgid "Base URL:" +msgstr "Base URL:" + +#: src/app/settings.py:88 +msgid "API Key:" +msgstr "API Key:" + +#: src/app/settings.py:95 +msgid "API 访问密钥,可选" +msgstr "API access key, optional" + +#: src/app/settings.py:103 src/app/settings.py:239 +msgid "模型:" +msgstr "Model:" + +#: src/app/settings.py:108 src/app/settings.py:244 +msgid "模型名称,可选" +msgstr "Model name, optional" + +#: src/app/settings.py:117 src/app/settings.py:272 +msgid "MCP 工具授权:" +msgstr "MCP Tool Authorization:" + +#: src/app/settings.py:119 src/app/settings.py:174 src/app/settings.py:274 +#: src/app/settings.py:549 src/app/settings.py:555 +msgid "自动执行" +msgstr "Auto Execute" + +#: src/app/settings.py:119 src/app/settings.py:174 src/app/settings.py:181 +#: src/app/settings.py:274 src/app/settings.py:549 src/app/settings.py:555 +msgid "手动确认" +msgstr "Manual Confirm" + +#: src/app/settings.py:133 +msgid "保存" +msgstr "Save" + +#: src/app/settings.py:134 src/app/dialogs/common.py:27 +msgid "取消" +msgstr "Cancel" + +#: src/app/settings.py:437 +msgid "Base URL 不能为空" +msgstr "Base URL cannot be empty" + +#: src/app/settings.py:534 +msgid "切换中..." +msgstr "Switching..." + #: src/app/tui.py:217 msgid "Enter command or question..." msgstr "Enter question or command..." @@ -137,6 +244,40 @@ msgstr "Backend configuration validation failed, please check and modify" msgid "Configuration Error" msgstr "Configuration Error" +#: src/app/dialogs/common.py:25 +msgid "确认退出吗?" +msgstr "Are you sure you want to exit?" + +#: src/app/dialogs/common.py:28 +msgid "确认" +msgstr "Confirm" + +#: src/app/dialogs/agent.py:27 +msgid "智能体功能提示" +msgstr "Agent Feature Notification" + +#: src/app/dialogs/agent.py:28 +msgid "请选择 openEuler Intelligence 后端来使用智能体功能" +msgstr "Please select openEuler Intelligence backend to use agent features" + +#: src/app/dialogs/agent.py:29 +msgid "按任意键关闭" +msgstr "Press any key to close" + +#: src/app/dialogs/agent.py:60 src/app/dialogs/agent.py:130 +#: src/app/dialogs/agent.py:185 src/app/dialogs/agent.py:205 +#: src/app/dialogs/agent.py:208 +msgid "智能问答" +msgstr "Smart Chat" + +#: src/app/dialogs/agent.py:113 +msgid "OS 智能助手" +msgstr "OS Smart Assistant" + +#: src/app/dialogs/agent.py:115 +msgid "使用上下键选择,回车确认,ESC取消 | ✓ 表示当前选中" +msgstr "Use arrow keys to select, Enter to confirm, ESC to cancel | ✓ indicates current selection" + #: src/main.py:28 msgid "openEuler Intelligence - Intelligent command-line tool" msgstr "openEuler Intelligence - Intelligent command-line tool" diff --git a/src/i18n/locales/messages.pot b/src/i18n/locales/messages.pot index a56ed25..19f1cd4 100644 --- a/src/i18n/locales/messages.pot +++ b/src/i18n/locales/messages.pot @@ -1,14 +1,14 @@ # SOME DESCRIPTIVE TITLE. # Copyright (C) YEAR openEuler Intelligence Project -# This file is distributed under the same license as the smart-shell package. +# This file is distributed under the same license as the oi-cli package. # FIRST AUTHOR , YEAR. # #, fuzzy msgid "" msgstr "" -"Project-Id-Version: smart-shell 0.10.2\n" +"Project-Id-Version: oi-cli 0.10.2\n" "Report-Msgid-Bugs-To: contact@openeuler.org\n" -"POT-Creation-Date: 2025-10-20 17:44+0800\n" +"POT-Creation-Date: 2025-10-21 09:54+0800\n" "PO-Revision-Date: YEAR-MO-DA HO:MI+ZONE\n" "Last-Translator: FULL NAME \n" "Language-Team: LANGUAGE \n" @@ -17,6 +17,113 @@ msgstr "" "Content-Type: text/plain; charset=UTF-8\n" "Content-Transfer-Encoding: 8bit\n" +#: src/app/mcp_widgets.py:47 +msgid "需要用户确认是否执行此工具" +msgstr "" + +#: src/app/mcp_widgets.py:51 +msgid "低风险" +msgstr "" + +#: src/app/mcp_widgets.py:52 +msgid "中等风险" +msgstr "" + +#: src/app/mcp_widgets.py:53 +msgid "高风险" +msgstr "" + +#: src/app/mcp_widgets.py:54 +msgid "未知风险" +msgstr "" + +#: src/app/mcp_widgets.py:81 +msgid "✓ 确认" +msgstr "" + +#: src/app/mcp_widgets.py:82 src/app/mcp_widgets.py:201 +msgid "✗ 取消" +msgstr "" + +#: src/app/mcp_widgets.py:164 +msgid "需要补充参数" +msgstr "" + +#: src/app/mcp_widgets.py:169 +msgid "📝 参数输入" +msgstr "" + +#: src/app/mcp_widgets.py:181 +#, python-brace-format +msgid "请输入 {param_name}" +msgstr "" + +#: src/app/mcp_widgets.py:191 +msgid "补充说明(可选)" +msgstr "" + +#: src/app/mcp_widgets.py:200 +msgid "✓ 提交" +msgstr "" + +#: src/app/settings.py:63 +msgid "设置" +msgstr "" + +#: src/app/settings.py:66 +msgid "后端:" +msgstr "" + +#: src/app/settings.py:76 +msgid "Base URL:" +msgstr "" + +#: src/app/settings.py:88 +msgid "API Key:" +msgstr "" + +#: src/app/settings.py:95 +msgid "API 访问密钥,可选" +msgstr "" + +#: src/app/settings.py:103 src/app/settings.py:239 +msgid "模型:" +msgstr "" + +#: src/app/settings.py:108 src/app/settings.py:244 +msgid "模型名称,可选" +msgstr "" + +#: src/app/settings.py:117 src/app/settings.py:272 +msgid "MCP 工具授权:" +msgstr "" + +#: src/app/settings.py:119 src/app/settings.py:174 src/app/settings.py:274 +#: src/app/settings.py:549 src/app/settings.py:555 +msgid "自动执行" +msgstr "" + +#: src/app/settings.py:119 src/app/settings.py:174 src/app/settings.py:181 +#: src/app/settings.py:274 src/app/settings.py:549 src/app/settings.py:555 +msgid "手动确认" +msgstr "" + +#: src/app/settings.py:133 +msgid "保存" +msgstr "" + +#: src/app/settings.py:134 src/app/dialogs/common.py:27 +msgid "取消" +msgstr "" + +#: src/app/settings.py:437 +msgid "Base URL 不能为空" +msgstr "" + +#: src/app/settings.py:534 +msgid "切换中..." +msgstr "" + #: src/app/tui.py:217 msgid "Enter command or question..." msgstr "" @@ -136,6 +243,40 @@ msgstr "" msgid "Configuration Error" msgstr "" +#: src/app/dialogs/common.py:25 +msgid "确认退出吗?" +msgstr "" + +#: src/app/dialogs/common.py:28 +msgid "确认" +msgstr "" + +#: src/app/dialogs/agent.py:27 +msgid "智能体功能提示" +msgstr "" + +#: src/app/dialogs/agent.py:28 +msgid "请选择 openEuler Intelligence 后端来使用智能体功能" +msgstr "" + +#: src/app/dialogs/agent.py:29 +msgid "按任意键关闭" +msgstr "" + +#: src/app/dialogs/agent.py:60 src/app/dialogs/agent.py:130 +#: src/app/dialogs/agent.py:185 src/app/dialogs/agent.py:205 +#: src/app/dialogs/agent.py:208 +msgid "智能问答" +msgstr "" + +#: src/app/dialogs/agent.py:113 +msgid "OS 智能助手" +msgstr "" + +#: src/app/dialogs/agent.py:115 +msgid "使用上下键选择,回车确认,ESC取消 | ✓ 表示当前选中" +msgstr "" + #: src/main.py:28 msgid "openEuler Intelligence - Intelligent command-line tool" msgstr "" diff --git a/src/i18n/locales/zh_CN/LC_MESSAGES/messages.po b/src/i18n/locales/zh_CN/LC_MESSAGES/messages.po index 0a1900c..ea8ef32 100644 --- a/src/i18n/locales/zh_CN/LC_MESSAGES/messages.po +++ b/src/i18n/locales/zh_CN/LC_MESSAGES/messages.po @@ -1,13 +1,13 @@ -# Simplified Chinese translations for smart-shell package. +# Simplified Chinese translations for oi-cli package. # Copyright (C) 2025 openEuler Intelligence Project -# This file is distributed under the same license as the smart-shell package. +# This file is distributed under the same license as the oi-cli package. # msgid "" msgstr "" -"Project-Id-Version: smart-shell\n" +"Project-Id-Version: oi-cli\n" "Report-Msgid-Bugs-To: contact@openeuler.org\n" -"POT-Creation-Date: 2025-10-20 17:44+0800\n" -"PO-Revision-Date: 2025-10-20 19:12+0800\n" +"POT-Creation-Date: 2025-10-21 09:54+0800\n" +"PO-Revision-Date: 2025-10-21 09:40+0800\n" "Last-Translator: openEuler Intelligence Team\n" "Language-Team: Chinese (Simplified)\n" "Language: zh_CN\n" @@ -17,6 +17,113 @@ msgstr "" "Plural-Forms: nplurals=1; plural=0;\n" "X-Generator: Poedit 3.8\n" +#: src/app/mcp_widgets.py:47 +msgid "需要用户确认是否执行此工具" +msgstr "需要用户确认是否执行此工具" + +#: src/app/mcp_widgets.py:51 +msgid "低风险" +msgstr "低风险" + +#: src/app/mcp_widgets.py:52 +msgid "中等风险" +msgstr "中等风险" + +#: src/app/mcp_widgets.py:53 +msgid "高风险" +msgstr "高风险" + +#: src/app/mcp_widgets.py:54 +msgid "未知风险" +msgstr "未知风险" + +#: src/app/mcp_widgets.py:81 +msgid "✓ 确认" +msgstr "✓ 确认" + +#: src/app/mcp_widgets.py:82 src/app/mcp_widgets.py:201 +msgid "✗ 取消" +msgstr "✗ 取消" + +#: src/app/mcp_widgets.py:164 +msgid "需要补充参数" +msgstr "需要补充参数" + +#: src/app/mcp_widgets.py:169 +msgid "📝 参数输入" +msgstr "📝 参数输入" + +#: src/app/mcp_widgets.py:181 +#, python-brace-format +msgid "请输入 {param_name}" +msgstr "请输入 {param_name}" + +#: src/app/mcp_widgets.py:191 +msgid "补充说明(可选)" +msgstr "补充说明(可选)" + +#: src/app/mcp_widgets.py:200 +msgid "✓ 提交" +msgstr "✓ 提交" + +#: src/app/settings.py:63 +msgid "设置" +msgstr "设置" + +#: src/app/settings.py:66 +msgid "后端:" +msgstr "后端:" + +#: src/app/settings.py:76 +msgid "Base URL:" +msgstr "基础 URL:" + +#: src/app/settings.py:88 +msgid "API Key:" +msgstr "API 密钥:" + +#: src/app/settings.py:95 +msgid "API 访问密钥,可选" +msgstr "API 访问密钥,可选" + +#: src/app/settings.py:103 src/app/settings.py:239 +msgid "模型:" +msgstr "模型:" + +#: src/app/settings.py:108 src/app/settings.py:244 +msgid "模型名称,可选" +msgstr "模型名称,可选" + +#: src/app/settings.py:117 src/app/settings.py:272 +msgid "MCP 工具授权:" +msgstr "MCP 工具授权:" + +#: src/app/settings.py:119 src/app/settings.py:174 src/app/settings.py:274 +#: src/app/settings.py:549 src/app/settings.py:555 +msgid "自动执行" +msgstr "自动执行" + +#: src/app/settings.py:119 src/app/settings.py:174 src/app/settings.py:181 +#: src/app/settings.py:274 src/app/settings.py:549 src/app/settings.py:555 +msgid "手动确认" +msgstr "手动确认" + +#: src/app/settings.py:133 +msgid "保存" +msgstr "保存" + +#: src/app/settings.py:134 src/app/dialogs/common.py:27 +msgid "取消" +msgstr "取消" + +#: src/app/settings.py:437 +msgid "Base URL 不能为空" +msgstr "基础 URL 不能为空" + +#: src/app/settings.py:534 +msgid "切换中..." +msgstr "切换中..." + #: src/app/tui.py:217 msgid "Enter command or question..." msgstr "输入命令或问题..." @@ -136,6 +243,40 @@ msgstr "后端配置验证失败,请检查并修改配置" msgid "Configuration Error" msgstr "配置错误" +#: src/app/dialogs/common.py:25 +msgid "确认退出吗?" +msgstr "确认退出吗?" + +#: src/app/dialogs/common.py:28 +msgid "确认" +msgstr "确认" + +#: src/app/dialogs/agent.py:27 +msgid "智能体功能提示" +msgstr "智能体功能提示" + +#: src/app/dialogs/agent.py:28 +msgid "请选择 openEuler Intelligence 后端来使用智能体功能" +msgstr "请选择 openEuler Intelligence 后端来使用智能体功能" + +#: src/app/dialogs/agent.py:29 +msgid "按任意键关闭" +msgstr "按任意键关闭" + +#: src/app/dialogs/agent.py:60 src/app/dialogs/agent.py:130 +#: src/app/dialogs/agent.py:185 src/app/dialogs/agent.py:205 +#: src/app/dialogs/agent.py:208 +msgid "智能问答" +msgstr "智能问答" + +#: src/app/dialogs/agent.py:113 +msgid "OS 智能助手" +msgstr "OS 智能助手" + +#: src/app/dialogs/agent.py:115 +msgid "使用上下键选择,回车确认,ESC取消 | ✓ 表示当前选中" +msgstr "使用上下键选择,回车确认,ESC取消 | ✓ 表示当前选中" + #: src/main.py:28 msgid "openEuler Intelligence - Intelligent command-line tool" msgstr "openEuler Intelligence - 智能命令行工具" -- Gitee From d539fbc9c91f50787262c47a13664411499398af Mon Sep 17 00:00:00 2001 From: Hongyu Shi Date: Tue, 21 Oct 2025 11:21:06 +0800 Subject: [PATCH 16/19] =?UTF-8?q?chore(i18n):=20=E6=9B=B4=E6=96=B0=20Tools?= =?UTF-8?q?=20=E5=9B=BD=E9=99=85=E5=8C=96=20(Part=201)?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Signed-off-by: Hongyu Shi --- src/app/tui.py | 2 +- src/i18n/locales/messages.pot | 318 +++++++++++++++++++++++++++++++++- src/tool/command_processor.py | 25 +-- src/tool/oi_llm_config.py | 19 +- src/tool/oi_select_agent.py | 23 +-- src/tool/validators.py | 106 +++++++----- 6 files changed, 411 insertions(+), 82 deletions(-) diff --git a/src/app/tui.py b/src/app/tui.py index 8b20da5..c8f4532 100644 --- a/src/app/tui.py +++ b/src/app/tui.py @@ -252,7 +252,7 @@ class IntelligentTerminal(App): super().__init__() # 设置应用标题 self.title = "openEuler Intelligence" - self.sub_title = _("Intelligent CLI Tool {version}").format(version=__version__) + self.sub_title = _("Intelligent CLI Assistant {version}").format(version=__version__) self.config_manager = ConfigManager() self.processing: bool = False # 添加保存任务的集合到类属性 diff --git a/src/i18n/locales/messages.pot b/src/i18n/locales/messages.pot index 0f5f27b..c110956 100644 --- a/src/i18n/locales/messages.pot +++ b/src/i18n/locales/messages.pot @@ -8,7 +8,7 @@ msgid "" msgstr "" "Project-Id-Version: oi-cli 0.10.2\n" "Report-Msgid-Bugs-To: contact@openeuler.org\n" -"POT-Creation-Date: 2025-10-21 10:23+0800\n" +"POT-Creation-Date: 2025-10-21 10:54+0800\n" "PO-Revision-Date: YEAR-MO-DA HO:MI+ZONE\n" "Last-Translator: FULL NAME \n" "Language-Team: LANGUAGE \n" @@ -154,7 +154,7 @@ msgstr "" #: src/app/tui.py:255 #, python-brace-format -msgid "Intelligent CLI Tool {version}" +msgid "Intelligent CLI Assistant {version}" msgstr "" #: src/app/tui.py:371 @@ -265,7 +265,8 @@ msgstr "" #: src/app/dialogs/agent.py:60 src/app/dialogs/agent.py:130 #: src/app/dialogs/agent.py:185 src/app/dialogs/agent.py:205 -#: src/app/dialogs/agent.py:208 +#: src/app/dialogs/agent.py:208 src/tool/oi_select_agent.py:67 +#: src/tool/oi_select_agent.py:94 msgid "智能问答" msgstr "" @@ -461,3 +462,314 @@ msgstr "" #: src/main.py:228 msgid "Fatal error in Intelligent Shell application" msgstr "" + +#: src/tool/oi_select_agent.py:29 +msgid "退出" +msgstr "" + +#: src/tool/oi_select_agent.py:123 +#, python-brace-format +msgid "✓ 默认智能体已设置为: {name}\n" +msgstr "" + +#: src/tool/oi_select_agent.py:125 +#, python-brace-format +msgid " App ID: {app_id}\n" +msgstr "" + +#: src/tool/oi_select_agent.py:127 +msgid " 已设置为智能问答模式(无智能体)\n" +msgstr "" + +#: src/tool/oi_select_agent.py:129 +msgid "已取消选择\n" +msgstr "" + +#: src/tool/oi_select_agent.py:143 +msgid "错误: 智能体功能需要使用 openEuler Intelligence 后端\n" +msgstr "" + +#: src/tool/oi_select_agent.py:144 +msgid "请先运行以下命令切换后端:\n" +msgstr "" + +#: src/tool/oi_select_agent.py:145 +msgid "" +" oi # 然后按下 Ctrl+S 进入设置界面切换到 openEuler Intelligence 后端\n" +msgstr "" + +#: src/tool/oi_select_agent.py:163 +#, python-brace-format +msgid "错误: {error}\n" +msgstr "" + +#: src/tool/oi_llm_config.py:77 +msgid "需要管理员权限才能修改 openEuler Intelligence 配置文件" +msgstr "" + +#: src/tool/oi_llm_config.py:84 +#, python-brace-format +msgid "配置文件不存在: {path}" +msgstr "" + +#: src/tool/oi_llm_config.py:85 +msgid "请先运行 '(sudo) oi --init' 部署后端服务" +msgstr "" + +#: src/tool/oi_llm_config.py:89 src/tool/oi_llm_config.py:93 +#, python-brace-format +msgid "配置文件不可写: {path}" +msgstr "" + +#: src/tool/oi_llm_config.py:96 +#, python-brace-format +msgid "访问配置文件时权限不足: {error}" +msgstr "" + +#: src/tool/oi_llm_config.py:98 +#, python-brace-format +msgid "访问配置文件时发生错误: {error}" +msgstr "" + +#: src/tool/oi_llm_config.py:127 +#, python-brace-format +msgid "权限不足:无法访问配置文件 {filename},请以管理员身份运行" +msgstr "" + +#: src/tool/validators.py:135 src/tool/validators.py:584 +#: src/tool/validators.py:647 +#, python-brace-format +msgid "连接超时 - 无法在 {timeout} 秒内连接到 {endpoint}" +msgstr "" + +#: src/tool/validators.py:140 +#, python-brace-format +msgid "LLM 配置验证失败: {error}" +msgstr "" + +#: src/tool/validators.py:144 +msgid "LLM 配置验证成功" +msgstr "" + +#: src/tool/validators.py:146 +#, python-brace-format +msgid " - 支持工具调用,类型: {func_type}" +msgstr "" + +#: src/tool/validators.py:148 +msgid " - 不支持工具调用" +msgstr "" + +#: src/tool/validators.py:201 +msgid "无法连接到 Embedding 模型服务。" +msgstr "" + +#: src/tool/validators.py:241 +msgid "基本对话测试失败" +msgstr "" + +#: src/tool/validators.py:244 +msgid "基本对话功能正常" +msgstr "" + +#: src/tool/validators.py:246 +msgid "对话响应为空" +msgstr "" + +#: src/tool/validators.py:318 +msgid "不支持任何 function_call 格式" +msgstr "" + +#: src/tool/validators.py:353 +#, python-brace-format +msgid "tools 格式测试失败: {error}" +msgstr "" + +#: src/tool/validators.py:358 +msgid "支持 tools 格式的 function_call" +msgstr "" + +#: src/tool/validators.py:360 +msgid "不支持工具调用功能" +msgstr "" + +#: src/tool/validators.py:401 +#, python-brace-format +msgid "structured_output 格式测试失败: {error}" +msgstr "" + +#: src/tool/validators.py:409 +msgid "structured_output 响应不是有效 JSON" +msgstr "" + +#: src/tool/validators.py:411 +msgid "支持 structured_output 格式" +msgstr "" + +#: src/tool/validators.py:413 +msgid "structured_output 响应为空" +msgstr "" + +#: src/tool/validators.py:439 +#, python-brace-format +msgid "json_mode 格式测试失败: {error}" +msgstr "" + +#: src/tool/validators.py:447 +msgid "json_mode 响应不是有效 JSON" +msgstr "" + +#: src/tool/validators.py:449 +msgid "支持 json_mode 格式" +msgstr "" + +#: src/tool/validators.py:451 +msgid "json_mode 响应为空" +msgstr "" + +#: src/tool/validators.py:499 +msgid "支持 vLLM 结构化输出(部分支持)" +msgstr "" + +#: src/tool/validators.py:504 +#, python-brace-format +msgid "不支持 vLLM guided_json 格式: {error}" +msgstr "" + +#: src/tool/validators.py:508 +msgid "vLLM guided_json 响应无效" +msgstr "" + +#: src/tool/validators.py:555 +msgid "支持 Ollama function_call 格式" +msgstr "" + +#: src/tool/validators.py:558 +#, python-brace-format +msgid "不支持 Ollama function_call 格式: {error}" +msgstr "" + +#: src/tool/validators.py:561 +msgid "Ollama function_call 响应无效" +msgstr "" + +#: src/tool/validators.py:589 +#, python-brace-format +msgid "OpenAI Embedding 配置验证失败: {error}" +msgstr "" + +#: src/tool/validators.py:598 +#, python-brace-format +msgid "OpenAI Embedding 配置验证成功 - 维度: {dimension}" +msgstr "" + +#: src/tool/validators.py:606 +msgid "OpenAI Embedding 响应为空" +msgstr "" + +#: src/tool/validators.py:634 +#, python-brace-format +msgid "MindIE Embedding 配置验证成功 - 维度: {dimension}" +msgstr "" + +#: src/tool/validators.py:644 +msgid "MindIE Embedding 响应格式不正确" +msgstr "" + +#: src/tool/validators.py:652 +#, python-brace-format +msgid "MindIE Embedding 配置验证失败: {error}" +msgstr "" + +#: src/tool/validators.py:674 +msgid "服务 URL 必须以 http:// 或 https:// 开头" +msgstr "" + +#: src/tool/validators.py:685 +msgid "访问令牌格式无效" +msgstr "" + +#: src/tool/validators.py:710 +msgid "服务返回的数据格式不正确" +msgstr "" + +#: src/tool/validators.py:716 +msgid "连接成功" +msgstr "" + +#: src/tool/validators.py:718 +#, python-brace-format +msgid "服务返回错误代码: {code}" +msgstr "" + +#: src/tool/validators.py:721 +msgid "无法连接到服务,请检查 URL 和网络连接" +msgstr "" + +#: src/tool/validators.py:723 +msgid "连接超时,请检查网络连接或服务状态" +msgstr "" + +#: src/tool/validators.py:726 +#, python-brace-format +msgid "连接验证失败: {error}" +msgstr "" + +#: src/tool/validators.py:732 +msgid "访问令牌无效或已过期" +msgstr "" + +#: src/tool/validators.py:733 +msgid "访问权限不足" +msgstr "" + +#: src/tool/validators.py:734 +msgid "API 接口不存在,请检查服务版本" +msgstr "" + +#: src/tool/validators.py:737 +#, python-brace-format +msgid "服务响应异常,状态码: {status_code}" +msgstr "" + +#: src/tool/command_processor.py:56 +msgid "请输入有效命令或问题。" +msgstr "" + +#: src/tool/command_processor.py:75 +msgid "检测到不安全命令,已阻止执行。" +msgstr "" + +#: src/tool/command_processor.py:135 +msgid "[命令启动失败] 无法创建子进程" +msgstr "" + +#: src/tool/command_processor.py:136 +#, python-brace-format +msgid "无法启动命令 '{command}',请分析可能原因并给出解决建议。" +msgstr "" + +#: src/tool/command_processor.py:165 +#, python-brace-format +msgid "" +"\n" +"[命令完成] 退出码: {returncode}" +msgstr "" + +#: src/tool/command_processor.py:183 +#, python-brace-format +msgid "[命令失败] 退出码: {returncode}" +msgstr "" + +#: src/tool/command_processor.py:188 +#, python-brace-format +msgid "" +"命令 '{command}' 以非零状态 {returncode} 退出。\n" +"标准错误输出如下:\n" +"{stderr_text}\n" +"请分析原因并提供解决建议。" +msgstr "" + +#: src/tool/command_processor.py:206 +msgid "读取 stderr 失败" +msgstr "" diff --git a/src/tool/command_processor.py b/src/tool/command_processor.py index c24b082..bfa91be 100644 --- a/src/tool/command_processor.py +++ b/src/tool/command_processor.py @@ -14,6 +14,7 @@ import shutil from typing import TYPE_CHECKING from backend.hermes.mcp_helpers import is_mcp_message +from i18n.manager import _ from log.manager import get_logger if TYPE_CHECKING: @@ -52,7 +53,7 @@ async def process_command(command: str, llm_client: LLMClientBase) -> AsyncGener tokens = command.split() if not tokens: - yield ("请输入有效命令或问题。", True) # 作为LLM输出处理 + yield (_("请输入有效命令或问题。"), True) # 作为LLM输出处理 return prog = tokens[0] @@ -71,7 +72,7 @@ async def process_command(command: str, llm_client: LLMClientBase) -> AsyncGener logger.info("检测到系统命令: %s", prog) if not is_command_safe(command): logger.warning("命令被安全检查阻止: %s", command) - yield ("检测到不安全命令,已阻止执行。", True) + yield (_("检测到不安全命令,已阻止执行。"), True) return # 流式执行 @@ -131,8 +132,8 @@ async def _handle_subprocess_creation_error( llm_client: LLMClientBase, ) -> AsyncGenerator[tuple[str, bool], None]: """处理子进程创建失败的情况""" - yield ("[命令启动失败] 无法创建子进程", False) - query = f"无法启动命令 '{command}',请分析可能原因并给出解决建议。" + yield (_("[命令启动失败] 无法创建子进程"), False) + query = _("无法启动命令 '{command}',请分析可能原因并给出解决建议。").format(command=command) async for suggestion in llm_client.get_llm_response(query): is_mcp_message_flag = is_mcp_message(suggestion) yield (suggestion, not is_mcp_message_flag) @@ -161,7 +162,7 @@ async def _execute_and_stream_output( success = returncode == 0 if success: - yield (f"\n[命令完成] 退出码: {returncode}", False) + yield (_("\n[命令完成] 退出码: {returncode}").format(returncode=returncode), False) return # 处理命令失败的情况 @@ -179,15 +180,15 @@ async def _handle_command_failure( """处理命令执行失败的情况""" # 读取 stderr stderr_text = await _read_stderr(proc) - yield (f"[命令失败] 退出码: {returncode}", False) + yield (_("[命令失败] 退出码: {returncode}").format(returncode=returncode), False) # 获取 LLM 建议 logger.info("命令执行失败(returncode=%s),向 LLM 请求建议", returncode) - query = ( - f"命令 '{command}' 以非零状态 {returncode} 退出。\n" - f"标准错误输出如下:\n{stderr_text}\n" - "请分析原因并提供解决建议。" - ) + query = _( + "命令 '{command}' 以非零状态 {returncode} 退出。\n" + "标准错误输出如下:\n{stderr_text}\n" + "请分析原因并提供解决建议。", + ).format(command=command, returncode=returncode, stderr_text=stderr_text) async for suggestion in llm_client.get_llm_response(query): is_mcp_message_flag = is_mcp_message(suggestion) yield (suggestion, not is_mcp_message_flag) @@ -202,7 +203,7 @@ async def _read_stderr(proc: asyncio.subprocess.Process) -> str: stderr_bytes = await proc.stderr.read() return stderr_bytes.decode(errors="replace") except (OSError, asyncio.CancelledError): - return "读取 stderr 失败" + return _("读取 stderr 失败") async def _handle_process_interruption(proc: asyncio.subprocess.Process, logger: logging.Logger) -> None: diff --git a/src/tool/oi_llm_config.py b/src/tool/oi_llm_config.py index 9d2c961..b6372d2 100644 --- a/src/tool/oi_llm_config.py +++ b/src/tool/oi_llm_config.py @@ -24,6 +24,7 @@ from textual.widgets import Button, Input, Label, Static, TabbedContent, TabPane from app.deployment.models import EmbeddingConfig, LLMConfig from app.tui_header import OIHeader +from i18n.manager import _ from log.manager import get_logger from tool.validators import APIValidator @@ -73,28 +74,28 @@ class LLMSystemConfig: # 检查是否以管理员权限运行 if os.geteuid() != 0: - errors.append("需要管理员权限才能修改 openEuler Intelligence 配置文件") + errors.append(_("需要管理员权限才能修改 openEuler Intelligence 配置文件")) # 如果没有管理员权限,直接返回,避免后续的文件操作引发权限错误 return False, errors try: # 检查核心配置文件是否存在(必须存在) if not cls.FRAMEWORK_CONFIG_PATH.exists(): - errors.append(f"配置文件不存在: {cls.FRAMEWORK_CONFIG_PATH}") - errors.append("请先运行 '(sudo) oi --init' 部署后端服务") + errors.append(_("配置文件不存在: {path}").format(path=cls.FRAMEWORK_CONFIG_PATH)) + errors.append(_("请先运行 '(sudo) oi --init' 部署后端服务")) # 检查核心配置文件是否可写(必须可写) if cls.FRAMEWORK_CONFIG_PATH.exists() and not os.access(cls.FRAMEWORK_CONFIG_PATH, os.W_OK): - errors.append(f"配置文件不可写: {cls.FRAMEWORK_CONFIG_PATH}") + errors.append(_("配置文件不可写: {path}").format(path=cls.FRAMEWORK_CONFIG_PATH)) # 检查 RAG_ENV_PATH 文件是否可写(如果存在的话) if cls.RAG_ENV_PATH.exists() and not os.access(cls.RAG_ENV_PATH, os.W_OK): - errors.append(f"配置文件不可写: {cls.RAG_ENV_PATH}") + errors.append(_("配置文件不可写: {path}").format(path=cls.RAG_ENV_PATH)) except PermissionError as e: - errors.append(f"访问配置文件时权限不足: {e}") + errors.append(_("访问配置文件时权限不足: {error}").format(error=str(e))) except OSError as e: - errors.append(f"访问配置文件时发生错误: {e}") + errors.append(_("访问配置文件时发生错误: {error}").format(error=str(e))) return len(errors) == 0, errors @@ -123,7 +124,9 @@ class LLMSystemConfig: except PermissionError as e: logger.exception("权限不足,无法访问配置文件") - error_msg = f"权限不足:无法访问配置文件 {e.filename if hasattr(e, 'filename') else ''},请以管理员身份运行" + error_msg = _("权限不足:无法访问配置文件 {filename},请以管理员身份运行").format( + filename=e.filename if hasattr(e, "filename") else "", + ) raise PermissionError(error_msg) from e except (OSError, ValueError, toml.TomlDecodeError): logger.exception("加载系统配置失败") diff --git a/src/tool/oi_select_agent.py b/src/tool/oi_select_agent.py index 6818bba..c13023b 100644 --- a/src/tool/oi_select_agent.py +++ b/src/tool/oi_select_agent.py @@ -13,6 +13,7 @@ from app.dialogs import AgentSelectionDialog from backend.factory import BackendFactory from config.manager import ConfigManager from config.model import Backend +from i18n.manager import _ from log.manager import get_logger, log_exception, setup_logging if TYPE_CHECKING: @@ -25,7 +26,7 @@ class AgentSelectorApp(App): CSS_PATH = Path(__file__).parent.parent / "app" / "css" / "styles.tcss" BINDINGS: ClassVar = [ - ("escape", "quit", "退出"), + ("escape", "quit", _("退出")), ] def __init__(self, agent_list: list[tuple[str, str]], current_agent: tuple[str, str]) -> None: @@ -63,7 +64,7 @@ async def get_agent_list(config_manager: ConfigManager, logger: Logger) -> list[ llm_client = BackendFactory.create_client(config_manager) # 构建智能体列表 - 默认第一项为"智能问答"(无智能体) - agent_list = [("", "智能问答")] + agent_list = [("", _("智能问答"))] # 尝试获取可用智能体 if not hasattr(llm_client, "get_available_agents"): @@ -90,7 +91,7 @@ async def get_agent_list(config_manager: ConfigManager, logger: Logger) -> list[ def get_current_agent(config_manager: ConfigManager, agent_list: list[tuple[str, str]]) -> tuple[str, str]: """获取当前默认智能体""" current_app_id = config_manager.get_default_app() - current_agent = ("", "智能问答") + current_agent = ("", _("智能问答")) for agent in agent_list: if agent[0] == current_app_id: current_agent = agent @@ -119,13 +120,13 @@ def handle_agent_selection( # 保存选择到配置 config_manager.set_default_app(selected_app_id) - sys.stdout.write(f"✓ 默认智能体已设置为: {selected_name}\n") + sys.stdout.write(_("✓ 默认智能体已设置为: {name}\n").format(name=selected_name)) if selected_app_id: - sys.stdout.write(f" App ID: {selected_app_id}\n") + sys.stdout.write(_(" App ID: {app_id}\n").format(app_id=selected_app_id)) else: - sys.stdout.write(" 已设置为智能问答模式(无智能体)\n") + sys.stdout.write(_(" 已设置为智能问答模式(无智能体)\n")) else: - sys.stdout.write("已取消选择\n") + sys.stdout.write(_("已取消选择\n")) async def select_agent() -> None: @@ -139,9 +140,9 @@ async def select_agent() -> None: # 检查是否使用 eulerintelli 后端 if config_manager.get_backend() != Backend.EULERINTELLI: - sys.stderr.write("错误: 智能体功能需要使用 openEuler Intelligence 后端\n") - sys.stderr.write("请先运行以下命令切换后端:\n") - sys.stderr.write(" oi # 然后按下 Ctrl+S 进入设置界面切换到 openEuler Intelligence 后端\n") + sys.stderr.write(_("错误: 智能体功能需要使用 openEuler Intelligence 后端\n")) + sys.stderr.write(_("请先运行以下命令切换后端:\n")) + sys.stderr.write(_(" oi # 然后按下 Ctrl+S 进入设置界面切换到 openEuler Intelligence 后端\n")) sys.exit(1) try: @@ -159,5 +160,5 @@ async def select_agent() -> None: except (OSError, ValueError, RuntimeError) as e: log_exception(logger, "智能体选择功能发生错误", e) - sys.stderr.write(f"错误: {e}\n") + sys.stderr.write(_("错误: {error}\n").format(error=str(e))) sys.exit(1) diff --git a/src/tool/validators.py b/src/tool/validators.py index 6439917..23ff210 100644 --- a/src/tool/validators.py +++ b/src/tool/validators.py @@ -14,6 +14,7 @@ from typing import Any import httpx from openai import APIError, AsyncOpenAI, AuthenticationError, OpenAIError +from i18n.manager import _ from log.manager import get_logger # 常量定义 @@ -131,17 +132,20 @@ class APIValidator: await client.close() except TimeoutError: - return False, f"连接超时 - 无法在 {timeout} 秒内连接到 {endpoint}", {} + return False, _("连接超时 - 无法在 {timeout} 秒内连接到 {endpoint}").format( + timeout=timeout, + endpoint=endpoint, + ), {} except (AuthenticationError, APIError, OpenAIError) as e: - error_msg = f"LLM 配置验证失败: {e!s}" + error_msg = _("LLM 配置验证失败: {error}").format(error=str(e)) self.logger.exception(error_msg) return False, error_msg, {} else: - success_msg = "LLM 配置验证成功" + success_msg = _("LLM 配置验证成功") if func_valid: - success_msg += f" - 支持工具调用,类型: {func_type}" + success_msg += _(" - 支持工具调用,类型: {func_type}").format(func_type=func_type) else: - success_msg += " - 不支持工具调用" + success_msg += _(" - 不支持工具调用") return ( True, @@ -194,7 +198,7 @@ class APIValidator: return True, mindie_msg, mindie_info # 两种格式都失败 - return False, "无法连接到 Embedding 模型服务。", {} + return False, _("无法连接到 Embedding 模型服务。"), {} def _create_openai_client( self, @@ -234,12 +238,12 @@ class APIValidator: response = await client.chat.completions.create(**call_kwargs) except (AuthenticationError, APIError, OpenAIError): - return False, "基本对话测试失败" + return False, _("基本对话测试失败") else: if response.choices and len(response.choices) > 0: - return True, "基本对话功能正常" + return True, _("基本对话功能正常") - return False, "对话响应为空" + return False, _("对话响应为空") async def _detect_function_call_type( self, @@ -311,7 +315,7 @@ class APIValidator: if ollama_valid: return True, ollama_msg, "ollama" - return False, "不支持任何 function_call 格式", "none" + return False, _("不支持任何 function_call 格式"), "none" async def _test_tools_format( self, @@ -346,14 +350,14 @@ class APIValidator: response = await client.chat.completions.create(**call_kwargs) except (AuthenticationError, APIError, OpenAIError) as e: - return False, f"tools 格式测试失败: {e!s}" + return False, _("tools 格式测试失败: {error}").format(error=str(e)) else: if response.choices and len(response.choices) > 0: choice = response.choices[0] if hasattr(choice.message, "tool_calls") and choice.message.tool_calls: - return True, "支持 tools 格式的 function_call" + return True, _("支持 tools 格式的 function_call") - return False, "不支持工具调用功能" + return False, _("不支持工具调用功能") async def _test_structured_output( self, @@ -394,7 +398,7 @@ class APIValidator: response = await client.chat.completions.create(**call_kwargs) except (AuthenticationError, APIError, OpenAIError) as e: - return False, f"structured_output 格式测试失败: {e!s}" + return False, _("structured_output 格式测试失败: {error}").format(error=str(e)) else: if response.choices and len(response.choices) > 0: choice = response.choices[0] @@ -402,11 +406,11 @@ class APIValidator: try: json.loads(choice.message.content) except (json.JSONDecodeError, ValueError): - return False, "structured_output 响应不是有效 JSON" + return False, _("structured_output 响应不是有效 JSON") else: - return True, "支持 structured_output 格式" + return True, _("支持 structured_output 格式") - return False, "structured_output 响应为空" + return False, _("structured_output 响应为空") async def _test_json_mode( self, @@ -432,7 +436,7 @@ class APIValidator: response = await client.chat.completions.create(**call_kwargs) except (AuthenticationError, APIError, OpenAIError) as e: - return False, f"json_mode 格式测试失败: {e!s}" + return False, _("json_mode 格式测试失败: {error}").format(error=str(e)) else: if response.choices and len(response.choices) > 0: choice = response.choices[0] @@ -440,11 +444,11 @@ class APIValidator: try: json.loads(choice.message.content) except (json.JSONDecodeError, ValueError): - return False, "json_mode 响应不是有效 JSON" + return False, _("json_mode 响应不是有效 JSON") else: - return True, "支持 json_mode 格式" + return True, _("支持 json_mode 格式") - return False, "json_mode 响应为空" + return False, _("json_mode 响应为空") async def _test_vllm_function_call( self, @@ -492,16 +496,16 @@ class APIValidator: # 检查是否包含结构化输出的迹象 if content and any(keyword in content.lower() for keyword in ["json", "{", "}"]): - return True, "支持 vLLM 结构化输出(部分支持)" + return True, _("支持 vLLM 结构化输出(部分支持)") except (AuthenticationError, APIError, OpenAIError) as e: error_str = str(e).lower() if any(keyword in error_str for keyword in ["extra_body", "guided_json", "not supported"]): - return False, f"不支持 vLLM guided_json 格式: {e!s}" + return False, _("不支持 vLLM guided_json 格式: {error}").format(error=str(e)) raise else: - return False, "vLLM guided_json 响应无效" + return False, _("vLLM guided_json 响应无效") async def _test_ollama_function_call( self, @@ -548,13 +552,13 @@ FUNCTION_CALL: get_current_time() "call", ] ): - return True, "支持 Ollama function_call 格式" + return True, _("支持 Ollama function_call 格式") except (AuthenticationError, APIError, OpenAIError) as e: - return False, f"不支持 Ollama function_call 格式: {e!s}" + return False, _("不支持 Ollama function_call 格式: {error}").format(error=str(e)) else: - return False, "Ollama function_call 响应无效" + return False, _("Ollama function_call 响应无效") async def _validate_openai_embedding( self, @@ -577,9 +581,12 @@ FUNCTION_CALL: get_current_time() await client.close() except TimeoutError: - return False, f"连接超时 - 无法在 {timeout} 秒内连接到 {endpoint}", {} + return False, _("连接超时 - 无法在 {timeout} 秒内连接到 {endpoint}").format( + timeout=timeout, + endpoint=endpoint, + ), {} except (AuthenticationError, APIError, OpenAIError) as e: - error_msg = f"OpenAI Embedding 配置验证失败: {e!s}" + error_msg = _("OpenAI Embedding 配置验证失败: {error}").format(error=str(e)) self.logger.exception(error_msg) return False, error_msg, {} else: @@ -588,7 +595,7 @@ FUNCTION_CALL: get_current_time() dimension = len(embedding) return ( True, - f"OpenAI Embedding 配置验证成功 - 维度: {dimension}", + _("OpenAI Embedding 配置验证成功 - 维度: {dimension}").format(dimension=dimension), { "type": "openai", "dimension": dimension, @@ -596,7 +603,7 @@ FUNCTION_CALL: get_current_time() }, ) - return False, "OpenAI Embedding 响应为空", {} + return False, _("OpenAI Embedding 响应为空"), {} async def _validate_mindie_embedding( self, @@ -624,7 +631,9 @@ FUNCTION_CALL: get_current_time() dimension = len(embedding) return ( True, - f"MindIE Embedding 配置验证成功 - 维度: {dimension}", + _("MindIE Embedding 配置验证成功 - 维度: {dimension}").format( + dimension=dimension, + ), { "type": "mindie", "dimension": dimension, @@ -632,12 +641,15 @@ FUNCTION_CALL: get_current_time() }, ) - return False, "MindIE Embedding 响应格式不正确", {} + return False, _("MindIE Embedding 响应格式不正确"), {} except httpx.TimeoutException: - return False, f"连接超时 - 无法在 {timeout} 秒内连接到 {endpoint}", {} + return False, _("连接超时 - 无法在 {timeout} 秒内连接到 {endpoint}").format( + timeout=timeout, + endpoint=endpoint, + ), {} except (httpx.RequestError, httpx.HTTPStatusError) as e: - error_msg = f"MindIE Embedding 配置验证失败: {e!s}" + error_msg = _("MindIE Embedding 配置验证失败: {error}").format(error=str(e)) self.logger.exception(error_msg) return False, error_msg, {} @@ -659,7 +671,7 @@ async def validate_oi_connection(base_url: str, access_token: str) -> tuple[bool try: # 确保 URL 格式正确 if not base_url.startswith(("http://", "https://")): - return False, "服务 URL 必须以 http:// 或 https:// 开头" + return False, _("服务 URL 必须以 http:// 或 https:// 开头") # 验证令牌格式 if not _is_valid_token_format(access_token): @@ -670,7 +682,7 @@ async def validate_oi_connection(base_url: str, access_token: str) -> tuple[bool else access_token ) logger.warning("访问令牌格式无效: %s", token_preview) - return False, "访问令牌格式无效" + return False, _("访问令牌格式无效") # 移除尾部的斜杠 base_url = base_url.rstrip("/") @@ -695,34 +707,34 @@ async def validate_oi_connection(base_url: str, access_token: str) -> tuple[bool try: response_data = response.json() except (ValueError, TypeError, KeyError): - return False, "服务返回的数据格式不正确" + return False, _("服务返回的数据格式不正确") # 检查 code 字段 code = response_data.get("code") if code == HTTP_OK: logger.info("openEuler Intelligence 服务连接成功") - return True, "连接成功" + return True, _("连接成功") - return False, f"服务返回错误代码: {code}" + return False, _("服务返回错误代码: {code}").format(code=code) except httpx.ConnectError: - return False, "无法连接到服务,请检查 URL 和网络连接" + return False, _("无法连接到服务,请检查 URL 和网络连接") except httpx.TimeoutException: - return False, "连接超时,请检查网络连接或服务状态" + return False, _("连接超时,请检查网络连接或服务状态") except Exception as e: logger.exception("验证 openEuler Intelligence 连接时发生异常") - return False, f"连接验证失败: {e}" + return False, _("连接验证失败: {error}").format(error=str(e)) def _handle_http_error(status_code: int) -> tuple[bool, str]: """处理 HTTP 错误状态码""" error_messages = { - HTTP_UNAUTHORIZED: "访问令牌无效或已过期", - HTTP_FORBIDDEN: "访问权限不足", - HTTP_NOT_FOUND: "API 接口不存在,请检查服务版本", + HTTP_UNAUTHORIZED: _("访问令牌无效或已过期"), + HTTP_FORBIDDEN: _("访问权限不足"), + HTTP_NOT_FOUND: _("API 接口不存在,请检查服务版本"), } - message = error_messages.get(status_code, f"服务响应异常,状态码: {status_code}") + message = error_messages.get(status_code, _("服务响应异常,状态码: {status_code}").format(status_code=status_code)) return False, message -- Gitee From cc454079c9f2d376cc2b7c822d48ce8f375a20bf Mon Sep 17 00:00:00 2001 From: Hongyu Shi Date: Tue, 21 Oct 2025 14:52:30 +0800 Subject: [PATCH 17/19] =?UTF-8?q?feat(build):=20=E6=B7=BB=E5=8A=A0?= =?UTF-8?q?=E5=BC=80=E5=8F=91=E6=A8=A1=E5=BC=8F=E6=94=AF=E6=8C=81=EF=BC=8C?= =?UTF-8?q?=E5=85=81=E8=AE=B8=E5=9C=A8=E6=9E=84=E5=BB=BA=E6=97=B6=E4=BD=BF?= =?UTF-8?q?=E7=94=A8=E6=97=B6=E9=97=B4=E6=88=B3?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Signed-off-by: Hongyu Shi --- distribution/linux/euler-copilot-shell.spec | 2 +- scripts/build/build_rpm.sh | 29 +++++++++++++++++++-- scripts/build/create_tarball.sh | 25 ++++++++++++++++++ 3 files changed, 53 insertions(+), 3 deletions(-) diff --git a/distribution/linux/euler-copilot-shell.spec b/distribution/linux/euler-copilot-shell.spec index 49801d5..b0a27fc 100644 --- a/distribution/linux/euler-copilot-shell.spec +++ b/distribution/linux/euler-copilot-shell.spec @@ -4,7 +4,7 @@ Name: euler-copilot-shell Version: 0.10.2 -Release: 1%{?dist} +Release: 1%{?dev_timestamp:.dev%{dev_timestamp}}%{?dist} Summary: openEuler Intelligence 智能命令行工具集 License: MulanPSL-2.0 URL: https://gitee.com/openeuler/euler-copilot-shell diff --git a/scripts/build/build_rpm.sh b/scripts/build/build_rpm.sh index 951e0df..f52c8b3 100755 --- a/scripts/build/build_rpm.sh +++ b/scripts/build/build_rpm.sh @@ -2,12 +2,32 @@ # build_rpm.sh: build RPM package using the tarball created by create_tarball.sh set -euo pipefail +# Parse arguments +DEV_MODE=0 +while [[ $# -gt 0 ]]; do + case "$1" in + --dev) + DEV_MODE=1 + shift + ;; + *) + echo "Unknown parameter: $1" >&2 + echo "Usage: $0 [--dev]" >&2 + exit 1 + ;; + esac +done + # Determine script directory and repo root SCRIPT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)" REPO_ROOT="$(cd "${SCRIPT_DIR}/../.." && pwd)" # Create the tarball and set BUILD_DIR and TARBALL -eval "$("${SCRIPT_DIR}"/create_tarball.sh)" +if [[ ${DEV_MODE} -eq 1 ]]; then + eval "$("${SCRIPT_DIR}"/create_tarball.sh --dev)" +else + eval "$("${SCRIPT_DIR}"/create_tarball.sh)" +fi set +u if [[ -z "${BUILD_DIR:-}" || -z "${TARBALL:-}" ]]; then echo "Error: BUILD_DIR 或 TARBALL 变量未设置,create_tarball.sh 执行失败。" >&2 @@ -27,7 +47,12 @@ cp "${SPEC_FILE}" "${BUILD_DIR}/SPECS/" # Build the RPMs echo "Building RPM using topdir ${BUILD_DIR}" -rpmbuild --define "_topdir ${BUILD_DIR}" -ba "${BUILD_DIR}/SPECS/$(basename "${SPEC_FILE}")" +if [[ ${DEV_MODE} -eq 1 ]]; then + # 在 dev 模式下,传递时间戳给 rpmbuild + rpmbuild --define "_topdir ${BUILD_DIR}" --define "dev_timestamp ${TIMESTAMP}" -ba "${BUILD_DIR}/SPECS/$(basename "${SPEC_FILE}")" +else + rpmbuild --define "_topdir ${BUILD_DIR}" -ba "${BUILD_DIR}/SPECS/$(basename "${SPEC_FILE}")" +fi # Output locations echo "RPM build complete." diff --git a/scripts/build/create_tarball.sh b/scripts/build/create_tarball.sh index baeb498..93615dc 100755 --- a/scripts/build/create_tarball.sh +++ b/scripts/build/create_tarball.sh @@ -2,6 +2,22 @@ # create_tarball.sh: create a tarball of current repo for RPM build. set -euo pipefail +# Parse arguments +DEV_MODE=0 +while [[ $# -gt 0 ]]; do + case "$1" in + --dev) + DEV_MODE=1 + shift + ;; + *) + echo "Unknown parameter: $1" >&2 + echo "Usage: $0 [--dev]" >&2 + exit 1 + ;; + esac +done + # Locate spec file relative to repo root SCRIPT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)" REPO_ROOT="$(cd "${SCRIPT_DIR}/../.." && pwd)" @@ -11,6 +27,12 @@ SPEC_FILE="${REPO_ROOT}/distribution/linux/euler-copilot-shell.spec" NAME=$(grep -E '^Name:' "$SPEC_FILE" | awk '{print $2}') VERSION=$(grep -E '^Version:' "$SPEC_FILE" | awk '{print $2}') +# 如果是 dev 模式,添加时间戳 +if [[ ${DEV_MODE} -eq 1 ]]; then + TIMESTAMP=$(date +%Y%m%d%H%M%S) + VERSION="${VERSION}.dev${TIMESTAMP}" +fi + # Create build directory in repo BUILD_DIR="${REPO_ROOT}/build" mkdir -p "${BUILD_DIR}" @@ -23,3 +45,6 @@ git archive --format=tar.gz --prefix="${NAME}-${VERSION}/" -o "${BUILD_DIR}/${TA # 输出变量用于 build_rpm.sh 的 eval echo "BUILD_DIR=${BUILD_DIR}" echo "TARBALL=${TARBALL}" +if [[ ${DEV_MODE} -eq 1 ]]; then + echo "TIMESTAMP=${TIMESTAMP}" +fi -- Gitee From a9ff25a799f8ae56a0b5fda9ceb7485e06da7ede Mon Sep 17 00:00:00 2001 From: Hongyu Shi Date: Tue, 21 Oct 2025 15:03:06 +0800 Subject: [PATCH 18/19] =?UTF-8?q?fix(build):=20=E7=A7=BB=E9=99=A4=E5=BC=80?= =?UTF-8?q?=E5=8F=91=E6=A8=A1=E5=BC=8F=E4=B8=8B=E7=89=88=E6=9C=AC=E5=8F=B7?= =?UTF-8?q?=E7=9A=84=E6=97=B6=E9=97=B4=E6=88=B3=E6=B7=BB=E5=8A=A0?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Signed-off-by: Hongyu Shi --- scripts/build/create_tarball.sh | 1 - 1 file changed, 1 deletion(-) diff --git a/scripts/build/create_tarball.sh b/scripts/build/create_tarball.sh index 93615dc..711e65e 100755 --- a/scripts/build/create_tarball.sh +++ b/scripts/build/create_tarball.sh @@ -30,7 +30,6 @@ VERSION=$(grep -E '^Version:' "$SPEC_FILE" | awk '{print $2}') # 如果是 dev 模式,添加时间戳 if [[ ${DEV_MODE} -eq 1 ]]; then TIMESTAMP=$(date +%Y%m%d%H%M%S) - VERSION="${VERSION}.dev${TIMESTAMP}" fi # Create build directory in repo -- Gitee From b4aa9f28283fd26155d7bcf396458e5838dd4ac8 Mon Sep 17 00:00:00 2001 From: Hongyu Shi Date: Wed, 22 Oct 2025 09:46:49 +0800 Subject: [PATCH 19/19] =?UTF-8?q?fix(build):=20=E4=BF=AE=E5=A4=8D=20i18n?= =?UTF-8?q?=20=E5=8C=85=E5=90=AB=E7=9A=84=E6=96=87=E4=BB=B6=E8=B7=AF?= =?UTF-8?q?=E5=BE=84=EF=BC=8C=E7=A1=AE=E4=BF=9D=E6=AD=A3=E7=A1=AE=E6=89=93?= =?UTF-8?q?=E5=8C=85=E7=BF=BB=E8=AF=91=E6=96=87=E4=BB=B6?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Signed-off-by: Hongyu Shi --- pyproject.toml | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/pyproject.toml b/pyproject.toml index 37dc1c7..98c8633 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -48,6 +48,7 @@ packages = [ "src/log", "src/tool", ] +artifacts = ["src/i18n/locales/*/LC_MESSAGES/*.mo"] [tool.hatch.build.targets.wheel.force-include] "src/main.py" = "main.py" @@ -55,7 +56,7 @@ packages = [ [tool.hatch.build.targets.wheel.package-data] app = ["css/*.tcss"] -i18n = ["locales/*/LC_MESSAGES/*.mo", "locales/*.pot"] +i18n = ["locales/*.pot", "locales/*/LC_MESSAGES/*.po"] [tool.hatch.build.targets.sdist] include = ["LICENSE", "MANIFEST.in", "README.md", "requirements.txt", "src"] -- Gitee