mirror of
https://github.com/stablyai/orca.git
synced 2026-09-22 00:02:31 +00:00
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:
@@ -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",
|
||||
|
||||
@@ -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('ワークスペースの同期エラー')
|
||||
})
|
||||
})
|
||||
@@ -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.",
|
||||
|
||||
@@ -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.",
|
||||
|
||||
@@ -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 によって使用されます。",
|
||||
|
||||
@@ -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를 사용자 정의하지 않은 리포지토리에서 사용됩니다.",
|
||||
|
||||
@@ -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
|
||||
}
|
||||
|
||||
@@ -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
|
||||
|
||||
Reference in New Issue
Block a user