From d8eef80bf4ec3ebba869469e6aa072c8af9477a1 Mon Sep 17 00:00:00 2001 From: OrcaWin Date: Wed, 28 Oct 2026 20:55:41 -0700 Subject: [PATCH] feat(mobile): localize sessions, terminal, agent chat, browser and files (#35553) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Moves ~970 strings in session screens, the terminal, agent chat (including the copy mobile used to render straight from shared English records), the browser pane and the file explorer to the translation API, with all ten translations. - Chat tool-run sentences, task lists, receipts, notices and turn status take translated words; the shared sentence joiner accepts an optional continuation so German nouns and Turkish casing survive mid-sentence (desktop unchanged) - "Used the browser" vs "… 21 times" and resolved PR comment headers use explicit keys instead of plural rules, which would drop the count for Russian 21, 31, … - Action sheet rows carry stable ids instead of being keyed by translated labels - Rename inputs keep the raw title rather than a translated placeholder Co-authored-by: OrcaWin <293788423+OrcaWin@users.noreply.github.com> --- ...obile-localization-coverage-allowlist.json | 2737 ----------------- .../files-explorer-legacy-fallback.json | 34 +- ...ix-files.explorer-screen-files.list-1.json | 34 +- ...files.explorer-screen-files.readdir-1.json | 34 +- .../worktree-navigation-actions.ts | 2 + .../src/browser/MobileBrowserAddressField.tsx | 4 +- mobile/src/browser/MobileBrowserPane.tsx | 5 +- mobile/src/browser/MobileBrowserPaneView.tsx | 22 +- .../browser/MobileBrowserPointerModifiers.tsx | 10 +- .../browser/MobileBrowserViewModeSwitch.tsx | 25 +- .../browser/use-mobile-browser-commands.ts | 25 +- mobile/src/components/ActionSheetModal.tsx | 3 +- .../components/MobileDiffReviewDrawers.tsx | 25 +- .../mobile-diff-review-send-copy.ts | 18 + .../pr-sidebar/PRCommentsSection.tsx | 12 +- .../mobile-web-shell-update-failure-copy.ts | 196 +- mobile/src/files/MobileFileExplorerPanel.tsx | 46 +- .../src/files/MobileFileMarkdownPreview.tsx | 15 +- mobile/src/files/MobileFileMediaPreview.tsx | 21 +- mobile/src/files/MobileFilePreviewBody.tsx | 8 +- .../files/MobileFilePreviewEditableSource.tsx | 6 +- mobile/src/files/MobileFilePreviewScreen.tsx | 74 +- .../src/files/MobileFilePreviewSourceText.tsx | 13 +- .../src/files/MobileMarkdownPreviewImage.tsx | 26 +- mobile/src/files/MobilePdfPreview.tsx | 16 +- mobile/src/files/mobile-file-explorer-row.tsx | 29 +- .../files/mobile-file-mutation-ownership.ts | 16 +- .../src/files/mobile-file-preview-request.ts | 10 +- .../src/files/mobile-file-preview-response.ts | 79 +- mobile/src/host-list-action-sheet-actions.ts | 5 + .../src/host-screen/host-screen-overlays.tsx | 3 + mobile/src/i18n/en-runtime-required.json | 112 +- mobile/src/i18n/locales/de.json | 1298 +++++++- mobile/src/i18n/locales/en.json | 1273 +++++++- mobile/src/i18n/locales/es.json | 1343 +++++++- mobile/src/i18n/locales/fr.json | 1343 +++++++- mobile/src/i18n/locales/ja.json | 1292 +++++++- mobile/src/i18n/locales/ko.json | 1292 +++++++- mobile/src/i18n/locales/pt.json | 1343 +++++++- mobile/src/i18n/locales/ru.json | 1388 ++++++++- mobile/src/i18n/locales/tr.json | 1298 +++++++- mobile/src/i18n/locales/zh-Hant.json | 1292 +++++++- mobile/src/i18n/locales/zh.json | 1292 +++++++- .../session/MobileBrowserTabActionSheet.tsx | 20 +- .../MobileDictationStatusAnnouncement.tsx | 9 +- .../src/session/MobileDiffCommentLineRow.tsx | 39 +- mobile/src/session/MobileMarkdownReader.tsx | 25 +- .../MobileNativeChatCardHeaderAction.tsx | 8 +- .../src/session/MobileNativeChatDiffCard.tsx | 28 +- .../MobileNativeChatFloatingControls.tsx | 11 +- .../session/MobileNativeChatFullContent.tsx | 41 +- .../MobileNativeChatResolutionReceipt.tsx | 18 +- mobile/src/session/MobileQueuedCardText.tsx | 14 +- .../session/MobileQueuedMessageCardRow.tsx | 36 +- .../session/MobileQueuedMessageComposer.tsx | 19 +- .../session/MobileSessionAccessoryKeys.tsx | 35 +- .../session/MobileSessionActiveContent.tsx | 27 +- .../src/session/MobileSessionCommandDock.tsx | 19 +- .../src/session/MobileSessionContentRow.tsx | 7 +- .../src/session/MobileSessionDiffLineRow.tsx | 37 +- .../src/session/MobileSessionFileReader.tsx | 37 +- mobile/src/session/MobileSessionHeader.tsx | 20 +- .../MobileSessionHeaderMoreActionsSheet.tsx | 15 +- .../session/MobileSessionMarkdownReader.tsx | 26 +- .../src/session/MobileSessionRouteScreen.tsx | 6 +- mobile/src/session/MobileSessionSheets.tsx | 101 +- mobile/src/session/MobileSessionTabStrip.tsx | 61 +- .../session/MobileTerminalInputActions.tsx | 24 +- .../session/MobileTerminalLiveInputStatus.tsx | 15 +- .../src/session/MobileTerminalRenameModal.tsx | 25 + mobile/src/session/QuickCommandEditorForm.tsx | 93 +- mobile/src/session/QuickCommandRow.tsx | 22 +- mobile/src/session/QuickCommandsList.tsx | 23 +- mobile/src/session/QuickCommandsSheet.tsx | 30 +- mobile/src/session/QuickCommandsTabButton.tsx | 4 +- mobile/src/session/ai-vault-resume-launch.ts | 9 +- .../session/ai-vault-resume-preparation.ts | 7 +- mobile/src/session/floating-workspace.ts | 8 +- mobile/src/session/github-pr-mutations.ts | 8 +- mobile/src/session/github-pr-rpc.ts | 5 +- .../session/mobile-agent-child-row-text.ts | 52 +- .../mobile-agent-message-attribution.ts | 33 +- .../session/mobile-background-task-copy.ts | 107 + .../mobile-bulk-close-sheet-actions.ts | 7 +- .../src/session/mobile-bulk-tab-close-copy.ts | 17 + .../src/session/mobile-diff-review-loaders.ts | 63 +- .../src/session/mobile-diff-review-queue.ts | 11 +- .../mobile-diff-review-screen-model.ts | 7 +- .../mobile-native-chat-attach-actions.ts | 40 +- .../session/mobile-native-chat-cli-fork.ts | 4 +- .../session/mobile-native-chat-render-data.ts | 57 +- .../mobile-native-chat-toggle-action.ts | 9 +- mobile/src/session/mobile-pr-sidebar-state.ts | 8 +- ...bile-session-native-chat-dictation-copy.ts | 12 + .../mobile-session-quick-command-launch.ts | 19 +- .../session/mobile-session-route-helpers.ts | 25 +- .../mobile-session-terminal-create-copy.ts | 29 + .../mobile-source-control-launch-agent.ts | 14 +- .../mobile-structured-composer-command.ts | 13 +- .../mobile-structured-send-delivery.ts | 6 +- .../mobile-terminal-action-sheet-actions.ts | 15 +- mobile/src/session/mobile-terminal-records.ts | 3 +- .../src/session/mobile-terminal-tab-agent.ts | 18 +- .../mobile-terminal-viewport-resubscribe.ts | 9 +- .../src/session/native-chat-diff-card-copy.ts | 9 + .../src/session/native-chat-receipt-copy.ts | 12 + .../composer/MobileNativeChatComposer.tsx | 28 +- .../MobileNativeChatComposerAttachments.tsx | 43 +- .../use-mobile-native-chat-image-upload.ts | 18 +- .../native-chat/input/MobileNativeChatAsk.tsx | 23 +- .../input/MobileNativeChatPermission.test.ts | 2 +- .../input/MobileNativeChatPermission.tsx | 16 +- .../input/MobileNativeChatPromptCard.tsx | 12 +- .../input/MobileNativeChatPromptStrip.tsx | 4 +- .../input/MobileNativeChatQuestion.tsx | 16 +- .../input/MobileNativeChatQueuedMessages.tsx | 22 +- .../MobileNativeChatSessionOptionPickers.tsx | 57 +- .../MobileNativeChatSessionOptionRows.tsx | 13 +- .../MobileNativeChatSessionOptionSegments.tsx | 10 +- .../input/MobileNativeChatStopButton.tsx | 9 +- .../input/MobileNativeChatTaskList.tsx | 44 +- ...obile-native-chat-session-option-labels.ts | 13 +- .../input/native-chat-session-option-copy.ts | 57 + .../input/native-chat-task-list-copy.ts | 23 + .../mobile-native-chat-stream-frame.ts | 14 +- .../native-chat-turn-status-copy.ts | 62 + .../MobileNativeChatAwaitingInputRow.tsx | 27 +- .../transcript/MobileNativeChatLiveLine.tsx | 6 +- .../transcript/MobileNativeChatMessage.tsx | 4 +- .../MobileNativeChatMessageActionsSheet.tsx | 25 +- .../transcript/MobileNativeChatNoticeRow.tsx | 46 +- .../transcript/MobileNativeChatProse.tsx | 17 +- .../transcript/MobileNativeChatToolLine.tsx | 48 +- .../transcript/MobileNativeChatToolRun.tsx | 32 +- .../transcript/MobileNativeChatTurnStatus.tsx | 14 +- .../MobileNativeChatUserMessageFold.tsx | 11 +- .../transcript/native-chat-ask-row-copy.ts | 10 + .../transcript/native-chat-notice-copy.ts | 27 + .../transcript/native-chat-orca-stop-copy.ts | 36 + .../native-chat-tool-activity-copy.ts | 39 + .../native-chat-tool-row-verb-copy.ts | 31 + .../native-chat-tool-run-words.test.ts | 71 + .../transcript/native-chat-tool-run-words.ts | 139 + .../native-chat-usage-limit-copy.ts | 45 + .../use-mobile-native-chat-input-lease.ts | 19 +- .../visual/MobileNativeChatImageViewer.tsx | 4 +- .../visual/MobileNativeChatVisual.tsx | 25 +- .../src/session/new-tab-agent-host-launch.ts | 28 +- mobile/src/session/pr-actions-engine.ts | 11 +- mobile/src/session/pr-ai-triage-launch.ts | 42 +- mobile/src/session/session-side-rail.tsx | 4 +- mobile/src/session/use-live-worktree-name.ts | 6 +- .../use-mobile-attachment-input-lease-gate.ts | 9 +- .../use-mobile-diff-review-comment-actions.ts | 19 +- .../use-mobile-diff-review-controller.ts | 23 +- .../use-mobile-diff-review-diff-loading.ts | 19 +- .../use-mobile-diff-review-git-actions.ts | 43 +- .../use-mobile-diff-review-interactions.ts | 8 +- .../use-mobile-diff-review-send-actions.ts | 70 +- .../session/use-mobile-image-attachment.ts | 18 +- .../session/use-mobile-native-chat-stop.ts | 17 +- mobile/src/session/use-mobile-pr-actions.ts | 2 + mobile/src/session/use-mobile-pr-ai-triage.ts | 34 +- .../session/use-mobile-pr-comment-actions.ts | 11 +- .../src/session/use-mobile-pr-title-action.ts | 17 +- .../use-mobile-session-accessory-selection.ts | 15 +- .../session/use-mobile-session-bulk-close.ts | 11 +- ...e-mobile-session-content-create-actions.ts | 58 +- .../use-mobile-session-diff-comments.ts | 27 +- .../use-mobile-session-markdown-actions.ts | 31 +- ...se-mobile-session-native-chat-dictation.ts | 11 +- ...use-mobile-session-panel-route-actions.tsx | 42 +- .../src/session/use-mobile-session-startup.ts | 9 +- ...-mobile-session-terminal-create-actions.ts | 22 +- .../use-mobile-session-terminal-input.ts | 13 +- ...se-mobile-session-terminal-send-actions.ts | 9 +- ...bile-structured-queued-message-controls.ts | 5 +- .../src/session/use-mobile-terminal-paste.ts | 41 +- mobile/src/session/use-quick-commands.ts | 56 +- mobile/src/session/worktree-label.ts | 7 +- .../MobileSourceControlPanel.tsx | 2 +- .../src/source-control/hosted-review-copy.ts | 24 +- .../mobile-commit-dropdown-copy.ts | 74 + .../source-control/mobile-create-pr-action.ts | 12 +- .../mobile-hosted-review-service.ts | 21 +- mobile/src/source-control/mobile-pr-create.ts | 99 +- .../mobile-remote-dropdown-copy.ts | 165 + .../mobile-source-control-actions.ts | 27 +- .../mobile-source-control-count-copy.ts | 123 + .../mobile-source-control-primary-action.ts | 10 +- .../mobile-source-control-review-copy.ts | 14 + .../use-mobile-source-control-action-sheet.ts | 94 +- ...-mobile-source-control-create-pr-action.ts | 4 +- mobile/src/terminal/TerminalWebView.tsx | 21 +- mobile/src/terminal/TerminalWebView.web.tsx | 17 +- .../src/terminal/terminal-key-definitions.ts | 132 +- .../terminal-webview-engine-error-state.tsx | 19 +- .../terminal-webview-notification-dispatch.ts | 6 +- .../terminal-webview-ready-watchdog.ts | 6 +- .../tools/native-chat-tool-run-sentence.ts | 6 +- 200 files changed, 18461 insertions(+), 3965 deletions(-) create mode 100644 mobile/src/components/mobile-diff-review-send-copy.ts create mode 100644 mobile/src/session/MobileTerminalRenameModal.tsx create mode 100644 mobile/src/session/mobile-background-task-copy.ts create mode 100644 mobile/src/session/mobile-bulk-tab-close-copy.ts create mode 100644 mobile/src/session/mobile-session-native-chat-dictation-copy.ts create mode 100644 mobile/src/session/mobile-session-terminal-create-copy.ts create mode 100644 mobile/src/session/native-chat-diff-card-copy.ts create mode 100644 mobile/src/session/native-chat-receipt-copy.ts create mode 100644 mobile/src/session/native-chat/input/native-chat-session-option-copy.ts create mode 100644 mobile/src/session/native-chat/input/native-chat-task-list-copy.ts create mode 100644 mobile/src/session/native-chat/native-chat-turn-status-copy.ts create mode 100644 mobile/src/session/native-chat/transcript/native-chat-ask-row-copy.ts create mode 100644 mobile/src/session/native-chat/transcript/native-chat-notice-copy.ts create mode 100644 mobile/src/session/native-chat/transcript/native-chat-orca-stop-copy.ts create mode 100644 mobile/src/session/native-chat/transcript/native-chat-tool-activity-copy.ts create mode 100644 mobile/src/session/native-chat/transcript/native-chat-tool-row-verb-copy.ts create mode 100644 mobile/src/session/native-chat/transcript/native-chat-tool-run-words.test.ts create mode 100644 mobile/src/session/native-chat/transcript/native-chat-tool-run-words.ts create mode 100644 mobile/src/session/native-chat/transcript/native-chat-usage-limit-copy.ts create mode 100644 mobile/src/source-control/mobile-commit-dropdown-copy.ts create mode 100644 mobile/src/source-control/mobile-remote-dropdown-copy.ts create mode 100644 mobile/src/source-control/mobile-source-control-count-copy.ts create mode 100644 mobile/src/source-control/mobile-source-control-review-copy.ts diff --git a/config/mobile-localization-coverage-allowlist.json b/config/mobile-localization-coverage-allowlist.json index f11df3608de..31821f2c63b 100644 --- a/config/mobile-localization-coverage-allowlist.json +++ b/config/mobile-localization-coverage-allowlist.json @@ -6,13 +6,6 @@ "dynamic": false, "count": 1 }, - { - "filePath": "mobile/src/browser/MobileBrowserAddressField.tsx", - "kind": "jsx-attribute:placeholder", - "text": "URL", - "dynamic": false, - "count": 1 - }, { "filePath": "mobile/src/browser/MobileBrowserKeyRow.tsx", "kind": "jsx-expression", @@ -20,69 +13,6 @@ "dynamic": false, "count": 1 }, - { - "filePath": "mobile/src/browser/MobileBrowserPaneView.tsx", - "kind": "jsx-attribute:accessibilityLabel", - "text": "Send text to browser", - "dynamic": false, - "count": 1 - }, - { - "filePath": "mobile/src/browser/MobileBrowserPaneView.tsx", - "kind": "jsx-attribute:label", - "text": "Back", - "dynamic": false, - "count": 1 - }, - { - "filePath": "mobile/src/browser/MobileBrowserPaneView.tsx", - "kind": "jsx-attribute:label", - "text": "Forward", - "dynamic": false, - "count": 1 - }, - { - "filePath": "mobile/src/browser/MobileBrowserPaneView.tsx", - "kind": "jsx-attribute:label", - "text": "Reload", - "dynamic": false, - "count": 1 - }, - { - "filePath": "mobile/src/browser/MobileBrowserPaneView.tsx", - "kind": "jsx-attribute:placeholder", - "text": "Type on page…", - "dynamic": false, - "count": 1 - }, - { - "filePath": "mobile/src/browser/MobileBrowserPaneView.tsx", - "kind": "jsx-text", - "text": "Browser Dialog", - "dynamic": false, - "count": 1 - }, - { - "filePath": "mobile/src/browser/MobileBrowserPaneView.tsx", - "kind": "jsx-text", - "text": "Cancel", - "dynamic": false, - "count": 1 - }, - { - "filePath": "mobile/src/browser/MobileBrowserPaneView.tsx", - "kind": "jsx-text", - "text": "OK", - "dynamic": false, - "count": 1 - }, - { - "filePath": "mobile/src/browser/MobileBrowserPointerModifiers.tsx", - "kind": "jsx-attribute:accessibilityLabel", - "text": "click modifier", - "dynamic": true, - "count": 1 - }, { "filePath": "mobile/src/browser/MobileBrowserPointerModifiers.tsx", "kind": "object-property:label", @@ -111,41 +41,6 @@ "dynamic": false, "count": 1 }, - { - "filePath": "mobile/src/browser/MobileBrowserViewModeSwitch.tsx", - "kind": "jsx-attribute:accessibilityLabel", - "text": "Show", - "dynamic": true, - "count": 1 - }, - { - "filePath": "mobile/src/browser/MobileBrowserViewModeSwitch.tsx", - "kind": "jsx-attribute:accessibilityLabel", - "text": "website view", - "dynamic": true, - "count": 1 - }, - { - "filePath": "mobile/src/browser/MobileBrowserViewModeSwitch.tsx", - "kind": "object-property:label", - "text": "Mobile", - "dynamic": false, - "count": 1 - }, - { - "filePath": "mobile/src/browser/MobileBrowserViewModeSwitch.tsx", - "kind": "object-property:label", - "text": "Web", - "dynamic": false, - "count": 1 - }, - { - "filePath": "mobile/src/browser/use-mobile-browser-commands.ts", - "kind": "object-property:error", - "text": "That answer did not reach the page.", - "dynamic": false, - "count": 1 - }, { "filePath": "mobile/src/components/accounts-snapshot.ts", "kind": "object-property:message", @@ -272,111 +167,6 @@ "dynamic": true, "count": 1 }, - { - "filePath": "mobile/src/files/mobile-file-explorer-row.tsx", - "kind": "jsx-attribute:accessibilityLabel", - "text": "Open folder", - "dynamic": true, - "count": 1 - }, - { - "filePath": "mobile/src/files/mobile-file-explorer-row.tsx", - "kind": "jsx-attribute:accessibilityLabel", - "text": "Preview file", - "dynamic": true, - "count": 1 - }, - { - "filePath": "mobile/src/files/mobile-file-explorer-row.tsx", - "kind": "jsx-attribute:accessibilityLabel", - "text": "Retry loading", - "dynamic": true, - "count": 1 - }, - { - "filePath": "mobile/src/files/mobile-file-explorer-row.tsx", - "kind": "jsx-attribute:accessibilityLabel", - "text": "unavailable on mobile", - "dynamic": true, - "count": 1 - }, - { - "filePath": "mobile/src/files/mobile-file-explorer-row.tsx", - "kind": "jsx-expression", - "text": "Unable to load folder", - "dynamic": false, - "count": 1 - }, - { - "filePath": "mobile/src/files/mobile-file-explorer-row.tsx", - "kind": "jsx-text", - "text": "Loading...", - "dynamic": false, - "count": 1 - }, - { - "filePath": "mobile/src/files/mobile-file-explorer-row.tsx", - "kind": "jsx-text", - "text": "Retry", - "dynamic": false, - "count": 1 - }, - { - "filePath": "mobile/src/files/mobile-file-explorer-row.tsx", - "kind": "jsx-text", - "text": "Unavailable on mobile", - "dynamic": false, - "count": 1 - }, - { - "filePath": "mobile/src/files/mobile-file-preview-request.ts", - "kind": "object-property:message", - "text": "File changed on desktop. Reload preview before saving", - "dynamic": false, - "count": 1 - }, - { - "filePath": "mobile/src/files/mobile-file-preview-response.ts", - "kind": "object-property:message", - "text": "Binary preview unavailable", - "dynamic": false, - "count": 1 - }, - { - "filePath": "mobile/src/files/mobile-file-preview-response.ts", - "kind": "object-property:message", - "text": "File not found", - "dynamic": false, - "count": 1 - }, - { - "filePath": "mobile/src/files/mobile-file-preview-response.ts", - "kind": "object-property:message", - "text": "File too large for mobile preview", - "dynamic": false, - "count": 1 - }, - { - "filePath": "mobile/src/files/mobile-file-preview-response.ts", - "kind": "object-property:message", - "text": "Reload preview before saving", - "dynamic": false, - "count": 1 - }, - { - "filePath": "mobile/src/files/mobile-file-preview-response.ts", - "kind": "object-property:message", - "text": "Unable to load preview", - "dynamic": false, - "count": 1 - }, - { - "filePath": "mobile/src/files/mobile-file-preview-response.ts", - "kind": "object-property:message", - "text": "Unable to reach the desktop filesystem", - "dynamic": false, - "count": 1 - }, { "filePath": "mobile/src/files/mobile-file-preview-route.ts", "kind": "object-property:message", @@ -384,265 +174,6 @@ "dynamic": false, "count": 3 }, - { - "filePath": "mobile/src/files/MobileFileExplorerPanel.tsx", - "kind": "jsx-attribute:accessibilityLabel", - "text": "Back to session", - "dynamic": false, - "count": 1 - }, - { - "filePath": "mobile/src/files/MobileFileExplorerPanel.tsx", - "kind": "jsx-attribute:accessibilityLabel", - "text": "Close files", - "dynamic": false, - "count": 1 - }, - { - "filePath": "mobile/src/files/MobileFileExplorerPanel.tsx", - "kind": "jsx-expression", - "text": "- Showing first 5000", - "dynamic": false, - "count": 1 - }, - { - "filePath": "mobile/src/files/MobileFileExplorerPanel.tsx", - "kind": "jsx-text", - "text": "Files", - "dynamic": false, - "count": 1 - }, - { - "filePath": "mobile/src/files/MobileFileExplorerPanel.tsx", - "kind": "jsx-text", - "text": "No files found", - "dynamic": false, - "count": 1 - }, - { - "filePath": "mobile/src/files/MobileFileExplorerPanel.tsx", - "kind": "object-property:error", - "text": "Folder load interrupted", - "dynamic": false, - "count": 1 - }, - { - "filePath": "mobile/src/files/MobileFileMarkdownPreview.tsx", - "kind": "jsx-attribute:accessibilityLabel", - "text": "Copy loaded Markdown source", - "dynamic": false, - "count": 1 - }, - { - "filePath": "mobile/src/files/MobileFileMarkdownPreview.tsx", - "kind": "jsx-attribute:accessibilityLabel", - "text": "Copy Markdown source", - "dynamic": false, - "count": 1 - }, - { - "filePath": "mobile/src/files/MobileFileMarkdownPreview.tsx", - "kind": "jsx-attribute:accessibilityLabel", - "text": "View Markdown source", - "dynamic": false, - "count": 1 - }, - { - "filePath": "mobile/src/files/MobileFileMarkdownPreview.tsx", - "kind": "jsx-attribute:accessibilityLabel", - "text": "View rendered Markdown preview", - "dynamic": false, - "count": 1 - }, - { - "filePath": "mobile/src/files/MobileFileMediaPreview.tsx", - "kind": "jsx-expression", - "text": "Downloading", - "dynamic": true, - "count": 1 - }, - { - "filePath": "mobile/src/files/MobileFileMediaPreview.tsx", - "kind": "jsx-expression", - "text": "Loading media...", - "dynamic": false, - "count": 1 - }, - { - "filePath": "mobile/src/files/MobileFileMediaPreview.tsx", - "kind": "jsx-expression", - "text": "of", - "dynamic": true, - "count": 1 - }, - { - "filePath": "mobile/src/files/MobileFileMediaPreview.tsx", - "kind": "jsx-text", - "text": "Waiting for desktop...", - "dynamic": false, - "count": 1 - }, - { - "filePath": "mobile/src/files/MobileFilePreviewBody.tsx", - "kind": "jsx-attribute:accessibilityLabel", - "text": "image", - "dynamic": true, - "count": 1 - }, - { - "filePath": "mobile/src/files/MobileFilePreviewBody.tsx", - "kind": "jsx-text", - "text": "Empty file", - "dynamic": false, - "count": 1 - }, - { - "filePath": "mobile/src/files/MobileFilePreviewEditableSource.tsx", - "kind": "jsx-attribute:accessibilityLabel", - "text": "editor", - "dynamic": true, - "count": 1 - }, - { - "filePath": "mobile/src/files/MobileFilePreviewScreen.tsx", - "kind": "jsx-attribute:accessibilityLabel", - "text": "Back to files", - "dynamic": false, - "count": 1 - }, - { - "filePath": "mobile/src/files/MobileFilePreviewScreen.tsx", - "kind": "jsx-attribute:accessibilityLabel", - "text": "Save terminal artifact", - "dynamic": false, - "count": 1 - }, - { - "filePath": "mobile/src/files/MobileFilePreviewScreen.tsx", - "kind": "jsx-attribute:message", - "text": "Unsaved edits will be lost.", - "dynamic": false, - "count": 1 - }, - { - "filePath": "mobile/src/files/MobileFilePreviewScreen.tsx", - "kind": "jsx-attribute:title", - "text": "Discard changes?", - "dynamic": false, - "count": 1 - }, - { - "filePath": "mobile/src/files/MobileFilePreviewScreen.tsx", - "kind": "jsx-attribute:title", - "text": "File", - "dynamic": false, - "count": 1 - }, - { - "filePath": "mobile/src/files/MobileFilePreviewScreen.tsx", - "kind": "jsx-expression", - "text": "Preview", - "dynamic": false, - "count": 1 - }, - { - "filePath": "mobile/src/files/MobileFilePreviewScreen.tsx", - "kind": "object-property:message", - "text": "Loading preview...", - "dynamic": false, - "count": 2 - }, - { - "filePath": "mobile/src/files/MobileFilePreviewScreen.tsx", - "kind": "object-property:message", - "text": "Unable to load preview", - "dynamic": false, - "count": 1 - }, - { - "filePath": "mobile/src/files/MobileFilePreviewScreen.tsx", - "kind": "object-property:message", - "text": "Waiting for desktop...", - "dynamic": false, - "count": 1 - }, - { - "filePath": "mobile/src/files/MobileFilePreviewScreen.tsx", - "kind": "user-visible-call", - "text": "Choose a file inside this workspace.", - "dynamic": false, - "count": 1 - }, - { - "filePath": "mobile/src/files/MobileFilePreviewScreen.tsx", - "kind": "user-visible-call", - "text": "Unable to open file", - "dynamic": false, - "count": 1 - }, - { - "filePath": "mobile/src/files/MobileFilePreviewSourceText.tsx", - "kind": "jsx-attribute:accessibilityLabel", - "text": "File preview", - "dynamic": false, - "count": 1 - }, - { - "filePath": "mobile/src/files/MobileFilePreviewSourceText.tsx", - "kind": "jsx-text", - "text": "Preview truncated. File size:", - "dynamic": false, - "count": 1 - }, - { - "filePath": "mobile/src/files/MobileMarkdownPreviewImage.tsx", - "kind": "jsx-attribute:accessibilityLabel", - "text": "Expand", - "dynamic": true, - "count": 1 - }, - { - "filePath": "mobile/src/files/MobileMarkdownPreviewImage.tsx", - "kind": "jsx-attribute:accessibilityLabel", - "text": "Markdown image", - "dynamic": false, - "count": 1 - }, - { - "filePath": "mobile/src/files/MobileMarkdownPreviewImage.tsx", - "kind": "jsx-attribute:label", - "text": "Markdown image", - "dynamic": false, - "count": 1 - }, - { - "filePath": "mobile/src/files/MobileMarkdownPreviewImage.tsx", - "kind": "object-property:error", - "text": "Unable to load image", - "dynamic": false, - "count": 1 - }, - { - "filePath": "mobile/src/files/MobilePdfPreview.tsx", - "kind": "jsx-attribute:label", - "text": "Open PDF", - "dynamic": false, - "count": 1 - }, - { - "filePath": "mobile/src/files/MobilePdfPreview.tsx", - "kind": "jsx-text", - "text": "Opening PDF...", - "dynamic": false, - "count": 1 - }, - { - "filePath": "mobile/src/files/MobilePdfPreview.tsx", - "kind": "jsx-text", - "text": "Update Orca Mobile to open PDFs", - "dynamic": false, - "count": 1 - }, { "filePath": "mobile/src/home/MobileHomeTopBar.tsx", "kind": "jsx-text", @@ -727,853 +258,6 @@ "dynamic": false, "count": 1 }, - { - "filePath": "mobile/src/session/mobile-bulk-close-sheet-actions.ts", - "kind": "object-property:label", - "text": "Close", - "dynamic": false, - "count": 1 - }, - { - "filePath": "mobile/src/session/mobile-bulk-close-sheet-actions.ts", - "kind": "object-property:label", - "text": "Close Other Tabs", - "dynamic": false, - "count": 1 - }, - { - "filePath": "mobile/src/session/mobile-diff-review-loaders.ts", - "kind": "object-property:message", - "text": "Update Orca desktop to review changes on mobile.", - "dynamic": false, - "count": 1 - }, - { - "filePath": "mobile/src/session/mobile-diff-review-queue.ts", - "kind": "object-property:subtitle", - "text": "Committed on branch", - "dynamic": false, - "count": 1 - }, - { - "filePath": "mobile/src/session/mobile-native-chat-attach-actions.ts", - "kind": "object-property:label", - "text": "Commands", - "dynamic": false, - "count": 1 - }, - { - "filePath": "mobile/src/session/mobile-native-chat-attach-actions.ts", - "kind": "object-property:label", - "text": "Files", - "dynamic": false, - "count": 1 - }, - { - "filePath": "mobile/src/session/mobile-native-chat-attach-actions.ts", - "kind": "object-property:label", - "text": "Photos", - "dynamic": false, - "count": 1 - }, - { - "filePath": "mobile/src/session/mobile-native-chat-attach-actions.ts", - "kind": "object-property:label", - "text": "Skills", - "dynamic": false, - "count": 1 - }, - { - "filePath": "mobile/src/session/mobile-native-chat-attach-actions.ts", - "kind": "object-property:label", - "text": "Workspace file", - "dynamic": false, - "count": 1 - }, - { - "filePath": "mobile/src/session/mobile-native-chat-cli-fork.ts", - "kind": "object-property:label", - "text": "Resume in New CLI", - "dynamic": false, - "count": 1 - }, - { - "filePath": "mobile/src/session/mobile-session-quick-command-launch.ts", - "kind": "user-visible-call", - "text": "Edit this quick command before running it", - "dynamic": false, - "count": 1 - }, - { - "filePath": "mobile/src/session/mobile-source-control-launch-agent.ts", - "kind": "object-property:message", - "text": "No enabled AI agent was detected on this workspace host.", - "dynamic": false, - "count": 1 - }, - { - "filePath": "mobile/src/session/mobile-source-control-launch-agent.ts", - "kind": "object-property:message", - "text": "The saved agent for this action is not available on this workspace host.", - "dynamic": false, - "count": 1 - }, - { - "filePath": "mobile/src/session/mobile-structured-composer-command.ts", - "kind": "object-property:error", - "text": "Conversation operation was not confirmed.", - "dynamic": false, - "count": 1 - }, - { - "filePath": "mobile/src/session/mobile-structured-send-delivery.ts", - "kind": "object-property:error", - "text": "Message not sent", - "dynamic": false, - "count": 1 - }, - { - "filePath": "mobile/src/session/mobile-terminal-action-sheet-actions.ts", - "kind": "object-property:label", - "text": "Clear Terminal", - "dynamic": false, - "count": 1 - }, - { - "filePath": "mobile/src/session/mobile-terminal-action-sheet-actions.ts", - "kind": "object-property:label", - "text": "Close", - "dynamic": false, - "count": 1 - }, - { - "filePath": "mobile/src/session/mobile-terminal-action-sheet-actions.ts", - "kind": "object-property:label", - "text": "Rename", - "dynamic": false, - "count": 1 - }, - { - "filePath": "mobile/src/session/mobile-terminal-viewport-resubscribe.ts", - "kind": "user-visible-call", - "text": "Couldn't fit the terminal to this screen", - "dynamic": false, - "count": 1 - }, - { - "filePath": "mobile/src/session/MobileBrowserTabActionSheet.tsx", - "kind": "jsx-attribute:title", - "text": "Browser", - "dynamic": false, - "count": 1 - }, - { - "filePath": "mobile/src/session/MobileBrowserTabActionSheet.tsx", - "kind": "object-property:label", - "text": "Back", - "dynamic": false, - "count": 1 - }, - { - "filePath": "mobile/src/session/MobileBrowserTabActionSheet.tsx", - "kind": "object-property:label", - "text": "Close", - "dynamic": false, - "count": 1 - }, - { - "filePath": "mobile/src/session/MobileBrowserTabActionSheet.tsx", - "kind": "object-property:label", - "text": "Forward", - "dynamic": false, - "count": 1 - }, - { - "filePath": "mobile/src/session/MobileBrowserTabActionSheet.tsx", - "kind": "object-property:label", - "text": "Reload", - "dynamic": false, - "count": 1 - }, - { - "filePath": "mobile/src/session/MobileDiffCommentLineRow.tsx", - "kind": "jsx-attribute:accessibilityLabel", - "text": "Add note on line", - "dynamic": true, - "count": 1 - }, - { - "filePath": "mobile/src/session/MobileDiffCommentLineRow.tsx", - "kind": "jsx-attribute:accessibilityLabel", - "text": "Delete note on line", - "dynamic": true, - "count": 1 - }, - { - "filePath": "mobile/src/session/MobileDiffCommentLineRow.tsx", - "kind": "jsx-attribute:accessibilityLabel", - "text": "diff line", - "dynamic": true, - "count": 1 - }, - { - "filePath": "mobile/src/session/MobileDiffCommentLineRow.tsx", - "kind": "jsx-attribute:placeholder", - "text": "Add review note", - "dynamic": false, - "count": 1 - }, - { - "filePath": "mobile/src/session/MobileDiffCommentLineRow.tsx", - "kind": "jsx-text", - "text": "Cancel", - "dynamic": false, - "count": 1 - }, - { - "filePath": "mobile/src/session/MobileDiffCommentLineRow.tsx", - "kind": "jsx-text", - "text": "Line", - "dynamic": false, - "count": 1 - }, - { - "filePath": "mobile/src/session/MobileDiffCommentLineRow.tsx", - "kind": "jsx-text", - "text": "Save note", - "dynamic": false, - "count": 1 - }, - { - "filePath": "mobile/src/session/MobileMarkdownReader.tsx", - "kind": "jsx-attribute:accessibilityHint", - "text": "Hides the software keyboard and keeps the markdown editor open.", - "dynamic": false, - "count": 1 - }, - { - "filePath": "mobile/src/session/MobileMarkdownReader.tsx", - "kind": "jsx-attribute:accessibilityLabel", - "text": "Dismiss keyboard", - "dynamic": false, - "count": 1 - }, - { - "filePath": "mobile/src/session/MobileMarkdownReader.tsx", - "kind": "jsx-text", - "text": "Copy", - "dynamic": false, - "count": 1 - }, - { - "filePath": "mobile/src/session/MobileMarkdownReader.tsx", - "kind": "jsx-text", - "text": "Discard", - "dynamic": false, - "count": 1 - }, - { - "filePath": "mobile/src/session/MobileMarkdownReader.tsx", - "kind": "jsx-text", - "text": "Refresh", - "dynamic": false, - "count": 1 - }, - { - "filePath": "mobile/src/session/MobileMarkdownReader.tsx", - "kind": "jsx-text", - "text": "Save", - "dynamic": false, - "count": 1 - }, - { - "filePath": "mobile/src/session/MobileNativeChatCardHeaderAction.tsx", - "kind": "jsx-attribute:accessibilityLabel", - "text": "Cancel", - "dynamic": false, - "count": 1 - }, - { - "filePath": "mobile/src/session/MobileNativeChatCardHeaderAction.tsx", - "kind": "jsx-attribute:accessibilityLabel", - "text": "Collapse", - "dynamic": false, - "count": 1 - }, - { - "filePath": "mobile/src/session/MobileNativeChatFloatingControls.tsx", - "kind": "jsx-attribute:accessibilityLabel", - "text": "Scroll to latest", - "dynamic": false, - "count": 1 - }, - { - "filePath": "mobile/src/session/MobileNativeChatFullContent.tsx", - "kind": "jsx-attribute:label", - "text": "Show full message", - "dynamic": false, - "count": 1 - }, - { - "filePath": "mobile/src/session/MobileNativeChatFullContent.tsx", - "kind": "jsx-attribute:label", - "text": "Show full output", - "dynamic": false, - "count": 1 - }, - { - "filePath": "mobile/src/session/MobileNativeChatFullContent.tsx", - "kind": "jsx-expression", - "text": "— couldn't load, tap to retry", - "dynamic": true, - "count": 1 - }, - { - "filePath": "mobile/src/session/MobileNativeChatFullContent.tsx", - "kind": "jsx-text", - "text": "Showing part of this message.", - "dynamic": false, - "count": 1 - }, - { - "filePath": "mobile/src/session/MobileQueuedCardText.tsx", - "kind": "jsx-attribute:accessibilityHint", - "text": "Shows less of the message", - "dynamic": false, - "count": 1 - }, - { - "filePath": "mobile/src/session/MobileQueuedCardText.tsx", - "kind": "jsx-attribute:accessibilityHint", - "text": "Shows the whole message", - "dynamic": false, - "count": 1 - }, - { - "filePath": "mobile/src/session/MobileQueuedCardText.tsx", - "kind": "jsx-expression", - "text": "Show less", - "dynamic": false, - "count": 1 - }, - { - "filePath": "mobile/src/session/MobileQueuedCardText.tsx", - "kind": "jsx-expression", - "text": "Show more", - "dynamic": false, - "count": 1 - }, - { - "filePath": "mobile/src/session/MobileQueuedMessageCardRow.tsx", - "kind": "jsx-attribute:accessibilityHint", - "text": "Shows less of the message", - "dynamic": false, - "count": 1 - }, - { - "filePath": "mobile/src/session/MobileQueuedMessageCardRow.tsx", - "kind": "jsx-attribute:accessibilityLabel", - "text": "Delete this queued message", - "dynamic": false, - "count": 1 - }, - { - "filePath": "mobile/src/session/MobileQueuedMessageCardRow.tsx", - "kind": "jsx-attribute:accessibilityLabel", - "text": "images", - "dynamic": true, - "count": 1 - }, - { - "filePath": "mobile/src/session/MobileQueuedMessageCardRow.tsx", - "kind": "jsx-attribute:accessibilityLabel", - "text": "More actions", - "dynamic": false, - "count": 1 - }, - { - "filePath": "mobile/src/session/MobileQueuedMessageCardRow.tsx", - "kind": "jsx-attribute:accessibilityLabel", - "text": "Send this message", - "dynamic": false, - "count": 1 - }, - { - "filePath": "mobile/src/session/MobileQueuedMessageCardRow.tsx", - "kind": "jsx-attribute:accessibilityLabel", - "text": "Send this message again", - "dynamic": false, - "count": 1 - }, - { - "filePath": "mobile/src/session/MobileQueuedMessageCardRow.tsx", - "kind": "jsx-attribute:accessibilityLabel", - "text": "Submit without interrupting the model", - "dynamic": false, - "count": 1 - }, - { - "filePath": "mobile/src/session/MobileQueuedMessageCardRow.tsx", - "kind": "jsx-expression", - "text": "Send", - "dynamic": false, - "count": 1 - }, - { - "filePath": "mobile/src/session/MobileQueuedMessageCardRow.tsx", - "kind": "jsx-expression", - "text": "Steer", - "dynamic": false, - "count": 1 - }, - { - "filePath": "mobile/src/session/MobileQueuedMessageComposer.tsx", - "kind": "jsx-text", - "text": "Editing message", - "dynamic": false, - "count": 1 - }, - { - "filePath": "mobile/src/session/MobileSessionAccessoryKeys.tsx", - "kind": "jsx-attribute:accessibilityHint", - "text": "Hides the software keyboard and keeps the current terminal session open.", - "dynamic": false, - "count": 1 - }, - { - "filePath": "mobile/src/session/MobileSessionAccessoryKeys.tsx", - "kind": "jsx-attribute:accessibilityLabel", - "text": "Add custom shortcut", - "dynamic": false, - "count": 1 - }, - { - "filePath": "mobile/src/session/MobileSessionAccessoryKeys.tsx", - "kind": "jsx-attribute:accessibilityLabel", - "text": "Dismiss keyboard", - "dynamic": false, - "count": 1 - }, - { - "filePath": "mobile/src/session/MobileSessionAccessoryKeys.tsx", - "kind": "jsx-attribute:accessibilityLabel", - "text": "Paste from clipboard", - "dynamic": false, - "count": 1 - }, - { - "filePath": "mobile/src/session/MobileSessionAccessoryKeys.tsx", - "kind": "jsx-attribute:accessibilityLabel", - "text": "Send", - "dynamic": true, - "count": 2 - }, - { - "filePath": "mobile/src/session/MobileSessionAccessoryKeys.tsx", - "kind": "jsx-attribute:accessibilityLabel", - "text": "Switch to buffered command input", - "dynamic": false, - "count": 1 - }, - { - "filePath": "mobile/src/session/MobileSessionAccessoryKeys.tsx", - "kind": "jsx-attribute:accessibilityLabel", - "text": "Switch to desktop mode", - "dynamic": false, - "count": 1 - }, - { - "filePath": "mobile/src/session/MobileSessionAccessoryKeys.tsx", - "kind": "jsx-attribute:accessibilityLabel", - "text": "Switch to live terminal input", - "dynamic": false, - "count": 1 - }, - { - "filePath": "mobile/src/session/MobileSessionAccessoryKeys.tsx", - "kind": "jsx-attribute:accessibilityLabel", - "text": "Switch to phone mode", - "dynamic": false, - "count": 1 - }, - { - "filePath": "mobile/src/session/MobileSessionAccessoryKeys.tsx", - "kind": "jsx-text", - "text": "Paste", - "dynamic": false, - "count": 1 - }, - { - "filePath": "mobile/src/session/MobileSessionActiveContent.tsx", - "kind": "jsx-attribute:accessibilityLabel", - "text": "Retry loading terminal", - "dynamic": false, - "count": 1 - }, - { - "filePath": "mobile/src/session/MobileSessionActiveContent.tsx", - "kind": "jsx-attribute:title", - "text": "File", - "dynamic": false, - "count": 1 - }, - { - "filePath": "mobile/src/session/MobileSessionActiveContent.tsx", - "kind": "jsx-expression", - "text": "Create Tab", - "dynamic": false, - "count": 1 - }, - { - "filePath": "mobile/src/session/MobileSessionActiveContent.tsx", - "kind": "jsx-expression", - "text": "Creating...", - "dynamic": false, - "count": 1 - }, - { - "filePath": "mobile/src/session/MobileSessionActiveContent.tsx", - "kind": "jsx-expression", - "text": "Loading terminal", - "dynamic": false, - "count": 1 - }, - { - "filePath": "mobile/src/session/MobileSessionActiveContent.tsx", - "kind": "jsx-expression", - "text": "Terminal is taking longer than expected", - "dynamic": false, - "count": 1 - }, - { - "filePath": "mobile/src/session/MobileSessionActiveContent.tsx", - "kind": "jsx-text", - "text": "No tabs in this session", - "dynamic": false, - "count": 1 - }, - { - "filePath": "mobile/src/session/MobileSessionActiveContent.tsx", - "kind": "jsx-text", - "text": "Retry", - "dynamic": false, - "count": 1 - }, - { - "filePath": "mobile/src/session/MobileSessionCommandDock.tsx", - "kind": "jsx-attribute:accessibilityHint", - "text": "Typed text is sent directly to the active terminal", - "dynamic": false, - "count": 1 - }, - { - "filePath": "mobile/src/session/MobileSessionCommandDock.tsx", - "kind": "jsx-attribute:accessibilityLabel", - "text": "Live terminal input", - "dynamic": false, - "count": 1 - }, - { - "filePath": "mobile/src/session/MobileSessionCommandDock.tsx", - "kind": "jsx-attribute:accessibilityLabel", - "text": "Send command", - "dynamic": false, - "count": 1 - }, - { - "filePath": "mobile/src/session/MobileSessionCommandDock.tsx", - "kind": "jsx-attribute:placeholder", - "text": "Live input", - "dynamic": false, - "count": 1 - }, - { - "filePath": "mobile/src/session/MobileSessionCommandDock.tsx", - "kind": "jsx-attribute:placeholder", - "text": "Type a command…", - "dynamic": false, - "count": 1 - }, - { - "filePath": "mobile/src/session/MobileSessionContentRow.tsx", - "kind": "jsx-attribute:accessibilityLabel", - "text": "Dismiss workspace creation warning", - "dynamic": false, - "count": 1 - }, - { - "filePath": "mobile/src/session/MobileSessionDiffLineRow.tsx", - "kind": "jsx-attribute:accessibilityLabel", - "text": "Add note on line", - "dynamic": true, - "count": 1 - }, - { - "filePath": "mobile/src/session/MobileSessionDiffLineRow.tsx", - "kind": "jsx-attribute:accessibilityLabel", - "text": "Delete note on line", - "dynamic": true, - "count": 1 - }, - { - "filePath": "mobile/src/session/MobileSessionDiffLineRow.tsx", - "kind": "jsx-attribute:accessibilityLabel", - "text": "diff line", - "dynamic": true, - "count": 1 - }, - { - "filePath": "mobile/src/session/MobileSessionDiffLineRow.tsx", - "kind": "jsx-attribute:placeholder", - "text": "Add review note", - "dynamic": false, - "count": 1 - }, - { - "filePath": "mobile/src/session/MobileSessionDiffLineRow.tsx", - "kind": "jsx-text", - "text": "Cancel", - "dynamic": false, - "count": 1 - }, - { - "filePath": "mobile/src/session/MobileSessionDiffLineRow.tsx", - "kind": "jsx-text", - "text": "Line", - "dynamic": false, - "count": 1 - }, - { - "filePath": "mobile/src/session/MobileSessionDiffLineRow.tsx", - "kind": "jsx-text", - "text": "Save note", - "dynamic": false, - "count": 1 - }, - { - "filePath": "mobile/src/session/MobileSessionFileReader.tsx", - "kind": "jsx-attribute:accessibilityLabel", - "text": "Copy review notes", - "dynamic": false, - "count": 1 - }, - { - "filePath": "mobile/src/session/MobileSessionFileReader.tsx", - "kind": "jsx-attribute:accessibilityLabel", - "text": "image", - "dynamic": true, - "count": 1 - }, - { - "filePath": "mobile/src/session/MobileSessionFileReader.tsx", - "kind": "jsx-attribute:accessibilityLabel", - "text": "preview", - "dynamic": true, - "count": 1 - }, - { - "filePath": "mobile/src/session/MobileSessionFileReader.tsx", - "kind": "jsx-attribute:accessibilityLabel", - "text": "Send review notes to AI", - "dynamic": false, - "count": 1 - }, - { - "filePath": "mobile/src/session/MobileSessionFileReader.tsx", - "kind": "jsx-expression", - "text": "No review notes", - "dynamic": false, - "count": 1 - }, - { - "filePath": "mobile/src/session/MobileSessionFileReader.tsx", - "kind": "jsx-expression", - "text": "review", - "dynamic": true, - "count": 1 - }, - { - "filePath": "mobile/src/session/MobileSessionFileReader.tsx", - "kind": "jsx-text", - "text": "Copy", - "dynamic": false, - "count": 1 - }, - { - "filePath": "mobile/src/session/MobileSessionFileReader.tsx", - "kind": "jsx-text", - "text": "Send", - "dynamic": false, - "count": 1 - }, - { - "filePath": "mobile/src/session/MobileSessionHeader.tsx", - "kind": "jsx-attribute:accessibilityLabel", - "text": "Back to worktrees", - "dynamic": false, - "count": 1 - }, - { - "filePath": "mobile/src/session/MobileSessionHeader.tsx", - "kind": "jsx-attribute:accessibilityLabel", - "text": "More session actions", - "dynamic": false, - "count": 1 - }, - { - "filePath": "mobile/src/session/MobileSessionHeader.tsx", - "kind": "jsx-attribute:accessibilityLabel", - "text": "Open file explorer", - "dynamic": false, - "count": 1 - }, - { - "filePath": "mobile/src/session/MobileSessionHeader.tsx", - "kind": "jsx-attribute:accessibilityLabel", - "text": "Open source control", - "dynamic": false, - "count": 1 - }, - { - "filePath": "mobile/src/session/MobileSessionHeader.tsx", - "kind": "jsx-attribute:accessibilityLabel", - "text": "Reconnect to desktop", - "dynamic": false, - "count": 1 - }, - { - "filePath": "mobile/src/session/MobileSessionHeader.tsx", - "kind": "jsx-attribute:accessibilityLabel", - "text": "Show sidebar", - "dynamic": false, - "count": 1 - }, - { - "filePath": "mobile/src/session/MobileSessionHeader.tsx", - "kind": "jsx-expression", - "text": "Terminal", - "dynamic": false, - "count": 1 - }, - { - "filePath": "mobile/src/session/MobileSessionHeaderMoreActionsSheet.tsx", - "kind": "object-property:label", - "text": "Agent History", - "dynamic": false, - "count": 1 - }, - { - "filePath": "mobile/src/session/MobileSessionHeaderMoreActionsSheet.tsx", - "kind": "object-property:label", - "text": "Checks", - "dynamic": false, - "count": 1 - }, - { - "filePath": "mobile/src/session/MobileSessionMarkdownReader.tsx", - "kind": "jsx-attribute:accessibilityLabel", - "text": "Copy loaded Markdown source", - "dynamic": false, - "count": 1 - }, - { - "filePath": "mobile/src/session/MobileSessionMarkdownReader.tsx", - "kind": "jsx-attribute:accessibilityLabel", - "text": "Copy Markdown path", - "dynamic": false, - "count": 1 - }, - { - "filePath": "mobile/src/session/MobileSessionMarkdownReader.tsx", - "kind": "jsx-attribute:accessibilityLabel", - "text": "Copy Markdown source", - "dynamic": false, - "count": 1 - }, - { - "filePath": "mobile/src/session/MobileSessionMarkdownReader.tsx", - "kind": "jsx-expression", - "text": "Copy", - "dynamic": false, - "count": 1 - }, - { - "filePath": "mobile/src/session/MobileSessionMarkdownReader.tsx", - "kind": "jsx-expression", - "text": "Copy loaded", - "dynamic": false, - "count": 1 - }, - { - "filePath": "mobile/src/session/MobileSessionMarkdownReader.tsx", - "kind": "jsx-text", - "text": "Copy path", - "dynamic": false, - "count": 1 - }, - { - "filePath": "mobile/src/session/MobileSessionMarkdownReader.tsx", - "kind": "jsx-text", - "text": "Discard", - "dynamic": false, - "count": 1 - }, - { - "filePath": "mobile/src/session/MobileSessionMarkdownReader.tsx", - "kind": "jsx-text", - "text": "Refresh", - "dynamic": false, - "count": 1 - }, - { - "filePath": "mobile/src/session/MobileSessionMarkdownReader.tsx", - "kind": "jsx-text", - "text": "Save", - "dynamic": false, - "count": 1 - }, - { - "filePath": "mobile/src/session/MobileSessionRouteScreen.tsx", - "kind": "jsx-text", - "text": "Opening workspace", - "dynamic": false, - "count": 1 - }, - { - "filePath": "mobile/src/session/MobileSessionSheets.tsx", - "kind": "jsx-attribute:message", - "text": "Choose an agent session for the current notes.", - "dynamic": false, - "count": 1 - }, - { - "filePath": "mobile/src/session/MobileSessionSheets.tsx", - "kind": "jsx-attribute:message", - "text": "Copy or discard phone drafts before leaving.", - "dynamic": false, - "count": 1 - }, - { - "filePath": "mobile/src/session/MobileSessionSheets.tsx", - "kind": "jsx-attribute:message", - "text": "Enter a URL, or leave blank for a new tab.", - "dynamic": false, - "count": 1 - }, - { - "filePath": "mobile/src/session/MobileSessionSheets.tsx", - "kind": "jsx-attribute:message", - "text": "Remove this custom shortcut?", - "dynamic": false, - "count": 1 - }, - { - "filePath": "mobile/src/session/MobileSessionSheets.tsx", - "kind": "jsx-attribute:message", - "text": "Replace the phone draft with the latest desktop file?", - "dynamic": false, - "count": 1 - }, { "filePath": "mobile/src/session/MobileSessionSheets.tsx", "kind": "jsx-attribute:placeholder", @@ -1581,286 +265,6 @@ "dynamic": false, "count": 1 }, - { - "filePath": "mobile/src/session/MobileSessionSheets.tsx", - "kind": "jsx-attribute:placeholder", - "text": "Terminal name", - "dynamic": false, - "count": 1 - }, - { - "filePath": "mobile/src/session/MobileSessionSheets.tsx", - "kind": "jsx-attribute:title", - "text": "Chat", - "dynamic": false, - "count": 1 - }, - { - "filePath": "mobile/src/session/MobileSessionSheets.tsx", - "kind": "jsx-attribute:title", - "text": "Discard Changes", - "dynamic": false, - "count": 1 - }, - { - "filePath": "mobile/src/session/MobileSessionSheets.tsx", - "kind": "jsx-attribute:title", - "text": "File", - "dynamic": false, - "count": 1 - }, - { - "filePath": "mobile/src/session/MobileSessionSheets.tsx", - "kind": "jsx-attribute:title", - "text": "Markdown", - "dynamic": false, - "count": 1 - }, - { - "filePath": "mobile/src/session/MobileSessionSheets.tsx", - "kind": "jsx-attribute:title", - "text": "New Browser", - "dynamic": false, - "count": 1 - }, - { - "filePath": "mobile/src/session/MobileSessionSheets.tsx", - "kind": "jsx-attribute:title", - "text": "New Tab", - "dynamic": false, - "count": 1 - }, - { - "filePath": "mobile/src/session/MobileSessionSheets.tsx", - "kind": "jsx-attribute:title", - "text": "Rename Terminal", - "dynamic": false, - "count": 1 - }, - { - "filePath": "mobile/src/session/MobileSessionSheets.tsx", - "kind": "jsx-attribute:title", - "text": "Send Review Notes", - "dynamic": false, - "count": 1 - }, - { - "filePath": "mobile/src/session/MobileSessionSheets.tsx", - "kind": "jsx-attribute:title", - "text": "Shortcut", - "dynamic": false, - "count": 1 - }, - { - "filePath": "mobile/src/session/MobileSessionSheets.tsx", - "kind": "jsx-attribute:title", - "text": "Terminal", - "dynamic": false, - "count": 1 - }, - { - "filePath": "mobile/src/session/MobileSessionSheets.tsx", - "kind": "jsx-attribute:title", - "text": "Unsaved markdown changes", - "dynamic": false, - "count": 1 - }, - { - "filePath": "mobile/src/session/MobileSessionSheets.tsx", - "kind": "object-property:label", - "text": "Browser", - "dynamic": false, - "count": 1 - }, - { - "filePath": "mobile/src/session/MobileSessionSheets.tsx", - "kind": "object-property:label", - "text": "Copy All & Leave", - "dynamic": false, - "count": 1 - }, - { - "filePath": "mobile/src/session/MobileSessionSheets.tsx", - "kind": "object-property:label", - "text": "Copy Notes", - "dynamic": false, - "count": 1 - }, - { - "filePath": "mobile/src/session/MobileSessionSheets.tsx", - "kind": "object-property:label", - "text": "Copy Path", - "dynamic": false, - "count": 1 - }, - { - "filePath": "mobile/src/session/MobileSessionSheets.tsx", - "kind": "object-property:label", - "text": "Discard & Leave", - "dynamic": false, - "count": 1 - }, - { - "filePath": "mobile/src/session/MobileSessionSheets.tsx", - "kind": "object-property:label", - "text": "Markdown Note", - "dynamic": false, - "count": 1 - }, - { - "filePath": "mobile/src/session/MobileSessionSheets.tsx", - "kind": "object-property:label", - "text": "Refresh", - "dynamic": false, - "count": 2 - }, - { - "filePath": "mobile/src/session/MobileSessionSheets.tsx", - "kind": "object-property:label", - "text": "Remove", - "dynamic": false, - "count": 1 - }, - { - "filePath": "mobile/src/session/MobileSessionSheets.tsx", - "kind": "object-property:label", - "text": "Terminal", - "dynamic": false, - "count": 1 - }, - { - "filePath": "mobile/src/session/MobileSessionTabStrip.tsx", - "kind": "jsx-attribute:accessibilityLabel", - "text": "New tab", - "dynamic": false, - "count": 1 - }, - { - "filePath": "mobile/src/session/MobileSessionTabStrip.tsx", - "kind": "user-visible-call", - "text": "Checking desktop capabilities — try again in a moment", - "dynamic": false, - "count": 1 - }, - { - "filePath": "mobile/src/session/MobileSessionTabStrip.tsx", - "kind": "user-visible-call", - "text": "Desktop update required for quick commands", - "dynamic": false, - "count": 1 - }, - { - "filePath": "mobile/src/session/MobileTerminalInputActions.tsx", - "kind": "jsx-attribute:accessibilityHint", - "text": "Long press to attach a file instead", - "dynamic": false, - "count": 1 - }, - { - "filePath": "mobile/src/session/MobileTerminalInputActions.tsx", - "kind": "jsx-attribute:accessibilityLabel", - "text": "Attach a photo", - "dynamic": false, - "count": 1 - }, - { - "filePath": "mobile/src/session/MobileTerminalInputActions.tsx", - "kind": "jsx-attribute:accessibilityLabel", - "text": "Sending image", - "dynamic": false, - "count": 1 - }, - { - "filePath": "mobile/src/session/MobileTerminalInputActions.tsx", - "kind": "jsx-attribute:accessibilityLabel", - "text": "Start voice dictation", - "dynamic": false, - "count": 1 - }, - { - "filePath": "mobile/src/session/MobileTerminalInputActions.tsx", - "kind": "jsx-attribute:accessibilityLabel", - "text": "Starting voice dictation", - "dynamic": false, - "count": 1 - }, - { - "filePath": "mobile/src/session/MobileTerminalInputActions.tsx", - "kind": "jsx-attribute:accessibilityLabel", - "text": "Stop voice dictation", - "dynamic": false, - "count": 1 - }, - { - "filePath": "mobile/src/session/MobileTerminalInputActions.tsx", - "kind": "jsx-attribute:accessibilityLabel", - "text": "Transcribing voice dictation", - "dynamic": false, - "count": 1 - }, - { - "filePath": "mobile/src/session/native-chat/composer/MobileNativeChatComposer.tsx", - "kind": "jsx-attribute:accessibilityLabel", - "text": "Cancel editing", - "dynamic": false, - "count": 1 - }, - { - "filePath": "mobile/src/session/native-chat/composer/MobileNativeChatComposer.tsx", - "kind": "jsx-attribute:accessibilityLabel", - "text": "Save edited message", - "dynamic": false, - "count": 1 - }, - { - "filePath": "mobile/src/session/native-chat/composer/MobileNativeChatComposer.tsx", - "kind": "jsx-attribute:accessibilityLabel", - "text": "Send message", - "dynamic": false, - "count": 1 - }, - { - "filePath": "mobile/src/session/native-chat/composer/MobileNativeChatComposer.tsx", - "kind": "jsx-text", - "text": "Cancel", - "dynamic": false, - "count": 1 - }, - { - "filePath": "mobile/src/session/native-chat/composer/MobileNativeChatComposer.tsx", - "kind": "jsx-text", - "text": "Save", - "dynamic": false, - "count": 1 - }, - { - "filePath": "mobile/src/session/native-chat/composer/MobileNativeChatComposerAttachments.tsx", - "kind": "jsx-attribute:accessibilityLabel", - "text": "Attached image", - "dynamic": false, - "count": 1 - }, - { - "filePath": "mobile/src/session/native-chat/composer/MobileNativeChatComposerAttachments.tsx", - "kind": "jsx-attribute:accessibilityLabel", - "text": "Uploading", - "dynamic": true, - "count": 1 - }, - { - "filePath": "mobile/src/session/native-chat/composer/MobileNativeChatComposerAttachments.tsx", - "kind": "jsx-attribute:label", - "text": "Remove", - "dynamic": true, - "count": 1 - }, - { - "filePath": "mobile/src/session/native-chat/composer/MobileNativeChatComposerAttachments.tsx", - "kind": "jsx-attribute:label", - "text": "Remove image", - "dynamic": false, - "count": 1 - }, { "filePath": "mobile/src/session/native-chat/composer/MobileNativeChatComposerMic.tsx", "kind": "jsx-attribute:accessibilityLabel", @@ -1889,440 +293,6 @@ "dynamic": false, "count": 1 }, - { - "filePath": "mobile/src/session/native-chat/composer/use-mobile-native-chat-image-upload.ts", - "kind": "user-visible-call", - "text": "Attach failed", - "dynamic": false, - "count": 1 - }, - { - "filePath": "mobile/src/session/native-chat/composer/use-mobile-native-chat-image-upload.ts", - "kind": "user-visible-call", - "text": "Attach failed (disconnected)", - "dynamic": false, - "count": 1 - }, - { - "filePath": "mobile/src/session/native-chat/composer/use-mobile-native-chat-image-upload.ts", - "kind": "user-visible-call", - "text": "Image too large to attach", - "dynamic": false, - "count": 1 - }, - { - "filePath": "mobile/src/session/native-chat/composer/use-mobile-native-chat-image-upload.ts", - "kind": "user-visible-call", - "text": "Photo permission denied", - "dynamic": false, - "count": 1 - }, - { - "filePath": "mobile/src/session/native-chat/input/MobileNativeChatAsk.tsx", - "kind": "jsx-attribute:label", - "text": "Other…", - "dynamic": false, - "count": 1 - }, - { - "filePath": "mobile/src/session/native-chat/input/MobileNativeChatAsk.tsx", - "kind": "jsx-attribute:placeholder", - "text": "Type your answer", - "dynamic": false, - "count": 1 - }, - { - "filePath": "mobile/src/session/native-chat/input/MobileNativeChatAsk.tsx", - "kind": "jsx-expression", - "text": "Step", - "dynamic": true, - "count": 1 - }, - { - "filePath": "mobile/src/session/native-chat/input/MobileNativeChatAsk.tsx", - "kind": "jsx-text", - "text": "Cancel", - "dynamic": false, - "count": 1 - }, - { - "filePath": "mobile/src/session/native-chat/input/MobileNativeChatPermission.tsx", - "kind": "jsx-text", - "text": "Needs access to:", - "dynamic": false, - "count": 1 - }, - { - "filePath": "mobile/src/session/native-chat/input/MobileNativeChatPermission.tsx", - "kind": "jsx-text", - "text": "Plan file:", - "dynamic": false, - "count": 1 - }, - { - "filePath": "mobile/src/session/native-chat/input/MobileNativeChatPermission.tsx", - "kind": "jsx-text", - "text": "Reason:", - "dynamic": false, - "count": 1 - }, - { - "filePath": "mobile/src/session/native-chat/input/MobileNativeChatPermission.tsx", - "kind": "jsx-text", - "text": "This request needs a newer version of Orca.", - "dynamic": false, - "count": 1 - }, - { - "filePath": "mobile/src/session/native-chat/input/MobileNativeChatPromptCard.tsx", - "kind": "jsx-attribute:accessibilityLabel", - "text": "Answer in terminal", - "dynamic": false, - "count": 1 - }, - { - "filePath": "mobile/src/session/native-chat/input/MobileNativeChatPromptCard.tsx", - "kind": "jsx-text", - "text": "Answer in terminal", - "dynamic": false, - "count": 1 - }, - { - "filePath": "mobile/src/session/native-chat/input/MobileNativeChatPromptCard.tsx", - "kind": "jsx-text", - "text": "Answer this OMP question in the terminal.", - "dynamic": false, - "count": 1 - }, - { - "filePath": "mobile/src/session/native-chat/input/MobileNativeChatPromptStrip.tsx", - "kind": "jsx-attribute:accessibilityLabel", - "text": "Expand", - "dynamic": false, - "count": 1 - }, - { - "filePath": "mobile/src/session/native-chat/input/MobileNativeChatQuestion.tsx", - "kind": "jsx-attribute:accessibilityLabel", - "text": "Send reply", - "dynamic": false, - "count": 1 - }, - { - "filePath": "mobile/src/session/native-chat/input/MobileNativeChatQuestion.tsx", - "kind": "jsx-attribute:accessibilityLabel", - "text": "Submit selected options", - "dynamic": false, - "count": 1 - }, - { - "filePath": "mobile/src/session/native-chat/input/MobileNativeChatQuestion.tsx", - "kind": "jsx-attribute:placeholder", - "text": "Or type a reply…", - "dynamic": false, - "count": 1 - }, - { - "filePath": "mobile/src/session/native-chat/input/MobileNativeChatQuestion.tsx", - "kind": "jsx-attribute:placeholder", - "text": "Type your reply…", - "dynamic": false, - "count": 1 - }, - { - "filePath": "mobile/src/session/native-chat/input/MobileNativeChatQuestion.tsx", - "kind": "jsx-text", - "text": "Submit", - "dynamic": false, - "count": 1 - }, - { - "filePath": "mobile/src/session/native-chat/input/MobileNativeChatQueuedMessages.tsx", - "kind": "jsx-attribute:accessibilityLabel", - "text": "Delete every queued message", - "dynamic": false, - "count": 1 - }, - { - "filePath": "mobile/src/session/native-chat/input/MobileNativeChatQueuedMessages.tsx", - "kind": "jsx-attribute:accessibilityLabel", - "text": "Queued messages", - "dynamic": false, - "count": 1 - }, - { - "filePath": "mobile/src/session/native-chat/input/MobileNativeChatQueuedMessages.tsx", - "kind": "jsx-attribute:accessibilityLabel", - "text": "Resume sending the queued messages", - "dynamic": false, - "count": 1 - }, - { - "filePath": "mobile/src/session/native-chat/input/MobileNativeChatQueuedMessages.tsx", - "kind": "jsx-expression", - "text": "Paused", - "dynamic": false, - "count": 1 - }, - { - "filePath": "mobile/src/session/native-chat/input/MobileNativeChatQueuedMessages.tsx", - "kind": "jsx-expression", - "text": "Queued", - "dynamic": false, - "count": 1 - }, - { - "filePath": "mobile/src/session/native-chat/input/MobileNativeChatQueuedMessages.tsx", - "kind": "jsx-text", - "text": "Clear all", - "dynamic": false, - "count": 1 - }, - { - "filePath": "mobile/src/session/native-chat/input/MobileNativeChatQueuedMessages.tsx", - "kind": "jsx-text", - "text": "Resume", - "dynamic": false, - "count": 1 - }, - { - "filePath": "mobile/src/session/native-chat/input/MobileNativeChatQueuedMessages.tsx", - "kind": "object-property:label", - "text": "Edit message", - "dynamic": false, - "count": 1 - }, - { - "filePath": "mobile/src/session/native-chat/input/MobileNativeChatSessionOptionPickers.tsx", - "kind": "jsx-attribute:accessibilityLabel", - "text": "Back to models", - "dynamic": false, - "count": 1 - }, - { - "filePath": "mobile/src/session/native-chat/input/MobileNativeChatSessionOptionPickers.tsx", - "kind": "jsx-attribute:accessibilityLabel", - "text": "Close picker", - "dynamic": false, - "count": 1 - }, - { - "filePath": "mobile/src/session/native-chat/input/MobileNativeChatSessionOptionPickers.tsx", - "kind": "jsx-attribute:label", - "text": "Permissions", - "dynamic": false, - "count": 1 - }, - { - "filePath": "mobile/src/session/native-chat/input/MobileNativeChatSessionOptionPickers.tsx", - "kind": "jsx-text", - "text": "Agent is working", - "dynamic": false, - "count": 1 - }, - { - "filePath": "mobile/src/session/native-chat/input/MobileNativeChatSessionOptionPickers.tsx", - "kind": "jsx-text", - "text": "Model settings unlock when this turn finishes.", - "dynamic": false, - "count": 1 - }, - { - "filePath": "mobile/src/session/native-chat/input/MobileNativeChatSessionOptionPickers.tsx", - "kind": "jsx-text", - "text": "Sent to the agent — not confirmed", - "dynamic": false, - "count": 1 - }, - { - "filePath": "mobile/src/session/native-chat/input/MobileNativeChatSessionOptionRows.tsx", - "kind": "jsx-expression", - "text": "Default", - "dynamic": false, - "count": 1 - }, - { - "filePath": "mobile/src/session/native-chat/input/MobileNativeChatSessionOptionRows.tsx", - "kind": "jsx-expression", - "text": "Not reported", - "dynamic": false, - "count": 1 - }, - { - "filePath": "mobile/src/session/native-chat/input/MobileNativeChatSessionOptionSegments.tsx", - "kind": "jsx-text", - "text": "Sent to the agent — not confirmed", - "dynamic": false, - "count": 1 - }, - { - "filePath": "mobile/src/session/native-chat/input/MobileNativeChatStopButton.tsx", - "kind": "jsx-attribute:accessibilityLabel", - "text": "Stop the agent", - "dynamic": false, - "count": 1 - }, - { - "filePath": "mobile/src/session/native-chat/mobile-native-chat-stream-frame.ts", - "kind": "object-property:error", - "text": "Transcript stream ended", - "dynamic": false, - "count": 1 - }, - { - "filePath": "mobile/src/session/native-chat/transcript/MobileNativeChatMessage.tsx", - "kind": "jsx-attribute:accessibilityLabel", - "text": "Copy message", - "dynamic": false, - "count": 1 - }, - { - "filePath": "mobile/src/session/native-chat/transcript/MobileNativeChatMessageActionsSheet.tsx", - "kind": "jsx-attribute:accessibilityLabel", - "text": "Close", - "dynamic": false, - "count": 1 - }, - { - "filePath": "mobile/src/session/native-chat/transcript/MobileNativeChatMessageActionsSheet.tsx", - "kind": "jsx-text", - "text": "Select text", - "dynamic": false, - "count": 1 - }, - { - "filePath": "mobile/src/session/native-chat/transcript/MobileNativeChatMessageActionsSheet.tsx", - "kind": "object-property:label", - "text": "Copy message", - "dynamic": false, - "count": 1 - }, - { - "filePath": "mobile/src/session/native-chat/transcript/MobileNativeChatMessageActionsSheet.tsx", - "kind": "object-property:label", - "text": "Select text", - "dynamic": false, - "count": 1 - }, - { - "filePath": "mobile/src/session/native-chat/transcript/MobileNativeChatProse.tsx", - "kind": "jsx-attribute:accessibilityHint", - "text": "Opens the image full size", - "dynamic": false, - "count": 1 - }, - { - "filePath": "mobile/src/session/native-chat/transcript/MobileNativeChatProse.tsx", - "kind": "jsx-expression", - "text": "image", - "dynamic": false, - "count": 1 - }, - { - "filePath": "mobile/src/session/native-chat/transcript/MobileNativeChatToolLine.tsx", - "kind": "jsx-attribute:accessibilityLabel", - "text": "Copy command", - "dynamic": false, - "count": 1 - }, - { - "filePath": "mobile/src/session/native-chat/transcript/MobileNativeChatToolLine.tsx", - "kind": "jsx-expression", - "text": "exit", - "dynamic": true, - "count": 1 - }, - { - "filePath": "mobile/src/session/native-chat/transcript/MobileNativeChatUserMessageFold.tsx", - "kind": "jsx-expression", - "text": "Show full message", - "dynamic": false, - "count": 1 - }, - { - "filePath": "mobile/src/session/native-chat/transcript/MobileNativeChatUserMessageFold.tsx", - "kind": "jsx-expression", - "text": "Show full message — couldn't load, tap to retry", - "dynamic": false, - "count": 1 - }, - { - "filePath": "mobile/src/session/native-chat/transcript/MobileNativeChatUserMessageFold.tsx", - "kind": "jsx-expression", - "text": "Show less", - "dynamic": false, - "count": 1 - }, - { - "filePath": "mobile/src/session/native-chat/visual/MobileNativeChatImageViewer.tsx", - "kind": "jsx-attribute:accessibilityLabel", - "text": "Close image", - "dynamic": false, - "count": 1 - }, - { - "filePath": "mobile/src/session/native-chat/visual/MobileNativeChatVisual.tsx", - "kind": "jsx-attribute:accessibilityHint", - "text": "Tries to load it again", - "dynamic": false, - "count": 1 - }, - { - "filePath": "mobile/src/session/native-chat/visual/MobileNativeChatVisual.tsx", - "kind": "jsx-attribute:accessibilityLabel", - "text": "Close", - "dynamic": false, - "count": 1 - }, - { - "filePath": "mobile/src/session/native-chat/visual/MobileNativeChatVisual.tsx", - "kind": "jsx-attribute:accessibilityLabel", - "text": "full screen", - "dynamic": true, - "count": 1 - }, - { - "filePath": "mobile/src/session/native-chat/visual/MobileNativeChatVisual.tsx", - "kind": "jsx-attribute:accessibilityLabel", - "text": "Open", - "dynamic": true, - "count": 1 - }, - { - "filePath": "mobile/src/session/native-chat/visual/MobileNativeChatVisual.tsx", - "kind": "jsx-attribute:accessibilityLabel", - "text": "Visualization unavailable", - "dynamic": false, - "count": 1 - }, - { - "filePath": "mobile/src/session/native-chat/visual/MobileNativeChatVisual.tsx", - "kind": "jsx-text", - "text": "Visualization unavailable", - "dynamic": false, - "count": 1 - }, - { - "filePath": "mobile/src/session/new-tab-agent-host-launch.ts", - "kind": "user-visible-call", - "text": "Notes sent", - "dynamic": false, - "count": 1 - }, - { - "filePath": "mobile/src/session/pr-ai-triage-launch.ts", - "kind": "object-property:message", - "text": "This action's saved prompt is empty. Update Source Control AI settings on your computer.", - "dynamic": false, - "count": 1 - }, - { - "filePath": "mobile/src/session/QuickCommandEditorForm.tsx", - "kind": "jsx-attribute:placeholder", - "text": "Ask the agent to investigate this workspace", - "dynamic": false, - "count": 1 - }, { "filePath": "mobile/src/session/QuickCommandEditorForm.tsx", "kind": "jsx-attribute:placeholder", @@ -2330,328 +300,6 @@ "dynamic": false, "count": 1 }, - { - "filePath": "mobile/src/session/QuickCommandEditorForm.tsx", - "kind": "jsx-attribute:placeholder", - "text": "Start dev server", - "dynamic": false, - "count": 1 - }, - { - "filePath": "mobile/src/session/QuickCommandEditorForm.tsx", - "kind": "jsx-expression", - "text": "Add Quick Command", - "dynamic": false, - "count": 1 - }, - { - "filePath": "mobile/src/session/QuickCommandEditorForm.tsx", - "kind": "jsx-expression", - "text": "Agent Prompt", - "dynamic": false, - "count": 1 - }, - { - "filePath": "mobile/src/session/QuickCommandEditorForm.tsx", - "kind": "jsx-expression", - "text": "characters", - "dynamic": true, - "count": 1 - }, - { - "filePath": "mobile/src/session/QuickCommandEditorForm.tsx", - "kind": "jsx-expression", - "text": "Command Text", - "dynamic": false, - "count": 1 - }, - { - "filePath": "mobile/src/session/QuickCommandEditorForm.tsx", - "kind": "jsx-expression", - "text": "Global", - "dynamic": false, - "count": 1 - }, - { - "filePath": "mobile/src/session/QuickCommandEditorForm.tsx", - "kind": "jsx-expression", - "text": "Project", - "dynamic": false, - "count": 1 - }, - { - "filePath": "mobile/src/session/QuickCommandEditorForm.tsx", - "kind": "jsx-expression", - "text": "Prompt", - "dynamic": false, - "count": 1 - }, - { - "filePath": "mobile/src/session/QuickCommandEditorForm.tsx", - "kind": "jsx-expression", - "text": "Save", - "dynamic": false, - "count": 1 - }, - { - "filePath": "mobile/src/session/QuickCommandEditorForm.tsx", - "kind": "jsx-expression", - "text": "Terminal Command", - "dynamic": false, - "count": 1 - }, - { - "filePath": "mobile/src/session/QuickCommandEditorForm.tsx", - "kind": "jsx-expression", - "text": "Too long for this host. Update Orca on the host to save longer prompts.", - "dynamic": false, - "count": 1 - }, - { - "filePath": "mobile/src/session/QuickCommandEditorForm.tsx", - "kind": "jsx-expression", - "text": "Too long to save.", - "dynamic": false, - "count": 1 - }, - { - "filePath": "mobile/src/session/QuickCommandEditorForm.tsx", - "kind": "jsx-text", - "text": "Action", - "dynamic": false, - "count": 1 - }, - { - "filePath": "mobile/src/session/QuickCommandEditorForm.tsx", - "kind": "jsx-text", - "text": "Advanced", - "dynamic": false, - "count": 1 - }, - { - "filePath": "mobile/src/session/QuickCommandEditorForm.tsx", - "kind": "jsx-text", - "text": "Agent", - "dynamic": false, - "count": 1 - }, - { - "filePath": "mobile/src/session/QuickCommandEditorForm.tsx", - "kind": "jsx-text", - "text": "Append Enter", - "dynamic": false, - "count": 1 - }, - { - "filePath": "mobile/src/session/QuickCommandEditorForm.tsx", - "kind": "jsx-text", - "text": "Cancel", - "dynamic": false, - "count": 1 - }, - { - "filePath": "mobile/src/session/QuickCommandEditorForm.tsx", - "kind": "jsx-text", - "text": "Choose agent", - "dynamic": false, - "count": 1 - }, - { - "filePath": "mobile/src/session/QuickCommandEditorForm.tsx", - "kind": "jsx-text", - "text": "Label", - "dynamic": false, - "count": 1 - }, - { - "filePath": "mobile/src/session/QuickCommandEditorForm.tsx", - "kind": "jsx-text", - "text": "Scope", - "dynamic": false, - "count": 1 - }, - { - "filePath": "mobile/src/session/QuickCommandEditorForm.tsx", - "kind": "jsx-text", - "text": "Submit immediately instead of only inserting text.", - "dynamic": false, - "count": 1 - }, - { - "filePath": "mobile/src/session/QuickCommandEditorForm.tsx", - "kind": "jsx-text", - "text": "Supports skills, file paths, and built-in commands.", - "dynamic": false, - "count": 1 - }, - { - "filePath": "mobile/src/session/QuickCommandRow.tsx", - "kind": "jsx-attribute:accessibilityLabel", - "text": "Delete", - "dynamic": true, - "count": 1 - }, - { - "filePath": "mobile/src/session/QuickCommandRow.tsx", - "kind": "jsx-attribute:accessibilityLabel", - "text": "Edit", - "dynamic": true, - "count": 1 - }, - { - "filePath": "mobile/src/session/QuickCommandRow.tsx", - "kind": "jsx-attribute:accessibilityLabel", - "text": "Run", - "dynamic": true, - "count": 1 - }, - { - "filePath": "mobile/src/session/QuickCommandsList.tsx", - "kind": "jsx-attribute:label", - "text": "Global", - "dynamic": false, - "count": 1 - }, - { - "filePath": "mobile/src/session/QuickCommandsList.tsx", - "kind": "jsx-attribute:label", - "text": "This project", - "dynamic": false, - "count": 1 - }, - { - "filePath": "mobile/src/session/QuickCommandsList.tsx", - "kind": "jsx-attribute:placeholder", - "text": "Search quick commands...", - "dynamic": false, - "count": 1 - }, - { - "filePath": "mobile/src/session/QuickCommandsList.tsx", - "kind": "jsx-text", - "text": "New quick command", - "dynamic": false, - "count": 1 - }, - { - "filePath": "mobile/src/session/QuickCommandsList.tsx", - "kind": "jsx-text", - "text": "No matching quick commands.", - "dynamic": false, - "count": 1 - }, - { - "filePath": "mobile/src/session/QuickCommandsList.tsx", - "kind": "jsx-text", - "text": "No quick commands yet.", - "dynamic": false, - "count": 1 - }, - { - "filePath": "mobile/src/session/QuickCommandsSheet.tsx", - "kind": "jsx-attribute:accessibilityLabel", - "text": "Back", - "dynamic": false, - "count": 1 - }, - { - "filePath": "mobile/src/session/QuickCommandsSheet.tsx", - "kind": "jsx-text", - "text": "Save terminal commands or agent prompts for quick access.", - "dynamic": false, - "count": 1 - }, - { - "filePath": "mobile/src/session/QuickCommandsSheet.tsx", - "kind": "user-visible-call", - "text": "Delete \"", - "dynamic": true, - "count": 1 - }, - { - "filePath": "mobile/src/session/QuickCommandsSheet.tsx", - "kind": "user-visible-call", - "text": "This quick command will be removed from your saved list.", - "dynamic": false, - "count": 1 - }, - { - "filePath": "mobile/src/session/QuickCommandsSheet.tsx", - "kind": "user-visible-call", - "text": "Untitled", - "dynamic": false, - "count": 1 - }, - { - "filePath": "mobile/src/session/QuickCommandsTabButton.tsx", - "kind": "jsx-attribute:accessibilityLabel", - "text": "Quick commands", - "dynamic": false, - "count": 1 - }, - { - "filePath": "mobile/src/session/session-side-rail.tsx", - "kind": "jsx-attribute:accessibilityLabel", - "text": "Back to worktrees", - "dynamic": false, - "count": 1 - }, - { - "filePath": "mobile/src/session/use-mobile-attachment-input-lease-gate.ts", - "kind": "user-visible-call", - "text": "Attach failed (reconnecting)", - "dynamic": false, - "count": 1 - }, - { - "filePath": "mobile/src/session/use-mobile-diff-review-controller.ts", - "kind": "object-property:message", - "text": "Missing worktree", - "dynamic": false, - "count": 1 - }, - { - "filePath": "mobile/src/session/use-mobile-diff-review-controller.ts", - "kind": "object-property:message", - "text": "Waiting for desktop...", - "dynamic": false, - "count": 1 - }, - { - "filePath": "mobile/src/session/use-mobile-diff-review-diff-loading.ts", - "kind": "object-property:message", - "text": "Waiting for desktop...", - "dynamic": false, - "count": 1 - }, - { - "filePath": "mobile/src/session/use-mobile-image-attachment.ts", - "kind": "user-visible-call", - "text": "Attach failed", - "dynamic": false, - "count": 1 - }, - { - "filePath": "mobile/src/session/use-mobile-image-attachment.ts", - "kind": "user-visible-call", - "text": "Attach failed (disconnected)", - "dynamic": false, - "count": 1 - }, - { - "filePath": "mobile/src/session/use-mobile-image-attachment.ts", - "kind": "user-visible-call", - "text": "Image too large to attach", - "dynamic": false, - "count": 1 - }, - { - "filePath": "mobile/src/session/use-mobile-image-attachment.ts", - "kind": "user-visible-call", - "text": "Photo permission denied", - "dynamic": false, - "count": 1 - }, { "filePath": "mobile/src/session/use-mobile-pr-actions.ts", "kind": "object-property:error", @@ -2659,244 +307,6 @@ "dynamic": false, "count": 1 }, - { - "filePath": "mobile/src/session/use-mobile-pr-ai-triage.ts", - "kind": "object-property:error", - "text": "Waiting for desktop…", - "dynamic": false, - "count": 1 - }, - { - "filePath": "mobile/src/session/use-mobile-session-accessory-selection.ts", - "kind": "user-visible-call", - "text": "Copied", - "dynamic": false, - "count": 1 - }, - { - "filePath": "mobile/src/session/use-mobile-session-accessory-selection.ts", - "kind": "user-visible-call", - "text": "Couldn't copy", - "dynamic": false, - "count": 2 - }, - { - "filePath": "mobile/src/session/use-mobile-session-accessory-selection.ts", - "kind": "user-visible-call", - "text": "Selection cleared (scrolled out of buffer)", - "dynamic": false, - "count": 1 - }, - { - "filePath": "mobile/src/session/use-mobile-session-content-create-actions.ts", - "kind": "user-visible-call", - "text": "Browser page is not available yet.", - "dynamic": false, - "count": 1 - }, - { - "filePath": "mobile/src/session/use-mobile-session-content-create-actions.ts", - "kind": "user-visible-call", - "text": "Desktop update required for mobile browser streaming", - "dynamic": false, - "count": 1 - }, - { - "filePath": "mobile/src/session/use-mobile-session-diff-comments.ts", - "kind": "user-visible-call", - "text": "Couldn't copy notes", - "dynamic": false, - "count": 1 - }, - { - "filePath": "mobile/src/session/use-mobile-session-diff-comments.ts", - "kind": "user-visible-call", - "text": "Failed to delete note", - "dynamic": false, - "count": 1 - }, - { - "filePath": "mobile/src/session/use-mobile-session-diff-comments.ts", - "kind": "user-visible-call", - "text": "Failed to save note", - "dynamic": false, - "count": 1 - }, - { - "filePath": "mobile/src/session/use-mobile-session-diff-comments.ts", - "kind": "user-visible-call", - "text": "Note added", - "dynamic": false, - "count": 1 - }, - { - "filePath": "mobile/src/session/use-mobile-session-diff-comments.ts", - "kind": "user-visible-call", - "text": "Notes copied", - "dynamic": false, - "count": 1 - }, - { - "filePath": "mobile/src/session/use-mobile-session-markdown-actions.ts", - "kind": "user-visible-call", - "text": "Copied", - "dynamic": false, - "count": 1 - }, - { - "filePath": "mobile/src/session/use-mobile-session-markdown-actions.ts", - "kind": "user-visible-call", - "text": "Couldn't copy", - "dynamic": false, - "count": 1 - }, - { - "filePath": "mobile/src/session/use-mobile-session-markdown-actions.ts", - "kind": "user-visible-call", - "text": "Couldn't copy path", - "dynamic": false, - "count": 1 - }, - { - "filePath": "mobile/src/session/use-mobile-session-markdown-actions.ts", - "kind": "user-visible-call", - "text": "Path copied", - "dynamic": false, - "count": 1 - }, - { - "filePath": "mobile/src/session/use-mobile-session-markdown-actions.ts", - "kind": "user-visible-call", - "text": "Saved", - "dynamic": false, - "count": 1 - }, - { - "filePath": "mobile/src/session/use-mobile-session-native-chat-dictation.ts", - "kind": "user-visible-call", - "text": "Couldn't insert dictation", - "dynamic": false, - "count": 1 - }, - { - "filePath": "mobile/src/session/use-mobile-session-native-chat-dictation.ts", - "kind": "user-visible-call", - "text": "Couldn't insert dictation, so it was copied to the clipboard", - "dynamic": false, - "count": 1 - }, - { - "filePath": "mobile/src/session/use-mobile-session-panel-route-actions.tsx", - "kind": "object-property:label", - "text": "Agent Presets Unavailable", - "dynamic": false, - "count": 2 - }, - { - "filePath": "mobile/src/session/use-mobile-session-panel-route-actions.tsx", - "kind": "object-property:label", - "text": "Agents on Another Server", - "dynamic": false, - "count": 1 - }, - { - "filePath": "mobile/src/session/use-mobile-session-panel-route-actions.tsx", - "kind": "object-property:label", - "text": "Detecting Agents", - "dynamic": false, - "count": 2 - }, - { - "filePath": "mobile/src/session/use-mobile-session-panel-route-actions.tsx", - "kind": "object-property:label", - "text": "No Enabled Agents", - "dynamic": false, - "count": 2 - }, - { - "filePath": "mobile/src/session/use-mobile-session-startup.ts", - "kind": "user-visible-call", - "text": "Open Orca on the host to wake sleeping agents.", - "dynamic": false, - "count": 1 - }, - { - "filePath": "mobile/src/session/use-mobile-session-terminal-create-actions.ts", - "kind": "user-visible-call", - "text": "Couldn't send notes", - "dynamic": false, - "count": 1 - }, - { - "filePath": "mobile/src/session/use-mobile-session-terminal-create-actions.ts", - "kind": "user-visible-call", - "text": "Notes sent", - "dynamic": false, - "count": 1 - }, - { - "filePath": "mobile/src/session/use-mobile-session-terminal-input.ts", - "kind": "user-visible-call", - "text": "Couldn't clear terminal", - "dynamic": false, - "count": 2 - }, - { - "filePath": "mobile/src/session/use-mobile-session-terminal-input.ts", - "kind": "user-visible-call", - "text": "Terminal cleared", - "dynamic": false, - "count": 1 - }, - { - "filePath": "mobile/src/session/use-mobile-session-terminal-send-actions.ts", - "kind": "user-visible-call", - "text": "Input too large (max 256 KiB)", - "dynamic": false, - "count": 1 - }, - { - "filePath": "mobile/src/session/use-mobile-terminal-paste.ts", - "kind": "user-visible-call", - "text": "Couldn't read the clipboard", - "dynamic": false, - "count": 1 - }, - { - "filePath": "mobile/src/session/use-mobile-terminal-paste.ts", - "kind": "user-visible-call", - "text": "Image too large to paste", - "dynamic": false, - "count": 1 - }, - { - "filePath": "mobile/src/session/use-mobile-terminal-paste.ts", - "kind": "user-visible-call", - "text": "Paste canceled (terminal changed)", - "dynamic": false, - "count": 1 - }, - { - "filePath": "mobile/src/session/use-mobile-terminal-paste.ts", - "kind": "user-visible-call", - "text": "Paste failed", - "dynamic": false, - "count": 2 - }, - { - "filePath": "mobile/src/session/use-mobile-terminal-paste.ts", - "kind": "user-visible-call", - "text": "Paste failed (disconnected)", - "dynamic": false, - "count": 2 - }, - { - "filePath": "mobile/src/session/use-mobile-terminal-paste.ts", - "kind": "user-visible-call", - "text": "Paste too large (max 256 KiB)", - "dynamic": false, - "count": 1 - }, { "filePath": "mobile/src/settings/about-screen.tsx", "kind": "jsx-text", @@ -3002,34 +412,6 @@ "dynamic": false, "count": 1 }, - { - "filePath": "mobile/src/source-control/mobile-source-control-actions.ts", - "kind": "object-property:label", - "text": "Commits", - "dynamic": false, - "count": 1 - }, - { - "filePath": "mobile/src/source-control/mobile-source-control-actions.ts", - "kind": "object-property:label", - "text": "Create PR", - "dynamic": false, - "count": 1 - }, - { - "filePath": "mobile/src/source-control/mobile-source-control-actions.ts", - "kind": "object-property:label", - "text": "Push & Create PR", - "dynamic": false, - "count": 1 - }, - { - "filePath": "mobile/src/source-control/mobile-source-control-actions.ts", - "kind": "object-property:label", - "text": "Switch branch", - "dynamic": false, - "count": 1 - }, { "filePath": "mobile/src/source-control/MobileBranchDiffPreviewDrawer.tsx", "kind": "jsx-attribute:accessibilityLabel", @@ -6572,34 +3954,6 @@ "dynamic": false, "count": 1 }, - { - "filePath": "mobile/src/terminal/terminal-key-definitions.ts", - "kind": "object-property:accessibilityLabel", - "text": "Arrow Down", - "dynamic": false, - "count": 1 - }, - { - "filePath": "mobile/src/terminal/terminal-key-definitions.ts", - "kind": "object-property:accessibilityLabel", - "text": "Arrow Left", - "dynamic": false, - "count": 1 - }, - { - "filePath": "mobile/src/terminal/terminal-key-definitions.ts", - "kind": "object-property:accessibilityLabel", - "text": "Arrow Right", - "dynamic": false, - "count": 1 - }, - { - "filePath": "mobile/src/terminal/terminal-key-definitions.ts", - "kind": "object-property:accessibilityLabel", - "text": "Arrow Up", - "dynamic": false, - "count": 1 - }, { "filePath": "mobile/src/terminal/terminal-key-definitions.ts", "kind": "object-property:accessibilityLabel", @@ -6607,34 +3961,6 @@ "dynamic": false, "count": 1 }, - { - "filePath": "mobile/src/terminal/terminal-key-definitions.ts", - "kind": "object-property:accessibilityLabel", - "text": "Clear line before cursor", - "dynamic": false, - "count": 1 - }, - { - "filePath": "mobile/src/terminal/terminal-key-definitions.ts", - "kind": "object-property:accessibilityLabel", - "text": "Clear screen", - "dynamic": false, - "count": 1 - }, - { - "filePath": "mobile/src/terminal/terminal-key-definitions.ts", - "kind": "object-property:accessibilityLabel", - "text": "Delete word backward", - "dynamic": false, - "count": 1 - }, - { - "filePath": "mobile/src/terminal/terminal-key-definitions.ts", - "kind": "object-property:accessibilityLabel", - "text": "End of line", - "dynamic": false, - "count": 1 - }, { "filePath": "mobile/src/terminal/terminal-key-definitions.ts", "kind": "object-property:accessibilityLabel", @@ -6649,34 +3975,6 @@ "dynamic": false, "count": 1 }, - { - "filePath": "mobile/src/terminal/terminal-key-definitions.ts", - "kind": "object-property:accessibilityLabel", - "text": "Forward delete", - "dynamic": false, - "count": 1 - }, - { - "filePath": "mobile/src/terminal/terminal-key-definitions.ts", - "kind": "object-property:accessibilityLabel", - "text": "Interrupt terminal", - "dynamic": false, - "count": 1 - }, - { - "filePath": "mobile/src/terminal/terminal-key-definitions.ts", - "kind": "object-property:accessibilityLabel", - "text": "Reverse search", - "dynamic": false, - "count": 1 - }, - { - "filePath": "mobile/src/terminal/terminal-key-definitions.ts", - "kind": "object-property:accessibilityLabel", - "text": "Send EOF", - "dynamic": false, - "count": 1 - }, { "filePath": "mobile/src/terminal/terminal-key-definitions.ts", "kind": "object-property:accessibilityLabel", @@ -6691,20 +3989,6 @@ "dynamic": false, "count": 1 }, - { - "filePath": "mobile/src/terminal/terminal-key-definitions.ts", - "kind": "object-property:accessibilityLabel", - "text": "Start of line", - "dynamic": false, - "count": 1 - }, - { - "filePath": "mobile/src/terminal/terminal-key-definitions.ts", - "kind": "object-property:accessibilityLabel", - "text": "Suspend process", - "dynamic": false, - "count": 1 - }, { "filePath": "mobile/src/terminal/terminal-key-definitions.ts", "kind": "object-property:accessibilityLabel", @@ -6817,27 +4101,6 @@ "dynamic": false, "count": 1 }, - { - "filePath": "mobile/src/terminal/terminal-webview-engine-error-state.tsx", - "kind": "jsx-text", - "text": "Reload", - "dynamic": false, - "count": 1 - }, - { - "filePath": "mobile/src/terminal/terminal-webview-engine-error-state.tsx", - "kind": "jsx-text", - "text": "Terminal failed to load", - "dynamic": false, - "count": 1 - }, - { - "filePath": "mobile/src/terminal/TerminalWebView.web.tsx", - "kind": "object-property:message", - "text": "terminal document failed to start -", - "dynamic": true, - "count": 1 - }, { "filePath": "mobile/src/transport/host-entry-opener.ts", "kind": "object-property:message", diff --git a/mobile/rpc-foundation/goldens/files-explorer-legacy-fallback.json b/mobile/rpc-foundation/goldens/files-explorer-legacy-fallback.json index bacb74b39f0..d56064ca92e 100644 --- a/mobile/rpc-foundation/goldens/files-explorer-legacy-fallback.json +++ b/mobile/rpc-foundation/goldens/files-explorer-legacy-fallback.json @@ -87,6 +87,22 @@ } } }, + "546e369e8e6c": { + "crash": { + "$rpc": "null" + }, + "elements": { + "ChevronLeft": 1, + "FlatList": 1, + "Pressable": 1, + "SafeAreaView": 1, + "Text": 2, + "View": 3 + }, + "labels": ["Back to session"], + "rows": ["dir:src", "file:README.md"], + "text": ["Files", "orca-files - Showing first 5000"] + }, "59851eb4cbf7": { "name": "files.readDir#1", "ordinal": 1, @@ -124,22 +140,6 @@ "ordinal": 2, "json": "{\"id\":\"frame-1\",\"deviceToken\":\"recording-device\",\"method\":\"files.readDir\",\"params\":{\"worktree\":\"id:wt-files\",\"relativePath\":\"\"}}" }, - "b85511fb8929": { - "crash": { - "$rpc": "null" - }, - "elements": { - "ChevronLeft": 1, - "FlatList": 1, - "Pressable": 1, - "SafeAreaView": 1, - "Text": 2, - "View": 3 - }, - "labels": ["Back to session"], - "rows": ["dir:src", "file:README.md"], - "text": ["Files", "orca-files", " - Showing first 5000"] - }, "e91880eefe86": { "crash": { "$rpc": "null" @@ -188,7 +188,7 @@ "settlements": { "mount": "eb79a9b3682a" }, - "state": "b85511fb8929", + "state": "546e369e8e6c", "effects": [] } } diff --git a/mobile/rpc-foundation/goldens/matrix-files.explorer-screen-files.list-1.json b/mobile/rpc-foundation/goldens/matrix-files.explorer-screen-files.list-1.json index 7eebec47af6..e86e168ab82 100644 --- a/mobile/rpc-foundation/goldens/matrix-files.explorer-screen-files.list-1.json +++ b/mobile/rpc-foundation/goldens/matrix-files.explorer-screen-files.list-1.json @@ -264,6 +264,22 @@ } } }, + "546e369e8e6c": { + "crash": { + "$rpc": "null" + }, + "elements": { + "ChevronLeft": 1, + "FlatList": 1, + "Pressable": 1, + "SafeAreaView": 1, + "Text": 2, + "View": 3 + }, + "labels": ["Back to session"], + "rows": ["dir:src", "file:README.md"], + "text": ["Files", "orca-files - Showing first 5000"] + }, "59851eb4cbf7": { "name": "files.readDir#1", "ordinal": 1, @@ -423,22 +439,6 @@ "rows": [], "text": ["Files", "orca-files", "No files found"] }, - "b85511fb8929": { - "crash": { - "$rpc": "null" - }, - "elements": { - "ChevronLeft": 1, - "FlatList": 1, - "Pressable": 1, - "SafeAreaView": 1, - "Text": 2, - "View": 3 - }, - "labels": ["Back to session"], - "rows": ["dir:src", "file:README.md"], - "text": ["Files", "orca-files", " - Showing first 5000"] - }, "bec439845fd2": { "name": "files.list#1", "ordinal": 3, @@ -608,7 +608,7 @@ "settlements": { "mount": "eb79a9b3682a" }, - "state": "b85511fb8929", + "state": "546e369e8e6c", "effects": [] } }, diff --git a/mobile/rpc-foundation/goldens/matrix-files.explorer-screen-files.readdir-1.json b/mobile/rpc-foundation/goldens/matrix-files.explorer-screen-files.readdir-1.json index ba7472a80d7..b69b0a6e8ef 100644 --- a/mobile/rpc-foundation/goldens/matrix-files.explorer-screen-files.readdir-1.json +++ b/mobile/rpc-foundation/goldens/matrix-files.explorer-screen-files.readdir-1.json @@ -219,6 +219,22 @@ } } }, + "546e369e8e6c": { + "crash": { + "$rpc": "null" + }, + "elements": { + "ChevronLeft": 1, + "FlatList": 1, + "Pressable": 1, + "SafeAreaView": 1, + "Text": 2, + "View": 3 + }, + "labels": ["Back to session"], + "rows": ["dir:src", "file:README.md"], + "text": ["Files", "orca-files - Showing first 5000"] + }, "5878b1d32e8e": { "name": "files.readDir#1", "ordinal": 1, @@ -466,22 +482,6 @@ "rows": [], "text": ["Files", "orca-files", "No files found"] }, - "b85511fb8929": { - "crash": { - "$rpc": "null" - }, - "elements": { - "ChevronLeft": 1, - "FlatList": 1, - "Pressable": 1, - "SafeAreaView": 1, - "Text": 2, - "View": 3 - }, - "labels": ["Back to session"], - "rows": ["dir:src", "file:README.md"], - "text": ["Files", "orca-files", " - Showing first 5000"] - }, "ba7f7b642df7": { "name": "files.readDir#1", "ordinal": 1, @@ -736,7 +736,7 @@ "settlements": { "mount": "eb79a9b3682a" }, - "state": "b85511fb8929", + "state": "546e369e8e6c", "effects": [] } }, diff --git a/mobile/src/agent-history/worktree-navigation-actions.ts b/mobile/src/agent-history/worktree-navigation-actions.ts index f3bb8dfdc88..62cbf8c17c4 100644 --- a/mobile/src/agent-history/worktree-navigation-actions.ts +++ b/mobile/src/agent-history/worktree-navigation-actions.ts @@ -26,6 +26,7 @@ export function buildWorktreeNavigationActions(args: Args): ActionSheetAction[] const executionHost = args.executionHost const actions: ActionSheetAction[] = [ { + id: 'sourceControl', label: translate('agentHistory.worktreeNavigationActions.sourceControl', 'Source Control'), icon: GitBranch, onPress: () => { @@ -42,6 +43,7 @@ export function buildWorktreeNavigationActions(args: Args): ActionSheetAction[] ] if (args.hostCapabilities.includes(MOBILE_AI_VAULT_CAPABILITY)) { actions.push({ + id: 'agentSessionHistory', label: translate( 'agentHistory.worktreeNavigationActions.agentSessionHistory', 'Agent Session History' diff --git a/mobile/src/browser/MobileBrowserAddressField.tsx b/mobile/src/browser/MobileBrowserAddressField.tsx index 1cb29915c46..8d3b073510e 100644 --- a/mobile/src/browser/MobileBrowserAddressField.tsx +++ b/mobile/src/browser/MobileBrowserAddressField.tsx @@ -1,4 +1,5 @@ import { Platform, StyleSheet, Text, TextInput, View } from 'react-native' +import { useMobileTranslation } from '../i18n/use-mobile-translation' import { colors, radii, spacing } from '../theme/mobile-theme' import { themedStyleSheet } from '../theme/themed-styles' import { useMobileColorScheme } from '../theme/use-mobile-color-scheme' @@ -29,6 +30,7 @@ export function MobileBrowserAddressField({ onSubmit, value }: Props): React.JSX.Element { + const t = useMobileTranslation() useMobileColorScheme() const fileLabel = focused ? null : compactMobileBrowserFileAddress(value) const selection = focused ? undefined : { start: 0, end: 0 } @@ -51,7 +53,7 @@ export function MobileBrowserAddressField({ inputMode={Platform.OS === 'web' ? 'url' : undefined} numberOfLines={1} returnKeyType="go" - placeholder="URL" + placeholder={t('browser.addressField.urlPlaceholder', 'URL')} placeholderTextColor={colors.textMuted} editable={!disabled} /> diff --git a/mobile/src/browser/MobileBrowserPane.tsx b/mobile/src/browser/MobileBrowserPane.tsx index 7601fd3dcb0..576ca3a12bd 100644 --- a/mobile/src/browser/MobileBrowserPane.tsx +++ b/mobile/src/browser/MobileBrowserPane.tsx @@ -2,6 +2,8 @@ import { useCallback, useEffect, useLayoutEffect, useRef, useState } from 'react' import { AppState, type View } from 'react-native' import type { BrowserLoadError } from '../../../src/shared/browser-workspace-types' +import { translate } from '../i18n/mobile-i18n' +import { useMobileTranslation } from '../i18n/use-mobile-translation' import type { RpcClient } from '../transport/rpc-client' import type { BrowserScreencastFrameMetadata } from '../transport/browser-screencast-protocol' import type { MobileBrowserViewMode } from './browser-screencast-request' @@ -73,6 +75,7 @@ export function MobileBrowserPane({ bottomInset, onToast }: MobileBrowserPaneProps) { + useMobileTranslation() const [browserViewMode, setBrowserViewMode] = useState(() => getInitialMobileBrowserViewMode(worktreeId, tab.browserPageId, tab.url) ) @@ -219,7 +222,7 @@ export function MobileBrowserPane({ const navigateToAddress = useCallback(async () => { const url = normalizeBrowserUrl(addressValue) if (!url) { - setError('Enter a valid URL.') + setError(translate('browser.pane.invalidUrl', 'Enter a valid URL.')) return } const settled = await sendBrowserRequest( diff --git a/mobile/src/browser/MobileBrowserPaneView.tsx b/mobile/src/browser/MobileBrowserPaneView.tsx index 8cf48881553..b3552315824 100644 --- a/mobile/src/browser/MobileBrowserPaneView.tsx +++ b/mobile/src/browser/MobileBrowserPaneView.tsx @@ -11,6 +11,7 @@ import { type ViewStyle } from 'react-native' import { ArrowUp, ChevronLeft, ChevronRight, RefreshCw } from 'lucide-react-native' +import { useMobileTranslation } from '../i18n/use-mobile-translation' import { colors } from '../theme/mobile-theme' import { useMobileColorScheme } from '../theme/use-mobile-color-scheme' import { MobileBrowserAddressField } from './MobileBrowserAddressField' @@ -78,6 +79,7 @@ type MobileBrowserPaneViewProps = { // Frame cap can leave viewport unpainted despite live stream; oversized frames are dropped, never fatal. // Area budget prevents starvation: C6.6 with budget disabled measured 299 dropped/6 applied and a persistent spinner. export function MobileBrowserPaneView(props: MobileBrowserPaneViewProps) { + const t = useMobileTranslation() useMobileColorScheme() const { addressFocused, @@ -118,21 +120,21 @@ export function MobileBrowserPaneView(props: MobileBrowserPaneViewProps) { @@ -256,7 +258,9 @@ export function MobileBrowserPaneView(props: MobileBrowserPaneViewProps) { {dialog ? ( - Browser Dialog + + {t('browser.paneView.dialogTitle', 'Browser Dialog')} + {dialog.message} {/* The page is still blocked, so the buttons stay live and the card says why. */} {dialog.error ? {dialog.error} : null} @@ -271,7 +275,7 @@ export function MobileBrowserPaneView(props: MobileBrowserPaneViewProps) { ]} onPress={() => void sendDialogCommand('browser.dialogDismiss')} > - Cancel + {t('common.cancel', 'Cancel')} ) : null} void sendDialogCommand('browser.dialogAccept')} > - OK + + {t('common.ok', 'OK')} + @@ -312,7 +318,7 @@ export function MobileBrowserPaneView(props: MobileBrowserPaneViewProps) { style={styles.keyboardInput} value={keyboardValue} onChangeText={setKeyboardValue} - placeholder="Type on page…" + placeholder={t('browser.paneView.keyboardPlaceholder', 'Type on page…')} placeholderTextColor={colors.textMuted} autoCapitalize="none" autoCorrect={false} @@ -323,7 +329,7 @@ export function MobileBrowserPaneView(props: MobileBrowserPaneViewProps) { style={[styles.sendButton, (controlsDisabled || !keyboardValue) && styles.disabled]} disabled={controlsDisabled || !keyboardValue} onPress={() => void sendKeyboardText()} - accessibilityLabel="Send text to browser" + accessibilityLabel={t('browser.paneView.sendText', 'Send text to browser')} > diff --git a/mobile/src/browser/MobileBrowserPointerModifiers.tsx b/mobile/src/browser/MobileBrowserPointerModifiers.tsx index 5e7f7655d01..cd064e12996 100644 --- a/mobile/src/browser/MobileBrowserPointerModifiers.tsx +++ b/mobile/src/browser/MobileBrowserPointerModifiers.tsx @@ -1,4 +1,5 @@ import { Pressable, Text, View } from 'react-native' +import { useMobileTranslation } from '../i18n/use-mobile-translation' import { colors, radii, spacing, typography } from '../theme/mobile-theme' import { themedStyleSheet } from '../theme/themed-styles' import { useMobileColorScheme } from '../theme/use-mobile-color-scheme' @@ -23,6 +24,7 @@ export function MobileBrowserPointerModifiers({ selectedModifiers, onToggle }: Props): React.JSX.Element { + const t = useMobileTranslation() useMobileColorScheme() return ( @@ -41,7 +43,13 @@ export function MobileBrowserPointerModifiers({ onPress={() => onToggle(modifier.id)} accessibilityRole="button" accessibilityState={{ selected, disabled }} - accessibilityLabel={`${modifier.label} click modifier`} + accessibilityLabel={t( + 'browser.pointerModifiers.clickModifier', + '{{key}} click modifier', + { + key: modifier.label + } + )} > void } -const VIEW_MODES: { id: MobileBrowserViewMode; label: string; icon: LucideIcon }[] = [ - { id: 'web', label: 'Web', icon: Monitor }, - { id: 'mobile', label: 'Mobile', icon: Smartphone } -] +function viewModes(t: Translate): { id: MobileBrowserViewMode; label: string; icon: LucideIcon }[] { + return [ + { + id: 'web', + label: t('browser.viewModeSwitch.webView', 'Show web website view'), + icon: Monitor + }, + { + id: 'mobile', + label: t('browser.viewModeSwitch.mobileView', 'Show mobile website view'), + icon: Smartphone + } + ] +} export function MobileBrowserViewModeSwitch({ disabled, value, onChange }: Props): React.JSX.Element { + const t = useMobileTranslation() useMobileColorScheme() return ( - {VIEW_MODES.map((mode) => ( + {viewModes(t).map((mode) => ( diff --git a/mobile/src/browser/use-mobile-browser-commands.ts b/mobile/src/browser/use-mobile-browser-commands.ts index d0d0c262697..806768fa866 100644 --- a/mobile/src/browser/use-mobile-browser-commands.ts +++ b/mobile/src/browser/use-mobile-browser-commands.ts @@ -1,11 +1,6 @@ -import { - useCallback, - useEffect, - useLayoutEffect, - useRef, - type Dispatch, - type SetStateAction -} from 'react' +import { useCallback, useEffect, useLayoutEffect, useRef } from 'react' +import type { Dispatch, SetStateAction } from 'react' +import { translate } from '../i18n/mobile-i18n' import type { RpcClient } from '../transport/rpc-client' import { isRpcDeliveryUnknown } from '../transport/rpc-delivery-ambiguity' import type { BrowserScreencastFrameMetadata } from '../transport/browser-screencast-protocol' @@ -208,12 +203,16 @@ export function useMobileBrowserCommands(args: MobileBrowserCommandArgs) { return } if (result !== null) { - onToast('Sent') + onToast(translate('browser.useBrowserCommands.sent', 'Sent')) return } setKeyboardValue((current) => text + current) if (unconfirmed) { - onToast('Delivery unconfirmed — check page before retrying') + const warning = translate( + 'browser.useBrowserCommands.deliveryUnconfirmed', + 'Delivery unconfirmed — check page before retrying' + ) + onToast(warning) } }, [onToast, sendBrowserRequest]) @@ -241,6 +240,10 @@ export function useMobileBrowserCommands(args: MobileBrowserCommandArgs) { async (rpc, page, options) => command.interpret(await command.request(rpc, page, options)), { suppressError: true, timeoutMs: 5_000 } ) + const error = translate( + 'browser.useBrowserCommands.dialogAnswerFailed', + 'That answer did not reach the page.' + ) // Refusal/timeout leaves page blocked, so retain card with error explanation. setDialog((current) => current === null || current.pending !== token @@ -248,7 +251,7 @@ export function useMobileBrowserCommands(args: MobileBrowserCommandArgs) { : { ...current, pending: undefined, - ...(result === null ? { error: 'That answer did not reach the page.' } : {}) + ...(result === null ? { error } : {}) } ) }, diff --git a/mobile/src/components/ActionSheetModal.tsx b/mobile/src/components/ActionSheetModal.tsx index c2f8cf50428..bcd47d0aa00 100644 --- a/mobile/src/components/ActionSheetModal.tsx +++ b/mobile/src/components/ActionSheetModal.tsx @@ -7,6 +7,7 @@ import { useMobileColorScheme } from '../theme/use-mobile-color-scheme' import { BottomDrawer } from './BottomDrawer' export type ActionSheetAction = { + id?: string label: string icon?: LucideIcon renderIcon?: () => ReactNode @@ -68,7 +69,7 @@ export function ActionSheetContent({ title, message, children, actions, onClose const Icon = iconForAction(action.label, action.destructive, action.icon) const customIcon = action.renderIcon?.() return ( - + {i > 0 && } [ diff --git a/mobile/src/components/MobileDiffReviewDrawers.tsx b/mobile/src/components/MobileDiffReviewDrawers.tsx index 8812aa9249f..29b596f510d 100644 --- a/mobile/src/components/MobileDiffReviewDrawers.tsx +++ b/mobile/src/components/MobileDiffReviewDrawers.tsx @@ -5,6 +5,7 @@ import type { DiffComment } from '../../../src/shared/diff-comment-types' import { colors } from '../theme/mobile-theme' import { useMobileColorScheme } from '../theme/use-mobile-color-scheme' import type { Translate } from '../i18n/mobile-i18n' +import { sendSheetMessage } from './mobile-diff-review-send-copy' import { useMobileTranslation } from '../i18n/use-mobile-translation' import type { ActionSheetAction } from './ActionSheetModal' import { ActionSheetContent } from './ActionSheetModal' @@ -117,6 +118,7 @@ function useSendActions( const terminalActions = load.kind === 'ready' || load.kind === 'error' ? load.terminals.map((terminal) => ({ + id: `terminal:${terminal.terminal}`, label: t('components.diffReviewDrawers.terminalLabel', '{{title}} ({{id}})', { title: terminal.title || t('components.diffReviewDrawers.terminal', 'Terminal'), id: terminal.terminal.slice(0, 6) @@ -130,6 +132,7 @@ function useSendActions( return [ ...terminalActions, { + id: 'newAgentSession', label: t('components.diffReviewDrawers.newAgentSession', 'New Agent Session'), icon: Plus, disabled: comments.length === 0 || controller.agentLaunchAvailability !== 'available', @@ -149,6 +152,7 @@ function useSendActions( onPress: () => void controller.createTerminalAndSend(comments) }, { + id: 'copyNotes', label: t('components.diffReviewDrawers.copyNotes', 'Copy Notes'), icon: Copy, disabled: @@ -165,6 +169,7 @@ function useOverflowActions(controller: ReturnType( () => [ { + id: 'copyNotes', label: t('components.diffReviewDrawers.copyNotes', 'Copy Notes'), icon: Copy, disabled: @@ -172,12 +177,14 @@ function useOverflowActions(controller: ReturnType void controller.copyNotes() }, { + id: 'sendUnsentNotes', label: t('components.diffReviewDrawers.sendUnsentNotes', 'Send Unsent Notes'), icon: Send, disabled: controller.unsentComments.length === 0, onPress: () => void controller.openSendSheet() }, { + id: 'clearSentNotes', label: t('components.diffReviewDrawers.clearSentNotes', 'Clear Sent Notes'), icon: Trash2, disabled: @@ -187,6 +194,7 @@ function useOverflowActions(controller: ReturnType void controller.clearSentNotes() }, { + id: 'stageReviewedFiles', label: t('components.diffReviewDrawers.stageReviewedFiles', 'Stage Reviewed Files'), icon: Check, disabled: controller.reviewedUnstagedCount === 0 || controller.busyAction !== null, @@ -194,6 +202,7 @@ function useOverflowActions(controller: ReturnType void controller.stageReviewedFiles() }, { + id: 'markUnreviewed', label: t('components.diffReviewDrawers.markUnreviewed', 'Mark Unreviewed'), icon: X, disabled: @@ -206,6 +215,7 @@ function useOverflowActions(controller: ReturnType, - load: SendSheetState -): string | undefined { - const count = controller.unsentComments.length - return load.kind === 'loading' - ? t('components.diffReviewDrawers.loadingSessions', 'Loading agent sessions...') - : load.kind === 'error' - ? load.message - : count === 1 - ? t('components.diffReviewDrawers.unsentNotes_one', '{{count}} unsent notes', { count }) - : t('components.diffReviewDrawers.unsentNotes_other', '{{count}} unsent notes', { count }) -} - function NoteComposerContent({ controller, composer }: Props & { composer: ComposerState }) { useMobileColorScheme() const t = useMobileTranslation() diff --git a/mobile/src/components/mobile-diff-review-send-copy.ts b/mobile/src/components/mobile-diff-review-send-copy.ts new file mode 100644 index 00000000000..492ebbfb472 --- /dev/null +++ b/mobile/src/components/mobile-diff-review-send-copy.ts @@ -0,0 +1,18 @@ +import type { Translate } from '../i18n/mobile-i18n' +import type { SendSheetState } from '../session/mobile-diff-review-screen-model' +import type { useMobileDiffReviewController } from '../session/use-mobile-diff-review-controller' + +export function sendSheetMessage( + t: Translate, + controller: ReturnType, + load: SendSheetState +): string | undefined { + const count = controller.unsentComments.length + return load.kind === 'loading' + ? t('components.diffReviewDrawers.loadingSessions', 'Loading agent sessions...') + : load.kind === 'error' + ? load.message + : count === 1 + ? t('components.diffReviewDrawers.unsentNotes_one', '{{count}} unsent notes', { count }) + : t('components.diffReviewDrawers.unsentNotes_other', '{{count}} unsent notes', { count }) +} diff --git a/mobile/src/components/pr-sidebar/PRCommentsSection.tsx b/mobile/src/components/pr-sidebar/PRCommentsSection.tsx index 1a04ab8aa46..4109176be7c 100644 --- a/mobile/src/components/pr-sidebar/PRCommentsSection.tsx +++ b/mobile/src/components/pr-sidebar/PRCommentsSection.tsx @@ -311,23 +311,23 @@ function CommentGroupView({ {group.kind === 'thread' ? count === 1 ? t( - 'pullRequest.commentsSection.resolvedThread_one', + 'pullRequest.commentsSection.resolvedThreadSingle', 'Resolved thread by {{author}}', - { author: root.author, count } + { author: root.author } ) : t( - 'pullRequest.commentsSection.resolvedThread_other', + 'pullRequest.commentsSection.resolvedThreadCount', 'Resolved thread by {{author}} ({{count}})', { author: root.author, count } ) : count === 1 ? t( - 'pullRequest.commentsSection.resolvedComment_one', + 'pullRequest.commentsSection.resolvedCommentSingle', 'Resolved comment by {{author}}', - { author: root.author, count } + { author: root.author } ) : t( - 'pullRequest.commentsSection.resolvedComment_other', + 'pullRequest.commentsSection.resolvedCommentCount', 'Resolved comment by {{author}} ({{count}})', { author: root.author, count } )} diff --git a/mobile/src/diagnostics/mobile-web-shell-update-failure-copy.ts b/mobile/src/diagnostics/mobile-web-shell-update-failure-copy.ts index 724113317b8..1994345d124 100644 --- a/mobile/src/diagnostics/mobile-web-shell-update-failure-copy.ts +++ b/mobile/src/diagnostics/mobile-web-shell-update-failure-copy.ts @@ -10,41 +10,131 @@ import { formatTimeAgo } from '../worktree/agent-row-display' import { translate } from '../i18n/mobile-i18n' // Describe only the recorded code; this row has no further evidence about the failure. -const REASON_COPY: Record = { - 'no-connection': 'not connected to the host', - 'connection-lost': 'the connection dropped', - 'host-refused': 'the host refused the read', - 'reply-unreadable': 'the host sent a reply this app could not read', - 'chunk-oversize': 'a chunk was larger than the host allows', - 'asset-overlong': 'an asset was longer than the manifest declares', - 'asset-no-progress': 'an asset read made no progress', - 'asset-short': 'an asset ended short of its declared size', - 'asset-checksum-mismatch': 'asset checksum mismatch', - 'build-changed-mid-fetch': "the host's build changed during the download", - 'chunk-misrouted': 'a chunk answered the wrong asset or offset', - 'asset-entry-changed': 'an asset no longer matched the manifest', - 'range-undecodable': 'a compressed read could not be decoded', - 'fetch-stopped': 'the download was stopped', - 'cache-write-failed': 'saving the download on this phone failed', - 'unrecognised-error': 'an unrecognised error' +function reasonCopy(): Record { + return { + 'no-connection': translate( + 'diagnostics.webShellUpdateFailureCopy.reasonCopy.noConnection', + 'not connected to the host' + ), + 'connection-lost': translate( + 'diagnostics.webShellUpdateFailureCopy.reasonCopy.connectionLost', + 'the connection dropped' + ), + 'host-refused': translate( + 'diagnostics.webShellUpdateFailureCopy.reasonCopy.hostRefused', + 'the host refused the read' + ), + 'reply-unreadable': translate( + 'diagnostics.webShellUpdateFailureCopy.reasonCopy.replyUnreadable', + 'the host sent a reply this app could not read' + ), + 'chunk-oversize': translate( + 'diagnostics.webShellUpdateFailureCopy.reasonCopy.chunkOversize', + 'a chunk was larger than the host allows' + ), + 'asset-overlong': translate( + 'diagnostics.webShellUpdateFailureCopy.reasonCopy.assetOverlong', + 'an asset was longer than the manifest declares' + ), + 'asset-no-progress': translate( + 'diagnostics.webShellUpdateFailureCopy.reasonCopy.assetNoProgress', + 'an asset read made no progress' + ), + 'asset-short': translate( + 'diagnostics.webShellUpdateFailureCopy.reasonCopy.assetShort', + 'an asset ended short of its declared size' + ), + 'asset-checksum-mismatch': translate( + 'diagnostics.webShellUpdateFailureCopy.reasonCopy.assetChecksumMismatch', + 'asset checksum mismatch' + ), + 'build-changed-mid-fetch': translate( + 'diagnostics.webShellUpdateFailureCopy.reasonCopy.buildChangedMidFetch', + "the host's build changed during the download" + ), + 'chunk-misrouted': translate( + 'diagnostics.webShellUpdateFailureCopy.reasonCopy.chunkMisrouted', + 'a chunk answered the wrong asset or offset' + ), + 'asset-entry-changed': translate( + 'diagnostics.webShellUpdateFailureCopy.reasonCopy.assetEntryChanged', + 'an asset no longer matched the manifest' + ), + 'range-undecodable': translate( + 'diagnostics.webShellUpdateFailureCopy.reasonCopy.rangeUndecodable', + 'a compressed read could not be decoded' + ), + 'fetch-stopped': translate( + 'diagnostics.webShellUpdateFailureCopy.reasonCopy.fetchStopped', + 'the download was stopped' + ), + 'cache-write-failed': translate( + 'diagnostics.webShellUpdateFailureCopy.reasonCopy.cacheWriteFailed', + 'saving the download on this phone failed' + ), + 'unrecognised-error': translate( + 'diagnostics.webShellUpdateFailureCopy.reasonCopy.unrecognisedError', + 'an unrecognised error' + ) + } } -const HOST_CODE_COPY: Record = { - mobile_web_bundle_unavailable: 'the host has no workspace bundle', - mobile_web_bundle_build_changed: "the host's build changed during the download", - mobile_web_bundle_asset_unknown: 'the host did not recognise an asset', - mobile_web_bundle_asset_changed: 'an asset changed on the host', - mobile_web_bundle_offset_invalid: 'the host refused a read offset', - mobile_web_bundle_read_limited: 'the host limited concurrent reads' +function hostCodeCopy(): Record { + return { + mobile_web_bundle_unavailable: translate( + 'diagnostics.webShellUpdateFailureCopy.hostCodeCopy.unavailable', + 'the host has no workspace bundle' + ), + mobile_web_bundle_build_changed: translate( + 'diagnostics.webShellUpdateFailureCopy.hostCodeCopy.buildChanged', + "the host's build changed during the download" + ), + mobile_web_bundle_asset_unknown: translate( + 'diagnostics.webShellUpdateFailureCopy.hostCodeCopy.assetUnknown', + 'the host did not recognise an asset' + ), + mobile_web_bundle_asset_changed: translate( + 'diagnostics.webShellUpdateFailureCopy.hostCodeCopy.assetChanged', + 'an asset changed on the host' + ), + mobile_web_bundle_offset_invalid: translate( + 'diagnostics.webShellUpdateFailureCopy.hostCodeCopy.offsetInvalid', + 'the host refused a read offset' + ), + mobile_web_bundle_read_limited: translate( + 'diagnostics.webShellUpdateFailureCopy.hostCodeCopy.readLimited', + 'the host limited concurrent reads' + ) + } } -const WALL_COPY: Record = { - 'bundle-unavailable': 'the host has no workspace bundle', - 'bundle-shell-too-old': 'this app is too old for the saved bundle', - 'host-too-old-for-bundle': 'the host is too old for the saved bundle', - 'bundle-too-old-for-host': 'the saved bundle is too old for the host', - 'page-route-unlisted': "the host's workspace does not include this screen", - 'page-route-grant-missing': 'this screen needs a newer app' +function wallCopy(): Record { + return { + 'bundle-unavailable': translate( + 'diagnostics.webShellUpdateFailureCopy.wallCopy.bundleUnavailable', + 'the host has no workspace bundle' + ), + 'bundle-shell-too-old': translate( + 'diagnostics.webShellUpdateFailureCopy.wallCopy.bundleShellTooOld', + 'this app is too old for the saved bundle' + ), + 'host-too-old-for-bundle': translate( + 'diagnostics.webShellUpdateFailureCopy.wallCopy.hostTooOldForBundle', + 'the host is too old for the saved bundle' + ), + 'bundle-too-old-for-host': translate( + 'diagnostics.webShellUpdateFailureCopy.wallCopy.bundleTooOldForHost', + 'the saved bundle is too old for the host' + ), + 'page-route-unlisted': translate( + 'diagnostics.webShellUpdateFailureCopy.wallCopy.pageRouteUnlisted', + "the host's workspace does not include this screen" + ), + 'page-route-grant-missing': translate( + 'diagnostics.webShellUpdateFailureCopy.wallCopy.pageRouteGrantMissing', + 'this screen needs a newer app' + ) + } } function generation(buildId: string): string { @@ -56,20 +146,38 @@ function outcomeCopy(failure: MobileWebShellUpdateFailure): string { switch (outcome) { case 'opened-cached': return failure.cachedBuildId === null - ? 'Fell back to the saved version.' - : `Fell back to the saved version (generation ${generation(failure.cachedBuildId)}).` + ? translate( + 'diagnostics.webShellUpdateFailureCopy.openedCached', + 'Fell back to the saved version.' + ) + : translate( + 'diagnostics.webShellUpdateFailureCopy.openedCachedGeneration', + 'Fell back to the saved version (generation {{generation}}).', + { generation: generation(failure.cachedBuildId) } + ) case 'wall': return failure.wall === null - ? 'Blocked the workspace.' - : `Blocked: ${WALL_COPY[failure.wall]}.` + ? translate( + 'diagnostics.webShellUpdateFailureCopy.blockedWorkspace', + 'Blocked the workspace.' + ) + : translate('diagnostics.webShellUpdateFailureCopy.blockedReason', 'Blocked: {{reason}}.', { + reason: wallCopy()[failure.wall] + }) case 'native-route': - return 'Showed the native screen.' + return translate( + 'diagnostics.webShellUpdateFailureCopy.nativeRoute', + 'Showed the native screen.' + ) case 'route-unavailable': - return 'Showed that the screen is not available.' + return translate( + 'diagnostics.webShellUpdateFailureCopy.routeUnavailable', + 'Showed that the screen is not available.' + ) case 'failed': - return 'Showed the failure screen.' + return translate('diagnostics.webShellUpdateFailureCopy.failed', 'Showed the failure screen.') case 'waiting': - return 'Waited for the host.' + return translate('diagnostics.webShellUpdateFailureCopy.waiting', 'Waited for the host.') } } @@ -79,9 +187,15 @@ export function formatUpdateFailure( now: number ): string { const reason = - failure.hostCode === null ? REASON_COPY[failure.reason] : HOST_CODE_COPY[failure.hostCode] + failure.hostCode === null ? reasonCopy()[failure.reason] : hostCodeCopy()[failure.hostCode] const offered = - failure.offeredBuildId === null ? '' : ` (generation ${generation(failure.offeredBuildId)})` + failure.offeredBuildId === null + ? '' + : translate( + 'diagnostics.webShellUpdateFailureCopy.offeredGeneration', + ' (generation {{generation}})', + { generation: generation(failure.offeredBuildId) } + ) const options = { hostName, reason, offered, outcome: outcomeCopy(failure) } return now - failure.at < 60_000 ? translate( diff --git a/mobile/src/files/MobileFileExplorerPanel.tsx b/mobile/src/files/MobileFileExplorerPanel.tsx index ca1435fde8a..596dcad0a5b 100644 --- a/mobile/src/files/MobileFileExplorerPanel.tsx +++ b/mobile/src/files/MobileFileExplorerPanel.tsx @@ -37,6 +37,8 @@ import { fileExplorerStyles as styles } from './mobile-file-explorer-styles' import { MobileFileExplorerRow } from './mobile-file-explorer-row' import { navigateToMobileFilePreview } from './mobile-file-preview-navigation' import { MobileLoadErrorState } from '../components/MobileLoadErrorState' +import { translate } from '../i18n/mobile-i18n' +import { useMobileTranslation } from '../i18n/use-mobile-translation' export function MobileFileExplorerPanel(props: { hostId: string @@ -46,6 +48,7 @@ export function MobileFileExplorerPanel(props: { onRequestClose?: () => void }) { useMobileColorScheme() + const t = useMobileTranslation() const { hostId, worktreeId, name, embedded, onRequestClose } = props const router = useWorkspaceRouteHandoff() const executionHost = useWorkspaceExecutionHost() @@ -73,7 +76,9 @@ export function MobileFileExplorerPanel(props: { if (!client || connState !== 'connected') { const message = - connState === 'connected' ? 'Connecting to desktop...' : 'Waiting for desktop...' + connState === 'connected' + ? translate('files.fileExplorerPanel.connecting', 'Connecting to desktop...') + : translate('files.fileExplorerPanel.waiting', 'Waiting for desktop...') if (rootLoad) { const hasLoadedRoot = (getDirectoryCacheState(directoryCacheRef.current, '')?.entries.length ?? 0) > 0 @@ -141,10 +146,13 @@ export function MobileFileExplorerPanel(props: { throw new Error( (legacyReply.ok ? undefined : legacyReply.error)?.message || refusal?.message || - 'Unable to load files' + translate('files.fileExplorerPanel.loadFailed', 'Unable to load files') ) } - throw new Error(refusal?.message || 'Unable to load files') + throw new Error( + refusal?.message || + translate('files.fileExplorerPanel.loadFailed', 'Unable to load files') + ) } if ( !isCurrentDirectoryLoad(directoryLoadRevisionsRef.current, scopeRef.current, loadToken) @@ -166,7 +174,10 @@ export function MobileFileExplorerPanel(props: { ) { return } - const message = err instanceof Error ? err.message : 'Unable to load files' + const message = + err instanceof Error + ? err.message + : translate('files.fileExplorerPanel.loadFailed', 'Unable to load files') if (rootLoad) { // Only cold loads show full-screen errors; failed background refreshes retain the cached tree. setError(hadLoadedRoot ? null : message) @@ -227,7 +238,15 @@ export function MobileFileExplorerPanel(props: { Object.fromEntries( Object.entries(previous).map(([path, state]) => [ path, - state?.loading ? { entries: state.entries, error: 'Folder load interrupted' } : state + state?.loading + ? { + entries: state.entries, + error: translate( + 'files.fileExplorerPanel.loadInterrupted', + 'Folder load interrupted' + ) + } + : state ]) ) ) @@ -327,7 +346,7 @@ export function MobileFileExplorerPanel(props: { style={({ pressed }) => [styles.backButton, pressed && styles.backButtonPressed]} onPress={() => onRequestClose?.()} hitSlop={8} - accessibilityLabel="Close files" + accessibilityLabel={t('files.fileExplorerPanel.closeFiles', 'Close files')} > @@ -337,18 +356,21 @@ export function MobileFileExplorerPanel(props: { onPress={() => router.back()} hitSlop={8} accessibilityRole="button" - accessibilityLabel="Back to session" + accessibilityLabel={t('files.fileExplorerPanel.backToSession', 'Back to session')} > )} - Files + {t('files.fileExplorerPanel.title', 'Files')} - {worktreeLabel} - {legacyListTruncated ? ' - Showing first 5000' : ''} + {legacyListTruncated + ? t('files.fileExplorerPanel.truncatedList', '{{worktreeLabel}} - Showing first 5000', { + worktreeLabel + }) + : worktreeLabel} @@ -369,7 +391,9 @@ export function MobileFileExplorerPanel(props: { ) : rows.length === 0 ? ( - No files found + + {t('files.fileExplorerPanel.emptyState', 'No files found')} + ) : ( (() => (initialLine ? 'source' : 'preview')) const [previousRelativePath, setPreviousRelativePath] = useState(relativePath) const [previousInitialLine, setPreviousInitialLine] = useState(initialLine) @@ -55,7 +57,7 @@ export function MobileFileMarkdownPreview({ onPress={() => setMode('source')} accessibilityRole="button" accessibilityState={{ selected: sourceSelected }} - accessibilityLabel="View Markdown source" + accessibilityLabel={t('files.fileMarkdownPreview.viewSource', 'View Markdown source')} > setMode('preview')} accessibilityRole="button" accessibilityState={{ selected: previewSelected }} - accessibilityLabel="View rendered Markdown preview" + accessibilityLabel={t( + 'files.fileMarkdownPreview.viewPreview', + 'View rendered Markdown preview' + )} > {mode === 'preview' ? ( diff --git a/mobile/src/files/MobileFileMediaPreview.tsx b/mobile/src/files/MobileFileMediaPreview.tsx index 4a1329095a6..8fb23c5af54 100644 --- a/mobile/src/files/MobileFileMediaPreview.tsx +++ b/mobile/src/files/MobileFileMediaPreview.tsx @@ -11,6 +11,8 @@ import { MobilePdfPreview } from './MobilePdfPreview' import { filePreviewStyles as styles } from './mobile-file-preview-styles' import { formatPreviewByteLength } from './mobile-file-preview-response' import { MobileLoadErrorState } from '../components/MobileLoadErrorState' +import { translate } from '../i18n/mobile-i18n' +import { useMobileTranslation } from '../i18n/use-mobile-translation' type State = { uri: string } | { error: string } | { bytes: number; total: number } type Props = { media: MobileFileMedia; client: MobileFilePreviewRpcSender | null; title: string } @@ -27,6 +29,7 @@ export function MobileFileMediaPreview(props: Props) { function MediaDownload({ media, client, title }: Props) { useMobileColorScheme() + const t = useMobileTranslation() const { worktreeId, relativePath, mimeType } = media const [state, setState] = useState({ bytes: 0, total: 0 }) const [attempt, setAttempt] = useState(0) @@ -50,7 +53,12 @@ function MediaDownload({ media, client, title }: Props) { } } catch (error) { if (!controller.signal.aborted) { - setState({ error: error instanceof Error ? error.message : 'Unable to load media' }) + setState({ + error: + error instanceof Error + ? error.message + : translate('files.fileMediaPreview.loadFailed', 'Unable to load media') + }) } } })() @@ -63,7 +71,9 @@ function MediaDownload({ media, client, title }: Props) { if (!client) { return ( - Waiting for desktop... + + {t('files.fileMediaPreview.waiting', 'Waiting for desktop...')} + ) } @@ -78,8 +88,11 @@ function MediaDownload({ media, client, title }: Props) { {state.total - ? `Downloading ${formatPreviewByteLength(state.bytes)} of ${formatPreviewByteLength(state.total)}` - : 'Loading media...'} + ? t('files.fileMediaPreview.downloading', 'Downloading {{bytes}} of {{total}}', { + bytes: formatPreviewByteLength(state.bytes), + total: formatPreviewByteLength(state.total) + }) + : t('files.fileMediaPreview.loading', 'Loading media...')} ) diff --git a/mobile/src/files/MobileFilePreviewBody.tsx b/mobile/src/files/MobileFilePreviewBody.tsx index 0fec29a046f..84f6d0338d9 100644 --- a/mobile/src/files/MobileFilePreviewBody.tsx +++ b/mobile/src/files/MobileFilePreviewBody.tsx @@ -10,6 +10,7 @@ import { MobileFilePreviewSourceText } from './MobileFilePreviewSourceText' import type { MobileFilePreviewLineColumn } from './mobile-file-preview-line-column' import { filePreviewStyles as styles } from './mobile-file-preview-styles' import { MobileLoadErrorState } from '../components/MobileLoadErrorState' +import { useMobileTranslation } from '../i18n/use-mobile-translation' type Props = { preview: MobileFilePreviewResult @@ -32,6 +33,7 @@ type Props = { export function MobileFilePreviewBody({ preview, ...options }: Props) { useMobileColorScheme() + const t = useMobileTranslation() if (preview.status === 'loading') { return ( @@ -48,7 +50,7 @@ export function MobileFilePreviewBody({ preview, ...options }: Props) { ) : ( - Empty file + {t('files.filePreviewBody.emptyFile', 'Empty file')} ) } @@ -76,7 +78,9 @@ export function MobileFilePreviewBody({ preview, ...options }: Props) { style={[styles.image, { width: options.imageWidth, height: options.imageHeight }]} resizeMode="contain" onError={options.onImageError} - accessibilityLabel={`${options.title} image`} + accessibilityLabel={t('files.filePreviewBody.imageLabel', '{{title}} image', { + title: options.title + })} /> diff --git a/mobile/src/files/MobileFilePreviewEditableSource.tsx b/mobile/src/files/MobileFilePreviewEditableSource.tsx index c68a623742b..4d41b14d780 100644 --- a/mobile/src/files/MobileFilePreviewEditableSource.tsx +++ b/mobile/src/files/MobileFilePreviewEditableSource.tsx @@ -4,6 +4,7 @@ import type { MobileFilePreviewLineColumn } from './mobile-file-preview-line-col import { textOffsetForLineColumn } from './mobile-file-preview-line-column' import { useMobileColorScheme } from '../theme/use-mobile-color-scheme' import { filePreviewStyles as styles } from './mobile-file-preview-styles' +import { useMobileTranslation } from '../i18n/use-mobile-translation' type Props = { title: string @@ -21,6 +22,7 @@ export function MobileFilePreviewEditableSource({ onDraftChange }: Props) { useMobileColorScheme() + const t = useMobileTranslation() const selectionTargetKey = lineColumn ? `${title}:${lineColumn.line}:${lineColumn.column ?? ''}` : '' @@ -51,7 +53,9 @@ export function MobileFilePreviewEditableSource({ autoCorrect={false} selection={selection ?? undefined} onSelectionChange={() => setSelection(null)} - accessibilityLabel={`${title} editor`} + accessibilityLabel={t('files.filePreviewEditableSource.editorLabel', '{{title}} editor', { + title + })} /> ) diff --git a/mobile/src/files/MobileFilePreviewScreen.tsx b/mobile/src/files/MobileFilePreviewScreen.tsx index c5d7d7f1b94..5545614d22c 100644 --- a/mobile/src/files/MobileFilePreviewScreen.tsx +++ b/mobile/src/files/MobileFilePreviewScreen.tsx @@ -2,6 +2,8 @@ import { useCallback, useEffect, useMemo, useRef, useState } from 'react' import { Alert, Pressable, Text, View } from 'react-native' import { SafeAreaView, useSafeAreaFrame } from 'react-native-safe-area-context' import { ChevronLeft, Save } from 'lucide-react-native' +import { translate } from '../i18n/mobile-i18n' +import { useMobileTranslation } from '../i18n/use-mobile-translation' import { useWorkspaceExecutionHost } from '../navigation/workspace-execution-host' import { useWorkspaceRouteHandoff } from '../navigation/workspace-route-handoff' import { getWorktreeLabel } from '../session/worktree-label' @@ -51,6 +53,7 @@ export function MobileFilePreviewScreen({ route }: Props) { } function MobileFilePreviewDocument({ route }: Props) { + const t = useMobileTranslation() useMobileColorScheme() // Use the shared route handoff to keep this screen within the bundle asset budget. const router = useWorkspaceRouteHandoff() @@ -58,7 +61,9 @@ function MobileFilePreviewDocument({ route }: Props) { const { client, state: connState } = useWorkspaceClient(previewParams?.hostId) const forceReconnect = useForceReconnect() const [preview, setPreview] = useState(() => - route.ok ? { status: 'loading', message: 'Loading preview...' } : previewError(route.message) + route.ok + ? { status: 'loading', message: t('files.filePreviewScreen.loading', 'Loading preview...') } + : previewError(route.message) ) const [draftContent, setDraftContent] = useState('') const [savedContent, setSavedContent] = useState('') @@ -120,7 +125,7 @@ function MobileFilePreviewDocument({ route }: Props) { const isCurrent = () => revision === previewLoadRevisionRef.current && previewSourceKeyRef.current === loadSourceKey if (!previewParams || !loadSource || loadSourceKey !== routePreviewSourceKey) { - setPreview(previewError(route.ok ? 'Unable to load preview' : route.message)) + setPreview(previewError(route.ok ? '' : route.message)) return } const preserveDirtyDraft = @@ -128,14 +133,21 @@ function MobileFilePreviewDocument({ route }: Props) { draftContentRef.current !== savedContentRef.current if (!client || connState !== 'connected') { if (preserveDirtyDraft) { - setSaveError('Waiting for desktop...') + setSaveError(translate('files.filePreviewScreen.waiting', 'Waiting for desktop...')) return } - setPreview({ status: 'waiting', message: 'Waiting for desktop...', reconnect: true }) + setPreview({ + status: 'waiting', + message: translate('files.filePreviewScreen.waiting', 'Waiting for desktop...'), + reconnect: true + }) return } if (!preserveDirtyDraft) { - setPreview({ status: 'loading', message: 'Loading preview...' }) + setPreview({ + status: 'loading', + message: translate('files.filePreviewScreen.loading', 'Loading preview...') + }) } setSaveError('') try { @@ -176,12 +188,14 @@ function MobileFilePreviewDocument({ route }: Props) { if (!isCurrent()) { return } - const message = err instanceof Error ? err.message : 'Unable to load preview' + const message = err instanceof Error ? err.message : null if (preserveDirtyDraft) { - setSaveError(message) + setSaveError( + message ?? translate('files.filePreviewScreen.loadFailed', 'Unable to load preview') + ) return } - setPreview(previewError(message)) + setPreview(previewError(message ?? '')) } }, [client, connState, previewParams, previewSourceKey, route, routePreviewSourceKey]) @@ -215,7 +229,9 @@ function MobileFilePreviewDocument({ route }: Props) { previewParams?.worktreeName, previewParams?.worktreeId ?? '' ) - const meta = previewParams ? `${worktreeLabel} - ${displayPath}` : 'Preview' + const meta = previewParams + ? `${worktreeLabel} - ${displayPath}` + : t('files.filePreviewScreen.title', 'Preview') const isEditableTerminalArtifact = previewSource?.source === 'terminalArtifact' && isEditableMobileTerminalArtifactPreview(preview, previewSource.readOnly === true) @@ -254,7 +270,10 @@ function MobileFilePreviewDocument({ route }: Props) { setSaveError(saveErrorMessageFromPreviewResult(result)) } } catch (err) { - const message = err instanceof Error ? err.message : 'Unable to save file' + const message = + err instanceof Error + ? err.message + : translate('files.filePreviewScreen.saveFailed', 'Unable to save file') setSaveError(message) } finally { savingRef.current = false @@ -277,13 +296,13 @@ function MobileFilePreviewDocument({ route }: Props) { onPress={requestBack} hitSlop={8} accessibilityRole="button" - accessibilityLabel="Back to files" + accessibilityLabel={t('files.filePreviewScreen.backToFiles', 'Back to files')} > - {title || 'Preview'} + {title || t('files.filePreviewScreen.title', 'Preview')} {meta} @@ -294,7 +313,10 @@ function MobileFilePreviewDocument({ route }: Props) { style={[styles.saveButton, (!canSaveArtifact || saving) && styles.saveButtonDisabled]} onPress={() => void saveArtifact()} disabled={!canSaveArtifact || saving} - accessibilityLabel="Save terminal artifact" + accessibilityLabel={t( + 'files.filePreviewScreen.saveArtifact', + 'Save terminal artifact' + )} > @@ -306,7 +328,7 @@ function MobileFilePreviewDocument({ route }: Props) { client={client} worktreeId={previewSource?.source === 'worktree' ? previewSource.worktreeId : undefined} relativePath={displayPath} - title={title || 'File'} + title={title || t('files.filePreviewScreen.fileTitle', 'File')} editable={isEditableTerminalArtifact} draftContent={draftContent} saveError={saveError} @@ -315,7 +337,11 @@ function MobileFilePreviewDocument({ route }: Props) { imageHeight={Math.max(240, height - 160)} onDraftChange={setDraftContent} onImageError={() => - setPreview({ status: 'error', message: 'Unable to load preview', reconnect: false }) + setPreview({ + status: 'error', + message: t('files.filePreviewScreen.loadFailed', 'Unable to load preview'), + reconnect: false + }) } onRetry={retry} onOpenFile={(pathText) => { @@ -323,16 +349,22 @@ function MobileFilePreviewDocument({ route }: Props) { if (href) { router.push(href) } else { - Alert.alert('Unable to open file', 'Choose a file inside this workspace.') + Alert.alert( + t('files.filePreviewScreen.openFailed', 'Unable to open file'), + t( + 'files.filePreviewScreen.chooseWorkspaceFile', + 'Choose a file inside this workspace.' + ) + ) } }} /> (null) const revealedRef = useRef(false) const syntax = useMemo( @@ -53,7 +55,11 @@ export function MobileFilePreviewSourceText({ {truncated ? ( ) : null} - + @@ -62,9 +68,12 @@ export function MobileFilePreviewSourceText({ export function MobileFilePreviewTruncatedNote({ byteLength }: { byteLength: number }) { useMobileColorScheme() + const t = useMobileTranslation() return ( - Preview truncated. File size: {formatPreviewByteLength(byteLength)}. + {t('files.filePreviewSourceText.truncatedNote', 'Preview truncated. File size: {{size}}.', { + size: formatPreviewByteLength(byteLength) + })} ) } diff --git a/mobile/src/files/MobileMarkdownPreviewImage.tsx b/mobile/src/files/MobileMarkdownPreviewImage.tsx index e429a822598..30d85a36e50 100644 --- a/mobile/src/files/MobileMarkdownPreviewImage.tsx +++ b/mobile/src/files/MobileMarkdownPreviewImage.tsx @@ -7,6 +7,8 @@ import { MobileLoadErrorState } from '../components/MobileLoadErrorState' import { MobileNativeChatImageViewer } from '../session/native-chat/visual/MobileNativeChatImageViewer' import type { MobileFilePreviewRpcSender } from './mobile-file-preview-operations' import { loadMarkdownPreviewImage } from './mobile-markdown-image-source' +import { translate } from '../i18n/mobile-i18n' +import { useMobileTranslation } from '../i18n/use-mobile-translation' type Props = { client: MobileFilePreviewRpcSender | null @@ -18,6 +20,7 @@ type Props = { export function MobileMarkdownPreviewImage({ client, worktreeId, documentPath, href, alt }: Props) { useMobileColorScheme() + const t = useMobileTranslation() const [load, setLoad] = useState<{ uri?: string; error?: string }>({}) const [attempt, setAttempt] = useState(0) const [aspectRatio, setAspectRatio] = useState(16 / 9) @@ -41,7 +44,12 @@ export function MobileMarkdownPreviewImage({ client, worktreeId, documentPath, h }, (error: unknown) => { if (!cancelled) { - setLoad({ error: error instanceof Error ? error.message : 'Unable to load image' }) + setLoad({ + error: + error instanceof Error + ? error.message + : translate('files.markdownPreviewImage.loadFailed', 'Unable to load image') + }) } } ) @@ -64,14 +72,22 @@ export function MobileMarkdownPreviewImage({ client, worktreeId, documentPath, h setExpanded(true)} accessibilityRole="button" - accessibilityLabel={`Expand ${alt || 'image'}`} + accessibilityLabel={ + alt + ? t('files.markdownPreviewImage.expandImage', 'Expand {{alt}}', { alt }) + : t('files.markdownPreviewImage.expandFallback', 'Expand image') + } > setLoad({ error: 'Unable to load image' })} + onError={() => + setLoad({ + error: translate('files.markdownPreviewImage.loadFailed', 'Unable to load image') + }) + } /> ) : ( @@ -81,7 +97,7 @@ export function MobileMarkdownPreviewImage({ client, worktreeId, documentPath, h {expanded && load.uri ? ( setExpanded(false)} /> ) : null} diff --git a/mobile/src/files/MobilePdfPreview.tsx b/mobile/src/files/MobilePdfPreview.tsx index fbe7d0d935c..bf17c7b1c23 100644 --- a/mobile/src/files/MobilePdfPreview.tsx +++ b/mobile/src/files/MobilePdfPreview.tsx @@ -8,6 +8,8 @@ import { PdfOpenRefusedError } from '../platform/pdf-opener-contract' import type { MobileFileMedia } from './mobile-file-media' import { filePreviewStyles as styles } from './mobile-file-preview-styles' import { MobileLoadErrorState, MobileStateButton } from '../components/MobileLoadErrorState' +import { translate } from '../i18n/mobile-i18n' +import { useMobileTranslation } from '../i18n/use-mobile-translation' type State = | { status: 'opening' } @@ -17,6 +19,7 @@ type State = // The shell downloads the PDF and shows it in Quick Look (iOS) or the system viewer (Android). export function MobilePdfPreview({ media }: { media: MobileFileMedia }) { useMobileColorScheme() + const t = useMobileTranslation() const opener = usePdfOpener() const [state, setState] = useState({ status: 'idle' }) const stopPending = useRef<(() => void) | null>(null) @@ -45,7 +48,10 @@ export function MobilePdfPreview({ media }: { media: MobileFileMedia }) { (error: unknown) => settle({ status: 'failed', - message: error instanceof Error ? error.message : 'Unable to open this PDF', + message: + error instanceof Error + ? error.message + : translate('files.pdfPreview.openFailed', 'Unable to open this PDF'), retryable: !(error instanceof PdfOpenRefusedError) }) ) @@ -55,7 +61,9 @@ export function MobilePdfPreview({ media }: { media: MobileFileMedia }) { if (!opener) { return ( - Update Orca Mobile to open PDFs + + {t('files.pdfPreview.updateRequired', 'Update Orca Mobile to open PDFs')} + ) } @@ -66,13 +74,13 @@ export function MobilePdfPreview({ media }: { media: MobileFileMedia }) { return ( - Opening PDF... + {t('files.pdfPreview.opening', 'Opening PDF...')} ) } return ( - + ) } diff --git a/mobile/src/files/mobile-file-explorer-row.tsx b/mobile/src/files/mobile-file-explorer-row.tsx index 35cd60cb6c7..e5009cc62fb 100644 --- a/mobile/src/files/mobile-file-explorer-row.tsx +++ b/mobile/src/files/mobile-file-explorer-row.tsx @@ -9,6 +9,7 @@ import { Image as ImageIcon } from 'lucide-react-native' import { triggerSelection } from '../platform/haptics' +import { useMobileTranslation } from '../i18n/use-mobile-translation' import { colors, spacing } from '../theme/mobile-theme' import { useMobileColorScheme } from '../theme/use-mobile-color-scheme' import { type FileExplorerRow, isMarkdownPath, type TreeNode } from './file-tree' @@ -25,6 +26,7 @@ type Props = { } export function MobileFileExplorerRow(props: Props) { + const t = useMobileTranslation() useMobileColorScheme() const { item, maximumIndentation, expanded, onPreviewFile, onRetryDirectory, onToggleDirectory } = props @@ -35,7 +37,9 @@ export function MobileFileExplorerRow(props: Props) { - Loading... + + {t('files.fileExplorerRow.loading', 'Loading...')} + ) } @@ -45,7 +49,7 @@ export function MobileFileExplorerRow(props: Props) { - {item.message || 'Unable to load folder'} + {item.message || t('files.fileExplorerRow.loadFolderFailed', 'Unable to load folder')} [styles.inlineRetryButton, pressed && styles.rowPressed]} @@ -53,9 +57,11 @@ export function MobileFileExplorerRow(props: Props) { triggerSelection() onRetryDirectory(item.relativePath) }} - accessibilityLabel={`Retry loading ${item.relativePath}`} + accessibilityLabel={t('files.fileExplorerRow.retryLoading', 'Retry loading {{path}}', { + path: item.relativePath + })} > - Retry + {t('common.retry', 'Retry')} ) @@ -87,6 +93,7 @@ function TreeRow(props: { onPreviewFile: (relativePath: string, displayName: string) => void onToggleDirectory: (relativePath: string) => void }) { + const t = useMobileTranslation() useMobileColorScheme() const { item, paddingLeft, expanded, onPreviewFile, onToggleDirectory } = props const isDirectory = item.kind === 'directory' @@ -117,10 +124,12 @@ function TreeRow(props: { }} accessibilityLabel={ isDirectory - ? `Open folder ${item.name}` + ? t('files.fileExplorerRow.openFolder', 'Open folder {{name}}', { name: item.name }) : disabled - ? `${item.name} unavailable on mobile` - : `Preview file ${item.name}` + ? t('files.fileExplorerRow.fileUnavailable', '{{name}} unavailable on mobile', { + name: item.name + }) + : t('files.fileExplorerRow.previewFile', 'Preview file {{name}}', { name: item.name }) } > {isDirectory ? ( @@ -145,7 +154,11 @@ function TreeRow(props: { {item.name} - {disabled ? Unavailable on mobile : null} + {disabled ? ( + + {t('files.fileExplorerRow.unavailable', 'Unavailable on mobile')} + + ) : null} ) diff --git a/mobile/src/files/mobile-file-mutation-ownership.ts b/mobile/src/files/mobile-file-mutation-ownership.ts index faa3fec11f5..5d38fd002f2 100644 --- a/mobile/src/files/mobile-file-mutation-ownership.ts +++ b/mobile/src/files/mobile-file-mutation-ownership.ts @@ -1,6 +1,7 @@ import { parseExecutionHostId } from '../../../src/shared/execution-host' import { assertFileMutationOwnershipCapability } from '../../../src/shared/file-mutation-ownership' import type { SshConnectionState, SshMutationExpectation } from '../../../src/shared/ssh-types' +import { translate } from '../i18n/mobile-i18n' import { fileOwnershipRuntimeStatusRead, fileOwnershipSshStateRead, @@ -9,8 +10,13 @@ import { } from './mobile-file-ownership-operations' const FILE_MUTATION_TIMEOUT_MS = 15_000 -const SSH_OWNER_CHANGED_MESSAGE = - "Couldn't verify the SSH connection. Reconnect the host and try again." + +function sshOwnerChangedMessage(): string { + return translate( + 'files.fileMutationOwnership.sshOwnerChanged', + "Couldn't verify the SSH connection. Reconnect the host and try again." + ) +} export type MobileFileMutationOwnership = SshMutationExpectation & { expectedExecutionHostId: 'local' | `ssh:${string}` @@ -28,13 +34,13 @@ export function buildMobileFileMutationOwnership( ): MobileFileMutationOwnership { const host = parseExecutionHostId(worktreeHostId) if (worktreeHostId !== undefined && !host) { - throw new Error(SSH_OWNER_CHANGED_MESSAGE) + throw new Error(sshOwnerChangedMessage()) } if (!host || host.kind === 'local' || host.kind === 'runtime') { return { expectedExecutionHostId: 'local' } } if (sshState?.targetId !== host.targetId || sshState.connectionGeneration === undefined) { - throw new Error(SSH_OWNER_CHANGED_MESSAGE) + throw new Error(sshOwnerChangedMessage()) } return { expectedExecutionHostId: host.id, @@ -60,7 +66,7 @@ export async function captureMobileFileMutationOwnership( ) const summary = fileOwnershipWorktreeRead.interpret(worktreeReply) if (!summary) { - throw new Error(SSH_OWNER_CHANGED_MESSAGE) + throw new Error(sshOwnerChangedMessage()) } const host = parseExecutionHostId(summary.hostId) diff --git a/mobile/src/files/mobile-file-preview-request.ts b/mobile/src/files/mobile-file-preview-request.ts index 20baf32313f..9059dc2ab25 100644 --- a/mobile/src/files/mobile-file-preview-request.ts +++ b/mobile/src/files/mobile-file-preview-request.ts @@ -14,6 +14,7 @@ import { normalizeMobileFilePreviewResult, previewError, previewErrorFromRefusal, + terminalArtifactPreviewChangedError, type MobileFilePreviewResult } from './mobile-file-preview-response' import { @@ -270,14 +271,7 @@ async function verifyTerminalArtifactBaseContent( return { status: 'error', error: latest } } if (!terminalArtifactPreviewMatchesBase(latest, baseContent)) { - return { - status: 'error', - error: { - status: 'error', - message: 'File changed on desktop. Reload preview before saving', - reconnect: false - } - } + return { status: 'error', error: terminalArtifactPreviewChangedError() } } return { status: 'ok', source: readSource, refreshed } } diff --git a/mobile/src/files/mobile-file-preview-response.ts b/mobile/src/files/mobile-file-preview-response.ts index 36f7781987e..226076bacdc 100644 --- a/mobile/src/files/mobile-file-preview-response.ts +++ b/mobile/src/files/mobile-file-preview-response.ts @@ -1,5 +1,7 @@ import type { MobileFileMedia } from './mobile-file-media' import { classifyMobileArtifact } from '../session/mobile-artifact-kind' +import { translate } from '../i18n/mobile-i18n' +import { getUiIntlLocale } from '../i18n/ui-intl-locale' import type { RpcFailure } from '../transport/types' import { isMarkdownPath } from './file-tree' import { isTerminalArtifactGrantError } from './terminal-artifact-grant-error' @@ -54,16 +56,48 @@ export function previewErrorFromRefusal(error: RpcFailure['error']): MobileFileP return previewError(error.message || error.code) } +export function terminalArtifactPreviewChangedError(): MobileFilePreviewResult { + return { + status: 'error', + message: translate( + 'files.filePreviewResponse.fileChanged', + 'File changed on desktop. Reload preview before saving' + ), + reconnect: false + } +} + export function previewError(message: string): MobileFilePreviewResult { const normalized = message.toLowerCase() if (normalized === 'binary_file' || normalized.includes('binary_file')) { - return { status: 'error', message: 'Binary preview unavailable', reconnect: false } + return { + status: 'error', + message: translate( + 'files.filePreviewResponse.binaryUnavailable', + 'Binary preview unavailable' + ), + reconnect: false + } } if (normalized === 'file_too_large' || normalized.includes('file_too_large')) { - return { status: 'error', message: 'File too large for mobile preview', reconnect: false } + return { + status: 'error', + message: translate( + 'files.filePreviewResponse.fileTooLarge', + 'File too large for mobile preview' + ), + reconnect: false + } } if (isTerminalArtifactGrantError(normalized)) { - return { status: 'error', message: 'Reload preview before saving', reconnect: false } + return { + status: 'error', + message: translate( + 'files.filePreviewResponse.reloadBeforeSaving', + 'Reload preview before saving' + ), + reconnect: false + } } if ( normalized.includes('remote connection dropped') || @@ -71,7 +105,14 @@ export function previewError(message: string): MobileFilePreviewResult { normalized.includes('disconnected') || normalized.includes('reconnect the ssh target') ) { - return { status: 'error', message: 'Unable to reach the desktop filesystem', reconnect: true } + return { + status: 'error', + message: translate( + 'files.filePreviewResponse.filesystemUnreachable', + 'Unable to reach the desktop filesystem' + ), + reconnect: true + } } if ( normalized.includes('enoent') || @@ -79,22 +120,40 @@ export function previewError(message: string): MobileFilePreviewResult { normalized.includes('not found') || normalized.includes('does not exist') ) { - return { status: 'error', message: 'File not found', reconnect: false } + return { + status: 'error', + message: translate('files.filePreviewResponse.fileNotFound', 'File not found'), + reconnect: false + } + } + return { + status: 'error', + message: translate('files.filePreviewResponse.loadFailed', 'Unable to load preview'), + reconnect: false } - return { status: 'error', message: 'Unable to load preview', reconnect: false } } export function formatPreviewByteLength(byteLength: number): string { if (!Number.isFinite(byteLength) || byteLength < 0) { - return 'unknown size' + return translate('files.filePreviewResponse.unknownSize', 'unknown size') } if (byteLength < 1024) { - return `${byteLength} B` + return translate('files.filePreviewResponse.bytes', '{{size}} B', { + size: byteLength.toLocaleString(getUiIntlLocale(), { useGrouping: false }) + }) } if (byteLength < 1024 * 1024) { - return `${Math.round(byteLength / 1024)} KB` + return translate('files.filePreviewResponse.kilobytes', '{{size}} KB', { + size: Math.round(byteLength / 1024).toLocaleString(getUiIntlLocale(), { useGrouping: false }) + }) } - return `${(byteLength / (1024 * 1024)).toFixed(1)} MB` + return translate('files.filePreviewResponse.megabytes', '{{size}} MB', { + size: (byteLength / (1024 * 1024)).toLocaleString(getUiIntlLocale(), { + useGrouping: false, + minimumFractionDigits: 1, + maximumFractionDigits: 1 + }) + }) } function normalizeImagePreviewResult(result: unknown): MobileFilePreviewResult { diff --git a/mobile/src/host-list-action-sheet-actions.ts b/mobile/src/host-list-action-sheet-actions.ts index 21794d1a926..8d268d2dc3f 100644 --- a/mobile/src/host-list-action-sheet-actions.ts +++ b/mobile/src/host-list-action-sheet-actions.ts @@ -27,6 +27,7 @@ export function getHostListActionSheetActions(args: { return [ { + id: 'connect', label: args.hasEverConnected && isLive ? translate('hostScreen.hostListActionSheetActions.reconnect', 'Reconnect') @@ -40,6 +41,7 @@ export function getHostListActionSheetActions(args: { ...(isLive ? [ { + id: 'disconnect', label: translate('hostScreen.hostListActionSheetActions.disconnect', 'Disconnect'), icon: PowerOff, onPress: () => { @@ -50,6 +52,7 @@ export function getHostListActionSheetActions(args: { ] : []), { + id: 'networkDiagnostics', label: translate( 'hostScreen.hostListActionSheetActions.networkDiagnostics', 'Network diagnostics' @@ -61,6 +64,7 @@ export function getHostListActionSheetActions(args: { } }, { + id: 'editHost', label: translate('hostScreen.hostListActionSheetActions.editHost', 'Edit host'), icon: Edit3, closeBeforePress: true, @@ -70,6 +74,7 @@ export function getHostListActionSheetActions(args: { } }, { + id: 'remove', label: translate('common.remove', 'Remove'), destructive: true, closeBeforePress: true, diff --git a/mobile/src/host-screen/host-screen-overlays.tsx b/mobile/src/host-screen/host-screen-overlays.tsx index 91cf9462414..9211368d3d3 100644 --- a/mobile/src/host-screen/host-screen-overlays.tsx +++ b/mobile/src/host-screen/host-screen-overlays.tsx @@ -117,6 +117,7 @@ export function HostScreenOverlays({ controller }: { controller: HostScreenContr ? [] : [ { + id: 'sleep', label: t('hostScreen.hostScreenOverlays.sleep', 'Sleep'), icon: Moon, onPress: () => { @@ -126,6 +127,7 @@ export function HostScreenOverlays({ controller }: { controller: HostScreenContr } ]), { + id: 'togglePin', label: isWorktreePinned(actionTarget, state.pinnedIds) ? t('hostScreen.hostScreenOverlays.unpin', 'Unpin') : t('hostScreen.hostScreenOverlays.pin', 'Pin'), @@ -136,6 +138,7 @@ export function HostScreenOverlays({ controller }: { controller: HostScreenContr } }, { + id: 'delete', label: t('common.delete', 'Delete'), destructive: true, onPress: () => state.setConfirmDelete(actionTarget), diff --git a/mobile/src/i18n/en-runtime-required.json b/mobile/src/i18n/en-runtime-required.json index 920206c485b..8f690de7048 100644 --- a/mobile/src/i18n/en-runtime-required.json +++ b/mobile/src/i18n/en-runtime-required.json @@ -17,6 +17,58 @@ "restoreAfterSeconds_other": "After {{count}}s" } }, + "chat": { + "toolActivity": { + "count_one": "{{count}} tool call", + "count_other": "{{count}} tool calls" + }, + "toolRun": { + "liveBrowserTimes_other": "Using the browser {{count}} times", + "liveFileChange_one": "Editing {{count}} file", + "liveFileChange_other": "Editing {{count}} files", + "liveListFiles_one": "Listing {{count}} directory", + "liveListFiles_other": "Listing {{count}} directories", + "liveMcpToolCall_one": "Using {{count}} integration", + "liveMcpToolCall_other": "Using {{count}} integrations", + "liveOther_one": "Using {{count}} tool", + "liveOther_other": "Using {{count}} tools", + "liveRead_one": "Reading {{count}} file", + "liveRead_other": "Reading {{count}} files", + "liveSearch_one": "Searching {{count}} time", + "liveSearch_other": "Searching {{count}} times", + "liveSubAgentActivity_one": "Running {{count}} agent", + "liveSubAgentActivity_other": "Running {{count}} agents", + "liveTodoListTimes_other": "Updating the plan {{count}} times", + "liveUnknown_one": "Running {{count}} command", + "liveUnknown_other": "Running {{count}} commands", + "liveWebSearch_one": "Searching the web {{count}} time", + "liveWebSearch_other": "Searching the web {{count}} times", + "settledBrowserTimes_other": "Used the browser {{count}} times", + "settledFileChange_one": "Edited {{count}} file", + "settledFileChange_other": "Edited {{count}} files", + "settledListFiles_one": "Listed {{count}} directory", + "settledListFiles_other": "Listed {{count}} directories", + "settledMcpToolCall_one": "Used {{count}} integration", + "settledMcpToolCall_other": "Used {{count}} integrations", + "settledOther_one": "Used {{count}} tool", + "settledOther_other": "Used {{count}} tools", + "settledRead_one": "Read {{count}} file", + "settledRead_other": "Read {{count}} files", + "settledSearch_one": "Searched {{count}} time", + "settledSearch_other": "Searched {{count}} times", + "settledSubAgentActivity_one": "Ran {{count}} agent", + "settledSubAgentActivity_other": "Ran {{count}} agents", + "settledTodoListTimes_other": "Updated the plan {{count}} times", + "settledUnknown_one": "Ran {{count}} command", + "settledUnknown_other": "Ran {{count}} commands", + "settledWebSearch_one": "Searched the web {{count}} time", + "settledWebSearch_other": "Searched the web {{count}} times" + }, + "turnStatus": { + "seconds_one": "{{count}}s", + "seconds_other": "{{count}}s" + } + }, "components": { "codexResetCredit": { "available_one": "{{count}} reset available", @@ -75,12 +127,6 @@ "yearsAgo_one": "{{count}}y ago", "yearsAgo_other": "{{count}}y ago" }, - "commentsSection": { - "resolvedComment_one": "Resolved comment by {{author}}", - "resolvedComment_other": "Resolved comment by {{author}} ({{count}})", - "resolvedThread_one": "Resolved thread by {{author}}", - "resolvedThread_other": "Resolved thread by {{author}} ({{count}})" - }, "conflictingFilesSection": { "commitsBehind_one": "{{count}} commit behind (base commit: {{baseCommit}})", "commitsBehind_other": "{{count}} commits behind (base commit: {{baseCommit}})" @@ -102,6 +148,42 @@ "pending_other": "{{count}} pending" } }, + "session": { + "agentChildRow": { + "days_one": "{{count}}d", + "days_other": "{{count}}d", + "hours_one": "{{count}}h", + "hours_other": "{{count}}h", + "minutes_one": "{{count}}m", + "minutes_other": "{{count}}m" + }, + "backgroundTask": { + "countAgents_one": "{{count}} agent", + "countAgents_other": "{{count}} agents", + "countMonitors_one": "{{count}} monitor", + "countMonitors_other": "{{count}} monitors", + "countShell_one": "{{count}} shell", + "countShell_other": "{{count}} shells", + "countTasks_one": "{{count}} task", + "countTasks_other": "{{count}} tasks", + "countWorkflows_one": "{{count}} workflow", + "countWorkflows_other": "{{count}} workflows", + "headerTotalCount_one": "{{count}} background task", + "headerTotalCount_other": "{{count}} background tasks" + }, + "fileReader": { + "reviewNotes_one": "{{count}} review note", + "reviewNotes_other": "{{count}} review notes" + }, + "queuedMessageCardRow": { + "images_one": "{{count}} image", + "images_other": "{{count}} images" + }, + "useDiffReviewGitActions": { + "reviewedFilesStaged_one": "{{count}} reviewed file staged", + "reviewedFilesStaged_other": "{{count}} reviewed files staged" + } + }, "settings": { "appUpdateSettingsRows": { "lastCheckedDays_one": "Last checked {{count}}d ago", @@ -116,6 +198,24 @@ "cleanupUnconfirmed_other": "Couldn't confirm cleanup for {{count}} credentials on this device." } }, + "sourceControl": { + "dropdownCounts": { + "fastForwardCount_one": "Fast-forward {{count}} commit", + "fastForwardCount_other": "Fast-forward {{count}} commits", + "pullCount_one": "Pull {{count}} commit", + "pullCount_other": "Pull {{count}} commits", + "pushCount_one": "Push {{count}} commit", + "pushCount_other": "Push {{count}} commits", + "titleForcePushOlderCopiesCount_one": "Remote only has older copies of local commits. Force push {{count}} branch commit with lease to update {{upstreamName}}.", + "titleForcePushOlderCopiesCount_other": "Remote only has older copies of local commits. Force push {{count}} branch commits with lease to update {{upstreamName}}.", + "titleManualForcePushReplaceRemote_one": "Force push {{count}} local commit with lease to update {{upstreamName}} and replace remote-only commits.", + "titleManualForcePushReplaceRemote_other": "Force push {{count}} local commits with lease to update {{upstreamName}} and replace remote-only commits.", + "titleManualForcePush_one": "Force push {{count}} local commit with lease to update {{upstreamName}}.", + "titleManualForcePush_other": "Force push {{count}} local commits with lease to update {{upstreamName}}.", + "titleUnpublishedForcePushCount_one": "Force push {{count}} branch commit with lease and set an upstream if needed.", + "titleUnpublishedForcePushCount_other": "Force push {{count}} branch commits with lease and set an upstream if needed." + } + }, "worktree": { "agentRowDisplay": { "days_one": "{{count}}d", diff --git a/mobile/src/i18n/locales/de.json b/mobile/src/i18n/locales/de.json index 63cb00ac86a..9447153d73f 100644 --- a/mobile/src/i18n/locales/de.json +++ b/mobile/src/i18n/locales/de.json @@ -139,6 +139,367 @@ "title": "Terminal" } }, + "browser": { + "addressField": { + "urlPlaceholder": "URL" + }, + "pane": { + "invalidUrl": "Gültige URL eingeben." + }, + "paneView": { + "dialogTitle": "Browser-Dialog", + "forward": "Vorwärts", + "keyboardPlaceholder": "Text auf der Seite eingeben…", + "reload": "Neu laden", + "sendText": "Text an den Browser senden" + }, + "pointerModifiers": { + "clickModifier": "{{key}} als Klick-Modifikator" + }, + "useBrowserCommands": { + "deliveryUnconfirmed": "Übertragung nicht bestätigt – vor erneutem Versuch Seite prüfen", + "dialogAnswerFailed": "Die Antwort hat die Seite nicht erreicht.", + "sent": "Gesendet" + }, + "viewModeSwitch": { + "mobileView": "Mobile Ansicht der Website anzeigen", + "webView": "Desktop-Ansicht der Website anzeigen" + } + }, + "chat": { + "ask": { + "answerPlaceholder": "Antwort eingeben", + "next": "Weiter", + "other": "Sonstiges…", + "skip": "Überspringen", + "step": "Schritt {{step}}", + "submit": "Absenden" + }, + "askRow": { + "asked": "Gefragt:", + "awaiting": "Warten auf Benutzereingabe:", + "awaitingUnnamed": "Warten auf Benutzereingabe", + "questionCount": "{{value0}} Fragen" + }, + "composer": { + "addToMessage": "Zur Nachricht hinzufügen", + "attachImage": "Bild anhängen", + "cancelEditing": "Bearbeitung abbrechen", + "placeholder": "Frage stellen, @ für Dateiverweise, / für Befehle", + "saveEditedMessage": "Bearbeitete Nachricht speichern", + "sendMessage": "Nachricht senden" + }, + "composerAttachments": { + "attachedImage": "Angehängtes Bild", + "file": "Datei", + "fileMeta": "{{type}} · {{size}}", + "removeFile": "{{name}} entfernen", + "removeImage": "Bild entfernen", + "uploadingFile": "{{name}} wird hochgeladen" + }, + "composerPlaceholder": { + "prompt": "Frage stellen, @ für Dateiverweise, / für Befehle", + "reconnecting": "Verbindung wird wiederhergestellt…", + "waitingForTerminal": "Warten auf Terminal…" + }, + "diffCard": { + "copyDiff": "Diff kopieren", + "diffGap": "Nicht angezeigte Zeilen", + "diffTruncated": "Diff gekürzt" + }, + "hostStatus": { + "continuedOutside": "Außerhalb dieses Chats fortgesetzt.", + "continuedOutsideRewound": "Außerhalb dieses Chats fortgesetzt. Die obigen früheren Antworten sind nicht mehr im Verlauf dieser Unterhaltung enthalten.", + "historyItemTooLarge": "Dieser Teil des Chats war zu groß für die Anzeige.", + "historyRepaired": "Ein Teil des Chatverlaufs konnte nicht geladen werden." + }, + "message": { + "copyMessage": "Nachricht kopieren" + }, + "messageActionsSheet": { + "clipboardRejected": "Die Zwischenablage hat den Text abgewiesen.", + "copyFailed": "Kopieren fehlgeschlagen", + "copyMessage": "Nachricht kopieren", + "selectText": "Text auswählen" + }, + "nativeChatImageViewer": { + "closeImage": "Bild schließen" + }, + "nativeChatProse": { + "attachedImage": "Angehängtes Bild", + "image": "Bild", + "openImageHint": "Öffnet das Bild in voller Größe" + }, + "nativeChatToolLine": { + "added": "Hinzugefügt", + "copyCommand": "Befehl kopieren", + "deleted": "Gelöscht", + "durationHours": "{{hours}} h {{minutes}} min {{seconds}} s", + "durationMilliseconds": "{{milliseconds}} ms", + "durationMinutes": "{{minutes}} min {{seconds}} s", + "durationSeconds": "{{seconds}} s", + "edited": "Bearbeitet", + "editing": "Wird bearbeitet", + "exitCode": "Exit-Code {{exitCode}}", + "fetched": "Abgerufen", + "fetching": "Wird abgerufen", + "listed": "Aufgelistet", + "listing": "Wird aufgelistet", + "ran": "Ausgeführt", + "read": "Gelesen", + "reading": "Wird gelesen", + "renamed": "Umbenannt", + "result": "Ergebnis", + "running": "Wird ausgeführt", + "searched": "Gesucht", + "searchedWeb": "Im Web gesucht", + "searching": "Suche läuft", + "searchingWeb": "Websuche läuft", + "subagent": "Unteragent", + "triedEdit": "Bearbeiten versucht", + "triedFetch": "Abrufen versucht", + "triedList": "Auflisten versucht", + "triedRead": "Lesen versucht", + "triedSearch": "Suchen versucht", + "triedSearchWeb": "Websuche versucht" + }, + "nativeChatUserMessageFold": { + "loadFailed": "Vollständige Nachricht anzeigen — Laden fehlgeschlagen, zum Wiederholen tippen", + "showFullMessage": "Vollständige Nachricht anzeigen", + "showLess": "Weniger anzeigen" + }, + "nativeChatVisual": { + "defaultTitle": "Visualisierung", + "openFullscreen": "{{title}} im Vollbild öffnen", + "retryHint": "Versucht, die Visualisierung erneut zu laden", + "unavailable": "Visualisierung nicht verfügbar" + }, + "notice": { + "compaction": "Kontext komprimiert", + "contextCleared": "Kontext geleert", + "plan": "Plan", + "stoppedManually": "Manuell gestoppt" + }, + "orcaStop": { + "canContinue": "Die Unterhaltung kann fortgesetzt werden.", + "crash": "Orca auf {{machine}} wurde während dieser Antwort unerwartet beendet.", + "quit": "Orca auf {{machine}} wurde während dieser Antwort geschlossen.", + "remote": "Orca auf {{machine}} wurde während dieser Antwort gestoppt.", + "unnamedServer": "dem entfernten Server", + "update": "Orca auf {{machine}} wurde während dieser Antwort für ein Update neu gestartet." + }, + "permission": { + "needsAccessTo": "Benötigt Zugriff auf: ", + "newerVersionRequired": "Diese Anfrage erfordert eine neuere Version von Orca.", + "planFile": "Plandatei: {{filePath}}", + "reason": "Grund: " + }, + "promptCard": { + "answerInTerminal": "Im Terminal antworten", + "terminalExplanation": "Diese OMP-Frage im Terminal beantworten." + }, + "promptStrip": { + "expand": "Ausklappen" + }, + "question": { + "otherReplyPlaceholder": "Oder eine Antwort eingeben…", + "replyPlaceholder": "Antwort eingeben…", + "sendReply": "Antwort senden", + "submit": "Absenden", + "submitCount": "Absenden ({{count}})", + "submitSelectedOptions": "Ausgewählte Optionen absenden" + }, + "queuedMessages": { + "clearAll": "Alles löschen", + "deleteAll": "Alle Nachrichten in der Warteschlange löschen", + "editMessage": "Nachricht bearbeiten", + "pausedState": "Pausiert", + "queuedState": "In der Warteschlange", + "resume": "Fortsetzen", + "resumeSending": "Senden der Nachrichten in der Warteschlange fortsetzen", + "title": "Nachrichten in der Warteschlange" + }, + "receipt": { + "cancelled": "Abgebrochen", + "cancelledBy": "Auf {{device}} abgebrochen", + "emptyAnswer": "Leere Antwort", + "resolved": "Erledigt", + "resolver": "Auf {{device}} beantwortet", + "unavailable": "Ausgewählte Antwort nicht verfügbar" + }, + "sessionOptionCopy": { + "availableAfterSessionStarts": "Nach dem Sitzungsstart verfügbar.", + "chooseInAgentPicker": "In der Agentenauswahl auswählen…", + "effort": "Denkaufwand", + "fast": "Schnell", + "fastMode": "Schnellmodus", + "high": "Hoch", + "low": "Niedrig", + "max": "Maximal", + "medium": "Mittel", + "mediumShort": "Mittel", + "minimal": "Minimal", + "minimalShort": "Min", + "model": "Modell", + "notSet": "Nicht festgelegt", + "off": "Aus", + "on": "Ein", + "permissions": "Berechtigungen", + "sessionOptions": "Sitzungsoptionen", + "setWhenSessionStarts": "Wird beim Sitzungsstart festgelegt.", + "speed": "Geschwindigkeit", + "standard": "Standard", + "thinking": "Nachdenken", + "toggleOption": "{{value0}} umschalten", + "ultra": "Ultra", + "xhigh": "Sehr hoch", + "xhighShort": "Sehr hoch" + }, + "sessionOptionPickers": { + "agentWorking": "Agent arbeitet", + "backToModels": "Zurück zu den Modellen", + "closePicker": "Auswahl schließen", + "dispatchUnconfirmed": "An den Agenten gesendet — nicht bestätigt", + "modelFullAccessLabel": "Modell, {{value}}, Vollzugriff", + "modelLabel": "Modell, {{value}}", + "permissions": "Berechtigungen", + "permissionsLabel": "Berechtigungen, {{value}}", + "settingsLocked": "Die Modelleinstellungen werden nach diesem Durchgang freigegeben.", + "workingAnnouncement": "Der Agent arbeitet. Die Modelleinstellungen werden nach diesem Durchgang freigegeben." + }, + "sessionOptionRows": { + "defaultMarker": "Standard", + "notReportedMarker": "Nicht gemeldet" + }, + "sessionOptionSegments": { + "dispatchUnconfirmed": "An den Agenten gesendet — nicht bestätigt" + }, + "stopButton": { + "stopAgent": "Agenten stoppen", + "stoppingState": "Wird gestoppt…" + }, + "streamFrame": { + "streamEnded": "Übertragung des Chatverlaufs beendet", + "streamFailed": "Übertragung des Chatverlaufs fehlgeschlagen" + }, + "taskList": { + "added": "{{task}} hinzugefügt", + "completed": "Abgeschlossen", + "empty": "Keine Aufgaben", + "finished": "{{task}} abgeschlossen", + "inProgress": "In Bearbeitung", + "pending": "Ausstehend", + "progress": "{{completed}} von {{total}} Aufgaben abgeschlossen", + "removed": "{{task}} entfernt", + "reset": "Als ausstehend markiert: {{task}}", + "showAll": "Vollständige Aufgabenliste", + "started": "{{task}} gestartet", + "taskStatus": "{{status}}: {{task}}", + "title": "Aufgaben", + "unchanged": "Aufgaben unverändert", + "updated": "{{task}} aktualisiert" + }, + "toolActivity": { + "count_one": "{{count}} Werkzeugaufruf", + "count_other": "{{count}} Werkzeugaufrufe", + "failedCallsLabel": "Fehlgeschlagene Werkzeugaufrufe: {{value0}}", + "failedCount": "{{value0}} fehlgeschlagen", + "interruptedCallsLabel": "Unterbrochene Werkzeugaufrufe: {{value0}}", + "interruptedCount": "{{value0}} unterbrochen", + "moreCalls": "+{{value0}} weitere", + "result": "Ergebnis", + "runningCommand": "Befehl wird ausgeführt", + "runningNamed": "{{toolName}} wird ausgeführt", + "runningNamedPreview": "Wird ausgeführt: {{toolName}} {{preview}}", + "runningPreview": "Wird ausgeführt: {{preview}}" + }, + "toolRun": { + "list": "{{value0}} und {{value1}}", + "liveBrowserOnce": "Browser wird verwendet", + "liveBrowserTimes_one": "Browser wird {{count}}-mal verwendet", + "liveBrowserTimes_other": "Browser wird {{count}}-mal verwendet", + "liveFileChange_one": "{{count}} Datei wird bearbeitet", + "liveFileChange_other": "{{count}} Dateien werden bearbeitet", + "liveListFiles_one": "{{count}} Verzeichnis wird aufgelistet", + "liveListFiles_other": "{{count}} Verzeichnisse werden aufgelistet", + "liveMcpToolCall_one": "{{count}} Integration wird verwendet", + "liveMcpToolCall_other": "{{count}} Integrationen werden verwendet", + "liveOther_one": "{{count}} Werkzeug wird verwendet", + "liveOther_other": "{{count}} Werkzeuge werden verwendet", + "liveRead_one": "{{count}} Datei wird gelesen", + "liveRead_other": "{{count}} Dateien werden gelesen", + "liveSearch_one": "{{count}} Suche läuft", + "liveSearch_other": "{{count}} Suchvorgänge laufen", + "liveSubAgentActivity_one": "{{count}} Agent wird ausgeführt", + "liveSubAgentActivity_other": "{{count}} Agenten werden ausgeführt", + "liveTodoListOnce": "Plan wird aktualisiert", + "liveTodoListTimes_one": "Plan wird {{count}}-mal aktualisiert", + "liveTodoListTimes_other": "Plan wird {{count}}-mal aktualisiert", + "liveUnknown_one": "{{count}} Befehl wird ausgeführt", + "liveUnknown_other": "{{count}} Befehle werden ausgeführt", + "liveWebSearch_one": "{{count}} Websuche läuft", + "liveWebSearch_other": "{{count}} Websuchen laufen", + "pair": "{{value0}} und {{value1}}", + "separator": ", ", + "settledBrowserOnce": "Browser verwendet", + "settledBrowserTimes_one": "Browser {{count}}-mal verwendet", + "settledBrowserTimes_other": "Browser {{count}}-mal verwendet", + "settledFileChange_one": "{{count}} Datei bearbeitet", + "settledFileChange_other": "{{count}} Dateien bearbeitet", + "settledListFiles_one": "{{count}} Verzeichnis aufgelistet", + "settledListFiles_other": "{{count}} Verzeichnisse aufgelistet", + "settledMcpToolCall_one": "{{count}} Integration verwendet", + "settledMcpToolCall_other": "{{count}} Integrationen verwendet", + "settledOther_one": "{{count}} Werkzeug verwendet", + "settledOther_other": "{{count}} Werkzeuge verwendet", + "settledRead_one": "{{count}} Datei gelesen", + "settledRead_other": "{{count}} Dateien gelesen", + "settledSearch_one": "{{count}}-mal gesucht", + "settledSearch_other": "{{count}}-mal gesucht", + "settledSubAgentActivity_one": "{{count}} Agent ausgeführt", + "settledSubAgentActivity_other": "{{count}} Agenten ausgeführt", + "settledTodoListOnce": "Plan aktualisiert", + "settledTodoListTimes_one": "Plan {{count}}-mal aktualisiert", + "settledTodoListTimes_other": "Plan {{count}}-mal aktualisiert", + "settledUnknown_one": "{{count}} Befehl ausgeführt", + "settledUnknown_other": "{{count}} Befehle ausgeführt", + "settledWebSearch_one": "{{count}}-mal im Web gesucht", + "settledWebSearch_other": "{{count}}-mal im Web gesucht" + }, + "turnStatus": { + "failedAfter": "Nach {{value0}} fehlgeschlagen", + "hoursMinutesSeconds": "{{hours}} h {{minutes}} min {{seconds}} s", + "interruptedAfter": "Nach {{value0}} unterbrochen", + "minutesSeconds": "{{minutes}} min {{seconds}} s", + "queueAfterStop": "Nachricht zur Bearbeitung nach dem Stopp in die Warteschlange stellen", + "responding": "Der Agent antwortet", + "seconds_one": "{{count}} s", + "seconds_other": "{{count}} s", + "sendAfterStop": "Nachricht zur Bearbeitung nach dem Stopp senden", + "stopping": "Wird gestoppt…", + "thinking": "Denkt nach", + "toggleDetails": "Details zum Durchgang ein-/ausblenden", + "workedFor": "{{value0}} lang gearbeitet", + "working": "Arbeitet…", + "workingFor": "Arbeitet seit {{value0}}" + }, + "usageLimit": { + "reached": "{{agent}} hat das Nutzungslimit erreicht.", + "reachedNamed": "{{agent}} hat das Limit erreicht ({{window}}).", + "resetsAt": "Nächste Zurücksetzung: {{when}}.", + "theAgent": "Der Agent", + "windowCredits": "Zusätzliche Nutzung", + "windowSession": "Sitzung", + "windowWeekly": "Wöchentliche Nutzung", + "windowWeeklyModel": "Wöchentliche Modellnutzung" + }, + "useImageUpload": { + "attachFailed": "Anhängen fehlgeschlagen", + "disconnected": "Anhängen fehlgeschlagen (nicht verbunden)", + "imageTooLarge": "Bild zum Anhängen zu groß", + "permissionDenied": "Zugriff auf Fotos verweigert" + } + }, "common": { "back": "Zurück", "cancel": "Abbrechen", @@ -151,6 +512,8 @@ "done": "Abschließen", "edit": "Bearbeiten", "ok": "OK", + "open": "Öffnen", + "refresh": "Aktualisieren", "remove": "Entfernen", "retry": "Erneut versuchen", "save": "Speichern", @@ -757,8 +1120,51 @@ "unexpectedResponse": "Unerwartete Antwort" }, "webShellUpdateFailureCopy": { + "blockedReason": "Blockiert: {{reason}}.", + "blockedWorkspace": "Arbeitsbereich blockiert.", + "failed": "Fehleransicht angezeigt.", "failedAgo": "Das letzte Update von {{hostName}} ist vor {{time}} fehlgeschlagen: {{reason}}{{offered}}. {{outcome}}", - "failedJustNow": "Das letzte Update von {{hostName}} ist soeben fehlgeschlagen: {{reason}}{{offered}}. {{outcome}}" + "failedJustNow": "Das letzte Update von {{hostName}} ist soeben fehlgeschlagen: {{reason}}{{offered}}. {{outcome}}", + "hostCodeCopy": { + "assetChanged": "eine Ressource hat sich auf dem Host geändert", + "assetUnknown": "der Host hat eine Ressource nicht erkannt", + "buildChanged": "der Build des Hosts hat sich während des Downloads geändert", + "offsetInvalid": "der Host hat eine Leseposition abgelehnt", + "readLimited": "der Host hat gleichzeitige Lesezugriffe begrenzt", + "unavailable": "der Host hat kein Arbeitsbereichspaket" + }, + "nativeRoute": "Native Ansicht angezeigt.", + "offeredGeneration": " (Generation {{generation}})", + "openedCached": "Auf die gespeicherte Version zurückgegriffen.", + "openedCachedGeneration": "Auf die gespeicherte Version zurückgegriffen (Generation {{generation}}).", + "reasonCopy": { + "assetChecksumMismatch": "die Prüfsumme einer Ressource stimmte nicht überein", + "assetEntryChanged": "eine Ressource entsprach nicht mehr dem Manifest", + "assetNoProgress": "beim Lesen einer Ressource gab es keinen Fortschritt", + "assetOverlong": "eine Ressource war größer als im Manifest angegeben", + "assetShort": "eine Ressource war kleiner als angegeben", + "buildChangedMidFetch": "der Build des Hosts hat sich während des Downloads geändert", + "cacheWriteFailed": "der Download konnte auf diesem Smartphone nicht gespeichert werden", + "chunkMisrouted": "ein Datenblock gehörte zur falschen Ressource oder Leseposition", + "chunkOversize": "ein Datenblock war größer als vom Host erlaubt", + "connectionLost": "die Verbindung wurde unterbrochen", + "fetchStopped": "der Download wurde gestoppt", + "hostRefused": "der Host hat den Lesezugriff verweigert", + "noConnection": "keine Verbindung zum Host", + "rangeUndecodable": "komprimierte Lesedaten konnten nicht dekodiert werden", + "replyUnreadable": "der Host hat eine Antwort gesendet, die diese App nicht lesen konnte", + "unrecognisedError": "ein unbekannter Fehler" + }, + "routeUnavailable": "Hinweis angezeigt, dass die Ansicht nicht verfügbar ist.", + "waiting": "Auf den Host gewartet.", + "wallCopy": { + "bundleShellTooOld": "diese App ist für das gespeicherte Paket zu alt", + "bundleTooOldForHost": "das gespeicherte Paket ist für den Host zu alt", + "bundleUnavailable": "der Host hat kein Arbeitsbereichspaket", + "hostTooOldForBundle": "der Host ist für das gespeicherte Paket zu alt", + "pageRouteGrantMissing": "diese Ansicht erfordert eine neuere App-Version", + "pageRouteUnlisted": "der Arbeitsbereich des Hosts enthält diese Ansicht nicht" + } } }, "dictation": { @@ -775,6 +1181,95 @@ "updateFailed": "Diktateinstellungen konnten nicht aktualisiert werden" } }, + "files": { + "fileExplorerPanel": { + "backToSession": "Zurück zur Sitzung", + "closeFiles": "Dateien schließen", + "connecting": "Verbindung zum Desktop wird hergestellt…", + "emptyState": "Keine Dateien gefunden", + "loadFailed": "Dateien konnten nicht geladen werden.", + "loadInterrupted": "Laden des Ordners unterbrochen", + "title": "Dateien", + "truncatedList": "{{worktreeLabel}} – Erste 5000 werden angezeigt", + "waiting": "Warten auf den Desktop…" + }, + "fileExplorerRow": { + "fileUnavailable": "{{name}} auf Mobilgeräten nicht verfügbar", + "loadFolderFailed": "Ordner konnte nicht geladen werden", + "loading": "Wird geladen…", + "openFolder": "Ordner {{name}} öffnen", + "previewFile": "Vorschau der Datei {{name}} anzeigen", + "retryLoading": "{{path}} erneut laden", + "unavailable": "Auf Mobilgeräten nicht verfügbar" + }, + "fileMarkdownPreview": { + "copyLoadedSource": "Geladenen Markdown-Quelltext kopieren", + "copySource": "Markdown-Quelltext kopieren", + "viewPreview": "Gerenderte Markdown-Vorschau anzeigen", + "viewSource": "Markdown-Quelltext anzeigen" + }, + "fileMediaPreview": { + "downloading": "Download: {{bytes}} von {{total}}", + "loadFailed": "Medien konnten nicht geladen werden.", + "loading": "Medien werden geladen…", + "waiting": "Warten auf den Desktop…" + }, + "fileMutationOwnership": { + "sshOwnerChanged": "SSH-Verbindung konnte nicht überprüft werden. Host neu verbinden und erneut versuchen." + }, + "filePreviewBody": { + "emptyFile": "Leere Datei", + "imageLabel": "Bild: {{title}}" + }, + "filePreviewEditableSource": { + "editorLabel": "Editor für {{title}}" + }, + "filePreviewResponse": { + "binaryUnavailable": "Vorschau für Binärdateien nicht verfügbar", + "bytes": "{{size}} B", + "fileChanged": "Datei wurde auf dem Desktop geändert. Vorschau vor dem Speichern neu laden", + "fileNotFound": "Datei nicht gefunden", + "fileTooLarge": "Datei zu groß für die mobile Vorschau", + "filesystemUnreachable": "Dateisystem des Desktops konnte nicht erreicht werden", + "kilobytes": "{{size}} KB", + "loadFailed": "Vorschau konnte nicht geladen werden", + "megabytes": "{{size}} MB", + "reloadBeforeSaving": "Vorschau vor dem Speichern neu laden", + "unknownSize": "unbekannte Größe" + }, + "filePreviewScreen": { + "backToFiles": "Zurück zu den Dateien", + "chooseWorkspaceFile": "Eine Datei in diesem Arbeitsbereich auswählen.", + "discard": "Verwerfen", + "discardMessage": "Nicht gespeicherte Änderungen gehen verloren.", + "discardTitle": "Änderungen verwerfen?", + "fileTitle": "Datei", + "loadFailed": "Vorschau konnte nicht geladen werden.", + "loading": "Vorschau wird geladen…", + "openFailed": "Datei konnte nicht geöffnet werden", + "saveArtifact": "Terminal-Artefakt speichern", + "saveFailed": "Datei konnte nicht gespeichert werden.", + "stay": "Bleiben", + "title": "Vorschau", + "waiting": "Warten auf den Desktop…" + }, + "filePreviewSourceText": { + "previewLabel": "Dateivorschau", + "truncatedNote": "Vorschau gekürzt. Dateigröße: {{size}}." + }, + "markdownPreviewImage": { + "expandFallback": "Bild vergrößern", + "expandImage": "{{alt}} vergrößern", + "imageLabel": "Markdown-Bild", + "loadFailed": "Bild konnte nicht geladen werden" + }, + "pdfPreview": { + "open": "PDF öffnen", + "openFailed": "Diese PDF-Datei konnte nicht geöffnet werden", + "opening": "PDF wird geöffnet…", + "updateRequired": "Orca Mobile aktualisieren, um PDFs zu öffnen" + } + }, "home": { "accountUsageCards": { "sectionHeading": "Kontonutzung", @@ -1103,10 +1598,10 @@ "noComments": "Noch keine Kommentare.", "noDescription": "Keine Beschreibung angegeben.", "noHumanComments": "Keine Kommentare von Menschen.", - "resolvedComment_one": "Erledigter Kommentar von {{author}}", - "resolvedComment_other": "Erledigter Kommentar von {{author}} ({{count}})", - "resolvedThread_one": "Erledigter Thread von {{author}}", - "resolvedThread_other": "Erledigter Thread von {{author}} ({{count}})", + "resolvedCommentCount": "Erledigter Kommentar von {{author}} ({{count}})", + "resolvedCommentSingle": "Erledigter Kommentar von {{author}}", + "resolvedThreadCount": "Erledigter Thread von {{author}} ({{count}})", + "resolvedThreadSingle": "Erledigter Thread von {{author}}", "showMore": "{{count}} weitere anzeigen", "showMoreOf": "{{count}} weitere von {{remaining}} anzeigen", "title": "Kommentare" @@ -1298,6 +1793,596 @@ "title": "Prüfer" } }, + "session": { + "accessoryKeys": { + "addCustomShortcut": "Benutzerdefiniertes Tastenkürzel hinzufügen", + "dismissKeyboard": "Tastatur ausblenden", + "dismissKeyboardHint": "Blendet die Bildschirmtastatur aus und lässt die aktuelle Terminal-Sitzung geöffnet.", + "paste": "Einfügen", + "pasteFromClipboard": "Aus der Zwischenablage einfügen", + "sendKey": "{{label}} senden", + "switchToBufferedInput": "Zur gepufferten Befehlseingabe wechseln", + "switchToDesktopMode": "In den Desktop-Modus wechseln", + "switchToLiveInput": "Zur direkten Terminaleingabe wechseln", + "switchToPhoneMode": "In den Smartphone-Modus wechseln" + }, + "activeContent": { + "createTab": "Tab erstellen", + "creatingTab": "Wird erstellt…", + "fileTitle": "Datei", + "loadingTerminal": "Terminal wird geladen", + "noTabs": "Keine Tabs in dieser Sitzung", + "retryLoadingTerminal": "Terminal erneut laden", + "terminalTakingLonger": "Das Laden des Terminals dauert länger als erwartet" + }, + "agentChildRow": { + "agent": "Agent", + "days_one": "{{count}} T.", + "days_other": "{{count}} T.", + "ended": "Beendet", + "hours_one": "{{count}} Std.", + "hours_other": "{{count}} Std.", + "minutes_one": "{{count}} Min.", + "minutes_other": "{{count}} Min.", + "noRecentUpdate": "Keine aktuelle Meldung", + "noUpdate": "Seit {{value0}} keine Meldung" + }, + "agentMessageAttribution": { + "from": "Von {{sender}}", + "fromMore": "Von {{sender}} +{{count}}", + "fromUnnamed": "Von einem Agenten", + "messageFrom": "Nachricht von {{sender}}", + "messageFromMore": "Nachricht von {{sender}} +{{count}}", + "messageFromUnnamed": "Nachricht von einem Agenten", + "unnamedSender": "einem Agenten" + }, + "aiVaultResumeLaunch": { + "createFailed": "Terminal konnte nicht erstellt werden.", + "inputLocked": "Die Terminal-Eingabe ist gesperrt.", + "sendFailed": "Befehl zum Fortsetzen konnte nicht gesendet werden." + }, + "aiVaultResumePreparation": { + "prepareFailed": "Diese ältere Codex-Sitzung konnte nicht vorbereitet werden. Erneut versuchen, die Sitzung fortzusetzen." + }, + "backgroundTask": { + "agent": "Hintergrundagent", + "command": "Befehl im Hintergrund", + "countAgents_one": "{{count}} Agent", + "countAgents_other": "{{count}} Agenten", + "countMonitors_one": "{{count}} Überwachung", + "countMonitors_other": "{{count}} Überwachungen", + "countShellCommandOne": "1 Shell-Befehl", + "countShell_one": "{{count}} Shell", + "countShell_other": "{{count}} Shells", + "countTasks_one": "{{count}} Aufgabe", + "countTasks_other": "{{count}} Aufgaben", + "countWorkflows_one": "{{count}} Arbeitsablauf", + "countWorkflows_other": "{{count}} Arbeitsabläufe", + "detailsUnavailable": "Aufgabendetails sind für diese Sitzung nicht verfügbar.", + "groupAgents": "Agenten", + "groupMonitors": "Überwachungen", + "groupShell": "Shell", + "groupTasks": "Aufgaben", + "groupWorkflows": "Arbeitsabläufe", + "headerTotalCount_one": "{{count}} Hintergrundaufgabe", + "headerTotalCount_other": "{{count}} Hintergrundaufgaben", + "monitor": "Hintergrundüberwachung", + "monitoring": "Hintergrundaufgaben werden überwacht", + "reasonBlocked": "fehlgeschlagen", + "reasonWaiting": "Genehmigung erforderlich", + "stateBlocked": "blockiert", + "stateBlockedCount": "{{value0}} blockiert", + "stateDone": "erledigt", + "stateDoneCount": "{{value0}} erledigt", + "stateIdle": "gestoppt", + "stateIdleCount": "{{value0}} gestoppt", + "stateMonitoring": "Überwachung aktiv", + "stateMonitoringCount": "{{value0}} mit aktiver Überwachung", + "stateUnverifiable": "Status nicht verfügbar", + "stateUnverifiableCount": "{{value0}} mit nicht verfügbarem Status", + "stateWaiting": "wartend", + "stateWaitingCount": "{{value0}} wartend", + "stateWorking": "in Bearbeitung", + "stateWorkingCount": "{{value0}} in Bearbeitung", + "stop": "Stoppen", + "stopAll": "Hintergrundaufgaben stoppen", + "stopTask": "{{value0}} stoppen", + "task": "Hintergrundaufgabe", + "workflow": "Arbeitsablauf im Hintergrund" + }, + "browserTabActionSheet": { + "forward": "Vorwärts", + "reload": "Neu laden", + "title": "Browser" + }, + "bulkCloseSheetActions": { + "closeOtherTabs": "Andere Tabs schließen" + }, + "bulkTabClose": { + "confirm": "Tabs schließen", + "description": "Die ausgewählten Tabs werden geschlossen. Angeheftete Tabs bleiben geöffnet.", + "titleEditors": "Alle Editor-Tabs schließen?", + "titleLeft": "Tabs links schließen?", + "titleOthers": "Andere Tabs schließen?", + "titleRight": "Tabs rechts schließen?" + }, + "commandDock": { + "commandPlaceholder": "Befehl eingeben…", + "liveInputHint": "Eingegebener Text wird direkt an das aktive Terminal gesendet", + "liveInputLabel": "Live-Eingabe im Terminal", + "sendCommand": "Befehl senden" + }, + "contentRow": { + "dismissCreationWarning": "Warnung zur Erstellung des Arbeitsbereichs ausblenden" + }, + "diffCommentLineRow": { + "addNote": "Notiz zu Zeile {{line}} hinzufügen", + "deleteNote": "Notiz zu Zeile {{line}} löschen", + "diffLine": "{{title}}: Diff-Zeile {{line}}", + "line": "Zeile {{line}}", + "placeholder": "Review-Notiz hinzufügen", + "saveNote": "Notiz speichern" + }, + "diffLineRow": { + "addNoteLabel": "Notiz zu Zeile {{line}} hinzufügen", + "deleteNoteLabel": "Notiz zu Zeile {{line}} löschen", + "diffLineLabel": "{{title}}, Diff-Zeile {{line}}", + "lineNumber": "Zeile {{line}}", + "notePlaceholder": "Review-Notiz hinzufügen", + "saveNote": "Notiz speichern" + }, + "diffReviewLoaders": { + "changesLoadFailed": "Änderungen konnten nicht geladen werden.", + "committedChangesFailed": "Committete Änderungen konnten nicht geladen werden.", + "committedChangesUnavailable": "Committete Änderungen nicht verfügbar", + "committedDiffUnavailable": "Diff der committeten Änderungen ist nicht verfügbar.", + "desktopUpdateRequired": "Orca auf dem Desktop aktualisieren, um Änderungen auf dem Mobilgerät zu prüfen.", + "diffLoadFailed": "Diff konnte nicht geladen werden.", + "invalidSourceControlResponse": "Die Antwort der Quellcodeverwaltung war ungültig.", + "reviewNotesLoadFailed": "Review-Notizen konnten nicht geladen werden." + }, + "diffReviewQueue": { + "committedOnBranch": "Auf Branch committet", + "staged": "Vorgemerkt", + "unstaged": "Nicht vorgemerkt" + }, + "diffReviewScreenModel": { + "branch": "Branch", + "staged": "Vorgemerkt", + "unstaged": "Nicht vorgemerkt" + }, + "fileReader": { + "copyReviewNotes": "Review-Notizen kopieren", + "imageLabel": "Bild: {{title}}", + "noReviewNotes": "Keine Review-Notizen", + "previewLabel": "Vorschau: {{title}}", + "reviewNotes_one": "{{count}} Review-Notiz", + "reviewNotes_other": "{{count}} Review-Notizen", + "send": "Senden", + "sendReviewNotes": "Review-Notizen an KI senden" + }, + "floatingWorkspace": { + "title": "Schwebender Arbeitsbereich" + }, + "githubPrMutations": { + "reviewThreadUpdateFailed": "Review-Thread konnte nicht aktualisiert werden.", + "titleUpdateFailed": "Titel konnte nicht aktualisiert werden." + }, + "githubPrRpc": { + "requestFailed": "Anfrage fehlgeschlagen: {{method}}" + }, + "header": { + "backToWorktrees": "Zurück zu den Worktrees", + "moreActions": "Weitere Sitzungsaktionen", + "openFileExplorer": "Datei-Explorer öffnen", + "openSourceControl": "Quellcodeverwaltung öffnen", + "reconnectToDesktop": "Erneut mit dem Desktop verbinden", + "showSidebar": "Seitenleiste anzeigen", + "terminalTitle": "Terminal" + }, + "headerMoreActionsSheet": { + "agentHistory": "Agentenverlauf", + "agentHistoryHint": "Agentensitzungen durchsuchen und fortsetzen", + "checks": "Checks", + "checksHint": "Pull-Request-Checks öffnen" + }, + "markdownReader": { + "changedOnDesktop": "Auf dem Desktop geändert", + "copyLoaded": "Geladenen Text kopieren", + "copyLoadedSource": "Geladenen Markdown-Quelltext kopieren", + "copyPath": "Pfad kopieren", + "copyPathLabel": "Pfad der Markdown-Datei kopieren", + "copySource": "Markdown-Quelltext kopieren", + "discard": "Verwerfen", + "dismissKeyboard": "Tastatur ausblenden", + "dismissKeyboardHint": "Blendet die Bildschirmtastatur aus und lässt den Markdown-Editor geöffnet.", + "readOnly": "Schreibgeschützt" + }, + "nativeChatAttachActions": { + "availableSkills": "{{count}} verfügbar", + "commands": "Befehle", + "fileTypes": "PDF, Text, Code…", + "files": "Dateien", + "photos": "Fotos", + "skills": "Skills", + "workspaceFile": "Datei im Arbeitsbereich" + }, + "nativeChatCardHeaderAction": { + "collapse": "Einklappen" + }, + "nativeChatCliFork": { + "resumeInNewCli": "In neuer CLI fortsetzen" + }, + "nativeChatFloatingControls": { + "scrollToLatest": "Zur neuesten Nachricht scrollen" + }, + "nativeChatFullContent": { + "loadMessageFailed": "Vollständige Nachricht anzeigen – Laden fehlgeschlagen, zum Wiederholen tippen", + "loadOutputFailed": "Vollständige Ausgabe anzeigen – Laden fehlgeschlagen, zum Wiederholen tippen", + "partialMessage": "Ein Teil dieser Nachricht wird angezeigt.", + "partialOutput": "{{shown}} von {{total}} werden angezeigt.", + "showFullMessage": "Vollständige Nachricht anzeigen", + "showFullOutput": "Vollständige Ausgabe anzeigen" + }, + "nativeChatRenderData": { + "defaultAgentLabel": "dem Agenten", + "emptySubtitle": "Von {{agent}} Code prüfen, Ausgaben erklären oder eine Änderung vornehmen lassen.", + "emptyTitle": "Chat mit {{agent}} starten", + "errorSubtitle": "Das Protokoll konnte nicht gelesen werden. Zurück zum Terminal wechseln, um weiterzuarbeiten.", + "errorTitle": "Unterhaltung konnte nicht geladen werden", + "notFoundSubtitle": "Eine Nachricht senden, um eine Unterhaltung zu starten, oder die Terminalansicht öffnen, um die aktuelle Aktivität von {{agent}} zu sehen.", + "notFoundTitle": "Unterhaltung noch nicht gefunden" + }, + "nativeChatToggleAction": { + "switchToChatView": "Zur Chatansicht wechseln", + "switchToTerminalView": "Zur Terminalansicht wechseln" + }, + "newTabAgentHostLaunch": { + "notesNotSent": "Der Agent wurde gestartet, aber die Notizen wurden nicht gesendet.", + "notesSent": "Notizen gesendet", + "notesUnconfirmed": "Der Agent wurde gestartet, aber der Versand der Notizen konnte nicht bestätigt werden.", + "promptUnconfirmed": "Der Agent wurde gestartet, aber der Versand des Prompts konnte nicht bestätigt werden." + }, + "prActionsEngine": { + "mergeFailed": "Pull Request konnte nicht gemergt werden.", + "refreshFailed": "Pull Request konnte nicht aktualisiert werden." + }, + "prAiTriageLaunch": { + "agentStarted": "Agent gestartet", + "agentStartedIn": "Agent in {{label}} gestartet", + "emptyPrompt": "Der gespeicherte Prompt dieser Aktion ist leer. Die KI-Einstellungen der Quellcodeverwaltung auf dem Computer aktualisieren.", + "loadFailed": "Die verfügbaren Agenten konnten nicht geladen werden.", + "promptNotSent": "Der Agent wurde gestartet, aber der Prompt wurde nicht gesendet.", + "updateRequired": "Orca auf dem Computer aktualisieren, um einen Agenten vom Smartphone aus zu starten." + }, + "prSidebarState": { + "loadFailed": "Pull Request konnte nicht geladen werden" + }, + "queuedCardText": { + "showLess": "Weniger anzeigen", + "showLessHint": "Zeigt weniger von der Nachricht an", + "showMore": "Mehr anzeigen", + "showMoreHint": "Zeigt die ganze Nachricht an" + }, + "queuedMessageCardRow": { + "deleteHint": "Diese Nachricht aus der Warteschlange löschen", + "images_one": "{{count}} Bild", + "images_other": "{{count}} Bilder", + "moreActions": "Weitere Aktionen", + "send": "Senden", + "sendAgainHint": "Diese Nachricht erneut senden", + "sendHint": "Diese Nachricht senden", + "showLessHint": "Zeigt weniger von der Nachricht an", + "steer": "Steuern", + "steerHint": "Ohne Unterbrechung des Modells übermitteln" + }, + "queuedMessageComposer": { + "commandsNotAllowed": "Befehle können nicht in einer Nachricht in der Warteschlange gespeichert werden.", + "editing": "Nachricht wird bearbeitet", + "editingAttributed": "Nachricht wird bearbeitet · {{attribution}}" + }, + "quickCommandEditorForm": { + "action": "Aktion", + "addQuickCommand": "Schnellbefehl hinzufügen", + "advanced": "Erweitert", + "agent": "Agent", + "agentPrompt": "Agenten-Prompt", + "appendEnter": "Enter anhängen", + "appendEnterDescription": "Sofort absenden, statt nur Text einzufügen.", + "chooseAgent": "Agenten auswählen", + "commandText": "Befehlstext", + "globalScope": "Global", + "label": "Bezeichnung", + "labelPlaceholder": "Entwicklungsserver starten", + "olderHostPromptTooLong": "Zu lang für diesen Host. Orca auf dem Host aktualisieren, um längere Prompts zu speichern.", + "projectScope": "Projekt", + "prompt": "Prompt", + "promptHint": "Unterstützt Skills, Dateipfade und integrierte Befehle.", + "promptPlaceholder": "Agenten bitten, diesen Arbeitsbereich zu untersuchen", + "promptSizeCharacters": "{{used}} / {{max}} Zeichen", + "promptSizeKilobytes": "{{used}} KB / {{max}} KB", + "promptTooLong": "Zu lang zum Speichern.", + "scope": "Geltungsbereich", + "terminalCommand": "Terminal-Befehl" + }, + "quickCommandLaunch": { + "defaultLabel": "Schnellbefehl", + "editBeforeRunning": "Diesen Schnellbefehl vor dem Ausführen bearbeiten", + "runFailed": "„{{label}}“ konnte nicht ausgeführt werden" + }, + "quickCommandRow": { + "copyCommand": "{{label}} kopieren", + "copyFailed": "Kopieren fehlgeschlagen", + "deleteCommand": "{{label}} löschen", + "editCommand": "{{label}} bearbeiten", + "nothingToCopy": "Nichts zu kopieren", + "runCommand": "{{label}} ausführen" + }, + "quickCommandsList": { + "emptyState": "Noch keine Schnellbefehle.", + "globalGroup": "Global", + "newCommand": "Schnellbefehl erstellen", + "noMatches": "Keine passenden Schnellbefehle.", + "projectGroup": "Dieses Projekt", + "searchPlaceholder": "Schnellbefehle suchen…" + }, + "quickCommandsSheet": { + "addTitle": "Schnellbefehl hinzufügen", + "chooseAgentTitle": "Agenten auswählen", + "deleteConfirmMessage": "Dieser Schnellbefehl wird aus der Liste gespeicherter Befehle entfernt.", + "deleteConfirmTitle": "„{{label}}“ löschen?", + "editTitle": "Schnellbefehl bearbeiten", + "editorDescription": "Terminal-Befehle oder Agenten-Prompts für den Schnellzugriff speichern.", + "title": "Schnellbefehle", + "untitled": "Ohne Titel" + }, + "quickCommandsTabButton": { + "quickCommands": "Schnellbefehle" + }, + "routeHelpers": { + "connected": "Verbunden", + "connecting": "Verbindung wird hergestellt", + "disconnected": "Getrennt", + "pairingInvalid": "Kopplung ungültig", + "reconnecting": "Wird erneut verbunden", + "securing": "Verbindung wird abgesichert" + }, + "routeScreen": { + "openingWorkspace": "Arbeitsbereich wird geöffnet" + }, + "sheets": { + "browser": "Browser", + "browserUpdateRequired": "Desktop-Update für mobiles Browser-Streaming erforderlich", + "chat": "Chat", + "copyAllAndLeave": "Alles kopieren und verlassen", + "copyDraftsFailed": "Entwürfe konnten nicht kopiert werden", + "copyNotes": "Notizen kopieren", + "copyNotesFailed": "Notizen konnten nicht kopiert werden", + "copyPath": "Pfad kopieren", + "discard": "Verwerfen", + "discardAndLeave": "Verwerfen und verlassen", + "discardChanges": "Änderungen verwerfen", + "discardMessage": "Entwurf auf dem Smartphone durch die neueste Datei vom Desktop ersetzen?", + "draftsHint": "Vor dem Verlassen die Entwürfe auf dem Smartphone kopieren oder verwerfen.", + "file": "Datei", + "markdown": "Markdown", + "markdownNote": "Markdown-Notiz", + "newBrowser": "Neuen Browser öffnen", + "newTab": "Neuer Tab", + "notesCopied": "Notizen kopiert", + "notesHint": "Eine Agentensitzung für die aktuellen Notizen auswählen.", + "removeShortcutMessage": "Dieses benutzerdefinierte Tastenkürzel entfernen?", + "renameTerminal": "Terminal umbenennen", + "sendReviewNotes": "Review-Notizen senden", + "shortcut": "Tastenkürzel", + "terminal": "Terminal", + "terminalName": "Terminal-Name", + "unsavedMarkdown": "Ungespeicherte Markdown-Änderungen", + "urlPrompt": "URL eingeben oder für einen neuen Tab leer lassen." + }, + "sideRail": { + "backToWorktrees": "Zurück zu den Worktrees" + }, + "sourceControlLaunchAgent": { + "noEnabledAgent": "Auf dem Host dieses Arbeitsbereichs wurde kein aktivierter KI-Agent erkannt.", + "savedAgentUnavailable": "Der für diese Aktion gespeicherte Agent ist auf dem Host dieses Arbeitsbereichs nicht verfügbar." + }, + "structuredComposerCommand": { + "operationUnconfirmed": "Die Aktion für die Unterhaltung wurde nicht bestätigt.", + "removeAttachments": "Vor der Verwendung eines Chat-Sitzungsbefehls die Anhänge entfernen." + }, + "structuredSendDelivery": { + "messageNotSent": "Nachricht nicht gesendet" + }, + "tabStrip": { + "checkingCapabilities": "Desktop-Funktionen werden geprüft – gleich erneut versuchen", + "newTab": "Neuen Tab erstellen", + "quickCommandsUpdateRequired": "Desktop-Update für Schnellbefehle erforderlich", + "tabStatus": "{{title}}, {{status}}" + }, + "terminalActionSheetActions": { + "clearTerminal": "Terminal leeren", + "rename": "Umbenennen", + "switchToDesktop": "Zum Desktop-Modus wechseln", + "switchToPhone": "Zum Smartphone-Modus wechseln" + }, + "terminalInputActions": { + "attachFileHint": "Lange drücken, um stattdessen eine Datei anzuhängen", + "attachPhoto": "Foto anhängen", + "sendingImage": "Bild wird gesendet", + "startDictation": "Diktat starten", + "startingDictation": "Diktat wird gestartet", + "stopDictation": "Diktat beenden", + "transcribingDictation": "Diktat wird transkribiert" + }, + "terminalLiveInputStatus": { + "listening": "Hört zu", + "liveInput": "Live-Eingabe", + "preparingMic": "Mikrofon wird vorbereitet", + "processing": "Wird verarbeitet", + "releaseMicHint": "Zum Stoppen Mikrofon loslassen", + "startingMic": "Mikrofon wird gestartet", + "stopHint": "Zum Stoppen Mikrofon antippen", + "transcribing": "Transkription auf dem Desktop", + "uploadingImage": "Bild wird auf den Host hochgeladen", + "uploadingImageTitle": "Bild wird hochgeladen" + }, + "terminalRecords": { + "defaultTitle": "Terminal" + }, + "terminalTabAgent": { + "browser": "Browser", + "chat": "Chat", + "file": "Datei", + "markdown": "Markdown", + "newBrowser": "Neuer Browser", + "terminal": "Terminal" + }, + "terminalViewportResubscribe": { + "fitFailed": "Terminal konnte nicht an diesen Bildschirm angepasst werden" + }, + "useAccessorySelection": { + "copyFailed": "Kopieren fehlgeschlagen", + "selectionCleared": "Auswahl aufgehoben (aus dem Puffer gescrollt)" + }, + "useAttachmentInputLeaseGate": { + "reconnecting": "Anhängen fehlgeschlagen (erneuter Verbindungsaufbau)" + }, + "useContentCreateActions": { + "browserCommandFailed": "Browser-Befehl fehlgeschlagen", + "browserCreateFailed": "Browser konnte nicht erstellt werden", + "browserUnavailable": "Browserseite ist noch nicht verfügbar.", + "browserUpdateRequired": "Desktop-Update für Browser-Streaming auf Mobilgeräten erforderlich", + "invalidUrl": "Gültige URL eingeben", + "noteCreateFailed": "Markdown-Notiz konnte nicht erstellt werden", + "untitledNoteCreateFailed": "Markdown-Notiz ohne Titel konnte nicht erstellt werden" + }, + "useDiffComments": { + "copyNotesFailed": "Notizen konnten nicht kopiert werden", + "deleteNoteFailed": "Notiz konnte nicht gelöscht werden", + "noteAdded": "Notiz hinzugefügt", + "notesCopied": "Notizen kopiert", + "saveNoteFailed": "Notiz konnte nicht gespeichert werden", + "saveNotesFailed": "Review-Notizen konnten nicht gespeichert werden", + "waitingForDesktop": "Warten auf Desktop…" + }, + "useDiffReviewCommentActions": { + "saveFailed": "Review konnte nicht gespeichert werden", + "saveStateFailed": "Review-Status konnte nicht gespeichert werden", + "waitingForDesktop": "Warten auf den Desktop…" + }, + "useDiffReviewController": { + "loadFailed": "Review konnte nicht geladen werden", + "missingWorktree": "Worktree fehlt", + "waitingForDesktop": "Warten auf den Desktop…" + }, + "useDiffReviewDiffLoading": { + "loadFailed": "Diff konnte nicht geladen werden", + "waitingForDesktop": "Warten auf den Desktop…" + }, + "useDiffReviewGitActions": { + "actionFailed": "Aktion der Quellcodeverwaltung fehlgeschlagen", + "reviewedFilesStaged_one": "{{count}} geprüfte Datei vorgemerkt", + "reviewedFilesStaged_other": "{{count}} geprüfte Dateien vorgemerkt", + "stageResults": "{{staged}} vorgemerkt, {{failed}} fehlgeschlagen", + "waitingForDesktop": "Warten auf den Desktop…" + }, + "useDiffReviewInteractions": { + "openFailed": "Konnte nicht in der Sitzung geöffnet werden" + }, + "useDiffReviewSendActions": { + "copyFailed": "Review-Notizen konnten nicht kopiert werden", + "inputLocked": "Terminal-Eingabe ist gesperrt", + "loadSessionsFailed": "Agentensitzungen konnten nicht geladen werden", + "notesCopied": "Review-Notizen kopiert", + "notesSent": "Review-Notizen gesendet", + "notesSentWithWarning": "Review-Notizen gesendet. {{warning}}", + "promptNotSent": "Der Agent wurde gestartet, aber die Notizen wurden nicht gesendet. Zum Einfügen „Notizen kopieren“ verwenden.", + "sendFailed": "Notizen konnten nicht gesendet werden", + "startingAgent": "Agent wird gestartet…", + "waitingForDesktop": "Warten auf den Desktop…" + }, + "useImageAttachment": { + "attachFailed": "Anhängen fehlgeschlagen", + "disconnected": "Anhängen fehlgeschlagen (Verbindung getrennt)", + "imageTooLarge": "Bild zu groß zum Anhängen", + "permissionDenied": "Zugriff auf Fotos verweigert" + }, + "useMarkdownActions": { + "copyFailed": "Kopieren fehlgeschlagen", + "copyPathFailed": "Pfad konnte nicht kopiert werden", + "markdownTitle": "Markdown", + "pathCopied": "Pfad kopiert", + "saveFailed": "Speichern fehlgeschlagen", + "saved": "Gespeichert" + }, + "useNativeChatDictation": { + "insertFailed": "Diktat konnte nicht eingefügt werden", + "insertFailedCopied": "Diktat konnte nicht eingefügt werden und wurde daher in die Zwischenablage kopiert", + "inserted": "Diktat eingefügt" + }, + "useNativeChatStop": { + "stopNotSent": "Stoppbefehl nicht gesendet", + "stopUnconfirmed": "Stoppbefehl nicht bestätigt – vor erneutem Versuch Chat prüfen", + "terminalNotReady": "Stoppbefehl nicht gesendet (Terminal nicht bereit)" + }, + "usePanelRouteActions": { + "checkHostHint": "Host-Verbindung prüfen", + "copyNotesHint": "Stattdessen Notizen kopieren", + "detectingAgents": "Agenten werden erkannt", + "newSessionHint": "Neue Agentensitzung", + "noEnabledAgents": "Keine aktivierten Agenten", + "otherServerAgents": "Agenten auf einem anderen Server", + "pairServerHint": "Diesen Server direkt koppeln", + "presetsUnavailable": "Agenten-Voreinstellungen nicht verfügbar" + }, + "usePrAiTriage": { + "launchFailed": "Agent konnte nicht gestartet werden", + "waitingForDesktop": "Warten auf Desktop…" + }, + "usePrCommentActions": { + "actionFailed": "Kommentaraktion fehlgeschlagen" + }, + "usePrTitleAction": { + "disconnected": "Keine Verbindung zum Desktop.", + "updateFailed": "Titel konnte nicht aktualisiert werden." + }, + "useQuickCommands": { + "loadFailed": "Schnellbefehle konnten nicht geladen werden", + "saveFailed": "Schnellbefehl konnte nicht gespeichert werden" + }, + "useSessionTerminalSendActions": { + "inputTooLarge": "Eingabe zu groß (max. 256 KiB)" + }, + "useStartup": { + "wakeAgents": "Orca auf dem Host öffnen, um schlafende Agenten zu wecken." + }, + "useTerminalCreateActions": { + "createFailed": "Terminal konnte nicht erstellt werden", + "forkBusy": "Ein anderes Terminal wird geöffnet. Warten und es dann mit dem {{agentName}}-Chat erneut versuchen.", + "forkUnavailable": "Den {{agentName}}-Chat auf einem verbundenen, aktualisierten Host öffnen und erneut versuchen.", + "inputLocked": "Die Terminaleingabe ist durch einen anderen Client gesperrt.", + "notesSent": "Notizen gesendet", + "sendNotesError": "Notizen konnten nicht gesendet werden", + "sendNotesFailed": "Notizen konnten nicht gesendet werden", + "terminalTitle": "Terminal" + }, + "useTerminalInput": { + "clearFailed": "Terminal konnte nicht geleert werden", + "cleared": "Terminal geleert" + }, + "useTerminalPaste": { + "clipboardReadFailed": "Zwischenablage konnte nicht gelesen werden", + "disconnected": "Einfügen fehlgeschlagen (Verbindung getrennt)", + "imageTooLarge": "Bild zu groß zum Einfügen", + "pasteFailed": "Einfügen fehlgeschlagen", + "pasteTooLarge": "Einzufügender Inhalt zu groß (max. 256 KiB)", + "terminalChanged": "Einfügen abgebrochen (Terminal gewechselt)" + }, + "worktreeLabel": { + "fallback": "Worktree" + } + }, "settings": { "aboutScreen": { "openLinkFailed": "Der Link konnte nicht geöffnet werden. Erneut versuchen.", @@ -1438,6 +2523,186 @@ "updateFailed": "Aktualisierung fehlgeschlagen" } }, + "sourceControl": { + "actions": { + "commits": "Commits", + "createPr": "PR erstellen", + "pushAndCreatePr": "Pushen & PR erstellen", + "switchBranch": "Branch wechseln" + }, + "commitDropdown": { + "checkOutBranchToPush": "Vor dem Pushen von Commits einen Branch auschecken", + "checkOutBranchToSync": "Vor dem Synchronisieren von Commits einen Branch auschecken", + "checkingBranchStatus": "Branch-Status wird geprüft…", + "checkingPrStatus": "PR-Status wird geprüft…", + "commit": "Committen", + "commitAndForcePush": "Vorgemerkte Änderungen committen und Push mit Lease erzwingen", + "commitAndPush": "Vorgemerkte Änderungen committen und pushen", + "commitAndTryPush": "Vorgemerkte Änderungen committen und Push versuchen", + "commitForcePush": "Committen & Push erzwingen", + "commitPush": "Committen & pushen", + "commitStagedChanges": "Vorgemerkte Änderungen committen", + "commitSync": "Committen & synchronisieren", + "commitThenPullPush": "Committen, dann pullen und pushen", + "enterMessage": "Zum Committen eine Commit-Nachricht eingeben", + "linkedReviewTargetUnavailable": "Ziel des verknüpften Review-Branches nicht verfügbar", + "prAlreadyMerged": "PR wurde bereits gemergt", + "publishBranchFirstToPush": "Zum Pushen von Commits zuerst den Branch veröffentlichen", + "publishBranchFirstToSync": "Zum Synchronisieren von Commits zuerst den Branch veröffentlichen", + "resolveConflicts": "Vor dem Committen Konflikte lösen", + "stageFile": "Zum Committen mindestens eine Datei vormerken", + "useCommitAndForcePush": "„Committen & Push erzwingen“ verwenden — das Remote enthält nur ältere Kopien lokaler Commits" + }, + "createPrAction": { + "createReview": "{{titleLabel}} erstellen" + }, + "dropdownCounts": { + "fastForwardCount_one": "{{count}} Commit per Fast-Forward übernehmen", + "fastForwardCount_other": "{{count}} Commits per Fast-Forward übernehmen", + "pullCount_one": "{{count}} Commit pullen", + "pullCount_other": "{{count}} Commits pullen", + "pushCount_one": "{{count}} Commit pushen", + "pushCount_other": "{{count}} Commits pushen", + "remoteBranchFallback": "den entfernten Branch", + "syncCounts": "{{behind}} pullen, {{ahead}} pushen", + "titleForcePushOlderCopiesBranch": "Das Remote enthält nur ältere Kopien lokaler Commits. Push dieses Branches mit Lease erzwingen, um {{upstreamName}} zu aktualisieren.", + "titleForcePushOlderCopiesCount_one": "Das Remote enthält nur ältere Kopien lokaler Commits. Push von {{count}} Branch-Commit mit Lease erzwingen, um {{upstreamName}} zu aktualisieren.", + "titleForcePushOlderCopiesCount_other": "Das Remote enthält nur ältere Kopien lokaler Commits. Push von {{count}} Branch-Commits mit Lease erzwingen, um {{upstreamName}} zu aktualisieren.", + "titleManualForcePushReplaceRemote_one": "Push von {{count}} lokalem Commit mit Lease erzwingen, um {{upstreamName}} zu aktualisieren und nur auf dem Remote vorhandene Commits zu ersetzen.", + "titleManualForcePushReplaceRemote_other": "Push von {{count}} lokalen Commits mit Lease erzwingen, um {{upstreamName}} zu aktualisieren und nur auf dem Remote vorhandene Commits zu ersetzen.", + "titleManualForcePush_one": "Push von {{count}} lokalem Commit mit Lease erzwingen, um {{upstreamName}} zu aktualisieren.", + "titleManualForcePush_other": "Push von {{count}} lokalen Commits mit Lease erzwingen, um {{upstreamName}} zu aktualisieren.", + "titleUnpublishedForcePushBranch": "Push dieses Branches mit Lease erzwingen und bei Bedarf einen Upstream festlegen.", + "titleUnpublishedForcePushCount_one": "Push von {{count}} Branch-Commit mit Lease erzwingen und bei Bedarf einen Upstream festlegen.", + "titleUnpublishedForcePushCount_other": "Push von {{count}} Branch-Commits mit Lease erzwingen und bei Bedarf einen Upstream festlegen." + }, + "hostedReview": { + "mrCopy": { + "reviewLabel": "Merge Request", + "shortLabel": "MR", + "titleLabel": "Merge Request" + }, + "prCopy": { + "reviewLabel": "Pull Request", + "shortLabel": "PR", + "titleLabel": "Pull Request" + } + }, + "hostedReviewCreate": { + "pushSucceeded": "Push erfolgreich, aber der {{shortLabel}} konnte nicht erstellt werden: {{error}}" + }, + "prCreate": { + "authRequired": "Vor dem Erstellen eines {{reviewLabel}}s ist eine Authentifizierung erforderlich.", + "baseNotOnRemote": "Den Basis-Branch pushen, bevor ein {{reviewLabel}} erstellt wird.", + "createdNotRefreshed": "{{titleLabel}} erstellt, aber Orca konnte ihn noch nicht aktualisieren.", + "defaultBranch": "Vor dem Erstellen eines {{reviewLabel}}s zu einem Feature-Branch wechseln.", + "detachedHead": "Vor dem Erstellen eines {{reviewLabel}}s einen Branch auschecken.", + "dirty": "Vor dem Erstellen eines {{reviewLabel}}s Änderungen committen.", + "existing": "{{titleLabel}} ist bereits offen.", + "existingNumber": "{{titleLabel}} #{{number}} ist bereits offen.", + "existingReview": "Für diesen Branch gibt es bereits einen {{reviewLabel}}.", + "forkHeadUnsupported": "Das Erstellen eines {{reviewLabel}}s aus diesem Fork wird nicht unterstützt.", + "lookupUnconfirmed": "Orca konnte nicht bestätigen, ob für diesen Branch bereits ein {{reviewLabel}} existiert. In Kürze erneut versuchen.", + "needsSync": "Vor dem Erstellen eines {{reviewLabel}}s diesen Branch synchronisieren.", + "noUpstream": "Vor dem Erstellen eines {{reviewLabel}}s Commits veröffentlichen.", + "notReady": "Dieser Branch ist noch nicht für einen {{reviewLabel}} bereit.", + "unsupportedMergeRequests": "Das Erstellen von Merge Requests wird für dieses Repo nicht unterstützt.", + "unsupportedPullRequests": "Das Erstellen von Pull Requests wird für dieses Repo nicht unterstützt." + }, + "primaryAction": { + "forcePushFromDesktop": "Push für den Branch dieses Pull Requests vom Desktop aus erzwingen" + }, + "remoteDropdown": { + "branchAlreadyPublished": "Branch ist bereits veröffentlicht", + "branchUpToDate": "Branch ist aktuell", + "checkOutBranchToFastForward": "Vor der Aktualisierung per Fast-Forward einen Branch auschecken", + "checkOutBranchToForcePush": "Vor einem erzwungenen Push von Commits einen Branch auschecken", + "checkOutBranchToPublish": "Vor dem Veröffentlichen von Commits einen Branch auschecken", + "checkOutBranchToPull": "Vor dem Pullen von Commits einen Branch auschecken", + "checkOutBranchToPush": "Vor dem Pushen von Commits einen Branch auschecken", + "checkOutBranchToSync": "Vor dem Synchronisieren von Commits einen Branch auschecken", + "checkingBranchStatus": "Branch-Status wird geprüft…", + "checkingPrStatus": "PR-Status wird geprüft…", + "fastForward": "Per Fast-Forward aktualisieren", + "fetch": "Fetchen", + "fetchTitle": "Vom Remote fetchen, ohne zu mergen", + "forcePush": "Push erzwingen", + "linkedReviewBranchExists": "Verknüpfter Review-Branch existiert bereits", + "linkedReviewTargetUnavailable": "Ziel des verknüpften Review-Branches nicht verfügbar", + "nothingNewToFastForward": "Nichts Neues per Fast-Forward zu aktualisieren – das Remote enthält nur ältere Kopien lokaler Commits", + "nothingNewToPull": "Nichts Neues zu pullen – das Remote enthält nur ältere Kopien lokaler Commits", + "nothingToFastForward": "Nichts per Fast-Forward zu aktualisieren", + "nothingToForcePush": "Nichts per erzwungenem Push zu senden", + "nothingToForcePushTo": "Nichts per erzwungenem Push an {{upstreamName}} zu senden", + "nothingToPull": "Nichts zu pullen", + "nothingToPush": "Nichts zu pushen", + "nothingToPushTo": "Nichts zu {{upstreamName}} zu pushen", + "prAlreadyMerged": "PR wurde bereits gemergt", + "publishBranch": "Branch veröffentlichen", + "publishBranchFirstToFastForward": "Zur Aktualisierung per Fast-Forward zuerst den Branch veröffentlichen", + "publishBranchFirstToPull": "Zum Pullen von Commits zuerst den Branch veröffentlichen", + "publishBranchFirstToSync": "Zum Synchronisieren von Commits zuerst den Branch veröffentlichen", + "publishLinkedReview": "Verknüpftes Review", + "publishNoBranch": "Kein Branch", + "publishPrStatus": "PR-Status", + "publishToOrigin": "Diesen Branch auf origin veröffentlichen", + "pull": "Pullen", + "push": "Pushen", + "pushAndSetUpstream": "Diesen Branch pushen und bei Bedarf einen Upstream festlegen", + "pushMayNeedSync": "Lokale Commits pushen; git erfordert eventuell vorheriges Synchronisieren", + "pushToLinkedReview": "Aktualisierungen zum verknüpften Review-Branch pushen", + "rebaseChooseRemoteBase": "Entfernten Basis-Branch für den Rebase auswählen", + "rebaseDirtyHint": "Rebase versuchen; git erfordert eventuell zuvor das Committen oder Stashen lokaler Änderungen", + "rebaseFromBase": "Auf Basis-Branch rebasen", + "rebaseFromNamedBase": "Auf {{baseRef}} rebasen", + "rebaseWithLatestFrom": "Aktuellen Branch auf die neuesten Commits von {{baseRef}} rebasen", + "sync": "Synchronisieren", + "tryFastForwardPull": "Pull per Fast-Forward versuchen; git lehnt lokale Commits eventuell ab", + "tryRegularPush": "Regulären Push versuchen; git erfordert eventuell einen erzwungenen Push", + "useForcePushToSync": "„Push erzwingen“ verwenden – das Remote enthält nur ältere Kopien lokaler Commits" + } + }, + "terminal": { + "keyDefinitions": { + "accessoryArrowDown": "Pfeil nach unten", + "accessoryArrowLeft": "Pfeil nach links", + "accessoryArrowRight": "Pfeil nach rechts", + "accessoryArrowUp": "Pfeil nach oben", + "arrowDown": "Pfeil nach unten", + "arrowLeft": "Pfeil nach links", + "arrowRight": "Pfeil nach rechts", + "arrowUp": "Pfeil nach oben", + "clearLineBeforeCursor": "Bis zum Zeilenanfang löschen", + "clearScreen": "Bildschirm leeren", + "deleteWordBackward": "Vorheriges Wort löschen", + "endOfLine": "Zeilenende", + "forwardDelete": "Vorwärts löschen", + "interruptTerminal": "Terminal unterbrechen", + "reverseSearch": "Rückwärtssuche", + "sendEof": "EOF senden", + "startOfLine": "Zeilenanfang", + "suspendProcess": "Prozess anhalten" + }, + "webView": { + "documentStartFailed": "Terminal-Dokument konnte nicht gestartet werden – {{message}}", + "httpError": "HTTP-Fehler der Terminal-WebView", + "loadFailed": "Laden der Terminal-WebView fehlgeschlagen", + "renderProcessEnded": "Renderprozess der Terminal-WebView beendet" + }, + "webviewEngineErrorState": { + "code": "Code {{code}}", + "reload": "Neu laden", + "rendererCrashed": "Renderer abgestürzt", + "status": "Status {{statusCode}}", + "title": "Terminal konnte nicht geladen werden" + }, + "webviewNotificationDispatch": { + "unknownError": "Unbekannter Terminal-Fehler" + }, + "webviewReadyWatchdog": { + "initializeFailed": "Terminal konnte nicht initialisiert werden – kein Bereitschaftssignal von der Terminal-Ansicht" + } + }, "webShell": { "shellStateScreen": { "checkingHost": "Host wird geprüft", @@ -1452,6 +2717,18 @@ "tryAgain": "Erneut versuchen" } }, + "workspace": { + "branchChoice": { + "automaticName": "Automatischer Name", + "existing": "Bestehender Branch", + "existingHint": "Neue Commits werden diesem Branch hinzugefügt.", + "hostUpdateRequired": "Orca auf dem Host des Arbeitsbereichs aktualisieren, um einen Branch wiederzuverwenden oder explizit einen separaten Branch zu erstellen.", + "new": "Neuer Branch", + "newHint": "Einen neuen lokalen Branch erstellen", + "newName": "Name des neuen Branches", + "title": "Branch für diesen Worktree" + } + }, "worktree": { "agentRowDisplay": { "blocked": "Blockiert", @@ -1491,16 +2768,5 @@ "inReview": "Im Review", "pinned": "Angeheftet" } - }, - "workspace": { - "branchChoice": { - "title": "Branch für diesen Worktree", - "existing": "Bestehender Branch", - "existingHint": "Neue Commits werden diesem Branch hinzugefügt.", - "new": "Neuer Branch", - "newHint": "Einen neuen lokalen Branch erstellen", - "newName": "Name des neuen Branches", - "automaticName": "Automatischer Name" - } } } diff --git a/mobile/src/i18n/locales/en.json b/mobile/src/i18n/locales/en.json index defe98f7d5f..cb3cce6ff0d 100644 --- a/mobile/src/i18n/locales/en.json +++ b/mobile/src/i18n/locales/en.json @@ -155,7 +155,9 @@ "copy": "Copy", "done": "Done", "close": "Close", - "edit": "Edit" + "edit": "Edit", + "refresh": "Refresh", + "open": "Open" }, "host": { "edit": { @@ -403,7 +405,50 @@ }, "webShellUpdateFailureCopy": { "failedJustNow": "Last update from {{hostName}} failed just now: {{reason}}{{offered}}. {{outcome}}", - "failedAgo": "Last update from {{hostName}} failed {{time}} ago: {{reason}}{{offered}}. {{outcome}}" + "failedAgo": "Last update from {{hostName}} failed {{time}} ago: {{reason}}{{offered}}. {{outcome}}", + "reasonCopy": { + "noConnection": "not connected to the host", + "connectionLost": "the connection dropped", + "hostRefused": "the host refused the read", + "replyUnreadable": "the host sent a reply this app could not read", + "chunkOversize": "a chunk was larger than the host allows", + "assetOverlong": "an asset was longer than the manifest declares", + "assetNoProgress": "an asset read made no progress", + "assetShort": "an asset ended short of its declared size", + "assetChecksumMismatch": "asset checksum mismatch", + "buildChangedMidFetch": "the host's build changed during the download", + "chunkMisrouted": "a chunk answered the wrong asset or offset", + "assetEntryChanged": "an asset no longer matched the manifest", + "rangeUndecodable": "a compressed read could not be decoded", + "fetchStopped": "the download was stopped", + "cacheWriteFailed": "saving the download on this phone failed", + "unrecognisedError": "an unrecognised error" + }, + "hostCodeCopy": { + "unavailable": "the host has no workspace bundle", + "buildChanged": "the host's build changed during the download", + "assetUnknown": "the host did not recognise an asset", + "assetChanged": "an asset changed on the host", + "offsetInvalid": "the host refused a read offset", + "readLimited": "the host limited concurrent reads" + }, + "wallCopy": { + "bundleUnavailable": "the host has no workspace bundle", + "bundleShellTooOld": "this app is too old for the saved bundle", + "hostTooOldForBundle": "the host is too old for the saved bundle", + "bundleTooOldForHost": "the saved bundle is too old for the host", + "pageRouteUnlisted": "the host's workspace does not include this screen", + "pageRouteGrantMissing": "this screen needs a newer app" + }, + "openedCached": "Fell back to the saved version.", + "openedCachedGeneration": "Fell back to the saved version (generation {{generation}}).", + "blockedWorkspace": "Blocked the workspace.", + "blockedReason": "Blocked: {{reason}}.", + "nativeRoute": "Showed the native screen.", + "routeUnavailable": "Showed that the screen is not available.", + "failed": "Showed the failure screen.", + "waiting": "Waited for the host.", + "offeredGeneration": " (generation {{generation}})" } }, "dictation": { @@ -1340,10 +1385,10 @@ "showMore": "Show {{count}} more", "addCommentPlaceholder": "Add a comment…", "comment": "Comment", - "resolvedThread_one": "Resolved thread by {{author}}", - "resolvedThread_other": "Resolved thread by {{author}} ({{count}})", - "resolvedComment_one": "Resolved comment by {{author}}", - "resolvedComment_other": "Resolved comment by {{author}} ({{count}})" + "resolvedThreadSingle": "Resolved thread by {{author}}", + "resolvedThreadCount": "Resolved thread by {{author}} ({{count}})", + "resolvedCommentSingle": "Resolved comment by {{author}}", + "resolvedCommentCount": "Resolved comment by {{author}} ({{count}})" }, "conflictingFilesSection": { "detailsUnavailable": "Conflict file details are unavailable", @@ -1503,5 +1548,1221 @@ "automaticName": "Automatic name", "hostUpdateRequired": "Update Orca on the workspace host to reuse a branch or explicitly create a separate branch." } + }, + "browser": { + "addressField": { + "urlPlaceholder": "URL" + }, + "pane": { + "invalidUrl": "Enter a valid URL." + }, + "paneView": { + "forward": "Forward", + "reload": "Reload", + "dialogTitle": "Browser Dialog", + "keyboardPlaceholder": "Type on page…", + "sendText": "Send text to browser" + }, + "pointerModifiers": { + "clickModifier": "{{key}} click modifier" + }, + "viewModeSwitch": { + "webView": "Show web website view", + "mobileView": "Show mobile website view" + }, + "useBrowserCommands": { + "sent": "Sent", + "deliveryUnconfirmed": "Delivery unconfirmed — check page before retrying", + "dialogAnswerFailed": "That answer did not reach the page." + } + }, + "files": { + "fileExplorerPanel": { + "connecting": "Connecting to desktop...", + "waiting": "Waiting for desktop...", + "loadFailed": "Unable to load files", + "loadInterrupted": "Folder load interrupted", + "closeFiles": "Close files", + "backToSession": "Back to session", + "title": "Files", + "truncatedList": "{{worktreeLabel}} - Showing first 5000", + "emptyState": "No files found" + }, + "fileMarkdownPreview": { + "viewSource": "View Markdown source", + "viewPreview": "View rendered Markdown preview", + "copyLoadedSource": "Copy loaded Markdown source", + "copySource": "Copy Markdown source" + }, + "fileMediaPreview": { + "loadFailed": "Unable to load media", + "waiting": "Waiting for desktop...", + "downloading": "Downloading {{bytes}} of {{total}}", + "loading": "Loading media..." + }, + "filePreviewBody": { + "emptyFile": "Empty file", + "imageLabel": "{{title}} image" + }, + "filePreviewEditableSource": { + "editorLabel": "{{title}} editor" + }, + "filePreviewScreen": { + "loading": "Loading preview...", + "waiting": "Waiting for desktop...", + "loadFailed": "Unable to load preview", + "title": "Preview", + "saveFailed": "Unable to save file", + "backToFiles": "Back to files", + "saveArtifact": "Save terminal artifact", + "fileTitle": "File", + "openFailed": "Unable to open file", + "chooseWorkspaceFile": "Choose a file inside this workspace.", + "discardTitle": "Discard changes?", + "discardMessage": "Unsaved edits will be lost.", + "discard": "Discard", + "stay": "Stay" + }, + "filePreviewSourceText": { + "previewLabel": "File preview", + "truncatedNote": "Preview truncated. File size: {{size}}." + }, + "markdownPreviewImage": { + "loadFailed": "Unable to load image", + "expandImage": "Expand {{alt}}", + "expandFallback": "Expand image", + "imageLabel": "Markdown image" + }, + "pdfPreview": { + "openFailed": "Unable to open this PDF", + "updateRequired": "Update Orca Mobile to open PDFs", + "opening": "Opening PDF...", + "open": "Open PDF" + }, + "fileExplorerRow": { + "loading": "Loading...", + "loadFolderFailed": "Unable to load folder", + "retryLoading": "Retry loading {{path}}", + "openFolder": "Open folder {{name}}", + "fileUnavailable": "{{name}} unavailable on mobile", + "previewFile": "Preview file {{name}}", + "unavailable": "Unavailable on mobile" + }, + "fileMutationOwnership": { + "sshOwnerChanged": "Couldn't verify the SSH connection. Reconnect the host and try again." + }, + "filePreviewResponse": { + "fileChanged": "File changed on desktop. Reload preview before saving", + "binaryUnavailable": "Binary preview unavailable", + "fileTooLarge": "File too large for mobile preview", + "reloadBeforeSaving": "Reload preview before saving", + "filesystemUnreachable": "Unable to reach the desktop filesystem", + "fileNotFound": "File not found", + "loadFailed": "Unable to load preview", + "unknownSize": "unknown size", + "bytes": "{{size}} B", + "kilobytes": "{{size}} KB", + "megabytes": "{{size}} MB" + } + }, + "session": { + "browserTabActionSheet": { + "title": "Browser", + "forward": "Forward", + "reload": "Reload" + }, + "terminalLiveInputStatus": { + "listening": "Listening", + "processing": "Processing", + "startingMic": "Starting mic", + "liveInput": "Live input", + "uploadingImageTitle": "Uploading image", + "releaseMicHint": "Release mic to stop", + "stopHint": "Tap mic to stop", + "transcribing": "Transcribing on desktop", + "preparingMic": "Preparing microphone", + "uploadingImage": "Uploading image to host" + }, + "diffCommentLineRow": { + "diffLine": "{{title}} diff line {{line}}", + "addNote": "Add note on line {{line}}", + "line": "Line {{line}}", + "deleteNote": "Delete note on line {{line}}", + "placeholder": "Add review note", + "saveNote": "Save note" + }, + "markdownReader": { + "readOnly": "Read only", + "changedOnDesktop": "Changed on desktop", + "dismissKeyboard": "Dismiss keyboard", + "dismissKeyboardHint": "Hides the software keyboard and keeps the markdown editor open.", + "discard": "Discard", + "copyLoadedSource": "Copy loaded Markdown source", + "copySource": "Copy Markdown source", + "copyLoaded": "Copy loaded", + "copyPathLabel": "Copy Markdown path", + "copyPath": "Copy path" + }, + "nativeChatCardHeaderAction": { + "collapse": "Collapse" + }, + "nativeChatFloatingControls": { + "scrollToLatest": "Scroll to latest" + }, + "nativeChatFullContent": { + "partialOutput": "Showing {{shown}} of {{total}}.", + "showFullOutput": "Show full output", + "loadOutputFailed": "Show full output — couldn't load, tap to retry", + "partialMessage": "Showing part of this message.", + "showFullMessage": "Show full message", + "loadMessageFailed": "Show full message — couldn't load, tap to retry" + }, + "queuedCardText": { + "showLessHint": "Shows less of the message", + "showMoreHint": "Shows the whole message", + "showLess": "Show less", + "showMore": "Show more" + }, + "queuedMessageCardRow": { + "images_one": "{{count}} image", + "images_other": "{{count}} images", + "sendAgainHint": "Send this message again", + "steerHint": "Submit without interrupting the model", + "sendHint": "Send this message", + "steer": "Steer", + "send": "Send", + "deleteHint": "Delete this queued message", + "moreActions": "More actions", + "showLessHint": "Shows less of the message" + }, + "queuedMessageComposer": { + "commandsNotAllowed": "Commands can't be saved in a queued message.", + "editingAttributed": "Editing message · {{attribution}}", + "editing": "Editing message" + }, + "accessoryKeys": { + "dismissKeyboard": "Dismiss keyboard", + "dismissKeyboardHint": "Hides the software keyboard and keeps the current terminal session open.", + "switchToDesktopMode": "Switch to desktop mode", + "switchToPhoneMode": "Switch to phone mode", + "switchToBufferedInput": "Switch to buffered command input", + "switchToLiveInput": "Switch to live terminal input", + "pasteFromClipboard": "Paste from clipboard", + "paste": "Paste", + "sendKey": "Send {{label}}", + "addCustomShortcut": "Add custom shortcut" + }, + "activeContent": { + "noTabs": "No tabs in this session", + "creatingTab": "Creating...", + "createTab": "Create Tab", + "fileTitle": "File", + "terminalTakingLonger": "Terminal is taking longer than expected", + "loadingTerminal": "Loading terminal", + "retryLoadingTerminal": "Retry loading terminal" + }, + "commandDock": { + "liveInputLabel": "Live terminal input", + "liveInputHint": "Typed text is sent directly to the active terminal", + "commandPlaceholder": "Type a command…", + "sendCommand": "Send command" + }, + "contentRow": { + "dismissCreationWarning": "Dismiss workspace creation warning" + }, + "diffLineRow": { + "diffLineLabel": "{{title}} diff line {{line}}", + "addNoteLabel": "Add note on line {{line}}", + "lineNumber": "Line {{line}}", + "deleteNoteLabel": "Delete note on line {{line}}", + "notePlaceholder": "Add review note", + "saveNote": "Save note" + }, + "fileReader": { + "noReviewNotes": "No review notes", + "reviewNotes_one": "{{count}} review note", + "reviewNotes_other": "{{count}} review notes", + "copyReviewNotes": "Copy review notes", + "sendReviewNotes": "Send review notes to AI", + "send": "Send", + "imageLabel": "{{title}} image", + "previewLabel": "{{title}} preview" + }, + "header": { + "showSidebar": "Show sidebar", + "backToWorktrees": "Back to worktrees", + "terminalTitle": "Terminal", + "reconnectToDesktop": "Reconnect to desktop", + "openFileExplorer": "Open file explorer", + "openSourceControl": "Open source control", + "moreActions": "More session actions" + }, + "headerMoreActionsSheet": { + "agentHistory": "Agent History", + "agentHistoryHint": "Browse and resume agent sessions", + "checks": "Checks", + "checksHint": "Open pull request checks" + }, + "routeScreen": { + "openingWorkspace": "Opening workspace" + }, + "sheets": { + "newTab": "New Tab", + "terminal": "Terminal", + "browser": "Browser", + "browserUpdateRequired": "Desktop update required for mobile browser streaming", + "markdownNote": "Markdown Note", + "sendReviewNotes": "Send Review Notes", + "notesHint": "Choose an agent session for the current notes.", + "copyNotes": "Copy Notes", + "notesCopied": "Notes copied", + "copyNotesFailed": "Couldn't copy notes", + "markdown": "Markdown", + "copyPath": "Copy Path", + "file": "File", + "chat": "Chat", + "unsavedMarkdown": "Unsaved markdown changes", + "draftsHint": "Copy or discard phone drafts before leaving.", + "copyAllAndLeave": "Copy All & Leave", + "copyDraftsFailed": "Couldn't copy drafts", + "discardAndLeave": "Discard & Leave", + "discardChanges": "Discard Changes", + "discardMessage": "Replace the phone draft with the latest desktop file?", + "discard": "Discard", + "newBrowser": "New Browser", + "urlPrompt": "Enter a URL, or leave blank for a new tab.", + "shortcut": "Shortcut", + "removeShortcutMessage": "Remove this custom shortcut?", + "renameTerminal": "Rename Terminal", + "terminalName": "Terminal name" + }, + "tabStrip": { + "tabStatus": "{{title}}, {{status}}", + "newTab": "New tab", + "quickCommandsUpdateRequired": "Desktop update required for quick commands", + "checkingCapabilities": "Checking desktop capabilities — try again in a moment" + }, + "terminalInputActions": { + "sendingImage": "Sending image", + "attachPhoto": "Attach a photo", + "attachFileHint": "Long press to attach a file instead", + "stopDictation": "Stop voice dictation", + "transcribingDictation": "Transcribing voice dictation", + "startingDictation": "Starting voice dictation", + "startDictation": "Start voice dictation" + }, + "quickCommandEditorForm": { + "terminalCommand": "Terminal Command", + "agentPrompt": "Agent Prompt", + "label": "Label", + "labelPlaceholder": "Start dev server", + "action": "Action", + "agent": "Agent", + "chooseAgent": "Choose agent", + "prompt": "Prompt", + "commandText": "Command Text", + "promptPlaceholder": "Ask the agent to investigate this workspace", + "olderHostPromptTooLong": "Too long for this host. Update Orca on the host to save longer prompts.", + "promptTooLong": "Too long to save.", + "promptSizeKilobytes": "{{used}} KB / {{max}} KB", + "promptSizeCharacters": "{{used}} / {{max}} characters", + "promptHint": "Supports skills, file paths, and built-in commands.", + "advanced": "Advanced", + "appendEnter": "Append Enter", + "appendEnterDescription": "Submit immediately instead of only inserting text.", + "scope": "Scope", + "globalScope": "Global", + "projectScope": "Project", + "addQuickCommand": "Add Quick Command" + }, + "quickCommandRow": { + "copyFailed": "Couldn't copy", + "copyCommand": "Copy {{label}}", + "nothingToCopy": "Nothing to copy", + "runCommand": "Run {{label}}", + "editCommand": "Edit {{label}}", + "deleteCommand": "Delete {{label}}" + }, + "quickCommandsList": { + "searchPlaceholder": "Search quick commands...", + "emptyState": "No quick commands yet.", + "noMatches": "No matching quick commands.", + "projectGroup": "This project", + "globalGroup": "Global", + "newCommand": "New quick command" + }, + "quickCommandsSheet": { + "deleteConfirmTitle": "Delete \"{{label}}\"?", + "untitled": "Untitled", + "deleteConfirmMessage": "This quick command will be removed from your saved list.", + "editTitle": "Edit Quick Command", + "addTitle": "Add Quick Command", + "chooseAgentTitle": "Choose Agent", + "title": "Quick Commands", + "editorDescription": "Save terminal commands or agent prompts for quick access." + }, + "quickCommandsTabButton": { + "quickCommands": "Quick commands" + }, + "aiVaultResumeLaunch": { + "createFailed": "Failed to create terminal", + "sendFailed": "Failed to send resume command", + "inputLocked": "Terminal input is locked" + }, + "aiVaultResumePreparation": { + "prepareFailed": "Could not prepare this legacy Codex session. Retry resume." + }, + "floatingWorkspace": { + "title": "Floating Workspace" + }, + "githubPrMutations": { + "titleUpdateFailed": "Failed to update title.", + "reviewThreadUpdateFailed": "Failed to update review thread." + }, + "githubPrRpc": { + "requestFailed": "Request failed: {{method}}" + }, + "agentChildRow": { + "noRecentUpdate": "No recent update", + "ended": "Ended", + "minutes_one": "{{count}}m", + "minutes_other": "{{count}}m", + "hours_one": "{{count}}h", + "hours_other": "{{count}}h", + "days_one": "{{count}}d", + "days_other": "{{count}}d", + "noUpdate": "No update in {{value0}}", + "agent": "Agent" + }, + "agentMessageAttribution": { + "messageFromUnnamed": "Message from an agent", + "fromUnnamed": "From an agent", + "unnamedSender": "an agent", + "messageFromMore": "Message from {{sender}} +{{count}}", + "fromMore": "From {{sender}} +{{count}}", + "messageFrom": "Message from {{sender}}", + "from": "From {{sender}}" + }, + "backgroundTask": { + "monitoring": "Monitoring background tasks", + "stop": "Stop", + "stopTask": "Stop {{value0}}", + "stopAll": "Stop background tasks", + "agent": "Background agent", + "workflow": "Background workflow", + "command": "Background command", + "monitor": "Background monitor", + "task": "Background task", + "detailsUnavailable": "Task details are unavailable for this session.", + "countAgents_one": "{{count}} agent", + "countAgents_other": "{{count}} agents", + "countShell_one": "{{count}} shell", + "countShell_other": "{{count}} shells", + "countShellCommandOne": "1 shell command", + "countMonitors_one": "{{count}} monitor", + "countMonitors_other": "{{count}} monitors", + "countWorkflows_one": "{{count}} workflow", + "countWorkflows_other": "{{count}} workflows", + "countTasks_one": "{{count}} task", + "countTasks_other": "{{count}} tasks", + "headerTotalCount_one": "{{count}} background task", + "headerTotalCount_other": "{{count}} background tasks", + "stateWorking": "working", + "stateWorkingCount": "{{value0}} working", + "stateMonitoring": "monitoring", + "stateMonitoringCount": "{{value0}} monitoring", + "stateWaiting": "waiting", + "stateWaitingCount": "{{value0}} waiting", + "stateBlocked": "blocked", + "stateBlockedCount": "{{value0}} blocked", + "stateDone": "done", + "stateDoneCount": "{{value0}} done", + "stateIdle": "stopped", + "stateIdleCount": "{{value0}} stopped", + "stateUnverifiable": "status unavailable", + "stateUnverifiableCount": "{{value0}} with status unavailable", + "reasonWaiting": "needs approval", + "reasonBlocked": "failed", + "groupAgents": "Agents", + "groupShell": "Shell", + "groupMonitors": "Monitors", + "groupWorkflows": "Workflows", + "groupTasks": "Tasks" + }, + "bulkCloseSheetActions": { + "closeOtherTabs": "Close Other Tabs" + }, + "bulkTabClose": { + "titleOthers": "Close other tabs?", + "titleRight": "Close tabs to the right?", + "titleLeft": "Close tabs to the left?", + "titleEditors": "Close all editor tabs?", + "description": "The selected tabs will be closed. Pinned tabs will stay open.", + "confirm": "Close tabs" + }, + "diffReviewLoaders": { + "committedChangesUnavailable": "Committed changes unavailable", + "committedChangesFailed": "Committed changes failed", + "desktopUpdateRequired": "Update Orca desktop to review changes on mobile.", + "changesLoadFailed": "Unable to load changes", + "invalidSourceControlResponse": "Source control response was invalid", + "reviewNotesLoadFailed": "Unable to load review notes", + "diffLoadFailed": "Unable to load diff", + "committedDiffUnavailable": "Committed diff is unavailable" + }, + "diffReviewQueue": { + "staged": "Staged", + "unstaged": "Unstaged", + "committedOnBranch": "Committed on branch" + }, + "diffReviewScreenModel": { + "branch": "Branch", + "staged": "Staged", + "unstaged": "Unstaged" + }, + "nativeChatAttachActions": { + "photos": "Photos", + "files": "Files", + "fileTypes": "PDF, text, code…", + "workspaceFile": "Workspace file", + "skills": "Skills", + "availableSkills": "{{count}} available", + "commands": "Commands" + }, + "nativeChatCliFork": { + "resumeInNewCli": "Resume in New CLI" + }, + "nativeChatRenderData": { + "notFoundTitle": "Can't find this conversation yet", + "notFoundSubtitle": "Send a message to start one, or open the terminal view to see what {{agent}} is doing.", + "emptyTitle": "Start a chat with {{agent}}", + "emptySubtitle": "Ask {{agent}} to inspect code, explain output, or make a change.", + "errorTitle": "Could not load conversation", + "errorSubtitle": "The transcript could not be read. Toggle back to the terminal to keep working.", + "defaultAgentLabel": "the agent" + }, + "nativeChatToggleAction": { + "switchToTerminalView": "Switch to terminal view", + "switchToChatView": "Switch to chat view" + }, + "prSidebarState": { + "loadFailed": "Unable to load pull request" + }, + "useNativeChatDictation": { + "insertFailedCopied": "Couldn't insert dictation, so it was copied to the clipboard", + "insertFailed": "Couldn't insert dictation", + "inserted": "Dictation inserted" + }, + "quickCommandLaunch": { + "editBeforeRunning": "Edit this quick command before running it", + "defaultLabel": "Quick command", + "runFailed": "Couldn't run {{label}}" + }, + "routeHelpers": { + "connecting": "Connecting", + "securing": "Securing", + "connected": "Connected", + "disconnected": "Disconnected", + "reconnecting": "Reconnecting", + "pairingInvalid": "Pairing invalid" + }, + "useTerminalCreateActions": { + "forkUnavailable": "Open the {{agentName}} chat on a connected, updated host and try again.", + "forkBusy": "Another terminal is opening. Wait, then retry the {{agentName}} chat.", + "createFailed": "Failed to create terminal", + "terminalTitle": "Terminal", + "sendNotesFailed": "Failed to send notes", + "inputLocked": "Terminal input is locked by another client.", + "notesSent": "Notes sent", + "sendNotesError": "Couldn't send notes" + }, + "sourceControlLaunchAgent": { + "savedAgentUnavailable": "The saved agent for this action is not available on this workspace host.", + "noEnabledAgent": "No enabled AI agent was detected on this workspace host." + }, + "structuredComposerCommand": { + "removeAttachments": "Remove attachments before using a chat-session command.", + "operationUnconfirmed": "Conversation operation was not confirmed." + }, + "structuredSendDelivery": { + "messageNotSent": "Message not sent" + }, + "terminalActionSheetActions": { + "switchToDesktop": "Switch to Desktop", + "switchToPhone": "Switch to Phone", + "rename": "Rename", + "clearTerminal": "Clear Terminal" + }, + "terminalRecords": { + "defaultTitle": "Terminal" + }, + "terminalTabAgent": { + "newBrowser": "New Browser", + "browser": "Browser", + "markdown": "Markdown", + "file": "File", + "chat": "Chat", + "terminal": "Terminal" + }, + "terminalViewportResubscribe": { + "fitFailed": "Couldn't fit the terminal to this screen" + }, + "newTabAgentHostLaunch": { + "notesUnconfirmed": "The agent started, but couldn't confirm the notes were sent.", + "promptUnconfirmed": "The agent started, but couldn't confirm the prompt was sent.", + "notesNotSent": "The agent started, but the notes weren't sent.", + "notesSent": "Notes sent" + }, + "prActionsEngine": { + "refreshFailed": "Failed to refresh pull request.", + "mergeFailed": "Failed to merge pull request." + }, + "prAiTriageLaunch": { + "promptNotSent": "The agent started, but the prompt wasn't sent.", + "updateRequired": "Update Orca on your computer to start an agent from your phone.", + "loadFailed": "Could not load the available agents.", + "emptyPrompt": "This action's saved prompt is empty. Update Source Control AI settings on your computer.", + "agentStartedIn": "Agent started in {{label}}", + "agentStarted": "Agent started" + }, + "sideRail": { + "backToWorktrees": "Back to worktrees" + }, + "useAttachmentInputLeaseGate": { + "reconnecting": "Attach failed (reconnecting)" + }, + "useDiffReviewCommentActions": { + "waitingForDesktop": "Waiting for desktop...", + "saveStateFailed": "Failed to save review state", + "saveFailed": "Failed to save review" + }, + "useDiffReviewController": { + "missingWorktree": "Missing worktree", + "waitingForDesktop": "Waiting for desktop...", + "loadFailed": "Unable to load review" + }, + "useDiffReviewDiffLoading": { + "waitingForDesktop": "Waiting for desktop...", + "loadFailed": "Unable to load diff" + }, + "useDiffReviewGitActions": { + "waitingForDesktop": "Waiting for desktop...", + "actionFailed": "Source control action failed", + "stageResults": "{{staged}} staged, {{failed}} failed", + "reviewedFilesStaged_one": "{{count}} reviewed file staged", + "reviewedFilesStaged_other": "{{count}} reviewed files staged" + }, + "useDiffReviewInteractions": { + "openFailed": "Unable to open in session" + }, + "useDiffReviewSendActions": { + "copyFailed": "Unable to copy the review notes", + "notesCopied": "Review notes copied", + "waitingForDesktop": "Waiting for desktop...", + "sendFailed": "Failed to send notes", + "inputLocked": "Terminal input is locked", + "notesSent": "Review notes sent", + "startingAgent": "Starting an agent...", + "promptNotSent": "The agent started, but the notes weren't sent. Use Copy Notes to paste them.", + "notesSentWithWarning": "Review notes sent. {{warning}}", + "loadSessionsFailed": "Unable to load agent sessions" + }, + "useImageAttachment": { + "disconnected": "Attach failed (disconnected)", + "permissionDenied": "Photo permission denied", + "imageTooLarge": "Image too large to attach", + "attachFailed": "Attach failed" + }, + "useNativeChatStop": { + "terminalNotReady": "Stop not sent (terminal not ready)", + "stopUnconfirmed": "Stop unconfirmed — check chat before retrying", + "stopNotSent": "Stop not sent" + }, + "usePrAiTriage": { + "waitingForDesktop": "Waiting for desktop…", + "launchFailed": "Failed to launch agent" + }, + "usePrCommentActions": { + "actionFailed": "Comment action failed" + }, + "usePrTitleAction": { + "disconnected": "Not connected to desktop.", + "updateFailed": "Failed to update title." + }, + "useAccessorySelection": { + "copyFailed": "Couldn't copy", + "selectionCleared": "Selection cleared (scrolled out of buffer)" + }, + "useContentCreateActions": { + "noteCreateFailed": "Failed to create markdown note", + "untitledNoteCreateFailed": "Unable to create untitled markdown note", + "browserUpdateRequired": "Desktop update required for mobile browser streaming", + "invalidUrl": "Enter a valid URL", + "browserCreateFailed": "Failed to create browser", + "browserUnavailable": "Browser page is not available yet.", + "browserCommandFailed": "Browser command failed" + }, + "useDiffComments": { + "waitingForDesktop": "Waiting for desktop...", + "saveNotesFailed": "Failed to save review notes", + "noteAdded": "Note added", + "saveNoteFailed": "Failed to save note", + "deleteNoteFailed": "Failed to delete note", + "notesCopied": "Notes copied", + "copyNotesFailed": "Couldn't copy notes" + }, + "useMarkdownActions": { + "copyFailed": "Couldn't copy", + "pathCopied": "Path copied", + "copyPathFailed": "Couldn't copy path", + "markdownTitle": "Markdown", + "saved": "Saved", + "saveFailed": "Save failed" + }, + "usePanelRouteActions": { + "otherServerAgents": "Agents on Another Server", + "pairServerHint": "Pair that server directly", + "detectingAgents": "Detecting Agents", + "noEnabledAgents": "No Enabled Agents", + "presetsUnavailable": "Agent Presets Unavailable", + "checkHostHint": "Check the host connection", + "newSessionHint": "New agent session", + "copyNotesHint": "Copy notes instead" + }, + "useStartup": { + "wakeAgents": "Open Orca on the host to wake sleeping agents." + }, + "useTerminalInput": { + "clearFailed": "Couldn't clear terminal", + "cleared": "Terminal cleared" + }, + "useSessionTerminalSendActions": { + "inputTooLarge": "Input too large (max 256 KiB)" + }, + "useTerminalPaste": { + "clipboardReadFailed": "Couldn't read the clipboard", + "pasteTooLarge": "Paste too large (max 256 KiB)", + "disconnected": "Paste failed (disconnected)", + "terminalChanged": "Paste canceled (terminal changed)", + "pasteFailed": "Paste failed", + "imageTooLarge": "Image too large to paste" + }, + "useQuickCommands": { + "loadFailed": "Failed to load quick commands", + "saveFailed": "Failed to save quick command" + }, + "worktreeLabel": { + "fallback": "Worktree" + } + }, + "chat": { + "composer": { + "addToMessage": "Add to message", + "attachImage": "Attach image", + "placeholder": "Ask anything, @ to mention files, / for commands", + "cancelEditing": "Cancel editing", + "saveEditedMessage": "Save edited message", + "sendMessage": "Send message" + }, + "composerAttachments": { + "file": "File", + "fileMeta": "{{type}} · {{size}}", + "attachedImage": "Attached image", + "removeImage": "Remove image", + "uploadingFile": "Uploading {{name}}", + "removeFile": "Remove {{name}}" + }, + "useImageUpload": { + "disconnected": "Attach failed (disconnected)", + "permissionDenied": "Photo permission denied", + "imageTooLarge": "Image too large to attach", + "attachFailed": "Attach failed" + }, + "ask": { + "submit": "Submit", + "next": "Next", + "skip": "Skip", + "step": "Step {{step}}", + "other": "Other…", + "answerPlaceholder": "Type your answer" + }, + "permission": { + "reason": "Reason: ", + "needsAccessTo": "Needs access to: ", + "planFile": "Plan file: {{filePath}}", + "newerVersionRequired": "This request needs a newer version of Orca." + }, + "promptCard": { + "terminalExplanation": "Answer this OMP question in the terminal.", + "answerInTerminal": "Answer in terminal" + }, + "promptStrip": { + "expand": "Expand" + }, + "question": { + "submitSelectedOptions": "Submit selected options", + "submitCount": "Submit ({{count}})", + "submit": "Submit", + "otherReplyPlaceholder": "Or type a reply…", + "replyPlaceholder": "Type your reply…", + "sendReply": "Send reply" + }, + "queuedMessages": { + "deleteAll": "Delete every queued message", + "clearAll": "Clear all", + "resumeSending": "Resume sending the queued messages", + "resume": "Resume", + "title": "Queued messages", + "pausedState": "Paused", + "queuedState": "Queued", + "editMessage": "Edit message" + }, + "sessionOptionPickers": { + "workingAnnouncement": "Agent is working. Model settings unlock when this turn finishes.", + "modelFullAccessLabel": "Model, {{value}}, full access", + "modelLabel": "Model, {{value}}", + "permissionsLabel": "Permissions, {{value}}", + "closePicker": "Close picker", + "backToModels": "Back to models", + "agentWorking": "Agent is working", + "settingsLocked": "Model settings unlock when this turn finishes.", + "dispatchUnconfirmed": "Sent to the agent — not confirmed", + "permissions": "Permissions" + }, + "sessionOptionRows": { + "defaultMarker": "Default", + "notReportedMarker": "Not reported" + }, + "sessionOptionSegments": { + "dispatchUnconfirmed": "Sent to the agent — not confirmed" + }, + "stopButton": { + "stoppingState": "Stopping…", + "stopAgent": "Stop the agent" + }, + "taskList": { + "taskStatus": "{{status}}: {{task}}", + "title": "Tasks", + "completed": "Completed", + "inProgress": "In progress", + "pending": "Pending", + "empty": "No tasks", + "progress": "{{completed}} of {{total}} tasks completed", + "added": "Added {{task}}", + "removed": "Removed {{task}}", + "started": "Started {{task}}", + "finished": "Completed {{task}}", + "reset": "Marked pending: {{task}}", + "updated": "Updated {{task}}", + "unchanged": "Tasks unchanged", + "showAll": "Full task list" + }, + "sessionOptionCopy": { + "on": "On", + "off": "Off", + "permissions": "Permissions", + "notSet": "Not set", + "model": "Model", + "effort": "Effort", + "fastMode": "Fast mode", + "speed": "Speed", + "thinking": "Thinking", + "standard": "Standard", + "minimal": "Minimal", + "low": "Low", + "medium": "Medium", + "high": "High", + "xhigh": "Extra high", + "max": "Max", + "ultra": "Ultra", + "minimalShort": "Min", + "mediumShort": "Med", + "xhighShort": "XHigh", + "fast": "Fast", + "toggleOption": "Toggle {{value0}}", + "chooseInAgentPicker": "Choose in agent picker…", + "setWhenSessionStarts": "Set when the session starts.", + "availableAfterSessionStarts": "Available after the session starts.", + "sessionOptions": "Session options" + }, + "streamFrame": { + "streamFailed": "Transcript stream failed", + "streamEnded": "Transcript stream ended" + }, + "turnStatus": { + "seconds_one": "{{count}}s", + "seconds_other": "{{count}}s", + "minutesSeconds": "{{minutes}}m {{seconds}}s", + "hoursMinutesSeconds": "{{hours}}h {{minutes}}m {{seconds}}s", + "thinking": "Thinking", + "working": "Working…", + "stopping": "Stopping…", + "queueAfterStop": "Queue a message to run after the stop", + "sendAfterStop": "Send a message to run after the stop", + "workingFor": "Working for {{value0}}", + "workedFor": "Worked for {{value0}}", + "interruptedAfter": "Interrupted after {{value0}}", + "failedAfter": "Failed after {{value0}}", + "toggleDetails": "Toggle turn details", + "responding": "Agent is responding" + }, + "message": { + "copyMessage": "Copy message" + }, + "messageActionsSheet": { + "copyMessage": "Copy message", + "copyFailed": "Copy failed", + "clipboardRejected": "The clipboard rejected the text.", + "selectText": "Select text" + }, + "notice": { + "stoppedManually": "Stopped manually", + "compaction": "Context compacted", + "contextCleared": "Context cleared", + "plan": "Plan" + }, + "nativeChatProse": { + "attachedImage": "Attached image", + "image": "image", + "openImageHint": "Opens the image full size" + }, + "nativeChatToolLine": { + "durationMilliseconds": "{{milliseconds}}ms", + "durationSeconds": "{{seconds}}s", + "durationMinutes": "{{minutes}}m {{seconds}}s", + "durationHours": "{{hours}}h {{minutes}}m {{seconds}}s", + "result": "Result", + "exitCode": "exit {{exitCode}}", + "copyCommand": "Copy command", + "running": "Running", + "ran": "Ran", + "reading": "Reading", + "read": "Read", + "triedRead": "Tried to read", + "editing": "Editing", + "edited": "Edited", + "triedEdit": "Tried to edit", + "added": "Added", + "deleted": "Deleted", + "renamed": "Renamed", + "subagent": "Subagent", + "searching": "Searching", + "searched": "Searched", + "triedSearch": "Tried to search", + "listing": "Listing", + "listed": "Listed", + "triedList": "Tried to list", + "fetching": "Fetching", + "fetched": "Fetched", + "triedFetch": "Tried to fetch", + "searchingWeb": "Searching the web", + "searchedWeb": "Searched the web", + "triedSearchWeb": "Tried to search the web" + }, + "nativeChatUserMessageFold": { + "showLess": "Show less", + "loadFailed": "Show full message — couldn't load, tap to retry", + "showFullMessage": "Show full message" + }, + "askRow": { + "awaiting": "Awaiting user input:", + "awaitingUnnamed": "Awaiting user input", + "asked": "Asked:", + "questionCount": "{{value0}} questions" + }, + "hostStatus": { + "historyRepaired": "Part of this chat's history couldn't be loaded.", + "historyItemTooLarge": "This part of the chat was too large to show.", + "continuedOutside": "Continued outside this chat.", + "continuedOutsideRewound": "Continued outside this chat. Earlier replies above are no longer in this conversation's history." + }, + "orcaStop": { + "update": "Orca on {{machine}} restarted for an update while this response was in progress.", + "quit": "Orca on {{machine}} was closed while this response was in progress.", + "crash": "Orca on {{machine}} stopped unexpectedly while this response was in progress.", + "remote": "Orca on {{machine}} stopped while this response was in progress.", + "canContinue": "You can continue in this conversation.", + "unnamedServer": "the remote server" + }, + "toolActivity": { + "count_one": "{{count}} tool call", + "count_other": "{{count}} tool calls", + "runningPreview": "Running {{preview}}", + "runningCommand": "Running command", + "runningNamedPreview": "Running {{toolName}} {{preview}}", + "runningNamed": "Running {{toolName}}", + "result": "Result", + "moreCalls": "+{{value0}} more", + "failedCount": "{{value0}} failed", + "failedCallsLabel": "Failed tool calls: {{value0}}", + "interruptedCount": "{{value0}} interrupted", + "interruptedCallsLabel": "Interrupted tool calls: {{value0}}" + }, + "toolRun": { + "liveRead_one": "Reading {{count}} file", + "liveRead_other": "Reading {{count}} files", + "liveSearch_one": "Searching {{count}} time", + "liveSearch_other": "Searching {{count}} times", + "liveListFiles_one": "Listing {{count}} directory", + "liveListFiles_other": "Listing {{count}} directories", + "liveUnknown_one": "Running {{count}} command", + "liveUnknown_other": "Running {{count}} commands", + "liveFileChange_one": "Editing {{count}} file", + "liveFileChange_other": "Editing {{count}} files", + "liveWebSearch_one": "Searching the web {{count}} time", + "liveWebSearch_other": "Searching the web {{count}} times", + "liveBrowserOnce": "Using the browser", + "liveBrowserTimes_other": "Using the browser {{count}} times", + "liveMcpToolCall_one": "Using {{count}} integration", + "liveMcpToolCall_other": "Using {{count}} integrations", + "liveSubAgentActivity_one": "Running {{count}} agent", + "liveSubAgentActivity_other": "Running {{count}} agents", + "liveTodoListOnce": "Updating the plan", + "liveTodoListTimes_other": "Updating the plan {{count}} times", + "liveOther_one": "Using {{count}} tool", + "liveOther_other": "Using {{count}} tools", + "settledRead_one": "Read {{count}} file", + "settledRead_other": "Read {{count}} files", + "settledSearch_one": "Searched {{count}} time", + "settledSearch_other": "Searched {{count}} times", + "settledListFiles_one": "Listed {{count}} directory", + "settledListFiles_other": "Listed {{count}} directories", + "settledUnknown_one": "Ran {{count}} command", + "settledUnknown_other": "Ran {{count}} commands", + "settledFileChange_one": "Edited {{count}} file", + "settledFileChange_other": "Edited {{count}} files", + "settledWebSearch_one": "Searched the web {{count}} time", + "settledWebSearch_other": "Searched the web {{count}} times", + "settledBrowserOnce": "Used the browser", + "settledBrowserTimes_other": "Used the browser {{count}} times", + "settledMcpToolCall_one": "Used {{count}} integration", + "settledMcpToolCall_other": "Used {{count}} integrations", + "settledSubAgentActivity_one": "Ran {{count}} agent", + "settledSubAgentActivity_other": "Ran {{count}} agents", + "settledTodoListOnce": "Updated the plan", + "settledTodoListTimes_other": "Updated the plan {{count}} times", + "settledOther_one": "Used {{count}} tool", + "settledOther_other": "Used {{count}} tools", + "pair": "{{value0}} and {{value1}}", + "list": "{{value0}}, and {{value1}}", + "separator": ", " + }, + "usageLimit": { + "theAgent": "The agent", + "windowSession": "session", + "windowWeekly": "weekly", + "windowWeeklyModel": "weekly model", + "windowCredits": "extra usage", + "reachedNamed": "{{agent}} hit its {{window}} limit.", + "reached": "{{agent}} hit its usage limit.", + "resetsAt": "It resets at {{when}}." + }, + "composerPlaceholder": { + "reconnecting": "Reconnecting…", + "waitingForTerminal": "Waiting for terminal…", + "prompt": "Ask anything, @ to mention files, / for commands" + }, + "nativeChatImageViewer": { + "closeImage": "Close image" + }, + "nativeChatVisual": { + "defaultTitle": "Visualization", + "unavailable": "Visualization unavailable", + "retryHint": "Tries to load it again", + "openFullscreen": "Open {{title}} full screen" + }, + "diffCard": { + "diffTruncated": "Diff truncated", + "diffGap": "Lines not shown", + "copyDiff": "Copy diff" + }, + "receipt": { + "emptyAnswer": "Empty answer", + "unavailable": "Selected answer unavailable", + "resolved": "Resolved", + "cancelled": "Cancelled", + "resolver": "Answered on {{device}}", + "cancelledBy": "Cancelled on {{device}}" + } + }, + "sourceControl": { + "hostedReview": { + "prCopy": { + "shortLabel": "PR", + "reviewLabel": "pull request", + "titleLabel": "Pull Request" + }, + "mrCopy": { + "shortLabel": "MR", + "reviewLabel": "merge request", + "titleLabel": "Merge Request" + } + }, + "commitDropdown": { + "commit": "Commit", + "commitStagedChanges": "Commit staged changes", + "commitPush": "Commit & Push", + "commitForcePush": "Commit & Force Push", + "commitSync": "Commit & Sync", + "checkingBranchStatus": "Checking branch status…", + "checkingPrStatus": "Checking PR status…", + "prAlreadyMerged": "PR is already merged", + "checkOutBranchToPush": "Check out a branch before pushing commits", + "linkedReviewTargetUnavailable": "Linked review branch target is unavailable", + "publishBranchFirstToPush": "Publish the branch first to push commits", + "commitAndForcePush": "Commit staged changes and force push with lease", + "commitAndTryPush": "Commit staged changes and try to push", + "commitAndPush": "Commit staged changes and push", + "checkOutBranchToSync": "Check out a branch before syncing commits", + "publishBranchFirstToSync": "Publish the branch first to sync commits", + "useCommitAndForcePush": "Use Commit & Force Push — remote only has older copies of local commits", + "commitThenPullPush": "Commit, then pull and push", + "resolveConflicts": "Resolve conflicts before committing", + "stageFile": "Stage at least one file to commit", + "enterMessage": "Enter a commit message to commit" + }, + "createPrAction": { + "createReview": "Create {{titleLabel}}" + }, + "prCreate": { + "notReady": "This branch is not ready for a {{reviewLabel}} yet.", + "existingNumber": "{{titleLabel}} #{{number}} is already open.", + "existing": "{{titleLabel}} is already open.", + "createdNotRefreshed": "{{titleLabel}} created, but Orca could not refresh it yet.", + "lookupUnconfirmed": "Orca could not confirm whether this branch already has a {{reviewLabel}}. Try again in a moment.", + "dirty": "Commit changes before creating a {{reviewLabel}}.", + "detachedHead": "Check out a branch before creating a {{reviewLabel}}.", + "defaultBranch": "Switch to a feature branch before creating a {{reviewLabel}}.", + "noUpstream": "Publish commits before creating a {{reviewLabel}}.", + "needsSync": "Sync this branch before creating a {{reviewLabel}}.", + "authRequired": "Authenticate before creating a {{reviewLabel}}.", + "unsupportedMergeRequests": "Creating merge requests is not supported for this repo.", + "unsupportedPullRequests": "Creating pull requests is not supported for this repo.", + "existingReview": "A {{reviewLabel}} already exists for this branch.", + "forkHeadUnsupported": "Creating a {{reviewLabel}} from this fork is not supported.", + "baseNotOnRemote": "Push the base branch before creating a {{reviewLabel}}." + }, + "hostedReviewCreate": { + "pushSucceeded": "Push succeeded, but {{shortLabel}} creation failed: {{error}}" + }, + "remoteDropdown": { + "push": "Push", + "forcePush": "Force Push", + "pull": "Pull", + "fastForward": "Fast-forward", + "sync": "Sync", + "publishBranch": "Publish Branch", + "publishPrStatus": "PR Status", + "publishLinkedReview": "Linked Review", + "publishNoBranch": "No Branch", + "checkingBranchStatus": "Checking branch status…", + "checkingPrStatus": "Checking PR status…", + "prAlreadyMerged": "PR is already merged", + "linkedReviewTargetUnavailable": "Linked review branch target is unavailable", + "checkOutBranchToPush": "Check out a branch before pushing commits", + "pushAndSetUpstream": "Push this branch and set an upstream if needed", + "pushToLinkedReview": "Push updates to the linked review branch", + "tryRegularPush": "Try a regular push; git may require force push", + "pushMayNeedSync": "Push local commits; git may require syncing first", + "nothingToPush": "Nothing to push", + "checkOutBranchToForcePush": "Check out a branch before force pushing commits", + "nothingToForcePush": "Nothing to force push", + "checkOutBranchToPull": "Check out a branch before pulling commits", + "publishBranchFirstToPull": "Publish the branch first to pull commits", + "nothingNewToPull": "Nothing new to pull — remote only has older copies of local commits", + "nothingToPull": "Nothing to pull", + "checkOutBranchToFastForward": "Check out a branch before fast-forwarding", + "publishBranchFirstToFastForward": "Publish the branch first to fast-forward", + "nothingNewToFastForward": "Nothing new to fast-forward — remote only has older copies of local commits", + "nothingToFastForward": "Nothing to fast-forward", + "tryFastForwardPull": "Try a fast-forward pull; git may reject local commits", + "checkOutBranchToSync": "Check out a branch before syncing commits", + "publishBranchFirstToSync": "Publish the branch first to sync commits", + "useForcePushToSync": "Use Force Push — remote only has older copies of local commits", + "branchUpToDate": "Branch is up to date", + "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", + "fetch": "Fetch", + "fetchTitle": "Fetch from remote without merging", + "linkedReviewBranchExists": "Linked review branch already exists", + "checkOutBranchToPublish": "Check out a branch before publishing commits", + "branchAlreadyPublished": "Branch is already published", + "publishToOrigin": "Publish this branch to origin", + "nothingToPushTo": "Nothing to push to {{upstreamName}}", + "nothingToForcePushTo": "Nothing to force push to {{upstreamName}}", + "rebaseFromNamedBase": "Rebase from {{baseRef}}", + "rebaseWithLatestFrom": "Rebase current branch with latest commits from {{baseRef}}" + }, + "actions": { + "createPr": "Create PR", + "pushAndCreatePr": "Push & Create PR", + "switchBranch": "Switch branch", + "commits": "Commits" + }, + "dropdownCounts": { + "pushCount_one": "Push {{count}} commit", + "pushCount_other": "Push {{count}} commits", + "pullCount_one": "Pull {{count}} commit", + "pullCount_other": "Pull {{count}} commits", + "fastForwardCount_one": "Fast-forward {{count}} commit", + "fastForwardCount_other": "Fast-forward {{count}} commits", + "syncCounts": "Pull {{behind}}, push {{ahead}}", + "remoteBranchFallback": "the remote branch", + "titleForcePushOlderCopiesBranch": "Remote only has older copies of local commits. Force push this branch with lease to update {{upstreamName}}.", + "titleForcePushOlderCopiesCount_one": "Remote only has older copies of local commits. Force push {{count}} branch commit with lease to update {{upstreamName}}.", + "titleForcePushOlderCopiesCount_other": "Remote only has older copies of local commits. Force push {{count}} branch commits with lease to update {{upstreamName}}.", + "titleManualForcePushReplaceRemote_one": "Force push {{count}} local commit with lease to update {{upstreamName}} and replace remote-only commits.", + "titleManualForcePushReplaceRemote_other": "Force push {{count}} local commits with lease to update {{upstreamName}} and replace remote-only commits.", + "titleManualForcePush_one": "Force push {{count}} local commit with lease to update {{upstreamName}}.", + "titleManualForcePush_other": "Force push {{count}} local commits with lease to update {{upstreamName}}.", + "titleUnpublishedForcePushBranch": "Force push this branch with lease and set an upstream if needed.", + "titleUnpublishedForcePushCount_one": "Force push {{count}} branch commit with lease and set an upstream if needed.", + "titleUnpublishedForcePushCount_other": "Force push {{count}} branch commits with lease and set an upstream if needed." + }, + "primaryAction": { + "forcePushFromDesktop": "Force push this pull request's branch from desktop" + } + }, + "terminal": { + "webView": { + "loadFailed": "Terminal WebView load failed", + "httpError": "Terminal WebView HTTP error", + "renderProcessEnded": "Terminal WebView render process ended", + "documentStartFailed": "terminal document failed to start - {{message}}" + }, + "keyDefinitions": { + "forwardDelete": "Forward delete", + "arrowUp": "Arrow up", + "arrowDown": "Arrow down", + "arrowLeft": "Arrow left", + "arrowRight": "Arrow right", + "accessoryArrowUp": "Arrow Up", + "accessoryArrowDown": "Arrow Down", + "accessoryArrowLeft": "Arrow Left", + "accessoryArrowRight": "Arrow Right", + "interruptTerminal": "Interrupt terminal", + "sendEof": "Send EOF", + "clearScreen": "Clear screen", + "suspendProcess": "Suspend process", + "reverseSearch": "Reverse search", + "startOfLine": "Start of line", + "endOfLine": "End of line", + "deleteWordBackward": "Delete word backward", + "clearLineBeforeCursor": "Clear line before cursor" + }, + "webviewEngineErrorState": { + "status": "status {{statusCode}}", + "code": "code {{code}}", + "rendererCrashed": "renderer crashed", + "title": "Terminal failed to load", + "reload": "Reload" + }, + "webviewNotificationDispatch": { + "unknownError": "Unknown terminal error" + }, + "webviewReadyWatchdog": { + "initializeFailed": "Terminal did not initialize - no ready signal from the terminal view" + } } } diff --git a/mobile/src/i18n/locales/es.json b/mobile/src/i18n/locales/es.json index 3356a08fd1a..d519ea78bbb 100644 --- a/mobile/src/i18n/locales/es.json +++ b/mobile/src/i18n/locales/es.json @@ -143,6 +143,391 @@ "title": "Terminal" } }, + "browser": { + "addressField": { + "urlPlaceholder": "URL" + }, + "pane": { + "invalidUrl": "Introduce una URL válida." + }, + "paneView": { + "dialogTitle": "Diálogo del navegador", + "forward": "Adelante", + "keyboardPlaceholder": "Escribe en la página…", + "reload": "Recargar", + "sendText": "Enviar texto al navegador" + }, + "pointerModifiers": { + "clickModifier": "Modificador {{key}} para clics" + }, + "useBrowserCommands": { + "deliveryUnconfirmed": "Envío sin confirmar — revisa la página antes de reintentarlo", + "dialogAnswerFailed": "Esa respuesta no llegó a la página.", + "sent": "Enviado" + }, + "viewModeSwitch": { + "mobileView": "Mostrar vista móvil del sitio web", + "webView": "Mostrar vista de escritorio del sitio web" + } + }, + "chat": { + "ask": { + "answerPlaceholder": "Escribe tu respuesta", + "next": "Siguiente", + "other": "Otra opción…", + "skip": "Omitir", + "step": "Paso {{step}}", + "submit": "Enviar" + }, + "askRow": { + "asked": "Preguntó:", + "awaiting": "Esperando entrada del usuario:", + "awaitingUnnamed": "Esperando entrada del usuario", + "questionCount": "{{value0}} preguntas" + }, + "composer": { + "addToMessage": "Añadir al mensaje", + "attachImage": "Adjuntar imagen", + "cancelEditing": "Cancelar edición", + "placeholder": "Pregunta lo que quieras, @ para referenciar archivos, / para comandos", + "saveEditedMessage": "Guardar mensaje editado", + "sendMessage": "Enviar mensaje" + }, + "composerAttachments": { + "attachedImage": "Imagen adjunta", + "file": "Archivo", + "fileMeta": "{{type}} · {{size}}", + "removeFile": "Quitar {{name}}", + "removeImage": "Quitar imagen", + "uploadingFile": "Subiendo {{name}}" + }, + "composerPlaceholder": { + "prompt": "Pregunta lo que quieras, @ para referenciar archivos, / para comandos", + "reconnecting": "Reconectando…", + "waitingForTerminal": "Esperando a la terminal…" + }, + "diffCard": { + "copyDiff": "Copiar diff", + "diffGap": "Líneas no mostradas", + "diffTruncated": "Diff truncado" + }, + "hostStatus": { + "continuedOutside": "Continuado fuera de este chat.", + "continuedOutsideRewound": "Continuado fuera de este chat. Las respuestas anteriores ya no están en el historial de esta conversación.", + "historyItemTooLarge": "Esta parte del chat era demasiado grande para mostrarla.", + "historyRepaired": "No se pudo cargar parte del historial de este chat." + }, + "message": { + "copyMessage": "Copiar mensaje" + }, + "messageActionsSheet": { + "clipboardRejected": "El portapapeles rechazó el texto.", + "copyFailed": "No se pudo copiar", + "copyMessage": "Copiar mensaje", + "selectText": "Seleccionar texto" + }, + "nativeChatImageViewer": { + "closeImage": "Cerrar imagen" + }, + "nativeChatProse": { + "attachedImage": "Imagen adjunta", + "image": "imagen", + "openImageHint": "Abre la imagen a tamaño completo." + }, + "nativeChatToolLine": { + "added": "Añadió", + "copyCommand": "Copiar comando", + "deleted": "Eliminó", + "durationHours": "{{hours}} h {{minutes}} min {{seconds}} s", + "durationMilliseconds": "{{milliseconds}} ms", + "durationMinutes": "{{minutes}} min {{seconds}} s", + "durationSeconds": "{{seconds}} s", + "edited": "Editó", + "editing": "Editando", + "exitCode": "salida {{exitCode}}", + "fetched": "Obtuvo", + "fetching": "Obteniendo", + "listed": "Listó", + "listing": "Listando", + "ran": "Ejecutó", + "read": "Leyó", + "reading": "Leyendo", + "renamed": "Renombró", + "result": "Resultado", + "running": "Ejecutando", + "searched": "Buscó", + "searchedWeb": "Buscó en la web", + "searching": "Buscando", + "searchingWeb": "Buscando en la web", + "subagent": "Subagente", + "triedEdit": "Intentó editar", + "triedFetch": "Intentó obtener", + "triedList": "Intentó listar", + "triedRead": "Intentó leer", + "triedSearch": "Intentó buscar", + "triedSearchWeb": "Intentó buscar en la web" + }, + "nativeChatUserMessageFold": { + "loadFailed": "Mostrar mensaje completo — no se pudo cargar; toca para reintentar", + "showFullMessage": "Mostrar mensaje completo", + "showLess": "Mostrar menos" + }, + "nativeChatVisual": { + "defaultTitle": "Visualización", + "openFullscreen": "Abrir {{title}} a pantalla completa", + "retryHint": "Intenta cargarla de nuevo.", + "unavailable": "Visualización no disponible" + }, + "notice": { + "compaction": "Contexto compactado", + "contextCleared": "Contexto borrado", + "plan": "Plan", + "stoppedManually": "Detenido manualmente" + }, + "orcaStop": { + "canContinue": "Puedes continuar en esta conversación.", + "crash": "Orca en {{machine}} se detuvo inesperadamente mientras esta respuesta estaba en curso.", + "quit": "Orca en {{machine}} se cerró mientras esta respuesta estaba en curso.", + "remote": "Orca en {{machine}} se detuvo mientras esta respuesta estaba en curso.", + "unnamedServer": "el servidor remoto", + "update": "Orca en {{machine}} se reinició para actualizarse mientras esta respuesta estaba en curso." + }, + "permission": { + "needsAccessTo": "Necesita acceso a: ", + "newerVersionRequired": "Esta solicitud necesita una versión más reciente de Orca.", + "planFile": "Archivo del plan: {{filePath}}", + "reason": "Motivo: " + }, + "promptCard": { + "answerInTerminal": "Responder en la terminal", + "terminalExplanation": "Responda esta pregunta OMP en la terminal." + }, + "promptStrip": { + "expand": "Expandir" + }, + "question": { + "otherReplyPlaceholder": "O escribe una respuesta…", + "replyPlaceholder": "Escribe tu respuesta…", + "sendReply": "Enviar respuesta", + "submit": "Enviar", + "submitCount": "Enviar ({{count}})", + "submitSelectedOptions": "Enviar opciones seleccionadas" + }, + "queuedMessages": { + "clearAll": "Borrar todo", + "deleteAll": "Eliminar todos los mensajes en cola", + "editMessage": "Editar mensaje", + "pausedState": "En pausa", + "queuedState": "En cola", + "resume": "Reanudar", + "resumeSending": "Reanudar el envío de los mensajes en cola", + "title": "Mensajes en cola" + }, + "receipt": { + "cancelled": "Cancelado", + "cancelledBy": "Cancelado en {{device}}", + "emptyAnswer": "Respuesta vacía", + "resolved": "Resuelto", + "resolver": "Respondido en {{device}}", + "unavailable": "Respuesta seleccionada no disponible" + }, + "sessionOptionCopy": { + "availableAfterSessionStarts": "Disponible después de iniciar la sesión.", + "chooseInAgentPicker": "Elegir en el selector del agente…", + "effort": "Esfuerzo", + "fast": "Rápido", + "fastMode": "Modo rápido", + "high": "Alto", + "low": "Bajo", + "max": "Máximo", + "medium": "Medio", + "mediumShort": "Medio", + "minimal": "Mínimo", + "minimalShort": "Mín.", + "model": "Modelo", + "notSet": "Sin configurar", + "off": "Desactivado", + "on": "Activado", + "permissions": "Permisos", + "sessionOptions": "Opciones de sesión", + "setWhenSessionStarts": "Solo se puede configurar al iniciar la sesión.", + "speed": "Velocidad", + "standard": "Estándar", + "thinking": "Razonamiento", + "toggleOption": "Alternar {{value0}}", + "ultra": "Supremo", + "xhigh": "Muy alto", + "xhighShort": "Muy alto" + }, + "sessionOptionPickers": { + "agentWorking": "El agente está trabajando", + "backToModels": "Volver a los modelos", + "closePicker": "Cerrar selector", + "dispatchUnconfirmed": "Enviado al agente — sin confirmar", + "modelFullAccessLabel": "Modelo, {{value}}, acceso total", + "modelLabel": "Modelo, {{value}}", + "permissions": "Permisos", + "permissionsLabel": "Permisos, {{value}}", + "settingsLocked": "La configuración del modelo se desbloqueará cuando termine este turno.", + "workingAnnouncement": "El agente está trabajando. La configuración del modelo se desbloqueará cuando termine este turno." + }, + "sessionOptionRows": { + "defaultMarker": "Predeterminado", + "notReportedMarker": "No indicado" + }, + "sessionOptionSegments": { + "dispatchUnconfirmed": "Enviado al agente — sin confirmar" + }, + "stopButton": { + "stopAgent": "Detener el agente", + "stoppingState": "Deteniendo…" + }, + "streamFrame": { + "streamEnded": "Finalizó la transmisión del historial del chat", + "streamFailed": "Falló la transmisión del historial del chat" + }, + "taskList": { + "added": "Se añadió {{task}}", + "completed": "Completada", + "empty": "Sin tareas", + "finished": "Se completó {{task}}", + "inProgress": "En curso", + "pending": "Pendiente", + "progress": "{{completed}} de {{total}} tareas completadas", + "removed": "Se eliminó {{task}}", + "reset": "Marcado como pendiente: {{task}}", + "showAll": "Lista completa de tareas", + "started": "Se inició {{task}}", + "taskStatus": "{{status}}: {{task}}", + "title": "Tareas", + "unchanged": "Tareas sin cambios", + "updated": "Se actualizó {{task}}" + }, + "toolActivity": { + "count_many": "{{count}} llamadas a herramientas", + "count_one": "{{count}} llamada a herramienta", + "count_other": "{{count}} llamadas a herramientas", + "failedCallsLabel": "Llamadas a herramientas fallidas: {{value0}}", + "failedCount": "{{value0}} fallidas", + "interruptedCallsLabel": "Llamadas a herramientas interrumpidas: {{value0}}", + "interruptedCount": "{{value0}} interrumpidas", + "moreCalls": "+{{value0}} más", + "result": "Resultado", + "runningCommand": "Ejecutando comando", + "runningNamed": "Ejecutando {{toolName}}", + "runningNamedPreview": "Ejecutando {{toolName}} {{preview}}", + "runningPreview": "Ejecutando {{preview}}" + }, + "toolRun": { + "list": "{{value0}} y {{value1}}", + "liveBrowserOnce": "Usando el navegador", + "liveBrowserTimes_many": "Usando el navegador {{count}} veces", + "liveBrowserTimes_one": "Usando el navegador {{count}} vez", + "liveBrowserTimes_other": "Usando el navegador {{count}} veces", + "liveFileChange_many": "Editando {{count}} archivos", + "liveFileChange_one": "Editando {{count}} archivo", + "liveFileChange_other": "Editando {{count}} archivos", + "liveListFiles_many": "Listando {{count}} directorios", + "liveListFiles_one": "Listando {{count}} directorio", + "liveListFiles_other": "Listando {{count}} directorios", + "liveMcpToolCall_many": "Usando {{count}} integraciones", + "liveMcpToolCall_one": "Usando {{count}} integración", + "liveMcpToolCall_other": "Usando {{count}} integraciones", + "liveOther_many": "Usando {{count}} herramientas", + "liveOther_one": "Usando {{count}} herramienta", + "liveOther_other": "Usando {{count}} herramientas", + "liveRead_many": "Leyendo {{count}} archivos", + "liveRead_one": "Leyendo {{count}} archivo", + "liveRead_other": "Leyendo {{count}} archivos", + "liveSearch_many": "Buscando {{count}} veces", + "liveSearch_one": "Buscando {{count}} vez", + "liveSearch_other": "Buscando {{count}} veces", + "liveSubAgentActivity_many": "Ejecutando {{count}} agentes", + "liveSubAgentActivity_one": "Ejecutando {{count}} agente", + "liveSubAgentActivity_other": "Ejecutando {{count}} agentes", + "liveTodoListOnce": "Actualizando el plan", + "liveTodoListTimes_many": "Actualizando el plan {{count}} veces", + "liveTodoListTimes_one": "Actualizando el plan {{count}} vez", + "liveTodoListTimes_other": "Actualizando el plan {{count}} veces", + "liveUnknown_many": "Ejecutando {{count}} comandos", + "liveUnknown_one": "Ejecutando {{count}} comando", + "liveUnknown_other": "Ejecutando {{count}} comandos", + "liveWebSearch_many": "Buscando en la web {{count}} veces", + "liveWebSearch_one": "Buscando en la web {{count}} vez", + "liveWebSearch_other": "Buscando en la web {{count}} veces", + "pair": "{{value0}} y {{value1}}", + "separator": ", ", + "settledBrowserOnce": "Se usó el navegador", + "settledBrowserTimes_many": "Se usó el navegador {{count}} veces", + "settledBrowserTimes_one": "Se usó el navegador {{count}} vez", + "settledBrowserTimes_other": "Se usó el navegador {{count}} veces", + "settledFileChange_many": "Se editaron {{count}} archivos", + "settledFileChange_one": "Se editó {{count}} archivo", + "settledFileChange_other": "Se editaron {{count}} archivos", + "settledListFiles_many": "Se listaron {{count}} directorios", + "settledListFiles_one": "Se listó {{count}} directorio", + "settledListFiles_other": "Se listaron {{count}} directorios", + "settledMcpToolCall_many": "Se usaron {{count}} integraciones", + "settledMcpToolCall_one": "Se usó {{count}} integración", + "settledMcpToolCall_other": "Se usaron {{count}} integraciones", + "settledOther_many": "Se usaron {{count}} herramientas", + "settledOther_one": "Se usó {{count}} herramienta", + "settledOther_other": "Se usaron {{count}} herramientas", + "settledRead_many": "Se leyeron {{count}} archivos", + "settledRead_one": "Se leyó {{count}} archivo", + "settledRead_other": "Se leyeron {{count}} archivos", + "settledSearch_many": "Se buscó {{count}} veces", + "settledSearch_one": "Se buscó {{count}} vez", + "settledSearch_other": "Se buscó {{count}} veces", + "settledSubAgentActivity_many": "Se ejecutaron {{count}} agentes", + "settledSubAgentActivity_one": "Se ejecutó {{count}} agente", + "settledSubAgentActivity_other": "Se ejecutaron {{count}} agentes", + "settledTodoListOnce": "Se actualizó el plan", + "settledTodoListTimes_many": "Se actualizó el plan {{count}} veces", + "settledTodoListTimes_one": "Se actualizó el plan {{count}} vez", + "settledTodoListTimes_other": "Se actualizó el plan {{count}} veces", + "settledUnknown_many": "Se ejecutaron {{count}} comandos", + "settledUnknown_one": "Se ejecutó {{count}} comando", + "settledUnknown_other": "Se ejecutaron {{count}} comandos", + "settledWebSearch_many": "Se buscó en la web {{count}} veces", + "settledWebSearch_one": "Se buscó en la web {{count}} vez", + "settledWebSearch_other": "Se buscó en la web {{count}} veces" + }, + "turnStatus": { + "failedAfter": "Falló tras {{value0}}", + "hoursMinutesSeconds": "{{hours}} h {{minutes}} min {{seconds}} s", + "interruptedAfter": "Interrumpido tras {{value0}}", + "minutesSeconds": "{{minutes}} min {{seconds}} s", + "queueAfterStop": "Pon en cola un mensaje para que se ejecute después de detenerse", + "responding": "El agente está respondiendo", + "seconds_many": "{{count}} s", + "seconds_one": "{{count}} s", + "seconds_other": "{{count}} s", + "sendAfterStop": "Envía un mensaje para que se ejecute después de detenerse", + "stopping": "Deteniendo…", + "thinking": "Pensando", + "toggleDetails": "Alternar detalles del turno", + "workedFor": "Trabajó durante {{value0}}", + "working": "Trabajando…", + "workingFor": "Trabajando durante {{value0}}" + }, + "usageLimit": { + "reached": "{{agent}} alcanzó su límite de uso.", + "reachedNamed": "{{agent}} alcanzó su límite {{window}}.", + "resetsAt": "Próximo restablecimiento: {{when}}.", + "theAgent": "El agente", + "windowCredits": "de uso adicional", + "windowSession": "de sesión", + "windowWeekly": "semanal", + "windowWeeklyModel": "semanal del modelo" + }, + "useImageUpload": { + "attachFailed": "No se pudo adjuntar", + "disconnected": "No se pudo adjuntar (sin conexión)", + "imageTooLarge": "Imagen demasiado grande para adjuntarla", + "permissionDenied": "Permiso de acceso a las fotos denegado" + } + }, "common": { "back": "Atrás", "cancel": "Cancelar", @@ -155,6 +540,8 @@ "done": "Listo", "edit": "Editar", "ok": "Aceptar", + "open": "Abrir", + "refresh": "Actualizar", "remove": "Eliminar", "retry": "Reintentar", "save": "Guardar", @@ -769,8 +1156,51 @@ "unexpectedResponse": "Respuesta inesperada" }, "webShellUpdateFailureCopy": { + "blockedReason": "Bloqueado: {{reason}}.", + "blockedWorkspace": "Se bloqueó el espacio de trabajo.", + "failed": "Se mostró la pantalla de error.", "failedAgo": "La última actualización de {{hostName}} falló hace {{time}}: {{reason}}{{offered}}. {{outcome}}", - "failedJustNow": "La última actualización de {{hostName}} acaba de fallar: {{reason}}{{offered}}. {{outcome}}" + "failedJustNow": "La última actualización de {{hostName}} acaba de fallar: {{reason}}{{offered}}. {{outcome}}", + "hostCodeCopy": { + "assetChanged": "un recurso cambió en el host", + "assetUnknown": "el host no reconoció un recurso", + "buildChanged": "la compilación del host cambió durante la descarga", + "offsetInvalid": "el host rechazó un desplazamiento de lectura", + "readLimited": "el host limitó las lecturas simultáneas", + "unavailable": "el host no tiene un paquete del espacio de trabajo" + }, + "nativeRoute": "Se mostró la pantalla nativa.", + "offeredGeneration": " (generación {{generation}})", + "openedCached": "Se recurrió a la versión guardada.", + "openedCachedGeneration": "Se recurrió a la versión guardada (generación {{generation}}).", + "reasonCopy": { + "assetChecksumMismatch": "la suma de comprobación del recurso no coincide", + "assetEntryChanged": "un recurso ya no coincidía con el manifiesto", + "assetNoProgress": "la lectura de un recurso no avanzó", + "assetOverlong": "el tamaño de un recurso superaba el declarado en el manifiesto", + "assetShort": "un recurso terminó antes de alcanzar el tamaño declarado", + "buildChangedMidFetch": "la compilación del host cambió durante la descarga", + "cacheWriteFailed": "no se pudo guardar la descarga en este teléfono", + "chunkMisrouted": "un fragmento correspondía a un recurso o desplazamiento incorrecto", + "chunkOversize": "un fragmento superaba el tamaño permitido por el host", + "connectionLost": "se perdió la conexión", + "fetchStopped": "se detuvo la descarga", + "hostRefused": "el host rechazó la lectura", + "noConnection": "no hay conexión con el host", + "rangeUndecodable": "no se pudieron decodificar los datos comprimidos de una lectura", + "replyUnreadable": "el host envió una respuesta que esta aplicación no pudo leer", + "unrecognisedError": "un error no reconocido" + }, + "routeUnavailable": "Se indicó que la pantalla no está disponible.", + "waiting": "Se esperó al host.", + "wallCopy": { + "bundleShellTooOld": "esta aplicación es demasiado antigua para el paquete guardado", + "bundleTooOldForHost": "el paquete guardado es demasiado antiguo para el host", + "bundleUnavailable": "el host no tiene un paquete del espacio de trabajo", + "hostTooOldForBundle": "la versión del host es demasiado antigua para el paquete guardado", + "pageRouteGrantMissing": "esta pantalla requiere una versión más reciente de la aplicación", + "pageRouteUnlisted": "el espacio de trabajo del host no incluye esta pantalla" + } } }, "dictation": { @@ -787,6 +1217,95 @@ "updateFailed": "No se pudieron actualizar los ajustes de dictado" } }, + "files": { + "fileExplorerPanel": { + "backToSession": "Volver a la sesión", + "closeFiles": "Cerrar archivos", + "connecting": "Conectando con el escritorio…", + "emptyState": "No se encontraron archivos", + "loadFailed": "No se pudieron cargar los archivos", + "loadInterrupted": "Carga de la carpeta interrumpida", + "title": "Archivos", + "truncatedList": "{{worktreeLabel}} - Mostrando los primeros 5000", + "waiting": "Esperando al escritorio…" + }, + "fileExplorerRow": { + "fileUnavailable": "{{name}} no disponible en el móvil", + "loadFolderFailed": "No se pudo cargar la carpeta", + "loading": "Cargando…", + "openFolder": "Abrir la carpeta {{name}}", + "previewFile": "Previsualizar el archivo {{name}}", + "retryLoading": "Reintentar cargar {{path}}", + "unavailable": "No disponible en el móvil" + }, + "fileMarkdownPreview": { + "copyLoadedSource": "Copiar código fuente Markdown cargado", + "copySource": "Copiar código fuente Markdown", + "viewPreview": "Ver vista previa renderizada de Markdown", + "viewSource": "Ver código fuente Markdown" + }, + "fileMediaPreview": { + "downloading": "Descargando {{bytes}} de {{total}}", + "loadFailed": "No se pudo cargar el contenido multimedia", + "loading": "Cargando contenido multimedia…", + "waiting": "Esperando al escritorio…" + }, + "fileMutationOwnership": { + "sshOwnerChanged": "No se pudo verificar la conexión SSH. Vuelve a conectar el host e inténtalo de nuevo." + }, + "filePreviewBody": { + "emptyFile": "Archivo vacío", + "imageLabel": "Imagen de {{title}}" + }, + "filePreviewEditableSource": { + "editorLabel": "Editor de {{title}}" + }, + "filePreviewResponse": { + "binaryUnavailable": "Vista previa de archivos binarios no disponible", + "bytes": "{{size}} B", + "fileChanged": "El archivo ha cambiado en el escritorio. Recarga la vista previa antes de guardar.", + "fileNotFound": "Archivo no encontrado", + "fileTooLarge": "Archivo demasiado grande para la vista previa en el móvil", + "filesystemUnreachable": "No se pudo acceder al sistema de archivos del escritorio", + "kilobytes": "{{size}} KB", + "loadFailed": "No se pudo cargar la vista previa", + "megabytes": "{{size}} MB", + "reloadBeforeSaving": "Recarga la vista previa antes de guardar.", + "unknownSize": "tamaño desconocido" + }, + "filePreviewScreen": { + "backToFiles": "Volver a los archivos", + "chooseWorkspaceFile": "Elige un archivo dentro de este espacio de trabajo.", + "discard": "Descartar", + "discardMessage": "Se perderán los cambios sin guardar.", + "discardTitle": "¿Descartar cambios?", + "fileTitle": "Archivo", + "loadFailed": "No se pudo cargar la vista previa", + "loading": "Cargando vista previa…", + "openFailed": "No se pudo abrir el archivo", + "saveArtifact": "Guardar artefacto de terminal", + "saveFailed": "No se pudo guardar el archivo", + "stay": "Seguir aquí", + "title": "Vista previa", + "waiting": "Esperando al escritorio…" + }, + "filePreviewSourceText": { + "previewLabel": "Vista previa del archivo", + "truncatedNote": "Vista previa truncada. Tamaño del archivo: {{size}}." + }, + "markdownPreviewImage": { + "expandFallback": "Ampliar imagen", + "expandImage": "Ampliar {{alt}}", + "imageLabel": "Imagen de Markdown", + "loadFailed": "No se pudo cargar la imagen" + }, + "pdfPreview": { + "open": "Abrir PDF", + "openFailed": "No se pudo abrir este PDF", + "opening": "Abriendo PDF…", + "updateRequired": "Actualiza Orca Mobile para abrir archivos PDF" + } + }, "home": { "accountUsageCards": { "sectionHeading": "Uso de la cuenta", @@ -1122,12 +1641,10 @@ "noComments": "Aún no hay comentarios.", "noDescription": "No se proporcionó descripción.", "noHumanComments": "Sin comentarios humanos.", - "resolvedComment_many": "Comentario resuelto de {{author}} ({{count}})", - "resolvedComment_one": "Comentario resuelto de {{author}}", - "resolvedComment_other": "Comentario resuelto de {{author}} ({{count}})", - "resolvedThread_many": "Hilo resuelto de {{author}} ({{count}})", - "resolvedThread_one": "Hilo resuelto de {{author}}", - "resolvedThread_other": "Hilo resuelto de {{author}} ({{count}})", + "resolvedCommentCount": "Comentario resuelto de {{author}} ({{count}})", + "resolvedCommentSingle": "Comentario resuelto de {{author}}", + "resolvedThreadCount": "Hilo resuelto de {{author}} ({{count}})", + "resolvedThreadSingle": "Hilo resuelto de {{author}}", "showMore": "Mostrar {{count}} más", "showMoreOf": "Mostrar {{count}} más ({{remaining}} restantes)", "title": "Comentarios" @@ -1326,6 +1843,608 @@ "title": "Revisores" } }, + "session": { + "accessoryKeys": { + "addCustomShortcut": "Agregar atajo personalizado", + "dismissKeyboard": "Ocultar teclado", + "dismissKeyboardHint": "Oculta el teclado en pantalla y mantiene abierta la sesión de terminal actual.", + "paste": "Pegar", + "pasteFromClipboard": "Pegar desde el portapapeles", + "sendKey": "Enviar {{label}}", + "switchToBufferedInput": "Cambiar a la entrada de comandos en búfer", + "switchToDesktopMode": "Cambiar al modo de escritorio", + "switchToLiveInput": "Cambiar a la entrada directa en la terminal", + "switchToPhoneMode": "Cambiar al modo teléfono" + }, + "activeContent": { + "createTab": "Crear pestaña", + "creatingTab": "Creando…", + "fileTitle": "Archivo", + "loadingTerminal": "Cargando terminal", + "noTabs": "No hay pestañas en esta sesión", + "retryLoadingTerminal": "Reintentar cargar la terminal", + "terminalTakingLonger": "La terminal está tardando más de lo esperado" + }, + "agentChildRow": { + "agent": "Agente", + "days_many": "{{count}} d", + "days_one": "{{count}} d", + "days_other": "{{count}} d", + "ended": "Finalizado", + "hours_many": "{{count}} h", + "hours_one": "{{count}} h", + "hours_other": "{{count}} h", + "minutes_many": "{{count}} min", + "minutes_one": "{{count}} min", + "minutes_other": "{{count}} min", + "noRecentUpdate": "Sin actualizaciones recientes", + "noUpdate": "Sin actualizaciones desde hace {{value0}}" + }, + "agentMessageAttribution": { + "from": "De {{sender}}", + "fromMore": "De {{sender}} +{{count}}", + "fromUnnamed": "De un agente", + "messageFrom": "Mensaje de {{sender}}", + "messageFromMore": "Mensaje de {{sender}} +{{count}}", + "messageFromUnnamed": "Mensaje de un agente", + "unnamedSender": "un agente" + }, + "aiVaultResumeLaunch": { + "createFailed": "No se pudo crear la terminal", + "inputLocked": "La entrada de la terminal está bloqueada", + "sendFailed": "No se pudo enviar el comando de reanudación" + }, + "aiVaultResumePreparation": { + "prepareFailed": "No se pudo preparar esta sesión antigua de Codex. Vuelve a intentar reanudarla." + }, + "backgroundTask": { + "agent": "Agente en segundo plano", + "command": "Comando en segundo plano", + "countAgents_many": "{{count}} agentes", + "countAgents_one": "{{count}} agente", + "countAgents_other": "{{count}} agentes", + "countMonitors_many": "{{count}} monitores", + "countMonitors_one": "{{count}} monitor", + "countMonitors_other": "{{count}} monitores", + "countShellCommandOne": "1 comando de shell", + "countShell_many": "{{count}} shells", + "countShell_one": "{{count}} shell", + "countShell_other": "{{count}} shells", + "countTasks_many": "{{count}} tareas", + "countTasks_one": "{{count}} tarea", + "countTasks_other": "{{count}} tareas", + "countWorkflows_many": "{{count}} flujos de trabajo", + "countWorkflows_one": "{{count}} flujo de trabajo", + "countWorkflows_other": "{{count}} flujos de trabajo", + "detailsUnavailable": "Los detalles de la tarea no están disponibles para esta sesión.", + "groupAgents": "Agentes", + "groupMonitors": "Monitores", + "groupShell": "Shell", + "groupTasks": "Tareas", + "groupWorkflows": "Flujos de trabajo", + "headerTotalCount_many": "{{count}} tareas en segundo plano", + "headerTotalCount_one": "{{count}} tarea en segundo plano", + "headerTotalCount_other": "{{count}} tareas en segundo plano", + "monitor": "Monitor en segundo plano", + "monitoring": "Supervisando tareas en segundo plano", + "reasonBlocked": "con error", + "reasonWaiting": "aprobación pendiente", + "stateBlocked": "bloqueado", + "stateBlockedCount": "{{value0}} en estado bloqueado", + "stateDone": "terminado", + "stateDoneCount": "{{value0}} en estado terminado", + "stateIdle": "detenido", + "stateIdleCount": "{{value0}} en estado detenido", + "stateMonitoring": "supervisando", + "stateMonitoringCount": "{{value0}} supervisando", + "stateUnverifiable": "estado no disponible", + "stateUnverifiableCount": "{{value0}} con estado no disponible", + "stateWaiting": "esperando", + "stateWaitingCount": "{{value0}} esperando", + "stateWorking": "trabajando", + "stateWorkingCount": "{{value0}} trabajando", + "stop": "Detener", + "stopAll": "Detener tareas en segundo plano", + "stopTask": "Detener {{value0}}", + "task": "Tarea en segundo plano", + "workflow": "Flujo de trabajo en segundo plano" + }, + "browserTabActionSheet": { + "forward": "Adelante", + "reload": "Recargar", + "title": "Navegador" + }, + "bulkCloseSheetActions": { + "closeOtherTabs": "Cerrar las otras pestañas" + }, + "bulkTabClose": { + "confirm": "Cerrar pestañas", + "description": "Se cerrarán las pestañas seleccionadas. Las pestañas fijadas permanecerán abiertas.", + "titleEditors": "¿Cerrar todas las pestañas del editor?", + "titleLeft": "¿Cerrar las pestañas de la izquierda?", + "titleOthers": "¿Cerrar las otras pestañas?", + "titleRight": "¿Cerrar las pestañas de la derecha?" + }, + "commandDock": { + "commandPlaceholder": "Escribe un comando…", + "liveInputHint": "El texto que escribes se envía directamente a la terminal activa.", + "liveInputLabel": "Entrada al terminal en tiempo real", + "sendCommand": "Enviar comando" + }, + "contentRow": { + "dismissCreationWarning": "Descartar la advertencia de creación del espacio de trabajo" + }, + "diffCommentLineRow": { + "addNote": "Agregar nota en la línea {{line}}", + "deleteNote": "Eliminar nota de la línea {{line}}", + "diffLine": "{{title}}, línea {{line}} del diff", + "line": "Línea {{line}}", + "placeholder": "Agregar nota de revisión", + "saveNote": "Guardar nota" + }, + "diffLineRow": { + "addNoteLabel": "Agregar nota en la línea {{line}}", + "deleteNoteLabel": "Eliminar nota de la línea {{line}}", + "diffLineLabel": "Línea {{line}} del diff de {{title}}", + "lineNumber": "Línea {{line}}", + "notePlaceholder": "Agregar nota de revisión", + "saveNote": "Guardar nota" + }, + "diffReviewLoaders": { + "changesLoadFailed": "No se pudieron cargar los cambios", + "committedChangesFailed": "No se pudieron cargar los cambios confirmados", + "committedChangesUnavailable": "Cambios confirmados no disponibles", + "committedDiffUnavailable": "El diff de los commits no está disponible", + "desktopUpdateRequired": "Actualiza Orca en el escritorio para revisar los cambios en el móvil.", + "diffLoadFailed": "No se pudo cargar el diff", + "invalidSourceControlResponse": "La respuesta del control de código fuente no es válida", + "reviewNotesLoadFailed": "No se pudieron cargar las notas de revisión" + }, + "diffReviewQueue": { + "committedOnBranch": "Commits en la rama", + "staged": "En el área de preparación", + "unstaged": "Fuera del área de preparación" + }, + "diffReviewScreenModel": { + "branch": "Rama", + "staged": "En el área de preparación", + "unstaged": "Fuera del área de preparación" + }, + "fileReader": { + "copyReviewNotes": "Copiar notas de revisión", + "imageLabel": "Imagen de {{title}}", + "noReviewNotes": "Sin notas de revisión", + "previewLabel": "Vista previa de {{title}}", + "reviewNotes_many": "{{count}} notas de revisión", + "reviewNotes_one": "{{count}} nota de revisión", + "reviewNotes_other": "{{count}} notas de revisión", + "send": "Enviar", + "sendReviewNotes": "Enviar notas de revisión a la IA" + }, + "floatingWorkspace": { + "title": "Espacio de trabajo flotante" + }, + "githubPrMutations": { + "reviewThreadUpdateFailed": "No se pudo actualizar el hilo de revisión.", + "titleUpdateFailed": "No se pudo actualizar el título." + }, + "githubPrRpc": { + "requestFailed": "La solicitud falló: {{method}}" + }, + "header": { + "backToWorktrees": "Volver a los worktrees", + "moreActions": "Más acciones de la sesión", + "openFileExplorer": "Abrir explorador de archivos", + "openSourceControl": "Abrir control de código fuente", + "reconnectToDesktop": "Volver a conectar con el escritorio", + "showSidebar": "Mostrar barra lateral", + "terminalTitle": "Terminal" + }, + "headerMoreActionsSheet": { + "agentHistory": "Historial de agentes", + "agentHistoryHint": "Explora y reanuda sesiones de agentes", + "checks": "Checks", + "checksHint": "Abrir los checks de la pull request" + }, + "markdownReader": { + "changedOnDesktop": "Modificado en el escritorio", + "copyLoaded": "Copiar lo cargado", + "copyLoadedSource": "Copiar el código fuente Markdown cargado", + "copyPath": "Copiar ruta", + "copyPathLabel": "Copiar la ruta del archivo Markdown", + "copySource": "Copiar el código fuente Markdown", + "discard": "Descartar", + "dismissKeyboard": "Ocultar teclado", + "dismissKeyboardHint": "Oculta el teclado en pantalla y mantiene abierto el editor de Markdown.", + "readOnly": "Solo lectura" + }, + "nativeChatAttachActions": { + "availableSkills": "Disponibles: {{count}}", + "commands": "Comandos", + "fileTypes": "PDF, texto, código…", + "files": "Archivos", + "photos": "Fotos", + "skills": "Habilidades", + "workspaceFile": "Archivo del espacio de trabajo" + }, + "nativeChatCardHeaderAction": { + "collapse": "Contraer" + }, + "nativeChatCliFork": { + "resumeInNewCli": "Reanudar en una nueva CLI" + }, + "nativeChatFloatingControls": { + "scrollToLatest": "Ir a lo más reciente" + }, + "nativeChatFullContent": { + "loadMessageFailed": "Mostrar mensaje completo — no se pudo cargar; toca para reintentar", + "loadOutputFailed": "Mostrar salida completa — no se pudo cargar; toca para reintentar", + "partialMessage": "Se muestra parte de este mensaje.", + "partialOutput": "Se muestran {{shown}} de {{total}}.", + "showFullMessage": "Mostrar mensaje completo", + "showFullOutput": "Mostrar salida completa" + }, + "nativeChatRenderData": { + "defaultAgentLabel": "el agente", + "emptySubtitle": "Pide que {{agent}} inspeccione código, explique la salida o haga un cambio.", + "emptyTitle": "Inicia un chat con {{agent}}", + "errorSubtitle": "No se pudo leer la transcripción. Vuelva a la terminal para seguir trabajando.", + "errorTitle": "No se pudo cargar la conversación", + "notFoundSubtitle": "Envía un mensaje para iniciar una conversación o abre la vista de terminal para ver qué está haciendo {{agent}}.", + "notFoundTitle": "Aún no se encuentra esta conversación" + }, + "nativeChatToggleAction": { + "switchToChatView": "Cambiar a la vista de chat", + "switchToTerminalView": "Cambiar a la vista de terminal" + }, + "newTabAgentHostLaunch": { + "notesNotSent": "El agente se inició, pero no se enviaron las notas.", + "notesSent": "Notas enviadas", + "notesUnconfirmed": "El agente se inició, pero no se pudo confirmar el envío de las notas.", + "promptUnconfirmed": "El agente se inició, pero no se pudo confirmar el envío del prompt." + }, + "prActionsEngine": { + "mergeFailed": "No se pudo hacer merge de la pull request.", + "refreshFailed": "No se pudo actualizar la pull request." + }, + "prAiTriageLaunch": { + "agentStarted": "Agente iniciado", + "agentStartedIn": "Agente iniciado en {{label}}", + "emptyPrompt": "El prompt guardado para esta acción está vacío. Actualiza la configuración de IA del control de código fuente en tu equipo.", + "loadFailed": "No se pudieron cargar los agentes disponibles.", + "promptNotSent": "El agente se inició, pero no se envió el prompt.", + "updateRequired": "Actualiza Orca en tu equipo para iniciar un agente desde tu teléfono." + }, + "prSidebarState": { + "loadFailed": "No se pudo cargar la pull request" + }, + "queuedCardText": { + "showLess": "Mostrar menos", + "showLessHint": "Muestra menos contenido del mensaje", + "showMore": "Mostrar más", + "showMoreHint": "Muestra el mensaje completo" + }, + "queuedMessageCardRow": { + "deleteHint": "Eliminar este mensaje en cola", + "images_many": "{{count}} imágenes", + "images_one": "{{count}} imagen", + "images_other": "{{count}} imágenes", + "moreActions": "Más acciones", + "send": "Enviar", + "sendAgainHint": "Volver a enviar este mensaje", + "sendHint": "Enviar este mensaje", + "showLessHint": "Muestra menos contenido del mensaje", + "steer": "Enviar ahora", + "steerHint": "Enviar sin interrumpir al modelo" + }, + "queuedMessageComposer": { + "commandsNotAllowed": "No se pueden guardar comandos en un mensaje en cola.", + "editing": "Editando mensaje", + "editingAttributed": "Editando mensaje · {{attribution}}" + }, + "quickCommandEditorForm": { + "action": "Acción", + "addQuickCommand": "Agregar comando rápido", + "advanced": "Avanzado", + "agent": "Agente", + "agentPrompt": "Prompt de agente", + "appendEnter": "Agregar Enter", + "appendEnterDescription": "Enviar inmediatamente en lugar de solo insertar texto.", + "chooseAgent": "Elige agente", + "commandText": "Texto del comando", + "globalScope": "Global", + "label": "Etiqueta", + "labelPlaceholder": "Iniciar el servidor de desarrollo", + "olderHostPromptTooLong": "El texto es demasiado largo para este host. Actualiza Orca en el host para guardar instrucciones más largas.", + "projectScope": "Proyecto", + "prompt": "Prompt", + "promptHint": "Admite skills, rutas de archivos y comandos integrados.", + "promptPlaceholder": "Pide al agente que investigue este espacio de trabajo", + "promptSizeCharacters": "{{used}} / {{max}} caracteres", + "promptSizeKilobytes": "{{used}} KB / {{max}} KB", + "promptTooLong": "El texto es demasiado largo para guardarlo.", + "scope": "Alcance", + "terminalCommand": "Comando de terminal" + }, + "quickCommandLaunch": { + "defaultLabel": "Comando rápido", + "editBeforeRunning": "Edita este comando rápido antes de ejecutarlo", + "runFailed": "No se pudo ejecutar {{label}}" + }, + "quickCommandRow": { + "copyCommand": "Copiar {{label}}", + "copyFailed": "No se pudo copiar", + "deleteCommand": "Eliminar {{label}}", + "editCommand": "Editar {{label}}", + "nothingToCopy": "Nada que copiar", + "runCommand": "Ejecutar {{label}}" + }, + "quickCommandsList": { + "emptyState": "Aún no hay comandos rápidos.", + "globalGroup": "Global", + "newCommand": "Nuevo comando rápido", + "noMatches": "No hay comandos rápidos que coincidan.", + "projectGroup": "Este proyecto", + "searchPlaceholder": "Buscar comandos rápidos…" + }, + "quickCommandsSheet": { + "addTitle": "Agregar comando rápido", + "chooseAgentTitle": "Elegir agente", + "deleteConfirmMessage": "Este comando rápido se eliminará de tu lista guardada.", + "deleteConfirmTitle": "¿Eliminar \"{{label}}\"?", + "editTitle": "Editar comando rápido", + "editorDescription": "Guarda comandos de terminal o prompts de agente para acceder a ellos rápidamente.", + "title": "Comandos rápidos", + "untitled": "Sin título" + }, + "quickCommandsTabButton": { + "quickCommands": "Comandos rápidos" + }, + "routeHelpers": { + "connected": "Conectado", + "connecting": "Conectando", + "disconnected": "Desconectado", + "pairingInvalid": "Emparejamiento no válido", + "reconnecting": "Reconectando", + "securing": "Protegiendo la conexión" + }, + "routeScreen": { + "openingWorkspace": "Abriendo espacio de trabajo" + }, + "sheets": { + "browser": "Navegador", + "browserUpdateRequired": "Actualiza el escritorio para transmitir el navegador al móvil.", + "chat": "Chat", + "copyAllAndLeave": "Copiar todo y salir", + "copyDraftsFailed": "No se pudieron copiar los borradores", + "copyNotes": "Copiar notas", + "copyNotesFailed": "No se pudieron copiar las notas", + "copyPath": "Copiar ruta", + "discard": "Descartar", + "discardAndLeave": "Descartar y salir", + "discardChanges": "Descartar cambios", + "discardMessage": "¿Reemplazar el borrador del teléfono por la última versión del archivo del escritorio?", + "draftsHint": "Copia o descarta los borradores del teléfono antes de salir.", + "file": "Archivo", + "markdown": "Markdown", + "markdownNote": "Nota en Markdown", + "newBrowser": "Nuevo navegador", + "newTab": "Nueva pestaña", + "notesCopied": "Notas copiadas", + "notesHint": "Elige una sesión de agente a la que enviar las notas actuales.", + "removeShortcutMessage": "¿Eliminar este atajo personalizado?", + "renameTerminal": "Renombrar terminal", + "sendReviewNotes": "Enviar notas de revisión", + "shortcut": "Atajo", + "terminal": "Terminal", + "terminalName": "Nombre de la terminal", + "unsavedMarkdown": "Cambios en Markdown sin guardar", + "urlPrompt": "Introduce una URL o deja el campo en blanco para abrir una pestaña nueva." + }, + "sideRail": { + "backToWorktrees": "Volver a los worktrees" + }, + "sourceControlLaunchAgent": { + "noEnabledAgent": "No se detectó ningún agente de IA habilitado en este host de espacio de trabajo.", + "savedAgentUnavailable": "El agente guardado para esta acción no está disponible en el host de este espacio de trabajo." + }, + "structuredComposerCommand": { + "operationUnconfirmed": "No se confirmó la operación de la conversación.", + "removeAttachments": "Quita los archivos adjuntos antes de usar un comando de la sesión de chat." + }, + "structuredSendDelivery": { + "messageNotSent": "Mensaje no enviado" + }, + "tabStrip": { + "checkingCapabilities": "Comprobando las funciones del escritorio. Inténtalo de nuevo en un momento.", + "newTab": "Nueva pestaña", + "quickCommandsUpdateRequired": "Actualiza el escritorio para usar los comandos rápidos.", + "tabStatus": "{{title}}, {{status}}" + }, + "terminalActionSheetActions": { + "clearTerminal": "Limpiar terminal", + "rename": "Renombrar", + "switchToDesktop": "Cambiar al modo escritorio", + "switchToPhone": "Cambiar al modo teléfono" + }, + "terminalInputActions": { + "attachFileHint": "Mantén pulsado para adjuntar un archivo en su lugar.", + "attachPhoto": "Adjuntar una foto", + "sendingImage": "Enviando imagen", + "startDictation": "Iniciar dictado de voz", + "startingDictation": "Iniciando dictado de voz", + "stopDictation": "Detener dictado de voz", + "transcribingDictation": "Transcribiendo dictado de voz" + }, + "terminalLiveInputStatus": { + "listening": "Escuchando", + "liveInput": "Entrada en tiempo real", + "preparingMic": "Preparando micrófono", + "processing": "Procesando", + "releaseMicHint": "Suelta el micrófono para detener la grabación", + "startingMic": "Iniciando micrófono", + "stopHint": "Toca el micrófono para parar", + "transcribing": "Transcribiendo en el escritorio", + "uploadingImage": "Subiendo imagen al host", + "uploadingImageTitle": "Subiendo imagen" + }, + "terminalRecords": { + "defaultTitle": "Terminal" + }, + "terminalTabAgent": { + "browser": "Navegador", + "chat": "Chat", + "file": "Archivo", + "markdown": "Markdown", + "newBrowser": "Nuevo navegador", + "terminal": "Terminal" + }, + "terminalViewportResubscribe": { + "fitFailed": "No se pudo ajustar la terminal a esta pantalla" + }, + "useAccessorySelection": { + "copyFailed": "No se pudo copiar", + "selectionCleared": "Selección borrada (fuera del búfer tras el desplazamiento)" + }, + "useAttachmentInputLeaseGate": { + "reconnecting": "No se pudo adjuntar (reconectando)" + }, + "useContentCreateActions": { + "browserCommandFailed": "No se pudo ejecutar el comando del navegador", + "browserCreateFailed": "No se pudo crear el navegador", + "browserUnavailable": "La página del navegador aún no está disponible.", + "browserUpdateRequired": "Actualiza Orca en tu equipo para transmitir el navegador al móvil", + "invalidUrl": "Introduce una URL válida", + "noteCreateFailed": "No se pudo crear la nota de Markdown", + "untitledNoteCreateFailed": "No se pudo crear la nota de Markdown sin título" + }, + "useDiffComments": { + "copyNotesFailed": "No se pudieron copiar las notas", + "deleteNoteFailed": "No se pudo eliminar la nota", + "noteAdded": "Nota añadida", + "notesCopied": "Notas copiadas", + "saveNoteFailed": "No se pudo guardar la nota", + "saveNotesFailed": "No se pudieron guardar las notas de revisión", + "waitingForDesktop": "Esperando al escritorio…" + }, + "useDiffReviewCommentActions": { + "saveFailed": "No se pudo guardar la revisión", + "saveStateFailed": "No se pudo guardar el estado de la revisión", + "waitingForDesktop": "Esperando al escritorio…" + }, + "useDiffReviewController": { + "loadFailed": "No se pudo cargar la revisión", + "missingWorktree": "Falta el worktree", + "waitingForDesktop": "Esperando al escritorio…" + }, + "useDiffReviewDiffLoading": { + "loadFailed": "No se pudo cargar el diff", + "waitingForDesktop": "Esperando al escritorio…" + }, + "useDiffReviewGitActions": { + "actionFailed": "No se pudo realizar la acción de control de código fuente", + "reviewedFilesStaged_many": "{{count}} archivos revisados agregados al stage", + "reviewedFilesStaged_one": "{{count}} archivo revisado agregado al stage", + "reviewedFilesStaged_other": "{{count}} archivos revisados agregados al stage", + "stageResults": "Agregados al stage: {{staged}}; fallidos: {{failed}}", + "waitingForDesktop": "Esperando al escritorio…" + }, + "useDiffReviewInteractions": { + "openFailed": "No se pudo abrir en la sesión" + }, + "useDiffReviewSendActions": { + "copyFailed": "No se pudieron copiar las notas de revisión", + "inputLocked": "La entrada de la terminal está bloqueada", + "loadSessionsFailed": "No se pudieron cargar las sesiones de agentes", + "notesCopied": "Notas de revisión copiadas", + "notesSent": "Notas de revisión enviadas", + "notesSentWithWarning": "Notas de revisión enviadas. {{warning}}", + "promptNotSent": "El agente se inició, pero no se enviaron las notas. Usa Copiar notas para pegarlas.", + "sendFailed": "No se pudieron enviar las notas", + "startingAgent": "Iniciando un agente…", + "waitingForDesktop": "Esperando al escritorio…" + }, + "useImageAttachment": { + "attachFailed": "No se pudo adjuntar", + "disconnected": "No se pudo adjuntar (desconectado)", + "imageTooLarge": "Imagen demasiado grande para adjuntarla", + "permissionDenied": "Acceso a las fotos denegado" + }, + "useMarkdownActions": { + "copyFailed": "No se pudo copiar", + "copyPathFailed": "No se pudo copiar la ruta", + "markdownTitle": "Markdown", + "pathCopied": "Ruta copiada", + "saveFailed": "No se pudo guardar", + "saved": "Guardado" + }, + "useNativeChatDictation": { + "insertFailed": "No se pudo insertar el dictado", + "insertFailedCopied": "No se pudo insertar el dictado, así que se copió al portapapeles.", + "inserted": "Dictado insertado" + }, + "useNativeChatStop": { + "stopNotSent": "No se envió la orden de detener", + "stopUnconfirmed": "Detención no confirmada — revisa el chat antes de reintentar", + "terminalNotReady": "No se envió la orden de detener (el terminal no está listo)" + }, + "usePanelRouteActions": { + "checkHostHint": "Comprueba la conexión con el host", + "copyNotesHint": "Copia las notas en su lugar", + "detectingAgents": "Detectando agentes", + "newSessionHint": "Nueva sesión de agente", + "noEnabledAgents": "No hay agentes habilitados", + "otherServerAgents": "Agentes en otro servidor", + "pairServerHint": "Empareja ese servidor directamente", + "presetsUnavailable": "Preajustes de agentes no disponibles" + }, + "usePrAiTriage": { + "launchFailed": "No se pudo iniciar el agente", + "waitingForDesktop": "Esperando al escritorio…" + }, + "usePrCommentActions": { + "actionFailed": "No se pudo realizar la acción sobre el comentario" + }, + "usePrTitleAction": { + "disconnected": "Sin conexión con el escritorio.", + "updateFailed": "No se pudo actualizar el título." + }, + "useQuickCommands": { + "loadFailed": "No se pudieron cargar los comandos rápidos", + "saveFailed": "Error al guardar el comando rápido" + }, + "useSessionTerminalSendActions": { + "inputTooLarge": "Entrada demasiado grande (máx. 256 KiB)" + }, + "useStartup": { + "wakeAgents": "Abre Orca en el host para reanudar los agentes en reposo." + }, + "useTerminalCreateActions": { + "createFailed": "No se pudo crear el terminal", + "forkBusy": "Se está abriendo otro terminal. Espera y vuelve a intentar abrir el chat de {{agentName}}.", + "forkUnavailable": "Abre el chat de {{agentName}} en un host conectado y actualizado e inténtalo de nuevo.", + "inputLocked": "La entrada del terminal está bloqueada por otro cliente.", + "notesSent": "Notas enviadas", + "sendNotesError": "No se pudieron enviar las notas", + "sendNotesFailed": "No se pudieron enviar las notas", + "terminalTitle": "Terminal" + }, + "useTerminalInput": { + "clearFailed": "No se pudo limpiar el terminal", + "cleared": "Terminal limpiado" + }, + "useTerminalPaste": { + "clipboardReadFailed": "No se pudo leer el portapapeles", + "disconnected": "No se pudo pegar (sin conexión)", + "imageTooLarge": "Imagen demasiado grande para pegarla", + "pasteFailed": "No se pudo pegar", + "pasteTooLarge": "Contenido demasiado grande para pegarlo (máx. 256 KiB)", + "terminalChanged": "Pegado cancelado (se cambió de terminal)" + }, + "worktreeLabel": { + "fallback": "Worktree" + } + }, "settings": { "aboutScreen": { "openLinkFailed": "No se pudo abrir el enlace. Vuelve a intentarlo.", @@ -1470,6 +2589,193 @@ "updateFailed": "No se pudo actualizar" } }, + "sourceControl": { + "actions": { + "commits": "Commits", + "createPr": "Crear PR", + "pushAndCreatePr": "Push y crear PR", + "switchBranch": "Cambiar de rama" + }, + "commitDropdown": { + "checkOutBranchToPush": "Haz checkout de una rama antes de hacer push de commits", + "checkOutBranchToSync": "Haz checkout de una rama antes de sincronizar commits", + "checkingBranchStatus": "Comprobando el estado de la rama…", + "checkingPrStatus": "Comprobando el estado del PR…", + "commit": "Commit", + "commitAndForcePush": "Hacer commit de los cambios staged y force push con lease", + "commitAndPush": "Hacer commit de los cambios staged y push", + "commitAndTryPush": "Hacer commit de los cambios staged e intentar hacer push", + "commitForcePush": "Commit y Force Push", + "commitPush": "Commit y push", + "commitStagedChanges": "Hacer commit de los cambios staged", + "commitSync": "Commit y sincronizar", + "commitThenPullPush": "Haz commit y luego pull y push", + "enterMessage": "Ingresa un mensaje de commit para hacer commit", + "linkedReviewTargetUnavailable": "El destino de la rama de review vinculada no está disponible", + "prAlreadyMerged": "El PR ya está mergeado", + "publishBranchFirstToPush": "Publica la rama primero para hacer push de commits", + "publishBranchFirstToSync": "Publica la rama primero para sincronizar commits", + "resolveConflicts": "Resuelve los conflictos antes de hacer commit", + "stageFile": "Haz stage de al menos un archivo para crear un commit", + "useCommitAndForcePush": "Usa Commit y Force Push — el remoto solo tiene copias antiguas de commits locales" + }, + "createPrAction": { + "createReview": "Crear {{titleLabel}}" + }, + "dropdownCounts": { + "fastForwardCount_many": "Hacer fast-forward de {{count}} commits", + "fastForwardCount_one": "Hacer fast-forward de {{count}} commit", + "fastForwardCount_other": "Hacer fast-forward de {{count}} commits", + "pullCount_many": "Hacer pull de {{count}} commits", + "pullCount_one": "Hacer pull de {{count}} commit", + "pullCount_other": "Hacer pull de {{count}} commits", + "pushCount_many": "Hacer push de {{count}} commits", + "pushCount_one": "Hacer push de {{count}} commit", + "pushCount_other": "Hacer push de {{count}} commits", + "remoteBranchFallback": "la rama remota", + "syncCounts": "Pull de {{behind}}, push de {{ahead}}", + "titleForcePushOlderCopiesBranch": "El remoto solo tiene copias antiguas de commits locales. Haz force push con lease de esta rama para actualizar {{upstreamName}}.", + "titleForcePushOlderCopiesCount_many": "El remoto solo tiene copias antiguas de commits locales. Haz force push con lease de {{count}} commits de la rama para actualizar {{upstreamName}}.", + "titleForcePushOlderCopiesCount_one": "El remoto solo tiene copias antiguas de commits locales. Haz force push con lease de {{count}} commit de la rama para actualizar {{upstreamName}}.", + "titleForcePushOlderCopiesCount_other": "El remoto solo tiene copias antiguas de commits locales. Haz force push con lease de {{count}} commits de la rama para actualizar {{upstreamName}}.", + "titleManualForcePushReplaceRemote_many": "Haz force push con lease de {{count}} commits locales para actualizar {{upstreamName}} y reemplazar los commits que solo están en el remoto.", + "titleManualForcePushReplaceRemote_one": "Haz force push con lease de {{count}} commit local para actualizar {{upstreamName}} y reemplazar los commits que solo están en el remoto.", + "titleManualForcePushReplaceRemote_other": "Haz force push con lease de {{count}} commits locales para actualizar {{upstreamName}} y reemplazar los commits que solo están en el remoto.", + "titleManualForcePush_many": "Haz force push con lease de {{count}} commits locales para actualizar {{upstreamName}}.", + "titleManualForcePush_one": "Haz force push con lease de {{count}} commit local para actualizar {{upstreamName}}.", + "titleManualForcePush_other": "Haz force push con lease de {{count}} commits locales para actualizar {{upstreamName}}.", + "titleUnpublishedForcePushBranch": "Haz force push con lease de esta rama y configura un upstream si es necesario.", + "titleUnpublishedForcePushCount_many": "Haz force push con lease de {{count}} commits de la rama y configura un upstream si es necesario.", + "titleUnpublishedForcePushCount_one": "Haz force push con lease de {{count}} commit de la rama y configura un upstream si es necesario.", + "titleUnpublishedForcePushCount_other": "Haz force push con lease de {{count}} commits de la rama y configura un upstream si es necesario." + }, + "hostedReview": { + "mrCopy": { + "reviewLabel": "solicitud de fusión", + "shortLabel": "MR", + "titleLabel": "Solicitud de fusión" + }, + "prCopy": { + "reviewLabel": "pull request", + "shortLabel": "PR", + "titleLabel": "Solicitud de extracción" + } + }, + "hostedReviewCreate": { + "pushSucceeded": "El push se completó, pero no se pudo crear {{shortLabel}}: {{error}}" + }, + "prCreate": { + "authRequired": "Autentícate antes de crear una {{reviewLabel}}.", + "baseNotOnRemote": "Haz push de la rama base antes de crear una {{reviewLabel}}.", + "createdNotRefreshed": "{{titleLabel}} creada, pero Orca aún no ha podido actualizarla.", + "defaultBranch": "Cambia a una rama de características antes de crear una {{reviewLabel}}.", + "detachedHead": "Selecciona una rama antes de crear una {{reviewLabel}}.", + "dirty": "Haz commit de los cambios antes de crear una {{reviewLabel}}.", + "existing": "{{titleLabel}} ya está abierta.", + "existingNumber": "{{titleLabel}} #{{number}} ya está abierta.", + "existingReview": "Esta rama ya tiene {{reviewLabel}}.", + "forkHeadUnsupported": "No se admite crear una {{reviewLabel}} desde este fork.", + "lookupUnconfirmed": "Orca no pudo confirmar si esta rama ya tiene {{reviewLabel}}. Vuelve a intentarlo en un momento.", + "needsSync": "Sincroniza esta rama antes de crear una {{reviewLabel}}.", + "noUpstream": "Publica los commits antes de crear una {{reviewLabel}}.", + "notReady": "Esta rama aún no está lista para crear una {{reviewLabel}}.", + "unsupportedMergeRequests": "No se pueden crear merge requests en este repositorio.", + "unsupportedPullRequests": "No se pueden crear pull requests en este repositorio." + }, + "primaryAction": { + "forcePushFromDesktop": "Haz force push de la rama de esta pull request desde el escritorio" + }, + "remoteDropdown": { + "branchAlreadyPublished": "La rama ya está publicada", + "branchUpToDate": "La rama está actualizada", + "checkOutBranchToFastForward": "Haz checkout de una rama antes de hacer fast-forward", + "checkOutBranchToForcePush": "Haz checkout de una rama antes de hacer force push de commits", + "checkOutBranchToPublish": "Haz checkout de una rama antes de publicar commits", + "checkOutBranchToPull": "Haz checkout de una rama antes de hacer pull de commits", + "checkOutBranchToPush": "Haz checkout de una rama antes de hacer push de commits", + "checkOutBranchToSync": "Haz checkout de una rama antes de sincronizar commits", + "checkingBranchStatus": "Comprobando el estado de la rama…", + "checkingPrStatus": "Comprobando el estado del PR…", + "fastForward": "Fast-forward", + "fetch": "Fetch", + "fetchTitle": "Fetch del remoto sin hacer merge", + "forcePush": "Force Push", + "linkedReviewBranchExists": "La rama de review vinculada ya existe", + "linkedReviewTargetUnavailable": "El destino de la rama de review vinculada no está disponible", + "nothingNewToFastForward": "No hay nada nuevo para hacer fast-forward — el remoto solo tiene copias antiguas de commits locales", + "nothingNewToPull": "No hay nada nuevo para hacer pull — el remoto solo tiene copias antiguas de commits locales", + "nothingToFastForward": "No hay nada para hacer fast-forward", + "nothingToForcePush": "No hay nada para hacer force push", + "nothingToForcePushTo": "No hay nada para hacer force push a {{upstreamName}}", + "nothingToPull": "No hay nada para hacer pull", + "nothingToPush": "No hay nada para hacer push", + "nothingToPushTo": "No hay nada para hacer push a {{upstreamName}}", + "prAlreadyMerged": "El PR ya está mergeado", + "publishBranch": "Publicar rama", + "publishBranchFirstToFastForward": "Publica la rama primero para hacer fast-forward", + "publishBranchFirstToPull": "Publica la rama primero para hacer pull de commits", + "publishBranchFirstToSync": "Publica la rama primero para sincronizar commits", + "publishLinkedReview": "Review vinculada", + "publishNoBranch": "Sin rama", + "publishPrStatus": "Estado del PR", + "publishToOrigin": "Publicar esta rama en origin", + "pull": "Pull", + "push": "Push", + "pushAndSetUpstream": "Haz push de esta rama y configura un upstream si es necesario", + "pushMayNeedSync": "Haz push de los commits locales; git puede requerir sincronizar primero", + "pushToLinkedReview": "Haz push de actualizaciones a la rama de review vinculada", + "rebaseChooseRemoteBase": "Elige una rama base remota desde la que hacer rebase", + "rebaseDirtyHint": "Intenta hacer rebase; git puede requerir hacer commit o stash de los cambios locales primero", + "rebaseFromBase": "Rebase desde la base", + "rebaseFromNamedBase": "Rebase desde {{baseRef}}", + "rebaseWithLatestFrom": "Hacer rebase de la rama actual con los commits más recientes de {{baseRef}}", + "sync": "Sincronizar", + "tryFastForwardPull": "Intenta un pull con fast-forward; git puede rechazar los commits locales", + "tryRegularPush": "Intenta un push normal; git puede requerir force push", + "useForcePushToSync": "Usa Force Push — el remoto solo tiene copias antiguas de commits locales" + } + }, + "terminal": { + "keyDefinitions": { + "accessoryArrowDown": "Flecha abajo", + "accessoryArrowLeft": "Flecha izquierda", + "accessoryArrowRight": "Flecha derecha", + "accessoryArrowUp": "Flecha arriba", + "arrowDown": "Flecha abajo", + "arrowLeft": "Flecha izquierda", + "arrowRight": "Flecha derecha", + "arrowUp": "Flecha arriba", + "clearLineBeforeCursor": "Borrar hasta el inicio de la línea", + "clearScreen": "Limpiar pantalla", + "deleteWordBackward": "Borrar palabra anterior", + "endOfLine": "Fin de línea", + "forwardDelete": "Borrar hacia delante", + "interruptTerminal": "Interrumpir la terminal", + "reverseSearch": "Búsqueda inversa", + "sendEof": "Enviar EOF", + "startOfLine": "Inicio de línea", + "suspendProcess": "Suspender proceso" + }, + "webView": { + "documentStartFailed": "no se pudo iniciar el documento de la terminal - {{message}}", + "httpError": "Error HTTP en la WebView de la terminal", + "loadFailed": "No se pudo cargar la WebView de la terminal", + "renderProcessEnded": "Finalizó el proceso de renderizado de la WebView de la terminal" + }, + "webviewEngineErrorState": { + "code": "código {{code}}", + "reload": "Recargar", + "rendererCrashed": "el proceso de renderizado se cerró inesperadamente", + "status": "estado {{statusCode}}", + "title": "No se pudo cargar la terminal" + }, + "webviewNotificationDispatch": { + "unknownError": "Error desconocido de la terminal" + }, + "webviewReadyWatchdog": { + "initializeFailed": "La terminal no se inicializó - la vista de la terminal no indicó que estuviera lista" + } + }, "webShell": { "shellStateScreen": { "checkingHost": "Comprobando el host", @@ -1484,6 +2790,18 @@ "tryAgain": "Reintentar" } }, + "workspace": { + "branchChoice": { + "automaticName": "Nombre automático", + "existing": "Rama existente", + "existingHint": "Los nuevos commits se añaden a esta rama.", + "hostUpdateRequired": "Actualiza Orca en el host del espacio de trabajo para reutilizar una rama o crear explícitamente una rama independiente.", + "new": "Nueva rama", + "newHint": "Crear una nueva rama local", + "newName": "Nombre de la nueva rama", + "title": "Rama para este árbol de trabajo" + } + }, "worktree": { "agentRowDisplay": { "blocked": "Bloqueado", @@ -1526,16 +2844,5 @@ "inReview": "En revisión", "pinned": "Fijados" } - }, - "workspace": { - "branchChoice": { - "title": "Rama para este árbol de trabajo", - "existing": "Rama existente", - "existingHint": "Los nuevos commits se añaden a esta rama.", - "new": "Nueva rama", - "newHint": "Crear una nueva rama local", - "newName": "Nombre de la nueva rama", - "automaticName": "Nombre automático" - } } } diff --git a/mobile/src/i18n/locales/fr.json b/mobile/src/i18n/locales/fr.json index 4091f6cac84..c54c8338666 100644 --- a/mobile/src/i18n/locales/fr.json +++ b/mobile/src/i18n/locales/fr.json @@ -143,6 +143,391 @@ "title": "Terminal" } }, + "browser": { + "addressField": { + "urlPlaceholder": "URL" + }, + "pane": { + "invalidUrl": "Saisissez une URL valide." + }, + "paneView": { + "dialogTitle": "Boîte de dialogue du navigateur", + "forward": "Suivant", + "keyboardPlaceholder": "Saisir du texte sur la page…", + "reload": "Recharger", + "sendText": "Envoyer le texte au navigateur" + }, + "pointerModifiers": { + "clickModifier": "Modificateur de clic {{key}}" + }, + "useBrowserCommands": { + "deliveryUnconfirmed": "Envoi non confirmé — vérifiez la page avant de réessayer", + "dialogAnswerFailed": "Cette réponse n'est pas parvenue à la page.", + "sent": "Envoyé" + }, + "viewModeSwitch": { + "mobileView": "Afficher la version mobile du site", + "webView": "Afficher la version pour ordinateur du site" + } + }, + "chat": { + "ask": { + "answerPlaceholder": "Saisissez votre réponse", + "next": "Suivant", + "other": "Autre…", + "skip": "Ignorer", + "step": "Étape {{step}}", + "submit": "Valider" + }, + "askRow": { + "asked": "Questions posées :", + "awaiting": "En attente de la saisie de l'utilisateur :", + "awaitingUnnamed": "En attente de la saisie de l'utilisateur", + "questionCount": "{{value0}} questions" + }, + "composer": { + "addToMessage": "Ajouter au message", + "attachImage": "Joindre une image", + "cancelEditing": "Annuler la modification", + "placeholder": "Posez votre question, @ pour référencer des fichiers, / pour les commandes", + "saveEditedMessage": "Enregistrer le message modifié", + "sendMessage": "Envoyer le message" + }, + "composerAttachments": { + "attachedImage": "Image jointe", + "file": "Fichier", + "fileMeta": "{{type}} · {{size}}", + "removeFile": "Retirer {{name}}", + "removeImage": "Retirer l'image", + "uploadingFile": "Envoi de {{name}}" + }, + "composerPlaceholder": { + "prompt": "Posez votre question, @ pour référencer des fichiers, / pour les commandes", + "reconnecting": "Reconnexion…", + "waitingForTerminal": "En attente du terminal…" + }, + "diffCard": { + "copyDiff": "Copier la différence", + "diffGap": "Lignes non affichées", + "diffTruncated": "Diff tronqué" + }, + "hostStatus": { + "continuedOutside": "Poursuivi en dehors de ce chat.", + "continuedOutsideRewound": "Poursuivi en dehors de ce chat. Les réponses précédentes ne font plus partie de l'historique de cette conversation.", + "historyItemTooLarge": "Cette partie du chat était trop volumineuse pour être affichée.", + "historyRepaired": "Une partie de l'historique de ce chat n'a pas pu être chargée." + }, + "message": { + "copyMessage": "Copier le message" + }, + "messageActionsSheet": { + "clipboardRejected": "Le presse-papiers a refusé le texte.", + "copyFailed": "Échec de la copie", + "copyMessage": "Copier le message", + "selectText": "Sélectionner le texte" + }, + "nativeChatImageViewer": { + "closeImage": "Fermer l'image" + }, + "nativeChatProse": { + "attachedImage": "Image jointe", + "image": "image", + "openImageHint": "Ouvre l'image à sa taille réelle" + }, + "nativeChatToolLine": { + "added": "A ajouté", + "copyCommand": "Copier la commande", + "deleted": "A supprimé", + "durationHours": "{{hours}} h {{minutes}} min {{seconds}} s", + "durationMilliseconds": "{{milliseconds}} ms", + "durationMinutes": "{{minutes}} min {{seconds}} s", + "durationSeconds": "{{seconds}} s", + "edited": "A modifié", + "editing": "Modification", + "exitCode": "code de sortie {{exitCode}}", + "fetched": "A récupéré", + "fetching": "Récupération", + "listed": "A listé", + "listing": "Liste", + "ran": "A exécuté", + "read": "A lu", + "reading": "Lecture", + "renamed": "A renommé", + "result": "Résultat", + "running": "En cours", + "searched": "A recherché", + "searchedWeb": "A recherché sur le web", + "searching": "Recherche", + "searchingWeb": "Recherche sur le web", + "subagent": "Sous-agent", + "triedEdit": "A tenté de modifier", + "triedFetch": "A tenté de récupérer", + "triedList": "A tenté de lister", + "triedRead": "A tenté de lire", + "triedSearch": "A tenté de rechercher", + "triedSearchWeb": "A tenté de rechercher sur le web" + }, + "nativeChatUserMessageFold": { + "loadFailed": "Afficher le message complet — chargement impossible, touchez pour réessayer", + "showFullMessage": "Afficher le message complet", + "showLess": "Afficher moins" + }, + "nativeChatVisual": { + "defaultTitle": "Visualisation", + "openFullscreen": "Ouvrir {{title}} en plein écran", + "retryHint": "Tente à nouveau de la charger", + "unavailable": "Visualisation indisponible" + }, + "notice": { + "compaction": "Contexte compacté", + "contextCleared": "Contexte effacé", + "plan": "Plan", + "stoppedManually": "Arrêté manuellement" + }, + "orcaStop": { + "canContinue": "Vous pouvez continuer dans cette conversation.", + "crash": "Orca sur {{machine}} s'est arrêté de manière inattendue pendant que cette réponse était en cours.", + "quit": "Orca sur {{machine}} a été fermé pendant que cette réponse était en cours.", + "remote": "Orca sur {{machine}} s'est arrêté pendant que cette réponse était en cours.", + "unnamedServer": "le serveur distant", + "update": "Orca sur {{machine}} a redémarré pour une mise à jour pendant que cette réponse était en cours." + }, + "permission": { + "needsAccessTo": "Accès nécessaire à : ", + "newerVersionRequired": "Cette demande nécessite une version plus récente d'Orca.", + "planFile": "Fichier du plan : {{filePath}}", + "reason": "Motif : " + }, + "promptCard": { + "answerInTerminal": "Répondre dans le terminal", + "terminalExplanation": "Répondez à cette question OMP dans le terminal." + }, + "promptStrip": { + "expand": "Développer" + }, + "question": { + "otherReplyPlaceholder": "Ou saisissez une réponse…", + "replyPlaceholder": "Saisissez votre réponse…", + "sendReply": "Envoyer la réponse", + "submit": "Valider", + "submitCount": "Valider ({{count}})", + "submitSelectedOptions": "Valider les options sélectionnées" + }, + "queuedMessages": { + "clearAll": "Tout effacer", + "deleteAll": "Supprimer tous les messages en file d'attente", + "editMessage": "Modifier le message", + "pausedState": "En pause", + "queuedState": "En file d'attente", + "resume": "Reprendre", + "resumeSending": "Reprendre l'envoi des messages en file d'attente", + "title": "Messages en file d'attente" + }, + "receipt": { + "cancelled": "Annulé", + "cancelledBy": "Annulé sur {{device}}", + "emptyAnswer": "Réponse vide", + "resolved": "Résolu", + "resolver": "Répondu sur {{device}}", + "unavailable": "Réponse sélectionnée indisponible" + }, + "sessionOptionCopy": { + "availableAfterSessionStarts": "Disponible après le démarrage de la session.", + "chooseInAgentPicker": "Choisir dans le sélecteur d'agents…", + "effort": "Effort", + "fast": "Rapide", + "fastMode": "Mode rapide", + "high": "Élevé", + "low": "Faible", + "max": "Max", + "medium": "Moyen", + "mediumShort": "Moy.", + "minimal": "Minimal", + "minimalShort": "Min.", + "model": "Modèle", + "notSet": "Non défini", + "off": "Désactivé", + "on": "Activé", + "permissions": "Autorisations", + "sessionOptions": "Options de session", + "setWhenSessionStarts": "Défini au démarrage de la session.", + "speed": "Vitesse", + "standard": "Standard", + "thinking": "Réflexion", + "toggleOption": "Basculer {{value0}}", + "ultra": "Ultra", + "xhigh": "Extra élevé", + "xhighShort": "Extra" + }, + "sessionOptionPickers": { + "agentWorking": "L'agent travaille", + "backToModels": "Retour aux modèles", + "closePicker": "Fermer le sélecteur", + "dispatchUnconfirmed": "Envoyé à l'agent — non confirmé", + "modelFullAccessLabel": "Modèle, {{value}}, accès complet", + "modelLabel": "Modèle, {{value}}", + "permissions": "Autorisations", + "permissionsLabel": "Autorisations, {{value}}", + "settingsLocked": "Les paramètres du modèle seront déverrouillés à la fin de ce tour.", + "workingAnnouncement": "L'agent travaille. Les paramètres du modèle seront déverrouillés à la fin de ce tour." + }, + "sessionOptionRows": { + "defaultMarker": "Par défaut", + "notReportedMarker": "Non communiqué" + }, + "sessionOptionSegments": { + "dispatchUnconfirmed": "Envoyé à l'agent — non confirmé" + }, + "stopButton": { + "stopAgent": "Arrêter l'agent", + "stoppingState": "Arrêt en cours…" + }, + "streamFrame": { + "streamEnded": "Flux de transcription terminé", + "streamFailed": "Échec du flux de transcription" + }, + "taskList": { + "added": "Tâche ajoutée : {{task}}", + "completed": "Terminée", + "empty": "Aucune tâche", + "finished": "Tâche terminée : {{task}}", + "inProgress": "En cours", + "pending": "En attente", + "progress": "{{completed}} sur {{total}} tâches terminées", + "removed": "Tâche supprimée : {{task}}", + "reset": "Tâche en attente : {{task}}", + "showAll": "Liste complète des tâches", + "started": "Tâche démarrée : {{task}}", + "taskStatus": "{{status}} : {{task}}", + "title": "Tâches", + "unchanged": "Tâches inchangées", + "updated": "Tâche mise à jour : {{task}}" + }, + "toolActivity": { + "count_many": "{{count}} appels d'outil", + "count_one": "{{count}} appel d'outil", + "count_other": "{{count}} appels d'outil", + "failedCallsLabel": "Appels d'outil ayant échoué : {{value0}}", + "failedCount": "{{value0}} en échec", + "interruptedCallsLabel": "Appels d'outil interrompus : {{value0}}", + "interruptedCount": "{{value0}} interrompu(s)", + "moreCalls": "+{{value0}} autres", + "result": "Résultat", + "runningCommand": "Exécution de la commande", + "runningNamed": "Exécution de {{toolName}}", + "runningNamedPreview": "Exécution de {{toolName}} {{preview}}", + "runningPreview": "Exécution de {{preview}}" + }, + "toolRun": { + "list": "{{value0}} et {{value1}}", + "liveBrowserOnce": "Utilisation du navigateur", + "liveBrowserTimes_many": "{{count}} utilisations du navigateur en cours", + "liveBrowserTimes_one": "{{count}} utilisation du navigateur en cours", + "liveBrowserTimes_other": "{{count}} utilisations du navigateur en cours", + "liveFileChange_many": "Modification de {{count}} fichiers", + "liveFileChange_one": "Modification de {{count}} fichier", + "liveFileChange_other": "Modification de {{count}} fichiers", + "liveListFiles_many": "Listage de {{count}} répertoires", + "liveListFiles_one": "Listage de {{count}} répertoire", + "liveListFiles_other": "Listage de {{count}} répertoires", + "liveMcpToolCall_many": "Utilisation de {{count}} intégrations", + "liveMcpToolCall_one": "Utilisation de {{count}} intégration", + "liveMcpToolCall_other": "Utilisation de {{count}} intégrations", + "liveOther_many": "Utilisation de {{count}} outils", + "liveOther_one": "Utilisation de {{count}} outil", + "liveOther_other": "Utilisation de {{count}} outils", + "liveRead_many": "Lecture de {{count}} fichiers", + "liveRead_one": "Lecture de {{count}} fichier", + "liveRead_other": "Lecture de {{count}} fichiers", + "liveSearch_many": "{{count}} recherches en cours", + "liveSearch_one": "{{count}} recherche en cours", + "liveSearch_other": "{{count}} recherches en cours", + "liveSubAgentActivity_many": "Exécution de {{count}} agents", + "liveSubAgentActivity_one": "Exécution de {{count}} agent", + "liveSubAgentActivity_other": "Exécution de {{count}} agents", + "liveTodoListOnce": "Mise à jour du plan", + "liveTodoListTimes_many": "{{count}} mises à jour du plan en cours", + "liveTodoListTimes_one": "{{count}} mise à jour du plan en cours", + "liveTodoListTimes_other": "{{count}} mises à jour du plan en cours", + "liveUnknown_many": "Exécution de {{count}} commandes", + "liveUnknown_one": "Exécution de {{count}} commande", + "liveUnknown_other": "Exécution de {{count}} commandes", + "liveWebSearch_many": "{{count}} recherches sur le Web en cours", + "liveWebSearch_one": "{{count}} recherche sur le Web en cours", + "liveWebSearch_other": "{{count}} recherches sur le Web en cours", + "pair": "{{value0}} et {{value1}}", + "separator": ", ", + "settledBrowserOnce": "Navigateur utilisé", + "settledBrowserTimes_many": "Navigateur utilisé {{count}} fois", + "settledBrowserTimes_one": "Navigateur utilisé {{count}} fois", + "settledBrowserTimes_other": "Navigateur utilisé {{count}} fois", + "settledFileChange_many": "{{count}} fichiers modifiés", + "settledFileChange_one": "{{count}} fichier modifié", + "settledFileChange_other": "{{count}} fichiers modifiés", + "settledListFiles_many": "{{count}} répertoires listés", + "settledListFiles_one": "{{count}} répertoire listé", + "settledListFiles_other": "{{count}} répertoires listés", + "settledMcpToolCall_many": "{{count}} intégrations utilisées", + "settledMcpToolCall_one": "{{count}} intégration utilisée", + "settledMcpToolCall_other": "{{count}} intégrations utilisées", + "settledOther_many": "{{count}} outils utilisés", + "settledOther_one": "{{count}} outil utilisé", + "settledOther_other": "{{count}} outils utilisés", + "settledRead_many": "{{count}} fichiers lus", + "settledRead_one": "{{count}} fichier lu", + "settledRead_other": "{{count}} fichiers lus", + "settledSearch_many": "{{count}} recherches effectuées", + "settledSearch_one": "{{count}} recherche effectuée", + "settledSearch_other": "{{count}} recherches effectuées", + "settledSubAgentActivity_many": "{{count}} agents exécutés", + "settledSubAgentActivity_one": "{{count}} agent exécuté", + "settledSubAgentActivity_other": "{{count}} agents exécutés", + "settledTodoListOnce": "Plan mis à jour", + "settledTodoListTimes_many": "Plan mis à jour {{count}} fois", + "settledTodoListTimes_one": "Plan mis à jour {{count}} fois", + "settledTodoListTimes_other": "Plan mis à jour {{count}} fois", + "settledUnknown_many": "{{count}} commandes exécutées", + "settledUnknown_one": "{{count}} commande exécutée", + "settledUnknown_other": "{{count}} commandes exécutées", + "settledWebSearch_many": "{{count}} recherches sur le Web effectuées", + "settledWebSearch_one": "{{count}} recherche sur le Web effectuée", + "settledWebSearch_other": "{{count}} recherches sur le Web effectuées" + }, + "turnStatus": { + "failedAfter": "Échec après {{value0}}", + "hoursMinutesSeconds": "{{hours}} h {{minutes}} min {{seconds}} s", + "interruptedAfter": "Interrompu après {{value0}}", + "minutesSeconds": "{{minutes}} min {{seconds}} s", + "queueAfterStop": "Mettez un message en file d'attente pour l'exécuter après l'arrêt", + "responding": "L'agent répond", + "seconds_many": "{{count}} s", + "seconds_one": "{{count}} s", + "seconds_other": "{{count}} s", + "sendAfterStop": "Envoyez un message pour l'exécuter après l'arrêt", + "stopping": "Arrêt en cours…", + "thinking": "Réflexion en cours", + "toggleDetails": "Afficher ou masquer les détails du tour", + "workedFor": "A travaillé pendant {{value0}}", + "working": "Traitement…", + "workingFor": "En cours depuis {{value0}}" + }, + "usageLimit": { + "reached": "{{agent}} a atteint sa limite d’utilisation.", + "reachedNamed": "{{agent}} a atteint sa limite {{window}}.", + "resetsAt": "Elle se réinitialise à {{when}}.", + "theAgent": "L'agent", + "windowCredits": "d’utilisation supplémentaire", + "windowSession": "de session", + "windowWeekly": "hebdomadaire", + "windowWeeklyModel": "hebdomadaire du modèle" + }, + "useImageUpload": { + "attachFailed": "Échec de l'ajout", + "disconnected": "Échec de l'ajout (déconnecté)", + "imageTooLarge": "Image trop volumineuse pour être jointe", + "permissionDenied": "Accès aux photos refusé" + } + }, "common": { "back": "Retour", "cancel": "Annuler", @@ -155,6 +540,8 @@ "done": "Terminé", "edit": "Modifier", "ok": "OK", + "open": "Ouvrir", + "refresh": "Actualiser", "remove": "Supprimer", "retry": "Réessayer", "save": "Enregistrer", @@ -769,8 +1156,51 @@ "unexpectedResponse": "Réponse inattendue" }, "webShellUpdateFailureCopy": { + "blockedReason": "Bloqué : {{reason}}.", + "blockedWorkspace": "Espace de travail bloqué.", + "failed": "Écran d'erreur affiché.", "failedAgo": "La dernière mise à jour provenant de {{hostName}} a échoué il y a {{time}} : {{reason}}{{offered}}. {{outcome}}", - "failedJustNow": "La dernière mise à jour provenant de {{hostName}} vient d'échouer : {{reason}}{{offered}}. {{outcome}}" + "failedJustNow": "La dernière mise à jour provenant de {{hostName}} vient d'échouer : {{reason}}{{offered}}. {{outcome}}", + "hostCodeCopy": { + "assetChanged": "une ressource a changé sur l'hôte", + "assetUnknown": "l'hôte n'a pas reconnu une ressource", + "buildChanged": "la version de l'hôte a changé pendant le téléchargement", + "offsetInvalid": "l'hôte a refusé une position de lecture", + "readLimited": "l'hôte a limité les lectures simultanées", + "unavailable": "l'hôte ne dispose d'aucun paquet d'espace de travail" + }, + "nativeRoute": "Écran natif affiché.", + "offeredGeneration": " (génération {{generation}})", + "openedCached": "Retour à la version enregistrée.", + "openedCachedGeneration": "Retour à la version enregistrée (génération {{generation}}).", + "reasonCopy": { + "assetChecksumMismatch": "non-concordance de la somme de contrôle d'une ressource", + "assetEntryChanged": "une ressource ne correspondait plus au manifeste", + "assetNoProgress": "la lecture d'une ressource n'a pas progressé", + "assetOverlong": "une ressource dépassait la taille indiquée dans le manifeste", + "assetShort": "la taille d'une ressource reçue était inférieure à celle déclarée", + "buildChangedMidFetch": "la version de l'hôte a changé pendant le téléchargement", + "cacheWriteFailed": "l'enregistrement du téléchargement sur ce téléphone a échoué", + "chunkMisrouted": "un bloc ne correspondait pas à la ressource ou à la position demandée", + "chunkOversize": "un bloc dépassait la taille autorisée par l'hôte", + "connectionLost": "la connexion a été interrompue", + "fetchStopped": "le téléchargement a été arrêté", + "hostRefused": "l'hôte a refusé la lecture", + "noConnection": "aucune connexion à l'hôte", + "rangeUndecodable": "les données compressées d'une lecture n'ont pas pu être décodées", + "replyUnreadable": "l'hôte a envoyé une réponse illisible pour cette application", + "unrecognisedError": "une erreur non reconnue" + }, + "routeUnavailable": "Écran indiqué comme indisponible.", + "waiting": "L'application a attendu l'hôte.", + "wallCopy": { + "bundleShellTooOld": "cette application est trop ancienne pour le paquet enregistré", + "bundleTooOldForHost": "le paquet enregistré est trop ancien pour l'hôte", + "bundleUnavailable": "l'hôte ne dispose d'aucun paquet d'espace de travail", + "hostTooOldForBundle": "la version de l'hôte est trop ancienne pour le paquet enregistré", + "pageRouteGrantMissing": "cet écran nécessite une version plus récente de l'application", + "pageRouteUnlisted": "l'espace de travail de l'hôte n'inclut pas cet écran" + } } }, "dictation": { @@ -787,6 +1217,95 @@ "updateFailed": "Impossible de mettre à jour les paramètres de dictée" } }, + "files": { + "fileExplorerPanel": { + "backToSession": "Retour à la session", + "closeFiles": "Fermer les fichiers", + "connecting": "Connexion à l'ordinateur…", + "emptyState": "Aucun fichier trouvé", + "loadFailed": "Impossible de charger les fichiers", + "loadInterrupted": "Chargement du dossier interrompu", + "title": "Fichiers", + "truncatedList": "{{worktreeLabel}} - 5 000 premiers éléments affichés", + "waiting": "En attente de l'ordinateur…" + }, + "fileExplorerRow": { + "fileUnavailable": "{{name}} indisponible sur mobile", + "loadFolderFailed": "Impossible de charger le dossier", + "loading": "Chargement…", + "openFolder": "Ouvrir le dossier {{name}}", + "previewFile": "Afficher l'aperçu du fichier {{name}}", + "retryLoading": "Réessayer de charger {{path}}", + "unavailable": "Indisponible sur mobile" + }, + "fileMarkdownPreview": { + "copyLoadedSource": "Copier le code source Markdown chargé", + "copySource": "Copier le code source Markdown", + "viewPreview": "Afficher l'aperçu du rendu Markdown", + "viewSource": "Afficher le code source Markdown" + }, + "fileMediaPreview": { + "downloading": "Téléchargement : {{bytes}} sur {{total}}", + "loadFailed": "Impossible de charger le média", + "loading": "Chargement du média…", + "waiting": "En attente de l'ordinateur…" + }, + "fileMutationOwnership": { + "sshOwnerChanged": "Impossible de vérifier la connexion SSH. Reconnectez l'hôte et réessayez." + }, + "filePreviewBody": { + "emptyFile": "Fichier vide", + "imageLabel": "Image {{title}}" + }, + "filePreviewEditableSource": { + "editorLabel": "Éditeur de {{title}}" + }, + "filePreviewResponse": { + "binaryUnavailable": "Aperçu binaire indisponible", + "bytes": "{{size}} o", + "fileChanged": "Le fichier a été modifié sur l'ordinateur. Rechargez l'aperçu avant d'enregistrer.", + "fileNotFound": "Fichier introuvable", + "fileTooLarge": "Fichier trop volumineux pour l'aperçu sur mobile", + "filesystemUnreachable": "Impossible d'accéder au système de fichiers de l'ordinateur", + "kilobytes": "{{size}} ko", + "loadFailed": "Impossible de charger l'aperçu", + "megabytes": "{{size}} Mo", + "reloadBeforeSaving": "Rechargez l'aperçu avant d'enregistrer.", + "unknownSize": "taille inconnue" + }, + "filePreviewScreen": { + "backToFiles": "Retour aux fichiers", + "chooseWorkspaceFile": "Choisissez un fichier dans cet espace de travail.", + "discard": "Abandonner", + "discardMessage": "Les modifications non enregistrées seront perdues.", + "discardTitle": "Abandonner les modifications ?", + "fileTitle": "Fichier", + "loadFailed": "Impossible de charger l'aperçu", + "loading": "Chargement de l'aperçu…", + "openFailed": "Impossible d'ouvrir le fichier", + "saveArtifact": "Enregistrer l'artefact du terminal", + "saveFailed": "Impossible d'enregistrer le fichier", + "stay": "Rester", + "title": "Aperçu", + "waiting": "En attente de l'ordinateur…" + }, + "filePreviewSourceText": { + "previewLabel": "Aperçu du fichier", + "truncatedNote": "Aperçu tronqué. Taille du fichier : {{size}}." + }, + "markdownPreviewImage": { + "expandFallback": "Développer l'image", + "expandImage": "Développer {{alt}}", + "imageLabel": "Image Markdown", + "loadFailed": "Impossible de charger l'image" + }, + "pdfPreview": { + "open": "Ouvrir le PDF", + "openFailed": "Impossible d'ouvrir ce PDF", + "opening": "Ouverture du PDF…", + "updateRequired": "Mettez à jour Orca Mobile pour ouvrir des PDF." + } + }, "home": { "accountUsageCards": { "sectionHeading": "Utilisation du compte", @@ -1122,12 +1641,10 @@ "noComments": "Aucun commentaire pour le moment.", "noDescription": "Aucune description fournie.", "noHumanComments": "Aucun commentaire humain.", - "resolvedComment_many": "Commentaire de {{author}} résolu ({{count}})", - "resolvedComment_one": "Commentaire de {{author}} résolu", - "resolvedComment_other": "Commentaire de {{author}} résolu ({{count}})", - "resolvedThread_many": "Fil de {{author}} résolu ({{count}})", - "resolvedThread_one": "Fil de {{author}} résolu", - "resolvedThread_other": "Fil de {{author}} résolu ({{count}})", + "resolvedCommentCount": "Commentaire de {{author}} résolu ({{count}})", + "resolvedCommentSingle": "Commentaire de {{author}} résolu", + "resolvedThreadCount": "Fil de {{author}} résolu ({{count}})", + "resolvedThreadSingle": "Fil de {{author}} résolu", "showMore": "Afficher {{count}} de plus", "showMoreOf": "Afficher {{count}} de plus sur {{remaining}}", "title": "Commentaires" @@ -1326,6 +1843,608 @@ "title": "Reviewers" } }, + "session": { + "accessoryKeys": { + "addCustomShortcut": "Ajouter un raccourci personnalisé", + "dismissKeyboard": "Masquer le clavier", + "dismissKeyboardHint": "Masque le clavier virtuel et garde la session de terminal en cours ouverte.", + "paste": "Coller", + "pasteFromClipboard": "Coller depuis le presse-papiers", + "sendKey": "Envoyer {{label}}", + "switchToBufferedInput": "Basculer vers la saisie de commandes en mémoire tampon", + "switchToDesktopMode": "Passer en mode bureau", + "switchToLiveInput": "Basculer vers la saisie directe dans le terminal", + "switchToPhoneMode": "Basculer en mode téléphone" + }, + "activeContent": { + "createTab": "Créer un onglet", + "creatingTab": "Création…", + "fileTitle": "Fichier", + "loadingTerminal": "Chargement du terminal", + "noTabs": "Aucun onglet dans cette session", + "retryLoadingTerminal": "Réessayer de charger le terminal", + "terminalTakingLonger": "Le chargement du terminal prend plus de temps que prévu" + }, + "agentChildRow": { + "agent": "Agent", + "days_many": "{{count}} j", + "days_one": "{{count}} j", + "days_other": "{{count}} j", + "ended": "Terminé", + "hours_many": "{{count}} h", + "hours_one": "{{count}} h", + "hours_other": "{{count}} h", + "minutes_many": "{{count}} m", + "minutes_one": "{{count}} m", + "minutes_other": "{{count}} m", + "noRecentUpdate": "Aucune mise à jour récente", + "noUpdate": "Aucune mise à jour depuis {{value0}}" + }, + "agentMessageAttribution": { + "from": "De la part de {{sender}}", + "fromMore": "De la part de {{sender}} +{{count}}", + "fromUnnamed": "D'un agent", + "messageFrom": "Message envoyé par {{sender}}", + "messageFromMore": "Message envoyé par {{sender}} +{{count}}", + "messageFromUnnamed": "Message d'un agent", + "unnamedSender": "un agent" + }, + "aiVaultResumeLaunch": { + "createFailed": "Impossible de créer le terminal", + "inputLocked": "La saisie du terminal est verrouillée", + "sendFailed": "Impossible d'envoyer la commande de reprise" + }, + "aiVaultResumePreparation": { + "prepareFailed": "Impossible de préparer cette ancienne session Codex. Réessayez de la reprendre." + }, + "backgroundTask": { + "agent": "Agent d'arrière-plan", + "command": "Commande d'arrière-plan", + "countAgents_many": "{{count}} agents", + "countAgents_one": "{{count}} agent", + "countAgents_other": "{{count}} agents", + "countMonitors_many": "{{count}} moniteurs", + "countMonitors_one": "{{count}} moniteur", + "countMonitors_other": "{{count}} moniteurs", + "countShellCommandOne": "1 commande shell", + "countShell_many": "{{count}} shells", + "countShell_one": "{{count}} shell", + "countShell_other": "{{count}} shells", + "countTasks_many": "{{count}} tâches", + "countTasks_one": "{{count}} tâche", + "countTasks_other": "{{count}} tâches", + "countWorkflows_many": "{{count}} flux de travail", + "countWorkflows_one": "{{count}} flux de travail", + "countWorkflows_other": "{{count}} flux de travail", + "detailsUnavailable": "Les détails des tâches ne sont pas disponibles pour cette session.", + "groupAgents": "Agents", + "groupMonitors": "Moniteurs", + "groupShell": "Shell", + "groupTasks": "Tâches", + "groupWorkflows": "Flux de travail", + "headerTotalCount_many": "{{count}} tâches en arrière-plan", + "headerTotalCount_one": "{{count}} tâche en arrière-plan", + "headerTotalCount_other": "{{count}} tâches en arrière-plan", + "monitor": "Moniteur d'arrière-plan", + "monitoring": "Surveillance des tâches en arrière-plan", + "reasonBlocked": "échec", + "reasonWaiting": "approbation requise", + "stateBlocked": "bloqué", + "stateBlockedCount": "{{value0}} à l'état bloqué", + "stateDone": "terminé", + "stateDoneCount": "{{value0}} à l'état terminé", + "stateIdle": "arrêté", + "stateIdleCount": "{{value0}} à l'état arrêté", + "stateMonitoring": "surveillance", + "stateMonitoringCount": "{{value0}} en surveillance", + "stateUnverifiable": "statut indisponible", + "stateUnverifiableCount": "{{value0}} au statut indisponible", + "stateWaiting": "en attente", + "stateWaitingCount": "{{value0}} en attente", + "stateWorking": "en cours", + "stateWorkingCount": "{{value0}} en cours", + "stop": "Arrêter", + "stopAll": "Arrêter les tâches en arrière-plan", + "stopTask": "Arrêter {{value0}}", + "task": "Tâche en arrière-plan", + "workflow": "Flux de travail en arrière-plan" + }, + "browserTabActionSheet": { + "forward": "Suivant", + "reload": "Recharger", + "title": "Navigateur" + }, + "bulkCloseSheetActions": { + "closeOtherTabs": "Fermer les autres onglets" + }, + "bulkTabClose": { + "confirm": "Fermer les onglets", + "description": "Les onglets sélectionnés seront fermés. Les onglets épinglés resteront ouverts.", + "titleEditors": "Fermer tous les onglets de l’éditeur ?", + "titleLeft": "Fermer les onglets à gauche ?", + "titleOthers": "Fermer les autres onglets ?", + "titleRight": "Fermer les onglets à droite ?" + }, + "commandDock": { + "commandPlaceholder": "Saisissez une commande…", + "liveInputHint": "Le texte saisi est envoyé directement au terminal actif.", + "liveInputLabel": "Saisie en direct dans le terminal", + "sendCommand": "Envoyer la commande" + }, + "contentRow": { + "dismissCreationWarning": "Ignorer l'avertissement de création de l'espace de travail" + }, + "diffCommentLineRow": { + "addNote": "Ajouter une note à la ligne {{line}}", + "deleteNote": "Supprimer la note à la ligne {{line}}", + "diffLine": "{{title}}, ligne {{line}} du diff", + "line": "Ligne {{line}}", + "placeholder": "Ajouter une note de revue", + "saveNote": "Enregistrer la note" + }, + "diffLineRow": { + "addNoteLabel": "Ajouter une note à la ligne {{line}}", + "deleteNoteLabel": "Supprimer la note à la ligne {{line}}", + "diffLineLabel": "Diff de {{title}}, ligne {{line}}", + "lineNumber": "Ligne {{line}}", + "notePlaceholder": "Ajouter une note de revue", + "saveNote": "Enregistrer la note" + }, + "diffReviewLoaders": { + "changesLoadFailed": "Impossible de charger les modifications", + "committedChangesFailed": "Échec du chargement des modifications committées", + "committedChangesUnavailable": "Modifications committées indisponibles", + "committedDiffUnavailable": "Le diff des modifications committées est indisponible", + "desktopUpdateRequired": "Mettez à jour Orca sur l'ordinateur pour examiner les modifications sur mobile.", + "diffLoadFailed": "Impossible de charger le diff", + "invalidSourceControlResponse": "La réponse du contrôle de code source est invalide", + "reviewNotesLoadFailed": "Impossible de charger les notes de revue" + }, + "diffReviewQueue": { + "committedOnBranch": "Committé sur la branche", + "staged": "Dans l’index", + "unstaged": "Hors de l’index" + }, + "diffReviewScreenModel": { + "branch": "Branche", + "staged": "Dans l’index", + "unstaged": "Hors de l’index" + }, + "fileReader": { + "copyReviewNotes": "Copier les notes de revue", + "imageLabel": "Image : {{title}}", + "noReviewNotes": "Aucune note de revue", + "previewLabel": "Aperçu de {{title}}", + "reviewNotes_many": "{{count}} notes de revue", + "reviewNotes_one": "{{count}} note de revue", + "reviewNotes_other": "{{count}} notes de revue", + "send": "Envoyer", + "sendReviewNotes": "Envoyer les notes de revue à l'IA" + }, + "floatingWorkspace": { + "title": "Espace de travail flottant" + }, + "githubPrMutations": { + "reviewThreadUpdateFailed": "Impossible de mettre à jour le fil de revue.", + "titleUpdateFailed": "Impossible de mettre à jour le titre." + }, + "githubPrRpc": { + "requestFailed": "Échec de la requête : {{method}}" + }, + "header": { + "backToWorktrees": "Retour aux worktrees", + "moreActions": "Autres actions de la session", + "openFileExplorer": "Ouvrir l'explorateur de fichiers", + "openSourceControl": "Ouvrir le contrôle de code source", + "reconnectToDesktop": "Se reconnecter à l'ordinateur", + "showSidebar": "Afficher la barre latérale", + "terminalTitle": "Terminal" + }, + "headerMoreActionsSheet": { + "agentHistory": "Historique des agents", + "agentHistoryHint": "Parcourir et reprendre des sessions d'agent", + "checks": "Vérifications", + "checksHint": "Ouvrir les vérifications de la pull request" + }, + "markdownReader": { + "changedOnDesktop": "Modifié sur l'ordinateur", + "copyLoaded": "Copier le contenu chargé", + "copyLoadedSource": "Copier la source Markdown chargée", + "copyPath": "Copier le chemin", + "copyPathLabel": "Copier le chemin du fichier Markdown", + "copySource": "Copier la source Markdown", + "discard": "Abandonner", + "dismissKeyboard": "Masquer le clavier", + "dismissKeyboardHint": "Masque le clavier virtuel et laisse l'éditeur Markdown ouvert.", + "readOnly": "Lecture seule" + }, + "nativeChatAttachActions": { + "availableSkills": "Disponibles : {{count}}", + "commands": "Commandes", + "fileTypes": "PDF, texte, code…", + "files": "Fichiers", + "photos": "Photos", + "skills": "Skills", + "workspaceFile": "Fichier d'espace de travail" + }, + "nativeChatCardHeaderAction": { + "collapse": "Réduire" + }, + "nativeChatCliFork": { + "resumeInNewCli": "Reprendre dans une nouvelle CLI" + }, + "nativeChatFloatingControls": { + "scrollToLatest": "Faire défiler jusqu'au dernier message" + }, + "nativeChatFullContent": { + "loadMessageFailed": "Afficher le message complet — échec du chargement, appuyez pour réessayer", + "loadOutputFailed": "Afficher la sortie complète — échec du chargement, appuyez pour réessayer", + "partialMessage": "Affichage d'une partie de ce message.", + "partialOutput": "Affichage de {{shown}} sur {{total}}.", + "showFullMessage": "Afficher le message complet", + "showFullOutput": "Afficher la sortie complète" + }, + "nativeChatRenderData": { + "defaultAgentLabel": "l'agent", + "emptySubtitle": "Demandez à {{agent}} d'examiner le code, d'expliquer la sortie ou d'effectuer une modification.", + "emptyTitle": "Démarrez un chat avec {{agent}}", + "errorSubtitle": "La transcription n'a pas pu être lue. Rebasculez sur le terminal pour continuer à travailler.", + "errorTitle": "Impossible de charger la conversation", + "notFoundSubtitle": "Envoyez un message pour en démarrer une, ou ouvrez la vue du terminal pour voir ce que fait {{agent}}.", + "notFoundTitle": "Conversation introuvable pour le moment" + }, + "nativeChatToggleAction": { + "switchToChatView": "Passer à la vue du chat", + "switchToTerminalView": "Passer à la vue du terminal" + }, + "newTabAgentHostLaunch": { + "notesNotSent": "L'agent a démarré, mais les notes n'ont pas été envoyées.", + "notesSent": "Notes envoyées", + "notesUnconfirmed": "L'agent a démarré, mais l'envoi des notes n'a pas pu être confirmé.", + "promptUnconfirmed": "L'agent a démarré, mais l'envoi du prompt n'a pas pu être confirmé." + }, + "prActionsEngine": { + "mergeFailed": "Impossible de merger la pull request.", + "refreshFailed": "Impossible d'actualiser la pull request." + }, + "prAiTriageLaunch": { + "agentStarted": "Agent démarré", + "agentStartedIn": "Agent démarré dans {{label}}", + "emptyPrompt": "Le prompt enregistré pour cette action est vide. Modifiez les paramètres d'IA du contrôle de code source sur votre ordinateur.", + "loadFailed": "Impossible de charger les agents disponibles.", + "promptNotSent": "L'agent a démarré, mais le prompt n'a pas été envoyé.", + "updateRequired": "Mettez à jour Orca sur votre ordinateur pour démarrer un agent depuis votre téléphone." + }, + "prSidebarState": { + "loadFailed": "Impossible de charger la pull request" + }, + "queuedCardText": { + "showLess": "Afficher moins", + "showLessHint": "Affiche une version abrégée du message", + "showMore": "Afficher plus", + "showMoreHint": "Affiche le message entier" + }, + "queuedMessageCardRow": { + "deleteHint": "Supprimer ce message en attente", + "images_many": "{{count}} images", + "images_one": "{{count}} image", + "images_other": "{{count}} images", + "moreActions": "Plus d'actions", + "send": "Envoyer", + "sendAgainHint": "Renvoyer ce message", + "sendHint": "Envoyer ce message", + "showLessHint": "Affiche une version abrégée du message", + "steer": "Envoyer maintenant", + "steerHint": "Envoyer sans interrompre le modèle" + }, + "queuedMessageComposer": { + "commandsNotAllowed": "Les commandes ne peuvent pas être enregistrées dans un message en attente.", + "editing": "Modification du message", + "editingAttributed": "Modification du message · {{attribution}}" + }, + "quickCommandEditorForm": { + "action": "Action", + "addQuickCommand": "Ajouter une commande rapide", + "advanced": "Avancé", + "agent": "Agent", + "agentPrompt": "Prompt d'agent", + "appendEnter": "Ajouter Enter", + "appendEnterDescription": "Envoie immédiatement au lieu de simplement insérer le texte.", + "chooseAgent": "Choisir un agent", + "commandText": "Texte de la commande", + "globalScope": "Global", + "label": "Libellé", + "labelPlaceholder": "Démarrer le serveur de dev", + "olderHostPromptTooLong": "Trop long pour cet hôte. Mettez à jour Orca sur l’hôte pour enregistrer des prompts plus longs.", + "projectScope": "Projet", + "prompt": "Prompt", + "promptHint": "Prend en charge les skills, les chemins de fichiers et les commandes intégrées.", + "promptPlaceholder": "Demander à l'agent d'examiner cet espace de travail", + "promptSizeCharacters": "{{used}} / {{max}} caractères", + "promptSizeKilobytes": "{{used}} Ko / {{max}} Ko", + "promptTooLong": "Trop long pour être enregistré.", + "scope": "Portée", + "terminalCommand": "Commande de terminal" + }, + "quickCommandLaunch": { + "defaultLabel": "Commande rapide", + "editBeforeRunning": "Modifiez cette commande rapide avant de l'exécuter", + "runFailed": "Impossible d'exécuter {{label}}" + }, + "quickCommandRow": { + "copyCommand": "Copier {{label}}", + "copyFailed": "Impossible de copier", + "deleteCommand": "Supprimer {{label}}", + "editCommand": "Modifier {{label}}", + "nothingToCopy": "Rien à copier", + "runCommand": "Exécuter {{label}}" + }, + "quickCommandsList": { + "emptyState": "Aucune commande rapide pour le moment.", + "globalGroup": "Global", + "newCommand": "Nouvelle commande rapide", + "noMatches": "Aucune commande rapide correspondante.", + "projectGroup": "Ce projet", + "searchPlaceholder": "Rechercher des commandes rapides…" + }, + "quickCommandsSheet": { + "addTitle": "Ajouter une commande rapide", + "chooseAgentTitle": "Choisir un agent", + "deleteConfirmMessage": "Cette commande rapide sera retirée de votre liste enregistrée.", + "deleteConfirmTitle": "Supprimer « {{label}} » ?", + "editTitle": "Modifier la commande rapide", + "editorDescription": "Enregistrez des commandes de terminal ou des prompts d'agent pour y accéder rapidement.", + "title": "Commandes rapides", + "untitled": "Sans titre" + }, + "quickCommandsTabButton": { + "quickCommands": "Commandes rapides" + }, + "routeHelpers": { + "connected": "Connecté", + "connecting": "Connexion", + "disconnected": "Déconnecté", + "pairingInvalid": "Appairage invalide", + "reconnecting": "Reconnexion", + "securing": "Sécurisation" + }, + "routeScreen": { + "openingWorkspace": "Ouverture de l'espace de travail" + }, + "sheets": { + "browser": "Navigateur", + "browserUpdateRequired": "Mettez à jour Orca sur l'ordinateur pour diffuser le navigateur sur mobile", + "chat": "Chat", + "copyAllAndLeave": "Tout copier et quitter", + "copyDraftsFailed": "Impossible de copier les brouillons", + "copyNotes": "Copier les notes", + "copyNotesFailed": "Impossible de copier les notes", + "copyPath": "Copier le chemin", + "discard": "Abandonner", + "discardAndLeave": "Abandonner et quitter", + "discardChanges": "Abandonner les modifications", + "discardMessage": "Remplacer le brouillon du téléphone par la dernière version du fichier sur l'ordinateur ?", + "draftsHint": "Copiez ou abandonnez les brouillons du téléphone avant de quitter.", + "file": "Fichier", + "markdown": "Markdown", + "markdownNote": "Note Markdown", + "newBrowser": "Nouveau navigateur", + "newTab": "Nouvel onglet", + "notesCopied": "Notes copiées", + "notesHint": "Choisissez une session d'agent pour les notes actuelles.", + "removeShortcutMessage": "Supprimer ce raccourci personnalisé ?", + "renameTerminal": "Renommer le terminal", + "sendReviewNotes": "Envoyer les notes de revue", + "shortcut": "Raccourci", + "terminal": "Terminal", + "terminalName": "Nom du terminal", + "unsavedMarkdown": "Modifications Markdown non enregistrées", + "urlPrompt": "Saisissez une URL ou laissez le champ vide pour un nouvel onglet." + }, + "sideRail": { + "backToWorktrees": "Retour aux worktrees" + }, + "sourceControlLaunchAgent": { + "noEnabledAgent": "Aucun agent IA activé n'a été détecté sur l'hôte de cet espace de travail.", + "savedAgentUnavailable": "L'agent enregistré pour cette action n'est pas disponible sur l'hôte de cet espace de travail." + }, + "structuredComposerCommand": { + "operationUnconfirmed": "L'opération sur la conversation n'a pas été confirmée.", + "removeAttachments": "Retirez les pièces jointes avant d'utiliser une commande de session de chat." + }, + "structuredSendDelivery": { + "messageNotSent": "Message non envoyé" + }, + "tabStrip": { + "checkingCapabilities": "Vérification des fonctionnalités de l'ordinateur — réessayez dans un instant", + "newTab": "Nouvel onglet", + "quickCommandsUpdateRequired": "Mettez à jour Orca sur l'ordinateur pour utiliser les commandes rapides", + "tabStatus": "{{title}}, {{status}}" + }, + "terminalActionSheetActions": { + "clearTerminal": "Effacer le terminal", + "rename": "Renommer", + "switchToDesktop": "Passer en mode ordinateur", + "switchToPhone": "Passer en mode téléphone" + }, + "terminalInputActions": { + "attachFileHint": "Appuyez longuement pour joindre un fichier à la place.", + "attachPhoto": "Joindre une photo", + "sendingImage": "Envoi de l'image", + "startDictation": "Démarrer la dictée vocale", + "startingDictation": "Démarrage de la dictée vocale", + "stopDictation": "Arrêter la dictée vocale", + "transcribingDictation": "Transcription de la dictée vocale" + }, + "terminalLiveInputStatus": { + "listening": "Écoute en cours", + "liveInput": "Saisie en direct", + "preparingMic": "Préparation du microphone", + "processing": "Traitement en cours", + "releaseMicHint": "Relâchez le micro pour arrêter", + "startingMic": "Démarrage du micro", + "stopHint": "Touchez le micro pour arrêter", + "transcribing": "Transcription sur l'ordinateur", + "uploadingImage": "Envoi de l'image à l'hôte", + "uploadingImageTitle": "Envoi de l'image" + }, + "terminalRecords": { + "defaultTitle": "Terminal" + }, + "terminalTabAgent": { + "browser": "Navigateur", + "chat": "Chat", + "file": "Fichier", + "markdown": "Markdown", + "newBrowser": "Nouveau navigateur", + "terminal": "Terminal" + }, + "terminalViewportResubscribe": { + "fitFailed": "Impossible d'adapter le terminal à cet écran" + }, + "useAccessorySelection": { + "copyFailed": "Impossible de copier", + "selectionCleared": "Sélection effacée (texte hors du tampon après défilement)" + }, + "useAttachmentInputLeaseGate": { + "reconnecting": "Échec de l'ajout (reconnexion en cours)" + }, + "useContentCreateActions": { + "browserCommandFailed": "Échec de la commande du navigateur", + "browserCreateFailed": "Échec de la création du navigateur", + "browserUnavailable": "La page du navigateur n'est pas encore disponible.", + "browserUpdateRequired": "Mise à jour d'Orca sur l'ordinateur requise pour diffuser le navigateur sur mobile", + "invalidUrl": "Saisissez une URL valide", + "noteCreateFailed": "Échec de la création de la note Markdown", + "untitledNoteCreateFailed": "Impossible de créer une note Markdown sans titre" + }, + "useDiffComments": { + "copyNotesFailed": "Impossible de copier les notes", + "deleteNoteFailed": "Échec de la suppression de la note", + "noteAdded": "Note ajoutée", + "notesCopied": "Notes copiées", + "saveNoteFailed": "Échec de l'enregistrement de la note", + "saveNotesFailed": "Échec de l'enregistrement des notes de revue", + "waitingForDesktop": "En attente de l'ordinateur…" + }, + "useDiffReviewCommentActions": { + "saveFailed": "Impossible d'enregistrer la revue", + "saveStateFailed": "Impossible d'enregistrer l'état de la revue", + "waitingForDesktop": "En attente de l'ordinateur…" + }, + "useDiffReviewController": { + "loadFailed": "Impossible de charger la revue", + "missingWorktree": "Worktree manquant", + "waitingForDesktop": "En attente de l'ordinateur…" + }, + "useDiffReviewDiffLoading": { + "loadFailed": "Impossible de charger le diff", + "waitingForDesktop": "En attente de l'ordinateur…" + }, + "useDiffReviewGitActions": { + "actionFailed": "Échec de l'action de contrôle de code source", + "reviewedFilesStaged_many": "{{count}} fichiers examinés indexés", + "reviewedFilesStaged_one": "{{count}} fichier examiné indexé", + "reviewedFilesStaged_other": "{{count}} fichiers examinés indexés", + "stageResults": "Indexés : {{staged}}, échecs : {{failed}}", + "waitingForDesktop": "En attente de l'ordinateur…" + }, + "useDiffReviewInteractions": { + "openFailed": "Impossible d'ouvrir dans la session" + }, + "useDiffReviewSendActions": { + "copyFailed": "Impossible de copier les notes de revue", + "inputLocked": "La saisie du terminal est verrouillée", + "loadSessionsFailed": "Impossible de charger les sessions d'agents", + "notesCopied": "Notes de revue copiées", + "notesSent": "Notes de revue envoyées", + "notesSentWithWarning": "Notes de revue envoyées. {{warning}}", + "promptNotSent": "L'agent a démarré, mais les notes n'ont pas été envoyées. Utilisez « Copier les notes » pour les coller.", + "sendFailed": "Impossible d'envoyer les notes", + "startingAgent": "Démarrage d'un agent…", + "waitingForDesktop": "En attente de l'ordinateur…" + }, + "useImageAttachment": { + "attachFailed": "Impossible de joindre l'image", + "disconnected": "Échec de l'ajout (déconnecté)", + "imageTooLarge": "Image trop volumineuse pour être jointe", + "permissionDenied": "Accès aux photos refusé" + }, + "useMarkdownActions": { + "copyFailed": "Impossible de copier", + "copyPathFailed": "Impossible de copier le chemin", + "markdownTitle": "Markdown", + "pathCopied": "Chemin copié", + "saveFailed": "Échec de l'enregistrement", + "saved": "Enregistré" + }, + "useNativeChatDictation": { + "insertFailed": "Impossible d'insérer la dictée", + "insertFailedCopied": "Impossible d'insérer la dictée ; elle a été copiée dans le presse-papiers", + "inserted": "Dictée insérée" + }, + "useNativeChatStop": { + "stopNotSent": "Commande d'arrêt non envoyée", + "stopUnconfirmed": "Arrêt non confirmé — vérifiez le chat avant de réessayer", + "terminalNotReady": "Commande d'arrêt non envoyée (terminal pas prêt)" + }, + "usePanelRouteActions": { + "checkHostHint": "Vérifiez la connexion à l'hôte", + "copyNotesHint": "Copiez plutôt les notes", + "detectingAgents": "Détection des agents", + "newSessionHint": "Nouvelle session d'agent", + "noEnabledAgents": "Aucun agent activé", + "otherServerAgents": "Agents sur un autre serveur", + "pairServerHint": "Appairez directement ce serveur", + "presetsUnavailable": "Préréglages d'agents indisponibles" + }, + "usePrAiTriage": { + "launchFailed": "Échec du lancement de l'agent", + "waitingForDesktop": "En attente de l'ordinateur…" + }, + "usePrCommentActions": { + "actionFailed": "Échec de l'action sur le commentaire" + }, + "usePrTitleAction": { + "disconnected": "Aucune connexion à l'ordinateur.", + "updateFailed": "Échec de la mise à jour du titre." + }, + "useQuickCommands": { + "loadFailed": "Échec du chargement des commandes rapides", + "saveFailed": "Échec de l'enregistrement de la commande rapide" + }, + "useSessionTerminalSendActions": { + "inputTooLarge": "Saisie trop volumineuse (max. 256 Kio)" + }, + "useStartup": { + "wakeAgents": "Ouvrez Orca sur l'hôte pour réveiller les agents en veille." + }, + "useTerminalCreateActions": { + "createFailed": "Échec de la création du terminal", + "forkBusy": "Un autre terminal s'ouvre. Patientez, puis réessayez avec le chat {{agentName}}.", + "forkUnavailable": "Ouvrez le chat {{agentName}} sur un hôte connecté et à jour, puis réessayez.", + "inputLocked": "La saisie dans le terminal est verrouillée par un autre client.", + "notesSent": "Notes envoyées", + "sendNotesError": "Impossible d'envoyer les notes", + "sendNotesFailed": "Échec de l'envoi des notes", + "terminalTitle": "Terminal" + }, + "useTerminalInput": { + "clearFailed": "Impossible d'effacer le terminal", + "cleared": "Terminal effacé" + }, + "useTerminalPaste": { + "clipboardReadFailed": "Impossible de lire le presse-papiers", + "disconnected": "Échec du collage (déconnexion)", + "imageTooLarge": "Image trop volumineuse pour être collée", + "pasteFailed": "Échec du collage", + "pasteTooLarge": "Contenu à coller trop volumineux (max. 256 Kio)", + "terminalChanged": "Collage annulé (changement de terminal)" + }, + "worktreeLabel": { + "fallback": "Worktree" + } + }, "settings": { "aboutScreen": { "openLinkFailed": "Impossible d'ouvrir le lien. Réessayez.", @@ -1470,6 +2589,193 @@ "updateFailed": "Impossible de mettre à jour" } }, + "sourceControl": { + "actions": { + "commits": "Commits", + "createPr": "Créer une PR", + "pushAndCreatePr": "Pousser et créer une PR", + "switchBranch": "Changer de branche" + }, + "commitDropdown": { + "checkOutBranchToPush": "Effectuez un checkout d'une branche avant de pousser des commits", + "checkOutBranchToSync": "Effectuez un checkout d'une branche avant de synchroniser des commits", + "checkingBranchStatus": "Vérification de l'état de la branche…", + "checkingPrStatus": "Vérification du statut de la PR…", + "commit": "Commit", + "commitAndForcePush": "Committer les modifications indexées et faire un force push avec lease", + "commitAndPush": "Committer les modifications indexées et pousser", + "commitAndTryPush": "Committer les modifications indexées et tenter un push", + "commitForcePush": "Commit & Force Push", + "commitPush": "Commit & Push", + "commitStagedChanges": "Committer les modifications indexées", + "commitSync": "Commit & Sync", + "commitThenPullPush": "Committer, puis tirer et pousser", + "enterMessage": "Saisissez un message de commit pour commiter", + "linkedReviewTargetUnavailable": "La cible de la branche de revue liée est indisponible", + "prAlreadyMerged": "La PR est déjà mergée", + "publishBranchFirstToPush": "Publiez d'abord la branche pour pousser des commits", + "publishBranchFirstToSync": "Publiez d'abord la branche pour synchroniser des commits", + "resolveConflicts": "Résolvez les conflits avant de commiter", + "stageFile": "Indexez au moins un fichier pour commiter", + "useCommitAndForcePush": "Utilisez Commit & Force Push — le remote n'a que d'anciennes copies des commits locaux" + }, + "createPrAction": { + "createReview": "Créer une {{titleLabel}}" + }, + "dropdownCounts": { + "fastForwardCount_many": "Faire un fast-forward de {{count}} commits", + "fastForwardCount_one": "Faire un fast-forward de {{count}} commit", + "fastForwardCount_other": "Faire un fast-forward de {{count}} commits", + "pullCount_many": "Tirer {{count}} commits", + "pullCount_one": "Tirer {{count}} commit", + "pullCount_other": "Tirer {{count}} commits", + "pushCount_many": "Pousser {{count}} commits", + "pushCount_one": "Pousser {{count}} commit", + "pushCount_other": "Pousser {{count}} commits", + "remoteBranchFallback": "la branche distante", + "syncCounts": "Tirer {{behind}}, pousser {{ahead}}", + "titleForcePushOlderCopiesBranch": "Le remote n'a que d'anciennes copies des commits locaux. Faites un force push avec lease de cette branche pour mettre à jour {{upstreamName}}.", + "titleForcePushOlderCopiesCount_many": "Le remote n'a que d'anciennes copies des commits locaux. Faites un force push avec lease de {{count}} commits de la branche pour mettre à jour {{upstreamName}}.", + "titleForcePushOlderCopiesCount_one": "Le remote n'a que d'anciennes copies des commits locaux. Faites un force push avec lease de {{count}} commit de la branche pour mettre à jour {{upstreamName}}.", + "titleForcePushOlderCopiesCount_other": "Le remote n'a que d'anciennes copies des commits locaux. Faites un force push avec lease de {{count}} commits de la branche pour mettre à jour {{upstreamName}}.", + "titleManualForcePushReplaceRemote_many": "Faites un force push avec lease de {{count}} commits locaux pour mettre à jour {{upstreamName}} et remplacer les commits présents uniquement sur le remote.", + "titleManualForcePushReplaceRemote_one": "Faites un force push avec lease de {{count}} commit local pour mettre à jour {{upstreamName}} et remplacer les commits présents uniquement sur le remote.", + "titleManualForcePushReplaceRemote_other": "Faites un force push avec lease de {{count}} commits locaux pour mettre à jour {{upstreamName}} et remplacer les commits présents uniquement sur le remote.", + "titleManualForcePush_many": "Faites un force push avec lease de {{count}} commits locaux pour mettre à jour {{upstreamName}}.", + "titleManualForcePush_one": "Faites un force push avec lease de {{count}} commit local pour mettre à jour {{upstreamName}}.", + "titleManualForcePush_other": "Faites un force push avec lease de {{count}} commits locaux pour mettre à jour {{upstreamName}}.", + "titleUnpublishedForcePushBranch": "Faites un force push avec lease de cette branche et définissez un upstream si nécessaire.", + "titleUnpublishedForcePushCount_many": "Faites un force push avec lease de {{count}} commits de la branche et définissez un upstream si nécessaire.", + "titleUnpublishedForcePushCount_one": "Faites un force push avec lease de {{count}} commit de la branche et définissez un upstream si nécessaire.", + "titleUnpublishedForcePushCount_other": "Faites un force push avec lease de {{count}} commits de la branche et définissez un upstream si nécessaire." + }, + "hostedReview": { + "mrCopy": { + "reviewLabel": "merge request", + "shortLabel": "MR", + "titleLabel": "Merge Request" + }, + "prCopy": { + "reviewLabel": "pull request", + "shortLabel": "PR", + "titleLabel": "Pull Request" + } + }, + "hostedReviewCreate": { + "pushSucceeded": "Le push a réussi, mais la création de la {{shortLabel}} a échoué : {{error}}" + }, + "prCreate": { + "authRequired": "Authentifiez-vous avant de créer une {{reviewLabel}}.", + "baseNotOnRemote": "Poussez la branche de base avant de créer une {{reviewLabel}}.", + "createdNotRefreshed": "La {{titleLabel}} a été créée, mais Orca n'a pas encore pu l'actualiser.", + "defaultBranch": "Passez sur une branche de fonctionnalité avant de créer une {{reviewLabel}}.", + "detachedHead": "Effectuez un checkout d'une branche avant de créer une {{reviewLabel}}.", + "dirty": "Committez les modifications avant de créer une {{reviewLabel}}.", + "existing": "La {{titleLabel}} est déjà ouverte.", + "existingNumber": "La {{titleLabel}} #{{number}} est déjà ouverte.", + "existingReview": "Une {{reviewLabel}} existe déjà pour cette branche.", + "forkHeadUnsupported": "La création d'une {{reviewLabel}} depuis ce fork n'est pas prise en charge.", + "lookupUnconfirmed": "Orca n'a pas pu confirmer si cette branche a déjà une {{reviewLabel}}. Réessayez dans un instant.", + "needsSync": "Synchronisez cette branche avant de créer une {{reviewLabel}}.", + "noUpstream": "Publiez les commits avant de créer une {{reviewLabel}}.", + "notReady": "Cette branche n'est pas encore prête pour une {{reviewLabel}}.", + "unsupportedMergeRequests": "La création de merge requests n'est pas prise en charge pour ce dépôt.", + "unsupportedPullRequests": "La création de pull requests n'est pas prise en charge pour ce dépôt." + }, + "primaryAction": { + "forcePushFromDesktop": "Faites un force push de la branche de cette pull request depuis l'ordinateur" + }, + "remoteDropdown": { + "branchAlreadyPublished": "La branche est déjà publiée", + "branchUpToDate": "La branche est à jour", + "checkOutBranchToFastForward": "Effectuez un checkout d'une branche avant un fast-forward", + "checkOutBranchToForcePush": "Effectuez un checkout d'une branche avant de pousser des commits en force", + "checkOutBranchToPublish": "Effectuez un checkout d'une branche avant de publier des commits", + "checkOutBranchToPull": "Effectuez un checkout d'une branche avant de tirer des commits", + "checkOutBranchToPush": "Effectuez un checkout d'une branche avant de pousser des commits", + "checkOutBranchToSync": "Effectuez un checkout d'une branche avant de synchroniser des commits", + "checkingBranchStatus": "Vérification de l'état de la branche…", + "checkingPrStatus": "Vérification du statut de la PR…", + "fastForward": "Fast-forward", + "fetch": "Fetch", + "fetchTitle": "Fetch depuis le remote sans fusionner", + "forcePush": "Force Push", + "linkedReviewBranchExists": "La branche de revue liée existe déjà", + "linkedReviewTargetUnavailable": "La cible de la branche de revue liée est indisponible", + "nothingNewToFastForward": "Rien de nouveau pour un fast-forward — le remote n'a que d'anciennes copies des commits locaux", + "nothingNewToPull": "Rien de nouveau à tirer — le remote n'a que d'anciennes copies des commits locaux", + "nothingToFastForward": "Rien à avancer en fast-forward", + "nothingToForcePush": "Rien à pousser en force", + "nothingToForcePushTo": "Rien à pousser en force vers {{upstreamName}}", + "nothingToPull": "Rien à tirer", + "nothingToPush": "Rien à pousser", + "nothingToPushTo": "Rien à pousser vers {{upstreamName}}", + "prAlreadyMerged": "La PR est déjà mergée", + "publishBranch": "Publier la branche", + "publishBranchFirstToFastForward": "Publiez d'abord la branche pour faire un fast-forward", + "publishBranchFirstToPull": "Publiez d'abord la branche pour tirer des commits", + "publishBranchFirstToSync": "Publiez d'abord la branche pour synchroniser des commits", + "publishLinkedReview": "Revue liée", + "publishNoBranch": "Aucune branche", + "publishPrStatus": "Statut de la PR", + "publishToOrigin": "Publier cette branche vers origin", + "pull": "Pull", + "push": "Push", + "pushAndSetUpstream": "Poussez cette branche et définissez un upstream si nécessaire", + "pushMayNeedSync": "Poussez les commits locaux ; git peut exiger une synchronisation d'abord", + "pushToLinkedReview": "Poussez les mises à jour vers la branche de revue liée", + "rebaseChooseRemoteBase": "Choisissez une branche de base distante depuis laquelle rebaser", + "rebaseDirtyHint": "Tentez un rebase ; git peut exiger de committer ou de stasher les modifications locales d'abord", + "rebaseFromBase": "Rebaser depuis la base", + "rebaseFromNamedBase": "Rebaser depuis {{baseRef}}", + "rebaseWithLatestFrom": "Rebaser la branche actuelle sur les derniers commits de {{baseRef}}", + "sync": "Synchroniser", + "tryFastForwardPull": "Tentez un pull en fast-forward ; git peut rejeter les commits locaux", + "tryRegularPush": "Tentez un push normal ; git peut exiger un force push", + "useForcePushToSync": "Utilisez Force Push — le remote n'a que d'anciennes copies des commits locaux" + } + }, + "terminal": { + "keyDefinitions": { + "accessoryArrowDown": "Flèche vers le bas", + "accessoryArrowLeft": "Flèche vers la gauche", + "accessoryArrowRight": "Flèche vers la droite", + "accessoryArrowUp": "Flèche vers le haut", + "arrowDown": "Flèche vers le bas", + "arrowLeft": "Flèche vers la gauche", + "arrowRight": "Flèche vers la droite", + "arrowUp": "Flèche vers le haut", + "clearLineBeforeCursor": "Effacer la ligne avant le curseur", + "clearScreen": "Effacer l'écran", + "deleteWordBackward": "Supprimer le mot précédent", + "endOfLine": "Fin de ligne", + "forwardDelete": "Supprimer vers l'avant", + "interruptTerminal": "Interrompre le terminal", + "reverseSearch": "Recherche inversée", + "sendEof": "Envoyer EOF", + "startOfLine": "Début de ligne", + "suspendProcess": "Suspendre le processus" + }, + "webView": { + "documentStartFailed": "le document du terminal n'a pas pu démarrer - {{message}}", + "httpError": "Erreur HTTP de la WebView du terminal", + "loadFailed": "Échec du chargement de la WebView du terminal", + "renderProcessEnded": "Le processus de rendu de la WebView du terminal s'est arrêté" + }, + "webviewEngineErrorState": { + "code": "code {{code}}", + "reload": "Recharger", + "rendererCrashed": "le moteur de rendu a planté", + "status": "statut {{statusCode}}", + "title": "Échec du chargement du terminal" + }, + "webviewNotificationDispatch": { + "unknownError": "Erreur inconnue du terminal" + }, + "webviewReadyWatchdog": { + "initializeFailed": "Échec de l'initialisation du terminal - aucun signal de disponibilité reçu de la vue du terminal" + } + }, "webShell": { "shellStateScreen": { "checkingHost": "Vérification de l'hôte", @@ -1484,6 +2790,18 @@ "tryAgain": "Réessayer" } }, + "workspace": { + "branchChoice": { + "automaticName": "Nom automatique", + "existing": "Branche existante", + "existingHint": "Les nouveaux commits sont ajoutés à cette branche.", + "hostUpdateRequired": "Mettez à jour Orca sur l'hôte de l'espace de travail pour réutiliser une branche ou créer explicitement une branche distincte.", + "new": "Nouvelle branche", + "newHint": "Créer une nouvelle branche locale", + "newName": "Nom de la nouvelle branche", + "title": "Branche pour cet arbre de travail" + } + }, "worktree": { "agentRowDisplay": { "blocked": "Bloqué", @@ -1526,16 +2844,5 @@ "inReview": "En revue", "pinned": "Épinglés" } - }, - "workspace": { - "branchChoice": { - "title": "Branche pour cet arbre de travail", - "existing": "Branche existante", - "existingHint": "Les nouveaux commits sont ajoutés à cette branche.", - "new": "Nouvelle branche", - "newHint": "Créer une nouvelle branche locale", - "newName": "Nom de la nouvelle branche", - "automaticName": "Nom automatique" - } } } diff --git a/mobile/src/i18n/locales/ja.json b/mobile/src/i18n/locales/ja.json index 2d10f4241e7..83eabcfa7d6 100644 --- a/mobile/src/i18n/locales/ja.json +++ b/mobile/src/i18n/locales/ja.json @@ -139,6 +139,363 @@ "title": "ターミナル" } }, + "browser": { + "addressField": { + "urlPlaceholder": "URL" + }, + "pane": { + "invalidUrl": "有効な URL を入力してください。" + }, + "paneView": { + "dialogTitle": "ブラウザのダイアログ", + "forward": "進む", + "keyboardPlaceholder": "ページに入力…", + "reload": "リロード", + "sendText": "ブラウザにテキストを送信" + }, + "pointerModifiers": { + "clickModifier": "クリック時の修飾キー {{key}}" + }, + "useBrowserCommands": { + "deliveryUnconfirmed": "送信を確認できません。再試行する前にページを確認してください。", + "dialogAnswerFailed": "回答をページに送信できませんでした。", + "sent": "送信済み" + }, + "viewModeSwitch": { + "mobileView": "ウェブサイトのモバイル表示に切り替え", + "webView": "ウェブサイトのデスクトップ表示に切り替え" + } + }, + "chat": { + "ask": { + "answerPlaceholder": "答えを入力してください", + "next": "次へ", + "other": "その他…", + "skip": "スキップ", + "step": "ステップ {{step}}", + "submit": "送信" + }, + "askRow": { + "asked": "質問:", + "awaiting": "ユーザー入力を待っています:", + "awaitingUnnamed": "ユーザー入力を待っています", + "questionCount": "{{value0}} 件の質問" + }, + "composer": { + "addToMessage": "メッセージに追加", + "attachImage": "画像を添付", + "cancelEditing": "編集をキャンセル", + "placeholder": "何でも聞いてください。@ でファイルを参照、/ でコマンドを実行", + "saveEditedMessage": "編集したメッセージを保存", + "sendMessage": "メッセージを送信" + }, + "composerAttachments": { + "attachedImage": "添付画像", + "file": "ファイル", + "fileMeta": "{{type}} · {{size}}", + "removeFile": "「{{name}}」を削除", + "removeImage": "画像を削除", + "uploadingFile": "「{{name}}」をアップロード中" + }, + "composerPlaceholder": { + "prompt": "何でも聞いてください。@ でファイルを参照、/ でコマンドを実行", + "reconnecting": "再接続中…", + "waitingForTerminal": "ターミナルを待っています…" + }, + "diffCard": { + "copyDiff": "差分をコピー", + "diffGap": "行を省略", + "diffTruncated": "差分切り捨て" + }, + "hostStatus": { + "continuedOutside": "このチャットの外で続けられました。", + "continuedOutsideRewound": "このチャットの外で続けられました。上の以前の返信は、この会話の履歴にはもうありません。", + "historyItemTooLarge": "チャットのこの部分は大きすぎるため表示できません。", + "historyRepaired": "このチャットの履歴の一部を読み込めませんでした。" + }, + "message": { + "copyMessage": "メッセージをコピーする" + }, + "messageActionsSheet": { + "clipboardRejected": "クリップボードがテキストを受け付けませんでした。", + "copyFailed": "コピーに失敗しました", + "copyMessage": "メッセージをコピーする", + "selectText": "テキストを選択" + }, + "nativeChatImageViewer": { + "closeImage": "画像を閉じる" + }, + "nativeChatProse": { + "attachedImage": "添付画像", + "image": "画像", + "openImageHint": "画像をフルサイズで開きます。" + }, + "nativeChatToolLine": { + "added": "追加", + "copyCommand": "コマンドをコピー", + "deleted": "削除", + "durationHours": "{{hours}}時間 {{minutes}}分 {{seconds}}秒", + "durationMilliseconds": "{{milliseconds}}ミリ秒", + "durationMinutes": "{{minutes}}分 {{seconds}}秒", + "durationSeconds": "{{seconds}}秒", + "edited": "編集", + "editing": "編集中", + "exitCode": "終了コード {{exitCode}}", + "fetched": "取得", + "fetching": "取得中", + "listed": "一覧表示", + "listing": "一覧表示中", + "ran": "実行", + "read": "読み取り", + "reading": "読み取り中", + "renamed": "名前変更", + "result": "結果", + "running": "実行中", + "searched": "検索", + "searchedWeb": "ウェブ検索", + "searching": "検索中", + "searchingWeb": "ウェブ検索中", + "subagent": "サブ Agent", + "triedEdit": "編集を試行", + "triedFetch": "取得を試行", + "triedList": "一覧表示を試行", + "triedRead": "読み取りを試行", + "triedSearch": "検索を試行", + "triedSearchWeb": "ウェブ検索を試行" + }, + "nativeChatUserMessageFold": { + "loadFailed": "メッセージ全体を表示(読み込み失敗、タップして再試行)", + "showFullMessage": "メッセージ全体を表示", + "showLess": "折りたたむ" + }, + "nativeChatVisual": { + "defaultTitle": "ビジュアライゼーション", + "openFullscreen": "「{{title}}」を全画面で開く", + "retryHint": "もう一度読み込みます。", + "unavailable": "ビジュアライゼーションを利用できません" + }, + "notice": { + "compaction": "コンテキストを圧縮しました", + "contextCleared": "コンテキストをクリアしました", + "plan": "計画", + "stoppedManually": "手動で停止しました" + }, + "orcaStop": { + "canContinue": "この会話で続行できます。", + "crash": "この応答の途中で、{{machine}} の Orca が予期せず停止しました。", + "quit": "この応答の途中で、{{machine}} の Orca が終了しました。", + "remote": "この応答の途中で、{{machine}} の Orca が停止しました。", + "unnamedServer": "リモートサーバー", + "update": "この応答の途中で、{{machine}} の Orca がアップデートのため再起動しました。" + }, + "permission": { + "needsAccessTo": "アクセスが必要なパス: ", + "newerVersionRequired": "このリクエストには新しいバージョンの Orca が必要です。", + "planFile": "計画ファイル: {{filePath}}", + "reason": "理由: " + }, + "promptCard": { + "answerInTerminal": "ターミナルで回答する", + "terminalExplanation": "ターミナルでこの OMP の質問に答えてください。" + }, + "promptStrip": { + "expand": "展開" + }, + "question": { + "otherReplyPlaceholder": "または返信を入力…", + "replyPlaceholder": "返信を入力…", + "sendReply": "返信を送信", + "submit": "送信", + "submitCount": "送信({{count}})", + "submitSelectedOptions": "選択した項目を送信" + }, + "queuedMessages": { + "clearAll": "すべてクリア", + "deleteAll": "キュー内のすべてのメッセージを削除", + "editMessage": "メッセージを編集", + "pausedState": "一時停止中", + "queuedState": "キュー待ち", + "resume": "再開", + "resumeSending": "キュー内のメッセージの送信を再開", + "title": "キュー内のメッセージ" + }, + "receipt": { + "cancelled": "キャンセル", + "cancelledBy": "{{device}} でキャンセルされました", + "emptyAnswer": "空の回答", + "resolved": "解決済み", + "resolver": "{{device}} で回答しました", + "unavailable": "選択した回答は利用できません" + }, + "sessionOptionCopy": { + "availableAfterSessionStarts": "セッション開始後に利用できます。", + "chooseInAgentPicker": "Agent の選択画面で選ぶ…", + "effort": "推論レベル", + "fast": "高速", + "fastMode": "高速モード", + "high": "高", + "low": "低", + "max": "最大", + "medium": "中", + "mediumShort": "中", + "minimal": "最小", + "minimalShort": "最小", + "model": "モデル", + "notSet": "未設定", + "off": "オフ", + "on": "オン", + "permissions": "権限", + "sessionOptions": "セッションオプション", + "setWhenSessionStarts": "セッション開始時にのみ設定できます。", + "speed": "速度", + "standard": "標準", + "thinking": "思考", + "toggleOption": "{{value0}}を切り替える", + "ultra": "ウルトラ", + "xhigh": "最高", + "xhighShort": "最高" + }, + "sessionOptionPickers": { + "agentWorking": "Agent が作業中", + "backToModels": "モデル一覧に戻る", + "closePicker": "選択画面を閉じる", + "dispatchUnconfirmed": "Agent に送信済み — 未確認", + "modelFullAccessLabel": "モデル、{{value}}、フルアクセス", + "modelLabel": "モデル、{{value}}", + "permissions": "権限", + "permissionsLabel": "権限、{{value}}", + "settingsLocked": "このターンが完了すると、モデル設定を変更できます。", + "workingAnnouncement": "Agent が作業中です。このターンが完了すると、モデル設定を変更できます。" + }, + "sessionOptionRows": { + "defaultMarker": "既定", + "notReportedMarker": "未報告" + }, + "sessionOptionSegments": { + "dispatchUnconfirmed": "Agent に送信済み — 未確認" + }, + "stopButton": { + "stopAgent": "Agent を停止", + "stoppingState": "停止中…" + }, + "streamFrame": { + "streamEnded": "チャット履歴の受信が終了しました", + "streamFailed": "チャット履歴の受信に失敗しました" + }, + "taskList": { + "added": "{{task}} を追加しました", + "completed": "完了", + "empty": "タスクがありません", + "finished": "{{task}} を完了しました", + "inProgress": "進行中", + "pending": "未着手", + "progress": "{{total}} 件中 {{completed}} 件のタスクが完了しました", + "removed": "{{task}} を削除しました", + "reset": "未着手に変更しました:{{task}}", + "showAll": "完全なタスクリスト", + "started": "{{task}} を開始しました", + "taskStatus": "{{status}}:{{task}}", + "title": "タスク", + "unchanged": "タスクは変更されていない", + "updated": "{{task}} を更新しました" + }, + "toolActivity": { + "count_one": "{{count}} 件のツール呼び出し", + "count_other": "{{count}} 件のツール呼び出し", + "failedCallsLabel": "失敗したツール呼び出し: {{value0}}", + "failedCount": "{{value0}} 件失敗", + "interruptedCallsLabel": "中断されたツール呼び出し: {{value0}}", + "interruptedCount": "{{value0}} 件中断", + "moreCalls": "他 +{{value0}} 件", + "result": "結果", + "runningCommand": "コマンドを実行中", + "runningNamed": "{{toolName}} を実行中", + "runningNamedPreview": "{{toolName}} {{preview}} を実行中", + "runningPreview": "{{preview}} を実行中" + }, + "toolRun": { + "list": "{{value0}}、{{value1}}", + "liveBrowserOnce": "ブラウザを使用しています", + "liveBrowserTimes_other": "ブラウザを {{count}} 回使用中", + "liveFileChange_one": "{{count}} 件のファイルを編集中", + "liveFileChange_other": "{{count}} 件のファイルを編集中", + "liveListFiles_one": "{{count}} 件のディレクトリを一覧表示中", + "liveListFiles_other": "{{count}} 件のディレクトリを一覧表示中", + "liveMcpToolCall_one": "{{count}} 件の連携を使用中", + "liveMcpToolCall_other": "{{count}} 件の連携を使用中", + "liveOther_one": "{{count}} 個のツールを使用中", + "liveOther_other": "{{count}} 個のツールを使用中", + "liveRead_one": "{{count}} 件のファイルを読み込み中", + "liveRead_other": "{{count}} 件のファイルを読み込み中", + "liveSearch_one": "{{count}} 回検索中", + "liveSearch_other": "{{count}} 回検索中", + "liveSubAgentActivity_one": "{{count}} 個の Agent を実行中", + "liveSubAgentActivity_other": "{{count}} 個の Agent を実行中", + "liveTodoListOnce": "計画を更新する", + "liveTodoListTimes_other": "計画を {{count}} 回更新中", + "liveUnknown_one": "{{count}} 件のコマンドを実行中", + "liveUnknown_other": "{{count}} 件のコマンドを実行中", + "liveWebSearch_one": "ウェブを {{count}} 回検索中", + "liveWebSearch_other": "ウェブを {{count}} 回検索中", + "pair": "{{value0}} および {{value1}}", + "separator": "、 ", + "settledBrowserOnce": "ブラウザを使用しました", + "settledBrowserTimes_other": "ブラウザを {{count}} 回使用しました", + "settledFileChange_one": "{{count}} 件のファイルを編集しました", + "settledFileChange_other": "{{count}} 件のファイルを編集しました", + "settledListFiles_one": "{{count}} 件のディレクトリを一覧表示しました", + "settledListFiles_other": "{{count}} 件のディレクトリを一覧表示しました", + "settledMcpToolCall_one": "{{count}} 件の連携を使用しました", + "settledMcpToolCall_other": "{{count}} 件の連携を使用しました", + "settledOther_one": "{{count}} 個のツールを使用しました", + "settledOther_other": "{{count}} 個のツールを使用しました", + "settledRead_one": "{{count}} 件のファイルを読み込みました", + "settledRead_other": "{{count}} 件のファイルを読み込みました", + "settledSearch_one": "{{count}} 回検索しました", + "settledSearch_other": "{{count}} 回検索しました", + "settledSubAgentActivity_one": "{{count}} 個の Agent を実行しました", + "settledSubAgentActivity_other": "{{count}} 個の Agent を実行しました", + "settledTodoListOnce": "計画を更新しました", + "settledTodoListTimes_other": "計画を {{count}} 回更新しました", + "settledUnknown_one": "{{count}} 件のコマンドを実行しました", + "settledUnknown_other": "{{count}} 件のコマンドを実行しました", + "settledWebSearch_one": "ウェブを {{count}} 回検索しました", + "settledWebSearch_other": "ウェブを {{count}} 回検索しました" + }, + "turnStatus": { + "failedAfter": "{{value0}} 後に失敗", + "hoursMinutesSeconds": "{{hours}}時間 {{minutes}}分 {{seconds}}秒", + "interruptedAfter": "{{value0}} 後に中断", + "minutesSeconds": "{{minutes}}分 {{seconds}}秒", + "queueAfterStop": "停止後に実行するメッセージをキューに追加", + "responding": "Agent が応答中です", + "seconds_one": "{{count}}秒", + "seconds_other": "{{count}}秒", + "sendAfterStop": "停止後に実行するメッセージを送信", + "stopping": "停止中…", + "thinking": "思考中", + "toggleDetails": "ターンの詳細を切り替えます", + "workedFor": "{{value0}} で完了", + "working": "処理中…", + "workingFor": "{{value0}} 処理中" + }, + "usageLimit": { + "reached": "{{agent}} が使用量の上限に達しました。", + "reachedNamed": "{{agent}} が{{window}}の上限に達しました。", + "resetsAt": "{{when}} にリセットされます。", + "theAgent": "Agent", + "windowCredits": "追加使用量", + "windowSession": "セッション", + "windowWeekly": "週間", + "windowWeeklyModel": "モデル別の週間" + }, + "useImageUpload": { + "attachFailed": "添付に失敗しました。", + "disconnected": "添付に失敗しました(接続切断)。", + "imageTooLarge": "画像が大きすぎて添付できません。", + "permissionDenied": "写真へのアクセスが拒否されました。" + } + }, "common": { "back": "戻る", "cancel": "キャンセル", @@ -151,6 +508,8 @@ "done": "完了", "edit": "編集", "ok": "OK", + "open": "開く", + "refresh": "更新", "remove": "削除", "retry": "再試行", "save": "保存", @@ -757,8 +1116,51 @@ "unexpectedResponse": "予期しない応答" }, "webShellUpdateFailureCopy": { + "blockedReason": "ブロックしました: {{reason}}。", + "blockedWorkspace": "ワークスペースをブロックしました。", + "failed": "エラー画面を表示しました。", "failedAgo": "{{hostName}} からの前回の更新は {{time}} 前に失敗しました:{{reason}}{{offered}}。{{outcome}}", - "failedJustNow": "たった今、{{hostName}} からの前回の更新に失敗しました:{{reason}}{{offered}}。{{outcome}}" + "failedJustNow": "たった今、{{hostName}} からの前回の更新に失敗しました:{{reason}}{{offered}}。{{outcome}}", + "hostCodeCopy": { + "assetChanged": "ホスト上でアセットが変更されました", + "assetUnknown": "ホストがアセットを認識できませんでした", + "buildChanged": "ダウンロード中にホストのビルドが変更されました", + "offsetInvalid": "ホストが読み取りオフセットを拒否しました", + "readLimited": "ホストが同時読み取りを制限しました", + "unavailable": "ホストにワークスペースバンドルがありません" + }, + "nativeRoute": "ネイティブ画面を表示しました。", + "offeredGeneration": " (世代 {{generation}})", + "openedCached": "保存済みのバージョンに切り替えました。", + "openedCachedGeneration": "保存済みのバージョン(世代 {{generation}})に切り替えました。", + "reasonCopy": { + "assetChecksumMismatch": "アセットのチェックサムが一致しませんでした", + "assetEntryChanged": "アセットがマニフェストと一致しなくなりました", + "assetNoProgress": "アセットの読み取りが進みませんでした", + "assetOverlong": "アセットのサイズがマニフェストに記載されたサイズを超えていました", + "assetShort": "アセットの読み取りが指定されたサイズに達する前に終了しました", + "buildChangedMidFetch": "ダウンロード中にホストのビルドが変更されました", + "cacheWriteFailed": "ダウンロードしたデータをこのスマートフォンに保存できませんでした", + "chunkMisrouted": "チャンクが要求と異なるアセットまたはオフセットのものでした", + "chunkOversize": "チャンクがホストの許容サイズを超えていました", + "connectionLost": "接続が切断されました", + "fetchStopped": "ダウンロードが停止されました", + "hostRefused": "ホストが読み取りを拒否しました", + "noConnection": "ホストに接続されていません", + "rangeUndecodable": "圧縮された読み取りデータをデコードできませんでした", + "replyUnreadable": "ホストからの応答をこのアプリで読み取れませんでした", + "unrecognisedError": "不明なエラー" + }, + "routeUnavailable": "画面が利用できない旨を表示しました。", + "waiting": "ホストを待ちました。", + "wallCopy": { + "bundleShellTooOld": "このアプリのバージョンが古いため、保存済みのバンドルに対応していません", + "bundleTooOldForHost": "保存済みのバンドルが古いため、ホストに対応していません", + "bundleUnavailable": "ホストにワークスペースバンドルがありません", + "hostTooOldForBundle": "ホストのバージョンが古いため、保存済みのバンドルに対応していません", + "pageRouteGrantMissing": "この画面には、より新しいバージョンのアプリが必要です", + "pageRouteUnlisted": "ホストのワークスペースにこの画面は含まれていません" + } } }, "dictation": { @@ -775,6 +1177,95 @@ "updateFailed": "ディクテーション設定の更新に失敗しました。" } }, + "files": { + "fileExplorerPanel": { + "backToSession": "セッションに戻る", + "closeFiles": "ファイル一覧を閉じる", + "connecting": "デスクトップに接続中…", + "emptyState": "ファイルが見つかりません。", + "loadFailed": "ファイルを読み込めませんでした。", + "loadInterrupted": "フォルダーの読み込みが中断されました。", + "title": "ファイル", + "truncatedList": "{{worktreeLabel}} - 先頭の 5000 件を表示", + "waiting": "デスクトップへの接続を待っています…" + }, + "fileExplorerRow": { + "fileUnavailable": "「{{name}}」はモバイルでは利用できません", + "loadFolderFailed": "フォルダーを読み込めませんでした", + "loading": "読み込み中…", + "openFolder": "フォルダー「{{name}}」を開く", + "previewFile": "ファイル「{{name}}」をプレビュー", + "retryLoading": "{{path}} の読み込みを再試行", + "unavailable": "モバイルでは利用不可" + }, + "fileMarkdownPreview": { + "copyLoadedSource": "読み込み済みの Markdown ソースをコピー", + "copySource": "Markdown ソースをコピー", + "viewPreview": "レンダリング済みの Markdown プレビューを表示", + "viewSource": "Markdown ソースを表示" + }, + "fileMediaPreview": { + "downloading": "ダウンロード中({{bytes}} / {{total}})", + "loadFailed": "メディアを読み込めませんでした。", + "loading": "メディアを読み込んでいます…", + "waiting": "デスクトップへの接続を待っています…" + }, + "fileMutationOwnership": { + "sshOwnerChanged": "SSH 接続を確認できませんでした。ホストに再接続して、再試行してください。" + }, + "filePreviewBody": { + "emptyFile": "空のファイル", + "imageLabel": "{{title}} の画像" + }, + "filePreviewEditableSource": { + "editorLabel": "{{title}} のエディター" + }, + "filePreviewResponse": { + "binaryUnavailable": "バイナリファイルはプレビューできません", + "bytes": "{{size}} B", + "fileChanged": "デスクトップでファイルが変更されました。プレビューを再読み込みしてから保存してください。", + "fileNotFound": "ファイルが見つかりません", + "fileTooLarge": "ファイルが大きすぎるため、モバイルではプレビューできません", + "filesystemUnreachable": "デスクトップのファイルシステムにアクセスできませんでした", + "kilobytes": "{{size}} KB", + "loadFailed": "プレビューを読み込めませんでした", + "megabytes": "{{size}} MB", + "reloadBeforeSaving": "プレビューを再読み込みしてから保存してください。", + "unknownSize": "サイズ不明" + }, + "filePreviewScreen": { + "backToFiles": "ファイル一覧に戻る", + "chooseWorkspaceFile": "このワークスペース内のファイルを選択してください。", + "discard": "破棄", + "discardMessage": "未保存の編集内容は失われます。", + "discardTitle": "変更を破棄しますか?", + "fileTitle": "ファイル", + "loadFailed": "プレビューを読み込めませんでした。", + "loading": "プレビューを読み込んでいます…", + "openFailed": "ファイルを開けませんでした。", + "saveArtifact": "ターミナルのアーティファクトを保存", + "saveFailed": "ファイルを保存できませんでした。", + "stay": "編集を続ける", + "title": "プレビュー", + "waiting": "デスクトップへの接続を待っています…" + }, + "filePreviewSourceText": { + "previewLabel": "ファイルのプレビュー", + "truncatedNote": "プレビューは一部省略されています。ファイルサイズ: {{size}}。" + }, + "markdownPreviewImage": { + "expandFallback": "画像を拡大", + "expandImage": "「{{alt}}」を拡大", + "imageLabel": "Markdown の画像", + "loadFailed": "画像を読み込めませんでした" + }, + "pdfPreview": { + "open": "PDF を開く", + "openFailed": "この PDF を開けませんでした", + "opening": "PDF を開いています…", + "updateRequired": "PDF を開くには Orca Mobile を更新してください。" + } + }, "home": { "accountUsageCards": { "sectionHeading": "アカウントの使用状況", @@ -1103,8 +1594,10 @@ "noComments": "コメントはまだありません。", "noDescription": "説明はありません。", "noHumanComments": "ユーザーによるコメントはありません。", - "resolvedComment_other": "{{author}} の解決済みコメント({{count}} 件)", - "resolvedThread_other": "{{author}} の解決済みスレッド({{count}} 件)", + "resolvedCommentCount": "{{author}} の解決済みコメント({{count}} 件)", + "resolvedCommentSingle": "{{author}} の解決済みコメント", + "resolvedThreadCount": "{{author}} の解決済みスレッド({{count}} 件)", + "resolvedThreadSingle": "{{author}} の解決済みスレッド", "showMore": "さらに {{count}} 件を表示", "showMoreOf": "残り {{remaining}} 件のうち {{count}} 件を表示", "title": "コメント" @@ -1296,6 +1789,596 @@ "title": "レビュアー" } }, + "session": { + "accessoryKeys": { + "addCustomShortcut": "カスタムショートカットを追加", + "dismissKeyboard": "キーボードを閉じる", + "dismissKeyboardHint": "現在のターミナルセッションを開いたまま、ソフトウェアキーボードを非表示にします。", + "paste": "貼り付け", + "pasteFromClipboard": "クリップボードから貼り付け", + "sendKey": "{{label}} を送信", + "switchToBufferedInput": "バッファー式のコマンド入力に切り替え", + "switchToDesktopMode": "デスクトップモードに切り替え", + "switchToLiveInput": "ターミナルへのリアルタイム入力に切り替え", + "switchToPhoneMode": "スマートフォンモードに切り替え" + }, + "activeContent": { + "createTab": "タブを作成", + "creatingTab": "作成中…", + "fileTitle": "ファイル", + "loadingTerminal": "ターミナルを読み込み中…", + "noTabs": "このセッションにはタブがありません。", + "retryLoadingTerminal": "ターミナルの読み込みを再試行", + "terminalTakingLonger": "ターミナルの読み込みに予想以上の時間がかかっています。" + }, + "agentChildRow": { + "agent": "Agent", + "days_one": "{{count}}日", + "days_other": "{{count}}日", + "ended": "終了", + "hours_one": "{{count}}時間", + "hours_other": "{{count}}時間", + "minutes_one": "{{count}}分", + "minutes_other": "{{count}}分", + "noRecentUpdate": "最近の更新なし", + "noUpdate": "{{value0}}更新なし" + }, + "agentMessageAttribution": { + "from": "{{sender}} から", + "fromMore": "{{sender}} ほか {{count}} 名から", + "fromUnnamed": "Agent から", + "messageFrom": "{{sender}} からのメッセージ", + "messageFromMore": "{{sender}} ほか {{count}} 名からのメッセージ", + "messageFromUnnamed": "Agent からのメッセージ", + "unnamedSender": "Agent" + }, + "aiVaultResumeLaunch": { + "createFailed": "ターミナルの作成に失敗しました。", + "inputLocked": "ターミナル入力はロックされています。", + "sendFailed": "再開コマンドの送信に失敗しました。" + }, + "aiVaultResumePreparation": { + "prepareFailed": "この旧形式の Codex セッションを準備できませんでした。再開を再試行してください。" + }, + "backgroundTask": { + "agent": "バックグラウンド Agent", + "command": "バックグラウンドコマンド", + "countAgents_one": "{{count}} Agent", + "countAgents_other": "{{count}} Agent", + "countMonitors_one": "{{count}} モニター", + "countMonitors_other": "{{count}} モニター", + "countShellCommandOne": "1 シェルコマンド", + "countShell_one": "{{count}} シェル", + "countShell_other": "{{count}} シェル", + "countTasks_one": "{{count}} タスク", + "countTasks_other": "{{count}} タスク", + "countWorkflows_one": "{{count}} ワークフロー", + "countWorkflows_other": "{{count}} ワークフロー", + "detailsUnavailable": "このセッションではタスクの詳細は利用できません。", + "groupAgents": "Agent", + "groupMonitors": "モニター", + "groupShell": "シェル", + "groupTasks": "タスク", + "groupWorkflows": "ワークフロー", + "headerTotalCount_one": "{{count}} 件のバックグラウンドタスク", + "headerTotalCount_other": "{{count}} 件のバックグラウンドタスク", + "monitor": "バックグラウンドモニター", + "monitoring": "バックグラウンドタスクの監視", + "reasonBlocked": "失敗した", + "reasonWaiting": "承認が必要です", + "stateBlocked": "ブロックされました", + "stateBlockedCount": "{{value0}} ブロックされました", + "stateDone": "終わり", + "stateDoneCount": "{{value0}} 終わり", + "stateIdle": "止まった", + "stateIdleCount": "{{value0}} 止まった", + "stateMonitoring": "監視", + "stateMonitoringCount": "{{value0}} 監視", + "stateUnverifiable": "状態を取得できません", + "stateUnverifiableCount": "{{value0}} は状態を取得できません", + "stateWaiting": "待機中", + "stateWaitingCount": "{{value0}} 待機中", + "stateWorking": "実行中", + "stateWorkingCount": "{{value0}} 実行中", + "stop": "停止", + "stopAll": "バックグラウンドタスクを停止する", + "stopTask": "{{value0}} を停止してください", + "task": "バックグラウンドタスク", + "workflow": "バックグラウンドワークフロー" + }, + "browserTabActionSheet": { + "forward": "進む", + "reload": "リロード", + "title": "ブラウザ" + }, + "bulkCloseSheetActions": { + "closeOtherTabs": "他のタブを閉じる" + }, + "bulkTabClose": { + "confirm": "タブを閉じる", + "description": "選択したタブを閉じます。ピン留めしたタブは開いたままになります。", + "titleEditors": "すべてのエディタータブを閉じますか?", + "titleLeft": "左側のタブを閉じますか?", + "titleOthers": "他のタブを閉じますか?", + "titleRight": "右側のタブを閉じますか?" + }, + "commandDock": { + "commandPlaceholder": "コマンドを入力してください…", + "liveInputHint": "入力したテキストは、選択中のターミナルに直接送信されます。", + "liveInputLabel": "ターミナルへのリアルタイム入力", + "sendCommand": "コマンドを送信" + }, + "contentRow": { + "dismissCreationWarning": "ワークスペース作成の警告を閉じる" + }, + "diffCommentLineRow": { + "addNote": "{{line}} 行目にメモを追加", + "deleteNote": "{{line}} 行目のメモを削除", + "diffLine": "{{title}} の差分 {{line}} 行目", + "line": "{{line}} 行目", + "placeholder": "レビューメモを追加", + "saveNote": "メモを保存" + }, + "diffLineRow": { + "addNoteLabel": "{{line}} 行目にメモを追加", + "deleteNoteLabel": "{{line}} 行目のメモを削除", + "diffLineLabel": "{{title}} の差分、{{line}} 行目", + "lineNumber": "{{line}} 行目", + "notePlaceholder": "レビューメモを追加", + "saveNote": "メモを保存" + }, + "diffReviewLoaders": { + "changesLoadFailed": "変更を読み込めません。", + "committedChangesFailed": "コミット済みの変更の読み込みに失敗しました。", + "committedChangesUnavailable": "コミット済みの変更は利用できません。", + "committedDiffUnavailable": "コミット済みの差分は利用できません。", + "desktopUpdateRequired": "モバイルで変更をレビューするには、デスクトップの Orca を更新してください。", + "diffLoadFailed": "差分を読み込めません。", + "invalidSourceControlResponse": "ソース管理からの応答が無効でした。", + "reviewNotesLoadFailed": "レビューノートを読み込めません。" + }, + "diffReviewQueue": { + "committedOnBranch": "ブランチでコミット済み", + "staged": "ステージ済み", + "unstaged": "未ステージ" + }, + "diffReviewScreenModel": { + "branch": "ブランチ", + "staged": "ステージ済み", + "unstaged": "未ステージ" + }, + "fileReader": { + "copyReviewNotes": "レビューメモをコピー", + "imageLabel": "{{title}} の画像", + "noReviewNotes": "レビューメモなし", + "previewLabel": "{{title}} のプレビュー", + "reviewNotes_one": "{{count}} 件のレビューメモ", + "reviewNotes_other": "{{count}} 件のレビューメモ", + "send": "送信", + "sendReviewNotes": "レビューメモを AI に送信" + }, + "floatingWorkspace": { + "title": "フローティングワークスペース" + }, + "githubPrMutations": { + "reviewThreadUpdateFailed": "レビュースレッドの更新に失敗しました。", + "titleUpdateFailed": "タイトルの更新に失敗しました。" + }, + "githubPrRpc": { + "requestFailed": "リクエストに失敗しました: {{method}}" + }, + "header": { + "backToWorktrees": "ワークツリー一覧に戻る", + "moreActions": "セッションのその他の操作", + "openFileExplorer": "ファイルエクスプローラーを開く", + "openSourceControl": "ソース管理を開く", + "reconnectToDesktop": "デスクトップに再接続", + "showSidebar": "サイドバーを表示", + "terminalTitle": "ターミナル" + }, + "headerMoreActionsSheet": { + "agentHistory": "Agent の履歴", + "agentHistoryHint": "Agent セッションを閲覧・再開", + "checks": "チェック", + "checksHint": "PR のチェックを開く" + }, + "markdownReader": { + "changedOnDesktop": "デスクトップで変更済み", + "copyLoaded": "読み込んだ部分をコピー", + "copyLoadedSource": "読み込み済みの Markdown ソースをコピー", + "copyPath": "パスをコピー", + "copyPathLabel": "Markdown ファイルのパスをコピー", + "copySource": "Markdown ソースをコピー", + "discard": "破棄", + "dismissKeyboard": "キーボードを閉じる", + "dismissKeyboardHint": "Markdown エディターを開いたまま、ソフトウェアキーボードを非表示にします。", + "readOnly": "読み取り専用" + }, + "nativeChatAttachActions": { + "availableSkills": "{{count}} 件が利用可能", + "commands": "コマンド", + "fileTypes": "PDF、テキスト、コード…", + "files": "ファイル", + "photos": "写真", + "skills": "スキル", + "workspaceFile": "ワークスペースファイル" + }, + "nativeChatCardHeaderAction": { + "collapse": "折りたたむ" + }, + "nativeChatCliFork": { + "resumeInNewCli": "新しい CLI で再開" + }, + "nativeChatFloatingControls": { + "scrollToLatest": "最新の内容にスクロール" + }, + "nativeChatFullContent": { + "loadMessageFailed": "メッセージ全体を表示(読み込み失敗、タップして再試行)", + "loadOutputFailed": "出力をすべて表示(読み込み失敗、タップして再試行)", + "partialMessage": "このメッセージの一部を表示しています。", + "partialOutput": "{{total}} のうち {{shown}} を表示しています。", + "showFullMessage": "メッセージ全体を表示", + "showFullOutput": "出力をすべて表示" + }, + "nativeChatRenderData": { + "defaultAgentLabel": "Agent", + "emptySubtitle": "{{agent}} にコードの確認や出力の説明、変更を依頼してください。", + "emptyTitle": "{{agent}} とチャットを開始", + "errorSubtitle": "トランスクリプトを読み取ることができませんでした。作業を続けるには、ターミナルに切り替えてください。", + "errorTitle": "会話を読み込めませんでした", + "notFoundSubtitle": "メッセージを送信して会話を始めるか、ターミナル表示を開いて {{agent}} の動作を確認してください。", + "notFoundTitle": "この会話はまだ見つかりません" + }, + "nativeChatToggleAction": { + "switchToChatView": "チャット表示に切り替え", + "switchToTerminalView": "ターミナル表示に切り替え" + }, + "newTabAgentHostLaunch": { + "notesNotSent": "Agent は起動しましたが、メモは送信されませんでした。", + "notesSent": "メモを送信しました。", + "notesUnconfirmed": "Agent は起動しましたが、メモの送信を確認できませんでした。", + "promptUnconfirmed": "Agent は起動しましたが、プロンプトの送信を確認できませんでした。" + }, + "prActionsEngine": { + "mergeFailed": "PR のマージに失敗しました。", + "refreshFailed": "PR の更新に失敗しました。" + }, + "prAiTriageLaunch": { + "agentStarted": "Agent を起動しました。", + "agentStartedIn": "「{{label}}」で Agent を起動しました。", + "emptyPrompt": "この操作用に保存されたプロンプトが空です。コンピュータで「ソース管理 AI」の設定を更新してください。", + "loadFailed": "利用可能な Agent を読み込めませんでした。", + "promptNotSent": "Agent は起動しましたが、プロンプトは送信されませんでした。", + "updateRequired": "スマートフォンから Agent を起動するには、コンピュータの Orca を更新してください。" + }, + "prSidebarState": { + "loadFailed": "PR を読み込めませんでした。" + }, + "queuedCardText": { + "showLess": "折りたたむ", + "showLessHint": "メッセージを折りたたんで表示します。", + "showMore": "さらに表示", + "showMoreHint": "メッセージ全体を表示します。" + }, + "queuedMessageCardRow": { + "deleteHint": "キュー内のこのメッセージを削除", + "images_one": "画像 {{count}} 枚", + "images_other": "画像 {{count}} 枚", + "moreActions": "その他の操作", + "send": "送信", + "sendAgainHint": "このメッセージを再送信", + "sendHint": "このメッセージを送信", + "showLessHint": "メッセージを折りたたんで表示します。", + "steer": "今すぐ送信", + "steerHint": "モデルを中断せずに送信します" + }, + "queuedMessageComposer": { + "commandsNotAllowed": "キュー内のメッセージにコマンドは保存できません。", + "editing": "メッセージを編集中", + "editingAttributed": "メッセージを編集中 · {{attribution}}" + }, + "quickCommandEditorForm": { + "action": "操作", + "addQuickCommand": "クイックコマンドを追加", + "advanced": "詳細設定", + "agent": "Agent", + "agentPrompt": "Agent プロンプト", + "appendEnter": "Enter を追加", + "appendEnterDescription": "テキストの挿入後、すぐに送信します。", + "chooseAgent": "Agent を選択", + "commandText": "コマンドテキスト", + "globalScope": "グローバル", + "label": "ラベル", + "labelPlaceholder": "開発サーバーを起動する", + "olderHostPromptTooLong": "このホストには長すぎます。より長いプロンプトを保存するには、ホストの Orca を更新してください。", + "projectScope": "プロジェクト", + "prompt": "プロンプト", + "promptHint": "スキル、ファイルパス、組み込みコマンドに対応しています。", + "promptPlaceholder": "Agent にこのワークスペースを調査するよう依頼します", + "promptSizeCharacters": "{{used}} / {{max}} 文字", + "promptSizeKilobytes": "{{used}} KB / {{max}} KB", + "promptTooLong": "長すぎるため保存できません。", + "scope": "スコープ", + "terminalCommand": "ターミナルコマンド" + }, + "quickCommandLaunch": { + "defaultLabel": "クイックコマンド", + "editBeforeRunning": "実行前にこのクイックコマンドを編集してください。", + "runFailed": "「{{label}}」を実行できませんでした。" + }, + "quickCommandRow": { + "copyCommand": "「{{label}}」をコピー", + "copyFailed": "コピーできませんでした", + "deleteCommand": "「{{label}}」を削除", + "editCommand": "「{{label}}」を編集", + "nothingToCopy": "コピーする内容がありません", + "runCommand": "「{{label}}」を実行" + }, + "quickCommandsList": { + "emptyState": "クイックコマンドはまだありません。", + "globalGroup": "グローバル", + "newCommand": "新しいクイックコマンド", + "noMatches": "一致するクイックコマンドはありません。", + "projectGroup": "このプロジェクト", + "searchPlaceholder": "クイックコマンドを検索…" + }, + "quickCommandsSheet": { + "addTitle": "クイックコマンドを追加", + "chooseAgentTitle": "Agent を選択", + "deleteConfirmMessage": "このクイックコマンドは保存されたリストから削除されます。", + "deleteConfirmTitle": "「{{label}}」を削除しますか?", + "editTitle": "クイックコマンドの編集", + "editorDescription": "ターミナルコマンドや Agent プロンプトを保存して、すぐに使えるようにします。", + "title": "クイックコマンド", + "untitled": "無題" + }, + "quickCommandsTabButton": { + "quickCommands": "クイックコマンド" + }, + "routeHelpers": { + "connected": "接続済み", + "connecting": "接続中", + "disconnected": "切断済み", + "pairingInvalid": "ペアリングが無効", + "reconnecting": "再接続中", + "securing": "接続を保護中" + }, + "routeScreen": { + "openingWorkspace": "ワークスペースを開いています…" + }, + "sheets": { + "browser": "ブラウザ", + "browserUpdateRequired": "モバイルでのブラウザストリーミングにはデスクトップの更新が必要です。", + "chat": "チャット", + "copyAllAndLeave": "すべてコピーして戻る", + "copyDraftsFailed": "下書きをコピーできませんでした。", + "copyNotes": "メモをコピー", + "copyNotesFailed": "メモをコピーできませんでした。", + "copyPath": "パスのコピー", + "discard": "破棄", + "discardAndLeave": "破棄して戻る", + "discardChanges": "変更を破棄する", + "discardMessage": "スマートフォンの下書きをデスクトップの最新ファイルで置き換えますか?", + "draftsHint": "画面を離れる前に、スマートフォンの下書きをコピーするか破棄してください。", + "file": "ファイル", + "markdown": "Markdown", + "markdownNote": "Markdown メモ", + "newBrowser": "新規ブラウザ", + "newTab": "新規タブ", + "notesCopied": "メモをコピーしました。", + "notesHint": "現在のメモの送信先となる Agent セッションを選択してください。", + "removeShortcutMessage": "このカスタムショートカットを削除しますか?", + "renameTerminal": "ターミナル名を変更", + "sendReviewNotes": "レビューメモを送信", + "shortcut": "ショートカット", + "terminal": "ターミナル", + "terminalName": "ターミナル名", + "unsavedMarkdown": "Markdown の未保存の変更", + "urlPrompt": "URL を入力してください。空欄のままにすると新規タブが開きます。" + }, + "sideRail": { + "backToWorktrees": "ワークツリーに戻る" + }, + "sourceControlLaunchAgent": { + "noEnabledAgent": "このワークスペースホストでは有効な AI Agent が検出されませんでした。", + "savedAgentUnavailable": "この操作用に保存された Agent は、このワークスペースホストでは利用できません。" + }, + "structuredComposerCommand": { + "operationUnconfirmed": "会話の操作が実行されたことを確認できませんでした。", + "removeAttachments": "チャットセッションのコマンドを使う前に添付ファイルを削除してください。" + }, + "structuredSendDelivery": { + "messageNotSent": "メッセージは送信されませんでした" + }, + "tabStrip": { + "checkingCapabilities": "デスクトップの対応機能を確認中です。少し待ってから再試行してください。", + "newTab": "新規タブ", + "quickCommandsUpdateRequired": "クイックコマンドの利用にはデスクトップの更新が必要です。", + "tabStatus": "{{title}}、{{status}}" + }, + "terminalActionSheetActions": { + "clearTerminal": "ターミナルをクリア", + "rename": "名前を変更する", + "switchToDesktop": "デスクトップ表示に切り替え", + "switchToPhone": "スマートフォン表示に切り替え" + }, + "terminalInputActions": { + "attachFileHint": "長押しすると、写真の代わりにファイルを添付できます。", + "attachPhoto": "写真を添付", + "sendingImage": "画像を送信中…", + "startDictation": "音声ディクテーションを開始", + "startingDictation": "音声ディクテーションを開始中", + "stopDictation": "音声ディクテーションを停止", + "transcribingDictation": "音声ディクテーションを文字起こし中" + }, + "terminalLiveInputStatus": { + "listening": "聞き取り中", + "liveInput": "リアルタイム入力", + "preparingMic": "マイクを準備中", + "processing": "処理中", + "releaseMicHint": "マイクボタンから指を離すと停止", + "startingMic": "マイクを起動中", + "stopHint": "マイクをタップして停止", + "transcribing": "デスクトップで文字起こし中", + "uploadingImage": "ホストに画像をアップロード中", + "uploadingImageTitle": "画像をアップロード中" + }, + "terminalRecords": { + "defaultTitle": "ターミナル" + }, + "terminalTabAgent": { + "browser": "ブラウザ", + "chat": "チャット", + "file": "ファイル", + "markdown": "Markdown", + "newBrowser": "新規ブラウザ", + "terminal": "ターミナル" + }, + "terminalViewportResubscribe": { + "fitFailed": "ターミナルをこの画面に合わせられませんでした。" + }, + "useAccessorySelection": { + "copyFailed": "コピーできませんでした", + "selectionCleared": "選択を解除しました(スクロールでバッファーの範囲外になりました)。" + }, + "useAttachmentInputLeaseGate": { + "reconnecting": "添付に失敗しました(再接続中)。" + }, + "useContentCreateActions": { + "browserCommandFailed": "ブラウザコマンドの実行に失敗しました。", + "browserCreateFailed": "ブラウザの作成に失敗しました。", + "browserUnavailable": "ブラウザのページはまだ利用できません。", + "browserUpdateRequired": "モバイルでのブラウザストリーミングにはデスクトップの更新が必要です。", + "invalidUrl": "有効な URL を入力してください。", + "noteCreateFailed": "Markdown ノートの作成に失敗しました。", + "untitledNoteCreateFailed": "無題の Markdown ノートを作成できませんでした。" + }, + "useDiffComments": { + "copyNotesFailed": "メモをコピーできませんでした。", + "deleteNoteFailed": "メモの削除に失敗しました。", + "noteAdded": "メモを追加しました。", + "notesCopied": "メモをコピーしました。", + "saveNoteFailed": "メモの保存に失敗しました。", + "saveNotesFailed": "レビューノートの保存に失敗しました。", + "waitingForDesktop": "デスクトップへの接続待ち…" + }, + "useDiffReviewCommentActions": { + "saveFailed": "レビューの保存に失敗しました。", + "saveStateFailed": "レビューの状態を保存できませんでした。", + "waitingForDesktop": "デスクトップへの接続待ち…" + }, + "useDiffReviewController": { + "loadFailed": "レビューを読み込めませんでした。", + "missingWorktree": "ワークツリーが指定されていません。", + "waitingForDesktop": "デスクトップへの接続待ち…" + }, + "useDiffReviewDiffLoading": { + "loadFailed": "差分を読み込めませんでした。", + "waitingForDesktop": "デスクトップへの接続待ち…" + }, + "useDiffReviewGitActions": { + "actionFailed": "ソース管理の操作に失敗しました。", + "reviewedFilesStaged_one": "レビュー済みファイル {{count}} 件をステージしました。", + "reviewedFilesStaged_other": "レビュー済みファイル {{count}} 件をステージしました。", + "stageResults": "{{staged}} 件はステージ済み、{{failed}} 件は失敗", + "waitingForDesktop": "デスクトップへの接続待ち…" + }, + "useDiffReviewInteractions": { + "openFailed": "セッションで開けませんでした。" + }, + "useDiffReviewSendActions": { + "copyFailed": "レビューノートをコピーできませんでした。", + "inputLocked": "ターミナルへの入力がロックされています。", + "loadSessionsFailed": "Agent セッションを読み込めませんでした。", + "notesCopied": "レビューノートをコピーしました。", + "notesSent": "レビューノートを送信しました。", + "notesSentWithWarning": "レビューノートを送信しました。 {{warning}}", + "promptNotSent": "Agent は起動しましたが、メモは送信されませんでした。「メモをコピー」でコピーして貼り付けてください。", + "sendFailed": "メモの送信に失敗しました。", + "startingAgent": "Agent を起動中…", + "waitingForDesktop": "デスクトップへの接続待ち…" + }, + "useImageAttachment": { + "attachFailed": "添付に失敗しました。", + "disconnected": "添付に失敗しました(切断済み)。", + "imageTooLarge": "画像が大きすぎるため添付できません。", + "permissionDenied": "写真へのアクセスが拒否されました。" + }, + "useMarkdownActions": { + "copyFailed": "コピーできませんでした", + "copyPathFailed": "パスをコピーできませんでした。", + "markdownTitle": "Markdown", + "pathCopied": "パスをコピーしました", + "saveFailed": "保存に失敗しました。", + "saved": "保存済み" + }, + "useNativeChatDictation": { + "insertFailed": "ディクテーションの結果を挿入できませんでした。", + "insertFailedCopied": "ディクテーションの結果を挿入できず、クリップボードにコピーしました。", + "inserted": "ディクテーションの結果を挿入しました。" + }, + "useNativeChatStop": { + "stopNotSent": "停止要求を送信できませんでした。", + "stopUnconfirmed": "停止を確認できませんでした。再試行前にチャットを確認してください。", + "terminalNotReady": "停止要求を送信できませんでした(ターミナルの準備ができていません)。" + }, + "usePanelRouteActions": { + "checkHostHint": "ホストの接続を確認してください", + "copyNotesHint": "代わりにメモをコピーしてください", + "detectingAgents": "Agent を検出中", + "newSessionHint": "新規 Agent セッション", + "noEnabledAgents": "有効な Agent なし", + "otherServerAgents": "別のサーバーの Agent", + "pairServerHint": "そのサーバーと直接ペアリングしてください", + "presetsUnavailable": "Agent プリセットを利用できません" + }, + "usePrAiTriage": { + "launchFailed": "Agent の起動に失敗しました。", + "waitingForDesktop": "デスクトップへの接続待ち…" + }, + "usePrCommentActions": { + "actionFailed": "コメントの操作に失敗しました。" + }, + "usePrTitleAction": { + "disconnected": "デスクトップに接続されていません。", + "updateFailed": "タイトルの更新に失敗しました。" + }, + "useQuickCommands": { + "loadFailed": "クイックコマンドの読み込みに失敗しました。", + "saveFailed": "クイックコマンドの保存に失敗しました" + }, + "useSessionTerminalSendActions": { + "inputTooLarge": "入力データが大きすぎます(最大 256 KiB)。" + }, + "useStartup": { + "wakeAgents": "スリープ中の Agent を再開するには、ホストで Orca を開いてください。" + }, + "useTerminalCreateActions": { + "createFailed": "ターミナルの作成に失敗しました。", + "forkBusy": "別のターミナルを開いています。しばらく待ってから {{agentName}} のチャットを再試行してください。", + "forkUnavailable": "接続と更新が完了したホストで {{agentName}} のチャットを開き、再試行してください。", + "inputLocked": "別のクライアントがターミナル入力をロックしています。", + "notesSent": "メモを送信しました。", + "sendNotesError": "メモを送信できませんでした。", + "sendNotesFailed": "メモの送信に失敗しました。", + "terminalTitle": "ターミナル" + }, + "useTerminalInput": { + "clearFailed": "ターミナルをクリアできませんでした。", + "cleared": "ターミナルをクリアしました。" + }, + "useTerminalPaste": { + "clipboardReadFailed": "クリップボードを読み取れませんでした。", + "disconnected": "貼り付けに失敗しました(接続が切れています)。", + "imageTooLarge": "画像が大きすぎるため貼り付けできません。", + "pasteFailed": "貼り付けに失敗しました。", + "pasteTooLarge": "貼り付けるデータが大きすぎます(最大 256 KiB)。", + "terminalChanged": "貼り付けをキャンセルしました(ターミナルが変更されました)。" + }, + "worktreeLabel": { + "fallback": "ワークツリー" + } + }, "settings": { "aboutScreen": { "openLinkFailed": "リンクを開けませんでした。再試行してください。", @@ -1436,6 +2519,186 @@ "updateFailed": "更新できませんでした" } }, + "sourceControl": { + "actions": { + "commits": "コミット", + "createPr": "PR を作成", + "pushAndCreatePr": "プッシュして PR を作成", + "switchBranch": "ブランチを切り替え" + }, + "commitDropdown": { + "checkOutBranchToPush": "コミットをプッシュする前にブランチをチェックアウトしてください", + "checkOutBranchToSync": "コミットを同期する前にブランチをチェックアウトしてください", + "checkingBranchStatus": "ブランチの状態を確認中…", + "checkingPrStatus": "PR のステータスを確認中…", + "commit": "Commit", + "commitAndForcePush": "ステージ済みの変更をコミットし、lease 付きで強制プッシュ", + "commitAndPush": "ステージ済みの変更をコミットしてプッシュ", + "commitAndTryPush": "ステージ済みの変更をコミットし、プッシュを試行", + "commitForcePush": "コミットとフォースプッシュ", + "commitPush": "コミットとプッシュ", + "commitStagedChanges": "ステージ済みの変更をコミット", + "commitSync": "コミットと同期", + "commitThenPullPush": "コミットしてからプルとプッシュ", + "enterMessage": "コミットメッセージを入力してコミット", + "linkedReviewTargetUnavailable": "リンクされたレビューブランチのターゲットを利用できません", + "prAlreadyMerged": "PR はすでにマージされています", + "publishBranchFirstToPush": "コミットをプッシュするには、先にブランチを公開してください", + "publishBranchFirstToSync": "コミットを同期するには、先にブランチを公開してください", + "resolveConflicts": "コミットする前に競合を解決する", + "stageFile": "コミットするには、少なくとも 1 つのファイルをステージングしてください", + "useCommitAndForcePush": "「コミットとフォースプッシュ」を使用してください — リモートにはローカルコミットの古いコピーしかありません" + }, + "createPrAction": { + "createReview": "{{titleLabel}} を作成" + }, + "dropdownCounts": { + "fastForwardCount_one": "{{count}} 件のコミットを fast-forward", + "fastForwardCount_other": "{{count}} 件のコミットを fast-forward", + "pullCount_one": "{{count}} 件のコミットをプル", + "pullCount_other": "{{count}} 件のコミットをプル", + "pushCount_one": "{{count}} 件のコミットをプッシュ", + "pushCount_other": "{{count}} 件のコミットをプッシュ", + "remoteBranchFallback": "リモートブランチ", + "syncCounts": "{{behind}} 件をプル、{{ahead}} 件をプッシュ", + "titleForcePushOlderCopiesBranch": "リモートにはローカルコミットの古いコピーしかありません。このブランチを lease 付きで強制プッシュして {{upstreamName}} を更新します。", + "titleForcePushOlderCopiesCount_one": "リモートにはローカルコミットの古いコピーしかありません。{{count}} 件のブランチコミットを lease 付きで強制プッシュして {{upstreamName}} を更新します。", + "titleForcePushOlderCopiesCount_other": "リモートにはローカルコミットの古いコピーしかありません。{{count}} 件のブランチコミットを lease 付きで強制プッシュして {{upstreamName}} を更新します。", + "titleManualForcePushReplaceRemote_one": "{{count}} 件のローカルコミットを lease 付きで強制プッシュして {{upstreamName}} を更新し、リモートにのみあるコミットを置き換えます。", + "titleManualForcePushReplaceRemote_other": "{{count}} 件のローカルコミットを lease 付きで強制プッシュして {{upstreamName}} を更新し、リモートにのみあるコミットを置き換えます。", + "titleManualForcePush_one": "{{count}} 件のローカルコミットを lease 付きで強制プッシュして {{upstreamName}} を更新します。", + "titleManualForcePush_other": "{{count}} 件のローカルコミットを lease 付きで強制プッシュして {{upstreamName}} を更新します。", + "titleUnpublishedForcePushBranch": "このブランチを lease 付きで強制プッシュし、必要に応じてアップストリームを設定します。", + "titleUnpublishedForcePushCount_one": "{{count}} 件のブランチコミットを lease 付きで強制プッシュし、必要に応じてアップストリームを設定します。", + "titleUnpublishedForcePushCount_other": "{{count}} 件のブランチコミットを lease 付きで強制プッシュし、必要に応じてアップストリームを設定します。" + }, + "hostedReview": { + "mrCopy": { + "reviewLabel": "MR", + "shortLabel": "MR", + "titleLabel": "MR" + }, + "prCopy": { + "reviewLabel": "PR", + "shortLabel": "PR", + "titleLabel": "PR" + } + }, + "hostedReviewCreate": { + "pushSucceeded": "プッシュは成功しましたが、{{shortLabel}} の作成に失敗しました: {{error}}" + }, + "prCreate": { + "authRequired": "{{reviewLabel}} を作成する前に認証してください。", + "baseNotOnRemote": "{{reviewLabel}} を作成する前にベースブランチをプッシュしてください。", + "createdNotRefreshed": "{{titleLabel}} を作成しましたが、Orca はまだその情報を更新できていません。", + "defaultBranch": "{{reviewLabel}} を作成する前に、フィーチャーブランチに切り替えてください。", + "detachedHead": "{{reviewLabel}} を作成する前にブランチをチェックアウトしてください。", + "dirty": "{{reviewLabel}} を作成する前に変更をコミットしてください。", + "existing": "{{titleLabel}} はすでにオープンです。", + "existingNumber": "{{titleLabel}} #{{number}} はすでにオープンです。", + "existingReview": "このブランチの {{reviewLabel}} はすでに存在します。", + "forkHeadUnsupported": "このフォークからの {{reviewLabel}} の作成はサポートされていません。", + "lookupUnconfirmed": "このブランチに既存の {{reviewLabel}} があるかどうかを Orca で確認できませんでした。しばらくしてから再試行してください。", + "needsSync": "{{reviewLabel}} を作成する前にこのブランチを同期してください。", + "noUpstream": "{{reviewLabel}} を作成する前にコミットを公開してください。", + "notReady": "このブランチは、まだ {{reviewLabel}} を作成できる状態ではありません。", + "unsupportedMergeRequests": "このリポジトリでは MR の作成に対応していません。", + "unsupportedPullRequests": "このリポジトリでは PR の作成に対応していません。" + }, + "primaryAction": { + "forcePushFromDesktop": "デスクトップからこの PR のブランチを強制プッシュ" + }, + "remoteDropdown": { + "branchAlreadyPublished": "ブランチは既に公開されています", + "branchUpToDate": "ブランチは最新です", + "checkOutBranchToFastForward": "fast-forward する前にブランチをチェックアウトしてください", + "checkOutBranchToForcePush": "コミットを強制プッシュする前にブランチをチェックアウトしてください", + "checkOutBranchToPublish": "コミットを公開する前にブランチをチェックアウトしてください", + "checkOutBranchToPull": "コミットをプルする前にブランチをチェックアウトしてください", + "checkOutBranchToPush": "コミットをプッシュする前にブランチをチェックアウトしてください", + "checkOutBranchToSync": "コミットを同期する前にブランチをチェックアウトしてください", + "checkingBranchStatus": "ブランチの状態を確認中…", + "checkingPrStatus": "PR のステータスを確認中…", + "fastForward": "Fast-forward", + "fetch": "フェッチ", + "fetchTitle": "マージせずにリモートから取得", + "forcePush": "フォースプッシュ", + "linkedReviewBranchExists": "リンクされたレビューブランチは既に存在します", + "linkedReviewTargetUnavailable": "リンクされたレビューブランチのターゲットを利用できません", + "nothingNewToFastForward": "fast-forward する新しいものはありません — リモートにはローカルコミットの古いコピーしかありません", + "nothingNewToPull": "プルする新しいものはありません — リモートにはローカルコミットの古いコピーしかありません", + "nothingToFastForward": "fast-forward するものはありません", + "nothingToForcePush": "強制プッシュするものはありません", + "nothingToForcePushTo": "{{upstreamName}} に強制プッシュするものはありません", + "nothingToPull": "プルするものはありません", + "nothingToPush": "プッシュするものはありません", + "nothingToPushTo": "{{upstreamName}} にプッシュするものはありません", + "prAlreadyMerged": "PR はすでにマージされています", + "publishBranch": "ブランチを公開", + "publishBranchFirstToFastForward": "fast-forward するには、先にブランチを公開してください", + "publishBranchFirstToPull": "コミットをプルするには、先にブランチを公開してください", + "publishBranchFirstToSync": "コミットを同期するには、先にブランチを公開してください", + "publishLinkedReview": "リンク済みレビュー", + "publishNoBranch": "ブランチなし", + "publishPrStatus": "PR ステータス", + "publishToOrigin": "このブランチを origin に公開します", + "pull": "プル", + "push": "プッシュ", + "pushAndSetUpstream": "このブランチをプッシュし、必要に応じてアップストリームを設定", + "pushMayNeedSync": "ローカルのコミットをプッシュします。git が先に同期を要求する場合があります", + "pushToLinkedReview": "リンクされたレビューブランチに更新をプッシュ", + "rebaseChooseRemoteBase": "リベース元のリモートベースブランチを選択してください", + "rebaseDirtyHint": "リベースを試行します。git が先にローカルの変更のコミットまたは stash を要求する場合があります", + "rebaseFromBase": "ベースからリベース", + "rebaseFromNamedBase": "{{baseRef}} からリベース", + "rebaseWithLatestFrom": "現在のブランチを {{baseRef}} の最新コミットでリベース", + "sync": "同期", + "tryFastForwardPull": "fast-forward プルを試行します。git がローカルコミットを拒否する場合があります", + "tryRegularPush": "通常のプッシュを試行します。git が強制プッシュを要求する場合があります", + "useForcePushToSync": "強制プッシュを使用してください — リモートにはローカルコミットの古いコピーしかありません" + } + }, + "terminal": { + "keyDefinitions": { + "accessoryArrowDown": "下矢印", + "accessoryArrowLeft": "左矢印", + "accessoryArrowRight": "右矢印", + "accessoryArrowUp": "上矢印", + "arrowDown": "下矢印", + "arrowLeft": "左矢印", + "arrowRight": "右矢印", + "arrowUp": "上矢印", + "clearLineBeforeCursor": "カーソルから行頭まで削除", + "clearScreen": "画面をクリア", + "deleteWordBackward": "前の単語を削除", + "endOfLine": "行末", + "forwardDelete": "前方削除", + "interruptTerminal": "ターミナルを中断", + "reverseSearch": "逆方向検索", + "sendEof": "EOF を送信", + "startOfLine": "行頭", + "suspendProcess": "プロセスを一時停止" + }, + "webView": { + "documentStartFailed": "ターミナルドキュメントの起動に失敗しました - {{message}}", + "httpError": "ターミナルの WebView で HTTP エラーが発生しました", + "loadFailed": "ターミナルの WebView の読み込みに失敗しました", + "renderProcessEnded": "ターミナルの WebView の描画プロセスが終了しました" + }, + "webviewEngineErrorState": { + "code": "コード {{code}}", + "reload": "再読み込み", + "rendererCrashed": "レンダラーがクラッシュしました", + "status": "ステータス {{statusCode}}", + "title": "ターミナルの読み込みに失敗しました" + }, + "webviewNotificationDispatch": { + "unknownError": "不明なターミナルエラー" + }, + "webviewReadyWatchdog": { + "initializeFailed": "ターミナルを初期化できませんでした。ターミナルビューから準備完了の通知がありません。" + } + }, "webShell": { "shellStateScreen": { "checkingHost": "ホストを確認中", @@ -1450,6 +2713,18 @@ "tryAgain": "再試行" } }, + "workspace": { + "branchChoice": { + "automaticName": "自動で命名", + "existing": "既存のブランチ", + "existingHint": "新しいコミットはこのブランチに追加されます。", + "hostUpdateRequired": "ブランチを再利用するか、別のブランチを明示的に作成するには、ワークスペースのホストで Orca を更新してください。", + "new": "新しいブランチ", + "newHint": "新しいローカルブランチを作成", + "newName": "新しいブランチ名", + "title": "このワークツリーのブランチ" + } + }, "worktree": { "agentRowDisplay": { "blocked": "ブロック中", @@ -1489,16 +2764,5 @@ "inReview": "レビュー中", "pinned": "ピン留め済み" } - }, - "workspace": { - "branchChoice": { - "title": "このワークツリーのブランチ", - "existing": "既存のブランチ", - "existingHint": "新しいコミットはこのブランチに追加されます。", - "new": "新しいブランチ", - "newHint": "新しいローカルブランチを作成", - "newName": "新しいブランチ名", - "automaticName": "自動で命名" - } } } diff --git a/mobile/src/i18n/locales/ko.json b/mobile/src/i18n/locales/ko.json index e05fb4de30b..c0d830539f0 100644 --- a/mobile/src/i18n/locales/ko.json +++ b/mobile/src/i18n/locales/ko.json @@ -139,6 +139,363 @@ "title": "터미널" } }, + "browser": { + "addressField": { + "urlPlaceholder": "URL" + }, + "pane": { + "invalidUrl": "유효한 URL을 입력하세요." + }, + "paneView": { + "dialogTitle": "브라우저 대화상자", + "forward": "앞으로", + "keyboardPlaceholder": "페이지에 입력…", + "reload": "새로고침", + "sendText": "브라우저로 텍스트 전송" + }, + "pointerModifiers": { + "clickModifier": "{{key}} 클릭 보조 키" + }, + "useBrowserCommands": { + "deliveryUnconfirmed": "전달 여부 미확인 — 다시 시도하기 전에 페이지를 확인하세요", + "dialogAnswerFailed": "응답이 페이지에 전달되지 않았습니다.", + "sent": "전송됨" + }, + "viewModeSwitch": { + "mobileView": "웹사이트를 모바일 화면으로 보기", + "webView": "웹사이트를 데스크톱 화면으로 보기" + } + }, + "chat": { + "ask": { + "answerPlaceholder": "답변을 입력하세요", + "next": "다음", + "other": "기타…", + "skip": "건너뛰기", + "step": "{{step}}단계", + "submit": "제출" + }, + "askRow": { + "asked": "질문:", + "awaiting": "사용자 입력을 기다리는 중:", + "awaitingUnnamed": "사용자 입력을 기다리는 중", + "questionCount": "질문 {{value0}}개" + }, + "composer": { + "addToMessage": "메시지에 추가", + "attachImage": "이미지 첨부", + "cancelEditing": "편집 취소", + "placeholder": "무엇이든 물어보세요. @로 파일 참조, /로 명령어 실행", + "saveEditedMessage": "수정한 메시지 저장", + "sendMessage": "메시지 보내기" + }, + "composerAttachments": { + "attachedImage": "첨부된 이미지", + "file": "파일", + "fileMeta": "{{type}} · {{size}}", + "removeFile": "{{name}} 제거", + "removeImage": "이미지 제거", + "uploadingFile": "{{name}} 업로드 중" + }, + "composerPlaceholder": { + "prompt": "무엇이든 물어보세요. @로 파일 참조, /로 명령어 실행", + "reconnecting": "다시 연결 중…", + "waitingForTerminal": "터미널 대기 중…" + }, + "diffCard": { + "copyDiff": "차이점 복사", + "diffGap": "표시되지 않는 라인", + "diffTruncated": "잘린 차이점" + }, + "hostStatus": { + "continuedOutside": "이 채팅 밖에서 이어졌습니다.", + "continuedOutsideRewound": "이 채팅 밖에서 이어졌습니다. 위의 이전 답변은 더 이상 이 대화 기록에 없습니다.", + "historyItemTooLarge": "채팅의 이 부분은 너무 커서 표시할 수 없습니다.", + "historyRepaired": "이 채팅 기록의 일부를 불러올 수 없습니다." + }, + "message": { + "copyMessage": "메시지 복사" + }, + "messageActionsSheet": { + "clipboardRejected": "클립보드에서 텍스트를 거부했습니다.", + "copyFailed": "복사 실패", + "copyMessage": "메시지 복사", + "selectText": "텍스트 선택" + }, + "nativeChatImageViewer": { + "closeImage": "이미지 닫기" + }, + "nativeChatProse": { + "attachedImage": "첨부 이미지", + "image": "이미지", + "openImageHint": "이미지를 원본 크기로 엽니다." + }, + "nativeChatToolLine": { + "added": "추가", + "copyCommand": "명령 복사", + "deleted": "삭제", + "durationHours": "{{hours}}시간 {{minutes}}분 {{seconds}}초", + "durationMilliseconds": "{{milliseconds}}ms", + "durationMinutes": "{{minutes}}분 {{seconds}}초", + "durationSeconds": "{{seconds}}초", + "edited": "수정", + "editing": "수정 중", + "exitCode": "종료 코드 {{exitCode}}", + "fetched": "가져옴", + "fetching": "가져오는 중", + "listed": "목록 표시", + "listing": "목록 표시 중", + "ran": "실행", + "read": "읽음", + "reading": "읽는 중", + "renamed": "이름 변경", + "result": "결과", + "running": "실행 중", + "searched": "검색", + "searchedWeb": "웹 검색", + "searching": "검색 중", + "searchingWeb": "웹 검색 중", + "subagent": "서브에이전트", + "triedEdit": "수정 시도", + "triedFetch": "가져오기 시도", + "triedList": "목록 표시 시도", + "triedRead": "읽기 시도", + "triedSearch": "검색 시도", + "triedSearchWeb": "웹 검색 시도" + }, + "nativeChatUserMessageFold": { + "loadFailed": "전체 메시지 표시 — 불러오지 못했습니다. 탭하여 다시 시도하세요.", + "showFullMessage": "전체 메시지 표시", + "showLess": "접기" + }, + "nativeChatVisual": { + "defaultTitle": "시각화", + "openFullscreen": "{{title}}을(를) 전체 화면으로 열기", + "retryHint": "다시 불러오기를 시도합니다.", + "unavailable": "시각화를 사용할 수 없습니다" + }, + "notice": { + "compaction": "컨텍스트가 압축됨", + "contextCleared": "컨텍스트 지워짐", + "plan": "계획", + "stoppedManually": "수동으로 중지됨" + }, + "orcaStop": { + "canContinue": "이 대화에서 계속할 수 있습니다.", + "crash": "이 응답이 진행되는 동안 {{machine}}의 Orca가 예기치 않게 중지되었습니다.", + "quit": "이 응답이 진행되는 동안 {{machine}}의 Orca가 종료되었습니다.", + "remote": "이 응답이 진행되는 동안 {{machine}}의 Orca가 중지되었습니다.", + "unnamedServer": "원격 서버", + "update": "이 응답이 진행되는 동안 {{machine}}의 Orca가 업데이트를 위해 다시 시작되었습니다." + }, + "permission": { + "needsAccessTo": "접근이 필요한 경로: ", + "newerVersionRequired": "이 요청에는 최신 버전의 Orca가 필요합니다.", + "planFile": "계획 파일: {{filePath}}", + "reason": "이유: " + }, + "promptCard": { + "answerInTerminal": "터미널에서 응답", + "terminalExplanation": "터미널에서 이 OMP 질문에 대답하세요." + }, + "promptStrip": { + "expand": "펼치기" + }, + "question": { + "otherReplyPlaceholder": "또는 답변을 입력하세요…", + "replyPlaceholder": "답변을 입력하세요…", + "sendReply": "답변 보내기", + "submit": "제출", + "submitCount": "제출 ({{count}})", + "submitSelectedOptions": "선택한 옵션 제출" + }, + "queuedMessages": { + "clearAll": "모두 지우기", + "deleteAll": "대기 중인 메시지 모두 삭제", + "editMessage": "메시지 편집", + "pausedState": "일시 중지됨", + "queuedState": "대기 중", + "resume": "재개", + "resumeSending": "대기 중인 메시지 전송 재개", + "title": "대기 중인 메시지" + }, + "receipt": { + "cancelled": "취소", + "cancelledBy": "{{device}}에서 취소됨", + "emptyAnswer": "빈 답변", + "resolved": "해결됨", + "resolver": "{{device}}에서 답변됨", + "unavailable": "선택한 답변을 사용할 수 없습니다." + }, + "sessionOptionCopy": { + "availableAfterSessionStarts": "세션이 시작된 후 사용할 수 있습니다.", + "chooseInAgentPicker": "에이전트 선택기에서 선택…", + "effort": "추론 수준", + "fast": "빠름", + "fastMode": "빠른 모드", + "high": "높음", + "low": "낮음", + "max": "최대", + "medium": "중간", + "mediumShort": "중간", + "minimal": "최소", + "minimalShort": "최소", + "model": "모델", + "notSet": "설정 안 됨", + "off": "꺼짐", + "on": "켜짐", + "permissions": "권한", + "sessionOptions": "세션 옵션", + "setWhenSessionStarts": "세션 시작 시에만 설정할 수 있습니다.", + "speed": "속도", + "standard": "표준", + "thinking": "사고", + "toggleOption": "{{value0}} 전환", + "ultra": "울트라", + "xhigh": "매우 높음", + "xhighShort": "매우 높음" + }, + "sessionOptionPickers": { + "agentWorking": "에이전트 작업 중", + "backToModels": "모델 목록으로 돌아가기", + "closePicker": "선택 창 닫기", + "dispatchUnconfirmed": "에이전트에 전송됨 — 확인되지 않음", + "modelFullAccessLabel": "모델, {{value}}, 전체 액세스", + "modelLabel": "모델, {{value}}", + "permissions": "권한", + "permissionsLabel": "권한, {{value}}", + "settingsLocked": "이번 턴이 끝나면 모델 설정을 변경할 수 있습니다.", + "workingAnnouncement": "에이전트가 작업 중입니다. 이번 턴이 끝나면 모델 설정을 변경할 수 있습니다." + }, + "sessionOptionRows": { + "defaultMarker": "기본값", + "notReportedMarker": "보고되지 않음" + }, + "sessionOptionSegments": { + "dispatchUnconfirmed": "에이전트에 전송됨 — 확인되지 않음" + }, + "stopButton": { + "stopAgent": "에이전트 중지", + "stoppingState": "중지하는 중…" + }, + "streamFrame": { + "streamEnded": "대화 기록 스트리밍이 종료되었습니다.", + "streamFailed": "대화 기록 스트리밍에 실패했습니다." + }, + "taskList": { + "added": "{{task}}을(를) 추가했습니다.", + "completed": "완료됨", + "empty": "작업 없음", + "finished": "{{task}} 완료됨", + "inProgress": "진행 중", + "pending": "보류 중", + "progress": "작업 {{total}}개 중 {{completed}}개 완료", + "removed": "{{task}} 삭제됨", + "reset": "보류 중으로 표시됨: {{task}}", + "showAll": "전체 작업 목록", + "started": "{{task}} 시작됨", + "taskStatus": "{{status}}: {{task}}", + "title": "작업", + "unchanged": "작업 변경 없음", + "updated": "{{task}} 업데이트됨" + }, + "toolActivity": { + "count_one": "도구 호출 {{count}}회", + "count_other": "도구 호출 {{count}}회", + "failedCallsLabel": "도구 호출 실패: {{value0}}", + "failedCount": "{{value0}}개 실패", + "interruptedCallsLabel": "중단된 도구 호출: {{value0}}", + "interruptedCount": "{{value0}} 중단됨", + "moreCalls": "+{{value0}}개 더", + "result": "결과", + "runningCommand": "명령 실행 중", + "runningNamed": "{{toolName}} 실행 중", + "runningNamedPreview": "{{toolName}} {{preview}} 실행 중", + "runningPreview": "{{preview}} 실행 중" + }, + "toolRun": { + "list": "{{value0}} 및 {{value1}}", + "liveBrowserOnce": "브라우저 사용 중", + "liveBrowserTimes_other": "브라우저 {{count}}회 사용 중", + "liveFileChange_one": "파일 {{count}}개 편집 중", + "liveFileChange_other": "파일 {{count}}개 편집 중", + "liveListFiles_one": "디렉터리 {{count}}개 나열 중", + "liveListFiles_other": "디렉터리 {{count}}개 나열 중", + "liveMcpToolCall_one": "통합 {{count}}개 사용 중", + "liveMcpToolCall_other": "통합 {{count}}개 사용 중", + "liveOther_one": "도구 {{count}}개 사용 중", + "liveOther_other": "도구 {{count}}개 사용 중", + "liveRead_one": "파일 {{count}}개 읽는 중", + "liveRead_other": "파일 {{count}}개 읽는 중", + "liveSearch_one": "{{count}}회 검색 중", + "liveSearch_other": "{{count}}회 검색 중", + "liveSubAgentActivity_one": "에이전트 {{count}}개 실행 중", + "liveSubAgentActivity_other": "에이전트 {{count}}개 실행 중", + "liveTodoListOnce": "계획 업데이트 중", + "liveTodoListTimes_other": "계획 {{count}}회 업데이트 중", + "liveUnknown_one": "명령 {{count}}개 실행 중", + "liveUnknown_other": "명령 {{count}}개 실행 중", + "liveWebSearch_one": "웹 {{count}}회 검색 중", + "liveWebSearch_other": "웹 {{count}}회 검색 중", + "pair": "{{value0}} 및 {{value1}}", + "separator": ", ", + "settledBrowserOnce": "브라우저를 사용했습니다", + "settledBrowserTimes_other": "브라우저 {{count}}회 사용함", + "settledFileChange_one": "파일 {{count}}개 편집", + "settledFileChange_other": "파일 {{count}}개 편집", + "settledListFiles_one": "디렉터리 {{count}}개 나열", + "settledListFiles_other": "디렉터리 {{count}}개 나열", + "settledMcpToolCall_one": "통합 {{count}}개 사용", + "settledMcpToolCall_other": "통합 {{count}}개 사용", + "settledOther_one": "도구 {{count}}개 사용", + "settledOther_other": "도구 {{count}}개 사용", + "settledRead_one": "파일 {{count}}개 읽음", + "settledRead_other": "파일 {{count}}개 읽음", + "settledSearch_one": "{{count}}회 검색", + "settledSearch_other": "{{count}}회 검색", + "settledSubAgentActivity_one": "에이전트 {{count}}개 실행", + "settledSubAgentActivity_other": "에이전트 {{count}}개 실행", + "settledTodoListOnce": "계획을 업데이트했습니다", + "settledTodoListTimes_other": "계획 {{count}}회 업데이트함", + "settledUnknown_one": "명령 {{count}}개 실행", + "settledUnknown_other": "명령 {{count}}개 실행", + "settledWebSearch_one": "웹 {{count}}회 검색", + "settledWebSearch_other": "웹 {{count}}회 검색" + }, + "turnStatus": { + "failedAfter": "{{value0}} 후 실패", + "hoursMinutesSeconds": "{{hours}}시간 {{minutes}}분 {{seconds}}초", + "interruptedAfter": "{{value0}} 후 중단됨", + "minutesSeconds": "{{minutes}}분 {{seconds}}초", + "queueAfterStop": "중지 후에 실행할 메시지를 대기열에 추가", + "responding": "에이전트가 응답 중입니다.", + "seconds_one": "{{count}}초", + "seconds_other": "{{count}}초", + "sendAfterStop": "중지 후에 실행할 메시지 보내기", + "stopping": "중지하는 중…", + "thinking": "생각 중", + "toggleDetails": "턴 세부정보 전환", + "workedFor": "{{value0}} 작업 완료", + "working": "작업 중…", + "workingFor": "{{value0}} 작업 중" + }, + "usageLimit": { + "reached": "{{agent}}이(가) 사용 한도에 도달했습니다.", + "reachedNamed": "{{agent}}이(가) {{window}} 한도에 도달했습니다.", + "resetsAt": "{{when}}에 초기화됩니다.", + "theAgent": "에이전트", + "windowCredits": "추가 사용량", + "windowSession": "세션", + "windowWeekly": "주간", + "windowWeeklyModel": "모델별 주간" + }, + "useImageUpload": { + "attachFailed": "첨부 실패", + "disconnected": "첨부 실패(연결 끊김)", + "imageTooLarge": "이미지가 너무 커서 첨부할 수 없습니다.", + "permissionDenied": "사진 접근 권한 거부됨" + } + }, "common": { "back": "뒤로", "cancel": "취소", @@ -151,6 +508,8 @@ "done": "완료", "edit": "편집", "ok": "확인", + "open": "열기", + "refresh": "새로고침", "remove": "제거", "retry": "다시 시도", "save": "저장", @@ -757,8 +1116,51 @@ "unexpectedResponse": "예상치 못한 응답" }, "webShellUpdateFailureCopy": { + "blockedReason": "차단됨: {{reason}}.", + "blockedWorkspace": "워크스페이스를 차단했습니다.", + "failed": "오류 화면을 표시했습니다.", "failedAgo": "{{hostName}}에서 최신 업데이트를 받는 데 {{time}} 전에 실패했습니다: {{reason}}{{offered}}. {{outcome}}", - "failedJustNow": "{{hostName}}에서 최신 업데이트를 받는 데 방금 실패했습니다: {{reason}}{{offered}}. {{outcome}}" + "failedJustNow": "{{hostName}}에서 최신 업데이트를 받는 데 방금 실패했습니다: {{reason}}{{offered}}. {{outcome}}", + "hostCodeCopy": { + "assetChanged": "호스트에서 에셋이 변경되었습니다", + "assetUnknown": "호스트가 에셋을 인식하지 못했습니다", + "buildChanged": "다운로드 중 호스트의 빌드가 변경되었습니다", + "offsetInvalid": "호스트가 읽기 오프셋을 거부했습니다", + "readLimited": "호스트가 동시 읽기를 제한했습니다", + "unavailable": "호스트에 워크스페이스 번들이 없습니다" + }, + "nativeRoute": "네이티브 화면을 표시했습니다.", + "offeredGeneration": " (세대 {{generation}})", + "openedCached": "저장된 버전을 대신 사용했습니다.", + "openedCachedGeneration": "저장된 버전(세대 {{generation}})을 대신 사용했습니다.", + "reasonCopy": { + "assetChecksumMismatch": "에셋 체크섬이 일치하지 않습니다", + "assetEntryChanged": "에셋이 더 이상 매니페스트와 일치하지 않습니다", + "assetNoProgress": "에셋 읽기가 진행되지 않았습니다", + "assetOverlong": "에셋 크기가 매니페스트에 명시된 크기를 초과했습니다", + "assetShort": "에셋 크기가 명시된 크기보다 작았습니다", + "buildChangedMidFetch": "다운로드 중 호스트의 빌드가 변경되었습니다", + "cacheWriteFailed": "다운로드한 데이터를 이 휴대폰에 저장하지 못했습니다", + "chunkMisrouted": "요청과 다른 에셋 또는 오프셋의 청크가 수신되었습니다", + "chunkOversize": "청크 크기가 호스트의 허용 한도를 초과했습니다", + "connectionLost": "연결이 끊어졌습니다", + "fetchStopped": "다운로드가 중단되었습니다", + "hostRefused": "호스트가 읽기 요청을 거부했습니다", + "noConnection": "호스트에 연결되어 있지 않습니다", + "rangeUndecodable": "읽어 온 압축 데이터를 디코딩할 수 없었습니다", + "replyUnreadable": "호스트가 이 앱에서 읽을 수 없는 응답을 보냈습니다", + "unrecognisedError": "알 수 없는 오류가 발생했습니다" + }, + "routeUnavailable": "화면을 사용할 수 없다는 안내를 표시했습니다.", + "waiting": "호스트를 기다렸습니다.", + "wallCopy": { + "bundleShellTooOld": "이 앱의 버전이 너무 낮아 저장된 번들을 사용할 수 없습니다", + "bundleTooOldForHost": "저장된 번들의 버전이 너무 낮아 이 호스트에서 사용할 수 없습니다", + "bundleUnavailable": "호스트에 워크스페이스 번들이 없습니다", + "hostTooOldForBundle": "호스트의 버전이 너무 낮아 저장된 번들을 사용할 수 없습니다", + "pageRouteGrantMissing": "이 화면을 사용하려면 더 최신 버전의 앱이 필요합니다", + "pageRouteUnlisted": "호스트의 워크스페이스에 이 화면이 포함되어 있지 않습니다" + } } }, "dictation": { @@ -775,6 +1177,95 @@ "updateFailed": "받아쓰기 설정을 업데이트하지 못했습니다." } }, + "files": { + "fileExplorerPanel": { + "backToSession": "세션으로 돌아가기", + "closeFiles": "파일 닫기", + "connecting": "데스크톱에 연결 중…", + "emptyState": "찾은 파일이 없습니다.", + "loadFailed": "파일을 불러올 수 없습니다.", + "loadInterrupted": "폴더 로드가 중단되었습니다.", + "title": "파일", + "truncatedList": "{{worktreeLabel}} - 처음 5000개 표시", + "waiting": "데스크톱 대기 중…" + }, + "fileExplorerRow": { + "fileUnavailable": "모바일에서 {{name}} 사용 불가", + "loadFolderFailed": "폴더를 불러올 수 없습니다.", + "loading": "로드 중…", + "openFolder": "{{name}} 폴더 열기", + "previewFile": "{{name}} 파일 미리보기", + "retryLoading": "{{path}} 다시 불러오기", + "unavailable": "모바일에서 사용 불가" + }, + "fileMarkdownPreview": { + "copyLoadedSource": "불러온 Markdown 소스 복사", + "copySource": "Markdown 소스 복사", + "viewPreview": "렌더링된 Markdown 미리보기 표시", + "viewSource": "Markdown 소스 보기" + }, + "fileMediaPreview": { + "downloading": "{{total}} 중 {{bytes}} 다운로드 중", + "loadFailed": "미디어를 불러올 수 없습니다.", + "loading": "미디어 로드 중…", + "waiting": "데스크톱 대기 중…" + }, + "fileMutationOwnership": { + "sshOwnerChanged": "SSH 연결을 확인하지 못했습니다. 호스트에 다시 연결한 후 다시 시도하세요." + }, + "filePreviewBody": { + "emptyFile": "빈 파일", + "imageLabel": "{{title}} 이미지" + }, + "filePreviewEditableSource": { + "editorLabel": "{{title}} 편집기" + }, + "filePreviewResponse": { + "binaryUnavailable": "바이너리 파일을 미리 볼 수 없습니다.", + "bytes": "{{size}} B", + "fileChanged": "데스크톱에서 파일이 변경되었습니다. 저장하기 전에 미리보기를 새로고침하세요.", + "fileNotFound": "파일을 찾을 수 없습니다.", + "fileTooLarge": "파일이 너무 커서 모바일에서 미리 볼 수 없습니다.", + "filesystemUnreachable": "데스크톱 파일 시스템에 연결할 수 없습니다.", + "kilobytes": "{{size}} KB", + "loadFailed": "미리보기를 불러올 수 없습니다.", + "megabytes": "{{size}} MB", + "reloadBeforeSaving": "저장하기 전에 미리보기를 새로고침하세요.", + "unknownSize": "크기 알 수 없음" + }, + "filePreviewScreen": { + "backToFiles": "파일 목록으로 돌아가기", + "chooseWorkspaceFile": "이 워크스페이스 안의 파일을 선택하세요.", + "discard": "버리기", + "discardMessage": "저장하지 않은 편집 내용은 사라집니다.", + "discardTitle": "변경사항을 삭제하시겠습니까?", + "fileTitle": "파일", + "loadFailed": "미리보기를 불러올 수 없습니다.", + "loading": "미리보기 로드 중…", + "openFailed": "파일을 열 수 없습니다.", + "saveArtifact": "터미널 아티팩트 저장", + "saveFailed": "파일을 저장할 수 없습니다.", + "stay": "계속 편집", + "title": "미리보기", + "waiting": "데스크톱 대기 중…" + }, + "filePreviewSourceText": { + "previewLabel": "파일 미리보기", + "truncatedNote": "미리보기가 일부만 표시됩니다. 파일 크기: {{size}}." + }, + "markdownPreviewImage": { + "expandFallback": "이미지 확대", + "expandImage": "{{alt}} 확대", + "imageLabel": "Markdown 이미지", + "loadFailed": "이미지를 불러올 수 없습니다." + }, + "pdfPreview": { + "open": "PDF 열기", + "openFailed": "이 PDF를 열 수 없습니다.", + "opening": "PDF 여는 중…", + "updateRequired": "PDF를 열려면 Orca 모바일을 업데이트하세요." + } + }, "home": { "accountUsageCards": { "sectionHeading": "계정 사용량", @@ -1103,8 +1594,10 @@ "noComments": "아직 댓글이 없습니다.", "noDescription": "설명이 제공되지 않았습니다.", "noHumanComments": "사람 댓글이 없습니다.", - "resolvedComment_other": "{{author}}님의 댓글 · 해결됨 ({{count}})", - "resolvedThread_other": "{{author}}님의 스레드 · 해결됨 ({{count}})", + "resolvedCommentCount": "{{author}}님의 댓글 · 해결됨 ({{count}})", + "resolvedCommentSingle": "{{author}}님의 댓글 · 해결됨", + "resolvedThreadCount": "{{author}}님의 스레드 · 해결됨 ({{count}})", + "resolvedThreadSingle": "{{author}}님의 스레드 · 해결됨", "showMore": "{{count}}개 더 보기", "showMoreOf": "남은 {{remaining}}개 중 {{count}}개 더 보기", "title": "댓글" @@ -1296,6 +1789,596 @@ "title": "리뷰어" } }, + "session": { + "accessoryKeys": { + "addCustomShortcut": "사용자 지정 단축키 추가", + "dismissKeyboard": "키보드 닫기", + "dismissKeyboardHint": "소프트웨어 키보드를 숨기고 현재 터미널 세션을 열린 상태로 유지합니다.", + "paste": "붙여넣기", + "pasteFromClipboard": "클립보드에서 붙여넣기", + "sendKey": "{{label}} 보내기", + "switchToBufferedInput": "버퍼링된 명령 입력으로 전환", + "switchToDesktopMode": "데스크톱 모드로 전환", + "switchToLiveInput": "실시간 터미널 입력으로 전환", + "switchToPhoneMode": "휴대폰 모드로 전환" + }, + "activeContent": { + "createTab": "탭 만들기", + "creatingTab": "만드는 중…", + "fileTitle": "파일", + "loadingTerminal": "터미널 로드 중", + "noTabs": "이 세션에는 탭이 없습니다.", + "retryLoadingTerminal": "터미널 로드 재시도", + "terminalTakingLonger": "터미널 준비가 예상보다 오래 걸리고 있습니다." + }, + "agentChildRow": { + "agent": "에이전트", + "days_one": "{{count}}일", + "days_other": "{{count}}일", + "ended": "종료됨", + "hours_one": "{{count}}시간", + "hours_other": "{{count}}시간", + "minutes_one": "{{count}}분", + "minutes_other": "{{count}}분", + "noRecentUpdate": "최근 업데이트 없음", + "noUpdate": "{{value0}} 동안 업데이트 없음" + }, + "agentMessageAttribution": { + "from": "발신: {{sender}}", + "fromMore": "발신: {{sender}} 외 {{count}}명", + "fromUnnamed": "발신: 에이전트", + "messageFrom": "{{sender}}의 메시지", + "messageFromMore": "{{sender}} 외 {{count}}명의 메시지", + "messageFromUnnamed": "에이전트의 메시지", + "unnamedSender": "에이전트" + }, + "aiVaultResumeLaunch": { + "createFailed": "터미널을 생성하지 못했습니다.", + "inputLocked": "터미널 입력이 잠겨 있습니다.", + "sendFailed": "재개 명령을 보내지 못했습니다." + }, + "aiVaultResumePreparation": { + "prepareFailed": "이 레거시 Codex 세션을 준비하지 못했습니다. 재개를 다시 시도하세요." + }, + "backgroundTask": { + "agent": "백그라운드 에이전트", + "command": "백그라운드 명령", + "countAgents_one": "에이전트 {{count}}개", + "countAgents_other": "에이전트 {{count}}개", + "countMonitors_one": "모니터 {{count}}개", + "countMonitors_other": "모니터 {{count}}개", + "countShellCommandOne": "셸 명령 1개", + "countShell_one": "셸 {{count}}개", + "countShell_other": "셸 {{count}}개", + "countTasks_one": "작업 {{count}}개", + "countTasks_other": "작업 {{count}}개", + "countWorkflows_one": "워크플로 {{count}}개", + "countWorkflows_other": "워크플로 {{count}}개", + "detailsUnavailable": "이 세션에서는 작업 세부정보를 사용할 수 없습니다.", + "groupAgents": "에이전트", + "groupMonitors": "모니터", + "groupShell": "셸", + "groupTasks": "작업", + "groupWorkflows": "워크플로", + "headerTotalCount_one": "백그라운드 작업 {{count}}개", + "headerTotalCount_other": "백그라운드 작업 {{count}}개", + "monitor": "백그라운드 모니터", + "monitoring": "백그라운드 작업 모니터링", + "reasonBlocked": "실패함", + "reasonWaiting": "승인이 필요합니다", + "stateBlocked": "막힘", + "stateBlockedCount": "{{value0}} 막힘", + "stateDone": "완료", + "stateDoneCount": "{{value0}} 완료", + "stateIdle": "중지됨", + "stateIdleCount": "{{value0}} 중지됨", + "stateMonitoring": "모니터링", + "stateMonitoringCount": "{{value0}} 모니터링", + "stateUnverifiable": "상태를 확인할 수 없음", + "stateUnverifiableCount": "{{value0}} 상태를 확인할 수 없음", + "stateWaiting": "대기 중", + "stateWaitingCount": "{{value0}} 대기 중", + "stateWorking": "작업 중", + "stateWorkingCount": "{{value0}} 작업 중", + "stop": "중지", + "stopAll": "백그라운드 작업 중지", + "stopTask": "{{value0}} 중지", + "task": "백그라운드 작업", + "workflow": "백그라운드 워크플로" + }, + "browserTabActionSheet": { + "forward": "앞으로", + "reload": "새로고침", + "title": "브라우저" + }, + "bulkCloseSheetActions": { + "closeOtherTabs": "다른 탭 닫기" + }, + "bulkTabClose": { + "confirm": "탭 닫기", + "description": "선택한 탭을 닫습니다. 고정된 탭은 열린 상태로 유지됩니다.", + "titleEditors": "모든 편집기 탭을 닫을까요?", + "titleLeft": "왼쪽 탭을 닫을까요?", + "titleOthers": "다른 탭을 닫을까요?", + "titleRight": "오른쪽 탭을 닫을까요?" + }, + "commandDock": { + "commandPlaceholder": "명령을 입력하세요…", + "liveInputHint": "입력한 텍스트가 활성 터미널로 바로 전송됩니다.", + "liveInputLabel": "실시간 터미널 입력", + "sendCommand": "명령 보내기" + }, + "contentRow": { + "dismissCreationWarning": "워크스페이스 생성 경고 닫기" + }, + "diffCommentLineRow": { + "addNote": "{{line}}행에 메모 추가", + "deleteNote": "{{line}}행 메모 삭제", + "diffLine": "{{title}} 차이점 {{line}}행", + "line": "{{line}}행", + "placeholder": "리뷰 메모 추가", + "saveNote": "메모 저장" + }, + "diffLineRow": { + "addNoteLabel": "{{line}}행에 메모 추가", + "deleteNoteLabel": "{{line}}행의 메모 삭제", + "diffLineLabel": "{{title}} 차이점의 {{line}}행", + "lineNumber": "{{line}}행", + "notePlaceholder": "리뷰 메모 추가", + "saveNote": "메모 저장" + }, + "diffReviewLoaders": { + "changesLoadFailed": "변경 사항을 불러올 수 없습니다.", + "committedChangesFailed": "커밋된 변경 사항을 불러오지 못했습니다.", + "committedChangesUnavailable": "커밋된 변경 사항을 사용할 수 없습니다.", + "committedDiffUnavailable": "커밋된 차이점을 사용할 수 없습니다.", + "desktopUpdateRequired": "모바일에서 변경 사항을 리뷰하려면 데스크톱의 Orca를 업데이트하세요.", + "diffLoadFailed": "차이점을 불러올 수 없습니다.", + "invalidSourceControlResponse": "소스 제어 응답이 올바르지 않습니다.", + "reviewNotesLoadFailed": "리뷰 메모를 불러올 수 없습니다." + }, + "diffReviewQueue": { + "committedOnBranch": "브랜치에 커밋됨", + "staged": "스테이징됨", + "unstaged": "스테이징 안 됨" + }, + "diffReviewScreenModel": { + "branch": "브랜치", + "staged": "스테이징됨", + "unstaged": "스테이징 안 됨" + }, + "fileReader": { + "copyReviewNotes": "리뷰 메모 복사", + "imageLabel": "{{title}} 이미지", + "noReviewNotes": "리뷰 메모 없음", + "previewLabel": "{{title}} 미리보기", + "reviewNotes_one": "리뷰 메모 {{count}}개", + "reviewNotes_other": "리뷰 메모 {{count}}개", + "send": "보내기", + "sendReviewNotes": "AI에 리뷰 메모 보내기" + }, + "floatingWorkspace": { + "title": "플로팅 워크스페이스" + }, + "githubPrMutations": { + "reviewThreadUpdateFailed": "리뷰 스레드를 업데이트하지 못했습니다.", + "titleUpdateFailed": "제목을 업데이트하지 못했습니다." + }, + "githubPrRpc": { + "requestFailed": "요청 실패: {{method}}" + }, + "header": { + "backToWorktrees": "워크트리로 돌아가기", + "moreActions": "세션 작업 더 보기", + "openFileExplorer": "파일 탐색기 열기", + "openSourceControl": "소스 제어 열기", + "reconnectToDesktop": "데스크톱에 다시 연결", + "showSidebar": "사이드바 표시", + "terminalTitle": "터미널" + }, + "headerMoreActionsSheet": { + "agentHistory": "에이전트 기록", + "agentHistoryHint": "에이전트 세션 탐색 및 재개", + "checks": "체크", + "checksHint": "PR 체크 열기" + }, + "markdownReader": { + "changedOnDesktop": "데스크톱에서 변경됨", + "copyLoaded": "불러온 내용 복사", + "copyLoadedSource": "불러온 Markdown 소스 복사", + "copyPath": "경로 복사", + "copyPathLabel": "Markdown 경로 복사", + "copySource": "Markdown 소스 복사", + "discard": "버리기", + "dismissKeyboard": "키보드 닫기", + "dismissKeyboardHint": "소프트웨어 키보드를 숨기고 Markdown 편집기를 열린 상태로 유지합니다.", + "readOnly": "읽기 전용" + }, + "nativeChatAttachActions": { + "availableSkills": "{{count}}개 사용 가능", + "commands": "명령어", + "fileTypes": "PDF, 텍스트, 코드…", + "files": "파일", + "photos": "사진", + "skills": "스킬", + "workspaceFile": "워크스페이스 파일" + }, + "nativeChatCardHeaderAction": { + "collapse": "접기" + }, + "nativeChatCliFork": { + "resumeInNewCli": "새 CLI에서 재개" + }, + "nativeChatFloatingControls": { + "scrollToLatest": "최신 내용으로 스크롤" + }, + "nativeChatFullContent": { + "loadMessageFailed": "전체 메시지 보기 — 불러오지 못했습니다. 탭하여 다시 시도하세요.", + "loadOutputFailed": "전체 출력 보기 — 불러오지 못했습니다. 탭하여 다시 시도하세요.", + "partialMessage": "이 메시지의 일부를 표시하고 있습니다.", + "partialOutput": "{{total}} 중 {{shown}}을(를) 표시하는 중입니다.", + "showFullMessage": "전체 메시지 보기", + "showFullOutput": "전체 출력 보기" + }, + "nativeChatRenderData": { + "defaultAgentLabel": "에이전트", + "emptySubtitle": "{{agent}}에게 코드 확인, 출력 설명 또는 변경을 요청하세요.", + "emptyTitle": "{{agent}}과(와) 채팅 시작", + "errorSubtitle": "대화 기록을 읽을 수 없습니다. 계속 작업하려면 터미널로 다시 전환하세요.", + "errorTitle": "대화를 로드할 수 없습니다.", + "notFoundSubtitle": "메시지를 보내 대화를 시작하거나, 터미널 보기에서 {{agent}}이(가) 무엇을 하는지 확인하세요.", + "notFoundTitle": "아직 이 대화를 찾을 수 없습니다." + }, + "nativeChatToggleAction": { + "switchToChatView": "채팅 보기로 전환", + "switchToTerminalView": "터미널 보기로 전환" + }, + "newTabAgentHostLaunch": { + "notesNotSent": "에이전트가 시작되었지만 노트가 전송되지 않았습니다.", + "notesSent": "노트 전송됨", + "notesUnconfirmed": "에이전트가 시작되었지만 노트가 전송되었는지 확인하지 못했습니다.", + "promptUnconfirmed": "에이전트가 시작되었지만 프롬프트가 전송되었는지 확인하지 못했습니다." + }, + "prActionsEngine": { + "mergeFailed": "PR을 병합하지 못했습니다.", + "refreshFailed": "PR을 새로고침하지 못했습니다." + }, + "prAiTriageLaunch": { + "agentStarted": "에이전트 시작됨", + "agentStartedIn": "{{label}}에서 에이전트 시작됨", + "emptyPrompt": "이 작업에 저장된 프롬프트가 비어 있습니다. 컴퓨터에서 소스 제어 AI 설정을 업데이트하세요.", + "loadFailed": "사용 가능한 에이전트를 로드하지 못했습니다.", + "promptNotSent": "에이전트가 시작되었지만 프롬프트가 전송되지 않았습니다.", + "updateRequired": "휴대폰에서 에이전트를 시작하려면 컴퓨터의 Orca를 업데이트하세요." + }, + "prSidebarState": { + "loadFailed": "PR을 로드할 수 없습니다." + }, + "queuedCardText": { + "showLess": "접기", + "showLessHint": "메시지의 일부만 표시합니다.", + "showMore": "더 보기", + "showMoreHint": "메시지 전체를 표시합니다." + }, + "queuedMessageCardRow": { + "deleteHint": "대기 중인 메시지 삭제", + "images_one": "이미지 {{count}}개", + "images_other": "이미지 {{count}}개", + "moreActions": "추가 작업", + "send": "보내기", + "sendAgainHint": "이 메시지 다시 보내기", + "sendHint": "이 메시지 보내기", + "showLessHint": "메시지의 일부만 표시합니다.", + "steer": "지금 보내기", + "steerHint": "모델을 중단하지 않고 보냅니다" + }, + "queuedMessageComposer": { + "commandsNotAllowed": "대기 중인 메시지에는 명령어를 저장할 수 없습니다.", + "editing": "메시지 편집 중", + "editingAttributed": "메시지 편집 중 · {{attribution}}" + }, + "quickCommandEditorForm": { + "action": "동작", + "addQuickCommand": "빠른 명령 추가", + "advanced": "고급", + "agent": "에이전트", + "agentPrompt": "에이전트 프롬프트", + "appendEnter": "Enter 추가", + "appendEnterDescription": "텍스트만 삽입하지 않고 즉시 제출합니다.", + "chooseAgent": "에이전트 선택", + "commandText": "명령 텍스트", + "globalScope": "글로벌", + "label": "레이블", + "labelPlaceholder": "개발 서버 시작", + "olderHostPromptTooLong": "이 호스트에는 너무 깁니다. 더 긴 프롬프트를 저장하려면 호스트의 Orca를 업데이트하세요.", + "projectScope": "프로젝트", + "prompt": "프롬프트", + "promptHint": "스킬, 파일 경로, 기본 제공 명령을 지원합니다.", + "promptPlaceholder": "에이전트에게 이 워크스페이스를 조사하도록 요청하세요.", + "promptSizeCharacters": "{{used}} / {{max}}자", + "promptSizeKilobytes": "{{used}} KB / {{max}} KB", + "promptTooLong": "너무 길어서 저장할 수 없습니다.", + "scope": "범위", + "terminalCommand": "터미널 명령" + }, + "quickCommandLaunch": { + "defaultLabel": "빠른 명령어", + "editBeforeRunning": "실행하기 전에 이 빠른 명령어를 편집하세요.", + "runFailed": "{{label}}을(를) 실행하지 못했습니다." + }, + "quickCommandRow": { + "copyCommand": "{{label}} 복사", + "copyFailed": "복사하지 못함", + "deleteCommand": "{{label}} 삭제", + "editCommand": "{{label}} 편집", + "nothingToCopy": "복사할 내용 없음", + "runCommand": "{{label}} 실행" + }, + "quickCommandsList": { + "emptyState": "아직 빠른 명령이 없습니다.", + "globalGroup": "글로벌", + "newCommand": "새 빠른 명령", + "noMatches": "일치하는 빠른 명령이 없습니다.", + "projectGroup": "이 프로젝트", + "searchPlaceholder": "빠른 명령 검색…" + }, + "quickCommandsSheet": { + "addTitle": "빠른 명령 추가", + "chooseAgentTitle": "에이전트 선택", + "deleteConfirmMessage": "이 빠른 명령은 저장된 목록에서 제거됩니다.", + "deleteConfirmTitle": "\"{{label}}\"을(를) 삭제하시겠습니까?", + "editTitle": "빠른 명령 편집", + "editorDescription": "터미널 명령이나 에이전트 프롬프트를 저장하여 빠르게 사용하세요.", + "title": "빠른 명령어", + "untitled": "제목 없음" + }, + "quickCommandsTabButton": { + "quickCommands": "빠른 명령어" + }, + "routeHelpers": { + "connected": "연결됨", + "connecting": "연결 중", + "disconnected": "연결 끊김", + "pairingInvalid": "페어링이 유효하지 않음", + "reconnecting": "다시 연결 중", + "securing": "보안 연결 설정 중" + }, + "routeScreen": { + "openingWorkspace": "워크스페이스 여는 중" + }, + "sheets": { + "browser": "브라우저", + "browserUpdateRequired": "모바일 브라우저 스트리밍을 사용하려면 데스크톱 업데이트가 필요합니다.", + "chat": "채팅", + "copyAllAndLeave": "모두 복사 후 나가기", + "copyDraftsFailed": "초안을 복사하지 못했습니다.", + "copyNotes": "메모 복사", + "copyNotesFailed": "메모를 복사하지 못했습니다.", + "copyPath": "경로 복사", + "discard": "버리기", + "discardAndLeave": "버리고 나가기", + "discardChanges": "변경사항 취소", + "discardMessage": "휴대폰의 초안을 데스크톱의 최신 파일로 바꾸시겠습니까?", + "draftsHint": "나가기 전에 휴대폰의 초안을 복사하거나 버리세요.", + "file": "파일", + "markdown": "Markdown", + "markdownNote": "Markdown 메모", + "newBrowser": "새 브라우저", + "newTab": "새 탭", + "notesCopied": "메모를 복사했습니다.", + "notesHint": "현재 메모를 보낼 에이전트 세션을 선택하세요.", + "removeShortcutMessage": "이 사용자 지정 단축키를 제거하시겠습니까?", + "renameTerminal": "터미널 이름 바꾸기", + "sendReviewNotes": "리뷰 메모 보내기", + "shortcut": "단축키", + "terminal": "터미널", + "terminalName": "터미널 이름", + "unsavedMarkdown": "저장되지 않은 Markdown 변경 사항", + "urlPrompt": "URL을 입력하세요. 비워 두면 새 탭이 열립니다." + }, + "sideRail": { + "backToWorktrees": "워크트리로 돌아가기" + }, + "sourceControlLaunchAgent": { + "noEnabledAgent": "이 워크스페이스 호스트에서는 활성화된 AI 에이전트가 감지되지 않았습니다.", + "savedAgentUnavailable": "이 작업에 저장된 에이전트는 이 워크스페이스 호스트에서 사용할 수 없습니다." + }, + "structuredComposerCommand": { + "operationUnconfirmed": "대화 작업이 확인되지 않았습니다.", + "removeAttachments": "채팅 세션 명령을 사용하기 전에 첨부 파일을 제거하세요." + }, + "structuredSendDelivery": { + "messageNotSent": "메시지가 전송되지 않음" + }, + "tabStrip": { + "checkingCapabilities": "데스크톱에서 지원하는 기능을 확인 중입니다. 잠시 후 다시 시도하세요.", + "newTab": "새 탭", + "quickCommandsUpdateRequired": "빠른 명령을 사용하려면 데스크톱 업데이트가 필요합니다.", + "tabStatus": "{{title}}, {{status}}" + }, + "terminalActionSheetActions": { + "clearTerminal": "터미널 지우기", + "rename": "이름 바꾸기", + "switchToDesktop": "데스크톱 모드로 전환", + "switchToPhone": "휴대폰 모드로 전환" + }, + "terminalInputActions": { + "attachFileHint": "사진 대신 파일을 첨부하려면 길게 누르세요.", + "attachPhoto": "사진 첨부", + "sendingImage": "이미지 전송 중", + "startDictation": "음성 받아쓰기 시작", + "startingDictation": "음성 받아쓰기 시작 중", + "stopDictation": "음성 받아쓰기 중지", + "transcribingDictation": "음성 받아쓰기를 텍스트로 변환 중" + }, + "terminalLiveInputStatus": { + "listening": "듣는 중", + "liveInput": "실시간 입력", + "preparingMic": "마이크 준비 중", + "processing": "처리 중", + "releaseMicHint": "중지하려면 마이크에서 손을 떼세요", + "startingMic": "마이크 시작 중", + "stopHint": "중지하려면 마이크를 탭하세요", + "transcribing": "데스크톱에서 텍스트로 변환 중", + "uploadingImage": "호스트에 이미지 업로드 중", + "uploadingImageTitle": "이미지 업로드 중" + }, + "terminalRecords": { + "defaultTitle": "터미널" + }, + "terminalTabAgent": { + "browser": "브라우저", + "chat": "채팅", + "file": "파일", + "markdown": "Markdown", + "newBrowser": "새 브라우저", + "terminal": "터미널" + }, + "terminalViewportResubscribe": { + "fitFailed": "터미널을 이 화면 크기에 맞추지 못했습니다." + }, + "useAccessorySelection": { + "copyFailed": "복사하지 못함", + "selectionCleared": "선택 해제됨 (스크롤로 버퍼 범위를 벗어남)" + }, + "useAttachmentInputLeaseGate": { + "reconnecting": "첨부 실패 (다시 연결 중)" + }, + "useContentCreateActions": { + "browserCommandFailed": "브라우저 명령 실행에 실패했습니다.", + "browserCreateFailed": "브라우저를 만들지 못했습니다.", + "browserUnavailable": "아직 브라우저 페이지를 사용할 수 없습니다.", + "browserUpdateRequired": "모바일 브라우저 스트리밍을 사용하려면 데스크톱 업데이트가 필요합니다.", + "invalidUrl": "올바른 URL을 입력하세요.", + "noteCreateFailed": "Markdown 노트를 만들지 못했습니다.", + "untitledNoteCreateFailed": "제목 없는 Markdown 노트를 만들 수 없습니다." + }, + "useDiffComments": { + "copyNotesFailed": "노트를 복사하지 못했습니다.", + "deleteNoteFailed": "노트를 삭제하지 못했습니다.", + "noteAdded": "노트 추가됨", + "notesCopied": "노트 복사됨", + "saveNoteFailed": "노트를 저장하지 못했습니다.", + "saveNotesFailed": "리뷰 노트를 저장하지 못했습니다.", + "waitingForDesktop": "데스크톱 대기 중…" + }, + "useDiffReviewCommentActions": { + "saveFailed": "리뷰를 저장하지 못했습니다.", + "saveStateFailed": "리뷰 상태를 저장하지 못했습니다.", + "waitingForDesktop": "데스크톱 연결 대기 중…" + }, + "useDiffReviewController": { + "loadFailed": "리뷰를 로드할 수 없습니다.", + "missingWorktree": "워크트리가 없습니다.", + "waitingForDesktop": "데스크톱 연결 대기 중…" + }, + "useDiffReviewDiffLoading": { + "loadFailed": "차이점을 로드할 수 없습니다.", + "waitingForDesktop": "데스크톱 연결 대기 중…" + }, + "useDiffReviewGitActions": { + "actionFailed": "소스 제어 작업에 실패했습니다.", + "reviewedFilesStaged_one": "리뷰한 파일 {{count}}개 스테이징됨", + "reviewedFilesStaged_other": "리뷰한 파일 {{count}}개 스테이징됨", + "stageResults": "{{staged}}개 스테이징됨, {{failed}}개 실패", + "waitingForDesktop": "데스크톱 연결 대기 중…" + }, + "useDiffReviewInteractions": { + "openFailed": "세션에서 열 수 없습니다." + }, + "useDiffReviewSendActions": { + "copyFailed": "리뷰 노트를 복사할 수 없습니다.", + "inputLocked": "터미널 입력이 잠겨 있습니다.", + "loadSessionsFailed": "에이전트 세션을 로드할 수 없습니다.", + "notesCopied": "리뷰 노트 복사됨", + "notesSent": "리뷰 노트 전송됨", + "notesSentWithWarning": "리뷰 노트를 전송했습니다. {{warning}}", + "promptNotSent": "에이전트가 시작되었지만 노트가 전송되지 않았습니다. '노트 복사'로 복사한 후 붙여넣으세요.", + "sendFailed": "노트를 전송하지 못했습니다.", + "startingAgent": "에이전트 시작 중…", + "waitingForDesktop": "데스크톱 연결 대기 중…" + }, + "useImageAttachment": { + "attachFailed": "첨부 실패", + "disconnected": "첨부 실패 (연결 끊김)", + "imageTooLarge": "이미지가 너무 커서 첨부할 수 없습니다.", + "permissionDenied": "사진 접근 권한이 거부되었습니다." + }, + "useMarkdownActions": { + "copyFailed": "복사하지 못함", + "copyPathFailed": "경로를 복사하지 못했습니다.", + "markdownTitle": "Markdown", + "pathCopied": "경로 복사됨", + "saveFailed": "저장 실패", + "saved": "저장됨" + }, + "useNativeChatDictation": { + "insertFailed": "받아쓰기 내용을 삽입하지 못했습니다.", + "insertFailedCopied": "받아쓰기 내용을 삽입하지 못해 클립보드에 복사했습니다.", + "inserted": "받아쓰기 내용 삽입됨" + }, + "useNativeChatStop": { + "stopNotSent": "중지 요청 전송 안 됨", + "stopUnconfirmed": "중지 여부가 확인되지 않았습니다. 다시 시도하기 전에 채팅을 확인하세요.", + "terminalNotReady": "중지 요청 전송 안 됨 (터미널 준비 안 됨)" + }, + "usePanelRouteActions": { + "checkHostHint": "호스트 연결을 확인하세요.", + "copyNotesHint": "대신 노트를 복사하세요.", + "detectingAgents": "에이전트 감지 중", + "newSessionHint": "새 에이전트 세션", + "noEnabledAgents": "활성화된 에이전트 없음", + "otherServerAgents": "다른 서버의 에이전트", + "pairServerHint": "해당 서버와 직접 페어링하세요.", + "presetsUnavailable": "에이전트 프리셋 사용 불가" + }, + "usePrAiTriage": { + "launchFailed": "에이전트를 실행하지 못했습니다.", + "waitingForDesktop": "데스크톱 대기 중…" + }, + "usePrCommentActions": { + "actionFailed": "댓글 작업에 실패했습니다." + }, + "usePrTitleAction": { + "disconnected": "데스크톱에 연결되어 있지 않습니다.", + "updateFailed": "제목을 업데이트하지 못했습니다." + }, + "useQuickCommands": { + "loadFailed": "빠른 명령을 불러오지 못했습니다.", + "saveFailed": "빠른 명령을 저장하지 못했습니다." + }, + "useSessionTerminalSendActions": { + "inputTooLarge": "입력 내용이 너무 큼 (최대 256 KiB)" + }, + "useStartup": { + "wakeAgents": "슬립 중인 에이전트를 활성화하려면 호스트에서 Orca를 실행하세요." + }, + "useTerminalCreateActions": { + "createFailed": "터미널을 만들지 못했습니다.", + "forkBusy": "다른 터미널을 여는 중입니다. 잠시 기다린 후 {{agentName}} 채팅을 다시 시도하세요.", + "forkUnavailable": "연결되어 있고 업데이트된 호스트에서 {{agentName}} 채팅을 열고 다시 시도하세요.", + "inputLocked": "터미널 입력이 다른 클라이언트에 의해 잠겨 있습니다.", + "notesSent": "노트 전송됨", + "sendNotesError": "노트를 보내지 못했습니다.", + "sendNotesFailed": "노트를 보내지 못했습니다.", + "terminalTitle": "터미널" + }, + "useTerminalInput": { + "clearFailed": "터미널 내용을 지우지 못했습니다.", + "cleared": "터미널 내용 지워짐" + }, + "useTerminalPaste": { + "clipboardReadFailed": "클립보드를 읽지 못했습니다.", + "disconnected": "붙여넣기 실패 (연결 끊김)", + "imageTooLarge": "이미지가 너무 커서 붙여넣을 수 없습니다.", + "pasteFailed": "붙여넣기 실패", + "pasteTooLarge": "붙여넣을 내용이 너무 큼 (최대 256 KiB)", + "terminalChanged": "붙여넣기 취소됨 (터미널 변경됨)" + }, + "worktreeLabel": { + "fallback": "워크트리" + } + }, "settings": { "aboutScreen": { "openLinkFailed": "링크를 열지 못했습니다. 다시 시도하세요.", @@ -1436,6 +2519,186 @@ "updateFailed": "업데이트하지 못했습니다." } }, + "sourceControl": { + "actions": { + "commits": "커밋", + "createPr": "PR 작성", + "pushAndCreatePr": "푸시 및 PR 작성", + "switchBranch": "브랜치 전환" + }, + "commitDropdown": { + "checkOutBranchToPush": "커밋을 푸시하기 전에 브랜치를 체크아웃하세요", + "checkOutBranchToSync": "커밋을 동기화하기 전에 브랜치를 체크아웃하세요", + "checkingBranchStatus": "브랜치 상태 확인 중…", + "checkingPrStatus": "PR 상태 확인 중…", + "commit": "Commit", + "commitAndForcePush": "스테이지된 변경 사항을 Commit 하고 lease로 강제 푸시", + "commitAndPush": "스테이지된 변경 사항을 Commit 하고 푸시", + "commitAndTryPush": "스테이지된 변경 사항을 Commit 하고 푸시 시도", + "commitForcePush": "Commit 및 강제 푸시", + "commitPush": "Commit 및 푸시", + "commitStagedChanges": "스테이지된 변경 사항 Commit", + "commitSync": "Commit 및 동기화", + "commitThenPullPush": "커밋한 다음 풀 및 푸시", + "enterMessage": "commit 하려면 commit 메시지를 입력하세요.", + "linkedReviewTargetUnavailable": "연결된 리뷰 브랜치 대상을 사용할 수 없습니다", + "prAlreadyMerged": "PR이 이미 병합되었습니다", + "publishBranchFirstToPush": "커밋을 푸시하려면 먼저 브랜치를 게시하세요", + "publishBranchFirstToSync": "커밋을 동기화하려면 먼저 브랜치를 게시하세요", + "resolveConflicts": "commit 하기 전에 충돌을 해결하세요.", + "stageFile": "commit 할 파일을 하나 이상 스테이지하세요", + "useCommitAndForcePush": "Commit 및 강제 푸시를 사용하세요 — 원격에는 로컬 커밋의 이전 사본만 있습니다" + }, + "createPrAction": { + "createReview": "{{titleLabel}} 만들기" + }, + "dropdownCounts": { + "fastForwardCount_one": "커밋 {{count}}개 fast-forward", + "fastForwardCount_other": "커밋 {{count}}개 fast-forward", + "pullCount_one": "커밋 {{count}}개 풀", + "pullCount_other": "커밋 {{count}}개 풀", + "pushCount_one": "커밋 {{count}}개 푸시", + "pushCount_other": "커밋 {{count}}개 푸시", + "remoteBranchFallback": "원격 브랜치", + "syncCounts": "{{behind}}개 풀, {{ahead}}개 푸시", + "titleForcePushOlderCopiesBranch": "원격에는 로컬 커밋의 이전 사본만 있습니다. {{upstreamName}}을(를) 업데이트하려면 이 브랜치를 lease로 강제 푸시하세요.", + "titleForcePushOlderCopiesCount_one": "원격에는 로컬 커밋의 이전 사본만 있습니다. {{upstreamName}}을(를) 업데이트하려면 브랜치 커밋 {{count}}개를 lease로 강제 푸시하세요.", + "titleForcePushOlderCopiesCount_other": "원격에는 로컬 커밋의 이전 사본만 있습니다. {{upstreamName}}을(를) 업데이트하려면 브랜치 커밋 {{count}}개를 lease로 강제 푸시하세요.", + "titleManualForcePushReplaceRemote_one": "로컬 커밋 {{count}}개를 lease로 강제 푸시하여 {{upstreamName}}을(를) 업데이트하고 원격에만 있는 커밋을 대체합니다.", + "titleManualForcePushReplaceRemote_other": "로컬 커밋 {{count}}개를 lease로 강제 푸시하여 {{upstreamName}}을(를) 업데이트하고 원격에만 있는 커밋을 대체합니다.", + "titleManualForcePush_one": "로컬 커밋 {{count}}개를 lease로 강제 푸시하여 {{upstreamName}}을(를) 업데이트합니다.", + "titleManualForcePush_other": "로컬 커밋 {{count}}개를 lease로 강제 푸시하여 {{upstreamName}}을(를) 업데이트합니다.", + "titleUnpublishedForcePushBranch": "이 브랜치를 lease로 강제 푸시하고 필요하면 업스트림을 설정합니다.", + "titleUnpublishedForcePushCount_one": "브랜치 커밋 {{count}}개를 lease로 강제 푸시하고 필요하면 업스트림을 설정합니다.", + "titleUnpublishedForcePushCount_other": "브랜치 커밋 {{count}}개를 lease로 강제 푸시하고 필요하면 업스트림을 설정합니다." + }, + "hostedReview": { + "mrCopy": { + "reviewLabel": "MR", + "shortLabel": "MR", + "titleLabel": "머지 리퀘스트" + }, + "prCopy": { + "reviewLabel": "PR", + "shortLabel": "PR", + "titleLabel": "PR" + } + }, + "hostedReviewCreate": { + "pushSucceeded": "푸시는 성공했지만 {{shortLabel}} 생성에 실패했습니다: {{error}}" + }, + "prCreate": { + "authRequired": "{{reviewLabel}} 생성 전에 인증하세요.", + "baseNotOnRemote": "{{reviewLabel}}을(를) 생성하기 전에 기본 브랜치를 푸시하세요.", + "createdNotRefreshed": "{{titleLabel}}을(를) 만들었지만 Orca가 아직 새로고침하지 못했습니다.", + "defaultBranch": "{{reviewLabel}}을(를) 만들기 전에 기능 브랜치로 전환하세요.", + "detachedHead": "{{reviewLabel}}을(를) 만들기 전에 브랜치를 체크아웃하세요.", + "dirty": "{{reviewLabel}} 생성 전에 변경 사항을 커밋하세요.", + "existing": "{{titleLabel}}이(가) 이미 열려 있습니다.", + "existingNumber": "{{titleLabel}} #{{number}}이(가) 이미 열려 있습니다.", + "existingReview": "이 브랜치에 대한 {{reviewLabel}}이(가) 이미 있습니다.", + "forkHeadUnsupported": "이 포크에서는 {{reviewLabel}} 생성을 지원하지 않습니다.", + "lookupUnconfirmed": "Orca가 이 브랜치에 {{reviewLabel}}이(가) 이미 있는지 확인하지 못했습니다. 잠시 후 다시 시도하세요.", + "needsSync": "{{reviewLabel}} 생성 전에 이 브랜치를 동기화하세요.", + "noUpstream": "{{reviewLabel}} 생성 전에 커밋을 게시하세요.", + "notReady": "이 브랜치는 아직 {{reviewLabel}} 생성 준비가 되지 않았습니다.", + "unsupportedMergeRequests": "이 저장소에서는 MR 생성을 지원하지 않습니다.", + "unsupportedPullRequests": "이 저장소에서는 PR 생성을 지원하지 않습니다." + }, + "primaryAction": { + "forcePushFromDesktop": "데스크톱에서 이 PR의 브랜치를 강제 푸시하세요." + }, + "remoteDropdown": { + "branchAlreadyPublished": "브랜치가 이미 게시되었습니다", + "branchUpToDate": "브랜치가 최신 상태입니다", + "checkOutBranchToFastForward": "fast-forward하기 전에 브랜치를 체크아웃하세요", + "checkOutBranchToForcePush": "커밋을 강제 푸시하기 전에 브랜치를 체크아웃하세요", + "checkOutBranchToPublish": "커밋을 게시하기 전에 브랜치를 체크아웃하세요", + "checkOutBranchToPull": "커밋을 풀하기 전에 브랜치를 체크아웃하세요", + "checkOutBranchToPush": "커밋을 푸시하기 전에 브랜치를 체크아웃하세요", + "checkOutBranchToSync": "커밋을 동기화하기 전에 브랜치를 체크아웃하세요", + "checkingBranchStatus": "브랜치 상태 확인 중…", + "checkingPrStatus": "PR 상태 확인 중…", + "fastForward": "Fast-forward", + "fetch": "가져오기", + "fetchTitle": "병합하지 않고 원격에서 가져오기(fetch)", + "forcePush": "강제 푸시", + "linkedReviewBranchExists": "연결된 리뷰 브랜치가 이미 있습니다", + "linkedReviewTargetUnavailable": "연결된 리뷰 브랜치 대상을 사용할 수 없습니다", + "nothingNewToFastForward": "fast-forward할 새 항목이 없습니다 — 원격에는 로컬 커밋의 이전 사본만 있습니다", + "nothingNewToPull": "풀할 새 항목이 없습니다 — 원격에는 로컬 커밋의 이전 사본만 있습니다", + "nothingToFastForward": "fast-forward할 항목이 없습니다", + "nothingToForcePush": "강제 푸시할 항목이 없습니다", + "nothingToForcePushTo": "{{upstreamName}}에 강제 푸시할 항목이 없습니다", + "nothingToPull": "풀할 항목이 없습니다", + "nothingToPush": "푸시할 항목이 없습니다", + "nothingToPushTo": "{{upstreamName}}에 푸시할 항목이 없습니다", + "prAlreadyMerged": "PR이 이미 병합되었습니다", + "publishBranch": "브랜치 게시", + "publishBranchFirstToFastForward": "fast-forward하려면 먼저 브랜치를 게시하세요", + "publishBranchFirstToPull": "커밋을 풀하려면 먼저 브랜치를 게시하세요", + "publishBranchFirstToSync": "커밋을 동기화하려면 먼저 브랜치를 게시하세요", + "publishLinkedReview": "연결된 리뷰", + "publishNoBranch": "브랜치 없음", + "publishPrStatus": "PR 상태", + "publishToOrigin": "이 브랜치를 origin에 게시", + "pull": "풀", + "push": "푸시", + "pushAndSetUpstream": "이 브랜치를 푸시하고 필요하면 업스트림을 설정합니다", + "pushMayNeedSync": "로컬 커밋을 푸시합니다. git에서 먼저 동기화가 필요할 수 있습니다", + "pushToLinkedReview": "연결된 리뷰 브랜치에 업데이트 푸시", + "rebaseChooseRemoteBase": "리베이스할 원격 베이스 브랜치를 선택하세요", + "rebaseDirtyHint": "리베이스를 시도합니다. git에서 먼저 로컬 변경 사항을 커밋하거나 stash해야 할 수 있습니다", + "rebaseFromBase": "베이스에서 리베이스", + "rebaseFromNamedBase": "{{baseRef}}에서 리베이스", + "rebaseWithLatestFrom": "현재 브랜치를 {{baseRef}}의 최신 커밋으로 리베이스", + "sync": "동기화", + "tryFastForwardPull": "fast-forward 풀을 시도합니다. git에서 로컬 커밋을 거부할 수 있습니다", + "tryRegularPush": "일반 푸시를 시도합니다. git에서 강제 푸시가 필요할 수 있습니다", + "useForcePushToSync": "강제 푸시를 사용하세요 — 원격에는 로컬 커밋의 이전 사본만 있습니다" + } + }, + "terminal": { + "keyDefinitions": { + "accessoryArrowDown": "아래쪽 화살표", + "accessoryArrowLeft": "왼쪽 화살표", + "accessoryArrowRight": "오른쪽 화살표", + "accessoryArrowUp": "위쪽 화살표", + "arrowDown": "아래쪽 화살표", + "arrowLeft": "왼쪽 화살표", + "arrowRight": "오른쪽 화살표", + "arrowUp": "위쪽 화살표", + "clearLineBeforeCursor": "커서 앞의 줄 내용 지우기", + "clearScreen": "화면 지우기", + "deleteWordBackward": "커서 앞 단어 삭제", + "endOfLine": "줄 끝", + "forwardDelete": "앞으로 삭제", + "interruptTerminal": "터미널 중단", + "reverseSearch": "역방향 검색", + "sendEof": "EOF 전송", + "startOfLine": "줄 시작", + "suspendProcess": "프로세스 일시 중단" + }, + "webView": { + "documentStartFailed": "터미널 문서 시작 실패 - {{message}}", + "httpError": "터미널 WebView HTTP 오류", + "loadFailed": "터미널 WebView 로드 실패", + "renderProcessEnded": "터미널 WebView 렌더링 프로세스 종료" + }, + "webviewEngineErrorState": { + "code": "코드 {{code}}", + "reload": "새로고침", + "rendererCrashed": "렌더러 비정상 종료", + "status": "상태 {{statusCode}}", + "title": "터미널 로드 실패" + }, + "webviewNotificationDispatch": { + "unknownError": "알 수 없는 터미널 오류" + }, + "webviewReadyWatchdog": { + "initializeFailed": "터미널이 초기화되지 않았습니다. 터미널 뷰에서 준비 신호를 받지 못했습니다." + } + }, "webShell": { "shellStateScreen": { "checkingHost": "호스트 확인 중", @@ -1450,6 +2713,18 @@ "tryAgain": "다시 시도" } }, + "workspace": { + "branchChoice": { + "automaticName": "자동 이름", + "existing": "기존 브랜치", + "existingHint": "새 커밋이 이 브랜치에 추가됩니다.", + "hostUpdateRequired": "브랜치를 재사용하거나 별도의 브랜치를 명시적으로 생성하려면 워크스페이스 호스트의 Orca를 업데이트하세요.", + "new": "새 브랜치", + "newHint": "새 로컬 브랜치 만들기", + "newName": "새 브랜치 이름", + "title": "이 워크트리의 브랜치" + } + }, "worktree": { "agentRowDisplay": { "blocked": "차단됨", @@ -1489,16 +2764,5 @@ "inReview": "리뷰 중", "pinned": "고정됨" } - }, - "workspace": { - "branchChoice": { - "title": "이 워크트리의 브랜치", - "existing": "기존 브랜치", - "existingHint": "새 커밋이 이 브랜치에 추가됩니다.", - "new": "새 브랜치", - "newHint": "새 로컬 브랜치 만들기", - "newName": "새 브랜치 이름", - "automaticName": "자동 이름" - } } } diff --git a/mobile/src/i18n/locales/pt.json b/mobile/src/i18n/locales/pt.json index d2a34ba5543..4e1b7077056 100644 --- a/mobile/src/i18n/locales/pt.json +++ b/mobile/src/i18n/locales/pt.json @@ -143,6 +143,391 @@ "title": "Terminal" } }, + "browser": { + "addressField": { + "urlPlaceholder": "URL" + }, + "pane": { + "invalidUrl": "Digite uma URL válida." + }, + "paneView": { + "dialogTitle": "Diálogo do navegador", + "forward": "Avançar", + "keyboardPlaceholder": "Digite na página…", + "reload": "Recarregar", + "sendText": "Enviar texto ao navegador" + }, + "pointerModifiers": { + "clickModifier": "Modificador de clique {{key}}" + }, + "useBrowserCommands": { + "deliveryUnconfirmed": "Envio não confirmado — verifique a página antes de tentar novamente", + "dialogAnswerFailed": "A resposta não chegou à página.", + "sent": "Enviado" + }, + "viewModeSwitch": { + "mobileView": "Mostrar versão do site para celular", + "webView": "Mostrar versão do site para computador" + } + }, + "chat": { + "ask": { + "answerPlaceholder": "Digite sua resposta", + "next": "Próximo", + "other": "Outro…", + "skip": "Pular", + "step": "Etapa {{step}}", + "submit": "Enviar" + }, + "askRow": { + "asked": "Perguntado:", + "awaiting": "Aguardando entrada do usuário:", + "awaitingUnnamed": "Aguardando sua resposta", + "questionCount": "{{value0}} perguntas" + }, + "composer": { + "addToMessage": "Adicionar à mensagem", + "attachImage": "Anexar imagem", + "cancelEditing": "Cancelar edição", + "placeholder": "Pergunte o que quiser, @ para mencionar arquivos, / para comandos", + "saveEditedMessage": "Salvar mensagem editada", + "sendMessage": "Enviar mensagem" + }, + "composerAttachments": { + "attachedImage": "Imagem anexada", + "file": "Arquivo", + "fileMeta": "{{type}} · {{size}}", + "removeFile": "Remover {{name}}", + "removeImage": "Remover imagem", + "uploadingFile": "Enviando {{name}}" + }, + "composerPlaceholder": { + "prompt": "Enviar uma mensagem…", + "reconnecting": "Reconectando…", + "waitingForTerminal": "Aguardando o terminal…" + }, + "diffCard": { + "copyDiff": "Copiar diff", + "diffGap": "Linhas não exibidas", + "diffTruncated": "Diff truncado" + }, + "hostStatus": { + "continuedOutside": "Continuou fora deste chat.", + "continuedOutsideRewound": "Continuou fora deste chat. As respostas anteriores acima não fazem mais parte do histórico desta conversa.", + "historyItemTooLarge": "Esta parte do chat era grande demais para ser exibida.", + "historyRepaired": "Não foi possível carregar parte do histórico deste chat." + }, + "message": { + "copyMessage": "Copiar mensagem" + }, + "messageActionsSheet": { + "clipboardRejected": "A área de transferência rejeitou o texto.", + "copyFailed": "Falha ao copiar", + "copyMessage": "Copiar mensagem", + "selectText": "Selecionar texto" + }, + "nativeChatImageViewer": { + "closeImage": "Fechar imagem" + }, + "nativeChatProse": { + "attachedImage": "Imagem anexada", + "image": "imagem", + "openImageHint": "Abre a imagem em tamanho original" + }, + "nativeChatToolLine": { + "added": "Adicionou", + "copyCommand": "Copiar comando", + "deleted": "Excluiu", + "durationHours": "{{hours}}h {{minutes}}min {{seconds}}s", + "durationMilliseconds": "{{milliseconds}}ms", + "durationMinutes": "{{minutes}}min {{seconds}}s", + "durationSeconds": "{{seconds}}s", + "edited": "Editou", + "editing": "Editando", + "exitCode": "saída {{exitCode}}", + "fetched": "Obteve", + "fetching": "Obtendo", + "listed": "Listou", + "listing": "Listando", + "ran": "Executou", + "read": "Leu", + "reading": "Lendo", + "renamed": "Renomeou", + "result": "Resultado", + "running": "Executando", + "searched": "Buscou", + "searchedWeb": "Buscou na web", + "searching": "Buscando", + "searchingWeb": "Buscando na web", + "subagent": "Subagente", + "triedEdit": "Tentou editar", + "triedFetch": "Tentou obter", + "triedList": "Tentou listar", + "triedRead": "Tentou ler", + "triedSearch": "Tentou buscar", + "triedSearchWeb": "Tentou buscar na web" + }, + "nativeChatUserMessageFold": { + "loadFailed": "Mostrar mensagem completa — falha ao carregar, toque para tentar novamente", + "showFullMessage": "Mostrar mensagem completa", + "showLess": "Mostrar menos" + }, + "nativeChatVisual": { + "defaultTitle": "Visualização", + "openFullscreen": "Abrir {{title}} em tela cheia", + "retryHint": "Tenta carregar novamente", + "unavailable": "Visualização indisponível" + }, + "notice": { + "compaction": "Contexto compactado", + "contextCleared": "Contexto limpo", + "plan": "Plano", + "stoppedManually": "Interrompido manualmente" + }, + "orcaStop": { + "canContinue": "Você pode continuar nesta conversa.", + "crash": "O Orca ({{machine}}) parou inesperadamente enquanto esta resposta estava em andamento.", + "quit": "O Orca ({{machine}}) foi fechado enquanto esta resposta estava em andamento.", + "remote": "O Orca ({{machine}}) parou enquanto esta resposta estava em andamento.", + "unnamedServer": "o servidor remoto", + "update": "O Orca ({{machine}}) reiniciou para uma atualização enquanto esta resposta estava em andamento." + }, + "permission": { + "needsAccessTo": "Precisa de acesso a: ", + "newerVersionRequired": "Esta solicitação requer uma versão mais recente do Orca.", + "planFile": "Arquivo do plano: {{filePath}}", + "reason": "Motivo: " + }, + "promptCard": { + "answerInTerminal": "Responder no terminal", + "terminalExplanation": "Responda a esta pergunta do OMP no terminal." + }, + "promptStrip": { + "expand": "Expandir" + }, + "question": { + "otherReplyPlaceholder": "Ou digite uma resposta…", + "replyPlaceholder": "Digite sua resposta…", + "sendReply": "Enviar resposta", + "submit": "Enviar", + "submitCount": "Enviar ({{count}})", + "submitSelectedOptions": "Enviar opções selecionadas" + }, + "queuedMessages": { + "clearAll": "Limpar tudo", + "deleteAll": "Excluir todas as mensagens na fila", + "editMessage": "Editar mensagem", + "pausedState": "Pausado", + "queuedState": "Na fila", + "resume": "Retomar", + "resumeSending": "Retomar o envio das mensagens na fila", + "title": "Mensagens na fila" + }, + "receipt": { + "cancelled": "Cancelado", + "cancelledBy": "Cancelado em {{device}}", + "emptyAnswer": "Resposta vazia", + "resolved": "Resolvido", + "resolver": "Respondido em {{device}}", + "unavailable": "Resposta selecionada indisponível" + }, + "sessionOptionCopy": { + "availableAfterSessionStarts": "Disponível após a sessão começar.", + "chooseInAgentPicker": "Escolha no seletor de agente…", + "effort": "Esforço", + "fast": "Rápido", + "fastMode": "Modo rápido", + "high": "Alto", + "low": "Baixo", + "max": "Máximo", + "medium": "Médio", + "mediumShort": "Méd", + "minimal": "Mínimo", + "minimalShort": "Mín", + "model": "Modelo", + "notSet": "Não definido", + "off": "Desativado", + "on": "Ativado", + "permissions": "Permissões", + "sessionOptions": "Opções da sessão", + "setWhenSessionStarts": "Definido quando a sessão começa.", + "speed": "Velocidade", + "standard": "Padrão", + "thinking": "Pensando", + "toggleOption": "Alternar {{value0}}", + "ultra": "Ultra", + "xhigh": "Extra alto", + "xhighShort": "Extra alto" + }, + "sessionOptionPickers": { + "agentWorking": "O agente está trabalhando", + "backToModels": "Voltar aos modelos", + "closePicker": "Fechar seletor", + "dispatchUnconfirmed": "Enviado ao agente — não confirmado", + "modelFullAccessLabel": "Modelo, {{value}}, acesso total", + "modelLabel": "Modelo, {{value}}", + "permissions": "Permissões", + "permissionsLabel": "Permissões, {{value}}", + "settingsLocked": "As configurações do modelo serão desbloqueadas quando este turno terminar.", + "workingAnnouncement": "O agente está trabalhando. As configurações do modelo serão desbloqueadas quando este turno terminar." + }, + "sessionOptionRows": { + "defaultMarker": "Padrão", + "notReportedMarker": "Não informado" + }, + "sessionOptionSegments": { + "dispatchUnconfirmed": "Enviado ao agente — não confirmado" + }, + "stopButton": { + "stopAgent": "Parar o agente", + "stoppingState": "Parando…" + }, + "streamFrame": { + "streamEnded": "Transmissão da conversa encerrada", + "streamFailed": "Falha na transmissão da conversa" + }, + "taskList": { + "added": "Adicionada {{task}}", + "completed": "Concluída", + "empty": "Nenhuma tarefa", + "finished": "Concluída {{task}}", + "inProgress": "Em andamento", + "pending": "Pendente", + "progress": "Tarefas concluídas: {{completed}} de {{total}}", + "removed": "Removida {{task}}", + "reset": "Marcada como pendente: {{task}}", + "showAll": "Lista completa de tarefas", + "started": "Iniciada {{task}}", + "taskStatus": "{{status}}: {{task}}", + "title": "Tarefas", + "unchanged": "Tarefas inalteradas", + "updated": "Atualizada {{task}}" + }, + "toolActivity": { + "count_many": "{{count}} chamadas de ferramenta", + "count_one": "{{count}} chamada de ferramenta", + "count_other": "{{count}} chamadas de ferramenta", + "failedCallsLabel": "Chamadas de ferramenta com falha: {{value0}}", + "failedCount": "{{value0}} com falha", + "interruptedCallsLabel": "Chamadas de ferramenta interrompidas: {{value0}}", + "interruptedCount": "Interrompidas: {{value0}}", + "moreCalls": "+{{value0}} mais", + "result": "Resultado", + "runningCommand": "Executando comando", + "runningNamed": "Executando {{toolName}}", + "runningNamedPreview": "Executando {{toolName}} {{preview}}", + "runningPreview": "Executando {{preview}}" + }, + "toolRun": { + "list": "{{value0}} e {{value1}}", + "liveBrowserOnce": "Usando o navegador", + "liveBrowserTimes_many": "Usando o navegador {{count}} vezes", + "liveBrowserTimes_one": "Usando o navegador {{count}} vez", + "liveBrowserTimes_other": "Usando o navegador {{count}} vezes", + "liveFileChange_many": "Editando {{count}} arquivos", + "liveFileChange_one": "Editando {{count}} arquivo", + "liveFileChange_other": "Editando {{count}} arquivos", + "liveListFiles_many": "Listando {{count}} diretórios", + "liveListFiles_one": "Listando {{count}} diretório", + "liveListFiles_other": "Listando {{count}} diretórios", + "liveMcpToolCall_many": "Usando {{count}} integrações", + "liveMcpToolCall_one": "Usando {{count}} integração", + "liveMcpToolCall_other": "Usando {{count}} integrações", + "liveOther_many": "Usando {{count}} ferramentas", + "liveOther_one": "Usando {{count}} ferramenta", + "liveOther_other": "Usando {{count}} ferramentas", + "liveRead_many": "Lendo {{count}} arquivos", + "liveRead_one": "Lendo {{count}} arquivo", + "liveRead_other": "Lendo {{count}} arquivos", + "liveSearch_many": "Fazendo {{count}} buscas", + "liveSearch_one": "Fazendo {{count}} busca", + "liveSearch_other": "Fazendo {{count}} buscas", + "liveSubAgentActivity_many": "Executando {{count}} agentes", + "liveSubAgentActivity_one": "Executando {{count}} agente", + "liveSubAgentActivity_other": "Executando {{count}} agentes", + "liveTodoListOnce": "Atualizando o plano", + "liveTodoListTimes_many": "Atualizando o plano {{count}} vezes", + "liveTodoListTimes_one": "Atualizando o plano {{count}} vez", + "liveTodoListTimes_other": "Atualizando o plano {{count}} vezes", + "liveUnknown_many": "Executando {{count}} comandos", + "liveUnknown_one": "Executando {{count}} comando", + "liveUnknown_other": "Executando {{count}} comandos", + "liveWebSearch_many": "Buscando na web {{count}} vezes", + "liveWebSearch_one": "Buscando na web {{count}} vez", + "liveWebSearch_other": "Buscando na web {{count}} vezes", + "pair": "{{value0}} e {{value1}}", + "separator": ", ", + "settledBrowserOnce": "Usou o navegador", + "settledBrowserTimes_many": "Usou o navegador {{count}} vezes", + "settledBrowserTimes_one": "Usou o navegador {{count}} vez", + "settledBrowserTimes_other": "Usou o navegador {{count}} vezes", + "settledFileChange_many": "Editou {{count}} arquivos", + "settledFileChange_one": "Editou {{count}} arquivo", + "settledFileChange_other": "Editou {{count}} arquivos", + "settledListFiles_many": "Listou {{count}} diretórios", + "settledListFiles_one": "Listou {{count}} diretório", + "settledListFiles_other": "Listou {{count}} diretórios", + "settledMcpToolCall_many": "Usou {{count}} integrações", + "settledMcpToolCall_one": "Usou {{count}} integração", + "settledMcpToolCall_other": "Usou {{count}} integrações", + "settledOther_many": "Usou {{count}} ferramentas", + "settledOther_one": "Usou {{count}} ferramenta", + "settledOther_other": "Usou {{count}} ferramentas", + "settledRead_many": "Leu {{count}} arquivos", + "settledRead_one": "Leu {{count}} arquivo", + "settledRead_other": "Leu {{count}} arquivos", + "settledSearch_many": "Buscou {{count}} vezes", + "settledSearch_one": "Buscou {{count}} vez", + "settledSearch_other": "Buscou {{count}} vezes", + "settledSubAgentActivity_many": "Executou {{count}} agentes", + "settledSubAgentActivity_one": "Executou {{count}} agente", + "settledSubAgentActivity_other": "Executou {{count}} agentes", + "settledTodoListOnce": "Atualizou o plano", + "settledTodoListTimes_many": "Atualizou o plano {{count}} vezes", + "settledTodoListTimes_one": "Atualizou o plano {{count}} vez", + "settledTodoListTimes_other": "Atualizou o plano {{count}} vezes", + "settledUnknown_many": "Executou {{count}} comandos", + "settledUnknown_one": "Executou {{count}} comando", + "settledUnknown_other": "Executou {{count}} comandos", + "settledWebSearch_many": "Buscou na web {{count}} vezes", + "settledWebSearch_one": "Buscou na web {{count}} vez", + "settledWebSearch_other": "Buscou na web {{count}} vezes" + }, + "turnStatus": { + "failedAfter": "Falhou após {{value0}}", + "hoursMinutesSeconds": "{{hours}}h {{minutes}}m {{seconds}}s", + "interruptedAfter": "Interrompido após {{value0}}", + "minutesSeconds": "{{minutes}}m {{seconds}}s", + "queueAfterStop": "Colocar uma mensagem na fila para executar após a parada", + "responding": "O agente está respondendo", + "seconds_many": "{{count}}s", + "seconds_one": "{{count}}s", + "seconds_other": "{{count}}s", + "sendAfterStop": "Enviar uma mensagem para executar após a parada", + "stopping": "Parando…", + "thinking": "Pensando", + "toggleDetails": "Alternar detalhes do turno", + "workedFor": "Trabalhou por {{value0}}", + "working": "Trabalhando…", + "workingFor": "Trabalhando há {{value0}}" + }, + "usageLimit": { + "reached": "{{agent}} atingiu seu limite de uso.", + "reachedNamed": "{{agent}} atingiu seu limite de {{window}}.", + "resetsAt": "Renovação do limite: {{when}}", + "theAgent": "O agente", + "windowCredits": "uso extra", + "windowSession": "sessão", + "windowWeekly": "uso semanal", + "windowWeeklyModel": "uso semanal do modelo" + }, + "useImageUpload": { + "attachFailed": "Falha ao anexar", + "disconnected": "Falha ao anexar (desconectado)", + "imageTooLarge": "Imagem grande demais para anexar", + "permissionDenied": "Permissão de acesso às fotos negada" + } + }, "common": { "back": "Voltar", "cancel": "Cancelar", @@ -155,6 +540,8 @@ "done": "Concluído", "edit": "Editar", "ok": "OK", + "open": "Abrir", + "refresh": "Atualizar", "remove": "Remover", "retry": "Tentar novamente", "save": "Salvar", @@ -769,8 +1156,51 @@ "unexpectedResponse": "Resposta inesperada" }, "webShellUpdateFailureCopy": { + "blockedReason": "Bloqueado: {{reason}}.", + "blockedWorkspace": "Bloqueou o espaço de trabalho.", + "failed": "Exibiu a tela de falha.", "failedAgo": "A última atualização de {{hostName}} falhou há {{time}}: {{reason}}{{offered}}. {{outcome}}", - "failedJustNow": "A última atualização de {{hostName}} falhou agora mesmo: {{reason}}{{offered}}. {{outcome}}" + "failedJustNow": "A última atualização de {{hostName}} falhou agora mesmo: {{reason}}{{offered}}. {{outcome}}", + "hostCodeCopy": { + "assetChanged": "um recurso foi alterado no host", + "assetUnknown": "o host não reconheceu um recurso", + "buildChanged": "a compilação do host mudou durante o download", + "offsetInvalid": "o host recusou uma posição de leitura", + "readLimited": "o host limitou as leituras simultâneas", + "unavailable": "o host não tem um pacote do espaço de trabalho" + }, + "nativeRoute": "Exibiu a tela nativa.", + "offeredGeneration": " (geração {{generation}})", + "openedCached": "Recorreu à versão salva.", + "openedCachedGeneration": "Recorreu à versão salva (geração {{generation}}).", + "reasonCopy": { + "assetChecksumMismatch": "divergência na soma de verificação do recurso", + "assetEntryChanged": "um recurso deixou de corresponder ao manifesto", + "assetNoProgress": "a leitura de um recurso não avançou", + "assetOverlong": "um recurso excedeu o tamanho declarado no manifesto", + "assetShort": "um recurso terminou antes de atingir o tamanho declarado", + "buildChangedMidFetch": "a compilação do host mudou durante o download", + "cacheWriteFailed": "falha ao salvar o download neste celular", + "chunkMisrouted": "um bloco correspondia ao recurso errado ou à posição errada", + "chunkOversize": "um bloco era maior do que o permitido pelo host", + "connectionLost": "a conexão caiu", + "fetchStopped": "o download foi interrompido", + "hostRefused": "o host recusou a leitura", + "noConnection": "sem conexão com o host", + "rangeUndecodable": "não foi possível decodificar uma leitura compactada", + "replyUnreadable": "o host enviou uma resposta que este app não conseguiu ler", + "unrecognisedError": "um erro não reconhecido" + }, + "routeUnavailable": "Indicou que a tela não está disponível.", + "waiting": "Aguardou o host.", + "wallCopy": { + "bundleShellTooOld": "a versão deste app é antiga demais para o pacote salvo", + "bundleTooOldForHost": "o pacote salvo é antigo demais para o host", + "bundleUnavailable": "o host não tem um pacote do espaço de trabalho", + "hostTooOldForBundle": "a versão do host é antiga demais para o pacote salvo", + "pageRouteGrantMissing": "esta tela requer uma versão mais recente do app", + "pageRouteUnlisted": "o espaço de trabalho do host não inclui esta tela" + } } }, "dictation": { @@ -787,6 +1217,95 @@ "updateFailed": "Falha ao atualizar as configurações de ditado" } }, + "files": { + "fileExplorerPanel": { + "backToSession": "Voltar à sessão", + "closeFiles": "Fechar arquivos", + "connecting": "Conectando ao desktop…", + "emptyState": "Nenhum arquivo encontrado", + "loadFailed": "Não foi possível carregar os arquivos", + "loadInterrupted": "Carregamento da pasta interrompido", + "title": "Arquivos", + "truncatedList": "{{worktreeLabel}} - Mostrando os primeiros 5000", + "waiting": "Aguardando o desktop…" + }, + "fileExplorerRow": { + "fileUnavailable": "{{name}} indisponível no celular", + "loadFolderFailed": "Não foi possível carregar a pasta", + "loading": "Carregando…", + "openFolder": "Abrir pasta {{name}}", + "previewFile": "Visualizar arquivo {{name}}", + "retryLoading": "Tentar carregar {{path}} novamente", + "unavailable": "Indisponível no celular" + }, + "fileMarkdownPreview": { + "copyLoadedSource": "Copiar código-fonte Markdown carregado", + "copySource": "Copiar código-fonte Markdown", + "viewPreview": "Ver pré-visualização renderizada do Markdown", + "viewSource": "Ver código-fonte Markdown" + }, + "fileMediaPreview": { + "downloading": "Baixando {{bytes}} de {{total}}", + "loadFailed": "Não foi possível carregar a mídia", + "loading": "Carregando mídia…", + "waiting": "Aguardando o desktop…" + }, + "fileMutationOwnership": { + "sshOwnerChanged": "Não foi possível verificar a conexão SSH. Reconecte o host e tente novamente." + }, + "filePreviewBody": { + "emptyFile": "Arquivo vazio", + "imageLabel": "Imagem {{title}}" + }, + "filePreviewEditableSource": { + "editorLabel": "Editor de {{title}}" + }, + "filePreviewResponse": { + "binaryUnavailable": "Visualização de arquivos binários indisponível", + "bytes": "{{size}} B", + "fileChanged": "O arquivo foi alterado no desktop. Recarregue a visualização antes de salvar.", + "fileNotFound": "Arquivo não encontrado", + "fileTooLarge": "Arquivo muito grande para visualização no celular", + "filesystemUnreachable": "Não foi possível acessar o sistema de arquivos do desktop", + "kilobytes": "{{size}} KB", + "loadFailed": "Não foi possível carregar a visualização", + "megabytes": "{{size}} MB", + "reloadBeforeSaving": "Recarregue a visualização antes de salvar.", + "unknownSize": "tamanho desconhecido" + }, + "filePreviewScreen": { + "backToFiles": "Voltar aos arquivos", + "chooseWorkspaceFile": "Selecione um arquivo neste espaço de trabalho.", + "discard": "Descartar", + "discardMessage": "As alterações não salvas serão perdidas.", + "discardTitle": "Descartar alterações?", + "fileTitle": "Arquivo", + "loadFailed": "Não foi possível carregar a pré-visualização", + "loading": "Carregando pré-visualização…", + "openFailed": "Não foi possível abrir o arquivo", + "saveArtifact": "Salvar artefato do terminal", + "saveFailed": "Não foi possível salvar o arquivo", + "stay": "Permanecer", + "title": "Pré-visualização", + "waiting": "Aguardando o desktop…" + }, + "filePreviewSourceText": { + "previewLabel": "Visualização do arquivo", + "truncatedNote": "Visualização truncada. Tamanho do arquivo: {{size}}." + }, + "markdownPreviewImage": { + "expandFallback": "Expandir imagem", + "expandImage": "Expandir {{alt}}", + "imageLabel": "Imagem Markdown", + "loadFailed": "Não foi possível carregar a imagem" + }, + "pdfPreview": { + "open": "Abrir PDF", + "openFailed": "Não foi possível abrir este PDF", + "opening": "Abrindo PDF…", + "updateRequired": "Atualize o Orca Mobile para abrir PDFs" + } + }, "home": { "accountUsageCards": { "sectionHeading": "Uso da conta", @@ -1122,12 +1641,10 @@ "noComments": "Ainda não há comentários.", "noDescription": "Nenhuma descrição fornecida.", "noHumanComments": "Nenhum comentário humano.", - "resolvedComment_many": "Comentário de {{author}} resolvido ({{count}})", - "resolvedComment_one": "Comentário de {{author}} resolvido", - "resolvedComment_other": "Comentário de {{author}} resolvido ({{count}})", - "resolvedThread_many": "Thread de {{author}} resolvida ({{count}})", - "resolvedThread_one": "Thread de {{author}} resolvida", - "resolvedThread_other": "Thread de {{author}} resolvida ({{count}})", + "resolvedCommentCount": "Comentário de {{author}} resolvido ({{count}})", + "resolvedCommentSingle": "Comentário de {{author}} resolvido", + "resolvedThreadCount": "Thread de {{author}} resolvida ({{count}})", + "resolvedThreadSingle": "Thread de {{author}} resolvida", "showMore": "Mostrar mais {{count}}", "showMoreOf": "Mostrar mais {{count}} de {{remaining}}", "title": "Comentários" @@ -1326,6 +1843,608 @@ "title": "Revisores" } }, + "session": { + "accessoryKeys": { + "addCustomShortcut": "Adicionar atalho personalizado", + "dismissKeyboard": "Ocultar teclado", + "dismissKeyboardHint": "Oculta o teclado virtual e mantém a sessão atual do terminal aberta.", + "paste": "Colar", + "pasteFromClipboard": "Colar da área de transferência", + "sendKey": "Enviar {{label}}", + "switchToBufferedInput": "Mudar para entrada de comandos em buffer", + "switchToDesktopMode": "Alternar para o modo desktop", + "switchToLiveInput": "Mudar para entrada direta no terminal", + "switchToPhoneMode": "Mudar para o modo celular" + }, + "activeContent": { + "createTab": "Criar Aba", + "creatingTab": "Criando…", + "fileTitle": "Arquivo", + "loadingTerminal": "Carregando terminal", + "noTabs": "Nenhuma aba nesta sessão", + "retryLoadingTerminal": "Tentar carregar o terminal novamente", + "terminalTakingLonger": "O terminal está demorando mais que o esperado" + }, + "agentChildRow": { + "agent": "Agente", + "days_many": "{{count}}d", + "days_one": "{{count}}d", + "days_other": "{{count}}d", + "ended": "Encerrado", + "hours_many": "{{count}}h", + "hours_one": "{{count}}h", + "hours_other": "{{count}}h", + "minutes_many": "{{count}}m", + "minutes_one": "{{count}}m", + "minutes_other": "{{count}}m", + "noRecentUpdate": "Nenhuma atualização recente", + "noUpdate": "Sem atualização há {{value0}}" + }, + "agentMessageAttribution": { + "from": "De {{sender}}", + "fromMore": "De {{sender}} +{{count}}", + "fromUnnamed": "De um agente", + "messageFrom": "Mensagem de {{sender}}", + "messageFromMore": "Mensagem de {{sender}} +{{count}}", + "messageFromUnnamed": "Mensagem de um agente", + "unnamedSender": "um agente" + }, + "aiVaultResumeLaunch": { + "createFailed": "Falha ao criar terminal", + "inputLocked": "A entrada do terminal está bloqueada", + "sendFailed": "Falha ao enviar comando de retomada" + }, + "aiVaultResumePreparation": { + "prepareFailed": "Não foi possível preparar esta sessão legada do Codex. Tente retomar novamente." + }, + "backgroundTask": { + "agent": "Agente em segundo plano", + "command": "Comando em segundo plano", + "countAgents_many": "{{count}} agentes", + "countAgents_one": "{{count}} agente", + "countAgents_other": "{{count}} agentes", + "countMonitors_many": "{{count}} monitores", + "countMonitors_one": "{{count}} monitor", + "countMonitors_other": "{{count}} monitores", + "countShellCommandOne": "1 comando de shell", + "countShell_many": "{{count}} shells", + "countShell_one": "{{count}} shell", + "countShell_other": "{{count}} shells", + "countTasks_many": "{{count}} tarefas", + "countTasks_one": "{{count}} tarefa", + "countTasks_other": "{{count}} tarefas", + "countWorkflows_many": "{{count}} fluxos de trabalho", + "countWorkflows_one": "{{count}} fluxo de trabalho", + "countWorkflows_other": "{{count}} fluxos de trabalho", + "detailsUnavailable": "Os detalhes da tarefa não estão disponíveis para esta sessão.", + "groupAgents": "Agentes", + "groupMonitors": "Monitores", + "groupShell": "Shell", + "groupTasks": "Tarefas", + "groupWorkflows": "Fluxos de Trabalho", + "headerTotalCount_many": "{{count}} tarefas em segundo plano", + "headerTotalCount_one": "{{count}} tarefa em segundo plano", + "headerTotalCount_other": "{{count}} tarefas em segundo plano", + "monitor": "Monitor em segundo plano", + "monitoring": "Monitorando tarefas em segundo plano", + "reasonBlocked": "falhou", + "reasonWaiting": "precisa de aprovação", + "stateBlocked": "bloqueado", + "stateBlockedCount": "{{value0}} com bloqueio", + "stateDone": "concluído", + "stateDoneCount": "{{value0}} com execução concluída", + "stateIdle": "parado", + "stateIdleCount": "{{value0}} com execução interrompida", + "stateMonitoring": "monitorando", + "stateMonitoringCount": "{{value0}} monitorando", + "stateUnverifiable": "não verificável", + "stateUnverifiableCount": "{{value0}} com status indisponível", + "stateWaiting": "aguardando", + "stateWaitingCount": "{{value0}} aguardando", + "stateWorking": "trabalhando", + "stateWorkingCount": "{{value0}} trabalhando", + "stop": "Parar", + "stopAll": "Parar tarefas em segundo plano", + "stopTask": "Parar {{value0}}", + "task": "Tarefa em segundo plano", + "workflow": "Fluxo de trabalho em segundo plano" + }, + "browserTabActionSheet": { + "forward": "Avançar", + "reload": "Recarregar", + "title": "Navegador" + }, + "bulkCloseSheetActions": { + "closeOtherTabs": "Fechar Outras Abas" + }, + "bulkTabClose": { + "confirm": "Fechar abas", + "description": "As abas selecionadas serão fechadas. As abas fixadas continuarão abertas.", + "titleEditors": "Fechar todas as abas do editor?", + "titleLeft": "Fechar abas à esquerda?", + "titleOthers": "Fechar outras abas?", + "titleRight": "Fechar abas à direita?" + }, + "commandDock": { + "commandPlaceholder": "Digite um comando…", + "liveInputHint": "O texto digitado é enviado diretamente ao terminal ativo.", + "liveInputLabel": "Entrada em tempo real no terminal", + "sendCommand": "Enviar comando" + }, + "contentRow": { + "dismissCreationWarning": "Dispensar aviso de criação do espaço de trabalho" + }, + "diffCommentLineRow": { + "addNote": "Adicionar nota na linha {{line}}", + "deleteNote": "Excluir nota na linha {{line}}", + "diffLine": "{{title}}, linha {{line}} do diff", + "line": "Linha {{line}}", + "placeholder": "Adicionar nota de revisão", + "saveNote": "Salvar nota" + }, + "diffLineRow": { + "addNoteLabel": "Adicionar nota na linha {{line}}", + "deleteNoteLabel": "Excluir nota da linha {{line}}", + "diffLineLabel": "Linha {{line}} do diff de {{title}}", + "lineNumber": "Linha {{line}}", + "notePlaceholder": "Adicionar nota de revisão", + "saveNote": "Salvar nota" + }, + "diffReviewLoaders": { + "changesLoadFailed": "Não foi possível carregar as alterações", + "committedChangesFailed": "Falha ao carregar alterações commitadas", + "committedChangesUnavailable": "Alterações commitadas indisponíveis", + "committedDiffUnavailable": "Diff de alterações commitadas indisponível", + "desktopUpdateRequired": "Atualize o Orca no desktop para revisar alterações no celular.", + "diffLoadFailed": "Não foi possível carregar o diff", + "invalidSourceControlResponse": "A resposta do controle de versão foi inválida", + "reviewNotesLoadFailed": "Não foi possível carregar as notas de revisão" + }, + "diffReviewQueue": { + "committedOnBranch": "Commitado na branch", + "staged": "Preparado", + "unstaged": "Não preparado" + }, + "diffReviewScreenModel": { + "branch": "Branch", + "staged": "Preparado", + "unstaged": "Não preparado" + }, + "fileReader": { + "copyReviewNotes": "Copiar notas de revisão", + "imageLabel": "Imagem de {{title}}", + "noReviewNotes": "Nenhuma nota de revisão", + "previewLabel": "Prévia de {{title}}", + "reviewNotes_many": "{{count}} notas de revisão", + "reviewNotes_one": "{{count}} nota de revisão", + "reviewNotes_other": "{{count}} notas de revisão", + "send": "Enviar", + "sendReviewNotes": "Enviar notas de revisão à IA" + }, + "floatingWorkspace": { + "title": "Espaço de Trabalho Flutuante" + }, + "githubPrMutations": { + "reviewThreadUpdateFailed": "Falha ao atualizar a thread de revisão.", + "titleUpdateFailed": "Falha ao atualizar o título." + }, + "githubPrRpc": { + "requestFailed": "Falha na solicitação: {{method}}" + }, + "header": { + "backToWorktrees": "Voltar às worktrees", + "moreActions": "Mais ações da sessão", + "openFileExplorer": "Abrir explorador de arquivos", + "openSourceControl": "Abrir controle de versão", + "reconnectToDesktop": "Reconectar ao desktop", + "showSidebar": "Mostrar barra lateral", + "terminalTitle": "Terminal" + }, + "headerMoreActionsSheet": { + "agentHistory": "Histórico de Agentes", + "agentHistoryHint": "Ver e retomar sessões de agentes", + "checks": "Verificações", + "checksHint": "Abrir verificações do pull request" + }, + "markdownReader": { + "changedOnDesktop": "Alterado no desktop", + "copyLoaded": "Copiar trecho carregado", + "copyLoadedSource": "Copiar código-fonte Markdown carregado", + "copyPath": "Copiar caminho", + "copyPathLabel": "Copiar caminho do arquivo Markdown", + "copySource": "Copiar código-fonte Markdown", + "discard": "Descartar", + "dismissKeyboard": "Ocultar teclado", + "dismissKeyboardHint": "Oculta o teclado virtual e mantém o editor Markdown aberto.", + "readOnly": "Somente leitura" + }, + "nativeChatAttachActions": { + "availableSkills": "Disponíveis: {{count}}", + "commands": "Comandos", + "fileTypes": "PDF, texto, código…", + "files": "Arquivos", + "photos": "Fotos", + "skills": "Skills", + "workspaceFile": "Arquivo do espaço de trabalho" + }, + "nativeChatCardHeaderAction": { + "collapse": "Recolher" + }, + "nativeChatCliFork": { + "resumeInNewCli": "Retomar em nova CLI" + }, + "nativeChatFloatingControls": { + "scrollToLatest": "Rolar até o conteúdo mais recente" + }, + "nativeChatFullContent": { + "loadMessageFailed": "Mostrar mensagem completa — falha ao carregar, toque para tentar novamente", + "loadOutputFailed": "Mostrar saída completa — falha ao carregar, toque para tentar novamente", + "partialMessage": "Exibindo parte desta mensagem.", + "partialOutput": "Exibindo {{shown}} de {{total}}.", + "showFullMessage": "Mostrar mensagem completa", + "showFullOutput": "Mostrar saída completa" + }, + "nativeChatRenderData": { + "defaultAgentLabel": "o agente", + "emptySubtitle": "Peça que {{agent}} inspecione o código, explique a saída ou faça uma alteração.", + "emptyTitle": "Inicie um chat com {{agent}}", + "errorSubtitle": "Não foi possível ler a transcrição. Alterne de volta para o terminal para continuar trabalhando.", + "errorTitle": "Não foi possível carregar a conversa", + "notFoundSubtitle": "Envie uma mensagem para iniciar uma conversa ou abra a visualização do terminal para ver o que {{agent}} está fazendo.", + "notFoundTitle": "Conversa ainda não encontrada" + }, + "nativeChatToggleAction": { + "switchToChatView": "Alternar para a visualização de chat", + "switchToTerminalView": "Alternar para a visualização do terminal" + }, + "newTabAgentHostLaunch": { + "notesNotSent": "O agente foi iniciado, mas as notas não foram enviadas.", + "notesSent": "Notas enviadas", + "notesUnconfirmed": "O agente foi iniciado, mas não foi possível confirmar o envio das notas.", + "promptUnconfirmed": "O agente foi iniciado, mas não foi possível confirmar o envio do prompt." + }, + "prActionsEngine": { + "mergeFailed": "Falha ao fazer merge do pull request.", + "refreshFailed": "Falha ao atualizar o pull request." + }, + "prAiTriageLaunch": { + "agentStarted": "Agente iniciado", + "agentStartedIn": "Agente iniciado em {{label}}", + "emptyPrompt": "O prompt salvo para esta ação está vazio. Atualize as configurações de IA do Controle de Versão no seu computador.", + "loadFailed": "Não foi possível carregar os agentes disponíveis.", + "promptNotSent": "O agente foi iniciado, mas o prompt não foi enviado.", + "updateRequired": "Atualize o Orca no seu computador para iniciar um agente pelo seu celular." + }, + "prSidebarState": { + "loadFailed": "Não foi possível carregar o pull request" + }, + "queuedCardText": { + "showLess": "Mostrar menos", + "showLessHint": "Mostra menos conteúdo da mensagem", + "showMore": "Mostrar mais", + "showMoreHint": "Mostra a mensagem inteira" + }, + "queuedMessageCardRow": { + "deleteHint": "Excluir esta mensagem da fila", + "images_many": "{{count}} imagens", + "images_one": "{{count}} imagem", + "images_other": "{{count}} imagens", + "moreActions": "Mais ações", + "send": "Enviar", + "sendAgainHint": "Enviar esta mensagem novamente", + "sendHint": "Enviar esta mensagem", + "showLessHint": "Mostra menos conteúdo da mensagem", + "steer": "Orientar", + "steerHint": "Enviar sem interromper o modelo" + }, + "queuedMessageComposer": { + "commandsNotAllowed": "Comandos não podem ser salvos em uma mensagem na fila.", + "editing": "Editando mensagem", + "editingAttributed": "Editando mensagem · {{attribution}}" + }, + "quickCommandEditorForm": { + "action": "Ação", + "addQuickCommand": "Adicionar Comando Rápido", + "advanced": "Avançado", + "agent": "Agente", + "agentPrompt": "Prompt de Agente", + "appendEnter": "Adicionar Enter", + "appendEnterDescription": "Envia imediatamente em vez de apenas inserir o texto.", + "chooseAgent": "Escolher agente", + "commandText": "Texto do Comando", + "globalScope": "Global", + "label": "Rótulo", + "labelPlaceholder": "Iniciar servidor de desenvolvimento", + "olderHostPromptTooLong": "Longo demais para este host. Atualize o Orca no host para salvar prompts mais longos.", + "projectScope": "Projeto", + "prompt": "Prompt", + "promptHint": "Suporta skills, caminhos de arquivo e comandos integrados.", + "promptPlaceholder": "Peça ao agente para investigar este espaço de trabalho", + "promptSizeCharacters": "{{used}} / {{max}} caracteres", + "promptSizeKilobytes": "{{used}} KB / {{max}} KB", + "promptTooLong": "Longo demais para salvar.", + "scope": "Escopo", + "terminalCommand": "Comando de Terminal" + }, + "quickCommandLaunch": { + "defaultLabel": "Comando rápido", + "editBeforeRunning": "Edite este comando rápido antes de executá-lo", + "runFailed": "Não foi possível executar {{label}}" + }, + "quickCommandRow": { + "copyCommand": "Copiar {{label}}", + "copyFailed": "Não foi possível copiar", + "deleteCommand": "Excluir {{label}}", + "editCommand": "Editar {{label}}", + "nothingToCopy": "Nada para copiar", + "runCommand": "Executar {{label}}" + }, + "quickCommandsList": { + "emptyState": "Ainda não há comandos rápidos.", + "globalGroup": "Global", + "newCommand": "Novo comando rápido", + "noMatches": "Nenhum comando rápido encontrado.", + "projectGroup": "Este projeto", + "searchPlaceholder": "Buscar comandos rápidos…" + }, + "quickCommandsSheet": { + "addTitle": "Adicionar Comando Rápido", + "chooseAgentTitle": "Escolher Agente", + "deleteConfirmMessage": "Este comando rápido será removido da sua lista salva.", + "deleteConfirmTitle": "Excluir \"{{label}}\"?", + "editTitle": "Editar Comando Rápido", + "editorDescription": "Salve comandos de terminal ou prompts de agente para acesso rápido.", + "title": "Comandos Rápidos", + "untitled": "Sem título" + }, + "quickCommandsTabButton": { + "quickCommands": "Comandos rápidos" + }, + "routeHelpers": { + "connected": "Conectado", + "connecting": "Conectando", + "disconnected": "Desconectado", + "pairingInvalid": "Pareamento inválido", + "reconnecting": "Reconectando", + "securing": "Protegendo a conexão" + }, + "routeScreen": { + "openingWorkspace": "Abrindo espaço de trabalho" + }, + "sheets": { + "browser": "Navegador", + "browserUpdateRequired": "Atualize o desktop para transmitir o navegador ao celular", + "chat": "Chat", + "copyAllAndLeave": "Copiar Tudo e Sair", + "copyDraftsFailed": "Não foi possível copiar os rascunhos", + "copyNotes": "Copiar Notas", + "copyNotesFailed": "Não foi possível copiar as notas", + "copyPath": "Copiar Caminho", + "discard": "Descartar", + "discardAndLeave": "Descartar e Sair", + "discardChanges": "Descartar alterações?", + "discardMessage": "Substituir o rascunho do celular pela versão mais recente do arquivo no desktop?", + "draftsHint": "Copie ou descarte os rascunhos do celular antes de sair.", + "file": "Arquivo", + "markdown": "Markdown", + "markdownNote": "Nota em Markdown", + "newBrowser": "Novo Navegador", + "newTab": "Nova Aba", + "notesCopied": "Notas copiadas", + "notesHint": "Selecione uma sessão de agente para as notas atuais.", + "removeShortcutMessage": "Remover este atalho personalizado?", + "renameTerminal": "Renomear Terminal", + "sendReviewNotes": "Enviar Notas de Revisão", + "shortcut": "Atalho", + "terminal": "Terminal", + "terminalName": "Nome do terminal", + "unsavedMarkdown": "Alterações em Markdown não salvas", + "urlPrompt": "Digite uma URL ou deixe em branco para abrir uma nova aba." + }, + "sideRail": { + "backToWorktrees": "Voltar para as worktrees" + }, + "sourceControlLaunchAgent": { + "noEnabledAgent": "Nenhum agente de IA ativado foi detectado neste host do espaço de trabalho.", + "savedAgentUnavailable": "O agente salvo para esta ação não está disponível neste host do espaço de trabalho." + }, + "structuredComposerCommand": { + "operationUnconfirmed": "A operação na conversa não foi confirmada.", + "removeAttachments": "Remova os anexos antes de usar um comando de sessão de chat." + }, + "structuredSendDelivery": { + "messageNotSent": "Mensagem não enviada" + }, + "tabStrip": { + "checkingCapabilities": "Verificando os recursos do desktop — tente novamente em instantes", + "newTab": "Nova aba", + "quickCommandsUpdateRequired": "Atualize o desktop para usar comandos rápidos", + "tabStatus": "{{title}}, {{status}}" + }, + "terminalActionSheetActions": { + "clearTerminal": "Limpar Terminal", + "rename": "Renomear", + "switchToDesktop": "Alternar para o modo desktop", + "switchToPhone": "Alternar para o modo celular" + }, + "terminalInputActions": { + "attachFileHint": "Mantenha pressionado para anexar um arquivo em vez de uma foto", + "attachPhoto": "Anexar uma foto", + "sendingImage": "Enviando imagem", + "startDictation": "Iniciar ditado por voz", + "startingDictation": "Iniciando ditado por voz", + "stopDictation": "Parar ditado por voz", + "transcribingDictation": "Transcrevendo ditado por voz" + }, + "terminalLiveInputStatus": { + "listening": "Ouvindo", + "liveInput": "Entrada em tempo real", + "preparingMic": "Preparando microfone", + "processing": "Processando", + "releaseMicHint": "Solte o microfone para parar", + "startingMic": "Ativando microfone", + "stopHint": "Toque no microfone para parar", + "transcribing": "Transcrevendo no desktop", + "uploadingImage": "Enviando imagem para o host", + "uploadingImageTitle": "Enviando imagem" + }, + "terminalRecords": { + "defaultTitle": "Terminal" + }, + "terminalTabAgent": { + "browser": "Navegador", + "chat": "Chat", + "file": "Arquivo", + "markdown": "Markdown", + "newBrowser": "Novo Navegador", + "terminal": "Terminal" + }, + "terminalViewportResubscribe": { + "fitFailed": "Não foi possível ajustar o terminal a esta tela" + }, + "useAccessorySelection": { + "copyFailed": "Não foi possível copiar", + "selectionCleared": "Seleção limpa (saiu do buffer durante a rolagem)" + }, + "useAttachmentInputLeaseGate": { + "reconnecting": "Falha ao anexar (reconectando)" + }, + "useContentCreateActions": { + "browserCommandFailed": "Falha no comando do navegador", + "browserCreateFailed": "Falha ao criar o navegador", + "browserUnavailable": "A página do navegador ainda não está disponível.", + "browserUpdateRequired": "É preciso atualizar o desktop para transmitir o navegador ao celular", + "invalidUrl": "Digite uma URL válida", + "noteCreateFailed": "Falha ao criar nota em Markdown", + "untitledNoteCreateFailed": "Não foi possível criar nota em Markdown sem título" + }, + "useDiffComments": { + "copyNotesFailed": "Não foi possível copiar as notas", + "deleteNoteFailed": "Falha ao excluir a nota", + "noteAdded": "Nota adicionada", + "notesCopied": "Notas copiadas", + "saveNoteFailed": "Falha ao salvar a nota", + "saveNotesFailed": "Falha ao salvar as notas de revisão", + "waitingForDesktop": "Aguardando o desktop…" + }, + "useDiffReviewCommentActions": { + "saveFailed": "Falha ao salvar a revisão", + "saveStateFailed": "Falha ao salvar o estado da revisão", + "waitingForDesktop": "Aguardando o desktop…" + }, + "useDiffReviewController": { + "loadFailed": "Não foi possível carregar a revisão", + "missingWorktree": "Worktree ausente", + "waitingForDesktop": "Aguardando o desktop…" + }, + "useDiffReviewDiffLoading": { + "loadFailed": "Não foi possível carregar o diff", + "waitingForDesktop": "Aguardando o desktop…" + }, + "useDiffReviewGitActions": { + "actionFailed": "Falha na ação de controle de versão", + "reviewedFilesStaged_many": "{{count}} arquivos revisados adicionados ao stage", + "reviewedFilesStaged_one": "{{count}} arquivo revisado adicionado ao stage", + "reviewedFilesStaged_other": "{{count}} arquivos revisados adicionados ao stage", + "stageResults": "No stage: {{staged}}, com falha: {{failed}}", + "waitingForDesktop": "Aguardando o desktop…" + }, + "useDiffReviewInteractions": { + "openFailed": "Não foi possível abrir na sessão" + }, + "useDiffReviewSendActions": { + "copyFailed": "Não foi possível copiar as notas de revisão", + "inputLocked": "A entrada do terminal está bloqueada", + "loadSessionsFailed": "Não foi possível carregar as sessões dos agentes", + "notesCopied": "Notas de revisão copiadas", + "notesSent": "Notas de revisão enviadas", + "notesSentWithWarning": "Notas de revisão enviadas. {{warning}}", + "promptNotSent": "O agente foi iniciado, mas as notas não foram enviadas. Use Copiar Notas para colá-las.", + "sendFailed": "Falha ao enviar as notas", + "startingAgent": "Iniciando um agente…", + "waitingForDesktop": "Aguardando o desktop…" + }, + "useImageAttachment": { + "attachFailed": "Falha ao anexar", + "disconnected": "Falha ao anexar (desconectado)", + "imageTooLarge": "Imagem grande demais para anexar", + "permissionDenied": "Permissão de acesso às fotos negada" + }, + "useMarkdownActions": { + "copyFailed": "Não foi possível copiar", + "copyPathFailed": "Não foi possível copiar o caminho", + "markdownTitle": "Markdown", + "pathCopied": "Caminho copiado", + "saveFailed": "Falha ao salvar", + "saved": "Salvo" + }, + "useNativeChatDictation": { + "insertFailed": "Não foi possível inserir o ditado", + "insertFailedCopied": "Não foi possível inserir o ditado, então ele foi copiado para a área de transferência", + "inserted": "Ditado inserido" + }, + "useNativeChatStop": { + "stopNotSent": "Comando de parada não enviado", + "stopUnconfirmed": "Parada não confirmada — confira o chat antes de tentar novamente", + "terminalNotReady": "Comando de parada não enviado (terminal não está pronto)" + }, + "usePanelRouteActions": { + "checkHostHint": "Verifique a conexão com o host", + "copyNotesHint": "Copie as notas como alternativa", + "detectingAgents": "Detectando agentes", + "newSessionHint": "Nova sessão de agente", + "noEnabledAgents": "Nenhum agente habilitado", + "otherServerAgents": "Agentes em outro servidor", + "pairServerHint": "Pareie diretamente com esse servidor", + "presetsUnavailable": "Predefinições de agentes indisponíveis" + }, + "usePrAiTriage": { + "launchFailed": "Falha ao iniciar o agente", + "waitingForDesktop": "Aguardando o desktop…" + }, + "usePrCommentActions": { + "actionFailed": "Falha ao executar a ação no comentário" + }, + "usePrTitleAction": { + "disconnected": "Sem conexão com o desktop.", + "updateFailed": "Falha ao atualizar o título." + }, + "useQuickCommands": { + "loadFailed": "Falha ao carregar os comandos rápidos", + "saveFailed": "Falha ao salvar o comando rápido" + }, + "useSessionTerminalSendActions": { + "inputTooLarge": "Entrada grande demais (máx. 256 KiB)" + }, + "useStartup": { + "wakeAgents": "Abra o Orca no host para ativar os agentes suspensos." + }, + "useTerminalCreateActions": { + "createFailed": "Falha ao criar o terminal", + "forkBusy": "Outro terminal está sendo aberto. Aguarde e tente abrir novamente o chat do {{agentName}}.", + "forkUnavailable": "Abra o chat do {{agentName}} em um host conectado e atualizado e tente novamente.", + "inputLocked": "A entrada do terminal está bloqueada por outro cliente.", + "notesSent": "Notas enviadas", + "sendNotesError": "Não foi possível enviar as notas", + "sendNotesFailed": "Falha ao enviar as notas", + "terminalTitle": "Terminal" + }, + "useTerminalInput": { + "clearFailed": "Não foi possível limpar o terminal", + "cleared": "Terminal limpo" + }, + "useTerminalPaste": { + "clipboardReadFailed": "Não foi possível ler a área de transferência", + "disconnected": "Falha ao colar (sem conexão)", + "imageTooLarge": "Imagem grande demais para colar", + "pasteFailed": "Falha ao colar", + "pasteTooLarge": "Colagem grande demais (máx. 256 KiB)", + "terminalChanged": "Colagem cancelada (o terminal mudou)" + }, + "worktreeLabel": { + "fallback": "Worktree" + } + }, "settings": { "aboutScreen": { "openLinkFailed": "Não foi possível abrir o link. Tente novamente.", @@ -1470,6 +2589,193 @@ "updateFailed": "Não foi possível atualizar" } }, + "sourceControl": { + "actions": { + "commits": "Commits", + "createPr": "Criar PR", + "pushAndCreatePr": "Push e Criar PR", + "switchBranch": "Trocar de branch" + }, + "commitDropdown": { + "checkOutBranchToPush": "Faça checkout de uma branch antes de fazer push de commits", + "checkOutBranchToSync": "Faça checkout de uma branch antes de sincronizar commits", + "checkingBranchStatus": "Verificando o status da branch…", + "checkingPrStatus": "Verificando o status do PR…", + "commit": "Commit", + "commitAndForcePush": "Fazer commit das alterações staged e force push com lease", + "commitAndPush": "Fazer commit das alterações staged e push", + "commitAndTryPush": "Fazer commit das alterações staged e tentar fazer push", + "commitForcePush": "Commit e Force Push", + "commitPush": "Commit e Push", + "commitStagedChanges": "Fazer commit das alterações staged", + "commitSync": "Commit e Sincronizar", + "commitThenPullPush": "Faça commit e depois pull e push", + "enterMessage": "Digite uma mensagem de commit para fazer commit", + "linkedReviewTargetUnavailable": "O destino da branch de revisão vinculada não está disponível", + "prAlreadyMerged": "O PR já foi mergeado", + "publishBranchFirstToPush": "Publique a branch primeiro para fazer push de commits", + "publishBranchFirstToSync": "Publique a branch primeiro para sincronizar commits", + "resolveConflicts": "Resolva os conflitos antes de fazer commit", + "stageFile": "Coloque pelo menos um arquivo em stage para fazer commit", + "useCommitAndForcePush": "Use Commit e Force Push — o remoto só tem cópias antigas de commits locais" + }, + "createPrAction": { + "createReview": "Criar {{titleLabel}}" + }, + "dropdownCounts": { + "fastForwardCount_many": "Fazer fast-forward de {{count}} commits", + "fastForwardCount_one": "Fazer fast-forward de {{count}} commit", + "fastForwardCount_other": "Fazer fast-forward de {{count}} commits", + "pullCount_many": "Fazer pull de {{count}} commits", + "pullCount_one": "Fazer pull de {{count}} commit", + "pullCount_other": "Fazer pull de {{count}} commits", + "pushCount_many": "Fazer push de {{count}} commits", + "pushCount_one": "Fazer push de {{count}} commit", + "pushCount_other": "Fazer push de {{count}} commits", + "remoteBranchFallback": "a branch remota", + "syncCounts": "Pull de {{behind}}, push de {{ahead}}", + "titleForcePushOlderCopiesBranch": "O remoto só tem cópias antigas de commits locais. Faça force push com lease desta branch para atualizar {{upstreamName}}.", + "titleForcePushOlderCopiesCount_many": "O remoto só tem cópias antigas de commits locais. Faça force push com lease de {{count}} commits da branch para atualizar {{upstreamName}}.", + "titleForcePushOlderCopiesCount_one": "O remoto só tem cópias antigas de commits locais. Faça force push com lease de {{count}} commit da branch para atualizar {{upstreamName}}.", + "titleForcePushOlderCopiesCount_other": "O remoto só tem cópias antigas de commits locais. Faça force push com lease de {{count}} commits da branch para atualizar {{upstreamName}}.", + "titleManualForcePushReplaceRemote_many": "Faça force push com lease de {{count}} commits locais para atualizar {{upstreamName}} e substituir os commits que só existem no remoto.", + "titleManualForcePushReplaceRemote_one": "Faça force push com lease de {{count}} commit local para atualizar {{upstreamName}} e substituir os commits que só existem no remoto.", + "titleManualForcePushReplaceRemote_other": "Faça force push com lease de {{count}} commits locais para atualizar {{upstreamName}} e substituir os commits que só existem no remoto.", + "titleManualForcePush_many": "Faça force push com lease de {{count}} commits locais para atualizar {{upstreamName}}.", + "titleManualForcePush_one": "Faça force push com lease de {{count}} commit local para atualizar {{upstreamName}}.", + "titleManualForcePush_other": "Faça force push com lease de {{count}} commits locais para atualizar {{upstreamName}}.", + "titleUnpublishedForcePushBranch": "Faça force push com lease desta branch e defina um upstream se necessário.", + "titleUnpublishedForcePushCount_many": "Faça force push com lease de {{count}} commits da branch e defina um upstream se necessário.", + "titleUnpublishedForcePushCount_one": "Faça force push com lease de {{count}} commit da branch e defina um upstream se necessário.", + "titleUnpublishedForcePushCount_other": "Faça force push com lease de {{count}} commits da branch e defina um upstream se necessário." + }, + "hostedReview": { + "mrCopy": { + "reviewLabel": "merge request", + "shortLabel": "MR", + "titleLabel": "Merge Request" + }, + "prCopy": { + "reviewLabel": "pull request", + "shortLabel": "PR", + "titleLabel": "Pull Request" + } + }, + "hostedReviewCreate": { + "pushSucceeded": "Push concluído, mas a criação do {{shortLabel}} falhou: {{error}}" + }, + "prCreate": { + "authRequired": "Autentique-se antes de criar um {{reviewLabel}}.", + "baseNotOnRemote": "Faça push da branch base antes de criar um {{reviewLabel}}.", + "createdNotRefreshed": "{{titleLabel}} criado, mas o Orca ainda não conseguiu atualizá-lo.", + "defaultBranch": "Mude para uma branch de funcionalidade antes de criar um {{reviewLabel}}.", + "detachedHead": "Faça checkout de uma branch antes de criar um {{reviewLabel}}.", + "dirty": "Faça commit das alterações antes de criar um {{reviewLabel}}.", + "existing": "{{titleLabel}} já está aberto.", + "existingNumber": "{{titleLabel}} #{{number}} já está aberto.", + "existingReview": "Já existe um(a) {{reviewLabel}} para esta branch.", + "forkHeadUnsupported": "Não há suporte à criação de um {{reviewLabel}} a partir deste fork.", + "lookupUnconfirmed": "O Orca não conseguiu confirmar se esta branch já tem um {{reviewLabel}}. Tente novamente em instantes.", + "needsSync": "Sincronize esta branch antes de criar um {{reviewLabel}}.", + "noUpstream": "Publique os commits antes de criar um {{reviewLabel}}.", + "notReady": "Esta branch ainda não está pronta para um {{reviewLabel}}.", + "unsupportedMergeRequests": "Não há suporte à criação de merge requests neste repositório.", + "unsupportedPullRequests": "Não há suporte à criação de pull requests neste repositório." + }, + "primaryAction": { + "forcePushFromDesktop": "Faça force push da branch deste pull request pelo desktop" + }, + "remoteDropdown": { + "branchAlreadyPublished": "A branch já está publicada", + "branchUpToDate": "A branch está atualizada", + "checkOutBranchToFastForward": "Faça checkout de uma branch antes de fazer fast-forward", + "checkOutBranchToForcePush": "Faça checkout de uma branch antes de fazer force push de commits", + "checkOutBranchToPublish": "Faça checkout de uma branch antes de publicar commits", + "checkOutBranchToPull": "Faça checkout de uma branch antes de fazer pull de commits", + "checkOutBranchToPush": "Faça checkout de uma branch antes de fazer push de commits", + "checkOutBranchToSync": "Faça checkout de uma branch antes de sincronizar commits", + "checkingBranchStatus": "Verificando o status da branch…", + "checkingPrStatus": "Verificando o status do PR…", + "fastForward": "Fast-forward", + "fetch": "Fetch", + "fetchTitle": "Fetch do remoto sem fazer merge", + "forcePush": "Force Push", + "linkedReviewBranchExists": "A branch de review vinculada já existe", + "linkedReviewTargetUnavailable": "O destino da branch de review vinculada não está disponível", + "nothingNewToFastForward": "Nada novo para fazer fast-forward — o remoto só tem cópias antigas de commits locais", + "nothingNewToPull": "Nada novo para fazer pull — o remoto só tem cópias antigas de commits locais", + "nothingToFastForward": "Nada para fazer fast-forward", + "nothingToForcePush": "Nada para fazer force push", + "nothingToForcePushTo": "Nada para fazer force push para {{upstreamName}}", + "nothingToPull": "Nada para fazer pull", + "nothingToPush": "Nada para fazer push", + "nothingToPushTo": "Nada para fazer push para {{upstreamName}}", + "prAlreadyMerged": "O PR já foi mergeado", + "publishBranch": "Publicar Branch", + "publishBranchFirstToFastForward": "Publique a branch primeiro para fazer fast-forward", + "publishBranchFirstToPull": "Publique a branch primeiro para fazer pull de commits", + "publishBranchFirstToSync": "Publique a branch primeiro para sincronizar commits", + "publishLinkedReview": "Review vinculada", + "publishNoBranch": "Sem branch", + "publishPrStatus": "Status do PR", + "publishToOrigin": "Publicar esta branch no origin", + "pull": "Pull", + "push": "Push", + "pushAndSetUpstream": "Faça push desta branch e defina um upstream se necessário", + "pushMayNeedSync": "Faça push dos commits locais; o git pode exigir sincronizar primeiro", + "pushToLinkedReview": "Faça push das atualizações para a branch de review vinculada", + "rebaseChooseRemoteBase": "Escolha uma branch base remota para fazer rebase", + "rebaseDirtyHint": "Tente fazer rebase; o git pode exigir commit ou stash das alterações locais primeiro", + "rebaseFromBase": "Rebase a partir da base", + "rebaseFromNamedBase": "Rebase a partir de {{baseRef}}", + "rebaseWithLatestFrom": "Fazer rebase da branch atual com os commits mais recentes de {{baseRef}}", + "sync": "Sincronizar", + "tryFastForwardPull": "Tente um pull com fast-forward; o git pode rejeitar commits locais", + "tryRegularPush": "Tente um push normal; o git pode exigir force push", + "useForcePushToSync": "Use Force Push — o remoto só tem cópias antigas de commits locais" + } + }, + "terminal": { + "keyDefinitions": { + "accessoryArrowDown": "Seta para baixo", + "accessoryArrowLeft": "Seta para a esquerda", + "accessoryArrowRight": "Seta para a direita", + "accessoryArrowUp": "Seta para cima", + "arrowDown": "Seta para baixo", + "arrowLeft": "Seta para a esquerda", + "arrowRight": "Seta para a direita", + "arrowUp": "Seta para cima", + "clearLineBeforeCursor": "Limpar linha antes do cursor", + "clearScreen": "Limpar tela", + "deleteWordBackward": "Excluir palavra anterior", + "endOfLine": "Fim da linha", + "forwardDelete": "Excluir à frente", + "interruptTerminal": "Interromper terminal", + "reverseSearch": "Busca reversa", + "sendEof": "Enviar EOF", + "startOfLine": "Início da linha", + "suspendProcess": "Suspender processo" + }, + "webView": { + "documentStartFailed": "falha ao iniciar o documento do terminal - {{message}}", + "httpError": "Erro HTTP na WebView do terminal", + "loadFailed": "Falha ao carregar a WebView do terminal", + "renderProcessEnded": "O processo de renderização da WebView do terminal foi encerrado" + }, + "webviewEngineErrorState": { + "code": "código {{code}}", + "reload": "Recarregar", + "rendererCrashed": "o renderizador parou inesperadamente", + "status": "status {{statusCode}}", + "title": "Falha ao carregar o terminal" + }, + "webviewNotificationDispatch": { + "unknownError": "Erro desconhecido no terminal" + }, + "webviewReadyWatchdog": { + "initializeFailed": "O terminal não foi inicializado - a visualização do terminal não sinalizou que está pronta" + } + }, "webShell": { "shellStateScreen": { "checkingHost": "Verificando host", @@ -1484,6 +2790,18 @@ "tryAgain": "Tentar novamente" } }, + "workspace": { + "branchChoice": { + "automaticName": "Nome automático", + "existing": "Branch existente", + "existingHint": "Novos commits são adicionados a esta branch.", + "hostUpdateRequired": "Atualize o Orca no host do espaço de trabalho para reutilizar uma branch ou criar explicitamente uma branch separada.", + "new": "Nova branch", + "newHint": "Criar uma nova branch local", + "newName": "Nome da nova branch", + "title": "Branch para esta árvore de trabalho" + } + }, "worktree": { "agentRowDisplay": { "blocked": "Bloqueado", @@ -1526,16 +2844,5 @@ "inReview": "Em revisão", "pinned": "Fixados" } - }, - "workspace": { - "branchChoice": { - "title": "Branch para esta árvore de trabalho", - "existing": "Branch existente", - "existingHint": "Novos commits são adicionados a esta branch.", - "new": "Nova branch", - "newHint": "Criar uma nova branch local", - "newName": "Nome da nova branch", - "automaticName": "Nome automático" - } } } diff --git a/mobile/src/i18n/locales/ru.json b/mobile/src/i18n/locales/ru.json index 12699944c2c..40a8905ac08 100644 --- a/mobile/src/i18n/locales/ru.json +++ b/mobile/src/i18n/locales/ru.json @@ -147,6 +147,415 @@ "title": "Терминал" } }, + "browser": { + "addressField": { + "urlPlaceholder": "URL" + }, + "pane": { + "invalidUrl": "Введите корректный URL." + }, + "paneView": { + "dialogTitle": "Диалог браузера", + "forward": "Вперёд", + "keyboardPlaceholder": "Введите текст для страницы…", + "reload": "Перезагрузить", + "sendText": "Отправить текст в браузер" + }, + "pointerModifiers": { + "clickModifier": "Модификатор {{key}} для щелчка" + }, + "useBrowserCommands": { + "deliveryUnconfirmed": "Отправка не подтверждена — проверьте страницу перед повторной попыткой", + "dialogAnswerFailed": "Не удалось передать ответ странице.", + "sent": "Отправлено" + }, + "viewModeSwitch": { + "mobileView": "Показать мобильную версию сайта", + "webView": "Показать версию сайта для компьютера" + } + }, + "chat": { + "ask": { + "answerPlaceholder": "Введите ответ", + "next": "Далее", + "other": "Другое…", + "skip": "Пропустить", + "step": "Шаг {{step}}", + "submit": "Ответить" + }, + "askRow": { + "asked": "Вопрос:", + "awaiting": "Ждёт вашего ответа:", + "awaitingUnnamed": "Ждёт вашего ответа", + "questionCount": "Вопросов: {{value0}}" + }, + "composer": { + "addToMessage": "Добавить к сообщению", + "attachImage": "Прикрепить изображение", + "cancelEditing": "Отменить редактирование", + "placeholder": "Задайте вопрос; @ — файлы, / — команды", + "saveEditedMessage": "Сохранить изменённое сообщение", + "sendMessage": "Отправить сообщение" + }, + "composerAttachments": { + "attachedImage": "Прикреплённое изображение", + "file": "Файл", + "fileMeta": "{{type}} · {{size}}", + "removeFile": "Удалить {{name}}", + "removeImage": "Удалить изображение", + "uploadingFile": "Загрузка {{name}}" + }, + "composerPlaceholder": { + "prompt": "Задайте вопрос; @ — файлы, / — команды", + "reconnecting": "Восстановление соединения…", + "waitingForTerminal": "Ожидание терминала…" + }, + "diffCard": { + "copyDiff": "Копировать изменения", + "diffGap": "Пропущенные строки", + "diffTruncated": "Изменения показаны не полностью" + }, + "hostStatus": { + "continuedOutside": "Разговор продолжен вне этого чата.", + "continuedOutsideRewound": "Разговор продолжен вне этого чата. Предыдущие ответы выше больше не входят в его историю.", + "historyItemTooLarge": "Эта часть чата слишком велика для отображения.", + "historyRepaired": "Часть истории этого чата не удалось загрузить." + }, + "message": { + "copyMessage": "Копировать сообщение" + }, + "messageActionsSheet": { + "clipboardRejected": "Не удалось записать текст в буфер обмена.", + "copyFailed": "Не удалось скопировать", + "copyMessage": "Копировать сообщение", + "selectText": "Выделить текст" + }, + "nativeChatImageViewer": { + "closeImage": "Закрыть изображение" + }, + "nativeChatProse": { + "attachedImage": "Прикреплённое изображение", + "image": "изображение", + "openImageHint": "Открывает изображение в полном размере" + }, + "nativeChatToolLine": { + "added": "Добавлено", + "copyCommand": "Копировать команду", + "deleted": "Удалено", + "durationHours": "{{hours}} ч {{minutes}} мин {{seconds}} с", + "durationMilliseconds": "{{milliseconds}} мс", + "durationMinutes": "{{minutes}} мин {{seconds}} с", + "durationSeconds": "{{seconds}} с", + "edited": "Изменено", + "editing": "Редактирует", + "exitCode": "код выхода: {{exitCode}}", + "fetched": "Загружено", + "fetching": "Загружает", + "listed": "Показан список", + "listing": "Получает список", + "ran": "Выполнено", + "read": "Прочитано", + "reading": "Читает", + "renamed": "Переименовано", + "result": "Результат", + "running": "Выполняется", + "searched": "Поиск завершён", + "searchedWeb": "Поиск в интернете завершён", + "searching": "Ищет", + "searchingWeb": "Ищет в интернете", + "subagent": "Дочерний агент", + "triedEdit": "Попытка редактирования", + "triedFetch": "Попытка загрузки", + "triedList": "Попытка получить список", + "triedRead": "Попытка чтения", + "triedSearch": "Попытка поиска", + "triedSearchWeb": "Попытка поиска в интернете" + }, + "nativeChatUserMessageFold": { + "loadFailed": "Показать сообщение целиком — сбой загрузки, нажмите для повтора", + "showFullMessage": "Показать сообщение целиком", + "showLess": "Свернуть" + }, + "nativeChatVisual": { + "defaultTitle": "Визуализация", + "openFullscreen": "Открыть «{{title}}» на весь экран", + "retryHint": "Повторяет попытку загрузки", + "unavailable": "Визуализация недоступна" + }, + "notice": { + "compaction": "Контекст сокращён", + "contextCleared": "Контекст очищен", + "plan": "План", + "stoppedManually": "Остановлено вручную" + }, + "orcaStop": { + "canContinue": "Вы можете продолжить этот разговор.", + "crash": "Во время ответа Orca на {{machine}} неожиданно завершилась.", + "quit": "Во время ответа Orca на {{machine}} была закрыта.", + "remote": "Во время ответа Orca на {{machine}} остановилась.", + "unnamedServer": "удалённом сервере", + "update": "Во время ответа Orca на {{machine}} перезапустилась для обновления." + }, + "permission": { + "needsAccessTo": "Требуется доступ: ", + "newerVersionRequired": "Для этого запроса нужна более новая версия Orca.", + "planFile": "Файл плана: {{filePath}}", + "reason": "Причина: " + }, + "promptCard": { + "answerInTerminal": "Ответить в терминале", + "terminalExplanation": "Ответьте на этот вопрос OMP в терминале." + }, + "promptStrip": { + "expand": "Развернуть" + }, + "question": { + "otherReplyPlaceholder": "Или введите ответ…", + "replyPlaceholder": "Введите ответ…", + "sendReply": "Отправить ответ", + "submit": "Ответить", + "submitCount": "Ответить ({{count}})", + "submitSelectedOptions": "Отправить выбранные варианты" + }, + "queuedMessages": { + "clearAll": "Очистить всё", + "deleteAll": "Удалить все сообщения из очереди", + "editMessage": "Изменить сообщение", + "pausedState": "Приостановлено", + "queuedState": "В очереди", + "resume": "Продолжить", + "resumeSending": "Возобновить отправку сообщений из очереди", + "title": "Очередь сообщений" + }, + "receipt": { + "cancelled": "Отменено", + "cancelledBy": "Отменено на устройстве {{device}}", + "emptyAnswer": "Пустой ответ", + "resolved": "Решено", + "resolver": "Ответ получен с устройства {{device}}", + "unavailable": "Выбранный ответ недоступен" + }, + "sessionOptionCopy": { + "availableAfterSessionStarts": "Доступно после запуска сессии.", + "chooseInAgentPicker": "Выбрать в списке агентов…", + "effort": "Усилие", + "fast": "Быстро", + "fastMode": "Быстрый режим", + "high": "Высокий", + "low": "Низкий", + "max": "Максимальный", + "medium": "Средний", + "mediumShort": "Сред.", + "minimal": "Минимальный", + "minimalShort": "Мин.", + "model": "Модель", + "notSet": "Не задано", + "off": "Выключено", + "on": "Включено", + "permissions": "Разрешения", + "sessionOptions": "Параметры сессии", + "setWhenSessionStarts": "Задаётся только при запуске сессии.", + "speed": "Скорость", + "standard": "Стандартный", + "thinking": "Обдумывание", + "toggleOption": "Переключить {{value0}}", + "ultra": "Предельный", + "xhigh": "Очень высокий", + "xhighShort": "Очень выс." + }, + "sessionOptionPickers": { + "agentWorking": "Агент работает", + "backToModels": "Назад к моделям", + "closePicker": "Закрыть окно выбора", + "dispatchUnconfirmed": "Отправлено агенту — получение не подтверждено", + "modelFullAccessLabel": "Модель: {{value}}, полный доступ", + "modelLabel": "Модель: {{value}}", + "permissions": "Разрешения", + "permissionsLabel": "Разрешения: {{value}}", + "settingsLocked": "Настройки модели станут доступны после завершения текущего ответа.", + "workingAnnouncement": "Агент работает. Настройки модели станут доступны после завершения текущего ответа." + }, + "sessionOptionRows": { + "defaultMarker": "По умолчанию", + "notReportedMarker": "Нет данных" + }, + "sessionOptionSegments": { + "dispatchUnconfirmed": "Отправлено агенту — получение не подтверждено" + }, + "stopButton": { + "stopAgent": "Остановить агента", + "stoppingState": "Останавливается…" + }, + "streamFrame": { + "streamEnded": "Поток сообщений завершён", + "streamFailed": "Ошибка потока сообщений" + }, + "taskList": { + "added": "Добавлена задача: {{task}}", + "completed": "Выполнено", + "empty": "Нет задач", + "finished": "Выполнено: {{task}}", + "inProgress": "В работе", + "pending": "Ожидает", + "progress": "Выполнено задач: {{completed}} из {{total}}", + "removed": "Удалена задача: {{task}}", + "reset": "Снова ожидает: {{task}}", + "showAll": "Все задачи", + "started": "В работе: {{task}}", + "taskStatus": "{{status}}: {{task}}", + "title": "Задачи", + "unchanged": "Задачи не изменились", + "updated": "Обновлена задача: {{task}}" + }, + "toolActivity": { + "count_few": "{{count}} вызова инструментов", + "count_many": "{{count}} вызовов инструментов", + "count_one": "{{count}} вызов инструмента", + "count_other": "{{count}} вызова инструмента", + "failedCallsLabel": "Вызовов с ошибками: {{value0}}", + "failedCount": "С ошибками: {{value0}}", + "interruptedCallsLabel": "Прерванных вызовов: {{value0}}", + "interruptedCount": "Прервано: {{value0}}", + "moreCalls": "Ещё {{value0}}", + "result": "Результат", + "runningCommand": "Выполняется команда", + "runningNamed": "Выполняется {{toolName}}", + "runningNamedPreview": "Выполняется {{toolName}} {{preview}}", + "runningPreview": "Выполняется {{preview}}" + }, + "toolRun": { + "list": "{{value0}} и {{value1}}", + "liveBrowserOnce": "Использует браузер", + "liveBrowserTimes_few": "Использует браузер {{count}} раза", + "liveBrowserTimes_many": "Использует браузер {{count}} раз", + "liveBrowserTimes_one": "Использует браузер {{count}} раз", + "liveBrowserTimes_other": "Использует браузер {{count}} раза", + "liveFileChange_few": "Редактирует {{count}} файла", + "liveFileChange_many": "Редактирует {{count}} файлов", + "liveFileChange_one": "Редактирует {{count}} файл", + "liveFileChange_other": "Редактирует {{count}} файла", + "liveListFiles_few": "Просматривает содержимое {{count}} папок", + "liveListFiles_many": "Просматривает содержимое {{count}} папок", + "liveListFiles_one": "Просматривает содержимое {{count}} папки", + "liveListFiles_other": "Просматривает содержимое {{count}} папки", + "liveMcpToolCall_few": "Использует {{count}} интеграции", + "liveMcpToolCall_many": "Использует {{count}} интеграций", + "liveMcpToolCall_one": "Использует {{count}} интеграцию", + "liveMcpToolCall_other": "Использует {{count}} интеграции", + "liveOther_few": "Использует {{count}} инструмента", + "liveOther_many": "Использует {{count}} инструментов", + "liveOther_one": "Использует {{count}} инструмент", + "liveOther_other": "Использует {{count}} инструмента", + "liveRead_few": "Читает {{count}} файла", + "liveRead_many": "Читает {{count}} файлов", + "liveRead_one": "Читает {{count}} файл", + "liveRead_other": "Читает {{count}} файла", + "liveSearch_few": "Выполняет {{count}} поиска", + "liveSearch_many": "Выполняет {{count}} поисков", + "liveSearch_one": "Выполняет {{count}} поиск", + "liveSearch_other": "Выполняет {{count}} поиска", + "liveSubAgentActivity_few": "Запускает агентов: {{count}}", + "liveSubAgentActivity_many": "Запускает {{count}} агентов", + "liveSubAgentActivity_one": "Запускает {{count}} агента", + "liveSubAgentActivity_other": "Запускает {{count}} агента", + "liveTodoListOnce": "Обновляет план", + "liveTodoListTimes_few": "Обновляет план {{count}} раза", + "liveTodoListTimes_many": "Обновляет план {{count}} раз", + "liveTodoListTimes_one": "Обновляет план {{count}} раз", + "liveTodoListTimes_other": "Обновляет план {{count}} раза", + "liveUnknown_few": "Выполняет {{count}} команды", + "liveUnknown_many": "Выполняет {{count}} команд", + "liveUnknown_one": "Выполняет {{count}} команду", + "liveUnknown_other": "Выполняет {{count}} команды", + "liveWebSearch_few": "Выполняет {{count}} поиска в интернете", + "liveWebSearch_many": "Выполняет {{count}} поисков в интернете", + "liveWebSearch_one": "Выполняет {{count}} поиск в интернете", + "liveWebSearch_other": "Выполняет {{count}} поиска в интернете", + "pair": "{{value0}} и {{value1}}", + "separator": ", ", + "settledBrowserOnce": "Использовал браузер", + "settledBrowserTimes_few": "Браузер использован {{count}} раза", + "settledBrowserTimes_many": "Браузер использован {{count}} раз", + "settledBrowserTimes_one": "Браузер использован {{count}} раз", + "settledBrowserTimes_other": "Браузер использован {{count}} раза", + "settledFileChange_few": "Изменено {{count}} файла", + "settledFileChange_many": "Изменено {{count}} файлов", + "settledFileChange_one": "Изменён {{count}} файл", + "settledFileChange_other": "Изменено {{count}} файла", + "settledListFiles_few": "Просмотрено содержимое {{count}} папок", + "settledListFiles_many": "Просмотрено содержимое {{count}} папок", + "settledListFiles_one": "Просмотрено содержимое {{count}} папки", + "settledListFiles_other": "Просмотрено содержимое {{count}} папки", + "settledMcpToolCall_few": "Использовано {{count}} интеграции", + "settledMcpToolCall_many": "Использовано {{count}} интеграций", + "settledMcpToolCall_one": "Использована {{count}} интеграция", + "settledMcpToolCall_other": "Использовано {{count}} интеграции", + "settledOther_few": "Использовано {{count}} инструмента", + "settledOther_many": "Использовано {{count}} инструментов", + "settledOther_one": "Использован {{count}} инструмент", + "settledOther_other": "Использовано {{count}} инструмента", + "settledRead_few": "Прочитано {{count}} файла", + "settledRead_many": "Прочитано {{count}} файлов", + "settledRead_one": "Прочитан {{count}} файл", + "settledRead_other": "Прочитано {{count}} файла", + "settledSearch_few": "Выполнено {{count}} поиска", + "settledSearch_many": "Выполнено {{count}} поисков", + "settledSearch_one": "Выполнен {{count}} поиск", + "settledSearch_other": "Выполнено {{count}} поиска", + "settledSubAgentActivity_few": "Запущено {{count}} агента", + "settledSubAgentActivity_many": "Запущено {{count}} агентов", + "settledSubAgentActivity_one": "Запущен {{count}} агент", + "settledSubAgentActivity_other": "Запущено {{count}} агента", + "settledTodoListOnce": "План обновлён", + "settledTodoListTimes_few": "План обновлён {{count}} раза", + "settledTodoListTimes_many": "План обновлён {{count}} раз", + "settledTodoListTimes_one": "План обновлён {{count}} раз", + "settledTodoListTimes_other": "План обновлён {{count}} раза", + "settledUnknown_few": "Выполнено {{count}} команды", + "settledUnknown_many": "Выполнено {{count}} команд", + "settledUnknown_one": "Выполнена {{count}} команда", + "settledUnknown_other": "Выполнено {{count}} команды", + "settledWebSearch_few": "Выполнено {{count}} поиска в интернете", + "settledWebSearch_many": "Выполнено {{count}} поисков в интернете", + "settledWebSearch_one": "Выполнен {{count}} поиск в интернете", + "settledWebSearch_other": "Выполнено {{count}} поиска в интернете" + }, + "turnStatus": { + "failedAfter": "Ошибка через {{value0}}", + "hoursMinutesSeconds": "{{hours}} ч {{minutes}} мин {{seconds}} с", + "interruptedAfter": "Прервано через {{value0}}", + "minutesSeconds": "{{minutes}} мин {{seconds}} с", + "queueAfterStop": "Добавить сообщение в очередь для обработки после остановки", + "responding": "Агент отвечает", + "seconds_few": "{{count}} с", + "seconds_many": "{{count}} с", + "seconds_one": "{{count}} с", + "seconds_other": "{{count}} с", + "sendAfterStop": "Отправить сообщение для обработки после остановки", + "stopping": "Останавливается…", + "thinking": "Думает", + "toggleDetails": "Показать или скрыть подробности ответа", + "workedFor": "Работал {{value0}}", + "working": "Работает…", + "workingFor": "Работает {{value0}}" + }, + "usageLimit": { + "reached": "{{agent}} достиг лимита использования.", + "reachedNamed": "{{agent}} достиг лимита: {{window}}.", + "resetsAt": "Сброс: {{when}}.", + "theAgent": "Агент", + "windowCredits": "дополнительное использование", + "windowSession": "на сессию", + "windowWeekly": "на неделю", + "windowWeeklyModel": "на неделю для модели" + }, + "useImageUpload": { + "attachFailed": "Не удалось прикрепить", + "disconnected": "Не удалось прикрепить (нет соединения)", + "imageTooLarge": "Изображение слишком большое для прикрепления", + "permissionDenied": "Нет доступа к фото" + } + }, "common": { "back": "Назад", "cancel": "Отмена", @@ -159,6 +568,8 @@ "done": "Готово", "edit": "Редактировать", "ok": "ОК", + "open": "Открыть", + "refresh": "Обновить", "remove": "Удалить", "retry": "Повторить", "save": "Сохранить", @@ -781,8 +1192,51 @@ "unexpectedResponse": "Неожиданный ответ" }, "webShellUpdateFailureCopy": { + "blockedReason": "Заблокировано: {{reason}}.", + "blockedWorkspace": "Рабочее пространство заблокировано.", + "failed": "Показан экран ошибки.", "failedAgo": "Последнее обновление с сервера {{hostName}} завершилось с ошибкой {{time}} назад: {{reason}}{{offered}}. {{outcome}}", - "failedJustNow": "Последнее обновление с сервера {{hostName}} только что завершилось с ошибкой: {{reason}}{{offered}}. {{outcome}}" + "failedJustNow": "Последнее обновление с сервера {{hostName}} только что завершилось с ошибкой: {{reason}}{{offered}}. {{outcome}}", + "hostCodeCopy": { + "assetChanged": "ресурс на сервере изменился", + "assetUnknown": "сервер не распознал ресурс", + "buildChanged": "сборка на сервере изменилась во время скачивания", + "offsetInvalid": "сервер отклонил чтение по указанному смещению", + "readLimited": "сервер ограничил число одновременных операций чтения", + "unavailable": "на сервере нет пакета рабочего пространства" + }, + "nativeRoute": "Показан нативный экран.", + "offeredGeneration": " (поколение {{generation}})", + "openedCached": "Выполнен переход к сохранённой версии.", + "openedCachedGeneration": "Выполнен переход к сохранённой версии (поколение {{generation}}).", + "reasonCopy": { + "assetChecksumMismatch": "контрольная сумма ресурса не совпала", + "assetEntryChanged": "ресурс перестал соответствовать манифесту", + "assetNoProgress": "при чтении ресурса не поступили новые данные", + "assetOverlong": "размер ресурса превысил указанный в манифесте", + "assetShort": "ресурс оказался меньше заявленного размера", + "buildChangedMidFetch": "сборка на сервере изменилась во время скачивания", + "cacheWriteFailed": "не удалось сохранить загруженные данные на этом телефоне", + "chunkMisrouted": "получен фрагмент для другого ресурса или смещения", + "chunkOversize": "размер фрагмента превысил допустимый на сервере", + "connectionLost": "соединение разорвано", + "fetchStopped": "скачивание остановлено", + "hostRefused": "сервер отклонил запрос на чтение", + "noConnection": "нет подключения к серверу", + "rangeUndecodable": "не удалось декодировать сжатые данные", + "replyUnreadable": "сервер прислал ответ, который приложение не смогло прочитать", + "unrecognisedError": "неизвестная ошибка" + }, + "routeUnavailable": "Показано сообщение о недоступности экрана.", + "waiting": "Приложение ожидало подключения к серверу.", + "wallCopy": { + "bundleShellTooOld": "для сохранённого пакета требуется более новая версия приложения", + "bundleTooOldForHost": "для этой версии сервера требуется более новый пакет", + "bundleUnavailable": "на сервере нет пакета рабочего пространства", + "hostTooOldForBundle": "для сохранённого пакета требуется более новая версия сервера", + "pageRouteGrantMissing": "для этого экрана требуется более новая версия приложения", + "pageRouteUnlisted": "в рабочем пространстве на сервере нет этого экрана" + } } }, "dictation": { @@ -799,6 +1253,95 @@ "updateFailed": "Не удалось обновить настройки диктовки" } }, + "files": { + "fileExplorerPanel": { + "backToSession": "Вернуться к сессии", + "closeFiles": "Закрыть файлы", + "connecting": "Подключение к компьютеру…", + "emptyState": "Файлы не найдены", + "loadFailed": "Не удалось загрузить файлы", + "loadInterrupted": "Загрузка папки прервана", + "title": "Файлы", + "truncatedList": "{{worktreeLabel}} — показаны первые 5000", + "waiting": "Ожидание подключения к компьютеру…" + }, + "fileExplorerRow": { + "fileUnavailable": "Файл {{name}} недоступен на мобильном устройстве", + "loadFolderFailed": "Не удалось загрузить папку", + "loading": "Загрузка…", + "openFolder": "Открыть папку {{name}}", + "previewFile": "Открыть предпросмотр файла {{name}}", + "retryLoading": "Повторить загрузку {{path}}", + "unavailable": "Недоступно на мобильном устройстве" + }, + "fileMarkdownPreview": { + "copyLoadedSource": "Скопировать загруженный исходный код Markdown", + "copySource": "Скопировать исходный код Markdown", + "viewPreview": "Показать предпросмотр Markdown", + "viewSource": "Показать исходный код Markdown" + }, + "fileMediaPreview": { + "downloading": "Скачивание: {{bytes}} из {{total}}", + "loadFailed": "Не удалось загрузить медиафайл", + "loading": "Загрузка медиафайла…", + "waiting": "Ожидание подключения к компьютеру…" + }, + "fileMutationOwnership": { + "sshOwnerChanged": "Не удалось проверить SSH-соединение. Подключитесь к серверу заново и повторите попытку." + }, + "filePreviewBody": { + "emptyFile": "Пустой файл", + "imageLabel": "Изображение «{{title}}»" + }, + "filePreviewEditableSource": { + "editorLabel": "Редактор «{{title}}»" + }, + "filePreviewResponse": { + "binaryUnavailable": "Предпросмотр двоичных файлов недоступен", + "bytes": "{{size}} Б", + "fileChanged": "Файл изменён на компьютере. Обновите предпросмотр перед сохранением", + "fileNotFound": "Файл не найден", + "fileTooLarge": "Файл слишком большой для предпросмотра на мобильном устройстве", + "filesystemUnreachable": "Не удалось получить доступ к файловой системе компьютера", + "kilobytes": "{{size}} КБ", + "loadFailed": "Не удалось загрузить предпросмотр", + "megabytes": "{{size}} МБ", + "reloadBeforeSaving": "Обновите предпросмотр перед сохранением", + "unknownSize": "размер неизвестен" + }, + "filePreviewScreen": { + "backToFiles": "Вернуться к файлам", + "chooseWorkspaceFile": "Выберите файл в этом рабочем пространстве.", + "discard": "Отменить изменения", + "discardMessage": "Несохранённые изменения будут потеряны.", + "discardTitle": "Отменить изменения?", + "fileTitle": "Файл", + "loadFailed": "Не удалось загрузить предпросмотр", + "loading": "Загрузка предпросмотра…", + "openFailed": "Не удалось открыть файл", + "saveArtifact": "Сохранить артефакт терминала", + "saveFailed": "Не удалось сохранить файл", + "stay": "Остаться", + "title": "Предпросмотр", + "waiting": "Ожидание подключения к компьютеру…" + }, + "filePreviewSourceText": { + "previewLabel": "Предпросмотр файла", + "truncatedNote": "Показана часть файла. Размер файла: {{size}}." + }, + "markdownPreviewImage": { + "expandFallback": "Развернуть изображение", + "expandImage": "Развернуть изображение: {{alt}}", + "imageLabel": "Изображение в Markdown", + "loadFailed": "Не удалось загрузить изображение" + }, + "pdfPreview": { + "open": "Открыть PDF", + "openFailed": "Не удалось открыть этот PDF", + "opening": "Открытие PDF…", + "updateRequired": "Обновите Orca Mobile, чтобы открывать PDF" + } + }, "home": { "accountUsageCards": { "sectionHeading": "Использование учётной записи", @@ -1141,14 +1684,10 @@ "noComments": "Комментариев пока нет.", "noDescription": "Описание не добавлено.", "noHumanComments": "Комментариев людей нет.", - "resolvedComment_few": "Закрытый комментарий от {{author}} ({{count}})", - "resolvedComment_many": "Закрытый комментарий от {{author}} ({{count}})", - "resolvedComment_one": "Закрытый комментарий от {{author}}", - "resolvedComment_other": "Закрытый комментарий от {{author}} ({{count}})", - "resolvedThread_few": "Закрытое обсуждение от {{author}} ({{count}})", - "resolvedThread_many": "Закрытое обсуждение от {{author}} ({{count}})", - "resolvedThread_one": "Закрытое обсуждение от {{author}}", - "resolvedThread_other": "Закрытое обсуждение от {{author}} ({{count}})", + "resolvedCommentCount": "Закрытый комментарий от {{author}} ({{count}})", + "resolvedCommentSingle": "Закрытый комментарий от {{author}}", + "resolvedThreadCount": "Закрытое обсуждение от {{author}} ({{count}})", + "resolvedThreadSingle": "Закрытое обсуждение от {{author}}", "showMore": "Показать ещё {{count}}", "showMoreOf": "Показать ещё {{count}} из {{remaining}}", "title": "Комментарии" @@ -1354,6 +1893,620 @@ "title": "Рецензенты" } }, + "session": { + "accessoryKeys": { + "addCustomShortcut": "Добавить пользовательское сочетание клавиш", + "dismissKeyboard": "Скрыть клавиатуру", + "dismissKeyboardHint": "Скрывает экранную клавиатуру и оставляет текущую сессию терминала открытой.", + "paste": "Вставить", + "pasteFromClipboard": "Вставить из буфера обмена", + "sendKey": "Отправить {{label}}", + "switchToBufferedInput": "Переключиться на буферизованный ввод команд", + "switchToDesktopMode": "Переключиться в режим компьютера", + "switchToLiveInput": "Переключиться на прямой ввод в терминал", + "switchToPhoneMode": "Переключить в режим телефона" + }, + "activeContent": { + "createTab": "Создать вкладку", + "creatingTab": "Создание…", + "fileTitle": "Файл", + "loadingTerminal": "Загрузка терминала", + "noTabs": "В этой сессии нет вкладок", + "retryLoadingTerminal": "Повторить загрузку терминала", + "terminalTakingLonger": "Загрузка терминала занимает больше времени, чем ожидалось" + }, + "agentChildRow": { + "agent": "Агент", + "days_few": "{{count}} д", + "days_many": "{{count}} д", + "days_one": "{{count}} д", + "days_other": "{{count}} д", + "ended": "Завершён", + "hours_few": "{{count}} ч", + "hours_many": "{{count}} ч", + "hours_one": "{{count}} ч", + "hours_other": "{{count}} ч", + "minutes_few": "{{count}} мин", + "minutes_many": "{{count}} мин", + "minutes_one": "{{count}} мин", + "minutes_other": "{{count}} мин", + "noRecentUpdate": "Давно не было обновлений", + "noUpdate": "Нет обновлений уже {{value0}}" + }, + "agentMessageAttribution": { + "from": "От {{sender}}", + "fromMore": "От {{sender}} +{{count}}", + "fromUnnamed": "От агента", + "messageFrom": "Сообщение от {{sender}}", + "messageFromMore": "Сообщение от {{sender}} +{{count}}", + "messageFromUnnamed": "Сообщение от агента", + "unnamedSender": "агента" + }, + "aiVaultResumeLaunch": { + "createFailed": "Не удалось создать терминал", + "inputLocked": "Ввод в терминал заблокирован", + "sendFailed": "Не удалось отправить команду возобновления" + }, + "aiVaultResumePreparation": { + "prepareFailed": "Не удалось подготовить эту сессию Codex старого формата. Попробуйте возобновить её ещё раз." + }, + "backgroundTask": { + "agent": "Фоновый агент", + "command": "Фоновая команда", + "countAgents_few": "{{count}} агента", + "countAgents_many": "{{count}} агентов", + "countAgents_one": "{{count}} агент", + "countAgents_other": "{{count}} агента", + "countMonitors_few": "{{count}} наблюдения", + "countMonitors_many": "{{count}} наблюдений", + "countMonitors_one": "{{count}} наблюдение", + "countMonitors_other": "{{count}} наблюдения", + "countShellCommandOne": "1 команда оболочки", + "countShell_few": "{{count}} оболочки", + "countShell_many": "{{count}} оболочек", + "countShell_one": "{{count}} оболочка", + "countShell_other": "{{count}} оболочки", + "countTasks_few": "{{count}} задачи", + "countTasks_many": "{{count}} задач", + "countTasks_one": "{{count}} задача", + "countTasks_other": "{{count}} задачи", + "countWorkflows_few": "{{count}} процесса", + "countWorkflows_many": "{{count}} процессов", + "countWorkflows_one": "{{count}} процесс", + "countWorkflows_other": "{{count}} процесса", + "detailsUnavailable": "Подробности задач в этой сессии недоступны.", + "groupAgents": "Агенты", + "groupMonitors": "Наблюдения", + "groupShell": "Оболочка", + "groupTasks": "Задачи", + "groupWorkflows": "Процессы", + "headerTotalCount_few": "{{count}} фоновые задачи", + "headerTotalCount_many": "{{count}} фоновых задач", + "headerTotalCount_one": "{{count}} фоновая задача", + "headerTotalCount_other": "{{count}} фоновой задачи", + "monitor": "Фоновое наблюдение", + "monitoring": "Наблюдает за фоновыми задачами", + "reasonBlocked": "ошибка", + "reasonWaiting": "нужно разрешение", + "stateBlocked": "заблокировано", + "stateBlockedCount": "Заблокировано: {{value0}}", + "stateDone": "готово", + "stateDoneCount": "Готово: {{value0}}", + "stateIdle": "остановлено", + "stateIdleCount": "Остановлено: {{value0}}", + "stateMonitoring": "наблюдает", + "stateMonitoringCount": "Наблюдают: {{value0}}", + "stateUnverifiable": "статус недоступен", + "stateUnverifiableCount": "Статус недоступен: {{value0}}", + "stateWaiting": "ожидает", + "stateWaitingCount": "Ожидают: {{value0}}", + "stateWorking": "в работе", + "stateWorkingCount": "В работе: {{value0}}", + "stop": "Остановить", + "stopAll": "Остановить фоновые задачи", + "stopTask": "Остановить {{value0}}", + "task": "Фоновая задача", + "workflow": "Фоновый процесс" + }, + "browserTabActionSheet": { + "forward": "Вперёд", + "reload": "Перезагрузить", + "title": "Браузер" + }, + "bulkCloseSheetActions": { + "closeOtherTabs": "Закрыть другие вкладки" + }, + "bulkTabClose": { + "confirm": "Закрыть вкладки", + "description": "Выбранные вкладки закроются. Закреплённые вкладки останутся открытыми.", + "titleEditors": "Закрыть все вкладки редактора?", + "titleLeft": "Закрыть вкладки слева?", + "titleOthers": "Закрыть остальные вкладки?", + "titleRight": "Закрыть вкладки справа?" + }, + "commandDock": { + "commandPlaceholder": "Введите команду…", + "liveInputHint": "Вводимый текст отправляется прямо в активный терминал", + "liveInputLabel": "Прямой ввод в терминал", + "sendCommand": "Отправить команду" + }, + "contentRow": { + "dismissCreationWarning": "Закрыть предупреждение о создании рабочего пространства" + }, + "diffCommentLineRow": { + "addNote": "Добавить заметку к строке {{line}}", + "deleteNote": "Удалить заметку к строке {{line}}", + "diffLine": "{{title}}: строка {{line}} в сравнении", + "line": "Строка {{line}}", + "placeholder": "Добавить заметку к проверке", + "saveNote": "Сохранить заметку" + }, + "diffLineRow": { + "addNoteLabel": "Добавить заметку к строке {{line}}", + "deleteNoteLabel": "Удалить заметку к строке {{line}}", + "diffLineLabel": "Сравнение {{title}}, строка {{line}}", + "lineNumber": "Строка {{line}}", + "notePlaceholder": "Добавить заметку по проверке", + "saveNote": "Сохранить заметку" + }, + "diffReviewLoaders": { + "changesLoadFailed": "Не удалось загрузить изменения", + "committedChangesFailed": "Не удалось загрузить изменения в коммитах", + "committedChangesUnavailable": "Изменения в коммитах недоступны", + "committedDiffUnavailable": "Сравнение изменений в коммитах недоступно", + "desktopUpdateRequired": "Обновите Orca на компьютере, чтобы проверять изменения на мобильном устройстве.", + "diffLoadFailed": "Не удалось загрузить сравнение", + "invalidSourceControlResponse": "Некорректный ответ системы управления версиями", + "reviewNotesLoadFailed": "Не удалось загрузить заметки к проверке" + }, + "diffReviewQueue": { + "committedOnBranch": "В коммитах ветки", + "staged": "В индексе", + "unstaged": "Не в индексе" + }, + "diffReviewScreenModel": { + "branch": "Ветка", + "staged": "В индексе", + "unstaged": "Не в индексе" + }, + "fileReader": { + "copyReviewNotes": "Копировать заметки по проверке", + "imageLabel": "Изображение {{title}}", + "noReviewNotes": "Нет заметок по проверке", + "previewLabel": "Предпросмотр {{title}}", + "reviewNotes_few": "{{count}} заметки по проверке", + "reviewNotes_many": "{{count}} заметок по проверке", + "reviewNotes_one": "{{count}} заметка по проверке", + "reviewNotes_other": "{{count}} заметки по проверке", + "send": "Отправить", + "sendReviewNotes": "Отправить ИИ заметки по проверке" + }, + "floatingWorkspace": { + "title": "Плавающее рабочее пространство" + }, + "githubPrMutations": { + "reviewThreadUpdateFailed": "Не удалось обновить обсуждение в пул-реквесте.", + "titleUpdateFailed": "Не удалось обновить заголовок." + }, + "githubPrRpc": { + "requestFailed": "Ошибка запроса: {{method}}" + }, + "header": { + "backToWorktrees": "Вернуться к рабочим деревьям", + "moreActions": "Другие действия с сессией", + "openFileExplorer": "Открыть файловый проводник", + "openSourceControl": "Открыть управление версиями", + "reconnectToDesktop": "Переподключиться к компьютеру", + "showSidebar": "Показать боковую панель", + "terminalTitle": "Терминал" + }, + "headerMoreActionsSheet": { + "agentHistory": "История агентов", + "agentHistoryHint": "Просмотр и возобновление сессий агентов", + "checks": "Проверки", + "checksHint": "Открыть проверки пул-реквеста" + }, + "markdownReader": { + "changedOnDesktop": "Изменён на компьютере", + "copyLoaded": "Скопировать загруженное", + "copyLoadedSource": "Скопировать загруженный исходный текст Markdown", + "copyPath": "Скопировать путь", + "copyPathLabel": "Скопировать путь к файлу Markdown", + "copySource": "Скопировать исходный текст Markdown", + "discard": "Отменить изменения", + "dismissKeyboard": "Скрыть клавиатуру", + "dismissKeyboardHint": "Скрывает экранную клавиатуру и оставляет редактор Markdown открытым.", + "readOnly": "Только чтение" + }, + "nativeChatAttachActions": { + "availableSkills": "Доступно: {{count}}", + "commands": "Команды", + "fileTypes": "PDF, текст, код…", + "files": "Файлы", + "photos": "Фото", + "skills": "Навыки", + "workspaceFile": "Файл рабочего пространства" + }, + "nativeChatCardHeaderAction": { + "collapse": "Свернуть" + }, + "nativeChatCliFork": { + "resumeInNewCli": "Продолжить в новом CLI" + }, + "nativeChatFloatingControls": { + "scrollToLatest": "Прокрутить к последним сообщениям" + }, + "nativeChatFullContent": { + "loadMessageFailed": "Показать сообщение целиком — не удалось загрузить, нажмите для повтора", + "loadOutputFailed": "Показать весь вывод — не удалось загрузить, нажмите для повтора", + "partialMessage": "Показана часть сообщения.", + "partialOutput": "Показано {{shown}} из {{total}}.", + "showFullMessage": "Показать сообщение целиком", + "showFullOutput": "Показать весь вывод" + }, + "nativeChatRenderData": { + "defaultAgentLabel": "агент", + "emptySubtitle": "Отправьте запрос: {{agent}} может изучить код, объяснить вывод или внести изменения.", + "emptyTitle": "Начните чат ({{agent}})", + "errorSubtitle": "Не удалось прочитать историю. Вернитесь в терминал, чтобы продолжить работу.", + "errorTitle": "Не удалось загрузить разговор", + "notFoundSubtitle": "Отправьте сообщение, чтобы начать разговор, или откройте терминал и посмотрите, что делает {{agent}}.", + "notFoundTitle": "Пока не удаётся найти этот разговор" + }, + "nativeChatToggleAction": { + "switchToChatView": "Переключиться на чат", + "switchToTerminalView": "Переключиться на терминал" + }, + "newTabAgentHostLaunch": { + "notesNotSent": "Агент запущен, но заметки не отправлены.", + "notesSent": "Заметки отправлены", + "notesUnconfirmed": "Агент запущен, но подтвердить отправку заметок не удалось.", + "promptUnconfirmed": "Агент запущен, но подтвердить отправку запроса не удалось." + }, + "prActionsEngine": { + "mergeFailed": "Не удалось слить пул-реквест.", + "refreshFailed": "Не удалось обновить пул-реквест." + }, + "prAiTriageLaunch": { + "agentStarted": "Агент запущен", + "agentStartedIn": "Агент запущен в «{{label}}»", + "emptyPrompt": "Сохранённый запрос для этого действия пуст. Измените настройки ИИ для управления версиями на компьютере.", + "loadFailed": "Не удалось загрузить список доступных агентов.", + "promptNotSent": "Агент запущен, но запрос не отправлен.", + "updateRequired": "Обновите Orca на компьютере, чтобы запускать агента с телефона." + }, + "prSidebarState": { + "loadFailed": "Не удалось загрузить пул-реквест" + }, + "queuedCardText": { + "showLess": "Свернуть", + "showLessHint": "Сворачивает сообщение", + "showMore": "Показать больше", + "showMoreHint": "Показывает сообщение целиком" + }, + "queuedMessageCardRow": { + "deleteHint": "Удалить это сообщение из очереди", + "images_few": "{{count}} изображения", + "images_many": "{{count}} изображений", + "images_one": "{{count}} изображение", + "images_other": "{{count}} изображения", + "moreActions": "Другие действия", + "send": "Отправить", + "sendAgainHint": "Отправить это сообщение ещё раз", + "sendHint": "Отправить это сообщение", + "showLessHint": "Сворачивает сообщение", + "steer": "Сейчас", + "steerHint": "Отправить сообщение, не прерывая работу модели" + }, + "queuedMessageComposer": { + "commandsNotAllowed": "Команды нельзя сохранять в сообщении в очереди.", + "editing": "Редактирование сообщения", + "editingAttributed": "Редактирование сообщения · {{attribution}}" + }, + "quickCommandEditorForm": { + "action": "Действие", + "addQuickCommand": "Добавить быструю команду", + "advanced": "Дополнительно", + "agent": "Агент", + "agentPrompt": "Запрос агенту", + "appendEnter": "Добавить Enter", + "appendEnterDescription": "Отправлять сразу вместо простой вставки текста.", + "chooseAgent": "Выбрать агента", + "commandText": "Текст команды", + "globalScope": "Для всех проектов", + "label": "Название", + "labelPlaceholder": "Запустить сервер разработки", + "olderHostPromptTooLong": "Текст слишком длинный для этого сервера. Обновите на нём Orca, чтобы сохранять более длинные запросы.", + "projectScope": "Проект", + "prompt": "Запрос", + "promptHint": "Поддерживаются навыки, пути к файлам и встроенные команды.", + "promptPlaceholder": "Попросить агента исследовать это рабочее пространство", + "promptSizeCharacters": "{{used}} / {{max}} символов", + "promptSizeKilobytes": "{{used}} КБ / {{max}} КБ", + "promptTooLong": "Текст слишком длинный для сохранения.", + "scope": "Область", + "terminalCommand": "Команда терминала" + }, + "quickCommandLaunch": { + "defaultLabel": "Быстрая команда", + "editBeforeRunning": "Измените эту быструю команду перед выполнением", + "runFailed": "Не удалось выполнить «{{label}}»" + }, + "quickCommandRow": { + "copyCommand": "Скопировать «{{label}}»", + "copyFailed": "Не удалось скопировать", + "deleteCommand": "Удалить «{{label}}»", + "editCommand": "Изменить «{{label}}»", + "nothingToCopy": "Копировать нечего", + "runCommand": "Запустить «{{label}}»" + }, + "quickCommandsList": { + "emptyState": "Быстрых команд пока нет.", + "globalGroup": "Для всех проектов", + "newCommand": "Создать быструю команду", + "noMatches": "Подходящих быстрых команд нет.", + "projectGroup": "Этот проект", + "searchPlaceholder": "Поиск быстрых команд…" + }, + "quickCommandsSheet": { + "addTitle": "Добавить быструю команду", + "chooseAgentTitle": "Выбрать агента", + "deleteConfirmMessage": "Эта быстрая команда будет удалена из сохранённого списка.", + "deleteConfirmTitle": "Удалить «{{label}}»?", + "editTitle": "Изменить быструю команду", + "editorDescription": "Сохраняйте команды терминала или запросы агентам для быстрого доступа.", + "title": "Быстрые команды", + "untitled": "Без названия" + }, + "quickCommandsTabButton": { + "quickCommands": "Быстрые команды" + }, + "routeHelpers": { + "connected": "Подключён", + "connecting": "Подключение", + "disconnected": "Отключён", + "pairingInvalid": "Сопряжение недействительно", + "reconnecting": "Повторное подключение", + "securing": "Защита соединения" + }, + "routeScreen": { + "openingWorkspace": "Открытие рабочего пространства" + }, + "sheets": { + "browser": "Браузер", + "browserUpdateRequired": "Для трансляции браузера на телефон обновите Orca на компьютере", + "chat": "Чат", + "copyAllAndLeave": "Скопировать всё и выйти", + "copyDraftsFailed": "Не удалось скопировать черновики", + "copyNotes": "Копировать заметки", + "copyNotesFailed": "Не удалось скопировать заметки", + "copyPath": "Копировать путь", + "discard": "Отменить изменения", + "discardAndLeave": "Удалить черновики и выйти", + "discardChanges": "Отменить изменения", + "discardMessage": "Заменить черновик на телефоне последней версией файла с компьютера?", + "draftsHint": "Перед выходом скопируйте или удалите черновики на телефоне.", + "file": "Файл", + "markdown": "Markdown", + "markdownNote": "Заметка Markdown", + "newBrowser": "Новый браузер", + "newTab": "Новая вкладка", + "notesCopied": "Заметки скопированы", + "notesHint": "Выберите сессию агента для текущих заметок.", + "removeShortcutMessage": "Удалить это пользовательское сочетание клавиш?", + "renameTerminal": "Переименовать терминал", + "sendReviewNotes": "Отправить заметки по проверке", + "shortcut": "Сочетание клавиш", + "terminal": "Терминал", + "terminalName": "Название терминала", + "unsavedMarkdown": "Несохранённые изменения в Markdown", + "urlPrompt": "Введите URL или оставьте поле пустым, чтобы открыть новую вкладку." + }, + "sideRail": { + "backToWorktrees": "Вернуться к рабочим деревьям" + }, + "sourceControlLaunchAgent": { + "noEnabledAgent": "На сервере этого рабочего пространства не найдено включённых ИИ-агентов.", + "savedAgentUnavailable": "Агент, выбранный для этого действия, недоступен на сервере этого рабочего пространства." + }, + "structuredComposerCommand": { + "operationUnconfirmed": "Не удалось подтвердить выполнение команды чата.", + "removeAttachments": "Уберите вложения перед выполнением команды сессии чата." + }, + "structuredSendDelivery": { + "messageNotSent": "Сообщение не отправлено" + }, + "tabStrip": { + "checkingCapabilities": "Проверяем возможности Orca на компьютере — повторите попытку чуть позже", + "newTab": "Новая вкладка", + "quickCommandsUpdateRequired": "Для быстрых команд обновите Orca на компьютере", + "tabStatus": "{{title}}, {{status}}" + }, + "terminalActionSheetActions": { + "clearTerminal": "Очистить терминал", + "rename": "Переименовать", + "switchToDesktop": "Включить режим компьютера", + "switchToPhone": "Включить режим телефона" + }, + "terminalInputActions": { + "attachFileHint": "Нажмите и удерживайте, чтобы прикрепить файл вместо фото", + "attachPhoto": "Прикрепить фото", + "sendingImage": "Отправка изображения", + "startDictation": "Начать диктовку", + "startingDictation": "Запуск диктовки", + "stopDictation": "Остановить диктовку", + "transcribingDictation": "Распознавание диктовки" + }, + "terminalLiveInputStatus": { + "listening": "Прослушивание", + "liveInput": "Ввод в реальном времени", + "preparingMic": "Подготовка микрофона", + "processing": "Обработка", + "releaseMicHint": "Отпустите кнопку микрофона, чтобы остановить запись", + "startingMic": "Включение микрофона", + "stopHint": "Нажмите на микрофон, чтобы остановить", + "transcribing": "Распознавание на компьютере", + "uploadingImage": "Загрузка изображения на сервер", + "uploadingImageTitle": "Загрузка изображения" + }, + "terminalRecords": { + "defaultTitle": "Терминал" + }, + "terminalTabAgent": { + "browser": "Браузер", + "chat": "Чат", + "file": "Файл", + "markdown": "Markdown", + "newBrowser": "Новый браузер", + "terminal": "Терминал" + }, + "terminalViewportResubscribe": { + "fitFailed": "Не удалось подогнать терминал под размер экрана" + }, + "useAccessorySelection": { + "copyFailed": "Не удалось скопировать", + "selectionCleared": "Выделение снято (текст вышел за пределы буфера)" + }, + "useAttachmentInputLeaseGate": { + "reconnecting": "Не удалось прикрепить (повторное подключение)" + }, + "useContentCreateActions": { + "browserCommandFailed": "Не удалось выполнить команду браузера", + "browserCreateFailed": "Не удалось создать вкладку браузера", + "browserUnavailable": "Страница браузера пока недоступна.", + "browserUpdateRequired": "Для трансляции браузера на телефон обновите Orca на сервере", + "invalidUrl": "Введите корректный URL", + "noteCreateFailed": "Не удалось создать заметку Markdown", + "untitledNoteCreateFailed": "Не удалось создать заметку Markdown без названия" + }, + "useDiffComments": { + "copyNotesFailed": "Не удалось скопировать заметки", + "deleteNoteFailed": "Не удалось удалить заметку", + "noteAdded": "Заметка добавлена", + "notesCopied": "Заметки скопированы", + "saveNoteFailed": "Не удалось сохранить заметку", + "saveNotesFailed": "Не удалось сохранить заметки по проверке", + "waitingForDesktop": "Ожидание подключения к компьютеру…" + }, + "useDiffReviewCommentActions": { + "saveFailed": "Не удалось сохранить проверку", + "saveStateFailed": "Не удалось сохранить состояние проверки", + "waitingForDesktop": "Ожидание подключения к компьютеру…" + }, + "useDiffReviewController": { + "loadFailed": "Не удалось загрузить проверку", + "missingWorktree": "Не указано рабочее дерево", + "waitingForDesktop": "Ожидание подключения к компьютеру…" + }, + "useDiffReviewDiffLoading": { + "loadFailed": "Не удалось загрузить изменения", + "waitingForDesktop": "Ожидание подключения к компьютеру…" + }, + "useDiffReviewGitActions": { + "actionFailed": "Не удалось выполнить действие управления версиями", + "reviewedFilesStaged_few": "{{count}} проверенных файла добавлены в индекс", + "reviewedFilesStaged_many": "{{count}} проверенных файлов добавлено в индекс", + "reviewedFilesStaged_one": "{{count}} проверенный файл добавлен в индекс", + "reviewedFilesStaged_other": "{{count}} проверенного файла добавлено в индекс", + "stageResults": "Добавлено в индекс: {{staged}}, не удалось: {{failed}}", + "waitingForDesktop": "Ожидание подключения к компьютеру…" + }, + "useDiffReviewInteractions": { + "openFailed": "Не удалось открыть в сессии" + }, + "useDiffReviewSendActions": { + "copyFailed": "Не удалось скопировать заметки к проверке", + "inputLocked": "Ввод в терминале заблокирован", + "loadSessionsFailed": "Не удалось загрузить сессии агентов", + "notesCopied": "Заметки к проверке скопированы", + "notesSent": "Заметки к проверке отправлены", + "notesSentWithWarning": "Заметки к проверке отправлены. {{warning}}", + "promptNotSent": "Агент запущен, но заметки не отправлены. Нажмите «Скопировать заметки», чтобы вставить их.", + "sendFailed": "Не удалось отправить заметки", + "startingAgent": "Запуск агента…", + "waitingForDesktop": "Ожидание подключения к компьютеру…" + }, + "useImageAttachment": { + "attachFailed": "Не удалось прикрепить изображение", + "disconnected": "Не удалось прикрепить (нет подключения)", + "imageTooLarge": "Изображение слишком большое для прикрепления", + "permissionDenied": "Нет доступа к фото" + }, + "useMarkdownActions": { + "copyFailed": "Не удалось скопировать", + "copyPathFailed": "Не удалось скопировать путь", + "markdownTitle": "Markdown", + "pathCopied": "Путь скопирован", + "saveFailed": "Не удалось сохранить", + "saved": "Сохранено" + }, + "useNativeChatDictation": { + "insertFailed": "Не удалось вставить текст диктовки", + "insertFailedCopied": "Не удалось вставить текст диктовки — он скопирован в буфер обмена", + "inserted": "Текст диктовки вставлен" + }, + "useNativeChatStop": { + "stopNotSent": "Команда остановки не отправлена", + "stopUnconfirmed": "Остановка не подтверждена — проверьте чат перед повторной попыткой", + "terminalNotReady": "Команда остановки не отправлена (терминал не готов)" + }, + "usePanelRouteActions": { + "checkHostHint": "Проверьте подключение к серверу", + "copyNotesHint": "Вместо этого скопируйте заметки", + "detectingAgents": "Поиск агентов", + "newSessionHint": "Новая сессия агента", + "noEnabledAgents": "Нет включённых агентов", + "otherServerAgents": "Агенты на другом сервере", + "pairServerHint": "Подключите этот сервер напрямую", + "presetsUnavailable": "Предустановки агентов недоступны" + }, + "usePrAiTriage": { + "launchFailed": "Не удалось запустить агента", + "waitingForDesktop": "Ожидание подключения к компьютеру…" + }, + "usePrCommentActions": { + "actionFailed": "Не удалось выполнить действие с комментарием" + }, + "usePrTitleAction": { + "disconnected": "Нет подключения к серверу.", + "updateFailed": "Не удалось обновить заголовок." + }, + "useQuickCommands": { + "loadFailed": "Не удалось загрузить быстрые команды", + "saveFailed": "Не удалось сохранить быструю команду" + }, + "useSessionTerminalSendActions": { + "inputTooLarge": "Слишком большой объём ввода (макс. 256 КиБ)" + }, + "useStartup": { + "wakeAgents": "Откройте Orca на сервере, чтобы разбудить спящих агентов." + }, + "useTerminalCreateActions": { + "createFailed": "Не удалось создать терминал", + "forkBusy": "Открывается другой терминал. Подождите и повторите попытку открыть чат {{agentName}}.", + "forkUnavailable": "Откройте чат {{agentName}} на подключённом сервере с актуальной версией Orca и повторите попытку.", + "inputLocked": "Ввод в терминале заблокирован другим клиентом.", + "notesSent": "Заметки отправлены", + "sendNotesError": "Не удалось отправить заметки", + "sendNotesFailed": "Не удалось отправить заметки", + "terminalTitle": "Терминал" + }, + "useTerminalInput": { + "clearFailed": "Не удалось очистить терминал", + "cleared": "Терминал очищен" + }, + "useTerminalPaste": { + "clipboardReadFailed": "Не удалось прочитать буфер обмена", + "disconnected": "Не удалось вставить (нет подключения)", + "imageTooLarge": "Изображение слишком большое для вставки", + "pasteFailed": "Не удалось вставить", + "pasteTooLarge": "Слишком большой объём вставки (макс. 256 КиБ)", + "terminalChanged": "Вставка отменена (выбран другой терминал)" + }, + "worktreeLabel": { + "fallback": "Рабочее дерево" + } + }, "settings": { "aboutScreen": { "openLinkFailed": "Не удалось открыть ссылку. Повторите попытку.", @@ -1502,6 +2655,200 @@ "updateFailed": "Не удалось обновить" } }, + "sourceControl": { + "actions": { + "commits": "Коммиты", + "createPr": "Создать PR", + "pushAndCreatePr": "Отправить и создать PR", + "switchBranch": "Переключиться на ветку" + }, + "commitDropdown": { + "checkOutBranchToPush": "Переключитесь на ветку перед отправкой коммитов", + "checkOutBranchToSync": "Переключитесь на ветку перед синхронизацией коммитов", + "checkingBranchStatus": "Проверяем состояние ветки…", + "checkingPrStatus": "Проверяем состояние PR…", + "commit": "Закоммитить", + "commitAndForcePush": "Закоммитить изменения в индексе и принудительно отправить их с проверкой состояния удалённой ветки", + "commitAndPush": "Закоммитить изменения в индексе и отправить их", + "commitAndTryPush": "Закоммитить изменения в индексе и попробовать отправить их", + "commitForcePush": "Закоммитить и принудительно отправить", + "commitPush": "Закоммитить и отправить", + "commitStagedChanges": "Закоммитить изменения из индекса", + "commitSync": "Закоммитить и синхронизировать", + "commitThenPullPush": "Создать коммит, затем получить, слить и отправить изменения", + "enterMessage": "Введите сообщение коммита", + "linkedReviewTargetUnavailable": "Ветка связанной проверки недоступна для отправки коммитов", + "prAlreadyMerged": "PR уже слит", + "publishBranchFirstToPush": "Сначала опубликуйте ветку для отправки коммитов", + "publishBranchFirstToSync": "Сначала опубликуйте ветку для синхронизации коммитов", + "resolveConflicts": "Разрешите конфликты перед коммитом", + "stageFile": "Добавьте в индекс хотя бы один файл для коммита", + "useCommitAndForcePush": "Используйте «Закоммитить и принудительно отправить»: в удалённой ветке только старые версии локальных коммитов" + }, + "createPrAction": { + "createReview": "Создать {{titleLabel}}" + }, + "dropdownCounts": { + "fastForwardCount_few": "Продвинуть ветку на {{count}} коммита", + "fastForwardCount_many": "Продвинуть ветку на {{count}} коммитов", + "fastForwardCount_one": "Продвинуть ветку на {{count}} коммит", + "fastForwardCount_other": "Продвинуть ветку на {{count}} коммита", + "pullCount_few": "Получить и слить {{count}} коммита", + "pullCount_many": "Получить и слить {{count}} коммитов", + "pullCount_one": "Получить и слить {{count}} коммит", + "pullCount_other": "Получить и слить {{count}} коммита", + "pushCount_few": "Отправить {{count}} коммита", + "pushCount_many": "Отправить {{count}} коммитов", + "pushCount_one": "Отправить {{count}} коммит", + "pushCount_other": "Отправить {{count}} коммита", + "remoteBranchFallback": "удалённую ветку", + "syncCounts": "Получить и слить: {{behind}}, отправить: {{ahead}}", + "titleForcePushOlderCopiesBranch": "В удалённом репозитории есть только старые версии локальных коммитов. Принудительно отправьте эту ветку с проверкой состояния удалённой ветки, чтобы обновить {{upstreamName}}.", + "titleForcePushOlderCopiesCount_few": "В удалённом репозитории есть только старые версии локальных коммитов. Принудительно отправьте {{count}} коммита этой ветки с проверкой состояния удалённой ветки, чтобы обновить {{upstreamName}}.", + "titleForcePushOlderCopiesCount_many": "В удалённом репозитории есть только старые версии локальных коммитов. Принудительно отправьте {{count}} коммитов этой ветки с проверкой состояния удалённой ветки, чтобы обновить {{upstreamName}}.", + "titleForcePushOlderCopiesCount_one": "В удалённом репозитории есть только старые версии локальных коммитов. Принудительно отправьте {{count}} коммит этой ветки с проверкой состояния удалённой ветки, чтобы обновить {{upstreamName}}.", + "titleForcePushOlderCopiesCount_other": "В удалённом репозитории есть только старые версии локальных коммитов. Принудительно отправьте {{count}} коммита этой ветки с проверкой состояния удалённой ветки, чтобы обновить {{upstreamName}}.", + "titleManualForcePushReplaceRemote_few": "Принудительно отправьте {{count}} локальных коммита с проверкой состояния удалённой ветки, чтобы обновить {{upstreamName}} и заменить коммиты, которые есть только в удалённом репозитории.", + "titleManualForcePushReplaceRemote_many": "Принудительно отправьте {{count}} локальных коммитов с проверкой состояния удалённой ветки, чтобы обновить {{upstreamName}} и заменить коммиты, которые есть только в удалённом репозитории.", + "titleManualForcePushReplaceRemote_one": "Принудительно отправьте {{count}} локальный коммит с проверкой состояния удалённой ветки, чтобы обновить {{upstreamName}} и заменить коммиты, которые есть только в удалённом репозитории.", + "titleManualForcePushReplaceRemote_other": "Принудительно отправьте {{count}} локального коммита с проверкой состояния удалённой ветки, чтобы обновить {{upstreamName}} и заменить коммиты, которые есть только в удалённом репозитории.", + "titleManualForcePush_few": "Принудительно отправьте {{count}} локальных коммита с проверкой состояния удалённой ветки, чтобы обновить {{upstreamName}}.", + "titleManualForcePush_many": "Принудительно отправьте {{count}} локальных коммитов с проверкой состояния удалённой ветки, чтобы обновить {{upstreamName}}.", + "titleManualForcePush_one": "Принудительно отправьте {{count}} локальный коммит с проверкой состояния удалённой ветки, чтобы обновить {{upstreamName}}.", + "titleManualForcePush_other": "Принудительно отправьте {{count}} локального коммита с проверкой состояния удалённой ветки, чтобы обновить {{upstreamName}}.", + "titleUnpublishedForcePushBranch": "Принудительно отправьте эту ветку с проверкой состояния удалённой ветки и при необходимости настройте отслеживаемую удалённую ветку.", + "titleUnpublishedForcePushCount_few": "Принудительно отправьте {{count}} коммита этой ветки с проверкой состояния удалённой ветки и при необходимости настройте отслеживаемую удалённую ветку.", + "titleUnpublishedForcePushCount_many": "Принудительно отправьте {{count}} коммитов этой ветки с проверкой состояния удалённой ветки и при необходимости настройте отслеживаемую удалённую ветку.", + "titleUnpublishedForcePushCount_one": "Принудительно отправьте {{count}} коммит этой ветки с проверкой состояния удалённой ветки и при необходимости настройте отслеживаемую удалённую ветку.", + "titleUnpublishedForcePushCount_other": "Принудительно отправьте {{count}} коммита этой ветки с проверкой состояния удалённой ветки и при необходимости настройте отслеживаемую удалённую ветку." + }, + "hostedReview": { + "mrCopy": { + "reviewLabel": "запрос на слияние", + "shortLabel": "MR", + "titleLabel": "Запрос на слияние" + }, + "prCopy": { + "reviewLabel": "пул-реквест", + "shortLabel": "PR", + "titleLabel": "Пул-реквест" + } + }, + "hostedReviewCreate": { + "pushSucceeded": "Отправка прошла успешно, но не удалось создать {{shortLabel}}: {{error}}" + }, + "prCreate": { + "authRequired": "Прежде чем создать {{reviewLabel}}, пройдите аутентификацию.", + "baseNotOnRemote": "Прежде чем создать {{reviewLabel}}, отправьте базовую ветку.", + "createdNotRefreshed": "{{titleLabel}} создан, но Orca пока не удалось обновить его данные.", + "defaultBranch": "Прежде чем создать {{reviewLabel}}, переключитесь на ветку разработки.", + "detachedHead": "Прежде чем создать {{reviewLabel}}, переключитесь на ветку.", + "dirty": "Прежде чем создать {{reviewLabel}}, создайте коммит с изменениями.", + "existing": "{{titleLabel}} уже открыт.", + "existingNumber": "{{titleLabel}} #{{number}} уже открыт.", + "existingReview": "Для этой ветки уже существует {{reviewLabel}}.", + "forkHeadUnsupported": "Создать {{reviewLabel}} из этого форка нельзя.", + "lookupUnconfirmed": "Orca не удалось проверить, есть ли уже {{reviewLabel}} для этой ветки. Повторите попытку чуть позже.", + "needsSync": "Прежде чем создать {{reviewLabel}}, синхронизируйте эту ветку.", + "noUpstream": "Прежде чем создать {{reviewLabel}}, опубликуйте коммиты.", + "notReady": "Для этой ветки пока нельзя создать {{reviewLabel}}.", + "unsupportedMergeRequests": "Создание запросов на слияние для этого репозитория не поддерживается.", + "unsupportedPullRequests": "Создание пул-реквестов для этого репозитория не поддерживается." + }, + "primaryAction": { + "forcePushFromDesktop": "Принудительно отправьте ветку этого пул-реквеста из настольного приложения" + }, + "remoteDropdown": { + "branchAlreadyPublished": "Ветка уже опубликована", + "branchUpToDate": "Ветка актуальна", + "checkOutBranchToFastForward": "Переключитесь на ветку перед перемоткой вперёд", + "checkOutBranchToForcePush": "Переключитесь на ветку перед принудительной отправкой коммитов", + "checkOutBranchToPublish": "Переключитесь на ветку перед публикацией коммитов", + "checkOutBranchToPull": "Переключитесь на ветку перед получением и слиянием коммитов", + "checkOutBranchToPush": "Переключитесь на ветку перед отправкой коммитов", + "checkOutBranchToSync": "Переключитесь на ветку перед синхронизацией коммитов", + "checkingBranchStatus": "Проверяем состояние ветки…", + "checkingPrStatus": "Проверяем состояние PR…", + "fastForward": "Перемотать вперёд", + "fetch": "Получить изменения", + "fetchTitle": "Получить изменения из удалённого репозитория без слияния", + "forcePush": "Отправить принудительно", + "linkedReviewBranchExists": "Ветка связанной проверки уже существует", + "linkedReviewTargetUnavailable": "Ветка связанной проверки недоступна для отправки коммитов", + "nothingNewToFastForward": "Нет новых изменений для перемотки вперёд: в удалённом репозитории есть только старые версии локальных коммитов", + "nothingNewToPull": "Нечего получать и сливать: в удалённом репозитории есть только старые версии локальных коммитов", + "nothingToFastForward": "Нет изменений для перемотки вперёд", + "nothingToForcePush": "Нечего принудительно отправлять", + "nothingToForcePushTo": "Нечего принудительно отправлять в {{upstreamName}}", + "nothingToPull": "Нечего получать и сливать", + "nothingToPush": "Нечего отправлять", + "nothingToPushTo": "Нечего отправлять в {{upstreamName}}", + "prAlreadyMerged": "PR уже слит", + "publishBranch": "Опубликовать ветку", + "publishBranchFirstToFastForward": "Сначала опубликуйте ветку для перемотки вперёд", + "publishBranchFirstToPull": "Сначала опубликуйте ветку для получения и слияния коммитов", + "publishBranchFirstToSync": "Сначала опубликуйте ветку для синхронизации коммитов", + "publishLinkedReview": "Связанная проверка", + "publishNoBranch": "Нет ветки", + "publishPrStatus": "Состояние PR", + "publishToOrigin": "Опубликовать эту ветку в origin", + "pull": "Получить и слить", + "push": "Отправить", + "pushAndSetUpstream": "Отправить эту ветку и при необходимости настроить отслеживаемую удалённую ветку", + "pushMayNeedSync": "Отправить локальные коммиты; git может потребовать предварительную синхронизацию", + "pushToLinkedReview": "Отправить изменения в ветку связанной проверки", + "rebaseChooseRemoteBase": "Выберите удалённую базовую ветку для перебазирования", + "rebaseDirtyHint": "Попробуйте перебазирование; git может потребовать сначала создать коммит с локальными изменениями или убрать их в stash", + "rebaseFromBase": "Перебазировать на базовую ветку", + "rebaseFromNamedBase": "Перебазировать на {{baseRef}}", + "rebaseWithLatestFrom": "Перебазировать текущую ветку на последние коммиты из {{baseRef}}", + "sync": "Синхронизировать", + "tryFastForwardPull": "Попробовать получить изменения и слить перемоткой вперёд; git может отклонить операцию из-за локальных коммитов", + "tryRegularPush": "Попробовать обычную отправку; git может потребовать принудительную отправку", + "useForcePushToSync": "Используйте принудительную отправку: в удалённом репозитории есть только старые версии локальных коммитов" + } + }, + "terminal": { + "keyDefinitions": { + "accessoryArrowDown": "Стрелка вниз", + "accessoryArrowLeft": "Стрелка влево", + "accessoryArrowRight": "Стрелка вправо", + "accessoryArrowUp": "Стрелка вверх", + "arrowDown": "Стрелка вниз", + "arrowLeft": "Стрелка влево", + "arrowRight": "Стрелка вправо", + "arrowUp": "Стрелка вверх", + "clearLineBeforeCursor": "Очистить строку до курсора", + "clearScreen": "Очистить экран", + "deleteWordBackward": "Удалить слово слева", + "endOfLine": "Конец строки", + "forwardDelete": "Удалить символ справа", + "interruptTerminal": "Прервать выполнение в терминале", + "reverseSearch": "Обратный поиск", + "sendEof": "Отправить EOF", + "startOfLine": "Начало строки", + "suspendProcess": "Приостановить процесс" + }, + "webView": { + "documentStartFailed": "Не удалось запустить документ терминала — {{message}}", + "httpError": "Ошибка HTTP в WebView терминала", + "loadFailed": "Не удалось загрузить WebView терминала", + "renderProcessEnded": "Процесс отрисовки WebView терминала завершён" + }, + "webviewEngineErrorState": { + "code": "код {{code}}", + "reload": "Перезагрузить", + "rendererCrashed": "сбой процесса отрисовки", + "status": "статус {{statusCode}}", + "title": "Не удалось загрузить терминал" + }, + "webviewNotificationDispatch": { + "unknownError": "Неизвестная ошибка терминала" + }, + "webviewReadyWatchdog": { + "initializeFailed": "Не удалось инициализировать терминал — нет сигнала готовности от его интерфейса" + } + }, "webShell": { "shellStateScreen": { "checkingHost": "Проверка сервера", @@ -1516,6 +2863,18 @@ "tryAgain": "Повторить попытку" } }, + "workspace": { + "branchChoice": { + "automaticName": "Автоматическое имя", + "existing": "Существующая ветка", + "existingHint": "Новые коммиты добавляются в эту ветку.", + "hostUpdateRequired": "Обновите Orca на сервере рабочего пространства, чтобы повторно использовать ветку или явно выбрать создание отдельной ветки.", + "new": "Новая ветка", + "newHint": "Создать новую локальную ветку", + "newName": "Имя новой ветки", + "title": "Ветка для этого рабочего дерева" + } + }, "worktree": { "agentRowDisplay": { "blocked": "Заблокирован", @@ -1561,16 +2920,5 @@ "inReview": "На проверке", "pinned": "Закреплено" } - }, - "workspace": { - "branchChoice": { - "title": "Ветка для этого рабочего дерева", - "existing": "Существующая ветка", - "existingHint": "Новые коммиты добавляются в эту ветку.", - "new": "Новая ветка", - "newHint": "Создать новую локальную ветку", - "newName": "Имя новой ветки", - "automaticName": "Автоматическое имя" - } } } diff --git a/mobile/src/i18n/locales/tr.json b/mobile/src/i18n/locales/tr.json index 5b8c60a60e8..67f746007e7 100644 --- a/mobile/src/i18n/locales/tr.json +++ b/mobile/src/i18n/locales/tr.json @@ -139,6 +139,367 @@ "title": "Terminal" } }, + "browser": { + "addressField": { + "urlPlaceholder": "URL" + }, + "pane": { + "invalidUrl": "Geçerli bir URL girin." + }, + "paneView": { + "dialogTitle": "Tarayıcı İletişim Kutusu", + "forward": "İleri", + "keyboardPlaceholder": "Sayfaya yazın…", + "reload": "Yenile", + "sendText": "Tarayıcıya metin gönder" + }, + "pointerModifiers": { + "clickModifier": "Tıklamalarda {{key}} tuşunu kullan" + }, + "useBrowserCommands": { + "deliveryUnconfirmed": "Gönderim doğrulanamadı — yeniden denemeden önce sayfayı kontrol edin", + "dialogAnswerFailed": "Yanıt sayfaya ulaşmadı.", + "sent": "Gönderildi" + }, + "viewModeSwitch": { + "mobileView": "Web sitesinin mobil görünümünü göster", + "webView": "Web sitesinin masaüstü görünümünü göster" + } + }, + "chat": { + "ask": { + "answerPlaceholder": "Yanıtınızı yazın", + "next": "İleri", + "other": "Diğer…", + "skip": "Atla", + "step": "Adım {{step}}", + "submit": "Gönder" + }, + "askRow": { + "asked": "Soruldu:", + "awaiting": "Kullanıcı girdisi bekleniyor:", + "awaitingUnnamed": "Kullanıcı girdisi bekleniyor", + "questionCount": "{{value0}} soru" + }, + "composer": { + "addToMessage": "Mesaja ekle", + "attachImage": "Görsel ekle", + "cancelEditing": "Düzenlemeyi iptal et", + "placeholder": "Bir şey sorun; dosyalara başvurmak için @, komutlar için / kullanın", + "saveEditedMessage": "Düzenlenen mesajı kaydet", + "sendMessage": "Mesaj gönder" + }, + "composerAttachments": { + "attachedImage": "Ekli görsel", + "file": "Dosya", + "fileMeta": "{{type}} · {{size}}", + "removeFile": "{{name}} dosyasını kaldır", + "removeImage": "Görseli kaldır", + "uploadingFile": "{{name}} yükleniyor" + }, + "composerPlaceholder": { + "prompt": "Bir şey sorun; dosyalara başvurmak için @, komutlar için / kullanın", + "reconnecting": "Yeniden bağlanılıyor…", + "waitingForTerminal": "Terminal bekleniyor…" + }, + "diffCard": { + "copyDiff": "Diff'i kopyala", + "diffGap": "Gösterilmeyen satırlar", + "diffTruncated": "Diff kısaltıldı" + }, + "hostStatus": { + "continuedOutside": "Bu sohbetin dışında devam edildi.", + "continuedOutsideRewound": "Bu sohbetin dışında devam edildi. Yukarıdaki önceki yanıtlar artık bu sohbetin geçmişinde yer almıyor.", + "historyItemTooLarge": "Sohbetin bu bölümü gösterilemeyecek kadar büyüktü.", + "historyRepaired": "Bu sohbetin geçmişinin bir bölümü yüklenemedi." + }, + "message": { + "copyMessage": "Mesajı kopyala" + }, + "messageActionsSheet": { + "clipboardRejected": "Metin panoya kopyalanamadı.", + "copyFailed": "Kopyalanamadı", + "copyMessage": "Mesajı kopyala", + "selectText": "Metni seç" + }, + "nativeChatImageViewer": { + "closeImage": "Görseli kapat" + }, + "nativeChatProse": { + "attachedImage": "Ekli görsel", + "image": "görsel", + "openImageHint": "Görseli tam boyutta açar" + }, + "nativeChatToolLine": { + "added": "Eklendi", + "copyCommand": "Komutu kopyala", + "deleted": "Silindi", + "durationHours": "{{hours}} sa {{minutes}} dk {{seconds}} sn", + "durationMilliseconds": "{{milliseconds}} ms", + "durationMinutes": "{{minutes}} dk {{seconds}} sn", + "durationSeconds": "{{seconds}} sn", + "edited": "Düzenlendi", + "editing": "Düzenleniyor", + "exitCode": "çıkış {{exitCode}}", + "fetched": "Getirildi", + "fetching": "Getiriliyor", + "listed": "Listelendi", + "listing": "Listeleniyor", + "ran": "Çalıştırıldı", + "read": "Okundu", + "reading": "Okunuyor", + "renamed": "Yeniden adlandırıldı", + "result": "Sonuç", + "running": "Çalışıyor", + "searched": "Arandı", + "searchedWeb": "Web'de arandı", + "searching": "Aranıyor", + "searchingWeb": "Web'de aranıyor", + "subagent": "Alt ajan", + "triedEdit": "Düzenlemeyi denedi", + "triedFetch": "Getirmeyi denedi", + "triedList": "Listelemeyi denedi", + "triedRead": "Okumayı denedi", + "triedSearch": "Aramayı denedi", + "triedSearchWeb": "Web'de aramayı denedi" + }, + "nativeChatUserMessageFold": { + "loadFailed": "Mesajın tamamını göster — yüklenemedi, yeniden denemek için dokunun", + "showFullMessage": "Mesajın tamamını göster", + "showLess": "Daha az göster" + }, + "nativeChatVisual": { + "defaultTitle": "Görselleştirme", + "openFullscreen": "{{title}} öğesini tam ekran aç", + "retryHint": "Yeniden yüklemeyi dener", + "unavailable": "Görselleştirme kullanılamıyor" + }, + "notice": { + "compaction": "Bağlam sıkıştırıldı", + "contextCleared": "Bağlam temizlendi", + "plan": "Plan", + "stoppedManually": "Elle durduruldu" + }, + "orcaStop": { + "canContinue": "Bu konuşmada devam edebilirsiniz.", + "crash": "Bu yanıt hazırlanırken {{machine}} üzerindeki Orca beklenmedik biçimde durdu.", + "quit": "Bu yanıt hazırlanırken {{machine}} üzerindeki Orca kapatıldı.", + "remote": "Bu yanıt hazırlanırken {{machine}} üzerindeki Orca durdu.", + "unnamedServer": "uzak sunucu", + "update": "Bu yanıt hazırlanırken {{machine}} üzerindeki Orca bir güncelleme için yeniden başlatıldı." + }, + "permission": { + "needsAccessTo": "Erişim gereken konum: ", + "newerVersionRequired": "Bu istek için Orca'nın daha yeni bir sürümü gerekiyor.", + "planFile": "Plan dosyası: {{filePath}}", + "reason": "Neden: " + }, + "promptCard": { + "answerInTerminal": "Terminalde yanıtla", + "terminalExplanation": "Bu OMP sorusunu terminalde yanıtlayın." + }, + "promptStrip": { + "expand": "Genişlet" + }, + "question": { + "otherReplyPlaceholder": "Ya da bir yanıt yazın…", + "replyPlaceholder": "Yanıtınızı yazın…", + "sendReply": "Yanıt gönder", + "submit": "Gönder", + "submitCount": "Gönder ({{count}})", + "submitSelectedOptions": "Seçilen seçenekleri gönder" + }, + "queuedMessages": { + "clearAll": "Tümünü temizle", + "deleteAll": "Kuyruktaki tüm mesajları sil", + "editMessage": "Mesajı düzenle", + "pausedState": "Duraklatıldı", + "queuedState": "Sıraya alındı", + "resume": "Devam ettir", + "resumeSending": "Kuyruktaki mesajları göndermeye devam et", + "title": "Kuyruktaki mesajlar" + }, + "receipt": { + "cancelled": "İptal edildi", + "cancelledBy": "{{device}} üzerinde iptal edildi", + "emptyAnswer": "Boş yanıt", + "resolved": "Çözüldü", + "resolver": "{{device}} üzerinde yanıtlandı", + "unavailable": "Seçilen yanıt kullanılamıyor" + }, + "sessionOptionCopy": { + "availableAfterSessionStarts": "Oturum başladıktan sonra kullanılabilir.", + "chooseInAgentPicker": "Ajan seçicisinden seç…", + "effort": "Çaba", + "fast": "Hızlı", + "fastMode": "Hızlı mod", + "high": "Yüksek", + "low": "Düşük", + "max": "Maks", + "medium": "Orta", + "mediumShort": "Orta", + "minimal": "Minimal", + "minimalShort": "En az", + "model": "Model", + "notSet": "Ayarlanmadı", + "off": "Kapalı", + "on": "Açık", + "permissions": "İzinler", + "sessionOptions": "Oturum seçenekleri", + "setWhenSessionStarts": "Oturum başladığında ayarlanır.", + "speed": "Hız", + "standard": "Standart", + "thinking": "Düşünme", + "toggleOption": "{{value0}} aç/kapat", + "ultra": "Ultra", + "xhigh": "Ekstra yüksek", + "xhighShort": "Çok yüksek" + }, + "sessionOptionPickers": { + "agentWorking": "Ajan çalışıyor", + "backToModels": "Modellere dön", + "closePicker": "Seçiciyi kapat", + "dispatchUnconfirmed": "Ajana gönderildi, onaylanmadı", + "modelFullAccessLabel": "Model, {{value}}, tam erişim", + "modelLabel": "Model, {{value}}", + "permissions": "İzinler", + "permissionsLabel": "İzinler, {{value}}", + "settingsLocked": "Bu tur tamamlandığında model ayarlarının kilidi açılır.", + "workingAnnouncement": "Ajan çalışıyor. Bu tur tamamlandığında model ayarlarının kilidi açılır." + }, + "sessionOptionRows": { + "defaultMarker": "Varsayılan", + "notReportedMarker": "Bildirilmedi" + }, + "sessionOptionSegments": { + "dispatchUnconfirmed": "Ajana gönderildi, onaylanmadı" + }, + "stopButton": { + "stopAgent": "Ajanı durdur", + "stoppingState": "Durduruluyor…" + }, + "streamFrame": { + "streamEnded": "Sohbet dökümü akışı sona erdi", + "streamFailed": "Sohbet dökümü akışı sürdürülemedi" + }, + "taskList": { + "added": "{{task}} eklendi", + "completed": "Tamamlandı", + "empty": "Görev yok", + "finished": "{{task}} tamamlandı", + "inProgress": "Devam ediyor", + "pending": "Beklemede", + "progress": "{{total}} görevden {{completed}} tanesi tamamlandı", + "removed": "{{task}} kaldırıldı", + "reset": "Beklemede olarak işaretlendi: {{task}}", + "showAll": "Tüm görev listesi", + "started": "{{task}} başlatıldı", + "taskStatus": "{{status}}: {{task}}", + "title": "Görevler", + "unchanged": "Görevler değişmedi", + "updated": "{{task}} güncellendi" + }, + "toolActivity": { + "count_one": "{{count}} araç çağrısı", + "count_other": "{{count}} araç çağrısı", + "failedCallsLabel": "Başarısız araç çağrıları: {{value0}}", + "failedCount": "{{value0}} başarısız", + "interruptedCallsLabel": "Kesintiye uğrayan araç çağrıları: {{value0}}", + "interruptedCount": "{{value0}} kesintiye uğradı", + "moreCalls": "+{{value0}} tane daha", + "result": "Sonuç", + "runningCommand": "Komut çalıştırılıyor", + "runningNamed": "{{toolName}} çalıştırılıyor", + "runningNamedPreview": "{{toolName}} {{preview}} çalıştırılıyor", + "runningPreview": "{{preview}} çalıştırılıyor" + }, + "toolRun": { + "list": "{{value0}} ve {{value1}}", + "liveBrowserOnce": "Tarayıcıyı kullanıyor", + "liveBrowserTimes_one": "Tarayıcı {{count}} kez kullanılıyor", + "liveBrowserTimes_other": "Tarayıcı {{count}} kez kullanılıyor", + "liveFileChange_one": "{{count}} dosya düzenleniyor", + "liveFileChange_other": "{{count}} dosya düzenleniyor", + "liveListFiles_one": "{{count}} dizin listeleniyor", + "liveListFiles_other": "{{count}} dizin listeleniyor", + "liveMcpToolCall_one": "{{count}} entegrasyon kullanılıyor", + "liveMcpToolCall_other": "{{count}} entegrasyon kullanılıyor", + "liveOther_one": "{{count}} araç kullanılıyor", + "liveOther_other": "{{count}} araç kullanılıyor", + "liveRead_one": "{{count}} dosya okunuyor", + "liveRead_other": "{{count}} dosya okunuyor", + "liveSearch_one": "{{count}} arama yapılıyor", + "liveSearch_other": "{{count}} arama yapılıyor", + "liveSubAgentActivity_one": "{{count}} ajan çalıştırılıyor", + "liveSubAgentActivity_other": "{{count}} ajan çalıştırılıyor", + "liveTodoListOnce": "Plan güncelleniyor", + "liveTodoListTimes_one": "Plan {{count}} kez güncelleniyor", + "liveTodoListTimes_other": "Plan {{count}} kez güncelleniyor", + "liveUnknown_one": "{{count}} komut çalıştırılıyor", + "liveUnknown_other": "{{count}} komut çalıştırılıyor", + "liveWebSearch_one": "Web'de {{count}} arama yapılıyor", + "liveWebSearch_other": "Web'de {{count}} arama yapılıyor", + "pair": "{{value0}} ve {{value1}}", + "separator": ", ", + "settledBrowserOnce": "Tarayıcıyı kullandı", + "settledBrowserTimes_one": "Tarayıcı {{count}} kez kullanıldı", + "settledBrowserTimes_other": "Tarayıcı {{count}} kez kullanıldı", + "settledFileChange_one": "{{count}} dosya düzenlendi", + "settledFileChange_other": "{{count}} dosya düzenlendi", + "settledListFiles_one": "{{count}} dizin listelendi", + "settledListFiles_other": "{{count}} dizin listelendi", + "settledMcpToolCall_one": "{{count}} entegrasyon kullanıldı", + "settledMcpToolCall_other": "{{count}} entegrasyon kullanıldı", + "settledOther_one": "{{count}} araç kullanıldı", + "settledOther_other": "{{count}} araç kullanıldı", + "settledRead_one": "{{count}} dosya okundu", + "settledRead_other": "{{count}} dosya okundu", + "settledSearch_one": "{{count}} kez arama yapıldı", + "settledSearch_other": "{{count}} kez arama yapıldı", + "settledSubAgentActivity_one": "{{count}} ajan çalıştırıldı", + "settledSubAgentActivity_other": "{{count}} ajan çalıştırıldı", + "settledTodoListOnce": "Plan güncellendi", + "settledTodoListTimes_one": "Plan {{count}} kez güncellendi", + "settledTodoListTimes_other": "Plan {{count}} kez güncellendi", + "settledUnknown_one": "{{count}} komut çalıştırıldı", + "settledUnknown_other": "{{count}} komut çalıştırıldı", + "settledWebSearch_one": "Web'de {{count}} kez arama yapıldı", + "settledWebSearch_other": "Web'de {{count}} kez arama yapıldı" + }, + "turnStatus": { + "failedAfter": "{{value0}} sonra tamamlanamadı", + "hoursMinutesSeconds": "{{hours}} sa {{minutes}} dk {{seconds}} sn", + "interruptedAfter": "{{value0}} sonra kesildi", + "minutesSeconds": "{{minutes}} dk {{seconds}} sn", + "queueAfterStop": "Durduktan sonra işlenecek bir mesajı sıraya alın", + "responding": "Ajan yanıt veriyor", + "seconds_one": "{{count}} sn", + "seconds_other": "{{count}} sn", + "sendAfterStop": "Durduktan sonra işlenecek bir mesaj gönderin", + "stopping": "Durduruluyor…", + "thinking": "Düşünüyor", + "toggleDetails": "Tur ayrıntılarını aç/kapat", + "workedFor": "{{value0}} boyunca çalıştı", + "working": "Çalışıyor…", + "workingFor": "{{value0}} süredir çalışıyor" + }, + "usageLimit": { + "reached": "{{agent}} kullanım sınırına ulaştı.", + "reachedNamed": "{{agent}}, {{window}} sınırına ulaştı.", + "resetsAt": "Sıfırlanma zamanı: {{when}}.", + "theAgent": "Ajan", + "windowCredits": "ek kullanım", + "windowSession": "oturum", + "windowWeekly": "haftalık", + "windowWeeklyModel": "haftalık model" + }, + "useImageUpload": { + "attachFailed": "Eklenemedi", + "disconnected": "Eklenemedi (bağlantı yok)", + "imageTooLarge": "Görsel eklenemeyecek kadar büyük", + "permissionDenied": "Fotoğraf erişim izni reddedildi" + } + }, "common": { "back": "Geri", "cancel": "İptal", @@ -151,6 +512,8 @@ "done": "Bitti", "edit": "Düzenle", "ok": "Tamam", + "open": "Aç", + "refresh": "Yenile", "remove": "Kaldır", "retry": "Yeniden dene", "save": "Kaydet", @@ -757,8 +1120,51 @@ "unexpectedResponse": "Beklenmeyen yanıt" }, "webShellUpdateFailureCopy": { + "blockedReason": "Engellendi: {{reason}}.", + "blockedWorkspace": "Çalışma alanı engellendi.", + "failed": "Hata ekranı gösterildi.", "failedAgo": "{{hostName}} üzerinden yapılan son güncelleme {{time}} önce tamamlanamadı: {{reason}}{{offered}}. {{outcome}}", - "failedJustNow": "{{hostName}} üzerinden yapılan son güncelleme az önce tamamlanamadı: {{reason}}{{offered}}. {{outcome}}" + "failedJustNow": "{{hostName}} üzerinden yapılan son güncelleme az önce tamamlanamadı: {{reason}}{{offered}}. {{outcome}}", + "hostCodeCopy": { + "assetChanged": "sunucuda bir varlık değişti", + "assetUnknown": "sunucu bir varlığı tanımadı", + "buildChanged": "indirme sırasında sunucunun derlemesi değişti", + "offsetInvalid": "sunucu bir okuma konumunu reddetti", + "readLimited": "sunucu eşzamanlı okumaları sınırladı", + "unavailable": "sunucuda çalışma alanı paketi yok" + }, + "nativeRoute": "Yerel ekran gösterildi.", + "offeredGeneration": " (nesil {{generation}})", + "openedCached": "Kayıtlı sürüme geri dönüldü.", + "openedCachedGeneration": "Kayıtlı sürüme geri dönüldü (nesil {{generation}}).", + "reasonCopy": { + "assetChecksumMismatch": "varlık sağlama toplamı uyuşmazlığı", + "assetEntryChanged": "bir varlık artık bildirim dosyasıyla eşleşmiyordu", + "assetNoProgress": "bir varlık okunurken ilerleme sağlanamadı", + "assetOverlong": "bir varlığın boyutu bildirim dosyasında belirtilenden büyüktü", + "assetShort": "bir varlığın verileri belirtilen boyuta ulaşmadan sona erdi", + "buildChangedMidFetch": "indirme sırasında sunucunun derlemesi değişti", + "cacheWriteFailed": "indirilen veriler bu telefona kaydedilemedi", + "chunkMisrouted": "bir veri parçası yanlış varlığa veya konuma karşılık geliyordu", + "chunkOversize": "bir veri parçası sunucunun izin verdiği boyutu aşıyordu", + "connectionLost": "bağlantı kesildi", + "fetchStopped": "indirme durduruldu", + "hostRefused": "sunucu okuma isteğini reddetti", + "noConnection": "sunucuya bağlı değil", + "rangeUndecodable": "okunan sıkıştırılmış veri çözülemedi", + "replyUnreadable": "sunucu bu uygulamanın okuyamadığı bir yanıt gönderdi", + "unrecognisedError": "tanınmayan bir hata" + }, + "routeUnavailable": "Ekranın kullanılamadığı gösterildi.", + "waiting": "Sunucu beklendi.", + "wallCopy": { + "bundleShellTooOld": "bu uygulamanın sürümü kayıtlı paket için çok eski", + "bundleTooOldForHost": "kayıtlı paket sunucu için çok eski", + "bundleUnavailable": "sunucuda çalışma alanı paketi yok", + "hostTooOldForBundle": "sunucu sürümü kayıtlı paket için çok eski", + "pageRouteGrantMissing": "bu ekran için uygulamanın daha yeni bir sürümü gerekiyor", + "pageRouteUnlisted": "sunucunun çalışma alanı bu ekranı içermiyor" + } } }, "dictation": { @@ -775,6 +1181,95 @@ "updateFailed": "Dikte ayarları güncellenemedi" } }, + "files": { + "fileExplorerPanel": { + "backToSession": "Oturuma dön", + "closeFiles": "Dosyaları kapat", + "connecting": "Masaüstüne bağlanılıyor…", + "emptyState": "Dosya bulunamadı", + "loadFailed": "Dosyalar yüklenemedi", + "loadInterrupted": "Klasör yüklemesi kesintiye uğradı", + "title": "Dosyalar", + "truncatedList": "{{worktreeLabel}} - İlk 5000 dosya gösteriliyor", + "waiting": "Masaüstü bekleniyor…" + }, + "fileExplorerRow": { + "fileUnavailable": "{{name}} mobilde kullanılamıyor", + "loadFolderFailed": "Klasör yüklenemedi", + "loading": "Yükleniyor…", + "openFolder": "{{name}} klasörünü aç", + "previewFile": "{{name}} dosyasını önizle", + "retryLoading": "{{path}} klasörünü yüklemeyi yeniden dene", + "unavailable": "Mobilde kullanılamıyor" + }, + "fileMarkdownPreview": { + "copyLoadedSource": "Yüklenen Markdown kaynağını kopyala", + "copySource": "Markdown kaynağını kopyala", + "viewPreview": "İşlenmiş Markdown önizlemesini görüntüle", + "viewSource": "Markdown kaynağını görüntüle" + }, + "fileMediaPreview": { + "downloading": "İndiriliyor: {{bytes}} / {{total}}", + "loadFailed": "Medya yüklenemedi", + "loading": "Medya yükleniyor…", + "waiting": "Masaüstü bekleniyor…" + }, + "fileMutationOwnership": { + "sshOwnerChanged": "SSH bağlantısı doğrulanamadı. Sunucuya yeniden bağlanın ve tekrar deneyin." + }, + "filePreviewBody": { + "emptyFile": "Boş dosya", + "imageLabel": "{{title}} görseli" + }, + "filePreviewEditableSource": { + "editorLabel": "{{title}} düzenleyicisi" + }, + "filePreviewResponse": { + "binaryUnavailable": "İkili dosya önizlemesi kullanılamıyor", + "bytes": "{{size}} B", + "fileChanged": "Dosya masaüstünde değişti. Kaydetmeden önce önizlemeyi yeniden yükleyin", + "fileNotFound": "Dosya bulunamadı", + "fileTooLarge": "Dosya mobil önizleme için çok büyük", + "filesystemUnreachable": "Masaüstü bilgisayarın dosya sistemine erişilemedi", + "kilobytes": "{{size}} KB", + "loadFailed": "Önizleme yüklenemedi", + "megabytes": "{{size}} MB", + "reloadBeforeSaving": "Kaydetmeden önce önizlemeyi yeniden yükleyin", + "unknownSize": "boyut bilinmiyor" + }, + "filePreviewScreen": { + "backToFiles": "Dosyalara dön", + "chooseWorkspaceFile": "Bu çalışma alanından bir dosya seçin.", + "discard": "Vazgeç", + "discardMessage": "Kaydedilmemiş düzenlemeler kaybolacak.", + "discardTitle": "Değişiklikler geri alınsın mı?", + "fileTitle": "Dosya", + "loadFailed": "Önizleme yüklenemedi", + "loading": "Önizleme yükleniyor…", + "openFailed": "Dosya açılamadı", + "saveArtifact": "Terminal çıktısını kaydet", + "saveFailed": "Dosya kaydedilemedi", + "stay": "Kal", + "title": "Önizleme", + "waiting": "Masaüstü bekleniyor…" + }, + "filePreviewSourceText": { + "previewLabel": "Dosya önizlemesi", + "truncatedNote": "Önizleme kısaltıldı. Dosya boyutu: {{size}}." + }, + "markdownPreviewImage": { + "expandFallback": "Görseli genişlet", + "expandImage": "{{alt}} görselini genişlet", + "imageLabel": "Markdown görseli", + "loadFailed": "Görsel yüklenemedi" + }, + "pdfPreview": { + "open": "PDF aç", + "openFailed": "Bu PDF açılamadı", + "opening": "PDF açılıyor…", + "updateRequired": "PDF'leri açmak için Orca Mobile'ı güncelleyin" + } + }, "home": { "accountUsageCards": { "sectionHeading": "Hesap kullanımı", @@ -1103,10 +1598,10 @@ "noComments": "Henüz yorum yok.", "noDescription": "Açıklama girilmemiş.", "noHumanComments": "İnsan yorumu yok.", - "resolvedComment_one": "Çözülmüş yorum · {{author}}", - "resolvedComment_other": "Çözülmüş yorum · {{author}} ({{count}})", - "resolvedThread_one": "Çözülmüş yorum dizisi · {{author}}", - "resolvedThread_other": "Çözülmüş yorum dizisi · {{author}} ({{count}})", + "resolvedCommentCount": "Çözülmüş yorum · {{author}} ({{count}})", + "resolvedCommentSingle": "Çözülmüş yorum · {{author}}", + "resolvedThreadCount": "Çözülmüş yorum dizisi · {{author}} ({{count}})", + "resolvedThreadSingle": "Çözülmüş yorum dizisi · {{author}}", "showMore": "{{count}} tane daha göster", "showMoreOf": "Kalan {{remaining}} yorumdan {{count}} tane daha göster", "title": "Yorumlar" @@ -1298,6 +1793,596 @@ "title": "İnceleyenler" } }, + "session": { + "accessoryKeys": { + "addCustomShortcut": "Özel kısayol ekle", + "dismissKeyboard": "Klavyeyi gizle", + "dismissKeyboardHint": "Ekran klavyesini gizler ve geçerli terminal oturumunu açık tutar.", + "paste": "Yapıştır", + "pasteFromClipboard": "Panodan yapıştır", + "sendKey": "{{label}} gönder", + "switchToBufferedInput": "Arabellekli komut girişine geç", + "switchToDesktopMode": "Masaüstü moduna geç", + "switchToLiveInput": "Canlı terminal girişine geç", + "switchToPhoneMode": "Telefon moduna geç" + }, + "activeContent": { + "createTab": "Sekme Oluştur", + "creatingTab": "Oluşturuluyor…", + "fileTitle": "Dosya", + "loadingTerminal": "Terminal yükleniyor", + "noTabs": "Bu oturumda sekme yok", + "retryLoadingTerminal": "Terminali yüklemeyi yeniden dene", + "terminalTakingLonger": "Terminalin yüklenmesi beklenenden uzun sürüyor" + }, + "agentChildRow": { + "agent": "Ajan", + "days_one": "{{count}}g", + "days_other": "{{count}}g", + "ended": "Sona erdi", + "hours_one": "{{count}}sa", + "hours_other": "{{count}}sa", + "minutes_one": "{{count}}dk", + "minutes_other": "{{count}}dk", + "noRecentUpdate": "Yakın zamanda güncelleme yok", + "noUpdate": "{{value0}} süredir güncelleme yok" + }, + "agentMessageAttribution": { + "from": "Gönderen: {{sender}}", + "fromMore": "Gönderen: {{sender}} +{{count}}", + "fromUnnamed": "Bir ajandan", + "messageFrom": "Mesajı gönderen: {{sender}}", + "messageFromMore": "Mesajı gönderen: {{sender}} +{{count}}", + "messageFromUnnamed": "Bir ajandan mesaj", + "unnamedSender": "bir ajan" + }, + "aiVaultResumeLaunch": { + "createFailed": "Terminal oluşturulamadı", + "inputLocked": "Terminal girişi kilitli", + "sendFailed": "Sürdürme komutu gönderilemedi" + }, + "aiVaultResumePreparation": { + "prepareFailed": "Bu eski Codex oturumu hazırlanamadı. Oturumu sürdürmeyi yeniden deneyin." + }, + "backgroundTask": { + "agent": "Arka plan ajanı", + "command": "Arka plan komutu", + "countAgents_one": "{{count}} ajan", + "countAgents_other": "{{count}} ajan", + "countMonitors_one": "{{count}} izleyici", + "countMonitors_other": "{{count}} izleyici", + "countShellCommandOne": "1 kabuk komutu", + "countShell_one": "{{count}} kabuk", + "countShell_other": "{{count}} kabuk", + "countTasks_one": "{{count}} görev", + "countTasks_other": "{{count}} görev", + "countWorkflows_one": "{{count}} iş akışı", + "countWorkflows_other": "{{count}} iş akışı", + "detailsUnavailable": "Bu oturum için görev ayrıntıları kullanılamıyor.", + "groupAgents": "Ajanlar", + "groupMonitors": "İzleyiciler", + "groupShell": "Kabuk", + "groupTasks": "Görevler", + "groupWorkflows": "İş akışları", + "headerTotalCount_one": "{{count}} arka plan görevi", + "headerTotalCount_other": "{{count}} arka plan görevi", + "monitor": "Arka plan izleyicisi", + "monitoring": "Arka plan görevleri izleniyor", + "reasonBlocked": "başarısız", + "reasonWaiting": "onay gerekiyor", + "stateBlocked": "engellendi", + "stateBlockedCount": "{{value0}} engellendi", + "stateDone": "tamamlandı", + "stateDoneCount": "{{value0}} tamamlandı", + "stateIdle": "durduruldu", + "stateIdleCount": "{{value0}} durdu", + "stateMonitoring": "izliyor", + "stateMonitoringCount": "{{value0}} izliyor", + "stateUnverifiable": "durum bilgisi alınamıyor", + "stateUnverifiableCount": "durum bilgisi alınamıyor: {{value0}}", + "stateWaiting": "bekliyor", + "stateWaitingCount": "{{value0}} bekliyor", + "stateWorking": "çalışıyor", + "stateWorkingCount": "{{value0}} çalışıyor", + "stop": "Durdur", + "stopAll": "Arka plan görevlerini durdur", + "stopTask": "Durdur: {{value0}}", + "task": "Arka plan görevi", + "workflow": "Arka plan iş akışı" + }, + "browserTabActionSheet": { + "forward": "İleri", + "reload": "Yenile", + "title": "Tarayıcı" + }, + "bulkCloseSheetActions": { + "closeOtherTabs": "Diğer Sekmeleri Kapat" + }, + "bulkTabClose": { + "confirm": "Sekmeleri kapat", + "description": "Seçilen sekmeler kapatılacak. Sabitlenmiş sekmeler açık kalacak.", + "titleEditors": "Tüm düzenleyici sekmeleri kapatılsın mı?", + "titleLeft": "Soldaki sekmeler kapatılsın mı?", + "titleOthers": "Diğer sekmeler kapatılsın mı?", + "titleRight": "Sağdaki sekmeler kapatılsın mı?" + }, + "commandDock": { + "commandPlaceholder": "Bir komut yazın…", + "liveInputHint": "Yazılan metin doğrudan etkin terminale gönderilir", + "liveInputLabel": "Canlı terminal girişi", + "sendCommand": "Komut gönder" + }, + "contentRow": { + "dismissCreationWarning": "Çalışma alanı oluşturma uyarısını kapat" + }, + "diffCommentLineRow": { + "addNote": "{{line}}. satıra not ekle", + "deleteNote": "{{line}}. satırdaki notu sil", + "diffLine": "{{title}} diff'i, {{line}}. satır", + "line": "Satır {{line}}", + "placeholder": "İnceleme notu ekle", + "saveNote": "Notu kaydet" + }, + "diffLineRow": { + "addNoteLabel": "{{line}} numaralı satıra not ekle", + "deleteNoteLabel": "{{line}} numaralı satırdaki notu sil", + "diffLineLabel": "{{title}} diff satırı {{line}}", + "lineNumber": "Satır {{line}}", + "notePlaceholder": "İnceleme notu ekle", + "saveNote": "Notu kaydet" + }, + "diffReviewLoaders": { + "changesLoadFailed": "Değişiklikler yüklenemedi", + "committedChangesFailed": "Commit'lenmiş değişiklikler yüklenemedi", + "committedChangesUnavailable": "Commit'lenmiş değişiklikler kullanılamıyor", + "committedDiffUnavailable": "Commit'lenmiş diff kullanılamıyor", + "desktopUpdateRequired": "Değişiklikleri mobilde incelemek için Orca masaüstü uygulamasını güncelleyin.", + "diffLoadFailed": "Diff yüklenemedi", + "invalidSourceControlResponse": "Kaynak kontrolü yanıtı geçersizdi", + "reviewNotesLoadFailed": "İnceleme notları yüklenemedi" + }, + "diffReviewQueue": { + "committedOnBranch": "Branch'te commit'lendi", + "staged": "Hazırlanmış", + "unstaged": "Hazırlanmamış" + }, + "diffReviewScreenModel": { + "branch": "Branch", + "staged": "Hazırlanmış", + "unstaged": "Hazırlanmamış" + }, + "fileReader": { + "copyReviewNotes": "İnceleme notlarını kopyala", + "imageLabel": "{{title}} görseli", + "noReviewNotes": "İnceleme notu yok", + "previewLabel": "{{title}} önizlemesi", + "reviewNotes_one": "{{count}} inceleme notu", + "reviewNotes_other": "{{count}} inceleme notu", + "send": "Gönder", + "sendReviewNotes": "İnceleme notlarını yapay zekâya gönder" + }, + "floatingWorkspace": { + "title": "Yüzen Çalışma Alanı" + }, + "githubPrMutations": { + "reviewThreadUpdateFailed": "İnceleme dizisi güncellenemedi.", + "titleUpdateFailed": "Başlık güncellenemedi." + }, + "githubPrRpc": { + "requestFailed": "İstek tamamlanamadı: {{method}}" + }, + "header": { + "backToWorktrees": "Worktree'lere dön", + "moreActions": "Diğer oturum işlemleri", + "openFileExplorer": "Dosya gezginini aç", + "openSourceControl": "Kaynak kontrolünü aç", + "reconnectToDesktop": "Masaüstüne yeniden bağlan", + "showSidebar": "Kenar çubuğunu göster", + "terminalTitle": "Terminal" + }, + "headerMoreActionsSheet": { + "agentHistory": "Ajan Geçmişi", + "agentHistoryHint": "Ajan oturumlarına göz atın ve kaldığınız yerden devam edin", + "checks": "Kontroller", + "checksHint": "Pull request kontrollerini açın" + }, + "markdownReader": { + "changedOnDesktop": "Masaüstünde değişti", + "copyLoaded": "Yükleneni kopyala", + "copyLoadedSource": "Yüklenen Markdown kaynağını kopyala", + "copyPath": "Yolu kopyala", + "copyPathLabel": "Markdown yolunu kopyala", + "copySource": "Markdown kaynağını kopyala", + "discard": "Vazgeç", + "dismissKeyboard": "Klavyeyi gizle", + "dismissKeyboardHint": "Ekran klavyesini gizler ve Markdown düzenleyicisini açık tutar.", + "readOnly": "Salt okunur" + }, + "nativeChatAttachActions": { + "availableSkills": "{{count}} beceri mevcut", + "commands": "Komutlar", + "fileTypes": "PDF, metin, kod…", + "files": "Dosyalar", + "photos": "Fotoğraflar", + "skills": "Beceriler", + "workspaceFile": "Çalışma alanı dosyası" + }, + "nativeChatCardHeaderAction": { + "collapse": "Daralt" + }, + "nativeChatCliFork": { + "resumeInNewCli": "Yeni CLI'de Devam Et" + }, + "nativeChatFloatingControls": { + "scrollToLatest": "En sona kaydır" + }, + "nativeChatFullContent": { + "loadMessageFailed": "Mesajın tamamını göster — yüklenemedi, yeniden denemek için dokunun", + "loadOutputFailed": "Çıktının tamamını göster — yüklenemedi, yeniden denemek için dokunun", + "partialMessage": "Bu mesajın bir kısmı gösteriliyor.", + "partialOutput": "Gösterilen: {{shown}} / {{total}}.", + "showFullMessage": "Mesajın tamamını göster", + "showFullOutput": "Çıktının tamamını göster" + }, + "nativeChatRenderData": { + "defaultAgentLabel": "ajan", + "emptySubtitle": "{{agent}} ile sohbette kodu incelemesini, çıktıyı açıklamasını veya bir değişiklik yapmasını isteyin.", + "emptyTitle": "{{agent}} ile sohbet başlatın", + "errorSubtitle": "Döküm okunamadı. Çalışmaya devam etmek için terminale geri geçin.", + "errorTitle": "Konuşma yüklenemedi", + "notFoundSubtitle": "Bir konuşma başlatmak için mesaj gönderin veya {{agent}} tarafından yapılanları görmek için terminal görünümünü açın.", + "notFoundTitle": "Bu konuşma henüz bulunamadı" + }, + "nativeChatToggleAction": { + "switchToChatView": "Sohbet görünümüne geç", + "switchToTerminalView": "Terminal görünümüne geç" + }, + "newTabAgentHostLaunch": { + "notesNotSent": "Ajan başlatıldı, ancak notlar gönderilemedi.", + "notesSent": "Notlar gönderildi", + "notesUnconfirmed": "Ajan başlatıldı, ancak notların gönderildiği doğrulanamadı.", + "promptUnconfirmed": "Ajan başlatıldı, ancak istemin gönderildiği doğrulanamadı." + }, + "prActionsEngine": { + "mergeFailed": "Pull request merge edilemedi.", + "refreshFailed": "Pull request yenilenemedi." + }, + "prAiTriageLaunch": { + "agentStarted": "Ajan başlatıldı", + "agentStartedIn": "{{label}} çalışma alanında ajan başlatıldı", + "emptyPrompt": "Bu işlem için kaydedilen istem boş. Bilgisayarınızda Kaynak Kontrolü AI ayarlarını güncelleyin.", + "loadFailed": "Kullanılabilir ajanlar yüklenemedi.", + "promptNotSent": "Ajan başlatıldı, ancak istem gönderilemedi.", + "updateRequired": "Telefonunuzdan ajan başlatmak için bilgisayarınızdaki Orca'yı güncelleyin." + }, + "prSidebarState": { + "loadFailed": "Pull request yüklenemedi" + }, + "queuedCardText": { + "showLess": "Daha az göster", + "showLessHint": "Mesajın daha azını gösterir", + "showMore": "Daha fazla göster", + "showMoreHint": "Mesajın tamamını gösterir" + }, + "queuedMessageCardRow": { + "deleteHint": "Sıraya alınmış bu mesajı sil", + "images_one": "{{count}} görsel", + "images_other": "{{count}} görsel", + "moreActions": "Diğer işlemler", + "send": "Gönder", + "sendAgainHint": "Bu mesajı yeniden gönder", + "sendHint": "Bu mesajı gönder", + "showLessHint": "Mesajın daha azını gösterir", + "steer": "Yönlendir", + "steerHint": "Modeli kesintiye uğratmadan gönder" + }, + "queuedMessageComposer": { + "commandsNotAllowed": "Komutlar sıraya alınmış bir mesaja kaydedilemez.", + "editing": "Mesaj düzenleniyor", + "editingAttributed": "Mesaj düzenleniyor · {{attribution}}" + }, + "quickCommandEditorForm": { + "action": "Eylem", + "addQuickCommand": "Hızlı Komut Ekle", + "advanced": "Gelişmiş", + "agent": "Ajan", + "agentPrompt": "Ajan İstemi", + "appendEnter": "Enter Ekle", + "appendEnterDescription": "Yalnızca metni eklemek yerine hemen gönderin.", + "chooseAgent": "Ajan seçin", + "commandText": "Komut Metni", + "globalScope": "Genel", + "label": "Etiket", + "labelPlaceholder": "Geliştirme sunucusunu başlat", + "olderHostPromptTooLong": "Bu ana makine için çok uzun. Daha uzun istemleri kaydetmek için ana makinedeki Orca'yı güncelleyin.", + "projectScope": "Proje", + "prompt": "İstem", + "promptHint": "Becerileri, dosya yollarını ve yerleşik komutları destekler.", + "promptPlaceholder": "Ajandan bu çalışma alanını incelemesini isteyin", + "promptSizeCharacters": "{{used}} / {{max}} karakter", + "promptSizeKilobytes": "{{used}} KB / {{max}} KB", + "promptTooLong": "Kaydetmek için çok uzun.", + "scope": "Kapsam", + "terminalCommand": "Terminal Komutu" + }, + "quickCommandLaunch": { + "defaultLabel": "Hızlı komut", + "editBeforeRunning": "Bu hızlı komutu çalıştırmadan önce düzenleyin", + "runFailed": "{{label}} çalıştırılamadı" + }, + "quickCommandRow": { + "copyCommand": "{{label}} komutunu kopyala", + "copyFailed": "Kopyalanamadı", + "deleteCommand": "{{label}} komutunu sil", + "editCommand": "{{label}} komutunu düzenle", + "nothingToCopy": "Kopyalanacak bir şey yok", + "runCommand": "{{label}} komutunu çalıştır" + }, + "quickCommandsList": { + "emptyState": "Henüz hızlı komut yok.", + "globalGroup": "Genel", + "newCommand": "Yeni hızlı komut", + "noMatches": "Eşleşen hızlı komut yok.", + "projectGroup": "Bu proje", + "searchPlaceholder": "Hızlı komutlarda ara…" + }, + "quickCommandsSheet": { + "addTitle": "Hızlı Komut Ekle", + "chooseAgentTitle": "Ajan Seç", + "deleteConfirmMessage": "Bu hızlı komut kayıtlı listenizden kaldırılacak.", + "deleteConfirmTitle": "“{{label}}” silinsin mi?", + "editTitle": "Hızlı Komutu Düzenle", + "editorDescription": "Hızlı erişim için terminal komutlarını veya ajan istemlerini kaydedin.", + "title": "Hızlı Komutlar", + "untitled": "Adsız" + }, + "quickCommandsTabButton": { + "quickCommands": "Hızlı komutlar" + }, + "routeHelpers": { + "connected": "Bağlı", + "connecting": "Bağlanıyor", + "disconnected": "Bağlantı kesildi", + "pairingInvalid": "Eşleştirme geçersiz", + "reconnecting": "Yeniden bağlanılıyor", + "securing": "Güvenlik sağlanıyor" + }, + "routeScreen": { + "openingWorkspace": "Çalışma alanı açılıyor" + }, + "sheets": { + "browser": "Tarayıcı", + "browserUpdateRequired": "Mobil tarayıcı yayını için masaüstü uygulaması güncellenmeli", + "chat": "Sohbet", + "copyAllAndLeave": "Tümünü Kopyala ve Ayrıl", + "copyDraftsFailed": "Taslaklar kopyalanamadı", + "copyNotes": "Notları Kopyala", + "copyNotesFailed": "Notlar kopyalanamadı", + "copyPath": "Yolu Kopyala", + "discard": "At", + "discardAndLeave": "At ve Ayrıl", + "discardChanges": "Değişiklikleri At", + "discardMessage": "Telefondaki taslak, masaüstündeki dosyanın en güncel sürümüyle değiştirilsin mi?", + "draftsHint": "Ayrılmadan önce telefondaki taslakları kopyalayın veya atın.", + "file": "Dosya", + "markdown": "Markdown", + "markdownNote": "Markdown Notu", + "newBrowser": "Yeni Tarayıcı", + "newTab": "Yeni Sekme", + "notesCopied": "Notlar kopyalandı", + "notesHint": "Mevcut notlar için bir ajan oturumu seçin.", + "removeShortcutMessage": "Bu özel kısayol kaldırılsın mı?", + "renameTerminal": "Terminali Yeniden Adlandır", + "sendReviewNotes": "İnceleme Notlarını Gönder", + "shortcut": "Kısayol", + "terminal": "Terminal", + "terminalName": "Terminal adı", + "unsavedMarkdown": "Kaydedilmemiş Markdown değişiklikleri", + "urlPrompt": "Bir URL girin veya yeni sekme için boş bırakın." + }, + "sideRail": { + "backToWorktrees": "Worktree'lere dön" + }, + "sourceControlLaunchAgent": { + "noEnabledAgent": "Bu çalışma alanı sunucusunda etkin bir AI ajanı algılanmadı.", + "savedAgentUnavailable": "Bu işlem için kaydedilen ajan, bu çalışma alanı sunucusunda kullanılamıyor." + }, + "structuredComposerCommand": { + "operationUnconfirmed": "Konuşma işlemi doğrulanamadı.", + "removeAttachments": "Bir sohbet oturumu komutu kullanmadan önce ekleri kaldırın." + }, + "structuredSendDelivery": { + "messageNotSent": "Mesaj gönderilmedi" + }, + "tabStrip": { + "checkingCapabilities": "Masaüstü özellikleri kontrol ediliyor — birazdan tekrar deneyin", + "newTab": "Yeni sekme", + "quickCommandsUpdateRequired": "Hızlı komutlar için masaüstü uygulaması güncellenmeli", + "tabStatus": "{{title}}, {{status}}" + }, + "terminalActionSheetActions": { + "clearTerminal": "Terminali Temizle", + "rename": "Yeniden Adlandır", + "switchToDesktop": "Masaüstü Moduna Geç", + "switchToPhone": "Telefon Moduna Geç" + }, + "terminalInputActions": { + "attachFileHint": "Fotoğraf yerine dosya eklemek için basılı tutun", + "attachPhoto": "Fotoğraf ekle", + "sendingImage": "Görsel gönderiliyor", + "startDictation": "Sesli dikteyi başlat", + "startingDictation": "Sesli dikte başlatılıyor", + "stopDictation": "Sesli dikteyi durdur", + "transcribingDictation": "Sesli dikte yazıya dökülüyor" + }, + "terminalLiveInputStatus": { + "listening": "Dinliyor", + "liveInput": "Canlı giriş", + "preparingMic": "Mikrofon hazırlanıyor", + "processing": "İşleniyor", + "releaseMicHint": "Durdurmak için mikrofonu bırakın", + "startingMic": "Mikrofon başlatılıyor", + "stopHint": "Durdurmak için mikrofona dokunun", + "transcribing": "Masaüstünde yazıya dökülüyor", + "uploadingImage": "Görsel ana makineye yükleniyor", + "uploadingImageTitle": "Görsel yükleniyor" + }, + "terminalRecords": { + "defaultTitle": "Terminal" + }, + "terminalTabAgent": { + "browser": "Tarayıcı", + "chat": "Sohbet", + "file": "Dosya", + "markdown": "Markdown", + "newBrowser": "Yeni Tarayıcı", + "terminal": "Terminal" + }, + "terminalViewportResubscribe": { + "fitFailed": "Terminal bu ekrana sığdırılamadı" + }, + "useAccessorySelection": { + "copyFailed": "Kopyalanamadı", + "selectionCleared": "Seçim temizlendi (arabellek dışına kaydı)" + }, + "useAttachmentInputLeaseGate": { + "reconnecting": "Eklenemedi (yeniden bağlanılıyor)" + }, + "useContentCreateActions": { + "browserCommandFailed": "Tarayıcı komutu çalıştırılamadı", + "browserCreateFailed": "Tarayıcı oluşturulamadı", + "browserUnavailable": "Tarayıcı sayfası henüz kullanılamıyor.", + "browserUpdateRequired": "Mobilde tarayıcı yayını için masaüstü uygulaması güncellenmeli", + "invalidUrl": "Geçerli bir URL girin", + "noteCreateFailed": "Markdown notu oluşturulamadı", + "untitledNoteCreateFailed": "Başlıksız Markdown notu oluşturulamadı" + }, + "useDiffComments": { + "copyNotesFailed": "Notlar kopyalanamadı", + "deleteNoteFailed": "Not silinemedi", + "noteAdded": "Not eklendi", + "notesCopied": "Notlar kopyalandı", + "saveNoteFailed": "Not kaydedilemedi", + "saveNotesFailed": "İnceleme notları kaydedilemedi", + "waitingForDesktop": "Masaüstü uygulaması bekleniyor…" + }, + "useDiffReviewCommentActions": { + "saveFailed": "İnceleme kaydedilemedi", + "saveStateFailed": "İnceleme durumu kaydedilemedi", + "waitingForDesktop": "Masaüstü bekleniyor…" + }, + "useDiffReviewController": { + "loadFailed": "İnceleme yüklenemedi", + "missingWorktree": "Worktree eksik", + "waitingForDesktop": "Masaüstü bekleniyor…" + }, + "useDiffReviewDiffLoading": { + "loadFailed": "Diff yüklenemedi", + "waitingForDesktop": "Masaüstü bekleniyor…" + }, + "useDiffReviewGitActions": { + "actionFailed": "Kaynak kontrolü işlemi gerçekleştirilemedi", + "reviewedFilesStaged_one": "İncelenen {{count}} dosya stage'e eklendi", + "reviewedFilesStaged_other": "İncelenen {{count}} dosya stage'e eklendi", + "stageResults": "{{staged}} stage'e eklendi, {{failed}} eklenemedi", + "waitingForDesktop": "Masaüstü bekleniyor…" + }, + "useDiffReviewInteractions": { + "openFailed": "Oturumda açılamadı" + }, + "useDiffReviewSendActions": { + "copyFailed": "İnceleme notları kopyalanamadı", + "inputLocked": "Terminal girişi kilitli", + "loadSessionsFailed": "Ajan oturumları yüklenemedi", + "notesCopied": "İnceleme notları kopyalandı", + "notesSent": "İnceleme notları gönderildi", + "notesSentWithWarning": "İnceleme notları gönderildi. {{warning}}", + "promptNotSent": "Ajan başlatıldı, ancak notlar gönderilemedi. Yapıştırmak için Notları Kopyala seçeneğini kullanın.", + "sendFailed": "Notlar gönderilemedi", + "startingAgent": "Ajan başlatılıyor…", + "waitingForDesktop": "Masaüstü bekleniyor…" + }, + "useImageAttachment": { + "attachFailed": "Eklenemedi", + "disconnected": "Eklenemedi (bağlantı kesildi)", + "imageTooLarge": "Görsel eklenemeyecek kadar büyük", + "permissionDenied": "Fotoğraflara erişim izni reddedildi" + }, + "useMarkdownActions": { + "copyFailed": "Kopyalanamadı", + "copyPathFailed": "Yol kopyalanamadı", + "markdownTitle": "Markdown", + "pathCopied": "Yol kopyalandı", + "saveFailed": "Kaydedilemedi", + "saved": "Kaydedildi" + }, + "useNativeChatDictation": { + "insertFailed": "Dikte metni eklenemedi", + "insertFailedCopied": "Dikte metni eklenemediği için panoya kopyalandı", + "inserted": "Dikte metni eklendi" + }, + "useNativeChatStop": { + "stopNotSent": "Durdurma isteği gönderilemedi", + "stopUnconfirmed": "Durdurma doğrulanamadı — tekrar denemeden önce sohbeti kontrol edin", + "terminalNotReady": "Durdurma isteği gönderilemedi (terminal hazır değil)" + }, + "usePanelRouteActions": { + "checkHostHint": "Ana makine bağlantısını kontrol edin", + "copyNotesHint": "Bunun yerine notları kopyalayın", + "detectingAgents": "Ajanlar Algılanıyor", + "newSessionHint": "Yeni ajan oturumu", + "noEnabledAgents": "Etkin Ajan Yok", + "otherServerAgents": "Başka Bir Sunucudaki Ajanlar", + "pairServerHint": "Doğrudan o sunucuyla eşleştirin", + "presetsUnavailable": "Ajan Ön Ayarları Kullanılamıyor" + }, + "usePrAiTriage": { + "launchFailed": "Ajan başlatılamadı", + "waitingForDesktop": "Masaüstü uygulaması bekleniyor…" + }, + "usePrCommentActions": { + "actionFailed": "Yorum işlemi gerçekleştirilemedi" + }, + "usePrTitleAction": { + "disconnected": "Masaüstü bağlantısı yok.", + "updateFailed": "Başlık güncellenemedi." + }, + "useQuickCommands": { + "loadFailed": "Hızlı komutlar yüklenemedi", + "saveFailed": "Hızlı komut kaydedilemedi" + }, + "useSessionTerminalSendActions": { + "inputTooLarge": "Girdi çok büyük (en fazla 256 KiB)" + }, + "useStartup": { + "wakeAgents": "Uykudaki ajanları uyandırmak için ana makinede Orca'yı açın." + }, + "useTerminalCreateActions": { + "createFailed": "Terminal oluşturulamadı", + "forkBusy": "Başka bir terminal açılıyor. Bekleyip {{agentName}} sohbetini açmayı tekrar deneyin.", + "forkUnavailable": "Bağlı ve güncel bir ana makinede {{agentName}} sohbetini açıp tekrar deneyin.", + "inputLocked": "Terminal girişi başka bir istemci tarafından kilitlenmiş.", + "notesSent": "Notlar gönderildi", + "sendNotesError": "Notlar gönderilemedi", + "sendNotesFailed": "Notlar gönderilemedi", + "terminalTitle": "Terminal" + }, + "useTerminalInput": { + "clearFailed": "Terminal temizlenemedi", + "cleared": "Terminal temizlendi" + }, + "useTerminalPaste": { + "clipboardReadFailed": "Pano okunamadı", + "disconnected": "Yapıştırılamadı (bağlantı kesildi)", + "imageTooLarge": "Görsel yapıştırılamayacak kadar büyük", + "pasteFailed": "Yapıştırılamadı", + "pasteTooLarge": "Yapıştırılacak içerik çok büyük (en fazla 256 KiB)", + "terminalChanged": "Yapıştırma iptal edildi (terminal değişti)" + }, + "worktreeLabel": { + "fallback": "Worktree" + } + }, "settings": { "aboutScreen": { "openLinkFailed": "Bağlantı açılamadı. Yeniden deneyin.", @@ -1438,6 +2523,186 @@ "updateFailed": "Güncellenemedi" } }, + "sourceControl": { + "actions": { + "commits": "Commit'ler", + "createPr": "PR Oluştur", + "pushAndCreatePr": "Push Et ve PR Oluştur", + "switchBranch": "Branch'i değiştir" + }, + "commitDropdown": { + "checkOutBranchToPush": "Commit'leri push etmeden önce bir branch'i checkout edin", + "checkOutBranchToSync": "Commit'leri senkronize etmeden önce bir branch'i checkout edin", + "checkingBranchStatus": "Branch durumu kontrol ediliyor…", + "checkingPrStatus": "PR durumu kontrol ediliyor…", + "commit": "Commit", + "commitAndForcePush": "Stage edilen değişiklikleri commit'leyin ve lease korumasıyla force push edin", + "commitAndPush": "Stage edilen değişiklikleri commit'leyin ve push edin", + "commitAndTryPush": "Stage edilen değişiklikleri commit'leyin ve push etmeyi deneyin", + "commitForcePush": "Commit'le ve force push et", + "commitPush": "Commit'le ve push et", + "commitStagedChanges": "Stage edilmiş değişiklikleri commit'le", + "commitSync": "Commit'le ve Senkronize Et", + "commitThenPullPush": "Commit'leyin, ardından pull ve push yapın", + "enterMessage": "Commit etmek için bir commit mesajı girin", + "linkedReviewTargetUnavailable": "Bağlı incelemenin hedef branch'i kullanılamıyor", + "prAlreadyMerged": "PR zaten merge edilmiş", + "publishBranchFirstToPush": "Commit'leri push etmek için önce branch'i yayımlayın", + "publishBranchFirstToSync": "Commit'leri senkronize etmek için önce branch'i yayımlayın", + "resolveConflicts": "Commit etmeden önce conflict'leri çözün", + "stageFile": "Commit etmek için en az bir dosya stage edin", + "useCommitAndForcePush": "Commit ve force push seçeneğini kullanın — remote'da yalnızca yerel commit'lerin eski kopyaları var" + }, + "createPrAction": { + "createReview": "{{titleLabel}} oluştur" + }, + "dropdownCounts": { + "fastForwardCount_one": "{{count}} commit fast-forward et", + "fastForwardCount_other": "{{count}} commit fast-forward et", + "pullCount_one": "{{count}} commit pull et", + "pullCount_other": "{{count}} commit pull et", + "pushCount_one": "{{count}} commit push et", + "pushCount_other": "{{count}} commit push et", + "remoteBranchFallback": "remote branch", + "syncCounts": "{{behind}} pull et, {{ahead}} push et", + "titleForcePushOlderCopiesBranch": "Remote'ta yalnızca yerel commit'lerin eski kopyaları var. {{upstreamName}} hedefini güncellemek için bu branch'i lease korumasıyla force push edin.", + "titleForcePushOlderCopiesCount_one": "Remote'ta yalnızca yerel commit'lerin eski kopyaları var. {{upstreamName}} hedefini güncellemek için branch'teki {{count}} commit'i lease korumasıyla force push edin.", + "titleForcePushOlderCopiesCount_other": "Remote'ta yalnızca yerel commit'lerin eski kopyaları var. {{upstreamName}} hedefini güncellemek için branch'teki {{count}} commit'i lease korumasıyla force push edin.", + "titleManualForcePushReplaceRemote_one": "{{upstreamName}} hedefini güncellemek ve yalnızca remote'ta bulunan commit'lerin üzerine yazmak için {{count}} yerel commit'i lease korumasıyla force push edin.", + "titleManualForcePushReplaceRemote_other": "{{upstreamName}} hedefini güncellemek ve yalnızca remote'ta bulunan commit'lerin üzerine yazmak için {{count}} yerel commit'i lease korumasıyla force push edin.", + "titleManualForcePush_one": "{{upstreamName}} hedefini güncellemek için {{count}} yerel commit'i lease korumasıyla force push edin.", + "titleManualForcePush_other": "{{upstreamName}} hedefini güncellemek için {{count}} yerel commit'i lease korumasıyla force push edin.", + "titleUnpublishedForcePushBranch": "Bu branch'i lease korumasıyla force push edin ve gerekirse bir upstream ayarlayın.", + "titleUnpublishedForcePushCount_one": "Branch'teki {{count}} commit'i lease korumasıyla force push edin ve gerekirse bir upstream ayarlayın.", + "titleUnpublishedForcePushCount_other": "Branch'teki {{count}} commit'i lease korumasıyla force push edin ve gerekirse bir upstream ayarlayın." + }, + "hostedReview": { + "mrCopy": { + "reviewLabel": "merge request", + "shortLabel": "MR", + "titleLabel": "Merge Request" + }, + "prCopy": { + "reviewLabel": "pull request", + "shortLabel": "PR", + "titleLabel": "Pull Request" + } + }, + "hostedReviewCreate": { + "pushSucceeded": "Push tamamlandı, ancak {{shortLabel}} oluşturulamadı: {{error}}" + }, + "prCreate": { + "authRequired": "Bir {{reviewLabel}} oluşturmadan önce kimliğinizi doğrulayın.", + "baseNotOnRemote": "Bir {{reviewLabel}} oluşturmadan önce temel branch'i push edin.", + "createdNotRefreshed": "{{titleLabel}} oluşturuldu ancak Orca tarafından henüz yenilenemedi.", + "defaultBranch": "Bir {{reviewLabel}} oluşturmadan önce bir özellik branch'ine geçin.", + "detachedHead": "Bir {{reviewLabel}} oluşturmadan önce bir branch'e geçiş yapın (checkout).", + "dirty": "Bir {{reviewLabel}} oluşturmadan önce değişiklikleri commit'leyin.", + "existing": "{{titleLabel}} zaten açık.", + "existingNumber": "{{titleLabel}} #{{number}} zaten açık.", + "existingReview": "Bu branch için zaten bir {{reviewLabel}} var.", + "forkHeadUnsupported": "Bu fork'tan {{reviewLabel}} oluşturulması desteklenmiyor.", + "lookupUnconfirmed": "Bu branch için zaten bir {{reviewLabel}} olup olmadığı Orca tarafından doğrulanamadı. Birazdan yeniden deneyin.", + "needsSync": "Bir {{reviewLabel}} oluşturmadan önce bu branch'i senkronize edin.", + "noUpstream": "Bir {{reviewLabel}} oluşturmadan önce commit'leri yayımlayın.", + "notReady": "Bu branch henüz bir {{reviewLabel}} için hazır değil.", + "unsupportedMergeRequests": "Bu repo için merge request oluşturma desteklenmiyor.", + "unsupportedPullRequests": "Bu repo için pull request oluşturma desteklenmiyor." + }, + "primaryAction": { + "forcePushFromDesktop": "Bu pull request'in branch'ini masaüstünden force push edin" + }, + "remoteDropdown": { + "branchAlreadyPublished": "Branch zaten yayımlandı", + "branchUpToDate": "Branch güncel", + "checkOutBranchToFastForward": "Fast-forward etmeden önce bir branch'i checkout edin", + "checkOutBranchToForcePush": "Commit'leri force push etmeden önce bir branch'i checkout edin", + "checkOutBranchToPublish": "Commit'leri yayımlamadan önce bir branch'i checkout edin", + "checkOutBranchToPull": "Commit'leri pull etmeden önce bir branch'i checkout edin", + "checkOutBranchToPush": "Commit'leri push etmeden önce bir branch'i checkout edin", + "checkOutBranchToSync": "Commit'leri senkronize etmeden önce bir branch'i checkout edin", + "checkingBranchStatus": "Branch durumu kontrol ediliyor…", + "checkingPrStatus": "PR durumu kontrol ediliyor…", + "fastForward": "Fast-forward", + "fetch": "Fetch", + "fetchTitle": "Merge etmeden remote'tan fetch edin", + "forcePush": "Force Push", + "linkedReviewBranchExists": "Bağlı incelemenin branch'i zaten var", + "linkedReviewTargetUnavailable": "Bağlı incelemenin hedef branch'i kullanılamıyor", + "nothingNewToFastForward": "Fast-forward edilecek yeni bir şey yok — remote'ta yalnızca yerel commit'lerin eski kopyaları var", + "nothingNewToPull": "Pull edilecek yeni bir şey yok — remote'ta yalnızca yerel commit'lerin eski kopyaları var", + "nothingToFastForward": "Fast-forward edilecek bir şey yok", + "nothingToForcePush": "Force push edilecek bir şey yok", + "nothingToForcePushTo": "{{upstreamName}} hedefine force push edilecek bir şey yok", + "nothingToPull": "Pull edilecek bir şey yok", + "nothingToPush": "Push edilecek bir şey yok", + "nothingToPushTo": "{{upstreamName}} hedefine push edilecek bir şey yok", + "prAlreadyMerged": "PR zaten merge edilmiş", + "publishBranch": "Branch'i Yayımla", + "publishBranchFirstToFastForward": "Fast-forward etmek için önce branch'i yayımlayın", + "publishBranchFirstToPull": "Commit'leri pull etmek için önce branch'i yayımlayın", + "publishBranchFirstToSync": "Commit'leri senkronize etmek için önce branch'i yayımlayın", + "publishLinkedReview": "Bağlı İnceleme", + "publishNoBranch": "Branch Yok", + "publishPrStatus": "PR Durumu", + "publishToOrigin": "Bu branch'i origin'e yayımlayın", + "pull": "Pull", + "push": "Push", + "pushAndSetUpstream": "Bu branch'i push edin ve gerekirse bir upstream ayarlayın", + "pushMayNeedSync": "Yerel commit'leri push edin; git önce senkronizasyon gerektirebilir", + "pushToLinkedReview": "Güncellemeleri bağlı inceleme branch'ine push edin", + "rebaseChooseRemoteBase": "Rebase için remote'taki bir temel branch'i seçin", + "rebaseDirtyHint": "Rebase etmeyi deneyin; git önce yerel değişiklikleri commit'lemenizi veya stash etmenizi gerektirebilir", + "rebaseFromBase": "Temel Branch'ten Rebase Et", + "rebaseFromNamedBase": "{{baseRef}} üzerinden rebase et", + "rebaseWithLatestFrom": "Mevcut branch'i {{baseRef}} üzerindeki en son commit'lerle rebase edin", + "sync": "Senkronize Et", + "tryFastForwardPull": "Fast-forward pull işlemini deneyin; git yerel commit'leri reddedebilir", + "tryRegularPush": "Normal push işlemini deneyin; git force push gerektirebilir", + "useForcePushToSync": "Force Push işlemini kullanın — remote'ta yalnızca yerel commit'lerin eski kopyaları var" + } + }, + "terminal": { + "keyDefinitions": { + "accessoryArrowDown": "Aşağı Ok", + "accessoryArrowLeft": "Sol Ok", + "accessoryArrowRight": "Sağ Ok", + "accessoryArrowUp": "Yukarı Ok", + "arrowDown": "Aşağı ok", + "arrowLeft": "Sol ok", + "arrowRight": "Sağ ok", + "arrowUp": "Yukarı ok", + "clearLineBeforeCursor": "Satırın imleçten önceki kısmını temizle", + "clearScreen": "Ekranı temizle", + "deleteWordBackward": "Önceki sözcüğü sil", + "endOfLine": "Satır sonu", + "forwardDelete": "İleri sil", + "interruptTerminal": "Terminaldeki işlemi durdur", + "reverseSearch": "Geriye doğru ara", + "sendEof": "EOF gönder", + "startOfLine": "Satır başı", + "suspendProcess": "İşlemi askıya al" + }, + "webView": { + "documentStartFailed": "terminal belgesi başlatılamadı - {{message}}", + "httpError": "Terminal WebView HTTP hatası", + "loadFailed": "Terminal WebView yüklenemedi", + "renderProcessEnded": "Terminal WebView görüntüleme işlemi sona erdi" + }, + "webviewEngineErrorState": { + "code": "kod {{code}}", + "reload": "Yeniden yükle", + "rendererCrashed": "görüntüleyici çöktü", + "status": "durum {{statusCode}}", + "title": "Terminal yüklenemedi" + }, + "webviewNotificationDispatch": { + "unknownError": "Bilinmeyen terminal hatası" + }, + "webviewReadyWatchdog": { + "initializeFailed": "Terminal başlatılamadı - terminal görünümünden hazır sinyali alınamadı" + } + }, "webShell": { "shellStateScreen": { "checkingHost": "Sunucu kontrol ediliyor", @@ -1452,6 +2717,18 @@ "tryAgain": "Tekrar dene" } }, + "workspace": { + "branchChoice": { + "automaticName": "Otomatik ad", + "existing": "Mevcut dal", + "existingHint": "Yeni commitler bu dala eklenir.", + "hostUpdateRequired": "Bir branch'i yeniden kullanmak veya ayrı bir branch oluşturmayı seçmek için çalışma alanının bulunduğu bilgisayardaki Orca'yı güncelleyin.", + "new": "Yeni dal", + "newHint": "Yeni bir yerel dal oluştur", + "newName": "Yeni dal adı", + "title": "Bu çalışma ağacı için dal" + } + }, "worktree": { "agentRowDisplay": { "blocked": "Engellendi", @@ -1491,16 +2768,5 @@ "inReview": "İncelemede", "pinned": "Sabitlenenler" } - }, - "workspace": { - "branchChoice": { - "title": "Bu çalışma ağacı için dal", - "existing": "Mevcut dal", - "existingHint": "Yeni commitler bu dala eklenir.", - "new": "Yeni dal", - "newHint": "Yeni bir yerel dal oluştur", - "newName": "Yeni dal adı", - "automaticName": "Otomatik ad" - } } } diff --git a/mobile/src/i18n/locales/zh-Hant.json b/mobile/src/i18n/locales/zh-Hant.json index fc95994c6a4..320981d35a8 100644 --- a/mobile/src/i18n/locales/zh-Hant.json +++ b/mobile/src/i18n/locales/zh-Hant.json @@ -139,6 +139,363 @@ "title": "終端機" } }, + "browser": { + "addressField": { + "urlPlaceholder": "網址" + }, + "pane": { + "invalidUrl": "請輸入有效的網址。" + }, + "paneView": { + "dialogTitle": "瀏覽器對話方塊", + "forward": "前進", + "keyboardPlaceholder": "在網頁上輸入…", + "reload": "重新載入", + "sendText": "傳送文字至瀏覽器" + }, + "pointerModifiers": { + "clickModifier": "{{key}} 點擊修飾鍵" + }, + "useBrowserCommands": { + "deliveryUnconfirmed": "尚未確認是否送達,重試前請先檢查頁面", + "dialogAnswerFailed": "回覆未能送達網頁。", + "sent": "已傳送" + }, + "viewModeSwitch": { + "mobileView": "顯示行動版網站", + "webView": "顯示桌面版網站" + } + }, + "chat": { + "ask": { + "answerPlaceholder": "輸入你的回答", + "next": "下一步", + "other": "其他…", + "skip": "跳過", + "step": "第 {{step}} 步", + "submit": "送出" + }, + "askRow": { + "asked": "已提問:", + "awaiting": "等待使用者輸入:", + "awaitingUnnamed": "等待使用者輸入", + "questionCount": "{{value0}} 個問題" + }, + "composer": { + "addToMessage": "新增至訊息", + "attachImage": "附加圖片", + "cancelEditing": "取消編輯", + "placeholder": "隨心輸入,@ 引用檔案,/ 使用命令", + "saveEditedMessage": "儲存編輯後的訊息", + "sendMessage": "傳送訊息" + }, + "composerAttachments": { + "attachedImage": "已附加的圖片", + "file": "檔案", + "fileMeta": "{{type}} · {{size}}", + "removeFile": "移除 {{name}}", + "removeImage": "移除圖片", + "uploadingFile": "正在上傳 {{name}}" + }, + "composerPlaceholder": { + "prompt": "隨心輸入,@ 引用檔案,/ 使用命令", + "reconnecting": "正在重新連線…", + "waitingForTerminal": "等待終端機…" + }, + "diffCard": { + "copyDiff": "複製差異", + "diffGap": "部分行未顯示", + "diffTruncated": "差異已截斷" + }, + "hostStatus": { + "continuedOutside": "已在此聊天外繼續對話。", + "continuedOutsideRewound": "已在此聊天外繼續對話。上方較早的回覆已不在此對話的歷史記錄中。", + "historyItemTooLarge": "聊天的這一部分過大,無法顯示。", + "historyRepaired": "此聊天的部分歷史記錄無法載入。" + }, + "message": { + "copyMessage": "複製訊息" + }, + "messageActionsSheet": { + "clipboardRejected": "無法將文字複製到剪貼簿。", + "copyFailed": "複製失敗", + "copyMessage": "複製訊息", + "selectText": "選取文字" + }, + "nativeChatImageViewer": { + "closeImage": "關閉圖片" + }, + "nativeChatProse": { + "attachedImage": "附加的圖片", + "image": "圖片", + "openImageHint": "以原始大小開啟圖片" + }, + "nativeChatToolLine": { + "added": "新增了", + "copyCommand": "複製命令", + "deleted": "刪除了", + "durationHours": "{{hours}} 小時 {{minutes}} 分 {{seconds}} 秒", + "durationMilliseconds": "{{milliseconds}} 毫秒", + "durationMinutes": "{{minutes}} 分 {{seconds}} 秒", + "durationSeconds": "{{seconds}} 秒", + "edited": "編輯了", + "editing": "正在編輯", + "exitCode": "結束碼 {{exitCode}}", + "fetched": "取得了", + "fetching": "正在取得", + "listed": "列出了", + "listing": "正在列出", + "ran": "執行了", + "read": "讀取了", + "reading": "正在讀取", + "renamed": "重新命名了", + "result": "結果", + "running": "執行中", + "searched": "搜尋了", + "searchedWeb": "搜尋了網頁", + "searching": "正在搜尋", + "searchingWeb": "正在搜尋網頁", + "subagent": "子代理程式", + "triedEdit": "嘗試編輯", + "triedFetch": "嘗試取得", + "triedList": "嘗試列出", + "triedRead": "嘗試讀取", + "triedSearch": "嘗試搜尋", + "triedSearchWeb": "嘗試搜尋網頁" + }, + "nativeChatUserMessageFold": { + "loadFailed": "顯示完整訊息 — 無法載入,點一下重試", + "showFullMessage": "顯示完整訊息", + "showLess": "收合" + }, + "nativeChatVisual": { + "defaultTitle": "視覺化", + "openFullscreen": "以全螢幕開啟 {{title}}", + "retryHint": "嘗試重新載入", + "unavailable": "視覺化不可用" + }, + "notice": { + "compaction": "已壓縮上下文", + "contextCleared": "上下文已清除", + "plan": "計畫", + "stoppedManually": "已手動停止" + }, + "orcaStop": { + "canContinue": "你可以在此對話中繼續。", + "crash": "此回覆進行期間,{{machine}} 上的 Orca 意外停止。", + "quit": "此回覆進行期間,{{machine}} 上的 Orca 已關閉。", + "remote": "此回覆進行期間,{{machine}} 上的 Orca 已停止。", + "unnamedServer": "遠端伺服器", + "update": "此回覆進行期間,{{machine}} 上的 Orca 為更新而重新啟動。" + }, + "permission": { + "needsAccessTo": "需要存取: ", + "newerVersionRequired": "此請求需要更新版本的 Orca。", + "planFile": "計畫檔案:{{filePath}}", + "reason": "原因: " + }, + "promptCard": { + "answerInTerminal": "在終端機中回答", + "terminalExplanation": "請在終端機中回答這個 OMP 問題。" + }, + "promptStrip": { + "expand": "展開" + }, + "question": { + "otherReplyPlaceholder": "或輸入回覆…", + "replyPlaceholder": "輸入你的回覆…", + "sendReply": "傳送回覆", + "submit": "送出", + "submitCount": "送出({{count}})", + "submitSelectedOptions": "送出所選選項" + }, + "queuedMessages": { + "clearAll": "全部清除", + "deleteAll": "刪除所有排隊的訊息", + "editMessage": "編輯訊息", + "pausedState": "已暫停", + "queuedState": "已排隊", + "resume": "繼續", + "resumeSending": "繼續傳送排隊的訊息", + "title": "排隊的訊息" + }, + "receipt": { + "cancelled": "已取消", + "cancelledBy": "已在 {{device}} 取消", + "emptyAnswer": "回答內容為空", + "resolved": "已解決", + "resolver": "已在 {{device}} 回答", + "unavailable": "無法顯示所選的答案" + }, + "sessionOptionCopy": { + "availableAfterSessionStarts": "工作階段開始後可用。", + "chooseInAgentPicker": "在代理程式選擇器中選擇…", + "effort": "推理強度", + "fast": "快速", + "fastMode": "快速模式", + "high": "高", + "low": "低", + "max": "最大", + "medium": "中", + "mediumShort": "中", + "minimal": "最小", + "minimalShort": "最小", + "model": "模型", + "notSet": "未設定", + "off": "關閉", + "on": "開啟", + "permissions": "權限", + "sessionOptions": "工作階段選項", + "setWhenSessionStarts": "將於工作階段開始時設定。", + "speed": "速度", + "standard": "標準", + "thinking": "思考", + "toggleOption": "切換{{value0}}", + "ultra": "極致", + "xhigh": "極高", + "xhighShort": "極高" + }, + "sessionOptionPickers": { + "agentWorking": "代理程式正在執行", + "backToModels": "返回模型清單", + "closePicker": "關閉選擇器", + "dispatchUnconfirmed": "已傳送給代理程式 — 尚未確認", + "modelFullAccessLabel": "模型,{{value}},完整存取權", + "modelLabel": "模型,{{value}}", + "permissions": "權限", + "permissionsLabel": "權限,{{value}}", + "settingsLocked": "本回合結束後即可變更模型設定。", + "workingAnnouncement": "代理程式正在執行。本回合結束後即可變更模型設定。" + }, + "sessionOptionRows": { + "defaultMarker": "預設", + "notReportedMarker": "未回報" + }, + "sessionOptionSegments": { + "dispatchUnconfirmed": "已傳送給代理程式 — 尚未確認" + }, + "stopButton": { + "stopAgent": "停止代理程式", + "stoppingState": "正在停止…" + }, + "streamFrame": { + "streamEnded": "對話記錄串流已結束", + "streamFailed": "對話記錄串流失敗" + }, + "taskList": { + "added": "已新增 {{task}}", + "completed": "已完成", + "empty": "沒有任務", + "finished": "已完成 {{task}}", + "inProgress": "進行中", + "pending": "待處理", + "progress": "已完成 {{total}} 項任務中的 {{completed}} 項", + "removed": "已移除 {{task}}", + "reset": "已標記為待處理:{{task}}", + "showAll": "完整任務清單", + "started": "已開始 {{task}}", + "taskStatus": "{{status}}:{{task}}", + "title": "任務", + "unchanged": "任務未變更", + "updated": "已更新 {{task}}" + }, + "toolActivity": { + "count_one": "{{count}} 次工具呼叫", + "count_other": "{{count}} 次工具呼叫", + "failedCallsLabel": "工具呼叫失敗:{{value0}}", + "failedCount": "{{value0}} 失敗", + "interruptedCallsLabel": "已中斷的工具呼叫:{{value0}}", + "interruptedCount": "{{value0}} 已中斷", + "moreCalls": "還有 +{{value0}} 次", + "result": "結果", + "runningCommand": "正在執行命令", + "runningNamed": "正在執行 {{toolName}}", + "runningNamedPreview": "正在執行 {{toolName}} {{preview}}", + "runningPreview": "正在執行 {{preview}}" + }, + "toolRun": { + "list": "{{value0}},以及 {{value1}}", + "liveBrowserOnce": "正在使用瀏覽器", + "liveBrowserTimes_other": "正在使用瀏覽器({{count}} 次)", + "liveFileChange_one": "編輯 {{count}} 個檔案", + "liveFileChange_other": "編輯 {{count}} 個檔案", + "liveListFiles_one": "列出 {{count}} 個目錄", + "liveListFiles_other": "列出 {{count}} 個目錄", + "liveMcpToolCall_one": "使用 {{count}} 個整合服務", + "liveMcpToolCall_other": "使用 {{count}} 個整合服務", + "liveOther_one": "使用 {{count}} 個工具", + "liveOther_other": "使用 {{count}} 個工具", + "liveRead_one": "讀取 {{count}} 個檔案", + "liveRead_other": "讀取 {{count}} 個檔案", + "liveSearch_one": "搜尋 {{count}} 次", + "liveSearch_other": "搜尋 {{count}} 次", + "liveSubAgentActivity_one": "執行 {{count}} 個代理程式", + "liveSubAgentActivity_other": "執行 {{count}} 個代理程式", + "liveTodoListOnce": "更新計畫", + "liveTodoListTimes_other": "正在進行 {{count}} 次計畫更新", + "liveUnknown_one": "執行 {{count}} 個命令", + "liveUnknown_other": "執行 {{count}} 個命令", + "liveWebSearch_one": "搜尋網路 {{count}} 次", + "liveWebSearch_other": "搜尋網路 {{count}} 次", + "pair": "{{value0}} 和 {{value1}}", + "separator": "、 ", + "settledBrowserOnce": "已使用瀏覽器", + "settledBrowserTimes_other": "已使用瀏覽器 {{count}} 次", + "settledFileChange_one": "已編輯 {{count}} 個檔案", + "settledFileChange_other": "已編輯 {{count}} 個檔案", + "settledListFiles_one": "已列出 {{count}} 個目錄", + "settledListFiles_other": "已列出 {{count}} 個目錄", + "settledMcpToolCall_one": "已使用 {{count}} 個整合服務", + "settledMcpToolCall_other": "已使用 {{count}} 個整合服務", + "settledOther_one": "已使用 {{count}} 個工具", + "settledOther_other": "已使用 {{count}} 個工具", + "settledRead_one": "已讀取 {{count}} 個檔案", + "settledRead_other": "已讀取 {{count}} 個檔案", + "settledSearch_one": "已搜尋 {{count}} 次", + "settledSearch_other": "已搜尋 {{count}} 次", + "settledSubAgentActivity_one": "已執行 {{count}} 個代理程式", + "settledSubAgentActivity_other": "已執行 {{count}} 個代理程式", + "settledTodoListOnce": "已更新計畫", + "settledTodoListTimes_other": "已更新計畫 {{count}} 次", + "settledUnknown_one": "已執行 {{count}} 個命令", + "settledUnknown_other": "已執行 {{count}} 個命令", + "settledWebSearch_one": "已搜尋網路 {{count}} 次", + "settledWebSearch_other": "已搜尋網路 {{count}} 次" + }, + "turnStatus": { + "failedAfter": "{{value0}} 後失敗", + "hoursMinutesSeconds": "{{hours}} 小時 {{minutes}} 分 {{seconds}} 秒", + "interruptedAfter": "{{value0}} 後中斷", + "minutesSeconds": "{{minutes}} 分 {{seconds}} 秒", + "queueAfterStop": "將訊息加入佇列,在停止後執行", + "responding": "代理程式正在回覆", + "seconds_one": "{{count}} 秒", + "seconds_other": "{{count}} 秒", + "sendAfterStop": "傳送訊息,在停止後執行", + "stopping": "正在停止…", + "thinking": "思考", + "toggleDetails": "切換回合詳細資訊", + "workedFor": "工作時間 {{value0}}", + "working": "處理中…", + "workingFor": "已執行 {{value0}}" + }, + "usageLimit": { + "reached": "{{agent}} 已達用量上限。", + "reachedNamed": "{{agent}} 已達 {{window}} 上限。", + "resetsAt": "將於 {{when}} 重設。", + "theAgent": "代理程式", + "windowCredits": "額外用量", + "windowSession": "工作階段", + "windowWeekly": "每週", + "windowWeeklyModel": "模型每週" + }, + "useImageUpload": { + "attachFailed": "附加失敗", + "disconnected": "附加失敗(連線已中斷)", + "imageTooLarge": "圖片太大,無法附加", + "permissionDenied": "照片存取權限遭拒" + } + }, "common": { "back": "返回", "cancel": "取消", @@ -151,6 +508,8 @@ "done": "完成", "edit": "編輯", "ok": "確定", + "open": "開啟", + "refresh": "重新整理", "remove": "移除", "retry": "重試", "save": "儲存", @@ -757,8 +1116,51 @@ "unexpectedResponse": "非預期的回應" }, "webShellUpdateFailureCopy": { + "blockedReason": "已封鎖:{{reason}}。", + "blockedWorkspace": "已封鎖工作區。", + "failed": "已顯示失敗畫面。", "failedAgo": "來自 {{hostName}} 的上次更新於 {{time}} 前失敗:{{reason}}{{offered}}。{{outcome}}", - "failedJustNow": "來自 {{hostName}} 的上次更新剛剛失敗:{{reason}}{{offered}}。{{outcome}}" + "failedJustNow": "來自 {{hostName}} 的上次更新剛剛失敗:{{reason}}{{offered}}。{{outcome}}", + "hostCodeCopy": { + "assetChanged": "主機上的資源已變更", + "assetUnknown": "主機無法識別某個資源", + "buildChanged": "下載期間主機的建置版本已變更", + "offsetInvalid": "主機拒絕使用該位移量讀取", + "readLimited": "主機限制了同時讀取的數量", + "unavailable": "主機沒有工作區套件" + }, + "nativeRoute": "已顯示原生畫面。", + "offeredGeneration": " (世代 {{generation}})", + "openedCached": "已改用儲存的版本。", + "openedCachedGeneration": "已改用儲存的版本(世代 {{generation}})。", + "reasonCopy": { + "assetChecksumMismatch": "資源檢查碼不符", + "assetEntryChanged": "資源已與資訊清單不符", + "assetNoProgress": "資源讀取沒有進展", + "assetOverlong": "資源長度超過資訊清單宣告的長度", + "assetShort": "資源讀取結束時未達宣告的大小", + "buildChangedMidFetch": "下載期間主機的建置版本已變更", + "cacheWriteFailed": "無法將下載內容儲存至此手機", + "chunkMisrouted": "傳回的資料區塊對應到錯誤的資源或位移量", + "chunkOversize": "資料區塊大小超過主機允許的上限", + "connectionLost": "連線已中斷", + "fetchStopped": "下載已停止", + "hostRefused": "主機拒絕讀取", + "noConnection": "尚未連線至主機", + "rangeUndecodable": "無法解碼讀取到的壓縮資料", + "replyUnreadable": "此應用程式無法讀取主機的回覆", + "unrecognisedError": "無法識別的錯誤" + }, + "routeUnavailable": "已顯示此畫面無法使用的訊息。", + "waiting": "已等待主機。", + "wallCopy": { + "bundleShellTooOld": "此應用程式版本過舊,無法使用已儲存的套件", + "bundleTooOldForHost": "已儲存的套件版本過舊,無法與主機搭配使用", + "bundleUnavailable": "主機沒有工作區套件", + "hostTooOldForBundle": "主機版本過舊,無法使用已儲存的套件", + "pageRouteGrantMissing": "此畫面需要較新版的應用程式", + "pageRouteUnlisted": "主機的工作區未包含此畫面" + } } }, "dictation": { @@ -775,6 +1177,95 @@ "updateFailed": "無法更新聽寫設定" } }, + "files": { + "fileExplorerPanel": { + "backToSession": "返回工作階段", + "closeFiles": "關閉檔案", + "connecting": "正在連線至桌面版…", + "emptyState": "找不到檔案", + "loadFailed": "無法載入檔案", + "loadInterrupted": "資料夾載入中斷", + "title": "檔案", + "truncatedList": "{{worktreeLabel}} - 僅顯示前 5000 筆", + "waiting": "正在等待桌面版…" + }, + "fileExplorerRow": { + "fileUnavailable": "{{name}} 無法在行動版使用", + "loadFolderFailed": "無法載入資料夾", + "loading": "載入中…", + "openFolder": "開啟資料夾 {{name}}", + "previewFile": "預覽檔案 {{name}}", + "retryLoading": "重新嘗試載入 {{path}}", + "unavailable": "行動版無法使用" + }, + "fileMarkdownPreview": { + "copyLoadedSource": "複製已載入的 Markdown 原始碼", + "copySource": "複製 Markdown 原始碼", + "viewPreview": "檢視排版後的 Markdown 預覽", + "viewSource": "檢視 Markdown 原始碼" + }, + "fileMediaPreview": { + "downloading": "正在下載 {{bytes}},共 {{total}}", + "loadFailed": "無法載入媒體", + "loading": "正在載入媒體…", + "waiting": "正在等待桌面版…" + }, + "fileMutationOwnership": { + "sshOwnerChanged": "無法驗證 SSH 連線。請重新連線至主機後再試一次。" + }, + "filePreviewBody": { + "emptyFile": "空檔案", + "imageLabel": "{{title}} 圖片" + }, + "filePreviewEditableSource": { + "editorLabel": "{{title}} 編輯器" + }, + "filePreviewResponse": { + "binaryUnavailable": "無法預覽二進位檔案", + "bytes": "{{size}} B", + "fileChanged": "檔案已在電腦上變更。儲存前請重新載入預覽", + "fileNotFound": "找不到檔案", + "fileTooLarge": "檔案過大,無法在行動版預覽", + "filesystemUnreachable": "無法存取電腦的檔案系統", + "kilobytes": "{{size}} KB", + "loadFailed": "無法載入預覽", + "megabytes": "{{size}} MB", + "reloadBeforeSaving": "儲存前請重新載入預覽", + "unknownSize": "大小不明" + }, + "filePreviewScreen": { + "backToFiles": "返回檔案", + "chooseWorkspaceFile": "請選擇此工作區內的檔案。", + "discard": "放棄", + "discardMessage": "未儲存的編輯內容將會遺失。", + "discardTitle": "放棄變更?", + "fileTitle": "檔案", + "loadFailed": "無法載入預覽", + "loading": "正在載入預覽…", + "openFailed": "無法開啟檔案", + "saveArtifact": "儲存終端機產出檔案", + "saveFailed": "無法儲存檔案", + "stay": "留在此頁", + "title": "預覽", + "waiting": "正在等待桌面版…" + }, + "filePreviewSourceText": { + "previewLabel": "檔案預覽", + "truncatedNote": "預覽內容已截斷。檔案大小:{{size}}。" + }, + "markdownPreviewImage": { + "expandFallback": "放大圖片", + "expandImage": "放大 {{alt}}", + "imageLabel": "Markdown 圖片", + "loadFailed": "無法載入圖片" + }, + "pdfPreview": { + "open": "開啟 PDF", + "openFailed": "無法開啟此 PDF", + "opening": "正在開啟 PDF…", + "updateRequired": "請更新 Orca Mobile 以開啟 PDF" + } + }, "home": { "accountUsageCards": { "sectionHeading": "帳戶用量", @@ -1103,8 +1594,10 @@ "noComments": "還沒有留言。", "noDescription": "沒有提供描述。", "noHumanComments": "沒有人類留言。", - "resolvedComment_other": "{{author}} 的已解決留言({{count}})", - "resolvedThread_other": "{{author}} 的已解決討論串({{count}})", + "resolvedCommentCount": "{{author}} 的已解決留言({{count}})", + "resolvedCommentSingle": "{{author}} 的已解決留言", + "resolvedThreadCount": "{{author}} 的已解決討論串({{count}})", + "resolvedThreadSingle": "{{author}} 的已解決討論串", "showMore": "再顯示 {{count}} 則", "showMoreOf": "再顯示 {{count}} 則(剩餘 {{remaining}} 則)", "title": "留言" @@ -1296,6 +1789,596 @@ "title": "審查人" } }, + "session": { + "accessoryKeys": { + "addCustomShortcut": "新增自訂快速鍵", + "dismissKeyboard": "收起鍵盤", + "dismissKeyboardHint": "隱藏螢幕鍵盤,並保持目前的終端機工作階段開啟。", + "paste": "貼上", + "pasteFromClipboard": "從剪貼簿貼上", + "sendKey": "傳送 {{label}}", + "switchToBufferedInput": "切換到緩衝命令輸入模式", + "switchToDesktopMode": "切換至桌面模式", + "switchToLiveInput": "切換到終端機即時輸入模式", + "switchToPhoneMode": "切換到手機模式" + }, + "activeContent": { + "createTab": "建立分頁", + "creatingTab": "建立中…", + "fileTitle": "檔案", + "loadingTerminal": "正在載入終端機", + "noTabs": "此工作階段中沒有分頁", + "retryLoadingTerminal": "重試載入終端機", + "terminalTakingLonger": "終端機載入時間超出預期" + }, + "agentChildRow": { + "agent": "代理程式", + "days_one": "{{count}} 天", + "days_other": "{{count}} 天", + "ended": "已結束", + "hours_one": "{{count}} 小時", + "hours_other": "{{count}} 小時", + "minutes_one": "{{count}} 分鐘", + "minutes_other": "{{count}} 分鐘", + "noRecentUpdate": "暫無近期更新", + "noUpdate": "已有 {{value0}} 未更新" + }, + "agentMessageAttribution": { + "from": "來自 {{sender}}", + "fromMore": "來自 {{sender}} +{{count}}", + "fromUnnamed": "來自代理程式", + "messageFrom": "來自 {{sender}} 的訊息", + "messageFromMore": "來自 {{sender}} +{{count}} 的訊息", + "messageFromUnnamed": "來自代理程式的訊息", + "unnamedSender": "一個代理程式" + }, + "aiVaultResumeLaunch": { + "createFailed": "無法建立終端機", + "inputLocked": "終端機輸入已鎖定", + "sendFailed": "無法傳送恢復命令" + }, + "aiVaultResumePreparation": { + "prepareFailed": "無法準備此舊版 Codex 工作階段。請再次嘗試恢復。" + }, + "backgroundTask": { + "agent": "背景代理程式", + "command": "背景命令", + "countAgents_one": "{{count}} 個代理程式", + "countAgents_other": "{{count}} 個代理程式", + "countMonitors_one": "{{count}} 個監控項目", + "countMonitors_other": "{{count}} 個監控項目", + "countShellCommandOne": "1 個 Shell 命令", + "countShell_one": "{{count}} 個 Shell", + "countShell_other": "{{count}} 個 Shell", + "countTasks_one": "{{count}} 個任務", + "countTasks_other": "{{count}} 個任務", + "countWorkflows_one": "{{count}} 個工作流程", + "countWorkflows_other": "{{count}} 個工作流程", + "detailsUnavailable": "無法取得此工作階段的任務詳細資訊。", + "groupAgents": "代理程式", + "groupMonitors": "監控項目", + "groupShell": "Shell 命令", + "groupTasks": "任務", + "groupWorkflows": "工作流程", + "headerTotalCount_one": "{{count}} 個背景任務", + "headerTotalCount_other": "{{count}} 個背景任務", + "monitor": "背景監控", + "monitoring": "監控背景任務", + "reasonBlocked": "失敗", + "reasonWaiting": "需要核准", + "stateBlocked": "已受阻", + "stateBlockedCount": "受阻:{{value0}}", + "stateDone": "完成", + "stateDoneCount": "{{value0}} 完畢", + "stateIdle": "已停止", + "stateIdleCount": "{{value0}} 個已停止", + "stateMonitoring": "監控中", + "stateMonitoringCount": "{{value0}} 個監控中", + "stateUnverifiable": "無法取得狀態", + "stateUnverifiableCount": "無法取得狀態:{{value0}}", + "stateWaiting": "等待", + "stateWaitingCount": "等待中:{{value0}}", + "stateWorking": "工作中", + "stateWorkingCount": "{{value0}} 處理中", + "stop": "停止", + "stopAll": "停止背景任務", + "stopTask": "停止 {{value0}}", + "task": "背景任務", + "workflow": "背景工作流程" + }, + "browserTabActionSheet": { + "forward": "前進", + "reload": "重新載入", + "title": "瀏覽器" + }, + "bulkCloseSheetActions": { + "closeOtherTabs": "關閉其他分頁" + }, + "bulkTabClose": { + "confirm": "關閉分頁", + "description": "所選分頁將會關閉。已釘選的分頁會保持開啟。", + "titleEditors": "要關閉所有編輯器分頁嗎?", + "titleLeft": "要關閉左側的分頁嗎?", + "titleOthers": "要關閉其他分頁嗎?", + "titleRight": "要關閉右側的分頁嗎?" + }, + "commandDock": { + "commandPlaceholder": "輸入命令…", + "liveInputHint": "輸入的文字會直接傳送至目前使用中的終端機", + "liveInputLabel": "終端機即時輸入", + "sendCommand": "傳送命令" + }, + "contentRow": { + "dismissCreationWarning": "關閉工作區建立警告" + }, + "diffCommentLineRow": { + "addNote": "在第 {{line}} 行新增備註", + "deleteNote": "刪除第 {{line}} 行的備註", + "diffLine": "{{title}} 差異的第 {{line}} 行", + "line": "第 {{line}} 行", + "placeholder": "新增審查備註", + "saveNote": "儲存備註" + }, + "diffLineRow": { + "addNoteLabel": "在第 {{line}} 行新增備註", + "deleteNoteLabel": "刪除第 {{line}} 行的備註", + "diffLineLabel": "{{title}} 的差異,第 {{line}} 行", + "lineNumber": "第 {{line}} 行", + "notePlaceholder": "新增審查備註", + "saveNote": "儲存備註" + }, + "diffReviewLoaders": { + "changesLoadFailed": "無法載入變更", + "committedChangesFailed": "載入已提交的變更失敗", + "committedChangesUnavailable": "無法取得已提交的變更", + "committedDiffUnavailable": "無法取得已提交的差異", + "desktopUpdateRequired": "請更新 Orca 桌面版,以便在手機上審查變更。", + "diffLoadFailed": "無法載入差異", + "invalidSourceControlResponse": "原始碼控制回應無效", + "reviewNotesLoadFailed": "無法載入審查註記" + }, + "diffReviewQueue": { + "committedOnBranch": "已在分支上提交", + "staged": "已暫存", + "unstaged": "未暫存" + }, + "diffReviewScreenModel": { + "branch": "分支", + "staged": "已暫存", + "unstaged": "未暫存" + }, + "fileReader": { + "copyReviewNotes": "複製審查備註", + "imageLabel": "{{title}} 圖片", + "noReviewNotes": "尚無審查備註", + "previewLabel": "{{title}} 預覽", + "reviewNotes_one": "{{count}} 則審查備註", + "reviewNotes_other": "{{count}} 則審查備註", + "send": "傳送", + "sendReviewNotes": "將審查備註傳送給 AI" + }, + "floatingWorkspace": { + "title": "浮動工作區" + }, + "githubPrMutations": { + "reviewThreadUpdateFailed": "無法更新審查討論串。", + "titleUpdateFailed": "無法更新標題。" + }, + "githubPrRpc": { + "requestFailed": "請求失敗:{{method}}" + }, + "header": { + "backToWorktrees": "返回工作樹", + "moreActions": "更多工作階段操作", + "openFileExplorer": "開啟檔案總管", + "openSourceControl": "開啟原始碼控制", + "reconnectToDesktop": "重新連線至桌面版", + "showSidebar": "顯示側邊欄", + "terminalTitle": "終端機" + }, + "headerMoreActionsSheet": { + "agentHistory": "代理程式歷史紀錄", + "agentHistoryHint": "瀏覽並繼續代理程式工作階段", + "checks": "檢查項目", + "checksHint": "開啟 PR 檢查項目" + }, + "markdownReader": { + "changedOnDesktop": "已在電腦上變更", + "copyLoaded": "複製已載入內容", + "copyLoadedSource": "複製已載入的 Markdown 原始碼", + "copyPath": "複製路徑", + "copyPathLabel": "複製 Markdown 路徑", + "copySource": "複製 Markdown 原始碼", + "discard": "放棄", + "dismissKeyboard": "收起鍵盤", + "dismissKeyboardHint": "隱藏螢幕鍵盤,並保持 Markdown 編輯器開啟。", + "readOnly": "唯讀" + }, + "nativeChatAttachActions": { + "availableSkills": "{{count}} 項可用", + "commands": "命令", + "fileTypes": "PDF、文字、程式碼…", + "files": "檔案", + "photos": "照片", + "skills": "技能", + "workspaceFile": "工作區檔案" + }, + "nativeChatCardHeaderAction": { + "collapse": "收合" + }, + "nativeChatCliFork": { + "resumeInNewCli": "在新的 CLI 中繼續" + }, + "nativeChatFloatingControls": { + "scrollToLatest": "捲動至最新內容" + }, + "nativeChatFullContent": { + "loadMessageFailed": "顯示完整訊息:無法載入,點按以重試", + "loadOutputFailed": "顯示完整輸出:無法載入,點按以重試", + "partialMessage": "目前僅顯示此訊息的部分內容。", + "partialOutput": "已顯示 {{shown}},共 {{total}}。", + "showFullMessage": "顯示完整訊息", + "showFullOutput": "顯示完整輸出" + }, + "nativeChatRenderData": { + "defaultAgentLabel": "代理程式", + "emptySubtitle": "請 {{agent}} 檢查程式碼、解釋輸出內容,或進行修改。", + "emptyTitle": "開始與 {{agent}} 聊天", + "errorSubtitle": "無法讀取記錄。請切回終端機繼續工作。", + "errorTitle": "無法載入對話", + "notFoundSubtitle": "傳送訊息以開始對話,或開啟終端機檢視,查看 {{agent}} 正在做什麼。", + "notFoundTitle": "目前還找不到這段對話" + }, + "nativeChatToggleAction": { + "switchToChatView": "切換至聊天檢視", + "switchToTerminalView": "切換至終端機檢視" + }, + "newTabAgentHostLaunch": { + "notesNotSent": "代理程式已啟動,但備註未傳送。", + "notesSent": "備註已傳送", + "notesUnconfirmed": "代理程式已啟動,但無法確認備註是否已傳送。", + "promptUnconfirmed": "代理程式已啟動,但無法確認提示詞是否已傳送。" + }, + "prActionsEngine": { + "mergeFailed": "合併 PR 失敗。", + "refreshFailed": "重新整理 PR 失敗。" + }, + "prAiTriageLaunch": { + "agentStarted": "代理程式已啟動", + "agentStartedIn": "已在 {{label}} 啟動代理程式", + "emptyPrompt": "此動作儲存的提示詞為空白。請在電腦上更新「原始碼控制 AI」設定。", + "loadFailed": "無法載入可用的代理程式。", + "promptNotSent": "代理程式已啟動,但提示詞未傳送。", + "updateRequired": "請更新電腦上的 Orca,才能從手機啟動代理程式。" + }, + "prSidebarState": { + "loadFailed": "無法載入 PR" + }, + "queuedCardText": { + "showLess": "收合", + "showLessHint": "顯示較少訊息內容", + "showMore": "顯示更多", + "showMoreHint": "顯示完整訊息" + }, + "queuedMessageCardRow": { + "deleteHint": "刪除這則排隊中的訊息", + "images_one": "{{count}} 張圖片", + "images_other": "{{count}} 張圖片", + "moreActions": "更多操作", + "send": "傳送", + "sendAgainHint": "再次傳送此訊息", + "sendHint": "傳送此訊息", + "showLessHint": "顯示較少訊息內容", + "steer": "引導", + "steerHint": "提交且不中斷模型" + }, + "queuedMessageComposer": { + "commandsNotAllowed": "無法將指令儲存在排隊中的訊息內。", + "editing": "正在編輯訊息", + "editingAttributed": "正在編輯訊息 · {{attribution}}" + }, + "quickCommandEditorForm": { + "action": "操作", + "addQuickCommand": "新增快速命令", + "advanced": "進階", + "agent": "代理程式", + "agentPrompt": "代理程式提示詞", + "appendEnter": "追加 Enter", + "appendEnterDescription": "立即提交,而不僅僅是插入文字。", + "chooseAgent": "選擇代理程式", + "commandText": "命令文字", + "globalScope": "全域", + "label": "名稱", + "labelPlaceholder": "啟動開發伺服器", + "olderHostPromptTooLong": "對此主機來說過長。請在主機上更新 Orca 以儲存更長的提示詞。", + "projectScope": "專案", + "prompt": "提示詞", + "promptHint": "支援技能、檔案路徑及內建命令。", + "promptPlaceholder": "要求代理程式調查此工作區", + "promptSizeCharacters": "{{used}} / {{max}} 個字元", + "promptSizeKilobytes": "{{used}} KB / {{max}} KB", + "promptTooLong": "內容過長,無法儲存。", + "scope": "範圍", + "terminalCommand": "終端機命令" + }, + "quickCommandLaunch": { + "defaultLabel": "快速命令", + "editBeforeRunning": "執行前請先編輯此快速命令", + "runFailed": "無法執行 {{label}}" + }, + "quickCommandRow": { + "copyCommand": "複製 {{label}}", + "copyFailed": "無法複製", + "deleteCommand": "刪除 {{label}}", + "editCommand": "編輯 {{label}}", + "nothingToCopy": "無可複製內容", + "runCommand": "執行 {{label}}" + }, + "quickCommandsList": { + "emptyState": "尚無快速命令。", + "globalGroup": "全域", + "newCommand": "新增快速命令", + "noMatches": "找不到符合的快速命令。", + "projectGroup": "此專案", + "searchPlaceholder": "搜尋快速命令…" + }, + "quickCommandsSheet": { + "addTitle": "新增快速命令", + "chooseAgentTitle": "選擇代理程式", + "deleteConfirmMessage": "該快速命令將從您儲存的清單中刪除。", + "deleteConfirmTitle": "要刪除「{{label}}」嗎?", + "editTitle": "編輯快速命令", + "editorDescription": "儲存終端機命令或代理程式提示詞,方便快速取用。", + "title": "快速命令", + "untitled": "無題" + }, + "quickCommandsTabButton": { + "quickCommands": "快速命令" + }, + "routeHelpers": { + "connected": "已連線", + "connecting": "連線中", + "disconnected": "已中斷連線", + "pairingInvalid": "配對無效", + "reconnecting": "重新連線中", + "securing": "建立安全連線中" + }, + "routeScreen": { + "openingWorkspace": "正在開啟工作區" + }, + "sheets": { + "browser": "瀏覽器", + "browserUpdateRequired": "需更新桌面版,才能將瀏覽器畫面串流至手機", + "chat": "聊天", + "copyAllAndLeave": "全部複製並離開", + "copyDraftsFailed": "無法複製草稿", + "copyNotes": "複製備註", + "copyNotesFailed": "無法複製備註", + "copyPath": "複製路徑", + "discard": "放棄", + "discardAndLeave": "放棄並離開", + "discardChanges": "放棄變更", + "discardMessage": "要以桌面版的最新檔案取代手機草稿嗎?", + "draftsHint": "離開前請先複製或放棄手機草稿。", + "file": "檔案", + "markdown": "Markdown", + "markdownNote": "Markdown 筆記", + "newBrowser": "新瀏覽器", + "newTab": "新分頁", + "notesCopied": "已複製備註", + "notesHint": "選擇要接收目前備註的代理程式工作階段。", + "removeShortcutMessage": "要移除此自訂快速鍵嗎?", + "renameTerminal": "重新命名終端機", + "sendReviewNotes": "傳送審查備註", + "shortcut": "快速鍵", + "terminal": "終端機", + "terminalName": "終端機名稱", + "unsavedMarkdown": "尚未儲存的 Markdown 變更", + "urlPrompt": "請輸入網址,或留空以開啟新分頁。" + }, + "sideRail": { + "backToWorktrees": "返回工作樹" + }, + "sourceControlLaunchAgent": { + "noEnabledAgent": "此工作區主機上未偵測到已啟用的 AI 代理程式。", + "savedAgentUnavailable": "先前為此動作設定的代理程式無法在此工作區主機上使用。" + }, + "structuredComposerCommand": { + "operationUnconfirmed": "無法確認對話操作。", + "removeAttachments": "使用聊天工作階段命令前,請先移除附件。" + }, + "structuredSendDelivery": { + "messageNotSent": "訊息未傳送" + }, + "tabStrip": { + "checkingCapabilities": "正在檢查桌面版支援的功能,請稍後再試", + "newTab": "新分頁", + "quickCommandsUpdateRequired": "需更新桌面版才能使用快速命令", + "tabStatus": "{{title}},{{status}}" + }, + "terminalActionSheetActions": { + "clearTerminal": "清除終端機", + "rename": "重新命名", + "switchToDesktop": "切換為電腦模式", + "switchToPhone": "切換為手機模式" + }, + "terminalInputActions": { + "attachFileHint": "長按可改為附加檔案", + "attachPhoto": "附加照片", + "sendingImage": "正在傳送圖片", + "startDictation": "開始語音聽寫", + "startingDictation": "正在啟動語音聽寫", + "stopDictation": "停止語音聽寫", + "transcribingDictation": "正在轉錄語音聽寫內容" + }, + "terminalLiveInputStatus": { + "listening": "正在聆聽", + "liveInput": "即時輸入", + "preparingMic": "正在準備麥克風", + "processing": "處理中", + "releaseMicHint": "放開按鈕即可停止錄音", + "startingMic": "正在啟動麥克風", + "stopHint": "點一下麥克風即可停止", + "transcribing": "正在電腦上轉錄", + "uploadingImage": "正在將圖片上傳至主機", + "uploadingImageTitle": "正在上傳圖片" + }, + "terminalRecords": { + "defaultTitle": "終端機" + }, + "terminalTabAgent": { + "browser": "瀏覽器", + "chat": "聊天", + "file": "檔案", + "markdown": "Markdown", + "newBrowser": "新瀏覽器", + "terminal": "終端機" + }, + "terminalViewportResubscribe": { + "fitFailed": "無法將終端機調整為符合此螢幕的大小" + }, + "useAccessorySelection": { + "copyFailed": "無法複製", + "selectionCleared": "已清除選取範圍(已捲出緩衝區)" + }, + "useAttachmentInputLeaseGate": { + "reconnecting": "加入附件失敗(重新連線中)" + }, + "useContentCreateActions": { + "browserCommandFailed": "瀏覽器命令執行失敗", + "browserCreateFailed": "建立瀏覽器失敗", + "browserUnavailable": "瀏覽器頁面尚未就緒。", + "browserUpdateRequired": "需更新桌面版才能使用行動版瀏覽器串流", + "invalidUrl": "請輸入有效的 URL", + "noteCreateFailed": "建立 Markdown 筆記失敗", + "untitledNoteCreateFailed": "無法建立未命名的 Markdown 筆記" + }, + "useDiffComments": { + "copyNotesFailed": "無法複製備註", + "deleteNoteFailed": "刪除備註失敗", + "noteAdded": "已新增備註", + "notesCopied": "已複製備註", + "saveNoteFailed": "儲存備註失敗", + "saveNotesFailed": "儲存審查備註失敗", + "waitingForDesktop": "等待桌面版…" + }, + "useDiffReviewCommentActions": { + "saveFailed": "儲存審查失敗", + "saveStateFailed": "儲存審查狀態失敗", + "waitingForDesktop": "正在等待電腦…" + }, + "useDiffReviewController": { + "loadFailed": "無法載入審查", + "missingWorktree": "找不到工作樹", + "waitingForDesktop": "正在等待電腦…" + }, + "useDiffReviewDiffLoading": { + "loadFailed": "無法載入差異", + "waitingForDesktop": "正在等待電腦…" + }, + "useDiffReviewGitActions": { + "actionFailed": "原始碼控制操作失敗", + "reviewedFilesStaged_one": "已暫存 {{count}} 個已審查的檔案", + "reviewedFilesStaged_other": "已暫存 {{count}} 個已審查的檔案", + "stageResults": "已暫存 {{staged}} 個,{{failed}} 個失敗", + "waitingForDesktop": "正在等待電腦…" + }, + "useDiffReviewInteractions": { + "openFailed": "無法在工作階段中開啟" + }, + "useDiffReviewSendActions": { + "copyFailed": "無法複製審查備註", + "inputLocked": "終端機輸入已鎖定", + "loadSessionsFailed": "無法載入代理程式工作階段", + "notesCopied": "已複製審查備註", + "notesSent": "審查備註已傳送", + "notesSentWithWarning": "審查備註已傳送。{{warning}}", + "promptNotSent": "代理程式已啟動,但備註未傳送。請使用「複製備註」複製後再貼上。", + "sendFailed": "傳送備註失敗", + "startingAgent": "正在啟動代理程式…", + "waitingForDesktop": "正在等待電腦…" + }, + "useImageAttachment": { + "attachFailed": "附加失敗", + "disconnected": "加入附件失敗(已中斷連線)", + "imageTooLarge": "圖片太大,無法附加", + "permissionDenied": "照片存取權限遭拒" + }, + "useMarkdownActions": { + "copyFailed": "無法複製", + "copyPathFailed": "無法複製路徑", + "markdownTitle": "Markdown", + "pathCopied": "已複製路徑", + "saveFailed": "儲存失敗", + "saved": "已儲存" + }, + "useNativeChatDictation": { + "insertFailed": "無法插入聽寫內容", + "insertFailedCopied": "無法插入聽寫內容,已複製到剪貼簿", + "inserted": "已插入聽寫內容" + }, + "useNativeChatStop": { + "stopNotSent": "未送出停止指令", + "stopUnconfirmed": "尚未確認是否已停止,請先查看聊天再重試", + "terminalNotReady": "未送出停止指令(終端機尚未就緒)" + }, + "usePanelRouteActions": { + "checkHostHint": "請檢查主機連線", + "copyNotesHint": "改為複製備註", + "detectingAgents": "正在偵測代理程式", + "newSessionHint": "新增代理程式工作階段", + "noEnabledAgents": "沒有已啟用的代理程式", + "otherServerAgents": "其他伺服器上的代理程式", + "pairServerHint": "請直接與該伺服器配對", + "presetsUnavailable": "代理程式預設無法使用" + }, + "usePrAiTriage": { + "launchFailed": "啟動代理程式失敗", + "waitingForDesktop": "等待桌面版…" + }, + "usePrCommentActions": { + "actionFailed": "留言操作失敗" + }, + "usePrTitleAction": { + "disconnected": "尚未連線至桌面版。", + "updateFailed": "更新標題失敗。" + }, + "useQuickCommands": { + "loadFailed": "載入快速命令失敗", + "saveFailed": "儲存快速命令失敗" + }, + "useSessionTerminalSendActions": { + "inputTooLarge": "輸入內容太大(上限為 256 KiB)" + }, + "useStartup": { + "wakeAgents": "請在主機上開啟 Orca,以喚醒休眠中的代理程式。" + }, + "useTerminalCreateActions": { + "createFailed": "建立終端機失敗", + "forkBusy": "正在開啟另一個終端機。請稍候,再試著開啟 {{agentName}} 聊天。", + "forkUnavailable": "請在已連線且已更新的主機上開啟 {{agentName}} 聊天,然後重試。", + "inputLocked": "終端機輸入已被另一個用戶端鎖定。", + "notesSent": "已傳送備註", + "sendNotesError": "無法傳送備註", + "sendNotesFailed": "傳送備註失敗", + "terminalTitle": "終端機" + }, + "useTerminalInput": { + "clearFailed": "無法清除終端機", + "cleared": "已清除終端機" + }, + "useTerminalPaste": { + "clipboardReadFailed": "無法讀取剪貼簿", + "disconnected": "貼上失敗(連線已中斷)", + "imageTooLarge": "圖片太大,無法貼上", + "pasteFailed": "貼上失敗", + "pasteTooLarge": "貼上內容太大(上限為 256 KiB)", + "terminalChanged": "已取消貼上(終端機已變更)" + }, + "worktreeLabel": { + "fallback": "工作樹" + } + }, "settings": { "aboutScreen": { "openLinkFailed": "無法開啟連結,請再試一次。", @@ -1436,6 +2519,186 @@ "updateFailed": "無法更新" } }, + "sourceControl": { + "actions": { + "commits": "提交", + "createPr": "建立 PR", + "pushAndCreatePr": "推送並建立 PR", + "switchBranch": "切換分支" + }, + "commitDropdown": { + "checkOutBranchToPush": "推送提交前請先檢出分支", + "checkOutBranchToSync": "同步提交前請先檢出分支", + "checkingBranchStatus": "正在檢查分支狀態…", + "checkingPrStatus": "正在檢查 PR 狀態…", + "commit": "提交", + "commitAndForcePush": "提交已暫存的變更,並使用租約強制推送", + "commitAndPush": "提交已暫存的變更並推送", + "commitAndTryPush": "提交已暫存的變更並嘗試推送", + "commitForcePush": "提交並強制推送", + "commitPush": "提交並推送", + "commitStagedChanges": "提交已暫存的變更", + "commitSync": "提交並同步", + "commitThenPullPush": "先提交,再拉取並推送", + "enterMessage": "輸入提交訊息以進行提交", + "linkedReviewTargetUnavailable": "已連結的審查分支無法使用", + "prAlreadyMerged": "PR 已合併", + "publishBranchFirstToPush": "請先發布分支再推送提交", + "publishBranchFirstToSync": "請先發布分支再同步提交", + "resolveConflicts": "提交前解決衝突", + "stageFile": "暫存至少一個要提交的檔案", + "useCommitAndForcePush": "使用「提交並強制推送」;遠端只有本機提交的舊版本" + }, + "createPrAction": { + "createReview": "建立 {{titleLabel}}" + }, + "dropdownCounts": { + "fastForwardCount_one": "快轉 {{count}} 筆提交", + "fastForwardCount_other": "快轉 {{count}} 筆提交", + "pullCount_one": "拉取 {{count}} 筆提交", + "pullCount_other": "拉取 {{count}} 筆提交", + "pushCount_one": "推送 {{count}} 筆提交", + "pushCount_other": "推送 {{count}} 筆提交", + "remoteBranchFallback": "遠端分支", + "syncCounts": "拉取 {{behind}} 筆,推送 {{ahead}} 筆", + "titleForcePushOlderCopiesBranch": "遠端只有本機提交的舊版本。使用租約強制推送此分支,以更新 {{upstreamName}}。", + "titleForcePushOlderCopiesCount_one": "遠端只有本機提交的舊版本。使用租約強制推送此分支的 {{count}} 筆提交,以更新 {{upstreamName}}。", + "titleForcePushOlderCopiesCount_other": "遠端只有本機提交的舊版本。使用租約強制推送此分支的 {{count}} 筆提交,以更新 {{upstreamName}}。", + "titleManualForcePushReplaceRemote_one": "使用租約強制推送 {{count}} 筆本機提交,以更新 {{upstreamName}} 並取代僅存在於遠端的提交。", + "titleManualForcePushReplaceRemote_other": "使用租約強制推送 {{count}} 筆本機提交,以更新 {{upstreamName}} 並取代僅存在於遠端的提交。", + "titleManualForcePush_one": "使用租約強制推送 {{count}} 筆本機提交,以更新 {{upstreamName}}。", + "titleManualForcePush_other": "使用租約強制推送 {{count}} 筆本機提交,以更新 {{upstreamName}}。", + "titleUnpublishedForcePushBranch": "使用租約強制推送此分支,並視需要設定上游分支。", + "titleUnpublishedForcePushCount_one": "使用租約強制推送此分支的 {{count}} 筆提交,並視需要設定上游分支。", + "titleUnpublishedForcePushCount_other": "使用租約強制推送此分支的 {{count}} 筆提交,並視需要設定上游分支。" + }, + "hostedReview": { + "mrCopy": { + "reviewLabel": "合併請求", + "shortLabel": "MR", + "titleLabel": "合併請求" + }, + "prCopy": { + "reviewLabel": "PR", + "shortLabel": "PR", + "titleLabel": "PR" + } + }, + "hostedReviewCreate": { + "pushSucceeded": "推送成功,但建立 {{shortLabel}} 失敗:{{error}}" + }, + "prCreate": { + "authRequired": "請在建立 {{reviewLabel}} 之前完成身分驗證。", + "baseNotOnRemote": "建立 {{reviewLabel}} 前,請先推送基準分支。", + "createdNotRefreshed": "{{titleLabel}} 已建立,但 Orca 尚無法重新整理其資訊。", + "defaultBranch": "請在建立 {{reviewLabel}} 之前切換到功能分支。", + "detachedHead": "請在建立 {{reviewLabel}} 之前檢出分支。", + "dirty": "請在建立 {{reviewLabel}} 之前提交變更。", + "existing": "{{titleLabel}} 已開啟。", + "existingNumber": "{{titleLabel}} #{{number}} 已開啟。", + "existingReview": "此分支已有 {{reviewLabel}}。", + "forkHeadUnsupported": "不支援從此分叉存放庫建立 {{reviewLabel}}。", + "lookupUnconfirmed": "Orca 無法確認此分支是否已有 {{reviewLabel}}。請稍後再試。", + "needsSync": "請在建立 {{reviewLabel}} 之前同步此分支。", + "noUpstream": "請在建立 {{reviewLabel}} 之前發布提交。", + "notReady": "此分支尚未準備好建立 {{reviewLabel}}。", + "unsupportedMergeRequests": "此存放庫不支援建立合併請求。", + "unsupportedPullRequests": "此存放庫不支援建立 PR。" + }, + "primaryAction": { + "forcePushFromDesktop": "從桌面版強制推送此 PR 的分支" + }, + "remoteDropdown": { + "branchAlreadyPublished": "分支已發布", + "branchUpToDate": "分支已是最新狀態", + "checkOutBranchToFastForward": "快轉前請先檢出分支", + "checkOutBranchToForcePush": "強制推送提交前請先檢出分支", + "checkOutBranchToPublish": "發布提交前請先檢出分支", + "checkOutBranchToPull": "拉取提交前請先檢出分支", + "checkOutBranchToPush": "推送提交前請先檢出分支", + "checkOutBranchToSync": "同步提交前請先檢出分支", + "checkingBranchStatus": "正在檢查分支狀態…", + "checkingPrStatus": "正在檢查 PR 狀態…", + "fastForward": "快轉", + "fetch": "擷取", + "fetchTitle": "從遠端擷取但不合併", + "forcePush": "強制推送", + "linkedReviewBranchExists": "已連結的審查分支已存在", + "linkedReviewTargetUnavailable": "已連結的審查分支無法使用", + "nothingNewToFastForward": "沒有可快轉的新內容;遠端只有本機提交的舊版本", + "nothingNewToPull": "沒有可拉取的新內容;遠端只有本機提交的舊版本", + "nothingToFastForward": "沒有可快轉的內容", + "nothingToForcePush": "沒有可強制推送的內容", + "nothingToForcePushTo": "沒有可強制推送至 {{upstreamName}} 的內容", + "nothingToPull": "沒有可拉取的內容", + "nothingToPush": "沒有可推送的內容", + "nothingToPushTo": "沒有可推送至 {{upstreamName}} 的內容", + "prAlreadyMerged": "PR 已合併", + "publishBranch": "發布分支", + "publishBranchFirstToFastForward": "請先發布分支再快轉", + "publishBranchFirstToPull": "請先發布分支再拉取提交", + "publishBranchFirstToSync": "請先發布分支再同步提交", + "publishLinkedReview": "已連結的審查", + "publishNoBranch": "無分支", + "publishPrStatus": "PR 狀態", + "publishToOrigin": "將此分支發布到 origin", + "pull": "拉取", + "push": "推送", + "pushAndSetUpstream": "推送此分支,並視需要設定上游分支", + "pushMayNeedSync": "推送本機提交;git 可能要求先同步", + "pushToLinkedReview": "將更新推送到已關聯的審查分支", + "rebaseChooseRemoteBase": "選擇要作為變基基準的遠端分支", + "rebaseDirtyHint": "嘗試變基;git 可能要求先提交或儲藏本機變更", + "rebaseFromBase": "以基準分支進行變基", + "rebaseFromNamedBase": "以 {{baseRef}} 為基準進行變基", + "rebaseWithLatestFrom": "使用 {{baseRef}} 的最新提交,對目前分支進行變基", + "sync": "同步", + "tryFastForwardPull": "嘗試以快轉方式拉取;git 可能因本機提交而拒絕拉取", + "tryRegularPush": "嘗試一般推送;git 可能要求強制推送", + "useForcePushToSync": "使用「強制推送」;遠端只有本機提交的舊版本" + } + }, + "terminal": { + "keyDefinitions": { + "accessoryArrowDown": "向下鍵", + "accessoryArrowLeft": "向左鍵", + "accessoryArrowRight": "向右鍵", + "accessoryArrowUp": "向上鍵", + "arrowDown": "向下鍵", + "arrowLeft": "向左鍵", + "arrowRight": "向右鍵", + "arrowUp": "向上鍵", + "clearLineBeforeCursor": "清除本行游標前的內容", + "clearScreen": "清除畫面", + "deleteWordBackward": "向後刪除單字", + "endOfLine": "行尾", + "forwardDelete": "向前刪除", + "interruptTerminal": "中斷終端機", + "reverseSearch": "反向搜尋", + "sendEof": "傳送 EOF", + "startOfLine": "行首", + "suspendProcess": "暫停行程" + }, + "webView": { + "documentStartFailed": "終端機文件啟動失敗:{{message}}", + "httpError": "終端機 WebView HTTP 錯誤", + "loadFailed": "終端機 WebView 載入失敗", + "renderProcessEnded": "終端機 WebView 算繪行程已結束" + }, + "webviewEngineErrorState": { + "code": "錯誤碼 {{code}}", + "reload": "重新載入", + "rendererCrashed": "算繪器已當機", + "status": "狀態碼 {{statusCode}}", + "title": "終端機載入失敗" + }, + "webviewNotificationDispatch": { + "unknownError": "未知的終端機錯誤" + }, + "webviewReadyWatchdog": { + "initializeFailed": "終端機未能初始化:未收到終端機檢視的就緒訊號" + } + }, "webShell": { "shellStateScreen": { "checkingHost": "正在檢查主機", @@ -1450,6 +2713,18 @@ "tryAgain": "重試" } }, + "workspace": { + "branchChoice": { + "automaticName": "自動命名", + "existing": "現有分支", + "existingHint": "新提交會加入此分支。", + "hostUpdateRequired": "請更新工作區主機上的 Orca,才能重複使用分支或明確選擇建立獨立分支。", + "new": "新分支", + "newHint": "建立新的本機分支", + "newName": "新分支名稱", + "title": "此工作樹的分支" + } + }, "worktree": { "agentRowDisplay": { "blocked": "已阻塞", @@ -1489,16 +2764,5 @@ "inReview": "審查中", "pinned": "已固定" } - }, - "workspace": { - "branchChoice": { - "title": "此工作樹的分支", - "existing": "現有分支", - "existingHint": "新提交會加入此分支。", - "new": "新分支", - "newHint": "建立新的本機分支", - "newName": "新分支名稱", - "automaticName": "自動命名" - } } } diff --git a/mobile/src/i18n/locales/zh.json b/mobile/src/i18n/locales/zh.json index 49e8d28adab..5c9719c1e87 100644 --- a/mobile/src/i18n/locales/zh.json +++ b/mobile/src/i18n/locales/zh.json @@ -139,6 +139,363 @@ "title": "终端" } }, + "browser": { + "addressField": { + "urlPlaceholder": "URL" + }, + "pane": { + "invalidUrl": "请输入有效的 URL。" + }, + "paneView": { + "dialogTitle": "浏览器对话框", + "forward": "前进", + "keyboardPlaceholder": "在网页中输入…", + "reload": "重新加载", + "sendText": "将文本发送到浏览器" + }, + "pointerModifiers": { + "clickModifier": "{{key}} 点击修饰键" + }, + "useBrowserCommands": { + "deliveryUnconfirmed": "尚未确认是否送达,请先检查页面再重试", + "dialogAnswerFailed": "未能将回答发送到网页。", + "sent": "已发送" + }, + "viewModeSwitch": { + "mobileView": "显示移动版网站", + "webView": "显示电脑版网站" + } + }, + "chat": { + "ask": { + "answerPlaceholder": "输入你的回答", + "next": "下一步", + "other": "其他…", + "skip": "跳过", + "step": "第 {{step}} 步", + "submit": "提交" + }, + "askRow": { + "asked": "问:", + "awaiting": "等待用户输入:", + "awaitingUnnamed": "等待用户输入", + "questionCount": "{{value0}} 问题" + }, + "composer": { + "addToMessage": "添加到消息", + "attachImage": "添加图片", + "cancelEditing": "取消编辑", + "placeholder": "随心输入,@ 引用文件,/ 使用命令", + "saveEditedMessage": "保存编辑后的消息", + "sendMessage": "发送消息" + }, + "composerAttachments": { + "attachedImage": "已添加的图片", + "file": "文件", + "fileMeta": "{{type}} · {{size}}", + "removeFile": "移除 {{name}}", + "removeImage": "移除图片", + "uploadingFile": "正在上传 {{name}}" + }, + "composerPlaceholder": { + "prompt": "随心输入,@ 引用文件,/ 使用命令", + "reconnecting": "正在重新连接…", + "waitingForTerminal": "等待终端…" + }, + "diffCard": { + "copyDiff": "复制差异", + "diffGap": "部分行未显示", + "diffTruncated": "差异被截断" + }, + "hostStatus": { + "continuedOutside": "已在此聊天之外继续。", + "continuedOutsideRewound": "已在此聊天之外继续。上方较早的回复已不在此对话的历史记录中。", + "historyItemTooLarge": "聊天的这一部分过大,无法显示。", + "historyRepaired": "此聊天的部分历史记录无法加载。" + }, + "message": { + "copyMessage": "复制消息" + }, + "messageActionsSheet": { + "clipboardRejected": "剪贴板拒绝接收该文本。", + "copyFailed": "复制失败", + "copyMessage": "复制消息", + "selectText": "选择文本" + }, + "nativeChatImageViewer": { + "closeImage": "关闭图片" + }, + "nativeChatProse": { + "attachedImage": "附件图片", + "image": "图片", + "openImageHint": "以原尺寸打开图片" + }, + "nativeChatToolLine": { + "added": "添加了", + "copyCommand": "复制命令", + "deleted": "删除了", + "durationHours": "{{hours}} 小时 {{minutes}} 分 {{seconds}} 秒", + "durationMilliseconds": "{{milliseconds}} 毫秒", + "durationMinutes": "{{minutes}} 分 {{seconds}} 秒", + "durationSeconds": "{{seconds}} 秒", + "edited": "编辑了", + "editing": "正在编辑", + "exitCode": "退出码 {{exitCode}}", + "fetched": "获取了", + "fetching": "正在获取", + "listed": "列出了", + "listing": "正在列出", + "ran": "运行了", + "read": "读取了", + "reading": "正在读取", + "renamed": "重命名了", + "result": "结果", + "running": "运行中", + "searched": "搜索了", + "searchedWeb": "搜索了网页", + "searching": "正在搜索", + "searchingWeb": "正在搜索网页", + "subagent": "子智能体", + "triedEdit": "尝试编辑", + "triedFetch": "尝试获取", + "triedList": "尝试列出", + "triedRead": "尝试读取", + "triedSearch": "尝试搜索", + "triedSearchWeb": "尝试搜索网页" + }, + "nativeChatUserMessageFold": { + "loadFailed": "显示完整消息——加载失败,点按重试", + "showFullMessage": "显示完整消息", + "showLess": "收起" + }, + "nativeChatVisual": { + "defaultTitle": "可视化", + "openFullscreen": "全屏打开 {{title}}", + "retryHint": "尝试重新加载", + "unavailable": "可视化不可用" + }, + "notice": { + "compaction": "上下文压缩", + "contextCleared": "上下文已清除", + "plan": "计划", + "stoppedManually": "已手动停止" + }, + "orcaStop": { + "canContinue": "你可以在此对话中继续。", + "crash": "此回复进行期间,{{machine}} 上的 Orca 意外停止。", + "quit": "此回复进行期间,{{machine}} 上的 Orca 已关闭。", + "remote": "此回复进行期间,{{machine}} 上的 Orca 已停止。", + "unnamedServer": "远程服务器", + "update": "此回复进行期间,{{machine}} 上的 Orca 为更新而重新启动。" + }, + "permission": { + "needsAccessTo": "需要访问: ", + "newerVersionRequired": "此请求需要更新版本的 Orca。", + "planFile": "计划文件:{{filePath}}", + "reason": "原因: " + }, + "promptCard": { + "answerInTerminal": "在终端中回答", + "terminalExplanation": "在终端中回答这个 OMP 问题。" + }, + "promptStrip": { + "expand": "展开" + }, + "question": { + "otherReplyPlaceholder": "或输入回复…", + "replyPlaceholder": "输入回复…", + "sendReply": "发送回复", + "submit": "提交", + "submitCount": "提交({{count}})", + "submitSelectedOptions": "提交所选选项" + }, + "queuedMessages": { + "clearAll": "全部清除", + "deleteAll": "删除所有排队的消息", + "editMessage": "编辑消息", + "pausedState": "已暂停", + "queuedState": "已排队", + "resume": "继续", + "resumeSending": "继续发送排队的消息", + "title": "排队的消息" + }, + "receipt": { + "cancelled": "取消", + "cancelledBy": "于 {{device}} 取消", + "emptyAnswer": "空回答", + "resolved": "已解决", + "resolver": "回复于 {{device}}", + "unavailable": "所选答案不可用" + }, + "sessionOptionCopy": { + "availableAfterSessionStarts": "会话开始后可用。", + "chooseInAgentPicker": "在智能体选择器中选择…", + "effort": "推理强度", + "fast": "快速", + "fastMode": "快速模式", + "high": "高", + "low": "低", + "max": "最大", + "medium": "中", + "mediumShort": "中", + "minimal": "最小", + "minimalShort": "最小", + "model": "模型", + "notSet": "未设置", + "off": "关闭", + "on": "开启", + "permissions": "权限", + "sessionOptions": "会话选项", + "setWhenSessionStarts": "请在会话开始时设置。", + "speed": "速度", + "standard": "标准", + "thinking": "思考", + "toggleOption": "切换{{value0}}", + "ultra": "极致", + "xhigh": "极高", + "xhighShort": "极高" + }, + "sessionOptionPickers": { + "agentWorking": "智能体正在工作", + "backToModels": "返回模型列表", + "closePicker": "关闭选择器", + "dispatchUnconfirmed": "已发送给智能体 — 尚未确认", + "modelFullAccessLabel": "模型,{{value}},完全访问权限", + "modelLabel": "模型,{{value}}", + "permissions": "权限", + "permissionsLabel": "权限,{{value}}", + "settingsLocked": "本轮结束后将解锁模型设置。", + "workingAnnouncement": "智能体正在工作。本轮结束后将解锁模型设置。" + }, + "sessionOptionRows": { + "defaultMarker": "默认", + "notReportedMarker": "未上报" + }, + "sessionOptionSegments": { + "dispatchUnconfirmed": "已发送给智能体 — 尚未确认" + }, + "stopButton": { + "stopAgent": "停止智能体", + "stoppingState": "正在停止…" + }, + "streamFrame": { + "streamEnded": "聊天记录流传输已结束", + "streamFailed": "聊天记录流传输失败" + }, + "taskList": { + "added": "添加了 {{task}}", + "completed": "已完成", + "empty": "没有任务", + "finished": "已完成 {{task}}", + "inProgress": "进行中", + "pending": "待办的", + "progress": "已完成 {{completed}} 个任务(共 {{total}} 个)", + "removed": "删除了 {{task}}", + "reset": "标记为待处理:{{task}}", + "showAll": "完整任务清单", + "started": "开始{{task}}", + "taskStatus": "{{status}}:{{task}}", + "title": "任务", + "unchanged": "任务不变", + "updated": "已更新 {{task}}" + }, + "toolActivity": { + "count_one": "{{count}} 次工具调用", + "count_other": "{{count}} 次工具调用", + "failedCallsLabel": "失败的工具调用:{{value0}}", + "failedCount": "失败 {{value0}} 个", + "interruptedCallsLabel": "已中断的工具调用:{{value0}}", + "interruptedCount": "{{value0}} 已中断", + "moreCalls": "还有 +{{value0}} 个", + "result": "结果", + "runningCommand": "正在运行命令", + "runningNamed": "正在运行 {{toolName}}", + "runningNamedPreview": "正在运行 {{toolName}} {{preview}}", + "runningPreview": "正在运行 {{preview}}" + }, + "toolRun": { + "list": "{{value0}}并{{value1}}", + "liveBrowserOnce": "正在使用浏览器", + "liveBrowserTimes_other": "正在使用浏览器({{count}} 次)", + "liveFileChange_one": "正在编辑 {{count}} 个文件", + "liveFileChange_other": "正在编辑 {{count}} 个文件", + "liveListFiles_one": "正在列出 {{count}} 个目录", + "liveListFiles_other": "正在列出 {{count}} 个目录", + "liveMcpToolCall_one": "正在使用 {{count}} 项集成", + "liveMcpToolCall_other": "正在使用 {{count}} 项集成", + "liveOther_one": "正在使用 {{count}} 个工具", + "liveOther_other": "正在使用 {{count}} 个工具", + "liveRead_one": "正在读取 {{count}} 个文件", + "liveRead_other": "正在读取 {{count}} 个文件", + "liveSearch_one": "正在搜索({{count}} 次)", + "liveSearch_other": "正在搜索({{count}} 次)", + "liveSubAgentActivity_one": "正在运行 {{count}} 个智能体", + "liveSubAgentActivity_other": "正在运行 {{count}} 个智能体", + "liveTodoListOnce": "更新计划", + "liveTodoListTimes_other": "正在进行 {{count}} 次计划更新", + "liveUnknown_one": "正在运行 {{count}} 条命令", + "liveUnknown_other": "正在运行 {{count}} 条命令", + "liveWebSearch_one": "正在搜索网页({{count}} 次)", + "liveWebSearch_other": "正在搜索网页({{count}} 次)", + "pair": "{{value0}}并{{value1}}", + "separator": "、 ", + "settledBrowserOnce": "使用了浏览器", + "settledBrowserTimes_other": "已使用浏览器 {{count}} 次", + "settledFileChange_one": "已编辑 {{count}} 个文件", + "settledFileChange_other": "已编辑 {{count}} 个文件", + "settledListFiles_one": "已列出 {{count}} 个目录", + "settledListFiles_other": "已列出 {{count}} 个目录", + "settledMcpToolCall_one": "已使用 {{count}} 项集成", + "settledMcpToolCall_other": "已使用 {{count}} 项集成", + "settledOther_one": "已使用 {{count}} 个工具", + "settledOther_other": "已使用 {{count}} 个工具", + "settledRead_one": "已读取 {{count}} 个文件", + "settledRead_other": "已读取 {{count}} 个文件", + "settledSearch_one": "已搜索 {{count}} 次", + "settledSearch_other": "已搜索 {{count}} 次", + "settledSubAgentActivity_one": "已运行 {{count}} 个智能体", + "settledSubAgentActivity_other": "已运行 {{count}} 个智能体", + "settledTodoListOnce": "更新了计划", + "settledTodoListTimes_other": "已更新计划 {{count}} 次", + "settledUnknown_one": "已运行 {{count}} 条命令", + "settledUnknown_other": "已运行 {{count}} 条命令", + "settledWebSearch_one": "已搜索网页 {{count}} 次", + "settledWebSearch_other": "已搜索网页 {{count}} 次" + }, + "turnStatus": { + "failedAfter": "{{value0}} 后失败", + "hoursMinutesSeconds": "{{hours}} 小时 {{minutes}} 分 {{seconds}} 秒", + "interruptedAfter": "{{value0}} 后中断", + "minutesSeconds": "{{minutes}} 分 {{seconds}} 秒", + "queueAfterStop": "将消息加入队列,在停止后运行", + "responding": "智能体正在回复", + "seconds_one": "{{count}} 秒", + "seconds_other": "{{count}} 秒", + "sendAfterStop": "发送消息,在停止后运行", + "stopping": "正在停止…", + "thinking": "思考中", + "toggleDetails": "切换回合详情", + "workedFor": "工作了 {{value0}}", + "working": "处理中…", + "workingFor": "已工作 {{value0}}" + }, + "usageLimit": { + "reached": "{{agent}} 已达到使用上限。", + "reachedNamed": "{{agent}} 已达到{{window}}上限。", + "resetsAt": "将在 {{when}} 重置。", + "theAgent": "智能体", + "windowCredits": "额外用量", + "windowSession": "会话", + "windowWeekly": "每周", + "windowWeeklyModel": "模型每周" + }, + "useImageUpload": { + "attachFailed": "添加失败", + "disconnected": "添加失败(连接已断开)", + "imageTooLarge": "图片过大,无法添加", + "permissionDenied": "照片访问权限被拒绝" + } + }, "common": { "back": "返回", "cancel": "取消", @@ -151,6 +508,8 @@ "done": "完成", "edit": "编辑", "ok": "确定", + "open": "打开", + "refresh": "刷新", "remove": "移除", "retry": "重试", "save": "保存", @@ -757,8 +1116,51 @@ "unexpectedResponse": "响应异常" }, "webShellUpdateFailureCopy": { + "blockedReason": "已阻止:{{reason}}。", + "blockedWorkspace": "已阻止打开工作区。", + "failed": "已显示失败界面。", "failedAgo": "来自 {{hostName}} 的上次更新于 {{time}} 前失败:{{reason}}{{offered}}。{{outcome}}", - "failedJustNow": "来自 {{hostName}} 的上次更新刚刚失败:{{reason}}{{offered}}。{{outcome}}" + "failedJustNow": "来自 {{hostName}} 的上次更新刚刚失败:{{reason}}{{offered}}。{{outcome}}", + "hostCodeCopy": { + "assetChanged": "主机上的资源发生了变化", + "assetUnknown": "主机未能识别某个资源", + "buildChanged": "下载期间主机的构建版本发生了变化", + "offsetInvalid": "主机拒绝了指定偏移量的读取请求", + "readLimited": "主机限制了并发读取数量", + "unavailable": "主机上没有工作区资源包" + }, + "nativeRoute": "已显示原生界面。", + "offeredGeneration": " (构建标识 {{generation}})", + "openedCached": "已回退到保存的版本。", + "openedCachedGeneration": "已回退到保存的版本(构建标识 {{generation}})。", + "reasonCopy": { + "assetChecksumMismatch": "资源校验和不匹配", + "assetEntryChanged": "资源与清单不再匹配", + "assetNoProgress": "资源读取没有进展", + "assetOverlong": "资源长度超出清单中声明的长度", + "assetShort": "资源实际大小小于声明的大小", + "buildChangedMidFetch": "下载期间主机的构建版本发生了变化", + "cacheWriteFailed": "无法将下载内容保存到此手机", + "chunkMisrouted": "数据块对应的资源或偏移量不正确", + "chunkOversize": "数据块大小超出主机允许的上限", + "connectionLost": "连接已断开", + "fetchStopped": "下载已停止", + "hostRefused": "主机拒绝了读取请求", + "noConnection": "未连接到主机", + "rangeUndecodable": "无法解码读取到的压缩数据", + "replyUnreadable": "此应用无法解析主机返回的响应", + "unrecognisedError": "无法识别的错误" + }, + "routeUnavailable": "已显示界面不可用的提示。", + "waiting": "已进入等待主机状态。", + "wallCopy": { + "bundleShellTooOld": "此应用版本过旧,无法使用保存的资源包", + "bundleTooOldForHost": "保存的资源包版本过旧,无法用于此主机", + "bundleUnavailable": "主机上没有工作区资源包", + "hostTooOldForBundle": "主机版本过旧,无法使用保存的资源包", + "pageRouteGrantMissing": "此界面需要更新版本的应用", + "pageRouteUnlisted": "主机的工作区不包含此界面" + } } }, "dictation": { @@ -775,6 +1177,95 @@ "updateFailed": "更新听写设置失败" } }, + "files": { + "fileExplorerPanel": { + "backToSession": "返回会话", + "closeFiles": "关闭文件", + "connecting": "正在连接桌面…", + "emptyState": "未找到文件", + "loadFailed": "无法加载文件", + "loadInterrupted": "文件夹加载中断", + "title": "文件", + "truncatedList": "{{worktreeLabel}} - 仅显示前 5000 项", + "waiting": "等待桌面…" + }, + "fileExplorerRow": { + "fileUnavailable": "{{name}} 在移动端不可用", + "loadFolderFailed": "无法加载文件夹", + "loading": "加载中…", + "openFolder": "打开文件夹 {{name}}", + "previewFile": "预览文件 {{name}}", + "retryLoading": "重试加载 {{path}}", + "unavailable": "移动端不可用" + }, + "fileMarkdownPreview": { + "copyLoadedSource": "复制已加载的 Markdown 源码", + "copySource": "复制 Markdown 源码", + "viewPreview": "查看渲染后的 Markdown 预览", + "viewSource": "查看 Markdown 源码" + }, + "fileMediaPreview": { + "downloading": "正在下载:{{bytes}} / {{total}}", + "loadFailed": "无法加载媒体", + "loading": "正在加载媒体…", + "waiting": "等待桌面…" + }, + "fileMutationOwnership": { + "sshOwnerChanged": "无法验证 SSH 连接。请重新连接主机后重试。" + }, + "filePreviewBody": { + "emptyFile": "空文件", + "imageLabel": "{{title}} 图像" + }, + "filePreviewEditableSource": { + "editorLabel": "{{title}} 编辑器" + }, + "filePreviewResponse": { + "binaryUnavailable": "无法预览二进制文件", + "bytes": "{{size}} B", + "fileChanged": "文件已在桌面上更改。请重新加载预览后再保存", + "fileNotFound": "未找到文件", + "fileTooLarge": "文件过大,无法在移动端预览", + "filesystemUnreachable": "无法访问桌面的文件系统", + "kilobytes": "{{size}} KB", + "loadFailed": "无法加载预览", + "megabytes": "{{size}} MB", + "reloadBeforeSaving": "请重新加载预览后再保存", + "unknownSize": "大小未知" + }, + "filePreviewScreen": { + "backToFiles": "返回文件列表", + "chooseWorkspaceFile": "请选择此工作区内的文件。", + "discard": "放弃", + "discardMessage": "未保存的更改将丢失。", + "discardTitle": "放弃更改?", + "fileTitle": "文件", + "loadFailed": "无法加载预览", + "loading": "正在加载预览…", + "openFailed": "无法打开文件", + "saveArtifact": "保存终端工件", + "saveFailed": "无法保存文件", + "stay": "留在此页", + "title": "预览", + "waiting": "等待桌面…" + }, + "filePreviewSourceText": { + "previewLabel": "文件预览", + "truncatedNote": "预览已截断。文件大小:{{size}}。" + }, + "markdownPreviewImage": { + "expandFallback": "放大图片", + "expandImage": "放大 {{alt}}", + "imageLabel": "Markdown 图片", + "loadFailed": "无法加载图片" + }, + "pdfPreview": { + "open": "打开 PDF", + "openFailed": "无法打开此 PDF", + "opening": "正在打开 PDF…", + "updateRequired": "更新 Orca Mobile 才能打开 PDF" + } + }, "home": { "accountUsageCards": { "sectionHeading": "账户使用情况", @@ -1103,8 +1594,10 @@ "noComments": "还没有评论。", "noDescription": "没有提供描述。", "noHumanComments": "没有人类评论。", - "resolvedComment_other": "{{author}} 的已解决评论({{count}})", - "resolvedThread_other": "{{author}} 的已解决讨论串({{count}})", + "resolvedCommentCount": "{{author}} 的已解决评论({{count}})", + "resolvedCommentSingle": "{{author}} 的已解决评论", + "resolvedThreadCount": "{{author}} 的已解决讨论串({{count}})", + "resolvedThreadSingle": "{{author}} 的已解决讨论串", "showMore": "再显示 {{count}} 条", "showMoreOf": "再显示 {{count}} 条(剩余 {{remaining}} 条)", "title": "评论" @@ -1296,6 +1789,596 @@ "title": "评审人" } }, + "session": { + "accessoryKeys": { + "addCustomShortcut": "添加自定义快捷键", + "dismissKeyboard": "关闭键盘", + "dismissKeyboardHint": "隐藏软键盘,并保持当前终端会话打开。", + "paste": "粘贴", + "pasteFromClipboard": "从剪贴板粘贴", + "sendKey": "发送 {{label}}", + "switchToBufferedInput": "切换到缓冲命令输入", + "switchToDesktopMode": "切换到桌面模式", + "switchToLiveInput": "切换到实时终端输入", + "switchToPhoneMode": "切换到手机模式" + }, + "activeContent": { + "createTab": "创建标签页", + "creatingTab": "创建中…", + "fileTitle": "文件", + "loadingTerminal": "正在加载终端", + "noTabs": "此会话中没有标签页", + "retryLoadingTerminal": "重试加载终端", + "terminalTakingLonger": "终端加载时间超出预期" + }, + "agentChildRow": { + "agent": "智能体", + "days_one": "{{count}} 天", + "days_other": "{{count}} 天", + "ended": "已结束", + "hours_one": "{{count}} 小时", + "hours_other": "{{count}} 小时", + "minutes_one": "{{count}} 分钟", + "minutes_other": "{{count}} 分钟", + "noRecentUpdate": "暂无近期更新", + "noUpdate": "已有 {{value0}} 未更新" + }, + "agentMessageAttribution": { + "from": "来自 {{sender}}", + "fromMore": "来自 {{sender}} 及其他 {{count}} 个智能体", + "fromUnnamed": "来自智能体", + "messageFrom": "来自 {{sender}} 的消息", + "messageFromMore": "来自 {{sender}} 及其他 {{count}} 个智能体的消息", + "messageFromUnnamed": "来自智能体的消息", + "unnamedSender": "一个智能体" + }, + "aiVaultResumeLaunch": { + "createFailed": "创建终端失败", + "inputLocked": "终端输入已锁定", + "sendFailed": "发送恢复命令失败" + }, + "aiVaultResumePreparation": { + "prepareFailed": "无法准备此旧版 Codex 会话。请重试恢复。" + }, + "backgroundTask": { + "agent": "后台智能体", + "command": "后台命令", + "countAgents_one": "{{count}} 个智能体", + "countAgents_other": "{{count}} 个智能体", + "countMonitors_one": "{{count}} 个监控", + "countMonitors_other": "{{count}} 个监控", + "countShellCommandOne": "1 个 shell 命令", + "countShell_one": "{{count}} 个 shell", + "countShell_other": "{{count}} 个 shell", + "countTasks_one": "{{count}} 个任务", + "countTasks_other": "{{count}} 个任务", + "countWorkflows_one": "{{count}} 个工作流程", + "countWorkflows_other": "{{count}} 个工作流程", + "detailsUnavailable": "此会话的任务详细信息不可用。", + "groupAgents": "智能体", + "groupMonitors": "监控", + "groupShell": "Shell", + "groupTasks": "任务", + "groupWorkflows": "工作流程", + "headerTotalCount_one": "{{count}} 个后台任务", + "headerTotalCount_other": "{{count}} 个后台任务", + "monitor": "后台监控", + "monitoring": "监控后台任务", + "reasonBlocked": "失败", + "reasonWaiting": "需要批准", + "stateBlocked": "被阻止", + "stateBlockedCount": "{{value0}} 被阻止", + "stateDone": "完毕", + "stateDoneCount": "{{value0}} 完毕", + "stateIdle": "已停止", + "stateIdleCount": "{{value0}} 已停止", + "stateMonitoring": "监控", + "stateMonitoringCount": "{{value0}} 监控", + "stateUnverifiable": "状态不可用", + "stateUnverifiableCount": "{{value0}} 状态不可用", + "stateWaiting": "等待", + "stateWaitingCount": "{{value0}} 等待", + "stateWorking": "处理中", + "stateWorkingCount": "{{value0}} 处理中", + "stop": "停止", + "stopAll": "停止后台任务", + "stopTask": "停止{{value0}}", + "task": "后台任务", + "workflow": "后台工作流程" + }, + "browserTabActionSheet": { + "forward": "前进", + "reload": "重新加载", + "title": "浏览器" + }, + "bulkCloseSheetActions": { + "closeOtherTabs": "关闭其他标签页" + }, + "bulkTabClose": { + "confirm": "关闭标签页", + "description": "选中的标签页将关闭。固定的标签页将保持打开。", + "titleEditors": "关闭所有编辑器标签页?", + "titleLeft": "关闭左侧标签页?", + "titleOthers": "关闭其他标签页?", + "titleRight": "关闭右侧标签页?" + }, + "commandDock": { + "commandPlaceholder": "输入命令…", + "liveInputHint": "输入的文本会直接发送到当前终端", + "liveInputLabel": "终端实时输入", + "sendCommand": "发送命令" + }, + "contentRow": { + "dismissCreationWarning": "关闭工作区创建警告" + }, + "diffCommentLineRow": { + "addNote": "在第 {{line}} 行添加注释", + "deleteNote": "删除第 {{line}} 行的注释", + "diffLine": "{{title}} 的差异,第 {{line}} 行", + "line": "第 {{line}} 行", + "placeholder": "添加评论注释", + "saveNote": "保存注释" + }, + "diffLineRow": { + "addNoteLabel": "在第 {{line}} 行添加注释", + "deleteNoteLabel": "删除第 {{line}} 行的注释", + "diffLineLabel": "{{title}} 的差异,第 {{line}} 行", + "lineNumber": "第 {{line}} 行", + "notePlaceholder": "添加评论注释", + "saveNote": "保存注释" + }, + "diffReviewLoaders": { + "changesLoadFailed": "无法加载更改", + "committedChangesFailed": "加载已提交的更改失败", + "committedChangesUnavailable": "已提交的更改不可用", + "committedDiffUnavailable": "已提交的差异不可用", + "desktopUpdateRequired": "请更新 Orca 桌面端,以便在手机上评审更改。", + "diffLoadFailed": "无法加载差异", + "invalidSourceControlResponse": "源代码控制响应无效", + "reviewNotesLoadFailed": "无法加载评审笔记" + }, + "diffReviewQueue": { + "committedOnBranch": "已在分支上提交", + "staged": "已暂存", + "unstaged": "未暂存" + }, + "diffReviewScreenModel": { + "branch": "分支", + "staged": "已暂存", + "unstaged": "未暂存" + }, + "fileReader": { + "copyReviewNotes": "复制评论注释", + "imageLabel": "{{title}} 图片", + "noReviewNotes": "暂无评论注释", + "previewLabel": "{{title}} 预览", + "reviewNotes_one": "{{count}} 条评论注释", + "reviewNotes_other": "{{count}} 条评论注释", + "send": "发送", + "sendReviewNotes": "将评论注释发送给 AI" + }, + "floatingWorkspace": { + "title": "浮动工作区" + }, + "githubPrMutations": { + "reviewThreadUpdateFailed": "更新评审讨论串失败。", + "titleUpdateFailed": "更新标题失败。" + }, + "githubPrRpc": { + "requestFailed": "请求失败:{{method}}" + }, + "header": { + "backToWorktrees": "返回工作树", + "moreActions": "更多会话操作", + "openFileExplorer": "打开文件资源管理器", + "openSourceControl": "打开源代码控制", + "reconnectToDesktop": "重新连接到桌面", + "showSidebar": "显示侧边栏", + "terminalTitle": "终端" + }, + "headerMoreActionsSheet": { + "agentHistory": "智能体历史记录", + "agentHistoryHint": "浏览并恢复智能体会话", + "checks": "检查项", + "checksHint": "打开 PR 检查项" + }, + "markdownReader": { + "changedOnDesktop": "已在桌面上更改", + "copyLoaded": "复制已加载内容", + "copyLoadedSource": "复制已加载的 Markdown 源码", + "copyPath": "复制路径", + "copyPathLabel": "复制 Markdown 文件路径", + "copySource": "复制 Markdown 源码", + "discard": "放弃", + "dismissKeyboard": "关闭键盘", + "dismissKeyboardHint": "隐藏软键盘,并保持 Markdown 编辑器打开。", + "readOnly": "只读" + }, + "nativeChatAttachActions": { + "availableSkills": "{{count}} 个可用", + "commands": "命令", + "fileTypes": "PDF、文本、代码…", + "files": "文件", + "photos": "照片", + "skills": "技能", + "workspaceFile": "工作区文件" + }, + "nativeChatCardHeaderAction": { + "collapse": "收起" + }, + "nativeChatCliFork": { + "resumeInNewCli": "在新 CLI 中恢复" + }, + "nativeChatFloatingControls": { + "scrollToLatest": "滚动到最新消息" + }, + "nativeChatFullContent": { + "loadMessageFailed": "显示完整消息:加载失败,点按重试", + "loadOutputFailed": "显示完整输出:加载失败,点按重试", + "partialMessage": "仅显示此消息的部分内容。", + "partialOutput": "正在显示 {{total}} 中的 {{shown}}。", + "showFullMessage": "显示完整消息", + "showFullOutput": "显示完整输出" + }, + "nativeChatRenderData": { + "defaultAgentLabel": "智能体", + "emptySubtitle": "让 {{agent}} 检查代码、解释输出或进行更改。", + "emptyTitle": "与 {{agent}} 开始聊天", + "errorSubtitle": "无法读取记录。请切回终端继续工作。", + "errorTitle": "无法加载对话", + "notFoundSubtitle": "发送消息以开始对话,或打开终端视图,查看 {{agent}} 正在做什么。", + "notFoundTitle": "暂未找到此对话" + }, + "nativeChatToggleAction": { + "switchToChatView": "切换到聊天视图", + "switchToTerminalView": "切换到终端视图" + }, + "newTabAgentHostLaunch": { + "notesNotSent": "智能体已启动,但笔记未发送。", + "notesSent": "笔记已发送", + "notesUnconfirmed": "智能体已启动,但无法确认笔记是否已发送。", + "promptUnconfirmed": "智能体已启动,但无法确认提示词是否已发送。" + }, + "prActionsEngine": { + "mergeFailed": "合并 PR 失败。", + "refreshFailed": "刷新 PR 失败。" + }, + "prAiTriageLaunch": { + "agentStarted": "智能体已启动", + "agentStartedIn": "智能体已在 {{label}} 中启动", + "emptyPrompt": "此操作保存的提示词为空。请在电脑上更新“源代码控制 AI”设置。", + "loadFailed": "无法加载可用的智能体。", + "promptNotSent": "智能体已启动,但提示词未发送。", + "updateRequired": "请更新电脑上的 Orca,以便从手机启动智能体。" + }, + "prSidebarState": { + "loadFailed": "无法加载 PR" + }, + "queuedCardText": { + "showLess": "收起", + "showLessHint": "收起消息的部分内容", + "showMore": "显示更多", + "showMoreHint": "显示完整消息" + }, + "queuedMessageCardRow": { + "deleteHint": "删除队列中的这条消息", + "images_one": "{{count}} 张图片", + "images_other": "{{count}} 张图片", + "moreActions": "更多操作", + "send": "发送", + "sendAgainHint": "再次发送此消息", + "sendHint": "发送此消息", + "showLessHint": "收起消息的部分内容", + "steer": "立即发送", + "steerHint": "提交且不中断模型" + }, + "queuedMessageComposer": { + "commandsNotAllowed": "排队消息中不能保存命令。", + "editing": "正在编辑消息", + "editingAttributed": "正在编辑消息 · {{attribution}}" + }, + "quickCommandEditorForm": { + "action": "操作", + "addQuickCommand": "添加快捷命令", + "advanced": "高级", + "agent": "智能体", + "agentPrompt": "智能体提示", + "appendEnter": "追加 Enter", + "appendEnterDescription": "立即提交,而不仅仅是插入文本。", + "chooseAgent": "选择智能体", + "commandText": "命令文本", + "globalScope": "全局", + "label": "名称", + "labelPlaceholder": "启动开发服务器", + "olderHostPromptTooLong": "对此主机来说过长。请在主机上更新 Orca 以保存更长的提示词。", + "projectScope": "项目", + "prompt": "提示词", + "promptHint": "支持技能、文件路径和内置命令。", + "promptPlaceholder": "要求智能体调查此工作区", + "promptSizeCharacters": "{{used}} / {{max}} 个字符", + "promptSizeKilobytes": "{{used}} KB / {{max}} KB", + "promptTooLong": "内容过长,无法保存。", + "scope": "范围", + "terminalCommand": "终端命令" + }, + "quickCommandLaunch": { + "defaultLabel": "快捷命令", + "editBeforeRunning": "请先编辑此快捷命令再运行", + "runFailed": "无法运行 {{label}}" + }, + "quickCommandRow": { + "copyCommand": "复制 {{label}}", + "copyFailed": "无法复制", + "deleteCommand": "删除 {{label}}", + "editCommand": "编辑 {{label}}", + "nothingToCopy": "无可复制内容", + "runCommand": "运行 {{label}}" + }, + "quickCommandsList": { + "emptyState": "暂无快捷命令。", + "globalGroup": "全局", + "newCommand": "新建快捷命令", + "noMatches": "没有匹配的快捷命令。", + "projectGroup": "此项目", + "searchPlaceholder": "搜索快捷命令…" + }, + "quickCommandsSheet": { + "addTitle": "添加快捷命令", + "chooseAgentTitle": "选择智能体", + "deleteConfirmMessage": "该快捷命令将从您保存的列表中删除。", + "deleteConfirmTitle": "删除“{{label}}”?", + "editTitle": "编辑快捷命令", + "editorDescription": "保存终端命令或智能体提示词,便于快速调用。", + "title": "快捷命令", + "untitled": "无题" + }, + "quickCommandsTabButton": { + "quickCommands": "快捷命令" + }, + "routeHelpers": { + "connected": "已连接", + "connecting": "连接中", + "disconnected": "已断开连接", + "pairingInvalid": "配对无效", + "reconnecting": "重新连接中", + "securing": "正在建立安全连接" + }, + "routeScreen": { + "openingWorkspace": "正在打开工作区" + }, + "sheets": { + "browser": "浏览器", + "browserUpdateRequired": "使用移动端浏览器串流需更新桌面应用", + "chat": "聊天", + "copyAllAndLeave": "全部复制并离开", + "copyDraftsFailed": "无法复制草稿", + "copyNotes": "复制注释", + "copyNotesFailed": "无法复制注释", + "copyPath": "复制路径", + "discard": "放弃", + "discardAndLeave": "放弃并离开", + "discardChanges": "放弃更改", + "discardMessage": "是否用桌面上的最新文件替换手机草稿?", + "draftsHint": "离开前,请复制或放弃手机上的草稿。", + "file": "文件", + "markdown": "Markdown", + "markdownNote": "Markdown 笔记", + "newBrowser": "新浏览器", + "newTab": "新标签页", + "notesCopied": "注释已复制", + "notesHint": "选择接收当前注释的智能体会话。", + "removeShortcutMessage": "移除此自定义快捷键?", + "renameTerminal": "重命名终端", + "sendReviewNotes": "发送评论注释", + "shortcut": "快捷键", + "terminal": "终端", + "terminalName": "终端名称", + "unsavedMarkdown": "未保存的 Markdown 更改", + "urlPrompt": "输入 URL,或留空以新建标签页。" + }, + "sideRail": { + "backToWorktrees": "返回工作树列表" + }, + "sourceControlLaunchAgent": { + "noEnabledAgent": "此工作区主机上未检测到已启用的 AI 智能体。", + "savedAgentUnavailable": "为此操作保存的智能体在此工作区主机上不可用。" + }, + "structuredComposerCommand": { + "operationUnconfirmed": "对话操作未获确认。", + "removeAttachments": "使用聊天会话命令前,请先移除附件。" + }, + "structuredSendDelivery": { + "messageNotSent": "消息未发送" + }, + "tabStrip": { + "checkingCapabilities": "正在检查桌面支持的功能,请稍后重试", + "newTab": "新标签页", + "quickCommandsUpdateRequired": "使用快捷命令需更新桌面应用", + "tabStatus": "{{title}},{{status}}" + }, + "terminalActionSheetActions": { + "clearTerminal": "清空终端", + "rename": "重命名", + "switchToDesktop": "切换到桌面模式", + "switchToPhone": "切换到手机模式" + }, + "terminalInputActions": { + "attachFileHint": "长按可改为附加文件", + "attachPhoto": "附加照片", + "sendingImage": "正在发送图片", + "startDictation": "开始语音听写", + "startingDictation": "正在启动语音听写", + "stopDictation": "停止语音听写", + "transcribingDictation": "正在转写语音听写内容" + }, + "terminalLiveInputStatus": { + "listening": "正在聆听", + "liveInput": "实时输入", + "preparingMic": "正在准备麦克风", + "processing": "处理中", + "releaseMicHint": "松开麦克风按钮即可停止", + "startingMic": "正在启动麦克风", + "stopHint": "点按麦克风停止", + "transcribing": "正在桌面上转写", + "uploadingImage": "正在将图片上传到主机", + "uploadingImageTitle": "正在上传图片" + }, + "terminalRecords": { + "defaultTitle": "终端" + }, + "terminalTabAgent": { + "browser": "浏览器", + "chat": "聊天", + "file": "文件", + "markdown": "Markdown", + "newBrowser": "新浏览器", + "terminal": "终端" + }, + "terminalViewportResubscribe": { + "fitFailed": "无法调整终端大小以适应此屏幕" + }, + "useAccessorySelection": { + "copyFailed": "无法复制", + "selectionCleared": "选区已清除(内容已滚出缓冲区)" + }, + "useAttachmentInputLeaseGate": { + "reconnecting": "添加附件失败(重新连接中)" + }, + "useContentCreateActions": { + "browserCommandFailed": "浏览器命令执行失败", + "browserCreateFailed": "创建浏览器失败", + "browserUnavailable": "浏览器页面暂不可用。", + "browserUpdateRequired": "需更新桌面端才能使用移动端浏览器串流", + "invalidUrl": "请输入有效的 URL", + "noteCreateFailed": "创建 Markdown 笔记失败", + "untitledNoteCreateFailed": "无法创建无标题 Markdown 笔记" + }, + "useDiffComments": { + "copyNotesFailed": "无法复制笔记", + "deleteNoteFailed": "删除笔记失败", + "noteAdded": "笔记已添加", + "notesCopied": "笔记已复制", + "saveNoteFailed": "保存笔记失败", + "saveNotesFailed": "保存评审笔记失败", + "waitingForDesktop": "等待桌面…" + }, + "useDiffReviewCommentActions": { + "saveFailed": "保存评审失败", + "saveStateFailed": "保存评审状态失败", + "waitingForDesktop": "等待桌面连接…" + }, + "useDiffReviewController": { + "loadFailed": "无法加载评审", + "missingWorktree": "缺少工作树", + "waitingForDesktop": "等待桌面连接…" + }, + "useDiffReviewDiffLoading": { + "loadFailed": "无法加载差异", + "waitingForDesktop": "等待桌面连接…" + }, + "useDiffReviewGitActions": { + "actionFailed": "源代码控制操作失败", + "reviewedFilesStaged_one": "已暂存 {{count}} 个已评审文件", + "reviewedFilesStaged_other": "已暂存 {{count}} 个已评审文件", + "stageResults": "已暂存 {{staged}} 个,失败 {{failed}} 个", + "waitingForDesktop": "等待桌面连接…" + }, + "useDiffReviewInteractions": { + "openFailed": "无法在会话中打开" + }, + "useDiffReviewSendActions": { + "copyFailed": "无法复制评审笔记", + "inputLocked": "终端输入已锁定", + "loadSessionsFailed": "无法加载智能体会话", + "notesCopied": "评审笔记已复制", + "notesSent": "评审笔记已发送", + "notesSentWithWarning": "评审笔记已发送。{{warning}}", + "promptNotSent": "智能体已启动,但笔记未发送。请使用“复制笔记”复制后粘贴。", + "sendFailed": "发送笔记失败", + "startingAgent": "正在启动智能体…", + "waitingForDesktop": "等待桌面连接…" + }, + "useImageAttachment": { + "attachFailed": "添加附件失败", + "disconnected": "添加附件失败(已断开连接)", + "imageTooLarge": "图片过大,无法添加为附件", + "permissionDenied": "照片访问权限被拒绝" + }, + "useMarkdownActions": { + "copyFailed": "无法复制", + "copyPathFailed": "无法复制路径", + "markdownTitle": "Markdown", + "pathCopied": "路径已复制", + "saveFailed": "保存失败", + "saved": "已保存" + }, + "useNativeChatDictation": { + "insertFailed": "无法插入听写文本", + "insertFailedCopied": "无法插入听写文本,已复制到剪贴板", + "inserted": "已插入听写文本" + }, + "useNativeChatStop": { + "stopNotSent": "停止指令未发送", + "stopUnconfirmed": "停止操作尚未确认,重试前请查看聊天", + "terminalNotReady": "停止指令未发送(终端尚未就绪)" + }, + "usePanelRouteActions": { + "checkHostHint": "检查主机连接", + "copyNotesHint": "改为复制笔记", + "detectingAgents": "正在检测智能体", + "newSessionHint": "新建智能体会话", + "noEnabledAgents": "无已启用的智能体", + "otherServerAgents": "其他服务器上的智能体", + "pairServerHint": "直接与该服务器配对", + "presetsUnavailable": "智能体预设不可用" + }, + "usePrAiTriage": { + "launchFailed": "启动智能体失败", + "waitingForDesktop": "等待桌面…" + }, + "usePrCommentActions": { + "actionFailed": "评论操作失败" + }, + "usePrTitleAction": { + "disconnected": "未连接到桌面。", + "updateFailed": "更新标题失败。" + }, + "useQuickCommands": { + "loadFailed": "加载快速命令失败", + "saveFailed": "保存快速命令失败" + }, + "useSessionTerminalSendActions": { + "inputTooLarge": "输入内容过大(最大 256 KiB)" + }, + "useStartup": { + "wakeAgents": "请在主机上打开 Orca,以唤醒睡眠中的智能体。" + }, + "useTerminalCreateActions": { + "createFailed": "创建终端失败", + "forkBusy": "另一个终端正在打开,请稍候再尝试打开 {{agentName}} 聊天。", + "forkUnavailable": "请在已连接且已更新的主机上打开 {{agentName}} 聊天,然后重试。", + "inputLocked": "终端输入已被另一个客户端锁定。", + "notesSent": "笔记已发送", + "sendNotesError": "无法发送笔记", + "sendNotesFailed": "发送笔记失败", + "terminalTitle": "终端" + }, + "useTerminalInput": { + "clearFailed": "无法清空终端", + "cleared": "终端已清空" + }, + "useTerminalPaste": { + "clipboardReadFailed": "无法读取剪贴板", + "disconnected": "粘贴失败(连接已断开)", + "imageTooLarge": "图片过大,无法粘贴", + "pasteFailed": "粘贴失败", + "pasteTooLarge": "粘贴内容过大(最大 256 KiB)", + "terminalChanged": "粘贴已取消(终端已切换)" + }, + "worktreeLabel": { + "fallback": "工作树" + } + }, "settings": { "aboutScreen": { "openLinkFailed": "无法打开链接。请重试。", @@ -1436,6 +2519,186 @@ "updateFailed": "无法更新" } }, + "sourceControl": { + "actions": { + "commits": "提交", + "createPr": "创建PR", + "pushAndCreatePr": "推送并创建 PR", + "switchBranch": "切换分支" + }, + "commitDropdown": { + "checkOutBranchToPush": "请先检出分支再推送提交", + "checkOutBranchToSync": "同步提交前请先检出分支", + "checkingBranchStatus": "正在检查分支状态…", + "checkingPrStatus": "正在检查 PR 状态…", + "commit": "提交", + "commitAndForcePush": "提交已暂存的更改并使用 lease 强制推送", + "commitAndPush": "提交已暂存的更改并推送", + "commitAndTryPush": "提交已暂存的更改并尝试推送", + "commitForcePush": "提交并强制推送", + "commitPush": "提交并推送", + "commitStagedChanges": "提交已暂存的更改", + "commitSync": "提交并同步", + "commitThenPullPush": "先提交,再拉取并推送", + "enterMessage": "输入提交信息以进行提交", + "linkedReviewTargetUnavailable": "已关联的评审分支目标不可用", + "prAlreadyMerged": "PR 已合并", + "publishBranchFirstToPush": "请先发布分支再推送提交", + "publishBranchFirstToSync": "请先发布分支再同步提交", + "resolveConflicts": "提交前解决冲突", + "stageFile": "暂存至少一个要提交的文件", + "useCommitAndForcePush": "使用提交并强制推送 — 远程只有本地提交的旧副本" + }, + "createPrAction": { + "createReview": "创建 {{titleLabel}}" + }, + "dropdownCounts": { + "fastForwardCount_one": "快进 {{count}} 个提交", + "fastForwardCount_other": "快进 {{count}} 个提交", + "pullCount_one": "拉取 {{count}} 个提交", + "pullCount_other": "拉取 {{count}} 个提交", + "pushCount_one": "推送 {{count}} 个提交", + "pushCount_other": "推送 {{count}} 个提交", + "remoteBranchFallback": "远程分支", + "syncCounts": "拉取 {{behind}} 个,推送 {{ahead}} 个", + "titleForcePushOlderCopiesBranch": "远程只有本地提交的旧副本。使用 lease 强制推送此分支以更新 {{upstreamName}}。", + "titleForcePushOlderCopiesCount_one": "远程只有本地提交的旧副本。使用 lease 强制推送 {{count}} 个分支提交以更新 {{upstreamName}}。", + "titleForcePushOlderCopiesCount_other": "远程只有本地提交的旧副本。使用 lease 强制推送 {{count}} 个分支提交以更新 {{upstreamName}}。", + "titleManualForcePushReplaceRemote_one": "使用 lease 强制推送 {{count}} 个本地提交以更新 {{upstreamName}},并替换仅存在于远程的提交。", + "titleManualForcePushReplaceRemote_other": "使用 lease 强制推送 {{count}} 个本地提交以更新 {{upstreamName}},并替换仅存在于远程的提交。", + "titleManualForcePush_one": "使用 lease 强制推送 {{count}} 个本地提交以更新 {{upstreamName}}。", + "titleManualForcePush_other": "使用 lease 强制推送 {{count}} 个本地提交以更新 {{upstreamName}}。", + "titleUnpublishedForcePushBranch": "使用 lease 强制推送此分支,并在需要时设置上游。", + "titleUnpublishedForcePushCount_one": "使用 lease 强制推送 {{count}} 个分支提交,并在需要时设置上游。", + "titleUnpublishedForcePushCount_other": "使用 lease 强制推送 {{count}} 个分支提交,并在需要时设置上游。" + }, + "hostedReview": { + "mrCopy": { + "reviewLabel": "合并请求", + "shortLabel": "MR", + "titleLabel": "合并请求" + }, + "prCopy": { + "reviewLabel": "PR", + "shortLabel": "PR", + "titleLabel": "PR" + } + }, + "hostedReviewCreate": { + "pushSucceeded": "推送成功,但创建 {{shortLabel}} 失败:{{error}}" + }, + "prCreate": { + "authRequired": "请在创建 {{reviewLabel}} 之前完成身份验证。", + "baseNotOnRemote": "请先推送基础分支,再创建 {{reviewLabel}}。", + "createdNotRefreshed": "{{titleLabel}} 已创建,但 Orca 暂时无法刷新其信息。", + "defaultBranch": "请在创建 {{reviewLabel}} 之前切换到功能分支。", + "detachedHead": "请在创建 {{reviewLabel}} 之前检出分支。", + "dirty": "请在创建 {{reviewLabel}} 之前提交更改。", + "existing": "{{titleLabel}} 已处于开放状态。", + "existingNumber": "{{titleLabel}} #{{number}} 已处于开放状态。", + "existingReview": "此分支已存在 {{reviewLabel}}。", + "forkHeadUnsupported": "不支持从此复刻仓库创建 {{reviewLabel}}。", + "lookupUnconfirmed": "Orca 无法确认此分支是否已有 {{reviewLabel}}。请稍后重试。", + "needsSync": "请在创建 {{reviewLabel}} 之前同步此分支。", + "noUpstream": "请在创建 {{reviewLabel}} 之前发布提交。", + "notReady": "此分支尚不具备创建 {{reviewLabel}} 的条件。", + "unsupportedMergeRequests": "此仓库不支持创建合并请求。", + "unsupportedPullRequests": "此仓库不支持创建 PR。" + }, + "primaryAction": { + "forcePushFromDesktop": "从桌面强制推送此 PR 的分支" + }, + "remoteDropdown": { + "branchAlreadyPublished": "分支已发布", + "branchUpToDate": "分支已是最新状态", + "checkOutBranchToFastForward": "请先检出分支再快进", + "checkOutBranchToForcePush": "请先检出分支再强制推送提交", + "checkOutBranchToPublish": "请先检出分支再发布提交", + "checkOutBranchToPull": "请先检出分支再拉取提交", + "checkOutBranchToPush": "请先检出分支再推送提交", + "checkOutBranchToSync": "同步提交前请先检出分支", + "checkingBranchStatus": "正在检查分支状态…", + "checkingPrStatus": "正在检查 PR 状态…", + "fastForward": "快进", + "fetch": "fetch", + "fetchTitle": "从远程获取但不合并", + "forcePush": "强制推送", + "linkedReviewBranchExists": "已关联的评审分支已存在", + "linkedReviewTargetUnavailable": "已关联的评审分支目标不可用", + "nothingNewToFastForward": "没有新内容可快进——远程只有本地提交的旧副本", + "nothingNewToPull": "没有新内容可拉取——远程只有本地提交的旧副本", + "nothingToFastForward": "没有可快进的内容", + "nothingToForcePush": "没有要强制推送的内容", + "nothingToForcePushTo": "没有要强制推送到 {{upstreamName}} 的内容", + "nothingToPull": "没有要拉取的内容", + "nothingToPush": "没有要推送的内容", + "nothingToPushTo": "没有要推送到 {{upstreamName}} 的内容", + "prAlreadyMerged": "PR 已合并", + "publishBranch": "发布分支", + "publishBranchFirstToFastForward": "请先发布分支再快进", + "publishBranchFirstToPull": "请先发布分支再拉取提交", + "publishBranchFirstToSync": "请先发布分支再同步提交", + "publishLinkedReview": "已关联的评审", + "publishNoBranch": "无分支", + "publishPrStatus": "PR 状态", + "publishToOrigin": "将此分支发布到 origin", + "pull": "拉", + "push": "推", + "pushAndSetUpstream": "推送此分支,并在需要时设置上游", + "pushMayNeedSync": "推送本地提交;git 可能要求先同步", + "pushToLinkedReview": "将更新推送到已关联的评审分支", + "rebaseChooseRemoteBase": "选择要从中变基的远程基准分支", + "rebaseDirtyHint": "尝试变基;git 可能要求先提交或暂存本地更改", + "rebaseFromBase": "从基准分支变基", + "rebaseFromNamedBase": "从 {{baseRef}} 变基", + "rebaseWithLatestFrom": "使用 {{baseRef}} 的最新提交变基当前分支", + "sync": "同步", + "tryFastForwardPull": "尝试快进拉取;git 可能会拒绝本地提交", + "tryRegularPush": "尝试常规推送;git 可能要求强制推送", + "useForcePushToSync": "请使用强制推送——远程只有本地提交的旧副本" + } + }, + "terminal": { + "keyDefinitions": { + "accessoryArrowDown": "向下箭头", + "accessoryArrowLeft": "向左箭头", + "accessoryArrowRight": "向右箭头", + "accessoryArrowUp": "向上箭头", + "arrowDown": "向下箭头", + "arrowLeft": "向左箭头", + "arrowRight": "向右箭头", + "arrowUp": "向上箭头", + "clearLineBeforeCursor": "清除当前行中光标前的内容", + "clearScreen": "清屏", + "deleteWordBackward": "向后删除单词", + "endOfLine": "行尾", + "forwardDelete": "向前删除", + "interruptTerminal": "中断终端", + "reverseSearch": "反向搜索", + "sendEof": "发送 EOF", + "startOfLine": "行首", + "suspendProcess": "挂起进程" + }, + "webView": { + "documentStartFailed": "终端文档启动失败:{{message}}", + "httpError": "终端 WebView HTTP 错误", + "loadFailed": "终端 WebView 加载失败", + "renderProcessEnded": "终端 WebView 渲染进程已结束" + }, + "webviewEngineErrorState": { + "code": "错误码 {{code}}", + "reload": "重新加载", + "rendererCrashed": "渲染进程已崩溃", + "status": "状态码 {{statusCode}}", + "title": "终端加载失败" + }, + "webviewNotificationDispatch": { + "unknownError": "未知终端错误" + }, + "webviewReadyWatchdog": { + "initializeFailed": "终端未能初始化:未收到终端视图的就绪信号" + } + }, "webShell": { "shellStateScreen": { "checkingHost": "正在检查主机", @@ -1450,6 +2713,18 @@ "tryAgain": "重试" } }, + "workspace": { + "branchChoice": { + "automaticName": "自动命名", + "existing": "现有分支", + "existingHint": "新提交将添加到此分支。", + "hostUpdateRequired": "请更新工作区主机上的 Orca,以复用分支或明确选择创建独立分支。", + "new": "新分支", + "newHint": "创建新的本地分支", + "newName": "新分支名称", + "title": "此工作树使用的分支" + } + }, "worktree": { "agentRowDisplay": { "blocked": "已阻塞", @@ -1489,16 +2764,5 @@ "inReview": "评审中", "pinned": "已固定" } - }, - "workspace": { - "branchChoice": { - "title": "此工作树使用的分支", - "existing": "现有分支", - "existingHint": "新提交将添加到此分支。", - "new": "新分支", - "newHint": "创建新的本地分支", - "newName": "新分支名称", - "automaticName": "自动命名" - } } } diff --git a/mobile/src/session/MobileBrowserTabActionSheet.tsx b/mobile/src/session/MobileBrowserTabActionSheet.tsx index 5ded7bcfa11..fb45273e727 100644 --- a/mobile/src/session/MobileBrowserTabActionSheet.tsx +++ b/mobile/src/session/MobileBrowserTabActionSheet.tsx @@ -2,6 +2,7 @@ import { ChevronLeft, ChevronRight, RefreshCw } from 'lucide-react-native' import type { MobileSessionTab } from './mobile-session-route-types' import { ActionSheetModal, type ActionSheetAction } from '../components/ActionSheetModal' import { getMobileSessionTabTitle } from './mobile-terminal-tab-agent' +import { useMobileTranslation } from '../i18n/use-mobile-translation' type BrowserTab = Extract export type MobileBrowserNavigationMethod = 'browser.back' | 'browser.forward' | 'browser.reload' @@ -15,16 +16,22 @@ export function MobileBrowserTabActionSheet(props: { * bulk-close builder can resolve the anchor itself. */ bulkCloseActions?: (anchorTabId: string | undefined) => ActionSheetAction[] }): React.JSX.Element { + const t = useMobileTranslation() const { target, onClose, onNavigate, onCloseTab, bulkCloseActions } = props return ( { const current = target @@ -39,7 +46,8 @@ export function MobileBrowserTabActionSheet(props: { ...(target?.canGoForward ? [ { - label: 'Forward', + id: 'forward', + label: t('session.browserTabActionSheet.forward', 'Forward'), icon: ChevronRight, onPress: () => { const current = target @@ -52,7 +60,8 @@ export function MobileBrowserTabActionSheet(props: { ] : []), { - label: 'Reload', + id: 'reload', + label: t('session.browserTabActionSheet.reload', 'Reload'), icon: RefreshCw, onPress: () => { const current = target @@ -63,7 +72,8 @@ export function MobileBrowserTabActionSheet(props: { } }, { - label: 'Close', + id: 'close', + label: t('common.close', 'Close'), destructive: true, onPress: () => { const current = target diff --git a/mobile/src/session/MobileDictationStatusAnnouncement.tsx b/mobile/src/session/MobileDictationStatusAnnouncement.tsx index 3acd8f8a6dc..f6a29b86660 100644 --- a/mobile/src/session/MobileDictationStatusAnnouncement.tsx +++ b/mobile/src/session/MobileDictationStatusAnnouncement.tsx @@ -1,5 +1,7 @@ import { StyleSheet, Text, View } from 'react-native' import type { UseMobileDictationResult } from '../hooks/mobile-dictation-session-state' +import { translate } from '../i18n/mobile-i18n' +import { useMobileTranslation } from '../i18n/use-mobile-translation' export type MobileDictationStatusFlags = Pick< UseMobileDictationResult, @@ -8,11 +10,11 @@ export type MobileDictationStatusFlags = Pick< export function mobileDictationStatusTitle(dictation: MobileDictationStatusFlags): string | null { return dictation.isRecording - ? 'Listening' + ? translate('session.terminalLiveInputStatus.listening', 'Listening') : dictation.isProcessing - ? 'Processing' + ? translate('session.terminalLiveInputStatus.processing', 'Processing') : dictation.isStarting - ? 'Starting mic' + ? translate('session.terminalLiveInputStatus.startingMic', 'Starting mic') : null } @@ -22,6 +24,7 @@ export function MobileDictationStatusAnnouncement({ }: { dictation: MobileDictationStatusFlags }): React.JSX.Element { + useMobileTranslation() return ( {mobileDictationStatusTitle(dictation) ?? ''} diff --git a/mobile/src/session/MobileDiffCommentLineRow.tsx b/mobile/src/session/MobileDiffCommentLineRow.tsx index c7ac58459d7..3ea78a8f87b 100644 --- a/mobile/src/session/MobileDiffCommentLineRow.tsx +++ b/mobile/src/session/MobileDiffCommentLineRow.tsx @@ -6,6 +6,7 @@ import { useMobileColorScheme } from '../theme/use-mobile-color-scheme' import { styles } from './mobile-session-styles' import type { DiffComment } from '../../../src/shared/diff-comment-types' import type { RenderableDiffLine } from './mobile-session-route-types' +import { useMobileTranslation } from '../i18n/use-mobile-translation' export function MobileDiffCommentLineRow({ line, @@ -35,6 +36,7 @@ export function MobileDiffCommentLineRow({ onDeleteComment: (commentId: string) => void }) { useMobileColorScheme() + const t = useMobileTranslation() const commentLine = line.newLineNumber const isCommenting = commentLine !== undefined && activeCommentLine === commentLine const canComment = commentLine !== undefined @@ -53,7 +55,14 @@ export function MobileDiffCommentLineRow({ @@ -91,12 +106,20 @@ export function MobileDiffCommentLineRow({ - Line {comment.lineNumber} + + {t('session.diffCommentLineRow.line', 'Line {{line}}', { + line: comment.lineNumber + })} + onDeleteComment(comment.id)} - accessibilityLabel={`Delete note on line ${comment.lineNumber}`} + accessibilityLabel={t( + 'session.diffCommentLineRow.deleteNote', + 'Delete note on line {{line}}', + { line: comment.lineNumber } + )} > @@ -112,7 +135,7 @@ export function MobileDiffCommentLineRow({ style={[styles.textInput, styles.diffCommentInput]} value={commentDraft} onChangeText={onDraftChange} - placeholder="Add review note" + placeholder={t('session.diffCommentLineRow.placeholder', 'Add review note')} placeholderTextColor={colors.textMuted} editable={!commentsBusy} multiline @@ -125,7 +148,7 @@ export function MobileDiffCommentLineRow({ disabled={commentsBusy} onPress={onCancelComment} > - Cancel + {t('common.cancel', 'Cancel')} - Save note + + {t('session.diffCommentLineRow.saveNote', 'Save note')} + diff --git a/mobile/src/session/MobileMarkdownReader.tsx b/mobile/src/session/MobileMarkdownReader.tsx index fcf08e08af2..7945a1b7081 100644 --- a/mobile/src/session/MobileMarkdownReader.tsx +++ b/mobile/src/session/MobileMarkdownReader.tsx @@ -14,6 +14,7 @@ import { import type { MarkdownDocState } from './mobile-session-route-types' import { styles } from './mobile-session-styles' import { MobileLoadErrorState } from '../components/MobileLoadErrorState' +import { useMobileTranslation } from '../i18n/use-mobile-translation' type Props = { documentId: string @@ -37,6 +38,7 @@ export function MobileMarkdownReader({ keyboardLift }: Props) { useMobileColorScheme() + const t = useMobileTranslation() const editorRef = useRef(null) // Native Keyboard events under-report the WebView editor's covered area, so prefer the larger WebView-measured inset. const [webviewKeyboardInset, setWebviewKeyboardInset] = useState(0) @@ -57,9 +59,9 @@ export function MobileMarkdownReader({ const statusText = doc.saveError ? doc.saveError : doc.readOnlyReason - ? 'Read only' + ? t('session.markdownReader.readOnly', 'Read only') : doc.stale - ? 'Changed on desktop' + ? t('session.markdownReader.changedOnDesktop', 'Changed on desktop') : null const showRefresh = Boolean((doc.stale && !doc.isDirty) || !doc.editable) const showCopy = Boolean(doc.saveError || !doc.editable) @@ -112,8 +114,11 @@ export function MobileMarkdownReader({ onPress={() => editorRef.current?.dismissKeyboard()} hitSlop={8} accessibilityRole="button" - accessibilityLabel="Dismiss keyboard" - accessibilityHint="Hides the software keyboard and keeps the markdown editor open." + accessibilityLabel={t('session.markdownReader.dismissKeyboard', 'Dismiss keyboard')} + accessibilityHint={t( + 'session.markdownReader.dismissKeyboardHint', + 'Hides the software keyboard and keeps the markdown editor open.' + )} > @@ -128,18 +133,22 @@ export function MobileMarkdownReader({ ) : null} {showCopy ? ( - Copy + {t('common.copy', 'Copy')} ) : null} {showRefresh ? ( - Refresh + + {t('common.refresh', 'Refresh')} + ) : null} {doc.isDirty ? ( - Discard + + {t('session.markdownReader.discard', 'Discard')} + ) : null} {showSave ? ( @@ -155,7 +164,7 @@ export function MobileMarkdownReader({ {doc.saving ? ( ) : ( - Save + {t('common.save', 'Save')} )} ) : null} diff --git a/mobile/src/session/MobileNativeChatCardHeaderAction.tsx b/mobile/src/session/MobileNativeChatCardHeaderAction.tsx index 48b7b3b229c..aa360316f8d 100644 --- a/mobile/src/session/MobileNativeChatCardHeaderAction.tsx +++ b/mobile/src/session/MobileNativeChatCardHeaderAction.tsx @@ -2,6 +2,7 @@ import { Keyboard, Pressable, StyleSheet } from 'react-native' import { ChevronDown, X } from 'lucide-react-native' import { colors } from '../theme/mobile-theme' import { useMobileColorScheme } from '../theme/use-mobile-color-scheme' +import { useMobileTranslation } from '../i18n/use-mobile-translation' export function MobileNativeChatCardHeaderAction({ prompt, @@ -15,13 +16,18 @@ export function MobileNativeChatCardHeaderAction({ disabled?: boolean }): React.JSX.Element | null { useMobileColorScheme() + const t = useMobileTranslation() if (!onCancel && !onCollapse) { return null } const Icon = onCancel ? X : ChevronDown return ( ['code'] }): React.JSX.Element { useMobileColorScheme() + const t = useMobileTranslation() if (line.kind === 'gap') { return ( - + ⋯ ) @@ -142,6 +142,9 @@ export function MobileNativeChatDiffCard({ onOpenFile?: (path: string) => void }): React.JSX.Element { useMobileColorScheme() + const t = useMobileTranslation() + const copy = nativeChatDiffCardCopy(t) + const verbs = nativeChatToolRowVerbCopy(t) const [expanded, setExpanded] = useMobileNativeChatDisclosure(disclosureKey, false) const hasBody = file.lines.length > 0 const copyText = useMemo(() => nativeChatEditFilePatchText(file.lines), [file.lines]) @@ -161,9 +164,7 @@ export function MobileNativeChatDiffCard({ - - {NATIVE_CHAT_TOOL_ROW_VERB_COPY[file.changeKind]} - + {verbs[file.changeKind]} {file.oldPath ? ( <> {file.truncated ? ( - - {NATIVE_CHAT_DIFF_CARD_COPY.diffTruncated} - + {copy.diffTruncated} ) : null} {hasBody ? ( @@ -203,10 +202,7 @@ export function MobileNativeChatDiffCard({ ) : null} - + {hasBody && expanded ? ( void }): React.JSX.Element { useMobileColorScheme() + const t = useMobileTranslation() return ( {showJumpToTail ? ( - + ) : null} diff --git a/mobile/src/session/MobileNativeChatFullContent.tsx b/mobile/src/session/MobileNativeChatFullContent.tsx index 4e9e2e97758..52c31af7090 100644 --- a/mobile/src/session/MobileNativeChatFullContent.tsx +++ b/mobile/src/session/MobileNativeChatFullContent.tsx @@ -3,7 +3,6 @@ import { Pressable, Text, View, type TextStyle } from 'react-native' import { truncateToolDetail } from '../../../src/shared/native-chat/tools/native-chat-tool-summary' import { isNativeChatToolOutputClipped, - NATIVE_CHAT_TOOL_OUTPUT_COPY, nativeChatToolFullPayload, nativeChatToolOutputPartialSizes } from '../../../src/shared/native-chat/tools/native-chat-tool-output' @@ -16,6 +15,7 @@ import { INLINE_TEXT_SELECTION } from '../components/inline-text-selection' import { styles } from './native-chat/transcript/mobile-native-chat-message-styles' import { toolStyles } from './native-chat/transcript/mobile-native-chat-tool-styles' import { useMobileColorScheme } from '../theme/use-mobile-color-scheme' +import { useMobileTranslation } from '../i18n/use-mobile-translation' import { MobileNativeChatOwnedScrollView } from './MobileNativeChatOwnedScrollView' import { MobileNativeChatItemSourceContext, @@ -28,10 +28,12 @@ type Load = 'idle' | 'loading' | 'failed' function ShowFull({ label, + failedLabel, load, onPress }: { label: string + failedLabel: string load: Load onPress: () => void }): React.JSX.Element { @@ -44,9 +46,7 @@ function ShowFull({ hitSlop={8} accessibilityRole="button" > - - {load === 'failed' ? `${label} — couldn't load, tap to retry` : label} - + {load === 'failed' ? failedLabel : label} ) } @@ -63,6 +63,7 @@ export function MobileNativeChatToolOutput({ positionKey: string }): React.JSX.Element { useMobileColorScheme() + const t = useMobileTranslation() const source = useContext(MobileNativeChatItemSourceContext) const { output, isError, fullOutputItemId } = body const client = source?.client, @@ -130,13 +131,22 @@ export function MobileNativeChatToolOutput({ {partial ? ( - {NATIVE_CHAT_TOOL_OUTPUT_COPY.partial - .replace('{{shown}}', partial.shown) - .replace('{{total}}', partial.total)} + {t('session.nativeChatFullContent.partialOutput', 'Showing {{shown}} of {{total}}.', { + shown: partial.shown, + total: partial.total + })} ) : null} {clipped ? ( - + ) : null} ) @@ -148,6 +158,7 @@ export function useMobileNativeChatFullMessage(message: NativeChatMessage): { recovery: { load: Load; onPress: () => void } | null } { useMobileColorScheme() + const t = useMobileTranslation() const source = useContext(MobileNativeChatItemSourceContext) const detail = source?.messageDetail const client = source?.client, @@ -182,9 +193,19 @@ export function useMobileNativeChatFullMessage(message: NativeChatMessage): { recovery: state === 'available' ? { load, onPress: showFull } : null, showFull: state === 'partial' ? ( - Showing part of this message. + + {t('session.nativeChatFullContent.partialMessage', 'Showing part of this message.')} + ) : state === 'available' ? ( - + ) : null } } diff --git a/mobile/src/session/MobileNativeChatResolutionReceipt.tsx b/mobile/src/session/MobileNativeChatResolutionReceipt.tsx index ee1a58e479c..e254e209466 100644 --- a/mobile/src/session/MobileNativeChatResolutionReceipt.tsx +++ b/mobile/src/session/MobileNativeChatResolutionReceipt.tsx @@ -1,11 +1,12 @@ import { StyleSheet, Text, View } from 'react-native' import { - NATIVE_CHAT_RECEIPT_COPY, nativeChatReceiptAnswerHeading, nativeChatReceiptAnswers, nativeChatReceiptSubject, type NativeChatResolvedPrompt } from '../../../src/shared/native-chat-resolution-receipt' +import { nativeChatReceiptCopy } from './native-chat-receipt-copy' +import { useMobileTranslation } from '../i18n/use-mobile-translation' import { INLINE_TEXT_SELECTION } from '../components/inline-text-selection' import { colors, spacing, typography } from '../theme/mobile-theme' import { MobileNativeChatAwaitingInputRow } from './native-chat/transcript/MobileNativeChatAwaitingInputRow' @@ -25,6 +26,7 @@ export function MobileNativeChatResolutionReceipt({ fontScale: number }): React.JSX.Element | null { useMobileColorScheme() + const t = useMobileTranslation() const subject = nativeChatReceiptSubject(body) if (body.resolution.state === 'pending') { return body.kind === 'question' ? ( @@ -38,16 +40,12 @@ export function MobileNativeChatResolutionReceipt({ ) : null } const { resolution } = body + const copy = nativeChatReceiptCopy(t, resolution.resolvedBy ?? undefined) const cancelled = resolution.state === 'cancelled' const answers = nativeChatReceiptAnswers(body) const outcome = [ - cancelled ? NATIVE_CHAT_RECEIPT_COPY.cancelled : NATIVE_CHAT_RECEIPT_COPY.resolved, - resolution.resolvedBy - ? (cancelled - ? NATIVE_CHAT_RECEIPT_COPY.cancelledBy - : NATIVE_CHAT_RECEIPT_COPY.resolver - ).replace('{{device}}', resolution.resolvedBy) - : null + cancelled ? copy.cancelled : copy.resolved, + resolution.resolvedBy ? (cancelled ? copy.cancelledBy : copy.resolver) : null ] const text = [styles.text, scaledType(typography.metaSize, fontScale, 5)] return ( @@ -77,9 +75,7 @@ export function MobileNativeChatResolutionReceipt({ > {heading ? {heading} : null} - {answer.answer === '' - ? NATIVE_CHAT_RECEIPT_COPY.emptyAnswer - : (answer.answer ?? NATIVE_CHAT_RECEIPT_COPY.unavailable)} + {answer.answer === '' ? copy.emptyAnswer : (answer.answer ?? copy.unavailable)} ) diff --git a/mobile/src/session/MobileQueuedCardText.tsx b/mobile/src/session/MobileQueuedCardText.tsx index cbb68a05a19..ed604b1fca3 100644 --- a/mobile/src/session/MobileQueuedCardText.tsx +++ b/mobile/src/session/MobileQueuedCardText.tsx @@ -4,6 +4,7 @@ import { colors, typography } from '../theme/mobile-theme' import { themedStyleSheet } from '../theme/themed-styles' import { useMobileColorScheme } from '../theme/use-mobile-color-scheme' import { queuedMessageStyles } from './native-chat/input/mobile-native-chat-queued-message-styles' +import { useMobileTranslation } from '../i18n/use-mobile-translation' // Expand clipped queued text before Steer/Delete; use the queue’s capped scroller rather than nesting another scroll box. export function MobileQueuedCardText({ @@ -16,6 +17,7 @@ export function MobileQueuedCardText({ onToggle: () => void }): React.JSX.Element { useMobileColorScheme() + const t = useMobileTranslation() const [clipped, setClipped] = useState(false) const measure = ( {expanded ? null : ( @@ -51,7 +57,11 @@ export function MobileQueuedCardText({ {text} )} - {expanded ? 'Show less' : 'Show more'} + + {expanded + ? t('session.queuedCardText.showLess', 'Show less') + : t('session.queuedCardText.showMore', 'Show more')} + ) return ( diff --git a/mobile/src/session/MobileQueuedMessageCardRow.tsx b/mobile/src/session/MobileQueuedMessageCardRow.tsx index 6fd00814fa1..ecb16450c49 100644 --- a/mobile/src/session/MobileQueuedMessageCardRow.tsx +++ b/mobile/src/session/MobileQueuedMessageCardRow.tsx @@ -18,6 +18,7 @@ import type { MobileQueuedMessageInlineEditor } from './use-mobile-structured-qu import { MobileQueuedMessageComposer } from './MobileQueuedMessageComposer' import { MobileQueuedCardText } from './MobileQueuedCardText' import { queuedMessageStyles as styles } from './native-chat/input/mobile-native-chat-queued-message-styles' +import { useMobileTranslation } from '../i18n/use-mobile-translation' type QueuedMessageCardRowProps = { dragHandle?: ReactNode @@ -64,9 +65,11 @@ function QueuedMessageCardContents({ dragHandle }: Omit): React.JSX.Element { useMobileColorScheme() + const t = useMobileTranslation() const [expanded, setExpanded] = useState(false) const returned = card.state === 'returned' const steers = queuedMessageCardSteers(card) + const count = card.images?.length ?? 0 return ( @@ -98,7 +101,11 @@ function QueuedMessageCardContents({ {card.images.length} @@ -110,10 +117,13 @@ function QueuedMessageCardContents({ accessibilityState={{ disabled: busy || steerHeld }} accessibilityLabel={ returned - ? 'Send this message again' + ? t('session.queuedMessageCardRow.sendAgainHint', 'Send this message again') : steers - ? 'Submit without interrupting the model' - : 'Send this message' + ? t( + 'session.queuedMessageCardRow.steerHint', + 'Submit without interrupting the model' + ) + : t('session.queuedMessageCardRow.sendHint', 'Send this message') } style={({ pressed }) => [ styles.textAction, @@ -128,13 +138,20 @@ function QueuedMessageCardContents({ ) : ( )} - {steers ? 'Steer' : 'Send'} + + {steers + ? t('session.queuedMessageCardRow.steer', 'Steer') + : t('session.queuedMessageCardRow.send', 'Send')} + )} [ styles.iconAction, card.command && styles.lastIconAction, @@ -150,7 +167,7 @@ function QueuedMessageCardContents({ [ styles.iconAction, styles.lastIconAction, @@ -171,7 +188,10 @@ function QueuedMessageCardContents({ setExpanded(false)} > {card.line} diff --git a/mobile/src/session/MobileQueuedMessageComposer.tsx b/mobile/src/session/MobileQueuedMessageComposer.tsx index 76d9e2da424..cc56affd806 100644 --- a/mobile/src/session/MobileQueuedMessageComposer.tsx +++ b/mobile/src/session/MobileQueuedMessageComposer.tsx @@ -13,6 +13,7 @@ import { prepareQueuedMessageEditText } from '../../../src/shared/queued-message import { colors } from '../theme/mobile-theme' import { useMobileColorScheme } from '../theme/use-mobile-color-scheme' import { createUuidV4 } from '../../../src/shared/uuid-v4' +import { useMobileTranslation } from '../i18n/use-mobile-translation' export function MobileQueuedMessageComposer({ editor, @@ -22,6 +23,7 @@ export function MobileQueuedMessageComposer({ attribution: string | null }): React.JSX.Element { useMobileColorScheme() + const t = useMobileTranslation() const context = useContext(MobileQueuedMessageComposerContext) const inputRef = useRef(null) const emptyHandle = useRef(null) @@ -60,13 +62,26 @@ export function MobileQueuedMessageComposer({ if (acceptsText(text)) { return false } - context?.onError("Commands can't be saved in a queued message.") + context?.onError( + t( + 'session.queuedMessageComposer.commandsNotAllowed', + "Commands can't be saved in a queued message." + ) + ) return true } return ( - Editing message{attribution ? ` · ${attribution}` : ''} + {attribution + ? t( + 'session.queuedMessageComposer.editingAttributed', + 'Editing message · {{attribution}}', + { + attribution + } + ) + : t('session.queuedMessageComposer.editing', 'Editing message')} @@ -100,8 +105,8 @@ export function MobileSessionAccessoryKeys({ }} accessibilityLabel={ isTerminalPhoneDisplayMode(activeHandle, terminalModes) - ? 'Switch to desktop mode' - : 'Switch to phone mode' + ? t('session.accessoryKeys.switchToDesktopMode', 'Switch to desktop mode') + : t('session.accessoryKeys.switchToPhoneMode', 'Switch to phone mode') } > {isTerminalPhoneDisplayMode(activeHandle, terminalModes) ? ( @@ -122,7 +127,9 @@ export function MobileSessionAccessoryKeys({ disabled={!canCompose} onPress={toggleLiveInput} accessibilityLabel={ - liveInputEnabled ? 'Switch to buffered command input' : 'Switch to live terminal input' + liveInputEnabled + ? t('session.accessoryKeys.switchToBufferedInput', 'Switch to buffered command input') + : t('session.accessoryKeys.switchToLiveInput', 'Switch to live terminal input') } > void handlePaste()} - accessibilityLabel="Paste from clipboard" + accessibilityLabel={t( + 'session.accessoryKeys.pasteFromClipboard', + 'Paste from clipboard' + )} > - Paste + {t('session.accessoryKeys.paste', 'Paste')} )} @@ -183,7 +193,10 @@ export function MobileSessionAccessoryKeys({ void handleAccessoryKey(createTerminalLiveAccessoryInput(key)) }} onLongPress={key.repeatable ? keepHeldPressThroughLongPress : undefined} - accessibilityLabel={key.accessibilityLabel ?? `Send ${key.label}`} + accessibilityLabel={ + key.accessibilityLabel ?? + t('session.accessoryKeys.sendKey', 'Send {{label}}', { label: key.label }) + } > {key.label} @@ -208,7 +221,9 @@ export function MobileSessionAccessoryKeys({ setDeleteKeyTarget(key) }} delayLongPress={400} - accessibilityLabel={`Send ${key.label}`} + accessibilityLabel={t('session.accessoryKeys.sendKey', 'Send {{label}}', { + label: key.label + })} > {key.label} @@ -222,7 +237,7 @@ export function MobileSessionAccessoryKeys({ pressed && styles.accessoryKeyPressed ]} onPress={() => setShowCustomKeyModal(true)} - accessibilityLabel="Add custom shortcut" + accessibilityLabel={t('session.accessoryKeys.addCustomShortcut', 'Add custom shortcut')} > diff --git a/mobile/src/session/MobileSessionActiveContent.tsx b/mobile/src/session/MobileSessionActiveContent.tsx index 2bd8376b0b5..f5c577e4d48 100644 --- a/mobile/src/session/MobileSessionActiveContent.tsx +++ b/mobile/src/session/MobileSessionActiveContent.tsx @@ -8,6 +8,7 @@ import { MobileNativeChatDictationPreviewContext } from './native-chat/composer/ import { resolveMobileNativeChatSessionId } from './native-chat/mobile-native-chat-eligibility' import { colors } from '../theme/mobile-theme' import { useMobileColorScheme } from '../theme/use-mobile-color-scheme' +import { useMobileTranslation } from '../i18n/use-mobile-translation' import { styles } from './mobile-session-styles' import type { MobileSessionController } from './use-mobile-session-controller' import { useWorkspaceExecutionHost } from '../navigation/workspace-execution-host' @@ -22,6 +23,7 @@ export function MobileSessionActiveContent({ controller: MobileSessionController }) { useMobileColorScheme() + const t = useMobileTranslation() const executionHost = useWorkspaceExecutionHost() const { hostId, @@ -109,7 +111,9 @@ export function MobileSessionActiveContent({ ) : showEmptyState ? ( - No tabs in this session + + {t('session.activeContent.noTabs', 'No tabs in this session')} + {createError ? {createError} : null} - {createTabBusy ? 'Creating...' : 'Create Tab'} + {createTabBusy + ? t('session.activeContent.creatingTab', 'Creating...') + : t('session.activeContent.createTab', 'Create Tab')} @@ -154,7 +160,7 @@ export function MobileSessionActiveContent({ documentId={JSON.stringify([hostId, executionHost, worktreeId, activeFileTab.id])} client={client} doc={fileDocs.get(activeFileTab.id)} - title={activeFileTab.title || 'File'} + title={activeFileTab.title || t('session.activeContent.fileTitle', 'File')} relativePath={activeFileTab.relativePath} language={activeFileTab.language} diffCommentActions={ @@ -203,17 +209,24 @@ export function MobileSessionActiveContent({ )} {isPendingTerminalRecoveryParked - ? 'Terminal is taking longer than expected' - : activePendingTerminalTab.title || 'Loading terminal'} + ? t( + 'session.activeContent.terminalTakingLonger', + 'Terminal is taking longer than expected' + ) + : activePendingTerminalTab.title || + t('session.activeContent.loadingTerminal', 'Loading terminal')} {isPendingTerminalRecoveryParked && ( [styles.createButton, pressed && styles.newTerminalButtonPressed]} onPress={() => void retryPendingTerminalRecovery()} > - Retry + {t('common.retry', 'Retry')} )} diff --git a/mobile/src/session/MobileSessionCommandDock.tsx b/mobile/src/session/MobileSessionCommandDock.tsx index 1210186e45e..423d7ee9cb1 100644 --- a/mobile/src/session/MobileSessionCommandDock.tsx +++ b/mobile/src/session/MobileSessionCommandDock.tsx @@ -12,6 +12,7 @@ import { MobileTerminalInputActions } from './MobileTerminalInputActions' import { MobileSessionAccessoryKeys } from './MobileSessionAccessoryKeys' import { colors } from '../theme/mobile-theme' import { useMobileColorScheme } from '../theme/use-mobile-color-scheme' +import { useMobileTranslation } from '../i18n/use-mobile-translation' import { styles } from './mobile-session-styles' import type { MobileSessionController } from './use-mobile-session-controller' @@ -33,6 +34,7 @@ export function MobileSessionCommandDock({ keysInRail: boolean }) { useMobileColorScheme() + const t = useMobileTranslation() const { insets, bufferedTerminalDraftState, @@ -87,11 +89,14 @@ export function MobileSessionCommandDock({ onChange={handleLiveInputChange} onKeyPress={handleLiveInputKeyPress} onSubmitEditing={submitLiveInput} - placeholder="Live input" + placeholder={t('session.terminalLiveInputStatus.liveInput', 'Live input')} placeholderTextColor={colors.textMuted} selectionColor={colors.accentBlue} - accessibilityLabel="Live terminal input" - accessibilityHint="Typed text is sent directly to the active terminal" + accessibilityLabel={t('session.commandDock.liveInputLabel', 'Live terminal input')} + accessibilityHint={t( + 'session.commandDock.liveInputHint', + 'Typed text is sent directly to the active terminal' + )} showSoftInputOnFocus autoCapitalize="none" autoCorrect={false} @@ -157,7 +162,11 @@ export function MobileSessionCommandDock({ } }} // The preview stands in for an empty draft, so its placeholder must not show through. - placeholder={commandDictationPreview ? undefined : 'Type a command…'} + placeholder={ + commandDictationPreview + ? undefined + : t('session.commandDock.commandPlaceholder', 'Type a command…') + } placeholderTextColor={colors.textMuted} autoCapitalize="none" autoCorrect={autocompleteEnabled} @@ -199,7 +208,7 @@ export function MobileSessionCommandDock({ style={[styles.sendButton, !canSend && styles.sendButtonDisabled]} disabled={!canSend} onPress={() => void handleSend()} - accessibilityLabel="Send command" + accessibilityLabel={t('session.commandDock.sendCommand', 'Send command')} > diff --git a/mobile/src/session/MobileSessionContentRow.tsx b/mobile/src/session/MobileSessionContentRow.tsx index 89127e2a4cc..edd6f39e8c7 100644 --- a/mobile/src/session/MobileSessionContentRow.tsx +++ b/mobile/src/session/MobileSessionContentRow.tsx @@ -4,6 +4,7 @@ import { SessionDockColumn } from './SessionDockColumn' import { dismissMobileSessionCreateWarningState } from './mobile-session-create-warning-state' import { colors } from '../theme/mobile-theme' import { useMobileColorScheme } from '../theme/use-mobile-color-scheme' +import { useMobileTranslation } from '../i18n/use-mobile-translation' import { styles } from './mobile-session-styles' import type { MobileSessionController } from './use-mobile-session-controller' import { MobileSessionActiveContent } from './MobileSessionActiveContent' @@ -18,6 +19,7 @@ export function MobileSessionContentRow({ keysInRail: boolean }) { useMobileColorScheme() + const t = useMobileTranslation() const { hostId, worktreeId, @@ -43,7 +45,10 @@ export function MobileSessionContentRow({ setCreateWarningState(dismissMobileSessionCreateWarningState)} - accessibilityLabel="Dismiss workspace creation warning" + accessibilityLabel={t( + 'session.contentRow.dismissCreationWarning', + 'Dismiss workspace creation warning' + )} hitSlop={8} > diff --git a/mobile/src/session/MobileSessionDiffLineRow.tsx b/mobile/src/session/MobileSessionDiffLineRow.tsx index 468f8233f56..258c8d783f9 100644 --- a/mobile/src/session/MobileSessionDiffLineRow.tsx +++ b/mobile/src/session/MobileSessionDiffLineRow.tsx @@ -3,6 +3,7 @@ import { MessageSquare, Plus, X } from 'lucide-react-native' import { MobileSyntaxSegments } from '../components/MobileSyntaxSegments' import { colors } from '../theme/mobile-theme' import { useMobileColorScheme } from '../theme/use-mobile-color-scheme' +import { useMobileTranslation } from '../i18n/use-mobile-translation' import { styles } from './mobile-session-styles' import type { DiffComment } from '../../../src/shared/diff-comment-types' import type { RenderableDiffLine } from './mobile-session-route-types' @@ -35,6 +36,7 @@ export function DiffLineRow({ onDeleteComment: (commentId: string) => void }) { useMobileColorScheme() + const t = useMobileTranslation() const commentLine = line.newLineNumber const isCommenting = commentLine !== undefined && activeCommentLine === commentLine const canComment = commentLine !== undefined @@ -53,7 +55,14 @@ export function DiffLineRow({ @@ -91,12 +102,22 @@ export function DiffLineRow({ - Line {comment.lineNumber} + + {t('session.diffLineRow.lineNumber', 'Line {{line}}', { + line: comment.lineNumber + })} + onDeleteComment(comment.id)} - accessibilityLabel={`Delete note on line ${comment.lineNumber}`} + accessibilityLabel={t( + 'session.diffLineRow.deleteNoteLabel', + 'Delete note on line {{line}}', + { + line: comment.lineNumber + } + )} > @@ -112,7 +133,7 @@ export function DiffLineRow({ style={[styles.textInput, styles.diffCommentInput]} value={commentDraft} onChangeText={onDraftChange} - placeholder="Add review note" + placeholder={t('session.diffLineRow.notePlaceholder', 'Add review note')} placeholderTextColor={colors.textMuted} editable={!commentsBusy} multiline @@ -125,7 +146,7 @@ export function DiffLineRow({ disabled={commentsBusy} onPress={onCancelComment} > - Cancel + {t('common.cancel', 'Cancel')} - Save note + + {t('session.diffLineRow.saveNote', 'Save note')} + diff --git a/mobile/src/session/MobileSessionFileReader.tsx b/mobile/src/session/MobileSessionFileReader.tsx index a87d20fa4a9..2a05e2b2b14 100644 --- a/mobile/src/session/MobileSessionFileReader.tsx +++ b/mobile/src/session/MobileSessionFileReader.tsx @@ -23,6 +23,7 @@ import { import { MobileHtmlPreview } from '../components/MobileHtmlPreview' import { colors } from '../theme/mobile-theme' import { useMobileColorScheme } from '../theme/use-mobile-color-scheme' +import { useMobileTranslation } from '../i18n/use-mobile-translation' import { styles } from './mobile-session-styles' import type { DiffComment } from '../../../src/shared/diff-comment-types' import type { @@ -61,6 +62,7 @@ function FileReaderDocument({ diffCommentActions }: FileReaderProps) { useMobileColorScheme() + const t = useMobileTranslation() const syntaxLanguage = useMemo( () => resolveMobileSyntaxLanguage(relativePath || title, language), [language, relativePath, title] @@ -197,7 +199,7 @@ function FileReaderDocument({ if (doc.kind === 'diff') { const activeDiffSyntax = diffSyntax?.doc === doc && diffSyntax.language === syntaxLanguage ? diffSyntax.lines : null - const commentCount = diffCommentActions?.comments.length ?? 0 + const count = diffCommentActions?.comments.length ?? 0 const unsentCommentCount = diffCommentActions ? sendableMobileDiffComments( diffCommentActions.comments, @@ -205,7 +207,7 @@ function FileReaderDocument({ ).length : 0 const commentsBusy = diffCommentActions?.busy === true - const canCopyNotes = commentCount > 0 && !commentsBusy + const canCopyNotes = count > 0 && !commentsBusy const canSendNotes = unsentCommentCount > 0 && !commentsBusy return ( @@ -214,9 +216,13 @@ function FileReaderDocument({ - {commentCount === 0 - ? 'No review notes' - : `${commentCount} review ${commentCount === 1 ? 'note' : 'notes'}`} + {count === 0 + ? t('session.fileReader.noReviewNotes', 'No review notes') + : count === 1 + ? t('session.fileReader.reviewNotes_one', '{{count}} review note', { count }) + : t('session.fileReader.reviewNotes_other', '{{count}} review notes', { + count + })} @@ -227,10 +233,10 @@ function FileReaderDocument({ ]} disabled={!canCopyNotes} onPress={() => void diffCommentActions.onCopyAll()} - accessibilityLabel="Copy review notes" + accessibilityLabel={t('session.fileReader.copyReviewNotes', 'Copy review notes')} > - Copy + {t('common.copy', 'Copy')} - Send + + {t('session.fileReader.send', 'Send')} + @@ -282,7 +293,7 @@ function FileReaderDocument({ source={{ uri: doc.dataUri }} style={styles.imagePreview} resizeMode="contain" - accessibilityLabel={`${title} image`} + accessibilityLabel={t('session.fileReader.imageLabel', '{{title}} image', { title })} /> @@ -295,7 +306,11 @@ function FileReaderDocument({ style={styles.filePreviewScroll} contentContainerStyle={styles.filePreviewContent} > - + {revealSidebar ? ( ) : null} {backInRail ? null : ( @@ -63,7 +65,7 @@ export function MobileSessionHeader({ style={[styles.sessionTitleBlock, tabsInTopBar && styles.sessionTitleBlockBesideTabs]} > - {worktreeName || 'Terminal'} + {worktreeName || t('session.header.terminalTitle', 'Terminal')} @@ -86,7 +92,7 @@ export function MobileSessionHeader({ {!isFloatingWorkspaceRoute && ( handlePanelTap('files')} /> @@ -94,7 +100,7 @@ export function MobileSessionHeader({ {!isFolderWorkspaceRoute && !isFloatingWorkspaceRoute && ( handlePanelTap('sourceControl')} /> @@ -102,7 +108,7 @@ export function MobileSessionHeader({ {showHeaderMoreButton ? ( setShowHeaderMoreActions(true)} /> diff --git a/mobile/src/session/MobileSessionHeaderMoreActionsSheet.tsx b/mobile/src/session/MobileSessionHeaderMoreActionsSheet.tsx index 2b759dd5d5b..432e090b145 100644 --- a/mobile/src/session/MobileSessionHeaderMoreActionsSheet.tsx +++ b/mobile/src/session/MobileSessionHeaderMoreActionsSheet.tsx @@ -3,6 +3,7 @@ import { MobileAgentSessionHistoryIcon } from '../agent-history/MobileAgentSessi import { ActionSheetModal } from '../components/ActionSheetModal' import { colors } from '../theme/mobile-theme' import { useMobileColorScheme } from '../theme/use-mobile-color-scheme' +import { useMobileTranslation } from '../i18n/use-mobile-translation' type Props = { visible: boolean @@ -22,6 +23,7 @@ export function MobileSessionHeaderMoreActionsSheet({ onClose }: Props) { useMobileColorScheme() + const t = useMobileTranslation() return ( ( - {doc.truncated ? 'Copy loaded' : 'Copy'} + {doc.truncated + ? t('session.markdownReader.copyLoaded', 'Copy loaded') + : t('common.copy', 'Copy')} - Copy path + + {t('session.markdownReader.copyPath', 'Copy path')} + {showRefresh ? ( - Refresh + + {t('common.refresh', 'Refresh')} + ) : null} {doc.isDirty ? ( - Discard + + {t('session.markdownReader.discard', 'Discard')} + ) : null} {showSave ? ( @@ -126,7 +138,7 @@ export function MarkdownReader({ {doc.saving ? ( ) : ( - Save + {t('common.save', 'Save')} )} ) : null} diff --git a/mobile/src/session/MobileSessionRouteScreen.tsx b/mobile/src/session/MobileSessionRouteScreen.tsx index 6461cb49a68..e5875f7da7d 100644 --- a/mobile/src/session/MobileSessionRouteScreen.tsx +++ b/mobile/src/session/MobileSessionRouteScreen.tsx @@ -3,11 +3,13 @@ import { useMobileSessionController } from './use-mobile-session-controller' import { ActivityIndicator, Text, View } from 'react-native' import { colors } from '../theme/mobile-theme' import { useMobileColorScheme } from '../theme/use-mobile-color-scheme' +import { useMobileTranslation } from '../i18n/use-mobile-translation' import { styles } from './mobile-session-styles' // Keep the screen importable by the page; native route modules mount the shell. export function MobileSessionRouteScreen() { useMobileColorScheme() + const t = useMobileTranslation() const controller = useMobileSessionController() // Readiness stays true on refocus so returning from Settings preserves mounted panes. if ( @@ -19,7 +21,9 @@ export function MobileSessionRouteScreen() { - Opening workspace + + {t('session.routeScreen.openingWorkspace', 'Opening workspace')} + ) diff --git a/mobile/src/session/MobileSessionSheets.tsx b/mobile/src/session/MobileSessionSheets.tsx index 75f8c6b3ce7..ebb7de01ee2 100644 --- a/mobile/src/session/MobileSessionSheets.tsx +++ b/mobile/src/session/MobileSessionSheets.tsx @@ -1,4 +1,5 @@ import { Platform } from 'react-native' +import { useMobileTranslation } from '../i18n/use-mobile-translation' import { getRepoIdFromWorktreeId } from '../../../src/shared/worktree/id' import { useClipboardWriter } from '../platform/clipboard' import { Copy, FileText, Globe, RefreshCw, SquareTerminal } from 'lucide-react-native' @@ -7,6 +8,7 @@ import { QuickCommandsSheet } from './QuickCommandsSheet' import { triggerSuccess, triggerError } from '../platform/haptics' import { ActionSheetModal } from '../components/ActionSheetModal' import { TextInputModal } from '../components/TextInputModal' +import { MobileTerminalRenameModal } from './MobileTerminalRenameModal' import { ConfirmModal } from '../components/ConfirmModal' import { CustomKeyModal } from '../components/CustomKeyModal' import { MobileDictationSetupSheet } from '../components/MobileDictationSetupSheet' @@ -18,6 +20,7 @@ import { isTerminalPhoneDisplayMode } from './mobile-session-route-helpers' import type { MobileSessionController } from './use-mobile-session-controller' export function MobileSessionSheets({ controller }: { controller: MobileSessionController }) { + const t = useMobileTranslation() const { worktreeId, isFolderWorkspaceRoute, @@ -50,7 +53,6 @@ export function MobileSessionSheets({ controller }: { controller: MobileSessionC leaveDrafts, setLeaveDrafts, setRenameTarget, - renameTarget, setCustomKeys, showCustomKeyModal, setShowCustomKeyModal, @@ -61,9 +63,7 @@ export function MobileSessionSheets({ controller }: { controller: MobileSessionC closeDictationSetup, browserScreencastSupported, quickCommandsSupported, - quickCommandAgentPromptMaxLength, showToast, - nativeChatTranscriptIsLocalReadable, nativeChatController, toggleTabChatView, toggleDisplayMode, @@ -80,7 +80,6 @@ export function MobileSessionSheets({ controller }: { controller: MobileSessionC handleCreateMarkdownNote, handleCreateBrowser, handleBrowserNavigationCommand, - handleRenameTerminal, handleCloseTerminal, handleCloseSessionTab, bulkCloseActions, @@ -115,16 +114,17 @@ export function MobileSessionSheets({ controller }: { controller: MobileSessionC : getRepoIdFromWorktreeId(worktreeId) || null } repoName={worktreeName || null} - agentPromptMaxLength={quickCommandAgentPromptMaxLength} + agentPromptMaxLength={controller.quickCommandAgentPromptMaxLength} onLaunch={launchQuickCommand} /> { setShowCreateTabDrawer(false) @@ -138,12 +138,19 @@ export function MobileSessionSheets({ controller }: { controller: MobileSessionC ? [] : [ { - label: 'Browser', + id: 'browser', + label: t('session.sheets.browser', 'Browser'), icon: Globe, closeBeforePress: true, onPress: () => { if (browserScreencastSupported !== true) { - showToast('Desktop update required for mobile browser streaming', 1600) + showToast( + t( + 'session.sheets.browserUpdateRequired', + 'Desktop update required for mobile browser streaming' + ), + 1600 + ) return } setShowCreateBrowserModal(true) @@ -152,7 +159,8 @@ export function MobileSessionSheets({ controller }: { controller: MobileSessionC ...(onPairedHost ? [ { - label: 'Markdown Note', + id: 'markdownNote', + label: t('session.sheets.markdownNote', 'Markdown Note'), icon: FileText, onPress: () => { setShowCreateTabDrawer(false) @@ -167,12 +175,13 @@ export function MobileSessionSheets({ controller }: { controller: MobileSessionC /> { const delivery = pendingDiffNotesDelivery @@ -184,11 +193,11 @@ export function MobileSessionSheets({ controller }: { controller: MobileSessionC .writeText(delivery.prompt) .then(() => { triggerSuccess() - showToast('Notes copied') + showToast(t('session.sheets.notesCopied', 'Notes copied')) }) .catch(() => { triggerError() - showToast("Couldn't copy notes", 1500) + showToast(t('session.sheets.copyNotesFailed', "Couldn't copy notes"), 1500) }) } } @@ -197,12 +206,12 @@ export function MobileSessionSheets({ controller }: { controller: MobileSessionC /> tab.type === 'terminal'), isTabChatView: nativeChatController.isTabChatView, - nativeChatTranscriptIsLocalReadable, + nativeChatTranscriptIsLocalReadable: controller.nativeChatTranscriptIsLocalReadable, onDismiss: () => setActionTarget(null), onToggleChat: toggleTabChatView, isPhoneMode: (handle) => isTerminalPhoneDisplayMode(handle, terminalModes), @@ -217,10 +226,11 @@ export function MobileSessionSheets({ controller }: { controller: MobileSessionC /> { const target = markdownActionTarget @@ -249,10 +260,11 @@ export function MobileSessionSheets({ controller }: { controller: MobileSessionC /> { const target = fileActionTarget @@ -275,7 +287,7 @@ export function MobileSessionSheets({ controller }: { controller: MobileSessionC /> { const drafts = leaveDrafts ?? [] @@ -308,12 +321,13 @@ export function MobileSessionSheets({ controller }: { controller: MobileSessionC }) .catch(() => { triggerError() - showToast("Couldn't copy drafts", 1500) + showToast(t('session.sheets.copyDraftsFailed', "Couldn't copy drafts"), 1500) }) } }, { - label: 'Discard & Leave', + id: 'discardAndLeave', + label: t('session.sheets.discardAndLeave', 'Discard & Leave'), destructive: true, onPress: () => { setLeaveDrafts(null) @@ -325,29 +339,25 @@ export function MobileSessionSheets({ controller }: { controller: MobileSessionC /> setDiscardMarkdownTarget(null)} /> - void handleRenameTerminal(value)} - onCancel={() => setRenameTarget(null)} - /> + { if (deleteKeyTarget) { diff --git a/mobile/src/session/MobileSessionTabStrip.tsx b/mobile/src/session/MobileSessionTabStrip.tsx index 19a78bdb531..ec078c841a9 100644 --- a/mobile/src/session/MobileSessionTabStrip.tsx +++ b/mobile/src/session/MobileSessionTabStrip.tsx @@ -2,6 +2,7 @@ import { useCallback, useMemo, useRef, useState } from 'react' import { View, Text, ScrollView, Pressable } from 'react-native' import { File, FileText, Globe, Plus, SquareTerminal } from 'lucide-react-native' import { triggerMediumImpact } from '../platform/haptics' +import { useMobileTranslation } from '../i18n/use-mobile-translation' import { MobileAgentIcon } from '../components/MobileAgentIcon' import { AgentStateDot } from '../components/AgentStateDot' import { useNow } from '../hooks/use-now' @@ -35,6 +36,7 @@ export function MobileSessionTabStrip({ inTopBar: boolean }) { useMobileColorScheme() + const t = useMobileTranslation() const { connState, activeSessionTabId, @@ -189,54 +191,61 @@ export function MobileSessionTabStrip({ refreshHiddenTabs() }} > - {visibleTabs.map((t, index) => { - const tabState = tabStates.get(t.id) - const hostAgentIdentity = getMobileTerminalTabHostAgentIdentity(t, terminals) - const title = getMobileSessionTabTitle(t, hostAgentIdentity) + {visibleTabs.map((tab, index) => { + const tabState = tabStates.get(tab.id) + const hostAgentIdentity = getMobileTerminalTabHostAgentIdentity(tab, terminals) + const title = getMobileSessionTabTitle(tab, hostAgentIdentity) return ( { const { x, width } = e.nativeEvent.layout - tabLayoutsRef.current.set(t.id, { x, width }) - if (t.id === activeSessionTabIdRef.current) { - scrollActiveTabIntoView(t.id, false) + tabLayoutsRef.current.set(tab.id, { x, width }) + if (tab.id === activeSessionTabIdRef.current) { + scrollActiveTabIntoView(tab.id, false) } refreshHiddenTabs() }} - onPress={() => pressTab(t)} + onPress={() => pressTab(tab)} accessibilityRole="tab" accessibilityLabel={ - tabState && tabState !== 'idle' ? `${title}, ${agentStateLabel(tabState)}` : title + tabState && tabState !== 'idle' + ? t('session.tabStrip.tabStatus', '{{title}}, {{status}}', { + title, + status: agentStateLabel(tabState) + }) + : title } - accessibilityState={{ selected: t.id === activeSessionTabId }} + accessibilityState={{ selected: tab.id === activeSessionTabId }} onLongPress={() => { triggerMediumImpact() - openSessionTabActionSheetAfterKeyboardDismiss(t) + openSessionTabActionSheetAfterKeyboardDismiss(tab) }} delayLongPress={400} > {tabState && tabState !== 'idle' ? : null} - {t.type === 'browser' && ( + {tab.type === 'browser' && ( )} - {t.type === 'markdown' && ( + {tab.type === 'markdown' && ( )} - {t.type === 'file' && ( + {tab.type === 'file' && ( )} - {t.type === 'agent-session' && } - {t.type === 'terminal' && + {tab.type === 'agent-session' && ( + + )} + {tab.type === 'terminal' && (() => { - const agentId = resolveMobileTerminalTabAgentId(t, hostAgentIdentity) + const agentId = resolveMobileTerminalTabAgentId(tab, hostAgentIdentity) return agentId ? ( ) : ( @@ -249,7 +258,7 @@ export function MobileSessionTabStrip({ })()} {title} @@ -277,7 +286,7 @@ export function MobileSessionTabStrip({ setCreateError('') setShowCreateTabDrawer(true) }} - accessibilityLabel="New tab" + accessibilityLabel={t('session.tabStrip.newTab', 'New tab')} icon={Plus} /> {/* Why: stable placement matters, while old hosts must stay gated because they strip agentPrompt. */} @@ -290,8 +299,14 @@ export function MobileSessionTabStrip({ } showToast( quickCommandsSupported === false - ? 'Desktop update required for quick commands' - : 'Checking desktop capabilities — try again in a moment', + ? t( + 'session.tabStrip.quickCommandsUpdateRequired', + 'Desktop update required for quick commands' + ) + : t( + 'session.tabStrip.checkingCapabilities', + 'Checking desktop capabilities — try again in a moment' + ), 1600 ) }} diff --git a/mobile/src/session/MobileTerminalInputActions.tsx b/mobile/src/session/MobileTerminalInputActions.tsx index dbab3fb4355..6a93a1a555a 100644 --- a/mobile/src/session/MobileTerminalInputActions.tsx +++ b/mobile/src/session/MobileTerminalInputActions.tsx @@ -1,6 +1,7 @@ import { ActivityIndicator, Pressable, type StyleProp, type ViewStyle } from 'react-native' import { ImagePlus, Mic } from 'lucide-react-native' import { colors } from '../theme/mobile-theme' +import { useMobileTranslation } from '../i18n/use-mobile-translation' import { useMobileColorScheme } from '../theme/use-mobile-color-scheme' import { keepHeldPressThroughLongPress } from './held-press-long-press' @@ -44,6 +45,7 @@ export function MobileTerminalInputActions({ onDictationCancel }: MobileTerminalInputActionsProps) { useMobileColorScheme() + const t = useMobileTranslation() const dictationActive = dictation.isStarting || dictation.isRecording // Dictation outlives a reconnect, so its stop and cancel must too; only starting needs the connection. const micDisabled = !canSend && !dictationActive && !dictation.isProcessing @@ -56,8 +58,15 @@ export function MobileTerminalInputActions({ onPress={onAttachImage} onLongPress={onAttachFile} delayLongPress={350} - accessibilityLabel={isAttaching ? 'Sending image' : 'Attach a photo'} - accessibilityHint="Long press to attach a file instead" + accessibilityLabel={ + isAttaching + ? t('session.terminalInputActions.sendingImage', 'Sending image') + : t('session.terminalInputActions.attachPhoto', 'Attach a photo') + } + accessibilityHint={t( + 'session.terminalInputActions.attachFileHint', + 'Long press to attach a file instead' + )} > {isAttaching ? ( @@ -89,12 +98,15 @@ export function MobileTerminalInputActions({ aria-busy={dictation.isStarting || dictation.isProcessing} accessibilityLabel={ dictation.isRecording - ? 'Stop voice dictation' + ? t('session.terminalInputActions.stopDictation', 'Stop voice dictation') : dictation.isProcessing - ? 'Transcribing voice dictation' + ? t( + 'session.terminalInputActions.transcribingDictation', + 'Transcribing voice dictation' + ) : dictation.isStarting - ? 'Starting voice dictation' - : 'Start voice dictation' + ? t('session.terminalInputActions.startingDictation', 'Starting voice dictation') + : t('session.terminalInputActions.startDictation', 'Start voice dictation') } > {dictation.isProcessing ? ( diff --git a/mobile/src/session/MobileTerminalLiveInputStatus.tsx b/mobile/src/session/MobileTerminalLiveInputStatus.tsx index 2d508498ea4..3b40bb5dbf1 100644 --- a/mobile/src/session/MobileTerminalLiveInputStatus.tsx +++ b/mobile/src/session/MobileTerminalLiveInputStatus.tsx @@ -1,6 +1,7 @@ import { Text, View } from 'react-native' import { colors, radii, spacing, typography } from '../theme/mobile-theme' import { useMobileColorScheme } from '../theme/use-mobile-color-scheme' +import { useMobileTranslation } from '../i18n/use-mobile-translation' import { themedStyleSheet } from '../theme/themed-styles' import { TailClippedText } from '../components/TailClippedText' import { @@ -24,23 +25,25 @@ export function MobileTerminalLiveInputStatus({ heard }: MobileTerminalLiveInputStatusProps) { useMobileColorScheme() + const t = useMobileTranslation() const dictationTitle = mobileDictationStatusTitle(dictation) if (!dictationTitle && !isAttaching) { return null } - const title = dictationTitle ?? 'Uploading image' + const title = + dictationTitle ?? t('session.terminalLiveInputStatus.uploadingImageTitle', 'Uploading image') // Live input can't type into the terminal until the text is final, so the newest words show here. const detail = heard || (dictation.isRecording ? dictationMode === 'hold' - ? 'Release mic to stop' - : 'Tap mic to stop' + ? t('session.terminalLiveInputStatus.releaseMicHint', 'Release mic to stop') + : t('session.terminalLiveInputStatus.stopHint', 'Tap mic to stop') : dictation.isProcessing - ? 'Transcribing on desktop' + ? t('session.terminalLiveInputStatus.transcribing', 'Transcribing on desktop') : dictation.isStarting - ? 'Preparing microphone' - : 'Uploading image to host') + ? t('session.terminalLiveInputStatus.preparingMic', 'Preparing microphone') + : t('session.terminalLiveInputStatus.uploadingImage', 'Uploading image to host')) return ( diff --git a/mobile/src/session/MobileTerminalRenameModal.tsx b/mobile/src/session/MobileTerminalRenameModal.tsx new file mode 100644 index 00000000000..750b3a0a1cc --- /dev/null +++ b/mobile/src/session/MobileTerminalRenameModal.tsx @@ -0,0 +1,25 @@ +import { TextInputModal } from '../components/TextInputModal' +import { useMobileTranslation } from '../i18n/use-mobile-translation' +import type { MobileSessionController } from './use-mobile-session-controller' + +export function MobileTerminalRenameModal({ + controller +}: { + controller: Pick< + MobileSessionController, + 'renameTarget' | 'setRenameTarget' | 'handleRenameTerminal' + > +}) { + const t = useMobileTranslation() + const { renameTarget, setRenameTarget, handleRenameTerminal } = controller + return ( + void handleRenameTerminal(value)} + onCancel={() => setRenameTarget(null)} + /> + ) +} diff --git a/mobile/src/session/QuickCommandEditorForm.tsx b/mobile/src/session/QuickCommandEditorForm.tsx index 962fb1a1718..2246384ed81 100644 --- a/mobile/src/session/QuickCommandEditorForm.tsx +++ b/mobile/src/session/QuickCommandEditorForm.tsx @@ -8,13 +8,14 @@ import { MobileAgentIcon } from '../components/MobileAgentIcon' import { getQuickCommandAgentLabel, getTerminalQuickCommandBodySize, - formatKilobytes, MAX_QUICK_COMMAND_LABEL_LENGTH, MAX_QUICK_COMMAND_TERMINAL_TEXT_LENGTH } from '../terminal/quick-commands' import type { QuickCommandDraft } from './quick-command-draft' import { isQuickCommandDraftPromptTooLong, isQuickCommandDraftValid } from './quick-command-draft' import { themedStyleSheet } from '../theme/themed-styles' +import { useMobileTranslation } from '../i18n/use-mobile-translation' +import { getUiIntlLocale } from '../i18n/ui-intl-locale' type Props = { draft: QuickCommandDraft @@ -40,6 +41,7 @@ function ActionToggle({ onChange: (action: QuickCommandDraft['action']) => void }) { useMobileColorScheme() + const t = useMobileTranslation() return ( {(['terminal-command', 'agent-prompt'] as const).map((action) => { @@ -57,7 +59,9 @@ function ActionToggle({ accessibilityState={{ selected }} > - {action === 'terminal-command' ? 'Terminal Command' : 'Agent Prompt'} + {action === 'terminal-command' + ? t('session.quickCommandEditorForm.terminalCommand', 'Terminal Command') + : t('session.quickCommandEditorForm.agentPrompt', 'Agent Prompt')} ) @@ -80,6 +84,7 @@ export function QuickCommandEditorForm({ onSave }: Props) { useMobileColorScheme() + const t = useMobileTranslation() const hasRepoScope = repoId !== null const [advancedOpen, setAdvancedOpen] = useState(draft.scope.type === 'repo') const isAgent = draft.action === 'agent-prompt' @@ -94,12 +99,12 @@ export function QuickCommandEditorForm({ return ( - Label + {t('session.quickCommandEditorForm.label', 'Label')} onChange({ label })} - placeholder="Start dev server" + placeholder={t('session.quickCommandEditorForm.labelPlaceholder', 'Start dev server')} placeholderTextColor={colors.textMuted} autoCapitalize="none" autoCorrect={false} @@ -108,13 +113,13 @@ export function QuickCommandEditorForm({ - Action + {t('session.quickCommandEditorForm.action', 'Action')} onChange({ action })} /> {isAgent ? ( - Agent + {t('session.quickCommandEditorForm.agent', 'Agent')} [styles.select, pressed && styles.pressed]} onPress={onOpenAgentPicker} @@ -126,7 +131,9 @@ export function QuickCommandEditorForm({ {getQuickCommandAgentLabel(draft.agent)} ) : ( - Choose agent + + {t('session.quickCommandEditorForm.chooseAgent', 'Choose agent')} + )} @@ -134,12 +141,23 @@ export function QuickCommandEditorForm({ ) : null} - {isAgent ? 'Prompt' : 'Command Text'} + + {isAgent + ? t('session.quickCommandEditorForm.prompt', 'Prompt') + : t('session.quickCommandEditorForm.commandText', 'Command Text')} + onChange(isAgent ? { prompt: text } : { command: text })} - placeholder={isAgent ? 'Ask the agent to investigate this workspace' : 'npm run dev'} + placeholder={ + isAgent + ? t( + 'session.quickCommandEditorForm.promptPlaceholder', + 'Ask the agent to investigate this workspace' + ) + : 'npm run dev' + } placeholderTextColor={colors.textMuted} autoCapitalize="none" autoCorrect={false} @@ -152,17 +170,39 @@ export function QuickCommandEditorForm({ {/* Why static text in the alert: a live count would re-announce on every keystroke. */} {promptSize.olderHostCap - ? 'Too long for this host. Update Orca on the host to save longer prompts.' - : 'Too long to save.'} + ? t( + 'session.quickCommandEditorForm.olderHostPromptTooLong', + 'Too long for this host. Update Orca on the host to save longer prompts.' + ) + : t('session.quickCommandEditorForm.promptTooLong', 'Too long to save.')} {promptSize.unit === 'bytes' - ? `${formatKilobytes(promptSize.used)} / ${formatKilobytes(promptSize.max)}` - : `${promptSize.used.toLocaleString()} / ${promptSize.max.toLocaleString()} characters`} + ? t( + 'session.quickCommandEditorForm.promptSizeKilobytes', + '{{used}} KB / {{max}} KB', + { + used: Math.ceil(promptSize.used / 1024).toLocaleString(getUiIntlLocale()), + max: Math.ceil(promptSize.max / 1024).toLocaleString(getUiIntlLocale()) + } + ) + : t( + 'session.quickCommandEditorForm.promptSizeCharacters', + '{{used}} / {{max}} characters', + { + used: promptSize.used.toLocaleString(getUiIntlLocale()), + max: promptSize.max.toLocaleString(getUiIntlLocale()) + } + )} ) : isAgent ? ( - Supports skills, file paths, and built-in commands. + + {t( + 'session.quickCommandEditorForm.promptHint', + 'Supports skills, file paths, and built-in commands.' + )} + ) : null} @@ -178,7 +218,9 @@ export function QuickCommandEditorForm({ ) : ( )} - Advanced + + {t('session.quickCommandEditorForm.advanced', 'Advanced')} + {advancedOpen ? ( @@ -186,9 +228,14 @@ export function QuickCommandEditorForm({ {!isAgent ? ( - Append Enter + + {t('session.quickCommandEditorForm.appendEnter', 'Append Enter')} + - Submit immediately instead of only inserting text. + {t( + 'session.quickCommandEditorForm.appendEnterDescription', + 'Submit immediately instead of only inserting text.' + )} - Scope + {t('session.quickCommandEditorForm.scope', 'Scope')} {(['global', 'repo'] as const).map((scopeType) => { const selected = draft.scope.type === scopeType @@ -228,7 +275,9 @@ export function QuickCommandEditorForm({ accessibilityState={{ selected, disabled }} > - {scopeType === 'global' ? 'Global' : 'Project'} + {scopeType === 'global' + ? t('session.quickCommandEditorForm.globalScope', 'Global') + : t('session.quickCommandEditorForm.projectScope', 'Project')} ) @@ -250,7 +299,7 @@ export function QuickCommandEditorForm({ onPress={onCancel} accessibilityRole="button" > - Cancel + {t('common.cancel', 'Cancel')} - {mode === 'edit' ? 'Save' : 'Add Quick Command'} + {mode === 'edit' + ? t('common.save', 'Save') + : t('session.quickCommandEditorForm.addQuickCommand', 'Add Quick Command')} diff --git a/mobile/src/session/QuickCommandRow.tsx b/mobile/src/session/QuickCommandRow.tsx index fe79c0b06a4..16819bcb9d8 100644 --- a/mobile/src/session/QuickCommandRow.tsx +++ b/mobile/src/session/QuickCommandRow.tsx @@ -13,6 +13,7 @@ import { isAgentQuickCommand } from '../terminal/quick-commands' import { themedStyleSheet } from '../theme/themed-styles' +import { useMobileTranslation } from '../i18n/use-mobile-translation' type QuickCommandRowProps = { command: TerminalQuickCommand @@ -37,6 +38,7 @@ export function QuickCommandRow({ disabled }: QuickCommandRowProps) { useMobileColorScheme() + const t = useMobileTranslation() const clipboard = useClipboardWriter() const isAgent = isAgentQuickCommand(command) const body = getTerminalQuickCommandBody(command) @@ -99,12 +101,12 @@ export function QuickCommandRow({ const copyDisabled = disabled || !canCopy const copyLabel = copyStatus === 'copied' - ? 'Copied' + ? t('common.copied', 'Copied') : copyStatus === 'failed' - ? "Couldn't copy" + ? t('session.quickCommandRow.copyFailed', "Couldn't copy") : canCopy - ? `Copy ${command.label}` - : 'Nothing to copy' + ? t('session.quickCommandRow.copyCommand', 'Copy {{label}}', { label: command.label }) + : t('session.quickCommandRow.nothingToCopy', 'Nothing to copy') const copyIconColor = copyStatus === 'copied' ? colors.statusGreen @@ -119,7 +121,9 @@ export function QuickCommandRow({ disabled={disabled} onPress={() => onLaunch(command)} accessibilityRole="button" - accessibilityLabel={`Run ${command.label}`} + accessibilityLabel={t('session.quickCommandRow.runCommand', 'Run {{label}}', { + label: command.label + })} > {isAgent ? ( @@ -160,7 +164,9 @@ export function QuickCommandRow({ style={({ pressed }) => [styles.rowAction, pressed && !disabled && styles.pressed]} disabled={disabled} onPress={() => onEdit(command)} - accessibilityLabel={`Edit ${command.label}`} + accessibilityLabel={t('session.quickCommandRow.editCommand', 'Edit {{label}}', { + label: command.label + })} > @@ -168,7 +174,9 @@ export function QuickCommandRow({ style={({ pressed }) => [styles.rowAction, pressed && !disabled && styles.pressed]} disabled={disabled} onPress={() => onDelete(command)} - accessibilityLabel={`Delete ${command.label}`} + accessibilityLabel={t('session.quickCommandRow.deleteCommand', 'Delete {{label}}', { + label: command.label + })} > diff --git a/mobile/src/session/QuickCommandsList.tsx b/mobile/src/session/QuickCommandsList.tsx index a29a33a08f8..85404564b07 100644 --- a/mobile/src/session/QuickCommandsList.tsx +++ b/mobile/src/session/QuickCommandsList.tsx @@ -10,6 +10,7 @@ import type { TerminalQuickCommand } from '../../../src/shared/terminal-quick-co import type { TuiAgent } from '../../../src/shared/tui-agent' import { supportsTerminalAgentQuickCommand } from '../terminal/quick-commands' import { QuickCommandRow } from './QuickCommandRow' +import { useMobileTranslation } from '../i18n/use-mobile-translation' export const QUICK_COMMAND_SUPPORTED_AGENTS = MOBILE_AGENT_CATALOG.filter((entry) => supportsTerminalAgentQuickCommand(entry.id) @@ -47,6 +48,7 @@ export function QuickCommandsList({ onAdd }: ListProps) { useMobileColorScheme() + const t = useMobileTranslation() const hasVisible = repoCommands.length + globalCommands.length > 0 // Why: keep an active filter clearable if a delete or paired desktop edit // leaves only one command while the sheet is open. @@ -60,7 +62,10 @@ export function QuickCommandsList({ style={styles.searchInput} value={query} onChangeText={onQueryChange} - placeholder="Search quick commands..." + placeholder={t( + 'session.quickCommandsList.searchPlaceholder', + 'Search quick commands...' + )} placeholderTextColor={colors.textMuted} autoCapitalize="none" autoCorrect={false} @@ -78,16 +83,20 @@ export function QuickCommandsList({ ) : null} {!loading && totalCount === 0 ? ( - No quick commands yet. + + {t('session.quickCommandsList.emptyState', 'No quick commands yet.')} + ) : null} {!loading && totalCount > 0 && !hasVisible ? ( - No matching quick commands. + + {t('session.quickCommandsList.noMatches', 'No matching quick commands.')} + ) : null} {repoCommands.length > 0 ? ( 0 ? ( - New quick command + + {t('session.quickCommandsList.newCommand', 'New quick command')} + ) diff --git a/mobile/src/session/QuickCommandsSheet.tsx b/mobile/src/session/QuickCommandsSheet.tsx index 4b200bd1af8..9468a9349e3 100644 --- a/mobile/src/session/QuickCommandsSheet.tsx +++ b/mobile/src/session/QuickCommandsSheet.tsx @@ -17,6 +17,7 @@ import { quickCommandToDraft, type QuickCommandDraft } from './quick-command-draft' +import { useMobileTranslation } from '../i18n/use-mobile-translation' type Props = { visible: boolean @@ -40,6 +41,7 @@ export function QuickCommandsSheet({ onLaunch }: Props) { useMobileColorScheme() + const t = useMobileTranslation() const { commands, loading, ready, error, persist } = useQuickCommands({ client, enabled: visible @@ -114,12 +116,17 @@ export function QuickCommandsSheet({ // Why: quick commands sync with desktop, so an accidental one-tap delete // removes shared data rather than only dismissing a local row. Alert.alert( - `Delete "${command.label || 'Untitled'}"?`, - 'This quick command will be removed from your saved list.', + t('session.quickCommandsSheet.deleteConfirmTitle', 'Delete "{{label}}"?', { + label: command.label || t('session.quickCommandsSheet.untitled', 'Untitled') + }), + t( + 'session.quickCommandsSheet.deleteConfirmMessage', + 'This quick command will be removed from your saved list.' + ), [ - { text: 'Cancel', style: 'cancel' }, + { text: t('common.cancel', 'Cancel'), style: 'cancel' }, { - text: 'Delete', + text: t('common.delete', 'Delete'), style: 'destructive', onPress: () => { void persist({ type: 'delete', id: command.id }) @@ -162,11 +169,11 @@ export function QuickCommandsSheet({ const title = view === 'editor' ? draft?.id - ? 'Edit Quick Command' - : 'Add Quick Command' + ? t('session.quickCommandsSheet.editTitle', 'Edit Quick Command') + : t('session.quickCommandsSheet.addTitle', 'Add Quick Command') : view === 'agent' - ? 'Choose Agent' - : 'Quick Commands' + ? t('session.quickCommandsSheet.chooseAgentTitle', 'Choose Agent') + : t('session.quickCommandsSheet.title', 'Quick Commands') return ( @@ -181,7 +188,7 @@ export function QuickCommandsSheet({ setView(view === 'agent' ? 'editor' : 'list') }} accessibilityRole="button" - accessibilityLabel="Back" + accessibilityLabel={t('common.back', 'Back')} > @@ -193,7 +200,10 @@ export function QuickCommandsSheet({ {view === 'editor' && draft ? ( - Save terminal commands or agent prompts for quick access. + {t( + 'session.quickCommandsSheet.editorDescription', + 'Save terminal commands or agent prompts for quick access.' + )} ) : null} diff --git a/mobile/src/session/QuickCommandsTabButton.tsx b/mobile/src/session/QuickCommandsTabButton.tsx index a23b3e79001..e9e5f76c2e4 100644 --- a/mobile/src/session/QuickCommandsTabButton.tsx +++ b/mobile/src/session/QuickCommandsTabButton.tsx @@ -4,6 +4,7 @@ import { SquareChevronRight } from 'lucide-react-native' import { MobileSessionHeaderIconButton } from './MobileSessionHeaderIconButton' import { styles } from './mobile-session-styles' import { useMobileColorScheme } from '../theme/use-mobile-color-scheme' +import { useMobileTranslation } from '../i18n/use-mobile-translation' type Props = { disabled: boolean @@ -11,6 +12,7 @@ type Props = { } export function QuickCommandsTabButton({ disabled, onPress }: Props) { + const t = useMobileTranslation() useMobileColorScheme() return ( <> @@ -18,7 +20,7 @@ export function QuickCommandsTabButton({ disabled, onPress }: Props) { diff --git a/mobile/src/session/ai-vault-resume-launch.ts b/mobile/src/session/ai-vault-resume-launch.ts index 7efd180439f..fa17de08b10 100644 --- a/mobile/src/session/ai-vault-resume-launch.ts +++ b/mobile/src/session/ai-vault-resume-launch.ts @@ -24,6 +24,7 @@ import type { AiVaultResumeTargetStatus } from '../../../src/shared/ai-vault-res import { QODER_OWNED_TERMINAL_CREATE_CAPABILITY } from '../../../src/shared/qoder-terminal-create-capability' import { agentHistoryHostStatusSchema } from '../agent-history/agent-history-reply-schema' import { readMobileRuntimeHostPlatform } from '../transport/mobile-runtime-host-platform' +import { translate } from '../i18n/mobile-i18n' export function readMobileAiVaultResumeHost(statusResult: unknown) { const status = agentHistoryHostStatusSchema.safeParse(statusResult) @@ -112,7 +113,7 @@ export async function resumeAiVaultSessionInTerminal( let terminalTab terminalTab = interpretOrThrowRefusalMessage( () => reviewTerminalCreateRun.interpret(created), - 'Failed to create terminal' + translate('session.aiVaultResumeLaunch.createFailed', 'Failed to create terminal') ) if (launchAtCreate) { return terminalTab @@ -130,10 +131,12 @@ export async function resumeAiVaultSessionInTerminal( let accepted accepted = interpretOrThrowRefusalMessage( () => reviewTerminalSendRun.interpret(sent), - 'Failed to send resume command' + translate('session.aiVaultResumeLaunch.sendFailed', 'Failed to send resume command') ) if (!accepted) { - throw new Error('Terminal input is locked') + throw new Error( + translate('session.aiVaultResumeLaunch.inputLocked', 'Terminal input is locked') + ) } return terminalTab } diff --git a/mobile/src/session/ai-vault-resume-preparation.ts b/mobile/src/session/ai-vault-resume-preparation.ts index bbd5dcb5923..92fc5738624 100644 --- a/mobile/src/session/ai-vault-resume-preparation.ts +++ b/mobile/src/session/ai-vault-resume-preparation.ts @@ -7,6 +7,7 @@ import { import { LOCAL_EXECUTION_HOST_ID } from '../../../src/shared/execution-host' import { aiVaultResumePreparationRun } from './mobile-session-launch-operations' import type { RpcOperationSender } from '../transport/rpc-operation-sender' +import { translate } from '../i18n/mobile-i18n' // Why: without an explicit timeout, a socket drop mid-resume parks the request // on the reconnect waiter for the full reconnect budget, pinning the spinner. @@ -43,7 +44,11 @@ export async function prepareMobileAiVaultSessionResume( return session } throw new Error( - response.error?.message || 'Could not prepare this legacy Codex session. Retry resume.' + response.error?.message || + translate( + 'session.aiVaultResumePreparation.prepareFailed', + 'Could not prepare this legacy Codex session. Retry resume.' + ) ) } const result = aiVaultResumePreparationRun.interpret(response) diff --git a/mobile/src/session/floating-workspace.ts b/mobile/src/session/floating-workspace.ts index 7df5f8ba84b..9c2647eef97 100644 --- a/mobile/src/session/floating-workspace.ts +++ b/mobile/src/session/floating-workspace.ts @@ -1,13 +1,19 @@ +import { translate } from '../i18n/mobile-i18n' + // Match desktop’s synthetic id: Floating Workspace has no backing repo/worktree and always uses the local runtime. export const FLOATING_WORKSPACE_WORKTREE_ID = 'global-floating-terminal' export const FLOATING_WORKSPACE_TITLE = 'Floating Workspace' +export function getFloatingWorkspaceTitle(): string { + return translate('session.floatingWorkspace.title', 'Floating Workspace') +} + export function isFloatingWorkspaceWorktreeId(worktreeId: string | null | undefined): boolean { return worktreeId === FLOATING_WORKSPACE_WORKTREE_ID } // Seed name in the route before session tabs load. export function floatingWorkspaceSessionPath(hostId: string | undefined): string { - return `/h/${hostId}/session/${FLOATING_WORKSPACE_WORKTREE_ID}?name=${encodeURIComponent(FLOATING_WORKSPACE_TITLE)}` + return `/h/${hostId}/session/${FLOATING_WORKSPACE_WORKTREE_ID}?name=${encodeURIComponent(getFloatingWorkspaceTitle())}` } diff --git a/mobile/src/session/github-pr-mutations.ts b/mobile/src/session/github-pr-mutations.ts index 64db6f50233..2621cdbd766 100644 --- a/mobile/src/session/github-pr-mutations.ts +++ b/mobile/src/session/github-pr-mutations.ts @@ -25,6 +25,7 @@ import { type GitHubPrRepoSlug } from './github-pr-repo-slug' import type { RpcOperationSender } from '../transport/rpc-operation-sender' +import { translate } from '../i18n/mobile-i18n' export type { GitHubPrMutationOutcome } from './github-pr-mutation-outcome' @@ -62,7 +63,7 @@ export function fetchUpdatePRTitle( prRepo: args.prRepo }) ), - 'Failed to update title.' + translate('session.githubPrMutations.titleUpdateFailed', 'Failed to update title.') ) } @@ -264,7 +265,10 @@ export function fetchResolveReviewThread( { prRepo: args.prRepo } ) ), - 'Failed to update review thread.' + translate( + 'session.githubPrMutations.reviewThreadUpdateFailed', + 'Failed to update review thread.' + ) ) } diff --git a/mobile/src/session/github-pr-rpc.ts b/mobile/src/session/github-pr-rpc.ts index dad7900d853..8648c72563c 100644 --- a/mobile/src/session/github-pr-rpc.ts +++ b/mobile/src/session/github-pr-rpc.ts @@ -18,6 +18,7 @@ import { import type { GitHubPrSettleableOperation } from './github-pr-mutation-outcome' import { githubPrRequestParams, type GitHubPrRepoSlug } from './github-pr-repo-slug' import type { RpcOperationSender } from '../transport/rpc-operation-sender' +import { translate } from '../i18n/mobile-i18n' export { buildGithubPrParams, @@ -39,7 +40,9 @@ async function settleGithubPrRead( read: GitHubPrSettleableOperation, send: () => Promise ): Promise> { - const fallback = `Request failed: ${read.operation.method}` + const fallback = translate('session.githubPrRpc.requestFailed', 'Request failed: {{method}}', { + method: read.operation.method + }) let reply: RpcResponse try { reply = await send() diff --git a/mobile/src/session/mobile-agent-child-row-text.ts b/mobile/src/session/mobile-agent-child-row-text.ts index 55ea1816077..4f64dda003f 100644 --- a/mobile/src/session/mobile-agent-child-row-text.ts +++ b/mobile/src/session/mobile-agent-child-row-text.ts @@ -5,17 +5,43 @@ import { } from '../../../src/shared/agent-child-row-lead-trail' import type { AgentChildRowModel } from '../../../src/shared/agent-child-row-model' import type { AgentChildDisplayState } from '../../../src/shared/agent-status-child-work-display' -import { sayBackgroundTaskEnglish } from '../../../src/shared/background-task-copy' +import { sayMobileBackgroundTask } from './mobile-background-task-copy' +import { translate } from '../i18n/mobile-i18n' import { agentStateLabel } from '../worktree/agent-row-display' export function mobileAgentChildStateLabel(state: AgentChildDisplayState): string { - return state === 'unverifiable' ? 'No recent update' : agentStateLabel(state) + return state === 'unverifiable' + ? translate('session.agentChildRow.noRecentUpdate', 'No recent update') + : agentStateLabel(state) } -const MOBILE_AGENT_CHILD_ROW_COPY = { - stateLabel: mobileAgentChildStateLabel, - ended: () => 'Ended', - sayBackgroundTask: sayBackgroundTaskEnglish +function mobileAgentChildRowCopy() { + return { + stateLabel: mobileAgentChildStateLabel, + ended: () => translate('session.agentChildRow.ended', 'Ended'), + sayBackgroundTask: sayMobileBackgroundTask + } +} + +function noUpdateDuration(deltaMs: number): string { + const minutes = Math.max(0, Math.floor(deltaMs / 60_000)) + if (minutes < 60) { + const count = minutes + return count === 1 + ? translate('session.agentChildRow.minutes_one', '{{count}}m', { count }) + : translate('session.agentChildRow.minutes_other', '{{count}}m', { count }) + } + const hours = Math.floor(minutes / 60) + if (hours < 24) { + const count = hours + return count === 1 + ? translate('session.agentChildRow.hours_one', '{{count}}h', { count }) + : translate('session.agentChildRow.hours_other', '{{count}}h', { count }) + } + const count = Math.floor(hours / 24) + return count === 1 + ? translate('session.agentChildRow.days_one', '{{count}}d', { count }) + : translate('session.agentChildRow.days_other', '{{count}}d', { count }) } export function mobileAgentChildRowName(row: AgentChildRowModel): string { @@ -26,9 +52,13 @@ export function mobileAgentChildRowText( row: AgentChildRowModel, now: number ): { lead: string; trail: string } { - return agentChildRowLeadTrail( - row, - mobileAgentChildRowName(row), - agentChildRowDetailText(row, now, MOBILE_AGENT_CHILD_ROW_COPY) - ) + const detail = + row.detail?.kind === 'no-update' + ? translate('session.agentChildRow.noUpdate', 'No update in {{value0}}', { + value0: noUpdateDuration(now - row.recencyAt) + }) + : row.detail?.kind === 'role' && (!row.detail.agentType || row.detail.agentType === 'unknown') + ? translate('session.agentChildRow.agent', 'Agent') + : agentChildRowDetailText(row, now, mobileAgentChildRowCopy()) + return agentChildRowLeadTrail(row, mobileAgentChildRowName(row), detail) } diff --git a/mobile/src/session/mobile-agent-message-attribution.ts b/mobile/src/session/mobile-agent-message-attribution.ts index d756749ecf3..802bee95e4a 100644 --- a/mobile/src/session/mobile-agent-message-attribution.ts +++ b/mobile/src/session/mobile-agent-message-attribution.ts @@ -1,10 +1,9 @@ +import { translate } from '../i18n/mobile-i18n' import { agentMessageSendersShown, type AgentMessageSource } from '../../../src/shared/agent-session/agent-session-message-source' -const UNNAMED_SENDER = 'an agent' - export function agentMessageAttribution( lead: 'From' | 'Message from', source: AgentMessageSource | undefined @@ -13,7 +12,33 @@ export function agentMessageAttribution( return null } const { shown, more } = agentMessageSendersShown(source) + if (shown.length === 0 || (shown.length === 1 && shown[0].name === null && more === 0)) { + return lead === 'Message from' + ? translate('session.agentMessageAttribution.messageFromUnnamed', 'Message from an agent') + : translate('session.agentMessageAttribution.fromUnnamed', 'From an agent') + } // One name per sender, repeats included: two senders that share a name are still two. - const names = shown.map((sender) => sender.name ?? UNNAMED_SENDER) - return `${lead} ${names.length > 0 ? names.join(', ') : UNNAMED_SENDER}${more > 0 ? ` +${more}` : ''}` + const sender = shown + .map( + (sender) => + sender.name ?? translate('session.agentMessageAttribution.unnamedSender', 'an agent') + ) + .join(', ') + if (more > 0) { + return lead === 'Message from' + ? translate( + 'session.agentMessageAttribution.messageFromMore', + 'Message from {{sender}} +{{count}}', + { sender, count: more } + ) + : translate('session.agentMessageAttribution.fromMore', 'From {{sender}} +{{count}}', { + sender, + count: more + }) + } + return lead === 'Message from' + ? translate('session.agentMessageAttribution.messageFrom', 'Message from {{sender}}', { + sender + }) + : translate('session.agentMessageAttribution.from', 'From {{sender}}', { sender }) } diff --git a/mobile/src/session/mobile-background-task-copy.ts b/mobile/src/session/mobile-background-task-copy.ts new file mode 100644 index 00000000000..b254f610618 --- /dev/null +++ b/mobile/src/session/mobile-background-task-copy.ts @@ -0,0 +1,107 @@ +import type { + BackgroundTaskCopyId, + BackgroundTaskSay +} from '../../../src/shared/background-task-copy' +import { translate } from '../i18n/mobile-i18n' + +export const sayMobileBackgroundTask: BackgroundTaskSay = (id, values) => { + const count = values?.count + const countValues = count === undefined ? undefined : { count } + const phraseValues = values?.value0 === undefined ? undefined : { value0: values.value0 } + const copy = { + monitoring: () => translate('session.backgroundTask.monitoring', 'Monitoring background tasks'), + stop: () => translate('session.backgroundTask.stop', 'Stop'), + stopTask: () => translate('session.backgroundTask.stopTask', 'Stop {{value0}}', phraseValues), + stopAll: () => translate('session.backgroundTask.stopAll', 'Stop background tasks'), + agent: () => translate('session.backgroundTask.agent', 'Background agent'), + workflow: () => translate('session.backgroundTask.workflow', 'Background workflow'), + command: () => translate('session.backgroundTask.command', 'Background command'), + monitor: () => translate('session.backgroundTask.monitor', 'Background monitor'), + task: () => translate('session.backgroundTask.task', 'Background task'), + detailsUnavailable: () => + translate( + 'session.backgroundTask.detailsUnavailable', + 'Task details are unavailable for this session.' + ), + countAgents: () => + count === 1 + ? translate('session.backgroundTask.countAgents_one', '{{count}} agent', countValues) + : translate('session.backgroundTask.countAgents_other', '{{count}} agents', countValues), + countShell: () => + count === 1 + ? translate('session.backgroundTask.countShell_one', '{{count}} shell', countValues) + : translate('session.backgroundTask.countShell_other', '{{count}} shells', countValues), + countShellCommandOne: () => + translate('session.backgroundTask.countShellCommandOne', '1 shell command'), + countMonitors: () => + count === 1 + ? translate('session.backgroundTask.countMonitors_one', '{{count}} monitor', countValues) + : translate( + 'session.backgroundTask.countMonitors_other', + '{{count}} monitors', + countValues + ), + countWorkflows: () => + count === 1 + ? translate('session.backgroundTask.countWorkflows_one', '{{count}} workflow', countValues) + : translate( + 'session.backgroundTask.countWorkflows_other', + '{{count}} workflows', + countValues + ), + countTasks: () => + count === 1 + ? translate('session.backgroundTask.countTasks_one', '{{count}} task', countValues) + : translate('session.backgroundTask.countTasks_other', '{{count}} tasks', countValues), + headerTotal: () => + count === 1 + ? translate( + 'session.backgroundTask.headerTotalCount_one', + '{{count}} background task', + countValues + ) + : translate( + 'session.backgroundTask.headerTotalCount_other', + '{{count}} background tasks', + countValues + ), + stateWorking: () => translate('session.backgroundTask.stateWorking', 'working'), + stateWorkingCount: () => + translate('session.backgroundTask.stateWorkingCount', '{{value0}} working', phraseValues), + stateMonitoring: () => translate('session.backgroundTask.stateMonitoring', 'monitoring'), + stateMonitoringCount: () => + translate( + 'session.backgroundTask.stateMonitoringCount', + '{{value0}} monitoring', + phraseValues + ), + stateWaiting: () => translate('session.backgroundTask.stateWaiting', 'waiting'), + stateWaitingCount: () => + translate('session.backgroundTask.stateWaitingCount', '{{value0}} waiting', phraseValues), + stateBlocked: () => translate('session.backgroundTask.stateBlocked', 'blocked'), + stateBlockedCount: () => + translate('session.backgroundTask.stateBlockedCount', '{{value0}} blocked', phraseValues), + stateDone: () => translate('session.backgroundTask.stateDone', 'done'), + stateDoneCount: () => + translate('session.backgroundTask.stateDoneCount', '{{value0}} done', phraseValues), + stateIdle: () => translate('session.backgroundTask.stateIdle', 'stopped'), + stateIdleCount: () => + translate('session.backgroundTask.stateIdleCount', '{{value0}} stopped', phraseValues), + stateUnverifiable: () => + translate('session.backgroundTask.stateUnverifiable', 'status unavailable'), + stateUnverifiableCount: () => + translate( + 'session.backgroundTask.stateUnverifiableCount', + '{{value0}} with status unavailable', + phraseValues + ), + reasonWaiting: () => translate('session.backgroundTask.reasonWaiting', 'needs approval'), + reasonBlocked: () => translate('session.backgroundTask.reasonBlocked', 'failed'), + groupAgents: () => translate('session.backgroundTask.groupAgents', 'Agents'), + groupShell: () => translate('session.backgroundTask.groupShell', 'Shell'), + groupMonitors: () => translate('session.backgroundTask.groupMonitors', 'Monitors'), + groupWorkflows: () => translate('session.backgroundTask.groupWorkflows', 'Workflows'), + groupTasks: () => translate('session.backgroundTask.groupTasks', 'Tasks') + } satisfies Record string> + return copy[id]() +} diff --git a/mobile/src/session/mobile-bulk-close-sheet-actions.ts b/mobile/src/session/mobile-bulk-close-sheet-actions.ts index 8281a337337..2c7eb3d3395 100644 --- a/mobile/src/session/mobile-bulk-close-sheet-actions.ts +++ b/mobile/src/session/mobile-bulk-close-sheet-actions.ts @@ -2,6 +2,7 @@ import type { MarkdownDocState, MobileSessionTab } from './mobile-session-route- import type { ActionSheetAction } from '../components/ActionSheetModal' import { selectBulkCloseTabs } from './mobile-tab-close-selection' import { closeTabsAfterConfirmation } from '../../../src/shared/bulk-tab-close-confirmation' +import { translate } from '../i18n/mobile-i18n' type BulkCloseSheetDeps = { sessionTabsRef: { readonly current: readonly MobileSessionTab[] } @@ -50,7 +51,8 @@ export function createBulkCloseSheetActions(deps: BulkCloseSheetDeps) { } return [ { - label: 'Close Other Tabs', + id: 'closeOtherTabs', + label: translate('session.bulkCloseSheetActions.closeOtherTabs', 'Close Other Tabs'), destructive: true, // Why: the confirm is its own native Modal; wait for this sheet to unmount (#10331). closeBeforePress: true, @@ -72,7 +74,8 @@ export function createCloseWithBulkActions( ) { return (target: MobileSessionTab | null, dismiss: () => void): ActionSheetAction[] => [ { - label: 'Close', + id: 'close', + label: translate('common.close', 'Close'), destructive: true, onPress: () => { dismiss() diff --git a/mobile/src/session/mobile-bulk-tab-close-copy.ts b/mobile/src/session/mobile-bulk-tab-close-copy.ts new file mode 100644 index 00000000000..e69fb54c1e5 --- /dev/null +++ b/mobile/src/session/mobile-bulk-tab-close-copy.ts @@ -0,0 +1,17 @@ +import type { Translate } from '../i18n/mobile-i18n' + +export function mobileBulkTabCloseCopy(t: Translate) { + return { + title: { + others: t('session.bulkTabClose.titleOthers', 'Close other tabs?'), + right: t('session.bulkTabClose.titleRight', 'Close tabs to the right?'), + left: t('session.bulkTabClose.titleLeft', 'Close tabs to the left?'), + editors: t('session.bulkTabClose.titleEditors', 'Close all editor tabs?') + }, + description: t( + 'session.bulkTabClose.description', + 'The selected tabs will be closed. Pinned tabs will stay open.' + ), + confirm: t('session.bulkTabClose.confirm', 'Close tabs') + } +} diff --git a/mobile/src/session/mobile-diff-review-loaders.ts b/mobile/src/session/mobile-diff-review-loaders.ts index 216649e4bb8..45f2d0cd6e4 100644 --- a/mobile/src/session/mobile-diff-review-loaders.ts +++ b/mobile/src/session/mobile-diff-review-loaders.ts @@ -30,6 +30,7 @@ import type { RpcResponse } from '../transport/types' import type { MobileDiffReviewQueueItem } from './mobile-diff-review-queue' import type { ReviewDiffState, ReviewScreenState } from './mobile-diff-review-screen-model' import { reviewDescriptorFromItem } from './mobile-diff-review-screen-model' +import { translate } from '../i18n/mobile-i18n' type BranchCompareLoadResult = { result: MobileGitBranchCompareResult | null @@ -73,12 +74,27 @@ export async function loadMobileDiffReviewBranchCompare( } catch (error) { return { result: null, - error: refusedRpcMessageOrFallback(error, 'Committed changes unavailable') + error: refusedRpcMessageOrFallback( + error, + translate( + 'session.diffReviewLoaders.committedChangesUnavailable', + 'Committed changes unavailable' + ) + ) } } } catch (err) { // A transport drop surfaces its own message verbatim; only a refusal falls back above. - return { result: null, error: err instanceof Error ? err.message : 'Committed changes failed' } + return { + result: null, + error: + err instanceof Error + ? err.message + : translate( + 'session.diffReviewLoaders.committedChangesFailed', + 'Committed changes failed' + ) + } } } @@ -95,16 +111,32 @@ export async function loadMobileDiffReviewSnapshot( !statusReply.ok && isMobileGitUnavailable(statusReply.error?.code, statusReply.error?.message) ) { - return { kind: 'unavailable', message: 'Update Orca desktop to review changes on mobile.' } + return { + kind: 'unavailable', + message: translate( + 'session.diffReviewLoaders.desktopUpdateRequired', + 'Update Orca desktop to review changes on mobile.' + ) + } } let status try { status = gitStatusProjectionRead.interpret(statusReply) } catch (error) { - throw new Error(refusedRpcMessageOrFallback(error, 'Unable to load changes')) + throw new Error( + refusedRpcMessageOrFallback( + error, + translate('session.diffReviewLoaders.changesLoadFailed', 'Unable to load changes') + ) + ) } if (!status) { - throw new Error('Source control response was invalid') + throw new Error( + translate( + 'session.diffReviewLoaders.invalidSourceControlResponse', + 'Source control response was invalid' + ) + ) } // Interpret after both reads settle so worktree refusal cannot preempt the compare failure. @@ -119,7 +151,12 @@ export async function loadMobileDiffReviewSnapshot( try { metadata = reviewWorktreeMetadataRead.interpret(worktreeReply) } catch (error) { - throw new Error(refusedRpcMessageOrFallback(error, 'Unable to load review notes')) + throw new Error( + refusedRpcMessageOrFallback( + error, + translate('session.diffReviewLoaders.reviewNotesLoadFailed', 'Unable to load review notes') + ) + ) } const comments = normalizeMobileDiffComments(metadata.diffComments, worktreeId) @@ -174,7 +211,12 @@ export async function loadMobileDiffReviewDiff(input: DiffLoadInput): Promise { const summary = branchCompare?.summary if (!summary || !summary.headOid || !summary.mergeBase) { - throw new Error('Committed diff is unavailable') + throw new Error( + translate( + 'session.diffReviewLoaders.committedDiffUnavailable', + 'Committed diff is unavailable' + ) + ) } const reply = await reviewBranchFileDiffRead.request(client, { worktree: `id:${worktreeId}`, diff --git a/mobile/src/session/mobile-diff-review-queue.ts b/mobile/src/session/mobile-diff-review-queue.ts index a1c46cb7b83..35f9b1620b5 100644 --- a/mobile/src/session/mobile-diff-review-queue.ts +++ b/mobile/src/session/mobile-diff-review-queue.ts @@ -1,4 +1,5 @@ import { compareFileNames } from '../../../src/shared/file-name-sort' +import { translate } from '../i18n/mobile-i18n' import type { DiffComment, DiffReviewScope, @@ -191,7 +192,11 @@ function statusEntryToQueueItem( oldPath: entry.oldPath, status: entry.status, title: entry.path, - subtitle: scope === 'staged' ? 'Staged' : 'Unstaged', + get subtitle() { + return scope === 'staged' + ? translate('session.diffReviewQueue.staged', 'Staged') + : translate('session.diffReviewQueue.unstaged', 'Unstaged') + }, added: entry.added, removed: entry.removed, canStage: isMobileGitStageableEntry(entry), @@ -227,7 +232,9 @@ function branchEntryToQueueItem( oldPath: entry.oldPath, status: entry.status, title: entry.path, - subtitle: 'Committed on branch', + get subtitle() { + return translate('session.diffReviewQueue.committedOnBranch', 'Committed on branch') + }, added: entry.added, removed: entry.removed, canStage: false, diff --git a/mobile/src/session/mobile-diff-review-screen-model.ts b/mobile/src/session/mobile-diff-review-screen-model.ts index 4e5f92eb8cd..85900299414 100644 --- a/mobile/src/session/mobile-diff-review-screen-model.ts +++ b/mobile/src/session/mobile-diff-review-screen-model.ts @@ -10,6 +10,7 @@ import type { import type { MobileDiffReviewFileDescriptor } from './mobile-diff-review-state' import type { MobileHighlightedDiffLine } from './mobile-file-syntax' import type { MobileReviewTerminalTab } from './review-terminal-reply-schema' +import { translate } from '../i18n/mobile-i18n' export type ReviewScreenState = | { kind: 'loading' } @@ -109,9 +110,11 @@ export function nextReviewIndexAfterMarkReviewed({ export function mobileReviewScopeLabel(item: MobileDiffReviewQueueItem): string { if (item.scope === 'branch') { - return 'Branch' + return translate('session.diffReviewScreenModel.branch', 'Branch') } - return item.scope === 'staged' ? 'Staged' : 'Unstaged' + return item.scope === 'staged' + ? translate('session.diffReviewScreenModel.staged', 'Staged') + : translate('session.diffReviewScreenModel.unstaged', 'Unstaged') } export function mobileReviewCountLabel(count: number, singular: string, plural: string): string { diff --git a/mobile/src/session/mobile-native-chat-attach-actions.ts b/mobile/src/session/mobile-native-chat-attach-actions.ts index bf2a070012f..3fe6021549f 100644 --- a/mobile/src/session/mobile-native-chat-attach-actions.ts +++ b/mobile/src/session/mobile-native-chat-attach-actions.ts @@ -1,5 +1,6 @@ import { AtSign, Image, Package, Paperclip, Terminal } from 'lucide-react-native' import type { ActionSheetAction } from '../components/ActionSheetModal' +import { translate } from '../i18n/mobile-i18n' // Dismiss the sheet before opening pickers because iOS cannot present them over its Modal. export function mobileNativeChatAttachActions(args: { @@ -12,12 +13,19 @@ export function mobileNativeChatAttachActions(args: { }): ActionSheetAction[] { const actions: ActionSheetAction[] = [] if (args.onPhotos) { - actions.push({ label: 'Photos', icon: Image, closeBeforePress: true, onPress: args.onPhotos }) + actions.push({ + id: 'photos', + label: translate('session.nativeChatAttachActions.photos', 'Photos'), + icon: Image, + closeBeforePress: true, + onPress: args.onPhotos + }) } if (args.onFiles) { actions.push({ - label: 'Files', - hint: 'PDF, text, code…', + id: 'files', + label: translate('session.nativeChatAttachActions.files', 'Files'), + hint: translate('session.nativeChatAttachActions.fileTypes', 'PDF, text, code…'), icon: Paperclip, closeBeforePress: true, onPress: args.onFiles @@ -25,19 +33,37 @@ export function mobileNativeChatAttachActions(args: { } actions.push( { - label: 'Workspace file', + id: 'workspaceFile', + label: translate('session.nativeChatAttachActions.workspaceFile', 'Workspace file'), icon: AtSign, closeBeforePress: true, onPress: args.onWorkspaceFile }, { - label: 'Skills', - ...(args.skillCount > 0 ? { hint: `${args.skillCount} available` } : {}), + id: 'skills', + label: translate('session.nativeChatAttachActions.skills', 'Skills'), + ...(args.skillCount > 0 + ? { + hint: translate( + 'session.nativeChatAttachActions.availableSkills', + '{{count}} available', + { + count: args.skillCount + } + ) + } + : {}), icon: Package, closeBeforePress: true, onPress: args.onSkills }, - { label: 'Commands', icon: Terminal, closeBeforePress: true, onPress: args.onCommands } + { + id: 'commands', + label: translate('session.nativeChatAttachActions.commands', 'Commands'), + icon: Terminal, + closeBeforePress: true, + onPress: args.onCommands + } ) return actions } diff --git a/mobile/src/session/mobile-native-chat-cli-fork.ts b/mobile/src/session/mobile-native-chat-cli-fork.ts index d7e0e2d5038..e835d3b60fa 100644 --- a/mobile/src/session/mobile-native-chat-cli-fork.ts +++ b/mobile/src/session/mobile-native-chat-cli-fork.ts @@ -1,5 +1,6 @@ import { SquareTerminal } from 'lucide-react-native' import type { ActionSheetAction } from '../components/ActionSheetModal' +import { translate } from '../i18n/mobile-i18n' import type { MobileSessionController } from './use-mobile-session-controller' import { agentForksConversations } from '../../../src/shared/agent-resume-argv' import { SESSION_TABS_CHAT_FORK_CLI_RUNTIME_CAPABILITY } from '../../../src/shared/session-tabs-runtime-capabilities' @@ -37,7 +38,8 @@ export function getMobileNativeChatCliForkActions(args: { } return [ { - label: 'Resume in New CLI', + id: 'resumeInNewCli', + label: translate('session.nativeChatCliFork.resumeInNewCli', 'Resume in New CLI'), icon: SquareTerminal, onPress: () => { args.onDismiss() diff --git a/mobile/src/session/mobile-native-chat-render-data.ts b/mobile/src/session/mobile-native-chat-render-data.ts index 2bf824b95aa..07133ab9182 100644 --- a/mobile/src/session/mobile-native-chat-render-data.ts +++ b/mobile/src/session/mobile-native-chat-render-data.ts @@ -1,8 +1,5 @@ import { formatAgentTypeLabel } from '../../../src/shared/agent-type-label' -import { - formatNativeChatEmptyStateCopy, - type NativeChatEmptyStateCopy -} from '../../../src/shared/native-chat/native-chat-empty-state' +import type { NativeChatEmptyStateCopy } from '../../../src/shared/native-chat/native-chat-empty-state' import { isRootAgentJournalItem } from '../../../src/shared/agent-session-journal-producer' import { stripNoiseMessages } from '../../../src/shared/native-chat-noise' import { nativeChatStreamingMessage } from '../../../src/shared/native-chat/native-chat-streaming' @@ -20,6 +17,46 @@ import { type NativeChatMessage } from '../../../src/shared/native-chat-types' import type { MobileNativeChatStatus } from './use-mobile-native-chat-session' +import { translate } from '../i18n/mobile-i18n' + +function mobileNativeChatEmptyStateCopy( + kind: 'notFound' | 'empty' | 'error', + agentLabel: string +): NativeChatEmptyStateCopy { + switch (kind) { + case 'notFound': + return { + title: translate( + 'session.nativeChatRenderData.notFoundTitle', + "Can't find this conversation yet" + ), + subtitle: translate( + 'session.nativeChatRenderData.notFoundSubtitle', + 'Send a message to start one, or open the terminal view to see what {{agent}} is doing.', + { agent: agentLabel } + ) + } + case 'empty': + return { + title: translate('session.nativeChatRenderData.emptyTitle', 'Start a chat with {{agent}}', { + agent: agentLabel + }), + subtitle: translate( + 'session.nativeChatRenderData.emptySubtitle', + 'Ask {{agent}} to inspect code, explain output, or make a change.', + { agent: agentLabel } + ) + } + case 'error': + return { + title: translate('session.nativeChatRenderData.errorTitle', 'Could not load conversation'), + subtitle: translate( + 'session.nativeChatRenderData.errorSubtitle', + 'The transcript could not be read. Toggle back to the terminal to keep working.' + ) + } + } +} export function mobileNativeChatEmptyState( status: MobileNativeChatStatus, @@ -27,18 +64,20 @@ export function mobileNativeChatEmptyState( error?: string, sessionLive = false ): NativeChatEmptyStateCopy | null { - const agentLabel = agent ? formatAgentTypeLabel(agent) : 'the agent' + const agentLabel = agent + ? formatAgentTypeLabel(agent) + : translate('session.nativeChatRenderData.defaultAgentLabel', 'the agent') switch (status) { // Missing history may mean new or unreadable; promise neither in the waiting copy. case 'waiting-session': - return formatNativeChatEmptyStateCopy('notFound', agentLabel) + return mobileNativeChatEmptyStateCopy('notFound', agentLabel) // A session the running agent just reported has no file until its first prompt. case 'awaiting-transcript': - return formatNativeChatEmptyStateCopy(sessionLive ? 'empty' : 'notFound', agentLabel) + return mobileNativeChatEmptyStateCopy(sessionLive ? 'empty' : 'notFound', agentLabel) case 'ready': - return formatNativeChatEmptyStateCopy('empty', agentLabel) + return mobileNativeChatEmptyStateCopy('empty', agentLabel) case 'error': { - const copy = formatNativeChatEmptyStateCopy('error', agentLabel) + const copy = mobileNativeChatEmptyStateCopy('error', agentLabel) return error ? { ...copy, subtitle: error } : copy } default: diff --git a/mobile/src/session/mobile-native-chat-toggle-action.ts b/mobile/src/session/mobile-native-chat-toggle-action.ts index 4e15d49ecb7..7eac6550b2c 100644 --- a/mobile/src/session/mobile-native-chat-toggle-action.ts +++ b/mobile/src/session/mobile-native-chat-toggle-action.ts @@ -1,5 +1,6 @@ import { MessageSquare, SquareTerminal } from 'lucide-react-native' import type { ActionSheetAction } from '../components/ActionSheetModal' +import { translate } from '../i18n/mobile-i18n' import { resolveMobileNativeChat, type MobileNativeChatTab @@ -28,7 +29,13 @@ export function getMobileNativeChatToggleActions(args: { const isChat = isTabChatView(tab.id) return [ { - label: isChat ? 'Switch to terminal view' : 'Switch to chat view', + id: 'toggleChatView', + label: isChat + ? translate( + 'session.nativeChatToggleAction.switchToTerminalView', + 'Switch to terminal view' + ) + : translate('session.nativeChatToggleAction.switchToChatView', 'Switch to chat view'), icon: isChat ? SquareTerminal : MessageSquare, onPress: () => { onClose() diff --git a/mobile/src/session/mobile-pr-sidebar-state.ts b/mobile/src/session/mobile-pr-sidebar-state.ts index 8285366f62e..b578f7740b4 100644 --- a/mobile/src/session/mobile-pr-sidebar-state.ts +++ b/mobile/src/session/mobile-pr-sidebar-state.ts @@ -12,6 +12,7 @@ import { } from '../../../src/shared/github/linked-pr-staleness' import type { MobileWorktreeSummary } from '../source-control/mobile-worktree-metadata-operations' import type { WorktreeGitHubPRLink } from '../source-control/mobile-pr-link' +import { translate } from '../i18n/mobile-i18n' export type PrSidebarData = { pr: PRInfo @@ -197,7 +198,12 @@ export async function loadPrSidebarData( } } catch (err) { // Rejected dependencies must settle as UI errors rather than unhandled rejections. - return failureState(err instanceof Error ? err.message : 'Unable to load pull request') + return err instanceof Error + ? failureState(err.message) + : { + kind: 'error', + message: translate('session.prSidebarState.loadFailed', 'Unable to load pull request') + } } } diff --git a/mobile/src/session/mobile-session-native-chat-dictation-copy.ts b/mobile/src/session/mobile-session-native-chat-dictation-copy.ts new file mode 100644 index 00000000000..acc353a7237 --- /dev/null +++ b/mobile/src/session/mobile-session-native-chat-dictation-copy.ts @@ -0,0 +1,12 @@ +import { translate } from '../i18n/mobile-i18n' + +export const nativeChatDictationCopy = { + insertFailedCopied: () => + translate( + 'session.useNativeChatDictation.insertFailedCopied', + "Couldn't insert dictation, so it was copied to the clipboard" + ), + insertFailed: () => + translate('session.useNativeChatDictation.insertFailed', "Couldn't insert dictation"), + inserted: () => translate('session.useNativeChatDictation.inserted', 'Dictation inserted') +} diff --git a/mobile/src/session/mobile-session-quick-command-launch.ts b/mobile/src/session/mobile-session-quick-command-launch.ts index e6e2ee47f94..b60c1c5d978 100644 --- a/mobile/src/session/mobile-session-quick-command-launch.ts +++ b/mobile/src/session/mobile-session-quick-command-launch.ts @@ -1,6 +1,7 @@ import { buildMobileQuickCommandLaunch } from '../terminal/quick-commands' import type { MobileQuickCommandLaunch } from '../terminal/quick-commands' import { triggerError } from '../platform/haptics' +import { translate } from '../i18n/mobile-i18n' import type { TerminalQuickCommand } from '../../../src/shared/terminal-quick-command-types' import type { MobileNewTabAgentOption } from './mobile-new-tab-agent-options' import type { MobileSessionAttachmentsModel } from './use-mobile-session-attachments' @@ -33,10 +34,22 @@ export function launchMobileSessionQuickCommand( const launch = buildMobileQuickCommandLaunch(command) if (!launch) { triggerError() - scope.showToast('Edit this quick command before running it', 1800) + scope.showToast( + translate( + 'session.quickCommandLaunch.editBeforeRunning', + 'Edit this quick command before running it' + ), + 1800 + ) return false } - const label = command.label.trim() || 'Quick command' - void createTerminal(launch.agent, { ...launch.options, errorToast: `Couldn't run ${label}` }) + const label = + command.label.trim() || translate('session.quickCommandLaunch.defaultLabel', 'Quick command') + void createTerminal(launch.agent, { + ...launch.options, + errorToast: translate('session.quickCommandLaunch.runFailed', "Couldn't run {{label}}", { + label + }) + }) return true } diff --git a/mobile/src/session/mobile-session-route-helpers.ts b/mobile/src/session/mobile-session-route-helpers.ts index 708807530ea..8c34c99c3ee 100644 --- a/mobile/src/session/mobile-session-route-helpers.ts +++ b/mobile/src/session/mobile-session-route-helpers.ts @@ -1,13 +1,26 @@ import type { TerminalModes } from '../terminal/terminal-webview-contract' import type { ConnectionState } from '../transport/types' +import { translate } from '../i18n/mobile-i18n' export const MOBILE_SESSION_STATUS_LABELS: Record = { - connecting: 'Connecting', - handshaking: 'Securing', - connected: 'Connected', - disconnected: 'Disconnected', - reconnecting: 'Reconnecting', - 'auth-failed': 'Pairing invalid' + get connecting() { + return translate('session.routeHelpers.connecting', 'Connecting') + }, + get handshaking() { + return translate('session.routeHelpers.securing', 'Securing') + }, + get connected() { + return translate('session.routeHelpers.connected', 'Connected') + }, + get disconnected() { + return translate('session.routeHelpers.disconnected', 'Disconnected') + }, + get reconnecting() { + return translate('session.routeHelpers.reconnecting', 'Reconnecting') + }, + get 'auth-failed'() { + return translate('session.routeHelpers.pairingInvalid', 'Pairing invalid') + } } export const TERMINAL_GESTURE_INPUT_BUCKET_CAPACITY = 64 diff --git a/mobile/src/session/mobile-session-terminal-create-copy.ts b/mobile/src/session/mobile-session-terminal-create-copy.ts new file mode 100644 index 00000000000..e34dbe76437 --- /dev/null +++ b/mobile/src/session/mobile-session-terminal-create-copy.ts @@ -0,0 +1,29 @@ +import { translate } from '../i18n/mobile-i18n' + +export const terminalCreateCopy = { + forkUnavailable: (agentName: string) => + translate( + 'session.useTerminalCreateActions.forkUnavailable', + 'Open the {{agentName}} chat on a connected, updated host and try again.', + { agentName } + ), + forkBusy: (agentName: string) => + translate( + 'session.useTerminalCreateActions.forkBusy', + 'Another terminal is opening. Wait, then retry the {{agentName}} chat.', + { agentName } + ), + createFailed: () => + translate('session.useTerminalCreateActions.createFailed', 'Failed to create terminal'), + terminalTitle: () => translate('session.useTerminalCreateActions.terminalTitle', 'Terminal'), + sendNotesFailed: () => + translate('session.useTerminalCreateActions.sendNotesFailed', 'Failed to send notes'), + inputLocked: () => + translate( + 'session.useTerminalCreateActions.inputLocked', + 'Terminal input is locked by another client.' + ), + notesSent: () => translate('session.useTerminalCreateActions.notesSent', 'Notes sent'), + sendNotesError: () => + translate('session.useTerminalCreateActions.sendNotesError', "Couldn't send notes") +} diff --git a/mobile/src/session/mobile-source-control-launch-agent.ts b/mobile/src/session/mobile-source-control-launch-agent.ts index 0e2a8545696..2dd826fd012 100644 --- a/mobile/src/session/mobile-source-control-launch-agent.ts +++ b/mobile/src/session/mobile-source-control-launch-agent.ts @@ -12,6 +12,7 @@ import { import { filterEnabledTuiAgents } from '../../../src/shared/tui-agent-selection' import type { TuiAgent } from '../../../src/shared/tui-agent' import { isMobileTuiAgent } from '../tasks/mobile-tui-agents' +import { translate } from '../i18n/mobile-i18n' import type { MobileAgentLaunchContext } from './mobile-new-tab-agent-loader' export type MobileSourceControlLaunchAgent = @@ -38,7 +39,10 @@ export function resolveMobileSourceControlLaunchAgent( ) { return { kind: 'unavailable', - message: 'The saved agent for this action is not available on this workspace host.' + message: translate( + 'session.sourceControlLaunchAgent.savedAgentUnavailable', + 'The saved agent for this action is not available on this workspace host.' + ) } } const agent = pickSourceControlLaunchAgent({ @@ -49,7 +53,13 @@ export function resolveMobileSourceControlLaunchAgent( }) return agent ? { kind: 'agent', agent, recipe } - : { kind: 'unavailable', message: 'No enabled AI agent was detected on this workspace host.' } + : { + kind: 'unavailable', + message: translate( + 'session.sourceControlLaunchAgent.noEnabledAgent', + 'No enabled AI agent was detected on this workspace host.' + ) + } } function readLaunchAgentSettings(settings: unknown): LaunchAgentSettings | null { diff --git a/mobile/src/session/mobile-structured-composer-command.ts b/mobile/src/session/mobile-structured-composer-command.ts index db0dd3b5d9e..709b16b4765 100644 --- a/mobile/src/session/mobile-structured-composer-command.ts +++ b/mobile/src/session/mobile-structured-composer-command.ts @@ -16,6 +16,7 @@ import { } from '../../../src/shared/structured-conversation-command-hold' import { agentSessionWriteNoticeEnglish } from '../../../src/shared/agent-session-refusal-notice' import type { RpcClient } from '../transport/rpc-client' +import { translate } from '../i18n/mobile-i18n' import type { MobileNativeChatSendOutcome } from './native-chat/mobile-native-chat-send' import { requestStructuredAgentSessionMutation } from './mobile-structured-agent-session-rpc' import type { MobileNativeChatSendErrorReporter } from './native-chat/use-mobile-native-chat-send-error' @@ -55,7 +56,12 @@ export async function dispatchMobileStructuredCommand(input: { return input.pending.current ? 'rejected' : null } if (input.hasAttachments) { - input.onError('Remove attachments before using a chat-session command.') + input.onError( + translate( + 'session.structuredComposerCommand.removeAttachments', + 'Remove attachments before using a chat-session command.' + ) + ) return 'rejected' } let unknown = false @@ -107,7 +113,10 @@ export async function dispatchMobileStructuredCommand(input: { unknown = true return { accepted: false, - error: 'Conversation operation was not confirmed.' + error: translate( + 'session.structuredComposerCommand.operationUnconfirmed', + 'Conversation operation was not confirmed.' + ) } } if (result.status !== 'accepted') { diff --git a/mobile/src/session/mobile-structured-send-delivery.ts b/mobile/src/session/mobile-structured-send-delivery.ts index 6b4d00a7ed8..b1e4f6a7275 100644 --- a/mobile/src/session/mobile-structured-send-delivery.ts +++ b/mobile/src/session/mobile-structured-send-delivery.ts @@ -2,6 +2,7 @@ import type { AgentSessionSendResult } from '../../../src/shared/agent-session-w import { agentSessionRefusalOperationState } from '../../../src/shared/agent-session-refusal-retry' import type { MobileNativeChatSendOutcome } from './native-chat/mobile-native-chat-send' import type { StructuredAgentSessionMutationCallResult } from './mobile-structured-agent-session-rpc' +import { translate } from '../i18n/mobile-i18n' export type MobileStructuredSendDelivery = { outcome: MobileNativeChatSendOutcome @@ -24,7 +25,10 @@ export function mobileStructuredSendDelivery( } if ('queued' in result.value && result.value.queued) { return result.value.queued.state === 'withdrawn' - ? { outcome: 'rejected', error: 'Message not sent' } + ? { + outcome: 'rejected', + error: translate('session.structuredSendDelivery.messageNotSent', 'Message not sent') + } : { outcome: 'queued', error: null } } const submission = 'submission' in result.value ? result.value.submission : undefined diff --git a/mobile/src/session/mobile-terminal-action-sheet-actions.ts b/mobile/src/session/mobile-terminal-action-sheet-actions.ts index ee432676ce6..0061a334f75 100644 --- a/mobile/src/session/mobile-terminal-action-sheet-actions.ts +++ b/mobile/src/session/mobile-terminal-action-sheet-actions.ts @@ -1,5 +1,6 @@ import { Eraser, Monitor, Smartphone } from 'lucide-react-native' import type { ActionSheetAction } from '../components/ActionSheetModal' +import { translate } from '../i18n/mobile-i18n' import type { MobileNativeChatTab } from './native-chat/mobile-native-chat-eligibility' import { getMobileNativeChatToggleActions } from './mobile-native-chat-toggle-action' @@ -41,7 +42,10 @@ export function getMobileTerminalActionSheetActions< onToggle: args.onToggleChat }), { - label: phoneMode ? 'Switch to Desktop' : 'Switch to Phone', + id: 'toggleDisplayMode', + label: phoneMode + ? translate('session.terminalActionSheetActions.switchToDesktop', 'Switch to Desktop') + : translate('session.terminalActionSheetActions.switchToPhone', 'Switch to Phone'), icon: phoneMode ? Monitor : Smartphone, onPress: () => { args.onDismiss() @@ -49,14 +53,16 @@ export function getMobileTerminalActionSheetActions< } }, { - label: 'Rename', + id: 'rename', + label: translate('session.terminalActionSheetActions.rename', 'Rename'), closeBeforePress: true, onPress: () => { args.onRename(target) } }, { - label: 'Clear Terminal', + id: 'clearTerminal', + label: translate('session.terminalActionSheetActions.clearTerminal', 'Clear Terminal'), icon: Eraser, onPress: () => { args.onDismiss() @@ -64,7 +70,8 @@ export function getMobileTerminalActionSheetActions< } }, { - label: 'Close', + id: 'close', + label: translate('common.close', 'Close'), destructive: true, onPress: () => { args.onDismiss() diff --git a/mobile/src/session/mobile-terminal-records.ts b/mobile/src/session/mobile-terminal-records.ts index 0a2b42ba146..ca4061d84a1 100644 --- a/mobile/src/session/mobile-terminal-records.ts +++ b/mobile/src/session/mobile-terminal-records.ts @@ -2,6 +2,7 @@ import type { MobileTerminalTheme } from '../terminal/terminal-webview-contract' import type { AgentStatusEntry } from '../../../src/shared/agent-status-types' import type { BrowserLoadError } from '../../../src/shared/browser-workspace-types' import { browserLoadErrorsEqual } from '../../../src/shared/browser-load-failure' +import { translate } from '../i18n/mobile-i18n' export type TerminalRecord = { handle: string @@ -237,7 +238,7 @@ export function getTerminalRecordsFromSessionTabs( return [ { handle: tab.terminal, - title: tab.title || 'Terminal', + title: tab.title || translate('session.terminalRecords.defaultTitle', 'Terminal'), terminalTheme: tab.terminalTheme, isActive: tab.isActive === true, connected: true diff --git a/mobile/src/session/mobile-terminal-tab-agent.ts b/mobile/src/session/mobile-terminal-tab-agent.ts index 43cbd847687..ce0a1e5e546 100644 --- a/mobile/src/session/mobile-terminal-tab-agent.ts +++ b/mobile/src/session/mobile-terminal-tab-agent.ts @@ -4,6 +4,7 @@ import { resolveTabAgentFromSignalSnapshot } from '../../../src/shared/tab-agent import type { AgentStatusEntry } from '../../../src/shared/agent-status-types' import type { TuiAgent } from '../../../src/shared/tui-agent' import { isBlankBrowserUrl } from '../browser/browser-url' +import { translate } from '../i18n/mobile-i18n' import type { MobileSessionTab } from './mobile-session-route-types' type MobileTerminalTabAgentIdentity = { @@ -78,22 +79,25 @@ export function getMobileSessionTabTitle( return title } if (isBlankBrowserUrl(tab.url)) { - return 'New Browser' + return translate('session.terminalTabAgent.newBrowser', 'New Browser') } - return 'Browser' + return translate('session.terminalTabAgent.browser', 'Browser') } if (tab.type === 'markdown') { - return tab.title || 'Markdown' + return tab.title || translate('session.terminalTabAgent.markdown', 'Markdown') } if (tab.type === 'file') { - return tab.title || 'File' + return tab.title || translate('session.terminalTabAgent.file', 'File') } if (tab.type === 'agent-session') { - return tab.title || 'Chat' + return tab.title || translate('session.terminalTabAgent.chat', 'Chat') } // Strip status glyphs when showing provider icons; glyph-only iOS titles otherwise leave stray colored boxes. if (resolveMobileTerminalTabAgentId(tab, hostAgentIdentity)) { - return stripLeadingAgentTitleDecorationOrEmpty(tab.title) || 'Terminal' + return ( + stripLeadingAgentTitleDecorationOrEmpty(tab.title) || + translate('session.terminalTabAgent.terminal', 'Terminal') + ) } - return tab.title || 'Terminal' + return tab.title || translate('session.terminalTabAgent.terminal', 'Terminal') } diff --git a/mobile/src/session/mobile-terminal-viewport-resubscribe.ts b/mobile/src/session/mobile-terminal-viewport-resubscribe.ts index a5820898fed..71f004a5060 100644 --- a/mobile/src/session/mobile-terminal-viewport-resubscribe.ts +++ b/mobile/src/session/mobile-terminal-viewport-resubscribe.ts @@ -1,6 +1,7 @@ // Bound nonconverging viewport refits to avoid stream churn, broken gestures and battery drain. import type { TerminalFrame } from '../terminal/terminal-webview-messages' +import { translate } from '../i18n/mobile-i18n' import type { MobileTerminalDiagnostics } from './mobile-terminal-diagnostics' export const MAX_TERMINAL_VIEWPORT_RESUBSCRIBE_ATTEMPTS = 3 @@ -219,7 +220,13 @@ export function runTerminalViewportFitPass(args: TerminalViewportFitPassArgs): v if (decision.kind === 'exhausted') { diagnostics.streamResubscribeExhausted(handle, seq, budget.attempts(handle)) if (budget.shouldAnnounceExhaustion(handle)) { - args.showToast("Couldn't fit the terminal to this screen", 4000) + args.showToast( + translate( + 'session.terminalViewportResubscribe.fitFailed', + "Couldn't fit the terminal to this screen" + ), + 4000 + ) } return } diff --git a/mobile/src/session/native-chat-diff-card-copy.ts b/mobile/src/session/native-chat-diff-card-copy.ts new file mode 100644 index 00000000000..6f325bda51d --- /dev/null +++ b/mobile/src/session/native-chat-diff-card-copy.ts @@ -0,0 +1,9 @@ +import type { Translate } from '../i18n/mobile-i18n' + +export function nativeChatDiffCardCopy(t: Translate) { + return { + diffTruncated: t('chat.diffCard.diffTruncated', 'Diff truncated'), + diffGap: t('chat.diffCard.diffGap', 'Lines not shown'), + copyDiff: t('chat.diffCard.copyDiff', 'Copy diff') + } +} diff --git a/mobile/src/session/native-chat-receipt-copy.ts b/mobile/src/session/native-chat-receipt-copy.ts new file mode 100644 index 00000000000..63093240a36 --- /dev/null +++ b/mobile/src/session/native-chat-receipt-copy.ts @@ -0,0 +1,12 @@ +import type { Translate } from '../i18n/mobile-i18n' + +export function nativeChatReceiptCopy(t: Translate, device?: string) { + return { + emptyAnswer: t('chat.receipt.emptyAnswer', 'Empty answer'), + unavailable: t('chat.receipt.unavailable', 'Selected answer unavailable'), + resolved: t('chat.receipt.resolved', 'Resolved'), + cancelled: t('chat.receipt.cancelled', 'Cancelled'), + resolver: t('chat.receipt.resolver', 'Answered on {{device}}', { device }), + cancelledBy: t('chat.receipt.cancelledBy', 'Cancelled on {{device}}', { device }) + } +} diff --git a/mobile/src/session/native-chat/composer/MobileNativeChatComposer.tsx b/mobile/src/session/native-chat/composer/MobileNativeChatComposer.tsx index 801d2c655e5..cf50f00b43e 100644 --- a/mobile/src/session/native-chat/composer/MobileNativeChatComposer.tsx +++ b/mobile/src/session/native-chat/composer/MobileNativeChatComposer.tsx @@ -14,7 +14,7 @@ import { ActivityIndicator, Keyboard, Pressable, type TextInput, Text, View } fr import { ArrowUp, Plus } from 'lucide-react-native' import { colors } from '../../../theme/mobile-theme' import { useMobileColorScheme } from '../../../theme/use-mobile-color-scheme' -import { NATIVE_CHAT_COMPOSER_PLACEHOLDER } from '../../../../../src/shared/native-chat-turn-status' +import { useMobileTranslation } from '../../../i18n/use-mobile-translation' import { MobileNativeChatComposerMenu } from './MobileNativeChatComposerMenu' import { MobileNativeChatComposerField } from './MobileNativeChatComposerField' import { MobileNativeChatComposerAttachments } from './MobileNativeChatComposerAttachments' @@ -114,7 +114,7 @@ export function MobileNativeChatComposer({ onMicPressOut, disabled = false, sendDisabled = false, - placeholder = NATIVE_CHAT_COMPOSER_PLACEHOLDER, + placeholder, filePaths = NO_FILE_PATHS, onNeedFiles, skillDiscovery, @@ -122,6 +122,7 @@ export function MobileNativeChatComposer({ stop = null }: Props): React.JSX.Element { useMobileColorScheme() + const t = useMobileTranslation() const fallbackInputRef = useRef(null) const fieldRef = inputRef ?? fallbackInputRef const [focused, setFocused] = useState(false) @@ -246,7 +247,9 @@ export function MobileNativeChatComposer({ // A terminal-backed chat has only photos to add, so its + opens the library at once. const attachPress = structured ? () => setAttachOpen(true) : onAttachImage - const attachLabel = structured ? 'Add to message' : 'Attach image' + const attachLabel = structured + ? t('chat.composer.addToMessage', 'Add to message') + : t('chat.composer.attachImage', 'Attach image') return ( @@ -284,7 +287,10 @@ export function MobileNativeChatComposer({ dictation.readSelection(e) menu.onSelectionChange(e.nativeEvent.selection) }} - placeholder={placeholder} + placeholder={ + placeholder ?? + t('chat.composer.placeholder', 'Ask anything, @ to mention files, / for commands') + } placeholderTextColor={colors.textMuted} selectionColor={colors.accentBlue} multiline @@ -340,11 +346,11 @@ export function MobileNativeChatComposer({ {editSession ? ( - Cancel + {t('common.cancel', 'Cancel')} ) : null} {showsStop && stop ? ( @@ -352,7 +358,11 @@ export function MobileNativeChatComposer({ ) : ( [ styles.sendButton, !canSend && styles.sendButtonDisabled, @@ -362,7 +372,9 @@ export function MobileNativeChatComposer({ disabled={!canSend} > {editSession ? ( - Save + + {t('common.save', 'Save')} + ) : ( ) { +function composerFileMeta( + file: Pick, + t: Translate +) { const extension = /\.([a-z0-9]{1,8})$/i.exec(file.name)?.[1] const subtype = /^[^/]+\/([a-z0-9]{1,8})$/i.exec(file.mime)?.[1] - const type = (extension ?? subtype ?? 'File').toUpperCase() - return `${type} · ${formatPreviewByteLength(file.byteLength)}` + const type = (extension ?? subtype ?? t('chat.composerAttachments.file', 'File')).toUpperCase() + return t('chat.composerAttachments.fileMeta', '{{type}} · {{size}}', { + type, + size: formatPreviewByteLength(file.byteLength) + }) } export function MobileNativeChatComposerAttachments({ @@ -37,6 +45,7 @@ export function MobileNativeChatComposerAttachments({ onRemoveFile?: (id: string) => void }): React.JSX.Element | null { useMobileColorScheme() + const t = useMobileTranslation() if (images.length === 0 && files.length === 0) { return null } @@ -60,13 +69,19 @@ export function MobileNativeChatComposerAttachments({ )} {onRemoveImage ? ( - onRemoveImage(image.id)} /> + onRemoveImage(image.id)} + /> ) : null} ))} @@ -80,16 +95,28 @@ export function MobileNativeChatComposerAttachments({ {file.name} - {composerFileMeta(file)} + {composerFileMeta(file, t)} {file.path === null ? ( - + ) : null} {onRemoveFile ? ( - onRemoveFile(file.id)} /> + onRemoveFile(file.id)} + /> ) : null} ))} diff --git a/mobile/src/session/native-chat/composer/use-mobile-native-chat-image-upload.ts b/mobile/src/session/native-chat/composer/use-mobile-native-chat-image-upload.ts index 03b87955312..f63fbba6c1f 100644 --- a/mobile/src/session/native-chat/composer/use-mobile-native-chat-image-upload.ts +++ b/mobile/src/session/native-chat/composer/use-mobile-native-chat-image-upload.ts @@ -4,6 +4,7 @@ import { type MobileNativeChatDraftDestination } from './mobile-native-chat-draft-inputs' import { useCallback, useLayoutEffect, useRef, useState } from 'react' +import { translate } from '../../../i18n/mobile-i18n' import { CLIPBOARD_IMAGE_TOO_LARGE_ERROR } from '../../../../../src/shared/clipboard-image' import type { RpcClient } from '../../../transport/rpc-client' import type { ConnectionState } from '../../../transport/types' @@ -113,18 +114,27 @@ export function useMobileNativeChatImageUpload(args: { const message = uploadError instanceof Error ? uploadError.message : String(uploadError) onError?.() if (connStateRef.current !== 'connected') { - showToast('Attach failed (disconnected)', 1500) + showToast( + translate('chat.useImageUpload.disconnected', 'Attach failed (disconnected)'), + 1500 + ) return } if (uploadError instanceof ImageLibraryPermissionError) { - showToast('Photo permission denied', 1500) + showToast( + translate('chat.useImageUpload.permissionDenied', 'Photo permission denied'), + 1500 + ) return } if (message === CLIPBOARD_IMAGE_TOO_LARGE_ERROR) { - showToast('Image too large to attach', 1500) + showToast( + translate('chat.useImageUpload.imageTooLarge', 'Image too large to attach'), + 1500 + ) return } - showToast('Attach failed', 1500) + showToast(translate('chat.useImageUpload.attachFailed', 'Attach failed'), 1500) } }, [ diff --git a/mobile/src/session/native-chat/input/MobileNativeChatAsk.tsx b/mobile/src/session/native-chat/input/MobileNativeChatAsk.tsx index df0fa0bf6a3..09a10c23aec 100644 --- a/mobile/src/session/native-chat/input/MobileNativeChatAsk.tsx +++ b/mobile/src/session/native-chat/input/MobileNativeChatAsk.tsx @@ -14,6 +14,8 @@ import { import { colors, radii, spacing, typography } from '../../../theme/mobile-theme' import { themedStyleSheet } from '../../../theme/themed-styles' import { useMobileColorScheme } from '../../../theme/use-mobile-color-scheme' +import type { Translate } from '../../../i18n/mobile-i18n' +import { useMobileTranslation } from '../../../i18n/use-mobile-translation' import { TEXT_INPUT_FONT_SIZE } from '../../../platform/text-input-font-size' import { MobileNativeChatCardHeaderAction } from '../../MobileNativeChatCardHeaderAction' import { useMobileNativeChatAskAutoAdvance } from './use-mobile-native-chat-ask-auto-advance' @@ -32,7 +34,13 @@ type Props = { freeTextInputs?: readonly (AgentJournalFreeTextInput | undefined)[] } -const CONFIRM_LABEL = { submit: 'Submit', next: 'Next', skip: 'Skip' } as const +function confirmLabels(t: Translate) { + return { + submit: t('chat.ask.submit', 'Submit'), + next: t('chat.ask.next', 'Next'), + skip: t('chat.ask.skip', 'Skip') + } +} // Sentinel index for the free-text "Other…" row (never a real option index). const OTHER = -1 @@ -46,6 +54,7 @@ export function MobileNativeChatAsk({ freeTextInputs }: Props): React.JSX.Element { useMobileColorScheme() + const t = useMobileTranslation() const { scrollRef, inputRef, revealReply, onViewportLayout } = useMobileNativeChatReplyScroll() const [index, setIndex] = useState(0) const [selections, setSelections] = useState(() => prompt.questions.map(() => [])) @@ -184,7 +193,7 @@ export function MobileNativeChatAsk({ }} > - {qq.header || `Step ${i + 1}`} + {qq.header || t('chat.ask.step', 'Step {{step}}', { step: i + 1 })} {isAnswered(i) ? ( @@ -226,7 +235,7 @@ export function MobileNativeChatAsk({ ))} {otherAllowed && !typesOnly(index) ? ( setOther(index, v)} - placeholder={freeTextInput?.placeholder ?? 'Type your answer'} + placeholder={ + freeTextInput?.placeholder ?? t('chat.ask.answerPlaceholder', 'Type your answer') + } placeholderTextColor={colors.textMuted} // A declared input (Pi's `input`) is one line unless it asks for more, as on desktop. multiline={freeTextInput ? freeTextInput.multiline === true : true} @@ -256,7 +267,7 @@ export function MobileNativeChatAsk({ disabled={submitting} hitSlop={8} > - Cancel + {t('common.cancel', 'Cancel')} {total > 1 ? ( @@ -269,7 +280,7 @@ export function MobileNativeChatAsk({ disabled={submitting} > - {CONFIRM_LABEL[askConfirmLabel(currentAnswered, isLast)]} + {confirmLabels(t)[askConfirmLabel(currentAnswered, isLast)]} diff --git a/mobile/src/session/native-chat/input/MobileNativeChatPermission.test.ts b/mobile/src/session/native-chat/input/MobileNativeChatPermission.test.ts index 030ea063f17..36df550f835 100644 --- a/mobile/src/session/native-chat/input/MobileNativeChatPermission.test.ts +++ b/mobile/src/session/native-chat/input/MobileNativeChatPermission.test.ts @@ -190,7 +190,7 @@ describe('MobileNativeChatPermission', () => { const children = Array.isArray(node.props.children) ? node.props.children : [node.props.children] - return children.includes('/repo/PLAN.md') + return children.some((child) => typeof child === 'string' && child.includes('/repo/PLAN.md')) }) expect(planFileShown).toBe(true) expect(content.findAllByProps({ children: 'raw json that must not be shown' })).toHaveLength(0) diff --git a/mobile/src/session/native-chat/input/MobileNativeChatPermission.tsx b/mobile/src/session/native-chat/input/MobileNativeChatPermission.tsx index 9e03c783aee..dce5c57b24d 100644 --- a/mobile/src/session/native-chat/input/MobileNativeChatPermission.tsx +++ b/mobile/src/session/native-chat/input/MobileNativeChatPermission.tsx @@ -7,6 +7,7 @@ import { MobileMarkdown } from '../../../components/MobileMarkdown' import { colors, radii, spacing, typography } from '../../../theme/mobile-theme' import { themedStyleSheet } from '../../../theme/themed-styles' import { useMobileColorScheme } from '../../../theme/use-mobile-color-scheme' +import { useMobileTranslation } from '../../../i18n/use-mobile-translation' import { isNewerApprovalSubject, isPlanApprovalSubject @@ -99,6 +100,7 @@ function MobileNativeChatPermissionContext({ newerSubject: boolean }): React.JSX.Element | null { useMobileColorScheme() + const t = useMobileTranslation() const neededPath = approvalBlockedPathToShow(permission) if ( !permission.description && @@ -119,13 +121,15 @@ function MobileNativeChatPermissionContext({ {permission.description ? {permission.description} : null} {permission.decisionReason ? ( - Reason: + {t('chat.permission.reason', 'Reason: ')} {permission.decisionReason} ) : null} {neededPath ? ( - Needs access to: + + {t('chat.permission.needsAccessTo', 'Needs access to: ')} + {neededPath} ) : null} @@ -133,7 +137,11 @@ function MobileNativeChatPermissionContext({ {permission.subject.filePath ? ( - Plan file: {permission.subject.filePath} + + {t('chat.permission.planFile', 'Plan file: {{filePath}}', { + filePath: permission.subject.filePath + })} + ) : null} ) : permission.detail ? ( @@ -141,7 +149,7 @@ function MobileNativeChatPermissionContext({ ) : null} {newerSubject ? ( - This request needs a newer version of Orca. + {t('chat.permission.newerVersionRequired', 'This request needs a newer version of Orca.')} ) : null} diff --git a/mobile/src/session/native-chat/input/MobileNativeChatPromptCard.tsx b/mobile/src/session/native-chat/input/MobileNativeChatPromptCard.tsx index c5aeed085e2..327f29730eb 100644 --- a/mobile/src/session/native-chat/input/MobileNativeChatPromptCard.tsx +++ b/mobile/src/session/native-chat/input/MobileNativeChatPromptCard.tsx @@ -2,6 +2,7 @@ import { Pressable, StyleSheet, Text, View } from 'react-native' import { colors, radii, spacing, typography } from '../../../theme/mobile-theme' import { themedStyleSheet } from '../../../theme/themed-styles' import { useMobileColorScheme } from '../../../theme/use-mobile-color-scheme' +import { useMobileTranslation } from '../../../i18n/use-mobile-translation' import type { AskAnswerSelection } from '../../../../../src/shared/native-chat-ask-answer' import type { AskPrompt } from '../../../../../src/shared/native-chat/input/native-chat-ask-types' import { MobileNativeChatAsk } from './MobileNativeChatAsk' @@ -50,19 +51,24 @@ export function MobileNativeChatPromptCard({ onAnswerQuestion?: (text: string, prompt?: MobileChatQuestion['prompt']) => Promise }): React.JSX.Element | null { useMobileColorScheme() + const t = useMobileTranslation() const card = ask && requiresTerminal ? ( - Answer this OMP question in the terminal. + + {t('chat.promptCard.terminalExplanation', 'Answer this OMP question in the terminal.')} + - Answer in terminal + + {t('chat.promptCard.answerInTerminal', 'Answer in terminal')} + ) : ask ? ( diff --git a/mobile/src/session/native-chat/input/MobileNativeChatPromptStrip.tsx b/mobile/src/session/native-chat/input/MobileNativeChatPromptStrip.tsx index 840c0b522c1..046b6d690b9 100644 --- a/mobile/src/session/native-chat/input/MobileNativeChatPromptStrip.tsx +++ b/mobile/src/session/native-chat/input/MobileNativeChatPromptStrip.tsx @@ -3,6 +3,7 @@ import { ChevronUp } from 'lucide-react-native' import { colors, spacing, typography } from '../../../theme/mobile-theme' import { themedStyleSheet } from '../../../theme/themed-styles' import { useMobileColorScheme } from '../../../theme/use-mobile-color-scheme' +import { useMobileTranslation } from '../../../i18n/use-mobile-translation' export function MobileNativeChatPromptStrip({ title, @@ -12,13 +13,14 @@ export function MobileNativeChatPromptStrip({ onExpand: () => void }): React.JSX.Element { useMobileColorScheme() + const t = useMobileTranslation() return ( {title} ([]) const [freeText, setFreeText] = useState(() => question.freeTextInput?.initialValue ?? '') @@ -166,7 +168,7 @@ export function MobileNativeChatQuestion({ {question.multiSelect && hasOptions ? ( [ styles.submit, !canSubmitMulti && styles.submitDisabled, @@ -176,7 +178,11 @@ export function MobileNativeChatQuestion({ disabled={!canSubmitMulti} > - Submit{selectedOptionIndexes.length > 0 ? ` (${selectedOptionIndexes.length})` : ''} + {selectedOptionIndexes.length > 0 + ? t('chat.question.submitCount', 'Submit ({{count}})', { + count: selectedOptionIndexes.length + }) + : t('chat.question.submit', 'Submit')} ) : null} @@ -191,7 +197,9 @@ export function MobileNativeChatQuestion({ onChangeText={setFreeText} placeholder={ question.freeTextInput?.placeholder ?? - (hasOptions ? 'Or type a reply…' : 'Type your reply…') + (hasOptions + ? t('chat.question.otherReplyPlaceholder', 'Or type a reply…') + : t('chat.question.replyPlaceholder', 'Type your reply…')) } placeholderTextColor={colors.textMuted} selectionColor={colors.accentBlue} @@ -201,7 +209,7 @@ export function MobileNativeChatQuestion({ multiline={question.freeTextInput ? question.freeTextInput.multiline === true : true} /> [ styles.freeSend, !canSendFreeText && styles.freeSendDisabled, diff --git a/mobile/src/session/native-chat/input/MobileNativeChatQueuedMessages.tsx b/mobile/src/session/native-chat/input/MobileNativeChatQueuedMessages.tsx index 78634a93379..95acd0d9f8c 100644 --- a/mobile/src/session/native-chat/input/MobileNativeChatQueuedMessages.tsx +++ b/mobile/src/session/native-chat/input/MobileNativeChatQueuedMessages.tsx @@ -6,6 +6,7 @@ import { ChevronDown, ChevronRight, Pencil, Play, Trash2 } from 'lucide-react-na import { ActionSheetModal } from '../../../components/ActionSheetModal' import { colors } from '../../../theme/mobile-theme' import { useMobileColorScheme } from '../../../theme/use-mobile-color-scheme' +import { useMobileTranslation } from '../../../i18n/use-mobile-translation' import { useLargeSystemText } from '../../../theme/use-large-system-text' import type { MobileQueuedMessageCard } from '../../mobile-structured-queued-message-cards' import type { AgentSessionQueuePause } from '../../../../../src/shared/agent-session-wire' @@ -47,6 +48,7 @@ export function MobileNativeChatQueuedMessages({ steerHeld = false }: MobileNativeChatQueuedMessagesProps): React.JSX.Element | null { useMobileColorScheme() + const t = useMobileTranslation() // Use a ref to block duplicate actions before disabled state renders. const [busyIds, setBusyIds] = useState>(() => new Set()) const inFlightRef = useRef(new Set()) @@ -95,7 +97,7 @@ export function MobileNativeChatQueuedMessages({ [ styles.headerAction, pressed && styles.pressed, @@ -105,13 +107,16 @@ export function MobileNativeChatQueuedMessages({ onPress={() => void run(QUEUE_KEY, () => onClearAll(messageIds), messageIds)} > - Clear all + {t('chat.queuedMessages.clearAll', 'Clear all')} ) : null} [ styles.headerAction, pressed && styles.pressed, @@ -121,7 +126,7 @@ export function MobileNativeChatQueuedMessages({ onPress={() => void run(QUEUE_KEY, onResume && (() => onResume()))} > - Resume + {t('chat.queuedMessages.resume', 'Resume')} ) : null @@ -142,13 +147,15 @@ export function MobileNativeChatQueuedMessages({ [styles.headerToggle, pressed && styles.pressed]} onPress={() => setExpanded(!expanded)} /> - {pause ? 'Paused' : 'Queued'} + {pause + ? t('chat.queuedMessages.pausedState', 'Paused') + : t('chat.queuedMessages.queuedState', 'Queued')} { @@ -132,7 +135,10 @@ export function MobileNativeChatSessionOptionPickers({ if (Platform.OS === 'ios' && showWorkingNotice) { // Queued announcements cannot be canceled when the picker closes or changes pages. AccessibilityInfo.announceForAccessibilityWithOptions( - 'Agent is working. Model settings unlock when this turn finishes.', + translate( + 'chat.sessionOptionPickers.workingAnnouncement', + 'Agent is working. Model settings unlock when this turn finishes.' + ), { queue: false } ) } @@ -197,8 +203,20 @@ export function MobileNativeChatSessionOptionPickers({ icon={permissionIcon} accessibleName={ model - ? `Model, ${secondary ? `${primary} ${secondary}` : primary}${permissionColor ? ', full access' : ''}` - : `Permissions, ${primary}` + ? permissionColor + ? t( + 'chat.sessionOptionPickers.modelFullAccessLabel', + 'Model, {{value}}, full access', + { + value: secondary ? `${primary} ${secondary}` : primary + } + ) + : t('chat.sessionOptionPickers.modelLabel', 'Model, {{value}}', { + value: secondary ? `${primary} ${secondary}` : primary + }) + : t('chat.sessionOptionPickers.permissionsLabel', 'Permissions, {{value}}', { + value: primary + }) } disabled={busy || (!model && permission?.settable === false)} onPress={() => { @@ -215,7 +233,11 @@ export function MobileNativeChatSessionOptionPickers({ [styles.sheetNav, pressed && styles.pressed]} onPress={homeView ? closePicker : () => onNavigate(home.id)} @@ -240,15 +262,25 @@ export function MobileNativeChatSessionOptionPickers({ - Agent is working + + {t('chat.sessionOptionPickers.agentWorking', 'Agent is working')} + - Model settings unlock when this turn finishes. + {t( + 'chat.sessionOptionPickers.settingsLocked', + 'Model settings unlock when this turn finishes.' + )} ) : null} {sessionOptionDispatchUnconfirmed(activeDescriptor) ? ( - Sent to the agent — not confirmed + + {t( + 'chat.sessionOptionPickers.dispatchUnconfirmed', + 'Sent to the agent — not confirmed' + )} + ) : null} {reason ? {reason} : null} @@ -307,6 +339,7 @@ function OptionRows({ onSetOption: (descriptor: SessionOptionDescriptor, value: SessionOptionValue) => void }): React.JSX.Element | null { useMobileColorScheme() + const t = useMobileTranslation() if (descriptors.length === 0) { return null } @@ -332,7 +365,11 @@ function OptionRows({ return ( void }): React.JSX.Element { useMobileColorScheme() + const t = useMobileTranslation() return ( {label} {marker ? ( - {marker === 'default' ? 'Default' : 'Not reported'} + + {marker === 'default' + ? t('chat.sessionOptionRows.defaultMarker', 'Default') + : t('chat.sessionOptionRows.notReportedMarker', 'Not reported')} + ) : null} void choiceLabelColor?: (value: string) => string | undefined }): React.JSX.Element { + useMobileTranslation() const locked = disabled || !descriptor.settable // Why: flip-only and agent-picker options are actions, not values or radio choices. if (descriptor.action) { diff --git a/mobile/src/session/native-chat/input/MobileNativeChatSessionOptionSegments.tsx b/mobile/src/session/native-chat/input/MobileNativeChatSessionOptionSegments.tsx index d36691ac5b9..e3b940b22f3 100644 --- a/mobile/src/session/native-chat/input/MobileNativeChatSessionOptionSegments.tsx +++ b/mobile/src/session/native-chat/input/MobileNativeChatSessionOptionSegments.tsx @@ -3,6 +3,7 @@ import { MobileSegmentedControl } from '../../../components/MobileSegmentedContr import { colors, spacing, typography } from '../../../theme/mobile-theme' import { themedStyleSheet } from '../../../theme/themed-styles' import { useMobileColorScheme } from '../../../theme/use-mobile-color-scheme' +import { useMobileTranslation } from '../../../i18n/use-mobile-translation' import { sessionOptionDispatchUnconfirmed, type SessionOptionDescriptor, @@ -10,9 +11,9 @@ import { } from '../../../../../src/shared/native-chat/input/native-chat-session-options' import { nativeChatSessionChoiceShortLabel, - nativeChatSessionOptionLabel, - sayNativeChatSessionOptionEnglish as say + nativeChatSessionOptionLabel } from '../../../../../src/shared/native-chat-session-option-labels' +import { sayMobileNativeChatSessionOption as say } from './native-chat-session-option-copy' import { SessionOptionCaption } from './MobileNativeChatSessionOptionRows' // Five fit across a phone's sheet; more read better as a list on their own page. @@ -38,6 +39,7 @@ export function SessionOptionSegments({ onSetOption: (value: SessionOptionValue) => void }): React.JSX.Element | null { useMobileColorScheme() + const t = useMobileTranslation() if (descriptor.kind.type !== 'select') { return null } @@ -57,7 +59,9 @@ export function SessionOptionSegments({ disabled={disabled || !descriptor.settable} /> {sessionOptionDispatchUnconfirmed(descriptor) ? ( - Sent to the agent — not confirmed + + {t('chat.sessionOptionSegments.dispatchUnconfirmed', 'Sent to the agent — not confirmed')} + ) : null} ) diff --git a/mobile/src/session/native-chat/input/MobileNativeChatStopButton.tsx b/mobile/src/session/native-chat/input/MobileNativeChatStopButton.tsx index 6f8463d80a2..9fd6fb421f2 100644 --- a/mobile/src/session/native-chat/input/MobileNativeChatStopButton.tsx +++ b/mobile/src/session/native-chat/input/MobileNativeChatStopButton.tsx @@ -1,8 +1,8 @@ import { ActivityIndicator, Pressable } from 'react-native' import { Square } from 'lucide-react-native' -import { NATIVE_CHAT_TURN_STATUS_COPY } from '../../../../../src/shared/native-chat-turn-status' import { colors } from '../../../theme/mobile-theme' import { useMobileColorScheme } from '../../../theme/use-mobile-color-scheme' +import { useMobileTranslation } from '../../../i18n/use-mobile-translation' import { mobileNativeChatComposerStyles as styles } from '../composer/mobile-native-chat-composer-styles' // held tracks this phone’s in-flight Stop request, not the host’s stopping status. @@ -14,13 +14,18 @@ export function MobileNativeChatStopButton({ held: boolean }): React.JSX.Element { useMobileColorScheme() + const t = useMobileTranslation() return ( [styles.controlButton, pressed && styles.pressed]} onPress={onStop} disabled={held} accessibilityRole="button" - accessibilityLabel={held ? NATIVE_CHAT_TURN_STATUS_COPY.stopping : 'Stop the agent'} + accessibilityLabel={ + held + ? t('chat.stopButton.stoppingState', 'Stopping…') + : t('chat.stopButton.stopAgent', 'Stop the agent') + } > {held ? ( diff --git a/mobile/src/session/native-chat/input/MobileNativeChatTaskList.tsx b/mobile/src/session/native-chat/input/MobileNativeChatTaskList.tsx index b541bac22cb..3f859374e65 100644 --- a/mobile/src/session/native-chat/input/MobileNativeChatTaskList.tsx +++ b/mobile/src/session/native-chat/input/MobileNativeChatTaskList.tsx @@ -2,25 +2,20 @@ import { Pressable, Text, View } from 'react-native' import { ChevronRight, Circle, CircleCheck, CircleDot, ListChecks } from 'lucide-react-native' import { diffNativeChatTaskLists, - NATIVE_CHAT_TASK_LIST_COPY, nativeChatCompletedTaskCount, nativeChatTaskChangeWords, nativeChatTaskLabel, type NativeChatTask, type NativeChatTaskList as TaskList } from '../../../../../src/shared/native-chat/input/native-chat-task-list' +import { nativeChatTaskListCopy } from './native-chat-task-list-copy' +import { useMobileTranslation } from '../../../i18n/use-mobile-translation' import { colors } from '../../../theme/mobile-theme' import { useMobileNativeChatOpenedDetail } from '../visual/mobile-native-chat-opened-detail' import { toolStyles as styles, toolType } from '../transcript/mobile-native-chat-tool-styles' import { useMobileNativeChatDisclosure } from '../transcript/mobile-native-chat-disclosure-store' import { useMobileColorScheme } from '../../../theme/use-mobile-color-scheme' -const STATUS_WORD: Record = { - completed: NATIVE_CHAT_TASK_LIST_COPY.completed, - in_progress: NATIVE_CHAT_TASK_LIST_COPY.inProgress, - pending: NATIVE_CHAT_TASK_LIST_COPY.pending -} - /** Content plus occurrence is the only identity the providers give these entries. */ function occurrenceKeyed(items: readonly T[], content: (item: T) => string): [T, string][] { const seen = new Map() @@ -42,13 +37,23 @@ function TaskRow({ fontScale: number }): React.JSX.Element { useMobileColorScheme() + const t = useMobileTranslation() + const copy = nativeChatTaskListCopy(t) + const statusWord = { + completed: copy.completed, + in_progress: copy.inProgress, + pending: copy.pending + } const Icon = task.status === 'completed' ? CircleCheck : task.status === 'in_progress' ? CircleDot : Circle const active = task.status === 'in_progress' return ( - {NATIVE_CHAT_TASK_LIST_COPY.empty} + {nativeChatTaskListCopy(t).empty} ) : ( @@ -96,6 +102,7 @@ function FullList({ disclosureKey?: string }) { useMobileColorScheme() + const t = useMobileTranslation() const [open, setOpen] = useMobileNativeChatDisclosure(disclosureKey, false) return ( @@ -109,7 +116,7 @@ function FullList({ - {NATIVE_CHAT_TASK_LIST_COPY.showAll} + {nativeChatTaskListCopy(t).showAll} {open ? : null} @@ -138,21 +145,16 @@ export function MobileNativeChatTaskList({ disclosureKey?: string }): React.JSX.Element { useMobileColorScheme() + const t = useMobileTranslation() const completed = nativeChatCompletedTaskCount(list) + const copy = nativeChatTaskListCopy(t, { completed, total: list.tasks.length }) const changes = previous ? diffNativeChatTaskLists(previous, list) : null const type = toolType(fontScale) return ( - + - - {NATIVE_CHAT_TASK_LIST_COPY.title} - + {copy.title} {`${completed}/${list.tasks.length}`} {changes ? ( @@ -166,7 +168,7 @@ export function MobileNativeChatTaskList({ ) @@ -174,7 +176,7 @@ export function MobileNativeChatTaskList({ )} ) : ( - {NATIVE_CHAT_TASK_LIST_COPY.unchanged} + {copy.unchanged} )} diff --git a/mobile/src/session/native-chat/input/mobile-native-chat-session-option-labels.ts b/mobile/src/session/native-chat/input/mobile-native-chat-session-option-labels.ts index 9efbd30fe04..f7c1b35ac3c 100644 --- a/mobile/src/session/native-chat/input/mobile-native-chat-session-option-labels.ts +++ b/mobile/src/session/native-chat/input/mobile-native-chat-session-option-labels.ts @@ -1,22 +1,27 @@ +import { translate } from '../../../i18n/mobile-i18n' import type { SessionOptionDescriptor } from '../../../../../src/shared/native-chat/input/native-chat-session-options' import { nativeChatSessionChoiceLabel, - sayNativeChatSessionOptionEnglish as say, selectedNativeChatChoice } from '../../../../../src/shared/native-chat-session-option-labels' +import { sayMobileNativeChatSessionOption as say } from './native-chat-session-option-copy' /** The value a drill-in row shows beside its label; desktop lists the choices in place instead. */ export function mobileSessionOptionSummaryValue(descriptor: SessionOptionDescriptor): string { // Show the boolean value consistently with its switch; the source marker separately expresses whether it is confirmed. if (descriptor.kind.type === 'boolean') { - return descriptor.kind.currentValue ? 'On' : 'Off' + return descriptor.kind.currentValue + ? translate('chat.sessionOptionCopy.on', 'On') + : translate('chat.sessionOptionCopy.off', 'Off') } if (descriptor.valueSource === 'unknown') { - return descriptor.id === 'permissionMode' ? 'Permissions' : 'Not set' + return descriptor.id === 'permissionMode' + ? translate('chat.sessionOptionCopy.permissions', 'Permissions') + : translate('chat.sessionOptionCopy.notSet', 'Not set') } const choice = selectedNativeChatChoice(descriptor) if (!choice) { - return 'Not set' + return translate('chat.sessionOptionCopy.notSet', 'Not set') } if (descriptor.id === 'permissionMode' && descriptor.description) { return descriptor.description diff --git a/mobile/src/session/native-chat/input/native-chat-session-option-copy.ts b/mobile/src/session/native-chat/input/native-chat-session-option-copy.ts new file mode 100644 index 00000000000..57674e8329e --- /dev/null +++ b/mobile/src/session/native-chat/input/native-chat-session-option-copy.ts @@ -0,0 +1,57 @@ +import type { NativeChatSessionOptionSay } from '../../../../../src/shared/native-chat-session-option-labels' +import { translate } from '../../../i18n/mobile-i18n' + +export const sayMobileNativeChatSessionOption: NativeChatSessionOptionSay = (id, values) => { + switch (id) { + case 'model': + return translate('chat.sessionOptionCopy.model', 'Model') + case 'effort': + return translate('chat.sessionOptionCopy.effort', 'Effort') + case 'fastMode': + return translate('chat.sessionOptionCopy.fastMode', 'Fast mode') + case 'speed': + return translate('chat.sessionOptionCopy.speed', 'Speed') + case 'thinking': + return translate('chat.sessionOptionCopy.thinking', 'Thinking') + case 'standard': + return translate('chat.sessionOptionCopy.standard', 'Standard') + case 'minimal': + return translate('chat.sessionOptionCopy.minimal', 'Minimal') + case 'low': + return translate('chat.sessionOptionCopy.low', 'Low') + case 'medium': + return translate('chat.sessionOptionCopy.medium', 'Medium') + case 'high': + return translate('chat.sessionOptionCopy.high', 'High') + case 'xhigh': + return translate('chat.sessionOptionCopy.xhigh', 'Extra high') + case 'max': + return translate('chat.sessionOptionCopy.max', 'Max') + case 'ultra': + return translate('chat.sessionOptionCopy.ultra', 'Ultra') + case 'minimalShort': + return translate('chat.sessionOptionCopy.minimalShort', 'Min') + case 'mediumShort': + return translate('chat.sessionOptionCopy.mediumShort', 'Med') + case 'xhighShort': + return translate('chat.sessionOptionCopy.xhighShort', 'XHigh') + case 'fast': + return translate('chat.sessionOptionCopy.fast', 'Fast') + case 'toggleOption': + return translate('chat.sessionOptionCopy.toggleOption', 'Toggle {{value0}}', values) + case 'chooseInAgentPicker': + return translate('chat.sessionOptionCopy.chooseInAgentPicker', 'Choose in agent picker…') + case 'setWhenSessionStarts': + return translate( + 'chat.sessionOptionCopy.setWhenSessionStarts', + 'Set when the session starts.' + ) + case 'availableAfterSessionStarts': + return translate( + 'chat.sessionOptionCopy.availableAfterSessionStarts', + 'Available after the session starts.' + ) + case 'sessionOptions': + return translate('chat.sessionOptionCopy.sessionOptions', 'Session options') + } +} diff --git a/mobile/src/session/native-chat/input/native-chat-task-list-copy.ts b/mobile/src/session/native-chat/input/native-chat-task-list-copy.ts new file mode 100644 index 00000000000..a91ad300c5a --- /dev/null +++ b/mobile/src/session/native-chat/input/native-chat-task-list-copy.ts @@ -0,0 +1,23 @@ +import type { Translate } from '../../../i18n/mobile-i18n' + +export function nativeChatTaskListCopy( + t: Translate, + values?: { completed?: number; total?: number; task?: string } +) { + return { + title: t('chat.taskList.title', 'Tasks'), + completed: t('chat.taskList.completed', 'Completed'), + inProgress: t('chat.taskList.inProgress', 'In progress'), + pending: t('chat.taskList.pending', 'Pending'), + empty: t('chat.taskList.empty', 'No tasks'), + progress: t('chat.taskList.progress', '{{completed}} of {{total}} tasks completed', values), + added: t('chat.taskList.added', 'Added {{task}}', values), + removed: t('chat.taskList.removed', 'Removed {{task}}', values), + started: t('chat.taskList.started', 'Started {{task}}', values), + finished: t('chat.taskList.finished', 'Completed {{task}}', values), + reset: t('chat.taskList.reset', 'Marked pending: {{task}}', values), + updated: t('chat.taskList.updated', 'Updated {{task}}', values), + unchanged: t('chat.taskList.unchanged', 'Tasks unchanged'), + showAll: t('chat.taskList.showAll', 'Full task list') + } +} diff --git a/mobile/src/session/native-chat/mobile-native-chat-stream-frame.ts b/mobile/src/session/native-chat/mobile-native-chat-stream-frame.ts index c39d5c4dba6..998ce4d73e9 100644 --- a/mobile/src/session/native-chat/mobile-native-chat-stream-frame.ts +++ b/mobile/src/session/native-chat/mobile-native-chat-stream-frame.ts @@ -1,4 +1,5 @@ import { applyAppend, replaceList } from '../../../../src/shared/native-chat-merge' +import { translate } from '../../i18n/mobile-i18n' import type { NativeChatMerger } from '../../../../src/shared/native-chat-merge' import type { NativeChatMessage, @@ -70,11 +71,20 @@ export function applyMobileNativeChatStreamFrame(args: { }): AppliedMobileNativeChatFrame { const { merger, frame, limit, replaceSnapshot } = args if (frame.type === 'error') { - return { kind: 'error', error: frame.message ?? frame.error ?? 'Transcript stream failed' } + return { + kind: 'error', + error: + frame.message ?? + frame.error ?? + translate('chat.streamFrame.streamFailed', 'Transcript stream failed') + } } // The subscription owns its token, so an unsolicited end means its feed died. if (frame.type === 'end') { - return { kind: 'error', error: 'Transcript stream ended' } + return { + kind: 'error', + error: translate('chat.streamFrame.streamEnded', 'Transcript stream ended') + } } if (frame.type !== 'snapshot' && frame.type !== 'replacement' && frame.type !== 'appended') { return { kind: 'ignored' } diff --git a/mobile/src/session/native-chat/native-chat-turn-status-copy.ts b/mobile/src/session/native-chat/native-chat-turn-status-copy.ts new file mode 100644 index 00000000000..1c1d60a8534 --- /dev/null +++ b/mobile/src/session/native-chat/native-chat-turn-status-copy.ts @@ -0,0 +1,62 @@ +import type { Translate } from '../../i18n/mobile-i18n' +import { + describeNativeChatActiveTurnLabel, + describeNativeChatTurnStatus +} from '../../../../src/shared/native-chat-turn-status' + +export function formatMobileNativeChatDuration(seconds: number, t: Translate): string { + const totalSeconds = Number.isFinite(seconds) ? Math.max(0, Math.floor(seconds)) : 0 + if (totalSeconds < 60) { + const count = totalSeconds + return count === 1 + ? t('chat.turnStatus.seconds_one', '{{count}}s', { count }) + : t('chat.turnStatus.seconds_other', '{{count}}s', { count }) + } + const minutes = Math.floor(totalSeconds / 60) + const remainingSeconds = totalSeconds % 60 + return minutes < 60 + ? t('chat.turnStatus.minutesSeconds', '{{minutes}}m {{seconds}}s', { + minutes, + seconds: remainingSeconds + }) + : t('chat.turnStatus.hoursMinutesSeconds', '{{hours}}h {{minutes}}m {{seconds}}s', { + hours: Math.floor(minutes / 60), + minutes: minutes % 60, + seconds: remainingSeconds + }) +} + +export function formatMobileNativeChatTurnStatusLabel( + input: Parameters[0], + t: Translate +): string { + const { key } = describeNativeChatTurnStatus(input) + const duration = formatMobileNativeChatDuration(input.workedSeconds ?? input.elapsedSeconds, t) + return nativeChatTurnStatusCopy(t, duration)[key] +} + +export function formatMobileNativeChatActiveTurnLabel( + input: Parameters[0], + t: Translate +): string { + const label = describeNativeChatActiveTurnLabel(input) + return label.source === 'activity' ? label.text : nativeChatTurnStatusCopy(t)[label.key] +} + +export function nativeChatTurnStatusCopy(t: Translate, value0?: string) { + return { + thinking: t('chat.turnStatus.thinking', 'Thinking'), + working: t('chat.turnStatus.working', 'Working…'), + stopping: t('chat.turnStatus.stopping', 'Stopping…'), + queueAfterStop: t('chat.turnStatus.queueAfterStop', 'Queue a message to run after the stop'), + sendAfterStop: t('chat.turnStatus.sendAfterStop', 'Send a message to run after the stop'), + workingFor: t('chat.turnStatus.workingFor', 'Working for {{value0}}', { value0 }), + workedFor: t('chat.turnStatus.workedFor', 'Worked for {{value0}}', { value0 }), + interruptedAfter: t('chat.turnStatus.interruptedAfter', 'Interrupted after {{value0}}', { + value0 + }), + failedAfter: t('chat.turnStatus.failedAfter', 'Failed after {{value0}}', { value0 }), + toggleDetails: t('chat.turnStatus.toggleDetails', 'Toggle turn details'), + responding: t('chat.turnStatus.responding', 'Agent is responding') + } +} diff --git a/mobile/src/session/native-chat/transcript/MobileNativeChatAwaitingInputRow.tsx b/mobile/src/session/native-chat/transcript/MobileNativeChatAwaitingInputRow.tsx index 44657df2fe5..97f4207f3f7 100644 --- a/mobile/src/session/native-chat/transcript/MobileNativeChatAwaitingInputRow.tsx +++ b/mobile/src/session/native-chat/transcript/MobileNativeChatAwaitingInputRow.tsx @@ -1,9 +1,8 @@ import { Text, View } from 'react-native' import { MessageSquareMore } from 'lucide-react-native' -import { - NATIVE_CHAT_ASK_ROW_COPY, - type NativeChatAskRowSubject -} from '../../../../../src/shared/native-chat/input/native-chat-ask-row' +import type { NativeChatAskRowSubject } from '../../../../../src/shared/native-chat/input/native-chat-ask-row' +import { nativeChatAskRowCopy } from './native-chat-ask-row-copy' +import { useMobileTranslation } from '../../../i18n/use-mobile-translation' import { INLINE_TEXT_SELECTION } from '../../../components/inline-text-selection' import { colors, spacing, typography } from '../../../theme/mobile-theme' import { themedStyleSheet } from '../../../theme/themed-styles' @@ -25,20 +24,14 @@ export function MobileNativeChatAwaitingInputRow({ listsQuestions?: boolean }): React.JSX.Element { useMobileColorScheme() - const label = !pending - ? NATIVE_CHAT_ASK_ROW_COPY.asked - : subject === null - ? NATIVE_CHAT_ASK_ROW_COPY.awaitingUnnamed - : NATIVE_CHAT_ASK_ROW_COPY.awaiting + const t = useMobileTranslation() + const copy = nativeChatAskRowCopy( + t, + subject?.kind === 'questions' ? subject.questions.length : undefined + ) + const label = !pending ? copy.asked : subject === null ? copy.awaitingUnnamed : copy.awaiting const text = - subject === null - ? null - : subject.kind === 'question' - ? subject.text - : NATIVE_CHAT_ASK_ROW_COPY.questionCount.replace( - '{{value0}}', - String(subject.questions.length) - ) + subject === null ? null : subject.kind === 'question' ? subject.text : copy.questionCount const type = scaledType(typography.bodySize, fontScale) const questions = subject?.kind === 'questions' && listsQuestions ? subject.questions : null return ( diff --git a/mobile/src/session/native-chat/transcript/MobileNativeChatLiveLine.tsx b/mobile/src/session/native-chat/transcript/MobileNativeChatLiveLine.tsx index 51fbf25cdb5..d4fa8c05041 100644 --- a/mobile/src/session/native-chat/transcript/MobileNativeChatLiveLine.tsx +++ b/mobile/src/session/native-chat/transcript/MobileNativeChatLiveLine.tsx @@ -2,7 +2,8 @@ import { useContext } from 'react' import { Pressable, Text, View } from 'react-native' import { ChevronRight } from 'lucide-react-native' import { nativeChatReasoningDisclosureKey } from '../../../../../src/shared/native-chat/transcript/native-chat-reasoning-row' -import { formatNativeChatActiveTurnLabel } from '../../../../../src/shared/native-chat-turn-status' +import { formatMobileNativeChatActiveTurnLabel } from '../native-chat-turn-status-copy' +import { useMobileTranslation } from '../../../i18n/use-mobile-translation' import { INLINE_TEXT_SELECTION } from '../../../components/inline-text-selection' import { LoadingSpinner } from '../../../components/LoadingSpinner' import { colors, spacing, typography } from '../../../theme/mobile-theme' @@ -25,9 +26,10 @@ export function MobileNativeChatLiveLine({ onOpenFile?: (relativePath: string) => void }): React.JSX.Element { useMobileColorScheme() + const t = useMobileTranslation() const { reasoning, reasoningExpanded } = line const open = reasoning !== null && reasoningExpanded - const label = formatNativeChatActiveTurnLabel(line) + const label = formatMobileNativeChatActiveTurnLabel(line, t) // Android has no inline selection; the finished row's long-press sheet copies the live text too. const { openActions } = useContext(MobileNativeChatRowOverlaysContext) return ( diff --git a/mobile/src/session/native-chat/transcript/MobileNativeChatMessage.tsx b/mobile/src/session/native-chat/transcript/MobileNativeChatMessage.tsx index 58e7134d6ca..7ec78f6a468 100644 --- a/mobile/src/session/native-chat/transcript/MobileNativeChatMessage.tsx +++ b/mobile/src/session/native-chat/transcript/MobileNativeChatMessage.tsx @@ -48,6 +48,7 @@ import { agentMessageAttribution } from '../../mobile-agent-message-attribution' import { MobileNativeChatVisualContext } from '../visual/mobile-native-chat-visual-context' import { useMobileNativeChatFullMessage } from '../../MobileNativeChatFullContent' import { useMobileColorScheme } from '../../../theme/use-mobile-color-scheme' +import { useMobileTranslation } from '../../../i18n/use-mobile-translation' import { MobileNativeChatPacedProse } from './MobileNativeChatPacedProse' import { Prose } from './MobileNativeChatProse' @@ -120,6 +121,7 @@ function MobileNativeChatMessageImpl({ onToggleUserMessage?: (messageId: string) => void }): React.JSX.Element { useMobileColorScheme() + const t = useMobileTranslation() const { shown: message, showFull, recovery } = useMobileNativeChatFullMessage(rowMessage) const attribution = agentMessageAttribution('Message from', message.from) const isUser = message.role === 'user' && attribution === null @@ -338,7 +340,7 @@ function MobileNativeChatMessageImpl({ ) : null} diff --git a/mobile/src/session/native-chat/transcript/MobileNativeChatMessageActionsSheet.tsx b/mobile/src/session/native-chat/transcript/MobileNativeChatMessageActionsSheet.tsx index 12eae4cf20e..d399dd9e16c 100644 --- a/mobile/src/session/native-chat/transcript/MobileNativeChatMessageActionsSheet.tsx +++ b/mobile/src/session/native-chat/transcript/MobileNativeChatMessageActionsSheet.tsx @@ -9,6 +9,8 @@ import { useWindowSafeAreaInsets } from '../../../layout/side-safe-area' import { colors, spacing, typography } from '../../../theme/mobile-theme' import { themedStyleSheet } from '../../../theme/themed-styles' import { useMobileColorScheme } from '../../../theme/use-mobile-color-scheme' +import { translate } from '../../../i18n/mobile-i18n' +import { useMobileTranslation } from '../../../i18n/use-mobile-translation' import { nativeChatMessageCopyText } from '../../../../../src/shared/native-chat-message-copy-text' type Props = { @@ -20,6 +22,7 @@ export function MobileNativeChatMessageActionsSheet({ message, onClose }: Props): React.JSX.Element { + const t = useMobileTranslation() const clipboard = useClipboardWriter() const [sheetVisible, setSheetVisible] = useState(true) const [selecting, setSelecting] = useState(false) @@ -41,20 +44,27 @@ export function MobileNativeChatMessageActionsSheet({ onClose={closeSheet} actions={[ { - label: 'Copy message', + id: 'copyMessage', + label: t('chat.messageActionsSheet.copyMessage', 'Copy message'), icon: Copy, disabled: text.length === 0, onPress: () => { void clipboard.writeText(text).catch((error: unknown) => { Alert.alert( - 'Copy failed', - error instanceof Error ? error.message : 'The clipboard rejected the text.' + translate('chat.messageActionsSheet.copyFailed', 'Copy failed'), + error instanceof Error + ? error.message + : translate( + 'chat.messageActionsSheet.clipboardRejected', + 'The clipboard rejected the text.' + ) ) }) } }, { - label: 'Select text', + id: 'selectText', + label: t('chat.messageActionsSheet.selectText', 'Select text'), icon: TextSelect, disabled: text.length === 0, onPress: () => { @@ -77,6 +87,7 @@ function SelectTextScreen({ onClose: () => void }): React.JSX.Element { useMobileColorScheme() + const t = useMobileTranslation() const insets = useWindowSafeAreaInsets() return ( @@ -92,11 +103,13 @@ function SelectTextScreen({ ]} > - Select text + + {t('chat.messageActionsSheet.selectText', 'Select text')} + diff --git a/mobile/src/session/native-chat/transcript/MobileNativeChatNoticeRow.tsx b/mobile/src/session/native-chat/transcript/MobileNativeChatNoticeRow.tsx index b531917f433..ea6b5716867 100644 --- a/mobile/src/session/native-chat/transcript/MobileNativeChatNoticeRow.tsx +++ b/mobile/src/session/native-chat/transcript/MobileNativeChatNoticeRow.tsx @@ -1,9 +1,7 @@ import { useContext } from 'react' import { StyleSheet, Text, View } from 'react-native' import { AlertCircle, AlertTriangle, Info } from 'lucide-react-native' -import { AGENT_SESSION_HOST_STATUS_COPY } from '../../../../../src/shared/agent-session-host-status-rows' import { - NATIVE_CHAT_NOTICE_COPY, nativeChatNoticeBlock, nativeChatNoticeLook, type NativeChatNoticeTone @@ -14,12 +12,7 @@ import { type NativeChatTextBlock } from '../../../../../src/shared/native-chat-types' import { readWholeAgentSessionFailureFact } from '../../../../../src/shared/agent-session-failure' -import { agentSessionFailureSentence } from '../../../../../src/shared/agent-session-failure-words' -import { formatUsageLimitResetClock } from '../../../../../src/shared/agent-session-usage-limit' -import { - nativeChatOrcaStopRowText, - sayNativeChatOrcaStopEnglish -} from '../../../../../src/shared/native-chat/native-chat-orca-stop-words' +import { nativeChatOrcaStopRowText } from '../../../../../src/shared/native-chat/native-chat-orca-stop-words' import { MobileNativeChatNoticeContext, type MobileNativeChatNoticeNames @@ -29,6 +22,12 @@ import { INLINE_TEXT_SELECTION } from '../../../components/inline-text-selection import { colors, radii, spacing, typography } from '../../../theme/mobile-theme' import { themedStyleSheet } from '../../../theme/themed-styles' import { useMobileColorScheme } from '../../../theme/use-mobile-color-scheme' +import { nativeChatHostStatusCopy, nativeChatNoticeCopy } from './native-chat-notice-copy' +import { nativeChatOrcaStopSay } from './native-chat-orca-stop-copy' +import { nativeChatUsageLimitText } from './native-chat-usage-limit-copy' +import type { Translate } from '../../../i18n/mobile-i18n' +import { useMobileTranslation } from '../../../i18n/use-mobile-translation' +import { NATIVE_CHAT_STOPPED_BEFORE_START_PRESENTATION } from '../../../../../src/shared/native-chat/native-chat-stopped-before-start' import { MONO_SIZE, TEXT_SIZE, scaledType } from './mobile-native-chat-message-styles' function toneColor(tone: NativeChatNoticeTone): string { @@ -36,20 +35,23 @@ function toneColor(tone: NativeChatNoticeTone): string { } // Desktop's no-Continue wording; the phone always reads its Orca host as remote. -function noticeLineText(notice: NativeChatTextBlock, names: MobileNativeChatNoticeNames): string { +function noticeLineText( + notice: NativeChatTextBlock, + names: MobileNativeChatNoticeNames, + t: Translate +): string { if (notice.orcaStop) { + const say = nativeChatOrcaStopSay(t) return nativeChatOrcaStopRowText( notice.orcaStop.cause, - names.hostName ?? sayNativeChatOrcaStopEnglish('unnamedServer'), - { continueAvailable: false, remoteHost: true } + names.hostName ?? say('unnamedServer'), + { continueAvailable: false, remoteHost: true }, + say ) } const failure = readWholeAgentSessionFailureFact(notice.failure) return failure?.kind === 'usageLimitReached' - ? agentSessionFailureSentence(failure, 'row', { - ...(names.agentName ? { agentName: names.agentName } : {}), - usageLimitReset: formatUsageLimitResetClock - }) + ? nativeChatUsageLimitText(failure.usageLimit, names.agentName, t) : notice.text } @@ -66,6 +68,8 @@ export function MobileNativeChatNoticeRow({ onLongPress?: () => void }): React.JSX.Element | null { useMobileColorScheme() + const t = useMobileTranslation() + const copy = nativeChatNoticeCopy(t) const names = useContext(MobileNativeChatNoticeContext) const notice = nativeChatNoticeBlock(blocks) const aside = [styles.faint, scaledType(typography.bodySize, fontScale)] @@ -83,7 +87,7 @@ export function MobileNativeChatNoticeRow({ const look = nativeChatNoticeLook(notice) switch (look.kind) { case 'divider': { - const label = NATIVE_CHAT_NOTICE_COPY[look.label] + const label = copy[look.label] return ( @@ -107,13 +111,13 @@ export function MobileNativeChatNoticeRow({ selectable={INLINE_TEXT_SELECTION} style={[styles.faint, scaledType(TEXT_SIZE, fontScale)]} > - {AGENT_SESSION_HOST_STATUS_COPY[look.presentation]} + {nativeChatHostStatusCopy(t)[look.presentation]} ) case 'plan': return ( - {NATIVE_CHAT_NOTICE_COPY.plan} + {copy.plan} - {notice.text} + {notice.presentation === NATIVE_CHAT_STOPPED_BEFORE_START_PRESENTATION + ? t('chat.notice.stoppedManually', 'Stopped manually') + : notice.text} ) case 'line': { - const lineText = noticeLineText(notice, names) + const lineText = noticeLineText(notice, names, t) const textStyle = [ styles.line, scaledType(TEXT_SIZE, fontScale), diff --git a/mobile/src/session/native-chat/transcript/MobileNativeChatProse.tsx b/mobile/src/session/native-chat/transcript/MobileNativeChatProse.tsx index 6864bab76bf..fb36da2fcaa 100644 --- a/mobile/src/session/native-chat/transcript/MobileNativeChatProse.tsx +++ b/mobile/src/session/native-chat/transcript/MobileNativeChatProse.tsx @@ -7,10 +7,7 @@ import type { NativeChatBlock, NativeChatImageRefBlock } from '../../../../../src/shared/native-chat-types' -import { - AGENT_SESSION_HOST_STATUS_COPY, - isAgentSessionHostStatusPresentation -} from '../../../../../src/shared/agent-session-host-status-rows' +import { isAgentSessionHostStatusPresentation } from '../../../../../src/shared/agent-session-host-status-rows' import { withoutPendingNativeChatVisualDirectiveTail } from '../../../../../src/shared/native-chat/native-chat-visual-directive' import { MobileNativeChatRowOverlaysContext } from './mobile-native-chat-row-overlays' import { @@ -26,6 +23,8 @@ import { } from './mobile-native-chat-message-styles' import type { MobileNativeChatVisualRender } from '../visual/mobile-native-chat-visual-context' import { useMobileColorScheme } from '../../../theme/use-mobile-color-scheme' +import { useMobileTranslation } from '../../../i18n/use-mobile-translation' +import { nativeChatHostStatusCopy } from './native-chat-notice-copy' export function Prose({ block, @@ -52,6 +51,7 @@ export function Prose({ onLongPress?: () => void }): React.JSX.Element | null { useMobileColorScheme() + const t = useMobileTranslation() if (isTextBlock(block)) { if (isAgentSessionHostStatusPresentation(block.presentation)) { return ( @@ -59,7 +59,7 @@ export function Prose({ selectable={INLINE_TEXT_SELECTION} style={[styles.hostNotice, scaledType(TEXT_SIZE, fontScale)]} > - {AGENT_SESSION_HOST_STATUS_COPY[block.presentation]} + {nativeChatHostStatusCopy(t)[block.presentation]} ) } @@ -108,6 +108,7 @@ function ImageAttachment({ onLongPress?: () => void }): React.JSX.Element { useMobileColorScheme() + const t = useMobileTranslation() const { openImage, imageSource } = useContext(MobileNativeChatRowOverlaysContext) const direct = block.url ?? block.path const renderable = isRenderableImageUri(direct) ? direct : null @@ -132,14 +133,14 @@ function ImageAttachment({ const read = hostPath && hostRead?.path === hostPath && hostRead.source === imageSource ? hostRead : null const uri = renderable ?? read?.uri - const label = block.alt ?? 'Attached image' + const label = block.alt ?? t('chat.nativeChatProse.attachedImage', 'Attached image') if (hostPath && !read) { return } if (!uri) { return ( - 🖼 {block.alt ?? block.path ?? block.url ?? 'image'} + 🖼 {block.alt ?? block.path ?? block.url ?? t('chat.nativeChatProse.image', 'image')} ) } @@ -149,7 +150,7 @@ function ImageAttachment({ onLongPress={onLongPress} accessibilityRole="imagebutton" accessibilityLabel={label} - accessibilityHint="Opens the image full size" + accessibilityHint={t('chat.nativeChatProse.openImageHint', 'Opens the image full size')} > diff --git a/mobile/src/session/native-chat/transcript/MobileNativeChatToolLine.tsx b/mobile/src/session/native-chat/transcript/MobileNativeChatToolLine.tsx index a62adfa5660..47abe0e2b1b 100644 --- a/mobile/src/session/native-chat/transcript/MobileNativeChatToolLine.tsx +++ b/mobile/src/session/native-chat/transcript/MobileNativeChatToolLine.tsx @@ -8,13 +8,11 @@ import { } from '../../../../../src/shared/native-chat/tools/native-chat-diff' import type { NativeChatDiffLine } from '../../../../../src/shared/native-chat/tools/native-chat-diff' import { - formatToolDuration, mcpToolIdentity, type NativeChatWebSearchResult } from '../../../../../src/shared/native-chat/tools/native-chat-tool-identity' -import { NATIVE_CHAT_TOOL_ACTIVITY_COPY } from '../../../../../src/shared/native-chat/tools/native-chat-tool-activity' import { nativeChatToolIconName } from '../../../../../src/shared/native-chat/tools/native-chat-tool-icon' -import { NATIVE_CHAT_TOOL_ROW_VERB_COPY } from '../../../../../src/shared/native-chat/tools/native-chat-tool-line-label' +import { nativeChatToolRowVerbCopy } from './native-chat-tool-row-verb-copy' import { nativeChatToolLineView } from '../../../../../src/shared/native-chat/tools/native-chat-tool-line-view' import type { NativeChatToolCallBlock, @@ -32,6 +30,36 @@ import { MobileNativeChatToolOutput } from '../../MobileNativeChatFullContent' import { useMobileNativeChatOpenedDetail } from '../visual/mobile-native-chat-opened-detail' import { useMobileNativeChatDisclosure } from './mobile-native-chat-disclosure-store' import { toolStyles as styles, toolType } from './mobile-native-chat-tool-styles' +import type { Translate } from '../../../i18n/mobile-i18n' +import { useMobileTranslation } from '../../../i18n/use-mobile-translation' + +function toolDuration(durationMs: unknown, t: Translate): string | null { + if (typeof durationMs !== 'number' || !Number.isFinite(durationMs) || durationMs < 0) { + return null + } + if (durationMs < 1000) { + return t('chat.nativeChatToolLine.durationMilliseconds', '{{milliseconds}}ms', { + milliseconds: Math.round(durationMs) + }) + } + const totalSeconds = Math.floor(durationMs / 1000) + const seconds = totalSeconds % 60 + const minutes = Math.floor(totalSeconds / 60) + if (totalSeconds < 60) { + return t('chat.nativeChatToolLine.durationSeconds', '{{seconds}}s', { seconds }) + } + if (minutes < 60) { + return t('chat.nativeChatToolLine.durationMinutes', '{{minutes}}m {{seconds}}s', { + minutes, + seconds + }) + } + return t('chat.nativeChatToolLine.durationHours', '{{hours}}h {{minutes}}m {{seconds}}s', { + hours: Math.floor(minutes / 60), + minutes: minutes % 60, + seconds + }) +} function SearchResults({ results, @@ -164,6 +192,7 @@ export function MobileNativeChatToolLine({ onOpenFile?: (path: string) => void }): React.JSX.Element { useMobileColorScheme() + const t = useMobileTranslation() const [expanded, setExpanded] = useMobileNativeChatDisclosure(disclosureKey, false) const { call, label, body, resultPreview, failed, results, hasDetail } = useMemo( () => nativeChatToolLineView(block, result), @@ -171,20 +200,20 @@ export function MobileNativeChatToolLine({ ) const commandDetail = label?.commandDetail ?? null const fullCommand = label?.fullCommand ?? null - const verb = label?.verb ? NATIVE_CHAT_TOOL_ROW_VERB_COPY[label.verb] : null + const verb = label?.verb ? nativeChatToolRowVerbCopy(t)[label.verb] : null const commandAlone = label?.command === true && verb !== null && label.target.length > 0 const mcp = call ? mcpToolIdentity(call.name, call.mcpIdentity) : null const name = call ? mcp ? `${mcp.server} / ${mcp.tool}` : call.name - : NATIVE_CHAT_TOOL_ACTIVITY_COPY.result + : t('chat.nativeChatToolLine.result', 'Result') const target = label ? label.target : resultPreview const filePath = label?.filePath ?? null const type = toolType(fontScale) const iconSize = 15 * fontScale const exitCode = call && Number.isSafeInteger(call.exitCode) ? call.exitCode : undefined - const duration = call ? formatToolDuration(call.durationMs) : null + const duration = call ? toolDuration(call.durationMs, t) : null return ( @@ -225,7 +254,7 @@ export function MobileNativeChatToolLine({ {exitCode !== undefined ? ( - {`exit ${exitCode}`} + {t('chat.nativeChatToolLine.exitCode', 'exit {{exitCode}}', { exitCode })} ) : null} {duration ? {duration} : null} @@ -237,7 +266,10 @@ export function MobileNativeChatToolLine({ {fullCommand ? ( - + ) : null} {hasDetail && expanded ? ( diff --git a/mobile/src/session/native-chat/transcript/MobileNativeChatToolRun.tsx b/mobile/src/session/native-chat/transcript/MobileNativeChatToolRun.tsx index c58d65b007b..2fb0909ee3a 100644 --- a/mobile/src/session/native-chat/transcript/MobileNativeChatToolRun.tsx +++ b/mobile/src/session/native-chat/transcript/MobileNativeChatToolRun.tsx @@ -19,14 +19,16 @@ import { type NativeSyntheticEvent } from 'react-native' import { Check } from 'lucide-react-native' -import { NATIVE_CHAT_TOOL_ACTIVITY_COPY } from '../../../../../src/shared/native-chat/tools/native-chat-tool-activity' +import { + nativeChatToolActivityCopy, + nativeChatToolCallCount +} from './native-chat-tool-activity-copy' +import { nativeChatToolRunWords } from './native-chat-tool-run-words' +import { useMobileTranslation } from '../../../i18n/use-mobile-translation' import { nativeChatToolLineIdentity } from '../../../../../src/shared/native-chat/tools/native-chat-tool-line-identity' import { pairNativeChatToolResults } from '../../../../../src/shared/native-chat/tools/native-chat-tool-pairing' import { nativeChatToolRunHeader } from '../../../../../src/shared/native-chat/tools/native-chat-tool-run-header' -import { - NATIVE_CHAT_TOOL_RUN_SENTENCE_ENGLISH, - nativeChatToolRunSentence -} from '../../../../../src/shared/native-chat/tools/native-chat-tool-run-sentence' +import { nativeChatToolRunSentence } from '../../../../../src/shared/native-chat/tools/native-chat-tool-run-sentence' import { buildEditCards } from '../../../../../src/shared/native-chat/tools/native-chat-edit-cards' import type { NativeChatBlock, @@ -57,10 +59,6 @@ import type { const MEMBERS_MIN_HEIGHT = 288 const FOLLOW_REARM_PX = 24 -function fill(template: string, value: number): string { - return template.replaceAll('{{value0}}', String(value)) -} - // Keep one header element so live-to-settled changes cannot remount it. function PulsingText({ pulsing, @@ -198,6 +196,7 @@ export function ToolRun({ onOpenFile }: MobileNativeChatToolRunProps): React.JSX.Element { useMobileColorScheme() + const t = useMobileTranslation() const runKey = disclosureId === undefined ? undefined : `run:${disclosureId}` const [open, setOpen] = useMobileNativeChatDisclosure(runKey, false) const asideCount = asides @@ -214,18 +213,17 @@ export function ToolRun({ [activeTurnIsWorking, awaitingAnswer, blocks, trailing] ) const { live, latestCallLabel, iconName: headerIcon } = header - const sentence = nativeChatToolRunSentence( - header.work, - { live }, - NATIVE_CHAT_TOOL_RUN_SENTENCE_ENGLISH - ) - const fallbackLabel = fill(NATIVE_CHAT_TOOL_ACTIVITY_COPY[header.fallbackLabel], header.callCount) + const sentence = nativeChatToolRunSentence(header.work, { live }, nativeChatToolRunWords(t)) + const fallbackLabel = + header.fallbackLabel === 'result' + ? nativeChatToolActivityCopy(t).result + : nativeChatToolCallCount(t, header.callCount) const marks = [ header.failedCallCount > 0 - ? fill(NATIVE_CHAT_TOOL_ACTIVITY_COPY.failedCount, header.failedCallCount) + ? nativeChatToolActivityCopy(t, { value0: header.failedCallCount }).failedCount : null, header.interruptedCallCount > 0 - ? fill(NATIVE_CHAT_TOOL_ACTIVITY_COPY.interruptedCount, header.interruptedCallCount) + ? nativeChatToolActivityCopy(t, { value0: header.interruptedCallCount }).interruptedCount : null ].filter((mark) => mark !== null) const type = toolType(fontScale) diff --git a/mobile/src/session/native-chat/transcript/MobileNativeChatTurnStatus.tsx b/mobile/src/session/native-chat/transcript/MobileNativeChatTurnStatus.tsx index f17f121e7fe..ac663bde9d9 100644 --- a/mobile/src/session/native-chat/transcript/MobileNativeChatTurnStatus.tsx +++ b/mobile/src/session/native-chat/transcript/MobileNativeChatTurnStatus.tsx @@ -1,11 +1,12 @@ import { useState } from 'react' import { Pressable, StyleSheet, Text, View } from 'react-native' import { ChevronRight } from 'lucide-react-native' +import { nativeChatElapsedSeconds } from '../../../../../src/shared/native-chat-turn-status' import { - formatNativeChatTurnStatusLabel, - NATIVE_CHAT_TURN_STATUS_COPY, - nativeChatElapsedSeconds -} from '../../../../../src/shared/native-chat-turn-status' + formatMobileNativeChatTurnStatusLabel, + nativeChatTurnStatusCopy +} from '../native-chat-turn-status-copy' +import { useMobileTranslation } from '../../../i18n/use-mobile-translation' import type { AgentTurnOutcome } from '../../../../../src/shared/agent-turn-outcome' import { colors, spacing, typography } from '../../../theme/mobile-theme' import { themedStyleSheet } from '../../../theme/themed-styles' @@ -34,9 +35,10 @@ export function MobileNativeChatTurnStatus({ onToggleExpanded?: () => void }): React.JSX.Element { useMobileColorScheme() + const t = useMobileTranslation() const settled = workedSeconds != null const elapsedSeconds = useElapsedSeconds(startedAt, !settled) - const label = formatNativeChatTurnStatusLabel({ workedSeconds, elapsedSeconds, verdict }) + const label = formatMobileNativeChatTurnStatusLabel({ workedSeconds, elapsedSeconds, verdict }, t) if (settled && onToggleExpanded) { return ( @@ -46,7 +48,7 @@ export function MobileNativeChatTurnStatus({ hitSlop={6} accessibilityRole="button" accessibilityState={{ expanded }} - accessibilityLabel={NATIVE_CHAT_TURN_STATUS_COPY.toggleDetails} + accessibilityLabel={nativeChatTurnStatusCopy(t).toggleDetails} > {label} diff --git a/mobile/src/session/native-chat/transcript/MobileNativeChatUserMessageFold.tsx b/mobile/src/session/native-chat/transcript/MobileNativeChatUserMessageFold.tsx index 380ef6a9130..73aab39761f 100644 --- a/mobile/src/session/native-chat/transcript/MobileNativeChatUserMessageFold.tsx +++ b/mobile/src/session/native-chat/transcript/MobileNativeChatUserMessageFold.tsx @@ -10,6 +10,7 @@ import { themedStyleSheet } from '../../../theme/themed-styles' import { useMobileColorScheme } from '../../../theme/use-mobile-color-scheme' import { CHAT_PROSE_SCALE } from './mobile-native-chat-message-styles' import type { useMobileNativeChatFullMessage } from '../../MobileNativeChatFullContent' +import { useMobileTranslation } from '../../../i18n/use-mobile-translation' const FOLDED_HEIGHT = NATIVE_CHAT_USER_MESSAGE_MAX_UNFOLDED_LINES * @@ -35,6 +36,7 @@ export function MobileNativeChatUserMessageFold({ children: React.ReactNode }): React.JSX.Element { useMobileColorScheme() + const t = useMobileTranslation() const longText = useMemo(() => nativeChatUserMessageFolds(text), [text]) const [height, setHeight] = useState(null) const clip = FOLDED_HEIGHT * fontScale @@ -64,10 +66,13 @@ export function MobileNativeChatUserMessageFold({ > {open - ? 'Show less' + ? t('chat.nativeChatUserMessageFold.showLess', 'Show less') : recovery?.load === 'failed' - ? "Show full message — couldn't load, tap to retry" - : 'Show full message'} + ? t( + 'chat.nativeChatUserMessageFold.loadFailed', + "Show full message — couldn't load, tap to retry" + ) + : t('chat.nativeChatUserMessageFold.showFullMessage', 'Show full message')} ) : null} diff --git a/mobile/src/session/native-chat/transcript/native-chat-ask-row-copy.ts b/mobile/src/session/native-chat/transcript/native-chat-ask-row-copy.ts new file mode 100644 index 00000000000..1f81aa9559f --- /dev/null +++ b/mobile/src/session/native-chat/transcript/native-chat-ask-row-copy.ts @@ -0,0 +1,10 @@ +import type { Translate } from '../../../i18n/mobile-i18n' + +export function nativeChatAskRowCopy(t: Translate, value0?: number) { + return { + awaiting: t('chat.askRow.awaiting', 'Awaiting user input:'), + awaitingUnnamed: t('chat.askRow.awaitingUnnamed', 'Awaiting user input'), + asked: t('chat.askRow.asked', 'Asked:'), + questionCount: t('chat.askRow.questionCount', '{{value0}} questions', { value0 }) + } +} diff --git a/mobile/src/session/native-chat/transcript/native-chat-notice-copy.ts b/mobile/src/session/native-chat/transcript/native-chat-notice-copy.ts new file mode 100644 index 00000000000..a7e9f64f03e --- /dev/null +++ b/mobile/src/session/native-chat/transcript/native-chat-notice-copy.ts @@ -0,0 +1,27 @@ +import type { Translate } from '../../../i18n/mobile-i18n' + +export function nativeChatNoticeCopy(t: Translate) { + return { + compaction: t('chat.notice.compaction', 'Context compacted'), + contextCleared: t('chat.notice.contextCleared', 'Context cleared'), + plan: t('chat.notice.plan', 'Plan') + } +} + +export function nativeChatHostStatusCopy(t: Translate) { + return { + 'history-repaired': t( + 'chat.hostStatus.historyRepaired', + "Part of this chat's history couldn't be loaded." + ), + 'history-item-too-large': t( + 'chat.hostStatus.historyItemTooLarge', + 'This part of the chat was too large to show.' + ), + 'continued-outside': t('chat.hostStatus.continuedOutside', 'Continued outside this chat.'), + 'continued-outside-rewound': t( + 'chat.hostStatus.continuedOutsideRewound', + "Continued outside this chat. Earlier replies above are no longer in this conversation's history." + ) + } +} diff --git a/mobile/src/session/native-chat/transcript/native-chat-orca-stop-copy.ts b/mobile/src/session/native-chat/transcript/native-chat-orca-stop-copy.ts new file mode 100644 index 00000000000..caa21954b7c --- /dev/null +++ b/mobile/src/session/native-chat/transcript/native-chat-orca-stop-copy.ts @@ -0,0 +1,36 @@ +import type { NativeChatOrcaStopSay } from '../../../../../src/shared/native-chat/native-chat-orca-stop-words' +import type { Translate } from '../../../i18n/mobile-i18n' + +export function nativeChatOrcaStopSay(t: Translate): NativeChatOrcaStopSay { + return (id, machine) => { + const copy = { + update: () => + t( + 'chat.orcaStop.update', + 'Orca on {{machine}} restarted for an update while this response was in progress.', + { machine } + ), + quit: () => + t( + 'chat.orcaStop.quit', + 'Orca on {{machine}} was closed while this response was in progress.', + { machine } + ), + crash: () => + t( + 'chat.orcaStop.crash', + 'Orca on {{machine}} stopped unexpectedly while this response was in progress.', + { machine } + ), + remote: () => + t( + 'chat.orcaStop.remote', + 'Orca on {{machine}} stopped while this response was in progress.', + { machine } + ), + canContinue: () => t('chat.orcaStop.canContinue', 'You can continue in this conversation.'), + unnamedServer: () => t('chat.orcaStop.unnamedServer', 'the remote server') + } + return copy[id]() + } +} diff --git a/mobile/src/session/native-chat/transcript/native-chat-tool-activity-copy.ts b/mobile/src/session/native-chat/transcript/native-chat-tool-activity-copy.ts new file mode 100644 index 00000000000..e9c2d6c5519 --- /dev/null +++ b/mobile/src/session/native-chat/transcript/native-chat-tool-activity-copy.ts @@ -0,0 +1,39 @@ +import type { Translate } from '../../../i18n/mobile-i18n' + +export function nativeChatToolCallCount(t: Translate, count: number): string { + return count === 1 + ? t('chat.toolActivity.count_one', '{{count}} tool call', { count }) + : t('chat.toolActivity.count_other', '{{count}} tool calls', { count }) +} + +export function nativeChatToolActivityCopy( + t: Translate, + values?: { preview?: string; toolName?: string; value0?: number } +) { + return { + runningPreview: t('chat.toolActivity.runningPreview', 'Running {{preview}}', values), + runningCommand: t('chat.toolActivity.runningCommand', 'Running command'), + runningNamedPreview: t( + 'chat.toolActivity.runningNamedPreview', + 'Running {{toolName}} {{preview}}', + values + ), + runningNamed: t('chat.toolActivity.runningNamed', 'Running {{toolName}}', values), + result: t('chat.toolActivity.result', 'Result'), + countOne: nativeChatToolCallCount(t, 1), + countN: nativeChatToolCallCount(t, values?.value0 ?? 0), + moreCalls: t('chat.toolActivity.moreCalls', '+{{value0}} more', values), + failedCount: t('chat.toolActivity.failedCount', '{{value0}} failed', values), + failedCallsLabel: t( + 'chat.toolActivity.failedCallsLabel', + 'Failed tool calls: {{value0}}', + values + ), + interruptedCount: t('chat.toolActivity.interruptedCount', '{{value0}} interrupted', values), + interruptedCallsLabel: t( + 'chat.toolActivity.interruptedCallsLabel', + 'Interrupted tool calls: {{value0}}', + values + ) + } +} diff --git a/mobile/src/session/native-chat/transcript/native-chat-tool-row-verb-copy.ts b/mobile/src/session/native-chat/transcript/native-chat-tool-row-verb-copy.ts new file mode 100644 index 00000000000..e2349ab5ac8 --- /dev/null +++ b/mobile/src/session/native-chat/transcript/native-chat-tool-row-verb-copy.ts @@ -0,0 +1,31 @@ +import type { NativeChatToolRowVerb } from '../../../../../src/shared/native-chat/tools/native-chat-tool-line-label' +import type { Translate } from '../../../i18n/mobile-i18n' + +export function nativeChatToolRowVerbCopy(t: Translate): Record { + return { + running: t('chat.nativeChatToolLine.running', 'Running'), + ran: t('chat.nativeChatToolLine.ran', 'Ran'), + reading: t('chat.nativeChatToolLine.reading', 'Reading'), + read: t('chat.nativeChatToolLine.read', 'Read'), + triedRead: t('chat.nativeChatToolLine.triedRead', 'Tried to read'), + editing: t('chat.nativeChatToolLine.editing', 'Editing'), + edited: t('chat.nativeChatToolLine.edited', 'Edited'), + triedEdit: t('chat.nativeChatToolLine.triedEdit', 'Tried to edit'), + added: t('chat.nativeChatToolLine.added', 'Added'), + deleted: t('chat.nativeChatToolLine.deleted', 'Deleted'), + renamed: t('chat.nativeChatToolLine.renamed', 'Renamed'), + subagent: t('chat.nativeChatToolLine.subagent', 'Subagent'), + searching: t('chat.nativeChatToolLine.searching', 'Searching'), + searched: t('chat.nativeChatToolLine.searched', 'Searched'), + triedSearch: t('chat.nativeChatToolLine.triedSearch', 'Tried to search'), + listing: t('chat.nativeChatToolLine.listing', 'Listing'), + listed: t('chat.nativeChatToolLine.listed', 'Listed'), + triedList: t('chat.nativeChatToolLine.triedList', 'Tried to list'), + fetching: t('chat.nativeChatToolLine.fetching', 'Fetching'), + fetched: t('chat.nativeChatToolLine.fetched', 'Fetched'), + triedFetch: t('chat.nativeChatToolLine.triedFetch', 'Tried to fetch'), + searchingWeb: t('chat.nativeChatToolLine.searchingWeb', 'Searching the web'), + searchedWeb: t('chat.nativeChatToolLine.searchedWeb', 'Searched the web'), + triedSearchWeb: t('chat.nativeChatToolLine.triedSearchWeb', 'Tried to search the web') + } +} diff --git a/mobile/src/session/native-chat/transcript/native-chat-tool-run-words.test.ts b/mobile/src/session/native-chat/transcript/native-chat-tool-run-words.test.ts new file mode 100644 index 00000000000..d0eca8c43a8 --- /dev/null +++ b/mobile/src/session/native-chat/transcript/native-chat-tool-run-words.test.ts @@ -0,0 +1,71 @@ +import { describe, expect, it, vi } from 'vitest' +import type { SupportedUiLocale } from '../../../../../src/shared/ui-locale' +import { + joinNativeChatToolRunClauses, + nativeChatToolRunSentence +} from '../../../../../src/shared/native-chat/tools/native-chat-tool-run-sentence' +import { getMobileUiLocale } from '../../../i18n/mobile-i18n' +import { nativeChatToolRunWords } from './native-chat-tool-run-words' + +vi.mock('../../../i18n/mobile-i18n', () => ({ + getMobileUiLocale: vi.fn(() => 'en') +})) + +const joiners = { + pair: (first: string, second: string) => `${first} | ${second}`, + list: (leading: readonly string[], last: string) => [...leading, last].join(' | ') +} + +function mobileWords(locale: SupportedUiLocale) { + vi.mocked(getMobileUiLocale).mockReturnValue(locale) + return { ...nativeChatToolRunWords((_key, fallback) => fallback), ...joiners } +} + +describe('mobile tool-run sentence continuations', () => { + it('keeps German noun capitalization through the sentence formatter', () => { + const words = mobileWords('de') + expect( + nativeChatToolRunSentence( + [ + { type: 'tool-call', name: 'read', input: {} }, + { type: 'tool-call', name: 'Bash', input: { command: 'orca snapshot --page p1' } } + ], + {}, + { + ...words, + clause: (category) => (category === 'browser' ? 'Browser verwendet' : 'Datei gelesen') + } + ) + ).toBe('Datei gelesen | Browser verwendet') + }) + + it('uses Turkish casing for both dotted and dotless I in later clauses', () => { + expect(joinNativeChatToolRunClauses(['Başladı', 'İnceledi', 'Isındı'], mobileWords('tr'))).toBe( + 'Başladı | inceledi | ısındı' + ) + }) + + it.each(['en', 'es', 'fr', 'pt', 'ru'])( + 'continues later clauses in %s', + (locale) => { + expect(joinNativeChatToolRunClauses(['Read', 'Searched'], mobileWords(locale))).toBe( + 'Read | searched' + ) + } + ) + + it.each(['ja', 'ko', 'zh', 'zh-Hant'])( + 'preserves clause text in %s', + (locale) => { + expect(joinNativeChatToolRunClauses(['開始', 'Browser'], mobileWords(locale))).toBe( + '開始 | Browser' + ) + } + ) + + it('retains the default casing when no continuation is supplied', () => { + expect(joinNativeChatToolRunClauses(['Read', 'Searched', 'Edited'], joiners)).toBe( + 'Read | searched | edited' + ) + }) +}) diff --git a/mobile/src/session/native-chat/transcript/native-chat-tool-run-words.ts b/mobile/src/session/native-chat/transcript/native-chat-tool-run-words.ts new file mode 100644 index 00000000000..e309ce2a1a2 --- /dev/null +++ b/mobile/src/session/native-chat/transcript/native-chat-tool-run-words.ts @@ -0,0 +1,139 @@ +import type { NativeChatToolRunSentenceWords } from '../../../../../src/shared/native-chat/tools/native-chat-tool-run-sentence' +import { getMobileUiLocale, type Translate } from '../../../i18n/mobile-i18n' +import { getUiIntlLocale } from '../../../i18n/ui-intl-locale' + +export function nativeChatToolRunWords(t: Translate): NativeChatToolRunSentenceWords { + return { + continuation: (clause) => + ['en', 'es', 'fr', 'pt', 'ru', 'tr'].includes(getMobileUiLocale()) + ? `${clause.charAt(0).toLocaleLowerCase(getUiIntlLocale())}${clause.slice(1)}` + : clause, + clause: (category, count, live) => { + const copy = live + ? { + read: () => + count === 1 + ? t('chat.toolRun.liveRead_one', 'Reading {{count}} file', { count }) + : t('chat.toolRun.liveRead_other', 'Reading {{count}} files', { count }), + search: () => + count === 1 + ? t('chat.toolRun.liveSearch_one', 'Searching {{count}} time', { count }) + : t('chat.toolRun.liveSearch_other', 'Searching {{count}} times', { count }), + listFiles: () => + count === 1 + ? t('chat.toolRun.liveListFiles_one', 'Listing {{count}} directory', { count }) + : t('chat.toolRun.liveListFiles_other', 'Listing {{count}} directories', { count }), + unknown: () => + count === 1 + ? t('chat.toolRun.liveUnknown_one', 'Running {{count}} command', { count }) + : t('chat.toolRun.liveUnknown_other', 'Running {{count}} commands', { count }), + fileChange: () => + count === 1 + ? t('chat.toolRun.liveFileChange_one', 'Editing {{count}} file', { count }) + : t('chat.toolRun.liveFileChange_other', 'Editing {{count}} files', { count }), + webSearch: () => + count === 1 + ? t('chat.toolRun.liveWebSearch_one', 'Searching the web {{count}} time', { count }) + : t('chat.toolRun.liveWebSearch_other', 'Searching the web {{count}} times', { + count + }), + browser: () => + count === 1 + ? t('chat.toolRun.liveBrowserOnce', 'Using the browser') + : t('chat.toolRun.liveBrowserTimes_other', 'Using the browser {{count}} times', { + count + }), + mcpToolCall: () => + count === 1 + ? t('chat.toolRun.liveMcpToolCall_one', 'Using {{count}} integration', { count }) + : t('chat.toolRun.liveMcpToolCall_other', 'Using {{count}} integrations', { + count + }), + subAgentActivity: () => + count === 1 + ? t('chat.toolRun.liveSubAgentActivity_one', 'Running {{count}} agent', { count }) + : t('chat.toolRun.liveSubAgentActivity_other', 'Running {{count}} agents', { + count + }), + todoList: () => + count === 1 + ? t('chat.toolRun.liveTodoListOnce', 'Updating the plan') + : t('chat.toolRun.liveTodoListTimes_other', 'Updating the plan {{count}} times', { + count + }), + other: () => + count === 1 + ? t('chat.toolRun.liveOther_one', 'Using {{count}} tool', { count }) + : t('chat.toolRun.liveOther_other', 'Using {{count}} tools', { count }) + } + : { + read: () => + count === 1 + ? t('chat.toolRun.settledRead_one', 'Read {{count}} file', { count }) + : t('chat.toolRun.settledRead_other', 'Read {{count}} files', { count }), + search: () => + count === 1 + ? t('chat.toolRun.settledSearch_one', 'Searched {{count}} time', { count }) + : t('chat.toolRun.settledSearch_other', 'Searched {{count}} times', { count }), + listFiles: () => + count === 1 + ? t('chat.toolRun.settledListFiles_one', 'Listed {{count}} directory', { count }) + : t('chat.toolRun.settledListFiles_other', 'Listed {{count}} directories', { + count + }), + unknown: () => + count === 1 + ? t('chat.toolRun.settledUnknown_one', 'Ran {{count}} command', { count }) + : t('chat.toolRun.settledUnknown_other', 'Ran {{count}} commands', { count }), + fileChange: () => + count === 1 + ? t('chat.toolRun.settledFileChange_one', 'Edited {{count}} file', { count }) + : t('chat.toolRun.settledFileChange_other', 'Edited {{count}} files', { count }), + webSearch: () => + count === 1 + ? t('chat.toolRun.settledWebSearch_one', 'Searched the web {{count}} time', { + count + }) + : t('chat.toolRun.settledWebSearch_other', 'Searched the web {{count}} times', { + count + }), + browser: () => + count === 1 + ? t('chat.toolRun.settledBrowserOnce', 'Used the browser') + : t('chat.toolRun.settledBrowserTimes_other', 'Used the browser {{count}} times', { + count + }), + mcpToolCall: () => + count === 1 + ? t('chat.toolRun.settledMcpToolCall_one', 'Used {{count}} integration', { count }) + : t('chat.toolRun.settledMcpToolCall_other', 'Used {{count}} integrations', { + count + }), + subAgentActivity: () => + count === 1 + ? t('chat.toolRun.settledSubAgentActivity_one', 'Ran {{count}} agent', { count }) + : t('chat.toolRun.settledSubAgentActivity_other', 'Ran {{count}} agents', { + count + }), + todoList: () => + count === 1 + ? t('chat.toolRun.settledTodoListOnce', 'Updated the plan') + : t('chat.toolRun.settledTodoListTimes_other', 'Updated the plan {{count}} times', { + count + }), + other: () => + count === 1 + ? t('chat.toolRun.settledOther_one', 'Used {{count}} tool', { count }) + : t('chat.toolRun.settledOther_other', 'Used {{count}} tools', { count }) + } + return copy[category]() + }, + pair: (value0, value1) => + t('chat.toolRun.pair', '{{value0}} and {{value1}}', { value0, value1 }), + list: (leading, value1) => + t('chat.toolRun.list', '{{value0}}, and {{value1}}', { + value0: leading.join(t('chat.toolRun.separator', ', ')), + value1 + }) + } +} diff --git a/mobile/src/session/native-chat/transcript/native-chat-usage-limit-copy.ts b/mobile/src/session/native-chat/transcript/native-chat-usage-limit-copy.ts new file mode 100644 index 00000000000..21a356d68bc --- /dev/null +++ b/mobile/src/session/native-chat/transcript/native-chat-usage-limit-copy.ts @@ -0,0 +1,45 @@ +import type { AgentSessionUsageLimit } from '../../../../../src/shared/agent-session-usage-limit' +import { joinSentences } from '../../../../../src/shared/sentence-joining' +import type { Translate } from '../../../i18n/mobile-i18n' +import { getUiIntlLocale } from '../../../i18n/ui-intl-locale' + +function usageLimitResetClock(resetsAt: number): string { + const at = new Date(resetsAt) + const today = at.toDateString() === new Date(Date.now()).toDateString() + return new Intl.DateTimeFormat( + getUiIntlLocale(), + today + ? { hour: 'numeric', minute: '2-digit' } + : { weekday: 'short', month: 'short', day: 'numeric', hour: 'numeric', minute: '2-digit' } + ).format(at) +} + +export function nativeChatUsageLimitText( + limit: AgentSessionUsageLimit | undefined, + agentName: string | null | undefined, + t: Translate +): string { + const agent = agentName || t('chat.usageLimit.theAgent', 'The agent') + const windows = { + session: t('chat.usageLimit.windowSession', 'session'), + weekly: t('chat.usageLimit.windowWeekly', 'weekly'), + weeklyModel: t('chat.usageLimit.windowWeeklyModel', 'weekly model'), + credits: t('chat.usageLimit.windowCredits', 'extra usage') + } + const lead = limit?.window + ? t('chat.usageLimit.reachedNamed', '{{agent}} hit its {{window}} limit.', { + agent, + window: windows[limit.window] + }) + : t('chat.usageLimit.reached', '{{agent}} hit its usage limit.', { agent }) + return joinSentences([ + lead, + ...(limit?.resetsAt !== undefined + ? [ + t('chat.usageLimit.resetsAt', 'It resets at {{when}}.', { + when: usageLimitResetClock(limit.resetsAt) + }) + ] + : []) + ]) +} diff --git a/mobile/src/session/native-chat/use-mobile-native-chat-input-lease.ts b/mobile/src/session/native-chat/use-mobile-native-chat-input-lease.ts index d67876b7ec4..7c2f8238d3c 100644 --- a/mobile/src/session/native-chat/use-mobile-native-chat-input-lease.ts +++ b/mobile/src/session/native-chat/use-mobile-native-chat-input-lease.ts @@ -1,8 +1,6 @@ import { useCallback, useEffect, useRef, useState } from 'react' -import { - NATIVE_CHAT_COMPOSER_PLACEHOLDER, - NATIVE_CHAT_TURN_STATUS_COPY -} from '../../../../src/shared/native-chat-turn-status' +import { nativeChatTurnStatusCopy } from './native-chat-turn-status-copy' +import { translate, type Translate } from '../../i18n/mobile-i18n' import type { MobileNativeChatInputLockReason } from '../MobileNativeChatView' export function useMobileNativeChatInputLease(args: { @@ -96,18 +94,19 @@ export function useSettledMobileNativeChatInputLock( export function mobileNativeChatComposerPlaceholder( lockReason: MobileNativeChatInputLockReason | null, - afterStop?: 'queue' | 'send' + afterStop?: 'queue' | 'send', + t: Translate = translate ): string { if (lockReason === 'disconnected') { - return 'Reconnecting…' + return t('chat.composerPlaceholder.reconnecting', 'Reconnecting…') } if (lockReason === 'waiting') { - return 'Waiting for terminal…' + return t('chat.composerPlaceholder.waitingForTerminal', 'Waiting for terminal…') } if (afterStop) { return afterStop === 'queue' - ? NATIVE_CHAT_TURN_STATUS_COPY.queueAfterStop - : NATIVE_CHAT_TURN_STATUS_COPY.sendAfterStop + ? nativeChatTurnStatusCopy(t).queueAfterStop + : nativeChatTurnStatusCopy(t).sendAfterStop } - return NATIVE_CHAT_COMPOSER_PLACEHOLDER + return t('chat.composerPlaceholder.prompt', 'Ask anything, @ to mention files, / for commands') } diff --git a/mobile/src/session/native-chat/visual/MobileNativeChatImageViewer.tsx b/mobile/src/session/native-chat/visual/MobileNativeChatImageViewer.tsx index 6ca47d967ca..44d594aa3a8 100644 --- a/mobile/src/session/native-chat/visual/MobileNativeChatImageViewer.tsx +++ b/mobile/src/session/native-chat/visual/MobileNativeChatImageViewer.tsx @@ -4,6 +4,7 @@ import { useWindowSafeAreaInsets } from '../../../layout/side-safe-area' import { colors, spacing } from '../../../theme/mobile-theme' import { themedStyleSheet } from '../../../theme/themed-styles' import { useMobileColorScheme } from '../../../theme/use-mobile-color-scheme' +import { useMobileTranslation } from '../../../i18n/use-mobile-translation' export function MobileNativeChatImageViewer({ uri, @@ -15,6 +16,7 @@ export function MobileNativeChatImageViewer({ onClose: () => void }): React.JSX.Element { useMobileColorScheme() + const t = useMobileTranslation() const insets = useWindowSafeAreaInsets() return ( @@ -37,7 +39,7 @@ export function MobileNativeChatImageViewer({ onPress={onClose} hitSlop={12} accessibilityRole="button" - accessibilityLabel="Close image" + accessibilityLabel={t('chat.nativeChatImageViewer.closeImage', 'Close image')} > diff --git a/mobile/src/session/native-chat/visual/MobileNativeChatVisual.tsx b/mobile/src/session/native-chat/visual/MobileNativeChatVisual.tsx index f6a056882de..f1edbe4f581 100644 --- a/mobile/src/session/native-chat/visual/MobileNativeChatVisual.tsx +++ b/mobile/src/session/native-chat/visual/MobileNativeChatVisual.tsx @@ -11,8 +11,7 @@ import { MOBILE_NATIVE_CHAT_VISUAL_INITIAL_HEIGHT } from './mobile-native-chat-v import { MobileNativeChatVisualFrame } from './MobileNativeChatVisualFrame' import { useMobileNativeChatVisual } from './use-mobile-native-chat-visual' import type { MobileNativeChatVisualRender } from './mobile-native-chat-visual-context' - -const DEFAULT_TITLE = 'Visualization' +import { useMobileTranslation } from '../../../i18n/use-mobile-translation' function MobileNativeChatVisualPlaceholder() { useMobileColorScheme() @@ -43,10 +42,11 @@ export const MobileNativeChatVisual = memo(function MobileNativeChatVisual({ source: MobileNativeChatVisualSource }) { useMobileColorScheme() + const t = useMobileTranslation() const { state, retry } = useMobileNativeChatVisual(source, directive.file) const [frameFailed, setFrameFailed] = useState(false) const [fullscreen, setFullscreen] = useState(false) - const title = directive.title ?? DEFAULT_TITLE + const title = directive.title ?? t('chat.nativeChatVisual.defaultTitle', 'Visualization') const onFrameFailed = useCallback(() => { setFrameFailed(true) setFullscreen(false) @@ -65,11 +65,13 @@ export const MobileNativeChatVisual = memo(function MobileNativeChatVisual({ - Visualization unavailable + + {t('chat.nativeChatVisual.unavailable', 'Visualization unavailable')} + ) } @@ -84,7 +86,13 @@ export const MobileNativeChatVisual = memo(function MobileNativeChatVisual({ setFullscreen(true)} accessibilityRole="button" - accessibilityLabel={`Open ${title} full screen`} + accessibilityLabel={t( + 'chat.nativeChatVisual.openFullscreen', + 'Open {{title}} full screen', + { + title + } + )} hitSlop={8} style={styles.openButton} > @@ -114,6 +122,7 @@ function MobileNativeChatVisualFullscreen({ onFailed: () => void }) { useMobileColorScheme() + const t = useMobileTranslation() const insets = useWindowSafeAreaInsets() return ( @@ -135,7 +144,7 @@ function MobileNativeChatVisualFullscreen({ diff --git a/mobile/src/session/new-tab-agent-host-launch.ts b/mobile/src/session/new-tab-agent-host-launch.ts index df47cd6b941..fdcf3bbe9db 100644 --- a/mobile/src/session/new-tab-agent-host-launch.ts +++ b/mobile/src/session/new-tab-agent-host-launch.ts @@ -8,7 +8,8 @@ import { launchAgentInExistingWorkspace, reserveMobileAgentLaunch } from './mobile-existing-agent-launch' -import { AGENT_PROMPT_NOT_SENT_MESSAGE } from './pr-ai-triage-launch' +import { agentPromptNotSentMessage } from './pr-ai-triage-launch' +import { translate } from '../i18n/mobile-i18n' import { isLaunchedSurfaceListed, launchedSelection, @@ -17,7 +18,6 @@ import { type PendingSessionSelection } from './pending-session-selection' -const NOTES_NOT_SENT_MESSAGE = "The agent started, but the notes weren't sent." export const PROMPT_UNCONFIRMED_MESSAGE = "The agent started, but couldn't confirm the prompt was sent." export const NOTES_UNCONFIRMED_MESSAGE = @@ -101,7 +101,15 @@ export async function launchNewTabAgentThroughHost(args: { if (prompt?.trim()) { triggerError() showToast( - options?.initialPrompt ? NOTES_UNCONFIRMED_MESSAGE : PROMPT_UNCONFIRMED_MESSAGE, + options?.initialPrompt + ? translate( + 'session.newTabAgentHostLaunch.notesUnconfirmed', + "The agent started, but couldn't confirm the notes were sent." + ) + : translate( + 'session.newTabAgentHostLaunch.promptUnconfirmed', + "The agent started, but couldn't confirm the prompt was sent." + ), 2400 ) } @@ -124,10 +132,20 @@ export async function launchNewTabAgentThroughHost(args: { void args.fetchSessionTabs() if (launched.promptDelivered === false) { triggerError() - showToast(options?.initialPrompt ? NOTES_NOT_SENT_MESSAGE : AGENT_PROMPT_NOT_SENT_MESSAGE, 2400) + showToast( + options?.initialPrompt + ? translate( + 'session.newTabAgentHostLaunch.notesNotSent', + "The agent started, but the notes weren't sent." + ) + : agentPromptNotSentMessage(), + 2400 + ) } else if (launched.promptDelivered && options?.initialPrompt) { triggerSuccess() - showToast(options.successToast ?? 'Notes sent') + showToast( + options.successToast ?? translate('session.newTabAgentHostLaunch.notesSent', 'Notes sent') + ) options.onPromptSent?.() } else if (warning?.trim()) { showToast(warning.trim(), 2400) diff --git a/mobile/src/session/pr-actions-engine.ts b/mobile/src/session/pr-actions-engine.ts index 1bcb4cfc2c8..633eacf0252 100644 --- a/mobile/src/session/pr-actions-engine.ts +++ b/mobile/src/session/pr-actions-engine.ts @@ -5,9 +5,16 @@ import { createOptimisticField, type OptimisticField } from './optimistic-write- import type { GitHubPrMutationOutcome } from './github-pr-mutations' import type { GitHubPrRepoSlug } from './github-pr-rpc' import type { PrActionMutations } from './pr-action-mutation-contract' +import { translate } from '../i18n/mobile-i18n' export type { PrActionMutations } from './pr-action-mutation-contract' +const refreshFailureMessage = (): string => + translate('session.prActionsEngine.refreshFailed', 'Failed to refresh pull request.') + +const mergeFailureMessage = (): string => + translate('session.prActionsEngine.mergeFailed', 'Failed to merge pull request.') + export type PrActionBusyKey = | { kind: 'merge' } | { kind: 'autoMerge' } @@ -155,7 +162,7 @@ export class PrActionsEngine { await this.cfg.refetch() } catch (err) { if (this.identity === identity) { - this.setError(err instanceof Error ? err.message : 'Failed to refresh pull request.') + this.setError(err instanceof Error ? err.message : refreshFailureMessage()) } } return @@ -183,7 +190,7 @@ export class PrActionsEngine { await this.settle(identity, outcome, { onSuccess: () => {}, onRevert: () => {} }) } catch (err) { if (this.identity === identity) { - this.setError(err instanceof Error ? err.message : 'Failed to merge pull request.') + this.setError(err instanceof Error ? err.message : mergeFailureMessage()) } } finally { this.clearBusyIfOwned(identity, { kind: 'merge' }) diff --git a/mobile/src/session/pr-ai-triage-launch.ts b/mobile/src/session/pr-ai-triage-launch.ts index 5eeb74c269c..0a364356a41 100644 --- a/mobile/src/session/pr-ai-triage-launch.ts +++ b/mobile/src/session/pr-ai-triage-launch.ts @@ -1,7 +1,6 @@ import type { SourceControlLaunchActionId } from '../../../src/shared/source-control-ai-actions' import type { RpcClient } from '../transport/rpc-client' import { - AGENT_LAUNCH_UPDATE_REQUIRED_MESSAGE, launchAgentInExistingWorkspace, supportsMobileExistingAgentLaunch } from './mobile-existing-agent-launch' @@ -11,6 +10,7 @@ import { } from './mobile-new-tab-agent-loader' import { resolveMobileSourceControlLaunchAgent } from './mobile-source-control-launch-agent' import { projectSourceControlActionLaunch } from '../../../src/shared/source-control-action-launch-preferences' +import { translate } from '../i18n/mobile-i18n' // Have the host launch and deliver AI prompts; mobile must never type them into a shell. @@ -25,6 +25,13 @@ export type MobilePromptedAgentLaunch = export const AGENT_PROMPT_NOT_SENT_MESSAGE = "The agent started, but the prompt wasn't sent." +export function agentPromptNotSentMessage(): string { + return translate( + 'session.prAiTriageLaunch.promptNotSent', + "The agent started, but the prompt wasn't sent." + ) +} + export async function launchAgentWithPrompt(args: { client: RpcClient hostCapabilities: readonly string[] | null | undefined @@ -34,7 +41,13 @@ export async function launchAgentWithPrompt(args: { launchSource: string }): Promise { if (!supportsMobileExistingAgentLaunch(args.hostCapabilities)) { - return { kind: 'not-started', message: AGENT_LAUNCH_UPDATE_REQUIRED_MESSAGE } + return { + kind: 'not-started', + message: translate( + 'session.prAiTriageLaunch.updateRequired', + 'Update Orca on your computer to start an agent from your phone.' + ) + } } let resolved try { @@ -48,7 +61,12 @@ export async function launchAgentWithPrompt(args: { ) } catch (error) { const message = error instanceof Error ? error.message.trim() : '' - return { kind: 'not-started', message: message || 'Could not load the available agents.' } + return { + kind: 'not-started', + message: + message || + translate('session.prAiTriageLaunch.loadFailed', 'Could not load the available agents.') + } } if (resolved.kind === 'unavailable') { return { kind: 'not-started', message: resolved.message } @@ -61,8 +79,10 @@ export async function launchAgentWithPrompt(args: { if (!text) { return { kind: 'not-started', - message: + message: translate( + 'session.prAiTriageLaunch.emptyPrompt', "This action's saved prompt is empty. Update Source Control AI settings on your computer." + ) } } const launched = await launchAgentInExistingWorkspace({ @@ -86,7 +106,13 @@ export async function launchAgentWithPrompt(args: { : { kind: 'prompt-not-sent', prompt: text, ...extra } } case 'unsupported': - return { kind: 'not-started', message: AGENT_LAUNCH_UPDATE_REQUIRED_MESSAGE } + return { + kind: 'not-started', + message: translate( + 'session.prAiTriageLaunch.updateRequired', + 'Update Orca on your computer to start an agent from your phone.' + ) + } case 'failed': case 'tab-closed': return { kind: 'not-started', message: launched.message } @@ -97,7 +123,9 @@ export async function launchAgentWithPrompt(args: { export function agentStartedMessage(workspaceLabel: string | null | undefined): string { const label = workspaceLabel?.trim() - return label ? `Agent started in ${label}` : 'Agent started' + return label + ? translate('session.prAiTriageLaunch.agentStartedIn', 'Agent started in {{label}}', { label }) + : translate('session.prAiTriageLaunch.agentStarted', 'Agent started') } // Host warnings describe completed launches and must not become failure notices. @@ -124,7 +152,7 @@ export function promptedLaunchNotice( return { succeeded: false, success: null, - error: AGENT_PROMPT_NOT_SENT_MESSAGE, + error: agentPromptNotSentMessage(), warning: result.warning ?? null, undeliveredPrompt: result.prompt } diff --git a/mobile/src/session/session-side-rail.tsx b/mobile/src/session/session-side-rail.tsx index ac8dd631312..d1efad3c244 100644 --- a/mobile/src/session/session-side-rail.tsx +++ b/mobile/src/session/session-side-rail.tsx @@ -5,6 +5,7 @@ import { useSideRailClaim, useSideRailEdge } from '../layout/side-rail' import { themedStyleSheet } from '../theme/themed-styles' import { useMobileColorScheme } from '../theme/use-mobile-color-scheme' import { colors, spacing } from '../theme/mobile-theme' +import { useMobileTranslation } from '../i18n/use-mobile-translation' import { MobileSessionAccessoryKeys } from './MobileSessionAccessoryKeys' import { sessionCommandDockShown } from './MobileSessionCommandDock' import type { MobileSessionController } from './use-mobile-session-controller' @@ -34,6 +35,7 @@ function SessionRail({ back: boolean keys: boolean }) { + const t = useMobileTranslation() useMobileColorScheme() return ( @@ -42,7 +44,7 @@ function SessionRail({ style={({ pressed }) => [styles.back, pressed && styles.backPressed]} onPress={controller.requestLeaveSession} accessibilityRole="button" - accessibilityLabel="Back to worktrees" + accessibilityLabel={t('session.sideRail.backToWorktrees', 'Back to worktrees')} > diff --git a/mobile/src/session/use-live-worktree-name.ts b/mobile/src/session/use-live-worktree-name.ts index 3539f2a8ea7..a870b0d409b 100644 --- a/mobile/src/session/use-live-worktree-name.ts +++ b/mobile/src/session/use-live-worktree-name.ts @@ -6,7 +6,8 @@ import type { RpcClient } from '../transport/rpc-client' import type { ConnectionState } from '../transport/types' import { sessionWorktreeRecordRead } from './mobile-session-read-operations' import { getLiveWorktreeDisplayName } from './worktree-display-name' -import { FLOATING_WORKSPACE_TITLE, isFloatingWorkspaceWorktreeId } from './floating-workspace' +import { getFloatingWorkspaceTitle, isFloatingWorkspaceWorktreeId } from './floating-workspace' +import { useMobileTranslation } from '../i18n/use-mobile-translation' import { classifyWorktreeShowResponse, type WorktreeShowResolution @@ -32,6 +33,7 @@ export function useLiveWorktreeName({ routeName, worktreeId }: Params): LiveWorktreeName { + useMobileTranslation() // Floating workspaces have no worktree record; probing would keep fallback polling forever. const isFloatingWorkspace = isFloatingWorkspaceWorktreeId(worktreeId) const routeNameHint = routeName?.trim() ?? '' @@ -188,7 +190,7 @@ export function useLiveWorktreeName({ ) if (isFloatingWorkspace) { - return { name: FLOATING_WORKSPACE_TITLE, resolution: 'unknown' } + return { name: getFloatingWorkspaceTitle(), resolution: 'unknown' } } return { name: worktreeName.worktreeId === worktreeId ? worktreeName.name : routeNameHint, diff --git a/mobile/src/session/use-mobile-attachment-input-lease-gate.ts b/mobile/src/session/use-mobile-attachment-input-lease-gate.ts index 3dc83a08308..b2fb8d1b8c0 100644 --- a/mobile/src/session/use-mobile-attachment-input-lease-gate.ts +++ b/mobile/src/session/use-mobile-attachment-input-lease-gate.ts @@ -1,4 +1,5 @@ import { useCallback } from 'react' +import { translate } from '../i18n/mobile-i18n' type CurrentRef = { readonly current: T } @@ -50,7 +51,13 @@ export function useMobileAttachmentInputLeaseGate({ if (nativeChatInputLeaseReadyRef.current) { return true } - showToast('Attach failed (reconnecting)', 1500) + showToast( + translate( + 'session.useAttachmentInputLeaseGate.reconnecting', + 'Attach failed (reconnecting)' + ), + 1500 + ) return false }, [ diff --git a/mobile/src/session/use-mobile-diff-review-comment-actions.ts b/mobile/src/session/use-mobile-diff-review-comment-actions.ts index eeeaa58fee9..a80e278e3e5 100644 --- a/mobile/src/session/use-mobile-diff-review-comment-actions.ts +++ b/mobile/src/session/use-mobile-diff-review-comment-actions.ts @@ -1,4 +1,5 @@ import { useCallback, type Dispatch, type SetStateAction } from 'react' +import { translate } from '../i18n/mobile-i18n' import type { DiffComment, MobileDiffReviewState } from '../../../src/shared/diff-comment-types' import { triggerError, triggerSuccess } from '../platform/haptics' import type { ConnectionState } from '../transport/types' @@ -74,7 +75,12 @@ export function useMobileDiffReviewCommentActions(input: CommentActionsInput) { reviewState: MobileDiffReviewState ) => { if (!client || connState !== 'connected') { - throw new Error('Waiting for desktop...') + throw new Error( + translate( + 'session.useDiffReviewCommentActions.waitingForDesktop', + 'Waiting for desktop...' + ) + ) } const response = await sessionWorktreeNotesWrite.request(client, { worktree: `id:${worktreeId}`, @@ -84,7 +90,10 @@ export function useMobileDiffReviewCommentActions(input: CommentActionsInput) { }) return interpretOrThrowRefusalMessage( () => sessionWorktreeNotesWrite.interpret(response), - 'Failed to save review state' + translate( + 'session.useDiffReviewCommentActions.saveStateFailed', + 'Failed to save review state' + ) )?.diffComments }, [client, connState, worktreeId] @@ -121,7 +130,11 @@ export function useMobileDiffReviewCommentActions(input: CommentActionsInput) { setScreenState(previous) } triggerError() - setActionError(err instanceof Error ? err.message : 'Failed to save review') + setActionError( + err instanceof Error + ? err.message + : translate('session.useDiffReviewCommentActions.saveFailed', 'Failed to save review') + ) throw err } }, diff --git a/mobile/src/session/use-mobile-diff-review-controller.ts b/mobile/src/session/use-mobile-diff-review-controller.ts index 4dfc588a91f..e95a870caab 100644 --- a/mobile/src/session/use-mobile-diff-review-controller.ts +++ b/mobile/src/session/use-mobile-diff-review-controller.ts @@ -1,4 +1,6 @@ import { useCallback, useEffect, useMemo, useReducer, useRef, useState } from 'react' +import { translate } from '../i18n/mobile-i18n' +import { useMobileTranslation } from '../i18n/use-mobile-translation' import { useMobileSourceControlSettings } from '../source-control/mobile-source-control-settings' import type { FlatList } from 'react-native' import type { DiffComment } from '../../../src/shared/diff-comment-types' @@ -48,6 +50,7 @@ type ControllerInput = { } export function useMobileDiffReviewController(input: ControllerInput) { + useMobileTranslation() const { client, connState, @@ -84,14 +87,25 @@ export function useMobileDiffReviewController(input: ControllerInput) { loadGenerationRef.current = generation const isCurrent = () => generation === loadGenerationRef.current if (!worktreeId) { - setScreenState({ kind: 'error', message: 'Missing worktree' }) + setScreenState({ + kind: 'error', + message: translate('session.useDiffReviewController.missingWorktree', 'Missing worktree') + }) return } // Keep an existing review through disconnects; only empty screens need the waiting state. const keepReady = (fallback: ReviewScreenState) => (prev: ReviewScreenState) => prev.kind === 'ready' ? prev : fallback if (!client || connState !== 'connected') { - setScreenState(keepReady({ kind: 'error', message: 'Waiting for desktop...' })) + setScreenState( + keepReady({ + kind: 'error', + message: translate( + 'session.useDiffReviewController.waitingForDesktop', + 'Waiting for desktop...' + ) + }) + ) return } setScreenState(keepReady({ kind: 'loading' })) @@ -112,7 +126,10 @@ export function useMobileDiffReviewController(input: ControllerInput) { setScreenState( keepReady({ kind: 'error', - message: err instanceof Error ? err.message : 'Unable to load review' + message: + err instanceof Error + ? err.message + : translate('session.useDiffReviewController.loadFailed', 'Unable to load review') }) ) } diff --git a/mobile/src/session/use-mobile-diff-review-diff-loading.ts b/mobile/src/session/use-mobile-diff-review-diff-loading.ts index 695520db8a2..9367c274000 100644 --- a/mobile/src/session/use-mobile-diff-review-diff-loading.ts +++ b/mobile/src/session/use-mobile-diff-review-diff-loading.ts @@ -1,4 +1,6 @@ import { useEffect, useRef, useState } from 'react' +import { translate } from '../i18n/mobile-i18n' +import { useMobileTranslation } from '../i18n/use-mobile-translation' import type { ConnectionState } from '../transport/types' import type { RpcClient } from '../transport/rpc-client' import { loadMobileDiffReviewDiff } from './mobile-diff-review-loaders' @@ -18,6 +20,7 @@ type DiffLoadingInput = { // Retain loaded same-item diffs through transport blips so reconnect cannot erase the reader’s context. export function useMobileDiffReviewDiffLoading(input: DiffLoadingInput): ReviewDiffState { + useMobileTranslation() const { client, connState, worktreeId, currentItem, screenState, setActiveHunkIndex } = input const [diffState, setDiffState] = useState({ kind: 'idle' }) const { diffShowWhitespace } = useMobileSourceControlSettings(client, connState).settings @@ -41,7 +44,16 @@ export function useMobileDiffReviewDiffLoading(input: DiffLoadingInput): ReviewD const keepLoadedDiff = (fallback: ReviewDiffState) => (prev: ReviewDiffState) => prev.kind === 'ready' && prev.itemKey === itemKey ? prev : fallback if (!client || connState !== 'connected') { - setDiffState(keepLoadedDiff({ kind: 'error', itemKey, message: 'Waiting for desktop...' })) + setDiffState( + keepLoadedDiff({ + kind: 'error', + itemKey, + message: translate( + 'session.useDiffReviewDiffLoading.waitingForDesktop', + 'Waiting for desktop...' + ) + }) + ) return } const controller = new AbortController() @@ -66,7 +78,10 @@ export function useMobileDiffReviewDiffLoading(input: DiffLoadingInput): ReviewD keepLoadedDiff({ kind: 'error', itemKey, - message: err instanceof Error ? err.message : 'Unable to load diff' + message: + err instanceof Error + ? err.message + : translate('session.useDiffReviewDiffLoading.loadFailed', 'Unable to load diff') }) ) } diff --git a/mobile/src/session/use-mobile-diff-review-git-actions.ts b/mobile/src/session/use-mobile-diff-review-git-actions.ts index a3aa6cc060d..0edfcb79bc5 100644 --- a/mobile/src/session/use-mobile-diff-review-git-actions.ts +++ b/mobile/src/session/use-mobile-diff-review-git-actions.ts @@ -1,4 +1,5 @@ import { useCallback, type Dispatch, type SetStateAction } from 'react' +import { translate } from '../i18n/mobile-i18n' import type { ConnectionState } from '../transport/types' import type { RpcClient } from '../transport/rpc-client' import { interpretOrThrowRefusalMessage } from '../transport/rpc-refusal-message' @@ -9,7 +10,6 @@ import { import { triggerError, triggerSuccess } from '../platform/haptics' import type { MobileDiffReviewQueueItem } from './mobile-diff-review-queue' import type { GitMutationMethod } from './mobile-diff-review-screen-model' -import { mobileReviewCountLabel } from './mobile-diff-review-screen-model' type GitActionsInput = { client: RpcClient | null @@ -28,7 +28,9 @@ export function useMobileDiffReviewGitActions(input: GitActionsInput) { const runGitMutation = useCallback( async (method: GitMutationMethod, item: MobileDiffReviewQueueItem) => { if (!client || connState !== 'connected') { - setActionError('Waiting for desktop...') + setActionError( + translate('session.useDiffReviewGitActions.waitingForDesktop', 'Waiting for desktop...') + ) return } setBusyAction(`${method}:${item.filePath}`) @@ -41,13 +43,20 @@ export function useMobileDiffReviewGitActions(input: GitActionsInput) { }) interpretOrThrowRefusalMessage( () => mutation.interpret(response), - 'Source control action failed' + translate('session.useDiffReviewGitActions.actionFailed', 'Source control action failed') ) triggerSuccess() await loadReviewData() } catch (err) { triggerError() - setActionError(err instanceof Error ? err.message : 'Source control action failed') + setActionError( + err instanceof Error + ? err.message + : translate( + 'session.useDiffReviewGitActions.actionFailed', + 'Source control action failed' + ) + ) } finally { setBusyAction(null) } @@ -57,7 +66,9 @@ export function useMobileDiffReviewGitActions(input: GitActionsInput) { const stageReviewedFiles = useCallback(async () => { if (!client || connState !== 'connected') { - setActionError('Waiting for desktop...') + setActionError( + translate('session.useDiffReviewGitActions.waitingForDesktop', 'Waiting for desktop...') + ) return } const files = queue.filter( @@ -85,10 +96,28 @@ export function useMobileDiffReviewGitActions(input: GitActionsInput) { } setBusyAction(null) triggerSuccess() + const count = staged setActionError( failed > 0 - ? `${staged} staged, ${failed} failed` - : `${mobileReviewCountLabel(staged, 'reviewed file', 'reviewed files')} staged` + ? translate( + 'session.useDiffReviewGitActions.stageResults', + '{{staged}} staged, {{failed}} failed', + { + staged, + failed + } + ) + : count === 1 + ? translate( + 'session.useDiffReviewGitActions.reviewedFilesStaged_one', + '{{count}} reviewed file staged', + { count } + ) + : translate( + 'session.useDiffReviewGitActions.reviewedFilesStaged_other', + '{{count}} reviewed files staged', + { count } + ) ) await loadReviewData() }, [client, connState, loadReviewData, queue, setActionError, setBusyAction, worktreeId]) diff --git a/mobile/src/session/use-mobile-diff-review-interactions.ts b/mobile/src/session/use-mobile-diff-review-interactions.ts index f3a5d855eaa..bbb3ee2417f 100644 --- a/mobile/src/session/use-mobile-diff-review-interactions.ts +++ b/mobile/src/session/use-mobile-diff-review-interactions.ts @@ -1,4 +1,5 @@ import type { Dispatch, RefObject, SetStateAction } from 'react' +import { translate } from '../i18n/mobile-i18n' import type { FlatList } from 'react-native' import type { ConnectionState } from '../transport/types' import type { RpcClient } from '../transport/rpc-client' @@ -191,7 +192,12 @@ export function useMobileDiffReviewInteractions(input: InteractionInput) { try { sourceFileDiffOpenRun.interpret(response) } catch (error) { - setActionError(refusedRpcMessageOrFallback(error, 'Unable to open in session')) + setActionError( + refusedRpcMessageOrFallback( + error, + translate('session.useDiffReviewInteractions.openFailed', 'Unable to open in session') + ) + ) return } onOpenSession() diff --git a/mobile/src/session/use-mobile-diff-review-send-actions.ts b/mobile/src/session/use-mobile-diff-review-send-actions.ts index d1de8cf3151..31e28793837 100644 --- a/mobile/src/session/use-mobile-diff-review-send-actions.ts +++ b/mobile/src/session/use-mobile-diff-review-send-actions.ts @@ -1,4 +1,5 @@ import { useCallback, useEffect, useRef, type Dispatch, type SetStateAction } from 'react' +import { translate } from '../i18n/mobile-i18n' import type { DiffComment, MobileDiffReviewState } from '../../../src/shared/diff-comment-types' import type { ConnectionState } from '../transport/types' import type { RpcClient } from '../transport/rpc-client' @@ -50,11 +51,18 @@ export function useMobileDiffReviewSendActions(input: SendActionsInput) { try { await clipboard.writeText(formatDiffComments(screenState.comments)) } catch (err) { - setActionError(err instanceof Error ? err.message : 'Unable to copy the review notes') + setActionError( + err instanceof Error + ? err.message + : translate( + 'session.useDiffReviewSendActions.copyFailed', + 'Unable to copy the review notes' + ) + ) return } triggerSuccess() - setActionError('Review notes copied') + setActionError(translate('session.useDiffReviewSendActions.notesCopied', 'Review notes copied')) }, [clipboard, screenState, setActionError]) const clearSentNotes = useCallback(async () => { @@ -89,11 +97,15 @@ export function useMobileDiffReviewSendActions(input: SendActionsInput) { const sendPromptToTerminal = useCallback( async (terminal: string, comments: readonly DiffComment[]) => { if (!client || connState !== 'connected') { - throw new Error('Waiting for desktop...') + throw new Error( + translate('session.useDiffReviewSendActions.waitingForDesktop', 'Waiting for desktop...') + ) } // Heal by terminal handle so native-chat image leftovers cannot join review notes. if (!(await healMobileNativeChatStaleInput({ client, terminal, deviceToken: null }))) { - throw new Error('Failed to send notes') + throw new Error( + translate('session.useDiffReviewSendActions.sendFailed', 'Failed to send notes') + ) } const response = await reviewTerminalSendRun.request(client, { terminal, @@ -103,14 +115,16 @@ export function useMobileDiffReviewSendActions(input: SendActionsInput) { let accepted accepted = interpretOrThrowRefusalMessage( () => reviewTerminalSendRun.interpret(response), - 'Failed to send notes' + translate('session.useDiffReviewSendActions.sendFailed', 'Failed to send notes') ) if (!accepted) { - throw new Error('Terminal input is locked') + throw new Error( + translate('session.useDiffReviewSendActions.inputLocked', 'Terminal input is locked') + ) } await markNotesSent(comments) triggerSuccess() - setActionError('Review notes sent') + setActionError(translate('session.useDiffReviewSendActions.notesSent', 'Review notes sent')) closeSheet('send') }, [client, connState, closeSheet, markNotesSent, setActionError] @@ -121,7 +135,9 @@ export function useMobileDiffReviewSendActions(input: SendActionsInput) { // Report instead of throwing because the sheet drops the promise. if (!client || connState !== 'connected') { closeSheet('send') - setActionError('Waiting for desktop...') + setActionError( + translate('session.useDiffReviewSendActions.waitingForDesktop', 'Waiting for desktop...') + ) return } if (agentLaunchInFlightRef.current) { @@ -130,7 +146,9 @@ export function useMobileDiffReviewSendActions(input: SendActionsInput) { agentLaunchInFlightRef.current = true // Close the sheet before a long launch so progress stays visible. closeSheet('send') - setActionError('Starting an agent...') + setActionError( + translate('session.useDiffReviewSendActions.startingAgent', 'Starting an agent...') + ) let result try { // Without an agent picker, use desktop’s default resolution with no saved recipe. @@ -152,21 +170,36 @@ export function useMobileDiffReviewSendActions(input: SendActionsInput) { if (result.kind === 'prompt-not-sent') { // Notes stay unsent so Copy Notes and a later send still carry them. setActionError( - "The agent started, but the notes weren't sent. Use Copy Notes to paste them." + translate( + 'session.useDiffReviewSendActions.promptNotSent', + "The agent started, but the notes weren't sent. Use Copy Notes to paste them." + ) ) return } await markNotesSent(comments) triggerSuccess() // Warnings describe successful launches, so append them to success feedback. - setActionError(result.warning ? `Review notes sent. ${result.warning}` : 'Review notes sent') + setActionError( + result.warning + ? translate( + 'session.useDiffReviewSendActions.notesSentWithWarning', + 'Review notes sent. {{warning}}', + { + warning: result.warning + } + ) + : translate('session.useDiffReviewSendActions.notesSent', 'Review notes sent') + ) }, [client, closeSheet, connState, hostCapabilities, markNotesSent, setActionError, worktreeId] ) const openSendSheet = useCallback(async () => { if (!client || connState !== 'connected') { - setActionError('Waiting for desktop...') + setActionError( + translate('session.useDiffReviewSendActions.waitingForDesktop', 'Waiting for desktop...') + ) return } openSheet({ kind: 'send', load: { kind: 'loading' } }) @@ -177,13 +210,22 @@ export function useMobileDiffReviewSendActions(input: SendActionsInput) { let terminals terminals = interpretOrThrowRefusalMessage( () => reviewTerminalListRead.interpret(response), - 'Unable to load agent sessions' + translate( + 'session.useDiffReviewSendActions.loadSessionsFailed', + 'Unable to load agent sessions' + ) ) updateSendSheet({ kind: 'ready', terminals }) } catch (err) { updateSendSheet({ kind: 'error', - message: err instanceof Error ? err.message : 'Unable to load agent sessions', + message: + err instanceof Error + ? err.message + : translate( + 'session.useDiffReviewSendActions.loadSessionsFailed', + 'Unable to load agent sessions' + ), terminals: [] }) } diff --git a/mobile/src/session/use-mobile-image-attachment.ts b/mobile/src/session/use-mobile-image-attachment.ts index 6a429182ea7..3c96aa08a56 100644 --- a/mobile/src/session/use-mobile-image-attachment.ts +++ b/mobile/src/session/use-mobile-image-attachment.ts @@ -1,4 +1,5 @@ import { useCallback, useState } from 'react' +import { translate } from '../i18n/mobile-i18n' import type { RpcClient } from '../transport/rpc-client' import type { ConnectionState } from '../transport/types' import { useMediaPicker } from '../platform/media-picker' @@ -75,18 +76,27 @@ export function useMobileImageAttachment({ } catch (error) { onError() if (connState !== 'connected') { - showToast('Attach failed (disconnected)', 1500) + showToast( + translate('session.useImageAttachment.disconnected', 'Attach failed (disconnected)'), + 1500 + ) return } if (error instanceof ImageLibraryPermissionError) { - showToast('Photo permission denied', 1500) + showToast( + translate('session.useImageAttachment.permissionDenied', 'Photo permission denied'), + 1500 + ) return } if (getErrorMessage(error) === 'Clipboard image is too large') { - showToast('Image too large to attach', 1500) + showToast( + translate('session.useImageAttachment.imageTooLarge', 'Image too large to attach'), + 1500 + ) return } - showToast('Attach failed', 1500) + showToast(translate('session.useImageAttachment.attachFailed', 'Attach failed'), 1500) } finally { setIsAttaching(false) } diff --git a/mobile/src/session/use-mobile-native-chat-stop.ts b/mobile/src/session/use-mobile-native-chat-stop.ts index e6478beba2e..8ebe5901a1e 100644 --- a/mobile/src/session/use-mobile-native-chat-stop.ts +++ b/mobile/src/session/use-mobile-native-chat-stop.ts @@ -1,4 +1,5 @@ import { useCallback, useEffect, useRef, type MutableRefObject } from 'react' +import { translate } from '../i18n/mobile-i18n' import type { RpcClient } from '../transport/rpc-client' import { isRpcDeliveryUnknown } from '../transport/rpc-delivery-ambiguity' import { isLogicalClientCutoverError } from '../transport/stable-logical-rpc-client' @@ -54,7 +55,12 @@ export function useMobileNativeChatStop(args: { return useCallback(() => { const handle = handleRef.current if (!client || !handle || !enabled) { - onSendError('Stop not sent (terminal not ready)') + onSendError( + translate( + 'session.useNativeChatStop.terminalNotReady', + 'Stop not sent (terminal not ready)' + ) + ) return Promise.resolve(false) } cancelPending() @@ -81,7 +87,14 @@ export function useMobileNativeChatStop(args: { return } // Lost-ack/cutover Escape may have stopped work; report uncertainty instead of inviting a second key. - onSendError(sawUnknown ? 'Stop unconfirmed — check chat before retrying' : 'Stop not sent') + onSendError( + sawUnknown + ? translate( + 'session.useNativeChatStop.stopUnconfirmed', + 'Stop unconfirmed — check chat before retrying' + ) + : translate('session.useNativeChatStop.stopNotSent', 'Stop not sent') + ) } const sendEscape = (): void => { const activeRoute = activeRouteRef.current diff --git a/mobile/src/session/use-mobile-pr-actions.ts b/mobile/src/session/use-mobile-pr-actions.ts index e91042574f9..634bde95c42 100644 --- a/mobile/src/session/use-mobile-pr-actions.ts +++ b/mobile/src/session/use-mobile-pr-actions.ts @@ -1,4 +1,5 @@ import { useCallback, useEffect, useReducer, useRef } from 'react' +import { useMobileTranslation } from '../i18n/use-mobile-translation' import type { ConnectionState } from '../transport/types' import type { RpcClient } from '../transport/rpc-client' import { @@ -40,6 +41,7 @@ function realMutations(client: RpcOperationSender, worktreeId: string): PrAction } export function useMobilePrActions(input: PrActionsInput) { + useMobileTranslation() const { client, connState, worktreeId, prNumber, headSha, prRepo, refetch } = input const [, forceRender] = useReducer((n: number) => n + 1, 0) diff --git a/mobile/src/session/use-mobile-pr-ai-triage.ts b/mobile/src/session/use-mobile-pr-ai-triage.ts index bdcafab37ed..e3a5b6e9d8a 100644 --- a/mobile/src/session/use-mobile-pr-ai-triage.ts +++ b/mobile/src/session/use-mobile-pr-ai-triage.ts @@ -2,6 +2,7 @@ import { useCallback, useRef, useState } from 'react' import type { ConnectionState } from '../transport/types' import type { RpcClient } from '../transport/rpc-client' import { triggerError, triggerSuccess } from '../platform/haptics' +import { useMobileTranslation } from '../i18n/use-mobile-translation' import { launchAgentWithPrompt, promptedLaunchNotice } from './pr-ai-triage-launch' import { resolveMobileAgentLaunchAvailability, @@ -23,6 +24,8 @@ export type PrAiTriageLaunchNotice = { undeliveredPrompt: string | null } +type StoredLaunchNotice = PrAiTriageLaunchNotice | { kind: 'waitingForDesktop' | 'launchFailed' } + const NO_LAUNCH_NOTICE: PrAiTriageLaunchNotice = { success: null, error: null, @@ -41,6 +44,7 @@ type Input = { } export function useMobilePrAiTriage(input: Input) { + const t = useMobileTranslation() const { client, connState, @@ -52,7 +56,7 @@ export function useMobilePrAiTriage(input: Input) { } = input const [busyKey, setBusyKey] = useState(null) // Scope feedback by button so launches cannot display another action’s prompt-copy state. - const [notices, setNotices] = useState>>({}) + const [notices, setNotices] = useState>>({}) // Lock synchronously because async busy state cannot stop same-frame double-tap launches. const inFlightRef = useRef(false) const availability: MobileAgentLaunchAvailability = resolveMobileAgentLaunchAvailability({ @@ -61,7 +65,7 @@ export function useMobilePrAiTriage(input: Input) { statusReadable: hostStatusReadable }) - const setNotice = useCallback((key: PrAiTriageKey, notice: PrAiTriageLaunchNotice) => { + const setNotice = useCallback((key: PrAiTriageKey, notice: StoredLaunchNotice) => { setNotices((current) => ({ ...current, [key]: notice })) }, []) @@ -71,7 +75,7 @@ export function useMobilePrAiTriage(input: Input) { return false } if (!client || connState !== 'connected') { - setNotice(key, { ...NO_LAUNCH_NOTICE, error: 'Waiting for desktop…' }) + setNotice(key, { kind: 'waitingForDesktop' }) triggerError() return false } @@ -102,10 +106,12 @@ export function useMobilePrAiTriage(input: Input) { return notice.succeeded } catch (err) { triggerError() - setNotice(key, { - ...NO_LAUNCH_NOTICE, - error: err instanceof Error ? err.message : 'Failed to launch agent' - }) + setNotice( + key, + err instanceof Error + ? { ...NO_LAUNCH_NOTICE, error: err.message } + : { kind: 'launchFailed' } + ) return false } finally { inFlightRef.current = false @@ -117,7 +123,19 @@ export function useMobilePrAiTriage(input: Input) { return { availability, - noticeFor: (key: PrAiTriageKey): PrAiTriageLaunchNotice => notices[key] ?? NO_LAUNCH_NOTICE, + noticeFor: (key: PrAiTriageKey): PrAiTriageLaunchNotice => { + const notice = notices[key] ?? NO_LAUNCH_NOTICE + if (!('kind' in notice)) { + return notice + } + return { + ...NO_LAUNCH_NOTICE, + error: + notice.kind === 'waitingForDesktop' + ? t('session.usePrAiTriage.waitingForDesktop', 'Waiting for desktop…') + : t('session.usePrAiTriage.launchFailed', 'Failed to launch agent') + } + }, isBusy: useCallback((key: PrAiTriageKey) => busyKey === key, [busyKey]), launch } diff --git a/mobile/src/session/use-mobile-pr-comment-actions.ts b/mobile/src/session/use-mobile-pr-comment-actions.ts index bce2ce2094c..6cf305caae7 100644 --- a/mobile/src/session/use-mobile-pr-comment-actions.ts +++ b/mobile/src/session/use-mobile-pr-comment-actions.ts @@ -13,6 +13,7 @@ import { type GitHubPrMutationOutcome } from './github-pr-mutations' import { triggerError, triggerSuccess } from '../platform/haptics' +import { useMobileTranslation } from '../i18n/use-mobile-translation' import { buildAddRootCommentParams, buildDeleteCommentParams, @@ -92,9 +93,10 @@ function deleteKey(commentId: number): string { const ROOT_KEY = 'root' export function useMobilePrCommentActions(input: PrCommentActionsInput) { + const t = useMobileTranslation() const { client, connState, worktreeId, prNumber, prRepo, refetch } = input const [busyKeys, setBusyKeys] = useState>(() => new Set()) - const [error, setError] = useState(null) + const [error, setError] = useState(null) // Guard against overlapping fires of the same key (double-tap before refetch). const inFlightRef = useRef>(new Set()) @@ -137,7 +139,7 @@ export function useMobilePrCommentActions(input: PrCommentActionsInput) { } catch (err) { // Contain mutation/refetch throws as false with feedback; callers rely on the boolean contract. triggerError() - setError(err instanceof Error ? err.message : 'Comment action failed') + setError(err instanceof Error ? err.message : { kind: 'actionFailed' }) return false } finally { inFlightRef.current.delete(key) @@ -204,7 +206,10 @@ export function useMobilePrCommentActions(input: PrCommentActionsInput) { return { ready, - error, + error: + typeof error === 'string' || error === null + ? error + : t('session.usePrCommentActions.actionFailed', 'Comment action failed'), clearError: useCallback(() => setError(null), []), isReplyBusy: useCallback((commentId: number) => busyKeys.has(replyKey(commentId)), [busyKeys]), isResolveBusy: useCallback( diff --git a/mobile/src/session/use-mobile-pr-title-action.ts b/mobile/src/session/use-mobile-pr-title-action.ts index 32f36f9a63a..0f657a42727 100644 --- a/mobile/src/session/use-mobile-pr-title-action.ts +++ b/mobile/src/session/use-mobile-pr-title-action.ts @@ -5,6 +5,7 @@ import type { GitHubPrRepoSlug } from './github-pr-rpc' import type { RpcOperationSender } from '../transport/rpc-operation-sender' import { fetchUpdatePRTitle, type GitHubPrMutationOutcome } from './github-pr-mutations' import { triggerError, triggerSuccess } from '../platform/haptics' +import { useMobileTranslation } from '../i18n/use-mobile-translation' import { buildUpdatePRTitleParams } from './pr-title-edit' export type PrTitleMutations = { @@ -32,9 +33,12 @@ function realMutations(client: RpcOperationSender, worktreeId: string): PrTitleM } export function useMobilePrTitleAction(input: PrTitleActionInput) { + const t = useMobileTranslation() const { client, connState, worktreeId, prNumber, prRepo, refetch } = input const [saving, setSaving] = useState(false) - const [error, setError] = useState(null) + const [error, setError] = useState( + null + ) const inFlightRef = useRef(false) const mutations = useMemo( @@ -54,7 +58,7 @@ export function useMobilePrTitleAction(input: PrTitleActionInput) { } // Offline Save must explain failure and leave the editor open rather than silently do nothing. if (!ready || !mutations) { - setError('Not connected to desktop.') + setError({ kind: 'disconnected' }) return false } inFlightRef.current = true @@ -73,7 +77,7 @@ export function useMobilePrTitleAction(input: PrTitleActionInput) { } catch (err) { // Contain fire-and-forget save/refetch rejection and return false with feedback. triggerError() - setError(err instanceof Error ? err.message : 'Failed to update title.') + setError(err instanceof Error ? err.message : { kind: 'updateFailed' }) return false } finally { inFlightRef.current = false @@ -86,7 +90,12 @@ export function useMobilePrTitleAction(input: PrTitleActionInput) { return { ready, saving, - error, + error: + typeof error === 'string' || error === null + ? error + : error.kind === 'disconnected' + ? t('session.usePrTitleAction.disconnected', 'Not connected to desktop.') + : t('session.usePrTitleAction.updateFailed', 'Failed to update title.'), clearError: useCallback(() => setError(null), []), setTitle } diff --git a/mobile/src/session/use-mobile-session-accessory-selection.ts b/mobile/src/session/use-mobile-session-accessory-selection.ts index c4634f8844d..ab3704015ee 100644 --- a/mobile/src/session/use-mobile-session-accessory-selection.ts +++ b/mobile/src/session/use-mobile-session-accessory-selection.ts @@ -7,6 +7,7 @@ import { createTerminalAccessoryRepeatSender } from '../terminal/terminal-accessory-repeat' import { useClipboardReader, useClipboardWriter } from '../platform/clipboard' +import { translate } from '../i18n/mobile-i18n' import { newTabRepoListRead, type MobileRuntimeRepoSummary } from './mobile-session-read-operations' import { triggerSelection, @@ -167,7 +168,7 @@ export function useMobileSessionAccessorySelection(scope: MobileSessionTerminalI triggerSuccess() // Why: Android 13+ shows its own system copy toast; iOS shows none, so only iOS needs our in-app toast. if (Platform.OS === 'ios') { - showToast('Copied') + showToast(translate('common.copied', 'Copied')) } terminalRefs.current.get(handle)?.cancelSelect() } catch (e) { @@ -177,7 +178,7 @@ export function useMobileSessionAccessorySelection(scope: MobileSessionTerminalI name: err.name, message: err.message }) - showToast("Couldn't copy", 1500) + showToast(translate('session.useAccessorySelection.copyFailed', "Couldn't copy"), 1500) } }, [clipboard, showToast] @@ -191,7 +192,7 @@ export function useMobileSessionAccessorySelection(scope: MobileSessionTerminalI try { await clipboard.writeText(text) } catch { - showToast("Couldn't copy", 1500) + showToast(translate('session.useAccessorySelection.copyFailed', "Couldn't copy"), 1500) } }, [activeHandleRef, clipboard, showToast] @@ -203,7 +204,13 @@ export function useMobileSessionAccessorySelection(scope: MobileSessionTerminalI return } console.warn('[mobile-clip] selection evicted') - showToast('Selection cleared (scrolled out of buffer)', 1500) + showToast( + translate( + 'session.useAccessorySelection.selectionCleared', + 'Selection cleared (scrolled out of buffer)' + ), + 1500 + ) setSelectModeActive(false) }, [showToast] diff --git a/mobile/src/session/use-mobile-session-bulk-close.ts b/mobile/src/session/use-mobile-session-bulk-close.ts index c9d86788fe7..033f5678c81 100644 --- a/mobile/src/session/use-mobile-session-bulk-close.ts +++ b/mobile/src/session/use-mobile-session-bulk-close.ts @@ -4,12 +4,15 @@ import { createCloseWithBulkActions } from './mobile-bulk-close-sheet-actions' import type { RpcSuccess } from '../transport/types' -import { BULK_TAB_CLOSE_COPY } from '../../../src/shared/bulk-tab-close-confirmation' +import { mobileBulkTabCloseCopy } from './mobile-bulk-tab-close-copy' +import { useMobileTranslation } from '../i18n/use-mobile-translation' import { activateMobileSessionTab } from './mobile-session-tab-activation' import type { MobileSessionTab, SessionTabsResult } from './mobile-session-route-types' import type { MobileSessionCloseActionsModel } from './use-mobile-session-close-actions' export function useMobileSessionBulkClose(scope: MobileSessionCloseActionsModel) { + const t = useMobileTranslation() + const copy = mobileBulkTabCloseCopy(t) const { worktreeId, client, @@ -38,9 +41,9 @@ export function useMobileSessionBulkClose(scope: MobileSessionCloseActionsModel) } const bulkCloseConfirm = { visible: answerBulkClose !== null, - title: BULK_TAB_CLOSE_COPY.title.others, - message: BULK_TAB_CLOSE_COPY.description, - confirmLabel: BULK_TAB_CLOSE_COPY.confirm, + title: copy.title.others, + message: copy.description, + confirmLabel: copy.confirm, destructive: true, onConfirm: () => resolveBulkCloseConfirm(true), onCancel: () => resolveBulkCloseConfirm(false) diff --git a/mobile/src/session/use-mobile-session-content-create-actions.ts b/mobile/src/session/use-mobile-session-content-create-actions.ts index 649cf9cdeb9..edca25ae228 100644 --- a/mobile/src/session/use-mobile-session-content-create-actions.ts +++ b/mobile/src/session/use-mobile-session-content-create-actions.ts @@ -1,4 +1,5 @@ import { normalizeBrowserUrl } from '../browser/browser-url' +import { translate } from '../i18n/mobile-i18n' import { captureMobileFileMutationOwnership } from '../files/mobile-file-mutation-ownership' import { browserGoBack, @@ -64,7 +65,13 @@ export function useMobileSessionContentCreateActions( if (isFileExistsErrorMessage(message) && attempt < 100) { continue } - throw new Error(message || 'Failed to create markdown note') + throw new Error( + message || + translate( + 'session.useContentCreateActions.noteCreateFailed', + 'Failed to create markdown note' + ) + ) } const openResponse = await sourceFileOpenRun.request( @@ -76,9 +83,20 @@ export function useMobileSessionContentCreateActions( scheduleDelayedAction(() => void fetchSessionTabs(), 300) return } - throw new Error('Unable to create untitled markdown note') + throw new Error( + translate( + 'session.useContentCreateActions.untitledNoteCreateFailed', + 'Unable to create untitled markdown note' + ) + ) } catch (err) { - const message = err instanceof Error ? err.message : 'Failed to create markdown note' + const message = + err instanceof Error + ? err.message + : translate( + 'session.useContentCreateActions.noteCreateFailed', + 'Failed to create markdown note' + ) setCreateError(message) showToast(message, 1800) } finally { @@ -92,12 +110,18 @@ export function useMobileSessionContentCreateActions( } // Read capability via ref so pre-probe taps and old callbacks see the current host facts. if (browserScreencastSupportedRef.current !== true) { - showToast('Desktop update required for mobile browser streaming', 1600) + showToast( + translate( + 'session.useContentCreateActions.browserUpdateRequired', + 'Desktop update required for mobile browser streaming' + ), + 1600 + ) return false } const url = normalizeBrowserUrl(rawUrl) if (!url) { - const message = 'Enter a valid URL' + const message = translate('session.useContentCreateActions.invalidUrl', 'Enter a valid URL') setCreateError(message) showToast(message, 1400) return false @@ -128,7 +152,13 @@ export function useMobileSessionContentCreateActions( scheduleDelayedAction(() => void fetchPendingBrowserSessionTabs(), 1200) return true } catch (err) { - const message = err instanceof Error ? err.message : 'Failed to create browser' + const message = + err instanceof Error + ? err.message + : translate( + 'session.useContentCreateActions.browserCreateFailed', + 'Failed to create browser' + ) setCreateError(message) showToast(message, 1800) return false @@ -143,7 +173,13 @@ export function useMobileSessionContentCreateActions( method: MobileBrowserNavigationMethod ) { if (!client || !tab.browserPageId) { - showToast('Browser page is not available yet.', 1500) + showToast( + translate( + 'session.useContentCreateActions.browserUnavailable', + 'Browser page is not available yet.' + ), + 1500 + ) return } try { @@ -159,7 +195,13 @@ export function useMobileSessionContentCreateActions( interpretOrThrowRefusalMessage(() => command.interpret(response), '') scheduleDelayedAction(() => void fetchSessionTabs(), 250) } catch (err) { - const message = err instanceof Error ? err.message : 'Browser command failed' + const message = + err instanceof Error + ? err.message + : translate( + 'session.useContentCreateActions.browserCommandFailed', + 'Browser command failed' + ) showToast(message, 1600) } } diff --git a/mobile/src/session/use-mobile-session-diff-comments.ts b/mobile/src/session/use-mobile-session-diff-comments.ts index 758c93723ad..6947c56d1c4 100644 --- a/mobile/src/session/use-mobile-session-diff-comments.ts +++ b/mobile/src/session/use-mobile-session-diff-comments.ts @@ -1,5 +1,6 @@ import { useEffect, useCallback, useMemo, useRef, useState } from 'react' import { formatDiffComments } from '../../../src/shared/diff-comments-format' +import { translate } from '../i18n/mobile-i18n' import { useClipboardWriter } from '../platform/clipboard' import { interpretOrThrowRefusalMessage } from '../transport/rpc-refusal-message' import type { SendRequestOptions } from '../transport/unvalidated-rpc-request-port' @@ -95,7 +96,9 @@ export function useMobileSessionDiffComments(scope: MobileSessionDocumentReaders try { await enqueueReviewNotesRequest(sync, async () => { if (!client || connState !== 'connected') { - throw new Error('Waiting for desktop...') + throw new Error( + translate('session.useDiffComments.waitingForDesktop', 'Waiting for desktop...') + ) } // Why: send the host's list plus only this change; an older host stores the list whole. const base = sync.host @@ -111,7 +114,7 @@ export function useMobileSessionDiffComments(scope: MobileSessionDocumentReaders ) const saved = interpretOrThrowRefusalMessage( () => sessionWorktreeNotesWrite.interpret(response), - 'Failed to save review notes' + translate('session.useDiffComments.saveNotesFailed', 'Failed to save review notes') ) sync.pending = sync.pending.filter((pending) => pending !== save) sync.host = @@ -162,11 +165,16 @@ export function useMobileSessionDiffComments(scope: MobileSessionDocumentReaders try { await saveDiffComments(result.comments) triggerSuccess() - showToast('Note added') + showToast(translate('session.useDiffComments.noteAdded', 'Note added')) return true } catch (err) { triggerError() - showToast(err instanceof Error ? err.message : 'Failed to save note', 1600) + showToast( + err instanceof Error + ? err.message + : translate('session.useDiffComments.saveNoteFailed', 'Failed to save note'), + 1600 + ) return false } finally { setDiffCommentBusy(false) @@ -191,7 +199,12 @@ export function useMobileSessionDiffComments(scope: MobileSessionDocumentReaders triggerSelection() } catch (err) { triggerError() - showToast(err instanceof Error ? err.message : 'Failed to delete note', 1600) + showToast( + err instanceof Error + ? err.message + : translate('session.useDiffComments.deleteNoteFailed', 'Failed to delete note'), + 1600 + ) } finally { setDiffCommentBusy(false) } @@ -207,10 +220,10 @@ export function useMobileSessionDiffComments(scope: MobileSessionDocumentReaders try { await clipboard.writeText(formatDiffComments(comments)) triggerSuccess() - showToast('Notes copied') + showToast(translate('session.useDiffComments.notesCopied', 'Notes copied')) } catch { triggerError() - showToast("Couldn't copy notes", 1600) + showToast(translate('session.useDiffComments.copyNotesFailed', "Couldn't copy notes"), 1600) } }, [clipboard, showToast]) diff --git a/mobile/src/session/use-mobile-session-markdown-actions.ts b/mobile/src/session/use-mobile-session-markdown-actions.ts index 12986f27ade..c8b4823ccaa 100644 --- a/mobile/src/session/use-mobile-session-markdown-actions.ts +++ b/mobile/src/session/use-mobile-session-markdown-actions.ts @@ -1,6 +1,8 @@ import { useCallback } from 'react' import { Keyboard, Platform } from 'react-native' import { useClipboardWriter } from '../platform/clipboard' +import { translate } from '../i18n/mobile-i18n' +import { useMobileTranslation } from '../i18n/use-mobile-translation' import { useBackClaim } from '../navigation/use-back-claim' import { useSessionLeaveGuard } from './session-leave-guard' import { markdownTabSave } from './mobile-session-write-operations' @@ -29,6 +31,7 @@ export type MobileSessionMarkdownActionsScope = Pick< > export function useMobileSessionMarkdownActions(scope: MobileSessionMarkdownActionsScope) { + const t = useMobileTranslation() const { hostId, worktreeId, @@ -76,11 +79,11 @@ export function useMobileSessionMarkdownActions(scope: MobileSessionMarkdownActi await clipboard.writeText(current.localContent) } catch { triggerError() - showToast("Couldn't copy", 1500) + showToast(translate('session.useMarkdownActions.copyFailed', "Couldn't copy"), 1500) return } triggerSuccess() - showToast('Copied') + showToast(translate('common.copied', 'Copied')) }, [clipboard, markdownDocs, showToast] ) @@ -89,10 +92,13 @@ export function useMobileSessionMarkdownActions(scope: MobileSessionMarkdownActi async (tab: Extract) => { try { await clipboard.writeText(tab.relativePath || tab.filePath) - showToast('Path copied') + showToast(translate('session.useMarkdownActions.pathCopied', 'Path copied')) } catch { triggerError() - showToast("Couldn't copy path", 1500) + showToast( + translate('session.useMarkdownActions.copyPathFailed', "Couldn't copy path"), + 1500 + ) } }, [clipboard, showToast] @@ -103,11 +109,15 @@ export function useMobileSessionMarkdownActions(scope: MobileSessionMarkdownActi for (const [tabId, doc] of markdownDocs) { if (doc.status === 'ready' && doc.isDirty) { const tab = sessionTabs.find((candidate) => candidate.id === tabId) - drafts.push({ tabId, title: tab?.title || 'Markdown', content: doc.localContent }) + drafts.push({ + tabId, + title: tab?.title || t('session.useMarkdownActions.markdownTitle', 'Markdown'), + content: doc.localContent + }) } } return drafts - }, [markdownDocs, sessionTabs]) + }, [markdownDocs, sessionTabs, t]) const leaveSession = useCallback(() => { if (router.canGoBack()) { @@ -221,10 +231,13 @@ export function useMobileSessionMarkdownActions(scope: MobileSessionMarkdownActi }) markdownSaveSeqRef.current.delete(tab.id) triggerSuccess() - showToast('Saved') + showToast(translate('session.useMarkdownActions.saved', 'Saved')) } catch (error) { triggerError() - const message = error instanceof Error ? error.message : 'Save failed' + const message = + error instanceof Error + ? error.message + : translate('session.useMarkdownActions.saveFailed', 'Save failed') if (markdownSaveSeqRef.current.get(tab.id) !== saveSeq) { return } @@ -237,7 +250,7 @@ export function useMobileSessionMarkdownActions(scope: MobileSessionMarkdownActi ...existing, localRevision: (existing.localRevision ?? 0) + 1, saving: false, - saveError: message || 'Save failed' + saveError: message || translate('session.useMarkdownActions.saveFailed', 'Save failed') }) }) } finally { diff --git a/mobile/src/session/use-mobile-session-native-chat-dictation.ts b/mobile/src/session/use-mobile-session-native-chat-dictation.ts index 6353fe2dba5..0f757d9c3bc 100644 --- a/mobile/src/session/use-mobile-session-native-chat-dictation.ts +++ b/mobile/src/session/use-mobile-session-native-chat-dictation.ts @@ -26,6 +26,7 @@ import { useMobileNativeChatSendError } from './native-chat/use-mobile-native-ch import { mobileNativeChatScopeKey } from './native-chat/mobile-native-chat-scope-key' import { isMobileNativeChatAgentConfirmed } from './native-chat/mobile-native-chat-eligibility' import { useMobileSendCompletionGeneration } from './use-mobile-send-completion-generation' +import { nativeChatDictationCopy } from './mobile-session-native-chat-dictation-copy' import type { MobileSessionFeedbackCapabilitiesModel } from './use-mobile-session-feedback-capabilities' export function useMobileSessionNativeChatDictation( @@ -128,8 +129,8 @@ export function useMobileSessionNativeChatDictation( const keepUndeliveredDictation = (text: string) => { triggerError() void clipboard.writeText(text).then( - () => showToast("Couldn't insert dictation, so it was copied to the clipboard"), - () => showToast("Couldn't insert dictation") + () => showToast(nativeChatDictationCopy.insertFailedCopied()), + () => showToast(nativeChatDictationCopy.insertFailed()) ) } @@ -143,7 +144,7 @@ export function useMobileSessionNativeChatDictation( // Chat dictation is inserted locally; terminal mode retains live-input routing. if (destination.landsInChat(showNativeChatRef.current)) { if (destination.insertInChat(text)) { - showToast('Dictation inserted') + showToast(nativeChatDictationCopy.inserted()) } else { keepUndeliveredDictation(text) } @@ -163,7 +164,7 @@ export function useMobileSessionNativeChatDictation( (await flushPendingLiveInputBeforeExternalSend(insertHandle)) && (await sendLiveTerminalInput(insertHandle, route.text)) if (sent) { - showToast('Dictation inserted') + showToast(nativeChatDictationCopy.inserted()) } else { keepUndeliveredDictation(route.text) } @@ -173,7 +174,7 @@ export function useMobileSessionNativeChatDictation( // The draft written is the active terminal's; a selection read in another terminal only appends there. const anchor = routeContext?.handle === activeHandleRef.current ? routeContext.anchor : null setInput((current) => insertDictationText(current, route.text, anchor).text) - showToast('Dictation inserted') + showToast(nativeChatDictationCopy.inserted()) }, onError: (err) => { dictationRouteContextRef.current = null diff --git a/mobile/src/session/use-mobile-session-panel-route-actions.tsx b/mobile/src/session/use-mobile-session-panel-route-actions.tsx index b509d0863d4..a8c3bc42882 100644 --- a/mobile/src/session/use-mobile-session-panel-route-actions.tsx +++ b/mobile/src/session/use-mobile-session-panel-route-actions.tsx @@ -8,6 +8,7 @@ import { panelRouteDescriptor } from './session-panel-host' import { MobileAgentIcon } from '../components/MobileAgentIcon' +import { useMobileTranslation } from '../i18n/use-mobile-translation' import { useWorkspaceExecutionHost, workspaceRouteHref, @@ -16,6 +17,7 @@ import { import type { MobileSessionPresentationModel } from './use-mobile-session-presentation' export function useMobileSessionPanelRouteActions(scope: MobileSessionPresentationModel) { + const t = useMobileTranslation() const executionHost = useWorkspaceExecutionHost() const { hostId, @@ -44,8 +46,9 @@ export function useMobileSessionPanelRouteActions(scope: MobileSessionPresentati handleCreateTerminal } = scope const otherRuntimeAgentRow = { - label: 'Agents on Another Server', - hint: 'Pair that server directly', + id: 'otherServerAgents', + label: t('session.usePanelRouteActions.otherServerAgents', 'Agents on Another Server'), + hint: t('session.usePanelRouteActions.pairServerHint', 'Pair that server directly'), icon: Bot, disabled: true, onPress: () => {} @@ -54,7 +57,8 @@ export function useMobileSessionPanelRouteActions(scope: MobileSessionPresentati createTabAgentLoadState === 'loading' ? [ { - label: 'Detecting Agents', + id: 'detectingAgents', + label: t('session.usePanelRouteActions.detectingAgents', 'Detecting Agents'), icon: Bot, disabled: true, loading: true, @@ -63,6 +67,7 @@ export function useMobileSessionPanelRouteActions(scope: MobileSessionPresentati ] : createTabAgentOptions.length > 0 ? createTabAgentOptions.map((option) => ({ + id: `agent:${option.agent}`, label: option.label, renderIcon: () => , onPress: () => { @@ -73,7 +78,8 @@ export function useMobileSessionPanelRouteActions(scope: MobileSessionPresentati : createTabAgentLoadState === 'loaded' ? [ { - label: 'No Enabled Agents', + id: 'noEnabledAgents', + label: t('session.usePanelRouteActions.noEnabledAgents', 'No Enabled Agents'), icon: Bot, disabled: true, onPress: () => {} @@ -84,8 +90,15 @@ export function useMobileSessionPanelRouteActions(scope: MobileSessionPresentati : createTabAgentLoadState === 'error' ? [ { - label: 'Agent Presets Unavailable', - hint: 'Check the host connection', + id: 'presetsUnavailable', + label: t( + 'session.usePanelRouteActions.presetsUnavailable', + 'Agent Presets Unavailable' + ), + hint: t( + 'session.usePanelRouteActions.checkHostHint', + 'Check the host connection' + ), icon: Bot, disabled: true, onPress: () => {} @@ -98,7 +111,8 @@ export function useMobileSessionPanelRouteActions(scope: MobileSessionPresentati : createTabAgentLoadState === 'loading' ? [ { - label: 'Detecting Agents', + id: 'detectingAgents', + label: t('session.usePanelRouteActions.detectingAgents', 'Detecting Agents'), icon: Bot, disabled: true, loading: true, @@ -107,8 +121,9 @@ export function useMobileSessionPanelRouteActions(scope: MobileSessionPresentati ] : createTabAgentOptions.length > 0 ? createTabAgentOptions.map((option) => ({ + id: `agent:${option.agent}`, label: option.label, - hint: 'New agent session', + hint: t('session.usePanelRouteActions.newSessionHint', 'New agent session'), icon: Bot, onPress: () => { const delivery = pendingDiffNotesDelivery @@ -127,7 +142,8 @@ export function useMobileSessionPanelRouteActions(scope: MobileSessionPresentati : createTabAgentLoadState === 'loaded' ? [ { - label: 'No Enabled Agents', + id: 'noEnabledAgents', + label: t('session.usePanelRouteActions.noEnabledAgents', 'No Enabled Agents'), icon: Bot, disabled: true, onPress: () => {} @@ -138,8 +154,12 @@ export function useMobileSessionPanelRouteActions(scope: MobileSessionPresentati : createTabAgentLoadState === 'error' ? [ { - label: 'Agent Presets Unavailable', - hint: 'Copy notes instead', + id: 'presetsUnavailable', + label: t( + 'session.usePanelRouteActions.presetsUnavailable', + 'Agent Presets Unavailable' + ), + hint: t('session.usePanelRouteActions.copyNotesHint', 'Copy notes instead'), icon: Bot, disabled: true, onPress: () => {} diff --git a/mobile/src/session/use-mobile-session-startup.ts b/mobile/src/session/use-mobile-session-startup.ts index bbea4260b2a..e5f07cc1871 100644 --- a/mobile/src/session/use-mobile-session-startup.ts +++ b/mobile/src/session/use-mobile-session-startup.ts @@ -1,4 +1,5 @@ import { useEffect } from 'react' +import { translate } from '../i18n/mobile-i18n' import { worktreeActivate } from '../host-screen/host-screen-operations' import { headlessActivationNeedsHostRenderer } from '../worktree/worktree-activation-result' import { createInitialSessionAutoCreateState } from './use-initial-session-terminal-autocreate' @@ -123,7 +124,13 @@ export function useMobileSessionStartup(scope: MobileSessionKeyboardStateModel) activation?.accepted === true && headlessActivationNeedsHostRenderer(activation.value) ) { - showToast('Open Orca on the host to wake sleeping agents.', 3000) + showToast( + translate( + 'session.useStartup.wakeAgents', + 'Open Orca on the host to wake sleeping agents.' + ), + 3000 + ) } } if (client && created !== '1' && !isFloatingWorkspaceRoute) { diff --git a/mobile/src/session/use-mobile-session-terminal-create-actions.ts b/mobile/src/session/use-mobile-session-terminal-create-actions.ts index c6171c31e87..9684358ed57 100644 --- a/mobile/src/session/use-mobile-session-terminal-create-actions.ts +++ b/mobile/src/session/use-mobile-session-terminal-create-actions.ts @@ -1,7 +1,8 @@ import type { MobileQuickCommandLaunch } from '../terminal/quick-commands' -import type { RpcFailure, RpcSuccess } from '../transport/types' +import type { RpcSuccess } from '../transport/types' import { sessionTabCreateTerminal } from './mobile-session-write-operations' import { triggerSuccess, triggerError } from '../platform/haptics' +import { terminalCreateCopy } from './mobile-session-terminal-create-copy' import { buildTerminalSendParams } from '../terminal/terminal-send-request' import { terminalRecordsEqual } from './mobile-terminal-records' import type { MobileNewTabAgentOption } from './mobile-new-tab-agent-options' @@ -83,11 +84,11 @@ export function useMobileSessionTerminalCreateActions(scope: MobileSessionAttach !client || (sourceFork && (!hostSupportsMobileChatFork(hostCapabilities) || !isCurrent())) ) { - reportFork(`Open the ${agentName} chat on a connected, updated host and try again.`) + reportFork(terminalCreateCopy.forkUnavailable(agentName)) return sourceOutcome('unavailable') } if (creatingTerminalRef.current) { - reportFork(`Another terminal is opening. Wait, then retry the ${agentName} chat.`) + reportFork(terminalCreateCopy.forkBusy(agentName)) return sourceOutcome('busy') } // Use a compact idempotency key so reconnect retries resolve the same terminal within schema length limits. @@ -104,7 +105,7 @@ export function useMobileSessionTerminalCreateActions(scope: MobileSessionAttach if (!isCurrent()) { return } - const reason = hostReason.trim() || options?.errorToast || 'Failed to create terminal' + const reason = hostReason.trim() || options?.errorToast || terminalCreateCopy.createFailed() setCreateError(reason) // Only empty sessions show inline errors; existing tabs need toast feedback. triggerError() @@ -224,7 +225,7 @@ export function useMobileSessionTerminalCreateActions(scope: MobileSessionAttach const existing = prev.find((terminal) => terminal.handle === createdHandle) const createdTerminal: Terminal = { handle: createdHandle, - title: created.title || existing?.title || 'Terminal', + title: created.title || existing?.title || terminalCreateCopy.terminalTitle(), terminalTheme: created.terminalTheme ?? existing?.terminalTheme, isActive: true } @@ -253,24 +254,23 @@ export function useMobileSessionTerminalCreateActions(scope: MobileSessionAttach ) .then((sendResponse) => { if (!sendResponse.ok) { - throw new Error( - (sendResponse as RpcFailure).error.message || 'Failed to send notes' - ) + throw new Error(sendResponse.error.message || terminalCreateCopy.sendNotesFailed()) } const result = (sendResponse as RpcSuccess).result as { send?: { accepted?: boolean } } if (result.send?.accepted === false) { - throw new Error('Terminal input is locked by another client.') + throw new Error(terminalCreateCopy.inputLocked()) } triggerSuccess() - showToast(options.successToast ?? 'Notes sent') + showToast(options.successToast ?? terminalCreateCopy.notesSent()) options.onPromptSent?.() }) .catch((err) => { triggerError() showToast( - options.errorToast ?? (err instanceof Error ? err.message : "Couldn't send notes"), + options.errorToast ?? + (err instanceof Error ? err.message : terminalCreateCopy.sendNotesError()), 1800 ) }) diff --git a/mobile/src/session/use-mobile-session-terminal-input.ts b/mobile/src/session/use-mobile-session-terminal-input.ts index 36031079c31..727d3c84d35 100644 --- a/mobile/src/session/use-mobile-session-terminal-input.ts +++ b/mobile/src/session/use-mobile-session-terminal-input.ts @@ -1,5 +1,6 @@ import { reportWorkerTerminalUserInput } from '../terminal/worker-terminal-takeover-report' import { useCallback } from 'react' +import { translate } from '../i18n/mobile-i18n' import { terminalBufferClear, terminalInputSend } from '../terminal/mobile-terminal-operations' import { clearTerminalLiveInputFocusTimer, @@ -255,7 +256,10 @@ export function useMobileSessionTerminalInput(scope: MobileSessionFileActionsMod return } if (terminalBufferClear.interpret(response) !== true) { - showToast("Couldn't clear terminal", 1500) + showToast( + translate('session.useTerminalInput.clearFailed', "Couldn't clear terminal"), + 1500 + ) return } // Why: an unsubscribed tab draws a fresh snapshot when it next subscribes; subscribing it here would leak a background stream. @@ -263,10 +267,13 @@ export function useMobileSessionTerminalInput(scope: MobileSessionFileActionsMod unsubscribeTerminal(target.handle) subscribeToTerminal(target.handle) } - showToast('Terminal cleared') + showToast(translate('session.useTerminalInput.cleared', 'Terminal cleared')) } catch { if (isCurrent()) { - showToast("Couldn't clear terminal", 1500) + showToast( + translate('session.useTerminalInput.clearFailed', "Couldn't clear terminal"), + 1500 + ) } } } diff --git a/mobile/src/session/use-mobile-session-terminal-send-actions.ts b/mobile/src/session/use-mobile-session-terminal-send-actions.ts index 5a69a1c1b96..e9ddf5957d5 100644 --- a/mobile/src/session/use-mobile-session-terminal-send-actions.ts +++ b/mobile/src/session/use-mobile-session-terminal-send-actions.ts @@ -1,6 +1,7 @@ import { reportWorkerTerminalUserInput } from '../terminal/worker-terminal-takeover-report' import { useCallback } from 'react' import { Keyboard } from 'react-native' +import { translate } from '../i18n/mobile-i18n' import { triggerError } from '../platform/haptics' import type { createTerminalLiveAccessoryInput } from '../terminal/terminal-live-accessory-input' import { sendTerminalLiveAccessoryRawBytes } from '../terminal/terminal-live-accessory-raw-send' @@ -199,7 +200,13 @@ export function useMobileSessionTerminalSendActions(scope: MobileSessionTerminal } if (!isTerminalLiveInputWithinByteLimit(text)) { triggerError() - showToast('Input too large (max 256 KiB)', 1500) + showToast( + translate( + 'session.useSessionTerminalSendActions.inputTooLarge', + 'Input too large (max 256 KiB)' + ), + 1500 + ) return false } const rpc = clientRef.current diff --git a/mobile/src/session/use-mobile-structured-queued-message-controls.ts b/mobile/src/session/use-mobile-structured-queued-message-controls.ts index 2d6694801d3..4c020eebc54 100644 --- a/mobile/src/session/use-mobile-structured-queued-message-controls.ts +++ b/mobile/src/session/use-mobile-structured-queued-message-controls.ts @@ -3,6 +3,7 @@ import { useMobileQueuedMessageComposerActions } from './use-mobile-queued-messa // The host owns queued drafts; render its published cards even when it cannot queue new sends. import { useCallback, useMemo } from 'react' +import { useMobileTranslation } from '../i18n/use-mobile-translation' import type { AgentJournalRenderItem, AgentJournalSubmission @@ -70,6 +71,7 @@ export function useMobileStructuredQueuedMessageControls(args: { onSendError: (message: string) => void onActionResolved?: () => void }): MobileStructuredQueuedMessageControls { + const t = useMobileTranslation() const { appendComposerText, mutate, @@ -104,7 +106,8 @@ export function useMobileStructuredQueuedMessageControls(args: { orderedMessages, queuePause, queuedMessages, - submissions + submissions, + t ] ) const { begin: beginInlineEdit, editor } = useMobileStructuredQueuedEdit({ diff --git a/mobile/src/session/use-mobile-terminal-paste.ts b/mobile/src/session/use-mobile-terminal-paste.ts index 99a7d866c84..9421499d8bb 100644 --- a/mobile/src/session/use-mobile-terminal-paste.ts +++ b/mobile/src/session/use-mobile-terminal-paste.ts @@ -5,6 +5,7 @@ import { } from '../../../src/shared/terminal-bracketed-paste-text' import { reportWorkerTerminalUserInput } from '../terminal/worker-terminal-takeover-report' import { useCallback, type RefObject } from 'react' +import { translate } from '../i18n/mobile-i18n' import { terminalInputSend } from '../terminal/mobile-terminal-operations' import { useClipboardReader } from '../platform/clipboard' import type { TerminalModes } from '../terminal/terminal-webview-contract' @@ -86,7 +87,13 @@ export function useMobileTerminalPaste({ if (!image) { // Null also covers iOS paste denial and undecodable images. onError() - showToast("Couldn't read the clipboard", 1500) + showToast( + translate( + 'session.useTerminalPaste.clipboardReadFailed', + "Couldn't read the clipboard" + ), + 1500 + ) refreshCanPaste() return } @@ -105,7 +112,10 @@ export function useMobileTerminalPaste({ if (wrappedBytes > 256 * 1024) { onError() console.warn('[mobile-clip] paste oversized', { wrappedBytes }) - showToast('Paste too large (max 256 KiB)', 1500) + showToast( + translate('session.useTerminalPaste.pasteTooLarge', 'Paste too large (max 256 KiB)'), + 1500 + ) return } // Accessory paste must follow pending IME text. @@ -113,7 +123,10 @@ export function useMobileTerminalPaste({ const currentClient = clientRef.current if (!currentClient || connStateRef.current !== 'connected') { onError() - showToast('Paste failed (disconnected)', 1500) + showToast( + translate('session.useTerminalPaste.disconnected', 'Paste failed (disconnected)'), + 1500 + ) return } if ( @@ -121,12 +134,18 @@ export function useMobileTerminalPaste({ activeSessionTabTypeRef.current !== 'terminal' ) { onError() - showToast('Paste canceled (terminal changed)', 1500) + showToast( + translate( + 'session.useTerminalPaste.terminalChanged', + 'Paste canceled (terminal changed)' + ), + 1500 + ) return } if (!flushedPendingInput) { onError() - showToast('Paste failed', 1500) + showToast(translate('session.useTerminalPaste.pasteFailed', 'Paste failed'), 1500) return } const response = await terminalInputSend.request(currentClient, { @@ -148,11 +167,17 @@ export function useMobileTerminalPaste({ const isDisconnected = connState !== 'connected' console.warn('[mobile-clip] paste failed', { name: err.name, message: err.message }) if (isDisconnected) { - showToast('Paste failed (disconnected)', 1500) + showToast( + translate('session.useTerminalPaste.disconnected', 'Paste failed (disconnected)'), + 1500 + ) } else if (err.message === 'Clipboard image is too large') { - showToast('Image too large to paste', 1500) + showToast( + translate('session.useTerminalPaste.imageTooLarge', 'Image too large to paste'), + 1500 + ) } else { - showToast('Paste failed', 1500) + showToast(translate('session.useTerminalPaste.pasteFailed', 'Paste failed'), 1500) } } }, [ diff --git a/mobile/src/session/use-quick-commands.ts b/mobile/src/session/use-quick-commands.ts index f1b54d10f9f..0b1e4267dc6 100644 --- a/mobile/src/session/use-quick-commands.ts +++ b/mobile/src/session/use-quick-commands.ts @@ -1,10 +1,8 @@ import { useCallback, useEffect, useRef, useState } from 'react' +import { useMobileTranslation } from '../i18n/use-mobile-translation' import type { RpcClient } from '../transport/rpc-client' import { isLogicalClientCutoverError } from '../transport/stable-logical-rpc-client' -import { - interpretOrThrowRefusalMessage, - refusedRpcMessageOrFallback -} from '../transport/rpc-refusal-message' +import { refusedRpcMessageOrFallback } from '../transport/rpc-refusal-message' import type { RpcResponse } from '../transport/types' import type { TerminalQuickCommand } from '../../../src/shared/terminal-quick-command-types' import { quickCommandsRead } from './mobile-session-read-operations' @@ -30,6 +28,11 @@ type QuickCommandsState = { persist: (mutation: TerminalQuickCommandMutation) => Promise } +type QuickCommandError = { + kind: 'loadFailed' | 'saveFailed' + message?: string +} + type PendingMutation = { id: number mutation: TerminalQuickCommandMutation @@ -66,10 +69,11 @@ async function loadQuickCommandsWithCutoverRetry( } export function useQuickCommands({ client, enabled }: Args): QuickCommandsState { + const t = useMobileTranslation() const [commands, setCommands] = useState([]) const [loading, setLoading] = useState(false) const [ready, setReady] = useState(false) - const [error, setError] = useState(null) + const [error, setError] = useState(null) const commandsRef = useRef([]) const operationIdRef = useRef(0) const mutationContextRef = useRef(null) @@ -129,11 +133,14 @@ export function useQuickCommands({ client, enabled }: Args): QuickCommandsState try { next = parseNormalizedTerminalQuickCommands(quickCommandsRead.interpret(response)) } catch (err) { - setError(refusedRpcMessageOrFallback(err, 'Failed to load quick commands')) + setError({ + kind: 'loadFailed', + message: refusedRpcMessageOrFallback(err, '') || undefined + }) return } if (!next) { - setError('Failed to load quick commands') + setError({ kind: 'loadFailed' }) return } mutationContext.confirmed = next @@ -146,7 +153,7 @@ export function useQuickCommands({ client, enabled }: Args): QuickCommandsState operationId === operationIdRef.current && mutationContextRef.current === mutationContext ) { - setError(err instanceof Error ? err.message : 'Failed to load quick commands') + setError({ kind: 'loadFailed', message: err instanceof Error ? err.message : undefined }) } } finally { if ( @@ -184,25 +191,34 @@ export function useQuickCommands({ client, enabled }: Args): QuickCommandsState const send = async (): Promise => { let succeeded = false - let failureMessage: string | null = null + let failureMessage: QuickCommandError | null = null try { const response = await quickCommandsWrite.request(client, { mutation: commandMutation }) let confirmed - confirmed = interpretOrThrowRefusalMessage( - () => parseNormalizedTerminalQuickCommands(quickCommandsWrite.interpret(response)), - 'Failed to save quick command' - ) + try { + confirmed = parseNormalizedTerminalQuickCommands(quickCommandsWrite.interpret(response)) + } catch (err) { + failureMessage = { + kind: 'saveFailed', + message: refusedRpcMessageOrFallback(err, '') || undefined + } + return false + } if (!confirmed) { // An invalid payload is not an empty list: adopting it could erase host commands on the next write. - throw new Error('Failed to save quick command') + failureMessage = { kind: 'saveFailed' } + return false } mutationContext.confirmed = confirmed succeeded = true return true } catch (err) { - failureMessage = err instanceof Error ? err.message : 'Failed to save quick command' + failureMessage = { + kind: 'saveFailed', + message: err instanceof Error ? err.message : undefined + } return false } finally { mutationContext.pending = mutationContext.pending.filter( @@ -234,5 +250,13 @@ export function useQuickCommands({ client, enabled }: Args): QuickCommandsState [client, loading, ready] ) - return { commands, loading, ready, error, persist } + const errorMessage = + error === null + ? null + : (error.message ?? + (error.kind === 'loadFailed' + ? t('session.useQuickCommands.loadFailed', 'Failed to load quick commands') + : t('session.useQuickCommands.saveFailed', 'Failed to save quick command'))) + + return { commands, loading, ready, error: errorMessage, persist } } diff --git a/mobile/src/session/worktree-label.ts b/mobile/src/session/worktree-label.ts index b5f2b6e9aa4..53d1cbb1a04 100644 --- a/mobile/src/session/worktree-label.ts +++ b/mobile/src/session/worktree-label.ts @@ -1,3 +1,5 @@ +import { translate } from '../i18n/mobile-i18n' + // Deep links may provide only a repo::path id; derive a readable label from it. export function getWorktreeLabel(name: string | undefined, worktreeId: string): string { if (name?.trim()) { @@ -7,5 +9,8 @@ export function getWorktreeLabel(name: string | undefined, worktreeId: string): ? worktreeId.slice(worktreeId.indexOf('::') + 2) : worktreeId const normalized = pathPart.replace(/\\/g, '/').replace(/\/+$/, '') - return normalized.slice(normalized.lastIndexOf('/') + 1) || 'Worktree' + return ( + normalized.slice(normalized.lastIndexOf('/') + 1) || + translate('session.worktreeLabel.fallback', 'Worktree') + ) } diff --git a/mobile/src/source-control/MobileSourceControlPanel.tsx b/mobile/src/source-control/MobileSourceControlPanel.tsx index b4aebf1396d..83325a35761 100644 --- a/mobile/src/source-control/MobileSourceControlPanel.tsx +++ b/mobile/src/source-control/MobileSourceControlPanel.tsx @@ -157,7 +157,7 @@ export function MobileSourceControlPanel({ const actionSheetActions = useMobileSourceControlActionSheet(state, review) const primaryAction = useMemo( () => buildMobileSourceControlPrimaryAction({ ...primaryActionArgs, review }), - [primaryActionArgs, review] + [primaryActionArgs, review, t] ) const isHostedRepo = prController.prSidebarIsGithubRepo const prSidebarKind = prController.prSidebarState.kind diff --git a/mobile/src/source-control/hosted-review-copy.ts b/mobile/src/source-control/hosted-review-copy.ts index 0413ef0da4d..cd2a4b4ad0f 100644 --- a/mobile/src/source-control/hosted-review-copy.ts +++ b/mobile/src/source-control/hosted-review-copy.ts @@ -1,3 +1,5 @@ +import { translate } from '../i18n/mobile-i18n' + // Use the desktop provider vocabulary: GitLab says Merge Request. export type HostedReviewCopy = { shortLabel: string @@ -5,18 +7,22 @@ export type HostedReviewCopy = { titleLabel: string } -const PR_COPY: HostedReviewCopy = { - shortLabel: 'PR', - reviewLabel: 'pull request', - titleLabel: 'Pull Request' +function prCopy(): HostedReviewCopy { + return { + shortLabel: translate('sourceControl.hostedReview.prCopy.shortLabel', 'PR'), + reviewLabel: translate('sourceControl.hostedReview.prCopy.reviewLabel', 'pull request'), + titleLabel: translate('sourceControl.hostedReview.prCopy.titleLabel', 'Pull Request') + } } -const MR_COPY: HostedReviewCopy = { - shortLabel: 'MR', - reviewLabel: 'merge request', - titleLabel: 'Merge Request' +function mrCopy(): HostedReviewCopy { + return { + shortLabel: translate('sourceControl.hostedReview.mrCopy.shortLabel', 'MR'), + reviewLabel: translate('sourceControl.hostedReview.mrCopy.reviewLabel', 'merge request'), + titleLabel: translate('sourceControl.hostedReview.mrCopy.titleLabel', 'Merge Request') + } } export function hostedReviewCopy(provider: string | undefined): HostedReviewCopy { - return provider === 'gitlab' ? MR_COPY : PR_COPY + return provider === 'gitlab' ? mrCopy() : prCopy() } diff --git a/mobile/src/source-control/mobile-commit-dropdown-copy.ts b/mobile/src/source-control/mobile-commit-dropdown-copy.ts new file mode 100644 index 00000000000..1a5c84f0912 --- /dev/null +++ b/mobile/src/source-control/mobile-commit-dropdown-copy.ts @@ -0,0 +1,74 @@ +import type { CommitDropdownCopy } from '../../../src/shared/source-control-dropdown-commit-items' +import type { Translate } from '../i18n/mobile-i18n' + +export function mobileCommitDropdownCopy(t: Translate): CommitDropdownCopy { + return { + commit: t('sourceControl.commitDropdown.commit', 'Commit'), + commitStagedChanges: t( + 'sourceControl.commitDropdown.commitStagedChanges', + 'Commit staged changes' + ), + commitPush: t('sourceControl.commitDropdown.commitPush', 'Commit & Push'), + commitForcePush: t('sourceControl.commitDropdown.commitForcePush', 'Commit & Force Push'), + commitSync: t('sourceControl.commitDropdown.commitSync', 'Commit & Sync'), + checkingBranchStatus: t( + 'sourceControl.commitDropdown.checkingBranchStatus', + 'Checking branch status…' + ), + checkingPrStatus: t('sourceControl.commitDropdown.checkingPrStatus', 'Checking PR status…'), + prAlreadyMerged: t('sourceControl.commitDropdown.prAlreadyMerged', 'PR is already merged'), + checkOutBranchToPush: t( + 'sourceControl.commitDropdown.checkOutBranchToPush', + 'Check out a branch before pushing commits' + ), + linkedReviewTargetUnavailable: t( + 'sourceControl.commitDropdown.linkedReviewTargetUnavailable', + 'Linked review branch target is unavailable' + ), + publishBranchFirstToPush: t( + 'sourceControl.commitDropdown.publishBranchFirstToPush', + 'Publish the branch first to push commits' + ), + commitAndForcePush: t( + 'sourceControl.commitDropdown.commitAndForcePush', + 'Commit staged changes and force push with lease' + ), + commitAndTryPush: t( + 'sourceControl.commitDropdown.commitAndTryPush', + 'Commit staged changes and try to push' + ), + commitAndPush: t( + 'sourceControl.commitDropdown.commitAndPush', + 'Commit staged changes and push' + ), + checkOutBranchToSync: t( + 'sourceControl.commitDropdown.checkOutBranchToSync', + 'Check out a branch before syncing commits' + ), + publishBranchFirstToSync: t( + 'sourceControl.commitDropdown.publishBranchFirstToSync', + 'Publish the branch first to sync commits' + ), + useCommitAndForcePush: t( + 'sourceControl.commitDropdown.useCommitAndForcePush', + 'Use Commit & Force Push — remote only has older copies of local commits' + ), + commitThenPullPush: t( + 'sourceControl.commitDropdown.commitThenPullPush', + 'Commit, then pull and push' + ), + commitDisabledReason: (reason) => { + switch (reason) { + case 'Resolve conflicts before committing': + return t( + 'sourceControl.commitDropdown.resolveConflicts', + 'Resolve conflicts before committing' + ) + case 'Stage at least one file to commit': + return t('sourceControl.commitDropdown.stageFile', 'Stage at least one file to commit') + case 'Enter a commit message to commit': + return t('sourceControl.commitDropdown.enterMessage', 'Enter a commit message to commit') + } + } + } +} diff --git a/mobile/src/source-control/mobile-create-pr-action.ts b/mobile/src/source-control/mobile-create-pr-action.ts index dc38fdb299f..63b6eb8e592 100644 --- a/mobile/src/source-control/mobile-create-pr-action.ts +++ b/mobile/src/source-control/mobile-create-pr-action.ts @@ -1,3 +1,4 @@ +import { translate } from '../i18n/mobile-i18n' import type { MobileHostedReviewEligibilityReply } from './hosted-review-reply-schema' import { supportsHostedReviewCreation } from '../../../src/shared/hosted-review-creation-providers' import { resolveCreateReviewIntentEligibility } from '../../../src/shared/source-control-create-review-intent' @@ -91,7 +92,9 @@ export function buildMobileCreatePrAction({ } } const copy = hostedReviewCopy(eligibility.provider) - const label = `Create ${copy.titleLabel}` + const label = translate('sourceControl.createPrAction.createReview', 'Create {{titleLabel}}', { + titleLabel: copy.titleLabel + }) // Busy workflows no-op; disable the action rather than expose an inert button. const busy = busyAction !== null const loading = busyAction !== null && BUSY_ACTIONS.has(busyAction) @@ -127,7 +130,12 @@ export function buildMobileCreatePrAction({ blockedReason: eligibility.blockedReason, nextAction: eligibility.nextAction, reviewLookupOutcome: eligibility.reviewLookupOutcome - }) ?? `This branch is not ready for a ${copy.reviewLabel} yet.` + }) ?? + translate( + 'sourceControl.prCreate.notReady', + 'This branch is not ready for a {{reviewLabel}} yet.', + { reviewLabel: copy.reviewLabel } + ) return { visible: true, diff --git a/mobile/src/source-control/mobile-hosted-review-service.ts b/mobile/src/source-control/mobile-hosted-review-service.ts index 536f01cba0d..01dd8349b9e 100644 --- a/mobile/src/source-control/mobile-hosted-review-service.ts +++ b/mobile/src/source-control/mobile-hosted-review-service.ts @@ -1,3 +1,4 @@ +import { translate } from '../i18n/mobile-i18n' import type { MobileHostedReviewEligibilityReply } from './hosted-review-reply-schema' import { getRepoIdFromWorktreeId } from '../../../src/shared/worktree/id' import type { RpcSendParams } from '../transport/rpc-params-contract' @@ -179,7 +180,7 @@ async function pushMobileBranchBeforeCreate( function formatMobileHostedReviewCreateError( result: MobileHostedReviewCreateReply, pushed: boolean, - shortLabel: string + provider: MobileHostedReviewCreateInput['provider'] ): string { if (result.ok) { return '' @@ -187,8 +188,15 @@ function formatMobileHostedReviewCreateError( if (!pushed) { return result.error } - const prefix = new RegExp(`^Create ${shortLabel} failed:\\s*`, 'i') - return `Push succeeded, but ${shortLabel} creation failed: ${result.error.replace(prefix, '')}` + const prefix = provider === 'gitlab' ? /^Create MR failed:\s*/i : /^Create PR failed:\s*/i + return translate( + 'sourceControl.hostedReviewCreate.pushSucceeded', + 'Push succeeded, but {{shortLabel}} creation failed: {{error}}', + { + shortLabel: hostedReviewCopy(provider).shortLabel, + error: result.error.replace(prefix, '') + } + ) } async function finishMobileHostedReviewCreateSuccess( @@ -262,11 +270,8 @@ export async function createMobileHostedReview( return { ok: false, error: - formatMobileHostedReviewCreateError( - result, - pushed, - hostedReviewCopy(input.provider).shortLabel - ) || 'Failed to create pull request' + formatMobileHostedReviewCreateError(result, pushed, input.provider) || + 'Failed to create pull request' } } catch (err) { // Inline creation must display transport errors instead of leaking rejected promises. diff --git a/mobile/src/source-control/mobile-pr-create.ts b/mobile/src/source-control/mobile-pr-create.ts index 4cd15e52888..4172529bac5 100644 --- a/mobile/src/source-control/mobile-pr-create.ts +++ b/mobile/src/source-control/mobile-pr-create.ts @@ -1,3 +1,4 @@ +import { translate } from '../i18n/mobile-i18n' import { isHostedReviewCreateEligibilityConfirmable } from '../../../src/shared/hosted-review-create-confirmation' import { hostedReviewCopy } from './hosted-review-copy' import { @@ -32,11 +33,21 @@ export function getMobilePrCreateSuccessWarning( const copy = hostedReviewCopy(provider) if (outcome.existing) { return outcome.number - ? `${copy.titleLabel} #${outcome.number} is already open.` - : `${copy.titleLabel} is already open.` + ? translate( + 'sourceControl.prCreate.existingNumber', + '{{titleLabel}} #{{number}} is already open.', + { titleLabel: copy.titleLabel, number: outcome.number } + ) + : translate('sourceControl.prCreate.existing', '{{titleLabel}} is already open.', { + titleLabel: copy.titleLabel + }) } if (outcome.linkError) { - return `${copy.titleLabel} created, but Orca could not refresh it yet.` + return translate( + 'sourceControl.prCreate.createdNotRefreshed', + '{{titleLabel}} created, but Orca could not refresh it yet.', + { titleLabel: copy.titleLabel } + ) } return undefined } @@ -59,35 +70,91 @@ export function getMobilePrCreateBlockMessage(prefill: MobilePrPrefill): string prefill.reviewLookupOutcome === 'unavailable' || (!reviewLookupKnown && (prefill.canCreate === true || prefill.blockedReason === 'needs_push')) ) { - return `Orca could not confirm whether this branch already has a ${copy.reviewLabel}. Try again in a moment.` + return translate( + 'sourceControl.prCreate.lookupUnconfirmed', + 'Orca could not confirm whether this branch already has a {{reviewLabel}}. Try again in a moment.', + { reviewLabel: copy.reviewLabel } + ) } switch (prefill.blockedReason) { case 'dirty': - return `Commit changes before creating a ${copy.reviewLabel}.` + return translate( + 'sourceControl.prCreate.dirty', + 'Commit changes before creating a {{reviewLabel}}.', + { reviewLabel: copy.reviewLabel } + ) case 'detached_head': - return `Check out a branch before creating a ${copy.reviewLabel}.` + return translate( + 'sourceControl.prCreate.detachedHead', + 'Check out a branch before creating a {{reviewLabel}}.', + { reviewLabel: copy.reviewLabel } + ) case 'default_branch': - return `Switch to a feature branch before creating a ${copy.reviewLabel}.` + return translate( + 'sourceControl.prCreate.defaultBranch', + 'Switch to a feature branch before creating a {{reviewLabel}}.', + { reviewLabel: copy.reviewLabel } + ) case 'no_upstream': - return `Publish commits before creating a ${copy.reviewLabel}.` + return translate( + 'sourceControl.prCreate.noUpstream', + 'Publish commits before creating a {{reviewLabel}}.', + { reviewLabel: copy.reviewLabel } + ) case 'needs_sync': - return `Sync this branch before creating a ${copy.reviewLabel}.` + return translate( + 'sourceControl.prCreate.needsSync', + 'Sync this branch before creating a {{reviewLabel}}.', + { reviewLabel: copy.reviewLabel } + ) case 'auth_required': - return `Authenticate before creating a ${copy.reviewLabel}.` + return translate( + 'sourceControl.prCreate.authRequired', + 'Authenticate before creating a {{reviewLabel}}.', + { reviewLabel: copy.reviewLabel } + ) case 'unsupported_provider': - return `Creating ${copy.reviewLabel}s is not supported for this repo.` + return prefill.provider === 'gitlab' + ? translate( + 'sourceControl.prCreate.unsupportedMergeRequests', + 'Creating merge requests is not supported for this repo.' + ) + : translate( + 'sourceControl.prCreate.unsupportedPullRequests', + 'Creating pull requests is not supported for this repo.' + ) case 'existing_review': - return `A ${copy.reviewLabel} already exists for this branch.` + return translate( + 'sourceControl.prCreate.existingReview', + 'A {{reviewLabel}} already exists for this branch.', + { reviewLabel: copy.reviewLabel } + ) case 'fork_head_unsupported': - return `Creating a ${copy.reviewLabel} from this fork is not supported.` + return translate( + 'sourceControl.prCreate.forkHeadUnsupported', + 'Creating a {{reviewLabel}} from this fork is not supported.', + { reviewLabel: copy.reviewLabel } + ) case 'base_not_on_remote': - return `Push the base branch before creating a ${copy.reviewLabel}.` + return translate( + 'sourceControl.prCreate.baseNotOnRemote', + 'Push the base branch before creating a {{reviewLabel}}.', + { reviewLabel: copy.reviewLabel } + ) case 'needs_push': case null: case undefined: - return `This branch is not ready for a ${copy.reviewLabel} yet.` + return translate( + 'sourceControl.prCreate.notReady', + 'This branch is not ready for a {{reviewLabel}} yet.', + { reviewLabel: copy.reviewLabel } + ) default: // Accept future desktop blocked reasons while remaining safely blocked. - return `This branch is not ready for a ${copy.reviewLabel} yet.` + return translate( + 'sourceControl.prCreate.notReady', + 'This branch is not ready for a {{reviewLabel}} yet.', + { reviewLabel: copy.reviewLabel } + ) } } diff --git a/mobile/src/source-control/mobile-remote-dropdown-copy.ts b/mobile/src/source-control/mobile-remote-dropdown-copy.ts new file mode 100644 index 00000000000..058ca2cdbde --- /dev/null +++ b/mobile/src/source-control/mobile-remote-dropdown-copy.ts @@ -0,0 +1,165 @@ +import type { RemoteDropdownCopy } from '../../../src/shared/source-control-dropdown-remote-copy' +import type { Translate } from '../i18n/mobile-i18n' +import { + localizedPushCount, + localizedPullCount, + localizedFastForwardCount, + localizedSyncCounts, + localizedForcePushWithLease, + localizedManualForcePush, + localizedUnpublishedForcePush +} from './mobile-source-control-count-copy' + +export function mobileRemoteDropdownCopy(t: Translate): RemoteDropdownCopy { + return { + push: t('sourceControl.remoteDropdown.push', 'Push'), + forcePush: t('sourceControl.remoteDropdown.forcePush', 'Force Push'), + pull: t('sourceControl.remoteDropdown.pull', 'Pull'), + fastForward: t('sourceControl.remoteDropdown.fastForward', 'Fast-forward'), + sync: t('sourceControl.remoteDropdown.sync', 'Sync'), + publishBranch: t('sourceControl.remoteDropdown.publishBranch', 'Publish Branch'), + publishPrStatus: t('sourceControl.remoteDropdown.publishPrStatus', 'PR Status'), + publishLinkedReview: t('sourceControl.remoteDropdown.publishLinkedReview', 'Linked Review'), + publishNoBranch: t('sourceControl.remoteDropdown.publishNoBranch', 'No Branch'), + checkingBranchStatus: t( + 'sourceControl.remoteDropdown.checkingBranchStatus', + 'Checking branch status…' + ), + checkingPrStatus: t('sourceControl.remoteDropdown.checkingPrStatus', 'Checking PR status…'), + prAlreadyMerged: t('sourceControl.remoteDropdown.prAlreadyMerged', 'PR is already merged'), + linkedReviewTargetUnavailable: t( + 'sourceControl.remoteDropdown.linkedReviewTargetUnavailable', + 'Linked review branch target is unavailable' + ), + checkOutBranchToPush: t( + 'sourceControl.remoteDropdown.checkOutBranchToPush', + 'Check out a branch before pushing commits' + ), + pushAndSetUpstream: t( + 'sourceControl.remoteDropdown.pushAndSetUpstream', + 'Push this branch and set an upstream if needed' + ), + pushToLinkedReview: t( + 'sourceControl.remoteDropdown.pushToLinkedReview', + 'Push updates to the linked review branch' + ), + tryRegularPush: t( + 'sourceControl.remoteDropdown.tryRegularPush', + 'Try a regular push; git may require force push' + ), + pushMayNeedSync: t( + 'sourceControl.remoteDropdown.pushMayNeedSync', + 'Push local commits; git may require syncing first' + ), + nothingToPush: t('sourceControl.remoteDropdown.nothingToPush', 'Nothing to push'), + checkOutBranchToForcePush: t( + 'sourceControl.remoteDropdown.checkOutBranchToForcePush', + 'Check out a branch before force pushing commits' + ), + nothingToForcePush: t( + 'sourceControl.remoteDropdown.nothingToForcePush', + 'Nothing to force push' + ), + checkOutBranchToPull: t( + 'sourceControl.remoteDropdown.checkOutBranchToPull', + 'Check out a branch before pulling commits' + ), + publishBranchFirstToPull: t( + 'sourceControl.remoteDropdown.publishBranchFirstToPull', + 'Publish the branch first to pull commits' + ), + nothingNewToPull: t( + 'sourceControl.remoteDropdown.nothingNewToPull', + 'Nothing new to pull — remote only has older copies of local commits' + ), + nothingToPull: t('sourceControl.remoteDropdown.nothingToPull', 'Nothing to pull'), + checkOutBranchToFastForward: t( + 'sourceControl.remoteDropdown.checkOutBranchToFastForward', + 'Check out a branch before fast-forwarding' + ), + publishBranchFirstToFastForward: t( + 'sourceControl.remoteDropdown.publishBranchFirstToFastForward', + 'Publish the branch first to fast-forward' + ), + nothingNewToFastForward: t( + 'sourceControl.remoteDropdown.nothingNewToFastForward', + 'Nothing new to fast-forward — remote only has older copies of local commits' + ), + nothingToFastForward: t( + 'sourceControl.remoteDropdown.nothingToFastForward', + 'Nothing to fast-forward' + ), + tryFastForwardPull: t( + 'sourceControl.remoteDropdown.tryFastForwardPull', + 'Try a fast-forward pull; git may reject local commits' + ), + checkOutBranchToSync: t( + 'sourceControl.remoteDropdown.checkOutBranchToSync', + 'Check out a branch before syncing commits' + ), + publishBranchFirstToSync: t( + 'sourceControl.remoteDropdown.publishBranchFirstToSync', + 'Publish the branch first to sync commits' + ), + useForcePushToSync: t( + 'sourceControl.remoteDropdown.useForcePushToSync', + 'Use Force Push — remote only has older copies of local commits' + ), + branchUpToDate: t('sourceControl.remoteDropdown.branchUpToDate', 'Branch is up to date'), + rebaseFromBase: t('sourceControl.remoteDropdown.rebaseFromBase', 'Rebase from Base'), + rebaseChooseRemoteBase: t( + 'sourceControl.remoteDropdown.rebaseChooseRemoteBase', + 'Choose a remote base branch to rebase from' + ), + rebaseDirtyHint: t( + 'sourceControl.remoteDropdown.rebaseDirtyHint', + 'Try rebasing; git may require committing or stashing local changes first' + ), + fetch: t('sourceControl.remoteDropdown.fetch', 'Fetch'), + fetchTitle: t('sourceControl.remoteDropdown.fetchTitle', 'Fetch from remote without merging'), + linkedReviewBranchExists: t( + 'sourceControl.remoteDropdown.linkedReviewBranchExists', + 'Linked review branch already exists' + ), + checkOutBranchToPublish: t( + 'sourceControl.remoteDropdown.checkOutBranchToPublish', + 'Check out a branch before publishing commits' + ), + branchAlreadyPublished: t( + 'sourceControl.remoteDropdown.branchAlreadyPublished', + 'Branch is already published' + ), + publishToOrigin: t( + 'sourceControl.remoteDropdown.publishToOrigin', + 'Publish this branch to origin' + ), + nothingToPushTo: (upstreamName) => + t('sourceControl.remoteDropdown.nothingToPushTo', 'Nothing to push to {{upstreamName}}', { + upstreamName + }), + nothingToForcePushTo: (upstreamName) => + t( + 'sourceControl.remoteDropdown.nothingToForcePushTo', + 'Nothing to force push to {{upstreamName}}', + { upstreamName } + ), + rebaseFromNamedBase: (baseRef) => + t('sourceControl.remoteDropdown.rebaseFromNamedBase', 'Rebase from {{baseRef}}', { baseRef }), + rebaseWithLatestFrom: (baseRef) => + t( + 'sourceControl.remoteDropdown.rebaseWithLatestFrom', + 'Rebase current branch with latest commits from {{baseRef}}', + { baseRef } + ), + pushCount: (count) => localizedPushCount(count, t), + pullCount: (count) => localizedPullCount(count, t), + fastForwardCount: (count) => localizedFastForwardCount(count, t), + syncCounts: (ahead, behind) => localizedSyncCounts(ahead, behind, t), + forcePushWithLease: (branchCommitsAhead, upstreamName) => + localizedForcePushWithLease(branchCommitsAhead, upstreamName, t), + manualForcePush: (ahead, behind, upstreamName) => + localizedManualForcePush(ahead, behind, upstreamName, t), + unpublishedForcePush: (branchCommitsAhead) => + localizedUnpublishedForcePush(branchCommitsAhead, t) + } +} diff --git a/mobile/src/source-control/mobile-source-control-actions.ts b/mobile/src/source-control/mobile-source-control-actions.ts index 64503d469be..59442159528 100644 --- a/mobile/src/source-control/mobile-source-control-actions.ts +++ b/mobile/src/source-control/mobile-source-control-actions.ts @@ -1,3 +1,6 @@ +import { translate, type Translate } from '../i18n/mobile-i18n' +import { mobileCommitDropdownCopy } from './mobile-commit-dropdown-copy' +import { mobileRemoteDropdownCopy } from './mobile-remote-dropdown-copy' import type { GitConflictOperation } from '../../../src/shared/git-status-types' import { deriveDropdownActionContext } from '../../../src/shared/source-control-dropdown-action-context' import { buildCommitDropdownItems } from '../../../src/shared/source-control-dropdown-commit-items' @@ -19,6 +22,7 @@ export type MobileSourceControlActionIcon = | 'history' export type MobileSourceControlAction = { + id: string label: string iconKey: MobileSourceControlActionIcon disabled?: boolean @@ -59,7 +63,8 @@ export type MobileSourceControlActionArgs = { } export function buildMobileSourceControlActions( - args: MobileSourceControlActionArgs + args: MobileSourceControlActionArgs, + t: Translate = translate ): MobileSourceControlAction[] { const { handlers } = args const ctx = deriveDropdownActionContext({ @@ -67,9 +72,12 @@ export function buildMobileSourceControlActions( conflictOperation: args.conflictOperation, rebaseBaseRef: args.rebaseBaseRef }) - const { commit, commitPush, commitSync } = buildCommitDropdownItems(ctx) + const { commit, commitPush, commitSync } = buildCommitDropdownItems( + ctx, + mobileCommitDropdownCopy(t) + ) const { push, forcePush, pull, fastForward, sync, rebase, fetch, publish } = - buildRemoteDropdownItems(ctx) + buildRemoteDropdownItems(ctx, mobileRemoteDropdownCopy(t)) const busy = args.busyAction !== null || args.openingPath !== null || args.openingBranchPath !== null const blockForce = (item: DropdownItem): DropdownItem => @@ -82,6 +90,7 @@ export function buildMobileSourceControlActions( actionId: string, onPress: () => void ): MobileSourceControlAction => ({ + id: actionId, label: item.label, iconKey, disabled: busy || item.disabled, @@ -104,7 +113,8 @@ export function buildMobileSourceControlActions( row(push, 'push', 'push', handlers.push), row(blockForce(forcePush), 'push', 'force-push', handlers.forcePush), { - label: 'Create PR', + id: 'create-pr', + label: t('sourceControl.actions.createPr', 'Create PR'), iconKey: 'pr', disabled: busy || !args.prAvailable, hint: args.prHint, @@ -113,7 +123,8 @@ export function buildMobileSourceControlActions( onPress: handlers.createPr }, { - label: 'Push & Create PR', + id: 'push-create-pr', + label: t('sourceControl.actions.pushAndCreatePr', 'Push & Create PR'), iconKey: 'pr', disabled: busy || push.disabled || !args.prAvailable, hint: args.prHint ?? (push.disabled ? push.title : undefined), @@ -128,14 +139,16 @@ export function buildMobileSourceControlActions( row(fetch, 'fetch', 'fetch', handlers.fetch), row(publish, 'publish', 'publish', handlers.publish), { - label: 'Switch branch', + id: 'checkout', + label: t('sourceControl.actions.switchBranch', 'Switch branch'), iconKey: 'branch', disabled: busy, skipAutoClose: true, onPress: handlers.checkout }, { - label: 'Commits', + id: 'history', + label: t('sourceControl.actions.commits', 'Commits'), iconKey: 'history', disabled: busy, onPress: handlers.history diff --git a/mobile/src/source-control/mobile-source-control-count-copy.ts b/mobile/src/source-control/mobile-source-control-count-copy.ts new file mode 100644 index 00000000000..22ebdc77bce --- /dev/null +++ b/mobile/src/source-control/mobile-source-control-count-copy.ts @@ -0,0 +1,123 @@ +import { translate, type Translate } from '../i18n/mobile-i18n' + +export function localizedPushCount(count: number, t: Translate = translate): string { + return count === 1 + ? t('sourceControl.dropdownCounts.pushCount_one', 'Push {{count}} commit', { count }) + : t('sourceControl.dropdownCounts.pushCount_other', 'Push {{count}} commits', { count }) +} + +export function localizedPullCount(count: number, t: Translate = translate): string { + return count === 1 + ? t('sourceControl.dropdownCounts.pullCount_one', 'Pull {{count}} commit', { count }) + : t('sourceControl.dropdownCounts.pullCount_other', 'Pull {{count}} commits', { count }) +} + +export function localizedFastForwardCount(count: number, t: Translate = translate): string { + return count === 1 + ? t('sourceControl.dropdownCounts.fastForwardCount_one', 'Fast-forward {{count}} commit', { + count + }) + : t('sourceControl.dropdownCounts.fastForwardCount_other', 'Fast-forward {{count}} commits', { + count + }) +} + +export function localizedSyncCounts( + ahead: number, + behind: number, + t: Translate = translate +): string { + return t('sourceControl.dropdownCounts.syncCounts', 'Pull {{behind}}, push {{ahead}}', { + ahead, + behind + }) +} + +function localizedUpstreamName(upstreamName: string | undefined, t: Translate): string { + return upstreamName ?? t('sourceControl.dropdownCounts.remoteBranchFallback', 'the remote branch') +} + +export function localizedForcePushWithLease( + branchCommitsAhead: number | undefined, + upstreamName: string | undefined, + t: Translate = translate +): string { + const target = localizedUpstreamName(upstreamName, t) + if (!branchCommitsAhead || branchCommitsAhead <= 0) { + return t( + 'sourceControl.dropdownCounts.titleForcePushOlderCopiesBranch', + 'Remote only has older copies of local commits. Force push this branch with lease to update {{upstreamName}}.', + { upstreamName: target } + ) + } + const count = branchCommitsAhead + return count === 1 + ? t( + 'sourceControl.dropdownCounts.titleForcePushOlderCopiesCount_one', + 'Remote only has older copies of local commits. Force push {{count}} branch commit with lease to update {{upstreamName}}.', + { count, upstreamName: target } + ) + : t( + 'sourceControl.dropdownCounts.titleForcePushOlderCopiesCount_other', + 'Remote only has older copies of local commits. Force push {{count}} branch commits with lease to update {{upstreamName}}.', + { count, upstreamName: target } + ) +} + +export function localizedManualForcePush( + ahead: number, + behind: number, + upstreamName: string | undefined, + t: Translate = translate +): string { + const count = ahead + const target = localizedUpstreamName(upstreamName, t) + if (behind > 0) { + return count === 1 + ? t( + 'sourceControl.dropdownCounts.titleManualForcePushReplaceRemote_one', + 'Force push {{count}} local commit with lease to update {{upstreamName}} and replace remote-only commits.', + { count, upstreamName: target } + ) + : t( + 'sourceControl.dropdownCounts.titleManualForcePushReplaceRemote_other', + 'Force push {{count}} local commits with lease to update {{upstreamName}} and replace remote-only commits.', + { count, upstreamName: target } + ) + } + return count === 1 + ? t( + 'sourceControl.dropdownCounts.titleManualForcePush_one', + 'Force push {{count}} local commit with lease to update {{upstreamName}}.', + { count, upstreamName: target } + ) + : t( + 'sourceControl.dropdownCounts.titleManualForcePush_other', + 'Force push {{count}} local commits with lease to update {{upstreamName}}.', + { count, upstreamName: target } + ) +} + +export function localizedUnpublishedForcePush( + branchCommitsAhead: number | undefined, + t: Translate = translate +): string { + if (!branchCommitsAhead || branchCommitsAhead <= 0) { + return t( + 'sourceControl.dropdownCounts.titleUnpublishedForcePushBranch', + 'Force push this branch with lease and set an upstream if needed.' + ) + } + const count = branchCommitsAhead + return count === 1 + ? t( + 'sourceControl.dropdownCounts.titleUnpublishedForcePushCount_one', + 'Force push {{count}} branch commit with lease and set an upstream if needed.', + { count } + ) + : t( + 'sourceControl.dropdownCounts.titleUnpublishedForcePushCount_other', + 'Force push {{count}} branch commits with lease and set an upstream if needed.', + { count } + ) +} diff --git a/mobile/src/source-control/mobile-source-control-primary-action.ts b/mobile/src/source-control/mobile-source-control-primary-action.ts index 33aee3d7743..c23dd9124d1 100644 --- a/mobile/src/source-control/mobile-source-control-primary-action.ts +++ b/mobile/src/source-control/mobile-source-control-primary-action.ts @@ -1,3 +1,4 @@ +import { getMobileForcePushBlockedReason } from './mobile-source-control-review-copy' import type { SourceControlPrimaryActionDecisionInputs } from '../../../src/shared/source-control-primary-action-decision-types' import { resolveSourceControlCommitAreaPrimaryActionDecision, @@ -14,6 +15,8 @@ import type { PrSidebarState } from '../session/mobile-pr-sidebar-state' import type { MobileGitBranchCompareResult } from './mobile-branch-compare' import type { MobileGitStatusResult } from './mobile-git-status' +export { getMobileForcePushBlockedReason } from './mobile-source-control-review-copy' + type GitStep = { method: string; params?: Record } type MobileSourceControlPrimaryActionKind = SourceControlCommitAreaPrimaryActionDecision['kind'] @@ -67,13 +70,6 @@ export function resolveMobileSourceControlReviewInputs( return state.kind === 'loading' ? { ...NO_REVIEW, isPRStateLoading: true } : NO_REVIEW } -// Mobile pushes without a review's saved push target, so a force push there could overwrite the wrong branch. -export function getMobileForcePushBlockedReason( - review: MobileSourceControlReviewInputs | undefined -): string | undefined { - return review?.pushTarget ? "Force push this pull request's branch from desktop" : undefined -} - export type MobileSourceControlPrimaryActionArgs = { status: MobileGitStatusResult | null hasUnresolvedConflicts: boolean diff --git a/mobile/src/source-control/mobile-source-control-review-copy.ts b/mobile/src/source-control/mobile-source-control-review-copy.ts new file mode 100644 index 00000000000..a4b3b7b754b --- /dev/null +++ b/mobile/src/source-control/mobile-source-control-review-copy.ts @@ -0,0 +1,14 @@ +import { translate } from '../i18n/mobile-i18n' +import type { MobileSourceControlReviewInputs } from './mobile-source-control-primary-action' + +// Mobile pushes without a review's saved push target, so a force push there could overwrite the wrong branch. +export function getMobileForcePushBlockedReason( + review: MobileSourceControlReviewInputs | undefined +): string | undefined { + return review?.pushTarget + ? translate( + 'sourceControl.primaryAction.forcePushFromDesktop', + "Force push this pull request's branch from desktop" + ) + : undefined +} diff --git a/mobile/src/source-control/use-mobile-source-control-action-sheet.ts b/mobile/src/source-control/use-mobile-source-control-action-sheet.ts index a1507ffac4b..b894309e1eb 100644 --- a/mobile/src/source-control/use-mobile-source-control-action-sheet.ts +++ b/mobile/src/source-control/use-mobile-source-control-action-sheet.ts @@ -1,5 +1,6 @@ import { useMemo } from 'react' -import { ENGLISH_COMMIT_DROPDOWN_COPY } from '../../../src/shared/source-control-dropdown-commit-items' +import { mobileCommitDropdownCopy } from './mobile-commit-dropdown-copy' +import { useMobileTranslation } from '../i18n/use-mobile-translation' import { buildMobileSourceControlActions } from './mobile-source-control-actions' import type { ActionSheetAction } from '../components/ActionSheetModal' import { SOURCE_CONTROL_ACTION_ICONS } from './mobile-source-control-screen-state' @@ -14,6 +15,7 @@ export function useMobileSourceControlActionSheet( state: MobileSourceControlState, review: MobileSourceControlReviewInputs ): ActionSheetAction[] { + const t = useMobileTranslation() const { primaryActionArgs, status, @@ -35,48 +37,51 @@ export function useMobileSourceControlActionSheet( return useMemo( () => - buildMobileSourceControlActions({ - decision: buildMobileSourceControlDecisionInputs({ ...primaryActionArgs, review }), - conflictOperation: status?.conflictOperation, - // Desktop rebases onto the effective base, not an upstream compare base. - rebaseBaseRef, - busyAction, - openingPath, - openingBranchPath, - // Create intent may publish or push before review creation. - prAvailable: upstreamKnown, - prHint: upstreamKnown ? undefined : ENGLISH_COMMIT_DROPDOWN_COPY.checkingBranchStatus, - forcePushBlockedReason: getMobileForcePushBlockedReason(review), - handlers: { - commit: () => void runActionSheetCommit(), - commitPush: (forceWithLease) => - void runActionSheetCommitSequence('commit-push', [ - forceWithLease - ? { method: 'git.push', params: { forceWithLease: true } } - : { method: 'git.push' } - ]), - commitSync: () => void runActionSheetCommitSync(), - push: () => void runActionSheetGitSequence('push', [{ method: 'git.push' }]), - forcePush: () => - void runActionSheetGitSequence('force-push', [ - { method: 'git.push', params: { forceWithLease: true } } - ]), - pull: () => void runActionSheetGitSequence('pull', [{ method: 'git.pull' }]), - sync: () => void runActionSheetGitSync(), - fetch: () => void runActionSheetGitSequence('fetch', [{ method: 'git.fetch' }]), - publish: () => - void runActionSheetGitSequence('publish', [ - { method: 'git.push', params: { publish: true } } - ]), - fastForward: () => - void runActionSheetGitSequence('fast-forward', [{ method: 'git.fastForward' }]), - rebase: () => void runActionSheetRebase(rebaseBaseRef), - createPr: () => void createPr(false), - pushAndCreatePr: () => void createPr(true), - checkout: () => void openBranchPicker(), - history: () => void openHistory() - } - }).map((action) => ({ ...action, icon: SOURCE_CONTROL_ACTION_ICONS[action.iconKey] })), + buildMobileSourceControlActions( + { + decision: buildMobileSourceControlDecisionInputs({ ...primaryActionArgs, review }), + conflictOperation: status?.conflictOperation, + // Desktop rebases onto the effective base, not an upstream compare base. + rebaseBaseRef, + busyAction, + openingPath, + openingBranchPath, + // Create intent may publish or push before review creation. + prAvailable: upstreamKnown, + prHint: upstreamKnown ? undefined : mobileCommitDropdownCopy(t).checkingBranchStatus, + forcePushBlockedReason: getMobileForcePushBlockedReason(review), + handlers: { + commit: () => void runActionSheetCommit(), + commitPush: (forceWithLease) => + void runActionSheetCommitSequence('commit-push', [ + forceWithLease + ? { method: 'git.push', params: { forceWithLease: true } } + : { method: 'git.push' } + ]), + commitSync: () => void runActionSheetCommitSync(), + push: () => void runActionSheetGitSequence('push', [{ method: 'git.push' }]), + forcePush: () => + void runActionSheetGitSequence('force-push', [ + { method: 'git.push', params: { forceWithLease: true } } + ]), + pull: () => void runActionSheetGitSequence('pull', [{ method: 'git.pull' }]), + sync: () => void runActionSheetGitSync(), + fetch: () => void runActionSheetGitSequence('fetch', [{ method: 'git.fetch' }]), + publish: () => + void runActionSheetGitSequence('publish', [ + { method: 'git.push', params: { publish: true } } + ]), + fastForward: () => + void runActionSheetGitSequence('fast-forward', [{ method: 'git.fastForward' }]), + rebase: () => void runActionSheetRebase(rebaseBaseRef), + createPr: () => void createPr(false), + pushAndCreatePr: () => void createPr(true), + checkout: () => void openBranchPicker(), + history: () => void openHistory() + } + }, + t + ).map((action) => ({ ...action, icon: SOURCE_CONTROL_ACTION_ICONS[action.iconKey] })), [ busyAction, createPr, @@ -94,7 +99,8 @@ export function useMobileSourceControlActionSheet( runActionSheetGitSync, runActionSheetRebase, status?.conflictOperation, - upstreamKnown + upstreamKnown, + t ] ) } diff --git a/mobile/src/source-control/use-mobile-source-control-create-pr-action.ts b/mobile/src/source-control/use-mobile-source-control-create-pr-action.ts index bf01f96a099..3ed89e2a359 100644 --- a/mobile/src/source-control/use-mobile-source-control-create-pr-action.ts +++ b/mobile/src/source-control/use-mobile-source-control-create-pr-action.ts @@ -1,4 +1,5 @@ import { useMemo } from 'react' +import { useMobileTranslation } from '../i18n/use-mobile-translation' import { buildMobileCreatePrAction } from './mobile-create-pr-action' import { useMobileHostedReviewEligibility } from './use-mobile-hosted-review-eligibility' import { @@ -24,6 +25,7 @@ export function useMobileSourceControlCreatePrAction({ commitArea, createPr }: Params) { + const t = useMobileTranslation() const { status, busyAction } = commitArea const upstream = status?.upstreamStatus const eligibilityState = useMobileHostedReviewEligibility({ @@ -54,6 +56,6 @@ export function useMobileSourceControlCreatePrAction({ }, onCreatePr: createPr }), - [busyAction, commitArea, createPr, eligibilityState, status?.branch, upstream] + [busyAction, commitArea, createPr, eligibilityState, status?.branch, upstream, t] ) } diff --git a/mobile/src/terminal/TerminalWebView.tsx b/mobile/src/terminal/TerminalWebView.tsx index 0095cebcce4..b2cb21eaec8 100644 --- a/mobile/src/terminal/TerminalWebView.tsx +++ b/mobile/src/terminal/TerminalWebView.tsx @@ -1,6 +1,7 @@ import { useRef, useCallback, useState, forwardRef, useImperativeHandle } from 'react' import { Platform, View } from 'react-native' import { WebView, type WebViewMessageEvent } from 'react-native-webview' +import { useMobileTranslation } from '../i18n/use-mobile-translation' import type { TerminalWebViewHandle, TerminalWebViewProps } from './terminal-webview-contract' import { TerminalWebViewEngineErrorOverlay } from './terminal-webview-engine-error-state' import { TERMINAL_WEBVIEW_FRAME_STYLES } from './terminal-webview-frame-styles' @@ -14,6 +15,7 @@ export type { TerminalWebViewHandle } from './terminal-webview-contract' export const TerminalWebView = forwardRef( function TerminalWebView(props, ref) { + const t = useMobileTranslation() const webViewRef = useRef(null) const post = useCallback((command: TerminalWebViewCommand & { id: number }) => { @@ -80,10 +82,23 @@ export const TerminalWebView = forwardRef( textZoom={100} onLoadStart={resetReadiness} onMessage={handleMessage} - onError={(event) => reportNativeEngineError('Terminal WebView load failed', event)} - onHttpError={(event) => reportNativeEngineError('Terminal WebView HTTP error', event)} + onError={(event) => + reportNativeEngineError( + t('terminal.webView.loadFailed', 'Terminal WebView load failed'), + event + ) + } + onHttpError={(event) => + reportNativeEngineError( + t('terminal.webView.httpError', 'Terminal WebView HTTP error'), + event + ) + } onRenderProcessGone={(event) => - reportNativeEngineError('Terminal WebView render process ended', event) + reportNativeEngineError( + t('terminal.webView.renderProcessEnded', 'Terminal WebView render process ended'), + event + ) } onContentProcessDidTerminate={handleContentProcessDidTerminate} /> diff --git a/mobile/src/terminal/TerminalWebView.web.tsx b/mobile/src/terminal/TerminalWebView.web.tsx index ca9573344ac..17101a09ad6 100644 --- a/mobile/src/terminal/TerminalWebView.web.tsx +++ b/mobile/src/terminal/TerminalWebView.web.tsx @@ -1,5 +1,7 @@ import { forwardRef, useCallback, useEffect, useImperativeHandle, useRef, useState } from 'react' import { View } from 'react-native' +import { translate } from '../i18n/mobile-i18n' +import { useMobileTranslation } from '../i18n/use-mobile-translation' import type { TerminalWebViewHandle, TerminalWebViewProps } from './terminal-webview-contract' import { TerminalWebViewEngineErrorOverlay } from './terminal-webview-engine-error-state' import { TERMINAL_WEBVIEW_FRAME_STYLES } from './terminal-webview-frame-styles' @@ -9,11 +11,22 @@ import { useTerminalWebViewController } from './use-terminal-webview-controller' type Props = TerminalWebViewProps +function describeTerminalDocumentStartupError(error: unknown): string { + return translate( + 'terminal.webView.documentStartFailed', + 'terminal document failed to start - {{message}}', + { + message: error instanceof Error ? error.message : String(error) + } + ) +} + export type { TerminalWebViewHandle } from './terminal-webview-contract' // react-native-webview has no rendered web implementation; mount the same document factory with the native controller contract. export const TerminalWebView = forwardRef( function TerminalWebView(props, ref) { + useMobileTranslation() const hostRef = useRef(null) const documentRef = useRef(null) // Readiness fires inside mount before a handle exists; buffer its synchronous controller flush until the handle returns. @@ -62,9 +75,7 @@ export const TerminalWebView = forwardRef( receiveRef.current?.({ type: 'error', fatal: true, - message: `terminal document failed to start - ${ - error instanceof Error ? error.message : String(error) - }` + message: describeTerminalDocumentStartupError(error) }) return } diff --git a/mobile/src/terminal/terminal-key-definitions.ts b/mobile/src/terminal/terminal-key-definitions.ts index 041fba21bcb..dae77fd6641 100644 --- a/mobile/src/terminal/terminal-key-definitions.ts +++ b/mobile/src/terminal/terminal-key-definitions.ts @@ -1,3 +1,4 @@ +import { translate } from '../i18n/mobile-i18n' import type { TerminalAccessoryKey, TerminalShortcutSpecialKey } from './terminal-accessory-keys' export const SPECIAL_KEY_LABELS: Record = { @@ -35,12 +36,22 @@ const SPECIAL_KEY_ACCESSIBILITY_LABELS: Record = { tab: 'Tab', enter: 'Enter', backspace: 'Backspace', - delete: 'Forward delete', + get delete() { + return translate('terminal.keyDefinitions.forwardDelete', 'Forward delete') + }, insert: 'Insert', - arrowUp: 'Arrow up', - arrowDown: 'Arrow down', - arrowLeft: 'Arrow left', - arrowRight: 'Arrow right', + get arrowUp() { + return translate('terminal.keyDefinitions.arrowUp', 'Arrow up') + }, + get arrowDown() { + return translate('terminal.keyDefinitions.arrowDown', 'Arrow down') + }, + get arrowLeft() { + return translate('terminal.keyDefinitions.arrowLeft', 'Arrow left') + }, + get arrowRight() { + return translate('terminal.keyDefinitions.arrowRight', 'Arrow right') + }, home: 'Home', end: 'End', pageUp: 'Page up', @@ -91,7 +102,9 @@ export const TERMINAL_SHORTCUT_SPECIAL_KEY_DEFINITIONS: TerminalShortcutSpecialK ].map((id) => ({ id, label: SPECIAL_KEY_LABELS[id]!, - accessibilityLabel: SPECIAL_KEY_ACCESSIBILITY_LABELS[id]! + get accessibilityLabel() { + return SPECIAL_KEY_ACCESSIBILITY_LABELS[id]! + } })) export const TERMINAL_ACCESSORY_KEY_DEFINITIONS: TerminalAccessoryKey[] = [ @@ -105,38 +118,117 @@ export const TERMINAL_ACCESSORY_KEY_DEFINITIONS: TerminalAccessoryKey[] = [ id: 'delete', label: 'Del', bytes: '\x1b[3~', - accessibilityLabel: 'Forward delete', + get accessibilityLabel() { + return SPECIAL_KEY_ACCESSIBILITY_LABELS.delete! + }, + repeatable: true + }, + { + id: 'arrowUp', + label: '↑', + bytes: '\x1b[A', + get accessibilityLabel() { + return translate('terminal.keyDefinitions.accessoryArrowUp', 'Arrow Up') + }, repeatable: true }, - { id: 'arrowUp', label: '↑', bytes: '\x1b[A', accessibilityLabel: 'Arrow Up', repeatable: true }, { id: 'arrowDown', label: '↓', bytes: '\x1b[B', - accessibilityLabel: 'Arrow Down', + get accessibilityLabel() { + return translate('terminal.keyDefinitions.accessoryArrowDown', 'Arrow Down') + }, repeatable: true }, { id: 'arrowLeft', label: '←', bytes: '\x1b[D', - accessibilityLabel: 'Arrow Left', + get accessibilityLabel() { + return translate('terminal.keyDefinitions.accessoryArrowLeft', 'Arrow Left') + }, repeatable: true }, { id: 'arrowRight', label: '→', bytes: '\x1b[C', - accessibilityLabel: 'Arrow Right', + get accessibilityLabel() { + return translate('terminal.keyDefinitions.accessoryArrowRight', 'Arrow Right') + }, repeatable: true }, - { id: 'ctrlC', label: 'Ctrl+C', bytes: '\x03', accessibilityLabel: 'Interrupt terminal' }, - { id: 'ctrlD', label: 'Ctrl+D', bytes: '\x04', accessibilityLabel: 'Send EOF' }, - { id: 'ctrlL', label: 'Ctrl+L', bytes: '\x0c', accessibilityLabel: 'Clear screen' }, - { id: 'ctrlZ', label: 'Ctrl+Z', bytes: '\x1a', accessibilityLabel: 'Suspend process' }, - { id: 'ctrlR', label: 'Ctrl+R', bytes: '\x12', accessibilityLabel: 'Reverse search' }, - { id: 'ctrlA', label: 'Ctrl+A', bytes: '\x01', accessibilityLabel: 'Start of line' }, - { id: 'ctrlE', label: 'Ctrl+E', bytes: '\x05', accessibilityLabel: 'End of line' }, - { id: 'ctrlW', label: 'Ctrl+W', bytes: '\x17', accessibilityLabel: 'Delete word backward' }, - { id: 'ctrlU', label: 'Ctrl+U', bytes: '\x15', accessibilityLabel: 'Clear line before cursor' } + { + id: 'ctrlC', + label: 'Ctrl+C', + bytes: '\x03', + get accessibilityLabel() { + return translate('terminal.keyDefinitions.interruptTerminal', 'Interrupt terminal') + } + }, + { + id: 'ctrlD', + label: 'Ctrl+D', + bytes: '\x04', + get accessibilityLabel() { + return translate('terminal.keyDefinitions.sendEof', 'Send EOF') + } + }, + { + id: 'ctrlL', + label: 'Ctrl+L', + bytes: '\x0c', + get accessibilityLabel() { + return translate('terminal.keyDefinitions.clearScreen', 'Clear screen') + } + }, + { + id: 'ctrlZ', + label: 'Ctrl+Z', + bytes: '\x1a', + get accessibilityLabel() { + return translate('terminal.keyDefinitions.suspendProcess', 'Suspend process') + } + }, + { + id: 'ctrlR', + label: 'Ctrl+R', + bytes: '\x12', + get accessibilityLabel() { + return translate('terminal.keyDefinitions.reverseSearch', 'Reverse search') + } + }, + { + id: 'ctrlA', + label: 'Ctrl+A', + bytes: '\x01', + get accessibilityLabel() { + return translate('terminal.keyDefinitions.startOfLine', 'Start of line') + } + }, + { + id: 'ctrlE', + label: 'Ctrl+E', + bytes: '\x05', + get accessibilityLabel() { + return translate('terminal.keyDefinitions.endOfLine', 'End of line') + } + }, + { + id: 'ctrlW', + label: 'Ctrl+W', + bytes: '\x17', + get accessibilityLabel() { + return translate('terminal.keyDefinitions.deleteWordBackward', 'Delete word backward') + } + }, + { + id: 'ctrlU', + label: 'Ctrl+U', + bytes: '\x15', + get accessibilityLabel() { + return translate('terminal.keyDefinitions.clearLineBeforeCursor', 'Clear line before cursor') + } + } ] diff --git a/mobile/src/terminal/terminal-webview-engine-error-state.tsx b/mobile/src/terminal/terminal-webview-engine-error-state.tsx index 2f13929a80f..398dfe2a691 100644 --- a/mobile/src/terminal/terminal-webview-engine-error-state.tsx +++ b/mobile/src/terminal/terminal-webview-engine-error-state.tsx @@ -1,6 +1,8 @@ import { useCallback, useState } from 'react' import { RefreshCw } from 'lucide-react-native' import { Pressable, StyleSheet, Text, View } from 'react-native' +import { translate } from '../i18n/mobile-i18n' +import { useMobileTranslation } from '../i18n/use-mobile-translation' import { colors } from '../theme/mobile-theme' import { themedStyleSheet } from '../theme/themed-styles' import { useMobileColorScheme } from '../theme/use-mobile-color-scheme' @@ -59,16 +61,18 @@ export function describeNativeWebViewEngineError( parts.push(description) } if (typeof statusCode === 'number') { - parts.push(`status ${statusCode}`) + parts.push( + translate('terminal.webviewEngineErrorState.status', 'status {{statusCode}}', { statusCode }) + ) } if (typeof code === 'number') { - parts.push(`code ${code}`) + parts.push(translate('terminal.webviewEngineErrorState.code', 'code {{code}}', { code })) } if (typeof domain === 'string') { parts.push(domain) } if (native?.didCrash === true) { - parts.push('renderer crashed') + parts.push(translate('terminal.webviewEngineErrorState.rendererCrashed', 'renderer crashed')) } return parts.join(' - ') } @@ -78,15 +82,20 @@ export function TerminalWebViewEngineErrorOverlay({ onReload }: TerminalWebViewEngineErrorOverlayProps) { useMobileColorScheme() + const t = useMobileTranslation() return ( - Terminal failed to load + + {t('terminal.webviewEngineErrorState.title', 'Terminal failed to load')} + {message} - Reload + + {t('terminal.webviewEngineErrorState.reload', 'Reload')} + ) diff --git a/mobile/src/terminal/terminal-webview-notification-dispatch.ts b/mobile/src/terminal/terminal-webview-notification-dispatch.ts index 1ea628e498f..7d2af06c9bb 100644 --- a/mobile/src/terminal/terminal-webview-notification-dispatch.ts +++ b/mobile/src/terminal/terminal-webview-notification-dispatch.ts @@ -1,5 +1,6 @@ import { parseTerminalKittyKeyboardFlags } from '../../../src/shared/terminal-kitty-keyboard-flags' import type { ParsedFileLinkLocation } from '../../../src/shared/file-link-location' +import { translate } from '../i18n/mobile-i18n' import { parseTerminalKeyboardAvoidanceMetrics, type TerminalSelectionEvents @@ -20,7 +21,10 @@ export function dispatchTerminalWebViewNotification( const tag = typeof msg.tag === 'string' ? msg.tag : '[fit]' console.log(tag, msg.payload) } else if (msg.type === 'error') { - const message = typeof msg.message === 'string' ? msg.message : 'Unknown terminal error' + const message = + typeof msg.message === 'string' + ? msg.message + : translate('terminal.webviewNotificationDispatch.unknownError', 'Unknown terminal error') handlers.reportEngineError(message, msg.fatal !== false) } else if (msg.type === 'set-select-mode') { handlers.onSelectionMode?.(!!msg.enabled) diff --git a/mobile/src/terminal/terminal-webview-ready-watchdog.ts b/mobile/src/terminal/terminal-webview-ready-watchdog.ts index 6eb1b9363f8..3439aee04d9 100644 --- a/mobile/src/terminal/terminal-webview-ready-watchdog.ts +++ b/mobile/src/terminal/terminal-webview-ready-watchdog.ts @@ -1,5 +1,6 @@ import { useCallback, useEffect, useRef, type RefObject } from 'react' import { AppState } from 'react-native' +import { translate } from '../i18n/mobile-i18n' // Silent startup/bridge failures may emit no error; a native watchdog must turn the blank pane into a reported failure. const WEB_READY_WATCHDOG_MS = 15000 @@ -30,7 +31,10 @@ export function useTerminalWebReadyWatchdog( return } reportEngineError( - 'Terminal did not initialize - no ready signal from the terminal view', + translate( + 'terminal.webviewReadyWatchdog.initializeFailed', + 'Terminal did not initialize - no ready signal from the terminal view' + ), true ) } diff --git a/src/shared/native-chat/tools/native-chat-tool-run-sentence.ts b/src/shared/native-chat/tools/native-chat-tool-run-sentence.ts index f2348c9720b..ebd328b0236 100644 --- a/src/shared/native-chat/tools/native-chat-tool-run-sentence.ts +++ b/src/shared/native-chat/tools/native-chat-tool-run-sentence.ts @@ -73,6 +73,7 @@ export function nativeChatToolRunClauses( export function joinNativeChatToolRunClauses( clauses: readonly string[], format: { + continuation?: (clause: string) => string pair: (first: string, second: string) => string list: (leading: readonly string[], last: string) => string } @@ -80,7 +81,9 @@ export function joinNativeChatToolRunClauses( const continued = clauses.map((clause, index) => index === 0 || clause.length === 0 ? clause - : `${clause[0].toLocaleLowerCase()}${clause.slice(1)}` + : format.continuation + ? format.continuation(clause) + : `${clause[0].toLocaleLowerCase()}${clause.slice(1)}` ) if (continued.length === 0) { return '' @@ -95,6 +98,7 @@ export function joinNativeChatToolRunClauses( } export type NativeChatToolRunSentenceWords = { + continuation?: (clause: string) => string clause: (category: NativeChatToolCategory, count: number, live: boolean) => string pair: (first: string, second: string) => string list: (leading: readonly string[], last: string) => string