mirror of
https://github.com/stablyai/orca.git
synced 2026-11-13 17:37:40 +00:00
refactor(i18n): read render-path translations from useTranslate in smaller component areas (activity through maintenance) (#39424)
Applies pnpm i18n:reactive-translate --helpers. Output is unchanged: t delegates to translate() and helpers default the threaded parameter. Co-authored-by: OrcaWin <293788423+OrcaWin@users.noreply.github.com>
This commit is contained in:
@@ -50,110 +50,17 @@
|
||||
11 src/renderer/src/components/UpdateErrorCardContent.tsx
|
||||
3 src/renderer/src/components/WorktreeBaseFallbackDialog.tsx
|
||||
3 src/renderer/src/components/ZoomOverlay.tsx
|
||||
4 src/renderer/src/components/activity/ActivityTitlebarControls.tsx
|
||||
5 src/renderer/src/components/activity/activity-scope-filter-controls.tsx
|
||||
11 src/renderer/src/components/activity/activity-thread-context-menu.tsx
|
||||
6 src/renderer/src/components/activity/activity-thread-detail-pane.tsx
|
||||
3 src/renderer/src/components/activity/activity-thread-hover-card-summary.tsx
|
||||
2 src/renderer/src/components/activity/activity-thread-hover-card.tsx
|
||||
1 src/renderer/src/components/activity/activity-thread-list-pane.tsx
|
||||
2 src/renderer/src/components/activity/activity-thread-list-resize-handle.tsx
|
||||
12 src/renderer/src/components/activity/activity-thread-list-toolbar.tsx
|
||||
13 src/renderer/src/components/activity/activity-thread-options-menu.tsx
|
||||
6 src/renderer/src/components/activity/activity-thread-row.tsx
|
||||
1 src/renderer/src/components/activity/activity-thread-virtual-row.tsx
|
||||
21 src/renderer/src/components/agent-session-continuation/AgentSessionContinuationDialog.tsx
|
||||
1 src/renderer/src/components/agent-session-continuation/AgentSessionContinuationMenuItem.tsx
|
||||
2 src/renderer/src/components/agent/AgentLaunchPromptComposer.tsx
|
||||
9 src/renderer/src/components/agent/AgentLaunchSetupPanel.tsx
|
||||
6 src/renderer/src/components/artifacts/ArtifactActions.tsx
|
||||
2 src/renderer/src/components/artifacts/ArtifactCollection.tsx
|
||||
1 src/renderer/src/components/artifacts/ArtifactDetailDrawer.tsx
|
||||
3 src/renderer/src/components/artifacts/ArtifactDetailHeader.tsx
|
||||
1 src/renderer/src/components/artifacts/ArtifactListRow.tsx
|
||||
3 src/renderer/src/components/artifacts/ArtifactListSearchField.tsx
|
||||
6 src/renderer/src/components/artifacts/ArtifactListTableHeader.tsx
|
||||
2 src/renderer/src/components/artifacts/ArtifactListToolbar.tsx
|
||||
2 src/renderer/src/components/artifacts/ArtifactPreview.tsx
|
||||
16 src/renderer/src/components/artifacts/ArtifactPublishButton.tsx
|
||||
6 src/renderer/src/components/artifacts/ArtifactPublishedLinkPanel.tsx
|
||||
5 src/renderer/src/components/artifacts/ArtifactsPage.tsx
|
||||
1 src/renderer/src/components/artifacts/ArtifactsPageSkeleton.tsx
|
||||
17 src/renderer/src/components/artifacts/ArtifactsPageStates.tsx
|
||||
5 src/renderer/src/components/automations/use-external-automation-actions.ts
|
||||
10 src/renderer/src/components/browser-extension-install-dialog.tsx
|
||||
4 src/renderer/src/components/browser-webauthn-account-dialog.tsx
|
||||
8 src/renderer/src/components/cmd-j/PaletteCreateWorktreeRow.tsx
|
||||
3 src/renderer/src/components/cmd-j/PaletteFilterChips.tsx
|
||||
9 src/renderer/src/components/cmd-j/PaletteFilterFieldOptions.tsx
|
||||
6 src/renderer/src/components/cmd-j/PaletteFilterMenu.tsx
|
||||
1 src/renderer/src/components/cmd-j/palette-live-status.tsx
|
||||
3 src/renderer/src/components/confirmation-dialog.tsx
|
||||
2 src/renderer/src/components/contextual-tours/ContextualTourControl.tsx
|
||||
7 src/renderer/src/components/contextual-tours/ContextualTourOverlaySurface.tsx
|
||||
2 src/renderer/src/components/contextual-tours/ContextualTourProgressDots.tsx
|
||||
13 src/renderer/src/components/crash-report/CrashReportDialogSurface.tsx
|
||||
3 src/renderer/src/components/dashboard-popout/AgentDashboardFilterChips.tsx
|
||||
10 src/renderer/src/components/dashboard-popout/AgentDashboardToolbar.tsx
|
||||
4 src/renderer/src/components/dashboard-popout/AgentKanbanBoard.tsx
|
||||
5 src/renderer/src/components/dashboard-popout/AgentKanbanCard.tsx
|
||||
2 src/renderer/src/components/dashboard-popout/AgentTerminalDialog.tsx
|
||||
1 src/renderer/src/components/dashboard/AgentDashboardDrawer.tsx
|
||||
10 src/renderer/src/components/dashboard/AgentDashboardExperimentalSetting.tsx
|
||||
10 src/renderer/src/components/dashboard/AgentDashboardSettingsMenu.tsx
|
||||
2 src/renderer/src/components/dashboard/DashboardAgentChildDisclosure.tsx
|
||||
4 src/renderer/src/components/dashboard/DashboardAgentRow.tsx
|
||||
3 src/renderer/src/components/dashboard/DashboardAgentRowMessage.tsx
|
||||
9 src/renderer/src/components/dashboard/DashboardAgentRowTrailingControls.tsx
|
||||
1 src/renderer/src/components/dictation/DictationController.tsx
|
||||
7 src/renderer/src/components/dictation/DictationIndicator.tsx
|
||||
12 src/renderer/src/components/diff-comments/DiffCommentCard.tsx
|
||||
9 src/renderer/src/components/diff-comments/DiffCommentDraftCard.tsx
|
||||
7 src/renderer/src/components/diff-comments/DiffCommentPopover.tsx
|
||||
3 src/renderer/src/components/diff-comments/DiffCommentPopover.tsx
|
||||
1 src/renderer/src/components/diff-comments/useDiffCommentDecorator.tsx
|
||||
2 src/renderer/src/components/editor/markdown-preview/render-tree/use-markdown-preview-components.tsx
|
||||
3 src/renderer/src/components/editor/review-note-delivery/ReviewNotesSendMenuContent.tsx
|
||||
1 src/renderer/src/components/emulator-pane/EmulatorPane.tsx
|
||||
9 src/renderer/src/components/emulator-pane/MobileEmulatorAgentSetupGuide.tsx
|
||||
3 src/renderer/src/components/emulator-pane/MobileEmulatorAgentSetupGuideSteps.tsx
|
||||
5 src/renderer/src/components/emulator-pane/MobileEmulatorTabIntroCallout.tsx
|
||||
10 src/renderer/src/components/emulator-pane/emulator-device-picker.tsx
|
||||
9 src/renderer/src/components/emulator-pane/emulator-pane-toolbar.tsx
|
||||
5 src/renderer/src/components/emulator-pane/emulator-screen-stream-content.tsx
|
||||
2 src/renderer/src/components/file-actions/OpenInContextSubmenu.tsx
|
||||
5 src/renderer/src/components/floating-terminal/FloatingTerminalEmptyState.tsx
|
||||
3 src/renderer/src/components/floating-terminal/FloatingTerminalIconContextMenu.tsx
|
||||
8 src/renderer/src/components/floating-terminal/FloatingTerminalOrchestrationDialog.tsx
|
||||
4 src/renderer/src/components/floating-terminal/FloatingTerminalToggleButton.tsx
|
||||
8 src/renderer/src/components/floating-terminal/FloatingTerminalWindowControls.tsx
|
||||
13 src/renderer/src/components/github/CommentCodeContext.tsx
|
||||
3 src/renderer/src/components/github/CommentReactions.tsx
|
||||
1 src/renderer/src/components/github/GitHubMarkdownComposer.tsx
|
||||
4 src/renderer/src/components/github/IssueSourceSelector.tsx
|
||||
3 src/renderer/src/components/github/PRAssigneesPanel.tsx
|
||||
9 src/renderer/src/components/github/PRFilterDropdowns.tsx
|
||||
4 src/renderer/src/components/github/PRFilterPickers.tsx
|
||||
26 src/renderer/src/components/github/PRFilterSections.tsx
|
||||
5 src/renderer/src/components/github/PRViewedCheckbox.tsx
|
||||
1 src/renderer/src/components/github/github-markdown-composer-preview-pane.tsx
|
||||
2 src/renderer/src/components/github/github-markdown-composer-tabbar.tsx
|
||||
3 src/renderer/src/components/github/github-markdown-image-input.tsx
|
||||
7 src/renderer/src/components/github/github-rate-limit-display.tsx
|
||||
1 src/renderer/src/components/github/use-github-image-paste.ts
|
||||
2 src/renderer/src/components/github/use-github-image-uploads.ts
|
||||
10 src/renderer/src/components/gitlab-item-dialog-parts.tsx
|
||||
1 src/renderer/src/components/gitlab-item-dialog/gitlab-conversation-tab.tsx
|
||||
18 src/renderer/src/components/gitlab-item-dialog/gitlab-description-tab.tsx
|
||||
7 src/renderer/src/components/gitlab-item-dialog/gitlab-files-tab.tsx
|
||||
16 src/renderer/src/components/gitlab-item-dialog/gitlab-item-dialog-view.tsx
|
||||
2 src/renderer/src/components/gitlab-item-dialog/gitlab-pipeline-tab.tsx
|
||||
9 src/renderer/src/components/gitlab/gitlab-rate-limit-display.tsx
|
||||
23 src/renderer/src/components/integrations/source-control/bitbucket-credentials-dialog.tsx
|
||||
21 src/renderer/src/components/integrations/source-control/bitbucket-integration-card.tsx
|
||||
25 src/renderer/src/components/integrations/source-control/cli-source-control-integration-cards.tsx
|
||||
28 src/renderer/src/components/integrations/source-control/token-source-control-integration-cards.tsx
|
||||
20 src/renderer/src/components/integrations/task-trackers/jira-integration-card.tsx
|
||||
16 src/renderer/src/components/integrations/task-trackers/linear-integration-card.tsx
|
||||
1 src/renderer/src/components/jira-attachment-image.tsx
|
||||
5 src/renderer/src/components/jira-connect-credential-guide.tsx
|
||||
30 src/renderer/src/components/jira-connect-dialog.tsx
|
||||
@@ -182,15 +89,8 @@
|
||||
9 src/renderer/src/components/linear-project-view-surfaces.tsx
|
||||
8 src/renderer/src/components/linear-scope-selector.tsx
|
||||
5 src/renderer/src/components/linear-workspace-picker.tsx
|
||||
8 src/renderer/src/components/link-actions/LinkActionPopover.tsx
|
||||
14 src/renderer/src/components/link-routing-preference-dialog.tsx
|
||||
1 src/renderer/src/components/local-only-menu-hint.tsx
|
||||
4 src/renderer/src/components/machine-identity/MachineNameField.tsx
|
||||
17 src/renderer/src/components/macos-update-requirement/MacosUpdateRequirementDialog.tsx
|
||||
12 src/renderer/src/components/maintenance/update-card/UpdateAvailableCardContent.tsx
|
||||
6 src/renderer/src/components/maintenance/update-card/UpdateCardStateContent.tsx
|
||||
1 src/renderer/src/components/maintenance/update-card/UpdateCheckFeedback.tsx
|
||||
11 src/renderer/src/components/maintenance/update-card/UpdateDownloadCardContent.tsx
|
||||
2 src/renderer/src/components/managed-runtime/ManagedRuntimeActionsMenu.tsx
|
||||
3 src/renderer/src/components/managed-runtime/ManagedRuntimeInstallRow.tsx
|
||||
5 src/renderer/src/components/managed-runtime/ManagedRuntimeRemoveDialog.tsx
|
||||
|
||||
@@ -5,9 +5,10 @@ import { Badge } from '@/components/ui/badge'
|
||||
import { Button } from '@/components/ui/button'
|
||||
import { Tooltip, TooltipContent, TooltipTrigger } from '@/components/ui/tooltip'
|
||||
import { useActivityUnreadCount } from './useActivityUnreadCount'
|
||||
import { translate } from '@/i18n/i18n'
|
||||
import { useTranslate } from '@/i18n/use-translate'
|
||||
|
||||
export function ActivityTitlebarControls(): React.JSX.Element {
|
||||
const t = useTranslate()
|
||||
const unreadCount = useActivityUnreadCount()
|
||||
const closeActivityPage = useAppStore((s) => s.closeActivityPage)
|
||||
|
||||
@@ -25,7 +26,7 @@ export function ActivityTitlebarControls(): React.JSX.Element {
|
||||
variant="ghost"
|
||||
size="icon-xs"
|
||||
onClick={closeActivityPage}
|
||||
aria-label={translate(
|
||||
aria-label={t(
|
||||
'auto.components.activity.ActivityTitlebarControls.dc708f3eff',
|
||||
'Close agents'
|
||||
)}
|
||||
@@ -34,22 +35,18 @@ export function ActivityTitlebarControls(): React.JSX.Element {
|
||||
</Button>
|
||||
</TooltipTrigger>
|
||||
<TooltipContent side="bottom" sideOffset={6}>
|
||||
{translate(
|
||||
'auto.components.activity.ActivityTitlebarControls.dc708f3eff',
|
||||
'Close agents'
|
||||
)}
|
||||
{t('auto.components.activity.ActivityTitlebarControls.dc708f3eff', 'Close agents')}
|
||||
</TooltipContent>
|
||||
</Tooltip>
|
||||
<Bell className="size-3.5 shrink-0 text-muted-foreground" />
|
||||
<span className="truncate text-xs font-medium">
|
||||
{translate('auto.components.activity.ActivityTitlebarControls.d6a8de3934', 'agents')}
|
||||
{t('auto.components.activity.ActivityTitlebarControls.d6a8de3934', 'agents')}
|
||||
</span>
|
||||
<Badge variant="secondary" className="h-5 px-1.5 text-[11px] font-normal">
|
||||
{translate(
|
||||
'auto.components.activity.ActivityTitlebarControls.unreadCount',
|
||||
'{{count}} unread',
|
||||
{ count: unreadCount, defaultValue_one: '{{count}} unread' }
|
||||
)}
|
||||
{t('auto.components.activity.ActivityTitlebarControls.unreadCount', '{{count}} unread', {
|
||||
count: unreadCount,
|
||||
defaultValue_one: '{{count}} unread'
|
||||
})}
|
||||
</Badge>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
@@ -21,6 +21,7 @@ import { tabFromWorktreeAttributedStatusEntry } from '../sidebar/workspace-card/
|
||||
import { agentEntrySessionStartedAt as effectiveWorktreeAgentRowStartedAt } from '../../../../shared/agent-session/lifecycle/agent-session-start-time'
|
||||
import type { BuildActivityEventsArgs } from './activity-event-builder'
|
||||
import { standaloneActivityWorktree } from './activity-standalone-worktree'
|
||||
import type { Translate } from '@/i18n/use-translate'
|
||||
|
||||
export type ActivityTabContext = { worktreeId: string; tab: TerminalTab }
|
||||
export type ActivityEventOwner = { worktree: Worktree; repo: Repo | null; knownWorktree: boolean }
|
||||
@@ -141,7 +142,8 @@ export function resolveActivityEventOwner(
|
||||
entry: AgentStatusEntry,
|
||||
terminalPtyId: string | null | undefined,
|
||||
tabHostIndex: ActivityTabHostIndex,
|
||||
ownerCache: Map<string, ActivityEventOwner>
|
||||
ownerCache: Map<string, ActivityEventOwner>,
|
||||
t?: Translate
|
||||
): ActivityEventOwner {
|
||||
const executionHostId = resolveActivityExecutionHostId(
|
||||
context,
|
||||
@@ -160,7 +162,7 @@ export function resolveActivityEventOwner(
|
||||
const worktree =
|
||||
resolvedWorktree ??
|
||||
mappedWorktree ??
|
||||
standaloneActivityWorktree(context.worktreeId, executionHostId)
|
||||
standaloneActivityWorktree(context.worktreeId, executionHostId, t)
|
||||
let repo =
|
||||
executionHostId && args.repos
|
||||
? findIndexedRepoOwnerForHost(args.repos, worktree.repoId, executionHostId)
|
||||
|
||||
@@ -29,6 +29,7 @@ import {
|
||||
resolveActivityEventOwner,
|
||||
type ActivityEventOwner
|
||||
} from './activity-event-builder-context'
|
||||
import type { Translate } from '@/i18n/use-translate'
|
||||
|
||||
export { createActivityEventBuildCache, type ActivityEventBuildCache, newestActivityHistoryEntries }
|
||||
|
||||
@@ -52,7 +53,8 @@ export type BuildActivityEventsArgs = {
|
||||
|
||||
export function buildActivityEvents(
|
||||
args: BuildActivityEventsArgs,
|
||||
cache?: ActivityEventBuildCache
|
||||
cache?: ActivityEventBuildCache,
|
||||
t?: Translate
|
||||
): {
|
||||
events: ActivityEvent[]
|
||||
liveAgentByPaneKey: Record<string, ActivityLiveAgentSnapshot>
|
||||
@@ -103,7 +105,8 @@ export function buildActivityEvents(
|
||||
entry,
|
||||
context.tab.ptyId,
|
||||
tabHostIndex,
|
||||
ownerCache
|
||||
ownerCache,
|
||||
t
|
||||
)
|
||||
const orchestration = args.runtimeAgentOrchestrationByPaneKey?.[paneKey]
|
||||
// Only fresh raw live state proves working; history and orchestration enrichment cannot establish liveness.
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
import React from 'react'
|
||||
import { useAppStore } from '@/store'
|
||||
import { DropdownMenuCheckboxItem, DropdownMenuItem } from '@/components/ui/dropdown-menu'
|
||||
import { translate } from '@/i18n/i18n'
|
||||
import { useTranslate } from '@/i18n/use-translate'
|
||||
import SidebarRepositoryFilterSection from '@/components/sidebar/worktree-list/controls/SidebarRepositoryFilterSection'
|
||||
import { SidebarHostScopeMenuSection } from '@/components/sidebar/worktree-list/controls/SidebarHostScopeMenuSection'
|
||||
import {
|
||||
@@ -12,6 +12,7 @@ import { useSidebarHostScopeOptions } from '@/components/sidebar/worktree-list/c
|
||||
|
||||
/** Persisted Agents scope is separate from workspace navigation filters; the parent owns the Filters label/separator. */
|
||||
export function ActivityScopeFilterMenuItems(): React.JSX.Element {
|
||||
const t = useTranslate()
|
||||
const agentsVisibleHostIds = useAppStore((s) => s.agentsVisibleHostIds)
|
||||
const setAgentsVisibleHostIds = useAppStore((s) => s.setAgentsVisibleHostIds)
|
||||
const agentsFilterRepoIds = useAppStore((s) => s.agentsFilterRepoIds)
|
||||
@@ -42,7 +43,7 @@ export function ActivityScopeFilterMenuItems(): React.JSX.Element {
|
||||
onCheckedChange={(checked) => setHideAutomationGenerated(checked === true)}
|
||||
onSelect={(event) => event.preventDefault()}
|
||||
>
|
||||
{translate(
|
||||
{t(
|
||||
'auto.components.activity.ActivityScopeFilterControls.hideAutomationCreated',
|
||||
'Hide automation-created'
|
||||
)}
|
||||
@@ -52,7 +53,7 @@ export function ActivityScopeFilterMenuItems(): React.JSX.Element {
|
||||
onCheckedChange={(checked) => setHideCliCreated(checked === true)}
|
||||
onSelect={(event) => event.preventDefault()}
|
||||
>
|
||||
{translate(
|
||||
{t(
|
||||
'auto.components.activity.ActivityScopeFilterControls.hideCliCreated',
|
||||
'Hide CLI-created'
|
||||
)}
|
||||
@@ -62,12 +63,12 @@ export function ActivityScopeFilterMenuItems(): React.JSX.Element {
|
||||
checked={hideFromOtherDevices}
|
||||
onCheckedChange={(checked) => setHideFromOtherDevices(checked === true)}
|
||||
onSelect={(event) => event.preventDefault()}
|
||||
aria-label={translate(
|
||||
aria-label={t(
|
||||
'auto.components.activity.ActivityScopeFilterControls.hideOtherClientsAria',
|
||||
'Hide agents in workspaces created from other Orca clients on shared remote servers'
|
||||
)}
|
||||
>
|
||||
{translate(
|
||||
{t(
|
||||
'auto.components.activity.ActivityScopeFilterControls.hideOtherClients',
|
||||
'Hide other-client agents'
|
||||
)}
|
||||
@@ -75,7 +76,7 @@ export function ActivityScopeFilterMenuItems(): React.JSX.Element {
|
||||
) : null}
|
||||
{showHostScopeControls ? (
|
||||
<SidebarHostScopeMenuSection
|
||||
hostVisibilityLabel={getSidebarHostVisibilityLabel(agentsVisibleHostIds, hostOptions)}
|
||||
hostVisibilityLabel={getSidebarHostVisibilityLabel(agentsVisibleHostIds, hostOptions, t)}
|
||||
hostOptions={hostOptions}
|
||||
preserveWorkspaceBoardOpen={false}
|
||||
// Why: the section only calls this to reset to "all hosts".
|
||||
@@ -95,7 +96,7 @@ export function ActivityScopeFilterMenuItems(): React.JSX.Element {
|
||||
setAgentsFilterRepoIds([])
|
||||
}}
|
||||
>
|
||||
{translate(
|
||||
{t(
|
||||
'auto.components.activity.ActivityScopeFilterControls.resetScope',
|
||||
'Show all hosts and projects'
|
||||
)}
|
||||
|
||||
@@ -1,4 +1,5 @@
|
||||
import { i18n, translate } from '@/i18n/i18n'
|
||||
import type { Translate } from '@/i18n/use-translate'
|
||||
import { getTranslationResourceVersion } from '@/i18n/translation-resource-version'
|
||||
import { FLOATING_TERMINAL_WORKTREE_ID } from '../../../../shared/constants'
|
||||
import type { Worktree } from '../../../../shared/worktree/types'
|
||||
@@ -12,18 +13,13 @@ let cachedDisplayNameScope: string | undefined
|
||||
|
||||
function buildStandaloneActivityWorktree(
|
||||
worktreeId: string,
|
||||
executionHostId?: ExecutionHostId
|
||||
executionHostId?: ExecutionHostId,
|
||||
t: Translate = translate
|
||||
): Worktree {
|
||||
const displayName =
|
||||
worktreeId === FLOATING_TERMINAL_WORKTREE_ID
|
||||
? translate(
|
||||
'auto.components.activity.standaloneWorktree.floatingTerminal',
|
||||
'Floating terminal'
|
||||
)
|
||||
: translate(
|
||||
'auto.components.activity.standaloneWorktree.standaloneTerminal',
|
||||
'Standalone terminal'
|
||||
)
|
||||
? t('auto.components.activity.standaloneWorktree.floatingTerminal', 'Floating terminal')
|
||||
: t('auto.components.activity.standaloneWorktree.standaloneTerminal', 'Standalone terminal')
|
||||
return {
|
||||
id: worktreeId,
|
||||
...(executionHostId ? { hostId: executionHostId } : {}),
|
||||
@@ -49,7 +45,8 @@ function buildStandaloneActivityWorktree(
|
||||
/** Return a stable synthetic worktree for terminal-only activity. */
|
||||
export function standaloneActivityWorktree(
|
||||
worktreeId: string,
|
||||
executionHostId?: ExecutionHostId
|
||||
executionHostId?: ExecutionHostId,
|
||||
t?: Translate
|
||||
): Worktree {
|
||||
const displayNameScope = `${i18n.language}\0${getTranslationResourceVersion()}`
|
||||
if (cachedDisplayNameScope !== displayNameScope) {
|
||||
@@ -62,7 +59,7 @@ export function standaloneActivityWorktree(
|
||||
if (standaloneActivityWorktrees.size >= STANDALONE_ACTIVITY_WORKTREES_CAP) {
|
||||
standaloneActivityWorktrees.clear()
|
||||
}
|
||||
worktree = buildStandaloneActivityWorktree(worktreeId, executionHostId)
|
||||
worktree = buildStandaloneActivityWorktree(worktreeId, executionHostId, t)
|
||||
standaloneActivityWorktrees.set(cacheKey, worktree)
|
||||
}
|
||||
return worktree
|
||||
|
||||
@@ -9,6 +9,7 @@ import {
|
||||
ContextMenuTrigger
|
||||
} from '@/components/ui/context-menu'
|
||||
import { translate } from '@/i18n/i18n'
|
||||
import { useTranslate, type Translate } from '@/i18n/use-translate'
|
||||
import { CLOSE_ALL_CONTEXT_MENUS_EVENT } from '@/lib/close-all-context-menus'
|
||||
import { formatClipboardFilePath } from '@/lib/clipboard-file-path'
|
||||
import { getWorktreeRowLocalPathOpenOwner } from '@/lib/worktree-row-open-in-owner'
|
||||
@@ -25,12 +26,13 @@ type CopyTarget = { key: string; label: string; value: string }
|
||||
|
||||
export function getActivityThreadCopyTargets(
|
||||
thread: AgentPaneThread,
|
||||
hasWorkspace: boolean
|
||||
hasWorkspace: boolean,
|
||||
t: Translate = translate
|
||||
): CopyTarget[] {
|
||||
const title: CopyTarget = {
|
||||
key: 'title',
|
||||
label: translate('auto.components.activity.ActivityThreadContextMenu.copyTitle', 'Copy Title'),
|
||||
value: activityThreadRowCopy(thread).taskTitle
|
||||
label: t('auto.components.activity.ActivityThreadContextMenu.copyTitle', 'Copy Title'),
|
||||
value: activityThreadRowCopy(thread, t).taskTitle
|
||||
}
|
||||
// Why gated: synthetic floating/standalone worktrees have no path.
|
||||
if (!hasWorkspace || !thread.worktree.path) {
|
||||
@@ -38,7 +40,7 @@ export function getActivityThreadCopyTargets(
|
||||
}
|
||||
const path: CopyTarget = {
|
||||
key: 'path',
|
||||
label: translate('auto.components.activity.ActivityThreadContextMenu.copyPath', 'Copy Path'),
|
||||
label: t('auto.components.activity.ActivityThreadContextMenu.copyPath', 'Copy Path'),
|
||||
value: thread.worktree.path
|
||||
}
|
||||
// Same order as the workspace menu: Copy Path, then the name.
|
||||
@@ -85,6 +87,7 @@ export function ActivityThreadContextMenu({
|
||||
/** Receives whether the menu is open, so the row can keep its preview out of the way. */
|
||||
children: (menuOpen: boolean) => React.ReactElement
|
||||
}): React.JSX.Element {
|
||||
const t = useTranslate()
|
||||
const [menuOpen, setMenuOpen] = React.useState(false)
|
||||
// Why a snapshot: the pointerdown on a portaled item clears the list selection before onSelect.
|
||||
const [targets, setTargets] = React.useState<readonly AgentPaneThread[]>([thread])
|
||||
@@ -112,7 +115,7 @@ export function ActivityThreadContextMenu({
|
||||
{/* Why no focus restore: refocusing the row would reopen its hover preview and pin it open. */}
|
||||
<ContextMenuContent className="w-52" onCloseAutoFocus={(event) => event.preventDefault()}>
|
||||
<ContextMenuLabel>
|
||||
{translate('auto.components.activity.ActivityThreadContextMenu.agentSection', 'Agent')}
|
||||
{t('auto.components.activity.ActivityThreadContextMenu.agentSection', 'Agent')}
|
||||
</ContextMenuLabel>
|
||||
{targets.length > 1 ? (
|
||||
<ActivityThreadBulkMenuItems
|
||||
@@ -154,23 +157,21 @@ function ActivityThreadSingleMenuItems({
|
||||
onMarkRead: (thread: AgentPaneThread) => void
|
||||
onMarkUnread: (thread: AgentPaneThread) => void
|
||||
}): React.JSX.Element {
|
||||
const t = useTranslate()
|
||||
return (
|
||||
<>
|
||||
<ContextMenuItem onSelect={() => onOpen(thread)}>
|
||||
<PanelRight className="size-3.5" />
|
||||
{translate('auto.components.activity.ActivityThreadContextMenu.open', 'Open')}
|
||||
{t('auto.components.activity.ActivityThreadContextMenu.open', 'Open')}
|
||||
</ContextMenuItem>
|
||||
{canJump ? (
|
||||
<ContextMenuItem onSelect={() => onJump(thread)}>
|
||||
<ExternalLink className="size-3.5" />
|
||||
{translate(
|
||||
'auto.components.activity.ActivityThreadContextMenu.goToWorkspace',
|
||||
'Go to Workspace'
|
||||
)}
|
||||
{t('auto.components.activity.ActivityThreadContextMenu.goToWorkspace', 'Go to Workspace')}
|
||||
</ContextMenuItem>
|
||||
) : null}
|
||||
<ContextMenuSeparator />
|
||||
{getActivityThreadCopyTargets(thread, canJump).map((target) => (
|
||||
{getActivityThreadCopyTargets(thread, canJump, t).map((target) => (
|
||||
<ContextMenuItem
|
||||
key={target.key}
|
||||
onSelect={() => void window.api.ui.writeClipboardText(formatCopyTarget(thread, target))}
|
||||
@@ -186,21 +187,15 @@ function ActivityThreadSingleMenuItems({
|
||||
>
|
||||
{thread.unread ? <BellOff className="size-3.5" /> : <Bell className="size-3.5" />}
|
||||
{thread.unread
|
||||
? translate('auto.components.activity.ActivityThreadContextMenu.markRead', 'Mark Read')
|
||||
: translate(
|
||||
'auto.components.activity.ActivityThreadContextMenu.markUnread',
|
||||
'Mark Unread'
|
||||
)}
|
||||
? t('auto.components.activity.ActivityThreadContextMenu.markRead', 'Mark Read')
|
||||
: t('auto.components.activity.ActivityThreadContextMenu.markUnread', 'Mark Unread')}
|
||||
</ContextMenuItem>
|
||||
{isClearableActivityThread(thread) ? (
|
||||
<>
|
||||
<ContextMenuSeparator />
|
||||
<ContextMenuItem onSelect={() => clearActivityThread(thread)}>
|
||||
<X className="size-3.5" />
|
||||
{translate(
|
||||
'auto.components.activity.ActivityThreadContextMenu.clear',
|
||||
'Clear from List'
|
||||
)}
|
||||
{t('auto.components.activity.ActivityThreadContextMenu.clear', 'Clear from List')}
|
||||
</ContextMenuItem>
|
||||
</>
|
||||
) : null}
|
||||
@@ -220,6 +215,7 @@ function ActivityThreadBulkMenuItems({
|
||||
onMarkManyRead: (threads: readonly AgentPaneThread[]) => void
|
||||
onMarkManyUnread: (threads: readonly AgentPaneThread[]) => void
|
||||
}): React.JSX.Element {
|
||||
const t = useTranslate()
|
||||
const unread = targets.filter((target) => target.unread)
|
||||
const read = targets.filter((target) => !target.unread && canMarkUnread(target))
|
||||
const clearable = targets.filter(isClearableActivityThread)
|
||||
@@ -229,7 +225,7 @@ function ActivityThreadBulkMenuItems({
|
||||
{unread.length > 0 ? (
|
||||
<ContextMenuItem onSelect={() => onMarkManyRead(unread)}>
|
||||
<BellOff className="size-3.5" />
|
||||
{translate(
|
||||
{t(
|
||||
'auto.components.activity.ActivityThreadContextMenu.markManyRead',
|
||||
'Mark {{count}} Agents Read',
|
||||
{ count: unread.length, defaultValue_one: 'Mark {{count}} Agent Read' }
|
||||
@@ -239,15 +235,12 @@ function ActivityThreadBulkMenuItems({
|
||||
<ContextMenuItem disabled={read.length === 0} onSelect={() => onMarkManyUnread(read)}>
|
||||
<Bell className="size-3.5" />
|
||||
{read.length > 0
|
||||
? translate(
|
||||
? t(
|
||||
'auto.components.activity.ActivityThreadContextMenu.markManyUnread',
|
||||
'Mark {{count}} Agents Unread',
|
||||
{ count: read.length, defaultValue_one: 'Mark {{count}} Agent Unread' }
|
||||
)
|
||||
: translate(
|
||||
'auto.components.activity.ActivityThreadContextMenu.markUnread',
|
||||
'Mark Unread'
|
||||
)}
|
||||
: t('auto.components.activity.ActivityThreadContextMenu.markUnread', 'Mark Unread')}
|
||||
</ContextMenuItem>
|
||||
)}
|
||||
<ContextMenuSeparator />
|
||||
@@ -257,15 +250,12 @@ function ActivityThreadBulkMenuItems({
|
||||
>
|
||||
<X className="size-3.5" />
|
||||
{clearable.length > 0
|
||||
? translate(
|
||||
? t(
|
||||
'auto.components.activity.ActivityThreadContextMenu.clearMany',
|
||||
'Clear {{count}} Agents from List',
|
||||
{ count: clearable.length, defaultValue_one: 'Clear {{count}} Agent from List' }
|
||||
)
|
||||
: translate(
|
||||
'auto.components.activity.ActivityThreadContextMenu.clear',
|
||||
'Clear from List'
|
||||
)}
|
||||
: t('auto.components.activity.ActivityThreadContextMenu.clear', 'Clear from List')}
|
||||
</ContextMenuItem>
|
||||
</>
|
||||
)
|
||||
|
||||
@@ -14,6 +14,7 @@ import {
|
||||
threadAgentStateLabel
|
||||
} from './activity-thread-presentation'
|
||||
import type { ActivityThreadGroup, AgentPaneThread } from './activity-thread-types'
|
||||
import { useIntlLocale, useTranslate } from '@/i18n/use-translate'
|
||||
|
||||
export { ActivityThreadOptionsMenu } from './activity-thread-options-menu'
|
||||
|
||||
@@ -26,9 +27,10 @@ export function EventTime({
|
||||
compact?: boolean
|
||||
className?: string
|
||||
}): React.JSX.Element {
|
||||
const intlLocale = useIntlLocale()
|
||||
// Memoized rows ignore unrelated writes; refresh this leaf from the visibility-gated 30s clock, matching WorktreeCardAgents.
|
||||
const now = useNow(30_000)
|
||||
const absolute = formatAbsoluteDate(timestamp)
|
||||
const absolute = formatAbsoluteDate(timestamp, intlLocale)
|
||||
return (
|
||||
<Tooltip>
|
||||
<TooltipTrigger asChild>
|
||||
@@ -42,7 +44,9 @@ export function EventTime({
|
||||
aria-label={absolute}
|
||||
onClick={(event) => event.stopPropagation()}
|
||||
>
|
||||
{compact ? formatShortTimeAgo(timestamp, now) : formatRelativeTime(timestamp, now)}
|
||||
{compact
|
||||
? formatShortTimeAgo(timestamp, now)
|
||||
: formatRelativeTime(timestamp, now, intlLocale)}
|
||||
</button>
|
||||
</TooltipTrigger>
|
||||
<TooltipContent side="right" sideOffset={6}>
|
||||
@@ -71,8 +75,9 @@ export function ThreadAgentStateIndicator({
|
||||
}: {
|
||||
thread: AgentPaneThread
|
||||
}): React.JSX.Element {
|
||||
const t = useTranslate()
|
||||
const state = activityThreadStatusId(thread)
|
||||
const label = threadAgentStateLabel(thread)
|
||||
const label = threadAgentStateLabel(thread, t)
|
||||
return (
|
||||
<Tooltip>
|
||||
<TooltipTrigger asChild>
|
||||
|
||||
@@ -2,7 +2,7 @@ import React from 'react'
|
||||
import { MessageSquareText, TerminalSquare } from 'lucide-react'
|
||||
import { AgentTypeIcon } from '@/lib/agent-type-icon'
|
||||
import { cn } from '@/lib/utils'
|
||||
import { translate } from '@/i18n/i18n'
|
||||
import { useTranslate } from '@/i18n/use-translate'
|
||||
import { EventRepoBadge, ThreadAgentStateIndicator } from './activity-thread-controls'
|
||||
import type { ActivityTerminalPortalSlotId, AgentPaneThread } from './activity-thread-types'
|
||||
|
||||
@@ -33,6 +33,7 @@ export function ActivityThreadDetailPane({
|
||||
showTerminalLoadingLabel: boolean
|
||||
visibleThreadCount: number
|
||||
}): React.JSX.Element {
|
||||
const t = useTranslate()
|
||||
return (
|
||||
<section className="min-w-0 flex-1 overflow-hidden">
|
||||
{selectedThread ? (
|
||||
@@ -67,11 +68,11 @@ export function ActivityThreadDetailPane({
|
||||
<div className="flex min-h-0 flex-1 flex-col items-center justify-center gap-2 p-4 text-sm text-muted-foreground">
|
||||
<TerminalSquare className="size-7" />
|
||||
{selectedWorktreeAvailable
|
||||
? translate(
|
||||
? t(
|
||||
'auto.components.activity.ActivityPrototypePage.afdc2139a8',
|
||||
'Agent terminal closed. Open a new terminal in this workspace to continue.'
|
||||
)
|
||||
: translate(
|
||||
: t(
|
||||
'auto.components.activity.ActivityPrototypePage.22b22034bc',
|
||||
'Standalone terminal unavailable in Activity.'
|
||||
)}
|
||||
@@ -111,7 +112,7 @@ export function ActivityThreadDetailPane({
|
||||
<div className="ml-3 mt-3 inline-flex items-center gap-2 rounded-md border border-border bg-background/85 px-2 py-1 text-xs text-muted-foreground shadow-xs">
|
||||
<span className="h-3 w-1.5 rounded-sm bg-muted-foreground/70" />
|
||||
<span>
|
||||
{translate(
|
||||
{t(
|
||||
'auto.components.activity.ActivityPrototypePage.8de7c5beaa',
|
||||
'Terminal unavailable'
|
||||
)}
|
||||
@@ -121,7 +122,7 @@ export function ActivityThreadDetailPane({
|
||||
<div className="ml-3 mt-3 inline-flex items-center gap-2 rounded-md border border-border bg-background/85 px-2 py-1 text-xs text-muted-foreground shadow-xs">
|
||||
<span className="h-3 w-1.5 animate-pulse rounded-sm bg-muted-foreground/70" />
|
||||
<span>
|
||||
{translate(
|
||||
{t(
|
||||
'auto.components.activity.ActivityPrototypePage.1b633f5c1e',
|
||||
'Connecting terminal...'
|
||||
)}
|
||||
@@ -139,15 +140,12 @@ export function ActivityThreadDetailPane({
|
||||
{visibleThreadCount === 0 ? (
|
||||
<>
|
||||
<MessageSquareText className="size-7" />
|
||||
{translate(
|
||||
'auto.components.activity.ActivityPrototypePage.e3db9892f6',
|
||||
'No activity yet.'
|
||||
)}
|
||||
{t('auto.components.activity.ActivityPrototypePage.e3db9892f6', 'No activity yet.')}
|
||||
</>
|
||||
) : (
|
||||
<>
|
||||
<TerminalSquare className="size-7" />
|
||||
{translate(
|
||||
{t(
|
||||
'auto.components.activity.ActivityPrototypePage.cf780197a1',
|
||||
'Select an agent to view its activity'
|
||||
)}
|
||||
|
||||
@@ -1,5 +1,6 @@
|
||||
import type { AgentDotState } from '@/components/AgentStateDot'
|
||||
import { translate } from '@/i18n/i18n'
|
||||
import type { Translate } from '@/i18n/use-translate'
|
||||
import { formatAgentTypeLabel } from '../../../../shared/agent-status/presentation/agent-type-label'
|
||||
import { getAgentRowPrimaryText } from '../../../../shared/agent-status/presentation/agent-row-primary-text'
|
||||
import { getActivityThreadWorkspaceTitle } from '@/lib/activity-thread-display'
|
||||
@@ -37,7 +38,8 @@ function activityStatusRank(thread: AgentPaneThread): number {
|
||||
|
||||
export function getActivityThreadGroup(
|
||||
thread: AgentPaneThread,
|
||||
groupBy: ActivityGroupBy
|
||||
groupBy: ActivityGroupBy,
|
||||
t: Translate = translate
|
||||
): { key: string; label: string; state?: AgentDotState } {
|
||||
if (groupBy === 'none') {
|
||||
return { key: 'all', label: '' }
|
||||
@@ -45,17 +47,14 @@ export function getActivityThreadGroup(
|
||||
if (groupBy === 'status') {
|
||||
// The key is the glyph every row in the group draws, so it heads the group too.
|
||||
const status = activityThreadStatusId(thread)
|
||||
return { key: status, label: threadAgentStateLabel(thread), state: status }
|
||||
return { key: status, label: threadAgentStateLabel(thread, t), state: status }
|
||||
}
|
||||
if (groupBy === 'project') {
|
||||
return thread.repo
|
||||
? { key: `project:${thread.repo.id}`, label: thread.repo.displayName }
|
||||
: {
|
||||
key: 'project:unknown',
|
||||
label: translate(
|
||||
'auto.components.activity.ActivityPrototypePage.5651b216c6',
|
||||
'Unknown project'
|
||||
)
|
||||
label: t('auto.components.activity.ActivityPrototypePage.5651b216c6', 'Unknown project')
|
||||
}
|
||||
}
|
||||
if (groupBy === 'worktree') {
|
||||
@@ -66,7 +65,8 @@ export function getActivityThreadGroup(
|
||||
|
||||
export function buildActivityThreadGroups(
|
||||
threads: AgentPaneThread[],
|
||||
groupBy: ActivityGroupBy
|
||||
groupBy: ActivityGroupBy,
|
||||
t?: Translate
|
||||
): ActivityThreadGroup[] {
|
||||
if (groupBy === 'none') {
|
||||
return threads.length > 0 ? [{ key: 'all', label: '', threads }] : []
|
||||
@@ -74,7 +74,7 @@ export function buildActivityThreadGroups(
|
||||
const groups: ActivityThreadGroup[] = []
|
||||
const groupIndexByKey = new Map<string, number>()
|
||||
for (const thread of threads) {
|
||||
const group = getActivityThreadGroup(thread, groupBy)
|
||||
const group = getActivityThreadGroup(thread, groupBy, t)
|
||||
const existingIndex = groupIndexByKey.get(group.key)
|
||||
if (existingIndex === undefined) {
|
||||
groups.push({ ...group, threads: [thread] })
|
||||
|
||||
@@ -8,6 +8,7 @@ import { formatAgentTypeLabel } from '../../../../shared/agent-status/presentati
|
||||
import { getWorktreeGitIdentityDisplay } from '../../../../shared/worktree/git-identity-display'
|
||||
import { jumpToWorktreeFromSidebar } from '@/lib/worktree-jump-navigation'
|
||||
import { translate } from '@/i18n/i18n'
|
||||
import { useTranslate } from '@/i18n/use-translate'
|
||||
import { formatClipboardFilePath } from '@/lib/clipboard-file-path'
|
||||
import { getWorktreeRowLocalPathOpenOwner } from '@/lib/worktree-row-open-in-owner'
|
||||
import { cn } from '@/lib/utils'
|
||||
@@ -43,6 +44,7 @@ export function ActivityThreadHoverCardSummary({
|
||||
onJumpToWorkspace?: (event: React.MouseEvent) => void
|
||||
canJumpToWorkspace?: boolean
|
||||
}): React.JSX.Element {
|
||||
const t = useTranslate()
|
||||
const { worktree, repo } = thread
|
||||
const sshTargetLabels = useAppStore((s) => s.sshTargetLabels)
|
||||
const runtimeEnvironments = useAppStore((s) => s.runtimeEnvironments)
|
||||
@@ -69,12 +71,9 @@ export function ActivityThreadHoverCardSummary({
|
||||
return getExecutionHostLabel(executionHostId)
|
||||
}, [executionHostId, hostLabelOverrides, parsedHost, runtimeEnvironments, sshTargetLabels])
|
||||
const branchIdentityDisplay = useMemo(() => getWorktreeGitIdentityDisplay(worktree), [worktree])
|
||||
const { taskTitle, needsAttention } = activityThreadRowCopy(thread)
|
||||
const { taskTitle, needsAttention } = activityThreadRowCopy(thread, t)
|
||||
const workspaceTitle = getActivityThreadWorkspaceTitle(worktree)
|
||||
const copyPathLabel = translate(
|
||||
'auto.components.activity.ActivityThreadHoverCard.copyPath',
|
||||
'Copy path'
|
||||
)
|
||||
const copyPathLabel = t('auto.components.activity.ActivityThreadHoverCard.copyPath', 'Copy path')
|
||||
|
||||
const isKnownWorktree = useAppStore((s) =>
|
||||
Boolean(s.getKnownWorktreeById(worktree.id, executionHostId))
|
||||
@@ -144,7 +143,7 @@ export function ActivityThreadHoverCardSummary({
|
||||
needsAttention ? 'text-agent-question-text' : 'text-muted-foreground'
|
||||
)}
|
||||
>
|
||||
{threadAgentStateLabel(thread)}
|
||||
{threadAgentStateLabel(thread, t)}
|
||||
</span>
|
||||
<span className="text-muted-foreground/40">•</span>
|
||||
<EventTime timestamp={thread.latestTimestamp} compact />
|
||||
@@ -168,14 +167,11 @@ export function ActivityThreadHoverCardSummary({
|
||||
<WorktreeCardDetailSection>
|
||||
<DetailHeader
|
||||
icon={<FolderGit2 className="size-3 text-muted-foreground" />}
|
||||
label={translate(
|
||||
'auto.components.activity.ActivityThreadHoverCard.workspace',
|
||||
'Workspace'
|
||||
)}
|
||||
label={t('auto.components.activity.ActivityThreadHoverCard.workspace', 'Workspace')}
|
||||
actions={
|
||||
canJump ? (
|
||||
<MetadataActionIcon
|
||||
label={translate(
|
||||
label={t(
|
||||
'auto.components.activity.ActivityThreadHoverCard.jumpToWorkspace',
|
||||
'Jump to workspace'
|
||||
)}
|
||||
|
||||
@@ -2,7 +2,7 @@ import React, { useCallback } from 'react'
|
||||
import { Pencil, StickyNote } from 'lucide-react'
|
||||
import { HoverCardContent } from '@/components/ui/hover-card'
|
||||
import { SelectedTextCopyMenu } from '@/components/SelectedTextCopyMenu'
|
||||
import { translate } from '@/i18n/i18n'
|
||||
import { useTranslate } from '@/i18n/use-translate'
|
||||
import CommentMarkdown from '../sidebar/CommentMarkdown'
|
||||
import {
|
||||
DetailHeader,
|
||||
@@ -99,6 +99,7 @@ function ActivityThreadHoverCardContent({
|
||||
onJumpToWorkspace?: (thread: AgentPaneThread) => void
|
||||
canJumpToWorkspace?: boolean
|
||||
}): React.JSX.Element {
|
||||
const t = useTranslate()
|
||||
const { worktree, repo } = thread
|
||||
const foundation = useWorktreeCardFoundation({ worktree, repo: repo ?? undefined })
|
||||
const review = useWorktreeCardReviewDetails({
|
||||
@@ -260,13 +261,10 @@ function ActivityThreadHoverCardContent({
|
||||
<WorktreeCardDetailSection>
|
||||
<DetailHeader
|
||||
icon={<StickyNote className="size-3 text-muted-foreground" />}
|
||||
label={translate('auto.components.sidebar.WorktreeCardMeta.93cbea12c2', 'Notes')}
|
||||
label={t('auto.components.sidebar.WorktreeCardMeta.93cbea12c2', 'Notes')}
|
||||
actions={
|
||||
<MetadataActionIcon
|
||||
label={translate(
|
||||
'auto.components.sidebar.WorktreeCardMeta.c7fa72ead0',
|
||||
'Edit notes'
|
||||
)}
|
||||
label={t('auto.components.sidebar.WorktreeCardMeta.c7fa72ead0', 'Edit notes')}
|
||||
onClick={foundation.handleEditComment}
|
||||
>
|
||||
<Pencil className="size-3" />
|
||||
|
||||
@@ -7,7 +7,7 @@ import {
|
||||
type Range
|
||||
} from '@tanstack/react-virtual'
|
||||
import { cn } from '@/lib/utils'
|
||||
import { translate } from '@/i18n/i18n'
|
||||
import { useTranslate } from '@/i18n/use-translate'
|
||||
import { ActivityThreadListToolbar } from './activity-thread-list-toolbar'
|
||||
import {
|
||||
getActiveStickyHeaderIndex,
|
||||
@@ -118,6 +118,7 @@ export function ActivityThreadListPane({
|
||||
/** Optional view-local scroll memory; updated without triggering React renders. */
|
||||
scrollTopRef?: React.MutableRefObject<number>
|
||||
}): React.JSX.Element {
|
||||
const t = useTranslate()
|
||||
const [internalCollapsedGroupKeys, setInternalCollapsedGroupKeys] = useState<Set<string>>(
|
||||
() => new Set()
|
||||
)
|
||||
@@ -386,7 +387,7 @@ export function ActivityThreadListPane({
|
||||
</div>
|
||||
{visibleThreadCount === 0 ? (
|
||||
<div className="px-3 py-8 text-center text-xs text-muted-foreground">
|
||||
{translate(
|
||||
{t(
|
||||
'auto.components.activity.ActivityPrototypePage.7cd632006b',
|
||||
'No agent activity matches these filters.'
|
||||
)}
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
import type React from 'react'
|
||||
import { cn } from '@/lib/utils'
|
||||
import { translate } from '@/i18n/i18n'
|
||||
import { useTranslate } from '@/i18n/use-translate'
|
||||
|
||||
export function ActivityThreadListResizeHandle({
|
||||
isResizing,
|
||||
@@ -9,16 +9,14 @@ export function ActivityThreadListResizeHandle({
|
||||
isResizing?: boolean
|
||||
onResizeStart?: React.MouseEventHandler<HTMLDivElement>
|
||||
}): React.JSX.Element {
|
||||
const t = useTranslate()
|
||||
return (
|
||||
<div
|
||||
aria-label={translate(
|
||||
aria-label={t(
|
||||
'auto.components.activity.ActivityPrototypePage.443690186e',
|
||||
'Resize activity thread list'
|
||||
)}
|
||||
title={translate(
|
||||
'auto.components.activity.ActivityPrototypePage.866083500b',
|
||||
'Drag to resize'
|
||||
)}
|
||||
title={t('auto.components.activity.ActivityPrototypePage.866083500b', 'Drag to resize')}
|
||||
className={cn(
|
||||
'group absolute -right-1.5 top-0 z-20 flex h-full w-3 cursor-col-resize items-stretch justify-center',
|
||||
isResizing && 'bg-ring/10'
|
||||
|
||||
@@ -12,7 +12,7 @@ import {
|
||||
import { Toggle } from '@/components/ui/toggle'
|
||||
import { Tooltip, TooltipContent, TooltipTrigger } from '@/components/ui/tooltip'
|
||||
import { cn } from '@/lib/utils'
|
||||
import { translate } from '@/i18n/i18n'
|
||||
import { useTranslate } from '@/i18n/use-translate'
|
||||
import { ActivityThreadOptionsMenu } from './activity-thread-controls'
|
||||
import type {
|
||||
ActivityGroupBy,
|
||||
@@ -60,6 +60,7 @@ export function ActivityThreadListToolbar({
|
||||
showOptionsMenu: boolean
|
||||
showInlineActions?: boolean
|
||||
}): React.JSX.Element | null {
|
||||
const t = useTranslate()
|
||||
const showToolbar = showFilterControls || showOptionsMenu
|
||||
if (!showToolbar) {
|
||||
return null
|
||||
@@ -76,7 +77,7 @@ export function ActivityThreadListToolbar({
|
||||
ref={activityFilterInputRef}
|
||||
value={query}
|
||||
onChange={(event) => onQueryChange(event.target.value)}
|
||||
placeholder={translate(
|
||||
placeholder={t(
|
||||
'auto.components.activity.ActivityPrototypePage.795cbf26e2',
|
||||
'Filter...'
|
||||
)}
|
||||
@@ -91,7 +92,7 @@ export function ActivityThreadListToolbar({
|
||||
variant="ghost"
|
||||
size="icon-xs"
|
||||
className="absolute right-0.5 top-1/2 -translate-y-1/2 size-5 p-0 text-muted-foreground hover:text-foreground"
|
||||
aria-label={translate(
|
||||
aria-label={t(
|
||||
'auto.components.sidebar.WorkspaceKanbanSearchField.3b7ea51793',
|
||||
'Clear search'
|
||||
)}
|
||||
@@ -113,7 +114,7 @@ export function ActivityThreadListToolbar({
|
||||
<SelectTrigger
|
||||
size="sm"
|
||||
className="h-7 w-[116px] shrink-0 px-2 text-[11px]"
|
||||
aria-label={translate(
|
||||
aria-label={t(
|
||||
'auto.components.activity.ActivityPrototypePage.groupAgentActivityBy',
|
||||
'Group agent activity by'
|
||||
)}
|
||||
@@ -122,25 +123,19 @@ export function ActivityThreadListToolbar({
|
||||
</SelectTrigger>
|
||||
<SelectContent align="end">
|
||||
<SelectItem value="none">
|
||||
{translate('auto.components.activity.ActivityPrototypePage.none', 'None')}
|
||||
{t('auto.components.activity.ActivityPrototypePage.none', 'None')}
|
||||
</SelectItem>
|
||||
<SelectItem value="status">
|
||||
{translate('auto.components.activity.ActivityPrototypePage.4a3986b200', 'Status')}
|
||||
{t('auto.components.activity.ActivityPrototypePage.4a3986b200', 'Status')}
|
||||
</SelectItem>
|
||||
<SelectItem value="project">
|
||||
{translate(
|
||||
'auto.components.activity.ActivityPrototypePage.8c3b621ddf',
|
||||
'Project'
|
||||
)}
|
||||
{t('auto.components.activity.ActivityPrototypePage.8c3b621ddf', 'Project')}
|
||||
</SelectItem>
|
||||
<SelectItem value="worktree">
|
||||
{translate(
|
||||
'auto.components.activity.ActivityPrototypePage.b29191b3e0',
|
||||
'Worktree'
|
||||
)}
|
||||
{t('auto.components.activity.ActivityPrototypePage.b29191b3e0', 'Worktree')}
|
||||
</SelectItem>
|
||||
<SelectItem value="agent">
|
||||
{translate('auto.components.activity.ActivityPrototypePage.f6396e1f85', 'Agent')}
|
||||
{t('auto.components.activity.ActivityPrototypePage.f6396e1f85', 'Agent')}
|
||||
</SelectItem>
|
||||
</SelectContent>
|
||||
</Select>
|
||||
@@ -158,7 +153,7 @@ export function ActivityThreadListToolbar({
|
||||
? '!border border-primary/30 !bg-primary/10 !text-primary/90 shadow-xs hover:!bg-primary/15 hover:!text-primary'
|
||||
: 'text-muted-foreground hover:text-foreground hover:bg-muted/50'
|
||||
)}
|
||||
aria-label={translate(
|
||||
aria-label={t(
|
||||
'auto.components.activity.ActivityPrototypePage.d1a88df9a8',
|
||||
'Show unread threads only'
|
||||
)}
|
||||
@@ -167,7 +162,7 @@ export function ActivityThreadListToolbar({
|
||||
</Toggle>
|
||||
</TooltipTrigger>
|
||||
<TooltipContent side="bottom" sideOffset={6}>
|
||||
{translate(
|
||||
{t(
|
||||
'auto.components.activity.ActivityPrototypePage.d1a88df9a8',
|
||||
'Show unread threads only'
|
||||
)}
|
||||
@@ -203,10 +198,7 @@ export function ActivityThreadListToolbar({
|
||||
>
|
||||
<CheckCheck className="size-3.5" />
|
||||
<span>
|
||||
{translate(
|
||||
'auto.components.activity.ActivityPrototypePage.023ff75afe',
|
||||
'Mark all read'
|
||||
)}
|
||||
{t('auto.components.activity.ActivityPrototypePage.023ff75afe', 'Mark all read')}
|
||||
</span>
|
||||
</Button>
|
||||
) : null}
|
||||
@@ -221,7 +213,7 @@ export function ActivityThreadListToolbar({
|
||||
>
|
||||
<Trash2 className="size-3.5" />
|
||||
<span>
|
||||
{translate(
|
||||
{t(
|
||||
'auto.components.activity.ActivityPrototypePage.clearCompleted',
|
||||
'Clear completed'
|
||||
)}
|
||||
|
||||
@@ -17,6 +17,7 @@ import {
|
||||
} from '@/components/ui/dropdown-menu'
|
||||
import { Tooltip, TooltipContent, TooltipTrigger } from '@/components/ui/tooltip'
|
||||
import { translate } from '@/i18n/i18n'
|
||||
import { useTranslate, type Translate } from '@/i18n/use-translate'
|
||||
import { OptionsFilterCountBadge } from '@/components/sidebar/worktree-list/controls/options-filter-count-badge'
|
||||
import {
|
||||
ActivityScopeFilterMenuItems,
|
||||
@@ -32,18 +33,18 @@ const GROUP_BY_OPTIONS = [
|
||||
'agent'
|
||||
] as const satisfies readonly ActivityGroupBy[]
|
||||
|
||||
function getActivityGroupByLabel(groupBy: ActivityGroupBy): string {
|
||||
function getActivityGroupByLabel(groupBy: ActivityGroupBy, t: Translate = translate): string {
|
||||
switch (groupBy) {
|
||||
case 'none':
|
||||
return translate('auto.components.activity.ActivityPrototypePage.none', 'None')
|
||||
return t('auto.components.activity.ActivityPrototypePage.none', 'None')
|
||||
case 'status':
|
||||
return translate('auto.components.activity.ActivityPrototypePage.4a3986b200', 'Status')
|
||||
return t('auto.components.activity.ActivityPrototypePage.4a3986b200', 'Status')
|
||||
case 'project':
|
||||
return translate('auto.components.activity.ActivityPrototypePage.8c3b621ddf', 'Project')
|
||||
return t('auto.components.activity.ActivityPrototypePage.8c3b621ddf', 'Project')
|
||||
case 'worktree':
|
||||
return translate('auto.components.activity.ActivityPrototypePage.b29191b3e0', 'Worktree')
|
||||
return t('auto.components.activity.ActivityPrototypePage.b29191b3e0', 'Worktree')
|
||||
case 'agent':
|
||||
return translate('auto.components.activity.ActivityPrototypePage.f6396e1f85', 'Agent')
|
||||
return t('auto.components.activity.ActivityPrototypePage.f6396e1f85', 'Agent')
|
||||
}
|
||||
}
|
||||
|
||||
@@ -78,12 +79,13 @@ export function ActivityThreadOptionsMenu({
|
||||
unreadOnly?: boolean
|
||||
onUnreadOnlyChange?: (unreadOnly: boolean) => void
|
||||
}): React.JSX.Element {
|
||||
const t = useTranslate()
|
||||
const skipCloseAutoFocusRef = React.useRef(false)
|
||||
const activeFilterCount =
|
||||
useActivityScopeFilterCount() + (onUnreadOnlyChange && unreadOnly ? 1 : 0)
|
||||
const optionsLabel =
|
||||
activeFilterCount > 0
|
||||
? translate(
|
||||
? t(
|
||||
'auto.components.activity.ActivityPrototypePage.threadListOptionsActiveFilters',
|
||||
'Thread list options ({{count}} filters active)',
|
||||
{
|
||||
@@ -91,10 +93,7 @@ export function ActivityThreadOptionsMenu({
|
||||
defaultValue_one: 'Thread list options ({{count}} filter active)'
|
||||
}
|
||||
)
|
||||
: translate(
|
||||
'auto.components.activity.ActivityPrototypePage.db8a1878b5',
|
||||
'Thread list options'
|
||||
)
|
||||
: t('auto.components.activity.ActivityPrototypePage.db8a1878b5', 'Thread list options')
|
||||
|
||||
return (
|
||||
<DropdownMenu>
|
||||
@@ -115,7 +114,7 @@ export function ActivityThreadOptionsMenu({
|
||||
</TooltipTrigger>
|
||||
<TooltipContent side="bottom" sideOffset={6}>
|
||||
{activeFilterCount > 0
|
||||
? translate(
|
||||
? t(
|
||||
'auto.components.activity.ActivityPrototypePage.activityOptionsFilterCount',
|
||||
'Activity options ({{count}} filters)',
|
||||
{
|
||||
@@ -123,7 +122,7 @@ export function ActivityThreadOptionsMenu({
|
||||
defaultValue_one: 'Activity options ({{count}} filter)'
|
||||
}
|
||||
)
|
||||
: translate(
|
||||
: t(
|
||||
'auto.components.activity.ActivityPrototypePage.activityOptions',
|
||||
'Activity options'
|
||||
)}
|
||||
@@ -142,7 +141,7 @@ export function ActivityThreadOptionsMenu({
|
||||
}}
|
||||
>
|
||||
<DropdownMenuLabel>
|
||||
{translate('auto.components.activity.ActivityPrototypePage.filtersSection', 'Filters')}
|
||||
{t('auto.components.activity.ActivityPrototypePage.filtersSection', 'Filters')}
|
||||
</DropdownMenuLabel>
|
||||
{onUnreadOnlyChange ? (
|
||||
<DropdownMenuCheckboxItem
|
||||
@@ -150,10 +149,7 @@ export function ActivityThreadOptionsMenu({
|
||||
onCheckedChange={(checked) => onUnreadOnlyChange(checked === true)}
|
||||
onSelect={(event) => event.preventDefault()}
|
||||
>
|
||||
{translate(
|
||||
'auto.components.activity.ActivityPrototypePage.showUnreadOnly',
|
||||
'Show unread only'
|
||||
)}
|
||||
{t('auto.components.activity.ActivityPrototypePage.showUnreadOnly', 'Show unread only')}
|
||||
</DropdownMenuCheckboxItem>
|
||||
) : null}
|
||||
{onShowChildAgentsChange ? (
|
||||
@@ -162,7 +158,7 @@ export function ActivityThreadOptionsMenu({
|
||||
onCheckedChange={(checked) => onShowChildAgentsChange(checked === true)}
|
||||
onSelect={(event) => event.preventDefault()}
|
||||
>
|
||||
{translate(
|
||||
{t(
|
||||
'auto.components.activity.ActivityPrototypePage.showChildAgents',
|
||||
'Show child agents'
|
||||
)}
|
||||
@@ -171,20 +167,17 @@ export function ActivityThreadOptionsMenu({
|
||||
<ActivityScopeFilterMenuItems />
|
||||
<DropdownMenuSeparator />
|
||||
<DropdownMenuLabel>
|
||||
{translate('auto.components.activity.ActivityPrototypePage.viewSection', 'View')}
|
||||
{t('auto.components.activity.ActivityPrototypePage.viewSection', 'View')}
|
||||
</DropdownMenuLabel>
|
||||
{groupBy && onGroupByChange ? (
|
||||
<DropdownMenuSub>
|
||||
<DropdownMenuSubTrigger>
|
||||
<span className="flex flex-1 items-center justify-between gap-3">
|
||||
<span>
|
||||
{translate(
|
||||
'auto.components.activity.ActivityPrototypePage.770d458144',
|
||||
'Group by'
|
||||
)}
|
||||
{t('auto.components.activity.ActivityPrototypePage.770d458144', 'Group by')}
|
||||
</span>
|
||||
<span className="min-w-0 truncate text-[11px] font-medium text-muted-foreground">
|
||||
{getActivityGroupByLabel(groupBy)}
|
||||
{getActivityGroupByLabel(groupBy, t)}
|
||||
</span>
|
||||
</span>
|
||||
</DropdownMenuSubTrigger>
|
||||
@@ -199,7 +192,7 @@ export function ActivityThreadOptionsMenu({
|
||||
value={value}
|
||||
onSelect={(event) => event.preventDefault()}
|
||||
>
|
||||
{getActivityGroupByLabel(value)}
|
||||
{getActivityGroupByLabel(value, t)}
|
||||
</DropdownMenuRadioItem>
|
||||
))}
|
||||
</DropdownMenuRadioGroup>
|
||||
@@ -211,7 +204,7 @@ export function ActivityThreadOptionsMenu({
|
||||
onCheckedChange={(checked) => onCompactModeChange(checked === true)}
|
||||
onSelect={(event) => event.preventDefault()}
|
||||
>
|
||||
{translate('auto.components.activity.ActivityPrototypePage.f70e4bec47', 'Compact mode')}
|
||||
{t('auto.components.activity.ActivityPrototypePage.f70e4bec47', 'Compact mode')}
|
||||
</DropdownMenuCheckboxItem>
|
||||
{onShowSearchChange ? (
|
||||
<DropdownMenuCheckboxItem
|
||||
@@ -222,7 +215,7 @@ export function ActivityThreadOptionsMenu({
|
||||
onShowSearchChange(show)
|
||||
}}
|
||||
>
|
||||
{translate('auto.components.activity.ActivityPrototypePage.showSearch', 'Show search')}
|
||||
{t('auto.components.activity.ActivityPrototypePage.showSearch', 'Show search')}
|
||||
</DropdownMenuCheckboxItem>
|
||||
) : null}
|
||||
{onMarkAllThreadsRead || onClearCompleted ? (
|
||||
@@ -232,10 +225,7 @@ export function ActivityThreadOptionsMenu({
|
||||
<DropdownMenuItem onSelect={onMarkAllThreadsRead} disabled={!hasUnreadThreads}>
|
||||
<CheckCheck className="size-3.5 text-muted-foreground" />
|
||||
<span>
|
||||
{translate(
|
||||
'auto.components.activity.ActivityPrototypePage.023ff75afe',
|
||||
'Mark all read'
|
||||
)}
|
||||
{t('auto.components.activity.ActivityPrototypePage.023ff75afe', 'Mark all read')}
|
||||
</span>
|
||||
</DropdownMenuItem>
|
||||
) : null}
|
||||
@@ -243,7 +233,7 @@ export function ActivityThreadOptionsMenu({
|
||||
<DropdownMenuItem onSelect={onClearCompleted} disabled={!hasCompletedThreads}>
|
||||
<Trash2 className="size-3.5 text-muted-foreground" />
|
||||
<span>
|
||||
{translate(
|
||||
{t(
|
||||
'auto.components.activity.ActivityPrototypePage.clearCompleted',
|
||||
'Clear completed'
|
||||
)}
|
||||
|
||||
@@ -13,6 +13,7 @@ import {
|
||||
} from '@/lib/activity-thread-display'
|
||||
import { formatUiRelativeTime } from '@/i18n/relative-time-format'
|
||||
import { translate } from '@/i18n/i18n'
|
||||
import type { Translate } from '@/i18n/use-translate'
|
||||
import type {
|
||||
AgentStatusEntry,
|
||||
AgentStatusState
|
||||
@@ -36,12 +37,16 @@ const ABSOLUTE_DATE_OPTIONS: Intl.DateTimeFormatOptions = {
|
||||
minute: '2-digit'
|
||||
}
|
||||
|
||||
export function formatAbsoluteDate(timestamp: number): string {
|
||||
return uiDateTimeFormat(ABSOLUTE_DATE_OPTIONS).format(new Date(timestamp))
|
||||
export function formatAbsoluteDate(timestamp: number, intlLocale?: string): string {
|
||||
return uiDateTimeFormat(ABSOLUTE_DATE_OPTIONS, intlLocale).format(new Date(timestamp))
|
||||
}
|
||||
|
||||
export function formatRelativeTime(timestamp: number, now = Date.now()): string {
|
||||
return formatUiRelativeTime(timestamp - now)
|
||||
export function formatRelativeTime(
|
||||
timestamp: number,
|
||||
now = Date.now(),
|
||||
intlLocale?: string
|
||||
): string {
|
||||
return formatUiRelativeTime(timestamp - now, intlLocale)
|
||||
}
|
||||
|
||||
function truncatePreservingSurrogates(value: string, maxLength: number): string {
|
||||
@@ -189,46 +194,40 @@ function threadCurrentState(
|
||||
)
|
||||
}
|
||||
|
||||
export function threadAgentStateLabel(thread: AgentPaneThread): string {
|
||||
export function threadAgentStateLabel(thread: AgentPaneThread, t: Translate = translate): string {
|
||||
// Literal translation keys/fallbacks register catalog references and avoid bundling every state string at boot.
|
||||
switch (activityThreadStatusId(thread)) {
|
||||
case 'working':
|
||||
return translate('auto.components.activity.ActivityPrototypePage.state.working', 'Working')
|
||||
return t('auto.components.activity.ActivityPrototypePage.state.working', 'Working')
|
||||
case 'monitoring':
|
||||
return translate(
|
||||
return t(
|
||||
'auto.components.activity.ActivityPrototypePage.state.monitoring',
|
||||
'Monitoring background tasks'
|
||||
)
|
||||
case 'blocked':
|
||||
return translate('auto.components.activity.ActivityPrototypePage.state.blocked', 'Blocked')
|
||||
return t('auto.components.activity.ActivityPrototypePage.state.blocked', 'Blocked')
|
||||
case 'waiting':
|
||||
return translate(
|
||||
'auto.components.activity.ActivityPrototypePage.state.waiting',
|
||||
'Waiting for input'
|
||||
)
|
||||
return t('auto.components.activity.ActivityPrototypePage.state.waiting', 'Waiting for input')
|
||||
case 'interrupted':
|
||||
return translate('auto.components.activity.ActivityPrototypePage.interrupted', 'Interrupted')
|
||||
return t('auto.components.activity.ActivityPrototypePage.interrupted', 'Interrupted')
|
||||
case 'failed':
|
||||
return translate('auto.components.activity.ActivityPrototypePage.state.failed', 'Failed')
|
||||
return t('auto.components.activity.ActivityPrototypePage.state.failed', 'Failed')
|
||||
case 'done':
|
||||
return translate('auto.components.activity.ActivityPrototypePage.state.done', 'Done')
|
||||
return t('auto.components.activity.ActivityPrototypePage.state.done', 'Done')
|
||||
case 'idle':
|
||||
return translate('auto.components.activity.ActivityPrototypePage.state.idle', 'Idle')
|
||||
return t('auto.components.activity.ActivityPrototypePage.state.idle', 'Idle')
|
||||
case 'unverifiable':
|
||||
return translate(
|
||||
return t(
|
||||
'auto.components.activity.ActivityPrototypePage.state.unverifiable',
|
||||
'No recent update'
|
||||
)
|
||||
case 'unconfirmed':
|
||||
return translate(
|
||||
return t(
|
||||
'auto.components.activity.ActivityPrototypePage.state.unconfirmed',
|
||||
'Couldn’t confirm'
|
||||
)
|
||||
case 'permission':
|
||||
return translate(
|
||||
'auto.components.activity.ActivityPrototypePage.state.permission',
|
||||
'Needs attention'
|
||||
)
|
||||
return t('auto.components.activity.ActivityPrototypePage.state.permission', 'Needs attention')
|
||||
}
|
||||
}
|
||||
|
||||
@@ -254,7 +253,10 @@ function previewDuplicatesIdentity(preview: string, title: string, workspace: st
|
||||
return normalized === normalizeScanLabel(title) || normalized === normalizeScanLabel(workspace)
|
||||
}
|
||||
|
||||
export function activityThreadRowCopy(thread: AgentPaneThread): ActivityThreadRowCopy {
|
||||
export function activityThreadRowCopy(
|
||||
thread: AgentPaneThread,
|
||||
t?: Translate
|
||||
): ActivityThreadRowCopy {
|
||||
const workspaceLabel = getActivityThreadWorkspaceTitle(thread.worktree)
|
||||
const taskTitle = thread.paneTitle.trim() || workspaceLabel
|
||||
const renderedPreview = activityThreadResponseRenderPreview({
|
||||
@@ -277,7 +279,7 @@ export function activityThreadRowCopy(thread: AgentPaneThread): ActivityThreadRo
|
||||
if (state !== 'done' && state !== 'idle') {
|
||||
return {
|
||||
taskTitle,
|
||||
statusLine: threadAgentStateLabel(thread),
|
||||
statusLine: threadAgentStateLabel(thread, t),
|
||||
statusKind: 'state',
|
||||
needsAttention,
|
||||
workspaceLabel
|
||||
|
||||
@@ -5,7 +5,7 @@ import { formatAgentTypeLabel } from '../../../../shared/agent-status/presentati
|
||||
import { Button } from '@/components/ui/button'
|
||||
import { Tooltip, TooltipContent, TooltipProvider, TooltipTrigger } from '@/components/ui/tooltip'
|
||||
import { cn } from '@/lib/utils'
|
||||
import { translate } from '@/i18n/i18n'
|
||||
import { useTranslate } from '@/i18n/use-translate'
|
||||
import { useUiLanguageRevision } from '@/i18n/use-ui-language-revision'
|
||||
import '../sidebar/workspace-card/activity/worktree-card-empty-inputs'
|
||||
import { FilledBellIcon } from '../sidebar/workspace-card/identity/worktree-card-glyphs'
|
||||
@@ -86,9 +86,10 @@ export const ActivityThreadRow = React.memo(function ActivityThreadRow({
|
||||
showJumpAction?: boolean
|
||||
previewSuppressed?: boolean
|
||||
}): React.JSX.Element {
|
||||
const t = useTranslate()
|
||||
useUiLanguageRevision()
|
||||
const { taskTitle, statusLine, statusKind, needsAttention, workspaceLabel } =
|
||||
activityThreadRowCopy(thread)
|
||||
activityThreadRowCopy(thread, t)
|
||||
const showMarkdownStatus = statusKind === 'message'
|
||||
const agentLabel = formatAgentTypeLabel(thread.agentType)
|
||||
|
||||
@@ -176,7 +177,7 @@ export const ActivityThreadRow = React.memo(function ActivityThreadRow({
|
||||
<TooltipProvider delayDuration={800} skipDelayDuration={0}>
|
||||
{canJump && showJumpAction ? (
|
||||
<ActivityThreadRowAction
|
||||
label={translate(
|
||||
label={t(
|
||||
'auto.components.activity.ActivityPrototypePage.4616ea39fd',
|
||||
'Jump to workspace'
|
||||
)}
|
||||
@@ -187,7 +188,7 @@ export const ActivityThreadRow = React.memo(function ActivityThreadRow({
|
||||
) : null}
|
||||
{onClear ? (
|
||||
<ActivityThreadRowAction
|
||||
label={translate(
|
||||
label={t(
|
||||
'auto.components.activity.ActivityThreadRow.clearNotification',
|
||||
'Clear notification'
|
||||
)}
|
||||
@@ -212,7 +213,7 @@ export const ActivityThreadRow = React.memo(function ActivityThreadRow({
|
||||
}}
|
||||
onMouseDown={(event) => event.stopPropagation()}
|
||||
className="flex size-4 shrink-0 cursor-pointer items-center justify-center rounded hover:bg-accent/80 focus-visible:outline-none focus-visible:ring-1 focus-visible:ring-ring"
|
||||
aria-label={translate(
|
||||
aria-label={t(
|
||||
'auto.components.activity.ActivityPrototypePage.markThreadRead',
|
||||
'Mark thread as read'
|
||||
)}
|
||||
@@ -224,7 +225,7 @@ export const ActivityThreadRow = React.memo(function ActivityThreadRow({
|
||||
</button>
|
||||
</TooltipTrigger>
|
||||
<TooltipContent side="left">
|
||||
{translate(
|
||||
{t(
|
||||
'auto.components.activity.ActivityPrototypePage.markThreadRead',
|
||||
'Mark thread as read'
|
||||
)}
|
||||
@@ -247,7 +248,7 @@ export const ActivityThreadRow = React.memo(function ActivityThreadRow({
|
||||
'hover:bg-accent/80 active:scale-95',
|
||||
'focus-visible:opacity-100 focus-visible:outline-none focus-visible:ring-1 focus-visible:ring-ring'
|
||||
)}
|
||||
aria-label={translate(
|
||||
aria-label={t(
|
||||
'auto.components.activity.ActivityPrototypePage.59b131fbd9',
|
||||
'Mark thread unread'
|
||||
)}
|
||||
@@ -256,7 +257,7 @@ export const ActivityThreadRow = React.memo(function ActivityThreadRow({
|
||||
</button>
|
||||
</TooltipTrigger>
|
||||
<TooltipContent side="left">
|
||||
{translate(
|
||||
{t(
|
||||
'auto.components.activity.ActivityPrototypePage.59b131fbd9',
|
||||
'Mark thread unread'
|
||||
)}
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
import type React from 'react'
|
||||
import { translate } from '@/i18n/i18n'
|
||||
import { useTranslate } from '@/i18n/use-translate'
|
||||
import { clearActivityThread, isClearableActivityThread } from './activity-clear-completed'
|
||||
import { ActivityStatusGroupHeader } from './activity-thread-controls'
|
||||
import { ActivityThreadContextMenu } from './activity-thread-context-menu'
|
||||
@@ -46,11 +46,12 @@ export function ActivityThreadVirtualRow({
|
||||
compactMode: boolean
|
||||
showJumpAction: boolean
|
||||
}): React.JSX.Element {
|
||||
const t = useTranslate()
|
||||
if (item.type === 'header') {
|
||||
return (
|
||||
<div
|
||||
role="group"
|
||||
aria-label={translate(
|
||||
aria-label={t(
|
||||
'auto.components.activity.ActivityPrototypePage.a2b4437bfb',
|
||||
'{{value0}} activity',
|
||||
{ value0: item.group.label }
|
||||
|
||||
@@ -7,6 +7,7 @@ import type { AgentChildRowModel } from '../../../shared/agent-status/presentati
|
||||
import { agentStateLabel } from '@/components/AgentStateDot'
|
||||
import { sayBackgroundTaskTranslated } from '@/components/native-chat/tools/tasks/background-task-words-text'
|
||||
import { translate } from '@/i18n/i18n'
|
||||
import type { Translate } from '@/i18n/use-translate'
|
||||
import { agentNoUpdateLabel } from '../../../shared/agent-status/presentation/agent-row-decay-state'
|
||||
|
||||
export type AgentChildRowText = {
|
||||
@@ -32,11 +33,14 @@ export function agentChildRowDetailText(row: AgentChildRowModel, now: number): s
|
||||
}
|
||||
|
||||
/** The line beneath a full-width row: what the child said, or that it ended; '' otherwise. */
|
||||
export function agentChildRowMessageLine(row: AgentChildRowModel): string {
|
||||
export function agentChildRowMessageLine(
|
||||
row: AgentChildRowModel,
|
||||
t: Translate = translate
|
||||
): string {
|
||||
if (row.detail?.kind === 'message') {
|
||||
return row.detail.text
|
||||
}
|
||||
return row.detail?.kind === 'ended' ? translate('components.agentChildRow.ended', 'Ended') : ''
|
||||
return row.detail?.kind === 'ended' ? t('components.agentChildRow.ended', 'Ended') : ''
|
||||
}
|
||||
|
||||
/** The row's name, or its state when the child reported none. */
|
||||
|
||||
+23
-28
@@ -28,7 +28,7 @@ import {
|
||||
SelectTrigger,
|
||||
SelectValue
|
||||
} from '@/components/ui/select'
|
||||
import { translate } from '@/i18n/i18n'
|
||||
import { useTranslate } from '@/i18n/use-translate'
|
||||
import { getAgentCatalog, getAgentLabel } from '@/lib/agent-catalog'
|
||||
import {
|
||||
buildAgentSessionContinuationPrompt,
|
||||
@@ -63,6 +63,7 @@ export function AgentSessionContinuationDialog({
|
||||
onOpenChange,
|
||||
onStarted
|
||||
}: AgentSessionContinuationDialogProps): React.JSX.Element {
|
||||
const t = useTranslate()
|
||||
const workspace = request ? worktreeLaunchWorkspace(request.worktreeId) : null
|
||||
const abortRef = useRef<AbortController | null>(null)
|
||||
const settings = useAppStore((state) => state.settings)
|
||||
@@ -241,13 +242,10 @@ export function AgentSessionContinuationDialog({
|
||||
<DialogHeader>
|
||||
<DialogTitle className="flex items-center gap-2 text-sm">
|
||||
<MessageSquarePlus className="size-4" />
|
||||
{translate(
|
||||
'components.agentSessionContinuation.dialogTitle',
|
||||
'Hand Off to Another Agent'
|
||||
)}
|
||||
{t('components.agentSessionContinuation.dialogTitle', 'Hand Off to Another Agent')}
|
||||
</DialogTitle>
|
||||
<DialogDescription className="text-xs">
|
||||
{translate(
|
||||
{t(
|
||||
'components.agentSessionContinuation.dialogDescription',
|
||||
'Start a fresh Agent session from this stopping point. The original session stays unchanged.'
|
||||
)}
|
||||
@@ -258,11 +256,11 @@ export function AgentSessionContinuationDialog({
|
||||
<div className="min-w-0 rounded-md border border-border bg-muted/30 px-3 py-2">
|
||||
<div className="truncate text-xs font-medium">
|
||||
{sourceName ||
|
||||
translate('components.agentSessionContinuation.untitledSession', 'Current session')}
|
||||
t('components.agentSessionContinuation.untitledSession', 'Current session')}
|
||||
</div>
|
||||
<div className="mt-0.5 min-h-4 text-[11px] text-muted-foreground">
|
||||
{sourceAgentLabel
|
||||
? translate(
|
||||
? t(
|
||||
'components.agentSessionContinuation.originalAgent',
|
||||
'Original Agent: {{agent}}',
|
||||
{ agent: sourceAgentLabel }
|
||||
@@ -273,7 +271,7 @@ export function AgentSessionContinuationDialog({
|
||||
|
||||
<div className="min-w-0 space-y-1.5">
|
||||
<label className="text-xs font-medium">
|
||||
{translate('components.agentSessionContinuation.agent', 'Agent')}
|
||||
{t('components.agentSessionContinuation.agent', 'Agent')}
|
||||
</label>
|
||||
<AgentLaunchPicker
|
||||
draftScope={draftScope}
|
||||
@@ -290,21 +288,21 @@ export function AgentSessionContinuationDialog({
|
||||
<div className="min-h-4">
|
||||
{preparing ? (
|
||||
<p className="text-[11px] text-muted-foreground">
|
||||
{translate(
|
||||
{t(
|
||||
'components.agentSessionContinuation.preparingContext',
|
||||
'Preparing saved chat context…'
|
||||
)}
|
||||
</p>
|
||||
) : detecting ? (
|
||||
<p className="text-[11px] text-muted-foreground">
|
||||
{translate(
|
||||
{t(
|
||||
'components.agentSessionContinuation.detectingAgents',
|
||||
'Detecting Agents on this workspace host…'
|
||||
)}
|
||||
</p>
|
||||
) : detectionFailed ? (
|
||||
<p className="text-[11px] text-destructive">
|
||||
{translate(
|
||||
{t(
|
||||
'components.agentSessionContinuation.detectionFailed',
|
||||
'Could not detect Agents on this workspace host.'
|
||||
)}
|
||||
@@ -312,12 +310,12 @@ export function AgentSessionContinuationDialog({
|
||||
) : destinationModel.state === 'settled' && rows.length === 0 ? (
|
||||
<p className="text-[11px] text-muted-foreground">
|
||||
{onlySourceFamilyOffered && sourceAgentLabel
|
||||
? translate(
|
||||
? t(
|
||||
'components.agentSessionContinuation.onlySourceFamilyDetected',
|
||||
'Only Agents sharing the usage limit with {{agent}} are available on this workspace host. Enable or install a different Agent.',
|
||||
{ agent: sourceAgentLabel }
|
||||
)
|
||||
: translate(
|
||||
: t(
|
||||
'components.agentSessionContinuation.noAgents',
|
||||
'No enabled Agents were detected on this workspace host.'
|
||||
)}
|
||||
@@ -328,7 +326,7 @@ export function AgentSessionContinuationDialog({
|
||||
|
||||
<div className="min-w-0 space-y-1.5">
|
||||
<label className="text-xs font-medium">
|
||||
{translate('components.agentSessionContinuation.context', 'Context')}
|
||||
{t('components.agentSessionContinuation.context', 'Context')}
|
||||
</label>
|
||||
<Select
|
||||
disabled={controlsDisabled}
|
||||
@@ -344,16 +342,13 @@ export function AgentSessionContinuationDialog({
|
||||
</SelectTrigger>
|
||||
<SelectContent>
|
||||
<SelectItem value="focused">
|
||||
{translate(
|
||||
{t(
|
||||
'components.agentSessionContinuation.modeFocused',
|
||||
'Focused handoff (Recommended)'
|
||||
)}
|
||||
</SelectItem>
|
||||
<SelectItem value="full" disabled={!hasFullContext}>
|
||||
{translate(
|
||||
'components.agentSessionContinuation.modeFull',
|
||||
'Full session transcript'
|
||||
)}
|
||||
{t('components.agentSessionContinuation.modeFull', 'Full session transcript')}
|
||||
</SelectItem>
|
||||
</SelectContent>
|
||||
</Select>
|
||||
@@ -362,20 +357,20 @@ export function AgentSessionContinuationDialog({
|
||||
? null
|
||||
: !hasFullContext
|
||||
? request?.source.sourceKind === 'chat'
|
||||
? translate(
|
||||
? t(
|
||||
'components.agentSessionContinuation.modeLoadedDescription',
|
||||
'Uses recent messages currently loaded in this chat and the current workspace.'
|
||||
)
|
||||
: translate(
|
||||
: t(
|
||||
'components.agentSessionContinuation.modeCapturedDescription',
|
||||
'Uses captured text and the current workspace.'
|
||||
)
|
||||
: contextMode === 'focused'
|
||||
? translate(
|
||||
? t(
|
||||
'components.agentSessionContinuation.modeFocusedDescription',
|
||||
'Uses the latest status and current workspace, reading older transcript details only when needed.'
|
||||
)
|
||||
: translate(
|
||||
: t(
|
||||
'components.agentSessionContinuation.modeFullDescription',
|
||||
'Asks the new Agent to read the complete saved session before continuing. This can take longer and use significant context, plan usage, or API credits.'
|
||||
)}
|
||||
@@ -385,7 +380,7 @@ export function AgentSessionContinuationDialog({
|
||||
<div className="min-h-4 text-[11px] text-muted-foreground">
|
||||
{workingDirectory ? (
|
||||
<>
|
||||
{translate('components.agentSessionContinuation.startsIn', 'Starts in:')}{' '}
|
||||
{t('components.agentSessionContinuation.startsIn', 'Starts in:')}{' '}
|
||||
<span className="break-all font-mono text-foreground/80">{workingDirectory}</span>
|
||||
</>
|
||||
) : null}
|
||||
@@ -394,7 +389,7 @@ export function AgentSessionContinuationDialog({
|
||||
|
||||
<DialogFooter>
|
||||
<Button type="button" variant="ghost" onClick={() => onOpenChange(false)}>
|
||||
{translate('components.native-chat.question.cancel', 'Cancel')}
|
||||
{t('components.native-chat.question.cancel', 'Cancel')}
|
||||
</Button>
|
||||
<Button
|
||||
type="button"
|
||||
@@ -404,8 +399,8 @@ export function AgentSessionContinuationDialog({
|
||||
>
|
||||
{showStarting ? <Loader2 className="size-3.5 animate-spin" /> : null}
|
||||
{starting
|
||||
? translate('components.agentSessionContinuation.starting', 'Starting…')
|
||||
: translate('components.agentSessionContinuation.startSession', 'Start New Session')}
|
||||
? t('components.agentSessionContinuation.starting', 'Starting…')
|
||||
: t('components.agentSessionContinuation.startSession', 'Start New Session')}
|
||||
</Button>
|
||||
</DialogFooter>
|
||||
</DialogContent>
|
||||
|
||||
+3
-5
@@ -1,6 +1,6 @@
|
||||
import { MessageSquarePlus } from 'lucide-react'
|
||||
import { DropdownMenuItem } from '@/components/ui/dropdown-menu'
|
||||
import { translate } from '@/i18n/i18n'
|
||||
import { useTranslate } from '@/i18n/use-translate'
|
||||
|
||||
type AgentSessionContinuationMenuItemProps = {
|
||||
disabled?: boolean
|
||||
@@ -11,13 +11,11 @@ export function AgentSessionContinuationMenuItem({
|
||||
onSelect,
|
||||
disabled = false
|
||||
}: AgentSessionContinuationMenuItemProps): React.JSX.Element {
|
||||
const t = useTranslate()
|
||||
return (
|
||||
<DropdownMenuItem disabled={disabled} onSelect={onSelect}>
|
||||
<MessageSquarePlus />
|
||||
{translate(
|
||||
'components.agentSessionContinuation.handOffToAnotherAgent',
|
||||
'Hand Off to Another Agent'
|
||||
)}
|
||||
{t('components.agentSessionContinuation.handOffToAnotherAgent', 'Hand Off to Another Agent')}
|
||||
</DropdownMenuItem>
|
||||
)
|
||||
}
|
||||
|
||||
@@ -8,7 +8,7 @@ import {
|
||||
DropdownMenuTrigger
|
||||
} from '@/components/ui/dropdown-menu'
|
||||
import { Tooltip, TooltipContent, TooltipTrigger } from '@/components/ui/tooltip'
|
||||
import { translate } from '@/i18n/i18n'
|
||||
import { useTranslate } from '@/i18n/use-translate'
|
||||
import { copyArtifactLink, openArtifactInBrowser } from './artifact-link-actions'
|
||||
|
||||
type ArtifactActionsProps = {
|
||||
@@ -22,14 +22,15 @@ export function ArtifactActions({
|
||||
item,
|
||||
onDelete
|
||||
}: ArtifactActionsProps): React.JSX.Element {
|
||||
const t = useTranslate()
|
||||
return (
|
||||
<div
|
||||
className="flex shrink-0 items-center gap-2"
|
||||
aria-label={translate('auto.components.artifacts.actions', 'Artifact actions')}
|
||||
aria-label={t('auto.components.artifacts.actions', 'Artifact actions')}
|
||||
>
|
||||
<Button size="sm" onClick={() => void copyArtifactLink(item.shareUrl)}>
|
||||
<Copy />
|
||||
{translate('auto.components.artifacts.copyLink', 'Copy link')}
|
||||
{t('auto.components.artifacts.copyLink', 'Copy link')}
|
||||
</Button>
|
||||
<Tooltip>
|
||||
<TooltipTrigger asChild>
|
||||
@@ -38,13 +39,13 @@ export function ArtifactActions({
|
||||
size="icon-sm"
|
||||
className="text-muted-foreground hover:text-foreground"
|
||||
onClick={() => openArtifactInBrowser(item.shareUrl)}
|
||||
aria-label={translate('auto.components.artifacts.openInBrowser', 'Open in browser')}
|
||||
aria-label={t('auto.components.artifacts.openInBrowser', 'Open in browser')}
|
||||
>
|
||||
<ExternalLink />
|
||||
</Button>
|
||||
</TooltipTrigger>
|
||||
<TooltipContent side="bottom" sideOffset={6}>
|
||||
{translate('auto.components.artifacts.openInBrowser', 'Open in browser')}
|
||||
{t('auto.components.artifacts.openInBrowser', 'Open in browser')}
|
||||
</TooltipContent>
|
||||
</Tooltip>
|
||||
<DropdownMenu>
|
||||
@@ -55,7 +56,7 @@ export function ArtifactActions({
|
||||
size="icon-sm"
|
||||
className="text-muted-foreground hover:text-foreground"
|
||||
disabled={deleting}
|
||||
aria-label={translate(
|
||||
aria-label={t(
|
||||
'auto.components.artifacts.ArtifactActions.more',
|
||||
'More artifact actions'
|
||||
)}
|
||||
@@ -70,7 +71,7 @@ export function ArtifactActions({
|
||||
onSelect={() => onDelete(item)}
|
||||
>
|
||||
<Trash2 className="size-3.5" />
|
||||
{translate('auto.components.artifacts.ArtifactsPage.deleteArtifact', 'Delete artifact')}
|
||||
{t('auto.components.artifacts.ArtifactsPage.deleteArtifact', 'Delete artifact')}
|
||||
</DropdownMenuItem>
|
||||
</DropdownMenuContent>
|
||||
</DropdownMenu>
|
||||
|
||||
@@ -2,7 +2,7 @@ import { useMemo, useState } from 'react'
|
||||
import { Loader2 } from 'lucide-react'
|
||||
import type { ArtifactListItem } from '../../../../shared/artifacts'
|
||||
import { Button } from '@/components/ui/button'
|
||||
import { translate } from '@/i18n/i18n'
|
||||
import { useTranslate } from '@/i18n/use-translate'
|
||||
import { cn } from '@/lib/utils'
|
||||
import { clampArtifactListSearchQuery, filterArtifactsBySearchQuery } from './artifact-list-search'
|
||||
import { ArtifactListRows } from './ArtifactListRows'
|
||||
@@ -33,6 +33,7 @@ export function ArtifactCollection({
|
||||
onRefresh: () => void
|
||||
isRefreshing: boolean
|
||||
}): React.JSX.Element {
|
||||
const t = useTranslate()
|
||||
const [query, setQuery] = useState('')
|
||||
// Clamp before state/filtering so multi-MB pastes cannot reach either.
|
||||
const onQueryChange = (next: string): void => setQuery(clampArtifactListSearchQuery(next))
|
||||
@@ -67,7 +68,7 @@ export function ArtifactCollection({
|
||||
/>
|
||||
) : (
|
||||
<p className="px-3 py-6 text-center text-sm text-muted-foreground">
|
||||
{translate('auto.components.artifacts.ArtifactCollection.noMatches', 'No matches')}
|
||||
{t('auto.components.artifacts.ArtifactCollection.noMatches', 'No matches')}
|
||||
</p>
|
||||
)}
|
||||
{hasMore ? (
|
||||
@@ -81,7 +82,7 @@ export function ArtifactCollection({
|
||||
onClick={loadMore}
|
||||
>
|
||||
{loadingMore ? <Loader2 className="animate-spin" /> : null}
|
||||
{translate('auto.components.artifacts.ArtifactCollection.loadMore', 'Load more')}
|
||||
{t('auto.components.artifacts.ArtifactCollection.loadMore', 'Load more')}
|
||||
</Button>
|
||||
</div>
|
||||
) : null}
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
import * as VisuallyHidden from 'radix-ui/visually-hidden'
|
||||
import type { ArtifactListItem } from '../../../../shared/artifacts'
|
||||
import { Sheet, SheetContent, SheetDescription, SheetTitle } from '@/components/ui/sheet'
|
||||
import { translate } from '@/i18n/i18n'
|
||||
import { useTranslate } from '@/i18n/use-translate'
|
||||
import { artifactName } from './artifact-display-labels'
|
||||
import { ArtifactDetailHeader } from './ArtifactDetailHeader'
|
||||
import { ArtifactPreview } from './ArtifactPreview'
|
||||
@@ -17,6 +17,7 @@ export function ArtifactDetailDrawer({
|
||||
onClose: () => void
|
||||
onDelete: (item: ArtifactListItem) => void
|
||||
}): React.JSX.Element {
|
||||
const t = useTranslate()
|
||||
return (
|
||||
<Sheet open={item !== null} onOpenChange={(open) => !open && onClose()}>
|
||||
<SheetContent
|
||||
@@ -29,7 +30,7 @@ export function ArtifactDetailDrawer({
|
||||
<div className="flex h-full min-h-0 flex-col">
|
||||
<VisuallyHidden.Root asChild>
|
||||
<SheetDescription>
|
||||
{translate(
|
||||
{t(
|
||||
'auto.components.artifacts.ArtifactDetailDrawer.description',
|
||||
'Preview and manage this shared artifact.'
|
||||
)}
|
||||
|
||||
@@ -3,7 +3,7 @@ import { Globe, X } from 'lucide-react'
|
||||
import type { ArtifactListItem } from '../../../../shared/artifacts'
|
||||
import { Button } from '@/components/ui/button'
|
||||
import { Tooltip, TooltipContent, TooltipTrigger } from '@/components/ui/tooltip'
|
||||
import { translate } from '@/i18n/i18n'
|
||||
import { useIntlLocale, useTranslate } from '@/i18n/use-translate'
|
||||
import { ArtifactActions } from './ArtifactActions'
|
||||
import {
|
||||
formatArtifactExpiry,
|
||||
@@ -24,6 +24,8 @@ export function ArtifactDetailHeader({
|
||||
onClose: () => void
|
||||
onDelete: (target: ArtifactListItem) => void
|
||||
}): React.JSX.Element {
|
||||
const t = useTranslate()
|
||||
const intlLocale = useIntlLocale()
|
||||
return (
|
||||
// Inset actions below the fixed Windows/Linux controls overlay so they stay clickable.
|
||||
<div className="flex flex-wrap items-start justify-between gap-3 border-b border-border/50 px-4 py-3 pr-[max(1rem,var(--window-controls-width,0px))]">
|
||||
@@ -36,14 +38,14 @@ export function ArtifactDetailHeader({
|
||||
<Globe className="size-3 shrink-0 text-muted-foreground" />
|
||||
</TooltipTrigger>
|
||||
<TooltipContent side="bottom" sideOffset={6}>
|
||||
{translate(
|
||||
{t(
|
||||
'auto.components.artifacts.ArtifactDetailHeader.publicLink',
|
||||
'Anyone with this link can view it'
|
||||
)}
|
||||
</TooltipContent>
|
||||
</Tooltip>
|
||||
<span className="sr-only">
|
||||
{translate(
|
||||
{t(
|
||||
'auto.components.artifacts.ArtifactDetailHeader.publicLink',
|
||||
'Anyone with this link can view it'
|
||||
)}
|
||||
@@ -51,8 +53,9 @@ export function ArtifactDetailHeader({
|
||||
<p className="truncate font-mono text-xs text-muted-foreground">{item.shareUrl}</p>
|
||||
</div>
|
||||
<p className="truncate text-[11px] text-muted-foreground">
|
||||
{formatArtifactUpdatedAt(item.artifact.updatedAt)} ·{' '}
|
||||
{formatByteSize(item.artifact.byteSize)} · {formatArtifactExpiry(item.artifact.expiresAt)}
|
||||
{formatArtifactUpdatedAt(item.artifact.updatedAt, t, intlLocale)} ·{' '}
|
||||
{formatByteSize(item.artifact.byteSize)} ·{' '}
|
||||
{formatArtifactExpiry(item.artifact.expiresAt, t, intlLocale)}
|
||||
</p>
|
||||
</div>
|
||||
<div className="flex shrink-0 items-center gap-2">
|
||||
@@ -62,7 +65,7 @@ export function ArtifactDetailHeader({
|
||||
variant="ghost"
|
||||
size="icon-sm"
|
||||
onClick={onClose}
|
||||
aria-label={translate('auto.components.artifacts.ArtifactDetailHeader.close', 'Close')}
|
||||
aria-label={t('auto.components.artifacts.ArtifactDetailHeader.close', 'Close')}
|
||||
>
|
||||
<X />
|
||||
</Button>
|
||||
|
||||
@@ -18,6 +18,7 @@ import {
|
||||
DropdownMenuTrigger
|
||||
} from '@/components/ui/dropdown-menu'
|
||||
import { translate } from '@/i18n/i18n'
|
||||
import { useIntlLocale, useTranslate, type Translate } from '@/i18n/use-translate'
|
||||
import { cn } from '@/lib/utils'
|
||||
import { isPortaledRowMenuClick, isRowActivationKey } from '@/lib/list-row-interaction'
|
||||
import {
|
||||
@@ -49,24 +50,25 @@ type ArtifactRowAction = {
|
||||
function artifactRowActions(
|
||||
item: ArtifactListItem,
|
||||
deleting: boolean,
|
||||
deleteArtifact: (item: ArtifactListItem) => void
|
||||
deleteArtifact: (item: ArtifactListItem) => void,
|
||||
t: Translate = translate
|
||||
): readonly ArtifactRowAction[] {
|
||||
return [
|
||||
{
|
||||
key: 'copy',
|
||||
label: translate('auto.components.artifacts.copyLink', 'Copy link'),
|
||||
label: t('auto.components.artifacts.copyLink', 'Copy link'),
|
||||
icon: Copy,
|
||||
onSelect: () => void copyArtifactLink(item.shareUrl)
|
||||
},
|
||||
{
|
||||
key: 'open',
|
||||
label: translate('auto.components.artifacts.openInBrowser', 'Open in browser'),
|
||||
label: t('auto.components.artifacts.openInBrowser', 'Open in browser'),
|
||||
icon: ExternalLink,
|
||||
onSelect: () => openArtifactInBrowser(item.shareUrl)
|
||||
},
|
||||
{
|
||||
key: 'delete',
|
||||
label: translate('auto.components.artifacts.ArtifactsPage.deleteArtifact', 'Delete artifact'),
|
||||
label: t('auto.components.artifacts.ArtifactsPage.deleteArtifact', 'Delete artifact'),
|
||||
icon: Trash2,
|
||||
onSelect: () => deleteArtifact(item),
|
||||
destructive: true,
|
||||
@@ -91,12 +93,14 @@ export function ArtifactListRow({
|
||||
selectArtifact: (slug: string) => void
|
||||
deleteArtifact: (item: ArtifactListItem) => void
|
||||
}): React.JSX.Element {
|
||||
const t = useTranslate()
|
||||
const intlLocale = useIntlLocale()
|
||||
const name = artifactName(item)
|
||||
const typeLabel = artifactTypeLabel(item)
|
||||
const updatedLabel = formatArtifactUpdatedCompact(item.artifact.updatedAt)
|
||||
const expiryLabel = formatArtifactExpiryCompact(item.artifact.expiresAt)
|
||||
const typeLabel = artifactTypeLabel(item, t)
|
||||
const updatedLabel = formatArtifactUpdatedCompact(item.artifact.updatedAt, t, intlLocale)
|
||||
const expiryLabel = formatArtifactExpiryCompact(item.artifact.expiresAt, t, intlLocale)
|
||||
const sizeLabel = formatByteSize(item.artifact.byteSize)
|
||||
const rowActions = artifactRowActions(item, deleting, deleteArtifact)
|
||||
const rowActions = artifactRowActions(item, deleting, deleteArtifact, t)
|
||||
|
||||
return (
|
||||
// Non-modal, so scrolling is never blocked: windowing unmounting an open menu mid-scroll reads as dismissal.
|
||||
@@ -148,7 +152,7 @@ export function ArtifactListRow({
|
||||
variant="ghost"
|
||||
size="icon-xs"
|
||||
className="size-7 text-muted-foreground"
|
||||
aria-label={translate('auto.components.artifacts.actions', 'Artifact actions')}
|
||||
aria-label={t('auto.components.artifacts.actions', 'Artifact actions')}
|
||||
onClick={(event) => event.stopPropagation()}
|
||||
>
|
||||
<MoreHorizontal className="size-4" />
|
||||
|
||||
@@ -2,7 +2,7 @@ import { useRef } from 'react'
|
||||
import { Search, X } from 'lucide-react'
|
||||
import { Button } from '@/components/ui/button'
|
||||
import { Input } from '@/components/ui/input'
|
||||
import { translate } from '@/i18n/i18n'
|
||||
import { useTranslate } from '@/i18n/use-translate'
|
||||
import { cn } from '@/lib/utils'
|
||||
|
||||
export function ArtifactListSearchField({
|
||||
@@ -16,6 +16,7 @@ export function ArtifactListSearchField({
|
||||
onClear: () => void
|
||||
className?: string
|
||||
}): React.JSX.Element {
|
||||
const t = useTranslate()
|
||||
const inputRef = useRef<HTMLInputElement>(null)
|
||||
const hasText = query !== ''
|
||||
|
||||
@@ -27,11 +28,11 @@ export function ArtifactListSearchField({
|
||||
type="text"
|
||||
autoFocus
|
||||
value={query}
|
||||
aria-label={translate(
|
||||
aria-label={t(
|
||||
'auto.components.artifacts.ArtifactListSearchField.label',
|
||||
'Search artifacts'
|
||||
)}
|
||||
placeholder={translate(
|
||||
placeholder={t(
|
||||
'auto.components.artifacts.ArtifactListSearchField.placeholder',
|
||||
'Search...'
|
||||
)}
|
||||
@@ -56,10 +57,7 @@ export function ArtifactListSearchField({
|
||||
variant="ghost"
|
||||
size="icon-xs"
|
||||
className="absolute right-1 top-1/2 -translate-y-1/2"
|
||||
aria-label={translate(
|
||||
'auto.components.artifacts.ArtifactListSearchField.clear',
|
||||
'Clear search'
|
||||
)}
|
||||
aria-label={t('auto.components.artifacts.ArtifactListSearchField.clear', 'Clear search')}
|
||||
onMouseDown={(event) => event.preventDefault()}
|
||||
onClick={() => {
|
||||
onClear()
|
||||
|
||||
@@ -1,22 +1,19 @@
|
||||
import { cn } from '@/lib/utils'
|
||||
import { translate } from '@/i18n/i18n'
|
||||
import { useTranslate } from '@/i18n/use-translate'
|
||||
import { ARTIFACTS_TABLE_GRID_CLASS } from './artifacts-table-layout'
|
||||
import { LIST_TABLE_HEADER_CLASS } from '@/lib/list-table-layout'
|
||||
|
||||
export function ArtifactListTableHeader(): React.JSX.Element {
|
||||
const t = useTranslate()
|
||||
return (
|
||||
<div className={cn(ARTIFACTS_TABLE_GRID_CLASS, LIST_TABLE_HEADER_CLASS)}>
|
||||
<span>{translate('auto.components.artifacts.ArtifactListTableHeader.name', 'Name')}</span>
|
||||
<span>{translate('auto.components.artifacts.ArtifactListTableHeader.type', 'Type')}</span>
|
||||
<span>{translate('auto.components.artifacts.ArtifactListTableHeader.size', 'Size')}</span>
|
||||
<span>
|
||||
{translate('auto.components.artifacts.ArtifactListTableHeader.updated', 'Updated')}
|
||||
</span>
|
||||
<span>
|
||||
{translate('auto.components.artifacts.ArtifactListTableHeader.expires', 'Expires')}
|
||||
</span>
|
||||
<span>{t('auto.components.artifacts.ArtifactListTableHeader.name', 'Name')}</span>
|
||||
<span>{t('auto.components.artifacts.ArtifactListTableHeader.type', 'Type')}</span>
|
||||
<span>{t('auto.components.artifacts.ArtifactListTableHeader.size', 'Size')}</span>
|
||||
<span>{t('auto.components.artifacts.ArtifactListTableHeader.updated', 'Updated')}</span>
|
||||
<span>{t('auto.components.artifacts.ArtifactListTableHeader.expires', 'Expires')}</span>
|
||||
<span className="sr-only">
|
||||
{translate('auto.components.artifacts.ArtifactListTableHeader.actions', 'Actions')}
|
||||
{t('auto.components.artifacts.ArtifactListTableHeader.actions', 'Actions')}
|
||||
</span>
|
||||
</div>
|
||||
)
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
import { RefreshCw } from 'lucide-react'
|
||||
import { Button } from '@/components/ui/button'
|
||||
import { Tooltip, TooltipContent, TooltipTrigger } from '@/components/ui/tooltip'
|
||||
import { translate } from '@/i18n/i18n'
|
||||
import { useTranslate } from '@/i18n/use-translate'
|
||||
import { cn } from '@/lib/utils'
|
||||
import { ArtifactListSearchField } from './ArtifactListSearchField'
|
||||
|
||||
@@ -16,6 +16,7 @@ export function ArtifactListToolbar({
|
||||
onRefresh: () => void
|
||||
isRefreshing: boolean
|
||||
}): React.JSX.Element {
|
||||
const t = useTranslate()
|
||||
return (
|
||||
<div className="flex shrink-0 items-center gap-2">
|
||||
<ArtifactListSearchField
|
||||
@@ -30,7 +31,7 @@ export function ArtifactListToolbar({
|
||||
type="button"
|
||||
variant="ghost"
|
||||
size="icon-sm"
|
||||
aria-label={translate('auto.components.artifacts.ArtifactsPage.refresh', 'Refresh')}
|
||||
aria-label={t('auto.components.artifacts.ArtifactsPage.refresh', 'Refresh')}
|
||||
onClick={onRefresh}
|
||||
disabled={isRefreshing}
|
||||
className="shrink-0 border border-border bg-background shadow-none hover:bg-muted/50"
|
||||
@@ -39,7 +40,7 @@ export function ArtifactListToolbar({
|
||||
</Button>
|
||||
</TooltipTrigger>
|
||||
<TooltipContent side="bottom" sideOffset={6}>
|
||||
{translate('auto.components.artifacts.ArtifactsPage.refresh', 'Refresh')}
|
||||
{t('auto.components.artifacts.ArtifactsPage.refresh', 'Refresh')}
|
||||
</TooltipContent>
|
||||
</Tooltip>
|
||||
</div>
|
||||
|
||||
@@ -3,6 +3,7 @@ import { AlertCircle, Loader2 } from 'lucide-react'
|
||||
import { ORCA_BROWSER_GUEST_WEB_PREFERENCES_ATTRIBUTE } from '../../../../shared/browser-guest-web-preferences'
|
||||
import { moveFocusToRendererBeforeWebviewDetach } from '@/components/browser-pane/host-guest/webview-registry'
|
||||
import { translate } from '@/i18n/i18n'
|
||||
import { useTranslate } from '@/i18n/use-translate'
|
||||
|
||||
type PreviewState = 'loading' | 'ready' | 'unavailable'
|
||||
const ARTIFACT_PREVIEW_LOAD_TIMEOUT_MS = 20_000
|
||||
@@ -61,6 +62,7 @@ function attachArtifactWebview({
|
||||
}
|
||||
|
||||
export function ArtifactPreview({ shareUrl }: { shareUrl: string }): React.JSX.Element {
|
||||
const t = useTranslate()
|
||||
const containerRef = useRef<HTMLDivElement>(null)
|
||||
const [state, setState] = useState<PreviewState>('loading')
|
||||
|
||||
@@ -151,10 +153,10 @@ export function ArtifactPreview({ shareUrl }: { shareUrl: string }): React.JSX.E
|
||||
<div className="absolute inset-0 z-10 flex flex-col items-center justify-center gap-2 bg-editor-surface px-6 text-center">
|
||||
<AlertCircle className="size-6 text-muted-foreground" />
|
||||
<p className="text-sm font-medium">
|
||||
{translate('auto.components.artifacts.previewUnavailable', 'Preview unavailable')}
|
||||
{t('auto.components.artifacts.previewUnavailable', 'Preview unavailable')}
|
||||
</p>
|
||||
<p className="max-w-sm text-xs text-muted-foreground">
|
||||
{translate(
|
||||
{t(
|
||||
'auto.components.artifacts.previewUnavailableDescription',
|
||||
'Open this artifact in your browser to view it.'
|
||||
)}
|
||||
|
||||
@@ -4,7 +4,7 @@ import type { ArtifactWriteRequest } from '../../../../shared/artifacts'
|
||||
import { Button } from '@/components/ui/button'
|
||||
import { Popover, PopoverAnchor, PopoverContent, PopoverTrigger } from '@/components/ui/popover'
|
||||
import { Tooltip, TooltipContent, TooltipTrigger } from '@/components/ui/tooltip'
|
||||
import { translate } from '@/i18n/i18n'
|
||||
import { useTranslate } from '@/i18n/use-translate'
|
||||
import { cn } from '@/lib/utils'
|
||||
import { useAppStore } from '@/store'
|
||||
import { ArtifactPublishedLinkPanel } from './ArtifactPublishedLinkPanel'
|
||||
@@ -44,6 +44,7 @@ export function ArtifactPublishButton({
|
||||
onOpenChange: (open: boolean) => void
|
||||
}
|
||||
)): React.JSX.Element {
|
||||
const t = useTranslate()
|
||||
const [ownOpen, setOwnOpen] = useState(false)
|
||||
const open = controlledOpen ?? ownOpen
|
||||
const setOpen = onOpenChange ?? setOwnOpen
|
||||
@@ -122,10 +123,7 @@ export function ArtifactPublishButton({
|
||||
openSettingsPage()
|
||||
}
|
||||
|
||||
const label = translate(
|
||||
'auto.components.artifacts.ArtifactPublishButton.a4a49da6af',
|
||||
'Share as artifact'
|
||||
)
|
||||
const label = t('auto.components.artifacts.ArtifactPublishButton.a4a49da6af', 'Share as artifact')
|
||||
return (
|
||||
<Popover open={open} onOpenChange={(nextOpen) => !busy && setOpen(nextOpen)}>
|
||||
{anchorRef ? (
|
||||
@@ -175,18 +173,15 @@ export function ArtifactPublishButton({
|
||||
>
|
||||
<div className="space-y-1 border-b border-border/60 px-4 py-3.5">
|
||||
<h3 className="text-sm font-semibold">
|
||||
{translate(
|
||||
'auto.components.artifacts.ArtifactPublishButton.confirmTitle',
|
||||
'Share as artifact'
|
||||
)}
|
||||
{t('auto.components.artifacts.ArtifactPublishButton.confirmTitle', 'Share as artifact')}
|
||||
</h3>
|
||||
<p className="text-xs leading-5 text-muted-foreground">
|
||||
{publishedLink
|
||||
? translate(
|
||||
? t(
|
||||
'auto.components.artifacts.ArtifactPublishButton.publishedDescription',
|
||||
'Anyone with this link can view the shared file.'
|
||||
)
|
||||
: translate(
|
||||
: t(
|
||||
'auto.components.artifacts.ArtifactPublishButton.confirmDescription',
|
||||
'This publishes the current file at a link anyone with the URL can view.'
|
||||
)}
|
||||
@@ -198,13 +193,13 @@ export function ArtifactPublishButton({
|
||||
<div className="flex items-center justify-between gap-3">
|
||||
<div className="min-w-0 space-y-0.5">
|
||||
<p className="text-xs font-medium">
|
||||
{translate(
|
||||
{t(
|
||||
'auto.components.artifacts.ArtifactPublishButton.accountTitle',
|
||||
'Orca account'
|
||||
)}
|
||||
</p>
|
||||
<p className="text-[11px] leading-4 text-muted-foreground">
|
||||
{translate(
|
||||
{t(
|
||||
'auto.components.artifacts.ArtifactPublishButton.accountDescription',
|
||||
'Sign in to create and manage this link.'
|
||||
)}
|
||||
@@ -216,11 +211,11 @@ export function ArtifactPublishButton({
|
||||
disabled={authStatus?.configured !== true}
|
||||
>
|
||||
{authStatus?.state === 'reconnect-required'
|
||||
? translate(
|
||||
? t(
|
||||
'auto.components.artifacts.ArtifactPublishButton.signInAgain',
|
||||
'Sign in again'
|
||||
)
|
||||
: translate('auto.components.artifacts.ArtifactPublishButton.signIn', 'Sign in')}
|
||||
: t('auto.components.artifacts.ArtifactPublishButton.signIn', 'Sign in')}
|
||||
</OrcaSignInButton>
|
||||
</div>
|
||||
) : null}
|
||||
@@ -230,13 +225,13 @@ export function ArtifactPublishButton({
|
||||
<div className={cn('space-y-2', !signedIn && 'border-t border-border/60 pt-3')}>
|
||||
<div className="space-y-0.5">
|
||||
<p className="text-xs font-medium">
|
||||
{translate(
|
||||
{t(
|
||||
'auto.components.artifacts.ArtifactPublishButton.publishingOffTitle',
|
||||
'Artifact sharing is off'
|
||||
)}
|
||||
</p>
|
||||
<p className="text-[11px] leading-4 text-muted-foreground">
|
||||
{translate(
|
||||
{t(
|
||||
'auto.components.artifacts.ArtifactPublishButton.publishingOffDescription',
|
||||
'Learn about public links and enable sharing in Settings.'
|
||||
)}
|
||||
@@ -249,7 +244,7 @@ export function ArtifactPublishButton({
|
||||
className="w-full"
|
||||
onClick={openArtifactsSettings}
|
||||
>
|
||||
{translate(
|
||||
{t(
|
||||
'auto.components.artifacts.ArtifactPublishButton.openSettings',
|
||||
'Open Artifacts settings'
|
||||
)}
|
||||
@@ -261,7 +256,7 @@ export function ArtifactPublishButton({
|
||||
{checkingLink ? (
|
||||
<div className="flex items-center justify-center gap-2 py-2 text-xs text-muted-foreground">
|
||||
<Loader2 className="size-3.5 animate-spin" />
|
||||
{translate(
|
||||
{t(
|
||||
'auto.components.artifacts.ArtifactPublishButton.checkingLink',
|
||||
'Checking for an existing link…'
|
||||
)}
|
||||
@@ -269,7 +264,7 @@ export function ArtifactPublishButton({
|
||||
) : currentLookup?.status === 'error' ? (
|
||||
<div className="space-y-2">
|
||||
<p className="text-xs leading-5 text-muted-foreground">
|
||||
{translate(
|
||||
{t(
|
||||
'auto.components.artifacts.ArtifactPublishButton.checkFailed',
|
||||
'Could not check for an existing link.'
|
||||
)}
|
||||
@@ -281,7 +276,7 @@ export function ArtifactPublishButton({
|
||||
className="w-full"
|
||||
onClick={() => setLookupRevision((current) => current + 1)}
|
||||
>
|
||||
{translate('auto.components.artifacts.ArtifactPublishButton.tryAgain', 'Try again')}
|
||||
{t('auto.components.artifacts.ArtifactPublishButton.tryAgain', 'Try again')}
|
||||
</Button>
|
||||
</div>
|
||||
) : publishedLink ? (
|
||||
@@ -302,8 +297,8 @@ export function ArtifactPublishButton({
|
||||
>
|
||||
{publishing ? <Loader2 className="animate-spin" /> : <Share2 />}
|
||||
{publishing
|
||||
? translate('auto.components.artifacts.ArtifactPublishButton.sharing', 'Sharing…')
|
||||
: translate(
|
||||
? t('auto.components.artifacts.ArtifactPublishButton.sharing', 'Sharing…')
|
||||
: t(
|
||||
'auto.components.artifacts.ArtifactPublishButton.sharePublicLink',
|
||||
'Generate link'
|
||||
)}
|
||||
|
||||
@@ -2,7 +2,7 @@ import { useCallback, useRef, useState } from 'react'
|
||||
import { Check, Copy, ExternalLink, Loader2, RefreshCw } from 'lucide-react'
|
||||
import { Button } from '@/components/ui/button'
|
||||
import { Tooltip, TooltipContent, TooltipTrigger } from '@/components/ui/tooltip'
|
||||
import { translate } from '@/i18n/i18n'
|
||||
import { useTranslate } from '@/i18n/use-translate'
|
||||
import { copyArtifactLink, openArtifactInBrowser } from './artifact-link-actions'
|
||||
|
||||
export function ArtifactPublishedLinkPanel({
|
||||
@@ -16,6 +16,7 @@ export function ArtifactPublishedLinkPanel({
|
||||
sharingEnabled: boolean
|
||||
onUpdate: () => void
|
||||
}): React.JSX.Element {
|
||||
const t = useTranslate()
|
||||
const [copied, setCopied] = useState(false)
|
||||
const copiedResetTimerRef = useRef<number | null>(null)
|
||||
const mountedRef = useRef(false)
|
||||
@@ -52,8 +53,8 @@ export function ArtifactPublishedLinkPanel({
|
||||
}
|
||||
|
||||
const copyLabel = copied
|
||||
? translate('auto.components.artifacts.copySuccess', 'Artifact link copied')
|
||||
: translate('auto.components.artifacts.ArtifactPublishedLinkPanel.copyLink', 'Copy link')
|
||||
? t('auto.components.artifacts.copySuccess', 'Artifact link copied')
|
||||
: t('auto.components.artifacts.ArtifactPublishedLinkPanel.copyLink', 'Copy link')
|
||||
|
||||
return (
|
||||
<div ref={setPanelRef} className="space-y-3">
|
||||
@@ -89,7 +90,7 @@ export function ArtifactPublishedLinkPanel({
|
||||
size="icon-xs"
|
||||
className="text-muted-foreground hover:text-foreground"
|
||||
onClick={() => openArtifactInBrowser(shareUrl)}
|
||||
aria-label={translate(
|
||||
aria-label={t(
|
||||
'auto.components.artifacts.ArtifactPublishedLinkPanel.openLink',
|
||||
'Open link'
|
||||
)}
|
||||
@@ -98,10 +99,7 @@ export function ArtifactPublishedLinkPanel({
|
||||
</Button>
|
||||
</TooltipTrigger>
|
||||
<TooltipContent side="bottom" sideOffset={4}>
|
||||
{translate(
|
||||
'auto.components.artifacts.ArtifactPublishedLinkPanel.openLink',
|
||||
'Open link'
|
||||
)}
|
||||
{t('auto.components.artifacts.ArtifactPublishedLinkPanel.openLink', 'Open link')}
|
||||
</TooltipContent>
|
||||
</Tooltip>
|
||||
</div>
|
||||
@@ -117,11 +115,8 @@ export function ArtifactPublishedLinkPanel({
|
||||
>
|
||||
{publishing ? <Loader2 className="animate-spin" /> : <RefreshCw />}
|
||||
{publishing
|
||||
? translate(
|
||||
'auto.components.artifacts.ArtifactPublishedLinkPanel.updating',
|
||||
'Updating…'
|
||||
)
|
||||
: translate(
|
||||
? t('auto.components.artifacts.ArtifactPublishedLinkPanel.updating', 'Updating…')
|
||||
: t(
|
||||
'auto.components.artifacts.ArtifactPublishedLinkPanel.update',
|
||||
'Update shared content'
|
||||
)}
|
||||
|
||||
@@ -4,7 +4,7 @@ import { useConfirmationDialog } from '@/components/confirmation-dialog-context'
|
||||
import { persistConfirmationSkipPreference } from '@/components/confirmation-skip-preference'
|
||||
import { callRuntimeRpc } from '@/runtime/rpc/calls/runtime-rpc-call'
|
||||
import { useAppStore } from '@/store'
|
||||
import { translate } from '@/i18n/i18n'
|
||||
import { useTranslate } from '@/i18n/use-translate'
|
||||
import { ArtifactCollection } from './ArtifactCollection'
|
||||
import { ArtifactDetailDrawer } from './ArtifactDetailDrawer'
|
||||
import { ArtifactsPageSkeleton } from './ArtifactsPageSkeleton'
|
||||
@@ -18,6 +18,7 @@ import { artifactAccountIdentity, useArtifactPagination } from './useArtifactPag
|
||||
const LOCAL_RUNTIME = { kind: 'local' } as const
|
||||
|
||||
export default function ArtifactsPage(): React.JSX.Element {
|
||||
const t = useTranslate()
|
||||
const closePage = useAppStore((state) => state.closeArtifactsPage)
|
||||
const authStatus = useAppStore((state) => state.orcaProfileAuthStatus)
|
||||
const refreshAuth = useAppStore((state) => state.refreshCurrentOrcaProfileAuth)
|
||||
@@ -97,13 +98,13 @@ export default function ArtifactsPage(): React.JSX.Element {
|
||||
const name = item.artifact.title || item.artifact.originalFileName || item.artifact.slug
|
||||
if (!settings?.skipDeleteArtifactConfirm) {
|
||||
const accepted = await confirm({
|
||||
title: translate('auto.components.artifacts.ArtifactsPage.deleteTitle', 'Delete artifact?'),
|
||||
description: translate(
|
||||
title: t('auto.components.artifacts.ArtifactsPage.deleteTitle', 'Delete artifact?'),
|
||||
description: t(
|
||||
'auto.components.artifacts.ArtifactsPage.deleteDescription',
|
||||
'“{{name}}” will no longer be available at its public link.',
|
||||
{ name }
|
||||
),
|
||||
confirmLabel: translate('auto.components.artifacts.ArtifactsPage.delete', 'Delete'),
|
||||
confirmLabel: t('auto.components.artifacts.ArtifactsPage.delete', 'Delete'),
|
||||
confirmVariant: 'destructive',
|
||||
dontAskAgain: {
|
||||
onConfirmed: () =>
|
||||
@@ -148,7 +149,7 @@ export default function ArtifactsPage(): React.JSX.Element {
|
||||
console.error('Failed to delete artifact:', deleteError)
|
||||
if (requestedAccountIsCurrent()) {
|
||||
setError(
|
||||
translate(
|
||||
t(
|
||||
'auto.components.artifacts.ArtifactsPage.deleteFailed',
|
||||
'Could not delete the artifact.'
|
||||
)
|
||||
@@ -175,7 +176,7 @@ export default function ArtifactsPage(): React.JSX.Element {
|
||||
}
|
||||
>
|
||||
<h1 className="truncate text-base font-semibold leading-8">
|
||||
{translate('auto.components.artifacts.ArtifactsPage.title', 'Artifacts')}
|
||||
{t('auto.components.artifacts.ArtifactsPage.title', 'Artifacts')}
|
||||
</h1>
|
||||
</header>
|
||||
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
import { cn } from '@/lib/utils'
|
||||
import { translate } from '@/i18n/i18n'
|
||||
import { useTranslate } from '@/i18n/use-translate'
|
||||
import { ARTIFACTS_TABLE_GRID_CLASS } from './artifacts-table-layout'
|
||||
import { LIST_TABLE_CONTAINER_CLASS, LIST_TABLE_HEADER_CLASS } from '@/lib/list-table-layout'
|
||||
|
||||
@@ -17,16 +17,14 @@ const TABLE_ROW_SKELETONS = [
|
||||
] as const
|
||||
|
||||
export function ArtifactsPageSkeleton(): React.JSX.Element {
|
||||
const t = useTranslate()
|
||||
return (
|
||||
<div
|
||||
className="flex min-h-0 flex-1 flex-col gap-4 overflow-hidden px-3 pb-4 md:px-5"
|
||||
// Why: aria-label on a roleless div is not exposed to screen readers.
|
||||
role="status"
|
||||
aria-busy="true"
|
||||
aria-label={translate(
|
||||
'auto.components.artifacts.ArtifactsPageSkeleton.loading',
|
||||
'Loading artifacts'
|
||||
)}
|
||||
aria-label={t('auto.components.artifacts.ArtifactsPageSkeleton.loading', 'Loading artifacts')}
|
||||
>
|
||||
<div className="flex shrink-0 items-center gap-2">
|
||||
<SkeletonBar className="h-8 w-56 shrink-0 rounded-md" />
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
import { ArrowRight, Files, Loader2 } from 'lucide-react'
|
||||
import { Button } from '@/components/ui/button'
|
||||
import { translate } from '@/i18n/i18n'
|
||||
import { useTranslate } from '@/i18n/use-translate'
|
||||
import { OrcaSignInButton, OrcaSignInWaitNotice } from '../OrcaSignInProgress'
|
||||
|
||||
export function ArtifactsPageErrorBanner({
|
||||
@@ -12,11 +12,12 @@ export function ArtifactsPageErrorBanner({
|
||||
loading: boolean
|
||||
onRetry: () => void
|
||||
}): React.JSX.Element {
|
||||
const t = useTranslate()
|
||||
return (
|
||||
<div className="flex flex-wrap items-center justify-between gap-3 border-b border-destructive/30 bg-destructive/10 px-3 py-2 md:px-5">
|
||||
<p className="min-w-0 flex-1 text-xs text-destructive">{error}</p>
|
||||
<Button type="button" variant="outline" size="xs" disabled={loading} onClick={onRetry}>
|
||||
{translate('auto.components.artifacts.ArtifactsPage.retry', 'Retry')}
|
||||
{t('auto.components.artifacts.ArtifactsPage.retry', 'Retry')}
|
||||
</Button>
|
||||
</div>
|
||||
)
|
||||
@@ -31,28 +32,26 @@ export function ArtifactsPageAuthState({
|
||||
configured: boolean
|
||||
onOpenAccountSettings: () => void
|
||||
}): React.JSX.Element {
|
||||
const t = useTranslate()
|
||||
return (
|
||||
<div className="flex min-h-72 flex-1 flex-col items-center justify-center gap-3 px-5 py-5 text-center md:px-8">
|
||||
<Files className="size-8 text-muted-foreground" />
|
||||
<div className="space-y-1">
|
||||
<h2 className="text-sm font-semibold">
|
||||
{needsReconnect
|
||||
? translate(
|
||||
'auto.components.artifacts.ArtifactsPage.reconnectHeading',
|
||||
'Sign in to Orca again'
|
||||
)
|
||||
: translate(
|
||||
? t('auto.components.artifacts.ArtifactsPage.reconnectHeading', 'Sign in to Orca again')
|
||||
: t(
|
||||
'auto.components.artifacts.ArtifactsPage.shareSignInHeading',
|
||||
'Sign in to share artifacts'
|
||||
)}
|
||||
</h2>
|
||||
<p className="max-w-sm text-xs leading-5 text-muted-foreground">
|
||||
{needsReconnect
|
||||
? translate(
|
||||
? t(
|
||||
'auto.components.artifacts.ArtifactsPage.reconnectCopy',
|
||||
'Sign in again to view and manage the artifacts shared through your account.'
|
||||
)
|
||||
: translate(
|
||||
: t(
|
||||
'auto.components.artifacts.ArtifactsPage.shareSignInCopy',
|
||||
'Use your Orca account to upload artifacts and manage their public links.'
|
||||
)}
|
||||
@@ -62,24 +61,21 @@ export function ArtifactsPageAuthState({
|
||||
<>
|
||||
<OrcaSignInButton size="sm">
|
||||
{needsReconnect
|
||||
? translate(
|
||||
'auto.components.artifacts.ArtifactsPage.signInAgainAction',
|
||||
'Sign in again'
|
||||
)
|
||||
: translate('auto.components.artifacts.ArtifactsPage.signIn', 'Sign in to Orca')}
|
||||
? t('auto.components.artifacts.ArtifactsPage.signInAgainAction', 'Sign in again')
|
||||
: t('auto.components.artifacts.ArtifactsPage.signIn', 'Sign in to Orca')}
|
||||
</OrcaSignInButton>
|
||||
<OrcaSignInWaitNotice className="justify-center" />
|
||||
</>
|
||||
) : (
|
||||
<div className="flex flex-col items-center gap-2">
|
||||
<p className="max-w-sm text-xs leading-5 text-muted-foreground">
|
||||
{translate(
|
||||
{t(
|
||||
'auto.components.artifacts.ArtifactsPage.unconfiguredCopy',
|
||||
'Orca account sign-in is not configured on this machine yet.'
|
||||
)}
|
||||
</p>
|
||||
<Button variant="outline" size="sm" onClick={onOpenAccountSettings}>
|
||||
{translate(
|
||||
{t(
|
||||
'auto.components.artifacts.ArtifactsPage.openAccountSettings',
|
||||
'Open account settings'
|
||||
)}
|
||||
@@ -104,34 +100,32 @@ export function ArtifactsPageEmptyState({
|
||||
onLoadMore: () => void
|
||||
onOpenArtifactsSettings: () => void
|
||||
}): React.JSX.Element {
|
||||
const t = useTranslate()
|
||||
return (
|
||||
<div className="flex flex-1 flex-col items-center justify-center gap-2 px-5 py-5 text-center md:px-8">
|
||||
<Files className="size-8 text-muted-foreground" />
|
||||
<h2 className="text-sm font-semibold">
|
||||
{hasMore
|
||||
? translate(
|
||||
? t(
|
||||
'auto.components.artifacts.ArtifactsPage.moreAvailable',
|
||||
'More artifacts are available'
|
||||
)
|
||||
: publishingBlocked
|
||||
? translate(
|
||||
'auto.components.artifacts.ArtifactsPage.publishingOff',
|
||||
'Publishing is turned off'
|
||||
)
|
||||
: translate('auto.components.artifacts.ArtifactsPage.empty', 'No shared artifacts')}
|
||||
? t('auto.components.artifacts.ArtifactsPage.publishingOff', 'Publishing is turned off')
|
||||
: t('auto.components.artifacts.ArtifactsPage.empty', 'No shared artifacts')}
|
||||
</h2>
|
||||
<p className="max-w-sm text-xs leading-5 text-muted-foreground">
|
||||
{hasMore
|
||||
? translate(
|
||||
? t(
|
||||
'auto.components.artifacts.ArtifactsPage.moreAvailableCopy',
|
||||
'Load the next page to continue.'
|
||||
)
|
||||
: publishingBlocked
|
||||
? translate(
|
||||
? t(
|
||||
'auto.components.artifacts.ArtifactsPage.publishingOffCopy',
|
||||
'Nothing on this device can create a public artifact link yet. Allow publishing in Settings → Artifacts, then share from an open HTML or Markdown file or ask your agent.'
|
||||
)
|
||||
: translate(
|
||||
: t(
|
||||
'auto.components.artifacts.ArtifactsPage.emptyCopy',
|
||||
'Open an HTML or Markdown file and select Share as artifact, or ask your agent to share it.'
|
||||
)}
|
||||
@@ -144,7 +138,7 @@ export function ArtifactsPageEmptyState({
|
||||
className="mt-1"
|
||||
onClick={onOpenArtifactsSettings}
|
||||
>
|
||||
{translate(
|
||||
{t(
|
||||
'auto.components.artifacts.ArtifactsPage.openArtifactsSettings',
|
||||
'Open Settings → Artifacts'
|
||||
)}
|
||||
@@ -160,7 +154,7 @@ export function ArtifactsPageEmptyState({
|
||||
onClick={onLoadMore}
|
||||
>
|
||||
{loadingMore ? <Loader2 className="animate-spin" /> : null}
|
||||
{translate('auto.components.artifacts.ArtifactCollection.loadMore', 'Load more')}
|
||||
{t('auto.components.artifacts.ArtifactCollection.loadMore', 'Load more')}
|
||||
</Button>
|
||||
) : null}
|
||||
</div>
|
||||
|
||||
@@ -1,5 +1,6 @@
|
||||
import type { ArtifactListItem } from '../../../../shared/artifacts'
|
||||
import { translate } from '@/i18n/i18n'
|
||||
import type { Translate } from '@/i18n/use-translate'
|
||||
import { formatUiRelativeTime, formatUiRelativeTimeFromDate } from '@/i18n/relative-time-format'
|
||||
|
||||
export function artifactName(item: ArtifactListItem): string {
|
||||
@@ -16,50 +17,67 @@ export function formatByteSize(value: number): string {
|
||||
return `${(value / (1024 * 1024)).toFixed(1)} MB`
|
||||
}
|
||||
|
||||
export function formatArtifactUpdatedAt(value: string): string {
|
||||
return translate('auto.components.artifacts.updatedAt', 'Updated {{when}}', {
|
||||
when: formatArtifactUpdatedCompact(value)
|
||||
export function formatArtifactUpdatedAt(
|
||||
value: string,
|
||||
t: Translate = translate,
|
||||
intlLocale?: string
|
||||
): string {
|
||||
return t('auto.components.artifacts.updatedAt', 'Updated {{when}}', {
|
||||
when: formatArtifactUpdatedCompact(value, t, intlLocale)
|
||||
})
|
||||
}
|
||||
|
||||
export function formatArtifactUpdatedCompact(value: string): string {
|
||||
export function formatArtifactUpdatedCompact(
|
||||
value: string,
|
||||
t: Translate = translate,
|
||||
intlLocale?: string
|
||||
): string {
|
||||
return formatUiRelativeTimeFromDate(
|
||||
value,
|
||||
translate('auto.components.artifacts.updatedRecently', 'recently')
|
||||
t('auto.components.artifacts.updatedRecently', 'recently'),
|
||||
intlLocale
|
||||
)
|
||||
}
|
||||
|
||||
/** Phrased from the stored timestamp alone — never a claim about server-side state. */
|
||||
export function formatArtifactExpiry(value: string): string {
|
||||
export function formatArtifactExpiry(
|
||||
value: string,
|
||||
t: Translate = translate,
|
||||
intlLocale?: string
|
||||
): string {
|
||||
const expiresAt = new Date(value)
|
||||
if (Number.isNaN(expiresAt.getTime())) {
|
||||
return translate('auto.components.artifacts.expiryUnknown', 'Expiry unknown')
|
||||
return t('auto.components.artifacts.expiryUnknown', 'Expiry unknown')
|
||||
}
|
||||
const remainingMs = expiresAt.getTime() - Date.now()
|
||||
return remainingMs <= 0
|
||||
? translate('auto.components.artifacts.expired', 'Link expired')
|
||||
: translate('auto.components.artifacts.expires', 'Link expires {{when}}', {
|
||||
when: formatUiRelativeTime(remainingMs)
|
||||
? t('auto.components.artifacts.expired', 'Link expired')
|
||||
: t('auto.components.artifacts.expires', 'Link expires {{when}}', {
|
||||
when: formatUiRelativeTime(remainingMs, intlLocale)
|
||||
})
|
||||
}
|
||||
|
||||
export function formatArtifactExpiryCompact(value: string): string {
|
||||
export function formatArtifactExpiryCompact(
|
||||
value: string,
|
||||
t: Translate = translate,
|
||||
intlLocale?: string
|
||||
): string {
|
||||
const expiresAt = new Date(value)
|
||||
if (Number.isNaN(expiresAt.getTime())) {
|
||||
return translate('auto.components.artifacts.expiryUnknown', 'Expiry unknown')
|
||||
return t('auto.components.artifacts.expiryUnknown', 'Expiry unknown')
|
||||
}
|
||||
const remainingMs = expiresAt.getTime() - Date.now()
|
||||
return remainingMs <= 0
|
||||
? translate('auto.components.artifacts.expiredCompact', 'Expired')
|
||||
: formatUiRelativeTime(remainingMs)
|
||||
? t('auto.components.artifacts.expiredCompact', 'Expired')
|
||||
: formatUiRelativeTime(remainingMs, intlLocale)
|
||||
}
|
||||
|
||||
export function artifactTypeLabel(item: ArtifactListItem): string {
|
||||
export function artifactTypeLabel(item: ArtifactListItem, t: Translate = translate): string {
|
||||
if (item.artifact.sourceContentType === 'text/markdown') {
|
||||
return translate('auto.components.artifacts.typeMarkdown', 'Markdown')
|
||||
return t('auto.components.artifacts.typeMarkdown', 'Markdown')
|
||||
}
|
||||
if (item.artifact.sourceContentType === 'text/html') {
|
||||
return translate('auto.components.artifacts.typeHtml', 'HTML')
|
||||
return t('auto.components.artifacts.typeHtml', 'HTML')
|
||||
}
|
||||
return item.artifact.sourceContentType
|
||||
}
|
||||
|
||||
@@ -1,16 +1,17 @@
|
||||
import type { ArtifactListItem } from '../../../../shared/artifacts'
|
||||
import { isClipboardTextByteLengthOverLimit } from '../../../../shared/clipboard-text'
|
||||
import { artifactName, artifactTypeLabel } from './artifact-display-labels'
|
||||
import type { Translate } from '../../i18n/use-translate'
|
||||
|
||||
/** Pasted queries above this are clamped so filtering never runs on unbounded input. */
|
||||
export const ARTIFACT_LIST_SEARCH_QUERY_MAX_BYTES = 2 * 1024
|
||||
|
||||
export function artifactSearchHaystack(item: ArtifactListItem): string {
|
||||
export function artifactSearchHaystack(item: ArtifactListItem, t?: Translate): string {
|
||||
return [
|
||||
artifactName(item),
|
||||
item.artifact.originalFileName,
|
||||
item.artifact.slug,
|
||||
artifactTypeLabel(item)
|
||||
artifactTypeLabel(item, t)
|
||||
]
|
||||
.filter((value): value is string => Boolean(value))
|
||||
.join('\n')
|
||||
@@ -44,12 +45,13 @@ export function artifactMatchesSearchQuery(item: ArtifactListItem, query: string
|
||||
|
||||
export function filterArtifactsBySearchQuery(
|
||||
artifacts: readonly ArtifactListItem[],
|
||||
query: string
|
||||
query: string,
|
||||
t?: Translate
|
||||
): readonly ArtifactListItem[] {
|
||||
// Why: normalize once per filter, not once per artifact.
|
||||
const activeQuery = activeArtifactListSearchQuery(query)
|
||||
if (activeQuery === null) {
|
||||
return artifacts
|
||||
}
|
||||
return artifacts.filter((item) => artifactSearchHaystack(item).includes(activeQuery))
|
||||
return artifacts.filter((item) => artifactSearchHaystack(item, t).includes(activeQuery))
|
||||
}
|
||||
|
||||
@@ -6,7 +6,7 @@ import { CommandItem } from '@/components/ui/command'
|
||||
import { CREATE_WORKTREE_ITEM_ID } from '@/lib/workspace/search/worktrees/worktree-palette-create-action'
|
||||
import type { CmdJTaskUrlCreatePreview } from '@/lib/workspace/search/worktrees/worktree-palette-task-url-match'
|
||||
import type { LinearIssue } from '../../../../shared/linear/issue-types'
|
||||
import { translate } from '@/i18n/i18n'
|
||||
import { useTranslate } from '@/i18n/use-translate'
|
||||
|
||||
function TaskUrlProviderIcon({
|
||||
provider
|
||||
@@ -41,21 +41,20 @@ export function PaletteCreateWorktreeRow({
|
||||
taskUrlPreview: CmdJTaskUrlCreatePreview | null
|
||||
onSelect: () => void
|
||||
}): React.JSX.Element {
|
||||
const t = useTranslate()
|
||||
const showLinearPreview = linearPending || linearIssue !== null
|
||||
const linearPreviewLabel = linearIssue
|
||||
? translate(
|
||||
? t(
|
||||
'worktreeJumpPalette.linearIssue.createLabel',
|
||||
'Create worktree from Linear issue {{value0}}: {{value1}}',
|
||||
{ value0: linearIssue.identifier, value1: linearIssue.title }
|
||||
)
|
||||
: linearPending && showLinearLoadingFeedback
|
||||
? translate(
|
||||
'worktreeJumpPalette.linearIssue.loadingLabel',
|
||||
'Loading Linear issue {{value0}}',
|
||||
{ value0: linearIdentifier ?? '' }
|
||||
)
|
||||
? t('worktreeJumpPalette.linearIssue.loadingLabel', 'Loading Linear issue {{value0}}', {
|
||||
value0: linearIdentifier ?? ''
|
||||
})
|
||||
: linearPending
|
||||
? translate(
|
||||
? t(
|
||||
'worktreeJumpPalette.linearIssue.pendingLabel',
|
||||
'Create worktree from Linear issue {{value0}}',
|
||||
{ value0: linearIdentifier ?? '' }
|
||||
@@ -116,11 +115,11 @@ export function PaletteCreateWorktreeRow({
|
||||
</div>
|
||||
<div className="mt-0.5 truncate text-[12px] text-muted-foreground">
|
||||
{linearIssue || !showLinearLoadingFeedback
|
||||
? translate(
|
||||
? t(
|
||||
'worktreeJumpPalette.linearIssue.createHint',
|
||||
'Create worktree from Linear issue'
|
||||
)
|
||||
: translate('worktreeJumpPalette.linearIssue.loadingHint', 'Loading Linear issue…')}
|
||||
: t('worktreeJumpPalette.linearIssue.loadingHint', 'Loading Linear issue…')}
|
||||
</div>
|
||||
</div>
|
||||
</>
|
||||
@@ -140,14 +139,12 @@ export function PaletteCreateWorktreeRow({
|
||||
</div>
|
||||
<div className="mt-0.5 truncate text-[12px] text-muted-foreground">
|
||||
{taskUrlPreview.loading && showTaskUrlLoadingFeedback
|
||||
? translate('worktreeJumpPalette.taskUrl.loadingHint', 'Loading {{value0}}…', {
|
||||
? t('worktreeJumpPalette.taskUrl.loadingHint', 'Loading {{value0}}…', {
|
||||
value0: taskUrlPreview.kindLabel
|
||||
})
|
||||
: translate(
|
||||
'worktreeJumpPalette.taskUrl.createHint',
|
||||
'Create worktree from {{value0}}',
|
||||
{ value0: taskUrlPreview.kindLabel }
|
||||
)}
|
||||
: t('worktreeJumpPalette.taskUrl.createHint', 'Create worktree from {{value0}}', {
|
||||
value0: taskUrlPreview.kindLabel
|
||||
})}
|
||||
</div>
|
||||
</div>
|
||||
</>
|
||||
@@ -158,11 +155,9 @@ export function PaletteCreateWorktreeRow({
|
||||
</div>
|
||||
<div className="min-w-0 flex-1">
|
||||
<div className="text-[14px] font-semibold tracking-[-0.01em] text-foreground">
|
||||
{translate(
|
||||
'auto.components.WorktreeJumpPalette.95be6587d3',
|
||||
'Create worktree "{{value0}}"',
|
||||
{ value0: createWorktreeName }
|
||||
)}
|
||||
{t('auto.components.WorktreeJumpPalette.95be6587d3', 'Create worktree "{{value0}}"', {
|
||||
value0: createWorktreeName
|
||||
})}
|
||||
</div>
|
||||
</div>
|
||||
</>
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
import React, { useMemo } from 'react'
|
||||
import { ListFilter, X } from 'lucide-react'
|
||||
import { translate } from '@/i18n/i18n'
|
||||
import { useTranslate } from '@/i18n/use-translate'
|
||||
import type { PaletteFilterModel } from './palette-filter-options'
|
||||
import {
|
||||
EMPTY_PALETTE_FILTER,
|
||||
@@ -21,6 +21,7 @@ export default function PaletteFilterChips({
|
||||
filter: PaletteFilterState
|
||||
onFilterChange: (next: PaletteFilterState) => void
|
||||
}): React.JSX.Element | null {
|
||||
const t = useTranslate()
|
||||
const chips = useMemo<Chip[]>(() => {
|
||||
const hostLabels = new Map(model.hosts.map((host) => [host.id, host.label]))
|
||||
const repositoryLabels = new Map(
|
||||
@@ -49,9 +50,7 @@ export default function PaletteFilterChips({
|
||||
<div className="mx-3 mt-2 flex items-center gap-1.5 pl-3.5 text-[11px] text-muted-foreground">
|
||||
{/* Why: a fixed anchor the eye can find at one filter, where a bare muted line vanishes. */}
|
||||
<ListFilter className="size-3 shrink-0" aria-hidden="true" />
|
||||
<span className="sr-only">
|
||||
{translate('worktreeJumpPalette.filter.scopedTo', 'Scoped to')}
|
||||
</span>
|
||||
<span className="sr-only">{t('worktreeJumpPalette.filter.scopedTo', 'Scoped to')}</span>
|
||||
{/* Why: horizontal scroll keeps every chip reachable without a hard +N dead-end. */}
|
||||
<div className="scrollbar-sleek flex min-w-0 flex-1 items-center gap-1 overflow-x-auto">
|
||||
{chips.map((chip, index) => (
|
||||
@@ -64,13 +63,9 @@ export default function PaletteFilterChips({
|
||||
<button
|
||||
type="button"
|
||||
onClick={() => onFilterChange(togglePaletteFilterValue(filter, chip.field, chip.id))}
|
||||
aria-label={translate(
|
||||
'worktreeJumpPalette.filter.removeChip',
|
||||
'Remove filter {{value0}}',
|
||||
{
|
||||
value0: chip.label
|
||||
}
|
||||
)}
|
||||
aria-label={t('worktreeJumpPalette.filter.removeChip', 'Remove filter {{value0}}', {
|
||||
value0: chip.label
|
||||
})}
|
||||
className="group flex h-6 max-w-[140px] shrink-0 items-center gap-1 rounded-sm px-1.5 text-muted-foreground transition-colors hover:text-foreground focus-visible:bg-accent focus-visible:text-foreground"
|
||||
>
|
||||
<span className="min-w-0 truncate">{chip.label}</span>
|
||||
@@ -88,7 +83,7 @@ export default function PaletteFilterChips({
|
||||
onClick={() => onFilterChange(EMPTY_PALETTE_FILTER)}
|
||||
className="ml-1 mr-4 shrink-0 rounded-md px-1.5 py-0.5 hover:bg-accent hover:text-foreground"
|
||||
>
|
||||
{translate('worktreeJumpPalette.filter.clearAll', 'Clear all')}
|
||||
{t('worktreeJumpPalette.filter.clearAll', 'Clear all')}
|
||||
</button>
|
||||
</div>
|
||||
)
|
||||
|
||||
@@ -3,7 +3,7 @@ import React, { useCallback, useEffect, useMemo, useRef, useState } from 'react'
|
||||
import { useVirtualizer } from '@tanstack/react-virtual'
|
||||
import { Check, ChevronLeft, SearchIcon } from 'lucide-react'
|
||||
import { cn } from '@/lib/utils'
|
||||
import { translate } from '@/i18n/i18n'
|
||||
import { useTranslate } from '@/i18n/use-translate'
|
||||
import type { PaletteFilterOption } from './palette-filter-options'
|
||||
import type { PaletteFilterField } from './palette-filter'
|
||||
import {
|
||||
@@ -79,6 +79,7 @@ export function PaletteFilterFieldOptions({
|
||||
onClearField: () => void
|
||||
onSelectAllMatching: (ids: readonly string[]) => void
|
||||
}): React.JSX.Element {
|
||||
const t = useTranslate()
|
||||
const selected = useMemo(() => new Set(group.selected), [group.selected])
|
||||
const normalizedQuery = optionQuery.trim().toLowerCase()
|
||||
const rankMode: FilterOptionRankMode = group.field === 'host' ? 'registry' : 'popularity'
|
||||
@@ -189,17 +190,17 @@ export function PaletteFilterFieldOptions({
|
||||
|
||||
const searchPlaceholder =
|
||||
group.field === 'host'
|
||||
? translate('worktreeJumpPalette.filter.searchHosts', 'Filter hosts...')
|
||||
: translate('worktreeJumpPalette.filter.searchProjects', 'Filter projects...')
|
||||
? t('worktreeJumpPalette.filter.searchHosts', 'Filter hosts...')
|
||||
: t('worktreeJumpPalette.filter.searchProjects', 'Filter projects...')
|
||||
const emptyLabel =
|
||||
group.field === 'host'
|
||||
? translate('worktreeJumpPalette.filter.noHosts', 'No matching hosts')
|
||||
: translate('worktreeJumpPalette.filter.noProjects', 'No matching projects')
|
||||
? t('worktreeJumpPalette.filter.noHosts', 'No matching hosts')
|
||||
: t('worktreeJumpPalette.filter.noProjects', 'No matching projects')
|
||||
// Translate complete field-specific labels; lowercasing nouns breaks languages such as German.
|
||||
const clearLabel =
|
||||
group.field === 'host'
|
||||
? translate('worktreeJumpPalette.filter.clearHosts', 'Clear hosts')
|
||||
: translate('worktreeJumpPalette.filter.clearProjects', 'Clear projects')
|
||||
? t('worktreeJumpPalette.filter.clearHosts', 'Clear hosts')
|
||||
: t('worktreeJumpPalette.filter.clearProjects', 'Clear projects')
|
||||
|
||||
const canSelectAll = ranked.unselectedCount > 0
|
||||
const canClear = ranked.selectedCount > 0
|
||||
@@ -214,7 +215,7 @@ export function PaletteFilterFieldOptions({
|
||||
className="flex h-7 items-center gap-1 rounded-md px-1.5 text-[12px] text-muted-foreground hover:bg-accent hover:text-foreground"
|
||||
>
|
||||
<ChevronLeft className="size-3.5" aria-hidden="true" />
|
||||
{translate('worktreeJumpPalette.filter.back', 'Back')}
|
||||
{t('worktreeJumpPalette.filter.back', 'Back')}
|
||||
</button>
|
||||
<span className="min-w-0 flex-1 truncate pr-2 text-[12px] font-medium text-foreground">
|
||||
{group.heading}
|
||||
@@ -242,7 +243,7 @@ export function PaletteFilterFieldOptions({
|
||||
{ranked.selectedCollapsed ? (
|
||||
<div className="flex items-center justify-between gap-2 border-b border-border/55 px-2.5 py-1.5">
|
||||
<span className="text-[11px] text-muted-foreground">
|
||||
{translate(
|
||||
{t(
|
||||
'worktreeJumpPalette.filter.selectedCollapsed',
|
||||
'{{value0}} selected — remove via chips or Clear',
|
||||
{ value0: ranked.selectedCount }
|
||||
@@ -305,7 +306,7 @@ export function PaletteFilterFieldOptions({
|
||||
onClick={() => onSelectAllMatching(ranked.unselectedIds)}
|
||||
className="rounded-md px-1.5 py-0.5 text-[11px] text-muted-foreground hover:bg-accent hover:text-foreground"
|
||||
>
|
||||
{translate(
|
||||
{t(
|
||||
'worktreeJumpPalette.filter.selectAllMatching',
|
||||
'Select all matching ({{value0}})',
|
||||
{ value0: ranked.unselectedCount }
|
||||
|
||||
@@ -3,7 +3,7 @@ import { ChevronRight, ListFilter, X } from 'lucide-react'
|
||||
import { Command, CommandGroup, CommandItem, CommandList } from '@/components/ui/command'
|
||||
import { Button } from '@/components/ui/button'
|
||||
import { Popover, PopoverContent, PopoverTrigger } from '@/components/ui/popover'
|
||||
import { translate } from '@/i18n/i18n'
|
||||
import { useTranslate } from '@/i18n/use-translate'
|
||||
import { useUiLanguageRevision } from '@/i18n/use-ui-language-revision'
|
||||
import type { PaletteFilterModel } from './palette-filter-options'
|
||||
import {
|
||||
@@ -72,6 +72,7 @@ export default function PaletteFilterMenu({
|
||||
onRequestInputFocus: () => void
|
||||
portalContainer: HTMLElement | null
|
||||
}): React.JSX.Element | null {
|
||||
const t = useTranslate()
|
||||
const languageRevision = useUiLanguageRevision()
|
||||
const [open, setOpen] = useState(false)
|
||||
const [activeField, setActiveField] = useState<PaletteFilterField | null>(null)
|
||||
@@ -83,7 +84,7 @@ export default function PaletteFilterMenu({
|
||||
if (model.hosts.length > 1) {
|
||||
entries.push({
|
||||
field: 'host',
|
||||
heading: translate('worktreeJumpPalette.filter.hosts', 'Hosts'),
|
||||
heading: t('worktreeJumpPalette.filter.hosts', 'Hosts'),
|
||||
options: model.hosts,
|
||||
selected: filter.hostIds
|
||||
})
|
||||
@@ -92,14 +93,14 @@ export default function PaletteFilterMenu({
|
||||
entries.push({
|
||||
field: 'repository',
|
||||
// "Projects" is the user-facing term for repository-granular choices; see filter.emptySubtitle.
|
||||
heading: translate('worktreeJumpPalette.filter.projects', 'Projects'),
|
||||
heading: t('worktreeJumpPalette.filter.projects', 'Projects'),
|
||||
options: model.repositories,
|
||||
selected: filter.repoIds
|
||||
})
|
||||
}
|
||||
return entries
|
||||
// oxlint-disable-next-line react-hooks/exhaustive-deps -- translate() reads the UI language implicitly.
|
||||
}, [filter.hostIds, filter.repoIds, model.hosts, model.repositories, languageRevision])
|
||||
}, [filter.hostIds, filter.repoIds, model.hosts, model.repositories, languageRevision, t])
|
||||
|
||||
const activeGroup =
|
||||
activeField == null ? null : (groups.find((group) => group.field === activeField) ?? null)
|
||||
@@ -163,12 +164,12 @@ export default function PaletteFilterMenu({
|
||||
type="button"
|
||||
variant={active ? 'secondary' : 'outline'}
|
||||
size="xs"
|
||||
aria-label={translate('worktreeJumpPalette.filter.trigger', 'Filter results')}
|
||||
aria-label={t('worktreeJumpPalette.filter.trigger', 'Filter results')}
|
||||
data-active={active ? 'true' : undefined}
|
||||
className="ml-2"
|
||||
>
|
||||
<ListFilter className="size-3.5" aria-hidden="true" />
|
||||
<span>{translate('worktreeJumpPalette.filter.label', 'Filter')}</span>
|
||||
<span>{t('worktreeJumpPalette.filter.label', 'Filter')}</span>
|
||||
{active ? (
|
||||
<span className="rounded-full bg-primary/85 px-1.5 text-[10px] font-semibold tabular-nums text-primary-foreground">
|
||||
{selectionCount}
|
||||
@@ -210,7 +211,7 @@ export default function PaletteFilterMenu({
|
||||
{active ? (
|
||||
<div className="flex items-center justify-between border-t border-border/55 px-3 py-2">
|
||||
<span className="text-[11px] text-muted-foreground">
|
||||
{translate('worktreeJumpPalette.filter.activeCount', '{{value0}} active', {
|
||||
{t('worktreeJumpPalette.filter.activeCount', '{{value0}} active', {
|
||||
value0: selectionCount
|
||||
})}
|
||||
</span>
|
||||
@@ -220,7 +221,7 @@ export default function PaletteFilterMenu({
|
||||
className="flex items-center gap-1 rounded-md px-1.5 py-0.5 text-[11px] text-muted-foreground hover:bg-accent hover:text-foreground"
|
||||
>
|
||||
<X className="size-3" aria-hidden="true" />
|
||||
{translate('worktreeJumpPalette.filter.clearAll', 'Clear all')}
|
||||
{t('worktreeJumpPalette.filter.clearAll', 'Clear all')}
|
||||
</button>
|
||||
</div>
|
||||
) : null}
|
||||
|
||||
@@ -30,7 +30,7 @@ import {
|
||||
terminalTabHasUnreadActivity,
|
||||
type TerminalTabAttentionBadge
|
||||
} from '@/components/tab-bar/terminal-tab-activity-status'
|
||||
import { translate } from '@/i18n/i18n'
|
||||
import { useTranslate } from '@/i18n/use-translate'
|
||||
|
||||
import { AGENT_STATUS_STALE_AFTER_MS } from '../../../../shared/agent-status/contracts/agent-status-freshness'
|
||||
import type { AgentStatusEntry } from '../../../../shared/agent-status/contracts/agent-status-types'
|
||||
@@ -237,6 +237,7 @@ export function PaletteRecentTabStatusDot({
|
||||
row: RecentWorkspaceTabRow | null
|
||||
fallback: React.ReactNode
|
||||
}): React.JSX.Element {
|
||||
const t = useTranslate()
|
||||
const live = useLiveStatus()
|
||||
const terminalTabId = row?.terminalTab?.id
|
||||
const status: WorktreeStatus | null =
|
||||
@@ -258,10 +259,7 @@ export function PaletteRecentTabStatusDot({
|
||||
const statusLabel =
|
||||
badge === 'unread'
|
||||
? // Reuse the translated tab-bar label for the same unread signal.
|
||||
translate(
|
||||
'auto.components.tab.bar.TerminalTabLeadingIcon.7ab2964bea',
|
||||
'Unread agent completion'
|
||||
)
|
||||
t('auto.components.tab.bar.TerminalTabLeadingIcon.7ab2964bea', 'Unread agent completion')
|
||||
: getWorktreeStatusLabel(badge)
|
||||
// Why: the outer hit target owns the tooltip because the overlaid pip ignores pointer events.
|
||||
return (
|
||||
|
||||
@@ -3,7 +3,7 @@ import { Switch } from '@/components/ui/switch'
|
||||
import { useAppStore } from '@/store'
|
||||
import type { ContextualTourStepControl } from '../../../../shared/contextual-tours'
|
||||
import { CONTEXTUAL_TOUR_ENABLE_AUTO_WORKSPACE_NAME_EVENT } from './contextual-tour-composer-events'
|
||||
import { translate } from '@/i18n/i18n'
|
||||
import { useTranslate } from '@/i18n/use-translate'
|
||||
|
||||
export function ContextualTourControl({
|
||||
control
|
||||
@@ -29,6 +29,7 @@ export function toggleAutoRenameBranchFromWork(args: {
|
||||
}
|
||||
|
||||
function AutoRenameBranchFromWorkControl(): JSX.Element {
|
||||
const t = useTranslate()
|
||||
const enabled = useAppStore((s) => s.settings?.autoRenameBranchFromWork === true)
|
||||
const updateSettings = useAppStore((s) => s.updateSettings)
|
||||
|
||||
@@ -37,7 +38,7 @@ function AutoRenameBranchFromWorkControl(): JSX.Element {
|
||||
<div className="flex items-center justify-between gap-3">
|
||||
<div className="min-w-0">
|
||||
<div className="text-xs font-medium text-foreground">
|
||||
{translate(
|
||||
{t(
|
||||
'auto.components.contextual.tours.ContextualTourControl.731c5573df',
|
||||
'Auto-name from first message'
|
||||
)}
|
||||
@@ -45,7 +46,7 @@ function AutoRenameBranchFromWorkControl(): JSX.Element {
|
||||
</div>
|
||||
<Switch
|
||||
checked={enabled}
|
||||
aria-label={translate(
|
||||
aria-label={t(
|
||||
'auto.components.contextual.tours.ContextualTourControl.186eecc34f',
|
||||
'Auto-name workspace from first agent message'
|
||||
)}
|
||||
|
||||
@@ -27,7 +27,7 @@ import {
|
||||
watchContextualTourFloatingPosition,
|
||||
type ContextualTourFloatingPosition
|
||||
} from './contextual-tour-floating-position'
|
||||
import { translate } from '@/i18n/i18n'
|
||||
import { useTranslate } from '@/i18n/use-translate'
|
||||
import { addAppKeyListener, removeAppKeyListener } from '@/lib/app-keyboard-router'
|
||||
import { hasVisibleOverlay } from '@/lib/visible-overlay'
|
||||
import { getMeasurableContextualTourTarget } from './contextual-tour-gate'
|
||||
@@ -111,6 +111,7 @@ export function ContextualTourOverlaySurface({
|
||||
onStepAction,
|
||||
onOverlayKeyDownCapture
|
||||
}: ContextualTourOverlaySurfaceProps): JSX.Element {
|
||||
const t = useTranslate()
|
||||
const arrowRef = useRef<SVGSVGElement | null>(null)
|
||||
const [floatingPosition, setFloatingPosition] = useState<ContextualTourFloatingPosition | null>(
|
||||
null
|
||||
@@ -133,11 +134,8 @@ export function ContextualTourOverlaySurface({
|
||||
const defaultPrimaryAction = {
|
||||
kind: renderState.isLastStep ? 'complete' : 'next',
|
||||
label: renderState.isLastStep
|
||||
? translate('auto.components.contextual.tours.ContextualTourOverlaySurface.complete', 'Done')
|
||||
: translate(
|
||||
'auto.components.contextual.tours.contextual.tour.overlay.measurement.38b3155418',
|
||||
'Next'
|
||||
)
|
||||
? t('auto.components.contextual.tours.ContextualTourOverlaySurface.complete', 'Done')
|
||||
: t('auto.components.contextual.tours.contextual.tour.overlay.measurement.38b3155418', 'Next')
|
||||
} satisfies ContextualTourStepAction
|
||||
const primaryAction =
|
||||
renderState.primaryAction ?? (renderState.hidePrimaryAction ? null : defaultPrimaryAction)
|
||||
@@ -201,11 +199,11 @@ export function ContextualTourOverlaySurface({
|
||||
data-contextual-tour-dismiss=""
|
||||
aria-label={
|
||||
renderState.isLastStep
|
||||
? translate(
|
||||
? t(
|
||||
'auto.components.contextual.tours.ContextualTourOverlaySurface.d974f32a83',
|
||||
'Dismiss tour'
|
||||
)
|
||||
: translate(
|
||||
: t(
|
||||
'auto.components.contextual.tours.ContextualTourOverlaySurface.4f86e2a10b',
|
||||
'Skip tour'
|
||||
)
|
||||
@@ -217,9 +215,7 @@ export function ContextualTourOverlaySurface({
|
||||
</Button>
|
||||
<h2 className="flex items-center gap-2 pr-6 text-sm font-semibold tracking-tight text-foreground">
|
||||
{renderState.badge === 'new' ? (
|
||||
<Badge variant="secondary">
|
||||
{translate('components.contextualTours.badgeNew', 'New')}
|
||||
</Badge>
|
||||
<Badge variant="secondary">{t('components.contextualTours.badgeNew', 'New')}</Badge>
|
||||
) : null}
|
||||
<span className="min-w-0">{renderState.title}</span>
|
||||
</h2>
|
||||
@@ -236,14 +232,14 @@ export function ContextualTourOverlaySurface({
|
||||
type="button"
|
||||
variant="ghost"
|
||||
size="xs"
|
||||
aria-label={translate(
|
||||
aria-label={t(
|
||||
'auto.components.contextual.tours.ContextualTourOverlaySurface.4a9568f773',
|
||||
'Back'
|
||||
)}
|
||||
onClick={onBack}
|
||||
>
|
||||
<ArrowLeft />
|
||||
{translate(
|
||||
{t(
|
||||
'auto.components.contextual.tours.ContextualTourOverlaySurface.4a9568f773',
|
||||
'Back'
|
||||
)}
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
import type { JSX } from 'react'
|
||||
import { cn } from '@/lib/utils'
|
||||
import { translate } from '@/i18n/i18n'
|
||||
import { useTranslate } from '@/i18n/use-translate'
|
||||
|
||||
export function ContextualTourProgressDots({
|
||||
current,
|
||||
@@ -9,6 +9,7 @@ export function ContextualTourProgressDots({
|
||||
current: number
|
||||
total: number
|
||||
}): JSX.Element {
|
||||
const t = useTranslate()
|
||||
if (total <= 1) {
|
||||
return <span aria-hidden="true" className="h-1.5 w-4" />
|
||||
}
|
||||
@@ -19,7 +20,7 @@ export function ContextualTourProgressDots({
|
||||
aria-valuemin={1}
|
||||
aria-valuemax={total}
|
||||
aria-valuenow={current}
|
||||
aria-label={translate(
|
||||
aria-label={t(
|
||||
'auto.components.contextual.tours.ContextualTourProgressDots.dcd6e6b03e',
|
||||
'Step {{value0}} of {{value1}}',
|
||||
{ value0: current, value1: total }
|
||||
@@ -45,7 +46,7 @@ export function ContextualTourProgressDots({
|
||||
})}
|
||||
</span>
|
||||
<span className="whitespace-nowrap text-[11px] font-medium leading-none text-muted-foreground">
|
||||
{translate(
|
||||
{t(
|
||||
'auto.components.contextual.tours.ContextualTourProgressDots.stepOfTotal',
|
||||
'{{current}} of {{total}}',
|
||||
{ current, total }
|
||||
|
||||
@@ -21,7 +21,8 @@ import {
|
||||
type CrashReportRecord
|
||||
} from '../../../../shared/crash-reporting'
|
||||
import type { GitHubViewer } from '../../../../shared/github/pull-request-types'
|
||||
import { getIntlLocale, translate } from '@/i18n/i18n'
|
||||
import { translate } from '@/i18n/i18n'
|
||||
import { useIntlLocale, useTranslate, type Translate } from '@/i18n/use-translate'
|
||||
import {
|
||||
CRASH_REPORT_SUBMIT_FAILURE_TOAST_ID,
|
||||
getCrashReportCopySubmissionFailure,
|
||||
@@ -30,74 +31,68 @@ import {
|
||||
} from './crash-report-submit-notice'
|
||||
import { useCrashReportCopy } from './use-crash-report-copy'
|
||||
|
||||
function formatSummary(report: CrashReportRecord): string {
|
||||
function formatSummary(report: CrashReportRecord, t: Translate = translate): string {
|
||||
if (isReactErrorBoundaryReport(report)) {
|
||||
const surface = typeof report.details.surface === 'string' ? report.details.surface : null
|
||||
return surface
|
||||
? translate(
|
||||
? t(
|
||||
'auto.components.crash.report.CrashReportDialogSurface.renderErrorIn',
|
||||
'React render error in {{surface}}',
|
||||
{ surface }
|
||||
)
|
||||
: translate(
|
||||
'auto.components.crash.report.CrashReportDialogSurface.renderError',
|
||||
'React render error'
|
||||
)
|
||||
: t('auto.components.crash.report.CrashReportDialogSurface.renderError', 'React render error')
|
||||
}
|
||||
return `${report.processType} ${report.reason}${
|
||||
report.exitCode === null ? '' : ` (exit ${report.exitCode})`
|
||||
}`
|
||||
}
|
||||
|
||||
function getDialogTitle(report: CrashReportRecord | null): string {
|
||||
function getDialogTitle(report: CrashReportRecord | null, t: Translate = translate): string {
|
||||
if (!report) {
|
||||
return translate(
|
||||
'auto.components.crash.report.CrashReportDialogSurface.reportTitle',
|
||||
'Report a crash'
|
||||
)
|
||||
return t('auto.components.crash.report.CrashReportDialogSurface.reportTitle', 'Report a crash')
|
||||
}
|
||||
return report && isReactErrorBoundaryReport(report)
|
||||
? translate(
|
||||
? t(
|
||||
'auto.components.crash.report.CrashReportDialogSurface.uiErrorTitle',
|
||||
'Orca hit a recoverable UI error'
|
||||
)
|
||||
: translate(
|
||||
: t(
|
||||
'auto.components.crash.report.CrashReportDialogSurface.closedTitle',
|
||||
'Orca closed unexpectedly'
|
||||
)
|
||||
}
|
||||
|
||||
function getDialogDescription(report: CrashReportRecord | null): string {
|
||||
function getDialogDescription(report: CrashReportRecord | null, t: Translate = translate): string {
|
||||
if (!report) {
|
||||
return translate(
|
||||
return t(
|
||||
'auto.components.crash.report.CrashReportDialogSurface.reportDescription',
|
||||
'Send a privacy-safe crash report. Recent redacted diagnostic logs are included when available.'
|
||||
)
|
||||
}
|
||||
return report && isReactErrorBoundaryReport(report)
|
||||
? translate(
|
||||
? t(
|
||||
'auto.components.crash.report.CrashReportDialogSurface.uiErrorDescription',
|
||||
'Send a privacy-safe diagnostic report to help us understand the failed UI surface.'
|
||||
)
|
||||
: translate(
|
||||
: t(
|
||||
'auto.components.crash.report.CrashReportDialogSurface.closedDescription',
|
||||
'Send a privacy-safe diagnostic report to help us understand what happened.'
|
||||
)
|
||||
}
|
||||
|
||||
function getNotesPlaceholder(report: CrashReportRecord | null): string {
|
||||
function getNotesPlaceholder(report: CrashReportRecord | null, t: Translate = translate): string {
|
||||
if (!report) {
|
||||
return translate(
|
||||
return t(
|
||||
'auto.components.crash.report.CrashReportDialogSurface.reportNotesPlaceholder',
|
||||
'Optional: what happened?'
|
||||
)
|
||||
}
|
||||
return report && isReactErrorBoundaryReport(report)
|
||||
? translate(
|
||||
? t(
|
||||
'auto.components.crash.report.CrashReportDialogSurface.uiErrorNotesPlaceholder',
|
||||
'Optional: what were you doing before this UI error?'
|
||||
)
|
||||
: translate(
|
||||
: t(
|
||||
'auto.components.crash.report.CrashReportDialogSurface.closedNotesPlaceholder',
|
||||
'Optional: what were you doing before Orca closed?'
|
||||
)
|
||||
@@ -118,6 +113,8 @@ export function CrashReportDialogSurface({
|
||||
onOpenChange,
|
||||
onReportChange
|
||||
}: CrashReportDialogSurfaceProps): React.JSX.Element {
|
||||
const t = useTranslate()
|
||||
const intlLocale = useIntlLocale()
|
||||
const mountedRef = useMountedRef()
|
||||
const [notes, setNotes] = useState('')
|
||||
const [includeDiagnosticLogs, setIncludeDiagnosticLogs] = useState(true)
|
||||
@@ -230,10 +227,7 @@ export function CrashReportDialogSurface({
|
||||
toast.warning(warningNotice.title, { description: warningNotice.description })
|
||||
} else {
|
||||
toast.success(
|
||||
translate(
|
||||
'auto.components.crash.report.CrashReportDialog.8e24fe4f75',
|
||||
'Crash report sent.'
|
||||
)
|
||||
t('auto.components.crash.report.CrashReportDialog.8e24fe4f75', 'Crash report sent.')
|
||||
)
|
||||
}
|
||||
onOpenChange(false)
|
||||
@@ -270,26 +264,28 @@ export function CrashReportDialogSurface({
|
||||
<DialogHeader>
|
||||
<DialogTitle className="flex items-center gap-2 text-sm">
|
||||
<AlertTriangle className="size-4 text-destructive" />
|
||||
{getDialogTitle(report)}
|
||||
{getDialogTitle(report, t)}
|
||||
</DialogTitle>
|
||||
<DialogDescription className="text-xs">{getDialogDescription(report)}</DialogDescription>
|
||||
<DialogDescription className="text-xs">
|
||||
{getDialogDescription(report, t)}
|
||||
</DialogDescription>
|
||||
</DialogHeader>
|
||||
|
||||
<div className="min-w-0 space-y-3">
|
||||
{report ? (
|
||||
<>
|
||||
<div className="rounded-md border border-border/70 bg-muted/30 p-3 text-xs">
|
||||
<div className="font-medium text-foreground">{formatSummary(report)}</div>
|
||||
<div className="font-medium text-foreground">{formatSummary(report, t)}</div>
|
||||
<div className="mt-1 text-muted-foreground">
|
||||
{new Date(report.createdAt).toLocaleString(getIntlLocale())} · {report.platform}{' '}
|
||||
{new Date(report.createdAt).toLocaleString(intlLocale)} · {report.platform}{' '}
|
||||
{report.arch} ·{' '}
|
||||
{translate('auto.components.crash.report.CrashReportDialog.835037edc9', 'Orca')}{' '}
|
||||
{t('auto.components.crash.report.CrashReportDialog.835037edc9', 'Orca')}{' '}
|
||||
{report.appVersion}
|
||||
</div>
|
||||
</div>
|
||||
<div className="min-w-0 space-y-1.5">
|
||||
<div className="text-[11px] font-medium text-muted-foreground">
|
||||
{translate(
|
||||
{t(
|
||||
'auto.components.crash.report.CrashReportDialog.6d3ebe216a',
|
||||
'Diagnostic text'
|
||||
)}
|
||||
@@ -302,11 +298,11 @@ export function CrashReportDialogSurface({
|
||||
) : (
|
||||
<div className="rounded-md border border-border/70 bg-muted/30 p-3 text-xs text-muted-foreground">
|
||||
{loading
|
||||
? translate(
|
||||
? t(
|
||||
'auto.components.crash.report.CrashReportDialog.765591798d',
|
||||
'Checking for crash reports...'
|
||||
)
|
||||
: translate(
|
||||
: t(
|
||||
'auto.components.crash.report.CrashReportDialog.ead6fc0510',
|
||||
'No automatic crash report was captured. You can still send details and include recent diagnostic logs when available.'
|
||||
)}
|
||||
@@ -319,15 +315,15 @@ export function CrashReportDialogSurface({
|
||||
rows={4}
|
||||
// Keep the UI and formatter on the same input budget.
|
||||
maxLength={MAX_USER_NOTES_LENGTH}
|
||||
placeholder={getNotesPlaceholder(report)}
|
||||
placeholder={getNotesPlaceholder(report, t)}
|
||||
className="min-h-24 w-full rounded-md border border-border bg-background px-3 py-2 text-sm outline-none ring-offset-background placeholder:text-muted-foreground focus-visible:ring-2 focus-visible:ring-ring focus-visible:ring-offset-2"
|
||||
/>
|
||||
<div
|
||||
aria-hidden="true"
|
||||
className="text-right text-[11px] tabular-nums text-muted-foreground"
|
||||
>
|
||||
{notes.length.toLocaleString(getIntlLocale())} /{' '}
|
||||
{MAX_USER_NOTES_LENGTH.toLocaleString(getIntlLocale())}
|
||||
{notes.length.toLocaleString(intlLocale)} /{' '}
|
||||
{MAX_USER_NOTES_LENGTH.toLocaleString(intlLocale)}
|
||||
</div>
|
||||
</div>
|
||||
<div className="flex items-start gap-2 rounded-md border border-border/70 bg-muted/20 p-3">
|
||||
@@ -340,13 +336,13 @@ export function CrashReportDialogSurface({
|
||||
/>
|
||||
<div className="space-y-1">
|
||||
<Label htmlFor="crash-report-attach-diagnostics" className="text-xs">
|
||||
{translate(
|
||||
{t(
|
||||
'auto.components.crash.report.CrashReportDialog.b082f27490',
|
||||
'Attach recent diagnostic logs'
|
||||
)}
|
||||
</Label>
|
||||
<div className="text-xs leading-5 text-muted-foreground">
|
||||
{translate(
|
||||
{t(
|
||||
'auto.components.crash.report.CrashReportDialog.e59f0b9427',
|
||||
'Sends a capped redacted log bundle with the report.'
|
||||
)}
|
||||
@@ -364,7 +360,7 @@ export function CrashReportDialogSurface({
|
||||
disabled={loading}
|
||||
>
|
||||
<Clipboard className="size-3.5" />
|
||||
{translate('auto.components.crash.report.CrashReportDialog.50b00dc327', 'Copy Details')}
|
||||
{t('auto.components.crash.report.CrashReportDialog.50b00dc327', 'Copy Details')}
|
||||
</Button>
|
||||
<Button
|
||||
type="button"
|
||||
@@ -373,11 +369,11 @@ export function CrashReportDialogSurface({
|
||||
onClick={handleDismiss}
|
||||
disabled={submitting}
|
||||
>
|
||||
{translate('auto.components.crash.report.CrashReportDialog.88fea8e84e', "Don't Send")}
|
||||
{t('auto.components.crash.report.CrashReportDialog.88fea8e84e', "Don't Send")}
|
||||
</Button>
|
||||
<Button type="button" size="sm" onClick={handleSubmit} disabled={loading || submitting}>
|
||||
<Send className="size-3.5" />
|
||||
{translate('auto.components.crash.report.CrashReportDialog.b4951cd27c', 'Send Report')}
|
||||
{t('auto.components.crash.report.CrashReportDialog.b4951cd27c', 'Send Report')}
|
||||
</Button>
|
||||
</DialogFooter>
|
||||
</DialogContent>
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
import { X } from 'lucide-react'
|
||||
import { Button } from '@/components/ui/button'
|
||||
import { translate } from '@/i18n/i18n'
|
||||
import { useTranslate } from '@/i18n/use-translate'
|
||||
import type { DashboardFilters, DashboardReviewFilter } from './agent-board-filtering'
|
||||
|
||||
type FilterLabel = { id: string; label: string }
|
||||
@@ -12,13 +12,14 @@ function ActiveChip({
|
||||
label: string
|
||||
onRemove: () => void
|
||||
}): React.JSX.Element {
|
||||
const t = useTranslate()
|
||||
return (
|
||||
<span className="inline-flex h-[22px] items-center gap-1 rounded-full border border-border bg-muted/55 pr-1 pl-2 text-[11px]">
|
||||
{label}
|
||||
<button
|
||||
type="button"
|
||||
onClick={onRemove}
|
||||
aria-label={translate('dashboardPopout.filters.remove', 'Remove {{label}} filter', {
|
||||
aria-label={t('dashboardPopout.filters.remove', 'Remove {{label}} filter', {
|
||||
label
|
||||
})}
|
||||
className="rounded-full text-muted-foreground hover:text-foreground focus-visible:ring-2 focus-visible:ring-ring focus-visible:outline-none"
|
||||
@@ -48,10 +49,11 @@ export function AgentDashboardFilterChips({
|
||||
onReviewToggle: (id: DashboardReviewFilter) => void
|
||||
onClear: () => void
|
||||
}): React.JSX.Element {
|
||||
const t = useTranslate()
|
||||
return (
|
||||
<div className="flex shrink-0 flex-wrap items-center gap-1 border-b border-border px-3 py-2">
|
||||
<span className="mr-0.5 text-[10px] font-semibold uppercase tracking-[0.05em] text-muted-foreground">
|
||||
{translate('dashboardPopout.filters.active', 'Filters')}
|
||||
{t('dashboardPopout.filters.active', 'Filters')}
|
||||
</span>
|
||||
{filters.projects.map((id) => (
|
||||
<ActiveChip
|
||||
@@ -80,7 +82,7 @@ export function AgentDashboardFilterChips({
|
||||
onClick={onClear}
|
||||
className="h-[22px] px-1 text-[11px] text-muted-foreground"
|
||||
>
|
||||
{translate('dashboardPopout.filters.clear', 'Clear')}
|
||||
{t('dashboardPopout.filters.clear', 'Clear')}
|
||||
</Button>
|
||||
</div>
|
||||
)
|
||||
|
||||
@@ -10,7 +10,7 @@ import {
|
||||
DropdownMenuTrigger
|
||||
} from '@/components/ui/dropdown-menu'
|
||||
import { Input } from '@/components/ui/input'
|
||||
import { translate } from '@/i18n/i18n'
|
||||
import { useTranslate } from '@/i18n/use-translate'
|
||||
import { cn } from '@/lib/utils'
|
||||
import { getWorkspaceStatusVisualMeta } from '../sidebar/workspace-status/presentation/workspace-status'
|
||||
import type { DashboardCard, DashboardFilterOptions } from '../../../../shared/dashboard-snapshot'
|
||||
@@ -55,6 +55,7 @@ export function AgentDashboardToolbar({
|
||||
searchInputRef,
|
||||
filterControl
|
||||
}: AgentDashboardToolbarProps): React.JSX.Element {
|
||||
const t = useTranslate()
|
||||
const isMac = navigator.userAgent.includes('Mac')
|
||||
const projects = projectOptions(cards, filterOptions?.projects)
|
||||
const statuses = workspaceStatusOptions(cards, filterOptions?.workspaceStatuses)
|
||||
@@ -76,7 +77,7 @@ export function AgentDashboardToolbar({
|
||||
onFiltersChange({ projects: [], workspaceStatuses: [], reviewStates: [] })
|
||||
}
|
||||
const reviewLabel = (id: DashboardReviewFilter): string =>
|
||||
translate('dashboardPopout.filters.reviewChip', 'Review: {{state}}', {
|
||||
t('dashboardPopout.filters.reviewChip', 'Review: {{state}}', {
|
||||
state: reviewStateLabel(id)
|
||||
})
|
||||
|
||||
@@ -89,11 +90,11 @@ export function AgentDashboardToolbar({
|
||||
ref={searchInputRef}
|
||||
value={query}
|
||||
onChange={(event) => onQueryChange(event.target.value)}
|
||||
placeholder={translate(
|
||||
placeholder={t(
|
||||
'dashboardPopout.search.placeholder',
|
||||
'Search worktree, project, or agent…'
|
||||
)}
|
||||
aria-label={translate('dashboardPopout.search.label', 'Search agents')}
|
||||
aria-label={t('dashboardPopout.search.label', 'Search agents')}
|
||||
className="h-7 bg-muted/55 pr-16 pl-7 text-xs"
|
||||
/>
|
||||
{query ? (
|
||||
@@ -101,7 +102,7 @@ export function AgentDashboardToolbar({
|
||||
variant="ghost"
|
||||
size="icon-xs"
|
||||
onClick={() => onQueryChange('')}
|
||||
aria-label={translate('dashboardPopout.search.clear', 'Clear search')}
|
||||
aria-label={t('dashboardPopout.search.clear', 'Clear search')}
|
||||
className="absolute top-1/2 right-0.5 -translate-y-1/2 text-muted-foreground"
|
||||
>
|
||||
<X className="size-3" />
|
||||
@@ -117,7 +118,7 @@ export function AgentDashboardToolbar({
|
||||
</div>
|
||||
{!filterControl && (query || activeCount > 0) ? (
|
||||
<span className="shrink-0 text-[11px] tabular-nums text-muted-foreground">
|
||||
{translate('dashboardPopout.search.results', '{{shown}} of {{total}} shown', {
|
||||
{t('dashboardPopout.search.results', '{{shown}} of {{total}} shown', {
|
||||
shown: filteredCount,
|
||||
total: cards.length
|
||||
})}
|
||||
@@ -135,7 +136,7 @@ export function AgentDashboardToolbar({
|
||||
)}
|
||||
>
|
||||
<Filter className="size-3" />
|
||||
{translate('dashboardPopout.filters.label', 'Filter')}
|
||||
{t('dashboardPopout.filters.label', 'Filter')}
|
||||
{activeCount > 0 ? (
|
||||
<span className="rounded-full bg-foreground px-1.5 py-px text-[10px] leading-none text-background">
|
||||
{activeCount}
|
||||
@@ -146,7 +147,7 @@ export function AgentDashboardToolbar({
|
||||
</DropdownMenuTrigger>
|
||||
<DropdownMenuContent align="end" className="w-64" sideOffset={6}>
|
||||
<DropdownMenuLabel>
|
||||
{translate('dashboardPopout.filters.project', 'Project')}
|
||||
{t('dashboardPopout.filters.project', 'Project')}
|
||||
</DropdownMenuLabel>
|
||||
{projects.map((option) => (
|
||||
<DropdownMenuCheckboxItem
|
||||
@@ -161,14 +162,17 @@ export function AgentDashboardToolbar({
|
||||
))}
|
||||
<DropdownMenuSeparator />
|
||||
<DropdownMenuLabel>
|
||||
{translate('dashboardPopout.filters.workspaceStatus', 'Workspace status')}
|
||||
{t('dashboardPopout.filters.workspaceStatus', 'Workspace status')}
|
||||
</DropdownMenuLabel>
|
||||
{statuses.map((option) => {
|
||||
const meta = getWorkspaceStatusVisualMeta({
|
||||
id: option.id,
|
||||
label: option.label,
|
||||
color: option.color
|
||||
})
|
||||
const meta = getWorkspaceStatusVisualMeta(
|
||||
{
|
||||
id: option.id,
|
||||
label: option.label,
|
||||
color: option.color
|
||||
},
|
||||
t
|
||||
)
|
||||
return (
|
||||
<DropdownMenuCheckboxItem
|
||||
key={option.id}
|
||||
@@ -184,7 +188,7 @@ export function AgentDashboardToolbar({
|
||||
})}
|
||||
<DropdownMenuSeparator />
|
||||
<DropdownMenuLabel>
|
||||
{translate('dashboardPopout.filters.reviewStatus', 'PR / MR status')}
|
||||
{t('dashboardPopout.filters.reviewStatus', 'PR / MR status')}
|
||||
</DropdownMenuLabel>
|
||||
{REVIEW_OPTIONS.map((option) => (
|
||||
<DropdownMenuCheckboxItem
|
||||
@@ -193,7 +197,7 @@ export function AgentDashboardToolbar({
|
||||
onCheckedChange={() => toggleReview(option)}
|
||||
onSelect={(event) => event.preventDefault()}
|
||||
>
|
||||
<span>{reviewStateLabel(option)}</span>
|
||||
<span>{reviewStateLabel(option, t)}</span>
|
||||
<FilterOptionCount count={reviewCounts.get(option) ?? 0} />
|
||||
</DropdownMenuCheckboxItem>
|
||||
))}
|
||||
@@ -204,7 +208,7 @@ export function AgentDashboardToolbar({
|
||||
className="text-muted-foreground"
|
||||
>
|
||||
<X className="size-3.5" />
|
||||
{translate('dashboardPopout.filters.clearAll', 'Clear all filters')}
|
||||
{t('dashboardPopout.filters.clearAll', 'Clear all filters')}
|
||||
</DropdownMenuItem>
|
||||
</DropdownMenuContent>
|
||||
</DropdownMenu>
|
||||
|
||||
@@ -20,6 +20,7 @@ import {
|
||||
} from './agent-board-filtering'
|
||||
import './agent-board-transitions.css'
|
||||
import { translate } from '@/i18n/i18n'
|
||||
import { useTranslate, type Translate } from '@/i18n/use-translate'
|
||||
import type { AgentSubjectReadIntent } from '@/attention/agent-subject-read-actions'
|
||||
|
||||
/** Relay acknowledgments to the main renderer; optional calls tolerate dev-HMR preload skew until restart. */
|
||||
@@ -32,16 +33,16 @@ function revealAgentViaPopoutRelay(args: AgentRevealArgs): void {
|
||||
void window.api.dashboard.revealAgent?.(args)
|
||||
}
|
||||
|
||||
function bucketLabel(bucket: DashboardBucket): string {
|
||||
function bucketLabel(bucket: DashboardBucket, t: Translate = translate): string {
|
||||
switch (bucket) {
|
||||
case 'attention':
|
||||
return translate('dashboardPopout.bucket.attention', 'Needs You')
|
||||
return t('dashboardPopout.bucket.attention', 'Needs You')
|
||||
case 'working':
|
||||
return translate('dashboardPopout.bucket.working', 'Working')
|
||||
return t('dashboardPopout.bucket.working', 'Working')
|
||||
case 'done':
|
||||
return translate('dashboardPopout.bucket.done', 'Done')
|
||||
return t('dashboardPopout.bucket.done', 'Done')
|
||||
case 'idle':
|
||||
return translate('dashboardPopout.bucket.idle', 'Idle')
|
||||
return t('dashboardPopout.bucket.idle', 'Idle')
|
||||
}
|
||||
}
|
||||
|
||||
@@ -75,7 +76,8 @@ function KanbanColumn({
|
||||
now: number
|
||||
onOpenTerminal: (card: DashboardCard) => void
|
||||
}): React.JSX.Element {
|
||||
const label = bucketLabel(bucket)
|
||||
const t = useTranslate()
|
||||
const label = bucketLabel(bucket, t)
|
||||
|
||||
return (
|
||||
// Cards carry attention color; keep the column neutral to avoid duplicate signaling.
|
||||
@@ -94,7 +96,7 @@ function KanbanColumn({
|
||||
<div className="scrollbar-sleek flex min-h-0 flex-1 flex-col gap-2 overflow-y-auto px-2 pb-2">
|
||||
{cards.length === 0 ? (
|
||||
<p className="px-1 py-2 text-[11px] text-muted-foreground">
|
||||
{translate('dashboardPopout.bucket.empty', 'None')}
|
||||
{t('dashboardPopout.bucket.empty', 'None')}
|
||||
</p>
|
||||
) : (
|
||||
cards.map((card) => (
|
||||
@@ -137,6 +139,7 @@ export function AgentKanbanBoard({
|
||||
onBrowserForeground,
|
||||
headerActions
|
||||
}: AgentKanbanBoardProps): React.JSX.Element {
|
||||
const t = useTranslate()
|
||||
const visibleBuckets = useMemo(
|
||||
() =>
|
||||
DASHBOARD_BUCKET_ORDER.filter((bucket) => bucket !== 'idle' || snapshot.showIdle === true),
|
||||
@@ -242,11 +245,9 @@ export function AgentKanbanBoard({
|
||||
className={cn('relative flex flex-col bg-background text-foreground', containerClassName)}
|
||||
>
|
||||
<div className="flex shrink-0 items-center gap-2 border-b border-border px-4 py-2.5">
|
||||
<h1 className="text-[13px] font-semibold">
|
||||
{translate('dashboardPopout.title', 'Agents')}
|
||||
</h1>
|
||||
<h1 className="text-[13px] font-semibold">{t('dashboardPopout.title', 'Agents')}</h1>
|
||||
<span className="text-[11px] text-muted-foreground">
|
||||
{translate('dashboardPopout.total', '{{count}} total', {
|
||||
{t('dashboardPopout.total', '{{count}} total', {
|
||||
count: visibleCards.length
|
||||
})}
|
||||
</span>
|
||||
@@ -257,7 +258,7 @@ export function AgentKanbanBoard({
|
||||
<button
|
||||
type="button"
|
||||
onClick={onClose}
|
||||
aria-label={translate('dashboardPopout.close', 'Close dashboard')}
|
||||
aria-label={t('dashboardPopout.close', 'Close dashboard')}
|
||||
className="rounded-sm p-1 text-muted-foreground opacity-70 transition-opacity hover:opacity-100 focus-visible:ring-2 focus-visible:ring-ring focus-visible:outline-none"
|
||||
>
|
||||
<XIcon className="size-4" />
|
||||
|
||||
@@ -20,22 +20,23 @@ import {
|
||||
} from '../../../../shared/dashboard-snapshot'
|
||||
import type { RepoIcon } from '../../../../shared/repo-icon'
|
||||
import { translate } from '@/i18n/i18n'
|
||||
import { useTranslate, type Translate } from '@/i18n/use-translate'
|
||||
import { DashboardHostBadge } from './DashboardHostBadge'
|
||||
|
||||
function formatStartedAgo(startedAt: number, now: number): string {
|
||||
function formatStartedAgo(startedAt: number, now: number, t: Translate = translate): string {
|
||||
const seconds = Math.max(0, Math.floor((now - startedAt) / 1000))
|
||||
if (seconds < 60) {
|
||||
return translate('dashboardPopout.card.time.justNow', 'just now')
|
||||
return t('dashboardPopout.card.time.justNow', 'just now')
|
||||
}
|
||||
const minutes = Math.floor(seconds / 60)
|
||||
if (minutes < 60) {
|
||||
return translate('dashboardPopout.card.time.minutes', '{{count}}m', { count: minutes })
|
||||
return t('dashboardPopout.card.time.minutes', '{{count}}m', { count: minutes })
|
||||
}
|
||||
const hours = Math.floor(minutes / 60)
|
||||
if (hours < 24) {
|
||||
return translate('dashboardPopout.card.time.hours', '{{count}}h', { count: hours })
|
||||
return t('dashboardPopout.card.time.hours', '{{count}}h', { count: hours })
|
||||
}
|
||||
return translate('dashboardPopout.card.time.days', '{{count}}d', {
|
||||
return t('dashboardPopout.card.time.days', '{{count}}d', {
|
||||
count: Math.floor(hours / 24)
|
||||
})
|
||||
}
|
||||
@@ -45,8 +46,8 @@ function displayTimestamp(card: DashboardCard): number {
|
||||
return card.finishedAt ?? card.startedAt
|
||||
}
|
||||
|
||||
function formatSubagentCount(count: number): string {
|
||||
return translate('dashboardPopout.card.subagents', '{{count}} subagents', {
|
||||
function formatSubagentCount(count: number, t: Translate = translate): string {
|
||||
return t('dashboardPopout.card.subagents', '{{count}} subagents', {
|
||||
count,
|
||||
defaultValue_one: '{{count}} subagent'
|
||||
})
|
||||
@@ -133,6 +134,7 @@ const REVIEW_PRESENTATION = {
|
||||
} as const
|
||||
|
||||
function ReviewPill({ card }: { card: DashboardCard }): React.JSX.Element | null {
|
||||
const t = useTranslate()
|
||||
if (!card.review) {
|
||||
return null
|
||||
}
|
||||
@@ -141,13 +143,13 @@ function ReviewPill({ card }: { card: DashboardCard }): React.JSX.Element | null
|
||||
const title = (() => {
|
||||
switch (card.review.state) {
|
||||
case 'open':
|
||||
return translate('dashboardPopout.card.review.open', 'Open review')
|
||||
return t('dashboardPopout.card.review.open', 'Open review')
|
||||
case 'draft':
|
||||
return translate('dashboardPopout.card.review.draft', 'Draft review')
|
||||
return t('dashboardPopout.card.review.draft', 'Draft review')
|
||||
case 'merged':
|
||||
return translate('dashboardPopout.card.review.merged', 'Merged review')
|
||||
return t('dashboardPopout.card.review.merged', 'Merged review')
|
||||
case 'closed':
|
||||
return translate('dashboardPopout.card.review.closed', 'Closed review')
|
||||
return t('dashboardPopout.card.review.closed', 'Closed review')
|
||||
}
|
||||
})()
|
||||
return (
|
||||
@@ -198,6 +200,7 @@ export const AgentKanbanCard = memo(
|
||||
now,
|
||||
onOpenTerminal
|
||||
}: AgentKanbanCardProps): React.JSX.Element {
|
||||
const t = useTranslate()
|
||||
useTranslation()
|
||||
const [subagentsOpen, setSubagentsOpen] = useState(false)
|
||||
// Tint only attention and done so scan-worthy outcomes retain distinct meaning.
|
||||
@@ -247,7 +250,7 @@ export const AgentKanbanCard = memo(
|
||||
{card.lastUserMessage ? (
|
||||
<div className="line-clamp-1 text-[11px] leading-snug text-muted-foreground">
|
||||
<span className="font-medium text-foreground/45">
|
||||
{translate('dashboardPopout.card.you', 'You')}
|
||||
{t('dashboardPopout.card.you', 'You')}
|
||||
</span>{' '}
|
||||
{card.lastUserMessage}
|
||||
</div>
|
||||
@@ -286,7 +289,7 @@ export const AgentKanbanCard = memo(
|
||||
<ChevronRight
|
||||
className={cn('size-3 transition-transform', subagentsOpen && 'rotate-90')}
|
||||
/>
|
||||
{formatSubagentCount(card.subagents.length)}
|
||||
{formatSubagentCount(card.subagents.length, t)}
|
||||
</button>
|
||||
{subagentsOpen ? (
|
||||
<div className="ml-1 flex flex-col gap-1 border-l border-border pl-2">
|
||||
@@ -332,7 +335,7 @@ export const AgentKanbanCard = memo(
|
||||
<ReviewPill card={card} />
|
||||
{displayTimestamp(card) > 0 ? (
|
||||
<span className="ml-auto shrink-0 pl-1 tabular-nums">
|
||||
{formatStartedAgo(displayTimestamp(card), now)}
|
||||
{formatStartedAgo(displayTimestamp(card), now, t)}
|
||||
</span>
|
||||
) : null}
|
||||
</button>
|
||||
|
||||
@@ -12,7 +12,7 @@ import {
|
||||
} from '../../../../shared/dashboard-snapshot'
|
||||
import { AgentTerminalPreview } from './AgentTerminalPreview'
|
||||
import { terminalPreviewUnavailableMessage } from './terminal-preview-unavailable-message'
|
||||
import { translate } from '@/i18n/i18n'
|
||||
import { useTranslate } from '@/i18n/use-translate'
|
||||
import { cn } from '@/lib/utils'
|
||||
|
||||
/** Routing payload for focusing an agent's pane in the main window. */
|
||||
@@ -41,6 +41,7 @@ function AgentTerminalFrame({
|
||||
onReveal,
|
||||
onBrowserForeground
|
||||
}: AgentTerminalFrameProps): React.JSX.Element {
|
||||
const t = useTranslate()
|
||||
const reveal = (): void => {
|
||||
onReveal({
|
||||
repoId: card.repoId,
|
||||
@@ -71,7 +72,7 @@ function AgentTerminalFrame({
|
||||
onClick={() => onOpenChange(false)}
|
||||
>
|
||||
<XIcon className="size-4" />
|
||||
<span className="sr-only">{translate('dashboardPopout.terminal.close', 'Close')}</span>
|
||||
<span className="sr-only">{t('dashboardPopout.terminal.close', 'Close')}</span>
|
||||
</Button>
|
||||
</div>
|
||||
{card.ptyId ? (
|
||||
@@ -101,13 +102,13 @@ function AgentTerminalFrame({
|
||||
/>
|
||||
) : (
|
||||
<div className="min-h-0 flex-1 px-2.5 pb-2 text-[11px] text-muted-foreground">
|
||||
{terminalPreviewUnavailableMessage({ hostKind: card.hostKind })}
|
||||
{terminalPreviewUnavailableMessage({ hostKind: card.hostKind }, t)}
|
||||
</div>
|
||||
)}
|
||||
<div className="flex shrink-0 items-center gap-1.5 px-2.5 py-1.5">
|
||||
<Button type="button" variant="outline" size="xs" className="ml-auto" onClick={reveal}>
|
||||
<SquareArrowOutUpRight className="size-3" />
|
||||
{translate('dashboardPopout.terminal.focusWorktree', 'Open worktree')}
|
||||
{t('dashboardPopout.terminal.focusWorktree', 'Open worktree')}
|
||||
</Button>
|
||||
</div>
|
||||
</>
|
||||
|
||||
@@ -35,6 +35,7 @@ import {
|
||||
} from '@/components/link-actions/link-action-request'
|
||||
import { installDashboardPreviewLinkInteraction } from './dashboard-preview-link-interaction'
|
||||
import type { DashboardPreviewLinkSource } from './dashboard-preview-http-actions'
|
||||
import { useTranslate } from '@/i18n/use-translate'
|
||||
|
||||
const PREVIEW_SCROLLBACK_ROWS = 24
|
||||
// Preview snapshots bound history; pane scrollback would only cost memory.
|
||||
@@ -64,6 +65,7 @@ export function AgentTerminalPreview({
|
||||
linkSource?: DashboardPreviewLinkSource
|
||||
onBrowserForeground?: () => void
|
||||
}): React.JSX.Element {
|
||||
const t = useTranslate()
|
||||
const containerRef = useRef<HTMLDivElement>(null)
|
||||
const terminalRef = useRef<Terminal | null>(null)
|
||||
const [linkRequest, setLinkRequest] = useState<LinkActionRequest | null>(null)
|
||||
@@ -383,7 +385,7 @@ export function AgentTerminalPreview({
|
||||
) : null}
|
||||
{ptyGone ? (
|
||||
<div className="absolute inset-0 flex items-center justify-center px-2.5 py-8 text-center text-[11px] text-muted-foreground">
|
||||
{terminalPreviewUnavailableMessage({ ptyId })}
|
||||
{terminalPreviewUnavailableMessage({ ptyId }, t)}
|
||||
</div>
|
||||
) : null}
|
||||
<div
|
||||
|
||||
@@ -1,6 +1,7 @@
|
||||
import { Server } from 'lucide-react'
|
||||
import { Tooltip, TooltipContent, TooltipTrigger } from '@/components/ui/tooltip'
|
||||
import { translate } from '@/i18n/i18n'
|
||||
import { useTranslate, type Translate } from '@/i18n/use-translate'
|
||||
import { cn } from '@/lib/utils'
|
||||
import type { DashboardCardHostKind } from '../../../../shared/dashboard-snapshot'
|
||||
import { parseExecutionHostId, type ExecutionHostId } from '../../../../shared/execution-host'
|
||||
@@ -25,23 +26,26 @@ function fallbackHostLabel(executionHostId: ExecutionHostId | undefined): string
|
||||
return null
|
||||
}
|
||||
|
||||
export function dashboardHostTooltipLabel({
|
||||
hostKind,
|
||||
executionHostId,
|
||||
hostLabel
|
||||
}: Pick<DashboardHostBadgeProps, 'hostKind' | 'executionHostId' | 'hostLabel'>): string | null {
|
||||
export function dashboardHostTooltipLabel(
|
||||
{
|
||||
hostKind,
|
||||
executionHostId,
|
||||
hostLabel
|
||||
}: Pick<DashboardHostBadgeProps, 'hostKind' | 'executionHostId' | 'hostLabel'>,
|
||||
t: Translate = translate
|
||||
): string | null {
|
||||
if (hostKind !== 'ssh' && hostKind !== 'remote') {
|
||||
return null
|
||||
}
|
||||
const label = hostLabel?.trim() || fallbackHostLabel(executionHostId)
|
||||
if (hostKind === 'ssh') {
|
||||
return label
|
||||
? translate('dashboardPopout.host.sshNamed', 'SSH host · {{host}}', { host: label })
|
||||
: translate('dashboardPopout.host.ssh', 'SSH host')
|
||||
? t('dashboardPopout.host.sshNamed', 'SSH host · {{host}}', { host: label })
|
||||
: t('dashboardPopout.host.ssh', 'SSH host')
|
||||
}
|
||||
return label
|
||||
? translate('dashboardPopout.host.remoteNamed', 'Remote Orca host · {{host}}', { host: label })
|
||||
: translate('dashboardPopout.host.remote', 'Remote Orca host')
|
||||
? t('dashboardPopout.host.remoteNamed', 'Remote Orca host · {{host}}', { host: label })
|
||||
: t('dashboardPopout.host.remote', 'Remote Orca host')
|
||||
}
|
||||
|
||||
export function DashboardHostBadge({
|
||||
@@ -52,7 +56,8 @@ export function DashboardHostBadge({
|
||||
className,
|
||||
iconClassName
|
||||
}: DashboardHostBadgeProps): React.JSX.Element | null {
|
||||
const tooltipLabel = dashboardHostTooltipLabel({ hostKind, executionHostId, hostLabel })
|
||||
const t = useTranslate()
|
||||
const tooltipLabel = dashboardHostTooltipLabel({ hostKind, executionHostId, hostLabel }, t)
|
||||
if (!tooltipLabel) {
|
||||
return null
|
||||
}
|
||||
|
||||
@@ -1,4 +1,5 @@
|
||||
import { translate } from '@/i18n/i18n'
|
||||
import type { Translate } from '@/i18n/use-translate'
|
||||
import type { DashboardCard, DashboardFilterOption } from '../../../../shared/dashboard-snapshot'
|
||||
import type { DashboardReviewFilter } from './agent-board-filtering'
|
||||
|
||||
@@ -78,17 +79,17 @@ export const REVIEW_OPTIONS: readonly DashboardReviewFilter[] = [
|
||||
'none'
|
||||
]
|
||||
|
||||
export function reviewStateLabel(state: DashboardReviewFilter): string {
|
||||
export function reviewStateLabel(state: DashboardReviewFilter, t: Translate = translate): string {
|
||||
switch (state) {
|
||||
case 'open':
|
||||
return translate('dashboardPopout.filters.review.open', 'Open')
|
||||
return t('dashboardPopout.filters.review.open', 'Open')
|
||||
case 'draft':
|
||||
return translate('dashboardPopout.filters.review.draft', 'Draft')
|
||||
return t('dashboardPopout.filters.review.draft', 'Draft')
|
||||
case 'merged':
|
||||
return translate('dashboardPopout.filters.review.merged', 'Merged')
|
||||
return t('dashboardPopout.filters.review.merged', 'Merged')
|
||||
case 'closed':
|
||||
return translate('dashboardPopout.filters.review.closed', 'Closed')
|
||||
return t('dashboardPopout.filters.review.closed', 'Closed')
|
||||
case 'none':
|
||||
return translate('dashboardPopout.filters.review.none', 'No review')
|
||||
return t('dashboardPopout.filters.review.none', 'No review')
|
||||
}
|
||||
}
|
||||
|
||||
+10
-6
@@ -1,24 +1,28 @@
|
||||
import { translate } from '@/i18n/i18n'
|
||||
import type { Translate } from '@/i18n/use-translate'
|
||||
import type { DashboardCardHostKind } from '../../../../shared/dashboard-snapshot'
|
||||
import { parseAppSshPtyId } from '../../../../shared/ssh-pty-id'
|
||||
import { isRemoteRuntimePtyId } from '../../../../shared/remote-runtime/remote-runtime-pty-id'
|
||||
|
||||
/** Remote snapshot absence cannot prove pane exit because the client does not own the PTY; see docs/reference/ssh-execution-boundary.md. */
|
||||
export function terminalPreviewUnavailableMessage(source: {
|
||||
ptyId?: string | null
|
||||
hostKind?: DashboardCardHostKind
|
||||
}): string {
|
||||
export function terminalPreviewUnavailableMessage(
|
||||
source: {
|
||||
ptyId?: string | null
|
||||
hostKind?: DashboardCardHostKind
|
||||
},
|
||||
t: Translate = translate
|
||||
): string {
|
||||
const isRemote =
|
||||
source.hostKind === 'ssh' ||
|
||||
source.hostKind === 'remote' ||
|
||||
isRemoteRuntimePtyId(source.ptyId) ||
|
||||
(typeof source.ptyId === 'string' && parseAppSshPtyId(source.ptyId) !== null)
|
||||
return isRemote
|
||||
? translate(
|
||||
? t(
|
||||
'dashboardPopout.terminal.remotePreviewUnavailable',
|
||||
'No preview for this remote session — open the workspace to view the terminal.'
|
||||
)
|
||||
: translate(
|
||||
: t(
|
||||
'dashboardPopout.terminal.previewUnavailable',
|
||||
'No preview available — open the workspace to view this agent.'
|
||||
)
|
||||
|
||||
@@ -17,7 +17,7 @@ import {
|
||||
import { AgentDashboardSettingsMenu } from './AgentDashboardSettingsMenu'
|
||||
import { useLiveDashboardSnapshot } from './useLiveDashboardSnapshot'
|
||||
import { useAgentDashboardTelemetry } from '../dashboard-popout/use-agent-dashboard-telemetry'
|
||||
import { translate } from '@/i18n/i18n'
|
||||
import { useTranslate } from '@/i18n/use-translate'
|
||||
import { trackSidebarLiveWidth } from '@/components/sidebar/sidebar-live-width'
|
||||
|
||||
// Nested interactive overlays own Escape before the companion sheet; exclude the sheet’s own role=dialog marker.
|
||||
@@ -94,6 +94,7 @@ export function AgentDashboardDrawer({
|
||||
leftSidebarStyle,
|
||||
statusBarVisible
|
||||
}: AgentDashboardDrawerProps): React.JSX.Element {
|
||||
const t = useTranslate()
|
||||
const open = useAppStore((s) => s.agentDashboardDrawerOpen)
|
||||
const setOpen = useAppStore((s) => s.setAgentDashboardDrawerOpen)
|
||||
const sidebarOpen = useAppStore((s) => s.sidebarOpen)
|
||||
@@ -243,7 +244,7 @@ export function AgentDashboardDrawer({
|
||||
onPointerDownOutside={guardSidebarInteraction}
|
||||
onInteractOutside={guardSidebarInteraction}
|
||||
>
|
||||
<SheetTitle className="sr-only">{translate('dashboardPopout.title', 'Agents')}</SheetTitle>
|
||||
<SheetTitle className="sr-only">{t('dashboardPopout.title', 'Agents')}</SheetTitle>
|
||||
{/* Closed SheetContent unmounts the live snapshot body, avoiding closed-path derivation. */}
|
||||
<div ref={boardRef} className="flex min-h-0 flex-1 flex-col">
|
||||
<AgentDashboardDrawerBody
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
import type { GlobalSettings } from '../../../../shared/global-settings-types'
|
||||
import { translate } from '@/i18n/i18n'
|
||||
import { useTranslate } from '@/i18n/use-translate'
|
||||
import { Label } from '../ui/label'
|
||||
import { SearchableSetting } from '../settings/section-layout/SearchableSetting'
|
||||
import {
|
||||
@@ -18,33 +18,28 @@ export function AgentDashboardExperimentalSetting({
|
||||
settings,
|
||||
updateSettings
|
||||
}: AgentDashboardExperimentalSettingProps): React.JSX.Element {
|
||||
const t = useTranslate()
|
||||
const enabled = settings.experimentalAgentDashboardPopout === true
|
||||
const mode = settings.experimentalAgentDashboardMode ?? 'in-window'
|
||||
|
||||
return (
|
||||
<SearchableSetting
|
||||
title={translate(
|
||||
'auto.components.settings.ExperimentalPane.agentDashboard.title',
|
||||
'Agent Dashboard'
|
||||
)}
|
||||
description={translate(
|
||||
title={t('auto.components.settings.ExperimentalPane.agentDashboard.title', 'Agent Dashboard')}
|
||||
description={t(
|
||||
'auto.components.settings.ExperimentalPane.agentDashboard.description',
|
||||
'Kanban board for monitoring agents across worktrees, in-window or as a pop-out.'
|
||||
)}
|
||||
keywords={getExperimentalSearchEntry().agentDashboard.keywords}
|
||||
keywords={getExperimentalSearchEntry(t).agentDashboard.keywords}
|
||||
className="space-y-3 py-3"
|
||||
id="experimental-agent-dashboard"
|
||||
>
|
||||
<div className="flex items-start justify-between gap-4">
|
||||
<div className="min-w-0 shrink space-y-0.5">
|
||||
<Label>
|
||||
{translate(
|
||||
'auto.components.settings.ExperimentalPane.agentDashboard.title',
|
||||
'Agent Dashboard'
|
||||
)}
|
||||
{t('auto.components.settings.ExperimentalPane.agentDashboard.title', 'Agent Dashboard')}
|
||||
</Label>
|
||||
<p className="text-xs text-muted-foreground">
|
||||
{translate(
|
||||
{t(
|
||||
'auto.components.settings.ExperimentalPane.agentDashboard.copy',
|
||||
'Adds an Agent Dashboard entry to the left sidebar. Monitor agents that need you, are working, or are done, with optional idle agents.'
|
||||
)}
|
||||
@@ -52,7 +47,7 @@ export function AgentDashboardExperimentalSetting({
|
||||
</div>
|
||||
<SettingsSwitch
|
||||
checked={enabled}
|
||||
ariaLabel={translate(
|
||||
ariaLabel={t(
|
||||
'auto.components.settings.ExperimentalPane.agentDashboard.toggleLabel',
|
||||
'Toggle Agent Dashboard'
|
||||
)}
|
||||
@@ -64,13 +59,10 @@ export function AgentDashboardExperimentalSetting({
|
||||
<div className="flex items-start justify-between gap-4">
|
||||
<div className="min-w-0 shrink space-y-0.5">
|
||||
<Label>
|
||||
{translate(
|
||||
'auto.components.settings.ExperimentalPane.agentDashboard.modeLabel',
|
||||
'Open as'
|
||||
)}
|
||||
{t('auto.components.settings.ExperimentalPane.agentDashboard.modeLabel', 'Open as')}
|
||||
</Label>
|
||||
<p className="text-xs text-muted-foreground">
|
||||
{translate(
|
||||
{t(
|
||||
'auto.components.settings.ExperimentalPane.agentDashboard.modeCopy',
|
||||
'Show the dashboard as an in-window board beside the sidebar or a separate pop-out window.'
|
||||
)}
|
||||
@@ -79,7 +71,7 @@ export function AgentDashboardExperimentalSetting({
|
||||
<SettingsSegmentedControl
|
||||
value={mode}
|
||||
onChange={(next) => updateSettings({ experimentalAgentDashboardMode: next })}
|
||||
ariaLabel={translate(
|
||||
ariaLabel={t(
|
||||
'auto.components.settings.ExperimentalPane.agentDashboard.modeAriaLabel',
|
||||
'Agent Dashboard open mode'
|
||||
)}
|
||||
@@ -87,14 +79,14 @@ export function AgentDashboardExperimentalSetting({
|
||||
options={[
|
||||
{
|
||||
value: 'in-window',
|
||||
label: translate(
|
||||
label: t(
|
||||
'auto.components.settings.ExperimentalPane.agentDashboard.modeInWindow',
|
||||
'In-window'
|
||||
)
|
||||
},
|
||||
{
|
||||
value: 'popout',
|
||||
label: translate(
|
||||
label: t(
|
||||
'auto.components.settings.ExperimentalPane.agentDashboard.modePopout',
|
||||
'Pop-out'
|
||||
)
|
||||
|
||||
@@ -13,7 +13,7 @@ import {
|
||||
SettingsSwitch
|
||||
} from '../settings/form-controls/SettingsFormControls'
|
||||
import type { AgentDashboardMode } from '../../../../shared/ui-chrome-types'
|
||||
import { translate } from '@/i18n/i18n'
|
||||
import { useTranslate } from '@/i18n/use-translate'
|
||||
import { canOpenAgentDashboardPopout } from './agent-dashboard-open-mode'
|
||||
|
||||
type AgentDashboardSettingsMenuProps = {
|
||||
@@ -28,6 +28,7 @@ export function AgentDashboardSettingsMenu({
|
||||
onSwitchToPopout,
|
||||
onOpenChange
|
||||
}: AgentDashboardSettingsMenuProps): React.JSX.Element {
|
||||
const t = useTranslate()
|
||||
const mode = useAppStore((s) => s.settings?.experimentalAgentDashboardMode ?? 'in-window')
|
||||
const showIdle = useAppStore((s) => s.settings?.experimentalAgentDashboardShowIdle === true)
|
||||
const updateSettings = useAppStore((s) => s.updateSettings)
|
||||
@@ -50,7 +51,7 @@ export function AgentDashboardSettingsMenu({
|
||||
<Button
|
||||
variant="ghost"
|
||||
size="icon-xs"
|
||||
aria-label={translate('dashboardPopout.settingsLabel', 'Agent Dashboard settings')}
|
||||
aria-label={t('dashboardPopout.settingsLabel', 'Agent Dashboard settings')}
|
||||
className="text-muted-foreground"
|
||||
>
|
||||
<Settings className="size-3.5" />
|
||||
@@ -58,7 +59,7 @@ export function AgentDashboardSettingsMenu({
|
||||
</DropdownMenuTrigger>
|
||||
</TooltipTrigger>
|
||||
<TooltipContent side="top" sideOffset={4}>
|
||||
{translate('dashboardPopout.settingsTooltip', 'Board settings')}
|
||||
{t('dashboardPopout.settingsTooltip', 'Board settings')}
|
||||
</TooltipContent>
|
||||
</Tooltip>
|
||||
<DropdownMenuContent align="end" sideOffset={8} collisionPadding={8} className="w-72 p-2">
|
||||
@@ -67,13 +68,13 @@ export function AgentDashboardSettingsMenu({
|
||||
<div className="flex items-start justify-between gap-3 rounded-md px-1.5 py-1.5">
|
||||
<span className="min-w-0 space-y-0.5">
|
||||
<span className="block text-[12px] font-medium leading-4 text-foreground">
|
||||
{translate(
|
||||
{t(
|
||||
'auto.components.settings.ExperimentalPane.agentDashboard.modeLabel',
|
||||
'Open as'
|
||||
)}
|
||||
</span>
|
||||
<span className="block text-[11px] leading-4 text-muted-foreground">
|
||||
{translate(
|
||||
{t(
|
||||
'auto.components.settings.ExperimentalPane.agentDashboard.modeCopy',
|
||||
'Show the dashboard as an in-window board beside the sidebar or a separate pop-out window.'
|
||||
)}
|
||||
@@ -84,7 +85,7 @@ export function AgentDashboardSettingsMenu({
|
||||
<SettingsSegmentedControl
|
||||
value={mode}
|
||||
onChange={handleModeChange}
|
||||
ariaLabel={translate(
|
||||
ariaLabel={t(
|
||||
'auto.components.settings.ExperimentalPane.agentDashboard.modeAriaLabel',
|
||||
'Agent Dashboard open mode'
|
||||
)}
|
||||
@@ -93,14 +94,14 @@ export function AgentDashboardSettingsMenu({
|
||||
options={[
|
||||
{
|
||||
value: 'in-window',
|
||||
label: translate(
|
||||
label: t(
|
||||
'auto.components.settings.ExperimentalPane.agentDashboard.modeInWindow',
|
||||
'In-window'
|
||||
)
|
||||
},
|
||||
{
|
||||
value: 'popout',
|
||||
label: translate(
|
||||
label: t(
|
||||
'auto.components.settings.ExperimentalPane.agentDashboard.modePopout',
|
||||
'Pop-out'
|
||||
)
|
||||
@@ -114,10 +115,10 @@ export function AgentDashboardSettingsMenu({
|
||||
<div className="flex items-start justify-between gap-3 rounded-md px-1.5 py-1.5">
|
||||
<span className="min-w-0 space-y-0.5">
|
||||
<span className="block text-[12px] font-medium leading-4 text-foreground">
|
||||
{translate('dashboardPopout.settings.showIdle', 'Show idle agents')}
|
||||
{t('dashboardPopout.settings.showIdle', 'Show idle agents')}
|
||||
</span>
|
||||
<span className="block text-[11px] leading-4 text-muted-foreground">
|
||||
{translate(
|
||||
{t(
|
||||
'dashboardPopout.settings.showIdleCopy',
|
||||
'Include agents that have gone quiet for 30 minutes without reporting completion. Hidden by default.'
|
||||
)}
|
||||
@@ -130,7 +131,7 @@ export function AgentDashboardSettingsMenu({
|
||||
experimentalAgentDashboardShowIdle: !showIdle
|
||||
})
|
||||
}}
|
||||
ariaLabel={translate('dashboardPopout.settings.showIdle', 'Show idle agents')}
|
||||
ariaLabel={t('dashboardPopout.settings.showIdle', 'Show idle agents')}
|
||||
/>
|
||||
</div>
|
||||
</DropdownMenuContent>
|
||||
|
||||
@@ -2,7 +2,7 @@ import React, { useCallback } from 'react'
|
||||
import { ChevronDown } from 'lucide-react'
|
||||
import { cn } from '@/lib/utils'
|
||||
import { Button } from '@/components/ui/button'
|
||||
import { translate } from '@/i18n/i18n'
|
||||
import { useTranslate } from '@/i18n/use-translate'
|
||||
|
||||
type Props = {
|
||||
childAgentCount?: number
|
||||
@@ -17,6 +17,7 @@ export function DashboardAgentChildDisclosure({
|
||||
onToggleChildAgents,
|
||||
timestamp
|
||||
}: Props) {
|
||||
const t = useTranslate()
|
||||
const hasChildDisclosure =
|
||||
typeof childAgentCount === 'number' &&
|
||||
childAgentCount > 0 &&
|
||||
@@ -66,12 +67,12 @@ export function DashboardAgentChildDisclosure({
|
||||
className="col-start-1 row-start-1 -mr-1.5"
|
||||
aria-label={
|
||||
childAgentsExpanded
|
||||
? translate(
|
||||
? t(
|
||||
'auto.components.dashboard.DashboardAgentChildDisclosure.hideChildAgents',
|
||||
'Hide {{count}} child agents',
|
||||
{ count: childAgentCount, defaultValue_one: 'Hide {{count}} child agent' }
|
||||
)
|
||||
: translate(
|
||||
: t(
|
||||
'auto.components.dashboard.DashboardAgentChildDisclosure.showChildAgents',
|
||||
'Show {{count}} child agents',
|
||||
{ count: childAgentCount, defaultValue_one: 'Show {{count}} child agent' }
|
||||
|
||||
@@ -37,14 +37,15 @@ import {
|
||||
agentChildRowNoUpdateLabel
|
||||
} from '@/components/agent-child-row-text'
|
||||
import { translate } from '@/i18n/i18n'
|
||||
import { useTranslate, type Translate } from '@/i18n/use-translate'
|
||||
import { useUiLanguageRevision } from '@/i18n/use-ui-language-revision'
|
||||
|
||||
function formatTimeAgo(ts: number, now: number): string {
|
||||
function formatTimeAgo(ts: number, now: number, t: Translate = translate): string {
|
||||
const delta = now - ts
|
||||
if (delta < 60_000) {
|
||||
return translate('auto.components.dashboard.DashboardAgentRow.justNow', 'just now')
|
||||
return t('auto.components.dashboard.DashboardAgentRow.justNow', 'just now')
|
||||
}
|
||||
return translate('auto.components.dashboard.DashboardAgentRow.timeAgo', '{{duration}} ago', {
|
||||
return t('auto.components.dashboard.DashboardAgentRow.timeAgo', '{{duration}} ago', {
|
||||
duration: formatCompactDuration(delta)
|
||||
})
|
||||
}
|
||||
@@ -119,6 +120,7 @@ const DashboardAgentRow = React.memo(function DashboardAgentRow({
|
||||
sendTargetDisabledReason,
|
||||
onSendTargetClick
|
||||
}: Props) {
|
||||
const t = useTranslate()
|
||||
useUiLanguageRevision()
|
||||
const view = useClaudeAgentRowView(agent)
|
||||
const highlighted = view ? view.current && view.focused : isFocusedPane
|
||||
@@ -173,7 +175,7 @@ const DashboardAgentRow = React.memo(function DashboardAgentRow({
|
||||
const isMonitoring = agent.state === 'working' && agent.entry.workingMode === 'monitoring'
|
||||
const isWorking = agent.state === 'working' && !isMonitoring
|
||||
// Show tool details only for working/approval waits; ended or monitoring turns have stale last-tool metadata.
|
||||
const stoppingLabel = agentRowStoppingLabel(agent.entry, agent.state)
|
||||
const stoppingLabel = agentRowStoppingLabel(agent.entry, agent.state, t)
|
||||
const showsTool = showsAgentToolPreview(agent.state) && !isMonitoring && stoppingLabel === null
|
||||
const { presentation, dotStateOverride } = resolveRowProgramStatus(agent)
|
||||
const toolName = showsTool ? (agent.entry.toolName?.trim() ?? '') : ''
|
||||
@@ -183,7 +185,7 @@ const DashboardAgentRow = React.memo(function DashboardAgentRow({
|
||||
: ''
|
||||
// Why: a child row's message line is the model's, so a child that ended without an outcome says so.
|
||||
const lastAssistantMessage = agent.childRow
|
||||
? agentChildRowMessageLine(agent.childRow)
|
||||
? agentChildRowMessageLine(agent.childRow, t)
|
||||
: (agent.entry.lastAssistantMessage?.trim() ?? '')
|
||||
const isInterrupted = agentVerdictDisplayMark(agent.entry) === 'interrupted'
|
||||
const lineage = agent.lineage
|
||||
@@ -192,7 +194,7 @@ const DashboardAgentRow = React.memo(function DashboardAgentRow({
|
||||
const participatesInLineage = isLineageChild || lineageChildCount > 0
|
||||
const identityTitle =
|
||||
lineageChildCount > 0
|
||||
? translate(
|
||||
? t(
|
||||
'auto.components.dashboard.DashboardAgentRow.dispatchedAgents',
|
||||
'{{agentType}} - dispatched {{count}} agents',
|
||||
{
|
||||
@@ -212,20 +214,20 @@ const DashboardAgentRow = React.memo(function DashboardAgentRow({
|
||||
// Unverifiable rows expose elapsed silence in their timestamp slot; do not hide their only content in a tooltip.
|
||||
const noUpdateLabel = dotState === 'unverifiable' ? rowNoUpdateLabel(agent, now) : null
|
||||
|
||||
const startedTimeAgo = startedAt !== null ? formatTimeAgo(startedAt, now) : null
|
||||
const doneTimeAgo = doneAt !== null ? formatTimeAgo(doneAt, now) : null
|
||||
const startedTimeAgo = startedAt !== null ? formatTimeAgo(startedAt, now, t) : null
|
||||
const doneTimeAgo = doneAt !== null ? formatTimeAgo(doneAt, now, t) : null
|
||||
const relativeTimestamp = noUpdateLabel ?? doneTimeAgo ?? startedTimeAgo
|
||||
const tsParts: string[] = noUpdateLabel ? [noUpdateLabel] : []
|
||||
if (startedTimeAgo !== null) {
|
||||
tsParts.push(
|
||||
translate('auto.components.dashboard.DashboardAgentRow.startedAgo', 'started {{time}}', {
|
||||
t('auto.components.dashboard.DashboardAgentRow.startedAgo', 'started {{time}}', {
|
||||
time: startedTimeAgo
|
||||
})
|
||||
)
|
||||
}
|
||||
if (doneTimeAgo !== null) {
|
||||
tsParts.push(
|
||||
translate('auto.components.dashboard.DashboardAgentRow.doneAgo', 'done {{time}}', {
|
||||
t('auto.components.dashboard.DashboardAgentRow.doneAgo', 'done {{time}}', {
|
||||
time: doneTimeAgo
|
||||
})
|
||||
)
|
||||
@@ -254,10 +256,7 @@ const DashboardAgentRow = React.memo(function DashboardAgentRow({
|
||||
aria-current={view?.current ? 'true' : undefined}
|
||||
aria-description={
|
||||
opensClaudeParentConversation(agent)
|
||||
? translate(
|
||||
'auto.lib.claudeAgentRowView.openParentConversation',
|
||||
'Open parent conversation'
|
||||
)
|
||||
? t('auto.lib.claudeAgentRowView.openParentConversation', 'Open parent conversation')
|
||||
: undefined
|
||||
}
|
||||
data-agent-send-target={sendTargetStatus}
|
||||
|
||||
@@ -4,7 +4,7 @@ import {
|
||||
CommentMarkdownAsync,
|
||||
preloadCommentMarkdown
|
||||
} from '@/components/sidebar/comment-markdown-lazy'
|
||||
import { translate } from '@/i18n/i18n'
|
||||
import { useTranslate } from '@/i18n/use-translate'
|
||||
|
||||
type DashboardAgentRowMessageProps = {
|
||||
expanded: boolean
|
||||
@@ -21,6 +21,7 @@ export function DashboardAgentRowMessage({
|
||||
stoppedByUser,
|
||||
lastAssistantMessage
|
||||
}: DashboardAgentRowMessageProps): React.JSX.Element | null {
|
||||
const t = useTranslate()
|
||||
// Warm the sidebar’s only boot-visible markdown chunk on mount rather than waiting for text.
|
||||
useEffect(preloadCommentMarkdown, [])
|
||||
// Reserve the collapsed message slot so incoming/cleared assistant text cannot shift row height.
|
||||
@@ -37,20 +38,14 @@ export function DashboardAgentRowMessage({
|
||||
className="shrink-0 text-[10px] leading-snug text-muted-foreground/80"
|
||||
aria-label={
|
||||
stoppedByUser
|
||||
? translate(
|
||||
? t(
|
||||
'auto.components.dashboard.DashboardAgentRowMessage.1ec01cef03',
|
||||
'Interrupted by user'
|
||||
)
|
||||
: translate(
|
||||
'auto.components.activity.ActivityPrototypePage.interrupted',
|
||||
'Interrupted'
|
||||
)
|
||||
: t('auto.components.activity.ActivityPrototypePage.interrupted', 'Interrupted')
|
||||
}
|
||||
>
|
||||
{translate(
|
||||
'auto.components.dashboard.DashboardAgentRowMessage.0a01046763',
|
||||
'interrupted'
|
||||
)}
|
||||
{t('auto.components.dashboard.DashboardAgentRowMessage.0a01046763', 'interrupted')}
|
||||
</span>
|
||||
) : null}
|
||||
{lastAssistantMessage ? (
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
import React, { useCallback } from 'react'
|
||||
import { ChevronDown, Send, X } from 'lucide-react'
|
||||
import { translate } from '@/i18n/i18n'
|
||||
import { useTranslate } from '@/i18n/use-translate'
|
||||
import { cn } from '@/lib/utils'
|
||||
import { DashboardAgentChildDisclosure } from './DashboardAgentChildDisclosure'
|
||||
|
||||
@@ -34,6 +34,7 @@ export function DashboardAgentRowTrailingControls({
|
||||
onToggleExpanded,
|
||||
onSendTargetClick
|
||||
}: DashboardAgentRowTrailingControlsProps): React.JSX.Element {
|
||||
const t = useTranslate()
|
||||
// Nested controls must not activate the agent row or enclosing worktree card.
|
||||
const stopMouseDown = useCallback((event: React.MouseEvent) => {
|
||||
event.stopPropagation()
|
||||
@@ -91,17 +92,14 @@ export function DashboardAgentRowTrailingControls({
|
||||
'worktree-agent-send-target-button z-10 inline-flex h-5 items-center gap-1 rounded-md border px-1.5 text-[10px] font-medium leading-none transition-[background-color,border-color,color,opacity] whitespace-nowrap',
|
||||
sendTargetStatus === 'sending' && 'cursor-progress opacity-75'
|
||||
)}
|
||||
aria-label={translate(
|
||||
'auto.components.dashboard.DashboardAgentRow.0272969e28',
|
||||
'Send to this agent'
|
||||
)}
|
||||
title={translate(
|
||||
aria-label={t(
|
||||
'auto.components.dashboard.DashboardAgentRow.0272969e28',
|
||||
'Send to this agent'
|
||||
)}
|
||||
title={t('auto.components.dashboard.DashboardAgentRow.0272969e28', 'Send to this agent')}
|
||||
>
|
||||
<Send className="size-3" />
|
||||
<span>{translate('auto.components.dashboard.DashboardAgentRow.912e136cd9', 'Send')}</span>
|
||||
<span>{t('auto.components.dashboard.DashboardAgentRow.912e136cd9', 'Send')}</span>
|
||||
</button>
|
||||
)}
|
||||
{!hasChildDisclosure && !sendTargetStatus && hideDismiss && relativeTimestamp !== null && (
|
||||
@@ -135,11 +133,11 @@ export function DashboardAgentRowTrailingControls({
|
||||
'can-hover:opacity-0 transition-opacity duration-150',
|
||||
'group-hover/agent-row:opacity-100 focus-visible:opacity-100'
|
||||
)}
|
||||
aria-label={translate(
|
||||
aria-label={t(
|
||||
'auto.components.dashboard.DashboardAgentRow.b06e13fcf7',
|
||||
'Dismiss agent'
|
||||
)}
|
||||
title={translate('auto.components.dashboard.DashboardAgentRow.5ae84475cc', 'Dismiss')}
|
||||
title={t('auto.components.dashboard.DashboardAgentRow.5ae84475cc', 'Dismiss')}
|
||||
>
|
||||
<X className="size-3.5" />
|
||||
</button>
|
||||
@@ -156,11 +154,8 @@ export function DashboardAgentRowTrailingControls({
|
||||
'can-hover:opacity-0 transition-opacity duration-150',
|
||||
'group-hover/agent-row:opacity-100 focus-visible:opacity-100'
|
||||
)}
|
||||
aria-label={translate(
|
||||
'auto.components.dashboard.DashboardAgentRow.b06e13fcf7',
|
||||
'Dismiss agent'
|
||||
)}
|
||||
title={translate('auto.components.dashboard.DashboardAgentRow.5ae84475cc', 'Dismiss')}
|
||||
aria-label={t('auto.components.dashboard.DashboardAgentRow.b06e13fcf7', 'Dismiss agent')}
|
||||
title={t('auto.components.dashboard.DashboardAgentRow.5ae84475cc', 'Dismiss')}
|
||||
>
|
||||
<X className="size-3.5" />
|
||||
</button>
|
||||
@@ -186,14 +181,8 @@ export function DashboardAgentRowTrailingControls({
|
||||
className="inline-flex shrink-0 items-center justify-center text-muted-foreground/60 hover:text-foreground"
|
||||
aria-label={
|
||||
expanded
|
||||
? translate(
|
||||
'auto.components.dashboard.DashboardAgentRow.a41fb5376e',
|
||||
'Collapse details'
|
||||
)
|
||||
: translate(
|
||||
'auto.components.dashboard.DashboardAgentRow.a743da52ff',
|
||||
'Expand details'
|
||||
)
|
||||
? t('auto.components.dashboard.DashboardAgentRow.a41fb5376e', 'Collapse details')
|
||||
: t('auto.components.dashboard.DashboardAgentRow.a743da52ff', 'Expand details')
|
||||
}
|
||||
aria-expanded={expanded}
|
||||
>
|
||||
|
||||
@@ -50,6 +50,7 @@ import { buildDashboardSnapshotFilterOptions } from './dashboard-snapshot-filter
|
||||
import { groupSubagentsByParentPaneKey } from './dashboard-subagent-cards'
|
||||
import { selectDashboardOrchestration } from './dashboard-orchestration-selection'
|
||||
import { dashboardRowBucketProjection, readAgentRowTurnRecords } from './dashboard-row-bucket'
|
||||
import type { Translate } from '@/i18n/use-translate'
|
||||
|
||||
/** Keep builder inputs as a Pick so tests need only construct the store slices read here. */
|
||||
export type DashboardSnapshotState = Pick<
|
||||
@@ -95,7 +96,8 @@ export function buildDashboardSnapshot(
|
||||
rowsCache?: WorktreeAgentRowsCache
|
||||
/** Freshness token for rowsCache (agentStatusEpoch); required for the cache to be safe. */
|
||||
rowsGeneration?: unknown
|
||||
} = {}
|
||||
} = {},
|
||||
t?: Translate
|
||||
): DashboardSnapshot {
|
||||
const cards: DashboardCard[] = []
|
||||
const workspaces: DashboardWorkspace[] | undefined =
|
||||
@@ -141,7 +143,7 @@ export function buildDashboardSnapshot(
|
||||
? groupSubagentsByParentPaneKey(rows)
|
||||
: undefined
|
||||
const context = includeCardDetails
|
||||
? resolveDashboardCardContext(state, repo, worktree)
|
||||
? resolveDashboardCardContext(state, repo, worktree, t)
|
||||
: undefined
|
||||
if (workspaces && workspaces.length < DASHBOARD_MAX_MAP_WORKSPACES) {
|
||||
const hostMetadata = dashboardCardMapWorkspaceMetadata(
|
||||
|
||||
@@ -15,6 +15,7 @@ import {
|
||||
getParentPrChecksGitHubPRCacheEntry
|
||||
} from '../right-sidebar/hosted-review/folder-checks/parent-pr-checks-github-pr-cache'
|
||||
import { canUseParentPrChecksHostedReviewCacheEntry } from '../right-sidebar/hosted-review/folder-checks/parent-pr-checks-hosted-review-cache'
|
||||
import type { Translate } from '@/i18n/use-translate'
|
||||
|
||||
export type DashboardCardContextState = Partial<
|
||||
Pick<AppState, 'hostedReviewCache' | 'prCache' | 'settings' | 'workspaceStatuses'>
|
||||
@@ -39,7 +40,8 @@ function hasLinkedReview(worktree: Worktree): boolean {
|
||||
function resolveReview(
|
||||
state: DashboardCardContextState,
|
||||
repo: Repo | null,
|
||||
worktree: Worktree
|
||||
worktree: Worktree,
|
||||
t?: Translate
|
||||
): DashboardCardReview | undefined {
|
||||
if (!repo || !state.hostedReviewCache || !state.prCache || repo.kind === 'folder') {
|
||||
return undefined
|
||||
@@ -60,7 +62,7 @@ function resolveReview(
|
||||
const hostedReview = hostedReviewEntry?.data
|
||||
if (
|
||||
hostedReview &&
|
||||
canUseParentPrChecksHostedReviewCacheEntry(worktree, hostedReview, hostedReviewEntry)
|
||||
canUseParentPrChecksHostedReviewCacheEntry(worktree, hostedReview, hostedReviewEntry, t)
|
||||
) {
|
||||
return { number: hostedReview.number, state: hostedReview.state }
|
||||
}
|
||||
@@ -79,14 +81,15 @@ function resolveReview(
|
||||
export function resolveDashboardCardContext(
|
||||
state: DashboardCardContextState,
|
||||
repo: Repo | null,
|
||||
worktree: Worktree
|
||||
worktree: Worktree,
|
||||
t?: Translate
|
||||
): DashboardCardContext {
|
||||
const statuses =
|
||||
state.workspaceStatuses && state.workspaceStatuses.length > 0
|
||||
? state.workspaceStatuses
|
||||
: DEFAULT_WORKSPACE_STATUSES
|
||||
const workspaceStatusId = getWorkspaceStatus(worktree, statuses)
|
||||
const review = resolveReview(state, repo, worktree)
|
||||
const review = resolveReview(state, repo, worktree, t)
|
||||
return {
|
||||
workspaceStatus:
|
||||
statuses.find((status) => status.id === workspaceStatusId) ?? DEFAULT_WORKSPACE_STATUSES[0],
|
||||
|
||||
@@ -7,6 +7,7 @@ import { captureInsertionTarget } from './dictation-insertion-target'
|
||||
import { createDictationTranscriptDelivery } from './dictation-transcript-delivery'
|
||||
import { recordStoppedSession, waitForStoppedSession } from './dictation-stopped-sessions'
|
||||
import { translate } from '@/i18n/i18n'
|
||||
import { useTranslate } from '@/i18n/use-translate'
|
||||
import {
|
||||
showDictationDisabledToast,
|
||||
showDictationNoModelToast,
|
||||
@@ -30,6 +31,7 @@ import {
|
||||
} from '../../../../shared/dictation-session-machine'
|
||||
|
||||
export function DictationController() {
|
||||
const t = useTranslate()
|
||||
const dictationState = useAppStore((s) => s.dictationState)
|
||||
const setDictationState = useAppStore((s) => s.setDictationState)
|
||||
const setPartialTranscript = useAppStore((s) => s.setPartialTranscript)
|
||||
@@ -314,11 +316,9 @@ export function DictationController() {
|
||||
recordStoppedSession(sessionId, stoppedSessionIdsRef, stoppedResolversRef),
|
||||
onError: (sessionId, error) => {
|
||||
toast.error(
|
||||
translate(
|
||||
'auto.components.dictation.DictationController.de136f1199',
|
||||
'Speech error: {{value0}}',
|
||||
{ value0: error }
|
||||
)
|
||||
t('auto.components.dictation.DictationController.de136f1199', 'Speech error: {{value0}}', {
|
||||
value0: error
|
||||
})
|
||||
)
|
||||
const { run } = dispatch({ type: 'abandon' })
|
||||
stopCapture()
|
||||
|
||||
@@ -2,7 +2,7 @@ import { ShortcutKeyCombo } from '@/components/ShortcutKeyCombo'
|
||||
import { Button } from '@/components/ui/button'
|
||||
import { Tooltip, TooltipContent, TooltipTrigger } from '@/components/ui/tooltip'
|
||||
import { useShortcutKeyDetails } from '@/hooks/useShortcutLabel'
|
||||
import { translate } from '@/i18n/i18n'
|
||||
import { useTranslate } from '@/i18n/use-translate'
|
||||
import { cn } from '@/lib/utils'
|
||||
import { useAppStore } from '@/store'
|
||||
import { Square } from 'lucide-react'
|
||||
@@ -12,6 +12,7 @@ import { useDictationMeter } from './dictation-meter-store'
|
||||
|
||||
// captureLive: the mic is recording (and buffering) while the speech engine is still starting.
|
||||
export function DictationIndicator({ captureLive }: { captureLive: boolean }) {
|
||||
const t = useTranslate()
|
||||
const dictationState = useAppStore((state) => state.dictationState)
|
||||
const partialTranscript = useAppStore((state) => state.partialTranscript)
|
||||
const dictationMeter = useDictationMeter()
|
||||
@@ -28,23 +29,20 @@ export function DictationIndicator({ captureLive }: { captureLive: boolean }) {
|
||||
const isClipping = isListening && dictationMeter.isClipping
|
||||
const isSpeaking = isListening && dictationMeter.isSpeaking && !isClipping
|
||||
const lifecycleLabel = isListening
|
||||
? translate('auto.components.dictation.DictationIndicator.3de5a129e7', 'Listening')
|
||||
? t('auto.components.dictation.DictationIndicator.3de5a129e7', 'Listening')
|
||||
: dictationState === 'starting'
|
||||
? translate('auto.components.dictation.DictationIndicator.7f3660a7ba', 'Starting mic…')
|
||||
: translate('auto.components.dictation.DictationIndicator.f082d0cb9d', 'Processing…')
|
||||
? t('auto.components.dictation.DictationIndicator.7f3660a7ba', 'Starting mic…')
|
||||
: t('auto.components.dictation.DictationIndicator.f082d0cb9d', 'Processing…')
|
||||
const label = isClipping
|
||||
? translate('auto.components.dictation.DictationIndicator.4977162383', 'Too loud')
|
||||
? t('auto.components.dictation.DictationIndicator.4977162383', 'Too loud')
|
||||
: isSpeaking
|
||||
? translate('auto.components.dictation.DictationIndicator.25f2b7a6a5', 'Speaking')
|
||||
? t('auto.components.dictation.DictationIndicator.25f2b7a6a5', 'Speaking')
|
||||
: lifecycleLabel
|
||||
const announcedLabel = isClipping ? label : lifecycleLabel
|
||||
const canStop = dictationState !== 'stopping'
|
||||
const showShortcut = !isHoldMode && shortcut.keys.length > 0
|
||||
const transcript = partialTranscript.trim()
|
||||
const stopLabel = translate(
|
||||
'auto.components.dictation.DictationIndicator.335e1bc6cb',
|
||||
'Stop dictation'
|
||||
)
|
||||
const stopLabel = t('auto.components.dictation.DictationIndicator.335e1bc6cb', 'Stop dictation')
|
||||
|
||||
return (
|
||||
<div
|
||||
@@ -70,7 +68,7 @@ export function DictationIndicator({ captureLive }: { captureLive: boolean }) {
|
||||
</span>
|
||||
{isLoadingModel ? (
|
||||
<span aria-hidden className="min-w-0 truncate text-xs text-muted-foreground">
|
||||
{translate(
|
||||
{t(
|
||||
'auto.components.dictation.DictationIndicator.loadingModel',
|
||||
'Loading speech model…'
|
||||
)}
|
||||
|
||||
@@ -4,7 +4,7 @@ import { useLayoutEffect, useRef, useState, type ReactNode } from 'react'
|
||||
import { Button } from '@/components/ui/button'
|
||||
import { getDiffCommentLineMetaLabel } from './diff-comment-line-meta-label'
|
||||
import { useMountedRef } from '@/hooks/useMountedRef'
|
||||
import { translate } from '@/i18n/i18n'
|
||||
import { useTranslate } from '@/i18n/use-translate'
|
||||
import { useUiLanguageRevision } from '@/i18n/use-ui-language-revision'
|
||||
import { openAppHttpLink } from '@/lib/browser/navigation/app-http-link-open'
|
||||
|
||||
@@ -51,6 +51,7 @@ export function DiffCommentCard({
|
||||
onSubmitEdit,
|
||||
headerActions
|
||||
}: Props): React.JSX.Element {
|
||||
const t = useTranslate()
|
||||
// Zone cards live in separate React roots, so subscribe directly to language changes.
|
||||
useUiLanguageRevision()
|
||||
const [editing, setEditing] = useState(false)
|
||||
@@ -138,12 +139,12 @@ export function DiffCommentCard({
|
||||
const trimmedDraft = draft.trim()
|
||||
const canSubmit = !submitting && trimmedDraft.length > 0 && trimmedDraft !== body
|
||||
const lineLabel =
|
||||
label === undefined ? getDiffCommentLineMetaLabel({ lineNumber, startLine }) : label
|
||||
label === undefined ? getDiffCommentLineMetaLabel({ lineNumber, startLine }, t) : label
|
||||
const metaText = [
|
||||
author || translate('auto.components.diff.comments.DiffCommentCard.noteAuthorFallback', 'Note'),
|
||||
author || t('auto.components.diff.comments.DiffCommentCard.noteAuthorFallback', 'Note'),
|
||||
lineLabel,
|
||||
createdAtLabel ||
|
||||
(sentAt ? translate('auto.components.diff.comments.DiffCommentCard.sentMeta', 'sent') : null)
|
||||
(sentAt ? t('auto.components.diff.comments.DiffCommentCard.sentMeta', 'sent') : null)
|
||||
]
|
||||
.filter(Boolean)
|
||||
.join(' ')
|
||||
@@ -191,11 +192,11 @@ export function DiffCommentCard({
|
||||
<button
|
||||
type="button"
|
||||
className="orca-diff-comment-pill-btn"
|
||||
title={translate(
|
||||
title={t(
|
||||
'auto.components.diff.comments.DiffCommentCard.508ee678a5',
|
||||
'Open in browser'
|
||||
)}
|
||||
aria-label={translate(
|
||||
aria-label={t(
|
||||
'auto.components.diff.comments.DiffCommentCard.508ee678a5',
|
||||
'Open in browser'
|
||||
)}
|
||||
@@ -205,7 +206,7 @@ export function DiffCommentCard({
|
||||
openAppHttpLink(url, { event: ev })
|
||||
}}
|
||||
>
|
||||
{translate('auto.components.diff.comments.DiffCommentCard.6978871a3d', 'Open')}
|
||||
{t('auto.components.diff.comments.DiffCommentCard.6978871a3d', 'Open')}
|
||||
</button>
|
||||
{(onSubmitEdit || onDelete) && (
|
||||
<span className="orca-diff-comment-pill-divider" />
|
||||
@@ -217,11 +218,11 @@ export function DiffCommentCard({
|
||||
<button
|
||||
type="button"
|
||||
className="orca-diff-comment-pill-btn"
|
||||
title={translate(
|
||||
title={t(
|
||||
'auto.components.diff.comments.DiffCommentCard.cad3384faa',
|
||||
'Edit note'
|
||||
)}
|
||||
aria-label={translate(
|
||||
aria-label={t(
|
||||
'auto.components.diff.comments.DiffCommentCard.cad3384faa',
|
||||
'Edit note'
|
||||
)}
|
||||
@@ -240,11 +241,11 @@ export function DiffCommentCard({
|
||||
<button
|
||||
type="button"
|
||||
className="orca-diff-comment-pill-btn orca-diff-comment-pill-btn-danger"
|
||||
title={translate(
|
||||
title={t(
|
||||
'auto.components.diff.comments.DiffCommentCard.cce596969e',
|
||||
'Delete note'
|
||||
)}
|
||||
aria-label={translate(
|
||||
aria-label={t(
|
||||
'auto.components.diff.comments.DiffCommentCard.cce596969e',
|
||||
'Delete note'
|
||||
)}
|
||||
@@ -300,7 +301,7 @@ export function DiffCommentCard({
|
||||
/>
|
||||
<div className="orca-diff-comment-popover-footer">
|
||||
<Button variant="ghost" size="sm" onClick={handleCancel} disabled={submitting}>
|
||||
{translate('auto.components.diff.comments.DiffCommentCard.0203bed775', 'Cancel')}
|
||||
{t('auto.components.diff.comments.DiffCommentCard.0203bed775', 'Cancel')}
|
||||
</Button>
|
||||
<Button
|
||||
size="sm"
|
||||
@@ -308,14 +309,11 @@ export function DiffCommentCard({
|
||||
disabled={!canSubmit}
|
||||
title={
|
||||
submitting
|
||||
? translate(
|
||||
'auto.components.diff.comments.DiffCommentCard.bb0a55f856',
|
||||
'Saving…'
|
||||
)
|
||||
? t('auto.components.diff.comments.DiffCommentCard.bb0a55f856', 'Saving…')
|
||||
: undefined
|
||||
}
|
||||
>
|
||||
{translate('auto.components.diff.comments.DiffCommentCard.109a791e7b', 'Save')}
|
||||
{t('auto.components.diff.comments.DiffCommentCard.109a791e7b', 'Save')}
|
||||
<CornerDownLeft className="ml-1 size-3 opacity-70" />
|
||||
</Button>
|
||||
</div>
|
||||
|
||||
@@ -6,7 +6,7 @@ import { useCallback, useEffect, useId, useLayoutEffect, useRef, useState } from
|
||||
import { CornerDownLeft } from 'lucide-react'
|
||||
import { Button } from '@/components/ui/button'
|
||||
import { useMountedRef } from '@/hooks/useMountedRef'
|
||||
import { translate } from '@/i18n/i18n'
|
||||
import { useTranslate } from '@/i18n/use-translate'
|
||||
import {
|
||||
getCommentBodySubmitState,
|
||||
hasBoundedCommentBodyText
|
||||
@@ -48,6 +48,7 @@ export function DiffCommentDraftCard({
|
||||
initialBody = '',
|
||||
onBodyChange
|
||||
}: DiffCommentDraftCardProps): React.JSX.Element {
|
||||
const t = useTranslate()
|
||||
const [body, setBody] = useState(initialBody)
|
||||
const bodyRef = useRef(body)
|
||||
const [submitting, setSubmitting] = useState(false)
|
||||
@@ -81,16 +82,14 @@ export function DiffCommentDraftCard({
|
||||
const labelId = useId()
|
||||
const headerLabel =
|
||||
startLine && startLine !== lineNumber
|
||||
? translate(
|
||||
? t(
|
||||
'auto.components.diff.comments.DiffCommentPopover.c845170b3b',
|
||||
'Lines {{value0}}-{{value1}}',
|
||||
{ value0: startLine, value1: lineNumber }
|
||||
)
|
||||
: translate(
|
||||
'auto.components.diff.comments.DiffCommentPopover.e05063cfc1',
|
||||
'Line {{value0}}',
|
||||
{ value0: lineNumber }
|
||||
)
|
||||
: t('auto.components.diff.comments.DiffCommentPopover.e05063cfc1', 'Line {{value0}}', {
|
||||
value0: lineNumber
|
||||
})
|
||||
|
||||
const { attachDrop } = images
|
||||
const hasImageUpload = !!imageUploadAddress
|
||||
@@ -171,7 +170,7 @@ export function DiffCommentDraftCard({
|
||||
}
|
||||
if (bodyState.status === 'too-large-leading-whitespace') {
|
||||
toast.error(
|
||||
translate(
|
||||
t(
|
||||
'auto.components.diff.comments.DiffCommentPopover.commentTooLarge',
|
||||
'Comment is too large to submit safely.'
|
||||
)
|
||||
@@ -189,9 +188,7 @@ export function DiffCommentDraftCard({
|
||||
} catch (err) {
|
||||
console.error('Failed to submit diff comment draft:', err)
|
||||
// A rejected save never reaches the caller's own failure toast, so report it here.
|
||||
toast.error(
|
||||
translate('auto.components.editor.diffCommentSaveFailed', 'Failed to save comment')
|
||||
)
|
||||
toast.error(t('auto.components.editor.diffCommentSaveFailed', 'Failed to save comment'))
|
||||
if (mountedRef.current) {
|
||||
setSubmitting(false)
|
||||
}
|
||||
@@ -244,10 +241,7 @@ export function DiffCommentDraftCard({
|
||||
className="orca-diff-comment-popover-textarea"
|
||||
placeholder={
|
||||
placeholder ??
|
||||
translate(
|
||||
'auto.components.diff.comments.DiffCommentPopover.f6c449191e',
|
||||
'Add note for the AI'
|
||||
)
|
||||
t('auto.components.diff.comments.DiffCommentPopover.f6c449191e', 'Add note for the AI')
|
||||
}
|
||||
value={images.value}
|
||||
onPaste={images.onPaste}
|
||||
@@ -265,29 +259,23 @@ export function DiffCommentDraftCard({
|
||||
{/* Footer */}
|
||||
<div className="orca-diff-comment-popover-footer">
|
||||
<Button type="button" variant="ghost" size="sm" onClick={onCancel} disabled={submitting}>
|
||||
{translate('auto.components.diff.comments.DiffCommentPopover.2b3ce6d394', 'Cancel')}
|
||||
{t('auto.components.diff.comments.DiffCommentPopover.2b3ce6d394', 'Cancel')}
|
||||
</Button>
|
||||
<Button
|
||||
type="button"
|
||||
size="sm"
|
||||
aria-label={
|
||||
submitLabel ??
|
||||
translate('auto.components.diff.comments.DiffCommentPopover.5fa4bfebaa', 'Add note')
|
||||
t('auto.components.diff.comments.DiffCommentPopover.5fa4bfebaa', 'Add note')
|
||||
}
|
||||
onClick={() => void handleSubmit()}
|
||||
disabled={!canSubmit}
|
||||
>
|
||||
{submitting
|
||||
? (submittingLabel ??
|
||||
translate(
|
||||
'auto.components.diff.comments.DiffCommentPopover.5e9b3d0c2e',
|
||||
'Adding...'
|
||||
))
|
||||
t('auto.components.diff.comments.DiffCommentPopover.5e9b3d0c2e', 'Adding...'))
|
||||
: (submitLabel ??
|
||||
translate(
|
||||
'auto.components.diff.comments.DiffCommentPopover.5fa4bfebaa',
|
||||
'Add note'
|
||||
))}
|
||||
t('auto.components.diff.comments.DiffCommentPopover.5fa4bfebaa', 'Add note'))}
|
||||
{!submitting && <CornerDownLeft className="ml-1 size-3 opacity-70" />}
|
||||
</Button>
|
||||
</div>
|
||||
|
||||
@@ -9,6 +9,7 @@ import {
|
||||
hasBoundedCommentBodyText
|
||||
} from '@/lib/comment-body-submit-state'
|
||||
import { translate } from '@/i18n/i18n'
|
||||
import { useTranslate } from '@/i18n/use-translate'
|
||||
import { installOpenDraftAddReviewNoteGuard } from '../editor/editor-shortcuts'
|
||||
import { resolveDiffCommentPopoverTop } from './diff-comment-popover-position'
|
||||
|
||||
@@ -55,6 +56,7 @@ export function DiffCommentPopover({
|
||||
onCancel,
|
||||
onSubmit
|
||||
}: Props): React.JSX.Element {
|
||||
const t = useTranslate()
|
||||
const [body, setBody] = useState('')
|
||||
// Why: mirror the draft into a ref so the mousedown listener reads it fresh without re-registering each keystroke.
|
||||
const bodyRef = useRef(body)
|
||||
@@ -160,7 +162,7 @@ export function DiffCommentPopover({
|
||||
}
|
||||
if (bodyState.status === 'too-large-leading-whitespace') {
|
||||
toast.error(
|
||||
translate(
|
||||
t(
|
||||
'auto.components.diff.comments.DiffCommentPopover.commentTooLarge',
|
||||
'Comment is too large to submit safely.'
|
||||
)
|
||||
@@ -194,12 +196,12 @@ export function DiffCommentPopover({
|
||||
<div id={labelId} className="orca-diff-comment-popover-label">
|
||||
{title ??
|
||||
(startLine && startLine !== lineNumber
|
||||
? translate(
|
||||
? t(
|
||||
'auto.components.diff.comments.DiffCommentPopover.c845170b3b',
|
||||
'Lines {{value0}}-{{value1}}',
|
||||
{ value0: startLine, value1: lineNumber }
|
||||
)
|
||||
: translate(
|
||||
: t(
|
||||
'auto.components.diff.comments.DiffCommentPopover.e05063cfc1',
|
||||
'Line {{value0}}',
|
||||
{ value0: lineNumber }
|
||||
@@ -233,7 +235,7 @@ export function DiffCommentPopover({
|
||||
/>
|
||||
<div className="orca-diff-comment-popover-footer">
|
||||
<Button variant="ghost" size="sm" onClick={onCancel}>
|
||||
{translate('auto.components.diff.comments.DiffCommentPopover.2b3ce6d394', 'Cancel')}
|
||||
{t('auto.components.diff.comments.DiffCommentPopover.2b3ce6d394', 'Cancel')}
|
||||
</Button>
|
||||
<Button size="sm" onClick={handleSubmit} disabled={submitting || !canSubmitComment}>
|
||||
{submitting ? submittingLabel : submitLabel}
|
||||
|
||||
@@ -1,15 +1,17 @@
|
||||
import type { DiffComment } from '../../../../shared/diff-comment-types'
|
||||
import { translate } from '@/i18n/i18n'
|
||||
import type { Translate } from '@/i18n/use-translate'
|
||||
|
||||
// Card meta text, e.g. "Note line 4 sent"; agent prompts keep the English getDiffCommentLineLabel.
|
||||
export function getDiffCommentLineMetaLabel(
|
||||
comment: Pick<DiffComment, 'lineNumber' | 'startLine'>
|
||||
comment: Pick<DiffComment, 'lineNumber' | 'startLine'>,
|
||||
t: Translate = translate
|
||||
): string {
|
||||
if (comment.startLine !== undefined && comment.startLine !== comment.lineNumber) {
|
||||
return translate('diffComments.lineRangeMeta', 'lines {{start}}-{{end}}', {
|
||||
return t('diffComments.lineRangeMeta', 'lines {{start}}-{{end}}', {
|
||||
start: comment.startLine,
|
||||
end: comment.lineNumber
|
||||
})
|
||||
}
|
||||
return translate('diffComments.lineMeta', 'line {{line}}', { line: comment.lineNumber })
|
||||
return t('diffComments.lineMeta', 'line {{line}}', { line: comment.lineNumber })
|
||||
}
|
||||
|
||||
@@ -6,7 +6,7 @@ import { MobileEmulatorAgentSetupGuideLayer } from './MobileEmulatorAgentSetupGu
|
||||
import { useEmulatorPaneSession } from './use-emulator-pane-session'
|
||||
import { scrcpyDeviceIdFromStreamUrl } from './emulator-scrcpy-stream-url'
|
||||
import { useEmulatorInputCheck } from './use-emulator-input-check'
|
||||
import { translate } from '@/i18n/i18n'
|
||||
import { useTranslate } from '@/i18n/use-translate'
|
||||
import { useUiLanguageRevision } from '@/i18n/use-ui-language-revision'
|
||||
|
||||
type EmulatorPaneProps = {
|
||||
@@ -17,6 +17,7 @@ type EmulatorPaneProps = {
|
||||
}
|
||||
|
||||
export default function EmulatorPane({ tab, worktreeId, isActive = true }: EmulatorPaneProps) {
|
||||
const t = useTranslate()
|
||||
useUiLanguageRevision()
|
||||
const {
|
||||
devices,
|
||||
@@ -73,7 +74,7 @@ export default function EmulatorPane({ tab, worktreeId, isActive = true }: Emula
|
||||
|
||||
{selectionRequired && (
|
||||
<p role="status" className="border-b border-border px-3 py-2 text-xs text-muted-foreground">
|
||||
{emulatorOpeningMessage(selectionRequired)}
|
||||
{emulatorOpeningMessage(selectionRequired, t)}
|
||||
</p>
|
||||
)}
|
||||
{visibleError ? (
|
||||
@@ -92,10 +93,7 @@ export default function EmulatorPane({ tab, worktreeId, isActive = true }: Emula
|
||||
<MobileEmulatorAgentSetupGuideLayer isActive={isActive}>
|
||||
{!isLive && !loading ? (
|
||||
<p className="mb-4 text-center text-xs text-muted-foreground">
|
||||
{translate(
|
||||
'auto.components.emulator.pane.EmulatorPane.59b08fa031',
|
||||
'No emulator connected'
|
||||
)}
|
||||
{t('auto.components.emulator.pane.EmulatorPane.59b08fa031', 'No emulator connected')}
|
||||
</p>
|
||||
) : null}
|
||||
<EmulatorDeviceFrame
|
||||
|
||||
@@ -4,7 +4,7 @@ import { useAppStore } from '@/store'
|
||||
import { Button } from '../ui/button'
|
||||
import { MobileEmulatorAgentSetupGuideSteps } from './MobileEmulatorAgentSetupGuideSteps'
|
||||
import type { useMobileEmulatorAgentSetupState } from './use-mobile-emulator-agent-setup-state'
|
||||
import { translate } from '@/i18n/i18n'
|
||||
import { useTranslate } from '@/i18n/use-translate'
|
||||
|
||||
type MobileEmulatorAgentSetupGuideProps = {
|
||||
setup: ReturnType<typeof useMobileEmulatorAgentSetupState>
|
||||
@@ -13,6 +13,7 @@ type MobileEmulatorAgentSetupGuideProps = {
|
||||
export function MobileEmulatorAgentSetupGuide({
|
||||
setup
|
||||
}: MobileEmulatorAgentSetupGuideProps): React.JSX.Element {
|
||||
const t = useTranslate()
|
||||
const dismissMobileEmulatorAgentSetup = useAppStore((s) => s.dismissMobileEmulatorAgentSetup)
|
||||
const openSettingsPage = useAppStore((s) => s.openSettingsPage)
|
||||
const openSettingsTarget = useAppStore((s) => s.openSettingsTarget)
|
||||
@@ -32,7 +33,7 @@ export function MobileEmulatorAgentSetupGuide({
|
||||
return (
|
||||
<div
|
||||
role="region"
|
||||
aria-label={translate(
|
||||
aria-label={t(
|
||||
'auto.components.emulator.pane.MobileEmulatorAgentSetupGuide.2fda9ff015',
|
||||
'Set up agent control'
|
||||
)}
|
||||
@@ -42,7 +43,7 @@ export function MobileEmulatorAgentSetupGuide({
|
||||
<p className="min-w-0 flex-1 text-[11px] leading-4 text-muted-foreground">
|
||||
{setup.setupComplete ? (
|
||||
<span className="font-medium text-foreground">
|
||||
{translate(
|
||||
{t(
|
||||
'auto.components.emulator.pane.MobileEmulatorAgentSetupGuide.0ac0fef514',
|
||||
'Agent control is ready.'
|
||||
)}
|
||||
@@ -50,12 +51,12 @@ export function MobileEmulatorAgentSetupGuide({
|
||||
) : (
|
||||
<>
|
||||
<span className="font-medium text-foreground">
|
||||
{translate(
|
||||
{t(
|
||||
'auto.components.emulator.pane.MobileEmulatorAgentSetupGuide.2bdfff8763',
|
||||
'Agent control (optional).'
|
||||
)}{' '}
|
||||
</span>
|
||||
{translate(
|
||||
{t(
|
||||
'auto.components.emulator.pane.MobileEmulatorAgentSetupGuide.installSkillPrompt',
|
||||
'Install the Orca CLI skill when you want agents to drive this simulator.'
|
||||
)}
|
||||
@@ -72,10 +73,7 @@ export function MobileEmulatorAgentSetupGuide({
|
||||
className="h-6 px-2.5 text-[11px]"
|
||||
onClick={dismiss}
|
||||
>
|
||||
{translate(
|
||||
'auto.components.emulator.pane.MobileEmulatorAgentSetupGuide.d10ae98046',
|
||||
'Done'
|
||||
)}
|
||||
{t('auto.components.emulator.pane.MobileEmulatorAgentSetupGuide.d10ae98046', 'Done')}
|
||||
</Button>
|
||||
) : (
|
||||
<>
|
||||
@@ -86,7 +84,7 @@ export function MobileEmulatorAgentSetupGuide({
|
||||
className="h-6 px-2 text-[11px] text-muted-foreground"
|
||||
onClick={dismiss}
|
||||
>
|
||||
{translate(
|
||||
{t(
|
||||
'auto.components.emulator.pane.MobileEmulatorAgentSetupGuide.3756cbeca7',
|
||||
'Not now'
|
||||
)}
|
||||
@@ -100,11 +98,11 @@ export function MobileEmulatorAgentSetupGuide({
|
||||
onClick={() => setExpanded((value) => !value)}
|
||||
>
|
||||
{expanded
|
||||
? translate(
|
||||
? t(
|
||||
'auto.components.emulator.pane.MobileEmulatorAgentSetupGuide.6d950431d2',
|
||||
'Hide'
|
||||
)
|
||||
: translate(
|
||||
: t(
|
||||
'auto.components.emulator.pane.MobileEmulatorAgentSetupGuide.ebceac65a4',
|
||||
'Set up'
|
||||
)}
|
||||
@@ -124,7 +122,7 @@ export function MobileEmulatorAgentSetupGuide({
|
||||
onClick={openSettings}
|
||||
className="text-[11px] text-muted-foreground underline-offset-2 hover:text-foreground hover:underline"
|
||||
>
|
||||
{translate(
|
||||
{t(
|
||||
'auto.components.emulator.pane.MobileEmulatorAgentSetupGuide.3f003507f4',
|
||||
'Open full setup in Settings'
|
||||
)}
|
||||
|
||||
@@ -2,7 +2,7 @@ import { useAppStore } from '@/store'
|
||||
import { ORCA_CLI_SKILL_NAME } from '@/lib/agent-feature-skill-names'
|
||||
import { AgentSkillSetupPanel } from '../skills/setup/AgentSkillSetupPanel'
|
||||
import type { useMobileEmulatorAgentSetupState } from './use-mobile-emulator-agent-setup-state'
|
||||
import { translate } from '@/i18n/i18n'
|
||||
import { useTranslate } from '@/i18n/use-translate'
|
||||
|
||||
type MobileEmulatorAgentSetupGuideStepsProps = {
|
||||
setup: ReturnType<typeof useMobileEmulatorAgentSetupState>
|
||||
@@ -11,12 +11,13 @@ type MobileEmulatorAgentSetupGuideStepsProps = {
|
||||
export function MobileEmulatorAgentSetupGuideSteps({
|
||||
setup
|
||||
}: MobileEmulatorAgentSetupGuideStepsProps): React.JSX.Element {
|
||||
const t = useTranslate()
|
||||
const recordFeatureInteraction = useAppStore((s) => s.recordFeatureInteraction)
|
||||
|
||||
return (
|
||||
<div className="py-2.5">
|
||||
<p className="text-sm font-medium">
|
||||
{translate(
|
||||
{t(
|
||||
'auto.components.emulator.pane.MobileEmulatorAgentSetupGuideSteps.21f5687c07',
|
||||
'Orca CLI skill'
|
||||
)}
|
||||
@@ -25,11 +26,11 @@ export function MobileEmulatorAgentSetupGuideSteps({
|
||||
variant="inline"
|
||||
hideHeader
|
||||
className="min-w-0"
|
||||
title={translate(
|
||||
title={t(
|
||||
'auto.components.emulator.pane.MobileEmulatorAgentSetupGuideSteps.21f5687c07',
|
||||
'Orca CLI skill'
|
||||
)}
|
||||
description={translate(
|
||||
description={t(
|
||||
'auto.components.emulator.pane.MobileEmulatorAgentSetupGuideSteps.64fb057667',
|
||||
'Teaches agents the orca emulator commands for this worktree.'
|
||||
)}
|
||||
|
||||
@@ -1,10 +1,11 @@
|
||||
import { X } from 'lucide-react'
|
||||
import { Button } from '@/components/ui/button'
|
||||
import { Tooltip, TooltipContent, TooltipTrigger } from '@/components/ui/tooltip'
|
||||
import { translate } from '@/i18n/i18n'
|
||||
import { useTranslate } from '@/i18n/use-translate'
|
||||
import { useMobileEmulatorTabIntroActions } from './use-mobile-emulator-tab-intro-actions'
|
||||
|
||||
export function MobileEmulatorTabIntroCallout(): React.JSX.Element {
|
||||
const t = useTranslate()
|
||||
const { keepIntro, hideIntro, dismissIntro } = useMobileEmulatorTabIntroActions()
|
||||
|
||||
return (
|
||||
@@ -14,7 +15,7 @@ export function MobileEmulatorTabIntroCallout(): React.JSX.Element {
|
||||
onPointerDown={(event) => event.preventDefault()}
|
||||
>
|
||||
<p className="min-w-0 flex-1 text-[11px] leading-4 text-foreground/85">
|
||||
{translate(
|
||||
{t(
|
||||
'auto.components.emulator.pane.MobileEmulatorTabIntroCallout.5789936d9a',
|
||||
'Preview iOS simulators while agents drive the screen.'
|
||||
)}
|
||||
@@ -27,10 +28,7 @@ export function MobileEmulatorTabIntroCallout(): React.JSX.Element {
|
||||
className="h-6 px-2 text-[11px]"
|
||||
onClick={keepIntro}
|
||||
>
|
||||
{translate(
|
||||
'auto.components.emulator.pane.MobileEmulatorTabIntroCallout.8014b4b80b',
|
||||
'Keep'
|
||||
)}
|
||||
{t('auto.components.emulator.pane.MobileEmulatorTabIntroCallout.8014b4b80b', 'Keep')}
|
||||
</Button>
|
||||
<Button
|
||||
type="button"
|
||||
@@ -39,10 +37,7 @@ export function MobileEmulatorTabIntroCallout(): React.JSX.Element {
|
||||
className="h-6 px-2 text-[11px] text-muted-foreground"
|
||||
onClick={hideIntro}
|
||||
>
|
||||
{translate(
|
||||
'auto.components.emulator.pane.MobileEmulatorTabIntroCallout.6e051a40b7',
|
||||
'Hide'
|
||||
)}
|
||||
{t('auto.components.emulator.pane.MobileEmulatorTabIntroCallout.6e051a40b7', 'Hide')}
|
||||
</Button>
|
||||
<Tooltip>
|
||||
<TooltipTrigger asChild>
|
||||
@@ -50,7 +45,7 @@ export function MobileEmulatorTabIntroCallout(): React.JSX.Element {
|
||||
type="button"
|
||||
variant="ghost"
|
||||
size="icon-xs"
|
||||
aria-label={translate(
|
||||
aria-label={t(
|
||||
'auto.components.emulator.pane.MobileEmulatorTabIntroCallout.1924982130',
|
||||
'Dismiss'
|
||||
)}
|
||||
@@ -61,10 +56,7 @@ export function MobileEmulatorTabIntroCallout(): React.JSX.Element {
|
||||
</Button>
|
||||
</TooltipTrigger>
|
||||
<TooltipContent side="top" sideOffset={4}>
|
||||
{translate(
|
||||
'auto.components.emulator.pane.MobileEmulatorTabIntroCallout.1924982130',
|
||||
'Dismiss'
|
||||
)}
|
||||
{t('auto.components.emulator.pane.MobileEmulatorTabIntroCallout.1924982130', 'Dismiss')}
|
||||
</TooltipContent>
|
||||
</Tooltip>
|
||||
</div>
|
||||
|
||||
@@ -10,7 +10,7 @@ import {
|
||||
CommandItem,
|
||||
CommandList
|
||||
} from '@/components/ui/command'
|
||||
import { translate } from '@/i18n/i18n'
|
||||
import { useTranslate } from '@/i18n/use-translate'
|
||||
import type { SimulatorDeviceRow } from './emulator-pane-types'
|
||||
import { emulatorDeviceDescription, groupEmulatorDevices } from './emulator-device-picker-groups'
|
||||
|
||||
@@ -22,13 +22,14 @@ type Props = {
|
||||
}
|
||||
|
||||
export function EmulatorDevicePicker({ devices, selectedUdid, disabled, onSelectDevice }: Props) {
|
||||
const t = useTranslate()
|
||||
const [open, setOpen] = useState(false)
|
||||
const [search, setSearch] = useState('')
|
||||
const [otherOpen, setOtherOpen] = useState(false)
|
||||
const [unavailableOpen, setUnavailableOpen] = useState(false)
|
||||
const groups = groupEmulatorDevices(devices, selectedUdid)
|
||||
const selected = devices.find((device) => device.udid === selectedUdid)
|
||||
const chooseLabel = translate('emulator.picker.choose', 'Choose emulator')
|
||||
const chooseLabel = t('emulator.picker.choose', 'Choose emulator')
|
||||
const duplicateKeys = new Map<string, number>()
|
||||
for (const device of devices) {
|
||||
const key = `${device.name} ${emulatorDeviceDescription(device)}`
|
||||
@@ -39,10 +40,10 @@ export function EmulatorDevicePicker({ devices, selectedUdid, disabled, onSelect
|
||||
const duplicate = (duplicateKeys.get(`${device.name} ${description}`) ?? 0) > 1
|
||||
const status =
|
||||
device.state === 'Booted'
|
||||
? translate('emulator.picker.running', 'Running')
|
||||
? t('emulator.picker.running', 'Running')
|
||||
: device.state === 'Booting'
|
||||
? translate('emulator.picker.starting', 'Starting')
|
||||
: translate('emulator.picker.stopped', 'Stopped')
|
||||
? t('emulator.picker.starting', 'Starting')
|
||||
: t('emulator.picker.stopped', 'Stopped')
|
||||
return (
|
||||
<CommandItem
|
||||
key={device.udid}
|
||||
@@ -67,7 +68,7 @@ export function EmulatorDevicePicker({ devices, selectedUdid, disabled, onSelect
|
||||
{device.isAvailable === false && (
|
||||
<div className="text-xs text-muted-foreground">
|
||||
{device.availabilityError ||
|
||||
translate('emulator.picker.runtimeUnavailable', 'Runtime unavailable')}
|
||||
t('emulator.picker.runtimeUnavailable', 'Runtime unavailable')}
|
||||
</div>
|
||||
)}
|
||||
</div>
|
||||
@@ -106,12 +107,12 @@ export function EmulatorDevicePicker({ devices, selectedUdid, disabled, onSelect
|
||||
<CommandInput
|
||||
value={search}
|
||||
onValueChange={setSearch}
|
||||
placeholder={translate('emulator.picker.search', 'Search devices…')}
|
||||
placeholder={t('emulator.picker.search', 'Search devices…')}
|
||||
/>
|
||||
<CommandList className="max-h-80">
|
||||
<CommandEmpty>{translate('emulator.picker.empty', 'No matching devices')}</CommandEmpty>
|
||||
<CommandEmpty>{t('emulator.picker.empty', 'No matching devices')}</CommandEmpty>
|
||||
{groups.used.length > 0 && (
|
||||
<CommandGroup heading={translate('emulator.picker.used', 'Used devices')}>
|
||||
<CommandGroup heading={t('emulator.picker.used', 'Used devices')}>
|
||||
{groups.used.map(renderDevice)}
|
||||
</CommandGroup>
|
||||
)}
|
||||
@@ -125,7 +126,7 @@ export function EmulatorDevicePicker({ devices, selectedUdid, disabled, onSelect
|
||||
selection="palette"
|
||||
>
|
||||
<ChevronRight className={otherOpen ? 'rotate-90' : ''} />
|
||||
{translate('emulator.picker.other', 'Other devices on this computer')} (
|
||||
{t('emulator.picker.other', 'Other devices on this computer')} (
|
||||
{groups.other.length})
|
||||
</CommandItem>
|
||||
)}
|
||||
@@ -142,7 +143,7 @@ export function EmulatorDevicePicker({ devices, selectedUdid, disabled, onSelect
|
||||
selection="palette"
|
||||
>
|
||||
<ChevronRight className={unavailableOpen ? 'rotate-90' : ''} />
|
||||
{translate('emulator.picker.unavailable', 'Unavailable runtimes')} (
|
||||
{t('emulator.picker.unavailable', 'Unavailable runtimes')} (
|
||||
{groups.unavailable.length})
|
||||
</CommandItem>
|
||||
)}
|
||||
|
||||
@@ -1,21 +1,25 @@
|
||||
import type { EmulatorSelectionRequired } from '../../../../shared/emulator-opening-preferences'
|
||||
import { translate } from '@/i18n/i18n'
|
||||
export function emulatorOpeningMessage(reason: EmulatorSelectionRequired): string {
|
||||
import type { Translate } from '@/i18n/use-translate'
|
||||
export function emulatorOpeningMessage(
|
||||
reason: EmulatorSelectionRequired,
|
||||
t: Translate = translate
|
||||
): string {
|
||||
switch (reason) {
|
||||
case 'missing':
|
||||
return translate(
|
||||
return t(
|
||||
'emulator.opening.missing',
|
||||
'The saved device is no longer on this computer. Choose another device.'
|
||||
)
|
||||
case 'unavailable':
|
||||
return translate(
|
||||
return t(
|
||||
'emulator.opening.unavailable',
|
||||
'The saved device runtime is unavailable. Choose another device or install its runtime.'
|
||||
)
|
||||
case 'ask':
|
||||
return translate('emulator.opening.askPrompt', 'Choose a device to open.')
|
||||
return t('emulator.opening.askPrompt', 'Choose a device to open.')
|
||||
case 'first-use':
|
||||
return translate(
|
||||
return t(
|
||||
'emulator.opening.firstUse',
|
||||
'Choose your first device. Orca will remember successful selections on this computer.'
|
||||
)
|
||||
|
||||
@@ -4,7 +4,7 @@ import { Button } from '@/components/ui/button'
|
||||
import { Tooltip, TooltipContent, TooltipTrigger } from '@/components/ui/tooltip'
|
||||
import { cn } from '@/lib/utils'
|
||||
import type { SimulatorDeviceRow } from './emulator-pane-types'
|
||||
import { translate } from '@/i18n/i18n'
|
||||
import { useTranslate } from '@/i18n/use-translate'
|
||||
|
||||
type EmulatorPaneToolbarProps = {
|
||||
displayName: string
|
||||
@@ -31,6 +31,7 @@ export function EmulatorPaneToolbar({
|
||||
onHome,
|
||||
onRotate
|
||||
}: EmulatorPaneToolbarProps) {
|
||||
const t = useTranslate()
|
||||
// Show preview/control connection state rather than CoreSimulator boot state.
|
||||
const statusLabel = isLive ? 'Connected' : loading ? 'Working…' : 'Not connected'
|
||||
const subtleStatus = isLive || loading
|
||||
@@ -67,22 +68,19 @@ export function EmulatorPaneToolbar({
|
||||
className="h-7 gap-1 px-2 text-xs"
|
||||
onClick={onRotate}
|
||||
disabled={!isLive || loading}
|
||||
aria-label={translate(
|
||||
aria-label={t(
|
||||
'auto.components.emulator.pane.emulator.pane.toolbar.6bd8dff42a',
|
||||
'Rotate'
|
||||
)}
|
||||
>
|
||||
<RotateCw className="size-3.5" />
|
||||
<span className="hidden sm:inline">
|
||||
{translate(
|
||||
'auto.components.emulator.pane.emulator.pane.toolbar.6bd8dff42a',
|
||||
'Rotate'
|
||||
)}
|
||||
{t('auto.components.emulator.pane.emulator.pane.toolbar.6bd8dff42a', 'Rotate')}
|
||||
</span>
|
||||
</Button>
|
||||
</TooltipTrigger>
|
||||
<TooltipContent side="bottom" sideOffset={4}>
|
||||
{translate('auto.components.emulator.pane.emulator.pane.toolbar.6bd8dff42a', 'Rotate')}
|
||||
{t('auto.components.emulator.pane.emulator.pane.toolbar.6bd8dff42a', 'Rotate')}
|
||||
</TooltipContent>
|
||||
</Tooltip>
|
||||
<Tooltip>
|
||||
@@ -94,16 +92,13 @@ export function EmulatorPaneToolbar({
|
||||
className="size-7"
|
||||
onClick={onHome}
|
||||
disabled={!isLive || loading}
|
||||
aria-label={translate(
|
||||
'auto.components.emulator.pane.emulator.pane.toolbar.e7a0d1897e',
|
||||
'Home'
|
||||
)}
|
||||
aria-label={t('auto.components.emulator.pane.emulator.pane.toolbar.e7a0d1897e', 'Home')}
|
||||
>
|
||||
<Home className="size-3.5" />
|
||||
</Button>
|
||||
</TooltipTrigger>
|
||||
<TooltipContent side="bottom" sideOffset={4}>
|
||||
{translate('auto.components.emulator.pane.emulator.pane.toolbar.e7a0d1897e', 'Home')}
|
||||
{t('auto.components.emulator.pane.emulator.pane.toolbar.e7a0d1897e', 'Home')}
|
||||
</TooltipContent>
|
||||
</Tooltip>
|
||||
{isLive ? (
|
||||
@@ -116,7 +111,7 @@ export function EmulatorPaneToolbar({
|
||||
className="size-7 text-muted-foreground hover:text-destructive"
|
||||
onClick={onShutdown}
|
||||
disabled={loading}
|
||||
aria-label={translate(
|
||||
aria-label={t(
|
||||
'auto.components.emulator.pane.emulator.pane.toolbar.06e10d7356',
|
||||
'Shut down emulator'
|
||||
)}
|
||||
@@ -125,7 +120,7 @@ export function EmulatorPaneToolbar({
|
||||
</Button>
|
||||
</TooltipTrigger>
|
||||
<TooltipContent side="bottom" sideOffset={4}>
|
||||
{translate(
|
||||
{t(
|
||||
'auto.components.emulator.pane.emulator.pane.toolbar.06e10d7356',
|
||||
'Shut down emulator'
|
||||
)}
|
||||
@@ -145,14 +140,8 @@ export function EmulatorPaneToolbar({
|
||||
}
|
||||
>
|
||||
{loading
|
||||
? translate(
|
||||
'auto.components.emulator.pane.emulator.pane.toolbar.868c0f2938',
|
||||
'Working…'
|
||||
)
|
||||
: translate(
|
||||
'auto.components.emulator.pane.emulator.pane.toolbar.81b3571a07',
|
||||
'Connect'
|
||||
)}
|
||||
? t('auto.components.emulator.pane.emulator.pane.toolbar.868c0f2938', 'Working…')
|
||||
: t('auto.components.emulator.pane.emulator.pane.toolbar.81b3571a07', 'Connect')}
|
||||
</Button>
|
||||
)}
|
||||
</div>
|
||||
|
||||
@@ -1,16 +1,18 @@
|
||||
import { translate } from '@/i18n/i18n'
|
||||
import type { Translate } from '@/i18n/use-translate'
|
||||
|
||||
export function getEmulatorScreenAriaLabel(
|
||||
isLive: boolean,
|
||||
keyboardCaptureActive: boolean
|
||||
keyboardCaptureActive: boolean,
|
||||
t: Translate = translate
|
||||
): string | undefined {
|
||||
if (!isLive) {
|
||||
return undefined
|
||||
}
|
||||
return keyboardCaptureActive
|
||||
? translate(
|
||||
? t(
|
||||
'auto.components.emulator.pane.emulator.device.frame.8f25ffaf8a',
|
||||
'Emulator screen, keyboard captured. Press Escape to release.'
|
||||
)
|
||||
: translate('auto.components.emulator.pane.emulator.device.frame.9406c15775', 'Emulator screen')
|
||||
: t('auto.components.emulator.pane.emulator.device.frame.9406c15775', 'Emulator screen')
|
||||
}
|
||||
|
||||
@@ -3,7 +3,7 @@ import { useEffect, type CSSProperties } from 'react'
|
||||
import { useEmulatorFrameStream } from './use-emulator-frame-stream'
|
||||
import { useEmulatorVideoStream } from './use-emulator-video-stream'
|
||||
import { scrcpyDeviceIdFromStreamUrl } from './emulator-scrcpy-stream-url'
|
||||
import { translate } from '@/i18n/i18n'
|
||||
import { useTranslate } from '@/i18n/use-translate'
|
||||
import type { VisualStreamGeometry } from './emulator-device-frame-layout'
|
||||
|
||||
type StreamSize = {
|
||||
@@ -34,6 +34,7 @@ export function EmulatorScreenStreamContent({
|
||||
streamKey,
|
||||
streamRotation = 0
|
||||
}: EmulatorScreenStreamContentProps) {
|
||||
const t = useTranslate()
|
||||
const androidDeviceId = scrcpyDeviceIdFromStreamUrl(previewUrl)
|
||||
|
||||
const video = useEmulatorVideoStream(
|
||||
@@ -66,7 +67,7 @@ export function EmulatorScreenStreamContent({
|
||||
ref={video.canvasRef}
|
||||
className={mediaClassName}
|
||||
style={mediaStyle}
|
||||
aria-label={translate(
|
||||
aria-label={t(
|
||||
'auto.components.emulator.pane.emulator.screen.stream.content.5ee64cd44e',
|
||||
'Emulator screen'
|
||||
)}
|
||||
@@ -79,7 +80,7 @@ export function EmulatorScreenStreamContent({
|
||||
<img
|
||||
key={`${previewUrl}::${streamKey ?? ''}`}
|
||||
src={frameStream.frameUrl}
|
||||
alt={translate(
|
||||
alt={t(
|
||||
'auto.components.emulator.pane.emulator.screen.stream.content.5ee64cd44e',
|
||||
'Emulator screen'
|
||||
)}
|
||||
@@ -107,7 +108,7 @@ export function EmulatorScreenStreamContent({
|
||||
<>
|
||||
<Loader2 className="size-6 animate-spin text-primary" />
|
||||
<span className="text-xs">
|
||||
{translate(
|
||||
{t(
|
||||
'auto.components.emulator.pane.emulator.screen.stream.content.5f818f12ab',
|
||||
'Connecting emulator…'
|
||||
)}
|
||||
@@ -115,14 +116,14 @@ export function EmulatorScreenStreamContent({
|
||||
</>
|
||||
) : displayError ? (
|
||||
<span className="px-6 text-center text-xs">
|
||||
{translate(
|
||||
{t(
|
||||
'auto.components.emulator.pane.emulator.screen.stream.content.36841af608',
|
||||
'Stream disconnected'
|
||||
)}
|
||||
</span>
|
||||
) : (
|
||||
<span className="px-6 text-center text-xs">
|
||||
{translate(
|
||||
{t(
|
||||
'auto.components.emulator.pane.emulator.screen.stream.content.8b1a0d8694',
|
||||
'Emulator preview'
|
||||
)}
|
||||
|
||||
@@ -13,6 +13,7 @@ import type {
|
||||
StreamSize,
|
||||
VisualStreamGeometry
|
||||
} from './emulator-device-frame-layout'
|
||||
import { useTranslate } from '@/i18n/use-translate'
|
||||
|
||||
type EmulatorScreenSurfaceProps = {
|
||||
frameLayout: DeviceFrameLayout | null
|
||||
@@ -59,6 +60,7 @@ export function EmulatorScreenSurface({
|
||||
streamKey,
|
||||
streamRotation
|
||||
}: EmulatorScreenSurfaceProps) {
|
||||
const t = useTranslate()
|
||||
return (
|
||||
<div
|
||||
className={cn(
|
||||
@@ -83,7 +85,7 @@ export function EmulatorScreenSurface({
|
||||
role={isLive ? 'application' : undefined}
|
||||
tabIndex={isLive ? 0 : undefined}
|
||||
aria-keyshortcuts={keyboardCaptureActive ? 'Escape' : undefined}
|
||||
aria-label={getEmulatorScreenAriaLabel(isLive, keyboardCaptureActive)}
|
||||
aria-label={getEmulatorScreenAriaLabel(isLive, keyboardCaptureActive, t)}
|
||||
>
|
||||
{/* Use the real streamed screen; extra in-screen chrome would duplicate iOS status bars and distort bezels. */}
|
||||
<EmulatorScreenStreamContent
|
||||
|
||||
@@ -28,6 +28,7 @@ import { useEmulatorPaneLifecycle } from './use-emulator-pane-lifecycle'
|
||||
import { useEmulatorPaneShutdown } from './use-emulator-pane-shutdown'
|
||||
import { emulatorStartErrorText, noEmulatorDevicesError } from './emulator-pane-error-copy'
|
||||
import { SIMULATOR_DEFAULT_TAB_LABEL, formatSimulatorTabLabel } from '@/lib/simulator-tab-label'
|
||||
import { useTranslate } from '@/i18n/use-translate'
|
||||
|
||||
type UseEmulatorPaneSessionArgs = {
|
||||
worktreeId: string
|
||||
@@ -40,6 +41,7 @@ export function useEmulatorPaneSession({
|
||||
tabId,
|
||||
autoAttachOnMount
|
||||
}: UseEmulatorPaneSessionArgs) {
|
||||
const t = useTranslate()
|
||||
const configuredDefaultUdid = useAppStore((state) =>
|
||||
state.settings && getEmulatorOpeningMode(state.settings) === 'specific'
|
||||
? (state.settings.mobileEmulatorDefaultDeviceUdid ?? null)
|
||||
@@ -307,7 +309,7 @@ export function useEmulatorPaneSession({
|
||||
sendGesture,
|
||||
sendRotate,
|
||||
visualOrientation,
|
||||
displayName: formatSimulatorTabLabel(view.displayName),
|
||||
displayName: formatSimulatorTabLabel(view.displayName, t),
|
||||
previewUrl: view.previewUrl,
|
||||
wsUrl: view.wsUrl,
|
||||
streamKey: streamKey ?? undefined,
|
||||
|
||||
@@ -15,7 +15,7 @@ import { getWorkspaceEditorSshHost } from '@/lib/workspace-editor-ssh-target'
|
||||
import { NO_OPEN_IN_APPLICATIONS } from '@/lib/open-in-application-selection'
|
||||
import type { ExecutionHostId } from '../../../../shared/execution-host'
|
||||
import type { OpenInApplication } from '../../../../shared/ui-chrome-types'
|
||||
import { translate } from '@/i18n/i18n'
|
||||
import { useTranslate } from '@/i18n/use-translate'
|
||||
import {
|
||||
getOpenInEntryAvailability,
|
||||
openOpenInAppsSettings,
|
||||
@@ -33,6 +33,7 @@ export function OpenInContextSubmenu({
|
||||
workspaceId,
|
||||
owner
|
||||
}: OpenInContextSubmenuProps): React.JSX.Element {
|
||||
const t = useTranslate()
|
||||
const openInApplications = useAppStateValue(
|
||||
(s) => s.settings?.openInApplications ?? NO_OPEN_IN_APPLICATIONS
|
||||
)
|
||||
@@ -64,7 +65,7 @@ export function OpenInContextSubmenu({
|
||||
<ContextMenuSub>
|
||||
<ContextMenuSubTrigger disabled={!path}>
|
||||
<FolderOpen className="size-3.5" />
|
||||
{translate('auto.components.sidebar.WorktreeOpenInMenu.8009ab69a6', 'Open in')}
|
||||
{t('auto.components.sidebar.WorktreeOpenInMenu.8009ab69a6', 'Open in')}
|
||||
</ContextMenuSubTrigger>
|
||||
<ContextMenuSubContent className="w-52">
|
||||
{openInApplications.map((application) => {
|
||||
@@ -73,7 +74,8 @@ export function OpenInContextSubmenu({
|
||||
openInRoute.connectionId,
|
||||
openInRoute.runtimeEnvironmentId,
|
||||
openInRoute.ownerUnresolved,
|
||||
runtimeSshHost
|
||||
runtimeSshHost,
|
||||
t
|
||||
)
|
||||
return (
|
||||
<ContextMenuItem
|
||||
@@ -97,7 +99,7 @@ export function OpenInContextSubmenu({
|
||||
})}
|
||||
{openInApplications.length > 0 ? <ContextMenuSeparator /> : null}
|
||||
<ContextMenuItem onSelect={openOpenInAppsSettings}>
|
||||
{translate('auto.components.sidebar.WorktreeOpenInMenu.1417fd8380', 'Customize apps...')}
|
||||
{t('auto.components.sidebar.WorktreeOpenInMenu.1417fd8380', 'Customize apps...')}
|
||||
</ContextMenuItem>
|
||||
</ContextMenuSubContent>
|
||||
</ContextMenuSub>
|
||||
|
||||
@@ -2,7 +2,7 @@ import { FileText, Globe, Minus, TerminalSquare } from 'lucide-react'
|
||||
import { ShortcutKeyCombo } from '@/components/ShortcutKeyCombo'
|
||||
import { Button } from '@/components/ui/button'
|
||||
import type { ShortcutKeyComboDetails } from '@/hooks/useShortcutLabel'
|
||||
import { translate } from '@/i18n/i18n'
|
||||
import { useTranslate } from '@/i18n/use-translate'
|
||||
|
||||
type FloatingTerminalEmptyStateProps = {
|
||||
onNewTerminal: () => void
|
||||
@@ -33,6 +33,7 @@ export function FloatingTerminalEmptyState({
|
||||
openMarkdownShortcut,
|
||||
closeShortcut
|
||||
}: FloatingTerminalEmptyStateProps): React.JSX.Element {
|
||||
const t = useTranslate()
|
||||
return (
|
||||
<div
|
||||
className="absolute inset-0 flex items-center justify-center"
|
||||
@@ -50,7 +51,7 @@ export function FloatingTerminalEmptyState({
|
||||
>
|
||||
<TerminalSquare className="size-3.5 opacity-90" />
|
||||
<span className="truncate text-left leading-none">
|
||||
{translate(
|
||||
{t(
|
||||
'auto.components.floating.terminal.FloatingTerminalPanel.3215fc73e9',
|
||||
'New Terminal'
|
||||
)}
|
||||
@@ -66,7 +67,7 @@ export function FloatingTerminalEmptyState({
|
||||
>
|
||||
<FileText className="size-3.5 opacity-90" />
|
||||
<span className="truncate text-left leading-none">
|
||||
{translate(
|
||||
{t(
|
||||
'auto.components.floating.terminal.FloatingTerminalPanel.629528690b',
|
||||
'New Markdown Note'
|
||||
)}
|
||||
@@ -81,7 +82,7 @@ export function FloatingTerminalEmptyState({
|
||||
>
|
||||
<FileText className="size-3.5 opacity-90" />
|
||||
<span className="truncate text-left leading-none">
|
||||
{translate(
|
||||
{t(
|
||||
'auto.components.floating.terminal.FloatingTerminalPanel.88ffb502e5',
|
||||
'Open Markdown Note'
|
||||
)}
|
||||
@@ -97,7 +98,7 @@ export function FloatingTerminalEmptyState({
|
||||
>
|
||||
<Globe className="size-3.5 opacity-90" />
|
||||
<span className="truncate text-left leading-none">
|
||||
{translate(
|
||||
{t(
|
||||
'auto.components.floating.terminal.FloatingTerminalPanel.8b07759314',
|
||||
'New Browser'
|
||||
)}
|
||||
@@ -113,10 +114,7 @@ export function FloatingTerminalEmptyState({
|
||||
>
|
||||
<Minus className="size-3.5 opacity-90" />
|
||||
<span className="truncate text-left leading-none">
|
||||
{translate(
|
||||
'auto.components.floating.terminal.FloatingTerminalPanel.fc1042e92b',
|
||||
'Minimize'
|
||||
)}
|
||||
{t('auto.components.floating.terminal.FloatingTerminalPanel.fc1042e92b', 'Minimize')}
|
||||
</span>
|
||||
<FloatingEmptyStateShortcut shortcut={closeShortcut} />
|
||||
</Button>
|
||||
|
||||
@@ -9,7 +9,7 @@ import {
|
||||
} from '@/components/ui/dropdown-menu'
|
||||
import { useAppStore } from '@/store'
|
||||
import type { FloatingTerminalTriggerLocation } from '../../../../shared/ui-chrome-types'
|
||||
import { translate } from '@/i18n/i18n'
|
||||
import { useTranslate } from '@/i18n/use-translate'
|
||||
import { useUiLanguageRevision } from '@/i18n/use-ui-language-revision'
|
||||
|
||||
type FloatingTerminalIconContextMenuProps = {
|
||||
@@ -25,6 +25,7 @@ export function FloatingTerminalIconContextMenu({
|
||||
className,
|
||||
style
|
||||
}: FloatingTerminalIconContextMenuProps): React.JSX.Element {
|
||||
const t = useTranslate()
|
||||
const languageRevision = useUiLanguageRevision()
|
||||
const updateSettings = useAppStore((s) => s.updateSettings)
|
||||
const [open, setOpen] = useState(false)
|
||||
@@ -44,7 +45,7 @@ export function FloatingTerminalIconContextMenu({
|
||||
if (currentLocation === 'floating-button') {
|
||||
return {
|
||||
icon: <PanelBottom className="size-3.5" />,
|
||||
label: translate(
|
||||
label: t(
|
||||
'auto.components.floating.terminal.FloatingTerminalIconContextMenu.0ee79e0674',
|
||||
'Move to Status Bar'
|
||||
),
|
||||
@@ -53,14 +54,14 @@ export function FloatingTerminalIconContextMenu({
|
||||
}
|
||||
return {
|
||||
icon: <PanelTop className="size-3.5" />,
|
||||
label: translate(
|
||||
label: t(
|
||||
'auto.components.floating.terminal.FloatingTerminalIconContextMenu.763f5fa2c1',
|
||||
'Move to Floating Button'
|
||||
),
|
||||
location: 'floating-button' as const
|
||||
}
|
||||
// oxlint-disable-next-line react-hooks/exhaustive-deps -- translate() reads the UI language implicitly.
|
||||
}, [currentLocation, languageRevision])
|
||||
}, [currentLocation, languageRevision, t])
|
||||
|
||||
return (
|
||||
<>
|
||||
@@ -117,7 +118,7 @@ export function FloatingTerminalIconContextMenu({
|
||||
}}
|
||||
>
|
||||
<EyeOff className="size-3.5" />
|
||||
{translate(
|
||||
{t(
|
||||
'auto.components.floating.terminal.FloatingTerminalIconContextMenu.8e7d775287',
|
||||
'Hide Floating Workspace'
|
||||
)}
|
||||
|
||||
+9
-15
@@ -1,5 +1,6 @@
|
||||
import { Button } from '@/components/ui/button'
|
||||
import { translate } from '@/i18n/i18n'
|
||||
import type { Translate } from '@/i18n/use-translate'
|
||||
|
||||
type FloatingTerminalOrchestrationCardProps = {
|
||||
visible: boolean
|
||||
@@ -7,11 +8,10 @@ type FloatingTerminalOrchestrationCardProps = {
|
||||
onEnable: () => void
|
||||
}
|
||||
|
||||
export function renderFloatingTerminalOrchestrationCard({
|
||||
visible,
|
||||
onDismiss,
|
||||
onEnable
|
||||
}: FloatingTerminalOrchestrationCardProps): React.JSX.Element | null {
|
||||
export function renderFloatingTerminalOrchestrationCard(
|
||||
{ visible, onDismiss, onEnable }: FloatingTerminalOrchestrationCardProps,
|
||||
t: Translate = translate
|
||||
): React.JSX.Element | null {
|
||||
if (!visible) {
|
||||
return null
|
||||
}
|
||||
@@ -23,13 +23,13 @@ export function renderFloatingTerminalOrchestrationCard({
|
||||
<div className="space-y-2">
|
||||
<div className="space-y-0.5">
|
||||
<p className="text-sm font-medium">
|
||||
{translate(
|
||||
{t(
|
||||
'auto.components.floating.terminal.FloatingTerminalPanel.2a3c5ddf5e',
|
||||
'Enable orchestration'
|
||||
)}
|
||||
</p>
|
||||
<p className="text-xs leading-5 text-muted-foreground">
|
||||
{translate(
|
||||
{t(
|
||||
'auto.components.floating.terminal.FloatingTerminalPanel.8cf80db43b',
|
||||
'Set up the Orca CLI and agent skill so agents can coordinate through Orca.'
|
||||
)}
|
||||
@@ -37,16 +37,10 @@ export function renderFloatingTerminalOrchestrationCard({
|
||||
</div>
|
||||
<div className="flex items-center gap-2">
|
||||
<Button type="button" variant="ghost" size="sm" className="flex-1" onClick={onDismiss}>
|
||||
{translate(
|
||||
'auto.components.floating.terminal.FloatingTerminalPanel.adc281394d',
|
||||
'Dismiss'
|
||||
)}
|
||||
{t('auto.components.floating.terminal.FloatingTerminalPanel.adc281394d', 'Dismiss')}
|
||||
</Button>
|
||||
<Button type="button" variant="default" size="sm" className="flex-1" onClick={onEnable}>
|
||||
{translate(
|
||||
'auto.components.floating.terminal.FloatingTerminalPanel.bbc177f98f',
|
||||
'Enable'
|
||||
)}
|
||||
{t('auto.components.floating.terminal.FloatingTerminalPanel.bbc177f98f', 'Enable')}
|
||||
</Button>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
+10
-12
@@ -17,7 +17,7 @@ import {
|
||||
import { useActiveProjectSkillRuntime } from '@/hooks/useActiveProjectSkillRuntime'
|
||||
import { refreshSkillFreshness } from '@/hooks/useSkillFreshness'
|
||||
import { useAppStore } from '@/store'
|
||||
import { translate } from '@/i18n/i18n'
|
||||
import { useTranslate } from '@/i18n/use-translate'
|
||||
|
||||
type FloatingTerminalOrchestrationDialogProps = {
|
||||
open: boolean
|
||||
@@ -30,6 +30,7 @@ export function FloatingTerminalOrchestrationDialog({
|
||||
onOpenChange,
|
||||
onSetupStateChange
|
||||
}: FloatingTerminalOrchestrationDialogProps): React.JSX.Element {
|
||||
const t = useTranslate()
|
||||
const activeSkillRuntime = useActiveProjectSkillRuntime()
|
||||
const {
|
||||
installed: orchestrationSkillDetected,
|
||||
@@ -64,14 +65,14 @@ export function FloatingTerminalOrchestrationDialog({
|
||||
{/* The panel hides its header, so the modal owns the title and status. */}
|
||||
<div className="flex flex-wrap items-center gap-2 pr-6">
|
||||
<DialogTitle>
|
||||
{translate(
|
||||
{t(
|
||||
'auto.components.floating.terminal.FloatingTerminalOrchestrationDialog.543f325a14',
|
||||
'Enable orchestration'
|
||||
)}
|
||||
</DialogTitle>
|
||||
{orchestrationSkillLoading && !orchestrationSkillDetected ? (
|
||||
<IntegrationStatusPill tone="neutral">
|
||||
{translate(
|
||||
{t(
|
||||
'auto.components.floating.terminal.FloatingTerminalOrchestrationDialog.dfd021ce46',
|
||||
'Checking...'
|
||||
)}
|
||||
@@ -81,7 +82,7 @@ export function FloatingTerminalOrchestrationDialog({
|
||||
<SkillFreshnessStatusPill skillName={ORCHESTRATION_SKILL_NAME} />
|
||||
) : (
|
||||
<IntegrationStatusPill tone="connected">
|
||||
{translate(
|
||||
{t(
|
||||
'auto.components.floating.terminal.FloatingTerminalOrchestrationDialog.630c0ac8c8',
|
||||
'Installed'
|
||||
)}
|
||||
@@ -89,7 +90,7 @@ export function FloatingTerminalOrchestrationDialog({
|
||||
)
|
||||
) : (
|
||||
<IntegrationStatusPill tone="attention">
|
||||
{translate(
|
||||
{t(
|
||||
'auto.components.floating.terminal.FloatingTerminalOrchestrationDialog.05d7aabc20',
|
||||
'Not installed'
|
||||
)}
|
||||
@@ -97,7 +98,7 @@ export function FloatingTerminalOrchestrationDialog({
|
||||
)}
|
||||
</div>
|
||||
<DialogDescription className="sr-only">
|
||||
{translate(
|
||||
{t(
|
||||
'auto.components.floating.terminal.FloatingTerminalOrchestrationDialog.6f0aed26b8',
|
||||
'Install the Orca CLI and orchestration skill so agents can coordinate through Orca.'
|
||||
)}
|
||||
@@ -105,11 +106,11 @@ export function FloatingTerminalOrchestrationDialog({
|
||||
</DialogHeader>
|
||||
|
||||
<AgentSkillSetupPanel
|
||||
title={translate(
|
||||
title={t(
|
||||
'auto.components.floating.terminal.FloatingTerminalOrchestrationDialog.1cd3f8af64',
|
||||
'Orchestration skill'
|
||||
)}
|
||||
description={translate(
|
||||
description={t(
|
||||
'auto.components.floating.terminal.FloatingTerminalOrchestrationDialog.f726054620',
|
||||
'Enables agents to hand off context and coordinate work through Orca.'
|
||||
)}
|
||||
@@ -120,10 +121,7 @@ export function FloatingTerminalOrchestrationDialog({
|
||||
installDisabled={Boolean(activeSkillRuntime.installDisabledReason)}
|
||||
variant="inline"
|
||||
hideHeader
|
||||
installLabel={translate(
|
||||
'auto.components.skills.SkillInstallDialog.39acb9e8f4',
|
||||
'Install skill'
|
||||
)}
|
||||
installLabel={t('auto.components.skills.SkillInstallDialog.39acb9e8f4', 'Install skill')}
|
||||
onBeforeInstall={() => {
|
||||
useAppStore.getState().recordFeatureInteraction('agent-orchestration-setup')
|
||||
}}
|
||||
|
||||
@@ -1,10 +1,12 @@
|
||||
import { renderFloatingTerminalPanelSurface } from './FloatingTerminalPanelSurface'
|
||||
import type { FloatingTerminalPanelProps } from './floating-terminal-panel-types'
|
||||
import { useFloatingTerminalPanelController } from './use-floating-terminal-panel-controller'
|
||||
import { useTranslate } from '../../i18n/use-translate'
|
||||
|
||||
export { FloatingTerminalToggleButton } from './FloatingTerminalToggleButton'
|
||||
|
||||
export function FloatingTerminalPanel(props: FloatingTerminalPanelProps): React.JSX.Element {
|
||||
const t = useTranslate()
|
||||
const surface = useFloatingTerminalPanelController(props)
|
||||
return renderFloatingTerminalPanelSurface(surface)
|
||||
return renderFloatingTerminalPanelSurface(surface, t)
|
||||
}
|
||||
|
||||
@@ -11,66 +11,70 @@ import { FloatingTerminalOrchestrationDialog } from './FloatingTerminalOrchestra
|
||||
import { FloatingTerminalResizeHandles } from './FloatingTerminalResizeHandles'
|
||||
import { FloatingTerminalWindowControls } from './FloatingTerminalWindowControls'
|
||||
import type { useFloatingTerminalPanelController } from './use-floating-terminal-panel-controller'
|
||||
import type { Translate } from '@/i18n/use-translate'
|
||||
|
||||
const NO_ACTIVITY_TERMINAL_PORTALS: [] = []
|
||||
|
||||
export function renderFloatingTerminalPanelSurface({
|
||||
open,
|
||||
onOpenChange,
|
||||
bounds,
|
||||
maximized,
|
||||
stagedBoundsRef,
|
||||
setPanelNode,
|
||||
commitUserBounds,
|
||||
reportFloatingFocusFromTarget,
|
||||
handleShortcutSurfaceKeyDown,
|
||||
handleDragStart,
|
||||
handleDragMove,
|
||||
handleDragEnd,
|
||||
handleTitlebarDoubleClick,
|
||||
surface,
|
||||
model,
|
||||
terminalItems,
|
||||
activeGroup,
|
||||
activeTerminalId,
|
||||
activeEditorUnifiedId,
|
||||
activeBrowserId,
|
||||
activeTab,
|
||||
activeTabType,
|
||||
expandedPaneByTabId,
|
||||
closeFloatingItemConfirmed,
|
||||
closeOthers,
|
||||
closeToRight,
|
||||
closeToLeft,
|
||||
createFloatingTerminalTab,
|
||||
createFloatingBrowserTab,
|
||||
createFloatingMarkdownTab,
|
||||
openFloatingMarkdownTab,
|
||||
setTabCustomTitle,
|
||||
setTabColor,
|
||||
setTabPaneExpanded,
|
||||
createBrowserTab,
|
||||
closeAllFiles,
|
||||
makePreviewFilePermanent,
|
||||
pinFile,
|
||||
toggleMaximized,
|
||||
hasVisibleFloatingTabs,
|
||||
managedBrowserCreationEnabled,
|
||||
cwd,
|
||||
panelViewportSettled,
|
||||
focusPanelForShortcuts,
|
||||
newTerminalShortcut,
|
||||
newBrowserShortcut,
|
||||
newMarkdownShortcut,
|
||||
openMarkdownShortcut,
|
||||
closeShortcut,
|
||||
showOrchestrationSetup,
|
||||
dismissOrchestrationSetup,
|
||||
setOrchestrationDialogOpen,
|
||||
previewUserBounds,
|
||||
orchestrationDialogOpen,
|
||||
refreshOrchestrationSetupVisibility
|
||||
}: ReturnType<typeof useFloatingTerminalPanelController>): React.JSX.Element {
|
||||
export function renderFloatingTerminalPanelSurface(
|
||||
{
|
||||
open,
|
||||
onOpenChange,
|
||||
bounds,
|
||||
maximized,
|
||||
stagedBoundsRef,
|
||||
setPanelNode,
|
||||
commitUserBounds,
|
||||
reportFloatingFocusFromTarget,
|
||||
handleShortcutSurfaceKeyDown,
|
||||
handleDragStart,
|
||||
handleDragMove,
|
||||
handleDragEnd,
|
||||
handleTitlebarDoubleClick,
|
||||
surface,
|
||||
model,
|
||||
terminalItems,
|
||||
activeGroup,
|
||||
activeTerminalId,
|
||||
activeEditorUnifiedId,
|
||||
activeBrowserId,
|
||||
activeTab,
|
||||
activeTabType,
|
||||
expandedPaneByTabId,
|
||||
closeFloatingItemConfirmed,
|
||||
closeOthers,
|
||||
closeToRight,
|
||||
closeToLeft,
|
||||
createFloatingTerminalTab,
|
||||
createFloatingBrowserTab,
|
||||
createFloatingMarkdownTab,
|
||||
openFloatingMarkdownTab,
|
||||
setTabCustomTitle,
|
||||
setTabColor,
|
||||
setTabPaneExpanded,
|
||||
createBrowserTab,
|
||||
closeAllFiles,
|
||||
makePreviewFilePermanent,
|
||||
pinFile,
|
||||
toggleMaximized,
|
||||
hasVisibleFloatingTabs,
|
||||
managedBrowserCreationEnabled,
|
||||
cwd,
|
||||
panelViewportSettled,
|
||||
focusPanelForShortcuts,
|
||||
newTerminalShortcut,
|
||||
newBrowserShortcut,
|
||||
newMarkdownShortcut,
|
||||
openMarkdownShortcut,
|
||||
closeShortcut,
|
||||
showOrchestrationSetup,
|
||||
dismissOrchestrationSetup,
|
||||
setOrchestrationDialogOpen,
|
||||
previewUserBounds,
|
||||
orchestrationDialogOpen,
|
||||
refreshOrchestrationSetupVisibility
|
||||
}: ReturnType<typeof useFloatingTerminalPanelController>,
|
||||
t?: Translate
|
||||
): React.JSX.Element {
|
||||
return (
|
||||
// Stay above notifications but below modals; separate shadow and border shells to keep rounded corners clean.
|
||||
<div
|
||||
@@ -254,11 +258,14 @@ export function renderFloatingTerminalPanelSurface({
|
||||
</div>
|
||||
)}
|
||||
</WorkspaceTabDragLayer>
|
||||
{renderFloatingTerminalOrchestrationCard({
|
||||
visible: showOrchestrationSetup && activeTabType === 'terminal',
|
||||
onDismiss: dismissOrchestrationSetup,
|
||||
onEnable: () => setOrchestrationDialogOpen(true)
|
||||
})}
|
||||
{renderFloatingTerminalOrchestrationCard(
|
||||
{
|
||||
visible: showOrchestrationSetup && activeTabType === 'terminal',
|
||||
onDismiss: dismissOrchestrationSetup,
|
||||
onEnable: () => setOrchestrationDialogOpen(true)
|
||||
},
|
||||
t
|
||||
)}
|
||||
{!maximized && (
|
||||
<FloatingTerminalResizeHandles
|
||||
bounds={bounds}
|
||||
|
||||
@@ -21,7 +21,7 @@ import {
|
||||
type FloatingTerminalTriggerPosition,
|
||||
type FloatingTerminalTriggerPositionSource
|
||||
} from './floating-terminal-trigger-position'
|
||||
import { translate } from '@/i18n/i18n'
|
||||
import { useTranslate } from '@/i18n/use-translate'
|
||||
|
||||
const FLOATING_TERMINAL_TRIGGER_DRAG_THRESHOLD = 4
|
||||
|
||||
@@ -64,6 +64,7 @@ export function FloatingTerminalToggleButton({
|
||||
open: boolean
|
||||
onToggle: () => void
|
||||
}): React.JSX.Element {
|
||||
const t = useTranslate()
|
||||
// Memoized: subscribe so a language change still repaints its labels.
|
||||
useTranslation()
|
||||
const shortcutLabel = useShortcutLabel('floatingTerminal.toggle')
|
||||
@@ -208,16 +209,16 @@ export function FloatingTerminalToggleButton({
|
||||
data-floating-terminal-toggle
|
||||
aria-label={
|
||||
open
|
||||
? translate(
|
||||
? t(
|
||||
'auto.components.floating.terminal.FloatingTerminalToggleButton.5785dd9148',
|
||||
'Minimize floating workspace'
|
||||
)
|
||||
: showAttentionDot
|
||||
? translate(
|
||||
? t(
|
||||
'auto.components.floating.terminal.FloatingTerminalToggleButton.4cb418b991',
|
||||
'Show floating workspace, new activity'
|
||||
)
|
||||
: translate(
|
||||
: t(
|
||||
'auto.components.floating.terminal.FloatingTerminalToggleButton.3b04b065b5',
|
||||
'Show floating workspace'
|
||||
)
|
||||
@@ -241,7 +242,7 @@ export function FloatingTerminalToggleButton({
|
||||
</Button>
|
||||
</TooltipTrigger>
|
||||
<TooltipContent side="left" sideOffset={6}>
|
||||
{translate(
|
||||
{t(
|
||||
'auto.components.floating.terminal.FloatingTerminalToggleButton.bfe7809a70',
|
||||
'{{value0}} floating workspace ({{value1}})',
|
||||
{ value0: open ? 'Minimize' : 'Show', value1: shortcutLabel }
|
||||
|
||||
@@ -12,7 +12,7 @@ import {
|
||||
DEFAULT_DISABLED_TUI_AGENTS,
|
||||
isTuiAgentEnabled
|
||||
} from '../../../../shared/agent-launch/catalog/tui-agent-selection'
|
||||
import { translate } from '@/i18n/i18n'
|
||||
import { useTranslate } from '@/i18n/use-translate'
|
||||
import { useOptionalShortcutLabel } from '@/hooks/useShortcutLabel'
|
||||
|
||||
type FloatingTerminalWindowControlsProps = {
|
||||
@@ -33,6 +33,7 @@ export function FloatingTerminalWindowControls({
|
||||
onToggleMaximized,
|
||||
onMinimize
|
||||
}: FloatingTerminalWindowControlsProps): React.JSX.Element {
|
||||
const t = useTranslate()
|
||||
const defaultTuiAgent = useAppStore((s) => s.settings?.defaultTuiAgent ?? null)
|
||||
const setActiveTabForWorktree = useAppStore((s) => s.setActiveTabForWorktree)
|
||||
const activateTab = useAppStore((s) => s.activateTab)
|
||||
@@ -90,7 +91,7 @@ export function FloatingTerminalWindowControls({
|
||||
variant="outline"
|
||||
size="icon-xs"
|
||||
className={controlButtonClassName}
|
||||
aria-label={translate(
|
||||
aria-label={t(
|
||||
'auto.components.floating.terminal.FloatingTerminalWindowControls.648352c51f',
|
||||
'Open {{value0}} in floating workspace',
|
||||
{ value0: defaultAgentLabel ?? defaultAgent }
|
||||
@@ -101,7 +102,7 @@ export function FloatingTerminalWindowControls({
|
||||
</Button>
|
||||
</TooltipTrigger>
|
||||
<TooltipContent side="bottom" sideOffset={6}>
|
||||
{translate(
|
||||
{t(
|
||||
'auto.components.floating.terminal.FloatingTerminalWindowControls.648352c51f',
|
||||
'Open {{value0}} in floating workspace',
|
||||
{ value0: defaultAgentLabel ?? defaultAgent }
|
||||
@@ -118,11 +119,11 @@ export function FloatingTerminalWindowControls({
|
||||
className={controlButtonClassName}
|
||||
aria-label={
|
||||
maximized
|
||||
? translate(
|
||||
? t(
|
||||
'auto.components.floating.terminal.FloatingTerminalWindowControls.1c79cba25d',
|
||||
'Restore floating workspace'
|
||||
)
|
||||
: translate(
|
||||
: t(
|
||||
'auto.components.floating.terminal.FloatingTerminalWindowControls.3f4ca29961',
|
||||
'Maximize floating workspace'
|
||||
)
|
||||
@@ -136,14 +137,14 @@ export function FloatingTerminalWindowControls({
|
||||
<TooltipContent side="bottom" sideOffset={6}>
|
||||
{maximized
|
||||
? withShortcutHint(
|
||||
translate(
|
||||
t(
|
||||
'auto.components.floating.terminal.FloatingTerminalWindowControls.b5686fee1e',
|
||||
'Restore'
|
||||
),
|
||||
maximizeShortcutLabel
|
||||
)
|
||||
: withShortcutHint(
|
||||
translate(
|
||||
t(
|
||||
'auto.components.floating.terminal.FloatingTerminalWindowControls.109870e023',
|
||||
'Maximize'
|
||||
),
|
||||
@@ -158,7 +159,7 @@ export function FloatingTerminalWindowControls({
|
||||
variant="outline"
|
||||
size="icon-xs"
|
||||
className={controlButtonClassName}
|
||||
aria-label={translate(
|
||||
aria-label={t(
|
||||
'auto.components.floating.terminal.FloatingTerminalWindowControls.1bbaa0302f',
|
||||
'Minimize floating workspace'
|
||||
)}
|
||||
@@ -169,7 +170,7 @@ export function FloatingTerminalWindowControls({
|
||||
</TooltipTrigger>
|
||||
<TooltipContent side="bottom" sideOffset={6}>
|
||||
{withShortcutHint(
|
||||
translate(
|
||||
t(
|
||||
'auto.components.floating.terminal.FloatingTerminalWindowControls.2f6054342c',
|
||||
'Minimize'
|
||||
),
|
||||
|
||||
@@ -6,7 +6,7 @@ import { ButtonGroup } from '@/components/ui/button-group'
|
||||
import { Tooltip, TooltipContent, TooltipTrigger } from '@/components/ui/tooltip'
|
||||
import { detectLanguage } from '@/lib/language-detect'
|
||||
import { cn } from '@/lib/utils'
|
||||
import { translate } from '@/i18n/i18n'
|
||||
import { useTranslate } from '@/i18n/use-translate'
|
||||
import {
|
||||
createCommentCodeContextExpansionState,
|
||||
resolveCommentCodeContextExpansionState,
|
||||
@@ -64,6 +64,7 @@ export function CommentCodeContext({
|
||||
baseSha: string | undefined
|
||||
loadPRFileContents: LoadPRFileContents
|
||||
}): React.JSX.Element | null {
|
||||
const t = useTranslate()
|
||||
const [contents, setContents] = useState<GitHubPRFileContents | null>(null)
|
||||
const [error, setError] = useState(false)
|
||||
const [contextExpansionState, setContextExpansionState] = useState(() =>
|
||||
@@ -158,7 +159,7 @@ export function CommentCodeContext({
|
||||
return (
|
||||
<div className="mb-3 flex items-center gap-2 rounded-md border border-border/40 bg-muted/20 px-3 py-2 text-[12px] text-muted-foreground">
|
||||
<LoaderCircle className="size-3.5 animate-spin" />
|
||||
{translate('auto.components.GitHubItemDialog.db61d76cd5', 'Loading code context…')}
|
||||
{t('auto.components.GitHubItemDialog.db61d76cd5', 'Loading code context…')}
|
||||
</div>
|
||||
)
|
||||
}
|
||||
@@ -195,11 +196,11 @@ export function CommentCodeContext({
|
||||
} = codeContext
|
||||
const language = detectLanguage(comment.path)
|
||||
const blockTooltip = shouldUseBlockRange
|
||||
? translate(
|
||||
? t(
|
||||
'auto.components.github.CommentCodeContext.showSurroundingCodeBlock',
|
||||
'Show surrounding code block'
|
||||
)
|
||||
: translate(
|
||||
: t(
|
||||
'auto.components.github.CommentCodeContext.showNearbyCodeContext',
|
||||
'Show nearby code context'
|
||||
)
|
||||
@@ -212,17 +213,17 @@ export function CommentCodeContext({
|
||||
<span className="shrink-0 font-mono">
|
||||
L{from}
|
||||
{to !== from
|
||||
? translate('auto.components.GitHubItemDialog.d1c0dad471', '-L{{value0}}', {
|
||||
? t('auto.components.GitHubItemDialog.d1c0dad471', '-L{{value0}}', {
|
||||
value0: to
|
||||
})
|
||||
: ''}
|
||||
</span>
|
||||
{(from !== commentFrom || to !== commentTo) && (
|
||||
<span className="shrink-0 font-mono text-muted-foreground/70">
|
||||
{translate('auto.components.GitHubItemDialog.bd7be7b1fd', 'comment L')}
|
||||
{t('auto.components.GitHubItemDialog.bd7be7b1fd', 'comment L')}
|
||||
{commentFrom}
|
||||
{commentTo !== commentFrom
|
||||
? translate('auto.components.GitHubItemDialog.d1c0dad471', '-L{{value0}}', {
|
||||
? t('auto.components.GitHubItemDialog.d1c0dad471', '-L{{value0}}', {
|
||||
value0: commentTo
|
||||
})
|
||||
: ''}
|
||||
@@ -231,10 +232,7 @@ export function CommentCodeContext({
|
||||
</div>
|
||||
<ButtonGroup
|
||||
className="text-muted-foreground"
|
||||
aria-label={translate(
|
||||
'auto.components.GitHubItemDialog.d43736d09c',
|
||||
'Code context controls'
|
||||
)}
|
||||
aria-label={t('auto.components.GitHubItemDialog.d43736d09c', 'Code context controls')}
|
||||
>
|
||||
{(contextBefore > 0 || contextAfter > 0) && (
|
||||
<Tooltip>
|
||||
@@ -248,7 +246,7 @@ export function CommentCodeContext({
|
||||
setContextBefore(0)
|
||||
setContextAfter(0)
|
||||
}}
|
||||
aria-label={translate(
|
||||
aria-label={t(
|
||||
'auto.components.GitHubItemDialog.b1574e8ac2',
|
||||
'Reset code context'
|
||||
)}
|
||||
@@ -257,7 +255,7 @@ export function CommentCodeContext({
|
||||
</Button>
|
||||
</TooltipTrigger>
|
||||
<TooltipContent>
|
||||
{translate('auto.components.GitHubItemDialog.b1574e8ac2', 'Reset code context')}
|
||||
{t('auto.components.GitHubItemDialog.b1574e8ac2', 'Reset code context')}
|
||||
</TooltipContent>
|
||||
</Tooltip>
|
||||
)}
|
||||
@@ -274,7 +272,7 @@ export function CommentCodeContext({
|
||||
Math.min(current + CODE_CONTEXT_EXPAND_STEP, commentFrom - 1)
|
||||
)
|
||||
}
|
||||
aria-label={translate(
|
||||
aria-label={t(
|
||||
'auto.components.GitHubItemDialog.307c98e8e3',
|
||||
'Show {{value0}} more lines above',
|
||||
{ value0: CODE_CONTEXT_EXPAND_STEP }
|
||||
@@ -284,7 +282,7 @@ export function CommentCodeContext({
|
||||
</Button>
|
||||
</TooltipTrigger>
|
||||
<TooltipContent>
|
||||
{translate('auto.components.GitHubItemDialog.5664681624', 'Show more lines above')}
|
||||
{t('auto.components.GitHubItemDialog.5664681624', 'Show more lines above')}
|
||||
</TooltipContent>
|
||||
</Tooltip>
|
||||
<Tooltip>
|
||||
@@ -300,7 +298,7 @@ export function CommentCodeContext({
|
||||
Math.min(current + CODE_CONTEXT_EXPAND_STEP, totalLines - commentTo)
|
||||
)
|
||||
}
|
||||
aria-label={translate(
|
||||
aria-label={t(
|
||||
'auto.components.GitHubItemDialog.showMoreLinesBelow',
|
||||
'Show {{value0}} more lines below',
|
||||
{ value0: CODE_CONTEXT_EXPAND_STEP }
|
||||
@@ -310,7 +308,7 @@ export function CommentCodeContext({
|
||||
</Button>
|
||||
</TooltipTrigger>
|
||||
<TooltipContent>
|
||||
{translate('auto.components.GitHubItemDialog.06c06e58ba', 'Show more lines below')}
|
||||
{t('auto.components.GitHubItemDialog.06c06e58ba', 'Show more lines below')}
|
||||
</TooltipContent>
|
||||
</Tooltip>
|
||||
<Tooltip>
|
||||
|
||||
@@ -4,6 +4,7 @@ import { Button } from '@/components/ui/button'
|
||||
import { Popover, PopoverContent, PopoverTrigger } from '@/components/ui/popover'
|
||||
import { Tooltip, TooltipContent, TooltipTrigger } from '@/components/ui/tooltip'
|
||||
import { translate } from '@/i18n/i18n'
|
||||
import { useTranslate, type Translate } from '@/i18n/use-translate'
|
||||
import { cn } from '@/lib/utils'
|
||||
import { GITHUB_REACTION_ORDER } from '@/lib/source-control/review/comments/pr-comment-reactions'
|
||||
import type { GitHubReaction, GitHubReactionContent } from '../../../../shared/github/comment-types'
|
||||
@@ -19,33 +20,33 @@ const REACTION_EMOJI: Record<GitHubReaction['content'], string> = {
|
||||
eyes: '👀'
|
||||
}
|
||||
|
||||
function reactionLabel(content: GitHubReactionContent): string {
|
||||
function reactionLabel(content: GitHubReactionContent, t: Translate = translate): string {
|
||||
switch (content) {
|
||||
case '+1':
|
||||
return translate('auto.components.github.CommentReactions.reaction.thumbsUp', 'thumbs up')
|
||||
return t('auto.components.github.CommentReactions.reaction.thumbsUp', 'thumbs up')
|
||||
case '-1':
|
||||
return translate('auto.components.github.CommentReactions.reaction.thumbsDown', 'thumbs down')
|
||||
return t('auto.components.github.CommentReactions.reaction.thumbsDown', 'thumbs down')
|
||||
case 'laugh':
|
||||
return translate('auto.components.github.CommentReactions.reaction.laugh', 'laugh')
|
||||
return t('auto.components.github.CommentReactions.reaction.laugh', 'laugh')
|
||||
case 'confused':
|
||||
return translate('auto.components.github.CommentReactions.reaction.confused', 'confused')
|
||||
return t('auto.components.github.CommentReactions.reaction.confused', 'confused')
|
||||
case 'heart':
|
||||
return translate('auto.components.github.CommentReactions.reaction.heart', 'heart')
|
||||
return t('auto.components.github.CommentReactions.reaction.heart', 'heart')
|
||||
case 'hooray':
|
||||
return translate('auto.components.github.CommentReactions.reaction.hooray', 'hooray')
|
||||
return t('auto.components.github.CommentReactions.reaction.hooray', 'hooray')
|
||||
case 'rocket':
|
||||
return translate('auto.components.github.CommentReactions.reaction.rocket', 'rocket')
|
||||
return t('auto.components.github.CommentReactions.reaction.rocket', 'rocket')
|
||||
case 'eyes':
|
||||
return translate('auto.components.github.CommentReactions.reaction.eyes', 'eyes')
|
||||
return t('auto.components.github.CommentReactions.reaction.eyes', 'eyes')
|
||||
}
|
||||
}
|
||||
|
||||
function reactionCountLabel(count: number, reaction: string): string {
|
||||
return translate(
|
||||
'auto.components.GitHubItemDialog.reactionCount',
|
||||
'{{count}} {{reaction}} reactions',
|
||||
{ count, reaction, defaultValue_one: '{{count}} {{reaction}} reaction' }
|
||||
)
|
||||
function reactionCountLabel(count: number, reaction: string, t: Translate = translate): string {
|
||||
return t('auto.components.GitHubItemDialog.reactionCount', '{{count}} {{reaction}} reactions', {
|
||||
count,
|
||||
reaction,
|
||||
defaultValue_one: '{{count}} {{reaction}} reaction'
|
||||
})
|
||||
}
|
||||
|
||||
export function CommentReactions({
|
||||
@@ -60,6 +61,7 @@ export function CommentReactions({
|
||||
reacted: boolean
|
||||
) => Promise<boolean> | boolean
|
||||
}): React.JSX.Element | null {
|
||||
const t = useTranslate()
|
||||
const visibleReactions = (reactions ?? []).filter((reaction) => reaction.count > 0)
|
||||
const addReactionButtonRef = React.useRef<HTMLButtonElement>(null)
|
||||
const pickerGroupRef = React.useRef<HTMLDivElement>(null)
|
||||
@@ -95,10 +97,7 @@ export function CommentReactions({
|
||||
}
|
||||
}
|
||||
|
||||
const pickerLabel = translate(
|
||||
'auto.components.github.CommentReactions.addReaction',
|
||||
'Add reaction'
|
||||
)
|
||||
const pickerLabel = t('auto.components.github.CommentReactions.addReaction', 'Add reaction')
|
||||
|
||||
return (
|
||||
<div className={cn('mt-2 flex flex-wrap items-center gap-1.5', className)}>
|
||||
@@ -115,7 +114,7 @@ export function CommentReactions({
|
||||
reaction.viewerHasReacted && 'border-ring bg-accent text-accent-foreground'
|
||||
)}
|
||||
aria-pressed={Boolean(reaction.viewerHasReacted)}
|
||||
aria-label={reactionCountLabel(reaction.count, reactionLabel(reaction.content))}
|
||||
aria-label={reactionCountLabel(reaction.count, reactionLabel(reaction.content, t), t)}
|
||||
onClick={() =>
|
||||
void changeReaction(
|
||||
reaction.content,
|
||||
@@ -131,7 +130,7 @@ export function CommentReactions({
|
||||
) : (
|
||||
<span
|
||||
className="inline-flex h-6 items-center gap-1 rounded-full border border-border/60 bg-muted/35 px-2 text-[12px] leading-none text-foreground"
|
||||
aria-label={reactionCountLabel(reaction.count, reactionLabel(reaction.content))}
|
||||
aria-label={reactionCountLabel(reaction.count, reactionLabel(reaction.content, t), t)}
|
||||
>
|
||||
<span aria-hidden="true">{REACTION_EMOJI[reaction.content]}</span>
|
||||
<span className="tabular-nums">{reaction.count}</span>
|
||||
@@ -191,15 +190,15 @@ export function CommentReactions({
|
||||
className={cn('text-lg', reacted && 'bg-accent text-accent-foreground')}
|
||||
aria-label={
|
||||
reacted
|
||||
? translate(
|
||||
? t(
|
||||
'auto.components.github.CommentReactions.removeNamedReaction',
|
||||
'Remove {{value0}} reaction',
|
||||
{ value0: reactionLabel(content) }
|
||||
{ value0: reactionLabel(content, t) }
|
||||
)
|
||||
: translate(
|
||||
: t(
|
||||
'auto.components.github.CommentReactions.addNamedReaction',
|
||||
'Add {{value0}} reaction',
|
||||
{ value0: reactionLabel(content) }
|
||||
{ value0: reactionLabel(content, t) }
|
||||
)
|
||||
}
|
||||
aria-pressed={reacted}
|
||||
|
||||
@@ -26,7 +26,7 @@ import {
|
||||
import { GitHubMarkdownImageInput } from './github-markdown-image-input'
|
||||
import { useImageInput } from '@/components/github/use-image-input'
|
||||
import type { GitHubOwnerRepo } from '../../../../shared/github/pull-request-types'
|
||||
import { translate } from '@/i18n/i18n'
|
||||
import { useTranslate } from '@/i18n/use-translate'
|
||||
import { useAppStore } from '@/store'
|
||||
import {
|
||||
getRichMarkdownSpellcheckAttribute,
|
||||
@@ -65,6 +65,7 @@ export function GitHubMarkdownComposer({
|
||||
layout = 'stacked',
|
||||
previewGithubRepo = null
|
||||
}: GitHubMarkdownComposerProps): React.JSX.Element {
|
||||
const t = useTranslate()
|
||||
const rootRef = useRef<HTMLDivElement | null>(null)
|
||||
const editorRef = useRef<Editor | null>(null)
|
||||
const applyingExternalValueRef = useRef(false)
|
||||
@@ -336,7 +337,7 @@ export function GitHubMarkdownComposer({
|
||||
>
|
||||
<Paperclip className="size-3.5 shrink-0" />
|
||||
<span>
|
||||
{translate(
|
||||
{t(
|
||||
'auto.components.github.GitHubMarkdownComposer.b7e4a1c902',
|
||||
'Paste, drop, or click to add files'
|
||||
)}
|
||||
|
||||
@@ -3,6 +3,7 @@ import type { GitHubOwnerRepo } from '../../../../shared/github/pull-request-typ
|
||||
import RepoBadgeLabel from '@/components/repo/RepoBadgeLabel'
|
||||
import { Trans } from 'react-i18next'
|
||||
import { i18n, translate } from '@/i18n/i18n'
|
||||
import { useTranslate, type Translate } from '@/i18n/use-translate'
|
||||
import { cn } from '@/lib/utils'
|
||||
import {
|
||||
githubRepoIdentityKey,
|
||||
@@ -21,10 +22,10 @@ export type IssueSourceIndicatorProps = {
|
||||
}
|
||||
|
||||
// Name the resolved repository, not remote aliases that could imply a stable UI-to-git-config mapping.
|
||||
function issueSourceTemplate(variant: 'list' | 'item'): string {
|
||||
function issueSourceTemplate(variant: 'list' | 'item', t: Translate = translate): string {
|
||||
return variant === 'item'
|
||||
? translate('auto.components.github.IssueSourceIndicator.issueFromRepo', 'Issue from <repo/>')
|
||||
: translate('auto.components.github.IssueSourceIndicator.issuesFromRepo', 'Issues from <repo/>')
|
||||
? t('auto.components.github.IssueSourceIndicator.issueFromRepo', 'Issue from <repo/>')
|
||||
: t('auto.components.github.IssueSourceIndicator.issuesFromRepo', 'Issues from <repo/>')
|
||||
}
|
||||
|
||||
export function sameGitHubOwnerRepo(
|
||||
@@ -46,6 +47,7 @@ export default function IssueSourceIndicator({
|
||||
localRepo,
|
||||
className
|
||||
}: IssueSourceIndicatorProps): React.JSX.Element | null {
|
||||
const t = useTranslate()
|
||||
if (!issues || !prs) {
|
||||
return null
|
||||
}
|
||||
@@ -57,7 +59,7 @@ export default function IssueSourceIndicator({
|
||||
host && !isDefaultGitHubHost(host)
|
||||
? `${host}/${issues.owner}/${issues.repo}`
|
||||
: `${issues.owner}/${issues.repo}`
|
||||
const template = issueSourceTemplate(variant)
|
||||
const template = issueSourceTemplate(variant, t)
|
||||
const label = template.replace('<repo/>', slug)
|
||||
return (
|
||||
<span
|
||||
|
||||
@@ -5,7 +5,8 @@ import { sameGitHubOwnerRepo } from '@/components/github/IssueSourceIndicator'
|
||||
import { Tooltip, TooltipContent, TooltipTrigger } from '@/components/ui/tooltip'
|
||||
import { cn } from '@/lib/utils'
|
||||
import { Trans } from 'react-i18next'
|
||||
import { i18n, translate } from '@/i18n/i18n'
|
||||
import { i18n } from '@/i18n/i18n'
|
||||
import { useTranslate } from '@/i18n/use-translate'
|
||||
|
||||
export type IssueSourceSelectorProps = {
|
||||
/** Unset preferences behave as auto; untouched repos omit the stored key. */
|
||||
@@ -54,6 +55,7 @@ export default function IssueSourceSelector({
|
||||
suppressTooltip = false,
|
||||
issueSource
|
||||
}: IssueSourceSelectorProps): React.JSX.Element | null {
|
||||
const t = useTranslate()
|
||||
if (!origin || !upstream) {
|
||||
return null
|
||||
}
|
||||
@@ -71,10 +73,7 @@ export default function IssueSourceSelector({
|
||||
const group = (
|
||||
<div
|
||||
role="group"
|
||||
aria-label={translate(
|
||||
'auto.components.github.IssueSourceSelector.787c970baf',
|
||||
'Issue source'
|
||||
)}
|
||||
aria-label={t('auto.components.github.IssueSourceSelector.787c970baf', 'Issue source')}
|
||||
className={cn(
|
||||
'inline-flex items-center overflow-hidden rounded border border-border/40',
|
||||
className
|
||||
@@ -94,7 +93,7 @@ export default function IssueSourceSelector({
|
||||
>
|
||||
{density === 'compact'
|
||||
? 'U'
|
||||
: translate('auto.components.github.IssueSourceSelector.30b2c9df91', 'Upstream')}
|
||||
: t('auto.components.github.IssueSourceSelector.30b2c9df91', 'Upstream')}
|
||||
</button>
|
||||
<button
|
||||
type="button"
|
||||
@@ -113,7 +112,7 @@ export default function IssueSourceSelector({
|
||||
>
|
||||
{density === 'compact'
|
||||
? 'O'
|
||||
: translate('auto.components.github.IssueSourceSelector.51d1608920', 'Origin')}
|
||||
: t('auto.components.github.IssueSourceSelector.51d1608920', 'Origin')}
|
||||
</button>
|
||||
</div>
|
||||
)
|
||||
@@ -132,7 +131,7 @@ export default function IssueSourceSelector({
|
||||
<TooltipContent side="bottom" sideOffset={4} className="max-w-[260px]">
|
||||
<Trans
|
||||
i18n={i18n}
|
||||
defaults={translate(
|
||||
defaults={t(
|
||||
'auto.components.github.IssueSourceSelector.showingIssuesFromRepo',
|
||||
'Showing issues from <repo/>'
|
||||
)}
|
||||
|
||||
@@ -4,7 +4,7 @@ import { LoaderCircle, Pencil } from 'lucide-react'
|
||||
import { toast } from 'sonner'
|
||||
import { Popover, PopoverContent, PopoverTrigger } from '@/components/ui/popover'
|
||||
import { cn } from '@/lib/utils'
|
||||
import { translate } from '@/i18n/i18n'
|
||||
import { useTranslate } from '@/i18n/use-translate'
|
||||
import { useAppStore } from '@/store'
|
||||
import { useImmediateMutation } from '@/hooks/useIssueMetadata'
|
||||
import { useRepoAssignees } from '@/hooks/useGitHubRepoMetadata'
|
||||
@@ -34,6 +34,7 @@ export function PRAssigneesPanel({
|
||||
sourceContext?: TaskSourceContext | null
|
||||
onMutated: () => void
|
||||
}): React.JSX.Element {
|
||||
const t = useTranslate()
|
||||
const [assigneePopoverOpen, setAssigneePopoverOpen] = useState(false)
|
||||
const [localAssignees, setLocalAssignees] = useState<GitHubAssignableUser[]>(
|
||||
() => item.assignees ?? []
|
||||
@@ -176,16 +177,13 @@ export function PRAssigneesPanel({
|
||||
return (
|
||||
<section>
|
||||
<div className="mb-2 flex items-center justify-between text-[11px] font-semibold uppercase tracking-[0.05em] text-muted-foreground">
|
||||
<span>{translate('auto.components.GitHubItemDialog.83ac703dda', 'Assignees')}</span>
|
||||
<span>{t('auto.components.GitHubItemDialog.83ac703dda', 'Assignees')}</span>
|
||||
<Popover open={assigneePopoverOpen} onOpenChange={setAssigneePopoverOpen}>
|
||||
<PopoverTrigger asChild>
|
||||
<button
|
||||
type="button"
|
||||
disabled={!canEditAssignees || isPending('assignees') || repoAssignees.loading}
|
||||
aria-label={translate(
|
||||
'auto.components.GitHubItemDialog.76adcf5fe2',
|
||||
'Edit assignees'
|
||||
)}
|
||||
aria-label={t('auto.components.GitHubItemDialog.76adcf5fe2', 'Edit assignees')}
|
||||
className="rounded p-0.5 text-muted-foreground transition hover:bg-accent hover:text-foreground disabled:opacity-50"
|
||||
>
|
||||
{isPending('assignees') ? (
|
||||
@@ -244,7 +242,7 @@ export function PRAssigneesPanel({
|
||||
</div>
|
||||
{localAssignees.length === 0 ? (
|
||||
<div className="text-[12px] text-muted-foreground">
|
||||
{translate('auto.components.GitHubItemDialog.c67de9e2fe', 'No one assigned')}
|
||||
{t('auto.components.GitHubItemDialog.c67de9e2fe', 'No one assigned')}
|
||||
</div>
|
||||
) : (
|
||||
<ul className="flex flex-col gap-1.5">
|
||||
|
||||
@@ -21,6 +21,7 @@ import type {
|
||||
import type { RuntimeClientTarget } from '../../../../shared/runtime-client-target'
|
||||
import type { ParsedTaskQuery } from '../../../../shared/task-query'
|
||||
import { translate } from '@/i18n/i18n'
|
||||
import { useTranslate, type Translate } from '@/i18n/use-translate'
|
||||
|
||||
type Props = {
|
||||
parsed: ParsedTaskQuery
|
||||
@@ -35,8 +36,8 @@ type Props = {
|
||||
|
||||
export type { PRFilterChange } from '@/components/github/PRFilterSections'
|
||||
|
||||
function currentUserLabel(): string {
|
||||
return translate('auto.components.github.PRFilterDropdowns.currentUser', 'Current user')
|
||||
function currentUserLabel(t: Translate = translate): string {
|
||||
return t('auto.components.github.PRFilterDropdowns.currentUser', 'Current user')
|
||||
}
|
||||
|
||||
function userOptions(users: GitHubAssignableUser[]): PickerOption[] {
|
||||
@@ -52,13 +53,14 @@ function ActivePill({
|
||||
value: string
|
||||
onClear: () => void
|
||||
}): React.JSX.Element {
|
||||
const t = useTranslate()
|
||||
return (
|
||||
<span className="inline-flex h-6 items-center gap-1 rounded-full border border-border/60 bg-muted/50 pl-2 pr-1 text-[11px] text-foreground">
|
||||
<span className="text-muted-foreground">{label}:</span>
|
||||
<span className="max-w-[160px] truncate font-medium">{value}</span>
|
||||
<button
|
||||
type="button"
|
||||
aria-label={translate(
|
||||
aria-label={t(
|
||||
'auto.components.github.PRFilterDropdowns.8a2ffbf9b3',
|
||||
'Remove {{value0}} filter',
|
||||
{ value0: label }
|
||||
@@ -80,6 +82,7 @@ export default function PRFilterDropdowns({
|
||||
ownerTarget,
|
||||
onChange
|
||||
}: Props): React.JSX.Element {
|
||||
const t = useTranslate()
|
||||
const [openSection, setOpenSection] = useState<SectionKey | null>(null)
|
||||
const [popoverOpen, setPopoverOpen] = useState(false)
|
||||
const owner = primarySlug?.owner ?? null
|
||||
@@ -141,12 +144,12 @@ export default function PRFilterDropdowns({
|
||||
const statusPillValue = ((): string | null => {
|
||||
const parts: string[] = []
|
||||
if (parsed.state === 'closed' || parsed.state === 'merged') {
|
||||
parts.push(filterStateLabel(parsed.state))
|
||||
parts.push(filterStateLabel(parsed.state, t))
|
||||
} else if (parsed.state === 'all') {
|
||||
parts.push(translate('auto.components.github.PRFilterDropdowns.anyState', 'Any'))
|
||||
parts.push(t('auto.components.github.PRFilterDropdowns.anyState', 'Any'))
|
||||
}
|
||||
if (parsed.draft) {
|
||||
parts.push(filterDraftLabel())
|
||||
parts.push(filterDraftLabel(t))
|
||||
}
|
||||
return parts.length > 0 ? parts.join(' · ') : null
|
||||
})()
|
||||
@@ -185,7 +188,7 @@ export default function PRFilterDropdowns({
|
||||
)}
|
||||
>
|
||||
<ListFilter className="size-3.5" />
|
||||
{translate('auto.components.github.PRFilterDropdowns.79c54552f7', 'Filters')}
|
||||
{t('auto.components.github.PRFilterDropdowns.79c54552f7', 'Filters')}
|
||||
{activeCount > 0 ? (
|
||||
<span className="ml-0.5 rounded-full bg-muted px-1.5 text-[10px] font-medium text-foreground">
|
||||
{activeCount}
|
||||
@@ -246,22 +249,22 @@ export default function PRFilterDropdowns({
|
||||
</Popover>
|
||||
{statusPillValue ? (
|
||||
<ActivePill
|
||||
label={translate('auto.components.github.PRFilterDropdowns.13b3ac0a84', 'Status')}
|
||||
label={t('auto.components.github.PRFilterDropdowns.13b3ac0a84', 'Status')}
|
||||
value={statusPillValue}
|
||||
onClear={() => onChange({ state: 'open', draft: false })}
|
||||
/>
|
||||
) : null}
|
||||
{parsed.author ? (
|
||||
<ActivePill
|
||||
label={translate('auto.components.github.PRFilterDropdowns.01f3f3d161', 'Author')}
|
||||
label={t('auto.components.github.PRFilterDropdowns.01f3f3d161', 'Author')}
|
||||
value={parsed.author}
|
||||
onClear={() => onChange({ author: null })}
|
||||
/>
|
||||
) : null}
|
||||
{parsed.labels.length > 0 ? (
|
||||
<ActivePill
|
||||
label={translate('auto.components.github.PRFilterDropdowns.9d0f2eda6d', 'Label')}
|
||||
value={filterLabelsValue(parsed.labels) ?? ''}
|
||||
label={t('auto.components.github.PRFilterDropdowns.9d0f2eda6d', 'Label')}
|
||||
value={filterLabelsValue(parsed.labels, t) ?? ''}
|
||||
onClear={() => onChange({ labels: [] })}
|
||||
/>
|
||||
) : null}
|
||||
@@ -269,8 +272,8 @@ export default function PRFilterDropdowns({
|
||||
<ActivePill
|
||||
label={
|
||||
reviewerKind === 'reviewed-by'
|
||||
? translate('auto.components.github.PRFilterDropdowns.7f1ba66c3e', 'Reviewed by')
|
||||
: translate('auto.components.github.PRFilterDropdowns.b27b7e526c', 'Review from')
|
||||
? t('auto.components.github.PRFilterDropdowns.7f1ba66c3e', 'Reviewed by')
|
||||
: t('auto.components.github.PRFilterDropdowns.b27b7e526c', 'Review from')
|
||||
}
|
||||
value={reviewerActive}
|
||||
onClear={() => onChange({ reviewer: null })}
|
||||
@@ -278,7 +281,7 @@ export default function PRFilterDropdowns({
|
||||
) : null}
|
||||
{parsed.assignee ? (
|
||||
<ActivePill
|
||||
label={translate('auto.components.github.PRFilterDropdowns.979be3cf6b', 'Assignee')}
|
||||
label={t('auto.components.github.PRFilterDropdowns.979be3cf6b', 'Assignee')}
|
||||
value={parsed.assignee}
|
||||
onClear={() => onChange({ assignee: null })}
|
||||
/>
|
||||
|
||||
@@ -10,6 +10,7 @@ import {
|
||||
import { cn } from '@/lib/utils'
|
||||
import { Trans } from 'react-i18next'
|
||||
import { i18n, translate } from '@/i18n/i18n'
|
||||
import { useTranslate, type Translate } from '@/i18n/use-translate'
|
||||
import { isClipboardTextByteLengthOverLimit } from '../../../../shared/clipboard-text'
|
||||
|
||||
export type PickerOption = { key: string; primary: string; secondary?: string }
|
||||
@@ -51,31 +52,30 @@ export function filterPullRequestPickerOptions(
|
||||
)
|
||||
}
|
||||
|
||||
function pickerFallbackText({
|
||||
loading,
|
||||
queryTooLarge,
|
||||
error,
|
||||
emptyText
|
||||
}: {
|
||||
loading: boolean
|
||||
queryTooLarge: boolean
|
||||
error: string | null
|
||||
emptyText?: string
|
||||
}): string {
|
||||
function pickerFallbackText(
|
||||
{
|
||||
loading,
|
||||
queryTooLarge,
|
||||
error,
|
||||
emptyText
|
||||
}: {
|
||||
loading: boolean
|
||||
queryTooLarge: boolean
|
||||
error: string | null
|
||||
emptyText?: string
|
||||
},
|
||||
t: Translate = translate
|
||||
): string {
|
||||
if (loading) {
|
||||
return translate('auto.components.github.PRFilterPickers.loading', 'Loading…')
|
||||
return t('auto.components.github.PRFilterPickers.loading', 'Loading…')
|
||||
}
|
||||
if (queryTooLarge) {
|
||||
return translate(
|
||||
return t(
|
||||
'auto.components.github.PRFilterPickers.searchTextTooLarge',
|
||||
'Search text is too large.'
|
||||
)
|
||||
}
|
||||
return (
|
||||
error ??
|
||||
emptyText ??
|
||||
translate('auto.components.github.PRFilterPickers.noMatches', 'No matches')
|
||||
)
|
||||
return error ?? emptyText ?? t('auto.components.github.PRFilterPickers.noMatches', 'No matches')
|
||||
}
|
||||
|
||||
export function SingleSelectList({
|
||||
@@ -100,6 +100,7 @@ export function SingleSelectList({
|
||||
allowCustomValue?: boolean
|
||||
onSelect: (value: string | null) => void
|
||||
}): React.JSX.Element {
|
||||
const t = useTranslate()
|
||||
const [query, setQuery] = useState('')
|
||||
const filtered = useMemo(() => filterPullRequestPickerOptions(options, query), [options, query])
|
||||
const { queryTooLarge, trimmedQuery } = getPullRequestPickerQueryState(query)
|
||||
@@ -109,11 +110,11 @@ export function SingleSelectList({
|
||||
!queryTooLarge &&
|
||||
!filtered.some((o) => o.key.toLowerCase() === trimmedQuery.toLowerCase())
|
||||
const fallback = showCustom
|
||||
? translate(
|
||||
? t(
|
||||
'auto.components.github.PRFilterPickers.pressEnterToUseValue',
|
||||
'Press Enter to use the typed value.'
|
||||
)
|
||||
: pickerFallbackText({ loading, queryTooLarge, error, emptyText })
|
||||
: pickerFallbackText({ loading, queryTooLarge, error, emptyText }, t)
|
||||
|
||||
return (
|
||||
<Command shouldFilter={false}>
|
||||
@@ -134,10 +135,7 @@ export function SingleSelectList({
|
||||
<span className="min-w-0 truncate text-muted-foreground">
|
||||
<Trans
|
||||
i18n={i18n}
|
||||
defaults={translate(
|
||||
'auto.components.github.PRFilterPickers.useTypedValue',
|
||||
'Use <value/>'
|
||||
)}
|
||||
defaults={t('auto.components.github.PRFilterPickers.useTypedValue', 'Use <value/>')}
|
||||
components={{
|
||||
value: <span className="font-medium text-foreground">{trimmedQuery}</span>
|
||||
}}
|
||||
@@ -151,7 +149,7 @@ export function SingleSelectList({
|
||||
onSelect={() => onSelect(null)}
|
||||
className="gap-2 px-3 py-1.5 text-xs text-muted-foreground"
|
||||
>
|
||||
{translate('auto.components.github.PRFilterPickers.472c12ae03', 'Clear')}
|
||||
{t('auto.components.github.PRFilterPickers.472c12ae03', 'Clear')}
|
||||
</CommandItem>
|
||||
) : null}
|
||||
{filtered.map((opt) => {
|
||||
@@ -195,16 +193,20 @@ export function MultiSelectList({
|
||||
emptyText?: string
|
||||
onChange: (next: string[]) => void
|
||||
}): React.JSX.Element {
|
||||
const t = useTranslate()
|
||||
const [query, setQuery] = useState('')
|
||||
const filtered = useMemo(() => filterPullRequestPickerOptions(options, query), [options, query])
|
||||
const selectedSet = useMemo(() => new Set(selected), [selected])
|
||||
const { queryTooLarge } = getPullRequestPickerQueryState(query)
|
||||
const fallback = pickerFallbackText({
|
||||
loading,
|
||||
queryTooLarge,
|
||||
error,
|
||||
emptyText
|
||||
})
|
||||
const fallback = pickerFallbackText(
|
||||
{
|
||||
loading,
|
||||
queryTooLarge,
|
||||
error,
|
||||
emptyText
|
||||
},
|
||||
t
|
||||
)
|
||||
|
||||
const toggle = (key: string): void => {
|
||||
const next = new Set(selectedSet)
|
||||
@@ -232,13 +234,9 @@ export function MultiSelectList({
|
||||
onSelect={() => onChange([])}
|
||||
className="gap-2 px-3 py-1.5 text-xs text-muted-foreground"
|
||||
>
|
||||
{translate(
|
||||
'auto.components.github.PRFilterPickers.clearSelected',
|
||||
'Clear ({{count}})',
|
||||
{
|
||||
count: selected.length
|
||||
}
|
||||
)}
|
||||
{t('auto.components.github.PRFilterPickers.clearSelected', 'Clear ({{count}})', {
|
||||
count: selected.length
|
||||
})}
|
||||
</CommandItem>
|
||||
) : null}
|
||||
{filtered.map((opt) => {
|
||||
|
||||
@@ -8,6 +8,7 @@ import {
|
||||
import { cn } from '@/lib/utils'
|
||||
import type { ParsedTaskQuery } from '../../../../shared/task-query'
|
||||
import { translate } from '@/i18n/i18n'
|
||||
import { useTranslate, type Translate } from '@/i18n/use-translate'
|
||||
|
||||
export type PRFilterChange = {
|
||||
author?: string | null
|
||||
@@ -20,42 +21,45 @@ export type PRFilterChange = {
|
||||
|
||||
export type SectionKey = 'status' | 'author' | 'label' | 'reviewer' | 'assignee'
|
||||
|
||||
export function filterStateLabel(state: 'open' | 'closed' | 'merged'): string {
|
||||
export function filterStateLabel(
|
||||
state: 'open' | 'closed' | 'merged',
|
||||
t: Translate = translate
|
||||
): string {
|
||||
if (state === 'open') {
|
||||
return translate('auto.components.github.PRFilterSections.d78b60b5c2', 'Open')
|
||||
return t('auto.components.github.PRFilterSections.d78b60b5c2', 'Open')
|
||||
}
|
||||
if (state === 'closed') {
|
||||
return translate('auto.components.github.PRFilterSections.0fd3249e2e', 'Closed')
|
||||
return t('auto.components.github.PRFilterSections.0fd3249e2e', 'Closed')
|
||||
}
|
||||
return translate('auto.components.github.PRFilterSections.bd162b7d5a', 'Merged')
|
||||
return t('auto.components.github.PRFilterSections.bd162b7d5a', 'Merged')
|
||||
}
|
||||
|
||||
export function filterDraftLabel(): string {
|
||||
return translate('auto.components.github.PRFilterSections.draft', 'Draft')
|
||||
export function filterDraftLabel(t: Translate = translate): string {
|
||||
return t('auto.components.github.PRFilterSections.draft', 'Draft')
|
||||
}
|
||||
|
||||
export function filterLabelsValue(labels: string[]): string | null {
|
||||
export function filterLabelsValue(labels: string[], t: Translate = translate): string | null {
|
||||
if (labels.length === 0) {
|
||||
return null
|
||||
}
|
||||
if (labels.length === 1) {
|
||||
return labels[0]
|
||||
}
|
||||
return translate('auto.components.github.PRFilterSections.labelCount', '{{count}} labels', {
|
||||
return t('auto.components.github.PRFilterSections.labelCount', '{{count}} labels', {
|
||||
count: labels.length,
|
||||
defaultValue_one: '{{count}} label'
|
||||
})
|
||||
}
|
||||
|
||||
function statusLabel(parsed: ParsedTaskQuery): string {
|
||||
function statusLabel(parsed: ParsedTaskQuery, t: Translate = translate): string {
|
||||
const parts: string[] = []
|
||||
if (parsed.state === 'all') {
|
||||
parts.push(translate('auto.components.github.PRFilterSections.allStates', 'All'))
|
||||
parts.push(t('auto.components.github.PRFilterSections.allStates', 'All'))
|
||||
} else if (parsed.state) {
|
||||
parts.push(filterStateLabel(parsed.state))
|
||||
parts.push(filterStateLabel(parsed.state, t))
|
||||
}
|
||||
if (parsed.draft) {
|
||||
parts.push(filterDraftLabel())
|
||||
parts.push(filterDraftLabel(t))
|
||||
}
|
||||
return parts.join(' · ')
|
||||
}
|
||||
@@ -69,38 +73,39 @@ function StatusSection({
|
||||
kind: 'prs' | 'issues'
|
||||
onSelect: (change: PRFilterChange) => void
|
||||
}): React.JSX.Element {
|
||||
const t = useTranslate()
|
||||
const states: { key: 'open' | 'closed' | 'merged' | 'all'; label: string }[] =
|
||||
kind === 'prs'
|
||||
? [
|
||||
{
|
||||
key: 'open',
|
||||
label: filterStateLabel('open')
|
||||
label: filterStateLabel('open', t)
|
||||
},
|
||||
{
|
||||
key: 'closed',
|
||||
label: filterStateLabel('closed')
|
||||
label: filterStateLabel('closed', t)
|
||||
},
|
||||
{
|
||||
key: 'merged',
|
||||
label: filterStateLabel('merged')
|
||||
label: filterStateLabel('merged', t)
|
||||
},
|
||||
{
|
||||
key: 'all',
|
||||
label: translate('auto.components.github.PRFilterSections.2b2f019091', 'Any state')
|
||||
label: t('auto.components.github.PRFilterSections.2b2f019091', 'Any state')
|
||||
}
|
||||
]
|
||||
: [
|
||||
{
|
||||
key: 'open',
|
||||
label: filterStateLabel('open')
|
||||
label: filterStateLabel('open', t)
|
||||
},
|
||||
{
|
||||
key: 'closed',
|
||||
label: filterStateLabel('closed')
|
||||
label: filterStateLabel('closed', t)
|
||||
},
|
||||
{
|
||||
key: 'all',
|
||||
label: translate('auto.components.github.PRFilterSections.2b2f019091', 'Any state')
|
||||
label: t('auto.components.github.PRFilterSections.2b2f019091', 'Any state')
|
||||
}
|
||||
]
|
||||
return (
|
||||
@@ -120,7 +125,7 @@ function StatusSection({
|
||||
<span>{s.label}</span>
|
||||
{active ? (
|
||||
<span className="text-[10px] text-muted-foreground">
|
||||
{translate('auto.components.github.PRFilterSections.e0002f1eba', 'selected')}
|
||||
{t('auto.components.github.PRFilterSections.e0002f1eba', 'selected')}
|
||||
</span>
|
||||
) : null}
|
||||
</button>
|
||||
@@ -138,6 +143,7 @@ function DraftToggle({
|
||||
parsed: ParsedTaskQuery
|
||||
onSelect: (change: PRFilterChange) => void
|
||||
}): React.JSX.Element {
|
||||
const t = useTranslate()
|
||||
return (
|
||||
<>
|
||||
<div className="my-1 h-px bg-border" />
|
||||
@@ -149,14 +155,14 @@ function DraftToggle({
|
||||
parsed.draft && 'bg-muted/40 font-medium'
|
||||
)}
|
||||
>
|
||||
<span>{translate('auto.components.github.PRFilterSections.b930de7194', 'Draft only')}</span>
|
||||
<span>{t('auto.components.github.PRFilterSections.b930de7194', 'Draft only')}</span>
|
||||
{parsed.draft ? (
|
||||
<span className="text-[10px] text-muted-foreground">
|
||||
{translate('auto.components.github.PRFilterSections.1e9b5244f2', 'on')}
|
||||
{t('auto.components.github.PRFilterSections.1e9b5244f2', 'on')}
|
||||
</span>
|
||||
) : (
|
||||
<span className="text-[10px] text-muted-foreground">
|
||||
{translate('auto.components.github.PRFilterSections.f0cf6dd591', 'off')}
|
||||
{t('auto.components.github.PRFilterSections.f0cf6dd591', 'off')}
|
||||
</span>
|
||||
)}
|
||||
</button>
|
||||
@@ -190,22 +196,23 @@ export function SectionMenu({
|
||||
onPick: (s: SectionKey) => void
|
||||
onClearAll: (() => void) | null
|
||||
}): React.JSX.Element {
|
||||
const status = statusLabel(parsed)
|
||||
const t = useTranslate()
|
||||
const status = statusLabel(parsed, t)
|
||||
const rows: { key: SectionKey; label: string; value: string | null }[] = [
|
||||
{
|
||||
key: 'status',
|
||||
label: translate('auto.components.github.PRFilterSections.764a0b4ce1', 'Status'),
|
||||
label: t('auto.components.github.PRFilterSections.764a0b4ce1', 'Status'),
|
||||
value: status || null
|
||||
},
|
||||
{
|
||||
key: 'author',
|
||||
label: translate('auto.components.github.PRFilterSections.24754c44ad', 'Author'),
|
||||
label: t('auto.components.github.PRFilterSections.24754c44ad', 'Author'),
|
||||
value: parsed.author
|
||||
},
|
||||
{
|
||||
key: 'label',
|
||||
label: translate('auto.components.github.PRFilterSections.b1d9fdea08', 'Label'),
|
||||
value: filterLabelsValue(parsed.labels)
|
||||
label: t('auto.components.github.PRFilterSections.b1d9fdea08', 'Label'),
|
||||
value: filterLabelsValue(parsed.labels, t)
|
||||
},
|
||||
...(kind === 'prs'
|
||||
? [
|
||||
@@ -213,15 +220,15 @@ export function SectionMenu({
|
||||
key: 'reviewer' as SectionKey,
|
||||
label:
|
||||
reviewerKind === 'reviewed-by'
|
||||
? translate('auto.components.github.PRFilterDropdowns.7f1ba66c3e', 'Reviewed by')
|
||||
: translate('auto.components.github.PRFilterDropdowns.b27b7e526c', 'Review from'),
|
||||
? t('auto.components.github.PRFilterDropdowns.7f1ba66c3e', 'Reviewed by')
|
||||
: t('auto.components.github.PRFilterDropdowns.b27b7e526c', 'Review from'),
|
||||
value: reviewerActive
|
||||
}
|
||||
]
|
||||
: []),
|
||||
{
|
||||
key: 'assignee',
|
||||
label: translate('auto.components.github.PRFilterSections.ea3416d646', 'Assignee'),
|
||||
label: t('auto.components.github.PRFilterSections.ea3416d646', 'Assignee'),
|
||||
value: parsed.assignee
|
||||
}
|
||||
]
|
||||
@@ -229,11 +236,8 @@ export function SectionMenu({
|
||||
<div className="py-1 text-xs">
|
||||
<div className="px-3 py-1.5 text-[10px] font-medium uppercase tracking-wide text-muted-foreground">
|
||||
{kind === 'prs'
|
||||
? translate(
|
||||
'auto.components.github.PRFilterSections.filterPullRequests',
|
||||
'Filter pull requests'
|
||||
)
|
||||
: translate('auto.components.github.PRFilterSections.filterIssues', 'Filter issues')}
|
||||
? t('auto.components.github.PRFilterSections.filterPullRequests', 'Filter pull requests')
|
||||
: t('auto.components.github.PRFilterSections.filterIssues', 'Filter issues')}
|
||||
</div>
|
||||
{rows.map((row) => (
|
||||
<button
|
||||
@@ -257,7 +261,7 @@ export function SectionMenu({
|
||||
onClick={onClearAll}
|
||||
className="w-full px-3 py-1.5 text-left text-muted-foreground transition hover:bg-muted/50 hover:text-foreground"
|
||||
>
|
||||
{translate('auto.components.github.PRFilterSections.30ebb6ca44', 'Clear all filters')}
|
||||
{t('auto.components.github.PRFilterSections.30ebb6ca44', 'Clear all filters')}
|
||||
</button>
|
||||
</>
|
||||
) : null}
|
||||
@@ -296,6 +300,7 @@ export function SectionDetail({
|
||||
onBack: () => void
|
||||
onSelect: (change: PRFilterChange) => void
|
||||
}): React.JSX.Element {
|
||||
const t = useTranslate()
|
||||
return (
|
||||
<div>
|
||||
<button
|
||||
@@ -304,7 +309,7 @@ export function SectionDetail({
|
||||
className="flex w-full items-center gap-1 border-b border-border px-3 py-1.5 text-[11px] text-muted-foreground transition hover:bg-muted/50 hover:text-foreground"
|
||||
>
|
||||
<ChevronRight className="size-3 rotate-180" />
|
||||
{translate('auto.components.github.PRFilterSections.b69fa4fa20', 'Back')}
|
||||
{t('auto.components.github.PRFilterSections.b69fa4fa20', 'Back')}
|
||||
</button>
|
||||
{section === 'status' ? (
|
||||
<StatusSection parsed={parsed} kind={kind} onSelect={onSelect} />
|
||||
@@ -315,11 +320,11 @@ export function SectionDetail({
|
||||
activeValue={parsed.author}
|
||||
loading={false}
|
||||
error={null}
|
||||
searchPlaceholder={translate(
|
||||
searchPlaceholder={t(
|
||||
'auto.components.github.PRFilterSections.filterOrTypeLogin',
|
||||
'Filter or type a login...'
|
||||
)}
|
||||
emptyText={translate('auto.components.github.PRFilterSections.458ea3602b', 'No authors')}
|
||||
emptyText={t('auto.components.github.PRFilterSections.458ea3602b', 'No authors')}
|
||||
allowCustomValue
|
||||
renderOption={(opt) => <UserOptionRow option={opt} />}
|
||||
onSelect={(value) => onSelect({ author: value })}
|
||||
@@ -331,11 +336,11 @@ export function SectionDetail({
|
||||
activeValue={parsed.assignee}
|
||||
loading={usersLoading}
|
||||
error={usersError}
|
||||
searchPlaceholder={translate(
|
||||
searchPlaceholder={t(
|
||||
'auto.components.github.PRFilterSections.filterOrTypeLogin',
|
||||
'Filter or type a login...'
|
||||
)}
|
||||
emptyText={translate('auto.components.github.PRFilterSections.a00830d3f7', 'No users')}
|
||||
emptyText={t('auto.components.github.PRFilterSections.a00830d3f7', 'No users')}
|
||||
allowCustomValue
|
||||
renderOption={(opt) => <UserOptionRow option={opt} />}
|
||||
onSelect={(value) => onSelect({ assignee: value })}
|
||||
@@ -347,11 +352,11 @@ export function SectionDetail({
|
||||
selected={parsed.labels}
|
||||
loading={labelsLoading}
|
||||
error={labelsError}
|
||||
searchPlaceholder={translate(
|
||||
searchPlaceholder={t(
|
||||
'auto.components.github.PRFilterSections.filterLabels',
|
||||
'Filter labels...'
|
||||
)}
|
||||
emptyText={translate('auto.components.github.PRFilterSections.de26e2eb06', 'No labels')}
|
||||
emptyText={t('auto.components.github.PRFilterSections.de26e2eb06', 'No labels')}
|
||||
onChange={(next) => onSelect({ labels: next })}
|
||||
/>
|
||||
) : null}
|
||||
@@ -368,7 +373,7 @@ export function SectionDetail({
|
||||
: 'text-muted-foreground hover:bg-muted/50'
|
||||
)}
|
||||
>
|
||||
{translate('auto.components.github.PRFilterSections.94b42b0edf', 'Review requested')}
|
||||
{t('auto.components.github.PRFilterSections.94b42b0edf', 'Review requested')}
|
||||
</button>
|
||||
<button
|
||||
type="button"
|
||||
@@ -380,7 +385,7 @@ export function SectionDetail({
|
||||
: 'text-muted-foreground hover:bg-muted/50'
|
||||
)}
|
||||
>
|
||||
{translate('auto.components.github.PRFilterSections.0103e1cb18', 'Reviewed by')}
|
||||
{t('auto.components.github.PRFilterSections.0103e1cb18', 'Reviewed by')}
|
||||
</button>
|
||||
</div>
|
||||
<SingleSelectList
|
||||
@@ -388,11 +393,11 @@ export function SectionDetail({
|
||||
activeValue={reviewerMode === 'requested' ? parsed.reviewRequested : parsed.reviewedBy}
|
||||
loading={usersLoading}
|
||||
error={usersError}
|
||||
searchPlaceholder={translate(
|
||||
searchPlaceholder={t(
|
||||
'auto.components.github.PRFilterSections.filterOrTypeLogin',
|
||||
'Filter or type a login...'
|
||||
)}
|
||||
emptyText={translate('auto.components.github.PRFilterSections.a00830d3f7', 'No users')}
|
||||
emptyText={t('auto.components.github.PRFilterSections.a00830d3f7', 'No users')}
|
||||
allowCustomValue
|
||||
renderOption={(opt) => <UserOptionRow option={opt} />}
|
||||
onSelect={(login) =>
|
||||
|
||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user