feat: JLPT levels for Japanese, custom practice scenarios, UI improvements

- Add separate level systems: CEFR (A1-C2) for European languages, JLPT (N5-N1) for Japanese
- Store levels per language in new `levels_by_language` JSON field
- Add custom scenario option in AI practice mode
- Show action buttons after practice ends (new dialogue, tasks, words)
- Fix level display across all handlers to use correct level system
- Add Alembic migration for levels_by_language field
- Update all locale files (ru, en, ja) with new keys

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

Co-Authored-By: Claude <noreply@anthropic.com>
This commit is contained in:
2025-12-05 14:30:24 +03:00
parent 8bf3504d8d
commit 99deaafcbf
17 changed files with 983 additions and 308 deletions

View File

@@ -2,6 +2,7 @@ from sqlalchemy import select
from sqlalchemy.ext.asyncio import AsyncSession
from database.models import User, LanguageLevel
from typing import Optional
from utils.levels import set_user_level_for_language, get_default_level
class UserService:
@@ -59,14 +60,15 @@ class UserService:
return result.scalar_one_or_none()
@staticmethod
async def update_user_level(session: AsyncSession, user_id: int, level: LanguageLevel):
async def update_user_level(session: AsyncSession, user_id: int, level: str, language: str = None):
"""
Обновить уровень английского пользователя
Обновить уровень пользователя для языка изучения.
Args:
session: Сессия базы данных
user_id: ID пользователя
level: Новый уровень
level: Новый уровень (строка, например "B1" или "N4")
language: Язык (если None, берётся learning_language пользователя)
"""
result = await session.execute(
select(User).where(User.id == user_id)
@@ -74,7 +76,11 @@ class UserService:
user = result.scalar_one_or_none()
if user:
user.level = level
# Сохраняем в JSON для всех языков
set_user_level_for_language(user, level, language)
# Для обратной совместимости обновляем старое поле level (только для CEFR)
if level in ["A1", "A2", "B1", "B2", "C1", "C2"]:
user.level = LanguageLevel[level]
await session.commit()
@staticmethod