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

Internationalization (i18n) System

ErisPulse v2.5.0 includes full internationalization support. Both the framework core and CLI interface can automatically switch display text according to your system language, and external modules can also register their own translations.

Supported Languages

Language Code Description
Simplified Chinese zh-CN Default language (native framework language)
Traditional Chinese zh-TW Traditional Chinese (Hong Kong/Macau/Taiwan)
English en English (general fallback language)
日本語 ja Japanese
Русский ru Russian

Quick Experience

Switch via Environment Variable

# Windows PowerShell
$env:ERISPULSE_LANG = "en"
epsdk run

# macOS / Linux
ERISPULSE_LANG=ja epsdk run

Switch via Configuration File

Add to config/config.toml:

[ErisPulse.i18n]
language = "zh-TW"

Set to "auto" (default) to automatically detect system language.

Switch Manually in Code

from ErisPulse import i18n

# Manually set language
i18n.set_language("en")
print(i18n.get_language())  # "en"

# Reset to auto-detect
i18n.reset_language()

Language Detection Mechanism

The framework detects user language with the following priority:

  1. Environment variable ERISPULSE_LANG — Highest priority, for testing and temporary switching
  2. Windows API — GetUserDefaultLocaleName (Windows only, not affected by tools like Git Bash that override LANG)
  3. Environment variables — LANGUAGE > LC_ALL > LC_MESSAGES > LANG (Unix/macOS standard)
  4. System Locale — locale.getlocale() / locale.getdefaultlocale()
  5. Fallback — en (English)

Nearest Mapping Principle

When detected language does not match exactly, map to the nearest supported language:


Using i18n in Modules

You can register translation text for your own modules to make them support multiple languages.

From v2.7.0, modules/adapters can declare translation keys by nesting the I18nClass class, similar to declaring a ConfigClass. The framework will automatically register all declared translation keys without requiring manual i18n.register() calls.

from dataclasses import dataclass, field

from ErisPulse.Core.Bases import BaseConfig, BaseI18n, BaseModule, I18nKey


class MyModule(BaseModule):
    # Configuration class (optional)
    @dataclass
    class ConfigClass(BaseConfig):
        welcome_msg: str = field(
            default="欢迎",
            metadata={
                # Reference i18n key mymodule.welcome_msg here
                "description": {"i18n": "mymodule.welcome_msg", "default": "Welcome message"},
            },
        )

    # Translation key collection class (optional)
    # Declared keys will be automatically registered by the framework, with higher priority than default configuration generated from ConfigClass
    class I18nClass(BaseI18n):
        # Property names are automatically concatenated into full key paths: <module name>.<property name>
        welcome_msg: I18nKey = I18nKey(
            default="Welcome Message",   # Language-agnostic fallback, not registered to any language
            zh_CN="欢迎消息",
            en="Welcome Message",
            ja="ウェルカムメッセージ",
            ru="Приветственное сообщение",
            zh_TW="歡迎訊息",
        )
        # Other business-related translation keys
        hello: I18nKey = I18nKey(
            default="Hello, {name}!",
            zh_CN="你好,{name}!",
            zh_TW="你好,{name}!",
            en="Hello, {name}!",
            ja="こんにちは、{name}!",
            ru="Привет, {name}!",
        )

        # You can also explicitly specify the full key path (not using property name concatenation)
        custom: I18nKey = I18nKey(
            key="mymodule.deep.nested.key",
            default="Default text",
            zh_CN="默认文本",
            zh_TW="預設文本",
            en="Default text",
            ja="デフォルトテキスト",
            ru="Текст по умолчанию",
        )
Scenario Manual i18n.register() I18nClass Declarative
Configuration description referencing i18n keys Need manual registration, and must be done before configuration generation Framework automatically registers before configuration generation
Multi-language translation declaration Scattered in various on_load() methods Centralized in class, clearly visible
Key naming consistency Prone to spelling errors Property names as key suffixes, IDE can auto-complete
Unloading cleanup Need manual unregister_domain() Framework uses unified domain registration

I18nClass Key Path Rules

Using in Adapters

Adapters also support I18nClass, with identical usage:

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={
                # Configuration description references adapter.MyAdapter.endpoint key
                "description": {"i18n": "MyAdapter.endpoint", "default": "API address"},
            },
        )

    class I18nClass(BaseI18n):
        # Central declaration of configuration description referenced keys and other business keys' multilingual translations
        endpoint: I18nKey = I18nKey(
            default="API Endpoint",
            zh_CN="API address",
            zh_TW="API address",
            en="API Endpoint",
            ja="API address",
            ru="API address",
        )

The I18nClass of adapters is automatically registered during the __init__ phase (before configuration template generation), ensuring that i18n keys referenced in configuration descriptions are available.

Manual Registration of Custom Translations (Old Approach)

If you do not use I18nClass, you can directly call i18n.register() to register translation text.

from ErisPulse import i18n

# Register Chinese translations
i18n.register("zh-CN", {
    "my_module.welcome": "Welcome to my module!",
    "my_module.goodbye": "Goodbye!",
    "my_module.hello": "Hello, {name}!",
}, domain="my_module")

# Register English translations
i18n.register("en", {
    "my_module.welcome": "Welcome to my module!",
    "my_module.goodbye": "Goodbye!",
    "my_module.hello": "Hello, {name}!",
}, domain="my_module")

Using Translations

from ErisPulse import i18n

# Simple translation
i18n.t("my_module.welcome")  # Automatically uses current language

# With formatted parameters
i18n.t("my_module.hello", name="Alice")

# Specify default value (returns when translation key does not exist)
i18n.t("my_module.unknown_key", default="Default text")

Using in Module Classes

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": "Welcome message"},
            "ui": {"widget": "text", "group": "basic", "order": 1},
        },
    )

class MyModule(BaseModule):
    ConfigClass = MyModuleConfig

    async def on_load(self, event):
        # Real-time configuration access (reflects latest value on each access)
        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

Unregistering Translations

# Unregister all translations in a domain
i18n.unregister_domain("my_module")

Multi-language Configuration Fields

From v2.5.2, configuration Schema fully supports i18n. All user-visible text fields can reference i18n keys, and WebUI and other consumers will automatically resolve them into corresponding text based on the current language.

Supported i18n Fields

Field Location Description
description field metadata Field description
options[].label ui.options Select control option labels
placeholder ui.placeholder Input box placeholder
group_labels _schema_meta Group display names (Dashboard partition titles)

All use the {"i18n": "key", "default": "text"} format. Pure strings are passed through as-is (for backward compatibility).

Declaring i18n Fields

All user-visible text fields support 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": "Platform Token"},
            "required": True,
            "secret": True,
            "ui": {
                "widget": "password",
                "group": "basic",
                "order": 1,
                # placeholder i18n
                "placeholder": {"i18n": "my_adapter.token.ph", "default": "Enter Token"},
            },
        },
    )
    # options label i18n
    mode: str = field(
        default="a",
        metadata={
            "description": {"i18n": "my_adapter.mode", "default": "Operation mode"},
            "ui": {
                "widget": "select",
                "group": "basic",
                "order": 2,
                "options": [
                    {"label": {"i18n": "my_adapter.mode.a", "default": "Mode A"}, "value": "a"},
                    {"label": {"i18n": "my_adapter.mode.b", "default": "Mode B"}, "value": "b"},
                ],
            },
        },
    )

    # group_labels i18n (group display names)
    _schema_meta = {
        "group_labels": {
            "basic": {"i18n": "my_adapter.group.basic", "default": "Basic Settings"},
        }
    }

default is the fallback text—shown when translation is not registered or lookup fails.

Secret Masking and Configuration Validation

Fields marked as "secret": True will automatically receive masking protection (from v2.7.0):

from ErisPulse.Core.Bases.config_schema import redact_secret

redact_secret("sk-xxxxxx")  # '***'
redact_secret("")           # ''

Configuration validation (validate_config()) supports additional checks beyond required non-empty checks (from v2.7.0):

Validation Metadata Example
Type matching Field declaration type int field passed a string raises an error
Enum constraint ui.options or top-level options Value must be among allowed options
Numeric range Top-level 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))  # Two errors: enum + range

Registering Configuration Translations

Configuration field i18n keys and regular translation keys are registered the same way using i18n.register():

from ErisPulse import i18n

# Register Chinese (same as default, but can differ)
i18n.register("zh-CN", {
    "my_adapter.token": "Platform Token",
}, domain="my_adapter")

# Register English
i18n.register("en", {
    "my_adapter.token": "Platform Token",
}, domain="my_adapter")

Recommended approach: Use I18nClass to declare translation keys, and the framework will automatically register them (see the "Recommended approach" section above), eliminating the need to manually call i18n.register() or register_config_i18n().

A convenient function register_config_i18n() is also provided, which automatically extracts keys from the configuration class and registers them:

from ErisPulse.Core.Bases.config_schema import register_config_i18n

# Automatically extract description.default as zh-CN translation
register_config_i18n(MyAdapterConfig, "zh-CN")

# Manually provide English translation
register_config_i18n(MyAdapterConfig, "en", {
    "my_adapter.token": "Platform Token",
})

How WebUI Consumes

get_config_schema() returns a schema where the i18n dictionary is passed through as-is. The WebUI frontend can use i18n.t() to resolve based on the current language.

If you need the server to directly resolve into strings (e.g., for frontends that do not support i18n), use resolve_config_schema(), which resolves description, options[].label, placeholder, and group_labels into the current language's text:

from ErisPulse.Core.Bases.config_schema import resolve_config_schema

# All i18n fields are resolved into the current language's string
schema = resolve_config_schema(MyAdapterConfig)
print(schema["fields"]["token"]["description"])    # "Platform Token" or "Platform Token"
print(schema["fields"]["token"]["placeholder"])   # "Enter Token" or "Enter Token"
print(schema["fields"]["mode"]["options"][0]["label"])  # "Mode A" or "Mode A"
print(schema["group_labels"]["basic"])             # "Basic Settings" or "Basic"

BaseConfig, BotAccountConfig, register_config_i18n(), resolve_config_schema() and other types and utility functions are actually defined in ErisPulse.Core.Bases.config_schema. ErisPulse.runtime.config_schema is retained as a compatibility shim, recommended to import uniformly from ErisPulse.Core.Bases (except for i18n translation key related types, which are located in ErisPulse.Core.Bases.i18n_schema).

API Reference

I18nManager

Core Methods

Method Description
t(key, default=None, **kwargs) Get translated text (gettext() is an alias)
set_language(lang) Manually set language
get_language() Get current language
reset_language() Reset to auto-detection (and re-detect environment)
get_supported_languages() Get list of all supported languages
has_translation(key, lang=None) Check if translation key exists
register(lang, translations, domain) Register custom translations
unregister_domain(domain) Unload all translations in specified domain
reload() Reload built-in translations and re-detect language

t() Method Details

def t(self, key, /, default=None, **kwargs):

Example:

# Translation definition: "greeting": "你好,{name}!欢迎来到{place}。"
i18n.t("greeting", name="Alice", place="ErisPulse")
# Returns: "你好,Alice!欢迎来到ErisPulse。"

BaseI18n / I18nKey (Declarative Translation Keys)

Starting from v2.7.0, ErisPulse.Core.Bases provides a translation key declaration tool based on class attributes (recommended to import uniformly from ErisPulse.Core.Bases):

I18nKey.default is a language-agnostic fallback text and is not registered to any language. To make translations effective, at least one language parameter must be explicitly passed (zh_CN= / en= / ja= etc.). This allows developers from various countries to freely use their native language to fill in default, with no assumptions made by the framework.

Name Description
I18nKey(default, *, key=None, zh_CN, zh_TW, en, ja, ru) Declaration of a single translation key, default is a language-agnostic fallback
BaseI18n Translation key collection base class (naming aligned with BaseConfig), sub-classes declare multiple I18nKey via class attributes
BaseI18n.register(prefix="", domain="app") Class method: register all declared keys to the i18n system
key Alias for I18nKey (more concise writing)

Usage example:

from ErisPulse.Core.Bases import BaseI18n, key

class MyKeys(BaseI18n):
    # Concise alias writing
    hello = key(
        default="Hello",
        zh_CN="你好",
        zh_TW="你好",
        en="Hello",
        ja="こんにちは",
        ru="Привет",
    )
    bye = key(
        default="Bye",
        zh_CN="再见",
        zh_TW="再見",
        en="Bye",
        ja="さようなら",
        ru="До свидания",
    )

# Standalone use (manual registration)
MyKeys.register(prefix="myapp.", domain="myapp")

Accessing from SDK Instance

from ErisPulse import sdk

# sdk.i18n is the same object as directly imported i18n
sdk.i18n.set_language("en")
print(sdk.i18n.t("core.sdk.init.starting"))

Runtime Configuration

Reading i18n Configuration via Configuration API

from ErisPulse.Core.Bases import I18nConfig
from ErisPulse.runtime import get_i18n_config

config = get_i18n_config()
print(config["language"])  # "auto" or specific language code

# I18nConfig is a dataclass, suitable for generating configuration templates
schema = I18nConfig.__dataclass_fields__

Configuration Item Description

In the [ErisPulse.i18n] section of config/config.toml:

[ErisPulse.i18n]
# Display language, possible values:
# - "auto"      — Auto-detect system language (default)
# - "zh-CN"     — Simplified Chinese
# - "zh-TW"     — Traditional Chinese
# - "en"        — English
# - "ja"        — Japanese
# - "ru"        — Russian
language = "auto"

Best Practices

Translation Key Naming

It is recommended to use dot-separated namespace format:

<module name>.<category>.<description>

For example: my_module.command.hello_desc, core.adapter.start_failed

Multi-language Coverage

There is no need to provide translations for all languages at once; missing languages will automatically fall back to English, and if English is also missing, the key name itself will be displayed.

Dynamic Content

For dynamically generated content (such as usernames, quantities, etc.), use {placeholder} formatting:

# Translation definition
"user_count": "Current online users: {count} people"

# Usage
i18n.t("user_count", count=len(users))

Log Messages

If your module uses the framework's Logger, these messages will also automatically use the current language:

self.logger.info(i18n.t("my_module.startup"))

Relationship with CLI i18n

The CLI has an independent internationalization module (ErisPulse.CLI.i18n), which is completely decoupled from the framework core's internationalization module.

This design ensures that changes to CLI translations do not affect the stability of the framework core.