Files
github-zh-tw/SUMMARY.md
Timmy 5d48265bb2 docs: 新增 README / QUICKSTART / SUMMARY 三份文件
三份文件定位不重疊:
- README.md:參考手冊(功能、架構、規則對照表、相容性)
- QUICKSTART.md:操作指南(安裝、自訂翻譯、自訂跳過、FAQ)
- SUMMARY.md:開發故事線(v0.6→v0.15 順序敘事、設計心得)
2026-04-22 17:12:33 +08:00

164 lines
7.9 KiB
Markdown
Raw Permalink Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
# 開發故事:從 v0.6 到 v0.15
這份腳本不是一次寫到位的。初版只翻了十幾個字,每看一頁 GitHub 就發現一個新問題。下面是這條路的順序紀錄。
## 起點 v0.6:能翻幾個字的樣子
使用者給的初版就能跑,但:
- 詞庫只有十幾條,多數頁面還是英文
-`setInterval(500ms)` 輪詢檢查 DOM 變動 — 永遠在掃,浪費 CPU
- 每個詞建一個獨立 regex對同一段文字跑 N 次
- SPA 切頁常常翻不到Turbo 不會重新觸發 `DOMContentLoaded`
## v0.7:整理地基
先不加詞,把架構改對:
- 把所有詞合成一個 `WORD_BOUNDARY_REGEX`,長詞排前面(這個設計之後救了很多次半翻)
- 拿掉 `setInterval`,改 `MutationObserver` + `debounce(150ms)`
- Hook `history.pushState` / `replaceState`、聽 `turbo:load` — SPA 切頁能補翻
## v0.8regex 的第一個坑
使用者貼了側邊欄的 HTML`<strong>62.8k</strong> stars`
重載後發現 `stars` / `watching` / `forks` 都沒翻。
原因一點都不 glamorous — regex 旗標是 `g` 不是 `gi`。詞庫裡寫 `Stars` 大寫,遇到小寫 `stars` 就不配對。
改成 `gi` + 建 `LOWER_MAP` 查表做大小寫不敏感匹配。
同時發現 `Security and quality` 被翻成 `安全 and quality` — 因為只有 `Security` 在詞庫。
解法:整句丟進詞庫。配合「長詞優先」的排序,`Security and quality` 會先被吃掉,不會退化到只匹配 `Security`
**這個 pattern 之後反覆用**:看到半翻就加整句。
## v0.9:把數字弄漂亮
使用者想把 `62.8k` 改成更好看的樣子。
標準做法是 `X 萬`(台灣習慣):
```
62.8k → 6.3 萬
7.8k → 7,800 (< 1 萬就千分號)
1.2M → 120 萬
```
寫了 `formatAbbrev`,加一條 `ABBREV_REGEX = /\b(\d+(?:\.\d+)?)([kKmM])\b/g`
實作發現沒被吃到 — 因為 `62.8k` 包在 `<strong>` 裡,而 `<strong>` 不在 SCAN_SELECTORS 裡。
補了 `strong, b, em, h1-h4, li`。**但這是治標**(之後 v0.12 會把整個 SCAN 白名單砍掉)。
## v0.10Counter 的抉擇
按鈕上的 `7.8k` / `62.8k` 還是沒格式化。
追到原因:這些數字在 `<span class="Counter">` 裡,而 `Counter` 在我的 skip 清單。
當初加進 skip 是怕翻到純數字的計數 badge例如 `Issues 5`)。
但仔細想 — regex 只匹配有 k/M 後綴的數字,純 `5` 本來就不會被動。
所以 Counter 根本不需要 skip。拿掉。
> 教訓skip 規則是雙刃,加之前多想一步「它真的會被誤翻嗎」。
## v0.11Branches 頁的一片荒蕪
使用者貼了 `/branches` 的截圖Default、Active、Stale、All、Updated、Behind、Ahead 全沒翻還看到「Active 分支清單」這種慘劇(`Branches` 被翻了但 `Active branches` 沒整句)。
- 補了一堆詞
-`Branches` 的翻譯從 `分支清單` 改成 `分支` — 中文無複數差異,統一成單字更乾淨
- 加整句 `Active branches``活躍分支`
然後處理相對時間,寫了 `TIME_AGO_REGEX`
```js
/\b(\d+)\s+(second|minute|hour|day|week|month|year)s?\s+ago\b/gi
```
`3 days ago``3 天前`。順手加了 `yesterday` / `last month` 等短語。
## v0.12:白名單策略徹底失敗
下一張截圖又是 Branches 頁:`Branch`(表頭)、`Updated``Check status``Pull request``last month`(在 `<relative-time>` 裡)、`Search branches...`placeholder全都沒翻。
檢視原因 — 這些文字在 `<th>``<td>``<relative-time>``<input>` 等標籤裡,而我的 `SCAN_SELECTORS` 是寫死的白名單:`span, a, button, summary, strong, b, em, h1-h4, li`
每次 GitHub 改用新標籤我就要補 — 這條路走不下去。
**大改**
- 拿掉 SCAN_SELECTORS`TreeWalker(NodeFilter.SHOW_TEXT)` 掃所有文字節點
- `shouldSkipTextAncestor` 走 parent chain祖先命中 SKIP_TAGS 就跳
- 把屬性翻譯從文字節點跳過邏輯解耦 — `<input>` 文字不翻,但 `placeholder`
這次改動是全文重構最大的一次。之後再也沒為了「某個標籤翻不到」而補白名單。
## v0.13`of` 的誘惑
補 Branches 空狀態(`No branches match the search`)整句翻譯。
差點加 `of``/共`寫到一半停下來想「Member of organization」「one of many」「out of date」這些都會爆。
刪掉。`Showing` / `Page` 也一樣,太通用的詞只會製造誤傷。
> 原則:只有在能保證不誤傷的情況下才加單字;有歧義的留給整句處理。
## v0.14:使用者內容入侵
使用者翻進 repo 主頁,傻眼:
```
授權 ← LICENSE
自述檔.md ← README.md
更新.sh ← update.sh
Initial 提交 ← Initial commit
全部 IN ONE Hacking Tool For Hackers ← All IN ONE...
```
檔名、commit 訊息、repo 描述**都是使用者自己產生的內容**,一個字都不該動。
但這些內容在 DOM 上沒有 `<code>` 包起來(那是我 skip 的 tag跟 UI 的 `<a>` 長得一樣。
新增一條 `SKIP_CONTEXT_SELECTOR`,用 `el.closest()` 判斷祖先脈絡:
- `.react-directory-filename-column``a[href*="/blob/"]``a[href*="/tree/"]` — 檔名
- `a[href*="/commit/"]``[data-testid="latest-commit-details"]` — commit 訊息
- `.BorderGrid-cell > p``.f4.my-3``[itemprop="about"]` — repo 描述
- `a.topic-tag` — topic 標籤
- `.markdown-body` — 渲染後的 README
- `.blob-code-inner``.highlight``.react-code-lines` — 程式碼檢視
- `.markdown-title``.js-issue-title` — Issue / PR 標題
這是整份腳本**最重要的一次設計決策**:從「列出要翻什麼」轉向「列出不要翻什麼」。
GitHub UI 裡能翻的文字變動慢,使用者內容的容器類型變動也慢,反而中間那層 React 組件 class 變動最快 — 避開最穩的兩端。
## v0.15Issues 頁的三重災難
/issues 頁一次暴露三類問題:
**1. 搜尋語法被翻,破壞功能**
`is:issue state:開啟中``Open` tab 的翻譯污染了 `state:open` 這個過濾器語法。按下搜尋直接拋錯。
`[role="searchbox"]``[role="combobox"]``.pl-c1`syntax highlight token class等到 skip context。
**2. 使用者名稱被翻**
`karanraza608-星標` — 使用者 username 本來是 `karanraza608-stars`,結尾 `stars` 被詞庫吃掉。
`a[data-hovercard-type="user"]``a[data-hovercard-url*="/users/"]`
**這個 selector 的選擇很關鍵** — GitHub 對任何會顯示使用者浮動卡的連結都會放 `data-hovercard-*`,穩定度高。
**3. 下拉選單半翻**
`排序 by` / `Last 更新時間` — 又是經典半翻。
`Sort by``Last updated` 整句丟進去。
另外在考慮要不要加 `opened` / `closed` 這些動詞(`#654 opened 2 days ago`)時踩到一個限制 — `Closed` 是 tab 名稱翻成「已關閉」,而 `closed` 當動詞應該是「關閉於」。由於我的 regex 大小寫不敏感,同字只能有一種翻譯。只好不翻動詞。
## 累積下來的幾個心得
1. **白名單會吃屎**。規則導向的東西用黑名單skip比白名單scan更好維護。
2. **使用者內容 vs. UI 的邊界靠語意屬性**`role` / `data-testid` / `data-hovercard-*` / `href` pattern 比 class 穩。
3. **regex 的 `\b` 碰到標點會失敗**。詞庫別放結尾帶 `...``?` 的 key。
4. **半翻用整句修**。別妄想加個連接詞解決 — 直接加整句,讓長詞優先吃掉。
5. **大小寫不敏感有代價**。同字不同語境只能二選一,選使用頻率高的那個。
6. **一次只解一類問題**。每次使用者丟截圖就只修該截圖上的問題,不提前設計沒確認的需求。
## 接下來可能的方向
- Dark / light 主題感知的時間格式(目前沒差別)
- 讓使用者在擴充設定介面覆寫詞庫,不用改腳本原始碼
-`TRANSLATION_MAP` 拆成獨立 JSON腳本動態載入
- GitHub Enterprise 的 `@match` 範本
但這些不急。現版已經在實際瀏覽可接受。