简体中文 English 繁體中文 日本語 Русский
本文为静态镜像,内容以交互版为准 在交互式文档中心打开 →

國際化 (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()

語言檢測機制

框架按以下優先級檢測用戶語言:

  1. 環境變數 ERISPULSE_LANG — 最高優先級,用於測試和臨時切換
  2. Windows API — GetUserDefaultLocaleName(僅 Windows,不受 Git Bash 等工具覆蓋 LANG 的影響)
  3. 環境變數 — LANGUAGE > LC_ALL > LC_MESSAGES > LANG(Unix/macOS 標準)
  4. 系統 Locale — locale.getlocale() / locale.getdefaultlocale()
  5. 兜底 — en(英文)

最近映射原則

當檢測到的語言不是精確匹配時,按最近原則映射到支援的語言:


在模組中使用 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 的鍵路徑規則

在適配器中使用

適配器同樣支援 I18nClass,使用方式完全一致:

from ErisPulse 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 起):

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):

示例:

# 翻譯定義: "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),與框架核心的國際化模組完全解耦。

這種設計確保 CLI 的翻譯變更不會影響框架核心的穩定性。