# presentation/bot/i18n.py
from pathlib import Path
from fluent.runtime import FluentBundle, FluentResource
class I18n:
def __init__(self, locale: str = "ru"):
self.locale = locale
self.bundle = self._load_bundle(locale)
self._register_methods() # создаём методы для автодополнения
def _load_bundle(self, locale: str) -> FluentBundle:
"""Загружает все .ftl файлы для указанной локали."""
locale_dir = Path(__file__).parent / "locales" / locale
if not locale_dir.exists():
raise FileNotFoundError(f"Папка локализации не найдена: {locale_dir}")
bundle = FluentBundle([locale])
for ftl_path in sorted(locale_dir.rglob("*.ftl")):
with open(ftl_path, "r", encoding="utf-8") as f:
resource = FluentResource(f.read())
bundle.add_resource(resource)
return bundle
def t(self, key: str, **kwargs) -> str:
"""
Возвращает текст по ключу с подстановкой kwargs.
Если kwargs пусты, placeholders остаются в тексте в виде {name}.
"""
message = self.bundle.get_message(key)
if not message:
raise KeyError(f"Ключ '{key}' не найден в локали '{self.locale}'")
# Даже если kwargs пусты, передаем {} — fluent вернет строку
value, errors = self.bundle.format_pattern(message.value, kwargs or {})
# errors можно логировать, но не ломать рендер
return value
def _register_methods(self):
"""Создаёт методы для каждого ключа .ftl с автокомплитом."""
for key in self.bundle._messages.keys():
method_name = key.replace("-", "_")
setattr(self, method_name, self._make_method(key))
def _make_method(self, key):
"""Метод для каждого ключа .ftl."""
def method(**kwargs):
return self.t(key, **kwargs)
return method
# Экземпляр для бота
i18n = I18n(locale="ru")