DazedTL/util/wolf_names.py

134 lines
5.2 KiB
Python

"""Safe-to-translate ``note`` categories for the WOLF (WolfDawn) name glossary.
WolfDawn's ``names-extract`` produces ``names.json``: a project-wide list of every
"value name" the game references (item / skill / enemy names, but also variable
names, file/SE/BGM keys, event names, map settings, ...). Each entry carries a
``note`` field naming the WOLF database category it came from, e.g. ``武器``
(weapons), ``技能`` (skills), ``システム変数名`` (system variable names).
Translating *all* of them corrupts the game, because many categories are logic
keys (compared by value, used to build filenames, stored in variables). Only the
categories that are pure on-screen text are safe to translate.
Rather than hand-editing 2000+ entries, the workflow classifies by ``note``: a
repo-aware AI decides which *categories* are display-only, and that short list is
saved here. The WolfDawn translation module (:mod:`modules.wolfdawn`) then only
translates ``names.json`` entries whose ``note`` is in the safe list, leaving
everything else identical to the source (so injection is a no-op for it).
The default is an empty list: nothing is translated until the user opts a
category in, which keeps the safe-by-default behaviour the workflow relies on.
"""
from __future__ import annotations
import json
from util.paths import DATA_DIR
SAFE_NOTES_PATH = DATA_DIR / "wolf_safe_notes.json"
# WolfDawn labels standard database types bilingually in a document's group
# ``typeName`` (e.g. ``"Weapon · 武器"``). names.json ``note`` values are the JP
# half only, so this static map provides an English label for the common,
# safe-to-translate categories when the live DB labels are unavailable. Games
# with custom categories fall back to a JP-only header.
NOTE_EN: dict[str, str] = {
"武器": "Weapon",
"防具": "Armor",
"技能": "Skill",
"アイテム": "Item",
"状態設定": "State Setting",
"用語設定": "Term Setting",
"戦闘コマンド": "Battle Command",
"システム設定": "System Setting",
"属性名の設定": "Attribute",
"主人公ステータス": "Hero Status",
"敵グループ": "Enemy Group",
"敵キャラ個体データ": "Enemy Data",
}
# WolfDawn joins the English and Japanese halves of a database label with this
# separator, e.g. ``"Weapon · 武器"``.
_LABEL_SEP = " · "
def load_safe_notes() -> list[str]:
"""Return the saved list of ``note`` categories that are safe to translate."""
try:
if SAFE_NOTES_PATH.is_file():
data = json.loads(SAFE_NOTES_PATH.read_text(encoding="utf-8"))
if isinstance(data, list):
return [str(n) for n in data]
except Exception:
pass
return []
def save_safe_notes(notes: list[str]) -> None:
"""Persist the safe ``note`` categories (de-duplicated, order preserved)."""
seen: set[str] = set()
ordered: list[str] = []
for note in notes:
note = str(note)
if note not in seen:
seen.add(note)
ordered.append(note)
DATA_DIR.mkdir(parents=True, exist_ok=True)
SAFE_NOTES_PATH.write_text(
json.dumps(ordered, ensure_ascii=False, indent=4), encoding="utf-8"
)
def is_note_safe(note: str, safe_notes) -> bool:
"""True if *note* is in the *safe_notes* collection."""
if not safe_notes:
return False
return note in safe_notes
def derive_db_labels(files_dir) -> dict[str, str]:
"""Build a ``{japanese_note: english}`` map from staged WolfDawn ``db`` files.
WolfDawn tags each database group with a bilingual ``typeName`` (e.g.
``"Weapon · 武器"``). Splitting on the separator recovers the game-accurate
English label for a names.json ``note`` (the JP half), so harvested vocab
sections can be headed bilingually without hardcoding categories per game.
"""
from pathlib import Path
labels: dict[str, str] = {}
try:
base = Path(files_dir)
if not base.is_dir():
return labels
for path in sorted(base.glob("*.project.json")):
try:
data = json.loads(path.read_text(encoding="utf-8-sig"))
except Exception:
continue
if data.get("kind") != "db":
continue
for group in data.get("groups") or []:
type_name = group.get("typeName") or ""
if _LABEL_SEP in type_name:
english, _, japanese = type_name.partition(_LABEL_SEP)
english, japanese = english.strip(), japanese.strip()
if english and japanese and japanese not in labels:
labels[japanese] = english
except Exception:
pass
return labels
def note_header(note: str, db_labels: dict[str, str] | None = None) -> str:
"""Return a bilingual vocab section header for a names.json ``note``.
Prefers the live DB label (:func:`derive_db_labels`), then the static
:data:`NOTE_EN` map, and falls back to the JP note alone. Formats matching
WolfDawn's own labels, e.g. ``"Weapon · 武器"``.
"""
english = (db_labels or {}).get(note) or NOTE_EN.get(note)
if english and english != note:
return f"{english}{_LABEL_SEP}{note}"
return note