cdrop — 跨 OS 剪贴板与文件传输服务

Commilitia Drop:自托管的跨设备剪贴板同步与点对点文件传输。

- 后端 Go(chi / SQLite WAL / SSE Hub / WebRTC signaling + 状态机 / Relay ring buffer),编译进单个 distroless 镜像(前端 go:embed)。
- 前端 React + TanStack Router + Zustand,自实现 SSE + WebRTC P2P,NAT 受阻时回退服务端中继;聚珍(Juzhen)CJK 综合排版。
- 桌面端 Wails v2(macOS / Windows),瘦客户端复用 web。
- 鉴权 OIDC PKCE(自建 Casdoor 等),refresh_token 信封加密存系统密钥库;iOS Shortcut 用 HS256 scoped token。

架构文档与变更记录见 docs 分支(PROJECT_BRIEF / FRONTEND_DESIGN / CHANGELOG)。

本次为公开发布初始提交:完整开发历史(含部署细节)留存于私有归档,公开仓库自此干净起步。
This commit is contained in:
2026-06-15 21:38:28 +08:00
commit f21fa5b5e8
239 changed files with 29010 additions and 0 deletions
+240
View File
@@ -0,0 +1,240 @@
// zh-CN 是 i18n 类型推导的基准词典:必须包含全部 key。其他 locale
// 缺失的 key 会自动 fallback 到此处。
export const zhCN = {
// ---- 应用 / 全局 -----------------------------------------------------
"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": "与服务器的连接已断开,正在自动重连,无需刷新页面。",
// ---- 顶部栏 / 用户菜单 ------------------------------------------------
"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.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 回调 -------------------------------------------------------
"oauth.failed": "登录失败",
"oauth.signingIn": "正在登录…",
"oauth.missingParams": "缺少code或state查询参数",
// ---- 设置页 ----------------------------------------------------------
"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.unregister.peerConfirm": "确认要移除设备“{{name}}”?",
"settings.peers.title": "其他设备",
"settings.peers.empty": "暂无其他设备。",
"settings.peers.remove": "移除",
"settings.peers.removing": "正在移除…",
"settings.error.delete": "操作失败:{{message}}",
"settings.online": "在线",
"settings.offline": "离线",
"settings.lastSeen": "上次活跃 {{time}}",
"settings.lastSeenNever": "尚未上线",
"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 分钟后自动清除。",
// ---- iOS 快捷指令令牌 ------------------------------------------------
"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": "中继",
// ---- 客户端 phase(实时遥测,区分等待中和卡住)----------------------
"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": "确认",
// ---- 错误(lib/* 中抛出,呈现到 Alert-------------------------------
"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}}",
} as const;