diff --git a/frontend/package.json b/frontend/package.json index 6df7b260b..7ec195924 100644 --- a/frontend/package.json +++ b/frontend/package.json @@ -9,9 +9,10 @@ "lint": "eslint .", "preview": "vite preview", "test": "vitest", - "test:run": "vitest run", + "test:run": "vitest run && npm run check:i18n", "test:coverage": "vitest run --coverage", - "test:ui": "vitest --ui" + "test:ui": "vitest --ui", + "check:i18n": "node scripts/check-i18n-parity.mjs" }, "dependencies": { "@dnd-kit/core": "^6.3.1", diff --git a/frontend/scripts/check-i18n-parity.mjs b/frontend/scripts/check-i18n-parity.mjs new file mode 100644 index 000000000..ffc6cdb14 --- /dev/null +++ b/frontend/scripts/check-i18n-parity.mjs @@ -0,0 +1,174 @@ +#!/usr/bin/env node +// Verifies parity across locale files (en / zh-CN / zh-TW): +// 1. Leaf-key sets are identical +// 2. Each leaf's {{placeholder}} set is identical +// 3. Plural suffixes: every en key ending in _plural / _one / _other must +// exist in every other locale, and other locales must not introduce an +// _one key that en does not have. +// Malformed input (missing `export default`, parse errors, non-string leaves, +// unsupported property kinds) fails loudly instead of silently passing the gate. +// Exits 1 with a diagnostic report on any failure, else exits 0. + +import fs from 'node:fs'; +import path from 'node:path'; +import url from 'node:url'; + +const scriptDir = path.dirname(url.fileURLToPath(import.meta.url)); +const frontendDir = path.resolve(scriptDir, '..'); +const localesDir = path.join(frontendDir, 'src/i18n/locales'); +const tsPath = path.join(frontendDir, 'node_modules/typescript/lib/typescript.js'); + +const tsModule = await import(url.pathToFileURL(tsPath).href); +const ts = tsModule.default ?? tsModule; + +function collectLeaves(node, prefix, leaves) { + if (!ts.isObjectLiteralExpression(node)) return; + for (const prop of node.properties) { + if (!ts.isPropertyAssignment(prop)) { + console.error( + `Unsupported property kind ${ts.SyntaxKind[prop.kind]} at "${prefix}" ` + + `(locale files must use plain \`key: value\` assignments — no spread, shorthand, methods, or accessors).`, + ); + process.exit(1); + } + let name; + if (ts.isIdentifier(prop.name)) name = prop.name.text; + else if (ts.isStringLiteral(prop.name) || ts.isNoSubstitutionTemplateLiteral(prop.name)) name = prop.name.text; + else if (ts.isComputedPropertyName(prop.name)) { + console.error(`ComputedPropertyName not allowed in locale file at path "${prefix}"`); + process.exit(1); + } else { + console.error(`Unsupported property-name kind ${ts.SyntaxKind[prop.name.kind]} at "${prefix}"`); + process.exit(1); + } + const p = prefix ? `${prefix}.${name}` : name; + if (ts.isObjectLiteralExpression(prop.initializer)) { + collectLeaves(prop.initializer, p, leaves); + } else { + const value = extractStringValue(prop.initializer, p); + leaves.set(p, value); + } + } +} + +function extractStringValue(node, keyPath) { + if (ts.isStringLiteral(node) || ts.isNoSubstitutionTemplateLiteral(node)) return node.text; + if (ts.isTemplateExpression(node)) { + let out = node.head.text; + for (const span of node.templateSpans) { + out += '${' + span.expression.getText() + '}'; + out += span.literal.text; + } + return out; + } + console.error( + `Non-string leaf at "${keyPath}" (kind=${ts.SyntaxKind[node.kind]}): ${node.getText()}\n` + + `Locale files must only contain string or template literals as leaf values.`, + ); + process.exit(1); +} + +function loadLocale(filePath) { + const src = fs.readFileSync(filePath, 'utf8'); + const sf = ts.createSourceFile(filePath, src, ts.ScriptTarget.Latest, true); + if (sf.parseDiagnostics && sf.parseDiagnostics.length > 0) { + console.error(`${filePath}: ${sf.parseDiagnostics.length} parse error(s):`); + for (const d of sf.parseDiagnostics.slice(0, 10)) { + const msg = typeof d.messageText === 'string' ? d.messageText : d.messageText.messageText; + const { line, character } = sf.getLineAndCharacterOfPosition(d.start ?? 0); + console.error(` ${line + 1}:${character + 1} ${msg}`); + } + process.exit(1); + } + const leaves = new Map(); + let foundExport = false; + ts.forEachChild(sf, (n) => { + if (ts.isExportAssignment(n)) { + foundExport = true; + collectLeaves(n.expression, '', leaves); + } + }); + if (!foundExport) { + console.error(`${filePath}: no \`export default\` found — locale files must use \`export default { ... }\`.`); + process.exit(1); + } + if (leaves.size === 0) { + console.error(`${filePath}: \`export default\` resolved to zero leaves — file is empty or not a nested object.`); + process.exit(1); + } + return leaves; +} + +const locales = { + en: loadLocale(path.join(localesDir, 'en.ts')), + 'zh-CN': loadLocale(path.join(localesDir, 'zh-CN.ts')), + 'zh-TW': loadLocale(path.join(localesDir, 'zh-TW.ts')), +}; + +let failed = false; +const MAX_REPORT = 20; + +function reportList(label, items) { + if (items.length === 0) return; + failed = true; + console.error(`\n[${label}] (${items.length})`); + items.slice(0, MAX_REPORT).forEach((i) => console.error(` ${i}`)); + if (items.length > MAX_REPORT) console.error(` ... and ${items.length - MAX_REPORT} more`); +} + +// Check 1: key set equality +const enKeys = new Set(locales.en.keys()); +for (const [code, map] of Object.entries(locales)) { + if (code === 'en') continue; + const keys = new Set(map.keys()); + const missing = [...enKeys].filter((k) => !keys.has(k)).sort(); + const extra = [...keys].filter((k) => !enKeys.has(k)).sort(); + reportList(`${code}: missing keys vs en`, missing); + reportList(`${code}: extra keys vs en`, extra); +} + +// Check 2: placeholder set equality per leaf +const placeholderRe = /\{\{[^{}]+\}\}/g; +for (const [code, map] of Object.entries(locales)) { + if (code === 'en') continue; + const mismatches = []; + for (const [key, enValue] of locales.en) { + const otherValue = map.get(key); + if (otherValue === undefined) continue; + const enPlaceholders = new Set((enValue.match(placeholderRe) ?? [])); + const otherPlaceholders = new Set((otherValue.match(placeholderRe) ?? [])); + const missingPh = [...enPlaceholders].filter((p) => !otherPlaceholders.has(p)); + const extraPh = [...otherPlaceholders].filter((p) => !enPlaceholders.has(p)); + if (missingPh.length || extraPh.length) { + mismatches.push(`${key}: en=${[...enPlaceholders].join(',') || '∅'} vs ${code}=${[...otherPlaceholders].join(',') || '∅'}`); + } + } + reportList(`${code}: placeholder mismatch vs en`, mismatches); +} + +// Check 3: plural suffix presence + reverse _one guard +for (const [code, map] of Object.entries(locales)) { + if (code === 'en') continue; + const pluralIssues = []; + for (const key of enKeys) { + if (key.endsWith('_plural') && !map.has(key)) pluralIssues.push(`missing _plural key: ${key}`); + if (key.endsWith('_one') && !map.has(key)) pluralIssues.push(`missing _one key: ${key}`); + if (key.endsWith('_other') && !map.has(key)) pluralIssues.push(`missing _other key: ${key}`); + } + for (const key of map.keys()) { + if (key.endsWith('_one') && !enKeys.has(key)) { + pluralIssues.push(`unexpected _one not present in en: ${key}`); + } + } + reportList(`${code}: plural key mismatch`, pluralIssues); +} + +if (failed) { + console.error('\n❌ i18n parity check failed.'); + process.exit(1); +} + +console.log(`All locales in parity (en / zh-CN / zh-TW):`); +for (const [code, map] of Object.entries(locales)) { + console.log(` ${code}: ${map.size} leaves`); +} diff --git a/frontend/src/i18n/index.ts b/frontend/src/i18n/index.ts index 24203813f..5787a1fed 100644 --- a/frontend/src/i18n/index.ts +++ b/frontend/src/i18n/index.ts @@ -10,6 +10,7 @@ import ja from './locales/ja'; import it from './locales/it'; import ptBR from './locales/pt-BR'; import zhCN from './locales/zh-CN'; +import zhTW from './locales/zh-TW'; const resources = { en: { translation: en }, @@ -19,6 +20,7 @@ const resources = { it: { translation: it }, 'pt-BR': { translation: ptBR }, 'zh-CN': { translation: zhCN }, + 'zh-TW': { translation: zhTW }, }; i18n @@ -27,7 +29,7 @@ i18n .init({ resources, fallbackLng: 'en', - supportedLngs: ['en', 'de', 'fr', 'ja', 'it', 'pt-BR', 'zh-CN'], + supportedLngs: ['en', 'de', 'fr', 'ja', 'it', 'pt-BR', 'zh-CN', 'zh-TW'], detection: { // Order of detection methods @@ -58,4 +60,5 @@ export const availableLanguages = [ { code: 'it', name: 'Italian', nativeName: 'Italiano' }, { code: 'pt-BR', name: 'Portuguese (Brazil)', nativeName: 'Português (Brasil)' }, { code: 'zh-CN', name: 'Chinese (Simplified)', nativeName: '简体中文' }, + { code: 'zh-TW', name: 'Chinese (Traditional)', nativeName: '繁體中文' }, ]; diff --git a/frontend/src/i18n/locales/zh-CN.ts b/frontend/src/i18n/locales/zh-CN.ts index 9cce82a0a..ea91cff64 100644 --- a/frontend/src/i18n/locales/zh-CN.ts +++ b/frontend/src/i18n/locales/zh-CN.ts @@ -566,6 +566,13 @@ export default { uploadFirmware: '上传固件', uploadFailed: '上传启动失败:{{error}}', uploadedToast: '固件已上传!请在打印机屏幕上触发更新。', + availableVersions: '可用版本', + usable: '可用', + unavailable: '不可用', + installed: '已安装', + newerBadge: '较新', + olderBadge: '较旧', + currentBadge: '当前', }, accessCodePlaceholder: '留空以保持当前值', // ROI editor @@ -1335,6 +1342,7 @@ export default { network: '网络', apiKeys: 'API 密钥', virtualPrinter: '虚拟打印机', + spoolbuddy: 'SpoolBuddy', failureDetection: '故障检测', users: '身份验证', backup: '备份', @@ -1343,6 +1351,50 @@ export default { twoFa: '双因素认证', oidc: 'SSO / OIDC', }, + spoolbuddy: { + infoTitle: 'SpoolBuddy 设备', + infoBody: 'SpoolBuddy kiosk 通过心跳自动注册。如果设备不再使用,或守护进程崩溃遗留了陈旧的重复项,可在此注销。', + duplicatesTitle: '已注册 {{count}} 台设备', + duplicatesBody: 'kiosk 界面只使用最先注册的设备。如果其中有因崩溃遗留的陈旧重复项,请注销它——在线设备会在下次心跳时重新注册自己。', + empty: '尚未注册任何 SpoolBuddy 设备。', + online: '在线', + offline: '离线', + unregister: '注销', + unregisterSuccess: '设备已注销', + unregisterError: '注销设备失败', + confirmTitle: '注销 SpoolBuddy 设备?', + confirmBody: '将从数据库中移除 "{{hostname}}" ({{deviceId}})。如果设备在线,会在下次心跳时重新注册自己。', + ipAddress: 'IP 地址', + firmware: '固件', + lastSeen: '上次在线', + daemonUptime: '守护进程运行时间', + systemUptime: '系统运行时间', + never: '从未', + nfc: 'NFC', + scale: '秤', + cpuTemp: 'CPU 温度', + memory: '内存', + disk: '磁盘', + // Device actions + update: '更新', + updateConfirmTitle: '更新 SpoolBuddy 守护进程?', + updateConfirmBody: '对 "{{hostname}}" 触发软件更新?更新完成后守护进程将重启。', + restartBrowser: '重启浏览器', + restartBrowserConfirmTitle: '重启 kiosk 浏览器?', + restartBrowserConfirmBody: '在 "{{hostname}}" 上重启 kiosk 浏览器?显示将短暂黑屏。', + restartDaemon: '重启守护进程', + restartDaemonConfirmTitle: '重启 SpoolBuddy 守护进程?', + restartDaemonConfirmBody: '在 "{{hostname}}" 上重启 SpoolBuddy 守护进程?设备将离线几秒钟。', + reboot: '重启', + rebootConfirmTitle: '重启设备?', + rebootConfirmBody: '重启 "{{hostname}}"?设备将离线约一分钟。', + shutdown: '关机', + shutdownConfirmTitle: '关闭设备?', + shutdownConfirmBody: '关闭 "{{hostname}}"?您需要物理访问才能重新开机。', + commandConfirm: '确认', + commandQueued: '命令已加入队列', + commandError: '发送命令失败', + }, ldap: { title: 'LDAP 认证', enabledDesc: 'LDAP 认证已启用', @@ -2292,6 +2344,20 @@ export default { resetStep3: '他们可以为您设置一个临时密码', resetStep4: '使用新密码登录并在设置中修改密码', gotIt: '知道了', + resetPassword: { + title: '设置新密码', + subtitle: '请在下方输入并确认您的新密码。', + newPassword: '新密码', + newPasswordPlaceholder: '至少 8 个字符', + confirmPassword: '确认密码', + confirmPasswordPlaceholder: '重复输入新密码', + saving: '保存中…', + submit: '设置新密码', + backToLogin: '返回登录', + passwordsDoNotMatch: '密码不匹配', + passwordTooShort: '密码至少需要 8 个字符', + resetFailed: '密码重置失败。链接可能已过期。', + }, twoFA: { title: '两步验证', subtitle: '您的账户已启用两步验证。请在下方输入验证码。', @@ -2792,7 +2858,7 @@ export default { willBeExtracted: '将被解压', filesExtracted: '已解压 {{count}} 个文件', uploadComplete: '上传完成:{{succeeded}} 个成功', - uploadFailed: '{{count}} 个失败', + uploadFailed: '上传失败', zipFilesFailed: '{{count}} 个文件失败', uploading: '上传中...', changeLink: '更改链接...', @@ -4825,6 +4891,19 @@ export default { deviceInfo: '设备信息', hostname: '主机', uptime: '运行时间', + systemConfig: '后端与认证', + backendUrl: 'Bambuddy 后端 URL', + apiToken: 'API 令牌', + apiTokenPlaceholder: '输入 API 令牌', + saveConfig: '保存配置', + systemQueued: '配置已加入队列。', + nfcDiagnostic: 'NFC 诊断', + scaleDiagnostic: '秤诊断', + readTagDiagnostic: '读取标签诊断', + testNfc: '测试读卡器', + testScale: '测试精度', + testReadTag: '读取标签', + systemFieldsRequired: '后端 URL 为必填项。', // Display tab brightness: '亮度', saved: '已保存', diff --git a/frontend/src/i18n/locales/zh-TW.ts b/frontend/src/i18n/locales/zh-TW.ts new file mode 100644 index 000000000..5e1865e4b --- /dev/null +++ b/frontend/src/i18n/locales/zh-TW.ts @@ -0,0 +1,5061 @@ +export default { + // Navigation + nav: { + printers: '印表機', + archives: '歸檔', + queue: '佇列', + stats: '統計', + profiles: '設定檔案', + maintenance: '維護', + projects: '專案', + inventory: '耗材', + files: '檔案管理器', + notifications: '通知', + settings: '設定', + system: '系統', + collapseSidebar: '收起側邊欄', + expandSidebar: '展開側邊欄', + update: '更新', + updateAvailable: '有可用更新:v{{version}}', + updateAvailableBanner: '版本 {{version}} 已發布!', + viewUpdate: '檢視更新', + viewOnGithub: '在 GitHub 上檢視', + keyboardShortcuts: '鍵盤快捷鍵 (?)', + switchToLight: '切換到淺色模式', + switchToDark: '切換到深色模式', + smartSwitches: '智慧開關', + logout: '登出', + }, + + // Common + common: { + save: '儲存', + saving: '儲存中...', + cancel: '取消', + delete: '刪除', + edit: '編輯', + add: '新增', + close: '關閉', + confirm: '確認', + loading: '載入中...', + error: '錯誤', + success: '成功', + warning: '警告', + enabled: '已啟用', + disabled: '已停用', + yes: '是', + no: '否', + on: '開', + off: '關', + all: '全部', + none: '無', + search: '搜尋', + filter: '篩選', + sort: '排序', + refresh: '重新整理', + download: '下載', + upload: '上傳', + uploading: '上傳中...', + uploadFailed: '上傳失敗', + actions: '操作', + status: '狀態', + name: '名稱', + description: '描述', + date: '日期', + time: '時間', + hours: '小時', + minutes: '分鐘', + seconds: '秒', + days: '天', + enable: '啟用', + disable: '停用', + permissions: '權限', + noPrinters: '未設定印表機', + noData: '尚無資料', + linkNotFound: '未找到連結', + required: '必填', + optional: '可選', + dismiss: '關閉', + apply: '套用', + reset: '重設', + export: '匯出', + import: '匯入', + clear: '清除', + selectAll: '全選', + deselectAll: '取消全選', + noChange: '— 不更改 —', + unchanged: '未更改', + unassigned: '未分配', + unknown: '未知', + unknownError: '未知錯誤', + today: '今天', + tomorrow: '明天', + asap: '儘快', + overdue: '已逾期', + now: '現在', + collapse: '收起', + expand: '展開', + viewArchive: '檢視歸檔', + viewInFileManager: '在檔案管理器中檢視', + addedBy: '由 {{username}} 新增', + prints: '次列印', + more: '還有 {{count}} 個', + ascending: '升序', + descending: '降序', + back: '返回', + copy: '複製', + copied: '已複製!', + printer: '印表機', + remove: '移除', + type: '類型', + print: '列印', + rename: '重新命名', + move: '移動', + create: '建立', + duplicate: '複製', + left: '左', + right: '右', + }, + + // Printers page + printers: { + title: '印表機', + addPrinter: '新增印表機', + editPrinter: '編輯印表機', + deletePrinter: '刪除印表機', + printerName: '印表機名稱', + serialNumber: '序列號', + ipAddress: 'IP 位址 / 主機名稱', + accessCode: '存取碼', + model: '型號', + nozzleCount: '噴嘴數量', + autoArchive: '自動歸檔', + status: { + available: '可用', + idle: '空閒', + printing: '列印中', + paused: '已暫停', + offline: '離線', + problem: '故障', + error: '錯誤', + finished: '已完成', + unknown: '未知', + }, + temperatures: { + nozzle: '噴嘴', + bed: '熱床', + chamber: '腔室', + }, + progress: '{{percent}}% 完成', + timeRemaining: '剩餘 {{time}}', + deleteConfirm: '確定要刪除"{{name}}"嗎?', + maintenanceOk: '維護正常', + maintenanceWarning: '{{count}} 個警告', + maintenanceWarning_plural: '{{count}} 個警告', + maintenanceDue: '{{count}} 個到期', + maintenanceDue_plural: '{{count}} 個到期', + // Sort options + sort: { + name: '名稱', + status: '狀態', + model: '型號', + location: '位置', + ascending: '升序排列', + descending: '降序排列', + }, + // Card size + cardSize: { + small: '小卡片', + medium: '中卡片', + large: '大卡片', + extraLarge: '超大卡片', + }, + // Controls + hideOffline: '隱藏離線', + nextAvailable: '下一個可用', + powerOn: '開機', + offlinePrintersWithPlugs: '帶智慧插座的離線印表機', + noPrintersConfigured: '尚未設定印表機', + search: '搜尋印表機...', + noSearchResults: '沒有印表機符合您的搜尋或篩選條件', + filter: { + allStatuses: '所有狀態', + allLocations: '所有位置', + }, + // Printer card + readyToPrint: '準備列印', + external: '外部', + extL: '外接左', + extR: '外接右', + deleteArchives: '刪除列印歸檔', + noLabel: '無標籤', + printPreview: '列印預覽', + width: '寬度', + height: '高度', + noObjectsFound: '未找到物件', + objectsLoadedOnPrintStart: '物件在列印開始時載入', + willBeSkipped: '將被跳過', + name: '名稱', + serialCannotBeChanged: '序列號無法更改', + locationHelp: '用於分組印表機和篩選佇列任務', + // WiFi signal strength + wifiSignal: { + veryWeak: '非常弱', + weak: '弱', + fair: '一般', + good: '良好', + excellent: '優秀', + }, + // Maintenance + maintenanceUpToDate: '所有維護均已完成 - 點選檢視', + // Chamber light + chamberLightOn: '開啟腔室燈', + chamberLightOff: '關閉腔室燈', + // Files + files: '檔案', + browseFiles: '瀏覽印表機檔案', + // Smart plug + autoOffAfterPrint: '列印後自動關機', + autoOffExecuted: '已執行自動關機 - 開啟印表機以重設', + // HMS errors + hmsErrors: 'HMS 錯誤', + viewHmsErrors: '檢視 {{count}} 個 HMS 錯誤', + // Actions + resume: '繼續', + pause: '暫停', + stop: '停止', + camera: '攝影機', + skipObject: '跳過物件', + reconnect: '重新連線', + forceRefresh: '強制重新整理', + forceRefreshSuccess: '已請求重新整理', + mqttDebug: 'MQTT 偵錯', + printerInformation: '印表機資訊', + copyToClipboard: '複製', + copied: '已複製!', + state: '狀態', + wifiSignalLabel: 'Wi-Fi 訊號', + developerMode: '開發者模式', + enabled: '已啟用', + disabled: '已停用', + addedOn: '新增日期', + sdCard: 'SD 卡', + inserted: '已插入', + notInserted: '未插入', + totalPrintHours: '列印時長', + activeNozzle: '目前:{{nozzle}} 噴嘴', + nozzleRack: '噴嘴架', + nozzleDocked: '已停靠', + nozzleMounted: '已安裝', + nozzleActive: '使用中', + nozzleIdle: '空閒', + nozzleDiameter: '直徑', + nozzleType: '類型', + nozzleStatus: '狀態', + nozzleFilament: '耗材', + nozzleWear: '磨損', + nozzleMaxTemp: '最高溫度', + nozzleSerial: '序列號', + nozzleHardenedSteel: '硬化鋼', + nozzleStainlessSteel: '不鏽鋼', + nozzleTungstenCarbide: '碳化鎢', + nozzleFlow: '流量', + nozzleHighFlow: '高流量', + nozzleStandardFlow: '標準', + // Firmware + firmwareUpdate: '韌體更新', + firmwareInstructions: '在印表機觸控式螢幕上,前往', + firmwareNav: '導航到', + settings: '設定', + firmware: '韌體', + // Discovery + discoverPrinters: '發現印表機', + searching: '搜尋中...', + manualEntry: '手動輸入', + addFromCloud: '從雲端新增', + // Toast messages + toast: { + printerDeleted: '印表機已刪除', + missingSpoolAssignment: '已在{{printer}}上開始列印。以下料槽未分配耗材: {{slots}}', + printerAdded: '印表機已新增', + printerUpdated: '印表機已更新', + failedToDelete: '刪除印表機失敗', + failedToAdd: '新增印表機失敗', + failedToUpdate: '更新印表機失敗', + commandSent: '命令已傳送', + failedToSendCommand: '傳送命令失敗', + turnedOn: '{{name}} 已開啟', + failedToPowerOn: '開啟 {{name}} 失敗', + scriptTriggered: '腳本已觸發', + printStopped: '列印已停止', + printPaused: '列印已暫停', + printResumed: '列印已繼續', + referenceDeleted: '參考已刪除', + detectionAreaSaved: '檢測區域已儲存', + failedToRunScript: '執行腳本失敗', + failedToStopPrint: '停止列印失敗', + failedToPausePrint: '暫停列印失敗', + failedToResumePrint: '繼續列印失敗', + failedToControlChamberLight: '控制腔室燈失敗', + failedToSetSpeed: '設定列印速度失敗', + failedToUpdateSetting: '更新設定失敗', + failedToSkipObjects: '跳過物件失敗', + failedToRereadRfid: '重新讀取 RFID 失敗', + failedToCheckPlate: '檢查列印板失敗', + failedToUpdateLabel: '更新標籤失敗', + failedToDeleteReference: '刪除參考失敗', + failedToSaveDetectionArea: '儲存檢測區域失敗', + plateCheckEnabled: '列印板檢查已啟用', + plateCheckDisabled: '列印板檢查已停用', + calibrationSaved: '校準已儲存!', + calibrationFailed: '校準失敗', + rfidRereadInitiated: '已發起 RFID 重新讀取', + }, + // Connection status + connection: { + connected: '已連線', + offline: '離線', + }, + // Queue info + queue: { + inQueue: '佇列中有 {{count}} 個列印任務', + inQueue_plural: '佇列中有 {{count}} 個列印任務', + }, + // Controls section + controls: '控制', + // RFID + rfid: { + reread: '重新讀取 RFID', + }, + bedJog: { + title: '移動熱床', + bed: '熱床', + step: '步長 (mm)', + up: '熱床上移', + down: '熱床下移', + disabledWhilePrinting: '列印中已停用', + notHomedTitle: '印表機未歸零', + notHomedMessage: '自上次列印以來 Z 軸尚未歸零。請先歸零 Z 以確保安全定位,或者直接移動 — 軟限位將被繞過。', + homeZ: '歸零 Z', + moveAnyway: '強制移動', + homingStarted: 'Z 軸歸零中…', + }, + // Permissions + permission: { + noAdd: '您沒有新增印表機的權限', + noEdit: '您沒有編輯印表機的權限', + noDelete: '您沒有刪除印表機的權限', + noControl: '您沒有控制印表機的權限', + noFiles: '您沒有存取印表機檔案的權限', + noAmsRfid: '您沒有重新讀取 AMS RFID 的權限', + noSmartPlugControl: '您沒有控制智慧插座的權限', + noCamera: '您沒有檢視攝影機的權限', + }, + // Add/Edit modal + modal: { + addTitle: '新增印表機', + editTitle: '編輯印表機', + myPrinter: '我的印表機', + selectModel: '選擇型號...', + locationGroup: '位置 / 分組(可選)', + locationPlaceholder: '例如:工作室、辦公室、地下室', + autoArchiveLabel: '自動歸檔已完成的列印', + fromPrinterSettings: '來自印表機設定', + modelOptional: '型號(可選)', + saveChanges: '儲存更改', + }, + // Skip objects + skipObjects: { + tooltip: '跳過物件', + onlyWhilePrinting: '跳過物件(僅在列印時)', + requiresMultiple: '跳過物件(需要2個以上物件)', + title: '跳過物件', + matchIdsInfo: '將 ID 與印表機螢幕上的 ID 進行對照', + printerShowsIds: '印表機螢幕上顯示列印板上物件的 ID', + skipSelected: '跳過所選', + skipping: '跳過中...', + noObjectsSelected: '未選擇物件', + selectObjectsToSkip: '選擇要從目前列印中跳過的物件', + skipped: '已跳過', + objectsSkipped: '物件已跳過', + activeCount: '{{count}} 個活躍', + waitForLayer: '等待第2層以上才能跳過物件(目前第 {{layer}} 層)', + skip: '跳過', + confirmTitle: '跳過物件?', + confirmMessage: '確定要跳過"{{name}}"嗎?此操作無法復原。', + }, + // Confirm modals + confirm: { + deleteTitle: '刪除印表機', + deleteMessage: '確定要刪除"{{name}}"嗎?這將移除所有連線設定。', + deleteArchivesNote: '此印表機的所有列印歷史將被永久刪除。', + keepArchivesNote: '列印歷史將保留,但不再與此印表機連結。', + stopTitle: '停止列印', + stopMessage: '確定要停止"{{name}}"上的目前列印嗎?這將取消列印任務。', + stopButton: '停止列印', + pauseTitle: '暫停列印', + pauseMessage: '確定要暫停"{{name}}"上的目前列印嗎?', + pauseButton: '暫停列印', + resumeTitle: '繼續列印', + resumeMessage: '確定要繼續"{{name}}"上的列印嗎?', + resumeButton: '繼續列印', + powerOnTitle: '開啟印表機', + powerOnMessage: '確定要開啟"{{name}}"的電源嗎?', + powerOnButton: '開機', + powerOffTitle: '關閉印表機', + powerOffMessage: '確定要關閉"{{name}}"的電源嗎?', + powerOffWarning: '警告:"{{name}}"正在列印中!確定要關閉電源嗎?這將中斷列印並可能損壞印表機。', + powerOffButton: '關機', + }, + // Bulk actions + bulk: { + select: '選擇', + selectAll: '全選', + selectByLocation: '按位置選擇', + selected: '已選擇{{count}}臺', + actions: { + stop: '停止', + pause: '暫停', + resume: '繼續', + clearPlate: '清除列印床', + clearHMS: '清除通知', + }, + confirm: { + stopTitle: '停止{{count}}個列印任務', + stopMessage: '這將取消{{count}}臺印表機上的活動列印任務。此操作無法復原。', + stopButton: '全部停止', + pauseTitle: '暫停{{count}}個列印任務', + pauseMessage: '這將暫停{{count}}臺印表機上的活動列印任務。', + pauseButton: '全部暫停', + clearPlateTitle: '清除{{count}}個列印床', + clearPlateMessage: '這將清除{{count}}臺印表機的列印床,可能會觸發佇列中的任務。', + clearPlateButton: '全部清除', + }, + success: '{{action}}已在{{count}}臺印表機上完成', + partial: '{{succeeded}}成功,{{failed}}失敗', + noneApplicable: '沒有選中的印表機處於適合此操作的狀態', + selectByState: '按狀態選擇', + }, + // Discovery + discovery: { + title: '發現印表機', + searching: '搜尋中...', + scanning: '掃描中...', + scanProgress: '掃描中... {{scanned}}/{{total}}', + foundPrinters: '發現 {{count}} 臺印表機', + noPrintersFound: '未找到印表機', + noPrintersFoundSubnet: '在指定子網中未找到印表機。', + noPrintersFoundNetwork: '在網路上未找到印表機。', + allConfigured: '所有發現的印表機已設定完畢。', + alreadyAdded: '已新增', + select: '選擇', + manualEntry: '手動輸入', + addFromCloud: '從雲端新增', + subnetToScan: '要掃描的子網', + dockerNote: '偵測到 Docker 環境。請以 CIDR 格式輸入印表機所在子網。需要在 docker-compose.yml 中設定 network_mode: host。', + scanSubnet: '掃描子網查詢印表機', + discoverNetwork: '在網路上發現印表機', + scanningSubnet: '正在掃描子網查詢拓竹印表機...', + scanningNetwork: '正在掃描網路...', + serialRequired: '需要序列號', + unknown: '未知', + failedToStart: '啟動發現失敗', + }, + // AMS Drying + drying: { + start: '開始乾燥', + stop: '停止乾燥', + temperature: '溫度', + duration: '時長', + hours: '小時', + timeRemaining: '剩餘 {{time}}', + active: '乾燥中', + notSupported: '不支援乾燥', + powerRequired: '連線AMS電源介面卡以啟用乾燥', + startingDrying: '正在啟動乾燥...', + stoppingDrying: '正在停止乾燥...', + rotateTray: '乾燥時旋轉料盤', + }, + // Filaments section + filaments: '耗材', + // Camera + openCameraOverlay: '開啟攝影機疊加層', + openCameraWindow: '在新視窗中開啟攝影機', + // Firmware + firmwareUpdateAvailable: '韌體更新可用:{{current}} → {{latest}}', + firmwareUpToDate: '韌體 {{version}} — 已是最新', + firmwareUpdateButton: '更新', + // Plate detection + plateDetection: { + noPermission: '您沒有更新印表機的權限', + enabledClick: '列印板檢查已啟用 - 點選停用', + disabledClick: '列印板檢查已停用 - 點選啟用', + manageCalibration: '管理列印板檢測校準', + calibrationRequired: '需要校準', + calibrationInstructions: '請確保列印板完全空置,然後點選校準。', + calibrationDescription: '校準會拍攝空置列印板的參考影像。後續檢查將與此參考進行比較以檢測物體。', + calibrationTip: '提示:您最多可以為不同的列印板儲存5個校準。系統會在檢查時自動使用最佳匹配。', + plateEmpty: '列印板似乎是空的', + objectsDetected: '在列印板上偵測到物體', + confidence: '置信度', + difference: '差異', + analysisPreview: '分析預覽:', + analysisLegend: '綠色框 = 檢測區域,紅色覆蓋 = 與校準的差異', + savedReferences: '已儲存的參考 ({{count}}/{{max}})', + deleteReference: '刪除參考', + labelPlaceholder: '標籤...', + clickToEdit: '{{label}} - 點選編輯', + clickToAddLabel: '點選新增標籤', + }, + // Speed + speed: { + title: '列印速度', + silent: '靜音 (50%)', + standard: '標準 (100%)', + sport: '運動 (124%)', + ludicrous: '瘋狂 (166%)', + }, + airduct: { + title: '風道模式', + cooling: '製冷', + heating: '加熱', + }, + noSdCard: '無SD', + door: { + open: '開', + closed: '關', + }, + // Fans + fans: { + partCooling: '零件冷卻風扇', + auxiliary: '輔助風扇', + chamber: '腔室風扇', + }, + // HMS errors + clickToViewHmsErrors: '點選檢視 HMS 錯誤', + estimatedCompletion: '預計完成時間', + plateNumber: '板 {{number}}', + slotOptions: '槽位選項', + // AMS hover popup + amsPopup: { + friendlyName: 'AMS 名稱', + friendlyNamePlaceholder: '例如 AMS 友好名稱', + serialNumber: '序列號', + firmwareVersion: '韌體', + save: '儲存', + clear: '清除', + noEditPermission: '您沒有重新命名 AMS 單元的權限', + }, + // Firmware modal + firmwareModal: { + title: '韌體更新', + titleUpToDate: '韌體資訊', + currentVersion: '目前版本:', + latestVersion: '最新版本:', + releaseNotes: '發布說明', + checkingPrereqs: '正在檢查前提條件...', + sdCardReady: 'SD 卡已就緒。點選下方上傳韌體。', + uploadedSuccess: '韌體已上傳到 SD 卡!', + applyInstructions: '在印表機上套用更新:', + step1: '在印表機觸控式螢幕上,前往設定', + step2: '導航到韌體', + step3: '選擇從 SD 卡更新', + step4: '更新將需要 10-20 分鐘', + done: '完成', + starting: '啟動中...', + uploadFirmware: '上傳韌體', + uploadFailed: '上傳啟動失敗:{{error}}', + uploadedToast: '韌體已上傳!請在印表機螢幕上觸發更新。', + availableVersions: '可用版本', + usable: '可用', + unavailable: '不可用', + installed: '已安裝', + newerBadge: '較新', + olderBadge: '較舊', + currentBadge: '目前', + }, + accessCodePlaceholder: '留空以保持目前值', + // ROI editor + roi: { + title: '檢測區域 (ROI)', + xStart: 'X 起點', + yStart: 'Y 起點', + width: '寬度', + height: '高度', + instruction: '調整檢測區域以聚焦到列印板。預覽中的綠色框顯示目前區域。', + }, + developerModeWarning: '以下印表機未啟用開發者區域網路模式:{{names}}。某些功能可能無法使用。', + howToEnable: '如何啟用', + incompatibleFile: '此檔案是為 {{slicedFor}} 切片的,但該印表機是 {{printerModel}}', + dropNotPrintable: '只能列印 .gcode 和 .gcode.3mf 檔案', + dropToPrint: '拖放以列印', + cannotPrint: '印表機忙碌', + }, + + // Archives page + archives: { + title: '列印歸檔', + searchPlaceholder: '搜尋歸檔...', + filterByPrinter: '按印表機篩選', + filterByStatus: '按狀態篩選', + sortBy: '排序方式', + sortNewest: '最新優先', + sortOldest: '最舊優先', + sortName: '名稱', + sortDuration: '時長', + sortLargest: '最大優先', + sortSmallest: '最小優先', + sortSize: '大小', + noArchives: '未找到歸檔', + noArchivesSearch: '沒有匹配搜尋的歸檔', + originalPrintNotVisible: '原始列印不可見 - 請嘗試清除篩選條件', + noArchivesYet: '尚無歸檔', + prints: '條列印', + pagination: { + showing: '顯示', + to: '至', + of: '共', + show: '每頁', + page: '頁', + all: '全部', + }, + loadingArchives: '載入歸檔中...', + releaseToUpload: '放開以上傳', + showAll: '顯示全部', + showFavoritesOnly: '僅顯示收藏', + gridView: '網格檢視', + listView: '列表檢視', + calendarView: '日曆檢視', + logView: '列印日誌', + manageTags: '管理標籤', + showFailedPrints: '顯示失敗的列印', + hideFailedPrints: '隱藏失敗的列印', + hideDuplicates: '隱藏重複項', + viewOriginalPrint: '點選檢視原始列印 (#{{id}})', + printTime: '列印時間', + filamentUsed: '耗材用量', + cost: '成本', + reprint: '重新列印', + preview: '預覽', + deleteArchive: '刪除歸檔', + deleteConfirm: '確定要刪除此歸檔嗎?', + favorite: '收藏', + unfavorite: '取消收藏', + viewDetails: '檢視詳情', + status: { + completed: '已完成', + failed: '失敗', + stopped: '已停止', + }, + toast: { + source3mfAttached: '源 3MF 已附加:{{filename}}', + failedUploadSource3mf: '上傳源 3MF 失敗', + source3mfRemoved: '源 3MF 已移除', + failedRemoveSource3mf: '移除源 3MF 失敗', + f3dAttached: 'F3D 已附加:{{filename}}', + failedUploadF3d: '上傳 F3D 失敗', + f3dRemoved: 'F3D 已移除', + failedRemoveF3d: '移除 F3D 失敗', + timelapseAttached: '縮時攝影已附加:{{filename}}', + timelapseAlreadyAttached: '縮時攝影已附加', + noMatchingTimelapse: '未找到匹配的縮時攝影', + failedScanTimelapse: '掃描縮時攝影失敗', + failedAttachTimelapse: '附加縮時攝影失敗', + timelapseRemoved: '縮時攝影已移除', + failedRemoveTimelapse: '移除縮時攝影失敗', + timelapseUploaded: '縮時攝影已上傳:{{filename}}', + failedUploadTimelapse: '上傳縮時攝影失敗', + archiveDeleted: '歸檔已刪除', + failedDeleteArchive: '刪除歸檔失敗', + addedToFavorites: '已新增到收藏', + removedFromFavorites: '已從收藏中移除', + projectUpdated: '專案已更新', + failedUpdateProject: '更新項目失敗', + linkCopied: '連結已複製到剪貼簿', + failedCopyLink: '複製連結失敗', + photoDeleted: '照片已刪除', + failedDeletePhoto: '刪除照片失敗', + failedDeleteArchives: '刪除歸檔失敗', + failedUpdateFavorites: '更新收藏失敗', + exportDownloaded: '匯出已下載', + exportFailed: '匯出失敗', + }, + menu: { + print: '列印', + schedule: '排程', + openInBambuStudio: '在切片軟體中開啟', + slice: '切片', + externalLink: '外部連結', + viewOnMakerWorld: '在 MakerWorld 上檢視', + preview3d: '3D 預覽', + viewTimelapse: '檢視縮時攝影', + scanForTimelapse: '掃描縮時攝影', + uploadTimelapse: '上傳縮時攝影', + removeTimelapse: '移除縮時攝影', + downloadSource3mf: '下載源 3MF', + uploadSource3mf: '上傳源 3MF', + replaceSource3mf: '替換源 3MF', + removeSource3mf: '移除源 3MF', + uploadF3d: '上傳 F3D', + replaceF3d: '替換 F3D', + downloadF3d: '下載 F3D', + removeF3d: '移除 F3D', + download: '下載', + copyDownloadLink: '複製下載連結', + qrCode: 'QR Code', + viewPhotos: '檢視照片', + viewPhotosCount: '檢視照片 ({{count}})', + projectPage: '專案頁面', + addToFavorites: '新增到收藏', + removeFromFavorites: '從收藏中移除', + edit: '編輯', + goToProject: '前往專案:{{name}}', + addToProject: '新增到專案', + removeFromProject: '從專案中移除', + loading: '載入中...', + noProjectsAvailable: '無可用專案', + select: '選擇', + deselect: '取消選擇', + delete: '刪除', + }, + permission: { + noReprint: '您沒有重新列印此歸檔的權限', + noAddToQueue: '您沒有新增到佇列的權限', + noUpdateArchives: '您沒有更新歸檔的權限', + noUploadFiles: '您沒有上傳檔案的權限', + noDownload: '您沒有下載歸檔的權限', + noCopyLink: '您沒有複製下載連結的權限', + noDelete: '您沒有刪除此歸檔的權限', + noCreate: '您沒有建立歸檔的權限', + }, + card: { + previousPlate: '上一個板', + nextPlate: '下一個板', + plateNumber: '板 {{index}}', + moreOptions: '右鍵檢視更多選項', + addToFavorites: '新增到收藏', + removeFromFavorites: '從收藏中移除', + cancelled: '已取消', + failed: '失敗', + duplicate: '重複', + duplicateTitle: '此模型之前已列印過', + openSource3mf: '在 Bambu Studio 中開啟源 3MF(右鍵檢視更多選項)', + downloadF3d: '下載 Fusion 360 設計檔案', + viewTimelapse: '檢視縮時攝影', + viewPhoto: '檢視 1 張照片', + viewPhotos: '檢視 {{count}} 張照片', + openFolder: '開啟資料夾:{{name}}', + slicedFile: '已切片檔案 - 可以列印', + sourceFile: '僅原始檔 - 無 AMS 對應可用', + gcode: 'GCODE', + source: '原始檔', + project: '專案:{{name}}', + estimated: '預計:{{time}}', + actual: '實際:{{time}}', + accuracy: '準確度:{{percent}}%', + filament: '{{weight}}g', + layer: '{{count}} 層', + layers: '{{count}} 層', + object: '{{count}} 個物件', + objects: '{{count}} 個物件', + slicedFor: '為 {{model}} 切片', + uploadedBy: '上傳者', + noPermissionReprint: '您沒有重新列印的權限', + noFileForReprint: '無可用的 3MF 檔案 — 列印紀錄時無法從印表機下載該檔案', + noPermissionEdit: '您沒有編輯歸檔的權限', + noPermissionDelete: '您沒有刪除歸檔的權限', + reprint: '重新列印', + schedulePrint: '排程列印', + schedule: '排程', + openInBambuStudio: '在切片軟體中開啟', + openInBambuStudioToSlice: '在切片軟體中開啟進行切片', + slice: '切片', + externalLink: '外部連結', + makerWorld: 'MakerWorld:{{designer}}', + viewProject: '檢視專案', + noExternalLink: '無外部連結', + preview3d: '3D 預覽', + download: '下載', + edit: '編輯', + delete: '刪除', + }, + modal: { + deleteArchive: '刪除歸檔', + deleteConfirm: '確定要刪除"{{name}}"嗎?此操作無法復原。', + deleteButton: '刪除', + removeSource3mf: '移除源 3MF', + removeSource3mfConfirm: '確定要從"{{name}}"中移除源 3MF 檔案嗎?這將刪除原始切片專案檔案。', + removeButton: '移除', + removeF3d: '移除 F3D', + removeF3dConfirm: '確定要從"{{name}}"中移除 Fusion 360 設計檔案嗎?', + removeTimelapse: '移除縮時攝影', + removeTimelapseConfirm: '確定要從"{{name}}"中移除縮時攝影影片嗎?', + timelapse: '{{name}} - 縮時攝影', + selectTimelapse: '選擇縮時攝影', + selectTimelapseDesc: '未找到自動匹配。請選擇此列印的縮時攝影:', + deleteArchives: '刪除歸檔', + deleteArchivesConfirm: '確定要刪除 {{count}} 個歸檔嗎?此操作無法復原。', + deleteCount: '刪除 {{count}} 個', + }, + page: { + title: '歸檔', + printsCount: '{{filtered}} / {{total}} 次列印', + dropFilesHere: '將 .3mf 檔案拖放到此處', + releaseToUpload: '放開以上傳', + only3mfSupported: '僅支援 .3mf 檔案', + close: '關閉', + selected: '已選擇 {{count}} 個', + selectAll: '全選', + tags: '標籤', + project: '專案', + favorite: '收藏', + delete: '刪除', + toggledFavorites: '已切換 {{count}} 個歸檔的收藏狀態', + failedUpdateFavorites: '更新收藏失敗', + archivesDeleted: '已刪除 {{count}} 個歸檔', + failedDeleteArchives: '刪除歸檔失敗', + photoDeleted: '照片已刪除', + failedDeletePhoto: '刪除照片失敗', + }, + list: { + name: '名稱', + printer: '印表機', + date: '日期', + size: '大小', + actions: '操作', + hasTimelapse: '有縮時攝影', + }, + log: { + date: '日期', + printName: '列印名稱', + printer: '印表機', + user: '使用者', + status: '狀態', + duration: '時長', + filament: '耗材', + allPrinters: '所有印表機', + allUsers: '所有使用者', + allStatuses: '所有狀態', + cancelled: '已取消', + skipped: '已跳過', + dateFrom: '從', + dateTo: '到', + noEntries: '未找到列印日誌條目', + showing: '顯示 {{count}} / {{total}} 條', + rowsPerPage: '行數', + page: '頁', + prev: '上一頁', + next: '下一頁', + clearLog: '清除日誌', + clearLogTitle: '清除列印日誌', + clearLogConfirm: '所有列印日誌條目將被永久刪除。歸檔和佇列項目不受影響。此操作無法復原。確定要繼續嗎?', + clearLogButton: '全部清除', + cleared: '已清除 {{count}} 條日誌', + clearFailed: '清除列印日誌失敗', + }, + }, + + // Queue page + queue: { + title: '列印佇列', + subtitle: '排程和管理您的列印任務', + addToQueue: '新增到佇列', + // Print modal + print: '列印', + reprint: '重新列印', + schedulePrint: '排程列印', + editQueueItem: '編輯佇列項目', + printToPrinters: '列印到 {{count}} 臺印表機', + queueToPrinters: '佇列到 {{count}} 臺印表機', + queueSelectedPlates: '將 {{count}} 個熱床加入佇列', + selectAllPlates: '選擇全部 {{count}} 個熱床', + deselectAll: '取消全選', + printQueued: '已加入列印佇列', + itemsQueued: '{{count}} 個任務已加入佇列', + sending: '傳送中...', + sendingProgress: '傳送中 {{current}}/{{total}}...', + adding: '新增中...', + addingProgress: '新增中 {{current}}/{{total}}...', + savingProgress: '儲存中 {{current}}/{{total}}...', + clearQueue: '清空佇列', + clearHistory: '清除歷史', + emptyQueue: '佇列為空', + position: '位置', + scheduledTime: '排程時間', + moveUp: '上移', + moveDown: '下移', + startNow: '立即開始', + printingInProgress: '列印進行中...', + viewArchive: '檢視歸檔', + viewInFileManager: '在檔案管理器中檢視', + itemCount: '{{count}} 個項目', + itemCount_plural: '{{count}} 個項目', + dragToReorder: '拖曳以重新排序(僅限盡快)', + reorderHint: '位置僅影響"儘快"項目。排程項目按設定時間執行。', + sjf: { + label: 'SJF', + tooltip: '最短任務優先 — 排程器優先處理較短的列印任務', + }, + addedBy: '由 {{name}} 新增', + nextInQueue: '佇列中的下一個', + clearPlate: '清理列印板並開始下一個', + clearPlateSuccess: '列印板已清理 — 準備進行下一個列印', + plateReady: '列印板已清理 — 準備進行下一個列印', + plateNumber: '板 {{index}}', + // Batch / quantity + quantity: '數量', + quantityHint: '建立 {{count}} 個佇列項目', + activeBatches: '活躍批次', + batchProgress: '已完成 {{completed}}/{{total}}', + cancelBatch: '取消剩餘', + batchCancelled: '已取消剩餘批次項目', + cancelBatchConfirmTitle: '取消批次', + cancelBatchConfirmMessage: '取消此批次中所有剩餘的待處理項目?', + batch: '批次', + // Sections + sections: { + currentlyPrinting: '正在列印', + queued: '佇列中', + history: '歷史', + }, + // Status + status: { + pending: '等待中', + waiting: '等待中', + printing: '列印中', + paused: '已暫停', + completed: '已完成', + failed: '失敗', + skipped: '已跳過', + cancelled: '已取消', + }, + // Summary cards + summary: { + printing: '列印中', + queued: '佇列中', + totalTime: '總佇列時間', + totalWeight: '總佇列重量', + history: '歷史', + }, + // Filters + filter: { + allPrinters: '所有印表機', + unassigned: '未分配', + allStatus: '所有狀態', + allLocations: '所有位置', + any: '任意', + }, + // Sort + sort: { + byPosition: '按位置排序', + byName: '按名稱排序', + byPrinter: '按印表機排序', + bySchedule: '按排程排序', + byDate: '按日期排序', + ascendingOldest: '升序(最舊優先)', + descendingNewest: '降序(最新優先)', + }, + // Badges + badges: { + staged: '已暫存', + requiresPrevious: '需要前一個成功', + autoPowerOff: '自動關機', + gcodeInjection: 'G-code', + }, + // Empty state + empty: { + title: '沒有排程的列印', + description: '從歸檔頁面使用右鍵選單中的"排程"選項來排程列印,或拖放檔案開始。', + }, + // Time + time: { + asap: '儘快', + overdue: '已逾期', + now: '現在', + lessThanMinute: '不到一分鐘', + inMinutes: '{{count}} 分鐘後', + inHours: '{{count}} 小時後', + }, + // Actions + actions: { + stopPrint: '停止列印', + startPrint: '開始列印', + requeue: '重新佇列', + }, + // Bulk edit + bulkEdit: { + title: '編輯 {{count}} 個項目', + title_plural: '編輯 {{count}} 個項目', + description: '僅更改的設定將套用於所選項目。', + printer: '印表機', + noChange: '— 不更改 —', + queueOptions: '佇列選項', + staged: '暫存(手動開始)', + autoPowerOff: '列印後自動關機', + requirePrevious: '要求前一個成功', + printOptions: '列印選項', + bedLevelling: '熱床調平', + flowCalibration: '流量校準', + vibrationCalibration: '振動校準', + layerInspection: '首層檢查', + timelapse: '縮時攝影', + useAms: '使用 AMS', + applyChanges: '套用更改', + selectAll: '全選', + deselectAll: '取消全選', + selected: '已選擇 {{count}} 個', + editSelected: '編輯所選', + cancelSelected: '取消所選', + }, + // Confirmations + confirm: { + cancelTitle: '取消排程列印', + cancelMessage: '確定要取消"{{name}}"嗎?', + stopTitle: '停止列印', + stopMessage: '確定要停止目前列印"{{name}}"嗎?這將取消印表機上的列印任務。', + removeTitle: '從歷史中移除', + removeMessage: '確定要從佇列歷史中移除"{{name}}"嗎?', + clearHistoryTitle: '清除歷史', + clearHistoryMessage: '確定要從歷史中移除所有 {{count}} 個項目嗎?', + cancelButton: '取消列印', + stopButton: '停止列印', + thisPrint: '此列印', + thisItem: '此項目', + }, + // Toast messages + toast: { + cancelled: '佇列項目已取消', + cancelFailed: '取消項目失敗', + removed: '佇列項目已移除', + removeFailed: '移除項目失敗', + stopped: '列印已停止', + stopFailed: '停止列印失敗', + released: '列印已加入佇列', + startFailed: '開始列印失敗', + reorderFailed: '重新排序佇列失敗', + historyCleared: '已清除 {{count}} 條歷史紀錄', + clearHistoryFailed: '清除歷史失敗', + updateFailed: '更新項目失敗', + bulkCancelled: '已取消 {{count}} 個項目', + bulkCancelFailed: '批次取消項目失敗', + }, + // Timeline view + timeline: { + listView: '列表', + timelineView: '時間線', + unassigned: '未分配', + noData: '當天沒有計畫的列印任務', + allDoneBy: '所有列印預計在 {{time}} 前完成', + staged: '暫存', + filterAll: '全部顯示', + filterPrinting: '列印中', + filterQueued: '佇列中', + time: { + anyMoment: '即將完成', + minutesLeft: '剩餘{{minutes}} 分鐘', + hoursLeft: '剩餘{{hours}}小時', + hoursMinutesLeft: '剩餘{{hours}}小時{{minutes}} 分鐘', + }, + day: { + previous: '前一天', + next: '後一天', + today: '今天', + }, + }, + // Permissions + permissions: { + noStopPrint: '您沒有停止列印的權限', + noStartPrint: '您沒有開始列印的權限', + noEdit: '您沒有編輯此佇列項目的權限', + noCancel: '您沒有取消此佇列項目的權限', + noRequeue: '您沒有重新佇列的權限', + noRemove: '您沒有移除此佇列項目的權限', + noClearHistory: '您沒有清除所有歷史的權限', + noEditItems: '您沒有編輯佇列項目的權限', + noCancelItems: '您沒有取消佇列項目的權限', + }, + }, + + backgroundDispatch: { + unknownFile: '未知檔案', + unknownPrinter: '未知印表機', + startingPrints: '正在開始列印', + progressSummary: '{{complete}}/{{total}} 完成 • 已分發:{{dispatched}} • 處理中:{{processing}}', + expandDetails: '展開分發詳情', + collapseDetails: '收起分發詳情', + dismissToast: '關閉分發通知', + cancelDispatchJob: '取消分發任務', + cancel: '取消', + cancelling: '取消中…', + status: { + dispatched: '已分發', + processing: '處理中', + completed: '已完成', + failed: '失敗', + cancelled: '已取消', + }, + toast: { + cancellingUpload: '取消上傳中...', + cancelled: '分發已取消', + cancelFailed: '取消分發失敗', + completeWithFailures: '後台分發完成:{{completed}} 成功,{{failed}} 失敗', + completeSuccess: '後台分發完成:{{completed}} 成功', + printStartedRemaining: '{{completed}} 個列印已開始,{{remaining}} 個正在傳送...', + }, + }, + + // Statistics page + stats: { + title: '儀表板', + subtitle: '拖曳小工具以重新排列。點選眼睛圖示隱藏。', + overview: '概覽', + totalPrints: '總列印次數', + successRate: '成功率', + totalPrintTime: '總列印時間', + printTime: '列印時間', + totalFilament: '總耗材用量', + filamentUsed: '耗材用量', + filamentCost: '耗材成本', + totalCost: '總成本', + energyUsed: '能耗', + energyCost: '能源成本', + energyWarmingUpTooltip: '能耗追蹤正在收集每小時快照。當所選範圍之前至少存在一個快照時,時間段合計將變得準確。早期數值可能偏低。', + averagePrintTime: '平均列印時間', + printsPerDay: '每日列印次數', + byPrinter: '按印表機', + printsByPrinter: '各印表機列印次數', + byMaterial: '按材料', + byMonth: '按月份', + last7Days: '最近 7 天', + last30Days: '最近 30 天', + last90Days: '最近 90 天', + allTime: '全部時間', + // Widgets + quickStats: '快速統計', + printActivity: '列印活動', + filamentTypes: '耗材類型', + filamentTrends: '耗材趨勢', + failureAnalysis: '失敗分析', + timeAccuracy: '時間準確度', + successful: '成功:', + failed: '失敗:', + perfectEstimate: '100% = 完美估計', + noTimeAccuracyData: '尚無時間準確度資料', + noFilamentData: '尚無耗材資料', + noPrinterData: '尚無印表機資料', + noPrintData: '尚無列印資料', + noPrintDataLast30Days: '最近 30 天無列印資料', + failureReasons: '失敗原因', + topFailureReasons: '主要失敗原因', + failedPrintsCount: '{{failed}} / {{total}} 次列印失敗', + lastWeekRate: '上週:{{rate}}%', + // Actions + resetLayout: '重設佈局', + recalculateCosts: '重新計算成本', + recalculateCostsHint: '使用目前耗材價格重新計算所有歸檔成本', + exportStats: '匯出統計', + exportAsCsv: '匯出為 CSV', + exportAsExcel: '匯出為 Excel', + hiddenCount: '{{count}} 個已隱藏', + // Toast + exportDownloaded: '匯出已下載', + exportFailed: '匯出失敗', + layoutReset: '佈局已重設', + recalculatedCosts: '已為 {{count}} 個歸檔重新計算成本', + recalculateFailed: '重新計算成本失敗', + // Loading + loadingStats: '載入統計資料中...', + // Permissions + noPermissionResetLayout: '您沒有重設佈局的權限', + noPermissionRecalculate: '您沒有重新計算成本的權限', + noPrintDataInRange: '所選範圍內無列印資料', + periodFilament: '期間耗材', + periodCost: '期間成本', + avgPerPrint: '每次列印平均', + usageOverTime: '隨時間的使用量', + filamentByWeight: '重量', + printDuration: '列印時長', + printerUtilization: '印表機利用率', + filamentSuccess: '按材料成功率', + printHabits: '列印習慣', + printTimeOfDay: '列印時段', + colorDistribution: '顏色分佈', + noColorData: '尚無顏色資料', + records: '紀錄', + longestPrint: '最長列印', + heaviestPrint: '最重列印', + mostExpensivePrint: '最貴列印', + busiestDay: '最忙碌的一天', + successStreak: '連續成功', + streakPrint: '連續列印', + streakPrints: '{{count}} 次連續列印', + printerStats: '印表機統計', + hours: '小時', + avgPrints: '平均列印', + noArchiveData: '尚無列印資料', + filamentByTime: '時間', + avgWeight: '平均重量', + avgTime: '平均時間', + filamentByPrints: '列印次數', + timeframe: { + today: '今天', + 'this-week': '本週', + 'this-month': '本月', + 'last-7': '最近 7 天', + 'last-30': '最近 30 天', + 'last-90': '最近 90 天', + 'this-year': '今年', + 'all-time': '全部時間', + custom: '自訂範圍', + from: '從', + to: '到', + }, + allUsers: '所有使用者', + noUser: '無使用者(系統)', + filterByUser: '按使用者篩選', + }, + + // Maintenance page + maintenance: { + title: '維護', + overview: '概覽', + allOk: '所有維護均已完成', + dueCount: '{{count}} 項到期', + dueCount_plural: '{{count}} 項到期', + warningCount: '{{count}} 個警告', + warningCount_plural: '{{count}} 個警告', + totalPrintTime: '總列印時間', + nextMaintenance: '下次維護', + nothingDue: '無到期項目', + tasks: '任務', + lastPerformed: '上次執行', + interval: '間隔', + hoursRemaining: '剩餘 {{hours}} 小時', + hoursOverdue: '逾期 {{hours}} 小時', + markDone: '標記為完成', + performMaintenance: '執行維護', + history: '歷史', + noHistory: '無維護歷史', + editPrintHours: '編輯列印時間', + currentHours: '目前小時數', + // Tabs + statusTab: '狀態', + settingsTab: '設定', + // Status + overdueCount: '{{count}} 個逾期', + dueSoonCount: '{{count}} 個即將到期', + dueSoon: '即將到期', + allGood: '一切正常', + overdueBy: '逾期 {{duration}}', + dueIn: '{{duration}} 後到期', + timeLeft: '剩餘 {{duration}}', + // Duration formats + day: '1 天', + days: '{{count}} 天', + week: '1 週', + weeks: '{{count}} 週', + month: '1 個月', + months: '{{count}} 個月', + year: '1 年', + // Settings + maintenanceTypes: '維護類型', + maintenanceTypesDescription: '系統類型和您的自訂維護任務', + addCustomType: '新增自訂類型', + restoreDefaults: '恢復預設任務', + intervalType: '間隔類型', + intervalValue: '間隔 ({{type}})', + icon: '圖示', + documentationLink: '說明文件連結(可選)', + assignToPrinters: '分配給印表機', + selectAtLeastOnePrinter: '至少選擇一臺印表機', + addType: '新增類型', + custom: '自訂', + printHours: '列印小時數', + calendarDays: '日曆天數', + exampleName: '例如:更換 HEPA 過濾器', + viewDocumentation: '檢視說明文件', + timeBasedInterval: '基於時間的間隔', + // Interval overrides + intervalOverrides: '間隔覆蓋', + intervalOverridesDescription: '為特定印表機自訂間隔', + // Printer assignment + assignedToPrinters: '已分配給印表機:', + noPrintersAssigned: '未分配印表機', + addPrinterShort: '新增:', + printersAssignedClick: '已分配 {{count}} 臺印表機 - 點選管理', + removeFromPrinter: '從此印表機移除', + // Types + types: { + lubricateCarbonRods: '潤滑碳纖維杆', + lubricateRails: '潤滑線性導軌', + cleanNozzle: '清潔噴嘴/熱端', + checkBelts: '檢查皮帶張力', + cleanBuildPlate: '清潔列印板', + checkExtruder: '檢查擠出機齒輪', + checkCooling: '檢查冷卻風扇', + generalInspection: '綜合檢查', + cleanCarbonRods: '清潔碳纖維杆', + lubricateSteelRods: '潤滑鋼杆', + cleanSteelRods: '清潔鋼杆', + cleanLinearRails: '清潔線性導軌', + checkPtfeTube: '檢查 PTFE 管', + replaceHepaFilter: '更換 HEPA 過濾器', + replaceCarbonFilter: '更換活性炭過濾器', + lubricateLeftNozzleRail: '潤滑左噴嘴導軌', + }, + // Toast + maintenanceComplete: '維護已標記為完成', + typeUpdated: '維護類型已更新', + typeDeleted: '維護類型已刪除', + defaultsRestored: '已恢復 {{count}} 個預設任務', + printHoursUpdated: '列印小時數已更新', + printerAssigned: '印表機已分配', + printerRemoved: '印表機已移除', + // Confirmation + deleteTypeConfirm: '刪除"{{name}}"?', + deleteSystemTypeTitle: '刪除預設維護任務?', + deleteSystemTypeMessage: '確定要刪除預設維護任務"{{name}}"嗎?', + // Permissions + noPermissionUpdate: '您沒有更新維護項目的權限', + noPermissionPerform: '您沒有執行維護的權限', + noPermissionEditTypes: '您沒有編輯維護類型的權限', + noPermissionDeleteTypes: '您沒有刪除維護類型的權限', + noPermissionEditHours: '您沒有編輯列印時間的權限', + noPermissionRemovePrinter: '您沒有移除印表機分配的權限', + noPermissionAssignPrinter: '您沒有分配印表機的權限', + noPermissionEditIntervals: '您沒有編輯間隔的權限', + // Configure link + configureSettings: '設定維護類型和間隔', + }, + + // Settings page + settings: { + title: '設定', + general: '通用', + // Tab names + tabs: { + general: '通用', + smartPlugs: '智慧插座', + notifications: '通知', + queue: '工作流程', + filament: '耗材', + network: '網路', + apiKeys: 'API 金鑰', + virtualPrinter: '虛擬印表機', + spoolbuddy: 'SpoolBuddy', + failureDetection: '故障檢測', + users: '身份驗證', + backup: '備份', + emailAuth: '信箱認證', + ldap: 'LDAP', + twoFa: '雙因素認證', + oidc: 'SSO / OIDC', + }, + spoolbuddy: { + infoTitle: 'SpoolBuddy 裝置', + infoBody: 'SpoolBuddy kiosk 透過心跳自動註冊。如果裝置不再使用,或守護程式當機遺留了過時的重複項,可在此取消註冊。', + duplicatesTitle: '已註冊 {{count}} 台裝置', + duplicatesBody: 'kiosk 介面只使用最先註冊的裝置。如果其中有因當機遺留的過時重複項,請取消註冊它——線上裝置會在下次心跳時重新註冊自己。', + empty: '尚未註冊任何 SpoolBuddy 裝置。', + online: '線上', + offline: '離線', + unregister: '取消註冊', + unregisterSuccess: '裝置已取消註冊', + unregisterError: '取消註冊裝置失敗', + confirmTitle: '取消註冊 SpoolBuddy 裝置?', + confirmBody: '將從資料庫中移除 "{{hostname}}" ({{deviceId}})。如果裝置線上,會在下次心跳時重新註冊自己。', + ipAddress: 'IP 位址', + firmware: '韌體', + lastSeen: '上次線上', + daemonUptime: '守護程式執行時間', + systemUptime: '系統執行時間', + never: '從未', + nfc: 'NFC', + scale: '磅秤', + cpuTemp: 'CPU 溫度', + memory: '記憶體', + disk: '磁碟', + // Device actions + update: '更新', + updateConfirmTitle: '更新 SpoolBuddy 守護程式?', + updateConfirmBody: '對 "{{hostname}}" 觸發軟體更新?更新完成後守護程式將重新啟動。', + restartBrowser: '重新啟動瀏覽器', + restartBrowserConfirmTitle: '重新啟動 kiosk 瀏覽器?', + restartBrowserConfirmBody: '在 "{{hostname}}" 上重新啟動 kiosk 瀏覽器?顯示將短暫黑屏。', + restartDaemon: '重新啟動守護程式', + restartDaemonConfirmTitle: '重新啟動 SpoolBuddy 守護程式?', + restartDaemonConfirmBody: '在 "{{hostname}}" 上重新啟動 SpoolBuddy 守護程式?裝置將離線幾秒鐘。', + reboot: '重新開機', + rebootConfirmTitle: '重新開機?', + rebootConfirmBody: '重新開機 "{{hostname}}"?裝置將離線約一分鐘。', + shutdown: '關機', + shutdownConfirmTitle: '關閉裝置?', + shutdownConfirmBody: '關閉 "{{hostname}}"?您需要實體存取才能重新開機。', + commandConfirm: '確認', + commandQueued: '命令已加入佇列', + commandError: '傳送命令失敗', + }, + ldap: { + title: 'LDAP 認證', + enabledDesc: 'LDAP 認證已啟用', + disabledDesc: 'LDAP 認證已停用', + disabledHint: '在下方設定並儲存 LDAP 設定,然後啟用。', + enabled: 'LDAP 認證已啟用', + disabled: 'LDAP 認證已停用', + feature1: '使用者可以使用 LDAP 憑據登入', + feature2: '本機管理員帳戶作為後備保留', + feature3: '登入時 LDAP 群組對應到 BamBuddy 群組', + serverConfig: 'LDAP 伺服器設定', + serverUrl: '伺服器 URL', + serverUrlHint: '使用 ldap:// 進行標準連線或 ldaps:// 進行 SSL 連線', + security: '安全', + securityHint: 'StartTLS 將普通連線升級為 TLS。LDAPS 從一開始就使用 TLS。', + bindDn: '繫結 DN(服務帳戶)', + bindPassword: '繫結密碼', + searchBase: '搜尋基礎 DN', + userFilter: '使用者搜尋過濾器', + userFilterHint: '{username} 替換為登入使用者名稱。OpenLDAP 使用 (uid={username})。', + autoProvision: '自動建立使用者', + autoProvisionHint: '首次 LDAP 登入時自動建立 BamBuddy 帳戶', + defaultGroup: '預設群組', + defaultGroupNone: '— 無(無復原)—', + defaultGroupHint: '當 LDAP 使用者透過身份驗證但不在任何已對應的 LDAP 群組中時分配的備援群組。留空以使未對應的使用者沒有權限。', + groupMapping: '群組對應(JSON)', + groupMappingHint: '將 LDAP 群組 DN 對應到 BamBuddy 群組。可用群組:', + testConnection: '測試連線', + settingsSaved: 'LDAP 設定已儲存', + errors: { + serverRequired: 'LDAP 伺服器 URL 為必填項', + searchBaseRequired: '搜尋基礎 DN 為必填項', + enableAuthFirst: '請先啟用認證', + configureLdapFirst: '請先儲存 LDAP 設定', + }, + }, + // Email settings + email: { + smtpSettings: 'SMTP 設定', + smtpHost: 'SMTP 伺服器', + smtpPort: 'SMTP 連接埠', + security: '安全', + authentication: '認證', + username: '使用者名稱', + password: '密碼', + fromEmail: '寄件信箱', + fromName: '寄件人名稱', + testConnection: '測試 SMTP 連線', + testRecipient: '測試收件信箱', + sendTest: '傳送測試郵件', + sending: '傳送中...', + save: '儲存設定', + saving: '儲存中...', + advancedAuth: '進階認證', + advancedAuthEnabled: '進階認證已啟用', + advancedAuthEnabledDesc: '基於信箱的使用者管理功能已啟用。新使用者將透過郵件收到自動產生的密碼,使用者可以透過忘記密碼功能重設密碼。', + advancedAuthDisabled: '進階認證已停用', + advancedAuthDisabledDesc: '啟用進階認證以啟用基於信箱的使用者管理功能。', + enable: '啟用', + disable: '停用', + feature1: '密碼自動產生並透過郵件傳送給新使用者', + feature2: '使用者可以使用使用者名稱或信箱登入', + feature3: '忘記密碼功能可用', + feature4: '管理員可以透過郵件重設使用者密碼', + // Error messages + errors: { + requiredFields: '請填寫所有必填欄位', + usernameRequired: '啟用認證時需要使用者名稱', + enterTestEmail: '請輸入測試信箱地址', + smtpServerAndEmail: '測試前請填寫 SMTP 伺服器和寄件信箱', + usernamePasswordRequired: '啟用認證時需要使用者名稱和密碼', + configureSmtpFirst: '請先設定並測試 SMTP 設定', + enableAuthFirst: '請先啟用身份驗證才能使用基於電子郵件的功能。', + }, + // Success messages + success: { + settingsSaved: 'SMTP 設定儲存成功', + }, + // Security options + securityOptions: { + starttls: 'STARTTLS(連接埠 587)', + ssl: 'SSL/TLS(連接埠 465)', + none: '無(連接埠 25)', + }, + // Authentication options + authOptions: { + enabled: '已啟用', + disabled: '已停用', + }, + }, + appearance: '外觀', + notifications: '通知', + smartPlugs: '智慧插座', + spoolman: 'Spoolman', + updates: '更新', + language: '語言', + languageDescription: '選擇您的首選語言', + theme: '主題', + themeLight: '淺色', + themeDark: '深色', + themeSystem: '跟隨系統', + defaultView: '預設檢視', + defaultViewDescription: '開啟應用程式時顯示的頁面', + checkForUpdates: '檢查更新', + autoUpdate: '自動更新', + currentVersion: '目前版本', + latestVersion: '最新版本', + upToDate: '已是最新版本', + updateAvailable: '有可用更新', + // Notifications + notificationLanguage: '通知語言', + notificationLanguageDescription: '推送通知的語言', + bedCooledThreshold: '熱床冷卻閾值', + bedCooledThresholdDescription: '列印後熱床被視為已冷卻的溫度', + userNotificationsEnabled: '使用者通知', + userNotificationsEnabledDescription: '啟用使用者通知選單和列印任務事件的郵件通知。需要進階身份驗證。', + userNotificationsDisabledHint: '請啟用進階身份驗證以使用使用者通知。', + notificationProviders: '通知提供者', + addProvider: '新增提供者', + editProvider: '編輯提供者', + providerType: '提供者類型', + testNotification: '測試通知', + testSuccess: '測試通知傳送成功', + testFailed: '傳送測試通知失敗', + quietHours: '免打擾時間', + quietHoursDescription: '在此時間段內不傳送通知', + quietHoursStart: '開始', + quietHoursEnd: '結束', + events: { + title: '通知事件', + printStart: '列印開始', + printComplete: '列印完成', + printFailed: '列印失敗', + printStopped: '列印停止', + printProgress: '進度里程碑', + printProgressDescription: '在 25%、50%、75% 時通知', + printerOffline: '印表機離線', + printerError: '印表機錯誤', + filamentLow: '耗材不足', + maintenanceDue: '維護到期', + maintenanceDueDescription: '需要維護時通知', + }, + // Smart Plugs + smartPlug: { + title: '智慧插座', + add: '新增智慧插座', + edit: '編輯智慧插座', + name: '名稱', + ipAddress: 'IP 位址', + linkedPrinter: '連結印表機', + autoOn: '自動開啟', + autoOnDescription: '列印開始時開啟', + autoOff: '自動關閉', + autoOffDescription: '列印完成後關閉', + offDelay: '關閉延遲', + offDelayMinutes: '列印後分鐘數', + offDelayTemp: '當噴嘴溫度低於', + currentState: '目前狀態', + turnOn: '開啟', + turnOff: '關閉', + }, + // Filament Tracking Mode + filamentTracking: '耗材追蹤', + filamentTrackingDesc: '選擇如何追蹤您的耗材。您可以使用內建庫存或連線外部 Spoolman 伺服器。', + filamentChecks: '耗材檢查', + disableFilamentWarnings: '停用耗材警告', + disableFilamentWarningsDesc: '在列印或加入佇列時不顯示耗材不足警告', + preferLowestFilament: '優先使用剩餘最少的耗材', + preferLowestFilamentDesc: '當多個料盤匹配時,使用剩餘耗材最少的那個', + trackingModeBuiltIn: '內建庫存', + trackingModeBuiltInDesc: '包含 RFID 自動匹配和用量追蹤', + trackingModeSpoolmanDesc: '外部耗材管理伺服器', + builtInFeatureRfid: '自動檢測 AMS 中的拓竹 RFID 耗材', + builtInFeatureUsage: '追蹤每次列印的耗材消耗', + builtInFeatureCatalog: '管理耗材、顏色和 K 值設定檔案', + builtInFeatureThirdParty: '第三方耗材可分配到庫存耗材', + amsSyncButton: '從 AMS 同步重量', + amsSyncTitle: '從 AMS 同步耗材重量', + amsSyncMessage: '這將使用已連線印表機的目前 AMS 剩餘百分比值覆蓋所有庫存耗材重量。用於從損壞的重量資料中恢復。印表機必須線上。', + amsSyncing: '同步中...', + amsSyncSuccess: '已同步 {{synced}} 個耗材,跳過 {{skipped}} 個', + amsSyncError: '從 AMS 同步重量失敗', + // Spoolman settings + spoolmanUrl: 'Spoolman URL', + spoolmanUrlHint: 'Spoolman 伺服器的 URL(例如 http://localhost:7912)', + spoolmanConnected: '已連線', + spoolmanDisconnected: '未連線', + status: '狀態', + connect: '連線', + disconnect: '斷開', + howSyncWorks: '同步工作原理', + syncInfoRfidOnly: '僅同步帶有 RFID 的官方拓竹耗材', + syncInfoAutoCreate: '首次同步時自動在 Spoolman 中建立新耗材', + syncInfoThirdPartySkipped: '非拓竹耗材(第三方、重新填充的)將被跳過', + linkingExistingSpools: '連結現有耗材', + linkingExistingSpoolsDesc: '要將現有的 Spoolman 耗材連結到您的 AMS,請將滑鼠懸停在 AMS 槽位上並點選"連結到 Spoolman"。', + syncMode: '同步模式', + syncModeAuto: '自動', + syncModeManual: '僅手動', + syncModeAutoDesc: '偵測到更改時自動同步 AMS 資料', + syncModeManualDesc: '僅在手動觸發時同步', + syncAmsData: '同步 AMS 資料', + syncAmsDataDesc: '手動將印表機 AMS 資料同步到 Spoolman', + allPrinters: '所有印表機', + // Default printer + noDefaultPrinter: '無預設(每次詢問)', + // Sidebar + sidebarOrder: '側邊欄順序', + // Camera + saveThumbnails: '儲存縮圖', + captureFinishPhoto: '拍攝完成照片', + noPrintersConfigured: '未設定印表機', + // Archive settings + archiveMode: { + always: '始終建立歸檔條目', + never: '從不建立歸檔條目', + ask: '每次詢問', + }, + // Updates + checkForUpdatesLabel: '檢查更新', + checkPrinterFirmware: '檢查印表機韌體', + includeBetaUpdates: '包含測試版本', + includeBetaUpdatesDesc: '檢查更新時通知測試版和預發布版本', + // Queue + enableRetry: '啟用重試', + // Home Assistant + homeAssistantDescription: '透過 Home Assistant 控制智慧插座', + environmentManagedLabel: '(環境變數管理)', + autoEnabledViaEnv: '透過環境變數自動啟用', + urlFromEnvReadOnly: '值由 HA_URL 環境變數設定(只讀)', + tokenFromEnvReadOnly: '值由 HA_TOKEN 環境變數設定(只讀)', + // MQTT + mqttConnectedTo: '已連線到', + // Prometheus + prometheusDescription: '以 Prometheus 格式暴露印表機資料', + // Smart plugs empty state + noSmartPlugsTitle: '未設定智慧插座', + noSmartPlugsDescription: '新增基於 Tasmota 的智慧插座以追蹤能耗並自動化電源控制。', + // Notifications empty state + noProvidersTitle: '未設定提供者', + noProvidersDescription: '新增提供者以接收警報。', + noTemplatesAvailable: '無可用範本。重新啟動後端以載入預設範本。', + // API permissions + apiPermissionView: '檢視印表機狀態和佇列', + apiPermissionEdit: '新增和移除列印佇列中的項目', + // API keys + apiKeysEmptyTitle: '無 API 金鑰', + apiKeysEmptyDescription: '建立 API 金鑰以與外部服務整合。', + // Users + noUsersFound: '未找到使用者', + noGroupsFound: '未找到群組', + noGroupsAvailable: '無可用群組', + passwordsDoNotMatch: '密碼不符', + systemGroupWarning: '系統群組名稱不可更改', + // Auth disabled + authDisabledTitle: '身份驗證已停用', + authDisabledFeature1: '需要登入才能存取系統', + authDisabledFeature2: '建立多個使用者並基於群組的權限管理', + authDisabledFeature3: '使用 50+ 個細粒度權限控制存取', + // User deletion + userHasCreated: '此使用者已建立:', + userItemsQuestion: '您想如何處理這些項目?', + deleteUserConfirm: '確定要刪除此使用者嗎?', + actionCannotBeUndone: '此操作無法復原。', + // Smart plugs + addFirstSmartPlug: '新增您的第一個智慧插座', + // Notifications + providers: '提供者', + log: '日誌', + testAll: '全部測試', + testResults: '測試結果', + testPassedCount: '{{count}} 個透過', + testFailedCount: '{{count}} 個失敗', + messageTemplates: '訊息範本', + messageTemplatesDescription: '自訂每個事件的通知訊息。', + // API Keys section + apiKeys: 'API 金鑰', + apiKeysDescription: '建立 API 金鑰用於外部整合和 Webhook。', + createKey: '建立金鑰', + apiKeyCreated: 'API 金鑰建立成功', + apiKeyCopyWarning: '請立即複製此金鑰 - 它不會再次顯示!', + useInApiBrowser: '在 API 瀏覽器中使用', + createNewApiKey: '建立新 API 金鑰', + keyName: '金鑰名稱', + keyNamePlaceholder: '例如:Home Assistant、OctoPrint', + readStatus: '讀取狀態', + readStatusDescription: '檢視印表機狀態和佇列', + manageQueue: '管理佇列', + manageQueueDescription: '新增和移除列印佇列中的項目', + controlPrinter: '控制印表機', + controlPrinterDescription: '暫停、繼續和停止列印', + unnamedKey: '未命名金鑰', + lastUsed: '上次使用', + read: '讀取', + control: '控制', + createFirstKey: '建立您的第一個金鑰', + webhookEndpoints: 'Webhook 端點', + webhookApiKeyHint: '在 X-API-Key 請求頭中使用您的 API 金鑰。', + webhook: { + getAllStatus: '獲取所有印表機狀態', + getSpecificStatus: '獲取特定印表機狀態', + addToQueue: '新增到列印佇列', + pausePrint: '暫停列印', + resumePrint: '繼續列印', + stopPrint: '停止列印', + }, + apiBrowser: 'API 瀏覽器', + apiBrowserDescription: '瀏覽和測試所有可用的 API 端點。', + apiKeyForTesting: '測試用 API 金鑰', + apiKeyPlaceholder: '在此貼上您的 API 金鑰以測試需要認證的端點...', + apiKeyHint: '此金鑰將作為 X-API-Key 請求頭隨請求傳送。', + deleteApiKeyTitle: '刪除 API 金鑰', + deleteApiKeyMessage: '確定要刪除此 API 金鑰嗎?使用此金鑰的所有整合將停止工作。', + deleteKey: '刪除金鑰', + // Filament tab + amsDisplayThresholds: 'AMS 顯示閾值', + amsThresholdsDescription: '設定 AMS 濕度和溫度指示器的顏色閾值。', + humidity: '濕度', + goodGreen: '良好(綠色)', + fairOrange: '一般(橙色)', + aboveFairBad: '超過一般閾值顯示為紅色(差)', + fairAlsoDryingThreshold: '此閾值也用於觸發自動乾燥', + temperature: '溫度', + goodBlue: '良好(藍色)', + aboveFairHot: '超過一般閾值顯示為紅色(熱)', + historyRetention: '歷史保留', + keepSensorHistory: '保留感測器歷史', + historyRetentionDescription: '較舊的濕度和溫度資料將被自動刪除', + defaultPrintOptions: '預設列印選項', + defaultPrintOptionsDescription: '設定新列印的預設選項值。可在列印對話方塊中逐次覆蓋。', + defaultBedLevelling: '熱床調平', + defaultBedLevellingDesc: '列印前自動調平熱床', + defaultFlowCali: '流量校準', + defaultFlowCaliDesc: '校準擠出流量', + defaultVibrationCali: '振動校準', + defaultVibrationCaliDesc: '減少振紋偽影', + defaultLayerInspect: '首層檢測', + defaultLayerInspectDesc: 'AI首層檢測', + defaultTimelapse: '縮時攝影', + defaultTimelapseDesc: '錄製縮時攝影影片', + staggeredStart: '錯開啟動', + staggeredStartDescription: '多台印表機批次啟動時的預設群組大小與間隔。可在列印對話框中逐批覆寫。', + plateClear: '熱床清空確認', + requirePlateClear: '需要熱床清空確認', + requirePlateClearDescription: '啟用後,排程器會在已完成列印的印表機上啟動佇列列印之前,等待每臺印表機的熱床清空確認。對於物理驗證熱床的農場工作流,請停用此選項。', + gcodeInjection: 'G-code注入', + gcodeInjectionDescription: '為Farmloop、SwapMod、AutoClear和Printflow 3D等自動列印系統設定自訂G-code,在列印開始和/或結束時注入。程式碼片段按印表機型號設定,在佇列項目上啟用"注入G-code"時套用。', + gcodeInjectionNoPrinters: '未找到印表機。新增印表機以設定G-code程式碼片段。', + gcodeStartLabel: '開始G-code', + gcodeEndLabel: '結束G-code', + gcodeStartPlaceholder: '在列印開始前插入的G-code...', + gcodeEndPlaceholder: '在列印結束後追加的G-code...', + staggerGroupSize: '群組大小', + staggerGroupSizeHelp: '每個群組要同時啟動的印表機數量', + staggerInterval: '間隔(分鐘)', + staggerIntervalHelp: '每個群組啟動之間的延遲', + queueDrying: '自動乾燥', + queueDryingDescription: '在佇列列印之間,印表機空閒時自動乾燥AMS耗材。使用上方的濕度閾值觸發乾燥。', + queueDryingEnabled: '啟用自動乾燥', + queueDryingEnabledDescription: '當印表機空閒且濕度超過閾值時,自動啟動AMS乾燥', + queueDryingBlock: '等待乾燥完成', + queueDryingBlockDescription: '阻止列印佇列直到乾燥完成。關閉時,列印優先於乾燥。', + ambientDryingEnabled: '環境乾燥', + ambientDryingEnabledDescription: '當空閒印表機的濕度超過閾值時自動乾燥耗材,無需佇列列印。', + dryingPresets: '乾燥預設', + dryingPresetsDescription: '每種耗材類型的溫度和時長。AMS 2 Pro使用較低溫度,AMS-HT支援較高溫度。', + dryingFilament: '耗材', + printModal: '列印對話方塊', + expandCustomMapping: '預設展開自訂對應', + expandCustomMappingDescription: '列印到多臺印表機時,預設展開顯示每臺印表機的 AMS 對應', + // User management + authentication: '身份驗證', + authEnabledDescription: '您的實例已透過使用者身份驗證保護', + authDisabledDescription: '啟用以要求登入並管理使用者存取', + authDisabledMessage: '啟用身份驗證以建立使用者帳戶、管理權限並保護您的 Bambuddy 實例。', + enableAuthentication: '啟用身份驗證', + currentUser: '目前使用者', + changePassword: '修改密碼', + admin: '管理員', + users: '使用者', + addUser: '新增使用者', + groups: '群組', + addGroup: '新增群組', + system: '系統', + noDescription: '無描述', + userCount: '{{count}} 個使用者', + permissionCount: '{{count}} 個權限', + createUser: '建立使用者', + username: '使用者名稱', + enterUsername: '輸入使用者名稱', + password: '密碼', + enterPassword: '輸入密碼(至少 6 個字元)', + confirmPassword: '確認密碼', + confirmPasswordPlaceholder: '確認密碼', + // Title tooltips + viewReleaseOnGitHub: '在 GitHub 上檢視發布', + turnAllPlugsOn: '開啟所有插座', + turnAllPlugsOff: '關閉所有插座', + // Modal: Clear logs + clearNotificationLogs: '清除通知日誌', + clearLogsMessage: '這將永久刪除所有 30 天前的通知日誌。此操作無法復原。', + clearLogs: '清除日誌', + // Modal: Reset UI + resetUiPreferences: '重設 UI 偏好', + resetUiPreferencesMessage: '這將重設所有 UI 偏好為預設值:側邊欄順序、主題、儀表板佈局、檢視模式和排序偏好。您的印表機、歸檔和伺服器設定不會受到影響。清除後頁面將重新載入。', + resetPreferences: '重設偏好', + // Modal: Delete group + deleteGroupTitle: '刪除群組', + deleteGroupMessage: '確定要刪除此群組嗎?此群組中的使用者將失去這些權限。', + deleteGroup: '刪除群組', + // Modal: Disable auth + disableAuthenticationTitle: '停用身份驗證', + disableAuthenticationMessage: '確定要停用身份驗證嗎?這將使您的 Bambuddy 實例無需登入即可存取。所有使用者將保留在資料庫中但身份驗證將被停用。', + disableAuthentication: '停用身份驗證', + // Additional settings + configureBambuddy: '設定 Bambuddy', + systemDefault: '系統預設', + archiveSettings: '歸檔設定', + newWindow: '新視窗', + embeddedOverlay: '嵌入式疊加層', + preferredSlicer: '首選切片軟體', + preferredSlicerDescription: '選擇要用於開啟檔案的切片軟體', + externalCameras: '外部攝影機', + costTracking: '成本追蹤', + printsOnly: '僅列印', + totalConsumption: '總消耗', + dataManagement: '資料管理', + storageUsage: '儲存使用情況', + storageUsageDescription: '按類別的資料使用情況明細', + storageUsageTotal: '總計', + storageUsageErrors: '錯誤', + storageUsageOtherBreakdown: '其他(包括靜態資源、腳本和設定檔案)', + storageUsageSystem: '系統', + storageUsageData: '資料', + storageUsageUnavailable: '儲存使用資訊不可用', + clearNotificationLogsDescription: '刪除 30 天前的通知日誌', + resetUiPreferencesDescription: '重設側邊欄順序、主題、檢視模式和佈局偏好。印表機、歸檔和設定不受影響。', + enableHomeAssistant: '啟用 Home Assistant', + enableMqtt: '啟用 MQTT', + useTls: '使用 TLS', + enableMetricsEndpoint: '啟用指標端點', + availableMetrics: '可用指標', + editUser: '編輯使用者', + deleteUserTitle: '刪除使用者', + groupName: '群組名稱', + // Placeholders + leaveEmptyForAnonymous: '留空為匿名', + leaveEmptyForNoAuth: '留空為無認證', + enterNewPassword: '輸入新密碼', + confirmNewPassword: '確認新密碼', + enterGroupName: '輸入群組名稱', + enterDescriptionOptional: '輸入描述(可選)', + enterCurrentPassword: '輸入目前密碼', + enterNewPasswordMin6: '輸入新密碼(至少 6 個字元)', + toast: { + keyCopied: '金鑰已複製到剪貼簿', + copyFailed: '複製金鑰失敗', + keyAddedToBrowser: '金鑰已新增到 API 瀏覽器', + clearLogsFailed: '清除日誌失敗', + uiPreferencesReset: 'UI 偏好已重設。重新整理中...', + authDisabled: '身份驗證已成功停用', + authDisableFailed: '停用身份驗證失敗', + apiKeyCreated: 'API 金鑰已建立', + apiKeyDeleted: 'API 金鑰已刪除', + userCreated: '使用者建立成功', + userUpdated: '使用者更新成功', + userDeleted: '使用者刪除成功', + groupCreated: '群組建立成功', + groupUpdated: '群組更新成功', + groupDeleted: '群組刪除成功', + fillRequiredFields: '請填寫所有必填欄位', + passwordsDoNotMatch: '密碼不符', + passwordTooShort: '密碼至少需要 6 個字元', + enterGroupName: '請輸入群組名稱', + settingsSaved: '設定已儲存', + cameraSettingsSaved: '攝影機設定已儲存', + enterCameraUrl: '請輸入攝影機 URL', + passwordChanged: '密碼修改成功', + connectionFailed: '連線失敗', + testFailed: '測試失敗', + cameraConnected: '攝影機已連線{{resolution}}', + }, + testConnection: '測試連線', + catalog: { + spoolCatalog: '耗材目錄', + spoolCatalogDescription: '按品牌/類型的空耗材重量。用於新增耗材時的自動重量查詢。', + searchCatalog: '搜尋目錄...', + addNewEntry: '新增新條目', + namePlaceholder: '名稱(例如:Bambu Lab - 塑膠)', + weight: '重量', + type: '類型', + default: '預設', + custom: '自訂', + noMatch: '沒有條目匹配您的搜尋', + empty: '目錄中沒有條目', + deleteEntry: '刪除條目', + deleteConfirm: '確定要刪除"{{name}}"嗎?', + resetCatalog: '重設目錄', + resetConfirm: '重設目錄為預設值?這將移除所有自訂條目。', + loadFailed: '載入耗材目錄失敗', + nameWeightRequired: '名稱和重量為必填項', + entryAdded: '條目已新增', + addFailed: '新增條目失敗', + entryUpdated: '條目已更新', + updateFailed: '更新條目失敗', + entryDeleted: '條目已刪除', + deleteFailed: '刪除條目失敗', + resetSuccess: '目錄已重設為預設值', + resetFailed: '重設目錄失敗', + exported: '已匯出 {{count}} 條', + imported: '已匯入 {{added}} 條(跳過 {{skipped}} 條)', + importFailed: '匯入失敗:無效的 JSON 格式', + exportTooltip: '匯出目錄為 JSON', + importTooltip: '從 JSON 匯入目錄', + resetTooltip: '重設為預設值', + selectedCount: '已選擇 {{count}} 項', + deleteSelected: '刪除所選', + bulkDeleteConfirm: '確定要刪除 {{count}} 個條目嗎?', + bulkDeleted: '已刪除 {{count}} 個條目', + bulkDeleteFailed: '刪除條目失敗', + }, + colorCatalog: { + title: '顏色目錄', + description: '按製造商/材料的耗材顏色。用於新增耗材時的自動顏色查詢。', + searchColors: '搜尋顏色...', + allManufacturers: '所有製造商', + addNewColor: '新增新顏色', + manufacturer: '製造商', + colorName: '顏色名稱', + hex: '十六進位', + materialOptional: '材料(可選)', + showing: '顯示 {{filtered}} / {{total}} 種顏色', + noMatch: '沒有顏色匹配您的搜尋', + empty: '目錄中沒有顏色', + deleteColor: '刪除顏色', + deleteConfirm: '確定要刪除"{{name}}"嗎?', + resetCatalog: '重設顏色目錄', + resetConfirm: '重設目錄為預設值?這將移除所有自訂顏色。', + sync: '同步', + starting: '啟動中...', + syncTooltip: '從 FilamentColors.xyz 同步(2000+ 種顏色,可能需要一分鐘)', + loadFailed: '載入顏色目錄失敗', + fieldsRequired: '製造商、顏色名稱和十六進位顏色為必填項', + colorAdded: '顏色已新增', + addFailed: '新增顏色失敗', + colorUpdated: '顏色已更新', + updateFailed: '更新顏色失敗', + colorDeleted: '顏色已刪除', + deleteFailed: '刪除顏色失敗', + resetSuccess: '顏色目錄已重設為預設值', + resetFailed: '重設目錄失敗', + syncUpToDate: '已是最新(檢查了 {{count}} 種顏色)', + syncComplete: '新增了 {{added}} 種新顏色({{skipped}} 種已存在)', + syncError: '同步錯誤', + syncFailed: '從 FilamentColors.xyz 同步失敗', + exported: '已匯出 {{count}} 種顏色', + imported: '已匯入 {{added}} 種顏色(跳過 {{skipped}} 種)', + importFailed: '匯入失敗:無效的 JSON 格式', + selectedCount: '已選擇 {{count}} 項', + deleteSelected: '刪除所選', + bulkDeleteConfirm: '確定要刪除 {{count}} 種顏色嗎?', + bulkDeleted: '已刪除 {{count}} 種顏色', + bulkDeleteFailed: '刪除顏色失敗', + }, + dateFormat: '日期格式', + dateFormatUs: '美式 (MM/DD/YYYY)', + dateFormatEu: '歐式 (DD/MM/YYYY)', + dateFormatIso: 'ISO (YYYY-MM-DD)', + timeFormat: '時間格式', + timeFormat12: '12小時制 (3:30 PM)', + timeFormat24: '24小時制 (15:30)', + defaultPrinter: '預設印表機', + defaultPrinterDescription: '為上傳、重印和其他操作預選此印表機。', + slicerBambuStudio: 'Bambu Studio', + slicerOrcaSlicer: 'OrcaSlicer', + sidebarOrderDescription: '拖曳側邊欄項目以重新排序。在此處重設為預設順序。', + setDefault: '設為預設', + sidebarOrderSetDefaultHint: '設為預設將目前選單順序套用於尚未自訂的使用者。', + sidebarDefaultSet: '已設定預設選單順序。', + sidebarDefaultCleared: '已清除預設選單順序。', + sidebarDefaultFailed: '設定預設選單順序失敗。', + reset: '重設', + darkMode: '深色模式', + lightMode: '淺色模式', + active: '(目前)', + background: '背景', + accent: '強調色', + style: '樣式', + bgNeutral: '中性', + bgWarm: '暖色', + bgCool: '冷色', + bgOled: 'OLED 純黑', + bgSlate: '石板藍', + bgForest: '森林綠', + accentGreen: '綠色', + accentTeal: '青色', + accentBlue: '藍色', + accentOrange: '橙色', + accentPurple: '紫色', + accentRed: '紅色', + styleClassic: '經典', + styleGlow: '發光', + styleVibrant: '鮮豔', + themeToggleHint: '使用側邊欄中的太陽/月亮圖示在深色和淺色模式之間切換。', + autoArchivePrints: '自動歸檔列印', + autoArchiveDescription: '列印完成時自動儲存3MF檔案', + saveThumbnailsDescription: '從3MF檔案中提取並儲存預覽影像', + captureFinishPhotoDescription: '列印完成時從印表機攝影機拍照', + ffmpegNotInstalled: '未安裝ffmpeg', + ffmpegRequired: '攝影機捕獲需要ffmpeg。透過 brew install ffmpeg(macOS)或 apt install ffmpeg(Linux)安裝。', + camera: '攝影機', + cameraViewMode: '攝影機檢視模式', + cameraOverlayDescription: '攝影機在主螢幕上以可調大小的覆蓋層開啟', + cameraWindowDescription: '攝影機在單獨的瀏覽器視窗中開啟', + externalCamerasDescription: '設定外部攝影機以替換內建印表機攝影機。支援MJPEG流、RTSP、HTTP快照和USB攝影機(V4L2)。啟用後,外部攝影機將用於即時檢視和完成照片。', + cameraPlaceholderUsb: '裝置路徑 (/dev/video0)', + cameraPlaceholderUrl: '攝影機URL (rtsp://... 或 http://...)', + cameraTypeMjpeg: 'MJPEG 流', + cameraTypeRtsp: 'RTSP 流', + cameraTypeSnapshot: 'HTTP 快照', + cameraTypeUsb: 'USB 攝影機 (V4L2)', + cameraRotation: '旋轉', + test: '測試', + connected: '已連線', + disconnected: '未連線', + currency: '貨幣', + defaultFilamentCost: '預設耗材成本(每公斤)', + electricityCost: '每千瓦時電費', + energyDisplayMode: '能源顯示模式', + energyModePrintDescription: '儀表板顯示列印期間使用的能源總和', + energyModeTotalDescription: '儀表板顯示智慧插座的累計能源', + fileManager: '檔案管理器', + createArchiveEntry: '列印時建立歸檔條目', + createArchiveEntryDescription: '從檔案管理器列印時,可選擇建立歸檔條目', + lowDiskSpaceWarning: '磁碟空間不足警告', + lowDiskSpaceDescription: '當可用磁碟空間低於此閾值時顯示警告', + printerFirmware: '印表機韌體', + checkFirmwareDescription: '檢查Bambu Lab的印表機韌體更新', + bambuddySoftware: 'Bambuddy 軟體', + autoCheckDescription: '啟動時自動檢查新版本', + checkNow: '立即檢查', + updateAvailableVersion: '可用更新:v{{version}}', + releaseNotes: '發布說明', + updateViaDocker: '透過 Docker Compose 更新:', + installUpdate: '安裝更新', + latestVersionRunning: '您正在執行最新版本', + failedToCheckUpdates: '檢查更新失敗:{{error}}', + backupRestore: '備份與恢復', + backupRestoreDescription: '匯出/匯入設定並設定GitHub 備份', + goToBackup: '前往備份', + externalUrl: '外部URL', + externalUrlDescription: 'Bambuddy可存取的外部URL。用於通知影像和外部整合。', + bambuddyUrl: 'Bambuddy URL', + externalUrlHint: '包含協定和連接埠(例如:http://192.168.1.100:8000)', + ftpRetry: 'FTP重試', + ftpRetryDescription: '當印表機Wi-Fi 不穩定時重試FTP操作。適用於3MF下載、列印上傳、縮時攝影下載和韌體更新。', + autoRetryDescription: '自動重試失敗的FTP操作', + retryAttempts: '重試次數', + retryDelay: '重試延遲', + connectionTimeout: '連線超時', + time_one: '{{count}} 次', + time_other: '{{count}} 次', + second_one: '{{count}} 秒', + second_other: '{{count}} 秒', + nSeconds: '{{count}} 秒', + increaseForWeakWifi: '對Wi-Fi 訊號弱的印表機增加此值', + homeAssistant: 'Home Assistant', + homeAssistantFullDescription: '連線到Home Assistant,透過HA REST API控制智慧插座。支援switch、light、input_boolean和script實體。', + homeAssistantUrl: 'Home Assistant URL', + longLivedAccessToken: '長期存取權杖', + haTokenHint: '在HA中建立權杖:個人資料 → 長期存取權杖 → 建立權杖', + connectionSuccessful: '連線成功', + connectionFailed: '連線失敗', + haConnectionSuccess: '已成功連線到Home Assistant。', + haConnectionFailed: '連線Home Assistant失敗。', + mqttPublishing: 'MQTT發布', + mqttDescription: '將BamBuddy事件發布到外部MQTT代理,用於與Node-RED、Home Assistant和其他自動化系統整合。', + mqttEnableDescription: '向外部MQTT代理發布事件', + brokerHostname: '代理主機名稱', + port: '連接埠', + usernameOptional: '使用者名稱(可選)', + passwordOptional: '密碼(可選)', + topicPrefix: '主題前綴', + topicPrefixHint: '主題格式:{{prefix}}/printers//status 等', + prometheusMetrics: 'Prometheus 指標', + prometheusEndpointDescription: '在 /api/v1/metrics 公開印表機指標,用於Prometheus/Grafana監控。', + bearerTokenOptional: 'Bearer權杖(可選)', + bearerTokenHint: '設定後,請求必須包含 Authorization: Bearer ', + metricsConnectionStatus: '連線狀態', + metricsPrinterState: '印表機狀態(空閒/列印中等)', + metricsPrintProgress: '列印進度 0-100%', + metricsBedTemp: '熱床溫度', + metricsNozzleTemp: '噴嘴溫度', + metricsPrintsTotal: '按結果分類的總列印數', + metricsMore: '...以及更多(層數、風扇、佇列、耗材用量)', + smartPlugsDescription: '連線智慧插座(Tasmota或Home Assistant)以自動化電源控制並追蹤印表機的能源使用情況。', + allOn: '全部開啟', + allOff: '全部關閉', + addSmartPlug: '新增智慧插座', + energySummary: '能源概要', + currentPower: '目前功率', + plugsOnline: '{{reachable}}/{{total}} 個插座線上', + today: '今天', + yesterday: '昨天', + total: '總計', + enablePlugsForSummary: '啟用插座以檢視能源概要', + addNotificationProvider: '新增', + systemBadge: '(系統)', + creating: '建立中...', + changing: '修改中...', + deleteUserAndItems: '刪除使用者及其所有項目', + deleteUserKeepItems: '刪除使用者,保留項目(將變為無主項目)', + ok: '確定', + + // 2FA settings + twoFa: { + totpTitle: '身份驗證器 App (TOTP)', + totpDesc: '使用 Google Authenticator、Aegis 或 Authy 等 App。', + emailOtpTitle: '郵件 OTP', + emailOtpDesc: '登入時向 {{email}} 傳送一次性驗證碼。', + emailOtpNoEmail: '請先為帳戶新增信箱地址以啟用此方式。', + addEmailFirst: '您的帳戶沒有信箱地址,請聯絡管理員新增。', + setupTotp: '設定身份驗證器 App', + setupAuthApp: '設定身份驗證器 App', + setupInstructions: '使用身份驗證器 App 掃描QR Code,然後輸入驗證碼確認。', + manualEntry: '無法掃描?請手動輸入此金鑰:', + scannedContinue: '已掃描 — 繼續', + enterCodeToConfirm: '請輸入身份驗證器 App 中的6位驗證碼以確認設定。', + activate: '啟用', + disableTotp: '停用身份驗證器', + disableConfirmHint: '請輸入有效的 TOTP 碼或備用碼來停用身份驗證器。', + totpDisabled: '身份驗證器 App 已停用。', + emailOtpEnabled: '郵件 OTP 已啟用。', + emailOtpDisabled: '郵件 OTP 已停用。', + smtpRequired: '請先設定並測試SMTP設定。', + invalidCode: '無效驗證碼,請重試。', + enableEmailOtp: '啟用郵件 OTP', + disableEmailOtp: '停用郵件 OTP', + emailSetupEnterCode: '驗證碼已傳送至您的信箱地址。請在下方輸入以確認您擁有此信箱。', + verifyAndEnable: '驗證並啟用', + emailDisablePasswordHint: '請輸入您的帳戶密碼以確認停用郵件 OTP。', + passwordPlaceholder: '輸入您的密碼', + backupCodesTitle: '儲存備用碼', + backupCodesWarning: '請將這些碼儲存在安全的地方。每個碼只能使用一次,且不會再次顯示。', + backupCodesRemaining: '剩餘 {{count}} 個備用碼', + savedCodes: '已儲存', + regenBackup: '重新產生備用碼', + regenBackupHint: '輸入目前 TOTP 碼以產生 10 個新備用碼,所有現有備用碼將失效。', + newBackupCodes: '新備用碼', + linkedAccounts: '已連結的 SSO 帳戶', + linkedAccountsDesc: '以下外部身份提供者已與您的帳戶連結。', + oidcUnlinked: '帳戶已解除連結。', + }, + + // OIDC provider settings + oidc: { + title: 'SSO / OIDC 提供者', + desc: '設定 OpenID Connect 提供者以實現單點登入。', + addProvider: '新增提供者', + newProvider: '新提供者', + empty: '尚未設定 OIDC 提供者。', + created: '提供者已建立。', + updated: '提供者已更新。', + deleted: '提供者已刪除。', + deleteTitle: '刪除提供者', + deleteMessage: '刪除"{{name}}"?所有連結帳戶將斷開連線。', + form: { + name: '顯示名稱', + issuerUrl: '頒發者 URL', + clientId: '客戶端 ID', + clientSecret: '客戶端金鑰', + scopes: '作用域', + iconUrl: '圖示 URL(可選)', + enabled: '已啟用', + autoCreate: '自動建立使用者', + autoCreateDesc: '首次登入時自動建立本機帳戶。', + autoLink: '自動連結已有帳戶', + autoLinkDesc: '首次登入時透過信箱匹配現有本機帳戶並自動連結。', + secretHint: '留空以保留目前', + secretPlaceholder: '新金鑰', + }, + }, + + }, + + // Notifications (for push notifications) + notification: { + printStarted: { + title: '列印已開始', + body: '{{printer}}:{{filename}} 已開始列印', + }, + printCompleted: { + title: '列印已完成', + body: '{{printer}}:{{filename}} 已成功完成', + }, + printFailed: { + title: '列印失敗', + body: '{{printer}}:{{filename}} 列印失敗', + }, + printStopped: { + title: '列印已停止', + body: '{{printer}}:{{filename}} 已停止', + }, + printProgress: { + title: '列印進度', + body: '{{printer}}:{{filename}} 已完成 {{percent}}%', + }, + printerOffline: { + title: '印表機離線', + body: '{{printer}} 已離線', + }, + printerError: { + title: '印表機錯誤', + body: '{{printer}}:{{error}}', + }, + filamentLow: { + title: '耗材不足', + body: '{{printer}}:耗材即將用完', + }, + maintenanceDue: { + title: '維護到期', + body: '{{printer}}:{{items}} 需要關注', + }, + }, + + // Errors + errors: { + generic: '出了點問題', + networkError: '網路錯誤。請檢查您的連線。', + notFound: '未找到', + unauthorized: '未授權', + serverError: '伺服器錯誤', + validationError: '請檢查您的輸入', + printerConnectionFailed: '連線印表機失敗', + saveFailed: '儲存更改失敗', + deleteFailed: '刪除失敗', + loadFailed: '載入資料失敗', + }, + + // HMS Errors modal + hmsErrors: { + title: '錯誤 - {{name}}', + noErrors: '無錯誤', + viewOnWiki: '在拓竹 Wiki 上檢視', + clearInstructions: '在印表機上清除錯誤以在此處消除它們。', + clearErrors: '清除錯誤', + clearSuccess: 'HMS 錯誤已清除', + clearFailed: '清除 HMS 錯誤失敗', + }, + + // MQTT Debug modal + mqttDebug: { + title: 'MQTT 偵錯日誌', + searchPlaceholder: '搜尋主題或負載...', + noMessages: '尚未紀錄訊息', + startLoggingHint: '點選"開始紀錄"以開始捕獲 MQTT 訊息', + noMessagesMatch: '沒有訊息匹配您的篩選條件', + adjustFilterHint: '嘗試調整您的搜尋或篩選條件', + incoming: '傳入', + outgoing: '傳出', + loggingStopped: '紀錄已停止', + loggingActive: '紀錄中 - 訊息將自動重新整理', + startLogging: '開始紀錄', + stopLogging: '停止紀錄', + clearLog: '清除日誌', + topic: '主題', + timestamp: '時間戳', + direction: '方向', + all: '全部', + }, + + // Printer File Manager modal (printer internal storage) + printerFiles: { + title: '檔案管理器', + storageUsed: '已用:', + storageFree: '剩餘:', + filterPlaceholder: '篩選檔案...', + deleteButton: '刪除', + deleteFiles: '刪除 {{count}} 個檔案', + deleteFileConfirm: '刪除"{{name}}"?此操作無法復原。', + deleteFilesConfirm: '刪除 {{count}} 個選中的檔案?此操作無法復原。', + noFiles: '印表機上沒有檔案', + loadingFiles: '載入檔案中...', + failedToLoad: '載入檔案失敗', + toast: { + filesDeleted: '已刪除 {{count}} 個檔案', + deleteFailed: '刪除失敗:{{error}}', + }, + }, + + // Confirmations + confirm: { + delete: '確定要刪除嗎?', + unsavedChanges: '您有未儲存的更改。確定要離開嗎?', + clearQueue: '確定要清空佇列嗎?', + }, + + // Login page + login: { + title: 'Bambuddy 登入', + subtitle: '登入您的帳戶', + username: '使用者名稱', + usernamePlaceholder: '輸入您的使用者名稱', + usernameOrEmail: '使用者名稱或信箱', + usernameOrEmailPlaceholder: '使用者名稱或 @ 信箱', + password: '密碼', + passwordPlaceholder: '輸入您的密碼', + signIn: '登入', + signingIn: '登入中...', + forgotPassword: '忘記密碼?', + loginSuccess: '登入成功', + loginFailed: '登入失敗', + enterCredentials: '請輸入使用者名稱和密碼', + enterEmail: '請輸入您的電子郵件地址', + oidcLoginFailed: 'OIDC 登入失敗', + oidcErrors: { + providerError: '身份提供者返回了一個錯誤', + missingParameters: 'OIDC 回呼缺少必要引數', + invalidState: 'OIDC 狀態無效或已被使用', + stateExpired: 'OIDC 登入會話已過期,請重試', + providerNotFound: '未找到 OIDC 提供者', + discoveryFailed: '無法獲取 OIDC 探索文件', + invalidDiscovery: 'OIDC 探索文件無效', + networkError: 'OIDC 權杖交換時出現網路錯誤', + badResponse: 'OIDC 權杖交換時收到意外回應', + noIdToken: 'OIDC 提供者未返回 ID 權杖', + validationFailed: 'OIDC 權杖驗證失敗', + nonceMismatch: 'OIDC nonce 不符,可能存在重放攻擊', + missingSubClaim: 'OIDC 權杖缺少 sub 宣告', + noLinkedAccount: '沒有與此 OIDC 身份連結的本機帳戶', + accountInactive: '您的帳戶已被停用', + userResolutionFailed: '無法解析您的帳戶', + internalError: 'OIDC 登入過程中發生內部錯誤', + tokenExchangeFailed: 'OIDC 權杖交換失敗', + }, + forgotPasswordTitle: '忘記密碼', + forgotPasswordMessage: '如果您忘記了密碼,請聯絡系統管理員進行重設。', + forgotPasswordEmailMessage: '輸入您的信箱地址,我們將向您傳送新密碼。', + emailAddress: '信箱地址', + emailPlaceholder: 'your.email@example.com', + cancel: '取消', + sending: '傳送中...', + sendResetEmail: '傳送重設郵件', + howToReset: '如何重設密碼:', + resetStep1: '聯絡您的 Bambuddy 管理員', + resetStep2: '請他們在使用者管理中重設您的密碼', + resetStep3: '他們可以為您設定一個臨時密碼', + resetStep4: '使用新密碼登入並在設定中修改密碼', + gotIt: '知道了', + resetPassword: { + title: '設定新密碼', + subtitle: '請在下方輸入並確認您的新密碼。', + newPassword: '新密碼', + newPasswordPlaceholder: '至少 8 個字元', + confirmPassword: '確認密碼', + confirmPasswordPlaceholder: '重複輸入新密碼', + saving: '儲存中…', + submit: '設定新密碼', + backToLogin: '回到登入', + passwordsDoNotMatch: '密碼不符', + passwordTooShort: '密碼至少需要 8 個字元', + resetFailed: '密碼重設失敗。連結可能已過期。', + }, + twoFA: { + title: '兩步驗證', + subtitle: '您的帳戶已啟用兩步驗證。請在下方輸入驗證碼。', + methodAuthenticator: '身份驗證器 App', + methodEmail: '信箱驗證碼', + methodBackup: '備用恢復碼', + instructionsTotp: '請開啟您的身份驗證器 App,輸入 Bambuddy 的 6 位驗證碼。', + instructionsEmail: '6 位驗證碼已傳送至您的信箱,有效期為 10 分鐘。', + instructionsEmailNotSent: '點選下方按鈕,透過郵件獲取驗證碼。', + instructionsBackup: '請輸入您的一個 8 位備用恢復碼。每個恢復碼只能使用一次。', + sendCodeButton: '傳送信箱驗證碼', + sendingCode: '傳送中...', + resendCode: '重新傳送驗證碼', + codeLabel: '驗證碼', + backupCodeLabel: '備用恢復碼', + codePlaceholder: '000000', + backupCodePlaceholder: 'XXXXXXXX', + verifyButton: '驗證', + verifyingButton: '驗證中...', + backToLogin: '← 回到登入頁面', + orContinueWith: '或透過以下方式登入', + signInWith: '使用 {{provider}} 登入', + enterCode: '請輸入驗證碼', + sendCodeFailed: '驗證碼傳送失敗', + invalidCode: '無效驗證碼,請重試。', + }, + + }, + + // Setup page + setup: { + title: 'Bambuddy 設定', + subtitle: '為您的 Bambuddy 實例設定身份驗證', + enableAuth: '啟用身份驗證', + adminAccount: '管理員帳戶', + adminAccountDesc: '如果管理員使用者已存在,將使用現有管理員帳戶啟用身份驗證。如需使用現有管理員,請將下方欄位留空,或輸入新憑據建立新管理員使用者。', + adminUsername: '管理員使用者名稱', + adminPassword: '管理員密碼', + optionalIfAdminExists: '(如管理員使用者已存在則為可選)', + adminUsernamePlaceholder: '輸入管理員使用者名稱(可選)', + adminPasswordPlaceholder: '輸入管理員密碼(可選)', + confirmPassword: '確認密碼', + confirmPasswordPlaceholder: '確認管理員密碼', + settingUp: '設定中...', + completeSetup: '完成設定', + toast: { + authEnabledAdminCreated: '身份驗證已啟用並建立了管理員使用者', + authEnabledExistingAdmins: '使用現有管理員使用者啟用了身份驗證', + setupCompleted: '設定完成', + enterBothCredentials: '請輸入管理員使用者名稱和密碼,或將兩者留空以使用現有管理員使用者', + passwordsDoNotMatch: '密碼不符', + passwordTooShort: '密碼至少需要 6 個字元', + }, + }, + + // Password change + changePassword: { + title: '修改密碼', + currentPassword: '目前密碼', + currentPasswordPlaceholder: '輸入目前密碼', + newPassword: '新密碼', + newPasswordPlaceholder: '輸入新密碼(至少 6 個字元)', + confirmPassword: '確認新密碼', + confirmPasswordPlaceholder: '確認新密碼', + passwordsDoNotMatch: '密碼不符', + passwordTooShort: '密碼至少需要 6 個字元', + changing: '修改中...', + success: '密碼修改成功', + failed: '密碼修改失敗', + }, + + // Plate detection alert + plateAlert: { + title: '列印已暫停!', + message: '在列印板上偵測到物體。列印已自動暫停。請清理列印板並繼續列印。', + understand: '我知道了', + }, + + // Camera page + camera: { + title: '攝影機檢視', + invalidPrinterId: '無效的印表機 ID', + live: '即時', + snapshot: '快照', + restartStream: '重新啟動流', + refreshSnapshot: '重新整理快照', + fullscreen: '全螢幕', + exitFullscreen: '離開全螢幕', + connectingToCamera: '連線攝影機中...', + capturingSnapshot: '拍攝快照中...', + connectionLost: '連線已斷開', + connectionFailed: '攝影機連線失敗', + reconnecting: '{{countdown}} 秒後重新連線...(第 {{attempt}}/{{max}} 次嘗試)', + reconnectNow: '立即重新連線', + cameraUnavailable: '攝影機不可用', + cameraUnavailableDesc: '請確保印表機已通電並已連線。', + noCamera: '無可用攝影機', + retry: '重試', + cameraStream: '攝影機流', + zoomOut: '縮小', + zoomIn: '放大', + resetZoom: '重設縮放', + recording: '錄製中', + startRecording: '開始錄製', + stopRecording: '停止錄製', + chamberLight: '切換腔室燈', + }, + + // Groups management + groups: { + title: '群組管理', + subtitle: '管理存取控制的權限群組', + backToSettings: '返回設定', + createGroup: '建立群組', + noPermission: '您沒有存取此頁面的權限。', + system: '系統', + noDescription: '無描述', + usersCount: '{{count}} 個使用者', + permissionsCount: '{{count}} 個權限', + edit: '編輯', + delete: '刪除', + toast: { + created: '群組建立成功', + updated: '群組更新成功', + deleted: '群組刪除成功', + enterGroupName: '請輸入群組名稱', + }, + modal: { + editGroup: '編輯群組', + createGroup: '建立群組', + cancel: '取消', + saving: '儲存中...', + creating: '建立中...', + saveChanges: '儲存更改', + }, + form: { + groupName: '群組名稱', + groupNamePlaceholder: '輸入群組名稱', + systemGroupWarning: '系統群組名稱不可更改', + description: '描述', + descriptionPlaceholder: '輸入描述(可選)', + permissions: '權限(已選 {{count}} 個)', + }, + deleteModal: { + title: '刪除群組', + message: '確定要刪除此群組嗎?此群組中的使用者將失去這些權限。', + confirm: '刪除群組', + }, + editor: { + title: '編輯群組', + createTitle: '建立群組', + search: '搜尋權限...', + selectAll: '全選', + clearAll: '清除全部', + permissionsSelected: '已選 {{count}} 個', + noResults: '沒有權限匹配您的搜尋', + }, + }, + + // Users management + users: { + title: '使用者管理', + subtitle: '管理使用者及其對 Bambuddy 實例的存取', + backToSettings: '返回設定', + createUser: '建立使用者', + noPermission: '您沒有存取此頁面的權限。', + admin: '管理員', + noGroups: '無群組', + active: '活躍', + inactive: '非活躍', + edit: '編輯', + delete: '刪除', + system: '系統', + noGroupsAvailable: '無可用群組', + table: { + username: '使用者名稱', + groups: '群組', + status: '狀態', + actions: '操作', + }, + toast: { + created: '使用者建立成功', + updated: '使用者更新成功', + deleted: '使用者刪除成功', + fillRequired: '請填寫所有必填欄位', + passwordsDoNotMatch: '密碼不符', + passwordTooShort: '密碼至少需要 6 個字元', + }, + modal: { + createUser: '建立使用者', + editUser: '編輯使用者', + cancel: '取消', + creating: '建立中...', + saving: '儲存中...', + saveChanges: '儲存更改', + advancedAuthSubtitle: '使用進階認證', + }, + form: { + username: '使用者名稱', + usernamePlaceholder: '輸入使用者名稱', + email: '信箱', + emailPlaceholder: 'user@example.com', + password: '密碼', + passwordPlaceholder: '輸入密碼', + confirmPassword: '確認密碼', + confirmPasswordPlaceholder: '確認密碼', + newPasswordPlaceholder: '輸入新密碼', + confirmNewPasswordPlaceholder: '確認新密碼', + leaveBlankToKeep: '留空以保持目前值', + groups: '群組', + optional: '可選', + autoGeneratedPassword: '將自動產生安全密碼並透過郵件傳送給使用者。', + passwordManagedByAdvancedAuth: '密碼由進階認證管理。使用"重設密碼"透過郵件向使用者傳送新密碼。', + resetPassword: '重設密碼', + resettingPassword: '重設密碼中...', + }, + deleteModal: { + title: '刪除使用者', + message: '確定要刪除此使用者嗎?此操作無法復原。', + confirm: '刪除使用者', + }, + }, + + // Stream overlay + streamOverlay: { + title: '流疊加層', + invalidPrinterId: '無效的印表機 ID', + cameraStream: '攝影機流', + progress: '進度', + eta: '預計完成時間', + printerIdle: '印表機空閒', + printerOffline: '印表機離線', + status: { + printing: '列印中', + paused: '已暫停', + finished: '已完成', + failed: '失敗', + idle: '空閒', + unknown: '未知', + }, + }, + + // Profiles + profiles: { + title: '設定檔案', + subtitle: '管理您的切片預設和壓力推進校準', + tabs: { + cloud: '雲端設定檔案', + local: '本機設定檔案', + kprofiles: 'K 值設定', + }, + localProfiles: { + title: '本機設定檔案', + subtitle: '從 OrcaSlicer 匯入和管理切片預設', + import: '匯入設定檔案', + importDesc: '將 .bbscfg、.bbsflmt、.orca_filament、.zip 或 .json 檔案拖放到此處', + importing: '匯入中...', + search: '搜尋本機預設...', + noPresets: '尚無本機預設', + badge: '本機', + edit: '編輯', + delete: '刪除', + cancel: '取消', + deleteConfirmTitle: '刪除預設', + deleteConfirm: '確定要刪除此預設嗎?此操作無法復原。', + source: '來源', + inheritsFrom: '繼承自', + filamentType: '類型', + vendor: '廠商', + compatiblePrinters: '相容印表機', + nozzleTemp: '噴嘴溫度', + cost: '成本', + density: '密度', + pressureAdvance: '壓力推進', + filament: '耗材', + process: '工藝', + printer: '印表機', + toast: { + importSuccess: '已匯入 {{count}} 個預設', + importSkipped: '跳過 {{count}} 個預設(重複)', + importError: '匯入時出現 {{count}} 個錯誤', + deleted: '預設已刪除', + updated: '預設已更新', + }, + }, + connectedAs: '已連線為', + logout: '登出', + noLogoutPermission: '您沒有登出的權限', + failedToLoad: '載入設定檔案失敗', + retry: '重試', + time: { + justNow: '剛剛', + minsAgo: '{{count}} 分鐘前', + hoursAgo: '{{count}} 小時前', + daysAgo: '{{count}} 天前', + }, + toast: { + loggedOut: '已登出', + }, + login: { + title: '連線到拓竹雲', + subtitle: '跨裝置同步您的切片預設', + email: '信箱', + password: '密碼', + region: '地區', + regionGlobal: '全球', + regionChina: '中國', + verificationCode: '驗證碼', + totpCode: '驗證器驗證碼', + checkEmail: '檢查您的信箱 ({{email}}) 獲取 6 位驗證碼', + enterTotpHint: '輸入驗證器 App 中的 6 位驗證碼', + accessToken: '存取權杖', + accessTokenHint: '貼上您的拓竹存取權杖(來自 Bambu Studio)', + back: '返回', + loginButton: '登入', + verifyButton: '驗證', + setTokenButton: '設定權杖', + useToken: '改用存取權杖', + useEmail: '改用信箱登入', + toast: { + loggedIn: '登入成功', + codeSent: '驗證碼已傳送到您的信箱', + enterTotp: '輸入驗證器 App 中的程式碼', + tokenSet: '權杖設定成功', + }, + }, + presets: { + myPreset: '我的預設(可編輯)', + duplicate: '複製', + editable: '可編輯', + failedToLoadDetails: '載入預設詳情失敗', + deleteConfirm: '刪除此預設?', + deleteWarning: '這將從拓竹雲中永久刪除"{{name}}"。此操作無法復原。', + noDuplicatePermission: '您沒有複製預設的權限', + noEditPermission: '您沒有編輯預設的權限', + noDeletePermission: '您沒有刪除預設的權限', + types: { + filament: '耗材預設', + printer: '印表機預設', + process: '工藝預設', + }, + toast: { + deleted: '預設已刪除', + created: '預設已建立', + updated: '預設已更新', + duplicated: '預設已複製', + fieldAdded: '欄位"{{key}}"已新增', + exported: '預設已匯出', + }, + baseLabel: '基礎:{{name}}', + currentLabel: '目前:{{name}}', + newPreset: '新增預設', + editPreset: '編輯預設', + duplicatePreset: '複製預設', + createNewPreset: '建立新預設', + customizeSettings: '自訂新預設的設定', + compareWithBase: '與基礎預設比較', + compare: '比較', + // CreatePresetModal - Basic Info + basePreset: '基礎預設', + selectBasePreset: '選擇基礎預設...', + presetName: '預設名稱', + myCustomPreset: '我的自訂預設', + inheritsFrom: '繼承自', + dropJsonToImport: '拖放 JSON 以匯入', + // CreatePresetModal - Tabs + tabs: { + common: '常用', + allFields: '所有欄位', + }, + // CreatePresetModal - All Fields Tab + availableFields: '可用欄位', + searchFieldsPlaceholder: '搜尋欄位...', + noMatchingFields: '沒有匹配的欄位', + allFieldsAdded: '所有欄位已新增', + addCustomField: '新增自訂欄位', + yourOverrides: '您的覆蓋值', + noOverridesYet: '尚無覆蓋值', + clickFieldsToAdd: '點選左側的欄位進行新增', + saveAsTemplate: '儲存為範本', + jsonTip: '提示:將 .json 檔案拖放到此對話方塊的任意位置以匯入設定', + }, + cloudView: { + searchPlaceholder: '搜尋預設...', + templates: '範本', + refresh: '重新整理', + newPreset: '新增預設', + clearFilters: '清除篩選', + // Compare mode + compareMode: '比較模式', + selectAnotherPreset: '選擇另一個 {{type}} 預設', + clickTwoPresets: '點選兩個相同類型的預設進行比較', + selectFirst: '1. 選擇第一個', + selectSecond: '2. 選擇第二個', + compareNow: '立即比較', + // Status row + lastSynced: '上次同步:', + showingCount: '顯示 {{showing}} / {{total}} 個預設', + noPresetsFound: '未找到預設', + // Column headers + columns: { + filament: '耗材', + process: '工藝', + printer: '印表機', + }, + noFilamentPresets: '無耗材預設', + noProcessPresets: '無工藝預設', + noPrinterPresets: '無印表機預設', + // Filters + filters: { + type: '類型', + owner: '所有者', + printer: '印表機', + nozzle: '噴嘴', + filament: '耗材', + layer: '層', + all: '全部', + myPresets: '我的預設', + builtIn: '內建', + process: '工藝', + }, + // Permissions + noTemplatesPermission: '您沒有管理範本的權限', + noRefreshPermission: '您沒有重新整理設定檔案的權限', + noCreatePermission: '您沒有建立預設的權限', + }, + templates: { + title: '快速範本', + noTemplates: '尚無範本', + createFirst: '從預設編輯器建立範本', + typeFilter: '類型:', + deleteTitle: '刪除範本', + deleteWarning: '此操作無法復原', + deleteConfirm: '確定要刪除"{{name}}"嗎?', + namePlaceholder: '範本名稱', + descriptionPlaceholder: '描述', + settingsJson: '設定 (JSON)', + fieldsCount: '{{count}} 個欄位', + shownInModals: '在對話方塊中顯示', + hiddenInModals: '在對話方塊中隱藏', + apply: '套用', + toast: { + deleted: '範本已刪除', + updated: '範本已更新', + created: '範本已建立', + applied: '範本已套用', + }, + }, + }, + + // Support/Debug + support: { + debugLoggingActive: '偵錯日誌紀錄已啟用', + manageLogs: '管理', + collectItem7: '印表機連線和韌體版本', + collectItem8: '整合狀態(Spoolman、MQTT、HA)', + collectItem9: '網路介面(僅子網)', + collectItem10: 'Python 套件版本', + collectItem11: '資料庫健康檢查', + collectItem12: 'Docker 環境詳情', + }, + + // File manager + fileManager: { + title: '檔案管理器', + subtitle: '組織和管理您的列印檔案', + uploadFiles: '上傳檔案', + newFolder: '新增資料夾', + folderName: '資料夾名稱', + folderNamePlaceholder: '例如:功能零件', + renameFile: '重新命名檔案', + renameFolder: '重新命名資料夾', + moveFiles: '移動 {{count}} 個檔案', + rootNoFolder: '根目錄(無資料夾)', + current: '目前', + linkFolder: '連結資料夾', + linkFolderDescription: '將"{{name}}"連結到專案或歸檔以便快速存取。', + project: '專案', + archive: '歸檔', + noProjectsFound: '未找到專案', + noArchivesFound: '未找到歸檔', + unlink: '取消連結', + link: '連結', + dragDropFiles: '將檔案拖放到此處', + dropFilesHere: '將檔案放在此處', + orClickToBrowse: '或點選瀏覽', + allFileTypesSupported: '支援所有檔案類型。ZIP 檔案將被解壓。', + zipFilesDetected: '偵測到 ZIP 檔案', + zipExtractOptions: 'ZIP 檔案將被解壓。選擇如何處理資料夾結構:', + preserveZipStructure: '保留 ZIP 中的資料夾結構', + createFolderFromZip: '從 ZIP 檔名建立資料夾', + stlThumbnailGeneration: 'STL 縮圖產生', + zipMayContainStl: 'ZIP 檔案可能包含 STL 檔案。可以在解壓時產生縮圖。', + thumbnailsCanBeGenerated: '可以為 STL 檔案產生縮圖。大型模型可能需要更長時間處理。', + generateThumbnailsForStl: '為 STL 檔案產生縮圖', + threemfDetected: '偵測到 3MF 檔案', + threemfExtractionInfo: '將自動從 3MF 檔案中提取印表機型號、材料、顏色和列印設定。', + willBeExtracted: '將被解壓', + filesExtracted: '已解壓 {{count}} 個檔案', + uploadComplete: '上傳完成:{{succeeded}} 個成功', + uploadFailed: '上傳失敗', + zipFilesFailed: '{{count}} 個檔案失敗', + uploading: '上傳中...', + changeLink: '更改連結...', + linkTo: '連結到...', + linkToProjectOrArchive: '連結到專案或歸檔', + addToQueue: '新增到佇列', + schedulePrint: '排程', + generateThumbnail: '產生縮圖', + generateThumbnails: '產生縮圖', + generateThumbnailsForMissing: '為缺少縮圖的 STL 檔案產生縮圖', + gridView: '網格檢視', + listView: '列表檢視', + lowDiskSpaceWarning: '磁碟空間不足警告', + lowDiskSpaceDetails: '僅剩 {{free}}(總共 {{total}})。閾值設定為 {{threshold}} GB。', + files: '檔案', + folders: '資料夾', + size: '大小', + free: '剩餘', + allFiles: '所有檔案', + wrap: '換行', + enableTextWrapping: '啟用文字換行', + disableTextWrapping: '停用文字換行', + dragToResizeTooltip: '拖曳調整大小,雙擊重設', + searchFiles: '搜尋檔案...', + allTypes: '所有類型', + prints: '列印', + ascending: '升序', + descending: '降序', + resultsCount: '{{showing}} / {{total}} 個檔案', + selectAll: '全選', + deselectAll: '取消全選', + selected: '已選擇 {{count}} 個', + adding: '新增中...', + loadingFiles: '載入檔案中...', + folderIsEmpty: '資料夾為空', + noFilesYet: '尚無檔案', + folderEmptyDescription: '上傳檔案或將檔案移入此資料夾以開始使用。', + noFilesDescription: '上傳檔案以開始組織您的列印相關檔案。', + noMatchingFiles: '沒有匹配的檔案', + noMatchingFilesDescription: '沒有檔案匹配您目前的搜尋或篩選條件。', + clearFilters: '清除篩選', + printedCount: '已列印 {{count}} 次', + uploadedBy: '上傳者', + deleteFolder: '刪除資料夾', + deleteFile: '刪除檔案', + deleteFilesCount: '刪除 {{count}} 個檔案', + deleteFolderConfirm: '確定要刪除此資料夾嗎?其中的所有檔案也將被刪除。', + deleteFileConfirm: '確定要刪除此檔案嗎?', + deleteFilesConfirm: '確定要刪除 {{count}} 個選中的檔案嗎?此操作無法復原。', + deleting: '刪除中...', + noPermissionRenameFolder: '您沒有重新命名資料夾的權限', + noPermissionLinkFolder: '您沒有連結資料夾的權限', + noPermissionDeleteFolder: '您沒有刪除資料夾的權限', + noPermissionPrint: '您沒有列印的權限', + noPermissionAddToQueue: '您沒有新增到佇列的權限', + noPermissionDownload: '您沒有下載檔案的權限', + noPermissionRenameFile: '您沒有重新命名此檔案的權限', + noPermissionGenerateThumbnail: '您沒有產生縮圖的權限', + noPermissionDeleteFile: '您沒有刪除此檔案的權限', + noPermissionCreateFolder: '您沒有建立資料夾的權限', + noPermissionUpload: '您沒有上傳檔案的權限', + noPermissionMoveFiles: '您沒有移動檔案的權限', + noPermissionDeleteFiles: '您沒有刪除檔案的權限', + // External folder + linkExternal: '連結外部', + linkExternalFolder: '連結外部資料夾', + linkExternalFolderDescription: '將主機目錄(NAS、USB、網路共享)掛載到檔案管理器中。檔案不會被複制——直接從原始路徑存取。', + externalFolderNamePlaceholder: '例如:NAS列印檔案', + externalPath: '主機路徑', + externalPathHelp: 'Docker主機上目錄的絕對路徑。必須以繫結掛載方式掛載到容器中。', + readOnly: '只讀', + readOnlyHelp: '防止上傳和刪除', + showHiddenFiles: '顯示隱藏檔案(點檔案)', + externalFolder: '外部資料夾', + scanFolder: '掃描', + toast: { + folderCreated: '資料夾已建立', + folderDeleted: '資料夾已刪除', + fileDeleted: '檔案已刪除', + filesDeleted: '已刪除 {{count}} 個檔案', + filesMoved: '檔案已移動', + folderLinked: '資料夾已連結', + folderUnlinked: '資料夾已取消連結', + externalFolderLinked: '外部資料夾已連結並掃描', + folderScanned: '掃描完成:新增 {{added}} 個,移除 {{removed}} 個', + addedToQueue: '已將 {{count}} 個檔案新增到佇列', + addedToQueuePartial: '已新增 {{added}} 個檔案,{{failed}} 個失敗', + failedToAddToQueue: '新增檔案失敗:{{error}}', + fileRenamed: '檔案已重新命名', + folderRenamed: '資料夾已重新命名', + thumbnailsGenerated: '已產生 {{count}} 個縮圖', + thumbnailsGeneratedPartial: '已產生 {{succeeded}} 個縮圖,{{failed}} 個失敗', + noStlMissingThumbnails: '沒有缺少縮圖的 STL 檔案', + failedToGenerateThumbnails: '產生縮圖失敗:{{error}}', + thumbnailGenerated: '縮圖已產生', + failedToGenerateThumbnail: '產生縮圖失敗:{{error}}', + }, + }, + + // Projects + projects: { + title: '專案', + subtitle: '組織和追蹤您的 3D 列印專案', + newProject: '新增專案', + editProject: '編輯專案', + deleteProject: '刪除專案', + projectName: '專案名稱', + description: '描述', + noProjects: '尚無專案', + noProjectsFiltered: '沒有{{status}}專案', + noProjectsFilteredHelp: '您沒有任何{{status}}專案。當專案狀態更改時,它們將出現在這裡。', + createFirst: '建立您的第一個項目以開始組織相關列印、追蹤進度和管理構建。', + createFirstButton: '建立您的第一個項目', + create: '建立', + files: '檔案', + prints: '列印', + plates: '板', + parts: '零件', + lastModified: '最後修改', + deleteConfirm: '確定要刪除此項目嗎?歸檔和佇列項目將被取消連結但不會被刪除。', + addFiles: '新增檔案', + removeFile: '移除檔案', + viewDetails: '檢視詳情', + // Modal fields + namePlaceholder: '例如:Voron 2.4 構建', + descriptionPlaceholder: '可選描述...', + color: '顏色', + targetPlates: '目標板數', + targetPlatesPlaceholder: '例如:25', + targetPlatesHelp: '列印任務數量', + targetParts: '目標零件數', + targetPartsPlaceholder: '例如:150', + targetPartsHelp: '所需零件總數', + tagsLabel: '標籤(逗號分隔)', + tagsPlaceholder: '例如:voron、功能件、禮物', + dueDate: '截止日期', + priority: '優先順序', + priorityLow: '低', + priorityNormal: '普通', + priorityHigh: '高', + priorityUrgent: '緊急', + // Status + statusActive: '進行中', + statusCompleted: '已完成', + statusArchived: '已歸檔', + done: '完成', + completed: '已完成', + failed: '失敗', + inQueue: '佇列中', + noPrintsYet: '尚無列印', + // Footer stats + printJobs: '列印任務(板)', + partsPrinted: '已列印零件', + failedParts: '失敗零件', + // Actions + import: '匯入', + export: '匯出', + importProject: '匯入專案', + exportAll: '匯出所有專案', + loading: '載入專案中...', + // Permissions + noEditPermission: '您沒有編輯專案的權限', + noDeletePermission: '您沒有刪除專案的權限', + noCreatePermission: '您沒有建立專案的權限', + noImportPermission: '您沒有匯入專案的權限', + noExportPermission: '您沒有匯出專案的權限', + // Toast + toast: { + created: '專案已建立', + updated: '專案已更新', + deleted: '專案已刪除', + imported: '專案已匯入', + multipleImported: '已匯入 {{count}} 個項目', + importFailed: '匯入失敗', + exported: '專案已匯出(僅中繼資料)', + }, + }, + + // Project detail page + projectDetail: { + notFound: '未找到專案', + backToProjects: '返回專案', + export: '匯出', + exportProject: '匯出專案', + noExportPermission: '您沒有匯出專案的權限', + noEditPermission: '您沒有編輯專案的權限', + partOf: '屬於:', + priorityLabel: '優先順序:', + noPrints: '此項目尚無列印', + status: { + active: '進行中', + completed: '已完成', + archived: '已歸檔', + }, + priority: { + low: '低', + normal: '普通', + high: '高', + urgent: '緊急', + }, + dueDate: { + overdue: '已逾期', + today: '今天到期', + daysLeft: '還有 {{count}} 天', + }, + progress: { + platesProgress: '板進度', + partsProgress: '零件進度', + printJobs: '列印任務', + parts: '零件', + percentComplete: '{{percent}}% 完成', + remaining: '剩餘 {{count}} 個', + }, + stats: { + printJobs: '列印任務', + total: '總計', + failed: '{{count}} 個失敗', + partsPrinted: '已列印 {{count}} 個零件', + printTime: '列印時間', + filamentUsed: '耗材用量', + }, + cost: { + title: '成本追蹤', + filamentCost: '耗材成本', + energy: '能源', + totalCost: '總成本', + total: '總計', + includesBom: '含物料清單', + budget: '預算', + remaining: '剩餘', + }, + subProjects: { + title: '子專案 ({{count}})', + }, + notes: { + title: '備註', + noEditPermission: '您沒有編輯備註的權限', + placeholder: '新增關於此項目的備註...', + empty: '尚無備註。點選編輯新增備註。', + }, + files: { + title: '檔案', + linkFolders: '從檔案管理器連結資料夾', + forQuickAccess: '到此項目以便快速存取。', + fileCount: '{{count}} 個檔案', + empty: '未連結資料夾。前往檔案管理器將資料夾連結到此項目。', + noFiles: '此資料夾中沒有檔案。', + print: '立即列印', + addToQueue: '加入佇列', + }, + bom: { + title: '材料清單', + acquired: '已獲取 {{completed}}/{{total}}', + showAll: '顯示全部', + hideDone: '隱藏已完成', + addPart: '新增零件', + noAddPermission: '您沒有新增零件的權限', + partNamePlaceholder: '零件名稱(例如:M3x8 螺絲)', + partName: '零件名稱', + qty: '數量', + price: '價格 ({{currency}})', + sourcingUrlPlaceholder: '採購連結(可選)', + remarksPlaceholder: '備註(可選)', + deletePart: '刪除零件', + deleteConfirm: '確定要刪除"{{name}}"嗎?', + noUpdatePermission: '您沒有更新零件的權限', + noEditPermission: '您沒有編輯零件的權限', + noDeletePermission: '您沒有刪除零件的權限', + totalCost: '總成本:', + empty: '材料清單中沒有零件。新增硬體、電子元件或其他元件以追蹤需要採購的物品。', + }, + timeline: { + title: '活動時間線', + empty: '尚無活動。', + }, + template: { + saveAsTemplate: '儲存為範本', + noCreatePermission: '您沒有建立範本的權限', + }, + queue: { + title: '佇列', + viewAll: '檢視全部', + printing: '{{count}} 個列印中', + queued: '{{count}} 個佇列中', + }, + prints: { + title: '列印 ({{count}})', + }, + toast: { + projectUpdated: '專案已更新', + partAdded: '零件已新增', + partRemoved: '零件已移除', + exportFailed: '匯出失敗', + projectExported: '專案已匯出', + templateCreated: '範本已建立', + }, + }, + + // System info + system: { + title: '系統資訊', + version: '版本', + uptime: '執行時間', + cpuUsage: 'CPU 使用率', + memoryUsage: '記憶體使用率', + diskUsage: '磁碟使用率', + networkInfo: '網路資訊', + logs: '日誌', + debugMode: '偵錯模式', + enableDebug: '啟用偵錯日誌', + disableDebug: '停用偵錯日誌', + downloadLogs: '下載日誌', + clearLogs: '清除日誌', + dockerInfo: 'Docker 資訊', + containerName: '容器名稱', + imageName: '映象名稱', + platform: '平臺', + architecture: '架構', + }, + + // Library (K Profiles) + library: { + title: '耗材庫', + addFilament: '新增耗材', + editFilament: '編輯耗材', + deleteFilament: '刪除耗材', + vendor: '廠商', + material: '材料', + color: '顏色', + kFactor: 'K 值', + temperature: '溫度', + noFilaments: '耗材庫中沒有耗材', + deleteConfirm: '確定要刪除此耗材嗎?', + importFromPrinter: '從印表機匯入', + exportToFile: '匯出到檔案', + }, + + // Spoolman + spoolman: { + title: 'Spoolman 整合', + enabled: 'Spoolman 已啟用', + url: 'Spoolman URL', + connected: '已連線', + disconnected: '未連線', + testConnection: '測試連線', + sync: '同步', + syncing: '同步中...', + lastSync: '上次同步', + linkToSpoolman: '連結到 Spoolman', + openInSpoolman: '在 Spoolman 中開啟', + unlinkSpool: '取消連結耗材', + unlinkConfirmTitle: '解開料盤?', + unlinkConfirmMessage: '這將斷開卷軸與 Spoolman 的連線。Spoolman 中的卷軸資料將保持不變。', + selectSpool: '選擇耗材', + noUnlinkedSpools: '無未連結的耗材', + linkSuccess: '耗材已成功連結到 Spoolman', + linkFailed: '連結耗材失敗', + unlinkSuccess: '已成功從 Spoolman 取消連結耗材', + unlinkFailed: '取消連結耗材失敗', + spoolId: '耗材 ID', + fillSourceLabel: '(Spoolman)', + weight: '重量', + remaining: '剩餘', + disableWeightSync: '停用 AMS 估計重量同步', + disableWeightSyncDesc: '不從 AMS 估計值更新剩餘容量。如果您更喜歡 Spoolman 的用量追蹤而非 AMS 百分比估計,請使用此選項。新耗材仍將使用 AMS 估計值作為初始重量。', + reportPartialUsage: '報告失敗列印的部分用量', + reportPartialUsageDesc: '當列印失敗或被取消時,根據層進度報告估計的耗材使用量。', + }, + + // Inventory + inventory: { + title: '耗材庫存', + addSpool: '新增耗材', + editSpool: '編輯耗材', + material: '材料', + selectMaterial: '選擇材料...', + subtype: '子類型', + brand: '品牌', + searchBrand: '搜尋品牌...', + useCustomBrand: '使用"{{brand}}"', + useCustomMaterial: '使用自訂材料:{{material}}', + colorName: '顏色名稱', + colorNamePlaceholder: '翡翠白、烈焰紅...', + color: '顏色', + hexColor: '十六進位顏色', + pickColor: '選擇自訂顏色', + labelWeight: '標籤重量', + coreWeight: '空盤重量', + searchSpoolWeight: '搜尋耗材重量...', + weightUsed: '已使用', + currentWeight: '剩餘重量', + measuredWeight: '稱量重量', + spoolName: '料盤', + costPerKg: '每公斤成本', + measuredWeightError: '稱量重量必須在 {{min}}g 到 {{max}}g 之間。', + slicerFilament: '切片耗材', + slicerFilamentName: '切片預設名稱', + slicerPreset: '切片預設', + searchPresets: '搜尋耗材預設...', + selectedPreset: '已選擇', + noPresetsFound: '未找到預設', + tempOverrides: '溫度覆蓋', + note: '備註', + notePlaceholder: '關於此耗材的任何備註...', + archive: '歸檔', + restore: '恢復', + noSpools: '尚無耗材。新增您的第一個耗材開始使用。', + noManualSpools: '沒有手動新增的耗材。請先向庫存中新增耗材。', + kProfiles: 'K 值設定', + addKProfile: '新增 K 值設定', + assignSpool: '分配耗材', + unassignSpool: '取消分配', + assignSuccess: '耗材已分配,AMS 槽位已設定', + assignFailed: '分配耗材失敗', + assignMismatchTitle: '材料不符', + assignMismatchMessage: '所選料盤材料 "{{spoolMaterial}}" 與 {{location}} 的料槽材料 "{{trayMaterial}}" 不符。仍要分配嗎?', + assignMismatchConfirm: '仍然分配', + assignPartialMismatchMessage: '料盤材料 "{{spoolMaterial}}" 與 {{location}} 的 "{{trayMaterial}}" 相近但不完全一致。是否繼續?', + assignProfileMismatchMessage: '料盤設定 "{{spoolProfile}}" 與 {{location}} 的料槽設定 "{{trayProfile}}" 不一致。是否繼續?', + selectSpool: '選擇要分配到此槽位的耗材', + assigned: '已分配', + assigning: '分配中...', + searchSpools: '搜尋耗材...', + showAllSpools: '顯示所有耗材', + allMaterials: '所有材料', + filterByBrand: '按品牌篩選...', + showArchived: '顯示已歸檔', + quickAdd: '快速新增(庫存)', + quantity: '數量', + stock: '庫存', + configured: '已設定', + spoolsCreated: '已建立 {{count}} 個耗材', + spoolCreated: '耗材已建立', + spoolUpdated: '耗材已更新', + spoolDeleted: '耗材已刪除', + spoolArchived: '耗材已歸檔', + spoolRestored: '耗材已恢復', + deleteConfirm: '確定要刪除此耗材嗎?此操作無法復原。', + archiveConfirm: '確定要歸檔此耗材嗎?', + advancedSettings: '進階設定', + // Tabs + filamentInfoTab: '耗材資訊', + paProfileTab: 'PA 設定', + filamentInfo: '耗材', + additional: '附加', + // Cloud + loadingPresets: '載入雲端預設中...', + cloudConnected: '雲端已連線', + cloudNotConnected: '雲端未連線(使用預設值)', + // Colors + recentColors: '最近', + searchColors: '搜尋顏色...', + searchResults: '搜尋結果', + allColors: '所有顏色', + commonColors: '常用顏色', + showLess: '顯示更少', + showAll: '顯示全部', + noColorsFound: '沒有顏色匹配您的搜尋', + noResults: '未找到匹配項', + // PA Profiles + selectMaterialFirst: '請先在耗材資訊分頁中選擇材料。', + noPrintersConfigured: '未設定印表機。新增印表機以使用 PA 設定。', + matchingFilter: '匹配', + anyBrand: '任何品牌', + anyVariant: '任何變體', + autoSelect: '自動選擇', + matches: '匹配', + match: '匹配', + noMatches: '無匹配', + connected: '已連線', + offline: '離線', + printerOffline: '印表機離線。連線後檢視校準設定。', + noKProfilesMatch: '沒有 K 值設定匹配所選耗材。', + leftNozzle: '左噴嘴', + rightNozzle: '右噴嘴', + profilesSelected: '個校準設定已選擇', + // Stats & enhanced table + totalInventory: '總庫存', + totalConsumed: '總消耗', + byMaterial: '按材料', + inPrinter: '在印表機中', + lowStock: '庫存不足', + sinceTracking: '自開始追蹤', + loadedInAms: '已裝載到 AMS/外接', + remaining: '剩餘', + weightCheck: '重量檢查', + lastWeighed: '上次稱量', + neverWeighed: '從未稱量', + search: '搜尋耗材...', + showing: '顯示', + to: '到', + of: '共', + show: '顯示', + spools: '個耗材', + spool: '個耗材', + page: '頁', + noSpoolsMatch: '未找到結果', + noSpoolsMatchDesc: '嘗試調整您的搜尋或篩選條件。', + active: '活躍', + archived: '已歸檔', + all: '全部', + used: '已使用', + new: '新的', + clearFilters: '清除篩選', + table: '表格', + cards: '卡片', + net: '淨重', + // Grouping + groupSimilar: '分組', + groupedSpools: '{{count}} 個相同耗材', + groupedRows: '行', + // Column config + columns: '列', + configureColumns: '設定列', + configureColumnsDesc: '拖曳以重新排序列或使用箭頭。使用眼睛圖示切換可見性。', + visible: '可見', + reset: '重設', + cancel: '取消', + applyChanges: '套用更改', + moveUp: '上移', + moveDown: '下移', + hideColumn: '隱藏列', + showColumn: '顯示列', + // Tag linking + linkToSpool: '連結到耗材', + tagLinked: '標籤已連結到耗材', + tagLinkFailed: '連結標籤失敗', + tagAlreadyLinked: '標籤已連結到其他耗材', + unknownTag: '偵測到未知 RFID 標籤', + // Usage history + usageHistory: '使用歷史', + noUsageHistory: '尚無使用紀錄', + printName: '列印名稱', + weightConsumed: '消耗重量', + clearHistory: '清除', + historyCleared: '使用歷史已清除', + fillSourceLabel: '(庫存)', + lowStockThresholdError: '閾值必須在 0.1 到 99.9 之間', + }, + + // Timelapse + timelapse: { + title: '縮時攝影', + create: '建立縮時攝影', + download: '下載', + delete: '刪除', + preview: '預覽', + frameRate: '幀率', + quality: '品質', + processing: '處理中...', + noTimelapses: '無可用縮時攝影', + }, + + // AMS + ams: { + title: 'AMS', + slot: '槽位', + empty: '空', + emptySlot: '空槽位', + unknown: '未知', + humidity: '濕度', + temperature: '溫度', + filamentType: '耗材類型', + filamentColor: '顏色', + remaining: '剩餘', + history: 'AMS 歷史', + noHistory: '無可用歷史', + configureSlot: '設定槽位', + externalSpool: '外接耗材', + profile: '設定', + kFactor: 'K 值', + fill: '填充', + configure: '設定', + used: '已使用', + remainingUnit: '剩餘', + }, + + // Print modal + printModal: { + title: '開始列印', + selectPrinter: '選擇印表機', + selectPlate: '選擇板', + filamentMapping: '耗材對應', + totalCost: '總成本:', + slotRemainingShort: ' - 剩餘 {{grams}}g', + printSettings: '列印設定', + bedLeveling: '熱床調平', + flowCalibration: '流量校準', + vibrationCalibration: '振動校準', + layerInspection: '首層檢查', + timelapse: '縮時攝影', + startPrint: '開始列印', + addToQueue: '新增到佇列', + cancel: '取消', + noPrintersAvailable: '無可用印表機', + printerBusy: '印表機忙碌', + printerOffline: '印表機離線', + sameTypeDifferentColor: '相同類型,不同顏色', + filamentTypeNotLoaded: '耗材類型未裝載', + openCalendar: '開啟日曆', + leftNozzle: '左', + rightNozzle: '右', + leftNozzleTooltip: '左噴嘴', + rightNozzleTooltip: '右噴嘴', + filamentOverride: '耗材覆蓋', + filamentOverrideHint: '可選覆蓋用於基於模型的耗材分配。排程器將使用您選擇的耗材而不是原始 3MF 值進行匹配。', + originalFilament: '原始', + overrideWith: '覆蓋為', + resetToOriginal: '恢復為原始', + insufficientFilamentTitle: '耗材不足', + insufficientFilamentMessage: '部分已分配料盤的剩餘耗材少於本次列印所需:', + insufficientFilamentLine: '{{printer}} - {{slot}}:需要 {{required}}g,剩餘 {{remaining}}g', + printAnyway: '仍然列印', + forceColorMatch: '強制顏色匹配', + staggerPrinterStarts: '錯開印表機啟動', + staggerGroupSize: '群組大小', + staggerInterval: '間隔(分鐘)', + staggerPreview: '{{printers}} 台印表機 → 分成 {{groups}} 組,每組 {{size}} 台,每 {{interval}} 分鐘啟動一組', + staggerLastGroup: '最後一組:{{count}}', + staggerTotal: '總計:{{minutes}} 分鐘', + staggerToPrinters: '分批傳送到 {{count}} 臺印表機', + gcodeInjection: '注入自動列印G-code', + }, + + // Backup + backup: { + title: '備份與恢復', + createBackup: '建立備份', + restoreBackup: '恢復備份', + restoreDescription: '從備份檔案替換所有資料', + downloadBackup: '下載備份', + uploadBackup: '上傳備份', + lastBackup: '上次備份', + autoBackup: '自動備份', + backupNow: '立即備份', + restoreWarning: '警告:恢復備份將覆蓋所有目前資料。', + includeArchives: '包含歸檔', + includeSettings: '包含設定', + includeProfiles: '包含設定檔案', + backupSuccess: '備份建立成功', + restoreSuccess: '備份恢復成功', + backupFailed: '備份失敗', + restoreFailed: '恢復失敗', + restoreNote: '恢復期間虛擬印表機將停止', + + // GitHub Backup + githubBackup: 'GitHub 備份', + enabled: '已啟用', + cloudLoginRequired: '需要登入 Bambu Cloud。請在 設定檔案 → 雲設定檔案 中登入以啟用 GitHub 備份。', + cloudLoginRequiredShort: '需要雲端登入', + githubDescription: '自動將您的設定檔案同步到私有 GitHub 倉庫以進行備份和版本歷史紀錄。', + repositoryUrl: '倉庫 URL', + personalAccessToken: '個人存取權杖', + tokenSaved: '(已儲存)', + enterNewToken: '輸入新權杖以更新', + tokenHint: '具有內容讀寫權限的細粒度權杖', + branch: '分支', + manualOnly: '僅手動', + hourly: '每小時', + daily: '每天', + weekly: '每週', + includeInBackup: '包含在備份中', + kProfiles: 'K 設定檔案', + kProfilesDescription: '來自已連線印表機的壓力推進校準', + noPrintersConnected: '沒有印表機連線', + printersConnected: '{{connected}}/{{total}} 已連線', + cloudProfiles: '雲設定檔案', + cloudProfilesDescription: '來自 Bambu Cloud 的耗材、印表機和工藝預設', + appSettings: '應用程式設定', + appSettingsDescription: 'Bambuddy 設定(完整資料庫)', + spoolInventory: '耗材庫存', + spoolInventoryDescription: '耗材卷軸、使用紀錄和成本追蹤', + printArchives: '列印檔案', + printArchivesDescription: '列印歷史中繼資料(不含 gcode/3MF 檔案)', + lastBackupAt: '上次備份:', + noBackupsYet: '尚無備份', + next: '下次:', + startingBackup: '正在啟動備份...', + test: '測試', + enableBackup: '啟用備份', + testConnection: '測試連線', + enterRepoUrl: '請輸入倉庫 URL', + enterRepoAndToken: '請輸入倉庫 URL 和存取權杖', + repoRequired: '倉庫 URL 為必填項', + tokenRequired: '存取權杖為必填項', + githubBackupEnabled: 'GitHub 備份已啟用', + tokenUpdated: '權杖已更新', + settingsSaved: '設定已儲存', + failedToSave: '儲存失敗:{{message}}', + backupCompleteFiles: '備份完成 - {{count}} 個檔案已更新', + backupSkippedNoChanges: '備份已跳過 - 無更改', + backupFailed2: '備份失敗:{{message}}', + clearedLogs: '已清除 {{count}} 條日誌', + failedToClearLogs: '清除日誌失敗:{{message}}', + + // History + history: '歷史紀錄', + clear: '清除', + date: '日期', + status: '狀態', + commit: '提交', + + // Local Backup + localBackup: '本機備份', + localBackupDescription: '建立 Bambuddy 資料的完整備份,包括資料庫、檔案、上傳和所有檔案。', + downloadBackupLabel: '下載備份', + completeBackupZip: '完整備份:資料庫 + 所有檔案(ZIP)', + download: '下載', + preparingBackup: '正在準備備份...', + creatingArchive: '正在建立備份歸檔...對於大型歸檔可能需要一些時間。', + downloadingFile: '正在下載備份檔案...', + backupDownloaded: '備份下載成功', + failedToCreateBackup: '建立備份失敗:{{message}}', + restore: '恢復', + restoreReplacesAll: '恢復將替換所有資料。', + restoreReplacesAllDetail: '您目前的資料庫和檔案將被完全替換。恢復後需要重新啟動。', + restoreConfirmTitle: '恢復備份', + restoreConfirmMessage: '您確定要從"{{filename}}"恢復嗎?這將完全替換您目前的資料庫和所有檔案。恢復後需要重新啟動應用程式。', + restoreConfirmButton: '恢復備份', + uploadingFile: '正在上傳備份檔案...', + backupRestoredRestart: '備份已恢復。請重新啟動 Bambuddy。', + failedToRestore: '恢復備份失敗。請檢查檔案格式。', + reloadNow: '立即重新載入', + creatingBackup: '正在建立備份', + restoringBackup: '正在恢復備份', + preparing: '準備中...', + processing: '處理中...', + doNotClosePage: '請不要關閉此頁面或離開頁面。對於大型備份,此操作可能需要幾分鐘。', + + // RestoreModal + restoring: '恢復中...', + restoreComplete: '恢復完成', + restoreFailed2: '恢復失敗', + importSettings: '從備份檔案匯入設定', + pleaseWaitRestoring: '請等待資料恢復中', + selectBackupFile: '點選選擇備份檔案(.json 或 .zip)', + duplicateHandling: '重複項處理方式:', + matchPrinters: '印表機', + matchPrintersBy: '按序列號匹配', + matchSmartPlugs: '智慧插座', + matchSmartPlugsBy: '按 IP 位址匹配', + matchNotificationProviders: '通知提供者', + matchNotificationProvidersBy: '按名稱匹配', + matchFilaments: '耗材', + matchFilamentsBy: '按名稱 + 類型 + 品牌匹配', + matchArchives: '檔案', + matchArchivesBy: '按內容雜湊匹配(始終跳過)', + matchPendingUploads: '待上傳', + matchPendingUploadsBy: '按檔名匹配', + matchSettingsTemplates: '設定和範本', + matchSettingsTemplatesBy: '始終覆蓋', + replaceExisting: '替換現有資料', + keepExisting: '保留現有資料', + overwriteDescription: '用備份資料覆蓋已存在的項目', + keepDescription: '僅恢復尚不存在的項目', + overwriteCaution: '注意:', + overwriteWarning: '覆蓋將用備份資料替換您目前的設定。出於安全考慮,印表機存取碼永遠不會被覆蓋。', + cancel: '取消', + processingBackup: '正在處理備份檔案...', + itemsRestored: '已恢復項目', + itemsSkipped: '已跳過項目', + restored: '已恢復', + skippedAlreadyExist: '已跳過(已存在)', + filesCategory: '檔案(3MF、縮圖等)', + andMore: '...還有 {{count}} 項', + newApiKeysGenerated: '已產生新的 API 金鑰', + keysShownOnce: '這些金鑰僅顯示一次。請立即複製!', + copy: '複製', + noDataFound: '在備份檔案中未找到可恢復的資料。', + close: '關閉', + + // Scheduled local backups (#884) + scheduledBackup: '排程備份', + scheduledBackupDescription: '依排程自動建立備份快照。輸出目錄可掛載到 NAS 或外部儲存。', + frequency: '頻率', + backupTime: '時間', + retention: '保留', + retentionDescription: '保留的備份數量', + outputPath: '輸出路徑', + outputPathPlaceholder: '預設:{{path}}', + outputPathDescription: '留空以使用預設位置', + runNow: '立即執行', + backupFiles: '備份檔案', + noScheduledBackups: '尚無備份', + deleteBackup: '刪除', + deleteBackupConfirm: '要刪除此備份檔案嗎?', + backupRunning: '備份進行中…', + scheduledBackupComplete: '備份已成功完成', + scheduledBackupFailed: '備份失敗', + nextBackup: '下次備份', + backupSize: '大小', + utc: 'UTC', + defaultPathLabel: '預設:', + + // Category labels + categories: { + settings: '設定', + notification_providers: '通知提供者', + notification_templates: '通知範本', + smart_plugs: '智慧插座', + printers: '印表機', + filaments: '耗材', + maintenance_types: '維護類型', + archives: '檔案', + projects: '專案', + pending_uploads: '待上傳', + external_links: '外部連結', + api_keys: 'API 金鑰', + }, + }, + + // Tags + tags: { + title: '標籤', + addTag: '新增標籤', + editTag: '編輯標籤', + deleteTag: '刪除標籤', + tagName: '標籤名稱', + tagColor: '標籤顏色', + noTags: '無標籤', + deleteConfirm: '確定要刪除此標籤嗎?', + manageTags: '管理標籤', + }, + + // Upload modal (archives) + uploadModal: { + title: '上傳 3MF 檔案', + dragDrop: '將 .3mf 檔案拖放到此處', + or: '或', + browseFiles: '瀏覽檔案', + extractionInfo: '將從 3MF 檔案中繼資料中自動提取印表機型號。', + uploaded: '已上傳', + failed: '失敗', + uploading: '上傳中...', + upload: '上傳', + uploadFailed: '上傳失敗', + }, + + // Edit archive modal + // Edit Archive Modal + editArchive: { + title: '編輯歸檔', + name: '名稱', + namePlaceholder: '列印名稱', + printer: '印表機', + noPrinter: '無印表機', + project: '專案', + noProject: '無專案', + itemsPrinted: '列印數量', + itemsPrintedHelp: '此列印任務中生產的物品數量', + notes: '備註', + notesPlaceholder: '新增關於此列印的備註...', + externalLink: '外部連結', + externalLinkPlaceholder: 'https://printables.com/model/...', + externalLinkHelp: '連結到 Printables、Thingiverse 或其他來源', + tags: '標籤', + tagsPlaceholder: '新增標籤...', + addMoreTags: '新增更多標籤...', + matchingTags: '匹配"{{query}}"', + existingTags: '現有標籤', + clickToAdd: '(點選新增)', + status: '狀態', + failureReason: '失敗原因', + selectReason: '選擇原因...', + photos: '列印成品照片', + photosHelp: '點選 + 新增列印成品照片', + printResult: '列印成品', + saving: '儲存中...', + // Failure reasons + failureReasons: { + adhesionFailure: '附著力失敗', + spaghettiDetached: '拉絲 / 脫落', + layerShift: '層偏移', + cloggedNozzle: '噴嘴堵塞', + filamentRunout: '耗材用完', + warping: '翹曲', + stringing: '拉絲', + underExtrusion: '擠出不足', + powerFailure: '斷電', + userCancelled: '使用者取消', + other: '其他', + }, + // Archive statuses + statuses: { + completed: '已完成', + failed: '失敗', + aborted: '已取消', + printing: '列印中', + }, + }, + + // K-Profiles + kProfiles: { + title: 'K 值設定', + noPrintersConfigured: '未設定印表機', + addPrinterInSettings: '在設定中新增印表機以管理 K 值設定', + noActivePrinters: '無活躍印表機', + enablePrinterConnection: '啟用印表機連線以檢視其 K 值設定', + loadingProfiles: '載入 K 值設定中...', + printerOffline: '印表機離線', + printerOfflineDesc: '所選印表機未連線。開啟電源以檢視 K 值設定。', + noMatchingProfiles: '無匹配的設定', + noMatchingProfilesDesc: '沒有設定匹配您的搜尋條件', + noKProfiles: '無 K 值設定', + noKProfilesDesc: '未找到 {{diameter}}mm 噴嘴的壓力推進設定', + createFirstProfile: '建立第一個設定', + // Controls + printer: '印表機', + nozzle: '噴嘴', + refresh: '重新整理', + addProfile: '新增設定', + export: '匯出', + import: '匯入', + select: '選擇', + selectAll: '全選', + delete: '刪除', + // Filters + searchPlaceholder: '按名稱或耗材搜尋...', + allExtruders: '所有擠出機', + leftOnly: '僅左側', + rightOnly: '僅右側', + allFlow: '所有流量', + hfOnly: '僅高流量', + sOnly: '僅標準', + sortName: '排序:名稱', + sortKValue: '排序:K 值', + sortFilament: '排序:耗材', + // Dual extruder labels + leftExtruder: '左擠出機', + rightExtruder: '右擠出機', + // Modal + modal: { + addTitle: '新增 K 值設定', + editTitle: '編輯 K 值設定', + profileName: '設定名稱', + profileNamePlaceholder: '我的 PLA 設定', + kValue: 'K 值', + kValuePlaceholder: '0.020', + kValueHelp: '典型範圍:PLA 0.01 - 0.06,PETG 0.02 - 0.10', + filament: '耗材', + selectFilament: '選擇耗材...', + noFilamentsHelp: '未找到耗材。請先在 Bambu Studio 中建立 K 值設定。', + flowType: '流量類型', + highFlow: '高流量', + standard: '標準', + nozzleSize: '噴嘴尺寸', + extruder: '擠出機', + extruders: '擠出機', + left: '左', + right: '右', + notes: '備註(本機儲存)', + notesPlaceholder: '新增關於此設定的備註...', + notesHelp: '備註儲存在 Bambuddy 中,不在印表機上', + syncing: '與印表機同步中...', + savingExtruder: '儲存到擠出機 {{current}}/{{total}}...', + pleaseWait: '請稍候', + }, + // Delete confirmation + deleteConfirm: { + title: '刪除設定', + cannotUndo: '此操作無法復原', + message: '確定要從印表機刪除"{{name}}"嗎?', + }, + // Bulk delete + bulkDelete: { + title: '刪除設定', + cannotUndo: '此操作無法復原', + message: '確定要從印表機刪除 {{count}} 個選中的設定嗎?', + }, + // Toast + toast: { + profileSaved: 'K 值設定已儲存', + profilesSaved: 'K 值設定已儲存到 {{count}} 個擠出機', + selectAtLeastOneExtruder: '請至少選擇一個擠出機', + profileDeleted: 'K 值設定已刪除', + profilesDeleted: '已刪除 {{count}} 個設定', + exportedProfiles: '已匯出 {{count}} 個設定', + importedProfiles: '已匯入 {{count}} / {{total}} 個設定', + noProfilesToExport: '無可匯出的設定', + invalidFileFormat: '無效的檔案格式', + failedToParseImport: '解析匯入檔案失敗', + failedToSaveBatch: '批次儲存 K 值設定失敗', + noteSaved: '備註已儲存', + failedToSaveNote: '儲存備註失敗', + }, + // Permissions + permission: { + noRead: '您沒有重新整理設定的權限', + noCreate: '您沒有新增設定的權限', + noUpdate: '您沒有更新 K 值設定的權限', + noDelete: '您沒有刪除 K 值設定的權限', + noExport: '您沒有匯出設定的權限', + noImport: '您沒有匯入設定的權限', + }, + }, + + // Virtual Printer + virtualPrinter: { + title: '虛擬印表機', + running: '執行中', + stopped: '已停止', + description: { + default: '啟用虛擬印表機,使其在 Bambu Studio 和 OrcaSlicer 中可見。傳送到此印表機的檔案將直接歸檔而不列印。', + proxy: '啟用代理,將切片軟體流量中繼到真實印表機,允許在任何網路上遠端列印。', + }, + enable: { + title: '啟用虛擬印表機', + visibleInSlicer: '在切片軟體發現中顯示為"Bambuddy"', + proxyingTo: '代理到 {{name}}', + notActive: '未啟用', + }, + model: { + title: '印表機型號', + description: '選擇要模擬的印表機型號。', + restartWarning: '更改型號將重新啟動虛擬印表機', + }, + accessCode: { + title: '存取碼', + isSet: '存取碼已設定', + notSet: '未設定存取碼 - 需要設定才能啟用', + placeholder: '輸入 8 位字元程式碼', + placeholderChange: '輸入新程式碼以更改', + hint: '必須恰好 8 個字元。切片軟體使用此程式碼進行認證。', + charCount: '({{count}}/8)', + }, + targetPrinter: { + title: '目標印表機', + configured: '代理目標已設定', + notConfigured: '未選擇目標印表機 - 代理模式需要設定', + placeholder: '選擇印表機...', + hint: '選擇要將切片軟體流量代理到的印表機。印表機必須處於區域網路模式。', + noPrinters: '未設定印表機。請先新增印表機以使用代理模式。', + }, + remoteInterface: { + title: '網路介面覆蓋', + configured: '介面覆蓋已啟用', + optional: '可選 - 當自動檢測的 IP 不正確時使用(例如多網路卡、Docker、VPN)', + placeholder: '自動檢測(預設)...', + hint: '覆蓋透過 SSDP 廣播並在 TLS 憑證中使用的 IP 位址。在 Bambuddy 有多個網路介面時很有用。', + }, + mode: { + title: '模式', + archive: '歸檔', + archiveDesc: '立即歸檔檔案', + review: '審核', + reviewDesc: '歸檔前審核', + queue: '佇列', + queueDesc: '歸檔並新增到佇列', + proxy: '代理', + proxyDesc: '中繼到真實印表機', + }, + autoDispatch: { + title: '自動派發', + description: '新增到佇列時自動開始列印。關閉後,列印任務等待手動派發。', + }, + setupRequired: { + title: '需要設定', + description: '虛擬印表機功能需要額外的系統設定才能工作。包括埠轉發、防火牆規則和平臺特定設定。', + readGuide: '啟用前請閱讀設定指南', + }, + howItWorks: { + title: '工作原理', + step1: '在同一區域網路中,虛擬印表機會透過發現機制自動出現在您的切片軟體(Bambu Studio / OrcaSlicer)中。從其他網路,透過 IP 位址和存取碼手動新增。', + step2: '在歸檔、審核和佇列模式下,使用切片軟體中的"傳送"按鈕將 3MF 檔案上傳到 Bambuddy。切片軟體會顯示"列印成功"— 檔案已儲存,未列印。', + step3: '在代理模式下,虛擬印表機將所有流量中繼到真實印表機 — 列印會立即開始,就像直接連線一樣。', + }, + status: { + title: '狀態詳情', + printerName: '印表機名稱', + model: '型號', + serialNumber: '序列號', + mode: '模式', + pendingFiles: '待處理檔案', + targetPrinter: '目標印表機', + ftpPort: 'FTP 連接埠', + mqttPort: 'MQTT 連接埠', + ftpConnections: 'FTP 連線', + mqttConnections: 'MQTT 連線', + }, + toast: { + updated: '虛擬印表機設定已更新', + failedToUpdate: '更新設定失敗', + accessCodeRequired: '請先設定存取碼', + targetPrinterRequired: '請先選擇目標印表機', + bindIpRequired: '請先設定繫結 IP', + accessCodeEmpty: '存取碼不能為空', + accessCodeLength: '存取碼必須恰好 8 個字元', + created: '虛擬印表機已建立', + failedToCreate: '建立虛擬印表機失敗', + deleted: '虛擬印表機已刪除', + failedToDelete: '刪除虛擬印表機失敗', + }, + list: { + title: '虛擬印表機', + add: '新增', + addFirst: '新增虛擬印表機', + empty: '未設定虛擬印表機。新增一個以開始使用。', + }, + bindIp: { + title: '繫結介面', + placeholder: '選擇介面...', + hint: '此虛擬印表機繫結的網路介面。每臺印表機必須唯一。', + }, + proxy: { + accessCodeHint: '在代理模式下,在切片軟體中使用目標印表機的存取碼。連線會透明轉發到真實印表機。', + }, + addDialog: { + title: '新增虛擬印表機', + name: '名稱', + hint: '建立後可以設定存取碼、目標印表機和其他設定。', + create: '建立', + }, + deleteConfirm: { + title: '刪除虛擬印表機', + message: '確定要刪除"{{name}}"嗎?這將停止此印表機的所有服務。', + }, + }, + + // Model Viewer + modelViewer: { + openInSlicer: '在切片軟體中開啟', + tabs: { + model: '3D 模型', + gcode: 'G-code 預覽', + }, + notAvailable: '不可用', + notSliced: '未切片', + plates: '板', + allPlates: '所有板', + plateNumber: '板 {{number}}', + plateCount: '{{count}} 個板', + plateCount_other: '{{count}} 個板', + objectCount: '{{count}} 個物件', + objectCount_other: '{{count}} 個物件', + filamentCount: '{{count}} 種耗材', + filamentCount_other: '{{count}} 種耗材', + eta: '預計 {{minutes}} 分鐘', + noPreview: '此檔案無可用預覽', + pagination: { + pageOf: '第 {{current}} / {{total}} 頁', + prev: '上一頁', + next: '下一頁', + }, + errors: { + failedToLoad: '載入檔案失敗', + noMeshes: '3MF 檔案中未找到網格', + unsupportedFormat: '不支援的檔案格式', + }, + }, + + // Maintenance type descriptions (built-in) + maintenanceDescriptions: { + lubricateCarbonRods: '在碳纖維杆上塗抹潤滑劑以確保順暢運動', + lubricateRails: '在線性導軌上塗抹潤滑劑以確保順暢運動', + cleanNozzle: '清潔熱端和噴嘴以防止堵塞', + checkBelts: '檢查皮帶張力以確保列印精度', + cleanBuildPlate: '清潔列印板以獲得更好的附著力', + checkExtruder: '檢查擠出機齒輪磨損情況', + checkCooling: '確保冷卻風扇正常工作', + generalInspection: '印表機綜合檢查', + cleanCarbonRods: '清潔碳纖維杆以減少摩擦', + lubricateSteelRods: '在鋼杆上塗抹潤滑劑以確保順暢運動', + cleanSteelRods: '清潔鋼杆以減少摩擦', + cleanLinearRails: '擦拭線性導軌以清除灰塵和碎屑', + checkPtfeTube: '檢查 PTFE 管的磨損或損壞', + replaceHepaFilter: '更換 HEPA 過濾器以保證空氣品質', + replaceCarbonFilter: '更換活性炭過濾器', + lubricateLeftNozzleRail: '潤滑左噴嘴導軌(H2 系列)', + }, + + // Smart Plugs + smartPlugs: { + offline: '離線', + admin: '管理', + openPlugAdminPage: '開啟插座管理頁面', + deleteSmartPlug: '刪除智慧插座', + turnOnSmartPlug: '開啟智慧插座', + turnOffSmartPlug: '關閉智慧插座', + turnOn: '開啟', + turnOff: '關閉', + addSmartPlug: { + scanningNetwork: '掃描網路中...', + chooseEntity: '選擇實體...', + connectionFailed: '連線失敗', + searchEntities: '搜尋實體...', + searchPowerSensors: '搜尋功率感測器...', + searchEnergySensors: '搜尋能量感測器...', + placeholders: { + plugName: '客廳插座', + mqttStateOnValue: 'ON、true、1', + mqttSameAsPower: '與功率主題相同,或不同', + }, + }, + // SmartPlugCard + linkedTo: '連結到:', + monitorOnly: '僅監控', + alerts: '警報', + scheduleOn: '開啟 {{time}}', + scheduleOff: '關閉 {{time}}', + on: '開啟', + off: '關閉', + power: '功率', + kwhToday: '今日kWh', + settings: '設定', + automationSettings: '自動化設定', + showInSwitchbar: '在開關欄顯示', + quickAccessSidebar: '從側邊欄快速存取', + enabled: '已啟用', + enableAutomation: '為此插座啟用自動化', + autoOn: '自動開啟', + autoOnDescription: '列印開始時開啟', + autoOff: '自動關閉', + autoOffDescription: '列印完成時關閉(一次性)', + autoOffPersistent: '保持啟用', + autoOffPersistentDescription: '在列印之間保持啟用而非一次性', + turnOffDelayMode: '關閉延遲模式', + time: '時間', + temp: '溫度', + delayMinutes: '延遲(分鐘)', + tempThreshold: '溫度閾值(°C)', + tempThresholdDescription: '當噴嘴冷卻到此溫度以下時關閉', + edit: '編輯', + deleteConfirm: '確定要刪除"{{name}}"嗎?此操作無法復原。', + turnOnConfirm: '確定要開啟"{{name}}"嗎?', + turnOffConfirm: '確定要關閉"{{name}}"嗎?這將切斷連線裝置的電源。', + failedToTurn: '無法{{action}}"{{name}}"', + unknown: '未知', + // AddSmartPlugModal + addTitle: '新增智慧插座', + editTitle: '編輯智慧插座', + stopScanning: '停止掃描', + discoverTasmota: '發現Tasmota裝置', + foundDevices: '找到{{count}}個裝置 - 點選選擇:', + noDevicesFound: '未在您的網路中找到Tasmota裝置', + haNotConfigured: 'Home Assistant未設定。請在以下位置設定', + haSettingsPath: '設定 → 網路 → Home Assistant', + selectEntity: '選擇實體 *', + ipAddress: 'IP 位址 *', + nameLabel: '名稱 *', + username: '使用者名稱', + password: '密碼', + authHint: '如果您的Tasmota裝置不需要認證,請留空', + linkToPrinter: '連結印表機', + noPrinter: '無印表機(僅手動控制)', + linkingDescription: '連結後可在列印開始/完成時自動開關', + powerAlerts: '功率警報', + alertAbove: '高於時警報(W)', + alertBelow: '低於時警報(W)', + alertDescription: '當電力消耗超過這些閾值時收到通知。留空以停用該方向。', + dailySchedule: '每日計畫', + turnOnAt: '開啟時間', + turnOffAt: '關閉時間', + scheduleDescription: '每天在這些時間自動開關插座。留空以跳過該操作。', + showOnPrinterCard: '在印表機卡片上顯示', + displayOnPrinterCard: '在印表機卡片上顯示按鈕', + connectedResult: '已連線!', + deviceLabel: '裝置:{{name}} - ', + stateLabel: '狀態:{{state}}', + test: '測試', + delete: '刪除', + save: '儲存', + add: '新增', + cancel: '取消', + failedToStartScan: '無法開始掃描', + nameRequired: '名稱為必填項', + entityRequired: 'Home Assistant插座需要實體', + mqttTopicRequired: '必須為功率、能源或狀態監控設定至少一個MQTT主題', + loadingEntities: '正在載入實體...', + loading: '載入中...', + failedToLoadEntities: '載入實體失敗:{{error}}', + noEntitiesMatching: '未找到匹配"{{search}}"的實體', + noEntitiesAvailable: '無可用實體', + searchingEntities: '搜尋所有實體(找到{{count}}個)', + showingEntities: '顯示 switch、light、input_boolean({{count}}個可用)', + energyMonitoringOptional: '能源監控(可選)', + energyMonitoringHint: '搜尋並選擇提供功率/能源資料的感測器。', + powerSensorW: '功率感測器(W)', + energyTodayKwh: '今日能源(kWh)', + totalEnergyKwh: '總能源(kWh)', + noMatchingSensors: '無匹配的感測器', + none: '無', + mqttNotConfigured: 'MQTT代理未設定。請在以下位置設定代理地址', + mqttSettingsPath: '設定 → 網路 → MQTT發布', + mqttNotConfiguredSuffix: '(您不需要啟用發布,只需填寫代理詳細資訊)。', + mqttMonitorOnlyDescription: 'MQTT插座透過MQTT訂閱接收功率/能源資料。開關控制不可用 - 請使用您的MQTT代理或家庭自動化系統。', + powerMonitoring: '功率監控', + energyMonitoring: '能源監控', + stateMonitoring: '狀態監控', + optional: '可選', + topic: '主題', + jsonPath: 'JSON路徑', + multiplier: '乘數', + onValue: 'ON值', + mqttPowerHint: 'JSON路徑從JSON負載中提取值(例如"power_l1")。如果主題發布原始數值,請留空。\n乘數:mW→W使用0.001,kW→W使用1000。', + mqttEnergyHint: 'JSON路徑從JSON負載中提取值。原始值請留空。\n乘數:Wh→kWh使用0.001,MWh→kWh使用1000。', + mqttStateHint: 'JSON路徑從JSON負載中提取值。原始值請留空。\nON值:表示"ON"的確切字串。留空以自動檢測(ON、true、1)。', + // REST smart plug + restControl: '控制', + restOnUrl: '開啟 URL', + restOffUrl: '關閉 URL', + restOnBody: '開啟請求內容', + restOffBody: '關閉請求內容', + restMethod: 'HTTP 方法', + restHeaders: '自訂標頭(JSON)', + restStatusUrl: '狀態 URL', + restStatusPath: '狀態 JSON 路徑', + restStatusOnValue: 'ON 值', + restPowerUrl: '功率URL', + restPowerPath: '功率 JSON 路徑', + restPowerMultiplier: '功率乘數', + restEnergyUrl: '能耗URL', + restEnergyPath: '能耗 JSON 路徑', + restEnergyMultiplier: '能耗乘數', + restUrlRequired: 'REST 插座至少需要一個 URL(ON 或 OFF)', + restHeadersHint: '例如:{"Authorization": "Bearer your-token"}', + restBodyHint: '例如:ON、{"state": "on"}', + restStatusHint: '用於輪詢目前狀態的 URL', + restPathHint: '例如:state 或 data.power.status', + restPowerUrlHint: '功率資料的獨立URL(留空則使用狀態URL)', + restEnergyUrlHint: '能耗資料的獨立URL(留空則使用狀態URL)', + restEnergyHint: '每個值可以使用獨立的URL,或回退到狀態 URL。使用乘數進行單位轉換(例如:0.001 將 Wh 轉換為 kWh)。', + testConnection: '測試連線', + connectionSuccess: '連線成功', + noSwitchesInSwitchbar: '開關欄中沒有開關', + enableSwitchbarHint: '在設定 > 智慧插座中啟用"在開關欄顯示"', + }, + + // Notifications + notifications: { + // Provider types + providerTypes: { + callmebot: 'CallMeBot/WhatsApp', + ntfy: 'ntfy', + pushover: 'Pushover', + telegram: 'Telegram', + email: '電子郵件', + discord: 'Discord', + webhook: 'Webhook', + homeassistant: 'Home Assistant', + }, + // Provider descriptions + providerDescriptions: { + email: 'SMTP 電子郵件通知', + telegram: '透過 Telegram 機器人傳送通知', + discord: '透過 Webhook 傳送到 Discord 頻道', + ntfy: '免費、可自託管的推送通知', + pushover: '簡單、可靠的推送通知', + callmebot: '透過 CallMeBot 免費傳送 WhatsApp 通知', + webhook: '通用 HTTP POST 到任意 URL', + homeassistant: 'Home Assistant 儀表板中的持久通知', + }, + // NotificationProviderCard + lastSuccess: '上次:{{date}}', + error: '錯誤', + printer: '印表機:', + allPrinters: '所有印表機', + sendTestNotification: '傳送測試通知', + eventSettings: '事件設定', + enabled: '已啟用', + sendFromProvider: '從此提供者傳送通知', + // Event categories + printEvents: '列印事件', + printerStatus: '印表機狀態', + amsAlarms: 'AMS 警報', + amsHtAlarms: 'AMS-HT 警報', + printQueue: '列印佇列', + // Event tags (badges) + start: '開始', + plateCheck: '熱床檢測', + complete: '完成', + failed: '失敗', + stopped: '已停止', + progress: '進度', + offline: '離線', + lowFilament: '耗材不足', + maintenance: '維護', + amsHumidity: 'AMS 濕度', + amsTemp: 'AMS 溫度', + amsHtHumidity: 'AMS-HT 濕度', + amsHtTemp: 'AMS-HT 溫度', + bedCooled: '熱床已冷卻', + firstLayer: '首層完成', + quiet: '免打擾', + digest: '摘要 {{time}}', + // Event labels (expanded settings) + printStarted: '列印已開始', + plateNotEmpty: '熱床非空', + plateNotEmptyDescription: '列印前偵測到物體', + printCompleted: '列印已完成', + bedCooledLabel: '熱床已冷卻', + bedCooledDescription: '列印後熱床溫度降至閾值以下', + firstLayerCompleteLabel: '首層列印完成', + firstLayerCompleteDescription: '首層完成時傳送帶照片的通知', + missingSpoolAssignmentLabel: '缺少料卷分配', + missingSpoolAssignmentDescription: '當列印開始且所需料盤沒有分配料卷時傳送通知', + printFailed: '列印失敗', + printStopped: '列印已停止', + progressMilestones: '進度里程碑', + progressMilestonesDescription: '在 25%、50%、75% 時通知', + printerOffline: '印表機離線', + printerError: '印表機錯誤', + lowFilamentLabel: '耗材不足', + maintenanceDue: '需要維護', + maintenanceDueDescription: '需要維護時通知', + amsHumidityHigh: 'AMS 濕度過高', + amsHumidityHighDescription: '普通 AMS 濕度超過閾值', + amsTemperatureHigh: 'AMS 溫度過高', + amsTemperatureHighDescription: '普通 AMS 溫度超過閾值', + amsHtHumidityHigh: 'AMS-HT 濕度過高', + amsHtHumidityHighDescription: 'AMS-HT 濕度超過閾值', + amsHtTemperatureHigh: 'AMS-HT 溫度過高', + amsHtTemperatureHighDescription: 'AMS-HT 溫度超過閾值', + // Queue events + jobAdded: '任務已新增', + jobAddedDescription: '任務已新增到佇列', + jobAssigned: '任務已分配', + jobAssignedDescription: '基於模型的任務已分配給印表機', + jobStarted: '任務已開始', + jobStartedDescription: '佇列任務已開始列印', + jobWaiting: '任務等待中', + jobWaitingDescription: '任務正在等待耗材或印表機', + jobSkipped: '任務已跳過', + jobSkippedDescription: '任務已跳過(上一個失敗)', + jobFailed: '任務失敗', + jobFailedDescription: '任務啟動失敗', + queueComplete: '佇列已完成', + queueCompleteDescription: '所有佇列任務已完成', + // Quiet hours + quietHours: '免打擾時段', + noNotificationsDuring: '在此時段內不傳送通知', + editProviderToChangeQuietHours: '編輯提供者以更改免打擾時段', + // Daily digest + dailyDigest: '每日摘要', + batchNotifications: '將通知彙總為每日摘要', + sendAt: '傳送於 {{time}}', + editProviderToChangeDigestTime: '編輯提供者以更改摘要時間', + // Actions + edit: '編輯', + deleteProvider: '刪除通知提供者', + deleteConfirm: '確定要刪除"{{name}}"嗎?此操作無法復原。', + delete: '刪除', + // AddNotificationModal + addTitle: '新增通知提供者', + editTitle: '編輯通知提供者', + nameLabel: '名稱 *', + namePlaceholder: '我的通知', + providerTypeLabel: '提供者類型 *', + configuration: '設定', + testConfiguration: '測試設定', + printerFilter: '印表機篩選', + onlyFromPrinter: '僅傳送來自此印表機的事件通知', + quietHoursDnd: '免打擾時段', + quietStart: '開始', + quietEnd: '結束', + dailyDigestLabel: '每日摘要', + sendDigestAt: '傳送摘要於', + digestCollected: '事件將被收集並在此時間作為單條摘要傳送', + notificationEvents: '通知事件', + progressPercent: '(25%、50%、75%)', + bedCooledAfterPrint: '(列印完成後)', + cancel: '取消', + save: '儲存', + add: '新增', + nameRequired: '名稱為必填項', + fieldRequired: '{{field}}為必填項', + // Config field labels + phoneNumber: '電話號碼', + apiKey: 'API 金鑰', + serverUrl: '伺服器 URL', + topic: '主題', + authToken: '認證權杖', + userKey: '使用者金鑰', + appToken: '應用程式權杖', + priority: '優先順序', + botToken: '機器人權杖', + chatId: '聊天 ID', + smtpServer: 'SMTP 伺服器', + smtpPort: 'SMTP 連接埠', + security: '安全', + authentication: '認證', + username: '使用者名稱', + password: '密碼', + fromEmail: '寄件人信箱', + toEmail: '收件人信箱', + webhookUrl: 'Webhook URL', + payloadFormat: '負載格式', + authorization: '授權', + titleFieldName: '標題欄位名', + messageFieldName: '訊息欄位名', + // NotificationTemplateEditor + editTemplate: '編輯範本:{{name}}', + titleLabel: '標題', + bodyLabel: '正文', + titlePlaceholder: '通知標題...', + bodyPlaceholder: '通知正文...', + availableVariables: '可用變數', + clickToInsert: '點選插入到正文游標位置', + livePreview: '即時預覽', + hide: '隱藏', + show: '顯示', + loadingPreview: '載入預覽中...', + enterTemplateContent: '輸入範本內容以檢視預覽', + titlePreview: '標題:', + bodyPreview: '正文:', + resetToDefault: '恢復預設', + titleRequired: '標題為必填項', + bodyRequired: '正文為必填項', + // NotificationLogViewer + notificationLog: '通知日誌', + showFailedOnly: '僅顯示失敗', + last24Hours: '最近 24 小時', + last7Days: '最近 7 天', + last30Days: '最近 30 天', + last90Days: '最近 90 天', + justNow: '剛剛', + noFailedNotifications: '沒有失敗的通知', + noNotificationsLogged: '沒有通知紀錄', + unknownProvider: '未知提供者', + logTitle: '標題', + logMessage: '訊息', + logError: '錯誤', + logProvider: '提供者:{{type}}', + logTime: '時間:{{time}}', + refresh: '重新整理', + clearOld: '清除舊紀錄', + statsSummary: '最近 {{days}} 天:', + statsNotifications: '條通知', + statsSent: '{{count}} 條已傳送', + statsFailed: '{{count}} 條失敗', + // Event type labels (for log viewer) + eventTypes: { + print_start: '列印已開始', + print_complete: '列印完成', + print_failed: '列印失敗', + print_stopped: '列印已停止', + print_progress: '進度', + printer_offline: '印表機離線', + printer_error: '印表機錯誤', + filament_low: '耗材不足', + maintenance_due: '需要維護', + test: '測試', + }, + userEmail: { + title: '通知', + emailNotifications: '郵件通知', + emailNotificationsDesc: '接收您自己列印任務的郵件通知。郵件將透過進階身份驗證中設定的 SMTP 設定傳送。', + sendingTo: '通知將傳送至', + noEmailWarning: '您的帳戶沒有郵件地址。請聯絡管理員新增。', + printJobNotifications: '列印任務通知', + printJobNotificationsDesc: '選擇哪些事件會觸發您提交的列印任務的郵件通知。', + printJobStarts: '列印任務開始', + printJobStartsDesc: '當您的列印任務開始時收到通知。', + printJobFinishes: '列印任務完成', + printJobFinishesDesc: '當您的列印任務成功完成時收到通知。', + printErrors: '列印錯誤', + printErrorsDesc: '當您的列印任務失敗或遇到錯誤時收到通知。', + printJobStops: '列印任務停止', + printJobStopsDesc: '當您的列印任務被取消或停止時收到通知。', + saveSuccess: '通知偏好設定已儲存。', + saveError: '儲存通知偏好設定失敗。', + }, + }, + + // Rich Text Editor + richTextEditor: { + bold: '粗體', + italic: '斜體', + underline: '底線', + bulletList: '無序列表', + numberedList: '有序列表', + alignLeft: '左對齊', + alignCenter: '居中對齊', + alignRight: '右對齊', + addLink: '新增連結', + removeLink: '移除連結', + }, + + // External Links + externalLinks: { + noLinksConfigured: '未設定外部連結', + deleteLink: '刪除連結', + removeCustomIcon: '移除自訂圖示', + openInNewTab: '在新標籤頁中開啟', + placeholders: { + linkName: '我的連結', + }, + }, + + // Keyboard Shortcuts Modal + keyboardShortcuts: { + title: '鍵盤快捷鍵', + navigation: '導航', + archivesSection: '歸檔', + kProfilesSection: 'K 值設定', + generalSection: '通用', + shortcuts: { + goToPrinters: '前往印表機', + goToArchives: '前往歸檔', + goToQueue: '前往佇列', + goToStats: '前往統計', + goToProfiles: '前往雲端設定', + goToSettings: '前往設定', + focusSearch: '聚焦搜尋', + openUploadModal: '開啟上傳對話方塊', + clearSelection: '清除選擇 / 取消焦點', + contextMenu: '卡片右鍵選單', + refreshProfiles: '重新整理設定', + newProfile: '新增設定', + exitSelectionMode: '結束選擇模式', + showHelp: '顯示此協助', + }, + footer: '按 Esc 或點選外部關閉', + }, + + // Notification Log + notificationLog: { + title: '通知日誌', + events: { + printStarted: '列印開始', + printComplete: '列印完成', + printFailed: '列印失敗', + printStopped: '列印停止', + progress: '進度', + printerOffline: '印表機離線', + printerError: '印表機錯誤', + lowFilament: '耗材不足', + maintenanceDue: '維護到期', + test: '測試', + }, + timeAgo: { + justNow: '剛剛', + minutesAgo: '{{minutes}} 分鐘前', + hoursAgo: '{{hours}} 小時前', + }, + }, + + // Restore/Backup Modal + restoreBackup: { + title: '恢復備份', + restoring: '恢復中...', + restoreComplete: '恢復完成', + restoreFailed: '恢復失敗', + importSettings: '從備份檔案匯入設定', + pleaseWait: '請稍候,正在恢復您的資料', + clickToSelect: '點選選擇備份檔案(.json 或 .zip)', + howDuplicateHandling: '重複處理方式:', + categories: { + printers: '印表機', + smartPlugs: '智慧插座', + notificationProviders: '通知提供者', + filaments: '耗材', + archives: '歸檔', + pendingUploads: '待處理上傳', + settingsTemplates: '設定和範本', + }, + matchingInfo: { + printers: '按序列號匹配', + smartPlugs: '按 IP 位址匹配', + notificationProviders: '按名稱匹配', + filaments: '按名稱 + 類型 + 品牌匹配', + archives: '按內容雜湊匹配', + pendingUploads: '按檔名匹配', + settingsTemplates: '始終覆蓋', + }, + replaceExisting: '替換現有資料', + keepExisting: '保留現有資料', + replaceDescription: '用備份資料覆蓋已存在的項目', + keepDescription: '僅恢復不存在的項目', + caution: '注意:', + cautionText: '覆蓋將用備份資料替換您目前的設定。出於安全考慮,印表機存取碼永遠不會被覆蓋。', + itemsRestored: '已恢復項目', + itemsSkipped: '已跳過項目', + restored: '已恢復', + skipped: '已跳過(已存在)', + filesLabel: '檔案(3MF、縮圖等)', + newApiKeysGenerated: '已產生新 API 金鑰', + newApiKeysWarning: '這些金鑰僅顯示一次。請立即複製!', + processingBackup: '處理備份檔案中...', + noDataFound: '備份檔案中未找到可恢復的資料。', + failedToRestore: '恢復備份失敗。請檢查檔案格式。', + }, + + // Backup Export Modal + backupExport: { + title: '匯出備份', + selectData: '選擇要包含的資料', + selectAll: '全選', + selectNone: '全不選', + categoryDescriptions: { + settings: '語言、主題、更新偏好', + notifications: 'ntfy、Pushover、Discord 等', + templates: '自訂訊息範本', + smartPlugs: 'Tasmota 插座設定', + externalLinks: '側邊欄外部服務連結', + printers: '印表機資訊(不含存取碼)', + plateDetection: '空列印板參考影像', + filaments: '耗材類型和成本', + maintenance: '自訂維護計畫', + archives: '所有列印資料 + 檔案(3MF、縮圖、照片)', + projects: '專案、材料清單和附件', + pendingUploads: '虛擬印表機待審核的上傳', + apiKeys: 'Webhook API 金鑰(匯入時產生新金鑰)', + }, + requiresPrinters: '需要選擇印表機', + zipFileWarning: '將建立 ZIP 檔案。', + zipFileDescription: '包括所有 3MF 檔案、縮圖、縮時攝影和照片。這可能需要一些時間並產生較大的檔案。', + includeAccessCodes: '包含存取碼', + includeAccessCodesDescription: '用於轉移到另一臺機器', + includeAccessCodesWarning: '存取碼將以明文形式包含。請妥善保管此備份檔案!', + categoriesSelected: '已選擇 {{selectedCount}} 個類別', + }, + + // Pending Uploads Panel + pendingUploads: { + placeholders: { + notes: '新增關於此列印的備註...', + }, + discardUpload: '丟棄上傳', + archiveAllUploads: '歸檔所有上傳', + discardAllUploads: '丟棄所有上傳', + archive: '歸檔', + timeAgo: { + justNow: '剛剛', + minutesAgo: '{{minutes}} 分鐘前', + hoursAgo: '{{hours}} 小時前', + daysAgo: '{{days}} 天前', + }, + }, + + // API Browser + apiBrowser: { + placeholders: { + requestBody: 'JSON 請求體...', + searchEndpoints: '搜尋端點...', + }, + }, + + // Configure AMS Slot Modal + configureAmsSlot: { + title: '設定 AMS 槽位', + slotConfigured: '槽位已設定!', + configuringSlot: '正在設定槽位:', + slotLabel: '{{ams}} 槽位 {{slot}}', + searchPresets: '搜尋預設...', + colorPlaceholder: '顏色名稱或十六進位(例如:棕色、FF8800)', + clearCustomColor: '清除自訂顏色', + noCloudPresets: '無雲端預設。登入拓竹雲以同步。', + noPresetsAvailable: '無可用預設。登入拓竹雲或匯入本機設定。', + noMatchingPresets: '未找到匹配的預設。', + custom: '自訂', + builtin: '內建', + settingsSentToPrinter: '設定已傳送到印表機', + filamentProfile: '耗材設定', + kProfileLabel: 'K 值設定(壓力推進)', + filteringFor: '篩選:{{material}}', + noKProfile: '無 K 值設定(使用預設值 0.020)', + noMatchingKProfiles: '未找到匹配的 K 值設定。將使用預設 K=0.020。', + selectFilamentFirst: '請先選擇耗材設定', + kFromCalibration: 'K={{value}}(來自印表機校準)', + customColorLabel: '自訂顏色(可選)', + presetColors: '{{name}} 顏色:', + showLessColors: '顯示更少顏色', + showMoreColors: '顯示更多顏色', + clear: '清除', + hexLabel: '十六進位:#{{hex}}', + resetting: '重設中...', + resetSlot: '重設槽位', + cancel: '取消', + configuring: '設定中...', + configureSlot: '設定槽位', + }, + + // GitHub Backup Settings + githubBackup: { + title: 'GitHub 備份', + history: '歷史', + downloadBackup: '下載備份', + restoreBackup: '恢復備份', + noBackupsYet: '尚無備份', + }, + + // Email Settings + emailSettings: { + placeholders: { + fromName: 'BamBuddy', + }, + }, + + // Tag Management Modal + tagManagement: { + searchTags: '搜尋標籤...', + renameTag: '重新命名標籤', + deleteTag: '刪除標籤', + }, + + // Notification Template Editor + notificationTemplates: { + placeholders: { + title: '通知標題...', + body: '通知正文...', + }, + }, + + // Batch Tag Modal + batchTag: { + placeholders: { + newTag: '輸入新標籤...', + }, + }, + + // Photo Gallery Modal + photoGallery: { + deletePhoto: '刪除照片', + }, + + // Filament Hover Card + filamentHoverCard: { + copySpoolUuid: '複製耗材 UUID', + }, + + // K Profiles View + kProfilesView: { + hasNote: '有備註', + copyProfile: '複製設定', + }, + + // Layout/Navigation + layout: { + openMenu: '開啟選單', + noPermissionSystemInfo: '您沒有檢視系統資訊的權限', + }, + + // Dashboard + dashboard: { + dragToReorder: '拖曳以重新排列', + hideWidget: '隱藏小工具', + }, + + // Notification Provider Card + notificationProviderCard: { + deleteNotificationProvider: '刪除通知提供者', + }, + + // File Manager Modal + fileManagerModal: { + closeFileManager: '關閉檔案管理器', + sortFiles: '排序檔案', + goToParentFolder: '返回上級資料夾', + threeView: '3D 檢視', + }, + + // Embedded Camera Viewer + embeddedCameraViewer: { + refreshStream: '重新整理流', + close: '關閉', + zoomOut: '縮小', + resetZoom: '重設縮放', + zoomIn: '放大', + dragToResize: '拖曳調整大小', + }, + + // Timelapse Viewer + timelapseViewer: { + skipBack5s: '後退 5 秒', + skipForward5s: '前進 5 秒', + }, + + // Notification Providers + notificationProviders: { + descriptions: { + email: 'SMTP 郵件通知', + telegram: '透過 Telegram 機器人通知', + discord: '透過 Webhook 傳送到 Discord 頻道', + ntfy: '免費、可自託管的推送通知', + pushover: '簡單、可靠的推送通知', + callmebot: '透過 CallMeBot 的免費 WhatsApp 通知', + webhook: '通用 HTTP POST 到任意 URL', + }, + }, + + // Log Viewer + logViewer: { + searchPlaceholder: '搜尋訊息或日誌名稱...', + noLogEntries: '未找到日誌條目', + }, + + // Switchbar Popover + switchbarPopover: { + noSwitchesInSwitchbar: '切換欄中沒有開關', + }, + + // Project Page Modal + projectPageModal: { + placeholders: { + title: '標題', + designer: '設計師', + license: '許可證', + description: '輸入描述...', + profileTitle: '設定標題', + profileDescription: '設定描述...', + }, + }, + + // Spoolman Settings + spoolmanSettings: {}, + + // Time + time: { + unknown: '-', + waiting: '等待中', + justNow: '剛剛', + now: '現在', + minsAgo: '{{count}} 分鐘前', + inMins: '{{count}} 分鐘後', + hoursAgo: '{{count}} 小時前', + inHours: '{{count}} 小時後', + daysAgo: '{{count}} 天前', + inDays: '{{count}} 天後', + }, + + // SpoolBuddy Kiosk + spoolbuddy: { + nav: { + dashboard: '儀表板', + ams: 'AMS', + inventory: '庫存', + writeTag: '寫入', + settings: '設定', + }, + status: { + nfcReady: 'NFC 就緒', + nfcOff: 'NFC 關閉', + offline: '離線', + online: '線上', + noPrinters: '無印表機', + deviceOffline: '裝置離線', + waitingConnection: '等待裝置連線...', + systemReady: '系統就緒', + status: '狀態', + }, + dashboard: { + readyToScan: '準備掃描', + idleMessage: '將耗材放在磅秤上以識別', + nfcHint: 'NFC 標籤將自動讀取', + device: '裝置', + syncWeight: '同步重量', + weightSynced: '已同步!', + unknownTag: '未知標籤', + newTag: '偵測到新標籤', + onScale: '在磅秤上', + linkSpool: '連結到耗材', + linkTagTitle: '將標籤連結到耗材', + linkTag: '連結標籤', + selectSpool: '選擇要連結此標籤的耗材:', + noUntagged: '未找到沒有標籤的耗材', + tagDetected: '偵測到標籤', + noTag: '無標籤', + tagId: '標籤', + grossWeight: '毛重', + spoolSize: '耗材盤尺寸', + close: '關閉', + currentSpool: '目前耗材', + }, + modal: { + spoolDetected: '偵測到耗材', + assignToAms: '分配到 AMS', + syncWeight: '同步重量', + weightSynced: '已同步!', + syncing: '同步中...', + newTagDetected: '偵測到新標籤', + addToInventory: '新增到庫存', + assignToAmsTitle: '分配到 AMS', + selectSlot: '選擇槽位', + assign: '分配', + assigning: '分配中...', + assignSuccess: '已分配!', + assignError: '分配耗材失敗。請重試。', + noPrinterSelected: '選擇印表機...', + noAmsDetected: '此印表機未偵測到 AMS', + slot: '槽位', + }, + weight: { + noReading: '無讀數', + stable: '穩定', + measuring: '測量中...', + tare: '去皮', + calibrate: '校準', + }, + spool: { + remaining: '剩餘', + material: '材料', + brand: '品牌', + color: '顏色', + coreWeight: '軸心重', + labelWeight: '標籤重', + scaleWeight: '磅秤重', + netWeight: '淨重', + lastUsed: '上次使用', + }, + ams: { + noData: '未偵測到 AMS', + connectAms: '連線 AMS 以檢視耗材槽位', + noPrinter: '未選擇印表機', + selectPrinter: '從頂部欄選擇印表機', + printerDisconnected: '印表機已斷開', + humidity: '濕度', + level: '餘量', + active: '活躍', + slot: '槽位', + empty: '空', + }, + inventory: { + search: '搜尋耗材...', + empty: '庫存中沒有耗材', + noResults: '沒有匹配的耗材', + spools: '個耗材', + addSpool: '新增耗材', + }, + settings: { + // Tabs + tabDevice: '裝置', + tabDisplay: '顯示', + tabScale: '磅秤', + tabUpdates: '更新', + // Device tab + nfcReader: 'NFC 讀卡器', + type: '類型', + connection: '連線', + notConnected: '不適用', + deviceInfo: '裝置資訊', + hostname: '主機', + uptime: '執行時間', + systemConfig: '後端與認證', + backendUrl: 'Bambuddy 後端 URL', + apiToken: 'API 權杖', + apiTokenPlaceholder: '輸入 API 權杖', + saveConfig: '儲存設定', + systemQueued: '設定已加入佇列。', + nfcDiagnostic: 'NFC 診斷', + scaleDiagnostic: '磅秤診斷', + readTagDiagnostic: '讀取標籤診斷', + testNfc: '測試讀卡器', + testScale: '測試精度', + testReadTag: '讀取標籤', + systemFieldsRequired: '後端 URL 為必填項。', + // Display tab + brightness: '亮度', + saved: '已儲存', + noBacklight: '未偵測到 DSI 背光。亮度控制需要 DSI 螢幕。', + screenBlank: '螢幕熄滅超時', + screenBlankDesc: '不活動後螢幕關閉。觸控喚醒。', + displayNote: '亮度作為軟體濾鏡套用。', + // Scale tab + scaleCalibration: '磅秤校準', + currentWeight: '目前重量', + tareOffset: '去皮', + calFactor: '係數', + knownWeight: '已知重量', + calStep1: '移除磅秤上所有物品並按設定零點。', + calStep2: '將已知重量放在磅秤上。', + setZero: '設定零點', + calibrateNow: '校準', + calibrated: '已校準', + tareSet: '去皮命令已傳送。等待裝置回應...', + tareFailed: '傳送去皮命令失敗', + zeroSet: '零點已設定。將已知重量放在磅秤上。', + calibrationDone: '校準完成!', + calibrationFailed: '校準失敗', + lastCalibrated: '上次校準', + stable: '穩定', + settling: '穩定中...', + firmware: '韌體', + scale: '磅秤', + noDevice: '未找到 SpoolBuddy 裝置', + // Updates tab + daemonVersion: '守護程式版本', + currentVersion: '目前', + versionPending: '等待守護程式...', + checking: '檢查中...', + checkUpdates: '檢查更新', + updateAvailable: '有可用更新', + updateInstructions: '透過 SSH 更新:執行 SpoolBuddy 安裝腳本進行升級。', + upToDate: '已是最新', + includeBeta: '包含測試版本', + }, + writeTag: { + tabExisting: '現有耗材', + tabNew: '新耗材', + tabReplace: '替換標籤', + searchPlaceholder: '按材料、顏色、品牌搜尋...', + noUntaggedSpools: '沒有無標籤的耗材', + noTaggedSpools: '沒有有標籤的耗材', + selectSpool: '選擇一個耗材,然後將空白 NTAG 放在讀卡器上', + placeTag: '將 NTAG 放在讀卡器上', + tagReady: '偵測到標籤 — 準備寫入', + writeTag: '寫入標籤', + replaceTag: '替換標籤', + writing: '寫入標籤中...', + waiting: '等待 SpoolBuddy...', + writeSuccess: '標籤寫入成功!', + writeFailed: '寫入失敗', + queueFailed: '佇列寫入命令失敗', + tryAgain: '重試', + cancel: '取消', + replaceWarning: '舊標籤將被取消連結。新標籤將替換它。', + deviceOffline: 'SpoolBuddy 離線', + material: '材料', + colorName: '顏色名稱', + color: '顏色', + brand: '品牌', + weight: '重量 (g)', + createSpool: '建立耗材', + creating: '建立中...', + spoolCreated: '耗材已建立!準備寫入。', + createFailed: '建立耗材失敗', + }, + quickMenu: { + printerPower: '印表機電源', + systemControls: '系統', + restartDaemon: '重新啟動守護程式', + restartBrowser: '重新啟動瀏覽器', + reboot: '重新開機', + shutdown: '關機', + swipeToClose: '向下滑動關閉', + confirmTitle: '確認', + confirmShutdown: '確定要關閉 SpoolBuddy 嗎?您需要實體存取才能重新開啟。', + confirmReboot: '確定要重新開機 SpoolBuddy 嗎?', + confirmRestartDaemon: '重新啟動 SpoolBuddy 守護程式?NFC 和磅秤將暫時不可用。', + confirmRestartBrowser: '重新啟動kiosk瀏覽器?螢幕將短暫變黑。', + confirm: '確認', + confirmPlugOn: '開啟 {{name}}?', + confirmPlugOff: '關閉 {{name}}?', + turnOn: '開啟', + turnOff: '關閉', + }, + }, + + bugReport: { + title: '報告錯誤', + description: '描述', + descriptionPlaceholder: '出了什麼問題?請描述問題...', + email: '信箱(可選)', + emailPlaceholder: 'your@email.com', + emailPrivacy: '如果提供,您的信箱將包含在GitHub Issue的摺疊部分中,以便維護者後續跟進。', + screenshot: '截圖', + uploadOrPaste: '上傳、貼上或拖曳圖片', + dataCollectedSummary: '報告中包含哪些資料?', + dataIncluded: '包含:', + dataIncludedList: '應用程式版本、作業系統、架構、Python版本、資料庫統計(僅計數)、印表機型號、噴嘴數量、韌體版本、連線狀態、整合狀態(Spoolman、MQTT、HA)、非敏感設定、網路介面數量、Docker詳情、依賴版本。', + dataNeverIncluded: '絕不包含:', + dataNeverIncludedList: '印表機名稱、序列號、存取碼、密碼、IP 位址、信箱地址、API金鑰、權杖、Webhook URL、主機名稱或使用者名稱。', + submit: '提交', + startLogging: '開始偵錯日誌', + stepEnableLogging: '偵錯日誌已啟用', + stepReproduce: '請現在重現問題', + stepStopLogging: '停止並提交報告', + stopAndSubmit: '停止並提交', + maxDuration: '{{minutes}} 分鐘後自動停止', + stoppingLogs: '正在收集日誌並提交...', + submitting: '正在提交錯誤報告...', + submitSuccess: '錯誤報告提交成功!', + submitFailed: '提交錯誤報告失敗', + thankYou: '謝謝!', + submitted: '您的錯誤報告已提交。', + viewIssue: '檢視 Issue', + unexpectedError: '發生了意外錯誤', + }, + failureDetection: { + title: 'AI 故障檢測', + description: '透過自託管的 Obico ML API 監控列印,並對偵測到的故障自動採取行動。', + mlUrl: 'Obico ML API 地址', + mlUrlHint: '您自託管的 Obico ml_api 容器的基礎 URL(例如 http://192.168.1.10:3333)。', + test: '測試', + testSuccess: 'ML API 可存取且正常。', + testFailed: '無法存取 ML API。', + sensitivity: '靈敏度', + sensitivityLow: '低(減少誤報)', + sensitivityMedium: '中(平衡)', + sensitivityHigh: '高(更早檢測,更多誤報)', + sensitivityHint: '調整觸發警告和故障的置信度閾值。', + action: '偵測到故障時的操作', + actionNotify: '僅通知', + actionPause: '暫停列印', + actionPauseOff: '暫停並切斷電源', + pollInterval: '檢查間隔(秒)', + pollIntervalHint: '列印過程中每臺印表機的檢查頻率。最小 5 秒,最大 120 秒。', + externalUrlMissing: '尚未設定外部 URL。', + externalUrlHint: 'ML API 透過 URL 擷取攝影機快照。請在一般設定中設定外部 URL,讓 ML API 容器可以連線到 Bambuddy。', + perPrinterTitle: '監控的印表機', + perPrinterHint: '選擇檢測服務要監視哪些印表機。', + monitorAll: '監控所有已連線的印表機', + statusTitle: '狀態', + serviceRunning: '服務執行中', + thresholds: '低 / 高閾值', + activePrinters: '活動列印', + noActivePrints: '目前沒有正在進行的列印。', + historyTitle: '最近檢測', + noHistory: '尚無檢測紀錄。', + }, +};