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

@@ -24,11 +24,14 @@ async def cmd_level_test(message: Message, state: FSMContext):
await start_level_test(message, state)
async def start_level_test(message: Message, state: FSMContext):
async def start_level_test(message: Message, state: FSMContext, telegram_id: int = None):
"""Начать тест определения уровня"""
# Определяем ID пользователя (telegram_id передаётся при вызове из callback)
user_telegram_id = telegram_id or message.from_user.id
# Показываем описание теста
async with async_session_maker() as session:
user = await UserService.get_user_by_telegram_id(session, message.from_user.id)
user = await UserService.get_user_by_telegram_id(session, user_telegram_id)
lang = (user.language_interface if user else 'ru') or 'ru'
await message.answer(t(lang, 'level_test.intro'))
@@ -83,7 +86,8 @@ async def begin_test(callback: CallbackQuery, state: FSMContext):
current_question=0,
correct_answers=0,
answers=[], # Для отслеживания ответов по уровням
learning_language=learning_lang
learning_language=learning_lang,
user_id=user.id
)
await state.set_state(LevelTestStates.taking_test)
@@ -96,6 +100,7 @@ async def show_question(message: Message, state: FSMContext):
data = await state.get_data()
questions = data.get('questions', [])
current_idx = data.get('current_question', 0)
user_id = data.get('user_id')
if current_idx >= len(questions):
# Тест завершён
@@ -105,9 +110,9 @@ async def show_question(message: Message, state: FSMContext):
question = questions[current_idx]
# Формируем текст вопроса
# Язык интерфейса
# Язык интерфейса (берём user_id из state, т.к. message может быть от бота)
async with async_session_maker() as session:
user = await UserService.get_user_by_telegram_id(session, message.from_user.id)
user = await UserService.get_user_by_id(session, user_id)
lang = (user.language_interface if user else 'ru') or 'ru'
text = (
@@ -127,10 +132,6 @@ async def show_question(message: Message, state: FSMContext):
])
# Кнопка для показа перевода вопроса (локализованная)
async with async_session_maker() as session:
user = await UserService.get_user_by_telegram_id(session, message.chat.id)
from utils.i18n import t
lang = (user.language_interface if user else 'ru') or 'ru'
keyboard.append([
InlineKeyboardButton(text=t(lang, 'level_test.show_translation_btn'), callback_data=f"show_qtr_{current_idx}")
])
@@ -237,6 +238,7 @@ async def finish_test(message: Message, state: FSMContext):
correct_answers = data.get('correct_answers', 0)
answers = data.get('answers', [])
learning_lang = data.get('learning_language', 'en')
user_id = data.get('user_id')
total = len(questions)
accuracy = int((correct_answers / total) * 100) if total > 0 else 0
@@ -244,9 +246,9 @@ async def finish_test(message: Message, state: FSMContext):
# Определяем уровень на основе правильных ответов по уровням
level = determine_level(answers, learning_lang)
# Сохраняем уровень в базе данных
# Сохраняем уровень в базе данных (берём user_id из state, т.к. message может быть от бота)
async with async_session_maker() as session:
user = await UserService.get_user_by_telegram_id(session, message.chat.id)
user = await UserService.get_user_by_id(session, user_id)
if user:
await UserService.update_user_level(session, user.id, level, learning_lang)