diff --git a/plugins/minimax-ai/minimax-pet/.minimax-plugin/plugin.json b/plugins/minimax-ai/minimax-pet/.minimax-plugin/plugin.json index db1d5a21..7df6138b 100644 --- a/plugins/minimax-ai/minimax-pet/.minimax-plugin/plugin.json +++ b/plugins/minimax-ai/minimax-pet/.minimax-plugin/plugin.json @@ -2,7 +2,7 @@ "schemaVersion": 1, "name": "minimax-pet", "displayName": "MiniMax Pet", - "version": "0.8.0", + "version": "0.9.0", "description": "轻量桌宠,聚合主窗口可见会话的任务状态、需要回答的提醒和最新结果,支持外观切换及图集导入。", "author": "MiniMax", "icon": "icon.png", diff --git a/plugins/minimax-ai/minimax-pet/README.md b/plugins/minimax-ai/minimax-pet/README.md index f29b9bd2..f1ce6fb5 100644 --- a/plugins/minimax-ai/minimax-pet/README.md +++ b/plugins/minimax-ai/minimax-pet/README.md @@ -2,19 +2,26 @@ English | [简体中文](README.zh-CN.md) -A lightweight desktop companion for MiniMax Code, by **MiniMax**. It shows visible conversation activity, answer-needed reminders and recent results without creating another chat. The interface is currently in Chinese. +A lightweight desktop companion for MiniMax Code, by **MiniMax**. It shows visible conversation activity, answer-needed reminders and recent results without creating another chat. The interface is available in Simplified Chinese and English. ## Features - **Appearance:** preview and select pets, import compatible pet JSON and spritesheets, and remove imported pets. - **Tasks:** recent reminders, visible user conversations, and a scrollable latest reply rendered as Markdown. Desktop cards preview up to five lines. Clicking a card keeps the pet visible; observed `ask_user` calls show “需要你回答” (needs your answer). - **Open the conversation:** on MiniMax Code 3.1.11 or later, a task's detail, in the panel and on the desktop, has a **打开对话** (open conversation) button, labelled **去回答** (go answer) while the task needs an answer. It brings the MiniMax Code main window forward on that conversation. Older clients do not show the button. -- **Settings:** pet size, page-local reminder dismissal and mute, connection status, and reconnect. +- **Language:** the panel and desktop copy follow the MiniMax Code interface language on 3.1.11 or later, or the system language when the client does not report one, and can be pinned in **Settings**. Older clients fall back to the system language. +- **Settings:** pet size, interface language, how long desktop cards stay, page-local reminder dismissal and mute, connection status, and reconnect. - **Desktop window:** a draggable transparent pet with compact notification cards. Return it to the main panel to use the three tabs. - **Theme:** on MiniMax Code 3.1.11 or later the panel and desktop cards follow the client's light or dark theme; older clients follow the system setting. The pet does not run Agent turns, create independent chats, write to chat inputs, comment on every session, stop tasks, or answer questionnaires. Selecting a task only reads its details; the main window switches only when you click **打开对话** or **去回答**. +Version **0.9.0** adds two settings. **Task notifications** sets how many seconds a desktop card stays after a task finishes or fails, entered as a number and saved on the device; the default is 10 and **0** keeps the card until you close it. A card hiding never marks the task read, cards that need your answer never hide, and pointing at or focusing the cards pauses the countdown. **Language** follows the MiniMax Code interface language by default and can be pinned to 简体中文 or English; the whole panel and desktop copy switch, while conversation titles and replies stay as the Agent wrote them. + +Version **0.9.0** also gives the desktop window one fixed size, tall enough for the open card. Earlier versions grew the window when a card opened, which moved it on screen and made the pet jump for a frame; now opening or closing a card only shows or hides it. The area above the pet is transparent and passes clicks through to whatever is behind it. + +Version **0.8.1** fixes the desktop card: the title truncates to one line, only the reply scrolls, and the actions stay pinned, so a long title or reply no longer pushes 返回任务 / 打开对话 out of the card. Expanding the card also grows the window instead of squeezing the reply into one line. + Version **0.8.0** aligns the panel with the MiniMax Code design language: 24px page margins and section gaps, 14px/22px body copy with 12px/18px supporting text, 32px minimum control height, 12px cards and 8px controls, 0.5px borders on desktop overlays, and press/focus states. The desktop pet keeps its 280px layout with the same tokens. Version **0.7.0** renders each task's latest reply as Markdown: headings, emphasis, lists, task lists, fenced code, tables, quotes and links, with raw HTML shown as inert text and images as a label instead of a remote request. Notification cards and reminder previews stay plain text. Parsing uses a vendored bundle of unified, remark-parse and remark-gfm (MIT) built by `npm run build:markdown`; the page never builds an HTML string from model output. The bundle makes no network request and runs entirely in the page. @@ -39,6 +46,7 @@ Earlier local prototypes use a different plugin ID. The Host gives `minimax-pet` - **打开对话** / **去回答** uses `host.navigation.open` and the theme uses `host.ui.subscribe`, both added in MiniMax Code **3.1.11**; on 2026-11-10 the macOS and Windows installers of 3.1.11 answered on both production CDNs. Both calls were checked against the MiniMax Code source: opening a conversation the Mini App already holds needs no `hostAccess` and no extra grant, must start inside a click, and brings the main window forward. - An earlier UI version was exercised on macOS in a MiniMax Code development build: three tabs, wide/narrow layouts, task reading, real session/ask activity, import/delete, and desktop round trips. Version 0.6.0 has Node/DOM tests (including the open and theme paths against a simulated Host), an isolated HTTP smoke test, and a browser check of the panel layout outside the Host. - The default atlas is checked by actual PNG decoding, frame coverage and deterministic rebuild tests. The separate octopus icon is checked by PNG decoding and visual review. On 2026-10-26, the packaged Skill was exercised with a real Agent in the macOS development build to create, validate, preview and import a custom v2 pet; all nine animation previews and the four cardinal mouse-look directions were checked. This does not replace released-client acceptance. +- Version 0.9.0 has Node/DOM tests for the card timer, the preference round trip and the copy of both languages (every key the page uses must exist in both), plus a browser check of the panel and desktop window in both languages against a simulated Host. Not verified for 0.9.0: the language the real client reports on any given install, and both languages inside a real MiniMax Code window. - Not verified for 0.6.0: opening a conversation or following the theme inside a real MiniMax Code 3.1.11+ window (panel or desktop pet), any Windows GUI, and released-client installation or GUI acceptance on 3.1.6. Automated tests use synthetic data and do not prove those paths. ## Data & access @@ -49,7 +57,7 @@ Earlier local prototypes use a different plugin ID. The Host gives `minimax-pet` - **Local files:** the Node service serves packaged assets and stores preferences and user-imported JSON/WebP/PNG spritesheets under opaque `context.dataDir`. Imports, replacement recovery and deletion are limited to that plugin data. Browser storage is used only to migrate old view preferences. - **Rendered replies:** the latest reply is parsed as Markdown by the vendored parser and rebuilt as DOM nodes under the page's own stylesheet. Raw HTML in a reply is displayed as literal text, never executed or inserted as markup; links show their target on hover and are not opened; images are not fetched and appear as a text label. - **Host navigation:** only when you click **打开对话** or **去回答**, the page calls `host.navigation.open({ id: 'session', sessionId, source: 'local' })` for a conversation it already lists through `session.read`. It does not declare `hostAccess` and never reads which page of MiniMax Code you are on. It writes nothing to chat inputs and sends no message. -- **Display preferences:** `host.ui.subscribe` reports the client theme, which the page applies; the reported language is ignored. No grant is involved. +- **Display preferences:** `host.ui.subscribe` reports the client theme and interface language; the page applies the theme and, while the language setting is **Follow MiniMax Code**, the language. Choosing 简体中文 or English overrides it. The preference is stored with the other settings in the plugin data directory. No grant is involved. - **Network:** the plugin has no hard-coded external runtime endpoint; its own HTTP requests use its assigned loopback origin. - **Microphone, camera and notifications:** not used and not declared. - **Processes and Agent turns:** the Host starts one Node process. The plugin spawns no additional runtime process, launches no shell command, offers no MCP server, and never calls `agent.run`. diff --git a/plugins/minimax-ai/minimax-pet/README.zh-CN.md b/plugins/minimax-ai/minimax-pet/README.zh-CN.md index 45f799bc..939c0231 100644 --- a/plugins/minimax-ai/minimax-pet/README.zh-CN.md +++ b/plugins/minimax-ai/minimax-pet/README.zh-CN.md @@ -8,7 +8,9 @@ 将仓库 `plugins/minimax-ai/minimax-pet/` 完整复制到 `/plugins/minimax-pet/`,包括隐藏的 `.minimax-plugin`;默认 `` 为 `~/.minimax`,自定义数据目录时采用实际配置。不要复制外层作者目录。重启 MiniMax Code、确认插件启用后,从 MiniApp 列表打开“MiniMax Pet”,或对 Agent 说“打开 MiniMax Pet”。点击“放到桌面”显示透明桌宠窗口。 -外观页支持选择、导入预览和删除已导入宠物;任务页显示提醒、全部可见任务和按 Markdown 渲染的最近回复;设置页提供大小、提醒管理和重连。桌面通知最多预览五行,点击后可滚动读完整内容,桌宠保持可见。观察到 ask 时显示“需要你回答”。 +外观页支持选择、导入预览和删除已导入宠物;任务页显示提醒、全部可见任务和按 Markdown 渲染的最近回复;设置页提供大小、语言、通知时长、提醒管理和重连。 + +面板和桌面文案在 MiniMax Code 3.1.11 及以上跟随客户端界面语言;客户端不报告语言时跟随系统语言,也可以在设置页固定为简体中文或 English。旧版本客户端跟随系统语言。桌面通知最多预览五行,点击后可滚动读完整内容,桌宠保持可见。观察到 ask 时显示“需要你回答”。 在 MiniMax Code 3.1.11 及以上版本,面板和桌面上的任务详情都有“打开对话”按钮,任务需要回答时显示为“去回答”;点一下会把 MiniMax Code 主窗口切到这个对话。面板和桌面卡片的明暗也跟随 MiniMax Code 的主题。旧版本客户端不显示该按钮,明暗跟随系统设置。 @@ -16,6 +18,12 @@ **0.5.11** 新增可选内置宠物 **MMX 章鱼娘**,是一位橙发、白背心、棕格纹短裙的 Q 版全身章鱼娘。在“外观 → 选择宠物”中选择她即可;薄荷色机器人仍为默认宠物。她采用 v1 图集,包含九种动作,不含十六个视线方向,因此鼠标跟随对她不生效;沿用六帧、6.6 秒一轮的慢速待机,不新增权限或改变播放器。图集已通过校验。创作流程和贡献者许可见[素材说明](ARTWORK.md)。 +**0.9.0** 新增两项设置。“任务通知”用数字框设置桌面卡片在任务完成或异常后显示多少秒,默认 10 秒,填 0 表示一直显示到手动关闭;设置保存在本机。卡片自动收起不会把任务标记为已读,需要你回答的卡片不会自动收起,鼠标悬停或焦点停留时会暂停计时。“语言”默认跟随 MiniMax Code 的界面语言,也可以固定为简体中文或 English;面板和桌面文案整体切换,对话标题和回复保持 Agent 原文。 + +**0.9.0** 把桌面窗口固定成一个尺寸,本来就放得下展开的卡片。之前点开卡片会把窗口撑高,窗口在屏幕上跟着移动,桌宠会跳一下;现在开合卡片只显示或隐藏,不动窗口。桌宠上方是透明区域,点击会穿透到底下的窗口。 + +**0.8.1** 修复桌面卡片:标题单行截断,只有回复区滚动,底部操作固定在卡片内;长标题或长回复不再把「返回任务 / 打开对话」顶出卡片。展开卡片时窗口同步变高,回复不会被压成一行。 + **0.8.0** 按 MiniMax Code 的设计语言对齐面板:24px 页边距与区块间距、14px/22px 正文与 12px/18px 辅助文字、控件最小高度 32px、卡片 12px 与控件 8px 圆角、桌面浮层 0.5px 描边,并补齐按下与焦点状态。桌面宠物保持 280px 布局,沿用同一套 token。 **0.7.0** 把任务最近回复按 Markdown 渲染:标题、强调、列表、任务清单、代码块、表格、引用和链接;回复里的原始 HTML 只作为文本显示,图片显示为文字标签而不发起网络请求。通知卡片和提醒预览仍为纯文本。解析使用 `npm run build:markdown` 打包的 unified + remark-parse + remark-gfm(MIT);页面不会把模型输出拼成 HTML 字符串。 diff --git a/plugins/minimax-ai/minimax-pet/miniapp/client/activity.mjs b/plugins/minimax-ai/minimax-pet/miniapp/client/activity.mjs index 53c91ef6..c9a617dc 100644 --- a/plugins/minimax-ai/minimax-pet/miniapp/client/activity.mjs +++ b/plugins/minimax-ai/minimax-pet/miniapp/client/activity.mjs @@ -1,4 +1,5 @@ // Public Page Agent adapter. No Host activity projection, private IPC or inferred asks/unread state. +import { t } from './i18n.mjs'; export const STATUS_ANIM = Object.freeze({ idle: 'idle', running: 'running', error: 'failed', aborted: 'idle', completed: 'review' }); const RAW_STATUSES = new Set(['idle', 'running', 'error', 'aborted']); const PRIORITY = ['error', 'completed', 'running', 'aborted', 'idle']; @@ -10,7 +11,7 @@ const idOf = (value) => typeof value === 'string' && value.length > 0 && value.l export function normalizeSession(raw, { detail = false } = {}) { if (!raw || typeof raw !== 'object' || !idOf(raw.sessionId) || !RAW_STATUSES.has(raw.status) || !Number.isSafeInteger(raw.updatedAtMs) || raw.updatedAtMs < 0 || (raw.title !== undefined && typeof raw.title !== 'string')) throw invalid(); - const session = { sessionId: raw.sessionId, title: (raw.title || '未命名对话').slice(0, 1000), status: raw.status, updatedAtMs: raw.updatedAtMs }; + const session = { sessionId: raw.sessionId, title: (raw.title || t('desk.untitled')).slice(0, 1000), status: raw.status, updatedAtMs: raw.updatedAtMs }; if (detail) { if (typeof raw.error?.message === 'string') session.error = raw.error.message; if (typeof raw.lastAssistantText === 'string') session.lastAssistantText = raw.lastAssistantText; @@ -152,7 +153,7 @@ export function createActivitySource({ agent, onChange, pollIntervalMs = 5000, n removedAt.delete(event.sessionId); const previous = rows.get(event.sessionId); const status = outcome === 'finished' ? 'idle' : outcome; - rows.set(event.sessionId, { sessionId: event.sessionId, title: typeof event.title === 'string' && event.title ? event.title.slice(0, 1000) : previous?.title || '未命名对话', status, updatedAtMs: now(), liveCompleted: outcome === 'finished' }); + rows.set(event.sessionId, { sessionId: event.sessionId, title: typeof event.title === 'string' && event.title ? event.title.slice(0, 1000) : previous?.title || t('desk.untitled'), status, updatedAtMs: now(), liveCompleted: outcome === 'finished' }); emit({}); if (state.selectedSessionId === event.sessionId) void readDetail(epoch); } diff --git a/plugins/minimax-ai/minimax-pet/miniapp/client/compact-pet-view.mjs b/plugins/minimax-ai/minimax-pet/miniapp/client/compact-pet-view.mjs index ba2f2db6..9a573130 100644 --- a/plugins/minimax-ai/minimax-pet/miniapp/client/compact-pet-view.mjs +++ b/plugins/minimax-ai/minimax-pet/miniapp/client/compact-pet-view.mjs @@ -1,6 +1,8 @@ import { markdownToPlainText, renderMarkdown, resetMarkdown } from './markdown.mjs'; +import { t } from './i18n.mjs'; -const STATUS = { running: '进行中', error: '异常', completed: '已完成', aborted: '已停止', idle: '空闲', needs_answer: '需要你回答' }; +const STATUS_KEYS = { running: 'desk.statusRunning', error: 'status.error', completed: 'status.completed', aborted: 'status.aborted', idle: 'status.idle', needs_answer: 'status.needsAnswer' }; +const statusLabel = (status) => (STATUS_KEYS[status] ? t(STATUS_KEYS[status]) : t('desk.toView')); const textOf = (value) => typeof value === 'string' ? value : ''; const failureCode = (error) => textOf(error?.code) || textOf(error?.message) || textOf(error) || 'result_unknown'; // A completed live turn returns to raw idle. Only that observed completion @@ -55,39 +57,45 @@ export function createCompactPetView({ if (disposed) return; api.close(); void call(onClose); }); - closeButton.setAttribute('aria-label', '关闭桌宠快捷面板'); header.append(state, closeButton); const tasksPane = element('div', 'compact-tasks compact-body'); const taskList = element('ul', 'compact-task-list'); - const empty = element('li', 'compact-empty', '暂无新任务提醒'); + const empty = element('li', 'compact-empty', t('desk.emptyRecent')); const tasksFooter = element('div', 'compact-footer'); - const allTasks = button('全部任务', 'compact-link', () => api.open('allTasks')); + const allTasks = button('', 'compact-link', () => api.open('allTasks')); tasksFooter.append(allTasks); tasksPane.append(taskList, tasksFooter); - const detailPane = element('div', 'compact-detail compact-body'); + const detailPane = element('div', 'compact-detail'); + const detailHead = element('div', 'compact-detail-head'); const detailTitle = element('strong', 'compact-detail-title'); const detailStatus = element('span', 'compact-detail-status'); detailStatus.setAttribute('role', 'status'); + detailHead.append(detailTitle, detailStatus); + // Only the reply scrolls; the title and the actions stay pinned so a long title or a + // long reply can never push the buttons out of the 280px card. + const detailBody = element('div', 'compact-detail-body'); const detailReply = element('div', 'compact-detail-reply md'); - detailReply.setAttribute('aria-label', '任务最近回复'); detailReply.tabIndex = 0; + detailReply.tabIndex = 0; + detailBody.append(detailReply); const detailFooter = element('div', 'compact-footer'); - const back = button('返回任务', 'compact-detail-back compact-link', () => api.open(detailBackView)); + const back = button('', 'compact-detail-back compact-link', () => api.open(detailBackView)); const openHint = element('p', 'compact-hint'); openHint.setAttribute('role', 'status'); openHint.hidden = true; const setHint = (text, kind = 'info') => { setText(openHint, text); openHint.dataset.kind = kind; openHint.hidden = !openHint.textContent; }; // The Host checks user activation, so the callback must run synchronously inside this click. - const openSession = button('打开对话', 'compact-open', () => { + const openSession = button(t('tasks.open'), 'compact-open', () => { const { id } = selection(); if (disposed || !id || typeof onOpenSession !== 'function') return; const token = ++openToken; - openSession.disabled = true; setHint('正在打开对话…'); + openSession.disabled = true; setHint(t('desk.openHint')); void call(onOpenSession, id).then((result) => { if (disposed || token !== openToken) return; openSession.disabled = false; const failed = result?.ok === false; - setHint(textOf(result?.text) || (failed ? `没能打开对话(${failureCode(result)})。` : ''), textOf(result?.kind) || (failed ? 'error' : 'info')); + setHint(textOf(result?.text) || (failed ? t('desk.openFailed', { code: failureCode(result) }) : ''), textOf(result?.kind) || (failed ? 'error' : 'info')); }); }); - detailFooter.append(back, openSession); detailPane.append(detailTitle, detailStatus, detailReply, detailFooter, openHint); + detailFooter.append(back, openSession); + detailPane.append(detailHead, detailBody, detailFooter, openHint); container.append(header, tasksPane, detailPane); function paintTasks() { @@ -112,15 +120,15 @@ export function createCompactPetView({ row.node.append(row.title, row.status, row.preview); rows.set(item.sessionId, row); } row.item = item; - setText(row.title, textOf(item.title) || '未命名任务'); - row.title.setAttribute('aria-label', `查看任务:${textOf(item.title) || '未命名任务'}`); - setText(row.preview, item.needsAnswer === true ? '需要你回答' : markdownToPlainText(textOf(item.previewText))); row.preview.hidden = !row.preview.textContent; + setText(row.title, textOf(item.title) || t('desk.untitled')); + row.title.setAttribute('aria-label', t('desk.taskAria', { title: textOf(item.title) || t('desk.untitled') })); + setText(row.preview, item.needsAnswer === true ? t('status.needsAnswer') : markdownToPlainText(textOf(item.previewText))); row.preview.hidden = !row.preview.textContent; const status = taskStatus(item); - setText(row.status, STATUS[status] || '待查看'); row.status.dataset.status = status; + setText(row.status, statusLabel(status)); row.status.dataset.status = status; row.title.disabled = typeof onSelectTask !== 'function'; if (taskList.children[index] !== row.node) taskList.insertBefore(row.node, taskList.children[index] || null); } - setText(empty, view === 'allTasks' ? '暂无可见任务' : '暂无新任务提醒'); + setText(empty, view === 'allTasks' ? t('desk.emptyAll') : t('desk.emptyRecent')); allTasks.hidden = view === 'allTasks'; if (!visible.length && empty.parentNode !== taskList) taskList.append(empty); } @@ -129,19 +137,19 @@ export function createCompactPetView({ const { id, session, detail, available } = selection(); const needsAnswer = available && session?.needsAnswer === true; const status = taskStatus(session) || taskStatus(detail); - setText(detailTitle, textOf(session?.title) || textOf(detail?.title) || '任务回复'); - setText(detailStatus, !available ? '暂不可用' : STATUS[status] || '待查看'); + setText(detailTitle, textOf(session?.title) || textOf(detail?.title) || t('desk.reply')); + setText(detailStatus, available ? statusLabel(status) : t('desk.unavailableShort')); detailStatus.dataset.status = !available ? 'unavailable' : status; let text; - if (!available) text = '任务信息暂不可用,重新连接后继续查看。'; - else if (needsAnswer) text = '需要你回答'; + if (!available) text = t('desk.unavailable'); + else if (needsAnswer) text = t('status.needsAnswer'); // A background refresh keeps the matching reply mounted; a short loading // placeholder would shrink its scroll range and reset a reader's position. - else if (taskSelection.detailLoading && !detail) text = '正在读取任务…'; - else if (taskSelection.detailError) text = `无法读取任务(${failureCode(taskSelection.detailError)})。`; - else if (!id) text = '选择任务后查看回复。'; - else if (!detail) text = '正在读取任务…'; - else text = (detail.status === 'error' && textOf(detail.error)) || textOf(detail.lastAssistantText) || '该任务暂时没有可读取的最后回复。'; + else if (taskSelection.detailLoading && !detail) text = t('desk.loading'); + else if (taskSelection.detailError) text = t('desk.replyFailed', { code: failureCode(taskSelection.detailError) }); + else if (!id) text = t('desk.chooseTask'); + else if (!detail) text = t('desk.loading'); + else text = (detail.status === 'error' && textOf(detail.error)) || textOf(detail.lastAssistantText) || t('desk.replyEmpty'); // Status copy stays plain text; an actual reply is markdown. renderMarkdown keeps // the DOM when the text is unchanged and restores the scroll offset when a // streaming reply replaces it. @@ -152,19 +160,27 @@ export function createCompactPetView({ } detailPane.setAttribute('aria-busy', String(available && !needsAnswer && taskSelection.detailLoading === true)); if (paintedDetailId !== id) { - detailReply.scrollTop = 0; detailPane.scrollTop = 0; paintedDetailId = id; + detailReply.scrollTop = 0; detailBody.scrollTop = 0; paintedDetailId = id; openToken++; openSession.disabled = false; setHint(''); } openSession.hidden = typeof onOpenSession !== 'function' || !available || !id; - setText(openSession, needsAnswer ? '去回答' : '打开对话'); - openSession.setAttribute('aria-label', `${needsAnswer ? '去回答' : '打开对话'}:${textOf(session?.title) || textOf(detail?.title) || '该任务'}`); + setText(openSession, needsAnswer ? t('tasks.goAnswer') : t('tasks.open')); + openSession.setAttribute('aria-label', t('desk.openAria', { + action: needsAnswer ? t('tasks.goAnswer') : t('tasks.open'), + title: textOf(session?.title) || textOf(detail?.title) || t('tasks.openFallback'), + })); } function paint() { if (disposed) return; + // Labels follow the interface language, which the page resolves after this view exists. + closeButton.setAttribute('aria-label', t('desk.closePanel')); + detailReply.setAttribute('aria-label', t('desk.replyAria')); + setText(allTasks, t('tasks.all')); + setText(back, t('tasks.back')); container.hidden = !opened; tasksPane.hidden = !['tasks', 'allTasks'].includes(view); detailPane.hidden = view !== 'detail'; - setText(state, view === 'tasks' ? '最近任务' : view === 'allTasks' ? '全部可见任务' : '任务回复'); + setText(state, view === 'tasks' ? t('desk.panelState.tasks') : view === 'allTasks' ? t('desk.panelState.allTasks') : t('desk.panelState.detail')); paintTasks(); paintDetail(); } diff --git a/plugins/minimax-ai/minimax-pet/miniapp/client/i18n.mjs b/plugins/minimax-ai/minimax-pet/miniapp/client/i18n.mjs new file mode 100644 index 00000000..18ed5be4 --- /dev/null +++ b/plugins/minimax-ai/minimax-pet/miniapp/client/i18n.mjs @@ -0,0 +1,596 @@ +// MiniMax Pet — one place for user-visible copy. Keys are `domain.context.name`, values +// use `{{name}}` interpolation, matching the MiniMax Code copy conventions. English copy is +// sentence case and short; Chinese copy stays the authored voice. + +export const LANGUAGES = Object.freeze(['zh-Hans', 'en']); +const FALLBACK = 'zh-Hans'; + +export const MESSAGES = Object.freeze({ + 'zh-Hans': { + // App shell + 'app.title': 'MiniMax Pet', + 'app.tagline': '陪伴工作,留意任务的新动静。', + 'app.tabAppearance': '外观', + 'app.tabTasks': '任务', + 'app.tabSettings': '设置', + 'app.popOut': '放到桌面', + 'app.popOutBusy': '正在弹出…', + 'app.hostNote.browser': '普通浏览器没有任务和窗口能力;可用 ?demo=1 手动预览资源动作。', + 'app.hostNote.noWindow': '当前 MiniMax Code 版本不支持弹出窗口,只能在面板里使用。', + 'app.fatal.player': '播放器加载失败({{code}})', + 'app.fatal.boot': '启动失败({{code}})', + + // Status words + 'status.idle': '空闲', + 'status.running': '工作中', + 'status.error': '异常', + 'status.aborted': '已停止', + 'status.completed': '已完成', + 'status.needsAnswer': '需要你回答', + 'status.idleNow': '空闲中', + 'status.runningText': '正在工作…', + 'status.errorText': '任务出现异常', + 'status.abortedText': '任务已停止', + 'status.completedText': '任务已完成', + 'status.currentHint': '当前提示', + 'status.needsAnswerFor': '「{{title}}」需要你回答', + + // Availability + 'availability.connecting': '正在连接本机任务…', + 'availability.connectingShort': '连接中', + 'availability.notPermitted': '请先在插件详情中允许读取会话,再重试。', + 'availability.notPermittedShort': '未授权', + 'availability.readNotPermitted': '未授权读取会话', + 'availability.unavailable': '暂时无法读取本机任务{{reason}}。', + 'availability.reason': '({{code}})', + 'availability.unavailableShort': '不可用', + 'availability.sessionUnavailable': '会话状态不可用', + 'availability.reconnectHint': '{{text}} 可在设置中重新连接。', + 'availability.demo': '资源预览', + 'availability.real': '真实会话', + + // Appearance + 'appearance.preview': '动画预览', + 'appearance.loading': '正在加载宠物…', + 'appearance.loadFailed': '宠物没加载出来', + 'appearance.choose': '选择宠物', + 'appearance.import': '导入宠物…', + 'appearance.use': '使用 {{name}}', + 'appearance.delete': '删除', + 'appearance.deletePet': '删除 {{name}}', + 'appearance.bundled': '内置', + 'appearance.imported': '导入', + 'appearance.inUse': '使用中', + 'appearance.v2': 'v2·可看向鼠标', + 'appearance.noPets': '还没有可用的宠物', + 'appearance.noPetsReason': '没有可用的宠物', + 'appearance.noPetId': '没有 id 为 {{id}} 的宠物', + 'appearance.loadingPet': '正在加载「{{name}}」…', + 'appearance.loadFailedDetail': '{{name}}加载失败({{code}}){{detail}}', + 'appearance.confirmDelete': '删除导入的宠物「{{name}}」?删除后不能恢复。', + 'appearance.deleted': '已删除「{{name}}」', + 'appearance.deleting': '正在删除「{{name}}」…', + 'appearance.deleteCancelled': '已取消删除', + 'appearance.deleteFailed': '删除失败({{code}}){{detail}}', + 'appearance.listFailed': '读不到宠物列表', + 'appearance.thumbFailed': '图集无法预览', + 'appearance.looks': '(支持看向鼠标)', + + // Import + 'appearance.readListFailed': '读不到宠物列表', + 'appearance.missingId': '没有 id 为 {{id}} 的宠物', + 'import.invalidId': 'id 只能用小写字母、数字、点、下划线、连字符,最长 80 个字符', + 'import.bundledConflict': '和内置宠物的 id 重复了', + 'import.displayNameRange': 'displayName 需要 1–80 个字符', + 'import.descriptionRange': 'description 最多 300 个字符', + 'import.spriteNameRule': 'spritesheetPath 只能是同目录下的 .webp 或 .png 文件名', + 'import.tooLarge': '图集太大,上传内容会超过 12 MB', + 'import.metaVersion': ' · v{{version}}{{looks}}', + 'import.unreadableJson': 'pet.json 不是有效的 JSON(pet_json_unreadable)', + 'import.previewFailed': '图集无法预览', + 'import.spriteUnreadable': '读不到图集文件', + 'import.readListFailedAfterImport': '文件已导入,但暂时无法加载列表;请返回外观重试({{code}})', + 'import.failedCode': '导入失败({{code}})', + 'import.hint': '同时选择 pet.json 和对应的 .webp 或 .png 图集,导入前会先预览。', + 'tasks.activityTitle': '任务与提醒', + 'tasks.updatedLabel': '更新于', + 'tasks.demoLegend': '手动资源动作预览,均为演示', + 'desk.notices': '任务通知', + 'desk.menuButton': '菜单', + 'settings.reconnect': '重新连接', + 'import.title': '导入宠物', + 'import.back': '返回外观', + 'import.tooManyJson': '一次只能导入一个宠物,选了多个 .json(import_pet_json_ambiguous)', + 'import.noJson': '缺少 pet.json,请和图集一起选择(import_pet_json_missing)', + 'import.jsonUnreadable': 'pet.json 不是有效的 JSON(pet_json_unreadable)', + 'import.noSpritesheetPath': 'pet.json 缺少 spritesheetPath(pet_json_invalid)', + 'import.noSprite': '没找到 {{name}},请和 pet.json 一起选择(import_sprite_missing)', + 'import.unnamed': '未命名宠物', + 'import.meta': 'id:{{id}} · 图集:{{file}}({{kb}} KB)', + 'import.checking': '正在检查图集…', + 'import.previewState': '预览动作:{{state}}({{index}}/{{total}})', + 'import.sessionBusy': '正在导入…', + 'import.alreadyExists': '已经导入过 id 为「{{id}}」的宠物,要用这次的文件替换它吗?', + 'import.cancelled': '已取消导入', + 'import.cancelledKept': '已取消导入,原来的宠物没有变化', + 'import.failed': '导入失败({{code}}){{detail}}', + 'import.loadFailedAfterImport': '文件已导入,但宠物加载失败;请返回外观重新选择', + 'import.listFailedAfterImport': '文件已导入,但暂时无法加载列表;请返回外观重试({{code}})', + 'import.keptSelection': '文件已导入,已保留你当前的宠物选择', + 'import.success': '导入成功,已切换到「{{name}}」', + 'import.cancel': '取消', + 'import.confirm': '确认导入', + 'import.pick': '选择文件', + 'import.dropHint': '把文件拖到这里,或', + + // Settings + 'settings.size': '桌面大小', + 'settings.sizeHint': '只调整桌面上的宠物大小,面板预览保持原尺寸。', + 'settings.sizeLegend': '桌面窗口里宠物的大小', + 'settings.language': '语言', + 'settings.languageHint': '默认跟随 MiniMax Code 的界面语言;旧版本或不希望跟随时可在这里手动指定。', + 'settings.languageAuto': '跟随 MiniMax Code', + 'settings.languageZh': '简体中文', + 'settings.languageEn': 'English', + 'settings.languageFollows': '语言跟随 MiniMax Code(当前 {{language}})。', + 'settings.languageSet': '界面语言已切换为 {{language}}。', + 'settings.notice': '任务通知', + 'settings.noticeHint': '完成或异常后,桌面卡片显示多少秒后自动收起;填 0 表示一直显示,直到手动关闭。收起不会把任务标记为已读。', + 'settings.noticeField': '显示时长', + 'settings.secondsUnit': '秒', + 'settings.noticeSaved': '桌面卡片显示 {{seconds}} 秒后收起。', + 'settings.noticeNever': '桌面卡片会一直显示到手动关闭。', + 'settings.noticeInvalid': '请输入 0 到 {{max}} 之间的整数秒。', + 'settings.reminders': '本次提醒', + 'settings.remindersHint': '静音的任务可在这里恢复;重新打开桌宠后恢复默认提醒。', + 'settings.muted': '本次已静音', + 'settings.restoreReminder': '恢复提醒:{{title}}', + 'settings.restoreFallback': '该会话', + 'settings.clearCompleted': '清除本次完成提示', + 'settings.connection': '任务连接', + 'settings.connected': '已连接', + 'settings.disconnected': '连接中断', + 'settings.help': '版本与帮助', + 'settings.helpHint': '动画跟随系统的“减少动态效果”设置。桌宠不代答,也不会自动发送消息。', + 'settings.remembered': '偏好已保存到本机。', + 'settings.adjustHint': '调整后会保存到本机。', + 'settings.saving': '正在保存偏好…', + 'settings.saveFailed': '偏好未保存({{code}}),本次调整仅临时生效。', + 'settings.loadFailed': '偏好读取失败({{code}}),当前使用临时设置。', + + // Tasks + 'tasks.scope': '任务范围', + 'tasks.recent': '最近提醒', + 'tasks.all': '全部任务', + 'tasks.summary': '任务概览', + 'tasks.back': '返回任务', + 'tasks.showing': '显示当前可读取的任务。', + 'tasks.updated': '更新于 {{time}}', + 'tasks.selectHint': '选择任务后查看最近回复。', + 'tasks.replyLoading': '正在读取任务…', + 'tasks.replyUpdating': '正在更新…', + 'tasks.replyNote': '显示任务的最近回复。{{next}}', + 'tasks.replyNextOpen': '点「打开对话」继续处理。', + 'tasks.replyNextChat': '后续处理请回到对应聊天。', + 'tasks.replyEmpty': '这项任务暂时没有可查看的回复。', + 'tasks.answerNote': '{{next}}', + 'tasks.answerNextOpen': '点「去回答」在主窗口打开这个对话后回答。', + 'tasks.answerNextChat': '请回到 MiniMax Code 的对应聊天回答。', + 'tasks.demoNote': '资源动作预览,不提供真实任务概览。', + 'tasks.noSessions': '还没有会话', + 'tasks.reminderNote.idle': '当前没有需要提示的活动。历史状态仍在全部会话中。', + 'tasks.reminderNote.active': '按最新活动显示;静音只影响桌宠本次提示。', + 'tasks.noReminder': '暂无当前提醒', + 'tasks.mute': '本次静音', + 'tasks.muteTitle': '本次静音:{{title}}', + 'tasks.viewReminder': '查看提醒:{{title}}', + 'tasks.sessionRow': '{{title}},{{status}},查看详情', + 'tasks.selectDone': '已切换任务详情;桌宠继续聚合全部任务。', + 'tasks.selectFailed': '无法关注此任务({{code}})', + 'tasks.unavailable': '任务信息暂不可用', + 'tasks.summaryText': '{{total}} 个可见任务 · {{running}} 运行中 · {{completed}} 本次完成 · {{error}} 异常 · {{aborted}} 已停止 · {{idle}} 空闲', + 'tasks.coverage': '聚合最近 50 个用户主会话及本次可见活动。新出现的 ask 实时跟随 {{watching}}/{{running}} 个运行任务;订阅前的问题无法恢复。选择条目只查看详情。', + 'tasks.demoHint': '手动资源动作预览(演示),不代表真实任务。', + 'tasks.demoStatus': '演示:{{status}}', + 'tasks.demoTitle': '状态演示', + 'tasks.noCurrent': '暂无当前提醒', + 'tasks.open': '打开对话', + 'tasks.goAnswer': '去回答', + 'tasks.opening': '正在打开对话…', + 'tasks.openOpened': '已在 MiniMax Code 主窗口打开这个对话。', + 'tasks.openCancelled': '已取消,主窗口留在原来的页面。', + 'tasks.openSuperseded': '这次打开被另一次跳转取代了。', + 'tasks.openNoApi': '当前 MiniMax Code 版本不能从桌宠打开对话。', + 'tasks.openTargetUnavailable': '这个对话已不存在或暂时打不开(target_unavailable)。', + 'tasks.openUnsupportedSource': '这个对话不在本机,桌宠打不开(unsupported_source)。', + 'tasks.openNotPermitted': '需要直接点击按钮才能打开(not_permitted)。', + 'tasks.openHostUnavailable': 'MiniMax Code 主窗口暂不可用,请稍后再试(host_unavailable)。', + 'tasks.openUnknown': '没收到打开结果,请看看主窗口是否已经切过去(result_unknown)。', + 'tasks.openFailed': '没能打开对话({{code}})。', + 'tasks.openFallback': '该任务', + + // Desktop window + 'desk.bubbleAria': '{{text}},打开桌宠工作面板', + 'desk.bubbleAriaTitle': '{{title}},{{text}},打开桌宠工作面板', + 'desk.bubbleIdle': '任务提示', + 'desk.petAria': '{{name}},{{state}}', + 'desk.petAriaWindow': '{{name}},{{state}}。点击或回车查看任务,可拖动', + 'desk.pet': '宠物', + 'desk.panel': '桌宠快捷面板', + 'desk.panelState.tasks': '最近任务', + 'desk.panelState.allTasks': '全部可见任务', + 'desk.panelState.detail': '任务回复', + 'desk.closePanel': '关闭桌宠快捷面板', + 'desk.taskAria': '查看任务:{{title}}', + 'desk.replyAria': '任务最近回复', + 'desk.tableAria': '任务回复中的表格', + 'desk.openAria': '{{action}}:{{title}}', + 'desk.openHint': '正在打开对话…', + 'desk.untitled': '未命名任务', + 'desk.reply': '任务回复', + 'desk.toView': '待查看', + 'desk.emptyRecent': '暂无新任务提醒', + 'desk.emptyAll': '暂无可见任务', + 'desk.loading': '正在读取任务…', + 'desk.unavailable': '任务信息暂不可用,重新连接后继续查看。', + 'desk.replyFailed': '无法读取任务({{code}})。', + 'desk.replyEmpty': '该任务暂时没有可读取的最后回复。', + 'desk.chooseTask': '选择任务后查看回复。', + 'desk.unavailableShort': '暂不可用', + 'desk.openFailed': '没能打开对话({{code}})。', + 'desk.noticeView': '查看通知:{{title}}', + 'desk.noticeClose': '关闭通知:{{title}}', + 'desk.noticeCompleted': '本次观察到任务完成', + 'desk.noticeError': '任务出现异常', + 'desk.menu': '宠物菜单', + 'desk.menuBack': '返回', + 'desk.menuAllTasks': '全部任务', + 'desk.menuReconnect': '重新连接会话', + 'desk.menuDemo': '动作预览', + 'desk.menuDemoTitle': '动作预览(演示)', + 'desk.menuPets': '换宠物', + 'desk.menuScale': '大小', + 'desk.menuCollapse': '收回到面板', + 'desk.menuEmptySessions': '还没有会话', + 'desk.menuEmptyPets': '没有可用的宠物', + 'desk.statusRunning': '进行中', + 'desk.scaleSmall': '小', + 'desk.scaleMedium': '中', + 'desk.scaleLarge': '大', + 'desk.transientTimeout': '这里收到的鼠标移动不会触发穿透,透明区域会挡住下面的点击', + + // Mode switching + 'mode.notPermitted': '需要直接点击才能切换窗口(not_permitted)', + 'mode.busy': '窗口正在切换,请稍候再试(busy)', + 'mode.unknown': '没收到切换结果,请看看{{where}}(result_unknown)', + 'mode.failed': '切换失败({{code}})', + 'mode.whereWindow': '桌面上是否已经出现宠物', + 'mode.wherePanel': '宠物是否已经回到面板', + 'mode.windowStateUnavailable': '读不到窗口状态({{code}})', + 'mode.positionFailed': '位置未恢复({{code}}),当前窗口仍可交互。', + 'mode.interactionUnknown': '窗口交互状态未确认({{code}}),请收回面板重试。', + 'mode.expandFailed': '暂时无法展开({{code}}),可收回面板继续使用。', + 'mode.collapseFailed': '窗口大小暂未恢复({{code}}),仍可继续操作。', + 'mode.resizeFailed': '尺寸未调整({{code}}),请稍后重试。', + + // Markdown + 'markdown.image': '[图片{{alt}}]', + 'markdown.truncated': '…(内容过长,已截断显示)', + 'markdown.imageAlt': ':{{alt}}', + + // Common + 'common.ok': '知道了', + 'common.cancelled': '已取消', + }, + en: { + 'app.title': 'MiniMax Pet', + 'app.tagline': 'Keeps you company while you work.', + 'app.tabAppearance': 'Appearance', + 'app.tabTasks': 'Tasks', + 'app.tabSettings': 'Settings', + 'app.popOut': 'Show on desktop', + 'app.popOutBusy': 'Opening…', + 'app.hostNote.browser': 'An ordinary browser has no tasks or window controls. Add ?demo=1 to preview the animations.', + 'app.hostNote.noWindow': 'This version of MiniMax Code cannot pop out windows, so the pet lives in the panel.', + 'app.fatal.player': 'Could not load the player ({{code}})', + 'app.fatal.boot': 'Could not start ({{code}})', + + 'status.idle': 'Idle', + 'status.running': 'Working', + 'status.error': 'Error', + 'status.aborted': 'Stopped', + 'status.completed': 'Completed', + 'status.needsAnswer': 'Needs your answer', + 'status.idleNow': 'Idle', + 'status.runningText': 'Working…', + 'status.errorText': 'The task ran into an error', + 'status.abortedText': 'The task was stopped', + 'status.completedText': 'The task finished', + 'status.currentHint': 'Current notice', + 'status.needsAnswerFor': '“{{title}}” needs your answer', + + 'availability.connecting': 'Connecting to local tasks…', + 'availability.connectingShort': 'Connecting', + 'availability.notPermitted': 'Allow reading conversations in the plugin details, then retry.', + 'availability.notPermittedShort': 'Not allowed', + 'availability.readNotPermitted': 'Reading conversations is not allowed', + 'availability.unavailable': 'Could not read local tasks{{reason}}.', + 'availability.reason': ' ({{code}})', + 'availability.unavailableShort': 'Unavailable', + 'availability.sessionUnavailable': 'Conversation status is unavailable', + 'availability.reconnectHint': '{{text}} You can reconnect in Settings.', + 'availability.demo': 'Resource preview', + 'availability.real': 'Live conversations', + + 'appearance.preview': 'Animation preview', + 'appearance.loading': 'Loading the pet…', + 'appearance.loadFailed': 'The pet did not load', + 'appearance.choose': 'Choose a pet', + 'appearance.import': 'Import pet…', + 'appearance.use': 'Use {{name}}', + 'appearance.delete': 'Delete', + 'appearance.deletePet': 'Delete {{name}}', + 'appearance.bundled': 'Bundled', + 'appearance.imported': 'Imported', + 'appearance.inUse': 'In use', + 'appearance.v2': 'v2 · follows the mouse', + 'appearance.noPets': 'No pets available', + 'appearance.noPetsReason': 'No pets available', + 'appearance.noPetId': 'No pet with id {{id}}', + 'appearance.loadingPet': 'Loading {{name}}…', + 'appearance.loadFailedDetail': 'Could not load {{name}} ({{code}}){{detail}}', + 'appearance.confirmDelete': 'Delete the imported pet “{{name}}”? This cannot be undone.', + 'appearance.deleted': 'Deleted {{name}}', + 'appearance.deleting': 'Deleting {{name}}…', + 'appearance.deleteCancelled': 'Deletion cancelled', + 'appearance.deleteFailed': 'Could not delete ({{code}}){{detail}}', + 'appearance.listFailed': 'Could not read the pet list', + 'appearance.thumbFailed': 'Could not preview the spritesheet', + 'appearance.looks': ' (follows the mouse)', + + 'appearance.readListFailed': 'Could not read the pet list', + 'appearance.missingId': 'No pet with id {{id}}', + 'import.invalidId': 'Use lowercase letters, digits, dots, underscores and hyphens; 80 characters at most', + 'import.bundledConflict': 'This id is already used by a bundled pet', + 'import.displayNameRange': 'displayName must be 1–80 characters', + 'import.descriptionRange': 'description must be 300 characters at most', + 'import.spriteNameRule': 'spritesheetPath must be a .webp or .png file name in the same folder', + 'import.tooLarge': 'The spritesheet is too large; the upload would exceed 12 MB', + 'import.metaVersion': ' · v{{version}}{{looks}}', + 'import.unreadableJson': 'pet.json is not valid JSON (pet_json_unreadable)', + 'import.previewFailed': 'Could not preview the spritesheet', + 'import.spriteUnreadable': 'Could not read the spritesheet file', + 'import.readListFailedAfterImport': 'The file was imported, but the list did not load. Go back to Appearance and retry ({{code}}).', + 'import.failedCode': 'Import failed ({{code}})', + 'import.hint': 'Pick pet.json and its .webp or .png spritesheet together; you can preview before importing.', + 'tasks.activityTitle': 'Tasks and reminders', + 'tasks.updatedLabel': 'Updated', + 'tasks.demoLegend': 'Manual animation preview, all demo', + 'desk.notices': 'Task notices', + 'desk.menuButton': 'Menu', + 'settings.reconnect': 'Reconnect', + 'import.title': 'Import pet', + 'import.back': 'Back to appearance', + 'import.tooManyJson': 'Import one pet at a time; several .json files were selected (import_pet_json_ambiguous)', + 'import.noJson': 'pet.json is missing. Select it together with the spritesheet (import_pet_json_missing)', + 'import.jsonUnreadable': 'pet.json is not valid JSON (pet_json_unreadable)', + 'import.noSpritesheetPath': 'pet.json has no spritesheetPath (pet_json_invalid)', + 'import.noSprite': 'Could not find {{name}}. Select it together with pet.json (import_sprite_missing)', + 'import.unnamed': 'Unnamed pet', + 'import.meta': 'id: {{id}} · spritesheet: {{file}} ({{kb}} KB)', + 'import.checking': 'Checking the spritesheet…', + 'import.previewState': 'Previewing {{state}} ({{index}}/{{total}})', + 'import.sessionBusy': 'Importing…', + 'import.alreadyExists': 'A pet with id “{{id}}” was already imported. Replace it with this file?', + 'import.cancelled': 'Import cancelled', + 'import.cancelledKept': 'Import cancelled; the current pet is unchanged', + 'import.failed': 'Import failed ({{code}}){{detail}}', + 'import.loadFailedAfterImport': 'The file was imported, but the pet did not load. Go back to Appearance and pick another.', + 'import.listFailedAfterImport': 'The file was imported, but the list did not load. Go back to Appearance and retry ({{code}}).', + 'import.keptSelection': 'The file was imported; your current pet selection was kept', + 'import.success': 'Imported and switched to {{name}}', + 'import.cancel': 'Cancel', + 'import.confirm': 'Import', + 'import.pick': 'Choose files', + 'import.dropHint': 'Drop files here, or', + + 'settings.size': 'Desktop size', + 'settings.sizeHint': 'Changes only the pet on the desktop; the panel preview keeps its size.', + 'settings.sizeLegend': 'Pet size in the desktop window', + 'settings.language': 'Language', + 'settings.languageHint': 'Follows the MiniMax Code interface language by default; pick one explicitly on older clients or to override.', + 'settings.languageAuto': 'Follow MiniMax Code', + 'settings.languageZh': '简体中文', + 'settings.languageEn': 'English', + 'settings.languageFollows': 'Language follows MiniMax Code (currently {{language}}).', + 'settings.languageSet': 'Interface language set to {{language}}.', + 'settings.notice': 'Task notifications', + 'settings.noticeHint': 'How long a desktop card stays after a task finishes or fails. Use 0 to keep it until you close it. Hiding a card does not mark the task as read.', + 'settings.noticeField': 'Show for', + 'settings.secondsUnit': 'sec', + 'settings.noticeSaved': 'Desktop cards hide after {{seconds}} seconds.', + 'settings.noticeNever': 'Desktop cards stay until you close them.', + 'settings.noticeInvalid': 'Enter a whole number of seconds from 0 to {{max}}.', + 'settings.reminders': 'This session’s reminders', + 'settings.remindersHint': 'Restore muted tasks here; reopening the pet restores the default reminders.', + 'settings.muted': 'Muted this session', + 'settings.restoreReminder': 'Restore reminder: {{title}}', + 'settings.restoreFallback': 'this conversation', + 'settings.clearCompleted': 'Clear completion notices', + 'settings.connection': 'Task connection', + 'settings.connected': 'Connected', + 'settings.disconnected': 'Disconnected', + 'settings.help': 'Version and help', + 'settings.helpHint': 'Animation follows the system “reduce motion” setting. The pet never answers for you and never sends messages.', + 'settings.remembered': 'Preferences are saved on this device.', + 'settings.adjustHint': 'Changes are saved on this device.', + 'settings.saving': 'Saving preferences…', + 'settings.saveFailed': 'Preferences were not saved ({{code}}); this change applies temporarily.', + 'settings.loadFailed': 'Could not read preferences ({{code}}); using temporary settings.', + + 'tasks.scope': 'Task scope', + 'tasks.recent': 'Recent notices', + 'tasks.all': 'All tasks', + 'tasks.summary': 'Task overview', + 'tasks.back': 'Back to tasks', + 'tasks.showing': 'Showing the tasks this plugin can read.', + 'tasks.updated': 'Updated {{time}}', + 'tasks.selectHint': 'Select a task to read its latest reply.', + 'tasks.replyLoading': 'Reading the task…', + 'tasks.replyUpdating': 'Updating…', + 'tasks.replyNote': 'Showing the latest reply. {{next}}', + 'tasks.replyNextOpen': 'Select Open conversation to keep working.', + 'tasks.replyNextChat': 'Continue in the matching chat.', + 'tasks.replyEmpty': 'This task has no reply to show yet.', + 'tasks.answerNote': '{{next}}', + 'tasks.answerNextOpen': 'Select Go answer to open this conversation in the main window.', + 'tasks.answerNextChat': 'Answer in the matching MiniMax Code chat.', + 'tasks.demoNote': 'Animation preview; no real task details.', + 'tasks.noSessions': 'No conversations yet', + 'tasks.reminderNote.idle': 'Nothing needs attention right now. Past states remain under All tasks.', + 'tasks.reminderNote.active': 'Newest activity first. Muting affects only the desktop card for this session.', + 'tasks.noReminder': 'No reminders right now', + 'tasks.mute': 'Mute', + 'tasks.muteTitle': 'Mute this session: {{title}}', + 'tasks.viewReminder': 'View reminder: {{title}}', + 'tasks.sessionRow': '{{title}}, {{status}}. View details.', + 'tasks.selectDone': 'Opened the task details; the pet still aggregates every task.', + 'tasks.selectFailed': 'Could not follow this task ({{code}})', + 'tasks.unavailable': 'Task details are unavailable', + 'tasks.summaryText': '{{total}} visible · {{running}} running · {{completed}} finished this session · {{error}} failed · {{aborted}} stopped · {{idle}} idle', + 'tasks.coverage': 'Aggregates the 50 most recent user conversations plus live activity. New questions are followed live on {{watching}}/{{running}} running tasks; questions raised before subscribing cannot be recovered. Selecting an item only reads its details.', + 'tasks.demoHint': 'Manual animation preview (demo); not real tasks.', + 'tasks.demoStatus': 'Demo: {{status}}', + 'tasks.demoTitle': 'Status demo', + 'tasks.noCurrent': 'No reminders right now', + 'tasks.open': 'Open conversation', + 'tasks.goAnswer': 'Go answer', + 'tasks.opening': 'Opening the conversation…', + 'tasks.openOpened': 'Opened this conversation in the MiniMax Code main window.', + 'tasks.openCancelled': 'Cancelled; the main window stayed where it was.', + 'tasks.openSuperseded': 'Another navigation replaced this request.', + 'tasks.openNoApi': 'This version of MiniMax Code cannot open a conversation from the pet.', + 'tasks.openTargetUnavailable': 'This conversation no longer exists or cannot open (target_unavailable).', + 'tasks.openUnsupportedSource': 'This conversation is not local and cannot be opened from the pet (unsupported_source).', + 'tasks.openNotPermitted': 'Open it by clicking the button directly (not_permitted).', + 'tasks.openHostUnavailable': 'The MiniMax Code main window is unavailable. Try again later (host_unavailable).', + 'tasks.openUnknown': 'No result came back. Check whether the main window switched (result_unknown).', + 'tasks.openFailed': 'Could not open the conversation ({{code}}).', + 'tasks.openFallback': 'this task', + + 'desk.bubbleAria': '{{text}}. Open the pet work panel.', + 'desk.bubbleAriaTitle': '{{title}}, {{text}}. Open the pet work panel.', + 'desk.bubbleIdle': 'Task notice', + 'desk.petAria': '{{name}}, {{state}}', + 'desk.petAriaWindow': '{{name}}, {{state}}. Click or press Enter to view tasks; drag to move.', + 'desk.pet': 'Pet', + 'desk.panel': 'Pet quick panel', + 'desk.panelState.tasks': 'Recent tasks', + 'desk.panelState.allTasks': 'All visible tasks', + 'desk.panelState.detail': 'Task reply', + 'desk.closePanel': 'Close the pet quick panel', + 'desk.taskAria': 'View task: {{title}}', + 'desk.replyAria': 'Latest task reply', + 'desk.tableAria': 'Table in the task reply', + 'desk.openAria': '{{action}}: {{title}}', + 'desk.openHint': 'Opening the conversation…', + 'desk.untitled': 'Untitled task', + 'desk.reply': 'Task reply', + 'desk.toView': 'To review', + 'desk.emptyRecent': 'No new task notices', + 'desk.emptyAll': 'No visible tasks', + 'desk.loading': 'Reading the task…', + 'desk.unavailable': 'Task details are unavailable. Reconnect and try again.', + 'desk.replyFailed': 'Could not read the task ({{code}}).', + 'desk.replyEmpty': 'This task has no reply to read yet.', + 'desk.chooseTask': 'Select a task to read its reply.', + 'desk.unavailableShort': 'Unavailable', + 'desk.openFailed': 'Could not open the conversation ({{code}}).', + 'desk.noticeView': 'View notice: {{title}}', + 'desk.noticeClose': 'Close notice: {{title}}', + 'desk.noticeCompleted': 'Observed this task finish', + 'desk.noticeError': 'The task ran into an error', + 'desk.menu': 'Pet menu', + 'desk.menuBack': 'Back', + 'desk.menuAllTasks': 'All tasks', + 'desk.menuReconnect': 'Reconnect', + 'desk.menuDemo': 'Animation preview', + 'desk.menuDemoTitle': 'Animation preview (demo)', + 'desk.menuPets': 'Change pet', + 'desk.menuScale': 'Size', + 'desk.menuCollapse': 'Back to panel', + 'desk.menuEmptySessions': 'No conversations yet', + 'desk.menuEmptyPets': 'No pets available', + 'desk.statusRunning': 'In progress', + 'desk.scaleSmall': 'Small', + 'desk.scaleMedium': 'Medium', + 'desk.scaleLarge': 'Large', + 'desk.transientTimeout': 'Mouse moves are not forwarded here, so transparent areas block clicks below', + + 'mode.notPermitted': 'Switch windows by clicking directly (not_permitted)', + 'mode.busy': 'The window is switching. Try again shortly (busy)', + 'mode.unknown': 'No result came back. Check {{where}} (result_unknown)', + 'mode.failed': 'Could not switch ({{code}})', + 'mode.whereWindow': 'whether the pet appeared on the desktop', + 'mode.wherePanel': 'whether the pet returned to the panel', + 'mode.windowStateUnavailable': 'Could not read the window state ({{code}})', + 'mode.positionFailed': 'Position was not restored ({{code}}); the window still works.', + 'mode.interactionUnknown': 'The window interaction state is unconfirmed ({{code}}). Send the pet back to the panel and retry.', + 'mode.expandFailed': 'Could not expand ({{code}}). Send the pet back to the panel to keep working.', + 'mode.collapseFailed': 'The window size was not restored ({{code}}); you can keep working.', + 'mode.resizeFailed': 'Size was not changed ({{code}}). Try again later.', + + 'markdown.image': '[Image{{alt}}]', + 'markdown.truncated': '… (too long; truncated)', + 'markdown.imageAlt': ': {{alt}}', + + 'common.ok': 'OK', + 'common.cancelled': 'Cancelled', + }, +}); + +let language = FALLBACK; +const listeners = new Set(); + +/** Maps a Host or browser locale tag onto a supported language. */ +export function resolveLanguage(tag, preference = 'auto') { + if (preference === 'zh-Hans' || preference === 'en') return preference; + const value = typeof tag === 'string' ? tag.toLowerCase() : ''; + return value.startsWith('en') ? 'en' : FALLBACK; +} + +export function getLanguage() { + return language; +} + +export function setLanguage(tag, preference = 'auto') { + const next = resolveLanguage(tag, preference); + if (next === language) return language; + language = next; + for (const listener of [...listeners]) { + try { listener(language); } catch { /* A view listener must not break the others. */ } + } + return language; +} + +export function onLanguageChange(listener) { + listeners.add(listener); + return () => listeners.delete(listener); +} + +/** Interpolates `{{name}}` placeholders; missing values stay visible as the placeholder. */ +export function translate(key, params, tag = language) { + const table = MESSAGES[tag] ?? MESSAGES[FALLBACK]; + const template = table[key] ?? MESSAGES[FALLBACK][key] ?? key; + if (!params) return template; + return template.replace(/\{\{(\w+)\}\}/gu, (match, name) => ( + Object.hasOwn(params, name) ? String(params[name]) : match + )); +} + +export const t = translate; + +/** BCP 47 tag for dates and numbers. */ +export function localeFor(tag = language) { + return tag === 'en' ? 'en-US' : 'zh-CN'; +} diff --git a/plugins/minimax-ai/minimax-pet/miniapp/client/index.html b/plugins/minimax-ai/minimax-pet/miniapp/client/index.html index 5f6a102c..24c255f6 100644 --- a/plugins/minimax-ai/minimax-pet/miniapp/client/index.html +++ b/plugins/minimax-ai/minimax-pet/miniapp/client/index.html @@ -13,18 +13,18 @@

MiniMax Pet

-

陪伴工作,留意任务的新动静。

+

陪伴工作,留意任务的新动静。

- +

@@ -34,18 +34,18 @@

MiniMax Pet

-

动画预览

+

动画预览

-

正在加载宠物…

+

正在加载宠物…

@@ -83,10 +83,10 @@

导入宠物