r3gm commited on
Commit
9c74d51
·
verified ·
1 Parent(s): 352f115

Create app.py

Browse files
Files changed (1) hide show
  1. app.py +859 -0
app.py ADDED
@@ -0,0 +1,859 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ import os
2
+ import spaces
3
+ import io
4
+ import base64
5
+ import json
6
+ import re
7
+ import random # Added for introducing randomness to the vocabulary generator
8
+ import numpy as np
9
+ import soundfile as sf
10
+ import torch
11
+ from PIL import Image
12
+
13
+ # 1. Apply transformers monkey patch to fix AutoAWQ compatibility
14
+ import transformers.activations
15
+ if not hasattr(transformers.activations, "PytorchGELUTanh"):
16
+ if hasattr(transformers.activations, "GELUTanh"):
17
+ transformers.activations.PytorchGELUTanh = transformers.activations.GELUTanh
18
+ else:
19
+ import torch.nn as nn
20
+ class PytorchGELUTanh(nn.Module):
21
+ def forward(self, x):
22
+ return nn.functional.gelu(x, approximate="tanh")
23
+ transformers.activations.PytorchGELUTanh = PytorchGELUTanh
24
+
25
+ from transformers import AutoModelForCausalLM, AutoTokenizer
26
+ from diffusers import SanaSprintPipeline
27
+ from qwen_tts import Qwen3TTSModel
28
+ from gradio import Server
29
+ from fastapi.responses import HTMLResponse
30
+ from fastapi.staticfiles import StaticFiles
31
+
32
+ # Initialize Server
33
+ app = Server()
34
+
35
+ # Create 'games' directory if it doesn't exist
36
+ os.makedirs("games", exist_ok=True)
37
+
38
+ # Mount the static 'games' directory so index.html can load JS files securely
39
+ app.mount("/games", StaticFiles(directory="games"), name="games")
40
+
41
+ # Model Initialization with try-except block
42
+ try:
43
+ # GGUF Configuration (using Qwen2.5-7B-Instruct GGUF as a stable reference)
44
+ # Ensure 'pip install gguf' is run in your environment.
45
+ llm_repo = "bartowski/Qwen2.5-7B-Instruct-GGUF"
46
+ gguf_filename = "Qwen2.5-7B-Instruct-Q4_0.gguf"
47
+
48
+ print(f"Loading GGUF LLM model {llm_repo} ({gguf_filename}) on GPU via transformers...")
49
+ tokenizer = AutoTokenizer.from_pretrained(llm_repo, gguf_file=gguf_filename)
50
+ llm_model = AutoModelForCausalLM.from_pretrained(
51
+ llm_repo,
52
+ gguf_file=gguf_filename,
53
+ torch_dtype=torch.bfloat16,
54
+ device_map="auto"
55
+ )
56
+
57
+ print("Loading Image model Sana Sprint 1.6B on GPU...")
58
+ sana_pipe = SanaSprintPipeline.from_pretrained(
59
+ "Efficient-Large-Model/Sana_Sprint_1.6B_1024px_diffusers",
60
+ torch_dtype=torch.bfloat16
61
+ )
62
+ sana_pipe.to("cuda:0")
63
+
64
+ print("Loading TTS model Qwen3-TTS 1.7B on GPU...")
65
+ # Apply flash-attention-3 optimization similar to Qwen3-TTS Hugging Face Space app.py
66
+ # Fall back safely to standard 'sdpa' or 'flash_attention_2' if kernels/drivers lack support
67
+ try:
68
+ tts_model = Qwen3TTSModel.from_pretrained(
69
+ "Qwen/Qwen3-TTS-12Hz-1.7B-CustomVoice",
70
+ device_map="cuda:0",
71
+ dtype=torch.bfloat16,
72
+ attn_implementation="kernels-community/flash-attn3",
73
+ )
74
+ print("Qwen3-TTS 1.7B loaded successfully with Flash Attention 3.")
75
+ except Exception as e_fa3:
76
+ print(f"Could not initialize Flash Attention 3 ({e_fa3}). Attempting SDPA fallback...")
77
+ try:
78
+ tts_model = Qwen3TTSModel.from_pretrained(
79
+ "Qwen/Qwen3-TTS-12Hz-1.7B-CustomVoice",
80
+ device_map="cuda:0",
81
+ dtype=torch.bfloat16,
82
+ attn_implementation="sdpa",
83
+ )
84
+ print("Qwen3-TTS 1.7B loaded successfully with SDPA fallback.")
85
+ except Exception as e_all:
86
+ # Absolute fallback in case of initialization exceptions
87
+ tts_model = Qwen3TTSModel.from_pretrained(
88
+ "Qwen/Qwen3-TTS-12Hz-1.7B-CustomVoice",
89
+ device_map="cuda:0",
90
+ dtype=torch.bfloat16,
91
+ )
92
+ print("Qwen3-TTS 1.7B loaded successfully with default attention.")
93
+
94
+ except Exception as e:
95
+ print(f"Error loading models on GPU during startup: {e}")
96
+
97
+ # Localization Database containing GUI texts, layout labels, and game translations
98
+ LOCALIZATION_DATABASE = {
99
+ "English": {
100
+ "title": "✨ WordConjure: Play & Learn!",
101
+ "subtitle": "Embark on an audio-visual word adventure with games and custom illustrations! 🌟",
102
+ "settings": "🛠️ Wizard's Settings Panel",
103
+ "word_count_label": "🔮 How many words to summon?",
104
+ "expl_lang_label": "🗣️ Explanation Language",
105
+ "app_lang_label": "📱 App Language",
106
+ "source_lang": "🌍 My Native Tongue",
107
+ "target_lang": "🎯 Language to Practice",
108
+ "btn_get_words": "🪄 Summon Words!",
109
+ "tab_vocab": "📖 My Word Book",
110
+ "tab_games": "🎮 Play Arena",
111
+ "original_header": "Original Word",
112
+ "translation_header": "Magic Translation",
113
+ "explanation_header": "What does it mean?",
114
+ "visual_header": "Art Card",
115
+ "audio_header": "Hear It",
116
+ "game_selection_title": "Select Your Word Challenge",
117
+ "game_memory_title": "Flashcard Memory Match",
118
+ "game_memory_desc": "Flip face-down cards to match original terms with their translations and visual cards.",
119
+ "game_monster_title": "Feed the Hungry Word Monster",
120
+ "game_monster_desc": "The monster is starving! Feed it the correct translation card matching its request.",
121
+ "game_quiz_title": "Audio-Visual Quiz",
122
+ "game_quiz_desc": "Listen to the pronunciation or inspect the visual card, and choose the correct original term.",
123
+ "game_scramble_title": "Scrambled Word Builder",
124
+ "game_scramble_desc": "Arrange the scrambled letters in the correct order to spell the translation of the given word.",
125
+ "game_linker_title": "Visual Word Linker",
126
+ "game_linker_desc": "Match visual cards on the left with their corresponding practice words on the right, side by side.",
127
+ "game_monster_feed_me": "FEED ME THE CARD FOR:",
128
+ "game_monster_correct": "Yum! Correct! 🎉",
129
+ "game_monster_incorrect": "Ouch! Wrong card! 😢",
130
+ "game_monster_no_words": "No words summoned yet!",
131
+ "game_monster_no_words_desc": "Please return to 'My Word Book', select a language to practice, and click 'Summon Words' first.",
132
+ "game_quiz_hint_btn": "Hint 💡",
133
+ "game_quiz_replay_audio": "Play Audio 🔊",
134
+ "game_quiz_correct": "Correct! 🎉",
135
+ "game_quiz_incorrect": "Incorrect! Try again! 😢",
136
+ "game_quiz_question": "Identify this word:",
137
+ "game_quiz_need_four": "Please summon at least 4 words to play the Audio-Visual Quiz.",
138
+ "game_scramble_clue_label": "Translate this word:",
139
+ "game_scramble_explanation_label": "Clue:",
140
+ "game_scramble_reset_btn": "Reset 🔄",
141
+ "game_scramble_play_audio": "Pronounce 🔊",
142
+ "game_scramble_correct": "Perfect Spelling! 🎉",
143
+ "game_scramble_incorrect": "Oops! Try again.",
144
+ "game_memory_moves": "Moves:",
145
+ "game_memory_congrats": "Board Cleared! Perfect Match! 🎉",
146
+ "game_memory_need_words": "Please summon at least 4 words to play Flashcard Memory Match.",
147
+ "game_linker_instruction": "Click a visual card, then click its matching practice word!",
148
+ "game_linker_hint_btn": "Hint 💡",
149
+ "game_linker_correct": "Perfect Association! 🎉",
150
+ "game_linker_incorrect": "Mismatch! Try again. 😢",
151
+ "game_linker_select_clue": "Select a card on the left first to get a hint!",
152
+ "game_linker_need_words": "Please summon at least 4 words to play Visual Word Linker."
153
+ },
154
+ "Spanish": {
155
+ "title": "✨ ¡WordConjure: Juega y Aprende!",
156
+ "subtitle": "¡Embarca en una aventura de palabras audiovisual con minijuegos e ilustraciones mágicas! 🌟",
157
+ "settings": "🛠️ Panel del Mago (Ajustes)",
158
+ "word_count_label": "🔮 ¿Cuántas palabras quieres convocar?",
159
+ "expl_lang_label": "🗣️ Idioma de la explicación",
160
+ "app_lang_label": "📱 Idioma de la aplicación",
161
+ "source_lang": "🌍 Mi lengua materna",
162
+ "target_lang": "🎯 Idioma a practicar",
163
+ "btn_get_words": "🪄 ¡Invocar Palabras!",
164
+ "tab_vocab": "📖 Mi Libro de Palabras",
165
+ "tab_games": "🎮 Arena de Juegos",
166
+ "original_header": "Palabra Original",
167
+ "translation_header": "Traducción Mágica",
168
+ "explanation_header": "¿Qué significa?",
169
+ "visual_header": "Tarjeta de Arte",
170
+ "audio_header": "Escúchalo",
171
+ "game_selection_title": "Elige tu desafío de palabras",
172
+ "game_memory_title": "Memoria de Tarjetas",
173
+ "game_memory_desc": "Voltea cartas boca abajo para emparejar términos originales con sus traducciones e imágenes.",
174
+ "game_monster_title": "Alimenta al Monstruo de Palabras",
175
+ "game_monster_desc": "¡El monstruo se muere de hambre! Aliméntalo con la traducción correcta correspondiente a su petición.",
176
+ "game_quiz_title": "Quiz Audio-Visual",
177
+ "game_quiz_desc": "Escucha la pronunciación o inspecciona la tarjeta visual, luego asóciala con su traducción original.",
178
+ "game_scramble_title": "Palabras Mezcladas",
179
+ "game_scramble_desc": "Ordena las letras mezcladas para deletrear la traducción de la palabra dada.",
180
+ "game_linker_title": "Asociador de Palabras",
181
+ "game_linker_desc": "Une las tarjetas visuales de la izquierda con sus palabras correspondientes de la derecha, lado a lado.",
182
+ "game_monster_feed_me": "¡ALIMÉNTAME CON LA TARJETA DE:",
183
+ "game_monster_correct": "¡Rico! ¡Correcto! 🎉",
184
+ "game_monster_incorrect": "¡Ay! ¡Tarjeta incorrecta! 😢",
185
+ "game_monster_no_words": "¡Aún no has convocado palabras!",
186
+ "game_monster_no_words_desc": "Por favor, regresa a la pestaña 'Mi Libro de Palabras', selecciona un idioma para practicar y haz clic en 'Invocar Palabras' primero.",
187
+ "game_quiz_hint_btn": "Pista 💡",
188
+ "game_quiz_replay_audio": "Reproducir Audio 🔊",
189
+ "game_quiz_correct": "¡Correcto! 🎉",
190
+ "game_quiz_incorrect": "¡Incorrecto! Inténtalo de nuevo! 😢",
191
+ "game_quiz_question": "Identifica esta palabra:",
192
+ "game_quiz_need_four": "Por favor, convoca al menos 4 palabras para jugar el Quiz Audio-Visual.",
193
+ "game_scramble_clue_label": "Traduce esta palabra:",
194
+ "game_scramble_explanation_label": "Pista:",
195
+ "game_scramble_reset_btn": "Restablecer 🔄",
196
+ "game_scramble_play_audio": "Pronunciar 🔊",
197
+ "game_scramble_correct": "¡Deletreo perfecto! 🎉",
198
+ "game_scramble_incorrect": "¡Ups! Inténtalo de nuevo.",
199
+ "game_memory_moves": "Movimientos:",
200
+ "game_memory_congrats": "¡Tablero despejado! ¡Combinación perfecta! 🎉",
201
+ "game_memory_need_words": "Por favor, convoca al menos 4 palabras para jugar a Memoria de Tarjetas.",
202
+ "game_linker_instruction": "¡Haz clic en una tarjeta visual y luego en su palabra correspondiente!",
203
+ "game_linker_hint_btn": "Pista 💡",
204
+ "game_linker_correct": "¡Asociación perfecta! 🎉",
205
+ "game_linker_incorrect": "¡No coinciden! Inténtalo de nuevo. 😢",
206
+ "game_linker_select_clue": "¡Selecciona una tarjeta a la izquierda primero para obtener una pista!",
207
+ "game_linker_need_words": "Por favor, convoca al menos 4 palabras para jugar al Asociador de Palabras."
208
+ },
209
+ "French": {
210
+ "title": "✨ WordConjure : Jouez & Apprenez !",
211
+ "subtitle": "Embarquez pour une aventure de mots audiovisuelle avec des jeux et des illustrations magiques ! 🌟",
212
+ "settings": "🛠️ Paramètres du Magicien",
213
+ "word_count_label": "🔮 Combien de mots invoquer ?",
214
+ "expl_lang_label": "🗣️ Langue d'explication",
215
+ "app_lang_label": "📱 Langue de l'application",
216
+ "source_lang": "🌍 Ma langue maternelle",
217
+ "target_lang": "🎯 Langue à pratiquer",
218
+ "btn_get_words": "🪄 Invoquer des Mots !",
219
+ "tab_vocab": "📖 Mon Grimoire de Mots",
220
+ "tab_games": "🎮 Arène de Jeux",
221
+ "original_header": "Mot Original",
222
+ "translation_header": "Traduction Magique",
223
+ "explanation_header": "Qu'est-ce que ça veut dire ?",
224
+ "visual_header": "Carte d'Art",
225
+ "audio_header": "Écouter",
226
+ "game_selection_title": "Choisissez votre défi de mots",
227
+ "game_memory_title": "Mémoire de Cartes",
228
+ "game_memory_desc": "Retournez les cartes face cachée pour faire correspondre les termes originaux avec leurs traductions et visuels.",
229
+ "game_monster_title": "Nourrir le monstre de mots affamé",
230
+ "game_monster_desc": "Le monstre est affamé ! Nourrissez-le avec la bonne carte de traduction correspondant à sa demande.",
231
+ "game_quiz_title": "Quiz Audio-Visual",
232
+ "game_quiz_desc": "Écoutez la prononciation ou inspectez la carte visuelle, puis associez-la à sa traduction originale.",
233
+ "game_scramble_title": "Mots Mélangés",
234
+ "game_scramble_desc": "Arrangez les lettres mélangées dans le bon ordre pour épeler la traduction du mot donné.",
235
+ "game_linker_title": "Association Visuelle",
236
+ "game_linker_desc": "Associez les cartes visuelles à gauche avec leurs mots d'entraînement correspondants à droite, côte à côte.",
237
+ "game_monster_feed_me": "NOURRIS-MOI AVEC LA CARTE POUR :",
238
+ "game_monster_correct": "Miam ! Correct ! 🎉",
239
+ "game_monster_incorrect": "Aïe ! Mauvaise carte ! 😢",
240
+ "game_monster_no_words": "Aucun mot invoqué !",
241
+ "game_monster_no_words_desc": "Veuillez d'abord retourner à l'onglet 'Mon Grimoire de Mots', choisir une langue à pratiquer et cliquer sur 'Invoquer des Mots'.",
242
+ "game_quiz_hint_btn": "Indice 💡",
243
+ "game_quiz_replay_audio": "Jouer l'audio 🔊",
244
+ "game_quiz_correct": "Correct ! 🎉",
245
+ "game_quiz_incorrect": "Incorrect ! Réessayez ! 😢",
246
+ "game_quiz_question": "Identifiez ce mot :",
247
+ "game_quiz_need_four": "Veuillez invoquer au moins 4 mots pour jouer au Quiz Audio-Visual.",
248
+ "game_scramble_clue_label": "Traduisez ce mot :",
249
+ "game_scramble_explanation_label": "Indice :",
250
+ "game_scramble_reset_btn": "Réinitialiser 🔄",
251
+ "game_scramble_play_audio": "Prononcer 🔊",
252
+ "game_scramble_correct": "Orthographe parfaite ! 🎉",
253
+ "game_scramble_incorrect": "Oups ! Réessayez.",
254
+ "game_memory_moves": "Mouvements :",
255
+ "game_memory_congrats": "Tableau vidé ! Combinaison parfaite ! 🎉",
256
+ "game_memory_need_words": "Veuillez invoquer au moins 4 mots pour jouer à Mémoire de Cartes.",
257
+ "game_linker_instruction": "Cliquez sur une carte visuelle, puis sur son mot correspondant !",
258
+ "game_linker_hint_btn": "Indice 💡",
259
+ "game_linker_correct": "Association parfaite ! 🎉",
260
+ "game_linker_incorrect": "Désaccord ! Réessayez. 😢",
261
+ "game_linker_select_clue": "Sélectionnez d'abord une carte à gauche pour obtenir un indice !",
262
+ "game_linker_need_words": "Veuillez invoquer au moins 4 mots pour jouer à l'Association Visuelle."
263
+ },
264
+ "German": {
265
+ "title": "✨ WordConjure: Spiele & Lerne!",
266
+ "subtitle": "Begib dich auf ein audiovisuelles Wortabenteuer mit Spielen und magischen Illustrationen! 🌟",
267
+ "settings": "🛠️ Einstellungen des Zauberers",
268
+ "word_count_label": "🔮 Wie viele Wörter beschwören?",
269
+ "expl_lang_label": "🗣️ Erklärungssprache",
270
+ "app_lang_label": "📱 App-Sprache",
271
+ "source_lang": "🌍 Meine Muttersprache",
272
+ "target_lang": "🎯 Zielsprache zum Üben",
273
+ "btn_get_words": "🪄 Wörter beschwören!",
274
+ "tab_vocab": "📖 Mein Wörterbuch",
275
+ "tab_games": "🎮 Spielarena",
276
+ "original_header": "Ursprüngliches Wort",
277
+ "translation_header": "Magische Übersetzung",
278
+ "explanation_header": "Bedeutung",
279
+ "visual_header": "Kunstkarte",
280
+ "audio_header": "Anhören",
281
+ "game_selection_title": "Wähle deine Wortherausforderung",
282
+ "game_memory_title": "Karteikarten-Memory",
283
+ "game_memory_desc": "Decke verdeckte Karten auf, um Originalbegriffe mit ihren Übersetzungen und Bildern abzugleichen.",
284
+ "game_monster_title": "Füttere das Wortmonster",
285
+ "game_monster_desc": "Das Monster verhungert! Füttere es mit der richtigen Übersetzungskarte passend zu seiner Bitte.",
286
+ "game_quiz_title": "Audio-Visuelles Quiz",
287
+ "game_quiz_desc": "Höre dir die Aussprache an oder betrachte die Bildkarte und wähle den richtigen Originalbegriff.",
288
+ "game_scramble_title": "Buchstabensalat",
289
+ "game_scramble_desc": "Bringe die durcheinandergewürfelten Buchstaben in die richtige Reihenfolge, um die Übersetzung zu schreiben.",
290
+ "game_linker_title": "Visueller Wort-Verlinker",
291
+ "game_linker_desc": "Verbinde die Bildkarten auf der linken Seite mit den entsprechenden Übungswörtern auf der rechten Seite.",
292
+ "game_monster_feed_me": "FÜTTERE MICH MIT DER KARTE FÜR:",
293
+ "game_monster_correct": "Nom! Richtig! 🎉",
294
+ "game_monster_incorrect": "Aua! Falsche Karte! 😢",
295
+ "game_monster_no_words": "Noch keine Wörter beschworen!",
296
+ "game_monster_no_words_desc": "Bitte kehre zuerst zum Reiter 'Mein Wörterbuch' zurück, wähle eine Zielsprache aus und klicke auf 'Wörter beschwören'.",
297
+ "game_quiz_hint_btn": "Hinweis 💡",
298
+ "game_quiz_replay_audio": "Audio abspielen 🔊",
299
+ "game_quiz_correct": "Richtig! 🎉",
300
+ "game_quiz_incorrect": "Falsch! Versuche es noch einmal! 😢",
301
+ "game_quiz_question": "Erkenne dieses Wort:",
302
+ "game_quiz_need_four": "Bitte beschwöre mindestens 4 Wörter, um das Audio-Visuelle Quiz zu spielen.",
303
+ "game_scramble_clue_label": "Übersetze dieses Wort:",
304
+ "game_scramble_explanation_label": "Hinweis:",
305
+ "game_scramble_reset_btn": "Zurücksetzen 🔄",
306
+ "game_scramble_play_audio": "Anhören 🔊",
307
+ "game_scramble_correct": "Perfekte Rechtschreibung! 🎉",
308
+ "game_scramble_incorrect": "Ups! Versuche es noch einmal.",
309
+ "game_memory_moves": "Züge:",
310
+ "game_memory_congrats": "Spielfeld geräumt! Perfekt gelöst! 🎉",
311
+ "game_memory_need_words": "Bitte beschwöre mindestens 4 Wörter, um das Karteikarten-Memory zu spielen.",
312
+ "game_linker_instruction": "Klicke auf eine Bildkarte und dann auf das passende Übungswort!",
313
+ "game_linker_hint_btn": "Hinweis 💡",
314
+ "game_linker_correct": "Perfekte Zuordnung! 🎉",
315
+ "game_linker_incorrect": "Fehlpaarung! Versuche es noch einmal. 😢",
316
+ "game_linker_select_clue": "Wähle zuerst eine Karte auf der linken Seite aus, um einen Hinweis zu erhalten!",
317
+ "game_linker_need_words": "Bitte beschwöre mindestens 4 Wörter, um den Visuellen Wort-Verlinker zu spielen."
318
+ },
319
+ "Chinese": {
320
+ "title": "✨ WordConjure: 语言奇幻冒险!",
321
+ "subtitle": "开启视听单词大冒险!玩趣味小游戏,收集魔法卡片!🌟",
322
+ "settings": "🛠️ 魔法师设置面板",
323
+ "word_count_label": "🔮 召唤多少个单词?",
324
+ "expl_lang_label": "🗣️ 释义语言选项",
325
+ "app_lang_label": "📱 应用显示语言",
326
+ "source_lang": "🌍 我的母语",
327
+ "target_lang": "🎯 要练习的语言",
328
+ "btn_get_words": "🪄 召唤魔法单词!",
329
+ "tab_vocab": "📖 我的魔法词典",
330
+ "tab_games": "🎮 游戏大竞技场",
331
+ "original_header": "魔法原文",
332
+ "translation_header": "神奇译文",
333
+ "explanation_header": "单词释义",
334
+ "visual_header": "艺术插画卡",
335
+ "audio_header": "听听声音",
336
+ "game_selection_title": "选择你的单词挑战",
337
+ "game_memory_title": "卡片记忆匹配",
338
+ "game_memory_desc": "翻转背面朝上的卡片,将原始词汇与其翻译和视觉卡片匹配。",
339
+ "game_monster_title": "喂食饥饿的单词怪兽",
340
+ "game_monster_desc": "怪兽快饿扁了!喂它符合要求的正确翻译卡片。",
341
+ "game_quiz_title": "视听小测验",
342
+ "game_quiz_desc": "听发音或观察视觉卡片,并选择正确的原始词汇。",
343
+ "game_scramble_title": "字母拼词挑战",
344
+ "game_scramble_desc": "将打乱的字母重新排列成正确的顺序,拼写出给定单词的翻译。",
345
+ "game_linker_title": "视觉词汇连连看",
346
+ "game_linker_desc": "将左侧的视觉卡片与右侧相应的练习单词并排进行匹配。",
347
+ "game_monster_feed_me": "喂我这张卡片:",
348
+ "game_monster_correct": "好香!回答正确!🎉",
349
+ "game_monster_incorrect": "哎哟!卡片喂错了!😢",
350
+ "game_monster_no_words": "尚未召唤魔法单词!",
351
+ "game_monster_no_words_desc": "请先返回“我的魔法词典”标签页,选择要练习的语言,然后点击“召唤魔法单词”。",
352
+ "game_quiz_hint_btn": "提示 💡",
353
+ "game_quiz_replay_audio": "播放音频 🔊",
354
+ "game_quiz_correct": "正确!🎉",
355
+ "game_quiz_incorrect": "不正确!再试一次!😢",
356
+ "game_quiz_question": "辨识此单词:",
357
+ "game_quiz_need_four": "请召唤至少4个单词以进行视听小测验。",
358
+ "game_scramble_clue_label": "翻译这个单词:",
359
+ "game_scramble_explanation_label": "线索:",
360
+ "game_scramble_reset_btn": "重置 🔄",
361
+ "game_scramble_play_audio": "发音 🔊",
362
+ "game_scramble_correct": "拼写完全正确!🎉",
363
+ "game_scramble_incorrect": "糟糕!再试一次。",
364
+ "game_memory_moves": "步数:",
365
+ "game_memory_congrats": "翻牌完成!完美匹配!🎉",
366
+ "game_memory_need_words": "请召唤至少4个单词以进行卡片记忆匹配。",
367
+ "game_linker_instruction": "先点击一张视觉卡片,再点击与其匹配的练习单词!",
368
+ "game_linker_hint_btn": "提示 💡",
369
+ "game_linker_correct": "完美关联!🎉",
370
+ "game_linker_incorrect": "匹配错误!再试一次。😢",
371
+ "game_linker_select_clue": "请先点击左侧的卡片以获取提示!",
372
+ "game_linker_need_words": "请召唤至少4个单词以进行视觉词汇连连看。"
373
+ },
374
+ "Japanese": {
375
+ "title": "✨ WordConjure:楽しく学べる冒険!",
376
+ "subtitle": "音とイラストの世界へ!ミニゲームと魔法のカードで言葉の冒険に出発しよう!🌟",
377
+ "settings": "🛠️ 魔法使いの設定パネル",
378
+ "word_count_label": "🔮 何語の言葉を召喚する?",
379
+ "expl_lang_label": "🗣️ 解説の言葉",
380
+ "app_lang_label": "📱 アプリの言語",
381
+ "source_lang": "🌍 私の母国語",
382
+ "target_lang": "🎯 練習する言語",
383
+ "btn_get_words": "🪄 言葉を召喚する!",
384
+ "tab_vocab": "📖 魔法の単語帳",
385
+ "tab_games": "🎮 ゲームアリーナ",
386
+ "original_header": "元の言葉",
387
+ "translation_header": "魔法の翻訳",
388
+ "explanation_header": "どんな意味?",
389
+ "visual_header": "アートカード",
390
+ "audio_header": "聴いてみる",
391
+ "game_selection_title": "挑戦するミニゲームを選ぼう",
392
+ "game_memory_title": "フラッシュカード神経衰弱",
393
+ "game_memory_desc": "裏返しのカードをめくって、元の言葉とその翻訳、およびビジュアルカードを一致させます。",
394
+ "game_monster_title": "お腹ペコペコ単語モンスター",
395
+ "game_monster_desc": "モンスターはお腹が空いています!リクエストに合う正しい翻訳カードをあげましょう。",
396
+ "game_quiz_title": "視聴覚クイズ",
397
+ "game_quiz_desc": "発音を聞くかビジュアルカードを確認して、正しい元の言葉を選択してください。",
398
+ "game_scramble_title": "文字並べ替えクイズ",
399
+ "game_scramble_desc": "バラバラになったアルファベットを正しい順に並べ替えて、与えられた単語の翻訳をスペルします。",
400
+ "game_linker_title": "ビジュアル単語リンカー",
401
+ "game_linker_desc": "左側のビジュアルカードと右側の対応する練習用単語を並べてマッチングさせます。",
402
+ "game_monster_feed_me": "このカードをちょうだい:",
403
+ "game_monster_correct": "モグモグ!大正解!🎉",
404
+ "game_monster_incorrect": "いたっ!違うカードだよ!😢",
405
+ "game_monster_no_words": "単語がまだ召喚されていません!",
406
+ "game_monster_no_words_desc": "まず「魔法の単語帳」タブに戻り、練習する言語を選択して「言葉を召喚する」をクリックしてください。",
407
+ "game_quiz_hint_btn": "ヒント 💡",
408
+ "game_quiz_replay_audio": "音声を再生 🔊",
409
+ "game_quiz_correct": "正解!🎉",
410
+ "game_quiz_incorrect": "不正解!もう一度挑戦してください!😢",
411
+ "game_quiz_question": "この単語を特定してください:",
412
+ "game_quiz_need_four": "視聴覚クイズをプレイするには、少なくとも4つの単語を召喚してください。",
413
+ "game_scramble_clue_label": "この単語を翻訳してください:",
414
+ "game_scramble_explanation_label": "ヒント:",
415
+ "game_scramble_reset_btn": "リセット 🔄",
416
+ "game_scramble_play_audio": "発音する 🔊",
417
+ "game_scramble_correct": "完璧なスペルです!🎉",
418
+ "game_scramble_incorrect": "おっと!もう一度試してください。",
419
+ "game_memory_moves": "手数量:",
420
+ "game_memory_congrats": "クリア!パーフェクトマッチ!🎉",
421
+ "game_memory_need_words": "フラッシュカード神経衰弱をプレイするには、少なくとも4つの単語を召喚してください。",
422
+ "game_linker_instruction": "左側のビジュアルカードをクリックしてから、一致する練習用の単語をクリックしてください!",
423
+ "game_linker_hint_btn": "ヒント 💡",
424
+ "game_linker_correct": "完璧な関連付けです!🎉",
425
+ "game_linker_incorrect": "不一致!もう一度試してください。😢",
426
+ "game_linker_select_clue": "ヒントを得るには、まず左側のカードを選択してください!",
427
+ "game_linker_need_words": "ビジュアル単語リンカーをプレイするには、少なくとも4つの単語を召喚してください。"
428
+ },
429
+ "Korean": {
430
+ "title": "✨ WordConjure: 즐거운 언어 모험!",
431
+ "subtitle": "대화형 게임과 커스텀 그림 카드로 함께 떠나는 마법의 시청각 단어 여행! 🌟",
432
+ "settings": "🛠️ 마법사의 설정 패널",
433
+ "word_count_label": "🔮 소환할 단어의 개수는?",
434
+ "expl_lang_label": "🗣️ 설명 언어",
435
+ "app_lang_label": "📱 앱 표시 언어",
436
+ "source_lang": "🌍 나의 모국어",
437
+ "target_lang": "🎯 연습할 언어",
438
+ "btn_get_words": "🪄 단어 소환하기!",
439
+ "tab_vocab": "📖 나만의 단어 마법책",
440
+ "tab_games": "🎮 게임 아레나",
441
+ "original_header": "마법 원어",
442
+ "translation_header": "마법 번역",
443
+ "explanation_header": "어떤 뜻일까?",
444
+ "visual_header": "아트 일러스트",
445
+ "audio_header": "소리 듣기",
446
+ "game_selection_title": "단어 도전 과제 선택",
447
+ "game_memory_title": "플래시카드 메모리 매치",
448
+ "game_memory_desc": "뒤집힌 카드를 넘겨 원본 단어와 번역 및 이미지 카드를 맞추세요.",
449
+ "game_monster_title": "배고픈 단어 몬스터 밥 주기",
450
+ "game_monster_desc": "몬스터가 굶주리고 있습니다! 요청에 맞는 올바른 번역 카드를 먹여주세요.",
451
+ "game_quiz_title": "시청각 퀴즈",
452
+ "game_quiz_desc": "발음을 듣거나 이미지 카드를 관찰한 후 올바른 원본 단어를 선택하세요.",
453
+ "game_scramble_title": "글자 배열 단어 맞추기",
454
+ "game_scramble_desc": "뒤섞인 철자를 올바른 순서로 배열하여 주어진 단어의 번역을 완성하세요.",
455
+ "game_linker_title": "이미지 단어 연결기",
456
+ "game_linker_desc": "왼쪽의 이미지 카드와 오른쪽의 일치하는 연습 단어를 나란히 연결하세요.",
457
+ "game_monster_feed_me": "다음 카드를 주세요:",
458
+ "game_monster_correct": "냠냠! 정답입니다! 🎉",
459
+ "game_monster_incorrect": "아야! 틀린 카드입니다! 😢",
460
+ "game_monster_no_words": "단어가 아직 소환되지 않았습니다!",
461
+ "game_monster_no_words_desc": "먼저 '나만의 단어 마법책' 탭으로 돌아가 학습할 언어를 선택한 후 '단어 소환하기'를 클릭해 주세요.",
462
+ "game_quiz_hint_btn": "힌트 💡",
463
+ "game_quiz_replay_audio": "오디오 재생 🔊",
464
+ "game_quiz_correct": "정답입니다! 🎉",
465
+ "game_quiz_incorrect": "오답입니다! 다시 시도하세요! 😢",
466
+ "game_quiz_question": "이 단어를 맞추세요:",
467
+ "game_quiz_need_four": "시청각 퀴즈를 플레이하려면 최소 4개의 단어를 소환해 주셔야 합니다.",
468
+ "game_scramble_clue_label": "이 단어를 번역하세요:",
469
+ "game_scramble_explanation_label": "단서:",
470
+ "game_scramble_reset_btn": "초기화 🔄",
471
+ "game_scramble_play_audio": "발음 듣기 🔊",
472
+ "game_scramble_correct": "완벽한 스펠링입니다! 🎉",
473
+ "game_scramble_incorrect": "앗! 다시 시도해 보세요.",
474
+ "game_memory_moves": "이동 횟수:",
475
+ "game_memory_congrats": "보드 클리어! 완벽한 매치! 🎉",
476
+ "game_memory_need_words": "플래시카드 메모리 매치를 플레이하려면 최소 4개의 단어를 소환해야 합니다.",
477
+ "game_linker_instruction": "왼쪽의 이미지 카드를 먼저 클릭한 후, 일치하는 연습 단어를 클릭하세요!",
478
+ "game_linker_hint_btn": "힌트 💡",
479
+ "game_linker_correct": "완벽한 매칭입니다! 🎉",
480
+ "game_linker_incorrect": "불일치! 다시 시도하세요. 😢",
481
+ "game_linker_select_clue": "힌트를 얻으려면 먼저 왼쪽의 카드를 선택하세요!",
482
+ "game_linker_need_words": "이미지 단어 연결기를 플레이하려면 최소 4개의 단어를 소환해야 합니다."
483
+ },
484
+ "Russian": {
485
+ "title": "✨ WordConjure: Играй и Учи!",
486
+ "subtitle": "Отправься в аудиовизуальное путешествие с играми и волшебными иллюстрациями! 🌟",
487
+ "settings": "🛠️ Панель Настроек Волшебника",
488
+ "word_count_label": "🔮 Сколько слов призвать?",
489
+ "expl_lang_label": "🗣️ Язык объяснений",
490
+ "app_lang_label": "📱 Язык интерфейса",
491
+ "source_lang": "🌍 Мой родной язык",
492
+ "target_lang": "🎯 Язык для практики",
493
+ "btn_get_words": "🪄 Призвать Слова!",
494
+ "tab_vocab": "📖 Моя Книга Слов",
495
+ "tab_games": "🎮 Игровая Арена",
496
+ "original_header": "Оригинальное слово",
497
+ "translation_header": "Магический перевод",
498
+ "explanation_header": "Что это значит?",
499
+ "visual_header": "Карточка-Иллюстрация",
500
+ "audio_header": "Послушать",
501
+ "game_selection_title": "Выберите игровое испытание",
502
+ "game_memory_title": "Найди пару",
503
+ "game_memory_desc": "Переворачивайте карточки, чтобы сопоставить оригинальные слова с их переводами и картинками.",
504
+ "game_monster_title": "Накорми слово-монстра",
505
+ "game_monster_desc": "Монстр проголодался! Дайте ему правильную карточку перевода в соответствии с его запросом.",
506
+ "game_quiz_title": "Аудиовизуальная викторина",
507
+ "game_quiz_desc": "Прослушайте произношение или посмотрите на карточку и выберите правильное оригинальное слово.",
508
+ "game_scramble_title": "Собери слово",
509
+ "game_scramble_desc": "Расположите перемешанные буквы в правильном порядке, чтобы написать перевод данного слова.",
510
+ "game_linker_title": "Визуальный соединитель слов",
511
+ "game_linker_desc": "Сопоставьте карточки слева с соответствующими словами для практики справа.",
512
+ "game_monster_feed_me": "ДАЙ МНЕ КАРТОЧКУ ДЛЯ:",
513
+ "game_monster_correct": "Ням! Верно! 🎉",
514
+ "game_monster_incorrect": "Ой! Не та карточка! 😢",
515
+ "game_monster_no_words": "Слова еще не призваны!",
516
+ "game_monster_no_words_desc": "Пожалуйста, вернитесь во вкладку 'Моя Книга Слов', выберите язык для практики и нажмите кнопку 'Призвать Слова'.",
517
+ "game_quiz_hint_btn": "Подсказка 💡",
518
+ "game_quiz_replay_audio": "Прослушать 🔊",
519
+ "game_quiz_correct": "Правильно! 🎉",
520
+ "game_quiz_incorrect": "Неверно! Попробуйте еще раз! 😢",
521
+ "game_quiz_question": "Определите это слово:",
522
+ "game_quiz_need_four": "Пожалуйста, призовите не менее 4 слов, чтобы сыграть в Аудиовизуальную викторину.",
523
+ "game_scramble_clue_label": "Переведите это слово:",
524
+ "game_scramble_explanation_label": "Подсказка:",
525
+ "game_scramble_reset_btn": "Сброс 🔄",
526
+ "game_scramble_play_audio": "Произнести 🔊",
527
+ "game_scramble_correct": "Отличное правописание! 🎉",
528
+ "game_scramble_incorrect": "Упс! Попробуйте еще раз.",
529
+ "game_memory_moves": "Ходы:",
530
+ "game_memory_congrats": "Поле очищено! Отличное совпадение! 🎉",
531
+ "game_memory_need_words": "Пожалуйста, призовите не менее 4 слов, чтобы сыграть в игру 'Найди пару'.",
532
+ "game_linker_instruction": "Нажмите на картинку слева, затем на соответствующее слово справа!",
533
+ "game_linker_hint_btn": "Подсказка ���",
534
+ "game_linker_correct": "Отличная ассоциация! 🎉",
535
+ "game_linker_incorrect": "Несовпадение! Попробуйте еще раз. 😢",
536
+ "game_linker_select_clue": "Сначала выберите карточку слева, чтобы получить подсказку!",
537
+ "game_linker_need_words": "Пожалуйста, призовите не менее 4 слов, чтобы сыграть в Визуальный соединитель слов."
538
+ },
539
+ "Portuguese": {
540
+ "title": "✨ WordConjure: Jogue e Aprenda!",
541
+ "subtitle": "Embarque em uma aventura de palavras com mini-jogos e ilustrações mágicas! 🌟",
542
+ "settings": "🛠️ Painel do Feiticeiro (Ajustes)",
543
+ "word_count_label": "🔮 Quantas palavras invocar?",
544
+ "expl_lang_label": "🗣️ Idioma das Explicações",
545
+ "app_lang_label": "📱 Idioma do Aplicativo",
546
+ "source_lang": "🌍 Minha língua nativa",
547
+ "target_lang": "🎯 Idioma para Praticar",
548
+ "btn_get_words": "🪄 Invocar Palavras!",
549
+ "tab_vocab": "📖 Meu Livro de Palavras",
550
+ "tab_games": "🎮 Arena de Jogos",
551
+ "original_header": "Palavra Original",
552
+ "translation_header": "Tradução Mágica",
553
+ "explanation_header": "O que significa?",
554
+ "visual_header": "Cartão de Arte",
555
+ "audio_header": "Ouvir som",
556
+ "game_selection_title": "Escolha o seu Desafio",
557
+ "game_memory_title": "Jogo da Memória",
558
+ "game_memory_desc": "Vire as cartas viradas para baixo para combinar os termos originais com suas traduções e cartões visuais.",
559
+ "game_monster_title": "Alimente o Monstro",
560
+ "game_monster_desc": "O monstro está faminto! Alimente-o com a carta de tradução correta que corresponde ao seu pedido.",
561
+ "game_quiz_title": "Quiz Audiovisual",
562
+ "game_quiz_desc": "Ouça a pronúncia ou inspecione o cartão visual e escolha o termo original correto.",
563
+ "game_scramble_title": "Palavras Embaralhadas",
564
+ "game_scramble_desc": "Organize as letras embaralhadas na ordem correta para soletrar a tradução da palavra fornecida.",
565
+ "game_linker_title": "Associador Visual de Palavras",
566
+ "game_linker_desc": "Combine os cartões visuais à esquerda com as palavras de treino correspondentes à direita.",
567
+ "game_monster_feed_me": "ALIMENTE-ME COM A CARTA PARA:",
568
+ "game_monster_correct": "Nham! Correto! 🎉",
569
+ "game_monster_incorrect": "Ui! Carta errada! 😢",
570
+ "game_monster_no_words": "Nenhuma palavra invocada ainda!",
571
+ "game_monster_no_words_desc": "Por favor, volte para 'Meu Livro de Palavras', selecione um idioma para praticar e clique em 'Invocar Palavras' primeiro.",
572
+ "game_quiz_hint_btn": "Dica 💡",
573
+ "game_quiz_replay_audio": "Tocar Áudio 🔊",
574
+ "game_quiz_correct": "Correto! 🎉",
575
+ "game_quiz_incorrect": "Incorreto! Tente novamente! 😢",
576
+ "game_quiz_question": "Identifique esta palavra:",
577
+ "game_quiz_need_four": "Por favor, invoque pelo menos 4 palavras para jogar o Quiz Audiovisual.",
578
+ "game_scramble_clue_label": "Traduza esta palavra:",
579
+ "game_scramble_explanation_label": "Dica:",
580
+ "game_scramble_reset_btn": "Redefinir 🔄",
581
+ "game_scramble_play_audio": "Pronunciar 🔊",
582
+ "game_scramble_correct": "Ortografia Perfeita! 🎉",
583
+ "game_scramble_incorrect": "Ops! Tente novamente.",
584
+ "game_memory_moves": "Jogadas:",
585
+ "game_memory_congrats": "Tabuleiro limpo! Combinação perfeita! 🎉",
586
+ "game_memory_need_words": "Por favor, invoque pelo menos 4 palavras para jogar o Jogo da Memória.",
587
+ "game_linker_instruction": "Clique em um cartão visual e, em seguida, clique na palavra de treino correspondente!",
588
+ "game_linker_hint_btn": "Dica 💡",
589
+ "game_linker_correct": "Associação Perfeita! 🎉",
590
+ "game_linker_incorrect": "Incompatível! Tente novamente. 😢",
591
+ "game_linker_select_clue": "Selecione primeiro uma carta à esquerda para obter uma dica!",
592
+ "game_linker_need_words": "Por favor, invoque pelo menos 4 palavras para jogar o Associador Visual de Palavras."
593
+ },
594
+ "Italian": {
595
+ "title": "✨ WordConjure: Gioca e Impara!",
596
+ "subtitle": "Parti per un'avventura di parole audiovisiva con minigiochi e illustrazioni magiche! 🌟",
597
+ "settings": "🛠️ Pannello delle Impostazioni Magiche",
598
+ "word_count_label": "🔮 Quante parole evocare?",
599
+ "expl_lang_label": "🗣️ Lingua delle spiegazioni",
600
+ "app_lang_label": "📱 Lingua dell'applicazione",
601
+ "source_lang": "🌍 Mia lingua madre",
602
+ "target_lang": "🎯 Lingua da praticare",
603
+ "btn_get_words": "🪄 Evoca Parole!",
604
+ "tab_vocab": "📖 Il Mio Grimoire delle Parole",
605
+ "tab_games": "🎮 Arena dei Giochi",
606
+ "original_header": "Parola Originale",
607
+ "translation_header": "Traduzione Magica",
608
+ "explanation_header": "Cosa significa?",
609
+ "visual_header": "Carta d'Arte",
610
+ "audio_header": "Ascolta",
611
+ "game_selection_title": "Scegli la tua Sfida",
612
+ "game_memory_title": "Memory di Flashcard",
613
+ "game_memory_desc": "Gira le carte coperte per abbinare i termini originali con le loro traduzioni e carte visive.",
614
+ "game_monster_title": "Nutri il Mostro",
615
+ "game_monster_desc": "Il mostro sta morendo di fame! Nutrilo con la carta di traduzione correta in base alla sua richiesta.",
616
+ "game_quiz_title": "Quiz Audio-Visivo",
617
+ "game_quiz_desc": "Ascolta la pronuncia o esamina la carta visiva, quindi scegli il termine originale corretto.",
618
+ "game_scramble_title": "Parole Disordinate",
619
+ "game_scramble_desc": "Disponi le lettere disordinate nell'ordine corretto per comporre la traduzione della parola data.",
620
+ "game_linker_title": "Collegamento Visivo",
621
+ "game_linker_desc": "Abbina le carte visive a sinistra con le corrispondenti parole da esercitare a destra.",
622
+ "game_monster_feed_me": "NUTRIMI CON LA CARTA PER:",
623
+ "game_monster_correct": "Gnam! Corretto! 🎉",
624
+ "game_monster_incorrect": "Ahia! Carta sbagliata! 😢",
625
+ "game_monster_no_words": "Nessuna palavra ancora evocata!",
626
+ "game_monster_no_words_desc": "Torna alla scheda 'Il Mio Grimoire delle Parole', seleziona una lingua e fai clic su 'Evoca Parole' per iniziare.",
627
+ "game_quiz_hint_btn": "Suggerimento 💡",
628
+ "game_quiz_replay_audio": "Riproduci Audio 🔊",
629
+ "game_quiz_correct": "Corretto! 🎉",
630
+ "game_quiz_incorrect": "Sbagliato! Riprova! 😢",
631
+ "game_quiz_question": "Identifica questa parola:",
632
+ "game_quiz_need_four": "Evoca almeno 4 parole per giocare al Quiz Audio-Visivo.",
633
+ "game_scramble_clue_label": "Traduci questa parola:",
634
+ "game_scramble_explanation_label": "Suggerimento:",
635
+ "game_scramble_reset_btn": "Ripristina 🔄",
636
+ "game_scramble_play_audio": "Pronuncia 🔊",
637
+ "game_scramble_correct": "Ortografia perfetta! 🎉",
638
+ "game_scramble_incorrect": "Ops! Riprova.",
639
+ "game_memory_moves": "Mosse:",
640
+ "game_memory_congrats": "Tabellone svuotato! Abbinamento perfetto! 🎉",
641
+ "game_memory_need_words": "Evoca almeno 4 parole per giocare al Memory delle Flashcard.",
642
+ "game_linker_instruction": "Fai clic su una carta visiva, poi sulla parola da esercitare corrispondente!",
643
+ "game_linker_hint_btn": "Suggerimento 💡",
644
+ "game_linker_correct": "Abbinamento perfetto! 🎉",
645
+ "game_linker_incorrect": "Mancata corrispondenza! Riprova. 😢",
646
+ "game_linker_select_clue": "Seleziona prima una carta a sinistra per ottenere un suggerimento!",
647
+ "game_linker_need_words": "Evoca almeno 4 parole per giocare al Collegamento Visivo."
648
+ }
649
+ }
650
+
651
+ TTS_SPEAKER_MAPPING = {
652
+ "English": "Aiden",
653
+ "Spanish": "Ryan",
654
+ "French": "Serena",
655
+ "German": "Aiden",
656
+ "Chinese": "Serena",
657
+ "Japanese": "Ono_Anna",
658
+ "Korean": "Sohee",
659
+ "Russian": "Aiden",
660
+ "Portuguese": "Ryan",
661
+ "Italian": "Ryan"
662
+ }
663
+
664
+ # Pool of topics and categories to maximize list variety across generations
665
+ VOCAB_THEMES = [
666
+ "nature and environment", "travel and sightseeing", "cooking and ingredients",
667
+ "occupations and careers", "hobbies and creative pursuits", "emotions and personalities",
668
+ "household items and architecture", "animals and biology", "urban life and transportation",
669
+ "weather, seasons and environment", "shopping and daily routines", "sports, health and human body",
670
+ "science, space and technology", "art, music and performances", "academic studies and school items"
671
+ ]
672
+
673
+ VOCAB_TYPES = [
674
+ "a mix of parts of speech", "action verbs", "descriptive adjectives", "useful nouns"
675
+ ]
676
+
677
+ # API: Fetch localization texts
678
+ @app.api(name="get_localization")
679
+ def get_localization(user_native_lang: str) -> dict:
680
+ return LOCALIZATION_DATABASE.get(user_native_lang, LOCALIZATION_DATABASE["English"])
681
+
682
+ # Helper: Query causal LM
683
+ def query_llm(prompt: str, max_new_tokens: int = 512) -> str:
684
+ messages = [
685
+ {"role": "user", "content": prompt}
686
+ ]
687
+ text = tokenizer.apply_chat_template(
688
+ messages, tokenize=False, add_generation_prompt=True
689
+ )
690
+ model_inputs = tokenizer([text], return_tensors="pt").to("cuda:0")
691
+ generated_ids = llm_model.generate(
692
+ **model_inputs,
693
+ max_new_tokens=max_new_tokens,
694
+ temperature=0.8, # Slightly raised temperature for more creative/diverse vocabulary choice
695
+ top_p=0.9
696
+ )
697
+ input_len = model_inputs.input_ids.shape[1]
698
+ response_tokens = generated_ids[0][input_len:]
699
+ return tokenizer.decode(response_tokens, skip_special_tokens=True).strip()
700
+
701
+ # Helper: Extract valid JSON array from LLM response
702
+ def extract_json_array(text: str) -> list:
703
+ cleaned = text.strip()
704
+ if cleaned.startswith("```json"):
705
+ cleaned = cleaned[7:]
706
+ if cleaned.endswith("```"):
707
+ cleaned = cleaned[:-3]
708
+ cleaned = cleaned.strip()
709
+
710
+ match = re.search(r'\[\s*\[.*\]\s*\]', cleaned, re.DOTALL)
711
+ if match:
712
+ try:
713
+ return json.loads(match.group(0))
714
+ except Exception:
715
+ pass
716
+ try:
717
+ return json.loads(cleaned)
718
+ except Exception as e:
719
+ print(f"Failed to parse JSON array: {e}")
720
+ return []
721
+
722
+ # Helper: Generate custom visual card using Sana Sprint 1.6B with English prompt
723
+ def generate_visual_base64(image_prompt: str) -> str:
724
+ # We prefix standard style guidelines onto the LLM's custom English description
725
+ prompt = f"A simple, clean, minimalist flat icon style illustration of {image_prompt}, high quality educational flashcard illustration, isolated on white background"
726
+
727
+ # Inference optimizations: Wrapped inside torch.inference_mode()
728
+ with torch.inference_mode():
729
+ image = sana_pipe(
730
+ prompt=prompt,
731
+ height=768,
732
+ width=768,
733
+ num_inference_steps=2,
734
+ guidance_scale=4.5
735
+ ).images[0]
736
+
737
+ buffered = io.BytesIO()
738
+ image.save(buffered, format="PNG")
739
+ return base64.b64encode(buffered.getvalue()).decode("utf-8")
740
+
741
+ # Helper: Generate TTS audio bytes via Qwen3-TTS
742
+ def generate_audio_base64(word: str, language: str) -> str:
743
+ speaker = TTS_SPEAKER_MAPPING.get(language, "Aiden")
744
+
745
+ # Inference optimizations: Wrapped inside torch.inference_mode()
746
+ with torch.inference_mode():
747
+ wavs, sr = tts_model.generate_custom_voice(
748
+ text=word,
749
+ language=language,
750
+ speaker=speaker,
751
+ instruct="Pronounce the text directly, clearly, and neutrally",
752
+ non_streaming_mode=True,
753
+ max_new_tokens=100,
754
+ )
755
+
756
+ audio_data = wavs[0]
757
+ buffered = io.BytesIO()
758
+ sf.write(buffered, audio_data, sr, format="WAV")
759
+ return base64.b64encode(buffered.getvalue()).decode("utf-8")
760
+
761
+ # API: Processes translation data sequentially
762
+ @app.api(name="get_translations")
763
+ @spaces.GPU(duration=30)
764
+ def get_translations(source_lang: str, target_lang: str, word_count: int, explanation_lang: str) -> list[list[str]]:
765
+ explanation_lang_target = source_lang if explanation_lang == "source" else target_lang
766
+
767
+ # Randomizing parameters to force variety in vocab selection
768
+ selected_category = random.choice(VOCAB_THEMES)
769
+ selected_type = random.choice(VOCAB_TYPES)
770
+ random_seed = random.randint(100000, 999999)
771
+
772
+ prompt = f"""You are a professional multilingual translator.
773
+ Generate a vocabulary list of exactly {word_count} words or phrases for language learning.
774
+ Source Language: {source_lang}
775
+ Practice/Target Language: {target_lang}
776
+
777
+ To ensure high variety and prevent repeating standard words across sessions, apply the following randomized criteria to select your words:
778
+ - Focus Topic/Theme: {selected_category}
779
+ - Grammar/Category: {selected_type}
780
+ - Generation Session Key: {random_seed}
781
+
782
+ Ensure you select unique and engaging vocabulary. Avoid simple introductory words like "dog", "hello", "goodbye", or "cat" unless they directly fit the requested focus topic.
783
+
784
+ For each item, generate:
785
+ 1. The source word or phrase.
786
+ 2. The target translation.
787
+ 3. A short, simple, clear, and friendly explanation (maximum 2 sentences) written in the {explanation_lang_target} language.
788
+ 4. An explicit, highly-descriptive illustration prompt in English, designed specifically for a text-to-image generator (e.g., instead of just "car", write "a modern red electric sedan parked quietly").
789
+
790
+ You must return ONLY a raw JSON list of lists of strings, where each sublist contains exactly:
791
+ [source_word, target_translation, explanation, english_image_prompt]
792
+
793
+ Example format:
794
+ [
795
+ ["Apple", "Manzana", "Una fruta redonda y de sabor dulce.", "a single crisp red apple with a small green leaf on top"],
796
+ ["Bicycle", "Bicicleta", "Un vehículo de dos ruedas que requiere pedalear.", "a classic blue commuter bicycle with a small front basket"]
797
+ ]
798
+
799
+ Do not include any chat commentary, introduction, or explanations outside the JSON array."""
800
+
801
+ response = query_llm(prompt, 1024)
802
+ raw_pairs = extract_json_array(response)
803
+
804
+ validated_pairs = []
805
+ for item in raw_pairs:
806
+ # Support fallback states in case the model returns fewer than 4 components
807
+ if isinstance(item, list) and len(item) >= 4:
808
+ validated_pairs.append([str(item[0]), str(item[1]), str(item[2]), str(item[3])])
809
+ elif isinstance(item, list) and len(item) == 3:
810
+ validated_pairs.append([str(item[0]), str(item[1]), str(item[2]), f"a minimalist illustration representing {item[1]}"])
811
+ elif isinstance(item, list) and len(item) == 2:
812
+ validated_pairs.append([str(item[0]), str(item[1]), "", f"a minimalist illustration representing {item[1]}"])
813
+
814
+ selected_words = validated_pairs[:word_count]
815
+
816
+ # Step 2: Generate corresponding images sequentially using the English prompt
817
+ visuals = []
818
+ for item in selected_words:
819
+ image_prompt = item[3] # Extracting English visual prompt
820
+ try:
821
+ visual_b64 = generate_visual_base64(image_prompt)
822
+ except Exception as e:
823
+ print(f"Error generating visual card with prompt '{image_prompt}': {e}")
824
+ visual_b64 = ""
825
+ visuals.append(visual_b64)
826
+
827
+ # Step 3: Generate all corresponding audio files sequentially
828
+ audios = []
829
+ for item in selected_words:
830
+ target_word = item[1]
831
+ try:
832
+ audio_b64 = generate_audio_base64(target_word, target_lang)
833
+ except Exception as e:
834
+ print(f"Error generating audio track for '{target_word}': {e}")
835
+ audio_b64 = ""
836
+ audios.append(audio_b64)
837
+
838
+ # Step 4: Assemble into final frontend format: [original, translated, explanation, visual_b64, audio_b64]
839
+ results = []
840
+ for idx, (original, translated, explanation, _) in enumerate(selected_words):
841
+ results.append([
842
+ original,
843
+ translated,
844
+ explanation,
845
+ visuals[idx],
846
+ audios[idx]
847
+ ])
848
+
849
+ return results
850
+
851
+ # Route: Serve HTML page
852
+ @app.get("/", response_class=HTMLResponse)
853
+ def homepage():
854
+ html_path = "index.html"
855
+ with open(html_path, "r", encoding="utf-8") as f:
856
+ return f.read()
857
+
858
+ if __name__ == "__main__":
859
+ app.launch(debug=True, show_error=True)