Translator Protocol¶
aiogram_cmds.translator.Translator is a callable used to resolve localized strings.
Signature:
def __call__(
key: str,
*,
locale: str | None = None,
plural: str | None = None,
count: int | None = None,
) -> str | None: ...
- Returns
Noneif translation is missing (callers should fallback) localecan be omitted to use ambient middleware context- When
pluralandcountare provided, plural form is used (maps tongettext)
Adapter¶
The adapter uses I18n.with_locale(locale) when locale is provided, otherwise gettext/ngettext use the current context.
Translator API Reference¶
This document provides detailed API reference for the translation system and i18n integration in aiogram-cmds.
Translator Protocol¶
The translator system is built around a simple protocol that allows for flexible i18n integration.
Parameters:
- key (str): Translation key to look up
- locale (str): Language code (e.g., "en", "ru", "es")
Returns:
- Optional[str]: Translated text or None if not found
Built-in Translators¶
build_translator_from_i18n()¶
Create a translator adapter from an aiogram i18n instance.
def build_translator_from_i18n(i18n_obj) -> Translator:
"""Build translator adapter from an aiogram i18n instance."""
Parameters:
- i18n_obj: aiogram i18n instance with gettext(key, locale=?) method
Returns:
- Translator: Translator function that can be used with aiogram-cmds
Behavior:
- Calls i18n_obj.gettext(key, locale=locale)
- Returns None if translation is missing or equals the key
- Handles exceptions gracefully
Example:
from aiogram_cmds import build_translator_from_i18n
# Assuming you have an aiogram i18n instance
translator = build_translator_from_i18n(i18n)
# Use with CommandScopeManager
manager = CommandScopeManager(bot, translator=translator)
noop_translator()¶
No-operation translator that always returns None.
def noop_translator(key: str, *, locale: str) -> Optional[str]:
"""No-op translator that always returns None."""
return None
Use Cases: - Testing and development - When i18n is not needed - Fallback when no translator is available
Example:
from aiogram_cmds import noop_translator
# Use no-op translator
manager = CommandScopeManager(bot, translator=noop_translator)
Custom Translators¶
You can create custom translators by implementing the Translator protocol.
Simple Dictionary Translator¶
from typing import Dict, Optional
from aiogram_cmds import Translator
class DictTranslator:
def __init__(self, translations: Dict[str, Dict[str, str]]):
self.translations = translations
def __call__(self, key: str, *, locale: str) -> Optional[str]:
return self.translations.get(locale, {}).get(key)
# Usage
translations = {
"en": {
"cmd.start.desc": "Start the bot",
"cmd.help.desc": "Show help information",
},
"ru": {
"cmd.start.desc": "Запустить бота",
"cmd.help.desc": "Показать справку",
}
}
translator = DictTranslator(translations)
manager = CommandScopeManager(bot, translator=translator)
File-based Translator¶
import json
from pathlib import Path
from typing import Optional
class FileTranslator:
def __init__(self, locales_dir: Path):
self.locales_dir = locales_dir
self._cache = {}
def __call__(self, key: str, *, locale: str) -> Optional[str]:
if locale not in self._cache:
locale_file = self.locales_dir / f"{locale}.json"
if locale_file.exists():
with open(locale_file) as f:
self._cache[locale] = json.load(f)
else:
self._cache[locale] = {}
return self._cache[locale].get(key)
# Usage
translator = FileTranslator(Path("locales"))
manager = CommandScopeManager(bot, translator=translator)
Database Translator¶
import asyncio
from typing import Optional
class DatabaseTranslator:
def __init__(self, db_connection):
self.db = db_connection
def __call__(self, key: str, *, locale: str) -> Optional[str]:
# This is a synchronous interface, so we need to handle async carefully
# In practice, you might want to preload translations or use a sync DB client
try:
# Assuming you have a sync database client
result = self.db.execute(
"SELECT translation FROM translations WHERE key = ? AND locale = ?",
(key, locale)
).fetchone()
return result[0] if result else None
except Exception:
return None
# Usage
translator = DatabaseTranslator(db_connection)
manager = CommandScopeManager(bot, translator=translator)
Translation Key Patterns¶
Default Pattern¶
The default translation key pattern is:
Where:
- prefix is the i18n_key_prefix from configuration (default: "cmd")
- command is the command name
- desc is the description suffix
Example:
# Configuration
config = CmdsConfig(
i18n_key_prefix="cmd",
commands={
"start": CommandDef(i18n_key="start.desc"),
"help": CommandDef(i18n_key="help.desc"),
}
)
# Translation keys
# cmd.start.desc -> "Start the bot"
# cmd.help.desc -> "Show help information"
Custom Patterns¶
You can use custom patterns by setting the i18n_key in CommandDef:
commands = {
"start": CommandDef(i18n_key="commands.start.description"),
"help": CommandDef(i18n_key="commands.help.description"),
"profile": CommandDef(i18n_key="menu.profile.tooltip"),
}
Nested Patterns¶
For complex bots, you might want nested translation keys:
commands = {
"start": CommandDef(i18n_key="bot.commands.start.description"),
"help": CommandDef(i18n_key="bot.commands.help.description"),
"admin_panel": CommandDef(i18n_key="admin.panel.commands.open.description"),
}
Fallback Strategy¶
The translation system implements a multi-level fallback strategy:
1. Primary Translation¶
Try to get translation for the requested key and locale.
2. Fallback Language¶
If primary translation fails, try with the fallback language.
# Try: cmd.start.desc with locale "en" (fallback)
translation = translator("cmd.start.desc", locale="en")
3. Title Case Fallback¶
If both translations fail, use Title Case of the command name.
4. Final Fallback¶
If all else fails, use "Command" as the description.
Integration with aiogram i18n¶
Basic Integration¶
from aiogram import Bot, Dispatcher
from aiogram.utils.i18n import I18n, FSMI18nMiddleware
from aiogram_cmds import build_translator_from_i18n, CommandScopeManager
# Set up aiogram i18n
i18n = I18n(path="locales", default_locale="en", domain="messages")
dp.message.middleware(FSMI18nMiddleware(i18n))
# Create translator
translator = build_translator_from_i18n(i18n)
# Use with aiogram-cmds
manager = CommandScopeManager(bot, translator=translator)
Advanced Integration¶
from aiogram.utils.i18n import I18n
from aiogram_cmds import build_translator_from_i18n
class CustomTranslator:
def __init__(self, i18n: I18n):
self.i18n = i18n
def __call__(self, key: str, *, locale: str) -> Optional[str]:
try:
# Use aiogram's i18n with custom context
translation = self.i18n.gettext(key, locale=locale)
# Additional processing
if translation and translation != key:
return translation.upper() # Example: make all caps
return None
except Exception:
return None
# Usage
translator = CustomTranslator(i18n)
manager = CommandScopeManager(bot, translator=translator)
Translation Files Structure¶
JSON Structure¶
// locales/en.json
{
"cmd.start.desc": "Start the bot",
"cmd.help.desc": "Show help information",
"cmd.profile.desc": "View your profile",
"cmd.settings.desc": "Bot settings"
}
// locales/ru.json
{
"cmd.start.desc": "Запустить бота",
"cmd.help.desc": "Показать справку",
"cmd.profile.desc": "Посмотреть профиль",
"cmd.settings.desc": "Настройки бота"
}
YAML Structure¶
# locales/en.yaml
cmd:
start:
desc: "Start the bot"
help:
desc: "Show help information"
profile:
desc: "View your profile"
# locales/ru.yaml
cmd:
start:
desc: "Запустить бота"
help:
desc: "Показать справку"
profile:
desc: "Посмотреть профиль"
PO/POT Structure¶
# locales/en/LC_MESSAGES/messages.po
msgid "cmd.start.desc"
msgstr "Start the bot"
msgid "cmd.help.desc"
msgstr "Show help information"
# locales/ru/LC_MESSAGES/messages.po
msgid "cmd.start.desc"
msgstr "Запустить бота"
msgid "cmd.help.desc"
msgstr "Показать справку"
Best Practices¶
1. Use Consistent Key Patterns¶
# ✅ Good - Consistent pattern
commands = {
"start": CommandDef(i18n_key="start.desc"),
"help": CommandDef(i18n_key="help.desc"),
"profile": CommandDef(i18n_key="profile.desc"),
}
# ❌ Avoid - Inconsistent patterns
commands = {
"start": CommandDef(i18n_key="start.desc"),
"help": CommandDef(i18n_key="commands.help"),
"profile": CommandDef(i18n_key="user.profile.description"),
}
2. Provide Fallback Translations¶
# ✅ Good - Provide fallback descriptions
commands = {
"start": CommandDef(
i18n_key="start.desc",
descriptions={"en": "Start the bot"}
),
}
# ❌ Avoid - No fallback
commands = {
"start": CommandDef(i18n_key="start.desc"), # No fallback if translation missing
}
3. Test Translation Coverage¶
def test_translation_coverage():
"""Test that all commands have translations."""
config = load_config()
translator = build_translator_from_i18n(i18n)
for lang in config.languages:
for cmd_name, cmd_def in config.commands.items():
if cmd_def.i18n_key:
key = f"{config.i18n_key_prefix}.{cmd_def.i18n_key}"
translation = translator(key, locale=lang)
assert translation is not None, f"Missing translation for {key} in {lang}"
4. Handle Translation Errors Gracefully¶
class SafeTranslator:
def __init__(self, base_translator: Translator):
self.base_translator = base_translator
def __call__(self, key: str, *, locale: str) -> Optional[str]:
try:
return self.base_translator(key, locale=locale)
except Exception as e:
logger.warning(f"Translation error for {key} in {locale}: {e}")
return None
# Usage
safe_translator = SafeTranslator(translator)
manager = CommandScopeManager(bot, translator=safe_translator)
Debugging Translations¶
Enable Debug Logging¶
Test Translations¶
def test_translations():
translator = build_translator_from_i18n(i18n)
# Test specific keys
keys = ["cmd.start.desc", "cmd.help.desc", "cmd.profile.desc"]
locales = ["en", "ru", "es"]
for key in keys:
for locale in locales:
translation = translator(key, locale=locale)
print(f"{key} ({locale}): {translation}")
Missing Translation Detection¶
def find_missing_translations():
"""Find commands without translations."""
config = load_config()
translator = build_translator_from_i18n(i18n)
missing = []
for lang in config.languages:
for cmd_name, cmd_def in config.commands.items():
if cmd_def.i18n_key:
key = f"{config.i18n_key_prefix}.{cmd_def.i18n_key}"
translation = translator(key, locale=lang)
if translation is None:
missing.append((key, lang))
return missing
For more information about using translators, see: - Core API - How to use translators with CommandScopeManager - Configuration API - Configuring i18n settings - Quickstart Guide - Getting started with i18n