- {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.'
)}
{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. */}
-
- {translate('worktreeJumpPalette.filter.scopedTo', 'Scoped to')}
-
+
{t('worktreeJumpPalette.filter.scopedTo', 'Scoped to')}
{/* Why: horizontal scroll keeps every chip reachable without a hard +N dead-end. */}
{chips.map((chip, index) => (
@@ -64,13 +63,9 @@ export default function PaletteFilterChips({
)
diff --git a/src/renderer/src/components/cmd-j/PaletteFilterFieldOptions.tsx b/src/renderer/src/components/cmd-j/PaletteFilterFieldOptions.tsx
index 2bf6b3c2775..5fa06bbd497 100644
--- a/src/renderer/src/components/cmd-j/PaletteFilterFieldOptions.tsx
+++ b/src/renderer/src/components/cmd-j/PaletteFilterFieldOptions.tsx
@@ -3,7 +3,7 @@ import React, { useCallback, useEffect, useMemo, useRef, useState } from 'react'
import { useVirtualizer } from '@tanstack/react-virtual'
import { Check, ChevronLeft, SearchIcon } from 'lucide-react'
import { cn } from '@/lib/utils'
-import { translate } from '@/i18n/i18n'
+import { useTranslate } from '@/i18n/use-translate'
import type { PaletteFilterOption } from './palette-filter-options'
import type { PaletteFilterField } from './palette-filter'
import {
@@ -79,6 +79,7 @@ export function PaletteFilterFieldOptions({
onClearField: () => void
onSelectAllMatching: (ids: readonly string[]) => void
}): React.JSX.Element {
+ const t = useTranslate()
const selected = useMemo(() => new Set(group.selected), [group.selected])
const normalizedQuery = optionQuery.trim().toLowerCase()
const rankMode: FilterOptionRankMode = group.field === 'host' ? 'registry' : 'popularity'
@@ -189,17 +190,17 @@ export function PaletteFilterFieldOptions({
const searchPlaceholder =
group.field === 'host'
- ? translate('worktreeJumpPalette.filter.searchHosts', 'Filter hosts...')
- : translate('worktreeJumpPalette.filter.searchProjects', 'Filter projects...')
+ ? t('worktreeJumpPalette.filter.searchHosts', 'Filter hosts...')
+ : t('worktreeJumpPalette.filter.searchProjects', 'Filter projects...')
const emptyLabel =
group.field === 'host'
- ? translate('worktreeJumpPalette.filter.noHosts', 'No matching hosts')
- : translate('worktreeJumpPalette.filter.noProjects', 'No matching projects')
+ ? t('worktreeJumpPalette.filter.noHosts', 'No matching hosts')
+ : t('worktreeJumpPalette.filter.noProjects', 'No matching projects')
// Translate complete field-specific labels; lowercasing nouns breaks languages such as German.
const clearLabel =
group.field === 'host'
- ? translate('worktreeJumpPalette.filter.clearHosts', 'Clear hosts')
- : translate('worktreeJumpPalette.filter.clearProjects', 'Clear projects')
+ ? t('worktreeJumpPalette.filter.clearHosts', 'Clear hosts')
+ : t('worktreeJumpPalette.filter.clearProjects', 'Clear projects')
const canSelectAll = ranked.unselectedCount > 0
const canClear = ranked.selectedCount > 0
@@ -214,7 +215,7 @@ export function PaletteFilterFieldOptions({
className="flex h-7 items-center gap-1 rounded-md px-1.5 text-[12px] text-muted-foreground hover:bg-accent hover:text-foreground"
>
- {translate('worktreeJumpPalette.filter.back', 'Back')}
+ {t('worktreeJumpPalette.filter.back', 'Back')}
{group.heading}
@@ -242,7 +243,7 @@ export function PaletteFilterFieldOptions({
{ranked.selectedCollapsed ? (
- {translate(
+ {t(
'worktreeJumpPalette.filter.selectedCollapsed',
'{{value0}} selected — remove via chips or Clear',
{ value0: ranked.selectedCount }
@@ -305,7 +306,7 @@ export function PaletteFilterFieldOptions({
onClick={() => onSelectAllMatching(ranked.unselectedIds)}
className="rounded-md px-1.5 py-0.5 text-[11px] text-muted-foreground hover:bg-accent hover:text-foreground"
>
- {translate(
+ {t(
'worktreeJumpPalette.filter.selectAllMatching',
'Select all matching ({{value0}})',
{ value0: ranked.unselectedCount }
diff --git a/src/renderer/src/components/cmd-j/PaletteFilterMenu.tsx b/src/renderer/src/components/cmd-j/PaletteFilterMenu.tsx
index 008ef43fbbf..110cbd87129 100644
--- a/src/renderer/src/components/cmd-j/PaletteFilterMenu.tsx
+++ b/src/renderer/src/components/cmd-j/PaletteFilterMenu.tsx
@@ -3,7 +3,7 @@ import { ChevronRight, ListFilter, X } from 'lucide-react'
import { Command, CommandGroup, CommandItem, CommandList } from '@/components/ui/command'
import { Button } from '@/components/ui/button'
import { Popover, PopoverContent, PopoverTrigger } from '@/components/ui/popover'
-import { translate } from '@/i18n/i18n'
+import { useTranslate } from '@/i18n/use-translate'
import { useUiLanguageRevision } from '@/i18n/use-ui-language-revision'
import type { PaletteFilterModel } from './palette-filter-options'
import {
@@ -72,6 +72,7 @@ export default function PaletteFilterMenu({
onRequestInputFocus: () => void
portalContainer: HTMLElement | null
}): React.JSX.Element | null {
+ const t = useTranslate()
const languageRevision = useUiLanguageRevision()
const [open, setOpen] = useState(false)
const [activeField, setActiveField] = useState(null)
@@ -83,7 +84,7 @@ export default function PaletteFilterMenu({
if (model.hosts.length > 1) {
entries.push({
field: 'host',
- heading: translate('worktreeJumpPalette.filter.hosts', 'Hosts'),
+ heading: t('worktreeJumpPalette.filter.hosts', 'Hosts'),
options: model.hosts,
selected: filter.hostIds
})
@@ -92,14 +93,14 @@ export default function PaletteFilterMenu({
entries.push({
field: 'repository',
// "Projects" is the user-facing term for repository-granular choices; see filter.emptySubtitle.
- heading: translate('worktreeJumpPalette.filter.projects', 'Projects'),
+ heading: t('worktreeJumpPalette.filter.projects', 'Projects'),
options: model.repositories,
selected: filter.repoIds
})
}
return entries
// oxlint-disable-next-line react-hooks/exhaustive-deps -- translate() reads the UI language implicitly.
- }, [filter.hostIds, filter.repoIds, model.hosts, model.repositories, languageRevision])
+ }, [filter.hostIds, filter.repoIds, model.hosts, model.repositories, languageRevision, t])
const activeGroup =
activeField == null ? null : (groups.find((group) => group.field === activeField) ?? null)
@@ -163,12 +164,12 @@ export default function PaletteFilterMenu({
type="button"
variant={active ? 'secondary' : 'outline'}
size="xs"
- aria-label={translate('worktreeJumpPalette.filter.trigger', 'Filter results')}
+ aria-label={t('worktreeJumpPalette.filter.trigger', 'Filter results')}
data-active={active ? 'true' : undefined}
className="ml-2"
>
- {translate('worktreeJumpPalette.filter.label', 'Filter')}
+ {t('worktreeJumpPalette.filter.label', 'Filter')}
{active ? (
{selectionCount}
@@ -210,7 +211,7 @@ export default function PaletteFilterMenu({
{active ? (
- {translate('worktreeJumpPalette.filter.activeCount', '{{value0}} active', {
+ {t('worktreeJumpPalette.filter.activeCount', '{{value0}} active', {
value0: selectionCount
})}
@@ -220,7 +221,7 @@ export default function PaletteFilterMenu({
className="flex items-center gap-1 rounded-md px-1.5 py-0.5 text-[11px] text-muted-foreground hover:bg-accent hover:text-foreground"
>
- {translate('worktreeJumpPalette.filter.clearAll', 'Clear all')}
+ {t('worktreeJumpPalette.filter.clearAll', 'Clear all')}
) : null}
diff --git a/src/renderer/src/components/cmd-j/palette-live-status.tsx b/src/renderer/src/components/cmd-j/palette-live-status.tsx
index dde7075c919..5bd08054f6f 100644
--- a/src/renderer/src/components/cmd-j/palette-live-status.tsx
+++ b/src/renderer/src/components/cmd-j/palette-live-status.tsx
@@ -30,7 +30,7 @@ import {
terminalTabHasUnreadActivity,
type TerminalTabAttentionBadge
} from '@/components/tab-bar/terminal-tab-activity-status'
-import { translate } from '@/i18n/i18n'
+import { useTranslate } from '@/i18n/use-translate'
import { AGENT_STATUS_STALE_AFTER_MS } from '../../../../shared/agent-status/contracts/agent-status-freshness'
import type { AgentStatusEntry } from '../../../../shared/agent-status/contracts/agent-status-types'
@@ -237,6 +237,7 @@ export function PaletteRecentTabStatusDot({
row: RecentWorkspaceTabRow | null
fallback: React.ReactNode
}): React.JSX.Element {
+ const t = useTranslate()
const live = useLiveStatus()
const terminalTabId = row?.terminalTab?.id
const status: WorktreeStatus | null =
@@ -258,10 +259,7 @@ export function PaletteRecentTabStatusDot({
const statusLabel =
badge === 'unread'
? // Reuse the translated tab-bar label for the same unread signal.
- translate(
- 'auto.components.tab.bar.TerminalTabLeadingIcon.7ab2964bea',
- 'Unread agent completion'
- )
+ t('auto.components.tab.bar.TerminalTabLeadingIcon.7ab2964bea', 'Unread agent completion')
: getWorktreeStatusLabel(badge)
// Why: the outer hit target owns the tooltip because the overlaid pip ignores pointer events.
return (
diff --git a/src/renderer/src/components/contextual-tours/ContextualTourControl.tsx b/src/renderer/src/components/contextual-tours/ContextualTourControl.tsx
index 3b574aa8f52..44b9373e7ac 100644
--- a/src/renderer/src/components/contextual-tours/ContextualTourControl.tsx
+++ b/src/renderer/src/components/contextual-tours/ContextualTourControl.tsx
@@ -3,7 +3,7 @@ import { Switch } from '@/components/ui/switch'
import { useAppStore } from '@/store'
import type { ContextualTourStepControl } from '../../../../shared/contextual-tours'
import { CONTEXTUAL_TOUR_ENABLE_AUTO_WORKSPACE_NAME_EVENT } from './contextual-tour-composer-events'
-import { translate } from '@/i18n/i18n'
+import { useTranslate } from '@/i18n/use-translate'
export function ContextualTourControl({
control
@@ -29,6 +29,7 @@ export function toggleAutoRenameBranchFromWork(args: {
}
function AutoRenameBranchFromWorkControl(): JSX.Element {
+ const t = useTranslate()
const enabled = useAppStore((s) => s.settings?.autoRenameBranchFromWork === true)
const updateSettings = useAppStore((s) => s.updateSettings)
@@ -37,7 +38,7 @@ function AutoRenameBranchFromWorkControl(): JSX.Element {
- {translate(
+ {t(
'auto.components.contextual.tours.ContextualTourControl.731c5573df',
'Auto-name from first message'
)}
@@ -45,7 +46,7 @@ function AutoRenameBranchFromWorkControl(): JSX.Element {
(null)
const [floatingPosition, setFloatingPosition] = useState(
null
@@ -133,11 +134,8 @@ export function ContextualTourOverlaySurface({
const defaultPrimaryAction = {
kind: renderState.isLastStep ? 'complete' : 'next',
label: renderState.isLastStep
- ? translate('auto.components.contextual.tours.ContextualTourOverlaySurface.complete', 'Done')
- : translate(
- 'auto.components.contextual.tours.contextual.tour.overlay.measurement.38b3155418',
- 'Next'
- )
+ ? t('auto.components.contextual.tours.ContextualTourOverlaySurface.complete', 'Done')
+ : t('auto.components.contextual.tours.contextual.tour.overlay.measurement.38b3155418', 'Next')
} satisfies ContextualTourStepAction
const primaryAction =
renderState.primaryAction ?? (renderState.hidePrimaryAction ? null : defaultPrimaryAction)
@@ -201,11 +199,11 @@ export function ContextualTourOverlaySurface({
data-contextual-tour-dismiss=""
aria-label={
renderState.isLastStep
- ? translate(
+ ? t(
'auto.components.contextual.tours.ContextualTourOverlaySurface.d974f32a83',
'Dismiss tour'
)
- : translate(
+ : t(
'auto.components.contextual.tours.ContextualTourOverlaySurface.4f86e2a10b',
'Skip tour'
)
@@ -217,9 +215,7 @@ export function ContextualTourOverlaySurface({
{renderState.badge === 'new' ? (
-
- {translate('components.contextualTours.badgeNew', 'New')}
-
+ {t('components.contextualTours.badgeNew', 'New')}
) : null}
{renderState.title}
@@ -236,14 +232,14 @@ export function ContextualTourOverlaySurface({
type="button"
variant="ghost"
size="xs"
- aria-label={translate(
+ aria-label={t(
'auto.components.contextual.tours.ContextualTourOverlaySurface.4a9568f773',
'Back'
)}
onClick={onBack}
>
- {translate(
+ {t(
'auto.components.contextual.tours.ContextualTourOverlaySurface.4a9568f773',
'Back'
)}
diff --git a/src/renderer/src/components/contextual-tours/ContextualTourProgressDots.tsx b/src/renderer/src/components/contextual-tours/ContextualTourProgressDots.tsx
index 90fb4f9c4a1..302f4094562 100644
--- a/src/renderer/src/components/contextual-tours/ContextualTourProgressDots.tsx
+++ b/src/renderer/src/components/contextual-tours/ContextualTourProgressDots.tsx
@@ -1,6 +1,6 @@
import type { JSX } from 'react'
import { cn } from '@/lib/utils'
-import { translate } from '@/i18n/i18n'
+import { useTranslate } from '@/i18n/use-translate'
export function ContextualTourProgressDots({
current,
@@ -9,6 +9,7 @@ export function ContextualTourProgressDots({
current: number
total: number
}): JSX.Element {
+ const t = useTranslate()
if (total <= 1) {
return
}
@@ -19,7 +20,7 @@ export function ContextualTourProgressDots({
aria-valuemin={1}
aria-valuemax={total}
aria-valuenow={current}
- aria-label={translate(
+ aria-label={t(
'auto.components.contextual.tours.ContextualTourProgressDots.dcd6e6b03e',
'Step {{value0}} of {{value1}}',
{ value0: current, value1: total }
@@ -45,7 +46,7 @@ export function ContextualTourProgressDots({
})}
- {translate(
+ {t(
'auto.components.contextual.tours.ContextualTourProgressDots.stepOfTotal',
'{{current}} of {{total}}',
{ current, total }
diff --git a/src/renderer/src/components/crash-report/CrashReportDialogSurface.tsx b/src/renderer/src/components/crash-report/CrashReportDialogSurface.tsx
index e7385949720..af459c293e1 100644
--- a/src/renderer/src/components/crash-report/CrashReportDialogSurface.tsx
+++ b/src/renderer/src/components/crash-report/CrashReportDialogSurface.tsx
@@ -21,7 +21,8 @@ import {
type CrashReportRecord
} from '../../../../shared/crash-reporting'
import type { GitHubViewer } from '../../../../shared/github/pull-request-types'
-import { getIntlLocale, translate } from '@/i18n/i18n'
+import { translate } from '@/i18n/i18n'
+import { useIntlLocale, useTranslate, type Translate } from '@/i18n/use-translate'
import {
CRASH_REPORT_SUBMIT_FAILURE_TOAST_ID,
getCrashReportCopySubmissionFailure,
@@ -30,74 +31,68 @@ import {
} from './crash-report-submit-notice'
import { useCrashReportCopy } from './use-crash-report-copy'
-function formatSummary(report: CrashReportRecord): string {
+function formatSummary(report: CrashReportRecord, t: Translate = translate): string {
if (isReactErrorBoundaryReport(report)) {
const surface = typeof report.details.surface === 'string' ? report.details.surface : null
return surface
- ? translate(
+ ? t(
'auto.components.crash.report.CrashReportDialogSurface.renderErrorIn',
'React render error in {{surface}}',
{ surface }
)
- : translate(
- 'auto.components.crash.report.CrashReportDialogSurface.renderError',
- 'React render error'
- )
+ : t('auto.components.crash.report.CrashReportDialogSurface.renderError', 'React render error')
}
return `${report.processType} ${report.reason}${
report.exitCode === null ? '' : ` (exit ${report.exitCode})`
}`
}
-function getDialogTitle(report: CrashReportRecord | null): string {
+function getDialogTitle(report: CrashReportRecord | null, t: Translate = translate): string {
if (!report) {
- return translate(
- 'auto.components.crash.report.CrashReportDialogSurface.reportTitle',
- 'Report a crash'
- )
+ return t('auto.components.crash.report.CrashReportDialogSurface.reportTitle', 'Report a crash')
}
return report && isReactErrorBoundaryReport(report)
- ? translate(
+ ? t(
'auto.components.crash.report.CrashReportDialogSurface.uiErrorTitle',
'Orca hit a recoverable UI error'
)
- : translate(
+ : t(
'auto.components.crash.report.CrashReportDialogSurface.closedTitle',
'Orca closed unexpectedly'
)
}
-function getDialogDescription(report: CrashReportRecord | null): string {
+function getDialogDescription(report: CrashReportRecord | null, t: Translate = translate): string {
if (!report) {
- return translate(
+ return t(
'auto.components.crash.report.CrashReportDialogSurface.reportDescription',
'Send a privacy-safe crash report. Recent redacted diagnostic logs are included when available.'
)
}
return report && isReactErrorBoundaryReport(report)
- ? translate(
+ ? t(
'auto.components.crash.report.CrashReportDialogSurface.uiErrorDescription',
'Send a privacy-safe diagnostic report to help us understand the failed UI surface.'
)
- : translate(
+ : t(
'auto.components.crash.report.CrashReportDialogSurface.closedDescription',
'Send a privacy-safe diagnostic report to help us understand what happened.'
)
}
-function getNotesPlaceholder(report: CrashReportRecord | null): string {
+function getNotesPlaceholder(report: CrashReportRecord | null, t: Translate = translate): string {
if (!report) {
- return translate(
+ return t(
'auto.components.crash.report.CrashReportDialogSurface.reportNotesPlaceholder',
'Optional: what happened?'
)
}
return report && isReactErrorBoundaryReport(report)
- ? translate(
+ ? t(
'auto.components.crash.report.CrashReportDialogSurface.uiErrorNotesPlaceholder',
'Optional: what were you doing before this UI error?'
)
- : translate(
+ : t(
'auto.components.crash.report.CrashReportDialogSurface.closedNotesPlaceholder',
'Optional: what were you doing before Orca closed?'
)
@@ -118,6 +113,8 @@ export function CrashReportDialogSurface({
onOpenChange,
onReportChange
}: CrashReportDialogSurfaceProps): React.JSX.Element {
+ const t = useTranslate()
+ const intlLocale = useIntlLocale()
const mountedRef = useMountedRef()
const [notes, setNotes] = useState('')
const [includeDiagnosticLogs, setIncludeDiagnosticLogs] = useState(true)
@@ -230,10 +227,7 @@ export function CrashReportDialogSurface({
toast.warning(warningNotice.title, { description: warningNotice.description })
} else {
toast.success(
- translate(
- 'auto.components.crash.report.CrashReportDialog.8e24fe4f75',
- 'Crash report sent.'
- )
+ t('auto.components.crash.report.CrashReportDialog.8e24fe4f75', 'Crash report sent.')
)
}
onOpenChange(false)
@@ -270,26 +264,28 @@ export function CrashReportDialogSurface({
- {getDialogTitle(report)}
+ {getDialogTitle(report, t)}
- {getDialogDescription(report)}
+
+ {getDialogDescription(report, t)}
+
{report ? (
<>
-
{formatSummary(report)}
+
{formatSummary(report, t)}
- {new Date(report.createdAt).toLocaleString(getIntlLocale())} · {report.platform}{' '}
+ {new Date(report.createdAt).toLocaleString(intlLocale)} · {report.platform}{' '}
{report.arch} ·{' '}
- {translate('auto.components.crash.report.CrashReportDialog.835037edc9', 'Orca')}{' '}
+ {t('auto.components.crash.report.CrashReportDialog.835037edc9', 'Orca')}{' '}
{report.appVersion}
- {translate(
+ {t(
'auto.components.crash.report.CrashReportDialog.6d3ebe216a',
'Diagnostic text'
)}
@@ -302,11 +298,11 @@ export function CrashReportDialogSurface({
) : (
{loading
- ? translate(
+ ? t(
'auto.components.crash.report.CrashReportDialog.765591798d',
'Checking for crash reports...'
)
- : translate(
+ : t(
'auto.components.crash.report.CrashReportDialog.ead6fc0510',
'No automatic crash report was captured. You can still send details and include recent diagnostic logs when available.'
)}
@@ -319,15 +315,15 @@ export function CrashReportDialogSurface({
rows={4}
// Keep the UI and formatter on the same input budget.
maxLength={MAX_USER_NOTES_LENGTH}
- placeholder={getNotesPlaceholder(report)}
+ placeholder={getNotesPlaceholder(report, t)}
className="min-h-24 w-full rounded-md border border-border bg-background px-3 py-2 text-sm outline-none ring-offset-background placeholder:text-muted-foreground focus-visible:ring-2 focus-visible:ring-ring focus-visible:ring-offset-2"
/>
- {notes.length.toLocaleString(getIntlLocale())} /{' '}
- {MAX_USER_NOTES_LENGTH.toLocaleString(getIntlLocale())}
+ {notes.length.toLocaleString(intlLocale)} /{' '}
+ {MAX_USER_NOTES_LENGTH.toLocaleString(intlLocale)}
@@ -340,13 +336,13 @@ export function CrashReportDialogSurface({
/>
- {translate(
+ {t(
'auto.components.crash.report.CrashReportDialog.e59f0b9427',
'Sends a capped redacted log bundle with the report.'
)}
@@ -364,7 +360,7 @@ export function CrashReportDialogSurface({
disabled={loading}
>
- {translate('auto.components.crash.report.CrashReportDialog.50b00dc327', 'Copy Details')}
+ {t('auto.components.crash.report.CrashReportDialog.50b00dc327', 'Copy Details')}
diff --git a/src/renderer/src/components/dashboard-popout/AgentDashboardFilterChips.tsx b/src/renderer/src/components/dashboard-popout/AgentDashboardFilterChips.tsx
index e148e132c96..429ffcba302 100644
--- a/src/renderer/src/components/dashboard-popout/AgentDashboardFilterChips.tsx
+++ b/src/renderer/src/components/dashboard-popout/AgentDashboardFilterChips.tsx
@@ -1,6 +1,6 @@
import { X } from 'lucide-react'
import { Button } from '@/components/ui/button'
-import { translate } from '@/i18n/i18n'
+import { useTranslate } from '@/i18n/use-translate'
import type { DashboardFilters, DashboardReviewFilter } from './agent-board-filtering'
type FilterLabel = { id: string; label: string }
@@ -12,13 +12,14 @@ function ActiveChip({
label: string
onRemove: () => void
}): React.JSX.Element {
+ const t = useTranslate()
return (
{label}
{!filterControl && (query || activeCount > 0) ? (
- {translate('dashboardPopout.search.results', '{{shown}} of {{total}} shown', {
+ {t('dashboardPopout.search.results', '{{shown}} of {{total}} shown', {
shown: filteredCount,
total: cards.length
})}
@@ -135,7 +136,7 @@ export function AgentDashboardToolbar({
)}
>
- {translate('dashboardPopout.filters.label', 'Filter')}
+ {t('dashboardPopout.filters.label', 'Filter')}
{activeCount > 0 ? (
{activeCount}
@@ -146,7 +147,7 @@ export function AgentDashboardToolbar({
- {translate('dashboardPopout.filters.project', 'Project')}
+ {t('dashboardPopout.filters.project', 'Project')}
{projects.map((option) => (
- {translate('dashboardPopout.filters.workspaceStatus', 'Workspace status')}
+ {t('dashboardPopout.filters.workspaceStatus', 'Workspace status')}
{statuses.map((option) => {
- const meta = getWorkspaceStatusVisualMeta({
- id: option.id,
- label: option.label,
- color: option.color
- })
+ const meta = getWorkspaceStatusVisualMeta(
+ {
+ id: option.id,
+ label: option.label,
+ color: option.color
+ },
+ t
+ )
return (
- {translate('dashboardPopout.filters.reviewStatus', 'PR / MR status')}
+ {t('dashboardPopout.filters.reviewStatus', 'PR / MR status')}
{REVIEW_OPTIONS.map((option) => (
toggleReview(option)}
onSelect={(event) => event.preventDefault()}
>
- {reviewStateLabel(option)}
+ {reviewStateLabel(option, t)}
))}
@@ -204,7 +208,7 @@ export function AgentDashboardToolbar({
className="text-muted-foreground"
>
- {translate('dashboardPopout.filters.clearAll', 'Clear all filters')}
+ {t('dashboardPopout.filters.clearAll', 'Clear all filters')}
diff --git a/src/renderer/src/components/dashboard-popout/AgentKanbanBoard.tsx b/src/renderer/src/components/dashboard-popout/AgentKanbanBoard.tsx
index 46cde94d62d..991255701c1 100644
--- a/src/renderer/src/components/dashboard-popout/AgentKanbanBoard.tsx
+++ b/src/renderer/src/components/dashboard-popout/AgentKanbanBoard.tsx
@@ -20,6 +20,7 @@ import {
} from './agent-board-filtering'
import './agent-board-transitions.css'
import { translate } from '@/i18n/i18n'
+import { useTranslate, type Translate } from '@/i18n/use-translate'
import type { AgentSubjectReadIntent } from '@/attention/agent-subject-read-actions'
/** Relay acknowledgments to the main renderer; optional calls tolerate dev-HMR preload skew until restart. */
@@ -32,16 +33,16 @@ function revealAgentViaPopoutRelay(args: AgentRevealArgs): void {
void window.api.dashboard.revealAgent?.(args)
}
-function bucketLabel(bucket: DashboardBucket): string {
+function bucketLabel(bucket: DashboardBucket, t: Translate = translate): string {
switch (bucket) {
case 'attention':
- return translate('dashboardPopout.bucket.attention', 'Needs You')
+ return t('dashboardPopout.bucket.attention', 'Needs You')
case 'working':
- return translate('dashboardPopout.bucket.working', 'Working')
+ return t('dashboardPopout.bucket.working', 'Working')
case 'done':
- return translate('dashboardPopout.bucket.done', 'Done')
+ return t('dashboardPopout.bucket.done', 'Done')
case 'idle':
- return translate('dashboardPopout.bucket.idle', 'Idle')
+ return t('dashboardPopout.bucket.idle', 'Idle')
}
}
@@ -75,7 +76,8 @@ function KanbanColumn({
now: number
onOpenTerminal: (card: DashboardCard) => void
}): React.JSX.Element {
- const label = bucketLabel(bucket)
+ const t = useTranslate()
+ const label = bucketLabel(bucket, t)
return (
// Cards carry attention color; keep the column neutral to avoid duplicate signaling.
@@ -94,7 +96,7 @@ function KanbanColumn({
{cards.length === 0 ? (
- {translate('dashboardPopout.bucket.empty', 'None')}
+ {t('dashboardPopout.bucket.empty', 'None')}
) : (
cards.map((card) => (
@@ -137,6 +139,7 @@ export function AgentKanbanBoard({
onBrowserForeground,
headerActions
}: AgentKanbanBoardProps): React.JSX.Element {
+ const t = useTranslate()
const visibleBuckets = useMemo(
() =>
DASHBOARD_BUCKET_ORDER.filter((bucket) => bucket !== 'idle' || snapshot.showIdle === true),
@@ -242,11 +245,9 @@ export function AgentKanbanBoard({
className={cn('relative flex flex-col bg-background text-foreground', containerClassName)}
>
-
- {translate('dashboardPopout.title', 'Agents')}
-
+
{t('dashboardPopout.title', 'Agents')}
- {translate('dashboardPopout.total', '{{count}} total', {
+ {t('dashboardPopout.total', '{{count}} total', {
count: visibleCards.length
})}
@@ -257,7 +258,7 @@ export function AgentKanbanBoard({
{subagentsOpen ? (
@@ -332,7 +335,7 @@ export const AgentKanbanCard = memo(
{displayTimestamp(card) > 0 ? (
- {formatStartedAgo(displayTimestamp(card), now)}
+ {formatStartedAgo(displayTimestamp(card), now, t)}
) : null}
diff --git a/src/renderer/src/components/dashboard-popout/AgentTerminalDialog.tsx b/src/renderer/src/components/dashboard-popout/AgentTerminalDialog.tsx
index 88ee7121fb5..cd9c92f07f6 100644
--- a/src/renderer/src/components/dashboard-popout/AgentTerminalDialog.tsx
+++ b/src/renderer/src/components/dashboard-popout/AgentTerminalDialog.tsx
@@ -12,7 +12,7 @@ import {
} from '../../../../shared/dashboard-snapshot'
import { AgentTerminalPreview } from './AgentTerminalPreview'
import { terminalPreviewUnavailableMessage } from './terminal-preview-unavailable-message'
-import { translate } from '@/i18n/i18n'
+import { useTranslate } from '@/i18n/use-translate'
import { cn } from '@/lib/utils'
/** Routing payload for focusing an agent's pane in the main window. */
@@ -41,6 +41,7 @@ function AgentTerminalFrame({
onReveal,
onBrowserForeground
}: AgentTerminalFrameProps): React.JSX.Element {
+ const t = useTranslate()
const reveal = (): void => {
onReveal({
repoId: card.repoId,
@@ -71,7 +72,7 @@ function AgentTerminalFrame({
onClick={() => onOpenChange(false)}
>
- {translate('dashboardPopout.terminal.close', 'Close')}
+ {t('dashboardPopout.terminal.close', 'Close')}
{card.ptyId ? (
@@ -101,13 +102,13 @@ function AgentTerminalFrame({
/>
) : (
- {terminalPreviewUnavailableMessage({ hostKind: card.hostKind })}
+ {terminalPreviewUnavailableMessage({ hostKind: card.hostKind }, t)}
)}
>
diff --git a/src/renderer/src/components/dashboard-popout/AgentTerminalPreview.tsx b/src/renderer/src/components/dashboard-popout/AgentTerminalPreview.tsx
index d13b7c7240f..9ef12243799 100644
--- a/src/renderer/src/components/dashboard-popout/AgentTerminalPreview.tsx
+++ b/src/renderer/src/components/dashboard-popout/AgentTerminalPreview.tsx
@@ -35,6 +35,7 @@ import {
} from '@/components/link-actions/link-action-request'
import { installDashboardPreviewLinkInteraction } from './dashboard-preview-link-interaction'
import type { DashboardPreviewLinkSource } from './dashboard-preview-http-actions'
+import { useTranslate } from '@/i18n/use-translate'
const PREVIEW_SCROLLBACK_ROWS = 24
// Preview snapshots bound history; pane scrollback would only cost memory.
@@ -64,6 +65,7 @@ export function AgentTerminalPreview({
linkSource?: DashboardPreviewLinkSource
onBrowserForeground?: () => void
}): React.JSX.Element {
+ const t = useTranslate()
const containerRef = useRef
(null)
const terminalRef = useRef(null)
const [linkRequest, setLinkRequest] = useState(null)
@@ -383,7 +385,7 @@ export function AgentTerminalPreview({
) : null}
{ptyGone ? (
- {terminalPreviewUnavailableMessage({ ptyId })}
+ {terminalPreviewUnavailableMessage({ ptyId }, t)}
) : null}
): string | null {
+export function dashboardHostTooltipLabel(
+ {
+ hostKind,
+ executionHostId,
+ hostLabel
+ }: Pick
,
+ t: Translate = translate
+): string | null {
if (hostKind !== 'ssh' && hostKind !== 'remote') {
return null
}
const label = hostLabel?.trim() || fallbackHostLabel(executionHostId)
if (hostKind === 'ssh') {
return label
- ? translate('dashboardPopout.host.sshNamed', 'SSH host · {{host}}', { host: label })
- : translate('dashboardPopout.host.ssh', 'SSH host')
+ ? t('dashboardPopout.host.sshNamed', 'SSH host · {{host}}', { host: label })
+ : t('dashboardPopout.host.ssh', 'SSH host')
}
return label
- ? translate('dashboardPopout.host.remoteNamed', 'Remote Orca host · {{host}}', { host: label })
- : translate('dashboardPopout.host.remote', 'Remote Orca host')
+ ? t('dashboardPopout.host.remoteNamed', 'Remote Orca host · {{host}}', { host: label })
+ : t('dashboardPopout.host.remote', 'Remote Orca host')
}
export function DashboardHostBadge({
@@ -52,7 +56,8 @@ export function DashboardHostBadge({
className,
iconClassName
}: DashboardHostBadgeProps): React.JSX.Element | null {
- const tooltipLabel = dashboardHostTooltipLabel({ hostKind, executionHostId, hostLabel })
+ const t = useTranslate()
+ const tooltipLabel = dashboardHostTooltipLabel({ hostKind, executionHostId, hostLabel }, t)
if (!tooltipLabel) {
return null
}
diff --git a/src/renderer/src/components/dashboard-popout/agent-dashboard-filter-options.ts b/src/renderer/src/components/dashboard-popout/agent-dashboard-filter-options.ts
index e91099ed0de..95443eb6f31 100644
--- a/src/renderer/src/components/dashboard-popout/agent-dashboard-filter-options.ts
+++ b/src/renderer/src/components/dashboard-popout/agent-dashboard-filter-options.ts
@@ -1,4 +1,5 @@
import { translate } from '@/i18n/i18n'
+import type { Translate } from '@/i18n/use-translate'
import type { DashboardCard, DashboardFilterOption } from '../../../../shared/dashboard-snapshot'
import type { DashboardReviewFilter } from './agent-board-filtering'
@@ -78,17 +79,17 @@ export const REVIEW_OPTIONS: readonly DashboardReviewFilter[] = [
'none'
]
-export function reviewStateLabel(state: DashboardReviewFilter): string {
+export function reviewStateLabel(state: DashboardReviewFilter, t: Translate = translate): string {
switch (state) {
case 'open':
- return translate('dashboardPopout.filters.review.open', 'Open')
+ return t('dashboardPopout.filters.review.open', 'Open')
case 'draft':
- return translate('dashboardPopout.filters.review.draft', 'Draft')
+ return t('dashboardPopout.filters.review.draft', 'Draft')
case 'merged':
- return translate('dashboardPopout.filters.review.merged', 'Merged')
+ return t('dashboardPopout.filters.review.merged', 'Merged')
case 'closed':
- return translate('dashboardPopout.filters.review.closed', 'Closed')
+ return t('dashboardPopout.filters.review.closed', 'Closed')
case 'none':
- return translate('dashboardPopout.filters.review.none', 'No review')
+ return t('dashboardPopout.filters.review.none', 'No review')
}
}
diff --git a/src/renderer/src/components/dashboard-popout/terminal-preview-unavailable-message.ts b/src/renderer/src/components/dashboard-popout/terminal-preview-unavailable-message.ts
index eb0529f1044..255dd4362eb 100644
--- a/src/renderer/src/components/dashboard-popout/terminal-preview-unavailable-message.ts
+++ b/src/renderer/src/components/dashboard-popout/terminal-preview-unavailable-message.ts
@@ -1,24 +1,28 @@
import { translate } from '@/i18n/i18n'
+import type { Translate } from '@/i18n/use-translate'
import type { DashboardCardHostKind } from '../../../../shared/dashboard-snapshot'
import { parseAppSshPtyId } from '../../../../shared/ssh-pty-id'
import { isRemoteRuntimePtyId } from '../../../../shared/remote-runtime/remote-runtime-pty-id'
/** Remote snapshot absence cannot prove pane exit because the client does not own the PTY; see docs/reference/ssh-execution-boundary.md. */
-export function terminalPreviewUnavailableMessage(source: {
- ptyId?: string | null
- hostKind?: DashboardCardHostKind
-}): string {
+export function terminalPreviewUnavailableMessage(
+ source: {
+ ptyId?: string | null
+ hostKind?: DashboardCardHostKind
+ },
+ t: Translate = translate
+): string {
const isRemote =
source.hostKind === 'ssh' ||
source.hostKind === 'remote' ||
isRemoteRuntimePtyId(source.ptyId) ||
(typeof source.ptyId === 'string' && parseAppSshPtyId(source.ptyId) !== null)
return isRemote
- ? translate(
+ ? t(
'dashboardPopout.terminal.remotePreviewUnavailable',
'No preview for this remote session — open the workspace to view the terminal.'
)
- : translate(
+ : t(
'dashboardPopout.terminal.previewUnavailable',
'No preview available — open the workspace to view this agent.'
)
diff --git a/src/renderer/src/components/dashboard/AgentDashboardDrawer.tsx b/src/renderer/src/components/dashboard/AgentDashboardDrawer.tsx
index 8de1890bddc..157d9b99e8a 100644
--- a/src/renderer/src/components/dashboard/AgentDashboardDrawer.tsx
+++ b/src/renderer/src/components/dashboard/AgentDashboardDrawer.tsx
@@ -17,7 +17,7 @@ import {
import { AgentDashboardSettingsMenu } from './AgentDashboardSettingsMenu'
import { useLiveDashboardSnapshot } from './useLiveDashboardSnapshot'
import { useAgentDashboardTelemetry } from '../dashboard-popout/use-agent-dashboard-telemetry'
-import { translate } from '@/i18n/i18n'
+import { useTranslate } from '@/i18n/use-translate'
import { trackSidebarLiveWidth } from '@/components/sidebar/sidebar-live-width'
// Nested interactive overlays own Escape before the companion sheet; exclude the sheet’s own role=dialog marker.
@@ -94,6 +94,7 @@ export function AgentDashboardDrawer({
leftSidebarStyle,
statusBarVisible
}: AgentDashboardDrawerProps): React.JSX.Element {
+ const t = useTranslate()
const open = useAppStore((s) => s.agentDashboardDrawerOpen)
const setOpen = useAppStore((s) => s.setAgentDashboardDrawerOpen)
const sidebarOpen = useAppStore((s) => s.sidebarOpen)
@@ -243,7 +244,7 @@ export function AgentDashboardDrawer({
onPointerDownOutside={guardSidebarInteraction}
onInteractOutside={guardSidebarInteraction}
>
- {translate('dashboardPopout.title', 'Agents')}
+ {t('dashboardPopout.title', 'Agents')}
{/* Closed SheetContent unmounts the live snapshot body, avoiding closed-path derivation. */}
- {translate(
+ {t(
'auto.components.settings.ExperimentalPane.agentDashboard.copy',
'Adds an Agent Dashboard entry to the left sidebar. Monitor agents that need you, are working, or are done, with optional idle agents.'
)}
@@ -52,7 +47,7 @@ export function AgentDashboardExperimentalSetting({
- {translate(
+ {t(
'auto.components.settings.ExperimentalPane.agentDashboard.modeCopy',
'Show the dashboard as an in-window board beside the sidebar or a separate pop-out window.'
)}
@@ -79,7 +71,7 @@ export function AgentDashboardExperimentalSetting({
updateSettings({ experimentalAgentDashboardMode: next })}
- ariaLabel={translate(
+ ariaLabel={t(
'auto.components.settings.ExperimentalPane.agentDashboard.modeAriaLabel',
'Agent Dashboard open mode'
)}
@@ -87,14 +79,14 @@ export function AgentDashboardExperimentalSetting({
options={[
{
value: 'in-window',
- label: translate(
+ label: t(
'auto.components.settings.ExperimentalPane.agentDashboard.modeInWindow',
'In-window'
)
},
{
value: 'popout',
- label: translate(
+ label: t(
'auto.components.settings.ExperimentalPane.agentDashboard.modePopout',
'Pop-out'
)
diff --git a/src/renderer/src/components/dashboard/AgentDashboardSettingsMenu.tsx b/src/renderer/src/components/dashboard/AgentDashboardSettingsMenu.tsx
index b57ae75824d..0da15b80f6e 100644
--- a/src/renderer/src/components/dashboard/AgentDashboardSettingsMenu.tsx
+++ b/src/renderer/src/components/dashboard/AgentDashboardSettingsMenu.tsx
@@ -13,7 +13,7 @@ import {
SettingsSwitch
} from '../settings/form-controls/SettingsFormControls'
import type { AgentDashboardMode } from '../../../../shared/ui-chrome-types'
-import { translate } from '@/i18n/i18n'
+import { useTranslate } from '@/i18n/use-translate'
import { canOpenAgentDashboardPopout } from './agent-dashboard-open-mode'
type AgentDashboardSettingsMenuProps = {
@@ -28,6 +28,7 @@ export function AgentDashboardSettingsMenu({
onSwitchToPopout,
onOpenChange
}: AgentDashboardSettingsMenuProps): React.JSX.Element {
+ const t = useTranslate()
const mode = useAppStore((s) => s.settings?.experimentalAgentDashboardMode ?? 'in-window')
const showIdle = useAppStore((s) => s.settings?.experimentalAgentDashboardShowIdle === true)
const updateSettings = useAppStore((s) => s.updateSettings)
@@ -50,7 +51,7 @@ export function AgentDashboardSettingsMenu({
)}
{!hasChildDisclosure && !sendTargetStatus && hideDismiss && relativeTimestamp !== null && (
@@ -135,11 +133,11 @@ export function DashboardAgentRowTrailingControls({
'can-hover:opacity-0 transition-opacity duration-150',
'group-hover/agent-row:opacity-100 focus-visible:opacity-100'
)}
- aria-label={translate(
+ aria-label={t(
'auto.components.dashboard.DashboardAgentRow.b06e13fcf7',
'Dismiss agent'
)}
- title={translate('auto.components.dashboard.DashboardAgentRow.5ae84475cc', 'Dismiss')}
+ title={t('auto.components.dashboard.DashboardAgentRow.5ae84475cc', 'Dismiss')}
>
@@ -156,11 +154,8 @@ export function DashboardAgentRowTrailingControls({
'can-hover:opacity-0 transition-opacity duration-150',
'group-hover/agent-row:opacity-100 focus-visible:opacity-100'
)}
- aria-label={translate(
- 'auto.components.dashboard.DashboardAgentRow.b06e13fcf7',
- 'Dismiss agent'
- )}
- title={translate('auto.components.dashboard.DashboardAgentRow.5ae84475cc', 'Dismiss')}
+ aria-label={t('auto.components.dashboard.DashboardAgentRow.b06e13fcf7', 'Dismiss agent')}
+ title={t('auto.components.dashboard.DashboardAgentRow.5ae84475cc', 'Dismiss')}
>
@@ -186,14 +181,8 @@ export function DashboardAgentRowTrailingControls({
className="inline-flex shrink-0 items-center justify-center text-muted-foreground/60 hover:text-foreground"
aria-label={
expanded
- ? translate(
- 'auto.components.dashboard.DashboardAgentRow.a41fb5376e',
- 'Collapse details'
- )
- : translate(
- 'auto.components.dashboard.DashboardAgentRow.a743da52ff',
- 'Expand details'
- )
+ ? t('auto.components.dashboard.DashboardAgentRow.a41fb5376e', 'Collapse details')
+ : t('auto.components.dashboard.DashboardAgentRow.a743da52ff', 'Expand details')
}
aria-expanded={expanded}
>
diff --git a/src/renderer/src/components/dashboard/build-dashboard-snapshot.ts b/src/renderer/src/components/dashboard/build-dashboard-snapshot.ts
index 67b9c03cbba..bd8c099b15d 100644
--- a/src/renderer/src/components/dashboard/build-dashboard-snapshot.ts
+++ b/src/renderer/src/components/dashboard/build-dashboard-snapshot.ts
@@ -50,6 +50,7 @@ import { buildDashboardSnapshotFilterOptions } from './dashboard-snapshot-filter
import { groupSubagentsByParentPaneKey } from './dashboard-subagent-cards'
import { selectDashboardOrchestration } from './dashboard-orchestration-selection'
import { dashboardRowBucketProjection, readAgentRowTurnRecords } from './dashboard-row-bucket'
+import type { Translate } from '@/i18n/use-translate'
/** Keep builder inputs as a Pick so tests need only construct the store slices read here. */
export type DashboardSnapshotState = Pick<
@@ -95,7 +96,8 @@ export function buildDashboardSnapshot(
rowsCache?: WorktreeAgentRowsCache
/** Freshness token for rowsCache (agentStatusEpoch); required for the cache to be safe. */
rowsGeneration?: unknown
- } = {}
+ } = {},
+ t?: Translate
): DashboardSnapshot {
const cards: DashboardCard[] = []
const workspaces: DashboardWorkspace[] | undefined =
@@ -141,7 +143,7 @@ export function buildDashboardSnapshot(
? groupSubagentsByParentPaneKey(rows)
: undefined
const context = includeCardDetails
- ? resolveDashboardCardContext(state, repo, worktree)
+ ? resolveDashboardCardContext(state, repo, worktree, t)
: undefined
if (workspaces && workspaces.length < DASHBOARD_MAX_MAP_WORKSPACES) {
const hostMetadata = dashboardCardMapWorkspaceMetadata(
diff --git a/src/renderer/src/components/dashboard/dashboard-card-context.ts b/src/renderer/src/components/dashboard/dashboard-card-context.ts
index 83d7f9d3b5d..61a7d836f30 100644
--- a/src/renderer/src/components/dashboard/dashboard-card-context.ts
+++ b/src/renderer/src/components/dashboard/dashboard-card-context.ts
@@ -15,6 +15,7 @@ import {
getParentPrChecksGitHubPRCacheEntry
} from '../right-sidebar/hosted-review/folder-checks/parent-pr-checks-github-pr-cache'
import { canUseParentPrChecksHostedReviewCacheEntry } from '../right-sidebar/hosted-review/folder-checks/parent-pr-checks-hosted-review-cache'
+import type { Translate } from '@/i18n/use-translate'
export type DashboardCardContextState = Partial<
Pick
@@ -39,7 +40,8 @@ function hasLinkedReview(worktree: Worktree): boolean {
function resolveReview(
state: DashboardCardContextState,
repo: Repo | null,
- worktree: Worktree
+ worktree: Worktree,
+ t?: Translate
): DashboardCardReview | undefined {
if (!repo || !state.hostedReviewCache || !state.prCache || repo.kind === 'folder') {
return undefined
@@ -60,7 +62,7 @@ function resolveReview(
const hostedReview = hostedReviewEntry?.data
if (
hostedReview &&
- canUseParentPrChecksHostedReviewCacheEntry(worktree, hostedReview, hostedReviewEntry)
+ canUseParentPrChecksHostedReviewCacheEntry(worktree, hostedReview, hostedReviewEntry, t)
) {
return { number: hostedReview.number, state: hostedReview.state }
}
@@ -79,14 +81,15 @@ function resolveReview(
export function resolveDashboardCardContext(
state: DashboardCardContextState,
repo: Repo | null,
- worktree: Worktree
+ worktree: Worktree,
+ t?: Translate
): DashboardCardContext {
const statuses =
state.workspaceStatuses && state.workspaceStatuses.length > 0
? state.workspaceStatuses
: DEFAULT_WORKSPACE_STATUSES
const workspaceStatusId = getWorkspaceStatus(worktree, statuses)
- const review = resolveReview(state, repo, worktree)
+ const review = resolveReview(state, repo, worktree, t)
return {
workspaceStatus:
statuses.find((status) => status.id === workspaceStatusId) ?? DEFAULT_WORKSPACE_STATUSES[0],
diff --git a/src/renderer/src/components/dictation/DictationController.tsx b/src/renderer/src/components/dictation/DictationController.tsx
index 526eb1132c8..30a4c1168a2 100644
--- a/src/renderer/src/components/dictation/DictationController.tsx
+++ b/src/renderer/src/components/dictation/DictationController.tsx
@@ -7,6 +7,7 @@ import { captureInsertionTarget } from './dictation-insertion-target'
import { createDictationTranscriptDelivery } from './dictation-transcript-delivery'
import { recordStoppedSession, waitForStoppedSession } from './dictation-stopped-sessions'
import { translate } from '@/i18n/i18n'
+import { useTranslate } from '@/i18n/use-translate'
import {
showDictationDisabledToast,
showDictationNoModelToast,
@@ -30,6 +31,7 @@ import {
} from '../../../../shared/dictation-session-machine'
export function DictationController() {
+ const t = useTranslate()
const dictationState = useAppStore((s) => s.dictationState)
const setDictationState = useAppStore((s) => s.setDictationState)
const setPartialTranscript = useAppStore((s) => s.setPartialTranscript)
@@ -314,11 +316,9 @@ export function DictationController() {
recordStoppedSession(sessionId, stoppedSessionIdsRef, stoppedResolversRef),
onError: (sessionId, error) => {
toast.error(
- translate(
- 'auto.components.dictation.DictationController.de136f1199',
- 'Speech error: {{value0}}',
- { value0: error }
- )
+ t('auto.components.dictation.DictationController.de136f1199', 'Speech error: {{value0}}', {
+ value0: error
+ })
)
const { run } = dispatch({ type: 'abandon' })
stopCapture()
diff --git a/src/renderer/src/components/dictation/DictationIndicator.tsx b/src/renderer/src/components/dictation/DictationIndicator.tsx
index 995125b9264..a231abedecf 100644
--- a/src/renderer/src/components/dictation/DictationIndicator.tsx
+++ b/src/renderer/src/components/dictation/DictationIndicator.tsx
@@ -2,7 +2,7 @@ import { ShortcutKeyCombo } from '@/components/ShortcutKeyCombo'
import { Button } from '@/components/ui/button'
import { Tooltip, TooltipContent, TooltipTrigger } from '@/components/ui/tooltip'
import { useShortcutKeyDetails } from '@/hooks/useShortcutLabel'
-import { translate } from '@/i18n/i18n'
+import { useTranslate } from '@/i18n/use-translate'
import { cn } from '@/lib/utils'
import { useAppStore } from '@/store'
import { Square } from 'lucide-react'
@@ -12,6 +12,7 @@ import { useDictationMeter } from './dictation-meter-store'
// captureLive: the mic is recording (and buffering) while the speech engine is still starting.
export function DictationIndicator({ captureLive }: { captureLive: boolean }) {
+ const t = useTranslate()
const dictationState = useAppStore((state) => state.dictationState)
const partialTranscript = useAppStore((state) => state.partialTranscript)
const dictationMeter = useDictationMeter()
@@ -28,23 +29,20 @@ export function DictationIndicator({ captureLive }: { captureLive: boolean }) {
const isClipping = isListening && dictationMeter.isClipping
const isSpeaking = isListening && dictationMeter.isSpeaking && !isClipping
const lifecycleLabel = isListening
- ? translate('auto.components.dictation.DictationIndicator.3de5a129e7', 'Listening')
+ ? t('auto.components.dictation.DictationIndicator.3de5a129e7', 'Listening')
: dictationState === 'starting'
- ? translate('auto.components.dictation.DictationIndicator.7f3660a7ba', 'Starting mic…')
- : translate('auto.components.dictation.DictationIndicator.f082d0cb9d', 'Processing…')
+ ? t('auto.components.dictation.DictationIndicator.7f3660a7ba', 'Starting mic…')
+ : t('auto.components.dictation.DictationIndicator.f082d0cb9d', 'Processing…')
const label = isClipping
- ? translate('auto.components.dictation.DictationIndicator.4977162383', 'Too loud')
+ ? t('auto.components.dictation.DictationIndicator.4977162383', 'Too loud')
: isSpeaking
- ? translate('auto.components.dictation.DictationIndicator.25f2b7a6a5', 'Speaking')
+ ? t('auto.components.dictation.DictationIndicator.25f2b7a6a5', 'Speaking')
: lifecycleLabel
const announcedLabel = isClipping ? label : lifecycleLabel
const canStop = dictationState !== 'stopping'
const showShortcut = !isHoldMode && shortcut.keys.length > 0
const transcript = partialTranscript.trim()
- const stopLabel = translate(
- 'auto.components.dictation.DictationIndicator.335e1bc6cb',
- 'Stop dictation'
- )
+ const stopLabel = t('auto.components.dictation.DictationIndicator.335e1bc6cb', 'Stop dictation')
return (
{isLoadingModel ? (
- {translate(
+ {t(
'auto.components.dictation.DictationIndicator.loadingModel',
'Loading speech model…'
)}
diff --git a/src/renderer/src/components/diff-comments/DiffCommentCard.tsx b/src/renderer/src/components/diff-comments/DiffCommentCard.tsx
index e1684e2a65f..6e13895d1a3 100644
--- a/src/renderer/src/components/diff-comments/DiffCommentCard.tsx
+++ b/src/renderer/src/components/diff-comments/DiffCommentCard.tsx
@@ -4,7 +4,7 @@ import { useLayoutEffect, useRef, useState, type ReactNode } from 'react'
import { Button } from '@/components/ui/button'
import { getDiffCommentLineMetaLabel } from './diff-comment-line-meta-label'
import { useMountedRef } from '@/hooks/useMountedRef'
-import { translate } from '@/i18n/i18n'
+import { useTranslate } from '@/i18n/use-translate'
import { useUiLanguageRevision } from '@/i18n/use-ui-language-revision'
import { openAppHttpLink } from '@/lib/browser/navigation/app-http-link-open'
@@ -51,6 +51,7 @@ export function DiffCommentCard({
onSubmitEdit,
headerActions
}: Props): React.JSX.Element {
+ const t = useTranslate()
// Zone cards live in separate React roots, so subscribe directly to language changes.
useUiLanguageRevision()
const [editing, setEditing] = useState(false)
@@ -138,12 +139,12 @@ export function DiffCommentCard({
const trimmedDraft = draft.trim()
const canSubmit = !submitting && trimmedDraft.length > 0 && trimmedDraft !== body
const lineLabel =
- label === undefined ? getDiffCommentLineMetaLabel({ lineNumber, startLine }) : label
+ label === undefined ? getDiffCommentLineMetaLabel({ lineNumber, startLine }, t) : label
const metaText = [
- author || translate('auto.components.diff.comments.DiffCommentCard.noteAuthorFallback', 'Note'),
+ author || t('auto.components.diff.comments.DiffCommentCard.noteAuthorFallback', 'Note'),
lineLabel,
createdAtLabel ||
- (sentAt ? translate('auto.components.diff.comments.DiffCommentCard.sentMeta', 'sent') : null)
+ (sentAt ? t('auto.components.diff.comments.DiffCommentCard.sentMeta', 'sent') : null)
]
.filter(Boolean)
.join(' ')
@@ -191,11 +192,11 @@ export function DiffCommentCard({
{(onSubmitEdit || onDelete) && (
@@ -217,11 +218,11 @@ export function DiffCommentCard({
diff --git a/src/renderer/src/components/diff-comments/DiffCommentDraftCard.tsx b/src/renderer/src/components/diff-comments/DiffCommentDraftCard.tsx
index 885ffd134d7..2f91ffcf48d 100644
--- a/src/renderer/src/components/diff-comments/DiffCommentDraftCard.tsx
+++ b/src/renderer/src/components/diff-comments/DiffCommentDraftCard.tsx
@@ -6,7 +6,7 @@ import { useCallback, useEffect, useId, useLayoutEffect, useRef, useState } from
import { CornerDownLeft } from 'lucide-react'
import { Button } from '@/components/ui/button'
import { useMountedRef } from '@/hooks/useMountedRef'
-import { translate } from '@/i18n/i18n'
+import { useTranslate } from '@/i18n/use-translate'
import {
getCommentBodySubmitState,
hasBoundedCommentBodyText
@@ -48,6 +48,7 @@ export function DiffCommentDraftCard({
initialBody = '',
onBodyChange
}: DiffCommentDraftCardProps): React.JSX.Element {
+ const t = useTranslate()
const [body, setBody] = useState(initialBody)
const bodyRef = useRef(body)
const [submitting, setSubmitting] = useState(false)
@@ -81,16 +82,14 @@ export function DiffCommentDraftCard({
const labelId = useId()
const headerLabel =
startLine && startLine !== lineNumber
- ? translate(
+ ? t(
'auto.components.diff.comments.DiffCommentPopover.c845170b3b',
'Lines {{value0}}-{{value1}}',
{ value0: startLine, value1: lineNumber }
)
- : translate(
- 'auto.components.diff.comments.DiffCommentPopover.e05063cfc1',
- 'Line {{value0}}',
- { value0: lineNumber }
- )
+ : t('auto.components.diff.comments.DiffCommentPopover.e05063cfc1', 'Line {{value0}}', {
+ value0: lineNumber
+ })
const { attachDrop } = images
const hasImageUpload = !!imageUploadAddress
@@ -171,7 +170,7 @@ export function DiffCommentDraftCard({
}
if (bodyState.status === 'too-large-leading-whitespace') {
toast.error(
- translate(
+ t(
'auto.components.diff.comments.DiffCommentPopover.commentTooLarge',
'Comment is too large to submit safely.'
)
@@ -189,9 +188,7 @@ export function DiffCommentDraftCard({
} catch (err) {
console.error('Failed to submit diff comment draft:', err)
// A rejected save never reaches the caller's own failure toast, so report it here.
- toast.error(
- translate('auto.components.editor.diffCommentSaveFailed', 'Failed to save comment')
- )
+ toast.error(t('auto.components.editor.diffCommentSaveFailed', 'Failed to save comment'))
if (mountedRef.current) {
setSubmitting(false)
}
@@ -244,10 +241,7 @@ export function DiffCommentDraftCard({
className="orca-diff-comment-popover-textarea"
placeholder={
placeholder ??
- translate(
- 'auto.components.diff.comments.DiffCommentPopover.f6c449191e',
- 'Add note for the AI'
- )
+ t('auto.components.diff.comments.DiffCommentPopover.f6c449191e', 'Add note for the AI')
}
value={images.value}
onPaste={images.onPaste}
@@ -265,29 +259,23 @@ export function DiffCommentDraftCard({
{/* Footer */}
diff --git a/src/renderer/src/components/diff-comments/DiffCommentPopover.tsx b/src/renderer/src/components/diff-comments/DiffCommentPopover.tsx
index 12b3b9cdc1a..7b461c0cdc4 100644
--- a/src/renderer/src/components/diff-comments/DiffCommentPopover.tsx
+++ b/src/renderer/src/components/diff-comments/DiffCommentPopover.tsx
@@ -9,6 +9,7 @@ import {
hasBoundedCommentBodyText
} from '@/lib/comment-body-submit-state'
import { translate } from '@/i18n/i18n'
+import { useTranslate } from '@/i18n/use-translate'
import { installOpenDraftAddReviewNoteGuard } from '../editor/editor-shortcuts'
import { resolveDiffCommentPopoverTop } from './diff-comment-popover-position'
@@ -55,6 +56,7 @@ export function DiffCommentPopover({
onCancel,
onSubmit
}: Props): React.JSX.Element {
+ const t = useTranslate()
const [body, setBody] = useState('')
// Why: mirror the draft into a ref so the mousedown listener reads it fresh without re-registering each keystroke.
const bodyRef = useRef(body)
@@ -160,7 +162,7 @@ export function DiffCommentPopover({
}
if (bodyState.status === 'too-large-leading-whitespace') {
toast.error(
- translate(
+ t(
'auto.components.diff.comments.DiffCommentPopover.commentTooLarge',
'Comment is too large to submit safely.'
)
@@ -194,12 +196,12 @@ export function DiffCommentPopover({
{title ??
(startLine && startLine !== lineNumber
- ? translate(
+ ? t(
'auto.components.diff.comments.DiffCommentPopover.c845170b3b',
'Lines {{value0}}-{{value1}}',
{ value0: startLine, value1: lineNumber }
)
- : translate(
+ : t(
'auto.components.diff.comments.DiffCommentPopover.e05063cfc1',
'Line {{value0}}',
{ value0: lineNumber }
@@ -233,7 +235,7 @@ export function DiffCommentPopover({
/>
@@ -106,12 +107,12 @@ export function EmulatorDevicePicker({ devices, selectedUdid, disabled, onSelect
- {translate('emulator.picker.empty', 'No matching devices')}
+ {t('emulator.picker.empty', 'No matching devices')}
{groups.used.length > 0 && (
-
+
{groups.used.map(renderDevice)}
)}
@@ -125,7 +126,7 @@ export function EmulatorDevicePicker({ devices, selectedUdid, disabled, onSelect
selection="palette"
>
- {translate('emulator.picker.other', 'Other devices on this computer')} (
+ {t('emulator.picker.other', 'Other devices on this computer')} (
{groups.other.length})
)}
@@ -142,7 +143,7 @@ export function EmulatorDevicePicker({ devices, selectedUdid, disabled, onSelect
selection="palette"
>
- {translate('emulator.picker.unavailable', 'Unavailable runtimes')} (
+ {t('emulator.picker.unavailable', 'Unavailable runtimes')} (
{groups.unavailable.length})
)}
diff --git a/src/renderer/src/components/emulator-pane/emulator-opening-message.ts b/src/renderer/src/components/emulator-pane/emulator-opening-message.ts
index 9c1a4ccf0fe..675cb910bcd 100644
--- a/src/renderer/src/components/emulator-pane/emulator-opening-message.ts
+++ b/src/renderer/src/components/emulator-pane/emulator-opening-message.ts
@@ -1,21 +1,25 @@
import type { EmulatorSelectionRequired } from '../../../../shared/emulator-opening-preferences'
import { translate } from '@/i18n/i18n'
-export function emulatorOpeningMessage(reason: EmulatorSelectionRequired): string {
+import type { Translate } from '@/i18n/use-translate'
+export function emulatorOpeningMessage(
+ reason: EmulatorSelectionRequired,
+ t: Translate = translate
+): string {
switch (reason) {
case 'missing':
- return translate(
+ return t(
'emulator.opening.missing',
'The saved device is no longer on this computer. Choose another device.'
)
case 'unavailable':
- return translate(
+ return t(
'emulator.opening.unavailable',
'The saved device runtime is unavailable. Choose another device or install its runtime.'
)
case 'ask':
- return translate('emulator.opening.askPrompt', 'Choose a device to open.')
+ return t('emulator.opening.askPrompt', 'Choose a device to open.')
case 'first-use':
- return translate(
+ return t(
'emulator.opening.firstUse',
'Choose your first device. Orca will remember successful selections on this computer.'
)
diff --git a/src/renderer/src/components/emulator-pane/emulator-pane-toolbar.tsx b/src/renderer/src/components/emulator-pane/emulator-pane-toolbar.tsx
index 10740ccfde0..550af731d7a 100644
--- a/src/renderer/src/components/emulator-pane/emulator-pane-toolbar.tsx
+++ b/src/renderer/src/components/emulator-pane/emulator-pane-toolbar.tsx
@@ -4,7 +4,7 @@ import { Button } from '@/components/ui/button'
import { Tooltip, TooltipContent, TooltipTrigger } from '@/components/ui/tooltip'
import { cn } from '@/lib/utils'
import type { SimulatorDeviceRow } from './emulator-pane-types'
-import { translate } from '@/i18n/i18n'
+import { useTranslate } from '@/i18n/use-translate'
type EmulatorPaneToolbarProps = {
displayName: string
@@ -31,6 +31,7 @@ export function EmulatorPaneToolbar({
onHome,
onRotate
}: EmulatorPaneToolbarProps) {
+ const t = useTranslate()
// Show preview/control connection state rather than CoreSimulator boot state.
const statusLabel = isLive ? 'Connected' : loading ? 'Working…' : 'Not connected'
const subtleStatus = isLive || loading
@@ -67,22 +68,19 @@ export function EmulatorPaneToolbar({
className="h-7 gap-1 px-2 text-xs"
onClick={onRotate}
disabled={!isLive || loading}
- aria-label={translate(
+ aria-label={t(
'auto.components.emulator.pane.emulator.pane.toolbar.6bd8dff42a',
'Rotate'
)}
>
- {translate(
- 'auto.components.emulator.pane.emulator.pane.toolbar.6bd8dff42a',
- 'Rotate'
- )}
+ {t('auto.components.emulator.pane.emulator.pane.toolbar.6bd8dff42a', 'Rotate')}
- {translate('auto.components.emulator.pane.emulator.pane.toolbar.6bd8dff42a', 'Rotate')}
+ {t('auto.components.emulator.pane.emulator.pane.toolbar.6bd8dff42a', 'Rotate')}
@@ -94,16 +92,13 @@ export function EmulatorPaneToolbar({
className="size-7"
onClick={onHome}
disabled={!isLive || loading}
- aria-label={translate(
- 'auto.components.emulator.pane.emulator.pane.toolbar.e7a0d1897e',
- 'Home'
- )}
+ aria-label={t('auto.components.emulator.pane.emulator.pane.toolbar.e7a0d1897e', 'Home')}
>
- {translate('auto.components.emulator.pane.emulator.pane.toolbar.e7a0d1897e', 'Home')}
+ {t('auto.components.emulator.pane.emulator.pane.toolbar.e7a0d1897e', 'Home')}
{isLive ? (
@@ -116,7 +111,7 @@ export function EmulatorPaneToolbar({
className="size-7 text-muted-foreground hover:text-destructive"
onClick={onShutdown}
disabled={loading}
- aria-label={translate(
+ aria-label={t(
'auto.components.emulator.pane.emulator.pane.toolbar.06e10d7356',
'Shut down emulator'
)}
@@ -125,7 +120,7 @@ export function EmulatorPaneToolbar({
- {translate(
+ {t(
'auto.components.emulator.pane.emulator.pane.toolbar.06e10d7356',
'Shut down emulator'
)}
@@ -145,14 +140,8 @@ export function EmulatorPaneToolbar({
}
>
{loading
- ? translate(
- 'auto.components.emulator.pane.emulator.pane.toolbar.868c0f2938',
- 'Working…'
- )
- : translate(
- 'auto.components.emulator.pane.emulator.pane.toolbar.81b3571a07',
- 'Connect'
- )}
+ ? t('auto.components.emulator.pane.emulator.pane.toolbar.868c0f2938', 'Working…')
+ : t('auto.components.emulator.pane.emulator.pane.toolbar.81b3571a07', 'Connect')}
)}
diff --git a/src/renderer/src/components/emulator-pane/emulator-screen-aria-label.ts b/src/renderer/src/components/emulator-pane/emulator-screen-aria-label.ts
index 4d203759d79..7c19d2db851 100644
--- a/src/renderer/src/components/emulator-pane/emulator-screen-aria-label.ts
+++ b/src/renderer/src/components/emulator-pane/emulator-screen-aria-label.ts
@@ -1,16 +1,18 @@
import { translate } from '@/i18n/i18n'
+import type { Translate } from '@/i18n/use-translate'
export function getEmulatorScreenAriaLabel(
isLive: boolean,
- keyboardCaptureActive: boolean
+ keyboardCaptureActive: boolean,
+ t: Translate = translate
): string | undefined {
if (!isLive) {
return undefined
}
return keyboardCaptureActive
- ? translate(
+ ? t(
'auto.components.emulator.pane.emulator.device.frame.8f25ffaf8a',
'Emulator screen, keyboard captured. Press Escape to release.'
)
- : translate('auto.components.emulator.pane.emulator.device.frame.9406c15775', 'Emulator screen')
+ : t('auto.components.emulator.pane.emulator.device.frame.9406c15775', 'Emulator screen')
}
diff --git a/src/renderer/src/components/emulator-pane/emulator-screen-stream-content.tsx b/src/renderer/src/components/emulator-pane/emulator-screen-stream-content.tsx
index c15815bf005..55b3821f221 100644
--- a/src/renderer/src/components/emulator-pane/emulator-screen-stream-content.tsx
+++ b/src/renderer/src/components/emulator-pane/emulator-screen-stream-content.tsx
@@ -3,7 +3,7 @@ import { useEffect, type CSSProperties } from 'react'
import { useEmulatorFrameStream } from './use-emulator-frame-stream'
import { useEmulatorVideoStream } from './use-emulator-video-stream'
import { scrcpyDeviceIdFromStreamUrl } from './emulator-scrcpy-stream-url'
-import { translate } from '@/i18n/i18n'
+import { useTranslate } from '@/i18n/use-translate'
import type { VisualStreamGeometry } from './emulator-device-frame-layout'
type StreamSize = {
@@ -34,6 +34,7 @@ export function EmulatorScreenStreamContent({
streamKey,
streamRotation = 0
}: EmulatorScreenStreamContentProps) {
+ const t = useTranslate()
const androidDeviceId = scrcpyDeviceIdFromStreamUrl(previewUrl)
const video = useEmulatorVideoStream(
@@ -66,7 +67,7 @@ export function EmulatorScreenStreamContent({
ref={video.canvasRef}
className={mediaClassName}
style={mediaStyle}
- aria-label={translate(
+ aria-label={t(
'auto.components.emulator.pane.emulator.screen.stream.content.5ee64cd44e',
'Emulator screen'
)}
@@ -79,7 +80,7 @@ export function EmulatorScreenStreamContent({
- {translate(
+ {t(
'auto.components.emulator.pane.emulator.screen.stream.content.5f818f12ab',
'Connecting emulator…'
)}
@@ -115,14 +116,14 @@ export function EmulatorScreenStreamContent({
>
) : displayError ? (
- {translate(
+ {t(
'auto.components.emulator.pane.emulator.screen.stream.content.36841af608',
'Stream disconnected'
)}
) : (
- {translate(
+ {t(
'auto.components.emulator.pane.emulator.screen.stream.content.8b1a0d8694',
'Emulator preview'
)}
diff --git a/src/renderer/src/components/emulator-pane/emulator-screen-surface.tsx b/src/renderer/src/components/emulator-pane/emulator-screen-surface.tsx
index 97b83d3ce15..da8aad04955 100644
--- a/src/renderer/src/components/emulator-pane/emulator-screen-surface.tsx
+++ b/src/renderer/src/components/emulator-pane/emulator-screen-surface.tsx
@@ -13,6 +13,7 @@ import type {
StreamSize,
VisualStreamGeometry
} from './emulator-device-frame-layout'
+import { useTranslate } from '@/i18n/use-translate'
type EmulatorScreenSurfaceProps = {
frameLayout: DeviceFrameLayout | null
@@ -59,6 +60,7 @@ export function EmulatorScreenSurface({
streamKey,
streamRotation
}: EmulatorScreenSurfaceProps) {
+ const t = useTranslate()
return (
{/* Use the real streamed screen; extra in-screen chrome would duplicate iOS status bars and distort bezels. */}
state.settings && getEmulatorOpeningMode(state.settings) === 'specific'
? (state.settings.mobileEmulatorDefaultDeviceUdid ?? null)
@@ -307,7 +309,7 @@ export function useEmulatorPaneSession({
sendGesture,
sendRotate,
visualOrientation,
- displayName: formatSimulatorTabLabel(view.displayName),
+ displayName: formatSimulatorTabLabel(view.displayName, t),
previewUrl: view.previewUrl,
wsUrl: view.wsUrl,
streamKey: streamKey ?? undefined,
diff --git a/src/renderer/src/components/file-actions/OpenInContextSubmenu.tsx b/src/renderer/src/components/file-actions/OpenInContextSubmenu.tsx
index 5c1d3d37d49..cec96cedec0 100644
--- a/src/renderer/src/components/file-actions/OpenInContextSubmenu.tsx
+++ b/src/renderer/src/components/file-actions/OpenInContextSubmenu.tsx
@@ -15,7 +15,7 @@ import { getWorkspaceEditorSshHost } from '@/lib/workspace-editor-ssh-target'
import { NO_OPEN_IN_APPLICATIONS } from '@/lib/open-in-application-selection'
import type { ExecutionHostId } from '../../../../shared/execution-host'
import type { OpenInApplication } from '../../../../shared/ui-chrome-types'
-import { translate } from '@/i18n/i18n'
+import { useTranslate } from '@/i18n/use-translate'
import {
getOpenInEntryAvailability,
openOpenInAppsSettings,
@@ -33,6 +33,7 @@ export function OpenInContextSubmenu({
workspaceId,
owner
}: OpenInContextSubmenuProps): React.JSX.Element {
+ const t = useTranslate()
const openInApplications = useAppStateValue(
(s) => s.settings?.openInApplications ?? NO_OPEN_IN_APPLICATIONS
)
@@ -64,7 +65,7 @@ export function OpenInContextSubmenu({
- {translate('auto.components.sidebar.WorktreeOpenInMenu.8009ab69a6', 'Open in')}
+ {t('auto.components.sidebar.WorktreeOpenInMenu.8009ab69a6', 'Open in')}
{openInApplications.map((application) => {
@@ -73,7 +74,8 @@ export function OpenInContextSubmenu({
openInRoute.connectionId,
openInRoute.runtimeEnvironmentId,
openInRoute.ownerUnresolved,
- runtimeSshHost
+ runtimeSshHost,
+ t
)
return (
0 ? : null}
- {translate('auto.components.sidebar.WorktreeOpenInMenu.1417fd8380', 'Customize apps...')}
+ {t('auto.components.sidebar.WorktreeOpenInMenu.1417fd8380', 'Customize apps...')}
diff --git a/src/renderer/src/components/floating-terminal/FloatingTerminalEmptyState.tsx b/src/renderer/src/components/floating-terminal/FloatingTerminalEmptyState.tsx
index a0d37d295d6..7a6d0778ba3 100644
--- a/src/renderer/src/components/floating-terminal/FloatingTerminalEmptyState.tsx
+++ b/src/renderer/src/components/floating-terminal/FloatingTerminalEmptyState.tsx
@@ -2,7 +2,7 @@ import { FileText, Globe, Minus, TerminalSquare } from 'lucide-react'
import { ShortcutKeyCombo } from '@/components/ShortcutKeyCombo'
import { Button } from '@/components/ui/button'
import type { ShortcutKeyComboDetails } from '@/hooks/useShortcutLabel'
-import { translate } from '@/i18n/i18n'
+import { useTranslate } from '@/i18n/use-translate'
type FloatingTerminalEmptyStateProps = {
onNewTerminal: () => void
@@ -33,6 +33,7 @@ export function FloatingTerminalEmptyState({
openMarkdownShortcut,
closeShortcut
}: FloatingTerminalEmptyStateProps): React.JSX.Element {
+ const t = useTranslate()
return (
- {translate(
+ {t(
'auto.components.floating.terminal.FloatingTerminalPanel.3215fc73e9',
'New Terminal'
)}
@@ -66,7 +67,7 @@ export function FloatingTerminalEmptyState({
>
- {translate(
+ {t(
'auto.components.floating.terminal.FloatingTerminalPanel.629528690b',
'New Markdown Note'
)}
@@ -81,7 +82,7 @@ export function FloatingTerminalEmptyState({
>
- {translate(
+ {t(
'auto.components.floating.terminal.FloatingTerminalPanel.88ffb502e5',
'Open Markdown Note'
)}
@@ -97,7 +98,7 @@ export function FloatingTerminalEmptyState({
>
- {translate(
+ {t(
'auto.components.floating.terminal.FloatingTerminalPanel.8b07759314',
'New Browser'
)}
@@ -113,10 +114,7 @@ export function FloatingTerminalEmptyState({
>
- {translate(
- 'auto.components.floating.terminal.FloatingTerminalPanel.fc1042e92b',
- 'Minimize'
- )}
+ {t('auto.components.floating.terminal.FloatingTerminalPanel.fc1042e92b', 'Minimize')}
diff --git a/src/renderer/src/components/floating-terminal/FloatingTerminalIconContextMenu.tsx b/src/renderer/src/components/floating-terminal/FloatingTerminalIconContextMenu.tsx
index a137c55dcb4..12a6bd7a864 100644
--- a/src/renderer/src/components/floating-terminal/FloatingTerminalIconContextMenu.tsx
+++ b/src/renderer/src/components/floating-terminal/FloatingTerminalIconContextMenu.tsx
@@ -9,7 +9,7 @@ import {
} from '@/components/ui/dropdown-menu'
import { useAppStore } from '@/store'
import type { FloatingTerminalTriggerLocation } from '../../../../shared/ui-chrome-types'
-import { translate } from '@/i18n/i18n'
+import { useTranslate } from '@/i18n/use-translate'
import { useUiLanguageRevision } from '@/i18n/use-ui-language-revision'
type FloatingTerminalIconContextMenuProps = {
@@ -25,6 +25,7 @@ export function FloatingTerminalIconContextMenu({
className,
style
}: FloatingTerminalIconContextMenuProps): React.JSX.Element {
+ const t = useTranslate()
const languageRevision = useUiLanguageRevision()
const updateSettings = useAppStore((s) => s.updateSettings)
const [open, setOpen] = useState(false)
@@ -44,7 +45,7 @@ export function FloatingTerminalIconContextMenu({
if (currentLocation === 'floating-button') {
return {
icon: ,
- label: translate(
+ label: t(
'auto.components.floating.terminal.FloatingTerminalIconContextMenu.0ee79e0674',
'Move to Status Bar'
),
@@ -53,14 +54,14 @@ export function FloatingTerminalIconContextMenu({
}
return {
icon: ,
- label: translate(
+ label: t(
'auto.components.floating.terminal.FloatingTerminalIconContextMenu.763f5fa2c1',
'Move to Floating Button'
),
location: 'floating-button' as const
}
// oxlint-disable-next-line react-hooks/exhaustive-deps -- translate() reads the UI language implicitly.
- }, [currentLocation, languageRevision])
+ }, [currentLocation, languageRevision, t])
return (
<>
@@ -117,7 +118,7 @@ export function FloatingTerminalIconContextMenu({
}}
>
- {translate(
+ {t(
'auto.components.floating.terminal.FloatingTerminalIconContextMenu.8e7d775287',
'Hide Floating Workspace'
)}
diff --git a/src/renderer/src/components/floating-terminal/FloatingTerminalOrchestrationCard.tsx b/src/renderer/src/components/floating-terminal/FloatingTerminalOrchestrationCard.tsx
index a1807b1f201..7a56e2c5a6a 100644
--- a/src/renderer/src/components/floating-terminal/FloatingTerminalOrchestrationCard.tsx
+++ b/src/renderer/src/components/floating-terminal/FloatingTerminalOrchestrationCard.tsx
@@ -1,5 +1,6 @@
import { Button } from '@/components/ui/button'
import { translate } from '@/i18n/i18n'
+import type { Translate } from '@/i18n/use-translate'
type FloatingTerminalOrchestrationCardProps = {
visible: boolean
@@ -7,11 +8,10 @@ type FloatingTerminalOrchestrationCardProps = {
onEnable: () => void
}
-export function renderFloatingTerminalOrchestrationCard({
- visible,
- onDismiss,
- onEnable
-}: FloatingTerminalOrchestrationCardProps): React.JSX.Element | null {
+export function renderFloatingTerminalOrchestrationCard(
+ { visible, onDismiss, onEnable }: FloatingTerminalOrchestrationCardProps,
+ t: Translate = translate
+): React.JSX.Element | null {
if (!visible) {
return null
}
@@ -23,13 +23,13 @@ export function renderFloatingTerminalOrchestrationCard({
- {translate(
+ {t(
'auto.components.floating.terminal.FloatingTerminalPanel.2a3c5ddf5e',
'Enable orchestration'
)}
- {translate(
+ {t(
'auto.components.floating.terminal.FloatingTerminalPanel.8cf80db43b',
'Set up the Orca CLI and agent skill so agents can coordinate through Orca.'
)}
@@ -37,16 +37,10 @@ export function renderFloatingTerminalOrchestrationCard({
- {translate(
- 'auto.components.floating.terminal.FloatingTerminalPanel.adc281394d',
- 'Dismiss'
- )}
+ {t('auto.components.floating.terminal.FloatingTerminalPanel.adc281394d', 'Dismiss')}
- {translate(
- 'auto.components.floating.terminal.FloatingTerminalPanel.bbc177f98f',
- 'Enable'
- )}
+ {t('auto.components.floating.terminal.FloatingTerminalPanel.bbc177f98f', 'Enable')}
diff --git a/src/renderer/src/components/floating-terminal/FloatingTerminalOrchestrationDialog.tsx b/src/renderer/src/components/floating-terminal/FloatingTerminalOrchestrationDialog.tsx
index 5a55f22860e..0140259f836 100644
--- a/src/renderer/src/components/floating-terminal/FloatingTerminalOrchestrationDialog.tsx
+++ b/src/renderer/src/components/floating-terminal/FloatingTerminalOrchestrationDialog.tsx
@@ -17,7 +17,7 @@ import {
import { useActiveProjectSkillRuntime } from '@/hooks/useActiveProjectSkillRuntime'
import { refreshSkillFreshness } from '@/hooks/useSkillFreshness'
import { useAppStore } from '@/store'
-import { translate } from '@/i18n/i18n'
+import { useTranslate } from '@/i18n/use-translate'
type FloatingTerminalOrchestrationDialogProps = {
open: boolean
@@ -30,6 +30,7 @@ export function FloatingTerminalOrchestrationDialog({
onOpenChange,
onSetupStateChange
}: FloatingTerminalOrchestrationDialogProps): React.JSX.Element {
+ const t = useTranslate()
const activeSkillRuntime = useActiveProjectSkillRuntime()
const {
installed: orchestrationSkillDetected,
@@ -64,14 +65,14 @@ export function FloatingTerminalOrchestrationDialog({
{/* The panel hides its header, so the modal owns the title and status. */}
- {translate(
+ {t(
'auto.components.floating.terminal.FloatingTerminalOrchestrationDialog.543f325a14',
'Enable orchestration'
)}
{orchestrationSkillLoading && !orchestrationSkillDetected ? (
- {translate(
+ {t(
'auto.components.floating.terminal.FloatingTerminalOrchestrationDialog.dfd021ce46',
'Checking...'
)}
@@ -81,7 +82,7 @@ export function FloatingTerminalOrchestrationDialog({
) : (
- {translate(
+ {t(
'auto.components.floating.terminal.FloatingTerminalOrchestrationDialog.630c0ac8c8',
'Installed'
)}
@@ -89,7 +90,7 @@ export function FloatingTerminalOrchestrationDialog({
)
) : (
- {translate(
+ {t(
'auto.components.floating.terminal.FloatingTerminalOrchestrationDialog.05d7aabc20',
'Not installed'
)}
@@ -97,7 +98,7 @@ export function FloatingTerminalOrchestrationDialog({
)}
- {translate(
+ {t(
'auto.components.floating.terminal.FloatingTerminalOrchestrationDialog.6f0aed26b8',
'Install the Orca CLI and orchestration skill so agents can coordinate through Orca.'
)}
@@ -105,11 +106,11 @@ export function FloatingTerminalOrchestrationDialog({
{
useAppStore.getState().recordFeatureInteraction('agent-orchestration-setup')
}}
diff --git a/src/renderer/src/components/floating-terminal/FloatingTerminalPanel.tsx b/src/renderer/src/components/floating-terminal/FloatingTerminalPanel.tsx
index 5d1bda32ff3..9ba90adf151 100644
--- a/src/renderer/src/components/floating-terminal/FloatingTerminalPanel.tsx
+++ b/src/renderer/src/components/floating-terminal/FloatingTerminalPanel.tsx
@@ -1,10 +1,12 @@
import { renderFloatingTerminalPanelSurface } from './FloatingTerminalPanelSurface'
import type { FloatingTerminalPanelProps } from './floating-terminal-panel-types'
import { useFloatingTerminalPanelController } from './use-floating-terminal-panel-controller'
+import { useTranslate } from '../../i18n/use-translate'
export { FloatingTerminalToggleButton } from './FloatingTerminalToggleButton'
export function FloatingTerminalPanel(props: FloatingTerminalPanelProps): React.JSX.Element {
+ const t = useTranslate()
const surface = useFloatingTerminalPanelController(props)
- return renderFloatingTerminalPanelSurface(surface)
+ return renderFloatingTerminalPanelSurface(surface, t)
}
diff --git a/src/renderer/src/components/floating-terminal/FloatingTerminalPanelSurface.tsx b/src/renderer/src/components/floating-terminal/FloatingTerminalPanelSurface.tsx
index 2eb4ba440b4..cccb6558352 100644
--- a/src/renderer/src/components/floating-terminal/FloatingTerminalPanelSurface.tsx
+++ b/src/renderer/src/components/floating-terminal/FloatingTerminalPanelSurface.tsx
@@ -11,66 +11,70 @@ import { FloatingTerminalOrchestrationDialog } from './FloatingTerminalOrchestra
import { FloatingTerminalResizeHandles } from './FloatingTerminalResizeHandles'
import { FloatingTerminalWindowControls } from './FloatingTerminalWindowControls'
import type { useFloatingTerminalPanelController } from './use-floating-terminal-panel-controller'
+import type { Translate } from '@/i18n/use-translate'
const NO_ACTIVITY_TERMINAL_PORTALS: [] = []
-export function renderFloatingTerminalPanelSurface({
- open,
- onOpenChange,
- bounds,
- maximized,
- stagedBoundsRef,
- setPanelNode,
- commitUserBounds,
- reportFloatingFocusFromTarget,
- handleShortcutSurfaceKeyDown,
- handleDragStart,
- handleDragMove,
- handleDragEnd,
- handleTitlebarDoubleClick,
- surface,
- model,
- terminalItems,
- activeGroup,
- activeTerminalId,
- activeEditorUnifiedId,
- activeBrowserId,
- activeTab,
- activeTabType,
- expandedPaneByTabId,
- closeFloatingItemConfirmed,
- closeOthers,
- closeToRight,
- closeToLeft,
- createFloatingTerminalTab,
- createFloatingBrowserTab,
- createFloatingMarkdownTab,
- openFloatingMarkdownTab,
- setTabCustomTitle,
- setTabColor,
- setTabPaneExpanded,
- createBrowserTab,
- closeAllFiles,
- makePreviewFilePermanent,
- pinFile,
- toggleMaximized,
- hasVisibleFloatingTabs,
- managedBrowserCreationEnabled,
- cwd,
- panelViewportSettled,
- focusPanelForShortcuts,
- newTerminalShortcut,
- newBrowserShortcut,
- newMarkdownShortcut,
- openMarkdownShortcut,
- closeShortcut,
- showOrchestrationSetup,
- dismissOrchestrationSetup,
- setOrchestrationDialogOpen,
- previewUserBounds,
- orchestrationDialogOpen,
- refreshOrchestrationSetupVisibility
-}: ReturnType): React.JSX.Element {
+export function renderFloatingTerminalPanelSurface(
+ {
+ open,
+ onOpenChange,
+ bounds,
+ maximized,
+ stagedBoundsRef,
+ setPanelNode,
+ commitUserBounds,
+ reportFloatingFocusFromTarget,
+ handleShortcutSurfaceKeyDown,
+ handleDragStart,
+ handleDragMove,
+ handleDragEnd,
+ handleTitlebarDoubleClick,
+ surface,
+ model,
+ terminalItems,
+ activeGroup,
+ activeTerminalId,
+ activeEditorUnifiedId,
+ activeBrowserId,
+ activeTab,
+ activeTabType,
+ expandedPaneByTabId,
+ closeFloatingItemConfirmed,
+ closeOthers,
+ closeToRight,
+ closeToLeft,
+ createFloatingTerminalTab,
+ createFloatingBrowserTab,
+ createFloatingMarkdownTab,
+ openFloatingMarkdownTab,
+ setTabCustomTitle,
+ setTabColor,
+ setTabPaneExpanded,
+ createBrowserTab,
+ closeAllFiles,
+ makePreviewFilePermanent,
+ pinFile,
+ toggleMaximized,
+ hasVisibleFloatingTabs,
+ managedBrowserCreationEnabled,
+ cwd,
+ panelViewportSettled,
+ focusPanelForShortcuts,
+ newTerminalShortcut,
+ newBrowserShortcut,
+ newMarkdownShortcut,
+ openMarkdownShortcut,
+ closeShortcut,
+ showOrchestrationSetup,
+ dismissOrchestrationSetup,
+ setOrchestrationDialogOpen,
+ previewUserBounds,
+ orchestrationDialogOpen,
+ refreshOrchestrationSetupVisibility
+ }: ReturnType,
+ t?: Translate
+): React.JSX.Element {
return (
// Stay above notifications but below modals; separate shadow and border shells to keep rounded corners clean.
)}
- {renderFloatingTerminalOrchestrationCard({
- visible: showOrchestrationSetup && activeTabType === 'terminal',
- onDismiss: dismissOrchestrationSetup,
- onEnable: () => setOrchestrationDialogOpen(true)
- })}
+ {renderFloatingTerminalOrchestrationCard(
+ {
+ visible: showOrchestrationSetup && activeTabType === 'terminal',
+ onDismiss: dismissOrchestrationSetup,
+ onEnable: () => setOrchestrationDialogOpen(true)
+ },
+ t
+ )}
{!maximized && (
void
}): React.JSX.Element {
+ const t = useTranslate()
// Memoized: subscribe so a language change still repaints its labels.
useTranslation()
const shortcutLabel = useShortcutLabel('floatingTerminal.toggle')
@@ -208,16 +209,16 @@ export function FloatingTerminalToggleButton({
data-floating-terminal-toggle
aria-label={
open
- ? translate(
+ ? t(
'auto.components.floating.terminal.FloatingTerminalToggleButton.5785dd9148',
'Minimize floating workspace'
)
: showAttentionDot
- ? translate(
+ ? t(
'auto.components.floating.terminal.FloatingTerminalToggleButton.4cb418b991',
'Show floating workspace, new activity'
)
- : translate(
+ : t(
'auto.components.floating.terminal.FloatingTerminalToggleButton.3b04b065b5',
'Show floating workspace'
)
@@ -241,7 +242,7 @@ export function FloatingTerminalToggleButton({
- {translate(
+ {t(
'auto.components.floating.terminal.FloatingTerminalToggleButton.bfe7809a70',
'{{value0}} floating workspace ({{value1}})',
{ value0: open ? 'Minimize' : 'Show', value1: shortcutLabel }
diff --git a/src/renderer/src/components/floating-terminal/FloatingTerminalWindowControls.tsx b/src/renderer/src/components/floating-terminal/FloatingTerminalWindowControls.tsx
index 69622311010..d31dda2e7ae 100644
--- a/src/renderer/src/components/floating-terminal/FloatingTerminalWindowControls.tsx
+++ b/src/renderer/src/components/floating-terminal/FloatingTerminalWindowControls.tsx
@@ -12,7 +12,7 @@ import {
DEFAULT_DISABLED_TUI_AGENTS,
isTuiAgentEnabled
} from '../../../../shared/agent-launch/catalog/tui-agent-selection'
-import { translate } from '@/i18n/i18n'
+import { useTranslate } from '@/i18n/use-translate'
import { useOptionalShortcutLabel } from '@/hooks/useShortcutLabel'
type FloatingTerminalWindowControlsProps = {
@@ -33,6 +33,7 @@ export function FloatingTerminalWindowControls({
onToggleMaximized,
onMinimize
}: FloatingTerminalWindowControlsProps): React.JSX.Element {
+ const t = useTranslate()
const defaultTuiAgent = useAppStore((s) => s.settings?.defaultTuiAgent ?? null)
const setActiveTabForWorktree = useAppStore((s) => s.setActiveTabForWorktree)
const activateTab = useAppStore((s) => s.activateTab)
@@ -90,7 +91,7 @@ export function FloatingTerminalWindowControls({
variant="outline"
size="icon-xs"
className={controlButtonClassName}
- aria-label={translate(
+ aria-label={t(
'auto.components.floating.terminal.FloatingTerminalWindowControls.648352c51f',
'Open {{value0}} in floating workspace',
{ value0: defaultAgentLabel ?? defaultAgent }
@@ -101,7 +102,7 @@ export function FloatingTerminalWindowControls({
- {translate(
+ {t(
'auto.components.floating.terminal.FloatingTerminalWindowControls.648352c51f',
'Open {{value0}} in floating workspace',
{ value0: defaultAgentLabel ?? defaultAgent }
@@ -118,11 +119,11 @@ export function FloatingTerminalWindowControls({
className={controlButtonClassName}
aria-label={
maximized
- ? translate(
+ ? t(
'auto.components.floating.terminal.FloatingTerminalWindowControls.1c79cba25d',
'Restore floating workspace'
)
- : translate(
+ : t(
'auto.components.floating.terminal.FloatingTerminalWindowControls.3f4ca29961',
'Maximize floating workspace'
)
@@ -136,14 +137,14 @@ export function FloatingTerminalWindowControls({
{maximized
? withShortcutHint(
- translate(
+ t(
'auto.components.floating.terminal.FloatingTerminalWindowControls.b5686fee1e',
'Restore'
),
maximizeShortcutLabel
)
: withShortcutHint(
- translate(
+ t(
'auto.components.floating.terminal.FloatingTerminalWindowControls.109870e023',
'Maximize'
),
@@ -158,7 +159,7 @@ export function FloatingTerminalWindowControls({
variant="outline"
size="icon-xs"
className={controlButtonClassName}
- aria-label={translate(
+ aria-label={t(
'auto.components.floating.terminal.FloatingTerminalWindowControls.1bbaa0302f',
'Minimize floating workspace'
)}
@@ -169,7 +170,7 @@ export function FloatingTerminalWindowControls({
{withShortcutHint(
- translate(
+ t(
'auto.components.floating.terminal.FloatingTerminalWindowControls.2f6054342c',
'Minimize'
),
diff --git a/src/renderer/src/components/github/CommentCodeContext.tsx b/src/renderer/src/components/github/CommentCodeContext.tsx
index 06b756218f8..ab7f7b720fa 100644
--- a/src/renderer/src/components/github/CommentCodeContext.tsx
+++ b/src/renderer/src/components/github/CommentCodeContext.tsx
@@ -6,7 +6,7 @@ import { ButtonGroup } from '@/components/ui/button-group'
import { Tooltip, TooltipContent, TooltipTrigger } from '@/components/ui/tooltip'
import { detectLanguage } from '@/lib/language-detect'
import { cn } from '@/lib/utils'
-import { translate } from '@/i18n/i18n'
+import { useTranslate } from '@/i18n/use-translate'
import {
createCommentCodeContextExpansionState,
resolveCommentCodeContextExpansionState,
@@ -64,6 +64,7 @@ export function CommentCodeContext({
baseSha: string | undefined
loadPRFileContents: LoadPRFileContents
}): React.JSX.Element | null {
+ const t = useTranslate()
const [contents, setContents] = useState(null)
const [error, setError] = useState(false)
const [contextExpansionState, setContextExpansionState] = useState(() =>
@@ -158,7 +159,7 @@ export function CommentCodeContext({
return (
- {translate('auto.components.GitHubItemDialog.db61d76cd5', 'Loading code context…')}
+ {t('auto.components.GitHubItemDialog.db61d76cd5', 'Loading code context…')}
)
}
@@ -195,11 +196,11 @@ export function CommentCodeContext({
} = codeContext
const language = detectLanguage(comment.path)
const blockTooltip = shouldUseBlockRange
- ? translate(
+ ? t(
'auto.components.github.CommentCodeContext.showSurroundingCodeBlock',
'Show surrounding code block'
)
- : translate(
+ : t(
'auto.components.github.CommentCodeContext.showNearbyCodeContext',
'Show nearby code context'
)
@@ -212,17 +213,17 @@ export function CommentCodeContext({
L{from}
{to !== from
- ? translate('auto.components.GitHubItemDialog.d1c0dad471', '-L{{value0}}', {
+ ? t('auto.components.GitHubItemDialog.d1c0dad471', '-L{{value0}}', {
value0: to
})
: ''}
{(from !== commentFrom || to !== commentTo) && (
- {translate('auto.components.GitHubItemDialog.bd7be7b1fd', 'comment L')}
+ {t('auto.components.GitHubItemDialog.bd7be7b1fd', 'comment L')}
{commentFrom}
{commentTo !== commentFrom
- ? translate('auto.components.GitHubItemDialog.d1c0dad471', '-L{{value0}}', {
+ ? t('auto.components.GitHubItemDialog.d1c0dad471', '-L{{value0}}', {
value0: commentTo
})
: ''}
@@ -231,10 +232,7 @@ export function CommentCodeContext({
{(contextBefore > 0 || contextAfter > 0) && (
@@ -248,7 +246,7 @@ export function CommentCodeContext({
setContextBefore(0)
setContextAfter(0)
}}
- aria-label={translate(
+ aria-label={t(
'auto.components.GitHubItemDialog.b1574e8ac2',
'Reset code context'
)}
@@ -257,7 +255,7 @@ export function CommentCodeContext({
- {translate('auto.components.GitHubItemDialog.b1574e8ac2', 'Reset code context')}
+ {t('auto.components.GitHubItemDialog.b1574e8ac2', 'Reset code context')}
)}
@@ -274,7 +272,7 @@ export function CommentCodeContext({
Math.min(current + CODE_CONTEXT_EXPAND_STEP, commentFrom - 1)
)
}
- aria-label={translate(
+ aria-label={t(
'auto.components.GitHubItemDialog.307c98e8e3',
'Show {{value0}} more lines above',
{ value0: CODE_CONTEXT_EXPAND_STEP }
@@ -284,7 +282,7 @@ export function CommentCodeContext({
- {translate('auto.components.GitHubItemDialog.5664681624', 'Show more lines above')}
+ {t('auto.components.GitHubItemDialog.5664681624', 'Show more lines above')}
@@ -300,7 +298,7 @@ export function CommentCodeContext({
Math.min(current + CODE_CONTEXT_EXPAND_STEP, totalLines - commentTo)
)
}
- aria-label={translate(
+ aria-label={t(
'auto.components.GitHubItemDialog.showMoreLinesBelow',
'Show {{value0}} more lines below',
{ value0: CODE_CONTEXT_EXPAND_STEP }
@@ -310,7 +308,7 @@ export function CommentCodeContext({
- {translate('auto.components.GitHubItemDialog.06c06e58ba', 'Show more lines below')}
+ {t('auto.components.GitHubItemDialog.06c06e58ba', 'Show more lines below')}
diff --git a/src/renderer/src/components/github/CommentReactions.tsx b/src/renderer/src/components/github/CommentReactions.tsx
index b9c4942ccb0..ad46673ae7a 100644
--- a/src/renderer/src/components/github/CommentReactions.tsx
+++ b/src/renderer/src/components/github/CommentReactions.tsx
@@ -4,6 +4,7 @@ import { Button } from '@/components/ui/button'
import { Popover, PopoverContent, PopoverTrigger } from '@/components/ui/popover'
import { Tooltip, TooltipContent, TooltipTrigger } from '@/components/ui/tooltip'
import { translate } from '@/i18n/i18n'
+import { useTranslate, type Translate } from '@/i18n/use-translate'
import { cn } from '@/lib/utils'
import { GITHUB_REACTION_ORDER } from '@/lib/source-control/review/comments/pr-comment-reactions'
import type { GitHubReaction, GitHubReactionContent } from '../../../../shared/github/comment-types'
@@ -19,33 +20,33 @@ const REACTION_EMOJI: Record = {
eyes: '👀'
}
-function reactionLabel(content: GitHubReactionContent): string {
+function reactionLabel(content: GitHubReactionContent, t: Translate = translate): string {
switch (content) {
case '+1':
- return translate('auto.components.github.CommentReactions.reaction.thumbsUp', 'thumbs up')
+ return t('auto.components.github.CommentReactions.reaction.thumbsUp', 'thumbs up')
case '-1':
- return translate('auto.components.github.CommentReactions.reaction.thumbsDown', 'thumbs down')
+ return t('auto.components.github.CommentReactions.reaction.thumbsDown', 'thumbs down')
case 'laugh':
- return translate('auto.components.github.CommentReactions.reaction.laugh', 'laugh')
+ return t('auto.components.github.CommentReactions.reaction.laugh', 'laugh')
case 'confused':
- return translate('auto.components.github.CommentReactions.reaction.confused', 'confused')
+ return t('auto.components.github.CommentReactions.reaction.confused', 'confused')
case 'heart':
- return translate('auto.components.github.CommentReactions.reaction.heart', 'heart')
+ return t('auto.components.github.CommentReactions.reaction.heart', 'heart')
case 'hooray':
- return translate('auto.components.github.CommentReactions.reaction.hooray', 'hooray')
+ return t('auto.components.github.CommentReactions.reaction.hooray', 'hooray')
case 'rocket':
- return translate('auto.components.github.CommentReactions.reaction.rocket', 'rocket')
+ return t('auto.components.github.CommentReactions.reaction.rocket', 'rocket')
case 'eyes':
- return translate('auto.components.github.CommentReactions.reaction.eyes', 'eyes')
+ return t('auto.components.github.CommentReactions.reaction.eyes', 'eyes')
}
}
-function reactionCountLabel(count: number, reaction: string): string {
- return translate(
- 'auto.components.GitHubItemDialog.reactionCount',
- '{{count}} {{reaction}} reactions',
- { count, reaction, defaultValue_one: '{{count}} {{reaction}} reaction' }
- )
+function reactionCountLabel(count: number, reaction: string, t: Translate = translate): string {
+ return t('auto.components.GitHubItemDialog.reactionCount', '{{count}} {{reaction}} reactions', {
+ count,
+ reaction,
+ defaultValue_one: '{{count}} {{reaction}} reaction'
+ })
}
export function CommentReactions({
@@ -60,6 +61,7 @@ export function CommentReactions({
reacted: boolean
) => Promise | boolean
}): React.JSX.Element | null {
+ const t = useTranslate()
const visibleReactions = (reactions ?? []).filter((reaction) => reaction.count > 0)
const addReactionButtonRef = React.useRef(null)
const pickerGroupRef = React.useRef(null)
@@ -95,10 +97,7 @@ export function CommentReactions({
}
}
- const pickerLabel = translate(
- 'auto.components.github.CommentReactions.addReaction',
- 'Add reaction'
- )
+ const pickerLabel = t('auto.components.github.CommentReactions.addReaction', 'Add reaction')
return (
@@ -115,7 +114,7 @@ export function CommentReactions({
reaction.viewerHasReacted && 'border-ring bg-accent text-accent-foreground'
)}
aria-pressed={Boolean(reaction.viewerHasReacted)}
- aria-label={reactionCountLabel(reaction.count, reactionLabel(reaction.content))}
+ aria-label={reactionCountLabel(reaction.count, reactionLabel(reaction.content, t), t)}
onClick={() =>
void changeReaction(
reaction.content,
@@ -131,7 +130,7 @@ export function CommentReactions({
) : (
{REACTION_EMOJI[reaction.content]}
{reaction.count}
@@ -191,15 +190,15 @@ export function CommentReactions({
className={cn('text-lg', reacted && 'bg-accent text-accent-foreground')}
aria-label={
reacted
- ? translate(
+ ? t(
'auto.components.github.CommentReactions.removeNamedReaction',
'Remove {{value0}} reaction',
- { value0: reactionLabel(content) }
+ { value0: reactionLabel(content, t) }
)
- : translate(
+ : t(
'auto.components.github.CommentReactions.addNamedReaction',
'Add {{value0}} reaction',
- { value0: reactionLabel(content) }
+ { value0: reactionLabel(content, t) }
)
}
aria-pressed={reacted}
diff --git a/src/renderer/src/components/github/GitHubMarkdownComposer.tsx b/src/renderer/src/components/github/GitHubMarkdownComposer.tsx
index 155750ea489..131f2ac0207 100644
--- a/src/renderer/src/components/github/GitHubMarkdownComposer.tsx
+++ b/src/renderer/src/components/github/GitHubMarkdownComposer.tsx
@@ -26,7 +26,7 @@ import {
import { GitHubMarkdownImageInput } from './github-markdown-image-input'
import { useImageInput } from '@/components/github/use-image-input'
import type { GitHubOwnerRepo } from '../../../../shared/github/pull-request-types'
-import { translate } from '@/i18n/i18n'
+import { useTranslate } from '@/i18n/use-translate'
import { useAppStore } from '@/store'
import {
getRichMarkdownSpellcheckAttribute,
@@ -65,6 +65,7 @@ export function GitHubMarkdownComposer({
layout = 'stacked',
previewGithubRepo = null
}: GitHubMarkdownComposerProps): React.JSX.Element {
+ const t = useTranslate()
const rootRef = useRef(null)
const editorRef = useRef(null)
const applyingExternalValueRef = useRef(false)
@@ -336,7 +337,7 @@ export function GitHubMarkdownComposer({
>
- {translate(
+ {t(
'auto.components.github.GitHubMarkdownComposer.b7e4a1c902',
'Paste, drop, or click to add files'
)}
diff --git a/src/renderer/src/components/github/IssueSourceIndicator.tsx b/src/renderer/src/components/github/IssueSourceIndicator.tsx
index fadd5fb3518..00f834f398b 100644
--- a/src/renderer/src/components/github/IssueSourceIndicator.tsx
+++ b/src/renderer/src/components/github/IssueSourceIndicator.tsx
@@ -3,6 +3,7 @@ import type { GitHubOwnerRepo } from '../../../../shared/github/pull-request-typ
import RepoBadgeLabel from '@/components/repo/RepoBadgeLabel'
import { Trans } from 'react-i18next'
import { i18n, translate } from '@/i18n/i18n'
+import { useTranslate, type Translate } from '@/i18n/use-translate'
import { cn } from '@/lib/utils'
import {
githubRepoIdentityKey,
@@ -21,10 +22,10 @@ export type IssueSourceIndicatorProps = {
}
// Name the resolved repository, not remote aliases that could imply a stable UI-to-git-config mapping.
-function issueSourceTemplate(variant: 'list' | 'item'): string {
+function issueSourceTemplate(variant: 'list' | 'item', t: Translate = translate): string {
return variant === 'item'
- ? translate('auto.components.github.IssueSourceIndicator.issueFromRepo', 'Issue from ')
- : translate('auto.components.github.IssueSourceIndicator.issuesFromRepo', 'Issues from ')
+ ? t('auto.components.github.IssueSourceIndicator.issueFromRepo', 'Issue from ')
+ : t('auto.components.github.IssueSourceIndicator.issuesFromRepo', 'Issues from ')
}
export function sameGitHubOwnerRepo(
@@ -46,6 +47,7 @@ export default function IssueSourceIndicator({
localRepo,
className
}: IssueSourceIndicatorProps): React.JSX.Element | null {
+ const t = useTranslate()
if (!issues || !prs) {
return null
}
@@ -57,7 +59,7 @@ export default function IssueSourceIndicator({
host && !isDefaultGitHubHost(host)
? `${host}/${issues.owner}/${issues.repo}`
: `${issues.owner}/${issues.repo}`
- const template = issueSourceTemplate(variant)
+ const template = issueSourceTemplate(variant, t)
const label = template.replace('', slug)
return (
{density === 'compact'
? 'U'
- : translate('auto.components.github.IssueSourceSelector.30b2c9df91', 'Upstream')}
+ : t('auto.components.github.IssueSourceSelector.30b2c9df91', 'Upstream')}
{density === 'compact'
? 'O'
- : translate('auto.components.github.IssueSourceSelector.51d1608920', 'Origin')}
+ : t('auto.components.github.IssueSourceSelector.51d1608920', 'Origin')}
)
@@ -132,7 +131,7 @@ export default function IssueSourceSelector({
'
)}
diff --git a/src/renderer/src/components/github/PRAssigneesPanel.tsx b/src/renderer/src/components/github/PRAssigneesPanel.tsx
index 0e82a239c28..a4e316ccbcf 100644
--- a/src/renderer/src/components/github/PRAssigneesPanel.tsx
+++ b/src/renderer/src/components/github/PRAssigneesPanel.tsx
@@ -4,7 +4,7 @@ import { LoaderCircle, Pencil } from 'lucide-react'
import { toast } from 'sonner'
import { Popover, PopoverContent, PopoverTrigger } from '@/components/ui/popover'
import { cn } from '@/lib/utils'
-import { translate } from '@/i18n/i18n'
+import { useTranslate } from '@/i18n/use-translate'
import { useAppStore } from '@/store'
import { useImmediateMutation } from '@/hooks/useIssueMetadata'
import { useRepoAssignees } from '@/hooks/useGitHubRepoMetadata'
@@ -34,6 +34,7 @@ export function PRAssigneesPanel({
sourceContext?: TaskSourceContext | null
onMutated: () => void
}): React.JSX.Element {
+ const t = useTranslate()
const [assigneePopoverOpen, setAssigneePopoverOpen] = useState(false)
const [localAssignees, setLocalAssignees] = useState(
() => item.assignees ?? []
@@ -176,16 +177,13 @@ export function PRAssigneesPanel({
return (
-
{translate('auto.components.GitHubItemDialog.83ac703dda', 'Assignees')}
+
{t('auto.components.GitHubItemDialog.83ac703dda', 'Assignees')}
{isPending('assignees') ? (
@@ -244,7 +242,7 @@ export function PRAssigneesPanel({
{localAssignees.length === 0 ? (
- {translate('auto.components.GitHubItemDialog.c67de9e2fe', 'No one assigned')}
+ {t('auto.components.GitHubItemDialog.c67de9e2fe', 'No one assigned')}
) : (
@@ -116,10 +111,7 @@ export function GitLabFilesTab({ item, state, reviewActions }: Props) {
) : (
- {translate(
- 'auto.components.GitLabItemDialog.007423f585',
- 'Diff content unavailable.'
- )}
+ {t('auto.components.GitLabItemDialog.007423f585', 'Diff content unavailable.')}
)}
@@ -128,7 +120,7 @@ export function GitLabFilesTab({ item, state, reviewActions }: Props) {
>
) : (
- {translate('auto.components.GitLabItemDialog.808b1ca1ba', 'No changed files.')}
+ {t('auto.components.GitLabItemDialog.808b1ca1ba', 'No changed files.')}
)}
diff --git a/src/renderer/src/components/gitlab-item-dialog/gitlab-item-dialog-view.tsx b/src/renderer/src/components/gitlab-item-dialog/gitlab-item-dialog-view.tsx
index 0a87bfd6567..5e697a39dbf 100644
--- a/src/renderer/src/components/gitlab-item-dialog/gitlab-item-dialog-view.tsx
+++ b/src/renderer/src/components/gitlab-item-dialog/gitlab-item-dialog-view.tsx
@@ -10,7 +10,7 @@ import {
SheetTitle
} from '@/components/ui/sheet'
import { Tabs, TabsList, TabsTrigger } from '@/components/ui/tabs'
-import { translate } from '@/i18n/i18n'
+import { useTranslate } from '@/i18n/use-translate'
import { hasBoundedCommentBodyText } from '@/lib/comment-body-submit-state'
import { isScreenSubmitShortcut } from '@/lib/screen-submit-shortcut'
import type { GitLabWorkItem } from '../../../../shared/gitlab-types'
@@ -51,6 +51,7 @@ export function GitLabItemDialogView({
handleRefresh,
updateCommentDraft
}: Props) {
+ const t = useTranslate()
const { actionInFlight, commentDraft, commentSubmitting, details, error, loading } = state
const Icon = item?.type === 'mr' ? GitMerge : CircleDot
const prefix = item?.type === 'mr' ? '!' : '#'
@@ -74,12 +75,10 @@ export function GitLabItemDialogView({
>
- {item
- ? visibleTitle
- : translate('auto.components.GitLabItemDialog.3a051b8ade', 'Work item')}
+ {item ? visibleTitle : t('auto.components.GitLabItemDialog.3a051b8ade', 'Work item')}
- {translate('auto.components.GitLabItemDialog.30c97083c2', 'GitLab work item detail')}
+ {t('auto.components.GitLabItemDialog.30c97083c2', 'GitLab work item detail')}
@@ -97,8 +96,7 @@ export function GitLabItemDialogView({
{item.author ? (
- {translate('auto.components.GitLabItemDialog.9bfb4a24d7', 'by')}{' '}
- {item.author}
+ {t('auto.components.GitLabItemDialog.9bfb4a24d7', 'by')} {item.author}
) : null}
@@ -122,7 +120,7 @@ export function GitLabItemDialogView({
@@ -150,10 +148,10 @@ export function GitLabItemDialogView({
- {translate('auto.components.GitLabItemDialog.908d8d2a73', 'Description')}
+ {t('auto.components.GitLabItemDialog.908d8d2a73', 'Description')}
- {translate('auto.components.GitLabItemDialog.c996e2962c', 'Conversation')}
+ {t('auto.components.GitLabItemDialog.c996e2962c', 'Conversation')}
{details?.comments?.length ? (
{details.comments.length}
@@ -162,7 +160,7 @@ export function GitLabItemDialogView({
{isMR ? (
- {translate('auto.components.GitLabItemDialog.be3d291837', 'Files')}
+ {t('auto.components.GitLabItemDialog.be3d291837', 'Files')}
{details?.files?.length ? (
{details.files.length}
@@ -172,7 +170,7 @@ export function GitLabItemDialogView({
) : null}
{isMR ? (
- {translate('auto.components.GitLabItemDialog.02cbe2de44', 'Pipeline')}
+ {t('auto.components.GitLabItemDialog.02cbe2de44', 'Pipeline')}
{details?.pipelineJobs?.length ? (
{details.pipelineJobs.length}
@@ -206,7 +204,7 @@ export function GitLabItemDialogView({
updateCommentDraft(event.target.value)}
- placeholder={translate(
+ placeholder={t(
'auto.components.GitLabItemDialog.c08e1d5a57',
'Comment on {{value0}}{{value1}}…',
{ value0: prefix, value1: item.number }
@@ -233,7 +231,7 @@ export function GitLabItemDialogView({
) : (
)}
- {translate('auto.components.GitLabItemDialog.84012fa8fb', 'Comment')}
+ {t('auto.components.GitLabItemDialog.84012fa8fb', 'Comment')}
@@ -245,12 +243,12 @@ export function GitLabItemDialogView({
className="gap-1.5"
>
- {translate('auto.components.GitLabItemDialog.f2e64d1c20', 'Open in GitLab')}
+ {t('auto.components.GitLabItemDialog.f2e64d1c20', 'Open in GitLab')}
{onCreateWorkspace ? (
onCreateWorkspace(item)}>
- {translate('auto.components.GitLabItemDialog.131865e231', 'Create workspace')}
+ {t('auto.components.GitLabItemDialog.131865e231', 'Create workspace')}
) : null}
{canMerge ? (
@@ -262,7 +260,7 @@ export function GitLabItemDialogView({
{actionInFlight === 'merge' ? (
) : null}
- {translate('auto.components.GitLabItemDialog.16b3412570', 'Merge')}
+ {t('auto.components.GitLabItemDialog.16b3412570', 'Merge')}
) : null}
{canClose ? (
@@ -275,7 +273,7 @@ export function GitLabItemDialogView({
{actionInFlight === 'close' ? (
) : null}
- {translate('auto.components.GitLabItemDialog.a199eb364b', 'Close')}
+ {t('auto.components.GitLabItemDialog.a199eb364b', 'Close')}
) : null}
{canReopen ? (
@@ -288,7 +286,7 @@ export function GitLabItemDialogView({
{actionInFlight === 'reopen' ? (
) : null}
- {translate('auto.components.GitLabItemDialog.65e784c1f1', 'Reopen')}
+ {t('auto.components.GitLabItemDialog.65e784c1f1', 'Reopen')}
) : null}
diff --git a/src/renderer/src/components/gitlab-item-dialog/gitlab-pipeline-tab.tsx b/src/renderer/src/components/gitlab-item-dialog/gitlab-pipeline-tab.tsx
index cb311e7bdd2..21c25db3f5b 100644
--- a/src/renderer/src/components/gitlab-item-dialog/gitlab-pipeline-tab.tsx
+++ b/src/renderer/src/components/gitlab-item-dialog/gitlab-pipeline-tab.tsx
@@ -1,6 +1,6 @@
import { CircleDashed, ExternalLink, LoaderCircle } from 'lucide-react'
import { TabsContent } from '@/components/ui/tabs'
-import { translate } from '@/i18n/i18n'
+import { useTranslate } from '@/i18n/use-translate'
import type { GitLabWorkItem } from '../../../../shared/gitlab-types'
import { PipelineJobRow } from '../gitlab-item-dialog-parts'
import type { GitLabItemDialogState } from './use-gitlab-item-dialog-state'
@@ -20,6 +20,7 @@ type Props = {
}
export function GitLabPipelineTab({ item, state, pipelineActions }: Props) {
+ const t = useTranslate()
if (item.type !== 'mr') {
return null
}
@@ -27,7 +28,7 @@ export function GitLabPipelineTab({ item, state, pipelineActions }: Props) {
const pipelineCheck = details?.pipeline
? gitLabPipelineToPRCheck(
details.pipeline,
- translate('auto.components.GitLabItemDialog.02cbe2de44', 'Pipeline')
+ t('auto.components.GitLabItemDialog.02cbe2de44', 'Pipeline')
)
: null
const PipelineIcon = CHECK_ICON[pipelineCheck?.conclusion ?? 'neutral'] ?? CircleDashed
@@ -56,7 +57,7 @@ export function GitLabPipelineTab({ item, state, pipelineActions }: Props) {
) : (
{pipelineCheck.name}
)}
- {getCheckStatusLabel(pipelineCheck)}
+ {getCheckStatusLabel(pipelineCheck, t)}
)}
{details?.pipelineJobs?.length ? (
@@ -75,10 +76,7 @@ export function GitLabPipelineTab({ item, state, pipelineActions }: Props) {
) : !pipelineCheck && !(error && !details) ? (
- {translate(
- 'auto.components.GitLabItemDialog.f11e3e7675',
- 'No pipeline runs for this MR.'
- )}
+ {t('auto.components.GitLabItemDialog.f11e3e7675', 'No pipeline runs for this MR.')}
) : null}
diff --git a/src/renderer/src/components/gitlab/gitlab-rate-limit-display.tsx b/src/renderer/src/components/gitlab/gitlab-rate-limit-display.tsx
index 5dd835ec0a6..7a9d0c3fd58 100644
--- a/src/renderer/src/components/gitlab/gitlab-rate-limit-display.tsx
+++ b/src/renderer/src/components/gitlab/gitlab-rate-limit-display.tsx
@@ -13,19 +13,24 @@ import type {
import { getProviderRateLimitScope } from '@/components/provider-accounts/provider-account-scope'
import { ProviderHostScopeControl } from '@/components/provider-accounts/ProviderHostScopeControl'
import { translate } from '@/i18n/i18n'
+import { useIntlLocale, useTranslate, type Translate } from '@/i18n/use-translate'
import { formatUiNarrowDuration } from '@/i18n/relative-time-format'
const REFRESH_INTERVAL_MS = 60_000
-export function formatGitLabRateLimitReset(resetAt: number | null): string {
+export function formatGitLabRateLimitReset(
+ resetAt: number | null,
+ t: Translate = translate,
+ intlLocale?: string
+): string {
if (resetAt === null) {
- return translate('auto.components.gitlab.gitlab.rate.limit.display.resetUnknown', 'unknown')
+ return t('auto.components.gitlab.gitlab.rate.limit.display.resetUnknown', 'unknown')
}
const deltaSec = Math.max(0, resetAt - Math.floor(Date.now() / 1000))
if (deltaSec < 60) {
- return formatUiNarrowDuration(deltaSec, 'second')
+ return formatUiNarrowDuration(deltaSec, 'second', intlLocale)
}
- return formatUiNarrowDuration(Math.round(deltaSec / 60), 'minute')
+ return formatUiNarrowDuration(Math.round(deltaSec / 60), 'minute', intlLocale)
}
export function toneForGitLabBucket(remaining: number, limit: number): 'ok' | 'warn' | 'crit' {
@@ -117,11 +122,13 @@ function GitLabRateLimitRows({
}: {
snapshot: GitLabRateLimitSnapshot
}): React.JSX.Element {
+ const t = useTranslate()
+ const intlLocale = useIntlLocale()
const rest = snapshot.rest
if (!rest) {
return (
- {translate(
+ {t(
'auto.components.gitlab.gitlab.rate.limit.display.953f7c6062',
'This GitLab host did not return rate-limit headers.'
)}
@@ -133,7 +140,7 @@ function GitLabRateLimitRows({
- {translate('auto.components.gitlab.gitlab.rate.limit.display.0a891e8935', 'REST API')}
+ {t('auto.components.gitlab.gitlab.rate.limit.display.0a891e8935', 'REST API')}
- {translate(
+ {t(
'auto.components.gitlab.gitlab.rate.limit.display.remainingOfLimit',
'{{remaining}} of {{limit}} left, resets in {{reset}}',
{
remaining: rest.remaining,
limit: rest.limit,
- reset: formatGitLabRateLimitReset(rest.resetAt)
+ reset: formatGitLabRateLimitReset(rest.resetAt, t, intlLocale)
}
)}
@@ -165,8 +172,9 @@ export function GitLabRateLimitPanel({
/** The host chosen at the top of Settings. */
target: RuntimeClientTarget
}): React.JSX.Element {
+ const t = useTranslate()
const { snapshot, hasError, isFetching, refresh } = useGitLabRateLimitSnapshot(target)
- const budgetScope = getProviderRateLimitScope(target, 'GitLab')
+ const budgetScope = getProviderRateLimitScope(target, 'GitLab', t)
return (
@@ -174,19 +182,16 @@ export function GitLabRateLimitPanel({
- {translate(
- 'auto.components.gitlab.gitlab.rate.limit.display.14e144f7a7',
- 'GitLab API Budget'
- )}
+ {t('auto.components.gitlab.gitlab.rate.limit.display.14e144f7a7', 'GitLab API Budget')}
- {translate(
+ {t(
'auto.components.gitlab.gitlab.rate.limit.display.2f9c16d6c3',
'Orca uses REST through the GitLab CLI.'
)}
void refresh(true)}
disabled={isFetching}
- aria-label={translate(
+ aria-label={t(
'auto.components.gitlab.gitlab.rate.limit.display.a2f68645ac',
'Refresh GitLab API budget'
)}
@@ -210,7 +215,7 @@ export function GitLabRateLimitPanel({
{hasError ? (
- {translate(
+ {t(
'auto.components.gitlab.gitlab.rate.limit.display.a2d3d1fdde',
'GitLab API budget is unavailable.'
)}
@@ -219,7 +224,7 @@ export function GitLabRateLimitPanel({
) : (
- {translate(
+ {t(
'auto.components.gitlab.gitlab.rate.limit.display.ebc0e8ecf1',
'Loading GitLab API budget...'
)}
diff --git a/src/renderer/src/components/integrations/source-control/bitbucket-credentials-dialog.tsx b/src/renderer/src/components/integrations/source-control/bitbucket-credentials-dialog.tsx
index 747c4749f04..f6991cd7f9c 100644
--- a/src/renderer/src/components/integrations/source-control/bitbucket-credentials-dialog.tsx
+++ b/src/renderer/src/components/integrations/source-control/bitbucket-credentials-dialog.tsx
@@ -19,7 +19,7 @@ import { cn } from '@/lib/utils'
import { hasRemoteProviderRuntime } from '@/lib/provider-runtime-context'
import { preventOutsideDismissWhenDirty } from '@/lib/outside-dismiss-guard'
import { bitbucketConnectErrorText } from '@/lib/bitbucket-connect-error-copy'
-import { translate } from '@/i18n/i18n'
+import { useTranslate } from '@/i18n/use-translate'
const API_TOKEN_DOCS_URL = 'https://support.atlassian.com/bitbucket-cloud/docs/using-api-tokens/'
@@ -50,6 +50,7 @@ export function BitbucketCredentialsDialog({
overlayClassName,
contentClassName
}: BitbucketCredentialsDialogProps): React.JSX.Element {
+ const t = useTranslate()
const settings = useAppStore((s) => s.settings)
const mountedRef = useMountedRef()
const emailId = useId()
@@ -150,7 +151,7 @@ export function BitbucketCredentialsDialog({
setConnectError(
error instanceof Error
? error.message
- : translate(
+ : t(
'auto.components.settings.bitbucket.credentials.dialog.connectFailed',
'Connection failed'
)
@@ -179,13 +180,10 @@ export function BitbucketCredentialsDialog({
>
- {translate(
- 'auto.components.settings.bitbucket.credentials.dialog.title',
- 'Connect Bitbucket'
- )}
+ {t('auto.components.settings.bitbucket.credentials.dialog.title', 'Connect Bitbucket')}
- {translate(
+ {t(
'auto.components.settings.bitbucket.credentials.dialog.description',
'Use a Bitbucket Cloud credential to browse pull requests and build statuses. Orca verifies it before saving.'
)}
@@ -193,7 +191,7 @@ export function BitbucketCredentialsDialog({
{locked ? (
- {translate(
+ {t(
'auto.components.settings.bitbucket.credentials.dialog.environmentManaged',
'Bitbucket is already configured through ORCA_BITBUCKET_* environment variables, which take precedence. Unset them to save a credential in Orca.'
)}
@@ -215,19 +213,19 @@ export function BitbucketCredentialsDialog({
setAccessToken('')
clearErrorOnEdit()
}}
- aria-label={translate(
+ aria-label={t(
'auto.components.settings.bitbucket.credentials.dialog.authModeLabel',
'Bitbucket authentication method'
)}
>
- {translate(
+ {t(
'auto.components.settings.bitbucket.credentials.dialog.modeBasic',
'Email & API token'
)}
- {translate(
+ {t(
'auto.components.settings.bitbucket.credentials.dialog.modeToken',
'Access token'
)}
@@ -236,7 +234,7 @@ export function BitbucketCredentialsDialog({
{isTokenMode ? (