skills: move translation skills to ./skills/, symlink from ~/.hermes/skills/

pptx-translate, chinese-text-normalize, dharma-translation: canonical
location now ./skills/ with symlinks in ~/.hermes/skills/.

translation-review: merged CSV/XLSX review + .dj comparison workflows
into single SKILL.md. Added buddhist-terminology.md and
terms-db-alignment.md references from Hermes version.
This commit is contained in:
iacore
2026-06-09 19:46:23 +08:00
parent 10c449f4d9
commit 7e8ba4b291
9 changed files with 693 additions and 51 deletions
+42
View File
@@ -0,0 +1,42 @@
---
name: chinese-text-normalize
description: Normalize Chinese markdown files — remove extraneous mid-sentence line breaks from fixed-width exports while preserving TOC structures, section headers, and intentional paragraph breaks.
---
When Chinese text has hard line breaks at a fixed width (~20-25 chars) — common in PDF exports, OCR output, or poorly-converted documents — use this skill to join them into flowing paragraphs.
## Triggers
- User asks to "fix line breaks" or "remove extraneous breaks" in Chinese text
- Chinese markdown files with lines that break mid-sentence at a consistent short width
- Files with vertical TOC (single-char-per-line 【】 sections) that need preservation
## Approach
Run `scripts/normalize_breaks.py <directory>` — it processes all .md files in the directory.
The script handles three file patterns:
1. **Vertical TOC + fixed-width body** — Preserves the decorative single-char TOC section, joins body paragraphs, strips inline page numbers (standalone digits like "3", "4")
2. **Outline TOC with stray breaks** — Preserves numbered outline items (一、...、1、...、...... separators), joins body paragraphs
3. **Already in paragraph format** — No change (safe to run idempotently)
### What it preserves
- Vertical TOC: single CJK/punctuation lines with 【】 brackets
- Section headers: 【...】、## ...、# ...、一、二、三、...、1、2、3、...
- Outline TOC entries: short numbered lines, lines with ...... separators
- Blank lines as paragraph separators
### What it removes
- Mid-sentence hard line breaks (joins consecutive CJK body lines)
- Inline page numbers (standalone 1-2 digit lines)
- Trailing blank lines
## Pitfalls
- **TOC detection boundaries**: The vertical TOC end is detected by finding the first line with 3+ CJK characters. If a page number like "2" sits between TOC and body, it lands in the TOC section — harmless but visible.
- **Section headers without markers**: Plain-text section titles (e.g., "生命可以被设计的依据") without 【】 or number prefixes won't be detected as headers. They'll form standalone paragraphs separated by blank lines, which is fine as long as blank lines exist around them.
- **Wiki-link TOC files**: Files like a course index with [[wiki links]] are NOT prose and should be excluded. The script has no special handling — skip those files manually or restore from git.
- **Not for mixed CJK/English prose**: The script treats any line with CJK characters as body text. Mixed-language documents may need manual review.
@@ -0,0 +1,168 @@
"""
Fix extraneous line breaks in Chinese markdown files.
Three file patterns:
1. Fixed-width body text (20-25 chars/line) + vertical TOC -> join lines, remove page nums
2. Mostly-paragraph with stray breaks + outline TOC -> join broken lines, preserve list items
3. Already fine -> skip (idempotent)
Usage: python3 normalize_breaks.py <directory>
"""
import re
import sys
from pathlib import Path
CJK = re.compile(r'[\u4e00-\u9fff\u3400-\u4dbf\uf900-\ufaff]')
CN_PUNCT = ',。!?;:、""''()《》【】…—~·'
NUM_MARKER = re.compile(r'^[一二三四五六七八九十]+[、,,]')
DIGIT_MARKER = re.compile(r'^\d+[、.,]')
TOC_SEP = re.compile(r'\.{3,}') # "......" separators in outline TOCs
def has_cjk(s):
return bool(CJK.search(s))
def is_page_num(line):
s = line.strip()
return s and s.isdigit() and len(s) <= 2
def is_toc_line(line):
"""Vertical TOC: single char, or 【, 】, ·, or solo digit"""
s = line.strip()
if not s:
return False
if len(s) == 1 and (has_cjk(s) or s in CN_PUNCT or s in '【】·' or s.isdigit()):
return True
return False
def is_section_header(line):
"""Section headers: 【...】, ## ..., # ..., 一、..., 1、..., or standalone title lines"""
s = line.strip()
if not s:
return False
if s.startswith('') and s.endswith(''):
return True
if s.startswith('#'):
return True
if NUM_MARKER.match(s):
return True
if DIGIT_MARKER.match(s):
return True
return False
def is_outline_toc_line(line):
"""Outline/list TOC: entries separated by ...... or short numbered items"""
s = line.strip()
if TOC_SEP.search(s):
return True
m = re.match(r'^(\d+[.、,]|[一二三四五六七八九十]+[、,])\s*\S', s)
if m and len(s) < 30:
return True
return False
def find_toc_end(lines):
"""Find where the vertical TOC section ends and body text begins."""
for i, line in enumerate(lines):
s = line.strip()
if has_cjk(s) and len([c for c in s if has_cjk(c)]) >= 3:
j = i
while j > 0 and not lines[j - 1].strip():
j -= 1
return j
return 0
def process_body(lines):
"""Join body text lines into paragraphs, preserving section headers and outline items."""
result = []
buf = []
def flush():
nonlocal buf
if buf:
joined = ''.join(buf)
result.append(joined)
buf = []
for line in lines:
s = line.strip()
if not s:
flush()
result.append('')
continue
if is_section_header(s):
flush()
result.append(s)
continue
if is_outline_toc_line(s):
flush()
result.append(s)
continue
if is_page_num(s):
continue
if has_cjk(s) or (buf and s):
buf.append(s)
else:
flush()
result.append(s)
flush()
return result
def process_file(filepath):
content = filepath.read_text(encoding='utf-8')
lines = content.split('\n')
toc_end = find_toc_end(lines)
if toc_end > 10:
toc_part = lines[:toc_end]
body_part = lines[toc_end:]
body_processed = process_body(body_part)
new_lines = toc_part + body_processed
else:
new_lines = process_body(lines)
cleaned = []
prev_blank = False
for line in new_lines:
is_blank = line.strip() == ''
if is_blank and prev_blank:
continue
cleaned.append(line)
prev_blank = is_blank
while cleaned and cleaned[-1] == '':
cleaned.pop()
new_content = '\n'.join(cleaned) + '\n'
if new_content != content:
filepath.write_text(new_content, encoding='utf-8')
return True
return False
def main():
workdir = Path(sys.argv[1])
files = sorted(workdir.glob('*.md'))
for f in files:
changed = process_file(f)
status = 'FIXED' if changed else 'OK'
print(f'{status}: {f.name}')
if __name__ == '__main__':
main()