diff --git a/apps/desktop/src/i18n/en.ts b/apps/desktop/src/i18n/en.ts index c4820b41194..5f7c0c5fcd6 100644 --- a/apps/desktop/src/i18n/en.ts +++ b/apps/desktop/src/i18n/en.ts @@ -2954,190 +2954,4 @@ export const en: Translations = { toggle: open => `${open ? 'Show' : 'Hide'} sidebar` } }, - - kanban: { - nav: 'Kanban', - openBoard: 'Kanban: Open board', - countTip: (running, ready) => `Kanban — ${running} running, ${ready} ready`, - col: { - triage: { label: 'Triage', help: 'Raw ideas — a specifier fleshes out the spec.' }, - todo: { label: 'Todo', help: 'Waiting on dependencies, or unassigned.' }, - scheduled: { label: 'Scheduled', help: 'Waiting for a scheduled time to arrive.' }, - ready: { label: 'Ready', help: 'Dependencies satisfied — assign a profile and the dispatcher runs it.' }, - running: { label: 'Running', help: 'Claimed by a worker — an agent is on it. Set by the dispatcher.' }, - blocked: { label: 'Blocked', help: 'The worker asked for human input.' }, - review: { label: 'Review', help: 'A review agent is checking the work. Set by the dispatcher.' }, - done: { label: 'Done', help: 'Completed; dependent children become ready.' }, - archived: { label: 'Archived', help: 'Hidden from the default board view.' } - }, - locked: { - review: 'Review is entered by the dispatcher when a review agent takes the card.', - running: 'Running is set by the dispatcher when a worker claims the card.', - scheduled: 'Scheduled needs a wake-up time — agents set it; it can’t be dragged into.' - }, - arcRunning: 'An agent is working on this now.', - arcStale: 'Claimed, but no worker heartbeat for 2+ minutes — the dispatcher will reclaim it.', - title: 'Kanban', - orchestrationSettings: 'Orchestration settings', - newTask: 'New task', - filterCards: 'Filter cards…', - noMatch: 'No tasks match the filters', - noTasks: 'No tasks on this board', - open: 'Open', - select: 'Select (⌘-click)', - deselect: 'Deselect', - moveTo: label => `Move to ${label}`, - delete: 'Delete', - reviewChecking: 'A review agent is checking the completed work.', - attachedTip: name => `${name} is attached — the dispatcher hands this over on its next tick (≤1m).`, - orchestratorTip: name => `${name} (the orchestrator) picks this up on the next tick and writes the spec.`, - autoAssignTip: name => `Auto-assigns to “${name}” (kanban.default_assignee) on the next dispatch tick.`, - wontRun: "won't run", - wontRunTip: - 'Ready cards only run once a profile is assigned. Open the card and set an assignee, or configure a default assignee in orchestration settings.', - noHeartbeat: 'no heartbeat', - expand: label => `Expand ${label}`, - collapse: label => `Collapse ${label}`, - newTaskIn: label => `New task in ${label}`, - empty: 'Empty', - unassigned: 'unassigned', - filters: 'Filters', - allProfiles: 'All profiles', - allTenants: 'All tenants', - showArchived: 'Show archived', - groupRunning: 'Group Running by profile', - nSelected: n => `${n} selected`, - moveToShort: 'Move to', - assign: 'Assign', - unassignAction: 'Unassign', - archive: 'Archive', - clearSelection: 'Clear selection (Esc)', - refused: 'refused', - bulkFailed: (failed, total, err) => `${failed} of ${total} failed — ${err}. Failed cards stay selected.`, - titlePlaceholderTriage: 'Rough idea — a specifier will flesh it out', - titlePlaceholder: 'Title', - descPlaceholder: 'Description (optional)', - priority: 'Priority', - workspace: 'Workspace', - boardDefaultSuffix: ' · board default', - workspaceOverride: 'Workspace path (optional override)', - workspaceInherit: 'Inherits the board’s project directory', - workspaceInheritDir: dir => `Leave empty to inherit ${dir}`, - workspaceInheritGeneric: 'Leave empty to inherit the board’s project directory.', - assignee: 'Assignee', - defaultOption: name => `${name} (default)`, - parkedOption: "unassigned (parked — won't run)", - skills: 'Skills (comma-separated)', - skillsPlaceholder: 'translation, github', - parent: "Parent (blocks until it's done)", - noParent: '— no parent —', - goalMode: "Goal mode (worker loops until a judge agrees it's done)", - creating: 'Creating…', - createTask: 'Create task', - cancel: 'Cancel', - save: 'Save', - estimate: 'Estimate', - estimateEffort: 'Estimate effort', - estimating: 'Estimating…', - reEstimate: 'Re-estimate', - makesModelCall: 'makes a model call', - estimateTip: 'Rough token + complexity estimate from the auxiliary model — makes a model call.', - estimateTipLong: 'Runs a quick auxiliary-model call to estimate tokens + complexity. A rough guide, not a bill.', - roughEstimate: 'Rough estimate', - tokUnit: 'tok', - couldNotEstimate: 'Could not estimate', - complexity: { S: 'Small', M: 'Medium', L: 'Large' }, - introBody: - 'You don’t run the cards — agents do. Put a card in Ready with an assignee and an agent picks it up within a minute. No assignee, no run. Triage: an agent rewrites the idea into a proper task first. Todo: waiting on other cards. Scheduled: waiting on a timer. Running and Review: the agents’ lanes, hands off. Blocked: it’s waiting on you. Results come back on the card.', - introGotIt: 'Got it', - evtCreated: (where, assignee) => `created${where ? ` in ${where}` : ''}${assignee ? ` · assigned to ${assignee}` : ''}`, - evtMovedTo: col => `moved to ${col}`, - evtParentReopened: parent => `parent ${parent} reopened`, - evtAssignedTo: assignee => `assigned to ${assignee}`, - evtUnassigned: 'unassigned', - evtCommentBy: author => `comment by ${author}`, - evtClaimedReview: 'claimed by a review agent', - evtClaimedWorker: 'claimed by a worker', - evtWorkerStarted: 'worker started', - evtCompleted: 'completed', - evtBlocked: 'blocked — needs human input', - evtUnblocked: col => `unblocked${col ? ` → ${col}` : ' → Ready'}`, - evtReclaimed: 'reclaimed — returned to the queue', - evtSpecified: 'spec written by the triage agent', - evtPromoted: 'dependencies done — promoted to Ready', - evtScheduled: 'scheduled for later', - evtArchived: 'archived', - evtReprioritized: priority => `priority set to ${priority}`, - someone: 'someone', - metaPriority: 'Priority', - metaTenant: 'Tenant', - metaCreatedBy: 'Created by', - metaCreated: 'Created', - metaWorkerPid: 'Worker pid', - readyUnassignedTitle: 'Ready, but unassigned — this card will never run.', - readyUnassignedBody: - 'The dispatcher only claims Ready cards that have an assignee. Pick a profile in the Assignee field above (or set a default assignee in the orchestration settings) and it runs within a minute.', - diagnosticsN: n => `Diagnostics · ${n}`, - commandCopied: 'Command copied', - description: 'Description', - editDescription: 'Edit description', - cancelEdit: 'Cancel edit', - noDescription: 'No description yet.', - result: 'Result', - latestSummary: 'Latest summary', - dependencies: 'Dependencies', - blockedBy: 'Blocked by', - blocks: 'Blocks', - comments: n => `Comments · ${n}`, - commentsHelpRunning: - 'This task is running. Your note is folded into the worker’s current turn within a few seconds — no block/unblock dance. “Requeue with note” instead restarts the task from scratch with your note in context.', - commentsHelp: - 'Comments are added to the task thread. When a worker picks the task up it reads them as part of its context.', - send: 'Send', - comment: 'Comment', - messageWorker: 'Message the running worker…', - addComment: 'Add a comment…', - deliveredLive: 'Delivered to the running worker within a few seconds.', - requeueWithNote: 'Requeue with note', - notePosted: 'Note posted — worker requeued', - activity: n => `Activity · ${n}`, - runs: n => `Runs · ${n}`, - workerLog: 'Worker log', - workerLogTail: 'Worker log · tail', - attachments: n => `Attachments · ${n}`, - noAttachments: 'No attachments yet.', - uploadAttachment: 'Upload attachment', - taskActions: 'Task actions', - copyTaskId: 'Copy task id', - copyTitle: 'Copy title', - copiedId: id => `Copied ${id}`, - copiedTitle: 'Copied title', - archiveTask: 'Archive task', - deleteTask: 'Delete task', - close: 'Close', - working: 'working', - board: 'Board', - newBoard: 'New board', - newBoardDots: 'New board…', - boardSettings: 'Board settings…', - boardSettingsFor: name => `Board settings — ${name}`, - name: 'Name', - boardNamePlaceholder: 'Board name', - slug: slug => `slug: ${slug}`, - project: 'Project', - noProject: 'No project (scratch sandboxes)', - projectHintPre: - 'New tasks run in the project’s repo (a worktree per task); each task can still override its workspace at creation. Manage projects with ', - projectHintCmd: 'hermes project', - createBoard: 'Create board', - orchestratorProfile: 'Orchestrator profile', - defaultAssignee: 'Default assignee', - defaultParen: '(default)', - autoDecompose: 'Auto-decompose triage tasks', - profileDescriptions: 'Profile descriptions', - profileDescriptionsHint: - 'Descriptions guide the decomposer’s routing. Auto-generate with the auxiliary model, or write your own.', - profileGoodAt: 'What is this profile good at?', - auto: 'Auto' - } } diff --git a/apps/desktop/src/i18n/ja.ts b/apps/desktop/src/i18n/ja.ts index 6be7c80042c..f61761f1d81 100644 --- a/apps/desktop/src/i18n/ja.ts +++ b/apps/desktop/src/i18n/ja.ts @@ -2799,190 +2799,4 @@ export const ja = defineLocale({ toggle: open => `サイドバーを${open ? '表示' : '非表示'}` } }, - - kanban: { - nav: 'カンバン', - openBoard: 'カンバン: ボードを開く', - countTip: (running, ready) => `カンバン — 実行中 ${running}、待機 ${ready}`, - col: { - triage: { label: 'トリアージ', help: '生のアイデア — スペシファイアが仕様に整えます。' }, - todo: { label: 'Todo', help: '依存関係の待ち、または未割り当て。' }, - scheduled: { label: 'スケジュール', help: '予定時刻を待っています。' }, - ready: { label: 'Ready', help: '依存関係が解決済み — プロフィールを割り当てるとディスパッチャが実行します。' }, - running: { label: '実行中', help: 'ワーカーが取得済み — エージェントが作業中。ディスパッチャが設定します。' }, - blocked: { label: 'ブロック', help: 'ワーカーが人間の入力を求めています。' }, - review: { label: 'レビュー', help: 'レビューエージェントが作業を確認中。ディスパッチャが設定します。' }, - done: { label: '完了', help: '完了。依存する子タスクが Ready になります。' }, - archived: { label: 'アーカイブ', help: 'デフォルトのボード表示から非表示。' } - }, - locked: { - review: 'レビューは、レビューエージェントがカードを取得するとディスパッチャによって設定されます。', - running: '実行中は、ワーカーがカードを取得するとディスパッチャによって設定されます。', - scheduled: 'スケジュールには起動時刻が必要です — エージェントが設定します。ドラッグでは移動できません。' - }, - arcRunning: 'エージェントが現在作業中です。', - arcStale: '取得済みですが、2分以上ワーカーのハートビートがありません — ディスパッチャが再取得します。', - title: 'カンバン', - orchestrationSettings: 'オーケストレーション設定', - newTask: '新しいタスク', - filterCards: 'カードを絞り込み…', - noMatch: 'フィルタに一致するタスクはありません', - noTasks: 'このボードにタスクはありません', - open: '開く', - select: '選択(⌘クリック)', - deselect: '選択解除', - moveTo: label => `${label} へ移動`, - delete: '削除', - reviewChecking: 'レビューエージェントが完了した作業を確認中です。', - attachedTip: name => `${name} が担当 — ディスパッチャが次のティック(≤1分)で引き渡します。`, - orchestratorTip: name => `${name}(オーケストレーター)が次のティックでこれを取得し、仕様を書きます。`, - autoAssignTip: name => `次のディスパッチティックで「${name}」(kanban.default_assignee)に自動割り当てされます。`, - wontRun: '実行されません', - wontRunTip: - 'Ready のカードはプロフィールが割り当てられて初めて実行されます。カードを開いて担当を設定するか、オーケストレーション設定でデフォルトの担当を設定してください。', - noHeartbeat: 'ハートビートなし', - expand: label => `${label} を展開`, - collapse: label => `${label} を折りたたむ`, - newTaskIn: label => `${label} に新しいタスク`, - empty: '空', - unassigned: '未割り当て', - filters: 'フィルタ', - allProfiles: 'すべてのプロフィール', - allTenants: 'すべてのテナント', - showArchived: 'アーカイブを表示', - groupRunning: '実行中をプロフィールでグループ化', - nSelected: n => `${n} 件選択中`, - moveToShort: '移動', - assign: '割り当て', - unassignAction: '割り当て解除', - archive: 'アーカイブ', - clearSelection: '選択をクリア(Esc)', - refused: '拒否されました', - bulkFailed: (failed, total, err) => `${total} 件中 ${failed} 件が失敗 — ${err}。失敗したカードは選択されたままです。`, - titlePlaceholderTriage: '大まかなアイデア — スペシファイアが具体化します', - titlePlaceholder: 'タイトル', - descPlaceholder: '説明(任意)', - priority: '優先度', - workspace: 'ワークスペース', - boardDefaultSuffix: '・ボード既定', - workspaceOverride: 'ワークスペースパス(任意の上書き)', - workspaceInherit: 'ボードのプロジェクトディレクトリを継承', - workspaceInheritDir: dir => `空欄にすると ${dir} を継承します`, - workspaceInheritGeneric: '空欄にするとボードのプロジェクトディレクトリを継承します。', - assignee: '担当', - defaultOption: name => `${name}(既定)`, - parkedOption: '未割り当て(保留 — 実行されません)', - skills: 'スキル(カンマ区切り)', - skillsPlaceholder: 'translation, github', - parent: '親(完了するまでブロック)', - noParent: '— 親なし —', - goalMode: 'ゴールモード(ジャッジが完了と認めるまでワーカーがループ)', - creating: '作成中…', - createTask: 'タスクを作成', - cancel: 'キャンセル', - save: '保存', - estimate: '見積もり', - estimateEffort: '工数を見積もり', - estimating: '見積もり中…', - reEstimate: '再見積もり', - makesModelCall: 'モデル呼び出しあり', - estimateTip: '補助モデルによるトークン数と複雑度の概算 — モデル呼び出しを行います。', - estimateTipLong: '補助モデルを呼び出してトークン数と複雑度を概算します。目安であり、請求ではありません。', - roughEstimate: '概算', - tokUnit: 'tok', - couldNotEstimate: '見積もりできませんでした', - complexity: { S: '小', M: '中', L: '大' }, - introBody: - 'カードはあなたではなくエージェントが実行します。担当を設定したカードを Ready に置くと、1分以内にエージェントが取得します。担当がなければ実行されません。トリアージ: エージェントがまずアイデアを適切なタスクに書き直します。Todo: 他のカード待ち。スケジュール: タイマー待ち。実行中とレビュー: エージェントのレーンなので手を出さないでください。ブロック: あなたの対応待ちです。結果はカードに戻ってきます。', - introGotIt: '了解', - evtCreated: (where, assignee) => - `作成${where ? `(${where})` : ''}${assignee ? `・${assignee} に割り当て` : ''}`, - evtMovedTo: col => `${col} へ移動`, - evtParentReopened: parent => `親 ${parent} が再オープン`, - evtAssignedTo: assignee => `${assignee} に割り当て`, - evtUnassigned: '割り当て解除', - evtCommentBy: author => `${author} のコメント`, - evtClaimedReview: 'レビューエージェントが取得', - evtClaimedWorker: 'ワーカーが取得', - evtWorkerStarted: 'ワーカー開始', - evtCompleted: '完了', - evtBlocked: 'ブロック — 人間の入力が必要', - evtUnblocked: col => `ブロック解除${col ? ` → ${col}` : ' → Ready'}`, - evtReclaimed: '再取得 — キューに戻しました', - evtSpecified: 'トリアージエージェントが仕様を作成', - evtPromoted: '依存関係が完了 — Ready に昇格', - evtScheduled: '後で実行するようスケジュール', - evtArchived: 'アーカイブ済み', - evtReprioritized: priority => `優先度を ${priority} に設定`, - someone: '誰か', - metaPriority: '優先度', - metaTenant: 'テナント', - metaCreatedBy: '作成者', - metaCreated: '作成', - metaWorkerPid: 'ワーカー PID', - readyUnassignedTitle: 'Ready ですが未割り当て — このカードは実行されません。', - readyUnassignedBody: - 'ディスパッチャは担当のある Ready カードのみ取得します。上の担当フィールドでプロフィールを選ぶ(またはオーケストレーション設定でデフォルトの担当を設定する)と、1分以内に実行されます。', - diagnosticsN: n => `診断・${n}`, - commandCopied: 'コマンドをコピーしました', - description: '説明', - editDescription: '説明を編集', - cancelEdit: '編集をキャンセル', - noDescription: 'まだ説明はありません。', - result: '結果', - latestSummary: '最新のサマリー', - dependencies: '依存関係', - blockedBy: 'ブロック元', - blocks: 'ブロック先', - comments: n => `コメント・${n}`, - commentsHelpRunning: - 'このタスクは実行中です。あなたのメモは数秒以内にワーカーの現在のターンに取り込まれます — ブロック/解除の操作は不要です。「メモを付けて再キュー」を選ぶと、メモを文脈に含めてタスクを最初からやり直します。', - commentsHelp: 'コメントはタスクのスレッドに追加されます。ワーカーがタスクを取得すると、文脈の一部として読み込みます。', - send: '送信', - comment: 'コメント', - messageWorker: '実行中のワーカーにメッセージ…', - addComment: 'コメントを追加…', - deliveredLive: '数秒以内に実行中のワーカーへ届きます。', - requeueWithNote: 'メモを付けて再キュー', - notePosted: 'メモを投稿しました — ワーカーを再キューしました', - activity: n => `アクティビティ・${n}`, - runs: n => `実行・${n}`, - workerLog: 'ワーカーログ', - workerLogTail: 'ワーカーログ・末尾', - attachments: n => `添付・${n}`, - noAttachments: 'まだ添付はありません。', - uploadAttachment: '添付をアップロード', - taskActions: 'タスクの操作', - copyTaskId: 'タスク ID をコピー', - copyTitle: 'タイトルをコピー', - copiedId: id => `${id} をコピーしました`, - copiedTitle: 'タイトルをコピーしました', - archiveTask: 'タスクをアーカイブ', - deleteTask: 'タスクを削除', - close: '閉じる', - working: '作業中', - board: 'ボード', - newBoard: '新しいボード', - newBoardDots: '新しいボード…', - boardSettings: 'ボード設定…', - boardSettingsFor: name => `ボード設定 — ${name}`, - name: '名前', - boardNamePlaceholder: 'ボード名', - slug: slug => `slug: ${slug}`, - project: 'プロジェクト', - noProject: 'プロジェクトなし(スクラッチのサンドボックス)', - projectHintPre: - '新しいタスクはプロジェクトのリポジトリで実行されます(タスクごとに worktree)。各タスクは作成時にワークスペースを上書きできます。プロジェクトの管理は ', - projectHintCmd: 'hermes project', - createBoard: 'ボードを作成', - orchestratorProfile: 'オーケストレータープロフィール', - defaultAssignee: 'デフォルトの担当', - defaultParen: '(既定)', - autoDecompose: 'トリアージタスクを自動分解', - profileDescriptions: 'プロフィールの説明', - profileDescriptionsHint: - '説明はデコンポーザーのルーティングを導きます。補助モデルで自動生成するか、自分で書いてください。', - profileGoodAt: 'このプロフィールの得意分野は?', - auto: '自動' - } }) diff --git a/apps/desktop/src/i18n/types.ts b/apps/desktop/src/i18n/types.ts index dc06c51504e..88f3ffdf95a 100644 --- a/apps/desktop/src/i18n/types.ts +++ b/apps/desktop/src/i18n/types.ts @@ -2512,176 +2512,4 @@ export interface Translations { toggle: (open: boolean) => string } } - - kanban: { - nav: string - openBoard: string - countTip: (running: number, ready: number) => string - col: Record< - 'archived' | 'blocked' | 'done' | 'ready' | 'review' | 'running' | 'scheduled' | 'todo' | 'triage', - { label: string; help: string } - > - locked: { review: string; running: string; scheduled: string } - arcRunning: string - arcStale: string - title: string - orchestrationSettings: string - newTask: string - filterCards: string - noMatch: string - noTasks: string - open: string - select: string - deselect: string - moveTo: (label: string) => string - delete: string - reviewChecking: string - attachedTip: (name: string) => string - orchestratorTip: (name: string) => string - autoAssignTip: (name: string) => string - wontRun: string - wontRunTip: string - noHeartbeat: string - expand: (label: string) => string - collapse: (label: string) => string - newTaskIn: (label: string) => string - empty: string - unassigned: string - filters: string - allProfiles: string - allTenants: string - showArchived: string - groupRunning: string - nSelected: (n: number) => string - moveToShort: string - assign: string - unassignAction: string - archive: string - clearSelection: string - refused: string - bulkFailed: (failed: number, total: number, err: string) => string - titlePlaceholderTriage: string - titlePlaceholder: string - descPlaceholder: string - priority: string - workspace: string - boardDefaultSuffix: string - workspaceOverride: string - workspaceInherit: string - workspaceInheritDir: (dir: string) => string - workspaceInheritGeneric: string - assignee: string - defaultOption: (name: string) => string - parkedOption: string - skills: string - skillsPlaceholder: string - parent: string - noParent: string - goalMode: string - creating: string - createTask: string - cancel: string - save: string - estimate: string - estimateEffort: string - estimating: string - reEstimate: string - makesModelCall: string - estimateTip: string - estimateTipLong: string - roughEstimate: string - tokUnit: string - couldNotEstimate: string - complexity: Record<'L' | 'M' | 'S', string> - introBody: string - introGotIt: string - // drawer — activity prose - evtCreated: (where: string, assignee: string) => string - evtMovedTo: (col: string) => string - evtParentReopened: (parent: string) => string - evtAssignedTo: (assignee: string) => string - evtUnassigned: string - evtCommentBy: (author: string) => string - evtClaimedReview: string - evtClaimedWorker: string - evtWorkerStarted: string - evtCompleted: string - evtBlocked: string - evtUnblocked: (col: string) => string - evtReclaimed: string - evtSpecified: string - evtPromoted: string - evtScheduled: string - evtArchived: string - evtReprioritized: (priority: string) => string - someone: string - // drawer — meta + sections - metaPriority: string - metaTenant: string - metaCreatedBy: string - metaCreated: string - metaWorkerPid: string - readyUnassignedTitle: string - readyUnassignedBody: string - diagnosticsN: (n: number) => string - commandCopied: string - description: string - editDescription: string - cancelEdit: string - noDescription: string - result: string - latestSummary: string - dependencies: string - blockedBy: string - blocks: string - comments: (n: number) => string - commentsHelpRunning: string - commentsHelp: string - send: string - comment: string - messageWorker: string - addComment: string - deliveredLive: string - requeueWithNote: string - notePosted: string - activity: (n: number) => string - runs: (n: number) => string - workerLog: string - workerLogTail: string - attachments: (n: number) => string - noAttachments: string - uploadAttachment: string - taskActions: string - copyTaskId: string - copyTitle: string - copiedId: (id: string) => string - copiedTitle: string - archiveTask: string - deleteTask: string - close: string - working: string - // board switcher - board: string - newBoard: string - newBoardDots: string - boardSettings: string - boardSettingsFor: (name: string) => string - name: string - boardNamePlaceholder: string - slug: (slug: string) => string - project: string - noProject: string - projectHintPre: string - projectHintCmd: string - createBoard: string - // orchestration - orchestratorProfile: string - defaultAssignee: string - defaultParen: string - autoDecompose: string - profileDescriptions: string - profileDescriptionsHint: string - profileGoodAt: string - auto: string - } } diff --git a/apps/desktop/src/i18n/zh-hant.ts b/apps/desktop/src/i18n/zh-hant.ts index f44388a8496..cc572f8a55e 100644 --- a/apps/desktop/src/i18n/zh-hant.ts +++ b/apps/desktop/src/i18n/zh-hant.ts @@ -2685,187 +2685,4 @@ export const zhHant = defineLocale({ toggle: open => `${open ? '顯示' : '隱藏'}側邊欄` } }, - - kanban: { - nav: '看板', - openBoard: '看板:開啟面板', - countTip: (running, ready) => `看板 — 執行中 ${running}、就緒 ${ready}`, - col: { - triage: { label: '分類', help: '原始想法 — 由細化代理整理出規格。' }, - todo: { label: '待辦', help: '等待相依項目,或未指派。' }, - scheduled: { label: '已排程', help: '等待預定時間到來。' }, - ready: { label: '就緒', help: '相依項目已滿足 — 指派一個設定檔,排程器便會執行它。' }, - running: { label: '執行中', help: '已被工作單元領取 — 有代理在處理。由排程器設定。' }, - blocked: { label: '受阻', help: '工作單元需要人工輸入。' }, - review: { label: '審查', help: '審查代理正在檢查工作。由排程器設定。' }, - done: { label: '完成', help: '已完成;相依它的子任務變為就緒。' }, - archived: { label: '已封存', help: '從預設面板檢視中隱藏。' } - }, - locked: { - review: '審查狀態由排程器在審查代理領取卡片時設定。', - running: '執行中由排程器在工作單元領取卡片時設定。', - scheduled: '排程需要喚醒時間 — 由代理設定;無法拖入。' - }, - arcRunning: '有代理正在處理它。', - arcStale: '已領取,但超過 2 分鐘沒有工作單元心跳 — 排程器將重新領取。', - title: '看板', - orchestrationSettings: '編排設定', - newTask: '新增任務', - filterCards: '篩選卡片…', - noMatch: '沒有符合篩選條件的任務', - noTasks: '此面板尚無任務', - open: '開啟', - select: '選取(⌘點擊)', - deselect: '取消選取', - moveTo: label => `移至 ${label}`, - delete: '刪除', - reviewChecking: '審查代理正在檢查已完成的工作。', - attachedTip: name => `${name} 已接手 — 排程器將在下一個週期(≤1 分鐘)移交。`, - orchestratorTip: name => `${name}(編排者)將在下一個週期領取並撰寫規格。`, - autoAssignTip: name => `將在下一個排程週期自動指派給「${name}」(kanban.default_assignee)。`, - wontRun: '不會執行', - wontRunTip: '就緒卡片只有在指派了設定檔後才會執行。開啟卡片設定負責人,或在編排設定中設定預設負責人。', - noHeartbeat: '無心跳', - expand: label => `展開 ${label}`, - collapse: label => `摺疊 ${label}`, - newTaskIn: label => `在 ${label} 新增任務`, - empty: '空', - unassigned: '未指派', - filters: '篩選', - allProfiles: '所有設定檔', - allTenants: '所有租戶', - showArchived: '顯示已封存', - groupRunning: '依設定檔分組執行中', - nSelected: n => `已選取 ${n} 個`, - moveToShort: '移至', - assign: '指派', - unassignAction: '取消指派', - archive: '封存', - clearSelection: '清除選取(Esc)', - refused: '被拒絕', - bulkFailed: (failed, total, err) => `${total} 個中有 ${failed} 個失敗 — ${err}。失敗的卡片仍保持選取。`, - titlePlaceholderTriage: '大致想法 — 細化代理會補全', - titlePlaceholder: '標題', - descPlaceholder: '描述(選填)', - priority: '優先順序', - workspace: '工作區', - boardDefaultSuffix: '・面板預設', - workspaceOverride: '工作區路徑(選填覆寫)', - workspaceInherit: '繼承面板的專案目錄', - workspaceInheritDir: dir => `留空則繼承 ${dir}`, - workspaceInheritGeneric: '留空則繼承面板的專案目錄。', - assignee: '負責人', - defaultOption: name => `${name}(預設)`, - parkedOption: '未指派(擱置 — 不會執行)', - skills: '技能(以逗號分隔)', - skillsPlaceholder: 'translation, github', - parent: '父任務(完成前會阻擋)', - noParent: '— 無父任務 —', - goalMode: '目標模式(工作單元循環直到評判代理認可完成)', - creating: '建立中…', - createTask: '建立任務', - cancel: '取消', - save: '儲存', - estimate: '估算', - estimateEffort: '估算工作量', - estimating: '估算中…', - reEstimate: '重新估算', - makesModelCall: '會呼叫模型', - estimateTip: '由輔助模型對 token 數與複雜度的粗略估算 — 會呼叫模型。', - estimateTipLong: '快速呼叫輔助模型來估算 token 數與複雜度。僅供參考,並非帳單。', - roughEstimate: '粗略估算', - tokUnit: 'tok', - couldNotEstimate: '無法估算', - complexity: { S: '小', M: '中', L: '大' }, - introBody: - '卡片不由你執行,而是由代理執行。把有負責人的卡片放入「就緒」,代理會在一分鐘內領取。沒有負責人就不會執行。分類:代理先把想法改寫成合適的任務。待辦:等待其他卡片。已排程:等待計時器。執行中與審查:這是代理的通道,請勿插手。受阻:正在等你。結果會回到卡片上。', - introGotIt: '知道了', - evtCreated: (where, assignee) => - `已建立${where ? `(${where})` : ''}${assignee ? `・指派給 ${assignee}` : ''}`, - evtMovedTo: col => `移至 ${col}`, - evtParentReopened: parent => `父任務 ${parent} 已重新開啟`, - evtAssignedTo: assignee => `指派給 ${assignee}`, - evtUnassigned: '取消指派', - evtCommentBy: author => `${author} 的留言`, - evtClaimedReview: '被審查代理領取', - evtClaimedWorker: '被工作單元領取', - evtWorkerStarted: '工作單元已啟動', - evtCompleted: '已完成', - evtBlocked: '受阻 — 需要人工輸入', - evtUnblocked: col => `已解除阻擋${col ? ` → ${col}` : ' → 就緒'}`, - evtReclaimed: '已重新領取 — 已放回佇列', - evtSpecified: '分類代理已撰寫規格', - evtPromoted: '相依項目已完成 — 提升為就緒', - evtScheduled: '已排程稍後執行', - evtArchived: '已封存', - evtReprioritized: priority => `優先順序設為 ${priority}`, - someone: '某人', - metaPriority: '優先順序', - metaTenant: '租戶', - metaCreatedBy: '建立者', - metaCreated: '建立於', - metaWorkerPid: '工作單元 PID', - readyUnassignedTitle: '就緒但未指派 — 這張卡片永遠不會執行。', - readyUnassignedBody: - '排程器只領取有負責人的就緒卡片。在上方的負責人欄位選擇一個設定檔(或在編排設定中設定預設負責人),它會在一分鐘內執行。', - diagnosticsN: n => `診斷・${n}`, - commandCopied: '指令已複製', - description: '描述', - editDescription: '編輯描述', - cancelEdit: '取消編輯', - noDescription: '尚無描述。', - result: '結果', - latestSummary: '最新摘要', - dependencies: '相依關係', - blockedBy: '受阻於', - blocks: '阻擋', - comments: n => `留言・${n}`, - commentsHelpRunning: - '此任務正在執行。你的備註會在幾秒內融入工作單元目前的回合 — 無需阻擋/解除操作。選擇「附上備註重新排入佇列」則會帶著你的備註從頭重跑任務。', - commentsHelp: '留言會加入任務討論串。工作單元領取任務時會將其作為脈絡的一部分讀取。', - send: '傳送', - comment: '留言', - messageWorker: '傳訊給執行中的工作單元…', - addComment: '新增留言…', - deliveredLive: '幾秒內送達執行中的工作單元。', - requeueWithNote: '附上備註重新排入佇列', - notePosted: '備註已發布 — 工作單元已重新排入佇列', - activity: n => `活動・${n}`, - runs: n => `執行・${n}`, - workerLog: '工作單元日誌', - workerLogTail: '工作單元日誌・末尾', - attachments: n => `附件・${n}`, - noAttachments: '尚無附件。', - uploadAttachment: '上傳附件', - taskActions: '任務操作', - copyTaskId: '複製任務 ID', - copyTitle: '複製標題', - copiedId: id => `已複製 ${id}`, - copiedTitle: '已複製標題', - archiveTask: '封存任務', - deleteTask: '刪除任務', - close: '關閉', - working: '進行中', - board: '面板', - newBoard: '新增面板', - newBoardDots: '新增面板…', - boardSettings: '面板設定…', - boardSettingsFor: name => `面板設定 — ${name}`, - name: '名稱', - boardNamePlaceholder: '面板名稱', - slug: slug => `slug: ${slug}`, - project: '專案', - noProject: '無專案(暫存沙箱)', - projectHintPre: '新任務將在專案的儲存庫中執行(每個任務一個 worktree);每個任務在建立時仍可覆寫其工作區。管理專案請使用 ', - projectHintCmd: 'hermes project', - createBoard: '建立面板', - orchestratorProfile: '編排者設定檔', - defaultAssignee: '預設負責人', - defaultParen: '(預設)', - autoDecompose: '自動分解分類任務', - profileDescriptions: '設定檔說明', - profileDescriptionsHint: '說明用於引導分解器的路由。可用輔助模型自動產生,或自行填寫。', - profileGoodAt: '這個設定檔擅長什麼?', - auto: '自動' - } }) diff --git a/apps/desktop/src/i18n/zh.ts b/apps/desktop/src/i18n/zh.ts index 49d0c2d3449..c0eea0e79a2 100644 --- a/apps/desktop/src/i18n/zh.ts +++ b/apps/desktop/src/i18n/zh.ts @@ -3115,187 +3115,4 @@ export const zh: Translations = { toggle: open => `${open ? '显示' : '隐藏'}侧边栏` } }, - - kanban: { - nav: '看板', - openBoard: '看板:打开面板', - countTip: (running, ready) => `看板 — 运行中 ${running}、就绪 ${ready}`, - col: { - triage: { label: '分诊', help: '原始想法 — 由细化代理整理出规格。' }, - todo: { label: '待办', help: '等待依赖,或未分配。' }, - scheduled: { label: '已排期', help: '等待预定时间到来。' }, - ready: { label: '就绪', help: '依赖已满足 — 分配一个配置档,调度器即会运行它。' }, - running: { label: '运行中', help: '已被工作单元领取 — 有代理在处理。由调度器设置。' }, - blocked: { label: '受阻', help: '工作单元需要人工输入。' }, - review: { label: '审查', help: '审查代理正在检查工作。由调度器设置。' }, - done: { label: '完成', help: '已完成;依赖它的子任务变为就绪。' }, - archived: { label: '已归档', help: '从默认面板视图中隐藏。' } - }, - locked: { - review: '审查状态由调度器在审查代理领取卡片时设置。', - running: '运行中由调度器在工作单元领取卡片时设置。', - scheduled: '排期需要唤醒时间 — 由代理设置;无法拖入。' - }, - arcRunning: '有代理正在处理它。', - arcStale: '已领取,但超过 2 分钟没有工作单元心跳 — 调度器将重新领取。', - title: '看板', - orchestrationSettings: '编排设置', - newTask: '新建任务', - filterCards: '筛选卡片…', - noMatch: '没有符合筛选条件的任务', - noTasks: '此面板暂无任务', - open: '打开', - select: '选择(⌘点击)', - deselect: '取消选择', - moveTo: label => `移动到 ${label}`, - delete: '删除', - reviewChecking: '审查代理正在检查已完成的工作。', - attachedTip: name => `${name} 已接手 — 调度器将在下一个周期(≤1 分钟)移交。`, - orchestratorTip: name => `${name}(编排者)将在下一个周期领取并撰写规格。`, - autoAssignTip: name => `将在下一个调度周期自动分配给“${name}”(kanban.default_assignee)。`, - wontRun: '不会运行', - wontRunTip: '就绪卡片只有在分配了配置档后才会运行。打开卡片设置负责人,或在编排设置中配置默认负责人。', - noHeartbeat: '无心跳', - expand: label => `展开 ${label}`, - collapse: label => `折叠 ${label}`, - newTaskIn: label => `在 ${label} 新建任务`, - empty: '空', - unassigned: '未分配', - filters: '筛选', - allProfiles: '所有配置档', - allTenants: '所有租户', - showArchived: '显示已归档', - groupRunning: '按配置档分组运行中', - nSelected: n => `已选择 ${n} 个`, - moveToShort: '移动到', - assign: '分配', - unassignAction: '取消分配', - archive: '归档', - clearSelection: '清除选择(Esc)', - refused: '被拒绝', - bulkFailed: (failed, total, err) => `${total} 个中有 ${failed} 个失败 — ${err}。失败的卡片仍保持选中。`, - titlePlaceholderTriage: '大致想法 — 细化代理会补全', - titlePlaceholder: '标题', - descPlaceholder: '描述(可选)', - priority: '优先级', - workspace: '工作区', - boardDefaultSuffix: '・面板默认', - workspaceOverride: '工作区路径(可选覆盖)', - workspaceInherit: '继承面板的项目目录', - workspaceInheritDir: dir => `留空则继承 ${dir}`, - workspaceInheritGeneric: '留空则继承面板的项目目录。', - assignee: '负责人', - defaultOption: name => `${name}(默认)`, - parkedOption: '未分配(搁置 — 不会运行)', - skills: '技能(逗号分隔)', - skillsPlaceholder: 'translation, github', - parent: '父任务(完成前会阻塞)', - noParent: '— 无父任务 —', - goalMode: '目标模式(工作单元循环直到评判代理认可完成)', - creating: '创建中…', - createTask: '创建任务', - cancel: '取消', - save: '保存', - estimate: '估算', - estimateEffort: '估算工作量', - estimating: '估算中…', - reEstimate: '重新估算', - makesModelCall: '会调用模型', - estimateTip: '由辅助模型对令牌数和复杂度的粗略估算 — 会调用模型。', - estimateTipLong: '快速调用辅助模型来估算令牌数和复杂度。仅供参考,并非账单。', - roughEstimate: '粗略估算', - tokUnit: 'tok', - couldNotEstimate: '无法估算', - complexity: { S: '小', M: '中', L: '大' }, - introBody: - '卡片不由你运行,而是由代理运行。把带有负责人的卡片放入“就绪”,代理会在一分钟内领取。没有负责人就不会运行。分诊:代理先把想法改写成合适的任务。待办:等待其他卡片。已排期:等待计时器。运行中与审查:这是代理的通道,请勿插手。受阻:正在等你。结果会回到卡片上。', - introGotIt: '知道了', - evtCreated: (where, assignee) => - `已创建${where ? `(${where})` : ''}${assignee ? `・分配给 ${assignee}` : ''}`, - evtMovedTo: col => `移动到 ${col}`, - evtParentReopened: parent => `父任务 ${parent} 已重新打开`, - evtAssignedTo: assignee => `分配给 ${assignee}`, - evtUnassigned: '取消分配', - evtCommentBy: author => `${author} 的评论`, - evtClaimedReview: '被审查代理领取', - evtClaimedWorker: '被工作单元领取', - evtWorkerStarted: '工作单元已启动', - evtCompleted: '已完成', - evtBlocked: '受阻 — 需要人工输入', - evtUnblocked: col => `已解除阻塞${col ? ` → ${col}` : ' → 就绪'}`, - evtReclaimed: '已重新领取 — 已放回队列', - evtSpecified: '分诊代理已撰写规格', - evtPromoted: '依赖已完成 — 提升为就绪', - evtScheduled: '已排期稍后运行', - evtArchived: '已归档', - evtReprioritized: priority => `优先级设为 ${priority}`, - someone: '某人', - metaPriority: '优先级', - metaTenant: '租户', - metaCreatedBy: '创建者', - metaCreated: '创建于', - metaWorkerPid: '工作单元 PID', - readyUnassignedTitle: '就绪但未分配 — 这张卡片永远不会运行。', - readyUnassignedBody: - '调度器只领取有负责人的就绪卡片。在上面的负责人字段选择一个配置档(或在编排设置中设置默认负责人),它会在一分钟内运行。', - diagnosticsN: n => `诊断・${n}`, - commandCopied: '命令已复制', - description: '描述', - editDescription: '编辑描述', - cancelEdit: '取消编辑', - noDescription: '暂无描述。', - result: '结果', - latestSummary: '最新摘要', - dependencies: '依赖关系', - blockedBy: '受阻于', - blocks: '阻塞', - comments: n => `评论・${n}`, - commentsHelpRunning: - '此任务正在运行。你的备注会在几秒内融入工作单元当前的回合 — 无需阻塞/解除操作。选择“附带备注重新入队”则会带着你的备注从头重跑任务。', - commentsHelp: '评论会添加到任务讨论串。工作单元领取任务时会将其作为上下文的一部分读取。', - send: '发送', - comment: '评论', - messageWorker: '给运行中的工作单元发消息…', - addComment: '添加评论…', - deliveredLive: '几秒内送达运行中的工作单元。', - requeueWithNote: '附带备注重新入队', - notePosted: '备注已发布 — 工作单元已重新入队', - activity: n => `活动・${n}`, - runs: n => `运行・${n}`, - workerLog: '工作单元日志', - workerLogTail: '工作单元日志・末尾', - attachments: n => `附件・${n}`, - noAttachments: '暂无附件。', - uploadAttachment: '上传附件', - taskActions: '任务操作', - copyTaskId: '复制任务 ID', - copyTitle: '复制标题', - copiedId: id => `已复制 ${id}`, - copiedTitle: '已复制标题', - archiveTask: '归档任务', - deleteTask: '删除任务', - close: '关闭', - working: '进行中', - board: '面板', - newBoard: '新建面板', - newBoardDots: '新建面板…', - boardSettings: '面板设置…', - boardSettingsFor: name => `面板设置 — ${name}`, - name: '名称', - boardNamePlaceholder: '面板名称', - slug: slug => `slug: ${slug}`, - project: '项目', - noProject: '无项目(临时沙箱)', - projectHintPre: '新任务将在项目的仓库中运行(每个任务一个 worktree);每个任务在创建时仍可覆盖其工作区。管理项目请使用 ', - projectHintCmd: 'hermes project', - createBoard: '创建面板', - orchestratorProfile: '编排者配置档', - defaultAssignee: '默认负责人', - defaultParen: '(默认)', - autoDecompose: '自动分解分诊任务', - profileDescriptions: '配置档说明', - profileDescriptionsHint: '说明用于引导分解器的路由。可用辅助模型自动生成,或自行填写。', - profileGoodAt: '这个配置档擅长什么?', - auto: '自动' - } } diff --git a/apps/desktop/src/plugins/kanban/i18n.ts b/apps/desktop/src/plugins/kanban/i18n.ts new file mode 100644 index 00000000000..62a5b3261b8 --- /dev/null +++ b/apps/desktop/src/plugins/kanban/i18n.ts @@ -0,0 +1,963 @@ +/** + * Plugin-scoped i18n for kanban — bundles shipped under the plugin id via + * ctx.i18n.register (#67303), never touching core en.ts. usePluginI18n('kanban') + * returns a stringly-typed t(key, …); `useKanban()` binds it to the message + * SHAPE so components keep typed `k.newTask` / `k.moveTo(label)` access. + */ + +import { type PluginLocaleBundles, type PluginTranslate, usePluginI18n } from '@hermes/plugin-sdk' +import { useMemo } from 'react' + +type KanbanMessages = { + nav: string + openBoard: string + countTip: (running: number, ready: number) => string + col: Record< + 'archived' | 'blocked' | 'done' | 'ready' | 'review' | 'running' | 'scheduled' | 'todo' | 'triage', + { label: string; help: string } + > + locked: { review: string; running: string; scheduled: string } + arcRunning: string + arcStale: string + title: string + orchestrationSettings: string + newTask: string + filterCards: string + noMatch: string + noTasks: string + open: string + select: string + deselect: string + moveTo: (label: string) => string + delete: string + reviewChecking: string + attachedTip: (name: string) => string + orchestratorTip: (name: string) => string + autoAssignTip: (name: string) => string + wontRun: string + wontRunTip: string + noHeartbeat: string + expand: (label: string) => string + collapse: (label: string) => string + newTaskIn: (label: string) => string + empty: string + unassigned: string + filters: string + allProfiles: string + allTenants: string + showArchived: string + groupRunning: string + nSelected: (n: number) => string + moveToShort: string + assign: string + unassignAction: string + archive: string + clearSelection: string + refused: string + bulkFailed: (failed: number, total: number, err: string) => string + titlePlaceholderTriage: string + titlePlaceholder: string + descPlaceholder: string + priority: string + workspace: string + boardDefaultSuffix: string + workspaceOverride: string + workspaceInherit: string + workspaceInheritDir: (dir: string) => string + workspaceInheritGeneric: string + assignee: string + defaultOption: (name: string) => string + parkedOption: string + skills: string + skillsPlaceholder: string + parent: string + noParent: string + goalMode: string + creating: string + createTask: string + cancel: string + save: string + estimate: string + estimateEffort: string + estimating: string + reEstimate: string + makesModelCall: string + estimateTip: string + estimateTipLong: string + roughEstimate: string + tokUnit: string + couldNotEstimate: string + complexity: Record<'L' | 'M' | 'S', string> + introBody: string + introGotIt: string + // drawer — activity prose + evtCreated: (where: string, assignee: string) => string + evtMovedTo: (col: string) => string + evtParentReopened: (parent: string) => string + evtAssignedTo: (assignee: string) => string + evtUnassigned: string + evtCommentBy: (author: string) => string + evtClaimedReview: string + evtClaimedWorker: string + evtWorkerStarted: string + evtCompleted: string + evtBlocked: string + evtUnblocked: (col: string) => string + evtReclaimed: string + evtSpecified: string + evtPromoted: string + evtScheduled: string + evtArchived: string + evtReprioritized: (priority: string) => string + someone: string + // drawer — meta + sections + metaPriority: string + metaTenant: string + metaCreatedBy: string + metaCreated: string + metaWorkerPid: string + readyUnassignedTitle: string + readyUnassignedBody: string + diagnosticsN: (n: number) => string + commandCopied: string + description: string + editDescription: string + cancelEdit: string + noDescription: string + result: string + latestSummary: string + dependencies: string + blockedBy: string + blocks: string + comments: (n: number) => string + commentsHelpRunning: string + commentsHelp: string + send: string + comment: string + messageWorker: string + addComment: string + deliveredLive: string + requeueWithNote: string + notePosted: string + activity: (n: number) => string + runs: (n: number) => string + workerLog: string + workerLogTail: string + attachments: (n: number) => string + noAttachments: string + uploadAttachment: string + taskActions: string + copyTaskId: string + copyTitle: string + copiedId: (id: string) => string + copiedTitle: string + archiveTask: string + deleteTask: string + close: string + working: string + // board switcher + board: string + newBoard: string + newBoardDots: string + boardSettings: string + boardSettingsFor: (name: string) => string + name: string + boardNamePlaceholder: string + slug: (slug: string) => string + project: string + noProject: string + projectHintPre: string + projectHintCmd: string + createBoard: string + // orchestration + orchestratorProfile: string + defaultAssignee: string + defaultParen: string + autoDecompose: string + profileDescriptions: string + profileDescriptionsHint: string + profileGoodAt: string + auto: string + } + +const en: KanbanMessages = { + nav: 'Kanban', + openBoard: 'Kanban: Open board', + countTip: (running, ready) => `Kanban — ${running} running, ${ready} ready`, + col: { + triage: { label: 'Triage', help: 'Raw ideas — a specifier fleshes out the spec.' }, + todo: { label: 'Todo', help: 'Waiting on dependencies, or unassigned.' }, + scheduled: { label: 'Scheduled', help: 'Waiting for a scheduled time to arrive.' }, + ready: { label: 'Ready', help: 'Dependencies satisfied — assign a profile and the dispatcher runs it.' }, + running: { label: 'Running', help: 'Claimed by a worker — an agent is on it. Set by the dispatcher.' }, + blocked: { label: 'Blocked', help: 'The worker asked for human input.' }, + review: { label: 'Review', help: 'A review agent is checking the work. Set by the dispatcher.' }, + done: { label: 'Done', help: 'Completed; dependent children become ready.' }, + archived: { label: 'Archived', help: 'Hidden from the default board view.' } + }, + locked: { + review: 'Review is entered by the dispatcher when a review agent takes the card.', + running: 'Running is set by the dispatcher when a worker claims the card.', + scheduled: 'Scheduled needs a wake-up time — agents set it; it can’t be dragged into.' + }, + arcRunning: 'An agent is working on this now.', + arcStale: 'Claimed, but no worker heartbeat for 2+ minutes — the dispatcher will reclaim it.', + title: 'Kanban', + orchestrationSettings: 'Orchestration settings', + newTask: 'New task', + filterCards: 'Filter cards…', + noMatch: 'No tasks match the filters', + noTasks: 'No tasks on this board', + open: 'Open', + select: 'Select (⌘-click)', + deselect: 'Deselect', + moveTo: label => `Move to ${label}`, + delete: 'Delete', + reviewChecking: 'A review agent is checking the completed work.', + attachedTip: name => `${name} is attached — the dispatcher hands this over on its next tick (≤1m).`, + orchestratorTip: name => `${name} (the orchestrator) picks this up on the next tick and writes the spec.`, + autoAssignTip: name => `Auto-assigns to “${name}” (kanban.default_assignee) on the next dispatch tick.`, + wontRun: "won't run", + wontRunTip: + 'Ready cards only run once a profile is assigned. Open the card and set an assignee, or configure a default assignee in orchestration settings.', + noHeartbeat: 'no heartbeat', + expand: label => `Expand ${label}`, + collapse: label => `Collapse ${label}`, + newTaskIn: label => `New task in ${label}`, + empty: 'Empty', + unassigned: 'unassigned', + filters: 'Filters', + allProfiles: 'All profiles', + allTenants: 'All tenants', + showArchived: 'Show archived', + groupRunning: 'Group Running by profile', + nSelected: n => `${n} selected`, + moveToShort: 'Move to', + assign: 'Assign', + unassignAction: 'Unassign', + archive: 'Archive', + clearSelection: 'Clear selection (Esc)', + refused: 'refused', + bulkFailed: (failed, total, err) => `${failed} of ${total} failed — ${err}. Failed cards stay selected.`, + titlePlaceholderTriage: 'Rough idea — a specifier will flesh it out', + titlePlaceholder: 'Title', + descPlaceholder: 'Description (optional)', + priority: 'Priority', + workspace: 'Workspace', + boardDefaultSuffix: ' · board default', + workspaceOverride: 'Workspace path (optional override)', + workspaceInherit: 'Inherits the board’s project directory', + workspaceInheritDir: dir => `Leave empty to inherit ${dir}`, + workspaceInheritGeneric: 'Leave empty to inherit the board’s project directory.', + assignee: 'Assignee', + defaultOption: name => `${name} (default)`, + parkedOption: "unassigned (parked — won't run)", + skills: 'Skills (comma-separated)', + skillsPlaceholder: 'translation, github', + parent: "Parent (blocks until it's done)", + noParent: '— no parent —', + goalMode: "Goal mode (worker loops until a judge agrees it's done)", + creating: 'Creating…', + createTask: 'Create task', + cancel: 'Cancel', + save: 'Save', + estimate: 'Estimate', + estimateEffort: 'Estimate effort', + estimating: 'Estimating…', + reEstimate: 'Re-estimate', + makesModelCall: 'makes a model call', + estimateTip: 'Rough token + complexity estimate from the auxiliary model — makes a model call.', + estimateTipLong: 'Runs a quick auxiliary-model call to estimate tokens + complexity. A rough guide, not a bill.', + roughEstimate: 'Rough estimate', + tokUnit: 'tok', + couldNotEstimate: 'Could not estimate', + complexity: { S: 'Small', M: 'Medium', L: 'Large' }, + introBody: + 'You don’t run the cards — agents do. Put a card in Ready with an assignee and an agent picks it up within a minute. No assignee, no run. Triage: an agent rewrites the idea into a proper task first. Todo: waiting on other cards. Scheduled: waiting on a timer. Running and Review: the agents’ lanes, hands off. Blocked: it’s waiting on you. Results come back on the card.', + introGotIt: 'Got it', + evtCreated: (where, assignee) => `created${where ? ` in ${where}` : ''}${assignee ? ` · assigned to ${assignee}` : ''}`, + evtMovedTo: col => `moved to ${col}`, + evtParentReopened: parent => `parent ${parent} reopened`, + evtAssignedTo: assignee => `assigned to ${assignee}`, + evtUnassigned: 'unassigned', + evtCommentBy: author => `comment by ${author}`, + evtClaimedReview: 'claimed by a review agent', + evtClaimedWorker: 'claimed by a worker', + evtWorkerStarted: 'worker started', + evtCompleted: 'completed', + evtBlocked: 'blocked — needs human input', + evtUnblocked: col => `unblocked${col ? ` → ${col}` : ' → Ready'}`, + evtReclaimed: 'reclaimed — returned to the queue', + evtSpecified: 'spec written by the triage agent', + evtPromoted: 'dependencies done — promoted to Ready', + evtScheduled: 'scheduled for later', + evtArchived: 'archived', + evtReprioritized: priority => `priority set to ${priority}`, + someone: 'someone', + metaPriority: 'Priority', + metaTenant: 'Tenant', + metaCreatedBy: 'Created by', + metaCreated: 'Created', + metaWorkerPid: 'Worker pid', + readyUnassignedTitle: 'Ready, but unassigned — this card will never run.', + readyUnassignedBody: + 'The dispatcher only claims Ready cards that have an assignee. Pick a profile in the Assignee field above (or set a default assignee in the orchestration settings) and it runs within a minute.', + diagnosticsN: n => `Diagnostics · ${n}`, + commandCopied: 'Command copied', + description: 'Description', + editDescription: 'Edit description', + cancelEdit: 'Cancel edit', + noDescription: 'No description yet.', + result: 'Result', + latestSummary: 'Latest summary', + dependencies: 'Dependencies', + blockedBy: 'Blocked by', + blocks: 'Blocks', + comments: n => `Comments · ${n}`, + commentsHelpRunning: + 'This task is running. Your note is folded into the worker’s current turn within a few seconds — no block/unblock dance. “Requeue with note” instead restarts the task from scratch with your note in context.', + commentsHelp: + 'Comments are added to the task thread. When a worker picks the task up it reads them as part of its context.', + send: 'Send', + comment: 'Comment', + messageWorker: 'Message the running worker…', + addComment: 'Add a comment…', + deliveredLive: 'Delivered to the running worker within a few seconds.', + requeueWithNote: 'Requeue with note', + notePosted: 'Note posted — worker requeued', + activity: n => `Activity · ${n}`, + runs: n => `Runs · ${n}`, + workerLog: 'Worker log', + workerLogTail: 'Worker log · tail', + attachments: n => `Attachments · ${n}`, + noAttachments: 'No attachments yet.', + uploadAttachment: 'Upload attachment', + taskActions: 'Task actions', + copyTaskId: 'Copy task id', + copyTitle: 'Copy title', + copiedId: id => `Copied ${id}`, + copiedTitle: 'Copied title', + archiveTask: 'Archive task', + deleteTask: 'Delete task', + close: 'Close', + working: 'working', + board: 'Board', + newBoard: 'New board', + newBoardDots: 'New board…', + boardSettings: 'Board settings…', + boardSettingsFor: name => `Board settings — ${name}`, + name: 'Name', + boardNamePlaceholder: 'Board name', + slug: slug => `slug: ${slug}`, + project: 'Project', + noProject: 'No project (scratch sandboxes)', + projectHintPre: + 'New tasks run in the project’s repo (a worktree per task); each task can still override its workspace at creation. Manage projects with ', + projectHintCmd: 'hermes project', + createBoard: 'Create board', + orchestratorProfile: 'Orchestrator profile', + defaultAssignee: 'Default assignee', + defaultParen: '(default)', + autoDecompose: 'Auto-decompose triage tasks', + profileDescriptions: 'Profile descriptions', + profileDescriptionsHint: + 'Descriptions guide the decomposer’s routing. Auto-generate with the auxiliary model, or write your own.', + profileGoodAt: 'What is this profile good at?', + auto: 'Auto' + } + +const ja: KanbanMessages = { + nav: 'カンバン', + openBoard: 'カンバン: ボードを開く', + countTip: (running, ready) => `カンバン — 実行中 ${running}、待機 ${ready}`, + col: { + triage: { label: 'トリアージ', help: '生のアイデア — スペシファイアが仕様に整えます。' }, + todo: { label: 'Todo', help: '依存関係の待ち、または未割り当て。' }, + scheduled: { label: 'スケジュール', help: '予定時刻を待っています。' }, + ready: { label: 'Ready', help: '依存関係が解決済み — プロフィールを割り当てるとディスパッチャが実行します。' }, + running: { label: '実行中', help: 'ワーカーが取得済み — エージェントが作業中。ディスパッチャが設定します。' }, + blocked: { label: 'ブロック', help: 'ワーカーが人間の入力を求めています。' }, + review: { label: 'レビュー', help: 'レビューエージェントが作業を確認中。ディスパッチャが設定します。' }, + done: { label: '完了', help: '完了。依存する子タスクが Ready になります。' }, + archived: { label: 'アーカイブ', help: 'デフォルトのボード表示から非表示。' } + }, + locked: { + review: 'レビューは、レビューエージェントがカードを取得するとディスパッチャによって設定されます。', + running: '実行中は、ワーカーがカードを取得するとディスパッチャによって設定されます。', + scheduled: 'スケジュールには起動時刻が必要です — エージェントが設定します。ドラッグでは移動できません。' + }, + arcRunning: 'エージェントが現在作業中です。', + arcStale: '取得済みですが、2分以上ワーカーのハートビートがありません — ディスパッチャが再取得します。', + title: 'カンバン', + orchestrationSettings: 'オーケストレーション設定', + newTask: '新しいタスク', + filterCards: 'カードを絞り込み…', + noMatch: 'フィルタに一致するタスクはありません', + noTasks: 'このボードにタスクはありません', + open: '開く', + select: '選択(⌘クリック)', + deselect: '選択解除', + moveTo: label => `${label} へ移動`, + delete: '削除', + reviewChecking: 'レビューエージェントが完了した作業を確認中です。', + attachedTip: name => `${name} が担当 — ディスパッチャが次のティック(≤1分)で引き渡します。`, + orchestratorTip: name => `${name}(オーケストレーター)が次のティックでこれを取得し、仕様を書きます。`, + autoAssignTip: name => `次のディスパッチティックで「${name}」(kanban.default_assignee)に自動割り当てされます。`, + wontRun: '実行されません', + wontRunTip: + 'Ready のカードはプロフィールが割り当てられて初めて実行されます。カードを開いて担当を設定するか、オーケストレーション設定でデフォルトの担当を設定してください。', + noHeartbeat: 'ハートビートなし', + expand: label => `${label} を展開`, + collapse: label => `${label} を折りたたむ`, + newTaskIn: label => `${label} に新しいタスク`, + empty: '空', + unassigned: '未割り当て', + filters: 'フィルタ', + allProfiles: 'すべてのプロフィール', + allTenants: 'すべてのテナント', + showArchived: 'アーカイブを表示', + groupRunning: '実行中をプロフィールでグループ化', + nSelected: n => `${n} 件選択中`, + moveToShort: '移動', + assign: '割り当て', + unassignAction: '割り当て解除', + archive: 'アーカイブ', + clearSelection: '選択をクリア(Esc)', + refused: '拒否されました', + bulkFailed: (failed, total, err) => `${total} 件中 ${failed} 件が失敗 — ${err}。失敗したカードは選択されたままです。`, + titlePlaceholderTriage: '大まかなアイデア — スペシファイアが具体化します', + titlePlaceholder: 'タイトル', + descPlaceholder: '説明(任意)', + priority: '優先度', + workspace: 'ワークスペース', + boardDefaultSuffix: '・ボード既定', + workspaceOverride: 'ワークスペースパス(任意の上書き)', + workspaceInherit: 'ボードのプロジェクトディレクトリを継承', + workspaceInheritDir: dir => `空欄にすると ${dir} を継承します`, + workspaceInheritGeneric: '空欄にするとボードのプロジェクトディレクトリを継承します。', + assignee: '担当', + defaultOption: name => `${name}(既定)`, + parkedOption: '未割り当て(保留 — 実行されません)', + skills: 'スキル(カンマ区切り)', + skillsPlaceholder: 'translation, github', + parent: '親(完了するまでブロック)', + noParent: '— 親なし —', + goalMode: 'ゴールモード(ジャッジが完了と認めるまでワーカーがループ)', + creating: '作成中…', + createTask: 'タスクを作成', + cancel: 'キャンセル', + save: '保存', + estimate: '見積もり', + estimateEffort: '工数を見積もり', + estimating: '見積もり中…', + reEstimate: '再見積もり', + makesModelCall: 'モデル呼び出しあり', + estimateTip: '補助モデルによるトークン数と複雑度の概算 — モデル呼び出しを行います。', + estimateTipLong: '補助モデルを呼び出してトークン数と複雑度を概算します。目安であり、請求ではありません。', + roughEstimate: '概算', + tokUnit: 'tok', + couldNotEstimate: '見積もりできませんでした', + complexity: { S: '小', M: '中', L: '大' }, + introBody: + 'カードはあなたではなくエージェントが実行します。担当を設定したカードを Ready に置くと、1分以内にエージェントが取得します。担当がなければ実行されません。トリアージ: エージェントがまずアイデアを適切なタスクに書き直します。Todo: 他のカード待ち。スケジュール: タイマー待ち。実行中とレビュー: エージェントのレーンなので手を出さないでください。ブロック: あなたの対応待ちです。結果はカードに戻ってきます。', + introGotIt: '了解', + evtCreated: (where, assignee) => + `作成${where ? `(${where})` : ''}${assignee ? `・${assignee} に割り当て` : ''}`, + evtMovedTo: col => `${col} へ移動`, + evtParentReopened: parent => `親 ${parent} が再オープン`, + evtAssignedTo: assignee => `${assignee} に割り当て`, + evtUnassigned: '割り当て解除', + evtCommentBy: author => `${author} のコメント`, + evtClaimedReview: 'レビューエージェントが取得', + evtClaimedWorker: 'ワーカーが取得', + evtWorkerStarted: 'ワーカー開始', + evtCompleted: '完了', + evtBlocked: 'ブロック — 人間の入力が必要', + evtUnblocked: col => `ブロック解除${col ? ` → ${col}` : ' → Ready'}`, + evtReclaimed: '再取得 — キューに戻しました', + evtSpecified: 'トリアージエージェントが仕様を作成', + evtPromoted: '依存関係が完了 — Ready に昇格', + evtScheduled: '後で実行するようスケジュール', + evtArchived: 'アーカイブ済み', + evtReprioritized: priority => `優先度を ${priority} に設定`, + someone: '誰か', + metaPriority: '優先度', + metaTenant: 'テナント', + metaCreatedBy: '作成者', + metaCreated: '作成', + metaWorkerPid: 'ワーカー PID', + readyUnassignedTitle: 'Ready ですが未割り当て — このカードは実行されません。', + readyUnassignedBody: + 'ディスパッチャは担当のある Ready カードのみ取得します。上の担当フィールドでプロフィールを選ぶ(またはオーケストレーション設定でデフォルトの担当を設定する)と、1分以内に実行されます。', + diagnosticsN: n => `診断・${n}`, + commandCopied: 'コマンドをコピーしました', + description: '説明', + editDescription: '説明を編集', + cancelEdit: '編集をキャンセル', + noDescription: 'まだ説明はありません。', + result: '結果', + latestSummary: '最新のサマリー', + dependencies: '依存関係', + blockedBy: 'ブロック元', + blocks: 'ブロック先', + comments: n => `コメント・${n}`, + commentsHelpRunning: + 'このタスクは実行中です。あなたのメモは数秒以内にワーカーの現在のターンに取り込まれます — ブロック/解除の操作は不要です。「メモを付けて再キュー」を選ぶと、メモを文脈に含めてタスクを最初からやり直します。', + commentsHelp: 'コメントはタスクのスレッドに追加されます。ワーカーがタスクを取得すると、文脈の一部として読み込みます。', + send: '送信', + comment: 'コメント', + messageWorker: '実行中のワーカーにメッセージ…', + addComment: 'コメントを追加…', + deliveredLive: '数秒以内に実行中のワーカーへ届きます。', + requeueWithNote: 'メモを付けて再キュー', + notePosted: 'メモを投稿しました — ワーカーを再キューしました', + activity: n => `アクティビティ・${n}`, + runs: n => `実行・${n}`, + workerLog: 'ワーカーログ', + workerLogTail: 'ワーカーログ・末尾', + attachments: n => `添付・${n}`, + noAttachments: 'まだ添付はありません。', + uploadAttachment: '添付をアップロード', + taskActions: 'タスクの操作', + copyTaskId: 'タスク ID をコピー', + copyTitle: 'タイトルをコピー', + copiedId: id => `${id} をコピーしました`, + copiedTitle: 'タイトルをコピーしました', + archiveTask: 'タスクをアーカイブ', + deleteTask: 'タスクを削除', + close: '閉じる', + working: '作業中', + board: 'ボード', + newBoard: '新しいボード', + newBoardDots: '新しいボード…', + boardSettings: 'ボード設定…', + boardSettingsFor: name => `ボード設定 — ${name}`, + name: '名前', + boardNamePlaceholder: 'ボード名', + slug: slug => `slug: ${slug}`, + project: 'プロジェクト', + noProject: 'プロジェクトなし(スクラッチのサンドボックス)', + projectHintPre: + '新しいタスクはプロジェクトのリポジトリで実行されます(タスクごとに worktree)。各タスクは作成時にワークスペースを上書きできます。プロジェクトの管理は ', + projectHintCmd: 'hermes project', + createBoard: 'ボードを作成', + orchestratorProfile: 'オーケストレータープロフィール', + defaultAssignee: 'デフォルトの担当', + defaultParen: '(既定)', + autoDecompose: 'トリアージタスクを自動分解', + profileDescriptions: 'プロフィールの説明', + profileDescriptionsHint: + '説明はデコンポーザーのルーティングを導きます。補助モデルで自動生成するか、自分で書いてください。', + profileGoodAt: 'このプロフィールの得意分野は?', + auto: '自動' + } + +const zh: KanbanMessages = { + nav: '看板', + openBoard: '看板:打开面板', + countTip: (running, ready) => `看板 — 运行中 ${running}、就绪 ${ready}`, + col: { + triage: { label: '分诊', help: '原始想法 — 由细化代理整理出规格。' }, + todo: { label: '待办', help: '等待依赖,或未分配。' }, + scheduled: { label: '已排期', help: '等待预定时间到来。' }, + ready: { label: '就绪', help: '依赖已满足 — 分配一个配置档,调度器即会运行它。' }, + running: { label: '运行中', help: '已被工作单元领取 — 有代理在处理。由调度器设置。' }, + blocked: { label: '受阻', help: '工作单元需要人工输入。' }, + review: { label: '审查', help: '审查代理正在检查工作。由调度器设置。' }, + done: { label: '完成', help: '已完成;依赖它的子任务变为就绪。' }, + archived: { label: '已归档', help: '从默认面板视图中隐藏。' } + }, + locked: { + review: '审查状态由调度器在审查代理领取卡片时设置。', + running: '运行中由调度器在工作单元领取卡片时设置。', + scheduled: '排期需要唤醒时间 — 由代理设置;无法拖入。' + }, + arcRunning: '有代理正在处理它。', + arcStale: '已领取,但超过 2 分钟没有工作单元心跳 — 调度器将重新领取。', + title: '看板', + orchestrationSettings: '编排设置', + newTask: '新建任务', + filterCards: '筛选卡片…', + noMatch: '没有符合筛选条件的任务', + noTasks: '此面板暂无任务', + open: '打开', + select: '选择(⌘点击)', + deselect: '取消选择', + moveTo: label => `移动到 ${label}`, + delete: '删除', + reviewChecking: '审查代理正在检查已完成的工作。', + attachedTip: name => `${name} 已接手 — 调度器将在下一个周期(≤1 分钟)移交。`, + orchestratorTip: name => `${name}(编排者)将在下一个周期领取并撰写规格。`, + autoAssignTip: name => `将在下一个调度周期自动分配给“${name}”(kanban.default_assignee)。`, + wontRun: '不会运行', + wontRunTip: '就绪卡片只有在分配了配置档后才会运行。打开卡片设置负责人,或在编排设置中配置默认负责人。', + noHeartbeat: '无心跳', + expand: label => `展开 ${label}`, + collapse: label => `折叠 ${label}`, + newTaskIn: label => `在 ${label} 新建任务`, + empty: '空', + unassigned: '未分配', + filters: '筛选', + allProfiles: '所有配置档', + allTenants: '所有租户', + showArchived: '显示已归档', + groupRunning: '按配置档分组运行中', + nSelected: n => `已选择 ${n} 个`, + moveToShort: '移动到', + assign: '分配', + unassignAction: '取消分配', + archive: '归档', + clearSelection: '清除选择(Esc)', + refused: '被拒绝', + bulkFailed: (failed, total, err) => `${total} 个中有 ${failed} 个失败 — ${err}。失败的卡片仍保持选中。`, + titlePlaceholderTriage: '大致想法 — 细化代理会补全', + titlePlaceholder: '标题', + descPlaceholder: '描述(可选)', + priority: '优先级', + workspace: '工作区', + boardDefaultSuffix: '・面板默认', + workspaceOverride: '工作区路径(可选覆盖)', + workspaceInherit: '继承面板的项目目录', + workspaceInheritDir: dir => `留空则继承 ${dir}`, + workspaceInheritGeneric: '留空则继承面板的项目目录。', + assignee: '负责人', + defaultOption: name => `${name}(默认)`, + parkedOption: '未分配(搁置 — 不会运行)', + skills: '技能(逗号分隔)', + skillsPlaceholder: 'translation, github', + parent: '父任务(完成前会阻塞)', + noParent: '— 无父任务 —', + goalMode: '目标模式(工作单元循环直到评判代理认可完成)', + creating: '创建中…', + createTask: '创建任务', + cancel: '取消', + save: '保存', + estimate: '估算', + estimateEffort: '估算工作量', + estimating: '估算中…', + reEstimate: '重新估算', + makesModelCall: '会调用模型', + estimateTip: '由辅助模型对令牌数和复杂度的粗略估算 — 会调用模型。', + estimateTipLong: '快速调用辅助模型来估算令牌数和复杂度。仅供参考,并非账单。', + roughEstimate: '粗略估算', + tokUnit: 'tok', + couldNotEstimate: '无法估算', + complexity: { S: '小', M: '中', L: '大' }, + introBody: + '卡片不由你运行,而是由代理运行。把带有负责人的卡片放入“就绪”,代理会在一分钟内领取。没有负责人就不会运行。分诊:代理先把想法改写成合适的任务。待办:等待其他卡片。已排期:等待计时器。运行中与审查:这是代理的通道,请勿插手。受阻:正在等你。结果会回到卡片上。', + introGotIt: '知道了', + evtCreated: (where, assignee) => + `已创建${where ? `(${where})` : ''}${assignee ? `・分配给 ${assignee}` : ''}`, + evtMovedTo: col => `移动到 ${col}`, + evtParentReopened: parent => `父任务 ${parent} 已重新打开`, + evtAssignedTo: assignee => `分配给 ${assignee}`, + evtUnassigned: '取消分配', + evtCommentBy: author => `${author} 的评论`, + evtClaimedReview: '被审查代理领取', + evtClaimedWorker: '被工作单元领取', + evtWorkerStarted: '工作单元已启动', + evtCompleted: '已完成', + evtBlocked: '受阻 — 需要人工输入', + evtUnblocked: col => `已解除阻塞${col ? ` → ${col}` : ' → 就绪'}`, + evtReclaimed: '已重新领取 — 已放回队列', + evtSpecified: '分诊代理已撰写规格', + evtPromoted: '依赖已完成 — 提升为就绪', + evtScheduled: '已排期稍后运行', + evtArchived: '已归档', + evtReprioritized: priority => `优先级设为 ${priority}`, + someone: '某人', + metaPriority: '优先级', + metaTenant: '租户', + metaCreatedBy: '创建者', + metaCreated: '创建于', + metaWorkerPid: '工作单元 PID', + readyUnassignedTitle: '就绪但未分配 — 这张卡片永远不会运行。', + readyUnassignedBody: + '调度器只领取有负责人的就绪卡片。在上面的负责人字段选择一个配置档(或在编排设置中设置默认负责人),它会在一分钟内运行。', + diagnosticsN: n => `诊断・${n}`, + commandCopied: '命令已复制', + description: '描述', + editDescription: '编辑描述', + cancelEdit: '取消编辑', + noDescription: '暂无描述。', + result: '结果', + latestSummary: '最新摘要', + dependencies: '依赖关系', + blockedBy: '受阻于', + blocks: '阻塞', + comments: n => `评论・${n}`, + commentsHelpRunning: + '此任务正在运行。你的备注会在几秒内融入工作单元当前的回合 — 无需阻塞/解除操作。选择“附带备注重新入队”则会带着你的备注从头重跑任务。', + commentsHelp: '评论会添加到任务讨论串。工作单元领取任务时会将其作为上下文的一部分读取。', + send: '发送', + comment: '评论', + messageWorker: '给运行中的工作单元发消息…', + addComment: '添加评论…', + deliveredLive: '几秒内送达运行中的工作单元。', + requeueWithNote: '附带备注重新入队', + notePosted: '备注已发布 — 工作单元已重新入队', + activity: n => `活动・${n}`, + runs: n => `运行・${n}`, + workerLog: '工作单元日志', + workerLogTail: '工作单元日志・末尾', + attachments: n => `附件・${n}`, + noAttachments: '暂无附件。', + uploadAttachment: '上传附件', + taskActions: '任务操作', + copyTaskId: '复制任务 ID', + copyTitle: '复制标题', + copiedId: id => `已复制 ${id}`, + copiedTitle: '已复制标题', + archiveTask: '归档任务', + deleteTask: '删除任务', + close: '关闭', + working: '进行中', + board: '面板', + newBoard: '新建面板', + newBoardDots: '新建面板…', + boardSettings: '面板设置…', + boardSettingsFor: name => `面板设置 — ${name}`, + name: '名称', + boardNamePlaceholder: '面板名称', + slug: slug => `slug: ${slug}`, + project: '项目', + noProject: '无项目(临时沙箱)', + projectHintPre: '新任务将在项目的仓库中运行(每个任务一个 worktree);每个任务在创建时仍可覆盖其工作区。管理项目请使用 ', + projectHintCmd: 'hermes project', + createBoard: '创建面板', + orchestratorProfile: '编排者配置档', + defaultAssignee: '默认负责人', + defaultParen: '(默认)', + autoDecompose: '自动分解分诊任务', + profileDescriptions: '配置档说明', + profileDescriptionsHint: '说明用于引导分解器的路由。可用辅助模型自动生成,或自行填写。', + profileGoodAt: '这个配置档擅长什么?', + auto: '自动' + } + +const zhHant: KanbanMessages = { + nav: '看板', + openBoard: '看板:開啟面板', + countTip: (running, ready) => `看板 — 執行中 ${running}、就緒 ${ready}`, + col: { + triage: { label: '分類', help: '原始想法 — 由細化代理整理出規格。' }, + todo: { label: '待辦', help: '等待相依項目,或未指派。' }, + scheduled: { label: '已排程', help: '等待預定時間到來。' }, + ready: { label: '就緒', help: '相依項目已滿足 — 指派一個設定檔,排程器便會執行它。' }, + running: { label: '執行中', help: '已被工作單元領取 — 有代理在處理。由排程器設定。' }, + blocked: { label: '受阻', help: '工作單元需要人工輸入。' }, + review: { label: '審查', help: '審查代理正在檢查工作。由排程器設定。' }, + done: { label: '完成', help: '已完成;相依它的子任務變為就緒。' }, + archived: { label: '已封存', help: '從預設面板檢視中隱藏。' } + }, + locked: { + review: '審查狀態由排程器在審查代理領取卡片時設定。', + running: '執行中由排程器在工作單元領取卡片時設定。', + scheduled: '排程需要喚醒時間 — 由代理設定;無法拖入。' + }, + arcRunning: '有代理正在處理它。', + arcStale: '已領取,但超過 2 分鐘沒有工作單元心跳 — 排程器將重新領取。', + title: '看板', + orchestrationSettings: '編排設定', + newTask: '新增任務', + filterCards: '篩選卡片…', + noMatch: '沒有符合篩選條件的任務', + noTasks: '此面板尚無任務', + open: '開啟', + select: '選取(⌘點擊)', + deselect: '取消選取', + moveTo: label => `移至 ${label}`, + delete: '刪除', + reviewChecking: '審查代理正在檢查已完成的工作。', + attachedTip: name => `${name} 已接手 — 排程器將在下一個週期(≤1 分鐘)移交。`, + orchestratorTip: name => `${name}(編排者)將在下一個週期領取並撰寫規格。`, + autoAssignTip: name => `將在下一個排程週期自動指派給「${name}」(kanban.default_assignee)。`, + wontRun: '不會執行', + wontRunTip: '就緒卡片只有在指派了設定檔後才會執行。開啟卡片設定負責人,或在編排設定中設定預設負責人。', + noHeartbeat: '無心跳', + expand: label => `展開 ${label}`, + collapse: label => `摺疊 ${label}`, + newTaskIn: label => `在 ${label} 新增任務`, + empty: '空', + unassigned: '未指派', + filters: '篩選', + allProfiles: '所有設定檔', + allTenants: '所有租戶', + showArchived: '顯示已封存', + groupRunning: '依設定檔分組執行中', + nSelected: n => `已選取 ${n} 個`, + moveToShort: '移至', + assign: '指派', + unassignAction: '取消指派', + archive: '封存', + clearSelection: '清除選取(Esc)', + refused: '被拒絕', + bulkFailed: (failed, total, err) => `${total} 個中有 ${failed} 個失敗 — ${err}。失敗的卡片仍保持選取。`, + titlePlaceholderTriage: '大致想法 — 細化代理會補全', + titlePlaceholder: '標題', + descPlaceholder: '描述(選填)', + priority: '優先順序', + workspace: '工作區', + boardDefaultSuffix: '・面板預設', + workspaceOverride: '工作區路徑(選填覆寫)', + workspaceInherit: '繼承面板的專案目錄', + workspaceInheritDir: dir => `留空則繼承 ${dir}`, + workspaceInheritGeneric: '留空則繼承面板的專案目錄。', + assignee: '負責人', + defaultOption: name => `${name}(預設)`, + parkedOption: '未指派(擱置 — 不會執行)', + skills: '技能(以逗號分隔)', + skillsPlaceholder: 'translation, github', + parent: '父任務(完成前會阻擋)', + noParent: '— 無父任務 —', + goalMode: '目標模式(工作單元循環直到評判代理認可完成)', + creating: '建立中…', + createTask: '建立任務', + cancel: '取消', + save: '儲存', + estimate: '估算', + estimateEffort: '估算工作量', + estimating: '估算中…', + reEstimate: '重新估算', + makesModelCall: '會呼叫模型', + estimateTip: '由輔助模型對 token 數與複雜度的粗略估算 — 會呼叫模型。', + estimateTipLong: '快速呼叫輔助模型來估算 token 數與複雜度。僅供參考,並非帳單。', + roughEstimate: '粗略估算', + tokUnit: 'tok', + couldNotEstimate: '無法估算', + complexity: { S: '小', M: '中', L: '大' }, + introBody: + '卡片不由你執行,而是由代理執行。把有負責人的卡片放入「就緒」,代理會在一分鐘內領取。沒有負責人就不會執行。分類:代理先把想法改寫成合適的任務。待辦:等待其他卡片。已排程:等待計時器。執行中與審查:這是代理的通道,請勿插手。受阻:正在等你。結果會回到卡片上。', + introGotIt: '知道了', + evtCreated: (where, assignee) => + `已建立${where ? `(${where})` : ''}${assignee ? `・指派給 ${assignee}` : ''}`, + evtMovedTo: col => `移至 ${col}`, + evtParentReopened: parent => `父任務 ${parent} 已重新開啟`, + evtAssignedTo: assignee => `指派給 ${assignee}`, + evtUnassigned: '取消指派', + evtCommentBy: author => `${author} 的留言`, + evtClaimedReview: '被審查代理領取', + evtClaimedWorker: '被工作單元領取', + evtWorkerStarted: '工作單元已啟動', + evtCompleted: '已完成', + evtBlocked: '受阻 — 需要人工輸入', + evtUnblocked: col => `已解除阻擋${col ? ` → ${col}` : ' → 就緒'}`, + evtReclaimed: '已重新領取 — 已放回佇列', + evtSpecified: '分類代理已撰寫規格', + evtPromoted: '相依項目已完成 — 提升為就緒', + evtScheduled: '已排程稍後執行', + evtArchived: '已封存', + evtReprioritized: priority => `優先順序設為 ${priority}`, + someone: '某人', + metaPriority: '優先順序', + metaTenant: '租戶', + metaCreatedBy: '建立者', + metaCreated: '建立於', + metaWorkerPid: '工作單元 PID', + readyUnassignedTitle: '就緒但未指派 — 這張卡片永遠不會執行。', + readyUnassignedBody: + '排程器只領取有負責人的就緒卡片。在上方的負責人欄位選擇一個設定檔(或在編排設定中設定預設負責人),它會在一分鐘內執行。', + diagnosticsN: n => `診斷・${n}`, + commandCopied: '指令已複製', + description: '描述', + editDescription: '編輯描述', + cancelEdit: '取消編輯', + noDescription: '尚無描述。', + result: '結果', + latestSummary: '最新摘要', + dependencies: '相依關係', + blockedBy: '受阻於', + blocks: '阻擋', + comments: n => `留言・${n}`, + commentsHelpRunning: + '此任務正在執行。你的備註會在幾秒內融入工作單元目前的回合 — 無需阻擋/解除操作。選擇「附上備註重新排入佇列」則會帶著你的備註從頭重跑任務。', + commentsHelp: '留言會加入任務討論串。工作單元領取任務時會將其作為脈絡的一部分讀取。', + send: '傳送', + comment: '留言', + messageWorker: '傳訊給執行中的工作單元…', + addComment: '新增留言…', + deliveredLive: '幾秒內送達執行中的工作單元。', + requeueWithNote: '附上備註重新排入佇列', + notePosted: '備註已發布 — 工作單元已重新排入佇列', + activity: n => `活動・${n}`, + runs: n => `執行・${n}`, + workerLog: '工作單元日誌', + workerLogTail: '工作單元日誌・末尾', + attachments: n => `附件・${n}`, + noAttachments: '尚無附件。', + uploadAttachment: '上傳附件', + taskActions: '任務操作', + copyTaskId: '複製任務 ID', + copyTitle: '複製標題', + copiedId: id => `已複製 ${id}`, + copiedTitle: '已複製標題', + archiveTask: '封存任務', + deleteTask: '刪除任務', + close: '關閉', + working: '進行中', + board: '面板', + newBoard: '新增面板', + newBoardDots: '新增面板…', + boardSettings: '面板設定…', + boardSettingsFor: name => `面板設定 — ${name}`, + name: '名稱', + boardNamePlaceholder: '面板名稱', + slug: slug => `slug: ${slug}`, + project: '專案', + noProject: '無專案(暫存沙箱)', + projectHintPre: '新任務將在專案的儲存庫中執行(每個任務一個 worktree);每個任務在建立時仍可覆寫其工作區。管理專案請使用 ', + projectHintCmd: 'hermes project', + createBoard: '建立面板', + orchestratorProfile: '編排者設定檔', + defaultAssignee: '預設負責人', + defaultParen: '(預設)', + autoDecompose: '自動分解分類任務', + profileDescriptions: '設定檔說明', + profileDescriptionsHint: '說明用於引導分解器的路由。可用輔助模型自動產生,或自行填寫。', + profileGoodAt: '這個設定檔擅長什麼?', + auto: '自動' + } + +/** Registered via `ctx.i18n.register` at plugin load (disposer tracked). */ +export const KANBAN_LOCALES: PluginLocaleBundles = { en, ja, zh, 'zh-hant': zhHant } + +// Bind the message SHAPE to a plugin translator: string leaves resolve now, +// function leaves forward their args through t(path, …). One tiny generic +// instead of a hand-written accessor per key. +type Bound = { + [K in keyof T]: T[K] extends (...args: infer A) => string + ? (...args: A) => string + : T[K] extends object + ? Bound + : string +} + +function bind(t: PluginTranslate, template: T, prefix = ''): Bound { + const out = {} as Record + + for (const [key, value] of Object.entries(template)) { + const path = prefix ? `${prefix}.${key}` : key + out[key] = + typeof value === 'function' + ? (...args: unknown[]) => t(path, ...args) + : value && typeof value === 'object' + ? bind(t, value as object, path) + : t(path) + } + + return out as Bound +} + +export type KanbanText = Bound + +/** The kanban strings for the active locale — one hook every component reads. */ +export function useKanban(): KanbanText { + const t = usePluginI18n('kanban') + + return useMemo(() => bind(t, en), [t]) +} + +// Column labels/help live in i18n; unknown backend statuses fall back to the id. +export const columnLabel = (k: KanbanText, name: string) => k.col[name as keyof KanbanText['col']]?.label ?? name +export const columnHelp = (k: KanbanText, name: string) => k.col[name as keyof KanbanText['col']]?.help ?? '' +export const lockedReason = (k: KanbanText, name: string) => k.locked[name as keyof KanbanText['locked']] ?? '' diff --git a/apps/desktop/src/plugins/kanban/plugin.tsx b/apps/desktop/src/plugins/kanban/plugin.tsx index 227d409655e..307adf0b432 100644 --- a/apps/desktop/src/plugins/kanban/plugin.tsx +++ b/apps/desktop/src/plugins/kanban/plugin.tsx @@ -30,6 +30,7 @@ import { import { $boardSlug, bindApi, boardKey, fetchBoard } from './api' import { KanbanBoardPage } from './board' +import { KANBAN_LOCALES } from './i18n' import { useKanban } from './ui' // Live "N running / ready" pill — one glance at fleet activity from anywhere, @@ -79,6 +80,7 @@ const plugin: HermesPlugin = { name: 'Kanban', defaultEnabled: false, register(ctx) { + ctx.i18n.register(KANBAN_LOCALES) ctx.onDispose(bindApi(ctx.rest, ctx.storage, ctx.socket)) ctx.registerMany([ diff --git a/apps/desktop/src/plugins/kanban/types.ts b/apps/desktop/src/plugins/kanban/types.ts index 430327ed26f..ca593dde4d5 100644 --- a/apps/desktop/src/plugins/kanban/types.ts +++ b/apps/desktop/src/plugins/kanban/types.ts @@ -184,7 +184,7 @@ export interface KanbanProfile { } /** Column presentation — codicon + tone only. Labels + help live in i18n - * (`t.kanban.col`); see `columnLabel`/`columnHelp` in ui.tsx. Order follows + * (plugin bundles); see `columnLabel`/`columnHelp` in i18n.ts. Order follows * the backend's BOARD_COLUMNS; anything the backend adds renders via the * fallback. */ export const COLUMN_META: Record = { diff --git a/apps/desktop/src/plugins/kanban/ui.tsx b/apps/desktop/src/plugins/kanban/ui.tsx index d4bac87ecb9..2b612172631 100644 --- a/apps/desktop/src/plugins/kanban/ui.tsx +++ b/apps/desktop/src/plugins/kanban/ui.tsx @@ -11,25 +11,17 @@ import { profileColor, profileColorSoft, relativeTime, - useI18n, useQuery } from '@hermes/plugin-sdk' import { type ReactNode, useEffect, useLayoutEffect, useRef, useState } from 'react' import { fetchOrchestration, ORCHESTRATION_KEY } from './api' +import { columnLabel, useKanban } from './i18n' import { columnMeta, type KanbanTask } from './types' -/** The kanban i18n slice — one hook every plugin component reads `t.kanban` from. */ -export function useKanban() { - return useI18n().t.kanban -} - -export type KanbanText = ReturnType - -// Column labels + help live in i18n (visuals stay in COLUMN_META); unknown -// backend statuses fall back to the raw id / no help. -export const columnLabel = (k: KanbanText, name: string) => k.col[name as keyof KanbanText['col']]?.label ?? name -export const columnHelp = (k: KanbanText, name: string) => k.col[name as keyof KanbanText['col']]?.help ?? '' +// Plugin-scoped i18n lives in ./i18n; re-exported so components import strings +// and chrome from one place (./ui). +export { columnHelp, columnLabel, type KanbanText, lockedReason, useKanban } from './i18n' /** Orchestration knobs (cached app-wide; the settings panel invalidates). */ export function useOrchestration() { @@ -46,14 +38,11 @@ export function useDefaultAssignee(): string { // them, so lanes/menus must not offer them as targets. `running`/`review` are // claimed by the dispatcher; `scheduled` needs a wake-up time only an agent or // the CLI can attach (a bare status drag is refused with a 409). The reason -// copy lives in i18n (`t.kanban.locked`). +// copy lives in the plugin i18n bundle (`locked.*`); see `lockedReason`. export const LOCKED_COLUMNS = ['review', 'running', 'scheduled'] as const export const isLockedTarget = (name: string): boolean => (LOCKED_COLUMNS as readonly string[]).includes(name) -/** Reason a locked column can't be dropped into (for the info toast). */ -export const lockedReason = (k: KanbanText, name: string) => k.locked[name as keyof KanbanText['locked']] ?? '' - export const shortId = (id?: null | string) => (id ?? '').replace(/^t_/, '').slice(0, 6) // The electron REST bridge throws `Error("409: {\"detail\":\"…\"}")`; pull out