diff --git a/lang/zh/common.json b/lang/zh/common.json
new file mode 100644
index 00000000..c826cd1b
--- /dev/null
+++ b/lang/zh/common.json
@@ -0,0 +1,86 @@
+{
+ "actions": {
+ "cancel": "取消",
+ "close": "关闭",
+ "copy": "复制",
+ "delete": "删除",
+ "duplicate": "复制",
+ "hide": "隐藏",
+ "lock": "锁定",
+ "remove": "移除",
+ "rename": "重命名",
+ "reset": "重置",
+ "save": "保存",
+ "saveChanges": "保存更改",
+ "select": "选择",
+ "undo": "撤销",
+ "unlock": "解锁"
+ },
+ "labels": {
+ "account": "账户",
+ "actions": "操作",
+ "dashboard": "后台",
+ "description": "描述",
+ "expires": "过期时间",
+ "language": "语言",
+ "name": "名称",
+ "owner": "所有者",
+ "profile": "个人资料",
+ "project": "项目",
+ "role": "角色",
+ "status": "状态",
+ "theme": "主题",
+ "title": "标题",
+ "type": "类型",
+ "view": "视图"
+ },
+ "dataTable": {
+ "filterPlaceholder": "筛选{title}...",
+ "noResults": "未找到结果。",
+ "selectedCount": "已选择 {count} 项",
+ "clearFilters": "清除筛选"
+ },
+ "measurementUnit": {
+ "metric": "公制",
+ "imperial": "英制",
+ "presetAriaLabel": "单位预设:当前 {current}。切换到 {next}。",
+ "presetLabel": "单位预设:{current}",
+ "description": "切换显示距离和兼容输入。"
+ },
+ "themeToggle": {
+ "labels": {
+ "light": "浅色",
+ "dark": "深色",
+ "system": "跟随系统"
+ },
+ "ariaLabel": "主题:{theme}",
+ "tooltip": "当前:{current}。点击切换到 {next}。"
+ },
+ "versionTag": {
+ "tooltip": {
+ "openReleaseNotes": "在 GitHub 打开更新说明",
+ "openReleaseNotesForCommit": "提交 {sha} · 在 GitHub 打开更新说明"
+ }
+ },
+ "runtimeError": {
+ "badge": "运行时错误",
+ "title": "TrackDraw 遇到了一个问题。",
+ "description": "当前页面已停止渲染。你的本地项目数据通常仍保存在这个浏览器里,但在尝试恢复前请先不要关闭这个标签页。",
+ "tryAgain": "重试",
+ "openStudio": "打开 Studio",
+ "nextStep": "如果问题持续出现,请重新打开 Studio,并在继续进行较大改动前先导出一份 JSON 备份。"
+ },
+ "status": {
+ "active": "生效中",
+ "expired": "已过期",
+ "featured": "精选",
+ "hidden": "已隐藏",
+ "listed": "已展示",
+ "loading": "加载中",
+ "off": "关闭",
+ "on": "开启",
+ "pending": "等待中",
+ "published": "已发布",
+ "revoked": "已撤销"
+ }
+}
diff --git a/lang/zh/dialogs.json b/lang/zh/dialogs.json
new file mode 100644
index 00000000..11f9236e
--- /dev/null
+++ b/lang/zh/dialogs.json
@@ -0,0 +1,827 @@
+{
+ "account": {
+ "dialog": {
+ "eyebrow": "Studio",
+ "mobileSubtitle": "管理你的 TrackDraw 账户。"
+ },
+ "nav": {
+ "security": "安全",
+ "apiKeys": "API 密钥",
+ "danger": "危险操作"
+ },
+ "panels": {
+ "profile": {
+ "title": "个人资料",
+ "description": "管理在 TrackDraw 账户中显示的名称。"
+ },
+ "security": {
+ "title": "安全",
+ "description": "管理 TrackDraw 账户的登录方式、账户邮箱和通行密钥。"
+ },
+ "apiKeys": {
+ "title": "API 密钥",
+ "description": "为外部 TrackDraw 集成创建和撤销带过期时间的密钥。"
+ },
+ "danger": {
+ "title": "危险操作",
+ "description": "具有破坏性且不可逆的账户操作。"
+ }
+ },
+ "profile": {
+ "displayName": {
+ "label": "显示名称",
+ "placeholder": "你的名字"
+ },
+ "language": {
+ "description": "更改会立即生效"
+ },
+ "status": {
+ "saving": "正在保存..."
+ },
+ "fallback": {
+ "noAccountEmail": "TrackDraw 账户"
+ }
+ },
+ "security": {
+ "email": {
+ "address": "邮箱地址",
+ "newEmail": "新邮箱",
+ "placeholder": "name@example.com",
+ "changeDescription": "保存后,我们会先向你当前邮箱发送确认邮件。确认后,你再去验证新的邮箱地址。",
+ "change": "更换邮箱",
+ "saving": "正在保存..."
+ },
+ "passkeys": {
+ "title": "通行密钥",
+ "description": "在支持的设备上添加通行密钥,登录会更快。",
+ "add": "添加通行密钥",
+ "adding": "正在添加...",
+ "devAuthUnavailable": "在 `npm run dev` 下无法使用通行密钥。请用 `npm run preview` 在本地测试真实认证流程。",
+ "unsupportedBrowser": "当前浏览器这里没有暴露 WebAuthn,所以这台设备暂时不能设置通行密钥。",
+ "loading": "正在加载通行密钥...",
+ "addedDate": "添加于 {date}",
+ "status": {
+ "synced": "已同步"
+ },
+ "reauth": {
+ "title": "请重新登录后继续",
+ "description": "设置通行密钥需要近期登录状态。平时你可以保持登录,但这类安全操作需要再次确认。",
+ "action": "重新登录"
+ },
+ "empty": {
+ "title": "还没有通行密钥",
+ "description": "添加一个后,在你信任的设备上登录会更快。"
+ },
+ "aria": {
+ "rename": "重命名通行密钥",
+ "removing": "正在移除通行密钥",
+ "remove": "移除通行密钥"
+ },
+ "actions": {
+ "removing": "正在移除...",
+ "removeTooltip": "从账户移除",
+ "saveName": "保存名称"
+ },
+ "fields": {
+ "namePlaceholder": "通行密钥名称"
+ },
+ "fallback": {
+ "unnamed": "未命名通行密钥"
+ },
+ "rename": {
+ "description": "这只会改变它在你 TrackDraw 账户里的显示名称。"
+ }
+ }
+ },
+ "apiKeys": {
+ "form": {
+ "keyNameLabel": "密钥名称",
+ "expiryDays": "{days} 天",
+ "expiryYear": "1 年",
+ "namePlaceholder": "Overlay 集成",
+ "create": "创建"
+ },
+ "created": {
+ "title": "API 密钥已创建",
+ "description": "这个密钥只显示一次。请在关闭对话框前妥善保存。"
+ },
+ "active": {
+ "title": "有效的 API 密钥",
+ "descriptionPrefix": "供受信任集成使用的只读密钥。",
+ "docsLink": "API 文档",
+ "refreshAriaLabel": "刷新 API 密钥",
+ "loading": "正在加载 API 密钥..."
+ },
+ "empty": {
+ "title": "还没有 API 密钥",
+ "description": "当受信任的集成需要只读访问你账户内的项目时,再创建一个即可。"
+ },
+ "fallback": {
+ "unnamed": "未命名 API 密钥"
+ },
+ "meta": {
+ "expiresOn": "过期于 {date}",
+ "lastUsed": "最后使用于 {date}"
+ },
+ "revoke": {
+ "confirmTitle": "撤销这个 API 密钥?",
+ "tooltip": "撤销",
+ "ariaLabel": "撤销 {name}",
+ "warningExpiring": "撤销后这个密钥会立即失效。",
+ "warningNonExpiring": "使用这个密钥的集成会失去只读访问权限。",
+ "revoking": "正在撤销...",
+ "action": "撤销"
+ }
+ },
+ "danger": {
+ "description": "删除账户会移除账户项目、已发布链接、API 密钥以及其他账户关联数据。继续前,请先导出你想保留的账户数据。",
+ "confirm": {
+ "label": "输入 DELETE 确认",
+ "placeholder": "DELETE"
+ },
+ "actions": {
+ "deleting": "正在删除...",
+ "deleteAccount": "删除账户"
+ }
+ },
+ "shared": {
+ "loading": "正在加载账户...",
+ "signInTitle": "登录后管理账户",
+ "signInBody": "使用邮箱登录,即可访问云端账户设置和个人资料。"
+ },
+ "errors": {
+ "loadPasskeysFailed": "加载通行密钥失败。",
+ "loadApiKeysFailed": "加载 API 密钥失败。",
+ "profileNameRequired": "请输入希望 TrackDraw 显示的名称。",
+ "profileUpdateFailed": "更新个人资料失败。",
+ "deleteConfirmRequired": "请输入 DELETE 以确认删除账户。",
+ "deleteFailed": "删除账户失败。",
+ "emailRequired": "请输入你要用于 TrackDraw 的邮箱地址。",
+ "emailSame": "请输入不同的邮箱地址来完成更换。",
+ "emailChangeFailed": "更换邮箱失败。",
+ "passkeyAddFailed": "添加通行密钥失败。",
+ "passkeyNameRequired": "保存前请输入通行密钥名称。",
+ "passkeyRenameFailed": "重命名通行密钥失败。",
+ "passkeyRemoveFailed": "移除通行密钥失败。",
+ "apiKeyNameRequired": "请为这个 API 密钥命名。",
+ "apiKeyCreateFailed": "创建 API 密钥失败。",
+ "apiKeyRevokeFailed": "撤销 API 密钥失败。"
+ },
+ "success": {
+ "profileUpdated": "个人资料已更新",
+ "passkeyAdded": "通行密钥已添加。你可以在下面继续重命名或移除它。",
+ "passkeyRenamed": "通行密钥已重命名",
+ "apiKeyCreated": "API 密钥已创建",
+ "apiKeyCopied": "API 密钥已复制",
+ "apiKeyRevoked": "API 密钥已撤销"
+ }
+ },
+ "export": {
+ "dialog": {
+ "title": "导出",
+ "subtitle": "把当前赛道导出为视觉素材、项目交接文件或可用于模拟器的输出。"
+ },
+ "fields": {
+ "filename": {
+ "label": "文件名"
+ }
+ },
+ "theme": {
+ "label": "主题",
+ "dark": "深色",
+ "light": "浅色"
+ },
+ "routeNumbers": {
+ "label": "路径编号",
+ "hint": "用于 2D 导出",
+ "enable": "导出中显示路径编号",
+ "disable": "导出中隐藏路径编号",
+ "enableMobile": "显示路径编号",
+ "disableMobile": "隐藏路径编号"
+ },
+ "actions": {
+ "switchTo3dView": "切换到 3D 视图",
+ "openRequiredView": "打开所需视图"
+ },
+ "aria": {
+ "exportFormat": "导出 {label}"
+ },
+ "messages": {
+ "view3dUnavailable": "3D 视图不可用——请先打开 3D 标签页",
+ "canvasNotReady": "画布尚未就绪",
+ "exported": "已导出"
+ },
+ "readiness": {
+ "open3d": "导出这个渲染前请先打开 3D 视图。",
+ "canvasNotReady": "2D 画布仍在加载,请稍后再试。",
+ "emptyRacePack": "导出布场清单前,请先在赛道里添加至少一个项目。",
+ "missingRaceLine": "导出 Cinematic FPV 视频前,请先画出路径。",
+ "emptyVelocidrone": "导出 Velocidrone 文件前,请先在赛道里添加至少一个项目。",
+ "experimentalSimulator": "部分 TrackDraw 物件导入 Velocidrone 后的效果可能会不同。"
+ },
+ "webm": {
+ "renderingBackground": "Cinematic FPV 正在后台渲染…",
+ "renderingProgress": "正在渲染 cinematic FPV… {status}",
+ "ready": "Cinematic FPV 导出已完成",
+ "progressLabel": "正在渲染模拟飞行… 视频长度 {duration}"
+ },
+ "categories": {
+ "visuals": {
+ "title": "视觉内容"
+ },
+ "raceDay": {
+ "title": "比赛日"
+ },
+ "projectData": {
+ "title": "项目数据"
+ },
+ "motion": {
+ "title": "视频"
+ },
+ "simulatorLab": {
+ "title": "实验性"
+ }
+ },
+ "formats": {
+ "image": {
+ "label": "图片",
+ "descriptionFull": "高分辨率 2D 平面图,适合打印、讲解页、聊天或快速审阅。",
+ "descriptionShort": "高分辨率 2D 平面图,适合打印、聊天或审阅。"
+ },
+ "vector": {
+ "label": "矢量图",
+ "descriptionFull": "可缩放图稿,适合在 Illustrator、Figma 或 Inkscape 中继续编辑。",
+ "descriptionShort": "可缩放图稿,适合在 Illustrator、Figma 或 Inkscape 中继续编辑。"
+ },
+ "render3d": {
+ "label": "3D 渲染",
+ "descriptionFull": "截取当前 3D 相机视角,用于视觉审阅。",
+ "descriptionShort": "当前 3D 相机截图,用于视觉审阅。"
+ },
+ "projectFile": {
+ "label": "项目文件",
+ "descriptionFull": "可编辑备份,方便在 TrackDraw 中再次打开、分享或归档。",
+ "descriptionShort": "可编辑备份,方便在 TrackDraw 中再次打开或归档。"
+ },
+ "racePack": {
+ "label": "布场清单",
+ "descriptionFull": "比赛日布场交接 PDF,包含地图、物料、顺序和二维码。",
+ "descriptionShort": "包含地图、物料、顺序和二维码的布场 PDF。"
+ },
+ "cinematicFpv": {
+ "label": "Cinematic FPV",
+ "descriptionFull": "单圈线路视频,适合检查 flow 或直接分享整圈。",
+ "descriptionShort": "单圈线路视频,用于检查 flow。"
+ },
+ "velocidrone": {
+ "label": "Velocidrone",
+ "descriptionFull": "实验性的 Velocidrone 赛道文件,可作为模拟器测试起点;导入后请自行复核布局。",
+ "descriptionShort": "实验性模拟器文件;导入后请自行复核。"
+ }
+ },
+ "errors": {
+ "exportFailed": "导出失败",
+ "exportFailedDescription": "{message} 请重试、切换导出格式,或导出项目 JSON 作为手动备份。"
+ }
+ },
+ "import": {
+ "dialogTitle": "打开项目",
+ "dialogSubtitle": "从 JSON 文件导入一个 TrackDraw 项目。",
+ "dropFile": "把文件拖到这里",
+ "browseFile": "或点击浏览",
+ "errorInvalidJson": "无效的 JSON 文件。",
+ "errorInvalidJsonHelp": "无效的 JSON 文件。请选择一份完整的 TrackDraw `.json` 导出文件后重试。",
+ "errorInvalidProject": "这看起来不像是一个 TrackDraw 项目。",
+ "errorInvalidProjectHelp": "这看起来不像是一个 TrackDraw 项目。请选择一份从 TrackDraw 导出的 JSON 项目文件。",
+ "errorUnsupportedFile": "目前只支持 TrackDraw 的 JSON 项目文件。",
+ "errorUnsupportedFileHelp": "目前只支持 TrackDraw 的 JSON 项目文件。请选择一份从 TrackDraw 导出的 `.json` 备份。",
+ "errorReadFailed": "TrackDraw 无法读取这个文件。",
+ "errorReadFailedHelp": "TrackDraw 无法读取这个文件。请重试,或换一份备份文件。",
+ "toastImportFailed": "导入失败",
+ "toastUnsupportedFile": "请选择一个从 TrackDraw 导出的 .json 文件。",
+ "toastInvalidJson": "TrackDraw 无法读取这个 JSON 文件。请检查文件后重试。",
+ "toastInvalidProject": "请选择一个从 TrackDraw 导出的 JSON 项目文件。",
+ "toastReadFailed": "TrackDraw 无法读取所选文件。请重试或换一个备份文件。",
+ "untitledProject": "未命名项目",
+ "objectCount": "{count} 个对象",
+ "replaceWarning": "当前项目会被替换。如果你想保留一份不依赖本地自动保存的手动恢复点,请先导出 JSON 备份。",
+ "replaceProject": "替换项目",
+ "backupFirst": "先备份当前项目"
+ },
+ "share": {
+ "dialog": {
+ "eyebrow": "Studio",
+ "existingEyebrow": "共享赛道",
+ "title": "分享",
+ "mobileSubtitle": "分享这条赛道的只读审阅链接"
+ },
+ "nav": {
+ "shareLink": "共享链接",
+ "share": "分享",
+ "embed": "嵌入",
+ "gallery": "图库"
+ },
+ "currentLink": {
+ "title": "当前共享链接",
+ "readOnlyReviewOn": "{hostname} 上的只读 {view} 审阅"
+ },
+ "content": {
+ "share": {
+ "title": "共享链接",
+ "description": {
+ "existing": "复制或再次发送当前已发布的只读链接;如果你想自己继续编辑,请打开 Studio 创建可编辑副本。",
+ "account": "为这个账户项目创建或更新长期有效的只读链接。",
+ "separate": "为这个可编辑副本或本地赛道创建一条独立的长期只读链接。",
+ "anonymous": "创建一条临时只读快照链接,并控制它的有效时间。"
+ }
+ },
+ "gallery": {
+ "title": "图库可见性",
+ "description": "管理这条已发布链接在公开图库中的展示方式。"
+ },
+ "embed": {
+ "title": "嵌入",
+ "description": "为已通过账户发布的赛道复制 iframe 嵌入代码,链接会一直有效,直到你撤销为止。"
+ },
+ "actions": {
+ "descriptionExisting": "在其他应用中使用当前已发布链接,或在新标签页里重新打开它。",
+ "description": "打开、再次发送、撤销或导出这个分享,不会改动赛道本身。"
+ }
+ },
+ "link": {
+ "expiry": {
+ "days": "{days} 天",
+ "label": "链接过期时间",
+ "description": "临时快照会一直可用,直到过期。"
+ },
+ "titles": {
+ "savedProject": "已保存项目链接",
+ "newShare": "新的共享链接",
+ "separatePublished": "独立发布链接",
+ "temporary": "临时链接",
+ "noSavedProject": "还没有已保存项目链接",
+ "noSeparate": "还没有独立链接",
+ "noTemporary": "还没有临时链接"
+ },
+ "descriptions": {
+ "savedProject": "这个已保存的账户项目使用同一条长期只读链接。更新它时,URL 不变,但内容会切到最新设计。",
+ "newShare": "这个可编辑副本会拥有自己的长期只读链接,不会覆盖其他项目已发布的链接。",
+ "createSavedProject": "为这个账户项目创建一条长期 URL,后续更新可以继续沿用同一条链接。",
+ "createSeparate": "为当前副本创建一条长期 URL,不会改动任何之前的分享。",
+ "createTemporary": "选择过期时间,然后创建一个只读快照。"
+ },
+ "lifetime": {
+ "published": "{hostname} 上的已发布链接,会一直有效直到撤销",
+ "temporary": "{hostname} 上的只读快照,将在 {days} 天后过期"
+ },
+ "warnings": {
+ "outdatedDesign": "这条链接已经不是最新设计内容。",
+ "outdatedExpiry": "这条链接已经不是最新的过期设置。"
+ }
+ },
+ "embed": {
+ "iframeTitle": "TrackDraw 赛道嵌入",
+ "code": {
+ "title": "嵌入代码",
+ "description": "通过账户发布的赛道可以嵌入到俱乐部或赛事网站中。",
+ "label": "Iframe 代码",
+ "selectedViewDescription": "会包含你选定的初始视图。",
+ "copy": "复制代码"
+ },
+ "state": {
+ "loading": "正在加载嵌入状态…"
+ },
+ "publish": {
+ "title": "先发布再嵌入",
+ "description": "嵌入使用的是账户发布的长期链接,所以请先创建发布链接,再复制 iframe 代码。",
+ "action": "创建发布链接"
+ },
+ "temporary": {
+ "title": "临时链接不能嵌入",
+ "description": "请先登录,并通过你的账户发布这条赛道,才能创建长期有效的嵌入。"
+ },
+ "refresh": {
+ "message": "请先更新已发布链接,这样嵌入内容才会切到最新设计。",
+ "action": "更新链接"
+ },
+ "initialView": {
+ "label": "初始视图",
+ "options": {
+ "layout2d": {
+ "label": "2D 布局",
+ "description": "以平面场地图打开"
+ },
+ "preview3d": {
+ "label": "3D 预览",
+ "description": "以环绕审阅视角打开"
+ }
+ }
+ }
+ },
+ "gallery": {
+ "status": {
+ "loading": "正在加载当前状态",
+ "featured": "已在图库置顶推荐",
+ "listed": "已展示在图库中",
+ "hiddenByModeration": "因审核被隐藏",
+ "linkOnly": "仅链接可见",
+ "noPublishedLink": "尚未发布链接",
+ "checking": "检查中",
+ "noLink": "无链接",
+ "expiresInDays": "{days} 天后过期",
+ "noExpiry": "不过期"
+ },
+ "descriptions": {
+ "checking": "正在检查当前图库状态。",
+ "featured": "已显示在公开图库中,并置顶推荐。",
+ "listed": "已显示在公开图库中。",
+ "hidden": "已因审核被隐藏。只要未撤销,直链仍然可以使用。",
+ "linkOnly": "只保留分享链接,不会出现在公开图库中。",
+ "noLink": "请先创建共享链接。"
+ },
+ "labels": {
+ "visibility": "可见性",
+ "shareLink": "共享链接",
+ "author": "作者"
+ },
+ "publishFirst": {
+ "title": "先发布再上架",
+ "description": "图库使用的是当前已发布快照,所以想把这条赛道展示到图库里,必须先有一条共享链接。",
+ "action": "先创建共享链接"
+ },
+ "moderation": {
+ "title": "审核锁定",
+ "description": "这条赛道已因审核被隐藏,不会出现在图库中。但只要未撤销,直链仍可能可用。"
+ },
+ "details": {
+ "title": "图库信息",
+ "description": "更新这条赛道在公开图库卡片上的标题和描述。",
+ "edit": "编辑信息"
+ },
+ "list": {
+ "title": "添加到图库",
+ "description": "填写标题、描述并生成预览图后,就能公开展示给其他人浏览。",
+ "add": "添加到图库",
+ "readyTitle": "已准备好上架",
+ "readyDescription": "把这个已发布快照加到公开图库中,并给它设置单独的标题、描述和预览图。"
+ },
+ "fields": {
+ "titlePlaceholder": "赛道标题",
+ "descriptionPlaceholder": "用于图库卡片的简短描述"
+ },
+ "validation": {
+ "titleRequired": "保存前请先填写标题。",
+ "descriptionLength": "描述请控制在 {min}-{max} 个字符之间。",
+ "displayNameRequired": "请先设置账户显示名。",
+ "noMetadataChanges": "请修改标题或描述后再保存。",
+ "refreshShareFirst": "请先更新共享链接,这样图库使用的才是最新快照。"
+ },
+ "preview": {
+ "generating": "正在生成预览图...",
+ "ready": "预览图已就绪"
+ },
+ "card": {
+ "title": "图库卡片",
+ "description": "查看或更新这条赛道对外展示的标题和描述。",
+ "untitled": "未命名",
+ "noDescription": "还没有设置图库描述。",
+ "viewGallery": "查看图库"
+ },
+ "remove": {
+ "confirm": "要从公开图库中移除吗?共享链接本身仍会继续有效,直到你主动撤销为止。",
+ "action": "从图库移除"
+ },
+ "fallback": {
+ "noDisplayName": "未设置显示名"
+ }
+ },
+ "actions": {
+ "primary": {
+ "updateLink": "更新链接",
+ "copyLink": "复制链接",
+ "createLink": "创建链接",
+ "createNewLink": "创建新链接"
+ },
+ "copyLink": "复制链接",
+ "revoke": "撤销",
+ "share": {
+ "existingDescription": "这个页面本身已经是已发布的只读审阅链接。你可以直接分享它,或打开 Studio 创建可编辑副本。",
+ "withPathDescription": "共享链接会以只读审阅页打开。这个链接会直接进入 {view} 审阅,并且路径已可用于模拟飞行。",
+ "withoutPathDescription": "共享链接会以只读审阅页打开。如果你希望审阅者使用模拟飞行和高度审阅,请先补一条路径。"
+ },
+ "nativeShare": {
+ "title": "通过以下方式分享...",
+ "description": "发送到应用或联系人"
+ },
+ "openInTab": {
+ "label": "在标签页中打开",
+ "reopenCurrent": "在新标签页里重新打开当前共享链接",
+ "previewLink": "在新窗口里预览共享链接"
+ },
+ "exportJson": {
+ "label": "改为导出 JSON",
+ "description": "用于在 TrackDraw 里再次打开的可编辑备份"
+ }
+ },
+ "errors": {
+ "createFailed": "创建共享链接失败",
+ "createFailedDescription": "TrackDraw 无法发布当前快照。请检查网络后重试;如果现在就需要交接,请先导出 JSON。",
+ "copyFailed": "复制链接失败",
+ "copyFailedDescription": "链接可能仍显示在输入框中。如果浏览器阻止剪贴板访问,请手动选中并复制。",
+ "nativeShareFailed": "无法打开分享面板",
+ "nativeShareFailedDescription": "当前浏览器无法打开系统分享面板。请改用复制链接。",
+ "revokeFailed": "撤销链接失败",
+ "revokeFailedDescription": "链接可能仍然处于可用状态。请在这个对话框里重试,或去 Project Manager 的共享标签页操作。",
+ "embedCopyFailed": "复制嵌入代码失败",
+ "embedCopyFailedDescription": "嵌入代码仍显示在文本框中。如果浏览器阻止剪贴板访问,请手动选中并复制。",
+ "galleryListFailed": "添加到图库失败",
+ "galleryListFailedDescription": "这条共享链接未被加入公开图库。请检查标题、描述、预览图和网络连接后再试。",
+ "galleryRemoveFailed": "无法从图库移除",
+ "galleryRemoveFailedDescription": "图库条目可能仍然可见。请重试;如果必须立刻停止访问,也可以直接撤销共享链接。",
+ "galleryUpdateFailed": "更新图库信息失败",
+ "galleryUpdateFailedDescription": "已发布的图库卡片没有被更新。请检查标题和描述后重试。",
+ "unableToReadLink": "无法读取当前共享链接",
+ "unableToReadLinkDescription": "请刷新这个分享页面;如果这是通过账户发布的共享,也可以从 Project Manager 中打开该链接。"
+ },
+ "success": {
+ "linkUpdated": "链接已更新",
+ "linkCreated": "链接已创建",
+ "linkCopied": "链接已复制",
+ "embedCopied": "嵌入代码已复制",
+ "galleryListed": "赛道已加入图库",
+ "galleryUpdated": "图库信息已更新",
+ "linkRevoked": "链接已撤销"
+ }
+ },
+ "projectManager": {
+ "dialog": {
+ "eyebrow": "Studio",
+ "title": "项目",
+ "mobileSubtitle": "打开、重命名、同步或恢复。"
+ },
+ "nav": {
+ "thisDevice": "本机",
+ "shares": "共享",
+ "snapshots": "快照"
+ },
+ "panels": {
+ "device": {
+ "label": "保存在本机",
+ "description": "保存在当前设备上的本地副本。已同步项目也可能同时出现在账户里。"
+ },
+ "account": {
+ "label": "账户项目",
+ "description": "登录设备之间可访问的账户副本。在这里打开它,即可刷新当前浏览器内容。"
+ },
+ "snapshots": {
+ "label": "快照",
+ "description": "当前项目过去保存的状态。恢复任意一个快照即可回退改动。"
+ },
+ "shares": {
+ "label": "已发布共享",
+ "description": "你从账户发布的有效共享链接。可在这里复制、打开或撤销。"
+ }
+ },
+ "device": {
+ "sync": {
+ "resolveConflict": "处理同步冲突",
+ "retry": "重试同步",
+ "updateAccountCopy": "更新账户副本",
+ "syncToAccount": "同步到账户",
+ "conflictDescription": "这个项目在另一台设备上已被改动。继续前请先检查版本。",
+ "failedDescription": "上次同步失败了。请尝试重新推送最新本地版本。",
+ "pendingDescription": "把最新本地改动推送到你的账户。",
+ "updateDescription": "用当前设备上的版本覆盖账户里的同步副本。",
+ "description": "让这个项目能在你已登录的其他设备上继续使用。",
+ "localChangesWaiting": "有本地改动等待同步",
+ "latestLocalCopySaved": "最新本地副本保存于 {relativeTime}",
+ "changedOnAnotherDevice": "这个项目在另一台设备上被改过",
+ "couldNotSyncProject": "无法同步这个项目",
+ "resolveVersionConflict": "处理版本冲突"
+ },
+ "status": {
+ "syncing": "同步中",
+ "reviewNeeded": "需复核",
+ "failed": "同步失败",
+ "synced": "已同步",
+ "localOnly": "仅本机"
+ },
+ "meta": {
+ "localCopyWaitingToSync": "{itemLabel} · 本地副本等待同步",
+ "localCopySynced": "{itemLabel} · 本地副本已于 {relativeTime} 同步",
+ "localCopy": "{itemLabel} · 本地副本",
+ "onlyOnThisDevice": "{itemLabel} · 仅本机 · {relativeTime}",
+ "onThisDeviceSubtitle": "本机上的 {itemLabel}"
+ },
+ "aria": {
+ "resolveVersionConflictFor": "处理 {title} 的版本冲突",
+ "retrySyncFor": "重试 {title} 的同步",
+ "updateAccountCopyFor": "更新 {title} 的账户副本",
+ "syncProjectToAccount": "把 {title} 同步到账户",
+ "exportProjectAsJson": "把 {title} 导出为 JSON",
+ "manageProject": "管理 {title}",
+ "renameProject": "重命名 {title}",
+ "deleteProject": "删除 {title}"
+ },
+ "actions": {
+ "confirmRenameTitle": "确认重命名",
+ "more": "更多操作",
+ "newProject": "新建项目",
+ "newProjectDescription": "开始一条全新赛道,或使用起始布局。",
+ "cleanUpLocal": "清理本地项目",
+ "cleanUpLocalDescription": "只移除当前设备上的旧副本。",
+ "renameDescription": "给这个项目起个更清晰的名字。",
+ "openBadge": "已打开"
+ },
+ "export": {
+ "json": "导出 JSON",
+ "jsonDescription": "把这个项目下载成可复用的 TrackDraw 文件。"
+ },
+ "delete": {
+ "localProject": "删除本地项目?",
+ "localProjectDescription": "会移除这个本地副本。",
+ "permanently": "永久删除?",
+ "permanentlyDescription": "这只会把本地项目从当前浏览器中移除。",
+ "localProjectActionDescription": "把这个本地项目从当前设备删除。",
+ "removeLocalProjectsTitle": "删除 {count} 个本地项目?",
+ "accountSyncedCopiesStayAvailable": "已同步到账户的副本仍然会保留在你的账户中。"
+ },
+ "groups": {
+ "localCopiesLinkedToAccount": "已关联到账户的本地副本",
+ "onlyOnThisDevice": "仅保存在本机"
+ },
+ "empty": {
+ "noProjects": "还没有已保存项目",
+ "noProjectsDescription": "你保存的项目会显示在这里。"
+ },
+ "fallback": {
+ "project": "项目",
+ "untitled": "未命名"
+ }
+ },
+ "account": {
+ "status": {
+ "loadingProjects": "正在加载账户项目...",
+ "syncing": "同步中",
+ "reviewNeeded": "需复核",
+ "failed": "同步失败",
+ "synced": "已同步"
+ },
+ "empty": {
+ "noProjects": "还没有账户项目",
+ "description": "先同步一个项目,才能在多设备间继续使用。"
+ },
+ "messages": {
+ "loadFailed": "无法加载账户项目",
+ "loadFailedDescription": "当前设备上的本地项目仍然可用。请检查网络连接或重新登录,然后再打开 Project Manager。",
+ "conflictWarning": "你离线期间,这个项目在另一台设备上被改动过",
+ "syncError": "无法同步最新改动",
+ "localCopySavedAfterFailure": "最新本地副本保存于 {relativeTime}",
+ "resolveConflictFirst": "请先处理版本冲突"
+ },
+ "actions": {
+ "retrySync": "重试同步",
+ "syncPendingChanges": "同步待处理改动"
+ },
+ "fallback": {
+ "untitled": "未命名"
+ }
+ },
+ "restore": {
+ "empty": {
+ "noSnapshots": "还没有快照",
+ "description": "打开或替换项目时,会自动创建快照。",
+ "desktopDescription": "按 ⌘S / Ctrl S,或使用顶部的保存按钮。"
+ },
+ "actions": {
+ "restoreTitle": "恢复这个快照",
+ "deleteTitle": "删除快照",
+ "restore": "恢复"
+ },
+ "aria": {
+ "restoreSnapshot": "恢复 {title}",
+ "deleteSnapshot": "删除 {title}"
+ },
+ "confirm": {
+ "restoreSnapshot": "恢复这个快照?"
+ },
+ "status": {
+ "active": "当前"
+ },
+ "fallback": {
+ "snapshot": "快照",
+ "untitled": "未命名"
+ }
+ },
+ "shares": {
+ "status": {
+ "loadingPublishedShares": "正在加载已发布共享...",
+ "published": "已发布",
+ "expired": "已过期",
+ "expiresInDays": "剩余 {days} 天"
+ },
+ "messages": {
+ "readOnlyReviewLinks": "共享内容都是只读审阅链接。如果对方需要可编辑备份,请导出 JSON。"
+ },
+ "empty": {
+ "noShares": "还没有有效共享",
+ "description": "账户发布链接会一直保留到你撤销为止。匿名临时链接会自动过期,也不会在这里管理。"
+ },
+ "lifetime": {
+ "published": "只读发布链接,会一直有效直到撤销",
+ "temporary": "只读临时链接,{expiresIn}"
+ },
+ "actions": {
+ "copyLink": "复制链接",
+ "openTab": "在新标签页打开",
+ "revokeLink": "撤销链接"
+ },
+ "aria": {
+ "copyLink": "复制共享链接",
+ "openTab": "打开共享链接",
+ "revokeLink": "撤销共享链接"
+ },
+ "confirm": {
+ "revokeLink": "撤销这条链接?"
+ }
+ },
+ "projectIdCopy": {
+ "success": "项目 ID 已复制",
+ "failed": "当前浏览器无法复制项目 ID。",
+ "label": "API 项目 ID",
+ "ariaLabel": "复制 API 项目 ID",
+ "action": "复制"
+ }
+ },
+ "completeProfile": {
+ "title": "补充你的名字",
+ "subtitle": "给账户设置一个清晰的显示名称,用于云端项目和分享。",
+ "displayNameLabel": "显示名称",
+ "namePlaceholder": "你的名字",
+ "description": "这是 TrackDraw 在云端项目和共享布局中显示给别人的名字。",
+ "descriptionWithEmail": "这是 TrackDraw 在 {email} 账户下的云端项目和共享布局中显示给别人的名字。",
+ "skip": "跳过",
+ "save": "保存名称",
+ "saving": "正在保存…",
+ "nameRequired": "请输入希望 TrackDraw 显示的名称。",
+ "updateFailed": "更新账户名称失败。",
+ "success": "个人资料已更新"
+ },
+ "newProject": {
+ "title": "新建项目",
+ "closeAriaLabel": "关闭",
+ "subtitle": "从空白画布开始,或选择一个起始布局继续搭建。",
+ "desktopSubtitle": "从空白画布开始,或选择一个起始布局继续搭建。",
+ "studioLabel": "Studio",
+ "blankCanvasLabel": "空白画布",
+ "starterLayoutsLabel": "起始布局",
+ "saveWarningTitle": "请先保存当前赛道",
+ "saveWarningBody": "开始新项目会替换当前画布内容。如果你还想保留它,请先导出或备份。",
+ "startFreshTitle": "从空白画布开始",
+ "startFreshBody": "清空当前场地,从零搭建你的赛道。",
+ "exportFirstTitle": "先备份当前项目",
+ "exportFirstBody": "开始新项目之前,先把当前赛道保存一份副本。"
+ },
+ "keyboardShortcuts": {
+ "dialogTitle": "快捷键",
+ "dialogSubtitle": "Studio 中可用的键盘与画布快捷操作。",
+ "sectionTools": "工具",
+ "sectionSelection": "选择",
+ "sectionPathEditing": "路径编辑",
+ "sectionCanvas": "画布",
+ "shortcuts": {
+ "duplicateItems": "复制当前选中项目",
+ "copyItems": "复制当前选中项目",
+ "pasteItems": "粘贴已复制项目",
+ "rotateLeft": "向左旋转选中项目",
+ "rotateRight": "向右旋转选中项目",
+ "mouseRotateSnap": "鼠标旋转吸附",
+ "mouseRotateFine": "鼠标精细旋转",
+ "keyRotateFine": "按键精细旋转",
+ "deleteItems": "删除当前选中项目",
+ "nudgeItems": "微移当前选中项目",
+ "fineNudge": "精细微移",
+ "finishPath": "完成路径",
+ "removeLastPoint": "移除最后一个草稿点",
+ "deletePathPoint": "删除选中的路径点",
+ "cancelDraft": "取消当前草稿",
+ "fitView": "视图适应场地",
+ "clearSelection": "清空选择",
+ "panView": "平移视图",
+ "bypassSnap": "临时绕过吸附",
+ "zoom": "缩放",
+ "saveSnapshot": "保存快照"
+ }
+ },
+ "versionConflict": {
+ "title": "选择要保留的项目版本",
+ "subtitle": "选择你希望在当前设备上继续使用的版本。",
+ "thisProject": "这个项目",
+ "body": "在你离线期间,这个项目在另一台设备上被改动过。请选择当前设备要继续使用哪一个版本。",
+ "localVersion": "当前设备版本",
+ "cloudVersion": "账户版本",
+ "lastChanged": "最后更改于 {date}",
+ "keepLocal": "保留当前设备版本",
+ "useCloud": "使用账户版本"
+ }
+}
diff --git a/lang/zh/editor.json b/lang/zh/editor.json
new file mode 100644
index 00000000..fae7b7c5
--- /dev/null
+++ b/lang/zh/editor.json
@@ -0,0 +1,465 @@
+{
+ "toolbar": {
+ "homepageLabel": "返回首页",
+ "projects": "项目",
+ "projectsTooltip": "管理项目",
+ "import": "导入",
+ "importTooltip": "导入 JSON",
+ "export": "导出",
+ "exportTooltip": "导出赛道"
+ },
+ "contextMenu": {
+ "quickActions": "快捷操作",
+ "selectedCount": "已选择 {count} 项",
+ "segmentSelected": "已选择线段",
+ "waypointSelected": "已选择路径点",
+ "addWaypoint": "添加路径点",
+ "deleteWaypoint": "删除路径点",
+ "arrange": "排列",
+ "bringForward": "上移一层",
+ "sendBackward": "下移一层",
+ "rotate": "旋转",
+ "rotateLeft": "向左旋转",
+ "rotateRight": "向右旋转"
+ },
+ "canvasOverlay": {
+ "pathEditing": "路径编辑",
+ "addPoints": "添加点",
+ "finish": "完成",
+ "cancel": "取消",
+ "connectEnds": "点击连接首尾",
+ "grid": "网格 {grid}",
+ "snapOn": "吸附开:网格、物件、路线",
+ "snapOff": "吸附关:自由摆放",
+ "midClick": "中键",
+ "rightClick": "右键",
+ "pan": "平移",
+ "menu": "菜单",
+ "bypass": "临时绕过",
+ "fitToWindow": "适应窗口",
+ "viewerPanZoom": "拖动画布平移,滚轮缩放"
+ },
+ "preview3dOverlay": {
+ "pause": "暂停",
+ "play": "播放",
+ "speed": "速度",
+ "toggleFpv": "切换 FPV 相机行为",
+ "on": "开",
+ "off": "关",
+ "exit": "退出",
+ "flyThrough": "模拟飞行",
+ "dragWaypointMobile": "上下拖动路径点控制点来调整高度",
+ "dragWaypointDesktop": "上下拖动路径点控制柄来调整高度",
+ "dragDiveGateMobile": "拖动圆环旋转 · 拖动橙色控制点调倾角",
+ "dragDiveGateDesktop": "拖动圆环旋转 · 拖动橙色控制点调倾角",
+ "dragRing": "拖动圆环旋转",
+ "orbitHint": "拖拽环绕查看 · 滚轮缩放 · 中键拖动平移",
+ "drawRoute": "先在 2D 里画出路径,才能启用模拟飞行"
+ },
+ "statusBar": {
+ "snapActive": "吸附",
+ "snapOn": "吸附开",
+ "snapOff": "吸附关",
+ "snapIsOn": "吸附已开启",
+ "snapIsOff": "吸附已关闭",
+ "snapOnDescription": "拖拽和摆放会锁到附近网格、物件和路径点。放置或拖动时按住 Alt 可临时绕过一次。",
+ "snapOffDescription": "摆放和拖动保持自由状态,直到你重新开启吸附。",
+ "selected": "已选 {count} 项",
+ "grouped": "已分组",
+ "groupNamed": "分组:{name}",
+ "groups": "{count} 个分组",
+ "devOn": "开发模式 开",
+ "dev": "开发模式"
+ },
+ "accountMenu": {
+ "checkingAuth": "正在检查登录状态…",
+ "signIn": "登录",
+ "signedIn": "已登录",
+ "noDisplayName": "未设置显示名",
+ "trackdrawAccount": "TrackDraw 账户",
+ "profile": "个人资料",
+ "dashboard": "后台",
+ "signOut": "退出登录",
+ "signingOut": "正在退出…"
+ },
+ "common": {
+ "canvas2d": "2D 画布",
+ "preview3d": "3D 预览"
+ },
+ "elementPlacementControl": {
+ "official": "官方",
+ "typeLabel": "{tool} 类型"
+ },
+ "header": {
+ "untitledTitle": "未命名",
+ "statusFailed": "失败",
+ "statusPending": "等待中",
+ "statusSyncing": "同步中",
+ "statusSynced": "已同步",
+ "expandSidebar": "展开侧边栏",
+ "collapseSidebar": "收起侧边栏",
+ "sharedReviewBadge": "共享审阅",
+ "makeEditableCopy": "创建可编辑副本",
+ "undo": "撤销",
+ "redo": "重做",
+ "saveSnapshot": "保存快照",
+ "keyboardShortcuts": "快捷键",
+ "retrySync": "重试账户同步 ({status})",
+ "share": "分享",
+ "export": "导出",
+ "openAppMenu": "打开应用菜单",
+ "hideObstacleNumbers": "隐藏障碍编号",
+ "showObstacleNumbers": "显示障碍编号"
+ },
+ "sharedHeader": {
+ "embedLabel": "嵌入",
+ "sharedPreviewLabel": "共享预览"
+ },
+ "mobileAppMenu": {
+ "openAppMenu": "打开应用菜单",
+ "title": "TrackDraw",
+ "subtitle": "项目、分享、账户和应用设置",
+ "signIn": "登录",
+ "yourAccount": "你的 TrackDraw 账户",
+ "trackdrawAccount": "TrackDraw 账户",
+ "projectsSection": "项目",
+ "projectsLabel": "项目",
+ "projectsDescription": "打开并管理已保存项目",
+ "accountSection": "账户",
+ "profileLabel": "个人资料",
+ "profileDescription": "管理个人资料、安全设置和 API 密钥",
+ "dashboardLabel": "后台",
+ "dashboardDescription": "打开审核和管理工具",
+ "shareSection": "分享与传输",
+ "shareLabel": "分享",
+ "shareDescription": "发布这条赛道的只读链接",
+ "importLabel": "导入",
+ "importDescription": "导入 JSON 项目文件",
+ "exportLabel": "导出",
+ "exportDescription": "下载 PNG、PDF、SVG 或 JSON",
+ "themeLabel": "主题",
+ "themeDescription": "切换浅色、深色或跟随系统",
+ "signOut": "退出登录",
+ "signingOut": "正在退出…"
+ },
+ "layoutPresetPicker": {
+ "itemsCount": "{count} 个项目",
+ "renamePresetTooltip": "重命名预设",
+ "deletePresetTooltip": "删除预设",
+ "signInTitle": "登录后使用预设",
+ "signInDescription": "预设会保存到你的账户中,这样你在所有设备上都能直接使用。",
+ "noPresetsTitle": "还没有预设",
+ "noPresetsDescription": "在画布上选中多个图形,然后在检查器里使用 保存为预设,就能把第一个预设加到这里。",
+ "comingSoonTitle": "即将推出",
+ "comingSoonDescription": "社区共享预设会显示在这里。先保存你自己的预设,之后也可以分享给 TrackDraw 社区。",
+ "myPresetsNav": "我的预设",
+ "storeNav": "商店",
+ "soonBadge": "即将推出",
+ "mobileTitle": "预设",
+ "mobileSubtitle": "先选一个预设,再在画布上点一下即可放置。",
+ "placeFlowTitle": "放置流程",
+ "placeFlowDescription": "在这里选择预设后点一下画布,结果会立即展开成普通可编辑图形。",
+ "desktopTitle": "布局预设",
+ "desktopSubtitle": "选择一个预设放到画布上,然后像普通图形一样继续编辑。",
+ "tipText": "在画布上选中图形,然后在检查器里使用 保存为预设 添加你自己的预设。"
+ },
+ "starterOverlay": {
+ "dialog": {
+ "title": "欢迎使用 TrackDraw",
+ "mobileSubtitle": "先摆几个门,画出路径,看看 3D,赛道准备好后再分享。",
+ "studioEyebrow": "Studio",
+ "desktopDescription": "先在 2D 里搭好布局,再到 3D 检查高度,赛道准备好后再分享只读链接。",
+ "dismissAria": "关闭新手引导"
+ },
+ "hints": {
+ "gate": {
+ "badge": "引导",
+ "title": "先放第一个门",
+ "description": "在画布上点一下就能放下第一个穿越门。先摆几个门,再切到路径工具。",
+ "action": "知道了",
+ "dismiss": "关闭穿越门摆放提示"
+ },
+ "path": {
+ "badge": "引导",
+ "title": "接下来画路径",
+ "description": "先摆几个门,再切到路径工具,把一圈线路串起来。画完路径后再去看 3D。",
+ "action": "开始画路径",
+ "dismiss": "关闭路径引导提示"
+ },
+ "preview3d": {
+ "badge": "预览",
+ "title": "先有路径,3D 才更有用",
+ "description": "这里已经能预览障碍摆放,但真正让高度和模拟飞行审阅有意义的,还是路径本身。",
+ "action": "去 2D 画路径",
+ "dismiss": "关闭 3D 预览引导提示"
+ },
+ "review3d": {
+ "badge": "引导",
+ "title": "现在去 3D 看看",
+ "description": "绕着路径看看高度和门距,感觉顺了以后再去分享或导出。",
+ "action": "下一步去分享或导出",
+ "dismiss": "关闭 3D 审阅提示"
+ },
+ "postPath": {
+ "badge": "下一步",
+ "title": "路径已就绪,去 3D 检查",
+ "description": "现在切到 3D 标签页,先检查这条线路,再继续细调障碍摆放。",
+ "action": "切换到 3D",
+ "dismiss": "关闭路径完成后的 3D 提示"
+ }
+ }
+ },
+ "starterFlow": {
+ "intro": {
+ "goodFirstSteps": "推荐的起步方式"
+ },
+ "steps": {
+ "step1": {
+ "title": "先摆几个门和障碍",
+ "description": "先从小布局开始,让赛道骨架尽快出现。"
+ },
+ "step2": {
+ "title": "再把比赛路径画出来",
+ "description": "很多时候,先把路径画出来,比单纯调障碍位置更快看出问题。"
+ },
+ "step3": {
+ "title": "检查 3D,然后导出或分享",
+ "description": "尽早检查路径,准备好后再发出只读链接。"
+ }
+ },
+ "starterLayouts": {
+ "label": "起始布局"
+ },
+ "guided": {
+ "label": "引导式开始",
+ "title": "按引导开始",
+ "descriptionDesktop": "打开一个空场地,并默认选中 `Gate`,先把赛道框架摆出来。等第一批障碍落位后,再去画路径。",
+ "descriptionMobile": "打开空场地并默认选中 Gate。第一批障碍落位后再去画路径。"
+ },
+ "empty": {
+ "label": "空白开始",
+ "continue": "以空白画布继续",
+ "description": "跳过引导,直接进入空白 Studio。"
+ },
+ "shortcuts": {
+ "gate": "放置穿越门",
+ "path": "开始画路径",
+ "finish": "完成路径"
+ }
+ },
+ "saveAsPresetDialog": {
+ "title": "保存为预设",
+ "subtitle": "给当前选择起个名字,之后就能从预设选择器里再次放置。",
+ "shapeCount": "{count} 个图形",
+ "pathsExcluded": "将排除 {count} 条路径——预设只会记录非路径图形。",
+ "presetNameLabel": "预设名称",
+ "presetNamePlaceholder": "例如:计时门组合",
+ "savePreset": "保存预设"
+ },
+ "mobilePanels": {
+ "viewControls": {
+ "mode": {
+ "current": "当前模式",
+ "viewModeLabel": "视图模式"
+ },
+ "controls": {
+ "fitToField": {
+ "label": "适应场地",
+ "description": "将当前设计居中显示"
+ },
+ "snap": {
+ "label": "吸附",
+ "description": "吸附到附近网格和物件"
+ },
+ "rulers": {
+ "label": "标尺",
+ "description": "在移动端显示上侧和左侧辅助标尺"
+ },
+ "obstacleNumbers": {
+ "label": "障碍编号",
+ "description": "在路径障碍上显示路径编号"
+ },
+ "flyThrough": {
+ "label": "模拟飞行",
+ "descriptions": {
+ "available": "启动路径预览相机",
+ "readOnly": "这条共享赛道没有路径",
+ "draw": "先在 2D 里画出路径后才能启用"
+ }
+ },
+ "gizmo": {
+ "label": "坐标轴",
+ "description": "在 3D 预览中显示坐标轴辅助器"
+ }
+ },
+ "readOnly": {
+ "review3dWithPath": "当前共享赛道为只读 3D 审阅,且可用模拟飞行。",
+ "review3dNoPath": "当前共享赛道为只读 3D 审阅,但没有路径可用于模拟飞行。",
+ "review2dWithPath": "当前共享赛道为只读 2D 审阅。切到 3D 后可使用模拟飞行。",
+ "review2dNoPath": "当前共享赛道为只读 2D 审阅。"
+ },
+ "share": {
+ "sectionLabel": "分享",
+ "editableCopy": "可编辑副本",
+ "shareButton": "分享"
+ }
+ },
+ "multiSelect": {
+ "header": {
+ "title": "多选",
+ "lockedHint": "当前选择已锁定。先解锁后才能移动或缩放。",
+ "tapHint": "点按项目可将其加入或移出选择。"
+ },
+ "fields": {
+ "groupNamePlaceholder": "分组名称"
+ },
+ "actions": {
+ "duplicate": "复制",
+ "lock": "锁定",
+ "unlock": "解锁",
+ "delete": "删除",
+ "ungroup": "取消分组",
+ "group": "分组"
+ }
+ },
+ "pathBuilder": {
+ "header": {
+ "title": "路径绘制"
+ },
+ "status": {
+ "loopConnected": "闭环已连接 · {count} 个点 · {length}",
+ "pointsCount": "{count} 个点 · {length}",
+ "tapToStart": "点按画布放置第一个点"
+ },
+ "actions": {
+ "undo": "撤销",
+ "connectEnds": "连接首尾",
+ "finish": "完成",
+ "cancel": "取消"
+ }
+ },
+ "editorPanels": {
+ "quickActions": {
+ "title": "快捷操作",
+ "adjustTitle": "微调",
+ "hints": {
+ "addPoint": "在线段上添加点",
+ "deleteWaypoint": "删除选中的路径点",
+ "resume": "继续编辑或调整这条路径",
+ "selectionStep": "{label} · 步进 {step}",
+ "selectionFallback": "选择"
+ },
+ "actions": {
+ "addPoint": "添加点",
+ "editPath": "编辑路径",
+ "duplicate": "复制",
+ "lock": "锁定",
+ "unlock": "解锁",
+ "delete": "删除",
+ "deletePoint": "删除点",
+ "adjust": "微调",
+ "back": "返回"
+ },
+ "adjust": {
+ "step": "步进",
+ "left": "左",
+ "up": "上",
+ "down": "下",
+ "right": "右"
+ }
+ },
+ "nav": {
+ "exit": "退出",
+ "select": "选择",
+ "tools": "工具",
+ "inspect": "检查",
+ "view": "视图",
+ "review": "审阅",
+ "share": "分享",
+ "editCopy": "编辑副本",
+ "preview": "预览"
+ },
+ "inspector": {
+ "designSettingsHint": "设计设置",
+ "title": "检查器",
+ "subtitles": {
+ "empty": "设计设置和已放置项目",
+ "selected": "当前选择的属性与编辑"
+ }
+ },
+ "tools": {
+ "title": "工具",
+ "sections": {
+ "items": "赛道项目",
+ "tools": "画布工具"
+ },
+ "toolDescriptions": {
+ "startfinish": "标记起飞区或计时起终点",
+ "cone": "放置赛道标记物",
+ "label": "在画布上添加备注",
+ "polyline": "绘制或编辑比赛路径"
+ },
+ "subtitles": {
+ "preview3d": "当前 3D 会话的项目操作",
+ "canvas2d": "绘制与编辑工具"
+ }
+ },
+ "view": {
+ "title": "视图",
+ "subtitle": "画布模式、辅助线和视口控制"
+ }
+ }
+ },
+ "viewModeSwitch": {
+ "ariaLabel": "视图模式",
+ "canvas2dShort": "2D",
+ "preview3dShort": "3D",
+ "canvas2dDrawer": "2D 画布",
+ "preview3dDrawer": "3D 预览"
+ },
+ "desktopInspectorPanel": {
+ "ariaLabel": "检查器",
+ "expandTooltip": "展开检查器",
+ "collapseTooltip": "收起检查器",
+ "verticalLabel": "检查器"
+ },
+ "shell": {
+ "untitledTrack": "未命名赛道",
+ "untitledFallback": "未命名",
+ "exportFailed": "导出失败",
+ "exportFailedDescription": "{message} 请重试,或打开导出面板,从那里下载项目 JSON 备份。",
+ "exportFailedNoProjectDescription": "TrackDraw 无法加载这个本地项目。请先打开它,或改选另一个已保存项目。",
+ "exportSuccess": "项目 JSON 已导出",
+ "embeddedTrack": "嵌入赛道",
+ "sharedPreview": "共享预览",
+ "loading3d": "正在加载 3D…"
+ },
+ "languagePicker": {
+ "ariaLabel": "语言"
+ },
+ "performanceHud": {
+ "title": "开发模式 HUD",
+ "summary": "工具 {tool} · 已选 {count} 项",
+ "reset": "重置",
+ "close": "关闭",
+ "metrics": {
+ "canvas2d": "2D 画布",
+ "preview3d": "3D 视图",
+ "inspector": "检查器",
+ "autosave": "自动保存"
+ },
+ "levels": {
+ "noData": "无数据",
+ "heavy": "负载高",
+ "busy": "繁忙",
+ "ok": "正常"
+ }
+ },
+ "textureDebug": {
+ "title": "贴图调试",
+ "copyConfig": "复制配置",
+ "copied": "已复制",
+ "noPanels": "还没有注册任何面板",
+ "flip": "翻转"
+ }
+}
diff --git a/lang/zh/exportPdf.json b/lang/zh/exportPdf.json
new file mode 100644
index 00000000..8a877949
--- /dev/null
+++ b/lang/zh/exportPdf.json
@@ -0,0 +1,66 @@
+{
+ "renderFailed": "PDF 导出失败",
+ "untitledTrack": "未命名赛道",
+ "footer": {
+ "brand": "TrackDraw · {pageLabel}",
+ "pageOf": "第 {pageNumber} / {totalPages} 页",
+ "pageOverview": "概览",
+ "pageTrackMap": "赛道图",
+ "pageBuildSheet": "布场清单"
+ },
+ "sharedView": {
+ "title": "共享视图",
+ "scanToOpen": "扫码打开赛道",
+ "scanDescription": "打开 TrackDraw 规范共享链接,方便手机端审阅和现场讲解。",
+ "noShare": "未关联已发布共享链接",
+ "noShareDescription": "如果你希望在布场清单里带上扫码入口,请先发布这个项目。"
+ },
+ "cover": {
+ "title": "赛事布场清单",
+ "numberingIncluded": "已含编号",
+ "numberingHidden": "已隐藏编号",
+ "statusLabel": "状态",
+ "statusBuildable": "可布场",
+ "statusShort": "库存不足",
+ "missingLabel": "缺口",
+ "obstaclesLabel": "障碍数",
+ "intro": "这份布场清单是给比赛日布场组随身携带用的。地图页用于现场摆放参考,物料页用于核对库存,布场顺序页则给出更实际的进场搭建顺序。时间估算基于常见 2-3 人俱乐部布场流程:卸车、摊器材、预组,然后进场摆放与固定。",
+ "contentsTitle": "内容",
+ "contentsItem1": "1. 赛道图与场地概览",
+ "contentsItem2": "2. 物料清单、库存核对与计时点",
+ "contentsItem3": "3. 布场顺序与比赛日备注",
+ "summaryTitle": "概要",
+ "summaryShortage": "{count, plural, one {当前布局在 1 类障碍上超出可用库存。} other {当前布局在 {count} 类障碍上超出可用库存。}} 在把这份布场清单作为最终参考前,请先补齐缺口。",
+ "summaryBuildable": "当前布局可由已保存的器材库存完成布场。以下页面可直接作为比赛日布场和交接参考。"
+ },
+ "mapPage": {
+ "title": "赛道图",
+ "stockShort": "库存差 {count}",
+ "stockBuildable": "库存检查:可布场",
+ "numberingShown": "显示编号",
+ "numberingHidden": "隐藏编号"
+ },
+ "buildSheet": {
+ "title": "布场清单",
+ "materialListTitle": "物料清单",
+ "obstacleColumn": "障碍",
+ "requiredColumn": "需求",
+ "availableColumn": "现有",
+ "missingColumn": "缺口",
+ "timingPointsTitle": "计时点",
+ "noTimingPoints": "目前还没有指定计时点。比赛日前交接前,请先在检查器里把门设置为起终点或分段点。",
+ "setupSequenceTitle": "布场顺序",
+ "setupIntro": "预计布场用时:约 {min}-{max} 分钟。总体复杂度:{complexity}。{summary}",
+ "crewStepLabel": "布场步骤",
+ "groupedTaskLabel": "分组任务",
+ "obstacleNumberSuffix": "{kind} · 障碍 #{number}",
+ "minutesSuffix": "{minutes} 分钟",
+ "noSetupSequenceNumbering": "当前还没有可用的布场顺序。先补一条路径并摆放一些障碍,才能生成更实用的布场清单。",
+ "noSetupSequenceHidden": "这次导出隐藏了编号。如果你希望布场顺序能按路径编号走,请开启障碍编号。",
+ "notesTitle": "比赛日备注",
+ "note1": "布场和飞手讲解时,请以赛道图页作为现场摆放参考。",
+ "note2": "决定是否按当前布局布场前,请把库存核对结果当作最终依据。",
+ "note3": "先把赛道主骨架落位,再去固定重力门、目字门等高工作量结构,最后完成常规穿越门。",
+ "note4": "如果路径或障碍数量发生变化,请重新导出布场清单,确保地图、库存表和布场顺序保持一致。"
+ }
+}
diff --git a/lang/zh/index.ts b/lang/zh/index.ts
new file mode 100644
index 00000000..315de9eb
--- /dev/null
+++ b/lang/zh/index.ts
@@ -0,0 +1,10 @@
+export { default as editor } from "./editor.json";
+export { default as common } from "./common.json";
+export { default as inspector } from "./inspector.json";
+export { default as dialogs } from "./dialogs.json";
+export { default as landing } from "./landing.json";
+export { default as login } from "./login.json";
+export { default as share } from "./share.json";
+export { default as shapes } from "./shapes.json";
+export { default as setupEstimate } from "./setupEstimate.json";
+export { default as exportPdf } from "./exportPdf.json";
diff --git a/lang/zh/inspector.json b/lang/zh/inspector.json
new file mode 100644
index 00000000..1cf4b701
--- /dev/null
+++ b/lang/zh/inspector.json
@@ -0,0 +1,343 @@
+{
+ "tabs": {
+ "ariaLabel": "检查器面板",
+ "layout": "布局",
+ "selection": "选择"
+ },
+ "emptyState": {
+ "title": "当前未选择内容",
+ "body": "在画布上选择一个或多个项目后,就能在这里编辑它们的属性。"
+ },
+ "actions": {
+ "group": "分组",
+ "continueEditing": "继续",
+ "connectEnds": "连接",
+ "deleteShort": "删除",
+ "duplicateShort": "复制",
+ "ungroup": "取消分组",
+ "groupSelection": "选中项分组",
+ "ungroupSelection": "取消选中项分组",
+ "lockShort": "锁定",
+ "unlockShort": "解锁",
+ "savePreset": "保存为预设",
+ "joinPaths": "合并路径",
+ "remove": "移除",
+ "removeItem": "移除项目"
+ },
+ "hints": {
+ "locked": "当前项目已在画布上锁定。解锁后才能移动、缩放或继续编辑路径。"
+ },
+ "group": {
+ "sectionTitle": "分组",
+ "nameLabel": "分组名称",
+ "namePlaceholder": "可选分组名称"
+ },
+ "raceTiming": {
+ "sectionTitle": "计时",
+ "ariaLabel": "计时角色",
+ "splitIdLabel": "分段 ID",
+ "splitIdPlaceholder": "split-1",
+ "roleStart": "起点",
+ "roleSplit": "分段",
+ "metaTimingStart": "计时:起点",
+ "metaTimingSplit": "计时:{id}",
+ "metaGrouped": "已分组",
+ "metaLocked": "已锁定",
+ "metaEditable": "可编辑"
+ },
+ "transform": {
+ "sectionTitle": "变换",
+ "positionLabel": "位置",
+ "rotationLabel": "旋转(度)",
+ "colorLabel": "颜色",
+ "pickColorAriaLabel": "选择颜色",
+ "editingSubtitle": "正在编辑 {kind} 的属性和摆放。"
+ },
+ "ladder": {
+ "sectionTitle": "目字门"
+ },
+ "tower": {
+ "sectionTitle": "日字门"
+ },
+ "catalog": {
+ "sectionTitle": "目录",
+ "sourceLabel": "来源",
+ "sizeLabel": "尺寸",
+ "officialBadge": "官方"
+ },
+ "dimensions": {
+ "widthLabel": "宽度({unit})",
+ "heightLabel": "高度({unit})",
+ "gridLabel": "网格({unit})",
+ "thicknessLabel": "厚度({unit})",
+ "radiusLabel": "半径({unit})",
+ "poleHeightLabel": "杆高({unit})",
+ "tiltLabel": "倾角(度)",
+ "gatesLabel": "门数",
+ "elevationLabel": "高度({unit})",
+ "levelsLabel": "层数",
+ "sizeLabel": "尺寸({unit})",
+ "openingHeightLabel": "开口高度({unit})"
+ },
+ "label": {
+ "sectionTitle": "标注",
+ "textLabel": "文本",
+ "fontSizeLabel": "字号(px)",
+ "threeDModeLabel": "3D 模式",
+ "floatOption": "悬浮(看板)",
+ "groundOption": "投射到地面"
+ },
+ "multiSelection": {
+ "sectionTitle": "选择",
+ "mixedTypesPlaceholder": "混合类型",
+ "titleSelected": "已选择 {count} 个项目",
+ "subtitle": "这里可以做批量操作。需要精细编辑时,请回到画布选中单个项目。",
+ "groupCountMeta": "{count} 个分组",
+ "joinAvailableMeta": "可合并",
+ "saveSectionLabel": "保存片段",
+ "lockedItemsNote": "已锁定的项目不会被改动。"
+ },
+ "polyline": {
+ "sectionTitle": "路径",
+ "flowLabel": "流向",
+ "directionLabel": "方向",
+ "waypointsTitle": "路径点",
+ "waypointsSubtitle": "调整每个点及其高度。",
+ "elevationColumn": "高",
+ "editColumn": "编",
+ "insertPointAfter": "在后面插入点",
+ "removePoint": "移除点",
+ "addPoint": "添加点"
+ },
+ "listPanel": {
+ "panelTitle": "赛道项目",
+ "placedItemsTitle": "已放置项目",
+ "placedItemsSubtitle": "从列表中选择一个项目。",
+ "filterPlaceholder": "筛选项目",
+ "filterAll": "全部",
+ "filterObstacles": "障碍",
+ "itemColumn": "项目",
+ "pathColumn": "路径",
+ "noItemsMatchFilter": "没有项目匹配当前筛选。",
+ "routeStatus": {
+ "off": "关"
+ }
+ },
+ "layout": {
+ "mapReference": {
+ "title": "地图参考",
+ "fields": {
+ "opacity": "透明度",
+ "rotation": "旋转"
+ },
+ "actions": {
+ "removeTitle": "移除地图参考",
+ "editTitle": "编辑地图参考",
+ "addTitle": "添加地图参考",
+ "edit": "编辑",
+ "add": "添加地图",
+ "showTitle": "显示地图参考",
+ "hideTitle": "隐藏地图参考",
+ "show": "显示"
+ }
+ },
+ "sections": {
+ "routeNumbering": "路径编号",
+ "inventory": "库存",
+ "field": "场地"
+ },
+ "field": {
+ "unitsLabel": "单位",
+ "renderScaleLabel": "渲染比例",
+ "renderScaleTooltip": "画布内部密度,单位为每米像素数",
+ "pxPerMeterAriaLabel": "每米像素数,画布内部比例"
+ },
+ "project": {
+ "titlePlaceholder": "未命名赛道",
+ "subtitle": "调整项目标题、场地设置和可用障碍库存。"
+ },
+ "overview": {
+ "title": "当前布局",
+ "subtitle": "查看当前布局的已放置项目、路径编号和可布场性。",
+ "emptyTitle": "还没有放置任何项目",
+ "emptyDescription": "先在画布上放几个障碍,再回来检查布局并和你的库存做对照。"
+ },
+ "inventory": {
+ "status": {
+ "buildable": "可布场",
+ "short": "库存不足"
+ },
+ "labels": {
+ "missing": "缺口",
+ "types": "类型"
+ },
+ "description": "TrackDraw 会把当前布局和这个项目里保存的障碍库存做对比。",
+ "needCountSuffix": "需求 {count}",
+ "stockOk": "足够"
+ },
+ "meta": {
+ "itemsCount": "{count} 个项目",
+ "buildable": "可布场",
+ "shortItems": "缺少 {count} 项",
+ "stockCovered": "库存已覆盖",
+ "typesShort": "缺 {count} 类",
+ "numbered": "已编号 {count}",
+ "numberingIdle": "编号未生成",
+ "numberingNeedsReview": "编号需复核"
+ }
+ },
+ "routeNumbering": {
+ "status": {
+ "ready": "就绪",
+ "partial": "需复核",
+ "noRouteMatches": "无匹配",
+ "missingRoute": "缺少路径",
+ "noNumberedObstacles": "无路径障碍",
+ "empty": "空"
+ },
+ "messages": {
+ "ready": "已编号 {count} 个路径障碍。",
+ "partial": "{mapped} / {total} 个路径障碍已编号。",
+ "noRouteMatches": "找到 {count} 个路径障碍,但都没有离路径足够近。",
+ "missingRoute": "支持的障碍已经准备好,可以生成比赛路径。",
+ "noNumberedObstacles": "穿越门、目字门和重力门会出现在路径编号里。",
+ "empty": "添加路径障碍和路径后即可生成编号。"
+ },
+ "issues": {
+ "offRoutePrefix": "偏离路径:{names}",
+ "moreSuffix": " 另有 {count} 个"
+ },
+ "meta": {
+ "numbered": "已编号",
+ "issues": "问题",
+ "raceLine": "比赛路径"
+ },
+ "generate": {
+ "button": "生成比赛路径",
+ "regenerateButton": "重新生成比赛路径",
+ "success": "比赛路径已生成。",
+ "errors": {
+ "noObstacles": "无法生成比赛路径——请先添加至少两个受支持的障碍(穿越门、目字门或重力门)。"
+ },
+ "warnings": {
+ "unsupportedShapes": "{count, plural, one {有 1 个障碍暂不支持,已跳过。} other {有 {count} 个障碍暂不支持,已跳过。}}",
+ "tooFewObstacles": "再添加一些受支持的障碍,生成的比赛路径会更有参考价值。",
+ "closeObstacles": "{count, plural, one {有 1 对障碍距离非常近,请检查生成的路径。} other {有 {count} 对障碍距离非常近,请检查生成的路径。}}"
+ }
+ }
+ },
+ "elevationChart": {
+ "noRouteSelected": "未选择路径",
+ "title": "高度剖面",
+ "subtitle": "路径高度、着色警告区段和线路审阅提示。",
+ "openDetailsAria": "打开高度详情",
+ "detailsTooltip": "高度详情",
+ "chartAriaLabel": "高度剖面图",
+ "routeDistance": "{distance} 路径",
+ "elevationRange": "高度 {min}-{max}",
+ "moreCount": "另有 {count} 个",
+ "maneuversHeading": "动作识别",
+ "maneuverDetected": "在 {range} 附近检测到 {maneuver}",
+ "apexSuffix": ",顶点 {index}",
+ "waypointSingle": "路径点 {index}",
+ "waypointRange": "路径点 {start}-{end}",
+ "waypointMarkerAria": "在高度剖面中选中路径点 {index}",
+ "warningMarkerAria": "跳转到 {warning} 的第 {segment} 段",
+ "timingMarkerAria": "在高度剖面中选中计时点 {label}",
+ "obstacleMarkerAria": "在高度剖面中选中障碍 {index}",
+ "routeReviewHeading": "路径审阅",
+ "warningSegmentsHeading": "警告区段",
+ "jumpToSegment": "跳转到第 {segment} 段",
+ "noWarnings": "这条路径当前没有路径警告。",
+ "normal": "正常",
+ "timingMarkersLegend": "计时",
+ "obstacleMarkersLegend": "障碍",
+ "warningMarkersLegend": "警告",
+ "maneuvers": {
+ "powerloop": "Powerloop",
+ "split-s": "Split-S"
+ },
+ "warnings": {
+ "stub": {
+ "summary": "路径至少需要 2 个路径点才能形成路径",
+ "shortLabel": "路线不完整",
+ "title": "路径还没画完整",
+ "problem": "路径至少需要两个路径点,TrackDraw 才能把它当成完整路径来审阅。",
+ "fix": "再添加一个路径点,或先把当前路径画完。"
+ },
+ "flat": {
+ "summary": "还没设置高度,3D 预览会保持平面",
+ "shortLabel": "高度平直",
+ "title": "还没有设置高度",
+ "problem": "所有路径点目前都还是 0 米,所以 3D 预览和高度剖面都会保持平的。",
+ "fix": "如果这条线路需要爬升或下落,请在检查器或 3D 视图里设置路径点高度。"
+ },
+ "steep": {
+ "summary": "{count, plural, one {路径点 {first} 附近坡度过大} other {{count} 段坡度过大}}",
+ "shortLabel": "坡度过大",
+ "title": "高度变化过于突然",
+ "problem": "有一段线路在很短距离内急升或急降,会让 3D 线路显得很突兀。",
+ "fix": "把高度变化拉长,或增加一个中间路径点。"
+ },
+ "hairpin": {
+ "summary": "{count, plural, one {路径点 {first} 处转向过急} other {{count} 个急转}}",
+ "shortLabel": "急转",
+ "title": "转向过于紧凑",
+ "problem": "某个路径点形成了近乎折返的急转,高速飞行时可能很难顺畅通过。",
+ "fix": "把路径点往外挪,或在转弯前后留出更多空间。"
+ },
+ "close-points": {
+ "summary": "{count, plural, one {路径点 {first} 附近点距过近} other {{count} 处路径点间距过近}}",
+ "shortLabel": "点距过近",
+ "title": "路径点之间太近",
+ "problem": "两个路径点距离太近,导致中间出现极短的路径段。",
+ "fix": "删除其中一个点,或把它移远一些。"
+ },
+ "spacing-shift": {
+ "summary": "{count, plural, one {路径点 {first} 附近节奏突变} other {{count} 处节奏突变}}",
+ "shortLabel": "节奏突变",
+ "title": "门距节奏变化过猛",
+ "problem": "某个路径点前后的距离变化太突然,会让整条线路节奏不均匀。",
+ "fix": "调整附近路径点,让间距变化更平滑。"
+ },
+ "rhythm-break": {
+ "summary": "{count, plural, one {路径点 {first} 附近有短修正段} other {{count} 段短修正}}",
+ "shortLabel": "短修正",
+ "title": "短修正打断流畅度",
+ "problem": "较长线路中夹了一个很短的中间线段,容易产生别扭的小摆动。",
+ "fix": "移动这个点、删除它,或加一个更平顺的过渡点。"
+ }
+ }
+ },
+ "mapReference": {
+ "title": "地图参考",
+ "subtitle": "选择场地中心并对齐场地轮廓。",
+ "searchPlaceholder": "搜索地点或粘贴坐标",
+ "searching": "搜索中",
+ "enterHint": "回车",
+ "zoomLabel": "缩放 {zoom}",
+ "zoomIn": "放大",
+ "zoomOut": "缩小",
+ "resetRotation": "重置旋转",
+ "rotateLeft15": "向左旋转 15 度",
+ "rotateLeft1": "向左旋转 1 度",
+ "rotateRight1": "向右旋转 1 度",
+ "rotateRight15": "向右旋转 15 度",
+ "latitude": "纬度",
+ "longitude": "经度",
+ "rotation": "旋转",
+ "rotationValue": "{deg} 度",
+ "view": "视图",
+ "out": "缩小",
+ "in": "放大",
+ "previewZoom": "预览缩放 {zoom}",
+ "center": "居中",
+ "useLocation": "使用当前位置",
+ "locating": "正在定位",
+ "saveMap": "保存地图",
+ "locationUnavailable": "当前浏览器不支持定位。",
+ "locationPermissionDenied": "定位权限被拒绝。",
+ "locationTimeout": "定位超时。",
+ "locationError": "无法确定你的位置。"
+ }
+}
diff --git a/lang/zh/landing.json b/lang/zh/landing.json
new file mode 100644
index 00000000..91798a50
--- /dev/null
+++ b/lang/zh/landing.json
@@ -0,0 +1,267 @@
+{
+ "metadata": {
+ "homeTitle": "TrackDraw · FPV 赛道设计工具",
+ "homeSocialTitle": "TrackDraw | FPV 赛道设计工具",
+ "homeDescription": "TrackDraw 是一款面向 FPV 赛事组织者的穿越机竞速赛道设计工具。按真实比例设计赛道,在 3D 中检查线路流畅度,并分享只读比赛日布局。",
+ "homeSocialDescription": "用 TrackDraw 按真实比例规划 FPV 赛道,在 3D 中检查线路流畅度,并分享只读比赛日方案。",
+ "studioTitle": "FPV 赛道设计工具",
+ "studioSocialTitle": "TrackDraw | FPV 赛道设计工具",
+ "studioDescription": "打开 TrackDraw,按真实比例设计 FPV 赛道布局,添加门型和障碍,并在 3D 中预览线路流畅度。",
+ "studioSocialDescription": "按真实比例设计 FPV 穿越机竞速赛道,添加门型和障碍,并在 3D 中预览线路流畅度。"
+ },
+ "header": {
+ "homeAriaLabel": "首页",
+ "homeTooltip": "返回主站首页",
+ "featuresNav": "功能",
+ "featuresTooltip": "跳到产品概览",
+ "galleryNav": "图库",
+ "galleryTooltip": "浏览社区公开布局",
+ "pricingNav": "方案",
+ "pricingTooltip": "查看方案与价格",
+ "faqNav": "FAQ",
+ "faqTooltip": "查看常见问题与解答",
+ "logoAriaLabel": "TrackDraw",
+ "siteNavAriaLabel": "首页、图库和公开站点导航",
+ "mobileNavAriaLabel": "导航",
+ "themeAriaLabel": "主题",
+ "themeTooltip": "切换公开站点主题。",
+ "languageAriaLabel": "语言",
+ "languageTooltip": "切换站点语言。",
+ "openStudio": "打开 Studio"
+ },
+ "hero": {
+ "eyebrow": "为 FPV 赛事组织者打造",
+ "headingLine1": "比赛日的顺畅,",
+ "headingLine2": "从一张好图开始。",
+ "description": "TrackDraw 是一款基于浏览器的 FPV 穿越机竞速赛道设计工具。你可以在 2D 视图中按比例布场,在 3D 视图中检查线路流畅度,把只读布局分享给飞手和布场组,而且桌面端和移动端都好用。",
+ "ctaStudio": "打开 Studio",
+ "ctaFeatures": "查看功能",
+ "feature1": "官方障碍目录",
+ "feature2": "赛道片段",
+ "feature3": "3D 预览与模拟飞行",
+ "feature4": "赛事布场清单 PDF 与 SVG",
+ "videoAlt": "TrackDraw FPV 赛道设计工具演示",
+ "videoDescription": "一个简短的产品演示,展示 TrackDraw 如何帮助 FPV 赛事组织者设计赛道、在 3D 中检查线路,并分享给飞手和布场组。"
+ },
+ "features": {
+ "sectionTitle": "功能",
+ "sectionHeading": "设计、检查、分享。",
+ "sectionDescription": "TrackDraw 让 FPV 赛事组织者能更实际地规划赛道:真实场地尺寸、正确的障碍类型,以及从布局到审阅再到现场交接的一体化流程。",
+ "trueToScale": {
+ "title": "真实比例画布",
+ "description": "设置场地尺寸,按比例摆放障碍,并在与真实场地一致的网格上工作。"
+ },
+ "obstacleCatalog": {
+ "title": "官方障碍目录",
+ "description": "放置带真实尺寸的官方 MultiGP 障碍,包括穿越门、标旗、目字门、跨栏和围挡,并获得准确的 3D 渲染。"
+ },
+ "preview3d": {
+ "title": "更接近实飞的 3D 预览",
+ "description": "在 3D 中环绕查看、给路径加高度、检查动作弧线,并通过模拟飞行提前发现节奏问题。"
+ },
+ "shareLinks": {
+ "title": "共享审阅链接",
+ "description": "把只读布局发给飞手和布场组。游客链接是临时的,账户发布链接则可一直保留到你撤销为止。"
+ },
+ "sections": {
+ "title": "赛道片段",
+ "description": "把任意画布选择保存成命名片段,一键再次放置。片段会跟随你的账户在设备间同步。"
+ },
+ "export": {
+ "title": "布场清单与导出",
+ "description": "生成包含障碍布局、路径图和布场备注的飞手说明 PDF,同时还能导出 SVG、PNG 和 JSON。"
+ }
+ },
+ "workflow": {
+ "sectionEyebrow": "深入看看",
+ "sectionHeading": "从第一版布局到现场交接。",
+ "sectionDescription": "最顺手的方式通常是按流程走:先在 2D 里把赛道搭起来,再到 3D 检查,场地临时变化时快速调整,最后把同一份方案交给飞手和布场组。",
+ "layout": {
+ "title": "2D 布局",
+ "subtitle": "先在和真实场地一致的画布上搭赛道。",
+ "description": "从场地尺寸开始,再摆放布场组和飞手都熟悉的门型与障碍。这样第一版方案就能立刻派上用场,而不是只是一张草图。",
+ "screenshotAlt": "TrackDraw 2D 赛道编辑器,带障碍目录和赛道方案",
+ "bullets": [
+ "按比例设置场地尺寸",
+ "穿越门、标旗、锥桶、起飞区和大件障碍",
+ "用于讲解的路径和标注"
+ ]
+ },
+ "fineTune": {
+ "title": "细调",
+ "subtitle": "场地有变化,也能继续改。",
+ "description": "好方案必须经得起现场变化。检查器控制项和移动端友好编辑,让你能快速改属性、挪障碍,即使现场条件变了,也能把方案继续用下去。",
+ "screenshotAlt": "TrackDraw 移动端编辑器,设置面板已展开",
+ "bullets": [
+ "尺寸、旋转和颜色的检查器控制",
+ "手机和平板也能顺手操作",
+ "不离开主方案就能快速修正"
+ ]
+ },
+ "preview": {
+ "title": "3D 预览",
+ "subtitle": "先走线,再进场。",
+ "description": "一键从 2D 平面切到实时 3D 场景。给路径上的每个路径点设置高度,就能把整个垂直剖面模拟出来,在地钉还没入地前先把风险线路看出来。",
+ "screenshotAlt": "TrackDraw 3D 预览,显示赛道流向与高度",
+ "bullets": [
+ "直接由 2D 设计生成的实时 3D 场景",
+ "路径路径点支持逐点设置高度",
+ "检查器内可查看高度剖面图"
+ ]
+ },
+ "handOff": {
+ "title": "交接",
+ "subtitle": "给飞手和布场组同一份当前版本。",
+ "description": "方案只有别人也能打开才有价值。只读共享链接和导出文件,能让飞手预看、布场组打印、主办方后续复用,看到的都是同一份内容。",
+ "screenshotAlt": "TrackDraw 只读共享视图在手机上打开",
+ "gallery": "去图库找灵感",
+ "bullets": [
+ "任何设备都能打开的只读链接",
+ "适合讲解和打印的 PDF、PNG 与 SVG",
+ "方便归档与复用的 JSON 导出"
+ ]
+ }
+ },
+ "faq": {
+ "sectionTitle": "FAQ",
+ "sectionHeading": "这些你可能会关心。",
+ "items": [
+ {
+ "question": "TrackDraw 是做穿越机赛道设计的吗?",
+ "answer": "是的。TrackDraw 是一款基于浏览器的 FPV 穿越机竞速赛道设计工具,把按比例布局、3D 审阅、分享和导出放在同一套流程里。"
+ },
+ {
+ "question": "规划一条 FPV 赛道最好的方式是什么?",
+ "answer": "先按场地尺寸起图,再摆上你实际有的障碍,然后在 3D 里检查路径,最后再分享或导出。TrackDraw 的整个流程就是围绕这个顺序设计的。"
+ },
+ {
+ "question": "飞手不用账户也能看布局吗?",
+ "answer": "可以。共享链接会以只读模式在任何设备上打开,不需要账户,也不用安装 App。发给飞手、裁判或布场组,大家看到的都是同一个已发布版本。"
+ },
+ {
+ "question": "在现场用平板改图行吗?",
+ "answer": "可以。编辑器对触摸操作做了适配,所以当现场条件变化时,用手机或平板快速微调是完全可行的。"
+ },
+ {
+ "question": "支持导出哪些格式?",
+ "answer": "支持 PDF、PNG、SVG、JSON,以及一条 cinematic FPV 视频导出。既能做打印版 briefing,也能发群里看图,或把项目留档复用。"
+ },
+ {
+ "question": "一开始就必须注册账户吗?",
+ "answer": "不用。你可以直接打开 Studio 开始设计。注册账户之后,才会额外获得云端项目、长期发布链接、图库发布和嵌入能力。"
+ },
+ {
+ "question": "以后办活动还能复用旧布局吗?",
+ "answer": "可以。你可以把布局中的一部分存成命名预设,一键再次摆放;如果要复用整张图,就把项目导出为 JSON,在新活动开始时再导入。"
+ },
+ {
+ "question": "TrackDraw 能帮我提前看出路径问题吗?",
+ "answer": "可以。3D 预览和路径审阅警告会提前暴露出门距不匀、节奏断裂和线路对位问题,让你在分享出去或开始布场前先修掉它们。"
+ }
+ ]
+ },
+ "pricing": {
+ "sectionEyebrow": "方案",
+ "sectionHeading": "先直接用,需要更多时再注册。",
+ "sectionDescription": "当前两种方式都免费。游客模式无需注册即可使用核心编辑器;账户模式则带来长期发布、嵌入、图库展示,以及跨设备项目连续性。",
+ "guest": {
+ "badge": "无需注册",
+ "name": "游客",
+ "price": "免费",
+ "description": "适合快速草图、一次性的临时审阅链接,以及不登录情况下的本地导出。",
+ "cta": "打开 Studio"
+ },
+ "account": {
+ "badge": "账户模式",
+ "name": "账户",
+ "price": "免费",
+ "description": "适合长期发布链接、嵌入、图库、赛道片段、REST API 和跨设备继续编辑。",
+ "cta": "创建账户"
+ },
+ "features": {
+ "fullDesigner": "完整赛道设计工具",
+ "preview2d3d": "2D 与 3D 预览",
+ "export": "PDF、SVG、PNG 与 JSON 导出",
+ "shareLinks": "共享链接",
+ "temporaryLinks": "临时链接",
+ "publishedLinks": "发布后持续有效,直到撤销",
+ "shareLinksNote": "游客分享链接是临时的。账户发布链接会一直保留到你撤销为止,也可以直接嵌入网页。",
+ "embeds": "网页嵌入",
+ "embedsDetail": "通过 iframe 嵌入任意网站",
+ "gallery": "发布到图库",
+ "cloudProjects": "云端同步项目",
+ "cloudProjectsDetail": "项目跟随账户同步,让你可以在任何设备上继续,从家里的桌面端一路接到现场的手机端。",
+ "sections": "赛道片段",
+ "sectionsDetail": "把任意画布选择保存成命名预设,并从预设选择器里再次放置。会跟随账户在多设备间同步。",
+ "api": "REST API 访问",
+ "apiDetail": "Bearer 鉴权,支持 API Key 管理"
+ },
+ "categories": {
+ "design": "设计",
+ "sharing": "分享",
+ "projects": "项目",
+ "integration": "集成"
+ }
+ },
+ "footer": {
+ "tagline": "面向 FPV 赛事组织者的浏览器赛道设计工具。按比例画图,3D 预览,分享只读链接,在移动端也能快速改图。",
+ "productTitle": "产品",
+ "openStudio": "打开 Studio",
+ "releaseNotes": "更新日志",
+ "legalTitle": "法律",
+ "privacy": "隐私政策",
+ "terms": "服务条款",
+ "communityTitle": "社区",
+ "github": "GitHub",
+ "dutchDroneSquad": "Dutch Drone Squad",
+ "reportIssue": "反馈问题",
+ "supportTitle": "支持项目",
+ "githubSponsors": "GitHub Sponsors",
+ "kofi": "Ko-fi",
+ "copyright": "© {year} Dutch Drone Squad。开源项目。",
+ "madeWithPre": "用",
+ "madeWithPost": "为 FPV 社区打造"
+ },
+ "gallery": {
+ "pageTitle": "FPV 穿越机竞速赛道图库",
+ "metaTitle": "TrackDraw | FPV 赛道图库",
+ "metaDescription": "浏览 TrackDraw 社区分享的 FPV 穿越机竞速赛道布局。",
+ "sectionLabel": "图库",
+ "headingLine1": "看看别人怎么摆,",
+ "headingLine2": "启发你的下一条赛道。",
+ "description": "浏览社区公开分享的 TrackDraw 布局找灵感。精选赛道会优先显示,其次是最新发布的社区作品。",
+ "grid": {
+ "recentlyAdded": "刚刚发布",
+ "fieldSizeNotSet": "未设置场地尺寸",
+ "unknownPilot": "TrackDraw 飞手",
+ "featuredBadge": "精选",
+ "byOwner": "作者:{owner}",
+ "obstacleCount": "{count} 个障碍",
+ "published": "发布于 {date}",
+ "open": "打开",
+ "emptyTitle": "这里还没有赛道。",
+ "emptyDescription": "等 TrackDraw 用户选择公开分享后,图库内容就会出现在这里。",
+ "featuredHeading": "精选",
+ "featuredSubtitle": "我们挑出来、优先推荐的布局。",
+ "recentHeading": "最新",
+ "recentSubtitle": "社区刚刚发布的新布局。",
+ "loadMore": "加载更多"
+ }
+ },
+ "notFound": {
+ "logoAriaLabel": "TrackDraw",
+ "badge": "错误 404",
+ "heading": "这个页面飞出赛道了",
+ "description": "你访问的页面不存在、可能已经移动,或者在门后拐错了方向。最新的赛道或路线应该就在附近。",
+ "whatToTry": "可以试试",
+ "step1": "回到 Studio,打开最新的赛道。",
+ "step2": "如果你是从旧书签进来的,先回首页。",
+ "step3": "如果页面最近调整过,重新打开最新链接再试一次。",
+ "goHome": "返回首页",
+ "openStudio": "打开 Studio"
+ },
+ "screenshotPlaceholder": {
+ "label": "截图占位",
+ "plannedAsset": "待补素材"
+ }
+}
diff --git a/lang/zh/login.json b/lang/zh/login.json
new file mode 100644
index 00000000..973ed815
--- /dev/null
+++ b/lang/zh/login.json
@@ -0,0 +1,62 @@
+{
+ "metadata": {
+ "title": "登录",
+ "description": "使用一次性魔法链接登录 TrackDraw。"
+ },
+ "goHome": "返回首页",
+ "backToStudio": "返回 Studio",
+ "pageTitle": "TrackDraw 账户",
+ "heading": "无论你在哪,都能继续你的 TrackDraw 工作。",
+ "description": "跨设备访问你的赛道布局,让共享赛道绑定到你的账户,并通过简单的邮件链接快速继续。",
+ "featureDevices": {
+ "title": "跨设备继续",
+ "description": "在另一台设备上接着做,不用改变原来的编辑流程。"
+ },
+ "featureOwnership": {
+ "title": "归属清晰",
+ "description": "让共享布局和后续账户功能都有明确归属。"
+ },
+ "featureMagicLink": {
+ "title": "魔法链接兜底",
+ "description": "支持时可用 passkey 登录,不支持时可退回到邮箱魔法链接。"
+ },
+ "signIn": {
+ "title": "登录",
+ "description": "在当前设备上用 passkey 登录,或通过邮箱接收无需密码的魔法链接。",
+ "checkInboxTitle": "请查收邮箱",
+ "checkInboxBody": "一次性登录链接已发送到 {email}。可在当前设备或另一台设备上打开继续。如果一时没收到,也请检查垃圾邮件箱。",
+ "howItWorksTitle": "魔法链接登录方式",
+ "howItWorksBody": "每封邮件都会带一个一次性登录链接。无需设置密码,直接打开链接即可登录 TrackDraw。",
+ "useDifferentEmail": "换个邮箱",
+ "openingPasskey": "正在打开 passkey…",
+ "signInPasskey": "使用 passkey 登录",
+ "or": "或",
+ "emailLabel": "邮箱",
+ "emailPlaceholder": "you@example.com",
+ "sendingLink": "正在发送链接…",
+ "sendLink": "发送登录链接到我的邮箱",
+ "noPasskeySupport": "当前浏览器这里还不支持 passkey,请改用魔法链接登录。",
+ "passkeyNotFound": "这个 passkey 没有关联到你的 TrackDraw 账户。请换一个 passkey,或改用邮箱登录链接。",
+ "passkeyFailed": "passkey 登录失败。",
+ "checkEmail": "请到邮箱查收登录链接。"
+ },
+ "magicLinkVerify": {
+ "eyebrow": "安全登录",
+ "secureSession": "受保护的魔法链接交接",
+ "needsNewLink": "需要新的链接",
+ "autoTitle": "正在为你登录",
+ "autoDescription": "TrackDraw 正在用你邮箱里的登录链接打开工作区。",
+ "title": "打开 TrackDraw",
+ "description": "请在当前浏览器继续完成登录。这能防止一次性链接被邮件安全检测自动消耗。",
+ "statusEmail": "已收到邮件链接",
+ "statusVerify": "正在验证登录链接",
+ "statusStudio": "正在打开工作区",
+ "continue": "继续前往 TrackDraw",
+ "continuing": "正在继续...",
+ "requestNewLink": "重新申请链接",
+ "backToLogin": "返回登录",
+ "invalidTitle": "这个登录链接不完整",
+ "invalidDescription": "请重新申请一个登录链接,并从最新的 TrackDraw 邮件中打开它。",
+ "failed": "这个登录链接无法使用。请重新申请一个新的链接后再试。"
+ }
+}
diff --git a/lang/zh/setupEstimate.json b/lang/zh/setupEstimate.json
new file mode 100644
index 00000000..ca1d9300
--- /dev/null
+++ b/lang/zh/setupEstimate.json
@@ -0,0 +1,48 @@
+{
+ "complexity": {
+ "light": "轻量",
+ "standard": "常规",
+ "heavy": "重度"
+ },
+ "kinds": {
+ "trackGroup": "赛道分组",
+ "crew": "布场组"
+ },
+ "flagsFinalPass": {
+ "label": "标旗收尾",
+ "note": "主编号障碍线摆完后,再用标旗完成场地可视化与最终视认检查。"
+ },
+ "conesTrackWalk": {
+ "label": "赛前走场时放锥桶(可选)",
+ "note": "锥桶可以放在飞手走场时一起摆放,避免单独再跑一轮;如果你更喜欢提前摆好,现场请预留一点额外时间。"
+ },
+ "crewUnloadStage": {
+ "label": "卸车并摆放器材",
+ "note": "先把车上器材卸下,分好地钉和五金件,并把整套器材摊开,避免后面因为预组门架卡住布场节奏。"
+ },
+ "crewPreassemble": {
+ "label": "预组门架和软质器材",
+ "noteWithSoftGoods": "在把器材搬进场地前,先把 PVC 门架和旗帜等软质器材预组好,这样进场摆放时可以专注在门距和线路上。",
+ "noteWithoutSoftGoods": "在把器材搬进场地前,先把 PVC 门架预组好,这样进场摆放时可以专注在门距和线路上。"
+ },
+ "crewRiggingCheck": {
+ "label": "吊挂与地钉检查",
+ "note": "在抬升或悬挂较重结构前,先确认锚点、上方线缆和整体净空方案。"
+ },
+ "obstacleNoteWithNumber": "{note} 以地图上的 #{number} 号障碍为摆放参考。",
+ "summaryHeavy": "{count, plural, one {有 1 个高工作量结构需要在常规门组前先摆放并固定。} other {有 {count} 个高工作量结构需要在常规门组前先摆放并固定。}} 该时间估算基于 2-3 人布场组;如果涉及吊挂或长距离搬运,耗时会继续增加。",
+ "summaryLight": "大部分布场时间会花在卸车、预组门架,以及由 2-3 人把编号障碍线按顺序摆正落位。",
+ "crewAssumption": "该时间估算基于 2-3 人布场组,默认按常见俱乐部流程进行:卸车、铺开器材、预组、再进场摆放和固定。",
+ "obstacleNotes": {
+ "gate": "等定位结构先摆好后,再把这个穿越门摆正落位。",
+ "tower": "和其他高工作量结构一起优先摆放并固定。日字门需要稳固支撑、垂直对正,以及足够的分层开口净空。",
+ "flag": "主障碍就位后,再用标旗补足可视性和边界识别。",
+ "cone": "最后收尾时用锥桶做边界标记和间距检查。",
+ "label": "这个项目暂时还没有布场建议。",
+ "polyline": "这个项目暂时还没有布场建议。",
+ "startfinish": "优先摆放这个起终点,让起飞和冲线参考先固定下来,再继续铺后面的赛道。",
+ "ladder": "把它视为高工作量障碍;如果需要吊挂或精细悬挂点,请额外预留时间。",
+ "divegate": "尽早摆放并固定。重力门通常比标准穿越门更吃对正和锚固。",
+ "barrier": "主门型和其他障碍摆完后,再布置围挡。"
+ }
+}
diff --git a/lang/zh/shapes.json b/lang/zh/shapes.json
new file mode 100644
index 00000000..f453379c
--- /dev/null
+++ b/lang/zh/shapes.json
@@ -0,0 +1,48 @@
+{
+ "kindLabels": {
+ "gate": "穿越门",
+ "tower": "日字门",
+ "flag": "标旗",
+ "cone": "锥桶",
+ "label": "标注",
+ "polyline": "路径",
+ "startfinish": "起终点",
+ "ladder": "目字门",
+ "divegate": "重力门",
+ "barrier": "围挡"
+ },
+ "toolLabels": {
+ "select": "选择",
+ "grab": "拖移",
+ "preset": "预设",
+ "gate": "穿越门",
+ "tower": "日字门",
+ "flag": "标旗",
+ "cone": "锥桶",
+ "label": "标注",
+ "polyline": "路径",
+ "startfinish": "起飞区",
+ "ladder": "目字门",
+ "divegate": "重力门",
+ "barrier": "围挡"
+ },
+ "toolGroups": {
+ "track": "赛道",
+ "extra": "附加"
+ },
+ "toolMobileLabels": {
+ "startfinish": "起飞",
+ "divegate": "重力门",
+ "barrier": "围挡"
+ },
+ "mobileStatus": {
+ "multiSelect": "多选",
+ "tool": "工具",
+ "itemsCount": "{count} 个项目"
+ },
+ "canvas": {
+ "selectionCount": "{count} 个项目",
+ "selectionFallback": "选择",
+ "frontLabel": "前方"
+ }
+}
diff --git a/lang/zh/share.json b/lang/zh/share.json
new file mode 100644
index 00000000..5865a9ec
--- /dev/null
+++ b/lang/zh/share.json
@@ -0,0 +1,58 @@
+{
+ "badge": "共享视图",
+ "backToHome": "返回首页",
+ "whatToTry": "可以试试",
+ "openStudio": "打开 Studio",
+ "error": {
+ "heading": "这个共享赛道链接已不再受支持",
+ "description": "TrackDraw 现在使用已发布共享链接,不再支持旧的 URL 内嵌共享格式。这个旧链接已经无法直接打开。",
+ "whatToDo": "该怎么做",
+ "step1": "请让发送者从 Studio 重新发布一个新的共享链接。",
+ "step2": "如果你仍然需要离线交接,请让对方发送一份 JSON 导出,然后在 Studio 里通过导入打开。",
+ "step3": "新的已发布链接使用规范的 `/share/[token]` 路由,也是当前唯一受支持的只读分享方式。",
+ "openStudioToImport": "打开 Studio 导入"
+ },
+ "expired": {
+ "heading": "这个共享链接已过期",
+ "description": "临时 TrackDraw 共享链接会在创建时选择的时长后过期;账户发布的项目链接则会一直有效,直到所有者主动撤销。如果你还需要这个布局,请让发送者重新发布。",
+ "step1": "请让发送者重新发布这个赛道,或发送一个账户发布链接来提供更长期的只读访问。",
+ "step2": "如果需要更长期的交接方式,请让对方发送 JSON 导出。"
+ },
+ "notFound": {
+ "heading": "无法打开这个共享赛道",
+ "description": "这个共享链接可能已被所有者撤销,也可能已经过时、不完整,或者复制时出错。请让发送者确认链接仍然有效,或重新发布一个新的链接。如果赛道特别大,JSON 导出会比共享链接更稳妥。",
+ "step1": "请让发送者确认分享仍然有效,并重新复制最新的共享链接。",
+ "step2": "尽量直接从原始消息里打开链接。",
+ "step3": "如果赛道特别大,请改用 JSON 导出;JSON 文件不受 URL 长度限制。"
+ },
+ "viewer": {
+ "dismissIntro": "关闭共享赛道介绍",
+ "sharedByReadOnly": "由 {author} 分享。当前为只读模式,不会保存修改。可切换到 {view},或在 Studio 中另存为可编辑副本。",
+ "readOnlyNoAuthor": "当前为只读模式,不会保存修改。可切换到 {view},或在 Studio 中另存为可编辑副本。",
+ "openView": "打开 {view}",
+ "makeEditableCopy": "创建可编辑副本"
+ },
+ "metadata": {
+ "title": "查看赛道",
+ "description": "查看这条用 TrackDraw 设计的 FPV 赛道。",
+ "expiredTitle": "赛道共享已过期",
+ "expiredDescription": "这个 TrackDraw 共享链接已过期。请让发送者重新发布一个新的链接。",
+ "retiredTitle": "旧版赛道共享不再受支持",
+ "retiredDescription": "旧版 TrackDraw 共享格式已不再受支持。请让发送者重新发布一个新的链接。",
+ "embedTitle": "赛道嵌入",
+ "embedDescription": "TrackDraw 只读赛道嵌入视图。"
+ },
+ "embedUnavailable": {
+ "temporaryTitle": "暂时无法嵌入",
+ "temporaryDescription": "只有账户发布的共享内容才支持嵌入。你可以直接打开共享链接,或登录后发布一个长期可用的嵌入。",
+ "expiredTitle": "这个嵌入已过期",
+ "expiredDescription": "这个临时 TrackDraw 共享已经失效。请让所有者发布一个长期有效的账户共享。",
+ "revokedTitle": "这个嵌入已被撤销",
+ "revokedDescription": "所有者已经撤销了这个 TrackDraw 共享,因此嵌入的赛道也无法继续查看。",
+ "missingTitle": "未找到赛道",
+ "missingDescription": "这个 TrackDraw 嵌入未对应到任何有效的账户发布共享。",
+ "openShareLink": "打开共享链接",
+ "signIn": "登录",
+ "brand": "TrackDraw"
+ }
+}
diff --git a/src/components/LanguagePicker.tsx b/src/components/LanguagePicker.tsx
index a57f3ed8..0197c4fa 100644
--- a/src/components/LanguagePicker.tsx
+++ b/src/components/LanguagePicker.tsx
@@ -23,6 +23,7 @@ const localeConfig: Record = {
en: { label: "English", abbr: "EN" },
nl: { label: "Nederlands", abbr: "NL" },
de: { label: "Deutsch", abbr: "DE" },
+ zh: { label: "简体中文", abbr: "ZH" },
};
function LocaleBadge({ abbr }: { abbr: string }) {
diff --git a/src/lib/i18n/locales.ts b/src/lib/i18n/locales.ts
index 2d582a65..6f7d8d8d 100644
--- a/src/lib/i18n/locales.ts
+++ b/src/lib/i18n/locales.ts
@@ -1,4 +1,4 @@
-export const supportedLocales = ["en", "nl", "de"] as const;
+export const supportedLocales = ["en", "nl", "de", "zh"] as const;
export type SupportedLocale = (typeof supportedLocales)[number];
export const defaultLocale: SupportedLocale = "en";