From 3362bf89e201e5ff81b979d1e63f43ead583f84d Mon Sep 17 00:00:00 2001 From: vrubelroman Date: Wed, 12 Nov 2025 23:20:01 +0300 Subject: [PATCH] EN language only --- LichessClientTG_bot/bot.py | 223 +++++++++++++++++++++--------- LichessClientTG_bot/database.py | 26 +++- LichessClientTG_bot/formatters.py | 62 ++++++--- LichessClientTG_bot/i18n.py | 136 ++++++++++++++++++ 4 files changed, 353 insertions(+), 94 deletions(-) create mode 100644 LichessClientTG_bot/i18n.py diff --git a/LichessClientTG_bot/bot.py b/LichessClientTG_bot/bot.py index e91c763..8f5872c 100644 --- a/LichessClientTG_bot/bot.py +++ b/LichessClientTG_bot/bot.py @@ -1,5 +1,6 @@ import asyncio import logging +import sqlite3 from datetime import datetime, timedelta from typing import Dict, Any, Optional @@ -17,6 +18,7 @@ from config import ( from database import Database from lichess_api import LichessAPI from formatters import StatsFormatter +from i18n import t # Configure logging logging.basicConfig( @@ -35,6 +37,20 @@ class LichessBot: self.periodic_tasks = {} # Store periodic tasks self.period_start_times = {} # Store start times for each gamer self.application = None # Will be set when application is created + + def get_user_language_from_update(self, update: Update) -> str: + """Always return English language""" + # Update user info in database + user = update.effective_user + if user: + self.db.add_or_get_telegram_user( + user_id=user.id, + username=user.username, + first_name=user.first_name, + last_name=user.last_name, + language_code=None + ) + return 'en' async def start_existing_periodic_tasks(self): """Start periodic tasks for all user-gamer pairs that have periods set""" @@ -56,41 +72,17 @@ class LichessBot: """Start command handler""" # Register user in database user = update.effective_user + lang_code = user.language_code if user else None self.db.add_or_get_telegram_user( user_id=user.id, username=user.username, first_name=user.first_name, - last_name=user.last_name + last_name=user.last_name, + language_code=lang_code ) - await update.message.reply_text( - "Бот отслеживает игру одного или нескольких игроков на Lichess.\n" - "Он показывает рейтинги, дневную, вчерашнюю и недельную активность.\n" - "При указании user_token (с правом чтения задач) можно также получать данные по задачам.\n" - "Бот поддерживает автоматические проверки с заданным интервалом и отправляет отчёт, если за это время была активность.\n\n" - "Пример за неделю:\n" - "🧩 Задачи: 114 (✅ 81 - ❌ 33)\n\n" - "🔥 Blitz — 5 игр • 🔴 -10\n" - "Рейтинг: 2245\n" - "✅ Победы: 1\n" - "❌ Поражения: 3\n" - "🤝 Ничьи: 1\n\n" - "🐇 Rapid — 19 игр • 🟢 +20\n" - "Рейтинг: 2248\n" - "✅ Победы: 8\n" - "❌ Поражения: 4\n" - "🤝 Ничьи: 7\n\n" - "📋 Доступные команды:\n" - "/addgamer - Добавить игрока Lichess для отслеживания (только username)\n" - "/addtoken - Добавить игрока с токеном для получения данных по задачам\n" - "/getgamers - Выбрать активного игрока (для которого будут работать команды /today и тд)\n" - "/delgamer - Удалить игрока из списка отслеживаемых\n" - "/today - Статистика за сегодня\n" - "/yesterday - Статистика за вчера\n" - "/week - Статистика за неделю\n" - "/setperiod - Настроить периодические уведомления активного игрока\n" - "(активный игрок меняется в меню команды /getgamers)" - ) + lang = self.get_user_language_from_update(update) + await update.message.reply_text(t('start_message', lang)) async def start_and_addgamer(self, update: Update, context: ContextTypes.DEFAULT_TYPE): """Start command that automatically launches addgamer""" @@ -101,16 +93,14 @@ class LichessBot: async def addgamer_start(self, update: Update, context: ContextTypes.DEFAULT_TYPE): """Start addgamer command - simple username only""" - await update.message.reply_text("👤 Введите Lichess username игрока для отслеживания:") + lang = self.get_user_language_from_update(update) + await update.message.reply_text(t('addgamer_prompt', lang)) return WAITING_FOR_USERNAME async def addtoken_start(self, update: Update, context: ContextTypes.DEFAULT_TYPE): """Start addtoken command - token required""" - await update.message.reply_text( - "🔑 Введите Lichess API токен, чтобы получать данные по задачам.\n" - "Токен создаётся в настройках профиля — дайте ему только право puzzle:read.\n" - "После этого будет добавлен игрок, соответствующий этому токену." - ) + lang = self.get_user_language_from_update(update) + await update.message.reply_text(t('addtoken_prompt', lang)) return WAITING_FOR_TOKEN async def handle_token(self, update: Update, context: ContextTypes.DEFAULT_TYPE): @@ -127,11 +117,12 @@ class LichessBot: user_gamers = self.db.get_user_gamers(user_id) existing_gamer = next((g for g in user_gamers if g['username'] == username), None) + lang = self.get_user_language_from_update(update) if existing_gamer: # Update token for existing gamer self.db.add_user_gamer(user_id, existing_gamer['id'], token) await update.message.reply_text( - f"✅ Токен добавлен для игрока {username}!" + t('token_added', lang, username=username) ) else: # Add new gamer and link with token @@ -147,17 +138,19 @@ class LichessBot: self.db.set_user_active_gamer(user_id, gamer_id) await update.message.reply_text( - f"✅ Игрок {username} добавлен с токеном!" + t('gamer_added_with_token', lang, username=username) ) return ConversationHandler.END else: + lang = self.get_user_language_from_update(update) await update.message.reply_text( - "❌ Не удалось получить username из токена. Попробуйте еще раз." + t('token_username_error', lang) ) return WAITING_FOR_TOKEN else: + lang = self.get_user_language_from_update(update) await update.message.reply_text( - "❌ Неверный токен. Попробуйте еще раз." + t('invalid_token', lang) ) return WAITING_FOR_TOKEN @@ -166,9 +159,10 @@ class LichessBot: username = update.message.text.strip() user_id = update.effective_user.id + lang = self.get_user_language_from_update(update) if not username: await update.message.reply_text( - "❌ Username не может быть пустым. Попробуйте еще раз." + t('empty_username', lang) ) return WAITING_FOR_USERNAME @@ -176,7 +170,7 @@ class LichessBot: user_exists = await self.lichess_api.check_user_exists(username) if not user_exists: await update.message.reply_text( - f"❌ Игрок {username} не найден на Lichess. Проверьте правильность написания имени." + t('user_not_found', lang, username=username) ) return WAITING_FOR_USERNAME @@ -193,8 +187,9 @@ class LichessBot: if len(user_gamers) == 1: self.db.set_user_active_gamer(user_id, gamer_id) + lang = self.get_user_language_from_update(update) await update.message.reply_text( - f"✅ Игрок {username} успешно добавлен!" + t('gamer_added', lang, username=username) ) return ConversationHandler.END @@ -206,12 +201,13 @@ class LichessBot: logger.info(f"getgamers: user_id={user_id}, found {len(gamers)} gamers") + lang = self.get_user_language_from_update(update) if not gamers: - await update.message.reply_text("📭 В базе нет игроков. Используйте /adduser для добавления.") + await update.message.reply_text(t('no_gamers', lang)) return # Show loading message - loading_msg = await update.message.reply_text("🔄 Загружаем рейтинги игроков...") + loading_msg = await update.message.reply_text(t('loading_ratings', lang)) # Prepare data for each gamer gamers_data = [] @@ -240,7 +236,8 @@ class LichessBot: # Add period information if period > 0 period_minutes = gamer.get('period_minutes', 0) - period_text = f" · {period_minutes}м" if period_minutes > 0 else "" + period_suffix = t('period_minutes_suffix', lang) + period_text = f" · {period_minutes}{period_suffix}" if period_minutes > 0 else "" gamers_data.append({ 'id': gamer['id'], @@ -257,6 +254,9 @@ class LichessBot: import traceback logger.error(f"Traceback: {traceback.format_exc()}") # Still add the gamer with N/A ratings + period_minutes = gamer.get('period_minutes', 0) + period_suffix = t('period_minutes_suffix', lang) + period_text = f" · {period_minutes}{period_suffix}" if period_minutes > 0 else "" gamers_data.append({ 'id': gamer['id'], 'status': "🟢" if gamer['is_active'] else "⚪", @@ -264,7 +264,7 @@ class LichessBot: 'bullet': 'N/A', 'blitz': 'N/A', 'rapid': 'N/A', - 'period': f" · {gamer.get('period_minutes', 0)}м" if gamer.get('period_minutes', 0) > 0 else "" + 'period': period_text }) logger.info(f"Added gamer {gamer['username']} with N/A ratings due to error") @@ -277,7 +277,7 @@ class LichessBot: ) logger.info(f"getgamers: prepared {len(gamers_data)} gamers for display") - gamers_text = "👥 Выберите активного игрока:\n\n" + "\n".join(text_lines) + gamers_text = t('select_active_gamer', lang) + "\n".join(text_lines) logger.info(f"getgamers: message length: {len(gamers_text)} characters") @@ -325,26 +325,37 @@ class LichessBot: gamers = self.db.get_user_gamers(user_id) selected_gamer = next((g for g in gamers if g['id'] == gamer_id), None) + # Для callback query обновляем язык пользователя если есть в update + if update.effective_user: + self.db.add_or_get_telegram_user( + user_id=update.effective_user.id, + username=update.effective_user.username, + first_name=update.effective_user.first_name, + last_name=update.effective_user.last_name, + language_code=update.effective_user.language_code + ) + lang = self.db.get_user_language(user_id) if selected_gamer: # Set active gamer for this user self.db.set_user_active_gamer(user_id, gamer_id) await query.edit_message_text( - f"✅ Активный игрок: {selected_gamer['username']}" + t('active_gamer_set', lang, username=selected_gamer['username']) ) else: - await query.edit_message_text("❌ Игрок не найден") + await query.edit_message_text(t('gamer_not_found', lang)) async def delgamer(self, update: Update, context: ContextTypes.DEFAULT_TYPE): """Show gamers list for deletion""" user_id = update.effective_user.id gamers = self.db.get_user_gamers(user_id) + lang = self.get_user_language_from_update(update) if not gamers: - await update.message.reply_text("📭 У вас нет отслеживаемых игроков.") + await update.message.reply_text(t('no_gamers_to_delete', lang)) return # Show loading message - loading_msg = await update.message.reply_text("🔄 Загружаем список игроков...") + loading_msg = await update.message.reply_text(t('loading_gamers', lang)) # Create text message with stats text_lines = [] @@ -370,7 +381,8 @@ class LichessBot: bullet_rating = blitz_rating = rapid_rating = 'N/A' period_minutes = gamer.get('period_minutes', 0) - period_text = f" · {period_minutes}м" if period_minutes > 0 else "" + period_suffix = t('period_minutes_suffix', lang) + period_text = f" · {period_minutes}{period_suffix}" if period_minutes > 0 else "" text_lines.append( f"{status} {username} " @@ -383,7 +395,7 @@ class LichessBot: callback_data=f"delete_{gamer['id']}" )]) - gamers_text = "🗑️ Выберите игрока для удаления:\n\n" + "\n".join(text_lines) + gamers_text = t('select_gamer_to_delete', lang) + "\n".join(text_lines) reply_markup = InlineKeyboardMarkup(keyboard) @@ -418,19 +430,29 @@ class LichessBot: gamers = self.db.get_user_gamers(user_id) gamer_to_delete = next((g for g in gamers if g['id'] == gamer_id), None) + # Для callback query получаем язык из БД + if update.effective_user: + self.db.add_or_get_telegram_user( + user_id=update.effective_user.id, + username=update.effective_user.username, + first_name=update.effective_user.first_name, + last_name=update.effective_user.last_name, + language_code=update.effective_user.language_code + ) + lang = self.db.get_user_language(user_id) if gamer_to_delete: username = gamer_to_delete['username'] deleted = self.db.remove_user_gamer(user_id, gamer_id) if deleted: await query.edit_message_text( - f"✅ Игрок {username} удален из списка отслеживаемых.", + t('gamer_deleted', lang, username=username), parse_mode='HTML' ) else: - await query.edit_message_text("❌ Не удалось удалить игрока") + await query.edit_message_text(t('delete_failed', lang)) else: - await query.edit_message_text("❌ Игрок не найден") + await query.edit_message_text(t('gamer_not_found', lang)) async def get_stats(self, update: Update, context: ContextTypes.DEFAULT_TYPE, period: str): """Get statistics for a period""" @@ -439,9 +461,10 @@ class LichessBot: # Get active gamer for this user active_gamer = self.db.get_user_active_gamer(user_id) + lang = self.get_user_language_from_update(update) if not active_gamer: await update.message.reply_text( - "❌ Нет активного игрока. Используйте /getgamers для выбора." + t('no_active_gamer', lang) ) return @@ -455,11 +478,11 @@ class LichessBot: elif period == "week": data = await self.lichess_api.get_week_stats(username) else: - await update.message.reply_text("❌ Неизвестный период") + await update.message.reply_text(t('unknown_period', lang)) return # Format and send response - formatted_response = StatsFormatter.format_stats_response(data, username, period) + formatted_response = StatsFormatter.format_stats_response(data, username, period, lang) await update.message.reply_text(formatted_response) async def today(self, update: Update, context: ContextTypes.DEFAULT_TYPE): @@ -481,24 +504,24 @@ class LichessBot: # Get active gamer for this user active_gamer = self.db.get_user_active_gamer(user_id) + lang = self.get_user_language_from_update(update) if not active_gamer: await update.message.reply_text( - "❌ Нет активного игрока. Используйте /getgamers для выбора." + t('no_active_gamer', lang) ) return keyboard = [] for period in PERIOD_OPTIONS: if period == 0: - button_text = "❌ Отключить уведомления" + button_text = t('disable_notifications', lang) else: - button_text = f"⏰ {period} минут" + button_text = t('period_minutes', lang, period=period) keyboard.append([InlineKeyboardButton(button_text, callback_data=f"period_{period}")]) reply_markup = InlineKeyboardMarkup(keyboard) await update.message.reply_text( - f"⏱️ Выберите период для игрока {active_gamer['username']}:\n" - f"📱 Уведомления будут приходить в личные сообщения", + t('select_period', lang, username=active_gamer['username']), reply_markup=reply_markup ) @@ -513,23 +536,83 @@ class LichessBot: # Get active gamer for this user active_gamer = self.db.get_user_active_gamer(user_id) + # Для callback query получаем язык из БД + if update.effective_user: + self.db.add_or_get_telegram_user( + user_id=update.effective_user.id, + username=update.effective_user.username, + first_name=update.effective_user.first_name, + last_name=update.effective_user.last_name, + language_code=update.effective_user.language_code + ) + lang = self.db.get_user_language(user_id) if active_gamer: # Set period for this user-gamer pair self.db.set_user_gamer_period(user_id, active_gamer['id'], period) if period == 0: await query.edit_message_text( - f"✅ Уведомления для {active_gamer['username']} отключены" + t('notifications_disabled', lang, username=active_gamer['username']) ) else: await query.edit_message_text( - f"✅ Период {period} минут установлен для {active_gamer['username']}\n" - f"📱 Уведомления будут приходить в личные сообщения" + t('period_set', lang, period=period, username=active_gamer['username']) ) # Start periodic task for this gamer (send to user's personal messages) await self.start_periodic_task(active_gamer, user_id, period) + async def check_language(self, update: Update, context: ContextTypes.DEFAULT_TYPE): + """Check and display current language settings""" + user = update.effective_user + if user: + # Обновляем язык из update + lang = self.get_user_language_from_update(update) + + # Получаем язык из БД для отображения + db_lang = self.db.get_user_language(user.id) + + # Получаем language_code из БД напрямую + with sqlite3.connect(self.db.db_path) as conn: + cursor = conn.cursor() + cursor.execute("SELECT language_code FROM telegram_users WHERE user_id = ?", (user.id,)) + row = cursor.fetchone() + db_language_code = row[0] if row and row[0] else None + + message = ( + f"🌐 Language Info:\n" + f"Current language: {lang}\n" + f"DB language: {db_lang}\n" + f"DB language_code: {db_language_code}\n" + f"Update language_code: {user.language_code}\n\n" + f"Language used: {lang}\n\n" + f"Bot uses English language only." + ) + await update.message.reply_text(message) + else: + await update.message.reply_text("❌ Failed to get user information") + + async def reset_language(self, update: Update, context: ContextTypes.DEFAULT_TYPE): + """Reset user language in database - will be detected from /start""" + user = update.effective_user + if user: + # Сбрасываем язык в БД (устанавливаем NULL) + with sqlite3.connect(self.db.db_path) as conn: + cursor = conn.cursor() + cursor.execute( + "UPDATE telegram_users SET language_code = NULL WHERE user_id = ?", + (user.id,) + ) + conn.commit() + + # Language is always English + lang = 'en' + await update.message.reply_text( + "✅ Language reset! Bot uses English language only." + ) + else: + await update.message.reply_text("❌ Failed to get user information") + async def start_periodic_task(self, gamer: Dict[str, Any], user_id: int, period_minutes: int): """Start periodic task for a gamer""" task_key = f"{gamer['id']}_{user_id}" @@ -622,8 +705,10 @@ class LichessBot: # Отправляем уведомление только если есть реальная активность if has_games or has_puzzles: try: + # Get user language from database + user_lang = self.db.get_user_language(user_id) notification = StatsFormatter.format_period_notification( - gamer['username'], games_data, puzzles_data, period_minutes + gamer['username'], games_data, puzzles_data, period_minutes, lang=user_lang ) if self.application: @@ -690,6 +775,8 @@ class LichessBot: application.add_handler(CommandHandler("yesterday", self.yesterday)) application.add_handler(CommandHandler("week", self.week)) application.add_handler(CommandHandler("setperiod", self.setperiod)) + application.add_handler(CommandHandler("lang", self.check_language)) + application.add_handler(CommandHandler("resetlang", self.reset_language)) # Callback handlers application.add_handler(CallbackQueryHandler(self.select_gamer, pattern="^select_")) diff --git a/LichessClientTG_bot/database.py b/LichessClientTG_bot/database.py index 595a839..af966cf 100644 --- a/LichessClientTG_bot/database.py +++ b/LichessClientTG_bot/database.py @@ -22,10 +22,18 @@ class Database: username TEXT, first_name TEXT, last_name TEXT, + language_code TEXT, created_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP ) ''') + # Add language_code column if it doesn't exist + try: + cursor.execute("ALTER TABLE telegram_users ADD COLUMN language_code TEXT") + except sqlite3.OperationalError: + # Column already exists + pass + # Create gamers table (Lichess players only) cursor.execute(''' CREATE TABLE IF NOT EXISTS gamers ( @@ -104,7 +112,8 @@ class Database: logger.info(f"Migrated {migrated} tokens from gamers to user_gamers") def add_or_get_telegram_user(self, user_id: int, username: Optional[str] = None, - first_name: Optional[str] = None, last_name: Optional[str] = None) -> bool: + first_name: Optional[str] = None, last_name: Optional[str] = None, + language_code: Optional[str] = None) -> bool: """Add or update Telegram user""" with sqlite3.connect(self.db_path) as conn: cursor = conn.cursor() @@ -114,13 +123,24 @@ class Database: if not existing: cursor.execute( - "INSERT INTO telegram_users (user_id, username, first_name, last_name) VALUES (?, ?, ?, ?)", - (user_id, username, first_name, last_name) + "INSERT INTO telegram_users (user_id, username, first_name, last_name, language_code) VALUES (?, ?, ?, ?, ?)", + (user_id, username, first_name, last_name, language_code) ) conn.commit() return True + else: + # Update language_code if provided (always update, even if None to clear old value) + cursor.execute( + "UPDATE telegram_users SET language_code = ? WHERE user_id = ?", + (language_code, user_id) + ) + conn.commit() return False + def get_user_language(self, user_id: int) -> str: + """Always return English language""" + return 'en' + def add_gamer(self, username: str) -> int: """Add a new gamer to the database (return gamer_id)""" with sqlite3.connect(self.db_path) as conn: diff --git a/LichessClientTG_bot/formatters.py b/LichessClientTG_bot/formatters.py index 872f32b..b0abbcd 100644 --- a/LichessClientTG_bot/formatters.py +++ b/LichessClientTG_bot/formatters.py @@ -1,5 +1,6 @@ from typing import Dict, Any, Optional from datetime import datetime +from i18n import t class StatsFormatter: @staticmethod @@ -13,10 +14,10 @@ class StatsFormatter: return f"0" @staticmethod - def format_stats_response(data: Dict[str, Any], username: str, period: str) -> str: + def format_stats_response(data: Dict[str, Any], username: str, period: str, lang: str = 'en') -> str: """Format statistics response according to the template""" if not data or data.get('data') is None: - message = data.get('message', 'Нет данных') if data else 'Нет данных' + message = data.get('message', t('no_data', lang)) if data else t('no_data', lang) return f"📭 {message}" # Extract data from API response @@ -25,7 +26,7 @@ class StatsFormatter: games = api_data.get('games', {}) # Format date range - date_range = StatsFormatter._get_date_range(period) + date_range = StatsFormatter._get_date_range(period, lang) # Format tasks section task_text = "" @@ -33,7 +34,7 @@ class StatsFormatter: total_tasks = tasks.get('total', 0) solved = tasks.get('solved', 0) unsolved = tasks.get('unsolved', 0) - task_text = f"🧩 Задачи: {total_tasks} (✅ {solved} - ❌ {unsolved})\n\n" + task_text = t('puzzles_section', lang, total=total_tasks, solved=solved, unsolved=unsolved) # Format games section games_text = "" @@ -55,21 +56,29 @@ class StatsFormatter: # Format rating change rating_change_str = StatsFormatter._format_rating_change(rating_change) - games_text += f"{emoji} {game_type.title()} — {games_count} игр • {rating_change_str}\n" - games_text += f"Рейтинг: {rating}\n" - games_text += f"✅ Победы: {wins}\n" - games_text += f"❌ Поражения: {losses}\n" - games_text += f"🤝 Ничьи: {draws}\n\n" + # Get game type name (capitalize first letter) + game_type_name = game_type.title() + + games_text += t('games_section', lang, + emoji=emoji, + game_type=game_type_name, + games_count=games_count, + rating_change=rating_change_str, + rating=rating, + wins=wins, + losses=losses, + draws=draws + ) # Combine all parts - result = f"📊 Статистика {username} • {date_range}\n\n" + result = t('stats_title', lang, username=username, date_range=date_range) result += task_text result += games_text.rstrip() return result @staticmethod - def _get_date_range(period: str) -> str: + def _get_date_range(period: str, lang: str = 'en') -> str: """Get date range string for the period""" from datetime import datetime, timedelta @@ -99,19 +108,19 @@ class StatsFormatter: return emoji_map.get(game_type.lower(), '🎯') @staticmethod - def format_period_notification(username: str, games_data: Optional[Dict], puzzles_data: Optional[Dict], period_minutes: int) -> str: + def format_period_notification(username: str, games_data: Optional[Dict], puzzles_data: Optional[Dict], period_minutes: int, lang: str = 'en') -> str: """Format notification for periodic checks""" from datetime import datetime # Format period text if period_minutes == 1: - period_text = "за 1 минуту" + period_text = t('period_1_minute', lang) elif period_minutes in [2, 3, 4]: - period_text = f"за {period_minutes} минуты" + period_text = t('period_2_3_4_minutes', lang, period=period_minutes) else: - period_text = f"за {period_minutes} минут" + period_text = t('period_minutes_text', lang, period=period_minutes) - result = f"📊 Статистика {username} • {period_text}\n\n" + result = t('period_notification_title', lang, username=username, period_text=period_text) # Format puzzles first (if available and there's actual activity) if puzzles_data and puzzles_data.get('data'): @@ -122,7 +131,7 @@ class StatsFormatter: # Only show tasks section if there's actual activity (not all zeros) if total_puzzles > 0 or solved > 0 or failed > 0: - result += f"🧩 Задачи: {total_puzzles} (✅ {solved} - ❌ {failed})\n\n" + result += t('period_puzzles_section', lang, total=total_puzzles, solved=solved, failed=failed) # Format games if games_data and games_data.get('data'): @@ -142,14 +151,21 @@ class StatsFormatter: draws = game_data.get('draws', 0) rating_change_str = StatsFormatter._format_rating_change(rating_change) - result += f"{emoji} {game_type.title()} — {games_count} игр • {rating_change_str}\n" - result += f"Рейтинг: {rating}\n" - result += f"✅ Победы: {wins}\n" - result += f"❌ Поражения: {losses}\n" - result += f"🤝 Ничьи: {draws}\n\n" + game_type_name = game_type.title() + + result += t('period_games_section', lang, + emoji=emoji, + game_type=game_type_name, + games_count=games_count, + rating_change=rating_change_str, + rating=rating, + wins=wins, + losses=losses, + draws=draws + ) # If no activity if not (games_data and games_data.get('data')) and not (puzzles_data and puzzles_data.get('data')): - result += "📭 Нет активности за этот период" + result += t('no_activity', lang) return result.rstrip() diff --git a/LichessClientTG_bot/i18n.py b/LichessClientTG_bot/i18n.py new file mode 100644 index 0000000..db279ee --- /dev/null +++ b/LichessClientTG_bot/i18n.py @@ -0,0 +1,136 @@ +""" +Internationalization module for the Lichess Telegram Bot +English language only +""" + +from typing import Optional + +# Translation dictionary (English only) +TRANSLATIONS = { + 'en': { + # Start command + 'start_message': ( + "The bot tracks one or more players on Lichess.\n" + "It shows ratings, daily, yesterday and weekly activity.\n" + "When specifying a user_token (with puzzle read permission), you can also get puzzle data.\n" + "The bot supports automatic checks at specified intervals and sends a report if there was activity during that time.\n\n" + "Example for a week:\n" + "🧩 Puzzles: 114 (✅ 81 - ❌ 33)\n\n" + "🔥 Blitz — 5 games • 🔴 -10\n" + "Rating: 2245\n" + "✅ Wins: 1\n" + "❌ Losses: 3\n" + "🤝 Draws: 1\n\n" + "🐇 Rapid — 19 games • 🟢 +20\n" + "Rating: 2248\n" + "✅ Wins: 8\n" + "❌ Losses: 4\n" + "🤝 Draws: 7\n\n" + "📋 Available commands:\n" + "/addgamer - Add a Lichess player to track (username only)\n" + "/addtoken - Add a player with a token to get puzzle data\n" + "/getgamers - Select active player (for which /today and other commands will work)\n" + "/delgamer - Remove a player from the tracked list\n" + "/today - Statistics for today\n" + "/yesterday - Statistics for yesterday\n" + "/week - Statistics for the week\n" + "/setperiod - Set up periodic notifications for the active player\n" + "(active player changes in the /getgamers menu)" + ), + + # Add gamer commands + 'addgamer_prompt': "👤 Enter the Lichess username of the player to track:", + 'addtoken_prompt': ( + "🔑 Enter the Lichess API token to get puzzle data.\n" + "The token is created in profile settings — give it only puzzle:read permission.\n" + "After that, the player corresponding to this token will be added." + ), + 'token_added': "✅ Token added for player {username}!", + 'gamer_added_with_token': "✅ Player {username} added with token!", + 'gamer_added': "✅ Player {username} successfully added!", + 'invalid_token': "❌ Invalid token. Please try again.", + 'token_username_error': "❌ Failed to get username from token. Please try again.", + 'empty_username': "❌ Username cannot be empty. Please try again.", + 'user_not_found': "❌ Player {username} not found on Lichess. Check the spelling of the name.", + + # Get gamers + 'no_gamers': "📭 No players in database. Use /addgamer to add.", + 'loading_ratings': "🔄 Loading player ratings...", + 'select_active_gamer': "👥 Select active player:\n\n", + 'active_gamer_set': "✅ Active player: {username}", + 'gamer_not_found': "❌ Player not found", + + # Delete gamer + 'no_gamers_to_delete': "📭 You have no tracked players.", + 'loading_gamers': "🔄 Loading player list...", + 'select_gamer_to_delete': "🗑️ Select player to delete:\n\n", + 'gamer_deleted': "✅ Player {username} removed from tracked list.", + 'delete_failed': "❌ Failed to delete player", + + # Stats commands + 'no_active_gamer': "❌ No active player. Use /getgamers to select.", + 'unknown_period': "❌ Unknown period", + 'no_data': "📭 No data", + 'stats_title': "📊 Statistics {username} • {date_range}\n\n", + 'puzzles_section': "🧩 Puzzles: {total} (✅ {solved} - ❌ {unsolved})\n\n", + 'games_section': "{emoji} {game_type} — {games_count} games • {rating_change}\nRating: {rating}\n✅ Wins: {wins}\n❌ Losses: {losses}\n🤝 Draws: {draws}\n\n", + + # Set period + 'select_period': "⏱️ Select period for player {username}:\n📱 Notifications will be sent to personal messages", + 'notifications_disabled': "✅ Notifications disabled for {username}", + 'period_set': "✅ Period {period} minutes set for {username}\n📱 Notifications will be sent to personal messages", + 'disable_notifications': "❌ Disable notifications", + 'period_minutes': "⏰ {period} minutes", + + # Period notification + 'period_1_minute': "for 1 minute", + 'period_2_3_4_minutes': "for {period} minutes", + 'period_minutes_text': "for {period} minutes", + 'period_notification_title': "📊 Statistics {username} • {period_text}\n\n", + 'period_puzzles_section': "🧩 Puzzles: {total} (✅ {solved} - ❌ {failed})\n\n", + 'period_games_section': "{emoji} {game_type} — {games_count} games • {rating_change}\nRating: {rating}\n✅ Wins: {wins}\n❌ Losses: {losses}\n🤝 Draws: {draws}\n\n", + 'no_activity': "📭 No activity for this period", + + # Common + 'period_minutes_suffix': "m", + } +} + + +def get_user_language(update) -> str: + """ + Always return English language. + """ + return 'en' + + +def t(key: str, lang: str = 'en', **kwargs) -> str: + """ + Translate a key (always English). + + Args: + key: Translation key + lang: Language code (ignored, always uses English) + **kwargs: Format arguments for the translation string + + Returns: + Translated string with formatted arguments + """ + translation = TRANSLATIONS['en'].get(key, key) + + # Format the string if kwargs provided + if kwargs: + try: + return translation.format(**kwargs) + except KeyError: + # If formatting fails, return the translation as-is + return translation + + return translation + + +def get_lang_from_update(update) -> str: + """ + Always return English. + """ + return 'en'