"""Gradio web UI for IPA Translator."""
from typing import Optional
import gradio as gr
from core import Phonemlyze, AnalysisResult
# ── Lazy singleton ─────────────────────────────────────────────────────────────
_engine: Optional[Phonemlyze] = None
WHISPER_SIZES = ["tiny", "base", "small", "medium", "large"]
LANGUAGES = {
"Arabic": "ar",
"Czech": "cs",
"Dutch": "nl",
"English (UK)": "en-gb",
"English (US)": "en-us",
"French (Canadian)": "fr-ca",
"French (Parisian)": "fr",
"German": "de",
"Greek": "el",
"Hindi": "hi",
"Hungarian": "hu",
"Italian": "it",
"Japanese": "ja",
"Mandarin (Simplified)": "zh",
"Mandarin (Traditional)": "zh-tw",
"Portuguese (Brazilian)": "pt-br",
"Russian": "ru",
"Spanish (Castilian)": "es-es",
"Spanish (Latin American)": "es-419",
"Thai": "th",
"Turkish": "tr",
"Ukrainian": "uk",
}
def get_engine(whisper_size: str) -> Phonemlyze:
global _engine
if _engine is None:
_engine = Phonemlyze(whisper_model=whisper_size)
return _engine
# ── Colour palette (dark-first) ───────────────────────────────────────────────
# Every custom HTML component uses these so the whole app is consistently dark.
_C_BG = "#111827" # page / deepest background
_C_CARD = "#1e2535" # card / IPA box background
_C_RAISED = "#2d3748" # raised elements (keys, input bg, table header)
_C_BORDER = "#4b5563" # borders / dividers
_C_TEXT = "#f1f5f9" # primary text
_C_TEXT2 = "#cbd5e1" # secondary text
_C_MUTED = "#94a3b8" # muted / label text
# Alignment status colours – muted dark backgrounds, bright foreground text
STATUS_COLORS = {
"match": ("#0a3320", "#6ee7b7"), # dark green / mint
"replace": ("#4a2000", "#fbbf24"), # dark amber / gold
"delete": ("#4a0e0e", "#fca5a5"), # dark red / rose
"insert": ("#0e2744", "#93c5fd"), # dark blue / sky
}
# ── HTML rendering ─────────────────────────────────────────────────────────────
STATUS_LABELS = {
"match": "✓ match",
"replace": "≠ substitution",
"delete": "− missing",
"insert": "+ extra",
}
# IPA reference: symbol → {desc, ex (examples), word (spoken on click)}
# "ex" uses plain-English spelling hints so non-linguists can map the sound.
IPA_REFERENCE = {
# ── Stops ────────────────────────────────────────────────────────────────
"p": {"desc": "voiceless stop", "ex": '"p" as in pat, cap, happy', "word": "pat"},
"b": {"desc": "voiced stop", "ex": '"b" as in bat, cab, lab', "word": "bat"},
"t": {"desc": "voiceless stop", "ex": '"t" as in tap, cat, butter', "word": "tap"},
"d": {"desc": "voiced stop", "ex": '"d" as in dog, bed, ladder', "word": "dog"},
"k": {"desc": "voiceless stop", "ex": '"k/c" as in cat, back, kick', "word": "cat"},
"ɡ": {"desc": "voiced stop", "ex": '"g" as in go, bag, tiger', "word": "go"},
"g": {"desc": "voiced stop", "ex": '"g" as in go, bag, tiger', "word": "go"},
"ʔ": {"desc": "glottal stop", "ex": 'the catch in uh-oh, or "t" in Button (UK)',"word": "uh oh"},
# ── Affricates ───────────────────────────────────────────────────────────
"tʃ": {"desc": "voiceless affricate", "ex": '"ch" as in chip, catch, nature', "word": "chip"},
"dʒ": {"desc": "voiced affricate", "ex": '"j/g" as in jam, age, magic', "word": "jam"},
"ts": {"desc": "voiceless affricate", "ex": '"ts" as in cats, bits', "word": "cats"},
"dz": {"desc": "voiced affricate", "ex": '"ds/z" as in beds, adze', "word": "beds"},
# ── Fricatives ───────────────────────────────────────────────────────────
"f": {"desc": "voiceless fricative", "ex": '"f/ph" as in fat, leaf, phone', "word": "fat"},
"v": {"desc": "voiced fricative", "ex": '"v" as in vat, love, over', "word": "vat"},
"θ": {"desc": "voiceless fricative", "ex": '"th" as in thin, bath', "word": "thin"},
"ð": {"desc": "voiced fricative", "ex": '"th" as in the, this, father', "word": "this"},
"s": {"desc": "voiceless fricative", "ex": '"s" as in sit, bus, hiss', "word": "sit"},
"z": {"desc": "voiced fricative", "ex": '"z" as in zoo, buzz, lazy', "word": "zoo"},
"ʃ": {"desc": "voiceless fricative", "ex": '"sh" as in ship, cash, sugar', "word": "ship"},
"ʒ": {"desc": "voiced fricative", "ex": '"s/g" as in measure, vision, beige',"word": "measure"},
"h": {"desc": "voiceless fricative", "ex": '"h" as in hat, ahead, who', "word": "hat"},
"x": {"desc": "voiceless fricative", "ex": '"ch" as in loch (Scottish), Bach', "word": "loch"},
"ç": {"desc": "voiceless fricative", "ex": 'soft "ch" as in ich (German)', "word": "huge"},
# ── Nasals ───────────────────────────────────────────────────────────────
"m": {"desc": "nasal", "ex": '"m" as in mat, ham, summer', "word": "mat"},
"n": {"desc": "nasal", "ex": '"n" as in nap, ban, sunny', "word": "nap"},
"ŋ": {"desc": "nasal", "ex": '"ng" as in sing, ring, singer', "word": "singing"},
"ɲ": {"desc": "palatal nasal", "ex": '"ny/gn" as in canyon, lasagne', "word": "canyon"},
# ── Liquids & Approximants ───────────────────────────────────────────────
"l": {"desc": "lateral", "ex": '"l" as in lip, bell, silly', "word": "lip"},
"r": {"desc": "trill/tap", "ex": 'rolled "r" as in Spanish rosa', "word": "red"},
"ɹ": {"desc": "approximant", "ex": '"r" as in red, car, very (US/UK)', "word": "red"},
"ɾ": {"desc": "flap", "ex": 'quick "d/t" as in butter, ladder (US)', "word": "butter"},
"w": {"desc": "approximant", "ex": '"w" as in wet, twin, away', "word": "wet"},
"j": {"desc": "approximant", "ex": '"y" as in yes, you, beyond', "word": "yes"},
"ʍ": {"desc": "voiceless approximant","ex": 'breathy "wh" as in which (Scottish/Irish)', "word": "which"},
# ── Short / lax vowels ───────────────────────────────────────────────────
"ɪ": {"desc": "short vowel", "ex": '"i" as in sit, ship, big', "word": "sit"},
"ʊ": {"desc": "short vowel", "ex": '"oo" as in put, foot, could', "word": "foot"},
"ɛ": {"desc": "short vowel", "ex": '"e" as in bed, head, said', "word": "bed"},
"æ": {"desc": "short vowel", "ex": '"a" as in cat, bad, hand', "word": "cat"},
"ɒ": {"desc": "short vowel", "ex": '"o" as in pot, dog, not (UK)', "word": "pot"},
"ʌ": {"desc": "short vowel", "ex": '"u" as in cup, luck, blood', "word": "cup"},
"ə": {"desc": "schwa (unstressed)", "ex": 'weak vowel: about, sofa, banana', "word": "about"},
"ɐ": {"desc": "open-mid central", "ex": 'short "a/u" as in cut, about (some accents)',"word": "cut"},
# ── Long vowels ──────────────────────────────────────────────────────────
"iː": {"desc": "long vowel", "ex": '"ee" as in see, feet, key', "word": "see"},
"i": {"desc": "close front vowel", "ex": '"ee" as in see, happy, coffee', "word": "see"},
"uː": {"desc": "long vowel", "ex": '"oo" as in too, blue, food', "word": "too"},
"u": {"desc": "close back vowel", "ex": '"oo" as in too, blue, food', "word": "too"},
"ɑː": {"desc": "long vowel", "ex": '"ah" as in car, father, palm', "word": "father"},
"ɑ": {"desc": "open back vowel", "ex": '"ah" as in father, spa', "word": "father"},
"ɔː": {"desc": "long vowel", "ex": '"aw" as in saw, call, thought', "word": "saw"},
"ɔ": {"desc": "open-mid back vowel", "ex": '"aw/o" as in saw, law', "word": "saw"},
"ɜː": {"desc": "long vowel", "ex": '"ur" as in bird, word, turn', "word": "bird"},
"ɜ": {"desc": "open-mid central", "ex": '"ur" as in bird, word', "word": "bird"},
"aː": {"desc": "long open vowel", "ex": '"ah" as in car, father', "word": "car"},
"a": {"desc": "open front vowel", "ex": '"a" as in father, spa', "word": "father"},
"e": {"desc": "mid front vowel", "ex": '"ay/e" as in day, face, they', "word": "day"},
"o": {"desc": "mid back vowel", "ex": '"oh" as in go, home, note', "word": "go"},
"ø": {"desc": "front rounded vowel", "ex": '"eu" as in feu (French), schön (German)', "word": "fur"},
"y": {"desc": "close front rounded", "ex": '"ü" as in lune (French), über (German)', "word": "few"},
"ʉ": {"desc": "close central vowel", "ex": 'between "ee" and "oo", e.g. goose (Australian)', "word": "goose"},
# ── R-coloured vowels ────────────────────────────────────────────────────
"ɚ": {"desc": "r-coloured schwa", "ex": '"er" as in butter, better (US)', "word": "butter"},
"ɝ": {"desc": "r-coloured vowel", "ex": '"ur" as in bird, word (US)', "word": "bird"},
"ɻ": {"desc": "retroflex approximant","ex": '"r" sound made with tongue curled back (some US)', "word": "red"},
# ── Diphthongs ───────────────────────────────────────────────────────────
"eɪ": {"desc": "diphthong", "ex": '"ay" as in day, face, take', "word": "day"},
"aɪ": {"desc": "diphthong", "ex": '"i" as in fly, time, high', "word": "fly"},
"ɔɪ": {"desc": "diphthong", "ex": '"oy" as in boy, toy, coin', "word": "boy"},
"aʊ": {"desc": "diphthong", "ex": '"ow" as in now, out, mouse', "word": "now"},
"oʊ": {"desc": "diphthong", "ex": '"oh" as in go, home, know', "word": "go"},
"əʊ": {"desc": "diphthong", "ex": '"oh" as in go, home, know (UK)', "word": "go"},
"ɪə": {"desc": "diphthong", "ex": '"ear" as in ear, here, near', "word": "ear"},
"ʊə": {"desc": "diphthong", "ex": '"oor" as in tour, poor (UK)', "word": "tour"},
"eə": {"desc": "diphthong", "ex": '"air" as in hair, care, there (UK)',"word": "hair"},
}
# Pure-CSS tooltip + inline onclick (no