drishti / src /i18n.py
rittika03's picture
Update existing Space with new files
864d4b1
Raw
History Blame Contribute Delete
10.2 kB
"""Light i18n layer.
Two jobs:
1. UI string translations for English / Kannada / Hindi.
2. Resolve a native-script (Kannada/Hindi) place query to the English place
names in the data, via transliteration + fuzzy matching.
NOTE: the Kannada/Hindi UI strings are standard civic terms but should be
sanity-checked by a native speaker before the finale.
"""
import re
import difflib
import unicodedata
from indic_transliteration import sanscript
from indic_transliteration.sanscript import transliterate
# label -> code, and code -> Web Speech API locale
LANGS = {"English": "en", "ಕನ್ನಡ": "kn", "हिन्दी": "hi"}
SPEECH_LANG = {"en": "en-IN", "kn": "kn-IN", "hi": "hi-IN"}
STRINGS = {
"title": {
"en": "ParkSight — Parking-Induced Congestion Intelligence",
"kn": "ಪಾರ್ಕ್‌ಸೈಟ್ — ಪಾರ್ಕಿಂಗ್ ದಟ್ಟಣೆ ವಿಶ್ಲೇಷಣೆ",
"hi": "पार्कसाइट — पार्किंग जनित भीड़ विश्लेषण"},
"kpi_violations": {"en": "Parking violations", "kn": "ಪಾರ್ಕಿಂಗ್ ಉಲ್ಲಂಘನೆಗಳು",
"hi": "पार्किंग उल्लंघन"},
"kpi_zones": {"en": "Impact zones", "kn": "ಪ್ರಭಾವ ವಲಯಗಳು", "hi": "प्रभाव क्षेत्र"},
"kpi_high": {"en": "High-impact (CII ≥ 70)", "kn": "ಹೆಚ್ಚು-ಪ್ರಭಾವ (CII ≥ 70)",
"hi": "उच्च-प्रभाव (CII ≥ 70)"},
"kpi_mae": {"en": "Forecast MAE", "kn": "ಮುನ್ಸೂಚನೆ MAE", "hi": "पूर्वानुमान MAE"},
"voice_nav": {"en": "Voice / command navigation", "kn": "ಧ್ವನಿ / ಆದೇಶ ಸಂಚಲನೆ",
"hi": "वॉइस / कमांड नेविगेशन"},
"ask": {"en": "Ask in plain language", "kn": "ಕನ್ನಡದಲ್ಲಿ ಹುಡುಕಿ", "hi": "हिंदी में खोजें"},
"placeholder": {"en": "e.g. 'show worst zones in Shivaji Nagar' · 'read top 5'",
"kn": "ಉದಾ: 'ಶಿವಾಜಿನಗರದ ಕೆಟ್ಟ ವಲಯಗಳು'",
"hi": "उदा: 'शिवाजी नगर के सबसे खराब क्षेत्र'"},
"speak": {"en": "🎤 Speak", "kn": "🎤 ಮಾತನಾಡಿ", "hi": "🎤 बोलें"},
"stop": {"en": "⏹ Stop", "kn": "⏹ ನಿಲ್ಲಿಸಿ", "hi": "⏹ रोकें"},
"understood": {"en": "Understood", "kn": "ಅರ್ಥವಾಯಿತು", "hi": "समझ गया"},
"tab_map": {"en": "🗺️ Impact map", "kn": "🗺️ ಪ್ರಭಾವ ನಕ್ಷೆ", "hi": "🗺️ प्रभाव नक्शा"},
"tab_ops": {"en": "🚨 Live alerts", "kn": "🚨 ಲೈವ್ ಎಚ್ಚರಿಕೆ", "hi": "🚨 लाइव अलर्ट"},
"tab_rank": {"en": "📋 Enforcement priorities", "kn": "📋 ಜಾರಿ ಆದ್ಯತೆಗಳು",
"hi": "📋 प्रवर्तन प्राथमिकताएँ"},
"tab_off": {"en": "🚨 Repeat offenders", "kn": "🚨 ಪುನರಾವರ್ತಿತ ಅಪರಾಧಿಗಳು",
"hi": "🚨 बार-बार उल्लंघनकर्ता"},
"tab_fc": {"en": "🔮 Tomorrow's forecast", "kn": "🔮 ನಾಳಿನ ಮುನ್ಸೂಚನೆ",
"hi": "🔮 कल का पूर्वानुमान"},
"tab_patrol": {"en": "🗓️ Patrol planner", "kn": "🗓️ ಗಸ್ತು ಯೋಜನೆ",
"hi": "🗓️ गश्त योजना"},
"tab_whatif": {"en": "🧪 What-if simulator", "kn": "🧪 ವಾಟ್-ಇಫ್ ಸಿಮ್ಯುಲೇಟರ್",
"hi": "🧪 व्हाट-इफ सिम्युलेटर"},
"tab_event": {"en": "🎪 Event mode", "kn": "🎪 ಈವೆಂಟ್ ಮೋಡ್",
"hi": "🎪 इवेंट मोड"},
"min_cii": {"en": "Minimum CII to display", "kn": "ಪ್ರದರ್ಶಿಸಲು ಕನಿಷ್ಠ CII",
"hi": "दिखाने हेतु न्यूनतम CII"},
"top_n_zones": {"en": "Show top N zones", "kn": "ಮೇಲಿನ N ವಲಯಗಳನ್ನು ತೋರಿಸಿ",
"hi": "शीर्ष N क्षेत्र दिखाएँ"},
"dl_priorities": {"en": "⬇️ Download enforcement priorities (CSV)",
"kn": "⬇️ ಜಾರಿ ಆದ್ಯತೆಗಳನ್ನು ಡೌನ್‌ಲೋಡ್ ಮಾಡಿ (CSV)",
"hi": "⬇️ प्रवर्तन प्राथमिकताएँ डाउनलोड करें (CSV)"},
"repeat_offenders": {"en": "Repeat offenders", "kn": "ಪುನರಾವರ್ತಿತ ಅಪರಾಧಿಗಳು",
"hi": "बार-बार उल्लंघनकर्ता"},
"share_violations": {"en": "Share of all violations", "kn": "ಎಲ್ಲಾ ಉಲ್ಲಂಘನೆಗಳ ಪಾಲು",
"hi": "कुल उल्लंघनों में हिस्सा"},
"worst_vehicle": {"en": "Worst single vehicle", "kn": "ಅತಿ ಕೆಟ್ಟ ಏಕೈಕ ವಾಹನ",
"hi": "सबसे खराब एकल वाहन"},
"search_vehicle": {"en": "Search a vehicle ID or area",
"kn": "ವಾಹನ ID ಅಥವಾ ಪ್ರದೇಶ ಹುಡುಕಿ", "hi": "वाहन ID या क्षेत्र खोजें"},
"top_n_off": {"en": "Show top N offenders", "kn": "ಮೇಲಿನ N ಅಪರಾಧಿಗಳನ್ನು ತೋರಿಸಿ",
"hi": "शीर्ष N उल्लंघनकर्ता दिखाएँ"},
"dl_offenders": {"en": "⬇️ Download offender watchlist (CSV)",
"kn": "⬇️ ಅಪರಾಧಿಗಳ ಪಟ್ಟಿ ಡೌನ್‌ಲೋಡ್ ಮಾಡಿ (CSV)",
"hi": "⬇️ उल्लंघनकर्ता सूची डाउनलोड करें (CSV)"},
"language": {"en": "Language", "kn": "ಭಾಷೆ", "hi": "भाषा"},
"theme": {"en": "Theme", "kn": "ಥೀಮ್", "hi": "थीम"},
"dark": {"en": "🌙 Dark", "kn": "🌙 ಕಪ್ಪು", "hi": "🌙 डार्क"},
"light": {"en": "☀️ Light", "kn": "☀️ ಬೆಳಕು", "hi": "☀️ लाइट"},
"history": {"en": "🕘 Your searches this session", "kn": "🕘 ಈ ಅವಧಿಯ ಹುಡುಕಾಟಗಳು",
"hi": "🕘 इस सत्र की खोजें"},
"clear_history": {"en": "Clear history", "kn": "ಇತಿಹಾಸ ಅಳಿಸಿ", "hi": "इतिहास साफ़ करें"},
"no_history": {"en": "No searches yet.", "kn": "ಇನ್ನೂ ಹುಡುಕಾಟಗಳಿಲ್ಲ.",
"hi": "अभी तक कोई खोज नहीं।"},
"tab_trends": {"en": "📈 Trends", "kn": "📈 ಪ್ರವೃತ್ತಿಗಳು", "hi": "📈 रुझान"},
"t_daily": {"en": "Daily violations", "kn": "ದೈನಂದಿನ ಉಲ್ಲಂಘನೆಗಳು", "hi": "दैनिक उल्लंघन"},
"t_hourly": {"en": "By hour of day", "kn": "ಗಂಟೆಯ ಪ್ರಕಾರ", "hi": "घंटे के अनुसार"},
"t_vehicle": {"en": "By vehicle type", "kn": "ವಾಹನ ಪ್ರಕಾರ", "hi": "वाहन प्रकार के अनुसार"},
"t_vtype": {"en": "By violation type", "kn": "ಉಲ್ಲಂಘನೆ ಪ್ರಕಾರ", "hi": "उल्लंघन प्रकार के अनुसार"},
"t_dow": {"en": "By day of week", "kn": "ವಾರದ ದಿನದ ಪ್ರಕಾರ", "hi": "सप्ताह के दिन अनुसार"},
"t_gauge": {"en": "Top-100 zones · share of all violations",
"kn": "ಮೇಲಿನ 100 ವಲಯಗಳ ಪಾಲು", "hi": "शीर्ष 100 क्षेत्रों का हिस्सा"},
"live_replay": {"en": "▶ Live replay (simulated from historical data)",
"kn": "▶ ಲೈವ್ ರಿಪ್ಲೇ (ಐತಿಹಾಸಿಕ ದತ್ತಾಂಶದಿಂದ)",
"hi": "▶ लाइव रीप्ले (ऐतिहासिक डेटा से)"},
}
def t(key, lang):
"""Translate a UI string key into the chosen language (falls back to English)."""
return STRINGS.get(key, {}).get(lang) or STRINGS.get(key, {}).get("en", key)
def _norm(s):
s = unicodedata.normalize("NFKD", s).encode("ascii", "ignore").decode()
s = re.sub(r"[^a-z]", "", s.lower())
s = re.sub(r"([a-z])\1+", r"\1", s) # collapse doubled letters
return s.replace("nagara", "nagar")
def romanize(text, lang):
scr = {"kn": sanscript.KANNADA, "hi": sanscript.DEVANAGARI}.get(lang)
if scr is None:
return text
try:
return transliterate(text, scr, sanscript.IAST)
except Exception:
return text
def build_area_vocab(hot):
"""Distinct locality names from the hotspot location strings."""
areas = set()
for loc in hot["location"].dropna():
for p in [x.strip() for x in str(loc).split(",")][1:4]:
pl = p.lower()
if p and "bengaluru" not in pl and "karnataka" not in pl and "pin" not in pl:
areas.add(p)
return sorted(areas)
def resolve_area(query, lang, area_vocab):
"""Map a (possibly Kannada/Hindi) place query to an English locality name."""
has_native = any(ord(c) > 127 for c in query)
rom = romanize(query, lang) if (has_native or lang != "en") else query
key = _norm(rom)
if not key:
return None
norm_map = {_norm(a): a for a in area_vocab}
best = difflib.get_close_matches(key, list(norm_map.keys()), n=1, cutoff=0.55)
return norm_map[best[0]] if best else None