before adding grammar field, adding notes field into the translation table

This commit is contained in:
stephen 2026-06-16 15:06:08 +10:00
parent b3104d66a5
commit 985bbb5839
6 changed files with 3389 additions and 406 deletions

View file

@ -1,121 +1,84 @@
# core/bulk_importer.py # core/bulk_importer.py
import os
import re import re
from database.connection import get_connection
from docling.document_converter import DocumentConverter from docling.document_converter import DocumentConverter
from database.connection import get_connection
class BulkImporter: class BulkImporter:
def __init__(self): def import_pdf_glossary(self, pdf_path: str, textbook_name: str):
self.converter = DocumentConverter() """Uses Docling layout extraction engine to parse tables out of multi-column glossary PDFs."""
self.unit_regex = re.compile(r'U([0-9]+)') print(f"🔄 Analyzing structural layout for '{pdf_path}'...")
def clean_field(self, text: str) -> str: if not os.path.exists(pdf_path):
"""Removes layout spacing, structural artifacts, and noise.""" print(f"❌ Target document path could not be found: {pdf_path}")
if not text: return
return ""
# Remove markdown bold/italics markers if any, and compress spaces
cleaned = text.replace('*', '').replace('_', '').replace('\\', '')
cleaned = re.sub(r'\s+', ' ', cleaned)
return cleaned.strip()
def parse_unit(self, context_str: str) -> int:
"""Extracts the integer unit ID out of strings like 'U3_8B' or 'U7'."""
match = self.unit_regex.search(context_str)
if match:
return int(match.group(1))
return None
def insert_pair(self, cursor, es_text, en_text, context_tag, textbook_name):
"""Helper to cleanly insert a single Spanish-English translation pair."""
es_clean = self.clean_field(es_text)
en_clean = self.clean_field(en_text)
tag_clean = self.clean_field(context_tag)
# Guard clause against column headers or empty layout cells
if not es_clean or not en_clean:
return False
if es_clean.lower() in ["spanish", "word", "alphabetical glossary", "es", "en"] or len(es_clean) <= 1:
return False
unit_number = self.parse_unit(tag_clean)
try:
# 1. Insert Spanish Phrase
cursor.execute("""
INSERT INTO phrases (text, language, textbook, unit, source_context)
VALUES (?, 'es', ?, ?, ?)
""", (es_clean, textbook_name, unit_number, tag_clean))
es_id = cursor.lastrowid
# 2. Insert English Translation
cursor.execute("""
INSERT INTO phrases (text, language, textbook, unit, source_context)
VALUES (?, 'en', ?, ?, ?)
""", (en_clean, textbook_name, unit_number, tag_clean))
en_id = cursor.lastrowid
# 3. Create Bidirectional Cross-Reference Entries
cursor.execute("INSERT INTO translations (source_phrase_id, target_phrase_id) VALUES (?, ?)", (es_id, en_id))
cursor.execute("INSERT INTO translations (source_phrase_id, target_phrase_id) VALUES (?, ?)", (en_id, es_id))
return True
except Exception:
return False
def import_pdf_glossary(self, file_path: str, textbook_name: str):
print(f"🔄 Docling is analyzing structural layout for '{file_path}'...")
try:
result = self.converter.convert(file_path)
document_text = result.document.export_to_markdown()
except Exception as e:
print(f"❌ Docling processing failed: {e}")
return 0
conn = get_connection() conn = get_connection()
cursor = conn.cursor() cursor = conn.cursor()
print("📥 Parsing 6-column text grid structure and writing to database...") # Reset staging tables to guarantee fresh data state
count = 0 cursor.execute("DELETE FROM translations")
cursor.execute("DELETE FROM phrases")
conn.commit()
# Process the markdown line-by-line print("📥 Initializing Docling layout analysis engine...")
lines = document_text.split('\n') try:
for line in lines: # 1. Initialize Docling converter
line_str = line.strip() converter = DocumentConverter()
result = converter.convert(pdf_path)
# Target lines containing table formatting row data print("📥 Parsing extracted text tables and tabular structures...")
if not line_str.startswith('|') or not line_str.endswith('|'): raw_inserts_count = 0
# 2. Iterate through extracted tables found inside the layout structure
for table_idx, table_element in enumerate(result.document.tables):
# Convert Docling table data framework back to standard pandas-like dictionary arrays
table_data = table_element.export_to_dataframe()
# Iterate rows while ensuring it's not looking at headers or broken table pieces
for row_idx, row in table_data.iterrows():
row_list = list(row)
# Ensure we have enough columns to look at your glossary structure (expecting 4-6 columns)
if len(row_list) < 3:
continue continue
# Split by markdown table pipes # Extract positions out of the 6-column structure:
# e.g., "| años60 m | 1960s | U8_7A | bañ osmpl | bath | U1_1A |" # Usually: Col 0 = Spanish, Col 1 = English, Col 2 or 3 = Context/Unit tag
parts = [p.strip() for p in line_str.split('|')] es_raw = str(row_list[0]).strip() if row_list[0] else ""
en_raw = str(row_list[1]).strip() if row_list[1] else ""
context_raw = str(row_list[2]).strip() if row_list[2] else ""
# A valid row split will include empty items at the ends due to leading/trailing pipes # Ignore empty lines or column labels (like "Spanish", "Español", "English")
# For a 6-column table, len(parts) should be at least 8 elements if not es_raw or not en_raw or "español" in es_raw.lower() or "english" in en_raw.lower():
if len(parts) < 7:
continue continue
# Skip Markdown table header separator rows: |---|---|... # Parse out unit identifiers from context strings (e.g. "U2_3A" gives unit = 2)
if '---' in parts[1]: unit_num = 1
continue unit_match = re.search(r'U(\d+)', context_raw, re.IGNORECASE)
if unit_match:
unit_num = int(unit_match.group(1))
# Extract Left-Hand Column Group (Columns 1, 2, 3) # 3. Stage Spanish item row
es_left = parts[1] cursor.execute("""
en_left = parts[2] INSERT INTO phrases (text, language, textbook, unit, source_context, word_type)
unit_left = parts[3] if len(parts) >= 4 else "" VALUES (?, 'es', ?, ?, ?, 'phrase')
""", (es_raw, textbook_name, unit_num, context_raw))
if self.insert_pair(cursor, es_left, en_left, unit_left, textbook_name): # 4. Stage English item row (immediately adjacent)
count += 1 cursor.execute("""
INSERT INTO phrases (text, language, textbook, unit, source_context, word_type)
VALUES (?, 'en', ?, ?, ?, 'phrase')
""", (en_raw, textbook_name, unit_num, context_raw))
# Extract Right-Hand Column Group (Columns 4, 5, 6) raw_inserts_count += 2
if len(parts) >= 7:
es_right = parts[4]
en_right = parts[5]
unit_right = parts[6]
if self.insert_pair(cursor, es_right, en_right, unit_right, textbook_name):
count += 1
conn.commit() conn.commit()
print(f"🎉 Bulk ingestion staging complete! Staged {raw_inserts_count} entries.")
except Exception as docling_error:
print(f"❌ Docling encountered a processing failure: {docling_error}")
import traceback
traceback.print_exc()
finally:
conn.close() conn.close()
print(f"🎉 Bulk ingestion complete! Registered {count} matched glossary items.")
return count

View file

@ -4,45 +4,27 @@ from database.connection import get_connection
class GlossaryCleaner: class GlossaryCleaner:
def __init__(self): def __init__(self):
# Captures verb irregular brackets: " (zc)", " (ie)", etc.
self.verb_pattern = re.compile(r'\s*\(([a-z\s]+)\)') self.verb_pattern = re.compile(r'\s*\(([a-z\s]+)\)')
# Aggressive character-level match for broken trailing gender tags def clean_and_expand_spanish(self, text: str) -> list[tuple[str, str, str]]:
# Tracks variations like: " m", " f", "mpl", "fpl", "smpl", "osmpl" at the end of a string
self.broken_gender_pattern = re.compile(r'[\s]*\b(m|f|pl)\b$|[\s\w]*(m|f|pl|mpl|fpl|smpl|osmpl)$', re.IGNORECASE)
def clean_and_expand_spanish(self, text: str) -> tuple[list[str], str, str]:
"""Parses and strips layout noise from Spanish strings, extracting rich metadata."""
word_type = "phrase" word_type = "phrase"
grammar_note = None grammar_note = None
clean_text = text.strip() clean_text = str(text).strip()
# 1. Extract verb irregularities if present
verb_match = self.verb_pattern.search(clean_text) verb_match = self.verb_pattern.search(clean_text)
if verb_match: if verb_match:
word_type = "verb" word_type = "verb"
grammar_note = verb_match.group(1).strip() grammar_note = verb_match.group(1).strip()
clean_text = self.verb_pattern.sub('', clean_text).strip() clean_text = self.verb_pattern.sub('', clean_text).strip()
# 2. Extract and remove sticky gender notations (e.g., "bañ osmpl" -> "baños")
# Check if text ends with common markers
lower_text = clean_text.lower() lower_text = clean_text.lower()
if lower_text.endswith(('mpl', 'fpl', 'smpl', 'osmpl')): if lower_text.endswith(('mpl', 'fpl', 'smpl', 'osmpl')):
word_type = "noun" word_type = "noun"
if 'f' in lower_text: grammar_note = "f, pl" if 'f' in lower_text else "m, pl"
grammar_note = "f, pl" if lower_text.endswith('osmpl'):
else:
grammar_note = "m, pl"
# Reconstruct the original word base before the layout break
# e.g., "bañ osmpl" -> remove "osmpl" and append "os" to restore "baños"
if lower_text.endswith('osmpl') and clean_text.lower().endswith('osmpl'):
clean_text = clean_text[:-5] + "os" clean_text = clean_text[:-5] + "os"
else: else:
# General strip of trailing garbage characters
clean_text = re.sub(r'\s*[a-zA-Z\s]*$', '', clean_text) clean_text = re.sub(r'\s*[a-zA-Z\s]*$', '', clean_text)
# Standard boundary check for clean tags (e.g., "años60 m")
elif re.search(r'\s+\b(m|f|pl|m,\s*pl|f,\s*pl)\b\s*$', clean_text): elif re.search(r'\s+\b(m|f|pl|m,\s*pl|f,\s*pl)\b\s*$', clean_text):
word_type = "noun" word_type = "noun"
match = re.search(r'\b(m|f|pl|m,\s*pl|f,\s*pl)\b\s*$', clean_text) match = re.search(r'\b(m|f|pl|m,\s*pl|f,\s*pl)\b\s*$', clean_text)
@ -50,102 +32,81 @@ class GlossaryCleaner:
grammar_note = match.group(1).strip() grammar_note = match.group(1).strip()
clean_text = re.sub(r'\s+\b(m|f|pl|m,\s*pl|f,\s*pl)\b\s*$', '', clean_text).strip() clean_text = re.sub(r'\s+\b(m|f|pl|m,\s*pl|f,\s*pl)\b\s*$', '', clean_text).strip()
clean_text = clean_text.strip() return [(clean_text.strip(), word_type, grammar_note)]
# 3. Expand dual-gender adjectives (e.g., "apasionado/a")
variants = []
if '/' in clean_text and ' ' not in clean_text:
word_type = "adjective"
base, suffix = clean_text.split('/', 1)
base = base.strip()
suffix = suffix.strip()
if suffix == 'a' and base.endswith('o'):
variants.append((base, word_type, "m"))
variants.append((base[:-1] + 'a', word_type, "f"))
elif suffix == 'ra' and base.endswith('r'):
variants.append((base, word_type, "m"))
variants.append((base + 'a', word_type, "f"))
else:
variants.append((clean_text, word_type, grammar_note))
else:
variants.append((clean_text, word_type, grammar_note))
return variants
def clean_english_text(self, text: str) -> str: def clean_english_text(self, text: str) -> str:
"""Fixes layout spacing issues on English infinitive verbs.""" cleaned = str(text).strip()
cleaned = text.strip()
# Ensure duplicate internal spacing is compressed
cleaned = re.sub(r'\s+', ' ', cleaned) cleaned = re.sub(r'\s+', ' ', cleaned)
# Intercept smashed English infinitives (e.g., "toappear" -> "to appear")
if cleaned.startswith("to") and len(cleaned) > 2 and not cleaned.startswith("to "): if cleaned.startswith("to") and len(cleaned) > 2 and not cleaned.startswith("to "):
cleaned = re.sub(r'^to([a-z])', r'to \1', cleaned) cleaned = re.sub(r'^to([a-z])', r'to \1', cleaned)
return cleaned return cleaned
def process_database_clean(self): def process_database_clean(self):
"""Processes and normalizes raw table entries into pristine structures.""" """Processes raw entries from the source tables and builds translation row connections."""
conn = get_connection() conn = get_connection()
cursor = conn.cursor() cursor = conn.cursor()
print("🧹 Extracting raw dataset for deep metadata extraction...") # Pull raw rows populated during BulkImporter phase
# Ensuring we look at your base uncleaned layout records
cursor.execute(""" try:
SELECT t.source_phrase_id, p1.text as es_text, p2.text as en_text, cursor.execute("SELECT id, text, language, textbook, unit, source_context FROM phrases")
p1.textbook, p1.unit, p1.source_context
FROM translations t
JOIN phrases p1 ON t.source_phrase_id = p1.id
JOIN phrases p2 ON t.target_phrase_id = p2.id
WHERE p1.language = 'es'
""")
raw_rows = cursor.fetchall() raw_rows = cursor.fetchall()
except Exception as e:
if not raw_rows: print(f"⚠️ Error reading raw phrases: {e}")
print("⚠️ No base phrases found to clean.")
conn.close() conn.close()
return return
print(f"⚙️ Migrating {len(raw_rows)} rows into corrected schemas...") # Separate them logically to reconstruct pairing matrices safely
# We find adjacent lines assuming index N is Spanish and N+1 is its English partner
paired_rows = []
for i in range(0, len(raw_rows) - 1, 2):
if raw_rows[i][2] == 'es':
paired_rows.append((
raw_rows[i][1], # Spanish raw text
raw_rows[i+1][1], # English raw text
raw_rows[i][3], # Textbook name
raw_rows[i][4], # Unit Integer
raw_rows[i][5] # Context string (e.g. U2_3A)
))
# Clear out previous passes completely # Clear down active runtime tables to map fresh structural pairs
cursor.execute("DELETE FROM translations") cursor.execute("DELETE FROM translations")
cursor.execute("DELETE FROM phrases") cursor.execute("DELETE FROM phrases")
cursor.execute("DELETE FROM audio_tracks")
print(f"⚙️ Migrating {len(paired_rows)} raw rows into translation pairs...")
inserted_count = 0 inserted_count = 0
for _, es_raw, en_raw, textbook, unit, context in raw_rows: for es_raw, en_raw, textbook, unit, context in paired_rows:
# Process Spanish layout text elements # Explicitly keep underscore formatting (e.g., U2_3A) intact
safe_context = str(context).strip() if context else f"U{unit}"
# Clean both sides through individual language filters
es_variants = self.clean_and_expand_spanish(es_raw) es_variants = self.clean_and_expand_spanish(es_raw)
# Process and restore spaces to English text elements
en_clean = self.clean_english_text(en_raw) en_clean = self.clean_english_text(en_raw)
for es_clean, w_type, g_note in es_variants: for es_clean, w_type, g_note in es_variants:
try: # 1. Store Spanish Text Item Node
# Insert pristine Spanish entry
cursor.execute(""" cursor.execute("""
INSERT INTO phrases (text, language, textbook, unit, source_context, word_type, grammar_note) INSERT INTO phrases (text, language, textbook, unit, source_context, word_type, grammar_note)
VALUES (?, 'es', ?, ?, ?, ?, ?) VALUES (?, 'es', ?, ?, ?, ?, ?)
""", (es_clean, textbook, unit, context, w_type, g_note)) """, (es_clean, textbook, unit, safe_context, w_type, g_note))
es_id = cursor.lastrowid es_id = cursor.lastrowid
# Insert spaced English entry # 2. Store English Text Item Node
cursor.execute(""" cursor.execute("""
INSERT INTO phrases (text, language, textbook, unit, source_context, word_type, grammar_note) INSERT INTO phrases (text, language, textbook, unit, source_context, word_type, grammar_note)
VALUES (?, 'en', ?, ?, ?, ?, NULL) VALUES (?, 'en', ?, ?, ?, ?, NULL)
""", (en_clean, textbook, unit, context, w_type)) """, (en_clean, textbook, unit, safe_context, w_type))
en_id = cursor.lastrowid en_id = cursor.lastrowid
# Re-map relations # 3. Form unique single translation row mapping bond
cursor.execute("INSERT INTO translations (source_phrase_id, target_phrase_id) VALUES (?, ?)", (es_id, en_id)) cursor.execute("""
cursor.execute("INSERT INTO translations (source_phrase_id, target_phrase_id) VALUES (?, ?)", (en_id, es_id)) INSERT INTO translations (source_phrase_id, target_phrase_id, deck_name)
VALUES (?, ?, 'General')
""", (es_id, en_id))
inserted_count += 1 inserted_count += 1
except Exception:
continue
conn.commit() conn.commit()
conn.close() conn.close()
print(f"🎉 Schema extraction pipeline completed! Saved {inserted_count} pristine phrase definitions.") print(f"🎉 Schema extraction pipeline completed! Saved {inserted_count} pristine translation pairs.")

View file

@ -1,14 +1,21 @@
# database/connection.py # database/connection.py
import sqlite3 import sqlite3
import os
def get_connection(): def get_connection():
return sqlite3.connect("spanish_trainer.db") # Force the path to be absolute relative to the project folder
db_path = os.path.abspath("spanish_trainer.db")
return sqlite3.connect(db_path)
def init_db(): def init_db():
print("🛠️ Constructing relational database schema...")
conn = get_connection() conn = get_connection()
cursor = conn.cursor() cursor = conn.cursor()
# 1. Main Phrases Table # Enable foreign keys explicitly for this connection instance
cursor.execute("PRAGMA foreign_keys = ON;")
# 1. Phrases Table (Holds individual localized text strings)
cursor.execute(""" cursor.execute("""
CREATE TABLE IF NOT EXISTS phrases ( CREATE TABLE IF NOT EXISTS phrases (
id INTEGER PRIMARY KEY AUTOINCREMENT, id INTEGER PRIMARY KEY AUTOINCREMENT,
@ -21,34 +28,38 @@ def init_db():
grammar_note TEXT, grammar_note TEXT,
voice_gender TEXT DEFAULT 'female', voice_gender TEXT DEFAULT 'female',
base_speed REAL DEFAULT 1.0, base_speed REAL DEFAULT 1.0,
deck_name TEXT DEFAULT 'General',
created_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP created_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP
) );
""") """)
# 2. Translations Cross-Reference Table # 2. Translations Table (The relational tie binding English and Spanish IDs together)
cursor.execute(""" cursor.execute("""
CREATE TABLE IF NOT EXISTS translations ( CREATE TABLE IF NOT EXISTS translations (
translation_id INTEGER PRIMARY KEY AUTOINCREMENT,
source_phrase_id INTEGER, source_phrase_id INTEGER,
target_phrase_id INTEGER, target_phrase_id INTEGER,
PRIMARY KEY (source_phrase_id, target_phrase_id), deck_name TEXT DEFAULT 'General',
FOREIGN KEY (source_phrase_id) REFERENCES phrases(id) ON DELETE CASCADE, FOREIGN KEY (source_phrase_id) REFERENCES phrases(id) ON DELETE CASCADE,
FOREIGN KEY (target_phrase_id) REFERENCES phrases(id) ON DELETE CASCADE FOREIGN KEY (target_phrase_id) REFERENCES phrases(id) ON DELETE CASCADE
) );
""") """)
# 3. Audio Tracks Metadata Table (Required by the glossary cleaner) # 3. Audio Tracks Table (Links phrase items to local disk storage clips)
cursor.execute(""" cursor.execute("""
CREATE TABLE IF NOT EXISTS audio_tracks ( CREATE TABLE IF NOT EXISTS audio_tracks (
id INTEGER PRIMARY KEY AUTOINCREMENT, id INTEGER PRIMARY KEY AUTOINCREMENT,
phrase_id INTEGER, phrase_id INTEGER,
file_path TEXT NOT NULL, file_path TEXT NOT NULL,
sample_rate INTEGER,
duration REAL,
FOREIGN KEY (phrase_id) REFERENCES phrases(id) ON DELETE CASCADE FOREIGN KEY (phrase_id) REFERENCES phrases(id) ON DELETE CASCADE
) );
""") """)
# CRITICAL: Force SQLite to physically commit the table architectures to disk
conn.commit() conn.commit()
# Verification Sweep: Double-check that tables actually exist before we hand over control
cursor.execute("SELECT name FROM sqlite_master WHERE type='table';")
tables = [row[0] for row in cursor.fetchall()]
conn.close() conn.close()
print("✅ Rich metadata database schema initialized successfully.")
print(f"✅ Database tables physically confirmed on disk: {tables}")

707
exported_phases.csv Normal file
View file

@ -0,0 +1,707 @@
1,aparecer,"to appear",verb,U3_6A,General
2,apasionada,passionate,phrase,U5_15C,General
3,apellido,surname,noun,U1_4A,General
4,aprender,"to learn",phrase,U2_2C,General
5,aproximadamente,approximately,phrase,U3_2A,General
6,apuntar,"to note down",phrase,U9_5A,General
7,"aquí",here,phrase,U3_2A,General
8,"aquí tiene","here you go",phrase,U4_GyC,General
9,"árabe",Arabic,noun,U2_LEX,General
10,archivo,file,noun,U9_7A,General
11,"área",area,noun,U3_2A,General
12,arena,sand,noun,U3_4A,General
13,arepa,arepa,noun,U7_9A,General
14,argentina,Argentinian,phrase,U1_3A,General
15,"árido/a",arid,phrase,U3_LEX,General
16,arma,weapon,noun,U3_2A,General
17,arroz,rice,noun,U7_LEX,General
18,arquitecta,architect,phrase,U1_6,General
19,arquitectura,architecture,noun,U1_3A,General
20,arte,art,noun,U1_1A,General
21,artesanal,artisanal,phrase,U4_1A,General
22,"artesanía f artista","crafts artist",phrase,U4_1A,General
23,asado/a,roasted,phrase,U7_7A,General
24,Asia,Asia,phrase,U3_LEX,General
25,"aspecto físico","physical feature",noun,U5_14A,General
26,aspecto,aspect,noun,U3_11B,General
27,"Asunción",Asuncion,phrase,U3_13A,General
28,"atención",takenote,phrase,U4_14A,General
29,atender,"to lookafter",verb,U9_3A,General
30,atento/a,attentive,phrase,U9_5A,General
31,"atlántico/a",Atlantic,phrase,U3_6A,General
32,atractivo/a,attractive,phrase,U8_7A,General
33,"atraído/a",attracted,phrase,U9_3A,General
34,"atún",tuna,noun,U7_2A,General
35,"autobús",bus,noun,U3_4A,General
36,"automático/a",automatic,phrase,U8_2A,General
37,"autónomo/a",self-employed,phrase,U3_2A,General
38,avenida,avenue,noun,U8_3A,General
39,aventurero/a,adventurous,phrase,U5_3C,General
40,"avión",plane,noun,U6_GyC,General
41,"azúcar m azul","sugar blue",phrase,"U7_6A U4_2A",General
42,"azul claro","light blue",phrase,U4_13A,General
43,bailar,"to dance",phrase,U2_2C,General
44,"bailarín/ina",dancer,phrase,U5_LEX,General
45,baile,dance,noun,U6_10A,General
46,bajito/a,short,phrase,U5_9A,General
47,bajo,groundfloor,noun,U9_1A,General
48,balalaica,balalaika,noun,U1_7A,General
49,banco,bank,noun,U1_LEX,General
50,bandera,flag,noun,U2_10B,General
51,"bañador",swimsuit,noun,U4_3A,General
52,"bañarse","to goforaswim",phrase,U9_GyC,General
53,"Cádiz",Cadiz,phrase,U3_11B,General
54,"café",coffee,noun,U3_2A,General
55,"café con leche",coffeewithmilk,noun,U7_6A,General
56,"café solo",espresso,noun,U7_4C,General
57,cafetal,"coffee plantation",noun,U3_2A,General
58,"cajero automático",cashmachine,noun,U8_2A,General
59,"calabacín",courgette,noun,U7_LEX,General
60,calabaza,pumpkin,noun,U7_LEX,General
61,calamar,squid,noun,U7_2A,General
62,"calcetín",sock,noun,U4_13B,General
63,calidad,quality,noun,U3_2A,General
64,"cálido/a",warm,phrase,U3_LEX,General
65,caliente,hot,phrase,U6_11B,General
66,calle,street,noun,U1_1A,General
67,"calle peatonal","pedestrian street",noun,U8_3A,General
68,calmado/a,calm,phrase,U5_15C,General
69,calor,hot,noun,U3_4A,General
70,calvo/a,bald,phrase,U5_LEX,General
71,camarera,waitress,phrase,U1_4A,General
72,camello,camel,noun,U3_9,General
73,camino,road/journey,noun,U3_1,General
74,Caminode,"Way of Saint James",phrase,U1,General
75,"Santiago m camisa",shirt,noun,"U3_2A U4_2A",General
76,camiseta,t-shirt,noun,U4_2A,General
77,campamento,camping,noun,U5_8A,General
78,campo,countryside,noun,U3_2A,General
79,"Canadá",Canada,phrase,U1_3B,General
80,canadiense,Canadian,phrase,U1_3A,General
81,"canal de televisión","television channel",noun,U1_3C,General
82,"canción",song,noun,U2_11B,General
83,canela,cinnamon,noun,U7_8A,General
84,cansado/a,tired,phrase,U6_2A,General
85,cantante,singer,phrase,U5_4A,General
86,cantar,"to sing",phrase,U5_5A,General
87,cantidad,amount,noun,U3_6C,General
88,canto,song,noun,U6_LEX,General
89,"caña",smalldraughtbeer,noun,U2_13A,General
90,capital,capital,noun,U3_1,General
91,Caracas,Caracas,phrase,U3_GyC,General
92,"carácter",personality,noun,U5_14A,General
93,"característica",characteristics,noun,U7_12A,General
94,"cargador de móvil",phonecharger,noun,U4_3A,General
95,Caribe,Caribbean,phrase,U3_8C,General
96,"cariñoso/a",caring,phrase,U9_12C,General
97,carnaval,carnival,noun,U3_11B,General
98,carne,meat,noun,U3_5C,General
99,"carné de conducir","driving license",noun,U4_4A,General
100,"carné deidentidad",IDcard,noun,U4_3A,General
101,caro/a,expensive,phrase,U4_6A,General
102,carta,menu,noun,U7_GyC,General
103,"Cartagena de Indias",CartagenadeIndias,phrase,U2_10B,General
104,"casa f casa rural","house houseinthecountry",noun,U1_8C,General
105,"casado/a casarse","married togetmarried",phrase,"U5_7C U9_GyC",General
106,"casco antiguo",oldtown,noun,U3_2A,General
107,"clásico/a",classic,phrase,U4_6C,General
108,clave,key,noun,U6_12A,General
109,cliente/a,customer,phrase,U4_9A,General
110,clima,climate,noun,U3_3B,General
111,cobre,copper,noun,U3_3B,General
112,coche,car,noun,U2_4A,General
113,cocido,stew,noun,U7_10A,General
114,cocido/a,baked,phrase,U7_7A,General
115,"cocido madrileño",Madridstew,noun,U7_12A,General
116,"cocidomontañés",Cantabrianbeanstew,noun,U7_10A,General
117,cocinar,"to cook",phrase,U2_2A,General
118,cocinero/a,chef,phrase,U1_3A,General
119,colocar,"to place",phrase,U4_14A,General
120,Colombia,Colombia,phrase,U2_10B,General
121,colombiano/a,Colombian,phrase,U1_5A,General
122,colonia,colony,noun,U3_11B,General
123,ColoniaTovar,ColoniaTovar,phrase,U3_11B,General
124,colonial,colonial,phrase,U3_2A,General
125,color,colour,noun,U2_10B,General
126,comer,"to eat",phrase,U3_5C,General
127,comercial,"sales representative",phrase,U1_LEX,General
128,comerciante,shopkeeper,phrase,U9_11A,General
129,"comilón/ona",foodlover,phrase,U6_13A,General
130,como,like,phrase,U3_2A,General
131,"cómo",how,phrase,U3_6A,General
132,"¿cómo andas?","how are you doing?",phrase,U1_2A,General
133,"¿cómoeres?",whatareyoulike?,phrase,U6_2A,General
134,"¿cómoestás?",howareyou?,phrase,U0_3,General
135,"¿cómolotomas?",howdoyoutakeit?,phrase,U7_6B,General
136,"¿cómosedice...?",howdoyousay...?,phrase,U0_5A,General
137,"¿cómoseescribe ...?",howdoyouspell...?,phrase,U0_6,General
138,"¿cómose pronuncia...?",howdoyoupronounce...?,phrase,U0_5A,General
139,"¿cómotellamas?",whatisyourname?,phrase,U0_1A,General
140,comodidad,comfort,noun,U1,General
141,"cómodo/a",comfortable,phrase,"U9_3A U4_2A",General
142,"compañero/a",flatmate,phrase,U1,General
143,"compañero/a de trabajo",workcolleague,phrase,U1,General
144,compartir,"to share",phrase,"U2_3A U6_12A",General
145,"competición",competition,noun,U9_10B,General
146,compi,"flatmate (colloq.)",phrase,U9_7A,General
147,completamente,completely,noun,U3_11B,General
148,"composición compositor/a",composition,phrase,U9_6B,General
149,comprar,composer,phrase,"U5_LEX U4_9A",General
150,compras,shopping,noun,U2_2A,General
151,comprender,"to understand engagement",phrase,U2_6A,General
152,"compromiso m común",common,phrase,"U6_2A U2_3A",General
153,"comunicado/a comunicarse",communicated,phrase,U8_1A,General
154,comunicativo/a,talkative,phrase,U9_12C,General
155,comunidad,autonomouscommunity,phrase,U1,General
156,"¿cuánto cuesta?",howmuchdoesitcost?,phrase,U4_9A,General
157,"¿cuánto es?",howmuchisit?,phrase,U7_4A,General
158,"¿cuánto/a/os/as?",howmuch/howmany?,phrase,U3_6A,General
159,"¿cuántos años",howoldareyou?,phrase,U1,General
160,"tienes? cuatro",four,phrase,"U1_4B U6_11B",General
161,Cuba,Cuba,phrase,U0_4A,General
162,cubano/a,Cuban,phrase,U1_6,General
163,cuchara,spoon,noun,U7_LEX,General
164,cucharilla,teaspoon,noun,U7_LEX,General
165,cuchillo,knife,noun,U7_LEX,General
166,cuenta,bill,noun,U7_4A,General
167,cuidar,"to takecareof",phrase,U6_3A,General
168,cultural,cultural,phrase,U8_LEX,General
169,"cumpleaños",birthday,noun,U4_4C,General
170,curso,course,noun,U2_2A,General
171,cuy,Guineapig,noun,U3_GyC,General
172,"dar clases","to giveclasses",phrase,U9_11C,General
173,"darse cuenta","to realise",phrase,U9_5A,General
174,de,of,phrase,U1_1A,General
175,"de acuerdo","all right",phrase,U7_6B,General
176,"de cuadros",check,phrase,U4_5A,General
177,"¿de dóndeeres?","where are you from?",phrase,U1_4B,General
178,"de estilo colonial","Colonial style",phrase,U8_11D,General
179,"de fuera",fromoutside/foreign,phrase,U9_11A,General
180,"de primero","for firstcourse",phrase,U7_4A,General
181,"de rayas",stripy,phrase,U4_2A,General
182,"de segundo",forsecondcourse,phrase,U7_4A,General
183,"de todas partes","from everywhere",phrase,U3_2A,General
184,"de valor",valuable,phrase,U9_8A,General
185,decidir,"to decide",phrase,U4_14A,General
186,decir,"to say",verb,U2_3A,General
187,"decisión",decision,noun,U9_3A,General
188,declarado/a,declared,phrase,U3_14A,General
189,"dedicar tiempo","to spendtime(onsomething)",phrase,U6_2A,General
190,defecto,weakness/defect,noun,U9_4A,General
191,definir,"to define",phrase,U9_5A,General
192,dejar,"to leave",phrase,U9_3A,General
193,"dejarse algo","to forgetsomething",phrase,U9_5A,General
194,"del tiempo",atroomtemperature,phrase,U7_6A,General
195,delgado/a,thin,phrase,U5_LEX,General
196,demasiado/a,"to omuch",phrase,U8_3A,General
197,"deporte m deportista","sport athlete",phrase,U3_11B,General
198,derecha,right,noun,U8_5A,General
199,desayunar,"to havebreakfast",phrase,U6_2A,General
200,"descendiente desconectar","descendent todisconnect",phrase,"U3_11B U6_12A",General
201,desde,since/from,phrase,U3_2A,General
202,"desde hace",since,phrase,U9_3A,General
203,desear,"to wantsomething",phrase,U4_9A,General
204,desfile,parade,noun,U6_8A,General
205,elegante,elegant,phrase,U4_2A,General
206,elegido/a,chosen,phrase,U5_14A,General
207,elegir,"to choose",verb,U9_2B,General
208,"emblemático/a",iconic,phrase,U8_7A,General
209,embutido,curedmeat,noun,U7_2A,General
210,empanada,empanada,noun,U3_3B,General
211,empezar,"to start",phrase,U6_5B,General
212,empleado/a,employee,phrase,U4_13A,General
213,emprendedor/a,enterprising,phrase,U9_4A,General
214,empresa,company,noun,U1_LEX,General
215,empresade,telecommunicationscompany,phrase,U1_LEX,General
216,empresade,transportcompany,phrase,U1_LEX,General
217,en,in/on,phrase,U0_6,General
218,encambio,ontheotherhand,phrase,U8_9C,General
219,enforma,inshape,phrase,U6_2A,General
220,enpunto,"o'clock",phrase,U6_GyC,General
221,"¿en quétrabajas?",whatdoyoudoforaliving?,phrase,U1_4B,General
222,entodo,entirely,phrase,U3_5A,General
223,enventa,"for sale",phrase,U9_3A,General
224,enamorarsea,"to fall in love at first sight",phrase,U9_8A,General
225,encantar,"to love",phrase,U5_3A,General
226,encanto,charm,noun,U8_LEX,General
227,enchilada,enchilada,noun,U3_6A,General
228,"energía",energy,noun,U6_2A,General
229,"enfermería",nursing,noun,U9_12B,General
230,enfermero/a,nurse,phrase,U1_LEX,General
231,enfermo/a,ill,phrase,U9_11A,General
232,ensalada,salad,noun,U7_1A,General
233,"ensalada mixta",mixedsalad,noun,U7_3A,General
234,enseguida,comingrightup,phrase,U1,General
235,entrante,starter,noun,U7_12C,General
236,entresemana,duringtheweek,phrase,U6_2A,General
237,equipaje,luggage,noun,U9_7C,General
238,equipo,team,noun,U3_5A,General
239,equivocarse,"to bewrongabout",phrase,U9_5A,General
240,escolar,school,phrase,U4_13A,General
241,escribir,"to write",phrase,U5_3A,General
242,escuchar,"to listento",phrase,U2_2A,General
243,escuela,school,noun,U1_1A,General
244,escultura,sculpture,noun,U2_10B,General
245,ese/a,this,phrase,U0_4C,General
246,"España",Spain,phrase,U0_4A,General
247,especial,special,phrase,U4_15B,General
248,especializado/a,specialised,phrase,U7_6A,General
249,espectacular,spectacular,phrase,U9_3A,General
250,esperar,"to wait",phrase,U5_3A,General
251,espinaca,spinach,noun,U7_3A,General
252,"esquí",skiing,noun,U1_1A,General
253,esquiar,"to ski",phrase,U9_6C,General
254,esquina,corner,noun,U8_5A,General
255,establecimiento,establishment,noun,U7_GyC,General
256,"estación de metro",metrostation,phrase,U8_2A,General
257,final,end,noun,U3_2A,General
258,"físico/a",physical,phrase,U5_14A,General
259,flamenco,flamenco,noun,U1_7A,General
260,flan,eggcustard,noun,U7_3A,General
261,flor,flower,noun,U6_8A,General
262,forma,shape,noun,U3_11B,General
263,foto,photo,noun,U2_LEX,General
264,"fotografía",photography,noun,U5_3A,General
265,"fotógrafo/a",photographer,phrase,U9_LEX,General
266,"francés/esa",French,phrase,U1_3A,General
267,frecuencia,frequency,noun,U6_2A,General
268,fresa,strawberry,noun,U7_5C,General
269,fresco/a,fresh,phrase,U7_2A,General
270,frijoles,beans,noun,U7_LEX,General
271,"frío/a",cold,phrase,U3_3B,General
272,frito/a,fried,phrase,U7_3A,General
273,fruta,fruit,noun,U7_3A,General
274,frutadetemporada,"seasonal fruit",phrase,U7_3A,General
275,"frutos secos",nuts,noun,U7_5C,General
276,fuera,outside,phrase,U6_9D,General
277,fumar,"to smoke",phrase,U6_3A,General
278,fundado/a,founded,phrase,U3_2A,General
279,fundamental,fundamental,phrase,U7_7A,General
280,"fútbol",football,noun,U2_LEX,General
281,futuro,future,noun,U2_12A,General
282,"gafas de sol",sunglasses,noun,U4_3A,General
283,"galería",shoppingcentre,noun,U4_1A,General
284,Galicia,Galicia,phrase,U3_2A,General
285,galleta,biscuit,noun,U6_11B,General
286,gamba,prawn,noun,U7_1A,General
287,ganar,"to win",phrase,U3_6A,General
288,ganarunpremio,"to winaprize",phrase,U9_8A,General
289,garbanzos,chickpeas,noun,U7_LEX,General
290,gas,petrol,noun,U7_4A,General
291,gasolinera,"petrol station",noun,U8_LEX,General
292,gasto,expense,noun,U9_2B,General
293,"gastronómico/a",culinary,phrase,U4_12A,General
294,"gazpacho m",gazpacho,noun,U7_4A,General
295,"geldebaño",showergel,phrase,U4_3A,General
296,generoso/a,generous,phrase,U9_4A,General
297,gente,people,noun,U2_13B,General
298,"geográfico/a",geographical,phrase,U8_9A,General
299,"geólogo/a",geology,phrase,U9_2B,General
300,gimnasio,gym,noun,U1_LEX,General
301,"ginecólogo/a",gynaecologist,phrase,U1_6,General
302,girar,"to turn",phrase,U8_6C,General
303,girasol,sunflower,noun,U7_LEX,General
304,golf,golf,noun,U5_GyC,General
305,gordo/a,fat,phrase,U5_LEX,General
306,gorra,cap,noun,U4_5A,General
307,"gorro m Gotemburgo","hat Gothenburg",phrase,"U4_LEX U3_8B",General
308,gracias,thankyou,phrase,U0_6,General
309,horario,schedule,phrase,U6_2A,General
310,horno,oven,noun,U7_3A,General
311,hortaliza,vegetable,noun,U7_2A,General
312,hospital,hospital,noun,U1_LEX,General
313,hospitalidad,hospitality,noun,U8_9A,General
314,hostelero/a,hotelier,phrase,U9_11A,General
315,hotel,hotel,noun,U1_1A,General
316,hoy,"to day",phrase,U3_4A,General
317,huerto,vegetablegarden,noun,U9_3A,General
318,huevo,egg,noun,U7_2A,General
319,humanidad,humanity,noun,U3_2A,General
320,"húmedo/a",humid,phrase,U3_4A,General
321,humor,mood,noun,U6_2A,General
322,humus,hummus,noun,U7_2C,General
323,idea,Iberian,noun,U3_2A,General
324,ideal,"idea ideal",phrase,"U2_5A U1_1A",General
325,"infusión",tea,noun,U3_GyC,General
326,ingeniero/a,engineer,phrase,U1_6,General
327,Inglaterra,England,phrase,U3_8C,General
328,insociable,unsociable,phrase,U9_LEX,General
329,instrumento,musicalinstrument,phrase,U1,General
330,intercambio,exchange,noun,U2_LEX,General
331,m,"to urist interest",phrase,U3_2A,General
332,interesante,interesting,phrase,U2_5A,General
333,interior,interior,noun,U4_3A,General
334,intermediario/a,intermediary,phrase,U9_11A,General
335,internacional,international,phrase,U1,General
336,internet,internet,phrase,U2_11A,General
337,invierno,winter,noun,U3_8B,General
338,invitado/a,guest,phrase,U5_14B,General
339,irdecompras,"to goshopping",phrase,U2_2A,General
340,"ir de viaje","to gotravelling",phrase,U4_4A,General
341,"idioma iglesia","important late incredible independent Indian infographic information",noun,U8_3A,General
342,"ibérico/a identidad","impatient independence",noun,"U9_4A U3_4A",General
343,"f impaciente",church,phrase,U1,General
344,levantarse,"to getup",phrase,U6_1A,General
345,libro,book,noun,U0_5A,General
346,lila,purple,phrase,U4_LEX,General
347,Lima,Lima,phrase,U8_7E,General
348,"limón",lemon,noun,U7_6A,General
349,limpieza,cleanliness,noun,U8_9A,General
350,limpio/a,clean,phrase,U8_LEX,General
351,lindo/a,cute,phrase,U3_6A,General
352,"lingüista",linguist,phrase,U1_6,General
353,liso/a,straight,phrase,U5_LEX,General
354,lista,list,noun,U9_5A,General
355,literatura,literature,noun,U2_1A,General
356,llamado/a,called,phrase,U3_2A,General
357,llamarse,"to becalled",phrase,U3_2A,General
358,llave,key,noun,U9_2B,General
359,llegar,"to arrive",phrase,U3_2A,General
360,llevar,"to wear",phrase,U4_2A,General
361,llevar,"to have",phrase,U7_2A,General
362,llevarse,"to take",phrase,U4_9A,General
363,llover,"to rain",verb,U3_4A,General
364,lluvioso/a,rainy,phrase,U3_6A,General
365,"loquemás",themost,phrase,U8_4D,General
366,loquemenos,theleast,phrase,U8_4D,General
367,"lo siento Londres","I'm sorry London",phrase,"U0_6 U5_2B",General
368,luego,later,phrase,U3_4A,General
369,lugar,place,noun,U1_4B,General
370,lunes,Monday,noun,U3_4A,General
371,luz,light,noun,U5_2A,General
372,macarrones,macaroni,noun,U7_GyC,General
373,madre,mother,noun,U5_2A,General
374,Madrid,Madrid,phrase,U2_6A,General
375,"madrileño/a",personfromMadrid,phrase,U7_12A,General
376,madrugar,"to getupearly",phrase,U6_2A,General
377,maestro/a,teacher,phrase,U9_4C,General
378,"mágico/a",magical,phrase,U2_10B,General
379,"maíz",corn,noun,U2_10B,General
380,malcomunicado,poorlyconnected,phrase,U8_1A,General
381,"Málaga",Malaga,phrase,U1_1A,General
382,maleta,"suitcase Majorca",noun,U4_6C,General
383,manera,way,phrase,U7_7A,General
384,mangacorta,"short sleeve",noun,U4_2A,General
385,"mangalarga f maniático/a",longsleeve,phrase,U4_2A,General
386,mano,hand,noun,U4_1B,General
387,manzana,apple,noun,U7_LEX,General
388,"mañana",morning,phrase,U3_4A,General
389,"mapa m","map worldmap",noun,U3_1,General
390,"mapamundi mar m,f",sea,phrase,"U3_1 U5_3A",General
391,"f marido","brand husband",noun,"U4_2A U5_7A",General
392,marinero/a,sailor,phrase,U1,General
393,miles,thousands,phrase,U3_2A,General
394,"millón",million,phrase,U3_2A,General
395,minuto,minute,noun,U6_2A,General
396,mire,look,phrase,U4_9A,General
397,mismo/a,same,phrase,U3_2A,General
398,mixto/a,mixed,phrase,U7_3A,General
399,mochila,backpack,noun,U0_5A,General
400,moda,fashion,noun,U1_3A,General
401,modelo,model,phrase,U1_GyC,General
402,moderno/a,modern,phrase,U4_6C,General
403,modo,way,noun,U3_6C,General
404,"molino de viento",windmill,noun,U3_9,General
405,momento,moment,noun,U6_1A,General
406,moneda,currency,noun,U3_2A,General
407,"montaña",mountain,noun,U3_1,General
408,"montañés/esa",mountain,phrase,U7_10A,General
409,montar,"to setup",phrase,U9_3A,General
410,Montevideo,Montevideo,phrase,U3_13A,General
411,monumento,landmark,noun,U3_2A,General
412,moreno/a,dark-haired,phrase,U5_9A,General
413,mostaza,mustard,noun,U7_2A,General
414,"móvil",mobile,noun,U1_4B,General
415,"mucho muchos/as","alot many/alotof",phrase,"U2_11B U2_7B",General
416,mueble,furniture,noun,U8_8A,General
417,mujer,woman,noun,U4_2A,General
418,mundo,world,noun,U2_3A,General
419,"unos pódcast","some podcasts",phrase,U2_9A-Extra,General
420,museo,museum,noun,U1_1A,General
421,"música",music,noun,U2_1A,General
422,"música clásica",classicalmusic,noun,U5_4A,General
423,"música electrónica",electronicmusic,noun,U5_4A,General
424,"música envivo",livemusic,noun,U5_6A,General
425,"música",indiemusic,phrase,U1,General
426,"músicapop",popmusic,noun,U4_10A,General
427,"música soul",soulmusic,noun,U5_4A,General
428,musical,musical,phrase,U3_GyC,General
429,"músico/a",musician,phrase,U5_2A,General
430,muy,very,phrase,U2_5A,General
431,O,O,phrase,O,General
432,N,N,phrase,N,General
433,nacer,"to beborn",verb,U3_GyC,General
434,nachos,nachos,noun,U7_1A,General
435,nacimiento,birth,noun,U5_2A,General
436,"nacional nacionalidad","national nationality",noun,"U2_10B U1_3D",General
437,nadar,"to swim",phrase,U1,General
438,naranja,orange,noun,U7_LEX,General
439,"naranja nativo/a","orange native",phrase,"U4_LEX U2_LEX",General
440,natural,natural,phrase,U2_3A,General
441,"naturaleza f Navarra","nature Navarre",phrase,"U2_1A U9_11A",General
442,Navidad,Christmas,noun,U6_11B,General
443,olvidar,"to forget",phrase,U9_5A,General
444,"ópera",opera,noun,U5_5A,General
445,opinar,"to haveanopinionon",phrase,U9_2B,General
446,orden,order,noun,U6_11A,General
447,ordenador,computer,noun,U0_5A,General
448,"ordenadorportátil",laptop,noun,U4_3B,General
449,organizado/a,organised,phrase,U6_9A,General
450,organizar,"to organise",phrase,U9_3A,General
451,origen,origin,noun,U1_4B,General
452,original,original,phrase,U4_6C,General
453,oso,bear,noun,U3_9,General
454,Otavalo,Otavalo,phrase,U4_1A,General
455,"otoño",autumn,noun,U3_8B,General
456,otro/a,other,phrase,U0_2A,General
457,paciente,patient,phrase,U9_4A,General
458,"Pacífico",Pacific,noun,U3_4A,General
459,padre,father,noun,U5_1A,General
460,paella,paella,noun,U2_13A,General
461,"página",page,noun,U0_6,General
462,"páginaweb",website,noun,U2_7B,General
463,"país","country countryside",noun,U2_3A,General
464,paisaje,word,noun,U9_3A,General
465,palacio,"palace PalmadeMallorca",noun,U3_2A,General
466,"PalmadeMallorca palmera",palmtree,noun,"U4_1A U3_9",General
467,pan,bread,noun,U7_1A,General
468,panblanco,whitebread,noun,U7_LEX,General
469,"Estoy subiendo las escaleras","I am going up the stairs",phrase,M1-phrase,General
470,"Panamá",Panama,phrase,U7_9A,General
471,"pantalón",trousers,noun,U4_3A,General
472,pantalones,shorts,noun,U4_3A,General
473,papelera,bin,noun,U0_5A,General
474,para,for/inorderto,phrase,U2_9A,General
475,paraempezar,"to start",phrase,U7_3A,General
476,"paramí",forme,phrase,U4_2B,General
477,"parada de autobús",busstop,noun,U8_2A,General
478,paraguas,umbrella,noun,U9_5A,General
479,Paraguay,Paraguay,phrase,U3_GyC,General
480,"pareja f París","partner Paris",phrase,"U5_LEX U1_GyC",General
481,parking,carpark,noun,U8_2A,General
482,parque,park,noun,U2_10B,General
483,"parque nacional",nationalpark,noun,U2_10B,General
484,participar,"to participate",phrase,U6_8A,General
485,particular,particular,phrase,U6_8A,General
486,pasaporte,passport,noun,U4_4A,General
487,pasar,"to spend",phrase,U2_8A,General
488,"pasar de largo","to goby",phrase,U9_5A,General
489,"paseo m",walk,noun,U4_12A,General
490,"paseo acaballo pasión","horseriding passion",noun,"U4_12A U5_3A",General
491,pasta,pasta,noun,U7_1C,General
492,plato,dish,noun,U2_11A,General
493,"plato principal",maincourse,noun,U7_3A,General
494,"plato único",singlecourse,phrase,U7_12A,General
495,playa,beach,noun,U0_6,General
496,plaza,square,noun,U3_2A,General
497,pleno/a,full,phrase,U6_2A,General
498,"plurilingüe",plurilingual,phrase,U2_3A,General
499,"población",population,noun,U3_2A,General
500,poblado/a,populated,phrase,U3_2A,General
501,poco,little,phrase,U3_8C,General
502,poco/a/os/as,"little,few",phrase,U3_2A,General
503,podcast,podcast,noun,U2_LEX,General
504,poder,"to can",verb,U0_6,General
505,"podríamos",wecould,phrase,U0_6,General
506,poema,poem,noun,U5_10A,General
507,"poesía",poetry,noun,U9_2B,General
508,"policía",police,phrase,U1_GyC,General
509,polideportivo,"sports centre",noun,U8_2A,General
510,"polifacético/a",well-rounded,phrase,U9_12C,General
511,pollo,chicken,noun,U7_2A,General
512,poner,"to put",verb,U7_4A,General
513,poplatino,Latinpop,noun,U5_4A,General
514,pop-rock,poprock,noun,U5_4A,General
515,popular,popular,phrase,U3_6A,General
516,"por eso",forthatreason,phrase,U3_11B,General
517,"por favor",please,phrase,U0_6,General
518,"por fin",finally,phrase,U3_1,General
519,"porlamañana/",inthemorning/atnight,phrase,U1,General
520,"¿por qué?",why?,phrase,U2_12A,General
521,porque,because,phrase,U2_9C,General
522,"portátil",laptop,phrase,U4_3B,General
523,"portugués/esa",Portuguese,phrase,U1_GyC,General
524,postal,postcard,noun,U2_11A,General
525,postre,dessert,noun,U7_3A,General
526,"práctica",practice,noun,U6_12A,General
527,practicar,"to practice",phrase,U2_6A,General
528,"práctico/a",practical,phrase,U4_6C,General
529,precio,price,noun,U4_9A,General
530,precioso/a,beautiful,phrase,U3_4A,General
531,preferencia,preference,noun,U6_2A,General
532,preferido/a,favourite,phrase,U5_2A,General
533,preferir,"to prefer",verb,U4_GyC,General
534,pregunta,question,noun,U3_6A,General
535,preguntar,"to ask",phrase,U1_4B,General
536,premio,prize,noun,U6_13A,General
537,prenda,itemofclothing,noun,U4_15B,General
538,preparar,"to prepare",phrase,U6_8A,General
539,primavera,spring,noun,U3_8B,General
540,primero/a,first,phrase,U3_2A,General
541,primo/a,cousin,phrase,U5_1A,General
542,principal,main,phrase,U7_3A,General
543,probar,"to try",phrase,U7_12C,General
544,"producción",production,noun,U3_2A,General
545,producto,product,noun,U3_3B,General
546,productor,producer,noun,U3_7A,General
547,"profesión",profession,noun,U1,General
548,reparar,"to repair",phrase,U9_7A,General
549,repetir,"to repeat",verb,U0_6,General
550,repoblar,"to repopulate",verb,U9_3A,General
551,"República",DominicanRepublic,phrase,U1,General
552,res,beef,noun,U7_LEX,General
553,"reserva natural",naturereserve,noun,U3_11B,General
554,residencia,residence,noun,U2_12A,General
555,residencial,residential,phrase,U8_11D,General
556,responsable,responsible,phrase,U9_4A,General
557,respuesta,answer,noun,U6_2A,General
558,restaurante,restaurant,noun,U1_1A,General
559,restos,remains,noun,U3_2A,General
560,resultado,result,noun,U6_2A,General
561,reunirse,"to gettogether",phrase,U6_8A,General
562,"revisión médica",medicalcheck-up,noun,U9_11A,General
563,revista,magazine,noun,U2_4A,General
564,ribera,riverbed,noun,U8_3A,General
565,"río",river,noun,U3_LEX,General
566,"Río de Janeiro",RiodeJaneiro,phrase,U5_14B,General
567,rizado/a,curly,phrase,U5_9A,General
568,robar,"to steal",phrase,U5_2A,General
569,rojo/a,red,phrase,U1,General
570,"romántico/a",romantic,phrase,U4_6A,General
571,ropa,clothes,noun,"U5_15C U1_10B",General
572,"ropa interior",underwear,noun,U4_3A,General
573,rosa,pink,phrase,U4_2A,General
574,rosario,rosary,noun,U3_11B,General
575,rubio/a,fair-haired,phrase,U5_9A,General
576,ruidoso/a,noisy,phrase,U8_1A,General
577,ruinas,ruins,noun,U3_4A,General
578,rural,rural,phrase,U9_3A,General
579,ruso,Russian,noun,U2_LEX,General
580,"ruta gastronómica",foodtour,noun,U4_12A,General
581,rutina,routine,noun,U6_12A,General
582,"sábado",Saturday,noun,U2_2C,General
583,saber,"to know",phrase,U5_2A,General
584,sabor,taste,noun,U7_8A,General
585,"Sáhara",Sahara,phrase,U3_8C,General
586,sal,salt,noun,U3_11B,General
587,salado/a,savoury,phrase,U7_9C,General
588,salar,"salt flat",noun,U3_11B,General
589,"salchichas f, Pl",sausages,phrase,U7_LEX,General
590,salir,"to goout",verb,U2_2A,General
591,"salir acenar salir con amigos","to gooutfordinner togooutwithfriends",phrase,"U2_2C U2_2C",General
592,"salir de noche","to gooutatnight",phrase,U2_2C,General
593,"salmón",salmon,phrase,U7_3A,General
594,"salsa f",sauce,noun,U7_LEX,General
595,"salsa brava salteado/a","spicysauce sautéed",phrase,"U7_12A U7_7A",General
596,salto,waterfall,noun,U3_14C,General
597,"sobre todo",aboveall,phrase,U7_6B,General
598,sobrino/a,nephew/niece,phrase,U1,General
599,sociable,sociable,phrase,"U5_1A U5_3C",General
600,sol,sun,noun,U1_1A,General
601,solar,sun,phrase,U4_3A,General
602,soledad,solitude,noun,U2_1A,General
603,solo,alone,phrase,U5_8A,General
604,soltero/a,single,phrase,U5_7C,General
605,sopa,soup,noun,U7_3A,General
606,sostenible,sustainable,phrase,U4_15B,General
607,soy,Iam,phrase,U1_3A,General
608,soyyo,"It'sme",phrase,U1_2A,General
609,su,its,phrase,U3_1,General
610,sucio/a,dirty,phrase,U8_4A,General
611,"sueño",sleepy,noun,U6_2A,General
612,suficiente,enough,phrase,U7_7A,General
613,suizo/a,Swiss,phrase,U1_6,General
614,supermercado,supermarket,noun,U1_LEX,General
615,sur,south,noun,U3_3B,General
616,sureste,southeast,noun,U3_2A,General
617,surf,surfing,noun,U4_12A,General
618,suroeste,southwest,noun,U3_LEX,General
619,sushi,sushi,noun,U1_7A,General
620,tableta,tablet,noun,U0_5A,General
621,taco,taco,noun,U7_9A,General
622,"Tacuarembó",Tacuarembo,phrase,U3_13A,General
623,Tailandia,Thailand,phrase,U3_12C,General
624,talla,size,noun,U4_2A,General
625,taller,workshop,noun,U1_3C,General
626,tamal,tamale,noun,U3_4A,General
627,"también","to o",phrase,U1_5A,General
628,tampoco,either,phrase,U5_5A,General
629,tango,tango,noun,U1_7,General
630,Tanzania,Tanzania,phrase,U3_10B,General
631,tapa,tapa,noun,U2_13A,General
632,"tapón",earplugs,noun,U4_3B,General
633,tarde,afternoon,phrase,U0_6,General
634,tarde-noche,evening,noun,U6_2A,General
635,Tarifa,Tarifa,phrase,U3_8A,General
636,tarjeta,card,noun,U4_3A,General
637,"tarjeta de crédito","credit card",phrase,U4_3A,General
638,tarta,cake,noun,U7_5C,General
639,taxi,taxi,noun,U1_1A,General
640,taza,mug,noun,U7_LEX,General
641,"té",tea,noun,U5_GyC,General
642,teatro,theatre,phrase,U2_2A,General
643,"m tejido",material/cloth,noun,U4_2A,General
644,tela,fabric,noun,U9_10B,General
645,"teleférico","cable car telephone",noun,"U3_14C U1_4A",General
646,"teléfono m templado/a",mild,phrase,U1,General
647,templo,temple,noun,U3_4A,General
648,"temporada f temprano","seasonal early",phrase,U7_3A,General
649,tropical,tropical,phrase,U3_5A,General
650,"tú",you,phrase,U4_14A,General
651,turismo,"to urism",noun,U2_3A,General
652,turista,"to urist",phrase,U2_6A,General
653,"turístico/a","to urist",phrase,U3_2A,General
654,"U ubicación",location,noun,U3_2A,General
655,"últimamente",recently,phrase,U5_4A,General
656,"último/a",last,phrase,"U2_5A U5_3A",General
657,"¡un abrazo!",ahug!,phrase,U5_3C,General
658,unpoco,alittle,phrase,U1,General
659,"único/a universidad","only university",noun,"U5_7C U1_3C",General
660,universitario/a,"Universitystudent universe",phrase,U3_2A,General
661,unos/as,some,phrase,U3_4A,General
662,Uruguay,Uruguay,phrase,U0_4A,General
663,usado/a,used,phrase,U4_2A,General
664,usar,"to use",phrase,U4_GyC,General
665,usted,you(formal),phrase,U1,General
666,vacaciones,holidays,noun,U1_10B,General
667,vainilla,vanilla,noun,U7_5A,General
668,vale,OK,phrase,U0_6,General
669,valer,"to beworth",verb,U9_3A,General
670,valle,valley,noun,U3_2A,General
671,vallenato,"Vallenato (popular",noun,U1,General
672,vapor,"Colombianfolkmusic) steam",noun,"U2_10B U7_3A",General
673,vaqueros,jeans,noun,U4_2A,General
674,varios/as,several,phrase,U1,General
675,vaso,cup,noun,U7_LEX,General
676,vegano/a,vegan,phrase,U7_3A,General
677,vegetal,vegetable,phrase,U7_2A,General
678,vendedor/a,salesperson,phrase,U9_4C,General
679,vender,"to sell",phrase,U4_1A,General
680,venezolano/a,Venezuelan,phrase,U1_6,General
681,"Venezuela venido/a","Venezuela comefrom",phrase,"U0_4A U8_7A",General
682,venir,"to come",verb,U1_5A,General
683,ventana,window,noun,U0_6,General
684,"ver ver la televisión","to see/watch towatchtelevision",phrase,U2_2A,General
685,verano,summer,noun,U3_8B,General
686,"¿verdad?",right?,phrase,U3_9,General
687,verdad,true,noun,U4_6A,General
688,"verde verdura","green vegetable",noun,U7_2A,General
689,vestido,dress,noun,U4_7C,General
690,vestirse,"to getdressed",verb,U6_8A,General
691,vez,time,noun,U2_3A,General
692,viajar,"to travel",phrase,U2_3A,General
693,apasionado,passionate,phrase,U5_15C,General
694,argentino,Argentinian,phrase,U1_3A,General
695,camarero,waiter,phrase,U1_4A,General
696,arquitecto,architect,phrase,U1_6,General
697,panintegral,"wholemeal bread",noun,U7_LEX,General
698,"escribir con lápiz","to write with a pencil",noun,M1-phrase,General
699,"Subo las escaleras.","I climb the stairs",phrase,M1-phrase,General
700,"mundo hispano","Hispanic world",phrase,U3_7A,General
701,hispanohablantes,"Spanish speakers",adjective,U2_9A-Extra,General
702,"Escucho un pódcast para practicar español.","I listen to a podcast to practice Spanish",phrase,U2_9A-Extra,General
703,"los museos de la ciudad","the city museums",phrase,U2_9-extra,General
704,"los pódcast","the podcasts",phrase,U2_9A-Extra,General
705,"un pódcast","a podcast",phrase,U2_9A-Extra,General
706,"el periódico","the newspaper",noun,U1_LEX,General
707,"cocinar platos hispanos","to cook Hispanic dishes",verb,U2_9-extra,General
1 1 aparecer to appear verb U3_6A General
2 2 apasionada passionate phrase U5_15C General
3 3 apellido surname noun U1_4A General
4 4 aprender to learn phrase U2_2C General
5 5 aproximadamente approximately phrase U3_2A General
6 6 apuntar to note down phrase U9_5A General
7 7 aquí here phrase U3_2A General
8 8 aquí tiene here you go phrase U4_GyC General
9 9 árabe Arabic noun U2_LEX General
10 10 archivo file noun U9_7A General
11 11 área area noun U3_2A General
12 12 arena sand noun U3_4A General
13 13 arepa arepa noun U7_9A General
14 14 argentina Argentinian phrase U1_3A General
15 15 árido/a arid phrase U3_LEX General
16 16 arma weapon noun U3_2A General
17 17 arroz rice noun U7_LEX General
18 18 arquitecta architect phrase U1_6 General
19 19 arquitectura architecture noun U1_3A General
20 20 arte art noun U1_1A General
21 21 artesanal artisanal phrase U4_1A General
22 22 artesanía f artista crafts artist phrase U4_1A General
23 23 asado/a roasted phrase U7_7A General
24 24 Asia Asia phrase U3_LEX General
25 25 aspecto físico physical feature noun U5_14A General
26 26 aspecto aspect noun U3_11B General
27 27 Asunción Asuncion phrase U3_13A General
28 28 atención takenote phrase U4_14A General
29 29 atender to lookafter verb U9_3A General
30 30 atento/a attentive phrase U9_5A General
31 31 atlántico/a Atlantic phrase U3_6A General
32 32 atractivo/a attractive phrase U8_7A General
33 33 atraído/a attracted phrase U9_3A General
34 34 atún tuna noun U7_2A General
35 35 autobús bus noun U3_4A General
36 36 automático/a automatic phrase U8_2A General
37 37 autónomo/a self-employed phrase U3_2A General
38 38 avenida avenue noun U8_3A General
39 39 aventurero/a adventurous phrase U5_3C General
40 40 avión plane noun U6_GyC General
41 41 azúcar m azul sugar blue phrase U7_6A U4_2A General
42 42 azul claro light blue phrase U4_13A General
43 43 bailar to dance phrase U2_2C General
44 44 bailarín/ina dancer phrase U5_LEX General
45 45 baile dance noun U6_10A General
46 46 bajito/a short phrase U5_9A General
47 47 bajo groundfloor noun U9_1A General
48 48 balalaica balalaika noun U1_7A General
49 49 banco bank noun U1_LEX General
50 50 bandera flag noun U2_10B General
51 51 bañador swimsuit noun U4_3A General
52 52 bañarse to goforaswim phrase U9_GyC General
53 53 Cádiz Cadiz phrase U3_11B General
54 54 café coffee noun U3_2A General
55 55 café con leche coffeewithmilk noun U7_6A General
56 56 café solo espresso noun U7_4C General
57 57 cafetal coffee plantation noun U3_2A General
58 58 cajero automático cashmachine noun U8_2A General
59 59 calabacín courgette noun U7_LEX General
60 60 calabaza pumpkin noun U7_LEX General
61 61 calamar squid noun U7_2A General
62 62 calcetín sock noun U4_13B General
63 63 calidad quality noun U3_2A General
64 64 cálido/a warm phrase U3_LEX General
65 65 caliente hot phrase U6_11B General
66 66 calle street noun U1_1A General
67 67 calle peatonal pedestrian street noun U8_3A General
68 68 calmado/a calm phrase U5_15C General
69 69 calor hot noun U3_4A General
70 70 calvo/a bald phrase U5_LEX General
71 71 camarera waitress phrase U1_4A General
72 72 camello camel noun U3_9 General
73 73 camino road/journey noun U3_1 General
74 74 Caminode Way of Saint James phrase U1 General
75 75 Santiago m camisa shirt noun U3_2A U4_2A General
76 76 camiseta t-shirt noun U4_2A General
77 77 campamento camping noun U5_8A General
78 78 campo countryside noun U3_2A General
79 79 Canadá Canada phrase U1_3B General
80 80 canadiense Canadian phrase U1_3A General
81 81 canal de televisión television channel noun U1_3C General
82 82 canción song noun U2_11B General
83 83 canela cinnamon noun U7_8A General
84 84 cansado/a tired phrase U6_2A General
85 85 cantante singer phrase U5_4A General
86 86 cantar to sing phrase U5_5A General
87 87 cantidad amount noun U3_6C General
88 88 canto song noun U6_LEX General
89 89 caña smalldraughtbeer noun U2_13A General
90 90 capital capital noun U3_1 General
91 91 Caracas Caracas phrase U3_GyC General
92 92 carácter personality noun U5_14A General
93 93 característica characteristics noun U7_12A General
94 94 cargador de móvil phonecharger noun U4_3A General
95 95 Caribe Caribbean phrase U3_8C General
96 96 cariñoso/a caring phrase U9_12C General
97 97 carnaval carnival noun U3_11B General
98 98 carne meat noun U3_5C General
99 99 carné de conducir driving license noun U4_4A General
100 100 carné deidentidad IDcard noun U4_3A General
101 101 caro/a expensive phrase U4_6A General
102 102 carta menu noun U7_GyC General
103 103 Cartagena de Indias CartagenadeIndias phrase U2_10B General
104 104 casa f casa rural house houseinthecountry noun U1_8C General
105 105 casado/a casarse married togetmarried phrase U5_7C U9_GyC General
106 106 casco antiguo oldtown noun U3_2A General
107 107 clásico/a classic phrase U4_6C General
108 108 clave key noun U6_12A General
109 109 cliente/a customer phrase U4_9A General
110 110 clima climate noun U3_3B General
111 111 cobre copper noun U3_3B General
112 112 coche car noun U2_4A General
113 113 cocido stew noun U7_10A General
114 114 cocido/a baked phrase U7_7A General
115 115 cocido madrileño Madridstew noun U7_12A General
116 116 cocidomontañés Cantabrianbeanstew noun U7_10A General
117 117 cocinar to cook phrase U2_2A General
118 118 cocinero/a chef phrase U1_3A General
119 119 colocar to place phrase U4_14A General
120 120 Colombia Colombia phrase U2_10B General
121 121 colombiano/a Colombian phrase U1_5A General
122 122 colonia colony noun U3_11B General
123 123 ColoniaTovar ColoniaTovar phrase U3_11B General
124 124 colonial colonial phrase U3_2A General
125 125 color colour noun U2_10B General
126 126 comer to eat phrase U3_5C General
127 127 comercial sales representative phrase U1_LEX General
128 128 comerciante shopkeeper phrase U9_11A General
129 129 comilón/ona foodlover phrase U6_13A General
130 130 como like phrase U3_2A General
131 131 cómo how phrase U3_6A General
132 132 ¿cómo andas? how are you doing? phrase U1_2A General
133 133 ¿cómoeres? whatareyoulike? phrase U6_2A General
134 134 ¿cómoestás? howareyou? phrase U0_3 General
135 135 ¿cómolotomas? howdoyoutakeit? phrase U7_6B General
136 136 ¿cómosedice...? howdoyousay...? phrase U0_5A General
137 137 ¿cómoseescribe ...? howdoyouspell...? phrase U0_6 General
138 138 ¿cómose pronuncia...? howdoyoupronounce...? phrase U0_5A General
139 139 ¿cómotellamas? whatisyourname? phrase U0_1A General
140 140 comodidad comfort noun U1 General
141 141 cómodo/a comfortable phrase U9_3A U4_2A General
142 142 compañero/a flatmate phrase U1 General
143 143 compañero/a de trabajo workcolleague phrase U1 General
144 144 compartir to share phrase U2_3A U6_12A General
145 145 competición competition noun U9_10B General
146 146 compi flatmate (colloq.) phrase U9_7A General
147 147 completamente completely noun U3_11B General
148 148 composición compositor/a composition phrase U9_6B General
149 149 comprar composer phrase U5_LEX U4_9A General
150 150 compras shopping noun U2_2A General
151 151 comprender to understand engagement phrase U2_6A General
152 152 compromiso m común common phrase U6_2A U2_3A General
153 153 comunicado/a comunicarse communicated phrase U8_1A General
154 154 comunicativo/a talkative phrase U9_12C General
155 155 comunidad autonomouscommunity phrase U1 General
156 156 ¿cuánto cuesta? howmuchdoesitcost? phrase U4_9A General
157 157 ¿cuánto es? howmuchisit? phrase U7_4A General
158 158 ¿cuánto/a/os/as? howmuch/howmany? phrase U3_6A General
159 159 ¿cuántos años howoldareyou? phrase U1 General
160 160 tienes? cuatro four phrase U1_4B U6_11B General
161 161 Cuba Cuba phrase U0_4A General
162 162 cubano/a Cuban phrase U1_6 General
163 163 cuchara spoon noun U7_LEX General
164 164 cucharilla teaspoon noun U7_LEX General
165 165 cuchillo knife noun U7_LEX General
166 166 cuenta bill noun U7_4A General
167 167 cuidar to takecareof phrase U6_3A General
168 168 cultural cultural phrase U8_LEX General
169 169 cumpleaños birthday noun U4_4C General
170 170 curso course noun U2_2A General
171 171 cuy Guineapig noun U3_GyC General
172 172 dar clases to giveclasses phrase U9_11C General
173 173 darse cuenta to realise phrase U9_5A General
174 174 de of phrase U1_1A General
175 175 de acuerdo all right phrase U7_6B General
176 176 de cuadros check phrase U4_5A General
177 177 ¿de dóndeeres? where are you from? phrase U1_4B General
178 178 de estilo colonial Colonial style phrase U8_11D General
179 179 de fuera fromoutside/foreign phrase U9_11A General
180 180 de primero for firstcourse phrase U7_4A General
181 181 de rayas stripy phrase U4_2A General
182 182 de segundo forsecondcourse phrase U7_4A General
183 183 de todas partes from everywhere phrase U3_2A General
184 184 de valor valuable phrase U9_8A General
185 185 decidir to decide phrase U4_14A General
186 186 decir to say verb U2_3A General
187 187 decisión decision noun U9_3A General
188 188 declarado/a declared phrase U3_14A General
189 189 dedicar tiempo to spendtime(onsomething) phrase U6_2A General
190 190 defecto weakness/defect noun U9_4A General
191 191 definir to define phrase U9_5A General
192 192 dejar to leave phrase U9_3A General
193 193 dejarse algo to forgetsomething phrase U9_5A General
194 194 del tiempo atroomtemperature phrase U7_6A General
195 195 delgado/a thin phrase U5_LEX General
196 196 demasiado/a to omuch phrase U8_3A General
197 197 deporte m deportista sport athlete phrase U3_11B General
198 198 derecha right noun U8_5A General
199 199 desayunar to havebreakfast phrase U6_2A General
200 200 descendiente desconectar descendent todisconnect phrase U3_11B U6_12A General
201 201 desde since/from phrase U3_2A General
202 202 desde hace since phrase U9_3A General
203 203 desear to wantsomething phrase U4_9A General
204 204 desfile parade noun U6_8A General
205 205 elegante elegant phrase U4_2A General
206 206 elegido/a chosen phrase U5_14A General
207 207 elegir to choose verb U9_2B General
208 208 emblemático/a iconic phrase U8_7A General
209 209 embutido curedmeat noun U7_2A General
210 210 empanada empanada noun U3_3B General
211 211 empezar to start phrase U6_5B General
212 212 empleado/a employee phrase U4_13A General
213 213 emprendedor/a enterprising phrase U9_4A General
214 214 empresa company noun U1_LEX General
215 215 empresade telecommunicationscompany phrase U1_LEX General
216 216 empresade transportcompany phrase U1_LEX General
217 217 en in/on phrase U0_6 General
218 218 encambio ontheotherhand phrase U8_9C General
219 219 enforma inshape phrase U6_2A General
220 220 enpunto o'clock phrase U6_GyC General
221 221 ¿en quétrabajas? whatdoyoudoforaliving? phrase U1_4B General
222 222 entodo entirely phrase U3_5A General
223 223 enventa for sale phrase U9_3A General
224 224 enamorarsea to fall in love at first sight phrase U9_8A General
225 225 encantar to love phrase U5_3A General
226 226 encanto charm noun U8_LEX General
227 227 enchilada enchilada noun U3_6A General
228 228 energía energy noun U6_2A General
229 229 enfermería nursing noun U9_12B General
230 230 enfermero/a nurse phrase U1_LEX General
231 231 enfermo/a ill phrase U9_11A General
232 232 ensalada salad noun U7_1A General
233 233 ensalada mixta mixedsalad noun U7_3A General
234 234 enseguida comingrightup phrase U1 General
235 235 entrante starter noun U7_12C General
236 236 entresemana duringtheweek phrase U6_2A General
237 237 equipaje luggage noun U9_7C General
238 238 equipo team noun U3_5A General
239 239 equivocarse to bewrongabout phrase U9_5A General
240 240 escolar school phrase U4_13A General
241 241 escribir to write phrase U5_3A General
242 242 escuchar to listento phrase U2_2A General
243 243 escuela school noun U1_1A General
244 244 escultura sculpture noun U2_10B General
245 245 ese/a this phrase U0_4C General
246 246 España Spain phrase U0_4A General
247 247 especial special phrase U4_15B General
248 248 especializado/a specialised phrase U7_6A General
249 249 espectacular spectacular phrase U9_3A General
250 250 esperar to wait phrase U5_3A General
251 251 espinaca spinach noun U7_3A General
252 252 esquí skiing noun U1_1A General
253 253 esquiar to ski phrase U9_6C General
254 254 esquina corner noun U8_5A General
255 255 establecimiento establishment noun U7_GyC General
256 256 estación de metro metrostation phrase U8_2A General
257 257 final end noun U3_2A General
258 258 físico/a physical phrase U5_14A General
259 259 flamenco flamenco noun U1_7A General
260 260 flan eggcustard noun U7_3A General
261 261 flor flower noun U6_8A General
262 262 forma shape noun U3_11B General
263 263 foto photo noun U2_LEX General
264 264 fotografía photography noun U5_3A General
265 265 fotógrafo/a photographer phrase U9_LEX General
266 266 francés/esa French phrase U1_3A General
267 267 frecuencia frequency noun U6_2A General
268 268 fresa strawberry noun U7_5C General
269 269 fresco/a fresh phrase U7_2A General
270 270 frijoles beans noun U7_LEX General
271 271 frío/a cold phrase U3_3B General
272 272 frito/a fried phrase U7_3A General
273 273 fruta fruit noun U7_3A General
274 274 frutadetemporada seasonal fruit phrase U7_3A General
275 275 frutos secos nuts noun U7_5C General
276 276 fuera outside phrase U6_9D General
277 277 fumar to smoke phrase U6_3A General
278 278 fundado/a founded phrase U3_2A General
279 279 fundamental fundamental phrase U7_7A General
280 280 fútbol football noun U2_LEX General
281 281 futuro future noun U2_12A General
282 282 gafas de sol sunglasses noun U4_3A General
283 283 galería shoppingcentre noun U4_1A General
284 284 Galicia Galicia phrase U3_2A General
285 285 galleta biscuit noun U6_11B General
286 286 gamba prawn noun U7_1A General
287 287 ganar to win phrase U3_6A General
288 288 ganarunpremio to winaprize phrase U9_8A General
289 289 garbanzos chickpeas noun U7_LEX General
290 290 gas petrol noun U7_4A General
291 291 gasolinera petrol station noun U8_LEX General
292 292 gasto expense noun U9_2B General
293 293 gastronómico/a culinary phrase U4_12A General
294 294 gazpacho m gazpacho noun U7_4A General
295 295 geldebaño showergel phrase U4_3A General
296 296 generoso/a generous phrase U9_4A General
297 297 gente people noun U2_13B General
298 298 geográfico/a geographical phrase U8_9A General
299 299 geólogo/a geology phrase U9_2B General
300 300 gimnasio gym noun U1_LEX General
301 301 ginecólogo/a gynaecologist phrase U1_6 General
302 302 girar to turn phrase U8_6C General
303 303 girasol sunflower noun U7_LEX General
304 304 golf golf noun U5_GyC General
305 305 gordo/a fat phrase U5_LEX General
306 306 gorra cap noun U4_5A General
307 307 gorro m Gotemburgo hat Gothenburg phrase U4_LEX U3_8B General
308 308 gracias thankyou phrase U0_6 General
309 309 horario schedule phrase U6_2A General
310 310 horno oven noun U7_3A General
311 311 hortaliza vegetable noun U7_2A General
312 312 hospital hospital noun U1_LEX General
313 313 hospitalidad hospitality noun U8_9A General
314 314 hostelero/a hotelier phrase U9_11A General
315 315 hotel hotel noun U1_1A General
316 316 hoy to day phrase U3_4A General
317 317 huerto vegetablegarden noun U9_3A General
318 318 huevo egg noun U7_2A General
319 319 humanidad humanity noun U3_2A General
320 320 húmedo/a humid phrase U3_4A General
321 321 humor mood noun U6_2A General
322 322 humus hummus noun U7_2C General
323 323 idea Iberian noun U3_2A General
324 324 ideal idea ideal phrase U2_5A U1_1A General
325 325 infusión tea noun U3_GyC General
326 326 ingeniero/a engineer phrase U1_6 General
327 327 Inglaterra England phrase U3_8C General
328 328 insociable unsociable phrase U9_LEX General
329 329 instrumento musicalinstrument phrase U1 General
330 330 intercambio exchange noun U2_LEX General
331 331 m to urist interest phrase U3_2A General
332 332 interesante interesting phrase U2_5A General
333 333 interior interior noun U4_3A General
334 334 intermediario/a intermediary phrase U9_11A General
335 335 internacional international phrase U1 General
336 336 internet internet phrase U2_11A General
337 337 invierno winter noun U3_8B General
338 338 invitado/a guest phrase U5_14B General
339 339 irdecompras to goshopping phrase U2_2A General
340 340 ir de viaje to gotravelling phrase U4_4A General
341 341 idioma iglesia important late incredible independent Indian infographic information noun U8_3A General
342 342 ibérico/a identidad impatient independence noun U9_4A U3_4A General
343 343 f impaciente church phrase U1 General
344 344 levantarse to getup phrase U6_1A General
345 345 libro book noun U0_5A General
346 346 lila purple phrase U4_LEX General
347 347 Lima Lima phrase U8_7E General
348 348 limón lemon noun U7_6A General
349 349 limpieza cleanliness noun U8_9A General
350 350 limpio/a clean phrase U8_LEX General
351 351 lindo/a cute phrase U3_6A General
352 352 lingüista linguist phrase U1_6 General
353 353 liso/a straight phrase U5_LEX General
354 354 lista list noun U9_5A General
355 355 literatura literature noun U2_1A General
356 356 llamado/a called phrase U3_2A General
357 357 llamarse to becalled phrase U3_2A General
358 358 llave key noun U9_2B General
359 359 llegar to arrive phrase U3_2A General
360 360 llevar to wear phrase U4_2A General
361 361 llevar to have phrase U7_2A General
362 362 llevarse to take phrase U4_9A General
363 363 llover to rain verb U3_4A General
364 364 lluvioso/a rainy phrase U3_6A General
365 365 loquemás themost phrase U8_4D General
366 366 loquemenos theleast phrase U8_4D General
367 367 lo siento Londres I'm sorry London phrase U0_6 U5_2B General
368 368 luego later phrase U3_4A General
369 369 lugar place noun U1_4B General
370 370 lunes Monday noun U3_4A General
371 371 luz light noun U5_2A General
372 372 macarrones macaroni noun U7_GyC General
373 373 madre mother noun U5_2A General
374 374 Madrid Madrid phrase U2_6A General
375 375 madrileño/a personfromMadrid phrase U7_12A General
376 376 madrugar to getupearly phrase U6_2A General
377 377 maestro/a teacher phrase U9_4C General
378 378 mágico/a magical phrase U2_10B General
379 379 maíz corn noun U2_10B General
380 380 malcomunicado poorlyconnected phrase U8_1A General
381 381 Málaga Malaga phrase U1_1A General
382 382 maleta suitcase Majorca noun U4_6C General
383 383 manera way phrase U7_7A General
384 384 mangacorta short sleeve noun U4_2A General
385 385 mangalarga f maniático/a longsleeve phrase U4_2A General
386 386 mano hand noun U4_1B General
387 387 manzana apple noun U7_LEX General
388 388 mañana morning phrase U3_4A General
389 389 mapa m map worldmap noun U3_1 General
390 390 mapamundi mar m,f sea phrase U3_1 U5_3A General
391 391 f marido brand husband noun U4_2A U5_7A General
392 392 marinero/a sailor phrase U1 General
393 393 miles thousands phrase U3_2A General
394 394 millón million phrase U3_2A General
395 395 minuto minute noun U6_2A General
396 396 mire look phrase U4_9A General
397 397 mismo/a same phrase U3_2A General
398 398 mixto/a mixed phrase U7_3A General
399 399 mochila backpack noun U0_5A General
400 400 moda fashion noun U1_3A General
401 401 modelo model phrase U1_GyC General
402 402 moderno/a modern phrase U4_6C General
403 403 modo way noun U3_6C General
404 404 molino de viento windmill noun U3_9 General
405 405 momento moment noun U6_1A General
406 406 moneda currency noun U3_2A General
407 407 montaña mountain noun U3_1 General
408 408 montañés/esa mountain phrase U7_10A General
409 409 montar to setup phrase U9_3A General
410 410 Montevideo Montevideo phrase U3_13A General
411 411 monumento landmark noun U3_2A General
412 412 moreno/a dark-haired phrase U5_9A General
413 413 mostaza mustard noun U7_2A General
414 414 móvil mobile noun U1_4B General
415 415 mucho muchos/as alot many/alotof phrase U2_11B U2_7B General
416 416 mueble furniture noun U8_8A General
417 417 mujer woman noun U4_2A General
418 418 mundo world noun U2_3A General
419 419 unos pódcast some podcasts phrase U2_9A-Extra General
420 420 museo museum noun U1_1A General
421 421 música music noun U2_1A General
422 422 música clásica classicalmusic noun U5_4A General
423 423 música electrónica electronicmusic noun U5_4A General
424 424 música envivo livemusic noun U5_6A General
425 425 música indiemusic phrase U1 General
426 426 músicapop popmusic noun U4_10A General
427 427 música soul soulmusic noun U5_4A General
428 428 musical musical phrase U3_GyC General
429 429 músico/a musician phrase U5_2A General
430 430 muy very phrase U2_5A General
431 431 O O phrase O General
432 432 N N phrase N General
433 433 nacer to beborn verb U3_GyC General
434 434 nachos nachos noun U7_1A General
435 435 nacimiento birth noun U5_2A General
436 436 nacional nacionalidad national nationality noun U2_10B U1_3D General
437 437 nadar to swim phrase U1 General
438 438 naranja orange noun U7_LEX General
439 439 naranja nativo/a orange native phrase U4_LEX U2_LEX General
440 440 natural natural phrase U2_3A General
441 441 naturaleza f Navarra nature Navarre phrase U2_1A U9_11A General
442 442 Navidad Christmas noun U6_11B General
443 443 olvidar to forget phrase U9_5A General
444 444 ópera opera noun U5_5A General
445 445 opinar to haveanopinionon phrase U9_2B General
446 446 orden order noun U6_11A General
447 447 ordenador computer noun U0_5A General
448 448 ordenadorportátil laptop noun U4_3B General
449 449 organizado/a organised phrase U6_9A General
450 450 organizar to organise phrase U9_3A General
451 451 origen origin noun U1_4B General
452 452 original original phrase U4_6C General
453 453 oso bear noun U3_9 General
454 454 Otavalo Otavalo phrase U4_1A General
455 455 otoño autumn noun U3_8B General
456 456 otro/a other phrase U0_2A General
457 457 paciente patient phrase U9_4A General
458 458 Pacífico Pacific noun U3_4A General
459 459 padre father noun U5_1A General
460 460 paella paella noun U2_13A General
461 461 página page noun U0_6 General
462 462 páginaweb website noun U2_7B General
463 463 país country countryside noun U2_3A General
464 464 paisaje word noun U9_3A General
465 465 palacio palace PalmadeMallorca noun U3_2A General
466 466 PalmadeMallorca palmera palmtree noun U4_1A U3_9 General
467 467 pan bread noun U7_1A General
468 468 panblanco whitebread noun U7_LEX General
469 469 Estoy subiendo las escaleras I am going up the stairs phrase M1-phrase General
470 470 Panamá Panama phrase U7_9A General
471 471 pantalón trousers noun U4_3A General
472 472 pantalones shorts noun U4_3A General
473 473 papelera bin noun U0_5A General
474 474 para for/inorderto phrase U2_9A General
475 475 paraempezar to start phrase U7_3A General
476 476 paramí forme phrase U4_2B General
477 477 parada de autobús busstop noun U8_2A General
478 478 paraguas umbrella noun U9_5A General
479 479 Paraguay Paraguay phrase U3_GyC General
480 480 pareja f París partner Paris phrase U5_LEX U1_GyC General
481 481 parking carpark noun U8_2A General
482 482 parque park noun U2_10B General
483 483 parque nacional nationalpark noun U2_10B General
484 484 participar to participate phrase U6_8A General
485 485 particular particular phrase U6_8A General
486 486 pasaporte passport noun U4_4A General
487 487 pasar to spend phrase U2_8A General
488 488 pasar de largo to goby phrase U9_5A General
489 489 paseo m walk noun U4_12A General
490 490 paseo acaballo pasión horseriding passion noun U4_12A U5_3A General
491 491 pasta pasta noun U7_1C General
492 492 plato dish noun U2_11A General
493 493 plato principal maincourse noun U7_3A General
494 494 plato único singlecourse phrase U7_12A General
495 495 playa beach noun U0_6 General
496 496 plaza square noun U3_2A General
497 497 pleno/a full phrase U6_2A General
498 498 plurilingüe plurilingual phrase U2_3A General
499 499 población population noun U3_2A General
500 500 poblado/a populated phrase U3_2A General
501 501 poco little phrase U3_8C General
502 502 poco/a/os/as little,few phrase U3_2A General
503 503 podcast podcast noun U2_LEX General
504 504 poder to can verb U0_6 General
505 505 podríamos wecould phrase U0_6 General
506 506 poema poem noun U5_10A General
507 507 poesía poetry noun U9_2B General
508 508 policía police phrase U1_GyC General
509 509 polideportivo sports centre noun U8_2A General
510 510 polifacético/a well-rounded phrase U9_12C General
511 511 pollo chicken noun U7_2A General
512 512 poner to put verb U7_4A General
513 513 poplatino Latinpop noun U5_4A General
514 514 pop-rock poprock noun U5_4A General
515 515 popular popular phrase U3_6A General
516 516 por eso forthatreason phrase U3_11B General
517 517 por favor please phrase U0_6 General
518 518 por fin finally phrase U3_1 General
519 519 porlamañana/ inthemorning/atnight phrase U1 General
520 520 ¿por qué? why? phrase U2_12A General
521 521 porque because phrase U2_9C General
522 522 portátil laptop phrase U4_3B General
523 523 portugués/esa Portuguese phrase U1_GyC General
524 524 postal postcard noun U2_11A General
525 525 postre dessert noun U7_3A General
526 526 práctica practice noun U6_12A General
527 527 practicar to practice phrase U2_6A General
528 528 práctico/a practical phrase U4_6C General
529 529 precio price noun U4_9A General
530 530 precioso/a beautiful phrase U3_4A General
531 531 preferencia preference noun U6_2A General
532 532 preferido/a favourite phrase U5_2A General
533 533 preferir to prefer verb U4_GyC General
534 534 pregunta question noun U3_6A General
535 535 preguntar to ask phrase U1_4B General
536 536 premio prize noun U6_13A General
537 537 prenda itemofclothing noun U4_15B General
538 538 preparar to prepare phrase U6_8A General
539 539 primavera spring noun U3_8B General
540 540 primero/a first phrase U3_2A General
541 541 primo/a cousin phrase U5_1A General
542 542 principal main phrase U7_3A General
543 543 probar to try phrase U7_12C General
544 544 producción production noun U3_2A General
545 545 producto product noun U3_3B General
546 546 productor producer noun U3_7A General
547 547 profesión profession noun U1 General
548 548 reparar to repair phrase U9_7A General
549 549 repetir to repeat verb U0_6 General
550 550 repoblar to repopulate verb U9_3A General
551 551 República DominicanRepublic phrase U1 General
552 552 res beef noun U7_LEX General
553 553 reserva natural naturereserve noun U3_11B General
554 554 residencia residence noun U2_12A General
555 555 residencial residential phrase U8_11D General
556 556 responsable responsible phrase U9_4A General
557 557 respuesta answer noun U6_2A General
558 558 restaurante restaurant noun U1_1A General
559 559 restos remains noun U3_2A General
560 560 resultado result noun U6_2A General
561 561 reunirse to gettogether phrase U6_8A General
562 562 revisión médica medicalcheck-up noun U9_11A General
563 563 revista magazine noun U2_4A General
564 564 ribera riverbed noun U8_3A General
565 565 río river noun U3_LEX General
566 566 Río de Janeiro RiodeJaneiro phrase U5_14B General
567 567 rizado/a curly phrase U5_9A General
568 568 robar to steal phrase U5_2A General
569 569 rojo/a red phrase U1 General
570 570 romántico/a romantic phrase U4_6A General
571 571 ropa clothes noun U5_15C U1_10B General
572 572 ropa interior underwear noun U4_3A General
573 573 rosa pink phrase U4_2A General
574 574 rosario rosary noun U3_11B General
575 575 rubio/a fair-haired phrase U5_9A General
576 576 ruidoso/a noisy phrase U8_1A General
577 577 ruinas ruins noun U3_4A General
578 578 rural rural phrase U9_3A General
579 579 ruso Russian noun U2_LEX General
580 580 ruta gastronómica foodtour noun U4_12A General
581 581 rutina routine noun U6_12A General
582 582 sábado Saturday noun U2_2C General
583 583 saber to know phrase U5_2A General
584 584 sabor taste noun U7_8A General
585 585 Sáhara Sahara phrase U3_8C General
586 586 sal salt noun U3_11B General
587 587 salado/a savoury phrase U7_9C General
588 588 salar salt flat noun U3_11B General
589 589 salchichas f, Pl sausages phrase U7_LEX General
590 590 salir to goout verb U2_2A General
591 591 salir acenar salir con amigos to gooutfordinner togooutwithfriends phrase U2_2C U2_2C General
592 592 salir de noche to gooutatnight phrase U2_2C General
593 593 salmón salmon phrase U7_3A General
594 594 salsa f sauce noun U7_LEX General
595 595 salsa brava salteado/a spicysauce sautéed phrase U7_12A U7_7A General
596 596 salto waterfall noun U3_14C General
597 597 sobre todo aboveall phrase U7_6B General
598 598 sobrino/a nephew/niece phrase U1 General
599 599 sociable sociable phrase U5_1A U5_3C General
600 600 sol sun noun U1_1A General
601 601 solar sun phrase U4_3A General
602 602 soledad solitude noun U2_1A General
603 603 solo alone phrase U5_8A General
604 604 soltero/a single phrase U5_7C General
605 605 sopa soup noun U7_3A General
606 606 sostenible sustainable phrase U4_15B General
607 607 soy Iam phrase U1_3A General
608 608 soyyo It'sme phrase U1_2A General
609 609 su its phrase U3_1 General
610 610 sucio/a dirty phrase U8_4A General
611 611 sueño sleepy noun U6_2A General
612 612 suficiente enough phrase U7_7A General
613 613 suizo/a Swiss phrase U1_6 General
614 614 supermercado supermarket noun U1_LEX General
615 615 sur south noun U3_3B General
616 616 sureste southeast noun U3_2A General
617 617 surf surfing noun U4_12A General
618 618 suroeste southwest noun U3_LEX General
619 619 sushi sushi noun U1_7A General
620 620 tableta tablet noun U0_5A General
621 621 taco taco noun U7_9A General
622 622 Tacuarembó Tacuarembo phrase U3_13A General
623 623 Tailandia Thailand phrase U3_12C General
624 624 talla size noun U4_2A General
625 625 taller workshop noun U1_3C General
626 626 tamal tamale noun U3_4A General
627 627 también to o phrase U1_5A General
628 628 tampoco either phrase U5_5A General
629 629 tango tango noun U1_7 General
630 630 Tanzania Tanzania phrase U3_10B General
631 631 tapa tapa noun U2_13A General
632 632 tapón earplugs noun U4_3B General
633 633 tarde afternoon phrase U0_6 General
634 634 tarde-noche evening noun U6_2A General
635 635 Tarifa Tarifa phrase U3_8A General
636 636 tarjeta card noun U4_3A General
637 637 tarjeta de crédito credit card phrase U4_3A General
638 638 tarta cake noun U7_5C General
639 639 taxi taxi noun U1_1A General
640 640 taza mug noun U7_LEX General
641 641 tea noun U5_GyC General
642 642 teatro theatre phrase U2_2A General
643 643 m tejido material/cloth noun U4_2A General
644 644 tela fabric noun U9_10B General
645 645 teleférico cable car telephone noun U3_14C U1_4A General
646 646 teléfono m templado/a mild phrase U1 General
647 647 templo temple noun U3_4A General
648 648 temporada f temprano seasonal early phrase U7_3A General
649 649 tropical tropical phrase U3_5A General
650 650 you phrase U4_14A General
651 651 turismo to urism noun U2_3A General
652 652 turista to urist phrase U2_6A General
653 653 turístico/a to urist phrase U3_2A General
654 654 U ubicación location noun U3_2A General
655 655 últimamente recently phrase U5_4A General
656 656 último/a last phrase U2_5A U5_3A General
657 657 ¡un abrazo! ahug! phrase U5_3C General
658 658 unpoco alittle phrase U1 General
659 659 único/a universidad only university noun U5_7C U1_3C General
660 660 universitario/a Universitystudent universe phrase U3_2A General
661 661 unos/as some phrase U3_4A General
662 662 Uruguay Uruguay phrase U0_4A General
663 663 usado/a used phrase U4_2A General
664 664 usar to use phrase U4_GyC General
665 665 usted you(formal) phrase U1 General
666 666 vacaciones holidays noun U1_10B General
667 667 vainilla vanilla noun U7_5A General
668 668 vale OK phrase U0_6 General
669 669 valer to beworth verb U9_3A General
670 670 valle valley noun U3_2A General
671 671 vallenato Vallenato (popular noun U1 General
672 672 vapor Colombianfolkmusic) steam noun U2_10B U7_3A General
673 673 vaqueros jeans noun U4_2A General
674 674 varios/as several phrase U1 General
675 675 vaso cup noun U7_LEX General
676 676 vegano/a vegan phrase U7_3A General
677 677 vegetal vegetable phrase U7_2A General
678 678 vendedor/a salesperson phrase U9_4C General
679 679 vender to sell phrase U4_1A General
680 680 venezolano/a Venezuelan phrase U1_6 General
681 681 Venezuela venido/a Venezuela comefrom phrase U0_4A U8_7A General
682 682 venir to come verb U1_5A General
683 683 ventana window noun U0_6 General
684 684 ver ver la televisión to see/watch towatchtelevision phrase U2_2A General
685 685 verano summer noun U3_8B General
686 686 ¿verdad? right? phrase U3_9 General
687 687 verdad true noun U4_6A General
688 688 verde verdura green vegetable noun U7_2A General
689 689 vestido dress noun U4_7C General
690 690 vestirse to getdressed verb U6_8A General
691 691 vez time noun U2_3A General
692 692 viajar to travel phrase U2_3A General
693 693 apasionado passionate phrase U5_15C General
694 694 argentino Argentinian phrase U1_3A General
695 695 camarero waiter phrase U1_4A General
696 696 arquitecto architect phrase U1_6 General
697 697 panintegral wholemeal bread noun U7_LEX General
698 698 escribir con lápiz to write with a pencil noun M1-phrase General
699 699 Subo las escaleras. I climb the stairs phrase M1-phrase General
700 700 mundo hispano Hispanic world phrase U3_7A General
701 701 hispanohablantes Spanish speakers adjective U2_9A-Extra General
702 702 Escucho un pódcast para practicar español. I listen to a podcast to practice Spanish phrase U2_9A-Extra General
703 703 los museos de la ciudad the city museums phrase U2_9-extra General
704 704 los pódcast the podcasts phrase U2_9A-Extra General
705 705 un pódcast a podcast phrase U2_9A-Extra General
706 706 el periódico the newspaper noun U1_LEX General
707 707 cocinar platos hispanos to cook Hispanic dishes verb U2_9-extra General

546
main.py
View file

@ -9,6 +9,8 @@ from PyQt6.QtWidgets import (
from PyQt6.QtCore import Qt, QUrl from PyQt6.QtCore import Qt, QUrl
from PyQt6.QtMultimedia import QMediaPlayer, QAudioOutput from PyQt6.QtMultimedia import QMediaPlayer, QAudioOutput
from PyQt6.QtGui import QFont from PyQt6.QtGui import QFont
# Internal Project Module Imports
from database.connection import init_db, get_connection from database.connection import init_db, get_connection
from core.bulk_importer import BulkImporter from core.bulk_importer import BulkImporter
from core.clean_glossary import GlossaryCleaner from core.clean_glossary import GlossaryCleaner
@ -17,146 +19,226 @@ class SpanishTrainerApp(QMainWindow):
def __init__(self): def __init__(self):
super().__init__() super().__init__()
self.setWindowTitle("Castilian Voice Trainer Pro") self.setWindowTitle("Castilian Voice Trainer Pro")
self.setMinimumSize(1000, 650) self.setMinimumSize(1150, 700)
# 1. Initialize and Seed Database if Empty # 1. Initialize schema structures and check ingestion status
self.ensure_database_populated() self.ensure_database_populated()
# 2. Initialize PyQt Multimedia Audio Engine Components # Audio Player Architecture Setup
self.media_player = QMediaPlayer() self.media_player = QMediaPlayer()
self.audio_output = QAudioOutput() self.audio_output = QAudioOutput()
self.media_player.setAudioOutput(self.audio_output) self.media_player.setAudioOutput(self.audio_output)
self.current_flashcard_id = None self.current_flashcard_id = None
# 3. Build UI Components # Central Main Window Tabs Interface
self.tabs = QTabWidget() self.tabs = QTabWidget()
self.setCentralWidget(self.tabs) self.setCentralWidget(self.tabs)
self.init_phrase_sandbox_tab() self.init_phrase_sandbox_tab()
self.init_flashcard_reviewer_tab() self.init_flashcard_reviewer_tab()
# 4. Initial Load of Database Data into Grid View # 2. Populate unified database rows into interface layout grid
self.refresh_crud_table() self.refresh_crud_table()
def ensure_database_populated(self): def ensure_database_populated(self):
"""Verifies if the database exists and has records; seeds it from the PDF if empty.""" """Forces database configuration structure and triggers pipeline execution if empty."""
print("🗄️ Verification Pass: Running schema configuration scripts...")
init_db() init_db()
conn = get_connection() conn = get_connection()
cursor = conn.cursor() cursor = conn.cursor()
# Verify if the translations table has rows populated
try: try:
cursor.execute("SELECT COUNT(*) FROM phrases") cursor.execute("SELECT COUNT(*) FROM translations")
count = cursor.fetchone()[0] count = cursor.fetchone()[0]
except Exception: print(f"📊 Current Translation Pairs found in database: {count}")
except Exception as e:
print(f"⚠️ Table check encountered an issue (likely empty tables): {e}")
count = 0 count = 0
finally:
conn.close() conn.close()
if count == 0: if count == 0:
print("🗄️ Database appears empty. Running the structural reconstruction pipeline...") print("🗄️ Database tables are empty. Triggering glossary reader pipeline...")
pdf_file = "aula_int_plus_1_glos_en_alfa.pdf" pdf_file = "aula_int_plus_1_glos_en_alfa.pdf"
textbook = "Aula Internacional Plus 1"
if os.path.exists(pdf_file): if os.path.exists(pdf_file):
# Run the layout parser staging execution pass
importer = BulkImporter() importer = BulkImporter()
importer.import_pdf_glossary(pdf_file, textbook) importer.import_pdf_glossary(pdf_file, "Aula Internacional Plus 1")
# Clean, pair up English/Spanish, and insert the final rows
cleaner = GlossaryCleaner() cleaner = GlossaryCleaner()
cleaner.process_database_clean() cleaner.process_database_clean()
print("Database successfully seeded with metadata-parsed entries.") print("Ingestion pipeline processing sequence successfully completed.")
else: else:
print(f"⚠️ Warning: Could not find '{pdf_file}' to automatically seed records.") print(f"❌ Error: Source document '{pdf_file}' is missing from the directory root.")
# ===================================================================== # =====================================================================
# 🗄️ TAB 1: PHRASE SANDBOX (CRUD Panel) # 🗄️ TAB 1: TRANSLATION-CENTRIC PHRASE SANDBOX (CRUD)
# ===================================================================== # =====================================================================
def init_phrase_sandbox_tab(self): def init_phrase_sandbox_tab(self):
tab = QWidget() tab = QWidget()
layout = QHBoxLayout(tab) layout = QHBoxLayout(tab)
# --- LEFT SIDE PANEL: Filter Controls & Unified Data Grid ---
left_panel = QVBoxLayout() left_panel = QVBoxLayout()
search_layout = QHBoxLayout()
search_layout.addWidget(QLabel("🔍 Filter text:"))
self.search_input = QLineEdit()
self.search_input.textChanged.connect(self.refresh_crud_table)
search_layout.addWidget(self.search_input)
left_panel.addLayout(search_layout)
self.phrase_table = QTableWidget() filter_layout = QHBoxLayout()
self.phrase_table.setColumnCount(6) filter_layout.addWidget(QLabel("🔍 Text Filter:"))
self.phrase_table.setHorizontalHeaderLabels(["ID", "Text", "Lang", "Type", "Voice Gender", "Deck Tag"]) self.search_text_input = QLineEdit()
self.phrase_table.itemSelectionChanged.connect(self.handle_table_row_select) self.search_text_input.setPlaceholderText("Search Spanish or English text blocks...")
left_panel.addWidget(self.phrase_table) self.search_text_input.textChanged.connect(self.refresh_crud_table)
filter_layout.addWidget(self.search_text_input)
filter_layout.addWidget(QLabel("📂 Context:"))
self.search_context_input = QLineEdit()
self.search_context_input.setPlaceholderText("e.g. U2 or U8_5A")
self.search_context_input.setMaximumWidth(130)
self.search_context_input.textChanged.connect(self.refresh_crud_table)
filter_layout.addWidget(self.search_context_input)
left_panel.addLayout(filter_layout)
# Unified Translations Row Table Matrix
self.translation_table = QTableWidget()
self.translation_table.setColumnCount(6)
self.translation_table.setHorizontalHeaderLabels([
"TX ID", "Spanish Phrase", "English Translation", "Type", "Source Context", "Deck Assignment"
])
self.translation_table.itemSelectionChanged.connect(self.handle_table_row_select)
left_panel.addWidget(self.translation_table)
# Row Pointer Navigation Steppers
nav_layout = QHBoxLayout()
self.btn_row_up = QPushButton("🔼 Previous Pair")
self.btn_row_down = QPushButton("🔽 Next Pair")
self.btn_row_up.clicked.connect(lambda: self.step_table_row(-1))
self.btn_row_down.clicked.connect(lambda: self.step_table_row(1))
nav_layout.addWidget(self.btn_row_up)
nav_layout.addWidget(self.btn_row_down)
left_panel.addLayout(nav_layout)
# --- RIGHT SIDE PANEL: Side-by-Side Unified Twin Form Box Views ---
right_panel = QVBoxLayout() right_panel = QVBoxLayout()
form_frame = QFrame() form_frame = QFrame()
form_frame.setFrameShape(QFrame.Shape.StyledPanel) form_frame.setFrameShape(QFrame.Shape.StyledPanel)
form_layout = QFormLayout(form_frame) form_layout = QFormLayout(form_frame)
self.input_id = QLineEdit() self.input_tx_id = QLineEdit()
self.input_id.setReadOnly(True) self.input_tx_id.setReadOnly(True)
self.input_id.setPlaceholderText("Auto-assigned ID") self.input_tx_id.setPlaceholderText("Auto-Increment ID")
self.input_text = QTextEdit() self.input_text_es = QTextEdit()
self.input_text.setMaximumHeight(60) self.input_text_es.setMaximumHeight(75)
self.combo_lang = QComboBox() self.input_text_en = QTextEdit()
self.combo_lang.addItems(["es", "en"]) self.input_text_en.setMaximumHeight(75)
self.combo_type = QComboBox() self.combo_type = QComboBox()
self.combo_type.addItems(["sentence", "phrase", "noun", "verb", "adjective"]) self.combo_type.addItems(["phrase", "sentence", "noun", "verb", "adjective"])
self.combo_voice_gender = QComboBox() self.input_context = QLineEdit()
self.combo_voice_gender.addItems(["female", "male"]) self.input_context.setPlaceholderText("e.g., U8_5A")
self.slider_base_speed = QSlider(Qt.Orientation.Horizontal)
self.slider_base_speed.setMinimum(50)
self.slider_base_speed.setMaximum(150)
self.slider_base_speed.setValue(100)
self.lbl_base_speed = QLabel("1.00x")
self.slider_base_speed.valueChanged.connect(lambda v: self.lbl_base_speed.setText(f"{v/100:.2f}x"))
speed_box = QHBoxLayout()
speed_box.addWidget(self.slider_base_speed)
speed_box.addWidget(self.lbl_base_speed)
self.input_deck_tag = QLineEdit() self.input_deck_tag = QLineEdit()
self.input_deck_tag.setPlaceholderText("e.g., Anki_Unit_1") self.input_deck_tag.setPlaceholderText("Anki Sub-deck Hierarchy")
form_layout.addRow("Phrase ID Resource:", self.input_id) # Common layout stylesheet for the utility buttons
form_layout.addRow(QLabel("<b>Target Conversational Text:</b>")) button_qss = """
form_layout.addRow(self.input_text) QPushButton {
form_layout.addRow("Language Accent:", self.combo_lang) background-color: #f0f0f0;
form_layout.addRow("Grammar Type Classification:", self.combo_type) border: 1px solid #c0c0c0;
form_layout.addRow("<b>Preferred Voice Gender:</b>", self.combo_voice_gender) border-radius: 4px;
form_layout.addRow("<b>Default Playback Speed:</b>", speed_box) font-size: 11px;
form_layout.addRow("Deck / Group Identifier Tag:", self.input_deck_tag) font-weight: bold;
color: #333333;
}
QPushButton:hover {
background-color: #e0e0e0;
border: 1px solid #a0a0a0;
}
QPushButton:pressed {
background-color: #d0d0d0;
}
"""
# 🔊 1. Spanish Header Layout with Speed Controls
es_header_layout = QHBoxLayout()
es_header_layout.setContentsMargins(0, 5, 0, 5)
es_header_layout.addWidget(QLabel("<b>🇪🇸 Castilian Spanish Text Element:</b>"))
self.btn_play_sandbox_es = QPushButton("Play 🔊")
self.btn_play_sandbox_es.setFixedWidth(75)
self.btn_play_sandbox_es.setFixedHeight(24)
self.btn_play_sandbox_es.setStyleSheet(button_qss)
self.btn_play_sandbox_es.clicked.connect(self.handle_sandbox_play_es)
es_header_layout.addWidget(self.btn_play_sandbox_es)
self.combo_speed_es = QComboBox()
self.combo_speed_es.addItems(["0.50x", "0.75x", "1.00x", "1.25x", "1.50x"])
self.combo_speed_es.setCurrentText("1.00x")
self.combo_speed_es.setFixedWidth(70)
self.combo_speed_es.setFixedHeight(24)
es_header_layout.addWidget(self.combo_speed_es)
es_header_layout.addStretch()
# 🔊 2. English Header Layout Activated with Speed Controls
en_header_layout = QHBoxLayout()
en_header_layout.setContentsMargins(0, 5, 0, 5)
en_header_layout.addWidget(QLabel("<b>🇬🇧 English Target Translation:</b>"))
self.btn_play_sandbox_en = QPushButton("Play 🔊")
self.btn_play_sandbox_en.setFixedWidth(75)
self.btn_play_sandbox_en.setFixedHeight(24)
self.btn_play_sandbox_en.setStyleSheet(button_qss)
self.btn_play_sandbox_en.clicked.connect(self.handle_sandbox_play_en)
en_header_layout.addWidget(self.btn_play_sandbox_en)
self.combo_speed_en = QComboBox()
self.combo_speed_en.addItems(["0.50x", "0.75x", "1.00x", "1.25x", "1.50x"])
self.combo_speed_en.setCurrentText("1.00x")
self.combo_speed_en.setFixedWidth(70)
self.combo_speed_en.setFixedHeight(24)
en_header_layout.addWidget(self.combo_speed_en)
en_header_layout.addStretch()
# Mount elements sequentially into the form frame mapping structure
form_layout.addRow("<b>Translation Link ID:</b>", self.input_tx_id)
form_layout.addRow(es_header_layout)
form_layout.addRow(self.input_text_es)
form_layout.addRow(en_header_layout)
form_layout.addRow(self.input_text_en)
form_layout.addRow("Classification Profile:", self.combo_type)
form_layout.addRow("Source Context ID (Raw):", self.input_context)
form_layout.addRow("<b>Target Deck Scope:</b>", self.input_deck_tag)
crud_buttons = QHBoxLayout() crud_buttons = QHBoxLayout()
self.btn_save = QPushButton(" Save New") self.btn_save = QPushButton(" Create Pair")
self.btn_update = QPushButton("💾 Update Entry") self.btn_update = QPushButton("💾 Update Node")
self.btn_delete = QPushButton("🗑️ Delete") self.btn_delete = QPushButton("🗑️ Sever Link")
self.btn_save.clicked.connect(self.crud_create) self.btn_save.clicked.connect(self.crud_create_pair)
self.btn_update.clicked.connect(self.crud_update) self.btn_update.clicked.connect(self.crud_update_pair)
self.btn_delete.clicked.connect(self.crud_delete) self.btn_delete.clicked.connect(self.crud_delete_pair)
crud_buttons.addWidget(self.btn_save) crud_buttons.addWidget(self.btn_save)
crud_buttons.addWidget(self.btn_update) crud_buttons.addWidget(self.btn_update)
crud_buttons.addWidget(self.btn_delete) crud_buttons.addWidget(self.btn_delete)
right_panel.addWidget(QLabel("<h3>Configure Phrase & Voice Variables</h3>")) right_panel.addWidget(QLabel("<h3>Translation Node Management Matrix</h3>"))
right_panel.addWidget(form_frame) right_panel.addWidget(form_frame)
right_panel.addLayout(crud_buttons) right_panel.addLayout(crud_buttons)
right_panel.addStretch() right_panel.addStretch()
layout.addLayout(left_panel, stretch=3) layout.addLayout(left_panel, stretch=4)
layout.addLayout(right_panel, stretch=2) layout.addLayout(right_panel, stretch=3)
self.tabs.addTab(tab, "🗄️ Phrase Sandbox (CRUD)") self.tabs.addTab(tab, "🗄️ Phrase Sandbox (CRUD)")
# ===================================================================== # =====================================================================
# 🃏 TAB 2: FLASHCARD STUDY PLAYER # 🃏 TAB 2: FLASHCARD STUDY MODULE
# ===================================================================== # =====================================================================
def init_flashcard_reviewer_tab(self): def init_flashcard_reviewer_tab(self):
tab = QWidget() tab = QWidget()
@ -166,11 +248,11 @@ class SpanishTrainerApp(QMainWindow):
card_frame.setStyleSheet("background-color: #ffffff; border: 2px solid #bdc3c7; border-radius: 12px;") card_frame.setStyleSheet("background-color: #ffffff; border: 2px solid #bdc3c7; border-radius: 12px;")
card_layout = QVBoxLayout(card_frame) card_layout = QVBoxLayout(card_frame)
self.lbl_card_text = QLabel("Click 'Load Next' to study sentences...") self.lbl_card_text = QLabel("Select 'Next Card' to initiate study sequence...")
self.lbl_card_text.setAlignment(Qt.AlignmentFlag.AlignCenter) self.lbl_card_text.setAlignment(Qt.AlignmentFlag.AlignCenter)
self.lbl_card_text.setFont(QFont("Arial", 22, QFont.Weight.Bold)) self.lbl_card_text.setFont(QFont("Arial", 22, QFont.Weight.Bold))
self.lbl_card_text.setWordWrap(True) self.lbl_card_text.setWordWrap(True)
self.lbl_card_text.setStyleSheet("color: #2c3e50; border: none; padding: 20px;") self.lbl_card_text.setStyleSheet("color: #2c3e50; border: none; padding: 25px;")
self.lbl_card_meta = QLabel("") self.lbl_card_meta = QLabel("")
self.lbl_card_meta.setAlignment(Qt.AlignmentFlag.AlignCenter) self.lbl_card_meta.setAlignment(Qt.AlignmentFlag.AlignCenter)
@ -183,22 +265,19 @@ class SpanishTrainerApp(QMainWindow):
card_layout.addStretch() card_layout.addStretch()
playback_layout = QHBoxLayout() playback_layout = QHBoxLayout()
playback_layout.addWidget(QLabel("🔊 Fine-Tune Study Speed:")) playback_layout.addWidget(QLabel("🔊 Voice Track Speed:"))
self.slider_review_speed = QSlider(Qt.Orientation.Horizontal) self.slider_review_speed = QSlider(Qt.Orientation.Horizontal)
self.slider_review_speed.setMinimum(50) self.slider_review_speed.setMinimum(50)
self.slider_review_speed.setMaximum(150) self.slider_review_speed.setMaximum(150)
self.slider_review_speed.setValue(100) self.slider_review_speed.setValue(100)
self.lbl_review_speed = QLabel("1.0x (Normal)") self.lbl_review_speed = QLabel("1.0x")
self.slider_review_speed.valueChanged.connect(self.handle_live_speed_change) self.slider_review_speed.valueChanged.connect(self.handle_live_speed_change)
playback_layout.addWidget(self.slider_review_speed) playback_layout.addWidget(self.slider_review_speed)
playback_layout.addWidget(self.lbl_review_speed) playback_layout.addWidget(self.lbl_review_speed)
action_buttons = QHBoxLayout() action_buttons = QHBoxLayout()
self.btn_play_voice = QPushButton("🗣️ Play Voice Track") self.btn_play_voice = QPushButton("🗣️ Play Voice Track")
self.btn_flip_card = QPushButton("👁️ Reveal Translation") self.btn_flip_card = QPushButton("👁️ Reveal English Partner")
self.btn_load_next = QPushButton("➡️ Next Card") self.btn_load_next = QPushButton("➡️ Next Card")
self.btn_play_voice.clicked.connect(self.handle_play_voice) self.btn_play_voice.clicked.connect(self.handle_play_voice)
@ -214,171 +293,258 @@ class SpanishTrainerApp(QMainWindow):
layout.addLayout(playback_layout) layout.addLayout(playback_layout)
layout.addLayout(action_buttons) layout.addLayout(action_buttons)
self.tabs.addTab(tab, "🃏 Flashcard Study") self.tabs.addTab(tab, "🃏 Flashcard Review")
# ===================================================================== # =====================================================================
# ⚡ ENGINE BUSINESS LOGIC OPERATIONS & DATABASE MAPPINGS # ⚡ ENGINE DATABASE LOGIC & FILTER COMPILATIONS
# ===================================================================== # =====================================================================
def refresh_crud_table(self): def refresh_crud_table(self):
"""Pulls unified translation nodes into pairs while enforcing context text matches."""
conn = get_connection() conn = get_connection()
cursor = conn.cursor() cursor = conn.cursor()
filter_text = self.search_input.text() text_filter = self.search_text_input.text().strip()
if filter_text: context_filter = self.search_context_input.text().strip()
cursor.execute("""
SELECT id, text, language, word_type, voice_gender, deck_name
FROM phrases WHERE text LIKE ? ORDER BY id DESC LIMIT 100
""", (f"%{filter_text}%",))
else:
cursor.execute("""
SELECT id, text, language, word_type, voice_gender, deck_name
FROM phrases ORDER BY id DESC LIMIT 100
""")
query = """
SELECT t.translation_id, p1.text, p2.text, p1.word_type, p1.source_context, t.deck_name
FROM translations t
JOIN phrases p1 ON t.source_phrase_id = p1.id
JOIN phrases p2 ON t.target_phrase_id = p2.id
WHERE p1.language = 'es' AND p2.language = 'en'
"""
params = []
if text_filter:
query += " AND (p1.text LIKE ? OR p2.text LIKE ?)"
params.extend([f"%{text_filter}%", f"%{text_filter}%"])
if context_filter:
query += " AND p1.source_context LIKE ?"
params.append(f"%{context_filter}%")
query += " ORDER BY t.translation_id ASC LIMIT 250"
cursor.execute(query, params)
rows = cursor.fetchall() rows = cursor.fetchall()
conn.close() conn.close()
self.phrase_table.setRowCount(0) self.translation_table.setRowCount(0)
for row_idx, row_data in enumerate(rows): for row_idx, row_data in enumerate(rows):
self.phrase_table.insertRow(row_idx) self.translation_table.insertRow(row_idx)
for col_idx, value in enumerate(row_data): for col_idx in range(6):
self.phrase_table.setItem(row_idx, col_idx, QTableWidgetItem(str(value if value is not None else ""))) val = row_data[col_idx]
self.translation_table.setItem(row_idx, col_idx, QTableWidgetItem(str(val if val is not None else "")))
def handle_table_row_select(self): def handle_table_row_select(self):
selected_ranges = self.phrase_table.selectedRanges() selected_ranges = self.translation_table.selectedRanges()
if not selected_ranges: if not selected_ranges:
return return
row = selected_ranges[0].topRow() row = selected_ranges[0].topRow()
item = self.phrase_table.item(row, 0) tx_id_item = self.translation_table.item(row, 0)
if not item: if not tx_id_item:
return return
phrase_id = item.text()
tx_id = tx_id_item.text()
conn = get_connection() conn = get_connection()
cursor = conn.cursor() cursor = conn.cursor()
cursor.execute("SELECT id, text, language, word_type, voice_gender, base_speed, deck_name FROM phrases WHERE id = ?", (phrase_id,)) cursor.execute("""
SELECT t.translation_id, p1.text, p2.text, p1.word_type, p1.source_context, t.deck_name
FROM translations t
JOIN phrases p1 ON t.source_phrase_id = p1.id
JOIN phrases p2 ON t.target_phrase_id = p2.id
WHERE t.translation_id = ?
""", (tx_id,))
record = cursor.fetchone() record = cursor.fetchone()
conn.close() conn.close()
if record: if record:
self.input_id.setText(str(record[0])) self.input_tx_id.setText(str(record[0]))
self.input_text.setPlainText(str(record[1])) self.input_text_es.setPlainText(str(record[1]))
self.combo_lang.setCurrentText(str(record[2])) self.input_text_en.setPlainText(str(record[2]))
self.combo_type.setCurrentText(str(record[3]) if record[3] else "sentence") self.combo_type.setCurrentText(str(record[3]) if record[3] else "phrase")
self.combo_voice_gender.setCurrentText(str(record[4]) if record[4] else "female") self.input_context.setText(str(record[4]) if record[4] else "")
self.input_deck_tag.setText(str(record[5]) if record[5] else "General")
speed_val = int((record[5] if record[5] else 1.0) * 100) def step_table_row(self, direction):
self.slider_base_speed.setValue(speed_val) """Steps your focus row highlighting pointer index sequentially."""
self.input_deck_tag.setText(str(record[6]) if record[6] else "General") current_row = self.translation_table.currentRow()
next_row = current_row + direction
if 0 <= next_row < self.translation_table.rowCount():
self.translation_table.setCurrentCell(next_row, 0)
def crud_create(self): def crud_create_pair(self):
conn = get_connection() conn = get_connection()
cursor = conn.cursor() cursor = conn.cursor()
# Write Spanish node entry
cursor.execute(""" cursor.execute("""
INSERT INTO phrases (text, language, word_type, voice_gender, base_speed, deck_name) INSERT INTO phrases (text, language, word_type, source_context) VALUES (?, 'es', ?, ?)
VALUES (?, ?, ?, ?, ?, ?) """, (self.input_text_es.toPlainText().strip(), self.combo_type.currentText(), self.input_context.text().strip()))
""", ( es_id = cursor.lastrowid
self.input_text.toPlainText().strip(),
self.combo_lang.currentText(), # Write English node entry
self.combo_type.currentText(), cursor.execute("""
self.combo_voice_gender.currentText(), INSERT INTO phrases (text, language, word_type, source_context) VALUES (?, 'en', ?, ?)
self.slider_base_speed.value() / 100.0, # Fixed typo: changed from .setValue() to .value() """, (self.input_text_en.toPlainText().strip(), self.combo_type.currentText(), self.input_context.text().strip()))
self.input_deck_tag.text().strip() or "General" en_id = cursor.lastrowid
))
# Build cross-referencing translation relational binding matrix row
cursor.execute("""
INSERT INTO translations (source_phrase_id, target_phrase_id, deck_name) VALUES (?, ?, ?)
""", (es_id, en_id, self.input_deck_tag.text().strip() or "General"))
conn.commit() conn.commit()
conn.close() conn.close()
self.refresh_crud_table() self.refresh_crud_table()
QMessageBox.information(self, "Success", "Phrase generated into database index store successfully.") QMessageBox.information(self, "Success", "Isolated phrase pairs created and relational link bound.")
def crud_update(self): def crud_update_pair(self):
pid = self.input_id.text() tx_id = self.input_tx_id.text()
if not pid: if not tx_id:
return
conn = get_connection()
cursor = conn.cursor()
cursor.execute("SELECT source_phrase_id, target_phrase_id FROM translations WHERE translation_id = ?", (tx_id,))
ids = cursor.fetchone()
if ids:
es_id, en_id = ids
# Keep underscores exact in context updates
cursor.execute("UPDATE phrases SET text=?, word_type=?, source_context=? WHERE id=?",
(self.input_text_es.toPlainText().strip(), self.combo_type.currentText(), self.input_context.text().strip(), es_id))
cursor.execute("UPDATE phrases SET text=?, word_type=?, source_context=? WHERE id=?",
(self.input_text_en.toPlainText().strip(), self.combo_type.currentText(), self.input_context.text().strip(), en_id))
cursor.execute("UPDATE translations SET deck_name=? WHERE translation_id=?",
(self.input_deck_tag.text().strip() or "General", tx_id))
conn.commit()
conn.close()
self.refresh_crud_table()
QMessageBox.information(self, "Success", "Relational node structural update complete.")
def crud_delete_pair(self):
tx_id = self.input_tx_id.text()
if not tx_id:
return return
conn = get_connection() conn = get_connection()
cursor = conn.cursor() cursor = conn.cursor()
cursor.execute(""" cursor.execute("SELECT source_phrase_id, target_phrase_id FROM translations WHERE translation_id = ?", (tx_id,))
UPDATE phrases SET text=?, language=?, word_type=?, voice_gender=?, base_speed=?, deck_name=? ids = cursor.fetchone()
WHERE id=? if ids:
""", ( es_id, en_id = ids
self.input_text.toPlainText().strip(), cursor.execute("DELETE FROM translations WHERE translation_id=?", (tx_id,))
self.combo_lang.currentText(), cursor.execute("DELETE FROM phrases WHERE id=?", (es_id,))
self.combo_type.currentText(), cursor.execute("DELETE FROM phrases WHERE id=?", (en_id,))
self.combo_voice_gender.currentText(),
self.slider_base_speed.value() / 100.0,
self.input_deck_tag.text().strip() or "General",
pid
))
conn.commit() conn.commit()
conn.close() conn.close()
self.refresh_crud_table() self.refresh_crud_table()
QMessageBox.information(self, "Success", "Database record fields updated cleanly.") self.input_tx_id.clear()
self.input_text_es.clear()
self.input_text_en.clear()
def crud_delete(self): # =====================================================================
pid = self.input_id.text() # 🔊 AUDIO OPERATIONS & FLASHCARD CONTROL
if not pid: # =====================================================================
def handle_sandbox_play_es(self):
"""Generates/plays the Spanish phrase from the Sandbox using the chosen speed rate."""
text_str = self.input_text_es.toPlainText().strip()
if not text_str:
QMessageBox.warning(self, "Empty Value", "Please select a valid translation pair or type a Spanish phrase to play.")
return return
conn = get_connection()
cursor = conn.cursor()
cursor.execute("DELETE FROM phrases WHERE id=?", (pid,))
conn.commit()
conn.close()
self.input_id.clear()
self.input_text.clear()
self.refresh_crud_table()
# ===================================================================== safe_name = "".join([c for c in text_str if c.isalnum() or c in (" ", "_")]).rstrip().replace(" ", "_").lower()
# 🔊 AUDIO PLAYER SYSTEM os.makedirs("media", exist_ok=True)
# ===================================================================== target_file = f"media/{safe_name}_es_female.mp3"
if not os.path.exists(target_file):
print(f"🔊 Generating Neural Castilian Spanish track for '{text_str}'...")
try:
import asyncio
import edge_tts
communicate = edge_tts.Communicate(text_str, "es-ES-ElviraNeural")
asyncio.run(communicate.save(target_file))
except Exception as tts_err:
QMessageBox.critical(self, "TTS Error", f"Failed to synthesize Spanish voice:\n{tts_err}")
return
if os.path.exists(target_file):
speed_multiplier = float(self.combo_speed_es.currentText().replace("x", ""))
self.media_player.setSource(QUrl.fromLocalFile(os.path.abspath(target_file)))
self.media_player.setPlaybackRate(speed_multiplier)
self.media_player.play()
def handle_sandbox_play_en(self):
"""Generates/plays the English translation string from the Sandbox using its speed rate."""
text_str = self.input_text_en.toPlainText().strip()
if not text_str:
QMessageBox.warning(self, "Empty Value", "Please enter text inside the English target translation container.")
return
safe_name = "".join([c for c in text_str if c.isalnum() or c in (" ", "_")]).rstrip().replace(" ", "_").lower()
os.makedirs("media", exist_ok=True)
target_file = f"media/{safe_name}_en_female.mp3"
if not os.path.exists(target_file):
print(f"🔊 Generating Neural English voice track for '{text_str}'...")
try:
import asyncio
import edge_tts
communicate = edge_tts.Communicate(text_str, "en-GB-SoniaNeural")
asyncio.run(communicate.save(target_file))
except Exception as tts_err:
QMessageBox.critical(self, "TTS Error", f"Failed to synthesize English voice:\n{tts_err}")
return
if os.path.exists(target_file):
speed_multiplier = float(self.combo_speed_en.currentText().replace("x", ""))
self.media_player.setSource(QUrl.fromLocalFile(os.path.abspath(target_file)))
self.media_player.setPlaybackRate(speed_multiplier)
self.media_player.play()
def handle_load_next_card(self): def handle_load_next_card(self):
"""Picks a random phrase node from the database, shifting state context to flashcard mode."""
conn = get_connection() conn = get_connection()
cursor = conn.cursor() cursor = conn.cursor()
cursor.execute("SELECT id, text, language, word_type, voice_gender, base_speed FROM phrases ORDER BY RANDOM() LIMIT 1") cursor.execute("""
SELECT t.translation_id, p1.text, p1.word_type, p1.source_context, t.deck_name, p1.id
FROM translations t
JOIN phrases p1 ON t.source_phrase_id = p1.id
WHERE p1.language = 'es'
ORDER BY RANDOM() LIMIT 1
""")
record = cursor.fetchone() record = cursor.fetchone()
conn.close() conn.close()
if record: if record:
self.current_flashcard_id = record[0] self.current_flashcard_id = record[5]
self.lbl_card_text.setText(record[1]) self.lbl_card_text.setText(record[1])
self.lbl_card_meta.setText(f"Tx Link node: {record[0]} • Context: {record[3]} • Subdeck: {record[4]}")
lang_lbl = "Spanish Accent" if record[2] == "es" else "English Accent"
gender_lbl = str(record[4]).capitalize() if record[4] else "Female"
self.lbl_card_meta.setText(f"Classification: {record[3]} • Configured Voice: {lang_lbl} ({gender_lbl})")
card_saved_speed = int((record[5] if record[5] else 1.0) * 100)
self.slider_review_speed.setValue(card_saved_speed)
def handle_play_voice(self): def handle_play_voice(self):
if not self.current_flashcard_id: if not self.current_flashcard_id:
return return
conn = get_connection() conn = get_connection()
cursor = conn.cursor() cursor = conn.cursor()
cursor.execute("SELECT text, language, voice_gender FROM phrases WHERE id = ?", (self.current_flashcard_id,)) cursor.execute("SELECT text, language, voice_gender FROM phrases WHERE id = ?", (self.current_flashcard_id,))
phrase_row = cursor.fetchone() row = cursor.fetchone()
conn.close() conn.close()
if not phrase_row: if row:
return text_str, lang, gender = row
safe_name = "".join([c for c in text_str if c.isalnum() or c in (" ", "_")]).rstrip().replace(" ", "_").lower()
text_str, lang_code, voice_gender = phrase_row resolved_gender = gender if gender else "female"
target_file = f"media/{safe_name}_{lang}_{resolved_gender}.mp3"
safe_filename = "".join([c for c in text_str if c.isalnum() or c in (" ", "_")]).rstrip().replace(" ", "_").lower()
target_audio_file = f"media/{safe_filename}_{lang_code}_{voice_gender}.mp3"
os.makedirs("media", exist_ok=True)
if not os.path.exists(target_audio_file):
QMessageBox.warning(self, "Audio Track Missing",
f"Audio track asset file not found in directory:\n'{target_audio_file}'\n\nRun the background Edge-TTS batch generator script next to download this track automatically.")
return
self.media_player.setSource(QUrl.fromLocalFile(os.path.abspath(target_audio_file)))
current_rate = self.slider_review_speed.value() / 100.0
self.media_player.setPlaybackRate(current_rate)
if os.path.exists(target_file):
self.media_player.setSource(QUrl.fromLocalFile(os.path.abspath(target_file)))
self.media_player.setPlaybackRate(self.slider_review_speed.value() / 100.0)
self.media_player.play() self.media_player.play()
else:
QMessageBox.warning(self, "Asset Missing", f"Audio file not found at path location:\n{target_file}")
def handle_live_speed_change(self, value): def handle_live_speed_change(self, value):
rate = value / 100.0 rate = value / 100.0
@ -393,18 +559,40 @@ class SpanishTrainerApp(QMainWindow):
cursor = conn.cursor() cursor = conn.cursor()
cursor.execute(""" cursor.execute("""
SELECT p2.text FROM translations t SELECT p2.text FROM translations t
JOIN phrases p1 ON t.source_phrase_id = p1.id
JOIN phrases p2 ON t.target_phrase_id = p2.id JOIN phrases p2 ON t.target_phrase_id = p2.id
WHERE t.source_phrase_id = ? WHERE p1.id = ?
""", (self.current_flashcard_id,)) """, (self.current_flashcard_id,))
row = cursor.fetchone() row = cursor.fetchone()
conn.close() conn.close()
if row: if row:
current_es = self.lbl_card_text.text().split("\n\n👉")[0] clean_es = self.lbl_card_text.text().split("\n\n👉")[0]
self.lbl_card_text.setText(f"{current_es}\n\n👉 [ {row[0]} ]") self.lbl_card_text.setText(f"{clean_es}\n\n👉 [ {row[0]} ]")
# =====================================================================
# 🚀 DIAGNOSTIC STARTUP FRAMEWORK WRAPPER
# =====================================================================
if __name__ == "__main__": if __name__ == "__main__":
print("🚀 Initializing PyQt6 Application Framework...")
try:
app = QApplication(sys.argv) app = QApplication(sys.argv)
print("🔧 Spawning SpanishTrainerApp Instance...")
window = SpanishTrainerApp() window = SpanishTrainerApp()
print("🖥️ Mounting User Interface Windows...")
window.show() window.show()
print("🎯 Event loop engaged. Handing over control thread...")
sys.exit(app.exec()) sys.exit(app.exec())
except Exception as fatal_error:
import traceback
print("\n❌ CRITICAL CRASH DETECTED ON CORE STARTUP THREAD!")
print("====================================================")
print(f"Error Type: {type(fatal_error).__name__}")
print(f"Error Message: {fatal_error}")
print("====================================================")
traceback.print_exc()
sys.exit(1)

2153
spanish_trainer_backup.sql Normal file

File diff suppressed because it is too large Load diff