"""专名本土化的回归测试。 重点盯住两个曾经真实踩过的坑: 1. 只处理全名,漏掉正文里大量使用的简称(`Ral`)与单呼的姓(`Fastenhatchets`)。 2. `YETI` 这类全大写枚举只改首字母得到 `YETI`,永远匹配不上正文里的 `Yeti`。 """ from __future__ import annotations import sys from pathlib import Path sys.path.insert(0, str(Path(__file__).resolve().parents[1])) from dfannals import glossary, localize def test_title_form_lowercases_tail() -> None: assert localize.title_form("YETI") == "Yeti" assert localize.title_form("EAGLE_MAN") == "Eagle_man" assert localize.title_form("fastenhatchets") == "Fastenhatchets" def test_person_full_name() -> None: assert localize.translate_person("Ral Fastenhatchets") == "拉尔·缚斧" assert localize.translate_person("Guspu Frillyknots") == "古斯普·褶结" assert localize.translate_person("Rigoth Stilledearthen") == "里戈斯·静土" def test_person_with_epithet() -> None: name = "Langgud Tradebears The Eviscerated Chill Of Pregnancies" assert localize.translate_person(name) == "朗古德·商熊「剖胎之寒」" def test_person_unknown_returns_none() -> None: assert localize.translate_person("Zzzq Unknownsurname") is None def test_place_and_epithet() -> None: assert localize.translate_place("Fordedwinds") == "涉风渡" assert localize.translate_place("Paperhushed") == "纸寂" assert localize.translate_place("Gloryholes The Trickery Of Mornings") == "荣耀穴(晨之诡计)" def test_group_translation() -> None: assert localize.translate_group("The Volcano Of Ravens") == "渡鸦火山" def test_race_enum_and_display_form() -> None: assert localize.translate_race("YETI") == "雪人" assert localize.translate_race("DWARF") == "矮人" assert localize.translate_race("UNKNOWN_RACE") == "UNKNOWN_RACE" def _fake_translator() -> localize.Translator: """不依赖真实世界数据的小翻译器,专测替换顺序。""" translator = localize.Translator( people={"Ral Fastenhatchets": "拉尔·缚斧", "Rigoth Stilledearthen": "里戈斯·静土"}, places={"Fordedwinds": "涉风渡"}, groups={"The Volcano Of Ravens": "渡鸦火山"}, stems={"Ral": "拉尔", "Rigoth": "里戈斯"}, surnames={"Fastenhatchets": "缚斧"}, races={"Yeti": "雪人", "EAGLE_MAN": "鹰人"}, ) return translator def test_render_full_name_wins_over_short_name() -> None: out = _fake_translator().render("Ral Fastenhatchets守着Fordedwinds。") assert out == "拉尔·缚斧守着涉风渡。", out def test_render_short_name_and_bare_surname() -> None: out = _fake_translator().render("Ral对Rigoth说:她那一支姓Fastenhatchets。") assert "拉尔" in out and "里戈斯" in out and "缚斧" in out, out assert not any(ch.isascii() and ch.isalpha() for ch in out), out def test_render_race_display_form() -> None: out = _fake_translator().render("一个Yeti站了起来。军团是EAGLE_MAN。") assert "雪人" in out and "鹰人" in out, out def test_render_tidies_spaces() -> None: out = _fake_translator().render("Ral 说 完 就 走 了") assert "拉尔" in out and "拉尔 说" not in out, out def test_accepted_includes_pieces() -> None: accepted = _fake_translator().accepted() assert "拉尔·缚斧" in accepted assert "拉尔" in accepted and "缚斧" in accepted, sorted(accepted) def test_glossary_tables_are_lowercase_keys() -> None: for table in (glossary.STEMS, glossary.SURNAMES, glossary.PLACES): for key in table: assert key == key.lower(), f"{key} 应小写,否则 title_form 对不上" def _main() -> int: tests = [v for k, v in sorted(globals().items()) if k.startswith("test_") and callable(v)] failed = 0 for fn in tests: try: fn() except AssertionError as exc: failed += 1 print(f" ✗ {fn.__name__}: {exc}") except Exception as exc: # noqa: BLE001 failed += 1 print(f" ✗ {fn.__name__}: {type(exc).__name__}: {exc}") else: print(f" ✓ {fn.__name__}") print(f"\n{len(tests) - failed}/{len(tests)} 通过") return 1 if failed else 0 if __name__ == "__main__": sys.exit(_main())