| """Lightweight, dependency-free language gate. |
| |
| We only need a binary "is this English?" decision to split scored vs counted-only turns. |
| Adding langdetect/langid/fasttext would violate the gradio-only dependency constraint, so |
| this is a deliberate heuristic placeholder (logged in DECISIONS_LOG.md): a turn counts as |
| English when it is mostly Latin script AND hits a few common English function words. Crude |
| but stable and zero-dep; the backend can swap in a real detector behind `is_english`. |
| """ |
| from __future__ import annotations |
|
|
| import re |
|
|
| _STOPWORDS = { |
| "the", "a", "an", "and", "or", "but", "if", "to", "of", "in", "on", "for", "with", |
| "is", "are", "was", "were", "be", "this", "that", "it", "you", "i", "we", "they", |
| "can", "could", "would", "should", "how", "what", "why", "do", "does", "please", |
| "me", "my", "your", "have", "has", "not", "no", "yes", "give", "make", "want", |
| } |
| _WORD_RE = re.compile(r"[a-zA-Z']+") |
|
|
|
|
| def latin_ratio(text: str) -> float: |
| letters = [c for c in text if c.isalpha()] |
| if not letters: |
| return 0.0 |
| latin = sum(1 for c in letters if "a" <= c.lower() <= "z") |
| return latin / len(letters) |
|
|
|
|
| def is_english(text: str) -> bool: |
| """Best-effort English check for a single turn.""" |
| text = (text or "").strip() |
| if len(text) < 2: |
| return False |
| if latin_ratio(text) < 0.6: |
| return False |
| words = [w.lower() for w in _WORD_RE.findall(text)] |
| if not words: |
| return False |
| if len(words) <= 3: |
| return True |
| hits = sum(1 for w in words if w in _STOPWORDS) |
| return hits / len(words) >= 0.08 |
|
|