feat: add translation language setting & onboarding flow

- Add separate translation_language setting (independent from interface language)
- Implement 3-step onboarding for new users:
  1. Choose interface language
  2. Choose learning language
  3. Choose translation language
- Fix localization issues when using callback.message (user_id from state)
- Add UserService.get_user_by_id() method
- Add get_user_translation_lang() helper in i18n
- Update all handlers to use correct translation language
- Add localization keys for onboarding (ru/en/ja)

🤖 Generated with [Claude Code](https://claude.com/claude-code)

Co-Authored-By: Claude <noreply@anthropic.com>
This commit is contained in:
2025-12-07 16:35:08 +03:00
parent d937b37a3b
commit 3e5c1be464
14 changed files with 360 additions and 81 deletions

View File

@@ -17,10 +17,16 @@ from utils.levels import (
router = Router()
def get_translation_language(user) -> str:
"""Получить язык перевода (translation_language или language_interface как fallback)"""
return getattr(user, 'translation_language', None) or getattr(user, 'language_interface', 'ru') or 'ru'
def get_settings_keyboard(user) -> InlineKeyboardMarkup:
"""Создать клавиатуру настроек"""
lang = get_user_lang(user)
ui_lang_code = getattr(user, 'language_interface', 'ru') or 'ru'
translation_lang_code = get_translation_language(user)
current_level = get_user_level_for_language(user)
keyboard = InlineKeyboardMarkup(inline_keyboard=[
[InlineKeyboardButton(
@@ -35,6 +41,10 @@ def get_settings_keyboard(user) -> InlineKeyboardMarkup:
text=t(lang, 'settings.interface_prefix') + t(lang, f'settings.lang_name.{ui_lang_code}'),
callback_data="settings_language"
)],
[InlineKeyboardButton(
text=t(lang, 'settings.translation_prefix') + t(lang, f'settings.lang_name.{translation_lang_code}'),
callback_data="settings_translation"
)],
[InlineKeyboardButton(
text=t(lang, 'settings.close'),
callback_data="settings_close"
@@ -73,6 +83,18 @@ def get_language_keyboard(user=None) -> InlineKeyboardMarkup:
return keyboard
def get_translation_language_keyboard(user=None) -> InlineKeyboardMarkup:
"""Клавиатура выбора языка перевода"""
lang = get_user_lang(user)
keyboard = InlineKeyboardMarkup(inline_keyboard=[
[InlineKeyboardButton(text=t(lang, 'settings.lang_name.ru'), callback_data="set_translation_ru")],
[InlineKeyboardButton(text=t(lang, 'settings.lang_name.en'), callback_data="set_translation_en")],
[InlineKeyboardButton(text=t(lang, 'settings.lang_name.ja'), callback_data="set_translation_ja")],
[InlineKeyboardButton(text=t(lang, 'settings.back'), callback_data="settings_back")]
])
return keyboard
def get_learning_language_keyboard(user=None) -> InlineKeyboardMarkup:
"""Клавиатура выбора языка изучения"""
lang = get_user_lang(user)
@@ -214,6 +236,40 @@ async def set_language(callback: CallbackQuery):
await callback.answer()
@router.callback_query(F.data == "settings_translation")
async def settings_translation(callback: CallbackQuery):
"""Показать выбор языка перевода"""
async with async_session_maker() as session:
user = await UserService.get_user_by_telegram_id(session, callback.from_user.id)
lang = get_user_lang(user)
await callback.message.edit_text(
t(lang, 'settings.translation_title') + t(lang, 'settings.translation_desc'),
reply_markup=get_translation_language_keyboard(user)
)
await callback.answer()
@router.callback_query(F.data.startswith("set_translation_"))
async def set_translation_language(callback: CallbackQuery):
"""Установить язык перевода"""
new_translation_lang = callback.data.split("_")[-1] # ru | en | ja
async with async_session_maker() as session:
user = await UserService.get_user_by_telegram_id(session, callback.from_user.id)
if user:
await UserService.update_user_translation_language(session, user.id, new_translation_lang)
lang = get_user_lang(user)
lang_name = t(lang, f'settings.lang_name.{new_translation_lang}')
text = t(lang, 'settings.translation_changed', lang_name=lang_name)
await callback.message.edit_text(
text,
reply_markup=InlineKeyboardMarkup(inline_keyboard=[[InlineKeyboardButton(text=t(lang, 'settings.back'), callback_data="settings_back")]])
)
await callback.answer()
@router.callback_query(F.data == "settings_back")
async def settings_back(callback: CallbackQuery):
"""Вернуться к настройкам"""