From 5ed3c8c07206c6efbfae5451c30fee57534a879a Mon Sep 17 00:00:00 2001 From: OrcaWin Date: Wed, 28 Oct 2026 18:17:58 -0700 Subject: [PATCH] feat(mobile): localize shared components, pull requests and diff review (#35532) Moves ~600 strings in the shared components (modals, drawers, host cards, dictation, accounts), the pull request sidebar and diff review to the translation API, with all ten translations. - Invalid account snapshots are detected by error type, not by matching English text - Credit expiry wording uses separate keys for one and several credits instead of plural rules, which would pick the wrong sentence in Japanese or Russian - The PR chip memo recomputes on a language change Co-authored-by: OrcaWin <293788423+OrcaWin@users.noreply.github.com> --- ...obile-localization-coverage-allowlist.json | 2842 ----------------- mobile/src/accounts/MobileAccountsScreen.tsx | 4 +- mobile/src/components/AgentLaunchNotice.tsx | 33 +- mobile/src/components/AgentSpinner.tsx | 12 +- mobile/src/components/AgentStateDot.tsx | 12 +- mobile/src/components/AuthFailedBanner.tsx | 7 +- .../components/AuthFailedBannerActions.tsx | 12 +- .../AuthFailedBannerActions.web.tsx | 13 +- .../src/components/CodexResetCreditAction.tsx | 27 +- mobile/src/components/ConfirmModal.tsx | 10 +- mobile/src/components/CustomKeyModal.tsx | 115 +- mobile/src/components/DragReorderRow.tsx | 13 +- mobile/src/components/HostDiagnosticsLink.tsx | 11 +- mobile/src/components/HostProtocolGate.tsx | 12 +- .../src/components/HostRouteNoticeBanner.tsx | 4 +- mobile/src/components/JiraConnectPrompt.tsx | 21 +- .../components/MobileDictationSetupSheet.tsx | 109 +- .../src/components/MobileDiffReviewBody.tsx | 65 +- .../components/MobileDiffReviewDrawers.tsx | 175 +- .../MobileDiffReviewFileSummary.tsx | 74 +- .../src/components/MobileDiffReviewFooter.tsx | 39 +- .../src/components/MobileDiffReviewHeader.tsx | 81 +- .../src/components/MobileDiffReviewLine.tsx | 36 +- .../src/components/MobileHomeQuickActions.tsx | 14 +- mobile/src/components/MobileHostCard.tsx | 47 +- mobile/src/components/MobileHtmlPreview.tsx | 16 +- .../src/components/MobileLoadErrorState.tsx | 4 +- mobile/src/components/MobilePRSidebar.tsx | 20 +- .../components/MobileRichMarkdownEditor.tsx | 13 +- .../components/MobileRichMarkdownToolbar.tsx | 97 +- mobile/src/components/MobileSearchField.tsx | 8 +- .../src/components/MobileSourceCopyButton.tsx | 29 +- .../components/MobileWorkspaceNameInput.tsx | 4 +- mobile/src/components/NewWorkspaceFab.tsx | 4 +- .../NewWorkspaceSetupScriptField.tsx | 22 +- .../NewWorkspaceSshConnectionField.tsx | 15 +- .../src/components/NewWorktreeFormSheet.tsx | 34 +- mobile/src/components/NewWorktreeModal.tsx | 11 +- .../components/NewWorktreeModalDrawers.tsx | 8 +- .../NewWorktreeProjectTargetFields.tsx | 26 +- mobile/src/components/ProtocolBlockScreen.tsx | 89 +- .../src/components/SetupHookTrustDrawer.tsx | 38 +- .../SmartWorkspaceAdvancedFields.tsx | 28 +- .../components/SmartWorkspaceSourceDrawer.tsx | 57 +- .../components/SmartWorkspaceSourceField.tsx | 17 +- .../components/SmartWorkspaceSourceRow.tsx | 37 +- .../components/TerminalShortcutSettings.tsx | 46 +- mobile/src/components/TextInputModal.tsx | 8 +- mobile/src/components/VoiceModelList.tsx | 59 +- .../components/WorkspaceDetailPlaceholder.tsx | 13 +- mobile/src/components/WorktreeListRow.tsx | 34 +- mobile/src/components/accounts-snapshot.ts | 15 +- mobile/src/components/codex-reset-credit.ts | 123 +- .../src/components/mobile-markdown-blocks.tsx | 10 +- .../src/components/mounted-bottom-drawer.tsx | 4 +- .../new-worktree-agent-selection.ts | 5 +- .../pr-sidebar/MobileLinkPrForm.tsx | 25 +- .../pr-sidebar/MobilePrViewPanel.tsx | 9 +- .../pr-sidebar/PRActionsSection.tsx | 100 +- .../components/pr-sidebar/PRCheckDetail.tsx | 25 +- .../components/pr-sidebar/PRChecksSection.tsx | 125 +- .../components/pr-sidebar/PRCommentCard.tsx | 100 +- .../pr-sidebar/PRCommentComposer.tsx | 6 +- .../pr-sidebar/PRCommentsSection.tsx | 99 +- .../pr-sidebar/PRConflictingFilesSection.tsx | 77 +- .../pr-sidebar/PRReviewersSection.tsx | 65 +- .../components/pr-sidebar/PRSidebarHeader.tsx | 36 +- .../pr-sidebar/PrSidebarCreateEmptyState.tsx | 69 +- .../pr-sidebar/ReviewerPickerDrawer.tsx | 31 +- .../components/pr-sidebar/markdown-blocks.ts | 12 +- .../components/pr-sidebar/pr-actions-state.ts | 259 +- .../pr-sidebar/pr-check-detail-content.ts | 21 +- .../pr-sidebar/pr-checks-presentation.ts | 62 +- .../pr-sidebar/pr-comment-audience-labels.ts | 28 +- .../use-codex-reset-credit-action.ts | 87 +- mobile/src/i18n/en-runtime-required.json | 72 + mobile/src/i18n/locales/de.json | 732 +++++ mobile/src/i18n/locales/en.json | 734 ++++- mobile/src/i18n/locales/es.json | 755 +++++ mobile/src/i18n/locales/fr.json | 755 +++++ mobile/src/i18n/locales/ja.json | 730 +++++ mobile/src/i18n/locales/ko.json | 730 +++++ mobile/src/i18n/locales/pt.json | 755 +++++ mobile/src/i18n/locales/ru.json | 778 +++++ mobile/src/i18n/locales/tr.json | 732 +++++ mobile/src/i18n/locales/zh-Hant.json | 730 +++++ mobile/src/i18n/locales/zh.json | 730 +++++ .../MobileSourceControlPanel.tsx | 4 +- 88 files changed, 10632 insertions(+), 3523 deletions(-) diff --git a/config/mobile-localization-coverage-allowlist.json b/config/mobile-localization-coverage-allowlist.json index f3b4a7c8bc5..8b3a409b7aa 100644 --- a/config/mobile-localization-coverage-allowlist.json +++ b/config/mobile-localization-coverage-allowlist.json @@ -202,97 +202,6 @@ "dynamic": false, "count": 1 }, - { - "filePath": "mobile/src/components/AgentLaunchNotice.tsx", - "kind": "jsx-attribute:accessibilityLabel", - "text": "Copy prompt", - "dynamic": false, - "count": 1 - }, - { - "filePath": "mobile/src/components/AgentLaunchNotice.tsx", - "kind": "object-property:label", - "text": "Copied", - "dynamic": false, - "count": 1 - }, - { - "filePath": "mobile/src/components/AgentLaunchNotice.tsx", - "kind": "object-property:label", - "text": "Couldn't copy", - "dynamic": false, - "count": 1 - }, - { - "filePath": "mobile/src/components/AgentSpinner.tsx", - "kind": "jsx-attribute:accessibilityLabel", - "text": "Couldn’t confirm", - "dynamic": false, - "count": 1 - }, - { - "filePath": "mobile/src/components/AgentSpinner.tsx", - "kind": "jsx-attribute:accessibilityLabel", - "text": "Monitoring background tasks", - "dynamic": false, - "count": 1 - }, - { - "filePath": "mobile/src/components/AgentStateDot.tsx", - "kind": "jsx-attribute:accessibilityLabel", - "text": "Monitoring background tasks", - "dynamic": false, - "count": 1 - }, - { - "filePath": "mobile/src/components/AgentStateDot.tsx", - "kind": "jsx-attribute:accessibilityLabel", - "text": "No recent update", - "dynamic": false, - "count": 1 - }, - { - "filePath": "mobile/src/components/AuthFailedBanner.tsx", - "kind": "jsx-text", - "text": "Authentication failed — try reconnecting first; if it keeps failing, re-pair from desktop.", - "dynamic": false, - "count": 1 - }, - { - "filePath": "mobile/src/components/AuthFailedBannerActions.tsx", - "kind": "jsx-text", - "text": "Re-pair", - "dynamic": false, - "count": 1 - }, - { - "filePath": "mobile/src/components/AuthFailedBannerActions.tsx", - "kind": "jsx-text", - "text": "Remove", - "dynamic": false, - "count": 1 - }, - { - "filePath": "mobile/src/components/AuthFailedBannerActions.tsx", - "kind": "jsx-text", - "text": "Retry", - "dynamic": false, - "count": 1 - }, - { - "filePath": "mobile/src/components/AuthFailedBannerActions.web.tsx", - "kind": "jsx-text", - "text": "Re-pair", - "dynamic": false, - "count": 1 - }, - { - "filePath": "mobile/src/components/AuthFailedBannerActions.web.tsx", - "kind": "jsx-text", - "text": "Reconnect or remove this host from the Orca app.", - "dynamic": false, - "count": 1 - }, { "filePath": "mobile/src/components/codex-reset-credit-reply-schema.ts", "kind": "object-property:message", @@ -307,251 +216,6 @@ "dynamic": false, "count": 1 }, - { - "filePath": "mobile/src/components/codex-reset-credit.ts", - "kind": "object-property:message", - "text": "Codex usage has been refreshed.", - "dynamic": false, - "count": 2 - }, - { - "filePath": "mobile/src/components/codex-reset-credit.ts", - "kind": "object-property:message", - "text": "No eligible Codex rate-limit window is exhausted.", - "dynamic": false, - "count": 1 - }, - { - "filePath": "mobile/src/components/codex-reset-credit.ts", - "kind": "object-property:message", - "text": "This account has no earned reset credits available.", - "dynamic": false, - "count": 1 - }, - { - "filePath": "mobile/src/components/codex-reset-credit.ts", - "kind": "object-property:title", - "text": "No reset available", - "dynamic": false, - "count": 1 - }, - { - "filePath": "mobile/src/components/codex-reset-credit.ts", - "kind": "object-property:title", - "text": "Nothing to reset", - "dynamic": false, - "count": 1 - }, - { - "filePath": "mobile/src/components/codex-reset-credit.ts", - "kind": "object-property:title", - "text": "Rate limits reset", - "dynamic": false, - "count": 1 - }, - { - "filePath": "mobile/src/components/codex-reset-credit.ts", - "kind": "object-property:title", - "text": "Reset already applied", - "dynamic": false, - "count": 1 - }, - { - "filePath": "mobile/src/components/CodexResetCreditAction.tsx", - "kind": "jsx-attribute:accessibilityHint", - "text": "Uses one earned reset for", - "dynamic": true, - "count": 1 - }, - { - "filePath": "mobile/src/components/CodexResetCreditAction.tsx", - "kind": "jsx-attribute:accessibilityHint", - "text": "Uses one earned reset for the active Codex account", - "dynamic": false, - "count": 1 - }, - { - "filePath": "mobile/src/components/CodexResetCreditAction.tsx", - "kind": "jsx-attribute:accessibilityLabel", - "text": "Resetting Codex rate limits", - "dynamic": false, - "count": 1 - }, - { - "filePath": "mobile/src/components/CodexResetCreditAction.tsx", - "kind": "jsx-attribute:accessibilityLabel", - "text": "Use Codex rate-limit reset", - "dynamic": false, - "count": 1 - }, - { - "filePath": "mobile/src/components/CodexResetCreditAction.tsx", - "kind": "jsx-expression", - "text": "Earned Codex rate-limit reset", - "dynamic": false, - "count": 1 - }, - { - "filePath": "mobile/src/components/CodexResetCreditAction.tsx", - "kind": "jsx-expression", - "text": "Resetting…", - "dynamic": false, - "count": 1 - }, - { - "filePath": "mobile/src/components/CodexResetCreditAction.tsx", - "kind": "jsx-expression", - "text": "Use reset", - "dynamic": false, - "count": 1 - }, - { - "filePath": "mobile/src/components/CustomKeyModal.tsx", - "kind": "jsx-attribute:accessibilityLabel", - "text": "Back", - "dynamic": false, - "count": 1 - }, - { - "filePath": "mobile/src/components/CustomKeyModal.tsx", - "kind": "jsx-attribute:placeholder", - "text": "e.g. Build", - "dynamic": false, - "count": 1 - }, - { - "filePath": "mobile/src/components/CustomKeyModal.tsx", - "kind": "jsx-attribute:placeholder", - "text": "e.g. pnpm build", - "dynamic": false, - "count": 1 - }, - { - "filePath": "mobile/src/components/CustomKeyModal.tsx", - "kind": "jsx-expression", - "text": "Add Shortcut", - "dynamic": false, - "count": 1 - }, - { - "filePath": "mobile/src/components/CustomKeyModal.tsx", - "kind": "jsx-expression", - "text": "Pick a key", - "dynamic": false, - "count": 1 - }, - { - "filePath": "mobile/src/components/CustomKeyModal.tsx", - "kind": "jsx-expression", - "text": "Shortcut Combo", - "dynamic": false, - "count": 1 - }, - { - "filePath": "mobile/src/components/CustomKeyModal.tsx", - "kind": "jsx-expression", - "text": "Text Macro", - "dynamic": false, - "count": 1 - }, - { - "filePath": "mobile/src/components/CustomKeyModal.tsx", - "kind": "jsx-text", - "text": "Add", - "dynamic": false, - "count": 1 - }, - { - "filePath": "mobile/src/components/CustomKeyModal.tsx", - "kind": "jsx-text", - "text": "Add Shortcut", - "dynamic": false, - "count": 1 - }, - { - "filePath": "mobile/src/components/CustomKeyModal.tsx", - "kind": "jsx-text", - "text": "Build Ctrl, Alt, and Shift key chords", - "dynamic": false, - "count": 1 - }, - { - "filePath": "mobile/src/components/CustomKeyModal.tsx", - "kind": "jsx-text", - "text": "Command", - "dynamic": false, - "count": 1 - }, - { - "filePath": "mobile/src/components/CustomKeyModal.tsx", - "kind": "jsx-text", - "text": "Key", - "dynamic": false, - "count": 1 - }, - { - "filePath": "mobile/src/components/CustomKeyModal.tsx", - "kind": "jsx-text", - "text": "Label", - "dynamic": false, - "count": 1 - }, - { - "filePath": "mobile/src/components/CustomKeyModal.tsx", - "kind": "jsx-text", - "text": "Manage Shortcuts", - "dynamic": false, - "count": 1 - }, - { - "filePath": "mobile/src/components/CustomKeyModal.tsx", - "kind": "jsx-text", - "text": "Modifiers", - "dynamic": false, - "count": 1 - }, - { - "filePath": "mobile/src/components/CustomKeyModal.tsx", - "kind": "jsx-text", - "text": "More keys — Tab, arrows, F1–F12…", - "dynamic": false, - "count": 1 - }, - { - "filePath": "mobile/src/components/CustomKeyModal.tsx", - "kind": "jsx-text", - "text": "Press Enter", - "dynamic": false, - "count": 1 - }, - { - "filePath": "mobile/src/components/CustomKeyModal.tsx", - "kind": "jsx-text", - "text": "Send custom text command", - "dynamic": false, - "count": 1 - }, - { - "filePath": "mobile/src/components/CustomKeyModal.tsx", - "kind": "jsx-text", - "text": "Shortcut Combo", - "dynamic": false, - "count": 1 - }, - { - "filePath": "mobile/src/components/CustomKeyModal.tsx", - "kind": "jsx-text", - "text": "Show, hide, or reorder shortcut keys", - "dynamic": false, - "count": 1 - }, - { - "filePath": "mobile/src/components/CustomKeyModal.tsx", - "kind": "jsx-text", - "text": "Text Macro", - "dynamic": false, - "count": 1 - }, { "filePath": "mobile/src/components/CustomKeyModal.tsx", "kind": "object-property:label", @@ -573,125 +237,6 @@ "dynamic": false, "count": 1 }, - { - "filePath": "mobile/src/components/CustomKeyModal.tsx", - "kind": "object-property:title", - "text": "Editing", - "dynamic": false, - "count": 1 - }, - { - "filePath": "mobile/src/components/CustomKeyModal.tsx", - "kind": "object-property:title", - "text": "Function", - "dynamic": false, - "count": 1 - }, - { - "filePath": "mobile/src/components/CustomKeyModal.tsx", - "kind": "object-property:title", - "text": "Navigation", - "dynamic": false, - "count": 1 - }, - { - "filePath": "mobile/src/components/DragReorderRow.tsx", - "kind": "jsx-attribute:accessibilityHint", - "text": "Use the move up and move down actions to reorder without dragging", - "dynamic": false, - "count": 1 - }, - { - "filePath": "mobile/src/components/DragReorderRow.tsx", - "kind": "jsx-attribute:accessibilityLabel", - "text": "Drag to reorder", - "dynamic": false, - "count": 1 - }, - { - "filePath": "mobile/src/components/DragReorderRow.tsx", - "kind": "object-property:label", - "text": "Move down", - "dynamic": false, - "count": 1 - }, - { - "filePath": "mobile/src/components/DragReorderRow.tsx", - "kind": "object-property:label", - "text": "Move up", - "dynamic": false, - "count": 1 - }, - { - "filePath": "mobile/src/components/HostDiagnosticsLink.tsx", - "kind": "jsx-attribute:accessibilityLabel", - "text": "View network diagnostics", - "dynamic": false, - "count": 1 - }, - { - "filePath": "mobile/src/components/HostDiagnosticsLink.tsx", - "kind": "jsx-text", - "text": "View network diagnostics", - "dynamic": false, - "count": 1 - }, - { - "filePath": "mobile/src/components/HostProtocolGate.tsx", - "kind": "jsx-attribute:accessibilityLabel", - "text": "Checking host compatibility", - "dynamic": false, - "count": 2 - }, - { - "filePath": "mobile/src/components/HostRouteNoticeBanner.tsx", - "kind": "jsx-attribute:accessibilityLabel", - "text": "Dismiss notice", - "dynamic": false, - "count": 1 - }, - { - "filePath": "mobile/src/components/JiraConnectPrompt.tsx", - "kind": "jsx-expression", - "text": "Add your Jira site in Orca desktop under Settings → Integrations, then check again.", - "dynamic": false, - "count": 1 - }, - { - "filePath": "mobile/src/components/JiraConnectPrompt.tsx", - "kind": "jsx-expression", - "text": "Connect Jira on your desktop", - "dynamic": false, - "count": 1 - }, - { - "filePath": "mobile/src/components/JiraConnectPrompt.tsx", - "kind": "jsx-expression", - "text": "Jira is saved but unreadable:", - "dynamic": true, - "count": 1 - }, - { - "filePath": "mobile/src/components/JiraConnectPrompt.tsx", - "kind": "jsx-expression", - "text": "Update Orca desktop", - "dynamic": false, - "count": 1 - }, - { - "filePath": "mobile/src/components/JiraConnectPrompt.tsx", - "kind": "jsx-text", - "text": "Check again", - "dynamic": false, - "count": 1 - }, - { - "filePath": "mobile/src/components/mobile-markdown-blocks.tsx", - "kind": "jsx-attribute:accessibilityLabel", - "text": "Copy code", - "dynamic": false, - "count": 1 - }, { "filePath": "mobile/src/components/mobile-markdown-blocks.tsx", "kind": "jsx-expression", @@ -699,671 +244,6 @@ "dynamic": false, "count": 1 }, - { - "filePath": "mobile/src/components/mobile-markdown-blocks.tsx", - "kind": "jsx-expression", - "text": "Open image", - "dynamic": false, - "count": 1 - }, - { - "filePath": "mobile/src/components/MobileDictationSetupSheet.tsx", - "kind": "jsx-attribute:accessibilityLabel", - "text": "Dictation enabled", - "dynamic": false, - "count": 1 - }, - { - "filePath": "mobile/src/components/MobileDictationSetupSheet.tsx", - "kind": "jsx-attribute:accessibilityLabel", - "text": "Download", - "dynamic": true, - "count": 1 - }, - { - "filePath": "mobile/src/components/MobileDictationSetupSheet.tsx", - "kind": "jsx-attribute:accessibilityLabel", - "text": "Use", - "dynamic": true, - "count": 1 - }, - { - "filePath": "mobile/src/components/MobileDictationSetupSheet.tsx", - "kind": "jsx-expression", - "text": "· extracting…", - "dynamic": false, - "count": 1 - }, - { - "filePath": "mobile/src/components/MobileDictationSetupSheet.tsx", - "kind": "jsx-expression", - "text": "OpenAI API", - "dynamic": false, - "count": 1 - }, - { - "filePath": "mobile/src/components/MobileDictationSetupSheet.tsx", - "kind": "jsx-text", - "text": "Dictation enabled", - "dynamic": false, - "count": 1 - }, - { - "filePath": "mobile/src/components/MobileDictationSetupSheet.tsx", - "kind": "jsx-text", - "text": "Download", - "dynamic": false, - "count": 1 - }, - { - "filePath": "mobile/src/components/MobileDictationSetupSheet.tsx", - "kind": "jsx-text", - "text": "Download or pick a model and enable dictation on the paired computer — all from here.", - "dynamic": false, - "count": 1 - }, - { - "filePath": "mobile/src/components/MobileDictationSetupSheet.tsx", - "kind": "jsx-text", - "text": "In use", - "dynamic": false, - "count": 1 - }, - { - "filePath": "mobile/src/components/MobileDictationSetupSheet.tsx", - "kind": "jsx-text", - "text": "Recommended", - "dynamic": false, - "count": 1 - }, - { - "filePath": "mobile/src/components/MobileDictationSetupSheet.tsx", - "kind": "jsx-text", - "text": "Set up voice dictation", - "dynamic": false, - "count": 1 - }, - { - "filePath": "mobile/src/components/MobileDictationSetupSheet.tsx", - "kind": "jsx-text", - "text": "Use", - "dynamic": false, - "count": 1 - }, - { - "filePath": "mobile/src/components/MobileDiffReviewBody.tsx", - "kind": "jsx-attribute:accessibilityLabel", - "text": "Retry loading review", - "dynamic": false, - "count": 1 - }, - { - "filePath": "mobile/src/components/MobileDiffReviewBody.tsx", - "kind": "jsx-attribute:text", - "text": "Loading diff...", - "dynamic": false, - "count": 1 - }, - { - "filePath": "mobile/src/components/MobileDiffReviewBody.tsx", - "kind": "jsx-attribute:text", - "text": "Loading review...", - "dynamic": false, - "count": 1 - }, - { - "filePath": "mobile/src/components/MobileDiffReviewBody.tsx", - "kind": "jsx-attribute:text", - "text": "Try a different review filter.", - "dynamic": false, - "count": 1 - }, - { - "filePath": "mobile/src/components/MobileDiffReviewBody.tsx", - "kind": "jsx-attribute:title", - "text": "No Reviewable Changes", - "dynamic": false, - "count": 1 - }, - { - "filePath": "mobile/src/components/MobileDiffReviewBody.tsx", - "kind": "jsx-attribute:title", - "text": "Review Unavailable", - "dynamic": false, - "count": 1 - }, - { - "filePath": "mobile/src/components/MobileDiffReviewBody.tsx", - "kind": "jsx-attribute:title", - "text": "Unable to Load Review", - "dynamic": false, - "count": 1 - }, - { - "filePath": "mobile/src/components/MobileDiffReviewBody.tsx", - "kind": "jsx-text", - "text": "Diff truncated for mobile preview.", - "dynamic": false, - "count": 1 - }, - { - "filePath": "mobile/src/components/MobileDiffReviewBody.tsx", - "kind": "jsx-text", - "text": "Retry", - "dynamic": false, - "count": 1 - }, - { - "filePath": "mobile/src/components/MobileDiffReviewDrawers.tsx", - "kind": "jsx-attribute:accessibilityLabel", - "text": "Cancel note", - "dynamic": false, - "count": 1 - }, - { - "filePath": "mobile/src/components/MobileDiffReviewDrawers.tsx", - "kind": "jsx-attribute:accessibilityLabel", - "text": "Delete note", - "dynamic": false, - "count": 1 - }, - { - "filePath": "mobile/src/components/MobileDiffReviewDrawers.tsx", - "kind": "jsx-attribute:accessibilityLabel", - "text": "Send notes to agent", - "dynamic": false, - "count": 1 - }, - { - "filePath": "mobile/src/components/MobileDiffReviewDrawers.tsx", - "kind": "jsx-attribute:accessibilityLabel", - "text": "Stage reviewed files", - "dynamic": false, - "count": 1 - }, - { - "filePath": "mobile/src/components/MobileDiffReviewDrawers.tsx", - "kind": "jsx-attribute:message", - "text": "\"? This cannot be undone.", - "dynamic": true, - "count": 1 - }, - { - "filePath": "mobile/src/components/MobileDiffReviewDrawers.tsx", - "kind": "jsx-attribute:message", - "text": "Discard changes to \"", - "dynamic": true, - "count": 1 - }, - { - "filePath": "mobile/src/components/MobileDiffReviewDrawers.tsx", - "kind": "jsx-attribute:message", - "text": "reviewed unstaged files can be staged", - "dynamic": true, - "count": 1 - }, - { - "filePath": "mobile/src/components/MobileDiffReviewDrawers.tsx", - "kind": "jsx-attribute:placeholder", - "text": "Review note", - "dynamic": false, - "count": 1 - }, - { - "filePath": "mobile/src/components/MobileDiffReviewDrawers.tsx", - "kind": "jsx-attribute:title", - "text": "Discard File", - "dynamic": false, - "count": 1 - }, - { - "filePath": "mobile/src/components/MobileDiffReviewDrawers.tsx", - "kind": "jsx-attribute:title", - "text": "Review Actions", - "dynamic": false, - "count": 1 - }, - { - "filePath": "mobile/src/components/MobileDiffReviewDrawers.tsx", - "kind": "jsx-attribute:title", - "text": "Send Notes", - "dynamic": false, - "count": 1 - }, - { - "filePath": "mobile/src/components/MobileDiffReviewDrawers.tsx", - "kind": "jsx-expression", - "text": "Add Note", - "dynamic": false, - "count": 1 - }, - { - "filePath": "mobile/src/components/MobileDiffReviewDrawers.tsx", - "kind": "jsx-expression", - "text": "Edit Note", - "dynamic": false, - "count": 1 - }, - { - "filePath": "mobile/src/components/MobileDiffReviewDrawers.tsx", - "kind": "jsx-expression", - "text": "File note", - "dynamic": false, - "count": 1 - }, - { - "filePath": "mobile/src/components/MobileDiffReviewDrawers.tsx", - "kind": "jsx-expression", - "text": "Line", - "dynamic": true, - "count": 1 - }, - { - "filePath": "mobile/src/components/MobileDiffReviewDrawers.tsx", - "kind": "jsx-text", - "text": "Delete", - "dynamic": false, - "count": 1 - }, - { - "filePath": "mobile/src/components/MobileDiffReviewDrawers.tsx", - "kind": "jsx-text", - "text": "Review Complete", - "dynamic": false, - "count": 1 - }, - { - "filePath": "mobile/src/components/MobileDiffReviewDrawers.tsx", - "kind": "jsx-text", - "text": "reviewed,", - "dynamic": false, - "count": 1 - }, - { - "filePath": "mobile/src/components/MobileDiffReviewDrawers.tsx", - "kind": "jsx-text", - "text": "Save", - "dynamic": false, - "count": 1 - }, - { - "filePath": "mobile/src/components/MobileDiffReviewDrawers.tsx", - "kind": "jsx-text", - "text": "Send Notes", - "dynamic": false, - "count": 1 - }, - { - "filePath": "mobile/src/components/MobileDiffReviewDrawers.tsx", - "kind": "jsx-text", - "text": "Stage Reviewed", - "dynamic": false, - "count": 1 - }, - { - "filePath": "mobile/src/components/MobileDiffReviewDrawers.tsx", - "kind": "object-property:label", - "text": "Clear Sent Notes", - "dynamic": false, - "count": 1 - }, - { - "filePath": "mobile/src/components/MobileDiffReviewDrawers.tsx", - "kind": "object-property:label", - "text": "Copy Notes", - "dynamic": false, - "count": 2 - }, - { - "filePath": "mobile/src/components/MobileDiffReviewDrawers.tsx", - "kind": "object-property:label", - "text": "Mark Unreviewed", - "dynamic": false, - "count": 1 - }, - { - "filePath": "mobile/src/components/MobileDiffReviewDrawers.tsx", - "kind": "object-property:label", - "text": "New Agent Session", - "dynamic": false, - "count": 1 - }, - { - "filePath": "mobile/src/components/MobileDiffReviewDrawers.tsx", - "kind": "object-property:label", - "text": "Open in Session", - "dynamic": false, - "count": 1 - }, - { - "filePath": "mobile/src/components/MobileDiffReviewDrawers.tsx", - "kind": "object-property:label", - "text": "Send Unsent Notes", - "dynamic": false, - "count": 1 - }, - { - "filePath": "mobile/src/components/MobileDiffReviewDrawers.tsx", - "kind": "object-property:label", - "text": "Stage Reviewed Files", - "dynamic": false, - "count": 1 - }, - { - "filePath": "mobile/src/components/MobileDiffReviewFileSummary.tsx", - "kind": "jsx-attribute:accessibilityLabel", - "text": "Edit file note", - "dynamic": false, - "count": 1 - }, - { - "filePath": "mobile/src/components/MobileDiffReviewFileSummary.tsx", - "kind": "jsx-attribute:accessibilityLabel", - "text": "Next hunk", - "dynamic": false, - "count": 1 - }, - { - "filePath": "mobile/src/components/MobileDiffReviewFileSummary.tsx", - "kind": "jsx-attribute:accessibilityLabel", - "text": "Previous hunk", - "dynamic": false, - "count": 1 - }, - { - "filePath": "mobile/src/components/MobileDiffReviewFileSummary.tsx", - "kind": "jsx-expression", - "text": "from", - "dynamic": true, - "count": 1 - }, - { - "filePath": "mobile/src/components/MobileDiffReviewFileSummary.tsx", - "kind": "jsx-text", - "text": "Changed", - "dynamic": false, - "count": 1 - }, - { - "filePath": "mobile/src/components/MobileDiffReviewFileSummary.tsx", - "kind": "jsx-text", - "text": "Hunk", - "dynamic": false, - "count": 2 - }, - { - "filePath": "mobile/src/components/MobileDiffReviewFileSummary.tsx", - "kind": "jsx-text", - "text": "Reviewed", - "dynamic": false, - "count": 1 - }, - { - "filePath": "mobile/src/components/MobileDiffReviewFileSummary.tsx", - "kind": "jsx-text", - "text": "stale", - "dynamic": false, - "count": 1 - }, - { - "filePath": "mobile/src/components/MobileDiffReviewFileSummary.tsx", - "kind": "jsx-text", - "text": "Stale", - "dynamic": false, - "count": 1 - }, - { - "filePath": "mobile/src/components/MobileDiffReviewFooter.tsx", - "kind": "jsx-attribute:accessibilityLabel", - "text": "Add file note", - "dynamic": false, - "count": 1 - }, - { - "filePath": "mobile/src/components/MobileDiffReviewFooter.tsx", - "kind": "jsx-attribute:accessibilityLabel", - "text": "Discard file", - "dynamic": false, - "count": 1 - }, - { - "filePath": "mobile/src/components/MobileDiffReviewFooter.tsx", - "kind": "jsx-attribute:accessibilityLabel", - "text": "Mark file reviewed", - "dynamic": false, - "count": 1 - }, - { - "filePath": "mobile/src/components/MobileDiffReviewFooter.tsx", - "kind": "jsx-attribute:accessibilityLabel", - "text": "Next file", - "dynamic": false, - "count": 1 - }, - { - "filePath": "mobile/src/components/MobileDiffReviewFooter.tsx", - "kind": "jsx-attribute:accessibilityLabel", - "text": "Previous file", - "dynamic": false, - "count": 1 - }, - { - "filePath": "mobile/src/components/MobileDiffReviewFooter.tsx", - "kind": "jsx-attribute:accessibilityLabel", - "text": "Stage file", - "dynamic": false, - "count": 1 - }, - { - "filePath": "mobile/src/components/MobileDiffReviewFooter.tsx", - "kind": "jsx-attribute:accessibilityLabel", - "text": "Unstage file", - "dynamic": false, - "count": 1 - }, - { - "filePath": "mobile/src/components/MobileDiffReviewFooter.tsx", - "kind": "jsx-expression", - "text": "Mark Reviewed", - "dynamic": false, - "count": 1 - }, - { - "filePath": "mobile/src/components/MobileDiffReviewFooter.tsx", - "kind": "jsx-expression", - "text": "Reviewed", - "dynamic": false, - "count": 1 - }, - { - "filePath": "mobile/src/components/MobileDiffReviewFooter.tsx", - "kind": "jsx-text", - "text": "Discard", - "dynamic": false, - "count": 1 - }, - { - "filePath": "mobile/src/components/MobileDiffReviewFooter.tsx", - "kind": "jsx-text", - "text": "Note", - "dynamic": false, - "count": 1 - }, - { - "filePath": "mobile/src/components/MobileDiffReviewFooter.tsx", - "kind": "jsx-text", - "text": "Stage", - "dynamic": false, - "count": 1 - }, - { - "filePath": "mobile/src/components/MobileDiffReviewFooter.tsx", - "kind": "jsx-text", - "text": "Unstage", - "dynamic": false, - "count": 1 - }, - { - "filePath": "mobile/src/components/MobileDiffReviewHeader.tsx", - "kind": "jsx-attribute:accessibilityLabel", - "text": "Back", - "dynamic": false, - "count": 1 - }, - { - "filePath": "mobile/src/components/MobileDiffReviewHeader.tsx", - "kind": "jsx-attribute:accessibilityLabel", - "text": "Open pull request sidebar", - "dynamic": false, - "count": 1 - }, - { - "filePath": "mobile/src/components/MobileDiffReviewHeader.tsx", - "kind": "jsx-attribute:accessibilityLabel", - "text": "Open review actions", - "dynamic": false, - "count": 1 - }, - { - "filePath": "mobile/src/components/MobileDiffReviewHeader.tsx", - "kind": "jsx-attribute:accessibilityLabel", - "text": "review files", - "dynamic": true, - "count": 1 - }, - { - "filePath": "mobile/src/components/MobileDiffReviewHeader.tsx", - "kind": "jsx-attribute:accessibilityLabel", - "text": "Show", - "dynamic": true, - "count": 1 - }, - { - "filePath": "mobile/src/components/MobileDiffReviewHeader.tsx", - "kind": "jsx-expression", - "text": "All", - "dynamic": false, - "count": 1 - }, - { - "filePath": "mobile/src/components/MobileDiffReviewHeader.tsx", - "kind": "jsx-text", - "text": "Changes", - "dynamic": false, - "count": 1 - }, - { - "filePath": "mobile/src/components/MobileDiffReviewHeader.tsx", - "kind": "jsx-text", - "text": "reviewed", - "dynamic": false, - "count": 1 - }, - { - "filePath": "mobile/src/components/MobileDiffReviewLine.tsx", - "kind": "jsx-attribute:accessibilityLabel", - "text": "Add note on line", - "dynamic": true, - "count": 1 - }, - { - "filePath": "mobile/src/components/MobileDiffReviewLine.tsx", - "kind": "jsx-attribute:accessibilityLabel", - "text": "Edit note on line", - "dynamic": true, - "count": 1 - }, - { - "filePath": "mobile/src/components/MobileHomeQuickActions.tsx", - "kind": "jsx-text", - "text": "New Workspace", - "dynamic": false, - "count": 1 - }, - { - "filePath": "mobile/src/components/MobileHomeQuickActions.tsx", - "kind": "jsx-text", - "text": "Pair Desktop", - "dynamic": false, - "count": 1 - }, - { - "filePath": "mobile/src/components/MobileHomeQuickActions.tsx", - "kind": "jsx-text", - "text": "Quick Actions", - "dynamic": false, - "count": 1 - }, - { - "filePath": "mobile/src/components/MobileHostCard.tsx", - "kind": "jsx-attribute:accessibilityLabel", - "text": "Actions for", - "dynamic": true, - "count": 1 - }, - { - "filePath": "mobile/src/components/MobileHtmlPreview.tsx", - "kind": "jsx-attribute:accessibilityLabel", - "text": "Preview rendered HTML", - "dynamic": false, - "count": 1 - }, - { - "filePath": "mobile/src/components/MobileHtmlPreview.tsx", - "kind": "jsx-attribute:accessibilityLabel", - "text": "View HTML source", - "dynamic": false, - "count": 1 - }, - { - "filePath": "mobile/src/components/MobileHtmlPreview.tsx", - "kind": "jsx-attribute:title", - "text": "HTML preview", - "dynamic": false, - "count": 1 - }, - { - "filePath": "mobile/src/components/MobileHtmlPreview.tsx", - "kind": "jsx-text", - "text": "Preview", - "dynamic": false, - "count": 1 - }, - { - "filePath": "mobile/src/components/MobileHtmlPreview.tsx", - "kind": "jsx-text", - "text": "Source", - "dynamic": false, - "count": 1 - }, - { - "filePath": "mobile/src/components/MobileLoadErrorState.tsx", - "kind": "jsx-attribute:label", - "text": "Retry", - "dynamic": false, - "count": 1 - }, - { - "filePath": "mobile/src/components/MobilePRSidebar.tsx", - "kind": "jsx-attribute:accessibilityLabel", - "text": "Retry loading pull request", - "dynamic": false, - "count": 1 - }, - { - "filePath": "mobile/src/components/MobilePRSidebar.tsx", - "kind": "jsx-text", - "text": "Loading pull request…", - "dynamic": false, - "count": 1 - }, - { - "filePath": "mobile/src/components/MobilePRSidebar.tsx", - "kind": "jsx-text", - "text": "Retry", - "dynamic": false, - "count": 1 - }, { "filePath": "mobile/src/components/MobileRichMarkdownEditor.tsx", "kind": "jsx-attribute:placeholder", @@ -1371,167 +251,6 @@ "dynamic": false, "count": 1 }, - { - "filePath": "mobile/src/components/MobileRichMarkdownToolbar.tsx", - "kind": "object-property:label", - "text": "Body", - "dynamic": false, - "count": 1 - }, - { - "filePath": "mobile/src/components/MobileRichMarkdownToolbar.tsx", - "kind": "object-property:label", - "text": "Bold", - "dynamic": false, - "count": 1 - }, - { - "filePath": "mobile/src/components/MobileRichMarkdownToolbar.tsx", - "kind": "object-property:label", - "text": "Bullet list", - "dynamic": false, - "count": 1 - }, - { - "filePath": "mobile/src/components/MobileRichMarkdownToolbar.tsx", - "kind": "object-property:label", - "text": "Checklist", - "dynamic": false, - "count": 1 - }, - { - "filePath": "mobile/src/components/MobileRichMarkdownToolbar.tsx", - "kind": "object-property:label", - "text": "Code block", - "dynamic": false, - "count": 1 - }, - { - "filePath": "mobile/src/components/MobileRichMarkdownToolbar.tsx", - "kind": "object-property:label", - "text": "H1", - "dynamic": false, - "count": 1 - }, - { - "filePath": "mobile/src/components/MobileRichMarkdownToolbar.tsx", - "kind": "object-property:label", - "text": "H2", - "dynamic": false, - "count": 1 - }, - { - "filePath": "mobile/src/components/MobileRichMarkdownToolbar.tsx", - "kind": "object-property:label", - "text": "H3", - "dynamic": false, - "count": 1 - }, - { - "filePath": "mobile/src/components/MobileRichMarkdownToolbar.tsx", - "kind": "object-property:label", - "text": "Image", - "dynamic": false, - "count": 1 - }, - { - "filePath": "mobile/src/components/MobileRichMarkdownToolbar.tsx", - "kind": "object-property:label", - "text": "Inline code", - "dynamic": false, - "count": 1 - }, - { - "filePath": "mobile/src/components/MobileRichMarkdownToolbar.tsx", - "kind": "object-property:label", - "text": "Italic", - "dynamic": false, - "count": 1 - }, - { - "filePath": "mobile/src/components/MobileRichMarkdownToolbar.tsx", - "kind": "object-property:label", - "text": "Link", - "dynamic": false, - "count": 1 - }, - { - "filePath": "mobile/src/components/MobileRichMarkdownToolbar.tsx", - "kind": "object-property:label", - "text": "Numbered list", - "dynamic": false, - "count": 1 - }, - { - "filePath": "mobile/src/components/MobileRichMarkdownToolbar.tsx", - "kind": "object-property:label", - "text": "Quote", - "dynamic": false, - "count": 1 - }, - { - "filePath": "mobile/src/components/MobileRichMarkdownToolbar.tsx", - "kind": "object-property:label", - "text": "Strike", - "dynamic": false, - "count": 1 - }, - { - "filePath": "mobile/src/components/MobileSourceCopyButton.tsx", - "kind": "jsx-attribute:accessibilityLabel", - "text": "Couldn't copy", - "dynamic": false, - "count": 1 - }, - { - "filePath": "mobile/src/components/MobileSourceCopyButton.tsx", - "kind": "user-visible-call", - "text": "Copy failed", - "dynamic": false, - "count": 1 - }, - { - "filePath": "mobile/src/components/MobileSourceCopyButton.tsx", - "kind": "user-visible-call", - "text": "The clipboard rejected the text.", - "dynamic": false, - "count": 1 - }, - { - "filePath": "mobile/src/components/MobileWorkspaceNameInput.tsx", - "kind": "jsx-attribute:placeholder", - "text": "Workspace name", - "dynamic": false, - "count": 1 - }, - { - "filePath": "mobile/src/components/mounted-bottom-drawer.tsx", - "kind": "jsx-attribute:accessibilityLabel", - "text": "Dismiss drawer", - "dynamic": false, - "count": 1 - }, - { - "filePath": "mobile/src/components/new-worktree-agent-selection.ts", - "kind": "object-property:label", - "text": "Blank Terminal", - "dynamic": false, - "count": 1 - }, - { - "filePath": "mobile/src/components/NewWorkspaceFab.tsx", - "kind": "jsx-attribute:accessibilityLabel", - "text": "New workspace", - "dynamic": false, - "count": 1 - }, - { - "filePath": "mobile/src/components/NewWorkspaceSetupScriptField.tsx", - "kind": "jsx-expression", - "text": "HOOKS", - "dynamic": false, - "count": 1 - }, { "filePath": "mobile/src/components/NewWorkspaceSetupScriptField.tsx", "kind": "jsx-expression", @@ -1539,188 +258,6 @@ "dynamic": false, "count": 1 }, - { - "filePath": "mobile/src/components/NewWorkspaceSetupScriptField.tsx", - "kind": "jsx-text", - "text": "Run", - "dynamic": false, - "count": 1 - }, - { - "filePath": "mobile/src/components/NewWorkspaceSetupScriptField.tsx", - "kind": "jsx-text", - "text": "Run setup command", - "dynamic": false, - "count": 1 - }, - { - "filePath": "mobile/src/components/NewWorkspaceSetupScriptField.tsx", - "kind": "jsx-text", - "text": "Setup script", - "dynamic": false, - "count": 1 - }, - { - "filePath": "mobile/src/components/NewWorkspaceSetupScriptField.tsx", - "kind": "jsx-text", - "text": "Skip", - "dynamic": false, - "count": 1 - }, - { - "filePath": "mobile/src/components/NewWorkspaceSshConnectionField.tsx", - "kind": "jsx-expression", - "text": "Connect", - "dynamic": false, - "count": 1 - }, - { - "filePath": "mobile/src/components/NewWorkspaceSshConnectionField.tsx", - "kind": "jsx-expression", - "text": "Connecting...", - "dynamic": false, - "count": 1 - }, - { - "filePath": "mobile/src/components/NewWorkspaceSshConnectionField.tsx", - "kind": "jsx-text", - "text": "Its server needs recovering from the desktop: Settings → SSH hosts → Recover server.", - "dynamic": false, - "count": 1 - }, - { - "filePath": "mobile/src/components/NewWorkspaceSshConnectionField.tsx", - "kind": "jsx-text", - "text": "SSH Connection", - "dynamic": false, - "count": 1 - }, - { - "filePath": "mobile/src/components/NewWorktreeFormSheet.tsx", - "kind": "jsx-attribute:label", - "text": "Name or 'Create From'", - "dynamic": false, - "count": 1 - }, - { - "filePath": "mobile/src/components/NewWorktreeFormSheet.tsx", - "kind": "jsx-attribute:label", - "text": "Workspace name", - "dynamic": false, - "count": 1 - }, - { - "filePath": "mobile/src/components/NewWorktreeFormSheet.tsx", - "kind": "jsx-attribute:placeholder", - "text": "Write a note", - "dynamic": false, - "count": 1 - }, - { - "filePath": "mobile/src/components/NewWorktreeFormSheet.tsx", - "kind": "jsx-expression", - "text": "Connect target", - "dynamic": false, - "count": 1 - }, - { - "filePath": "mobile/src/components/NewWorktreeFormSheet.tsx", - "kind": "jsx-expression", - "text": "Connect target first", - "dynamic": false, - "count": 1 - }, - { - "filePath": "mobile/src/components/NewWorktreeFormSheet.tsx", - "kind": "jsx-expression", - "text": "Create worktree", - "dynamic": false, - "count": 1 - }, - { - "filePath": "mobile/src/components/NewWorktreeFormSheet.tsx", - "kind": "jsx-text", - "text": "Advanced", - "dynamic": false, - "count": 1 - }, - { - "filePath": "mobile/src/components/NewWorktreeFormSheet.tsx", - "kind": "jsx-text", - "text": "Agent", - "dynamic": false, - "count": 1 - }, - { - "filePath": "mobile/src/components/NewWorktreeFormSheet.tsx", - "kind": "jsx-text", - "text": "Create worktree", - "dynamic": false, - "count": 1 - }, - { - "filePath": "mobile/src/components/NewWorktreeFormSheet.tsx", - "kind": "jsx-text", - "text": "No projects found", - "dynamic": false, - "count": 1 - }, - { - "filePath": "mobile/src/components/NewWorktreeFormSheet.tsx", - "kind": "jsx-text", - "text": "Note", - "dynamic": false, - "count": 1 - }, - { - "filePath": "mobile/src/components/NewWorktreeModalDrawers.tsx", - "kind": "jsx-attribute:title", - "text": "Agent", - "dynamic": false, - "count": 1 - }, - { - "filePath": "mobile/src/components/NewWorktreeModalDrawers.tsx", - "kind": "jsx-attribute:title", - "text": "Project", - "dynamic": false, - "count": 1 - }, - { - "filePath": "mobile/src/components/NewWorktreeModalDrawers.tsx", - "kind": "jsx-attribute:title", - "text": "Run on", - "dynamic": false, - "count": 1 - }, - { - "filePath": "mobile/src/components/NewWorktreeProjectTargetFields.tsx", - "kind": "jsx-attribute:placeholder", - "text": "Select a project", - "dynamic": false, - "count": 1 - }, - { - "filePath": "mobile/src/components/NewWorktreeProjectTargetFields.tsx", - "kind": "jsx-attribute:placeholder", - "text": "Select a run target", - "dynamic": false, - "count": 1 - }, - { - "filePath": "mobile/src/components/NewWorktreeProjectTargetFields.tsx", - "kind": "jsx-text", - "text": "Project", - "dynamic": false, - "count": 1 - }, - { - "filePath": "mobile/src/components/NewWorktreeProjectTargetFields.tsx", - "kind": "jsx-text", - "text": "Run on", - "dynamic": false, - "count": 1 - }, { "filePath": "mobile/src/components/pr-sidebar/MermaidDiagram.tsx", "kind": "jsx-text", @@ -1728,1385 +265,6 @@ "dynamic": false, "count": 1 }, - { - "filePath": "mobile/src/components/pr-sidebar/MobileLinkPrForm.tsx", - "kind": "jsx-attribute:accessibilityLabel", - "text": "Cancel", - "dynamic": false, - "count": 1 - }, - { - "filePath": "mobile/src/components/pr-sidebar/MobileLinkPrForm.tsx", - "kind": "jsx-attribute:placeholder", - "text": "#123 or https://github.com/owner/repo/pull/123", - "dynamic": false, - "count": 1 - }, - { - "filePath": "mobile/src/components/pr-sidebar/MobileLinkPrForm.tsx", - "kind": "jsx-expression", - "text": "Link #", - "dynamic": true, - "count": 1 - }, - { - "filePath": "mobile/src/components/pr-sidebar/MobileLinkPrForm.tsx", - "kind": "jsx-expression", - "text": "Link pull request", - "dynamic": false, - "count": 1 - }, - { - "filePath": "mobile/src/components/pr-sidebar/MobileLinkPrForm.tsx", - "kind": "jsx-text", - "text": "Cancel", - "dynamic": false, - "count": 1 - }, - { - "filePath": "mobile/src/components/pr-sidebar/MobileLinkPrForm.tsx", - "kind": "jsx-text", - "text": "Link existing pull request", - "dynamic": false, - "count": 1 - }, - { - "filePath": "mobile/src/components/pr-sidebar/MobileLinkPrForm.tsx", - "kind": "jsx-text", - "text": "PR number or GitHub URL", - "dynamic": false, - "count": 1 - }, - { - "filePath": "mobile/src/components/pr-sidebar/MobilePrViewPanel.tsx", - "kind": "object-property:message", - "text": "Current branch unavailable.", - "dynamic": false, - "count": 1 - }, - { - "filePath": "mobile/src/components/pr-sidebar/MobilePrViewPanel.tsx", - "kind": "object-property:message", - "text": "Hosted review panel unavailable for this provider.", - "dynamic": false, - "count": 1 - }, - { - "filePath": "mobile/src/components/pr-sidebar/pr-actions-state.ts", - "kind": "object-property:label", - "text": "Able to merge", - "dynamic": false, - "count": 2 - }, - { - "filePath": "mobile/src/components/pr-sidebar/pr-actions-state.ts", - "kind": "object-property:label", - "text": "Approval required", - "dynamic": false, - "count": 1 - }, - { - "filePath": "mobile/src/components/pr-sidebar/pr-actions-state.ts", - "kind": "object-property:label", - "text": "Auto-merge on", - "dynamic": false, - "count": 1 - }, - { - "filePath": "mobile/src/components/pr-sidebar/pr-actions-state.ts", - "kind": "object-property:label", - "text": "Behind", - "dynamic": false, - "count": 1 - }, - { - "filePath": "mobile/src/components/pr-sidebar/pr-actions-state.ts", - "kind": "object-property:label", - "text": "Blocked", - "dynamic": false, - "count": 1 - }, - { - "filePath": "mobile/src/components/pr-sidebar/pr-actions-state.ts", - "kind": "object-property:label", - "text": "Changes requested", - "dynamic": false, - "count": 1 - }, - { - "filePath": "mobile/src/components/pr-sidebar/pr-actions-state.ts", - "kind": "object-property:label", - "text": "Checking", - "dynamic": false, - "count": 1 - }, - { - "filePath": "mobile/src/components/pr-sidebar/pr-actions-state.ts", - "kind": "object-property:label", - "text": "Checks failed", - "dynamic": false, - "count": 1 - }, - { - "filePath": "mobile/src/components/pr-sidebar/pr-actions-state.ts", - "kind": "object-property:label", - "text": "Checks passed", - "dynamic": false, - "count": 1 - }, - { - "filePath": "mobile/src/components/pr-sidebar/pr-actions-state.ts", - "kind": "object-property:label", - "text": "Checks pending", - "dynamic": false, - "count": 1 - }, - { - "filePath": "mobile/src/components/pr-sidebar/pr-actions-state.ts", - "kind": "object-property:label", - "text": "Closed", - "dynamic": false, - "count": 1 - }, - { - "filePath": "mobile/src/components/pr-sidebar/pr-actions-state.ts", - "kind": "object-property:label", - "text": "Conflicts", - "dynamic": false, - "count": 1 - }, - { - "filePath": "mobile/src/components/pr-sidebar/pr-actions-state.ts", - "kind": "object-property:label", - "text": "Draft", - "dynamic": false, - "count": 1 - }, - { - "filePath": "mobile/src/components/pr-sidebar/pr-actions-state.ts", - "kind": "object-property:label", - "text": "Merge", - "dynamic": false, - "count": 1 - }, - { - "filePath": "mobile/src/components/pr-sidebar/pr-actions-state.ts", - "kind": "object-property:label", - "text": "Merge when ready", - "dynamic": false, - "count": 1 - }, - { - "filePath": "mobile/src/components/pr-sidebar/pr-actions-state.ts", - "kind": "object-property:label", - "text": "Merged", - "dynamic": false, - "count": 1 - }, - { - "filePath": "mobile/src/components/pr-sidebar/pr-checks-presentation.ts", - "kind": "object-property:label", - "text": "No checks", - "dynamic": false, - "count": 1 - }, - { - "filePath": "mobile/src/components/pr-sidebar/pr-comment-audience-labels.ts", - "kind": "object-property:label", - "text": "All", - "dynamic": false, - "count": 1 - }, - { - "filePath": "mobile/src/components/pr-sidebar/pr-comment-audience-labels.ts", - "kind": "object-property:label", - "text": "Bots", - "dynamic": false, - "count": 1 - }, - { - "filePath": "mobile/src/components/pr-sidebar/pr-comment-audience-labels.ts", - "kind": "object-property:label", - "text": "Humans", - "dynamic": false, - "count": 1 - }, - { - "filePath": "mobile/src/components/pr-sidebar/PRActionsSection.tsx", - "kind": "jsx-attribute:accessibilityLabel", - "text": "Close pull request", - "dynamic": false, - "count": 1 - }, - { - "filePath": "mobile/src/components/pr-sidebar/PRActionsSection.tsx", - "kind": "jsx-attribute:accessibilityLabel", - "text": "Mark pull request ready for review", - "dynamic": false, - "count": 1 - }, - { - "filePath": "mobile/src/components/pr-sidebar/PRActionsSection.tsx", - "kind": "jsx-attribute:accessibilityLabel", - "text": "Merge pull request", - "dynamic": false, - "count": 1 - }, - { - "filePath": "mobile/src/components/pr-sidebar/PRActionsSection.tsx", - "kind": "jsx-attribute:accessibilityLabel", - "text": "Recheck merge status", - "dynamic": false, - "count": 1 - }, - { - "filePath": "mobile/src/components/pr-sidebar/PRActionsSection.tsx", - "kind": "jsx-attribute:accessibilityLabel", - "text": "Reopen pull request", - "dynamic": false, - "count": 1 - }, - { - "filePath": "mobile/src/components/pr-sidebar/PRActionsSection.tsx", - "kind": "jsx-attribute:accessibilityLabel", - "text": "Toggle auto-merge", - "dynamic": false, - "count": 1 - }, - { - "filePath": "mobile/src/components/pr-sidebar/PRActionsSection.tsx", - "kind": "jsx-attribute:accessibilityLabel", - "text": "Unlink pull request", - "dynamic": false, - "count": 1 - }, - { - "filePath": "mobile/src/components/pr-sidebar/PRActionsSection.tsx", - "kind": "jsx-expression", - "text": "Close", - "dynamic": false, - "count": 1 - }, - { - "filePath": "mobile/src/components/pr-sidebar/PRActionsSection.tsx", - "kind": "jsx-expression", - "text": "Off", - "dynamic": false, - "count": 1 - }, - { - "filePath": "mobile/src/components/pr-sidebar/PRActionsSection.tsx", - "kind": "jsx-expression", - "text": "On", - "dynamic": false, - "count": 1 - }, - { - "filePath": "mobile/src/components/pr-sidebar/PRActionsSection.tsx", - "kind": "jsx-expression", - "text": "Reopen", - "dynamic": false, - "count": 1 - }, - { - "filePath": "mobile/src/components/pr-sidebar/PRActionsSection.tsx", - "kind": "jsx-text", - "text": "Auto-merge when ready", - "dynamic": false, - "count": 1 - }, - { - "filePath": "mobile/src/components/pr-sidebar/PRActionsSection.tsx", - "kind": "jsx-text", - "text": "Ready for review", - "dynamic": false, - "count": 1 - }, - { - "filePath": "mobile/src/components/pr-sidebar/PRActionsSection.tsx", - "kind": "jsx-text", - "text": "Recheck", - "dynamic": false, - "count": 1 - }, - { - "filePath": "mobile/src/components/pr-sidebar/PRActionsSection.tsx", - "kind": "jsx-text", - "text": "Unlink", - "dynamic": false, - "count": 1 - }, - { - "filePath": "mobile/src/components/pr-sidebar/PRActionsSection.tsx", - "kind": "object-property:message", - "text": "will be closed without merging.", - "dynamic": true, - "count": 1 - }, - { - "filePath": "mobile/src/components/pr-sidebar/PRActionsSection.tsx", - "kind": "object-property:message", - "text": "will be reopened.", - "dynamic": true, - "count": 1 - }, - { - "filePath": "mobile/src/components/pr-sidebar/PRActionsSection.tsx", - "kind": "object-property:title", - "text": "Close pull request?", - "dynamic": false, - "count": 1 - }, - { - "filePath": "mobile/src/components/pr-sidebar/PRActionsSection.tsx", - "kind": "object-property:title", - "text": "Merge pull request?", - "dynamic": false, - "count": 1 - }, - { - "filePath": "mobile/src/components/pr-sidebar/PRActionsSection.tsx", - "kind": "object-property:title", - "text": "Reopen pull request?", - "dynamic": false, - "count": 1 - }, - { - "filePath": "mobile/src/components/pr-sidebar/PRCheckDetail.tsx", - "kind": "jsx-text", - "text": "Annotations", - "dynamic": false, - "count": 1 - }, - { - "filePath": "mobile/src/components/pr-sidebar/PRCheckDetail.tsx", - "kind": "jsx-text", - "text": "No details available.", - "dynamic": false, - "count": 2 - }, - { - "filePath": "mobile/src/components/pr-sidebar/PRCheckDetail.tsx", - "kind": "jsx-text", - "text": "Showing first 100 jobs", - "dynamic": false, - "count": 1 - }, - { - "filePath": "mobile/src/components/pr-sidebar/PRCheckDetail.tsx", - "kind": "jsx-text", - "text": "Showing first 20 annotations", - "dynamic": false, - "count": 1 - }, - { - "filePath": "mobile/src/components/pr-sidebar/PRChecksSection.tsx", - "kind": "jsx-attribute:accessibilityLabel", - "text": "check details", - "dynamic": true, - "count": 1 - }, - { - "filePath": "mobile/src/components/pr-sidebar/PRChecksSection.tsx", - "kind": "jsx-attribute:accessibilityLabel", - "text": "Fix failing checks with AI", - "dynamic": false, - "count": 1 - }, - { - "filePath": "mobile/src/components/pr-sidebar/PRChecksSection.tsx", - "kind": "jsx-attribute:accessibilityLabel", - "text": "on the web", - "dynamic": true, - "count": 1 - }, - { - "filePath": "mobile/src/components/pr-sidebar/PRChecksSection.tsx", - "kind": "jsx-attribute:accessibilityLabel", - "text": "Open", - "dynamic": true, - "count": 1 - }, - { - "filePath": "mobile/src/components/pr-sidebar/PRChecksSection.tsx", - "kind": "jsx-attribute:accessibilityLabel", - "text": "Rerun failing checks", - "dynamic": false, - "count": 1 - }, - { - "filePath": "mobile/src/components/pr-sidebar/PRChecksSection.tsx", - "kind": "jsx-attribute:title", - "text": "Checks", - "dynamic": false, - "count": 1 - }, - { - "filePath": "mobile/src/components/pr-sidebar/PRChecksSection.tsx", - "kind": "jsx-text", - "text": "failing check", - "dynamic": false, - "count": 1 - }, - { - "filePath": "mobile/src/components/pr-sidebar/PRChecksSection.tsx", - "kind": "jsx-text", - "text": "Fix", - "dynamic": false, - "count": 1 - }, - { - "filePath": "mobile/src/components/pr-sidebar/PRChecksSection.tsx", - "kind": "jsx-text", - "text": "Inspect details or start an AI fix pass.", - "dynamic": false, - "count": 1 - }, - { - "filePath": "mobile/src/components/pr-sidebar/PRCommentCard.tsx", - "kind": "jsx-attribute:accessibilityLabel", - "text": "Delete comment", - "dynamic": false, - "count": 1 - }, - { - "filePath": "mobile/src/components/pr-sidebar/PRCommentCard.tsx", - "kind": "jsx-attribute:accessibilityLabel", - "text": "Edit comment", - "dynamic": false, - "count": 1 - }, - { - "filePath": "mobile/src/components/pr-sidebar/PRCommentCard.tsx", - "kind": "jsx-attribute:accessibilityLabel", - "text": "Open comment on GitHub", - "dynamic": false, - "count": 1 - }, - { - "filePath": "mobile/src/components/pr-sidebar/PRCommentCard.tsx", - "kind": "jsx-attribute:accessibilityLabel", - "text": "Reply to comment", - "dynamic": false, - "count": 1 - }, - { - "filePath": "mobile/src/components/pr-sidebar/PRCommentCard.tsx", - "kind": "jsx-attribute:accessibilityLabel", - "text": "Resolve thread", - "dynamic": false, - "count": 1 - }, - { - "filePath": "mobile/src/components/pr-sidebar/PRCommentCard.tsx", - "kind": "jsx-attribute:accessibilityLabel", - "text": "Unresolve thread", - "dynamic": false, - "count": 1 - }, - { - "filePath": "mobile/src/components/pr-sidebar/PRCommentCard.tsx", - "kind": "jsx-attribute:message", - "text": "This permanently deletes the comment on GitHub.", - "dynamic": false, - "count": 1 - }, - { - "filePath": "mobile/src/components/pr-sidebar/PRCommentCard.tsx", - "kind": "jsx-attribute:placeholder", - "text": "Edit comment…", - "dynamic": false, - "count": 1 - }, - { - "filePath": "mobile/src/components/pr-sidebar/PRCommentCard.tsx", - "kind": "jsx-attribute:placeholder", - "text": "Write a reply…", - "dynamic": false, - "count": 1 - }, - { - "filePath": "mobile/src/components/pr-sidebar/PRCommentCard.tsx", - "kind": "jsx-attribute:title", - "text": "Delete comment?", - "dynamic": false, - "count": 1 - }, - { - "filePath": "mobile/src/components/pr-sidebar/PRCommentCard.tsx", - "kind": "jsx-expression", - "text": "Delete", - "dynamic": false, - "count": 1 - }, - { - "filePath": "mobile/src/components/pr-sidebar/PRCommentCard.tsx", - "kind": "jsx-expression", - "text": "Resolve", - "dynamic": false, - "count": 1 - }, - { - "filePath": "mobile/src/components/pr-sidebar/PRCommentCard.tsx", - "kind": "jsx-expression", - "text": "Unresolve", - "dynamic": false, - "count": 1 - }, - { - "filePath": "mobile/src/components/pr-sidebar/PRCommentCard.tsx", - "kind": "jsx-text", - "text": "Edit", - "dynamic": false, - "count": 1 - }, - { - "filePath": "mobile/src/components/pr-sidebar/PRCommentCard.tsx", - "kind": "jsx-text", - "text": "Reply", - "dynamic": false, - "count": 1 - }, - { - "filePath": "mobile/src/components/pr-sidebar/PRCommentCard.tsx", - "kind": "jsx-text", - "text": "resolved", - "dynamic": false, - "count": 1 - }, - { - "filePath": "mobile/src/components/pr-sidebar/PRCommentComposer.tsx", - "kind": "jsx-attribute:accessibilityLabel", - "text": "Cancel", - "dynamic": false, - "count": 1 - }, - { - "filePath": "mobile/src/components/pr-sidebar/PRCommentComposer.tsx", - "kind": "jsx-text", - "text": "Cancel", - "dynamic": false, - "count": 1 - }, - { - "filePath": "mobile/src/components/pr-sidebar/PRCommentsSection.tsx", - "kind": "jsx-attribute:placeholder", - "text": "Add a comment…", - "dynamic": false, - "count": 1 - }, - { - "filePath": "mobile/src/components/pr-sidebar/PRCommentsSection.tsx", - "kind": "jsx-attribute:title", - "text": "Comments", - "dynamic": false, - "count": 1 - }, - { - "filePath": "mobile/src/components/pr-sidebar/PRCommentsSection.tsx", - "kind": "jsx-attribute:title", - "text": "Description", - "dynamic": false, - "count": 1 - }, - { - "filePath": "mobile/src/components/pr-sidebar/PRCommentsSection.tsx", - "kind": "jsx-expression", - "text": "comment", - "dynamic": false, - "count": 1 - }, - { - "filePath": "mobile/src/components/pr-sidebar/PRCommentsSection.tsx", - "kind": "jsx-expression", - "text": "of", - "dynamic": true, - "count": 1 - }, - { - "filePath": "mobile/src/components/pr-sidebar/PRCommentsSection.tsx", - "kind": "jsx-expression", - "text": "thread", - "dynamic": false, - "count": 1 - }, - { - "filePath": "mobile/src/components/pr-sidebar/PRCommentsSection.tsx", - "kind": "jsx-text", - "text": "by", - "dynamic": false, - "count": 1 - }, - { - "filePath": "mobile/src/components/pr-sidebar/PRCommentsSection.tsx", - "kind": "jsx-text", - "text": "Could not load comments. Tap refresh to try again.", - "dynamic": false, - "count": 1 - }, - { - "filePath": "mobile/src/components/pr-sidebar/PRCommentsSection.tsx", - "kind": "jsx-text", - "text": "Could not load description. Tap refresh to try again.", - "dynamic": false, - "count": 1 - }, - { - "filePath": "mobile/src/components/pr-sidebar/PRCommentsSection.tsx", - "kind": "jsx-text", - "text": "more", - "dynamic": false, - "count": 1 - }, - { - "filePath": "mobile/src/components/pr-sidebar/PRCommentsSection.tsx", - "kind": "jsx-text", - "text": "No comments yet.", - "dynamic": false, - "count": 1 - }, - { - "filePath": "mobile/src/components/pr-sidebar/PRCommentsSection.tsx", - "kind": "jsx-text", - "text": "No description provided.", - "dynamic": false, - "count": 1 - }, - { - "filePath": "mobile/src/components/pr-sidebar/PRCommentsSection.tsx", - "kind": "jsx-text", - "text": "Resolved", - "dynamic": false, - "count": 1 - }, - { - "filePath": "mobile/src/components/pr-sidebar/PRCommentsSection.tsx", - "kind": "jsx-text", - "text": "Show", - "dynamic": false, - "count": 1 - }, - { - "filePath": "mobile/src/components/pr-sidebar/PRConflictingFilesSection.tsx", - "kind": "jsx-attribute:accessibilityLabel", - "text": "Copy mergeability refresh commands", - "dynamic": false, - "count": 1 - }, - { - "filePath": "mobile/src/components/pr-sidebar/PRConflictingFilesSection.tsx", - "kind": "jsx-attribute:accessibilityLabel", - "text": "Resolve conflicts with AI", - "dynamic": false, - "count": 1 - }, - { - "filePath": "mobile/src/components/pr-sidebar/PRConflictingFilesSection.tsx", - "kind": "jsx-attribute:title", - "text": "Conflicts", - "dynamic": false, - "count": 1 - }, - { - "filePath": "mobile/src/components/pr-sidebar/PRConflictingFilesSection.tsx", - "kind": "jsx-text", - "text": "behind (base commit:", - "dynamic": false, - "count": 1 - }, - { - "filePath": "mobile/src/components/pr-sidebar/PRConflictingFilesSection.tsx", - "kind": "jsx-text", - "text": "commit", - "dynamic": false, - "count": 1 - }, - { - "filePath": "mobile/src/components/pr-sidebar/PRConflictingFilesSection.tsx", - "kind": "jsx-text", - "text": "Conflicting files", - "dynamic": false, - "count": 1 - }, - { - "filePath": "mobile/src/components/pr-sidebar/PRConflictingFilesSection.tsx", - "kind": "jsx-text", - "text": "Resolve conflicts with AI", - "dynamic": false, - "count": 1 - }, - { - "filePath": "mobile/src/components/pr-sidebar/PRConflictingFilesSection.tsx", - "kind": "jsx-text", - "text": "Run from this worktree", - "dynamic": false, - "count": 1 - }, - { - "filePath": "mobile/src/components/pr-sidebar/PRConflictingFilesSection.tsx", - "kind": "jsx-text", - "text": "This branch has conflicts that must be resolved", - "dynamic": false, - "count": 1 - }, - { - "filePath": "mobile/src/components/pr-sidebar/PRReviewersSection.tsx", - "kind": "jsx-attribute:accessibilityLabel", - "text": "Add or remove reviewers", - "dynamic": false, - "count": 1 - }, - { - "filePath": "mobile/src/components/pr-sidebar/PRReviewersSection.tsx", - "kind": "jsx-attribute:accessibilityLabel", - "text": "Remove", - "dynamic": true, - "count": 1 - }, - { - "filePath": "mobile/src/components/pr-sidebar/PRReviewersSection.tsx", - "kind": "jsx-attribute:title", - "text": "Reviewers", - "dynamic": false, - "count": 1 - }, - { - "filePath": "mobile/src/components/pr-sidebar/PRReviewersSection.tsx", - "kind": "jsx-text", - "text": "Could not load reviewers. Tap refresh to try again.", - "dynamic": false, - "count": 1 - }, - { - "filePath": "mobile/src/components/pr-sidebar/PRReviewersSection.tsx", - "kind": "jsx-text", - "text": "Loading reviewers…", - "dynamic": false, - "count": 1 - }, - { - "filePath": "mobile/src/components/pr-sidebar/PRReviewersSection.tsx", - "kind": "jsx-text", - "text": "No reviewers requested", - "dynamic": false, - "count": 1 - }, - { - "filePath": "mobile/src/components/pr-sidebar/PrSidebarCreateEmptyState.tsx", - "kind": "jsx-attribute:accessibilityLabel", - "text": "Create pull request", - "dynamic": false, - "count": 1 - }, - { - "filePath": "mobile/src/components/pr-sidebar/PrSidebarCreateEmptyState.tsx", - "kind": "jsx-attribute:accessibilityLabel", - "text": "Link an existing pull request", - "dynamic": false, - "count": 1 - }, - { - "filePath": "mobile/src/components/pr-sidebar/PrSidebarCreateEmptyState.tsx", - "kind": "jsx-attribute:accessibilityLabel", - "text": "Refresh pull request", - "dynamic": false, - "count": 1 - }, - { - "filePath": "mobile/src/components/pr-sidebar/PrSidebarCreateEmptyState.tsx", - "kind": "jsx-expression", - "text": "is not linked to an open PR.", - "dynamic": true, - "count": 1 - }, - { - "filePath": "mobile/src/components/pr-sidebar/PrSidebarCreateEmptyState.tsx", - "kind": "jsx-expression", - "text": "Linked PR #", - "dynamic": true, - "count": 1 - }, - { - "filePath": "mobile/src/components/pr-sidebar/PrSidebarCreateEmptyState.tsx", - "kind": "jsx-expression", - "text": "No open pull request", - "dynamic": false, - "count": 1 - }, - { - "filePath": "mobile/src/components/pr-sidebar/PrSidebarCreateEmptyState.tsx", - "kind": "jsx-expression", - "text": "Refresh to check again, or create a new PR for this branch.", - "dynamic": false, - "count": 1 - }, - { - "filePath": "mobile/src/components/pr-sidebar/PrSidebarCreateEmptyState.tsx", - "kind": "jsx-expression", - "text": "The current branch is not linked to an open PR.", - "dynamic": false, - "count": 1 - }, - { - "filePath": "mobile/src/components/pr-sidebar/PrSidebarCreateEmptyState.tsx", - "kind": "jsx-expression", - "text": "unavailable", - "dynamic": true, - "count": 1 - }, - { - "filePath": "mobile/src/components/pr-sidebar/PrSidebarCreateEmptyState.tsx", - "kind": "jsx-text", - "text": "Create PR", - "dynamic": false, - "count": 1 - }, - { - "filePath": "mobile/src/components/pr-sidebar/PrSidebarCreateEmptyState.tsx", - "kind": "jsx-text", - "text": "Link an existing PR", - "dynamic": false, - "count": 1 - }, - { - "filePath": "mobile/src/components/pr-sidebar/PrSidebarCreateEmptyState.tsx", - "kind": "jsx-text", - "text": "Pull request", - "dynamic": false, - "count": 1 - }, - { - "filePath": "mobile/src/components/pr-sidebar/PRSidebarHeader.tsx", - "kind": "jsx-attribute:accessibilityLabel", - "text": "Cancel editing title", - "dynamic": false, - "count": 1 - }, - { - "filePath": "mobile/src/components/pr-sidebar/PRSidebarHeader.tsx", - "kind": "jsx-attribute:accessibilityLabel", - "text": "Edit pull request title", - "dynamic": false, - "count": 1 - }, - { - "filePath": "mobile/src/components/pr-sidebar/PRSidebarHeader.tsx", - "kind": "jsx-attribute:accessibilityLabel", - "text": "in browser", - "dynamic": true, - "count": 1 - }, - { - "filePath": "mobile/src/components/pr-sidebar/PRSidebarHeader.tsx", - "kind": "jsx-attribute:accessibilityLabel", - "text": "on the web", - "dynamic": true, - "count": 2 - }, - { - "filePath": "mobile/src/components/pr-sidebar/PRSidebarHeader.tsx", - "kind": "jsx-attribute:accessibilityLabel", - "text": "Open pull request #", - "dynamic": true, - "count": 3 - }, - { - "filePath": "mobile/src/components/pr-sidebar/PRSidebarHeader.tsx", - "kind": "jsx-attribute:accessibilityLabel", - "text": "Save title", - "dynamic": false, - "count": 1 - }, - { - "filePath": "mobile/src/components/pr-sidebar/PRSidebarHeader.tsx", - "kind": "jsx-text", - "text": "Cancel", - "dynamic": false, - "count": 1 - }, - { - "filePath": "mobile/src/components/pr-sidebar/PRSidebarHeader.tsx", - "kind": "jsx-text", - "text": "Save", - "dynamic": false, - "count": 1 - }, - { - "filePath": "mobile/src/components/pr-sidebar/ReviewerPickerDrawer.tsx", - "kind": "jsx-attribute:placeholder", - "text": "Search people", - "dynamic": false, - "count": 1 - }, - { - "filePath": "mobile/src/components/pr-sidebar/ReviewerPickerDrawer.tsx", - "kind": "jsx-text", - "text": "No matching people", - "dynamic": false, - "count": 1 - }, - { - "filePath": "mobile/src/components/pr-sidebar/ReviewerPickerDrawer.tsx", - "kind": "jsx-text", - "text": "Reviewers", - "dynamic": false, - "count": 1 - }, - { - "filePath": "mobile/src/components/pr-sidebar/ReviewerPickerDrawer.tsx", - "kind": "object-property:message", - "text": "Failed to load people", - "dynamic": false, - "count": 1 - }, - { - "filePath": "mobile/src/components/ProtocolBlockScreen.tsx", - "kind": "jsx-text", - "text": "Back to hosts", - "dynamic": false, - "count": 1 - }, - { - "filePath": "mobile/src/components/ProtocolBlockScreen.tsx", - "kind": "object-property:label", - "text": "Get Orca", - "dynamic": true, - "count": 1 - }, - { - "filePath": "mobile/src/components/ProtocolBlockScreen.tsx", - "kind": "object-property:label", - "text": "Open App Store", - "dynamic": false, - "count": 1 - }, - { - "filePath": "mobile/src/components/ProtocolBlockScreen.tsx", - "kind": "object-property:label", - "text": "Open GitHub Releases", - "dynamic": false, - "count": 2 - }, - { - "filePath": "mobile/src/components/SetupHookTrustDrawer.tsx", - "kind": "jsx-expression", - "text": "'s setup script changed", - "dynamic": true, - "count": 1 - }, - { - "filePath": "mobile/src/components/SetupHookTrustDrawer.tsx", - "kind": "jsx-expression", - "text": "New setup script", - "dynamic": false, - "count": 1 - }, - { - "filePath": "mobile/src/components/SetupHookTrustDrawer.tsx", - "kind": "jsx-expression", - "text": "Run setup from", - "dynamic": true, - "count": 1 - }, - { - "filePath": "mobile/src/components/SetupHookTrustDrawer.tsx", - "kind": "jsx-expression", - "text": "Setup script", - "dynamic": false, - "count": 1 - }, - { - "filePath": "mobile/src/components/SetupHookTrustDrawer.tsx", - "kind": "jsx-text", - "text": "Always trust and run", - "dynamic": false, - "count": 1 - }, - { - "filePath": "mobile/src/components/SetupHookTrustDrawer.tsx", - "kind": "jsx-text", - "text": "Don't run", - "dynamic": false, - "count": 1 - }, - { - "filePath": "mobile/src/components/SetupHookTrustDrawer.tsx", - "kind": "jsx-text", - "text": "Run hooks", - "dynamic": false, - "count": 1 - }, - { - "filePath": "mobile/src/components/SetupHookTrustDrawer.tsx", - "kind": "jsx-text", - "text": "This repository's orca.yaml runs before the workspace starts. Only run it if you trust this repository.", - "dynamic": false, - "count": 1 - }, - { - "filePath": "mobile/src/components/SmartWorkspaceAdvancedFields.tsx", - "kind": "jsx-attribute:placeholder", - "text": "Derived from name", - "dynamic": false, - "count": 1 - }, - { - "filePath": "mobile/src/components/SmartWorkspaceAdvancedFields.tsx", - "kind": "jsx-attribute:placeholder", - "text": "Workspace name", - "dynamic": false, - "count": 1 - }, - { - "filePath": "mobile/src/components/SmartWorkspaceAdvancedFields.tsx", - "kind": "jsx-text", - "text": "Branch name", - "dynamic": false, - "count": 1 - }, - { - "filePath": "mobile/src/components/SmartWorkspaceAdvancedFields.tsx", - "kind": "jsx-text", - "text": "Name", - "dynamic": false, - "count": 1 - }, - { - "filePath": "mobile/src/components/SmartWorkspaceAdvancedFields.tsx", - "kind": "jsx-text", - "text": "Reuse branch “", - "dynamic": false, - "count": 1 - }, - { - "filePath": "mobile/src/components/SmartWorkspaceSourceDrawer.tsx", - "kind": "jsx-attribute:placeholder", - "text": "Type a name or search a source", - "dynamic": false, - "count": 1 - }, - { - "filePath": "mobile/src/components/SmartWorkspaceSourceDrawer.tsx", - "kind": "jsx-expression", - "text": "No results found.", - "dynamic": false, - "count": 1 - }, - { - "filePath": "mobile/src/components/SmartWorkspaceSourceDrawer.tsx", - "kind": "jsx-text", - "text": "Cancel", - "dynamic": false, - "count": 1 - }, - { - "filePath": "mobile/src/components/SmartWorkspaceSourceDrawer.tsx", - "kind": "jsx-text", - "text": "Connect the repository to search sources.", - "dynamic": false, - "count": 1 - }, - { - "filePath": "mobile/src/components/SmartWorkspaceSourceDrawer.tsx", - "kind": "jsx-text", - "text": "Done", - "dynamic": false, - "count": 1 - }, - { - "filePath": "mobile/src/components/SmartWorkspaceSourceDrawer.tsx", - "kind": "jsx-text", - "text": "Name or 'Create From'", - "dynamic": false, - "count": 1 - }, - { - "filePath": "mobile/src/components/SmartWorkspaceSourceDrawer.tsx", - "kind": "jsx-text", - "text": "Switch to", - "dynamic": false, - "count": 1 - }, - { - "filePath": "mobile/src/components/SmartWorkspaceSourceDrawer.tsx", - "kind": "jsx-text", - "text": "This item lives in", - "dynamic": false, - "count": 1 - }, - { - "filePath": "mobile/src/components/SmartWorkspaceSourceDrawer.tsx", - "kind": "jsx-text", - "text": "This SSH repo needs a GitHub remote to list issues and PRs.", - "dynamic": false, - "count": 1 - }, - { - "filePath": "mobile/src/components/SmartWorkspaceSourceDrawer.tsx", - "kind": "jsx-text", - "text": "Type a workspace name in the field below.", - "dynamic": false, - "count": 1 - }, - { - "filePath": "mobile/src/components/SmartWorkspaceSourceField.tsx", - "kind": "jsx-attribute:accessibilityLabel", - "text": "Open selected source", - "dynamic": false, - "count": 1 - }, - { - "filePath": "mobile/src/components/SmartWorkspaceSourceField.tsx", - "kind": "jsx-attribute:placeholder", - "text": "Type a name or search a source", - "dynamic": false, - "count": 1 - }, - { - "filePath": "mobile/src/components/SmartWorkspaceSourceField.tsx", - "kind": "jsx-text", - "text": "[Optional]", - "dynamic": false, - "count": 1 - }, - { - "filePath": "mobile/src/components/SmartWorkspaceSourceRow.tsx", - "kind": "object-property:subtitle", - "text": "Name this workspace", - "dynamic": false, - "count": 1 - }, - { - "filePath": "mobile/src/components/SmartWorkspaceSourceRow.tsx", - "kind": "object-property:subtitle", - "text": "New branch", - "dynamic": false, - "count": 1 - }, - { - "filePath": "mobile/src/components/SmartWorkspaceSourceRow.tsx", - "kind": "object-property:title", - "text": "Create branch \"", - "dynamic": true, - "count": 1 - }, - { - "filePath": "mobile/src/components/SmartWorkspaceSourceRow.tsx", - "kind": "object-property:title", - "text": "Use \"", - "dynamic": true, - "count": 1 - }, - { - "filePath": "mobile/src/components/TerminalShortcutSettings.tsx", - "kind": "jsx-attribute:accessibilityLabel", - "text": "Remove", - "dynamic": true, - "count": 1 - }, - { - "filePath": "mobile/src/components/TerminalShortcutSettings.tsx", - "kind": "jsx-text", - "text": "Add Custom Shortcut…", - "dynamic": false, - "count": 1 - }, - { - "filePath": "mobile/src/components/TerminalShortcutSettings.tsx", - "kind": "jsx-text", - "text": "Create key combo or text macro", - "dynamic": false, - "count": 1 - }, - { - "filePath": "mobile/src/components/TerminalShortcutSettings.tsx", - "kind": "jsx-text", - "text": "CUSTOM SHORTCUTS", - "dynamic": false, - "count": 1 - }, - { - "filePath": "mobile/src/components/TerminalShortcutSettings.tsx", - "kind": "jsx-text", - "text": "No custom shortcuts defined yet.", - "dynamic": false, - "count": 1 - }, - { - "filePath": "mobile/src/components/TerminalShortcutSettings.tsx", - "kind": "jsx-text", - "text": "Reset Defaults", - "dynamic": false, - "count": 1 - }, - { - "filePath": "mobile/src/components/TerminalShortcutSettings.tsx", - "kind": "jsx-text", - "text": "SHORTCUT BAR", - "dynamic": false, - "count": 1 - }, - { - "filePath": "mobile/src/components/TerminalShortcutSettings.tsx", - "kind": "jsx-text", - "text": "Show every built-in shortcut key in the original order", - "dynamic": false, - "count": 1 - }, - { - "filePath": "mobile/src/components/TerminalShortcutSettings.tsx", - "kind": "jsx-text", - "text": "Toggle keys to show or hide them, and hold the grip to drag a key into the order you want on the terminal shortcut bar.", - "dynamic": false, - "count": 1 - }, - { - "filePath": "mobile/src/components/TextInputModal.tsx", - "kind": "jsx-text", - "text": "Cancel", - "dynamic": false, - "count": 1 - }, - { - "filePath": "mobile/src/components/use-codex-reset-credit-action.ts", - "kind": "object-property:message", - "text": "and immediately resets eligible rate-limit windows.", - "dynamic": true, - "count": 1 - }, - { - "filePath": "mobile/src/components/use-codex-reset-credit-action.ts", - "kind": "object-property:message", - "text": "The account or reset offer changed before the host contacted Codex. Review the updated details, then confirm again.", - "dynamic": false, - "count": 1 - }, - { - "filePath": "mobile/src/components/use-codex-reset-credit-action.ts", - "kind": "object-property:message", - "text": "This spends one earned reset for", - "dynamic": true, - "count": 1 - }, - { - "filePath": "mobile/src/components/use-codex-reset-credit-action.ts", - "kind": "object-property:title", - "text": "Could not reset rate limits", - "dynamic": false, - "count": 1 - }, - { - "filePath": "mobile/src/components/use-codex-reset-credit-action.ts", - "kind": "object-property:title", - "text": "Reset details changed", - "dynamic": false, - "count": 1 - }, - { - "filePath": "mobile/src/components/use-codex-reset-credit-action.ts", - "kind": "object-property:title", - "text": "Use a rate-limit reset?", - "dynamic": false, - "count": 1 - }, - { - "filePath": "mobile/src/components/VoiceModelList.tsx", - "kind": "jsx-attribute:accessibilityLabel", - "text": "Delete", - "dynamic": true, - "count": 1 - }, - { - "filePath": "mobile/src/components/VoiceModelList.tsx", - "kind": "jsx-attribute:accessibilityLabel", - "text": "Download", - "dynamic": true, - "count": 1 - }, - { - "filePath": "mobile/src/components/VoiceModelList.tsx", - "kind": "jsx-attribute:accessibilityLabel", - "text": "Use", - "dynamic": true, - "count": 1 - }, - { - "filePath": "mobile/src/components/VoiceModelList.tsx", - "kind": "jsx-text", - "text": "In use", - "dynamic": false, - "count": 1 - }, - { - "filePath": "mobile/src/components/VoiceModelList.tsx", - "kind": "jsx-text", - "text": "Recommended", - "dynamic": false, - "count": 1 - }, - { - "filePath": "mobile/src/components/VoiceModelList.tsx", - "kind": "jsx-text", - "text": "Use", - "dynamic": false, - "count": 1 - }, - { - "filePath": "mobile/src/components/WorkspaceDetailPlaceholder.tsx", - "kind": "jsx-text", - "text": "No workspace open", - "dynamic": false, - "count": 1 - }, - { - "filePath": "mobile/src/components/WorkspaceDetailPlaceholder.tsx", - "kind": "jsx-text", - "text": "Pick a workspace from the sidebar to open its terminal here.", - "dynamic": false, - "count": 1 - }, - { - "filePath": "mobile/src/components/WorktreeListRow.tsx", - "kind": "jsx-attribute:accessibilityLabel", - "text": "Project", - "dynamic": true, - "count": 1 - }, - { - "filePath": "mobile/src/components/WorktreeListRow.tsx", - "kind": "jsx-attribute:accessibilityLabel", - "text": "Remote host", - "dynamic": false, - "count": 1 - }, - { - "filePath": "mobile/src/components/WorktreeListRow.tsx", - "kind": "jsx-expression", - "text": "child", - "dynamic": false, - "count": 1 - }, - { - "filePath": "mobile/src/components/WorktreeListRow.tsx", - "kind": "jsx-expression", - "text": "children", - "dynamic": false, - "count": 1 - }, - { - "filePath": "mobile/src/components/WorktreeListRow.tsx", - "kind": "jsx-text", - "text": "Child", - "dynamic": false, - "count": 1 - }, - { - "filePath": "mobile/src/components/WorktreeListRow.tsx", - "kind": "jsx-text", - "text": "Folder", - "dynamic": false, - "count": 1 - }, { "filePath": "mobile/src/diagnostics/connection-diagnostics-submission.ts", "kind": "object-property:error", diff --git a/mobile/src/accounts/MobileAccountsScreen.tsx b/mobile/src/accounts/MobileAccountsScreen.tsx index 1181a6f5794..91bcfc0e16c 100644 --- a/mobile/src/accounts/MobileAccountsScreen.tsx +++ b/mobile/src/accounts/MobileAccountsScreen.tsx @@ -11,10 +11,10 @@ import { useMobileTranslation } from '../i18n/use-mobile-translation' import { styles } from './mobile-accounts-screen-styles' import { useNow } from '../hooks/use-now' import { ClaudeIcon, OpenAIIcon } from '../components/AgentIcons' +import { AccountsSnapshotError, decodeAccountsSnapshot } from '../components/accounts-snapshot' import { type AccountsSnapshot, type ProviderKey, - decodeAccountsSnapshot, getActiveProviderRateLimits, getInactiveProviderUsage, getUsageBarState, @@ -142,7 +142,7 @@ export function MobileAccountsScreen() { setError(res.error.message) } } catch (e) { - if (e instanceof Error && e.message === 'Invalid accounts snapshot from host') { + if (e instanceof AccountsSnapshotError) { rejectInvalidSnapshot() } else { setError(e instanceof Error ? e.message : String(e)) diff --git a/mobile/src/components/AgentLaunchNotice.tsx b/mobile/src/components/AgentLaunchNotice.tsx index 6ddeed79074..804fd5cbb49 100644 --- a/mobile/src/components/AgentLaunchNotice.tsx +++ b/mobile/src/components/AgentLaunchNotice.tsx @@ -1,10 +1,7 @@ import { useState } from 'react' import { Pressable, Text, View, type StyleProp, type TextStyle } from 'react-native' import { useClipboardWriter } from '../platform/clipboard' -import { - AGENT_LAUNCH_STATUS_UNREADABLE_MESSAGE, - AGENT_LAUNCH_UPDATE_REQUIRED_MESSAGE -} from '../session/mobile-existing-agent-launch' +import { useMobileTranslation } from '../i18n/use-mobile-translation' import type { MobileAgentLaunchAvailability } from '../session/mobile-agent-launch-availability' import { colors, spacing, typography } from '../theme/mobile-theme' import { themedStyleSheet } from '../theme/themed-styles' @@ -31,14 +28,24 @@ export function AgentLaunchNotice({ undeliveredPrompt, errorStyle }: Props) { + const t = useMobileTranslation() useMobileColorScheme() const clipboard = useClipboardWriter() - const [copyState, setCopyState] = useState<{ prompt: string; label: string } | null>(null) + const [copyState, setCopyState] = useState<{ + prompt: string + status: 'copied' | 'failed' + } | null>(null) const availabilityMessage = availability === 'update-required' - ? AGENT_LAUNCH_UPDATE_REQUIRED_MESSAGE + ? t( + 'components.agentLaunchNotice.updateRequired', + 'Update Orca on your computer to start an agent from your phone.' + ) : availability === 'unverified' - ? AGENT_LAUNCH_STATUS_UNREADABLE_MESSAGE + ? t( + 'components.agentLaunchNotice.statusUnreadable', + "Could not read this host's status. Retrying…" + ) : null const message = availabilityMessage ?? error const note = availabilityMessage ? null : warning @@ -47,7 +54,11 @@ export function AgentLaunchNotice({ return null } const copyLabel = - copyState && copyState.prompt === undeliveredPrompt ? copyState.label : 'Copy prompt' + copyState && copyState.prompt === undeliveredPrompt + ? copyState.status === 'copied' + ? t('common.copied', 'Copied') + : t('components.agentLaunchNotice.copyFailed', "Couldn't copy") + : t('components.agentLaunchNotice.copyPrompt', 'Copy prompt') return ( {confirmation ? {confirmation} : null} @@ -57,12 +68,12 @@ export function AgentLaunchNotice({ { clipboard.writeText(undeliveredPrompt).then( - () => setCopyState({ prompt: undeliveredPrompt, label: 'Copied' }), - () => setCopyState({ prompt: undeliveredPrompt, label: "Couldn't copy" }) + () => setCopyState({ prompt: undeliveredPrompt, status: 'copied' }), + () => setCopyState({ prompt: undeliveredPrompt, status: 'failed' }) ) }} accessibilityRole="button" - accessibilityLabel="Copy prompt" + accessibilityLabel={t('components.agentLaunchNotice.copyPrompt', 'Copy prompt')} hitSlop={spacing.sm} > {copyLabel} diff --git a/mobile/src/components/AgentSpinner.tsx b/mobile/src/components/AgentSpinner.tsx index 23c0370c2e4..84788b6bbce 100644 --- a/mobile/src/components/AgentSpinner.tsx +++ b/mobile/src/components/AgentSpinner.tsx @@ -2,6 +2,7 @@ import { Activity } from 'lucide-react-native' import { Animated, View } from 'react-native' import type { AgentWorkingMode } from '../../../src/shared/agent-status-types' import type { WorktreeStatus } from '../../../src/shared/worktree-status-rollup' +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' @@ -27,6 +28,7 @@ export function AgentSpinner({ status: WorktreeStatus workingMode?: AgentWorkingMode }) { + const t = useMobileTranslation() useMobileColorScheme() const monitoring = status === 'monitoring' || (status === 'working' && workingMode === 'monitoring') @@ -37,7 +39,10 @@ export function AgentSpinner({ if (monitoring) { return ( - + ) @@ -45,7 +50,10 @@ export function AgentSpinner({ if (status === 'unconfirmed') { return ( - + ) diff --git a/mobile/src/components/AgentStateDot.tsx b/mobile/src/components/AgentStateDot.tsx index 28cac5f1a99..c1490ef0d7b 100644 --- a/mobile/src/components/AgentStateDot.tsx +++ b/mobile/src/components/AgentStateDot.tsx @@ -1,6 +1,7 @@ import { Activity } from 'lucide-react-native' import { Animated, View } from 'react-native' import type { AgentDotState } from '../worktree/agent-row-display' +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' @@ -25,6 +26,7 @@ export const AGENT_WORKING_COLOR = '#eab308' /** `unverifiable` is a child row's lost contact: desktop's dashed amber ring, a missing-evidence * mark rather than a state claim. */ export function AgentStateDot({ state }: { state: AgentDotState | 'unverifiable' }) { + const t = useMobileTranslation() useMobileColorScheme() const rotate = useWorkingRingRotation(state === 'working') @@ -38,7 +40,10 @@ export function AgentStateDot({ state }: { state: AgentDotState | 'unverifiable' if (state === 'monitoring') { return ( - + ) @@ -46,7 +51,10 @@ export function AgentStateDot({ state }: { state: AgentDotState | 'unverifiable' if (state === 'unverifiable') { return ( - + ) diff --git a/mobile/src/components/AuthFailedBanner.tsx b/mobile/src/components/AuthFailedBanner.tsx index b1552013834..9005b8d6ecb 100644 --- a/mobile/src/components/AuthFailedBanner.tsx +++ b/mobile/src/components/AuthFailedBanner.tsx @@ -1,4 +1,5 @@ import { View, Text } from 'react-native' +import { useMobileTranslation } from '../i18n/use-mobile-translation' import { AuthFailedBannerActions } from './AuthFailedBannerActions' import { authFailedBannerStyles as styles } from './auth-failed-banner-styles' import { useMobileColorScheme } from '../theme/use-mobile-color-scheme' @@ -15,11 +16,15 @@ export function AuthFailedBanner({ onRepair: () => void onRemove: () => void }) { + const t = useMobileTranslation() useMobileColorScheme() return ( - Authentication failed — try reconnecting first; if it keeps failing, re-pair from desktop. + {t( + 'components.authFailedBanner.message', + 'Authentication failed — try reconnecting first; if it keeps failing, re-pair from desktop.' + )} void onRemove: () => void }) { + const t = useMobileTranslation() useMobileColorScheme() return ( <> {canRetry && ( - Retry + {t('common.retry', 'Retry')} )} - Re-pair + + {t('components.authFailedBannerActions.repair', 'Re-pair')} + - Remove + + {t('common.remove', 'Remove')} + ) diff --git a/mobile/src/components/AuthFailedBannerActions.web.tsx b/mobile/src/components/AuthFailedBannerActions.web.tsx index 0c04cd69f25..312ea488852 100644 --- a/mobile/src/components/AuthFailedBannerActions.web.tsx +++ b/mobile/src/components/AuthFailedBannerActions.web.tsx @@ -1,4 +1,5 @@ import { Pressable, Text } from 'react-native' +import { useMobileTranslation } from '../i18n/use-mobile-translation' import { authFailedBannerStyles as styles } from './auth-failed-banner-styles' import { useMobileColorScheme } from '../theme/use-mobile-color-scheme' @@ -11,13 +12,21 @@ export function AuthFailedBannerActions({ onRepair: () => void onRemove: () => void }) { + const t = useMobileTranslation() useMobileColorScheme() return ( <> - Re-pair + + {t('components.authFailedBannerActions.repair', 'Re-pair')} + - Reconnect or remove this host from the Orca app. + + {t( + 'components.authFailedBannerActions.webNote', + 'Reconnect or remove this host from the Orca app.' + )} + ) } diff --git a/mobile/src/components/CodexResetCreditAction.tsx b/mobile/src/components/CodexResetCreditAction.tsx index 436ba12708d..87e964dea7e 100644 --- a/mobile/src/components/CodexResetCreditAction.tsx +++ b/mobile/src/components/CodexResetCreditAction.tsx @@ -1,5 +1,6 @@ import { ActivityIndicator, Pressable, StyleSheet, Text, View } from 'react-native' import { RotateCcw } from 'lucide-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' @@ -18,6 +19,7 @@ export function CodexResetCreditAction({ disabled: boolean onPress: () => void }) { + const t = useMobileTranslation() useMobileColorScheme() return ( <> @@ -27,7 +29,7 @@ export function CodexResetCreditAction({ {summary.availabilityLabel} {[summary.expiryLabel, scopeLabel].filter(Boolean).join(' · ') || - 'Earned Codex rate-limit reset'} + t('components.codexResetCreditAction.earnedReset', 'Earned Codex rate-limit reset')} )} - {busy ? 'Resetting…' : 'Use reset'} + + {busy + ? t('components.codexResetCreditAction.resetting', 'Resetting…') + : t('components.codexResetCreditAction.useReset', 'Use reset')} + diff --git a/mobile/src/components/ConfirmModal.tsx b/mobile/src/components/ConfirmModal.tsx index 7254d0f3759..d0baee3ff4d 100644 --- a/mobile/src/components/ConfirmModal.tsx +++ b/mobile/src/components/ConfirmModal.tsx @@ -1,4 +1,5 @@ import { View, Text, Pressable } from 'react-native' +import { useMobileTranslation } from '../i18n/use-mobile-translation' import { colors, spacing, radii, typography } from '../theme/mobile-theme' import { themedStyleSheet } from '../theme/themed-styles' import { useMobileColorScheme } from '../theme/use-mobile-color-scheme' @@ -29,12 +30,13 @@ export function ConfirmModal({ visible, ...content }: Props) { export function ConfirmContent({ title, message, - confirmLabel = 'Confirm', - cancelLabel = 'Cancel', + confirmLabel, + cancelLabel, destructive = false, onConfirm, onCancel }: ContentProps) { + const t = useMobileTranslation() useMobileColorScheme() return ( <> @@ -48,7 +50,7 @@ export function ConfirmContent({ style={({ pressed }) => [styles.button, styles.cancelButton, pressed && styles.pressed]} onPress={onCancel} > - {cancelLabel} + {cancelLabel ?? t('common.cancel', 'Cancel')} )} - {confirmLabel} + {confirmLabel ?? t('common.confirm', 'Confirm')} diff --git a/mobile/src/components/CustomKeyModal.tsx b/mobile/src/components/CustomKeyModal.tsx index f647b7acd76..fce1ab1a646 100644 --- a/mobile/src/components/CustomKeyModal.tsx +++ b/mobile/src/components/CustomKeyModal.tsx @@ -13,6 +13,8 @@ import { } from '../terminal/terminal-accessory-keys' import { customKeyModalStyles as styles } from './CustomKeyModal.styles' import { updateStoredCustomKeys, type CustomKey } from '../terminal/terminal-custom-keys' +import type { Translate } from '../i18n/mobile-i18n' +import { useMobileTranslation } from '../i18n/use-mobile-translation' type Step = 'choose-type' | 'shortcut-combo' | 'special-keys' | 'text-macro' @@ -24,23 +26,28 @@ const SHORTCUT_MODIFIERS: { id: TerminalShortcutModifier; label: string; glyph?: ] // Group keys into three fixed grids; ragged wrapping clipped F7–F12. -const SPECIAL_KEY_GROUPS: { title: string; ids: string[]; columns: number }[] = [ - { - title: 'Editing', - ids: ['escape', 'tab', 'enter', 'backspace', 'delete', 'insert', 'space'], - columns: 4 - }, - { - title: 'Navigation', - ids: ['arrowUp', 'arrowDown', 'arrowLeft', 'arrowRight', 'home', 'end', 'pageUp', 'pageDown'], - columns: 4 - }, - { - title: 'Function', - ids: ['f1', 'f2', 'f3', 'f4', 'f5', 'f6', 'f7', 'f8', 'f9', 'f10', 'f11', 'f12'], - columns: 6 - } -] +function specialKeyGroups(t: Translate) { + return [ + { + id: 'editing', + title: t('components.customKeyModal.editing', 'Editing'), + ids: ['escape', 'tab', 'enter', 'backspace', 'delete', 'insert', 'space'], + columns: 4 + }, + { + id: 'navigation', + title: t('components.customKeyModal.navigation', 'Navigation'), + ids: ['arrowUp', 'arrowDown', 'arrowLeft', 'arrowRight', 'home', 'end', 'pageUp', 'pageDown'], + columns: 4 + }, + { + id: 'function', + title: t('components.customKeyModal.function', 'Function'), + ids: ['f1', 'f2', 'f3', 'f4', 'f5', 'f6', 'f7', 'f8', 'f9', 'f10', 'f11', 'f12'], + columns: 6 + } + ] +} const SPECIAL_KEY_BY_ID: Record = Object.fromEntries( TERMINAL_SHORTCUT_SPECIAL_KEYS.map((key) => [key.id, key]) @@ -55,6 +62,7 @@ type Props = { export function CustomKeyModal({ visible, onClose, onKeysChanged, onManageShortcuts }: Props) { useMobileColorScheme() + const t = useMobileTranslation() const [step, setStep] = useState('choose-type') const [shortcutKey, setShortcutKey] = useState('c') const [shortcutModifiers, setShortcutModifiers] = useState(['ctrl']) @@ -173,7 +181,7 @@ export function CustomKeyModal({ visible, onClose, onKeysChanged, onManageShortc style={({ pressed }) => [styles.backButton, pressed && styles.backButtonPressed]} onPress={onBack} accessibilityRole="button" - accessibilityLabel="Back" + accessibilityLabel={t('common.back', 'Back')} > @@ -181,10 +189,11 @@ export function CustomKeyModal({ visible, onClose, onKeysChanged, onManageShortc )} - {step === 'choose-type' && 'Add Shortcut'} - {step === 'shortcut-combo' && 'Shortcut Combo'} - {step === 'special-keys' && 'Pick a key'} - {step === 'text-macro' && 'Text Macro'} + {step === 'choose-type' && t('components.customKeyModal.addShortcut', 'Add Shortcut')} + {step === 'shortcut-combo' && + t('components.customKeyModal.shortcutCombo', 'Shortcut Combo')} + {step === 'special-keys' && t('components.customKeyModal.pickKey', 'Pick a key')} + {step === 'text-macro' && t('components.customKeyModal.textMacro', 'Text Macro')} @@ -195,16 +204,27 @@ export function CustomKeyModal({ visible, onClose, onKeysChanged, onManageShortc style={({ pressed }) => [styles.row, pressed && styles.rowPressed]} onPress={() => setStep('shortcut-combo')} > - Shortcut Combo - Build Ctrl, Alt, and Shift key chords + + {t('components.customKeyModal.shortcutCombo', 'Shortcut Combo')} + + + {t( + 'components.customKeyModal.shortcutComboHint', + 'Build Ctrl, Alt, and Shift key chords' + )} + [styles.row, pressed && styles.rowPressed]} onPress={() => setStep('text-macro')} > - Text Macro - Send custom text command + + {t('components.customKeyModal.textMacro', 'Text Macro')} + + + {t('components.customKeyModal.textMacroHint', 'Send custom text command')} + {onManageShortcuts ? ( <> @@ -213,8 +233,15 @@ export function CustomKeyModal({ visible, onClose, onKeysChanged, onManageShortc style={({ pressed }) => [styles.row, pressed && styles.rowPressed]} onPress={onManageShortcuts} > - Manage Shortcuts - Show, hide, or reorder shortcut keys + + {t('components.customKeyModal.manageShortcuts', 'Manage Shortcuts')} + + + {t( + 'components.customKeyModal.manageShortcutsHint', + 'Show, hide, or reorder shortcut keys' + )} + ) : null} @@ -241,7 +268,9 @@ export function CustomKeyModal({ visible, onClose, onKeysChanged, onManageShortc - Modifiers + + {t('components.customKeyModal.modifiers', 'Modifiers')} + {SHORTCUT_MODIFIERS.map((modifier) => { const selected = shortcutModifiers.includes(modifier.id) @@ -272,7 +301,7 @@ export function CustomKeyModal({ visible, onClose, onKeysChanged, onManageShortc - Key + {t('components.customKeyModal.key', 'Key')} [styles.moreLink, pressed && styles.moreLinkPressed]} onPress={() => setStep('special-keys')} > - More keys — Tab, arrows, F1–F12… + + {t('components.customKeyModal.moreKeys', 'More keys — Tab, arrows, F1–F12…')} + @@ -299,7 +330,7 @@ export function CustomKeyModal({ visible, onClose, onKeysChanged, onManageShortc - Add + {t('components.customKeyModal.add', 'Add')} @@ -307,8 +338,8 @@ export function CustomKeyModal({ visible, onClose, onKeysChanged, onManageShortc {step === 'special-keys' && ( - {SPECIAL_KEY_GROUPS.map((group) => ( - + {specialKeyGroups(t).map((group) => ( + {group.title} {group.ids.map((id) => { @@ -346,28 +377,32 @@ export function CustomKeyModal({ visible, onClose, onKeysChanged, onManageShortc {step === 'text-macro' && ( - Label + {t('components.customKeyModal.label', 'Label')} - Command + + {t('components.customKeyModal.command', 'Command')} + - Press Enter + + {t('components.customKeyModal.pressEnter', 'Press Enter')} + - Add Shortcut + {t('components.customKeyModal.addShortcut', 'Add Shortcut')} diff --git a/mobile/src/components/DragReorderRow.tsx b/mobile/src/components/DragReorderRow.tsx index 2619b8d807b..53c313dcf74 100644 --- a/mobile/src/components/DragReorderRow.tsx +++ b/mobile/src/components/DragReorderRow.tsx @@ -14,6 +14,7 @@ import Animated, { 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' import { triggerMediumImpact } from '../platform/haptics' import { type DragReorderLayout, @@ -69,6 +70,7 @@ export function DragReorderRow({ renderContent: (handle: ReactNode) => ReactNode }): React.JSX.Element { useMobileColorScheme() + const t = useMobileTranslation() const { layout, initialPositions, @@ -183,11 +185,14 @@ export function DragReorderRow({ accessible accessibilityRole="button" accessibilityState={{ disabled }} - accessibilityLabel="Drag to reorder" - accessibilityHint="Use the move up and move down actions to reorder without dragging" + accessibilityLabel={t('components.dragReorderRow.dragToReorder', 'Drag to reorder')} + accessibilityHint={t( + 'components.dragReorderRow.reorderHint', + 'Use the move up and move down actions to reorder without dragging' + )} accessibilityActions={[ - { name: 'moveUp', label: 'Move up' }, - { name: 'moveDown', label: 'Move down' } + { name: 'moveUp', label: t('components.dragReorderRow.moveUp', 'Move up') }, + { name: 'moveDown', label: t('components.dragReorderRow.moveDown', 'Move down') } ]} onAccessibilityAction={(event) => { if (disabled) { diff --git a/mobile/src/components/HostDiagnosticsLink.tsx b/mobile/src/components/HostDiagnosticsLink.tsx index 6e90fca2633..ef1ec17625c 100644 --- a/mobile/src/components/HostDiagnosticsLink.tsx +++ b/mobile/src/components/HostDiagnosticsLink.tsx @@ -3,17 +3,24 @@ import { Pressable, Text } 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' export function HostDiagnosticsLink({ onPress }: { onPress: () => void }): React.JSX.Element { useMobileColorScheme() + const t = useMobileTranslation() return ( - View network diagnostics + + {t('components.hostDiagnosticsLink.viewDiagnostics', 'View network diagnostics')} + ) diff --git a/mobile/src/components/HostProtocolGate.tsx b/mobile/src/components/HostProtocolGate.tsx index 54ac9fe99d4..4dc5ae0ca28 100644 --- a/mobile/src/components/HostProtocolGate.tsx +++ b/mobile/src/components/HostProtocolGate.tsx @@ -5,6 +5,7 @@ import { useHostStatusGates } from '../transport/host-status-gates' import { colors } 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 { HostStatusGatesContext } from './host-protocol-gates-context' import { ProtocolBlockScreen } from './ProtocolBlockScreen' @@ -25,6 +26,7 @@ export function HostProtocolGate({ blocked: blockedView }: Props) { useMobileColorScheme() + const t = useMobileTranslation() const { client, state } = useHostClient(hostId) const gates = useHostStatusGates({ hostId, client, connState: state }) const { compatVerdict, statusPending } = gates @@ -59,7 +61,10 @@ export function HostProtocolGate({ ) @@ -89,7 +94,10 @@ export function HostProtocolGate({ > ) : null} diff --git a/mobile/src/components/HostRouteNoticeBanner.tsx b/mobile/src/components/HostRouteNoticeBanner.tsx index 25817fe5f1b..aa2837d0b81 100644 --- a/mobile/src/components/HostRouteNoticeBanner.tsx +++ b/mobile/src/components/HostRouteNoticeBanner.tsx @@ -3,6 +3,7 @@ import { X } from 'lucide-react-native' 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' // Monochrome notice reports vanished target; failure uses one red rule for action refusal without taking screen. // Announce inserted banners: failure uses alert/assertive, notice uses polite live region (RN has no status role). @@ -16,6 +17,7 @@ export function HostRouteNoticeBanner({ onDismiss: () => void }) { useMobileColorScheme() + const t = useMobileTranslation() return ( diff --git a/mobile/src/components/JiraConnectPrompt.tsx b/mobile/src/components/JiraConnectPrompt.tsx index 170e4318bcb..d28dbda4ac8 100644 --- a/mobile/src/components/JiraConnectPrompt.tsx +++ b/mobile/src/components/JiraConnectPrompt.tsx @@ -2,6 +2,7 @@ import { Pressable, 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 { TaskProviderLogo } from './TaskProviderLogo' type Props = { @@ -19,17 +20,27 @@ export function JiraConnectPrompt({ onCheckAgain }: Props) { useMobileColorScheme() + const t = useMobileTranslation() return ( - {unavailableReason ? 'Update Orca desktop' : 'Connect Jira on your desktop'} + {unavailableReason + ? t('components.jiraConnectPrompt.updateDesktop', 'Update Orca desktop') + : t('components.jiraConnectPrompt.connectOnDesktop', 'Connect Jira on your desktop')} {unavailableReason ?? (credentialError - ? `Jira is saved but unreadable: ${credentialError}` - : 'Add your Jira site in Orca desktop under Settings → Integrations, then check again.')} + ? t( + 'components.jiraConnectPrompt.credentialUnreadable', + 'Jira is saved but unreadable: {{credentialError}}', + { credentialError } + ) + : t( + 'components.jiraConnectPrompt.connectHint', + 'Add your Jira site in Orca desktop under Settings → Integrations, then check again.' + ))} - Check again + + {t('components.jiraConnectPrompt.checkAgain', 'Check again')} + ) diff --git a/mobile/src/components/MobileDictationSetupSheet.tsx b/mobile/src/components/MobileDictationSetupSheet.tsx index 3afa817c5f2..f9762fba214 100644 --- a/mobile/src/components/MobileDictationSetupSheet.tsx +++ b/mobile/src/components/MobileDictationSetupSheet.tsx @@ -6,6 +6,9 @@ import { MobileSwitch } from './MobileSwitch' 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 { getUiIntlLocale } from '../i18n/ui-intl-locale' import type { RpcClient } from '../transport/rpc-client' import { triggerError, triggerSuccess } from '../platform/haptics' import { useDictationSetupPoller } from '../dictation/use-dictation-setup-poller' @@ -30,18 +33,52 @@ type Props = { onReady?: () => void } -function formatSize(bytes: number | null | undefined): string { +type SetupError = { message: string } | { kind: 'load' | 'download' | 'select' | 'update' } + +function setupErrorMessage(error: SetupError, t: Translate): string { + if ('message' in error) { + return error.message + } + switch (error.kind) { + case 'load': + return t('components.dictationSetupSheet.loadFailed', 'Failed to load') + case 'download': + return t('components.dictationSetupSheet.downloadFailed', 'Download failed') + case 'select': + return t('components.dictationSetupSheet.selectFailed', 'Could not select model') + case 'update': + return t('components.dictationSetupSheet.updateFailed', 'Could not update') + } +} + +function formatSize(bytes: number | null | undefined, t: Translate): string { if (!bytes) { return '' } - return `${Math.round(bytes / 1_000_000)} MB` + return t('components.dictationSetupSheet.modelSize', '{{size}} MB', { + size: Math.round(bytes / 1_000_000).toLocaleString(getUiIntlLocale()) + }) +} + +function modelMetadata(model: MobileSpeechModel, t: Translate): string { + const details = model.provider === 'openai' ? 'OpenAI API' : formatSize(model.sizeBytes, t) + if (isModelInFlight(model) && model.progress != null) { + return t('components.dictationSetupSheet.modelProgress', '{{details}} · {{progress}}%', { + details, + progress: Math.round(model.progress * 100).toLocaleString(getUiIntlLocale()) + }) + } + return model.status === 'extracting' + ? t('components.dictationSetupSheet.modelExtracting', '{{details}} · extracting…', { details }) + : details } // Configure paired computer’s dictation/model from phone; poll while download is in flight. export function MobileDictationSetupSheet({ visible, client, onClose, onReady }: Props) { useMobileColorScheme() + const t = useMobileTranslation() const [setup, setSetup] = useState(null) - const [error, setError] = useState(null) + const [error, setError] = useState(null) const [busy, setBusy] = useState(null) const refresh = useCallback(async (): Promise => { if (!client) { @@ -53,7 +90,7 @@ export function MobileDictationSetupSheet({ visible, client, onClose, onReady }: setError(null) return next.models.some(isModelInFlight) } catch (err) { - setError(err instanceof Error ? err.message : 'Failed to load') + setError(err instanceof Error ? { message: err.message } : { kind: 'load' }) return undefined } }, [client]) @@ -84,7 +121,7 @@ export function MobileDictationSetupSheet({ visible, client, onClose, onReady }: await refreshSetup() } catch (err) { triggerError() - setError(err instanceof Error ? err.message : 'Download failed') + setError(err instanceof Error ? { message: err.message } : { kind: 'download' }) } finally { setBusy(null) } @@ -106,7 +143,7 @@ export function MobileDictationSetupSheet({ visible, client, onClose, onReady }: onReady?.() } catch (err) { triggerError() - setError(err instanceof Error ? err.message : 'Could not select model') + setError(err instanceof Error ? { message: err.message } : { kind: 'select' }) } finally { setBusy(null) } @@ -123,7 +160,7 @@ export function MobileDictationSetupSheet({ visible, client, onClose, onReady }: try { setSetup(await setDictationConfig(client, { enabled })) } catch (err) { - setError(err instanceof Error ? err.message : 'Could not update') + setError(err instanceof Error ? { message: err.message } : { kind: 'update' }) } }, [client] @@ -133,9 +170,14 @@ export function MobileDictationSetupSheet({ visible, client, onClose, onReady }: {/* BottomDrawer already scrolls above keyboard; nested capped ScrollView clipped lower controls. */} - Set up voice dictation + + {t('components.dictationSetupSheet.title', 'Set up voice dictation')} + - Download or pick a model and enable dictation on the paired computer — all from here. + {t( + 'components.dictationSetupSheet.description', + 'Download or pick a model and enable dictation on the paired computer — all from here.' + )} {setup === null ? ( @@ -145,16 +187,20 @@ export function MobileDictationSetupSheet({ visible, client, onClose, onReady }: ) : ( <> - Dictation enabled + + {t('components.dictationSetupSheet.enabled', 'Dictation enabled')} + void handleToggleEnabled(v)} /> {setup.models.map((model) => { - const inFlight = isModelInFlight(model) const action = dictationModelRowAction(model, setup.selectedModelId) const rowBusy = busy === model.id return ( @@ -163,24 +209,21 @@ export function MobileDictationSetupSheet({ visible, client, onClose, onReady }: {model.label} {model.recommended ? ( - Recommended + + {t('components.dictationSetupSheet.recommended', 'Recommended')} + ) : null} - - {model.provider === 'openai' ? 'OpenAI API' : formatSize(model.sizeBytes)} - {inFlight && model.progress != null - ? ` · ${Math.round(model.progress * 100)}%` - : model.status === 'extracting' - ? ' · extracting…' - : ''} - + {modelMetadata(model, t)} {action === 'needs-api-key' ? ( {cloudModelNeedsKey()} ) : action === 'in-use' ? ( - In use + + {t('components.dictationSetupSheet.inUse', 'In use')} + ) : action === 'use' ? ( void handleUseModel(model)} > - Use + + {t('components.dictationSetupSheet.use', 'Use')} + ) : action === 'downloading' ? ( @@ -206,7 +255,11 @@ export function MobileDictationSetupSheet({ visible, client, onClose, onReady }: ]} disabled={rowBusy} accessibilityRole="button" - accessibilityLabel={`Download ${model.label}`} + accessibilityLabel={t( + 'components.dictationSetupSheet.downloadModel', + 'Download {{model}}', + { model: model.label } + )} accessibilityState={{ disabled: rowBusy }} onPress={() => void handleDownload(model)} > @@ -215,7 +268,9 @@ export function MobileDictationSetupSheet({ visible, client, onClose, onReady }: ) : ( <> - Download + + {t('components.dictationSetupSheet.download', 'Download')} + )} @@ -225,7 +280,7 @@ export function MobileDictationSetupSheet({ visible, client, onClose, onReady }: })} )} - {error ? {error} : null} + {error ? {setupErrorMessage(error, t)} : null} ) diff --git a/mobile/src/components/MobileDiffReviewBody.tsx b/mobile/src/components/MobileDiffReviewBody.tsx index 43ccf066799..46bc70dda67 100644 --- a/mobile/src/components/MobileDiffReviewBody.tsx +++ b/mobile/src/components/MobileDiffReviewBody.tsx @@ -4,6 +4,7 @@ import type { RefObject } from 'react' import type { DiffComment } from '../../../src/shared/diff-comment-types' import { colors } from '../theme/mobile-theme' import { useMobileColorScheme } from '../theme/use-mobile-color-scheme' +import { useMobileTranslation } from '../i18n/use-mobile-translation' import { MobileDiffReviewLine } from './MobileDiffReviewLine' import type { ReviewDiffLine, @@ -41,23 +42,44 @@ export function MobileDiffReviewBody({ onRetry }: Props) { useMobileColorScheme() + const t = useMobileTranslation() if (screenState.kind === 'loading') { - return + return ( + + ) } if (screenState.kind === 'error' || screenState.kind === 'unavailable') { return ( ) } if (filteredCount === 0) { - return + return ( + + ) } if (diffState.kind === 'loading') { - return + return ( + + ) } if (diffState.kind !== 'ready') { return @@ -93,7 +115,9 @@ export function MobileDiffReviewBody({ }} ListFooterComponent={ diffState.truncated ? ( - Diff truncated for mobile preview. + + {t('components.diffReviewBody.diffTruncated', 'Diff truncated for mobile preview.')} + ) : null } /> @@ -107,24 +131,34 @@ function DiffUnavailableState({ diffState: ReviewDiffState onRetry: (() => void) | undefined }) { + const t = useMobileTranslation() const title = diffState.kind === 'binary' - ? 'Binary Diff' + ? t('components.diffReviewBody.binaryDiff', 'Binary Diff') : diffState.kind === 'too-large' - ? 'Diff Too Large' + ? t('components.diffReviewBody.diffTooLarge', 'Diff Too Large') : diffState.kind === 'deleted' - ? 'Deleted File' - : 'Diff Unavailable' + ? t('components.diffReviewBody.deletedFile', 'Deleted File') + : t('components.diffReviewBody.diffUnavailable', 'Diff Unavailable') const text = diffState.kind === 'binary' - ? 'This file cannot be rendered as text on mobile.' + ? t( + 'components.diffReviewBody.binaryMessage', + 'This file cannot be rendered as text on mobile.' + ) : diffState.kind === 'too-large' - ? 'This diff is too large for the mobile preview.' + ? t( + 'components.diffReviewBody.tooLargeMessage', + 'This diff is too large for the mobile preview.' + ) : diffState.kind === 'deleted' - ? 'This file was deleted. Add a file note or mark it reviewed.' + ? t( + 'components.diffReviewBody.deletedMessage', + 'This file was deleted. Add a file note or mark it reviewed.' + ) : diffState.kind === 'error' ? diffState.message - : 'Select a file to review.' + : t('components.diffReviewBody.selectFile', 'Select a file to review.') return } @@ -142,6 +176,7 @@ function CenteredState({ onRetry?: () => void }) { useMobileColorScheme() + const t = useMobileTranslation() return ( {busy ? ( @@ -154,10 +189,10 @@ function CenteredState({ style={({ pressed }) => [styles.retryButton, pressed && styles.buttonPressed]} onPress={onRetry} accessibilityRole="button" - accessibilityLabel="Retry loading review" + accessibilityLabel={t('components.diffReviewBody.retryLabel', 'Retry loading review')} > - Retry + {t('common.retry', 'Retry')} ) : null} diff --git a/mobile/src/components/MobileDiffReviewDrawers.tsx b/mobile/src/components/MobileDiffReviewDrawers.tsx index 99c2dc8a9e3..8812aa9249f 100644 --- a/mobile/src/components/MobileDiffReviewDrawers.tsx +++ b/mobile/src/components/MobileDiffReviewDrawers.tsx @@ -4,16 +4,13 @@ import { Check, Copy, FileText, Plus, Send, Trash2, X } from 'lucide-react-nativ 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 { useMobileTranslation } from '../i18n/use-mobile-translation' import type { ActionSheetAction } from './ActionSheetModal' import { ActionSheetContent } from './ActionSheetModal' import { ConfirmContent } from './ConfirmModal' import { KeyedBottomDrawer } from './keyed-bottom-drawer' -import { AGENT_LAUNCH_STATUS_UNREADABLE_MESSAGE } from '../session/mobile-existing-agent-launch' -import { - mobileReviewCountLabel, - type ComposerState, - type SendSheetState -} from '../session/mobile-diff-review-screen-model' +import type { ComposerState, SendSheetState } from '../session/mobile-diff-review-screen-model' import { reviewSheetKey, type ReviewSheet } from '../session/mobile-diff-review-sheets' import type { useMobileDiffReviewController } from '../session/use-mobile-diff-review-controller' import { mobileDiffReviewStyles as styles } from './mobile-diff-review-screen-styles' @@ -38,6 +35,7 @@ export function MobileDiffReviewDrawers({ controller }: Props) { } function ReviewSheetContent({ controller, sheet }: Props & { sheet: ReviewSheet }) { + const t = useMobileTranslation() switch (sheet.kind) { case 'actions': return @@ -46,9 +44,13 @@ function ReviewSheetContent({ controller, sheet }: Props & { sheet: ReviewSheet case 'discard': return ( { controller.closeSheet('discard') @@ -65,13 +67,25 @@ function ReviewSheetContent({ controller, sheet }: Props & { sheet: ReviewSheet } function ReviewActionsContent({ controller }: Props) { + const t = useMobileTranslation() const overflowActions = useOverflowActions(controller) + const count = controller.reviewedUnstagedCount return ( 0 - ? `${controller.reviewedUnstagedCount} reviewed unstaged files can be staged` + count > 0 + ? count === 1 + ? t( + 'components.diffReviewDrawers.stageableFiles_one', + '{{count}} reviewed unstaged files can be staged', + { count } + ) + : t( + 'components.diffReviewDrawers.stageableFiles_other', + '{{count}} reviewed unstaged files can be staged', + { count } + ) : undefined } actions={overflowActions} @@ -81,11 +95,12 @@ function ReviewActionsContent({ controller }: Props) { } function SendNotesContent({ controller, load }: Props & { load: SendSheetState }) { + const t = useMobileTranslation() const sendActions = useSendActions(controller, load) return ( controller.closeSheet('send')} /> @@ -96,12 +111,16 @@ function useSendActions( controller: ReturnType, load: SendSheetState ) { + const t = useMobileTranslation() return useMemo(() => { const comments = controller.unsentComments const terminalActions = load.kind === 'ready' || load.kind === 'error' ? load.terminals.map((terminal) => ({ - label: `${terminal.title || 'Terminal'} (${terminal.terminal.slice(0, 6)})`, + label: t('components.diffReviewDrawers.terminalLabel', '{{title}} ({{id}})', { + title: terminal.title || t('components.diffReviewDrawers.terminal', 'Terminal'), + id: terminal.terminal.slice(0, 6) + }), icon: Send, disabled: comments.length === 0, skipAutoClose: true, @@ -111,47 +130,55 @@ function useSendActions( return [ ...terminalActions, { - label: 'New Agent Session', + label: t('components.diffReviewDrawers.newAgentSession', 'New Agent Session'), icon: Plus, disabled: comments.length === 0 || controller.agentLaunchAvailability !== 'available', ...(controller.agentLaunchAvailability === 'update-required' - ? { hint: 'Update Orca on your computer' } + ? { + hint: t('components.diffReviewDrawers.updateRequired', 'Update Orca on your computer') + } : controller.agentLaunchAvailability === 'unverified' - ? { hint: AGENT_LAUNCH_STATUS_UNREADABLE_MESSAGE } + ? { + hint: t( + 'components.diffReviewDrawers.statusUnreadable', + "Could not read this host's status. Retrying…" + ) + } : {}), skipAutoClose: true, onPress: () => void controller.createTerminalAndSend(comments) }, { - label: 'Copy Notes', + label: t('components.diffReviewDrawers.copyNotes', 'Copy Notes'), icon: Copy, disabled: controller.screenState.kind !== 'ready' || controller.screenState.comments.length === 0, onPress: () => void controller.copyNotes() } ] - }, [controller, load]) + }, [controller, load, t]) } function useOverflowActions(controller: ReturnType) { + const t = useMobileTranslation() const sessionTabsAvailable = useWorkspaceOnPairedHost() return useMemo( () => [ { - label: 'Copy Notes', + label: t('components.diffReviewDrawers.copyNotes', 'Copy Notes'), icon: Copy, disabled: controller.screenState.kind !== 'ready' || controller.screenState.comments.length === 0, onPress: () => void controller.copyNotes() }, { - label: 'Send Unsent Notes', + label: t('components.diffReviewDrawers.sendUnsentNotes', 'Send Unsent Notes'), icon: Send, disabled: controller.unsentComments.length === 0, onPress: () => void controller.openSendSheet() }, { - label: 'Clear Sent Notes', + label: t('components.diffReviewDrawers.clearSentNotes', 'Clear Sent Notes'), icon: Trash2, disabled: controller.screenState.kind !== 'ready' || @@ -160,14 +187,14 @@ function useOverflowActions(controller: ReturnType void controller.clearSentNotes() }, { - label: 'Stage Reviewed Files', + label: t('components.diffReviewDrawers.stageReviewedFiles', 'Stage Reviewed Files'), icon: Check, disabled: controller.reviewedUnstagedCount === 0 || controller.busyAction !== null, skipAutoClose: true, onPress: () => void controller.stageReviewedFiles() }, { - label: 'Mark Unreviewed', + label: t('components.diffReviewDrawers.markUnreviewed', 'Mark Unreviewed'), icon: X, disabled: controller.screenState.kind !== 'ready' || @@ -179,7 +206,7 @@ function useOverflowActions(controller: ReturnType void controller.openInSession() @@ -187,41 +214,50 @@ function useOverflowActions(controller: ReturnType, load: SendSheetState ): string | undefined { + const count = controller.unsentComments.length return load.kind === 'loading' - ? 'Loading agent sessions...' + ? t('components.diffReviewDrawers.loadingSessions', 'Loading agent sessions...') : load.kind === 'error' ? load.message - : `${controller.unsentComments.length} unsent notes` + : 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() return ( - {composer.mode === 'edit' ? 'Edit Note' : 'Add Note'} + {composer.mode === 'edit' + ? t('components.diffReviewDrawers.editNote', 'Edit Note') + : t('components.diffReviewDrawers.addNote', 'Add Note')} {composer.mode === 'create' && composer.lineNumber > 0 - ? `Line ${composer.lineNumber}` - : 'File note'} + ? t('components.diffReviewDrawers.lineNumber', 'Line {{lineNumber}}', { + lineNumber: composer.lineNumber + }) + : t('components.diffReviewDrawers.fileNote', 'File note')} [styles.iconButton, pressed && styles.iconButtonPressed]} onPress={controller.closeComposer} accessibilityRole="button" - accessibilityLabel="Cancel note" + accessibilityLabel={t('components.diffReviewDrawers.cancelNote', 'Cancel note')} > @@ -232,9 +268,9 @@ function NoteComposerContent({ controller, composer }: Props & { composer: Compo onChangeText={controller.setComposerBody} multiline autoFocus - placeholder="Review note" + placeholder={t('components.diffReviewDrawers.reviewNote', 'Review note')} placeholderTextColor={colors.textMuted} - accessibilityLabel={composerLabel(composer)} + accessibilityLabel={composerLabel(t, composer)} /> {composer.mode === 'edit' ? : null} @@ -245,24 +281,28 @@ function NoteComposerContent({ controller, composer }: Props & { composer: Compo } function composerLabel( + t: Translate, composer: { mode: 'create'; lineNumber: number } | { mode: 'edit'; comment: DiffComment } | null ): string { return composer?.mode === 'create' && composer.lineNumber > 0 - ? `Save note on line ${composer.lineNumber}` - : 'Review note' + ? t('components.diffReviewDrawers.saveLineNote', 'Save note on line {{lineNumber}}', { + lineNumber: composer.lineNumber + }) + : t('components.diffReviewDrawers.reviewNote', 'Review note') } function DeleteNoteButton({ onPress }: { onPress: () => Promise }) { useMobileColorScheme() + const t = useMobileTranslation() return ( [styles.secondaryButton, pressed && styles.buttonPressed]} onPress={() => void onPress()} accessibilityRole="button" - accessibilityLabel="Delete note" + accessibilityLabel={t('components.diffReviewDrawers.deleteNote', 'Delete note')} > - Delete + {t('common.delete', 'Delete')} ) } @@ -275,6 +315,7 @@ function SaveNoteButton({ composer: ReturnType['composer'] }) { useMobileColorScheme() + const t = useMobileTranslation() const disabled = controller.composerBody.trim().length === 0 return ( void controller.saveComposer()} accessibilityRole="button" - accessibilityLabel={composerLabel(composer)} + accessibilityLabel={composerLabel(t, composer)} > - Save + {t('common.save', 'Save')} ) } function CompletionContent({ controller }: Props) { useMobileColorScheme() - const noteCount = - controller.screenState.kind === 'ready' ? controller.screenState.comments.length : 0 + const t = useMobileTranslation() + const fileCount = controller.queue.length + const count = controller.screenState.kind === 'ready' ? controller.screenState.comments.length : 0 return ( <> - Review Complete + + {t('components.diffReviewDrawers.completeTitle', 'Review Complete')} + - {mobileReviewCountLabel(controller.queue.length, 'file', 'files')} reviewed,{' '} - {mobileReviewCountLabel(noteCount, 'note', 'notes')} + {fileCount === 1 + ? count === 1 + ? t( + 'components.diffReviewDrawers.fileSummary_one', + '{{fileCount}} file reviewed, {{count}} note', + { fileCount, count } + ) + : t( + 'components.diffReviewDrawers.fileSummary_other', + '{{fileCount}} file reviewed, {{count}} notes', + { fileCount, count } + ) + : count === 1 + ? t( + 'components.diffReviewDrawers.filesSummary_one', + '{{fileCount}} files reviewed, {{count}} note', + { fileCount, count } + ) + : t( + 'components.diffReviewDrawers.filesSummary_other', + '{{fileCount}} files reviewed, {{count}} notes', + { fileCount, count } + )} void controller.stageReviewedFiles()} accessibilityRole="button" - accessibilityLabel="Stage reviewed files" + accessibilityLabel={t( + 'components.diffReviewDrawers.stageReviewedLabel', + 'Stage reviewed files' + )} > - Stage Reviewed + + {t('components.diffReviewDrawers.stageReviewed', 'Stage Reviewed')} + [styles.primaryButton, pressed && styles.buttonPressed]} disabled={controller.unsentComments.length === 0} onPress={() => void controller.openSendSheet()} accessibilityRole="button" - accessibilityLabel="Send notes to agent" + accessibilityLabel={t( + 'components.diffReviewDrawers.sendNotesLabel', + 'Send notes to agent' + )} > - Send Notes + + {t('components.diffReviewDrawers.sendNotes', 'Send Notes')} + diff --git a/mobile/src/components/MobileDiffReviewFileSummary.tsx b/mobile/src/components/MobileDiffReviewFileSummary.tsx index 9e8ce21a2d8..fc6c202e327 100644 --- a/mobile/src/components/MobileDiffReviewFileSummary.tsx +++ b/mobile/src/components/MobileDiffReviewFileSummary.tsx @@ -3,13 +3,11 @@ import { ArrowDown, ArrowUp } from 'lucide-react-native' 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 { useMobileTranslation } from '../i18n/use-mobile-translation' import type { MobileDiffReviewQueueItem } from '../session/mobile-diff-review-queue' import { STATUS_LABELS } from '../../../src/shared/git-status-labels' -import { - mobileReviewCountLabel, - mobileReviewScopeLabel, - type ReviewDiffState -} from '../session/mobile-diff-review-screen-model' +import type { ReviewDiffState } from '../session/mobile-diff-review-screen-model' import { mobileDiffReviewStyles as styles } from './mobile-diff-review-screen-styles' type Props = { @@ -40,6 +38,22 @@ function statusColor(status: MobileDiffReviewQueueItem['status']): string { } } +function fileScopeLabel(t: Translate, item: MobileDiffReviewQueueItem): string { + if (item.oldPath) { + const options = { oldPath: item.oldPath } + return item.scope === 'branch' + ? t('components.diffReviewFileSummary.branchFrom', 'Branch from {{oldPath}}', options) + : item.scope === 'staged' + ? t('components.diffReviewFileSummary.stagedFrom', 'Staged from {{oldPath}}', options) + : t('components.diffReviewFileSummary.unstagedFrom', 'Unstaged from {{oldPath}}', options) + } + return item.scope === 'branch' + ? t('components.diffReviewFileSummary.branch', 'Branch') + : item.scope === 'staged' + ? t('components.diffReviewFileSummary.staged', 'Staged') + : t('components.diffReviewFileSummary.unstaged', 'Unstaged') +} + export function MobileDiffReviewFileSummary({ currentIndex, diffState, @@ -51,6 +65,8 @@ export function MobileDiffReviewFileSummary({ onJumpHunk }: Props) { useMobileColorScheme() + const t = useMobileTranslation() + const count = item.noteCount const hunkDisabled = diffState.kind !== 'ready' || diffState.hunks.length === 0 const badgeColor = statusColor(item.status) return ( @@ -66,8 +82,7 @@ export function MobileDiffReviewFileSummary({ {item.filePath} - {mobileReviewScopeLabel(item)} - {item.oldPath ? ` from ${item.oldPath}` : ''} + {fileScopeLabel(t, item)} @@ -75,15 +90,29 @@ export function MobileDiffReviewFileSummary({ {currentIndex + 1}/{filteredCount} - {item.isReviewed ? Reviewed : null} - {item.changedSinceReview ? Changed : null} + {item.isReviewed ? ( + + {t('components.diffReviewFileSummary.reviewed', 'Reviewed')} + + ) : null} + {item.changedSinceReview ? ( + + {t('components.diffReviewFileSummary.changed', 'Changed')} + + ) : null} {item.noteCount > 0 ? ( - {mobileReviewCountLabel(item.noteCount, 'note', 'notes')} + {count === 1 + ? t('components.diffReviewFileSummary.notes_one', '{{count}} note', { count }) + : t('components.diffReviewFileSummary.notes_other', '{{count}} notes', { count })} ) : null} {item.staleNoteCount > 0 ? ( - {item.staleNoteCount} stale + + {t('components.diffReviewFileSummary.staleCount', '{{count}} stale', { + count: item.staleNoteCount + })} + ) : null} {fileNotes.length > 0 ? ( @@ -94,12 +123,19 @@ export function MobileDiffReviewFileSummary({ style={({ pressed }) => [styles.fileNote, pressed && styles.fileNotePressed]} onPress={() => onEditNote(note)} accessibilityRole="button" - accessibilityLabel="Edit file note" + accessibilityLabel={t( + 'components.diffReviewFileSummary.editFileNote', + 'Edit file note' + )} > {note.body} - {staleCommentIds.has(note.id) ? Stale : null} + {staleCommentIds.has(note.id) ? ( + + {t('components.diffReviewFileSummary.stale', 'Stale')} + + ) : null} ))} @@ -110,20 +146,24 @@ export function MobileDiffReviewFileSummary({ disabled={hunkDisabled} onPress={() => onJumpHunk('previous')} accessibilityRole="button" - accessibilityLabel="Previous hunk" + accessibilityLabel={t('components.diffReviewFileSummary.previousHunk', 'Previous hunk')} > - Hunk + + {t('components.diffReviewFileSummary.hunk', 'Hunk')} + [styles.hunkButton, pressed && styles.hunkButtonPressed]} disabled={hunkDisabled} onPress={() => onJumpHunk('next')} accessibilityRole="button" - accessibilityLabel="Next hunk" + accessibilityLabel={t('components.diffReviewFileSummary.nextHunk', 'Next hunk')} > - Hunk + + {t('components.diffReviewFileSummary.hunk', 'Hunk')} + diff --git a/mobile/src/components/MobileDiffReviewFooter.tsx b/mobile/src/components/MobileDiffReviewFooter.tsx index 215eed6e0cc..2de2016fc8d 100644 --- a/mobile/src/components/MobileDiffReviewFooter.tsx +++ b/mobile/src/components/MobileDiffReviewFooter.tsx @@ -9,6 +9,7 @@ import { Undo2 } from 'lucide-react-native' import { useSafeAreaInsets } from 'react-native-safe-area-context' +import { useMobileTranslation } from '../i18n/use-mobile-translation' import type { MobileDiffReviewQueueItem } from '../session/mobile-diff-review-queue' import type { GitMutationMethod } from '../session/mobile-diff-review-screen-model' import { colors, spacing } from '../theme/mobile-theme' @@ -34,6 +35,7 @@ export function MobileDiffReviewFooter({ onMarkReviewed, onMoveFile }: Props) { + const t = useMobileTranslation() useMobileColorScheme() const insets = useSafeAreaInsets() return ( @@ -45,10 +47,12 @@ export function MobileDiffReviewFooter({ disabled={busyAction !== null} onPress={() => onGitMutation('git.stage', item)} accessibilityRole="button" - accessibilityLabel="Stage file" + accessibilityLabel={t('components.diffReviewFooter.stageFile', 'Stage file')} > - Stage + + {t('components.diffReviewFooter.stage', 'Stage')} + ) : null} {item.canUnstage ? ( @@ -57,10 +61,12 @@ export function MobileDiffReviewFooter({ disabled={busyAction !== null} onPress={() => onGitMutation('git.unstage', item)} accessibilityRole="button" - accessibilityLabel="Unstage file" + accessibilityLabel={t('components.diffReviewFooter.unstageFile', 'Unstage file')} > - Unstage + + {t('components.diffReviewFooter.unstage', 'Unstage')} + ) : null} {item.canDiscard ? ( @@ -69,10 +75,12 @@ export function MobileDiffReviewFooter({ disabled={busyAction !== null} onPress={() => onDiscard(item)} accessibilityRole="button" - accessibilityLabel="Discard file" + accessibilityLabel={t('components.diffReviewFooter.discardFile', 'Discard file')} > - Discard + + {t('components.diffReviewFooter.discard', 'Discard')} + ) : null} @@ -81,7 +89,7 @@ export function MobileDiffReviewFooter({ style={({ pressed }) => [styles.navButton, pressed && styles.buttonPressed]} onPress={() => onMoveFile('previous')} accessibilityRole="button" - accessibilityLabel="Previous file" + accessibilityLabel={t('components.diffReviewFooter.previousFile', 'Previous file')} > @@ -89,10 +97,12 @@ export function MobileDiffReviewFooter({ style={({ pressed }) => [styles.footerButton, pressed && styles.buttonPressed]} onPress={onAddFileNote} accessibilityRole="button" - accessibilityLabel="Add file note" + accessibilityLabel={t('components.diffReviewFooter.addFileNote', 'Add file note')} > - Note + + {t('components.diffReviewFooter.note', 'Note')} + [ @@ -102,18 +112,23 @@ export function MobileDiffReviewFooter({ ]} onPress={onMarkReviewed} accessibilityRole="button" - accessibilityLabel="Mark file reviewed" + accessibilityLabel={t( + 'components.diffReviewFooter.markFileReviewed', + 'Mark file reviewed' + )} > - {item.isReviewed ? 'Reviewed' : 'Mark Reviewed'} + {item.isReviewed + ? t('components.diffReviewFooter.reviewed', 'Reviewed') + : t('components.diffReviewFooter.markReviewed', 'Mark Reviewed')} [styles.navButton, pressed && styles.buttonPressed]} onPress={() => onMoveFile('next')} accessibilityRole="button" - accessibilityLabel="Next file" + accessibilityLabel={t('components.diffReviewFooter.nextFile', 'Next file')} > diff --git a/mobile/src/components/MobileDiffReviewHeader.tsx b/mobile/src/components/MobileDiffReviewHeader.tsx index 8e087ef8075..e7470853b3c 100644 --- a/mobile/src/components/MobileDiffReviewHeader.tsx +++ b/mobile/src/components/MobileDiffReviewHeader.tsx @@ -1,9 +1,10 @@ import { FlatList, Pressable, Text, View } from 'react-native' import { ChevronLeft, ListChecks, MoreHorizontal } 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 type { MobileDiffReviewQueueFilter } from '../session/mobile-diff-review-queue' -import { REVIEW_FILTERS, mobileReviewCountLabel } from '../session/mobile-diff-review-screen-model' +import { REVIEW_FILTERS } from '../session/mobile-diff-review-screen-model' import { shouldShowTrigger } from './mobile-pr-sidebar-presentation' import { mobileDiffReviewStyles as styles } from './mobile-diff-review-screen-styles' @@ -36,7 +37,50 @@ export function MobileDiffReviewHeader({ onOpenPRSidebar, onSelectFilter }: Props) { + const t = useMobileTranslation() const colorScheme = useMobileColorScheme() + const count = unsentCount + const filters: Record< + MobileDiffReviewQueueFilter, + { label: string; accessibilityLabel: string } + > = { + all: { + label: t('components.diffReviewHeader.filterAll', 'All'), + accessibilityLabel: t('components.diffReviewHeader.showAllFiles', 'Show all review files') + }, + unreviewed: { + label: t('components.diffReviewHeader.filterUnreviewed', 'Unreviewed'), + accessibilityLabel: t( + 'components.diffReviewHeader.showUnreviewedFiles', + 'Show unreviewed review files' + ) + }, + notes: { + label: t('components.diffReviewHeader.filterNotes', 'Notes'), + accessibilityLabel: t('components.diffReviewHeader.showNotesFiles', 'Show notes review files') + }, + unstaged: { + label: t('components.diffReviewHeader.filterUnstaged', 'Unstaged'), + accessibilityLabel: t( + 'components.diffReviewHeader.showUnstagedFiles', + 'Show unstaged review files' + ) + }, + staged: { + label: t('components.diffReviewHeader.filterStaged', 'Staged'), + accessibilityLabel: t( + 'components.diffReviewHeader.showStagedFiles', + 'Show staged review files' + ) + }, + branch: { + label: t('components.diffReviewHeader.filterBranch', 'Branch'), + accessibilityLabel: t( + 'components.diffReviewHeader.showBranchFiles', + 'Show branch review files' + ) + } + } // Show PR icon on narrow/overlay GitHub repos; wide docked sidebar needs no trigger. const showPRTrigger = shouldShowTrigger({ isGithubRepo: prSidebarIsGithubRepo, @@ -50,13 +94,13 @@ export function MobileDiffReviewHeader({ style={({ pressed }) => [styles.iconButton, pressed && styles.iconButtonPressed]} onPress={onBack} accessibilityRole="button" - accessibilityLabel="Back" + accessibilityLabel={t('common.back', 'Back')} > - Changes + {t('components.diffReviewHeader.title', 'Changes')} {worktreeLabel} @@ -67,7 +111,10 @@ export function MobileDiffReviewHeader({ style={({ pressed }) => [styles.iconButton, pressed && styles.iconButtonPressed]} onPress={onOpenPRSidebar} accessibilityRole="button" - accessibilityLabel="Open pull request sidebar" + accessibilityLabel={t( + 'components.diffReviewHeader.openPullRequestSidebar', + 'Open pull request sidebar' + )} > @@ -76,17 +123,31 @@ export function MobileDiffReviewHeader({ style={({ pressed }) => [styles.iconButton, pressed && styles.iconButtonPressed]} onPress={onOpenActions} accessibilityRole="button" - accessibilityLabel="Open review actions" + accessibilityLabel={t( + 'components.diffReviewHeader.openReviewActions', + 'Open review actions' + )} > - {reviewedCount}/{queueLength} reviewed + {t( + 'components.diffReviewHeader.reviewedProgress', + '{{reviewedCount}}/{{queueLength}} reviewed', + { + reviewedCount, + queueLength + } + )} - {mobileReviewCountLabel(unsentCount, 'unsent note', 'unsent notes')} + {count === 1 + ? t('components.diffReviewHeader.unsentNotes_one', '{{count}} unsent note', { count }) + : t('components.diffReviewHeader.unsentNotes_other', '{{count}} unsent notes', { + count + })} item} - extraData={colorScheme} + extraData={[colorScheme, t]} contentContainerStyle={styles.filterRow} renderItem={({ item }) => ( onSelectFilter(item)} accessibilityRole="button" accessibilityState={{ selected: filter === item }} - accessibilityLabel={`Show ${item} review files`} + accessibilityLabel={filters[item].accessibilityLabel} > - {item === 'all' ? 'All' : item[0]?.toUpperCase() + item.slice(1)} + {filters[item].label} )} diff --git a/mobile/src/components/MobileDiffReviewLine.tsx b/mobile/src/components/MobileDiffReviewLine.tsx index 3beecc0330c..e2fde475783 100644 --- a/mobile/src/components/MobileDiffReviewLine.tsx +++ b/mobile/src/components/MobileDiffReviewLine.tsx @@ -1,5 +1,7 @@ import { Pressable, StyleSheet, Text, View } from 'react-native' import { MessageSquare } from 'lucide-react-native' +import type { Translate } from '../i18n/mobile-i18n' +import { useMobileTranslation } from '../i18n/use-mobile-translation' import type { DiffComment } from '../../../src/shared/diff-comment-types' import type { MobileDiffLine } from '../session/mobile-diff-lines' import type { MobileHighlightedDiffLine } from '../session/mobile-file-syntax' @@ -18,10 +20,21 @@ type Props = { onEditNote: (comment: DiffComment) => void } -function accessibilityLabelForLine(line: MobileDiffLine): string { +function accessibilityLabelForLine(line: MobileDiffLine, t: Translate): string { const number = mobileDiffLineNumber(line) - const label = line.kind === 'add' ? 'Added' : line.kind === 'delete' ? 'Deleted' : 'Context' - return number ? `${label} line ${number}` : `${label} line` + if (line.kind === 'add') { + return number + ? t('components.diffReviewLine.addedLineNumber', 'Added line {{number}}', { number }) + : t('components.diffReviewLine.addedLine', 'Added line') + } + if (line.kind === 'delete') { + return number + ? t('components.diffReviewLine.deletedLineNumber', 'Deleted line {{number}}', { number }) + : t('components.diffReviewLine.deletedLine', 'Deleted line') + } + return number + ? t('components.diffReviewLine.contextLineNumber', 'Context line {{number}}', { number }) + : t('components.diffReviewLine.contextLine', 'Context line') } function canCommentOnLine(line: MobileDiffLine): boolean { @@ -36,6 +49,7 @@ export function MobileDiffReviewLine({ onAddNote, onEditNote }: Props) { + const t = useMobileTranslation() useMobileColorScheme() const lineNumber = mobileDiffLineNumber(line) const canComment = canCommentOnLine(line) @@ -49,7 +63,7 @@ export function MobileDiffReviewLine({ active && styles.activeRow ]} accessible - accessibilityLabel={accessibilityLabelForLine(line)} + accessibilityLabel={accessibilityLabelForLine(line, t)} > {mobileDiffLinePrefix(line.kind)} {lineNumber ? String(lineNumber) : ''} @@ -64,8 +78,10 @@ export function MobileDiffReviewLine({ accessibilityRole={canComment ? 'button' : 'text'} accessibilityLabel={ canComment && line.newLineNumber !== undefined - ? `Add note on line ${line.newLineNumber}` - : accessibilityLabelForLine(line) + ? t('components.diffReviewLine.addNoteOnLine', 'Add note on line {{number}}', { + number: line.newLineNumber + }) + : accessibilityLabelForLine(line, t) } > @@ -80,7 +96,13 @@ export function MobileDiffReviewLine({ style={({ pressed }) => [styles.noteButton, pressed && styles.noteButtonPressed]} onPress={() => onEditNote(comment)} accessibilityRole="button" - accessibilityLabel={`Edit note on line ${comment.lineNumber}`} + accessibilityLabel={t( + 'components.diffReviewLine.editNoteOnLine', + 'Edit note on line {{number}}', + { + number: comment.lineNumber + } + )} > 0 return ( <> - Quick Actions + + {t('components.homeQuickActions.title', 'Quick Actions')} + - Pair Desktop + + {t('components.homeQuickActions.pairDesktop', 'Pair Desktop')} + - New Workspace + + {t('components.homeQuickActions.newWorkspace', 'New Workspace')} + diff --git a/mobile/src/components/MobileHostCard.tsx b/mobile/src/components/MobileHostCard.tsx index 7bafcf306ab..93eeab4c104 100644 --- a/mobile/src/components/MobileHostCard.tsx +++ b/mobile/src/components/MobileHostCard.tsx @@ -1,5 +1,7 @@ import { Monitor, MoreVertical } from 'lucide-react-native' import { Pressable, Text, View } from 'react-native' +import type { Translate } from '../i18n/mobile-i18n' +import { useMobileTranslation } from '../i18n/use-mobile-translation' import type { ConnectionVerdict } from '../transport/connection-health' import { verdictDisplayLabel } from '../transport/connection-health' import { @@ -15,6 +17,23 @@ import { useMobileColorScheme } from '../theme/use-mobile-color-scheme' import { homeHostWorktreeSummary, type HostWorktreeInfo } from '../worktree/home-worktree-info' import { StatusDot } from './StatusDot' +function hostCardWorktreeSummaries(info: HostWorktreeInfo | undefined, t: Translate) { + const now = Date.now() + const visual = homeHostWorktreeSummary(info, now) + const accessibility = + info && info.activeCount > 0 && (!info.catalogUnavailable || info.staleCounts) + ? t( + 'components.hostCard.worktreeAccessibilitySummary', + '{{summary}}, {{activeCount}} active', + { + summary: homeHostWorktreeSummary({ ...info, activeCount: 0 }, now), + activeCount: info.activeCount + } + ) + : visual + return { visual, accessibility } +} + export function MobileHostCard(props: { host: HostProfile | HostCatalogEntry credentialStatus?: HostCatalogEntry['credentialStatus'] @@ -27,6 +46,7 @@ export function MobileHostCard(props: { onLongPress: () => void onOpenActions: () => void }) { + const t = useMobileTranslation() useMobileColorScheme() const credentialUnavailable = props.credentialStatus === 'temporarily-unavailable' const credentialMissing = props.credentialStatus === 'missing' @@ -34,16 +54,16 @@ export function MobileHostCard(props: { const isError = credentialMissing || ['warning', 'unreachable', 'auth-failed'].includes(props.verdict.kind) const statusLabel = credentialMissing - ? 'Pairing invalid' + ? t('components.hostCard.pairingInvalid', 'Pairing invalid') : credentialUnavailable - ? 'Pairing temporarily unavailable' + ? t('components.hostCard.pairingTemporarilyUnavailable', 'Pairing temporarily unavailable') : verdictDisplayLabel(props.verdict) const statusVerdict: ConnectionVerdict = credentialMissing ? { kind: 'auth-failed', label: statusLabel } : credentialUnavailable ? { kind: 'warning', label: statusLabel } : props.verdict - const worktreeSummary = homeHostWorktreeSummary(props.worktreeInfo) + const worktreeSummary = hostCardWorktreeSummaries(props.worktreeInfo, t) const display = useHostDisplay(props.host) const descriptorText = display.descriptorLine const connectionPathLabel = @@ -52,22 +72,25 @@ export function MobileHostCard(props: { : null const discoveryHint = props.verdict.kind === 'unreachable' && !props.host.relay - ? 'Update desktop Orca and sign in to connect from anywhere' + ? t( + 'components.hostCard.updateDesktopHint', + 'Update desktop Orca and sign in to connect from anywhere' + ) : null const credentialHint = credentialMissing - ? 'Tap to re-pair with your desktop' + ? t('components.hostCard.repairHint', 'Tap to re-pair with your desktop') : credentialUnavailable - ? 'Unlock your phone, then tap to retry' + ? t('components.hostCard.unlockHint', 'Unlock your phone, then tap to retry') : null // The verdict's own second line (what to check on the desktop); credential copy wins. const verdictDetail = credentialHint === null && 'detail' in props.verdict ? (props.verdict.detail ?? null) : null const accessibilityLabel = [ - `Open ${display.title}`, + t('components.hostCard.openHost', 'Open {{title}}', { title: display.title }), descriptorText, statusLabel, connectionPathLabel ? mobileConnectionPathAccessibilityLabel(props.path) : null, - connected ? worktreeSummary?.replace(' · ', ', ') : null, + connected ? worktreeSummary.accessibility : null, discoveryHint, credentialHint, verdictDetail @@ -113,9 +136,9 @@ export function MobileHostCard(props: { {connectionPathLabel ? ` · ${connectionPathLabel}` : ''} - {connected && worktreeSummary ? ( + {connected && worktreeSummary.visual ? ( - {worktreeSummary} + {worktreeSummary.visual} ) : null} {discoveryHint ? ( @@ -137,7 +160,9 @@ export function MobileHostCard(props: { [styles.actionButton, pressed && styles.actionButtonPressed]} onPress={props.onOpenActions} diff --git a/mobile/src/components/MobileHtmlPreview.tsx b/mobile/src/components/MobileHtmlPreview.tsx index a12fe07cfab..ec2b4ad5f56 100644 --- a/mobile/src/components/MobileHtmlPreview.tsx +++ b/mobile/src/components/MobileHtmlPreview.tsx @@ -1,6 +1,7 @@ import { useMemo, useState } from 'react' import { Pressable, Text, View } from 'react-native' import { Code, Eye } from 'lucide-react-native' +import { useMobileTranslation } from '../i18n/use-mobile-translation' import { colors, spacing, typography } from '../theme/mobile-theme' import { themedStyleSheet } from '../theme/themed-styles' import { useMobileColorScheme } from '../theme/use-mobile-color-scheme' @@ -25,6 +26,7 @@ export const MOBILE_HTML_PREVIEW_SEALED_SANDBOX = '' // Inherited img/font/connect/script policies still constrain artifact; https images can report view through fixed author URL, but scripts cannot add dynamic data. // Shell Referrer-Policy:no-referrer is required: WebKit ignores iframe referrerPolicy for srcdoc images, unlike Chromium. export function MobileHtmlPreview({ html, renderSource }: MobileHtmlPreviewProps) { + const t = useMobileTranslation() useMobileColorScheme() const [mode, setMode] = useState<'preview' | 'source'>('preview') // Read immutable session grants once after init, before page mounts. @@ -46,10 +48,13 @@ export function MobileHtmlPreview({ html, renderSource }: MobileHtmlPreviewProps // Set both native accessibilityState and web aria-selected: RN Web drops former without emitting latter. accessibilityState={{ selected: mode === 'preview' }} aria-selected={mode === 'preview'} - accessibilityLabel="Preview rendered HTML" + accessibilityLabel={t( + 'components.htmlPreview.previewRenderedHtml', + 'Preview rendered HTML' + )} > - Preview + {t('components.htmlPreview.preview', 'Preview')} - Source + {t('components.htmlPreview.source', 'Source')} {/* Show author’s original source; inert-link rewrite is only this shell’s rendering policy. */} @@ -71,11 +76,12 @@ export function MobileHtmlPreview({ html, renderSource }: MobileHtmlPreviewProps // Use DOM iframe: RN has no primitive. Set srcdoc attribute to parse untrusted content only inside sealed frame. function PreviewFrame({ html, linksOpen }: { html: string; linksOpen: boolean }) { + const t = useMobileTranslation() useMobileColorScheme() return (