#!/usr/bin/env python3 """SCICRAFT — story generator. Reads STORY/*.md, merges every ```lang JSON block (in filename order) into en_us.json, writes it to BOTH packs, and validates that every lang key referenced from the KubeJS scripts and advancement JSONs resolves. Usage: python tools/gen_story.py Edit the story by editing STORY/*.md, then run this script. Never hand-edit en_us.json — it is a generated artifact. """ import json import re import sys from pathlib import Path ROOT = Path(__file__).resolve().parent.parent STORY_DIR = ROOT / "STORY" LANGS = [ ROOT / "server_pack" / "kubejs" / "assets" / "scicraft" / "lang" / "en_us.json", ROOT / "client_pack" / "kubejs" / "assets" / "scicraft" / "lang" / "en_us.json", ] KUBEJS_DIRS = [ROOT / "server_pack" / "kubejs"] ADVANCEMENT_DIR = ROOT / "server_pack" / "kubejs" / "data" / "scicraft" / "advancements" LANG_BLOCK = re.compile(r"```lang\s*\n(.*?)```", re.S) def load_lang_files(): merged = {} order = [] for path in sorted(STORY_DIR.glob("*.md")): text = path.read_text(encoding="utf-8") for i, match in enumerate(LANG_BLOCK.finditer(text)): try: data = json.loads(match.group(1)) except json.JSONDecodeError as e: sys.exit(f"BAD JSON in {path.name} block #{i + 1}: {e}") for key, value in data.items(): if key in merged: print(f"WARN: duplicate key {key!r} ({path.name} overrides)") merged[key] = value order.append(key) return merged, order def write_lang(merged, order): payload = {k: merged[k] for k in order} for target in LANGS: target.parent.mkdir(parents=True, exist_ok=True) target.write_text( json.dumps(payload, ensure_ascii=False, indent="\t") + "\n", encoding="utf-8", ) print(f"wrote {target.relative_to(ROOT)} ({len(payload)} keys)") def validate(merged): missing = set() def check_key(key, source): if key not in merged: missing.add((key, source)) for root_dir in KUBEJS_DIRS: for script in root_dir.rglob("*.js"): for key in re.findall(r"Text\.translate\(\s*['\"]([^'\"]+)['\"]\s*(?:\+|\))", script.read_text(encoding="utf-8")): # Keys followed by ` + msg` are dynamic templates — skip. if key.endswith("."): continue check_key(key, str(script.relative_to(ROOT))) if ADVANCEMENT_DIR.exists(): for jf in ADVANCEMENT_DIR.rglob("*.json"): for key in re.findall(r'"translate"\s*:\s*"([^"]+)"', jf.read_text(encoding="utf-8")): check_key(key, str(jf.relative_to(ROOT))) if missing: print("\nMISSING LANG KEYS:") for key, source in sorted(missing): print(f" {key} (used in {source})") sys.exit(1) print("all referenced lang keys resolve") if __name__ == "__main__": merged, order = load_lang_files() write_lang(merged, order) validate(merged)