import { createApp } from 'vue' import { createPinia } from 'pinia' import { createRouter, createWebHashHistory } from 'vue-router' import { createI18n } from 'vue-i18n' import App from './App.vue' // 首屏 Workbench 静态引入;其余路由懒加载(echarts / lunar 等大依赖随页面按需加载) import Workbench from './views/Workbench.vue' const Dashboard = () => import('./views/Dashboard.vue') const ProjectDetail = () => import('./views/ProjectDetail.vue') const AIChat = () => import('./views/AIChat.vue') const Todos = () => import('./views/Todos.vue') const Tickets = () => import('./views/Tickets.vue') const CalendarPage = () => import('./views/CalendarPage.vue') const Logs = () => import('./views/Logs.vue') const Settings = () => import('./views/Settings.vue') const Profile = () => import('./views/Profile.vue') const Launchpad = () => import('./views/Launchpad.vue') const Messages = () => import('./views/Messages.vue') const Today = () => import('./views/Today.vue') const Notes = () => import('./views/Notes.vue') const TeamHome = () => import('./views/TeamHome.vue') const TeamTasks = () => import('./views/TeamTasks.vue') const TeamReports = () => import('./views/TeamReports.vue') import './style.css' import './motion.css' import './database.css' import './runtime.css' import './git.css' import './polish.css' const zh = { app: '年糕崽崽 PMS', dashboard: '仪表盘', logs: '运行日志', settings: '设置', navOverview: '概览', navProjects: '项目', navWork: '事务', navSystem: '系统', launchpad: '启动台', launchpadSubtitle: '本机服务端口与应用启停管理', lpMyApps: '我的应用', lpScanned: '检测到的服务', lpAddApp: '添加应用', lpEditApp: '编辑应用', lpRefresh: '刷新', lpShowSys: '显示系统进程', lpName: '应用名称', lpKind: '种类', lpPort: '端口', lpDir: '工作目录', lpStartCmd: '启动命令', lpStopCmd: '停止命令', lpStopBlank: '留空则直接结束进程树', lpSuggest: '推荐', lpStart: '启动', lpStop: '停止', lpPin: '存为应用', lpRunning: '运行中', lpStopped: '未运行', lpMem: '内存', lpNeedCmd: '首次启动请先选择或填写启动命令', lpStopConfirm: '确认停止 {name}?', lpDelConfirm: '删除应用 {name}?(不会停止正在运行的进程)', lpEmptyApps: '还没有保存的应用:从下方检测列表「存为应用」或手动添加', lpEmptyScan: '未检测到监听端口的服务', workbench: '工作台', workbenchSubtitle: '收藏项目、今日待办与速记', todos: '待办事项', todosSubtitle: '管理待办、跟踪进度', tickets: '需求工单', ticketsSubtitle: '工单需要绑定项目与排期', calendar: '排期日历', calendarSubtitle: '聚合待办截止日与工单排期', statsOverview: '数据总览', statsCollapseBtn: '折叠', statsExpandBtn: '展开统计', calJumpPh: '跳转 0501 / 2026-05-01', calJumpTitle: '输入日期回车跳转;未填年份按当前视图年份补全', calBadDate: '无法识别的日期格式', dpClear: '清除', messages: '消息中心', boardView: '看板', listView: '列表', addTodo: '新建待办', editTodo: '编辑待办', quickAddTodo: '快速添加待办,回车确认...', todoTitle: '标题', todoContent: '备注(可选)', relatedProject: '关联项目', noProject: '不关联项目', selectProject: '请选择项目', dueDate: '截止时间', startDate: '开始日期', priorityLabel: '优先级', statusLabel: '状态', noTodos: '暂无待办事项', todoStatus: { open: '待处理', doing: '进行中', done: '已完成' }, priority: { low: '低', medium: '中', high: '高' }, addTicket: '新建工单', editTicket: '编辑工单', ticketTitle: '工单标题', ticketDesc: '需求描述', ticketTypeLabel: '类型', ticketType: { feature: '新功能', bug: '缺陷', task: '任务', improvement: '优化' }, ticketStatus: { open: '待处理', in_progress: '处理中', resolved: '已解决', closed: '已关闭' }, ticketFlow: { start: '开始处理', resolve: '标记解决', close: '关闭', reopen: '重新打开' }, noTickets: '暂无工单', today: '今天', selectDate: '选择日期', noSchedule: '当日无排期', weekdays: { sun: '日', mon: '一', tue: '二', wed: '三', thu: '四', fri: '五', sat: '六' }, favoriteProjects: '收藏项目', noFavorites: '还没有收藏项目,去项目卡片点击星标收藏', unfavorite: '取消收藏', favorite: '收藏', openTodos: '未完成待办', openTickets: '进行中工单', todayTodos: '今日待办', noTodayTodos: '今天没有到期的待办', weekTickets: '本周工单', noWeekTickets: '本周没有到期的工单', notepad: '记事本', notepadPlaceholder: '随手记点什么,自动保存...', autoSaved: '已自动保存', noteCenter: '笔记', noteNew: '新建笔记', noteEdit: '编辑笔记', noteEmpty: '还没有笔记,点右上角新建一条', noteUntitled: '(空白笔记)', noteDeleteConfirm: '删除这条笔记?', notesPage: '笔记', notesSubtitle: '共 {n} 条笔记,点击卡片编辑', noteSearchPh: '搜索笔记…', viewAll: '查看全部', todayTasks: '今日任务', todaySubtitle: '聚合今天需要处理的待办与工单', secOverdue: '已逾期', secToday: '今天到期', secDoing: '进行中', secUpcoming: '未来 7 天', todayEmpty: '今天没有需要处理的任务,好好休息!', messagesSubtitle: '系统通知与提醒汇总', msgUnreadOnly: '仅看未读', msgKindAll: '全部', msgKind: { todo_due: '待办提醒', ticket_due: '工单提醒', analysis: '统计分析', sync: '同步' }, aiScopeBtn: 'AI 总结', aiScopeEmpty: '还没有生成过总结,点击下方按钮让 AI 帮你分析。', aiScopeGen: '生成总结', aiScopeRegen: '重新生成', cloudPendingTitle: '云端项目待绑定', cloudPendingDesc: '这些项目来自你在其它电脑的同步数据,选择本机目录后即可继续统计', cloudBindBtn: '绑定目录', cloudBindTitle: '绑定云端项目到本机', cloudBindDone: '云端项目已绑定,可以开始统计了', navTeam: '团队', teamHome: '团队概览', teamHomeSubtitle: '成员、角色与团队设置', teamTasks: '团队任务', teamTasksSubtitle: '团队内的任务指派、流转与催办', teamReports: '团队日报', teamReportsSubtitle: '成员日报提交与 AI 摘要', teamLoginHint: '团队功能需要登录账号(成员连接同一台 MySQL 服务器)', teamNoneHint: '你还没有加入任何团队,先创建一个吧', teamNamePh: '团队名称…', teamCreateBtn: '创建团队', teamCreateMore: '再创建一个团队', teamCreatedToast: '团队已创建', teamGoHome: '进入团队页', teamSwitcher: '切换团队', teamSwitchedToast: '已切换到「{name}」', teamName: '团队名称', teamRenameBtn: '重命名', teamRole_owner: '拥有者', teamRole_admin: '管理员', teamRole_member: '成员', teamMembersCount: '{n} 名成员', teamDigestTime: '摘要时间', teamDigestTimeHint: '到点后管理员客户端自动生成当日日报摘要', teamInviteBtn: '邀请成员', teamInviteUser: '账号用户名', teamInviteUserPh: '输入对方的登录用户名', teamInviteRole: '角色', teamInvitedToast: '已加入团队', teamMakeAdmin: '设为管理员', teamMakeMember: '设为普通成员', teamRemoveBtn: '移出团队', teamRemoveConfirm: '把 {name} 移出团队?', teamLeaveBtn: '退出团队', teamLeaveConfirm: '确定退出这个团队?', teamDissolveBtn: '解散团队', teamDissolveConfirm: '解散团队「{name}」?所有团队任务与日报将被删除', teamFilter_all: '全部', teamFilter_mine: '指派给我', teamFilter_created: '我创建的', teamFilter_open: '未完成', teamTaskNew: '新建任务', teamTaskEdit: '编辑任务', teamTaskTitlePh: '要做什么…', teamTaskDesc: '描述(支持 Markdown)', teamKindTodo: '待办', teamKindTicket: '工单', teamQuickCreate: '快捷创建任务 / 工单', teamQuickCreateBtn: '创建并指派', teamQuickFor: '给 {name} 创建', teamQuickDoneToast: '已创建并指派给 {name}', teamTasksEmpty: '还没有团队任务', teamStatus_open: '待处理', teamStatus_doing: '进行中', teamStatus_done: '已完成', teamStatus_closed: '已关闭', teamReopen: '重新打开', teamAssignee: '负责人', teamCreator: '创建人', teamUnassigned: '未指派', teamUrgeBtn: '催办', teamUrgedToast: '已发送催办提醒', teamUrgedAt: '催办于', teamTaskDeleteConfirm: '删除任务「{title}」?', teamSharedItems: '成员共享的个人条目', teamSharedEmpty: '成员还没有共享待办或工单', teamMyReport: '我的日报', teamReportPh: '今天做了什么、遇到什么问题、明天的计划…(支持 Markdown)', teamReportSubmitBtn: '提交日报', teamReportUpdateBtn: '更新日报', teamReportSubmittedToast: '日报已提交', teamReportSubmittedAt: '已于 {at} 提交', teamReportNotSubmitted: '今天还没有提交', teamReportMissing: '未提交', teamReportContentHidden: '仅管理员可见日报内容', teamQuoteDayReport: '引用下班总结', teamQuoteDayReportHint: '把工作台生成的下班日报填入编辑器', teamNoDayReport: '还没有生成过下班总结,先去工作台生成', teamDigest: 'AI 团队摘要', teamDigestNone: '汇总当日全部成员日报,生成团队摘要', teamDigestAt: '{at} 由 {provider} 生成', teamDigestGen: '生成摘要', teamDigestRegen: '重新生成', teamDigestStartedToast: '摘要生成中,完成后自动刷新', teamDigestDoneToast: '团队摘要已生成', generating: '生成中…', prevDay: '前一天', nextDay: '后一天', retry: '重试', loading: '加载中…', noDescription: '(无描述)', savedToast: '已保存', profileTabInfo: '个人资料', profileTabTeams: '我的团队', profileInfoHint: '资料对同服务器的团队成员可见', profileNickname: '昵称', profileNicknamePh: '怎么称呼你', profileJobTitle: '头衔', profileJobTitlePh: '如:前端工程师', profileEmail: '邮箱', profileBio: '简介', profileBioPh: '一句话介绍自己…', profileTags: '技术栈标签', profileTagsPh: '回车添加标签…', profileTagsHint: '最多 20 个,回车或逗号添加,点 × 删除', profileSaveBtn: '保存资料', profileSavedToast: '资料已保存', profileTeamsHint: '切换当前团队或创建新团队', shareToTeam: '共享到团队', sharePrivate: '仅自己可见', sharedToTeamToast: '共享设置已更新', recentMessages: '最近消息', noMessages: '暂无消息', markAllRead: '全部已读', clearMessages: '清空消息', projects: '我的项目', addProject: '添加项目', batch: '批量统计', analyzingProject: '正在统计项目', search: '搜索项目...', totalProjects: '项目总数', totalLines: '代码总行数', commits: 'Git 提交数', code: '代码统计', git: 'Git 分析', structure: '项目结构', insights: '检查', analyze: '重新分析', analyzeGit: '重新分析 Git', empty: '暂无数据', back: '返回', refreshProject: '更新此项目', edit: '编辑', delete: '删除', add: '新增', save: '保存', cancel: '取消', browse: '浏览', selectWSL: '选择 WSL 目录', noWSL: '未发现可用的 WSL 发行版', dashboardSubtitle: '管理和分析你的代码项目', projectName: '项目名称', projectPath: '项目路径', description: '描述(可选)', addProjectTitle: '添加项目', editProjectTitle: '编辑项目', projectGroup: '项目组', allProjectGroups: '全部项目组', allProjects: '全部项目', myProjectGroup: '我的项目组', addProjectGroup: '新增项目组', editProjectGroup: '编辑项目组', deleteProjectGroup: '删除项目组', projectGroupName: '项目组名称', saveProjectGroup: '保存项目组', saveProject: '保存项目', saving: '正在保存...', pathRequired: '请选择项目目录', unanalyzed: '尚未分析', totalLineCount: '总行数', codeLines: '代码行', commentLines: '注释行', blankLines: '空行', fileCount: '文件数', languageDistribution: '语言分布', languageDetails: '语言详情', files: '文件', lines: '行', workspaceBranch: '工作区分支', viewRef: '统计视图', switchView: '查看统计', checkout: '切换工作区', checkoutConfirm: '确认将工作区切换到分支 {ref}?未提交或未跟踪改动会导致操作被拒绝。', dirty: '工作区存在未提交或未跟踪文件,已拒绝切换', branchChanged: '工作区分支已切换', commitDetail: '提交详情', copyHash: '复制哈希', filesChanged: '变更文件', clearFilter: '清除筛选', selectedDate: '已筛选 {date}', day: '日', week: '周', month: '月', commitCount: '总提交数', addedLines: '新增行数', deletedLines: '删除行数', contributors: '贡献者数', activityHeatmap: '活跃度热力图', branches: '分支信息', recentCommits: '最近提交', commitTrend: '提交趋势', contributorRanking: '贡献者排行', commitsUnit: '提交', gitUnavailable: 'Git 数据不可用', gitUnavailableHint: '当前项目的 Git 信息读取失败,代码统计不会受影响。', gitDetail: '诊断详情', totalFiles: '总文件数', folderCount: '文件夹数', totalSize: '总大小', largeFiles: '大文件数', directoryStructure: '目录结构', folderSize: '文件夹大小', largeFileDetection: '大文件检测(≥ 1 MB)', noLargeFiles: '未发现大文件', deepInsights: '深度检查', deepInsightsHint: '基于代码结构、源码标记和 Git 活跃度识别维护风险。', healthScore: '健康分', highRisk: '高风险', mediumRisk: '中风险', lowRisk: '低风险', issueList: '问题列表', allSeverity: '全部级别', allTypes: '全部类型', noIssues: '暂无风险项', insightDone: '深度检查完成', severity: { high: '高风险', medium: '中风险', low: '低风险' }, quickSettings: '快捷设置', language: '语言', theme: '主题', themeDark: '暗色', themeLight: '浅色', themeSystem: '跟随系统', glassOpacity: '卡片透明度', loadingStyle: '统计 Loading 样式', openSettings: '打开设置页', logSubtitle: '查看应用运行状态和错误信息', autoRefresh: '自动刷新', refresh: '刷新', clearLogs: '清空日志', allLogs: '全部日志', runLogs: '运行日志', errorLogs: '错误日志', totalLogs: '总日志', info: '信息', warning: '警告', error: '错误', noLogs: '暂无日志记录', searchLogs: '搜索日志内容...', allCategories: '全部分类', menuFile: '文件', menuView: '视图', menuTools: '工具', menuHelp: '帮助', menuDatabase: '数据管理', menuQuit: '退出', menuAnalyzeNow: '立即分析全部', menuNewProject: '新建项目', menuAbout: '关于', winMin: '最小化', winMax: '最大化', winRestore: '还原', winClose: '关闭', aiChat: 'AI 分析', aiSubtitle: '基于项目数据的智能分析与问答', aiSparkLite: '星火 Lite', aiKeys: 'Key 配置', aiNewChat: '新建会话', aiHistory: '历史会话', aiNoHistory: '还没有会话,从下方开始提问', aiDeleteConfirm: '删除该会话及全部消息?', aiNoProject: '不绑定项目', aiNeedProject: '请先在上方选择一个项目', aiQuickProject: '项目分析', aiQuickGit: 'Git 贡献', aiQuickTodo: 'Todo 分析', aiQuickTicket: '需求分析', aiPromptProject: '请分析这个项目的技术栈、代码规模和质量风险,并给出改进建议。', aiPromptGit: '请分析这个项目的 Git 贡献情况:贡献者结构、活跃度和热点文件风险。', aiPromptTodo: '请分析这个项目的待办事项:优先级是否合理、有哪些逾期风险、建议的执行顺序。', aiPromptTicket: '请从需求管理角度分析这个项目的工单:排期合理性、类型分布和处理顺序建议。', aiWelcome: '选择项目后可使用快捷分析,也可以直接提问', aiAskPlaceholder: '输入问题,Enter 发送,Shift+Enter 换行', aiSend: '发送', aiStop: '停止', aiNoKeyTitle: '尚未配置 AI Key', aiNoKeyHint: '前往设置填写讯飞星火或 DeepSeek 的 API Key 后即可使用 AI 分析。', aiConfigureNow: '去配置', aiAnalysis: 'AI 分析', batchSummaryTitle: '批量统计结果', batchTotal: '共 {n} 个项目', batchOk: '成功', batchFail: '失败', batchCancelled: '取消', batchDoneToast: '批量统计完成:{completed}/{total} 成功', fileHotspots: '文件热点', fileHotspotsHint: '按历史变更次数排序', changesUnit: '次变更', previewLoading: '正在读取文件...', clickPreview: '点击预览文件', previewBinary: '二进制文件,无法预览', previewTruncated: '文件过大,仅显示前 1MB', copyContent: '复制内容', copied: '已复制到剪贴板', copyFailed: '复制失败', copyReport: '复制', copyDigest: '复制摘要', settingsSubtitle: '管理排除规则、界面和数据库配置', tabRules: '排除规则', tabAppearance: '界面设置', tabSync: '登录与同步', tabDatabase: '数据管理', aboutSlogan: '本地优先的一站式项目工作台', aboutIntro: '年糕崽崽项目管理(NL PMS)是一款本地优先的桌面项目工作台:一键统计代码规模与语言分布、洞察 Git 协作热度,聚合待办、工单与排期日历,内置 AI 帮你解读项目、规划每一天。数据默认保存在本地 SQLite,登录后可加密同步到云端,多台设备无缝衔接。', aboutFeatCode: '代码统计', aboutFeatGit: 'Git 分析', aboutFeatTask: '待办与工单', aboutFeatCal: '排期日历', aboutFeatAI: 'AI 分析', aboutFeatSync: '加密云同步', aboutNameTitle: '名字的由来', aboutNameStory: 'NL 是"奶酪"的拼音缩写,年糕崽崽则是一只圆脸短腿的拿破仑小黄猫——它最喜欢趴在奶酪上打盹。就像这只小猫守着它的奶酪,NL PMS 替你守着每一行代码、每一个待办。', aboutFoot: '用心打磨 · 年糕崽崽出品', addRule: '添加排除规则', rulePatternPh: '例如 *.log, cache, temp', catGeneral: '通用', catCustom: '自定义', ruleHint: '支持 * 通配符;目录名会在任意层级匹配', rulesCount: '{n} 条规则', builtinTag: '默认', langThemeTitle: '语言与主题', uiLanguage: '界面语言', gitScopeDefault: 'Git 默认范围', scopeCurrent: '当前分支', scopeAll: '所有分支', loadingStyleOrbit: '能量轨道', loadingStyleOrbitDesc: '环形粒子、扫描光束和能量核心', loadingStyleGrid: '数据星云', loadingStyleGridDesc: '粒子流场汇聚成星云与光核', loadingStyleWarp: '星际穿越', loadingStyleWarpDesc: '高速星流粒子拉伸出光迹', loadingStyleMatrix: '黑客帝国', loadingStyleMatrixDesc: '绿色数字雨倾泻而下', loadingStyleBar: '底部进度条', loadingStyleBarDesc: '保留当前页面,只显示底部进度', sysIntegration: '系统集成', onWindowClose: '关闭窗口时', closeToTray: '最小化到系统托盘', closeQuit: '直接退出应用', autostartLabel: '开机自启', autostartOnBtn: '已开启,点击关闭', autostartOffBtn: '已关闭,点击开启', autostartOnHint: '登录系统后自动启动年糕崽崽 PMS', autostartOffHint: '不随系统启动', autostartOnToast: '已开启开机自启', autostartOffToast: '已关闭开机自启', autostartFail: '开机自启设置失败:{err}', autoUpdateTitle: '自动更新项目', autoUpdateEnable: '定时批量统计', optOn: '开启', optOff: '关闭', updateFreq: '更新频率', freqDaily: '每天', freqNDays: '每 N 天', freqNHours: '每 N 小时', intervalN: '间隔 N', triggerTime: '触发时刻', autoUpdateHint: '到达设定时间后自动对所有项目执行批量统计,结果写入运行日志。', previewModeTitle: '当前是浏览器预览模式', previewModeSync: '浏览器无法连接 MySQL,请运行 code-count.exe 使用同步功能。', previewModeDb: '浏览器无法访问本地数据库和文件选择器,请运行 code-count.exe 使用数据库功能。', accountSync: '账号与同步', menuAccount: '账号', loginOrRegister: '登录 / 注册', registerAndLogin: '注册并登录', loginModalHint: '登录后 Todo、工单、记事本、API Key 与头像会随账号在设备间同步', switchToRegister: '还没有账号?立即注册', loginWelcome: '欢迎回来', registerWelcome: '创建新账号', confirmPh: '再次输入密码', showPassword: '显示密码', hidePassword: '隐藏密码', dailyCard: '每日心语', dailyYi: '宜', dailyJi: '忌', dailyGotIt: '今日已读', dailyTodayBtn: '今日心语', dailyViewBtn: '查看当日心语', dailyHistory: '历史', dailyFuture: '未来的心语还没写好', dailyPrevDay: '前一天', dailyNextDay: '后一天', festImgTitle: '节日格样式(管理员)', festImgSet: '设置图片', festImgReplace: '更换', festImgRemove: '移除', festImgSaved: '节日图片已保存,将同步给所有用户', festImgRemoved: '节日图片已移除', festModeArtCard: '动态插画', festModePhotoCard: '自定义图片', fieldUser: '用户名', fieldPass: '密码', fieldConfirm: '确认密码', dqToday: '今天', dqTomorrow: '明天', dqDayAfter: '后天', dqDays: '{n}天后', dqSuffix: '天后', dqAddTip: '添加自定义天数', dqRemove: '删除标签', loginTagline: '本地优先\n云端随行', loginSecureBadge: 'API Key 加密存储', switchToLogin: '已有账号?直接登录', usernamePh: '用户名(3-64 个字符)', passwordPh: '密码(至少 6 位)', loginBtn: '登录', loggingIn: '登录中…', registerBtn: '注册新账号', registering: '注册中…', syncLoginHint: '登录后 Todo、工单、记事本会自动同步到服务器(每 5 分钟一次);离线时数据保存在本地,联网后自动补同步。', notLoggedIn: '未登录', loginNow: '点击登录账号', pendingSync: '{n} 条改动待同步', close: '关闭', ctxNewTodo: '新建待办', ctxNewTicket: '新建工单', ctxNewReminder: '快速提醒', ctxNeedProject: '需要先创建项目才能建工单', quickTitlePh: '输入标题,Enter 创建', quickCreate: '创建', quickCreated: '已创建', yesterday: '昨天', justNow: '刚刚', minAgo: '{n} 分钟前', hourAgo: '{n} 小时前', dayAgo: '{n} 天前', copyBtn: '复制', copiedShort: '已复制', noLogsHint: '应用运行事件会记录在这里', searchPlaceholder: '搜索项目、待办、工单、AI 会话…', searchEmpty: '没有匹配的结果', searchHint: '输入关键字开始搜索,↑↓ 选择,Enter 打开', kindProject: '项目', kindTodo: '待办', kindTicket: '工单', kindConversation: 'AI 会话', changePasswordTitle: '修改密码', oldPasswordLabel: '旧密码', newPasswordLabel: '新密码', confirmPasswordLabel: '确认新密码', changePasswordBtn: '确认修改', changingPassword: '修改中…', passwordChangedToast: '密码已修改', passwordMismatch: '两次输入的新密码不一致', changePasswordHint: '修改密码需要在线。云端 API Key 会用新密码派生的密钥重新加密;其他已登录设备需用新密码重新登录,否则无法解密云端 Key。', profileTitle: '个人资料', avatarSource: '头像来源', avatarNone: '不设置头像', avatarBase64: '本地图片(存为 Base64,随账号同步)', avatarUrl: '图片 URL(OSS / 图床直链)', avatarPath: '本地图片路径(仅本机显示)', avatarUrlLabel: '图片地址', avatarUrlPh: '例如 https://bucket.oss-cn-hangzhou.aliyuncs.com/avatar.png', avatarPick: '选择图片', avatarHint: '图片会缩放到 256px 以内。Base64 与 URL 模式登录后会随账号同步到其他设备;本地路径模式只在本机生效,不参与同步。', contentImageStore: '内容图片存储', imgModeBase64: '内嵌 Base64(随内容同步)', imgModePath: '本地文件(仅本机显示)', imgModeServer: '上传到服务器(跨设备可见)', contentImageHint: '待办 / 工单正文里粘贴或插入的图片按此方式保存:内嵌 Base64 会随内容同步到云端;本地文件体积更小,但换设备后无法显示;上传到服务器后以链接引用,任何设备都能访问。图片统一压缩到 1100px 以内。', avatarServer: '上传到服务器(管理员配置的文件服务)', fsTitle: '文件存储(全局)', fsHint: '管理员专属:配置随同步下发全员生效。服务器模式下内容图与头像上传到文件服务,跨设备可访问', fsMode: '存储方式', fsModeLocal: '本地(默认)', fsModeServer: '服务器(nl-pms-api)', fsBaseUrl: '服务器地址', fsApiKey: '上传密钥', fsApiKeyPh: '与 nl-pms-api 配置文件里的 api_key 一致', fsTestBtn: '测试连接', fsTesting: '测试中…', fsSaveBtn: '保存配置', fsSavedToast: '文件存储配置已保存,立即全员生效', fsTestOkToast: '文件服务器连接成功', tabFileStorage: '文件存储', fsPageHint: '配置保存在服务器数据库,保存后立即全员生效:每个客户端上传图片时都会实时读取该配置决定存储位置,无需等待同步。', fsAdminOnlyTitle: '仅管理员可配置', fsAdminOnlyDesc: '只有管理员账号(ID=1)可以修改全局文件存储方式,请联系管理员。', storageFollowHint: '头像与正文图片的存储方式由管理员统一配置,无需手动选择。当前:{mode}。', avatarClear: '清除头像', quitApp: '退出应用', quitConfirm: '确定退出应用?', assetsTab: '素材库', assetsHint: '管理上传到文件服务器的图片:本人管自己的,团队管理员管团队的,管理员(id=1)管全部', assetsNeedServer: '未启用服务器存储。管理员在「设置 → 文件存储」切到服务器模式后,上传的图片会在这里展示。', assetsScopeMine: '我的上传', assetsScopeTeam: '团队素材', assetsScopeAll: '全部(管理员)', assetsCount: '共 {n} 张', assetsRefresh: '刷新', assetsEmpty: '还没有图片。待办 / 工单正文粘贴的图片或上传的头像会出现在这里。', assetsLoadMore: '加载更多', assetsCopy: '复制链接', assetsCopiedToast: '链接已复制', assetsCopyFailed: '复制失败', assetsDeleteConfirm: '删除这张图片?引用它的内容将无法再显示。', assetsDeletedToast: '图片已删除', assetsKindAvatar: '头像', assetsKindContent: '内容图', mdEdit: '编辑', mdPreviewTab: '预览', insertImage: '插图', mdInserting: '处理中…', mdPlaceholder: '支持 Markdown:**加粗**、- 列表、`代码`、[链接](url);图片可直接粘贴或点右上角插图', mdEmpty: '*(暂无内容)*', lifecycle: '生命周期', lifecycleCreated: '创建', lifecycleNow: '进行中…', lifecycleTotal: '从创建到完结,共 {dur}', durMoment: '片刻', durMin: '{n} 分钟', durHour: '{n} 小时', durDay: '{n} 天', taskCenter: '任务中心', taskDoingGroup: '进行中', taskPendingGroup: '待开始', taskStart: '开始', taskDone: '完成', taskResolve: '解决', taskEmpty: '没有待处理的任务', aiBriefProject: 'AI 项目介绍', aiBriefGit: 'AI Git 介绍', aiBriefStructure: 'AI 结构分析', aiBriefInsights: 'AI 检查解读', aiBriefEmpty: '尚未生成,更新项目数据后将自动生成', aiBriefGenerating: 'AI 正在生成介绍…', aiBriefRegen: '重新生成', aiAskThisProject: '就这个项目问点什么…', aiAskGo: '问一问', aiAskDrawer: 'AI 问答', aiOpenFull: '完整页面', aiLoadEarlier: '加载更早的 {n} 条消息', online: '在线', offline: '离线', lastSync: '上次同步:{time}', profilePage: '个人主页', profileSubtitle: '账号信息、头像与数据同步', profileSecurity: '账号安全', profileGuest: '登录后可同步数据、设置头像与云端资料', profileTabSync: '数据同步', avatarEdit: '编辑头像', avatarModalTitle: '头像与图片', accountLabel: '登录账号', connectionState: '连接状态', lastSyncLabel: '上次同步', pendingLabel: '待推送', profileSyncHint: '登录后,待办、工单、设置、收藏、项目定义与文档会自动在设备间同步;离线的修改会在网络恢复后自动补推。', greetMorning: '早上好', greetNoon: '中午好', greetAfternoon: '下午好', greetEvening: '晚上好', greetNight: '夜深了', phDoneTodos: '已完成待办', phResolvedTickets: '已解决工单', pwdWeak: '较弱', pwdMedium: '中等', pwdStrong: '强', syncScope: '同步范围', scopeFavs: '收藏', scopeDocs: '文档', wbAiPlan: 'AI 今日规划', wbPlanGen: '生成规划', wbPlanRegen: '重新规划', wbReport: '下班日报', wbReportGen: '下班总结', wbGenerating: '生成中…', wbPlanEmpty: '点击「生成规划」,AI 会根据今天的待办与工单为你安排优先级', wbReportEmpty: '下班前点一下「下班总结」,AI 把今天处理的事项写成一段日报', wbAiNoKey: '配置 AI Key 后,可让 AI 生成今日工作规划与下班日报', pendingPush: '待推送 {n} 条', neverSynced: '尚未同步', syncNowBtn: '立即同步', syncingBtn: '同步中…', logoutBtn: '退出登录', registerOkToast: '注册成功', loginOkToast: '登录成功,正在同步数据', logoutToast: '已退出登录', syncDoneToast: '同步完成:推送 {pushed} 条,拉取 {pulled} 条', syncFailToast: '同步失败', aiProviderTitle: 'AI 服务商', aiCurrentProvider: '当前服务商', aiSparkOption: '讯飞星火 Lite(免费)', sparkKeyLabel: '星火 APIPassword', sparkKeyPh: '讯飞开放平台 → Spark Lite → HTTP 服务接口认证信息', deepseekKeyLabel: 'DeepSeek API Key', deepseekKeyPh: 'platform.deepseek.com → API Keys', aiKeyHint: 'Key 默认只保存在本地数据库。星火 Lite 免费(spark-api-open.xf-yun.com),DeepSeek 按量计费(api.deepseek.com)。配置后到「AI 分析」页即可对项目、Git 贡献、待办与工单做智能分析。', syncApiKeysLabel: '同步 API Key 到服务器', syncApiKeysOn: '开启(加密存储)', syncApiKeysOff: '关闭(仅保存在本地)', syncApiKeysHint: '开启后 API Key 会用登录密码派生的密钥加密(AES-256-GCM)再上传,服务器无法解密;换设备用同一账号登录即可自动取回。需要先登录。', dbLocation: '数据库位置', dbConnected: '已连接', dbCurrentLoc: '当前位置', dbNoPath: '未获取到数据库路径', copyPathTitle: '复制路径', dbMigratedMsg: '本地数据库文件已移动到新位置', dbMigrateFail: '移动数据库文件失败', dbPathCopied: '数据库路径已复制', dbCopyFail: '无法复制路径', migrateBtn: '更换数据库文件位置', dbRelocateHint: '仅移动本机 SQLite 文件;云端 MySQL 表结构请用仓库根目录 init.sql 初始化,应用与前端不会执行建表/迁移。', dangerZone: '清空数据', dangerDesc: '选择要清空的数据类型,此操作不可恢复。', clearStats: '清空统计数据', clearStatsDesc: '删除分析记录,保留项目配置', clearProject: '清空单个项目', clearProjectDesc: '删除指定项目的统计数据', clearAllData: '清空所有数据', clearAllDesc: '删除所有项目和分析记录', projectIdPrompt: '项目 ID', confirmIrreversible: '此操作不可恢复,确认继续?', srcLocal: '本地目录', srcGit: '从 Git 克隆', cloneUrl: 'Git 仓库地址', cloneDir: '克隆到', cloneDirHint: '选择存放仓库的父目录', cloneAndAdd: '克隆并添加', cloning: '克隆中...', cloneDone: '克隆完成,项目已添加', cloneUrlRequired: '请填写 Git 仓库地址', cloneDirRequired: '请选择克隆目录', errors: { URL_REQUIRED: '请填写 Git 仓库地址', DIR_NOT_FOUND: '克隆目录不存在', NAME_REQUIRED: '无法从地址推导项目名,请手动填写', TARGET_NOT_EMPTY: '目标目录已存在且非空', SYNC_NOT_CONFIGURED: '请先填写完整的 MySQL 服务器配置', SYNC_OFFLINE: '无法连接服务器,请检查网络与配置', SYNC_SCHEMA_MISSING: '数据库或数据表不存在,请先在服务器执行 init.sql 初始化脚本', SYNC_SCHEMA_OUTDATED: '云端表结构需要升级,请在服务器重新执行最新的 init.sql', SYNC_USER_EXISTS: '用户名已被注册', SYNC_BAD_CREDENTIALS: '用户名或密码错误', SYNC_USERNAME_INVALID: '用户名需为 3-64 个字符', SYNC_PASSWORD_TOO_SHORT: '密码至少 6 位', SYNC_NOT_LOGGED_IN: '请先登录', SYNC_OLD_PASSWORD_WRONG: '旧密码错误', SYNC_IN_PROGRESS: '同步正在进行中', SYNC_DECRYPT_FAILED: '云端 API Key 解密失败,请重新登录后再同步', AVATAR_FILE_TOO_LARGE: '图片超过 10MB,请换一张更小的图片', AVATAR_DECODE_FAILED: '无法识别的图片格式(支持 PNG / JPG / GIF / WebP)', FILE_STORAGE_ADMIN_ONLY: '只有管理员(id=1)可以配置文件存储', FILE_STORAGE_BAD_URL: '服务器地址无效,需以 http(s):// 开头', FILE_STORAGE_UNREACHABLE: '无法连接文件服务器,请检查地址与服务状态', FILE_API_UNCONFIGURED: '服务器存储未启用,请联系管理员配置文件存储', IMAGE_UPLOAD_FAILED: '图片上传失败,请检查文件服务器后重试', FILE_PERMISSION_DENIED: '没有权限操作这个文件', FILE_API_REQUEST_FAILED: '文件服务器请求失败,请稍后重试', FILE_NOT_FOUND: '文件不存在', FILE_IS_DIRECTORY: '该路径是目录', FILE_PATH_INVALID: '非法文件路径', FILE_READ_FAILED: '文件读取失败', FILE_PATH_REQUIRED: '文件路径不能为空', TODO_TITLE_REQUIRED: '请输入待办标题', TODO_STATUS_INVALID: '无效的待办状态', TICKET_TITLE_REQUIRED: '请输入工单标题', TICKET_PROJECT_REQUIRED: '工单必须绑定一个项目', TICKET_SCHEDULE_REQUIRED: '工单必须填写排期(开始与截止日期)', TICKET_SCHEDULE_INVALID: '截止日期不能早于开始日期', TICKET_STATUS_INVALID: '无效的工单状态', AI_PROVIDER_INVALID: '无效的 AI 服务商', AI_KEY_MISSING: '尚未配置 AI API Key', AI_EMPTY_RESPONSE: 'AI 返回了空内容,请重试', TEAM_SCHEMA_MISSING: '服务器缺少团队数据表,请重新执行 init.sql', TEAM_FORBIDDEN: '没有权限执行此团队操作', TEAM_NAME_INVALID: '团队名称需为 1-64 个字符', TEAM_TIME_INVALID: '时间格式应为 HH:MM', TEAM_OWNER_CANNOT_LEAVE: '拥有者不能退出,请先解散团队', TEAM_USER_NOT_FOUND: '没有找到这个用户名的账号', TEAM_ALREADY_MEMBER: '该用户已在团队中', TEAM_ROLE_INVALID: '无效的角色设置', TEAM_TASK_TITLE_REQUIRED: '请输入任务标题', TEAM_TASK_NOT_FOUND: '任务不存在或已删除', TEAM_ASSIGNEE_INVALID: '负责人不是团队成员', TEAM_STATUS_INVALID: '无效的任务状态', TEAM_NO_ASSIGNEE: '任务还没有负责人,无法催办', TEAM_DATE_INVALID: '日期格式应为 YYYY-MM-DD', TEAM_REPORT_EMPTY: '日报内容不能为空', TEAM_NO_REPORTS: '当天还没有成员提交日报', PROFILE_EMAIL_INVALID: '邮箱格式不正确', AI_KEY_INVALID: 'API Key 无效或已过期', AI_NETWORK_ERROR: '无法连接 AI 服务,请检查网络', AI_EMPTY_MESSAGE: '请输入内容', AI_STREAM_RUNNING: '当前会话正在回复中', AI_CONVERSATION_NOT_FOUND: '会话不存在', AI_CANCELLED: '已停止回复', AI_STREAM_INTERRUPTED: 'AI 回复中断', PROJECT_PATH_NOT_FOUND: '项目目录不存在', PROJECT_PATH_UNREADABLE: '项目目录不可读取', PROJECT_PATH_NOT_DIRECTORY: '选择的路径不是目录', PROJECT_PATH_DUPLICATE: '该项目目录已经添加', PROJECT_GROUP_NOT_FOUND: '项目组不存在', PROJECT_GROUP_NAME_REQUIRED: '请输入项目组名称', PROJECT_GROUP_DEFAULT_READONLY: '默认项目组不能修改或删除', PROJECT_GROUP_DUPLICATE: '项目组名称已存在', DATABASE_NOT_READY: '数据库尚未初始化', PROJECT_SAVE_FAILED: '项目保存失败', NOT_GIT_REPOSITORY: '该目录不是 Git 仓库', GIT_NOT_INSTALLED: '系统未安装 Git', WSL_NOT_INSTALLED: '系统未安装 WSL', WSL_DISTRO_NOT_FOUND: '未找到对应 WSL 发行版', WSL_GIT_NOT_INSTALLED: 'WSL 发行版内未安装 Git', WSL_PATH_UNREADABLE: 'WSL 路径不可访问', GIT_PERMISSION_DENIED: 'Git 没有权限读取该仓库', GIT_SAFE_DIRECTORY: 'Git 拒绝读取该仓库,请检查 safe.directory 配置', GIT_REF_NOT_FOUND: '未找到该分支或引用', GIT_COMMAND_FAILED: 'Git 命令执行失败' }, task: { start: '开始分析 {project}', scan: '正在扫描文件', count: '正在统计代码', save: '正在保存统计结果', git: '正在读取 Git 历史', completed: '分析完成', failed: '分析失败', cancelled: '分析已取消', cloning: '正在克隆 {project}', cloneDone: '克隆完成', cloneFailed: '克隆失败' } } const en = { app: '年糕崽崽 PMS', dashboard: 'Dashboard', logs: 'Activity Log', settings: 'Settings', navOverview: 'Overview', navProjects: 'Projects', navWork: 'Work', navSystem: 'System', launchpad: 'Launchpad', launchpadSubtitle: 'Local service ports & app start/stop', lpMyApps: 'My apps', lpScanned: 'Detected services', lpAddApp: 'Add app', lpEditApp: 'Edit app', lpRefresh: 'Refresh', lpShowSys: 'Show system processes', lpName: 'Name', lpKind: 'Kind', lpPort: 'Port', lpDir: 'Working directory', lpStartCmd: 'Start command', lpStopCmd: 'Stop command', lpStopBlank: 'Blank = kill the process tree', lpSuggest: 'Suggested', lpStart: 'Start', lpStop: 'Stop', lpPin: 'Save as app', lpRunning: 'Running', lpStopped: 'Stopped', lpMem: 'Memory', lpNeedCmd: 'Pick or enter a start command first', lpStopConfirm: 'Stop {name}?', lpDelConfirm: 'Delete app {name}? (running process is kept)', lpEmptyApps: 'No saved apps yet — pin one from the detected list or add manually', lpEmptyScan: 'No listening services detected', workbench: 'Workbench', workbenchSubtitle: 'Favorites, today\'s tasks and quick notes', todos: 'Todos', todosSubtitle: 'Manage todos and track progress', tickets: 'Tickets', ticketsSubtitle: 'Tickets are bound to a project and schedule', calendar: 'Calendar', calendarSubtitle: 'Todo deadlines and ticket schedules', statsOverview: 'Overview', statsCollapseBtn: 'Collapse', statsExpandBtn: 'Show stats', calJumpPh: 'Go to 0501 / 2026-05-01', calJumpTitle: 'Type a date and press Enter; year defaults to the current view', calBadDate: 'Unrecognized date format', dpClear: 'Clear', messages: 'Messages', boardView: 'Board', listView: 'List', addTodo: 'New todo', editTodo: 'Edit todo', quickAddTodo: 'Quick add todo, press Enter...', todoTitle: 'Title', todoContent: 'Notes (optional)', relatedProject: 'Related project', noProject: 'No project', selectProject: 'Select a project', dueDate: 'Due', startDate: 'Start date', priorityLabel: 'Priority', statusLabel: 'Status', noTodos: 'No todos yet', todoStatus: { open: 'Open', doing: 'Doing', done: 'Done' }, priority: { low: 'Low', medium: 'Medium', high: 'High' }, addTicket: 'New ticket', editTicket: 'Edit ticket', ticketTitle: 'Ticket title', ticketDesc: 'Description', ticketTypeLabel: 'Type', ticketType: { feature: 'Feature', bug: 'Bug', task: 'Task', improvement: 'Improvement' }, ticketStatus: { open: 'Open', in_progress: 'In progress', resolved: 'Resolved', closed: 'Closed' }, ticketFlow: { start: 'Start', resolve: 'Resolve', close: 'Close', reopen: 'Reopen' }, noTickets: 'No tickets yet', today: 'Today', selectDate: 'Select a date', noSchedule: 'Nothing scheduled', weekdays: { sun: 'S', mon: 'M', tue: 'T', wed: 'W', thu: 'T', fri: 'F', sat: 'S' }, favoriteProjects: 'Favorite projects', noFavorites: 'No favorites yet. Star a project card to pin it here.', unfavorite: 'Unfavorite', favorite: 'Favorite', openTodos: 'Open todos', openTickets: 'Active tickets', todayTodos: 'Due today', noTodayTodos: 'Nothing due today', weekTickets: 'Due this week', noWeekTickets: 'No tickets due this week', notepad: 'Notepad', notepadPlaceholder: 'Jot something down, autosaved...', autoSaved: 'Autosaved', noteCenter: 'Notes', noteNew: 'New note', noteEdit: 'Edit note', noteEmpty: 'No notes yet — create one from the top right', noteUntitled: '(Blank note)', noteDeleteConfirm: 'Delete this note?', notesPage: 'Notes', notesSubtitle: '{n} notes in total, click a card to edit', noteSearchPh: 'Search notes…', viewAll: 'View all', todayTasks: 'Today', todaySubtitle: 'Everything due or in progress today', secOverdue: 'Overdue', secToday: 'Due today', secDoing: 'In progress', secUpcoming: 'Next 7 days', todayEmpty: 'Nothing needs your attention today. Enjoy!', messagesSubtitle: 'All notifications and alerts', msgUnreadOnly: 'Unread only', msgKindAll: 'All', msgKind: { todo_due: 'Todo alerts', ticket_due: 'Ticket alerts', analysis: 'Analysis', sync: 'Sync' }, aiScopeBtn: 'AI Summary', aiScopeEmpty: 'No summary yet — let AI analyze this page below.', aiScopeGen: 'Generate', aiScopeRegen: 'Regenerate', cloudPendingTitle: 'Cloud projects to bind', cloudPendingDesc: 'These projects were synced from your other computers. Pick a local folder to continue.', cloudBindBtn: 'Bind folder', cloudBindTitle: 'Bind cloud project locally', cloudBindDone: 'Cloud project bound, ready to analyze', navTeam: 'Team', teamHome: 'Team overview', teamHomeSubtitle: 'Members, roles and team settings', teamTasks: 'Team tasks', teamTasksSubtitle: 'Assign, track and nudge team tasks', teamReports: 'Team reports', teamReportsSubtitle: 'Daily reports and AI digest', teamLoginHint: 'Teams require signing in (members share one MySQL server)', teamNoneHint: 'You are not in any team yet — create one', teamNamePh: 'Team name…', teamCreateBtn: 'Create team', teamCreateMore: 'Create another team', teamCreatedToast: 'Team created', teamGoHome: 'Open team page', teamSwitcher: 'Switch team', teamSwitchedToast: 'Switched to {name}', teamName: 'Team name', teamRenameBtn: 'Rename', teamRole_owner: 'Owner', teamRole_admin: 'Admin', teamRole_member: 'Member', teamMembersCount: '{n} members', teamDigestTime: 'Digest time', teamDigestTimeHint: 'Admin clients auto-generate the daily digest after this time', teamInviteBtn: 'Invite member', teamInviteUser: 'Username', teamInviteUserPh: 'Their sign-in username', teamInviteRole: 'Role', teamInvitedToast: 'Member added', teamMakeAdmin: 'Make admin', teamMakeMember: 'Make member', teamRemoveBtn: 'Remove from team', teamRemoveConfirm: 'Remove {name} from the team?', teamLeaveBtn: 'Leave team', teamLeaveConfirm: 'Leave this team?', teamDissolveBtn: 'Dissolve team', teamDissolveConfirm: 'Dissolve "{name}"? All team tasks and reports will be deleted', teamFilter_all: 'All', teamFilter_mine: 'Assigned to me', teamFilter_created: 'Created by me', teamFilter_open: 'Open', teamTaskNew: 'New task', teamTaskEdit: 'Edit task', teamTaskTitlePh: 'What needs doing…', teamTaskDesc: 'Description (Markdown supported)', teamKindTodo: 'Todo', teamKindTicket: 'Ticket', teamQuickCreate: 'Quick create task / ticket', teamQuickCreateBtn: 'Create & assign', teamQuickFor: 'Create for {name}', teamQuickDoneToast: 'Created and assigned to {name}', teamTasksEmpty: 'No team tasks yet', teamStatus_open: 'Open', teamStatus_doing: 'In progress', teamStatus_done: 'Done', teamStatus_closed: 'Closed', teamReopen: 'Reopen', teamAssignee: 'Assignee', teamCreator: 'Creator', teamUnassigned: 'Unassigned', teamUrgeBtn: 'Nudge', teamUrgedToast: 'Reminder sent', teamUrgedAt: 'Nudged at', teamTaskDeleteConfirm: 'Delete task "{title}"?', teamSharedItems: 'Personal items shared by members', teamSharedEmpty: 'No shared todos or tickets yet', teamMyReport: 'My report', teamReportPh: 'What you did today, blockers, plan for tomorrow… (Markdown supported)', teamReportSubmitBtn: 'Submit report', teamReportUpdateBtn: 'Update report', teamReportSubmittedToast: 'Report submitted', teamReportSubmittedAt: 'Submitted at {at}', teamReportNotSubmitted: 'Not submitted yet', teamReportMissing: 'Missing', teamReportContentHidden: 'Content visible to admins only', teamQuoteDayReport: 'Quote day summary', teamQuoteDayReportHint: 'Insert the workbench end-of-day report', teamNoDayReport: 'No end-of-day summary yet — generate it on the workbench first', teamDigest: 'AI team digest', teamDigestNone: 'Summarize all member reports for the day', teamDigestAt: 'Generated {at} by {provider}', teamDigestGen: 'Generate digest', teamDigestRegen: 'Regenerate', teamDigestStartedToast: 'Generating digest, it will refresh automatically', teamDigestDoneToast: 'Team digest ready', generating: 'Generating…', prevDay: 'Previous day', nextDay: 'Next day', retry: 'Retry', loading: 'Loading…', noDescription: '(No description)', savedToast: 'Saved', profileTabInfo: 'Profile', profileTabTeams: 'My teams', profileInfoHint: 'Visible to teammates on the same server', profileNickname: 'Nickname', profileNicknamePh: 'What should we call you', profileJobTitle: 'Title', profileJobTitlePh: 'e.g. Frontend engineer', profileEmail: 'Email', profileBio: 'Bio', profileBioPh: 'One line about yourself…', profileTags: 'Tech stack tags', profileTagsPh: 'Press Enter to add…', profileTagsHint: 'Up to 20 tags; Enter or comma to add, × to remove', profileSaveBtn: 'Save profile', profileSavedToast: 'Profile saved', profileTeamsHint: 'Switch current team or create a new one', shareToTeam: 'Share with team', sharePrivate: 'Private', sharedToTeamToast: 'Sharing updated', recentMessages: 'Recent messages', noMessages: 'No messages', markAllRead: 'Mark all read', clearMessages: 'Clear messages', projects: 'Projects', addProject: 'Add project', batch: 'Analyze all', analyzingProject: 'Analyzing project', search: 'Search projects...', totalProjects: 'Projects', totalLines: 'Total lines', commits: 'Git commits', code: 'Code stats', git: 'Git analysis', structure: 'Structure', insights: 'Check', analyze: 'Analyze again', analyzeGit: 'Analyze Git again', empty: 'No data', back: 'Back', refreshProject: 'Update this project', edit: 'Edit', delete: 'Delete', add: 'Add', save: 'Save', cancel: 'Cancel', browse: 'Browse', selectWSL: 'Select WSL directory', noWSL: 'No WSL distribution found', dashboardSubtitle: 'Manage and analyze your code projects', projectName: 'Project name', projectPath: 'Project path', description: 'Description (optional)', addProjectTitle: 'Add project', editProjectTitle: 'Edit project', projectGroup: 'Project group', allProjectGroups: 'All project groups', allProjects: 'All projects', myProjectGroup: 'My project group', addProjectGroup: 'Add project group', editProjectGroup: 'Edit project group', deleteProjectGroup: 'Delete project group', projectGroupName: 'Project group name', saveProjectGroup: 'Save project group', saveProject: 'Save project', saving: 'Saving...', pathRequired: 'Please select a project directory', unanalyzed: 'Not analyzed yet', totalLineCount: 'Total lines', codeLines: 'Code lines', commentLines: 'Comment lines', blankLines: 'Blank lines', fileCount: 'Files', languageDistribution: 'Language distribution', languageDetails: 'Language details', files: 'files', lines: 'lines', workspaceBranch: 'Workspace branch', viewRef: 'Statistics view', switchView: 'View statistics', checkout: 'Switch workspace', checkoutConfirm: 'Switch the workspace to {ref}? Uncommitted or untracked changes will be rejected.', dirty: 'The worktree has uncommitted or untracked files', branchChanged: 'Workspace branch changed', commitDetail: 'Commit details', copyHash: 'Copy hash', filesChanged: 'Changed files', clearFilter: 'Clear filter', selectedDate: 'Filtered by {date}', day: 'Day', week: 'Week', month: 'Month', commitCount: 'Commits', addedLines: 'Added lines', deletedLines: 'Deleted lines', contributors: 'Contributors', activityHeatmap: 'Activity heatmap', branches: 'Branches', recentCommits: 'Recent commits', commitTrend: 'Commit trend', contributorRanking: 'Contributor ranking', commitsUnit: 'commits', gitUnavailable: 'Git data unavailable', gitUnavailableHint: 'Git history could not be read for this project. Code statistics are unaffected.', gitDetail: 'Diagnostic detail', totalFiles: 'Total files', folderCount: 'Folders', totalSize: 'Total size', largeFiles: 'Large files', directoryStructure: 'Directory structure', folderSize: 'Folder size', largeFileDetection: 'Large files (≥ 1 MB)', noLargeFiles: 'No large files found', deepInsights: 'Deep inspection', deepInsightsHint: 'Find maintainability risks from structure, source markers, and Git activity.', healthScore: 'Health', highRisk: 'high', mediumRisk: 'medium', lowRisk: 'low', issueList: 'Issues', allSeverity: 'All severity', allTypes: 'All types', noIssues: 'No issues found', insightDone: 'Inspection completed', severity: { high: 'High', medium: 'Medium', low: 'Low' }, quickSettings: 'Quick settings', language: 'Language', theme: 'Theme', themeDark: 'Dark', themeLight: 'Light', themeSystem: 'System', glassOpacity: 'Card opacity', loadingStyle: 'Analysis loading style', openSettings: 'Open settings', logSubtitle: 'View application status and errors', autoRefresh: 'Auto refresh', refresh: 'Refresh', clearLogs: 'Clear logs', allLogs: 'All logs', runLogs: 'Run logs', errorLogs: 'Error logs', totalLogs: 'Total logs', info: 'Info', warning: 'Warning', error: 'Error', noLogs: 'No logs yet', searchLogs: 'Search logs...', allCategories: 'All categories', menuFile: 'File', menuView: 'View', menuTools: 'Tools', menuHelp: 'Help', menuDatabase: 'Data management', menuQuit: 'Quit', menuAnalyzeNow: 'Analyze all now', menuNewProject: 'New project', menuAbout: 'About', winMin: 'Minimize', winMax: 'Maximize', winRestore: 'Restore', winClose: 'Close', aiChat: 'AI Analysis', aiSubtitle: 'Project-aware analysis and chat', aiSparkLite: 'Spark Lite', aiKeys: 'API keys', aiNewChat: 'New chat', aiHistory: 'History', aiNoHistory: 'No conversations yet — ask below', aiDeleteConfirm: 'Delete this conversation and all messages?', aiNoProject: 'No project', aiNeedProject: 'Select a project above first', aiQuickProject: 'Project analysis', aiQuickGit: 'Git contribution', aiQuickTodo: 'Todo analysis', aiQuickTicket: 'Ticket analysis', aiPromptProject: 'Analyze this project: tech stack, code size and quality risks, with improvement suggestions.', aiPromptGit: 'Analyze the Git contribution of this project: contributor structure, activity and hotspot risks.', aiPromptTodo: 'Analyze the todos of this project: priority sanity, overdue risks and a suggested execution order.', aiPromptTicket: 'Analyze the tickets of this project from a requirements perspective: scheduling, type distribution and handling order.', aiWelcome: 'Pick a project for quick analysis, or just ask anything', aiAskPlaceholder: 'Type a question. Enter to send, Shift+Enter for newline', aiSend: 'Send', aiStop: 'Stop', aiNoKeyTitle: 'No AI key configured', aiNoKeyHint: 'Add your Xunfei Spark or DeepSeek API key in Settings to use AI analysis.', aiConfigureNow: 'Configure', aiAnalysis: 'AI Analysis', batchSummaryTitle: 'Batch analysis summary', batchTotal: '{n} projects', batchOk: 'completed', batchFail: 'failed', batchCancelled: 'cancelled', batchDoneToast: 'Batch analysis done: {completed}/{total} succeeded', fileHotspots: 'File hotspots', fileHotspotsHint: 'Ranked by change frequency', changesUnit: 'changes', previewLoading: 'Loading file...', clickPreview: 'Click to preview file', previewBinary: 'Binary file, preview unavailable', previewTruncated: 'Large file, first 1MB shown', copyContent: 'Copy content', copied: 'Copied to clipboard', copyFailed: 'Copy failed', copyReport: 'Copy', copyDigest: 'Copy digest', settingsSubtitle: 'Manage exclusion rules, appearance and database', tabRules: 'Exclusion rules', tabAppearance: 'Appearance', tabSync: 'Account & sync', tabDatabase: 'Data', aboutSlogan: 'A local-first, all-in-one project workbench', aboutIntro: 'Niangao Zaizai PMS (NL PMS) is a local-first desktop workbench for your projects: count code size and language mix in one click, explore Git collaboration heat, manage todos, tickets and a schedule calendar, with built-in AI that explains your projects and plans your day. Data lives in local SQLite by default, and can be encrypted and synced to the cloud after signing in.', aboutFeatCode: 'Code stats', aboutFeatGit: 'Git insights', aboutFeatTask: 'Todos & tickets', aboutFeatCal: 'Schedule calendar', aboutFeatAI: 'AI analysis', aboutFeatSync: 'Encrypted sync', aboutNameTitle: 'About the name', aboutNameStory: '"NL" is short for nailao (cheese), and Niangao Zaizai is a round-faced, short-legged Minuet (Napoleon) ginger kitten who loves napping on a cheese wedge. Just like the kitten guards its cheese, NL PMS looks after every line of your code and every task on your list.', aboutFoot: 'Crafted with care · by Niangao Zaizai', addRule: 'Add exclusion rule', rulePatternPh: 'e.g. *.log, cache, temp', catGeneral: 'General', catCustom: 'Custom', ruleHint: 'Supports * wildcards; directory names match at any depth', rulesCount: '{n} rules', builtinTag: 'built-in', langThemeTitle: 'Language & theme', uiLanguage: 'Interface language', gitScopeDefault: 'Default Git scope', scopeCurrent: 'Current branch', scopeAll: 'All branches', loadingStyleOrbit: 'Energy orbit', loadingStyleOrbitDesc: 'Orbiting particles, scan beams and a core', loadingStyleGrid: 'Particle nebula', loadingStyleGridDesc: 'Particle field converging into a glowing core', loadingStyleWarp: 'Starfield rush', loadingStyleWarpDesc: 'High-speed star streaks with light trails', loadingStyleMatrix: 'The Matrix', loadingStyleMatrixDesc: 'Green digital rain pouring down', loadingStyleBar: 'Bottom progress bar', loadingStyleBarDesc: 'Keep the page, show only a bottom bar', sysIntegration: 'System integration', onWindowClose: 'When closing the window', closeToTray: 'Minimize to system tray', closeQuit: 'Quit the app', autostartLabel: 'Launch at startup', autostartOnBtn: 'Enabled — click to disable', autostartOffBtn: 'Disabled — click to enable', autostartOnHint: '年糕崽崽 PMS starts after you sign in to the system', autostartOffHint: 'Does not start with the system', autostartOnToast: 'Launch at startup enabled', autostartOffToast: 'Launch at startup disabled', autostartFail: 'Failed to update autostart: {err}', autoUpdateTitle: 'Auto-update projects', autoUpdateEnable: 'Scheduled batch analysis', optOn: 'On', optOff: 'Off', updateFreq: 'Frequency', freqDaily: 'Daily', freqNDays: 'Every N days', freqNHours: 'Every N hours', intervalN: 'Interval N', triggerTime: 'Time of day', autoUpdateHint: 'All projects are analyzed automatically at the scheduled time; results are written to the run log.', previewModeTitle: 'Browser preview mode', previewModeSync: 'The browser cannot reach MySQL. Run code-count.exe to use sync.', previewModeDb: 'The browser cannot access the local database or file pickers. Run code-count.exe to use database features.', accountSync: 'Account & sync', menuAccount: 'Account', loginOrRegister: 'Sign In / Register', registerAndLogin: 'Register & Sign In', loginModalHint: 'After signing in, todos, tickets, notes, API keys and your avatar sync across devices', switchToRegister: 'No account yet? Register now', loginWelcome: 'Welcome back', registerWelcome: 'Create your account', confirmPh: 'Repeat the password', showPassword: 'Show password', hidePassword: 'Hide password', dailyCard: 'Daily Note', dailyYi: 'Do', dailyJi: 'Avoid', dailyGotIt: 'Got it', dailyTodayBtn: "Today's note", dailyViewBtn: 'View daily note', dailyHistory: 'Past', dailyFuture: 'Notes for the future are not written yet', dailyPrevDay: 'Previous day', dailyNextDay: 'Next day', festImgTitle: 'Festival cell style (admin)', festImgSet: 'Set image', festImgReplace: 'Replace', festImgRemove: 'Remove', festImgSaved: 'Festival image saved and will sync to all users', festImgRemoved: 'Festival image removed', festModeArtCard: 'Dynamic art', festModePhotoCard: 'Custom photo', fieldUser: 'Username', fieldPass: 'Password', fieldConfirm: 'Confirm password', dqToday: 'Today', dqTomorrow: 'Tomorrow', dqDayAfter: '+2d', dqDays: '+{n}d', dqSuffix: 'd', dqAddTip: 'Add custom days', dqRemove: 'Remove label', loginTagline: 'Local-first,\nsynced everywhere', loginSecureBadge: 'API keys encrypted', switchToLogin: 'Already have an account? Sign in', usernamePh: 'Username (3-64 characters)', passwordPh: 'Password (at least 6 characters)', loginBtn: 'Sign in', loggingIn: 'Signing in…', registerBtn: 'Create account', registering: 'Creating…', syncLoginHint: 'After signing in, todos, tickets and notes sync to the server every 5 minutes; offline edits stay local and sync once you are back online.', notLoggedIn: 'Not signed in', loginNow: 'Click to sign in', pendingSync: '{n} changes waiting to sync', close: 'Close', ctxNewTodo: 'New todo', ctxNewTicket: 'New ticket', ctxNewReminder: 'Quick reminder', ctxNeedProject: 'Create a project first to add tickets', quickTitlePh: 'Type a title, press Enter', quickCreate: 'Create', quickCreated: 'Created', yesterday: 'Yesterday', justNow: 'just now', minAgo: '{n} min ago', hourAgo: '{n} h ago', dayAgo: '{n} d ago', copyBtn: 'Copy', copiedShort: 'Copied', noLogsHint: 'Application events will appear here', searchPlaceholder: 'Search projects, todos, tickets, AI chats…', searchEmpty: 'No matches found', searchHint: 'Type to search. ↑↓ to select, Enter to open', kindProject: 'Projects', kindTodo: 'Todos', kindTicket: 'Tickets', kindConversation: 'AI Chats', changePasswordTitle: 'Change password', oldPasswordLabel: 'Current password', newPasswordLabel: 'New password', confirmPasswordLabel: 'Confirm new password', changePasswordBtn: 'Apply', changingPassword: 'Changing…', passwordChangedToast: 'Password changed', passwordMismatch: 'The two new passwords do not match', changePasswordHint: 'Changing the password requires being online. Cloud API keys are re-encrypted with the key derived from the new password; other signed-in devices must sign in again with the new password to decrypt them.', profileTitle: 'Profile', avatarSource: 'Avatar source', avatarNone: 'No avatar', avatarBase64: 'Local image (stored as Base64, syncs with account)', avatarUrl: 'Image URL (OSS / CDN link)', avatarPath: 'Local file path (this device only)', avatarUrlLabel: 'Image URL', avatarUrlPh: 'e.g. https://bucket.oss-cn-hangzhou.aliyuncs.com/avatar.png', avatarPick: 'Choose image', avatarHint: 'Images are scaled down to 256px. Base64 and URL avatars sync with your account across devices; a local path avatar only shows on this device and is never synced.', contentImageStore: 'Content image storage', imgModeBase64: 'Inline Base64 (syncs with content)', imgModePath: 'Local file (this device only)', imgModeServer: 'Upload to server (visible across devices)', contentImageHint: 'Images pasted or inserted into todo / ticket content are stored this way: inline Base64 syncs to the cloud with the text; local files keep the database small but won\'t show on other devices; server uploads are referenced by URL and load anywhere. Images are compressed to 1100px max.', avatarServer: 'Upload to server (admin-configured file service)', fsTitle: 'File storage (global)', fsHint: 'Admin only: the config syncs to every account. In server mode content images and avatars upload to the file service and stay accessible across devices', fsMode: 'Storage mode', fsModeLocal: 'Local (default)', fsModeServer: 'Server (nl-pms-api)', fsBaseUrl: 'Server URL', fsApiKey: 'Upload key', fsApiKeyPh: 'Same as api_key in the nl-pms-api config', fsTestBtn: 'Test connection', fsTesting: 'Testing…', fsSaveBtn: 'Save config', fsSavedToast: 'File storage config saved; effective for everyone immediately', fsTestOkToast: 'File server reachable', tabFileStorage: 'File storage', fsPageHint: 'The config lives in the server database and takes effect immediately: every client reads it in real time when uploading images, no sync wait.', fsAdminOnlyTitle: 'Admin only', fsAdminOnlyDesc: 'Only the admin account (ID=1) can change the global file storage mode.', storageFollowHint: 'Avatar and content image storage follows the admin-managed global config. Current: {mode}.', avatarClear: 'Clear avatar', quitApp: 'Quit app', quitConfirm: 'Quit the app?', assetsTab: 'Media library', assetsHint: 'Manage images uploaded to the file server: yours, your teams\' (as owner/admin), or everything (admin id=1)', assetsNeedServer: 'Server storage is off. Once the admin switches file storage to server mode on the Sync tab, uploads show up here.', assetsScopeMine: 'My uploads', assetsScopeTeam: 'Team media', assetsScopeAll: 'Everything (admin)', assetsCount: '{n} images', assetsRefresh: 'Refresh', assetsEmpty: 'No images yet. Pictures pasted into todos / tickets or uploaded avatars will appear here.', assetsLoadMore: 'Load more', assetsCopy: 'Copy link', assetsCopiedToast: 'Link copied', assetsCopyFailed: 'Copy failed', assetsDeleteConfirm: 'Delete this image? Content referencing it will stop displaying.', assetsDeletedToast: 'Image deleted', assetsKindAvatar: 'Avatar', assetsKindContent: 'Content', mdEdit: 'Edit', mdPreviewTab: 'Preview', insertImage: 'Image', mdInserting: 'Processing…', mdPlaceholder: 'Markdown supported: **bold**, - lists, `code`, [links](url); paste images directly or use the Image button', mdEmpty: '*(no content)*', lifecycle: 'Lifecycle', lifecycleCreated: 'Created', lifecycleNow: 'In progress…', lifecycleTotal: 'Created to completion in {dur}', durMoment: 'moments', durMin: '{n} min', durHour: '{n} h', durDay: '{n} d', taskCenter: 'Task center', taskDoingGroup: 'In progress', taskPendingGroup: 'Not started', taskStart: 'Start', taskDone: 'Done', taskResolve: 'Resolve', taskEmpty: 'No pending tasks', aiBriefProject: 'AI project brief', aiBriefGit: 'AI Git brief', aiBriefStructure: 'AI structure brief', aiBriefInsights: 'AI insights brief', aiBriefEmpty: 'Not generated yet; created automatically after analysis', aiBriefGenerating: 'Generating…', aiBriefRegen: 'Regenerate', aiAskThisProject: 'Ask about this project…', aiAskGo: 'Ask AI', aiAskDrawer: 'AI Q&A', aiOpenFull: 'Full page', aiLoadEarlier: 'Load {n} earlier messages', online: 'Online', offline: 'Offline', lastSync: 'Last sync: {time}', profilePage: 'Profile', profileSubtitle: 'Account, avatar and data sync', profileSecurity: 'Account security', profileGuest: 'Sign in to sync data, set an avatar and manage your cloud profile', profileTabSync: 'Data sync', avatarEdit: 'Edit avatar', avatarModalTitle: 'Avatar & images', accountLabel: 'Account', connectionState: 'Connection', lastSyncLabel: 'Last sync', pendingLabel: 'Pending push', profileSyncHint: 'Once signed in, todos, tickets, settings, favorites, project definitions and docs sync across devices; offline edits are pushed automatically when back online.', greetMorning: 'Good morning', greetNoon: 'Good noon', greetAfternoon: 'Good afternoon', greetEvening: 'Good evening', greetNight: 'Late night', phDoneTodos: 'Todos done', phResolvedTickets: 'Tickets resolved', pwdWeak: 'Weak', pwdMedium: 'Medium', pwdStrong: 'Strong', syncScope: 'Sync scope', scopeFavs: 'Favorites', scopeDocs: 'Docs', wbAiPlan: 'AI day plan', wbPlanGen: 'Plan my day', wbPlanRegen: 'Re-plan', wbReport: 'EOD report', wbReportGen: 'EOD summary', wbGenerating: 'Generating…', wbPlanEmpty: 'Click "Plan my day" and AI will prioritize today\'s todos and tickets', wbReportEmpty: 'Before you leave, click "EOD summary" to turn today\'s work into a short report', wbAiNoKey: 'Configure an AI key to get day plans and end-of-day reports', pendingPush: '{n} pending push', neverSynced: 'Not synced yet', syncNowBtn: 'Sync now', syncingBtn: 'Syncing…', logoutBtn: 'Sign out', registerOkToast: 'Account created', loginOkToast: 'Signed in — syncing data', logoutToast: 'Signed out', syncDoneToast: 'Sync finished: pushed {pushed}, pulled {pulled}', syncFailToast: 'Sync failed', aiProviderTitle: 'AI provider', aiCurrentProvider: 'Provider', aiSparkOption: 'iFlytek Spark Lite (free)', sparkKeyLabel: 'Spark APIPassword', sparkKeyPh: 'iFlytek Open Platform → Spark Lite → HTTP service auth info', deepseekKeyLabel: 'DeepSeek API Key', deepseekKeyPh: 'platform.deepseek.com → API Keys', aiKeyHint: 'Keys stay in the local database by default. Spark Lite is free (spark-api-open.xf-yun.com); DeepSeek is pay-as-you-go (api.deepseek.com). Then open the AI Analysis page to analyze projects, Git activity, todos and tickets.', syncApiKeysLabel: 'Sync API keys to server', syncApiKeysOn: 'On (encrypted)', syncApiKeysOff: 'Off (local only)', syncApiKeysHint: 'When enabled, API keys are encrypted with a key derived from your account password (AES-256-GCM) before upload; the server cannot decrypt them. Sign in with the same account on another device to retrieve them. Requires sign-in.', dbLocation: 'Database location', dbConnected: 'Connected', dbCurrentLoc: 'Current location', dbNoPath: 'Database path unavailable', copyPathTitle: 'Copy path', dbMigratedMsg: 'Local SQLite file moved to the new location', dbMigrateFail: 'Failed to move the database file', dbPathCopied: 'Database path copied', dbCopyFail: 'Could not copy the path', migrateBtn: 'Change database file location', dbRelocateHint: 'Only moves the local SQLite file. Cloud MySQL schema must be created with init.sql; the app and frontend never run DDL migrations.', dangerZone: 'Clear data', dangerDesc: 'Choose what to clear. This cannot be undone.', clearStats: 'Clear statistics', clearStatsDesc: 'Delete analysis history, keep projects', clearProject: 'Clear one project', clearProjectDesc: 'Delete stats of a chosen project', clearAllData: 'Clear all data', clearAllDesc: 'Delete all projects and analysis history', projectIdPrompt: 'Project ID', confirmIrreversible: 'This cannot be undone. Continue?', srcLocal: 'Local folder', srcGit: 'Clone from Git', cloneUrl: 'Git repository URL', cloneDir: 'Clone into', cloneDirHint: 'Parent folder for the repository', cloneAndAdd: 'Clone & add', cloning: 'Cloning...', cloneDone: 'Cloned and added', cloneUrlRequired: 'Git URL is required', cloneDirRequired: 'Choose a folder to clone into', errors: { URL_REQUIRED: 'Git URL is required', DIR_NOT_FOUND: 'Clone folder does not exist', NAME_REQUIRED: 'Cannot infer project name; enter one manually', TARGET_NOT_EMPTY: 'Target folder already exists and is not empty', SYNC_NOT_CONFIGURED: 'Fill in the complete MySQL server configuration first', SYNC_OFFLINE: 'Cannot reach the server; check network and configuration', SYNC_SCHEMA_MISSING: 'Database or tables missing; run init.sql on the server first', SYNC_SCHEMA_OUTDATED: 'Cloud schema is outdated; re-run the latest init.sql on the server', SYNC_USER_EXISTS: 'Username already registered', SYNC_BAD_CREDENTIALS: 'Wrong username or password', SYNC_USERNAME_INVALID: 'Username must be 3-64 characters', SYNC_PASSWORD_TOO_SHORT: 'Password must be at least 6 characters', SYNC_NOT_LOGGED_IN: 'Sign in first', SYNC_OLD_PASSWORD_WRONG: 'Current password is incorrect', SYNC_IN_PROGRESS: 'Sync already in progress', SYNC_DECRYPT_FAILED: 'Could not decrypt cloud API keys; sign in again and retry', AVATAR_FILE_TOO_LARGE: 'Image exceeds 10MB, please pick a smaller one', AVATAR_DECODE_FAILED: 'Unrecognized image format (PNG / JPG / GIF / WebP supported)', FILE_STORAGE_ADMIN_ONLY: 'Only the admin (id=1) can configure file storage', FILE_STORAGE_BAD_URL: 'Invalid server URL; it must start with http(s)://', FILE_STORAGE_UNREACHABLE: 'Cannot reach the file server; check the URL and service', FILE_API_UNCONFIGURED: 'Server storage is not enabled; ask the admin to configure file storage', IMAGE_UPLOAD_FAILED: 'Image upload failed; check the file server and retry', FILE_PERMISSION_DENIED: 'You do not have permission to manage this file', FILE_API_REQUEST_FAILED: 'File server request failed; try again later', FILE_NOT_FOUND: 'File does not exist', FILE_IS_DIRECTORY: 'The path is a directory', FILE_PATH_INVALID: 'Invalid file path', FILE_READ_FAILED: 'Failed to read file', FILE_PATH_REQUIRED: 'File path is required', TODO_TITLE_REQUIRED: 'Todo title is required', TODO_STATUS_INVALID: 'Invalid todo status', TICKET_TITLE_REQUIRED: 'Ticket title is required', TICKET_PROJECT_REQUIRED: 'A ticket must be bound to a project', TICKET_SCHEDULE_REQUIRED: 'A ticket requires start and due dates', TICKET_SCHEDULE_INVALID: 'Due date cannot be before start date', TICKET_STATUS_INVALID: 'Invalid ticket status', AI_PROVIDER_INVALID: 'Invalid AI provider', AI_KEY_MISSING: 'No AI API key configured', AI_EMPTY_RESPONSE: 'AI returned empty content, please retry', TEAM_SCHEMA_MISSING: 'Server is missing team tables — run init.sql again', TEAM_FORBIDDEN: 'You do not have permission for this team action', TEAM_NAME_INVALID: 'Team name must be 1-64 characters', TEAM_TIME_INVALID: 'Time must be HH:MM', TEAM_OWNER_CANNOT_LEAVE: 'The owner cannot leave — dissolve the team instead', TEAM_USER_NOT_FOUND: 'No account with that username', TEAM_ALREADY_MEMBER: 'Already a team member', TEAM_ROLE_INVALID: 'Invalid role', TEAM_TASK_TITLE_REQUIRED: 'Task title is required', TEAM_TASK_NOT_FOUND: 'Task not found or deleted', TEAM_ASSIGNEE_INVALID: 'Assignee is not a team member', TEAM_STATUS_INVALID: 'Invalid task status', TEAM_NO_ASSIGNEE: 'Task has no assignee to nudge', TEAM_DATE_INVALID: 'Date must be YYYY-MM-DD', TEAM_REPORT_EMPTY: 'Report content is required', TEAM_NO_REPORTS: 'No reports submitted for that day yet', PROFILE_EMAIL_INVALID: 'Invalid email address', AI_KEY_INVALID: 'API key invalid or expired', AI_NETWORK_ERROR: 'Cannot reach the AI service, check your network', AI_EMPTY_MESSAGE: 'Message is empty', AI_STREAM_RUNNING: 'This conversation is already replying', AI_CONVERSATION_NOT_FOUND: 'Conversation not found', AI_CANCELLED: 'Reply stopped', AI_STREAM_INTERRUPTED: 'AI reply interrupted', PROJECT_PATH_NOT_FOUND: 'Project directory does not exist', PROJECT_PATH_UNREADABLE: 'Project directory is not readable', PROJECT_PATH_NOT_DIRECTORY: 'Selected path is not a directory', PROJECT_PATH_DUPLICATE: 'This project path already exists', PROJECT_GROUP_NOT_FOUND: 'Project group does not exist', PROJECT_GROUP_NAME_REQUIRED: 'Enter a project group name', PROJECT_GROUP_DEFAULT_READONLY: 'The default project group cannot be changed or deleted', PROJECT_GROUP_DUPLICATE: 'Project group name already exists', DATABASE_NOT_READY: 'Database is not initialized', PROJECT_SAVE_FAILED: 'Failed to save project', NOT_GIT_REPOSITORY: 'This directory is not a Git repository', GIT_NOT_INSTALLED: 'Git is not installed', WSL_NOT_INSTALLED: 'WSL is not installed', WSL_DISTRO_NOT_FOUND: 'WSL distribution was not found', WSL_GIT_NOT_INSTALLED: 'Git is not installed inside the WSL distribution', WSL_PATH_UNREADABLE: 'WSL path is not accessible', GIT_PERMISSION_DENIED: 'Git does not have permission to read this repository', GIT_SAFE_DIRECTORY: 'Git refused this repository; check safe.directory', GIT_REF_NOT_FOUND: 'Branch or ref was not found', GIT_COMMAND_FAILED: 'Git command failed' }, task: { start: 'Analyzing {project}', scan: 'Scanning files', count: 'Counting code', save: 'Saving statistics', git: 'Reading Git history', completed: 'Analysis completed', failed: 'Analysis failed', cancelled: 'Analysis cancelled', cloning: 'Cloning {project}', cloneDone: 'Clone completed', cloneFailed: 'Clone failed' } } const saved = JSON.parse(localStorage.getItem('cc-settings') || '{}') let theme = new URLSearchParams(location.search).get('theme') || saved.theme || 'dark' if (theme === 'system') theme = matchMedia('(prefers-color-scheme: dark)').matches ? 'dark' : 'light' document.documentElement.dataset.theme = theme document.documentElement.style.setProperty('--glass-user-opacity', String((saved.glassOpacity || 55) / 100)) const i18n = createI18n({ legacy: false, locale: saved.locale || 'zh-CN', fallbackLocale: 'en', messages: { 'zh-CN': zh, en } }) const router = createRouter({ history: createWebHashHistory(), routes: [ { path: '/', component: Workbench }, { path: '/projects', component: Dashboard }, { path: '/project/:id', component: ProjectDetail }, { path: '/todos', component: Todos }, { path: '/tickets', component: Tickets }, { path: '/calendar', component: CalendarPage }, { path: '/ai', component: AIChat }, { path: '/logs', component: Logs }, { path: '/settings', component: Settings }, { path: '/profile', component: Profile }, { path: '/launchpad', component: Launchpad }, { path: '/messages', component: Messages }, { path: '/today', component: Today }, { path: '/notes', component: Notes }, { path: '/team', component: TeamHome }, { path: '/team/tasks', component: TeamTasks }, { path: '/team/reports', component: TeamReports } ] }) createApp(App).use(createPinia()).use(router).use(i18n).mount('#app')