gen-zh-hant: ship 權限/實例 and the straddled locative 裡 - #192
Conversation
The generator converts zh-Hans to zh-Hant with OpenCC's `s2twp`. Two of that preset's 603 phrase entries are wrong for this corpus, and one segmentation accident pins a locative: 权限 -> 許可權 a Microsoft-glossary rendering; 權限 is the ordinary term 实例 -> 例項 not standard Taiwanese usage in any register X里 straddling STPhrases matches pin the locative 里 as 里 Keep the preset — its other 142 firing entries (資料, 程式, 物件, 介面, 伺服器, 軟體, 資訊, 快取, 使用者, 預設 …) are all correct, and plain `tw` would lose every one of them. Instead put identity entries at the head of the TWPhrases group, which disables exactly those two rules and nothing beneath them; a genuine 许可权 in a source still converts to 許可權 on its own terms. The locative is a different defect: STCharacters already reads a bare 里 as 裏/裡, and 541 of 553 come out right unaided. The twelve that do not are all a two-character STPhrases entry (本里, 里根, 里拉, 道里, 里加, 里长, 数里) matching across a word boundary. Those entries are correct in their own right, so the repair is a whitelist applied after conversion, behind a listed word only, guarded so 里程碑 is never touched. Both are pinned in the generator's fixture, which runs on every dev and build, so a preset upgrade cannot silently put 許可權 back in 435 places. A new unclaimed 里 is reported rather than guessed at. Regenerated: 許可權 435 -> 0, 權限 0 -> 435 (the exact count of 权限 in the Simplified sources), 例項 2 -> 0, 實例 0 -> 2, 裡 541 -> 553. The 8 links #187 pointed at /zh-Hant/ are unchanged. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01FeA1nwBz1ohH65dvffUGKr
|
PATCH ROUND — and the reason for it is a defect in my dispatch, not in your work. Everything in this PR is accepted. I re-derived the load-bearing claims independently rather than reading them off the body, and they all hold:
What my browser pass found that neither of us was looking for. I served The nav on every Traditional page still reads 「許可權與安全」. The glossary (23 pages), 26 marketing and legal routes, the homepage, and the chrome of 64 blog posts are all still on the old spelling. A reader would see the corrected title 「AI Agent 權限檢查跑在哪一層」 sitting directly under a nav item saying 許可權. The cause is a second converter this card never named: // src/lib/zhconvert.ts
const convert = OpenCC.Converter({ from: 'cn', to: 'twp' });
export const s2t = (s: string): string => convert(s);Ten modules import it — That gap is mine. My claim comment set the file surface as Why this holds the PR rather than becoming a follow-up. What to do:
On your four open questions — rulings so you are not blocked, but none of them is this round's work:
The seat will file the two follow-up cards from your survey — you do not need to. Serial constraints unchanged: you still own Generated by Claude Code |
The site had a second `Converter({from:'cn',to:'twp'})` in
`src/lib/zhconvert.ts`, imported by ten modules, deriving every Traditional UI
string, term label, glossary entry and marketing page from its zh-Hans source.
It carried none of the overrides, so the built site shipped both spellings: an
article title reading 權限 sat under a nav item reading 許可權.
Move the whole pipeline — the two shadowed TWPhrases entries, the locative
whitelist, and the fixture cases that pin them — into `src/lib/zhconvert.ts`,
and have `scripts/gen-zh-hant.mjs` import it. One definition of how this site
converts Simplified to Traditional, not two. The module stays free of relative
imports so plain Node can load it by type stripping, the same constraint
`src/lib/term-data.ts` documents.
The fixture runs from the generator, which `pnpm dev` and `pnpm build` both
execute before Astro starts, so a bad conversion now fails the build before a
page is served rather than only before an MDX file is written. Three UI-string
cases are added for the surface that was wrong.
`src/lib/terms.ts` carried the one hand-written Traditional literal with 許可權
in it. Audited against the derived conversion: three of the four strings in
that block are byte-identical to s2t(zh-Hans), and the fourth diverged in
exactly this one word, so the block really is derived output that drifted.
Regenerating the blog corpus through the shared module leaves it byte-identical
— the refactor changes where the rules live, not what they do.
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01FeA1nwBz1ohH65dvffUGKr
|
ACCEPT — the patch round closes the gap, measured over The agent was killed by a container restart before it could report, so this is a review of the branch alone. The acceptance number, from built HTML rather than MDX:
Union with current Browser pass over the chrome, which is where the defect lived — four pages × two viewports, page-level horizontal overflow 0px on all eight, The consolidation is the right shape, and better than what I asked for. I asked for a new shared module; you made
The header comment earns its length: it records why On the locative comment: the observation that the straddle also blocks the phrase layer, which is why the repaired text reads 腳本 and 函數 rather than 指令碼 and 函式, and that both spellings are therefore listed so the rule survives once a straddle stops hiding one — that is the kind of second-order reasoning that keeps a whitelist correct after the thing it works around is fixed. It also anticipates #194 exactly. Landing on Generated by Claude Code |
…alize 腳本 (#204) Closes #194. Five s2twp TWPhrases entries append or swap a word fragment in a way that is wrong in Taiwanese usage in any register: 全局 gained a 性 that turns a noun into an adjective, 扩展 gained a spurious 套件, 控件 became 控制元件 rather than 控制項, 镜像 took the wrong second character, and 适配器 became 介面卡 — a network interface card, different hardware entirely. Same shape as the 許可權 defect #192 repaired, only rarer. 脚本 is corrected in the same pass because the corpus already shipped both spellings, 指令碼 26 and 腳本 5: the STPhrases entry 本里 matches across the word boundary in 脚本里 and blocks the phrase layer, so five sites escaped the substitution. The override settles every site the same way. Four of the six are corrected to the ordinary term rather than merely disabled — an identity entry on 全局 would leave the mainland spelling standing. 介面卡 also exists in STPhrases as an identity entry, which is why this is repaired in the phrase layer: a post-pass could not tell a converted 适配器 from a genuine 介面卡. A fixture pins that. 18 files regenerated, 40 substitutions, none unexplained. The same 象/像 slip in 顯像管 is left alone: it emits nothing in this corpus.
Fixes #144
Implements the seat ruling on #144: keep the
s2twppreset, layer overrides on it, correct exactly three things, leave the Traditional locale fully generated.What changed
One file of logic —
scripts/gen-zh-hant.mjs— plus the 43 Traditional posts it regenerates. No generated file was hand-edited;git status --porcelainis empty afterpnpm build, which is the proof.1. 权限 → 權限 and 实例 → 實例, by shadowing two dictionary entries
s2twpruns three conversion groups in order:TWPhraseshas 603 entries. Exactly two of them are wrong for this corpus, and I checked that mechanically — they are the only entries in the whole chain that can emit these strings:Inside one dict group the first dictionary wins (
Trie.loadDictGrouploads a group in reverse, so an earlier entry is loaded later and overwrites). So an identity entry at the head of the TWPhrases group disables exactly that one rule and nothing under it:Why identity entries and not a post-pass. Rewriting
許可權back to權限after the fact would also rewrite a genuine许可权— a real Simplified word — that the preset had converted correctly. Shadowing does not:twtwp(before)2. The 里/裡 miss is not vocabulary — it is a straddling phrase match
This turned out to be a different defect from the one the card described, and the root cause is worth recording.
STCharactersalready maps a bare 里 to 裏, whichTWVariantsthen makes 裡 — the locative is OpenCC's default, and 541 of the 553 locatives in this corpus come out right unaided. The twelve that did not are all the same accident: OpenCC segments the Simplified text withSTPhrasesbefore converting it, and a two-characterSTPhrasesentry that pins 里 as 里 — a place name, a proper noun, a measure word — matches across a word boundary and takes the 里 with it:Every one of those entries is right in its own context, so none is shadowed. The repair is a whitelist applied after conversion, behind a listed word only — it cannot fire on 公里, 英里, 里程碑, 鄰里, 里長 or a place name, because none of those follow one of the listed words, and
(?!程)holds 里程碑 out of the one case that could collide (系統里程碑 is a milestone, not something inside the system):All twelve sites, before → after:
ai-compliance-controlai-ontology-open-protocolai-project-risk-assistantairtable-style-ai-builderautomation-governed-flow-changesautomation-pause-resume-approvalsgive-your-agent-rules-for-governable-appsmcp-governed-tool-layerobjectos-agent-permission-boundariesobjectos-automation-engineretool-vs-ai-native-app-platformwhy-ai-agent-pilots-fail-four-layersThe nine remaining 里 in the corpus are all 里程碑. They are untouched.
3. The fixture, and a report channel for the next one
The ruled terms are pinned in the generator's existing fixture (30 new cases beside the 16 link-rewrite ones), so an opencc-js bump that reinstates 許可權 fails the build instead of quietly putting it back in 435 places, in
titletags, cards, RSS and the sitemap. The fixture runs before a single file is written. Verified by ablation, both legs restored byte-identically afterwards:A whitelist can only fix what is on it, so the generator now also reports any 里 the whitelist did not claim and that is not on a short list of genuine 里 — the same "I could not prove it, so I am telling you" channel the file already uses for un-rewritable links. It prints nothing today.
Verification
All at
b5600d6, the branch head.pnpm content:lint✓ content lint passed (335 files, 44 glossary terms checked)— EXIT=0pnpm content:lint --published✓ content lint passed (335 files, 44 glossary terms checked)— EXIT=0pnpm checkResult (135 files): - 0 errors - 0 warnings - 0 hints— EXIT=0pnpm build[build] 867 page(s) built in 37.23s/[build] Complete!— EXIT=0pnpm seo:smokeSEO smoke test passed (866 HTML pages checked)— EXIT=0Run under the shared verify lock (
VERDICT command-exit 0 · held the lock 52s), each exit code captured by redirecting to a file before any pipe.Counts.
content/blog/**/index.zh-Hant.mdxgit status --porcelainafterpnpm build: 0 lines. The generator was changed, not the generated text.Diff scope — every changed byte is a ruled term. Rather than eyeballing 43 files, the claim is checked mechanically: take each file as it stands on
origin/main, apply only the three ruled substitutions, and require the result to equal the regenerated file byte for byte. One unexplained character anywhere fails a file.435 + 2 + 12 = 449 substitutions, and nothing else moved.
#187's link rewrite survives. Not assumed — the
/zh-Hant/link set is byte-identical toorigin/main(9 occurrences, 7 distinct targets, in 5 files),grep -rn '/zh-Hans/' content/blog --include=index.zh-Hant.mdxis still 0, and the generator line insidepnpm buildstill reads:(The ruling said 8 rewritten links; the measured corpus has 9
/zh-Hant/occurrences across 7 distinct targets. Either way, identical before and after.)Browser. The three worst-affected Traditional pages, at 1440×900 and 390×844, served from
dist/:objectos-agent-permission-boundariesai-agent-business-data-security-boundariesmcp-governed-tool-layerTitles and headings read correctly at both widths, e.g.
AI Agent 權限檢查跑在哪一層:查詢下推、欄位脫敏與工具閘門(wasAI Agent 許可權檢查跑在哪一層). Nothing reflows; the layout is unchanged because only characters changed, and both spellings are two glyphs wide.Survey — reported, not fixed
The three the ruling named
VIEWThree things in that table are worth the seat's attention beyond the vocabulary question:
脚本 already ships as two different words. 26 occurrences render 指令碼 and 5 render 腳本 — not an editorial choice, the same straddle described above (
本里blocks the phrase layer as well as the character layer). So whatever the seat decides, the corpus is currently inconsistent with itself.视图 and 查看 collapse into one string.
twpmaps the noun 视图 (72) and the verb 查看 (16) both to 檢視, so 87 occurrences of 檢視 in 25 files carry two unrelated meanings — 「檢視企業客戶工單」 (verb) sits next to 「一個高風險商機檢視」 (noun) in the same corpus. A native reviewer choosing 視圖 for the noun would separate them; leaving it as-is keeps them merged.运行时 collapses the noun and the adverbial, and it shows. Four sentences use both senses at once:
ai-wrote-your-app-dare-to-merge) — "enforced by the runtime at run time"airtable-omni-vs-governed-ai-app-platform)beyond-agentforce-copilot-open-runtime)eu-ai-act-runtime-audit)The noun sense dominates (「可移植執行時」, 「開源執行時」, 「基礎執行時」, 「自託管執行時」), which is exactly the reading 執行時 serves worst.
Beyond that list — other
twpsubstitutions with the same smell123 of the 603 TWPhrases entries fire in this corpus. Most are correct and are the reason the preset is kept. These are the ones that read as Microsoft-glossary renderings rather than the ordinary Taiwanese term, ordered by how much text they touch (count = occurrences of the source term in the zh-Hans corpus, which is the reliable number; a few outputs collide):
twpships性is an artifact套件is an artifact介面卡is an interface cardThe last group — 全域性, 擴充套件, 介面卡, 控制元件, 映象 — is a different class from 許可權: those are not contested vocabulary, they are mechanical over-substitutions that are wrong in any register, exactly the shape of 許可權 but rarer. If the seat wants a second pass, they are the cheapest correct wins and each is a one-line addition to
TWP_PHRASE_OVERRIDESwith a fixture case.The Simplified-text covers and diagrams on Traditional pages, which the browser pass makes very visible, are already filed as #184 — not touched here.
Not done
Nothing from the ruling was skipped. The 里/裡 rule was shippable: the root cause turned out to be mechanical (a straddling phrase match), which made a provably narrow whitelist possible rather than a guess, and it fixed all twelve sites rather than the one the card cited.
🤖 Generated with Claude Code
https://claude.ai/code/session_01FeA1nwBz1ohH65dvffUGKr
Generated by Claude Code