Skip to content

gen-zh-hant: ship 權限/實例 and the straddled locative 裡 - #192

Merged
hotlong merged 4 commits into
mainfrom
claude/issue-144-opencc-twp-overrides
Sep 3, 2026
Merged

hotlong merged 4 commits into
mainfrom
claude/issue-144-opencc-twp-overrides

Conversation

@hotlong

@hotlong hotlong commented Sep 3, 2026

Copy link
Copy Markdown
Contributor

Fixes #144

Implements the seat ruling on #144: keep the s2twp preset, 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 --porcelain is empty after pnpm build, which is the proof.

1. 权限 → 權限 and 实例 → 實例, by shadowing two dictionary entries

s2twp runs three conversion groups in order:

[STPhrases, STCharacters]  →  [TWPhrases]  →  [TWVariants]

TWPhrases has 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:

TWPhrases:    603 entries; entries producing 許可權/例項: [ '實例 例項', '權限 許可權' ]
STPhrases:  49276 entries; entries producing 許可權/例項: []
STCharacters: 3882 entries; entries producing 許可權/例項: []
TWVariants:     39 entries; entries producing 許可權/例項: []

Inside one dict group the first dictionary wins (Trie.loadDictGroup loads 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:

const TWP_PHRASE_OVERRIDES = [
  ['權限', '權限'],
  ['實例', '實例'],
];

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:

source plain tw twp (before) this PR
权限 權限 許可權 權限
权限边界 權限邊界 許可權邊界 權限邊界
行级权限 行級權限 行級許可權 行級權限
读取权限 讀取權限 讀取許可權 讀取權限
字段级权限 字段級權限 欄位級許可權 欄位級權限
实例 / 数据库实例 實例 / 數據庫實例 例項 / 資料庫例項 實例 / 資料庫實例
许可权 (a genuine word) 許可權 許可權 許可權 — untouched
数据 程序 对象 接口 服务器 软件 信息 缓存 用户 默认 數據 程序 對象 接口 服務器 軟件 信息 緩存 用戶 默認 資料 程式 物件 介面 伺服器 軟體 資訊 快取 使用者 預設 unchanged — this is why the preset stays

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.

STCharacters already maps a bare 里 to 裏, which TWVariants then 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 with STPhrases before converting it, and a two-character STPhrases entry that pins 里 as 里 — a place name, a proper noun, a measure word — matches across a word boundary and takes the 里 with it:

the text the entry that straddled it what it means
脚本里 本里 a village
架构里根本 / 系统里根本 里根 Reagan
周报里拉出来 里拉 the lira
知道里面 道里 a district of Harbin
文件里加三条 里加 Riga
定义里长出来 里长 a village chief
函数里 数里 a distance

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):

const LOCATIVE_LI = [
  [/(指令碼|腳本|架構|系統|週報|檔案|定義|函數|函式)里(?!程)/g, '$1裡'],
  [/(知道|報道|頻道|通道)里(?!程)/g, '$1裡'],
];

All twelve sites, before → after:

file before after
ai-compliance-control 難改的腳本里 腳本裡
ai-ontology-open-protocol 架構里根本 架構裡根本
ai-project-risk-assistant 週報里拉出來 週報裡拉出來
airtable-style-ai-builder 不知道里面 不知道裡面
automation-governed-flow-changes 隱藏在腳本里 腳本裡
automation-pause-resume-approvals 腳本里一段 腳本裡一段
give-your-agent-rules-for-governable-apps 規則檔案里加三條 檔案裡加三條
mcp-governed-tool-layer 從定義里長出來 定義裡長出來
objectos-agent-permission-boundaries 處理函數里的授權 函數裡的授權
objectos-automation-engine 腳本里到底 腳本裡到底
retool-vs-ai-native-app-platform 和腳本里。 腳本裡。
why-ai-agent-pilots-fail-four-layers 系統里根本 系統裡根本

The 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 title tags, cards, RSS and the sitemap. The fixture runs before a single file is written. Verified by ablation, both legs restored byte-identically afterwards:

=== leg: drop the 權限 identity override ===
  anchor "['權限', '權限'],"  before: 1   after mutation: 0
  ✗ gen-zh-hant: fixture failed
    [conversion] in: "权限"     expected: "權限"     actual: "許可權"
    [conversion] in: "权限边界"  expected: "權限邊界"  actual: "許可權邊界"
    … 4 more
  generator exit: 1
  restored: blob 0d8a0c1d… == HEAD 0d8a0c1d…

=== leg: drop 函數 from the locative whitelist ===
  anchor "|函數|函式)里(?!程)"  before: 1   after mutation: 0
  ✗ gen-zh-hant: fixture failed
    [conversion] in: "只存在于命令式处理函数里"
      expected: "只存在於命令式處理函數裡"  actual: "只存在於命令式處理函數里"
  generator exit: 1
  restored: blob 0d8a0c1d… == HEAD 0d8a0c1d…

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.

gate verdict line it printed
pnpm content:lint ✓ content lint passed (335 files, 44 glossary terms checked) — EXIT=0
pnpm content:lint --published ✓ content lint passed (335 files, 44 glossary terms checked) — EXIT=0
pnpm check Result (135 files): - 0 errors - 0 warnings - 0 hints — EXIT=0
pnpm build [build] 867 page(s) built in 37.23s / [build] Complete! — EXIT=0
pnpm seo:smoke SEO smoke test passed (866 HTML pages checked) — EXIT=0

Run 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.

before after
許可權 in content/blog/**/index.zh-Hant.mdx 435 0
權限 0 435
权限 in the zh-Hans sources it derives from 435 435
例項 2 0
實例 0 2
实例 in the zh-Hans sources 2 2
里 21 9 (all 里程碑)
裡 541 553

git status --porcelain after pnpm 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.

files changed under content/blog: 43
files explained by the ruled terms alone: 43
files with an unexplained change: 0
substitutions applied: 許可權→權限 435, 例項→實例 2, straddled 里→裡 12

435 + 2 + 12 = 449 substitutions, and nothing else moved.

#187's link rewrite survives. Not assumed — the /zh-Hant/ link set is byte-identical to origin/main (9 occurrences, 7 distinct targets, in 5 files), grep -rn '/zh-Hans/' content/blog --include=index.zh-Hant.mdx is still 0, and the generator line inside pnpm build still reads:

✓ zh-Hant: generated 46, kept 0 hand-maintained, 5 with body links pointed at /zh-Hant/

(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/:

page 許可權 權限 horizontal overflow
objectos-agent-permission-boundaries 0 32 0 px
ai-agent-business-data-security-boundaries 0 26 0 px
mcp-governed-tool-layer 0 24 0 px

Titles and headings read correctly at both widths, e.g. AI Agent 權限檢查跑在哪一層:查詢下推、欄位脫敏與工具閘門 (was AI 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

term zh-Hans occurrences files ships as sense in this corpus
脚本 31 11 指令碼 (26) and 腳本 (5) shell/webhook glue in an automation argument — never a screenplay
视图 72 19 檢視 the application object (a saved list/board view: 物件、欄位、檢視、流程), not the SQL VIEW
运行时 218 36 執行時 overwhelmingly the noun — a runtime you self-host, replace, audit

Three things in that table are worth the seat's attention beyond the vocabulary question:

  1. 脚本 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.

  2. 视图 and 查看 collapse into one string. twp maps 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.

  3. 运行时 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 twp substitutions with the same smell

123 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):

zh-Hans n files twp ships the ordinary Taiwanese term
元数据 174 39 後設資料 中繼資料 / 詮釋資料
类型 67 20 型別 類型
发布 48 10 釋出 發布
审核 40 6 稽核 審核
优先级 20 9 優先順序 優先權 / 優先級
绑定 17 7 繫結 繫結 / 綁定
表达式 13 7 表示式 運算式 / 表達式
回调 11 2 回撥 回呼 / 回調
参数 8 5 引數 參數
全局 4 4 全域性 全域 — the 性 is an artifact
扩展 4 4 擴充套件 擴充 / 擴充功能 — the 套件 is an artifact
映射 4 3 對映 映射
命名空间 3 2 名稱空間 命名空間
外键 3 1 外部索引鍵 外來鍵
局部 3 3 區域性 局部
抛出 3 1 丟擲 拋出
适配器 3 1 介面卡 配接器 — 介面卡 is an interface card
高级 3 3 高階 高級
控件 2 2 控制元件 控制項
镜像 1 1 映象 映像

The 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_OVERRIDES with 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

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

hotlong commented Sep 3, 2026

Copy link
Copy Markdown
Contributor Author

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:

  • Counts against their own sources. 許可權 435→0, 權限 0→435 — and 435 is exactly the count of 权限 in the zh-Hans files it derives from. 例項 2→0, 實例 0→2, matching 实例 2. 里 21→9, all nine 里程碑; 裡 541→553.
  • Diff scope re-derived from scratch. I took each changed file as it stands on main, applied only 許可權→權限 and 例項→實例, and required the residue to be the locative alone: 43 files changed, 0 with an unexplained change, 12 lines needing the 里→裡 step.
  • The straddle diagnosis is right, and the guard is real. 里根 2→0, 里加 1→0, 里拉 1→0, 里長 1→0 — every one accounted for in your table of twelve, and there was never a genuine Reagan, Riga, lira or village chief in a corpus about ontology platforms. 里程碑 stays at 9.
  • Point generated zh-Hant body links at the Traditional locale #187 preserved: /zh-Hant/ link set byte-identical, /zh-Hans/ still 0.
  • The design decision verified from the other side. I ran the unpatched preset on a genuine 许可权: it gives 許可權. So a post-pass rewriting 許可權→權限 would have corrupted a real word, and shadowing does not. That is the single most important choice in this PR and it is the correct one, not a stylistic preference.
  • Union with current main (5d4b0b6 + this branch): all five gates 0, 867 pages, 866 HTML checked, tree clean.

What my browser pass found that neither of us was looking for.

I served dist/ and grepped the served HTML rather than the article text, and the Traditional pages come back carrying both spellings:

許可權   113 pages,  825 occurrences
權限      84 pages,  891 occurrences

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 — i18n.ts, terms.ts, glossary/*, content-pages/*, pricing.ts, home.ts, legal.ts, cluster-i18n.ts. It derives every Traditional UI string and term label at build time from the Simplified source (i18n.ts:179 is literally permissionsSecurity: '权限与安全'), and it carries none of your overrides.

That gap is mine. My claim comment set the file surface as scripts/gen-zh-hant.mjs plus the regenerated MDX, and explicitly said nothing else in flight touches the generator. You worked exactly the surface I gave you and worked it thoroughly. The card framed this as a generator problem too. Neither of us went looking for a second Converter({to:'twp'}) in src/lib/, and there was one.

Why this holds the PR rather than becoming a follow-up. main today is uniformly wrong, which reads as a (bad) house style. This branch would ship a site that is right in the prose and wrong in the chrome of the same page, roughly half and half — which reads as a bug. And a follow-up would have to touch your override table anyway, so splitting it guarantees two PRs editing one thing.

What to do:

  1. Move TWP_PHRASE_OVERRIDES and LOCATIVE_LI into one module that both worlds can load, and have scripts/gen-zh-hant.mjs and src/lib/zhconvert.ts build their converter from it. There is precedent from an hour ago: ci(content-lint): resolve topic hub slugs instead of exempting them #182 created src/lib/term-data.ts as an import-free module precisely so plain Node and Vite could both read it, and content-lint: derive all four frontmatter term sets from the taxonomy #190 (5d4b0b6) now consumes it from a Node script. Follow that shape. One definition of "how this site converts Simplified to Traditional", not two.
  2. Apply the whole pipeline to both — dictionary overrides and the locative whitelist. Short UI labels are unlikely to straddle, but two converters that differ will drift, and "they differ in a way that doesn't matter yet" is how this defect happened.
  3. Fix the hand-written literal at src/lib/terms.ts:80, which carries 許可權 in a Traditional template string. Check its neighbours in the same block while you are there and report anything else you find — that block claims to be derived and is not.
  4. Re-measure over dist/, not over the MDX. The acceptance number for this round is grep -rl '許可權' dist --include='*.html' | wc -l going to 0, with the 例項 page too. Report 權限 and 實例 counts alongside.
  5. Extend the fixture so it covers the shared module rather than only the generator's copy, and ablate it the same way you did the first two: remove an override, show the fixture red.
  6. Browser pass over the chrome this time, not the article body: the nav item, the glossary index, and one marketing route, at 1440 and 390. The article text is already proven.
  7. Re-run the five gates through the shared lock and quote each verdict with git rev-parse --short HEAD.

On your four open questions — rulings so you are not blocked, but none of them is this round's work:

  • 脚本 (Q1): C, normalize on 腳本, by shadowing the entry, same shape as 權限. The corpus is shell/webhook glue, and 腳本 is the ordinary Taiwanese word for it; 指令碼 is the Microsoft rendering. But the finding that matters more is the one you surfaced — the corpus already ships both, 26 and 5, from the same straddle. Not this PR. File it.
  • 视图/查看 (Q2): C, native reviewer, with your measurement as the input. You are right that B is mechanically safe here because this corpus only ever means the UI object — but 87 occurrences carrying a noun and a verb in one string is precisely what a native reader notices and I am not one.
  • 运行时 (Q3): A for now, leave 執行時. 218 occurrences cannot be sense-split by a dictionary, and 執行時 is defensible; 「由執行時在執行時強制」 is a genuine wart but it is a writing problem in the Simplified source as much as a conversion one. Native reviewer, with the four collision sentences you found.
  • The artifact group (Q4): A, and file it. 全域性, 擴充套件, 介面卡, 控制元件, 映象 are not vocabulary judgements — 介面卡 is a network interface card, not an adapter. Same shape as 許可權, one line each in the shared table plus a fixture case. Not this PR either; this round is the second-converter gap and nothing else.

The seat will file the two follow-up cards from your survey — you do not need to.

Serial constraints unchanged: you still own scripts/gen-zh-hant.mjs and the regenerated Traditional corpus, and this round adds src/lib/zhconvert.ts, src/lib/terms.ts and the new shared module to your surface. #190 landed on main as 5d4b0b6 and touches scripts/content-lint.mjs only — merge main in and expect no conflict. #159 owns content/blog/**/index.ja.mdx; #166 owns SVGs.


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

hotlong commented Sep 3, 2026

Copy link
Copy Markdown
Contributor Author

ACCEPT — the patch round closes the gap, measured over dist/ where the gap actually was.

The agent was killed by a container restart before it could report, so this is a review of the branch alone. 80c0170 was pushed first, which is the reason there is anything to review.

The acceptance number, from built HTML rather than MDX:

before this round after
許可權 825 occurrences, 113 pages 0, 0 pages
權限 891 1716, 113 pages
例項 1 0
「許可權與安全」 in the Traditional nav on every page 0 — 242 occurrences of 「權限與安全」

#187's link rewrite survives: /zh-Hans/ in generated Traditional files still 0, and the build line still reads zh-Hant: generated 46, kept 0 hand-maintained, 5 with body links pointed at /zh-Hant/.

Union with current main (c2b3171 + this branch + #196), all five gates through the shared lock: content:lint 0, content:lint --published 0, check 0, build 0 (867 page(s) built), seo:smoke 0 (866 HTML pages checked), git status --porcelain 0 lines.

Browser pass over the chrome, which is where the defect lived — four pages × two viewports, page-level horizontal overflow 0px on all eight, 許可權 0 on every one. (The thead/tr/th flagged wider than the viewport at 390 are the article tables inside the overflow-x:auto container #157 gave a scroll affordance; that is the design, not a regression.)

The consolidation is the right shape, and better than what I asked for. I asked for a new shared module; you made src/lib/zhconvert.ts itself the single definition and had the generator import it directly under Node type stripping — the term-data.ts pattern, one file instead of three. Three things in it are worth naming because they are what makes it hold:

  • It fails loudly on an upgrade instead of silently landing in the wrong group. conversionChain?.length !== 3 throws with a message that tells the next person to re-derive the overrides rather than take the bump. The shadow depends on a structural property of s2twp; asserting that property is the difference between a fix and a time bomb.
  • The fixture pins the negative. ['许可权', '許可權'] and ['例项', '例項'] are in the case list, so the exact behaviour that rules out a post-pass — a genuine word still converting on its own terms — is now a build gate rather than a paragraph of reasoning in a PR body. That is the single most important property here and it is now defended.
  • ['权限与安全', '權限與安全'] is in the fixture. The string my scoping missed is now the string the fixture watches. That is the correct response to this round's defect.

The header comment earns its length: it records why twp is kept rather than dropped to tw, why the shadow goes at the head of the phrase group, why identity entries beat a post-pass, and — critically — the Node-vs-Vite specifier constraint that would otherwise be rediscovered by breaking the generator. Documenting why the file may not have relative imports is exactly the note that stops the next refactor.

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 main. #194 is unblocked and can use this table.


Generated by Claude Code

@hotlong
hotlong marked this pull request as ready for review September 3, 2026 03:08
@hotlong
hotlong merged commit 9522e69 into main Sep 3, 2026
1 check passed
hotlong added a commit that referenced this pull request Sep 3, 2026
…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.
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

OpenCC s2twp renders 权限 as 許可權 in every generated zh-Hant page (430 occurrences, 42 files)

2 participants