Ship name corpus lookup/spin, nickname suggestions, significance UX, site-ideas SMTP, and remove the Logo concepts page and assets.
708 lines
16 KiB
Python
708 lines
16 KiB
Python
#!/usr/bin/env python3
|
|
"""Build data/world-names.json from curated regional lists + optional bulk file.
|
|
|
|
Usage:
|
|
.venv/bin/python scripts/build_world_names.py
|
|
.venv/bin/python scripts/build_world_names.py --bulk /path/to/first-names.txt
|
|
|
|
Bulk file: one name per line (e.g. NameDatabases first names). Tagged intl/en.
|
|
Curated regional tags always merge on top.
|
|
"""
|
|
|
|
from __future__ import annotations
|
|
|
|
import argparse
|
|
import json
|
|
import re
|
|
from pathlib import Path
|
|
|
|
ROOT = Path(__file__).resolve().parents[1]
|
|
OUT = ROOT / "stork" / "data" / "world-names.json"
|
|
OUT_REPO = ROOT / "data" / "world-names.json"
|
|
|
|
RAW: dict[str, dict[str, object]] = {}
|
|
LATIN = re.compile(r"^[A-Za-z][A-Za-z'\-]{0,39}$")
|
|
|
|
|
|
def add(names: list[str], regions: list[str], gender: str = "u") -> None:
|
|
for raw in names:
|
|
name = raw.strip().strip("\ufeff")
|
|
if not name or len(name) > 40:
|
|
continue
|
|
if not LATIN.match(name) and not any(ord(c) > 127 for c in name):
|
|
continue
|
|
# Prefer ASCII-friendly bulk; allow unicode from curated
|
|
if any(ord(c) > 127 for c in name) and "ru" not in regions and "he" not in regions and "ar" not in regions:
|
|
# keep non-latin only when curated with a region
|
|
if set(regions) <= {"intl", "en"}:
|
|
continue
|
|
key = name.casefold()
|
|
display = name.title() if name.isascii() and all(c.isalpha() or c in "'-" for c in name) else name
|
|
if key not in RAW:
|
|
RAW[key] = {"name": display, "regions": set(), "gender": gender}
|
|
entry = RAW[key]
|
|
assert isinstance(entry["regions"], set)
|
|
entry["regions"].update(regions)
|
|
if gender in {"f", "m"} and entry.get("gender") == "u":
|
|
entry["gender"] = gender
|
|
elif gender in {"f", "m"} and entry.get("gender") not in {gender, "u"}:
|
|
entry["gender"] = "u"
|
|
|
|
|
|
def seed_curated() -> None:
|
|
add(
|
|
[
|
|
"Emma",
|
|
"Olivia",
|
|
"Ava",
|
|
"Sophia",
|
|
"Isabella",
|
|
"Mia",
|
|
"Charlotte",
|
|
"Amelia",
|
|
"Harper",
|
|
"Evelyn",
|
|
"Abigail",
|
|
"Emily",
|
|
"Elizabeth",
|
|
"Sofia",
|
|
"Ella",
|
|
"Madison",
|
|
"Scarlett",
|
|
"Victoria",
|
|
"Aria",
|
|
"Grace",
|
|
"Chloe",
|
|
"Camila",
|
|
"Penelope",
|
|
"Riley",
|
|
"Layla",
|
|
"Lillian",
|
|
"Nora",
|
|
"Zoey",
|
|
"Mila",
|
|
"Aubrey",
|
|
"Hannah",
|
|
"Lily",
|
|
"Addison",
|
|
"Eleanor",
|
|
"Natalie",
|
|
"Luna",
|
|
"Savannah",
|
|
"Brooklyn",
|
|
"Leah",
|
|
"Zoe",
|
|
"Stella",
|
|
"Hazel",
|
|
"Ellie",
|
|
"Paisley",
|
|
"Audrey",
|
|
"Skylar",
|
|
"Violet",
|
|
"Claire",
|
|
"Bella",
|
|
"Lucy",
|
|
"Anna",
|
|
"Caroline",
|
|
"Genesis",
|
|
"Aaliyah",
|
|
"Kennedy",
|
|
"Kinsley",
|
|
"Allison",
|
|
"Maya",
|
|
"Sarah",
|
|
"Madelyn",
|
|
"Adeline",
|
|
"Alexa",
|
|
"Ariana",
|
|
"Elena",
|
|
"Gabriella",
|
|
"Naomi",
|
|
"Alice",
|
|
"Sadie",
|
|
"Hailey",
|
|
"Eva",
|
|
"Emilia",
|
|
"Autumn",
|
|
"Quinn",
|
|
"Nevaeh",
|
|
"Piper",
|
|
"Ruby",
|
|
"Serenity",
|
|
"Willow",
|
|
"Everly",
|
|
"Cora",
|
|
"Kaylee",
|
|
"Lydia",
|
|
"Aubree",
|
|
"Arianna",
|
|
"Eliana",
|
|
"Peyton",
|
|
"Melanie",
|
|
"Gianna",
|
|
"Isabelle",
|
|
"Julia",
|
|
"Valentina",
|
|
"Nova",
|
|
"Clara",
|
|
"Vivian",
|
|
"Reagan",
|
|
"Mackenzie",
|
|
"Madeline",
|
|
"Brielle",
|
|
"Delilah",
|
|
"Isla",
|
|
"Liam",
|
|
"Noah",
|
|
"Oliver",
|
|
"James",
|
|
"Elijah",
|
|
"William",
|
|
"Henry",
|
|
"Lucas",
|
|
"Benjamin",
|
|
"Theodore",
|
|
"Jack",
|
|
"Levi",
|
|
"Alexander",
|
|
"Samuel",
|
|
"Sebastian",
|
|
"Mateo",
|
|
"Daniel",
|
|
"Matthew",
|
|
"Owen",
|
|
"Asher",
|
|
"Leo",
|
|
"Jackson",
|
|
"David",
|
|
"Joseph",
|
|
"Julian",
|
|
"Luke",
|
|
"Michael",
|
|
"Thomas",
|
|
"Ezra",
|
|
"Charles",
|
|
"Hudson",
|
|
"John",
|
|
"Ryan",
|
|
"Jayden",
|
|
"Anthony",
|
|
"Dylan",
|
|
"Isaac",
|
|
"Lincoln",
|
|
"Nathan",
|
|
"Caleb",
|
|
"Aaron",
|
|
"Eli",
|
|
"Gabriel",
|
|
"Adrian",
|
|
"Landon",
|
|
"Jordan",
|
|
"Ian",
|
|
"Carson",
|
|
"Colton",
|
|
"Adam",
|
|
"Miles",
|
|
"Zachary",
|
|
"Cooper",
|
|
"Xavier",
|
|
"Zane",
|
|
"Zion",
|
|
"Zander",
|
|
"Felix",
|
|
"Oscar",
|
|
"Ivy",
|
|
"Jade",
|
|
"June",
|
|
"Iris",
|
|
"Eden",
|
|
"Rose",
|
|
"Daisy",
|
|
"Sage",
|
|
"Quinn",
|
|
"River",
|
|
"Skye",
|
|
],
|
|
["en", "intl"],
|
|
)
|
|
add(
|
|
[
|
|
"Anya",
|
|
"Anastasia",
|
|
"Daria",
|
|
"Dasha",
|
|
"Katya",
|
|
"Ekaterina",
|
|
"Elena",
|
|
"Lena",
|
|
"Irina",
|
|
"Ira",
|
|
"Olga",
|
|
"Olya",
|
|
"Natalia",
|
|
"Natasha",
|
|
"Sofia",
|
|
"Sonya",
|
|
"Svetlana",
|
|
"Sveta",
|
|
"Tatiana",
|
|
"Tanya",
|
|
"Yulia",
|
|
"Polina",
|
|
"Alina",
|
|
"Marina",
|
|
"Vera",
|
|
"Nadezhda",
|
|
"Nadia",
|
|
"Galina",
|
|
"Valentina",
|
|
"Zoya",
|
|
"Zina",
|
|
"Zinaida",
|
|
"Zlata",
|
|
"Zhanna",
|
|
"Liza",
|
|
"Elizaveta",
|
|
"Varvara",
|
|
"Varya",
|
|
"Ksenia",
|
|
"Oksana",
|
|
"Ludmila",
|
|
"Lyuda",
|
|
"Raisa",
|
|
"Inna",
|
|
"Nina",
|
|
"Tamara",
|
|
"Larisa",
|
|
"Alla",
|
|
"Rita",
|
|
"Alexei",
|
|
"Alexander",
|
|
"Sasha",
|
|
"Andrei",
|
|
"Andrey",
|
|
"Anton",
|
|
"Boris",
|
|
"Dmitri",
|
|
"Dima",
|
|
"Ivan",
|
|
"Vanya",
|
|
"Igor",
|
|
"Kirill",
|
|
"Konstantin",
|
|
"Kostya",
|
|
"Maxim",
|
|
"Misha",
|
|
"Mikhail",
|
|
"Nikolai",
|
|
"Kolya",
|
|
"Oleg",
|
|
"Pavel",
|
|
"Pasha",
|
|
"Petr",
|
|
"Pyotr",
|
|
"Roman",
|
|
"Sergei",
|
|
"Vadim",
|
|
"Viktor",
|
|
"Vladimir",
|
|
"Vova",
|
|
"Yuri",
|
|
"Yura",
|
|
"Timur",
|
|
"Artem",
|
|
"Denis",
|
|
"Ilya",
|
|
"Lev",
|
|
"Zakhar",
|
|
"Zahar",
|
|
"Gleb",
|
|
"Fedor",
|
|
"Fyodor",
|
|
"Stepan",
|
|
"Nikita",
|
|
"Matvei",
|
|
"Egor",
|
|
"Yaroslav",
|
|
"Mira",
|
|
"Lara",
|
|
"Klara",
|
|
"Rosa",
|
|
"Raya",
|
|
"Tonya",
|
|
"Zhenya",
|
|
"Vika",
|
|
"Alyosha",
|
|
"Seryozha",
|
|
],
|
|
["ru", "intl"],
|
|
)
|
|
add(
|
|
[
|
|
"Noa",
|
|
"Noah",
|
|
"Maya",
|
|
"Maayan",
|
|
"Yael",
|
|
"Talia",
|
|
"Shira",
|
|
"Shiri",
|
|
"Lior",
|
|
"Liya",
|
|
"Ayala",
|
|
"Adi",
|
|
"Orli",
|
|
"Orly",
|
|
"Hila",
|
|
"Hillel",
|
|
"Tamar",
|
|
"Rivka",
|
|
"Riva",
|
|
"Sarah",
|
|
"Sara",
|
|
"Miriam",
|
|
"Miri",
|
|
"Leah",
|
|
"Rachel",
|
|
"Esther",
|
|
"Hadassah",
|
|
"Chana",
|
|
"Hannah",
|
|
"Devorah",
|
|
"Naomi",
|
|
"Shoshana",
|
|
"Shoshi",
|
|
"Vered",
|
|
"Gal",
|
|
"Gali",
|
|
"Noya",
|
|
"Eden",
|
|
"Idan",
|
|
"Eitan",
|
|
"Yonatan",
|
|
"Yossi",
|
|
"Yosef",
|
|
"David",
|
|
"Daniel",
|
|
"Dani",
|
|
"Ari",
|
|
"Ariel",
|
|
"Aviv",
|
|
"Omri",
|
|
"Omer",
|
|
"Amir",
|
|
"Amit",
|
|
"Nadav",
|
|
"Noam",
|
|
"Roi",
|
|
"Roy",
|
|
"Shai",
|
|
"Shay",
|
|
"Ziv",
|
|
"Zeev",
|
|
"Zohar",
|
|
"Tomer",
|
|
"Tal",
|
|
"Yuval",
|
|
"Itai",
|
|
"Itay",
|
|
"Eyal",
|
|
"Eliana",
|
|
"Alon",
|
|
"Alona",
|
|
"Batya",
|
|
"Bracha",
|
|
"Chaya",
|
|
"Gila",
|
|
"Ilana",
|
|
"Inbal",
|
|
"Keren",
|
|
"Liora",
|
|
"Michal",
|
|
"Mor",
|
|
"Neta",
|
|
"Ofir",
|
|
"Orit",
|
|
"Pnina",
|
|
"Rina",
|
|
"Roni",
|
|
"Sigal",
|
|
"Smadar",
|
|
"Tehila",
|
|
"Yaara",
|
|
"Yasmin",
|
|
"Ziva",
|
|
"Zurit",
|
|
"Asher",
|
|
"Boaz",
|
|
"Caleb",
|
|
"Ezra",
|
|
],
|
|
["he", "intl"],
|
|
)
|
|
add(
|
|
[
|
|
"Amina",
|
|
"Amira",
|
|
"Fatima",
|
|
"Layla",
|
|
"Leila",
|
|
"Noor",
|
|
"Nur",
|
|
"Yasmin",
|
|
"Yasmine",
|
|
"Zara",
|
|
"Zahra",
|
|
"Zainab",
|
|
"Zaynab",
|
|
"Hana",
|
|
"Maryam",
|
|
"Mariam",
|
|
"Salma",
|
|
"Samira",
|
|
"Rania",
|
|
"Rana",
|
|
"Dina",
|
|
"Dalia",
|
|
"Lina",
|
|
"Lama",
|
|
"Nour",
|
|
"Noura",
|
|
"Farah",
|
|
"Jana",
|
|
"Aya",
|
|
"Iman",
|
|
"Huda",
|
|
"Heba",
|
|
"Reem",
|
|
"Rima",
|
|
"Sana",
|
|
"Zaina",
|
|
"Zayna",
|
|
"Zia",
|
|
"Ziana",
|
|
"Aisha",
|
|
"Omar",
|
|
"Ahmed",
|
|
"Ahmad",
|
|
"Ali",
|
|
"Hassan",
|
|
"Youssef",
|
|
"Yusuf",
|
|
"Karim",
|
|
"Samir",
|
|
"Tariq",
|
|
"Zaid",
|
|
"Zayd",
|
|
"Zain",
|
|
"Zayn",
|
|
"Yasin",
|
|
"Ibrahim",
|
|
"Ismail",
|
|
"Khalid",
|
|
"Malik",
|
|
"Nabil",
|
|
"Rami",
|
|
"Sami",
|
|
"Walid",
|
|
"Yazan",
|
|
"Adel",
|
|
"Faisal",
|
|
],
|
|
["ar", "intl"],
|
|
)
|
|
add(
|
|
[
|
|
"Camille",
|
|
"Chloe",
|
|
"Clara",
|
|
"Elise",
|
|
"Juliette",
|
|
"Louise",
|
|
"Manon",
|
|
"Margot",
|
|
"Ines",
|
|
"Adele",
|
|
"Celeste",
|
|
"Colette",
|
|
"Odette",
|
|
"Odet",
|
|
"Simone",
|
|
"Violette",
|
|
"Antoine",
|
|
"Louis",
|
|
"Hugo",
|
|
"Raphael",
|
|
"Theo",
|
|
"Enzo",
|
|
"Pierre",
|
|
"Paul",
|
|
"Jacques",
|
|
"Henri",
|
|
"Remy",
|
|
"Etienne",
|
|
"Olivier",
|
|
"Giulia",
|
|
"Francesca",
|
|
"Chiara",
|
|
"Alessia",
|
|
"Bianca",
|
|
"Lucia",
|
|
"Marco",
|
|
"Luca",
|
|
"Matteo",
|
|
"Alessandro",
|
|
"Giovanni",
|
|
"Francesco",
|
|
"Lorenzo",
|
|
"Carmen",
|
|
"Isabel",
|
|
"Pilar",
|
|
"Ana",
|
|
"Diego",
|
|
"Javier",
|
|
"Carlos",
|
|
"Miguel",
|
|
"Pablo",
|
|
"Luis",
|
|
"Jorge",
|
|
"Pedro",
|
|
"Santiago",
|
|
],
|
|
["fr", "intl"],
|
|
)
|
|
add(
|
|
[
|
|
"Greta",
|
|
"Ingrid",
|
|
"Freya",
|
|
"Freja",
|
|
"Astrid",
|
|
"Sigrid",
|
|
"Helga",
|
|
"Ebba",
|
|
"Alva",
|
|
"Saga",
|
|
"Linnea",
|
|
"Ida",
|
|
"Lars",
|
|
"Erik",
|
|
"Anders",
|
|
"Johan",
|
|
"Nils",
|
|
"Oskar",
|
|
"Bjorn",
|
|
"Sven",
|
|
"Magnus",
|
|
"Henrik",
|
|
"Hans",
|
|
"Franz",
|
|
"Otto",
|
|
"Klaus",
|
|
"Heidi",
|
|
"Anneliese",
|
|
"Ursula",
|
|
"Zelda",
|
|
"Hilda",
|
|
"Wanda",
|
|
"Helene",
|
|
"Anke",
|
|
"Birgit",
|
|
"Dagmar",
|
|
"Elke",
|
|
],
|
|
["de", "intl"],
|
|
)
|
|
add(
|
|
[
|
|
"Zed",
|
|
"Zee",
|
|
"Zia",
|
|
"Ziv",
|
|
"Zoa",
|
|
"Zoe",
|
|
"Zola",
|
|
"Zora",
|
|
"Zoya",
|
|
"Zuri",
|
|
"Zana",
|
|
"Zara",
|
|
"Zena",
|
|
"Zina",
|
|
"Ziva",
|
|
"Zuzu",
|
|
"Zizi",
|
|
"Zaza",
|
|
"Zeke",
|
|
"Ziggy",
|
|
"Aziz",
|
|
"Aziza",
|
|
"Eliza",
|
|
"Liza",
|
|
"Izzy",
|
|
"Ozzy",
|
|
"Suzy",
|
|
"Roza",
|
|
"Roze",
|
|
"Ruza",
|
|
],
|
|
["intl", "en", "ru", "he", "ar"],
|
|
)
|
|
for n in ["Emma", "Olivia", "Sofia", "Maya", "Noa", "Zara", "Zena", "Zina", "Anya", "Daria"]:
|
|
if n.casefold() in RAW:
|
|
RAW[n.casefold()]["gender"] = "f"
|
|
for n in ["Liam", "Noah", "Oliver", "Levi", "Ezra", "Ivan", "David", "Shai", "Omar", "Zane"]:
|
|
if n.casefold() in RAW:
|
|
RAW[n.casefold()]["gender"] = "m"
|
|
|
|
|
|
def load_bulk(path: Path, limit: int = 18000) -> int:
|
|
added = 0
|
|
for line in path.read_text(encoding="utf-8", errors="ignore").splitlines():
|
|
name = line.strip().strip("\ufeff")
|
|
if not LATIN.match(name):
|
|
continue
|
|
before = len(RAW)
|
|
add([name], ["intl", "en"])
|
|
if len(RAW) > before:
|
|
added += 1
|
|
if added >= limit:
|
|
break
|
|
return added
|
|
|
|
|
|
def main() -> int:
|
|
ap = argparse.ArgumentParser()
|
|
ap.add_argument("--bulk", type=Path, default=None, help="Optional one-name-per-line file")
|
|
ap.add_argument("--limit", type=int, default=18000)
|
|
args = ap.parse_args()
|
|
|
|
seed_curated()
|
|
if args.bulk and args.bulk.is_file():
|
|
n = load_bulk(args.bulk, limit=args.limit)
|
|
print(f"Merged {n} bulk names from {args.bulk}")
|
|
|
|
items = []
|
|
for entry in RAW.values():
|
|
regions = sorted(entry["regions"]) # type: ignore[arg-type]
|
|
items.append(
|
|
{
|
|
"name": entry["name"],
|
|
"regions": regions,
|
|
"gender": entry.get("gender") or "u",
|
|
}
|
|
)
|
|
items.sort(key=lambda x: str(x["name"]).casefold())
|
|
OUT.parent.mkdir(parents=True, exist_ok=True)
|
|
payload = json.dumps({"names": items}, ensure_ascii=False, separators=(",", ":")) + "\n"
|
|
OUT.write_text(payload, encoding="utf-8")
|
|
OUT_REPO.parent.mkdir(parents=True, exist_ok=True)
|
|
OUT_REPO.write_text(payload, encoding="utf-8")
|
|
print(f"Wrote {len(items)} names → {OUT}")
|
|
print(f"Wrote {len(items)} names → {OUT_REPO}")
|
|
return 0
|
|
|
|
|
|
if __name__ == "__main__":
|
|
raise SystemExit(main())
|