到昨天為止,手冊已經可以從設定檔一路產出 Word、PDF 與 HTML 了。剩下這幾天就是補充議題,看看這條產線還能怎麼優化。
第一個要處理的是多語言。
公司的產品如果要賣到海外或是賣給國際化的大公司,通常都需要提供不只一種語言的介面,同時,每種語言也都需要一份使用手冊。既然前面已經有中文使用手冊的產線了,也可以把它再延伸,變成是中文、英文都支援的使用手冊產線。
在 Day 02 有提過,要重新思考 4 個問題:
在面對新的使用手冊製作需求時,要思考的問題變成:
- 要重新截圖嗎?
- 截圖規則要調整嗎?
- 文字說明要調整嗎?
- 模板要調整嗎?
這個問題,也可以換個問法:英文版的截圖,能不能沿用中文版的?
答案顯然是「不行」。
畫面上的字本身就是截圖的一部分。英文讀者照著手冊操作時,如果截圖上寫的是「建立」,他在自己的英文介面上根本找不到這顆按鈕。所以截圖一定要跟著語言重拍。
好在這件事在這條產線上並不貴。DemoStreamApp 的語言是從 localStorage 讀的,而 Day 09 的狀態注入本來就會在 App 啟動前寫入 localStorage。換語言,就只是把 locale 從 zh-Hant 換成 en,同一份 manifest 再跑一遍。
不用,操作的流程沒有改變。
要,而且這會是花最多力氣處理的地方。
截圖重拍解決的只是「畫面上的字」。一本手冊裡跟語言有關的東西,其實散在四個地方:
| 內容 | 放在哪 | 怎麼處理 |
|---|---|---|
| 畫面上的字 | App 的 i18n 檔 (en.json) |
App 本來就有,換 locale 重拍 |
| 標號說明、章節標題、示範輸入值 | manifest | 逐語言指定 |
| 正文 | docs/ |
另外一份 docs/en/ |
| 圖號、表頭、封面、目錄標題 | build 自己產生 |
逐語言定義 |
幾乎不用,唯一需要注意的是字體。不同語言有各自適合放在使用手冊的字體,因此會建議重新挑選比較好。(甚至有些字體不是所有語言都有支援)
因此,面對多語言的需求,主要是調整截圖產線與翻譯正文。
先在 manifest/manual.yaml 列出要出哪些語言,第一個是主語言:
title: { zh-Hant: DemoStreamApp 使用手冊, en: DemoStreamApp User Manual }
version: 'v1.2.0'
locales: [zh-Hant, en]
runner 多了一層「語言」的迴圈,每一章開機時,把當下的語言跟其他 bootstrap 狀態一起注入:
await driver.setStorage({ ...storage, locale })
截圖改成一個語言一個資料夾 (screenshots/zh-Hant/、screenshots/en/),檔名不變,兩個語言的圖不會互相覆蓋。
npm run manual # 5 章 × 2 種語言
npm run manual -- --locale en # 只跑英文
實際執行的輸出 (省略中間幾章):
$ npm run manual
manifest: demo-stream-app v1.2.0 共 5 章 × 2 種語言(zh-Hant / en) [electron]
── zh-Hant ──
▶ [1/5] overview 介面總覽
✔ [1/5] overview 3 steps / 1 shot 1.1s
...
zh-Hant 完成,10.0s -> screenshots/zh-Hant/
── en ──
▶ [1/5] overview Interface Overview
✔ [1/5] overview 3 steps / 1 shot 1.1s
...
▶ [5/5] camera-add Adding a Camera
✔ [5/5] camera-add 11 steps / 3 shots 1.4s
en 完成,8.3s -> screenshots/en/
完成 5 章 × 2 種語言,總共 18.3s
換成英文之後,manifest 裡的步驟一行都沒改,全部照樣跑過。因為所有元件都是用 data-testid 找的,跟畫面上的字無關。如果當初是用「建立」這個字去找按鈕,英文版一跑就全部找不到了。

標號說明、章節標題、示範輸入值,這三種欄位寫在 manifest 裡,也要跟著語言變。做法是讓這些欄位可以寫成「語言 → 值」的對照:
title: { zh-Hant: 新增攝影機, en: Adding a Camera }
...
annotate:
- { key: name, testid: camera-dialog-name, legend: { zh-Hant: 顯示名稱, en: Display Name } }
...
- { action: fill, testid: camera-dialog-name, text: { zh-Hant: 大門西側, en: Gate West } }
- { action: fill, testid: camera-dialog-source, text: 'rtsp://192.0.2.10/live' }
只寫字串,就代表每個語言都用同一個值。像 RTSP 位址、搜尋關鍵字 lobby 這種本來就不用翻的東西,維持原樣就好。
示範輸入值也要翻,是因為它會出現在畫面上。英文介面裡的攝影機名稱如果是「大門西側」,截圖跟 toast 都會混進中文,英文讀者看了只會一頭霧水。
另外,缺翻譯時要直接失敗,不要默默退回主語言。退回主語言看起來比較貼心,但結果就是英文手冊裡混進一個中文的標號說明,而且沒有人會發現。validate 不用開瀏覽器就能先擋下來:
$ npm run validate -- --chapter camera-add
manifest 驗證失敗(1 個問題):
- camera-add.steps[7].annotate[0].legend: 缺少 en 的翻譯
英文正文放在 docs/en/,檔名跟中文版一樣,{{legend.*}} 與 {{screenshot:*}} 的寫法也一樣。build 時,標號說明會換成英文、截圖會指向 screenshots/en/。
翻譯正文最常見的問題,是把按鈕名稱翻得「更好」。畫面上寫的是 Add Camera,AI 覺得 New Camera 比較順,就自己改了。讀起來沒問題,但讀者在畫面上找不到這顆按鈕。
Day 19 的 validate 會把正文裡用「」引用的名稱,拿去跟 App 的文案 (zh-Hant.json) 比對。換成英文之後,同一套檢查改成對照 en.json。App 的 i18n 檔本來就有每個語言的正確翻譯,拿它當術語表,手冊上的名稱就一定等於畫面上的字,不需要另外維護一份術語表。
比較不一樣的是,英文不用「」,UI 名稱慣例上是用粗體標示,所以抓名稱的規則也要逐語言定義:
const TERM_PATTERN: Record<string, RegExp> = {
'zh-Hant': /「([^「」]+)」/g,
en: /\*\*([^*\n]+)\*\*/g,
}
可以故意把兩個名稱改「好」一點試試看:
$ npm run validate -- --chapter camera-add --locale en
manifest 驗證通過(1 章)。
[en] 需要人工確認(2 則):
- docs/en/50-camera-add.md: **New Camera** 在 en 的 App 文案、manifest、章節標題與示範資料裡都找不到,請人工確認畫面上真的有這個名稱
- docs/en/50-camera-add.md: **Front Gate** 在 en 的 App 文案、manifest、章節標題與示範資料裡都找不到,請人工確認畫面上真的有這個名稱
Add Camera 被寫成 New Camera、安裝位置的預設值 Main Gate 被寫成 Front Gate,兩個都抓到了。
至於人工保護區裡的法規聲明,翻譯也應該由人負責。範例專案裡的英文版是 AI 翻的,實務上要交給法務或母語人士審過才算數。保護區的檢查照樣有效,翻好之後,AI 一樣不能再改。
英文正文的寫法規則,我也補進了
agent/STYLE.md,之後讓 agent 寫英文正文時,它就知道要用粗體、名稱要一字不改地取自en.json。
最後是 build 自己寫進文件的字:圖號的「圖」、標號說明表格的「標號/說明」、封面的「版本」、目錄標題。這些不屬於任何一章,也不在 App 的 i18n 檔裡,所以直接在 build.ts 逐語言定義:
en: {
lang: 'en-US',
toc: 'Contents',
figure: (no) => `Figure ${no}`,
legendHeader: ['No.', 'Description'],
version: (v) => `Version ${v}`,
...
},
產物放在 output/{locale}/:
$ npm run build -- --pdf
[zh-Hant] 合併 5 章 -> output/zh-Hant/manual.md
[zh-Hant] pandoc -> output/zh-Hant/manual.docx
[zh-Hant] Word -> output/zh-Hant/manual.pdf(目錄頁碼已更新)
[en] 合併 5 章 -> output/en/manual.md
[en] pandoc -> output/en/manual.docx
[en] Word -> output/en/manual.pdf(目錄頁碼已更新)
$ npm run build -- --to html
[zh-Hant] 合併 5 章 -> output/zh-Hant/manual.md
[zh-Hant] pandoc -> output/zh-Hant/manual.html
[en] 合併 5 章 -> output/en/manual.md
[en] pandoc -> output/en/manual.html

英文比中文長,同樣的內容,英文版的第 5 章從第 12 頁才開始 (中文版是第 10 頁)。好在目錄頁碼是 Word 排完版才算的,不用手動調整。
跟前兩天一樣,英文版的手冊 (Word、HTML、PDF) 我都放在 範例專案的 Release 了,大家有興趣的話可以下載來比較看看。
今天讓同一份 manifest 產出了中英文兩份手冊:
locale 再跑一次。docs/en/,名稱拿 App 的 en.json 當術語表來檢查。output/{locale}/。多語言看起來像是一個新功能,但實際上要新增的東西不多,大部分是把前面已經有的機制 (狀態注入、data-testid、validate 的名稱檢查) 多套一層語言。前面每一步都把「內容」跟「畫面」分開,換語言的時候就少很多麻煩。
不過,今天做的只是「出一份英文版」。更麻煩的是之後的維護:中文正文改了一段,英文版怎麼知道要跟著改?明天來處理翻譯同步。