orca-zh-tw-installer 1.0.9 → 1.1.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +44 -10
- package/index.js +149 -75
- package/orca_zh_TW_translation.json +1223 -1223
- package/package.json +10 -2
- package/scripts/build-nested.js +45 -0
- package/scripts/sweep-spacing.js +59 -0
- package/scripts/sweep-terms.js +83 -0
- package/scripts/verify-install.js +91 -0
- package/zh-TW-nested.cjs.js +13697 -0
- package/zh-TW-nested.js +1223 -1223
package/README.md
CHANGED
|
@@ -30,8 +30,11 @@
|
|
|
30
30
|
|------|------|
|
|
31
31
|
| **一鍵跨平臺安裝** | 透過 `npx` 自動偵測作業系統並完成替換 |
|
|
32
32
|
| **自動破解白名單** | 突破官方限制,將 `zh-TW` 寫入前端與核心白名單 |
|
|
33
|
-
|
|
|
34
|
-
|
|
|
33
|
+
| **解除繁中強制降級** | 官方新版會把 `zh-TW`/`zh-HK`/`zh-Hant` 明確打回英文,本包一併解除 |
|
|
34
|
+
| **原生選單也中文化** | 除了介面,系統匣、原生選單、系統對話框(main process)同樣套用繁中 |
|
|
35
|
+
| **專業工程術語** | 對照 VS Code 官方 zh-TW 語系包精修,術語一詞一譯(如 存放庫、終端機、Worktree)|
|
|
36
|
+
| **安裝後自動驗證** | 重新封裝前檢查全部注入點,任一失敗即中止且不改動 `app.asar`,不會「安裝成功卻沒效果」|
|
|
37
|
+
| **無痛備份機制** | 首次安裝自動備份 `app.asar.bak`;重複執行會偵測既有補丁,不會用已修補版覆蓋乾淨備份 |
|
|
35
38
|
| **自動追蹤更新** | npm 發布機制確保未來套用更新時始終取得最新版本 |
|
|
36
39
|
|
|
37
40
|
---
|
|
@@ -45,11 +48,24 @@ npx orca-zh-tw
|
|
|
45
48
|
```
|
|
46
49
|
|
|
47
50
|
腳本將自動執行以下流程:
|
|
48
|
-
1. 自動定位作業系統對應的 Orca
|
|
49
|
-
2. 備份官方 `app.asar
|
|
50
|
-
3.
|
|
51
|
-
4.
|
|
52
|
-
5.
|
|
51
|
+
1. 自動定位作業系統對應的 Orca 安裝路徑並解包。
|
|
52
|
+
2. 備份官方 `app.asar`(已含補丁時會保留原本的乾淨備份)。
|
|
53
|
+
3. 破解主程式(Main)語言限制與 locale 解析。
|
|
54
|
+
4. 破解渲染器(Renderer)語言限制、下拉選單與 locale 解析。
|
|
55
|
+
5. 注入 11,000 句繁體中文字典(ESM 給 Renderer、CJS 給 Main)。
|
|
56
|
+
6. 驗證全部注入點後重新封裝。
|
|
57
|
+
|
|
58
|
+
若想在不改動 Orca 的前提下先確認相容性(**可在 Orca 執行中安全使用**):
|
|
59
|
+
|
|
60
|
+
```bash
|
|
61
|
+
npm run dry-run
|
|
62
|
+
```
|
|
63
|
+
|
|
64
|
+
安裝後可驗證實際封裝結果:
|
|
65
|
+
|
|
66
|
+
```bash
|
|
67
|
+
npm run verify
|
|
68
|
+
```
|
|
53
69
|
|
|
54
70
|
---
|
|
55
71
|
|
|
@@ -89,9 +105,27 @@ npm start
|
|
|
89
105
|
|
|
90
106
|
### 檔案結構
|
|
91
107
|
|
|
92
|
-
- `index.js
|
|
93
|
-
- `
|
|
94
|
-
- `
|
|
108
|
+
- `index.js`:核心安裝腳本,處理 asar 解包、修補、驗證與封裝。支援 `--dry-run`。
|
|
109
|
+
- `orca_zh_TW_translation.json`:**唯一的翻譯來源**(扁平點分鍵)。改翻譯只改這個檔。
|
|
110
|
+
- `zh-TW-nested.js`:由來源檔產生的 ESM 字典,供 Renderer 載入。**請勿手動編輯。**
|
|
111
|
+
- `zh-TW-nested.cjs.js`:由來源檔產生的 CJS 字典,供 Main process 載入。**請勿手動編輯。**
|
|
112
|
+
- `scripts/build-nested.js`:由 JSON 產生上述兩個字典檔。
|
|
113
|
+
- `scripts/sweep-terms.js`:依術語鎖定表統一用詞,預設 dry-run。
|
|
114
|
+
- `scripts/sweep-spacing.js`:中英之間補半形空格,預設 dry-run。
|
|
115
|
+
- `scripts/verify-install.js`:驗證已安裝的 `app.asar` 是否含全部補丁與字典。
|
|
116
|
+
|
|
117
|
+
### 改翻譯的流程
|
|
118
|
+
|
|
119
|
+
`zh-TW-nested.js` 與 `zh-TW-nested.cjs.js` 都是**產生物**,編輯 JSON 後必須重新建置,
|
|
120
|
+
否則安裝時不會生效:
|
|
121
|
+
|
|
122
|
+
```bash
|
|
123
|
+
# 1. 編輯 orca_zh_TW_translation.json
|
|
124
|
+
# 2. 重新產生字典檔(兩種格式)
|
|
125
|
+
npm run build
|
|
126
|
+
# 3. 套用
|
|
127
|
+
npm start
|
|
128
|
+
```
|
|
95
129
|
|
|
96
130
|
---
|
|
97
131
|
|
package/index.js
CHANGED
|
@@ -24,109 +24,183 @@ if (!fs.existsSync(orcaPath)) {
|
|
|
24
24
|
}
|
|
25
25
|
|
|
26
26
|
const asar = require('@electron/asar');
|
|
27
|
-
|
|
27
|
+
// --dry-run:解包、修補、驗證,但不備份也不重新打包。
|
|
28
|
+
// 可在 Orca 執行中安全使用,用來確認 Orca 更新後錨點是否仍然有效。
|
|
29
|
+
const DRY_RUN = process.argv.includes('--dry-run');
|
|
30
|
+
const workDir = path.join(os.tmpdir(), DRY_RUN ? 'orca-zh-tw-patcher-dry' : 'orca-zh-tw-patcher');
|
|
28
31
|
const unpackedDir = path.join(workDir, 'app.asar.unpacked');
|
|
29
32
|
|
|
33
|
+
const MARK = 'UI_LANGUAGE_TRADITIONAL_CHINESE = "zh-TW"';
|
|
34
|
+
|
|
35
|
+
/**
|
|
36
|
+
* 修補器:每個修補都要宣告「如何判斷已完成」。
|
|
37
|
+
* 若錨點找不到且尚未完成,就記為失敗——絕不靜默略過。
|
|
38
|
+
*/
|
|
39
|
+
function createPatcher(label, filePath) {
|
|
40
|
+
let code = fs.readFileSync(filePath, 'utf8');
|
|
41
|
+
const ok = [];
|
|
42
|
+
const failed = [];
|
|
43
|
+
return {
|
|
44
|
+
label,
|
|
45
|
+
/**
|
|
46
|
+
* @param name 修補名稱(顯示用)
|
|
47
|
+
* @param done (code) => boolean,判斷是否已修補過
|
|
48
|
+
* @param find string | RegExp 錨點
|
|
49
|
+
* @param repl 取代內容
|
|
50
|
+
*/
|
|
51
|
+
patch(name, done, find, repl) {
|
|
52
|
+
if (done(code)) { ok.push(`${name}(已存在)`); return; }
|
|
53
|
+
const before = code;
|
|
54
|
+
code = code.replace(find, repl);
|
|
55
|
+
if (code === before || !done(code)) failed.push(name);
|
|
56
|
+
else ok.push(name);
|
|
57
|
+
},
|
|
58
|
+
save() { fs.writeFileSync(filePath, code); },
|
|
59
|
+
get ok() { return ok; },
|
|
60
|
+
get failed() { return failed; },
|
|
61
|
+
};
|
|
62
|
+
}
|
|
63
|
+
|
|
64
|
+
// ── 共用修補:三處新版 Orca 才有的 zh-TW 阻擋點 ──────────────────────────
|
|
65
|
+
// A. locale 白名單沒有 zh-TW
|
|
66
|
+
// B. normalizeSupportedUiLocale() 明確把 zh-tw / zh-hk / zh-hant 打回 DEFAULT_UI_LOCALE("en")
|
|
67
|
+
// C. resolveUiLocale() 沒有 zh-TW 分支,選了繁中會掉出 if 鏈變成 "en"
|
|
68
|
+
function patchLocaleGate(p) {
|
|
69
|
+
p.patch('locale 白名單加入 zh-TW',
|
|
70
|
+
c => c.includes('const SUPPORTED_UI_LOCALES = ["en", "zh", "zh-TW", "ko", "ja", "es"]'),
|
|
71
|
+
'const SUPPORTED_UI_LOCALES = ["en", "zh", "ko", "ja", "es"];',
|
|
72
|
+
'const SUPPORTED_UI_LOCALES = ["en", "zh", "zh-TW", "ko", "ja", "es"];');
|
|
73
|
+
|
|
74
|
+
p.patch('解除 zh-tw/zh-hk/zh-hant 退回英文',
|
|
75
|
+
c => /tag\.startsWith\("zh-hant"\)\)\s*\{\s*return "zh-TW";/.test(c),
|
|
76
|
+
/(if \(tag\.startsWith\("zh-tw"\) \|\| tag\.startsWith\("zh-hk"\) \|\| tag\.startsWith\("zh-hant"\)\) \{\s*)return DEFAULT_UI_LOCALE;/,
|
|
77
|
+
'$1return "zh-TW";');
|
|
78
|
+
|
|
79
|
+
p.patch('resolveUiLocale 加入 zh-TW 分支',
|
|
80
|
+
c => c.includes('if (language === "zh-TW") {'),
|
|
81
|
+
/(if \(language === UI_LANGUAGE_CHINESE\) \{\s*return "zh";\s*\})/,
|
|
82
|
+
'$1\n if (language === "zh-TW") {\n return "zh-TW";\n }');
|
|
83
|
+
}
|
|
84
|
+
|
|
30
85
|
async function patch() {
|
|
31
86
|
try {
|
|
32
|
-
console.log('📦 1/
|
|
33
|
-
fs.
|
|
34
|
-
|
|
35
|
-
if (fs.existsSync(workDir)) {
|
|
36
|
-
fs.rmSync(workDir, { recursive: true, force: true });
|
|
37
|
-
}
|
|
87
|
+
console.log('📦 1/6 正在解包 app.asar (這可能需要數十秒)...');
|
|
88
|
+
if (fs.existsSync(workDir)) fs.rmSync(workDir, { recursive: true, force: true });
|
|
38
89
|
fs.mkdirSync(workDir, { recursive: true });
|
|
39
|
-
|
|
40
90
|
asar.extractAll(orcaPath, unpackedDir);
|
|
41
91
|
|
|
42
|
-
console.log('🛠️ 2/5 正在破解主程式 (main process) 語言限制...');
|
|
43
92
|
const mainFile = path.join(unpackedDir, 'out', 'main', 'index.js');
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
// 注入常數
|
|
47
|
-
if (!mainCode.includes('UI_LANGUAGE_TRADITIONAL_CHINESE = "zh-TW"')) {
|
|
48
|
-
mainCode = mainCode.replace(
|
|
49
|
-
'const UI_LANGUAGE_CHINESE = "zh";',
|
|
50
|
-
'const UI_LANGUAGE_CHINESE = "zh";\nconst UI_LANGUAGE_TRADITIONAL_CHINESE = "zh-TW";'
|
|
51
|
-
);
|
|
93
|
+
if (!fs.existsSync(mainFile)) {
|
|
94
|
+
throw new Error('找不到 out/main/index.js,Orca 可能已大幅更改架構!');
|
|
52
95
|
}
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
|
|
96
|
+
|
|
97
|
+
// 只在「目前的 asar 尚未被修補」時更新備份,
|
|
98
|
+
// 否則重複執行會用已修補的版本覆蓋掉乾淨備份。
|
|
99
|
+
const alreadyPatched = fs.readFileSync(mainFile, 'utf8').includes(MARK);
|
|
100
|
+
const bakPath = orcaPath + '.bak';
|
|
101
|
+
if (DRY_RUN) {
|
|
102
|
+
console.log(` ↳ [dry-run] 跳過備份。目前 app.asar ${alreadyPatched ? '已含' : '未含'}補丁。`);
|
|
103
|
+
} else if (alreadyPatched && fs.existsSync(bakPath)) {
|
|
104
|
+
console.log(' ↳ 偵測到目前 app.asar 已含補丁,保留既有的乾淨備份。');
|
|
105
|
+
} else {
|
|
106
|
+
fs.copyFileSync(orcaPath, bakPath);
|
|
107
|
+
console.log(' ↳ 已備份原始 app.asar → app.asar.bak');
|
|
59
108
|
}
|
|
60
|
-
fs.writeFileSync(mainFile, mainCode);
|
|
61
109
|
|
|
62
|
-
console.log('🛠️
|
|
110
|
+
console.log('🛠️ 2/6 正在修補主程式 (main process) 語言限制...');
|
|
111
|
+
const mp = createPatcher('main process', mainFile);
|
|
112
|
+
mp.patch('注入 zh-TW 常數',
|
|
113
|
+
c => c.includes(MARK),
|
|
114
|
+
'const UI_LANGUAGE_CHINESE = "zh";',
|
|
115
|
+
'const UI_LANGUAGE_CHINESE = "zh";\nconst UI_LANGUAGE_TRADITIONAL_CHINESE = "zh-TW";');
|
|
116
|
+
mp.patch('加入語言列舉白名單',
|
|
117
|
+
c => c.includes('UI_LANGUAGE_TRADITIONAL_CHINESE,'),
|
|
118
|
+
'UI_LANGUAGE_CHINESE,\n UI_LANGUAGE_KOREAN',
|
|
119
|
+
'UI_LANGUAGE_CHINESE,\n UI_LANGUAGE_TRADITIONAL_CHINESE,\n UI_LANGUAGE_KOREAN');
|
|
120
|
+
patchLocaleGate(mp);
|
|
121
|
+
// main process 有自己的 LAZY_LOCALE_LOADERS(CJS require),需另外注入
|
|
122
|
+
mp.patch('注入 main process 字典 loader',
|
|
123
|
+
c => c.includes('"zh-TW": () => Promise.resolve()'),
|
|
124
|
+
/(zh: \(\) => Promise\.resolve\(\)\.then\(\(\) => require\("\.\/chunks\/zh-[A-Za-z0-9_-]+\.js"\)\))/,
|
|
125
|
+
'$1,\n "zh-TW": () => Promise.resolve().then(() => require("./chunks/zh-TW-nested.js"))');
|
|
126
|
+
mp.save();
|
|
127
|
+
|
|
128
|
+
console.log('🛠️ 3/6 正在修補渲染器 (renderer process) 語言限制...');
|
|
63
129
|
const assetsDir = path.join(unpackedDir, 'out', 'renderer', 'assets');
|
|
64
|
-
const
|
|
65
|
-
|
|
66
|
-
|
|
130
|
+
const i18nFile = fs.readdirSync(assetsDir)
|
|
131
|
+
.find(f => f.startsWith('I18nProvider-') && f.endsWith('.js'));
|
|
67
132
|
if (!i18nFile) {
|
|
68
|
-
throw new Error('找不到 I18nProvider 檔案,Orca
|
|
69
|
-
}
|
|
70
|
-
|
|
71
|
-
const rendererPath = path.join(assetsDir, i18nFile);
|
|
72
|
-
let rendererCode = fs.readFileSync(rendererPath, 'utf8');
|
|
73
|
-
|
|
74
|
-
// 加入常數
|
|
75
|
-
if (!rendererCode.includes('UI_LANGUAGE_TRADITIONAL_CHINESE = "zh-TW"')) {
|
|
76
|
-
rendererCode = rendererCode.replace(
|
|
77
|
-
'const UI_LANGUAGE_CHINESE = "zh";',
|
|
78
|
-
'const UI_LANGUAGE_CHINESE = "zh";\nconst UI_LANGUAGE_TRADITIONAL_CHINESE = "zh-TW";'
|
|
79
|
-
);
|
|
80
|
-
}
|
|
81
|
-
// 加入下拉選單
|
|
82
|
-
if (!rendererCode.includes('labelKey: "settings.appearance.language.traditionalChinese"')) {
|
|
83
|
-
rendererCode = rendererCode.replace(
|
|
84
|
-
'{ value: UI_LANGUAGE_CHINESE, labelKey: "settings.appearance.language.chinese" },',
|
|
85
|
-
'{ value: UI_LANGUAGE_CHINESE, labelKey: "settings.appearance.language.chinese" },\n { value: UI_LANGUAGE_TRADITIONAL_CHINESE, labelKey: "settings.appearance.language.traditionalChinese" },'
|
|
86
|
-
);
|
|
133
|
+
throw new Error('找不到 I18nProvider 檔案,Orca 可能已大幅更改架構!');
|
|
87
134
|
}
|
|
88
|
-
|
|
89
|
-
|
|
90
|
-
|
|
91
|
-
|
|
92
|
-
|
|
93
|
-
);
|
|
135
|
+
const rendererFile = path.join(assetsDir, i18nFile);
|
|
136
|
+
const rp = createPatcher('renderer', rendererFile);
|
|
137
|
+
rp.patch('注入 zh-TW 常數',
|
|
138
|
+
c => c.includes(MARK),
|
|
139
|
+
'const UI_LANGUAGE_CHINESE = "zh";',
|
|
140
|
+
'const UI_LANGUAGE_CHINESE = "zh";\nconst UI_LANGUAGE_TRADITIONAL_CHINESE = "zh-TW";');
|
|
141
|
+
rp.patch('加入語言列舉白名單',
|
|
142
|
+
c => c.includes('UI_LANGUAGE_TRADITIONAL_CHINESE,'),
|
|
143
|
+
'UI_LANGUAGE_CHINESE,\n UI_LANGUAGE_KOREAN',
|
|
144
|
+
'UI_LANGUAGE_CHINESE,\n UI_LANGUAGE_TRADITIONAL_CHINESE,\n UI_LANGUAGE_KOREAN');
|
|
145
|
+
rp.patch('加入語言下拉選單項目',
|
|
146
|
+
c => c.includes('labelKey: "settings.appearance.language.traditionalChinese"'),
|
|
147
|
+
'{ value: UI_LANGUAGE_CHINESE, labelKey: "settings.appearance.language.chinese" },',
|
|
148
|
+
'{ value: UI_LANGUAGE_CHINESE, labelKey: "settings.appearance.language.chinese" },\n { value: UI_LANGUAGE_TRADITIONAL_CHINESE, labelKey: "settings.appearance.language.traditionalChinese" },');
|
|
149
|
+
rp.patch('加入語言名稱 fallback',
|
|
150
|
+
c => c.includes('[UI_LANGUAGE_TRADITIONAL_CHINESE]:'),
|
|
151
|
+
'[UI_LANGUAGE_CHINESE]: "中文(简体)",',
|
|
152
|
+
'[UI_LANGUAGE_CHINESE]: "中文(简体)",\n [UI_LANGUAGE_TRADITIONAL_CHINESE]: "中文(繁體)",');
|
|
153
|
+
rp.patch('注入 renderer 字典 loader',
|
|
154
|
+
c => c.includes('"zh-TW": () => __vitePreload'),
|
|
155
|
+
/ko: \(\) => __vitePreload\(\(\) => import\("\.\/ko-[a-zA-Z0-9_-]+\.js"\), [^,]+, import\.meta\.url\),/,
|
|
156
|
+
'$& \n "zh-TW": () => __vitePreload(() => import("./zh-TW-nested.js"), true ? [] : void 0, import.meta.url),');
|
|
157
|
+
patchLocaleGate(rp);
|
|
158
|
+
rp.save();
|
|
159
|
+
|
|
160
|
+
console.log('📂 4/6 正在植入繁體中文字典 (ESM + CJS 兩種格式)...');
|
|
161
|
+
const esmDict = path.join(__dirname, 'zh-TW-nested.js');
|
|
162
|
+
const cjsDict = path.join(__dirname, 'zh-TW-nested.cjs.js');
|
|
163
|
+
for (const f of [esmDict, cjsDict]) {
|
|
164
|
+
if (!fs.existsSync(f)) throw new Error(`找不到字典檔:${f}(請先執行 npm run build)`);
|
|
94
165
|
}
|
|
95
|
-
|
|
96
|
-
|
|
97
|
-
|
|
98
|
-
|
|
99
|
-
|
|
100
|
-
|
|
166
|
+
fs.copyFileSync(esmDict, path.join(assetsDir, 'zh-TW-nested.js'));
|
|
167
|
+
const chunksDir = path.join(unpackedDir, 'out', 'main', 'chunks');
|
|
168
|
+
fs.copyFileSync(cjsDict, path.join(chunksDir, 'zh-TW-nested.js'));
|
|
169
|
+
|
|
170
|
+
console.log('🔎 5/6 正在驗證所有注入點...');
|
|
171
|
+
const allFailed = [
|
|
172
|
+
...mp.failed.map(n => `[main] ${n}`),
|
|
173
|
+
...rp.failed.map(n => `[renderer] ${n}`),
|
|
174
|
+
];
|
|
175
|
+
for (const p of [mp, rp]) {
|
|
176
|
+
for (const n of p.ok) console.log(` ✅ [${p.label}] ${n}`);
|
|
101
177
|
}
|
|
102
|
-
|
|
103
|
-
|
|
104
|
-
|
|
105
|
-
|
|
106
|
-
|
|
178
|
+
if (allFailed.length) {
|
|
179
|
+
for (const n of allFailed) console.error(` ❌ ${n}`);
|
|
180
|
+
throw new Error(
|
|
181
|
+
`有 ${allFailed.length} 個注入點失敗,已中止且未改動你的 Orca。\n` +
|
|
182
|
+
' 這通常表示 Orca 更新後改了程式碼結構,需要更新本安裝包的錨點。'
|
|
107
183
|
);
|
|
108
184
|
}
|
|
109
|
-
fs.writeFileSync(rendererPath, rendererCode);
|
|
110
185
|
|
|
111
|
-
|
|
112
|
-
|
|
113
|
-
|
|
114
|
-
|
|
186
|
+
if (DRY_RUN) {
|
|
187
|
+
console.log('\n✅ [dry-run] 全部注入點驗證通過,未改動你的 Orca。');
|
|
188
|
+
console.log(' 關閉 Orca 後執行 npm start 即可正式套用。\n');
|
|
189
|
+
fs.rmSync(workDir, { recursive: true, force: true });
|
|
190
|
+
return;
|
|
115
191
|
}
|
|
116
|
-
fs.copyFileSync(dictSource, path.join(assetsDir, 'zh-TW-nested.js'));
|
|
117
192
|
|
|
118
|
-
console.log('🗜️
|
|
193
|
+
console.log('🗜️ 6/6 正在重新打包 app.asar (這可能需要數十秒)...');
|
|
119
194
|
await asar.createPackage(unpackedDir, orcaPath);
|
|
120
|
-
|
|
121
|
-
// 清理
|
|
122
195
|
fs.rmSync(workDir, { recursive: true, force: true });
|
|
123
196
|
|
|
124
|
-
console.log('\n🎉 安裝成功!請完全關閉 Orca (
|
|
197
|
+
console.log('\n🎉 安裝成功!請完全關閉 Orca (系統匣右鍵 Quit) 並重新啟動!');
|
|
125
198
|
console.log('然後前往 Settings -> Appearance -> Language 切換為「中文(繁體)」。\n');
|
|
126
199
|
|
|
127
200
|
} catch (err) {
|
|
128
201
|
console.error('\n❌ 安裝過程中發生錯誤:', err.message);
|
|
129
|
-
console.error(
|
|
202
|
+
console.error(`已保留原始備份:${orcaPath}.bak`);
|
|
203
|
+
process.exitCode = 1;
|
|
130
204
|
}
|
|
131
205
|
}
|
|
132
206
|
|