export const DASHBOARD_LOCALE_STORAGE_KEY = 'botmux.dashboard.locale';
const zh = {
    'app.name': 'botmux',
    'app.subtitle': '飞书 AI CLI 控制台',
    'time.secondsAgo': '{value} 秒前',
    'time.minutesAgo': '{value} 分钟前',
    'time.hoursAgo': '{value} 小时前',
    'nav.overview': '总览',
    'nav.sessions': '会话',
    'nav.sidebarCollapse': '收起菜单栏',
    'nav.sidebarExpand': '展开菜单栏',
    'nav.groups': '群组',
    'nav.schedules': '定时',
    'nav.settings': '设置',
    'nav.botDefaults': 'Bot 配置',
    'status.live': '实时连接',
    'status.disconnected': '连接断开',
    'status.system': '系统',
    'status.light': '浅色',
    'status.dark': '暗黑',
    'status.language': '语言',
    'status.theme': '主题',
    'theme.base': '基础',
    'theme.skins': '主题皮肤',
    'skin.cyber': '2077',
    'skin.genshin': '原神',
    'skin.fallout': 'Fallout',
    'skin.prts': 'PRTS',
    'skin.bluearchive': '蔚蓝档案',
    'skin.zzz': '绝区零',
    'skin.dragonball': '七龙珠',
    'skin.ikun': 'ikun',
    'botOnboarding.add': '添加机器人',
    'botOnboarding.title': '添加机器人',
    'botOnboarding.intro': '选择 CLI 与工作目录，扫码创建 PersonalAgent 应用后写入本机 bots.json，并自动配置开放平台权限。',
    'botOnboarding.cliLabel': 'CLI 适配器',
    'botOnboarding.dirLabel': '工作目录',
    'botOnboarding.dirPlaceholder': '默认 ~（家目录），需为 daemon 主机上已存在的目录',
    'botOnboarding.modelLabel': '模型（可选）',
    'botOnboarding.modelPlaceholder': '留空使用该 CLI 的默认模型',
    'botOnboarding.startScan': '开始扫码',
    'botOnboarding.cancel': '取消',
    'botOnboarding.starting': '正在生成二维码...',
    'botOnboarding.waiting': '请用飞书 App 扫码确认创建应用。',
    'botOnboarding.verifying': '扫码成功，正在校验凭证...',
    'botOnboarding.configuringPermissions': '正在自动配置开放平台权限…',
    'botOnboarding.platformScanHint': '请用飞书 App 再扫一次码登录开放平台（用于自动导入权限、配置回调、提交版本）。',
    'botOnboarding.platformQrAlt': '开放平台登录二维码',
    'botOnboarding.completed': '机器人已添加。',
    'botOnboarding.permissionOk': '已自动导入 {count} 项权限。',
    'botOnboarding.permissionSkipped': '（跳过 {count} 项当前租户目录中没有的权限）',
    'botOnboarding.permissionVersion': '已提交发布版本 {version}。',
    'botOnboarding.permissionManual': '权限未能自动配置，请手动完成以下步骤：',
    'botOnboarding.metaDir': '目录',
    'botOnboarding.failed': '添加失败',
    'botOnboarding.openLink': '打不开扫码？在浏览器中打开',
    'botOnboarding.close': '关闭',
    'botOnboarding.restartHint': '已写入 bots.json。执行 pnpm daemon:restart 后新机器人生效。',
    'botOnboarding.qrAlt': '飞书扫码添加机器人二维码',
    'overview.title': '工作台',
    'overview.subtitle': '数字员工实时状态 · 同步飞书话题',
    'overview.team': 'AI 团队',
    'overview.teamHint': '每位数字员工的实时状态',
    'overview.attention': '需要你处理',
    'overview.attentionHint': '卡住的任务，回个话就能继续跑',
    'overview.activeSessions': '活跃会话',
    'overview.activeSessionsHint': '正在运转的会话',
    'overview.today': '此刻概览',
    'overview.todayHint': '活跃会话分布',
    'overview.viewAll': '查看全部 →',
    'overview.botIdle': '待命中，随时可接新任务',
    'overview.botOffline': '离线 — daemon 未上线',
    'overview.botBusy': '执行中 · {count} 会话',
    'overview.botNeedsYou': '等你回复',
    'overview.botReady': '就绪',
    'overview.botOff': '离线',
    'overview.sessionsCount': '{count} 会话',
    'overview.lastActive': '{time}前活跃',
    'overview.noAttention': '没有等你处理的事项',
    'overview.teamExpand': '展开全部 {count} 位 ▾',
    'overview.teamCollapse': '收起 ▴',
    'strip.pending': '{count} 件待处理',
    'strip.longest': '最久已等 {time} — {bot} {reason}',
    'strip.handle': '立即处理',
    'overview.openSessions': '活跃会话',
    'overview.workingSessions': '工作中',
    'overview.onlineBots': '在线 Bot',
    'overview.schedules': '定时任务',
    'overview.groups': '群聊覆盖',
    'overview.enabledSchedules': '已启用',
    'overview.total': '总计',
    'overview.active': '活跃',
    'overview.daemonRegistry': 'daemon 注册表',
    'overview.chatMatrix': '群聊矩阵',
    'overview.recentSessions': '最近会话',
    'overview.nextSchedules': '即将执行',
    'overview.noSessions': '暂无会话。',
    'overview.noSchedules': '暂无定时任务。',
    'sessions.title': '会话控制',
    'sessions.subtitle': '定位飞书话题、打开 Web Terminal、关闭或恢复 CLI 会话。',
    'sessions.search': '搜索工作目录 / 标题 / ID',
    'sessions.anyStatus': '全部状态',
    'sessions.adoptAny': 'adopt: 全部',
    'sessions.adoptYes': 'adopt: 是',
    'sessions.adoptNo': 'adopt: 否',
    'sessions.activeOnly': '仅活跃',
    'sessions.closeSelected': '关闭选中',
    'sessions.clearSelection': '取消选择',
    'sessions.selectedCount': '已选 {count} 个会话',
    'sessions.bot': 'bot',
    'sessions.cli': 'CLI',
    'sessions.chat': '群聊',
    'sessions.openChat': '打开群聊',
    'sessions.status': '状态',
    'sessions.tokenIn': 'Token In',
    'sessions.tokenOut': 'Token Out',
    'sessions.titleCol': '标题',
    'sessions.workingDir': '工作目录',
    'sessions.created': '创建',
    'sessions.last': '最近',
    'sessions.adopt': '接入',
    'sessions.actions': '操作',
    'sessions.details': '详情',
    'sessions.copy': '复制',
    'sessions.copied': '已复制',
    'sessions.locate': '定位话题',
    'sessions.locating': '发送中...',
    'sessions.cooldown': '冷却 {seconds}s',
    'sessions.openTerminal': '终端',
    'sessions.writeLink': '操作链接',
    'sessions.writeLinkHint': '获取可操作终端链接（带写权限 token，新标签打开）',
    'sessions.writeLinkFail': '获取操作链接失败',
    'sessions.close': '关闭会话',
    'sessions.resume': '恢复会话',
    'sessions.dismiss': '关闭',
    'sessions.closeConfirm': '关闭这个会话？',
    'sessions.resumeFailed': '恢复失败',
    'sessions.land': '落盘',
    'sessions.landLoading': '加载沙盒 diff…',
    'sessions.landUnavailable': '无法落盘',
    'sessions.landEmpty': '沙盒副本没有改动。',
    'sessions.landApply': '应用到磁盘',
    'sessions.landDiscard': '丢弃',
    'sessions.landApplied': '已落盘',
    'sessions.landFailed': '落盘失败',
    'sessions.landDiscarded': '已丢弃（沙盒副本未应用）。',
    'connectors.lede': '让外部系统（监控告警、CI、工单…）通过一个 webhook 触发机器人在群里说话或跑工作流。',
    'connectors.createTitle': '新建 Webhook',
    'connectors.fName': '名称',
    'connectors.fNamePh': '如：线上告警',
    'connectors.fBot': '触发的机器人',
    'connectors.fKind': '触发方式',
    'connectors.kindTurn': '单轮对话（让机器人回应一次）',
    'connectors.kindWorkflow': '工作流',
    'connectors.fWf': '工作流 ID',
    'connectors.fMode': '投递到哪个群',
    'connectors.modeDynamic': '由请求指定（群随请求传入）',
    'connectors.modeFixed': '固定群',
    'connectors.modeNewGroup': '每次新建群',
    'connectors.fFixedChat': '投递到的群',
    'connectors.fChatManualPh': '手动填群 ID：oc_…',
    'connectors.chatManualLink': '找不到群？手动填 ID →',
    'connectors.chatListLink': '从群列表选择 ←',
    'connectors.fAllow': '允许的群',
    'connectors.optional': '（可选）',
    'connectors.allowHint': '按住 Ctrl/⌘ 多选；留空 = 不限。只用于校验请求传入的群是否被允许。',
    'connectors.dynamicHint': '<b>动态模式</b>：群 ID 随每次请求传入，三选一 —— 查询参数 <code>?chatId=&lt;群ID&gt;</code> · 请求头 <code>x-botmux-chat-id: &lt;群ID&gt;</code> · 请求体 <code>{"chatId":"&lt;群ID&gt;"}</code>。<br>想"一个 URL 直接触发、不带参数"，请改选「固定群」。',
    'connectors.fDedup': '去重字段',
    'connectors.fDedupPh': '如 alert.id（从事件 body 取值）',
    'connectors.dedupHint': '填了：命中相同值的事件都投到<b>同一个群</b>。留空：每个事件<b>新建一个群</b>。',
    'connectors.fInstruction': '处理指令',
    'connectors.fInstructionPh': '事件触发时让机器人做什么。如：总结这条告警的严重程度，@相关 oncall，给出排查建议。留空 = 只把事件原样交给模型自由发挥。',
    'connectors.fVerify': '校验方式',
    'connectors.verifyToken': '令牌（简单：密钥放进 URL，一条 curl 就能触发）',
    'connectors.verifyHmac': 'HMAC 签名（高级：更安全，需自行对请求签名）',
    'connectors.fSecret': '密钥 / 令牌',
    'connectors.fSecretPh': '留空自动生成（只显示一次）',
    'connectors.btnCreate': '创建',
    'connectors.listTitle': '已有 Webhook',
    'connectors.loading': '加载中…',
    'connectors.noBotGroups': '（该机器人暂无可见群，点右侧手动填 ID）',
    'connectors.modeLabelFixed': '固定群',
    'connectors.modeLabelNewGroup': '每次新建群',
    'connectors.modeLabelDynamic': '请求指定群',
    'connectors.kindLabelWorkflow': '工作流',
    'connectors.kindLabelTurn': '单轮',
    'connectors.count': '· {count} 个',
    'connectors.empty': '还没有 Webhook。用上面的表单创建一个。',
    'connectors.enabled': '已启用',
    'connectors.disabled': '已停用',
    'connectors.badgeToken': '令牌',
    'connectors.badgeSign': '签名',
    'connectors.dest': '投递「{name}」',
    'connectors.btnDisable': '停用',
    'connectors.btnEnable': '启用',
    'connectors.btnDel': '删除',
    'connectors.webhookUrl': 'Webhook URL：',
    'connectors.copy': '复制',
    'connectors.copied': '已复制',
    'connectors.tokenHint': '令牌模式：调用时在 URL 末尾追加 <code>/&lt;令牌&gt;</code>（令牌仅创建/轮换时显示一次）。',
    'connectors.dynamicReqHint': '动态模式：请求需带目标群 —— <code>?chatId=&lt;群ID&gt;</code> 或头 <code>x-botmux-chat-id</code> 或 body <code>{"chatId":"…"}</code>。',
    'connectors.instructionPrefix': '处理指令：',
    'connectors.delConfirm': '删除这个 Webhook？它的 URL 会立即失效。',
    'connectors.errName': '请填名称',
    'connectors.errBot': '请选机器人',
    'connectors.errWf': '请填工作流 ID',
    'connectors.errChat': '请选择（或手动填）投递的群',
    'connectors.creating': '创建中…',
    'connectors.usageDynamicLede': '动态模式：URL 已含令牌，调用时再带上目标群 ID{gn}：',
    'connectors.usageDynamicNote': '群也可放请求头 <code>x-botmux-chat-id</code> 或 body <code>{"chatId":"…"}</code>。⚠️ URL 即凭证，勿泄漏。',
    'connectors.usageTokenLede': '此 URL 已含令牌、且固定投递到所选群，直接 POST 即可触发：',
    'connectors.usageTokenNote': '⚠️ URL 即凭证，请勿公开泄漏；可在下方列表删除或轮换。',
    'connectors.usageHmac': '外部系统需对 <code>timestamp.body</code> 做 HMAC-SHA256 签名，并带上 <code>x-botmux-timestamp</code> / <code>x-botmux-nonce</code> / <code>x-botmux-signature</code> 头调用',
    'connectors.usageHmacDynamic': '，同时按上面方式带目标群 ID',
    'connectors.createdPrefix': '已创建「{name}」',
    'connectors.createdDest': '投递到「{name}」',
    'connectors.tokenLabel': '访问令牌',
    'connectors.signLabel': '签名密钥',
    'connectors.secretOnce': '（只显示这一次，请保存）：',
    'connectors.createFailed': '创建失败：{error}',
    'connectors.noOnlineBots': '（没有在线机器人）',
    'team.navHome': '我的团队',
    'team.navManage': '团队管理',
    'team.eyebrow': '团队',
    'team.homeTitle': '团队协作（跨部署）',
    'team.homeLede': '把别的部署（同事自己跑的 botmux）邀请进同一个团队，互相发现机器人、协作拉群。',
    'team.localDeployTitle': '本部署',
    'team.myIdentity': '我的飞书身份：',
    'team.unbound': '未绑定',
    'team.bindBtn': '绑定',
    'team.bindHint': '（用机器人凭证自动识别你；绑定后拉群会把你拉进群、机器人也归到你名下）',
    'team.myTeams': '我的团队',
    'team.searchPh': '搜索 名称/能力/CLI…',
    'team.allCli': '全部 CLI',
    'team.hasCap': '有能力标签',
    'team.hasRole': '有默认角色',
    'team.teamsHint': '每个团队里勾选机器人即可单独拉群（自动带上各自负责人）。要新建团队 / 生成邀请码 / 加入别人的团队，去「团队管理」。',
    'team.loading': '加载中…',
    'team.roleModalTitle': '默认角色',
    'team.roleModalHint': '该机器人的默认人设（跨群生效），此处只读。如需修改，请到「Bot 配置」页。',
    'team.close': '关闭',
    'team.tagLocal': '本部署',
    'team.tagRemoteStale': '远端·离线？',
    'team.tagRemote': '远端',
    'team.removeMember': '移除',
    'team.depTag': '（{tag}）',
    'team.depCount': '{count} 个',
    'team.depSelected': '，已选 {n}',
    'team.capPh': '能力标签…',
    'team.viewRole': '查看',
    'team.hasRoleShort': '有角色',
    'team.noMatch': '没有符合条件的机器人。',
    'team.gnamePh': '群名（如：跨团队排障）',
    'team.pullGroupBtn': '把勾选的机器人拉一个群',
    'team.pullGroupHint': '勾选机器人 → 拉到一个飞书群（自动含 owner）',
    'team.noTeams': '还没有团队。去「团队管理」生成邀请码让别人加入你，或加入别人的团队。',
    'team.connected': '已连接',
    'team.connectFail': '连接失败：{error}',
    'team.iHost': '我托管',
    'team.teamMeta': '{deps} 个部署 · {bots} 个机器人',
    'team.rosterFail': '无法获取该团队花名册。',
    'team.acrossTeams': '（跨 {n} 个团队，去重）',
    'team.botsWord': '个机器人',
    'team.groupCreated': '群已创建',
    'team.delegatedBy': '（由「{name}」建群）',
    'team.openInLark': '在飞书打开',
    'team.invalidBots': '未加入的机器人：{ids}',
    'team.invalidOwners': '{n} 个 owner 未能拉进',
    'team.missingIdentity': '你未绑定飞书身份，没把你自己拉进群（去「我的团队」绑定）',
    'team.skippedNoOwner': '{n} 个机器人因负责人未绑定身份被跳过（未加入群）',
    'team.errNoLocalBot': '请至少勾选一个你自己（本部署）的在线机器人——群要由它创建并把你（发起人）拉进群。',
    'team.errAllSkipped': '所选机器人的负责人都没绑定身份，无法拉群（机器人不能进一个 owner 不在的群）。请让对应部署先绑定身份。',
    'team.errNoCreator': '没有可用的建群发起方（相关部署都没有在线机器人，或不可达）',
    'team.errDelegationTimeout': '委托对方部署建群超时（可能已建，去飞书确认，勿重复点）',
    'team.errGroupCreate': '建群失败：{error}',
    'team.roleModalTitleName': '默认角色 · {name}',
    'team.bound': '已绑定',
    'team.myHostedTeam': '我托管的团队',
    'team.remoteTeamLabel': '{name} 的团队',
    'team.manageTitle': '团队管理',
    'team.manageLede': '创建多个团队、给每个团队生成邀请码、或加入别人的团队。一个团队 = 你本部署的机器人 + 加入该团队的其它部署。',
    'team.hostedTitle': '我托管的团队',
    'team.newTeamPh': '新团队名称',
    'team.createTeamBtn': '创建团队',
    'team.joinTitle': '加入别人的团队',
    'team.hubPh': 'Hub 地址，如 http://10.0.0.5:7891',
    'team.codePh': '邀请码',
    'team.joinBtn': '加入',
    'team.noTeamsShort': '还没有团队。',
    'team.default': '默认',
    'team.manageMetaDeps': '{count} 个部署',
    'team.manageMetaRemote': '（含 {r} 远端）',
    'team.manageMetaBots': '{count} 个机器人',
    'team.genInvite': '生成邀请码',
    'team.delBtn': '删除',
    'team.generating': '生成中…',
    'team.inviteResultLede': '把下面两项发给别的部署的人（24 小时内、单次有效）：',
    'team.inviteHub': 'Hub 地址：',
    'team.inviteCode': '邀请码：',
    'team.genFail': '生成失败。',
    'team.delConfirm': '删除团队「{name}」？已加入它的部署会被移除（不影响他们自己的部署）。',
    'team.errName': '请填团队名称',
    'team.creating': '创建中…',
    'team.created': '已创建',
    'team.createFail': '创建失败：{error}',
    'team.errHubCode': '请填 Hub 地址和邀请码。',
    'team.joining': '加入中…',
    'team.joined': '已加入「{name}」，去「我的团队」查看。',
    'team.joinErrSelf': '这是你自己的部署，不能加入自己（邀请码要发给别的部署的人用）',
    'team.joinErrAlready': '你的部署已经加入过这个团队了',
    'team.joinErrUnreachable': '连不上对方 Hub（检查地址/网络）',
    'team.joinErrTimeout': '对方 Hub 响应超时',
    'team.joinErrGeneric': '加入失败：{error}',
    'team.identifying': '识别中…',
    'team.bound2': '已绑定：{name}',
    'team.multiCandidate': '识别到多个候选，点你自己：',
    'team.binding': '绑定中…',
    'team.bindFail': '绑定失败：{error}',
    'team.noCandidates': '没识别到身份：请确认机器人配置了 allowedUsers（允许使用者），且机器人有通讯录权限。',
    'team.removeMemberConfirm': '把「{name}」移出这个团队？它的机器人将从本团队花名册消失（不影响对方自己的部署）。',
    'team.creatingGroup': '建群中…',
    'team.defaultGroupName': '协作群',
    'team.errPickBot': '请先勾选至少一个机器人',
    'sessions.closeBulkConfirm': '关闭选中的 {count} 个会话？',
    'sessions.empty': '没有符合条件的会话。',
    'sessions.viewMode': '会话视图',
    'sessions.viewKanban': '看板',
    'sessions.viewBoard': '状态板',
    'sessions.viewTable': '表格',
    'sessions.selectSession': '选择会话',
    'sessions.board.needsYou': '需要你',
    'sessions.board.needsYouHint': '等待仓库、TUI 选择或额度处理',
    'sessions.board.starting': '启动中',
    'sessions.board.startingHint': 'CLI 正在创建或恢复',
    'sessions.board.working': '干活中',
    'sessions.board.workingHint': '正在输出、分析或执行工具',
    'sessions.board.idle': '空闲',
    'sessions.board.idleHint': '可继续对话或接新任务',
    'sessions.board.emptyColumn': '暂无会话',
    'sessions.board.signalRepo': '待选仓库',
    'sessions.board.signalPrompt': '等待 TUI 选择',
    'sessions.board.signalLimited': '额度受限',
    'sessions.board.signalAgent': '需要人工介入',
    'sessions.board.waiting': '等待',
    'sessions.board.dragHint': '拖动列头调整列顺序',
    'sessions.board.moveLeft': '左移此列',
    'sessions.board.moveRight': '右移此列',
    'sessions.kanban.backlog': '待办池',
    'sessions.kanban.todo': '待办',
    'sessions.kanban.inProgress': '进行中',
    'sessions.kanban.inReview': '待确认',
    'sessions.kanban.done': '已完成',
    'sessions.kanban.updated': '更新于 {time}',
    'sessions.kanban.moreHidden': '还有 {count} 个未显示',
    'sessions.kanban.openTab': '新标签页打开',
    'sessions.kanban.openFeishu': '打开飞书',
    'sessions.kanban.terminalLoading': '正在打开终端…',
    'sessions.kanban.rename': '重命名（双击标题也可）',
    'sessions.kanban.renameFail': '重命名失败',
    'sessions.kanban.moveFail': '移动失败',
    'sessions.kanban.groupBy': '分组维度',
    'sessions.kanban.groupFlow': '工作流',
    'sessions.kanban.groupTeam': '团队',
    'sessions.kanban.groupBot': '机器人',
    'sessions.kanban.teamLoading': '正在加载团队…',
    'sessions.kanban.noTeam': '暂无团队',
    'sessions.kanban.teamScope': '{chats} 个协作群 · {sessions} 个会话',
    'sessions.kanban.remoteHint': '来自 {name} 部署的会话（快照，终端/历史在对方机器上）',
    'sessions.kanban.clusterDragHint': '拖动标题整组移动',
    'sessions.history.title': '会话历史',
    'sessions.history.loading': '正在拉取飞书消息…',
    'sessions.history.fail': '历史拉取失败',
    'sessions.history.empty': '没有消息',
    'sessions.history.user': '用户',
    'sessions.history.owner': '创建者',
    'sessions.history.staleHint': 'dashboard 或 daemon 进程可能还在跑旧版本，botmux restart 后重试',
    'groups.title': '群组与 Bot',
    'groups.subtitle': '查看 chat x bot 覆盖矩阵，管理拉群、oncall、退群和解散。',
    'groups.search': '搜索群名 / ID / owner',
    'groups.missingOnly': '仅缺 bot',
    'groups.refresh': '刷新',
    'groups.create': '新建群',
    'groups.chat': '群聊',
    'groups.actions': '操作',
    'groups.addBots': '添加 bot',
    'groups.manage': '管理',
    'groups.empty': '没有符合条件的群聊。',
    'groups.createTitle': '新建群聊',
    'groups.createHelp': '选择要邀请的 bot。dashboard 会自动选择在线 daemon 作为创建者。',
    'groups.name': '群名',
    'groups.namePlaceholder': '例如 AI ChangeLog',
    'groups.bindDir': '绑定目录',
    'groups.bindDirHelp': '新话题直接用该目录启动 CLI，跳过 repo 选择。',
    'groups.botPicker': 'Bot',
    'groups.createSubmit': '创建',
    'groups.cancel': '取消',
    'groups.successTitle': '群创建成功',
    'groups.openGroup': '打开新群',
    'groups.manageTitle': '管理 {name}',
    'groups.owner': '群主',
    'groups.oncall': 'Oncall 模式',
    'groups.oncallHelp': '开启后，群内成员可 @ 机器人；新话题直接使用绑定目录。',
    'groups.leaveTitle': '选择机器人退出群聊',
    'groups.leaveSelected': '选中机器人退出群聊',
    'groups.disband': '解散群聊',
    'groups.dangerHint': '解散仅当在群机器人是群主时才会成功，否则建议使用退出群聊。',
    'groups.save': '保存',
    'groups.needWorkingDir': '请填工作目录',
    'groups.noBotsOnline': '没有在线 bot。请先重启 daemon。',
    'schedules.title': '定时任务',
    'schedules.subtitle': '跨 daemon 查看、暂停、恢复和立即触发定时任务。',
    'schedules.search': '搜索名称 / prompt / 工作目录',
    'schedules.anyKind': '全部类型',
    'schedules.enabledOnly': '仅启用',
    'schedules.name': '名称',
    'schedules.bot': 'bot',
    'schedules.schedule': '规则',
    'schedules.next': '下次',
    'schedules.last': '上次',
    'schedules.repeat': '重复',
    'schedules.enabled': '启用',
    'schedules.actions': '操作',
    'schedules.runNow': '立即运行',
    'schedules.pause': '暂停',
    'schedules.resume': '恢复',
    'schedules.empty': '暂无定时任务。',
    'settings.title': '全局设置',
    'settings.subtitle': '管理本机 botmux dashboard 与所有 bot 共享的全局行为。',
    'settings.loading': '正在加载设置…',
    'settings.loadFailed': '设置加载失败',
    'settings.readOnlyVisitor': '当前是只读访问，修改设置需要授权链接（运行 botmux dashboard 获取）',
    'settings.sectionAccess': '访问权限',
    'settings.publicReadOnly': '允许无 token 只读访问 dashboard',
    'settings.publicReadOnlyHelp': '开启后，没有 token 的访问者可以查看 dashboard、会话、定时任务和 SSE；关闭、暂停、审批等写操作仍必须通过 token 鉴权。敏感终端原始日志始终需要 token。',
    'settings.sectionCards': '飞书卡片',
    'settings.openTerminalInFeishu': '流式卡片终端按钮在飞书侧栏打开',
    'settings.openTerminalInFeishuHelp': '默认关闭：终端按钮直接打开 Web Terminal URL。开启后使用飞书 web_url/open 包装，在飞书 PC 侧栏里打开；写权限仍由终端 token 控制。',
    'settings.sectionRepoPicker': '仓库选择',
    'settings.repoPickerMode': '仓库选择卡模式',
    'settings.repoPickerModeAll': '仓库 + worktree',
    'settings.repoPickerModeRepos': '仅主仓库',
    'settings.repoPickerModeHelp': '默认显示仓库和 linked worktree。选择「仅主仓库」后，初始化和裸 /repo 的选择卡只列主仓库；显式 /repo /abs/path/to/worktree 仍可直达 worktree。',
    'settings.saving': '保存中…',
    'settings.saved': '已保存',
    'settings.saveFailed': '保存失败',
    'settings.sectionMaintenance': '自动维护',
    'settings.autoUpdate': '自动更新',
    'settings.autoUpdateHelp': '默认关闭。到点执行 npm install -g botmux@latest 安装最新版本（只下载安装、本身不重启）。仅 npm 全局安装可用。',
    'settings.autoRestart': '自动重启',
    'settings.autoRestartHelp': '默认关闭，需先开启自动更新。自动更新装到新版本后，若没有进行中的会话则自动重启以生效（撞上忙碌会话则跳到次日）。',
    'settings.autoUpdateLocalDev': '当前为本地开发安装（从源码运行），自动更新不可用。',
    'settings.maintenanceTime': '时间',
    'botDefaults.title': '数字员工档案',
    'botDefaults.subtitle': '每位员工的默认行为：oncall、主动开工、人设角色、卡片与授权。',
    'botDefaults.metaOnline': '在线 · daemon 正常',
    'botDefaults.search': '搜索 bot 名 / app id',
    'botDefaults.refresh': '刷新',
    'botDefaults.sectionOncall': '新群 Oncall',
    'botDefaults.sectionBrand': '卡片签名',
    'botDefaults.warning': '开启后，没有 oncall binding 的群会在下次开新话题时自动绑定到该目录；手动绑定或手动解绑过的群不会被覆盖。',
    'botDefaults.empty': '没有在线 bot。先 botmux restart 让 daemon 上线。',
    'botDefaults.defaultOncall': '默认进入 oncall 模式',
    'botDefaults.defaultOncallHelp': '所有未绑定的群下次开话题自动绑定',
    'botDefaults.workingDir': '默认工作目录',
    'botDefaults.lastEnabled': '上次启用时间',
    'botDefaults.autobound': '已自动绑定 {count} 个群',
    'botDefaults.save': '保存',
    'botDefaults.required': '开启时必须填工作目录',
    'botDefaults.brandLabel': '个性签名（卡片页脚）',
    'botDefaults.brandLabelHelp': '该 bot 发出的卡片页脚签名。留空保存＝不显示；填写＝自定义（支持 markdown，如 [Acme](https://…)）；恢复默认＝显示 botmux。',
    'botDefaults.brandLabelPlaceholder': '默认：botmux（留空则不显示）',
    'botDefaults.sectionSandbox': '文件沙盒（oncall）',
    'botDefaults.sandboxToggle': '开启文件沙盒',
    'botDefaults.sandboxHelp': '开启后，该 bot 的每个会话都跑在按会话隔离的沙盒里：只看得到一份项目副本，碰不到你磁盘上的真实文件、密钥、别的会话数据。适合把机器人分享给半受信任的人（oncall）。仅 Linux 生效，下个新会话起效。',
    'botDefaults.sandboxSaved': '已保存（下个新会话生效）',
    'botDefaults.brandSave': '保存签名',
    'botDefaults.brandReset': '恢复默认',
    'botDefaults.brandStateDefault': '当前：默认 botmux',
    'botDefaults.brandStateOff': '当前：已关闭',
    'botDefaults.brandStateCustom': '当前：自定义',
    'botDefaults.sectionCard': '卡片行为',
    'botDefaults.disableStreaming': '关闭飞书流式卡片',
    'botDefaults.disableStreamingHelp': '不再发实时刷新的会话状态卡（含「打开终端」入口）；任务最终结果仍通过消息送达。适合嫌流式卡片烦的场景。',
    'botDefaults.writableLink': '卡片上直接给可操作（可写）终端链接',
    'botDefaults.writableLinkHelp': '⚠️ 在流式卡片正文直接贴出可写终端链接，群内任何人都能点开并操控终端。不勾＝保持现状（走「获取操作链接」按钮私聊发给点击者）。',
    'botDefaults.writableLinkMoot': '已关闭流式卡片',
    'botDefaults.privateCard': '/card 发私密卡片（仅授权人可见）',
    'botDefaults.privateCardHelp': '开启后 /card 改用「仅特定人可见」的临时卡片：只发给 owner（allowedUsers），/grant 授权对话的人和群里其他人都看不到。代价：是静态快照、不会实时刷新；且仅普通群可用（话题群 / 单聊会失败，不降级）。只影响 /card 命令，自动流式卡不变。',
    'botDefaults.cardPrefSaved': '已保存',
    'botDefaults.sectionRole': '默认角色',
    'botDefaults.roleHelp': '该 bot 的默认人设（跨群生效），会注入到该 bot 在各群的会话里；单个群可在「角色」页单独覆盖。留空保存＝删除。',
    'botDefaults.rolePlaceholder': '例如：你是后端排障助手，回答简洁、优先给可执行命令…',
    'botDefaults.roleSave': '保存角色',
    'botDefaults.roleDelete': '删除',
    'botDefaults.roleSaved': '已保存',
    'botDefaults.roleDeleted': '已删除',
    'botDefaults.roleLoadErr': '角色加载失败',
    'botDefaults.sectionAutoStart': '主动开工',
    'botDefaults.autoStartJoin': '被拉进新群自动开工',
    'botDefaults.autoStartJoinHelp': '开启后，机器人一被拉进新群（群里有授权用户 allowedUsers 时）就自动开一个会话开始工作，无需 @。在机器人的默认工作目录启动；未配置默认目录时先弹仓库选择卡让你选。前提：需在飞书开放平台为本应用订阅「机器人进群」事件 im.chat.member.bot.added_v1，并开通群成员读取权限。',
    'botDefaults.autoStartJoinPrompt': '入群首轮 prompt（可选）',
    'botDefaults.autoStartJoinPromptPlaceholder': '填了＝作为入群后的第一条任务；留空＝机器人自己看群里信息决定做什么',
    'botDefaults.autoStartJoinPromptSave': '保存 prompt',
    'botDefaults.autoStartTopic': '话题群新话题自动开工',
    'botDefaults.autoStartTopicHelp': '开启后，在话题群里每当有人新开一个话题，机器人就会自动接入该话题、把首条消息当作任务开始处理，无需 @。仅对话题群生效，普通群不受影响。',
    'botDefaults.sectionSessionMode': '会话模式',
    'botDefaults.p2pMode': '私聊会话模式',
    'botDefaults.p2pThread': 'thread（每条 DM 独立会话）',
    'botDefaults.p2pChat': 'chat（扁平连续单聊会话）',
    'botDefaults.p2pHelp': '私聊（1:1 DM）的会话方式：thread = 每条顶层消息各自起独立会话（避免把对话堆进同一个 CLI 进程）；chat = 整段 DM 共用一个连续会话、共享上下文（类 Hermes）。改后立即生效。',
    'botDefaults.regularGroupMode': '普通群会话模式',
    'botDefaults.regularGroupModeChat': 'chat（扁平连续单聊会话，默认）',
    'botDefaults.regularGroupModeNewTopic': 'new-topic（每条顶层 @ 各开独立话题与会话）',
    'botDefaults.regularGroupModeShared': 'shared（话题模式但复用同一个 session）',
    'botDefaults.regularGroupModeHelp': '普通群（非话题群）里 @ 该 bot 的新顶层消息怎么开会话：chat = 整群共用一个连续会话（默认）；new-topic = 每条顶层 @ 在原消息下开独立话题、各自独立会话；shared = 话题模式但复用同一个 session（回复落在话题里、但共用本群会话与上下文）。这是 per-bot 默认，具体某个群可用 /reply-mode 单独覆盖。改后立即生效。',
    'botDefaults.mentionMode': '群聊 @ 策略',
    'botDefaults.mentionModeAlways': '都需要 @（默认）',
    'botDefaults.mentionModeTopic': '仅话题内不需要 @',
    'botDefaults.mentionModeNever': '所有消息都不需要 @',
    'botDefaults.mentionModeHelp': '该 bot 全局生效，决定群里要不要 @ 才回：「都需要 @」= 任何消息都得 @（默认，最安全；多人群里话题内也要 @）；「仅话题内不需要 @」= 起新对话 / 顶层仍要 @，但在该 bot 已开的任何话题里（new-topic / shared / 话题群）后续消息免 @ 续聊；「所有消息都不需要 @」= 该 bot 有对话权限的群里非 @ 消息也回（含全新消息冷启动，仅适合专用 / 值班小群，多人群里会见消息就回）。注：群里只有你和该 bot（1 对 1）时一直免 @，与本设置无关。',
    'botDefaults.docSubscribeMode': '文档订阅触发范围（默认）',
    'botDefaults.docSubscribeModeMention': '仅评论 @ 我才触发',
    'botDefaults.docSubscribeModeAll': '所有新评论都触发',
    'botDefaults.docSubscribeModeHelp': '用 /subscribe-lark-doc 订阅飞书文档后，文档评论会喂进会话、bot 回复发回评论。这里设新订阅的默认触发范围：「仅评论 @ 我」最安全（防刷屏）；「所有新评论」适合专用文档。',
    'botDefaults.sectionGrant': '授权与额度',
    'botDefaults.restrictGrant': '限制被授权人只能纯对话',
    'botDefaults.restrictGrantHelp': '开启后，被 /grant 授权的人（owner 自己不受限）只能发普通对话，所有 slash 命令一律拦截：botmux 自带命令、透传命令、/workflow、/introduce、/t 以及 CLI 原生命令（/help 等）。形如 /path/to/file 的内容不会被误判。',
    'botDefaults.quotaDefault': '默认消息额度',
    'botDefaults.quotaPlaceholder': '留空＝不配置默认额度',
    'botDefaults.quotaHelp': '不带数字的 /grant @x 默认给的消息条数。留空或点「关闭」只是不再给裸 /grant 套默认额度，不会清掉已有的额度计数，也不影响显式 /grant @x N——它们照常继续 enforce。额度用尽会自动撤销该人授权。',
    'botDefaults.quotaSave': '保存额度',
    'botDefaults.quotaOff': '关闭',
    'botDefaults.quotaInvalid': '额度必须是正整数',
    'botDefaults.quotaStateOff': '当前：未配置默认额度',
    'botDefaults.quotaStateOn': '当前：每人 {count} 条',
    'nav.roles': '角色管理',
    'roles.title': '角色管理',
    'roles.subtitle': '为每个群组的每个 Bot 单独设置角色提示词，Bot 在该群中会以此角色行事。',
    'roles.search': '搜索群名/Bot/ID',
    'roles.refresh': '刷新',
    'roles.selectHint': '← 展开群组，选择一个 Bot 来编辑角色',
    'roles.editorPlaceholder': '输入角色描述，例如：\n你是本群的技术顾问，负责回答所有技术问题...',
    'roles.configured': '已配置',
    'roles.unconfigured': '未配置',
    'roles.noChats': '暂无群组',
    'roles.preview': '预览',
    'roles.previewEmpty': '（空内容）',
    'roles.saved': '已保存',
    'roles.delete': '删除',
    'roles.save': '保存',
    'roles.confirmDelete': '确认删除该 Bot 在此群的角色配置？',
    'roles.botsWithRoles': '个 Bot 已配置角色',
    'roles.emptyError': '角色内容不能为空，请先输入内容',
    'roles.saveFailed': '保存失败，请重试',
    'common.none': '无',
    'common.loading': '加载中…',
    'common.unknown': '未知',
    'common.now': '刚刚',
    'common.never': '从未',
    'common.all': '全部',
    'nav.workflows': '工作流(beta)',
    'nav.workflowCatalog': '目录',
    'workflow.subnav.runs': '运行',
    'workflow.subnav.catalog': '目录',
    'workflow.searchPlaceholder': '搜索 runId / workflowId / chatId',
    'workflow.filter.nonTerminal': '非终态',
    'workflow.filter.all': '全部',
    'workflow.status.pending': '待开始',
    'workflow.status.running': '运行中',
    'workflow.status.waiting': '等待中',
    'workflow.status.effectAttempting': '副作用中',
    'workflow.status.timedOut': '已超时',
    'workflow.status.succeeded': '成功',
    'workflow.status.failed': '失败',
    'workflow.status.cancelled': '已取消',
    'workflow.table.run': '运行',
    'workflow.table.workflow': '工作流',
    'workflow.table.status': '状态',
    'workflow.table.lastSeq': '最后序号',
    'workflow.table.dangling': '悬挂 dEf/dAct/dWait',
    'workflow.table.updated': '更新时间',
    'workflow.table.chatApp': '群聊 / 应用',
    'workflow.list.failedLoad': '加载失败：{error}',
    'workflow.list.noRuns': '没有匹配的运行。',
    'workflow.list.noFilterMatch': '没有符合筛选条件的运行。',
    'workflow.list.loaded': '{count} 个运行 · 刷新于 {time}',
    'workflow.list.error': '错误：{error}',
    'workflow.detail.back': '返回',
    'workflow.detail.loading': '加载中...',
    'workflow.detail.loadFailed': '加载失败',
    'workflow.detail.cancel': '取消',
    'workflow.detail.cliCancelOnly': '仅 CLI 可取消',
    'workflow.detail.cancelTitle': '取消这个工作流运行',
    'workflow.detail.cliCancelTitle': '无法在页面取消：请使用 botmux workflow cancel {runId}',
    'workflow.detail.nodes': '节点 / Activity',
    'workflow.detail.parallel': '并发执行',
    'workflow.detail.parallelMeta': '{count} 次尝试 · 最高并发 {max} · 运行中 {running}',
    'workflow.detail.noParallelData': '还没有 attempt 时间数据。',
    'workflow.detail.parallelNow': '现在',
    'workflow.detail.node': '节点',
    'workflow.detail.nodeStatus': '节点状态',
    'workflow.detail.activity': 'Activity',
    'workflow.detail.activityStatus': 'Activity 状态',
    'workflow.detail.attempts': '尝试次数',
    'workflow.detail.current': '当前尝试',
    'workflow.detail.detail': '详情',
    'workflow.detail.nodeIO': '节点输入输出',
    'workflow.detail.timeline': '时间线',
    'workflow.detail.loadOlder': '加载更早事件',
    'workflow.detail.seq': '序号',
    'workflow.detail.actor': '执行者',
    'workflow.detail.error': '错误',
    'workflow.detail.event': '事件',
    'workflow.detail.time': '时间',
    'workflow.detail.refreshed': '刷新于 {time}',
    'workflow.detail.unknownRun': '未知运行',
    'workflow.detail.snapshotHttp': 'snapshot HTTP {status}',
    'workflow.detail.eventsHttp': 'events HTTP {status}',
    'workflow.detail.cancelUnavailable': '无法取消：请使用 botmux workflow cancel {runId}',
    'workflow.detail.cancelConfirm': '确认取消工作流运行 {runId}？\n\n{total} 个悬挂项会由 cancel recovery 处理。\neffects={effects}, activities={activities}, waits={waits}, cancels={cancels}',
    'workflow.detail.writeAccessCancel': '需要写权限：请在终端运行 `botmux dashboard` 获取一次性 URL，打开后写入 cookie，再回来点击取消。',
    'workflow.detail.cancelHttp': 'cancel HTTP {status}',
    'workflow.detail.cancelPending': '取消已提交；等待运行中的 activity 收敛',
    'workflow.detail.writeAccessApproval': '需要写权限：请在终端运行 `botmux dashboard` 获取一次性 URL，打开后写入 cookie，再回来审批。',
    'workflow.detail.actionHttp': '{action} HTTP {status}',
    'workflow.detail.approved': '已通过',
    'workflow.detail.rejected': '已拒绝',
    'workflow.detail.alreadyTerminal': '运行已终态；未应用“{label}”。',
    'workflow.detail.workflowContinue': '{label}；等待工作流继续执行。',
    'workflow.detail.workflowRefreshing': '{label}；正在刷新工作流状态。',
    'workflow.detail.eventsLoaded': '已加载 {loaded}/{total} 个事件',
    'workflow.detail.dangling': '悬挂项',
    'workflow.detail.noDangling': '没有悬挂工作。',
    'workflow.detail.none': '无',
    'workflow.detail.noNodes': '还没有节点。',
    'workflow.detail.idle': '空闲',
    'workflow.detail.noNodeIO': '还没有节点输入输出。',
    'workflow.detail.notDispatched': '尚未派发',
    'workflow.detail.noAttempt': '还没有尝试',
    'workflow.detail.attempt': '尝试',
    'workflow.detail.authoredInput': '原始输入',
    'workflow.detail.resolvedInput': '解析后输入',
    'workflow.detail.output': '输出',
    'workflow.detail.executionLog': '执行日志',
    'workflow.detail.liveTerminal': '实时终端',
    'workflow.detail.terminalLive': '在线',
    'workflow.detail.terminalClosedShort': '已关闭',
    'workflow.detail.terminalClosed': '终端已关闭。请查看下方执行日志获取最终记录。',
    'workflow.detail.openTerminalNewTab': '在新标签页打开终端',
    'workflow.detail.terminalReplay': '终端回放',
    'workflow.detail.openReplayNewTab': '在新标签页打开回放',
    'workflow.detail.downloadFullLog': '下载完整日志',
    'workflow.detail.terminalResume': '调试会话',
    'workflow.detail.openResumeNewTab': '在新标签页打开调试会话',
    'workflow.detail.resumeSession': '继续会话',
    'workflow.detail.resumeStarting': '正在启动…',
    'workflow.detail.endResumeSession': '结束调试会话',
    'workflow.detail.resumeEnding': '结束中…',
    'workflow.detail.resumeUnsupportedCli': '{cliId} CLI 不支持 resume',
    'workflow.detail.resumeMissingCliSession': '缺少 cliSessionId，无法 resume',
    'workflow.detail.resumeStartFailed': '启动调试会话失败 (HTTP {status})',
    'workflow.detail.resumeEndFailed': '结束调试会话失败 (HTTP {status})',
    'workflow.detail.writeAccessResume': '需要写入权限才能 resume 会话。',
    'workflow.detail.waitPrompt': '等待提示',
    'workflow.detail.approvalComment': '审批备注',
    'workflow.detail.optionalComment': '可选备注',
    'workflow.detail.approve': '通过',
    'workflow.detail.reject': '拒绝',
    'workflow.detail.submitting': '提交中...',
    'workflow.detail.empty': '空',
    'workflow.detail.truncated': '已截断',
    'workflow.detail.noData': '没有数据。',
    'workflow.detail.noPreview': '没有预览。',
    'workflow.detail.open': '打开',
    'workflow.detail.deadline': '截止',
    'workflow.detail.effect': '副作用',
    'workflow.detail.wait': '等待',
    'workflow.detail.noEvents': '还没有事件。',
    'workflow.summary.workflow': '工作流',
    'workflow.summary.status': '状态',
    'workflow.summary.lastSeq': '最后序号',
    'workflow.summary.updated': '更新时间',
    'workflow.summary.revision': '修订',
    'workflow.summary.initiator': '发起人',
    'workflow.summary.failedNode': '失败节点',
    'workflow.summary.cancelOrigin': '取消来源',
    'workflow.summary.chat': '群聊',
    'workflow.summary.app': '应用',
    'workflow.dangling.activities': 'Activities',
    'workflow.dangling.effects': 'Effects',
    'workflow.dangling.waits': 'Waits',
    'workflow.dangling.cancels': 'Cancels',
    'catalog.title': '工作流目录',
    'catalog.subtitle': '从已保存的 workflow 定义创建一次运行。',
    'catalog.searchPlaceholder': '搜索 workflowId / 路径',
    'catalog.refresh': '刷新',
    'catalog.loading': '正在加载目录...',
    'catalog.loadFailed': '目录加载失败：{error}',
    'catalog.noDefinitions': '没有找到 workflow 定义。',
    'catalog.noFilterMatch': '没有符合筛选条件的定义。',
    'catalog.table.workflow': '工作流',
    'catalog.table.version': '版本',
    'catalog.table.params': '参数',
    'catalog.table.nodes': '节点',
    'catalog.table.revision': '修订',
    'catalog.table.path': '路径',
    'catalog.paramSummary': '{required}/{total} 必填',
    'catalog.back': '返回目录',
    'catalog.detailTitle': '工作流定义',
    'catalog.definitionLoadFailed': '定义加载失败：{error}',
    'catalog.summary': '摘要',
    'catalog.paramsSchema': '参数 Schema',
    'catalog.definitionJson': '定义 JSON',
    'catalog.runPanel': '运行工作流',
    'catalog.paramsJson': '参数 JSON',
    'catalog.paramsPlaceholder': '{\n  \"city\": \"北京\"\n}',
    'catalog.chatId': '群聊 ID',
    'catalog.larkAppId': '飞书应用 ID',
    'catalog.chatBindingHint': '必填，用于确定 humanGate 卡片发送到哪个飞书群，以及取消路由归属。',
    'catalog.run': '运行',
    'catalog.running': '启动中...',
    'catalog.badParamsJson': '参数必须是 JSON object。',
    'catalog.writeAccess': '需要写权限：请在终端运行 `botmux dashboard` 获取一次性 URL，打开后写入 cookie，再回来运行。',
    'catalog.runHttp': 'run HTTP {status}',
    'catalog.runStarted': '运行已启动；正在打开详情页...',
    'catalog.invalidParams': '参数无效',
    'catalog.issue': '{path}: {message}',
    'catalog.noParams': '没有声明参数。',
    'catalog.required': '必填',
    'catalog.optional': '可选',
    'catalog.default': '默认值',
    'catalog.description': '说明',
    'catalog.path': '路径',
    'catalog.revision': '修订',
    'catalog.nodeCount': '节点数',
};
const en = {
    'app.name': 'botmux',
    'app.subtitle': 'Feishu AI CLI Control',
    'time.secondsAgo': '{value}s ago',
    'time.minutesAgo': '{value}m ago',
    'time.hoursAgo': '{value}h ago',
    'nav.overview': 'Overview',
    'nav.sessions': 'Sessions',
    'nav.sidebarCollapse': 'Collapse sidebar',
    'nav.sidebarExpand': 'Expand sidebar',
    'nav.groups': 'Groups',
    'nav.schedules': 'Schedules',
    'nav.settings': 'Settings',
    'nav.botDefaults': 'Bot Defaults',
    'status.live': 'Live',
    'status.disconnected': 'Disconnected',
    'status.system': 'System',
    'status.light': 'Light',
    'status.dark': 'Dark',
    'status.language': 'Language',
    'status.theme': 'Theme',
    'theme.base': 'Basic',
    'theme.skins': 'Skins',
    'skin.cyber': '2077',
    'skin.genshin': 'Genshin',
    'skin.fallout': 'Fallout',
    'skin.prts': 'PRTS',
    'skin.bluearchive': 'Blue Archive',
    'skin.zzz': 'ZZZ',
    'skin.dragonball': 'Dragon Ball',
    'skin.ikun': 'ikun',
    'botOnboarding.add': 'Add Bot',
    'botOnboarding.title': 'Add Bot',
    'botOnboarding.intro': 'Pick a CLI and working directory, scan to create a PersonalAgent app — the dashboard writes it to local bots.json and auto-configures Open Platform permissions.',
    'botOnboarding.cliLabel': 'CLI adapter',
    'botOnboarding.dirLabel': 'Working directory',
    'botOnboarding.dirPlaceholder': 'Default ~ (home); must exist on the daemon host',
    'botOnboarding.modelLabel': 'Model (optional)',
    'botOnboarding.modelPlaceholder': 'Leave empty for the CLI default model',
    'botOnboarding.startScan': 'Start scan',
    'botOnboarding.cancel': 'Cancel',
    'botOnboarding.starting': 'Generating QR code...',
    'botOnboarding.waiting': 'Scan with the Feishu app to create the app.',
    'botOnboarding.verifying': 'Scan accepted. Verifying credentials...',
    'botOnboarding.configuringPermissions': 'Auto-configuring Open Platform permissions…',
    'botOnboarding.platformScanHint': 'Scan once more with the Feishu app to sign in to the Open Platform (to auto-import permissions, set the callback, and submit a version).',
    'botOnboarding.platformQrAlt': 'Open Platform login QR code',
    'botOnboarding.completed': 'Bot added.',
    'botOnboarding.permissionOk': 'Auto-imported {count} permissions.',
    'botOnboarding.permissionSkipped': '({count} skipped — not in the tenant catalog)',
    'botOnboarding.permissionVersion': 'Submitted version {version}.',
    'botOnboarding.permissionManual': 'Permissions could not be auto-configured. Complete these steps manually:',
    'botOnboarding.metaDir': 'Dir',
    'botOnboarding.failed': 'Add failed',
    'botOnboarding.openLink': 'Open scan link in browser',
    'botOnboarding.close': 'Close',
    'botOnboarding.restartHint': 'bots.json has been updated. Run pnpm daemon:restart for the new bot to take effect.',
    'botOnboarding.qrAlt': 'Feishu bot onboarding QR code',
    'overview.title': 'Workbench',
    'overview.subtitle': 'Live status of your AI teammates · synced with Feishu topics',
    'overview.team': 'AI Team',
    'overview.teamHint': 'Live status of every digital teammate',
    'overview.attention': 'Needs You',
    'overview.attentionHint': 'Blocked tasks — one reply gets them moving again',
    'overview.activeSessions': 'Active Sessions',
    'overview.activeSessionsHint': 'Sessions currently running',
    'overview.today': 'Right Now',
    'overview.todayHint': 'Active session distribution',
    'overview.viewAll': 'View all →',
    'overview.botIdle': 'Standing by, ready for new tasks',
    'overview.botOffline': 'Offline — daemon not running',
    'overview.botBusy': 'Working · {count} sessions',
    'overview.botNeedsYou': 'Waiting on you',
    'overview.botReady': 'Ready',
    'overview.botOff': 'Offline',
    'overview.sessionsCount': '{count} sessions',
    'overview.lastActive': 'active {time} ago',
    'overview.noAttention': 'Nothing waiting on you',
    'overview.teamExpand': 'Show all {count} ▾',
    'overview.teamCollapse': 'Collapse ▴',
    'strip.pending': '{count} pending',
    'strip.longest': 'waiting {time} — {bot} {reason}',
    'strip.handle': 'Handle now',
    'overview.openSessions': 'Active Sessions',
    'overview.workingSessions': 'Working',
    'overview.onlineBots': 'Online Bots',
    'overview.schedules': 'Schedules',
    'overview.groups': 'Groups Seen',
    'overview.enabledSchedules': 'Enabled',
    'overview.total': 'total',
    'overview.active': 'active',
    'overview.daemonRegistry': 'daemon registry',
    'overview.chatMatrix': 'chat matrix',
    'overview.recentSessions': 'Recent Sessions',
    'overview.nextSchedules': 'Next Runs',
    'overview.noSessions': 'No sessions yet.',
    'overview.noSchedules': 'No schedules yet.',
    'sessions.title': 'Session Control',
    'sessions.subtitle': 'Locate Feishu topics, open Web Terminal, close or resume CLI sessions.',
    'sessions.search': 'Search working dir / title / IDs',
    'sessions.anyStatus': 'Any status',
    'sessions.adoptAny': 'adopt: any',
    'sessions.adoptYes': 'adopt: yes',
    'sessions.adoptNo': 'adopt: no',
    'sessions.activeOnly': 'Active only',
    'sessions.closeSelected': 'Close selected',
    'sessions.clearSelection': 'Clear',
    'sessions.selectedCount': '{count} sessions selected',
    'sessions.bot': 'Bot',
    'sessions.cli': 'CLI',
    'sessions.chat': 'Chat',
    'sessions.openChat': 'Open chat',
    'sessions.status': 'Status',
    'sessions.tokenIn': 'Token In',
    'sessions.tokenOut': 'Token Out',
    'sessions.titleCol': 'Title',
    'sessions.workingDir': 'Working Dir',
    'sessions.created': 'Created',
    'sessions.last': 'Last',
    'sessions.adopt': 'Adopt',
    'sessions.actions': 'Actions',
    'sessions.details': 'Details',
    'sessions.copy': 'Copy',
    'sessions.copied': 'Copied',
    'sessions.locate': 'Locate Topic',
    'sessions.locating': 'Sending...',
    'sessions.cooldown': 'Cooldown {seconds}s',
    'sessions.openTerminal': 'Terminal',
    'sessions.writeLink': 'Write Link',
    'sessions.writeLinkHint': 'Get a writable terminal link (carries a write-access token; opens in a new tab)',
    'sessions.writeLinkFail': 'Failed to get write link',
    'sessions.close': 'Close Session',
    'sessions.resume': 'Resume Session',
    'sessions.dismiss': 'Close',
    'sessions.closeConfirm': 'Close this session?',
    'sessions.resumeFailed': 'Resume failed',
    'sessions.land': 'Land',
    'sessions.landLoading': 'Loading sandbox diff…',
    'sessions.landUnavailable': 'Cannot land',
    'sessions.landEmpty': 'No changes in the sandbox clone.',
    'sessions.landApply': 'Apply to disk',
    'sessions.landDiscard': 'Discard',
    'sessions.landApplied': 'Landed',
    'sessions.landFailed': 'Land failed',
    'sessions.landDiscarded': 'Discarded (sandbox clone not applied).',
    'connectors.lede': 'Let external systems (alerts, CI, tickets…) trigger a bot to speak in a group or run a workflow via one webhook.',
    'connectors.createTitle': 'New Webhook',
    'connectors.fName': 'Name',
    'connectors.fNamePh': 'e.g. Prod alerts',
    'connectors.fBot': 'Trigger bot',
    'connectors.fKind': 'Trigger type',
    'connectors.kindTurn': 'Single turn (bot responds once)',
    'connectors.kindWorkflow': 'Workflow',
    'connectors.fWf': 'Workflow ID',
    'connectors.fMode': 'Deliver to which group',
    'connectors.modeDynamic': 'Specified per request (chat passed in)',
    'connectors.modeFixed': 'Fixed group',
    'connectors.modeNewGroup': 'New group each time',
    'connectors.fFixedChat': 'Target group',
    'connectors.fChatManualPh': 'Enter chat ID manually: oc_…',
    'connectors.chatManualLink': 'Group not listed? Enter ID manually →',
    'connectors.chatListLink': '← Pick from group list',
    'connectors.fAllow': 'Allowed groups',
    'connectors.optional': ' (optional)',
    'connectors.allowHint': 'Hold Ctrl/⌘ to multi-select; empty = unrestricted. Only validates the chat passed in the request.',
    'connectors.dynamicHint': '<b>Dynamic mode</b>: the chat ID is passed per request, one of three ways — query <code>?chatId=&lt;chatId&gt;</code> · header <code>x-botmux-chat-id: &lt;chatId&gt;</code> · body <code>{"chatId":"&lt;chatId&gt;"}</code>.<br>For "one URL triggers directly, no params", choose Fixed group instead.',
    'connectors.fDedup': 'Dedup field',
    'connectors.fDedupPh': 'e.g. alert.id (read from event body)',
    'connectors.dedupHint': 'Set: events with the same value go to the <b>same group</b>. Empty: each event <b>creates a new group</b>.',
    'connectors.fInstruction': 'Instruction',
    'connectors.fInstructionPh': 'What the bot should do on the event. e.g. Summarize the severity, @ the oncall, suggest next steps. Empty = hand the raw event to the model.',
    'connectors.fVerify': 'Verification',
    'connectors.verifyToken': 'Token (simple: secret in the URL, one curl triggers it)',
    'connectors.verifyHmac': 'HMAC signature (advanced: more secure, sign the request yourself)',
    'connectors.fSecret': 'Secret / token',
    'connectors.fSecretPh': 'Leave empty to auto-generate (shown once)',
    'connectors.btnCreate': 'Create',
    'connectors.listTitle': 'Existing Webhooks',
    'connectors.loading': 'Loading…',
    'connectors.noBotGroups': '(This bot has no visible groups; enter an ID manually on the right)',
    'connectors.modeLabelFixed': 'Fixed group',
    'connectors.modeLabelNewGroup': 'New group each time',
    'connectors.modeLabelDynamic': 'Per-request group',
    'connectors.kindLabelWorkflow': 'Workflow',
    'connectors.kindLabelTurn': 'Single turn',
    'connectors.count': '· {count}',
    'connectors.empty': 'No webhooks yet. Create one with the form above.',
    'connectors.enabled': 'Enabled',
    'connectors.disabled': 'Disabled',
    'connectors.badgeToken': 'Token',
    'connectors.badgeSign': 'Signed',
    'connectors.dest': 'to "{name}"',
    'connectors.btnDisable': 'Disable',
    'connectors.btnEnable': 'Enable',
    'connectors.btnDel': 'Delete',
    'connectors.webhookUrl': 'Webhook URL: ',
    'connectors.copy': 'Copy',
    'connectors.copied': 'Copied',
    'connectors.tokenHint': 'Token mode: append <code>/&lt;token&gt;</code> to the URL when calling (the token is shown only on create/rotate).',
    'connectors.dynamicReqHint': 'Dynamic mode: the request must carry the target chat — <code>?chatId=&lt;chatId&gt;</code> or header <code>x-botmux-chat-id</code> or body <code>{"chatId":"…"}</code>.',
    'connectors.instructionPrefix': 'Instruction: ',
    'connectors.delConfirm': 'Delete this webhook? Its URL stops working immediately.',
    'connectors.errName': 'Enter a name',
    'connectors.errBot': 'Pick a bot',
    'connectors.errWf': 'Enter a workflow ID',
    'connectors.errChat': 'Pick (or enter) a target group',
    'connectors.creating': 'Creating…',
    'connectors.usageDynamicLede': 'Dynamic mode: the URL already has the token; pass the target chat ID when calling{gn}:',
    'connectors.usageDynamicNote': 'The chat can also go in header <code>x-botmux-chat-id</code> or body <code>{"chatId":"…"}</code>. ⚠️ The URL is the credential — keep it secret.',
    'connectors.usageTokenLede': 'This URL already has the token and a fixed target group; just POST to trigger:',
    'connectors.usageTokenNote': '⚠️ The URL is the credential — keep it private; delete or rotate it in the list below.',
    'connectors.usageHmac': 'The external system must HMAC-SHA256-sign <code>timestamp.body</code> and call with <code>x-botmux-timestamp</code> / <code>x-botmux-nonce</code> / <code>x-botmux-signature</code> headers',
    'connectors.usageHmacDynamic': ', and also pass the target chat ID as above',
    'connectors.createdPrefix': 'Created "{name}"',
    'connectors.createdDest': 'to "{name}"',
    'connectors.tokenLabel': 'Access token',
    'connectors.signLabel': 'Signing secret',
    'connectors.secretOnce': ' (shown once — save it): ',
    'connectors.createFailed': 'Create failed: {error}',
    'connectors.noOnlineBots': '(No online bots)',
    'team.navHome': 'My team',
    'team.navManage': 'Team management',
    'team.eyebrow': 'Team',
    'team.homeTitle': 'Team collaboration (cross-deployment)',
    'team.homeLede': 'Invite other deployments (botmux instances teammates run) into one team to discover bots across deployments and pull collaborative groups.',
    'team.localDeployTitle': 'This deployment',
    'team.myIdentity': 'My Lark identity: ',
    'team.unbound': 'Not bound',
    'team.bindBtn': 'Bind',
    'team.bindHint': ' (auto-identifies you via the bot credentials; once bound, pulling a group adds you and attributes the bots to you)',
    'team.myTeams': 'My teams',
    'team.searchPh': 'Search name / capability / CLI…',
    'team.allCli': 'All CLIs',
    'team.hasCap': 'Has capability tag',
    'team.hasRole': 'Has default role',
    'team.teamsHint': 'Tick bots in any team to pull them into a group (each with its owner). To create a team / generate an invite code / join another team, go to Team management.',
    'team.loading': 'Loading…',
    'team.roleModalTitle': 'Default role',
    'team.roleModalHint': 'The bot default persona (applies across groups), read-only here. To edit, go to the Bot Defaults page.',
    'team.close': 'Close',
    'team.tagLocal': 'This deployment',
    'team.tagRemoteStale': 'Remote · offline?',
    'team.tagRemote': 'Remote',
    'team.removeMember': 'Remove',
    'team.depTag': '({tag})',
    'team.depCount': '{count}',
    'team.depSelected': ', {n} selected',
    'team.capPh': 'Capability tag…',
    'team.viewRole': 'View',
    'team.hasRoleShort': 'Has role',
    'team.noMatch': 'No bots match the filters.',
    'team.gnamePh': 'Group name (e.g. cross-team triage)',
    'team.pullGroupBtn': 'Pull ticked bots into a group',
    'team.pullGroupHint': 'Tick bots → pull into a Lark group (owner included automatically)',
    'team.noTeams': 'No teams yet. Go to Team management to generate an invite code for others to join you, or join another team.',
    'team.connected': 'Connected',
    'team.connectFail': 'Connection failed: {error}',
    'team.iHost': 'I host',
    'team.teamMeta': '{deps} deployments · {bots} bots',
    'team.rosterFail': 'Could not fetch this team roster.',
    'team.acrossTeams': ' (across {n} teams, deduped)',
    'team.botsWord': 'bots',
    'team.groupCreated': 'Group created',
    'team.delegatedBy': ' (created by "{name}")',
    'team.openInLark': 'Open in Lark',
    'team.invalidBots': 'Bots not added: {ids}',
    'team.invalidOwners': '{n} owner(s) could not be added',
    'team.missingIdentity': 'You have not bound a Lark identity, so you were not added to the group (bind it in My team)',
    'team.skippedNoOwner': '{n} bot(s) skipped because their owner has no bound identity (not added)',
    'team.errNoLocalBot': 'Tick at least one of your own (this deployment) online bots — the group is created by it and adds you (the initiator).',
    'team.errAllSkipped': 'None of the selected bot owners have a bound identity, so the group cannot be pulled (a bot cannot join a group without its owner). Have those deployments bind an identity first.',
    'team.errNoCreator': 'No available group creator (the relevant deployments have no online bot, or are unreachable)',
    'team.errDelegationTimeout': 'Timed out delegating group creation to the other deployment (it may have succeeded — check Lark, do not retry)',
    'team.errGroupCreate': 'Group creation failed: {error}',
    'team.roleModalTitleName': 'Default role · {name}',
    'team.bound': 'Bound',
    'team.myHostedTeam': 'My hosted team',
    'team.remoteTeamLabel': '{name} team',
    'team.manageTitle': 'Team management',
    'team.manageLede': 'Create multiple teams, generate an invite code per team, or join another team. A team = this deployment bots + the other deployments that joined it.',
    'team.hostedTitle': 'Teams I host',
    'team.newTeamPh': 'New team name',
    'team.createTeamBtn': 'Create team',
    'team.joinTitle': 'Join another team',
    'team.hubPh': 'Hub address, e.g. http://10.0.0.5:7891',
    'team.codePh': 'Invite code',
    'team.joinBtn': 'Join',
    'team.noTeamsShort': 'No teams yet.',
    'team.default': 'Default',
    'team.manageMetaDeps': '{count} deployments',
    'team.manageMetaRemote': ' (incl. {r} remote)',
    'team.manageMetaBots': '{count} bots',
    'team.genInvite': 'Generate invite code',
    'team.delBtn': 'Delete',
    'team.generating': 'Generating…',
    'team.inviteResultLede': 'Send the two items below to someone on another deployment (valid once, within 24h):',
    'team.inviteHub': 'Hub address: ',
    'team.inviteCode': 'Invite code: ',
    'team.genFail': 'Generation failed.',
    'team.delConfirm': 'Delete team "{name}"? Deployments that joined it will be removed (their own deployments are unaffected).',
    'team.errName': 'Enter a team name',
    'team.creating': 'Creating…',
    'team.created': 'Created',
    'team.createFail': 'Create failed: {error}',
    'team.errHubCode': 'Enter the hub address and invite code.',
    'team.joining': 'Joining…',
    'team.joined': 'Joined "{name}" — see it in My team.',
    'team.joinErrSelf': 'This is your own deployment; you cannot join yourself (the invite code is for someone on another deployment).',
    'team.joinErrAlready': 'Your deployment already joined this team.',
    'team.joinErrUnreachable': 'Cannot reach the hub (check the address/network).',
    'team.joinErrTimeout': 'The hub timed out.',
    'team.joinErrGeneric': 'Join failed: {error}',
    'team.identifying': 'Identifying…',
    'team.bound2': 'Bound: {name}',
    'team.multiCandidate': 'Multiple candidates found — click yourself:',
    'team.binding': 'Binding…',
    'team.bindFail': 'Bind failed: {error}',
    'team.noCandidates': 'No identity found: make sure the bot has allowedUsers configured and contacts permission.',
    'team.removeMemberConfirm': 'Remove "{name}" from this team? Its bots disappear from this team roster (their own deployment is unaffected).',
    'team.creatingGroup': 'Creating group…',
    'team.defaultGroupName': 'Collaboration group',
    'team.errPickBot': 'Tick at least one bot first',
    'sessions.closeBulkConfirm': 'Close {count} selected sessions?',
    'sessions.empty': 'No sessions match the filters.',
    'sessions.viewMode': 'Session view',
    'sessions.viewKanban': 'Kanban',
    'sessions.viewBoard': 'Board',
    'sessions.viewTable': 'Table',
    'sessions.selectSession': 'Select session',
    'sessions.board.needsYou': 'Needs You',
    'sessions.board.needsYouHint': 'Repo, TUI, or usage limit waiting',
    'sessions.board.starting': 'Starting',
    'sessions.board.startingHint': 'CLI is spawning or resuming',
    'sessions.board.working': 'Working',
    'sessions.board.workingHint': 'Streaming, analyzing, or using tools',
    'sessions.board.idle': 'Idle',
    'sessions.board.idleHint': 'Ready for the next message',
    'sessions.board.emptyColumn': 'No sessions',
    'sessions.board.signalRepo': 'Repo needed',
    'sessions.board.signalPrompt': 'TUI choice needed',
    'sessions.board.signalLimited': 'Usage limited',
    'sessions.board.signalAgent': 'Needs human input',
    'sessions.board.waiting': 'Waiting',
    'sessions.board.dragHint': 'Drag header to reorder columns',
    'sessions.board.moveLeft': 'Move column left',
    'sessions.board.moveRight': 'Move column right',
    'sessions.kanban.backlog': 'Backlog',
    'sessions.kanban.todo': 'Todo',
    'sessions.kanban.inProgress': 'In Progress',
    'sessions.kanban.inReview': 'In Review',
    'sessions.kanban.done': 'Done',
    'sessions.kanban.updated': 'Updated {time}',
    'sessions.kanban.moreHidden': '{count} more hidden',
    'sessions.kanban.openTab': 'Open in new tab',
    'sessions.kanban.openFeishu': 'Open in Feishu',
    'sessions.kanban.terminalLoading': 'Opening terminal…',
    'sessions.kanban.rename': 'Rename (double-click title works too)',
    'sessions.kanban.renameFail': 'Rename failed',
    'sessions.kanban.moveFail': 'Move failed',
    'sessions.kanban.groupBy': 'Group by',
    'sessions.kanban.groupFlow': 'Workflow',
    'sessions.kanban.groupTeam': 'Team',
    'sessions.kanban.groupBot': 'Bots',
    'sessions.kanban.teamLoading': 'Loading teams…',
    'sessions.kanban.noTeam': 'No teams',
    'sessions.kanban.teamScope': '{chats} collab chats · {sessions} sessions',
    'sessions.kanban.remoteHint': 'Session from the {name} deployment (snapshot; terminal/history live on their machine)',
    'sessions.kanban.clusterDragHint': 'Drag header to move the whole group',
    'sessions.history.title': 'History',
    'sessions.history.loading': 'Fetching Feishu messages…',
    'sessions.history.fail': 'Failed to load history',
    'sessions.history.empty': 'No messages',
    'sessions.history.user': 'User',
    'sessions.history.owner': 'Owner',
    'sessions.history.staleHint': 'The dashboard or daemon process may still run an old build — botmux restart and retry',
    'groups.title': 'Groups & Bots',
    'groups.subtitle': 'Inspect the chat x bot matrix and manage group creation, oncall, leave, and disband flows.',
    'groups.search': 'Search chat name / ID / owner',
    'groups.missingOnly': 'Missing bot only',
    'groups.refresh': 'Refresh',
    'groups.create': 'New Group',
    'groups.chat': 'Chat',
    'groups.actions': 'Actions',
    'groups.addBots': 'Add Bots',
    'groups.manage': 'Manage',
    'groups.empty': 'No chats match the filters.',
    'groups.createTitle': 'Create New Group',
    'groups.createHelp': 'Pick bots to invite. The dashboard chooses an online daemon as creator.',
    'groups.name': 'Group Name',
    'groups.namePlaceholder': 'e.g. AI ChangeLog',
    'groups.bindDir': 'Bind Directory',
    'groups.bindDirHelp': 'New topics start the CLI here and skip repo selection.',
    'groups.botPicker': 'Bots',
    'groups.createSubmit': 'Create',
    'groups.cancel': 'Cancel',
    'groups.successTitle': 'Group Created',
    'groups.openGroup': 'Open Group',
    'groups.manageTitle': 'Manage {name}',
    'groups.owner': 'Owner',
    'groups.oncall': 'Oncall Mode',
    'groups.oncallHelp': 'When enabled, group members can @ the bot; new topics use the bound directory.',
    'groups.leaveTitle': 'Select Bots to Leave',
    'groups.leaveSelected': 'Selected Bots Leave',
    'groups.disband': 'Disband Group',
    'groups.dangerHint': 'Disband only works when an in-chat bot is the owner. Otherwise prefer leaving the chat.',
    'groups.save': 'Save',
    'groups.needWorkingDir': 'Working directory is required',
    'groups.noBotsOnline': 'No bots online. Restart the daemon first.',
    'schedules.title': 'Schedules',
    'schedules.subtitle': 'View, pause, resume, and run scheduled tasks across daemons.',
    'schedules.search': 'Search name / prompt / working dir',
    'schedules.anyKind': 'Any kind',
    'schedules.enabledOnly': 'Enabled only',
    'schedules.name': 'Name',
    'schedules.bot': 'Bot',
    'schedules.schedule': 'Schedule',
    'schedules.next': 'Next',
    'schedules.last': 'Last',
    'schedules.repeat': 'Repeat',
    'schedules.enabled': 'Enabled',
    'schedules.actions': 'Actions',
    'schedules.runNow': 'Run Now',
    'schedules.pause': 'Pause',
    'schedules.resume': 'Resume',
    'schedules.empty': 'No schedules.',
    'settings.title': 'Global Settings',
    'settings.subtitle': 'Manage machine-wide botmux dashboard behavior shared by every bot.',
    'settings.loading': 'Loading settings…',
    'settings.loadFailed': 'Failed to load settings',
    'settings.readOnlyVisitor': 'Read-only access — changing settings requires an authorized link (run botmux dashboard).',
    'settings.sectionAccess': 'Access',
    'settings.publicReadOnly': 'Allow tokenless read-only dashboard access',
    'settings.publicReadOnlyHelp': 'When enabled, visitors without a token can view the dashboard, sessions, schedules, and SSE. Writes such as close, pause, and approvals still require token auth. Sensitive raw terminal logs always require a token.',
    'settings.sectionCards': 'Feishu Cards',
    'settings.openTerminalInFeishu': 'Open streaming-card terminals in the Feishu sidebar',
    'settings.openTerminalInFeishuHelp': 'Off by default: terminal buttons open the Web Terminal URL directly. When enabled, botmux wraps the URL with Feishu web_url/open so Feishu PC opens it in a sidebar. Terminal write access is still controlled by its token.',
    'settings.sectionRepoPicker': 'Repo Picker',
    'settings.repoPickerMode': 'Repo picker card mode',
    'settings.repoPickerModeAll': 'Repos + worktrees',
    'settings.repoPickerModeRepos': 'Main repos only',
    'settings.repoPickerModeHelp': 'Default shows repositories and linked worktrees. Main repos only hides linked worktrees from initialization and bare /repo cards; explicit /repo /abs/path/to/worktree still works.',
    'settings.saving': 'Saving…',
    'settings.saved': 'Saved',
    'settings.saveFailed': 'Save failed',
    'settings.sectionMaintenance': 'Auto Maintenance',
    'settings.autoUpdate': 'Auto update',
    'settings.autoUpdateHelp': 'Off by default. At the set time, runs npm install -g botmux@latest to install the latest version (download/install only — no restart). npm-global installs only.',
    'settings.autoRestart': 'Auto restart',
    'settings.autoRestartHelp': 'Off by default; requires auto-update. After auto-update installs a newer version, restart to apply it if no session is in progress (busy ⇒ slips to the next day).',
    'settings.autoUpdateLocalDev': 'This is a local-dev install (running from source); auto-update is unavailable.',
    'settings.maintenanceTime': 'Time',
    'botDefaults.title': 'Bot Profiles',
    'botDefaults.subtitle': 'Per-bot defaults: oncall, proactive start, persona role, cards and grants.',
    'botDefaults.metaOnline': 'Online · daemon healthy',
    'botDefaults.search': 'Search bot name / app id',
    'botDefaults.refresh': 'Refresh',
    'botDefaults.sectionOncall': 'New-chat Oncall',
    'botDefaults.sectionBrand': 'Card Signature',
    'botDefaults.warning': 'When enabled, chats without an oncall binding auto-bind to this directory on their next new topic. Manually bound or unbound chats are preserved.',
    'botDefaults.empty': 'No bots online. Run botmux restart first.',
    'botDefaults.defaultOncall': 'Default to oncall mode',
    'botDefaults.defaultOncallHelp': 'Unbound chats auto-bind on the next new topic',
    'botDefaults.workingDir': 'Default Working Directory',
    'botDefaults.lastEnabled': 'Last Enabled',
    'botDefaults.autobound': '{count} chats auto-bound',
    'botDefaults.save': 'Save',
    'botDefaults.required': 'Working directory is required when enabled',
    'botDefaults.brandLabel': 'Signature (card footer)',
    'botDefaults.brandLabelHelp': 'Footer signature on cards this bot sends. Save empty = hide; fill in = custom (markdown ok, e.g. [Acme](https://…)); Reset = show botmux.',
    'botDefaults.brandLabelPlaceholder': 'Default: botmux (empty = hidden)',
    'botDefaults.sectionSandbox': 'File sandbox (oncall)',
    'botDefaults.sandboxToggle': 'Enable file sandbox',
    'botDefaults.sandboxHelp': "When on, every session of this bot runs in a per-session sandbox: only a project copy is visible — your real files, secrets, and other sessions are not. For sharing the bot with semi-trusted users (oncall). Linux only; applies to the next new session.",
    'botDefaults.sandboxSaved': 'Saved (applies to the next new session)',
    'botDefaults.brandSave': 'Save Signature',
    'botDefaults.brandReset': 'Reset to default',
    'botDefaults.brandStateDefault': 'Current: default botmux',
    'botDefaults.brandStateOff': 'Current: off',
    'botDefaults.brandStateCustom': 'Current: custom',
    'botDefaults.sectionCard': 'Card Behavior',
    'botDefaults.disableStreaming': 'Disable streaming card',
    'botDefaults.disableStreamingHelp': 'Stop posting the live session status card (and its Open Terminal entry). The task\'s final result still arrives as a message. For those who find the live card noisy.',
    'botDefaults.writableLink': 'Put a writable terminal link on the card',
    'botDefaults.writableLinkHelp': '⚠️ Embeds a writable terminal link in the streaming card body — anyone in the chat can open and drive the terminal. Off = current behavior (private DM via the "Get Write Link" button).',
    'botDefaults.writableLinkMoot': 'Streaming card disabled — this has no effect',
    'botDefaults.privateCard': '/card sends a private card (authorized users only)',
    'botDefaults.privateCardHelp': 'Makes /card send an ephemeral "visible-to-specific-people" card: delivered only to the owner (allowedUsers); /grant-authorized talk users and everyone else in the chat cannot see it. Trade-off: it is a static snapshot (no live updates) and only works in regular group chats (topic groups / DMs fail, with no fallback). Affects only the /card command; the auto streaming card is unchanged.',
    'botDefaults.cardPrefSaved': 'Saved',
    'botDefaults.sectionRole': 'Default Role',
    'botDefaults.roleHelp': 'This bot\'s default persona (applies across all chats), injected into the bot\'s sessions in every chat; a single group can override it on the Roles page. Save empty = delete.',
    'botDefaults.rolePlaceholder': 'e.g. You are a backend triage assistant; answer concisely, prefer runnable commands…',
    'botDefaults.roleSave': 'Save role',
    'botDefaults.roleDelete': 'Delete',
    'botDefaults.roleSaved': 'Saved',
    'botDefaults.roleDeleted': 'Deleted',
    'botDefaults.roleLoadErr': 'Failed to load role',
    'botDefaults.sectionAutoStart': 'Proactive Start',
    'botDefaults.autoStartJoin': 'Auto-start when added to a new chat',
    'botDefaults.autoStartJoinHelp': 'When enabled, the bot auto-starts a session and gets to work the moment it is added to a new chat (when an authorized user / allowedUsers is a member), no @ needed. It launches in the bot default working dir; if none is configured it shows a repo-select card first. Prerequisite: subscribe the "bot joined chat" event im.chat.member.bot.added_v1 for this app and grant the member-read scope in the Feishu console.',
    'botDefaults.autoStartJoinPrompt': 'First-turn prompt on join (optional)',
    'botDefaults.autoStartJoinPromptPlaceholder': 'Filled = used as the first task after joining; blank = the bot reads the chat and decides what to do',
    'botDefaults.autoStartJoinPromptSave': 'Save prompt',
    'botDefaults.autoStartTopic': 'Auto-start on new topics in topic groups',
    'botDefaults.autoStartTopicHelp': 'When enabled, in a topic group the bot automatically joins each newly opened topic and starts working on its first message, no @ needed. Topic groups only — regular groups are unaffected.',
    'botDefaults.sectionSessionMode': 'Session mode',
    'botDefaults.p2pMode': 'Private chat session mode',
    'botDefaults.p2pThread': 'thread (separate session per DM)',
    'botDefaults.p2pChat': 'chat (flat continuous session)',
    'botDefaults.p2pHelp': 'How 1:1 DMs are sessioned: thread = each top-level message starts its own session (keeps chatter out of one CLI process); chat = the whole DM shares one continuous session and context (Hermes-like). Takes effect immediately.',
    'botDefaults.regularGroupMode': 'Regular group session mode',
    'botDefaults.regularGroupModeChat': 'chat (flat continuous session, default)',
    'botDefaults.regularGroupModeNewTopic': 'new-topic (each top-level @ opens its own topic & session)',
    'botDefaults.regularGroupModeShared': 'shared (topic display, reusing one session)',
    'botDefaults.regularGroupModeHelp': 'How new top-level @mentions in regular (non-topic) groups are sessioned: chat = the whole group shares one continuous session (default); new-topic = each top-level @ opens its own topic with a separate session; shared = topic display but reuse the same session (replies fold into a topic yet share the group session & context). This is the per-bot default; a specific group can override it via /reply-mode. Takes effect immediately.',
    'botDefaults.mentionMode': 'Group @ policy',
    'botDefaults.mentionModeAlways': 'Always require @ (default)',
    'botDefaults.mentionModeTopic': 'No @ needed inside topics',
    'botDefaults.mentionModeNever': 'Never require @',
    'botDefaults.mentionModeHelp': 'Bot-global: controls when an @ is required to get a reply in groups. "Always require @" = every message needs an @ (default, safest; inside topics too, in multi-person groups); "No @ needed inside topics" = starting a new conversation / top-level still needs @, but follow-ups inside ANY topic this bot already drives (new-topic / shared / topic-group) continue without @; "Never require @" = non-@ messages are answered too wherever the bot has talk access (including cold-starting on a brand-new message — only suitable for dedicated / on-call small groups; in busy multi-person groups it replies to everything). Note: when you are alone with this bot (1:1) replies never need an @, independent of this setting.',
    'botDefaults.docSubscribeMode': 'Doc subscription trigger (default)',
    'botDefaults.docSubscribeModeMention': 'Only when a comment @s me',
    'botDefaults.docSubscribeModeAll': 'Every new comment',
    'botDefaults.docSubscribeModeHelp': 'After subscribing to a Feishu doc with /subscribe-lark-doc, its comments feed into the session and the bot replies back into the comment. This sets the default trigger range for new subscriptions: "only when a comment @s me" is safest (avoids noise); "every new comment" suits dedicated docs.',
    'botDefaults.sectionGrant': 'Authorization & Quota',
    'botDefaults.restrictGrant': 'Restrict grantees to plain conversation',
    'botDefaults.restrictGrantHelp': 'When enabled, /grant-authorized users (the owner is exempt) can only send plain messages; every slash command is blocked: botmux built-in commands, passthrough commands, /workflow, /introduce, /t, and CLI-native commands (/help, etc.). Text like /path/to/file is not misclassified.',
    'botDefaults.quotaDefault': 'Default message quota',
    'botDefaults.quotaPlaceholder': 'Empty = no default quota',
    'botDefaults.quotaHelp': 'Message count a bare /grant @x (no number) hands out. Empty or "Turn off" merely stops applying a default to bare /grant — it does NOT clear existing quota counters, nor affect an explicit /grant @x N; those keep being enforced. Authorization is auto-revoked once a quota runs out.',
    'botDefaults.quotaSave': 'Save quota',
    'botDefaults.quotaOff': 'Turn off',
    'botDefaults.quotaInvalid': 'Quota must be a positive integer',
    'botDefaults.quotaStateOff': 'Current: no default quota',
    'botDefaults.quotaStateOn': 'Current: {count} per grantee',
    'nav.roles': 'Roles',
    'roles.title': 'Role Management',
    'roles.subtitle': 'Set per-bot role prompts for each group. Each bot adopts its own persona in the selected group.',
    'roles.search': 'Search group / bot / ID',
    'roles.refresh': 'Refresh',
    'roles.selectHint': '← Expand a group and select a bot to edit its role',
    'roles.editorPlaceholder': 'Enter role description, e.g.:\nYou are a code reviewer for this group...',
    'roles.configured': 'Configured',
    'roles.unconfigured': 'None',
    'roles.noChats': 'No groups',
    'roles.preview': 'Preview',
    'roles.previewEmpty': '(empty)',
    'roles.saved': 'Saved',
    'roles.delete': 'Delete',
    'roles.save': 'Save',
    'roles.confirmDelete': 'Delete this bot\'s role config for this group?',
    'roles.botsWithRoles': 'bots configured',
    'roles.emptyError': 'Role content cannot be empty',
    'roles.saveFailed': 'Save failed, please retry',
    'common.none': 'None',
    'common.loading': 'Loading…',
    'common.unknown': 'Unknown',
    'common.now': 'now',
    'common.never': 'never',
    'common.all': 'All',
    'nav.workflows': 'Workflows (beta)',
    'nav.workflowCatalog': 'Catalog',
    'workflow.subnav.runs': 'Runs',
    'workflow.subnav.catalog': 'Catalog',
    'workflow.searchPlaceholder': 'search runId / workflowId / chatId',
    'workflow.filter.nonTerminal': 'non-terminal',
    'workflow.filter.all': 'all',
    'workflow.status.pending': 'pending',
    'workflow.status.running': 'running',
    'workflow.status.waiting': 'waiting',
    'workflow.status.effectAttempting': 'effect',
    'workflow.status.timedOut': 'timed out',
    'workflow.status.succeeded': 'succeeded',
    'workflow.status.failed': 'failed',
    'workflow.status.cancelled': 'cancelled',
    'workflow.table.run': 'run',
    'workflow.table.workflow': 'workflow',
    'workflow.table.status': 'status',
    'workflow.table.lastSeq': 'lastSeq',
    'workflow.table.dangling': 'dEf/dAct/dWait',
    'workflow.table.updated': 'updated',
    'workflow.table.chatApp': 'chat / app',
    'workflow.list.failedLoad': 'Failed to load: {error}',
    'workflow.list.noRuns': 'No runs match.',
    'workflow.list.noFilterMatch': 'No runs match this filter.',
    'workflow.list.loaded': '{count} runs · refreshed {time}',
    'workflow.list.error': 'error: {error}',
    'workflow.detail.back': 'Back',
    'workflow.detail.loading': 'Loading...',
    'workflow.detail.loadFailed': 'Load failed',
    'workflow.detail.cancel': 'Cancel',
    'workflow.detail.cliCancelOnly': 'CLI cancel only',
    'workflow.detail.cancelTitle': 'Cancel this workflow run',
    'workflow.detail.cliCancelTitle': 'Cancel unavailable: use botmux workflow cancel {runId}',
    'workflow.detail.nodes': 'Nodes / Activities',
    'workflow.detail.parallel': 'Parallel execution',
    'workflow.detail.parallelMeta': '{count} attempt(s) · max parallel {max} · running {running}',
    'workflow.detail.noParallelData': 'No attempt timing data yet.',
    'workflow.detail.parallelNow': 'now',
    'workflow.detail.node': 'node',
    'workflow.detail.nodeStatus': 'node status',
    'workflow.detail.activity': 'activity',
    'workflow.detail.activityStatus': 'activity status',
    'workflow.detail.attempts': 'attempts',
    'workflow.detail.current': 'current',
    'workflow.detail.detail': 'detail',
    'workflow.detail.nodeIO': 'Node I/O',
    'workflow.detail.timeline': 'Timeline',
    'workflow.detail.loadOlder': 'Load older',
    'workflow.detail.seq': 'seq',
    'workflow.detail.actor': 'actor',
    'workflow.detail.error': 'error',
    'workflow.detail.event': 'event',
    'workflow.detail.time': 'time',
    'workflow.detail.refreshed': 'refreshed {time}',
    'workflow.detail.unknownRun': 'unknown run',
    'workflow.detail.snapshotHttp': 'snapshot HTTP {status}',
    'workflow.detail.eventsHttp': 'events HTTP {status}',
    'workflow.detail.cancelUnavailable': 'cancel unavailable: use botmux workflow cancel {runId}',
    'workflow.detail.cancelConfirm': 'Cancel workflow run {runId}?\n\n{total} dangling item(s) will be handled by cancel-driven recovery.\neffects={effects}, activities={activities}, waits={waits}, cancels={cancels}',
    'workflow.detail.writeAccessCancel': 'write access required: run `botmux dashboard` in the terminal to get a one-time URL, open it once to set the cookie, then come back and click cancel again.',
    'workflow.detail.cancelHttp': 'cancel HTTP {status}',
    'workflow.detail.cancelPending': 'cancel pending; waiting for running activity to drain',
    'workflow.detail.writeAccessApproval': 'write access required: run `botmux dashboard` in the terminal to get a one-time URL, open it once to set the cookie, then come back and approve/reject again.',
    'workflow.detail.actionHttp': '{action} HTTP {status}',
    'workflow.detail.approved': 'approved',
    'workflow.detail.rejected': 'rejected',
    'workflow.detail.alreadyTerminal': 'Run already terminal; {label} was not applied.',
    'workflow.detail.workflowContinue': '{label}; waiting for workflow to continue.',
    'workflow.detail.workflowRefreshing': '{label}; refreshing workflow state.',
    'workflow.detail.eventsLoaded': '{loaded}/{total} events loaded',
    'workflow.detail.dangling': 'Dangling',
    'workflow.detail.noDangling': 'No dangling work.',
    'workflow.detail.none': 'none',
    'workflow.detail.noNodes': 'No nodes yet.',
    'workflow.detail.idle': 'idle',
    'workflow.detail.noNodeIO': 'No node I/O yet.',
    'workflow.detail.notDispatched': 'not dispatched',
    'workflow.detail.noAttempt': 'No attempt yet',
    'workflow.detail.attempt': 'attempt',
    'workflow.detail.authoredInput': 'Authored input',
    'workflow.detail.resolvedInput': 'Resolved input',
    'workflow.detail.output': 'Output',
    'workflow.detail.executionLog': 'Execution log',
    'workflow.detail.liveTerminal': 'Live terminal',
    'workflow.detail.terminalLive': 'live',
    'workflow.detail.terminalClosedShort': 'closed',
    'workflow.detail.terminalClosed': 'Terminal is closed. Use the execution log below for the final transcript.',
    'workflow.detail.openTerminalNewTab': 'Open terminal in new tab',
    'workflow.detail.terminalReplay': 'Terminal replay',
    'workflow.detail.openReplayNewTab': 'Open replay in new tab',
    'workflow.detail.downloadFullLog': 'Download full log',
    'workflow.detail.terminalResume': 'Debug session',
    'workflow.detail.openResumeNewTab': 'Open debug session in new tab',
    'workflow.detail.resumeSession': 'Resume session',
    'workflow.detail.resumeStarting': 'Starting…',
    'workflow.detail.endResumeSession': 'End debug session',
    'workflow.detail.resumeEnding': 'Ending…',
    'workflow.detail.resumeUnsupportedCli': 'CLI "{cliId}" does not support resume',
    'workflow.detail.resumeMissingCliSession': 'Missing cliSessionId — cannot resume',
    'workflow.detail.resumeStartFailed': 'Failed to start debug session (HTTP {status})',
    'workflow.detail.resumeEndFailed': 'Failed to end debug session (HTTP {status})',
    'workflow.detail.writeAccessResume': 'Resume requires dashboard write access.',
    'workflow.detail.waitPrompt': 'Wait prompt',
    'workflow.detail.approvalComment': 'Approval comment',
    'workflow.detail.optionalComment': 'Optional comment',
    'workflow.detail.approve': 'Approve',
    'workflow.detail.reject': 'Reject',
    'workflow.detail.submitting': 'Submitting...',
    'workflow.detail.empty': 'empty',
    'workflow.detail.truncated': 'truncated',
    'workflow.detail.noData': 'No data.',
    'workflow.detail.noPreview': 'No preview.',
    'workflow.detail.open': 'open',
    'workflow.detail.deadline': 'deadline',
    'workflow.detail.effect': 'effect',
    'workflow.detail.wait': 'wait',
    'workflow.detail.noEvents': 'No events.',
    'workflow.summary.workflow': 'workflow',
    'workflow.summary.status': 'status',
    'workflow.summary.lastSeq': 'lastSeq',
    'workflow.summary.updated': 'updated',
    'workflow.summary.revision': 'revision',
    'workflow.summary.initiator': 'initiator',
    'workflow.summary.failedNode': 'failedNode',
    'workflow.summary.cancelOrigin': 'cancelOrigin',
    'workflow.summary.chat': 'chat',
    'workflow.summary.app': 'app',
    'workflow.dangling.activities': 'activities',
    'workflow.dangling.effects': 'effects',
    'workflow.dangling.waits': 'waits',
    'workflow.dangling.cancels': 'cancels',
    'catalog.title': 'Workflow catalog',
    'catalog.subtitle': 'Create a workflow run from a saved workflow definition.',
    'catalog.searchPlaceholder': 'search workflowId / path',
    'catalog.refresh': 'Refresh',
    'catalog.loading': 'Loading catalog...',
    'catalog.loadFailed': 'Failed to load catalog: {error}',
    'catalog.noDefinitions': 'No workflow definitions found.',
    'catalog.noFilterMatch': 'No definitions match this filter.',
    'catalog.table.workflow': 'workflow',
    'catalog.table.version': 'version',
    'catalog.table.params': 'params',
    'catalog.table.nodes': 'nodes',
    'catalog.table.revision': 'revision',
    'catalog.table.path': 'path',
    'catalog.paramSummary': '{required}/{total} required',
    'catalog.back': 'Back to catalog',
    'catalog.detailTitle': 'Workflow definition',
    'catalog.definitionLoadFailed': 'Failed to load definition: {error}',
    'catalog.summary': 'Summary',
    'catalog.paramsSchema': 'Params schema',
    'catalog.definitionJson': 'Definition JSON',
    'catalog.runPanel': 'Run workflow',
    'catalog.paramsJson': 'Params JSON',
    'catalog.paramsPlaceholder': '{\n  \"city\": \"北京\"\n}',
    'catalog.chatId': 'Chat ID',
    'catalog.larkAppId': 'Lark app ID',
    'catalog.chatBindingHint': 'Required so humanGate cards and cancel routing know which Lark chat owns the run.',
    'catalog.run': 'Run',
    'catalog.running': 'Starting...',
    'catalog.badParamsJson': 'Params must be a JSON object.',
    'catalog.writeAccess': 'write access required: run `botmux dashboard` in the terminal to get a one-time URL, open it once to set the cookie, then come back and run again.',
    'catalog.runHttp': 'run HTTP {status}',
    'catalog.runStarted': 'Run started; opening detail page...',
    'catalog.invalidParams': 'Invalid params',
    'catalog.issue': '{path}: {message}',
    'catalog.noParams': 'No params declared.',
    'catalog.required': 'required',
    'catalog.optional': 'optional',
    'catalog.default': 'default',
    'catalog.description': 'description',
    'catalog.path': 'path',
    'catalog.revision': 'revision',
    'catalog.nodeCount': 'nodes',
};
const dictionaries = { zh, en };
export function normalizeDashboardLocale(value) {
    if (typeof value !== 'string')
        return null;
    const lower = value.trim().toLowerCase();
    if (lower === 'zh' || lower.startsWith('zh-'))
        return 'zh';
    if (lower === 'en' || lower.startsWith('en-'))
        return 'en';
    return null;
}
export function detectDashboardLocale(languages = []) {
    for (const lang of languages) {
        const normalized = normalizeDashboardLocale(lang);
        if (normalized)
            return normalized;
    }
    return 'zh';
}
export function createDashboardTranslator(locale) {
    return (key, params) => {
        const template = dictionaries[locale][key] ?? dictionaries.zh[key] ?? key;
        if (!params)
            return template;
        return template.replace(/\{(\w+)\}/g, (_, name) => {
            const value = params[name];
            return value === undefined || value === null ? `{${name}}` : String(value);
        });
    };
}
export function readStoredDashboardLocale(storage, languages) {
    const stored = storage ? normalizeDashboardLocale(storage.getItem(DASHBOARD_LOCALE_STORAGE_KEY)) : null;
    return stored ?? detectDashboardLocale(languages);
}
//# sourceMappingURL=i18n.js.map