local-tool/: visionA-local desktop app
- M1: Wails shell + Go server + Next.js UI + Mock mode (macOS dmg ready)
- M2: i18n (zh-TW/en) + Settings 4-tab refactor
- M3: Embedded Python 3.12 runtime (python-build-standalone) + KneronPLUS wheels
- M4: Windows Inno Setup script (build on Windows runner)
- M5: Linux AppImage script + udev rule (build on Linux runner)
- M6: ffmpeg (GPL, pending legal review) + yt-dlp bundled
- Lifecycle: watchServer health check, fatal native dialog,
Wails IPC raise endpoint, stale process cleanup
.autoflow/: full PRD / Design Spec / Architecture / Testing docs
(4 rounds tri-party discussion + cross review)
.github/workflows/: macOS / Windows / Linux build CI
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
9.5 KiB
9.5 KiB
i18n — visionA-local 多語系
支援語言:繁體中文(台灣)+ 英文(使用者決策 Q13) 預設:跟隨系統語系;找不到對應時 fallback 英文
1. 涵蓋範圍
| 層 | 是否要 i18n | 方式 |
|---|---|---|
| Next.js 業務前端 | ✅ 必要 | i18next / next-intl |
| Wails app(安裝精靈、錯誤對話框) | ✅ 必要 | Go 端 i18n map |
| Go server log 訊息 | ❌ 英文固定 | 給開發者看,不翻譯 |
| API 錯誤訊息(JSON response) | ⚠️ 看情境 | code + message 英文;前端用 code 轉成對應語系 |
| README / 說明文件 | ⚠️ | 英文優先,第二版補中文 |
2. 前端 i18n(Next.js)
2.1 原專案現況
edge-ai-platform 的 frontend/ 已經有 useTranslation hook(見 Design round 1),沿用即可。推測是用 react-i18next 或 next-intl。M2 階段拆分時一併確認並整合。
2.2 檔案結構
frontend/src/locales/
├── en/
│ ├── common.json
│ ├── dashboard.json
│ ├── devices.json
│ ├── models.json
│ ├── workspace.json
│ ├── settings.json
│ └── errors.json
└── zh-TW/
├── common.json
├── dashboard.json
├── devices.json
├── models.json
├── workspace.json
├── settings.json
└── errors.json
2.3 語系切換
- Settings 頁新增「語言」下拉:跟隨系統 / 中文 / English
- 使用者選擇後存進
localStorage.locale - 下次啟動讀取此值;無值時讀
navigator.language - 跟隨系統模式下,
zh-*全歸到zh-TW,其他歸en
2.4 關鍵字決策(避免翻譯混亂)
| 英文 | 繁中 | 備註 |
|---|---|---|
| Device | 裝置 | 不用「設備」 |
| Model | 模型 | |
| Inference | 推論 | 不用「推理」 |
| Workspace | 工作區 | |
| Mock mode | 模擬模式 | |
| Dashboard | 儀表板 | |
| Classification | 分類 | |
| Detection | 偵測 | 不用「檢測」 |
| Face recognition | 臉部辨識 | |
| Settings | 設定 | |
| Upload | 上傳 | |
| Flash | 燒錄 | (已砍,但文案保留供未來重開) |
3. Wails app i18n
Tray 已砍(第三輪使用者決策 Q-A=A3),本節僅涵蓋安裝精靈與錯誤對話框。
3.1 為何獨立做
Wails app 是 Go,在 Go server 還沒啟動前就需要顯示文字(安裝精靈、錯誤對話框)。不能依賴前端 i18n。
3.2 實作
// visiona-local/i18n.go
package main
import (
"embed"
"encoding/json"
"strings"
"golang.org/x/text/language"
)
//go:embed locales/*.json
var localesFS embed.FS
type Translator struct {
current language.Tag
messages map[language.Tag]map[string]string
}
func NewTranslator(preferred string) *Translator {
t := &Translator{
messages: make(map[language.Tag]map[string]string),
}
t.load("en")
t.load("zh-TW")
t.setLocale(preferred)
return t
}
func (t *Translator) load(code string) {
data, _ := localesFS.ReadFile("locales/" + code + ".json")
var m map[string]string
json.Unmarshal(data, &m)
tag, _ := language.Parse(code)
t.messages[tag] = m
}
func (t *Translator) setLocale(preferred string) {
if preferred == "" || preferred == "system" {
preferred = detectSystemLocale()
}
if strings.HasPrefix(preferred, "zh") {
t.current = language.TraditionalChinese
} else {
t.current = language.English
}
}
func (t *Translator) T(key string) string {
if msg, ok := t.messages[t.current][key]; ok {
return msg
}
// fallback to English
if msg, ok := t.messages[language.English][key]; ok {
return msg
}
return key // last resort
}
3.3 檔案
visiona-local/locales/
├── en.json
└── zh-TW.json
3.4 關鍵 key 清單(給安裝精靈)
// en.json
{
"installer.welcome.title": "Welcome to visionA-local",
"installer.welcome.subtitle": "Kneron KL520/KL720 local development tool",
"installer.step.creating_dir": "Creating install directory",
"installer.step.extract_binary": "Extracting server binary",
"installer.step.extract_data": "Extracting model files",
"installer.step.setup_python": "Setting up Python runtime",
"installer.step.install_wheels": "Installing Python packages",
"installer.step.install_driver": "Installing USB device driver",
"installer.step.verify": "Verifying installation",
"installer.error.no_python": "Python 3.12 is required but not found on your system.",
"installer.error.driver_failed": "USB driver installation failed. You may need to allow the UAC prompt.",
"error.already_running": "visionA-local is already running.",
"error.port_in_use": "Port {port} is in use.",
"error.server_unhealthy": "Server did not respond in time."
}
// zh-TW.json
{
"installer.welcome.title": "歡迎使用 visionA-local",
"installer.welcome.subtitle": "Kneron KL520/KL720 本機開發工具",
"installer.step.creating_dir": "建立安裝目錄",
"installer.step.extract_binary": "解壓伺服器程式",
"installer.step.extract_data": "解壓模型檔案",
"installer.step.setup_python": "設定 Python 執行環境",
"installer.step.install_wheels": "安裝 Python 套件",
"installer.step.install_driver": "安裝 USB 裝置驅動",
"installer.step.verify": "驗證安裝",
"installer.error.no_python": "找不到 Python 3.12。請安裝後重試,或使用內建 Python 模式。",
"installer.error.driver_failed": "USB 驅動安裝失敗,請確認已同意系統權限提示。",
"error.already_running": "visionA-local 已在執行中。",
"error.port_in_use": "連接埠 {port} 已被佔用。",
"error.server_unhealthy": "伺服器未在預期時間內回應。"
}
3.5 參數化
簡單字串替換 {key} → strings.Replace,不做複數形 / 性別等進階 i18n(不值得)。
4. 系統語系偵測
4.1 各平台 API
| 平台 | Go 偵測方式 |
|---|---|
| macOS | defaults read -g AppleLocale |
| Windows | GetUserDefaultLocaleName via golang.org/x/sys |
| Linux | 環境變數 $LANG / $LC_MESSAGES |
4.2 實作
func detectSystemLocale() string {
// 1. 先查 env(跨平台通用)
for _, env := range []string{"LC_ALL", "LC_MESSAGES", "LANG"} {
if v := os.Getenv(env); v != "" {
return normalizeLocale(v)
}
}
// 2. 各平台特殊查詢
switch runtime.GOOS {
case "darwin":
out, _ := exec.Command("defaults", "read", "-g", "AppleLocale").Output()
return normalizeLocale(strings.TrimSpace(string(out)))
case "windows":
// GetUserDefaultLocaleName via syscall
return getWindowsLocale()
}
return "en"
}
func normalizeLocale(s string) string {
// "zh_TW.UTF-8" → "zh-TW"
s = strings.Split(s, ".")[0]
s = strings.Replace(s, "_", "-", -1)
if strings.HasPrefix(strings.ToLower(s), "zh") {
return "zh-TW"
}
return "en"
}
5. 翻譯流程與維護
5.1 新增字串的流程
- 在 code 中使用
t.T("some.new.key") - 在
en.json加上 key - 在
zh-TW.json加上對應翻譯 - lint 腳本檢查所有 key 在兩邊都存在(
scripts/check-i18n.sh)
5.2 lint 腳本(建議加進 CI)
#!/usr/bin/env bash
# scripts/check-i18n.sh
set -e
for dir in frontend/src/locales visiona-local/locales; do
en_keys=$(jq -r 'keys[]' "$dir/en.json" | sort)
zh_keys=$(jq -r 'keys[]' "$dir/zh-TW.json" | sort)
diff <(echo "$en_keys") <(echo "$zh_keys") || {
echo "❌ i18n keys out of sync in $dir"
exit 1
}
done
echo "✅ i18n keys are in sync"
6. 第一版範圍(M2 階段實作)
M2 先做:
- 英文 + 繁中 locale 檔建立
- Wails app 端 i18n loader(安裝精靈 + 錯誤對話框)
- 前端整合現有 i18n(沿用 edge-ai-platform 的 hook)
- Settings 頁加入語言下拉
- 切換語言後需重啟 app 才生效(首版妥協)
首版策略:切換語言 → 顯示「已儲存,重啟後生效」→ 使用者手動關閉再開。這樣可以避開:
- Next.js WebView 內的 i18n hot-reload 需要重跑所有
useTranslationhook - Wails 原生 menu bar 需要
runtime.MenuSetApplicationMenu()重建 - 安裝精靈與錯誤對話框已經是 one-shot,重啟沒成本
M2 以後才做:
- 動態語系切換(即時生效,不需重啟):對應 Design 規格
10-i18n §10.7,前端走i18n.changeLanguage()、Wails 端 emit 事件重建 menu - API error code 對照表 — 先讓前端直接顯示英文
- 複數形處理
6.1 Wails 原生 menu 快捷鍵清單(第四輪修訂)
對應第四輪決策 R4-6(⌘R → ⌘Shift+R、取消 ⌘Shift+W):
| 功能 | 快捷鍵 | Wails menu API |
|---|---|---|
| 前往 Dashboard | ⌘1 / Ctrl+1 | keys.CmdOrCtrl("1") |
| 前往 Devices | ⌘2 / Ctrl+2 | keys.CmdOrCtrl("2") |
| 前往 Models | ⌘3 / Ctrl+3 | keys.CmdOrCtrl("3") |
| 前往 Workspace | ⌘4 / Ctrl+4 | keys.CmdOrCtrl("4") |
| 設定 | ⌘, / Ctrl+, | keys.CmdOrCtrl(",") |
| 重新整理裝置 | ⌘Shift+R / Ctrl+Shift+R | keys.Combo("r", keys.CmdOrCtrlKey, keys.ShiftKey) | 避開 WebView 內建 ⌘R reload |
| 結束 | ⌘Q / Ctrl+Q | keys.CmdOrCtrl("q") |
| 已移除:與 macOS 「關閉所有視窗」衝突,且 ⌘4 已有相同功能 |
生產模式必須 disable WebView 預設的 ⌘R reload(wails.json 或 app.go Assets middleware 攔截)。