From 500c31014d524cbdb2dcf25038d94944fa6f0a04 Mon Sep 17 00:00:00 2001 From: Evgenii Date: Mon, 12 Oct 2026 19:22:28 +0700 Subject: [PATCH] fix(i18n): select whole count sentences at twelve more call sites Twelve strings were a noun plus a plural ending passed as an interpolation value, so every non-English locale got a Latin "s" glued to its own noun whenever the count was not one. Each call site now chooses between two whole sentences with a single {{count}}, the same Singular/Plural pattern #27907 uses for three Settings strings. ja, ko and zh drop the stray ending. es and fr keep what they rendered for one and get grammatical plural sentences. The "commits ahead" tooltip in the compare summary takes the wording the catalogs already have for the identical sentence, because three translations treated the ending as an object. Legacy keys stay in the catalogs. Fixes #27973 --- .../components/github/CommentReactions.tsx | 36 +++++------ .../gitlab-description-tab.tsx | 19 +++--- .../onboarding/IntegrationsStep.tsx | 16 +++-- .../commit/use-discard-confirmation.ts | 16 +++-- .../listing/branch-entry-row.tsx | 8 +-- .../listing/review-note-count-title.test.ts | 37 +++++++++++ .../listing/review-note-count-title.ts | 13 ++++ .../listing/uncommitted-entry-row.tsx | 7 +-- .../source-control/sync/compare-summary.tsx | 20 +++--- .../sidebar/add-remote-host-ssh-actions.ts | 16 +++-- .../rows/use-project-group-dialogs.ts | 21 ++++--- .../task-page/linear/IssueAttributes.tsx | 6 +- .../task-page/linear/ProjectFields.tsx | 15 +---- .../linear-attribute-count-labels.test.ts | 58 ++++++++++++++++++ .../linear/linear-attribute-count-labels.ts | 16 +++++ .../terminal-drop-upload-report.test.ts | 22 ++++++- .../terminal-drop-upload-report.ts | 16 +++-- .../terminal-native-file-drop.ts | 32 +++++++--- .../src/i18n/count-sentence-locales.test.ts | 61 +++++++++++++++++++ .../src/i18n/en-runtime-required.json | 27 ++++++-- src/renderer/src/i18n/locales/en.json | 42 ++++++++++--- src/renderer/src/i18n/locales/es.json | 42 ++++++++++--- src/renderer/src/i18n/locales/fr.json | 42 ++++++++++--- src/renderer/src/i18n/locales/ja.json | 42 ++++++++++--- src/renderer/src/i18n/locales/ko.json | 42 ++++++++++--- src/renderer/src/i18n/locales/zh.json | 42 ++++++++++--- 26 files changed, 555 insertions(+), 159 deletions(-) create mode 100644 src/renderer/src/components/right-sidebar/source-control/listing/review-note-count-title.test.ts create mode 100644 src/renderer/src/components/right-sidebar/source-control/listing/review-note-count-title.ts create mode 100644 src/renderer/src/components/task-page/linear/linear-attribute-count-labels.test.ts create mode 100644 src/renderer/src/components/task-page/linear/linear-attribute-count-labels.ts create mode 100644 src/renderer/src/i18n/count-sentence-locales.test.ts diff --git a/src/renderer/src/components/github/CommentReactions.tsx b/src/renderer/src/components/github/CommentReactions.tsx index fc9027e89ca..600ae8e973e 100644 --- a/src/renderer/src/components/github/CommentReactions.tsx +++ b/src/renderer/src/components/github/CommentReactions.tsx @@ -30,6 +30,22 @@ const REACTION_LABEL: Record = { eyes: 'eyes' } +// Why whole sentences per count: the plural ending used to travel as an +// interpolation value, which glued an English "s" onto translated nouns. +function reactionCountLabel(count: number, reaction: string): string { + return count === 1 + ? translate( + 'auto.components.GitHubItemDialog.reactionCountSingular', + '{{count}} {{reaction}} reaction', + { count, reaction } + ) + : translate( + 'auto.components.GitHubItemDialog.reactionCountPlural', + '{{count}} {{reaction}} reactions', + { count, reaction } + ) +} + export function CommentReactions({ reactions, className, @@ -97,15 +113,7 @@ export function CommentReactions({ reaction.viewerHasReacted && 'border-ring bg-accent text-accent-foreground' )} aria-pressed={Boolean(reaction.viewerHasReacted)} - aria-label={translate( - 'auto.components.GitHubItemDialog.a18f669c7a', - '{{value0}} {{value1}} reaction{{value2}}', - { - value0: reaction.count, - value1: REACTION_LABEL[reaction.content], - value2: reaction.count === 1 ? '' : 's' - } - )} + aria-label={reactionCountLabel(reaction.count, REACTION_LABEL[reaction.content])} onClick={() => void changeReaction( reaction.content, @@ -121,15 +129,7 @@ export function CommentReactions({ ) : ( {reaction.count} diff --git a/src/renderer/src/components/gitlab-item-dialog/gitlab-description-tab.tsx b/src/renderer/src/components/gitlab-item-dialog/gitlab-description-tab.tsx index 4258b323ca4..807032c8563 100644 --- a/src/renderer/src/components/gitlab-item-dialog/gitlab-description-tab.tsx +++ b/src/renderer/src/components/gitlab-item-dialog/gitlab-description-tab.tsx @@ -73,14 +73,17 @@ export function GitLabDescriptionTab({ item, state, detailsEditing, reviewAction
{approvalState.approvalsLeft === 0 ? translate('auto.components.GitLabItemDialog.22511537d2', 'Approved') - : translate( - 'auto.components.GitLabItemDialog.40c56b95e2', - '{{value0}} approval{{value1}} remaining', - { - value0: approvalState.approvalsLeft ?? 0, - value1: approvalState.approvalsLeft === 1 ? '' : 's' - } - )} + : approvalState.approvalsLeft === 1 + ? translate( + 'auto.components.GitLabItemDialog.approvalsRemainingSingular', + '{{count}} approval remaining', + { count: approvalState.approvalsLeft } + ) + : translate( + 'auto.components.GitLabItemDialog.approvalsRemainingPlural', + '{{count}} approvals remaining', + { count: approvalState.approvalsLeft ?? 0 } + )} {typeof approvalState.approvalsRequired === 'number' ? translate( 'auto.components.GitLabItemDialog.00f3bab87b', diff --git a/src/renderer/src/components/onboarding/IntegrationsStep.tsx b/src/renderer/src/components/onboarding/IntegrationsStep.tsx index c65e68099f8..05995f8aa29 100644 --- a/src/renderer/src/components/onboarding/IntegrationsStep.tsx +++ b/src/renderer/src/components/onboarding/IntegrationsStep.tsx @@ -169,11 +169,17 @@ export function LinearRow(props: { compact?: boolean } = {}): React.JSX.Element

{linearStatus.connected - ? translate( - 'auto.components.onboarding.IntegrationsStep.b08a6ac93c', - '{{value0}} workspace{{value1}} linked. Add another workspace or replace a restricted key any time.', - { value0: workspaceCount, value1: workspaceCount === 1 ? '' : 's' } - ) + ? workspaceCount === 1 + ? translate( + 'auto.components.onboarding.IntegrationsStep.linearWorkspacesLinkedSingular', + '{{count}} workspace linked. Add another workspace or replace a restricted key any time.', + { count: workspaceCount } + ) + : translate( + 'auto.components.onboarding.IntegrationsStep.linearWorkspacesLinkedPlural', + '{{count}} workspaces linked. Add another workspace or replace a restricted key any time.', + { count: workspaceCount } + ) : translate( 'auto.components.onboarding.IntegrationsStep.4983ae7433', 'Add Linear access with a Personal API key. Full-access keys can show every team the key owner can access.' diff --git a/src/renderer/src/components/right-sidebar/source-control/commit/use-discard-confirmation.ts b/src/renderer/src/components/right-sidebar/source-control/commit/use-discard-confirmation.ts index d3f164b2f34..56d83937fa8 100644 --- a/src/renderer/src/components/right-sidebar/source-control/commit/use-discard-confirmation.ts +++ b/src/renderer/src/components/right-sidebar/source-control/commit/use-discard-confirmation.ts @@ -126,11 +126,17 @@ export function useSourceControlDiscardConfirmation({ const sample = result.failed.slice(0, 3).join(', ') const more = result.failed.length > 3 ? `, +${result.failed.length - 3} more` : '' toast.error( - translate( - 'auto.components.right.sidebar.SourceControl.8eb3782a0c', - 'Failed to discard {{value0}} file{{value1}}', - { value0: result.failed.length, value1: result.failed.length === 1 ? '' : 's' } - ), + result.failed.length === 1 + ? translate( + 'auto.components.right.sidebar.SourceControl.discardFailedSingular', + 'Failed to discard {{count}} file', + { count: result.failed.length } + ) + : translate( + 'auto.components.right.sidebar.SourceControl.discardFailedPlural', + 'Failed to discard {{count}} files', + { count: result.failed.length } + ), { description: firstMsg ? translate( diff --git a/src/renderer/src/components/right-sidebar/source-control/listing/branch-entry-row.tsx b/src/renderer/src/components/right-sidebar/source-control/listing/branch-entry-row.tsx index 68fd0a91223..c7c1d433411 100644 --- a/src/renderer/src/components/right-sidebar/source-control/listing/branch-entry-row.tsx +++ b/src/renderer/src/components/right-sidebar/source-control/listing/branch-entry-row.tsx @@ -4,9 +4,9 @@ import { getFileTypeIcon } from '@/lib/file-type-icons' import { basename, dirname, joinPath } from '@/lib/path' import { WORKSPACE_FILE_PATH_MIME } from '@/lib/workspace-file-drag' import { writeWorkspaceFileDragSourceForWorkspace } from '@/lib/workspace-file-drag-source' -import { translate } from '@/i18n/i18n' import type { GitBranchChangeEntry } from '../../../../../../shared/git-diff-compare-types' import { DiffLineCounts } from './diff-line-counts' +import { reviewNoteCountTitle } from './review-note-count-title' import { SourceControlEntryContextMenu } from './entry-context-menu' import { toPermanentSourceControlRowOpenEvent, type SourceControlRowOpenEvent } from './split-open' import { SOURCE_CONTROL_TREE_FILE_PADDING_PX, SOURCE_CONTROL_TREE_INDENT_PX } from './row-layout' @@ -76,11 +76,7 @@ export function BranchEntryRow({ {commentCount > 0 && ( {commentCount} diff --git a/src/renderer/src/components/right-sidebar/source-control/listing/review-note-count-title.test.ts b/src/renderer/src/components/right-sidebar/source-control/listing/review-note-count-title.test.ts new file mode 100644 index 00000000000..da3f5235a9e --- /dev/null +++ b/src/renderer/src/components/right-sidebar/source-control/listing/review-note-count-title.test.ts @@ -0,0 +1,37 @@ +import { beforeEach, describe, expect, it, vi } from 'vitest' +import { reviewNoteCountTitle } from './review-note-count-title' + +const mocks = vi.hoisted(() => ({ + translate: vi.fn((key: string, fallback: string) => `${key}:${fallback}`) +})) + +vi.mock('@/i18n/i18n', () => ({ + translate: mocks.translate +})) + +describe('reviewNoteCountTitle', () => { + beforeEach(() => { + mocks.translate.mockClear() + }) + + it('uses the singular sentence only for exactly one note', () => { + expect(reviewNoteCountTitle(1)).toBe( + 'auto.components.right.sidebar.SourceControl.noteCountSingular:{{count}} note' + ) + expect(reviewNoteCountTitle(4)).toBe( + 'auto.components.right.sidebar.SourceControl.noteCountPlural:{{count}} notes' + ) + expect(mocks.translate).toHaveBeenNthCalledWith( + 1, + 'auto.components.right.sidebar.SourceControl.noteCountSingular', + '{{count}} note', + { count: 1 } + ) + expect(mocks.translate).toHaveBeenNthCalledWith( + 2, + 'auto.components.right.sidebar.SourceControl.noteCountPlural', + '{{count}} notes', + { count: 4 } + ) + }) +}) diff --git a/src/renderer/src/components/right-sidebar/source-control/listing/review-note-count-title.ts b/src/renderer/src/components/right-sidebar/source-control/listing/review-note-count-title.ts new file mode 100644 index 00000000000..3442cb17a1c --- /dev/null +++ b/src/renderer/src/components/right-sidebar/source-control/listing/review-note-count-title.ts @@ -0,0 +1,13 @@ +import { translate } from '@/i18n/i18n' + +// Why whole sentences per count: the plural ending used to travel as an +// interpolation value, which glued an English "s" onto translated nouns. +export function reviewNoteCountTitle(count: number): string { + return count === 1 + ? translate('auto.components.right.sidebar.SourceControl.noteCountSingular', '{{count}} note', { + count + }) + : translate('auto.components.right.sidebar.SourceControl.noteCountPlural', '{{count}} notes', { + count + }) +} diff --git a/src/renderer/src/components/right-sidebar/source-control/listing/uncommitted-entry-row.tsx b/src/renderer/src/components/right-sidebar/source-control/listing/uncommitted-entry-row.tsx index d733b37e7cd..8e1c22e2256 100644 --- a/src/renderer/src/components/right-sidebar/source-control/listing/uncommitted-entry-row.tsx +++ b/src/renderer/src/components/right-sidebar/source-control/listing/uncommitted-entry-row.tsx @@ -11,6 +11,7 @@ import { ActionButton } from './action-button' import { ConflictBadge } from './conflict-badge' import { getLocalizedConflictKindLabel } from './conflict-label' import { DiffLineCounts } from './diff-line-counts' +import { reviewNoteCountTitle } from './review-note-count-title' import { SourceControlEntryContextMenu } from './entry-context-menu' import { canDiscardStatusEntry, canStageStatusEntry, canUnstageStatusEntry } from './entry-actions' import { isSubmoduleWorktreeOnlyChange } from '../commit/discard-all-sequence' @@ -185,11 +186,7 @@ export const UncommittedEntryRow = React.memo(function UncommittedEntryRow({ // Why: surface a per-row marker so files with review notes are visible without opening the Notes tab. {commentCount} diff --git a/src/renderer/src/components/right-sidebar/source-control/sync/compare-summary.tsx b/src/renderer/src/components/right-sidebar/source-control/sync/compare-summary.tsx index 93fd343a624..8e59e012d9f 100644 --- a/src/renderer/src/components/right-sidebar/source-control/sync/compare-summary.tsx +++ b/src/renderer/src/components/right-sidebar/source-control/sync/compare-summary.tsx @@ -110,13 +110,19 @@ export function CompareSummary({ const commitsAhead = summary.commitsAhead const showCommitsAhead = typeof commitsAhead === 'number' && commitsAhead > 0 - const commitsAheadTitle = showCommitsAhead - ? translate( - 'auto.components.right.sidebar.source.control.compare.summary.dd72a6fd37', - '{{value0}} commit{{value1}} ahead of {{value2}}', - { value0: commitsAhead, value1: commitsAhead === 1 ? '' : 's', value2: summary.baseRef } - ) - : undefined + const commitsAheadTitle = !showCommitsAhead + ? undefined + : commitsAhead === 1 + ? translate( + 'auto.components.right.sidebar.source.control.compare.summary.commitsAheadSingular', + '{{count}} commit ahead of {{ref}}', + { count: commitsAhead, ref: summary.baseRef } + ) + : translate( + 'auto.components.right.sidebar.source.control.compare.summary.commitsAheadPlural', + '{{count}} commits ahead of {{ref}}', + { count: commitsAhead, ref: summary.baseRef } + ) if (!showCommitsAhead) { return null diff --git a/src/renderer/src/components/sidebar/add-remote-host-ssh-actions.ts b/src/renderer/src/components/sidebar/add-remote-host-ssh-actions.ts index 76ac27ab2f9..072773f8e3b 100644 --- a/src/renderer/src/components/sidebar/add-remote-host-ssh-actions.ts +++ b/src/renderer/src/components/sidebar/add-remote-host-ssh-actions.ts @@ -183,11 +183,17 @@ export async function addAllSshConfigHostsToOrca({ return { kind: 'already-synced' } } toast.success( - translate( - 'auto.components.sidebar.AddRemoteHostDialog.sshImportSynced', - 'Added {{value0}} host{{value1}} to Orca.', - { value0: result.targets.length, value1: result.targets.length > 1 ? 's' : '' } - ) + result.targets.length > 1 + ? translate( + 'auto.components.sidebar.AddRemoteHostDialog.sshImportSyncedPlural', + 'Added {{count}} hosts to Orca.', + { count: result.targets.length } + ) + : translate( + 'auto.components.sidebar.AddRemoteHostDialog.sshImportSyncedSingular', + 'Added {{count}} host to Orca.', + { count: result.targets.length } + ) ) return { kind: 'added', count: result.targets.length } } catch (error) { diff --git a/src/renderer/src/components/sidebar/worktree-list/rows/use-project-group-dialogs.ts b/src/renderer/src/components/sidebar/worktree-list/rows/use-project-group-dialogs.ts index c9c3a87b2ac..ae0d8f68dcf 100644 --- a/src/renderer/src/components/sidebar/worktree-list/rows/use-project-group-dialogs.ts +++ b/src/renderer/src/components/sidebar/worktree-list/rows/use-project-group-dialogs.ts @@ -50,15 +50,18 @@ function reportProjectGroupDeleteFailures(result: { 'Some projects could not be removed from Orca' ), { - description: translate( - 'auto.components.sidebar.WorktreeList.f94466bc39', - '{{value0}} of {{value1}} contained project{{value2}} remained after deleting the group.', - { - value0: result.failedProjectRemovals.length, - value1: requestedCount, - value2: requestedCount === 1 ? '' : 's' - } - ) + description: + requestedCount === 1 + ? translate( + 'auto.components.sidebar.WorktreeList.groupProjectsRemainedSingular', + '{{remaining}} of {{count}} contained project remained after deleting the group.', + { remaining: result.failedProjectRemovals.length, count: requestedCount } + ) + : translate( + 'auto.components.sidebar.WorktreeList.groupProjectsRemainedPlural', + '{{remaining}} of {{count}} contained projects remained after deleting the group.', + { remaining: result.failedProjectRemovals.length, count: requestedCount } + ) } ) } diff --git a/src/renderer/src/components/task-page/linear/IssueAttributes.tsx b/src/renderer/src/components/task-page/linear/IssueAttributes.tsx index 21ec2584b0b..11182ecba6d 100644 --- a/src/renderer/src/components/task-page/linear/IssueAttributes.tsx +++ b/src/renderer/src/components/task-page/linear/IssueAttributes.tsx @@ -1,6 +1,7 @@ import type { TaskPageComposerActionsModel } from '../../use-task-page-composer-actions' import { Popover, PopoverTrigger, PopoverContent } from '@/components/ui/popover' import { translate } from '@/i18n/i18n' +import { linearLabelCountLabel } from './linear-attribute-count-labels' import { ChevronDown, LoaderCircle, Check, UserRound, FolderKanban, Tag } from 'lucide-react' import { LinearPriorityIcon } from '@/components/linear-priority-icon' export function TaskPageLinearIssueAttributes({ @@ -315,10 +316,7 @@ export function TaskPageLinearIssueAttributes({ {newLinearIssueLabelIds.length === 0 ? translate('auto.components.TaskPage.d0ca4aa1d0', 'Labels') - : translate('auto.components.TaskPage.eff9800d4b', '{{value0}} label{{value1}}', { - value0: newLinearIssueLabelIds.length, - value1: newLinearIssueLabelIds.length > 1 ? 's' : '' - })} + : linearLabelCountLabel(newLinearIssueLabelIds.length)} diff --git a/src/renderer/src/components/task-page/linear/ProjectFields.tsx b/src/renderer/src/components/task-page/linear/ProjectFields.tsx index 20683773123..6f2ac4e10db 100644 --- a/src/renderer/src/components/task-page/linear/ProjectFields.tsx +++ b/src/renderer/src/components/task-page/linear/ProjectFields.tsx @@ -1,6 +1,7 @@ import { ImeInput } from '@/lib/ime-text-field' import type { TaskPageComposerActionsModel } from '../../use-task-page-composer-actions' import { translate } from '@/i18n/i18n' +import { linearLabelCountLabel, linearMemberCountLabel } from './linear-attribute-count-labels' import { Popover, PopoverTrigger, PopoverContent } from '@/components/ui/popover' import { LinearPriorityIcon } from '@/components/linear-priority-icon' import { getLinearPriorityLabel } from '@/components/task-page-localized-options' @@ -188,14 +189,7 @@ export function TaskPageLinearProjectFields({ {newLinearProjectMemberIds.length === 0 ? translate('auto.components.TaskPage.d6cda23ef1', 'Members') - : translate( - 'auto.components.TaskPage.7719d8daa9', - '{{value0}} member{{value1}}', - { - value0: newLinearProjectMemberIds.length, - value1: newLinearProjectMemberIds.length > 1 ? 's' : '' - } - )} + : linearMemberCountLabel(newLinearProjectMemberIds.length)} @@ -260,10 +254,7 @@ export function TaskPageLinearProjectFields({ {newLinearProjectLabelIds.length === 0 ? translate('auto.components.TaskPage.d0ca4aa1d0', 'Labels') - : translate('auto.components.TaskPage.eff9800d4b', '{{value0}} label{{value1}}', { - value0: newLinearProjectLabelIds.length, - value1: newLinearProjectLabelIds.length > 1 ? 's' : '' - })} + : linearLabelCountLabel(newLinearProjectLabelIds.length)} diff --git a/src/renderer/src/components/task-page/linear/linear-attribute-count-labels.test.ts b/src/renderer/src/components/task-page/linear/linear-attribute-count-labels.test.ts new file mode 100644 index 00000000000..460c720d2af --- /dev/null +++ b/src/renderer/src/components/task-page/linear/linear-attribute-count-labels.test.ts @@ -0,0 +1,58 @@ +import { beforeEach, describe, expect, it, vi } from 'vitest' +import { linearLabelCountLabel, linearMemberCountLabel } from './linear-attribute-count-labels' + +const mocks = vi.hoisted(() => ({ + translate: vi.fn((key: string, fallback: string) => `${key}:${fallback}`) +})) + +vi.mock('@/i18n/i18n', () => ({ + translate: mocks.translate +})) + +describe('Linear attribute count labels', () => { + beforeEach(() => { + mocks.translate.mockClear() + }) + + it('uses the singular sentence for one label and the plural one above that', () => { + expect(linearLabelCountLabel(1)).toBe( + 'auto.components.TaskPage.labelCountSingular:{{count}} label' + ) + expect(linearLabelCountLabel(3)).toBe( + 'auto.components.TaskPage.labelCountPlural:{{count}} labels' + ) + expect(mocks.translate).toHaveBeenNthCalledWith( + 1, + 'auto.components.TaskPage.labelCountSingular', + '{{count}} label', + { count: 1 } + ) + expect(mocks.translate).toHaveBeenNthCalledWith( + 2, + 'auto.components.TaskPage.labelCountPlural', + '{{count}} labels', + { count: 3 } + ) + }) + + it('uses the singular sentence for one member and the plural one above that', () => { + expect(linearMemberCountLabel(1)).toBe( + 'auto.components.TaskPage.memberCountSingular:{{count}} member' + ) + expect(linearMemberCountLabel(2)).toBe( + 'auto.components.TaskPage.memberCountPlural:{{count}} members' + ) + expect(mocks.translate).toHaveBeenNthCalledWith( + 1, + 'auto.components.TaskPage.memberCountSingular', + '{{count}} member', + { count: 1 } + ) + expect(mocks.translate).toHaveBeenNthCalledWith( + 2, + 'auto.components.TaskPage.memberCountPlural', + '{{count}} members', + { count: 2 } + ) + }) +}) diff --git a/src/renderer/src/components/task-page/linear/linear-attribute-count-labels.ts b/src/renderer/src/components/task-page/linear/linear-attribute-count-labels.ts new file mode 100644 index 00000000000..a63e65a0f51 --- /dev/null +++ b/src/renderer/src/components/task-page/linear/linear-attribute-count-labels.ts @@ -0,0 +1,16 @@ +import { translate } from '@/i18n/i18n' + +// Why whole sentences per count: the plural ending used to travel as an +// interpolation value, which glued an English "s" onto translated nouns. +// Both callers show these only for a count of one or more. +export function linearLabelCountLabel(count: number): string { + return count > 1 + ? translate('auto.components.TaskPage.labelCountPlural', '{{count}} labels', { count }) + : translate('auto.components.TaskPage.labelCountSingular', '{{count}} label', { count }) +} + +export function linearMemberCountLabel(count: number): string { + return count > 1 + ? translate('auto.components.TaskPage.memberCountPlural', '{{count}} members', { count }) + : translate('auto.components.TaskPage.memberCountSingular', '{{count}} member', { count }) +} diff --git a/src/renderer/src/components/terminal-pane/terminal-drop-upload-report.test.ts b/src/renderer/src/components/terminal-pane/terminal-drop-upload-report.test.ts index 8448a117b83..63666fd00a1 100644 --- a/src/renderer/src/components/terminal-pane/terminal-drop-upload-report.test.ts +++ b/src/renderer/src/components/terminal-pane/terminal-drop-upload-report.test.ts @@ -32,12 +32,32 @@ describe('reportTerminalDropUploadSkipsAndFailures', () => { reportTerminalDropUploadSkipsAndFailures([{ reason: 'symlink' }, { reason: 'too_large' }], []) const mixedSkipKey = mocks.translate.mock.calls[0]?.[0] - expect(symlinkOnlyKey).toBe('auto.components.terminal.pane.terminal.drop.handler.53f015fd85') + expect(symlinkOnlyKey).toBe( + 'auto.components.terminal.pane.terminal.drop.handler.skippedSymlinkSingular' + ) expect(mixedSkipKey).toBe('auto.components.terminal.pane.terminal.drop.handler.b4cf68e889') expect(symlinkOnlyKey).not.toBe(mixedSkipKey) expect(toast.message).toHaveBeenCalledTimes(2) }) + it('picks a whole sentence per symlink count instead of passing a plural ending', () => { + reportTerminalDropUploadSkipsAndFailures([{ reason: 'symlink' }], []) + reportTerminalDropUploadSkipsAndFailures([{ reason: 'symlink' }, { reason: 'symlink' }], []) + + expect(mocks.translate.mock.calls).toEqual([ + [ + 'auto.components.terminal.pane.terminal.drop.handler.skippedSymlinkSingular', + 'Skipped {{count}} symlink.', + { count: 1 } + ], + [ + 'auto.components.terminal.pane.terminal.drop.handler.skippedSymlinkPlural', + 'Skipped {{count}} symlinks.', + { count: 2 } + ] + ]) + }) + it('reports upload failures without leaking individual paths', () => { reportTerminalDropUploadSkipsAndFailures([], [{ reason: '/secret/project/file.txt' }]) diff --git a/src/renderer/src/components/terminal-pane/terminal-drop-upload-report.ts b/src/renderer/src/components/terminal-pane/terminal-drop-upload-report.ts index b74e677aadf..07d46572250 100644 --- a/src/renderer/src/components/terminal-pane/terminal-drop-upload-report.ts +++ b/src/renderer/src/components/terminal-pane/terminal-drop-upload-report.ts @@ -13,11 +13,17 @@ export function reportTerminalDropUploadSkipsAndFailures( const commonReason = sharedReason(skipped) toast.message( allSymlinks - ? translate( - 'auto.components.terminal.pane.terminal.drop.handler.53f015fd85', - 'Skipped {{value0}} symlink{{value1}}.', - { value0: skipped.length, value1: skipped.length === 1 ? '' : 's' } - ) + ? skipped.length === 1 + ? translate( + 'auto.components.terminal.pane.terminal.drop.handler.skippedSymlinkSingular', + 'Skipped {{count}} symlink.', + { count: skipped.length } + ) + : translate( + 'auto.components.terminal.pane.terminal.drop.handler.skippedSymlinkPlural', + 'Skipped {{count}} symlinks.', + { count: skipped.length } + ) : translate( 'auto.components.terminal.pane.terminal.drop.handler.b4cf68e889', 'Skipped {{value0}} {{value1}}.', diff --git a/src/renderer/src/components/terminal-pane/terminal-native-file-drop.ts b/src/renderer/src/components/terminal-pane/terminal-native-file-drop.ts index ec512423fd9..bba3a823a36 100644 --- a/src/renderer/src/components/terminal-pane/terminal-native-file-drop.ts +++ b/src/renderer/src/components/terminal-pane/terminal-native-file-drop.ts @@ -128,11 +128,17 @@ async function uploadRuntimeDropPaths( const targetShell = getTerminalTargetShellForWorktreePath(args.worktreePath) const destinationDir = joinRuntimeTerminalDropDir(args.worktreePath) const pending = toast.loading( - translate( - 'auto.components.terminal.pane.terminal.drop.handler.29c031b49a', - 'Uploading {{value0}} file{{value1}} to runtime…', - { value0: args.dataPaths.length, value1: args.dataPaths.length === 1 ? '' : 's' } - ) + args.dataPaths.length === 1 + ? translate( + 'auto.components.terminal.pane.terminal.drop.handler.uploadingFileSingular', + 'Uploading {{count}} file to runtime…', + { count: args.dataPaths.length } + ) + : translate( + 'auto.components.terminal.pane.terminal.drop.handler.uploadingFilePlural', + 'Uploading {{count}} files to runtime…', + { count: args.dataPaths.length } + ) ) try { const { results } = await importExternalPathsToRuntime( @@ -200,11 +206,17 @@ async function uploadRemoteDropPaths( args: NativeDropFlowArgs & { connectionId: string; targetShell: 'posix' | 'windows' } ): Promise { const pending = toast.loading( - translate( - 'auto.components.terminal.pane.terminal.drop.handler.29c031b49a', - 'Uploading {{value0}} file{{value1}} to remote…', - { value0: args.dataPaths.length, value1: args.dataPaths.length === 1 ? '' : 's' } - ) + args.dataPaths.length === 1 + ? translate( + 'auto.components.terminal.pane.terminal.drop.handler.uploadingFileSingular', + 'Uploading {{count}} file to remote…', + { count: args.dataPaths.length } + ) + : translate( + 'auto.components.terminal.pane.terminal.drop.handler.uploadingFilePlural', + 'Uploading {{count}} files to remote…', + { count: args.dataPaths.length } + ) ) try { const { resolvedPaths, skipped, failed } = await window.api.fs.resolveDroppedPathsForAgent({ diff --git a/src/renderer/src/i18n/count-sentence-locales.test.ts b/src/renderer/src/i18n/count-sentence-locales.test.ts new file mode 100644 index 00000000000..a7604878bfc --- /dev/null +++ b/src/renderer/src/i18n/count-sentence-locales.test.ts @@ -0,0 +1,61 @@ +import { beforeEach, describe, expect, it } from 'vitest' + +import { i18n, setRendererPluginLanguagePacks } from './i18n' + +// Why: these sentences used to be a noun plus an English plural ending passed as an +// interpolation value, so Japanese, Korean and Chinese rendered a Latin "s" after the +// noun. Each is now a pair of whole sentences chosen by count; this guards what every +// bundled locale actually renders for one and for several. +const COUNT_SENTENCES: readonly (readonly [string, Record])[] = [ + ['auto.components.GitHubItemDialog.reactionCount', { reaction: 'heart' }], + ['auto.components.GitLabItemDialog.approvalsRemaining', {}], + ['auto.components.TaskPage.labelCount', {}], + ['auto.components.TaskPage.memberCount', {}], + ['auto.components.terminal.pane.terminal.drop.handler.skippedSymlink', {}], + ['auto.components.terminal.pane.terminal.drop.handler.uploadingFile', {}], + ['auto.components.sidebar.WorktreeList.groupProjectsRemained', { remaining: 1 }], + ['auto.components.sidebar.AddRemoteHostDialog.sshImportSynced', {}], + ['auto.components.right.sidebar.SourceControl.discardFailed', {}], + ['auto.components.right.sidebar.SourceControl.noteCount', {}], + ['auto.components.right.sidebar.source.control.compare.summary.commitsAhead', { ref: 'main' }], + ['auto.components.onboarding.IntegrationsStep.linearWorkspacesLinked', {}] +] + +const LATIN_S_AFTER_CJK = /[\u3040-\u30ff\u4e00-\u9fff\uac00-\ud7af]\s?s(?![a-z])/i + +function renderCountSentence( + baseKey: string, + count: number, + values: Record +): string { + const key = `${baseKey}${count === 1 ? 'Singular' : 'Plural'}` + return i18n.t(key, { ...values, count, defaultValue: '' }) +} + +describe('count sentences in the bundled locales', () => { + beforeEach(async () => { + setRendererPluginLanguagePacks([]) + await i18n.changeLanguage('en') + }) + + it.each(['es', 'fr', 'ja', 'ko', 'zh'])( + '%s renders whole sentences for one and several', + async (locale) => { + await i18n.changeLanguage(locale) + for (const [baseKey, values] of COUNT_SENTENCES) { + for (const count of [1, 3]) { + const text = renderCountSentence(baseKey, count, values) + expect(text, `${baseKey} (${count})`).toContain(String(count)) + expect(text, `${baseKey} (${count})`).not.toContain('{{') + } + } + } + ) + + it.each(['ja', 'ko', 'zh'])('%s gets no Latin plural ending on its nouns', async (locale) => { + await i18n.changeLanguage(locale) + for (const [baseKey, values] of COUNT_SENTENCES) { + expect(renderCountSentence(baseKey, 3, values), baseKey).not.toMatch(LATIN_S_AFTER_CJK) + } + }) +}) diff --git a/src/renderer/src/i18n/en-runtime-required.json b/src/renderer/src/i18n/en-runtime-required.json index 55263d5ee7f..48fd78035d9 100644 --- a/src/renderer/src/i18n/en-runtime-required.json +++ b/src/renderer/src/i18n/en-runtime-required.json @@ -37,6 +37,7 @@ "68796dafa0": "pr", "88fd82474d": "page", "924c2fe05e": "destructive", + "a18f669c7a": "{{value0}} {{value1}} reaction{{value2}}", "b0b7344684": "Request up to 15 reviewers", "b1157c78ff": "sheet", "ce8a85d209": "default", @@ -47,6 +48,7 @@ }, "GitLabItemDialog": { "11384f99aa": "number", + "40c56b95e2": "{{value0}} approval{{value1}} remaining", "4186685c78": "reopen", "881e522e04": "merge", "cae2712a23": "close" @@ -155,6 +157,7 @@ "6459faa8b3": "error", "68df347677": "you@example.com", "6edf402e11": "overview", + "7719d8daa9": "{{value0}} member{{value1}}", "7799ad9ab6": "draft", "887efe9140": "Connect", "937b29fa35": "items", @@ -176,6 +179,7 @@ "d0e3c8f933": "No matching GitHub work", "d6d08c1650": "Select a project to see GitLab work items.", "duplicateIssueNumberPlaceholder": "Issue number", + "eff9800d4b": "{{value0}} label{{value1}}", "f294c500ef": "No GitLab work matches this filter.", "fe28c9821f": "view" }, @@ -667,7 +671,8 @@ "IntegrationsStep": { "93f0c49ad1": "not-authenticated", "a3bcf13694": "connected", - "a74c6d6b18": "not-installed" + "a74c6d6b18": "not-installed", + "b08a6ac93c": "{{value0}} workspace{{value1}} linked. Add another workspace or replace a restricted key any time." }, "NotificationStep": { "2c47f5465f": "Turn on Allow notifications for Orca in System Settings. This step updates automatically once enabled.", @@ -839,9 +844,11 @@ "48a003c1b1": "Staged Changes", "522f44dce5": "Untracked Files", "527e130b6f": "Stop generating", + "657e0c90ad": "{{value0}} note{{value1}}", "77afaa8152": "All", "783a808870": "Close", "7a09d7f9d2": "base", + "8eb3782a0c": "Failed to discard {{value0}} file{{value1}}", "901140f47d": "Review the prompt before starting an agent.", "9bb062a886": "uncommitted", "9febd8ab5f": "create_pr", @@ -1034,6 +1041,11 @@ } } }, + "compare": { + "summary": { + "dd72a6fd37": "{{value0}} commit{{value1}} ahead of {{value2}}" + } + }, "discard": { "confirmation": { "1426c2efff": "This will revert all changes to this file. This cannot be undone.", @@ -1872,6 +1884,7 @@ "serverName": "Server name", "sshConfigPickerBulkHint": "Bulk sync stays in Settings → SSH", "sshConfigPickerDescription": "Pick a host to fill the form. Nothing is saved until you click Save.", + "sshImportSynced": "Added {{value0}} host{{value1}} to Orca.", "sshPersistenceDefault": "Remote terminals on this host stay alive until you end them or reset the relay." }, "AddRepoHostSelector": { @@ -2010,7 +2023,8 @@ "5fc9d1891b": "Deleting…", "84a2238242": "Show child workspaces", "ebadb7eadb": "{{value0}} {{value1}} child {{value2}}", - "ebc5c7dcef": "Hide child workspaces" + "ebc5c7dcef": "Hide child workspaces", + "f94466bc39": "{{value0}} of {{value1}} contained project{{value2}} remained after deleting the group." }, "WorktreeMetaDialog": { "029ea5ec57": "Open GitHub issue", @@ -2558,9 +2572,9 @@ "3eed73394f": "Your keyboard is paused" }, "TerminalErrorToast": { + "hostTerminalCap": "The host is already running as many terminals as Orca allows. Close a terminal you no longer need, then try again.", "ptyAllocationLimit": "Your system cannot allocate any more pty devices. Close some terminals you are not using, in Orca or another program, then try again.", - "terminalProcessLimit": "Your system cannot start another terminal process. Close unused terminals or quit unused processes, then try again.", - "hostTerminalCap": "The host is already running as many terminals as Orca allows. Close a terminal you no longer need, then try again." + "terminalProcessLimit": "Your system cannot start another terminal process. Close unused terminals or quit unused processes, then try again." }, "TerminalSessionStateSaveFailureDialog": { "678c780a2c": "Disk space is unavailable", @@ -2573,7 +2587,10 @@ "terminal": { "drop": { "handler": { - "29c031b49a": "Uploading {{value0}} file{{value1}} to runtime…" + "29c031b49a": "Uploading {{value0}} file{{value1}} to runtime…", + "53f015fd85": "Skipped {{value0}} symlink{{value1}}.", + "uploadingFilePlural": "Uploading {{count}} files to runtime…", + "uploadingFileSingular": "Uploading {{count}} file to runtime…" } } } diff --git a/src/renderer/src/i18n/locales/en.json b/src/renderer/src/i18n/locales/en.json index 4d51de70365..60291226247 100644 --- a/src/renderer/src/i18n/locales/en.json +++ b/src/renderer/src/i18n/locales/en.json @@ -1587,7 +1587,9 @@ "8812225174": "Reopen", "09d67a0f9b": "Failed to close pull request", "88809e79db": "Failed to reopen pull request", - "00f55cc17b": "Repository access is unavailable for this pull request." + "00f55cc17b": "Repository access is unavailable for this pull request.", + "reactionCountSingular": "{{count}} {{reaction}} reaction", + "reactionCountPlural": "{{count}} {{reaction}} reactions" }, "GitLabItemDialog": { "65e784c1f1": "Reopen", @@ -1653,7 +1655,9 @@ "32f8bef818": "No log output.", "4168eb2c51": "Resolve", "commentTooLarge": "Comment is too large to submit safely.", - "gitlabActionFailed": "GitLab action failed." + "gitlabActionFailed": "GitLab action failed.", + "approvalsRemainingSingular": "{{count}} approval remaining", + "approvalsRemainingPlural": "{{count}} approvals remaining" }, "JiraIssueWorkspace": { "b0b92666c9": "Comment", @@ -2503,7 +2507,11 @@ "linearModeHasWorktreeTooltip": "Linear tickets linked to an Orca workspace", "linearEmptyHasWorktree": "No Linear tickets are linked to an Orca workspace yet. Start work from a Linear issue to see it here.", "linearOpenAttachedWorkspace": "Open workspace attached to {{value0}}", - "linearWorktreesColumn": "Workspaces" + "linearWorktreesColumn": "Workspaces", + "labelCountPlural": "{{count}} labels", + "labelCountSingular": "{{count}} label", + "memberCountPlural": "{{count}} members", + "memberCountSingular": "{{count}} member" }, "Terminal": { "73768427cf": "Close", @@ -3506,7 +3514,11 @@ "internalHostMismatch": "Drop files from the same host as this terminal.", "b4cf68e889": "Skipped {{value0}} {{value1}}.", "writeTimeout": "File drop cancelled: terminal did not accept the path before the safety timeout.", - "writeRejected": "File drop cancelled: terminal could not accept the path." + "writeRejected": "File drop cancelled: terminal could not accept the path.", + "skippedSymlinkSingular": "Skipped {{count}} symlink.", + "skippedSymlinkPlural": "Skipped {{count}} symlinks.", + "uploadingFileSingular": "Uploading {{count}} file to runtime…", + "uploadingFilePlural": "Uploading {{count}} files to runtime…" } } }, @@ -6025,7 +6037,9 @@ "groupRenameFailedDesc": "Orca could not confirm the new name with the group's host. Recheck the group after reconnecting.", "failedNestWorkspace": "Failed to nest workspace", "sidebarRowMissing": "Target no longer exists", - "failedUnnestWorkspace": "Failed to unnest workspace" + "failedUnnestWorkspace": "Failed to unnest workspace", + "groupProjectsRemainedSingular": "{{remaining}} of {{count}} contained project remained after deleting the group.", + "groupProjectsRemainedPlural": "{{remaining}} of {{count}} contained projects remained after deleting the group." }, "WorktreeMetaDialog": { "3db0a2a593": "Cancel", @@ -6515,7 +6529,9 @@ "sshConfigPickerAddAllEmpty": "Add all to Orca", "identityFileFromConfigHint": "Left empty on purpose: Orca uses every key ~/.ssh/config resolves for {{value0}}. Type a path to use just that key.", "sshConfigPickerRetry": "Try again", - "sshConfigPickerResolving": "Reading…" + "sshConfigPickerResolving": "Reading…", + "sshImportSyncedPlural": "Added {{count}} hosts to Orca.", + "sshImportSyncedSingular": "Added {{count}} host to Orca." }, "ForgetSshWorkspaceDialog": { "reconnectFailed": "Reconnection failed", @@ -13234,7 +13250,11 @@ "entryDiscardFailed": "Failed to discard “{{value0}}”", "entryDeleteFailed": "Failed to delete “{{value0}}”", "entryFailedInWorkspace": "{{value0}} in {{value1}}", - "diffCommentNotesCopyFailed": "Failed to copy notes" + "diffCommentNotesCopyFailed": "Failed to copy notes", + "discardFailedSingular": "Failed to discard {{count}} file", + "discardFailedPlural": "Failed to discard {{count}} files", + "noteCountSingular": "{{count}} note", + "noteCountPlural": "{{count}} notes" }, "SourceControlAgentActionDialog": { "8e856842d1": "Could not start the selected agent.", @@ -13768,7 +13788,9 @@ }, "compare": { "summary": { - "dd72a6fd37": "{{value0}} commit{{value1}} ahead of {{value2}}" + "dd72a6fd37": "{{value0}} commit{{value1}} ahead of {{value2}}", + "commitsAheadSingular": "{{count}} commit ahead of {{ref}}", + "commitsAheadPlural": "{{count}} commits ahead of {{ref}}" } }, "conflict": { @@ -14324,7 +14346,9 @@ "a3bcf13694": "connected", "d6e5dba05a": "Sign in", "0b4a7d23ab": "Signing in", - "a74c6d6b18": "not-installed" + "a74c6d6b18": "not-installed", + "linearWorkspacesLinkedSingular": "{{count}} workspace linked. Add another workspace or replace a restricted key any time.", + "linearWorkspacesLinkedPlural": "{{count}} workspaces linked. Add another workspace or replace a restricted key any time." }, "NotificationStep": { "3bede04483": "Send test notification", diff --git a/src/renderer/src/i18n/locales/es.json b/src/renderer/src/i18n/locales/es.json index 4b92605e734..19388622658 100644 --- a/src/renderer/src/i18n/locales/es.json +++ b/src/renderer/src/i18n/locales/es.json @@ -1399,7 +1399,9 @@ "8812225174": "Reabrir", "09d67a0f9b": "No se pudo cerrar la PR", "88809e79db": "No se pudo reabrir la PR", - "00f55cc17b": "El acceso al repositorio no está disponible para esta PR." + "00f55cc17b": "El acceso al repositorio no está disponible para esta PR.", + "reactionCountSingular": "{{count}} {{reaction}} reacción", + "reactionCountPlural": "{{count}} {{reaction}} reacciones" }, "GitLabItemDialog": { "65e784c1f1": "Reabrir", @@ -1465,7 +1467,9 @@ "32f8bef818": "Sin output de log.", "4168eb2c51": "Resolver", "commentTooLarge": "El comentario es demasiado grande para enviarlo de forma segura.", - "gitlabActionFailed": "La acción de GitLab falló." + "gitlabActionFailed": "La acción de GitLab falló.", + "approvalsRemainingSingular": "{{count}} aprobación restante", + "approvalsRemainingPlural": "{{count}} aprobaciones restantes" }, "JiraIssueWorkspace": { "b0b92666c9": "Comentar", @@ -2272,7 +2276,11 @@ "jiraLinkSourceUnavailable": "Couldn’t link this Jira issue. Reconnect Jira or pick the matching site, then try again.", "loadPageUnreachable": "La página {{value0}} está más allá de lo que la búsqueda de GitHub puede devolver.", "loadPageFailed": "No se pudo cargar la página {{value0}} desde GitHub.", - "loadPageNoMoreResults": "No hay más resultados en la página {{value0}}." + "loadPageNoMoreResults": "No hay más resultados en la página {{value0}}.", + "labelCountPlural": "{{count}} etiquetas", + "labelCountSingular": "{{count}} etiqueta", + "memberCountPlural": "{{count}} miembros", + "memberCountSingular": "{{count}} miembro" }, "Terminal": { "73768427cf": "Cerrar", @@ -3256,7 +3264,11 @@ "internalHostMismatch": "Arrastra archivos del mismo host que este terminal.", "b4cf68e889": "Se omitió {{value0}} {{value1}}.", "writeTimeout": "Se canceló el drop de archivos: el terminal no aceptó la ruta antes del tiempo límite de seguridad.", - "writeRejected": "Se canceló el drop de archivos: el terminal no pudo aceptar la ruta." + "writeRejected": "Se canceló el drop de archivos: el terminal no pudo aceptar la ruta.", + "skippedSymlinkSingular": "Se omitió {{count}} symlink.", + "skippedSymlinkPlural": "Se omitieron {{count}} symlinks.", + "uploadingFileSingular": "Subiendo {{count}} archivo al host…", + "uploadingFilePlural": "Subiendo {{count}} archivos al host…" } } }, @@ -5737,7 +5749,9 @@ "sidebarRowMissing": "El destino ya no existe", "failedUnnestWorkspace": "Error al expandir el espacio de trabajo", "groupRenameFailed": "No se pudo cambiar el nombre del grupo", - "groupRenameFailedDesc": "Orca no pudo confirmar el nuevo nombre con el anfitrión del grupo. Vuelva a verificar el grupo después de volver a conectarse." + "groupRenameFailedDesc": "Orca no pudo confirmar el nuevo nombre con el anfitrión del grupo. Vuelva a verificar el grupo después de volver a conectarse.", + "groupProjectsRemainedSingular": "{{remaining}} de {{count}} proyecto contenido permaneció tras eliminar el grupo.", + "groupProjectsRemainedPlural": "Proyectos contenidos que permanecieron tras eliminar el grupo: {{remaining}} de {{count}}." }, "WorktreeMetaDialog": { "3db0a2a593": "Cancelar", @@ -6276,7 +6290,9 @@ "sshConfigPickerAddAllEmpty": "Agregar todo a Orca", "identityFileFromConfigHint": "Dejado vacío a propósito: Orca usa todas las claves ~/.ssh/config resuelve para {{value0}}. Escriba una ruta para usar solo esa clave.", "sshConfigPickerRetry": "Probar de nuevo", - "sshConfigPickerResolving": "Lectura…" + "sshConfigPickerResolving": "Lectura…", + "sshImportSyncedPlural": "Se sincronizaron {{count}} hosts.", + "sshImportSyncedSingular": "Se sincronizó {{count}} host." }, "ForgetSshWorkspaceDialog": { "reconnectFailed": "Error de reconexión", @@ -12938,7 +12954,11 @@ "entryDiscardFailed": "No se pudo descartar \"{{value0}}\"", "entryDeleteFailed": "No se pudo eliminar \"{{value0}}\"", "entryFailedInWorkspace": "{{value0}} en {{value1}}", - "diffCommentNotesCopyFailed": "No se pudieron copiar notas" + "diffCommentNotesCopyFailed": "No se pudieron copiar notas", + "discardFailedSingular": "No se pudo descartar {{count}} archivo", + "discardFailedPlural": "No se pudieron descartar {{count}} archivos", + "noteCountSingular": "{{count}} nota", + "noteCountPlural": "{{count}} notas" }, "SourceControlAgentActionDialog": { "8e856842d1": "No se pudo iniciar el agente seleccionado.", @@ -13471,7 +13491,9 @@ }, "compare": { "summary": { - "dd72a6fd37": "{{value0}} confirma{{value1}} antes de {{value2}}" + "dd72a6fd37": "{{value0}} confirma{{value1}} antes de {{value2}}", + "commitsAheadSingular": "{{count}} commit por delante de {{ref}}", + "commitsAheadPlural": "{{count}} commits por delante de {{ref}}" } }, "conflict": { @@ -14027,7 +14049,9 @@ "a3bcf13694": "conectado", "d6e5dba05a": "Iniciar sesión", "0b4a7d23ab": "Iniciando sesión", - "a74c6d6b18": "no instalado" + "a74c6d6b18": "no instalado", + "linearWorkspacesLinkedSingular": "{{count}} workspace vinculado. Agrega otro workspace o reemplaza una clave restringida en cualquier momento.", + "linearWorkspacesLinkedPlural": "{{count}} workspaces vinculados. Agrega otro workspace o reemplaza una clave restringida en cualquier momento." }, "NotificationStep": { "3bede04483": "Enviar notificación de prueba", diff --git a/src/renderer/src/i18n/locales/fr.json b/src/renderer/src/i18n/locales/fr.json index a9eae06bb86..a50d08a43fb 100644 --- a/src/renderer/src/i18n/locales/fr.json +++ b/src/renderer/src/i18n/locales/fr.json @@ -1504,7 +1504,9 @@ "8812225174": "Rouvrir", "09d67a0f9b": "Échec de la fermeture de la pull request", "88809e79db": "Échec de la réouverture de la pull request", - "00f55cc17b": "L'accès au dépôt est indisponible pour cette pull request." + "00f55cc17b": "L'accès au dépôt est indisponible pour cette pull request.", + "reactionCountSingular": "{{count}} {{reaction}} réaction", + "reactionCountPlural": "{{count}} {{reaction}} réactions" }, "GitLabItemDialog": { "65e784c1f1": "Rouvrir", @@ -1570,7 +1572,9 @@ "32f8bef818": "Aucune sortie de log.", "4168eb2c51": "Résoudre", "commentTooLarge": "Le commentaire est trop volumineux pour être envoyé sans risque.", - "gitlabActionFailed": "L'action GitLab a échoué." + "gitlabActionFailed": "L'action GitLab a échoué.", + "approvalsRemainingSingular": "Il reste {{count}} approbation", + "approvalsRemainingPlural": "Il reste {{count}} approbations" }, "JiraIssueWorkspace": { "b0b92666c9": "Commentaire", @@ -2377,7 +2381,11 @@ "linearModeHasWorktreeTooltip": "Tickets Linear liés à un espace de travail Orca", "linearEmptyHasWorktree": "Aucun ticket Linear n'est encore lié à un espace de travail Orca. Démarrez le travail depuis une issue Linear pour le voir ici.", "linearOpenAttachedWorkspace": "Ouvrir l'espace de travail attaché à {{value0}}", - "linearWorktreesColumn": "Espaces de travail" + "linearWorktreesColumn": "Espaces de travail", + "labelCountPlural": "{{count}} labels", + "labelCountSingular": "{{count}} label", + "memberCountPlural": "{{count}} membres", + "memberCountSingular": "{{count}} membre" }, "Terminal": { "73768427cf": "Fermer", @@ -3380,7 +3388,11 @@ "internalHostMismatch": "Déposez des fichiers provenant du même hôte que ce terminal.", "b4cf68e889": "Ignoré : {{value0}} {{value1}}.", "writeTimeout": "Glisser-déposer de fichiers annulé : le terminal n'a pas accepté le chemin avant le délai de sécurité.", - "writeRejected": "Glisser-déposer de fichiers annulé : le terminal n'a pas pu accepter le chemin." + "writeRejected": "Glisser-déposer de fichiers annulé : le terminal n'a pas pu accepter le chemin.", + "skippedSymlinkSingular": "Ignoré : {{count}} symlink.", + "skippedSymlinkPlural": "Ignorés : {{count}} symlinks.", + "uploadingFileSingular": "Envoi de {{count}} fichier vers le runtime…", + "uploadingFilePlural": "Envoi de {{count}} fichiers vers le runtime…" } } }, @@ -5866,7 +5878,9 @@ "groupRenameFailedDesc": "Orca n'a pas pu confirmer le nouveau nom auprès de l'hôte du groupe. Revérifiez le groupe après reconnexion.", "failedNestWorkspace": "Échec de l'imbrication de l'espace de travail", "sidebarRowMissing": "La cible n'existe plus", - "failedUnnestWorkspace": "Échec de la désimbrication de l'espace de travail" + "failedUnnestWorkspace": "Échec de la désimbrication de l'espace de travail", + "groupProjectsRemainedSingular": "{{remaining}} projet sur {{count}} est resté après la suppression du groupe.", + "groupProjectsRemainedPlural": "Projets restés après la suppression du groupe : {{remaining}} sur {{count}}." }, "WorktreeMetaDialog": { "3db0a2a593": "Annuler", @@ -6356,7 +6370,9 @@ "sshConfigPickerAddAllEmpty": "Tout ajouter à Orca", "identityFileFromConfigHint": "Volontairement laissé vide : Orca utilise toutes les clés résolues par ~/.ssh/config pour {{value0}}. Saisissez un chemin pour n'utiliser que cette clé.", "sshConfigPickerRetry": "Réessayer", - "sshConfigPickerResolving": "Lecture…" + "sshConfigPickerResolving": "Lecture…", + "sshImportSyncedPlural": "Ajout de {{count}} hôtes à Orca.", + "sshImportSyncedSingular": "Ajout de {{count}} hôte à Orca." }, "ForgetSshWorkspaceDialog": { "reconnectFailed": "Échec de la reconnexion", @@ -13044,7 +13060,11 @@ "entryDiscardFailed": "Échec de la suppression de « {{value0}} »", "entryDeleteFailed": "Échec de la suppression de « {{value0}} »", "entryFailedInWorkspace": "{{value0}} dans {{value1}}", - "diffCommentNotesCopyFailed": "Échec de la copie des notes" + "diffCommentNotesCopyFailed": "Échec de la copie des notes", + "discardFailedSingular": "Échec de l'abandon de {{count}} fichier", + "discardFailedPlural": "Échec de l'abandon de {{count}} fichiers", + "noteCountSingular": "{{count}} note", + "noteCountPlural": "{{count}} notes" }, "SourceControlAgentActionDialog": { "8e856842d1": "Impossible de démarrer l'agent sélectionné.", @@ -13577,7 +13597,9 @@ }, "compare": { "summary": { - "dd72a6fd37": "{{value0}} commit{{value1}} d'avance sur {{value2}}" + "dd72a6fd37": "{{value0}} commit{{value1}} d'avance sur {{value2}}", + "commitsAheadSingular": "{{count}} commit d'avance sur {{ref}}", + "commitsAheadPlural": "{{count}} commits d'avance sur {{ref}}" } }, "conflict": { @@ -14133,7 +14155,9 @@ "a3bcf13694": "connecté", "d6e5dba05a": "Se connecter", "0b4a7d23ab": "Connexion en cours", - "a74c6d6b18": "not-installed" + "a74c6d6b18": "not-installed", + "linearWorkspacesLinkedSingular": "{{count}} espace de travail associé. Ajoutez un autre espace de travail ou remplacez une clé restreinte à tout moment.", + "linearWorkspacesLinkedPlural": "{{count}} espaces de travail associés. Ajoutez un autre espace de travail ou remplacez une clé restreinte à tout moment." }, "NotificationStep": { "3bede04483": "Envoyer une notification de test", diff --git a/src/renderer/src/i18n/locales/ja.json b/src/renderer/src/i18n/locales/ja.json index ae613af9533..c02a0103312 100644 --- a/src/renderer/src/i18n/locales/ja.json +++ b/src/renderer/src/i18n/locales/ja.json @@ -1399,7 +1399,9 @@ "8812225174": "再度開く", "09d67a0f9b": "PR をクローズできませんでした", "88809e79db": "PR の再開に失敗しました", - "00f55cc17b": "この PR ではリポジトリにアクセスできません。" + "00f55cc17b": "この PR ではリポジトリにアクセスできません。", + "reactionCountSingular": "{{count}} {{reaction}} 反応", + "reactionCountPlural": "{{count}} {{reaction}} 反応" }, "GitLabItemDialog": { "65e784c1f1": "再度開く", @@ -1465,7 +1467,9 @@ "32f8bef818": "ログは出力されません。", "4168eb2c51": "解決", "commentTooLarge": "コメントが大きすぎるため安全に送信できません。", - "gitlabActionFailed": "GitLab 操作が失敗しました。" + "gitlabActionFailed": "GitLab 操作が失敗しました。", + "approvalsRemainingSingular": "{{count}} 承認 残り", + "approvalsRemainingPlural": "{{count}} 承認 残り" }, "JiraIssueWorkspace": { "b0b92666c9": "コメント", @@ -2272,7 +2276,11 @@ "jiraLinkSourceUnavailable": "この Jira Issue をリンクできませんでした。Jira に再接続するか該当するサイトを選択してから、もう一度お試しください。", "loadPageUnreachable": "ページ {{value0}} は GitHub 検索が返せる範囲を超えています。", "loadPageFailed": "GitHub からページ {{value0}} を読み込めませんでした。", - "loadPageNoMoreResults": "ページ {{value0}} にはこれ以上結果がありません。" + "loadPageNoMoreResults": "ページ {{value0}} にはこれ以上結果がありません。", + "labelCountPlural": "{{count}} ラベル", + "labelCountSingular": "{{count}} ラベル", + "memberCountPlural": "{{count}} メンバー", + "memberCountSingular": "{{count}} メンバー" }, "Terminal": { "73768427cf": "閉じる", @@ -3256,7 +3264,11 @@ "internalHostMismatch": "このターミナルと同じホストのファイルをドロップしてください。", "b4cf68e889": "{{value0}} {{value1}} をスキップしました。", "writeTimeout": "ファイルドロップをキャンセルしました: 安全タイムアウト前にターミナルがパスを受け付けませんでした。", - "writeRejected": "ファイルドロップをキャンセルしました: ターミナルがパスを受け付けられませんでした。" + "writeRejected": "ファイルドロップをキャンセルしました: ターミナルがパスを受け付けられませんでした。", + "skippedSymlinkSingular": "{{count}} シンボリックリンク をスキップしました。", + "skippedSymlinkPlural": "{{count}} シンボリックリンク をスキップしました。", + "uploadingFileSingular": "{{count}} ファイル をランタイムにアップロードしています…", + "uploadingFilePlural": "{{count}} ファイル をランタイムにアップロードしています…" } } }, @@ -5718,7 +5730,9 @@ "sidebarRowMissing": "対象はもう存在しません", "failedUnnestWorkspace": "ワークスペースの展開に失敗しました", "groupRenameFailed": "グループ名の変更に失敗しました", - "groupRenameFailedDesc": "Orca はグループのホストに新規名前を確認できませんでした。再接続後、グループを再確認してください。" + "groupRenameFailedDesc": "Orca はグループのホストに新規名前を確認できませんでした。再接続後、グループを再確認してください。", + "groupProjectsRemainedSingular": "グループ削除後も、含まれていたプロジェクト {{count}} 件中 {{remaining}} 件が残りました。", + "groupProjectsRemainedPlural": "グループ削除後も、含まれていたプロジェクト {{count}} 件中 {{remaining}} 件が残りました。" }, "WorktreeMetaDialog": { "3db0a2a593": "キャンセル", @@ -6276,7 +6290,9 @@ "sshConfigPickerAddAllEmpty": "すべてを Orca に追加", "identityFileFromConfigHint": "意図的に空のままにします: Orca は ~/.ssh/config が {{value0}} に対して解決するすべてのキーを使用します。そのキーのみを使用するパスを入力します。", "sshConfigPickerRetry": "再試行", - "sshConfigPickerResolving": "読む…" + "sshConfigPickerResolving": "読む…", + "sshImportSyncedPlural": "{{count}} ホスト を同期しました。", + "sshImportSyncedSingular": "{{count}} ホスト を同期しました。" }, "ForgetSshWorkspaceDialog": { "reconnectFailed": "再接続に失敗しました", @@ -12938,7 +12954,11 @@ "entryDiscardFailed": "「{{value0}}」を破棄できませんでした", "entryDeleteFailed": "「{{value0}}」を削除できませんでした", "entryFailedInWorkspace": "{{value0}} の {{value1}}", - "diffCommentNotesCopyFailed": "メモのコピーに失敗しました" + "diffCommentNotesCopyFailed": "メモのコピーに失敗しました", + "discardFailedSingular": "{{count}} ファイル を破棄できませんでした", + "discardFailedPlural": "{{count}} ファイル を破棄できませんでした", + "noteCountSingular": "{{count}} 注", + "noteCountPlural": "{{count}} 注" }, "SourceControlAgentActionDialog": { "8e856842d1": "選択した Agent を開始できませんでした。", @@ -13471,7 +13491,9 @@ }, "compare": { "summary": { - "dd72a6fd37": "{{value0}} {{value2}} より先に{{value1}} をコミット" + "dd72a6fd37": "{{value0}} {{value2}} より先に{{value1}} をコミット", + "commitsAheadSingular": "{{ref}}より{{count}}コミット進んでいます", + "commitsAheadPlural": "{{ref}}より{{count}}コミット進んでいます" } }, "conflict": { @@ -14027,7 +14049,9 @@ "a3bcf13694": "接続されています", "d6e5dba05a": "サインイン", "0b4a7d23ab": "サインインする", - "a74c6d6b18": "未インストール" + "a74c6d6b18": "未インストール", + "linearWorkspacesLinkedSingular": "{{count}} ワークスペース がリンクされました。いつでも別のワークスペースを追加したり、制限付きキーを置き換えたりできます。", + "linearWorkspacesLinkedPlural": "{{count}} ワークスペース がリンクされました。いつでも別のワークスペースを追加したり、制限付きキーを置き換えたりできます。" }, "NotificationStep": { "3bede04483": "テスト通知を送信する", diff --git a/src/renderer/src/i18n/locales/ko.json b/src/renderer/src/i18n/locales/ko.json index 673eb9c6c06..6695ae57f26 100644 --- a/src/renderer/src/i18n/locales/ko.json +++ b/src/renderer/src/i18n/locales/ko.json @@ -1399,7 +1399,9 @@ "8812225174": "다시 열기", "09d67a0f9b": "PR을 닫지 못했습니다.", "88809e79db": "PR을 다시 열지 못했습니다.", - "00f55cc17b": "이 PR에는 저장소 액세스를 사용할 수 없습니다." + "00f55cc17b": "이 PR에는 저장소 액세스를 사용할 수 없습니다.", + "reactionCountSingular": "{{count}} {{reaction}} 반응", + "reactionCountPlural": "{{count}} {{reaction}} 반응" }, "GitLabItemDialog": { "65e784c1f1": "다시 열기", @@ -1465,7 +1467,9 @@ "32f8bef818": "로그 출력이 없습니다.", "4168eb2c51": "해결", "commentTooLarge": "댓글이 너무 길어 안전하게 제출할 수 없습니다.", - "gitlabActionFailed": "GitLab 작업이 실패했습니다." + "gitlabActionFailed": "GitLab 작업이 실패했습니다.", + "approvalsRemainingSingular": "{{count}} 승인 남음", + "approvalsRemainingPlural": "{{count}} 승인 남음" }, "JiraIssueWorkspace": { "b0b92666c9": "댓글", @@ -2272,7 +2276,11 @@ "jiraLinkSourceUnavailable": "Couldn’t link this Jira issue. Reconnect Jira or pick the matching site, then try again.", "loadPageUnreachable": "{{value0}} 페이지는 GitHub 검색이 반환할 수 있는 범위를 벗어났습니다.", "loadPageFailed": "GitHub에서 {{value0}} 페이지를 불러오지 못했습니다.", - "loadPageNoMoreResults": "{{value0}} 페이지에 더 이상 결과가 없습니다." + "loadPageNoMoreResults": "{{value0}} 페이지에 더 이상 결과가 없습니다.", + "labelCountPlural": "{{count}} 라벨", + "labelCountSingular": "{{count}} 라벨", + "memberCountPlural": "{{count}} 멤버", + "memberCountSingular": "{{count}} 멤버" }, "Terminal": { "73768427cf": "닫기", @@ -3256,7 +3264,11 @@ "internalHostMismatch": "이 터미널과 같은 호스트의 파일을 드롭하세요.", "b4cf68e889": "{{value0}} {{value1}}을(를) 건너뛰었습니다.", "writeTimeout": "파일 드롭이 취소되었습니다. 안전 시간 제한 전에 터미널이 경로를 받지 않았습니다.", - "writeRejected": "파일 드롭이 취소되었습니다. 터미널이 경로를 받을 수 없습니다." + "writeRejected": "파일 드롭이 취소되었습니다. 터미널이 경로를 받을 수 없습니다.", + "skippedSymlinkSingular": "{{count}} 심볼릭 링크를 건너뛰었습니다.", + "skippedSymlinkPlural": "{{count}} 심볼릭 링크를 건너뛰었습니다.", + "uploadingFileSingular": "{{count}} 파일을 런타임에 업로드하는 중…", + "uploadingFilePlural": "{{count}} 파일을 런타임에 업로드하는 중…" } } }, @@ -5718,7 +5730,9 @@ "sidebarRowMissing": "대상이 더 이상 존재하지 않습니다", "failedUnnestWorkspace": "워크트리 펼치기 실패", "groupRenameFailed": "그룹 이름을 바꾸지 못했습니다.", - "groupRenameFailedDesc": "Orca는 그룹 호스트에게 새 이름을 확인할 수 없습니다. 다시 연결한 후 그룹을 다시 확인하세요." + "groupRenameFailedDesc": "Orca는 그룹 호스트에게 새 이름을 확인할 수 없습니다. 다시 연결한 후 그룹을 다시 확인하세요.", + "groupProjectsRemainedSingular": "그룹 삭제 후에도 {{remaining}}/{{count}} 프로젝트가 남아 있습니다.", + "groupProjectsRemainedPlural": "그룹 삭제 후에도 {{remaining}}/{{count}} 프로젝트가 남아 있습니다." }, "WorktreeMetaDialog": { "3db0a2a593": "취소", @@ -6276,7 +6290,9 @@ "sshConfigPickerAddAllEmpty": "Orca에 모두 추가", "identityFileFromConfigHint": "의도적으로 비워 두기: Orca는 ~/.ssh/config가 {{value0}}을 확인하는 모든 키를 사용합니다. 해당 키만 사용하려면 경로를 입력하세요.", "sshConfigPickerRetry": "다시 시도하세요", - "sshConfigPickerResolving": "독서…" + "sshConfigPickerResolving": "독서…", + "sshImportSyncedPlural": "{{count}} 호스트을(를) Orca에 추가했습니다.", + "sshImportSyncedSingular": "{{count}} 호스트을(를) Orca에 추가했습니다." }, "ForgetSshWorkspaceDialog": { "reconnectFailed": "재연결 실패", @@ -12938,7 +12954,11 @@ "entryDiscardFailed": "'{{value0}}'을(를) 삭제하지 못했습니다.", "entryDeleteFailed": "'{{value0}}'을(를) 삭제하지 못했습니다.", "entryFailedInWorkspace": "{{value1}}의 {{value0}}", - "diffCommentNotesCopyFailed": "메모를 복사하지 못했습니다." + "diffCommentNotesCopyFailed": "메모를 복사하지 못했습니다.", + "discardFailedSingular": "{{count}} 파일을(를) 삭제하지 못했습니다.", + "discardFailedPlural": "{{count}} 파일을(를) 삭제하지 못했습니다.", + "noteCountSingular": "{{count}} 메모", + "noteCountPlural": "{{count}} 메모" }, "SourceControlAgentActionDialog": { "8e856842d1": "선택한 agent를 시작할 수 없습니다.", @@ -13471,7 +13491,9 @@ }, "compare": { "summary": { - "dd72a6fd37": "{{value0}}은 {{value2}}보다 먼저 {{value1}}을 커밋합니다." + "dd72a6fd37": "{{value0}}은 {{value2}}보다 먼저 {{value1}}을 커밋합니다.", + "commitsAheadSingular": "{{ref}}보다 {{count}} commit 앞서 있음", + "commitsAheadPlural": "{{ref}}보다 {{count}} commits 앞서 있음" } }, "conflict": { @@ -14027,7 +14049,9 @@ "a3bcf13694": "연결됨", "d6e5dba05a": "로그인", "0b4a7d23ab": "로그인 중", - "a74c6d6b18": "설치되지 않은" + "a74c6d6b18": "설치되지 않은", + "linearWorkspacesLinkedSingular": "{{count}} 워크스페이스가 연결되었습니다. 언제든지 다른 워크스페이스를 추가하거나 제한된 키를 교체하세요.", + "linearWorkspacesLinkedPlural": "{{count}} 워크스페이스가 연결되었습니다. 언제든지 다른 워크스페이스를 추가하거나 제한된 키를 교체하세요." }, "NotificationStep": { "3bede04483": "테스트 알림 보내기", diff --git a/src/renderer/src/i18n/locales/zh.json b/src/renderer/src/i18n/locales/zh.json index 5920c46f849..d8ea1212440 100644 --- a/src/renderer/src/i18n/locales/zh.json +++ b/src/renderer/src/i18n/locales/zh.json @@ -1399,7 +1399,9 @@ "8812225174": "重新打开", "09d67a0f9b": "无法关闭拉取请求", "88809e79db": "无法重新提交拉取请求", - "00f55cc17b": "此拉取请求无法访问存储库。" + "00f55cc17b": "此拉取请求无法访问存储库。", + "reactionCountSingular": "{{count}} {{reaction}} 反应", + "reactionCountPlural": "{{count}} {{reaction}} 反应" }, "GitLabItemDialog": { "65e784c1f1": "重新打开", @@ -1465,7 +1467,9 @@ "32f8bef818": "无日志输出。", "4168eb2c51": "解决", "commentTooLarge": "评论过长,无法安全提交。", - "gitlabActionFailed": "GitLab 操作失败。" + "gitlabActionFailed": "GitLab 操作失败。", + "approvalsRemainingSingular": "{{count}} 批准 剩余", + "approvalsRemainingPlural": "{{count}} 批准 剩余" }, "JiraIssueWorkspace": { "b0b92666c9": "评论", @@ -2272,7 +2276,11 @@ "jiraLinkSourceUnavailable": "无法关联此 Jira 议题。请重新连接 Jira 或选择匹配的站点,然后重试。", "loadPageUnreachable": "第 {{value0}} 页超出了 GitHub 搜索可返回的范围。", "loadPageFailed": "无法从 GitHub 加载第 {{value0}} 页。", - "loadPageNoMoreResults": "第 {{value0}} 页没有更多结果。" + "loadPageNoMoreResults": "第 {{value0}} 页没有更多结果。", + "labelCountPlural": "{{count}} 标签", + "labelCountSingular": "{{count}} 标签", + "memberCountPlural": "{{count}} 成员", + "memberCountSingular": "{{count}} 成员" }, "Terminal": { "73768427cf": "关闭", @@ -3256,7 +3264,11 @@ "internalHostMismatch": "请拖放与此终端位于同一主机的文件。", "b4cf68e889": "已跳过 {{value0}} {{value1}}。", "writeTimeout": "文件拖放已取消:终端在安全超时前未接受路径。", - "writeRejected": "文件拖放已取消:终端无法接受路径。" + "writeRejected": "文件拖放已取消:终端无法接受路径。", + "skippedSymlinkSingular": "跳过 {{count}} 符号链接。", + "skippedSymlinkPlural": "跳过 {{count}} 符号链接。", + "uploadingFileSingular": "正在上传 {{count}} 文件 到运行时...", + "uploadingFilePlural": "正在上传 {{count}} 文件 到运行时..." } } }, @@ -5718,7 +5730,9 @@ "sidebarRowMissing": "目标不再存在", "failedUnnestWorkspace": "展开工作区失败", "groupRenameFailed": "重命名组失败", - "groupRenameFailedDesc": "Orca 无法与该群组的主持人确认新名称。重新连接后重新检查该组。" + "groupRenameFailedDesc": "Orca 无法与该群组的主持人确认新名称。重新连接后重新检查该组。", + "groupProjectsRemainedSingular": "删除分组后,{{count}} 个包含项目中仍剩 {{remaining}} 个。", + "groupProjectsRemainedPlural": "删除分组后,{{count}} 个包含项目中仍剩 {{remaining}} 个。" }, "WorktreeMetaDialog": { "3db0a2a593": "取消", @@ -6276,7 +6290,9 @@ "sshConfigPickerAddAllEmpty": "全部添加到 Orca", "identityFileFromConfigHint": "故意留空:Orca 使用 ~/.ssh/config 解析为 {{value0}} 的每个密钥。键入一个路径以仅使用该键。", "sshConfigPickerRetry": "重试", - "sshConfigPickerResolving": "阅读…" + "sshConfigPickerResolving": "阅读…", + "sshImportSyncedPlural": "已同步 {{count}} 个主机。", + "sshImportSyncedSingular": "已同步 {{count}} 个主机。" }, "ForgetSshWorkspaceDialog": { "reconnectFailed": "重新连接失败", @@ -12952,7 +12968,11 @@ "entryDiscardFailed": "无法丢弃“{{value0}}”", "entryDeleteFailed": "删除“{{value0}}”失败", "entryFailedInWorkspace": "{{value0}} 在 {{value1}} 中", - "diffCommentNotesCopyFailed": "复制笔记失败" + "diffCommentNotesCopyFailed": "复制笔记失败", + "discardFailedSingular": "无法丢弃 {{count}} 文件", + "discardFailedPlural": "无法丢弃 {{count}} 文件", + "noteCountSingular": "{{count}} 注", + "noteCountPlural": "{{count}} 注" }, "SourceControlAgentActionDialog": { "8e856842d1": "无法启动选定的智能体。", @@ -13485,7 +13505,9 @@ }, "compare": { "summary": { - "dd72a6fd37": "{{value0}} 在 {{value2}} 之前提交 {{value1}}" + "dd72a6fd37": "{{value0}} 在 {{value2}} 之前提交 {{value1}}", + "commitsAheadSingular": "领先 {{ref}} {{count}} 个提交", + "commitsAheadPlural": "领先 {{ref}} {{count}} 个提交" } }, "conflict": { @@ -14041,7 +14063,9 @@ "a3bcf13694": "已连接", "d6e5dba05a": "登录", "0b4a7d23ab": "登录", - "a74c6d6b18": "未安装" + "a74c6d6b18": "未安装", + "linearWorkspacesLinkedSingular": "{{count}} 工作区 已链接。随时添加另一个工作区或更换受限密钥。", + "linearWorkspacesLinkedPlural": "{{count}} 工作区 已链接。随时添加另一个工作区或更换受限密钥。" }, "NotificationStep": { "3bede04483": "发送测试通知",