国际化 (i18n) 系统
ErisPulse v2.5.0 起内置了完整的国际化支持。框架核心及 CLI 界面均可根据您的系统语言自动切换显示文本,也支持外部模块注册自己的翻译。
支持的语言
| 语言 | 代码 | 说明 |
|---|---|---|
| 简体中文 | zh-CN |
默认语言(框架原生语言) |
| 繁體中文 | zh-TW |
繁体中文(香港/澳门/台湾) |
| English | en |
英文(通用回退语言) |
| 日本語 | ja |
日文 |
| Русский | ru |
俄文 |
快速体验
通过环境变量切换
# Windows PowerShell
$env:ERISPULSE_LANG = "en"
epsdk run
# macOS / Linux
ERISPULSE_LANG=ja epsdk run
通过配置文件切换
在 config/config.toml 中添加:
[ErisPulse.i18n]
language = "zh-TW"
设为 "auto"(默认值)则自动检测系统语言。
在代码中手动切换
from ErisPulse import i18n
# 手动设置语言
i18n.set_language("en")
print(i18n.get_language()) # "en"
# 重置为自动检测
i18n.reset_language()
语言检测机制
框架按以下优先级检测用户语言:
- 环境变量
ERISPULSE_LANG— 最高优先级,用于测试和临时切换 - Windows API —
GetUserDefaultLocaleName(仅 Windows,不受 Git Bash 等工具覆盖LANG的影响) - 环境变量 —
LANGUAGE>LC_ALL>LC_MESSAGES>LANG(Unix/macOS 标准) - 系统 Locale —
locale.getlocale()/locale.getdefaultlocale() - 兜底 — en(英文)
就近映射原则
当检测到的语言不是精确匹配时,按就近原则映射到支持的语言:
zh-TW,zh-HK,zh-MO,zh-Hant→ 繁体中文- 其他所有
zh-*(如zh-CN,zh-SG)→ 简体中文 en-US,en-GB,en-AU等 → 英文ja-JP→ 日文ru-RU→ 俄文- 其他未识别语言 → 简体中文(兜底)
在模块中使用 i18n
您可以为自己的模块注册翻译文本,让您的模块也支持多语言。
推荐写法:通过 I18nClass 声明翻译键(v2.7.0+)
从 v2.7.0 起,模块/适配器可以像声明 ConfigClass 一样,通过嵌套类 I18nClass 声明翻译键。框架会在加载时自动注册所有声明的翻译键,无需手动调用 i18n.register()。
from dataclasses import dataclass, field
from ErisPulse.Core.Bases import BaseConfig, BaseI18n, BaseModule, I18nKey
class MyModule(BaseModule):
# 配置类(可选)
@dataclass
class ConfigClass(BaseConfig):
welcome_msg: str = field(
default="欢迎",
metadata={
# 这里引用了 i18n 键 mymodule.welcome_msg
"description": {"i18n": "mymodule.welcome_msg", "default": "欢迎消息"},
},
)
# 翻译键集合类(可选)
# 声明的键会被框架自动注册,优先级早于 ConfigClass 生成默认配置
class I18nClass(BaseI18n):
# 属性名自动拼接为完整键路径:<模块名>.<属性名>
welcome_msg: I18nKey = I18nKey(
default="Welcome Message", # 语言无关的兜底,不注册到任何语言
zh_CN="欢迎消息",
en="Welcome Message",
ja="ウェルカムメッセージ",
ru="Приветственное сообщение",
zh_TW="歡迎訊息",
)
# 业务用到的其他翻译键
hello: I18nKey = I18nKey(
default="Hello, {name}!",
zh_CN="你好,{name}!",
zh_TW="你好,{name}!",
en="Hello, {name}!",
ja="こんにちは、{name}!",
ru="Привет, {name}!",
)
# 也可以显式指定完整键路径(不使用属性名拼接)
custom: I18nKey = I18nKey(
key="mymodule.deep.nested.key",
default="Default text",
zh_CN="默认文本",
zh_TW="預設文本",
en="Default text",
ja="デフォルトテキスト",
ru="Текст по умолчанию",
)
为什么推荐 I18nClass?
| 场景 | 手动 i18n.register() | I18nClass 声明式 |
|---|---|---|
| 配置描述引用的 i18n 键 | 需手动注册,且要赶在配置生成前 | 框架自动在配置生成前注册 |
| 多语言翻译声明 | 散落在各个 on_load() 中 | 集中在类里,一目了然 |
| 键名命名一致性 | 容易拼写错误 | 属性名作为键名后缀,IDE 可补全 |
| 卸载时清理 | 需手动 unregister_domain() | 框架使用统一 domain 注册 |
I18nClass 的键路径规则
- 默认:使用
<模块注册名>.<属性名>作为完整键路径- 示例:模块名为
MyModule,属性welcome→ 键路径MyModule.welcome
- 示例:模块名为
- 显式:通过
I18nKey(key="...")参数指定任意点分路径- 适合深层嵌套的键名(如
mymodule.config.basic.token)
- 适合深层嵌套的键名(如
在适配器中使用
适配器同样支持 I18nClass,使用方式完全一致:
from ErisPulse.Core import BaseAdapter
from ErisPulse.Core.Bases import BaseConfig, BaseI18n, I18nKey
class MyAdapter(BaseAdapter):
@dataclass
class ConfigClass(BaseConfig):
endpoint: str = field(
default="",
metadata={
# 配置描述引用了 adapter.MyAdapter.endpoint 键
"description": {"i18n": "MyAdapter.endpoint", "default": "API 地址"},
},
)
class I18nClass(BaseI18n):
# 集中声明配置描述引用的键与其他业务键的多语言译文
endpoint: I18nKey = I18nKey(
default="API Endpoint",
zh_CN="API 地址",
zh_TW="API 位址",
en="API Endpoint",
ja="APIアドレス",
ru="API адрес",
)
适配器的 I18nClass 会在 __init__ 阶段(即配置模板生成之前)自动注册,确保配置描述引用的 i18n 键已可用。
手动注册自定义翻译(旧写法)
如果不使用 I18nClass,也可以直接调用 i18n.register() 注册翻译文本。
from ErisPulse import i18n
# 注册中文翻译
i18n.register("zh-CN", {
"my_module.welcome": "欢迎使用我的模块!",
"my_module.goodbye": "再见!",
"my_module.hello": "你好,{name}!",
}, domain="my_module")
# 注册英文翻译
i18n.register("en", {
"my_module.welcome": "Welcome to my module!",
"my_module.goodbye": "Goodbye!",
"my_module.hello": "Hello, {name}!",
}, domain="my_module")
使用翻译
from ErisPulse import i18n
# 简单翻译
i18n.t("my_module.welcome") # 自动使用当前语言
# 带格式化参数
i18n.t("my_module.hello", name="Alice")
# 指定默认值(翻译键不存在时返回)
i18n.t("my_module.unknown_key", default="默认文本")
在模块类中使用
from dataclasses import dataclass, field
from ErisPulse import i18n
from ErisPulse.Core.Bases import BaseConfig, BaseModule
@dataclass
class MyModuleConfig(BaseConfig):
welcome_msg: str = field(
default="欢迎",
metadata={
"description": {"i18n": "my_module.welcome_msg", "default": "欢迎消息"},
"ui": {"widget": "text", "group": "basic", "order": 1},
},
)
class MyModule(BaseModule):
ConfigClass = MyModuleConfig
async def on_load(self, event):
# 实时读取配置(每次访问都反映最新值)
self.logger.info(self.cfg.welcome_msg)
self.logger.info(i18n.t("my_module.welcome"))
@command("hello")
async def hello_handler(self, event):
name = event.get_user_nickname() or "friend"
await event.reply(i18n.t("my_module.hello", name=name))
async def on_unload(self, event):
pass
卸载翻译
# 卸载整个域的翻译
i18n.unregister_domain("my_module")
配置字段多语言
从 v2.5.2 起,配置 Schema 全面支持 i18n。所有用户可见的文本字段均可引用 i18n 键,WebUI 和其他消费者会自动根据当前语言解析为对应文本。
支持的 i18n 字段
| 字段 | 位置 | 说明 |
|---|---|---|
description |
field metadata | 字段描述 |
options[].label |
ui.options |
select 控件选项标签 |
placeholder |
ui.placeholder |
输入框占位符 |
group_labels |
_schema_meta |
分组显示名(Dashboard 分区标题) |
统一采用 {"i18n": "key", "default": "文本"} 格式,纯字符串则原样透传(向后兼容)。
声明 i18n 字段
所有用户可见文本字段都支持 i18n:
from dataclasses import dataclass, field
from ErisPulse.Core.Bases import BaseConfig
@dataclass
class MyAdapterConfig(BaseConfig):
# description i18n
token: str = field(
default="",
metadata={
"description": {"i18n": "my_adapter.token", "default": "平台 Token"},
"required": True,
"secret": True,
"ui": {
"widget": "password",
"group": "basic",
"order": 1,
# placeholder i18n
"placeholder": {"i18n": "my_adapter.token.ph", "default": "请输入 Token"},
},
},
)
# options label i18n
mode: str = field(
default="a",
metadata={
"description": {"i18n": "my_adapter.mode", "default": "运行模式"},
"ui": {
"widget": "select",
"group": "basic",
"order": 2,
"options": [
{"label": {"i18n": "my_adapter.mode.a", "default": "模式A"}, "value": "a"},
{"label": {"i18n": "my_adapter.mode.b", "default": "模式B"}, "value": "b"},
],
},
},
)
# group_labels i18n(分组显示名)
_schema_meta = {
"group_labels": {
"basic": {"i18n": "my_adapter.group.basic", "default": "基本设置"},
}
}
default 是兜底文本——当翻译未注册或查找失败时显示。
secret 脱敏与配置校验
标记为 "secret": True 的字段会自动获得脱敏保护(2.7.0 起):
- 模板生成脱敏:
dataclass_to_toml_with_comments()生成配置模板时,secret 字段的真实值不会写入文件(显示为空占位),避免敏感信息落盘 - 通用脱敏工具:
redact_secret(value)将非空值替换为***,空值原样返回,可用于日志输出等场景
from ErisPulse.Core.Bases.config_schema import redact_secret
redact_secret("sk-xxxxxx") # '***'
redact_secret("") # ''
配置校验(validate_config())除 required 非空检查外,2.7.0 起支持:
| 校验项 | 元数据 | 示例 |
|---|---|---|
| 类型匹配 | 字段声明类型 | int 字段传入字符串报错 |
| 枚举约束 | ui.options 或顶层 options |
值必须属于允许选项 |
| 数值范围 | 顶层 min / max |
metadata={"min": 1, "max": 65535} |
from ErisPulse.Core.Bases.config_schema import validate_config
@dataclass
class C(BaseConfig):
mode: str = field(default="a", metadata={"ui": {"widget": "select", "options": ["a", "b"]}})
port: int = field(default=80, metadata={"min": 1, "max": 65535})
errors = validate_config(C(mode="x", port=70000)) # 两条错误:枚举 + 范围
注册配置翻译
配置字段的 i18n 键和普通翻译键一样,使用 i18n.register() 注册:
from ErisPulse import i18n
# 注册中文(与 default 一致,也可以不同)
i18n.register("zh-CN", {
"my_adapter.token": "平台 Token",
}, domain="my_adapter")
# 注册英文
i18n.register("en", {
"my_adapter.token": "Platform Token",
}, domain="my_adapter")
推荐写法:使用
I18nClass声明翻译键,框架会自动注册(详见上文「推荐写法」章节), 无需手动调用i18n.register()或register_config_i18n()。
也提供了便捷函数 register_config_i18n(),可自动从配置类提取键并注册:
from ErisPulse.Core.Bases.config_schema import register_config_i18n
# 自动提取 description.default 作为 zh-CN 翻译
register_config_i18n(MyAdapterConfig, "zh-CN")
# 手动提供英文翻译
register_config_i18n(MyAdapterConfig, "en", {
"my_adapter.token": "Platform Token",
})
WebUI 如何消费
get_config_schema() 返回的 schema 中,i18n 字典会原样透传。WebUI 前端可以根据当前语言调用 i18n.t() 解析。
如果需要服务端直接解析为字符串(如返回给不支持 i18n 的前端),使用 resolve_config_schema(),它会将 description、options[].label、placeholder、group_labels 全部解析为当前语言的文本:
from ErisPulse.Core.Bases.config_schema import resolve_config_schema
# 所有 i18n 字段已解析为当前语言的字符串
schema = resolve_config_schema(MyAdapterConfig)
print(schema["fields"]["token"]["description"]) # "平台 Token" 或 "Platform Token"
print(schema["fields"]["token"]["placeholder"]) # "请输入 Token" 或 "Enter Token"
print(schema["fields"]["mode"]["options"][0]["label"]) # "模式A" 或 "Mode A"
print(schema["group_labels"]["basic"]) # "基本设置" 或 "Basic"
BaseConfig、BotAccountConfig、register_config_i18n()、resolve_config_schema()等类型与工具函数的实际定义位于ErisPulse.Core.Bases.config_schema。ErisPulse.runtime.config_schema保留为兼容性 shim, 推荐从ErisPulse.Core.Bases统一导入(i18n 翻译键相关类型除外, 它们位于ErisPulse.Core.Bases.i18n_schema)。
API 参考
I18nManager
核心方法
| 方法 | 说明 |
|---|---|
t(key, default=None, **kwargs) |
获取翻译文本(gettext() 是别名) |
set_language(lang) |
手动设置语言 |
get_language() |
获取当前语言 |
reset_language() |
重置为自动检测(并重新检测环境) |
get_supported_languages() |
获取所有支持的语言列表 |
has_translation(key, lang=None) |
检查翻译键是否存在 |
register(lang, translations, domain) |
注册自定义翻译 |
unregister_domain(domain) |
卸载指定域的所有翻译 |
reload() |
重新加载内置翻译并重新检测语言 |
t() 方法详解
def t(self, key, /, default=None, **kwargs):
key— 翻译键(仅位置参数,不与**kwargs中的key=冲突)default— 翻译不存在时返回的默认值,默认为None(返回键名本身)**kwargs— 格式化参数,用于填充翻译值中的{placeholder}
示例:
# 翻译定义: "greeting": "你好,{name}!欢迎来到{place}。"
i18n.t("greeting", name="Alice", place="ErisPulse")
# 返回: "你好,Alice!欢迎来到ErisPulse。"
BaseI18n / I18nKey(声明式翻译键)
从 v2.7.0 起,ErisPulse.Core.Bases 提供了基于类属性的翻译键声明工具(推荐从 ErisPulse.Core.Bases 统一导入):
I18nKey.default是语言无关的兜底文本,不会注册到任何语言。 要让翻译生效,必须显式传入至少一个语言参数(zh_CN=/en=/ja=等)。 这样各国开发者可以自由使用自己母语填写default,框架不做任何假设。
| 名称 | 说明 |
|---|---|
I18nKey(default, *, key=None, zh_CN, zh_TW, en, ja, ru) |
单个翻译键声明,default 为语言无关的兜底 |
BaseI18n |
翻译键集合基类(命名对齐 BaseConfig),子类以类属性声明多个 I18nKey |
BaseI18n.register(prefix="", domain="app") |
类方法:注册所有声明的键到 i18n系统 |
key |
I18nKey 的别名(书写更简洁) |
使用示例:
from ErisPulse.Core.Bases import BaseI18n, key
class MyKeys(BaseI18n):
# 简洁别名写法
hello = key(
default="Hello",
zh_CN="你好",
zh_TW="你好",
en="Hello",
ja="こんにちは",
ru="Привет",
)
bye = key(
default="Bye",
zh_CN="再见",
zh_TW="再見",
en="Bye",
ja="さようなら",
ru="До свидания",
)
# 独立使用(手动注册)
MyKeys.register(prefix="myapp.", domain="myapp")
从 SDK 实例访问
from ErisPulse import sdk
# sdk.i18n 与直接导入的 i18n 是同一个对象
sdk.i18n.set_language("en")
print(sdk.i18n.t("core.sdk.init.starting"))
运行时配置
通过配置 API 读取 i18n 配置
from ErisPulse.Core.Bases import I18nConfig
from ErisPulse.runtime import get_i18n_config
config = get_i18n_config()
print(config["language"]) # "auto" 或具体语言代码
# I18nConfig 是 dataclass,可用于生成配置模板
schema = I18nConfig.__dataclass_fields__
配置项说明
在 config/config.toml 的 [ErisPulse.i18n] 部分:
[ErisPulse.i18n]
# 显示语言,可选值:
# - "auto" — 自动检测系统语言(默认)
# - "zh-CN" — 简体中文
# - "zh-TW" — 繁体中文
# - "en" — 英文
# - "ja" — 日文
# - "ru" — 俄文
language = "auto"
最佳实践
翻译键命名
建议使用点号分隔的命名空间格式:
<模块名>.<类别>.<描述>
例如:my_module.command.hello_desc、core.adapter.start_failed
多语言覆盖
不必一次性提供所有语言的翻译,缺失的语言会自动回退到英文,如果英文也没有则显示键名本身。
动态内容
对于动态生成的内容(如用户名、数量等),使用 {placeholder} 格式化:
# 翻译定义
"user_count": "当前在线用户:{count} 人"
# 使用
i18n.t("user_count", count=len(users))
日志消息
如果您的模块使用了框架的 Logger,这些消息也会自动使用当前语言:
self.logger.info(i18n.t("my_module.startup"))
与 CLI i18n 的关系
CLI 拥有独立的国际化模块(ErisPulse.CLI.i18n),与框架核心的国际化模块完全解耦。
- Core i18n — 框架核心模块使用,外部模块可注册翻译
- CLI i18n — 命令行界面内部使用,不与 Core 共享翻译数据
这种设计确保 CLI 的翻译变更不会影响框架核心的稳定性。