diff --git a/.gitignore b/.gitignore
index 450a7e5e3945db67acc78c87c5976cf15a5caefe..95f19871372694b323daaf71e6df9c4d3ddb8b5a 100644
--- a/.gitignore
+++ b/.gitignore
@@ -20,3 +20,6 @@
# Cython
*.c
+
+# Compiled i18n files
+**/*.mo
diff --git a/README.md b/README.md
index e5860099f5318355ad91b4556e3225b7d8efc921..2568109b36964501dffcdf002513b4935ba34cd4 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/distribution/linux/euler-copilot-shell.spec b/distribution/linux/euler-copilot-shell.spec
index d65f52515c63defc9dee1a0f34e4cc9f9ce941bb..b0a27fc1d5db6540d2ffc758b88e607ae22f8bc3 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
@@ -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"
new file mode 100644
index 0000000000000000000000000000000000000000..6c545f27fc918ef1b8975875f9e842a63609ac0a
--- /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,455 @@
+# 国际化 (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
+```
+
+这会依次执行提取、更新和编译操作。
+
+### 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` 文件路径。
+
+### 添加新语言
+
+要添加对新语言的支持:
+
+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 0000000000000000000000000000000000000000..3d8459d650a270c7bfd769e6d78dcd9c966dcccd
--- /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)
+
+包括:
+
+- 架构设计详解
+- 添加新语言的步骤
+- 最佳实践和注意事项
+- 常见问题解决方案
diff --git a/oi-cli.spec b/oi-cli.spec
index 955e7c8bb57ae9e8b717f9d9dfc45e092f273365..b1ad2308851a93f0561ffd7473b05a4a4e91b55e 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(
diff --git a/pyproject.toml b/pyproject.toml
index a65febea17fbf9c99bdd3fceb7fc31c3267e7c66..98c8633a7794d0b3aae8a238d0094688ef36a0a5 100644
--- a/pyproject.toml
+++ b/pyproject.toml
@@ -40,7 +40,15 @@ 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",
+]
+artifacts = ["src/i18n/locales/*/LC_MESSAGES/*.mo"]
[tool.hatch.build.targets.wheel.force-include]
"src/main.py" = "main.py"
@@ -48,6 +56,7 @@ packages = ["src/app", "src/backend", "src/config", "src/log", "src/tool"]
[tool.hatch.build.targets.wheel.package-data]
app = ["css/*.tcss"]
+i18n = ["locales/*.pot", "locales/*/LC_MESSAGES/*.po"]
[tool.hatch.build.targets.sdist]
include = ["LICENSE", "MANIFEST.in", "README.md", "requirements.txt", "src"]
diff --git a/scripts/build/build_rpm.sh b/scripts/build/build_rpm.sh
index 951e0dfb9e3e9881b81892bed506f0b201cbf3a1..f52c8b300a85b0f9bf8179b84918e9c68d72d265 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 baeb4989d493a3590f67985130a2692777808bc5..711e65e50fa99afb9087df66b5f8c0551d9b5785 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,11 @@ 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)
+fi
+
# Create build directory in repo
BUILD_DIR="${REPO_ROOT}/build"
mkdir -p "${BUILD_DIR}"
@@ -23,3 +44,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
diff --git a/scripts/tools/i18n-manager.sh b/scripts/tools/i18n-manager.sh
new file mode 100755
index 0000000000000000000000000000000000000000..ef38b42c68b1330c5fb3031d66c81fab32e4af38
--- /dev/null
+++ b/scripts/tools/i18n-manager.sh
@@ -0,0 +1,259 @@
+#!/usr/bin/env bash
+# 国际化翻译管理脚本
+
+set -e
+set -o pipefail
+
+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 | sed 's/^[[:space:]]*//')
+ echo " Found $file_count Python files"
+ echo " Output file: $POT_FILE"
+
+ # 使用 xgettext 提取字符串(使用相对路径)
+ # shellcheck disable=SC2086
+ if xgettext \
+ --language=Python \
+ --keyword=_ \
+ --keyword=_n:1,2 \
+ --output="$POT_FILE" \
+ --from-code=UTF-8 \
+ --package-name=oi-cli \
+ --package-version=0.10.2 \
+ --msgid-bugs-address=contact@openeuler.org \
+ --copyright-holder="openEuler Intelligence Project" \
+ --add-comments=Translators \
+ $python_files; 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=$((updated + 1))
+ 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
+ failed=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..."
+ # 临时禁用 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 + 1))
+ else
+ print_yellow " ⚠️ Failed to compile $locale_name"
+ echo " Error: $error_output"
+ failed=$((failed + 1))
+ fi
+ done
+
+ echo ""
+ if [ "$compiled" -gt 0 ]; then
+ print_green "✅ Successfully compiled $compiled translation file(s)"
+ 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
+}
+
+# 执行完整流程
+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/app/dialogs/agent.py b/src/app/dialogs/agent.py
index c000d275c531abf11ca8ec0a1e3ca62305783d2f..b1cd115262d9d504b397ad9c1878a8ca3e96d728 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 995891722b033200753f9a23724e367ed83a8b2e..99049e2d0cf97acd8c8988c6c171882444d0e317 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 12cdd3840a99ae6717c95a3a8604473545ec346c..7fb5d27f126fc0eb9063e97d7dfcd0aee782f9b0 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 caf448e2a2b1f6a2a3386775ab2ea0e766337310..4d2ec66313c7a26990d3c58b77323f18dadffb00 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/app/tui.py b/src/app/tui.py
index a13d38a322fd88ef391780a80a323049f3d7da31..292662c9ff5501f2d55cbc7d03c5196c4fd8efb5 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 Assistant {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,
)
@@ -1372,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():
diff --git a/src/backend/hermes/client.py b/src/backend/hermes/client.py
index 49196f6f94319761094463c551f8827e5d578ffc..c13ab83ba899086f26cfea911695ee73bcea32af 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 a76a9af7b6dcd0ec05ab41450124d6ddfd7c43ea..f43b333ff2f8643b7dd8471f0e8736343871c141 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/config/manager.py b/src/config/manager.py
index e6ef9c05d683f532ea8566252d4664302b9bfe8d..ba45454038c50b2fcfd091f4964fce348700e0d2 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 f8df042fe6ce0313d1e87977c37c47d9de9a6bdc..ab986c4ea5db06ac56ca773a05551be75bd3fcff 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="") # 空字符串表示自动检测系统语言
@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", ""), # 空字符串表示自动检测
)
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 0000000000000000000000000000000000000000..dd0631b58bd8e878bebcbfd186ec9dd141fce689
--- /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/locales/en_US/LC_MESSAGES/messages.po b/src/i18n/locales/en_US/LC_MESSAGES/messages.po
new file mode 100644
index 0000000000000000000000000000000000000000..8e5ecdf708c6a62c5869239e4ed18bf90a6769f5
--- /dev/null
+++ b/src/i18n/locales/en_US/LC_MESSAGES/messages.po
@@ -0,0 +1,796 @@
+# English translations for oi-cli package.
+# Copyright (C) 2025 openEuler Intelligence Project
+# This file is distributed under the same license as the oi-cli package.
+#
+msgid ""
+msgstr ""
+"Project-Id-Version: oi-cli\n"
+"Report-Msgid-Bugs-To: contact@openeuler.org\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"
+"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"
+"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 src/backend/hermes/mcp_helpers.py:286
+msgid "低风险"
+msgstr "Low risk"
+
+#: src/app/mcp_widgets.py:52 src/backend/hermes/mcp_helpers.py:287
+msgid "中等风险"
+msgstr "Medium risk"
+
+#: src/app/mcp_widgets.py:53 src/backend/hermes/mcp_helpers.py:288
+msgid "高风险"
+msgstr "High risk"
+
+#: src/app/mcp_widgets.py:54 src/backend/hermes/mcp_helpers.py:289
+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..."
+
+#: 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 Assistant {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/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 src/tool/oi_select_agent.py:67
+#: src/tool/oi_select_agent.py:94
+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/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 - Command-line Client"
+
+#: 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:191
+#, python-brace-format
+msgid "✓ Language set to: {locale}\n"
+msgstr "✓ Language set to: {locale}\n"
+
+#: src/main.py:193
+#, python-brace-format
+msgid "✗ Unsupported language: {locale}\n"
+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/messages.pot b/src/i18n/locales/messages.pot
new file mode 100644
index 0000000000000000000000000000000000000000..c1109567f50c75bedae803427b824d588277939c
--- /dev/null
+++ b/src/i18n/locales/messages.pot
@@ -0,0 +1,775 @@
+# SOME DESCRIPTIVE TITLE.
+# Copyright (C) YEAR openEuler Intelligence Project
+# This file is distributed under the same license as the oi-cli package.
+# FIRST AUTHOR , YEAR.
+#
+#, fuzzy
+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:54+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/app/mcp_widgets.py:47
+msgid "需要用户确认是否执行此工具"
+msgstr ""
+
+#: src/app/mcp_widgets.py:51 src/backend/hermes/mcp_helpers.py:286
+msgid "低风险"
+msgstr ""
+
+#: src/app/mcp_widgets.py:52 src/backend/hermes/mcp_helpers.py:287
+msgid "中等风险"
+msgstr ""
+
+#: src/app/mcp_widgets.py:53 src/backend/hermes/mcp_helpers.py:288
+msgid "高风险"
+msgstr ""
+
+#: src/app/mcp_widgets.py:54 src/backend/hermes/mcp_helpers.py:289
+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 ""
+
+#: 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 Assistant {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/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 src/tool/oi_select_agent.py:67
+#: src/tool/oi_select_agent.py:94
+msgid "智能问答"
+msgstr ""
+
+#: src/app/dialogs/agent.py:113
+msgid "OS 智能助手"
+msgstr ""
+
+#: src/app/dialogs/agent.py:115
+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 ""
+
+#: 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:191
+#, python-brace-format
+msgid "✓ Language set to: {locale}\n"
+msgstr ""
+
+#: src/main.py:193
+#, python-brace-format
+msgid "✗ Unsupported language: {locale}\n"
+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/i18n/locales/zh_CN/LC_MESSAGES/messages.po b/src/i18n/locales/zh_CN/LC_MESSAGES/messages.po
new file mode 100644
index 0000000000000000000000000000000000000000..c0926a41829e5e31f074479285352f96dc871228
--- /dev/null
+++ b/src/i18n/locales/zh_CN/LC_MESSAGES/messages.po
@@ -0,0 +1,790 @@
+# Simplified Chinese translations for oi-cli package.
+# Copyright (C) 2025 openEuler Intelligence Project
+# This file is distributed under the same license as the oi-cli package.
+#
+msgid ""
+msgstr ""
+"Project-Id-Version: oi-cli\n"
+"Report-Msgid-Bugs-To: contact@openeuler.org\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"
+"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"
+"X-Generator: Poedit 3.8\n"
+
+#: src/app/mcp_widgets.py:47
+msgid "需要用户确认是否执行此工具"
+msgstr "需要用户确认是否执行此工具"
+
+#: src/app/mcp_widgets.py:51 src/backend/hermes/mcp_helpers.py:286
+msgid "低风险"
+msgstr "低风险"
+
+#: src/app/mcp_widgets.py:52 src/backend/hermes/mcp_helpers.py:287
+msgid "中等风险"
+msgstr "中等风险"
+
+#: src/app/mcp_widgets.py:53 src/backend/hermes/mcp_helpers.py:288
+msgid "高风险"
+msgstr "高风险"
+
+#: src/app/mcp_widgets.py:54 src/backend/hermes/mcp_helpers.py:289
+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 "输入命令或问题..."
+
+#: 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 Assistant {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/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 src/tool/oi_select_agent.py:67
+#: src/tool/oi_select_agent.py:94
+msgid "智能问答"
+msgstr "智能问答"
+
+#: src/app/dialogs/agent.py:113
+msgid "OS 智能助手"
+msgstr "OS 智能助手"
+
+#: src/app/dialogs/agent.py:115
+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 - 智能命令行助手"
+
+#: 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:191
+#, python-brace-format
+msgid "✓ Language set to: {locale}\n"
+msgstr "✓ 语言已设置为: {locale}\n"
+
+#: src/main.py:193
+#, python-brace-format
+msgid "✗ Unsupported language: {locale}\n"
+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 失败"
diff --git a/src/i18n/manager.py b/src/i18n/manager.py
new file mode 100644
index 0000000000000000000000000000000000000000..269c7c8461b135e7173a45f0d01ea5684a45bb6b
--- /dev/null
+++ b/src/i18n/manager.py
@@ -0,0 +1,223 @@
+"""国际化管理模块"""
+
+import gettext
+import locale
+from pathlib import Path
+from typing import ClassVar
+
+# 支持的语言列表
+SUPPORTED_LOCALES = {
+ "en_US": "English",
+ "zh_CN": "简体中文",
+}
+
+# 备用语言(当系统语言无法检测时使用)
+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:
+ """国际化管理器"""
+
+ _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:
+ 检测到的语言代码,如果不支持则返回默认语言
+
+ """
+ return _detect_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 c228c4fce6820d66588ec8c5b30034f466b9bfe0..bc2dd290644bbf969831f75ac19b5174fce0c23e 100644
--- a/src/main.py
+++ b/src/main.py
@@ -6,9 +6,9 @@ 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
from log.manager import (
cleanup_empty_logs,
disable_console_output,
@@ -24,76 +24,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 +136,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 +152,47 @@ 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()
+
+ # 初始化国际化系统
+ # 如果配置中没有设置语言(空字符串),则自动检测系统语言
+ 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()
+ # 处理语言设置参数
+ 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 +209,6 @@ def main() -> None:
llm_config()
return
- # 初始化配置和日志系统
- config_manager = ConfigManager()
-
# 处理命令行参数设置的日志级别
if args.log_level:
set_log_level(config_manager, args.log_level)
@@ -177,10 +221,13 @@ def main() -> None:
logger = get_logger(__name__)
try:
+ # 延迟导入 IntelligentTerminal,确保在 i18n 初始化之后
+ from app.tui import IntelligentTerminal # noqa: PLC0415
+
app = IntelligentTerminal()
app.run()
except Exception:
- logger.exception("智能 Shell 应用发生致命错误")
+ logger.exception(_("Fatal error in Intelligent Shell application"))
raise
diff --git a/src/tool/command_processor.py b/src/tool/command_processor.py
index c24b082a93780e77563dd1d162606440ce090aa2..bfa91be860c25aa3a8e55eac4e2eec557228e467 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_backend_init.py b/src/tool/oi_backend_init.py
index 551d91dd005e4001d7f89a10a840b416a710fb22..af07b372bfd70e17d954aaa10172cea8b592536a 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("刷新用户配置时发生未预期的错误")
diff --git a/src/tool/oi_llm_config.py b/src/tool/oi_llm_config.py
index 9d2c9611a8af463a4a9ff890cac1bba4f01b77b9..b6372d2e4cf9475ab9ab228067293c42db89d395 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 6818bbaedd95fcf2368cf82eeb0ca4ada12af4c0..c13023b28e444ad8b7bd51a0e628b236b2b5c40d 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 6439917bdb468510f6ecaa6629dbbabc5cbc83d3..23ff210c8e7b6c3d0a649ebade178c5124fc8d58 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