jim800121chen 6a797d5eb5 feat(frontend): 設備管理三態/註冊 + 模型共享庫/公開設定(B + C)
B 設備管理:device-store 補 registeredAt + register/unregister actions;
deriveTriState 三態(已連接/未連接/已連接未註冊);三態 badge(warning token
+ icon + 文字不只色);排序/filter chips;註冊 UI(明確區分取消註冊≠移除)。

C 模型共享:/models/library cursor 無限捲動 + 搜尋/filter/排序;visibility badge
三態;公開設定 Dialog(RadioGroup + shares 管理 + public 警告 + 二次確認);
profile 頁 owner/公開雙態;radio-group 新元件;owner 用 name 不洩 email。

共用檔 types/api.ts(B error codes)+ i18n en/zh(devices.* B / models.* C)。
零新 Design Token。reviewer B(0C/0M) + C(0C/0M、設計12/12 API8/8) 通過。

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-08-02 16:30:22 +08:00

879 lines
44 KiB
TypeScript
Raw Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

/**
* 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.done": "Done",
"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",
"devices.status.unknown": "Unknown",
// ── Devices: serial number (serial routing, ADR-018 / WP-C) ──
"devices.serial.label": "Serial number",
"devices.serial.missing": "Serial not reported yet",
"devices.serial.missingHint":
"This device hasn't reported its serial number, so inference-related actions are unavailable. Re-pair it once from local agent to report the serial.",
// ── Devices: remove (unpair) ──
"devices.remove.action": "Remove device",
"devices.remove.removing": "Removing…",
"devices.remove.confirm.title": "Remove this device?",
"devices.remove.confirm.description":
"This unpairs “{name}” from your account and revokes its access. To use it again, you'll need to pair it from local agent. This cannot be undone.",
"devices.remove.confirm.action": "Remove",
"devices.remove.toast.success": "Device removed",
"devices.remove.error.title": "Couldn't remove device",
"devices.remove.error.FORBIDDEN": "You don't have permission to remove this device.",
"devices.remove.error.NOT_FOUND": "This device no longer exists.",
"devices.remove.error.unknown": "Something went wrong. Please try again.",
// ── Devices: tri-state (connection × registration) ──
"devices.state.unregistered": "Unregistered",
// ── Devices: register / unregister ──
"devices.register.action": "Register",
"devices.register.pending": "Registering…",
"devices.register.toast.success": "Device registered",
"devices.register.error.title": "Couldn't register device",
"devices.register.error.ALREADY_REGISTERED": "This device is already registered.",
"devices.register.error.REPRESENTATIVE_DEVICE":
"This kind of device can't be registered or unregistered.",
"devices.register.error.FORBIDDEN": "You don't have permission to register this device.",
"devices.register.error.NOT_FOUND": "This device no longer exists.",
"devices.register.error.unknown": "Something went wrong. Please try again.",
// Unregister (return to unregistered state) — NOT the same as removing/unpairing the device.
"devices.unregister.action": "Unregister",
"devices.unregister.pending": "Unregistering…",
"devices.unregister.hint":
"This returns the device to an unregistered state. It stays in your list and isn't removed.",
"devices.unregister.toast.success": "Device unregistered",
"devices.unregister.error.title": "Couldn't unregister device",
// ── Devices: sort + filter ──
"devices.sort.label": "Sort by",
"devices.sort.status": "Status",
"devices.sort.name": "Name",
"devices.sort.registeredAt": "Registered",
"devices.filter.label": "Filter devices",
"devices.filter.all": "All",
"devices.filter.onlineRegistered": "Connected",
"devices.filter.onlineUnregistered": "Connected, unregistered",
"devices.filter.offline": "Disconnected",
"devices.filter.empty.title": "No devices match this filter",
"devices.filter.empty.description":
"Try a different filter, or clear it to see all your devices.",
"devices.filter.empty.action": "Clear filter",
// ── Devices: flash (load model to device) ──
"devices.flash.flashModel": "Load model",
"devices.flash.flashToDevice": "Load a model to this device",
"devices.flash.dialogDesc":
"Pick a model from your library and load it onto the device. The device must have a model loaded before it can run inference.",
"devices.flash.selectModel": "Select a model",
"devices.flash.noModels": "No models available. Add one to your library first.",
"devices.flash.startFlash": "Load to device",
"devices.flash.hardwareIncompatible": "Model may not be compatible",
"devices.flash.incompatibleDesc":
"This model targets a different chip than {device}. You can still try, but the device may reject it.",
"devices.flash.incompatibleCannotFlash": "Incompatible model",
"devices.flash.thisDevice": "this device",
"devices.flash.preparingFlash": "Preparing to load…",
"devices.flash.flashComplete": "Model loaded successfully",
"devices.flash.flashFailed": "Failed to load model",
// ── Devices: connect (driver-level connect, ADR-018 serial routing / two-step flow A) ──
"devices.connect.action": "Connect",
"devices.connect.connecting": "Connecting…",
"devices.connect.connectingHint":
"First connect loads firmware (~1 min). Please keep this page open.",
"devices.connect.disconnect": "Disconnect",
"devices.connect.disconnecting": "Disconnecting…",
"devices.connect.needConnectHint": "Connect the device first to flash a model",
"devices.connect.driverStatusLabel": "Connection",
"devices.connect.toast.success": "Device connected",
"devices.connect.toast.failed": "Connect failed",
"devices.connect.toast.failedDesc":
"Make sure the device is plugged in and the local agent is online.",
"devices.connect.toast.timeoutDesc": "Connect timed out. Please try again.",
"devices.connect.toast.disconnected": "Device disconnected",
"devices.connect.toast.disconnectFailed": "Disconnect failed",
// ── 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.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.section.preset": "Preset models",
"models.section.converted": "Converted by you",
"models.section.uploaded": "Uploaded by you",
"models.section.empty.preset": "No preset models available.",
"models.section.empty.converted": "You haven't converted any models yet.",
"models.section.empty.uploaded": "You haven't uploaded any models yet.",
"models.detail.description": "Description",
"models.detail.version": "Version",
"models.detail.checksum": "Checksum",
"models.detail.supportedChips": "Supported chips",
"models.detail.deployToDevice": "Deploy to device",
"models.detail.inputShape": "Input shape",
"models.detail.framework": "Framework",
"models.detail.classes": "Classes",
"models.detail.classesCountSuffix": "classes",
// ── 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.",
// ── Model Sharing ──
// Shared model library list
"models.library.title": "Shared Model Library",
"models.library.subtitle": "Browse models you can access: yours, public ones, and those shared with you",
"models.library.empty.title": "No shared models yet",
"models.library.empty.description": "Models shared with you or made public will appear here",
"models.library.empty.search.title": "No models match your criteria",
"models.library.empty.search.description": "Try other keywords or clear the filters",
"models.library.error.title": "Failed to load the library",
"models.library.error.description": "Please try again later",
"models.library.loadMore.retry": "Failed to load. Click to retry",
"models.library.resultCount": "Found {n} models",
"models.library.end": "All models shown",
"models.library.link": "Shared library",
// Search
"models.search.placeholder": "Search model name…",
"models.search.aria": "Search models",
"models.search.clear": "Clear search",
"models.search.clearAll": "Clear all filters",
// Filters
"models.filters.owned": "Ownership",
"models.filters.owned.all": "All",
"models.filters.owned.mine": "My models",
"models.filters.owned.shared": "Shared with me",
"models.filters.visibility": "Visibility",
// Sort
"models.sort.label": "Sort",
"models.sort.createdAt": "Newest",
"models.sort.name": "Name",
"models.sort.fileSize": "File size",
// Visibility badge (three states + shared)
"models.visibility.badge.private": "Private",
"models.visibility.badge.public": "Public",
"models.visibility.badge.tenant": "Same tenant",
"models.visibility.badge.sharedWithMe": "Shared with me",
"models.visibility.badge.sharedCount": "{n} people",
// Card owner menu
"models.card.menu.aria": "Model actions menu",
// Receiver info row (contract does not expose email, use name)
"models.sharedByName": "Shared by {name}",
"models.ownerBar.aria": "Model owner info",
// Visibility dialog
"models.visibility.title": "Visibility",
"models.visibility.question": "Who can access this model?",
"models.visibility.private": "Private",
"models.visibility.private.desc": "Only you",
"models.visibility.public": "Public",
"models.visibility.public.desc": "All visionA users",
"models.visibility.tenant": "Same tenant",
"models.visibility.tenant.desc": "Members of your organization",
"models.visibility.sharedPeopleTitle": "Specific people (additionally shared)",
"models.visibility.addEmail": "Add by email",
"models.visibility.addButton": "Add",
"models.visibility.noShares": "Not shared with anyone yet",
"models.visibility.permissionViewDownload": "View + download",
"models.visibility.removeShare": "Remove {email}",
"models.visibility.publicWarning": "Once public, all visionA users can view and download this model",
"models.visibility.saveButton": "Save changes",
"models.visibility.saved": "Visibility updated",
"models.visibility.saveFailed": "Failed to save, please retry",
"models.visibility.notReady": "Model is not ready and cannot be made public",
"models.visibility.emailInvalid": "Invalid email format",
"models.visibility.emailDuplicate": "Already in the list",
"models.visibility.userNotFound": "User {email} not found",
"models.visibility.revokeConfirm": "Setting to private revokes access for shared users. Continue?",
// Profile page
"models.profile.notFound.title": "Model not found or no access",
"models.profile.notFound.description": "This model does not exist, is not public, or was not shared with you",
"models.profile.backToLibrary": "Back to shared library",
// Generic sharing error
"models.sharing.error.generic": "Operation 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.inference.panelTitle": "Inference",
"workspace.inference.panelPending":
"Live results (labels, confidence, bounding boxes) will appear here once inference is running.",
"workspace.inference.fps": "FPS",
"workspace.inference.latency": "Latency",
"workspace.inference.waitingResults": "Waiting for the first result…",
"workspace.camera.idleHint":
"Press \"Start inference\" to open the camera and begin streaming.",
"workspace.camera.startNotWired":
"The backend camera proxy is not wired up yet.",
"workspace.camera.noSource": "No input source",
"workspace.camera.noSourceHint": "Start inference to see the live feed.",
"workspace.camera.altCameraFeed": "Live camera feed",
"workspace.camera.altUploadedImage": "Uploaded image",
"workspace.camera.altVideoPlayback": "Video playback",
"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.noSerial.title": "This device hasn't reported a serial number",
"workspace.noSerial.description":
"Inference, camera, and media upload need the device serial to route to local agent. Re-pair the device once from local agent; these actions unlock after the serial is reported.",
"workspace.tabs.camera": "Camera",
"workspace.tabs.image": "Image",
"workspace.tabs.video": "Video",
"workspace.tabs.batch": "Batch",
// ── Workspace media upload (Block 3) ──
"workspace.media.browse": "Choose file",
"workspace.media.uploading": "Uploading…",
"workspace.media.cancel": "Cancel",
"workspace.media.uploadAnother": "Upload another",
"workspace.media.errorType": "Unsupported file: {filename}",
"workspace.media.errorSize": "File too large: {filename}",
"workspace.media.errorCount": "You can upload at most 50 images at once.",
"workspace.media.errorEmpty": "Please choose at least one file.",
"workspace.media.errorGeneric": "Upload failed. Please try again.",
"workspace.media.image.primary": "Drag an image here to run inference",
"workspace.media.image.hint": "JPG or PNG · up to 20 MB",
"workspace.media.video.primary": "Drag a video here to run inference",
"workspace.media.video.hint": "MP4 / AVI / MOV / MPEG · up to 500 MB",
"workspace.media.video.frame": "Frame",
"workspace.media.video.seek": "Seek video",
"workspace.media.batch.primary": "Drag images here (up to 50) for batch inference",
"workspace.media.batch.hint": "JPG or PNG · up to 50 files · 20 MB each",
"workspace.media.batch.progress": "Image {current} / {total}",
// ── Video localhost direct-upload errors (ADR-019 WP-4) ──
"workspace.media.local.notFound":
"Video upload must be done on the same computer running the visionA Agent. Make sure the Agent is running on this machine and try again.",
"workspace.media.local.mismatch":
"The detected Agent doesn't match this device. Make sure this computer is running the visionA Agent for this device.",
"workspace.media.local.offline":
"This device is offline; video upload is unavailable. Please wait for the device to reconnect.",
"workspace.media.local.tokenInvalid": "Upload authorization expired. Please try uploading again.",
"workspace.media.local.tooLarge": "Video file is too large (500 MB max).",
// ── 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 110 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 1030 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 110 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 510 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.import.aria.done":
"Added to model library; open the model detail page",
"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 POCs multi-device parallel inference and degrade handling to the cloud.",
"clusters.phase1Badge": "Coming in Phase 1",
"clusters.phase1Toast": "Cluster creation arrives in Phase 1.",
};