fix(i18n): localize the status bar Resource Manager tooltip and remote-host count (#12478)

* fix(i18n): localize the status bar Resource Manager tooltip and remote-host count

The Resource Manager tooltip/aria label and the SSH segment's host count were
assembled from bare English literals inside helper functions, so they stayed
English under every non-English UI language while the labels around them
translated. Route them through the catalog with _one/_other plural keys and
whole-line messages (locales reorder and repunctuate the summary), and add
en/es/ja/ko/zh entries.

Root cause of the miss: audit-localization-coverage bailed on any ancestor
binary expression whose operator was not `+`, which hid every string under a
`cond && <JSX/>` guard or a `?? 'fallback'` — including this segment's
'Connecting…'. Only comparison operands are code, so keep `??`, `||` and
`&&` walking, and localize the four real strings that surfaced.

Co-authored-by: Orca <help@stably.ai>

* fix(status-bar): flag the space-scan tooltip row instead of matching its English text

The tooltip tinted a row with `line === 'Space scan ready'`, so routing that
copy through the catalog silently dropped the tint in every translated build.
Return `{ text, emphasized }` and let the segment read the flag.

Adopted from #12439 by @smwbev.

Co-authored-by: Evgenii <smwbev@users.noreply.github.com>

Co-authored-by: Orca <help@stably.ai>

* fix(status-bar): key Resource Manager tooltip rows by role instead of array index

Co-authored-by: Orca <help@stably.ai>

---------

Co-authored-by: Orca <help@stably.ai>
This commit is contained in:
Neil
2026-08-06 19:14:15 -07:00
committed by GitHub
co-authored by Evgenii Orca
parent c50a75142d
commit f2d62a7887
19 changed files with 623 additions and 61 deletions
@@ -6,6 +6,13 @@
"dynamic": false,
"count": 1
},
{
"filePath": "src/renderer/src/components/sidebar/WorktreeCard.tsx",
"kind": "jsx-attribute:label",
"text": "sidebar",
"dynamic": false,
"count": 1
},
{
"filePath": "src/renderer/src/components/settings/appearance-search.ts",
"kind": "object-property:keywords",
+11 -2
View File
@@ -60,6 +60,14 @@ const USER_VISIBLE_OBJECT_METHODS = new Set([
'warning'
])
const USER_VISIBLE_OBJECT_NAMES = new Set(['toast'])
// Why: only comparison operands are code, not copy. Bailing on every non-`+`
// operator hid whole subtrees behind `cond && <JSX/>` guards and `?? 'fallback'`.
const COPY_PRESERVING_BINARY_OPERATORS = new Set([
ts.SyntaxKind.PlusToken,
ts.SyntaxKind.QuestionQuestionToken,
ts.SyntaxKind.BarBarToken,
ts.SyntaxKind.AmpersandAmpersandToken
])
function normalizePath(root, filePath) {
return path.relative(root, filePath).split(path.sep).join('/')
@@ -226,7 +234,7 @@ function isRenderedJsxExpression(node) {
continue
}
if (ts.isBinaryExpression(current)) {
if (current.operatorToken.kind !== ts.SyntaxKind.PlusToken) {
if (!COPY_PRESERVING_BINARY_OPERATORS.has(current.operatorToken.kind)) {
return false
}
current = current.parent
@@ -318,7 +326,8 @@ function classifyStringNode(node) {
findAncestor(
node,
(ancestor) =>
ts.isBinaryExpression(ancestor) && ancestor.operatorToken.kind !== ts.SyntaxKind.PlusToken
ts.isBinaryExpression(ancestor) &&
!COPY_PRESERVING_BINARY_OPERATORS.has(ancestor.operatorToken.kind)
)
) {
return undefined
@@ -0,0 +1,44 @@
import { describe, expect, it } from 'vitest'
import { collectLocalizationCandidates } from './audit-localization-coverage.mjs'
const ROOT = process.cwd()
function candidates(fileName, source) {
return collectLocalizationCandidates(`${ROOT}/src/renderer/src/${fileName}`, source, ROOT)
}
describe('localization coverage candidates', () => {
it('sees copy guarded by a nullish or logical fallback', () => {
const reports = candidates(
'Sample.tsx',
`export function Sample({ label, connecting }) {
return <span>{label ?? (connecting ? 'Connecting…' : 'Idle')}</span>
}`
)
expect(reports.map((report) => report.text)).toEqual(['Connecting…', 'Idle'])
})
it('sees copy nested inside a conditional JSX guard', () => {
const reports = candidates(
'Sample.tsx',
`export function Sample({ show }) {
return <div>{show && <button aria-label="Retry the sync" />}</div>
}`
)
expect(reports.map((report) => report.text)).toEqual(['Retry the sync'])
})
it('ignores literals used as comparison operands', () => {
const reports = candidates(
'Sample.tsx',
`export function Sample({ phase }) {
return <span>{phase === 'workspace conflict' ? phase : null}</span>
}`
)
expect(reports).toEqual([])
})
})
@@ -280,7 +280,14 @@ export async function ensureWslCliAvailableForAgentSkillTerminal(
'auto.components.settings.CliSkillRuntimeSetup.windowsPathUnknown',
'WSL shell command PATH could not be checked'
),
{ description: status.detail ?? 'Refresh CLI registration status and try again.' }
{
description:
status.detail ??
translate(
'auto.components.settings.CliSkillRuntimeSetup.refreshCliRegistration',
'Refresh CLI registration status and try again.'
)
}
)
return status
}
@@ -1184,12 +1184,9 @@ export function ResourceUsageStatusSegment({
</TooltipTrigger>
<TooltipContent side="top" sideOffset={6}>
<div className="space-y-0.5">
{resourceManagerTooltipLines.map((line, index) => (
<div
key={`${index}:${line}`}
className={line === 'Space scan ready' ? 'text-primary' : ''}
>
{line}
{resourceManagerTooltipLines.map((line) => (
<div key={line.id} className={line.emphasized ? 'text-primary' : ''}>
{line.text}
</div>
))}
</div>
@@ -18,6 +18,11 @@ import {
} from '../../../../shared/execution-host'
import { isUserManagedRuntimeEnvironment } from '../../../../shared/runtime-environments'
import { RuntimeHostStatusRow, type RuntimeHostConnectionState } from './RuntimeHostStatusRow'
import {
connectedHostCountLabel,
connectingHostsLabel,
workspaceSyncProblemLabel
} from './ssh-status-segment-copy'
import { SshTargetStatusRow } from './SshTargetStatusRow'
import type { RemoteRuntimeSharedConnectionDiagnostics } from '../../../../shared/remote-runtime-shared-control-types'
import { connectRuntimeEnvironmentAndRecordStatus } from './runtime-environment-explicit-connect'
@@ -59,10 +64,6 @@ function overallDotColor(
}
}
function connectedHostCountLabel(count: number): string {
return `${count} ${count === 1 ? 'host' : 'hosts'}`
}
function sshStatusForOverall(status: SshConnectionStatus): HostStatus {
if (status === 'connected') {
return 'connected'
@@ -286,9 +287,7 @@ export function SshStatusSegment({
(t) => t.syncStatus?.phase === 'conflict' || t.syncStatus?.phase === 'error'
)
const syncProblemLabel = syncProblem
? syncProblem.syncStatus?.phase === 'conflict'
? 'Workspace conflict'
: 'Workspace sync error'
? workspaceSyncProblemLabel(syncProblem.syncStatus?.phase)
: null
return (
<DropdownMenu
@@ -340,7 +339,9 @@ export function SshStatusSegment({
<span className="text-[11px]">
<span className={syncProblem ? 'text-destructive' : 'text-muted-foreground'}>
{syncProblemLabel ??
(anyConnecting ? 'Connecting…' : connectedHostCountLabel(connectedHostCount))}
(anyConnecting
? connectingHostsLabel()
: connectedHostCountLabel(connectedHostCount))}
</span>
</span>
)}
@@ -2369,7 +2369,11 @@ function StatusBarInner({ floatingTerminalOpen }: StatusBarProps): React.JSX.Ele
className="relative inline-flex size-5 cursor-pointer items-center justify-center rounded border border-border bg-secondary text-secondary-foreground shadow-xs transition-colors hover:bg-accent hover:text-accent-foreground"
aria-label={
showFloatingWorkspaceAttentionDot
? `${floatingTerminalActionLabel}, new activity`
? translate(
'auto.components.status.bar.StatusBar.floatingTerminalNewActivity',
'{{label}}, new activity',
{ label: floatingTerminalActionLabel }
)
: floatingTerminalActionLabel
}
onClick={() => {
@@ -1,4 +1,12 @@
import { describe, expect, it } from 'vitest'
import { describe, expect, it, vi } from 'vitest'
// Mirrors resource-memory-metric-copy.test.ts: assert the English source copy
// through the catalog fallback, with placeholders filled the way i18next would.
vi.mock('@/i18n/i18n', () => ({
translate: (_key: string, fallback: string, options?: Record<string, unknown>) =>
fallback.replace(/\{\{(\w+)\}\}/g, (match, name: string) => String(options?.[name] ?? match))
}))
import {
formatTerminalSessionCount,
getResourceManagerAriaLabel,
@@ -19,8 +27,12 @@ describe('resource manager terminal copy', () => {
spaceScanReady: false
})
).toEqual([
'Resource Manager - 512 MB · Σ RSS - 2 terminal sessions',
'Terminal sessions are grouped by workspace.'
{
id: 'summary',
text: 'Resource Manager - 512 MB · Σ RSS - 2 terminal sessions',
emphasized: false
},
{ id: 'sessions-hint', text: 'Terminal sessions are grouped by workspace.', emphasized: false }
])
})
@@ -32,12 +44,45 @@ describe('resource manager terminal copy', () => {
spaceScanReady: true
})
).toEqual([
'Resource Manager - memory unavailable - 0 terminal sessions',
'Space scan ready',
'No terminal sessions yet.'
{
id: 'summary',
text: 'Resource Manager - memory unavailable - 0 terminal sessions',
emphasized: false
},
{ id: 'space-scan', text: 'Space scan ready', emphasized: true },
{ id: 'sessions-hint', text: 'No terminal sessions yet.', emphasized: false }
])
})
// Why: the tooltip used to tint this row by matching its English text, so any
// translated build lost the tint. The flag is what the segment reads now.
it('flags the space-scan row instead of leaving callers to match its wording', () => {
const lines = getResourceManagerTooltipLines({
memoryLabel: '512 MB',
sessionCount: 1,
spaceScanReady: true
})
expect(lines.filter((line) => line.emphasized).map((line) => line.text)).toEqual([
'Space scan ready'
])
})
// Why: the tooltip keys rows by id, so a repeated id would silently drop a row.
it('gives every tooltip row a unique id to key on', () => {
for (const spaceScanReady of [false, true]) {
for (const sessionCount of [0, 1, 4]) {
const ids = getResourceManagerTooltipLines({
memoryLabel: '512 MB',
sessionCount,
spaceScanReady
}).map((line) => line.id)
expect(new Set(ids).size).toBe(ids.length)
}
}
})
it('keeps the trigger label descriptive for screen readers', () => {
expect(
getResourceManagerAriaLabel({
@@ -1,30 +1,86 @@
import { translate } from '@/i18n/i18n'
export function formatTerminalSessionCount(count: number): string {
return `${count} terminal session${count === 1 ? '' : 's'}`
return count === 1
? translate(
'auto.components.status.bar.resource.manager.terminal.copy.terminalSessionCount_one',
'{{count}} terminal session',
{ count }
)
: translate(
'auto.components.status.bar.resource.manager.terminal.copy.terminalSessionCount_other',
'{{count}} terminal sessions',
{ count }
)
}
function spaceScanReadyLabel(): string {
return translate(
'auto.components.status.bar.resource.manager.terminal.copy.spaceScanReady',
'Space scan ready'
)
}
/**
* `emphasized` marks the space-scan row the tooltip tints. The segment used to
* recognize that row by comparing it against the English text, which stops
* matching the moment the copy comes from the catalog.
*
* `id` names the row's role. Each role appears at most once per tooltip, so it is
* a unique React key that survives locale switches and memory/count updates —
* unlike the translated text, which locales are free to duplicate across rows.
*/
export type ResourceManagerTooltipLine = {
id: 'summary' | 'space-scan' | 'sessions-hint'
text: string
emphasized: boolean
}
export function getResourceManagerTooltipLines(args: {
memoryLabel: string
sessionCount: number
spaceScanReady: boolean
}): string[] {
}): ResourceManagerTooltipLine[] {
const rawMemoryLabel = args.memoryLabel.trim()
const memoryLabel =
rawMemoryLabel === '' || rawMemoryLabel === '-' || rawMemoryLabel === '—'
? 'memory unavailable'
? translate(
'auto.components.status.bar.resource.manager.terminal.copy.memoryUnavailable',
'memory unavailable'
)
: rawMemoryLabel
const lines = [
`Resource Manager - ${memoryLabel} - ${formatTerminalSessionCount(args.sessionCount)}`
// Why: whole lines are single keys — locales reorder the summary and repunctuate
// its separators, so it can't be concatenated from translated fragments here.
const lines: ResourceManagerTooltipLine[] = [
{
id: 'summary',
text: translate(
'auto.components.status.bar.resource.manager.terminal.copy.tooltipSummary',
'Resource Manager - {{memory}} - {{sessions}}',
{ memory: memoryLabel, sessions: formatTerminalSessionCount(args.sessionCount) }
),
emphasized: false
}
]
if (args.spaceScanReady) {
lines.push('Space scan ready')
lines.push({ id: 'space-scan', text: spaceScanReadyLabel(), emphasized: true })
}
if (args.sessionCount > 0) {
lines.push('Terminal sessions are grouped by workspace.')
} else {
lines.push('No terminal sessions yet.')
}
lines.push({
id: 'sessions-hint',
text:
args.sessionCount > 0
? translate(
'auto.components.status.bar.resource.manager.terminal.copy.sessionsGroupedByWorkspace',
'Terminal sessions are grouped by workspace.'
)
: translate(
'auto.components.status.bar.resource.manager.terminal.copy.noTerminalSessions',
'No terminal sessions yet.'
),
emphasized: false
})
return lines
}
@@ -33,11 +89,19 @@ export function getResourceManagerAriaLabel(args: {
sessionCount: number
spaceScanReady: boolean
}): string {
const parts = ['Resource Manager', formatTerminalSessionCount(args.sessionCount)]
const sessions = formatTerminalSessionCount(args.sessionCount)
if (args.spaceScanReady) {
parts.push('Space scan ready')
return translate(
'auto.components.status.bar.resource.manager.terminal.copy.ariaLabelWithSpaceScan',
'Resource Manager, {{sessions}}, {{spaceScan}}',
{ sessions, spaceScan: spaceScanReadyLabel() }
)
}
return parts.join(', ')
return translate(
'auto.components.status.bar.resource.manager.terminal.copy.ariaLabel',
'Resource Manager, {{sessions}}',
{ sessions }
)
}
@@ -0,0 +1,26 @@
import { describe, expect, it, vi } from 'vitest'
vi.mock('@/i18n/i18n', () => ({
translate: (_key: string, fallback: string, options?: Record<string, unknown>) =>
fallback.replace(/\{\{(\w+)\}\}/g, (match, name: string) => String(options?.[name] ?? match))
}))
import {
connectedHostCountLabel,
connectingHostsLabel,
workspaceSyncProblemLabel
} from './ssh-status-segment-copy'
describe('ssh status segment copy', () => {
it('keeps the host noun singular for a lone connected host', () => {
expect(connectedHostCountLabel(0)).toBe('0 hosts')
expect(connectedHostCountLabel(1)).toBe('1 host')
expect(connectedHostCountLabel(4)).toBe('4 hosts')
})
it('distinguishes a sync conflict from a sync failure', () => {
expect(connectingHostsLabel()).toBe('Connecting…')
expect(workspaceSyncProblemLabel('conflict')).toBe('Workspace conflict')
expect(workspaceSyncProblemLabel('error')).toBe('Workspace sync error')
})
})
@@ -0,0 +1,31 @@
import { translate } from '@/i18n/i18n'
export function connectedHostCountLabel(count: number): string {
return count === 1
? translate(
'auto.components.status.bar.SshStatusSegment.connectedHostCount_one',
'{{count}} host',
{ count }
)
: translate(
'auto.components.status.bar.SshStatusSegment.connectedHostCount_other',
'{{count}} hosts',
{ count }
)
}
export function connectingHostsLabel(): string {
return translate('auto.components.status.bar.SshStatusSegment.connecting', 'Connecting…')
}
export function workspaceSyncProblemLabel(phase: string | undefined): string {
return phase === 'conflict'
? translate(
'auto.components.status.bar.SshStatusSegment.workspaceConflict',
'Workspace conflict'
)
: translate(
'auto.components.status.bar.SshStatusSegment.workspaceSyncError',
'Workspace sync error'
)
}
@@ -0,0 +1,183 @@
// @vitest-environment happy-dom
/**
* The Resource Manager tooltip and the SSH segment's host count were built from
* bare English literals inside helper functions, so they stayed English while
* every label around them translated. The coverage audit cannot see values
* returned from helpers, so only a runtime assertion against the real catalog
* keeps them honest — same reasoning as
* `src/renderer/src/i18n/settings-status-label-localization.test.ts`.
*/
import { cleanup, render, screen } from '@testing-library/react'
import { afterAll, afterEach, beforeAll, describe, expect, it, vi } from 'vitest'
import type { SshConnectionStatus } from '../../../../shared/ssh-types'
import { i18n } from '@/i18n/i18n'
import {
formatTerminalSessionCount,
getResourceManagerAriaLabel,
getResourceManagerTooltipLines
} from './resource-manager-terminal-copy'
import { SshStatusSegment } from './SshStatusSegment'
type StoreState = {
sshConnectionStates: Map<string, { status: SshConnectionStatus }>
sshTargetLabels: Map<string, string>
remoteWorkspaceSyncStatusByTargetId: Record<string, { phase: string }>
}
let storeState: StoreState = {
sshConnectionStates: new Map(),
sshTargetLabels: new Map(),
remoteWorkspaceSyncStatusByTargetId: {}
}
vi.mock('../../store', () => {
const state = (): Record<string, unknown> => ({
...storeState,
settings: null,
runtimeEnvironments: [],
runtimeStatusByEnvironmentId: new Map(),
setRuntimeEnvironmentStatus: vi.fn(),
hydrateRuntimeEnvironmentStatuses: vi.fn(),
setActiveView: vi.fn(),
openSettingsTarget: vi.fn(),
recordFeatureInteraction: vi.fn(),
fetchRuntimeEnvironmentRepos: vi.fn(),
fetchWorktrees: vi.fn(),
fetchWorktreeLineage: vi.fn()
})
const useAppStore = (selector: (value: Record<string, unknown>) => unknown): unknown =>
selector(state())
useAppStore.getState = state
return { useAppStore }
})
function setSshTargets(
entries: { id: string; label: string; status: SshConnectionStatus; syncPhase?: string }[]
): void {
storeState = {
sshConnectionStates: new Map(entries.map((entry) => [entry.id, { status: entry.status }])),
sshTargetLabels: new Map(entries.map((entry) => [entry.id, entry.label])),
remoteWorkspaceSyncStatusByTargetId: Object.fromEntries(
entries.flatMap((entry) => (entry.syncPhase ? [[entry.id, { phase: entry.syncPhase }]] : []))
)
}
}
function triggerText(): string {
return screen.getByRole('button').textContent ?? ''
}
describe('status-bar copy under a non-English UI language', () => {
beforeAll(async () => {
await i18n.changeLanguage('ja')
})
afterEach(() => {
cleanup()
})
afterAll(async () => {
await i18n.changeLanguage('en')
})
it('translates both plural forms of the terminal session count', () => {
expect(formatTerminalSessionCount(1)).toBe('1 件のターミナルセッション')
expect(formatTerminalSessionCount(5)).toBe('5 件のターミナルセッション')
})
it('translates every Resource Manager tooltip line', () => {
expect(
getResourceManagerTooltipLines({
memoryLabel: '512 MB',
sessionCount: 2,
spaceScanReady: true
})
).toEqual([
{
id: 'summary',
text: 'リソースマネージャー - 512 MB - 2 件のターミナルセッション',
emphasized: false
},
{ id: 'space-scan', text: '容量スキャンの準備完了', emphasized: true },
{
id: 'sessions-hint',
text: 'ターミナルセッションはワークスペースごとにグループ化されます。',
emphasized: false
}
])
})
// Why: the tint used to be selected by `line === 'Space scan ready'`, so it
// silently vanished for every non-English locale once the copy translated.
it('keeps the space-scan row flagged when its copy is no longer English', () => {
const lines = getResourceManagerTooltipLines({
memoryLabel: '512 MB',
sessionCount: 2,
spaceScanReady: true
})
const emphasized = lines.filter((line) => line.emphasized)
expect(emphasized).toHaveLength(1)
expect(emphasized[0]?.text).toBe('容量スキャンの準備完了')
expect(emphasized[0]?.text).not.toBe('Space scan ready')
})
it('translates the memory-unavailable and empty-session tooltip lines', () => {
expect(
getResourceManagerTooltipLines({ memoryLabel: '—', sessionCount: 0, spaceScanReady: false })
).toEqual([
{
id: 'summary',
text: 'リソースマネージャー - メモリ情報を取得できません - 0 件のターミナルセッション',
emphasized: false
},
{ id: 'sessions-hint', text: 'ターミナルセッションはまだありません。', emphasized: false }
])
})
it('translates the Resource Manager trigger label read by screen readers', () => {
expect(getResourceManagerAriaLabel({ sessionCount: 1, spaceScanReady: true })).toBe(
'リソースマネージャー、1 件のターミナルセッション、容量スキャンの準備完了'
)
expect(getResourceManagerAriaLabel({ sessionCount: 3, spaceScanReady: false })).toBe(
'リソースマネージャー、3 件のターミナルセッション'
)
})
it('translates the connected host count next to the translated aria label', () => {
setSshTargets([
{ id: 'ssh-1', label: 'builder', status: 'connected' },
{ id: 'ssh-2', label: 'openclaw', status: 'connected' }
])
render(<SshStatusSegment compact={false} iconOnly={false} />)
expect(screen.getByRole('button').getAttribute('aria-label')).toBe('リモートホスト接続状態')
expect(triggerText()).toContain('2 台のホスト')
})
it('translates the singular host count', () => {
setSshTargets([{ id: 'ssh-1', label: 'builder', status: 'connected' }])
render(<SshStatusSegment compact={false} iconOnly={false} />)
expect(triggerText()).toContain('1 台のホスト')
})
it('translates the connecting and workspace-sync states', () => {
setSshTargets([{ id: 'ssh-1', label: 'builder', status: 'connecting' }])
render(<SshStatusSegment compact={false} iconOnly={false} />)
expect(triggerText()).toContain('接続中…')
cleanup()
setSshTargets([
{ id: 'ssh-1', label: 'builder', status: 'connected', syncPhase: 'conflict' },
{ id: 'ssh-2', label: 'openclaw', status: 'connected', syncPhase: 'error' }
])
render(<SshStatusSegment compact={false} iconOnly={false} />)
expect(triggerText()).toContain('ワークスペースの競合')
cleanup()
setSshTargets([{ id: 'ssh-1', label: 'builder', status: 'connected', syncPhase: 'error' }])
render(<SshStatusSegment compact={false} iconOnly={false} />)
expect(triggerText()).toContain('ワークスペースの同期エラー')
})
})
+30 -4
View File
@@ -415,7 +415,8 @@
"8d6eedf97e": "Failed to register the Orca CLI in PATH.",
"0f116999f1": "Restart your shell or add the Orca CLI directory to PATH before setup.",
"15cbedc3e3": "Install the Orca CLI before running agent skill setup.",
"windowsPathUnknown": "Orca could not check your Windows user PATH"
"windowsPathUnknown": "Orca could not check your Windows user PATH",
"refreshCliRegistration": "Refresh CLI registration status and try again."
}
}
}
@@ -631,6 +632,9 @@
"0b01ff98d2": "Port",
"7d732521ec": "Comment"
}
},
"activation": {
"cannotOpenFolderWorkspace": "Cannot open folder workspace"
}
},
"folderWorkspacePathStatus": {
@@ -3262,7 +3266,12 @@
"runtime_disconnect_failed": "Disconnect failed",
"runtime_reconnecting": "Reconnecting",
"runtime_last_close_reason": "Closed: {{value0}}",
"runtime_reconnect_attempt": "Attempt {{value0}}"
"runtime_reconnect_attempt": "Attempt {{value0}}",
"connectedHostCount_one": "{{count}} host",
"connectedHostCount_other": "{{count}} hosts",
"connecting": "Connecting…",
"workspaceConflict": "Workspace conflict",
"workspaceSyncError": "Workspace sync error"
},
"StatusBar": {
"9659e38343": "Ports",
@@ -3322,7 +3331,8 @@
"antigravityUsage": "Antigravity Usage",
"antigravityUsageDetails": "Open Antigravity usage details",
"grokUsageAria": "Open Grok usage details",
"grokUsageMenu": "Grok Usage"
"grokUsageMenu": "Grok Usage",
"floatingTerminalNewActivity": "{{label}}, new activity"
},
"StatusBarUsageEmptyCta": {
"828c764a79": "Connect an account",
@@ -3548,6 +3558,21 @@
"workingSetDescription": "Summed working set (WS). Shared pages can appear in more than one process.",
"rssDescription": "Summed resident set size (RSS). Shared or aliased pages can appear in more than one process."
}
},
"manager": {
"terminal": {
"copy": {
"terminalSessionCount_one": "{{count}} terminal session",
"terminalSessionCount_other": "{{count}} terminal sessions",
"memoryUnavailable": "memory unavailable",
"tooltipSummary": "Resource Manager - {{memory}} - {{sessions}}",
"spaceScanReady": "Space scan ready",
"sessionsGroupedByWorkspace": "Terminal sessions are grouped by workspace.",
"noTerminalSessions": "No terminal sessions yet.",
"ariaLabel": "Resource Manager, {{sessions}}",
"ariaLabelWithSpaceScan": "Resource Manager, {{sessions}}, {{spaceScan}}"
}
}
}
}
}
@@ -5792,7 +5817,8 @@
"f00d6aa9b5": "WSL is not available on this machine.",
"7c776ff9d8": "wsl",
"fc0fcf72fd": "Register the WSL shell command before skill setup.",
"windowsPathUnknown": "WSL shell command PATH could not be checked"
"windowsPathUnknown": "WSL shell command PATH could not be checked",
"refreshCliRegistration": "Refresh CLI registration status and try again."
},
"CommitMessageAiPane": {
"841ed9884a": "Used by repositories that have not customized Source Control AI.",
+30 -4
View File
@@ -349,7 +349,8 @@
"8d6eedf97e": "No se pudo registrar Orca CLI en PATH.",
"0f116999f1": "Reinicia tu shell o agrega el directorio de Orca CLI a PATH antes de la configuración.",
"15cbedc3e3": "Instala la CLI de Orca antes de configurar skills para agentes.",
"windowsPathUnknown": "Orca could not check your Windows user PATH"
"windowsPathUnknown": "Orca could not check your Windows user PATH",
"refreshCliRegistration": "Actualiza el estado de registro de la CLI e inténtalo de nuevo."
}
}
}
@@ -560,6 +561,9 @@
"0b01ff98d2": "Puerto",
"7d732521ec": "Comentario"
}
},
"activation": {
"cannotOpenFolderWorkspace": "No se puede abrir el espacio de trabajo de carpeta"
}
},
"folderWorkspacePathStatus": {
@@ -3174,7 +3178,12 @@
"runtime_disconnect_failed": "No se pudo desconectar",
"runtime_reconnecting": "Reconectando",
"runtime_last_close_reason": "Cerrado: {{value0}}",
"runtime_reconnect_attempt": "Intento {{value0}}"
"runtime_reconnect_attempt": "Intento {{value0}}",
"connectedHostCount_one": "{{count}} host",
"connectedHostCount_other": "{{count}} hosts",
"connecting": "Conectando…",
"workspaceConflict": "Conflicto del espacio de trabajo",
"workspaceSyncError": "Error de sincronización del espacio de trabajo"
},
"StatusBar": {
"9659e38343": "Puertos",
@@ -3234,7 +3243,8 @@
"antigravityUsage": "Uso de Antigravity",
"antigravityUsageDetails": "Abrir detalles de uso de Antigravity",
"grokUsageAria": "Abrir detalles de uso de Grok",
"grokUsageMenu": "Uso de Grok"
"grokUsageMenu": "Uso de Grok",
"floatingTerminalNewActivity": "{{label}}, nueva actividad"
},
"StatusBarUsageEmptyCta": {
"828c764a79": "Conectar una cuenta",
@@ -3444,6 +3454,21 @@
"workingSetDescription": "Suma del conjunto de trabajo (WS). Las páginas compartidas pueden aparecer en más de un proceso.",
"rssDescription": "Suma del tamaño del conjunto residente (RSS). Las páginas compartidas o con alias pueden aparecer en más de un proceso."
}
},
"manager": {
"terminal": {
"copy": {
"terminalSessionCount_one": "{{count}} sesión de terminal",
"terminalSessionCount_other": "{{count}} sesiones de terminal",
"memoryUnavailable": "memoria no disponible",
"tooltipSummary": "Administrador de recursos - {{memory}} - {{sessions}}",
"spaceScanReady": "Escaneo de espacio listo",
"sessionsGroupedByWorkspace": "Las sesiones de terminal se agrupan por espacio de trabajo.",
"noTerminalSessions": "Aún no hay sesiones de terminal.",
"ariaLabel": "Administrador de recursos, {{sessions}}",
"ariaLabelWithSpaceScan": "Administrador de recursos, {{sessions}}, {{spaceScan}}"
}
}
}
},
"SkillUpdateStatusSegment": {
@@ -5624,7 +5649,8 @@
"f00d6aa9b5": "WSL no está disponible en esta máquina.",
"7c776ff9d8": "wsl",
"fc0fcf72fd": "Registra el comando de shell de WSL antes de configurar el skill.",
"windowsPathUnknown": "WSL shell command PATH could not be checked"
"windowsPathUnknown": "WSL shell command PATH could not be checked",
"refreshCliRegistration": "Actualiza el estado de registro de la CLI e inténtalo de nuevo."
},
"CommitMessageAiPane": {
"841ed9884a": "Usado por repositorios que no han personalizado Source Control AI.",
+30 -4
View File
@@ -349,7 +349,8 @@
"8d6eedf97e": "Orca CLI を PATH に登録できませんでした。",
"0f116999f1": "セットアップ前にシェルを再起動するか、Orca CLI ディレクトリを PATH に追加します。",
"15cbedc3e3": "agent スキル セットアップを実行する前に、Orca CLI をインストールします。",
"windowsPathUnknown": "Orca could not check your Windows user PATH"
"windowsPathUnknown": "Orca could not check your Windows user PATH",
"refreshCliRegistration": "CLI の登録状態を更新してから、もう一度お試しください。"
}
}
}
@@ -560,6 +561,9 @@
"0b01ff98d2": "ポート",
"7d732521ec": "コメント"
}
},
"activation": {
"cannotOpenFolderWorkspace": "フォルダーワークスペースを開けません"
}
},
"folderWorkspacePathStatus": {
@@ -3174,7 +3178,12 @@
"runtime_disconnect_failed": "切断に失敗しました",
"runtime_reconnecting": "再接続中",
"runtime_last_close_reason": "クローズ: {{value0}}",
"runtime_reconnect_attempt": "試行 {{value0}}"
"runtime_reconnect_attempt": "試行 {{value0}}",
"connectedHostCount_one": "{{count}} 台のホスト",
"connectedHostCount_other": "{{count}} 台のホスト",
"connecting": "接続中…",
"workspaceConflict": "ワークスペースの競合",
"workspaceSyncError": "ワークスペースの同期エラー"
},
"StatusBar": {
"9659e38343": "ポート",
@@ -3234,7 +3243,8 @@
"antigravityUsage": "Antigravity の使用状況",
"antigravityUsageDetails": "Antigravity の使用状況詳細を開く",
"grokUsageAria": "Grok の使用状況詳細を開く",
"grokUsageMenu": "Grok の使用状況"
"grokUsageMenu": "Grok の使用状況",
"floatingTerminalNewActivity": "{{label}}、新しいアクティビティ"
},
"StatusBarUsageEmptyCta": {
"828c764a79": "アカウントを接続する",
@@ -3444,6 +3454,21 @@
"workingSetDescription": "合計ワーキングセット(WS)。共有ページが複数のプロセスに表示されることがあります。",
"rssDescription": "合計レジデントセットサイズ(RSS)。共有またはエイリアスされたページが複数のプロセスに表示されることがあります。"
}
},
"manager": {
"terminal": {
"copy": {
"terminalSessionCount_one": "{{count}} 件のターミナルセッション",
"terminalSessionCount_other": "{{count}} 件のターミナルセッション",
"memoryUnavailable": "メモリ情報を取得できません",
"tooltipSummary": "リソースマネージャー - {{memory}} - {{sessions}}",
"spaceScanReady": "容量スキャンの準備完了",
"sessionsGroupedByWorkspace": "ターミナルセッションはワークスペースごとにグループ化されます。",
"noTerminalSessions": "ターミナルセッションはまだありません。",
"ariaLabel": "リソースマネージャー、{{sessions}}",
"ariaLabelWithSpaceScan": "リソースマネージャー、{{sessions}}、{{spaceScan}}"
}
}
}
},
"SkillUpdateStatusSegment": {
@@ -5609,7 +5634,8 @@
"f00d6aa9b5": "このマシンでは WSL を利用できません。",
"7c776ff9d8": "wsl",
"fc0fcf72fd": "スキルセットアップ前に WSL シェルコマンドを登録してください。",
"windowsPathUnknown": "WSL shell command PATH could not be checked"
"windowsPathUnknown": "WSL shell command PATH could not be checked",
"refreshCliRegistration": "CLI の登録状態を更新してから、もう一度お試しください。"
},
"CommitMessageAiPane": {
"841ed9884a": "ソース管理 AI をカスタマイズしていない repos によって使用されます。",
+31 -5
View File
@@ -349,7 +349,8 @@
"8d6eedf97e": "PATH에 Orca CLI를 등록하지 못했습니다.",
"0f116999f1": "설정하기 전에 셸을 다시 시작하거나 Orca CLI 디렉터리를 PATH에 추가하세요.",
"15cbedc3e3": "agent 스킬 설정을 실행하기 전에 Orca CLI를 설치하십시오.",
"windowsPathUnknown": "Orca could not check your Windows user PATH"
"windowsPathUnknown": "Orca could not check your Windows user PATH",
"refreshCliRegistration": "CLI 등록 상태를 새로 고친 후 다시 시도하세요."
}
}
}
@@ -560,6 +561,9 @@
"0b01ff98d2": "포트",
"7d732521ec": "댓글"
}
},
"activation": {
"cannotOpenFolderWorkspace": "폴더 워크스페이스를 열 수 없습니다"
}
},
"folderWorkspacePathStatus": {
@@ -3174,7 +3178,12 @@
"runtime_disconnect_failed": "연결 해제 실패",
"runtime_reconnecting": "다시 연결 중",
"runtime_last_close_reason": "닫힘: {{value0}}",
"runtime_reconnect_attempt": "{{value0}}번째 시도"
"runtime_reconnect_attempt": "{{value0}}번째 시도",
"connectedHostCount_one": "호스트 {{count}}개",
"connectedHostCount_other": "호스트 {{count}}개",
"connecting": "연결 중…",
"workspaceConflict": "워크스페이스 충돌",
"workspaceSyncError": "워크스페이스 동기화 오류"
},
"StatusBar": {
"9659e38343": "포트",
@@ -3234,7 +3243,8 @@
"antigravityUsage": "Antigravity 사용량",
"antigravityUsageDetails": "Antigravity 사용량 세부 정보 열기",
"grokUsageAria": "Grok 사용량 세부 정보 열기",
"grokUsageMenu": "Grok 사용량"
"grokUsageMenu": "Grok 사용량",
"floatingTerminalNewActivity": "{{label}}, 새 활동"
},
"StatusBarUsageEmptyCta": {
"828c764a79": "계정 연결",
@@ -3444,6 +3454,21 @@
"workingSetDescription": "합산된 워킹 세트(WS). 공유 페이지가 둘 이상의 프로세스에 나타날 수 있습니다.",
"rssDescription": "합산된 레지던트 세트 크기(RSS). 공유 또는 별칭된 페이지가 둘 이상의 프로세스에 나타날 수 있습니다."
}
},
"manager": {
"terminal": {
"copy": {
"terminalSessionCount_one": "터미널 세션 {{count}}개",
"terminalSessionCount_other": "터미널 세션 {{count}}개",
"memoryUnavailable": "메모리 정보 없음",
"tooltipSummary": "리소스 관리자 - {{memory}} - {{sessions}}",
"spaceScanReady": "공간 스캔 준비됨",
"sessionsGroupedByWorkspace": "터미널 세션은 워크스페이스별로 그룹화됩니다.",
"noTerminalSessions": "아직 터미널 세션이 없습니다.",
"ariaLabel": "리소스 관리자, {{sessions}}",
"ariaLabelWithSpaceScan": "리소스 관리자, {{sessions}}, {{spaceScan}}"
}
}
}
},
"SkillUpdateStatusSegment": {
@@ -5608,8 +5633,9 @@
"0c9f3cf9da": "Orca가 글로벌 agent 스킬을 확인하고 설치하는 위치를 선택합니다.",
"f00d6aa9b5": "이 컴퓨터에서는 WSL을 사용할 수 없습니다.",
"7c776ff9d8": "wsl",
"fc0fcf72fd": "스킬 설정 전 WSL 명령어를 등록하세요.",
"windowsPathUnknown": "WSL shell command PATH could not be checked"
"fc0fcf72fd": "스킬 설정 전 WSL 명령어를 등록하세요.",
"windowsPathUnknown": "WSL shell command PATH could not be checked",
"refreshCliRegistration": "CLI 등록 상태를 새로 고친 후 다시 시도하세요."
},
"CommitMessageAiPane": {
"841ed9884a": "소스 제어 AI를 사용자 정의하지 않은 리포지토리에서 사용됩니다.",
+30 -4
View File
@@ -349,7 +349,8 @@
"8d6eedf97e": "无法在 PATH 中注册 Orca CLI。",
"0f116999f1": "在安装之前重新启动 shell 或将 Orca CLI 目录添加到 PATH。",
"15cbedc3e3": "在运行智能体技能设置之前安装 Orca CLI。",
"windowsPathUnknown": "Orca 无法检查您的 Windows 用户 PATH"
"windowsPathUnknown": "Orca 无法检查您的 Windows 用户 PATH",
"refreshCliRegistration": "请刷新 CLI 注册状态后重试。"
}
}
}
@@ -560,6 +561,9 @@
"0b01ff98d2": "端口",
"7d732521ec": "评论"
}
},
"activation": {
"cannotOpenFolderWorkspace": "无法打开文件夹工作区"
}
},
"folderWorkspacePathStatus": {
@@ -3186,7 +3190,12 @@
"runtime_disconnect_failed": "断开连接失败",
"runtime_reconnecting": "重新连接中",
"runtime_last_close_reason": "已关闭:{{value0}}",
"runtime_reconnect_attempt": "第 {{value0}} 次尝试"
"runtime_reconnect_attempt": "第 {{value0}} 次尝试",
"connectedHostCount_one": "{{count}} 个主机",
"connectedHostCount_other": "{{count}} 个主机",
"connecting": "正在连接…",
"workspaceConflict": "工作区冲突",
"workspaceSyncError": "工作区同步错误"
},
"StatusBar": {
"9659e38343": "端口",
@@ -3246,7 +3255,8 @@
"antigravityUsage": "Antigravity 使用量",
"antigravityUsageDetails": "打开 Antigravity 使用详情",
"grokUsageAria": "打开 Grok 使用详情",
"grokUsageMenu": "Grok 使用量"
"grokUsageMenu": "Grok 使用量",
"floatingTerminalNewActivity": "{{label}},有新活动"
},
"StatusBarUsageEmptyCta": {
"828c764a79": "连接账户",
@@ -3456,6 +3466,21 @@
"workingSetDescription": "工作集 (WS) 的总和。共享页可能会出现在多个进程中。",
"rssDescription": "驻留集大小 (RSS) 的总和。共享页或别名映射页可能会出现在多个进程中。"
}
},
"manager": {
"terminal": {
"copy": {
"terminalSessionCount_one": "{{count}} 个终端会话",
"terminalSessionCount_other": "{{count}} 个终端会话",
"memoryUnavailable": "内存信息不可用",
"tooltipSummary": "资源管理器 - {{memory}} - {{sessions}}",
"spaceScanReady": "空间扫描已就绪",
"sessionsGroupedByWorkspace": "终端会话按工作区分组。",
"noTerminalSessions": "暂无终端会话。",
"ariaLabel": "资源管理器,{{sessions}}",
"ariaLabelWithSpaceScan": "资源管理器,{{sessions}}{{spaceScan}}"
}
}
}
},
"SkillUpdateStatusSegment": {
@@ -5621,7 +5646,8 @@
"f00d6aa9b5": "WSL 在此计算机上不可用。",
"7c776ff9d8": "wsl",
"fc0fcf72fd": "在技​​能设置之前注册 WSL shell 命令。",
"windowsPathUnknown": "无法检查 WSL shell 命令 PATH"
"windowsPathUnknown": "无法检查 WSL shell 命令 PATH",
"refreshCliRegistration": "请刷新 CLI 注册状态后重试。"
},
"CommitMessageAiPane": {
"841ed9884a": "由未自定义源代码控制 AI 的存储库使用。",
@@ -117,7 +117,14 @@ function showCliPrerequisiteWarning(status: CliInstallStatus): void {
'auto.lib.agent.skill.cli.prerequisite.windowsPathUnknown',
'Orca could not check your Windows user PATH'
),
{ description: status.detail ?? 'Refresh CLI registration status and try again.' }
{
description:
status.detail ??
translate(
'auto.lib.agent.skill.cli.prerequisite.refreshCliRegistration',
'Refresh CLI registration status and try again.'
)
}
)
return
}
+8 -1
View File
@@ -19,6 +19,7 @@ import { buildSetupRunnerCommand } from './setup-runner'
import { createSequencedSetupAgentCommands } from '../../../shared/setup-agent-sequencing'
import { getSetupRunnerCommandPlatformForPath } from '../../../shared/setup-runner-command'
import { agentKindToTuiAgent } from '../../../shared/agent-kind'
import { translate } from '@/i18n/i18n'
import { useAppStore } from '@/store'
import type { PendingSidebarWorktreeReveal } from '@/store/slices/ui'
import { tabHasLivePty } from '@/lib/tab-has-live-pty'
@@ -231,7 +232,13 @@ export function activateAndRevealFolderWorkspace(
{ runtimeEnvironmentId }
)
if (folderWorkspaceActivationBlocked(pathStatus)) {
toast.error(getFolderWorkspacePathStatusTitle(pathStatus) ?? 'Cannot open folder workspace', {
const title =
getFolderWorkspacePathStatusTitle(pathStatus) ??
translate(
'auto.lib.worktree.activation.cannotOpenFolderWorkspace',
'Cannot open folder workspace'
)
toast.error(title, {
description: getFolderWorkspacePathStatusDescription(pathStatus) ?? folderWorkspace.folderPath
})
return false