對齊 ADR-017 v1.2 決策 2:model-card 下載按鈕 → 打 visionA endpoint 拿 url+token → fetch 跨 origin 直連 FAA + Authorization Bearer → blob 觸發下載(不經 visionA、不經 AWS)。 - src/lib/api/model-download.ts: getModelDownload(打 GET /api/models/:id/download)+ downloadModelFile(fetch + Bearer header + blob,credentials:"omit" 不帶 visionA cookie)+ triggerBlobDownload(延遲 revoke + finally 釋放)+ deriveDownloadFilename + ModelDownloadError - model-store: downloadingId 互斥(同時只一個下載、finally 必清)+ downloadModel action + isModelDownloadable(source==converted && status==ready) - model-card: 下載按鈕(converted+ready 才顯示、上傳類隱藏;preventDefault+stopPropagation 防觸發外層 Link 導航;loading;toast 錯誤) - i18n zh/en: models.action.download.* / models.download.*(各 error code 對應訊息) 關鍵差異:模型庫下載跨 origin + 需 Bearer header,不能用 <a href> navigation(無法帶 header), 必須 fetch+blob。與既有 conversion download(同 origin navigation)分流。 測試: 43 unit + 互動 test(mock fetch / 按鈕互動 / 顯示條件 / error 分流);tsc/lint/build 全綠。 Reviewer: 0 Critical / 0 Major / 3 Minor / 4 Suggestion,通過(ADR 決策 2 規格 7/7 符合)。 待 stage 實測: FAA 端須設 CORS(允許 visionA origin + preflight Allow-Headers: Authorization), 撞到會落 network_error。 Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
656 lines
32 KiB
TypeScript
656 lines
32 KiB
TypeScript
/**
|
||
* English dictionary — visionA Cloud
|
||
*
|
||
* Must contain the exact same set of keys as zh-Hant.ts (the unit test enforces this).
|
||
*/
|
||
|
||
import type { Dictionary } from "../types";
|
||
|
||
export const en: Dictionary = {
|
||
// ── App ──
|
||
"app.title": "visionA Cloud",
|
||
"app.tagline": "Extending the local-tool experience to the cloud",
|
||
|
||
// ── Common ──
|
||
"common.loading": "Loading…",
|
||
"common.error": "Something went wrong",
|
||
"common.cancel": "Cancel",
|
||
"common.confirm": "Confirm",
|
||
"common.save": "Save",
|
||
"common.close": "Close",
|
||
"common.retry": "Retry",
|
||
"common.view": "View",
|
||
"common.manage": "Manage",
|
||
"common.connect": "Connect",
|
||
"common.disconnect": "Disconnect",
|
||
"common.delete": "Delete",
|
||
"common.edit": "Edit",
|
||
"common.back": "Back",
|
||
"common.na": "—",
|
||
|
||
// ── Sidebar navigation ──
|
||
"nav.dashboard": "Dashboard",
|
||
"nav.devices": "Devices",
|
||
"nav.models": "Models",
|
||
"nav.conversion": "Convert",
|
||
"nav.workspace": "Workspace",
|
||
"nav.clusters": "Clusters",
|
||
"nav.settings": "Settings",
|
||
|
||
// ── Prototype banner ──
|
||
// Phase 0.7 stage deployment fix: OIDC is wired to Innovedus Account Center; remove "no real auth" wording.
|
||
"banner.prototype":
|
||
"🚧 Prototype build · Member Center login enabled, but some flows are UI mockups; sample data only.",
|
||
"banner.prototype.short": "🚧 Prototype (demo)",
|
||
"banner.prototype.ariaLabel": "Prototype notice",
|
||
|
||
// ── Header ──
|
||
"header.toggleTheme": "Toggle theme",
|
||
"header.toggleLocale": "Toggle language",
|
||
"header.userMenu.open": "Open user menu",
|
||
"header.userMenu.profile": "Account settings",
|
||
"header.userMenu.logout": "Sign out",
|
||
"header.userMenu.fallbackName": "User",
|
||
"header.breadcrumb.home": "Home",
|
||
|
||
// ── Tunnel / remote status ──
|
||
"tunnel.status.online": "Connected",
|
||
"tunnel.status.offline": "Offline",
|
||
"tunnel.status.reconnecting": "Reconnecting",
|
||
"tunnel.rttLabel": "RTT {rtt}ms",
|
||
|
||
// ── Theme toggle ──
|
||
"theme.light": "Light",
|
||
"theme.dark": "Dark",
|
||
"theme.system": "System",
|
||
|
||
// ── Auth (Phase 0.6 OIDC redirect mode) ──
|
||
// Design notes:
|
||
// - All email/password form keys removed; visionA delegates auth to Innovedus Member Center
|
||
// - Shared sign-in/out copy lives under `auth.action.*` so non-form callsites (e.g. header
|
||
// dropdown) no longer borrow `auth.login.submit`
|
||
"auth.action.signIn": "Sign in",
|
||
"auth.action.signOut": "Sign out",
|
||
// Login page
|
||
"auth.login.title": "Sign in to visionA Cloud",
|
||
"auth.login.subtitle": "Welcome back to visionA Cloud",
|
||
"auth.login.welcomeBack": "Welcome back",
|
||
"auth.login.signInWithMC": "Sign in with your Innovedus account",
|
||
"auth.login.button": "Sign in",
|
||
"auth.login.noAccount": "Don't have an account?",
|
||
"auth.login.registerLink": "Register now",
|
||
"auth.login.prototypeHint":
|
||
"Phase 0.6 prototype — sign-in redirects to the Innovedus Account Center for verification.",
|
||
// Register page (Phase 0.6: visionA no longer hosts a sign-up form; redirects to Member Center)
|
||
"auth.register.title": "Register for visionA",
|
||
"auth.register.description":
|
||
"visionA uses the Innovedus unified account system.",
|
||
"auth.register.howTo":
|
||
"Please register at the Innovedus Account Center. Once done you can sign in to visionA.",
|
||
"auth.register.button": "Go to Innovedus Account Center",
|
||
"auth.register.disabledHint":
|
||
"Innovedus Account Center URL is not configured. Please contact the administrator.",
|
||
"auth.register.alreadyHaveAccount": "Already have an account?",
|
||
"auth.register.loginLink": "Sign in",
|
||
|
||
// ── Home / Dashboard ──
|
||
"home.welcome": "Welcome back, {name}",
|
||
"home.placeholder":
|
||
"This will be the Dashboard (StatCard / ActivityTimeline / ConnectedDevicesList); F6 will port it from local-tool.",
|
||
"dashboard.title": "Dashboard",
|
||
"dashboard.subtitle": "Manage your cloud-based Edge AI assets",
|
||
"dashboard.models": "Models",
|
||
"dashboard.devices": "Devices",
|
||
"dashboard.connected": "Online devices",
|
||
"dashboard.flashes": "Flashes",
|
||
"dashboard.connectedDevices": "Online devices",
|
||
"dashboard.noConnectedDevices":
|
||
"No devices are online. Pair a Kneron device to start cloud inference.",
|
||
"dashboard.recentActivity": "Recent activity",
|
||
"dashboard.noActivity":
|
||
"Nothing here yet. Activity appears after pairing, uploads, or inference runs.",
|
||
"dashboard.quickActions": "Quick actions",
|
||
"dashboard.browseModels": "Browse models",
|
||
"dashboard.manageDevices": "Manage devices",
|
||
"dashboard.uploadModel": "Upload model",
|
||
"dashboard.pairDevice": "Pair device",
|
||
"dashboard.empty.title": "No devices yet",
|
||
"dashboard.empty.description":
|
||
"Pair your first Kneron device to start running inference from anywhere.",
|
||
"dashboard.empty.action": "Pair a device",
|
||
"dashboard.activity.justNow": "just now",
|
||
"dashboard.activity.minutesAgo": "{n} minutes ago",
|
||
"dashboard.activity.hoursAgo": "{n} hours ago",
|
||
"dashboard.activity.daysAgo": "{n} days ago",
|
||
|
||
// ── Devices ──
|
||
"devices.title": "Devices",
|
||
"devices.subtitle": "Manage your Edge AI devices",
|
||
"devices.type": "Type",
|
||
"devices.firmware": "Firmware",
|
||
"devices.flashedModel": "Flashed model",
|
||
"devices.openWorkspace": "Open workspace",
|
||
"devices.addMore": "Pair a new device",
|
||
"devices.pairAction": "Pair a new device",
|
||
"devices.empty.title": "No devices paired yet",
|
||
"devices.empty.description":
|
||
"Run local agent on your computer and complete pairing to access your Kneron devices from anywhere.",
|
||
"devices.empty.action": "Pair your first device",
|
||
"devices.empty.secondaryAction": "How pairing works",
|
||
"devices.detail.id": "ID",
|
||
"devices.detail.type": "Type",
|
||
"devices.detail.firmware": "Firmware",
|
||
"devices.detail.port": "Port",
|
||
"devices.detail.deviceInfo": "Device info",
|
||
"devices.detail.modelStatus": "Model status",
|
||
"devices.detail.readyForInference": "Ready for inference",
|
||
"devices.detail.noModelFlashed": "No model has been flashed",
|
||
"devices.detail.pairedAt": "Paired at",
|
||
"devices.detail.hostName": "Host",
|
||
"devices.detail.lastSeen": "Last seen",
|
||
"devices.detail.offlineBanner.title": "This device is offline",
|
||
"devices.detail.offlineBanner.description":
|
||
"Some actions are unavailable until local agent reconnects.",
|
||
"devices.status.detected": "Detected",
|
||
"devices.status.connecting": "Connecting",
|
||
"devices.status.connected": "Connected",
|
||
"devices.status.flashing": "Flashing",
|
||
"devices.status.inferencing": "Inferencing",
|
||
"devices.status.error": "Error",
|
||
"devices.status.disconnected": "Disconnected",
|
||
|
||
// ── Remote Device Badge ──
|
||
"remote.status.online": "Online",
|
||
"remote.status.offline": "Offline",
|
||
"remote.status.reconnecting": "Reconnecting",
|
||
"remote.status.error": "Connection error",
|
||
"remote.status.unknown": "Unknown",
|
||
"remote.lastSeenNever": "Never connected",
|
||
"remote.lastSeen.justNow": "just now",
|
||
"remote.lastSeen.minutesAgo": "{n} min ago",
|
||
"remote.lastSeen.hoursAgo": "{n} h ago",
|
||
|
||
// ── Models ──
|
||
"models.title": "Models",
|
||
"models.subtitle": "Manage Kneron models on the cloud",
|
||
"models.size": "Size",
|
||
"models.createdAt": "Created",
|
||
"models.status.uploading": "Uploading",
|
||
"models.status.scanning": "Scanning",
|
||
"models.status.ready": "Ready",
|
||
"models.status.rejected": "Rejected",
|
||
"models.source.uploaded": "Uploaded",
|
||
"models.source.preset": "Preset",
|
||
"models.source.converted": "Converted",
|
||
"models.filters.label": "Model filters",
|
||
"models.filters.hardware": "Hardware",
|
||
"models.filters.source": "Source",
|
||
"models.filters.all": "All",
|
||
"models.empty.title": "No models yet",
|
||
"models.empty.description":
|
||
"Upload your first .nef model to deploy it to any paired Kneron device.",
|
||
"models.empty.action": "Upload your first model",
|
||
"models.detail.description": "Description",
|
||
"models.detail.version": "Version",
|
||
"models.detail.checksum": "Checksum",
|
||
"models.detail.supportedChips": "Supported chips",
|
||
"models.detail.deployToDevice": "Deploy to device",
|
||
|
||
// ── Model Upload Dialog ──
|
||
"models.upload.button": "Upload model",
|
||
"models.upload.dialog.title": "Upload model",
|
||
"models.upload.dropzone.hint": "Format: .nef · Max 100 MB",
|
||
"models.upload.selectedFile": "Choose file",
|
||
"models.upload.field.name": "Model name",
|
||
"models.upload.field.version": "Version",
|
||
"models.upload.field.notes": "Notes (optional)",
|
||
"models.upload.field.targetChip": "Target chip",
|
||
"models.upload.action.cancel": "Cancel",
|
||
"models.upload.action.start": "Start upload",
|
||
"models.upload.action.remove": "Remove",
|
||
"models.upload.uploading.title": "Uploading",
|
||
"models.upload.uploading.hint": "Do not close this window or navigate away.",
|
||
"models.upload.success.title": "Upload complete",
|
||
"models.upload.success.scanHint":
|
||
"The model is entering safety scan; it will be available once ready.",
|
||
"models.upload.error.invalidType":
|
||
"Only .nef files are supported, got {type}.",
|
||
"models.upload.error.tooLarge":
|
||
"File too large ({size}); max allowed is 100 MB.",
|
||
"models.upload.error.requiredField": "{field} is required.",
|
||
"models.upload.error.urlFailed": "Server is busy, please try again.",
|
||
"models.upload.error.networkLost": "Network lost, upload paused.",
|
||
"models.upload.toast.uploaded": "Model \"{name}\" uploaded.",
|
||
// ── Model download (Phase 0.9 FAA delegated download) ──
|
||
"models.action.download": "Download",
|
||
"models.action.downloading": "Downloading…",
|
||
"models.action.download.aria": "Download the model file to your device",
|
||
"models.action.download.unsupportedTooltip":
|
||
"Only converted models are downloadable in this phase",
|
||
"models.download.toast.start": "Download started",
|
||
"models.download.toast.hint":
|
||
"If you don't see a download prompt, check your browser settings",
|
||
"models.download.error.title": "Download failed",
|
||
"models.download.error.model_not_found": "Model not found.",
|
||
"models.download.error.forbidden": "You don't have permission to download this model.",
|
||
"models.download.error.upload_not_supported":
|
||
"Uploaded models are not downloadable in this phase.",
|
||
"models.download.error.sign_failed":
|
||
"Failed to obtain a download grant, please try again later.",
|
||
"models.download.error.download_failed":
|
||
"Failed to download from the file server, please try again later.",
|
||
"models.download.error.network_error":
|
||
"Network error, please check your connection and retry.",
|
||
"models.download.error.timeout": "Download timed out, please try again later.",
|
||
"models.download.error.busy": "A download is already in progress, please wait.",
|
||
"models.download.error.unknown": "Download failed, please try again later.",
|
||
|
||
// ── Workspace ──
|
||
"workspace.title": "Workspace",
|
||
"workspace.subtitle": "Select an online device to start inference",
|
||
"workspace.empty.title": "No devices are online",
|
||
"workspace.empty.description":
|
||
"Pair a device and make sure the local agent is connected to the cloud.",
|
||
"workspace.empty.action": "Go to devices",
|
||
"workspace.header.backToDevices": "Back to devices",
|
||
"workspace.header.title": "Workspace",
|
||
"workspace.inference.start": "Start inference",
|
||
"workspace.inference.stop": "Stop inference",
|
||
"workspace.placeholder.cameraComingSoon":
|
||
"Camera inference preview — F8 will wire up the MJPEG stream through the tunnel from local agent.",
|
||
"workspace.offline.title": "Device went offline",
|
||
"workspace.offline.description":
|
||
"The connection to {deviceName} was lost; inference has been stopped.",
|
||
"workspace.offline.backToList": "Back to devices",
|
||
"workspace.tabs.camera": "Camera",
|
||
"workspace.tabs.image": "Image (Phase 1)",
|
||
"workspace.tabs.video": "Video (Phase 1)",
|
||
"workspace.tabs.batch": "Batch (Phase 1)",
|
||
|
||
// ── Settings ──
|
||
"settings.title": "Settings",
|
||
"settings.subtitle": "Manage preferences and cloud endpoints",
|
||
"settings.tabs.general": "General",
|
||
"settings.tabs.advanced": "Advanced",
|
||
"settings.general.title": "Preferences",
|
||
"settings.general.language": "Language",
|
||
"settings.general.theme": "Theme",
|
||
"settings.general.themeHint":
|
||
"Automatically follow your system light / dark mode.",
|
||
"settings.advanced.title": "Cloud endpoints",
|
||
"settings.advanced.apiEndpoint": "API endpoint",
|
||
"settings.advanced.apiEndpointHint":
|
||
"Normal users should not change this; developers can switch to staging or localhost.",
|
||
"settings.advanced.apiUrl": "Current API URL",
|
||
"settings.advanced.wsUrl": "Current WebSocket URL",
|
||
"settings.advanced.about": "About",
|
||
"settings.advanced.version": "Version",
|
||
"settings.advanced.platform": "Platform",
|
||
|
||
// ── Pairing (F7) ──
|
||
"pairing.title": "Pair a new device",
|
||
"pairing.subtitle":
|
||
"Connect your Kneron device to the cloud so you can operate it from anywhere.",
|
||
"pairing.token.title": "Your pairing token",
|
||
"pairing.step1.description":
|
||
"Copy the token below and paste it into your local agent within 15 minutes.",
|
||
"pairing.copy": "Copy",
|
||
"pairing.copied": "Copied",
|
||
"pairing.regenerate": "Regenerate",
|
||
"pairing.timeRemaining": "{time} remaining",
|
||
"pairing.generatedAt": "Generated at {time}",
|
||
"pairing.token.expired.label": "This token has expired — please regenerate.",
|
||
"pairing.regenerateConfirm.title": "Regenerate token?",
|
||
"pairing.regenerateConfirm.description":
|
||
"The old token will be invalidated immediately; the new one is valid for 15 minutes.",
|
||
"pairing.security.warning":
|
||
"This token is valid for 15 minutes — complete pairing now.",
|
||
"pairing.security.oneTime":
|
||
"Tokens are single-use and expire automatically after pairing.",
|
||
"pairing.toast.copied": "Token copied — valid for 15 minutes.",
|
||
"pairing.toast.generateFailed": "Could not generate token — please retry.",
|
||
"pairing.toast.expiringSoon":
|
||
"Token expiring soon — complete pairing or regenerate.",
|
||
"pairing.toast.pairedSuccess": "Device {deviceName} paired successfully.",
|
||
"pairing.toast.cliCopied": "CLI command copied.",
|
||
"pairing.device.unknown": "Unknown device",
|
||
"pairing.cli.title": "CLI example",
|
||
"pairing.cli.description":
|
||
"Start local agent on your computer and pass the token to the --relay-token flag.",
|
||
"pairing.cli.copy": "Copy command",
|
||
"pairing.cli.hint":
|
||
"Once local agent connects to the cloud, this page detects it and forwards you to the device list.",
|
||
"pairing.step3.waiting": "Waiting for local agent to connect…",
|
||
"pairing.step3.elapsed": "Elapsed {time} (max 3 minutes)",
|
||
"pairing.step3.hints.running": "Confirm local agent is running",
|
||
"pairing.step3.hints.token":
|
||
"Confirm the token was pasted without missing or extra characters",
|
||
"pairing.step3.hints.network":
|
||
"Confirm your network can reach the cloud endpoint",
|
||
"pairing.step3.success": "Connected!",
|
||
"pairing.step3.success.detected": "Detected device",
|
||
"pairing.step3.failure.timeout": "Connection timeout",
|
||
"pairing.step3.failure.reason":
|
||
"No local agent connection within 3 minutes — agent may not be running.",
|
||
"pairing.step3.failure.retry": "Check again",
|
||
|
||
// ── Account (Phase 0.6 OIDC) ──
|
||
// Design notes:
|
||
// - Profile is read-only; edits go through the Innovedus Account Center
|
||
// - "Delete account" stays as a disabled stub; copy directs users to Member Center
|
||
"account.title": "Account settings",
|
||
"account.subtitle": "Profile is managed by the Innovedus Account Center",
|
||
"account.profile.title": "Profile",
|
||
"account.profile.userId": "User ID",
|
||
"account.profile.email": "Email",
|
||
"account.profile.name": "Display name",
|
||
"account.profile.namePlaceholder": "—",
|
||
"account.profile.managedBy":
|
||
"Your profile is managed by the Innovedus Account Center. To make changes, please go to the Innovedus Account Center.",
|
||
"account.profile.editAtMemberCenter": "Open Innovedus Account Center",
|
||
"account.profile.editDisabledHint":
|
||
"Innovedus Account Center URL is not configured. Please contact the administrator.",
|
||
"account.session.title": "Session",
|
||
"account.session.description":
|
||
"Signing out returns you to the login page. You will need to sign in again through the Innovedus Account Center.",
|
||
"account.danger.title": "Danger zone",
|
||
"account.danger.description":
|
||
"Account deletion is handled at the Innovedus Account Center; visionA does not perform deletion directly.",
|
||
"account.danger.deleteAccount": "Delete account",
|
||
"account.danger.deleteAccount.tooltip":
|
||
"Please handle this at the Innovedus Account Center.",
|
||
|
||
// ── Conversion (Phase 0.8 — feature-converter-integration) ──
|
||
// Spec: .autoflow/03-design/wireframes/wireframe-conversion.md §11
|
||
// PRD: .autoflow/02-prd/features/feature-converter-integration.md
|
||
// Scope: 5 states (idle / uploading / processing / success / failed) + 6 error code translations + expired
|
||
"conversion.title": "Convert",
|
||
"conversion.subtitle":
|
||
"Turn ONNX / TFLite models into .nef so they can run on Kneron edge devices.",
|
||
|
||
// idle empty state
|
||
"conversion.idle.heading": "No conversion in progress",
|
||
"conversion.idle.description":
|
||
"Upload an ONNX / TFLite model, pick a target Kneron chip, and we'll produce a flashable .nef file for you.",
|
||
"conversion.idle.cta": "Start conversion",
|
||
"conversion.idle.formats": "Supports .onnx / .tflite · max 500 MB",
|
||
"conversion.idle.about.title": "About conversion",
|
||
"conversion.idle.about.line1":
|
||
"Only one conversion job per user at a time (across tabs).",
|
||
"conversion.idle.about.line2":
|
||
"Results are downloadable for 7 days, then automatically cleared.",
|
||
"conversion.idle.about.line3":
|
||
"Conversion typically takes 1–10 minutes depending on model size.",
|
||
|
||
// Upload Dialog
|
||
"conversion.upload.title": "Start conversion",
|
||
"conversion.upload.description":
|
||
"Upload a model, choose a target chip, and optionally attach reference images for better accuracy.",
|
||
"conversion.upload.source.label": "Source model",
|
||
"conversion.upload.source.dropzone": "Drag .onnx / .tflite here",
|
||
"conversion.upload.source.or": "or",
|
||
"conversion.upload.source.browse": "Choose file",
|
||
"conversion.upload.source.formatHint":
|
||
"Format: .onnx · .tflite · max 500 MB",
|
||
"conversion.upload.source.remove": "Remove",
|
||
"conversion.upload.name.label": "Job name (optional)",
|
||
"conversion.upload.name.hint": "Display only; does not change output filename.",
|
||
"conversion.upload.chip.label": "Target chip",
|
||
"conversion.upload.refImages.label": "Reference images (optional)",
|
||
"conversion.upload.refImages.dropzone": "Drag images here (optional)",
|
||
"conversion.upload.refImages.hint":
|
||
"Reference images can improve quantized accuracy (max 100 images, ≤ 10 MB each).",
|
||
"conversion.upload.refImages.summary":
|
||
"{count} reference images selected ({totalSize})",
|
||
"conversion.upload.refImages.removeAll": "Remove all",
|
||
"conversion.upload.cancel": "Cancel",
|
||
"conversion.upload.start": "Start conversion",
|
||
"conversion.upload.error.noFile": "Please choose a .onnx or .tflite file.",
|
||
"conversion.upload.error.unsupported":
|
||
"Unsupported format — please use ONNX or TFLite.",
|
||
"conversion.upload.error.modelTooLarge":
|
||
"Model exceeds the 500 MB limit; please use a smaller model.",
|
||
"conversion.upload.error.noChip": "Please pick a target chip.",
|
||
"conversion.upload.error.refTooLarge":
|
||
"{filename} is over 10 MB — please remove or compress it.",
|
||
"conversion.upload.error.refTooMany":
|
||
"Reference images cannot exceed 100 files.",
|
||
|
||
// uploading stage (inside Dialog)
|
||
"conversion.uploading.title": "Uploading",
|
||
"conversion.uploading.heading": "Uploading to visionA…",
|
||
"conversion.uploading.progress": "{loaded} / {total} uploaded · {eta} remaining",
|
||
"conversion.uploading.almostDone": "Almost done…",
|
||
"conversion.uploading.warning":
|
||
"Please don't close this tab — the upload will be aborted.",
|
||
"conversion.uploading.cancel": "Cancel upload",
|
||
"conversion.uploading.cancelConfirm":
|
||
"Upload not finished — cancel anyway?",
|
||
"conversion.uploading.tabTitle": "visionA Cloud · Uploading ({pct}%)",
|
||
"conversion.uploading.toastCanceled": "Upload canceled.",
|
||
"conversion.uploading.toastFailed": "Upload failed: {reason}",
|
||
// F-T9 sub-3 / M1: upload failure toast hint
|
||
// M1 fix: File objects can't live in the store (non-serializable / unsafe across unmounts);
|
||
// chip / task name are preserved via store.formDraft, but the file itself must be re-selected.
|
||
"conversion.toast.retryHint":
|
||
"Your settings (chip / task name) are preserved — please re-select the file to retry.",
|
||
"conversion.toast.networkError":
|
||
"Network error — please check your connection and retry.",
|
||
"conversion.uploading.toastStarted":
|
||
"Conversion started (job #{shortJobId}).",
|
||
// F-T5: ETA / cancel confirm / beforeunload (detailed keys)
|
||
"conversion.uploading.subtitle": "Uploading model to visionA backend",
|
||
"conversion.uploading.eta.computing": "Estimating time remaining…",
|
||
"conversion.uploading.eta.almostDone": "Almost done…",
|
||
"conversion.uploading.eta.format": "{eta} remaining",
|
||
"conversion.uploading.progress.format": "{loaded} / {total}",
|
||
"conversion.uploading.info":
|
||
"Conversion will start automatically (typically 10–30 seconds).",
|
||
"conversion.uploading.cancel.confirm.title": "Cancel conversion?",
|
||
"conversion.uploading.cancel.confirm.message":
|
||
"Upload is not finished — uploaded progress will be lost. Cancel anyway?",
|
||
"conversion.uploading.cancel.confirm.yes": "Yes, cancel",
|
||
"conversion.uploading.cancel.confirm.no": "Keep uploading",
|
||
"conversion.uploading.warning.beforeunload":
|
||
"Upload not finished — leaving will abort the upload.",
|
||
"conversion.uploading.target": "Target: {chip}",
|
||
"conversion.uploading.aria.cancel": "Cancel upload",
|
||
"conversion.uploading.aria.progress": "Upload progress",
|
||
"conversion.uploading.aria.status": "Uploaded {pct}%, {eta}",
|
||
|
||
// processing stage
|
||
"conversion.processing.title": "Convert",
|
||
"conversion.processing.cardHeading": "In progress",
|
||
"conversion.processing.statusBadge": "Converting",
|
||
"conversion.processing.startedAgo": "Started {time}",
|
||
"conversion.processing.stage1": "Upload complete",
|
||
"conversion.processing.stage2": "Parsing model",
|
||
"conversion.processing.stage3": "Compiling NEF",
|
||
"conversion.processing.processing": "Processing…",
|
||
"conversion.processing.hint":
|
||
"Typically 1–10 minutes · You can leave this page; progress will resume when you return.",
|
||
"conversion.processing.background.title": "Feel free to walk away",
|
||
"conversion.processing.background.l1":
|
||
"We poll progress in the background (every 5–10 seconds).",
|
||
"conversion.processing.background.l2":
|
||
"The tab title will notify you on completion.",
|
||
"conversion.processing.background.l3":
|
||
"Closing this page is fine — it will resume automatically.",
|
||
"conversion.processing.queueLong":
|
||
"Queue is currently long — you can come back later.",
|
||
"conversion.processing.runLong":
|
||
"This conversion is taking a while; still running.",
|
||
"conversion.processing.pollFailed":
|
||
"Couldn't fetch conversion status — please retry.",
|
||
"conversion.processing.bannerActive":
|
||
"A conversion was still running when you left.",
|
||
"conversion.processing.bannerExisting":
|
||
"You already have a conversion in progress — switched to that job.",
|
||
// F-T9 sub-2: banner dismiss button a11y label
|
||
"conversion.processing.bannerDismiss": "Dismiss notice",
|
||
|
||
// F-T6: ProcessingView (queued / running shared UI)
|
||
"conversion.processing.subtitle":
|
||
"Conversion in progress — please keep this page open",
|
||
"conversion.processing.queued": "Queued…",
|
||
"conversion.processing.stage.onnx": "Parsing model",
|
||
"conversion.processing.stage.bie": "Quantize & compile",
|
||
"conversion.processing.stage.nef": "Compile NEF",
|
||
"conversion.processing.stage.status.completed": "Done",
|
||
"conversion.processing.stage.status.current": "In progress",
|
||
"conversion.processing.stage.status.pending": "Pending",
|
||
"conversion.processing.stage.aria.completed": "{name} (completed)",
|
||
"conversion.processing.stage.aria.current": "{name} (in progress)",
|
||
"conversion.processing.stage.aria.pending": "{name} (pending)",
|
||
"conversion.processing.eta.pending": "Please wait",
|
||
"conversion.processing.eta.computing": "Estimating time remaining",
|
||
"conversion.processing.expiryHint":
|
||
"Job will be auto-deleted in 7 days — please download or add it to your model library before then.",
|
||
"conversion.processing.tabTitle.prefix": "(Converting) ",
|
||
"conversion.processing.aria.progressIndeterminate":
|
||
"Conversion in progress; remaining time unknown",
|
||
"conversion.processing.aria.queueProgress":
|
||
"Queued, waiting for conversion to start",
|
||
"conversion.processing.targetChipPrefix": "→",
|
||
|
||
// active job hint (also shown on idle)
|
||
"conversion.busy.title": "You already have a conversion in progress",
|
||
"conversion.busy.cta": "View progress",
|
||
|
||
// success
|
||
"conversion.success.heading": "Conversion complete",
|
||
"conversion.success.summary.chip": "Target chip",
|
||
"conversion.success.summary.size": "Output size",
|
||
"conversion.success.summary.duration": "Duration",
|
||
"conversion.success.summary.checksum": "checksum",
|
||
"conversion.success.summary.jobId": "Job",
|
||
"conversion.success.nextStep": "What's next?",
|
||
"conversion.success.import.title": "Add to model library",
|
||
"conversion.success.import.description":
|
||
"You can deploy it from the model library to any {chip} device.",
|
||
"conversion.success.import.cta": "Add to library",
|
||
"conversion.success.import.dialog.title": "Add to model library",
|
||
"conversion.success.import.dialog.description":
|
||
"Add this conversion result to the model library so you can deploy it to {chip} devices.",
|
||
"conversion.success.import.dialog.nameLabel": "Model name",
|
||
"conversion.success.import.dialog.nameHint":
|
||
"Display name in the model library; up to 100 characters; cannot contain / or \\.",
|
||
"conversion.success.import.dialog.nameError.required": "Please enter a model name.",
|
||
"conversion.success.import.dialog.nameError.tooLong":
|
||
"Model name must be 100 characters or fewer.",
|
||
"conversion.success.import.dialog.nameError.invalidChars":
|
||
"Model name cannot contain / or \\.",
|
||
"conversion.success.import.dialog.descLabel": "Description (optional)",
|
||
"conversion.success.import.dialog.sourceLabel": "Source",
|
||
"conversion.success.import.dialog.sourceValue":
|
||
"Conversion (job #{shortJobId})",
|
||
"conversion.success.import.dialog.confirm": "Add to library",
|
||
"conversion.success.import.dialog.cancel": "Cancel",
|
||
"conversion.success.import.processing": "Processing…",
|
||
"conversion.success.import.toastDone": "Added to model library.",
|
||
"conversion.success.import.toastDoneAction": "Open model library →",
|
||
"conversion.success.import.toastDup":
|
||
"This job has already been added to the library.",
|
||
"conversion.success.import.toastDupAction": "View existing model →",
|
||
"conversion.success.import.statusDone": "✓ Added (open it →)",
|
||
"conversion.success.import.errorGeneric":
|
||
"Couldn't add to model library; please try again.",
|
||
"conversion.success.import.aria.cta":
|
||
"Add to model library (opens a confirmation dialog)",
|
||
"conversion.success.download.title": "Download .nef",
|
||
"conversion.success.description":
|
||
"{source} has been converted into a .nef compatible with {chip}.",
|
||
"conversion.success.summary.outputFile": "Output file",
|
||
"conversion.success.summary.notAvailable": "—",
|
||
"conversion.success.download.description":
|
||
"Save it locally to use elsewhere.",
|
||
"conversion.success.download.cta": "Download",
|
||
"conversion.success.download.preparing": "Preparing download…",
|
||
"conversion.success.download.toastStart": "Download started.",
|
||
"conversion.success.download.toastHint":
|
||
"If no download prompt appears, check your browser settings.",
|
||
"conversion.success.download.toastFail": "Couldn't fetch download link.",
|
||
"conversion.success.download.aria.cta": "Download .nef file to your computer",
|
||
"conversion.success.download.aria.disabled":
|
||
"Download link expired; start a new conversion",
|
||
"conversion.success.expiry":
|
||
"This result will be auto-deleted in {time}; please finish using it before then.",
|
||
"conversion.success.expiry.expired": "This conversion result has expired.",
|
||
"conversion.success.expiry.remaining.daysHours": "{days}d {hours}h",
|
||
"conversion.success.expiry.remaining.daysOnly": "{days}d",
|
||
"conversion.success.expiry.remaining.hoursMinutes": "{hours}h {minutes}m",
|
||
"conversion.success.expiry.remaining.hoursOnly": "{hours}h",
|
||
"conversion.success.expiry.remaining.minutes": "{minutes}m",
|
||
"conversion.success.startNew": "Start new conversion",
|
||
|
||
// failed
|
||
"conversion.failed.heading": "Conversion failed",
|
||
"conversion.failed.errorCode": "Error code",
|
||
"conversion.failed.suggestionsTitle": "Things you can try:",
|
||
"conversion.failed.retry": "Try again",
|
||
"conversion.failed.backToModels": "Back to models",
|
||
"conversion.failed.contactSupport":
|
||
"If this keeps happening, copy the job ID and contact support.",
|
||
"conversion.failed.copyJobId": "Copy job ID",
|
||
"conversion.failed.toastJobIdCopied": "Job ID copied.",
|
||
// F-T8 — FailedView
|
||
"conversion.failed.aria.alert": "Conversion failure notice",
|
||
"conversion.failed.jobIdLabel": "Job ID",
|
||
"conversion.failed.summary.failedSuffix": " (failed)",
|
||
"conversion.failed.aria.retry": "Reset and start a new conversion",
|
||
|
||
// error code translations (mirrors zh-Hant; one key per suggestion line)
|
||
"conversion.error.UNSUPPORTED_FORMAT.message":
|
||
"This model format isn't supported — please use ONNX / TFLite.",
|
||
"conversion.error.UNSUPPORTED_FORMAT.suggestion1": "Verify file extension.",
|
||
"conversion.error.UNSUPPORTED_FORMAT.suggestion2":
|
||
"Use a standard export tool.",
|
||
"conversion.error.INVALID_CHECKSUM.message":
|
||
"File was corrupted in transit — please re-upload.",
|
||
"conversion.error.INVALID_CHECKSUM.suggestion1": "Restart the upload.",
|
||
"conversion.error.QUANTIZATION_FAILED.message":
|
||
"Model contains unsupported operators and can't be quantized for the target chip.",
|
||
"conversion.error.QUANTIZATION_FAILED.suggestion1": "Simplify the model.",
|
||
"conversion.error.QUANTIZATION_FAILED.suggestion2": "Remove custom ops.",
|
||
"conversion.error.QUANTIZATION_FAILED.suggestion3":
|
||
"Use a smaller input shape.",
|
||
"conversion.error.MODEL_TOO_LARGE.message": "Model exceeds the 500 MB limit.",
|
||
"conversion.error.MODEL_TOO_LARGE.suggestion1": "Use a smaller model.",
|
||
"conversion.error.MODEL_TOO_LARGE.suggestion2":
|
||
"Try pruning / quantization first.",
|
||
"conversion.error.QUOTA_EXCEEDED.message":
|
||
"System is busy right now — please try again later.",
|
||
"conversion.error.QUOTA_EXCEEDED.suggestion1": "Wait 5 minutes and retry.",
|
||
"conversion.error.unknown.message":
|
||
"Conversion failed. If it keeps happening, contact support.",
|
||
"conversion.error.unknown.suggestion1":
|
||
"Copy the job ID and report it to support.",
|
||
|
||
// expired / not-found
|
||
"conversion.expired.heading": "This conversion result has expired",
|
||
"conversion.expired.description":
|
||
"Conversion results are kept for 7 days; this one has been auto-cleared.",
|
||
// F-T9 sub-1: ExpiredView extra description — explain how to recover
|
||
"conversion.expired.subDescription":
|
||
"To get a new conversion result, please submit the model again.",
|
||
// F-T9 sub-1: ExpiredView a11y
|
||
"conversion.expired.aria.alert": "Conversion result has expired notice",
|
||
"conversion.expired.aria.startNew": "Reset and start a new conversion",
|
||
"conversion.expired.startNew": "Start new conversion",
|
||
|
||
// mobile hint
|
||
"conversion.mobileHint":
|
||
"Uploading large models on mobile can be unreliable; we recommend a desktop browser.",
|
||
|
||
// ── Clusters (F7 stub) ──
|
||
"clusters.title": "Clusters",
|
||
"clusters.subtitle":
|
||
"Combine multiple Kneron devices into a parallel inference cluster.",
|
||
"clusters.create": "Create cluster",
|
||
"clusters.empty.title": "Cluster inference — coming in Phase 1",
|
||
"clusters.empty.description":
|
||
"Phase 1 will port the POC’s multi-device parallel inference and degrade handling to the cloud.",
|
||
"clusters.phase1Badge": "Coming in Phase 1",
|
||
"clusters.phase1Toast": "Cluster creation arrives in Phase 1.",
|
||
};
|