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:
2025-12-04 19:40:01 +03:00
parent 6223351ccf
commit 472771229f
22 changed files with 1587 additions and 471 deletions

View File

@@ -56,27 +56,27 @@ class AIService:
response.raise_for_status()
return response.json()
async def translate_word(self, word: str, target_lang: str = "ru") -> Dict:
async def translate_word(self, word: str, source_lang: str = "en", translation_lang: str = "ru") -> Dict:
"""
Перевести слово и получить дополнительную информацию
Args:
word: Слово для перевода
target_lang: Язык перевода (по умолчанию русский)
source_lang: Язык исходного слова (ISO2)
translation_lang: Язык перевода (ISO2)
Returns:
Dict с переводом, транскрипцией и примерами
"""
prompt = f"""Переведи английское слово/фразу "{word}" на русский язык.
prompt = f"""Переведи слово/фразу "{word}" с языка {source_lang} на {translation_lang}.
Верни ответ строго в формате JSON:
{{
"word": "{word}",
"translation": "перевод",
"transcription": "транскрипция в IPA",
"word": "исходное слово на {source_lang}",
"translation": "перевод на {translation_lang}",
"transcription": "транскрипция в IPA (если применимо)",
"examples": [
{{"en": "пример на английском", "ru": "перевод примера"}},
{{"en": "ещё один пример", "ru": "перевод примера"}}
{{"{source_lang}": "пример на языке обучения", "{translation_lang}": "перевод примера"}}
],
"category": "категория слова (работа, еда, путешествия и т.д.)",
"difficulty": "уровень сложности (A1/A2/B1/B2/C1/C2)"
@@ -85,10 +85,10 @@ class AIService:
Важно: верни только JSON, без дополнительного текста."""
try:
logger.info(f"[GPT Request] translate_word: word='{word}', target_lang='{target_lang}'")
logger.info(f"[GPT Request] translate_word: word='{word}', source='{source_lang}', to='{translation_lang}'")
messages = [
{"role": "system", "content": "Ты - помощник для изучения английского языка. Отвечай только в формате JSON."},
{"role": "system", "content": "Ты - помощник для изучения языков. Отвечай только в формате JSON."},
{"role": "user", "content": prompt}
]
@@ -161,33 +161,35 @@ class AIService:
"score": 0
}
async def generate_fill_in_sentence(self, word: str) -> Dict:
async def generate_fill_in_sentence(self, word: str, learning_lang: str = "en", translation_lang: str = "ru") -> Dict:
"""
Сгенерировать предложение с пропуском для заданного слова
Args:
word: Слово, для которого нужно создать предложение
word: Слово (на языке обучения), для которого нужно создать предложение
learning_lang: Язык обучения (ISO2)
translation_lang: Язык перевода предложения (ISO2)
Returns:
Dict с предложением и правильным ответом
"""
prompt = f"""Создай предложение на английском языке, используя слово "{word}".
prompt = f"""Создай предложение на языке {learning_lang}, используя слово "{word}".
Замени это слово на пропуск "___".
Верни ответ в формате JSON:
{{
"sentence": "предложение с пропуском ___",
"answer": "{word}",
"translation": "перевод предложения на русский"
"translation": "перевод предложения на {translation_lang}"
}}
Предложение должно быть простым и естественным. Контекст должен четко подсказывать правильное слово."""
try:
logger.info(f"[GPT Request] generate_fill_in_sentence: word='{word}'")
logger.info(f"[GPT Request] generate_fill_in_sentence: word='{word}', lang='{learning_lang}', to='{translation_lang}'")
messages = [
{"role": "system", "content": "Ты - преподаватель английского языка. Создавай простые и понятные упражнения."},
{"role": "system", "content": "Ты - преподаватель иностранных языков. Создавай простые и понятные упражнения."},
{"role": "user", "content": prompt}
]
@@ -206,7 +208,7 @@ class AIService:
"translation": f"Мне нравится {word} каждый день."
}
async def generate_thematic_words(self, theme: str, level: str = "B1", count: int = 10) -> List[Dict]:
async def generate_thematic_words(self, theme: str, level: str = "B1", count: int = 10, learning_lang: str = "en", translation_lang: str = "ru") -> List[Dict]:
"""
Сгенерировать подборку слов по теме
@@ -218,17 +220,17 @@ class AIService:
Returns:
Список словарей с информацией о словах
"""
prompt = f"""Создай подборку из {count} английских слов по теме "{theme}" для уровня {level}.
prompt = f"""Создай подборку из {count} слов на языке {learning_lang} по теме "{theme}" для уровня {level}. Переводы дай на {translation_lang}.
Верни ответ в формате JSON:
{{
"theme": "{theme}",
"words": [
{{
"word": "английское слово",
"translation": "перевод на русский",
"transcription": "транскрипция в IPA",
"example": "пример использования на английском"
"word": "слово на {learning_lang}",
"translation": "перевод на {translation_lang}",
"transcription": "транскрипция в IPA (если применимо)",
"example": "пример использования на {learning_lang}"
}}
]
}}
@@ -240,10 +242,10 @@ class AIService:
- Разнообразными (существительные, глаголы, прилагательные)"""
try:
logger.info(f"[GPT Request] generate_thematic_words: theme='{theme}', level='{level}', count={count}")
logger.info(f"[GPT Request] generate_thematic_words: theme='{theme}', level='{level}', count={count}, learn='{learning_lang}', to='{translation_lang}'")
messages = [
{"role": "system", "content": "Ты - преподаватель английского языка. Подбирай полезные и актуальные слова."},
{"role": "system", "content": "Ты - преподаватель иностранных языков. Подбирай полезные и актуальные слова."},
{"role": "user", "content": prompt}
]
@@ -259,7 +261,7 @@ class AIService:
logger.error(f"[GPT Error] generate_thematic_words: {type(e).__name__}: {str(e)}")
return []
async def extract_words_from_text(self, text: str, level: str = "B1", max_words: int = 15) -> List[Dict]:
async def extract_words_from_text(self, text: str, level: str = "B1", max_words: int = 15, learning_lang: str = "en", translation_lang: str = "ru") -> List[Dict]:
"""
Извлечь ключевые слова из текста для изучения
@@ -271,7 +273,7 @@ class AIService:
Returns:
Список словарей с информацией о словах
"""
prompt = f"""Проанализируй следующий английский текст и извлеки из него до {max_words} самых полезных слов для изучения на уровне {level}.
prompt = f"""Проанализируй следующий текст на языке {learning_lang} и извлеки из него до {max_words} самых полезных слов для изучения на уровне {level}. Переводы дай на {translation_lang}.
Текст:
{text}
@@ -280,10 +282,10 @@ class AIService:
{{
"words": [
{{
"word": "английское слово (в базовой форме)",
"translation": "перевод на русский",
"transcription": "транскрипция в IPA",
"context": "предложение из текста, где используется это слово"
"word": "слово на {learning_lang} (в базовой форме)",
"translation": "перевод на {translation_lang}",
"transcription": "транскрипция в IPA (если применимо)",
"context": "предложение из текста на {learning_lang}, где используется это слово"
}}
]
}}
@@ -297,10 +299,10 @@ class AIService:
try:
text_preview = text[:100] + "..." if len(text) > 100 else text
logger.info(f"[GPT Request] extract_words_from_text: text_length={len(text)}, level='{level}', max_words={max_words}")
logger.info(f"[GPT Request] extract_words_from_text: text_length={len(text)}, level='{level}', max_words={max_words}, learn='{learning_lang}', to='{translation_lang}'")
messages = [
{"role": "system", "content": "Ты - преподаватель английского языка. Помогаешь извлекать полезные слова для изучения из текстов."},
{"role": "system", "content": "Ты - преподаватель иностранных языков. Помогаешь извлекать полезные слова для изучения из текстов."},
{"role": "user", "content": prompt}
]
@@ -316,7 +318,7 @@ class AIService:
logger.error(f"[GPT Error] extract_words_from_text: {type(e).__name__}: {str(e)}")
return []
async def start_conversation(self, scenario: str, level: str = "B1") -> Dict:
async def start_conversation(self, scenario: str, level: str = "B1", learning_lang: str = "en", translation_lang: str = "ru") -> Dict:
"""
Начать диалоговую практику с AI
@@ -338,14 +340,14 @@ class AIService:
scenario_desc = scenarios.get(scenario, "повседневный разговор")
prompt = f"""Ты - собеседник для практики английского языка уровня {level}.
Начни диалог в сценарии: {scenario_desc}.
prompt = f"""Ты - собеседник для практики языка {learning_lang} уровня {level}.
Начни диалог в сценарии: {scenario_desc} на {learning_lang}.
Верни ответ в формате JSON:
{{
"message": "твоя первая реплика на английском",
"translation": "перевод на русский",
"context": "краткое описание ситуации на русском",
"message": "твоя первая реплика на {learning_lang}",
"translation": "перевод на {translation_lang}",
"context": "краткое описание ситуации на {translation_lang}",
"suggestions": ["подсказка 1", "подсказка 2", "подсказка 3"]
}}
@@ -356,10 +358,10 @@ class AIService:
- Подсказки должны помочь пользователю ответить"""
try:
logger.info(f"[GPT Request] start_conversation: scenario='{scenario}', level='{level}'")
logger.info(f"[GPT Request] start_conversation: scenario='{scenario}', level='{level}', learn='{learning_lang}', to='{translation_lang}'")
messages = [
{"role": "system", "content": "Ты - дружелюбный собеседник для практики английского. Веди естественный диалог."},
{"role": "system", "content": "Ты - дружелюбный собеседник для практики иностранных языков. Веди естественный диалог."},
{"role": "user", "content": prompt}
]
@@ -384,7 +386,9 @@ class AIService:
conversation_history: List[Dict],
user_message: str,
scenario: str,
level: str = "B1"
level: str = "B1",
learning_lang: str = "en",
translation_lang: str = "ru"
) -> Dict:
"""
Продолжить диалог и проверить ответ пользователя
@@ -404,7 +408,7 @@ class AIService:
for msg in conversation_history[-6:] # Последние 6 сообщений
])
prompt = f"""Ты ведешь диалог на английском языке уровня {level} в сценарии "{scenario}".
prompt = f"""Ты ведешь диалог на языке {learning_lang} уровня {level} в сценарии "{scenario}".
История диалога:
{history_text}
@@ -412,8 +416,8 @@ User: {user_message}
Верни ответ в формате JSON:
{{
"response": "твой ответ на английском",
"translation": "перевод твоего ответа на русский",
"response": "твой ответ на {learning_lang}",
"translation": "перевод твоего ответа на {translation_lang}",
"feedback": {{
"has_errors": true/false,
"corrections": "исправления ошибок пользователя (если есть)",
@@ -429,11 +433,11 @@ User: {user_message}
- Используй лексику уровня {level}"""
try:
logger.info(f"[GPT Request] continue_conversation: scenario='{scenario}', level='{level}', history_length={len(conversation_history)}")
logger.info(f"[GPT Request] continue_conversation: scenario='{scenario}', level='{level}', history_length={len(conversation_history)}, learn='{learning_lang}', to='{translation_lang}'")
# Формируем сообщения для API
messages = [
{"role": "system", "content": f"Ты - дружелюбный собеседник для практики английского языка уровня {level}. Веди естественный диалог и помогай исправлять ошибки."}
{"role": "system", "content": f"Ты - дружелюбный собеседник для практики языка {learning_lang} уровня {level}. Веди естественный диалог и помогай исправлять ошибки."}
]
# Добавляем историю