zhconvert: correct five mechanical s2twp over-substitutions, and normalize 腳本 - #204
Conversation
…e 腳本 `s2twp`'s TWPhrases group carries a handful of entries that are not contested vocabulary. They append or swap a word fragment in a way that is wrong in Taiwanese usage in any register: 全局 → 全域**性** turns a noun into an adjective, 扩展 → 擴充**套件** glues on "package" where every use here is the verb, 控件 → 控制**元件** where the standard UI-control term is 控制項, 镜像 → 映**象** swaps the second character, and 适配器 → **介面卡** substitutes different hardware entirely — 介面卡 is a network interface card. Same shape as the 許可權 defect fixed for #144, and the same mechanism: an entry at the head of the TWPhrases group, keyed on the post-s2t form. Verified before relying on it — TWPhrases is the only dictionary in the chain that can emit any of these strings; the one hit elsewhere is STPhrases' identity entry 介面卡 → 介面卡, which preserves a genuine NIC but can never introduce one. That asymmetry is also why these are repaired in the phrase layer and not by a post-pass, which could not tell a converted 适配器 from a source 介面卡. Also normalizes 脚本 to 腳本 rather than 指令碼. Not only a term choice: the corpus already shipped both (指令碼 26, 腳本 5), because the STPhrases entry 本里 matches across the word boundary in 脚本里 and blocks the phrase layer, so a handful of sites escaped the substitution. Whichever term wins, that split had to close in the same pass. Six fixture cases per term — bare, in the corpus's own phrase shapes, and with negative cases pinning that a genuine 介面卡 and a source 控制项 are untouched — so a preset upgrade cannot silently reinstate any of this. content/ is regenerated output only, nothing hand-edited: 18 files, 40 substitutions, every changed byte reproduced by applying the six ruled terms alone to the origin/main version and requiring byte equality. Fixes #194 Co-Authored-By: Claude Opus 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01FeA1nwBz1ohH65dvffUGKr
|
ACCEPT — re-verified against current
Diff scope re-derived from scratch, not read off the PR: each of the 18 files taken as it stands on And the artifacts are gone from the built output, chrome included:
Three things in this PR are why it needed no round trip. You checked the mechanism before relying on it, and the check found something. #192's shadow works only because Four of the six are corrected, not merely disabled. An identity entry on The One correction on my side, from your table. I read Landing on Generated by Claude Code |
Fixes #194
s2twp'sTWPhrasesgroup carries a handful of entries that are not contested vocabulary at all. They append or swap a word fragment in a way that is wrong in Taiwanese usage in any register, for any audience — the same shape as the 許可權 defect PR #192 repaired, only rarer. This corrects the five of them that fire in this corpus, and normalizes 脚本 in the same pass.twpshipped全局扩展适配器控件镜像脚本Six lines in
TWP_PHRASE_OVERRIDESand 18 fixture cases inCONVERSION_CASES, both insrc/lib/zhconvert.ts. Everything undercontent/is regenerated output; nothing there is hand-edited.脚本 is a defect, not only a term choice
指令碼is the Microsoft-glossary rendering and腳本the ordinary Taiwanese word, but the reason this could not wait for a vocabulary round is that the corpus already shipped both — 指令碼 26, 腳本 5. That split is the straddling-phrase accident #192 diagnosed for the locative 里: the two-characterSTPhrasesentry本里matches across the word boundary in脚本里, which blocks the phrase layer as well as the character layer, so five sites escaped the substitution. The identity entry settles every site the same way, straddled or not.The mechanism was checked before it was used, not after
#192's shadow works because
TWPhrasesis the only dictionary in the chain that can emit the wrong string, and its entries are keyed on the post-s2t form. Both halves were verified for all six against the shippedopencc-jsdictionaries rather than assumed:Two things fell out of that check and are recorded in the code:
介面卡also exists inSTPhrases, as an identity entry. That does not defeat the shadow — an identity entry can preserve a genuine NIC but can never introduce one — and it is the reason this is repaired in the phrase layer instead of by a post-pass. A post-pass rewriting 介面卡 → 配接器 after conversion could not tell a converted 适配器 from a source 介面卡; the shadow never sees the genuine one. There is a fixture case pinning exactly that (['介面卡', '介面卡']), the counterpart of gen-zh-hant: ship 權限/實例 and the straddled locative 裡 #192's['许可权', '許可權'].TWPhrasescarries the same 象/像 slip twice, in顯像管 → 映象管. Left alone deliberately: no source in this corpus writes 显像管, so it emits nothing (grep -roh '映象管' dist | wc -l→ 0), and a CRT is not this card's business. Noted in the comment beside the 鏡像 override so it need not be rediscovered.Four of the six are corrected to the ordinary term rather than merely disabled — an identity entry on
全局would have left the mainland spelling standing, which is its own defect.Every changed byte is accounted for
Not "the diff looks right": each of the 18 changed files was taken as it stands on
origin/main, had only the six ruled substitutions applied, and was required to be byte-identical to the regenerated file. One unexplained character would fail its file.40 = 4 + 4 + 3 + 2 + 1 + 26, the six counts in the table.
Counts, in
distand not only in the MDX#192's patch round is the reminder that those are two different populations, so both were measured. In the built output:
Two things to read carefully in that table. The right-hand "before" is the standalone count: a bare
grep -c 全域onmain'sdistreturns 6 andgrep -c 擴充returns 5, but every one of those is the artifact itself — 全域 is a substring of 全域性, 擴充 of 擴充套件 — so the standalone term appeared zero times before and appears at the artifact's old count after. And the介面卡18 from 3 source occurrences, like指令碼35 from 26, is chrome amplification: one hit sits in a post description and another in a title, so they reach the cards, the topic hubs,rss.xmland<title>.控制項going 2 → 5 rather than 0 → 3 is the point of that row: the corpus was already emitting 控制項 from a source控制项while emitting 控制元件 from控件. The override closes that split, the way the 脚本 entry closes its own.In the generated MDX corpus, the acceptance numbers exactly: 全域性 4→0, 擴充套件 4→0, 介面卡 3→0, 控制元件 2→0, 映象 1→0, 指令碼 26→0, and 腳本 5 → 31, matching
脚本in the zh-Hans sources.Verification
All five gates via
scripts/pm/os-verify-lock.sh, exit captured before any pipe. Re-run in full atf650569, the branch head, after mergingorigin/main— #202 landed a new content-lint rule mid-round and this branch regenerates 18 files that rule inspects, so the whole set was taken again rather than carried over from the pre-merge run:f650569pnpm gen:zh-hantVERDICT command-exit 0 · held the lock 2spnpm checkVERDICT command-exit 0 · held the lock 11s—Result (135 files): 0 errors, 0 warnings, 0 hintspnpm content:lint --publishedVERDICT command-exit 0—✓ content lint passed (335 files, 44 glossary terms checked)pnpm buildVERDICT command-exit 0 · held the lock 66s—✓ content lint --dist passed (474 built blog pages checked)(the--distpass from #201, green; and #202's new rule, green)pnpm seo:smokeVERDICT command-exit 0—SEO smoke test passed (866 HTML pages checked)git status --porcelainis empty afterpnpm build— the Traditional locale stays fully generated. #187's link rewrite survives:grep -rn '/zh-Hans/' content/blog --include=index.zh-Hant.mdxis 0. Thedistcounts above were re-measured atf650569and are unchanged.Fixture ablation. Dropped
['適配器', '配接器']and re-ran the generator. The mutation was proven on disk before anything was read — the anchor count went 1 → 0 and the blob hash moved767ba60…→e15fe3a…, both checked, with atrapholding the restore:Restored and proven restored: on-disk blob back to
767ba60…= theHEADblob,git diff HEADempty, generator green again, tree clean. No rebuild step sits between the edit and the reading —scripts/gen-zh-hant.mjsimportssrc/lib/zhconvert.tsdirectly via Node type stripping — so there is no stale-disthazard to defeat this one.Browser pass, chrome and article body, 1440×900 and 390×844. The fix that stopped at
content/is exactly what #192's patch round existed to repair, so this was checked on both halves.src/lib/zhconvert.tsderives the UI strings too:/zh-Hant/security/s2tfromsrc/lib/security.ts— a UI string, in no MDX/zh-Hant/blog//zh-Hant/blog/forward-deployed-engineer-tools/wrongis all six artifact spellings,rightthe intended forms, both read off renderedinnerText. No horizontal overflow and no console errors on any of the six loads; the nav rendersObjectOS 產品 開發者 資源 定價 繁體中文 開始使用. Every replacement is the same length or shorter than what it replaces, so there was no layout risk to begin with — measured anyway.Scope
Only the six ruled terms. The 15 contested renderings in #195 — 元数据 → 後設資料 and the rest — are untouched, as are the two semantic collapses it records.
A sweep of all 143
TWPhrasesentries that fire in this corpus turned up no further artifact of this class: every remaining candidate (局部 → 區域性, 回调 → 回撥, 抛出 → 丟擲, 命名空间 → 名稱空間, 优先级 → 優先順序, 表达式 → 表示式, 外键 → 外部索引鍵) is already a row in #195's table. One term the survey missed,连接器→ 聯結器 at 17 source occurrences, is reported in #203 — contested rather than mechanical, so out of scope here and not touched.🤖 Generated with Claude Code
Generated by Claude Code