diff --git a/src/renderer/src/components/sidebar/CacheTimer.tsx b/src/renderer/src/components/sidebar/CacheTimer.tsx
index 6702e330421..69892f9da07 100644
--- a/src/renderer/src/components/sidebar/CacheTimer.tsx
+++ b/src/renderer/src/components/sidebar/CacheTimer.tsx
@@ -9,6 +9,7 @@ import {
getPromptCacheCountdownForPane,
type PromptCacheCountdownSelection
} from './prompt-cache-timer-selection'
+import { translate } from '@/i18n/i18n'
/** Resolve the soonest-expiring Claude cache once for both row visibility and countdown rendering. */
export function usePromptCacheCountdownStartedAt(worktreeId: string, active = true): number | null {
@@ -70,8 +71,15 @@ export default function CacheTimer({
const warning = !expired && remainingMs <= 60_000
const tooltipText = expired
- ? 'The next message will re-send the full context as uncached tokens'
- : `Prompt cache expires in ${label}`
+ ? translate(
+ 'auto.components.sidebar.CacheTimer.expiredTooltip',
+ 'The next message will re-send the full context as uncached tokens'
+ )
+ : translate(
+ 'auto.components.sidebar.CacheTimer.expiresIn',
+ 'Prompt cache expires in {{time}}',
+ { time: label }
+ )
return (
diff --git a/src/renderer/src/components/sidebar/DeleteWorktreeDialogFooter.tsx b/src/renderer/src/components/sidebar/DeleteWorktreeDialogFooter.tsx
index dce8257956d..19579b70ec6 100644
--- a/src/renderer/src/components/sidebar/DeleteWorktreeDialogFooter.tsx
+++ b/src/renderer/src/components/sidebar/DeleteWorktreeDialogFooter.tsx
@@ -4,6 +4,7 @@ import { Button } from '@/components/ui/button'
import { translate } from '@/i18n/i18n'
import { useAppStore } from '@/store'
import { WorktreeForceDeleteButton } from './WorktreeForceDeleteButton'
+import { deleteWorkspacesCountLabel } from './workspace-batch-action-labels'
export function DeleteWorktreeDialogFooter({
isMainWorktree,
@@ -34,15 +35,24 @@ export function DeleteWorktreeDialogFooter({
}): JSX.Element {
const label = isDeleting
? canForceDelete
- ? 'Force Deleting...'
- : 'Deleting...'
+ ? translate(
+ 'auto.components.sidebar.DeleteWorktreeDialogFooter.forceDeleting',
+ 'Force Deleting...'
+ )
+ : translate('auto.components.sidebar.DeleteWorktreeDialogFooter.deleting', 'Deleting...')
: isBatchDelete
- ? `Delete ${worktreeCount} Workspaces`
+ ? deleteWorkspacesCountLabel(worktreeCount)
: canDeleteAllLineage
- ? `Delete ${lineageDeleteTargetCount} Workspaces`
+ ? deleteWorkspacesCountLabel(lineageDeleteTargetCount)
: canForceDelete
- ? 'Force Delete'
- : 'Delete Workspace'
+ ? translate(
+ 'auto.components.sidebar.DeleteWorktreeDialogFooter.forceDelete',
+ 'Force Delete'
+ )
+ : translate(
+ 'auto.components.sidebar.DeleteWorktreeDialogFooter.deleteWorkspace',
+ 'Delete Workspace'
+ )
return (
<>
diff --git a/src/renderer/src/components/sidebar/DeleteWorktreeDirtyChangeHint.tsx b/src/renderer/src/components/sidebar/DeleteWorktreeDirtyChangeHint.tsx
index 75deea5df63..6b5b590bbc0 100644
--- a/src/renderer/src/components/sidebar/DeleteWorktreeDirtyChangeHint.tsx
+++ b/src/renderer/src/components/sidebar/DeleteWorktreeDirtyChangeHint.tsx
@@ -42,8 +42,21 @@ export function DeleteWorktreeDirtyChangeHint({
const label =
changeCount > 0
- ? `${changeCount} uncommitted or untracked ${changeCount === 1 ? 'change' : 'changes'}`
- : 'Uncommitted or untracked changes'
+ ? changeCount === 1
+ ? translate(
+ 'auto.components.sidebar.DeleteWorktreeDirtyChangeHint.changeCount_one',
+ '1 uncommitted or untracked change',
+ { count: changeCount }
+ )
+ : translate(
+ 'auto.components.sidebar.DeleteWorktreeDirtyChangeHint.changeCount_other',
+ '{{count}} uncommitted or untracked changes',
+ { count: changeCount }
+ )
+ : translate(
+ 'auto.components.sidebar.DeleteWorktreeDirtyChangeHint.changes',
+ 'Uncommitted or untracked changes'
+ )
const warningLabel = (
<>
diff --git a/src/renderer/src/components/sidebar/ImportedWorktreesVisibilityLine.tsx b/src/renderer/src/components/sidebar/ImportedWorktreesVisibilityLine.tsx
index f548e10f722..e1420a974cb 100644
--- a/src/renderer/src/components/sidebar/ImportedWorktreesVisibilityLine.tsx
+++ b/src/renderer/src/components/sidebar/ImportedWorktreesVisibilityLine.tsx
@@ -34,7 +34,6 @@ type ImportedWorktreesVisibilityLineProps = {
}
const PREVIEW_LIMIT = 3
-const KEEP_HIDDEN_LABEL = 'Keep hidden - recover from the project menu'
const GROUP_LIMIT = 5
type ImportedWorktreePathGroup = {
@@ -42,10 +41,6 @@ type ImportedWorktreePathGroup = {
worktrees: ImportedWorktreeVisibilityPreview[]
}
-function pluralizeWorktree(count: number): string {
- return count === 1 ? 'worktree' : 'worktrees'
-}
-
function getWorktreeKey(
worktree: ImportedWorktreeVisibilityPreview,
index: number,
@@ -92,15 +87,29 @@ export default function ImportedWorktreesVisibilityLine({
const [isExpanded, setIsExpanded] = useState(false)
const [expandedGroupPathKeys, setExpandedGroupPathKeys] = useState>(new Set())
const hiddenCount = hiddenWorktrees.length
- const worktreeNoun = pluralizeWorktree(hiddenCount)
const worktreeGroups = groupWorktreesByParentPath(hiddenWorktrees)
const visibleWorktreeGroups = worktreeGroups.slice(0, GROUP_LIMIT)
const remainingGroupCount = Math.max(0, worktreeGroups.length - visibleWorktreeGroups.length)
// Host labels distinguish otherwise identical project notice rows.
const repoScopeLabel = hostContextLabel
- ? `${repoDisplayName} on ${hostContextLabel}`
+ ? translate(
+ 'auto.components.sidebar.ImportedWorktreesVisibilityLine.repoOnHost',
+ '{{repo}} on {{host}}',
+ { repo: repoDisplayName, host: hostContextLabel }
+ )
: repoDisplayName
- const keepHiddenAriaLabel = `Keep ${hiddenCount} discovered ${worktreeNoun} hidden for ${repoScopeLabel}; recover from the project menu`
+ const keepHiddenAriaLabel =
+ hiddenCount === 1
+ ? translate(
+ 'auto.components.sidebar.ImportedWorktreesVisibilityLine.keepHiddenAriaLabel_one',
+ 'Keep 1 discovered worktree hidden for {{repo}}; recover from the project menu',
+ { count: hiddenCount, repo: repoScopeLabel }
+ )
+ : translate(
+ 'auto.components.sidebar.ImportedWorktreesVisibilityLine.keepHiddenAriaLabel_other',
+ 'Keep {{count}} discovered worktrees hidden for {{repo}}; recover from the project menu',
+ { count: hiddenCount, repo: repoScopeLabel }
+ )
if (hiddenCount === 0) {
return null
@@ -108,8 +117,28 @@ export default function ImportedWorktreesVisibilityLine({
const lineText =
placement === 'pinned-fallback'
- ? `Hiding ${hiddenCount} discovered ${worktreeNoun} in ${repoScopeLabel}`
- : `Hiding ${hiddenCount} discovered ${worktreeNoun}`
+ ? hiddenCount === 1
+ ? translate(
+ 'auto.components.sidebar.ImportedWorktreesVisibilityLine.hidingInRepo_one',
+ 'Hiding 1 discovered worktree in {{repo}}',
+ { count: hiddenCount, repo: repoScopeLabel }
+ )
+ : translate(
+ 'auto.components.sidebar.ImportedWorktreesVisibilityLine.hidingInRepo_other',
+ 'Hiding {{count}} discovered worktrees in {{repo}}',
+ { count: hiddenCount, repo: repoScopeLabel }
+ )
+ : hiddenCount === 1
+ ? translate(
+ 'auto.components.sidebar.ImportedWorktreesVisibilityLine.hiding_one',
+ 'Hiding 1 discovered worktree',
+ { count: hiddenCount }
+ )
+ : translate(
+ 'auto.components.sidebar.ImportedWorktreesVisibilityLine.hiding_other',
+ 'Hiding {{count}} discovered worktrees',
+ { count: hiddenCount }
+ )
const toggleGroupExpanded = (path: string): void => {
const key = normalizeRuntimePathForComparison(path)
@@ -141,11 +170,19 @@ export default function ImportedWorktreesVisibilityLine({
size="icon-xs"
disabled={pending}
aria-expanded={isExpanded}
- aria-label={translate(
- 'auto.components.sidebar.ImportedWorktreesVisibilityLine.f54f2bec5d',
- '{{value0}} hidden worktrees for {{value1}}',
- { value0: isExpanded ? 'Collapse' : 'Expand', value1: repoScopeLabel }
- )}
+ aria-label={
+ isExpanded
+ ? translate(
+ 'auto.components.sidebar.ImportedWorktreesVisibilityLine.collapseAriaLabel',
+ 'Collapse hidden worktrees for {{repo}}',
+ { repo: repoScopeLabel }
+ )
+ : translate(
+ 'auto.components.sidebar.ImportedWorktreesVisibilityLine.expandAriaLabel',
+ 'Expand hidden worktrees for {{repo}}',
+ { repo: repoScopeLabel }
+ )
+ }
onClick={() => setIsExpanded((value) => !value)}
className="shrink-0 rounded-[4px] text-muted-foreground hover:bg-worktree-sidebar-accent hover:text-worktree-sidebar-accent-foreground"
>
@@ -185,7 +222,10 @@ export default function ImportedWorktreesVisibilityLine({
- {KEEP_HIDDEN_LABEL}
+ {translate(
+ 'auto.components.sidebar.ImportedWorktreesVisibilityLine.keepHiddenTooltip',
+ 'Keep hidden - recover from the project menu'
+ )}
) : null}
diff --git a/src/renderer/src/components/sidebar/PendingWorktreeRow.tsx b/src/renderer/src/components/sidebar/PendingWorktreeRow.tsx
index 896ae3d1e80..f3674aca6c1 100644
--- a/src/renderer/src/components/sidebar/PendingWorktreeRow.tsx
+++ b/src/renderer/src/components/sidebar/PendingWorktreeRow.tsx
@@ -10,7 +10,10 @@ import { translate } from '@/i18n/i18n'
function statusLabel(entry: PendingWorktreeCreation): string {
if (entry.status === 'error') {
- return entry.error ?? 'Creation failed'
+ return (
+ entry.error ??
+ translate('auto.components.sidebar.PendingWorktreeRow.creationFailed', 'Creation failed')
+ )
}
return getCreationProgressLabel(entry)
}
diff --git a/src/renderer/src/components/sidebar/RemoteFileBrowser.tsx b/src/renderer/src/components/sidebar/RemoteFileBrowser.tsx
index 07be1d9a3c1..e880a581205 100644
--- a/src/renderer/src/components/sidebar/RemoteFileBrowser.tsx
+++ b/src/renderer/src/components/sidebar/RemoteFileBrowser.tsx
@@ -27,7 +27,6 @@ type RemoteFileBrowserProps = (
const FILE_HINT_MS = 2000
const NO_ENTRIES: readonly DirEntry[] = []
-const FILE_HINT_TEXT = "Files can't be opened as a project"
export function RemoteFileBrowser({
targetId,
@@ -345,7 +344,10 @@ export function RemoteFileBrowser({
title={fileHint ? undefined : resolvedPath}
>
{fileHint
- ? FILE_HINT_TEXT
+ ? translate(
+ 'auto.components.sidebar.RemoteFileBrowser.fileHint',
+ "Files can't be opened as a project"
+ )
: translate(
'auto.components.sidebar.RemoteFileBrowser.971d85cc84',
'Opens as a project on this host · {{value0}}',
diff --git a/src/renderer/src/components/sidebar/RemoteFileBrowserEntryList.tsx b/src/renderer/src/components/sidebar/RemoteFileBrowserEntryList.tsx
index e5598091080..b44e47cccca 100644
--- a/src/renderer/src/components/sidebar/RemoteFileBrowserEntryList.tsx
+++ b/src/renderer/src/components/sidebar/RemoteFileBrowserEntryList.tsx
@@ -50,9 +50,6 @@ export function RemoteFileBrowserEntryList({
?.scrollIntoView?.({ block: 'nearest' })
}, [activeIndex, displayEntries])
- const displayEmptyDirCopy = isPreviewActive
- ? `${preview!.resolvedPath} is empty`
- : 'Empty directory'
const noMatchesFilter = isPreviewActive ? preview!.filter : filter
const displayNoMatchesCopy = isRemoteFileBrowserPathResolveTextTooLarge(noMatchesFilter)
? translate(
@@ -90,7 +87,13 @@ export function RemoteFileBrowserEntryList({
!preview!.error &&
!preview!.loading ? (
-
{displayEmptyDirCopy}
+
+ {translate(
+ 'auto.components.sidebar.RemoteFileBrowserEntryList.pathEmpty',
+ '{{path}} is empty',
+ { path: preview!.resolvedPath }
+ )}
+
) : !isPreviewActive && entries.length === 0 ? (
diff --git a/src/renderer/src/components/sidebar/SetupScriptPromptCard.tsx b/src/renderer/src/components/sidebar/SetupScriptPromptCard.tsx
index 65a0e1a112a..1285739c8ce 100644
--- a/src/renderer/src/components/sidebar/SetupScriptPromptCard.tsx
+++ b/src/renderer/src/components/sidebar/SetupScriptPromptCard.tsx
@@ -267,8 +267,21 @@ function SetupScriptPromptCard(): React.JSX.Element | null {
onOpenSettings: () => openLocalCommandSettings(importedRepoId, importedHostId),
description:
skippedCount > 0
- ? `${skippedCount} unsupported field${skippedCount === 1 ? '' : 's'} skipped. Saved the setup command.`
- : 'Saved the setup command.'
+ ? skippedCount === 1
+ ? translate(
+ 'auto.components.sidebar.SetupScriptPromptCard.savedWithSkippedFields_one',
+ '1 unsupported field skipped. Saved the setup command.',
+ { count: skippedCount }
+ )
+ : translate(
+ 'auto.components.sidebar.SetupScriptPromptCard.savedWithSkippedFields_other',
+ '{{count}} unsupported fields skipped. Saved the setup command.',
+ { count: skippedCount }
+ )
+ : translate(
+ 'auto.components.sidebar.SetupScriptPromptCard.savedSetupCommand',
+ 'Saved the setup command.'
+ )
})
}
} catch (error) {
diff --git a/src/renderer/src/components/sidebar/WorkspaceKanbanStatusLane.tsx b/src/renderer/src/components/sidebar/WorkspaceKanbanStatusLane.tsx
index d9cf6164944..6e169700221 100644
--- a/src/renderer/src/components/sidebar/WorkspaceKanbanStatusLane.tsx
+++ b/src/renderer/src/components/sidebar/WorkspaceKanbanStatusLane.tsx
@@ -90,7 +90,11 @@ function WorkspaceKanbanStatusLane({
undefined
)
}, [fullWorktreeIds, hasQuery, items])
- const createTooltip = `New workspace in ${status.label}`
+ const createTooltip = translate(
+ 'auto.components.sidebar.WorkspaceKanbanStatusLane.newWorkspaceIn',
+ 'New workspace in {{status}}',
+ { status: status.label }
+ )
const createButton = (