Система международной локализации (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, не подвержен влиянию переменных, таких какLANG, установленных в Git Bash и других инструментах) - Переменные среды —
LANGUAGE>LC_ALL>LC_MESSAGES>LANG(стандарт Unix/macOS) - Системная локаль —
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={
# Здесь используется ключ перевода 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 |
|---|---|---|
| Ключи перевода, используемые в описании конфигурации | Требуется ручная регистрация, и нужно успеть до генерации конфигурации | Фреймворк автоматически регистрирует до генерации конфигурации |
| Объявление многоязычных переводов | Распространены по разным on_load() | Собраны в одном классе, легко читаемы |
| Согласованность именования ключей | Легко допускать ошибки при написании | Имя атрибута используется как суффикс ключа, IDE может подсказывать |
| Очистка при выгрузке | Требуется ручной unregister_domain() | Фреймворк использует единый domain для регистрации |
Правила формирования пути ключей в I18nClass
- По умолчанию: используется путь
<имя_регистрации_модуля>.<имя_атрибута>в качестве полного пути ключа- Пример: имя модуля
MyModule, атрибутwelcome→ путь ключаMyModule.welcome
- Пример: имя модуля
- Явно: с помощью параметра
I18nKey(key="...")можно указать любой путь с разделителем точек- Подходит для глубоко вложенных ключей (например,
mymodule.config.basic.token)
- Подходит для глубоко вложенных ключей (например,
Использование в адаптере
Адаптеры также поддерживают 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__ (до генерации шаблона конфигурации), обеспечивая доступность ключей перевода, используемых в описании конфигурации.
Ручная регистрация пользовательских переводов (старый способ)
Если вы не используете 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, схема конфигурации полностью поддерживает i18n. Все видимые пользователю текстовые поля могут ссылаться на ключи i18n, и WebUI и другие потребители автоматически будут преобразовывать их в соответствующий текст в зависимости от текущего языка.
Поддерживаемые поля i18n
| Поле | Расположение | Описание |
|---|---|---|
description |
метаданные поля | Описание поля |
options[].label |
ui.options |
Метки опций для контрола select |
placeholder |
ui.placeholder |
Заполнитель для поля ввода |
group_labels |
_schema_meta |
Названия групп (заголовки разделов Dashboard) |
Используется единый формат {"i18n": "key", "default": "текст"}, а строки без i18n будут передаваться без изменений (для обратной совместимости).
Объявление полей i18n
Все видимые пользователю текстовые поля поддерживают i18n:
from dataclasses import dataclass, field
from ErisPulse.Core.Bases import BaseConfig
@dataclass
class MyAdapterConfig(BaseConfig):
# i18n для description
token: str = field(
default="",
metadata={
"description": {"i18n": "my_adapter.token", "default": "Токен платформы"},
"required": True,
"secret": True,
"ui": {
"widget": "password",
"group": "basic",
"order": 1,
# i18n для placeholder
"placeholder": {"i18n": "my_adapter.token.ph", "default": "Введите токен"},
},
},
)
# i18n для label опций
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"},
],
},
},
)
# i18n для group_labels (название группы)
_schema_meta = {
"group_labels": {
"basic": {"i18n": "my_adapter.group.basic", "default": "Основные настройки"},
}
}
default — это резервный текст, который будет отображаться, если перевод не зарегистрирован или поиск не удался.
Секретная обработка и проверка конфигурации
Поля, помеченные как "secret": True, автоматически получают защиту от раскрытия (начиная с версии 2.7.0):
- Обработка шаблона: при генерации шаблона конфигурации
dataclass_to_toml_with_comments()настоящее значение секретного поля не будет записано в файл (вместо него будет пустой заполнитель), чтобы избежать попадания чувствительной информации на диск. - Универсальный инструмент для обработки секретов:
redact_secret(value)заменяет непустое значение на***, а пустое значение возвращает без изменений. Используется, например, при выводе в логи.
from ErisPulse.Core.Bases.config_schema import redact_secret
redact_secret("sk-xxxxxx") # '***'
redact_secret("") # ''
Проверка конфигурации (validate_config()) помимо проверки обязательных полей на непустоту, начиная с версии 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": "Токен платформы",
}, 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 потребляет i18n
Функция get_config_schema() возвращает схему, в которой словарь i18n будет передан без изменений. Frontend WebUI может использовать i18n.t() для получения перевода в зависимости от текущего языка.
Если требуется серверная сторона для преобразования i18n в строки (например, для передачи на фронтенд, который не поддерживает 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"]) # "Токен платформы" или "Platform Token"
print(schema["fields"]["token"]["placeholder"]) # "Введите токен" или "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сохранён для обратной совместимости, рекомендуется импортировать из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— ключ перевода (только позиционный аргумент, не конфликтует сkey=в**kwargs)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") |
Классовый метод: регистрация всех объявленных ключей в систему перевода |
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")
Доступ к i18n через экземпляр SDK
from ErisPulse import sdk
# sdk.i18n и импортированный i18n — это один и тот же объект
sdk.i18n.set_language("en")
print(sdk.i18n.t("core.sdk.init.starting"))
Конфигурация во время выполнения
Чтение конфигурации i18n через API-конфигурации
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__
Описание параметров конфигурации
В разделе [ErisPulse.i18n] файла config/config.toml:
[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))
Сообщения в логах
Если ваш модуль использует логгер фреймворка, эти сообщения также будут автоматически отображаться на текущем языке:
self.logger.info(i18n.t("my_module.startup"))
Отношение к i18n CLI
CLI имеет независимый модуль международной локализации (ErisPulse.CLI.i18n), который полностью отделен от основного модуля международной локализации фреймворка.
- Core i18n — используется основным модулем фреймворка, внешние модули могут регистрировать переводы
- CLI i18n — используется внутри интерфейса командной строки, не делит данные перевода с Core
Такая архитектура гарантирует, что изменения в переводах CLI не повлияют на стабильность основного ядра фреймворка.