feat: restructure menu and add file import

- Consolidate "Add word" menu with submenu (Manual, Thematic, Import)
- Add file import support (.txt, .md) with AI batch translation
- Add vocabulary pagination with navigation buttons
- Add "Add word" button in tasks for new words mode
- Fix undefined variables bug in vocabulary confirm handler
- Add localization keys for add_menu in 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-05 20:15:47 +03:00
parent 2097950c60
commit 63e2615243
12 changed files with 883 additions and 47 deletions

View File

@@ -124,6 +124,11 @@ async def confirm_add_word(callback: CallbackQuery, state: FSMContext):
user_id = data.get("user_id")
async with async_session_maker() as session:
# Получаем пользователя для языков
user = await UserService.get_user_by_telegram_id(session, callback.from_user.id)
source_lang = user.learning_language if user else 'en'
ui_lang = user.language_interface if user else 'ru'
# Добавляем слово в базу
await VocabularyService.add_word(
session,
@@ -140,12 +145,9 @@ async def confirm_add_word(callback: CallbackQuery, state: FSMContext):
)
# Получаем общее количество слов
user = await UserService.get_user_by_telegram_id(session, callback.from_user.id)
words_count = await VocabularyService.get_words_count(session, user_id, learning_lang=user.learning_language)
lang = ui_lang or 'ru'
async with async_session_maker() as session:
user = await UserService.get_user_by_telegram_id(session, callback.from_user.id)
lang = (user.language_interface if user else 'ru') or 'ru'
await callback.message.edit_text(
t(lang, 'add.added_success', word=word_data['word'], count=words_count)
)
@@ -164,29 +166,55 @@ async def cancel_add_word(callback: CallbackQuery, state: FSMContext):
await callback.answer()
WORDS_PER_PAGE = 10
@router.message(Command("vocabulary"))
async def cmd_vocabulary(message: Message):
"""Обработчик команды /vocabulary"""
await show_vocabulary_page(message, page=0)
async def show_vocabulary_page(message_or_callback, page: int = 0, edit: bool = False):
"""Показать страницу словаря"""
# Определяем, это Message или CallbackQuery
# В CallbackQuery from_user — это пользователь, а message.from_user — бот
user_id = message_or_callback.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_id)
if not user:
await message.answer(t('ru', 'common.start_first'))
if edit:
await message_or_callback.message.edit_text(t('ru', 'common.start_first'))
else:
await message_or_callback.answer(t('ru', 'common.start_first'))
return
# Получаем слова пользователя
words = await VocabularyService.get_user_words(session, user.id, limit=10, learning_lang=user.learning_language)
# Получаем слова с пагинацией
offset = page * WORDS_PER_PAGE
words = await VocabularyService.get_user_words(
session, user.id,
limit=WORDS_PER_PAGE,
offset=offset,
learning_lang=user.learning_language
)
total_count = await VocabularyService.get_words_count(session, user.id, learning_lang=user.learning_language)
if not words:
lang = (user.language_interface if user else 'ru') or 'ru'
await message.answer(t(lang, 'vocab.empty'))
lang = get_user_lang(user)
if not words and page == 0:
if edit:
await message_or_callback.message.edit_text(t(lang, 'vocab.empty'))
else:
await message_or_callback.answer(t(lang, 'vocab.empty'))
return
# Формируем список слов
lang = (user.language_interface if user else 'ru') or 'ru'
total_pages = (total_count + WORDS_PER_PAGE - 1) // WORDS_PER_PAGE
words_list = t(lang, 'vocab.header') + "\n\n"
for idx, word in enumerate(words, 1):
for idx, word in enumerate(words, start=offset + 1):
progress = ""
if word.times_reviewed > 0:
accuracy = int((word.correct_answers / word.times_reviewed) * 100)
@@ -197,9 +225,49 @@ async def cmd_vocabulary(message: Message):
f" 🔊 [{word.transcription or ''}]{progress}\n\n"
)
if total_count > 10:
words_list += "\n" + t(lang, 'vocab.shown_last', n=total_count)
else:
words_list += "\n" + t(lang, 'vocab.total', n=total_count)
words_list += t(lang, 'vocab.page_info', page=page + 1, total=total_pages, count=total_count)
await message.answer(words_list)
# Кнопки пагинации
buttons = []
nav_row = []
if page > 0:
nav_row.append(InlineKeyboardButton(text="⬅️", callback_data=f"vocab_page_{page - 1}"))
nav_row.append(InlineKeyboardButton(text=f"{page + 1}/{total_pages}", callback_data="vocab_noop"))
if page < total_pages - 1:
nav_row.append(InlineKeyboardButton(text="➡️", callback_data=f"vocab_page_{page + 1}"))
if nav_row:
buttons.append(nav_row)
buttons.append([InlineKeyboardButton(text=t(lang, 'vocab.close_btn'), callback_data="vocab_close")])
keyboard = InlineKeyboardMarkup(inline_keyboard=buttons)
if edit:
await message_or_callback.message.edit_text(words_list, reply_markup=keyboard)
else:
await message_or_callback.answer(words_list, reply_markup=keyboard)
@router.callback_query(F.data.startswith("vocab_page_"))
async def vocab_page_callback(callback: CallbackQuery):
"""Переключение страницы словаря"""
page = int(callback.data.split("_")[-1])
await callback.answer()
await show_vocabulary_page(callback, page=page, edit=True)
@router.callback_query(F.data == "vocab_noop")
async def vocab_noop_callback(callback: CallbackQuery):
"""Пустой callback для кнопки с номером страницы"""
await callback.answer()
@router.callback_query(F.data == "vocab_close")
async def vocab_close_callback(callback: CallbackQuery):
"""Закрыть словарь"""
await callback.message.delete()
await callback.answer()