// visionA-local 控制台 i18n // namespace: desktop-control // 對齊 Design Spec v2.1 control-panel.md §9 + startup-progress.md §7 const dict = { 'zh-TW': { 'control.title': 'visionA-local · 伺服器控制台', 'control.status.idle': '閒置', 'control.status.starting': '啟動中...', 'control.status.running': '執行中', 'control.status.runningBrowserOpened': '執行中 · 已開啟瀏覽器', 'control.status.stopping': '停止中...', 'control.status.stopped': '已停止', 'control.status.error': '錯誤:{reason}', 'control.meta.port': '連接埠', 'control.meta.uptime': '執行時間', 'control.meta.pid': '程序 ID', 'control.meta.version': '版本', 'control.action.openBrowser': '在瀏覽器開啟', 'control.action.start': '啟動', 'control.action.stop': '停止', 'control.action.restart': '重新啟動', 'control.action.manage': '管理', 'control.action.stopServer': '停止伺服器', 'control.action.restartServer': '重新啟動伺服器', 'control.log.followTail': '自動跟隨最新', 'control.log.showTimestamps': '顯示時間戳', 'control.log.filterPlaceholder': '過濾 log...', 'control.log.jumpToLatest': '跳到最新', 'control.log.clear': '清空', 'control.log.clearToast': '已清空 log', 'control.log.copy': '複製', 'control.log.copied': '已複製到剪貼簿', 'control.log.copyPrivacyHint': 'Log 可能包含檔名與裝置資訊,請注意分享對象', 'control.log.export': '匯出 log', 'control.log.exported': '已匯出到 {path}', 'control.log.openFolder': '開啟 log 資料夾', 'control.log.lines': '行數:{current} / {max}', 'control.footer.closeWarning': '⚠ 關閉此視窗會停止伺服器', 'control.error.title': '伺服器無法啟動', 'control.error.description': '{reason}', 'control.error.restartButton': '重新啟動伺服器', 'control.error.viewLogDetails': '檢視 log 詳情', 'control.error.reportButton': '回報問題...', 'control.shutdown.stopping': '正在停止伺服器…', // startup progress 'startup.panel.title': '正在啟動 visionA-local', 'startup.panel.ariaLabel': '啟動進度:階段 {current} / {max}', 'startup.progressLabel': '進度 {current} / {max}', 'startup.progressWithElapsed': '進度 {current} / {max} · 已等待 {elapsed} 秒', 'startup.stage.1.label': '初始化控制台', 'startup.stage.1.description': '準備 visionA-local 桌面環境', 'startup.stage.2.label': '檢查 Python 執行環境', 'startup.stage.2.description': '首次啟動可能需要較長時間', 'startup.stage.3.label': '啟動本機伺服器', 'startup.stage.3.description': '在 127.0.0.1:3721 啟動服務', 'startup.stage.4.label': '偵測 Kneron 裝置', 'startup.stage.4.description': '掃描已連接的硬體', 'startup.stage.5.label': '開啟瀏覽器', 'startup.stage.5.description': '在預設瀏覽器開啟 Web UI', 'startup.stage.5.skipped.label': '跳過(依偏好設定)', 'startup.stage.6.label': '等待 Web UI 連線', 'startup.stage.6.description': '正在與瀏覽器建立即時連線', 'startup.stage.6.manualHint': '請點擊控制台的「在瀏覽器開啟」按鈕', // 各 stage 細步提示(由 Go 的 startup:stage-detail event 觸發) // Stage 1 - 初始化控制台 'startup.stage.1.detail.migrate': '檢查並遷移舊資料目錄...', 'startup.stage.1.detail.lock': '建立 single-instance lock...', 'startup.stage.1.detail.ipc': '啟動 Wails IPC server...', 'startup.stage.1.detail.seed': '正在準備內建模型資料(首次啟動會花幾秒鐘)...', 'startup.stage.1.detail.seedSlow': '正在準備內建模型資料(Windows Defender 掃描檔案中,已 {elapsed} 秒)', // Stage 2 - 檢查 Python 執行環境 'startup.stage.2.detail.detect': '偵測系統 Python 執行環境...', 'startup.stage.2.detail.bootstrap': '正在解壓內建 Python runtime(首次啟動需 1-2 分鐘)...', 'startup.stage.2.detail.venv': '正在建立 Python 虛擬環境...', 'startup.stage.2.detail.pip': '正在安裝 Python 套件 numpy / opencv / KneronPLUS(首次啟動需 1-3 分鐘)...', 'startup.stage.2.detail.driver': '正在安裝 Kneron USB 驅動程式(請點選 UAC 允許)...', // Stage 3 - 啟動本機伺服器 'startup.stage.3.detail.spawn': '正在啟動伺服器子程序...', 'startup.stage.3.detail.waitHealth': '正在等待伺服器健康檢查通過(已等 {elapsed} 秒)', 'startup.stage.3.detail.waitHealthSlow': '首次啟動較久屬正常,Windows Defender 掃描可能需 1-2 分鐘(已等 {elapsed} 秒)', // Stage 4 - 偵測 Kneron 裝置 'startup.stage.4.detail.probe': '正在掃描 USB 裝置...', // Stage 5 - 開啟瀏覽器 'startup.stage.5.detail.open': '正在開啟系統預設瀏覽器...', // Stage 6 - 等待 Web UI 連線 'startup.stage.6.detail.wait': '正在等待瀏覽器建立 WebSocket 連線...', // 啟動完成後 collapsed 面板的標題與提示 'startup.collapsed.title': '啟動完成', 'startup.collapsed.hint': '· 點此展開檢視', 'startup.collapsed.hintRestart': '· 點此或按重啟可重新展開', 'startup.status.pending': '等待中', 'startup.status.running': '進行中', 'startup.status.done': '完成', 'startup.status.failed': '失敗', 'startup.status.skipped': '跳過(依偏好設定)', 'startup.timeout.message': '這個步驟花的時間比預期久,正在重試...', 'startup.error.title': '啟動失敗', 'startup.error.description.timeout': '啟動時間超過 180 秒,可能是系統環境異常或網路中斷。', 'startup.error.description.stageFailed': '階段「{stageLabel}」執行失敗。', 'startup.error.failedStage': '失敗階段:{n} · {label}', 'startup.error.retry': '重試', 'startup.error.viewLog': '檢視 log', 'startup.error.report': '回報問題', // settings 'settings.title': '設定', 'settings.autoOpenBrowser.label': '啟動時自動開啟瀏覽器', 'settings.autoOpenBrowser.hintLinux': 'Linux 桌面環境差異大,預設關閉', 'settings.language.label': '語言', 'settings.about.title': '關於', }, 'en': { 'control.title': 'visionA-local · Server Control', 'control.status.idle': 'Idle', 'control.status.starting': 'Starting...', 'control.status.running': 'Running', 'control.status.runningBrowserOpened': 'Running · Browser opened', 'control.status.stopping': 'Stopping...', 'control.status.stopped': 'Stopped', 'control.status.error': 'Error: {reason}', 'control.meta.port': 'Port', 'control.meta.uptime': 'Uptime', 'control.meta.pid': 'PID', 'control.meta.version': 'Version', 'control.action.openBrowser': 'Open in Browser', 'control.action.start': 'Start', 'control.action.stop': 'Stop', 'control.action.restart': 'Restart', 'control.action.manage': 'Manage', 'control.action.stopServer': 'Stop server', 'control.action.restartServer': 'Restart server', 'control.log.followTail': 'Follow tail', 'control.log.showTimestamps': 'Show timestamps', 'control.log.filterPlaceholder': 'Filter...', 'control.log.jumpToLatest': 'Jump to latest', 'control.log.clear': 'Clear', 'control.log.clearToast': 'Log cleared', 'control.log.copy': 'Copy', 'control.log.copied': 'Copied to clipboard', 'control.log.copyPrivacyHint': 'Log may contain filenames and device info. Share with care.', 'control.log.export': 'Export log', 'control.log.exported': 'Exported to {path}', 'control.log.openFolder': 'Open log folder', 'control.log.lines': 'Lines: {current} / {max}', 'control.footer.closeWarning': '⚠ Closing this window will stop the server', 'control.error.title': 'Server failed to start', 'control.error.description': '{reason}', 'control.error.restartButton': 'Restart Server', 'control.error.viewLogDetails': 'View log details', 'control.error.reportButton': 'Report...', 'control.shutdown.stopping': 'Stopping server…', // startup progress 'startup.panel.title': 'Starting visionA-local', 'startup.panel.ariaLabel': 'Startup progress: stage {current} / {max}', 'startup.progressLabel': 'Progress {current} / {max}', 'startup.progressWithElapsed': 'Progress {current} / {max} · {elapsed}s elapsed', 'startup.stage.1.label': 'Initializing control panel', 'startup.stage.1.description': 'Preparing visionA-local desktop', 'startup.stage.2.label': 'Checking Python runtime', 'startup.stage.2.description': 'First launch may take longer', 'startup.stage.3.label': 'Starting local server', 'startup.stage.3.description': 'Starting service on 127.0.0.1:3721', 'startup.stage.4.label': 'Detecting Kneron devices', 'startup.stage.4.description': 'Scanning connected hardware', 'startup.stage.5.label': 'Opening browser', 'startup.stage.5.description': 'Opening the Web UI in your default browser', 'startup.stage.5.skipped.label': 'Skipped (per preference)', 'startup.stage.6.label': 'Waiting for Web UI to connect', 'startup.stage.6.description': 'Establishing realtime connection with the browser', 'startup.stage.6.manualHint': 'Please click "Open in Browser" in the Control Panel', // All stage sub-step hints (triggered by Go startup:stage-detail event) // Stage 1 'startup.stage.1.detail.migrate': 'Checking and migrating legacy data directories...', 'startup.stage.1.detail.lock': 'Acquiring single-instance lock...', 'startup.stage.1.detail.ipc': 'Starting Wails IPC server...', 'startup.stage.1.detail.seed': 'Preparing built-in model data (takes a few seconds on first launch)...', 'startup.stage.1.detail.seedSlow': 'Preparing built-in model data (Defender scanning files, {elapsed}s elapsed)', // Stage 2 'startup.stage.2.detail.detect': 'Detecting system Python runtime...', 'startup.stage.2.detail.bootstrap': 'Extracting bundled Python runtime (takes 1-2 min on first launch)...', 'startup.stage.2.detail.venv': 'Creating Python virtual environment...', 'startup.stage.2.detail.pip': 'Installing Python packages numpy / opencv / KneronPLUS (takes 1-3 min on first launch)...', 'startup.stage.2.detail.driver': 'Installing Kneron USB driver (please allow UAC)...', // Stage 3 'startup.stage.3.detail.spawn': 'Launching server subprocess...', 'startup.stage.3.detail.waitHealth': 'Waiting for server health check ({elapsed}s elapsed)', 'startup.stage.3.detail.waitHealthSlow': 'First launch is slow — Windows Defender scan may take 1-2 minutes ({elapsed}s elapsed)', // Stage 4 'startup.stage.4.detail.probe': 'Scanning USB devices...', // Stage 5 'startup.stage.5.detail.open': 'Opening system default browser...', // Stage 6 'startup.stage.6.detail.wait': 'Waiting for browser to establish WebSocket connection...', // Collapsed panel after startup ready 'startup.collapsed.title': 'Startup complete', 'startup.collapsed.hint': '· click to expand', 'startup.collapsed.hintRestart': '· click or restart to expand', 'startup.status.pending': 'Waiting', 'startup.status.running': 'Running', 'startup.status.done': 'Done', 'startup.status.failed': 'Failed', 'startup.status.skipped': 'Skipped (per preference)', 'startup.timeout.message': 'This step is taking longer than expected, retrying...', 'startup.error.title': 'Startup failed', 'startup.error.description.timeout': 'Startup exceeded 180 seconds. Your environment may have issues or the network is interrupted.', 'startup.error.description.stageFailed': 'Stage "{stageLabel}" failed.', 'startup.error.failedStage': 'Failed stage: {n} · {label}', 'startup.error.retry': 'Retry', 'startup.error.viewLog': 'View Log', 'startup.error.report': 'Report Issue', // settings 'settings.title': 'Settings', 'settings.autoOpenBrowser.label': 'Auto-open browser on startup', 'settings.autoOpenBrowser.hintLinux': 'Linux desktop environments vary, disabled by default', 'settings.language.label': 'Language', 'settings.about.title': 'About', }, }; let currentLocale = 'zh-TW'; // 根據 navigator.language 或儲存值決定 locale export function detectLocale(preferredLocale) { // 1. 使用者明確指定(preferences) if (preferredLocale && dict[preferredLocale]) return preferredLocale; // 2. localStorage const stored = localStorage.getItem('vl-locale'); if (stored && dict[stored]) return stored; // 3. navigator.language const lang = (navigator.language || navigator.userLanguage || '').toLowerCase(); if (lang.startsWith('zh')) return 'zh-TW'; if (lang.startsWith('en')) return 'en'; // 4. fallback return 'zh-TW'; } export function setLocale(locale) { if (dict[locale]) { currentLocale = locale; localStorage.setItem('vl-locale', locale); } } export function getLocale() { return currentLocale; } // t('key', {param: value}) → 翻譯字串並替換 {param} export function t(key, params) { let s = (dict[currentLocale] && dict[currentLocale][key]); if (s === undefined) { // fallback en s = (dict.en && dict.en[key]) || key; } if (params) { for (const k in params) { s = s.replace(new RegExp('\\{' + k + '\\}', 'g'), params[k]); } } return s; } // 對 DOM 中所有 [data-i18n] / [data-i18n-placeholder] 執行翻譯 export function applyI18n(root) { const scope = root || document; scope.querySelectorAll('[data-i18n]').forEach(el => { const key = el.getAttribute('data-i18n'); el.textContent = t(key); }); scope.querySelectorAll('[data-i18n-placeholder]').forEach(el => { const key = el.getAttribute('data-i18n-placeholder'); el.setAttribute('placeholder', t(key)); }); } export const LOCALES = Object.keys(dict);