feat: multiple translations with context, improved task examples

- Add WordTranslation model for storing multiple translations per word
- AI generates translations with example sentences and their translations
- Show example usage after answering tasks (learning + interface language)
- Save translations to word_translations table when adding words from tasks
- Improve word exclusion in new_words mode (stronger prompt + client filtering)
- Add migration for word_translations table

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

Co-Authored-By: Claude <noreply@anthropic.com>
This commit is contained in:
2025-12-06 21:29:41 +03:00
parent 63e2615243
commit d937b37a3b
10 changed files with 543 additions and 30 deletions

View File

@@ -69,33 +69,47 @@ async def process_word_addition(message: Message, state: FSMContext, word: str):
lang = (user.language_interface if user else 'ru') or 'ru'
processing_msg = await message.answer(t(lang, 'add.searching'))
# Получаем перевод через AI
# Получаем перевод через AI (с несколькими значениями)
async with async_session_maker() as session:
user = await UserService.get_user_by_telegram_id(session, message.from_user.id)
source_lang = user.learning_language if user else 'en'
ui_lang = user.language_interface if user else 'ru'
word_data = await ai_service.translate_word(word, source_lang=source_lang, translation_lang=ui_lang)
word_data = await ai_service.translate_word_with_contexts(
word, source_lang=source_lang, translation_lang=ui_lang, max_translations=3
)
# Удаляем сообщение о загрузке
await processing_msg.delete()
# Формируем примеры
examples_text = ""
if word_data.get("examples"):
examples_text = "\n\n" + t(lang, 'add.examples_header') + "\n"
for idx, example in enumerate(word_data["examples"][:2], 1):
src = example.get(source_lang) or example.get('en') or example.get('ru') or ''
tr = example.get(ui_lang) or example.get('ru') or example.get('en') or ''
examples_text += f"{idx}. {src}\n <i>{tr}</i>\n"
# Формируем текст с переводами
translations = word_data.get("translations", [])
translations_text = ""
if translations:
# Основной перевод для backward compatibility
primary = next((tr for tr in translations if tr.get('is_primary')), translations[0])
word_data['translation'] = primary.get('translation', '')
translations_text = "\n\n" + t(lang, 'add.translations_header') + "\n"
for idx, tr in enumerate(translations, 1):
marker = "" if tr.get('is_primary') else ""
translations_text += f"{idx}. {marker}<b>{tr.get('translation', '')}</b>\n"
if tr.get('context'):
translations_text += f" <i>«{tr.get('context', '')}»</i>\n"
if tr.get('context_translation'):
translations_text += f" <i>({tr.get('context_translation', '')})</i>\n"
translations_text += "\n"
else:
# Fallback если нет переводов
word_data['translation'] = 'Ошибка перевода'
# Отправляем карточку слова
card_text = (
f"📝 <b>{word_data['word']}</b>\n"
f"🔊 [{word_data.get('transcription', '')}]\n\n"
f"{t(lang, 'add.translation_label')}: {word_data['translation']}\n"
f"{t(lang, 'add.category_label')}: {word_data.get('category', '')}\n"
f"{t(lang, 'add.level_label')}: {word_data.get('difficulty', 'A1')}"
f"{examples_text}\n\n"
f"{translations_text}"
f"{t(lang, 'add.confirm_question')}"
)
@@ -130,7 +144,7 @@ async def confirm_add_word(callback: CallbackQuery, state: FSMContext):
ui_lang = user.language_interface if user else 'ru'
# Добавляем слово в базу
await VocabularyService.add_word(
new_word = await VocabularyService.add_word(
session,
user_id=user_id,
word_original=word_data["word"],
@@ -138,12 +152,20 @@ async def confirm_add_word(callback: CallbackQuery, state: FSMContext):
source_lang=source_lang,
translation_lang=ui_lang,
transcription=word_data.get("transcription"),
examples={"examples": word_data.get("examples", [])},
category=word_data.get("category"),
difficulty_level=word_data.get("difficulty"),
source=WordSource.MANUAL
)
# Сохраняем переводы с контекстами в отдельную таблицу
translations = word_data.get("translations", [])
if translations:
await VocabularyService.add_translations_bulk(
session,
vocabulary_id=new_word.id,
translations=translations
)
# Получаем общее количество слов
words_count = await VocabularyService.get_words_count(session, user_id, learning_lang=user.learning_language)
lang = ui_lang or 'ru'