Skip to content

About

CLI tool to translate English Word documents to Chinese using Qwen3.5 with format preservation and a glossary.

Resources

Stars

0 stars

Watchers

0 watching

Forks

Latest commit

 

History

26 Commits

Folders and files

Repository files navigation

LLMTranslation

CLI tool that translates English Word documents (.docx) to Chinese using a Kimi 2.6 OpenAI-compatible endpoint. Preserves run-level formatting (bold / italic / color / size), respects a user-supplied bilingual glossary, and forces canonical Chinese terms when the LLM ignores the glossary.

Built for legal / financial due-diligence reports with hundreds of paragraphs, including three-column glossary tables, bold sub-headers, and mixed-format runs inside cells.

Setup

python3 -m venv .venv
source .venv/bin/activate
pip install -r requirements.txt

cp .env.example .env
# Edit .env, set QWEN_API_KEY=<your real key>
export QWEN_API_KEY="$(grep ^QWEN_API_KEY .env | cut -d= -f2)"

Usage

python translate.py INPUT.docx --glossary GLOSSARY.docx

Outputs (next to the input):

  • INPUT_zh.docx — translated document, formatting preserved
  • INPUT_translation.log — per-paragraph diagnostics

Options

Flag Default
--glossary PATH none
--output PATH <input>_zh.docx
--log PATH <input>_translation.log
--concurrency N 5
--model NAME kimi26
--base-url URL (private — see internal config)
--verbose off (also tees the log to stderr on success)

The API key is read only from the QWEN_API_KEY environment variable, never from the command line, so it does not appear in shell history.

Glossary format

A .docx containing one or more two-column tables. Left column = English term, right column = canonical Chinese translation.

  • Recognized header rows (auto-skipped): 英文 / 中文, English / Chinese, en / zh, source / target. Comparison is case-insensitive.
  • Tables whose column count is not exactly 2 are ignored (with a warning in the log).
  • Empty rows are skipped.
  • Internal whitespace in keys is collapsed to single spaces.
  • Duplicate keys: the last one wins, with a warning logged.

Identity-mapped entries like ATR → ATR or Setting-up Authorisation → Setting-up Authorisation are valid and tell the model to keep the term as-is.

How it preserves formatting

The Word run model (a paragraph is a sequence of <w:r> runs, each with its own bold/italic/font) doesn't survive a naïve "translate the paragraph string" pipeline. This tool uses a tag-based encoding:

  1. Encode — adjacent runs with identical formatting are merged. Each run with non-default formatting gets wrapped in <rN>...</rN> tags; plain runs are emitted bare. The format properties for each rN are stored in a side map.
  2. LLM — the system prompt instructs the model to keep the <rN> tags verbatim while translating the surrounding text and the text inside the tags.
  3. Decode — the translated tagged string is parsed back into runs. Each <rN>...</rN> span becomes a run with the original rN formatting; bare text inherits the paragraph's first-run format. Tag-mismatch fallback writes the whole paragraph as a single run with a warning, so output is never corrupted by malformed LLM responses.

Glossary handling is two-stage: the glossary is injected into the system prompt, and a post-processing step (src/term_enforcer.py) rewrites any English glossary term still present in the translation to its canonical Chinese form. This catches cases where the LLM ignores the glossary for short bold subheaders.

What's translated, what isn't

Content Status
Body paragraphs ✅
Table cells (any nesting) ✅
Section headers / footers (default, first-page, even-page) ✅
Bold / italic / underline / color / font-size / font-name ✅ preserved
Already-Chinese paragraphs ✅ skipped (avoids LLM back-translating to English)
URL-only paragraphs ✅ skipped
Pure-numeric / pure-punctuation paragraphs ✅ skipped
Hyperlink text inside <w:hyperlink> ⚠️ left untranslated; URL target preserved
Footnotes (<w:footnote>) ⚠️ left untranslated under python-docx 1.x (walker has a guarded fallback for future support)
Comments (<w:comment>) ❌ skipped intentionally
Tracked-changes (<w:ins> / <w:del>) ❌ revision text skipped intentionally

Log format

Each line is a tagged warning the user may want to act on:

[failed] paragraph N: <error>            — three retries exhausted; paragraph kept as-is in source language
[tag-mismatch] paragraph N: ...           — LLM dropped/added <rN> tags; whole paragraph written as single run
[term-enforce] paragraph N: 'en' → 'zh' (×K)
                                          — enforcer rewrote K occurrences of an English term to its Chinese equivalent
[term-warning] paragraph N: 'en' appears in source but translation lacks 'zh'
                                          — validator detected a term that wasn't translated AND wasn't auto-fixed
                                            (often a false positive when the LLM uses a synonymous Chinese phrase)

If a run completes with no failures, no tag mismatches, no enforcement, and no warnings, the log file contains a single summary line:

# N paragraphs translated, no warnings.

Smoke test against the real endpoint

QWEN_API_KEY=... .venv/bin/python scripts/smoke.py

Builds a tiny in-memory docx with bold + italic runs and a 3-row glossary, runs the full pipeline against the real Qwen endpoint, and prints the translated paragraphs plus the log.

Run unit tests

.venv/bin/pytest -v

81 tests, all hermetic (mocked OpenAI client). No real network calls in the suite.

Real-world fixes baked in

Several non-obvious failure modes surfaced during real-document testing on a 1080-paragraph DD report and are now defended against:

  • LLM echo responses — the gateway occasionally returns the user message verbatim instead of a translation. Detected when output equals input and length ≥ 12; treated as a transient failure that retries.
  • Walker dedup with id() was unsafe — Python recycles memory addresses for short-lived lxml wrappers, so an id()-based "seen" set silently dropped ~80% of paragraphs. Walker now yields without dedup; the translator's content-keyed cache handles legitimate duplicates.
  • Already-translated source content — three-column glossary tables ship with the Chinese already in place. Sending Chinese-only paragraphs to an English→Chinese model causes back-translation. Walker now skips paragraphs without any Latin letters.
  • Glossary ignored for bold subheaders — term_enforcer.py post-process rewrites English glossary terms remaining in the translation to their canonical Chinese form, with word-boundary matching that respects multi-word terms and skips identity entries.
  • <w:hyperlink> cleanup on decode — _clear_runs removes both <w:r> and <w:hyperlink> so paragraphs with hyperlinks don't end up with mixed leftover XML.
  • HTML entity round-trip — the encoder escapes & along with < / >, and the decoder uses html.unescape, so source text containing literal &lt; survives the trip without corruption.

Project layout

LLMTranslation/
├── translate.py                 — CLI entry point
├── src/
│   ├── config.py                — defaults + system prompt builder
│   ├── glossary.py              — parse 2-column glossary docx → dict
│   ├── docx_walker.py           — iterate translatable paragraphs (body / tables / headers / footers)
│   ├── format_encoder.py        — runs ↔ <rN> tagged strings + format map
│   ├── translator.py            — async OpenAI-compatible client with retry / cache / echo detection
│   ├── term_enforcer.py         — rewrite remaining English glossary terms to canonical Chinese
│   ├── validator.py             — post-translation glossary consistency warnings
│   └── pipeline.py              — orchestrate walk → encode → translate → enforce → decode → save
├── tests/                       — pytest, 81 tests, all mocked
├── scripts/smoke.py             — real-API end-to-end smoke test
└── docs/superpowers/
    ├── specs/                   — design doc
    └── plans/                   — implementation plan

Design

See docs/superpowers/specs/2026-05-08-word-translator-design.md for the original spec, and docs/superpowers/plans/2026-05-08-word-translator.md for the task-by-task implementation plan that produced the initial cut.

About

CLI tool to translate English Word documents to Chinese using Qwen3.5 with format preservation and a glossary.

Resources

Stars

0 stars

Watchers

0 watching

Forks

Releases

Packages

Contributors

Languages