feat(i18n): localize start/help/menu, practice, words, import, reminder, vocabulary, tasks/stats for RU/EN/JA; add JSON-based i18n helper\n\nfeat(lang): support learning/translation languages across AI flows; hide translations with buttons; store examples per lang\n\nfeat(vocab): add source_lang and translation_lang to Vocabulary, unique constraint (user_id, source_lang, word_original); filter /vocabulary by user.learning_language\n\nchore(migrations): add Alembic setup + migration to add vocab lang columns; env.py reads app settings and supports asyncpg URLs\n\nfix(words/import): pass learning_lang + translation_lang everywhere; fix menu themes generation\n\nfeat(settings): add learning language selector; update main menu on language change
This commit is contained in:
@@ -8,6 +8,7 @@ from database.db import async_session_maker
|
||||
from services.user_service import UserService
|
||||
from services.task_service import TaskService
|
||||
from services.ai_service import ai_service
|
||||
from utils.i18n import t
|
||||
|
||||
router = Router()
|
||||
|
||||
@@ -25,17 +26,18 @@ async def cmd_task(message: Message, state: FSMContext):
|
||||
user = await UserService.get_user_by_telegram_id(session, message.from_user.id)
|
||||
|
||||
if not user:
|
||||
await message.answer("Сначала запусти бота командой /start")
|
||||
await message.answer(t('ru', 'common.start_first'))
|
||||
return
|
||||
|
||||
# Генерируем задания разных типов
|
||||
tasks = await TaskService.generate_mixed_tasks(session, user.id, count=5)
|
||||
tasks = await TaskService.generate_mixed_tasks(
|
||||
session, user.id, count=5,
|
||||
learning_lang=user.learning_language,
|
||||
translation_lang=user.language_interface,
|
||||
)
|
||||
|
||||
if not tasks:
|
||||
await message.answer(
|
||||
"📚 У тебя пока нет слов для практики!\n\n"
|
||||
"Добавь несколько слов командой /add, а затем возвращайся."
|
||||
)
|
||||
await message.answer(t(user.level.value and (user.language_interface or 'ru') or 'ru', 'tasks.no_words'))
|
||||
return
|
||||
|
||||
# Сохраняем задания в состоянии
|
||||
@@ -64,15 +66,20 @@ async def show_current_task(message: Message, state: FSMContext):
|
||||
|
||||
task = tasks[current_index]
|
||||
|
||||
# Определяем язык пользователя
|
||||
async with async_session_maker() as session:
|
||||
user = await UserService.get_user_by_telegram_id(session, message.from_user.id)
|
||||
lang = (user.language_interface if user else 'ru') or 'ru'
|
||||
|
||||
task_text = (
|
||||
f"📝 <b>Задание {current_index + 1} из {len(tasks)}</b>\n\n"
|
||||
t(lang, 'tasks.header', i=current_index + 1, n=len(tasks)) + "\n\n" +
|
||||
f"{task['question']}\n"
|
||||
)
|
||||
|
||||
if task.get('transcription'):
|
||||
task_text += f"🔊 [{task['transcription']}]\n"
|
||||
|
||||
task_text += f"\n💡 Напиши свой ответ:"
|
||||
task_text += t(lang, 'tasks.write_answer')
|
||||
|
||||
await state.set_state(TaskStates.waiting_for_answer)
|
||||
await message.answer(task_text)
|
||||
@@ -92,7 +99,12 @@ async def process_answer(message: Message, state: FSMContext):
|
||||
task = tasks[current_index]
|
||||
|
||||
# Показываем индикатор проверки
|
||||
checking_msg = await message.answer("⏳ Проверяю ответ...")
|
||||
# Язык пользователя
|
||||
async with async_session_maker() as session:
|
||||
user = await UserService.get_user_by_telegram_id(session, message.from_user.id)
|
||||
lang = (user.language_interface if user else 'ru') or 'ru'
|
||||
|
||||
checking_msg = await message.answer(t(lang, 'tasks.checking'))
|
||||
|
||||
# Проверяем ответ через AI
|
||||
check_result = await ai_service.check_answer(
|
||||
@@ -108,13 +120,13 @@ async def process_answer(message: Message, state: FSMContext):
|
||||
|
||||
# Формируем ответ
|
||||
if is_correct:
|
||||
result_text = f"✅ <b>Правильно!</b>\n\n"
|
||||
result_text = t(lang, 'tasks.correct') + "\n\n"
|
||||
correct_count += 1
|
||||
else:
|
||||
result_text = f"❌ <b>Неправильно</b>\n\n"
|
||||
result_text = t(lang, 'tasks.incorrect') + "\n\n"
|
||||
|
||||
result_text += f"Твой ответ: <i>{user_answer}</i>\n"
|
||||
result_text += f"Правильный ответ: <b>{task['correct_answer']}</b>\n\n"
|
||||
result_text += f"{t(lang, 'tasks.your_answer')}: <i>{user_answer}</i>\n"
|
||||
result_text += f"{t(lang, 'tasks.right_answer')}: <b>{task['correct_answer']}</b>\n\n"
|
||||
|
||||
if feedback:
|
||||
result_text += f"💬 {feedback}\n\n"
|
||||
@@ -151,8 +163,8 @@ async def process_answer(message: Message, state: FSMContext):
|
||||
|
||||
# Показываем результат и кнопку "Далее"
|
||||
keyboard = InlineKeyboardMarkup(inline_keyboard=[
|
||||
[InlineKeyboardButton(text="➡️ Следующее задание", callback_data="next_task")],
|
||||
[InlineKeyboardButton(text="🔚 Завершить", callback_data="stop_tasks")]
|
||||
[InlineKeyboardButton(text=t(lang, 'tasks.next_btn'), callback_data="next_task")],
|
||||
[InlineKeyboardButton(text=t(lang, 'tasks.stop_btn'), callback_data="stop_tasks")]
|
||||
])
|
||||
|
||||
await message.answer(result_text, reply_markup=keyboard)
|
||||
@@ -173,7 +185,10 @@ async def stop_tasks_callback(callback: CallbackQuery, state: FSMContext):
|
||||
"""Остановить выполнение заданий через кнопку"""
|
||||
await state.clear()
|
||||
await callback.message.edit_reply_markup(reply_markup=None)
|
||||
await callback.message.answer("Задания завершены. Используй /task, чтобы начать заново.")
|
||||
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.answer(t(lang, 'tasks.finished'))
|
||||
await callback.answer()
|
||||
|
||||
|
||||
@@ -182,7 +197,10 @@ async def stop_tasks_callback(callback: CallbackQuery, state: FSMContext):
|
||||
async def stop_tasks(message: Message, state: FSMContext):
|
||||
"""Остановить выполнение заданий командой /stop"""
|
||||
await state.clear()
|
||||
await message.answer("Задания остановлены. Используй /task, чтобы начать заново.")
|
||||
# Определяем язык пользователя
|
||||
async with async_session_maker() as session:
|
||||
user = await UserService.get_user_by_telegram_id(session, message.from_user.id)
|
||||
await message.answer(t((user.language_interface if user else 'ru') or 'ru', 'tasks.stopped'))
|
||||
|
||||
|
||||
@router.message(Command("cancel"), TaskStates.doing_tasks)
|
||||
@@ -190,7 +208,10 @@ async def stop_tasks(message: Message, state: FSMContext):
|
||||
async def cancel_tasks(message: Message, state: FSMContext):
|
||||
"""Отмена выполнения заданий командой /cancel"""
|
||||
await state.clear()
|
||||
await message.answer("Отменено. Можешь вернуться к заданиям командой /task.")
|
||||
async with async_session_maker() as session:
|
||||
user = await UserService.get_user_by_telegram_id(session, message.from_user.id)
|
||||
lang = (user.language_interface if user else 'ru') or 'ru'
|
||||
await message.answer(t(lang, 'tasks.cancelled'))
|
||||
|
||||
|
||||
async def finish_tasks(message: Message, state: FSMContext):
|
||||
@@ -205,24 +226,29 @@ async def finish_tasks(message: Message, state: FSMContext):
|
||||
# Определяем эмодзи на основе результата
|
||||
if accuracy >= 90:
|
||||
emoji = "🏆"
|
||||
comment = "Отличный результат!"
|
||||
comment_key = 'excellent'
|
||||
elif accuracy >= 70:
|
||||
emoji = "👍"
|
||||
comment = "Хорошая работа!"
|
||||
comment_key = 'good'
|
||||
elif accuracy >= 50:
|
||||
emoji = "📚"
|
||||
comment = "Неплохо, продолжай практиковаться!"
|
||||
comment_key = 'average'
|
||||
else:
|
||||
emoji = "💪"
|
||||
comment = "Повтори эти слова еще раз!"
|
||||
comment_key = 'poor'
|
||||
|
||||
# Язык пользователя
|
||||
async with async_session_maker() as session:
|
||||
user = await UserService.get_user_by_telegram_id(session, message.from_user.id)
|
||||
lang = (user.language_interface if user else 'ru') or 'ru'
|
||||
|
||||
result_text = (
|
||||
f"{emoji} <b>Задание завершено!</b>\n\n"
|
||||
f"Правильных ответов: <b>{correct_count}</b> из {total_count}\n"
|
||||
f"Точность: <b>{accuracy}%</b>\n\n"
|
||||
f"{comment}\n\n"
|
||||
f"Используй /task для нового задания\n"
|
||||
f"Используй /stats для просмотра статистики"
|
||||
t(lang, 'tasks.finish_title', emoji=emoji) + "\n\n" +
|
||||
t(lang, 'tasks.correct_of', correct=correct_count, total=total_count) + "\n" +
|
||||
t(lang, 'tasks.accuracy', accuracy=accuracy) + "\n\n" +
|
||||
t(lang, f"tasks.comment.{comment_key}") + "\n\n" +
|
||||
t(lang, 'tasks.use_task') + "\n" +
|
||||
t(lang, 'tasks.use_stats')
|
||||
)
|
||||
|
||||
await state.clear()
|
||||
@@ -236,26 +262,27 @@ async def cmd_stats(message: Message):
|
||||
user = await UserService.get_user_by_telegram_id(session, message.from_user.id)
|
||||
|
||||
if not user:
|
||||
await message.answer("Сначала запусти бота командой /start")
|
||||
await message.answer(t('ru', 'common.start_first'))
|
||||
return
|
||||
|
||||
# Получаем статистику
|
||||
stats = await TaskService.get_user_stats(session, user.id)
|
||||
lang = (user.language_interface if user else 'ru') or 'ru'
|
||||
|
||||
stats_text = (
|
||||
f"📊 <b>Твоя статистика</b>\n\n"
|
||||
f"📚 Слов в словаре: <b>{stats['total_words']}</b>\n"
|
||||
f"📖 Слов изучено: <b>{stats['reviewed_words']}</b>\n"
|
||||
f"✍️ Заданий выполнено: <b>{stats['total_tasks']}</b>\n"
|
||||
f"✅ Правильных ответов: <b>{stats['correct_tasks']}</b>\n"
|
||||
f"🎯 Точность: <b>{stats['accuracy']}%</b>\n\n"
|
||||
t(lang, 'stats.header') + "\n\n" +
|
||||
t(lang, 'stats.total_words', n=stats['total_words']) + "\n" +
|
||||
t(lang, 'stats.studied_words', n=stats['reviewed_words']) + "\n" +
|
||||
t(lang, 'stats.total_tasks', n=stats['total_tasks']) + "\n" +
|
||||
t(lang, 'stats.correct_tasks', n=stats['correct_tasks']) + "\n" +
|
||||
t(lang, 'stats.accuracy', n=stats['accuracy']) + "\n\n"
|
||||
)
|
||||
|
||||
if stats['total_words'] == 0:
|
||||
stats_text += "Добавь слова командой /add чтобы начать обучение!"
|
||||
stats_text += t(lang, 'stats.hint_add_words')
|
||||
elif stats['total_tasks'] == 0:
|
||||
stats_text += "Выполни первое задание командой /task!"
|
||||
stats_text += t(lang, 'stats.hint_first_task')
|
||||
else:
|
||||
stats_text += "Продолжай практиковаться! 💪"
|
||||
stats_text += t(lang, 'stats.hint_keep_practice')
|
||||
|
||||
await message.answer(stats_text)
|
||||
|
||||
Reference in New Issue
Block a user