docs: 新增 README / QUICKSTART / SUMMARY 三份文件

三份文件定位不重疊:
- README.md:參考手冊(功能、架構、規則對照表、相容性)
- QUICKSTART.md:操作指南(安裝、自訂翻譯、自訂跳過、FAQ)
- SUMMARY.md:開發故事線(v0.6→v0.15 順序敘事、設計心得)
This commit is contained in:
2026-04-22 17:12:33 +08:00
parent 9ea401ac1d
commit 5d48265bb2
3 changed files with 439 additions and 0 deletions

139
QUICKSTART.md Normal file
View File

@@ -0,0 +1,139 @@
# 快速上手
## 1. 安裝瀏覽器擴充套件
三選一皆可:
- [ScriptCat](https://scriptcat.org/)(推薦,開源)
- [Tampermonkey](https://www.tampermonkey.net/)
- [Violentmonkey](https://violentmonkey.github.io/)
## 2. 安裝腳本
### 方法 A直接貼
1. 開啟 ScriptCat / Tampermonkey 管理面板
2. 點「建立新腳本」
3.`github-zh-tw.user.js` 全文貼上
4. 儲存Cmd/Ctrl + S
### 方法 B從網址安裝
把腳本掛到任何 raw 檔案網址GitHub Gist、Gitea raw URL、自架 web server然後進該網址擴充套件會自動跳出安裝視窗。
## 3. 驗證
打開 https://github.com ,側欄應該變成:
```
Code → 原始碼
Issues → 問題
Pull requests → 拉取請求
62.8k stars → 6.3 萬 星標
3 days ago → 3 天前
```
沒作用?看本檔最後 [常見問題](#常見問題)。
---
## 自訂翻譯
### 新增一個詞
打開腳本,找 `TRANSLATION_MAP`
```js
const TRANSLATION_MAP = new Map([
['Code', '原始碼'],
// ...
['My custom term', '我的自訂詞'], // ← 加在這裡
]);
```
存檔後重新載入 GitHub 頁面即可。
### 長詞優先規則
若你加了 `Open pull request`,就算已經有 `Open``開啟中`,長詞會先被匹配。所以遇到半翻情況(像 `開啟 pull request`),直接加整句進去。
### 多種譯法選擇
同一個英文詞在不同語境可能想要不同翻譯(例如 `Open` 當 tab vs. 動詞)。**做不到**—regex 是大小寫不敏感的。
取捨原則:保留使用頻率高、不翻會更奇怪的那個意思。
---
## 自訂「不翻」範圍
### 不想翻某個 class / 元素
打開腳本找 `SKIP_CONTEXT_SELECTOR`,加一行:
```js
const SKIP_CONTEXT_SELECTOR = [
// ...
'.my-custom-dont-translate', // ← 加這
'[data-no-translate]', // 或用屬性
].join(',');
```
該元素與所有子孫的文字節點都會被跳過,但**它的屬性(如 `aria-label`)仍會翻**;若連屬性都不想翻,請從 `ATTR_SELECTOR` 那邊獨立處理(罕見)。
### 不想翻整個網域 / 路徑
改腳本開頭的 `@match`
```js
// @match https://github.com/*
// @match https://github.com/yourorg/* ← 只翻這個 org
// @exclude https://github.com/settings/* ← 排除設定頁
```
---
## 更新
從來源倉庫複製最新 `github-zh-tw.user.js` 全文,貼回 ScriptCat / Tampermonkey 的編輯器覆蓋儲存。不需要重裝擴充套件。
---
## 常見問題
### Q1安裝後沒變化
- 確認 ScriptCat / Tampermonkey 擴充套件是**開啟**狀態
- 確認腳本在 `https://github.com/*` 頁面顯示為「已執行」
- 重新載入頁面Cmd/Ctrl + Shift + R 強制重載)
### Q2某些詞還是英文
- 可能是剛加入 GitHub 的新 UI詞庫還沒收錄 → 依上方「新增一個詞」加入
- 可能是文字在被跳過的區塊README、程式碼、Issue 標題…)→ 這是刻意的
### Q3搜尋功能壞了
原因:`state:open` 這類過濾語法被翻。這不該發生 — 若發生,檢查是否有第三方腳本干擾,或 GitHub 改版了搜尋元件的 class。
暫時停用腳本可恢復。
### Q4檔名被翻成中文
原因GitHub 改版導致檔案列表 class 變動,跳過規則失效。
修法:檢查該檔名元素的 DevTools找穩定的 class / 屬性加進 `SKIP_CONTEXT_SELECTOR`
### Q5效能變慢、Chrome 高 CPU
- 先停用本腳本看是否為本腳本造成
- 若是,檢查自己是否改過腳本讓 debounce 失效 / MutationObserver 範圍過大
- 保持預設 `150ms` debounce 通常不會有感卡頓
### Q6看到「Active 分支清單」這種半翻
詞庫中 `Branches` 被單獨翻了卻沒有整句版本。加整句進 `TRANSLATION_MAP` 即可(長詞會優先匹配)。
### Q7自己伺服器上的 Gitea / GitHub Enterprise 也想用
`@match` 指向你的網域。注意 GHES 的 class 可能與 github.com 有差異,部分跳過規則需要調整。
---
## 回報 / 建議
看到沒翻或誤翻的地方,最快回報方式:
1. 截圖有問題的頁面
2. DevTools 右鍵檢查,把該元素的 HTML 片段一起附上
3. 提 Issue 或直接在詞庫 PR

137
README.md Normal file
View File

@@ -0,0 +1,137 @@
# GitHub 中文化 (繁體)
一份 ScriptCat / Tampermonkey 用的 UserScript把 GitHub 介面轉成台灣用語的繁體中文不動使用者內容檔名、commit 訊息、Issue 標題、程式碼、README
> 想快速裝起來用?看 [`QUICKSTART.md`](./QUICKSTART.md)
> 想看一路怎麼做出來的?看 [`SUMMARY.md`](./SUMMARY.md)
## 檔案
| 檔案 | 用途 |
|---|---|
| `github-zh-tw.user.js` | 腳本本體(貼到 ScriptCat / Tampermonkey |
| `README.md` | 參考手冊(本檔)— 功能、架構、規則 |
| `QUICKSTART.md` | 安裝與自訂操作指南 |
| `SUMMARY.md` | 開發故事線、設計取捨 |
## 功能概覽
| 類別 | 例子 |
|---|---|
| 導覽列 | `Code` / `Issues` / `Pull requests` / `Actions` / `Security` / `Insights` |
| 倉庫側欄 | `About` / `Releases` / `Contributors` / `Sponsor this project` |
| Branches 頁 | `Default` / `Active` / `Stale` / `All` / `Behind` / `Ahead` |
| Issue / PR 列表 | `Sort by` / `Newest` / `Oldest` / `No results found` |
| 數字縮寫 | `62.8k``6.3 萬``1.2M``120 萬``7,800` 千分號 |
| 相對時間 | `3 days ago``3 天前``last month``上個月` |
| 頁尾 / 無障礙 | `Terms` / `Privacy` / `Footer navigation` / `GitHub Homepage` |
## 不翻什麼(刻意)
| 內容類別 | 選擇器依據 |
|---|---|
| 檔名、資料夾名 | `.react-directory-filename-column``a[href*="/blob/"]``a[href*="/tree/"]` |
| Commit 訊息 | `a[href*="/commit/"]``[data-testid="latest-commit-details"]` |
| Repo 描述 | `.BorderGrid-cell > p``.f4.my-3``[itemprop="about"]` |
| Topic 標籤 | `a.topic-tag``.topic-tag-link` |
| 使用者 / 組織名 | `a[data-hovercard-type="user"]``a[data-hovercard-url*="/users/"]` |
| README渲染後 | `.markdown-body` |
| 程式碼檢視 | `.blob-code-inner``.highlight``.react-code-lines` |
| Issue / PR 標題 | `.markdown-title``.js-issue-title` |
| 搜尋語法(`is:issue state:open` | `[role="searchbox"]``[role="combobox"]``.pl-c1` 等 |
| Labels | `.IssueLabel``.Label` |
| 編輯器、程式碼輸入 | `<code>``<pre>``<input>``<textarea>``[contenteditable]` |
## 核心架構
### 替換流程(`replaceText` 三層)
```
text
├─ 1. TIME_AGO_REGEX "3 days ago" → "3 天前"
│ /\b(\d+)\s+(second|minute|hour|day|week|month|year)s?\s+ago\b/gi
├─ 2. WORD_BOUNDARY_REGEX 詞庫批次替換
│ 長詞優先、大小寫不敏感
└─ 3. ABBREV_REGEX "62.8k" → "6.3 萬"
/\b(\d+(?:\.\d+)?)([kKmM])\b/g
```
### 跳過規則(`shouldSkipTextAncestor`
```
elements (往祖先走)
├─ SKIP_TAGS SCRIPT / STYLE / CODE / PRE / INPUT / TEXTAREA / NOSCRIPT
├─ isContentEditable (所有可編輯區)
├─ role="textbox"
└─ closest(SKIP_CONTEXT_SELECTOR)
└─ 使用者內容脈絡(見上表)
```
**屬性翻譯獨立於文字節點**
`<input placeholder="Search branches...">` 的 placeholder 會翻,但輸入內容不動。
### 掃描策略
| 對象 | 方法 |
|---|---|
| 所有文字節點 | `TreeWalker(NodeFilter.SHOW_TEXT)`,不靠白名單 |
| 屬性 | `querySelectorAll('[data-content], [aria-label], [title], [placeholder]')` |
### 動態更新
- **`MutationObserver`**:監聽 `childList` + `attributes`,新增節點立即翻、屬性變更立即補。
- **SPA 路由**hook `history.pushState` / `replaceState`,監聽 `popstate``turbo:load``turbo:render`
- **150ms debounce**:保險起見切頁/大量 DOM 變動後做一次全域掃描。
## 詞庫結構
`TRANSLATION_MAP``Map`)按分類排列:
1. 倉庫導覽Code / Issues / PR / Actions…
2. 關注 / 分支 / 星標Watch / Fork / Star
3. 倉庫側欄About / Releases / Contributors…
4. 檔案列表操作Go to file / Add file / Clone…
5. PR / Issue 按鈕New issue / Merge / Review changes…
6. 列表篩選Open / Closed / Author / Labels…
7. 全站導覽 / 使用者選單
8. 通用動作Save / Cancel / Edit…
9. 頁尾 / 無障礙
10. 相對時間短語
**長詞優先**:建構 regex 時會依字串長度排序,讓 `Active branches` 優先於 `Branches` 單字匹配。
## 翻譯規則的限制
### 大小寫不敏感 → 同詞只能一個翻譯
`Open`tab`opened`(動詞)經 `gi` + `LOWER_MAP` 查表會得到同一翻譯。本腳本選「保留 tab 翻譯」,動詞的 `opened` 保持英文。
### 結尾標點不可作為關鍵詞一部分
`Search branches...` 的結尾 `\b` 會失敗(`.` 非字元 + 後面也非字元)。詞庫只放 `Search branches`regex 會在 `\b` 前停下,保留原 `...`
### 使用者內容的邊界判斷
靠穩定的語意屬性(`role` / `data-testid` / `data-hovercard-*` / `href` pattern避開 React CSS 模組的 hash class。
## 數字格式化規則
```
val ≥ 1,000,000 → "X 萬" (≥100 萬無小數 / 否則 1 位小數)
1,000,000 > val ≥ 10,000 → "X.X 萬" (1 位小數,.0 去掉)
10,000 > val → "X,XXX" (千分號)
val 為純整數(無 k/M 後綴) → 不處理
```
## 相容性
| 項目 | 狀態 |
|---|---|
| ScriptCat / Tampermonkey / Violentmonkey | ✅ |
| Chrome / Edge / Firefox / Safari | ✅ |
| GitHub Turbo / React 新 UI | ✅ |
| GitHub Classic UI | ✅(維持運作) |
| GitHub Enterprise | 未測試(`@match` 需調整) |
## 授權
MIT

163
SUMMARY.md Normal file
View File

@@ -0,0 +1,163 @@
# 開發故事:從 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` 範本
但這些不急。現版已經在實際瀏覽可接受。