diff --git a/src/renderer/src/components/right-sidebar/source-control-dropdown-commit-items.ts b/src/renderer/src/components/right-sidebar/source-control-dropdown-commit-items.ts index 1133121e105..4a05c0d6beb 100644 --- a/src/renderer/src/components/right-sidebar/source-control-dropdown-commit-items.ts +++ b/src/renderer/src/components/right-sidebar/source-control-dropdown-commit-items.ts @@ -73,28 +73,52 @@ export function buildCommitDropdownItems(ctx: DropdownActionContext): CommitDrop const commitSyncTitle = ((): string => { if (upstreamLoading) { - return 'Checking branch status…' + return translate( + 'auto.components.right.sidebar.source.control.dropdown.items.checkingBranchStatus', + 'Checking branch status…' + ) } if (publishBlockedByPRLoading) { - return 'Checking PR status…' + return translate( + 'auto.components.right.sidebar.source.control.dropdown.items.titleCheckingPrStatus', + 'Checking PR status…' + ) } if (publishBlockedByMergedPR) { - return 'PR is already merged' + return translate( + 'auto.components.right.sidebar.source.control.dropdown.items.titlePrAlreadyMerged', + 'PR is already merged' + ) } if (publishBlockedByDetachedHead) { - return 'Check out a branch before syncing commits' + return translate( + 'auto.components.right.sidebar.source.control.dropdown.items.titleCheckOutBranchToSync', + 'Check out a branch before syncing commits' + ) } if (!hasUpstream) { // Point to the existing Publish Branch primary instead of naming a nonexistent compound action. - return 'Publish the branch first to sync commits' + return translate( + 'auto.components.right.sidebar.source.control.dropdown.items.titlePublishBranchFirst', + 'Publish the branch first to sync commits' + ) } if (shouldForcePushWithLease) { return ( commitDisabledReason ?? - 'Use Commit & Force Push — remote only has older copies of local commits' + translate( + 'auto.components.right.sidebar.source.control.dropdown.items.titleUseForcePush', + 'Use Commit & Force Push — remote only has older copies of local commits' + ) ) } - return commitDisabledReason ?? 'Commit, then pull and push' + return ( + commitDisabledReason ?? + translate( + 'auto.components.right.sidebar.source.control.dropdown.items.titleCommitThenPullPush', + 'Commit, then pull and push' + ) + ) })() const commitSync: DropdownItem = { kind: 'commit_sync', diff --git a/src/renderer/src/components/right-sidebar/source-control-dropdown-items.test.ts b/src/renderer/src/components/right-sidebar/source-control-dropdown-items.test.ts index 7f433a29b67..e08128f9353 100644 --- a/src/renderer/src/components/right-sidebar/source-control-dropdown-items.test.ts +++ b/src/renderer/src/components/right-sidebar/source-control-dropdown-items.test.ts @@ -1,4 +1,6 @@ import { describe, expect, it } from 'vitest' +import { i18n } from '@/i18n/i18n' +import { PSEUDO_LOCALIZATION_LOCALE } from '@/i18n/pseudo-localization' import { resolveDropdownItems } from './source-control-dropdown-items' import type { DropdownActionInputs, DropdownItem } from './source-control-dropdown-item-types' import { @@ -645,6 +647,37 @@ describe('resolveDropdownItems', () => { 'Try a fast-forward pull; git may reject local commits' ) }) + + it('routes Commit & Sync, Rebase, and Create PR blocked copy through the active locale', async () => { + await i18n.changeLanguage(PSEUDO_LOCALIZATION_LOCALE) + try { + const loading = resolveDropdownItems(inputs({ rebaseBaseRef: 'origin/main' })) + const blocked = resolveDropdownItems( + inputs({ + upstreamStatus: { hasUpstream: true, ahead: 0, behind: 0 }, + hostedReviewCreation: { + provider: 'github', + review: null, + canCreate: false, + blockedReason: 'dirty', + nextAction: 'commit', + reviewLookupOutcome: 'not_found' + } + }) + ) + const loadingByKind = Object.fromEntries( + loading.filter((e) => e.kind !== 'separator').map((e) => [e.kind, e]) + ) + const blockedByKind = Object.fromEntries( + blocked.filter((e) => e.kind !== 'separator').map((e) => [e.kind, e]) + ) + expect(loadingByKind.commit_sync.title).toBe('[Checking branch status…]') + expect(loadingByKind.rebase_base.label).toBe('[Rebase from origin/main]') + expect(blockedByKind.create_pr.hint).toBe('[Commit changes first]') + } finally { + await i18n.changeLanguage('en') + } + }) }) // Exercise the real push-target resolution chain used by the component (#8196). diff --git a/src/renderer/src/components/right-sidebar/source-control-dropdown-remote-items.ts b/src/renderer/src/components/right-sidebar/source-control-dropdown-remote-items.ts index 006c47d621a..ad8336a0aec 100644 --- a/src/renderer/src/components/right-sidebar/source-control-dropdown-remote-items.ts +++ b/src/renderer/src/components/right-sidebar/source-control-dropdown-remote-items.ts @@ -165,15 +165,34 @@ export function buildRemoteDropdownItems(ctx: DropdownActionContext): RemoteDrop const hasRemoteBaseRef = rebaseBaseLabel?.includes('/') === true const rebase: DropdownItem = { kind: 'rebase_base', - label: rebaseBaseLabel ? `Rebase from ${rebaseBaseLabel}` : 'Rebase from Base', + label: rebaseBaseLabel + ? translate( + 'auto.components.right.sidebar.source.control.dropdown.items.rebaseFromNamedBase', + 'Rebase from {{baseRef}}', + { baseRef: rebaseBaseLabel } + ) + : translate( + 'auto.components.right.sidebar.source.control.dropdown.items.rebaseFromBase', + 'Rebase from Base' + ), title: ((): string => { if (!rebaseBaseLabel || !hasRemoteBaseRef) { - return 'Choose a remote base branch to rebase from' + return translate( + 'auto.components.right.sidebar.source.control.dropdown.items.rebaseChooseRemoteBase', + 'Choose a remote base branch to rebase from' + ) } if (hasDirtyLocalChanges) { - return 'Try rebasing; git may require committing or stashing local changes first' + return translate( + 'auto.components.right.sidebar.source.control.dropdown.items.rebaseDirtyHint', + 'Try rebasing; git may require committing or stashing local changes first' + ) } - return `Rebase current branch with latest commits from ${rebaseBaseLabel}` + return translate( + 'auto.components.right.sidebar.source.control.dropdown.items.rebaseWithLatestFrom', + 'Rebase current branch with latest commits from {{baseRef}}', + { baseRef: rebaseBaseLabel } + ) })(), disabled: globalBusy || !rebaseBaseRef || !hasRemoteBaseRef } diff --git a/src/renderer/src/components/right-sidebar/source-control-dropdown-review-items.ts b/src/renderer/src/components/right-sidebar/source-control-dropdown-review-items.ts index 0c5c9f129bd..c953cd2ba58 100644 --- a/src/renderer/src/components/right-sidebar/source-control-dropdown-review-items.ts +++ b/src/renderer/src/components/right-sidebar/source-control-dropdown-review-items.ts @@ -39,30 +39,78 @@ export function buildHostedReviewDropdownItems( const createBlockedHint = ((): string => { switch (hostedReviewCreation?.blockedReason) { case 'dirty': - return 'Commit changes first' + return translate( + 'auto.components.right.sidebar.source.control.dropdown.items.blockedCommitFirst', + 'Commit changes first' + ) case 'detached_head': - return 'Check out a branch first' + return translate( + 'auto.components.right.sidebar.source.control.dropdown.items.blockedCheckOutBranchFirst', + 'Check out a branch first' + ) case 'default_branch': - return 'Switch to a feature branch' + return translate( + 'auto.components.right.sidebar.source.control.dropdown.items.blockedSwitchToFeatureBranch', + 'Switch to a feature branch' + ) case 'no_upstream': - return 'Publish Branch' + return translate( + 'auto.components.right.sidebar.source.control.dropdown.items.blockedPublishBranch', + 'Publish Branch' + ) case 'needs_push': - return 'Push first' + return translate( + 'auto.components.right.sidebar.source.control.dropdown.items.blockedPushFirst', + 'Push first' + ) case 'needs_sync': - return shouldForcePushWithLease ? 'Force Push first' : 'Sync first' + return shouldForcePushWithLease + ? translate( + 'auto.components.right.sidebar.source.control.dropdown.items.blockedForcePushFirst', + 'Force Push first' + ) + : translate( + 'auto.components.right.sidebar.source.control.dropdown.items.blockedSyncFirst', + 'Sync first' + ) case 'auth_required': - return `${createReviewCopy.authInstruction} in this environment` + return translate( + 'auto.components.right.sidebar.source.control.dropdown.items.blockedAuthRequired', + '{{authInstruction}} in this environment', + { authInstruction: createReviewCopy.authInstruction } + ) case 'unsupported_provider': - return 'Unsupported provider' + return translate( + 'auto.components.right.sidebar.source.control.dropdown.items.blockedUnsupportedProvider', + 'Unsupported provider' + ) case 'existing_review': - return `A ${createReviewCopy.reviewLabel} already exists` + return translate( + 'auto.components.right.sidebar.source.control.dropdown.items.blockedExistingReview', + 'A {{reviewLabel}} already exists', + { reviewLabel: createReviewCopy.reviewLabel } + ) case 'fork_head_unsupported': - return 'Fork head unsupported' + return translate( + 'auto.components.right.sidebar.source.control.dropdown.items.blockedForkHeadUnsupported', + 'Fork head unsupported' + ) case 'base_not_on_remote': - return 'Base branch is not on the remote' + return translate( + 'auto.components.right.sidebar.source.control.dropdown.items.blockedBaseNotOnRemote', + 'Base branch is not on the remote' + ) case null: case undefined: - return upstreamLoading ? 'Checking branch status…' : 'Branch is not ready' + return upstreamLoading + ? translate( + 'auto.components.right.sidebar.source.control.dropdown.items.checkingBranchStatus', + 'Checking branch status…' + ) + : translate( + 'auto.components.right.sidebar.source.control.dropdown.items.blockedBranchNotReady', + 'Branch is not ready' + ) } })() diff --git a/src/renderer/src/i18n/locales/en.json b/src/renderer/src/i18n/locales/en.json index 05bf051ebda..2d0e8b3a217 100644 --- a/src/renderer/src/i18n/locales/en.json +++ b/src/renderer/src/i18n/locales/en.json @@ -14505,7 +14505,32 @@ "226b85a3a7": "Fetch", "323bb614aa": "Commit & Sync", "2b8e6595fd": "Commit", - "04d709801d": "Fetch from remote without merging" + "04d709801d": "Fetch from remote without merging", + "checkingBranchStatus": "Checking branch status…", + "titleCheckingPrStatus": "Checking PR status…", + "titlePrAlreadyMerged": "PR is already merged", + "titleCheckOutBranchToSync": "Check out a branch before syncing commits", + "titlePublishBranchFirst": "Publish the branch first to sync commits", + "titleUseForcePush": "Use Commit & Force Push — remote only has older copies of local commits", + "titleCommitThenPullPush": "Commit, then pull and push", + "rebaseFromNamedBase": "Rebase from {{baseRef}}", + "rebaseFromBase": "Rebase from Base", + "rebaseChooseRemoteBase": "Choose a remote base branch to rebase from", + "rebaseDirtyHint": "Try rebasing; git may require committing or stashing local changes first", + "rebaseWithLatestFrom": "Rebase current branch with latest commits from {{baseRef}}", + "blockedCommitFirst": "Commit changes first", + "blockedCheckOutBranchFirst": "Check out a branch first", + "blockedSwitchToFeatureBranch": "Switch to a feature branch", + "blockedPublishBranch": "Publish Branch", + "blockedPushFirst": "Push first", + "blockedForcePushFirst": "Force Push first", + "blockedSyncFirst": "Sync first", + "blockedAuthRequired": "{{authInstruction}} in this environment", + "blockedUnsupportedProvider": "Unsupported provider", + "blockedExistingReview": "A {{reviewLabel}} already exists", + "blockedForkHeadUnsupported": "Fork head unsupported", + "blockedBaseNotOnRemote": "Base branch is not on the remote", + "blockedBranchNotReady": "Branch is not ready" } }, "primary": {