revise 人生百问

proofread 佛教徒的人生态度
This commit is contained in:
iacore
2026-07-14 11:07:10 +08:00
parent e144784e71
commit 210fe97593
29 changed files with 4862 additions and 1065 deletions
+189
View File
@@ -0,0 +1,189 @@
#!/usr/bin/env python3
"""Regenerate source.dj, target.dj, bilingual.dj, and bilingual.typ
from the chapter files for 人生百问.
Usage:
python3 gen-bilingual-typst-b50c74.py
The script:
1. Concatenates chapters/intro.dj, ch01.dj, ..., ch16.dj into source.dj.
2. Concatenates chapters/intro-target.dj, ch01-target.dj, ..., ch16-target.dj
into target.dj.
3. Runs ../../toolkit/scripts/gen-bilingual.py to produce bilingual.dj.
4. Rebuilds bilingual.typ by preserving the existing Typst wrapper (header,
title block, opening quote, and mpi-bilingual-outline show rule) and
replacing only the body content with interleaved Chinese/English paragraphs
in the same style as the current bilingual.typ.
"""
import re
import subprocess
import sys
from pathlib import Path
ARTICLE_DIR = Path(__file__).resolve().parent
TOOLKIT = (ARTICLE_DIR / ".." / ".." / "toolkit").resolve()
CHAPTERS = [
"intro",
"ch01", "ch02", "ch03", "ch04", "ch05", "ch06", "ch07",
"ch08", "ch09", "ch10", "ch11", "ch12", "ch13", "ch14", "ch15", "ch16",
]
def concat_chapters():
"""Merge chapter files into source.dj and target.dj."""
source = ARTICLE_DIR / "source.dj"
target = ARTICLE_DIR / "target.dj"
with source.open("w", encoding="utf-8") as sf, target.open(
"w", encoding="utf-8"
) as tf:
for ch in CHAPTERS:
src_path = ARTICLE_DIR / "chapters" / f"{ch}.dj"
tgt_path = ARTICLE_DIR / "chapters" / f"{ch}-target.dj"
sf.write(src_path.read_text(encoding="utf-8"))
sf.write("\n")
tf.write(tgt_path.read_text(encoding="utf-8"))
tf.write("\n")
return source, target
def generate_bilingual_dj(source, target):
"""Run the existing gen-bilingual.py script."""
bilingual = ARTICLE_DIR / "bilingual.dj"
gen_script = TOOLKIT / "scripts" / "gen-bilingual.py"
with bilingual.open("w", encoding="utf-8") as out:
subprocess.run(
[sys.executable, str(gen_script), str(source), str(target)],
stdout=out,
check=True,
)
return bilingual
def extract_wrapper(typ_path):
"""Return the existing Typst wrapper (everything up to and including the
closing bracket of the opening quote block)."""
with typ_path.open("r", encoding="utf-8") as f:
lines = f.readlines()
quote_start = None
for i, line in enumerate(lines):
if line.strip() == "#quote(block: true)[":
quote_start = i
break
if quote_start is None:
raise ValueError("Could not find opening quote block in bilingual.typ")
wrapper_end = None
for i in range(quote_start + 1, len(lines)):
if lines[i].strip() == "]":
wrapper_end = i
break
if wrapper_end is None:
raise ValueError("Could not find closing bracket of opening quote block")
return "".join(lines[: wrapper_end + 1])
def transform_pair(src, tgt, state):
"""Convert one source/target line pair into the body format used by
bilingual.typ."""
# Chapter heading
if src.startswith("## ") and tgt.startswith("## "):
state["chapter"] += 1
state["question"] = 0
label = f"<ch{state['chapter']:02d}>"
return f"== {src[3:].strip()} / {tgt[3:].strip()} {label}\n\n"
# Question heading
if src.startswith("### ") and tgt.startswith("### "):
state["question"] += 1
label = f"<ch{state['chapter']:02d}-q{state['question']:02d}>"
return f"=== {src[4:].strip()} / {tgt[4:].strip()} {label}\n\n"
# Question marker
if src.startswith("问:") and tgt.startswith("Q:"):
new_src = re.sub(r"^问:", "_问:_ ", src)
new_tgt = re.sub(r"^Q:\s*", "_Q:_ ", tgt)
return f"{new_src}\n{new_tgt}\n\n"
# Answer marker
if src.startswith("答:") and tgt.startswith("A:"):
new_src = re.sub(r"^答:", "_答:_ ", src)
new_tgt = re.sub(r"^A:\s*", "_A:_ ", tgt)
return f"{new_src}\n{new_tgt}\n\n"
# Default paragraph pair
return f"{src}\n{tgt}\n\n"
def generate_body(source, target):
"""Build the new body content from the merged source and target files."""
src_lines = source.read_text(encoding="utf-8").splitlines()
tgt_lines = target.read_text(encoding="utf-8").splitlines()
if len(src_lines) != len(tgt_lines):
raise ValueError(
f"Line count mismatch: source={len(src_lines)} target={len(tgt_lines)}"
)
# Find the first chapter heading pair (skip intro title, subtitle, quote, TOC)
start_idx = None
for i in range(len(src_lines)):
if src_lines[i].startswith("## ") and tgt_lines[i].startswith("## "):
start_idx = i
break
if start_idx is None:
raise ValueError("Could not find first chapter heading pair")
state = {"chapter": 0, "question": 0}
out_lines = []
for i in range(start_idx, len(src_lines)):
s = src_lines[i]
t = tgt_lines[i]
if s == "" and t == "":
out_lines.append("")
continue
block = transform_pair(s, t, state)
out_lines.extend(block.rstrip("\n").split("\n"))
# Collapse runs of blank lines to a single blank line to match the original
# bilingual.typ style.
collapsed = []
for line in out_lines:
if line == "":
if collapsed and collapsed[-1] == "":
continue
collapsed.append(line)
# Ensure trailing newline
return "\n".join(collapsed) + "\n"
def main():
source, target = concat_chapters()
bilingual = generate_bilingual_dj(source, target)
wrapper = extract_wrapper(ARTICLE_DIR / "bilingual.typ")
body = generate_body(source, target)
with (ARTICLE_DIR / "bilingual.typ").open("w", encoding="utf-8") as f:
f.write(wrapper)
f.write("\n")
f.write(body)
# Basic verification
src_lines = source.read_text(encoding="utf-8").splitlines()
tgt_lines = target.read_text(encoding="utf-8").splitlines()
print(f"source.dj: {len(src_lines)} lines")
print(f"target.dj: {len(tgt_lines)} lines")
print(f"bilingual.dj: {len(bilingual.read_text(encoding='utf-8').splitlines())} lines")
if len(src_lines) != len(tgt_lines):
print("ERROR: source and target line counts do not match", file=sys.stderr)
sys.exit(1)
print("bilingual.typ regenerated successfully")
if __name__ == "__main__":
main()