|
| 1 | +--- |
| 2 | +name: review-rendered-diff |
| 3 | +description: Use when a reviewer needs to see how changed PO translations look on the rendered Python docs page — before/after comparison, red/green inline diff on the built HTML, or screenshots of changed paragraphs for a PR description or review comment. |
| 4 | +argument-hint: <po-file> [--base <git-ref>] [--new <git-ref>] |
| 5 | +--- |
| 6 | + |
| 7 | +# review-rendered-diff |
| 8 | + |
| 9 | +Show translation changes **inside the real rendered page**, each changed paragraph outlined, |
| 10 | +one screenshot per paragraph plus an overview image. Each entry is shown one of three ways: |
| 11 | + |
| 12 | +| Change | Shown as | Picked when (`--mode auto`) | |
| 13 | +|---|---|---| |
| 14 | +| Edit of an existing translation | **inline**: deleted text red with strikethrough, inserted green | default | |
| 15 | +| English → Chinese (new translation) | **two blocks**: gray「原文 (English)」above green「譯文」 | base msgstr empty, or the old rendered text is <20% CJK | |
| 16 | +| Heavy rewrite of a translation | **two blocks**: red「修改前」above green「修改後」 | text ≥30 chars and >50% of characters changed | |
| 17 | + |
| 18 | +Two blocks are used where a character diff would be solid red and green. Short text, such as |
| 19 | +headings and table cells, always stays inline. Force a style with `--mode inline|blocks`, or |
| 20 | +tune the rewrite cut-off with `--rewrite-threshold` (default 0.5). Each report line names the |
| 21 | +mode used, e.g. `[blocks: rewritten, 87% changed]`. |
| 22 | + |
| 23 | +## Run it |
| 24 | + |
| 25 | +From the repo root: |
| 26 | + |
| 27 | +```bash |
| 28 | +uv run .claude/skills/review-rendered-diff/scripts/render_diff.py howto/logging.po --base origin/3.15 |
| 29 | +``` |
| 30 | + |
| 31 | +The script declares its dependencies inline (PEP 723), so `uv run` needs no `--with` flags. |
| 32 | + |
| 33 | +| Flag | Default | Use | |
| 34 | +|---|---|---| |
| 35 | +| `--base` | `origin/3.15` | "before" version (fetch it first; SSH may fail, see below) | |
| 36 | +| `--new` | working-tree file | "after" version as a git ref, e.g. a PR branch | |
| 37 | +| `--out` | `.render-review/<po without .po>/` | where screenshots and builds go; with several POs, one subfolder per PO | |
| 38 | +| `--context` | `140` | px of surrounding page kept above/below each paragraph | |
| 39 | + |
| 40 | +Common targets: |
| 41 | + |
| 42 | +| Review | Command arguments | |
| 43 | +|---|---| |
| 44 | +| Uncommitted edits | `<po> --base HEAD` | |
| 45 | +| A PR branch | `<po> --base origin/3.15 --new <branch>` | |
| 46 | +| One past commit, all its POs | `$(git show --name-only --format= <sha> -- '*.po') --base <sha>^ --new <sha>` | |
| 47 | + |
| 48 | +Each PO takes about 40 seconds, because the page is built twice. The script prints one |
| 49 | +line per changed entry (`L<entry line>: diff-N.png`), plus the diff page path and |
| 50 | +`all-diffs.png`. |
| 51 | + |
| 52 | +Before reporting, open a couple of PNGs with Read. To share them, use SendUserFile on |
| 53 | +`all-diffs.png` if you have it. As a subagent, return the absolute paths instead. Or drag |
| 54 | +the images into a GitHub comment. |
| 55 | + |
| 56 | +## How it works (why it is safe) |
| 57 | + |
| 58 | +- **Isolated builds:** each version's PO goes into its own temp `locale_dirs`, and |
| 59 | + `sphinx-build` runs on that one `.rst` into `--out/build-{old,new}/`. The working-tree PO |
| 60 | + and `../cpython/Doc/locales` are never touched. |
| 61 | +- **Changed entries** are found by comparing msgstr per `(msgctxt, msgid)` with polib. A fuzzy |
| 62 | + msgstr counts as untranslated, because Sphinx does not render it, so a fixed fuzzy entry |
| 63 | + shows as a new translation. |
| 64 | +- **Locating paragraphs:** roles, inline literals and links are dropped first, because |
| 65 | + `` :meth:`~Handler.setFormatter` `` renders as `setFormatter()`. The longest remaining plain |
| 66 | + run is matched against the rendered text, and the smallest enclosing block is used. When |
| 67 | + that run appears in several blocks (e.g. the `asdict()`/`astuple()` sentences), the block |
| 68 | + not yet highlighted and closest to the whole entry wins. |
| 69 | +- **Two blocks in tables and lists** go inside the cell or item, so the table and list |
| 70 | + structure survives. |
| 71 | +- **Diff on text, not HTML:** a character diff of the rendered text, applied to text nodes only, |
| 72 | + so links, inline code and `¶` anchors keep working. |
| 73 | + |
| 74 | +## Gotchas |
| 75 | + |
| 76 | +- **Do not use `make build` for this.** It hard-links the PO into `../cpython`, runs |
| 77 | + `git pull` on cpython, and swapping the PO in place risks losing uncommitted edits. |
| 78 | +- **Exit code 1 is normal.** Sphinx fails on unrelated CPython redirect warnings |
| 79 | + (`library/functions.html redirects to ...`). The script judges success by whether the HTML |
| 80 | + exists. On failure, read `build-*/build.log`. |
| 81 | +- **`SKIPPED (paragraph not found)`** means the entry has no rendered paragraph, e.g. an index |
| 82 | + entry or code block, or its msgid no longer matches the `../cpython` sources (the page |
| 83 | + shows English). Check `../cpython` is on the same branch as the PO. `../cpython` can also be |
| 84 | + newer than the PO's last CPython sync: those entries are not a script bug, they will turn |
| 85 | + fuzzy at the next sync, and are worth telling the PR author about. A trailing ` ::` |
| 86 | + literal-block marker is already handled, so it is not the cause. For a skip you cannot |
| 87 | + explain, grep the new msgstr's plain text in `build-new/html/<po>.html`. |
| 88 | +- **A paragraph containing several changed entries** is highlighted once, and later entries |
| 89 | + report "already highlighted". |
| 90 | +- **Inline diffs fragment small CJK edits** (e.g. 可能~~不是~~ shows only the changed |
| 91 | + characters). That is accurate. If a reviewer finds a paragraph choppy, rerun with |
| 92 | + `--mode blocks` or a lower `--rewrite-threshold`. |
| 93 | +- **Do not judge a rewrite by `SequenceMatcher.ratio()`.** Common CJK characters keep |
| 94 | + matching, so a fully rewritten paragraph still scored 0.45. The script measures the share |
| 95 | + of changed characters instead. |
| 96 | +- **A large new translation** (a whole file) makes one screenshot per paragraph, e.g. 147 for |
| 97 | + `howto/logging.po`. Point reviewers to `<po>-diff.html` rather than `all-diffs.png`. |
| 98 | +- **Labels use the entry's first line** (the `#:` comment), not the msgstr line. |
| 99 | +- **No `../cpython`:** run `make prepare_deps` once. |
| 100 | +- **SSH to GitHub resets:** fetch over HTTPS: |
| 101 | + `git -c credential.helper='!gh auth git-credential' fetch https://github.com/python/python-docs-zh-tw.git 3.15:refs/remotes/origin/3.15` |
| 102 | + |
| 103 | +## Not for |
| 104 | + |
| 105 | +- Checking reST validity: use `make lint` / `sphinx-lint`. |
| 106 | +- Judging translation quality: use the `validate-translation` skill. This skill only |
| 107 | + visualizes changes. |
0 commit comments