translate: 生命也可以被设计的 — bilingual.dj + edit-suggestions
This commit is contained in:
@@ -1,5 +1,7 @@
|
||||
"""Generate bilingual.dj from DOCX for 「生命也可以被设计的」."""
|
||||
import re, subprocess, os, hashlib
|
||||
"""Generate bilingual.dj from DOCX for 「生命也可以被设计的」.
|
||||
One-pass approach: walk interleaved paragraphs, handle multi-CN sequences.
|
||||
"""
|
||||
import re, subprocess, os
|
||||
|
||||
DOCX = "/home/user/documents/mpi/translate-files/生命也可以被设计的/中英文定稿-260324-生命也是可以被设计的-妙一宽山静雅初翻 慈鎏妙一审议 宽山定稿.docx"
|
||||
OUT_DIR = "/home/user/documents/mpi/translate-files/生命也可以被设计的"
|
||||
@@ -13,39 +15,14 @@ def pandoc(path):
|
||||
return r.stdout
|
||||
|
||||
def split_toc_line(line):
|
||||
"""Split ' 一、教育是为了育人 EDUCATION IS ABOUT NURTURING THE PERSON 3'
|
||||
into (cn, en). Split at CJK→ASCII uppercase boundary."""
|
||||
s = line.strip()
|
||||
# Remove trailing page number
|
||||
s = re.sub(r'\s+\d+\s*$', '', s)
|
||||
# Find boundary: last CJK char followed by space(s) + ASCII uppercase
|
||||
m = re.match(r'^(.+[\u4e00-\u9fff\u3000-\u303f\uff00-\uffef)\)])\s+([A-Z].+)$', s)
|
||||
if m:
|
||||
return m.group(1).strip(), m.group(2).strip()
|
||||
return None, None
|
||||
|
||||
def join_split_paragraphs(lines):
|
||||
"""Join consecutive CJK lines that were split by page breaks.
|
||||
Only join when first line is long (>30 chars) to avoid joining headings."""
|
||||
result = []
|
||||
i = 0
|
||||
while i < len(lines):
|
||||
line = lines[i]
|
||||
s = line.strip()
|
||||
# Page-break splits happen in mid-paragraph; headings are short.
|
||||
# Only join when first line long AND doesn't end with terminal punctuation.
|
||||
long_and_abrupt = (s and has_cjk(s) and len(s) > 30
|
||||
and not re.search(r'[。!?:)\u201d\u2019]$', s))
|
||||
if long_and_abrupt and i + 2 < len(lines) and lines[i+1].strip() == '' and has_cjk(lines[i+2]):
|
||||
result.append(line.rstrip() + lines[i+2].lstrip())
|
||||
i += 3
|
||||
else:
|
||||
result.append(line)
|
||||
i += 1
|
||||
return result
|
||||
|
||||
def extract_toc_entries(text):
|
||||
"""Return (cn_entries, en_entries) lists from TOC area."""
|
||||
lines = text.split('\n')
|
||||
toc_start = None
|
||||
toc_end = None
|
||||
@@ -54,7 +31,6 @@ def extract_toc_entries(text):
|
||||
if s.startswith('一、') and ('EDUCATION' in s or 'NURTURING' in s):
|
||||
if toc_start is None:
|
||||
toc_start = i
|
||||
# TOC entries have page numbers at end
|
||||
if toc_start is not None and s and has_cjk(s) and re.search(r'\d+$', s):
|
||||
toc_end = i
|
||||
elif toc_start is not None and toc_end is not None and s and not re.search(r'\d+$', s) and has_cjk(s):
|
||||
@@ -63,53 +39,75 @@ def extract_toc_entries(text):
|
||||
cn_entries = []
|
||||
en_entries = []
|
||||
for i in range(toc_start, toc_end + 1):
|
||||
s = lines[i]
|
||||
cn, en = split_toc_line(s)
|
||||
cn, en = split_toc_line(lines[i])
|
||||
if cn and en:
|
||||
cn_entries.append(cn)
|
||||
en_entries.append(en)
|
||||
return cn_entries, en_entries
|
||||
|
||||
def extract_body_pairs(text):
|
||||
"""Return [(cn, en), ...] pairs from body paragraphs and headings."""
|
||||
"""One-pass: walk interleaved paras, joining consecutive same-language lines."""
|
||||
lines = text.split('\n')
|
||||
|
||||
# Find body start: first Chinese paragraph after TOC
|
||||
# Find body start
|
||||
body_start = None
|
||||
for i, l in enumerate(lines):
|
||||
if '现在是一个浮躁的时代' in l:
|
||||
body_start = i
|
||||
break
|
||||
|
||||
# Join split paragraphs first
|
||||
pre = lines[:body_start]
|
||||
body = lines[body_start:]
|
||||
body = join_split_paragraphs(body)
|
||||
# Extract non-blank paragraphs with language tags
|
||||
tagged = []
|
||||
for l in lines[body_start:]:
|
||||
s = l.strip()
|
||||
if s:
|
||||
tagged.append(('cn' if has_cjk(s) else 'en', s))
|
||||
|
||||
# Accumulate consecutive same-language paragraphs (page-break splits only, not headings)
|
||||
merged = []
|
||||
for lang, text in tagged:
|
||||
# Heading-like patterns that should not be merged
|
||||
prev_is_heading = (merged and merged[-1][0] == lang
|
||||
and bool(re.match(r'^[\dIVX]+[\.\s]', merged[-1][1].strip())
|
||||
and len(merged[-1][1].strip()) < 60))
|
||||
if (merged and merged[-1][0] == lang
|
||||
and not prev_is_heading
|
||||
and len(merged[-1][1]) > 30
|
||||
and not re.search(r'[。!?:)\u201d\u2019\uff0c\uff0e\.!\?]$', merged[-1][1])):
|
||||
# Long previous line, doesn't end naturally → page-break split, join
|
||||
merged[-1] = (lang, merged[-1][1].rstrip() + text.lstrip())
|
||||
else:
|
||||
merged.append((lang, text))
|
||||
|
||||
# Build pairs: group consecutive same-language items into blocks, then zip
|
||||
blocks = []
|
||||
for lang, text in merged:
|
||||
if blocks and blocks[-1][0] == lang:
|
||||
blocks[-1][1].append(text)
|
||||
else:
|
||||
blocks.append((lang, [text]))
|
||||
|
||||
# Extract pairs: pattern is cn, blank, en, blank
|
||||
pairs = []
|
||||
i = 0
|
||||
while i < len(body):
|
||||
s = body[i].strip()
|
||||
if not s:
|
||||
i += 1
|
||||
continue
|
||||
if not has_cjk(s):
|
||||
i += 1
|
||||
continue
|
||||
# Chinese line found, look for English after blank
|
||||
en = ''
|
||||
if i + 2 < len(body) and body[i+1].strip() == '':
|
||||
ec = body[i+2].strip()
|
||||
if ec and not has_cjk(ec):
|
||||
en = ec
|
||||
i += 3
|
||||
while i < len(blocks):
|
||||
if blocks[i][0] == 'cn':
|
||||
cn_block = blocks[i][1]
|
||||
# Find next EN block
|
||||
if i + 1 < len(blocks) and blocks[i+1][0] == 'en':
|
||||
en_block = blocks[i+1][1]
|
||||
n = min(len(cn_block), len(en_block))
|
||||
for j in range(n):
|
||||
pairs.append((cn_block[j], en_block[j]))
|
||||
if len(cn_block) != len(en_block):
|
||||
print(f" WARNING: block mismatch CN={len(cn_block)} EN={len(en_block)} at CN[{j}]: {cn_block[j][:60]}...")
|
||||
i += 2
|
||||
else:
|
||||
print(f" WARNING: CN block without EN block: {cn_block[0][:60]}...")
|
||||
i += 1
|
||||
else:
|
||||
print(f" WARNING: orphan EN block: {blocks[i][1][0][:60]}...")
|
||||
i += 1
|
||||
continue
|
||||
pairs.append((s, en))
|
||||
|
||||
return pairs
|
||||
|
||||
SANSKRIT = [
|
||||
@@ -121,14 +119,9 @@ SANSKRIT = [
|
||||
]
|
||||
|
||||
def apply_fixes(en_text, italicized):
|
||||
"""Apply typesetting fixes to English text."""
|
||||
# Fix: "2.How" → "2. How"
|
||||
en_text = re.sub(r'(\d)\.([A-Z][a-z])', r'\1. \2', en_text)
|
||||
# Fix: "said,\"When → "said, \"When
|
||||
en_text = re.sub(r'(said|says),\"', r'\1, "', en_text)
|
||||
# Fix: "...understanding.Yet..." → "...understanding. Yet..."
|
||||
en_text = re.sub(r'\.([A-Z][a-z])', r'. \1', en_text)
|
||||
# Italicize Sanskrit on first occurrence
|
||||
for term in SANSKRIT:
|
||||
if term not in italicized:
|
||||
pattern = re.compile(r'\b' + re.escape(term) + r'\b')
|
||||
@@ -143,7 +136,6 @@ def generate(toc_cn, toc_en, pairs, out_path):
|
||||
italicized = set()
|
||||
lines = []
|
||||
|
||||
# Title
|
||||
lines.append('# 生命也是可以被设计的')
|
||||
lines.append('# Life Can Also Be Designed')
|
||||
lines.append('')
|
||||
@@ -151,7 +143,6 @@ def generate(toc_cn, toc_en, pairs, out_path):
|
||||
lines.append('A teaching given by the Master Jiqun in the winter of 2025 at Amrita Retreat Center for Motherly Love Academy')
|
||||
lines.append('')
|
||||
|
||||
# TOC
|
||||
for e in toc_cn:
|
||||
lines.append(f'- {e}')
|
||||
lines.append('')
|
||||
@@ -159,7 +150,6 @@ def generate(toc_cn, toc_en, pairs, out_path):
|
||||
lines.append(f'- {e}')
|
||||
lines.append('')
|
||||
|
||||
# Body
|
||||
for cn, en in pairs:
|
||||
en_fixed = apply_fixes(en, italicized)
|
||||
lines.append(cn)
|
||||
|
||||
Reference in New Issue
Block a user