From 79894979d2a8acfa96abc13bb9a632958dfd5928 Mon Sep 17 00:00:00 2001 From: Evgenii Date: Mon, 26 Oct 2026 23:04:02 +0700 Subject: [PATCH] fix(i18n): localize Commit & Sync, Rebase, and Create PR hints in the source control dropdown (#14127) The Commit & Sync tooltip, the Rebase label/tooltip, and the Create PR blocked hints were bare English literals while the labels beside them went through translate(). Route all 25 strings through translate() with named placeholders and add the keys to the English catalog. Co-authored-by: Neil <4138956+nwparker@users.noreply.github.com> --- .../source-control-dropdown-commit-items.ts | 38 ++++++++-- .../source-control-dropdown-items.test.ts | 33 +++++++++ .../source-control-dropdown-remote-items.ts | 27 +++++-- .../source-control-dropdown-review-items.ts | 72 +++++++++++++++---- src/renderer/src/i18n/locales/en.json | 27 ++++++- 5 files changed, 173 insertions(+), 24 deletions(-) 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": {