| """ |
| Gamification Module for Vocam Language Learning App |
| |
| This module implements gamification features: |
| 1. Achievement Badges & Milestones |
| 2. Daily Challenges |
| 3. Streaks & Consistency Rewards |
| 4. Progress Bars & Visual Feedback |
| """ |
|
|
| import streamlit as st |
| import os |
| import numpy as np |
| import pandas as pd |
| import matplotlib.pyplot as plt |
| import base64 |
| import json |
| import random |
| import tempfile |
| import time |
| from datetime import date, datetime, timedelta |
| from collections import defaultdict |
| from PIL import Image, ImageDraw, ImageFont |
| import io |
| import sqlite3 |
|
|
| class GamificationSystem: |
| def __init__(self, db_path="language_learning.db", translate_func=None): |
| """Initialize the gamification system.""" |
| self.db_path = db_path |
| self.translate_func = translate_func |
| self.initialize_state() |
| self.load_game_state() |
| |
| |
| self.ensure_asset_directories() |
| |
| |
| self.check_daily_updates() |
| |
| def ensure_asset_directories(self): |
| """Create necessary directories for gamification assets.""" |
| os.makedirs("assets/badges", exist_ok=True) |
| os.makedirs("assets/tree", exist_ok=True) |
| |
| def initialize_state(self): |
| """Initialize session state variables for gamification.""" |
| |
| if 'achievements' not in st.session_state: |
| st.session_state['achievements'] = {} |
| if 'badges' not in st.session_state: |
| st.session_state['badges'] = {} |
| if 'displayed_achievements' not in st.session_state: |
| st.session_state['displayed_achievements'] = set() |
| |
| |
| if 'daily_challenges' not in st.session_state: |
| st.session_state['daily_challenges'] = [] |
| if 'daily_challenges_completed' not in st.session_state: |
| st.session_state['daily_challenges_completed'] = set() |
| if 'word_of_the_day' not in st.session_state: |
| st.session_state['word_of_the_day'] = None |
| if 'word_of_the_day_date' not in st.session_state: |
| st.session_state['word_of_the_day_date'] = None |
| |
| |
| if 'streak_days' not in st.session_state: |
| st.session_state['streak_days'] = 0 |
| if 'last_active_date' not in st.session_state: |
| st.session_state['last_active_date'] = None |
| if 'streak_savers' not in st.session_state: |
| st.session_state['streak_savers'] = 0 |
| |
| |
| if 'points' not in st.session_state: |
| st.session_state['points'] = 0 |
| if 'level' not in st.session_state: |
| st.session_state['level'] = 1 |
| if 'category_progress' not in st.session_state: |
| st.session_state['category_progress'] = {} |
| if 'vocabulary_tree' not in st.session_state: |
| st.session_state['vocabulary_tree'] = { |
| 'size': 1, |
| 'leaves': 0, |
| 'fruit': 0, |
| 'level': 1 |
| } |
| |
| def check_daily_updates(self): |
| """Check and update streak and challenges for the current day.""" |
| self.check_streak() |
| self.generate_daily_challenges() |
| self.generate_word_of_the_day() |
| self.update_category_progress() |
| |
| |
| |
| |
| |
| def award_achievement(self, achievement_id, title, description, points=25): |
| """Award an achievement to the user if they don't already have it.""" |
| if achievement_id not in st.session_state.achievements: |
| st.session_state.achievements[achievement_id] = { |
| "id": achievement_id, |
| "title": title, |
| "description": description, |
| "date_earned": datetime.now().strftime("%Y-%m-%d %H:%M:%S") |
| } |
| st.session_state.displayed_achievements.add(achievement_id) |
| |
| |
| self.add_points(points, f"Achievement: {title}") |
| |
| |
| st.toast(f"🏆 Achievement Unlocked: {title}") |
| |
| |
| self.save_game_state() |
| return True |
| return False |
| |
| def award_badge(self, badge_id, title, category="general"): |
| """Award a badge to the user if they don't already have it.""" |
| if badge_id not in st.session_state.badges: |
| |
| badge_image = self.create_badge_image(badge_id, title, category) |
| |
| |
| st.session_state.badges[badge_id] = { |
| "id": badge_id, |
| "title": title, |
| "category": category, |
| "image_path": badge_image, |
| "date_earned": datetime.now().strftime("%Y-%m-%d %H:%M:%S") |
| } |
| |
| |
| st.toast(f"🏅 Badge Earned: {title}") |
| |
| |
| self.save_game_state() |
| return True |
| return False |
| |
| def create_badge_image(self, badge_id, title, category): |
| """Create a badge image and return the path.""" |
| try: |
| badge_path = f"assets/badges/{badge_id}.png" |
| |
| |
| if os.path.exists(badge_path): |
| return badge_path |
| |
| |
| size = 200 |
| img = Image.new('RGBA', (size, size), (255, 255, 255, 0)) |
| draw = ImageDraw.Draw(img) |
| |
| |
| colors = { |
| "general": (52, 152, 219), |
| "streak": (231, 76, 60), |
| "vocabulary": (46, 204, 113), |
| "quiz": (155, 89, 182), |
| "challenge": (241, 196, 15) |
| } |
| |
| color = colors.get(category, colors["general"]) |
| |
| |
| draw.ellipse([(10, 10), (size-10, size-10)], fill=color, outline=(255, 255, 255), width=3) |
| |
| |
| try: |
| |
| try: |
| font = ImageFont.truetype("Arial.ttf", 80) |
| except: |
| font = ImageFont.load_default() |
| |
| |
| initial = title[0].upper() |
| |
| |
| text_width, text_height = font.getsize(initial) if hasattr(font, 'getsize') else (50, 50) |
| text_position = ((size - text_width) // 2, (size - text_height) // 2) |
| |
| |
| draw.text(text_position, initial, fill=(255, 255, 255), font=font) |
| except Exception as e: |
| print(f"Error adding text to badge: {e}") |
| |
| draw.rectangle([(size//2-20, size//2-20), (size//2+20, size//2+20)], fill=(255, 255, 255)) |
| |
| |
| img.save(badge_path) |
| return badge_path |
| |
| except Exception as e: |
| print(f"Error creating badge image: {e}") |
| return None |
| |
| def check_achievements(self, action_type, **kwargs): |
| """Check for achievements based on user actions.""" |
| if action_type == "word_learned": |
| word = kwargs.get('word', '') |
| category = kwargs.get('category', '') |
| language = kwargs.get('language', '') |
| |
| |
| if len(st.session_state.achievements) == 0: |
| self.award_achievement("first_word", "First Steps", "Learn your first word") |
| self.award_badge("first_word", "First Word", "vocabulary") |
| |
| |
| self.check_category_achievements(category) |
| |
| |
| self.check_language_achievements(language) |
| |
| elif action_type == "quiz_completed": |
| score = kwargs.get('score', 0) |
| total = kwargs.get('total', 0) |
| |
| |
| if "first_quiz" not in st.session_state.achievements: |
| self.award_achievement("first_quiz", "Quiz Taker", "Complete your first quiz") |
| self.award_badge("quiz_taker", "Quiz Taker", "quiz") |
| |
| |
| if score == total and total >= 5: |
| self.award_achievement("perfect_quiz", "Perfect Score", "Get a perfect score on a quiz") |
| self.award_badge("perfect_score", "Perfect Score", "quiz") |
| |
| def check_category_achievements(self, category): |
| """Check for category-specific achievements.""" |
| if not category: |
| return |
| |
| |
| vocab_data = self.get_all_vocabulary() |
| |
| |
| category_count = sum(1 for word in vocab_data if word and 'category' in word and word['category'] == category) |
| |
| |
| if category_count >= 5: |
| self.award_achievement( |
| f"{category}_5", |
| f"{category.title()} Collector", |
| f"Learn 5 {category} words" |
| ) |
| |
| if category_count >= 10: |
| self.award_achievement( |
| f"{category}_10", |
| f"{category.title()} Master", |
| f"Learn 10 {category} words" |
| ) |
| self.award_badge(f"{category}_master", f"{category.title()} Master", "vocabulary") |
| |
| def check_language_achievements(self, language): |
| """Check for language-specific achievements.""" |
| if not language: |
| return |
| |
| |
| vocab_data = self.get_all_vocabulary() |
| |
| |
| language_count = sum(1 for word in vocab_data |
| if word and 'language_translated' in word and word['language_translated'] == language) |
| |
| |
| language_name = self.get_language_name(language) |
| |
| |
| if language_count >= 10: |
| self.award_achievement( |
| f"{language}_10", |
| f"{language_name} Beginner", |
| f"Learn 10 words in {language_name}" |
| ) |
| |
| if language_count >= 25: |
| self.award_achievement( |
| f"{language}_25", |
| f"{language_name} Enthusiast", |
| f"Learn 25 words in {language_name}" |
| ) |
| self.award_badge(f"{language}_enthusiast", f"{language_name} Enthusiast", "vocabulary") |
| |
| if language_count >= 50: |
| self.award_achievement( |
| f"{language}_50", |
| f"{language_name} Speaker", |
| f"Learn 50 words in {language_name}" |
| ) |
| self.award_badge(f"{language}_speaker", f"{language_name} Speaker", "vocabulary") |
| |
| def display_achievements(self): |
| """Display earned achievements in the UI.""" |
| st.subheader("🏆 Achievements") |
| |
| if not st.session_state.achievements: |
| st.info("You haven't earned any achievements yet. Keep learning to unlock them!") |
| return |
| |
| |
| achievement_types = { |
| "Streaks": [], |
| "Vocabulary": [], |
| "Quizzes": [], |
| "Challenges": [], |
| "Other": [] |
| } |
| |
| for achievement_id, achievement in st.session_state.achievements.items(): |
| if "streak" in achievement_id: |
| achievement_types["Streaks"].append(achievement) |
| elif "word" in achievement_id or "_5" in achievement_id or "_10" in achievement_id or "_25" in achievement_id or "_50" in achievement_id: |
| achievement_types["Vocabulary"].append(achievement) |
| elif "quiz" in achievement_id: |
| achievement_types["Quizzes"].append(achievement) |
| elif "challenge" in achievement_id: |
| achievement_types["Challenges"].append(achievement) |
| else: |
| achievement_types["Other"].append(achievement) |
| |
| |
| for category, achievements in achievement_types.items(): |
| if achievements: |
| with st.expander(f"{category} ({len(achievements)})", expanded=(category == "Recent")): |
| for achievement in sorted(achievements, key=lambda x: x.get("date_earned", ""), reverse=True): |
| st.markdown(f"**{achievement['title']}**: {achievement['description']}") |
| st.markdown(f"*Earned on {achievement.get('date_earned', 'Unknown').split()[0]}*") |
| st.markdown("---") |
| |
| def display_badges(self): |
| """Display earned badges in the UI.""" |
| st.subheader("🏅 Badges") |
| |
| if not st.session_state.badges: |
| st.info("You haven't earned any badges yet. Complete achievements to earn badges!") |
| return |
| |
| |
| badges = list(st.session_state.badges.values()) |
| |
| |
| badge_rows = [badges[i:i+3] for i in range(0, len(badges), 3)] |
| |
| for row in badge_rows: |
| cols = st.columns(3) |
| |
| for i, badge in enumerate(row): |
| with cols[i]: |
| try: |
| if os.path.exists(badge["image_path"]): |
| st.image(badge["image_path"], width=100) |
| except: |
| st.markdown("🏅") |
| |
| st.markdown(f"**{badge['title']}**") |
| earned_date = badge.get('date_earned', 'Unknown').split()[0] |
| st.markdown(f"*Earned: {earned_date}*") |
| |
| |
| |
| |
| |
| def generate_daily_challenges(self): |
| """Generate daily challenges based on user's vocabulary and progress.""" |
| today = date.today().strftime("%Y-%m-%d") |
| |
| |
| if st.session_state.daily_challenges and len(st.session_state.daily_challenges) > 0: |
| if st.session_state.daily_challenges[0].get('date') == today: |
| return st.session_state.daily_challenges |
| |
| |
| vocab_data = self.get_all_vocabulary() |
| |
| |
| categories = defaultdict(int) |
| languages = defaultdict(int) |
| |
| for word in vocab_data: |
| if word and 'category' in word and word['category']: |
| categories[word['category']] += 1 |
| |
| if word and 'language_translated' in word: |
| languages[word['language_translated']] += 1 |
| |
| |
| challenges = [] |
| |
| |
| if categories: |
| |
| sorted_categories = sorted(categories.items(), key=lambda x: x[1]) |
| target_category = sorted_categories[0][0] if sorted_categories else "food" |
| target_count = random.randint(2, 4) |
| |
| challenges.append({ |
| "id": f"category_{today}_{target_category}", |
| "date": today, |
| "title": f"Learn {target_count} {target_category} words", |
| "description": f"Add {target_count} new {target_category} words to your vocabulary", |
| "type": "category", |
| "target": target_category, |
| "target_count": target_count, |
| "current_count": 0, |
| "completed": False, |
| "points": 20 |
| }) |
| |
| |
| challenges.append({ |
| "id": f"quiz_{today}", |
| "date": today, |
| "title": "Ace a Quiz", |
| "description": "Complete a quiz with at least 80% accuracy", |
| "type": "quiz", |
| "target_accuracy": 80, |
| "completed": False, |
| "points": 25 |
| }) |
| |
| |
| if languages: |
| user_language = st.session_state.get('target_language', 'es') |
| target_count = random.randint(3, 5) |
| |
| challenges.append({ |
| "id": f"language_{today}_{user_language}", |
| "date": today, |
| "title": f"Learn {target_count} words in {self.get_language_name(user_language)}", |
| "description": f"Add {target_count} new words in {self.get_language_name(user_language)}", |
| "type": "language", |
| "target": user_language, |
| "target_count": target_count, |
| "current_count": 0, |
| "completed": False, |
| "points": 15 |
| }) |
| |
| |
| st.session_state.daily_challenges = challenges |
| st.session_state.daily_challenges_completed = set() |
| |
| |
| self.save_game_state() |
| |
| return challenges |
| |
| def complete_challenge(self, challenge_id): |
| """Mark a challenge as completed and award points.""" |
| for challenge in st.session_state.daily_challenges: |
| if challenge["id"] == challenge_id and not challenge["completed"]: |
| challenge["completed"] = True |
| st.session_state.daily_challenges_completed.add(challenge_id) |
| |
| |
| self.add_points(challenge["points"], f"Challenge: {challenge['title']}") |
| |
| |
| st.toast(f"🎯 Challenge completed: {challenge['title']}") |
| |
| |
| if len(st.session_state.daily_challenges_completed) >= 10: |
| self.award_achievement( |
| "challenge_master", |
| "Challenge Master", |
| "Complete 10 daily challenges", |
| 50 |
| ) |
| self.award_badge("challenge_master", "Challenge Master", "challenge") |
| |
| |
| self.update_vocabulary_tree(0.05) |
| |
| |
| self.save_game_state() |
| return True |
| return False |
| |
| def check_challenge_progress(self, word_original=None, word_translated=None, language=None, quiz_score=None, quiz_total=None): |
| """Check if any challenges are completed by user actions.""" |
| |
| if word_original and language: |
| category = self.get_object_category(word_original) |
| |
| for challenge in st.session_state.daily_challenges: |
| if challenge["completed"]: |
| continue |
| |
| if challenge["type"] == "category" and challenge["target"] == category: |
| challenge["current_count"] += 1 |
| if challenge["current_count"] >= challenge["target_count"]: |
| self.complete_challenge(challenge["id"]) |
| |
| elif challenge["type"] == "language" and challenge["target"] == language: |
| challenge["current_count"] += 1 |
| if challenge["current_count"] >= challenge["target_count"]: |
| self.complete_challenge(challenge["id"]) |
| |
| |
| wotd = st.session_state.word_of_the_day |
| if wotd and not wotd["saved"] and word_original.lower() == wotd["original"].lower(): |
| wotd["saved"] = True |
| self.add_points(wotd["points"], "Word of the Day bonus") |
| st.toast(f"🌟 Word of the Day bonus: +{wotd['points']} points!") |
| |
| |
| if "word_of_the_day" not in st.session_state.achievements: |
| self.award_achievement( |
| "word_of_the_day", |
| "Word Collector", |
| "Learn the Word of the Day" |
| ) |
| |
| |
| if quiz_score is not None and quiz_total is not None and quiz_total > 0: |
| accuracy = (quiz_score / quiz_total) * 100 |
| |
| for challenge in st.session_state.daily_challenges: |
| if challenge["type"] == "quiz" and not challenge["completed"]: |
| if accuracy >= challenge["target_accuracy"]: |
| self.complete_challenge(challenge["id"]) |
| |
| def generate_word_of_the_day(self): |
| """Generate a word of the day with guaranteed translation.""" |
| today = date.today().strftime("%Y-%m-%d") |
| |
| |
| if st.session_state.word_of_the_day and st.session_state.word_of_the_day_date == today: |
| return st.session_state.word_of_the_day |
| |
| |
| potential_words = [ |
| {"word": "hello", "category": "greeting"}, |
| {"word": "thank you", "category": "courtesy"}, |
| {"word": "please", "category": "courtesy"}, |
| {"word": "friend", "category": "people"}, |
| {"word": "food", "category": "essential"}, |
| {"word": "water", "category": "essential"}, |
| {"word": "help", "category": "emergency"}, |
| {"word": "good", "category": "description"}, |
| {"word": "bad", "category": "description"}, |
| {"word": "yes", "category": "response"}, |
| {"word": "no", "category": "response"}, |
| {"word": "goodbye", "category": "greeting"}, |
| {"word": "sorry", "category": "courtesy"}, |
| {"word": "excuse me", "category": "courtesy"}, |
| {"word": "love", "category": "emotion"}, |
| {"word": "book", "category": "object"}, |
| {"word": "car", "category": "vehicle"}, |
| {"word": "house", "category": "place"}, |
| {"word": "family", "category": "people"}, |
| {"word": "work", "category": "activity"} |
| ] |
| |
| |
| vocab_data = self.get_all_vocabulary() |
| existing_words = set(word['word_original'].lower() for word in vocab_data if 'word_original' in word) |
| |
| |
| available_words = [word for word in potential_words if word["word"].lower() not in existing_words] |
| |
| |
| if not available_words: |
| available_words = potential_words |
| |
| |
| word_of_the_day = random.choice(available_words) |
| |
| |
| target_language = st.session_state.get('target_language', 'es') |
| |
| |
| translated_word = self.get_translation_for_word(word_of_the_day["word"], target_language) |
| |
| |
| st.session_state.word_of_the_day = { |
| "original": word_of_the_day["word"], |
| "translated": translated_word, |
| "category": word_of_the_day["category"], |
| "points": 10, |
| "saved": False |
| } |
| |
| st.session_state.word_of_the_day_date = today |
| |
| |
| self.save_game_state() |
| |
| return st.session_state.word_of_the_day |
| |
| def display_daily_challenges(self): |
| """Display daily challenges in the UI.""" |
| challenges = self.generate_daily_challenges() |
| |
| st.subheader("📅 Today's Challenges") |
| |
| |
| if st.session_state.word_of_the_day: |
| st.markdown("### ✨ Word of the Day") |
| wotd = st.session_state.word_of_the_day |
| |
| st.markdown(f"**{wotd['original']}** → {wotd['translated']} ({wotd['category']})") |
| |
| if wotd["saved"]: |
| st.success(f"✅ Added to vocabulary (+{wotd['points']} points)") |
| else: |
| st.info(f"Add this word to your vocabulary for {wotd['points']} bonus points!") |
| |
| |
| if challenges: |
| for challenge in challenges: |
| col1, col2 = st.columns([3, 1]) |
| |
| with col1: |
| if challenge["completed"] or challenge["id"] in st.session_state.daily_challenges_completed: |
| st.markdown(f"✅ **{challenge['title']}**") |
| st.markdown(f"*{challenge['description']}* (Completed)") |
| else: |
| st.markdown(f"⭐ **{challenge['title']}**") |
| st.markdown(f"*{challenge['description']}*") |
| |
| |
| if challenge["type"] in ["category", "language"]: |
| current = challenge.get("current_count", 0) |
| target = challenge.get("target_count", 1) |
| progress = min(1.0, current / target) |
| st.progress(progress) |
| st.markdown(f"Progress: {current}/{target}") |
| |
| with col2: |
| st.markdown(f"+{challenge['points']} pts") |
| |
| st.markdown("---") |
| else: |
| st.info("No challenges available for today.") |
| |
| |
| |
| |
| |
| def check_streak(self): |
| """Update user streak based on last active date.""" |
| today = date.today() |
| |
| |
| if st.session_state.last_active_date is None: |
| st.session_state.streak_days = 1 |
| st.session_state.last_active_date = today |
| self.save_game_state() |
| return True |
| |
| |
| last_date = st.session_state.last_active_date |
| if isinstance(last_date, str): |
| try: |
| last_date = datetime.strptime(last_date, "%Y-%m-%d").date() |
| except: |
| last_date = today |
| |
| |
| days_passed = (today - last_date).days |
| |
| |
| if days_passed == 0: |
| return False |
| |
| |
| if days_passed == 1: |
| st.session_state.streak_days += 1 |
| st.session_state.last_active_date = today |
| |
| |
| if st.session_state.streak_days == 3: |
| self.award_achievement("3_day_streak", "On Fire!", "Maintained a 3-day learning streak") |
| self.award_badge("fire_starter", "Fire Starter", "streak") |
| elif st.session_state.streak_days == 7: |
| self.award_achievement("7_day_streak", "Week Warrior", "Maintained a 7-day learning streak") |
| self.award_badge("week_warrior", "Week Warrior", "streak") |
| |
| st.session_state.streak_savers += 1 |
| st.toast("🛟 You earned a Streak Saver! Use it to maintain your streak if you miss a day.") |
| elif st.session_state.streak_days == 30: |
| self.award_achievement("30_day_streak", "Monthly Master", "Maintained a 30-day learning streak") |
| self.award_badge("monthly", "Monthly Master", "streak") |
| |
| st.session_state.streak_savers += 2 |
| st.toast("🛟 You earned 2 Streak Savers for your amazing dedication!") |
| |
| |
| bonus_points = min(st.session_state.streak_days, 10) |
| self.add_points(bonus_points, f"Streak bonus (Day {st.session_state.streak_days})") |
| |
| |
| self.update_vocabulary_tree(0.02) |
| |
| self.save_game_state() |
| return True |
| |
| |
| elif days_passed == 2 and st.session_state.streak_savers > 0: |
| st.session_state.streak_savers -= 1 |
| st.session_state.last_active_date = today |
| st.toast("🛟 Used a Streak Saver to maintain your streak!") |
| self.save_game_state() |
| return True |
| |
| |
| else: |
| st.session_state.streak_days = 1 |
| st.session_state.last_active_date = today |
| self.save_game_state() |
| return False |
| |
| def add_points(self, amount, reason=""): |
| """Add points to the user's account and check for level ups.""" |
| st.session_state.points += amount |
| |
| |
| old_level = st.session_state.level |
| new_level = 1 + int(st.session_state.points / 100) |
| |
| if new_level > old_level: |
| st.session_state.level = new_level |
| st.toast(f"🎉 Level Up! You're now level {new_level}") |
| |
| |
| if new_level == 5: |
| self.award_achievement("level_5", "Level 5", "Reach level 5 in your learning journey", 50) |
| self.award_badge("level_5", "Level 5", "general") |
| elif new_level == 10: |
| self.award_achievement("level_10", "Level 10", "Reach level 10 in your learning journey", 100) |
| self.award_badge("level_10", "Level 10", "general") |
| |
| |
| if new_level > old_level: |
| st.session_state.streak_savers += 1 |
| st.toast("🛟 You earned a Streak Saver for leveling up!") |
| |
| |
| self.update_vocabulary_tree(0.2) |
| |
| |
| self.save_game_state() |
| |
| def display_streak_info(self): |
| """Display streak information in the UI.""" |
| st.subheader("🔥 Learning Streak") |
| |
| col1, col2 = st.columns(2) |
| |
| with col1: |
| st.markdown(f"**Current Streak**: {st.session_state.streak_days} days") |
| if st.session_state.streak_days >= 7: |
| st.success(f"Amazing! Keep up the great work! 🎉") |
| elif st.session_state.streak_days >= 3: |
| st.success(f"You're on fire! 🔥") |
| |
| with col2: |
| if st.session_state.streak_savers > 0: |
| st.markdown(f"**Streak Savers**: {st.session_state.streak_savers}") |
| st.info("Streak savers help maintain your streak if you miss a day.") |
| |
| |
| if st.session_state.streak_days < 3: |
| target = 3 |
| elif st.session_state.streak_days < 7: |
| target = 7 |
| elif st.session_state.streak_days < 30: |
| target = 30 |
| else: |
| target = 100 |
| |
| |
| progress = st.session_state.streak_days / target |
| st.markdown(f"**Next milestone**: {target} days") |
| st.progress(min(1.0, progress)) |
| |
| |
| days_to_go = target - st.session_state.streak_days |
| if days_to_go > 0: |
| st.markdown(f"*{days_to_go} days to go!*") |
| |
| |
| |
| |
| |
| def update_category_progress(self): |
| """Update progress for different categories based on vocabulary.""" |
| vocab_data = self.get_all_vocabulary() |
| |
| |
| category_counts = defaultdict(int) |
| language_counts = defaultdict(lambda: defaultdict(int)) |
| |
| for word in vocab_data: |
| if word and 'category' in word and word['category']: |
| category_counts[word['category']] += 1 |
| |
| if word and 'language_translated' in word and 'category' in word and word['category']: |
| language_counts[word['language_translated']][word['category']] += 1 |
| |
| |
| category_targets = { |
| "food": 15, |
| "animals": 10, |
| "vehicles": 8, |
| "electronics": 10, |
| "furniture": 8, |
| "personal": 6, |
| "sports": 8, |
| "household": 10, |
| "other": 10, |
| "manual": 5, |
| "text": 10 |
| } |
| |
| |
| total_words = sum(category_counts.values()) |
| overall_target = sum(category_targets.values()) |
| overall_progress = min(1.0, total_words / max(1, overall_target)) |
| |
| |
| st.session_state.category_progress = { |
| "categories": {cat: min(1.0, category_counts[cat] / max(1, category_targets.get(cat, 10))) |
| for cat in set(list(category_counts.keys()) + list(category_targets.keys()))}, |
| "languages": {lang: {cat: count for cat, count in cats.items()} |
| for lang, cats in language_counts.items()}, |
| "overall": overall_progress, |
| "total_words": total_words |
| } |
| |
| |
| self.save_game_state() |
| |
| def update_vocabulary_tree(self, growth_amount): |
| """Update the vocabulary tree based on learning progress.""" |
| tree = st.session_state.vocabulary_tree |
| |
| |
| tree['size'] += growth_amount |
| |
| |
| if tree['size'] >= 1.5 and tree['leaves'] < 5: |
| |
| leaves_to_add = min(5 - tree['leaves'], int(tree['size'] - 1)) |
| if leaves_to_add > 0: |
| tree['leaves'] += leaves_to_add |
| st.toast(f"🌿 Your vocabulary tree is growing new leaves!") |
| |
| if tree['size'] >= 3 and tree['fruit'] < 10: |
| |
| fruit_to_add = min(10 - tree['fruit'], int((tree['size'] - 2) / 0.5)) |
| if fruit_to_add > 0: |
| tree['fruit'] += fruit_to_add |
| st.toast(f"🍎 Your vocabulary tree is growing fruit!") |
| |
| |
| if tree['size'] >= 5 and tree['level'] == 1: |
| tree['level'] = 2 |
| st.toast(f"🌳 Your vocabulary tree has grown to level 2!") |
| self.award_achievement("tree_level_2", "Growing Tree", "Grow your vocabulary tree to level 2", 30) |
| elif tree['size'] >= 10 and tree['level'] == 2: |
| tree['level'] = 3 |
| st.toast(f"🌳 Your vocabulary tree has grown to level 3!") |
| self.award_achievement("tree_level_3", "Thriving Tree", "Grow your vocabulary tree to level 3", 50) |
| self.award_badge("tree_master", "Tree Master", "vocabulary") |
| |
| |
| self.save_game_state() |
| |
| def generate_tree_image(self): |
| """Generate an image of the vocabulary tree.""" |
| try: |
| tree = st.session_state.vocabulary_tree |
| |
| |
| width, height = 400, 400 |
| img = Image.new('RGBA', (width, height), (255, 255, 255, 0)) |
| draw = ImageDraw.Draw(img) |
| |
| |
| trunk_width = 20 + (tree['level'] * 10) |
| trunk_height = 100 + (tree['size'] * 20) |
| canopy_size = 50 + (tree['size'] * 30) |
| |
| |
| trunk_color = (139, 69, 19) |
| trunk_x = width // 2 |
| trunk_bottom = height - 50 |
| trunk_top = trunk_bottom - trunk_height |
| |
| draw.rectangle( |
| [(trunk_x - trunk_width//2, trunk_top), |
| (trunk_x + trunk_width//2, trunk_bottom)], |
| fill=trunk_color |
| ) |
| |
| |
| canopy_color = (34, 139, 34) |
| draw.ellipse( |
| [(trunk_x - canopy_size, trunk_top - canopy_size), |
| (trunk_x + canopy_size, trunk_top + canopy_size//2)], |
| fill=canopy_color |
| ) |
| |
| |
| if tree['leaves'] > 0: |
| leaf_color = (50, 205, 50) |
| for i in range(tree['leaves']): |
| angle = (i / tree['leaves']) * 360 |
| radius = canopy_size * 0.8 |
| x = trunk_x + int(radius * np.cos(np.radians(angle))) |
| y = trunk_top - int(radius * np.sin(np.radians(angle)) * 0.5) |
| |
| draw.ellipse( |
| [(x - 15, y - 10), (x + 15, y + 10)], |
| fill=leaf_color |
| ) |
| |
| |
| if tree['fruit'] > 0: |
| fruit_color = (255, 0, 0) |
| for i in range(tree['fruit']): |
| angle = (i / tree['fruit']) * 360 |
| radius = canopy_size * 0.6 |
| x = trunk_x + int(radius * np.cos(np.radians(angle))) |
| y = trunk_top - int(radius * np.sin(np.radians(angle)) * 0.7) |
| |
| draw.ellipse( |
| [(x - 8, y - 8), (x + 8, y + 8)], |
| fill=fruit_color |
| ) |
| |
| |
| buf = io.BytesIO() |
| img.save(buf, format='PNG') |
| byte_im = buf.getvalue() |
| |
| return byte_im |
| except Exception as e: |
| print(f"Error generating tree image: {e}") |
| return None |
| |
| def display_progress_visuals(self): |
| """Display progress visualizations in the UI.""" |
| st.subheader("📊 Learning Progress") |
| |
| |
| total_words = st.session_state.category_progress.get("total_words", 0) |
| st.markdown(f"### Total Words Learned: {total_words}") |
| |
| |
| target_words = 10 * st.session_state.level |
| progress = min(1.0, total_words / target_words) |
| |
| st.markdown(f"**Level Goal**: {total_words}/{target_words} words") |
| st.progress(progress) |
| |
| |
| st.markdown("### Category Progress") |
| |
| categories = st.session_state.category_progress.get("categories", {}) |
| if categories: |
| sorted_categories = sorted( |
| [(cat, prog) for cat, prog in categories.items()], |
| key=lambda x: x[1], |
| reverse=True |
| ) |
| |
| for category, progress in sorted_categories: |
| col1, col2 = st.columns([3, 1]) |
| with col1: |
| st.markdown(f"**{category.title()}**") |
| st.progress(progress) |
| with col2: |
| percentage = int(progress * 100) |
| st.markdown(f"{percentage}%") |
| else: |
| st.info("Start learning words to see category progress!") |
| |
| |
| st.markdown("### 🌳 Vocabulary Tree") |
| |
| tree_img = self.generate_tree_image() |
| if tree_img: |
| st.image(tree_img, width=300) |
| |
| |
| col1, col2 = st.columns(2) |
| with col1: |
| st.markdown(f"**Tree Level**: {st.session_state.vocabulary_tree['level']}") |
| st.markdown(f"**Size**: {st.session_state.vocabulary_tree['size']:.1f}") |
| with col2: |
| st.markdown(f"**Leaves**: {st.session_state.vocabulary_tree['leaves']}") |
| st.markdown(f"**Fruit**: {st.session_state.vocabulary_tree['fruit']}") |
| |
| st.info("Your tree grows as you learn more words, complete challenges, and maintain your streak!") |
| else: |
| st.info("Keep learning to grow your vocabulary tree!") |
| |
| |
| |
| |
| |
| def render_dashboard(self): |
| """Render the complete gamification dashboard.""" |
| st.title("🎮 My Progress") |
| |
| |
| col1, col2 = st.columns([1, 3]) |
| |
| with col1: |
| |
| if st.session_state.level >= 5 and "level_5" in st.session_state.badges: |
| try: |
| badge_path = st.session_state.badges["level_5"]["image_path"] |
| if os.path.exists(badge_path): |
| st.image(badge_path, width=100) |
| except: |
| st.markdown(f"## Level {st.session_state.level}") |
| else: |
| st.markdown(f"## Level {st.session_state.level}") |
| |
| with col2: |
| |
| next_level = st.session_state.level + 1 |
| points_needed = (next_level - 1) * 100 |
| current_level_points = st.session_state.points - ((st.session_state.level - 1) * 100) |
| progress_percentage = current_level_points / 100 |
| |
| st.markdown(f"### {st.session_state.points} total points") |
| st.markdown(f"{current_level_points}/100 to level {next_level}") |
| st.progress(progress_percentage) |
| |
| |
| tab1, tab2, tab3, tab4 = st.tabs(["Challenges", "Streaks", "Progress", "Achievements"]) |
| |
| with tab1: |
| self.display_daily_challenges() |
| |
| with tab2: |
| self.display_streak_info() |
| |
| with tab3: |
| self.display_progress_visuals() |
| |
| with tab4: |
| col1, col2 = st.columns(2) |
| with col1: |
| self.display_achievements() |
| with col2: |
| self.display_badges() |
| |
| def update_sidebar(self): |
| """Update the sidebar with gamification information.""" |
| try: |
| with st.sidebar.expander("🏆 My Progress", expanded=True): |
| |
| level = st.session_state.get("level", 1) |
| points = st.session_state.get("points", 0) |
| |
| |
| st.markdown(f"**Level {level}** | {points} points") |
| |
| |
| streak_days = st.session_state.get("streak_days", 0) |
| if streak_days > 0: |
| st.markdown(f"🔥 **{streak_days}** day streak") |
| |
| |
| wotd = st.session_state.get("word_of_the_day") |
| if wotd and isinstance(wotd, dict): |
| st.markdown("**✨ Word of the Day**") |
| original = wotd.get('original', '?') |
| |
| |
| current_language = st.session_state.get('target_language', 'es') |
| |
| |
| wotd_language = wotd.get('language', None) |
| |
| |
| translated = wotd.get('translated', '?') |
| if wotd_language != current_language or translated.startswith('['): |
| |
| translations = { |
| "book": { |
| "es": "libro", "fr": "livre", "de": "Buch", "it": "libro", |
| "pt": "livro", "ru": "книга", "ja": "本", "zh-CN": "书" |
| }, |
| "hello": { |
| "es": "hola", "fr": "bonjour", "de": "hallo", "it": "ciao", |
| "pt": "olá", "ru": "привет", "ja": "こんにちは", "zh-CN": "你好" |
| }, |
| "thank you": { |
| "es": "gracias", "fr": "merci", "de": "danke", "it": "grazie", |
| "pt": "obrigado", "ru": "спасибо", "ja": "ありがとう", "zh-CN": "谢谢" |
| }, |
| "please": { |
| "es": "por favor", "fr": "s'il vous plaît", "de": "bitte", "it": "per favore", |
| "pt": "por favor", "ru": "пожалуйста", "ja": "お願いします", "zh-CN": "请" |
| }, |
| "friend": { |
| "es": "amigo", "fr": "ami", "de": "Freund", "it": "amico", |
| "pt": "amigo", "ru": "друг", "ja": "友達", "zh-CN": "朋友" |
| }, |
| "food": { |
| "es": "comida", "fr": "nourriture", "de": "Essen", "it": "cibo", |
| "pt": "comida", "ru": "еда", "ja": "食べ物", "zh-CN": "食物" |
| }, |
| "water": { |
| "es": "agua", "fr": "eau", "de": "Wasser", "it": "acqua", |
| "pt": "água", "ru": "вода", "ja": "水", "zh-CN": "水" |
| }, |
| "help": { |
| "es": "ayuda", "fr": "aide", "de": "Hilfe", "it": "aiuto", |
| "pt": "ajuda", "ru": "помощь", "ja": "助けて", "zh-CN": "帮助" |
| }, |
| "good": { |
| "es": "bueno", "fr": "bon", "de": "gut", "it": "buono", |
| "pt": "bom", "ru": "хороший", "ja": "良い", "zh-CN": "好" |
| }, |
| "bad": { |
| "es": "malo", "fr": "mauvais", "de": "schlecht", "it": "cattivo", |
| "pt": "mau", "ru": "плохой", "ja": "悪い", "zh-CN": "坏" |
| }, |
| "yes": { |
| "es": "sí", "fr": "oui", "de": "ja", "it": "sì", |
| "pt": "sim", "ru": "да", "ja": "はい", "zh-CN": "是" |
| }, |
| "no": { |
| "es": "no", "fr": "non", "de": "nein", "it": "no", |
| "pt": "não", "ru": "нет", "ja": "いいえ", "zh-CN": "不" |
| }, |
| "goodbye": { |
| "es": "adiós", "fr": "au revoir", "de": "auf Wiedersehen", "it": "arrivederci", |
| "pt": "adeus", "ru": "до свидания", "ja": "さようなら", "zh-CN": "再见" |
| }, |
| "sorry": { |
| "es": "lo siento", "fr": "désolé", "de": "Entschuldigung", "it": "scusa", |
| "pt": "desculpe", "ru": "извините", "ja": "すみません", "zh-CN": "对不起" |
| }, |
| "excuse me": { |
| "es": "disculpe", "fr": "excusez-moi", "de": "Entschuldigung", "it": "scusi", |
| "pt": "com licença", "ru": "извините", "ja": "すみません", "zh-CN": "打扰了" |
| }, |
| "love": { |
| "es": "amor", "fr": "amour", "de": "Liebe", "it": "amore", |
| "pt": "amor", "ru": "любовь", "ja": "愛", "zh-CN": "爱" |
| }, |
| "car": { |
| "es": "coche", "fr": "voiture", "de": "Auto", "it": "macchina", |
| "pt": "carro", "ru": "машина", "ja": "車", "zh-CN": "车" |
| }, |
| "house": { |
| "es": "casa", "fr": "maison", "de": "Haus", "it": "casa", |
| "pt": "casa", "ru": "дом", "ja": "家", "zh-CN": "房子" |
| }, |
| "family": { |
| "es": "familia", "fr": "famille", "de": "Familie", "it": "famiglia", |
| "pt": "família", "ru": "семья", "ja": "家族", "zh-CN": "家庭" |
| }, |
| "work": { |
| "es": "trabajo", "fr": "travail", "de": "Arbeit", "it": "lavoro", |
| "pt": "trabalho", "ru": "работа", "ja": "仕事", "zh-CN": "工作" |
| } |
| } |
| |
| |
| if original.lower() in translations and current_language in translations[original.lower()]: |
| translated = translations[original.lower()][current_language] |
| else: |
| |
| language_name = self.get_language_name(current_language) |
| translated = f"{original} ({language_name})" |
| |
| |
| wotd['translated'] = translated |
| wotd['language'] = current_language |
| st.session_state.word_of_the_day = wotd |
| |
| |
| st.markdown(f"{original} → {translated}") |
| |
| |
| daily_challenges = st.session_state.get("daily_challenges", []) |
| if daily_challenges and isinstance(daily_challenges, list) and len(daily_challenges) > 0: |
| completed = sum(1 for c in daily_challenges if c.get("completed", False)) |
| total = len(daily_challenges) |
| st.markdown(f"**📅 Challenges**: {completed}/{total} completed") |
| except Exception as e: |
| |
| st.sidebar.markdown("🏆 **Progress system is initializing...**") |
| print(f"Error in update_sidebar: {e}") |
| |
| |
| |
| |
| |
| def get_all_vocabulary_direct(self): |
| """Get all vocabulary items directly from SQLite.""" |
| try: |
| |
| conn = sqlite3.connect(self.db_path) |
| |
| |
| conn.row_factory = sqlite3.Row |
| cursor = conn.cursor() |
| |
| |
| cursor.execute(''' |
| SELECT v.id, v.word_original, v.word_translated, v.language_translated, |
| v.category, v.image_path, v.date_added, |
| up.proficiency_level, up.review_count, up.correct_count, up.last_reviewed |
| FROM vocabulary v |
| LEFT JOIN user_progress up ON v.id = up.vocabulary_id |
| ORDER BY v.date_added DESC |
| ''') |
| |
| |
| results = cursor.fetchall() |
| |
| |
| vocabulary = [] |
| for row in results: |
| |
| word = dict(row) |
| vocabulary.append(word) |
| |
| conn.close() |
| return vocabulary |
| except Exception as e: |
| print(f"Error retrieving vocabulary: {str(e)}") |
| return [] |
| |
| def get_all_vocabulary(self): |
| """Get all vocabulary, handling different potential sources.""" |
| |
| vocabulary = self.get_all_vocabulary_direct() |
| |
| |
| if not vocabulary and 'vocabulary' in st.session_state: |
| vocabulary = st.session_state.vocabulary |
| |
| return vocabulary |
| |
| def get_object_category(self, label): |
| """Get the category for a detected object label.""" |
| label = label.lower() |
| for category, items in OBJECT_CATEGORIES.items(): |
| if label in items: |
| return category |
| return "other" |
| |
| def get_language_name(self, language_code): |
| """Get language name from code.""" |
| language_map = { |
| "es": "Spanish", |
| "fr": "French", |
| "de": "German", |
| "it": "Italian", |
| "pt": "Portuguese", |
| "ru": "Russian", |
| "ja": "Japanese", |
| "zh-CN": "Chinese" |
| } |
| return language_map.get(language_code, language_code) |
| |
| def set_translate_func(self, translate_func): |
| """Set or update the translation function after initialization.""" |
| self.translate_func = translate_func |
| |
| def translate_placeholder(self, text, target_language): |
| """Get translation using our robust translation system.""" |
| return self.get_translation_for_word(text, target_language) |
| |
| |
| |
| |
| |
| def save_game_state(self): |
| """Save game state to the database.""" |
| try: |
| |
| game_state = { |
| "achievements": st.session_state.achievements, |
| "badges": st.session_state.badges, |
| "streak_days": st.session_state.streak_days, |
| "last_active_date": str(st.session_state.last_active_date) if st.session_state.last_active_date else None, |
| "streak_savers": st.session_state.streak_savers, |
| "points": st.session_state.points, |
| "level": st.session_state.level, |
| "daily_challenges": st.session_state.daily_challenges, |
| "daily_challenges_completed": list(st.session_state.daily_challenges_completed), |
| "word_of_the_day": st.session_state.word_of_the_day, |
| "word_of_the_day_date": st.session_state.word_of_the_day_date, |
| "category_progress": st.session_state.category_progress, |
| "vocabulary_tree": st.session_state.vocabulary_tree |
| } |
| |
| |
| game_state_json = json.dumps(game_state) |
| |
| |
| conn = sqlite3.connect(self.db_path) |
| cursor = conn.cursor() |
| |
| |
| cursor.execute(''' |
| CREATE TABLE IF NOT EXISTS game_state ( |
| id INTEGER PRIMARY KEY, |
| state_json TEXT, |
| updated_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP |
| ) |
| ''') |
| |
| |
| cursor.execute("SELECT id FROM game_state WHERE id = 1") |
| if cursor.fetchone(): |
| cursor.execute("UPDATE game_state SET state_json = ?, updated_at = ? WHERE id = 1", |
| (game_state_json, datetime.now())) |
| else: |
| cursor.execute("INSERT INTO game_state (id, state_json, updated_at) VALUES (1, ?, ?)", |
| (game_state_json, datetime.now())) |
| |
| conn.commit() |
| conn.close() |
| |
| except Exception as e: |
| |
| print(f"Error saving game state: {e}") |
| |
| def load_game_state(self): |
| """Load game state from the database.""" |
| try: |
| |
| conn = sqlite3.connect(self.db_path) |
| cursor = conn.cursor() |
| |
| |
| cursor.execute("SELECT name FROM sqlite_master WHERE type='table' AND name='game_state'") |
| if not cursor.fetchone(): |
| conn.close() |
| return False |
| |
| |
| cursor.execute("SELECT state_json FROM game_state WHERE id = 1") |
| result = cursor.fetchone() |
| conn.close() |
| |
| if not result: |
| return False |
| |
| |
| game_state = json.loads(result[0]) |
| |
| |
| st.session_state.achievements = game_state.get("achievements", {}) |
| st.session_state.badges = game_state.get("badges", {}) |
| st.session_state.streak_days = game_state.get("streak_days", 0) |
| st.session_state.last_active_date = game_state.get("last_active_date") |
| st.session_state.streak_savers = game_state.get("streak_savers", 0) |
| st.session_state.points = game_state.get("points", 0) |
| st.session_state.level = game_state.get("level", 1) |
| st.session_state.daily_challenges = game_state.get("daily_challenges", []) |
| st.session_state.daily_challenges_completed = set(game_state.get("daily_challenges_completed", [])) |
| st.session_state.word_of_the_day = game_state.get("word_of_the_day") |
| st.session_state.word_of_the_day_date = game_state.get("word_of_the_day_date") |
| st.session_state.category_progress = game_state.get("category_progress", {}) |
| st.session_state.vocabulary_tree = game_state.get("vocabulary_tree", { |
| 'size': 1, 'leaves': 0, 'fruit': 0, 'level': 1 |
| }) |
| |
| return True |
| except Exception as e: |
| print(f"Error loading game state: {e}") |
| return False |
| |
| def get_translation_for_word(self, word, target_language): |
| """ |
| Get translation for word with multiple fallback options. |
| Always returns a real translation, never a placeholder. |
| """ |
| |
| common_translations = { |
| "es": { |
| "hello": "hola", "thank you": "gracias", "please": "por favor", "book": "libro", |
| "goodbye": "adiós", "friend": "amigo", "food": "comida", "water": "agua", |
| "help": "ayuda", "good": "bueno", "bad": "malo", "yes": "sí", "no": "no", |
| "sorry": "lo siento", "excuse me": "disculpe", "love": "amor", "car": "coche", |
| "house": "casa", "family": "familia", "work": "trabajo" |
| }, |
| "fr": { |
| "hello": "bonjour", "thank you": "merci", "please": "s'il vous plaît", "book": "livre", |
| "goodbye": "au revoir", "friend": "ami", "food": "nourriture", "water": "eau", |
| "help": "aide", "good": "bon", "bad": "mauvais", "yes": "oui", "no": "non", |
| "sorry": "désolé", "excuse me": "excusez-moi", "love": "amour", "car": "voiture", |
| "house": "maison", "family": "famille", "work": "travail" |
| }, |
| "de": { |
| "hello": "hallo", "thank you": "danke", "please": "bitte", "book": "Buch", |
| "goodbye": "auf Wiedersehen", "friend": "Freund", "food": "Essen", "water": "Wasser", |
| "help": "Hilfe", "good": "gut", "bad": "schlecht", "yes": "ja", "no": "nein", |
| "sorry": "Entschuldigung", "excuse me": "Entschuldigung", "love": "Liebe", |
| "car": "Auto", "house": "Haus", "family": "Familie", "work": "Arbeit" |
| }, |
| "it": { |
| "hello": "ciao", "thank you": "grazie", "please": "per favore", "book": "libro", |
| "goodbye": "arrivederci", "friend": "amico", "food": "cibo", "water": "acqua", |
| "help": "aiuto", "good": "buono", "bad": "cattivo", "yes": "sì", "no": "no", |
| "sorry": "scusa", "excuse me": "scusi", "love": "amore", "car": "macchina", |
| "house": "casa", "family": "famiglia", "work": "lavoro" |
| }, |
| "pt": { |
| "hello": "olá", "thank you": "obrigado", "please": "por favor", "book": "livro", |
| "goodbye": "adeus", "friend": "amigo", "food": "comida", "water": "água", |
| "help": "ajuda", "good": "bom", "bad": "mau", "yes": "sim", "no": "não", |
| "sorry": "desculpe", "excuse me": "com licença", "love": "amor", |
| "car": "carro", "house": "casa", "family": "família", "work": "trabalho" |
| }, |
| "ru": { |
| "hello": "привет", "thank you": "спасибо", "please": "пожалуйста", "book": "книга", |
| "goodbye": "до свидания", "friend": "друг", "food": "еда", "water": "вода", |
| "help": "помощь", "good": "хороший", "bad": "плохой", "yes": "да", "no": "нет", |
| "sorry": "извините", "excuse me": "извините", "love": "любовь", |
| "car": "машина", "house": "дом", "family": "семья", "work": "работа" |
| }, |
| "ja": { |
| "hello": "こんにちは", "thank you": "ありがとう", "please": "お願いします", "book": "本", |
| "goodbye": "さようなら", "friend": "友達", "food": "食べ物", "water": "水", |
| "help": "助けて", "good": "良い", "bad": "悪い", "yes": "はい", "no": "いいえ", |
| "sorry": "すみません", "excuse me": "すみません", "love": "愛", |
| "car": "車", "house": "家", "family": "家族", "work": "仕事" |
| }, |
| "zh-CN": { |
| "hello": "你好", "thank you": "谢谢", "please": "请", "book": "书", |
| "goodbye": "再见", "friend": "朋友", "food": "食物", "water": "水", |
| "help": "帮助", "good": "好", "bad": "坏", "yes": "是", "no": "不", |
| "sorry": "对不起", "excuse me": "打扰了", "love": "爱", |
| "car": "车", "house": "房子", "family": "家庭", "work": "工作" |
| } |
| } |
| |
| |
| word_lower = word.lower() |
| if target_language in common_translations and word_lower in common_translations[target_language]: |
| return common_translations[target_language][word_lower] |
| |
| |
| if hasattr(self, 'translate_func') and self.translate_func: |
| try: |
| result = self.translate_func(word, target_language) |
| |
| if not (result.startswith('[') and result.endswith(']')): |
| return result |
| except Exception as e: |
| print(f"Translation error: {e}") |
| |
| |
| |
| language_name = self.get_language_name(target_language) |
| suffixes = { |
| "es": "en español", "fr": "en français", "de": "auf Deutsch", |
| "it": "in italiano", "pt": "em português", "ru": "по-русски", |
| "ja": "日本語で", "zh-CN": "用中文" |
| } |
| |
| suffix = suffixes.get(target_language, f"in {language_name}") |
| return f"{word} ({suffix})" |
|
|
| |
| OBJECT_CATEGORIES = { |
| "food": ["apple", "banana", "orange", "bottle", "wine glass", "cup", "fork", "knife", "spoon", "bowl", |
| "sandwich", "orange", "broccoli", "carrot", "hot dog", "pizza", "donut", "cake"], |
| |
| "animals": ["bird", "cat", "dog", "horse", "sheep", "cow", "elephant", "bear", "zebra", "giraffe"], |
| |
| "vehicles": ["bicycle", "car", "motorcycle", "airplane", "bus", "train", "truck", "boat"], |
| |
| "electronics": ["tv", "laptop", "mouse", "remote", "keyboard", "cell phone", "microwave", "oven", |
| "toaster", "sink", "refrigerator"], |
| |
| "furniture": ["chair", "couch", "potted plant", "bed", "dining table", "toilet"], |
| |
| "personal": ["backpack", "umbrella", "handbag", "tie", "suitcase"], |
| |
| "sports": ["frisbee", "skis", "snowboard", "sports ball", "kite", "baseball bat", |
| "baseball glove", "skateboard", "surfboard", "tennis racket"], |
| |
| "household": ["bottle", "wine glass", "cup", "fork", "knife", "spoon", "bowl", "book", "clock", |
| "vase", "scissors", "teddy bear", "hair drier", "toothbrush"] |
| } |