From efeee83b2c584bba427278bfffcc15dedf29bc91 Mon Sep 17 00:00:00 2001 From: OrcaWin Date: Sat, 7 Nov 2026 21:20:59 -0800 Subject: [PATCH] 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> --- config/render-path-translate-baseline.txt | 102 +------------- .../activity/ActivityTitlebarControls.tsx | 21 ++- .../activity-event-builder-context.ts | 6 +- .../activity/activity-event-builder.ts | 7 +- .../activity-scope-filter-controls.tsx | 15 +- .../activity/activity-standalone-worktree.ts | 19 ++- .../activity/activity-thread-context-menu.tsx | 52 +++---- .../activity/activity-thread-controls.tsx | 11 +- .../activity/activity-thread-detail-pane.tsx | 18 ++- .../activity/activity-thread-grouping.ts | 16 +-- .../activity-thread-hover-card-summary.tsx | 18 +-- .../activity/activity-thread-hover-card.tsx | 10 +- .../activity/activity-thread-list-pane.tsx | 5 +- .../activity-thread-list-resize-handle.tsx | 10 +- .../activity/activity-thread-list-toolbar.tsx | 36 ++--- .../activity/activity-thread-options-menu.tsx | 56 +++----- .../activity/activity-thread-presentation.ts | 50 +++---- .../activity/activity-thread-row.tsx | 17 +-- .../activity/activity-thread-virtual-row.tsx | 5 +- .../src/components/agent-child-row-text.ts | 8 +- .../AgentSessionContinuationDialog.tsx | 51 +++---- .../AgentSessionContinuationMenuItem.tsx | 8 +- .../components/artifacts/ArtifactActions.tsx | 15 +- .../artifacts/ArtifactCollection.tsx | 7 +- .../artifacts/ArtifactDetailDrawer.tsx | 5 +- .../artifacts/ArtifactDetailHeader.tsx | 15 +- .../components/artifacts/ArtifactListRow.tsx | 22 +-- .../artifacts/ArtifactListSearchField.tsx | 12 +- .../artifacts/ArtifactListTableHeader.tsx | 19 ++- .../artifacts/ArtifactListToolbar.tsx | 7 +- .../components/artifacts/ArtifactPreview.tsx | 6 +- .../artifacts/ArtifactPublishButton.tsx | 41 +++--- .../artifacts/ArtifactPublishedLinkPanel.tsx | 21 ++- .../components/artifacts/ArtifactsPage.tsx | 13 +- .../artifacts/ArtifactsPageSkeleton.tsx | 8 +- .../artifacts/ArtifactsPageStates.tsx | 48 +++---- .../artifacts/artifact-display-labels.ts | 52 ++++--- .../artifacts/artifact-list-search.ts | 10 +- .../cmd-j/PaletteCreateWorktreeRow.tsx | 37 +++-- .../components/cmd-j/PaletteFilterChips.tsx | 19 +-- .../cmd-j/PaletteFilterFieldOptions.tsx | 21 +-- .../components/cmd-j/PaletteFilterMenu.tsx | 17 +-- .../components/cmd-j/palette-live-status.tsx | 8 +- .../ContextualTourControl.tsx | 7 +- .../ContextualTourOverlaySurface.tsx | 22 ++- .../ContextualTourProgressDots.tsx | 7 +- .../crash-report/CrashReportDialogSurface.tsx | 80 +++++------ .../AgentDashboardFilterChips.tsx | 10 +- .../AgentDashboardToolbar.tsx | 38 ++--- .../dashboard-popout/AgentKanbanBoard.tsx | 25 ++-- .../dashboard-popout/AgentKanbanCard.tsx | 31 +++-- .../dashboard-popout/AgentTerminalDialog.tsx | 9 +- .../dashboard-popout/AgentTerminalPreview.tsx | 4 +- .../dashboard-popout/DashboardHostBadge.tsx | 25 ++-- .../agent-dashboard-filter-options.ts | 13 +- .../terminal-preview-unavailable-message.ts | 16 ++- .../dashboard/AgentDashboardDrawer.tsx | 5 +- .../AgentDashboardExperimentalSetting.tsx | 34 ++--- .../dashboard/AgentDashboardSettingsMenu.tsx | 23 +-- .../DashboardAgentChildDisclosure.tsx | 7 +- .../dashboard/DashboardAgentRow.tsx | 27 ++-- .../dashboard/DashboardAgentRowMessage.tsx | 15 +- .../DashboardAgentRowTrailingControls.tsx | 33 ++--- .../dashboard/build-dashboard-snapshot.ts | 6 +- .../dashboard/dashboard-card-context.ts | 11 +- .../dictation/DictationController.tsx | 10 +- .../dictation/DictationIndicator.tsx | 20 ++- .../diff-comments/DiffCommentCard.tsx | 32 ++--- .../diff-comments/DiffCommentDraftCard.tsx | 38 ++--- .../diff-comments/DiffCommentPopover.tsx | 10 +- .../diff-comment-line-meta-label.ts | 8 +- .../components/emulator-pane/EmulatorPane.tsx | 10 +- .../MobileEmulatorAgentSetupGuide.tsx | 24 ++-- .../MobileEmulatorAgentSetupGuideSteps.tsx | 9 +- .../MobileEmulatorTabIntroCallout.tsx | 22 +-- .../emulator-pane/emulator-device-picker.tsx | 23 +-- .../emulator-pane/emulator-opening-message.ts | 14 +- .../emulator-pane/emulator-pane-toolbar.tsx | 33 ++--- .../emulator-screen-aria-label.ts | 8 +- .../emulator-screen-stream-content.tsx | 13 +- .../emulator-pane/emulator-screen-surface.tsx | 4 +- .../use-emulator-pane-session.ts | 4 +- .../file-actions/OpenInContextSubmenu.tsx | 10 +- .../FloatingTerminalEmptyState.tsx | 16 +-- .../FloatingTerminalIconContextMenu.tsx | 11 +- .../FloatingTerminalOrchestrationCard.tsx | 24 ++-- .../FloatingTerminalOrchestrationDialog.tsx | 22 ++- .../FloatingTerminalPanel.tsx | 4 +- .../FloatingTerminalPanelSurface.tsx | 131 +++++++++--------- .../FloatingTerminalToggleButton.tsx | 11 +- .../FloatingTerminalWindowControls.tsx | 19 +-- .../components/github/CommentCodeContext.tsx | 32 ++--- .../components/github/CommentReactions.tsx | 49 ++++--- .../github/GitHubMarkdownComposer.tsx | 5 +- .../github/IssueSourceIndicator.tsx | 10 +- .../components/github/IssueSourceSelector.tsx | 15 +- .../components/github/PRAssigneesPanel.tsx | 12 +- .../components/github/PRFilterDropdowns.tsx | 31 +++-- .../src/components/github/PRFilterPickers.tsx | 74 +++++----- .../components/github/PRFilterSections.tsx | 101 +++++++------- .../components/github/PRViewedCheckbox.tsx | 13 +- .../github-markdown-composer-preview-pane.tsx | 8 +- .../github-markdown-composer-tabbar.tsx | 7 +- .../github/github-markdown-image-input.tsx | 12 +- .../github/github-rate-limit-display.tsx | 22 +-- .../github/use-github-image-paste.ts | 7 +- .../github/use-github-image-uploads.ts | 7 +- .../github/work-item-state-badge.tsx | 4 +- .../gitlab-conversation-tab.tsx | 5 +- .../gitlab-description-tab.tsx | 64 ++++----- .../gitlab-item-dialog/gitlab-files-tab.tsx | 26 ++-- .../gitlab-item-dialog-view.tsx | 38 +++-- .../gitlab-pipeline-tab.tsx | 12 +- .../gitlab/gitlab-rate-limit-display.tsx | 41 +++--- .../bitbucket-credentials-dialog.tsx | 52 ++++--- .../bitbucket-integration-card.tsx | 60 ++++---- .../cli-source-control-integration-cards.tsx | 83 +++++------ ...token-source-control-integration-cards.tsx | 88 ++++++------ .../token-source-control-status.ts | 30 ++-- .../task-trackers/jira-integration-card.tsx | 51 +++---- .../task-trackers/linear-integration-card.tsx | 41 +++--- .../link-actions/LinkActionPopover.tsx | 19 +-- .../machine-identity/MachineNameField.tsx | 14 +- .../MacosUpdateRequirementDialog.tsx | 40 +++--- .../UpdateAvailableCardContent.tsx | 29 ++-- .../update-card/UpdateCardStateContent.tsx | 17 ++- .../update-card/UpdateCheckFeedback.tsx | 5 +- .../update-card/UpdateDownloadCardContent.tsx | 34 +++-- .../provider-account-scope.ts | 22 +-- .../parent-pr-checks-hosted-review-cache.ts | 7 +- 130 files changed, 1475 insertions(+), 1634 deletions(-) diff --git a/config/render-path-translate-baseline.txt b/config/render-path-translate-baseline.txt index 09ce45a2b85..a8d0c062203 100644 --- a/config/render-path-translate-baseline.txt +++ b/config/render-path-translate-baseline.txt @@ -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 diff --git a/src/renderer/src/components/activity/ActivityTitlebarControls.tsx b/src/renderer/src/components/activity/ActivityTitlebarControls.tsx index 502f0cd0328..c7d9abd95ff 100644 --- a/src/renderer/src/components/activity/ActivityTitlebarControls.tsx +++ b/src/renderer/src/components/activity/ActivityTitlebarControls.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 { - {translate( - 'auto.components.activity.ActivityTitlebarControls.dc708f3eff', - 'Close agents' - )} + {t('auto.components.activity.ActivityTitlebarControls.dc708f3eff', 'Close agents')} - {translate('auto.components.activity.ActivityTitlebarControls.d6a8de3934', 'agents')} + {t('auto.components.activity.ActivityTitlebarControls.d6a8de3934', 'agents')} - {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' + })} diff --git a/src/renderer/src/components/activity/activity-event-builder-context.ts b/src/renderer/src/components/activity/activity-event-builder-context.ts index c63bf5ad338..5fb1e5cd5de 100644 --- a/src/renderer/src/components/activity/activity-event-builder-context.ts +++ b/src/renderer/src/components/activity/activity-event-builder-context.ts @@ -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 + ownerCache: Map, + 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) diff --git a/src/renderer/src/components/activity/activity-event-builder.ts b/src/renderer/src/components/activity/activity-event-builder.ts index 77f27801715..b1375164901 100644 --- a/src/renderer/src/components/activity/activity-event-builder.ts +++ b/src/renderer/src/components/activity/activity-event-builder.ts @@ -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 @@ -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. diff --git a/src/renderer/src/components/activity/activity-scope-filter-controls.tsx b/src/renderer/src/components/activity/activity-scope-filter-controls.tsx index 95dc58480fc..3431c3dcfbc 100644 --- a/src/renderer/src/components/activity/activity-scope-filter-controls.tsx +++ b/src/renderer/src/components/activity/activity-scope-filter-controls.tsx @@ -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 ? ( - {translate( + {t( 'auto.components.activity.ActivityScopeFilterControls.resetScope', 'Show all hosts and projects' )} diff --git a/src/renderer/src/components/activity/activity-standalone-worktree.ts b/src/renderer/src/components/activity/activity-standalone-worktree.ts index 1bde9133b93..62ccab46712 100644 --- a/src/renderer/src/components/activity/activity-standalone-worktree.ts +++ b/src/renderer/src/components/activity/activity-standalone-worktree.ts @@ -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 diff --git a/src/renderer/src/components/activity/activity-thread-context-menu.tsx b/src/renderer/src/components/activity/activity-thread-context-menu.tsx index a180b211aae..67ed72093fd 100644 --- a/src/renderer/src/components/activity/activity-thread-context-menu.tsx +++ b/src/renderer/src/components/activity/activity-thread-context-menu.tsx @@ -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([thread]) @@ -112,7 +115,7 @@ export function ActivityThreadContextMenu({ {/* Why no focus restore: refocusing the row would reopen its hover preview and pin it open. */} event.preventDefault()}> - {translate('auto.components.activity.ActivityThreadContextMenu.agentSection', 'Agent')} + {t('auto.components.activity.ActivityThreadContextMenu.agentSection', 'Agent')} {targets.length > 1 ? ( void onMarkUnread: (thread: AgentPaneThread) => void }): React.JSX.Element { + const t = useTranslate() return ( <> onOpen(thread)}> - {translate('auto.components.activity.ActivityThreadContextMenu.open', 'Open')} + {t('auto.components.activity.ActivityThreadContextMenu.open', 'Open')} {canJump ? ( onJump(thread)}> - {translate( - 'auto.components.activity.ActivityThreadContextMenu.goToWorkspace', - 'Go to Workspace' - )} + {t('auto.components.activity.ActivityThreadContextMenu.goToWorkspace', 'Go to Workspace')} ) : null} - {getActivityThreadCopyTargets(thread, canJump).map((target) => ( + {getActivityThreadCopyTargets(thread, canJump, t).map((target) => ( void window.api.ui.writeClipboardText(formatCopyTarget(thread, target))} @@ -186,21 +187,15 @@ function ActivityThreadSingleMenuItems({ > {thread.unread ? : } {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')} {isClearableActivityThread(thread) ? ( <> clearActivityThread(thread)}> - {translate( - 'auto.components.activity.ActivityThreadContextMenu.clear', - 'Clear from List' - )} + {t('auto.components.activity.ActivityThreadContextMenu.clear', 'Clear from List')} ) : 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 ? ( onMarkManyRead(unread)}> - {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({ onMarkManyUnread(read)}> {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')} )} @@ -257,15 +250,12 @@ function ActivityThreadBulkMenuItems({ > {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')} ) diff --git a/src/renderer/src/components/activity/activity-thread-controls.tsx b/src/renderer/src/components/activity/activity-thread-controls.tsx index 845574dc99c..d9520854b5f 100644 --- a/src/renderer/src/components/activity/activity-thread-controls.tsx +++ b/src/renderer/src/components/activity/activity-thread-controls.tsx @@ -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 ( @@ -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)} @@ -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 ( diff --git a/src/renderer/src/components/activity/activity-thread-detail-pane.tsx b/src/renderer/src/components/activity/activity-thread-detail-pane.tsx index 119b94f2096..8b4b8b28c67 100644 --- a/src/renderer/src/components/activity/activity-thread-detail-pane.tsx +++ b/src/renderer/src/components/activity/activity-thread-detail-pane.tsx @@ -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 (
{selectedThread ? ( @@ -67,11 +68,11 @@ export function ActivityThreadDetailPane({
{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({
- {translate( + {t( 'auto.components.activity.ActivityPrototypePage.8de7c5beaa', 'Terminal unavailable' )} @@ -121,7 +122,7 @@ export function ActivityThreadDetailPane({
- {translate( + {t( 'auto.components.activity.ActivityPrototypePage.1b633f5c1e', 'Connecting terminal...' )} @@ -139,15 +140,12 @@ export function ActivityThreadDetailPane({ {visibleThreadCount === 0 ? ( <> - {translate( - 'auto.components.activity.ActivityPrototypePage.e3db9892f6', - 'No activity yet.' - )} + {t('auto.components.activity.ActivityPrototypePage.e3db9892f6', 'No activity yet.')} ) : ( <> - {translate( + {t( 'auto.components.activity.ActivityPrototypePage.cf780197a1', 'Select an agent to view its activity' )} diff --git a/src/renderer/src/components/activity/activity-thread-grouping.ts b/src/renderer/src/components/activity/activity-thread-grouping.ts index a8c3125dc99..997a0404437 100644 --- a/src/renderer/src/components/activity/activity-thread-grouping.ts +++ b/src/renderer/src/components/activity/activity-thread-grouping.ts @@ -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() 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] }) diff --git a/src/renderer/src/components/activity/activity-thread-hover-card-summary.tsx b/src/renderer/src/components/activity/activity-thread-hover-card-summary.tsx index 70a4855616e..4c3fd034440 100644 --- a/src/renderer/src/components/activity/activity-thread-hover-card-summary.tsx +++ b/src/renderer/src/components/activity/activity-thread-hover-card-summary.tsx @@ -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)} • @@ -168,14 +167,11 @@ export function ActivityThreadHoverCardSummary({ } - label={translate( - 'auto.components.activity.ActivityThreadHoverCard.workspace', - 'Workspace' - )} + label={t('auto.components.activity.ActivityThreadHoverCard.workspace', 'Workspace')} actions={ canJump ? ( 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({ } - label={translate('auto.components.sidebar.WorktreeCardMeta.93cbea12c2', 'Notes')} + label={t('auto.components.sidebar.WorktreeCardMeta.93cbea12c2', 'Notes')} actions={ diff --git a/src/renderer/src/components/activity/activity-thread-list-pane.tsx b/src/renderer/src/components/activity/activity-thread-list-pane.tsx index 00609f6457d..473f72e8d3a 100644 --- a/src/renderer/src/components/activity/activity-thread-list-pane.tsx +++ b/src/renderer/src/components/activity/activity-thread-list-pane.tsx @@ -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 }): React.JSX.Element { + const t = useTranslate() const [internalCollapsedGroupKeys, setInternalCollapsedGroupKeys] = useState>( () => new Set() ) @@ -386,7 +387,7 @@ export function ActivityThreadListPane({
{visibleThreadCount === 0 ? (
- {translate( + {t( 'auto.components.activity.ActivityPrototypePage.7cd632006b', 'No agent activity matches these filters.' )} diff --git a/src/renderer/src/components/activity/activity-thread-list-resize-handle.tsx b/src/renderer/src/components/activity/activity-thread-list-resize-handle.tsx index a97a7ce223b..810a2b47853 100644 --- a/src/renderer/src/components/activity/activity-thread-list-resize-handle.tsx +++ b/src/renderer/src/components/activity/activity-thread-list-resize-handle.tsx @@ -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 }): React.JSX.Element { + const t = useTranslate() return (
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({ - {translate('auto.components.activity.ActivityPrototypePage.none', 'None')} + {t('auto.components.activity.ActivityPrototypePage.none', 'None')} - {translate('auto.components.activity.ActivityPrototypePage.4a3986b200', 'Status')} + {t('auto.components.activity.ActivityPrototypePage.4a3986b200', 'Status')} - {translate( - 'auto.components.activity.ActivityPrototypePage.8c3b621ddf', - 'Project' - )} + {t('auto.components.activity.ActivityPrototypePage.8c3b621ddf', 'Project')} - {translate( - 'auto.components.activity.ActivityPrototypePage.b29191b3e0', - 'Worktree' - )} + {t('auto.components.activity.ActivityPrototypePage.b29191b3e0', 'Worktree')} - {translate('auto.components.activity.ActivityPrototypePage.f6396e1f85', 'Agent')} + {t('auto.components.activity.ActivityPrototypePage.f6396e1f85', 'Agent')} @@ -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({ - {translate( + {t( 'auto.components.activity.ActivityPrototypePage.d1a88df9a8', 'Show unread threads only' )} @@ -203,10 +198,7 @@ export function ActivityThreadListToolbar({ > - {translate( - 'auto.components.activity.ActivityPrototypePage.023ff75afe', - 'Mark all read' - )} + {t('auto.components.activity.ActivityPrototypePage.023ff75afe', 'Mark all read')} ) : null} @@ -221,7 +213,7 @@ export function ActivityThreadListToolbar({ > - {translate( + {t( 'auto.components.activity.ActivityPrototypePage.clearCompleted', 'Clear completed' )} diff --git a/src/renderer/src/components/activity/activity-thread-options-menu.tsx b/src/renderer/src/components/activity/activity-thread-options-menu.tsx index f00c29f941e..6c1d9bc7f81 100644 --- a/src/renderer/src/components/activity/activity-thread-options-menu.tsx +++ b/src/renderer/src/components/activity/activity-thread-options-menu.tsx @@ -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 ( @@ -115,7 +114,7 @@ export function ActivityThreadOptionsMenu({ {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({ }} > - {translate('auto.components.activity.ActivityPrototypePage.filtersSection', 'Filters')} + {t('auto.components.activity.ActivityPrototypePage.filtersSection', 'Filters')} {onUnreadOnlyChange ? ( 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')} ) : 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({ - {translate('auto.components.activity.ActivityPrototypePage.viewSection', 'View')} + {t('auto.components.activity.ActivityPrototypePage.viewSection', 'View')} {groupBy && onGroupByChange ? ( - {translate( - 'auto.components.activity.ActivityPrototypePage.770d458144', - 'Group by' - )} + {t('auto.components.activity.ActivityPrototypePage.770d458144', 'Group by')} - {getActivityGroupByLabel(groupBy)} + {getActivityGroupByLabel(groupBy, t)} @@ -199,7 +192,7 @@ export function ActivityThreadOptionsMenu({ value={value} onSelect={(event) => event.preventDefault()} > - {getActivityGroupByLabel(value)} + {getActivityGroupByLabel(value, t)} ))} @@ -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')} {onShowSearchChange ? ( - {translate('auto.components.activity.ActivityPrototypePage.showSearch', 'Show search')} + {t('auto.components.activity.ActivityPrototypePage.showSearch', 'Show search')} ) : null} {onMarkAllThreadsRead || onClearCompleted ? ( @@ -232,10 +225,7 @@ export function ActivityThreadOptionsMenu({ - {translate( - 'auto.components.activity.ActivityPrototypePage.023ff75afe', - 'Mark all read' - )} + {t('auto.components.activity.ActivityPrototypePage.023ff75afe', 'Mark all read')} ) : null} @@ -243,7 +233,7 @@ export function ActivityThreadOptionsMenu({ - {translate( + {t( 'auto.components.activity.ActivityPrototypePage.clearCompleted', 'Clear completed' )} diff --git a/src/renderer/src/components/activity/activity-thread-presentation.ts b/src/renderer/src/components/activity/activity-thread-presentation.ts index c983d2fb028..aa37ffe17ee 100644 --- a/src/renderer/src/components/activity/activity-thread-presentation.ts +++ b/src/renderer/src/components/activity/activity-thread-presentation.ts @@ -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 diff --git a/src/renderer/src/components/activity/activity-thread-row.tsx b/src/renderer/src/components/activity/activity-thread-row.tsx index e1537e68935..ce64868966e 100644 --- a/src/renderer/src/components/activity/activity-thread-row.tsx +++ b/src/renderer/src/components/activity/activity-thread-row.tsx @@ -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({ {canJump && showJumpAction ? ( 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({ - {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({ - {translate( + {t( 'auto.components.activity.ActivityPrototypePage.59b131fbd9', 'Mark thread unread' )} diff --git a/src/renderer/src/components/activity/activity-thread-virtual-row.tsx b/src/renderer/src/components/activity/activity-thread-virtual-row.tsx index 3f1e8573db0..d49a0e29836 100644 --- a/src/renderer/src/components/activity/activity-thread-virtual-row.tsx +++ b/src/renderer/src/components/activity/activity-thread-virtual-row.tsx @@ -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 (
(null) const settings = useAppStore((state) => state.settings) @@ -241,13 +242,10 @@ export function AgentSessionContinuationDialog({ - {translate( - 'components.agentSessionContinuation.dialogTitle', - 'Hand Off to Another Agent' - )} + {t('components.agentSessionContinuation.dialogTitle', 'Hand Off to Another Agent')} - {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({
{sourceName || - translate('components.agentSessionContinuation.untitledSession', 'Current session')} + t('components.agentSessionContinuation.untitledSession', 'Current session')}
{sourceAgentLabel - ? translate( + ? t( 'components.agentSessionContinuation.originalAgent', 'Original Agent: {{agent}}', { agent: sourceAgentLabel } @@ -273,7 +271,7 @@ export function AgentSessionContinuationDialog({
{preparing ? (

- {translate( + {t( 'components.agentSessionContinuation.preparingContext', 'Preparing saved chat context…' )}

) : detecting ? (

- {translate( + {t( 'components.agentSessionContinuation.detectingAgents', 'Detecting Agents on this workspace host…' )}

) : detectionFailed ? (

- {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 ? (

{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({

@@ -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({
{workingDirectory ? ( <> - {translate('components.agentSessionContinuation.startsIn', 'Starts in:')}{' '} + {t('components.agentSessionContinuation.startsIn', 'Starts in:')}{' '} {workingDirectory} ) : null} @@ -394,7 +389,7 @@ export function AgentSessionContinuationDialog({ diff --git a/src/renderer/src/components/agent-session-continuation/AgentSessionContinuationMenuItem.tsx b/src/renderer/src/components/agent-session-continuation/AgentSessionContinuationMenuItem.tsx index ff6a3d708f7..a4ea5c0075b 100644 --- a/src/renderer/src/components/agent-session-continuation/AgentSessionContinuationMenuItem.tsx +++ b/src/renderer/src/components/agent-session-continuation/AgentSessionContinuationMenuItem.tsx @@ -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 ( - {translate( - 'components.agentSessionContinuation.handOffToAnotherAgent', - 'Hand Off to Another Agent' - )} + {t('components.agentSessionContinuation.handOffToAnotherAgent', 'Hand Off to Another Agent')} ) } diff --git a/src/renderer/src/components/artifacts/ArtifactActions.tsx b/src/renderer/src/components/artifacts/ArtifactActions.tsx index a7d7478fbd3..6529f0325ea 100644 --- a/src/renderer/src/components/artifacts/ArtifactActions.tsx +++ b/src/renderer/src/components/artifacts/ArtifactActions.tsx @@ -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 (
@@ -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')} > - {translate('auto.components.artifacts.openInBrowser', 'Open in browser')} + {t('auto.components.artifacts.openInBrowser', 'Open in browser')} @@ -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)} > - {translate('auto.components.artifacts.ArtifactsPage.deleteArtifact', 'Delete artifact')} + {t('auto.components.artifacts.ArtifactsPage.deleteArtifact', 'Delete artifact')} diff --git a/src/renderer/src/components/artifacts/ArtifactCollection.tsx b/src/renderer/src/components/artifacts/ArtifactCollection.tsx index 2a9b0fecaee..657d4f9f0bc 100644 --- a/src/renderer/src/components/artifacts/ArtifactCollection.tsx +++ b/src/renderer/src/components/artifacts/ArtifactCollection.tsx @@ -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({ /> ) : (

- {translate('auto.components.artifacts.ArtifactCollection.noMatches', 'No matches')} + {t('auto.components.artifacts.ArtifactCollection.noMatches', 'No matches')}

)} {hasMore ? ( @@ -81,7 +82,7 @@ export function ArtifactCollection({ onClick={loadMore} > {loadingMore ? : null} - {translate('auto.components.artifacts.ArtifactCollection.loadMore', 'Load more')} + {t('auto.components.artifacts.ArtifactCollection.loadMore', 'Load more')}
) : null} diff --git a/src/renderer/src/components/artifacts/ArtifactDetailDrawer.tsx b/src/renderer/src/components/artifacts/ArtifactDetailDrawer.tsx index 442a98f2485..6588b20d1a2 100644 --- a/src/renderer/src/components/artifacts/ArtifactDetailDrawer.tsx +++ b/src/renderer/src/components/artifacts/ArtifactDetailDrawer.tsx @@ -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 ( !open && onClose()}> - {translate( + {t( 'auto.components.artifacts.ArtifactDetailDrawer.description', 'Preview and manage this shared artifact.' )} diff --git a/src/renderer/src/components/artifacts/ArtifactDetailHeader.tsx b/src/renderer/src/components/artifacts/ArtifactDetailHeader.tsx index 062ab530588..609b2bdbcb5 100644 --- a/src/renderer/src/components/artifacts/ArtifactDetailHeader.tsx +++ b/src/renderer/src/components/artifacts/ArtifactDetailHeader.tsx @@ -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.
@@ -36,14 +38,14 @@ export function ArtifactDetailHeader({ - {translate( + {t( 'auto.components.artifacts.ArtifactDetailHeader.publicLink', 'Anyone with this link can view it' )} - {translate( + {t( 'auto.components.artifacts.ArtifactDetailHeader.publicLink', 'Anyone with this link can view it' )} @@ -51,8 +53,9 @@ export function ArtifactDetailHeader({

{item.shareUrl}

- {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)}

@@ -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')} > diff --git a/src/renderer/src/components/artifacts/ArtifactListRow.tsx b/src/renderer/src/components/artifacts/ArtifactListRow.tsx index c27cf199e78..6058c98af48 100644 --- a/src/renderer/src/components/artifacts/ArtifactListRow.tsx +++ b/src/renderer/src/components/artifacts/ArtifactListRow.tsx @@ -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()} > diff --git a/src/renderer/src/components/artifacts/ArtifactListSearchField.tsx b/src/renderer/src/components/artifacts/ArtifactListSearchField.tsx index 1a5f5021147..f33d9200119 100644 --- a/src/renderer/src/components/artifacts/ArtifactListSearchField.tsx +++ b/src/renderer/src/components/artifacts/ArtifactListSearchField.tsx @@ -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(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() diff --git a/src/renderer/src/components/artifacts/ArtifactListTableHeader.tsx b/src/renderer/src/components/artifacts/ArtifactListTableHeader.tsx index 9cae1d0c66b..4033ff8921b 100644 --- a/src/renderer/src/components/artifacts/ArtifactListTableHeader.tsx +++ b/src/renderer/src/components/artifacts/ArtifactListTableHeader.tsx @@ -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 (
- {translate('auto.components.artifacts.ArtifactListTableHeader.name', 'Name')} - {translate('auto.components.artifacts.ArtifactListTableHeader.type', 'Type')} - {translate('auto.components.artifacts.ArtifactListTableHeader.size', 'Size')} - - {translate('auto.components.artifacts.ArtifactListTableHeader.updated', 'Updated')} - - - {translate('auto.components.artifacts.ArtifactListTableHeader.expires', 'Expires')} - + {t('auto.components.artifacts.ArtifactListTableHeader.name', 'Name')} + {t('auto.components.artifacts.ArtifactListTableHeader.type', 'Type')} + {t('auto.components.artifacts.ArtifactListTableHeader.size', 'Size')} + {t('auto.components.artifacts.ArtifactListTableHeader.updated', 'Updated')} + {t('auto.components.artifacts.ArtifactListTableHeader.expires', 'Expires')} - {translate('auto.components.artifacts.ArtifactListTableHeader.actions', 'Actions')} + {t('auto.components.artifacts.ArtifactListTableHeader.actions', 'Actions')}
) diff --git a/src/renderer/src/components/artifacts/ArtifactListToolbar.tsx b/src/renderer/src/components/artifacts/ArtifactListToolbar.tsx index 2ed9a1672da..2c3272773f8 100644 --- a/src/renderer/src/components/artifacts/ArtifactListToolbar.tsx +++ b/src/renderer/src/components/artifacts/ArtifactListToolbar.tsx @@ -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 (
- {translate('auto.components.artifacts.ArtifactsPage.refresh', 'Refresh')} + {t('auto.components.artifacts.ArtifactsPage.refresh', 'Refresh')}
diff --git a/src/renderer/src/components/artifacts/ArtifactPreview.tsx b/src/renderer/src/components/artifacts/ArtifactPreview.tsx index a2ee0605f7f..d80e49691c6 100644 --- a/src/renderer/src/components/artifacts/ArtifactPreview.tsx +++ b/src/renderer/src/components/artifacts/ArtifactPreview.tsx @@ -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(null) const [state, setState] = useState('loading') @@ -151,10 +153,10 @@ export function ArtifactPreview({ shareUrl }: { shareUrl: string }): React.JSX.E

- {translate('auto.components.artifacts.previewUnavailable', 'Preview unavailable')} + {t('auto.components.artifacts.previewUnavailable', 'Preview unavailable')}

- {translate( + {t( 'auto.components.artifacts.previewUnavailableDescription', 'Open this artifact in your browser to view it.' )} diff --git a/src/renderer/src/components/artifacts/ArtifactPublishButton.tsx b/src/renderer/src/components/artifacts/ArtifactPublishButton.tsx index 094344fdb16..d1e81c98111 100644 --- a/src/renderer/src/components/artifacts/ArtifactPublishButton.tsx +++ b/src/renderer/src/components/artifacts/ArtifactPublishButton.tsx @@ -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 ( !busy && setOpen(nextOpen)}> {anchorRef ? ( @@ -175,18 +173,15 @@ export function ArtifactPublishButton({ >

- {translate( - 'auto.components.artifacts.ArtifactPublishButton.confirmTitle', - 'Share as artifact' - )} + {t('auto.components.artifacts.ArtifactPublishButton.confirmTitle', 'Share as artifact')}

{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({

- {translate( + {t( 'auto.components.artifacts.ArtifactPublishButton.accountTitle', 'Orca account' )}

- {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')}

) : null} @@ -230,13 +225,13 @@ export function ArtifactPublishButton({

- {translate( + {t( 'auto.components.artifacts.ArtifactPublishButton.publishingOffTitle', 'Artifact sharing is off' )}

- {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 ? (

- {translate( + {t( 'auto.components.artifacts.ArtifactPublishButton.checkingLink', 'Checking for an existing link…' )} @@ -269,7 +264,7 @@ export function ArtifactPublishButton({ ) : currentLookup?.status === 'error' ? (

- {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')}

) : publishedLink ? ( @@ -302,8 +297,8 @@ export function ArtifactPublishButton({ > {publishing ? : } {publishing - ? translate('auto.components.artifacts.ArtifactPublishButton.sharing', 'Sharing…') - : translate( + ? t('auto.components.artifacts.ArtifactPublishButton.sharing', 'Sharing…') + : t( 'auto.components.artifacts.ArtifactPublishButton.sharePublicLink', 'Generate link' )} diff --git a/src/renderer/src/components/artifacts/ArtifactPublishedLinkPanel.tsx b/src/renderer/src/components/artifacts/ArtifactPublishedLinkPanel.tsx index 4e5aa292be0..41b9bd77269 100644 --- a/src/renderer/src/components/artifacts/ArtifactPublishedLinkPanel.tsx +++ b/src/renderer/src/components/artifacts/ArtifactPublishedLinkPanel.tsx @@ -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(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 (
@@ -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({ - {translate( - 'auto.components.artifacts.ArtifactPublishedLinkPanel.openLink', - 'Open link' - )} + {t('auto.components.artifacts.ArtifactPublishedLinkPanel.openLink', 'Open link')}
@@ -117,11 +115,8 @@ export function ArtifactPublishedLinkPanel({ > {publishing ? : } {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' )} diff --git a/src/renderer/src/components/artifacts/ArtifactsPage.tsx b/src/renderer/src/components/artifacts/ArtifactsPage.tsx index e65a2be3398..d33bffac961 100644 --- a/src/renderer/src/components/artifacts/ArtifactsPage.tsx +++ b/src/renderer/src/components/artifacts/ArtifactsPage.tsx @@ -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 { } >

- {translate('auto.components.artifacts.ArtifactsPage.title', 'Artifacts')} + {t('auto.components.artifacts.ArtifactsPage.title', 'Artifacts')}

diff --git a/src/renderer/src/components/artifacts/ArtifactsPageSkeleton.tsx b/src/renderer/src/components/artifacts/ArtifactsPageSkeleton.tsx index 1d79ecaf4a6..919799b700d 100644 --- a/src/renderer/src/components/artifacts/ArtifactsPageSkeleton.tsx +++ b/src/renderer/src/components/artifacts/ArtifactsPageSkeleton.tsx @@ -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 (
diff --git a/src/renderer/src/components/artifacts/ArtifactsPageStates.tsx b/src/renderer/src/components/artifacts/ArtifactsPageStates.tsx index 39d3885010a..ae3a421be4a 100644 --- a/src/renderer/src/components/artifacts/ArtifactsPageStates.tsx +++ b/src/renderer/src/components/artifacts/ArtifactsPageStates.tsx @@ -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 (

{error}

) @@ -31,28 +32,26 @@ export function ArtifactsPageAuthState({ configured: boolean onOpenAccountSettings: () => void }): React.JSX.Element { + const t = useTranslate() return (

{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' )}

{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({ <> {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')} ) : (

- {translate( + {t( 'auto.components.artifacts.ArtifactsPage.unconfiguredCopy', 'Orca account sign-in is not configured on this machine yet.' )}

) : null}
diff --git a/src/renderer/src/components/artifacts/artifact-display-labels.ts b/src/renderer/src/components/artifacts/artifact-display-labels.ts index eb66b873a2e..c7ff3f28d4e 100644 --- a/src/renderer/src/components/artifacts/artifact-display-labels.ts +++ b/src/renderer/src/components/artifacts/artifact-display-labels.ts @@ -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 } diff --git a/src/renderer/src/components/artifacts/artifact-list-search.ts b/src/renderer/src/components/artifacts/artifact-list-search.ts index 227c337e251..6396afda883 100644 --- a/src/renderer/src/components/artifacts/artifact-list-search.ts +++ b/src/renderer/src/components/artifacts/artifact-list-search.ts @@ -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)) } diff --git a/src/renderer/src/components/cmd-j/PaletteCreateWorktreeRow.tsx b/src/renderer/src/components/cmd-j/PaletteCreateWorktreeRow.tsx index 415bfa7f55b..08a5a9d491e 100644 --- a/src/renderer/src/components/cmd-j/PaletteCreateWorktreeRow.tsx +++ b/src/renderer/src/components/cmd-j/PaletteCreateWorktreeRow.tsx @@ -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({
{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…')}
@@ -140,14 +139,12 @@ export function PaletteCreateWorktreeRow({
{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 + })}
@@ -158,11 +155,9 @@ export function PaletteCreateWorktreeRow({
- {translate( - 'auto.components.WorktreeJumpPalette.95be6587d3', - 'Create worktree "{{value0}}"', - { value0: createWorktreeName } - )} + {t('auto.components.WorktreeJumpPalette.95be6587d3', 'Create worktree "{{value0}}"', { + value0: createWorktreeName + })}
diff --git a/src/renderer/src/components/cmd-j/PaletteFilterChips.tsx b/src/renderer/src/components/cmd-j/PaletteFilterChips.tsx index 61427dc2e2b..5fd2913955b 100644 --- a/src/renderer/src/components/cmd-j/PaletteFilterChips.tsx +++ b/src/renderer/src/components/cmd-j/PaletteFilterChips.tsx @@ -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(() => { const hostLabels = new Map(model.hosts.map((host) => [host.id, host.label])) const repositoryLabels = new Map( @@ -49,9 +50,7 @@ export default function PaletteFilterChips({
{/* Why: a fixed anchor the eye can find at one filter, where a bare muted line vanishes. */}