鉴权并入 Auth Broker:委派设备会话统一模型 + 四端迁移

后端(委托 Auth Broker,路径 A):
- 删自建鉴权(OIDC exchange / 自签会话 / step-up / shortcut / web_sessions / accounts),cdrop 不再存任何凭证;鉴权中间件改读边缘注入的 X-Auth-Subject/Scope/Meta/Name/Roles 头(dev 旁路保留);Claims 加 Tier() / Guest()
- internal/brokerclient:mint / revoke(带 X-Broker-App)/ refresh / ListSessions(R1 列举),直连内网、吊销幂等

统一会话模型“委派设备会话”(Delegated Device Sessions):
- 每个客户端(浏览器 / 桌面 / 扫码设备)=一条带 meta(device_id) + label 的 broker 机器会话;Broker 作设备会话唯一注册表(R1 按用户+app 列举 + R2 按 (user,app,meta) 幂等铸造),cdrop 退化为薄覆盖层、不再自存权威会话表
- 新增代铸端点 POST /api/auth/device-session:凭边缘已验明的 X-Auth-Subject 委托 broker 铸 / 轮换设备会话(meta=device_id、按调用方 tier 防越权、sameOrigin CSRF、per-IP 限流);R2 幂等保证同一 device_id 重登原地轮换、不堆重复设备
- 会话列表=R1 权威 + 叠加 type(本地缓存)/ online(Hub presence,按设备名)/ current(meta 匹配本请求 X-Auth-Meta)+ 过滤 meta=""(device-authorize 引导会话残留);devices 表降级为 type/presence 薄缓存(非会话权威),device_id 主键、upsert 按 user 限定
- 吊销按 device_id → 缓存优先 / R1 兜底解析 sid → broker 吊销 + X-Broker-App;扫码登录保留三密钥编排,collect 改委托 broker 铸 + 落缓存行

Web 前端:
- 登录走 broker 全局 SSO 代跳(/api/auth/login 302);bootstrap 经 /api/me 注入身份后代铸设备会话(稳定 device_id 存 localStorage、Web Locks 跨 tab 串行防重复铸造);refresh 走 /api/auth/refresh
- 设备管理按 device_id;改名=同 device_id 重代铸(R2 原地轮换换 label、不产生重复行);登录页反应式守卫修登录回环
- 去 OIDC PKCE / step-up(删 oauth.callback / stepUp)

桌面客户端(Wails):
- loopback PKCE(RFC 8252)改指 broker 设备授权流(/device/authorize + /device/token)拿引导令牌,再代铸出带 meta 的托管设备会话——与浏览器同模型、同管理、同吊销;身份取自代铸响应(修“显示名显示为 UUID”);refresh 保留显示名;稳定 device_id 入桌面配置

iOS 客户端(arch A,原生 SwiftUI + 离屏无头 WebView 引擎 + 原生↔JS 桥):
- 引擎 / 文件管理 / 设备管理 / 应用图标 / 本地化(此前实现,随本次落入版本库)
- 鉴权=引擎自刷(boot 注入 refresh_token)+ broker 轮换经 sessionRotated 回报原生更新 Keychain;去 cookie 同步;Session 加 refreshToken / deviceId

实时 / 健壮性:
- presence 走 Hub union(设备表行 ∪ 表外实时连接,按名去重、live-only 标在线)
- Hub 通道 close 一律在写锁内、非阻塞 send 一律在读锁内,消除 close-vs-send 闭通道 send panic(revoke 每次 Kick 后该路径变热)

配置 / 删旧栈:
- config 改 broker 接入(CDROP_BROKER_* / CDROP_PUBLIC_URL / 按档 TTL),prod 强校验 broker 配置 + PUBLIC_URL(CSRF Origin 守卫不失效)
- 删 auth.go / selftoken.go / shortcut.go / jwks.go + 三表(web_sessions / accounts / shortcut_tokens)及验证链;.env.example / compose.snippet.yaml / Caddyfile.snippet 更新为 broker 模型(人机分流 + 公开端点放行 + X-Auth-Meta 透传)
- 测试全重写:QR / 会话含 mock broker(R1 列举 + R2 幂等);hub 加 close-vs-send 并发回归;config 加 prod 必填校验
This commit is contained in:
2026-06-26 02:07:11 +08:00
parent c79b176b87
commit 10cf36ecee
104 changed files with 7533 additions and 5318 deletions
+358
View File
@@ -0,0 +1,358 @@
{
"app.brand": "Commilitia Drop",
"deviceType.browser": "Browser",
"deviceType.macos": "macOS client",
"deviceType.windows": "Windows client",
"deviceType.linux": "Linux client",
"deviceType.ios": "iOS client",
"deviceType.shortcut": "Shortcut",
"app.connected": "Online",
"app.connecting": "Connecting…",
"app.disconnectedTitle": "Reconnecting",
"app.disconnectedHint": "Lost connection to the server — retrying automatically. No need to refresh.",
"auth.sessionLost.title": "Signed out",
"auth.sessionLost.body": "This device's login was revoked or expired. Please sign in again.",
"nav.accountMenu": "Account menu",
"nav.thisDeviceLabel": "This device: ",
"nav.settings": "Settings",
"nav.signOut": "Sign out",
"nav.backToHome": "← Home",
"nav.language": "语言 / Language",
"nav.theme": "主题 / Theme",
"nav.theme.light": "Light",
"nav.theme.dark": "Dark",
"nav.theme.system": "Follow system",
"nav.themeToggle": "Toggle theme",
"home.greeting": "Hello, {{name}}",
"home.deviceList.title": "Devices",
"home.deviceList.onlineSuffix": "({{count}} online)",
"home.deviceList.showOffline": "Show offline ({{count}})",
"home.deviceList.hideOffline": "Hide offline",
"home.deviceList.empty": "No peer devices yet. Open the app in another tab with a different device name (Settings → Device name) to see it here.",
"home.tabs.file": "File",
"home.tabs.message": "Message",
"home.tabs.clipboard": "Clipboard",
"home.file.dropzone": "Drop a file here or click to browse",
"home.file.selectedSize": "{{name}} ({{size}})",
"home.file.rejected": "File rejected",
"home.cta.selectDeviceAndFile": "Select a device and a file",
"home.cta.selectFile": "Select a file",
"home.cta.selectDevice": "Select a device",
"home.cta.sendTo": "Send to {{name}}",
"home.message.placeholder": "Type a message to {{name}}…",
"home.message.placeholderEmpty": "Select a device first",
"home.message.hint": "Enter to send, Shift+Enter for newline. Max 4 KB.",
"home.message.title": "Messages",
"home.message.countSuffix": "({{count}})",
"home.message.clearAll": "Clear all",
"home.transfer.active": "Active",
"home.transfer.history": "Recent transfers",
"home.clipboard.cloudTitle": "Latest from cloud",
"home.clipboard.cloudFrom": "From {{name}} · {{time}}",
"home.clipboard.cloudFromSelf": "From this device · {{time}}",
"home.clipboard.cloudEmpty": "Nothing synced yet.",
"home.clipboard.copyToLocal": "Copy to local clipboard",
"home.clipboard.copyToLocalSuccess": "Copied to local clipboard",
"home.clipboard.uploadTitle": "Sync local clipboard",
"home.clipboard.uploadHint": "Plain text only, max 64 KB. Styles (bold / color) are stripped.",
"home.clipboard.uploadButton": "Upload local clipboard",
"home.clipboard.uploading": "Uploading…",
"home.clipboard.uploadEmpty": "Local clipboard is empty",
"home.clipboard.refresh": "Refresh",
"home.clipboard.clear": "Clear cloud",
"home.clipboard.clearConfirm": "Clear the cloud clipboard? All devices will see empty content.",
"home.clipboard.clearSuccess": "Cloud clipboard cleared",
"home.clipboard.hidden": "Content hidden — click Reveal to show",
"home.clipboard.reveal": "Reveal",
"home.clipboard.hide": "Hide",
"setup.title": "Name this device",
"setup.help": "The name is shown to your other devices. Avoid spaces, ≤32 chars.",
"setup.field": "Device name",
"setup.save": "Save and continue",
"login.title": "Sign in",
"login.dev.help": "Dev mode: pick any user identifier. Two tabs with the same identifier simulate two devices of one user.",
"login.dev.field": "Dev user id",
"login.dev.continue": "Continue",
"login.prod.help": "Sign in via Casdoor.",
"login.prod.button": "Sign in with Casdoor",
"oauth.failed": "Sign-in failed",
"oauth.signingIn": "Signing in…",
"oauth.missingParams": "Missing 'code' or 'state' query parameter",
"qr.entry.fromLogin": "Sign in with a QR code",
"qr.entry.scanNewDevice": "Scan to link a device",
"qr.scan.title": "Scan to link a device",
"qr.scan.subtitle": "Point the code shown on the other device at the frame. Approval stays on this signed-in device.",
"qr.scan.starting": "Starting the camera…",
"qr.scan.aiming": "Line the code up inside the frame",
"qr.scan.foreign": "That isn't a sign-in code for this site. Aim at the code from “Sign in with a QR code” on the new device.",
"qr.scan.noCamera": "This device has no camera available. Use a phone or tablet with a camera to scan instead.",
"qr.scan.denied": "Camera access was blocked. Allow the camera in your browser's site settings, then reopen this page.",
"qr.scan.error": "Couldn't open the camera. Try again, or scan from a device with a camera.",
"qr.scan.howto1": "On the new device, open “Sign in with a QR code” to show a code.",
"qr.scan.howto2": "Line that code up in the frame above — approval starts automatically.",
"qr.scan.back": "Back",
"qr.show.title": "Sign in with a QR code",
"qr.show.subtitle": "Name this device, then scan the code from a signed-in phone to let it into your account.",
"qr.show.nameHint": "Shown to your other devices. ASCII characters only.",
"qr.show.generate": "Generate code",
"qr.show.scanTitle": "Scan to approve",
"qr.show.howto1": "Open the camera or cdrop on another signed-in device.",
"qr.show.howto2": "Scan the code above and approve this device.",
"qr.show.waiting": "Waiting for approval…",
"qr.show.approved": "Approved — signing you in…",
"qr.show.expired": "This code has expired. Generate a new one and scan again.",
"qr.show.denied": "This request was declined. If that was you, generate a new code and try again.",
"qr.show.regenerate": "Generate a new code",
"qr.show.back": "Back to home",
"qr.show.backToName": "Back",
"qr.approve.title": "Approve a new device",
"qr.approve.subtitle": "Confirm this device may sign in to your account. Check the device and origin first.",
"qr.approve.loading": "Reading device details…",
"qr.approve.badLink": "This approval link is incomplete. Generate a new code on the device and scan it again.",
"qr.approve.signInFirst": "Sign in to your account first, then approve this device.",
"qr.approve.trustLabel": "Trust duration",
"qr.approve.trust.title": "Trust this device (7 days)",
"qr.approve.trust.hint": "Skip approval for 7 days, renewed on each use.",
"qr.approve.once.title": "Just this once (1 hour)",
"qr.approve.once.hint": "Signed in for 1 hour, then scan again.",
"qr.approve.scopeFull": "This device signs fully into your account for 7 days: it can send and receive files and messages, and manage your account and devices.",
"qr.approve.scopeGuest": "Signs in as a limited guest — it can send and receive files and messages, but can't change your account or approve other devices; expires after 1 hour.",
"qr.approve.nativeFull": "This is a native app device — it signs in with full access (trusted, valid for 7 days). Native apps don't support limited guest sessions.",
"qr.approve.trustContinue": "Trust & Continue",
"qr.approve.approve": "Approve sign-in",
"qr.approve.stepUp.notice": "For security, verify your identity before approving — the button below takes you through a fresh sign-in.",
"qr.approve.stepUp.button": "Verify and approve",
"qr.approve.stepUp.rejected": "Identity verification didn't pass or has expired. Verify again to confirm it's you, then approve.",
"qr.approve.deny": "Decline",
"qr.approve.doneApprovedTitle": "Approved",
"qr.approve.doneApproved": "This device is now linked to your account. You can send and receive files on it.",
"qr.approve.doneDeniedTitle": "Declined",
"qr.approve.doneDenied": "The request was declined. That device won't sign in to your account.",
"qr.approve.expiredTitle": "Code expired",
"qr.approve.expired": "This code is no longer valid. Generate a new one on the device and scan again.",
"qr.approve.errorTitle": "Something went wrong",
"qr.approve.error": "That didn't go through. Try again, or generate a new code on the device.",
"qr.approve.backHome": "Back to home",
"settings.title": "Settings",
"settings.currentDevice.title": "This device",
"settings.deviceName.field": "Device name",
"settings.deviceName.save": "Save",
"settings.deviceName.unchanged": "Name is unchanged",
"settings.deviceName.empty": "Name cannot be empty",
"settings.deviceName.asciiOnly": "Device name must use ASCII characters only (letters, numbers, symbols).",
"settings.deviceName.success": "Device name updated",
"settings.unregister.current": "Unregister this device",
"settings.unregister.currentHint": "Removes this device's registration and signs you out.",
"settings.unregister.currentConfirm": "Unregistering this device will remove its registration and sign you out. Continue?",
"settings.error.delete": "Action failed: {{message}}",
"settings.guest.badge": "Limited guest",
"settings.guest.title": "This device is a limited guest",
"settings.guest.body": "This device is signed in as a limited guest: it can send and receive files, messages, and clipboard, but cannot manage the account, remove devices, or authorize new ones. To get full access, sign in again on your main device.",
"settings.sessions.title": "Login sessions",
"settings.sessions.hint": "Every device currently signed in to this account — browser, desktop, and mobile clients; online ones are listed first. Signing a device out ends its session immediately; it must sign in again on that device to continue.",
"settings.sessions.guestNote": "Limited guests can't manage login sessions. To manage them, sign in fully on your main device.",
"settings.sessions.empty": "No other login sessions.",
"settings.sessions.loadError": "Failed to load login sessions.",
"settings.sessions.retry": "Retry",
"settings.sessions.current": "This device",
"settings.sessions.scopeFull": "Full",
"settings.sessions.scopeGuest": "Limited guest",
"settings.sessions.kind.oidc": "Account login",
"settings.sessions.kind.self": "Local account",
"settings.sessions.kind.guest": "Scanned guest",
"settings.sessions.lastUsed": "Last active {{time}}",
"settings.sessions.neverUsed": "Not active yet",
"settings.sessions.signOut": "Sign out",
"settings.sessions.signingOut": "Signing out…",
"settings.sessions.signOutCurrent": "Sign out this device",
"settings.sessions.confirmOther": "Sign out device \"{{name}}\"? Its session will be invalidated immediately and it must sign in again.",
"settings.sessions.confirmCurrent": "Signing out this device is the same as logging out. Continue?",
"settings.sessions.revoked": "Signed that device out",
"settings.sessions.stepUpRejected": "Identity verification failed or expired. Please try again.",
"settings.online": "Online",
"settings.offline": "Offline",
"settings.lastSeen": "Last seen {{time}}",
"settings.push.title": "Push notifications",
"settings.push.hint": "When this page is closed, new files, messages, and transfer results arrive as system notifications; while it's open, you'll see in-app alerts instead.",
"settings.push.enable": "Enable push",
"settings.push.enabled": "Enabled",
"settings.push.disable": "Turn off",
"settings.push.deniedHint": "Notifications are blocked by the browser. Allow them in this site's settings, then try again.",
"settings.push.denied": "Notification permission denied",
"settings.push.enableOk": "Push notifications enabled",
"settings.push.disableOk": "Push notifications turned off",
"settings.push.failed": "Couldn't enable push. Please try again.",
"notify.incoming.title": "Incoming file",
"notify.incoming.body": "{{sender}} is sending {{file}}",
"notify.transfer.doneTitle": "Transfer complete",
"notify.transfer.failedTitle": "Transfer failed",
"settings.account.title": "Account",
"settings.account.signOut": "Sign out",
"settings.desktop.title": "Desktop",
"settings.desktop.clipboardSync": "Auto-sync clipboard",
"settings.desktop.clipboardSyncHint": "Copies are uploaded to the cloud, and clipboard updates from your other devices are applied here.",
"settings.desktop.launchAtLogin": "Launch at login",
"settings.desktop.launchAtLoginHint": "Start cdrop in the background after you sign in (lives in the menu bar).",
"settings.desktop.downloadDir": "Download folder",
"settings.desktop.downloadDirHint": "Received files are saved here. Leave unset to use the system Downloads folder.",
"settings.desktop.downloadDirChoose": "Change…",
"settings.desktop.downloadDirReset": "Reset to default",
"settings.desktop.downloadDirPicker": "Choose download folder",
"settings.desktop.ttlNote": "For security, cloud clipboard content is cleared automatically after 5 minutes.",
"settings.shortcut.title": "iOS Shortcut",
"settings.shortcut.intro": "Issue a long-lived, clipboard-only, revocable token for the iOS Shortcuts app. Even if leaked it can only read/write the clipboard — nothing else.",
"settings.shortcut.labelField": "Label",
"settings.shortcut.labelPlaceholder": "e.g. My iPhone",
"settings.shortcut.issue": "Issue",
"settings.shortcut.empty": "No tokens issued yet.",
"settings.shortcut.loadError": "Failed to load tokens.",
"settings.shortcut.retry": "Retry",
"settings.shortcut.revoke": "Revoke",
"settings.shortcut.revoking": "Revoking…",
"settings.shortcut.revokeConfirm": "Revoking immediately disables any Shortcut using this token. Continue?",
"settings.shortcut.revoked": "Revoked",
"settings.shortcut.revokedToast": "Token revoked",
"settings.shortcut.expired": "Expired",
"settings.shortcut.expiresAt": "Expires {{date}}",
"settings.shortcut.lastUsed": "Last used {{time}}",
"settings.shortcut.neverUsed": "Never used",
"settings.shortcut.unavailable": "Shortcut tokens are not enabled on the server.",
"settings.shortcut.tooMany": "You have reached the active-token limit. Revoke an old one first.",
"settings.shortcut.issueError": "Failed: {{message}}",
"settings.shortcut.issuedTitle": "Token issued",
"settings.shortcut.onceWarning": "This token is shown only once. Copy it now — you won't be able to see it again after closing.",
"settings.shortcut.tokenLabel": "Token (Bearer)",
"settings.shortcut.copy": "Copy",
"settings.shortcut.copyFailed": "Copy failed — select and copy manually.",
"settings.shortcut.copied": "Copied",
"settings.shortcut.baseLabel": "Server address",
"settings.shortcut.guideTitle": "Set up on iOS (manual sync)",
"settings.shortcut.guideIntro": "Build two shortcuts in the iPhone Shortcuts app, filling in the server address and token above on first run:",
"settings.shortcut.guideDeviceName": "Add header X-Device-Name: your-device-name (ASCII only) to both shortcuts so it shows as one device in the list.",
"settings.shortcut.guidePullTitle": "Pull (cloud → local clipboard)",
"settings.shortcut.guidePull1": "'Get Contents of URL': set the URL to your-server-address + /api/clipboard, method GET, add header Authorization: Bearer your-token.",
"settings.shortcut.guidePull2": "'Get Dictionary Value': key content.",
"settings.shortcut.guidePull3": "If non-empty, 'Copy to Clipboard'; optionally add 'Show Notification'.",
"settings.shortcut.guidePushTitle": "Push (local clipboard → cloud)",
"settings.shortcut.guidePush1": "'Get Clipboard'; stop if empty.",
"settings.shortcut.guidePush2": "'Get Contents of URL': same URL, method PUT, headers Authorization: Bearer your-token and Content-Type: application/json, JSON body: {\"content\": clipboard, \"content_type\": \"text/plain\"}.",
"settings.shortcut.guidePush3": "Optionally add 'Show Notification'.",
"settings.shortcut.guideToleranceNote": "A single run may fail on a flaky network — just retry. The automatic polling version (next phase) retries silently in the background.",
"settings.shortcut.done": "Done",
"transfer.savedTo": "Saved to {{path}}",
"transfer.saveFailed": "Failed to save file: {{error}}",
"transfer.action.cancel": "Cancel",
"transfer.action.delete": "Delete",
"transfer.action.relayNow": "Relay now",
"transfer.action.details": "Details",
"transfer.state.PENDING": "Pending",
"transfer.state.ACCEPTED": "Accepted",
"transfer.state.P2P_ACTIVE": "Transferring",
"transfer.state.RELAY_ACTIVE": "Relaying",
"transfer.state.DONE": "Done",
"transfer.state.FAILED": "Failed",
"transfer.state.CANCELLED": "Cancelled",
"transfer.mode.p2p": "P2P",
"transfer.mode.relay": "Relay",
"transfer.phase.initializing": "Initializing…",
"transfer.phase.waiting_accept": "Waiting for the other side to accept…",
"transfer.phase.ice_gathering": "Gathering network candidates…",
"transfer.phase.ice_checking": "Establishing direct tunnel…",
"transfer.phase.ice_connected": "Tunnel established",
"transfer.phase.dc_open": "Channel open",
"transfer.phase.transferring": "Transferring",
"transfer.phase.completing": "Finalizing…",
"transfer.phase.fallback_pending": "Falling back to relay…",
"transfer.phase.relay_uploading": "Uploading via relay",
"transfer.phase.relay_downloading": "Downloading via relay",
"transfer.stalled": "Looks stalled ({{seconds}}s without progress)",
"transfer.bytesProgress": "{{sent}} / {{total}}",
"transfer.bytesRate": "{{rate}}/s",
"transfer.debug.toggle": "Debug",
"transfer.debug.iceGathering": "Gathering",
"transfer.debug.iceConnection": "Connection",
"transfer.debug.candidatesLocal": "Local candidates",
"transfer.debug.candidatesRemote": "Remote candidates",
"transfer.debug.selectedPair": "Selected pair",
"transfer.debug.noPair": "No nominated pair yet",
"transfer.debug.candidatesLine": "host {{host}} · mDNS {{mdns}} · srflx {{srflx}} · prflx {{prflx}} · relay {{relay}}",
"transfer.debug.dataChannel": "Data channel",
"transfer.debug.dcLine": "{{state}} · buffered {{buffered}}",
"time.justNow": "just now",
"time.secondsAgo": "{{n}}s ago",
"time.minutesAgo": "{{n}}m ago",
"time.hoursAgo": "{{n}}h ago",
"common.dismiss": "Dismiss",
"common.cancel": "Cancel",
"common.confirm": "Confirm",
"errors.messageEmpty": "Message is empty",
"errors.messageOverflow": "Message exceeds 4 KB; send a file instead",
"errors.noReceiver": "No receiver selected",
"errors.devTokenMissing": "Dev mode: VITE_CDROP_DEV_TOKEN is not set in .env.local",
"errors.noAccessToken": "No access token; user must log in",
"errors.clipboardUnavailable": "Browser clipboard API unavailable (requires HTTPS + permission)",
"errors.clipboardOverflow": "Content too large; exceeds {{max}} byte limit",
"errors.clipboardWriteFailed": "Failed to write to local clipboard: {{message}}",
"ios.tab.transfer": "Transfers",
"ios.tab.devices": "Devices",
"ios.tab.settings": "Settings",
"ios.login.generating": "Generating code…",
"ios.login.waiting": "Waiting for approval",
"ios.login.guide": "Scan to approve from another signed-in device",
"ios.login.expired": "Code expired — please try again",
"ios.login.failed": "Sign-in failed — please try again",
"ios.login.refresh": "Refresh QR Code",
"ios.login.needFull": "This device needs full access — choose \"Trust this device\" when approving.",
"ios.send.pickDevice": "Choose a device",
"ios.transfer.sending": "Sending",
"ios.settings.engine": "Engine",
"ios.settings.status": "Status",
"ios.settings.device": "Device",
"ios.settings.clipboard": "Clipboard",
"ios.settings.clipboardNote": "Upload pushes this device's clipboard to your others; Pull writes the latest cloud clipboard.",
"ios.clipboard.upload": "Upload Clipboard",
"ios.clipboard.pull": "Pull Clipboard",
"ios.clipboard.uploading": "Uploading…",
"ios.clipboard.pulling": "Pulling…",
"ios.clipboard.uploaded": "Uploaded to cloud clipboard",
"ios.clipboard.pulled": "Written to clipboard",
"ios.clipboard.empty": "Clipboard is empty",
"ios.clipboard.failed": "Clipboard sync failed",
"ios.devices.remove": "Remove Device",
"ios.devices.thisDevice": "This device",
"ios.devices.revoked": "Device removed",
"ios.devices.revokeFailed": "Remove failed",
"ios.devices.revokeStepUp": "Re-verify on the web to remove this device",
"ios.engine.disconnected": "Disconnected",
"ios.engine.ready": "Ready",
"ios.transfer.empty": "No transfers yet",
"ios.transfer.incoming": "Receive",
"ios.transfer.outgoing": "Send",
"ios.send.noDevices": "No online devices to send to",
"ios.devices.empty": "No devices yet",
"ios.detail.title": "Transfer Details",
"ios.detail.direction": "Direction",
"ios.detail.peer": "Peer",
"ios.detail.state": "State",
"ios.detail.phase": "Phase",
"ios.detail.channel": "Channel",
"ios.detail.size": "Size",
"ios.detail.progress": "Progress",
"ios.detail.speed": "Speed",
"ios.settings.account": "Account",
"ios.settings.user": "User",
"ios.settings.logout": "Sign Out",
"ios.settings.deviceName": "Device Name",
"ios.settings.deviceNameNote": "Renaming takes effect on next sign-in",
"ios.settings.deviceCount": "Known Devices",
"ios.settings.signaling": "Signaling",
"ios.settings.presenceEvents": "Presence Events",
"ios.settings.reconnecting": "Reconnecting",
"ios.settings.lastLog": "Last Log",
"ios.tab.files": "Files",
"ios.files.title": "Received Files",
"ios.files.empty": "No files received yet",
"ios.files.share": "Share",
"ios.files.done": "Done",
"ios.detail.openFile": "Open File"
}
+358
View File
@@ -0,0 +1,358 @@
{
"app.brand": "Commilitia Drop",
"deviceType.browser": "浏览器",
"deviceType.macos": "macOS 客户端",
"deviceType.windows": "Windows 客户端",
"deviceType.linux": "Linux 客户端",
"deviceType.ios": "iOS 客户端",
"deviceType.shortcut": "快捷指令",
"app.connected": "已连接",
"app.connecting": "正在连接…",
"app.disconnectedTitle": "正在重连",
"app.disconnectedHint": "与服务器的连接已断开,正在自动重连,无需刷新页面。",
"auth.sessionLost.title": "登录已失效",
"auth.sessionLost.body": "此设备的登录凭据已被注销或过期,请重新登录。",
"nav.accountMenu": "账号菜单",
"nav.thisDeviceLabel": "本机:",
"nav.settings": "设置",
"nav.signOut": "退出登录",
"nav.backToHome": "← 主页",
"nav.language": "语言 / Language",
"nav.theme": "主题 / Theme",
"nav.theme.light": "浅色",
"nav.theme.dark": "深色",
"nav.theme.system": "跟随系统",
"nav.themeToggle": "切换主题",
"home.greeting": "你好,{{name}}",
"home.deviceList.title": "设备",
"home.deviceList.onlineSuffix": "{{count}} 台在线)",
"home.deviceList.showOffline": "显示离线设备({{count}}",
"home.deviceList.hideOffline": "隐藏离线设备",
"home.deviceList.empty": "暂无其他设备。在另一标签页或设备上以相同账户登录,并设置一个不同的设备名(设置 → 设备名称),即可在此处看到它。",
"home.tabs.file": "文件",
"home.tabs.message": "消息",
"home.tabs.clipboard": "剪贴板",
"home.file.dropzone": "拖放文件到此处或点击选择",
"home.file.selectedSize": "{{name}}{{size}}",
"home.file.rejected": "文件已拒绝",
"home.cta.selectDeviceAndFile": "请选择设备与文件",
"home.cta.selectFile": "请选择文件",
"home.cta.selectDevice": "请选择设备",
"home.cta.sendTo": "发送到 {{name}}",
"home.message.placeholder": "发送消息到 {{name}}…",
"home.message.placeholderEmpty": "请先选择设备",
"home.message.hint": "回车发送,Shift + 回车换行,最长4 KB。",
"home.message.title": "消息",
"home.message.countSuffix": "{{count}}",
"home.message.clearAll": "全部清除",
"home.transfer.active": "进行中",
"home.transfer.history": "最近的传输",
"home.clipboard.cloudTitle": "云端最新",
"home.clipboard.cloudFrom": "来自 {{name}} · {{time}}",
"home.clipboard.cloudFromSelf": "来自本机 · {{time}}",
"home.clipboard.cloudEmpty": "尚未同步任何内容。",
"home.clipboard.copyToLocal": "复制到本机剪贴板",
"home.clipboard.copyToLocalSuccess": "已复制到本机剪贴板",
"home.clipboard.uploadTitle": "同步本机剪贴板",
"home.clipboard.uploadHint": "仅纯文本,最大64 KB;样式(粗体/颜色等)会被剥除。",
"home.clipboard.uploadButton": "上传本机剪贴板",
"home.clipboard.uploading": "上传中…",
"home.clipboard.uploadEmpty": "本机剪贴板为空",
"home.clipboard.refresh": "刷新",
"home.clipboard.clear": "清空云端",
"home.clipboard.clearConfirm": "确认清空云端剪贴板?所有设备将看到空内容。",
"home.clipboard.clearSuccess": "已清空云端剪贴板",
"home.clipboard.hidden": "内容已隐藏 · 点击“显示”查看",
"home.clipboard.reveal": "显示",
"home.clipboard.hide": "隐藏",
"setup.title": "为本机命名",
"setup.help": "此名称会展示给你的其他设备。建议避免空格,长度不超过 32 字符。",
"setup.field": "设备名称",
"setup.save": "保存并继续",
"login.title": "登录",
"login.dev.help": "Dev模式:随意填写一个用户标识。同标识的两个标签页将模拟同一用户的两台设备。",
"login.dev.field": "Dev用户标识",
"login.dev.continue": "继续",
"login.prod.help": "通过Casdoor登录。",
"login.prod.button": "使用Casdoor登录",
"oauth.failed": "登录失败",
"oauth.signingIn": "正在登录…",
"oauth.missingParams": "缺少code或state查询参数",
"qr.entry.fromLogin": "扫码登录此设备",
"qr.entry.scanNewDevice": "扫码登录新设备",
"qr.scan.title": "扫码登录新设备",
"qr.scan.subtitle": "把另一台设备显示的二维码对准取景框。批准全程留在这台已登录设备上。",
"qr.scan.starting": "正在启动相机…",
"qr.scan.aiming": "把二维码对准取景框",
"qr.scan.foreign": "这不是本站的登录二维码。请对准新设备上“扫码登录此设备”生成的码。",
"qr.scan.noCamera": "这台设备没有可用的摄像头。请改用带相机的手机或平板扫码。",
"qr.scan.denied": "相机权限被拒绝。请在浏览器站点设置里允许使用相机,再重新打开此页。",
"qr.scan.error": "无法打开相机。请重试,或改用带相机的设备扫码。",
"qr.scan.howto1": "在新设备上打开“扫码登录此设备”,生成一张二维码。",
"qr.scan.howto2": "把那张二维码对准上方取景框,识别后会自动进入批准。",
"qr.scan.back": "返回",
"qr.show.title": "扫码登录此设备",
"qr.show.subtitle": "先给这台设备起个名字,再用已登录的手机扫码批准它接入你的账号。",
"qr.show.nameHint": "此名称会展示给你的其他设备,仅可使用 ASCII 字符。",
"qr.show.generate": "生成二维码",
"qr.show.scanTitle": "用手机扫码批准",
"qr.show.howto1": "在另一台已登录的设备上打开相机或 cdrop。",
"qr.show.howto2": "扫描上方二维码,按提示批准此设备。",
"qr.show.waiting": "等待批准…",
"qr.show.approved": "已批准,正在进入…",
"qr.show.expired": "二维码已过期。重新生成一张再扫。",
"qr.show.denied": "这次接入被拒绝。如确为你本人操作,重新生成二维码再试。",
"qr.show.regenerate": "重新生成二维码",
"qr.show.back": "返回主页",
"qr.show.backToName": "返回上一步",
"qr.approve.title": "批准新设备",
"qr.approve.subtitle": "确认让这台设备登录你的账号。请核对设备与来源无误。",
"qr.approve.loading": "正在读取设备信息…",
"qr.approve.badLink": "这个批准链接不完整。请在新设备上重新生成二维码再扫。",
"qr.approve.signInFirst": "先登录你的账号,再批准这台设备接入。",
"qr.approve.trustLabel": "信任时长",
"qr.approve.trust.title": "信任此设备(7 天)",
"qr.approve.trust.hint": "7 天内免再次批准,每次使用自动续期。",
"qr.approve.once.title": "仅此一次(1 小时)",
"qr.approve.once.hint": "登录有效 1 小时,到期需重新扫码。",
"qr.approve.scopeFull": "这台设备将完整登录你的账号、7 天内有效:可正常收发文件与消息,并能管理账号与设备。",
"qr.approve.scopeGuest": "以受限访客身份登录——可收发文件与消息,但不能更改账号、不能批准其他设备;1 小时后失效。",
"qr.approve.nativeFull": "这是一台原生应用设备,将以完整权限登录(信任此设备、7 天内有效)。原生应用不支持受限访客。",
"qr.approve.trustContinue": "信任并继续",
"qr.approve.approve": "批准登录",
"qr.approve.stepUp.notice": "为安全起见,批准前需先验证你的身份——点下方按钮会引导你重新登录一次。",
"qr.approve.stepUp.button": "验证并批准",
"qr.approve.stepUp.rejected": "身份验证未通过或已过期。请再验证一次,确认是你本人后即可批准。",
"qr.approve.deny": "拒绝",
"qr.approve.doneApprovedTitle": "已批准",
"qr.approve.doneApproved": "这台设备已接入你的账号,现在即可在它上面收发文件。",
"qr.approve.doneDeniedTitle": "已拒绝",
"qr.approve.doneDenied": "这次接入已被拒绝,那台设备不会登录你的账号。",
"qr.approve.expiredTitle": "二维码已过期",
"qr.approve.expired": "这张二维码已失效。请在新设备上重新生成再扫。",
"qr.approve.errorTitle": "出错了",
"qr.approve.error": "操作没能完成。请重试,或在新设备上重新生成二维码。",
"qr.approve.backHome": "返回主页",
"settings.title": "设置",
"settings.currentDevice.title": "当前设备",
"settings.deviceName.field": "设备名称",
"settings.deviceName.save": "保存",
"settings.deviceName.unchanged": "新名称与当前一致",
"settings.deviceName.empty": "名称不能为空",
"settings.deviceName.asciiOnly": "设备名称只能使用 ASCII 字符(英文字母、数字、符号)。",
"settings.deviceName.success": "设备名称已更新",
"settings.unregister.current": "注销当前设备",
"settings.unregister.currentHint": "将退出登录并移除此设备的注册。",
"settings.unregister.currentConfirm": "注销当前设备会移除此设备的注册并退出登录,确认继续?",
"settings.error.delete": "操作失败:{{message}}",
"settings.guest.badge": "受限访客",
"settings.guest.title": "这台设备是受限访客",
"settings.guest.body": "本设备以受限访客身份登录:可正常收发文件、消息与剪贴板,但不能管理账号、移除设备或授权新设备。需要完整权限,请在你的主设备上重新登录。",
"settings.sessions.title": "登录会话",
"settings.sessions.hint": "这里列出当前登录此账号的所有设备,含浏览器、桌面与移动客户端;在线的排在上方。登出某台设备会立即结束它的登录,需在该设备上重新登录才能继续使用。",
"settings.sessions.guestNote": "受限访客无法管理登录会话;如需管理,请在你的主设备上完整登录。",
"settings.sessions.empty": "暂无其他登录会话。",
"settings.sessions.loadError": "登录会话加载失败。",
"settings.sessions.retry": "重试",
"settings.sessions.current": "本机",
"settings.sessions.scopeFull": "完整",
"settings.sessions.scopeGuest": "受限访客",
"settings.sessions.kind.oidc": "账号登录",
"settings.sessions.kind.self": "本地账号",
"settings.sessions.kind.guest": "扫码访客",
"settings.sessions.lastUsed": "最近活跃 {{time}}",
"settings.sessions.neverUsed": "尚未活跃",
"settings.sessions.signOut": "登出",
"settings.sessions.signingOut": "正在登出…",
"settings.sessions.signOutCurrent": "登出本机",
"settings.sessions.confirmOther": "确认登出设备“{{name}}”?该会话将立即失效,需重新登录。",
"settings.sessions.confirmCurrent": "登出本机会等同于退出登录,确认继续?",
"settings.sessions.revoked": "已登出该设备",
"settings.sessions.stepUpRejected": "身份验证未通过或已过期,请再试一次。",
"settings.online": "在线",
"settings.offline": "离线",
"settings.lastSeen": "上次活跃 {{time}}",
"settings.push.title": "推送通知",
"settings.push.hint": "页面关闭时,新文件、消息和传输结果会以系统通知提醒;页面打开时仍走应用内提示。",
"settings.push.enable": "启用推送",
"settings.push.enabled": "已启用",
"settings.push.disable": "关闭推送",
"settings.push.deniedHint": "通知权限已被浏览器拒绝,请在浏览器的站点设置中允许后重试。",
"settings.push.denied": "通知权限被拒绝",
"settings.push.enableOk": "已启用推送通知",
"settings.push.disableOk": "已关闭推送通知",
"settings.push.failed": "启用推送失败,请重试",
"notify.incoming.title": "收到文件",
"notify.incoming.body": "{{sender}} 发来 {{file}}",
"notify.transfer.doneTitle": "传输完成",
"notify.transfer.failedTitle": "传输失败",
"settings.account.title": "账号",
"settings.account.signOut": "退出登录",
"settings.desktop.title": "桌面",
"settings.desktop.clipboardSync": "剪贴板自动同步",
"settings.desktop.clipboardSyncHint": "复制即上传到云端,并接收其他设备的剪贴板更新。",
"settings.desktop.launchAtLogin": "开机自启",
"settings.desktop.launchAtLoginHint": "登录系统后自动在后台启动 cdrop(菜单栏常驻)。",
"settings.desktop.downloadDir": "下载目录",
"settings.desktop.downloadDirHint": "接收到的文件保存到此目录;留空则使用系统下载目录。",
"settings.desktop.downloadDirChoose": "更改…",
"settings.desktop.downloadDirReset": "恢复默认",
"settings.desktop.downloadDirPicker": "选择下载目录",
"settings.desktop.ttlNote": "出于安全考虑,云端剪贴板内容会在 5 分钟后自动清除。",
"settings.shortcut.title": "iOS 快捷指令",
"settings.shortcut.intro": "为 iOS“快捷指令”签发长效、仅限剪贴板、可随时吊销的专用令牌。即便令牌泄漏也只能读写剪贴板、无法触及其他数据。",
"settings.shortcut.labelField": "备注名称",
"settings.shortcut.labelPlaceholder": "例如:我的 iPhone",
"settings.shortcut.issue": "签发",
"settings.shortcut.empty": "尚未签发任何令牌。",
"settings.shortcut.loadError": "令牌列表加载失败。",
"settings.shortcut.retry": "重试",
"settings.shortcut.revoke": "吊销",
"settings.shortcut.revoking": "正在吊销…",
"settings.shortcut.revokeConfirm": "吊销后使用此令牌的快捷指令将立即失效,确认继续?",
"settings.shortcut.revoked": "已吊销",
"settings.shortcut.revokedToast": "令牌已吊销",
"settings.shortcut.expired": "已过期",
"settings.shortcut.expiresAt": "到期 {{date}}",
"settings.shortcut.lastUsed": "上次使用 {{time}}",
"settings.shortcut.neverUsed": "尚未使用",
"settings.shortcut.unavailable": "服务器未启用快捷指令令牌。",
"settings.shortcut.tooMany": "活跃令牌已达上限,请先吊销旧令牌。",
"settings.shortcut.issueError": "操作失败:{{message}}",
"settings.shortcut.issuedTitle": "令牌已签发",
"settings.shortcut.onceWarning": "令牌仅显示这一次,请立即复制保存;关闭后无法再次查看。",
"settings.shortcut.tokenLabel": "令牌(Bearer",
"settings.shortcut.copy": "复制",
"settings.shortcut.copyFailed": "复制失败,请手动选择复制",
"settings.shortcut.copied": "已复制",
"settings.shortcut.baseLabel": "服务器地址",
"settings.shortcut.guideTitle": "在 iOS 上手动搭建(手动同步)",
"settings.shortcut.guideIntro": "在 iPhone 的“快捷指令”App 中新建两个指令,首次运行时填入上面的服务器地址与令牌:",
"settings.shortcut.guideDeviceName": "两个指令都加请求头 X-Device-Name: 你的设备名(仅 ASCII),让它在设备列表里统一显示。",
"settings.shortcut.guidePullTitle": "拉取(云端 → 本机剪贴板)",
"settings.shortcut.guidePull1": "“获取 URL 内容”:地址设为 你的服务器地址 加 /api/clipboard,方法 GET,请求头加 Authorization: Bearer 你的令牌。",
"settings.shortcut.guidePull2": "“获取词典值”:取键 content。",
"settings.shortcut.guidePull3": "内容非空时用“拷贝到剪贴板”写入,可再加“显示通知”提示已拉取。",
"settings.shortcut.guidePushTitle": "上传(本机剪贴板 → 云端)",
"settings.shortcut.guidePush1": "“获取剪贴板”;若为空则停止。",
"settings.shortcut.guidePush2": "“获取 URL 内容”:地址同上,方法 PUT,请求头加 Authorization: Bearer 你的令牌 与 Content-Type: application/json,请求体选 JSON{\"content\": 剪贴板, \"content_type\": \"text/plain\"}。",
"settings.shortcut.guidePush3": "可加“显示通知”提示已上传。",
"settings.shortcut.guideToleranceNote": "网络不稳定时单次运行可能失败,重试即可;自动轮询版会在后台静默重试(下一阶段提供)。",
"settings.shortcut.done": "完成",
"transfer.savedTo": "已保存到 {{path}}",
"transfer.saveFailed": "保存文件失败:{{error}}",
"transfer.action.cancel": "取消",
"transfer.action.delete": "删除",
"transfer.action.relayNow": "立即中继",
"transfer.action.details": "详情",
"transfer.state.PENDING": "等待中",
"transfer.state.ACCEPTED": "已接受",
"transfer.state.P2P_ACTIVE": "传输中",
"transfer.state.RELAY_ACTIVE": "中继中",
"transfer.state.DONE": "已完成",
"transfer.state.FAILED": "失败",
"transfer.state.CANCELLED": "已取消",
"transfer.mode.p2p": "P2P",
"transfer.mode.relay": "中继",
"transfer.phase.initializing": "正在初始化…",
"transfer.phase.waiting_accept": "等待对端接受…",
"transfer.phase.ice_gathering": "收集网络候选…",
"transfer.phase.ice_checking": "建立直连通道…",
"transfer.phase.ice_connected": "通道已连接",
"transfer.phase.dc_open": "信道已打开",
"transfer.phase.transferring": "传输中",
"transfer.phase.completing": "收尾中…",
"transfer.phase.fallback_pending": "切换到中继路径…",
"transfer.phase.relay_uploading": "中继上传中",
"transfer.phase.relay_downloading": "中继下载中",
"transfer.stalled": "似乎卡住了({{seconds}} 秒无进度)",
"transfer.bytesProgress": "{{sent}} / {{total}}",
"transfer.bytesRate": "{{rate}}/秒",
"transfer.debug.toggle": "调试信息",
"transfer.debug.iceGathering": "候选收集",
"transfer.debug.iceConnection": "连接状态",
"transfer.debug.candidatesLocal": "本端候选",
"transfer.debug.candidatesRemote": "对端候选",
"transfer.debug.selectedPair": "选中候选对",
"transfer.debug.noPair": "尚未选定候选对",
"transfer.debug.candidatesLine": "host {{host}} · mDNS {{mdns}} · srflx {{srflx}} · prflx {{prflx}} · relay {{relay}}",
"transfer.debug.dataChannel": "数据信道",
"transfer.debug.dcLine": "{{state}} · 待发送 {{buffered}}",
"time.justNow": "刚刚",
"time.secondsAgo": "{{n}} 秒前",
"time.minutesAgo": "{{n}} 分钟前",
"time.hoursAgo": "{{n}} 小时前",
"common.dismiss": "移除",
"common.cancel": "取消",
"common.confirm": "确认",
"errors.messageEmpty": "消息为空",
"errors.messageOverflow": "消息超过4 KB;请改用文件传输",
"errors.noReceiver": "未选择接收方",
"errors.devTokenMissing": "Dev模式:未在.env.local设置VITE_CDROP_DEV_TOKEN",
"errors.noAccessToken": "无访问令牌:用户必须先登录",
"errors.clipboardUnavailable": "浏览器不支持剪贴板API(需HTTPS与权限)",
"errors.clipboardOverflow": "内容过大,超过 {{max}} 字节上限",
"errors.clipboardWriteFailed": "写入本机剪贴板失败:{{message}}",
"ios.tab.transfer": "传输",
"ios.tab.devices": "设备",
"ios.tab.settings": "设置",
"ios.login.generating": "正在生成二维码…",
"ios.login.waiting": "等待扫码批准",
"ios.login.guide": "用另一台已登录的设备扫码批准",
"ios.login.expired": "二维码已失效,请重试",
"ios.login.failed": "登录失败,请重试",
"ios.login.refresh": "刷新二维码",
"ios.login.needFull": "此设备需要完整权限,批准时请选择“信任此设备”",
"ios.send.pickDevice": "选择接收设备",
"ios.transfer.sending": "发送中",
"ios.settings.engine": "引擎",
"ios.settings.status": "状态",
"ios.settings.device": "设备",
"ios.settings.clipboard": "剪贴板",
"ios.settings.clipboardNote": "上传把本机剪贴板推到其他设备;拉取写入最新云剪贴板。",
"ios.clipboard.upload": "上传剪贴板",
"ios.clipboard.pull": "拉取剪贴板",
"ios.clipboard.uploading": "正在上传…",
"ios.clipboard.pulling": "正在拉取…",
"ios.clipboard.uploaded": "已上传到云剪贴板",
"ios.clipboard.pulled": "已写入本机剪贴板",
"ios.clipboard.empty": "剪贴板为空",
"ios.clipboard.failed": "剪贴板同步失败",
"ios.devices.remove": "移除设备",
"ios.devices.thisDevice": "本机",
"ios.devices.revoked": "已移除设备",
"ios.devices.revokeFailed": "移除失败",
"ios.devices.revokeStepUp": "需在网页端重新验证后才能移除",
"ios.engine.disconnected": "未连接",
"ios.engine.ready": "已就绪",
"ios.transfer.empty": "暂无传输",
"ios.transfer.incoming": "接收",
"ios.transfer.outgoing": "发送",
"ios.send.noDevices": "没有在线设备,无法发送",
"ios.devices.empty": "暂无设备",
"ios.detail.title": "传输详情",
"ios.detail.direction": "方向",
"ios.detail.peer": "对端",
"ios.detail.state": "状态",
"ios.detail.phase": "阶段",
"ios.detail.channel": "通道",
"ios.detail.size": "大小",
"ios.detail.progress": "进度",
"ios.detail.speed": "速度",
"ios.settings.account": "账户",
"ios.settings.user": "用户",
"ios.settings.logout": "退出登录",
"ios.settings.deviceName": "设备名称",
"ios.settings.deviceNameNote": "改名将在下次登录后生效",
"ios.settings.deviceCount": "已知设备",
"ios.settings.signaling": "信令连接",
"ios.settings.presenceEvents": "在线事件",
"ios.settings.reconnecting": "重连中",
"ios.settings.lastLog": "最近日志",
"ios.tab.files": "文件",
"ios.files.title": "收到的文件",
"ios.files.empty": "还没有收到文件",
"ios.files.share": "分享",
"ios.files.done": "完成",
"ios.detail.openFile": "打开文件"
}
+358
View File
@@ -0,0 +1,358 @@
{
"app.brand": "Commilitia Drop",
"deviceType.browser": "瀏覽器",
"deviceType.macos": "macOS 用戶端",
"deviceType.windows": "Windows 用戶端",
"deviceType.linux": "Linux 用戶端",
"deviceType.ios": "iOS 用戶端",
"deviceType.shortcut": "捷徑",
"app.connected": "已連線",
"app.connecting": "正在連線…",
"app.disconnectedTitle": "正在重新連線",
"app.disconnectedHint": "與伺服器的連線已中斷,正在自動重新連線,無需重新整理頁面。",
"auth.sessionLost.title": "登入已失效",
"auth.sessionLost.body": "此裝置的登入憑證已被撤銷或逾期,請重新登入。",
"nav.accountMenu": "帳號選單",
"nav.thisDeviceLabel": "本機:",
"nav.settings": "設定",
"nav.signOut": "登出",
"nav.backToHome": "← 首頁",
"nav.language": "語言 / Language",
"nav.theme": "主題 / Theme",
"nav.theme.light": "淺色",
"nav.theme.dark": "深色",
"nav.theme.system": "跟隨系統",
"nav.themeToggle": "切換主題",
"home.greeting": "你好,{{name}}",
"home.deviceList.title": "裝置",
"home.deviceList.onlineSuffix": "{{count}} 部線上)",
"home.deviceList.showOffline": "顯示離線裝置({{count}}",
"home.deviceList.hideOffline": "隱藏離線裝置",
"home.deviceList.empty": "尚無其他裝置。在另一個分頁或裝置上以同一帳號登入,並設定一個不同的裝置名稱(設定 → 裝置名稱),即可在此處看到它。",
"home.tabs.file": "檔案",
"home.tabs.message": "訊息",
"home.tabs.clipboard": "剪貼簿",
"home.file.dropzone": "拖曳檔案到此處或點選選擇",
"home.file.selectedSize": "{{name}}{{size}}",
"home.file.rejected": "檔案已拒絕",
"home.cta.selectDeviceAndFile": "請選擇裝置與檔案",
"home.cta.selectFile": "請選擇檔案",
"home.cta.selectDevice": "請選擇裝置",
"home.cta.sendTo": "傳送到 {{name}}",
"home.message.placeholder": "傳送訊息給 {{name}}…",
"home.message.placeholderEmpty": "請先選擇裝置",
"home.message.hint": "Enter傳送,Shift + Enter換行,最長4 KB。",
"home.message.title": "訊息",
"home.message.countSuffix": "{{count}}",
"home.message.clearAll": "全部清除",
"home.transfer.active": "進行中",
"home.transfer.history": "最近的傳輸",
"home.clipboard.cloudTitle": "雲端最新",
"home.clipboard.cloudFrom": "來自 {{name}} · {{time}}",
"home.clipboard.cloudFromSelf": "來自本機 · {{time}}",
"home.clipboard.cloudEmpty": "尚未同步任何內容。",
"home.clipboard.copyToLocal": "複製到本機剪貼簿",
"home.clipboard.copyToLocalSuccess": "已複製到本機剪貼簿",
"home.clipboard.uploadTitle": "同步本機剪貼簿",
"home.clipboard.uploadHint": "僅純文字,最大64 KB;樣式(粗體 / 顏色等)會被剝除。",
"home.clipboard.uploadButton": "上傳本機剪貼簿",
"home.clipboard.uploading": "上傳中…",
"home.clipboard.uploadEmpty": "本機剪貼簿為空",
"home.clipboard.refresh": "重新整理",
"home.clipboard.clear": "清空雲端",
"home.clipboard.clearConfirm": "確認清空雲端剪貼簿?所有裝置將看到空內容。",
"home.clipboard.clearSuccess": "已清空雲端剪貼簿",
"home.clipboard.hidden": "內容已隱藏 · 點擊「顯示」查看",
"home.clipboard.reveal": "顯示",
"home.clipboard.hide": "隱藏",
"setup.title": "為本機命名",
"setup.help": "此名稱會顯示給你的其他裝置。建議避免空格,長度不超過 32 個字元。",
"setup.field": "裝置名稱",
"setup.save": "儲存並繼續",
"login.title": "登入",
"login.dev.help": "Dev模式:隨意填寫一個使用者識別碼。同識別碼的兩個分頁將模擬同一使用者的兩部裝置。",
"login.dev.field": "Dev使用者識別碼",
"login.dev.continue": "繼續",
"login.prod.help": "透過Casdoor登入。",
"login.prod.button": "透過Casdoor登入",
"oauth.failed": "登入失敗",
"oauth.signingIn": "正在登入…",
"oauth.missingParams": "缺少code或state查詢參數",
"qr.entry.fromLogin": "掃碼登入此裝置",
"qr.entry.scanNewDevice": "掃碼登入新裝置",
"qr.scan.title": "掃碼登入新裝置",
"qr.scan.subtitle": "把另一部裝置顯示的 QR 碼對準取景框。批准全程留在這部已登入裝置上。",
"qr.scan.starting": "正在啟動相機…",
"qr.scan.aiming": "把 QR 碼對準取景框",
"qr.scan.foreign": "這不是本站的登入 QR 碼。請對準新裝置上「掃碼登入此裝置」產生的碼。",
"qr.scan.noCamera": "這部裝置沒有可用的相機。請改用有相機的手機或平板掃碼。",
"qr.scan.denied": "相機權限被拒絕。請在瀏覽器網站設定裡允許使用相機,再重新開啟此頁。",
"qr.scan.error": "無法開啟相機。請重試,或改用有相機的裝置掃碼。",
"qr.scan.howto1": "在新裝置上開啟「掃碼登入此裝置」,產生一張 QR 碼。",
"qr.scan.howto2": "把那張 QR 碼對準上方取景框,辨識後會自動進入批准。",
"qr.scan.back": "返回",
"qr.show.title": "掃碼登入此裝置",
"qr.show.subtitle": "先為這部裝置命名,再用已登入的手機掃碼批准它接入你的帳號。",
"qr.show.nameHint": "此名稱會顯示給你的其他裝置,僅可使用 ASCII 字元。",
"qr.show.generate": "產生 QR 碼",
"qr.show.scanTitle": "用手機掃碼批准",
"qr.show.howto1": "在另一部已登入的裝置上開啟相機或 cdrop。",
"qr.show.howto2": "掃描上方 QR 碼,依提示批准此裝置。",
"qr.show.waiting": "等待批准…",
"qr.show.approved": "已批准,正在進入…",
"qr.show.expired": "QR 碼已逾時。重新產生一張再掃。",
"qr.show.denied": "這次接入被拒絕。如確為你本人操作,重新產生 QR 碼再試。",
"qr.show.regenerate": "重新產生 QR 碼",
"qr.show.back": "返回首頁",
"qr.show.backToName": "返回上一步",
"qr.approve.title": "批准新裝置",
"qr.approve.subtitle": "確認讓這部裝置登入你的帳號。請核對裝置與來源無誤。",
"qr.approve.loading": "正在讀取裝置資訊…",
"qr.approve.badLink": "這個批准連結不完整。請在新裝置上重新產生 QR 碼再掃。",
"qr.approve.signInFirst": "先登入你的帳號,再批准這部裝置接入。",
"qr.approve.trustLabel": "信任時長",
"qr.approve.trust.title": "信任此裝置(7 天)",
"qr.approve.trust.hint": "7 天內免再次批准,每次使用自動續期。",
"qr.approve.once.title": "僅此一次(1 小時)",
"qr.approve.once.hint": "登入有效 1 小時,逾時需重新掃碼。",
"qr.approve.scopeFull": "這部裝置將完整登入你的帳號、7 天內有效:可正常收發檔案與訊息,並能管理帳號與裝置。",
"qr.approve.scopeGuest": "以受限訪客身分登入——可收發檔案與訊息,但不能變更帳號、不能批准其他裝置;1 小時後失效。",
"qr.approve.nativeFull": "這是一台原生應用裝置,將以完整權限登入(信任此裝置、7 天內有效)。原生應用不支援受限訪客。",
"qr.approve.trustContinue": "信任並繼續",
"qr.approve.approve": "批准登入",
"qr.approve.stepUp.notice": "為安全起見,批准前需先驗證你的身分——點下方按鈕會引導你重新登入一次。",
"qr.approve.stepUp.button": "驗證並批准",
"qr.approve.stepUp.rejected": "身分驗證未通過或已逾時。請再驗證一次,確認是你本人後即可批准。",
"qr.approve.deny": "拒絕",
"qr.approve.doneApprovedTitle": "已批准",
"qr.approve.doneApproved": "這部裝置已接入你的帳號,現在即可在它上面收發檔案。",
"qr.approve.doneDeniedTitle": "已拒絕",
"qr.approve.doneDenied": "這次接入已被拒絕,那部裝置不會登入你的帳號。",
"qr.approve.expiredTitle": "QR 碼已逾時",
"qr.approve.expired": "這張 QR 碼已失效。請在新裝置上重新產生再掃。",
"qr.approve.errorTitle": "出錯了",
"qr.approve.error": "操作沒能完成。請重試,或在新裝置上重新產生 QR 碼。",
"qr.approve.backHome": "返回首頁",
"settings.title": "設定",
"settings.currentDevice.title": "目前裝置",
"settings.deviceName.field": "裝置名稱",
"settings.deviceName.save": "儲存",
"settings.deviceName.unchanged": "新名稱與目前一致",
"settings.deviceName.empty": "名稱不能為空",
"settings.deviceName.asciiOnly": "裝置名稱只能使用 ASCII 字元(英文字母、數字、符號)。",
"settings.deviceName.success": "裝置名稱已更新",
"settings.unregister.current": "解除註冊本裝置",
"settings.unregister.currentHint": "將登出並移除本裝置的註冊。",
"settings.unregister.currentConfirm": "解除註冊本裝置會移除其註冊並登出,確認繼續?",
"settings.error.delete": "操作失敗:{{message}}",
"settings.guest.badge": "受限訪客",
"settings.guest.title": "這部裝置是受限訪客",
"settings.guest.body": "本裝置以受限訪客身分登入:可正常收發檔案、訊息與剪貼簿,但不能管理帳號、移除裝置或授權新裝置。需要完整權限,請在你的主裝置上重新登入。",
"settings.sessions.title": "登入工作階段",
"settings.sessions.hint": "這裡列出目前登入此帳號的所有裝置,含瀏覽器、桌面與行動用戶端;線上的排在上方。登出某台裝置會立即結束它的登入,需在該裝置上重新登入才能繼續使用。",
"settings.sessions.guestNote": "受限訪客無法管理登入工作階段;如需管理,請在你的主裝置上完整登入。",
"settings.sessions.empty": "尚無其他登入工作階段。",
"settings.sessions.loadError": "登入工作階段載入失敗。",
"settings.sessions.retry": "重試",
"settings.sessions.current": "本機",
"settings.sessions.scopeFull": "完整",
"settings.sessions.scopeGuest": "受限訪客",
"settings.sessions.kind.oidc": "帳號登入",
"settings.sessions.kind.self": "本機帳號",
"settings.sessions.kind.guest": "掃碼訪客",
"settings.sessions.lastUsed": "最近活躍 {{time}}",
"settings.sessions.neverUsed": "尚未活躍",
"settings.sessions.signOut": "登出",
"settings.sessions.signingOut": "正在登出…",
"settings.sessions.signOutCurrent": "登出本機",
"settings.sessions.confirmOther": "確認登出裝置「{{name}}」?該工作階段將立即失效,需重新登入。",
"settings.sessions.confirmCurrent": "登出本機等同於登出帳號,確認繼續?",
"settings.sessions.revoked": "已登出該裝置",
"settings.sessions.stepUpRejected": "身分驗證未通過或已過期,請再試一次。",
"settings.online": "線上",
"settings.offline": "離線",
"settings.lastSeen": "上次活躍 {{time}}",
"settings.push.title": "推播通知",
"settings.push.hint": "頁面關閉時,新檔案、訊息與傳輸結果會以系統通知提醒;頁面開啟時仍走應用內提示。",
"settings.push.enable": "啟用推播",
"settings.push.enabled": "已啟用",
"settings.push.disable": "關閉推播",
"settings.push.deniedHint": "通知權限已被瀏覽器拒絕,請在瀏覽器的網站設定中允許後重試。",
"settings.push.denied": "通知權限被拒絕",
"settings.push.enableOk": "已啟用推播通知",
"settings.push.disableOk": "已關閉推播通知",
"settings.push.failed": "啟用推播失敗,請重試",
"notify.incoming.title": "收到檔案",
"notify.incoming.body": "{{sender}} 傳來 {{file}}",
"notify.transfer.doneTitle": "傳輸完成",
"notify.transfer.failedTitle": "傳輸失敗",
"settings.account.title": "帳號",
"settings.account.signOut": "登出",
"settings.desktop.title": "桌面",
"settings.desktop.clipboardSync": "剪貼簿自動同步",
"settings.desktop.clipboardSyncHint": "複製後即上傳雲端,並接收其他裝置的剪貼簿更新。",
"settings.desktop.launchAtLogin": "開機自動啟動",
"settings.desktop.launchAtLoginHint": "登入系統後自動在背景啟動 cdrop(常駐選單列)。",
"settings.desktop.downloadDir": "下載資料夾",
"settings.desktop.downloadDirHint": "接收到的檔案會儲存至此資料夾;留空則使用系統下載資料夾。",
"settings.desktop.downloadDirChoose": "變更…",
"settings.desktop.downloadDirReset": "還原預設",
"settings.desktop.downloadDirPicker": "選擇下載資料夾",
"settings.desktop.ttlNote": "基於安全考量,雲端剪貼簿內容會在 5 分鐘後自動清除。",
"settings.shortcut.title": "iOS 捷徑",
"settings.shortcut.intro": "為 iOS「捷徑」簽發長效、僅限剪貼簿、可隨時撤銷的專用權杖。即使權杖外洩也只能讀寫剪貼簿、無法觸及其他資料。",
"settings.shortcut.labelField": "備註名稱",
"settings.shortcut.labelPlaceholder": "例如:我的 iPhone",
"settings.shortcut.issue": "簽發",
"settings.shortcut.empty": "尚未簽發任何權杖。",
"settings.shortcut.loadError": "權杖清單載入失敗。",
"settings.shortcut.retry": "重試",
"settings.shortcut.revoke": "撤銷",
"settings.shortcut.revoking": "正在撤銷…",
"settings.shortcut.revokeConfirm": "撤銷後使用此權杖的捷徑將立即失效,確認繼續?",
"settings.shortcut.revoked": "已撤銷",
"settings.shortcut.revokedToast": "權杖已撤銷",
"settings.shortcut.expired": "已過期",
"settings.shortcut.expiresAt": "到期 {{date}}",
"settings.shortcut.lastUsed": "上次使用 {{time}}",
"settings.shortcut.neverUsed": "尚未使用",
"settings.shortcut.unavailable": "伺服器未啟用捷徑權杖。",
"settings.shortcut.tooMany": "使用中的權杖已達上限,請先撤銷舊權杖。",
"settings.shortcut.issueError": "操作失敗:{{message}}",
"settings.shortcut.issuedTitle": "權杖已簽發",
"settings.shortcut.onceWarning": "權杖僅顯示這一次,請立即複製保存;關閉後無法再次檢視。",
"settings.shortcut.tokenLabel": "權杖(Bearer",
"settings.shortcut.copy": "複製",
"settings.shortcut.copyFailed": "複製失敗,請手動選取複製",
"settings.shortcut.copied": "已複製",
"settings.shortcut.baseLabel": "伺服器位址",
"settings.shortcut.guideTitle": "在 iOS 上手動建立(手動同步)",
"settings.shortcut.guideIntro": "在 iPhone 的「捷徑」App 中新增兩個捷徑,首次執行時填入上方的伺服器位址與權杖:",
"settings.shortcut.guideDeviceName": "兩個捷徑都加標頭 X-Device-Name: 你的裝置名稱(僅 ASCII),讓它在裝置清單中統一顯示。",
"settings.shortcut.guidePullTitle": "拉取(雲端 → 本機剪貼簿)",
"settings.shortcut.guidePull1": "「取得 URL 內容」:網址設為 你的伺服器位址 加 /api/clipboard,方法 GET,標頭加 Authorization: Bearer 你的權杖。",
"settings.shortcut.guidePull2": "「取得字典值」:取鍵 content。",
"settings.shortcut.guidePull3": "內容非空時用「複製到剪貼簿」寫入,可再加「顯示通知」提示已拉取。",
"settings.shortcut.guidePushTitle": "上傳(本機剪貼簿 → 雲端)",
"settings.shortcut.guidePush1": "「取得剪貼簿」;若為空則停止。",
"settings.shortcut.guidePush2": "「取得 URL 內容」:網址同上,方法 PUT,標頭加 Authorization: Bearer 你的權杖 與 Content-Type: application/json,請求內容選 JSON{\"content\": 剪貼簿, \"content_type\": \"text/plain\"}。",
"settings.shortcut.guidePush3": "可加「顯示通知」提示已上傳。",
"settings.shortcut.guideToleranceNote": "網路不穩時單次執行可能失敗,重試即可;自動輪詢版會在背景靜默重試(下一階段提供)。",
"settings.shortcut.done": "完成",
"transfer.savedTo": "已儲存至 {{path}}",
"transfer.saveFailed": "儲存檔案失敗:{{error}}",
"transfer.action.cancel": "取消",
"transfer.action.delete": "刪除",
"transfer.action.relayNow": "立即中繼",
"transfer.action.details": "詳情",
"transfer.state.PENDING": "等待中",
"transfer.state.ACCEPTED": "已接受",
"transfer.state.P2P_ACTIVE": "傳輸中",
"transfer.state.RELAY_ACTIVE": "中繼中",
"transfer.state.DONE": "已完成",
"transfer.state.FAILED": "失敗",
"transfer.state.CANCELLED": "已取消",
"transfer.mode.p2p": "P2P",
"transfer.mode.relay": "中繼",
"transfer.phase.initializing": "正在初始化…",
"transfer.phase.waiting_accept": "等待對端接受…",
"transfer.phase.ice_gathering": "收集網路候選…",
"transfer.phase.ice_checking": "建立直連通道…",
"transfer.phase.ice_connected": "通道已連線",
"transfer.phase.dc_open": "通道已開啟",
"transfer.phase.transferring": "傳輸中",
"transfer.phase.completing": "收尾中…",
"transfer.phase.fallback_pending": "切換到中繼路徑…",
"transfer.phase.relay_uploading": "中繼上傳中",
"transfer.phase.relay_downloading": "中繼下載中",
"transfer.stalled": "似乎卡住了({{seconds}} 秒無進度)",
"transfer.bytesProgress": "{{sent}} / {{total}}",
"transfer.bytesRate": "{{rate}}/秒",
"transfer.debug.toggle": "偵錯資訊",
"transfer.debug.iceGathering": "候選收集",
"transfer.debug.iceConnection": "連線狀態",
"transfer.debug.candidatesLocal": "本端候選",
"transfer.debug.candidatesRemote": "對端候選",
"transfer.debug.selectedPair": "選中候選對",
"transfer.debug.noPair": "尚未選定候選對",
"transfer.debug.candidatesLine": "host {{host}} · mDNS {{mdns}} · srflx {{srflx}} · prflx {{prflx}} · relay {{relay}}",
"transfer.debug.dataChannel": "資料通道",
"transfer.debug.dcLine": "{{state}} · 待傳送 {{buffered}}",
"time.justNow": "剛剛",
"time.secondsAgo": "{{n}} 秒前",
"time.minutesAgo": "{{n}} 分鐘前",
"time.hoursAgo": "{{n}} 小時前",
"common.dismiss": "移除",
"common.cancel": "取消",
"common.confirm": "確認",
"errors.messageEmpty": "訊息為空",
"errors.messageOverflow": "訊息超過4 KB;請改用檔案傳輸",
"errors.noReceiver": "未選擇接收方",
"errors.devTokenMissing": "Dev模式:未在.env.local設定VITE_CDROP_DEV_TOKEN",
"errors.noAccessToken": "無存取權杖:使用者必須先登入",
"errors.clipboardUnavailable": "瀏覽器不支援剪貼簿API(需HTTPS與權限)",
"errors.clipboardOverflow": "內容過大,超過 {{max}} 位元組上限",
"errors.clipboardWriteFailed": "寫入本機剪貼簿失敗:{{message}}",
"ios.tab.transfer": "傳輸",
"ios.tab.devices": "裝置",
"ios.tab.settings": "設定",
"ios.login.generating": "正在產生 QR 碼…",
"ios.login.waiting": "等待掃碼核准",
"ios.login.guide": "用另一台已登入的裝置掃碼核准",
"ios.login.expired": "QR 碼已失效,請重試",
"ios.login.failed": "登入失敗,請重試",
"ios.login.refresh": "重新整理 QR 碼",
"ios.login.needFull": "此裝置需要完整權限,批准時請選擇「信任此裝置」",
"ios.send.pickDevice": "選擇接收裝置",
"ios.transfer.sending": "傳送中",
"ios.settings.engine": "引擎",
"ios.settings.status": "狀態",
"ios.settings.device": "裝置",
"ios.settings.clipboard": "剪貼簿",
"ios.settings.clipboardNote": "上傳把本機剪貼簿推到其他裝置;拉取寫入最新雲端剪貼簿。",
"ios.clipboard.upload": "上傳剪貼簿",
"ios.clipboard.pull": "拉取剪貼簿",
"ios.clipboard.uploading": "正在上傳…",
"ios.clipboard.pulling": "正在拉取…",
"ios.clipboard.uploaded": "已上傳到雲端剪貼簿",
"ios.clipboard.pulled": "已寫入本機剪貼簿",
"ios.clipboard.empty": "剪貼簿是空的",
"ios.clipboard.failed": "剪貼簿同步失敗",
"ios.devices.remove": "移除裝置",
"ios.devices.thisDevice": "本機",
"ios.devices.revoked": "已移除裝置",
"ios.devices.revokeFailed": "移除失敗",
"ios.devices.revokeStepUp": "需在網頁端重新驗證後才能移除",
"ios.engine.disconnected": "未連線",
"ios.engine.ready": "已就緒",
"ios.transfer.empty": "尚無傳輸",
"ios.transfer.incoming": "接收",
"ios.transfer.outgoing": "傳送",
"ios.send.noDevices": "沒有上線裝置,無法傳送",
"ios.devices.empty": "尚無裝置",
"ios.detail.title": "傳輸詳情",
"ios.detail.direction": "方向",
"ios.detail.peer": "對端",
"ios.detail.state": "狀態",
"ios.detail.phase": "階段",
"ios.detail.channel": "通道",
"ios.detail.size": "大小",
"ios.detail.progress": "進度",
"ios.detail.speed": "速度",
"ios.settings.account": "帳戶",
"ios.settings.user": "使用者",
"ios.settings.logout": "登出",
"ios.settings.deviceName": "裝置名稱",
"ios.settings.deviceNameNote": "改名將在下次登入後生效",
"ios.settings.deviceCount": "已知裝置",
"ios.settings.signaling": "信令連線",
"ios.settings.presenceEvents": "上線事件",
"ios.settings.reconnecting": "重新連線中",
"ios.settings.lastLog": "最近日誌",
"ios.tab.files": "檔案",
"ios.files.title": "收到的檔案",
"ios.files.empty": "尚未收到檔案",
"ios.files.share": "分享",
"ios.files.done": "完成",
"ios.detail.openFile": "開啟檔案"
}