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

@@ -10,7 +10,7 @@ from database.models import WordSource
from services.user_service import UserService
from services.vocabulary_service import VocabularyService
from services.ai_service import ai_service
from utils.i18n import t, get_user_lang
from utils.i18n import t, get_user_lang, get_user_translation_lang
from utils.levels import get_user_level_for_language
router = Router()
@@ -87,7 +87,7 @@ async def process_text(message: Message, state: FSMContext):
level=current_level,
max_words=15,
learning_lang=user.learning_language,
translation_lang=user.language_interface,
translation_lang=get_user_translation_lang(user),
)
await processing_msg.delete()
@@ -176,27 +176,29 @@ async def import_single_word(callback: CallbackQuery, state: FSMContext):
user_id = data.get('user_id')
if word_index >= len(words):
await callback.answer("❌ Ошибка: слово не найдено")
await callback.answer(t('ru', 'words.err_not_found'))
return
word_data = words[word_index]
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)
# Проверяем, нет ли уже такого слова
existing = await VocabularyService.get_word_by_original(
session, user_id, word_data['word']
)
if existing:
await callback.answer(f"Слово '{word_data['word']}' уже в словаре", show_alert=True)
await callback.answer(t(lang, 'words.already_exists', word=word_data['word']), show_alert=True)
return
# Добавляем слово
learn = user.learning_language if user else 'en'
ui = user.language_interface if user else 'ru'
translation_lang = get_user_translation_lang(user)
ctx = word_data.get('context')
examples = ([{learn: ctx, ui: ''}] if ctx else [])
examples = ([{learn: ctx, translation_lang: ''}] if ctx else [])
await VocabularyService.add_word(
session=session,
@@ -204,7 +206,7 @@ async def import_single_word(callback: CallbackQuery, state: FSMContext):
word_original=word_data['word'],
word_translation=word_data['translation'],
source_lang=user.learning_language if user else None,
translation_lang=user.language_interface if user else None,
translation_lang=translation_lang,
transcription=word_data.get('transcription'),
examples=examples,
source=WordSource.CONTEXT,
@@ -242,9 +244,9 @@ async def import_all_words(callback: CallbackQuery, state: FSMContext):
# Добавляем слово
learn = user.learning_language if user else 'en'
ui = user.language_interface if user else 'ru'
translation_lang = get_user_translation_lang(user)
ctx = word_data.get('context')
examples = ([{learn: ctx, ui: ''}] if ctx else [])
examples = ([{learn: ctx, translation_lang: ''}] if ctx else [])
await VocabularyService.add_word(
session=session,
@@ -252,7 +254,7 @@ async def import_all_words(callback: CallbackQuery, state: FSMContext):
word_original=word_data['word'],
word_translation=word_data['translation'],
source_lang=user.learning_language if user else None,
translation_lang=user.language_interface if user else None,
translation_lang=translation_lang,
transcription=word_data.get('transcription'),
examples=examples,
source=WordSource.CONTEXT,
@@ -389,7 +391,7 @@ async def handle_file_import(message: Message, state: FSMContext, bot: Bot):
translations = await ai_service.translate_words_batch(
words=words_to_translate,
source_lang=user.learning_language,
translation_lang=user.language_interface
translation_lang=get_user_translation_lang(user)
)
await processing_msg.delete()
@@ -490,7 +492,7 @@ async def import_file_all_words(callback: CallbackQuery, state: FSMContext):
word_original=word_data['word'],
word_translation=word_data.get('translation', ''),
source_lang=user.learning_language if user else None,
translation_lang=user.language_interface if user else None,
translation_lang=get_user_translation_lang(user),
transcription=word_data.get('transcription'),
source=WordSource.IMPORT
)