乐于分享
好东西不私藏

Hermes Agent 源码-国际化与多语言支持

Hermes Agent 源码-国际化与多语言支持

Hermes Agent 源码解析

第 19 讲:国际化与多语言支持

基于 Hermes Agent v0.16.0 源码 · 2026-07-02

一、为什么需要国际化

Hermes Agent 是一个面向全球开发者的工具——从 CLI 到 Telegram/Discord 网关,从 TUI 到桌面应用,用户遍布世界各地。如果所有提示信息都是英文的,非英语用户的使用体验会大打折扣。

但 Hermes 的国际化策略非常克制:它只翻译 Hermes 自身产生的静态用户界面消息(如审批提示、网关命令回复),而不翻译 Agent 生成的回复、日志、工具输出或命令描述。这一设计选择背后有深刻的原因。

💡 设计哲学:薄切片(Thin Slice)

只翻译最高影响的静态字符串——审批提示、网关斜杠命令回复、重启排空通知。Agent 生成的内容、日志、工具输出保持英文。

二、i18n 模块架构总览

Hermes 的国际化系统集中在 agent/i18n.py(302 行),不依赖任何第三方 i18n 框架(如 Babel、gettext),而是用纯 Python + PyYAML 实现了一套轻量级翻译引擎。

用户调用 t("gateway.reset.header_default")

语言解析链

get_language() → 环境 → 配置 → 默认

目录加载

_load_catalog(lang) → YAML → 扁平化

键值查找 + 回退

t(key, lang, **kwargs) → 翻译结果

三、语言解析链:四级优先级

Hermes 的语言解析遵循严格的优先级链,确保测试、开发和生产环境都能正确获取用户语言偏好。

优先级 来源 说明
1(最高) t() 的 lang 参数 显式指定语言,覆盖一切
2 HERMES_LANGUAGE 环境变量 测试/快速切换用
3 config.yaml 的 display.language 用户配置,进程级缓存
4(默认) "en"(英文) 兜底默认值

📄 agent/i18n.py (第 241-249 行)

def get_language() -> str:
    """Resolve the active language using env > config > default order."""
    env_lang = os.environ.get("HERMES_LANGUAGE")
    if env_lang:
        return _normalize_lang(env_lang)
    cfg_lang = _config_language_cached()
    if cfg_lang:
        return cfg_lang
    return DEFAULT_LANGUAGE

注意 _config_language_cached() 使用了 @lru_cache(maxsize=1) 装饰器——因为 t() 在热路径上调用(每次审批提示、每次网关回复),反复读取 YAML 配置会浪费性能。当配置变更时(如设置向导后),调用 reset_language_cache() 即可刷新。

四、语言别名系统:50+ 种输入映射到 16 种语言

Hermes 支持 16 种语言目录,但用户输入的语言标识可以非常灵活。系统内置了 _LANGUAGE_ALIASES 映射表,将自然语言名、BCP-47 区域标签、甚至非拉丁字符都映射到标准语言代码。

语言 支持别名 目录文件
简体中文 chinese, mandarin, zh-cn, zh-hans zh.yaml
繁体中文 zh-tw, zh-hk, traditional-chinese zh-hant.yaml
日语 japanese, jp, ja-jp ja.yaml
德语 german, deutsch, de-de, de-at de.yaml
西班牙语 spanish, español, es-mx, es-ar es.yaml
法语 french, français, fr-fr, fr-ca fr.yaml
乌克兰语 ukrainian, українська, ua uk.yaml
俄语 russian, русский, ru-ru ru.yaml
韩语 korean, 한국어, ko-kr ko.yaml
葡萄牙语 portuguese, português, pt-br pt.yaml
土耳其语 turkish, türkçe, tr-tr tr.yaml
其他 意大利语、爱尔兰语、匈牙利语、南非语 it/ga/hu/af.yaml

📄 agent/i18n.py (第 43-82 行)

SUPPORTED_LANGUAGES: tuple[str, ...] = (
    "en", "zh", "zh-hant", "ja", "de", "es", "fr", "tr", "uk",
    "af", "ko", "it", "ga", "pt", "ru", "hu",
)

_LANGUAGE_ALIASES: dict[str, str] = {
    "english": "en", "en-us": "en", "en-gb": "en",
    "chinese": "zh", "mandarin": "zh", "zh-cn": "zh",
    "zh-tw": "zh-hant", "zh-hk": "zh-hant",
    "japanese": "ja", "jp": "ja", "ja-jp": "ja",
    "german": "de", "deutsch": "de",
    "spanish": "es", "español": "es",
    "french": "fr", "français": "fr",
    "ukrainian": "uk", "українська": "uk",
    "korean": "ko", "한국어": "ko",
    "russian": "ru", "русский": "ru",
    # ... 共 50+ 条映射
}

亮点:系统甚至支持非拉丁字符输入(如 українська한국어русский),用户可以直接用自己母语的语言名来设置。

五、YAML 目录加载与扁平化

每个语言目录存放在 locales/<lang>.yaml,采用嵌套 YAML 结构提高可读性,但运行时会被扁平化为点号分隔的键值对。

📄 目录文件结构示例 (locales/en.yaml)

# 嵌套 YAML(人类可读)
approval:
  dangerous_header: "⚠️  DANGEROUS COMMAND: {description}"
  choose_long:     "      [o]nce  |  [s]ession  |  [a]lways  |  [d]eny"

gateway:
  model:
    switched:               "Model switched to `{model}`"
    provider_label:         "Provider: {provider}"
  agents:
    header:                 "🤖 **Active Agents & Tasks**"

加载时,<_flatten_into> 递归将嵌套结构扁平化:

📄 agent/i18n.py (第 200-207 行)

def _flatten_into(node: Any, prefix: str, out: dict[str, str]) -> None:
    if isinstance(node, dict):
        for key, value in node.items():
            child_key = f"{prefix}.{key}" if prefix else str(key)
            _flatten_into(value, child_key, out)
    elif isinstance(node, str):
        out[prefix] = node
    # Non-string, non-dict leaves are ignored -- catalogs are text-only.

扁平化后,gateway.model.switched 这样的点号路径可以直接在 t() 中查找。

目录位置解析:三级回退链

_locales_dir() 函数实现了三级回退链,确保在任何安装方式下都能找到语言目录:

HERMES_BUNDLED_LOCALES 环境变量

Nix 包管理器 / 密封安装包使用

↓ 不存在则回退

<repo-root>/locales

源码安装 / pip install -e

↓ 不存在则回退

sysconfig(data|purelib|platlib)/locales

pip wheel 安装

六、t() 翻译函数:三级回退机制

t() 是 i18n 模块的核心入口函数,实现了三级回退确保永远不会崩溃:

第 1 级:目标语言目录查找

在 zh.yaml 中查找 "gateway.reset.header_default"

↓ 未找到 → 回退

第 2 级:英文目录查找

在 en.yaml 中查找同一键

↓ 仍未找到 → 回退

第 3 级:返回原始键路径

返回 "gateway.reset.header_default" 本身

📄 agent/i18n.py (第 252-293 行)

def t(key: str, lang: str | None = None, **format_kwargs: Any) -> str:
    target = _normalize_lang(lang) if lang else get_language()
    catalog = _load_catalog(target)
    value = catalog.get(key)

    if value is None and target != DEFAULT_LANGUAGE:
        # Fall through to English rather than showing a key path.
        value = _load_catalog(DEFAULT_LANGUAGE).get(key)

    if value is None:
        # Last-ditch: return the key itself. A broken catalog
        # should not crash anything; it just looks ugly.
        logger.debug("i18n miss: key=%r lang=%r", key, target)
        value = key

    if format_kwargs:
        try:
            return value.format(**format_kwargs)
        except (KeyError, IndexError, ValueError) as exc:
            logger.warning("i18n format failed: %s", exc)
            return value
    return value

占位符格式化:翻译字符串中的 {placeholder} 通过 str.format() 替换。例如 t("gateway.draining", count=3) 会将 {count} 替换为 3

七、目录覆盖范围与实际使用

英文目录 en.yaml 是翻译的事实来源(source of truth),共 386 行,涵盖以下功能域:

功能域 键前缀 示例
审批提示 approval.* 危险命令确认、选择菜单
网关命令 gateway.* /reset、/model、/agents 等回复
模型切换 gateway.model.* 模型信息展示标签
代理状态 gateway.agents.* 活跃代理与任务列表
上下文压缩 gateway.compress.* 压缩状态与错误消息
对话分支 gateway.branch.* 分支创建与切换消息
调试与状态 gateway.debug.* 调试报告上传状态
MCP 重载 gateway.reload_mcp.* MCP 服务器重载通知

这些翻译在 gateway/run.pygateway/slash_commands.pytools/approval.py 中被大量调用。以 slash_commands.py 为例,/reset 命令就使用了 6 个不同的 i18n 键来处理不同场景(默认标题、新会话标题、AI 生成标题、标题错误等)。

八、测试保障:目录一致性测试

tests/agent/test_i18n.py(229 行)提供了完善的测试保障,确保翻译质量:

🔹 目录文件存在性测试:每个支持的语言必须有对应的 YAML 文件

🔹 键一致性测试:每个非英文目录必须与英文目录有完全相同的键集合(不能多也不能少)

🔹 占位符一致性测试:翻译文本中的 {placeholder} 必须与英文完全一致(防止拼写错误导致运行时 KeyError)

🔹 语言解析测试:验证别名映射、环境变量覆盖、默认回退等

🔹 目录回退链测试:验证三级回退机制(目标语言 → 英文 → 原始键)

🔹 安装路径回退测试:验证 Nix、pip wheel、源码安装三种场景下的目录解析

📄 tests/agent/test_i18n.py (第 44-74 行)

@pytest.mark.parametrize("lang", [l for l in SUPPORTED_LANGUAGES if l != "en"])
def test_catalog_keys_match_english(lang: str):
    """Every non-English catalog must have exactly the same key set."""
    en_keys = set(_flatten(_load_raw("en")).keys())
    lang_keys = set(_flatten(_load_raw(lang)).keys())
    missing = en_keys - lang_keys
    extra = lang_keys - en_keys
    assert not missing, f"{lang}.yaml missing keys: {sorted(missing)}"
    assert not extra, f"{lang}.yaml has keys not in en.yaml: {sorted(extra)}"

@pytest.mark.parametrize("lang", list(SUPPORTED_LANGUAGES))
def test_catalog_placeholders_match_english(lang: str):
    """Every translated value must use the same {placeholder} tokens."""
    placeholder_re = re.compile(r"\{([a-zA-Z_][a-zA-Z0-9_]*)\}")
    for key, en_value in en_flat.items():
        en_placeholders = set(placeholder_re.findall(en_value))
        lang_placeholders = set(placeholder_re.findall(lang_flat.get(key, "")))
        assert en_placeholders == lang_placeholders

九、配置与用户设置

用户可以通过 config.yamldisplay.language 字段设置界面语言:

📄 config.yaml 配置示例

display:
  language: zh          # 简体中文
  # 支持: en, zh, zh-hant, ja, de, es, fr, tr, uk,
  #       af, ko, it, ga, pt, ru, hu
  # 未知值回退到 en

也可以通过环境变量快速切换(测试/临时使用):

📄 环境变量方式

# 临时切换为日语
export HERMES_LANGUAGE=ja
hermes chat

# 或直接指定
HERMES_LANGUAGE=zh hermes tools

十、设计亮点与不足

亮点

🔹 零外部依赖:不依赖 Babel/gettext,只用 PyYAML,减少安装包体积

🔹 三级回退:目标语言 → 英文 → 原始键,确保永不崩溃

🔹 50+ 别名映射:支持自然语言名、BCP-47 标签、非拉丁字符输入

🔹 进程级缓存:目录和配置都做了缓存,热路径不影响性能

🔹 目录一致性测试:强制所有语言目录与英文保持键一致

🔹 三级安装路径回退:Nix / pip wheel / 源码安装都能正确找到目录

不足

🔹 覆盖范围有限:只翻译静态消息,Agent 生成的内容仍是英文

🔹 无复数形式支持:没有 clDRule 或 nplurals 机制,复数通过占位符手动处理

🔹 无 RTL 支持:没有阿拉伯语/希伯来语等从右到左语言的目录

🔹 手动翻译维护:没有 CI 自动检测新键,需要开发者手动同步所有目录

十一、总结

Hermes Agent 的国际化系统是一个克制但精妙的设计。它不追求全面翻译,而是聚焦在最影响用户体验的静态界面上,用 302 行代码实现了 16 种语言的支持。三级回退机制确保系统在任何情况下都不会崩溃,而完善的测试保障则维护了翻译质量。

下一讲我们将进行 Hermes Agent 系列的总结与实战指南,回顾整个系列的核心要点。

📦 本讲核心文件

agent/i18n.py(302 行)— i18n 引擎

locales/(16 个 YAML 文件)— 语言目录

tests/agent/test_i18n.py(229 行)— 一致性测试

hermes_cli/config.py(display.language)— 语言配置

← 第 18 讲:Hermes Agent 源码-TUI、Dashboard 与桌面应用

第 20 讲:Hermes Agent 总结与实战指南 →

关注公众号获取更多技术干货

源码:https://github.com/NousResearch/hermes-agent