From 14aae6f7cbc24da21e8d0361d486a4b4d532cdd6 Mon Sep 17 00:00:00 2001 From: Hugo Montenegro Date: Mon, 23 Feb 2026 12:54:58 +0000 Subject: [PATCH 01/12] fix: improve Arabic and Hebrew daily word quality MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Arabic (52% win rate → should improve): - Add character difficulty filter to improve_word_lists.py (3% threshold) - Remove 212 words with rare chars (آ إ ؤ ى ظ أ ذ غ), 1,838 daily words remain - Blocklist 31 proper nouns/place names (باريس, تركيا, روسيا, etc.) Hebrew (דרוזי/Druze and similar issues): - Expand blocklist by 1,470 entries: suffix variant dedup (209), words not in wordfreq (442), low-frequency words (215), proper nouns/demonyms (28) - Reduce daily pool to 1,000 words — 100% wordfreq-verified, 0 suffix groups - Was: 67% normal words, 89 suffix groups, 442 unknown → now: 100% clean New tooling: - scripts/analyze_word_quality.py: char-freq, difficult-words, hebrew-suffixes, hebrew-quality subcommands for analyzing word list quality - Regression tests for Arabic char difficulty and Hebrew suffix deduplication --- docs/CURATED_WORDS.md | 76 + scripts/analyze_word_quality.py | 407 +++++ scripts/improve_word_lists.py | 23 + tests/test_word_lists.py | 47 + webapp/data/languages/ar/ar_blocklist.txt | 34 + webapp/data/languages/ar/ar_daily_words.txt | 220 +-- .../languages/he/he_5words_supplement.txt | 140 +- webapp/data/languages/he/he_blocklist.txt | 1483 +++++++++++++++++ webapp/data/languages/he/he_daily_words.txt | 1000 ----------- 9 files changed, 2166 insertions(+), 1264 deletions(-) create mode 100644 docs/CURATED_WORDS.md create mode 100644 scripts/analyze_word_quality.py diff --git a/docs/CURATED_WORDS.md b/docs/CURATED_WORDS.md new file mode 100644 index 0000000..266be3c --- /dev/null +++ b/docs/CURATED_WORDS.md @@ -0,0 +1,76 @@ +# Curated Words Registry + +**DO NOT regenerate these word lists from the data pipeline without preserving manual curation.** + +This file tracks which languages have been manually curated and should not be overwritten by running `scripts/languages.ipynb`. + +## How Curation Works + +1. Words are extracted for the next 365 days using the daily word algorithm +2. An LLM or native speaker reviews the words for quality issues +3. Bad words are removed or reordered in the word list +4. The language is marked as "curated" below with the curation date + +## Protected Languages + +When running the data pipeline, **skip these languages** or merge changes carefully: + +| Language | Code | Curated Date | Curator | Notes | +|----------|------|--------------|---------|-------| +| Bulgarian | bg | 2026-01-25 | Claude | Removed 728 proper nouns | +| Turkish | tr | 2026-01-25 | Claude | Removed 21 names/places, blocklist created | +| Hungarian | hu | 2026-01-25 | Claude | Removed 39 names/foreign words, blocklist created | +| Arabic | ar | 2026-02-23 | Script | Char difficulty filter (3%): removed 212 words with rare chars, 1,788 daily words | +| Hebrew | he | 2026-02-23 | Script | Suffix dedup + wordfreq filter: 1,442 blocklist additions, 1,000 daily words (100% wordfreq-verified) | + +## Curation Checklist + +Before marking a language as curated, verify: + +- [ ] Removed proper nouns (names, places, brands) +- [ ] Removed obscure/archaic words +- [ ] Removed offensive words +- [ ] Removed grammatical forms (conjugations, declensions) if not common +- [ ] Removed borrowed words that don't fit the language +- [ ] Verified next 365 words are all reasonable daily words +- [ ] Tests pass: `pytest tests/test_word_lists.py -v -k "{lang}"` + +## How to Safely Update a Curated Language + +If you need to regenerate a curated language's word list: + +1. **Export current curated list**: + ```bash + python scripts/curate_words.py backup {lang} + ``` + +2. **Run the pipeline** to get new words + +3. **Apply blocklist** to remove known bad words: + ```bash + python scripts/curate_words.py apply-blocklist {lang} + ``` + +4. **Merge carefully**: + - Keep the curated word order for indices 0 to current_index + 365 + - Append new words that weren't in the old list + - Remove words that were deliberately deleted + +5. **Update this file** with new curation date + +## Blocklist Files + +Each curated language can have a `{lang}_blocklist.txt` file containing words to automatically exclude. +Format: one word per line, comments start with `#`. + +To apply all blocklists after regenerating: +```bash +python scripts/curate_words.py apply-all-blocklists +``` + +## Word Index Reference + +Current word index (Jan 2026): ~1681 +- Words 0-1680: Already shown to users +- Words 1681-2046: Next year (must be curated) +- Words 2047+: Future (can be regenerated) diff --git a/scripts/analyze_word_quality.py b/scripts/analyze_word_quality.py new file mode 100644 index 0000000..6c5a21b --- /dev/null +++ b/scripts/analyze_word_quality.py @@ -0,0 +1,407 @@ +#!/usr/bin/env python3 +""" +Word quality analysis tool for Arabic and Hebrew. + +Analyzes daily word lists to identify quality issues: +- Character frequency and difficulty scoring +- Hebrew suffix variant groups (possessive/construct forms) +- Cross-referencing against multiple frequency sources + +This script is READ-ONLY — it analyzes existing data and prints reports. +Use improve_word_lists.py to actually regenerate word lists. + +Usage: + python scripts/analyze_word_quality.py char-freq ar + python scripts/analyze_word_quality.py char-freq he + python scripts/analyze_word_quality.py difficult-words ar + python scripts/analyze_word_quality.py difficult-words ar --threshold 0.03 + python scripts/analyze_word_quality.py hebrew-suffixes + python scripts/analyze_word_quality.py hebrew-quality +""" + +import argparse +import sys +from collections import defaultdict +from math import log +from pathlib import Path + +SCRIPT_DIR = Path(__file__).parent +DATA_DIR = SCRIPT_DIR.parent / "webapp" / "data" / "languages" + + +# --------------------------------------------------------------------------- +# Data loading (reuse patterns from improve_word_lists.py) +# --------------------------------------------------------------------------- + + +def load_word_list(lang: str) -> list[str]: + """Load the main word list.""" + path = DATA_DIR / lang / f"{lang}_5words.txt" + if not path.exists(): + return [] + return [line.strip() for line in path.read_text(encoding="utf-8").splitlines() if line.strip()] + + +def load_daily_words(lang: str) -> list[str]: + """Load the daily word list (curated subset).""" + path = DATA_DIR / lang / f"{lang}_daily_words.txt" + if not path.exists(): + return [] + return [ + line.strip() + for line in path.read_text(encoding="utf-8").splitlines() + if line.strip() and not line.startswith("#") + ] + + +def load_characters(lang: str) -> list[str]: + """Load valid character set for a language (preserving order).""" + path = DATA_DIR / lang / f"{lang}_characters.txt" + if not path.exists(): + return [] + return [line.strip() for line in path.read_text(encoding="utf-8").splitlines() if line.strip()] + + +def load_blocklist(lang: str) -> set[str]: + """Load blocklist words.""" + path = DATA_DIR / lang / f"{lang}_blocklist.txt" + if not path.exists(): + return set() + blocklist = set() + for line in path.read_text(encoding="utf-8").splitlines(): + line = line.strip() + if line and not line.startswith("#"): + blocklist.add(line.lower()) + return blocklist + + +# --------------------------------------------------------------------------- +# Character frequency analysis +# --------------------------------------------------------------------------- + + +def compute_char_frequency(words: list[str]) -> dict[str, float]: + """Compute fraction of words containing each character. + + Returns {char: fraction} where fraction is in [0, 1]. + A character that appears in every word has fraction 1.0. + """ + if not words: + return {} + char_counts = defaultdict(int) + for word in words: + for char in set(word): # count each char once per word + char_counts[char] += 1 + return {char: count / len(words) for char, count in char_counts.items()} + + +def word_min_char_freq(word: str, char_freq: dict[str, float]) -> tuple[float, str]: + """Return (min_frequency, rarest_char) for a word.""" + if not word: + return (0.0, "") + rarest_char = min(set(word), key=lambda c: char_freq.get(c, 0.0)) + return (char_freq.get(rarest_char, 0.0), rarest_char) + + +# --------------------------------------------------------------------------- +# Subcommand: char-freq +# --------------------------------------------------------------------------- + + +def cmd_char_freq(args): + """Print character frequency table for a language.""" + lang = args.lang + use_daily = not args.main_list + + if use_daily: + words = load_daily_words(lang) + source = "daily_words" + else: + words = load_word_list(lang) + source = "main list" + + if not words: + print(f"No words found for {lang} ({source})", file=sys.stderr) + sys.exit(1) + + char_freq = compute_char_frequency(words) + sorted_chars = sorted(char_freq.items(), key=lambda x: x[1]) + + print(f"Character frequency for {lang} ({source}, {len(words)} words)") + print(f"{'Char':<6} {'Count':>6} {'Freq %':>8} {'Bar'}") + print("-" * 50) + for char, freq in sorted_chars: + count = int(freq * len(words)) + bar = "#" * int(freq * 100) + print(f" {char} {count:>6} {freq*100:>7.1f}% {bar}") + + # Threshold analysis + print(f"\n{'Threshold analysis':}") + print(f"{'Threshold':>10} {'Chars below':>12} {'Words filtered':>15} {'Words remaining':>16}") + print("-" * 55) + for threshold in [0.01, 0.02, 0.03, 0.05, 0.10]: + rare_chars = {c for c, f in char_freq.items() if f < threshold} + filtered = [w for w in words if any(c in rare_chars for c in w)] + remaining = len(words) - len(filtered) + print( + f" {threshold*100:>4.0f}% {len(rare_chars):>6} {len(filtered):>8} {remaining:>6}" + ) + + +# --------------------------------------------------------------------------- +# Subcommand: difficult-words +# --------------------------------------------------------------------------- + + +def cmd_difficult_words(args): + """List words containing characters rarer than threshold.""" + lang = args.lang + threshold = args.threshold + limit = args.limit + + words = load_daily_words(lang) + if not words: + words = load_word_list(lang) + + if not words: + print(f"No words found for {lang}", file=sys.stderr) + sys.exit(1) + + char_freq = compute_char_frequency(words) + + # Score each word by its rarest character + scored = [] + for word in words: + min_freq, rarest = word_min_char_freq(word, char_freq) + scored.append((word, min_freq, rarest)) + + # Sort by difficulty (rarest char frequency ascending = hardest first) + scored.sort(key=lambda x: x[1]) + + # Filter by threshold if specified + if threshold is not None: + scored = [(w, f, c) for w, f, c in scored if f < threshold] + print(f"Words in {lang} daily list with rarest character below {threshold*100:.0f}%:") + else: + print(f"All words in {lang} daily list sorted by difficulty (hardest first):") + + if limit: + scored = scored[:limit] + + print(f"{'Word':<12} {'Rarest Char':>12} {'Char Freq %':>12}") + print("-" * 38) + for word, freq, char in scored: + print(f" {word:<10} {char:>8} {freq*100:>7.1f}%") + + print(f"\nTotal: {len(scored)} words") + + +# --------------------------------------------------------------------------- +# Subcommand: hebrew-suffixes +# --------------------------------------------------------------------------- + +# Hebrew possessive/construct suffixes +HEBREW_SUFFIXES = { + "ו": "his", + "י": "my/adj", + "ם": "their-m", + "ן": "their-f", + "ה": "her/fem", + "ך": "your", + "ת": "construct/fem", +} + + +def find_suffix_groups(words: list[str], min_group_size: int = 3) -> dict[str, list[str]]: + """Find groups of words sharing a stem with different suffixes. + + Returns {stem: [word1, word2, ...]} for groups with >= min_group_size members. + """ + groups = defaultdict(list) + word_set = set(words) + + for word in words: + if len(word) == 5 and word[-1] in HEBREW_SUFFIXES: + stem = word[:-1] + groups[stem].append(word) + + # Filter to groups with enough variants + return {stem: variants for stem, variants in groups.items() if len(variants) >= min_group_size} + + +def cmd_hebrew_suffixes(args): + """Find Hebrew suffix variant groups in daily words.""" + words = load_daily_words("he") + if not words: + print("No Hebrew daily words found", file=sys.stderr) + sys.exit(1) + + min_size = args.min_group_size + groups = find_suffix_groups(words, min_size) + + print(f"Hebrew suffix groups (>= {min_size} variants) in daily words ({len(words)} words)") + print(f"Found {len(groups)} groups\n") + + # Sort by group size descending + sorted_groups = sorted(groups.items(), key=lambda x: -len(x[1])) + + total_to_blocklist = 0 + blocklist_words = [] + + for stem, variants in sorted_groups: + suffix_info = [] + for word in sorted(variants): + suffix = word[-1] + suffix_name = HEBREW_SUFFIXES.get(suffix, "?") + suffix_info.append(f" {word} (-{suffix} = {suffix_name})") + + print(f"Stem: {stem} ({len(variants)} variants)") + for info in suffix_info: + print(info) + + # Keep first variant (alphabetically), blocklist the rest + keep = sorted(variants)[0] + to_block = [w for w in sorted(variants) if w != keep] + blocklist_words.extend(to_block) + total_to_blocklist += len(to_block) + print(f" → Keep: {keep}, blocklist: {to_block}") + print() + + print(f"{'='*50}") + print(f"Total groups: {len(groups)}") + print(f"Total words to blocklist: {total_to_blocklist}") + print(f"\nBlocklist additions (copy-paste ready):") + for w in sorted(blocklist_words): + print(w) + + +# --------------------------------------------------------------------------- +# Subcommand: hebrew-quality +# --------------------------------------------------------------------------- + + +def cmd_hebrew_quality(args): + """Cross-reference Hebrew daily words against wordfreq for quality issues.""" + try: + from wordfreq import zipf_frequency + except ImportError: + print( + "Error: wordfreq library required. Install with: pip install wordfreq", file=sys.stderr + ) + sys.exit(1) + + words = load_daily_words("he") + if not words: + print("No Hebrew daily words found", file=sys.stderr) + sys.exit(1) + + print(f"Hebrew daily word quality analysis ({len(words)} words)") + print(f"Cross-referencing with wordfreq (Wikipedia, Reddit, Google Books, etc.)\n") + + # Score each word + not_in_wordfreq = [] + low_wordfreq = [] + normal = [] + + for word in words: + zf = zipf_frequency(word, "he") + if zf == 0.0: + not_in_wordfreq.append((word, zf)) + elif zf < 2.0: + low_wordfreq.append((word, zf)) + else: + normal.append((word, zf)) + + # Report + print(f"Category breakdown:") + print(f" Normal (zipf >= 2.0): {len(normal):>5} words") + print(f" Low frequency (zipf < 2.0): {len(low_wordfreq):>5} words") + print(f" Not in wordfreq at all: {len(not_in_wordfreq):>5} words") + + if not_in_wordfreq: + print(f"\n{'='*50}") + print(f"Words NOT found in wordfreq ({len(not_in_wordfreq)} words)") + print(f"These may be proper nouns, obscure, or malformed:") + print(f"{'='*50}") + # Show first N + limit = args.limit or 100 + for word, zf in sorted(not_in_wordfreq)[:limit]: + print(f" {word}") + if len(not_in_wordfreq) > limit: + print(f" ... and {len(not_in_wordfreq) - limit} more") + + if low_wordfreq: + print(f"\n{'='*50}") + print(f"Low-frequency words (zipf < 2.0, {len(low_wordfreq)} words)") + print(f"These may be uncommon or domain-specific:") + print(f"{'='*50}") + low_wordfreq.sort(key=lambda x: x[1]) + limit = args.limit or 50 + for word, zf in low_wordfreq[:limit]: + print(f" {word} (zipf={zf:.2f})") + if len(low_wordfreq) > limit: + print(f" ... and {len(low_wordfreq) - limit} more") + + # Summary stats + all_zf = [zipf_frequency(w, "he") for w in words] + has_freq = [z for z in all_zf if z > 0] + if has_freq: + avg = sum(has_freq) / len(has_freq) + print( + f"\nWordfreq coverage: {len(has_freq)}/{len(words)} words ({100*len(has_freq)/len(words):.1f}%)" + ) + print(f"Average zipf frequency (of found words): {avg:.2f}") + + +# --------------------------------------------------------------------------- +# Main +# --------------------------------------------------------------------------- + + +def main(): + parser = argparse.ArgumentParser(description="Word quality analysis for Arabic and Hebrew") + subparsers = parser.add_subparsers(dest="command", help="Commands") + + # char-freq + cf = subparsers.add_parser("char-freq", help="Character frequency table") + cf.add_argument("lang", help="Language code (e.g., ar, he)") + cf.add_argument( + "--main-list", action="store_true", help="Use main word list instead of daily words" + ) + + # difficult-words + dw = subparsers.add_parser("difficult-words", help="List words with rare characters") + dw.add_argument("lang", help="Language code") + dw.add_argument( + "--threshold", type=float, default=None, help="Min char frequency threshold (e.g., 0.03)" + ) + dw.add_argument("--limit", type=int, default=None, help="Max words to show") + + # hebrew-suffixes + hs = subparsers.add_parser("hebrew-suffixes", help="Find Hebrew suffix variant groups") + hs.add_argument( + "--min-group-size", type=int, default=3, help="Min group size to report (default: 3)" + ) + + # hebrew-quality + hq = subparsers.add_parser( + "hebrew-quality", help="Cross-reference Hebrew daily words with wordfreq" + ) + hq.add_argument("--limit", type=int, default=None, help="Max words to show per category") + + args = parser.parse_args() + + if args.command == "char-freq": + cmd_char_freq(args) + elif args.command == "difficult-words": + cmd_difficult_words(args) + elif args.command == "hebrew-suffixes": + cmd_hebrew_suffixes(args) + elif args.command == "hebrew-quality": + cmd_hebrew_quality(args) + else: + parser.print_help() + + +if __name__ == "__main__": + main() diff --git a/scripts/improve_word_lists.py b/scripts/improve_word_lists.py index df59375..d5a00ca 100644 --- a/scripts/improve_word_lists.py +++ b/scripts/improve_word_lists.py @@ -444,6 +444,29 @@ def process_language( # Sort alphabetically for reviewability daily_words.sort() + # === Character difficulty filter === + # For languages with large character sets (e.g., Arabic), remove words + # containing very rare characters that make them nearly impossible to guess. + CHAR_DIFFICULTY_THRESHOLD = {"ar": 0.03} + if lang in CHAR_DIFFICULTY_THRESHOLD: + threshold = CHAR_DIFFICULTY_THRESHOLD[lang] + from collections import defaultdict + + char_counts = defaultdict(int) + for w in daily_words: + for c in set(w): + char_counts[c] += 1 + char_freq = {c: count / len(daily_words) for c, count in char_counts.items()} + rare_chars = {c for c, f in char_freq.items() if f < threshold} + if rare_chars: + pre_filter = len(daily_words) + daily_words = [w for w in daily_words if not any(c in rare_chars for c in w)] + removed = pre_filter - len(daily_words) + print( + f" Character difficulty filter ({threshold*100:.0f}%): " + f"removed {removed} words with rare chars {rare_chars}" + ) + # Safety: every daily word must be in existing word list invalid_daily = [w for w in daily_words if w not in existing_word_set] assert not invalid_daily, f"BUG: daily words not in _5words.txt: {invalid_daily[:5]}" diff --git a/tests/test_word_lists.py b/tests/test_word_lists.py index 5aab9a5..fb69878 100644 --- a/tests/test_word_lists.py +++ b/tests/test_word_lists.py @@ -331,3 +331,50 @@ def test_no_roman_numerals_in_daily_words(self, lang): pytest.skip(f"{lang}: No daily words") romans = [w for w in daily if is_roman_numeral(w)] assert not romans, f"{lang}: Found Roman numerals in daily words: {romans[:10]}" + + def test_arabic_no_rare_characters_in_daily_words(self): + """Arabic daily words should not contain very rare characters (< 3% frequency). + + Characters like آ, إ, ؤ, ى, ظ appear in < 3% of words, making them + nearly impossible to guess through elimination. + """ + daily = load_daily_words("ar") + if not daily: + pytest.skip("No Arabic daily words") + from collections import defaultdict + + char_counts = defaultdict(int) + for w in daily: + for c in set(w): + char_counts[c] += 1 + char_freq = {c: count / len(daily) for c, count in char_counts.items()} + threshold = 0.03 + rare_chars = {c for c, f in char_freq.items() if f < threshold} + bad_words = [w for w in daily if any(c in rare_chars for c in w)] + assert not bad_words, ( + f"Arabic: {len(bad_words)} daily words contain characters below " + f"{threshold*100:.0f}% frequency. Rare chars: {rare_chars}. " + f"Examples: {bad_words[:5]}" + ) + + def test_hebrew_no_large_suffix_groups_in_daily_words(self): + """Hebrew daily words should not have large suffix variant groups. + + Groups of 3+ words sharing a stem with different possessive/construct + suffixes waste the daily word pool with near-identical words. + """ + daily = load_daily_words("he") + if not daily: + pytest.skip("No Hebrew daily words") + from collections import defaultdict + + suffixes = {"ו", "י", "ם", "ן", "ה", "ך", "ת"} + groups = defaultdict(list) + for word in daily: + if len(word) == 5 and word[-1] in suffixes: + groups[word[:-1]].append(word) + large_groups = {stem: variants for stem, variants in groups.items() if len(variants) >= 3} + assert not large_groups, ( + f"Hebrew: {len(large_groups)} suffix groups with 3+ variants. " + f"Examples: {dict(list(large_groups.items())[:3])}" + ) diff --git a/webapp/data/languages/ar/ar_blocklist.txt b/webapp/data/languages/ar/ar_blocklist.txt index e1ef549..d4a549a 100644 --- a/webapp/data/languages/ar/ar_blocklist.txt +++ b/webapp/data/languages/ar/ar_blocklist.txt @@ -781,3 +781,37 @@ بالون باليد باليه + +# Proper nouns, place names, demonyms (2026-02-23) +# Manual review: names and demonyms with no common Arabic meaning +باريس +برلين +بورما +بيروت +تركيا +تريزا +جبريل +داليا +راموس +روسيا +سقراط +سوريا +صقلية +صوفيا +طوكيو +فاروق +فرنسا +فرنسي +فينوس +فيينا +كشمير +لبنان +لومان +ليبيا +ليمان +ماريا +مدريد +موسكو +وارسو +يعقوب +يوحنا diff --git a/webapp/data/languages/ar/ar_daily_words.txt b/webapp/data/languages/ar/ar_daily_words.txt index ee8543d..128f1f6 100644 --- a/webapp/data/languages/ar/ar_daily_words.txt +++ b/webapp/data/languages/ar/ar_daily_words.txt @@ -1,18 +1,3 @@ -آثاره -آخرون -آسيوي -آنذاك -أخذنا -أرأيت -أسباب -أسوار -ألعاب -أموال -إرضاء -إلغاء -إمضاء -إنتاج -إنشاء ابتلع ابريل ابطال @@ -37,9 +22,7 @@ اجراء اجزاء احترم -احتفظ احداث -احذية احرار احساس احسنت @@ -50,7 +33,6 @@ اخبار اختار اخترق -اختفى اختلط اخراج اخرجه @@ -63,7 +45,6 @@ ادوات اربعة ارتاح -ارتدى ارتفع ارتكب ارجاء @@ -76,13 +57,11 @@ ازداد ازعاج ازياء -اسأله اسئلة اساسا اساسي اسباب اسبوع -استاذ استدر استقر استقل @@ -97,6 +76,7 @@ اسنان اشارة اشباح +اشتغل اشخاص اشعال اشكال @@ -111,6 +91,7 @@ اضافة اضافي اضعاف +اطباء اطفاء اطفال اطلاع @@ -145,11 +126,10 @@ اقدام اقناع اكمال +اكياس اماكن امامه -امامى امتلك -امرأة امسكت امنية اموات @@ -158,19 +138,16 @@ اميرة انبوب انتحر -انتظر انتقل انتما انجاب انجاز انجيل انحاء -انذار انزلق انسان انصرف انطلق -انقاذ انقلب انواع اوامر @@ -181,18 +158,14 @@ اولئك اولاد ايجاد -ايذاء ايصال ايقاف ايمان اينما -بأعلى -بأيام بائعة بادرة باردة بارود -باريس باكرا ببغاء بتاتا @@ -213,15 +186,12 @@ بدوري بديعة بديلة -بذاته -برأسه براءة براعة برامج برفقة برقية بركان -برلين برمته برميل برودة @@ -231,7 +201,6 @@ بساعة بستان بسلام -بشأنه بشراء بشرية بصدده @@ -248,12 +217,13 @@ بطانة بطريق بطولة -بعدئذ +بعقله بعودة بعيدا بعيدة بعينه بغداد +بفارغ بقالة بقايا بقشيش @@ -280,7 +250,6 @@ بوابة بودرة بورصة -بورما بوصلة بوضوح بوعده @@ -289,22 +258,9 @@ بوليس بيانو بيديه -بيروت بيضاء بيطري بينما -تأبين -تأثير -تأجير -تأجيل -تأخير -تأدية -تأسيس -تأكيد -تأليف -تأمين -تأنيب -تأييد تابعة تابوت تاريخ @@ -329,7 +285,6 @@ تجويف تحالف تحديد -تحذير تحرير تحسين تحصيل @@ -361,11 +316,6 @@ تدمير تدوير تدوين -تذاكر -تذكار -تذكرة -تذكري -تذكير تراجع تراكم تربية @@ -377,12 +327,10 @@ ترفيه ترقية تركته -تركيا تركيب تركيز ترميم ترياق -تريزا تزايد تزويد تزوير @@ -424,6 +372,7 @@ تصميم تصنيع تصنيف +تصوري تصويب تصويت تصوير @@ -439,14 +388,12 @@ تعارض تعاقب تعالج -تعالى تعامل تعبئة تعبير تعداد تعديل تعدين -تعذيب تعريض تعريف تعزيز @@ -455,7 +402,6 @@ تعليق تعليم تعيين -تغذية تغطية تغيير تفاعل @@ -487,6 +433,7 @@ تقويم تقييد تكبير +تكتيك تكرار تكريم تكسير @@ -500,7 +447,6 @@ تلقاء تلقين تلميح -تلميذ تلميع تماسك تمالك @@ -528,10 +474,7 @@ تنسيق تنشيط تنصيب -تنظيف -تنظيم تنفسي -تنفيذ تنكري تنورة تنوين @@ -550,7 +493,6 @@ توصية توصيل توضيح -توظيف توفير توقيت توقيع @@ -577,7 +519,6 @@ ثيابه ثيران جاءوا -جاؤوا جائزة جارية جاسوس @@ -585,20 +526,16 @@ جامعة جانبا جانبه -جانبى جانبي جبانة -جبريل جبناء جثمان جداول جدران جديدا جديدة -جذابة جرائم جراحة -جرذان جريدة جريمة جزئيا @@ -627,15 +564,16 @@ جنونه جنوني جوائز +جوابا جوارب جوانب جيران حاجته حادثة +حارقة حاسمة حاضرة حاضنة -حافظة حافلة حالما حاليا @@ -653,6 +591,7 @@ حديقة حرائق حرارة +حراري حراسة حربية حركات @@ -666,7 +605,6 @@ حضارة حضانة حضوري -حظيرة حفيدة حقائب حقائق @@ -692,14 +630,12 @@ حنجرة حواجز حوادث -حوالى حورية حياته حياتي حيازة حيتان حيثما -حينئذ حينما حيوات حيوان @@ -760,9 +696,9 @@ دائري دائما داخلي -داليا دبابة دجاجة +دخلاء دخولي دراجة دراسة @@ -802,13 +738,6 @@ ديكور دينار دينية -ذاتية -ذاكرة -ذبابة -ذخيرة -ذريعة -رأيته -رؤساء رئاسة رئيسا رئيسة @@ -823,7 +752,6 @@ راضية رافعة راقصة -راموس رايته رباعي رجالي @@ -851,7 +779,6 @@ رواية روبية روحية -روسيا رويدك رياضة رياضي @@ -877,7 +804,6 @@ زيادة زيارة زيتون -سؤالا سابقا سابقة ساحات @@ -889,6 +815,7 @@ ساعده ساقطة ساقيه +ساكنا سباحة سبحان سبعون @@ -919,9 +846,7 @@ سعادة سعداء سفينة -سقراط سقيفة -سكارى سكران سكوتي سكينة @@ -946,7 +871,6 @@ سوائل سوابق سوداء -سوريا سيادة سيارة سياسة @@ -979,13 +903,11 @@ شريكة شطرنج شطيرة -شظايا شعبية شعوره شعوري شقراء شقيقة -شكاوى شمالا شمالي شمسية @@ -1024,7 +946,6 @@ صفارة صفراء صفيحة -صقلية صلابة صناعة صناعي @@ -1033,7 +954,6 @@ صوابه صوتية صورته -صوفيا صياغة صيانة صيحات @@ -1047,6 +967,7 @@ ضروري ضريبة ضعفاء +ضغائن ضغينة ضمادة ضوئية @@ -1057,6 +978,7 @@ طائلة طابور طارئة +طاعون طاقته طالما طاولة @@ -1078,7 +1000,6 @@ طماطم طوابق طوارئ -طوكيو طويلا طيران عائدة @@ -1096,7 +1017,6 @@ عاقبة عاقلة عالمة -عالمى عالمي عالية عاليه @@ -1111,7 +1031,6 @@ عدائي عدالة عديدة -عذراء عربون عرجاء عرضية @@ -1132,9 +1051,6 @@ عصيان عضوية عطشان -عظامي -عظماء -عظيمة عفريت عقارب عقاري @@ -1159,7 +1075,6 @@ عميلة عناصر عناية -عندئذ عندما عنصري عنوان @@ -1176,7 +1091,7 @@ غالبا غالية غثيان -غذائي +غرائز غرابة غرامة غرامي @@ -1194,7 +1109,6 @@ فائدة فاتحة فادحة -فاروق فاصلة فترات فتيات @@ -1208,8 +1122,6 @@ فرضية فرعون فرقعة -فرنسا -فرنسي فريدة فريسة فستان @@ -1232,8 +1144,6 @@ فواكه فوضوي فيضان -فينوس -فيينا قائلا قائلة قائمة @@ -1241,7 +1151,6 @@ قابله قاتله قادرة -قاذفة قاضية قاطعة قاعدة @@ -1259,14 +1168,11 @@ قبيحة قبيلة قتلوا -قدامى +قداحة قدماء قدميه قديما قديمة -قذائف -قذارة -قذيفة قراءة قرابة قرارا @@ -1274,7 +1180,6 @@ قريبا قسيمة قصائد -قصارى قصيدة قصيرة قضائي @@ -1282,6 +1187,7 @@ قضبان قضيته قطرات +قطنية قلادة قلبية قليلا @@ -1295,13 +1201,13 @@ قوائم قوارب قواعد +قوسين قيادة قياسي قيراط قيمته كابوس كاتبة -كاذبة كارثة كانوا كانون @@ -1323,7 +1229,6 @@ كرتون كريمة كشافة -كشمير كفاءة كفالة كفاية @@ -1338,20 +1243,16 @@ كيران كيفما كيفية -لأجلك لائحة لاحقا لافتة لامعة -لبنان لحالة لحانة لحساب -لحظات لخدمة لدرجة لدينا -لذكرى لزوجة لسانه لساني @@ -1362,32 +1263,17 @@ لفائف لفافة لكونه -لماذا لنفسه -لومان لياقة ليالي -ليبيا ليتني ليلية -ليمان ليمون -مأجور -مأخوذ -مأدبة -مأساة -مأمور -مؤتمر -مؤخرا -مؤخرة -مؤسسة -مؤلما مئوية مائدة مائية مادية ماركة -ماريا ماسون ماشية مالكي @@ -1398,14 +1284,10 @@ مباشر مبالغ مبتدئ -مبتذل مبعوث مبكرا مبكرة مبنية -متأخر -متأسف -متأكد متاجر متاعب متجمد @@ -1429,6 +1311,7 @@ متعلق متعلم متعمد +متغير متفرغ متفوق متقدم @@ -1458,9 +1341,9 @@ مجزرة مجموع مجنون +مجيئه محارب محاسب -محافظ محالة محايد محبوب @@ -1481,10 +1364,6 @@ محصور محصول محطمة -محظور -محظوظ -محفظة -محفوظ محكمة محكوم محلول @@ -1509,6 +1388,7 @@ مختلف مخدرة مخزون +مخلوط مخلوق مخمور مداخل @@ -1517,18 +1397,15 @@ مدراء مدرسة مدرسي -مدريد +مدعاة مدمرة مدنية مدونة مديرة مدينة -مذبحة -مذكرة -مذكور -مذياع مراحل مرارا +مرارة مراسل مراسم مرافق @@ -1562,8 +1439,6 @@ مزدحم مزدوج مزرعة -مسألة -مسؤول مسائل مساحة مسارح @@ -1581,7 +1456,6 @@ مستمر مستمع مستند -مستوى مسجلة مسجون مسحوق @@ -1609,12 +1483,12 @@ مشتبه مشترك مشتعل +مشحون مشددة مشدود مشرحة مشروب مشروع -مشعوذ مشغول مشكلة مشكوك @@ -1626,7 +1500,6 @@ مصالح مصانع مصباح -مصطفى مصطلح مصغرة مصلحة @@ -1650,18 +1523,15 @@ مطاعم مطالب مطرقة -مطفأة مطلقا مطلقة مطلوب مطمئن -مظلمة معادن معارض معارف معارك معاطف -معافى معاقب معالم معامل @@ -1675,14 +1545,13 @@ معدات معدني معدية -معذرة معرفة معركة +معروض معروف معزول معسكر معضلة -معظمه معقول معلقة معلمة @@ -1703,6 +1572,7 @@ مفتول مفتون مفرغة +مفصلة مفضلة مفعول مفقود @@ -1754,7 +1624,6 @@ ملتزم ملتصق ملحمة -ملحوظ ملزمة ملعقة ملعون @@ -1780,23 +1649,18 @@ مناسب مناشف مناطق -مناظر مناعة منافس منافع منافق -منبوذ منتبه منتجع منتزه منتشر منتصف -منتظم منتفخ -منجذب منحدر منحرف -منحنى منخفض مندوب منديل @@ -1804,7 +1668,6 @@ منزلة منزله منزلي -منشأة منشار منشفة منصبه @@ -1812,9 +1675,6 @@ منطاد منطقة منطقي -منظار -منظمة -منظور منعزل منعطف منفرد @@ -1830,16 +1690,13 @@ موجات موجود موديل -موسكو موضوع موعدا موقعة موقعه موقفه موقوف -مولاى مولود -ميؤوس ميثاق ميدان ميراث @@ -1853,9 +1710,10 @@ نادرا نادرة نارية -نافذة +نافعة ناقلة نباتي +نبلاء نبوءة نتائج نتيجة @@ -1868,11 +1726,6 @@ نصائح نصيبه نصيحة -نظارة -نظافة -نظامي -نظرات -نظرية نعناع نفاية نفسية @@ -1882,28 +1735,22 @@ نقدية نقودا نكران -نماذج -نموذج -نوافذ نوايا نوبات نووية نيابة نيران نيسان -وأخرى واجبه واحدا واحدة واحده واردة -وارسو واسعة واقعة واقعي واقفا واقية -والتى والحق والدة والده @@ -1930,13 +1777,12 @@ ورقية وزارة وزراء +وسائد وسائل وسادة وسامة وسيطة وسيلة -وشأنه -وشأني وصاية وصلني وصولي @@ -1944,8 +1790,6 @@ وضعية وضيعة وطنية -وظائف -وظيفة وفيات وقائع وقائي @@ -1953,7 +1797,6 @@ وقتما وقعوا وكالة -وكذلك وكلاء ولائي ولادة @@ -1965,7 +1808,6 @@ ويوجد ياردة يبتسم -يتأخر يتصور يحتمل يخصني @@ -1979,21 +1821,17 @@ يسعني يعرفه يعطني -يعقوب يغتفر يفعله يقاوم يقوله -يلاحظ يمكنه يمينا يميني يناير -ينبغى ينبوع ينسون يوافق -يوحنا يوليو يومنا يومية diff --git a/webapp/data/languages/he/he_5words_supplement.txt b/webapp/data/languages/he/he_5words_supplement.txt index 08bf560..c2d7476 100644 --- a/webapp/data/languages/he/he_5words_supplement.txt +++ b/webapp/data/languages/he/he_5words_supplement.txt @@ -151,7 +151,6 @@ אונית אוסטן אוסטר -אוסיפ אוסמה אוסמו אוסמן @@ -446,7 +445,6 @@ אלמור אלמים אלמלך -אלמלכ אלנור אלנתן אלסבת @@ -506,7 +504,7 @@ אמינם אמיתי אמליה -אממממ +אמממם אמנדה אמסטר אמסלם @@ -1057,7 +1055,7 @@ בביאה בביאת בביבי -בביהמ +בביהם בביוב בביון בביוף @@ -1772,7 +1770,7 @@ בחוקת בחורב בחורף -בחורצ +בחורץ בחורש בחושך בחושן @@ -1942,7 +1940,7 @@ בטיטו בטיים בטייס -בטייפ +בטייף בטייק בטילה בטילי @@ -1983,9 +1981,9 @@ בטרוף בטרור בטרחה -בטריפ +בטריף בטריק -בטרמפ +בטרמף בטרנד בטרנס בטרשת @@ -2000,7 +1998,7 @@ ביבאס ביבוא ביבול -ביבופ +ביבוף ביבנה ביברס ביבשה @@ -2164,7 +2162,6 @@ בישבן בישוב בישוע -בישופ בישות בישין בישעי @@ -3586,7 +3583,7 @@ בקאבו בקאלה בקאלי -בקאמפ +בקאמף בקאנו בקאסל בקארי @@ -3668,7 +3665,7 @@ בקייב בקייט בקיים -בקייפ +בקייף בקייץ בקימה בקינג @@ -3684,7 +3681,7 @@ בקלות בקלחת בקלטת -בקליפ +בקליף בקללה בקללת בקלמר @@ -4307,7 +4304,7 @@ גאלאץ גאלגר גאלוס -גאלופ +גאלוף גאליה גאלים גאליס @@ -4455,7 +4452,7 @@ גלאנץ גלברד גלברט -גלגלצ +גלגלץ גלגמש גלדיס גלדמן @@ -4610,7 +4607,7 @@ דאנון דאנטה דאניה -דאנלפ +דאנלף דאנפי דאנקן דאסור @@ -4883,7 +4880,7 @@ דנינג דנינו דניקה -דנלופ +דנלוף דנסון דנפיק דנפקא @@ -5178,7 +5175,7 @@ האספן האסקי האסקל -האפאצ +האפאץ האפוד האפוי האפון @@ -5227,7 +5224,7 @@ הארלן הארמי הארנב -הארנכ +הארנך הארנק הארסי הארסן @@ -5727,7 +5724,7 @@ ההיגד ההיזק ההיטל -ההייפ +ההייף ההיית ההיכל ההיכר @@ -6221,7 +6218,7 @@ הטייה הטיים הטייס -הטייפ +הטייף הטייק הטיפש הטירה @@ -6259,10 +6256,9 @@ הטריה הטריו הטריז -הטריפ הטריק הטרמה -הטרמפ +הטרמף הטרנד הטרנס הטרסה @@ -6409,7 +6405,7 @@ היציע היצרי היצרן -היקאפ +היקאף היקום היקוק היקים @@ -6496,7 +6492,7 @@ הכיפה הכיפי הכיצד -הכירצ +הכירץ הכיתה הכלאם הכלבו @@ -7876,7 +7872,7 @@ הספרס הסצנה הסקאד -הסקופ +הסקוף הסקלה הסקסי הסקרן @@ -8194,7 +8190,7 @@ הפרוט הפרוס הפרוע -הפרופ +הפרוף הפרוץ הפרוש הפרזי @@ -8327,7 +8323,7 @@ הקאטה הקאלט הקאמה -הקאמפ +הקאמף הקאסט הקאפו הקארי @@ -8395,7 +8391,7 @@ הקטטר הקטיף הקטלן -הקטנצ +הקטנץ הקטרה הקטרי הקיאק @@ -8429,7 +8425,7 @@ הקליל הקלים הקליע -הקליפ +הקליף הקליר הקללה הקלסי @@ -8462,7 +8458,7 @@ הקערה הקפדן הקפוא -הקפוצ +הקפוץ הקפטן הקפית הקפלה @@ -8625,7 +8621,7 @@ הרמאן הרמבם הרמוז -הרמטכ +הרמטך הרמיה הרמים הרמפה @@ -10889,7 +10885,7 @@ והשיב והשיג והשיח -והשימ +והשים והשיק והשיר והשכל @@ -11002,7 +10998,7 @@ ווינט וויני ווינס -ווינצ +ווינץ ווינר וויען וויצק @@ -11058,7 +11054,7 @@ וועדי וועדת וועלט -ווקופ +ווקוף ווקמן וורדה וורדי @@ -12389,7 +12385,7 @@ ולטפל ולטפס וליאו -וליאכ +וליאך וליאם וליאן וליאת @@ -14502,7 +14498,7 @@ ופרדס ופרוס ופרוע -ופרופ +ופרוף ופרוץ ופרוש ופרות @@ -14771,7 +14767,7 @@ וקליי וקליל וקלים -וקליפ +וקליף וקללה וקללת וקלמן @@ -14964,7 +14960,7 @@ וריטה וריטי ורייס -ורייצ +ורייץ וריכז ורימה ורינה @@ -15951,7 +15947,7 @@ טולון טוליו טולין -טוליפ +טוליף טולמן טולסה טומאס @@ -16065,7 +16061,7 @@ טראגי טראוב טראוט -טראמפ +טראמף טראנג טראני טראנס @@ -16279,7 +16275,6 @@ ירדמו ירויח ירמגל -ירמוכ ירמות יררדי ישאלך @@ -17023,7 +17018,7 @@ כפסול כפעיל כפקיד -כפרופ +כפרוף כפרוש כפריט כפרעה @@ -17768,7 +17763,7 @@ לבטנה לבטנו לביבי -לביהמ +לביהם לביוב לביון לביוץ @@ -17951,7 +17946,7 @@ לגומה לגומי לגונן -לגוסמ +לגוסם לגופה לגופו לגופי @@ -19101,7 +19096,7 @@ לטריי לטריס לטרמן -לטרמפ +לטרמף לטרנד לטרנס לטרפד @@ -20877,7 +20872,7 @@ לפרום לפרוס לפרוע -לפרופ +לפרוף לפרוץ לפרוק לפרוש @@ -21149,7 +21144,7 @@ לקייל לקיים לקיין -לקייפ +לקייף לקילו לקימה לקינג @@ -21171,7 +21166,7 @@ לקליי לקלים לקליע -לקליפ +לקליף לקללה לקלמן לקלפי @@ -21356,7 +21351,7 @@ לריטה לרייך לריין -לרייצ +לרייץ לרייר לרינה לריסה @@ -21847,7 +21842,7 @@ םלועב םלועה םלועל -םלועמ +םלועם מאבדן מאבות מאביב @@ -22024,7 +22019,7 @@ מאתיו מאתים מאתיס -מבארכ +מבארך מבגדד מבגדי מבדרך @@ -23561,7 +23556,7 @@ מפצצת מפראג מפרדס -מפרופ +מפרוף מפרוץ מפרחי מפרנק @@ -23896,7 +23891,7 @@ מתשעת מתשרי מתתיה -ןבומכ +ןבומך ןויער ןיידע ןפואב @@ -24201,7 +24196,7 @@ סטאבס סטאוט סטאטי -סטאמפ +סטאמף סטארט סטארס סטארק @@ -24246,7 +24241,7 @@ סטפנו סטפני סטראט -סטראפ +סטראף סטרבו סטרוד סטרול @@ -24257,7 +24252,7 @@ סטריט סטרים סטרין -סטריפ +סטריף סטריק סטרלה סטרנד @@ -24428,7 +24423,7 @@ סנטוס סנטנה סנטרל -סנייפ +סנייף סנייק סנסאי סנסיי @@ -24500,7 +24495,7 @@ סקונס סקיטר סקייס -סקייפ +סקייף סקילס סקיני סקינס @@ -24509,7 +24504,7 @@ סקיפר סקסון סקסים -סקראפ +סקראף סקראץ סקרוג סקרין @@ -24910,7 +24905,7 @@ פילוט פילזן פיליס -פיליפ +פיליף פילפל פילקו פינאר @@ -24939,7 +24934,7 @@ פיפטי פיפין פיציו -פיקאצ +פיקאץ פיקאר פיקוק פיקלס @@ -25292,7 +25287,7 @@ קדרון קהלות קהלני -קואופ +קואוף קואוץ קואטס קואלו @@ -25543,7 +25538,7 @@ קיסטר קיסין קיעאן -קיפסמ +קיפסם קיציס קיקרו קיראו @@ -25561,7 +25556,7 @@ קלאוד קלאון קלאוס -קלאמפ +קלאמף קלאסה קלאסי קלארה @@ -25725,7 +25720,7 @@ קרייר קרינן קריסי -קריספ +קריסף קריפי קריפס קרישק @@ -25797,7 +25792,6 @@ ראפין ראפלס ראקון -ראשונ ראשיד ראשין רבודה @@ -25909,7 +25903,7 @@ רושדי רזיאל רזניק -רחואמ +רחואם רחימי רחמאן רחמין @@ -25998,7 +25992,7 @@ רכניץ רמדור רמדיה -רמולכ +רמולך רמונה רמיין רמיקס @@ -26032,7 +26026,7 @@ רקהאם רקובב רקובר -רקורפ +רקורף רקיעא רקנטי רשאין @@ -27615,7 +27609,7 @@ שטפון שטקסט שטרום -שטרופ +שטרוף שטרוק שטרחו שטרית @@ -29415,7 +29409,7 @@ שפרבר שפרדי שפרוט -שפרופ +שפרוף שפרוש שפרחה שפרחו @@ -29672,7 +29666,7 @@ שריחו שריחם שריחף -שרייצ +שרייץ שרייק שרייר שריכז diff --git a/webapp/data/languages/he/he_blocklist.txt b/webapp/data/languages/he/he_blocklist.txt index 4b2d3d6..f36caea 100644 --- a/webapp/data/languages/he/he_blocklist.txt +++ b/webapp/data/languages/he/he_blocklist.txt @@ -55988,3 +55988,1486 @@ תתרתח תתשאל תתשנה + +# Quality filter additions (2026-02-23) +# Suffix variant duplicates, words not in wordfreq, low-frequency words +# Total additions: 807 words +גביעה +גביעו +גביעי +גביעם +גבישו +גבסיי +גדועה +גדורי +גדליי +גודלו +גודלת +גודעת +גוונת +גוועם +גווען +גוועת +גוחכת +גויסת +גומרי +גונחי +גוננת +גופנם +גורלו +גורלי +גורלם +גורמי +גורמם +גורמן +גורמת +גורנם +גוררי +גזיזה +גחונן +גטואם +גיבבה +גידלו +גידלת +גידפה +גיורן +גיחכו +גייסם +גילחה +גימרה +גימרו +גלידו +גלידם +גלידת +גלליי +גלענם +גנאיי +גנונם +גנחנה +געגעי +גפניי +גרברת +גרוטה +גרועי +גרזנן +גרמיי +גרפיי +גשושן +גשמיי +דאונה +דבבנה +דגדגת +דגיגה +דובבם +דוברי +דוברר +דוברת +דוגלי +דוגמן +דוגמת +דווחו +דווחי +דוכנה +דוכסה +דולבו +דוממם +דונגה +דונגי +דחסנה +דחפנה +דיבבה +דיבגו +דיברו +דיברת +דיורו +דיורן +דיממה +דיקטי +דיקנן +דלגנה +דלדלם +דלדלת +דלילת +דללנה +דמיעה +דמנטי +דסקסי +דסקסם +דפקטן +דקדקת +דקיקי +דקלמו +דקלמן +דקלמת +דרדסה +דרוכת +דרעקי +דשדשם +זאירי +זגזגן +זדונן +זובחת +זובנה +זוגגה +זוגיי +זווגה +זוחלה +זוינה +זויפת +זוככה +זומרו +זומרת +זוקפי +זורמי +זיבלה +זיבלו +זיווי +זיונה +זיונן +זיופה +זייפן +זייפת +זיככת +זימנת +זיפיי +זיתיי +זמזמם +זמירן +זניחי +זעופה +זעמיי +זקופת +זקיפה +זקיפן +זקיפת +זקיקו +זקיקם +זקירת +זרועו +זרועי +זרוקת +זרזנה +זריזי +חבולת +חביבי +חביבם +חבלנם +חגיגי +חגיגת +חדשנה +חובבן +חובבת +חובקו +חודדת +חוטמה +חוטרם +חולשי +חומטה +חומלי +חומצם +חומצת +חומרי +חומרת +חוסרם +חופשי +חופשת +חוצצי +חוקרן +חוקרת +חושבי +חושבת +חושדי +חושכו +חותלו +חותנם +חזונן +חזירי +חזירם +חזרנה +חטאיי +חטיפי +חטיפת +חיבבו +חיבבת +חיברת +חיוטו +חיזקת +חיטאת +חיטטת +חיטיי +חייגם +חייטו +חייטם +חייכו +חייכי +חייכת +חיילו +חיילי +חיילת +חיככו +חיללת +חיסלו +חיסלת +חיפפת +חיפשו +חיפשת +חירוק +חישלת +חישקו +חלודם +חלומו +חלומי +חלוקי +חלוקת +חלחלי +חלכאי +חלמאי +חמורו +חמורת +חמציץ +חנוטת +חנכנה +חסודי +חסומי +חסרנה +חפוזת +חצאיי +חצביי +חצובי +חצילה +חצניי +חצצור +חצצרם +חצרנה +חקינה +חקלאם +חרוזן +חרוטו +חרונה +חרושה +חרישו +חרישן +חרמנם +חרמשה +חשאיי +חשושת +חשפנה +חתניי +טאטאם +טבלטי +טובגו +טווחה +טווסו +טווסם +טונפה +טופפו +טופפם +טופרי +טיובי +טייבי +טייחן +טייחת +טיפלו +טיפלת +טיפסת +טכנאו +טלטלת +טלפנן +טנדרם +טעימי +טפטפי +טפסנם +טפסרן +טרגיי +טריזי +טרמפם +טרמפן +טרפדן +טרפדת +טשטשן +טשטשת +סבוכי +סבונן +סביבו +סביבי +סביבם +סביבת +סבסדם +סגלנה +סגריי +סובאת +סובבי +סובבם +סובבת +סוביי +סובסד +סוגרה +סוהרן +סוחפי +סויגו +סוככו +סוכלת +סוכרו +סוכרי +סוכרת +סולאו +סולאת +סולמן +סומכי +סומלו +סומלת +סופדי +סופקי +סורחי +סורנה +סותרי +סחוסם +סחירת +סחלבי +סחפנה +סחררן +סחררת +סטוצן +סטטיי +סטינה +סיבכת +סיגול +סיגלי +סיגלם +סידרו +סידרת +סיוגה +סיוגן +סיוטו +סיידת +סייחו +סיימו +סיימי +סיימם +סיימן +סיימת +סייסה +סייסי +סייסן +סייעם +סייפם +סיירי +סיירת +סיכנת +סילפה +סימלת +סיממו +סיממת +סימנת +סינפו +סיפקו +סיפקת +סיפרו +סיפרת +סיקסק +סיקרת +סירבו +סירבת +סירנת +סכומן +סלילם +סלילן +סלסלן +סלפיי +סמלצת +סממנן +סנגרו +סנדקה +סניטה +סנכרן +סנתזן +ספירו +ספירת +ספסלו +ספסלן +ספסרת +סקולי +סקסטה +סרביי +סרבלה +סרבלם +סרבנה +סרוגי +סרחנה +סריגם +סרפדו +סתגלן +סתתנה +עבורו +עבורי +עבורם +עבורן +עבינה +עגולת +עגומת +עגורה +עגילם +עדינת +עובדי +עובדת +עוגבי +עוגבם +עוגלת +עודדת +עוורה +עוזרי +עוזרם +עוזרת +עוכלה +עוכלת +עולמו +עולמי +עולמם +עולמן +עולשה +עולשי +עולשם +עופפה +עופפם +עוקדן +עוקמו +עוקצם +עוקצן +עוריי +עורפן +עורפת +עורקם +עורקן +עורקת +עוררם +עוררת +עושקי +עושקת +עותקה +עיורן +עיינת +עיכלו +עימדת +עישנת +עכואי +עכוזן +עכירת +עלולת +עלעלה +עמודו +עמודי +עמודם +עמומי +עמידי +עמילה +עמעמה +ענברה +ענויי +ענניי +עסוקת +עסקיי +עסקנן +עצבנת +עצירן +עצירת +עצמיי +עקובי +עקודה +עקודי +עקלנה +עקצצו +עקצצת +ערגלו +ערגלן +ערגלת +ערדלי +ערוכת +ערומת +עריצי +עריצן +ערערי +ערערם +ערפלן +ערפלת +עשורו +עשורי +עשורם +עתידו +עתידי +עתידם +פאונם +פברקן +פגודת +פגוטי +פגומת +פגועת +פדלאה +פדנטי +פדרנה +פוגמי +פודלה +פודלם +פוזמו +פוחמה +פויחה +פולגה +פולפל +פולשן +פומלו +פוסחי +פוצצו +פוצצן +פוצצת +פורמי +פורקן +פורקת +פחדיי +פחלצן +פטפטה +פטרלי +פידרת +פיוחה +פיוחו +פיחום +פיטרו +פיטרת +פייטם +פיכחת +פילחו +פילחת +פיללת +פינקת +פיסלה +פיקדה +פיריי +פישטה +פישלה +פישלו +פישלת +פיתחו +פיתחת +פכפכת +פלחיי +פלטיי +פלנטת +פלסיי +פלצור +פלקטו +פמפמי +פנטוז +פנטזן +פנימי +פנימם +פניקי +פנקסם +פנתרו +פסולם +פסיקם +פסליי +פספסו +פספסת +פעוטת +פעירה +פעלנה +פענחי +פעריי +פקודו +פקודת +פקידם +פקידת +פקיחה +פקססי +פקפקן +פקששי +פרומת +פרופת +פריצי +פריצן +פריצת +פרכסי +פרכסם +פרמוט +פרמטו +פרמטת +פרסמו +פרסמת +פשוטו +פשוטי +פשוטת +פשינה +פתקיי +צאצאה +צבוטה +צבועת +צבורה +צבירו +צבענם +צדדנה +צובלי +צוהרו +צוהרי +צווחן +צולבי +צוערה +צופנה +צופפו +צופפם +צוקיי +צחצחן +צחקקו +ציוצן +ציידי +ציידת +צייצי +צייצם +ציירו +ציירם +ציירת +צילמו +צילמת +צימקה +צימרו +ציניי +ציקלי +ציריי +צלבנה +צלבנן +צלויי +צלילו +צלילי +צלילם +צלינה +צלליי +צלענה +צלצלו +צלצלי +צלצלם +צלצלת +צמיגם +צמידם +צמתיי +צנונה +צעיפם +צעירן +צפופת +צפינה +צרורו +צריום +צרפנה +קבאבם +קבועת +קבילי +קבלנן +קבקבי +קברנן +קדריי +קודיי +קוונם +קוונן +קוטעו +קוטרי +קולדי +קולנם +קולנן +קולרי +קומחת +קומנה +קומרה +קוננם +קוסמי +קוסמת +קופצי +קוציי +קוצפת +קוקטי +קורקף +קוששה +קותלי +קטונה +קטיפו +קטיפם +קטלגת +קטריי +קיאקי +קיבלו +קיבלת +קידשת +קיומו +קיומי +קיומם +קיטבה +קיטרו +קיימו +קיימן +קיימת +קימוץ +קינאת +קינחו +קיננת +קיסרה +קיפוץ +קיפחי +קיפצה +קלחיי +קלטרי +קלילת +קלמרו +קלמרם +קלסרם +קלסרן +קמינן +קנאנה +קנבסי +קסומת +קסמיי +קפואת +קצרנם +קרואי +קרובי +קרובת +קרונם +קרטעה +קרנפו +קרנפן +קרפדו +קרפדת +קרקשן +קשיחת +קשינה +קשישן +קשקשה +קשריי +ראינה +רבביי +רגביי +רגוזי +רגוזת +רגימה +רגשיי +רדידו +רדידי +רדידן +רובען +רודדה +רווחי +רווחת +רווקת +רוחצי +רוכבה +רוכבי +רוכבת +רוכלת +רומחי +רוממן +רוסיי +רוססת +רופאי +רופאם +רופאת +רוצחי +רוצחת +רוצצי +רוקקי +רזינה +רחובם +רחפנה +רחרחם +רטובי +רטינת +ריבנה +ריגלת +ריטטה +ריננת +ריסנת +ריפוף +ריצדה +רכונה +רכושו +רכושי +רמוסת +רמזנה +רמזרה +רמציי +רעועי +רעועת +רעשנן +רפאיי +רפואי +רפואת +רציפם +רקעיי +רשופי +רשופת +רתקנה +# Proper nouns and demonyms +דרוזי + +# Quality filter pass 2 (2026-02-23) +# Additional suffix variants, not-in-wordfreq, low-frequency from regenerated pool +# Total additions: 635 words +גאונן +גבאיי +גבהיי +גביען +גבירי +גבירם +גבירת +גבשנה +גדומה +גדורה +גדרנה +גובהת +גוזזי +גוזלו +גולגל +גולחה +גוללן +גולפה +גומדו +גוניי +גועלם +גופנה +גורסי +גורפו +גושרה +גותיי +גזוזת +גזולת +גזומי +גיורם +גיחכת +גיירן +גילמת +גיננת +גלגלן +גלמיי +גלשנה +גלשנם +גמיאת +גנרלי +געגעו +גפרנה +גרברה +גרגרם +גרינה +גריסן +גרענה +גרענת +גרשנה +דביקי +דביקת +דגומה +דגמנת +דואטי +דוגנה +דווחת +דוורם +דוחקו +דוחקי +דוכנם +דולבם +דוממי +דונמה +דופנם +דחיסי +דחיסת +דיבבו +דיבגה +דיבגת +דיונת +דייטי +דילרי +דינגה +דלפקן +דפדפי +דפקטי +דפקיי +דקדנס +דקדקם +דרגשם +דרדסן +דרדרן +דרדרת +דרומי +דרומם +דרכמת +דרשנה +זגגנה +זהמנה +זובלו +זווגת +זוחלם +זוינת +זומנת +זוקנה +זוקנם +זוקקה +זיזיי +זימיי +זיקקו +זמזמי +זממיי +זמרנה +זנקנה +זעומת +זעזעת +זפקיי +זקורי +זרזיי +חבוקת +חביקת +חבלנן +חבשיי +חדדנה +חדילת +חובצי +חוברי +חוברת +חוגנה +חודשם +חווקו +חווקי +חויבת +חויטה +חוילת +חומטו +חומצן +חומשם +חופיי +חופפי +חוצנם +חוקיי +חורבי +חורבן +חורבת +חוריי +חושלו +חותלה +חזיזי +חזירו +חזירן +חזרנם +חיבקת +חיזרה +חילטת +חיפפה +חיתלה +חלביי +חלבנם +חלודו +חלוטת +חלונם +חלוצם +חלוקן +חמוסי +חמריי +חנטרש +חסונת +חסידם +חצופי +חצופת +חצירם +חצצרו +חצרנם +חרוזו +חרוטם +חרוכת +חרונן +חרופת +חרחרו +חריצה +חריצן +חרצנם +חשלנה +חשמלו +טבורה +טבורם +טהרנה +טובחי +טוהרו +טוהרן +טווסי +טומאו +טוסטו +טוסנה +טופלו +טופלי +טופלת +טופרו +טוקטת +טחולם +טחורי +טיביי +טיובה +טיוחו +טיוחם +טיוחן +טיזוז +טיזזה +טייחם +טיילי +טיילם +טיילת +טייסה +טייסו +טייסי +טייסת +טיליי +טינפת +טיפוף +טיפיי +טמטמי +טמטמן +טמינה +טמירי +טפחיי +טפיחת +טפילת +טפיפה +טריזן +סבונה +סביאת +סבירי +סגורי +סגידת +סדירת +סוגלה +סווגן +סוונה +סוחבי +סוכרה +סולאה +סולפו +סומרה +סומרו +סונדל +סונפה +סונתז +סורבל +סורבת +סורגן +סורסו +סורקה +סורקי +סורקת +סחוטת +סחוסו +סחיטי +סחלבן +סטריי +סיאבו +סיבבו +סיגרה +סיגרו +סיגרם +סיוגם +סיודו +סיופם +סייפה +סיירם +סיירן +סיככה +סיכמת +סימאה +סימוי +סימרו +סיניי +סיננת +סינרם +סיקלו +סיקרי +סכמנה +סכסכי +סלביי +סלולי +סלסלם +סמוקת +סנגרן +סנדלו +סנדלם +סנדקם +סניפן +סעיפם +ספוגי +ספוגם +ספוגת +ספחיי +ספניי +ספסרה +ספרנן +ספררת +סקופם +סקלרי +סקריי +סרבלי +סרגלה +סרוגת +סרוקת +סריסן +סתומת +סתורה +עבדיי +עבוטם +עגביי +עדורי +עדריי +עובטת +עובשה +עובשם +עוגבו +עוטפי +עולעל +עונבת +עונשו +עונשן +עופאי +עופרם +עופשו +עוקדת +עוקמם +עוקרה +עורבב +עורבל +עורמי +עותקו +עיברת +עיגלת +עיווה +עיופה +עיופי +עיינם +עייפם +עיכבת +עינגת +עיפשת +עיקלה +עיקמת +עיקרי +עיקרת +עכוזי +עלונה +עלונם +עלונן +עלעלם +עמוקי +עמירי +ענובי +ענטזו +ענקיי +עפעפי +עצביי +עציצו +עציצם +עקוצת +עקרבם +ערבבי +ערבלו +ערגלה +ערגלי +ערגלם +ערופי +ערטלם +עריקן +ערסלן +ערפדו +ערפלו +ערפלם +ערצבי +פאודל +פברקו +פגיעי +פגיעת +פגניי +פוגגו +פוגלה +פוגשי +פודלן +פודרת +פוזמק +פוטמה +פוטמת +פוטרו +פוטרת +פוכרת +פומפת +פונמת +פוצחי +פוצצי +פוקדי +פוקעי +פורזה +פורזו +פורזת +פורפי +פוררת +פורשו +פורשת +פושרי +פותלת +פזילת +פחוסה +פחוסת +פחלצה +פחמיי +פטישה +פטרלה +פיוסם +פיוסן +פיזמו +פיילה +פילגו +פילחה +פילמי +פיפטת +פירטן +פכירה +פכפכו +פליטם +פליטן +פליטת +פללנה +פלמרן +פלפלה +פלפלן +פלפלת +פלקטה +פלקטם +פמפמה +פנימו +פנקסה +פנתרי +פסוקת +פסיגו +פסילן +פסקיי +פעירת +פענחם +פצועם +פצלנה +פצעיי +פצפצו +פצפצת +פקועי +פקוקת +פקטרת +פרדיי +פרוזת +פרועי +פרזלם +פרחחו +פרחחם +פרנוס +פרשיי +פרשנם +פתילם +צבאנה +צבועם +צבענה +צובטי +צודנה +צווחו +צוחקי +צולעי +צוריי +צורנה +צחקקן +ציודן +ציוצם +ציורן +צייקן +צילקת +צימדה +צימקת +צימרה +צימרם +ציננה +ציננת +צלבנם +צליעת +צמחיי +צמיגה +צמצמם +צמררם +צנועת +צנימו +צעיפה +צפצפם +צפצפן +צקצוק +צקצקה +צקצקן +צריחו +צריחם +צרצרן +קבלנה +קבקבן +קבריי +קובלת +קובצן +קודדם +קודמן +קודנה +קוטבן +קוטנן +קוימו +קוימת +קולבי +קולבם +קולדת +קולעי +קולרה +קומחו +קומצה +קונסת +קופלת +קוצפי +קוצצי +קוששת +קותלה +קטבנה +קטולי +קטולת +קטופה +קטלבו +קטלגה +קיאקם +קייצם +קילוס +קילחה +קינחת +קיפדו +קיציי +קיררה +קלטור +קליטי +קליעי +קליעת +קליקי +קלסיי +קלסנה +קלסרי +קלפיי +קלקלי +קנטרת +קנרסה +קנרסי +קפיצי +קפיצת +קצוצת +קצורה +קציבה +קצינן +קצינת +קציפה +קצירו +קצריי +קרטען +קרישו +קרניי +קרפדן +קרקסה +קרקסי +קרקרם +קרקשי +קרקשם +קתודת +ראשיי +רביכת +רבינה +רגלנה +רדינה +רובבו +רובדה +רוביי +רוגזת +רודדו +רווקם +רווקן +רוכבן +רוכלו +רולדה +רומנם +רוסקת +רופדה +רופפן +רוצעי +רוצצה +רוצצן +רוקדי +רוקעו +רוקעת +רושתת +רותמי +רחובו +רחומת +רחיקת +רחקנה +רחשיי +רטביי +רטיטת +ריבבו +ריבוב +ריגלה +ריחפת +ריכול +ריסיי +ריסנה +ריפאת +ריצדו +ריקעת +רכביי +רכבלי +רכונת +רכוסה +רכיבו +רכיבת +רכסיי +רמזרן +רמינה +רסקיי +רעילי +רעלנה +רעלנם +רפינה +רצענה +רצפנה +רקוחת +רקועת +רקיחה +רקיעו +רקיעי +רשרשת +רתומת +רתוקי +רתיעת + +# Proper nouns, place names, demonyms (2026-02-23) +# Manual review: names and demonyms with no common Hebrew meaning +גולדה +גלזגו +גרנדה +דובאי +דניאל +דנמרק +דקוטה +טהיטי +טורקי +טיטוס +ספרדי +ספרטה +סיאטל +סקוטי +עיראק +פוארו +פורשה +פלוטו +פרויד +פרנקו +קונגו +קוסמו +קנטקי +ראובן +רודוס +רומאו +רומאי +רוסקו diff --git a/webapp/data/languages/he/he_daily_words.txt b/webapp/data/languages/he/he_daily_words.txt index bada892..55ad7ca 100644 --- a/webapp/data/languages/he/he_daily_words.txt +++ b/webapp/data/languages/he/he_daily_words.txt @@ -6,92 +6,38 @@ גבורה גבינה גבינת -גביעה -גביעו -גביעי -גביעם גבירה -גבירת -גבישו גבישי -גבסיי גבעול גדולה גדולי -גדועה -גדורי -גדושה גדילה -גדליי גוברת גודלה -גודלו -גודלת -גודעת -גוונת -גוועה -גוועם -גווען -גוועת -גוחכת -גויסה -גויסת -גולדה גולמי גולשי גולשת -גומלי -גומרי גומרת גונבת -גונחי גונחת -גוננת גוססת גועלי -גופנם גורדי גורלה -גורלו -גורלי -גורלם גורמה -גורמי -גורמם -גורמן -גורמת -גורנם -גוררי גוררת -גזיזה -גחונן -גטואם -גיבבה גיבוי גיבור גידול גידור גידלה -גידלו -גידלת -גידפה -גיורן -גיחכו גיטרה -גיימר גייסו -גייסם גילדה -גילדת גילוח גילוי -גילחה -גילמה גימור -גימרה -גימרו גינאה -גירדו גירוד גירוי גירוש @@ -102,92 +48,47 @@ גלוטן גלולה גלולת -גלזגו גלידה -גלידו -גלידם -גלידת גלילי גלימה גלימת גלישה גלישת -גלליי -גלענם גמגום גמולם גמורה גמילה גמישה -גנאיי גנדהי גנדרן גנובה -גנונם -גנחנה -געגעי -גפניי גפרור -גרביל -גרברת גרגור גרגרי -גרדום -גרוטה גרונה גרועה -גרועי גרושה -גרזנן גרילה גרימת גריעה -גריפת גרירה -גרמיי -גרנדה גרניט -גרפיי -גשושן -גשמיי -דאונה -דבבנה דבורה -דבורי דבילי דביקה דבריי -דגדגת דגדוג -דגושה -דגיגה דגימה דגימת דואגת -דובאי -דובבם דוברו -דוברי -דוברר -דוברת -דוגלי דוגמה -דוגמן -דוגמת דוגרת דווחה -דווחו -דווחי דווקא דוושת דוחפת -דוכנה -דוכסה -דולבו דולקת -דוממם -דונגה -דונגי דועכת דופקת דוקרן @@ -195,25 +96,16 @@ דורשת דחופה דחיסה -דחיסת דחיפה דחליל -דחסנה -דחפנה דיאטה דיאטת -דיבבה -דיבגו דיבוק דיבור דיברה -דיברו -דיברת דיווה דיווח דיוקן -דיורו -דיורן דיחוי דייגו דיילי @@ -222,431 +114,224 @@ דייסה דייקן דיירי -דיירת דיכוי דילגה דילוג דילמה דימוי דימום -דיממה דינמו דיסקו דיקור -דיקטי -דיקנן דירוג -דלגנה -דלדלם -דלדלת דלוקה דלילה -דלילת דליפה דליפת -דללנה דמויי -דמיעה -דמנטי -דנובה דנטלי -דניאל -דנמרק -דסקסי -דסקסם דפוסי דפוקה דפיקה -דפקטן דצמבר דקדוק -דקדקת -דקוטה -דקיקי דקירה -דקלמו -דקלמן -דקלמת -דרדסה -דרדרה -דרדרת -דרוזי -דרוכת דרומה -דרומי דרושה דריסת דרישה דרישת דרמטי דרסטי -דרעקי -דשדשם -זאטוט -זאירי -זגזגן -זדונן זהירה זובור -זובחת -זובנה -זוגגה -זוגיי זוהמה זוהרת -זווגה זוועה -זוועת -זוחלה זוחלת -זוינה -זויפת -זוככה זוכרת זומבי זוממת -זומרו -זומרת זועמת -זוקפי זורחת -זורמי זורמת זורקת זחילה -זיבלה -זיבלו זיגוג זיהוי זיהום זיווג -זיווי -זיונה -זיונן -זיופה זיינת זייפה -זייפן -זייפת זיכוי -זיככת -זימנת זינוק -זיפיי זיקוק -זיתיי זלזול -זליגת זמזום -זמזמם זמינה -זמירי -זמירן -זניחי זעומה -זעופה זעזוע זעירה -זעמיי -זקופת זקוקה -זקיפה -זקיפן -זקיפת -זקיקו -זקיקם -זקירת זרועה -זרועו -זרועי -זרוקת -זרזנה זריזה -זריזי זריחה זריחת זרימה זרימת זריקה זריקת -חבולת חבורה חבורת חביבה -חביבי -חביבם חבילה חבילת -חבישת -חבלנם -חבקוק חבריי חגורה חגורת חגיגה -חגיגי -חגיגת -חדורת חדירה -חדשנה חובבי -חובבן -חובבת חובלה -חובקו -חוברה -חוברת חובשת חוגגת -חודדת חודרי חודרת חודשי חוואי -חוזקם חוזרת -חוטמה חוטפת -חוטרם -חוכרי חולדה -חוללה חולמת חולפת חולצה חולצת חולקת חולשה -חולשי חומוס -חומטה -חומלי חומצה -חומצם -חומצת חומקת חומרה -חומרי -חומרת חונקת חוסכת חוסלה חוסלו חוסמת -חוסרם חופרת חופשה -חופשי -חופשת חוצפה חוצפן -חוצצי חוקרי -חוקרן -חוקרת חורבה -חורבן חורגת -חורפן -חורצת חורקת -חורשי חושבה -חושבי -חושבת -חושדי חושדת -חושכו חושפת חוששת חותכת -חותלו חותמת -חותנם חותרת -חזונן חזירה -חזירי -חזירם -חזרנה -חטאיי חטופה -חטופי חטיבה חטיבת חטיפה -חטיפי -חטיפת חיבבה -חיבבו -חיבבת חיבוק חיבור חיברו -חיברת חידוש חיובי חיווט חיוור -חיוטו -חיוכי חיזוי חיזוק חיזור -חיזקת חיזרו -חיטאת חיטוי חיטטה -חיטטת -חיטיי חייבת -חייגי -חייגם חיידק חייזר -חייטו -חייטם חייכה -חייכו -חייכי -חייכת -חיילו -חיילי -חיילת חיישן -חיככו חילול חילוף חילוץ חילוק -חיללת חילצה חילקו חימום חימוש חיסול חיסלה -חיסלו -חיסלת חיפוי חיפוש -חיפפת חיפשה -חיפשו -חיפשת חיקוי -חיקוק חירום -חירוק חירשת חישבו חישוב חישוק -חישלת -חישקו חיתול חלודה -חלודם -חלווה חלומה -חלומו -חלומי -חלונה חלופה חלופי -חלוצי חלוקה -חלוקי -חלוקת -חלחלי חלילה חליפה חליפת -חלכאי -חלמאי חלקיק חלקלק חמודה חמודי -חמוטל חמוצה חמורה -חמורו -חמורת חמושה חמימה -חמיקה חמישה חמישי -חמציץ חמקמק -חנוטת חנוכה חנוכת חנופה חניכה חנינה חניקה -חנכנה -חסודי חסומה -חסומי חסימה חסימת חסינה חסינת -חסרנה -חפוזת -חפורה חפיסה חפיסת חפיפה חפיפת חפירה חפציי -חצאיי -חצביי -חצובה -חצובי חצופה -חצילה -חצניי -חצצור -חצצרם -חצרנה -חקינה חקיקה חקירה חקירת חקלאי -חקלאם -חרוזן -חרוטו -חרונה חרוצה -חרושה חרושת חרטום חריגה חריפה חריקה חריקת -חרישו -חרישן -חרמנם -חרמשה -חשאיי חשובה חשודה חשוכה חשופה -חשושת חשיבה חשיפה חשיפת חשמלי -חשפנה חתולה חתולי חתומה @@ -657,543 +342,260 @@ חתימה חתימת חתירה -חתניי -טאטאם -טבועה טבילה טביעה טביעת -טבלטי טהורה טהורת -טהיטי טואלט -טובגו טובעת -טוהרו -טווחה -טווסו -טווסם טוניק -טונפה טוסטר טוסיק טוענת -טופחה -טופחת טופלה -טופלו טופסי -טופפו -טופפם -טופרי טוראי טורבו טורבן -טורדת טורחת טורפת -טורקי טטנוס טיבטי טיהור -טיהרה -טיובי טיוטה טיולי -טיטוס -טייבי -טייחן -טייחת טיילה -טיילת טיימר -טייסי -טייסת טיפוח טיפול טיפוס טיפלה -טיפלו -טיפלת -טיפסו -טיפסת -טיפקס טיפשה טיפשי טירוף -טכנאו טכנאי טלגרף -טלטלת -טלפנן טמונה -טמטום -טמפרה -טנדרם טעונה טעימה -טעימי טעינה טפטוף -טפטפי טפיחה -טפסנם -טפסרן טקילה -טרגיי טרודי טרוטה טרופי טרוקי טריגר -טריזי טרייר טריקו -טרמפם -טרמפן -טרפדן -טרפדת טשטוש -טשטשן -טשטשת סאונד סאונה -סבוכי -סבונן סבורה סביבה -סביבו -סביבי -סביבם -סביבת סבירה -סבסדם סגולה סגורה סגירה סגירת -סגלנה -סגריי סדוקה סדיסט -סובאת סובבו -סובבי -סובבם -סובבת -סוביי סובלת -סובסד -סוגרה סוגרת -סודרו -סוהרן -סווגה סוודר סוחבת סוחטת -סוחפי סוחרי סוחרת -סויגו -סוככו -סוכלת סוכנת -סוכרו -סוכרי -סוכרת -סולאו -סולאת סולחת סוללה סוללת -סולמן -סומכי סומכת -סומלו -סומלת סוערת סופגת -סופדי סופלה -סופקי סופרת סוקרה -סורחי -סורנה -סורקי -סורקת -סותרי -סחוסם סחורה סחיטה -סחירת -סחלבי -סחפנה -סחרחר -סחררן -סחררת סטואי -סטוצן סטטוס -סטטיי סטייק -סטינה -סטיקר סטירה סטירת -סיאטל סיבוב -סיבכת -סיגול -סיגלי -סיגלם סידור סידרה -סידרו -סידרת -סיוגה -סיוגן סיווג -סיוטו -סיומה סיורי סייבר סיידי סיידר -סיידת -סייחו סיימה -סיימו -סיימי -סיימם -סיימן -סיימת -סייסה -סייסי -סייסן -סייעם סייעת -סייפם סיירה -סיירי -סיירת סיכוי סיכום סיכנה -סיכנת סילוק -סילפה סילקו סימור -סימלת -סיממו -סיממת סימנה -סימנת סינגל סינוס -סינפו סיפוק סיפור סיפקה -סיפקו -סיפקת סיפרה -סיפרו -סיפרת סיקור -סיקסק -סיקרת סירבה -סירבו -סירבת סירוב סירוס סירופ סירנה -סירנת סירקה סכומי -סכומן סלידה סליחה -סלילם -סלילן סלסול -סלסלן סלסלת -סלפיי סמוכה סמוקי סמיכה סמינר סמירה -סמלצת -סממנן סמסטר סנגור -סנגרו סנדלר -סנדקה סנוקר סנטור -סנטרי סניור -סניטה סנילי -סנכרן סנפיר -סנתזן סעודה סעודת סעיפי ספגטי ספוגה -ספוגי ספורט ספינה ספינת ספיקת ספירה -ספירו -ספירת -ספסלו -ספסלן -ספסרת -ספרדי -ספרטה ספריי -סקוטי -סקולה -סקולי -סקורי סקיצה סקירה סקירת סקנדל -סקסטה סקפטי -סרביי -סרבלה -סרבלם -סרבנה -סרוגי -סרוקה -סרחנה -סריגם סריקה סריקת סרסור -סרפדו סרקזם סשימי -סתגלן סתומה סתימה סתימת סתירה -סתתנה עבודה עבודת עבורה -עבורו -עבורי -עבורם -עבורן -עבינה עבירה עבירת עגולה -עגולת -עגומת -עגורה עגילי -עגילם עגינה עדינה -עדינת עדיפה עובדה -עובדי -עובדת עוברי עוברת -עוגבי -עוגבם -עוגלת -עודדת -עוורה -עוזבי עוזבת עוזרו -עוזרי -עוזרם -עוזרת עוינת -עוכלה -עוכלת עוללת עולמה -עולמו -עולמי -עולמם -עולמן -עולשה -עולשי -עולשם עומדת -עומקי עונדת -עונשה -עונשו עוסקת -עופפה -עופפם עופרת עוצמת עוצרת עוקבת -עוקדן -עוקלו -עוקמו -עוקצם -עוקצן -עוריי עורכי עורכת -עורפן -עורפת עורקי -עורקם -עורקן -עורקת עוררה -עוררם -עוררת -עושקי -עושקת עושרי -עושרם -עותקה עזאזל -עזובה עזיבה עזיבת -עזיזי עטופה עטורה עטיפה עטיפת עיבוד -עיברו עיגול עידוד עיוור -עיורן עיטור -עיינת עייפה עיירה עיירת -עיכבה עיכוב עיכול -עיכלו עילאי עילוי עימדו -עימדת עינוי עיניי עיסוי עיסוק עיצבה -עיצבו עיצוב עיקול עיקוף עיקור -עיקרה -עיקרי -עיקשת -עיראק עירוי עירום עישנה -עישנת עיתוי עכביש עכברי -עכואי -עכוזן -עכירת עלובה עלובי עלולה -עלולת עלוקה עליזה עלילה -עלעלה -עמודה -עמודו -עמודי -עמודם -עמומי עמוסה עמוקה עמידה -עמידי -עמילה -עמעמה -ענברה ענווה -ענויי עניבה עניבת ענישה -ענניי עסוקה -עסוקת עסיסי -עסקיי -עסקנן -עצבנת עצובה עצומה עצורה עצירה -עצירן -עצירת עצמאי -עצמיי -עקובי -עקובת -עקודה -עקודי עקומה -עקומי -עקיבא עקיפה עקיצה עקיצת -עקלנה -עקצצו -עקצצת ערבבו ערבוב ערביי -ערגלו -ערגלן -ערגלת -ערדלי ערובה ערווה -ערוכת ערומה -ערומת ערוצי עריכה עריכת @@ -1201,244 +603,118 @@ ערימת עריסה עריפת -עריצי -עריצן ערעור -ערערי -ערערם -ערפלן -ערפלת -עשורו -עשורי -עשורם עשירה עשירי עתידה -עתידו -עתידי -עתידם עתיקה עתירה פאונד -פאונם -פברקן -פגודה -פגודת -פגוטי פגומה -פגומת פגועה -פגועת פגיעה -פגיעת פגישה פגישת -פדלאה -פדנטי פדרלי -פדרנה פואטי -פוארו -פוגמי פוגעת פוגשת -פודלה -פודלם -פוזמו פוחדת -פוחמה פוטרה -פוטרת -פויחה -פולגה פולחן פולטי פולטת -פולפל -פולשן -פולשת פומבי -פומלו -פונאר פונדו פונדק -פונקה -פוסחי פוסטר פוסקו פוסקת פועלי פועלת -פוערת פוצצה -פוצצו -פוצצן -פוצצת פורום פורחת -פורטה פורטו פורטל -פורמי פורסם פורעי פורצת -פורקו -פורקן -פורקת -פורשה -פורשת פושעי פושעת פותחן פותחת פותרת פזיזה -פחדיי -פחלצן פטירה פטפוט -פטפטה פטפטן פטרול -פטרלי פיגוע פיגור -פידרת -פיוחה -פיוחו פיוטי פיזור -פיזרה -פיחום -פיטום פיטרה -פיטרו -פיטרת -פייטם -פיכחת פילגש -פילוס -פילחו -פילחת פילטר -פיללת -פילסו פינוי פינוק -פינקו -פינקת -פיסלה פיצוח פיצוי פיצול פיצוץ -פיצחו -פיקדה -פיקדו פיקוד פיקוח פירוט פירוק פירור פירוש -פיריי -פישטה -פישלה -פישלו -פישלת פיתוח פיתוי פיתחה -פיתחו -פיתחת -פכפכת פלאפל פלוגה פלוגת -פלוטו פלומה פלזמה -פלחיי -פלטיי פליטה -פליטת פלייר פלילי פלישה פלישת פלמור פלנטה -פלנטת פלסטי פלסטר -פלסיי -פלפלת -פלצור -פלקטו -פמפמי -פנטוז -פנטזן פנימה -פנימי -פנימם פנינה פניקה -פניקי פניקס -פנקסם -פנתרו -פסולם פסולת -פסוקי פסטור פסיבי פסיכי פסילה -פסיקם -פסליי פסנתר פספוס פספסה -פספסו -פספסת -פסקול -פעוטת פעולה פעולת פעילה פעימה פעימת -פעירה פעלול -פעלנה פענוח -פענחי -פעריי פצועה פציעה פציעת פקודה -פקודו -פקודת פקידי -פקידם -פקידת -פקיחה -פקססי -פקפקו -פקפקן -פקששי פרווה -פרוור פרוטה -פרויד -פרומו -פרומת פרוסה פרוסת פרועה -פרופת פרוצה פרידה פריחה @@ -1450,67 +726,35 @@ פריסת פריעת פריצה -פריצי -פריצן -פריצת פרישה -פרכסי -פרכסם -פרמוט פרמזן -פרמטו -פרמטר -פרמטת פרנסה פרנסי -פרנקו פרסום פרסמה -פרסמו -פרסמת פרעוש פרפור פרצוף פרקדן פשוטה -פשוטו -פשוטי -פשוטת פשיטה פשיטת -פשינה פשיעה פתאום -פתגמי -פתוגן פתוחה פתורה פתיחה פתיחת -פתקיי -צאצאה צאצאי -צבוטה צבועה -צבועת -צבורה צביטה צביעה -צבירו -צבירי -צבענם -צדדנה צהובה -צובלי צובעת צודקת -צוהרו -צוהרי צוואה צוואר -צווחן צוחקת -צולבי צולבת צוללן צוללת @@ -1520,287 +764,135 @@ צונחת צועדת צועקת -צוערה -צוערי -צופנה -צופפו -צופפם -צוקיי צורחת -צורכה צחצוח -צחצחן צחקוק -צחקקו ציבור ציווה -ציוצן ציורי ציטוט -ציידו -ציידי -ציידת ציינה ציינת -צייצו -צייצי -צייצם ציירה -ציירו -ציירם -ציירת צילום צילמה -צילמו -צילמת -צימקה -צימרו צינוק צינור -ציניי ציפוי ציפור -ציקלי צירוף -ציריי -צלבנה -צלבנן -צלויי צלולה צלופח -צליאק -צליחה צלילה -צלילו -צלילי -צלילם -צלינה -צליפת -צלליי -צלענה צלצול צלצלה -צלצלו -צלצלי -צלצלם -צלצלת צמודה צמיגי -צמיגם צמידי -צמידם צמיחה צמצום -צמתיי -צנונה צנועה צניחה צנצנת -צנתור צעידה -צעיפם צעירה -צעירן צעצוע צפונה צפופה -צפופת -צפינה צפצוף צפרדע -צרודה צרורה -צרורו צריבה -צריום צריכה צריכת -צרפנה -קבאבם קבועה -קבועת קבוצה קבוצת קבורה קבילה -קבילי קביעת -קבלנן -קבקבי -קברנן קדומה קדושה קדימה -קדריי קהילה קהילת קואלה -קובלט קובעת קוברה קודחת -קודיי קודמת קודרת -קודשו -קוונם -קוונן קוטלת -קוטעו -קוטרי -קוימה -קולבי קולגה -קולדי קולטי קולטת -קולנם -קולנן -קולעת -קולרי -קומחת -קומנה -קומרה -קונגו קונדס -קוננם -קוסמו -קוסמי -קוסמת קופאת קופיף קופסה קופסת -קופצי קופצת -קוציי -קוצפת -קוצצו קוקוס -קוקטי קוראה קוראת קורנת קורסת קורעת -קורקף קושרת -קוששה -קותלי -קטונה קטורת קטינה -קטיעה קטיפה -קטיפו -קטיפם -קטלגת קטלוג קטנוע קטנטן -קטריי קטשופ -קיאקי קיבלה -קיבלו -קיבלת קידוד קידוח קידום קידמה -קידשה -קידשת קיווה קיומה -קיומו -קיומי -קיומם -קיטבה -קיטוע קיטור -קיטרו קיימא קיימה -קיימו -קיימן -קיימת -קיללה -קימוץ קינאה -קינאת קינוח -קינחו -קיננת -קיסמי -קיסרה קיסרי קיפוד -קיפוץ -קיפחי -קיפצה -קיפצו -קיצבה קיצוץ קיצור -קיצרו קירבה קירוב קירור קישוט קישור קישטה -קלחיי -קלטרי קליבר קליטה קלילה -קלילת קליעה -קליעי קליפה קליפת -קלישה -קלמרו -קלמרם -קלסרם -קלסרן קלקול קלקלו קמילה -קמינן קמפוס קמצוץ -קנאנה -קנבסי -קנולה -קנטקי קניבל קנצלר קסומה -קסומת -קסמיי -קעורה קעקוע קפואה -קפואת -קפוצה קפיטן קפיצה -קפיצת קצובת קצינה -קצינת קציצה -קצרנם קקטוס קרדיט -קרואי קרובה -קרובי -קרובת -קרונם קרועה -קרטעה קריאה קריאת קריטי @@ -1814,187 +906,95 @@ קרירה קרישה קרנבל -קרנפו -קרנפן קרסול קרפדה -קרפדו -קרפדת קרקעי -קרקשן קשוחה קשורה -קשיחת -קשינה קשירה קשירת -קשישן קשמיר קשקוש -קשקשה קשקשן -קשריי קתולי -ראובן ראווה -ראינה -רבביי -רביעה רביעי רברבן -רגביי -רגוזי -רגוזת רגועה רגילה -רגימה רגיעה רגישה רגליי -רגשיי רדודה רדומה רדופה -רדידו -רדידי -רדידן רדיוס רדיפה רובוט -רובען רוגבי רוגזו -רודדה -רודוס רודפת רווחה -רווחי -רווחת רווקה -רווקת רוזנת -רוחבו -רוחצי רוחצת -רוכבה -רוכבי -רוכבת -רוכלת רוכסן רולטה -רומאו -רומאי רומזת -רומחי רוממו -רוממן -רומנה רומסת -רוסיי -רוססת -רוסקו רועדת רועמת רועשת רופאה -רופאי -רופאם -רופאת רופפת רוצחו -רוצחי -רוצחת רוצפו -רוצצי רוקדת -רוקנה -רוקקי -רושמן רושמת רותחת -רזינה -רזרבה רזרבי -רחובם רחוקה -רחפנה -רחרחם רטובה -רטובי -רטינת -ריבוד ריבוי ריבוע -ריבנה ריגול ריגוש -ריגלת ריהוט ריחוף -ריחפו -ריטטה ריכוז -ריכזה ריככה -ריננת ריסוס ריסוק -ריסנת ריפוד ריפוי -ריפוף -ריצדה ריצפת ריקוד רישוי רישום ריתוק -רכונה -רכושה -רכושו -רכושי רכיבה רכיכה רכישה רכישת -רמוסת רמזור -רמזנה -רמזרה -רמיסה -רמציי רמקול רנטגן רסיטל רסיסי -רעולי -רעועי -רעועת רעידת רעילה רעננה -רעשנן -רפאיי רפואה -רפואי -רפואת רפלקס רצונה רצונם רצועה רצועת -רצופי רציחה -רציפם רקובה רקורד -רקטור -רקעיי רשומה -רשומת -רשופי -רשופת רשימה רשימת רשרוש -רתקנה From e4d7c23167af8ec79a57022a89669ed4d5193cf9 Mon Sep 17 00:00:00 2001 From: Hugo Montenegro Date: Mon, 23 Feb 2026 12:59:09 +0000 Subject: [PATCH 02/12] feat: enhance /stats page with word quality data Add Daily/Main/Supplement/Blocklist columns to the language table, color-coded daily word counts, curated language count, and a legend explaining the quality indicators. --- webapp/app.py | 17 +++ webapp/templates/stats.html | 222 +++++++++++++++++++++--------------- 2 files changed, 150 insertions(+), 89 deletions(-) diff --git a/webapp/app.py b/webapp/app.py index 2dcc988..1a03000 100644 --- a/webapp/app.py +++ b/webapp/app.py @@ -927,11 +927,20 @@ def _build_stats_data(): earliest_stats_idx = None + total_daily_words_all = 0 + for lc in language_codes: n_words = len(language_codes_5words.get(lc, [])) n_supplement = len(language_codes_5words_supplements.get(lc, [])) total_words_all += n_words + n_supplement + # Daily words and blocklist counts + daily = language_daily_words.get(lc) + n_daily = len(daily) if daily else 0 + n_blocklist = len(language_blocklists.get(lc, set())) + has_schedule = bool(language_curated_schedules.get(lc)) + total_daily_words_all += n_daily if n_daily else n_words + # Aggregate community stats from cached files (if any) lang_total_plays = 0 lang_total_wins = 0 @@ -961,6 +970,9 @@ def _build_stats_data(): "name_native": languages[lc].get("language_name_native", ""), "n_words": n_words, "n_supplement": n_supplement, + "n_daily": n_daily, + "n_blocklist": n_blocklist, + "has_schedule": has_schedule, "total_plays": lang_total_plays, "total_wins": lang_total_wins, "win_rate": ( @@ -980,10 +992,15 @@ def _build_stats_data(): if earliest_stats_idx is not None: stats_since_date = idx_to_date(earliest_stats_idx).strftime("%B %d, %Y") + # Count languages with curated daily words + n_curated = sum(1 for ls in lang_stats if ls["n_daily"] > 0) + data = { "lang_stats": lang_stats, "total_languages": len(language_codes), "total_words": total_words_all, + "total_daily_words": total_daily_words_all, + "n_curated": n_curated, "total_puzzles": todays_idx * len(language_codes), "todays_idx": todays_idx, "global_plays": global_plays, diff --git a/webapp/templates/stats.html b/webapp/templates/stats.html index cf23e0b..bd8df4f 100644 --- a/webapp/templates/stats.html +++ b/webapp/templates/stats.html @@ -15,9 +15,9 @@ -
+
- {# Header — matches word.html pattern #} + {# Header #}
← Home @@ -29,108 +29,152 @@

Wordle Global Stats

{# Global Stats — grid in a card #} -
-

- Overview -

-
-
-

{{ total_languages }}

-

Languages

-
-
-

{{ "{:,}".format(total_words) }}

-

Total Words

-
-
-

{{ "{:,}".format(todays_idx) }}

-

Days Running

-
-
-

{{ "{:,}".format(total_puzzles) }}

-

Puzzles Served

-
-
-
- - {# Community Stats (only show if we have data) #} - {% if global_plays > 0 %} -
-

- Community Stats -

- {% if stats_since_date %} -

Since {{ stats_since_date }}

- {% endif %} -
-
-

{{ "{:,}".format(global_plays) }}

-

Games Played

-
-
-

{{ "{:,}".format(global_wins) }}

-

Games Won

-
-
-

{{ global_win_rate }}%

-

Win Rate

+
+
+

+ Overview +

+
+
+

{{ total_languages }}

+

Languages

+
+
+

{{ "{:,}".format(total_words) }}

+

Total Words

+
+
+

{{ "{:,}".format(total_daily_words) }}

+

Daily Word Pool

+
+
+

{{ n_curated }}

+

Curated Languages

+
+
+

{{ "{:,}".format(todays_idx) }}

+

Days Running

+
+
+

{{ "{:,}".format(total_puzzles) }}

+

Puzzles Served

+
-
- {% endif %} - {# Word count bar chart #} -
-

- Words by Language -

-
- {% set max_words = lang_stats[0].n_words if lang_stats else 1 %} - {% for lang in lang_stats %} -
- - {{ lang.code }} - -
-
+ {# Community Stats (only show if we have data) #} + {% if global_plays > 0 %} +
+

+ Community Stats +

+ {% if stats_since_date %} +

Since {{ stats_since_date }}

+ {% endif %} +
+
+

{{ "{:,}".format(global_plays) }}

+

Games Played

+
+
+

{{ "{:,}".format(global_wins) }}

+

Games Won

+
+
+

{{ global_win_rate }}%

+

Win Rate

- {{ "{:,}".format(lang.n_words) }}
- {% endfor %}
+ {% endif %}
- {# Language list #} + {# Language table — full width #}

All Languages ({{ total_languages }})

-
- {% for lang in lang_stats %} -
-
- - {{ lang.name }} - - {% if lang.name_native and lang.name_native != lang.name %} - {{ lang.name_native }} - {% endif %} -
-
- {{ "{:,}".format(lang.n_words) }} words - {% if lang.n_supplement > 0 %} - +{{ "{:,}".format(lang.n_supplement) }} - {% endif %} - {% if global_plays > 0 and lang.total_plays > 0 %} - {{ lang.win_rate }}% - {% endif %} -
-
- {% endfor %} +
+ + + + + + + + + {% if global_plays > 0 %} + + + {% endif %} + + + + {% for lang in lang_stats %} + + + + + + + {% if global_plays > 0 %} + + + {% endif %} + + {% endfor %} + +
LanguageDailyMainSupplementPlaysWin %
+ + {{ lang.name }} + + {% if lang.name_native and lang.name_native != lang.name %} + {{ lang.name_native }} + {% endif %} + {% if lang.has_schedule %} + scheduled + {% endif %} + + {% if lang.n_daily > 0 %} + {{ "{:,}".format(lang.n_daily) }} + {% else %} + — + {% endif %} + + {{ "{:,}".format(lang.n_words) }} + + {% if lang.n_supplement > 0 %} + {{ "{:,}".format(lang.n_supplement) }} + {% else %} + — + {% endif %} + + {% if lang.total_plays > 0 %} + {{ "{:,}".format(lang.total_plays) }} + {% else %} + — + {% endif %} + + {% if lang.win_rate is not none %} + {{ lang.win_rate }}% + {% else %} + — + {% endif %} +
+
+
+

Daily = curated daily word pool (frequency-ranked common words). Main = all valid 5-letter words. Supplement = extra valid guesses (not used as daily words).

+

Daily column: green = 2,000+, yellow = 1,000+, orange = <1,000, = not yet curated.

- {# CTA — matches word.html and 404 pattern #} + {# CTA #}
From f0d8273c932b0100ac3c21bd5ac2252563f8e5cd Mon Sep 17 00:00:00 2001 From: Hugo Montenegro Date: Mon, 23 Feb 2026 14:43:26 +0000 Subject: [PATCH 03/12] feat: tabbed stats page, game percentile, language word archive Three related features: 1. Percentile in game modal: POST to /api/word-stats now returns community stats JSON. After winning, the TODAY tab shows "Better than X% of players" linking to the word page. 2. Tabbed /stats page: "My Stats" tab shows personal stats from localStorage (inline JS, no Vue needed). "Languages" tab shows server-rendered community data with simplified columns (Daily Words + Total Words instead of Main/Supplement/Blocklist). Homepage stats icon now links to /stats instead of opening modal. 3. Language word archive: New /{lang}/words route with paginated gallery of all historical daily words. Each card shows word tiles, date, definition snippet, community stats, and lazy-loaded AI art. Includes SEO (CollectionPage schema, rel=prev/next, sitemap). --- frontend/src/game.ts | 20 +- frontend/src/index-app.ts | 155 ---------- webapp/app.py | 68 +++++ webapp/templates/game.html | 12 + webapp/templates/index.html | 118 +------- webapp/templates/sitemap_main.xml | 4 + webapp/templates/stats.html | 472 ++++++++++++++++++++++-------- webapp/templates/word.html | 42 +++ webapp/templates/words_hub.html | 158 ++++++++++ 9 files changed, 648 insertions(+), 401 deletions(-) create mode 100644 webapp/templates/words_hub.html diff --git a/frontend/src/game.ts b/frontend/src/game.ts index d129b3e..1804a1d 100644 --- a/frontend/src/game.ts +++ b/frontend/src/game.ts @@ -116,6 +116,8 @@ interface GameData { total_stats: TotalStats; languages: Record; shareButtonState: 'idle' | 'success'; + communityPercentile: number | null; + communityStatsLink: string | null; } export const createGameApp = () => { @@ -299,6 +301,8 @@ export const createGameApp = () => { n_losses: 0, }, languages: {}, + communityPercentile: null, + communityStatsLink: null, }; }, @@ -1302,7 +1306,21 @@ export const createGameApp = () => { attempts: typeof attempts === 'number' ? attempts : 0, won, }), - }).catch(() => {}); // Fire and forget + }) + .then((resp) => (resp.ok ? resp.json() : null)) + .then((stats) => { + if (!stats || !stats.total || !won) return; + const playerAttempts = typeof attempts === 'number' ? attempts : 7; + let worsePlayers = stats.losses || 0; + for (let i = playerAttempts + 1; i <= 6; i++) { + worsePlayers += stats.distribution?.[String(i)] || 0; + } + this.communityPercentile = Math.round( + (worsePlayers / stats.total) * 100 + ); + this.communityStatsLink = `/${langCode}/word/${dayIdx}`; + }) + .catch(() => {}); } catch { // Ignore errors } diff --git a/frontend/src/index-app.ts b/frontend/src/index-app.ts index 84e20d8..b1cc668 100644 --- a/frontend/src/index-app.ts +++ b/frontend/src/index-app.ts @@ -26,31 +26,6 @@ interface GameResult { date: string; } -interface LanguageStats { - n_wins: number; - n_losses: number; - n_games: number; - n_attempts: number; - avg_attempts: number; - win_percentage: number; - longest_streak: number; - current_streak: number; -} - -interface TotalStats { - total_games: number; - game_stats: Record; - languages_won: string[]; - total_win_percentage: number; - longest_overall_streak: number; - current_overall_streak: number; - longest_language_streak: number; - current_longest_language_streak: number; - current_longest_language_streak_language: string; - n_victories: number; - n_losses: number; -} - // Extend Window interface for homepage globals declare global { interface Window { @@ -74,7 +49,6 @@ export default function createIndexApp(): App { return { showPopup: false, showAboutModal: false, - showStatsModal: false, showSettingsModal: false, clickedLanguage: '', darkMode: document.documentElement.classList.contains('dark'), @@ -90,9 +64,7 @@ export default function createIndexApp(): App { languages_vis: [] as Language[], search_text: '', - total_stats: {} as TotalStats, game_results: {} as Record, - expandedLanguage: '' as string, // For stats modal expansion detectedLanguage: null as Language | null, }; }, @@ -121,7 +93,6 @@ export default function createIndexApp(): App { // Load preferences this.loadFeedbackPreference(); - this.total_stats = this.calculateTotalStats(); // Initialize languages with recently played first this.languages_vis = this.getSortedLanguages(); // Detect browser language for hero CTA @@ -328,14 +299,6 @@ export default function createIndexApp(): App { return stats?.win_percentage ?? 0; }, - toggleLanguageExpansion(language_code: string): void { - if (this.expandedLanguage === language_code) { - this.expandedLanguage = ''; - } else { - this.expandedLanguage = language_code; - } - }, - filterWordles(search_text: string): void { if (search_text === '') { this.other_wordles_vis = this.other_wordles; @@ -368,124 +331,6 @@ export default function createIndexApp(): App { this.languages_vis = visible_languages; } }, - - calculateStats(language_code: string): LanguageStats { - const results = this.game_results[language_code]; - if (!results) { - return { - n_wins: 0, - n_losses: 0, - n_games: 0, - n_attempts: 0, - avg_attempts: 0, - win_percentage: 0, - longest_streak: 0, - current_streak: 0, - }; - } - - let n_wins = 0; - let n_losses = 0; - let n_attempts = 0; - let current_streak = 0; - let longest_streak = 0; - - for (const result of results) { - if (result.won) { - n_wins++; - current_streak++; - longest_streak = Math.max(longest_streak, current_streak); - } else { - n_losses++; - current_streak = 0; - } - n_attempts += result.attempts; - } - - const total = n_wins + n_losses; - return { - n_wins, - n_losses, - n_games: results.length, - n_attempts, - avg_attempts: results.length > 0 ? n_attempts / results.length : 0, - win_percentage: total > 0 ? (n_wins / total) * 100 : 0, - longest_streak, - current_streak, - }; - }, - - calculateTotalStats(): TotalStats { - let n_victories = 0; - let n_losses = 0; - let current_overall_streak = 0; - let longest_overall_streak = 0; - let longest_language_streak = 0; - let current_longest_language_streak = 0; - let current_longest_language_streak_language = ''; - const languages_won: string[] = []; - const game_stats: Record = {}; - - // Collect and sort all results by date - const all_results: (GameResult & { language?: string })[] = []; - for (const [language_code, results] of Object.entries(this.game_results) as [ - string, - GameResult[], - ][]) { - for (const result of results) { - all_results.push({ ...result, language: language_code }); - } - } - all_results.sort((a, b) => new Date(a.date).getTime() - new Date(b.date).getTime()); - - // Calculate overall streaks - for (const result of all_results) { - if (result.won) { - n_victories++; - current_overall_streak++; - longest_overall_streak = Math.max( - longest_overall_streak, - current_overall_streak - ); - } else { - n_losses++; - current_overall_streak = 0; - } - } - - // Calculate per-language stats - for (const language_code of Object.keys(this.game_results)) { - const stats = this.calculateStats(language_code); - game_stats[language_code] = stats; - - if (stats.n_wins > 0) { - languages_won.push(language_code); - } - longest_language_streak = Math.max( - longest_language_streak, - stats.longest_streak - ); - if (stats.current_streak > current_longest_language_streak) { - current_longest_language_streak = stats.current_streak; - current_longest_language_streak_language = language_code; - } - } - - const total_games = n_victories + n_losses; - return { - total_games, - game_stats, - languages_won, - total_win_percentage: total_games > 0 ? (n_victories / total_games) * 100 : 0, - longest_overall_streak, - current_overall_streak, - longest_language_streak, - current_longest_language_streak, - current_longest_language_streak_language, - n_victories, - n_losses, - }; - }, }, }); } diff --git a/webapp/app.py b/webapp/app.py index 1a03000..7c12415 100644 --- a/webapp/app.py +++ b/webapp/app.py @@ -1129,6 +1129,70 @@ def sitemap_words(lang_code): return response +@app.route("//words") +def language_words_hub(lang_code): + """Gallery of all historical daily words for a language.""" + if lang_code not in language_codes: + abort(404) + + config = language_configs[lang_code] + lang_name = config.get("name", lang_code) + lang_name_native = config.get("name_native", lang_name) + tz = config.get("timezone", "UTC") + todays_idx = get_todays_idx(tz) + + # Pagination: 30 words per page, page 1 = most recent + page = request.args.get("page", 1, type=int) + per_page = 30 + total_pages = max(1, (todays_idx + per_page - 1) // per_page) + page = max(1, min(page, total_pages)) + + start_idx = todays_idx - (page - 1) * per_page + end_idx = max(1, start_idx - per_page + 1) + + words = [] + for day_idx in range(start_idx, end_idx - 1, -1): + word = get_word_for_day(lang_code, day_idx) + word_date = idx_to_date(day_idx) + + # Load cached definition (fast disk read) + definition = None + def_path = os.path.join(WORD_DEFS_DIR, lang_code, f"{word.lower()}.json") + if os.path.exists(def_path): + try: + with open(def_path, "r") as f: + loaded = json.load(f) + if loaded and loaded.get("definition"): + definition = loaded + except Exception: + pass + + word_stats = _load_word_stats(lang_code, day_idx) + + words.append( + { + "day_idx": day_idx, + "word": word, + "date": word_date, + "definition": definition, + "stats": word_stats, + } + ) + + return render_template( + "words_hub.html", + lang_code=lang_code, + lang_name=lang_name, + lang_name_native=lang_name_native, + words=words, + page=page, + total_pages=total_pages, + todays_idx=todays_idx, + config=config, + hreflang_url_pattern="https://wordle.global/{lang}/words", + ) + + # arbitrary app route @app.route("/") def language(lang_code): @@ -1409,6 +1473,10 @@ def submit_word_stats(lang_code): _stats_seen_ips[dedup_key] = True _update_word_stats(lang_code, day_idx, won, attempts) + # Return updated stats so client can compute percentile + updated_stats = _load_word_stats(lang_code, day_idx) + if updated_stats: + return jsonify(updated_stats), 200 return "", 200 except Exception: logging.exception("Stats submission failed for %s", lang_code) diff --git a/webapp/templates/game.html b/webapp/templates/game.html index 264b5dc..f745fec 100644 --- a/webapp/templates/game.html +++ b/webapp/templates/game.html @@ -436,6 +436,18 @@

Wordle {{

+ + + diff --git a/webapp/templates/index.html b/webapp/templates/index.html index b6597ce..21fa6e9 100644 --- a/webapp/templates/index.html +++ b/webapp/templates/index.html @@ -77,7 +77,7 @@

- +
-
-
-
- -

{{ ui.global_stats or "Global Stats" }}

- - -
-
-
[[total_stats.total_games || 0]]
-
{{ ui.games_played or "Games Played" }}
-
-
-
[[Math.round(total_stats.total_win_percentage || 0)]]%
-
{{ ui.win_rate or "Win Rate" }}
-
-
-
- 🔥[[total_stats.current_overall_streak || 0]] -
-
{{ ui.current_streak or "Current Streak" }}
-
-
-
[[total_stats.languages_won?.length || 0]]
-
{{ ui.languages_won or "Languages Won" }}
-
-
- - -
-
{{ ui.best_overall_streak or "Best Overall Streak" }}:
-
[[total_stats.longest_overall_streak || 0]]
-
-
-
- {{ ui.best_active_streak or "Best Active Streak" }} ([[languages[total_stats.current_longest_language_streak_language]?.language_name]]): -
-
🔥 [[total_stats.current_longest_language_streak]]
-
- - -
-
{{ ui.your_languages or "Your Languages" }}
-
- -
- {{ ui.no_games_yet or "You haven't played any games yet. Pick a language to start!" }} -
- - -
-
- - - -
-
-
-
[[stats.n_wins]]
-
{{ ui.wins or "Wins" }}
-
-
-
[[stats.n_losses]]
-
{{ ui.losses or "Losses" }}
-
-
-
[[Math.round(stats.win_percentage)]]%
-
{{ ui.win_rate or "Win Rate" }}
-
-
-
-
-
[[stats.avg_attempts?.toFixed(1) || '-']]
-
{{ ui.avg_attempts or "Avg Attempts" }}
-
-
-
[[stats.longest_streak]]
-
{{ ui.best_streak or "Best Streak" }}
-
-
- -
-
-
- -
-
-
-
diff --git a/webapp/templates/sitemap_main.xml b/webapp/templates/sitemap_main.xml index f23cb8c..3bccd44 100644 --- a/webapp/templates/sitemap_main.xml +++ b/webapp/templates/sitemap_main.xml @@ -13,5 +13,9 @@ {{ base_url }}/{{ language }} 0.9 + + {{ base_url }}/{{ language }}/words + 0.7 + {% endfor %} diff --git a/webapp/templates/stats.html b/webapp/templates/stats.html index bd8df4f..42ecc95 100644 --- a/webapp/templates/stats.html +++ b/webapp/templates/stats.html @@ -28,149 +28,210 @@

Wordle Global Stats

- {# Global Stats — grid in a card #} -
-
-

- Overview -

-
-
-

{{ total_languages }}

-

Languages

-
-
-

{{ "{:,}".format(total_words) }}

-

Total Words

-
-
-

{{ "{:,}".format(total_daily_words) }}

-

Daily Word Pool

-
-
-

{{ n_curated }}

-

Curated Languages

+ {# Tab bar #} +
+ + +
+ + {# ===== MY STATS TAB (populated by JS) ===== #} + '; } + document.getElementById('my-distribution').innerHTML = distHtml; // Build per-language list var list = document.getElementById('my-language-list'); @@ -391,7 +402,8 @@

'; html += '' + l.games + ' games'; - html += '' + l.winPct + '% win'; + html += '' + l.winPct + '%'; + html += '' + l.avgAttempts + ' avg'; if (l.streak > 0) html += '\uD83D\uDD25' + l.streak + ''; html += '

'; } From 4750a6353785438d48ba2d62e6b245ad6ed4abfa Mon Sep 17 00:00:00 2001 From: Hugo Montenegro Date: Mon, 23 Feb 2026 15:44:05 +0000 Subject: [PATCH 08/12] fix: remove lazy loading on words hub images MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit loading="lazy" prevented images from loading until scrolled into view, but combined with on-demand DALL-E generation (15-20s), images would time out or get cancelled on first visit. Removing lazy loading lets all images start loading immediately — most are cached so this is fine. --- webapp/templates/words_hub.html | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/webapp/templates/words_hub.html b/webapp/templates/words_hub.html index 8d7c79e..6cd723f 100644 --- a/webapp/templates/words_hub.html +++ b/webapp/templates/words_hub.html @@ -106,12 +106,11 @@

Wordle {{ lang_name_native }} — All Words<

{% endif %} - {# AI art thumbnail (lazy loaded, hidden until loaded) #} + {# AI art thumbnail (hidden until loaded) #} From 0155c0998cc66f54a1c23fb36e40929f6dd258c6 Mon Sep 17 00:00:00 2001 From: Hugo Montenegro Date: Mon, 23 Feb 2026 15:52:45 +0000 Subject: [PATCH 09/12] fix: prevent Chrome translate bar from clipping game keyboard On Android PWA, Chrome's "Translate this page?" bar steals ~40px from the viewport. With overflow-hidden and h-[100dvh], the bottom keyboard row gets clipped. Fixes: - Add translate="no" and to suppress Chrome's translate prompt (game is already localized) - Switch from 100dvh to 100svh (small viewport height) which accounts for browser chrome, translate bars, and gesture navigation bars --- webapp/templates/game.html | 13 +++++++++---- webapp/templates/partials/_loading_skeleton.html | 2 +- 2 files changed, 10 insertions(+), 5 deletions(-) diff --git a/webapp/templates/game.html b/webapp/templates/game.html index c336cb4..bc63021 100644 --- a/webapp/templates/game.html +++ b/webapp/templates/game.html @@ -1,10 +1,12 @@ - + {% from 'partials/_toggle_switch.html' import toggle_switch %} {% include 'partials/_dark_mode_init.html' %} {% include 'partials/_base_head.html' %} + {# Suppress Chrome translate bar — the game is already localized #} + {# Page-specific SEO meta tags #} {% set title = "Play Wordle in " ~ language.config.name ~ " — Free Daily Word Game (" ~ language.config.name_native ~ ")" %} @@ -61,11 +63,11 @@ - + {% include 'partials/_loading_skeleton.html' %} -