diff --git a/config/render-path-translate-baseline.txt b/config/render-path-translate-baseline.txt index 75b9990b5d6..f75d002f088 100644 --- a/config/render-path-translate-baseline.txt +++ b/config/render-path-translate-baseline.txt @@ -790,23 +790,6 @@ 7 src/renderer/src/components/right-sidebar/use-right-sidebar-activity-items.ts 16 src/renderer/src/components/runtime-access/RuntimeHostAccessForm.tsx 8 src/renderer/src/components/runtime-access/RuntimeRepairDialog.tsx -2 src/renderer/src/components/settings/accounts/AccountsPane.tsx -5 src/renderer/src/components/settings/accounts/claude/claude-running-session-switch-setting.tsx -2 src/renderer/src/components/settings/accounts/claude/claude-sign-in-link-menu.tsx -5 src/renderer/src/components/settings/accounts/codex/CodexLoginLinkNotice.tsx -12 src/renderer/src/components/settings/accounts/orca/OrcaAccountSettingsPane.tsx -25 src/renderer/src/components/settings/accounts/providers/AntigravityAccountsSection.tsx -18 src/renderer/src/components/settings/accounts/providers/CursorAccountsSection.tsx -17 src/renderer/src/components/settings/accounts/providers/GrokAccountsSection.tsx -17 src/renderer/src/components/settings/accounts/providers/ManagedDataAccountsSection.tsx -12 src/renderer/src/components/settings/accounts/sign-in/HostAccountSignInPrompt.tsx -19 src/renderer/src/components/settings/accounts/usage/MiniMaxAccountsSection.tsx -32 src/renderer/src/components/settings/accounts/usage/ZcodePlanAccountsSection.tsx -36 src/renderer/src/components/settings/accounts/usage/accounts-pane-minimax-credentials.tsx -13 src/renderer/src/components/settings/accounts/usage/accounts-pane-opencode-credentials.tsx -1 src/renderer/src/components/settings/accounts/usage/use-usage-host-settings.ts -2 src/renderer/src/components/settings/accounts/usage/use-zcode-plan-credentials.ts -1 src/renderer/src/components/settings/accounts/usage/zcode-plan-usage-windows.tsx 10 src/renderer/src/components/settings/advanced/AdvancedPane.tsx 27 src/renderer/src/components/settings/advanced/DevToolsPane.tsx 23 src/renderer/src/components/settings/advanced/ExperimentalPane.tsx @@ -814,39 +797,6 @@ 11 src/renderer/src/components/settings/advanced/network/AdvancedNetworkSettingsSection.tsx 7 src/renderer/src/components/settings/advanced/network/NodeDistMirrorDialog.tsx 3 src/renderer/src/components/settings/advanced/network/NodeDistMirrorSettingField.tsx -13 src/renderer/src/components/settings/agents/behavior/AgentCacheTimerSection.tsx -14 src/renderer/src/components/settings/agents/launch/AgentCatalogRow.tsx -3 src/renderer/src/components/settings/agents/launch/AgentDefaultSetting.tsx -15 src/renderer/src/components/settings/agents/launch/AgentDetectionCatalog.tsx -3 src/renderer/src/components/settings/agents/launch/AgentLaunchSettingsCatalog.tsx -1 src/renderer/src/components/settings/agents/launch/AgentLaunchSettingsLoading.tsx -2 src/renderer/src/components/settings/agents/launch/AgentLaunchSettingsSection.tsx -8 src/renderer/src/components/settings/agents/launch/HostAgentEnvironmentEditor.tsx -4 src/renderer/src/components/settings/agents/launch/HostAgentLaunchSettings.tsx -12 src/renderer/src/components/settings/agents/launch/ZcodeCliSetupSection.tsx -2 src/renderer/src/components/settings/agents/permissions/AgentPermissionModeMenu.tsx -9 src/renderer/src/components/settings/agents/permissions/AgentPermissionsSetting.tsx -6 src/renderer/src/components/settings/agents/permissions/AgentRowPermissions.tsx -5 src/renderer/src/components/settings/agents/permissions/agent-permission-override-notice.tsx -6 src/renderer/src/components/settings/agents/runtime/AgentRuntimeSetting.tsx -20 src/renderer/src/components/settings/agents/surfaces/AgentDefaultSurfaceSection.tsx -3 src/renderer/src/components/settings/appearance/AppIconSelector.tsx -1 src/renderer/src/components/settings/appearance/AppearanceAdvancedDisclosure.tsx -25 src/renderer/src/components/settings/appearance/AppearanceInterfaceSection.tsx -7 src/renderer/src/components/settings/appearance/AppearancePane.tsx -1 src/renderer/src/components/settings/appearance/AppearanceShortcutHintList.tsx -20 src/renderer/src/components/settings/appearance/AppearanceWindowSidebarSection.tsx -9 src/renderer/src/components/settings/appearance/LeftSidebarAppearanceSetting.tsx -5 src/renderer/src/components/settings/appearance/RecentTabOrderControl.tsx -1 src/renderer/src/components/settings/appearance/ReduceVisualEffectsSetting.tsx -1 src/renderer/src/components/settings/appearance/UIZoomControl.tsx -5 src/renderer/src/components/settings/appearance/WindowMaterialSetting.tsx -3 src/renderer/src/components/settings/appearance/chat/AppearanceChatContrastControls.tsx -3 src/renderer/src/components/settings/appearance/chat/AppearanceChatSection.tsx -3 src/renderer/src/components/settings/appearance/chat/ChatFontFamilySetting.tsx -6 src/renderer/src/components/settings/appearance/fonts/FontAutocomplete.tsx -12 src/renderer/src/components/settings/appearance/themes/AppearanceThemeControl.tsx -1 src/renderer/src/components/settings/appearance/use-appearance-picker-labels.ts 23 src/renderer/src/components/settings/artifacts/ArtifactsSettingsPane.tsx 12 src/renderer/src/components/settings/automations/AutomationsSettingsPane.tsx 3 src/renderer/src/components/settings/chat/ChatBehaviorSetting.tsx @@ -864,14 +814,6 @@ 7 src/renderer/src/components/settings/developer-permissions/DeveloperPermissionsPane.tsx 9 src/renderer/src/components/settings/developer-permissions/LocalNetworkConnectionTest.tsx 3 src/renderer/src/components/settings/developer-permissions/TerminalTccAttributionNotice.tsx -2 src/renderer/src/components/settings/editor/CollapseUnchangedRegionsSetting.tsx -7 src/renderer/src/components/settings/editor/DiffShowWhitespaceSetting.tsx -5 src/renderer/src/components/settings/editor/EditorFontFamilySetting.tsx -9 src/renderer/src/components/settings/editor/EditorTypographySettings.tsx -7 src/renderer/src/components/settings/editor/EditorWordWrapSetting.tsx -42 src/renderer/src/components/settings/editor/GeneralEditorSettingsSection.tsx -4 src/renderer/src/components/settings/editor/MarkdownDefaultViewSetting.tsx -4 src/renderer/src/components/settings/editor/RichMarkdownSpellcheckSetting.tsx 11 src/renderer/src/components/settings/floating-workspaces/FloatingWorkspacePane.tsx 1 src/renderer/src/components/settings/form-controls/SettingsFormControls.tsx 1 src/renderer/src/components/settings/form-controls/SettingsSearchableSelect.tsx @@ -885,20 +827,6 @@ 5 src/renderer/src/components/settings/host-scope/SettingsHostScopePicker.tsx 5 src/renderer/src/components/settings/input/InputPane.tsx 4 src/renderer/src/components/settings/integrations/IntegrationsPane.tsx -2 src/renderer/src/components/settings/mobile/MobileAutoRestoreFitSection.tsx -1 src/renderer/src/components/settings/mobile/MobilePane.tsx -10 src/renderer/src/components/settings/mobile/MobileSettingsPane.tsx -9 src/renderer/src/components/settings/mobile/emulator/EmulatorOpeningSettings.tsx -6 src/renderer/src/components/settings/mobile/emulator/MobileEmulatorAgentControlRow.tsx -12 src/renderer/src/components/settings/mobile/emulator/MobileEmulatorAvailabilityDetails.tsx -6 src/renderer/src/components/settings/mobile/emulator/MobileEmulatorExamples.tsx -8 src/renderer/src/components/settings/mobile/emulator/MobileEmulatorSettingsPane.tsx -8 src/renderer/src/components/settings/mobile/pairing/MobilePairedDevicesSection.tsx -10 src/renderer/src/components/settings/mobile/pairing/MobilePairingConnectionOptions.tsx -9 src/renderer/src/components/settings/mobile/pairing/MobilePairingQrSection.tsx -11 src/renderer/src/components/settings/mobile/pairing/MobilePairingSetupSection.tsx -1 src/renderer/src/components/settings/mobile/pairing/MobileRelayBetaNotice.tsx -3 src/renderer/src/components/settings/mobile/pairing/MobileRelaySignInPanel.tsx 10 src/renderer/src/components/settings/navigation/SettingsSidebar.tsx 3 src/renderer/src/components/settings/notifications/NotificationHostToggles.tsx 8 src/renderer/src/components/settings/notifications/NotificationSoundSection.tsx @@ -918,33 +846,10 @@ 7 src/renderer/src/components/settings/session-history/SessionHistorySettingsPane.tsx 8 src/renderer/src/components/settings/session-history/SessionSearchAdvancedSection.tsx 4 src/renderer/src/components/settings/session-history/SessionSearchComputerList.tsx -11 src/renderer/src/components/settings/shortcuts/KeybindingsFileActions.tsx -14 src/renderer/src/components/settings/shortcuts/ShortcutCommandBlock.tsx -5 src/renderer/src/components/settings/shortcuts/ShortcutFilterRail.tsx -8 src/renderer/src/components/settings/shortcuts/ShortcutRecorderButton.tsx -2 src/renderer/src/components/settings/shortcuts/ShortcutRemoveButton.tsx -1 src/renderer/src/components/settings/shortcuts/ShortcutRowsList.tsx -6 src/renderer/src/components/settings/shortcuts/ShortcutTerminalPolicyControl.tsx -8 src/renderer/src/components/settings/shortcuts/ShortcutsPane.tsx 11 src/renderer/src/components/settings/skills/linear/LinearAgentSkillGuide.tsx 2 src/renderer/src/components/settings/skills/linear/LinearAgentSkillNotes.tsx 8 src/renderer/src/components/settings/skills/linear/LinearAgentSkillPane.tsx 27 src/renderer/src/components/settings/skills/sharing/ShareSkillsSettingsPane.tsx -16 src/renderer/src/components/settings/source-control/AutoRenameBranchFromWorkSetting.tsx -3 src/renderer/src/components/settings/source-control/BranchPrefixFeedback.tsx -34 src/renderer/src/components/settings/source-control/CommitMessageAiPane.tsx -2 src/renderer/src/components/settings/source-control/CompareAgainstUpstreamSetting.tsx -5 src/renderer/src/components/settings/source-control/CustomAgentCommandField.tsx -21 src/renderer/src/components/settings/source-control/GitPane.tsx -17 src/renderer/src/components/settings/source-control/GitProviderApiBudgetPane.tsx -4 src/renderer/src/components/settings/source-control/HostedReviewCreationDefaults.tsx -5 src/renderer/src/components/settings/source-control/RepositorySourceControlAiActionSaveControls.tsx -2 src/renderer/src/components/settings/source-control/SourceControlActionAgentPicker.tsx -6 src/renderer/src/components/settings/source-control/SourceControlActionArguments.tsx -8 src/renderer/src/components/settings/source-control/SourceControlActionRecipeRow.tsx -6 src/renderer/src/components/settings/source-control/SourceControlActionRepoOverrideNote.tsx -2 src/renderer/src/components/settings/source-control/SourceControlAiActionRecipeDefaults.tsx -4 src/renderer/src/components/settings/source-control/text-actions/SourceControlTextActionAgentPicker.tsx 2 src/renderer/src/components/settings/tasks/JiraIssueKeyCasingSetting.tsx 13 src/renderer/src/components/settings/tasks/TaskSourceLinearSetup.tsx 2 src/renderer/src/components/settings/tasks/TaskSourceProviderCard.tsx @@ -964,13 +869,6 @@ 5 src/renderer/src/components/settings/virtual-machines/runtimes/EphemeralVmCleanupStopDialog.tsx 5 src/renderer/src/components/settings/virtual-machines/runtimes/EphemeralVmRuntimeRow.tsx 10 src/renderer/src/components/settings/virtual-machines/runtimes/EphemeralVmRuntimesSection.tsx -8 src/renderer/src/components/settings/voice/OpenAiTranscriptionKeyDialog.tsx -7 src/renderer/src/components/settings/voice/OpenAiTranscriptionSettingsRow.tsx -2 src/renderer/src/components/settings/voice/VoiceDictationLanguageSetting.tsx -7 src/renderer/src/components/settings/voice/VoiceDictationSettingsSection.tsx -7 src/renderer/src/components/settings/voice/VoiceMicrophoneSetting.tsx -9 src/renderer/src/components/settings/voice/VoicePane.tsx -11 src/renderer/src/components/settings/voice/VoiceSpeechModelSection.tsx 1 src/renderer/src/components/settings/wsl/hosts/WslHostsExperimentalSetting.tsx 13 src/renderer/src/components/settings/wsl/hosts/WslHostsSection.tsx 3 src/renderer/src/components/setup-guide/SetupGuideModal.tsx diff --git a/src/renderer/src/components/agent/launch-settings/AgentPermissionChoiceControls.tsx b/src/renderer/src/components/agent/launch-settings/AgentPermissionChoiceControls.tsx index 1b1659c8b4b..bac97f62d38 100644 --- a/src/renderer/src/components/agent/launch-settings/AgentPermissionChoiceControls.tsx +++ b/src/renderer/src/components/agent/launch-settings/AgentPermissionChoiceControls.tsx @@ -4,6 +4,7 @@ import type { Ref } from 'react' import type { AgentChatPermissionMode } from '../../../../../shared/agent-launch/permissions/agent-chat-permission-mode' import type { AgentPermissionChoice } from '../../../../../shared/agent-launch/permissions/tui-agent-permission-choice' import { translate } from '@/i18n/i18n' +import type { Translate } from '@/i18n/use-translate' import { cn } from '@/lib/utils' import { Button } from '../../ui/button' import { Label } from '../../ui/label' @@ -20,16 +21,17 @@ import { AgentPermissionChoiceItems, AgentPermissionChoiceName } from './AgentPe /** Use chat-mode wording; own names the agent when available. */ export function agentPermissionChoiceLabel( choice: AgentPermissionChoice, - agentLabel: string | null + agentLabel: string | null, + t: Translate = translate ): string { if (choice !== 'own') { - return nativeChatPermissionModeLabel(choice) + return nativeChatPermissionModeLabel(choice, t) } return agentLabel - ? translate('settings.agents.permissionOwnAgent', "Use {{agent}}'s own settings", { + ? t('settings.agents.permissionOwnAgent', "Use {{agent}}'s own settings", { agent: agentLabel }) - : translate('settings.agents.permissionOwnAll', "Agents' own settings") + : t('settings.agents.permissionOwnAll', "Agents' own settings") } export function AllAgentsPermissionSetting({ diff --git a/src/renderer/src/components/mobile/relay-clock-skew-message.ts b/src/renderer/src/components/mobile/relay-clock-skew-message.ts index 6f984484d9f..14ebea790af 100644 --- a/src/renderer/src/components/mobile/relay-clock-skew-message.ts +++ b/src/renderer/src/components/mobile/relay-clock-skew-message.ts @@ -1,7 +1,8 @@ import { translate } from '@/i18n/i18n' +import type { Translate } from '@/i18n/use-translate' -export function relayClockSkewMessage(clockOffsetMs: number): string { - return translate( +export function relayClockSkewMessage(clockOffsetMs: number, t: Translate = translate): string { + return t( 'auto.components.mobile.relayClockSkewMessage.message', "Your computer's clock is off by about {{seconds}} seconds. Turn on automatic time, then retry.", { seconds: Math.round(Math.abs(clockOffsetMs) / 1000) } diff --git a/src/renderer/src/components/plugins/list/plugin-list-entries.ts b/src/renderer/src/components/plugins/list/plugin-list-entries.ts index 139edcc8bea..3cd87cf6fa0 100644 --- a/src/renderer/src/components/plugins/list/plugin-list-entries.ts +++ b/src/renderer/src/components/plugins/list/plugin-list-entries.ts @@ -18,6 +18,7 @@ import { pluginDisplayNameFromKey } from './plugin-display-name' import { sanitizePluginDisplayText } from '../../../../../shared/plugins/manifest/plugin-display-text' import type { PluginAutoUpdateState } from '../../../../../shared/plugins/catalog/plugin-update-policy' import { withDisambiguatedPluginNames } from '../presentation/plugin-name-disambiguation' +import type { Translate } from '../../../i18n/use-translate' /** One plugin as the list and detail page show it, whether installed, listed, or both. */ export type PluginListEntry = { @@ -265,7 +266,8 @@ export function buildPluginListEntries( sources?: readonly PluginMarketplaceHostSourceState[] orcaVersion?: string | null autoUpdates?: PluginAutoUpdateState | null - } = {} + } = {}, + t?: Translate ): { installed: PluginListEntry[]; available: PluginListEntry[] } { const listingsByKey = new Map() for (const listing of listings) { @@ -275,54 +277,57 @@ export function buildPluginListEntries( const catalogIdsOf = (pluginKey: string): string[] => (listingsByKey.get(pluginKey) ?? []).map((listing) => listing.marketplaceSourceId) const installedKeys = new Set(installed.map((plugin) => plugin.pluginKey)) - return withDisambiguatedPluginNames({ - installed: installed.map((plugin) => { - const { listing, ownCatalog } = sourceListing( - plugin, - listingsByKey.get(plugin.pluginKey) ?? [], - catalogById - ) - const entry = pluginListEntryFromInstalled(plugin, listing, { - catalogIds: catalogIdsOf(plugin.pluginKey), - orcaVersion, - autoUpdates - }) - const own = ownCatalog && plugin.source?.marketplace ? listing : null - // Why bundled too: it ships the official catalog's release, never local bytes. - entry.installCountEligible = - !plugin.isDev && - ((plugin.source?.kind === 'marketplace' && isPublicCatalogListing(own, catalogById)) || - (plugin.bundled && isPublicCatalogListing(listing, catalogById))) - entry.alsoListedBy = (listingsByKey.get(plugin.pluginKey) ?? []) - .filter((candidate) => candidate !== own) - .map((candidate) => candidate.marketplaceName) - if (!own && entry.verifiedSource.kind === 'catalog') { - // Why: another catalog's listing must never lend the install its name. - const from = plugin.source?.marketplace?.reference - entry.verifiedSource.catalog = - sources.find((source) => source.source.url === from)?.marketplace?.name ?? null - } - return ownCatalog - ? entry - : { - ...entry, - updateVersion: null, - reinstallVersion: null, - updatable: false, - autoUpdate: { ...entry.autoUpdate, needsReview: false } - } - }), - available: [...listingsByKey.entries()] - .filter(([pluginKey]) => !installedKeys.has(pluginKey)) - .map(([pluginKey, candidates]) => ({ - ...pluginListEntryFromListing( - candidates[0], - catalogIdsOf(pluginKey), - catalogById.get(candidates[0].marketplaceSourceId)?.source.url ?? null - ), - installCountEligible: isPublicCatalogListing(candidates[0], catalogById) - })) - }) + return withDisambiguatedPluginNames( + { + installed: installed.map((plugin) => { + const { listing, ownCatalog } = sourceListing( + plugin, + listingsByKey.get(plugin.pluginKey) ?? [], + catalogById + ) + const entry = pluginListEntryFromInstalled(plugin, listing, { + catalogIds: catalogIdsOf(plugin.pluginKey), + orcaVersion, + autoUpdates + }) + const own = ownCatalog && plugin.source?.marketplace ? listing : null + // Why bundled too: it ships the official catalog's release, never local bytes. + entry.installCountEligible = + !plugin.isDev && + ((plugin.source?.kind === 'marketplace' && isPublicCatalogListing(own, catalogById)) || + (plugin.bundled && isPublicCatalogListing(listing, catalogById))) + entry.alsoListedBy = (listingsByKey.get(plugin.pluginKey) ?? []) + .filter((candidate) => candidate !== own) + .map((candidate) => candidate.marketplaceName) + if (!own && entry.verifiedSource.kind === 'catalog') { + // Why: another catalog's listing must never lend the install its name. + const from = plugin.source?.marketplace?.reference + entry.verifiedSource.catalog = + sources.find((source) => source.source.url === from)?.marketplace?.name ?? null + } + return ownCatalog + ? entry + : { + ...entry, + updateVersion: null, + reinstallVersion: null, + updatable: false, + autoUpdate: { ...entry.autoUpdate, needsReview: false } + } + }), + available: [...listingsByKey.entries()] + .filter(([pluginKey]) => !installedKeys.has(pluginKey)) + .map(([pluginKey, candidates]) => ({ + ...pluginListEntryFromListing( + candidates[0], + catalogIdsOf(pluginKey), + catalogById.get(candidates[0].marketplaceSourceId)?.source.url ?? null + ), + installCountEligible: isPublicCatalogListing(candidates[0], catalogById) + })) + }, + t + ) } /** Search across what a user can see or would type: name, key, publisher, description, categories. */ diff --git a/src/renderer/src/components/plugins/presentation/plugin-name-disambiguation.ts b/src/renderer/src/components/plugins/presentation/plugin-name-disambiguation.ts index 1d0165ed177..d9a1b029dcc 100644 --- a/src/renderer/src/components/plugins/presentation/plugin-name-disambiguation.ts +++ b/src/renderer/src/components/plugins/presentation/plugin-name-disambiguation.ts @@ -97,12 +97,16 @@ type NamedPluginEntry = Omit & { } /** Installed and listed plugins sharing a name: the non-official ones show their publisher. */ -export function withDisambiguatedPluginNames(lists: { - installed: Entry[] - available: Entry[] -}): { installed: Entry[]; available: Entry[] } { +export function withDisambiguatedPluginNames( + lists: { + installed: Entry[] + available: Entry[] + }, + t?: Translate +): { installed: Entry[]; available: Entry[] } { const names = disambiguatedPluginNames( - [...lists.installed, ...lists.available].map((entry) => ({ ...entry, key: entry.pluginKey })) + [...lists.installed, ...lists.available].map((entry) => ({ ...entry, key: entry.pluginKey })), + t ) const rename = (entry: Entry): Entry => { const name = names.get(entry.pluginKey) ?? entry.name diff --git a/src/renderer/src/components/provider-accounts/provider-account-scope.ts b/src/renderer/src/components/provider-accounts/provider-account-scope.ts index a373ce52448..4039caf337f 100644 --- a/src/renderer/src/components/provider-accounts/provider-account-scope.ts +++ b/src/renderer/src/components/provider-accounts/provider-account-scope.ts @@ -1,4 +1,5 @@ import { translate } from '@/i18n/i18n' +import type { Translate } from '@/i18n/use-translate' import { getLocalExecutionHostLabel } from '../../../../shared/execution-host' import type { GlobalSettings } from '../../../../shared/global-settings-types' import { defaultScopeHost } from '@/lib/default-creation-host' @@ -10,8 +11,8 @@ export type ProviderHostScope = { description: string } -function remoteServerScopeLabel(name: string): string { - return translate( +function remoteServerScopeLabel(name: string, t: Translate = translate): string { + return t( 'auto.components.settings.providerAccountScope.remoteServer', 'Remote server: {{value0}}', { value0: name } @@ -25,17 +26,17 @@ function providerHostScopeLabel(target: RuntimeClientTarget): string { } /** Accounts-pane scope while a remote server owns the roster (#8186). */ -export function getRemoteAccountsPaneScope(serverName: string | null): ProviderHostScope { +export function getRemoteAccountsPaneScope( + serverName: string | null, + t: Translate = translate +): ProviderHostScope { const name = serverName?.trim() return { // Unloaded server names need bare fallback labels; interpolation would repeat Remote server. label: name - ? remoteServerScopeLabel(name) - : translate( - 'auto.components.settings.AccountsPane.accountScopeRemoteServerUnnamed', - 'Remote server' - ), - description: translate( + ? remoteServerScopeLabel(name, t) + : t('auto.components.settings.AccountsPane.accountScopeRemoteServerUnnamed', 'Remote server'), + description: t( 'auto.components.settings.AccountsPane.remoteScopeLocalAccountsKeptHostPicker', 'Accounts managed on this desktop are unchanged. Choose this computer in the Host menu at the top of Settings to view them.' ) diff --git a/src/renderer/src/components/settings/accounts/AccountsPane.tsx b/src/renderer/src/components/settings/accounts/AccountsPane.tsx index 0cc92a621d8..be9aa57d2b7 100644 --- a/src/renderer/src/components/settings/accounts/AccountsPane.tsx +++ b/src/renderer/src/components/settings/accounts/AccountsPane.tsx @@ -7,6 +7,7 @@ import type { CodexConfigSyncStatus } from '../../../../../shared/codex-config-s import { toast } from 'sonner' import { useAppStore } from '../../../store/index' import { translate } from '@/i18n/i18n' +import { useIntlLocale, useTranslate } from '@/i18n/use-translate' import { isWebClientLocation } from '@/lib/web-client-location' import { emptyClaudeAccountsState, @@ -86,6 +87,8 @@ export function AccountsPane({ accountOwnerPlatform = null, wslHostDistro = null }: AccountsPaneProps): React.JSX.Element { + const t = useTranslate() + const intlLocale = useIntlLocale() const searchQuery = useAppStore((s) => s.settingsSearchQuery) const codexRateLimits = useAppStore((s) => s.rateLimits.codex) const codexRateLimitTarget = useAppStore((s) => s.rateLimits.codexTarget) @@ -102,7 +105,8 @@ export function AccountsPane({ wslAvailable, wslDistros, wslCapabilitiesLoading, - wslHostDistro + wslHostDistro, + t ) // Route all account actions to the Settings-selected owner, independent of focused host (#7973). const accountOwner = hostScope.target @@ -122,7 +126,7 @@ export function AccountsPane({ : null const remoteServerLabel = isRemoteAccountScope ? (remoteServerName ?? - translate('auto.components.settings.AccountsPane.remoteServerFallback', 'the remote server')) + t('auto.components.settings.AccountsPane.remoteServerFallback', 'the remote server')) : null const accountRuntime = isRemoteAccountScope ? { runtime: 'host' as const, label: remoteServerLabel ?? '' } @@ -135,11 +139,8 @@ export function AccountsPane({ const remoteAccountScopeNotice = isRemoteAccountScope && !isWebClientLocation() ? ( ) : null @@ -335,16 +336,16 @@ export function AccountsPane({ !isRemoteAccountScope && !wslHostDistro && matchesSettingsSearch(searchQuery, getAccountsLocationSearchEntries()) - ? renderAccountsLocationSection(model) + ? renderAccountsLocationSection(model, t) : null, matchesSettingsSearch(searchQuery, getAccountsClaudeSearchEntries()) - ? renderClaudeAccountsSection(model) + ? renderClaudeAccountsSection(model, t, intlLocale) : null, matchesSettingsSearch(searchQuery, getAccountsCodexSearchEntries()) - ? renderCodexAccountsSection(model) + ? renderCodexAccountsSection(model, t, intlLocale) : null, matchesSettingsSearch(searchQuery, getAccountsGeminiSearchEntries()) - ? renderGeminiAccountsSection(model) + ? renderGeminiAccountsSection(model, t) : null, matchesSettingsSearch(searchQuery, getAccountsAntigravitySearchEntries()) ? ( ) : null, matchesSettingsSearch(searchQuery, getAccountsOpencodeSearchEntries()) - ? renderOpenCodeAccountsSection(model) + ? renderOpenCodeAccountsSection(model, t) : null, matchesSettingsSearch(searchQuery, getAccountsMiniMaxSearchEntries()) ? ( - {renderAccountsRemovalDialogs(model, removeCodexTarget, removeClaudeTarget)} + {renderAccountsRemovalDialogs(model, removeCodexTarget, removeClaudeTarget, t)} ) diff --git a/src/renderer/src/components/settings/accounts/accounts-pane-removal-dialogs.tsx b/src/renderer/src/components/settings/accounts/accounts-pane-removal-dialogs.tsx index 0d4c3f05bb3..efd18c7f1c1 100644 --- a/src/renderer/src/components/settings/accounts/accounts-pane-removal-dialogs.tsx +++ b/src/renderer/src/components/settings/accounts/accounts-pane-removal-dialogs.tsx @@ -1,4 +1,5 @@ import { translate } from '@/i18n/i18n' +import type { Translate } from '@/i18n/use-translate' import { removeClaudeProviderAccount, removeCodexProviderAccount @@ -17,7 +18,8 @@ import type { AccountsPaneSectionModel, RemoveAccountTarget } from './accounts-p export function renderAccountsRemovalDialogs( model: AccountsPaneSectionModel, removeCodexTarget: RemoveAccountTarget | null, - removeClaudeTarget: RemoveAccountTarget | null + removeClaudeTarget: RemoveAccountTarget | null, + t: Translate = translate ): React.JSX.Element { const { runClaudeAccountAction, @@ -35,13 +37,10 @@ export function renderAccountsRemovalDialogs( - {translate( - 'auto.components.settings.AccountsPane.0d47394635', - 'Remove Codex Account?' - )} + {t('auto.components.settings.AccountsPane.0d47394635', 'Remove Codex Account?')} - {translate( + {t( 'auto.components.settings.AccountsPane.380a7736cc', 'Removing this account permanently deletes its managed Codex home, including all Codex session history and MCP logins stored inside. This cannot be undone. If the account is currently active, Orca falls back to the system default Codex login.' )} @@ -49,7 +48,7 @@ export function renderAccountsRemovalDialogs( @@ -78,13 +77,10 @@ export function renderAccountsRemovalDialogs( - {translate( - 'auto.components.settings.AccountsPane.63843e37e2', - 'Remove Claude Account?' - )} + {t('auto.components.settings.AccountsPane.63843e37e2', 'Remove Claude Account?')} - {translate( + {t( 'settings.accounts.claudeRemoveFolder', "Orca deletes this account's Claude folder and its sign-in from this computer. Chat history shared with System default stays. If it is selected, the next Claude you start uses System default." )} @@ -92,7 +88,7 @@ export function renderAccountsRemovalDialogs( diff --git a/src/renderer/src/components/settings/accounts/claude/accounts-pane-claude-section.tsx b/src/renderer/src/components/settings/accounts/claude/accounts-pane-claude-section.tsx index 66c2cd00294..117ef72b818 100644 --- a/src/renderer/src/components/settings/accounts/claude/accounts-pane-claude-section.tsx +++ b/src/renderer/src/components/settings/accounts/claude/accounts-pane-claude-section.tsx @@ -1,5 +1,6 @@ import { Loader2, Plus, RefreshCw, Trash2, X } from 'lucide-react' import { translate } from '@/i18n/i18n' +import type { Translate } from '@/i18n/use-translate' import { selectClaudeProviderAccount } from '@/runtime/provider-accounts/observations/runtime-provider-accounts-client' import { Badge } from '../../../ui/badge' import { Button } from '../../../ui/button' @@ -31,7 +32,11 @@ import { } from '../sign-in/account-add-cancel-target' import { SettingsGroup } from '../../section-layout/SettingsGroup' -export function renderClaudeAccountsSection(model: AccountsPaneSectionModel): React.JSX.Element { +export function renderClaudeAccountsSection( + model: AccountsPaneSectionModel, + t: Translate = translate, + intlLocale?: string +): React.JSX.Element { const { accountRuntime, accountRuntimeSentenceLabel, @@ -84,17 +89,17 @@ export function renderClaudeAccountsSection(model: AccountsPaneSectionModel): Re title={ - {translate('auto.components.settings.AccountsPane.26ef4b55be', 'Claude')} + {t('auto.components.settings.AccountsPane.26ef4b55be', 'Claude')} } - description={translate( + description={t( 'auto.components.settings.AccountsPane.72b36ea174', 'Optional. Orca can use your normal Claude login; add accounts only if you want quick switching without moving chat sessions.' )} >
- +

{isRemoteAccountScope - ? translate( + ? t( 'accounts.remoteScopeAccountsAddHere', 'Showing accounts managed by {{value0}}. New accounts sign in there; you finish in this device’s browser.', { value0: accountRuntimeSentenceLabel } ) - : translate( + : t( 'auto.components.settings.AccountsPane.c0a52abfc5', 'Showing accounts for {{value0}}. New accounts are added there.', { value0: accountRuntimeSentenceLabel } @@ -134,7 +137,7 @@ export function renderClaudeAccountsSection(model: AccountsPaneSectionModel): Re ) : ( )} - {translate('auto.components.settings.AccountsPane.b0e948a4f9', 'Add Account')} + {t('auto.components.settings.AccountsPane.b0e948a4f9', 'Add Account')} {!isRemoteAccountScope && canOfferClaudeSignInLink(accountRuntime.runtime) ? ( - {translate('auto.components.settings.AccountsPane.dbb9626ed1', 'Cancel')} + {t('auto.components.settings.AccountsPane.dbb9626ed1', 'Cancel')} ) : null}

@@ -195,29 +198,22 @@ export function renderClaudeAccountsSection(model: AccountsPaneSectionModel): Re
{systemDefaultEmail - ? translate( - 'accounts.claude.systemDefaultNamed', - 'System default: {{value0}}', - { - value0: systemDefaultEmail - } - ) - : translate( - 'auto.components.settings.AccountsPane.f2a265f8c7', - 'System default' - )} + ? t('accounts.claude.systemDefaultNamed', 'System default: {{value0}}', { + value0: systemDefaultEmail + }) + : t('auto.components.settings.AccountsPane.f2a265f8c7', 'System default')} {systemClaudeActive ? ( - {translate('auto.components.settings.AccountsPane.e74831fb6b', 'Active')} + {t('auto.components.settings.AccountsPane.e74831fb6b', 'Active')} ) : null}
- {translate( + {t( 'auto.components.settings.AccountsPane.e05d0ff737', 'Use your current {{value0}} Claude login.', { value0: accountRuntimeSentenceLabel } @@ -228,7 +224,7 @@ export function renderClaudeAccountsSection(model: AccountsPaneSectionModel): Re {systemDefaultIsSaved ? (

- {translate( + {t( 'accounts.claude.systemDefaultIsSaved', 'System default is signed in as {{value0}}, which is also a saved account. An earlier Orca version may have copied that login there. If it is not your own login, select System default and run claude /login.', { value0: systemDefaultEmail } @@ -239,19 +235,19 @@ export function renderClaudeAccountsSection(model: AccountsPaneSectionModel): Re size="xs" onClick={() => updateSettings({ claudeCopiedSystemDefaultNoticeDismissed: true })} > - {translate('accounts.claude.systemDefaultIsSavedDismiss', 'Dismiss')} + {t('accounts.claude.systemDefaultIsSavedDismiss', 'Dismiss')}

) : null} {visibleClaudeAccounts.length === 0 ? (
{isRemoteAccountScope - ? translate( + ? t( 'auto.components.settings.AccountsPane.remoteEmptyClaudeAccounts', 'No managed Claude accounts on {{value0}}. It uses its system default Claude login; add accounts on that server.', { value0: accountRuntimeSentenceLabel } ) - : translate( + : t( 'auto.components.settings.AccountsPane.3fe7862418', "No managed Claude accounts for {{value0}}. Orca will use that environment's system default Claude login until you add one here.", { value0: accountRuntimeSentenceLabel } @@ -321,22 +317,19 @@ export function renderClaudeAccountsSection(model: AccountsPaneSectionModel): Re variant="outline" className="h-4 shrink-0 rounded px-1.5 text-[10px] font-medium leading-none text-foreground/80" > - {translate( - 'auto.components.settings.AccountsPane.e74831fb6b', - 'Active' - )} + {t('auto.components.settings.AccountsPane.e74831fb6b', 'Active')} ) : null}
{account.needsSignIn - ? translate( + ? t( 'accounts.claude.signInRequired', 'Sign in again to use this account' ) : account.organizationName - ? `${account.organizationName} · ${formatAccountTimestamp(account.lastAuthenticatedAt)}` - : formatAccountTimestamp(account.lastAuthenticatedAt)} + ? `${account.organizationName} · ${formatAccountTimestamp(account.lastAuthenticatedAt, intlLocale)}` + : formatAccountTimestamp(account.lastAuthenticatedAt, intlLocale)}
@@ -356,7 +349,7 @@ export function renderClaudeAccountsSection(model: AccountsPaneSectionModel): Re ) : ( )} - {translate('accounts.claude.signInAgain', 'Sign in again')} + {t('accounts.claude.signInAgain', 'Sign in again')} {canOfferClaudeSignInLink(accountRuntimeView.runtime) ? ( - {translate('auto.components.settings.AccountsPane.db209ee572', 'Remove')} + {t('auto.components.settings.AccountsPane.db209ee572', 'Remove')}
diff --git a/src/renderer/src/components/settings/accounts/claude/claude-running-session-switch-setting.tsx b/src/renderer/src/components/settings/accounts/claude/claude-running-session-switch-setting.tsx index 1fa6b9fa3b8..3daa2947443 100644 --- a/src/renderer/src/components/settings/accounts/claude/claude-running-session-switch-setting.tsx +++ b/src/renderer/src/components/settings/accounts/claude/claude-running-session-switch-setting.tsx @@ -1,6 +1,6 @@ import type { GlobalSettings } from '../../../../../../shared/global-settings-types' import type { ClaudeAccountSwitchRunningSessions } from '../../../../../../shared/claude-account-switch-settings' -import { translate } from '@/i18n/i18n' +import { useTranslate } from '@/i18n/use-translate' import { Label } from '../../../ui/label' import { Select, SelectContent, SelectItem, SelectTrigger, SelectValue } from '../../../ui/select' import { SearchableSetting } from '../../section-layout/SearchableSetting' @@ -18,11 +18,12 @@ export function ClaudeRunningSessionSwitchSetting({ settings: GlobalSettings updateSettings: (updates: Partial) => void }): React.JSX.Element { - const title = translate( + const t = useTranslate() + const title = t( 'accounts.claude.switchSessions.settingTitle', 'When you switch accounts while Claude is running in terminals' ) - const description = translate( + const description = t( 'accounts.claude.switchSessions.settingDescription', 'Restarting continues each terminal conversation on the new account.' ) @@ -50,13 +51,13 @@ export function ClaudeRunningSessionSwitchSetting({ - {translate('accounts.claude.switchSessions.settingAsk', 'Ask')} + {t('accounts.claude.switchSessions.settingAsk', 'Ask')} - {translate('accounts.claude.switchSessions.settingRestart', 'Always restart')} + {t('accounts.claude.switchSessions.settingRestart', 'Always restart')} - {translate('accounts.claude.switchSessions.settingKeep', 'Never restart')} + {t('accounts.claude.switchSessions.settingKeep', 'Never restart')} diff --git a/src/renderer/src/components/settings/accounts/claude/claude-sign-in-link-menu.tsx b/src/renderer/src/components/settings/accounts/claude/claude-sign-in-link-menu.tsx index 0674a3170a5..9855d003c66 100644 --- a/src/renderer/src/components/settings/accounts/claude/claude-sign-in-link-menu.tsx +++ b/src/renderer/src/components/settings/accounts/claude/claude-sign-in-link-menu.tsx @@ -1,6 +1,7 @@ import { ChevronDown, Link } from 'lucide-react' import { toast } from 'sonner' import { translate } from '@/i18n/i18n' +import { useTranslate } from '@/i18n/use-translate' import type { ClaudeRateLimitAccountsState } from '../../../../../../shared/managed-account-types' import { canCopyClaudeSignInLink } from '../../../../../../shared/claude-sign-in-link' import { Button } from '../../../ui/button' @@ -53,6 +54,7 @@ export function ClaudeSignInLinkMenu({ disabled: boolean onCopyLink: () => void }): React.JSX.Element { + const t = useTranslate() return ( @@ -60,7 +62,7 @@ export function ClaudeSignInLinkMenu({ variant={variant} size="icon-xs" disabled={disabled} - aria-label={translate('accounts.claude.moreSignInOptions', 'More sign-in options')} + aria-label={t('accounts.claude.moreSignInOptions', 'More sign-in options')} > @@ -68,7 +70,7 @@ export function ClaudeSignInLinkMenu({ - {translate('accounts.claude.copySignInLink', 'Copy sign-in link instead')} + {t('accounts.claude.copySignInLink', 'Copy sign-in link instead')} diff --git a/src/renderer/src/components/settings/accounts/codex/CodexLoginLinkNotice.tsx b/src/renderer/src/components/settings/accounts/codex/CodexLoginLinkNotice.tsx index 8c111e69e6c..c8d0cbd444e 100644 --- a/src/renderer/src/components/settings/accounts/codex/CodexLoginLinkNotice.tsx +++ b/src/renderer/src/components/settings/accounts/codex/CodexLoginLinkNotice.tsx @@ -1,11 +1,12 @@ import { Check, Copy, ExternalLink } from 'lucide-react' -import { translate } from '@/i18n/i18n' +import { useTranslate } from '@/i18n/use-translate' import { useClipboardTextCopyFeedback } from '@/hooks/use-clipboard-text-copy-feedback' import { Button } from '../../../ui/button' import { useCodexPendingLoginUrl } from './use-codex-pending-login-url' /** Expose in-flight login links so users can finish in a different browser. */ export function CodexLoginLinkNotice(): React.JSX.Element | null { + const t = useTranslate() const url = useCodexPendingLoginUrl() const { copyText, status } = useClipboardTextCopyFeedback(url ?? '') if (!url) { @@ -14,7 +15,7 @@ export function CodexLoginLinkNotice(): React.JSX.Element | null { return (

- {translate( + {t( 'auto.components.settings.AccountsPane.codexLoginLinkPending', 'Codex opened this sign-in link in your browser. Copy it to finish signing in somewhere else — a private window, or another profile.' )} @@ -26,17 +27,17 @@ export function CodexLoginLinkNotice(): React.JSX.Element | null {

{status === 'failed' ? (

- {translate( + {t( 'auto.components.settings.AccountsPane.codexLoginLinkCopyFailed', 'Could not copy the link.' )} diff --git a/src/renderer/src/components/settings/accounts/codex/accounts-pane-codex-account-row.tsx b/src/renderer/src/components/settings/accounts/codex/accounts-pane-codex-account-row.tsx index aa4ede4655d..f5f6f4f5916 100644 --- a/src/renderer/src/components/settings/accounts/codex/accounts-pane-codex-account-row.tsx +++ b/src/renderer/src/components/settings/accounts/codex/accounts-pane-codex-account-row.tsx @@ -1,6 +1,7 @@ import { Loader2, RefreshCw, Trash2 } from 'lucide-react' import type { CodexRateLimitAccountsState } from '../../../../../../shared/managed-account-types' import { translate } from '@/i18n/i18n' +import type { Translate } from '@/i18n/use-translate' import { getCodexAccountDisplayDetail } from '@/lib/codex-account-display-label' import { selectCodexProviderAccount } from '@/runtime/provider-accounts/observations/runtime-provider-accounts-client' import { Badge } from '../../../ui/badge' @@ -15,7 +16,9 @@ import type { AccountsPaneSectionModel } from '../accounts-pane-types' export function renderCodexAccountRow( account: CodexRateLimitAccountsState['accounts'][number], - model: AccountsPaneSectionModel + model: AccountsPaneSectionModel, + t: Translate = translate, + intlLocale?: string ): React.JSX.Element { const { accountRuntime, @@ -95,7 +98,7 @@ export function renderCodexAccountRow( variant="outline" className="h-4 shrink-0 rounded px-1.5 text-[10px] font-medium leading-none text-foreground/80" > - {translate('auto.components.settings.AccountsPane.e74831fb6b', 'Active')} + {t('auto.components.settings.AccountsPane.e74831fb6b', 'Active')} ) : null} {needsReauthentication ? ( @@ -103,7 +106,7 @@ export function renderCodexAccountRow( variant="destructive" className="h-4 shrink-0 rounded px-1.5 text-[10px] font-medium leading-none" > - {translate('auto.components.settings.AccountsPane.589eba1eee', 'Needs re-auth')} + {t('auto.components.settings.AccountsPane.589eba1eee', 'Needs re-auth')} ) : null} @@ -120,14 +123,16 @@ export function renderCodexAccountRow( ) : null} {needsReauthentication ? ( - {translate( + {t( 'auto.components.settings.AccountsPane.3d245ef7d9', 'Codex reported this sign-in is out of date' )} ) : null} {needsReauthentication ? • : null} - {formatAccountTimestamp(account.lastAuthenticatedAt)} + + {formatAccountTimestamp(account.lastAuthenticatedAt, intlLocale)} + @@ -155,7 +160,7 @@ export function renderCodexAccountRow( ) : ( )} - {translate('auto.components.settings.AccountsPane.8a0f870153', 'Re-authenticate')} + {t('auto.components.settings.AccountsPane.8a0f870153', 'Re-authenticate')} diff --git a/src/renderer/src/components/settings/accounts/codex/accounts-pane-codex-section.tsx b/src/renderer/src/components/settings/accounts/codex/accounts-pane-codex-section.tsx index d5468983467..d309f31d96a 100644 --- a/src/renderer/src/components/settings/accounts/codex/accounts-pane-codex-section.tsx +++ b/src/renderer/src/components/settings/accounts/codex/accounts-pane-codex-section.tsx @@ -1,5 +1,6 @@ import { AlertTriangle, Loader2, Plus, X } from 'lucide-react' import { translate } from '@/i18n/i18n' +import type { Translate } from '@/i18n/use-translate' import { selectCodexProviderAccount } from '@/runtime/provider-accounts/observations/runtime-provider-accounts-client' import { Badge } from '../../../ui/badge' import { Button } from '../../../ui/button' @@ -20,7 +21,11 @@ import { } from '../sign-in/account-add-cancel-target' import { SettingsGroup } from '../../section-layout/SettingsGroup' -export function renderCodexAccountsSection(model: AccountsPaneSectionModel): React.JSX.Element { +export function renderCodexAccountsSection( + model: AccountsPaneSectionModel, + t: Translate = translate, + intlLocale?: string +): React.JSX.Element { const { accountRuntime, accountRuntimeSentenceLabel, @@ -47,25 +52,25 @@ export function renderCodexAccountsSection(model: AccountsPaneSectionModel): Rea title={ - {translate('auto.components.settings.AccountsPane.ef91cfa06b', 'Codex')} + {t('auto.components.settings.AccountsPane.ef91cfa06b', 'Codex')} } description={ <> - {translate( + {t( 'auto.components.settings.AccountsPane.cedfab35ab', 'Optional. Orca can use your normal Codex login; add accounts only if you want quick switching in Orca.' )} {isRemoteAccountScope - ? translate( + ? t( 'auto.components.settings.AccountsPane.remoteScopeAuthContext', 'Each account keeps its own sign-in context on {{value0}}.', { value0: accountRuntimeSentenceLabel } ) - : translate( + : t( 'auto.components.settings.AccountsPane.340d6f7a85', 'Each account keeps its own local sign-in context in Orca. Account auth stays on this device.' )} @@ -74,8 +79,8 @@ export function renderCodexAccountsSection(model: AccountsPaneSectionModel): Rea } > {systemCodexMissingSignIn - ? translate( + ? t( 'auto.components.settings.AccountsPane.codexSystemDefaultNeedsSignIn', 'No Codex sign-in was found for {{value0}}.', { value0: accountRuntimeSentenceLabel } ) : activeCodexAccountId - ? translate( + ? t( 'auto.components.settings.AccountsPane.75ca9b718e', 'Codex reported that the active account needs a fresh sign-in. Re-authenticate it before starting new Codex sessions.' ) - : translate( + : t( 'auto.components.settings.AccountsPane.e4a28e8894', 'Codex reported that the {{value0}} login needs a fresh sign-in. Sign in again before starting new Codex sessions.', { value0: accountRuntimeSentenceLabel } @@ -116,23 +121,23 @@ export function renderCodexAccountsSection(model: AccountsPaneSectionModel): Rea {codexConfigSyncWarning === 'managed-home-unavailable' - ? translate( + ? t( 'auto.components.settings.AccountsPane.codexConfigSyncManagedHomeUnavailable', 'Orca could not read this account’s Codex files just now, so settings may not be syncing. This usually clears on its own — antivirus or a backup tool briefly locks them.' ) : codexConfigSyncWarning === 'missing-source' - ? translate( + ? t( 'auto.components.settings.AccountsPane.codexConfigSyncMissingSource', 'Codex is still using the settings it last synced because {{value0}} is missing. Restore that file to resume syncing.', { value0: codexConfigSync?.systemConfigPath ?? '' } ) : codexConfigSyncWarning === 'blank-source' - ? translate( + ? t( 'auto.components.settings.AccountsPane.codexConfigSyncBlankSource', 'Codex is still using the settings it last synced because {{value0}} is empty. That is expected while a synced folder finishes downloading.', { value0: codexConfigSync?.systemConfigPath ?? '' } ) - : translate( + : t( 'auto.components.settings.AccountsPane.codexConfigSyncUnreadableSource', "Codex is still using the settings it last synced because {{value0}} could not be read. Check that file's permissions.", { value0: codexConfigSync?.systemConfigPath ?? '' } @@ -142,17 +147,15 @@ export function renderCodexAccountsSection(model: AccountsPaneSectionModel): Rea ) : null}

- +

{isRemoteAccountScope - ? translate( + ? t( 'accounts.remoteScopeAccountsAddHere', 'Showing accounts managed by {{value0}}. New accounts sign in there; you finish in this device’s browser.', { value0: accountRuntimeSentenceLabel } ) - : translate( + : t( 'auto.components.settings.AccountsPane.c0a52abfc5', 'Showing accounts for {{value0}}. New accounts are added there.', { value0: accountRuntimeSentenceLabel } @@ -186,7 +189,7 @@ export function renderCodexAccountsSection(model: AccountsPaneSectionModel): Rea ) : ( )} - {translate('auto.components.settings.AccountsPane.b0e948a4f9', 'Add Account')} + {t('auto.components.settings.AccountsPane.b0e948a4f9', 'Add Account')} {/* Follow the running addition; hide Cancel only when its server prompt already offers it. */} {codexAction === 'adding' && !accountAdditionShownInPrompt('codex', accountOwner) ? ( @@ -201,7 +204,7 @@ export function renderCodexAccountsSection(model: AccountsPaneSectionModel): Rea className="gap-1.5" > - {translate('auto.components.settings.AccountsPane.dbb9626ed1', 'Cancel')} + {t('auto.components.settings.AccountsPane.dbb9626ed1', 'Cancel')} ) : null}

@@ -242,17 +245,14 @@ export function renderCodexAccountsSection(model: AccountsPaneSectionModel): Rea
- {translate( - 'auto.components.settings.AccountsPane.f2a265f8c7', - 'System default' - )} + {t('auto.components.settings.AccountsPane.f2a265f8c7', 'System default')} {systemCodexActive ? ( - {translate('auto.components.settings.AccountsPane.e74831fb6b', 'Active')} + {t('auto.components.settings.AccountsPane.e74831fb6b', 'Active')} ) : null} {systemCodexNeedsSignIn ? ( @@ -260,10 +260,7 @@ export function renderCodexAccountsSection(model: AccountsPaneSectionModel): Rea variant="destructive" className="h-4 shrink-0 rounded px-1.5 text-[10px] font-medium leading-none" > - {translate( - 'auto.components.settings.AccountsPane.93c47b333a', - 'Needs sign-in' - )} + {t('auto.components.settings.AccountsPane.93c47b333a', 'Needs sign-in')} ) : null}
@@ -274,19 +271,20 @@ export function renderCodexAccountsSection(model: AccountsPaneSectionModel): Rea > {systemCodexNeedsSignIn ? systemCodexMissingSignIn - ? translate( + ? t( 'auto.components.settings.AccountsPane.codexSystemDefaultNeedsSignIn', 'No Codex sign-in was found for {{value0}}.', { value0: accountRuntimeSentenceLabel } ) - : translate( + : t( 'auto.components.settings.AccountsPane.fd62f37c24', 'Codex reported this {{value0}} login is out of date.', { value0: accountRuntimeSentenceLabel } ) : getCodexSystemDefaultSubtitle( systemCodexIdentity, - accountRuntimeSentenceLabel + accountRuntimeSentenceLabel, + t )}
@@ -294,19 +292,21 @@ export function renderCodexAccountsSection(model: AccountsPaneSectionModel): Rea {visibleCodexAccounts.length === 0 ? (
{isRemoteAccountScope - ? translate( + ? t( 'auto.components.settings.AccountsPane.remoteEmptyCodexAccounts', 'No managed Codex accounts on {{value0}}. It uses its system default Codex login; add accounts on that server.', { value0: accountRuntimeSentenceLabel } ) - : translate( + : t( 'auto.components.settings.AccountsPane.b4c9450319', "No managed Codex accounts for {{value0}}. Orca will use that environment's system default Codex login until you add one here.", { value0: accountRuntimeSentenceLabel } )}
) : ( - visibleCodexAccounts.map((account) => renderCodexAccountRow(account, model)) + visibleCodexAccounts.map((account) => + renderCodexAccountRow(account, model, t, intlLocale) + ) )}
diff --git a/src/renderer/src/components/settings/accounts/orca/OrcaAccountSettingsPane.tsx b/src/renderer/src/components/settings/accounts/orca/OrcaAccountSettingsPane.tsx index 34c4750806f..c4c5645da55 100644 --- a/src/renderer/src/components/settings/accounts/orca/OrcaAccountSettingsPane.tsx +++ b/src/renderer/src/components/settings/accounts/orca/OrcaAccountSettingsPane.tsx @@ -5,36 +5,38 @@ import { Button } from '@/components/ui/button' import { useOrcaProfileAuthStatusRefresh } from '@/hooks/use-orca-profile-auth-status-refresh' import { cn } from '@/lib/utils' import { translate } from '@/i18n/i18n' +import { useTranslate, type Translate } from '@/i18n/use-translate' import { useAppStore } from '@/store/index' import { OrcaProfileSignOutConfirmDialog } from '../../../orca-profiles/OrcaProfileSignOutConfirmDialog' import { OrcaSignInButton, OrcaSignInWaitNotice } from '../../../OrcaSignInProgress' function accountStatusCopy( state: 'local' | 'unconfigured' | 'connected' | 'reconnect-required' | undefined, - email: string | undefined + email: string | undefined, + t: Translate = translate ): string { if (state === 'connected') { - return email ?? translate('auto.components.settings.orcaAccount.connected', 'Connected') + return email ?? t('auto.components.settings.orcaAccount.connected', 'Connected') } if (state === 'reconnect-required') { - return translate( + return t( 'auto.components.settings.orcaAccount.reconnectRequired', 'Your session expired. Sign in again to use cloud features.' ) } if (state === 'unconfigured') { - return translate( + return t( 'auto.components.settings.orcaAccount.unavailable', 'Orca sign-in is unavailable in this build.' ) } if (state === 'local') { - return translate( + return t( 'auto.components.settings.orcaAccount.signedOut', 'Sign in to extend Orca with cloud features, including Artifacts and Orca Relay.' ) } - return translate('auto.components.settings.orcaAccount.checking', 'Checking account status…') + return t('auto.components.settings.orcaAccount.checking', 'Checking account status…') } function AccountBenefit({ @@ -60,6 +62,7 @@ function AccountBenefit({ } export function OrcaAccountSettingsPane(): React.JSX.Element { + const t = useTranslate() const authStatus = useAppStore((state) => state.orcaProfileAuthStatus) const signOut = useAppStore((state) => state.signOutCurrentOrcaProfile) const [signOutOpen, setSignOutOpen] = useState(false) @@ -92,17 +95,17 @@ export function OrcaAccountSettingsPane(): React.JSX.Element {

{authStatus?.cloud?.displayName?.trim() || - translate('auto.components.settings.orcaAccount.account', 'Orca account')} + t('auto.components.settings.orcaAccount.account', 'Orca account')}

{connected ? ( - {translate('auto.components.settings.orcaAccount.connected', 'Connected')} + {t('auto.components.settings.orcaAccount.connected', 'Connected')} ) : null}

- {accountStatusCopy(authStatus?.state, authStatus?.cloud?.email)} + {accountStatusCopy(authStatus?.state, authStatus?.cloud?.email, t)}

{connected ? ( @@ -113,13 +116,13 @@ export function OrcaAccountSettingsPane(): React.JSX.Element { disabled={signingOut} onClick={() => setSignOutOpen(true)} > - {translate('auto.components.settings.orcaAccount.signOut', 'Sign out')} + {t('auto.components.settings.orcaAccount.signOut', 'Sign out')} ) : ( {authStatus?.state === 'reconnect-required' - ? translate('auto.components.settings.orcaAccount.signInAgain', 'Sign in again') - : translate('auto.components.settings.orcaAccount.signIn', 'Sign in to Orca')} + ? t('auto.components.settings.orcaAccount.signInAgain', 'Sign in again') + : t('auto.components.settings.orcaAccount.signIn', 'Sign in to Orca')} )} {connected ? null : } @@ -127,21 +130,15 @@ export function OrcaAccountSettingsPane(): React.JSX.Element {

- {translate( - 'auto.components.settings.orcaAccount.benefitsTitle', - 'Included with your account' - )} + {t('auto.components.settings.orcaAccount.benefitsTitle', 'Included with your account')}

(null) const [usage, setUsage] = useState(null) const [error, setError] = useState(null) @@ -188,13 +186,13 @@ export function AntigravityAccountsSection({ title={ - {translate('accounts.antigravity.title', 'Antigravity')} + {t('accounts.antigravity.title', 'Antigravity')} } >

- {translate( + {t( 'accounts.antigravity.signIn', 'Start agy on this host and complete its browser sign-in, then save the current account. To add a different account, use /logout in agy and sign in again.' )}{' '} @@ -205,7 +203,7 @@ export function AntigravityAccountsSection({ onClick={openAppHttpLinkFromAnchor} rel="noopener noreferrer" > - {translate('accounts.antigravity.docs', 'Sign-in instructions')} + {t('accounts.antigravity.docs', 'Sign-in instructions')}

{error && ( @@ -215,7 +213,7 @@ export function AntigravityAccountsSection({ )} {!state && error && ( )} {state && ( @@ -223,23 +221,17 @@ export function AntigravityAccountsSection({

{state.currentAccount ? (state.currentAccount.email ?? - translate( - 'accounts.antigravity.identityUnknown', - 'Signed-in identity unavailable' - )) - : translate( - 'accounts.antigravity.signedOut', - 'No native agy account is signed in.' - )} + t('accounts.antigravity.identityUnknown', 'Signed-in identity unavailable')) + : t('accounts.antigravity.signedOut', 'No native agy account is signed in.')}

{state.selectedAccountId && state.activeAccountId !== state.selectedAccountId && (

{windowsHost - ? translate( + ? t( 'accounts.antigravity.windowsChanged', 'The host default account changed. Select a saved account to apply it again.' ) - : translate( + : t( 'accounts.antigravity.changed', 'The native account changed. Select a saved account again before launching agy.' )} @@ -251,7 +243,7 @@ export function AntigravityAccountsSection({ disabled={busy || !state.currentAccount} onClick={() => void run('AddCurrent')} > - {translate('accounts.antigravity.save', 'Save current account')} + {t('accounts.antigravity.save', 'Save current account')} {busy && ( )}

{usage && (

- {usage.error ?? formatUsageReading(usage)} + {usage.error ?? formatUsageReading(usage, t)}

)} {state.accounts.map((account) => ( @@ -288,29 +280,21 @@ export function AntigravityAccountsSection({ >

- {account.email ?? - translate('accounts.antigravity.saved', 'Saved Google account')} + {account.email ?? t('accounts.antigravity.saved', 'Saved Google account')}

{account.usage !== undefined && (

{account.usage - ? translate( - 'accounts.antigravity.lastReading', - '{{reading}} · Read {{time}}', - { - reading: formatUsageReading(account.usage), - time: formatTimeAgo(account.usage.updatedAt) - } - ) - : translate( - 'accounts.antigravity.noReading', - 'Usage unknown: not read yet' - )} + ? t('accounts.antigravity.lastReading', '{{reading}} · Read {{time}}', { + reading: formatUsageReading(account.usage, t), + time: formatTimeAgo(account.usage.updatedAt, t) + }) + : t('accounts.antigravity.noReading', 'Usage unknown: not read yet')}

)} {state.activeAccountId === account.id && ( - {translate('accounts.antigravity.nativeActive', 'Native account')} + {t('accounts.antigravity.nativeActive', 'Native account')} )}
@@ -323,8 +307,8 @@ export function AntigravityAccountsSection({ > {state.selectedAccountId === account.id && state.activeAccountId === account.id - ? translate('accounts.antigravity.selected', 'Selected') - : translate('accounts.antigravity.select', 'Select')} + ? t('accounts.antigravity.selected', 'Selected') + : t('accounts.antigravity.select', 'Select')}
@@ -345,11 +329,11 @@ export function AntigravityAccountsSection({ )}

{windowsHost - ? translate( + ? t( 'accounts.antigravity.windowsSessions', 'Select applies and verifies the host default agy credential. Command choices and later account changes are handled by agy. Existing sessions may keep their previous account.' ) - : translate( + : t( 'accounts.antigravity.sessions', 'Selection applies to new agy sessions on this host. Existing sessions may keep their previous account.' )} @@ -358,7 +342,7 @@ export function AntigravityAccountsSection({ @@ -374,7 +358,7 @@ export function AntigravityAccountsSection({

- {translate('settings.agents.chatRuntime', 'Chat runtime')} + {t('settings.agents.chatRuntime', 'Chat runtime')}

{line}
@@ -385,9 +369,7 @@ export function AntigravityAccountsSection({ } /> ) : chats && structuredChatUnavailableOnPlatform('antigravity', ownerPlatform) ? ( - + ) : null}
diff --git a/src/renderer/src/components/settings/accounts/providers/CursorAccountsSection.tsx b/src/renderer/src/components/settings/accounts/providers/CursorAccountsSection.tsx index 4431b103d1b..35ea269ce80 100644 --- a/src/renderer/src/components/settings/accounts/providers/CursorAccountsSection.tsx +++ b/src/renderer/src/components/settings/accounts/providers/CursorAccountsSection.tsx @@ -2,6 +2,7 @@ import { useCallback, useEffect, useRef, useState } from 'react' import { ExternalLink, Loader2, RefreshCw, ShieldCheck } from 'lucide-react' import { AgentIcon } from '@/lib/agent-catalog' import { translate } from '@/i18n/i18n' +import { useTranslate, type Translate } from '@/i18n/use-translate' import { cn } from '@/lib/utils' import { useAppStore } from '../../../../store/index' import { Badge } from '../../../ui/badge' @@ -14,26 +15,27 @@ import { SettingsGroup } from '../../section-layout/SettingsGroup' const CURSOR_USAGE_DASHBOARD_URL = 'https://cursor.com/dashboard/spending' const SEARCH_KEYWORDS = ['cursor', 'usage', 'plan', 'spend', 'billing', 'rate limit'] -function credentialSourceLabel(source: CursorAccountStatus['credentialSource']): string | null { +function credentialSourceLabel( + source: CursorAccountStatus['credentialSource'], + t: Translate = translate +): string | null { if (source === 'keychain') { - return translate( + return t( 'auto.components.settings.CursorAccountsSection.source.keychain', 'macOS Keychain (cursor-agent)' ) } if (source === 'cli') { - return translate( - 'auto.components.settings.CursorAccountsSection.source.cli', - 'Cursor CLI auth file' - ) + return t('auto.components.settings.CursorAccountsSection.source.cli', 'Cursor CLI auth file') } if (source === 'desktop') { - return translate('auto.components.settings.CursorAccountsSection.source.desktop', 'Cursor IDE') + return t('auto.components.settings.CursorAccountsSection.source.desktop', 'Cursor IDE') } return null } export function CursorAccountsSection(): React.JSX.Element { + const t = useTranslate() const refreshRateLimits = useAppStore((s) => s.refreshRateLimits) const cursorUsage = useAppStore((s) => s.rateLimits.cursor) const [status, setStatus] = useState(null) @@ -86,7 +88,7 @@ export function CursorAccountsSection(): React.JSX.Element { const signedIn = status?.signedIn === true const tokenFresh = status?.tokenFresh === true - const sourceLabel = credentialSourceLabel(status?.credentialSource ?? null) + const sourceLabel = credentialSourceLabel(status?.credentialSource ?? null, t) const pools = cursorUsage?.buckets ?? [] const monthly = cursorUsage?.monthly ?? null // Explain unknown/failed usage rather than hiding the signed-in account or implying healthy allowance. @@ -108,10 +110,10 @@ export function CursorAccountsSection(): React.JSX.Element { title={ - {translate('auto.components.settings.CursorAccountsSection.title', 'Cursor')} + {t('auto.components.settings.CursorAccountsSection.title', 'Cursor')} } - description={translate( + description={t( 'auto.components.settings.CursorAccountsSection.subtitle', 'Shows your Cursor plan usage from the sign-in already on this computer. Orca only reads it — it never changes your Cursor login.' )} @@ -123,10 +125,7 @@ export function CursorAccountsSection(): React.JSX.Element { rel="noopener noreferrer" className="inline-flex items-center gap-1 text-xs text-muted-foreground hover:text-foreground" > - {translate( - 'auto.components.settings.CursorAccountsSection.dashboardLink', - 'Cursor dashboard' - )} + {t('auto.components.settings.CursorAccountsSection.dashboardLink', 'Cursor dashboard')} } @@ -141,31 +140,28 @@ export function CursorAccountsSection(): React.JSX.Element {
{loading ? (

- {translate('auto.components.settings.CursorAccountsSection.loading', 'Loading…')} + {t('auto.components.settings.CursorAccountsSection.loading', 'Loading…')}

) : signedIn ? ( <>

{status?.email ?? status?.displayName ?? - translate( - 'auto.components.settings.CursorAccountsSection.signedIn', - 'Signed in' - )} + t('auto.components.settings.CursorAccountsSection.signedIn', 'Signed in')}

{tokenFresh ? sourceLabel - ? translate( + ? t( 'auto.components.settings.CursorAccountsSection.signedInFrom', 'Signed in. Orca reads the session stored in {{source}}.', { source: sourceLabel } ) - : translate( + : t( 'auto.components.settings.CursorAccountsSection.signedInGeneric', 'Signed in. Orca reads the Cursor session stored on this computer.' ) - : translate( + : t( 'auto.components.settings.CursorAccountsSection.expired', 'Sign-in expired — run cursor-agent login on the computer running Orca, then click Refresh usage.' )} @@ -174,13 +170,13 @@ export function CursorAccountsSection(): React.JSX.Element { ) : ( <>

- {translate( + {t( 'auto.components.settings.CursorAccountsSection.signedOut', 'No Cursor sign-in found on this computer' )}

- {translate( + {t( 'auto.components.settings.CursorAccountsSection.signedOutHelp', 'Sign in with Cursor IDE, or run cursor-agent login in a terminal, then click Refresh usage here.' )} @@ -201,20 +197,14 @@ export function CursorAccountsSection(): React.JSX.Element { ) : ( )} - {translate( - 'auto.components.settings.CursorAccountsSection.refreshUsage', - 'Refresh usage' - )} + {t('auto.components.settings.CursorAccountsSection.refreshUsage', 'Refresh usage')}

{pools.length > 0 || monthly ? ( {Math.round(monthly.usedPercent)}% - {translate('auto.components.settings.CursorAccountsSection.planTotal', 'Plan')} + {t('auto.components.settings.CursorAccountsSection.planTotal', 'Plan')}
) : null} {(monthly ?? pools[0])?.resetDescription ? (

- {translate( - 'auto.components.settings.CursorAccountsSection.resets', - 'Resets {{when}}', - { when: (monthly ?? pools[0])?.resetDescription ?? '' } - )} + {t('auto.components.settings.CursorAccountsSection.resets', 'Resets {{when}}', { + when: (monthly ?? pools[0])?.resetDescription ?? '' + })}

) : null} {staleUsageError ? (

- {translate( + {t( 'auto.components.settings.CursorAccountsSection.staleUsage', 'Last known usage — the latest refresh failed: {{reason}}', { reason: staleUsageError } @@ -262,8 +250,8 @@ export function CursorAccountsSection(): React.JSX.Element { ) : unavailableReason ? ( s.refreshGrokRateLimits) const grokUsage = useAppStore((s) => s.rateLimits.grok) const [status, setStatus] = useState(null) @@ -69,10 +70,10 @@ export function GrokAccountsSection(): React.JSX.Element { title={ - {translate('auto.components.settings.GrokAccountsSection.a1b2c3d4e5', 'Grok (xAI)')} + {t('auto.components.settings.GrokAccountsSection.a1b2c3d4e5', 'Grok (xAI)')} } - description={translate( + description={t( 'auto.components.settings.GrokAccountsSection.f6e5d4c3b2', 'Shows weekly credit usage from your Grok CLI sign-in (session file ~/.grok/auth.json).' )} @@ -84,7 +85,7 @@ export function GrokAccountsSection(): React.JSX.Element { rel="noopener noreferrer" className="inline-flex items-center gap-1 text-xs text-muted-foreground hover:text-foreground" > - {translate('auto.components.settings.GrokAccountsSection.0d8e77bc40', 'Grok CLI docs')} + {t('auto.components.settings.GrokAccountsSection.0d8e77bc40', 'Grok CLI docs')} } @@ -99,24 +100,21 @@ export function GrokAccountsSection(): React.JSX.Element {

{loading ? (

- {translate('auto.components.settings.GrokAccountsSection.ad47a33f72', 'Loading…')} + {t('auto.components.settings.GrokAccountsSection.ad47a33f72', 'Loading…')}

) : signedIn ? ( <>

{status?.email ?? - translate( - 'auto.components.settings.GrokAccountsSection.b2c3d4e5f6', - 'Signed in' - )} + t('auto.components.settings.GrokAccountsSection.b2c3d4e5f6', 'Signed in')}

{tokenFresh - ? translate( + ? t( 'auto.components.settings.GrokAccountsSection.b36fa2c908', 'Signed in. Orca reads the Grok CLI session stored on disk.' ) - : translate( + : t( 'auto.components.settings.GrokAccountsSection.f08c41de73', 'Session expired — run grok on the computer running Orca and wait for it to start. If prompted, complete sign-in, then click Refresh usage. No chat message is needed.' )} @@ -125,13 +123,13 @@ export function GrokAccountsSection(): React.JSX.Element { ) : ( <>

- {translate( + {t( 'auto.components.settings.GrokAccountsSection.e5f6a7b8c9', 'Not signed in to Grok CLI' )}

- {translate( + {t( 'auto.components.settings.GrokAccountsSection.f6a7b8c9d0', 'In a terminal, run grok login, then click Refresh usage here.' )} @@ -152,7 +150,7 @@ export function GrokAccountsSection(): React.JSX.Element { ) : ( )} - {translate('auto.components.settings.GrokAccountsSection.3325d996cb', 'Refresh usage')} + {t('auto.components.settings.GrokAccountsSection.3325d996cb', 'Refresh usage')}

@@ -160,22 +158,16 @@ export function GrokAccountsSection(): React.JSX.Element { {usageWindow.resetDescription ? ( - {translate( - 'auto.components.settings.GrokAccountsSection.c6d1a8f4e2', - 'Resets {{when}}', - { when: usageWindow.resetDescription } - )} + {t('auto.components.settings.GrokAccountsSection.c6d1a8f4e2', 'Resets {{when}}', { + when: usageWindow.resetDescription + })} ) : null} {grokUsage?.usageMetadata?.authProvenance ? ( @@ -205,8 +195,8 @@ export function GrokAccountsSection(): React.JSX.Element { ) : unavailableReason ? ( (null) const [error, setError] = useState(null) const [busy, setBusy] = useState(false) @@ -90,10 +91,10 @@ export function ManagedDataAccountsSection({ {copied - ? translate('accounts.managedData.copied', 'Copied') - : translate('accounts.managedData.add', 'Copy add account command')} + ? t('accounts.managedData.copied', 'Copied') + : t('accounts.managedData.add', 'Copy add account command')} {!state && !error && (

- {translate( + {t( 'accounts.managedData.upgrade', 'If accounts do not appear, update or restart the Orca host.' )} @@ -143,9 +144,7 @@ export function ManagedDataAccountsSection({ {state && ( <>

- - {translate('accounts.managedData.system', 'System default')} - + {t('accounts.managedData.system', 'System default')}
{state.accounts.map((account) => ( @@ -171,9 +170,7 @@ export function ManagedDataAccountsSection({
{state.activeAccountId === account.id ? ( - - {translate('accounts.managedData.active', 'Active')} - + {t('accounts.managedData.active', 'Active')} ) : ( )}
@@ -210,10 +207,10 @@ export function ManagedDataAccountsSection({ - {translate('accounts.managedData.removeTitle', 'Remove managed account?')} + {t('accounts.managedData.removeTitle', 'Remove managed account?')} - {translate( + {t( 'accounts.managedData.removeDescription', 'Stop agents using this profile first. Removal deletes its saved credentials and conversation data. Your system login stays unchanged.' )} @@ -221,7 +218,7 @@ export function ManagedDataAccountsSection({ diff --git a/src/renderer/src/components/settings/accounts/providers/accounts-pane-provider-setting-sections.tsx b/src/renderer/src/components/settings/accounts/providers/accounts-pane-provider-setting-sections.tsx index 3c971a225e1..3b5c99a6b22 100644 --- a/src/renderer/src/components/settings/accounts/providers/accounts-pane-provider-setting-sections.tsx +++ b/src/renderer/src/components/settings/accounts/providers/accounts-pane-provider-setting-sections.tsx @@ -1,4 +1,5 @@ import { translate } from '@/i18n/i18n' +import type { Translate } from '@/i18n/use-translate' import { Button } from '../../../ui/button' import { Label } from '../../../ui/label' import { Switch } from '../../../ui/switch' @@ -9,7 +10,10 @@ import { OpenCodeGoCredentials } from '../usage/accounts-pane-opencode-credentia import { DebouncedSettingsTextInput } from '../../form-controls/DebouncedSettingsTextInput' import { SettingsGroup } from '../../section-layout/SettingsGroup' -export function renderGeminiAccountsSection(model: AccountsPaneSectionModel): React.JSX.Element { +export function renderGeminiAccountsSection( + model: AccountsPaneSectionModel, + t: Translate = translate +): React.JSX.Element { const { localAccountRuntimeSentenceLabel, recordFeatureInteraction, settings, updateSettings } = model return ( @@ -18,20 +22,20 @@ export function renderGeminiAccountsSection(model: AccountsPaneSectionModel): Re title={ - {translate('auto.components.settings.AccountsPane.0c64dc2a64', 'Gemini CLI (legacy)')} + {t('auto.components.settings.AccountsPane.0c64dc2a64', 'Gemini CLI (legacy)')} } - description={translate( + description={t( 'auto.components.settings.AccountsPane.973741a871', 'Keep existing Gemini CLI sessions available. Gemini credentials do not sign you into Antigravity.' )} >

- {translate( + {t( 'auto.components.settings.AccountsPane.c2aee76420', 'Extracts OAuth credentials from your local Gemini CLI installation to authenticate with Google for {{value0}}. This uses credentials issued to the Gemini CLI app, not Orca. May break if Google updates the CLI. Use at your own risk.', { value0: localAccountRuntimeSentenceLabel } @@ -62,7 +66,7 @@ export function renderGeminiAccountsSection(model: AccountsPaneSectionModel): Re

@@ -88,10 +95,10 @@ export function renderOpenCodeAccountsSection(model: AccountsPaneSectionModel): title={ - {translate('auto.components.settings.AccountsPane.4ac10b4d08', 'OpenCode Go')} + {t('auto.components.settings.AccountsPane.4ac10b4d08', 'OpenCode Go')} } - description={translate( + description={t( 'auto.components.settings.AccountsPane.ea631977b5', 'Configure OpenCode Go provider settings.' )} @@ -99,11 +106,11 @@ export function renderOpenCodeAccountsSection(model: AccountsPaneSectionModel): recordOpenCodeSettingEdit('apiKey')} />
recordOpenCodeSettingEdit('cookie')} commit={(opencodeSessionCookie) => updateSettings({ opencodeSessionCookie })} - placeholder={translate( + placeholder={t( 'auto.components.settings.AccountsPane.37b4b4a3f7', 'auth=…; __Host-console_session=…' )} @@ -139,22 +143,22 @@ export function renderOpenCodeAccountsSection(model: AccountsPaneSectionModel): }} className="h-7 shrink-0 text-xs text-muted-foreground hover:text-foreground" > - {translate('auto.components.settings.AccountsPane.b398b834c9', 'Clear')} + {t('auto.components.settings.AccountsPane.b398b834c9', 'Clear')} )}

- {translate( + {t( 'auto.components.settings.AccountsPane.62ab430f94', "Paste the full Cookie header from your browser's DevTools → Network → any opencode.ai request, including __Host-console_session (e.g." )}{' '} - {translate( + {t( 'auto.components.settings.AccountsPane.37b4b4a3f7', 'auth=…; __Host-console_session=…' )} - {translate( + {t( 'auto.components.settings.AccountsPane.d5267cce63', '). The auth cookie still covers workspace discovery; auth alone is not enough for usage. OpenCode Go auth is web-based and shared across Windows and WSL terminals.' )} @@ -162,11 +166,8 @@ export function renderOpenCodeAccountsSection(model: AccountsPaneSectionModel):

recordOpenCodeSettingEdit('workspaceId')} commit={(opencodeWorkspaceId) => updateSettings({ opencodeWorkspaceId })} - placeholder={translate( + placeholder={t( 'auto.components.settings.AccountsPane.a122332371', 'wrk_… (leave blank for automatic lookup)' )} @@ -199,17 +200,17 @@ export function renderOpenCodeAccountsSection(model: AccountsPaneSectionModel): }} className="h-7 shrink-0 text-xs text-muted-foreground hover:text-foreground" > - {translate('auto.components.settings.AccountsPane.b398b834c9', 'Clear')} + {t('auto.components.settings.AccountsPane.b398b834c9', 'Clear')} )}

- {translate( + {t( 'auto.components.settings.AccountsPane.51c9104e13', 'Find this in the URL after logging into opencode.ai (e.g.' )}{' '} - {translate( + {t( 'auto.components.settings.AccountsPane.ae3b21eb6c', 'opencode.ai/workspace/wrk_…/go' )} diff --git a/src/renderer/src/components/settings/accounts/scope/accounts-pane-location-section.tsx b/src/renderer/src/components/settings/accounts/scope/accounts-pane-location-section.tsx index 36878270e3b..087f9dd9b2a 100644 --- a/src/renderer/src/components/settings/accounts/scope/accounts-pane-location-section.tsx +++ b/src/renderer/src/components/settings/accounts/scope/accounts-pane-location-section.tsx @@ -1,5 +1,6 @@ import { toast } from 'sonner' import { translate } from '@/i18n/i18n' +import type { Translate } from '@/i18n/use-translate' import { SearchableSetting } from '../../section-layout/SearchableSetting' import { SettingsRow, SettingsSegmentedControl } from '../../form-controls/SettingsFormControls' import { Select, SelectContent, SelectItem, SelectTrigger, SelectValue } from '../../../ui/select' @@ -10,7 +11,8 @@ import type { AccountsPaneSectionModel } from '../accounts-pane-types' import { SettingsGroup } from '../../section-layout/SettingsGroup' export function renderAccountsLocationSection( - model: AccountsPaneSectionModel + model: AccountsPaneSectionModel, + t: Translate = translate ): React.JSX.Element | null { const { accountRuntime, @@ -28,7 +30,7 @@ export function renderAccountsLocationSection( onAccountLocationChange?.() } catch { toast.error( - translate( + t( 'auto.components.settings.AccountsPane.locationSaveFailed', 'Account location could not be saved. Try again.' ) @@ -37,24 +39,24 @@ export function renderAccountsLocationSection( } const accountRuntimeControls = wslSupportedPlatform ? ( void updateAccountLocation({ localAccountRuntime: value })} equalWidth options={[ - { value: 'host', label: getHostRuntimeLabel() }, + { value: 'host', label: getHostRuntimeLabel(t) }, ...(wslSupportedPlatform ? [ { value: 'wsl', - label: translate('auto.components.settings.AccountsPane.8619f9afa9', 'WSL'), + label: t('auto.components.settings.AccountsPane.8619f9afa9', 'WSL'), disabled: wslCapabilitiesLoading || !wslAvailable } as const ] @@ -97,20 +96,14 @@ export function renderAccountsLocationSection( - {translate('auto.components.settings.AccountsPane.2358ac71d2', 'WSL default')} + {t('auto.components.settings.AccountsPane.2358ac71d2', 'WSL default')} {wslDistros.map((distro) => ( diff --git a/src/renderer/src/components/settings/accounts/scope/accounts-pane-runtime.ts b/src/renderer/src/components/settings/accounts/scope/accounts-pane-runtime.ts index c6fe0b81e77..8d0fee372f0 100644 --- a/src/renderer/src/components/settings/accounts/scope/accounts-pane-runtime.ts +++ b/src/renderer/src/components/settings/accounts/scope/accounts-pane-runtime.ts @@ -7,31 +7,33 @@ import type { import { resolveLocalAccountRuntimeTarget } from '../../../../../../shared/local-account-runtime' import { getRendererAppPlatform } from '../../../../lib/renderer-app-platform' import { getIntlLocale, translate } from '@/i18n/i18n' +import type { Translate } from '@/i18n/use-translate' import type { LocalAccountRuntime } from '../accounts-pane-types' export const EMPTY_WSL_DISTROS: string[] = [] -export function getHostRuntimeLabel(): string { +export function getHostRuntimeLabel(t: Translate = translate): string { return navigator.userAgent.includes('Windows') ? 'Windows' - : translate('auto.components.settings.AccountsPane.9baf45d071', 'This device') + : t('auto.components.settings.AccountsPane.9baf45d071', 'This device') } // System default has no saved identity; show live host login or custom-provider detail, falling back when signed out. export function getCodexSystemDefaultSubtitle( identity: CodexSystemDefaultIdentity | undefined, - runtimeSentenceLabel: string + runtimeSentenceLabel: string, + t: Translate = translate ): string { if (identity?.authKind === 'oauth' && identity.email) { return identity.email } if (identity?.authKind === 'api-key') { - return translate( + return t( 'auto.components.settings.AccountsPane.codexSystemDefaultCustomProvider', 'Custom provider — no usage tracked.' ) } - return translate( + return t( 'auto.components.settings.AccountsPane.fcc4093fc1', 'Use your current {{value0}} Codex login.', { value0: runtimeSentenceLabel } @@ -65,7 +67,8 @@ export function getSelectedAccountRuntime( wslDistros: string[], wslCapabilitiesLoading: boolean, /** A WSL host's distro: its accounts are this desktop's for that distro, with no location to pick. */ - wslHostDistro: string | null = null + wslHostDistro: string | null = null, + t: Translate = translate ): LocalAccountRuntime { // Display the concrete target behind persisted auto policy. const resolvedRuntime = wslHostDistro @@ -75,7 +78,7 @@ export function getSelectedAccountRuntime( if (!wslHostDistro && !wslAvailable && !wslCapabilitiesLoading) { return { runtime: 'wsl', - label: translate('auto.components.settings.AccountsPane.8619f9afa9', 'WSL') + label: t('auto.components.settings.AccountsPane.8619f9afa9', 'WSL') } } const configuredDistro = resolvedRuntime.wslDistro?.trim() || null @@ -89,10 +92,10 @@ export function getSelectedAccountRuntime( wslDistro: selectedDistro, label: selectedDistro ? `WSL ${selectedDistro}` - : translate('auto.components.settings.AccountsPane.2358ac71d2', 'WSL default') + : t('auto.components.settings.AccountsPane.2358ac71d2', 'WSL default') } } - return { runtime: 'host', label: getHostRuntimeLabel() } + return { runtime: 'host', label: getHostRuntimeLabel(t) } } // Standalone runtime labels need sentence casing when interpolated into prose. @@ -102,8 +105,11 @@ export function getRuntimeSentenceLabel(runtime: LocalAccountRuntime): string { : runtime.label } -export function formatAccountTimestamp(timestamp: number): string { - return new Date(timestamp).toLocaleString(getIntlLocale(), { +export function formatAccountTimestamp( + timestamp: number, + intlLocale: string = getIntlLocale() +): string { + return new Date(timestamp).toLocaleString(intlLocale, { month: 'short', day: 'numeric', hour: 'numeric', diff --git a/src/renderer/src/components/settings/accounts/sign-in/HostAccountSignInPrompt.tsx b/src/renderer/src/components/settings/accounts/sign-in/HostAccountSignInPrompt.tsx index 7d6da5dc330..47f10edae1e 100644 --- a/src/renderer/src/components/settings/accounts/sign-in/HostAccountSignInPrompt.tsx +++ b/src/renderer/src/components/settings/accounts/sign-in/HostAccountSignInPrompt.tsx @@ -1,6 +1,6 @@ import { useId, useState, useSyncExternalStore } from 'react' import { Check, Copy, ExternalLink, X } from 'lucide-react' -import { translate } from '@/i18n/i18n' +import { useTranslate } from '@/i18n/use-translate' import { useClipboardTextCopyFeedback } from '@/hooks/use-clipboard-text-copy-feedback' import { cancelHostAccountSignIn, @@ -19,14 +19,15 @@ import { showHostCancelFailure } from './account-add-cancel-target' import { Input } from '../../../ui/input' function CopyButton(props: { text: string; label: string }): React.JSX.Element { + const t = useTranslate() const { copyText, status } = useClipboardTextCopyFeedback(props.text) return ( ) @@ -36,6 +37,7 @@ function PastedCodeForm(props: { owner: RuntimeClientTarget provider: HostAccountSignInProvider }): React.JSX.Element { + const t = useTranslate() const [code, setCode] = useState('') const [pending, setPending] = useState(false) const [refusal, setRefusal] = useState(null) @@ -58,7 +60,7 @@ function PastedCodeForm(props: { }} >

- {translate( + {t( 'accounts.hostSignIn.pasteCodeHelp', 'After you sign in, the page shows a code. Paste it here.' )} @@ -78,7 +80,7 @@ function PastedCodeForm(props: { }} /> {refusal !== null ? ( @@ -95,6 +97,7 @@ export function HostAccountSignInPrompt(props: { provider: HostAccountSignInProvider hostLabel: string }): React.JSX.Element | null { + const t = useTranslate() const read = () => readHostAccountSignIn(props.owner, props.provider) const status = useSyncExternalStore(subscribeHostAccountSignIns, read, read) const readRunning = () => hasHostAccountSignIn(props.owner, props.provider) @@ -109,14 +112,14 @@ export function HostAccountSignInPrompt(props: {

{!status || status.phase === 'starting' - ? translate('accounts.hostSignIn.starting', 'Starting sign-in on {{host}}…', { + ? t('accounts.hostSignIn.starting', 'Starting sign-in on {{host}}…', { host: props.hostLabel }) : status.phase === 'verifying' - ? translate('accounts.hostSignIn.verifying', 'Finishing sign-in on {{host}}…', { + ? t('accounts.hostSignIn.verifying', 'Finishing sign-in on {{host}}…', { host: props.hostLabel }) - : translate( + : t( 'accounts.hostSignIn.waiting', 'Open this page on this device and sign in. The account is saved on {{host}}.', { host: props.hostLabel } @@ -132,7 +135,7 @@ export function HostAccountSignInPrompt(props: { className="gap-1.5" > - {translate('accounts.hostSignIn.cancel', 'Cancel')} + {t('accounts.hostSignIn.cancel', 'Cancel')}

{url ? ( @@ -140,24 +143,24 @@ export function HostAccountSignInPrompt(props: { {url} - + ) : null} {url && prompt?.userCode ? (
- {translate('accounts.hostSignIn.enterCode', 'Enter this code on that page:')} + {t('accounts.hostSignIn.enterCode', 'Enter this code on that page:')} {prompt.userCode}
) : null} diff --git a/src/renderer/src/components/settings/accounts/usage/MiniMaxAccountsSection.tsx b/src/renderer/src/components/settings/accounts/usage/MiniMaxAccountsSection.tsx index 272258c83eb..b027879ca2c 100644 --- a/src/renderer/src/components/settings/accounts/usage/MiniMaxAccountsSection.tsx +++ b/src/renderer/src/components/settings/accounts/usage/MiniMaxAccountsSection.tsx @@ -1,5 +1,5 @@ import { ExternalLink, ShieldCheck } from 'lucide-react' -import { translate } from '@/i18n/i18n' +import { useTranslate } from '@/i18n/use-translate' import { cn } from '@/lib/utils' import type { FeatureInteractionId } from '../../../../../../shared/feature-interaction-catalog' import type { ProviderRateLimits } from '../../../../../../shared/rate-limit-types' @@ -30,6 +30,7 @@ export function MiniMaxAccountsSection({ miniMaxRateLimits: ProviderRateLimits | null recordFeatureInteraction: (featureId: FeatureInteractionId) => void }): React.JSX.Element { + const t = useTranslate() const credentials = useMiniMaxCredentials(host.owner) const { miniMaxConfigured, miniMaxApiKeyConfigured, miniMaxCredentialBusy } = credentials const settings = usageSettings.settings @@ -38,7 +39,7 @@ export function MiniMaxAccountsSection({ ? 'https://platform.minimaxi.com/console/usage' : 'https://platform.minimax.io/console/usage' const configured = miniMaxConfigured || miniMaxApiKeyConfigured - const accessNotice = usageCredentialAccessNotice(host, credentials.access) + const accessNotice = usageCredentialAccessNotice(host, credentials.access, t) const settingsUnavailable = settings === null const handleMiniMaxEndpointChange = (value: string): void => { if ((value !== 'overseas' && value !== 'cn') || value === settings?.minimaxEndpoint) { @@ -53,10 +54,10 @@ export function MiniMaxAccountsSection({ title={ - {translate('auto.components.settings.AccountsPane.5d63bbfbec', 'MiniMax')} + {t('auto.components.settings.AccountsPane.5d63bbfbec', 'MiniMax')} } - description={translate( + description={t( 'auto.components.settings.AccountsPane.usageTracking', 'Configure MiniMax usage tracking for your account.' )} @@ -67,7 +68,7 @@ export function MiniMaxAccountsSection({ rel="noopener noreferrer" className="inline-flex items-center gap-1 text-xs text-muted-foreground hover:text-foreground" > - {translate('auto.components.settings.AccountsPane.0d8e77bc40', 'Open console')} + {t('auto.components.settings.AccountsPane.0d8e77bc40', 'Open console')} } @@ -85,20 +86,20 @@ export function MiniMaxAccountsSection({ ) : (

{configured - ? usageCredentialStoredLabel(host) - : translate( + ? usageCredentialStoredLabel(host, t) + : t( 'auto.components.settings.AccountsPane.credentialsNotSet', 'Credentials not set' )}

)} -

{miniMaxStorageHelp(host)}

+

{miniMaxStorageHelp(host, t)}

{(['zai', 'bigmodel'] as const).map((option) => ( - {siteLabel(option)} + {siteLabel(option, t)} ))} @@ -289,32 +279,29 @@ export function ZcodePlanAccountsSection({
{!detailsUnavailable ? ( {apiKeyConfigured ? : } {apiKeyConfigured - ? translate('auto.components.settings.ZcodePlanAccountsSection.saved', 'Saved') - : translate( - 'auto.components.settings.ZcodePlanAccountsSection.notSaved', - 'Not saved' - )} + ? t('auto.components.settings.ZcodePlanAccountsSection.saved', 'Saved') + : t('auto.components.settings.ZcodePlanAccountsSection.notSaved', 'Not saved')} ) : null}
setApiKeyDraft(e.target.value)} - placeholder={translate( + placeholder={t( 'auto.components.settings.ZcodePlanAccountsSection.keyPlaceholder', 'Paste your GLM Coding Plan API key' )} @@ -344,8 +331,8 @@ export function ZcodePlanAccountsSection({ > {credentialBusy ? : null} {apiKeyConfigured - ? translate('auto.components.settings.ZcodePlanAccountsSection.replace', 'Replace') - : translate('auto.components.settings.ZcodePlanAccountsSection.save', 'Save')} + ? t('auto.components.settings.ZcodePlanAccountsSection.replace', 'Replace') + : t('auto.components.settings.ZcodePlanAccountsSection.save', 'Save')} {apiKeyConfigured ? ( ) : null}

- {translate( + {t( 'auto.components.settings.ZcodePlanAccountsSection.keyHelp', 'The same key your coding tools use for the plan (for example Claude Code with ANTHROPIC_BASE_URL pointed at the site). Switching the site above changes which host receives it.' )} @@ -374,11 +358,8 @@ export function ZcodePlanAccountsSection({ {usageWindows.length > 0 ? ( - {translate( + {t( 'auto.components.settings.ZcodePlanAccountsSection.planLevel', 'Plan: {{value0}}', { value0: usage.planType } diff --git a/src/renderer/src/components/settings/accounts/usage/accounts-pane-minimax-actions.ts b/src/renderer/src/components/settings/accounts/usage/accounts-pane-minimax-actions.ts index 45204709f4f..18bc814305f 100644 --- a/src/renderer/src/components/settings/accounts/usage/accounts-pane-minimax-actions.ts +++ b/src/renderer/src/components/settings/accounts/usage/accounts-pane-minimax-actions.ts @@ -4,6 +4,7 @@ import type { MiniMaxCredentialsStatus } from '../../../../../../shared/minimax- import type { RuntimeClientTarget } from '../../../../../../shared/runtime-client-target' import { toast } from 'sonner' import { translate } from '@/i18n/i18n' +import type { Translate } from '@/i18n/use-translate' import { callMiniMaxCredentials, UsageCredentialsHostOutdatedError, @@ -23,7 +24,10 @@ type MiniMaxCredentialActionContext = { recordFeatureInteraction: (featureId: FeatureInteractionId) => void } -export function createMiniMaxCredentialActions(context: MiniMaxCredentialActionContext): { +export function createMiniMaxCredentialActions( + context: MiniMaxCredentialActionContext, + t: Translate = translate +): { saveMiniMaxApiKey: () => Promise clearMiniMaxApiKey: () => Promise saveMiniMaxCookie: () => Promise @@ -72,11 +76,11 @@ export function createMiniMaxCredentialActions(context: MiniMaxCredentialActionC const clearCookieDraft = (): void => context.setMiniMaxCookieDraft('') const clearApiKeyDraft = (): void => context.setMiniMaxApiKeyDraft('') - const cookieFailure = translate( + const cookieFailure = t( 'auto.components.settings.AccountsPane.b43e761fe5', 'MiniMax cookie update failed.' ) - const apiKeyFailure = translate( + const apiKeyFailure = t( 'auto.components.settings.AccountsPane.apiKeyUpdateFailed', 'MiniMax API key update failed.' ) @@ -85,7 +89,7 @@ export function createMiniMaxCredentialActions(context: MiniMaxCredentialActionC const cookie = context.miniMaxCookieDraft.trim() if (!cookie) { toast.error( - translate('auto.components.settings.AccountsPane.2f24f244a4', 'MiniMax cookie is required.') + t('auto.components.settings.AccountsPane.2f24f244a4', 'MiniMax cookie is required.') ) return } @@ -95,11 +99,11 @@ export function createMiniMaxCredentialActions(context: MiniMaxCredentialActionC failureMessage: cookieFailure, saved: { landed: (status) => status.cookieConfigured, - notSaved: translate( + notSaved: t( 'auto.components.settings.AccountsPane.8e6f0cb1d8', 'MiniMax cookie was not saved.' ), - ok: translate('auto.components.settings.AccountsPane.8d61637a77', 'MiniMax cookie saved.') + ok: t('auto.components.settings.AccountsPane.8d61637a77', 'MiniMax cookie saved.') } }) } @@ -108,10 +112,7 @@ export function createMiniMaxCredentialActions(context: MiniMaxCredentialActionC const apiKey = context.miniMaxApiKeyDraft.trim() if (!apiKey) { toast.error( - translate( - 'auto.components.settings.AccountsPane.d6f1b9b6a2', - 'MiniMax API key is required.' - ) + t('auto.components.settings.AccountsPane.d6f1b9b6a2', 'MiniMax API key is required.') ) return } @@ -121,11 +122,11 @@ export function createMiniMaxCredentialActions(context: MiniMaxCredentialActionC failureMessage: apiKeyFailure, saved: { landed: (status) => status.apiKeyConfigured, - notSaved: translate( + notSaved: t( 'auto.components.settings.AccountsPane.7c5d8a4e1b', 'MiniMax API key was not saved.' ), - ok: translate('auto.components.settings.AccountsPane.4d2c7b9e83', 'MiniMax API key saved.') + ok: t('auto.components.settings.AccountsPane.4d2c7b9e83', 'MiniMax API key saved.') } }) } diff --git a/src/renderer/src/components/settings/accounts/usage/accounts-pane-minimax-credentials.tsx b/src/renderer/src/components/settings/accounts/usage/accounts-pane-minimax-credentials.tsx index 480db0a9843..bcdf0fbf007 100644 --- a/src/renderer/src/components/settings/accounts/usage/accounts-pane-minimax-credentials.tsx +++ b/src/renderer/src/components/settings/accounts/usage/accounts-pane-minimax-credentials.tsx @@ -1,6 +1,7 @@ import { HelpCircle, Loader2, Lock, LockOpen } from 'lucide-react' import { useNow } from '../../../../hooks/use-now' import { translate } from '@/i18n/i18n' +import { useIntlLocale, useTranslate, type Translate } from '@/i18n/use-translate' import { formatUiRelativeTime } from '@/i18n/relative-time-format' import { Badge } from '../../../ui/badge' import { Button } from '../../../ui/button' @@ -18,25 +19,30 @@ import { } from './usage-credential-host' /** Name the storage host because its poller sends the key. */ -export function miniMaxStorageHelp(host: UsageCredentialHost): string { +export function miniMaxStorageHelp(host: UsageCredentialHost, t: Translate = translate): string { return host.label === null - ? translate( + ? t( 'auto.components.settings.AccountsPane.selectedEndpointStorage', 'Stored locally and sent to the selected MiniMax endpoint for usage refreshes.' ) - : translate( + : t( 'auto.components.settings.usageCredentialHost.miniMaxStorage', 'Stored on {{host}} and sent from there to the selected MiniMax endpoint for usage refreshes.', { host: host.label } ) } -function formatMiniMaxRelativeRefresh(updatedAt: number, now: number): string { +function formatMiniMaxRelativeRefresh( + updatedAt: number, + now: number, + t: Translate = translate, + intlLocale?: string +): string { const diffMs = Math.max(0, now - updatedAt) if (diffMs < 60_000) { - return translate('auto.components.settings.AccountsPane.3a30aaf526', 'just now') + return t('auto.components.settings.AccountsPane.3a30aaf526', 'just now') } - return formatUiRelativeTime(-diffMs) + return formatUiRelativeTime(-diffMs, intlLocale) } function MiniMaxCookieHelpPopover({ @@ -46,41 +52,42 @@ function MiniMaxCookieHelpPopover({ consoleUrl: string host: UsageCredentialHost }): React.JSX.Element { + const t = useTranslate() const steps = [ - translate( + t( 'auto.components.settings.AccountsPane.openSelectedConsole', 'Open {{url}} in your browser and sign in.', { url: consoleUrl } ), - translate('auto.components.settings.AccountsPane.24560fe830', 'Open DevTools.'), - translate( + t('auto.components.settings.AccountsPane.24560fe830', 'Open DevTools.'), + t( 'auto.components.settings.AccountsPane.4cab0fa42d', 'Go to the Network tab and enable Preserve log.' ), - translate('auto.components.settings.AccountsPane.bee4e63e1c', 'Reload the page.'), - translate( + t('auto.components.settings.AccountsPane.bee4e63e1c', 'Reload the page.'), + t( 'auto.components.settings.AccountsPane.87f814af6f', 'Filter for remains and select the coding_plan/remains request.' ), - translate( + t( 'auto.components.settings.AccountsPane.435df0ee51', 'Under Request Headers, copy the Cookie value.' ), - translate('auto.components.settings.AccountsPane.7492fb3bba', 'Paste it here and click Save.') + t('auto.components.settings.AccountsPane.7492fb3bba', 'Paste it here and click Save.') ] return (

- {translate('auto.components.settings.AccountsPane.9fec52de4b', 'How to copy the cookie')} + {t('auto.components.settings.AccountsPane.9fec52de4b', 'How to copy the cookie')}

{host.label === null - ? translate( + ? t( 'auto.components.settings.AccountsPane.cookieSelectedEndpoint', 'Stored locally and sent to the selected MiniMax endpoint for usage refreshes.' ) - : miniMaxStorageHelp(host)} + : miniMaxStorageHelp(host, t)}

    @@ -103,6 +110,8 @@ export function MiniMaxCredentials({ miniMaxRateLimits: ProviderRateLimits | null consoleUrl: string }): React.JSX.Element { + const t = useTranslate() + const intlLocale = useIntlLocale() const { editable, sessionCookieSupported, @@ -123,8 +132,8 @@ export function MiniMaxCredentials({ clearMiniMaxApiKey } = credentials const now = useNow(60_000) - const cookieNotice = usageCredentialUnreadableNotice(host, miniMaxCookieUnreadable) - const apiKeyNotice = usageCredentialUnreadableNotice(host, miniMaxApiKeyUnreadable) + const cookieNotice = usageCredentialUnreadableNotice(host, miniMaxCookieUnreadable, t) + const apiKeyNotice = usageCredentialUnreadableNotice(host, miniMaxApiKeyUnreadable, t) const cookieEditable = editable && sessionCookieSupported && @@ -136,11 +145,8 @@ export function MiniMaxCredentials({ return ( <>
    {miniMaxConfigured ? : } {miniMaxConfigured - ? translate('auto.components.settings.AccountsPane.73ea15f24b', 'Saved') - : translate('auto.components.settings.AccountsPane.23afe8f226', 'Not saved')} + ? t('auto.components.settings.AccountsPane.73ea15f24b', 'Saved') + : t('auto.components.settings.AccountsPane.23afe8f226', 'Not saved')}
    @@ -173,7 +176,7 @@ export function MiniMaxCredentials({ className="h-6 gap-1 px-2 text-xs text-muted-foreground hover:text-foreground" > - {translate('auto.components.settings.AccountsPane.43d7a45b97', 'How to copy')} + {t('auto.components.settings.AccountsPane.43d7a45b97', 'How to copy')} @@ -183,7 +186,7 @@ export function MiniMaxCredentials({
setMiniMaxCookieDraft(e.target.value)} - placeholder={translate( + placeholder={t( 'auto.components.settings.AccountsPane.b8a4f21c3e', 'Paste the Cookie header from DevTools' )} @@ -211,8 +214,8 @@ export function MiniMaxCredentials({ > {miniMaxCredentialBusy ? : null} {miniMaxConfigured - ? translate('auto.components.settings.AccountsPane.f38b9cc4bd', 'Replace') - : translate('auto.components.settings.AccountsPane.590a3130f9', 'Save')} + ? t('auto.components.settings.AccountsPane.f38b9cc4bd', 'Replace') + : t('auto.components.settings.AccountsPane.590a3130f9', 'Save')} {miniMaxConfigured ? ( ) : null} {editable && !sessionCookieSupported ? (

- {translate( + {t( 'auto.components.settings.usageCredentialHost.cookieNeedsDesktop', '{{host}} has no Orca desktop app to send a session cookie from. Save an API key instead.', { host: host.label ?? '' } @@ -236,7 +239,7 @@ export function MiniMaxCredentials({

) : null}

- {translate( + {t( 'auto.components.settings.AccountsPane.copySelectedConsoleCookie', 'Open the selected console, sign in, then copy the Cookie request header from DevTools (Network → any remains request → Cookie).' )} @@ -245,17 +248,13 @@ export function MiniMaxCredentials({ miniMaxRateLimits?.status === 'ok' && miniMaxRateLimits.error === null ? (

- {translate( - 'auto.components.settings.AccountsPane.53f7b8c7a2', - 'Last refresh: {{value0}}', - { - value0: formatMiniMaxRelativeRefresh(miniMaxRateLimits.updatedAt, now) - } - )} + {t('auto.components.settings.AccountsPane.53f7b8c7a2', 'Last refresh: {{value0}}', { + value0: formatMiniMaxRelativeRefresh(miniMaxRateLimits.updatedAt, now, t, intlLocale) + })}

) : null}

- {translate( + {t( 'auto.components.settings.AccountsPane.31d24a4e87', 'Cookie expires when you sign out in the browser.' )} @@ -263,14 +262,14 @@ export function MiniMaxCredentials({

)} {miniMaxApiKeyConfigured - ? translate('auto.components.settings.AccountsPane.73ea15f24b', 'Saved') - : translate('auto.components.settings.AccountsPane.23afe8f226', 'Not saved')} + ? t('auto.components.settings.AccountsPane.73ea15f24b', 'Saved') + : t('auto.components.settings.AccountsPane.23afe8f226', 'Not saved')}
{apiKeyNotice ?

{apiKeyNotice}

: null}
@@ -314,7 +310,7 @@ export function MiniMaxCredentials({ disabled={apiKeyReplaceLocked} value={miniMaxApiKeyDraft} onChange={(e) => setMiniMaxApiKeyDraft(e.target.value)} - placeholder={translate( + placeholder={t( 'auto.components.settings.AccountsPane.4f2c8a7e1b', 'Paste your MiniMax API key' )} @@ -329,8 +325,8 @@ export function MiniMaxCredentials({ > {miniMaxCredentialBusy ? : null} {miniMaxApiKeyConfigured - ? translate('auto.components.settings.AccountsPane.f38b9cc4bd', 'Replace') - : translate('auto.components.settings.AccountsPane.590a3130f9', 'Save')} + ? t('auto.components.settings.AccountsPane.f38b9cc4bd', 'Replace') + : t('auto.components.settings.AccountsPane.590a3130f9', 'Save')} {miniMaxApiKeyConfigured ? ( ) : null}

- {translate( + {t( 'auto.components.settings.AccountsPane.apiKeyInstructions', 'Copy the API key from your MiniMax console → API keys. A saved API key takes priority over the cookie; use Forget key to switch back to the cookie.' )} diff --git a/src/renderer/src/components/settings/accounts/usage/accounts-pane-opencode-credentials.tsx b/src/renderer/src/components/settings/accounts/usage/accounts-pane-opencode-credentials.tsx index 1fec09e663b..d56984df1b0 100644 --- a/src/renderer/src/components/settings/accounts/usage/accounts-pane-opencode-credentials.tsx +++ b/src/renderer/src/components/settings/accounts/usage/accounts-pane-opencode-credentials.tsx @@ -1,6 +1,6 @@ import { useEffect, useRef, useState } from 'react' import { toast } from 'sonner' -import { translate } from '@/i18n/i18n' +import { useTranslate } from '@/i18n/use-translate' import { Badge } from '../../../ui/badge' import { Button } from '../../../ui/button' import { Input } from '../../../ui/input' @@ -11,6 +11,7 @@ type CredentialStatus = boolean | 'loading' | 'unavailable' /** Keeps late initial reads from replacing a completed credential mutation. */ export function OpenCodeGoCredentials({ onSaved }: { onSaved: () => void }): React.JSX.Element { + const t = useTranslate() const [draft, setDraft] = useState('') const [credentialStatus, setCredentialStatus] = useState('loading') const [busy, setBusy] = useState(false) @@ -48,7 +49,7 @@ export function OpenCodeGoCredentials({ onSaved }: { onSaved: () => void }): Rea setDraft('') onSaved() } catch { - toast.error(translate('sessionHistory.settings.saveError', 'Could not save. Try again.')) + toast.error(t('sessionHistory.settings.saveError', 'Could not save. Try again.')) } finally { setBusy(false) } @@ -56,11 +57,11 @@ export function OpenCodeGoCredentials({ onSaved }: { onSaved: () => void }): Rea return ( void }): Rea >

{credentialStatus === 'loading' - ? translate( - 'auto.components.settings.AccountsPane.opencodeGo.apiKey.checking', - 'Checking…' - ) + ? t('auto.components.settings.AccountsPane.opencodeGo.apiKey.checking', 'Checking…') : credentialStatus === 'unavailable' - ? translate( + ? t( 'auto.components.settings.AccountsPane.opencodeGo.apiKey.unavailable', 'Status unavailable' ) : configured - ? translate('auto.components.settings.AccountsPane.73ea15f24b', 'Saved') - : translate('auto.components.settings.AccountsPane.23afe8f226', 'Not saved')} + ? t('auto.components.settings.AccountsPane.73ea15f24b', 'Saved') + : t('auto.components.settings.AccountsPane.23afe8f226', 'Not saved')}
@@ -97,7 +95,7 @@ export function OpenCodeGoCredentials({ onSaved }: { onSaved: () => void }): Rea value={draft} disabled={busy} onChange={(event) => setDraft(event.target.value)} - placeholder={translate( + placeholder={t( 'auto.components.settings.AccountsPane.opencodeGo.apiKey.placeholder', 'Leave blank to use the key saved by /connect or OPENCODE_API_KEY' )} @@ -110,8 +108,8 @@ export function OpenCodeGoCredentials({ onSaved }: { onSaved: () => void }): Rea onClick={() => void updateCredential(false)} > {configured - ? translate('auto.components.settings.AccountsPane.f38b9cc4bd', 'Replace') - : translate('auto.components.settings.AccountsPane.590a3130f9', 'Save')} + ? t('auto.components.settings.AccountsPane.f38b9cc4bd', 'Replace') + : t('auto.components.settings.AccountsPane.590a3130f9', 'Save')} {configured && ( )}

- {translate( + {t( 'auto.components.settings.AccountsPane.opencodeGo.apiKey.help', 'Used for OpenCode Go usage in the status bar. The session cookie below is only needed for legacy console (OpenCode Black) accounts.' )} diff --git a/src/renderer/src/components/settings/accounts/usage/usage-credential-host.ts b/src/renderer/src/components/settings/accounts/usage/usage-credential-host.ts index a6e940c1a6b..8d6ec78e097 100644 --- a/src/renderer/src/components/settings/accounts/usage/usage-credential-host.ts +++ b/src/renderer/src/components/settings/accounts/usage/usage-credential-host.ts @@ -1,4 +1,5 @@ import { translate } from '@/i18n/i18n' +import type { Translate } from '@/i18n/use-translate' import type { RuntimeClientTarget } from '../../../../../../shared/runtime-client-target' import { UsageCredentialsHostOutdatedError } from '@/runtime/provider-accounts/usage/runtime-usage-credentials-client' @@ -23,29 +24,26 @@ export function usageCredentialAccessAfterError(error: unknown): 'outdated' | 'u export function usageCredentialAccessNotice( host: UsageCredentialHost, - access: UsageCredentialAccess + access: UsageCredentialAccess, + t: Translate = translate ): string | null { if (host.label === null) { return null } if (access === 'checking') { - return translate( - 'auto.components.settings.usageCredentialHost.checking', - 'Checking {{host}}…', - { - host: host.label - } - ) + return t('auto.components.settings.usageCredentialHost.checking', 'Checking {{host}}…', { + host: host.label + }) } if (access === 'outdated') { - return translate( + return t( 'auto.components.settings.usageCredentialHost.outdated', 'Update Orca on {{host}} to save this key there.', { host: host.label } ) } if (access === 'unreachable') { - return translate( + return t( 'auto.components.settings.usageCredentialHost.unreachable', 'Could not reach {{host}}, so whether a key is saved there is unknown.', { host: host.label } @@ -57,18 +55,19 @@ export function usageCredentialAccessNotice( /** A remote host's saved key it cannot use: sealed by its other Orca app, or just unreadable. */ export function usageCredentialUnreadableNotice( host: UsageCredentialHost, - unreadable: 'sealed-elsewhere' | 'unreadable' | undefined + unreadable: 'sealed-elsewhere' | 'unreadable' | undefined, + t: Translate = translate ): string | null { if (host.label === null || unreadable === undefined) { return null } return unreadable === 'sealed-elsewhere' - ? translate( + ? t( 'auto.components.settings.usageCredentialHost.sealedElsewhereForgettable', 'Saved by another Orca app on {{host}}, which this one cannot read. Change it in that app, or forget it here.', { host: host.label } ) - : translate( + : t( 'auto.components.settings.usageCredentialHost.unreadableKey', 'Orca on {{host}} cannot read the saved key. Replace it or forget it.', { host: host.label } @@ -83,21 +82,27 @@ export function usageCredentialSealedElsewhere( return host.label !== null && unreadable === 'sealed-elsewhere' } -export function usageCredentialStoredLabel(host: UsageCredentialHost): string { +export function usageCredentialStoredLabel( + host: UsageCredentialHost, + t: Translate = translate +): string { return host.label === null - ? translate('auto.components.settings.AccountsPane.0b8c1c7e02', 'Stored locally') - : translate('auto.components.settings.usageCredentialHost.storedOnHost', 'Stored on {{host}}', { + ? t('auto.components.settings.AccountsPane.0b8c1c7e02', 'Stored locally') + : t('auto.components.settings.usageCredentialHost.storedOnHost', 'Stored on {{host}}', { host: host.label }) } -export function zcodePlanKeyDescription(host: UsageCredentialHost): string { +export function zcodePlanKeyDescription( + host: UsageCredentialHost, + t: Translate = translate +): string { return host.label === null - ? translate( + ? t( 'auto.components.settings.ZcodePlanAccountsSection.keyDescription', 'Paste the API key from the selected console’s API Keys page. Stored locally, encrypted when the OS supports it, and sent only to that site for usage refreshes.' ) - : translate( + : t( 'auto.components.settings.ZcodePlanAccountsSection.keyOnHostDescription', 'Paste the API key from the selected console’s API Keys page. Stored on {{host}} and sent from there only to that site for usage refreshes.', { host: host.label } diff --git a/src/renderer/src/components/settings/accounts/usage/use-minimax-credentials.ts b/src/renderer/src/components/settings/accounts/usage/use-minimax-credentials.ts index fb30674b7c7..c6f18dd0c27 100644 --- a/src/renderer/src/components/settings/accounts/usage/use-minimax-credentials.ts +++ b/src/renderer/src/components/settings/accounts/usage/use-minimax-credentials.ts @@ -8,11 +8,13 @@ import { usageCredentialAccessAfterError, type UsageCredentialAccess } from './usage-credential-host' +import { useTranslate } from '@/i18n/use-translate' export type MiniMaxCredentialsModel = ReturnType /** Key by owner so switching credential hosts resets drafts. */ export function useMiniMaxCredentials(owner: RuntimeClientTarget) { + const t = useTranslate() const recordFeatureInteraction = useAppStore((s) => s.recordFeatureInteraction) const [status, setStatus] = useState(null) const [access, setAccess] = useState( @@ -48,17 +50,20 @@ export function useMiniMaxCredentials(owner: RuntimeClientTarget) { } }, [ownerEnvironmentId]) - const actions = createMiniMaxCredentialActions({ - owner, - miniMaxApiKeyDraft, - setMiniMaxApiKeyDraft, - miniMaxCookieDraft, - setMiniMaxCookieDraft, - setStatus, - setAccess, - setMiniMaxCredentialBusy, - recordFeatureInteraction - }) + const actions = createMiniMaxCredentialActions( + { + owner, + miniMaxApiKeyDraft, + setMiniMaxApiKeyDraft, + miniMaxCookieDraft, + setMiniMaxCookieDraft, + setStatus, + setAccess, + setMiniMaxCredentialBusy, + recordFeatureInteraction + }, + t + ) return { access, diff --git a/src/renderer/src/components/settings/accounts/usage/use-usage-host-settings.ts b/src/renderer/src/components/settings/accounts/usage/use-usage-host-settings.ts index 6e6a2968de3..be904eb2b9d 100644 --- a/src/renderer/src/components/settings/accounts/usage/use-usage-host-settings.ts +++ b/src/renderer/src/components/settings/accounts/usage/use-usage-host-settings.ts @@ -1,6 +1,6 @@ import { useEffect, useState } from 'react' import { toast } from 'sonner' -import { translate } from '@/i18n/i18n' +import { useTranslate } from '@/i18n/use-translate' import { callRuntimeRpc } from '@/runtime/rpc/calls/runtime-rpc-call' import type { GlobalSettings } from '../../../../../../shared/global-settings-types' import type { RuntimeClientTarget } from '../../../../../../shared/runtime-client-target' @@ -34,6 +34,7 @@ export function useUsageHostSettings( localSettings: GlobalSettings, updateLocalSettings: (updates: Partial) => Promise | void ): UsageHostSettingsModel { + const t = useTranslate() const ownerEnvironmentId = owner.kind === 'environment' ? owner.environmentId : null const [remote, setRemote] = useState<{ environmentId: string @@ -90,7 +91,7 @@ export function useUsageHostSettings( }) } catch (error) { toast.error( - translate( + t( 'auto.components.settings.useUsageHostSettings.updateFailed', 'Could not update the setting on {{host}}.', { host: hostLabel ?? '' } diff --git a/src/renderer/src/components/settings/accounts/usage/use-zcode-plan-credentials.ts b/src/renderer/src/components/settings/accounts/usage/use-zcode-plan-credentials.ts index db1c56ce464..b401160bf64 100644 --- a/src/renderer/src/components/settings/accounts/usage/use-zcode-plan-credentials.ts +++ b/src/renderer/src/components/settings/accounts/usage/use-zcode-plan-credentials.ts @@ -1,6 +1,6 @@ import { useEffect, useRef, useState } from 'react' import { toast } from 'sonner' -import { translate } from '@/i18n/i18n' +import { useTranslate } from '@/i18n/use-translate' import { useAppStore } from '../../../../store/index' import type { ZcodePlanCredentialsStatus } from '../../../../../../shared/zcode-plan-sites' import type { RuntimeClientTarget } from '../../../../../../shared/runtime-client-target' @@ -20,6 +20,7 @@ export function useZcodePlanCredentials( updatedAt: number | undefined, owner: RuntimeClientTarget = LOCAL_OWNER ) { + const t = useTranslate() const recordFeatureInteraction = useAppStore((s) => s.recordFeatureInteraction) const [status, setStatus] = useState(null) const [access, setAccess] = useState( @@ -86,7 +87,7 @@ export function useZcodePlanCredentials( recordFeatureInteraction('usage-tracking') if (action === 'save') { toast.success( - translate( + t( 'auto.components.settings.ZcodePlanAccountsSection.keySaved', 'GLM Coding Plan API key saved.' ) @@ -97,7 +98,7 @@ export function useZcodePlanCredentials( setAccess('outdated') } toast.error( - translate( + t( 'auto.components.settings.ZcodePlanAccountsSection.keySaveFailed', 'GLM Coding Plan credential update failed.' ), diff --git a/src/renderer/src/components/settings/accounts/usage/zcode-plan-usage-windows.tsx b/src/renderer/src/components/settings/accounts/usage/zcode-plan-usage-windows.tsx index 5088fc0a646..729b4b644d5 100644 --- a/src/renderer/src/components/settings/accounts/usage/zcode-plan-usage-windows.tsx +++ b/src/renderer/src/components/settings/accounts/usage/zcode-plan-usage-windows.tsx @@ -1,4 +1,5 @@ import { translate } from '@/i18n/i18n' +import { useTranslate, type Translate } from '@/i18n/use-translate' import { formatResetDuration } from '../../../../../../shared/rate-limit-reset-format' import type { ProviderRateLimits, RateLimitWindow } from '../../../../../../shared/rate-limit-types' import { Badge } from '../../../ui/badge' @@ -10,14 +11,14 @@ export type ZcodeUsageWindowRow = { window: RateLimitWindow } -function windowLabel(kind: ZcodeUsageWindowKind): string { +function windowLabel(kind: ZcodeUsageWindowKind, t: Translate = translate): string { if (kind === 'session') { - return translate('auto.components.settings.ZcodePlanAccountsSection.window.session', '5 hours') + return t('auto.components.settings.ZcodePlanAccountsSection.window.session', '5 hours') } if (kind === 'weekly') { - return translate('auto.components.settings.ZcodePlanAccountsSection.window.weekly', 'Weekly') + return t('auto.components.settings.ZcodePlanAccountsSection.window.weekly', 'Weekly') } - return translate('auto.components.settings.ZcodePlanAccountsSection.window.mcp', 'MCP monthly') + return t('auto.components.settings.ZcodePlanAccountsSection.window.mcp', 'MCP monthly') } function formatWindowReset(window: RateLimitWindow, now: number): string | null { @@ -50,6 +51,7 @@ export function ZcodeUsageWindowView({ row: ZcodeUsageWindowRow now: number }): React.JSX.Element { + const t = useTranslate() const resetLabel = formatWindowReset(row.window, now) return (

@@ -57,9 +59,9 @@ export function ZcodeUsageWindowView({ {Math.round(row.window.usedPercent)}% - {windowLabel(row.kind)} + {windowLabel(row.kind, t)} {resetLabel - ? translate( + ? t( 'auto.components.settings.ZcodePlanAccountsSection.resetIn', ' — resets in {{value0}}', { value0: resetLabel } diff --git a/src/renderer/src/components/settings/agents/AgentsPane.tsx b/src/renderer/src/components/settings/agents/AgentsPane.tsx index 361ff528af0..300cb30f862 100644 --- a/src/renderer/src/components/settings/agents/AgentsPane.tsx +++ b/src/renderer/src/components/settings/agents/AgentsPane.tsx @@ -32,6 +32,7 @@ import { } from './permissions/AgentPermissionsSetting' import type { SettingsHostScope } from '../host-scope/settings-host-scope' import { SettingsGroup } from '../section-layout/SettingsGroup' +import { useTranslate } from '@/i18n/use-translate' type AgentSettingProps = { settings: GlobalSettings @@ -56,6 +57,7 @@ export function AgentsPane({ wslDistros, wslCapabilitiesLoading }: AgentsPaneProps): React.JSX.Element { + const t = useTranslate() return (
updateSettings({ claudeSubagentNavigationEnabled: settings.claudeSubagentNavigationEnabled === false }) } - ariaLabel={getClaudeSubagentNavigationTitle()} + ariaLabel={getClaudeSubagentNavigationTitle(t)} /> {!isPairedWebClientWindow() ? ( <> @@ -119,40 +121,43 @@ function renderAgentPermissions(props: AgentPermissionsSettingProps): React.Reac } export function AgentStatusHooksSetting({ settings, updateSettings }: AgentSettingProps) { + const t = useTranslate() const enabled = settings.agentStatusHooksEnabled !== false return ( updateSettings({ agentStatusHooksEnabled: !enabled })} - ariaLabel={getAgentStatusHooksTitle()} + ariaLabel={getAgentStatusHooksTitle(t)} /> ) } export function AgentWorkspaceTrustSetting({ settings, updateSettings }: AgentSettingProps) { + const t = useTranslate() const enabled = settings.agentWorkspaceTrustEnabled !== false return ( updateSettings({ agentWorkspaceTrustEnabled: !enabled })} - ariaLabel={getAgentWorkspaceTrustTitle()} + ariaLabel={getAgentWorkspaceTrustTitle(t)} /> ) } export function AgentGeneratedTabTitlesSetting({ settings, updateSettings }: AgentSettingProps) { + const t = useTranslate() const enabled = settings.tabAutoGenerateTitle === true return ( updateSettings({ tabAutoGenerateTitle: !enabled })} - ariaLabel={getAgentGeneratedTabTitlesTitle()} + ariaLabel={getAgentGeneratedTabTitlesTitle(t)} /> ) } diff --git a/src/renderer/src/components/settings/agents/behavior/AgentAwakeSetting.tsx b/src/renderer/src/components/settings/agents/behavior/AgentAwakeSetting.tsx index bd394dde6ad..06c38d3519d 100644 --- a/src/renderer/src/components/settings/agents/behavior/AgentAwakeSetting.tsx +++ b/src/renderer/src/components/settings/agents/behavior/AgentAwakeSetting.tsx @@ -13,6 +13,7 @@ import { normalizeComputerAwakeMode, type ComputerAwakeMode } from '../../../../../../shared/computer-awake-mode' +import { useTranslate } from '../../../../i18n/use-translate' type AgentAwakeSettingProps = { settings: GlobalSettings @@ -23,8 +24,9 @@ export function AgentAwakeSetting({ settings, updateSettings }: AgentAwakeSettingProps): React.JSX.Element { - const title = getAgentAwakeTitle() - const description = getAgentAwakeDescription() + const t = useTranslate() + const title = getAgentAwakeTitle(t) + const description = getAgentAwakeDescription(undefined, t) const mode = normalizeComputerAwakeMode( settings.computerAwakeMode, settings.keepComputerAwakeWhileAgentsRun @@ -37,7 +39,7 @@ export function AgentAwakeSetting({
@@ -52,15 +54,15 @@ export function AgentAwakeSetting({ options={[ { value: 'on', - label: getAgentAwakeModeLabel('on') + label: getAgentAwakeModeLabel('on', t) }, { value: 'auto', - label: getAgentAwakeModeLabel('auto') + label: getAgentAwakeModeLabel('auto', t) }, { value: 'off', - label: getAgentAwakeModeLabel('off') + label: getAgentAwakeModeLabel('off', t) } ]} /> diff --git a/src/renderer/src/components/settings/agents/behavior/AgentCacheTimerSection.tsx b/src/renderer/src/components/settings/agents/behavior/AgentCacheTimerSection.tsx index cf86fff57aa..997fd8fdc46 100644 --- a/src/renderer/src/components/settings/agents/behavior/AgentCacheTimerSection.tsx +++ b/src/renderer/src/components/settings/agents/behavior/AgentCacheTimerSection.tsx @@ -7,7 +7,7 @@ import { Select, SelectContent, SelectItem, SelectTrigger, SelectValue } from '. import { getAgentCacheTimerSearchEntries } from './agent-cache-timer-search' import { SearchableSetting } from '../../section-layout/SearchableSetting' import { SettingsSwitch } from '../../form-controls/SettingsFormControls' -import { translate } from '@/i18n/i18n' +import { useTranslate } from '@/i18n/use-translate' import { SettingsGroup } from '../../section-layout/SettingsGroup' type AgentCacheTimerSectionProps = { @@ -19,23 +19,18 @@ export function AgentCacheTimerSection({ settings, updateSettings }: AgentCacheTimerSectionProps): React.JSX.Element { + const t = useTranslate() return (

- {translate( + {t( 'auto.components.settings.AgentCacheTimerSection.487b176240', 'Show a countdown in the sidebar after a Claude agent becomes idle.' )}

{ const enabling = !settings.promptCacheTimerEnabled @@ -81,11 +70,8 @@ export function AgentCacheTimerSection({ {settings.promptCacheTimerEnabled && (

- {translate( + {t( 'auto.components.settings.AgentCacheTimerSection.8b9e202e0a', "Match this to your provider's cache TTL. The default is 5 minutes." )} @@ -115,13 +98,10 @@ export function AgentCacheTimerSection({ - {translate( - 'auto.components.settings.AgentCacheTimerSection.54395ecd7c', - '5 minutes' - )} + {t('auto.components.settings.AgentCacheTimerSection.54395ecd7c', '5 minutes')} - {translate('auto.components.settings.AgentCacheTimerSection.05de84a104', '1 hour')} + {t('auto.components.settings.AgentCacheTimerSection.05de84a104', '1 hour')} diff --git a/src/renderer/src/components/settings/agents/behavior/CodexHistorySharingSetting.tsx b/src/renderer/src/components/settings/agents/behavior/CodexHistorySharingSetting.tsx index af28db10132..e809bb19ee6 100644 --- a/src/renderer/src/components/settings/agents/behavior/CodexHistorySharingSetting.tsx +++ b/src/renderer/src/components/settings/agents/behavior/CodexHistorySharingSetting.tsx @@ -10,6 +10,7 @@ import { getCodexHistorySharingUnsupported } from './codex-history-sharing-copy' import { useCodexHistorySharingHostSetting } from './use-codex-history-sharing-host-setting' +import { useTranslate } from '@/i18n/use-translate' export function CodexHistorySharingSetting({ settings, @@ -21,6 +22,7 @@ export function CodexHistorySharingSetting({ /** The host whose Codex launches write the history links. */ hostScope: SettingsHostScope }): React.JSX.Element { + const t = useTranslate() const environmentId = runtimeTargetEnvironmentId(hostScope.target) const hostName = useAppStateValue((state) => environmentId @@ -37,11 +39,11 @@ export function CodexHistorySharingSetting({ const enabled = setting.state === 'ready' ? setting.enabled : true return ( void }): React.JSX.Element { + const t = useTranslate() const value: AgentAvailability = isEnabled ? 'enabled' : 'disabled' return ( @@ -53,20 +54,18 @@ export function AgentAvailabilityControl({ onSetEnabled(next === 'enabled') } }} - ariaLabel={translate( - 'auto.components.settings.AgentsPane.1c9a9679ec', - '{{value0}} availability', - { value0: label } - )} + ariaLabel={t('auto.components.settings.AgentsPane.1c9a9679ec', '{{value0}} availability', { + value0: label + })} size="sm" options={[ { value: 'enabled', - label: translate('auto.components.settings.AgentsPane.d4d2a45d63', 'Enabled') + label: t('auto.components.settings.AgentsPane.d4d2a45d63', 'Enabled') }, { value: 'disabled', - label: translate('auto.components.settings.AgentsPane.8dc0192e48', 'Disabled') + label: t('auto.components.settings.AgentsPane.8dc0192e48', 'Disabled') } ]} /> @@ -135,6 +134,7 @@ export function AgentCatalogRow({ opensAs, chatRuntime }: AgentCatalogRowProps): React.JSX.Element { + const t = useTranslate() const envSummary = environmentNames?.join(' ') ?? stringifyAgentDefaultEnvDraft(envOverride) const defaultEnvSummary = stringifyAgentDefaultEnvDraft(defaultEnv) const [cmdOpen, setCmdOpen] = useState( @@ -169,7 +169,7 @@ export function AgentCatalogRow({ {label} {!isEnabled && ( - {translate('auto.components.settings.AgentsPane.8dc0192e48', 'Disabled')} + {t('auto.components.settings.AgentsPane.8dc0192e48', 'Disabled')} )}

@@ -196,7 +196,7 @@ export function AgentCatalogRow({ className="rounded-full outline-none focus-visible:ring-[3px] focus-visible:ring-ring/50" > - {agentPermissionChoiceLabel(permissionChoice.choice, label)} + {agentPermissionChoiceLabel(permissionChoice.choice, label, t)}
@@ -227,16 +227,13 @@ export function AgentCatalogRow({ aria-pressed={isDefault} title={ isDefault - ? translate('settings.agents.clearDefault', 'Clear default agent') - : translate( - 'auto.components.settings.AgentsPane.5f986a9b92', - 'Set as default' - ) + ? t('settings.agents.clearDefault', 'Clear default agent') + : t('auto.components.settings.AgentsPane.5f986a9b92', 'Set as default') } className="h-7 w-full justify-center gap-1 text-xs" > - {translate('auto.components.settings.AgentsPane.24e032fa34', 'Default')} + {t('auto.components.settings.AgentsPane.24e032fa34', 'Default')} )}
@@ -247,8 +244,8 @@ export function AgentCatalogRow({ rel="noopener noreferrer" title={ isDetected - ? translate('auto.components.settings.AgentsPane.fe4d630c94', 'Docs') - : translate('auto.components.settings.AgentsPane.f95b5c79b8', 'Install') + ? t('auto.components.settings.AgentsPane.fe4d630c94', 'Docs') + : t('auto.components.settings.AgentsPane.f95b5c79b8', 'Install') } className="flex size-7 items-center justify-center rounded-md text-muted-foreground transition-colors hover:bg-muted/50 hover:text-foreground" > @@ -262,8 +259,8 @@ export function AgentCatalogRow({ onClick={() => setCmdOpen((previous) => !previous)} aria-label={ cmdOpen - ? translate('settings.agents.collapseAgentSettings', 'Collapse agent settings') - : translate('settings.agents.expandAgentSettings', 'Expand agent settings') + ? t('settings.agents.collapseAgentSettings', 'Collapse agent settings') + : t('settings.agents.expandAgentSettings', 'Expand agent settings') } className="size-7 text-muted-foreground hover:text-foreground" > @@ -301,7 +298,7 @@ export function AgentCatalogRow({ onSaveOverride={onSaveOverride} />

- {translate( + {t( 'settings.agents.undetectedCommandHint', 'If the {{agent}} CLI is installed outside PATH, enter the full path to its executable on this host.', { agent: label } @@ -342,7 +339,7 @@ export function AgentCatalogRow({ } note={ permissionChoice?.argsSetPermission - ? translate( + ? t( 'settings.agents.permissionArgsTerminalOnly', 'These arguments may control {{agent}}’s terminal permissions, so Orca adds no flag. Chats use the Permissions choice above.', { agent: label } @@ -373,7 +370,7 @@ export function AgentCatalogRow({ )}

- {translate( + {t( 'auto.components.settings.AgentsPane.f9f127d664', 'Override the binary path or name, and edit the default launch arguments or environment for this agent.' )} diff --git a/src/renderer/src/components/settings/agents/launch/AgentDefaultSetting.tsx b/src/renderer/src/components/settings/agents/launch/AgentDefaultSetting.tsx index f5b39d50ab4..b8b54123c39 100644 --- a/src/renderer/src/components/settings/agents/launch/AgentDefaultSetting.tsx +++ b/src/renderer/src/components/settings/agents/launch/AgentDefaultSetting.tsx @@ -3,7 +3,7 @@ import type { TuiAgent } from '../../../../../../shared/agent-launch/catalog/tui import { type AgentCatalogEntry, AgentIcon } from '@/lib/agent-catalog' import { cn } from '@/lib/utils' -import { translate } from '@/i18n/i18n' +import { useTranslate } from '@/i18n/use-translate' import { SettingsGroup } from '../../section-layout/SettingsGroup' function DefaultAgentPill({ @@ -55,6 +55,7 @@ export function AgentDefaultSetting({ onSetDefault: (agent: TuiAgent | 'blank' | null) => void permissions: React.ReactNode }): React.JSX.Element { + const t = useTranslate() const storedDefaultAgent = defaultAgent !== null && defaultAgent !== 'blank' ? catalog.find((agent) => agent.id === defaultAgent) @@ -66,7 +67,7 @@ export function AgentDefaultSetting({ return (

@@ -75,7 +76,7 @@ export function AgentDefaultSetting({ onClick={() => onSetDefault(defaultAgent === null ? 'blank' : null)} > {defaultAgent === null && } - {translate('auto.components.settings.AgentsPane.92033495ff', 'Auto')} + {t('auto.components.settings.AgentsPane.92033495ff', 'Auto')} {defaultAgentPills.map((agent) => { const isActive = defaultAgent === agent.id @@ -88,7 +89,7 @@ export function AgentDefaultSetting({ onClick={() => onSetDefault(isActive ? 'blank' : agent.id)} title={ isUndetected - ? translate( + ? t( 'auto.components.settings.AgentsPane.storedDefaultUndetected', 'Saved as your default, but not detected right now' ) diff --git a/src/renderer/src/components/settings/agents/launch/AgentDetectionCatalog.tsx b/src/renderer/src/components/settings/agents/launch/AgentDetectionCatalog.tsx index eac236fe4be..20bbacf2fd3 100644 --- a/src/renderer/src/components/settings/agents/launch/AgentDetectionCatalog.tsx +++ b/src/renderer/src/components/settings/agents/launch/AgentDetectionCatalog.tsx @@ -2,7 +2,7 @@ import { Fragment } from 'react' import { AlertTriangle, RefreshCw } from 'lucide-react' import type { AgentCatalogEntry } from '@/lib/agent-catalog' import { cn } from '@/lib/utils' -import { translate } from '@/i18n/i18n' +import { useTranslate } from '@/i18n/use-translate' import { Button } from '../../../ui/button' import { AgentCatalogRow, type AgentCatalogRowProps } from './AgentCatalogRow' import { SettingsBadge } from '../../form-controls/SettingsFormControls' @@ -30,12 +30,13 @@ export function AgentDetectionCatalog({ onRefresh: () => void getRowProps: (agent: AgentCatalogEntry, isDetected: boolean) => AgentCatalogRowProps }): React.JSX.Element { + const t = useTranslate() return ( <> {detectedAgents.length === 0 && !detectionPending && !detectionFailed && (
- {translate( + {t( 'auto.components.settings.AgentsPane.noAgentsDetected', 'No agents detected. If one is installed, the probe may have timed out.' )} @@ -63,32 +64,28 @@ export function AgentDetectionCatalog({ title={ installed ? ( - {translate('auto.components.settings.AgentsPane.02e0143be5', 'Installed')} + {t('auto.components.settings.AgentsPane.02e0143be5', 'Installed')} {detectedAgents.length}{' '} - {translate('auto.components.settings.AgentsPane.ed3e110e61', 'detected')} + {t('auto.components.settings.AgentsPane.ed3e110e61', 'detected')} {activeServerName ? ( - {translate( - 'auto.components.settings.AgentsPane.03e1a5081a', - 'on {{value0}}', - { - value0: activeServerName - } - )} + {t('auto.components.settings.AgentsPane.03e1a5081a', 'on {{value0}}', { + value0: activeServerName + })} ) : null} ) : ( - {translate( + {t( 'auto.components.settings.AgentsPane.e8da2af684', 'Available to install' )} {undetectedAgents.length}{' '} - {translate('auto.components.settings.AgentsPane.024bd95089', 'agents')} + {t('auto.components.settings.AgentsPane.024bd95089', 'agents')} ) @@ -103,11 +100,11 @@ export function AgentDetectionCatalog({ disabled={isRefreshing} title={ activeServerEnvironmentId - ? translate( + ? t( 'auto.components.settings.AgentsPane.25a41a9aad', 'Re-detect agents installed on this server' ) - : translate( + : t( 'auto.components.settings.AgentsPane.13647f9f80', 'Re-read your shell PATH and re-detect installed agents' ) @@ -116,11 +113,8 @@ export function AgentDetectionCatalog({ > {isRefreshing - ? translate( - 'auto.components.settings.AgentsPane.c9b33eb5c0', - 'Refreshing…' - ) - : translate('auto.components.settings.AgentsPane.0d9e293a02', 'Refresh')} + ? t('auto.components.settings.AgentsPane.c9b33eb5c0', 'Refreshing…') + : t('auto.components.settings.AgentsPane.0d9e293a02', 'Refresh')} ) : undefined } @@ -144,10 +138,7 @@ export function AgentDetectionCatalog({ {detectionPending && !detectionFailed && (
- {translate( - 'auto.components.settings.AgentsPane.d83834f5e6', - 'Detecting installed agents…' - )} + {t('auto.components.settings.AgentsPane.d83834f5e6', 'Detecting installed agents…')}
)} @@ -155,7 +146,7 @@ export function AgentDetectionCatalog({
- {translate( + {t( 'auto.components.settings.AgentsPane.remoteDetectionFailed', 'Couldn’t detect installed agents. Check the host connection and try again.' )} @@ -168,7 +159,7 @@ export function AgentDetectionCatalog({ className="h-6 shrink-0 gap-1.5 px-2 text-destructive hover:text-destructive" > - {translate('auto.components.settings.AgentsPane.retryDetection', 'Retry')} + {t('auto.components.settings.AgentsPane.retryDetection', 'Retry')}
)} @@ -183,6 +174,7 @@ function RefreshButton({ isRefreshing: boolean onRefresh: () => void }): React.JSX.Element { + const t = useTranslate() return ( ) } diff --git a/src/renderer/src/components/settings/agents/launch/AgentLaunchSettingsCatalog.tsx b/src/renderer/src/components/settings/agents/launch/AgentLaunchSettingsCatalog.tsx index aebdf0e6bba..35cd6884267 100644 --- a/src/renderer/src/components/settings/agents/launch/AgentLaunchSettingsCatalog.tsx +++ b/src/renderer/src/components/settings/agents/launch/AgentLaunchSettingsCatalog.tsx @@ -61,7 +61,7 @@ import { import { chatProviderAgents, useHostStructuredAgentsVersion } from '@/lib/chat-provider-agents' import { readHostStructuredAgents } from '@/runtime/host-structured-agents' import { LOCAL_EXECUTION_HOST_ID } from '../../../../../../shared/execution-host' -import { translate } from '@/i18n/i18n' +import { useTranslate } from '@/i18n/use-translate' export type AgentPermissionsRenderer = (props: AgentPermissionsSettingProps) => React.ReactNode @@ -94,6 +94,7 @@ export function AgentLaunchSettingsCatalog({ updateSettings: (updates: Partial) => void | Promise } }): React.JSX.Element { + const t = useTranslate() const { detectedIds: detected, detectionFailed, @@ -201,12 +202,12 @@ export function AgentLaunchSettingsCatalog({ const values = { agent: label, mode: agentPermissionChoiceLabel(lifted.choice, label) } toast.success( chatSupported - ? translate( + ? t( 'settings.agents.permissionFromArgs', '{{agent}} set to {{mode}} in terminals and new chats.', values ) - : translate( + : t( 'settings.agents.permissionFromArgsTerminals', '{{agent}} set to {{mode}} in terminals.', values @@ -335,7 +336,7 @@ export function AgentLaunchSettingsCatalog({ isTuiAgentEnabled(agent.id, settings.disabledTuiAgents) )} catalog={catalog} - description={translate( + description={t( 'settings.agents.hostDefaults', 'Defaults for new agents on the selected host.' )} diff --git a/src/renderer/src/components/settings/agents/launch/AgentLaunchSettingsLoading.tsx b/src/renderer/src/components/settings/agents/launch/AgentLaunchSettingsLoading.tsx index b820e1dc047..8af60e1f1c2 100644 --- a/src/renderer/src/components/settings/agents/launch/AgentLaunchSettingsLoading.tsx +++ b/src/renderer/src/components/settings/agents/launch/AgentLaunchSettingsLoading.tsx @@ -1,8 +1,9 @@ -import { translate } from '@/i18n/i18n' +import { useTranslate } from '@/i18n/use-translate' import { AgentDefaultSetting } from './AgentDefaultSetting' import { AllAgentsPermissionSetting } from '../../../agent/launch-settings/AgentPermissionChoiceControls' export function AgentLaunchSettingsLoading(): React.JSX.Element { + const t = useTranslate() return (
) => void | Promise renderPermissions: AgentPermissionsRenderer }): React.JSX.Element { + const t = useTranslate() const notify = useAgentPermissionOverrideNotice() const chatOwnSupported = useChatSettingsOwnPermissions() const tail = useRef | null>(null) @@ -95,15 +96,12 @@ function LocalAgentLaunchSettings({ const failed = (agent: TuiAgent | undefined) => (error: unknown) => { toast.error( agent - ? translate( + ? t( 'settings.agents.localSaveFailedAgent', '{{agent}} settings could not be saved. Try again.', { agent: getAgentLabel(agent) } ) - : translate( - 'settings.agents.localSaveFailed', - 'Agent settings could not be saved. Try again.' - ) + : t('settings.agents.localSaveFailed', 'Agent settings could not be saved. Try again.') ) throw error } diff --git a/src/renderer/src/components/settings/agents/launch/HostAgentEnvironmentEditor.tsx b/src/renderer/src/components/settings/agents/launch/HostAgentEnvironmentEditor.tsx index 115ce6d48d5..a5d664785d5 100644 --- a/src/renderer/src/components/settings/agents/launch/HostAgentEnvironmentEditor.tsx +++ b/src/renderer/src/components/settings/agents/launch/HostAgentEnvironmentEditor.tsx @@ -1,7 +1,7 @@ import { useState } from 'react' import { Button } from '../../../ui/button' import { Input } from '../../../ui/input' -import { translate } from '@/i18n/i18n' +import { useTranslate } from '@/i18n/use-translate' import type { AgentLaunchSettingsMutation } from '../../../../../../shared/agent-launch/choices/agent-launch-settings' import type { TuiAgent } from '../../../../../../shared/agent-launch/catalog/tui-agent' import { useAgentLaunchFieldWrite } from '../../../agent/launch-settings/use-agent-launch-field-write' @@ -15,11 +15,12 @@ export function HostAgentEnvironmentEditor({ names: readonly string[] mutate: (mutation: AgentLaunchSettingsMutation) => Promise }): React.JSX.Element { + const t = useTranslate() const [newName, setNewName] = useState('') return (
- {translate('auto.components.settings.AgentsPane.8fbe1f37c1', 'Environment')} + {t('auto.components.settings.AgentsPane.8fbe1f37c1', 'Environment')} {names.map((name) => ( @@ -28,8 +29,8 @@ export function HostAgentEnvironmentEditor({ setNewName(event.target.value)} - aria-label={translate('settings.agents.environmentName', 'Environment variable name')} - placeholder={translate('settings.agents.environmentName', 'Environment variable name')} + aria-label={t('settings.agents.environmentName', 'Environment variable name')} + placeholder={t('settings.agents.environmentName', 'Environment variable name')} spellCheck={false} className="h-7" /> @@ -66,6 +67,7 @@ function EnvironmentValueRow({ > ) => Promise }): React.JSX.Element { + const t = useTranslate() const [value, setValue] = useState('') const [changed, setChanged] = useState(false) const { pending, write } = useAgentLaunchFieldWrite() @@ -91,17 +93,17 @@ function EnvironmentValueRow({ setValue(event.target.value) setChanged(true) }} - aria-label={translate('settings.agents.environmentValue', 'Value for {{name}}', { name })} + aria-label={t('settings.agents.environmentValue', 'Value for {{name}}', { name })} placeholder={ saved - ? translate('settings.agents.savedEnvironment', 'Saved value · enter to replace') - : translate('settings.agents.newEnvironment', 'Value') + ? t('settings.agents.savedEnvironment', 'Saved value · enter to replace') + : t('settings.agents.newEnvironment', 'Value') } spellCheck={false} className="h-7" /> {saved ? ( ) : null}
diff --git a/src/renderer/src/components/settings/agents/launch/HostAgentLaunchSettings.tsx b/src/renderer/src/components/settings/agents/launch/HostAgentLaunchSettings.tsx index 14045aa55d9..3fcafc50673 100644 --- a/src/renderer/src/components/settings/agents/launch/HostAgentLaunchSettings.tsx +++ b/src/renderer/src/components/settings/agents/launch/HostAgentLaunchSettings.tsx @@ -23,7 +23,7 @@ import { useAgentPermissionOverrideNotice, type SelectPermissionOverrides } from '../permissions/agent-permission-override-notice' -import { translate } from '@/i18n/i18n' +import { useTranslate } from '@/i18n/use-translate' type ReadState = | { kind: 'loading' | 'unsupported' | 'failed' } @@ -45,6 +45,7 @@ export function HostAgentLaunchSettings({ hostName: string renderPermissions: AgentPermissionsRenderer }): React.JSX.Element { + const t = useTranslate() const owner = useMemo( () => ({ environmentId, pairingRevision }), [environmentId, pairingRevision] @@ -160,18 +161,18 @@ export function HostAgentLaunchSettings({

{read.kind === 'unsupported' - ? translate( + ? t( 'settings.agents.updateHost', 'Update this server to edit its agent launch settings.' ) - : translate( + : t( 'settings.agents.readFailed', 'Couldn’t read agent settings. Check the host connection and try again.' )}

{read.kind === 'failed' ? ( ) : null}
@@ -195,7 +196,7 @@ export function HostAgentLaunchSettings({
{saveFailed ? (

- {translate( + {t( 'settings.agents.saveFailed', 'Couldn’t save agent settings. Check the host connection and try again.' )} diff --git a/src/renderer/src/components/settings/agents/launch/ZcodeCliSetupSection.tsx b/src/renderer/src/components/settings/agents/launch/ZcodeCliSetupSection.tsx index 6a7e0e2fac3..1b63f12a8ee 100644 --- a/src/renderer/src/components/settings/agents/launch/ZcodeCliSetupSection.tsx +++ b/src/renderer/src/components/settings/agents/launch/ZcodeCliSetupSection.tsx @@ -1,5 +1,5 @@ import { lazy, Suspense, useState } from 'react' -import { translate } from '@/i18n/i18n' +import { useTranslate } from '@/i18n/use-translate' import { useAppStore } from '@/store/index' import { Button } from '../../../ui/button' import { SearchableSetting } from '../../section-layout/SearchableSetting' @@ -13,16 +13,14 @@ const OnboardingInlineCommandTerminal = lazy(() => type SetupChoice = 'zai' | 'bigmodel' | 'api-key' export function ZcodeCliSetupSection(): React.JSX.Element { + const t = useTranslate() const commandOverride = useAppStore((s) => s.settings?.agentCmdOverrides?.zcode) const [choice, setChoice] = useState(null) const executable = commandOverride?.trim() || 'zcode' const command = choice === 'api-key' ? executable : `${executable} login ${choice} --no-browser` - const title = translate( - 'auto.components.settings.ZcodeCliSetupSection.title', - 'Use GLM with ZCode' - ) - const description = translate( + const title = t('auto.components.settings.ZcodeCliSetupSection.title', 'Use GLM with ZCode') + const description = t( 'auto.components.settings.ZcodeCliSetupSection.description', 'Sign in to use GLM as a coding agent. The quota API key above only monitors usage; ZCode owns its own sign-in and model selection.' ) @@ -37,7 +35,7 @@ export function ZcodeCliSetupSection(): React.JSX.Element {

{title}

{description}

- {translate( + {t( 'auto.components.settings.ZcodeCliSetupSection.help', 'Choose browser sign-in for your plan site, or use ZCode’s masked API-key setup. After signing in, start ZCode and use /model to choose a GLM model.' )} @@ -49,7 +47,7 @@ export function ZcodeCliSetupSection(): React.JSX.Element { disabled={choice !== null} onClick={() => setChoice('zai')} > - {translate('auto.components.settings.ZcodeCliSetupSection.zai', 'Z.AI browser sign-in')} + {t('auto.components.settings.ZcodeCliSetupSection.zai', 'Z.AI browser sign-in')} {choice ? ( ) : null}

@@ -83,7 +75,7 @@ export function ZcodeCliSetupSection(): React.JSX.Element { - {translate( + {t( 'auto.components.settings.ZcodeCliSetupSection.loading', 'Loading sign-in terminal…' )} @@ -92,21 +84,21 @@ export function ZcodeCliSetupSection(): React.JSX.Element { > ) => void }): React.JSX.Element { - const ownLabel = translate('settings.agents.permissionEachOwn', "Each agent's own settings") + const t = useTranslate() + const ownLabel = t('settings.agents.permissionEachOwn', "Each agent's own settings") const choice = mode === 'mixed' ? '' : agentPermissionModeChoice(mode) return ( @@ -41,9 +42,7 @@ export function AgentPermissionModeMenu({ className="min-w-0 max-w-full" > {choice === '' ? ( - - {translate('settings.agents.permissionMixed', 'Mixed')} - + {t('settings.agents.permissionMixed', 'Mixed')} ) : ( )} diff --git a/src/renderer/src/components/settings/agents/permissions/AgentPermissionsSetting.tsx b/src/renderer/src/components/settings/agents/permissions/AgentPermissionsSetting.tsx index 822ebdd933d..a54ae2bf9b4 100644 --- a/src/renderer/src/components/settings/agents/permissions/AgentPermissionsSetting.tsx +++ b/src/renderer/src/components/settings/agents/permissions/AgentPermissionsSetting.tsx @@ -1,6 +1,7 @@ import { Trans } from 'react-i18next' import type { AgentPermissionMode } from '../../../../../../shared/agent-launch/permissions/tui-agent-permissions' -import { getIntlLocale, i18n, translate } from '@/i18n/i18n' +import { i18n } from '@/i18n/i18n' +import { useIntlLocale, useTranslate } from '@/i18n/use-translate' import { cn } from '@/lib/utils' import { Tooltip, TooltipContent, TooltipTrigger } from '../../../ui/tooltip' import { AgentPermissionModeMenu } from './AgentPermissionModeMenu' @@ -28,6 +29,7 @@ function SupportedAgentsTrigger({ labels: readonly string[] children?: React.ReactNode }): React.JSX.Element { + const intlLocale = useIntlLocale() return ( @@ -39,7 +41,7 @@ function SupportedAgentsTrigger({ - {new Intl.ListFormat(getIntlLocale(), { type: 'conjunction' }).format(labels)} + {new Intl.ListFormat(intlLocale, { type: 'conjunction' }).format(labels)} ) @@ -50,11 +52,12 @@ function AgentPermissionsDescription({ majorityMode, autoAgentLabels }: AgentPermissionsSettingProps): React.ReactNode { + const t = useTranslate() if (mode === 'auto') { return ( Supported agents approve safe actions on their own.' )} @@ -64,28 +67,26 @@ function AgentPermissionsDescription({ } if (mode === 'mixed') { return majorityMode - ? translate( + ? t( 'settings.agents.permissionsMixed', 'Most agents use {{mode}}. Choosing a mode resets the marked exceptions, except custom arguments.', - { mode: agentPermissionModeLabel(majorityMode) } + { mode: agentPermissionModeLabel(majorityMode, t) } ) - : translate( + : t( 'settings.agents.permissionsMixedNoMajority', 'Agents use different settings. Choosing a mode applies to all.' ) } - return translate( + return t( 'settings.agents.permissionsDescription', 'How much agents can do without asking you. Agents with custom launch arguments keep them.' ) } export function AgentPermissionsSetting(props: AgentPermissionsSettingProps): React.JSX.Element { + const t = useTranslate() const { mode, nonAutoMode, onChange, onChangeNonAuto, autoSupported = true } = props - const title = translate( - 'auto.components.settings.AgentsPane.agentPermissions', - 'Agent Permissions' - ) + const title = t('auto.components.settings.AgentsPane.agentPermissions', 'Agent Permissions') const showOthers = mode === 'auto' && onChangeNonAuto !== undefined return (
@@ -96,7 +97,7 @@ export function AgentPermissionsSetting(props: AgentPermissionsSettingProps): Re

{!autoSupported ? (

- {translate( + {t( 'settings.agents.autoUnsupported', 'Approve for me needs a newer Orca on this host.' )} @@ -131,7 +132,7 @@ export function AgentPermissionsSetting(props: AgentPermissionsSettingProps): Re )} > - {translate('settings.agents.otherAgentsPermissions', 'Other agents')} + {t('settings.agents.otherAgentsPermissions', 'Other agents')} void }): React.JSX.Element { + const t = useTranslate() const { mode } = permission - const text = mode === 'mixed' ? customLabel() : agentPermissionModeLabel(mode) + const text = mode === 'mixed' ? customLabel(t) : agentPermissionModeLabel(mode, t) const button = ( ) : null} @@ -90,7 +89,7 @@ export function AgentRowPermissionControl({ ) { return mode === 'manual' ? 'own' : mode === 'yolo' ? 'full-access' : 'auto' } -export function agentPermissionModeLabel(mode: Exclude): string { - return agentPermissionChoiceLabel(agentPermissionModeChoice(mode), null) +export function agentPermissionModeLabel( + mode: Exclude, + t?: Translate +): string { + return agentPermissionChoiceLabel(agentPermissionModeChoice(mode), null, t) } diff --git a/src/renderer/src/components/settings/agents/permissions/agent-permission-override-notice.tsx b/src/renderer/src/components/settings/agents/permissions/agent-permission-override-notice.tsx index 0fee6e3bc05..2e2c72f4209 100644 --- a/src/renderer/src/components/settings/agents/permissions/agent-permission-override-notice.tsx +++ b/src/renderer/src/components/settings/agents/permissions/agent-permission-override-notice.tsx @@ -2,6 +2,7 @@ import { useEffect, useId, useRef } from 'react' import { toast } from 'sonner' import { getAgentLabel } from '@/lib/agent-catalog' import { translate } from '@/i18n/i18n' +import { useTranslate } from '@/i18n/use-translate' import { useMountedRef } from '@/hooks/useMountedRef' import type { AgentLaunchSettings, @@ -27,6 +28,7 @@ function modeLabel(choice: AgentPermissionChoice): string { } export function useAgentPermissionOverrideNotice() { + const t = useTranslate() const id = useId() const mounted = useMountedRef() const active = useRef(undefined) @@ -90,14 +92,14 @@ export function useAgentPermissionOverrideNotice() { const more = (count: number) => count > 3 ? (

- {translate('settings.agents.permissionOverrideMore', 'and {{count}} more', { + {t('settings.agents.permissionOverrideMore', 'and {{count}} more', { count: count - 3 })}
) : null toast.success( retained.length - ? translate( + ? t( 'settings.agents.permissionOverridesKept', '{{count}} agents still use their own permission setting:', { @@ -105,15 +107,11 @@ export function useAgentPermissionOverrideNotice() { defaultValue_one: '{{count}} agent still uses their own permission setting:' } ) - : translate( - 'settings.agents.permissionOverridesFollow', - '{{count}} agents now follow {{mode}}', - { - count: vanished.length, - mode, - defaultValue_one: '{{count}} agent now follows {{mode}}' - } - ), + : t('settings.agents.permissionOverridesFollow', '{{count}} agents now follow {{mode}}', { + count: vanished.length, + mode, + defaultValue_one: '{{count}} agent now follows {{mode}}' + }), { id, description: ( @@ -128,11 +126,10 @@ export function useAgentPermissionOverrideNotice() { {more(retained.length)} {vanished.slice(0, 3).map((agent) => (
- {translate( - 'settings.agents.permissionOverrideFollows', - '{{agent}} now follows {{mode}}', - { agent: getAgentLabel(agent), mode } - )} + {t('settings.agents.permissionOverrideFollows', '{{agent}} now follows {{mode}}', { + agent: getAgentLabel(agent), + mode + })}
))} {more(vanished.length)} @@ -142,7 +139,7 @@ export function useAgentPermissionOverrideNotice() { ...(retained.length ? { action: { - label: translate('settings.agents.permissionOverridesUse', 'Use {{mode}}', { + label: t('settings.agents.permissionOverridesUse', 'Use {{mode}}', { mode }), onClick: () => { diff --git a/src/renderer/src/components/settings/agents/runtime/AgentRuntimeSetting.tsx b/src/renderer/src/components/settings/agents/runtime/AgentRuntimeSetting.tsx index 51809d843e0..abbee918f04 100644 --- a/src/renderer/src/components/settings/agents/runtime/AgentRuntimeSetting.tsx +++ b/src/renderer/src/components/settings/agents/runtime/AgentRuntimeSetting.tsx @@ -7,6 +7,7 @@ import { import { Select, SelectContent, SelectItem, SelectTrigger, SelectValue } from '../../../ui/select' import { SettingsRow, SettingsSegmentedControl } from '../../form-controls/SettingsFormControls' import { translate } from '@/i18n/i18n' +import { useTranslate, type Translate } from '@/i18n/use-translate' type AgentRuntimeSegment = GlobalWindowsRuntimeDefault['kind'] @@ -38,6 +39,7 @@ export function AgentRuntimeSetting({ wslDistros = EMPTY_WSL_DISTROS, wslCapabilitiesLoading = false }: AgentRuntimeSettingProps): React.JSX.Element | null { + const t = useTranslate() if (!wslSupportedPlatform) { return null } @@ -62,16 +64,13 @@ export function AgentRuntimeSetting({ return ( - ariaLabel={translate( - 'auto.components.settings.AgentRuntimeSetting.label', - 'Agent runtime' - )} + ariaLabel={t('auto.components.settings.AgentRuntimeSetting.label', 'Agent runtime')} value={runtimeDefault.kind} onChange={handleRuntimeChange} equalWidth @@ -82,7 +81,7 @@ export function AgentRuntimeSetting({ }, { value: 'wsl', - label: translate('auto.components.settings.AgentRuntimeSetting.wsl', 'WSL'), + label: t('auto.components.settings.AgentRuntimeSetting.wsl', 'WSL'), disabled: wslCapabilitiesLoading || !wslAvailable || !nextWslDistro } ]} @@ -103,11 +102,8 @@ export function AgentRuntimeSetting({ {!runtimeDefault.distro ? ( - {translate( + {t( 'auto.components.settings.AgentRuntimeSetting.selectDistro', 'Select distro' )} @@ -165,27 +161,28 @@ function getVisibleDistroOptions( function getDescription( runtimeDefault: GlobalWindowsRuntimeDefault, wslAvailable: boolean, - wslCapabilitiesLoading: boolean + wslCapabilitiesLoading: boolean, + t: Translate = translate ): string { if (runtimeDefault.kind === 'windows-host') { - return translate( + return t( 'auto.components.settings.AgentRuntimeSetting.windowsDescription', 'Detect and launch agents on Windows for projects that do not override their runtime.' ) } if (!wslAvailable && !wslCapabilitiesLoading) { - return translate( + return t( 'auto.components.settings.AgentRuntimeSetting.wslUnavailable', 'WSL is not available on this machine.' ) } if (!runtimeDefault.distro) { - return translate( + return t( 'auto.components.settings.AgentRuntimeSetting.distroRequired', 'Choose a WSL distro before projects can inherit WSL.' ) } - return translate( + return t( 'auto.components.settings.AgentRuntimeSetting.wslDescription', 'Detect and launch agents in {{value0}} via WSL for projects that do not override their runtime.', { value0: runtimeDefault.distro } diff --git a/src/renderer/src/components/settings/agents/runtime/CodexTerminalServerIsolationSetting.tsx b/src/renderer/src/components/settings/agents/runtime/CodexTerminalServerIsolationSetting.tsx index b7a19b2a4d0..b9e0154b5f2 100644 --- a/src/renderer/src/components/settings/agents/runtime/CodexTerminalServerIsolationSetting.tsx +++ b/src/renderer/src/components/settings/agents/runtime/CodexTerminalServerIsolationSetting.tsx @@ -13,6 +13,7 @@ import { } from './codex-terminal-server-isolation-copy' import { SearchableSetting } from '../../section-layout/SearchableSetting' import { SettingsSwitchRow } from '../../form-controls/SettingsFormControls' +import { useTranslate } from '@/i18n/use-translate' type CodexTerminalServerIsolationSettingProps = { settings: GlobalSettings @@ -23,15 +24,16 @@ export function CodexTerminalServerIsolationSetting({ settings, updateSettings }: CodexTerminalServerIsolationSettingProps): React.JSX.Element { - const title = getCodexTerminalServerIsolationTitle() - const description = getCodexTerminalServerIsolationDescription() + const t = useTranslate() + const title = getCodexTerminalServerIsolationTitle(t) + const description = getCodexTerminalServerIsolationDescription(t) const enabled = isCodexTerminalServerIsolationEnabled(settings) return ( @@ -44,8 +46,8 @@ export function CodexTerminalServerIsolationSetting({ {/* With isolation off, sharing is intentional and needs no warning control. */} {enabled ? ( void updateSettings({ diff --git a/src/renderer/src/components/settings/agents/runtime/codex-terminal-server-isolation-copy.ts b/src/renderer/src/components/settings/agents/runtime/codex-terminal-server-isolation-copy.ts index f8c958907c0..5d388761e12 100644 --- a/src/renderer/src/components/settings/agents/runtime/codex-terminal-server-isolation-copy.ts +++ b/src/renderer/src/components/settings/agents/runtime/codex-terminal-server-isolation-copy.ts @@ -1,46 +1,50 @@ import { translate } from '@/i18n/i18n' +import type { Translate } from '@/i18n/use-translate' import { searchKeywords } from '../../search/settings-search-keywords' -export function getCodexTerminalServerIsolationTitle(): string { - return translate( +export function getCodexTerminalServerIsolationTitle(t: Translate = translate): string { + return t( 'settings.agents.codexTerminalServerIsolation.title', 'Run each Codex terminal on its own server' ) } -export function getCodexTerminalServerIsolationDescription(): string { - return translate( +export function getCodexTerminalServerIsolationDescription(t: Translate = translate): string { + return t( 'settings.agents.codexTerminalServerIsolation.description', "Keeps Orca's status and closing tabs working correctly. Turn off to use Codex's shared server and its agents overview. Applies to new terminals." ) } -export function getCodexSharedServerWarningTitle(): string { - return translate( +export function getCodexSharedServerWarningTitle(t: Translate = translate): string { + return t( 'settings.agents.codexSharedServerWarning.title', 'Warn when a Codex tab shares a server' ) } -export function getCodexSharedServerWarningDescription(): string { - return translate( +export function getCodexSharedServerWarningDescription(t: Translate = translate): string { + return t( 'settings.agents.codexSharedServerWarning.description', 'Shows a notice on a Codex you started yourself when it shares a server with other tabs, because its agent status may be wrong.' ) } -export function getCodexTerminalServerIsolationSearchKeywords(): string[] { - return searchKeywords([ - { - key: 'auto.components.settings.agents.search.5ded38b843', - fallback: 'codex', - englishOnly: true - }, - { key: 'auto.components.settings.agents.search.7e15b89f6e', fallback: 'server' }, - { key: 'auto.components.settings.agents.search.ff457e1e7b', fallback: 'daemon' }, - { key: 'auto.components.settings.agents.search.9f3becc4e8', fallback: 'shared' }, - { key: 'auto.components.settings.agents.search.34337ed5c7', fallback: 'isolate' }, - { key: 'auto.components.settings.agents.search.9a84e65118', fallback: 'agents overview' }, - { key: 'auto.components.settings.agents.search.6984d4291a', fallback: 'status' } - ]) +export function getCodexTerminalServerIsolationSearchKeywords(t?: Translate): string[] { + return searchKeywords( + [ + { + key: 'auto.components.settings.agents.search.5ded38b843', + fallback: 'codex', + englishOnly: true + }, + { key: 'auto.components.settings.agents.search.7e15b89f6e', fallback: 'server' }, + { key: 'auto.components.settings.agents.search.ff457e1e7b', fallback: 'daemon' }, + { key: 'auto.components.settings.agents.search.9f3becc4e8', fallback: 'shared' }, + { key: 'auto.components.settings.agents.search.34337ed5c7', fallback: 'isolate' }, + { key: 'auto.components.settings.agents.search.9a84e65118', fallback: 'agents overview' }, + { key: 'auto.components.settings.agents.search.6984d4291a', fallback: 'status' } + ], + t + ) } diff --git a/src/renderer/src/components/settings/agents/surfaces/AgentDefaultSurfaceSection.tsx b/src/renderer/src/components/settings/agents/surfaces/AgentDefaultSurfaceSection.tsx index 7970239e2dd..64650664606 100644 --- a/src/renderer/src/components/settings/agents/surfaces/AgentDefaultSurfaceSection.tsx +++ b/src/renderer/src/components/settings/agents/surfaces/AgentDefaultSurfaceSection.tsx @@ -8,7 +8,8 @@ import { LoadingSpinner } from '@/components/LoadingSpinner' import { useImmediateMutation } from '@/hooks/useImmediateMutation' import { useMountedRef } from '@/hooks/useMountedRef' import { motionSafeScrollBehavior } from '@/hooks/usePrefersReducedMotion' -import { getIntlLocale, translate } from '@/i18n/i18n' +import { translate } from '@/i18n/i18n' +import { useIntlLocale, useTranslate } from '@/i18n/use-translate' import { getAgentLabel } from '@/lib/agent-catalog' import { cn } from '@/lib/utils' import { useAppStore } from '@/store/index' @@ -41,6 +42,8 @@ type Writer = { /** After the default changes, names the agents set for themselves to the other surface. */ function useKeptSurfaceNotice(): (surface: AgentDefaultSurface, writer: Writer) => void { + const intlLocale = useIntlLocale() + const t = useTranslate() const id = useId() const mounted = useMountedRef() useEffect( @@ -61,7 +64,7 @@ function useKeptSurfaceNotice(): (surface: AgentDefaultSurface, writer: Writer) return } const values = { - agents: new Intl.ListFormat(getIntlLocale(), { type: 'conjunction' }).format( + agents: new Intl.ListFormat(intlLocale, { type: 'conjunction' }).format( kept.map((agent) => getAgentLabel(agent)) ), count: kept.length @@ -69,12 +72,12 @@ function useKeptSurfaceNotice(): (surface: AgentDefaultSurface, writer: Writer) const chat = surface === 'chat' toast.success( chat - ? translate('settings.agents.defaultSurfaceNowChat', 'Agents now open as chats') - : translate('settings.agents.defaultSurfaceNowTerminal', 'Agents now open in the terminal'), + ? t('settings.agents.defaultSurfaceNowChat', 'Agents now open as chats') + : t('settings.agents.defaultSurfaceNowTerminal', 'Agents now open in the terminal'), { id, description: chat - ? translate( + ? t( 'settings.agents.defaultSurfaceKeptTerminal', '{{agents}} stay in the terminal, as you set.', { @@ -83,19 +86,15 @@ function useKeptSurfaceNotice(): (surface: AgentDefaultSurface, writer: Writer) defaultValue_one: '{{agents}} stays in the terminal, as you set.' } ) - : translate( - 'settings.agents.defaultSurfaceKeptChat', - '{{agents}} stay as chats, as you set.', - { - ...values, - count: values.count, - defaultValue_one: '{{agents}} stays as a chat, as you set.' - } - ), + : t('settings.agents.defaultSurfaceKeptChat', '{{agents}} stay as chats, as you set.', { + ...values, + count: values.count, + defaultValue_one: '{{agents}} stays as a chat, as you set.' + }), action: { label: chat - ? translate('settings.agents.defaultSurfaceSetAllChat', 'Set all to Chat') - : translate('settings.agents.defaultSurfaceSetAllTerminal', 'Set all to Terminal'), + ? t('settings.agents.defaultSurfaceSetAllChat', 'Set all to Chat') + : t('settings.agents.defaultSurfaceSetAllTerminal', 'Set all to Terminal'), onClick: () => void followAgentDefaultSurface({ agents: kept, surface, ...writer }).catch(saveFailed) } @@ -174,13 +173,14 @@ export function AgentDefaultSurfaceSection({ settings: GlobalSettings presentation?: 'tiles' | 'switch' }): JSX.Element { + const t = useTranslate() const name = useId() const notify = useKeptSurfaceNotice() const updateSettings = useAppStore((s) => s.updateSettingsOrThrow) const mutation = useImmediateMutation() const pending = mutation.isPending('default-surface') const surface = normalizeAgentDefaultSurface(settings.agentDefaultSurface) - const title = translate('settings.agents.defaultSurfaceTitle', 'Open agents as') + const title = t('settings.agents.defaultSurfaceTitle', 'Open agents as') const choose = (next: AgentDefaultSurface): void => { const writer = { fallbackSettings: settings, updateSettings } void mutation.run('default-surface', { @@ -209,17 +209,17 @@ export function AgentDefaultSurfaceSection({ label={ {surface === 'chat' - ? translate('settings.chat.launch.on', 'Chat UI is on') - : translate('settings.chat.launch.off', 'Chat UI is off')} + ? t('settings.chat.launch.on', 'Chat UI is on') + : t('settings.chat.launch.off', 'Chat UI is off')} } description={ surface === 'chat' - ? translate( + ? t( 'settings.chat.launch.chatDescription', 'New agents will launch in chat mode by default.' ) - : translate( + : t( 'settings.chat.launch.cliDescription', 'New agents will launch as CLI by default.' ) @@ -231,8 +231,8 @@ export function AgentDefaultSurfaceSection({ onClick={() => choose(surface === 'chat' ? 'terminal' : 'chat')} > {surface === 'chat' - ? translate('settings.chat.launch.turnOff', 'Turn off') - : translate('settings.chat.launch.turnOn', 'Turn on')} + ? t('settings.chat.launch.turnOff', 'Turn off') + : t('settings.chat.launch.turnOn', 'Turn on')} } /> @@ -244,7 +244,7 @@ export function AgentDefaultSurfaceSection({
- {translate('settings.agents.defaultSurfacePerAgent', 'Choose per agent')} + {t('settings.agents.defaultSurfacePerAgent', 'Choose per agent')}
diff --git a/src/renderer/src/components/settings/appearance/AppIconSelector.tsx b/src/renderer/src/components/settings/appearance/AppIconSelector.tsx index a7dff400087..a75c83811f1 100644 --- a/src/renderer/src/components/settings/appearance/AppIconSelector.tsx +++ b/src/renderer/src/components/settings/appearance/AppIconSelector.tsx @@ -10,7 +10,7 @@ import { } from '../../../../../shared/app-icon' import { Button } from '../../ui/button' import { Tooltip, TooltipContent, TooltipTrigger } from '../../ui/tooltip' -import { translate } from '@/i18n/i18n' +import { useTranslate } from '@/i18n/use-translate' const APP_ICON_URLS = { classic: classicIconUrl, @@ -56,23 +56,24 @@ function IconCycleButton({ label, onClick, children }: IconCycleButtonProps): Re } export function AppIconSelector({ value, onChange }: AppIconSelectorProps): React.JSX.Element { + const t = useTranslate() const selected = normalizeAppIconId(value) return (
onChange(getOffsetIcon(selected, -1))} > {translate('auto.components.settings.AppIconSelector.415fa76f64', onChange(getOffsetIcon(selected, 1))} > diff --git a/src/renderer/src/components/settings/appearance/AppearanceAdvancedDisclosure.tsx b/src/renderer/src/components/settings/appearance/AppearanceAdvancedDisclosure.tsx index 4d45589264e..acf9f26be10 100644 --- a/src/renderer/src/components/settings/appearance/AppearanceAdvancedDisclosure.tsx +++ b/src/renderer/src/components/settings/appearance/AppearanceAdvancedDisclosure.tsx @@ -4,7 +4,7 @@ import { ChevronRight } from 'lucide-react' import { cn } from '@/lib/utils' import { useAppStore } from '../../../store/index' import { normalizeSettingsSearchQuery } from '../search/settings-search' -import { translate } from '@/i18n/i18n' +import { useTranslate } from '@/i18n/use-translate' type AppearanceAdvancedDisclosureProps = { label?: string @@ -24,6 +24,7 @@ export function AppearanceAdvancedDisclosure({ contentClassName, children }: AppearanceAdvancedDisclosureProps): React.JSX.Element { + const t = useTranslate() const searchQuery = useAppStore((state) => state.settingsSearchQuery) const isSearching = normalizeSettingsSearchQuery(searchQuery).length > 0 const [open, setOpen] = useState(false) @@ -46,8 +47,7 @@ export function AppearanceAdvancedDisclosure({ expanded && 'rotate-90' )} /> - {label ?? - translate('auto.components.settings.AppearanceAdvancedDisclosure.advanced', 'Advanced')} + {label ?? t('auto.components.settings.AppearanceAdvancedDisclosure.advanced', 'Advanced')} {expanded ?
{children}
: null}
diff --git a/src/renderer/src/components/settings/appearance/AppearanceInterfaceSection.tsx b/src/renderer/src/components/settings/appearance/AppearanceInterfaceSection.tsx index 5ebb5fefad5..0ef59d980ef 100644 --- a/src/renderer/src/components/settings/appearance/AppearanceInterfaceSection.tsx +++ b/src/renderer/src/components/settings/appearance/AppearanceInterfaceSection.tsx @@ -24,7 +24,7 @@ import { } from './appearance-search' import { getMenuBarIconEntries, getSystemTrayEntries } from './appearance-system-presence-search' import { SHOW_UI_LANGUAGE_SETTING, UI_LANGUAGE_CHOICES } from '@/i18n/supported-languages' -import { translate } from '@/i18n/i18n' +import { useTranslate } from '@/i18n/use-translate' import type { UiLanguage } from '../../../../../shared/ui-language' import { matchesSettingsSearch, normalizeSettingsSearchQuery } from '../search/settings-search' import { translateSearchKeyword } from '../search/settings-search-keywords' @@ -56,6 +56,7 @@ export function AppearanceInterfaceSection({ onRequestFontSuggestions, forceVisiblePrimary = false }: AppearanceInterfaceSectionProps): React.JSX.Element { + const t = useTranslate() const searchQuery = useAppStore((state) => state.settingsSearchQuery) const iconThemes = useIconThemePickerLabels() const pluginIconThemes = iconThemes.themes @@ -63,39 +64,33 @@ export function AppearanceInterfaceSection({ const themeSlots = [ { base: 'light' as const, - title: translate( - 'auto.components.settings.AppearanceInterfaceSection.lightTheme', - 'Light theme' - ) + title: t('auto.components.settings.AppearanceInterfaceSection.lightTheme', 'Light theme') }, { base: 'dark' as const, - title: translate( - 'auto.components.settings.AppearanceInterfaceSection.darkTheme', - 'Dark theme' - ) + title: t('auto.components.settings.AppearanceInterfaceSection.darkTheme', 'Dark theme') } ] const editorThemeEntry = getCodeEditorThemeEntries()[0] const editorThemeSlots = [ { base: 'light' as const, - title: translate('settings.appearance.editorTheme.light', 'Light editor theme'), - description: translate( + title: t('settings.appearance.editorTheme.light', 'Light editor theme'), + description: t( 'settings.appearance.editorTheme.lightDescription', 'Code editor, diff and notebook colors while the app is light.' ) }, { base: 'dark' as const, - title: translate('settings.appearance.editorTheme.dark', 'Dark editor theme'), - description: translate( + title: t('settings.appearance.editorTheme.dark', 'Dark editor theme'), + description: t( 'settings.appearance.editorTheme.darkDescription', 'Code editor, diff and notebook colors while the app is dark.' ) } ] - const iconThemeTitle = translate( + const iconThemeTitle = t( 'auto.components.settings.AppearanceInterfaceSection.pluginIconTheme.title', 'Icon theme' ) @@ -106,7 +101,7 @@ export function AppearanceInterfaceSection({ const menuBarIconEntry = getMenuBarIconEntries({ showMenuBarIcon: true })[0] const systemTrayEntry = getSystemTrayEntries({ showSystemTray: true })[0] const themeEntry = getThemeEntries()[0] - const themeLabel = translate('auto.components.settings.AppearancePane.932ff1fbff', 'Theme') + const themeLabel = t('auto.components.settings.AppearancePane.932ff1fbff', 'Theme') const titlebarEntry = getTitlebarEntries()[0] const typographyEntry = getTypographyEntries()[0] const zoomEntry = getZoomEntries()[0] @@ -118,7 +113,7 @@ export function AppearanceInterfaceSection({ ...getMenuBarIconEntries({ showMenuBarIcon: isDesktopMac }) ] const showAdvanced = !isSearching || matchesSettingsSearch(searchQuery, advancedEntries) - const languageTitle = translate('settings.appearance.language.title', 'Language') + const languageTitle = t('settings.appearance.language.title', 'Language') return (
@@ -145,7 +140,7 @@ export function AppearanceInterfaceSection({ 0 ? ( theme.id === value)?.id ?? null }) } - searchPlaceholder={translate( + searchPlaceholder={t( 'auto.components.settings.AppearanceInterfaceSection.pluginIconTheme.search', 'Search icon themes…' )} @@ -281,19 +278,19 @@ export function AppearanceInterfaceSection({ ) : null} /{' '} {' '} - {translate( + {t( 'auto.components.settings.AppearancePane.ef89200c1f', 'when not in a terminal pane.' )} @@ -304,13 +301,13 @@ export function AppearanceInterfaceSection({
updateSettings({ showTitlebarAppName: !settings.showTitlebarAppName }) @@ -356,7 +347,7 @@ export function AppearanceInterfaceSection({ {isDesktopWindows ? ( state.settingsSearchQuery) const appearanceAccordionDeepLink = useAppStore((state) => state.appearanceAccordionDeepLink) const clearAppearanceAccordionDeepLink = useAppStore( @@ -114,19 +115,13 @@ export function AppearancePane({ cancelAnimationFrame(frameId) } }, [appearanceAccordionDeepLink, clearAppearanceAccordionDeepLink]) - const interfaceTitle = translate( - 'auto.components.settings.AppearancePane.interfaceTitle', - 'Interface' - ) - const terminalTitle = translate( - 'auto.components.settings.AppearancePane.terminalTitle', - 'Terminal' - ) - const windowSidebarTitle = translate( + const interfaceTitle = t('auto.components.settings.AppearancePane.interfaceTitle', 'Interface') + const terminalTitle = t('auto.components.settings.AppearancePane.terminalTitle', 'Terminal') + const windowSidebarTitle = t( 'auto.components.settings.AppearancePane.windowSidebarTitle', 'Window & Sidebar' ) - const windowSidebarSummary = translate( + const windowSidebarSummary = t( 'auto.components.settings.AppearancePane.windowSidebarSummary', 'Sidebar, status bar, and file explorer' ) @@ -196,10 +191,10 @@ export function AppearancePane({ }) } - const interfaceSummary = resolveInterfaceSectionSummary(settings) + const interfaceSummary = resolveInterfaceSectionSummary(settings, t) const terminalSummary = `${ settings.terminalFontFamily || - translate('auto.components.settings.AppearancePane.terminalDefaultFont', 'Default font') + t('auto.components.settings.AppearancePane.terminalDefaultFont', 'Default font') } · ${settings.terminalFontSize}px` return ( @@ -272,8 +267,8 @@ export function AppearancePane({ {appIconMatches ? ( - {translate('auto.components.settings.AppearancePane.3057983501', 'Unassigned')} + {t('auto.components.settings.AppearancePane.3057983501', 'Unassigned')} ) } diff --git a/src/renderer/src/components/settings/appearance/AppearanceWindowSidebarSection.tsx b/src/renderer/src/components/settings/appearance/AppearanceWindowSidebarSection.tsx index a16e437ae42..ca5597bf06b 100644 --- a/src/renderer/src/components/settings/appearance/AppearanceWindowSidebarSection.tsx +++ b/src/renderer/src/components/settings/appearance/AppearanceWindowSidebarSection.tsx @@ -28,7 +28,7 @@ import { import { LeftSidebarAppearanceSetting } from './LeftSidebarAppearanceSetting' -import { translate } from '@/i18n/i18n' +import { useTranslate } from '@/i18n/use-translate' import { matchesSettingsSearch, normalizeSettingsSearchQuery } from '../search/settings-search' type AppearanceWindowSidebarSectionProps = { @@ -68,6 +68,7 @@ export function AppearanceWindowSidebarSection({ updateSettings, forceVisiblePrimary = false }: AppearanceWindowSidebarSectionProps): React.JSX.Element { + const t = useTranslate() const searchQuery = useAppStore((state) => state.settingsSearchQuery) const isSearching = normalizeSettingsSearchQuery(searchQuery).length > 0 const statusBarItems = useAppStore((state) => state.statusBarItems) @@ -82,12 +83,9 @@ export function AppearanceWindowSidebarSection({ const sidebarEntries = getSidebarEntries() const workspaceCardLayoutEntry = getWorkspaceCardLayoutEntry() const layoutEntries = getLayoutEntries() - const followSymlinkEntry = getFollowSymlinkedDirectoriesEntry() - const statusBarTitle = translate( - 'auto.components.settings.AppearancePane.3e4175e5c6', - 'Status Bar' - ) - const statusBarDescription = translate( + const followSymlinkEntry = getFollowSymlinkedDirectoriesEntry(t) + const statusBarTitle = t('auto.components.settings.AppearancePane.3e4175e5c6', 'Status Bar') + const statusBarDescription = t( 'auto.components.settings.AppearancePane.statusBarDescription', 'Choose which indicators appear in the status bar.' ) @@ -154,14 +152,14 @@ export function AppearanceWindowSidebarSection({ options={[ { value: 'used', - label: translate( + label: t( 'auto.components.settings.AppearanceWindowSidebarSection.usagePercentageDisplayUsed', 'Used' ) }, { value: 'remaining', - label: translate( + label: t( 'auto.components.settings.AppearanceWindowSidebarSection.usagePercentageDisplayRemaining', 'Remaining' ) @@ -205,7 +203,7 @@ export function AppearanceWindowSidebarSection({ {showSidebarAdvanced ? (
updateSettings({ leftSidebarAppearanceMode })} - ariaLabel={translate( + ariaLabel={t( 'auto.components.settings.AppearancePane.leftSidebarAppearance.title', 'Left Sidebar Appearance' )} options={[ { value: 'default', - label: translate( + label: t( 'auto.components.settings.AppearancePane.leftSidebarAppearance.default', 'Default' ) }, { value: 'tinted', - label: translate( + label: t( 'auto.components.settings.AppearancePane.leftSidebarAppearance.tinted', 'Tinted' ) @@ -66,11 +67,11 @@ export function LeftSidebarAppearanceSetting({ {settings.leftSidebarAppearanceMode === 'tinted' ? (
updateSettings({ leftSidebarTintColor })} /> Promise | void }): React.JSX.Element { + const t = useTranslate() return ( - {translate( - 'auto.components.settings.RecentTabOrderControl.6e6a3fcc61', - 'Most recent' - )} + {t('auto.components.settings.RecentTabOrderControl.6e6a3fcc61', 'Most recent')} - {translate( - 'auto.components.settings.RecentTabOrderControl.3b17c81ede', - 'Tab strip order' - )} + {t('auto.components.settings.RecentTabOrderControl.3b17c81ede', 'Tab strip order')} diff --git a/src/renderer/src/components/settings/appearance/ReduceVisualEffectsSetting.tsx b/src/renderer/src/components/settings/appearance/ReduceVisualEffectsSetting.tsx index 908c3092c75..c61792d634a 100644 --- a/src/renderer/src/components/settings/appearance/ReduceVisualEffectsSetting.tsx +++ b/src/renderer/src/components/settings/appearance/ReduceVisualEffectsSetting.tsx @@ -2,7 +2,7 @@ import type { GlobalSettings } from '../../../../../shared/global-settings-types import type { SettingsSearchEntry } from '../search/settings-search' import { SearchableSetting } from '../section-layout/SearchableSetting' import { SettingsSwitchRow } from '../form-controls/SettingsFormControls' -import { translate } from '@/i18n/i18n' +import { useTranslate } from '@/i18n/use-translate' export function ReduceVisualEffectsSetting({ entry, @@ -13,7 +13,8 @@ export function ReduceVisualEffectsSetting({ enabled: boolean updateSettings: (updates: Partial) => void }): React.JSX.Element { - const title = translate('settings.appearance.reduceVisualEffects.title', 'Reduce Visual Effects') + const t = useTranslate() + const title = t('settings.appearance.reduceVisualEffects.title', 'Reduce Visual Effects') return ( window.api.ui.getZoomLevel()) useEffect(() => { @@ -55,7 +56,7 @@ export function UIZoomControl(): React.JSX.Element { className="ml-1 gap-1.5" > - {translate('auto.components.settings.UIZoomControl.c2c64b24d0', 'Reset')} + {t('auto.components.settings.UIZoomControl.c2c64b24d0', 'Reset')}
) diff --git a/src/renderer/src/components/settings/appearance/WindowMaterialSetting.tsx b/src/renderer/src/components/settings/appearance/WindowMaterialSetting.tsx index 8d434cce3cd..663ddc589f2 100644 --- a/src/renderer/src/components/settings/appearance/WindowMaterialSetting.tsx +++ b/src/renderer/src/components/settings/appearance/WindowMaterialSetting.tsx @@ -16,31 +16,40 @@ import { useMountedRef } from '@/hooks/useMountedRef' import { readPrefersReducedTransparency } from '@/hooks/use-reduce-visual-effects' import { activeWindowMaterial, rendererWindowMaterialHost } from '@/lib/window-material-runtime' import { translate } from '@/i18n/i18n' +import { useTranslate, type Translate } from '@/i18n/use-translate' type WindowMaterialSettingProps = { settings: GlobalSettings updateSettings: (updates: Partial) => void } -function windowMaterialLabel(material: WindowMaterial, host: WindowMaterialHost): string { +function windowMaterialLabel( + material: WindowMaterial, + host: WindowMaterialHost, + t: Translate = translate +): string { if (material === 'off') { - return translate('settings.windowMaterial.off', 'Opaque') + return t('settings.windowMaterial.off', 'Opaque') } if (material === 'translucent') { - return translate('settings.windowMaterial.translucent', 'Translucent') + return t('settings.windowMaterial.translucent', 'Translucent') } return host.platform === 'win32' - ? translate('settings.windowMaterial.acrylic', 'Acrylic blur') - : translate('settings.windowMaterial.blur', 'Blurred') + ? t('settings.windowMaterial.acrylic', 'Acrylic blur') + : t('settings.windowMaterial.blur', 'Blurred') } -function windowMaterialHint(settings: GlobalSettings, host: WindowMaterialHost): string { - const description = translate( +function windowMaterialHint( + settings: GlobalSettings, + host: WindowMaterialHost, + t: Translate = translate +): string { + const description = t( 'settings.windowMaterial.description', 'What shows behind translucent terminals. Requires restart.' ) if (selectableWindowMaterials(host).length === 1) { - return translate( + return t( 'settings.windowMaterial.windowsUnsupported', 'Requires Windows 11 version 22H2 or later.' ) @@ -49,19 +58,19 @@ function windowMaterialHint(settings: GlobalSettings, host: WindowMaterialHost): return description } if (settings.reduceVisualEffects === true || readPrefersReducedTransparency()) { - return translate( + return t( 'settings.windowMaterial.reducedEffects', 'Off while Reduce Visual Effects or the system Reduce Transparency setting is on.' ) } if ((settings.terminalBackgroundOpacity ?? 1) >= 1) { - return translate( + return t( 'settings.windowMaterial.needsOpacity', 'Lower Background Opacity below 1 to see the desktop through terminals.' ) } return host.platform === 'linux' - ? translate( + ? t( 'settings.windowMaterial.linuxCompositor', 'Your compositor decides whether to blur it. Requires restart.' ) @@ -69,6 +78,7 @@ function windowMaterialHint(settings: GlobalSettings, host: WindowMaterialHost): } function RestartBanner(): React.JSX.Element { + const t = useTranslate() const [relaunching, setRelaunching] = useState(false) const mountedRef = useMountedRef() @@ -90,13 +100,10 @@ function RestartBanner(): React.JSX.Element {

- {translate( - 'auto.components.settings.TerminalWindowSection.c65bb9ce63', - 'Restart required' - )} + {t('auto.components.settings.TerminalWindowSection.c65bb9ce63', 'Restart required')}

- {translate( + {t( 'settings.windowMaterial.restartDescription', 'Restart Orca to apply the window material change.' )} @@ -111,8 +118,8 @@ function RestartBanner(): React.JSX.Element { > {relaunching - ? translate('auto.components.settings.TerminalWindowSection.907131d741', 'Restarting…') - : translate('auto.components.settings.TerminalWindowSection.8abdab9f7c', 'Restart now')} + ? t('auto.components.settings.TerminalWindowSection.907131d741', 'Restarting…') + : t('auto.components.settings.TerminalWindowSection.8abdab9f7c', 'Restart now')}

) @@ -123,9 +130,10 @@ export function WindowMaterialSetting({ settings, updateSettings }: WindowMaterialSettingProps): React.JSX.Element { + const t = useTranslate() const host = rendererWindowMaterialHost() const selectable = selectableWindowMaterials(host) - const label = translate('settings.windowMaterial.title', 'Window Material') + const label = t('settings.windowMaterial.title', 'Window Material') // Why compare resolved values: opacity and Reduce Visual Effects also decide the material. const restartPending = resolveWindowMaterial(settings, host, readPrefersReducedTransparency()) !== @@ -135,7 +143,7 @@ export function WindowMaterialSetting({ <> {selectable.map((material) => ( - {windowMaterialLabel(material, host)} + {windowMaterialLabel(material, host, t)} ))} diff --git a/src/renderer/src/components/settings/appearance/appearance-interface-summary.ts b/src/renderer/src/components/settings/appearance/appearance-interface-summary.ts index 4e684aa5ce1..32f3451fe3e 100644 --- a/src/renderer/src/components/settings/appearance/appearance-interface-summary.ts +++ b/src/renderer/src/components/settings/appearance/appearance-interface-summary.ts @@ -5,31 +5,38 @@ import { UI_LANGUAGE_CHOICES } from '@/i18n/supported-languages' import { translate } from '@/i18n/i18n' +import type { Translate } from '@/i18n/use-translate' -function resolveThemeSummary(theme: GlobalSettings['theme']): string { +function resolveThemeSummary(theme: GlobalSettings['theme'], t: Translate = translate): string { if (theme === 'system') { - return translate('auto.components.settings.AppearancePane.fb0e0b4453', 'System') + return t('auto.components.settings.AppearancePane.fb0e0b4453', 'System') } if (theme === 'light') { - return translate('auto.components.settings.AppearancePane.fd89b5487c', 'Light') + return t('auto.components.settings.AppearancePane.fd89b5487c', 'Light') } - return translate('auto.components.settings.AppearancePane.7d26ccabe8', 'Dark') + return t('auto.components.settings.AppearancePane.7d26ccabe8', 'Dark') } -function resolveLanguageSummary(uiLanguage: GlobalSettings['uiLanguage']): string { +function resolveLanguageSummary( + uiLanguage: GlobalSettings['uiLanguage'], + t: Translate = translate +): string { const choice = UI_LANGUAGE_CHOICES.find((entry) => entry.value === uiLanguage) if (choice == null) { - return translate('settings.appearance.language.system', 'System') + return t('settings.appearance.language.system', 'System') } return getUiLanguageChoiceLabel(choice, translate) } -export function resolveInterfaceSectionSummary(settings: GlobalSettings): string { +export function resolveInterfaceSectionSummary( + settings: GlobalSettings, + t: Translate = translate +): string { const fontSummary = settings.appFontFamily || - translate('auto.components.settings.AppearancePane.interfaceDefaultFont', 'Default font') + t('auto.components.settings.AppearancePane.interfaceDefaultFont', 'Default font') if (!SHOW_UI_LANGUAGE_SETTING) { - return `${resolveThemeSummary(settings.theme)} · ${fontSummary}` + return `${resolveThemeSummary(settings.theme, t)} · ${fontSummary}` } - return `${resolveThemeSummary(settings.theme)} · ${resolveLanguageSummary(settings.uiLanguage)} · ${fontSummary}` + return `${resolveThemeSummary(settings.theme, t)} · ${resolveLanguageSummary(settings.uiLanguage, t)} · ${fontSummary}` } diff --git a/src/renderer/src/components/settings/appearance/appearance-search.ts b/src/renderer/src/components/settings/appearance/appearance-search.ts index 38742c483b6..f04c04b938d 100644 --- a/src/renderer/src/components/settings/appearance/appearance-search.ts +++ b/src/renderer/src/components/settings/appearance/appearance-search.ts @@ -3,6 +3,7 @@ import { getTerminalAppearanceSearchEntries } from '../terminal/terminal-search' import { getSidebarEntries } from './appearance-sidebar-search' import { createLocalizedCatalog } from '@/i18n/localized-catalog' import { translate } from '@/i18n/i18n' +import type { Translate } from '@/i18n/use-translate' import { translateSearchKeyword } from '../search/settings-search-keywords' import { SHOW_UI_LANGUAGE_SETTING } from '@/i18n/supported-languages' import { getStatusBarToggles } from './appearance-status-bar-search' @@ -144,25 +145,38 @@ export const getTypographyEntries = createLocalizedCatalog((): SettingsSearchEnt } ]) -export const getFollowSymlinkedDirectoriesEntry = (): SettingsSearchEntry => ({ - title: translate('settings.followSymlinkedDirectories', 'Follow symlinked directories'), - description: translate( +export const getFollowSymlinkedDirectoriesEntry = ( + t: Translate = translate +): SettingsSearchEntry => ({ + title: t('settings.followSymlinkedDirectories', 'Follow symlinked directories'), + description: t( 'settings.followSymlinkedDirectoriesDescription', 'Search linked folders in Quick Open and expand local linked folders in File Explorer.' ), keywords: [ - ...translateSearchKeyword('settings.followSymlinkedDirectoriesKeywords.symlink', 'symlink'), + ...translateSearchKeyword( + 'settings.followSymlinkedDirectoriesKeywords.symlink', + 'symlink', + undefined, + t + ), ...translateSearchKeyword( 'settings.followSymlinkedDirectoriesKeywords.linkedFolder', - 'linked folder' + 'linked folder', + undefined, + t ), ...translateSearchKeyword( 'settings.followSymlinkedDirectoriesKeywords.quickOpen', - 'quick open' + 'quick open', + undefined, + t ), ...translateSearchKeyword( 'settings.followSymlinkedDirectoriesKeywords.fileExplorer', - 'file explorer' + 'file explorer', + undefined, + t ) ] }) diff --git a/src/renderer/src/components/settings/appearance/chat/AppearanceChatContrastControls.tsx b/src/renderer/src/components/settings/appearance/chat/AppearanceChatContrastControls.tsx index c2d20b78f3e..49e46e8123b 100644 --- a/src/renderer/src/components/settings/appearance/chat/AppearanceChatContrastControls.tsx +++ b/src/renderer/src/components/settings/appearance/chat/AppearanceChatContrastControls.tsx @@ -5,7 +5,7 @@ import { chatTerminalControlledTooltip, getChatContrastEntriesByKey } from './chat-appearance-search' -import { translate } from '@/i18n/i18n' +import { useTranslate } from '@/i18n/use-translate' import { Slider } from '../../../ui/slider' import { SearchableSetting } from '../../section-layout/SearchableSetting' import { SettingsDisabledControlTooltip } from '../../form-controls/SettingsDisabledControlTooltip' @@ -22,6 +22,7 @@ export function AppearanceChatContrastControls({ onChange, forceVisiblePrimary }: ChatContrastControlsProps): React.JSX.Element { + const t = useTranslate() const entries = getChatContrastEntriesByKey() const { contrast, matchTerminalInterface: matching } = appearance return ( @@ -45,7 +46,7 @@ export function AppearanceChatContrastControls({ > } @@ -63,6 +64,7 @@ function ChatContrastSlider({ contrast: number disabled: boolean }): React.JSX.Element { + const t = useTranslate() const [draft, setDraft] = useState({ savedContrast: contrast, value: contrast }) // Keep the thumb mounted so committed keyboard changes retain focus. if (draft.savedContrast !== contrast) { @@ -71,10 +73,10 @@ function ChatContrastSlider({ return (
- {translate('settings.appearance.chat.softer', 'Softer')} + {t('settings.appearance.chat.softer', 'Softer')}
setDraft({ savedContrast: contrast, value })} onValueCommit={([value]) => onChange({ contrast: value })} />
- {translate('settings.appearance.chat.sharper', 'Sharper')} + {t('settings.appearance.chat.sharper', 'Sharper')} {draft.value} diff --git a/src/renderer/src/components/settings/appearance/chat/AppearanceChatSection.tsx b/src/renderer/src/components/settings/appearance/chat/AppearanceChatSection.tsx index f89375abe18..08da321a3b2 100644 --- a/src/renderer/src/components/settings/appearance/chat/AppearanceChatSection.tsx +++ b/src/renderer/src/components/settings/appearance/chat/AppearanceChatSection.tsx @@ -6,7 +6,7 @@ import { } from '../../../../../../shared/native-chat/native-chat-appearance-settings' import { useAppStore } from '../../../../store/index' import { formatPrimaryShortcutLabel } from '@/hooks/useShortcutLabel' -import { translate } from '@/i18n/i18n' +import { useTranslate } from '@/i18n/use-translate' import { AppearanceChatContrastControls } from './AppearanceChatContrastControls' import { ChatFontFamilySetting } from './ChatFontFamilySetting' import { Button } from '../../../ui/button' @@ -40,12 +40,13 @@ export function AppearanceChatSection({ onRequestFontSuggestions, forceVisiblePrimary = false }: AppearanceChatSectionProps): React.JSX.Element { + const t = useTranslate() const appearance = resolveNativeChatAppearanceSettings(settings.nativeChatAppearance) const matching = appearance.matchTerminalInterface const keybindings = useAppStore((state) => state.keybindings) const increase = formatPrimaryShortcutLabel('zoom.in', keybindings) const decrease = formatPrimaryShortcutLabel('zoom.out', keybindings) - const entries = getChatAppearanceEntriesByKey({ increase, decrease }) + const entries = getChatAppearanceEntriesByKey({ increase, decrease }, t) const update = (updates: NativeChatAppearanceSettings): void => { void writeNativeChatAppearance( (current) => normalizeNativeChatAppearanceSettings({ ...current, ...updates }), @@ -73,15 +74,15 @@ export function AppearanceChatSection({ > update({ fontSize })} /> @@ -92,15 +93,15 @@ export function AppearanceChatSection({ > update({ codeFontSize })} /> @@ -116,7 +117,7 @@ export function AppearanceChatSection({ update({ width })} - options={getChatWidthOptions()} + options={getChatWidthOptions(t)} ariaLabel={entries.width.title} /> } @@ -138,7 +139,7 @@ export function AppearanceChatSection({ void writeNativeChatAppearance(resetNativeChatAppearanceSettings, updateSettings) } > - {translate('settings.appearance.chat.reset', 'Reset')} + {t('settings.appearance.chat.reset', 'Reset')} } /> diff --git a/src/renderer/src/components/settings/appearance/chat/ChatFontFamilySetting.tsx b/src/renderer/src/components/settings/appearance/chat/ChatFontFamilySetting.tsx index d8a89b650d1..618259f0e28 100644 --- a/src/renderer/src/components/settings/appearance/chat/ChatFontFamilySetting.tsx +++ b/src/renderer/src/components/settings/appearance/chat/ChatFontFamilySetting.tsx @@ -1,7 +1,7 @@ import { useState } from 'react' import type { GlobalSettings } from '../../../../../../shared/global-settings-types' import { resolveNativeChatAppearanceSettings } from '../../../../../../shared/native-chat/native-chat-appearance-settings' -import { translate } from '@/i18n/i18n' +import { useTranslate } from '@/i18n/use-translate' import { NATIVE_CHAT_APPEARANCE_ROOT_CLASS, useNativeChatAppearanceStyle @@ -31,10 +31,11 @@ export function ChatFontFamilySetting({ onRequestFontSuggestions?: () => void forceVisible?: boolean }): React.JSX.Element { + const t = useTranslate() const appearance = resolveNativeChatAppearanceSettings(settings.nativeChatAppearance) const matching = appearance.matchTerminalInterface const [previewFamily, setPreviewFamily] = useState(null) - const entry = getChatAppearanceEntriesByKey().fontFamily + const entry = getChatAppearanceEntriesByKey(undefined, t).fontFamily const style = useNativeChatAppearanceStyle({ ...settings, nativeChatAppearance: { @@ -42,16 +43,16 @@ export function ChatFontFamilySetting({ fontFamily: previewFamily ?? appearance.fontFamily } }) - const defaultLabel = translate('settings.appearance.chat.inheritFont', 'Same as IDE font') + const defaultLabel = t('settings.appearance.chat.inheritFont', 'Same as IDE font') return (

- {translate( - 'settings.appearance.chat.fontPreview', - 'The quick brown fox jumps over the lazy dog.' - )}{' '} + {t('settings.appearance.chat.fontPreview', 'The quick brown fox jumps over the lazy dog.')}{' '} fontFamily

diff --git a/src/renderer/src/components/settings/appearance/chat/chat-appearance-search.ts b/src/renderer/src/components/settings/appearance/chat/chat-appearance-search.ts index ae0a6679c0e..a161e913cc1 100644 --- a/src/renderer/src/components/settings/appearance/chat/chat-appearance-search.ts +++ b/src/renderer/src/components/settings/appearance/chat/chat-appearance-search.ts @@ -5,12 +5,12 @@ import { translateSearchKeyword } from '../../search/settings-search-keywords' import type { SettingsSearchEntry } from '../../search/settings-search' import { formatPrimaryShortcutLabel } from '@/hooks/useShortcutLabel' -export function chatTerminalControlledHint(): string { - return translate('settings.appearance.chat.terminalControlledHint', 'Set by terminal interface.') +export function chatTerminalControlledHint(t: Translate = translate): string { + return t('settings.appearance.chat.terminalControlledHint', 'Set by terminal interface.') } -export function chatTerminalControlledTooltip(): string { - return translate( +export function chatTerminalControlledTooltip(t: Translate = translate): string { + return t( 'settings.appearance.chat.terminalControlledTooltip', 'Matching your terminal interface. Turn off Match terminal interface to change this.' ) @@ -99,13 +99,16 @@ const getChatAppearanceCatalog = createLocalizedCatalog( }) satisfies Record ) -export function getChatAppearanceEntriesByKey(shortcuts?: { increase: string; decrease: string }) { +export function getChatAppearanceEntriesByKey( + shortcuts?: { increase: string; decrease: string }, + t: Translate = translate +) { const entries = getChatAppearanceCatalog() return { ...entries, textSize: { ...entries.textSize, - description: translate( + description: t( 'settings.appearance.chat.textSizeDescription', 'Messages, tool activity and the message box. {{increase}} / {{decrease}} in a chat change this too.', { diff --git a/src/renderer/src/components/settings/appearance/fonts/FontAutocomplete.tsx b/src/renderer/src/components/settings/appearance/fonts/FontAutocomplete.tsx index 89d1a09713c..2289b5d42c6 100644 --- a/src/renderer/src/components/settings/appearance/fonts/FontAutocomplete.tsx +++ b/src/renderer/src/components/settings/appearance/fonts/FontAutocomplete.tsx @@ -6,7 +6,7 @@ import { Popover, PopoverAnchor, PopoverContent } from '../../../ui/popover' import { ScrollArea } from '../../../ui/scroll-area' import { filterFontSuggestions, getRenderedFontSuggestions } from './font-suggestion-filter' import { useInstalledFontsUnavailable } from './installed-font-list' -import { translate } from '@/i18n/i18n' +import { useTranslate } from '@/i18n/use-translate' import { buildAppFontFamily } from '@/lib/app-font-family' // Why: these OS-shipped fonts draw pictographs or Greek for Latin letters, so their own name would be unreadable. @@ -55,6 +55,7 @@ export function FontAutocomplete({ onRequestSuggestions, onPreviewFontFamily }: FontAutocompleteProps): React.JSX.Element { + const t = useTranslate() const [query, setQuery] = useState(value) const [prevValue, setPrevValue] = useState(value) const [open, setOpen] = useState(false) @@ -297,14 +298,11 @@ export function FontAutocomplete({ focusInput() }} className="rounded-sm p-1 text-muted-foreground transition-colors hover:bg-muted hover:text-foreground" - aria-label={translate( + aria-label={t( 'auto.components.settings.SettingsFormControls.a4ff6143f8', 'Clear font selection' )} - title={translate( - 'auto.components.settings.SettingsFormControls.74bcecd5ec', - 'Clear' - )} + title={t('auto.components.settings.SettingsFormControls.74bcecd5ec', 'Clear')} > @@ -325,14 +323,11 @@ export function FontAutocomplete({ } }} className="rounded-sm p-1 text-muted-foreground transition-colors hover:bg-muted hover:text-foreground" - aria-label={translate( + aria-label={t( 'auto.components.settings.SettingsFormControls.c766f8ac75', 'Toggle font suggestions' )} - title={translate( - 'auto.components.settings.SettingsFormControls.b55371ea18', - 'Fonts' - )} + title={t('auto.components.settings.SettingsFormControls.b55371ea18', 'Fonts')} > @@ -390,7 +385,7 @@ export function FontAutocomplete({ )) ) : (
- {translate( + {t( 'auto.components.settings.SettingsFormControls.42a4d15a30', 'No matching fonts.' )} @@ -400,7 +395,7 @@ export function FontAutocomplete({ {installedFontsUnavailable ? (

- {translate( + {t( 'auto.components.settings.FontAutocomplete.installedFontsUnavailable', 'Couldn’t list installed fonts. Type any installed font name to use it.' )} diff --git a/src/renderer/src/components/settings/appearance/themes/AppearanceThemeControl.tsx b/src/renderer/src/components/settings/appearance/themes/AppearanceThemeControl.tsx index f00e01c62c1..b3d7b8c8d51 100644 --- a/src/renderer/src/components/settings/appearance/themes/AppearanceThemeControl.tsx +++ b/src/renderer/src/components/settings/appearance/themes/AppearanceThemeControl.tsx @@ -15,6 +15,7 @@ import { } from '../../form-controls/SettingsSearchableSelect' import { SettingsSegmentedControl } from '../../form-controls/SettingsFormControls' import { translate } from '@/i18n/i18n' +import { useTranslate, type Translate } from '@/i18n/use-translate' import { usePluginTerminalThemes, usePluginThemes } from '@/store/plugins/appearance/plugin-themes' import { PluginThemeSwatch, ThemeColorSwatch } from '../../../plugins/page/PluginThemeSwatch' import { @@ -35,36 +36,36 @@ type BuiltInTheme = GlobalSettings['theme'] const BUILT_IN = 'built-in' const FOLLOW_APP = 'follow-app' -function browseThemesLabel(): string { - return translate('auto.components.settings.AppearanceThemeControl.browseThemes', 'Browse themes') +function browseThemesLabel(t: Translate = translate): string { + return t('auto.components.settings.AppearanceThemeControl.browseThemes', 'Browse themes') } -function builtInLabel(): string { - return translate( +function builtInLabel(t: Translate = translate): string { + return t( 'auto.components.settings.AppearanceInterfaceSection.pluginIconTheme.builtIn', 'Built-in' ) } -function searchThemesLabel(): string { - return translate('auto.components.settings.AppearanceThemeControl.searchThemes', 'Search themes…') +function searchThemesLabel(t: Translate = translate): string { + return t('auto.components.settings.AppearanceThemeControl.searchThemes', 'Search themes…') } -function noThemesLabel(): string { - return translate( +function noThemesLabel(t: Translate = translate): string { + return t( 'auto.components.settings.AppearanceThemeControl.noThemes', 'No themes match your search.' ) } /** Desktop only: the web client has no plugin catalog to open. */ -function browseThemesActions(): SettingsSearchableSelectAction[] { +function browseThemesActions(t?: Translate): SettingsSearchableSelectAction[] { return isWebClientLocation() ? [] : [ { key: 'browse-themes', - label: browseThemesLabel(), + label: browseThemesLabel(t), icon: , onSelect: () => requestPluginBrowse('themes') } @@ -74,9 +75,10 @@ function browseThemesActions(): SettingsSearchableSelectAction[] { /** `labels` holds every theme the picker lists, so two sharing a name show their publishers. */ function pluginThemeGroups( themes: readonly PluginThemeRegistration[], - labels: ReadonlyMap = themeOptionLabels(themes) + labels: ReadonlyMap = themeOptionLabels(themes), + t?: Translate ): SettingsSearchableSelectGroup[] { - const pluginNames = disambiguatedContentPluginNames(themes) + const pluginNames = disambiguatedContentPluginNames(themes, t) return [...Map.groupBy(themes, (theme) => theme.pluginKey).values()].map((group) => ({ key: group[0].pluginKey, heading: pluginNames.get(group[0].pluginKey) ?? group[0].pluginName, @@ -91,16 +93,21 @@ function pluginThemeGroups( function themeOptionLabels( themes: readonly PluginThemeRegistration[], - catalogThemes: readonly CatalogAppTheme[] = [] + catalogThemes: readonly CatalogAppTheme[] = [], + t?: Translate ): ReadonlyMap { - return disambiguatedPluginContentLabels([ - ...themes, - ...catalogThemes.map((theme) => ({ - id: theme.value, - pluginKey: theme.listing.pluginKey, - label: theme.label - })) - ]) + return disambiguatedPluginContentLabels( + [ + ...themes, + ...catalogThemes.map((theme) => ({ + id: theme.value, + pluginKey: theme.listing.pluginKey, + label: theme.label + })) + ], + undefined, + t + ) } /** System, Dark or Light; the Light theme and Dark theme slots pick the colors for each. */ @@ -115,13 +122,14 @@ export function AppearanceThemeControl({ updateSettings: (updates: Partial) => void applyTheme: (theme: BuiltInTheme) => void }): React.JSX.Element { + const t = useTranslate() return (

{/* Why desktop only: the web client has no plugin catalog to open. */} {isWebClientLocation() ? null : ( )} @@ -163,6 +171,7 @@ export function AppearanceThemeSlotSelect({ settings: GlobalSettings updateSettings: (updates: Partial) => void }): React.JSX.Element { + const t = useTranslate() const allThemes = usePluginThemes() const themes = allThemes.filter((theme) => theme.base === base) const catalog = useCatalogAppThemes(base, allThemes, settings.pluginSystemEnabled === true) @@ -179,14 +188,14 @@ export function AppearanceThemeSlotSelect({ : undefined const terminalInUse = terminal !== undefined && isPluginTerminalThemeSelected(settings, terminal, base === 'dark') - const labels = themeOptionLabels(themes, catalog.themes) + const labels = themeOptionLabels(themes, catalog.themes, t) return (
{ if (open) { catalog.open() @@ -209,32 +218,26 @@ export function AppearanceThemeSlotSelect({ { key: BUILT_IN, options: [ - { value: BUILT_IN, label: builtInLabel() }, + { value: BUILT_IN, label: builtInLabel(t) }, { value: MATCH_TERMINAL_APP_THEME, - label: translate( + label: t( 'auto.components.settings.AppearanceThemeControl.matchTerminal', 'Match Terminal' ), detail: ( <> - {translate( - 'auto.components.settings.AppearanceThemeControl.dynamic', - 'dynamic' - )} + {t('auto.components.settings.AppearanceThemeControl.dynamic', 'dynamic')} ) } ] }, - ...pluginThemeGroups(themes, labels), + ...pluginThemeGroups(themes, labels, t), { key: 'catalog', - heading: translate( - 'auto.components.settings.AppearanceThemeControl.moreThemes', - 'More themes' - ), + heading: t('auto.components.settings.AppearanceThemeControl.moreThemes', 'More themes'), options: catalog.themes.map((theme) => ({ value: theme.value, label: labels.get(theme.value) ?? theme.label, @@ -248,19 +251,19 @@ export function AppearanceThemeSlotSelect({ catalog.loading ? (
- {translate( + {t( 'auto.components.settings.AppearanceThemeControl.moreThemesLoading', 'Loading themes…' )}
) : null } - actions={browseThemesActions()} + actions={browseThemesActions(t)} /> {catalog.installing ? ( - {translate( + {t( 'auto.components.settings.AppearanceThemeControl.installingTheme', 'Installing {{value0}}…', { value0: labels.get(catalog.installing.value) ?? catalog.installing.label } @@ -282,7 +285,7 @@ export function AppearanceThemeSlotSelect({ ) } > - {translate( + {t( 'auto.components.settings.AppearanceThemeControl.useTerminalTheme', 'Use its terminal colors' )} @@ -304,6 +307,7 @@ export function CodeEditorThemeSelect({ settings: GlobalSettings updateSettings: (updates: Partial) => void }): React.JSX.Element { + const t = useTranslate() const themes = usePluginThemes() const selected = findCodeEditorTheme( base === 'dark' ? settings.editorThemeDark : settings.editorThemeLight, @@ -313,8 +317,8 @@ export function CodeEditorThemeSelect({ updateSettings( editorThemeSlotUpdate( @@ -331,25 +335,25 @@ export function CodeEditorThemeSelect({ options: [ { value: FOLLOW_APP, - label: translate('settings.appearance.editorTheme.followApp', 'Same as app theme') + label: t('settings.appearance.editorTheme.followApp', 'Same as app theme') } ] }, { key: BUILT_IN, - heading: builtInLabel(), + heading: builtInLabel(t), options: [ { value: 'light', - label: translate('auto.components.settings.AppearancePane.fd89b5487c', 'Light') + label: t('auto.components.settings.AppearancePane.fd89b5487c', 'Light') }, { value: 'dark', - label: translate('auto.components.settings.AppearancePane.7d26ccabe8', 'Dark') + label: t('auto.components.settings.AppearancePane.7d26ccabe8', 'Dark') } ] }, - ...pluginThemeGroups(themes) + ...pluginThemeGroups(themes, undefined, t) ]} /> ) diff --git a/src/renderer/src/components/settings/appearance/themes/use-catalog-app-themes.ts b/src/renderer/src/components/settings/appearance/themes/use-catalog-app-themes.ts index c30a303c10b..578e7558a48 100644 --- a/src/renderer/src/components/settings/appearance/themes/use-catalog-app-themes.ts +++ b/src/renderer/src/components/settings/appearance/themes/use-catalog-app-themes.ts @@ -7,6 +7,7 @@ import { buildPluginListEntries, needsNewerOrca } from '../../../plugins/list/pl import { applyPluginAppThemeToSlot } from '../../../plugins/state/plugin-theme-apply' import { createPluginPagePreviewCache } from '../../../plugins/state/use-plugin-catalog' import { usePluginInstallAction } from '../../../plugins/state/use-plugin-install-action' +import type { Translate } from '../../../../i18n/use-translate' type CatalogSnapshot = { installed: readonly PluginHostListEntry[] @@ -28,9 +29,10 @@ export type CatalogAppTheme = { export function catalogAppThemes( snapshot: CatalogSnapshot, base: 'light' | 'dark', - installedPluginKeys: ReadonlySet + installedPluginKeys: ReadonlySet, + t?: Translate ): CatalogAppTheme[] { - return buildPluginListEntries(snapshot.installed, snapshot.listings) + return buildPluginListEntries(snapshot.installed, snapshot.listings, undefined, t) .available.flatMap((entry) => { const { listing } = entry const release = listing?.release diff --git a/src/renderer/src/components/settings/appearance/use-appearance-picker-labels.ts b/src/renderer/src/components/settings/appearance/use-appearance-picker-labels.ts index 6df67ea2af9..df77c4e1a40 100644 --- a/src/renderer/src/components/settings/appearance/use-appearance-picker-labels.ts +++ b/src/renderer/src/components/settings/appearance/use-appearance-picker-labels.ts @@ -1,21 +1,24 @@ import { getUiLanguageChoiceLabel, UI_LANGUAGE_CHOICES } from '@/i18n/supported-languages' import { translate } from '@/i18n/i18n' +import { useTranslate } from '@/i18n/use-translate' import { usePluginIconThemes } from '@/store/plugins/appearance/plugin-icon-themes' import { usePluginLanguagePacks } from '@/store/plugins/localization/plugin-language-packs' import { disambiguatedPluginContentLabels } from '../../plugins/presentation/plugin-name-disambiguation' /** Icon themes with their picker labels; a plugin one sharing a name shows its publisher. */ export function useIconThemePickerLabels() { + const t = useTranslate() const themes = usePluginIconThemes() - const builtIn = translate( + const builtIn = t( 'auto.components.settings.AppearanceInterfaceSection.pluginIconTheme.builtIn', 'Built-in' ) - return { themes, builtIn, labels: disambiguatedPluginContentLabels(themes, [builtIn]) } + return { themes, builtIn, labels: disambiguatedPluginContentLabels(themes, [builtIn], t) } } /** Orca's languages and plugin language packs, labelled as the language picker lists them. */ export function useUiLanguagePickerLabels() { + const t = useTranslate() const packs = usePluginLanguagePacks() const choices = UI_LANGUAGE_CHOICES.map((choice) => getUiLanguageChoiceLabel(choice, translate)) const packLabels = disambiguatedPluginContentLabels( @@ -23,7 +26,8 @@ export function useUiLanguagePickerLabels() { ...pack, label: pack.displayName ?? `${pack.locale} — ${pack.pluginKey}` })), - choices + choices, + t ) return { packs, choices, packLabels } } diff --git a/src/renderer/src/components/settings/editor/CollapseUnchangedRegionsSetting.tsx b/src/renderer/src/components/settings/editor/CollapseUnchangedRegionsSetting.tsx index e24a2affc77..0368bfa74cc 100644 --- a/src/renderer/src/components/settings/editor/CollapseUnchangedRegionsSetting.tsx +++ b/src/renderer/src/components/settings/editor/CollapseUnchangedRegionsSetting.tsx @@ -1,5 +1,6 @@ import type { GlobalSettings } from '../../../../../shared/global-settings-types' import { translate } from '@/i18n/i18n' +import { useTranslate, type Translate } from '@/i18n/use-translate' import { Label } from '../../ui/label' import { SearchableSetting } from '../section-layout/SearchableSetting' import { SettingsSegmentedControl } from '../form-controls/SettingsFormControls' @@ -14,15 +15,15 @@ const KEYWORDS = [ 'hunk' ] -function getTitle(): string { - return translate( +function getTitle(t: Translate = translate): string { + return t( 'auto.components.settings.GeneralEditorSettingsSection.collapseUnchangedTitle', 'Collapse Unchanged Regions' ) } -function getDescription(): string { - return translate( +function getDescription(t: Translate = translate): string { + return t( 'auto.components.settings.GeneralEditorSettingsSection.collapseUnchangedDescription', 'Show only changed lines and a little surrounding context in a file diff, hiding the rest behind expandable bands. The View All Changes diff always collapses this way.' ) @@ -35,8 +36,9 @@ export function CollapseUnchangedRegionsSetting({ settings: GlobalSettings updateSettings: (updates: Partial) => void | Promise }): React.JSX.Element { - const title = getTitle() - const description = getDescription() + const t = useTranslate() + const title = getTitle(t) + const description = getDescription(t) return ( diff --git a/src/renderer/src/components/settings/editor/DiffShowWhitespaceSetting.tsx b/src/renderer/src/components/settings/editor/DiffShowWhitespaceSetting.tsx index dd78ef90b07..a0c01f6cb33 100644 --- a/src/renderer/src/components/settings/editor/DiffShowWhitespaceSetting.tsx +++ b/src/renderer/src/components/settings/editor/DiffShowWhitespaceSetting.tsx @@ -1,5 +1,5 @@ import type { GlobalSettings } from '../../../../../shared/global-settings-types' -import { translate } from '@/i18n/i18n' +import { useTranslate } from '@/i18n/use-translate' import { SearchableSetting } from '../section-layout/SearchableSetting' import { Label } from '../../ui/label' import { SettingsSegmentedControl } from '../form-controls/SettingsFormControls' @@ -13,13 +13,14 @@ export function DiffShowWhitespaceSetting({ settings, updateSettings }: DiffShowWhitespaceSettingProps): React.JSX.Element { + const t = useTranslate() return (

- {translate( + {t( 'auto.components.settings.GeneralEditorSettingsSection.94a479cef3', 'Show leading and trailing whitespace differences in diffs.' )}

diff --git a/src/renderer/src/components/settings/editor/EditorFontFamilySetting.tsx b/src/renderer/src/components/settings/editor/EditorFontFamilySetting.tsx index 01e4728eb3a..fdb2212513e 100644 --- a/src/renderer/src/components/settings/editor/EditorFontFamilySetting.tsx +++ b/src/renderer/src/components/settings/editor/EditorFontFamilySetting.tsx @@ -1,6 +1,6 @@ import type React from 'react' import type { GlobalSettings } from '../../../../../shared/global-settings-types' -import { translate } from '@/i18n/i18n' +import { useTranslate } from '@/i18n/use-translate' import { SearchableSetting } from '../section-layout/SearchableSetting' import { FontAutocomplete } from '../appearance/fonts/FontAutocomplete' import { SettingsRow } from '../form-controls/SettingsFormControls' @@ -18,24 +18,19 @@ export function EditorFontFamilySetting({ fontSuggestions, onRequestFontSuggestions }: EditorFontFamilySettingProps): React.JSX.Element { + const t = useTranslate() return ( Projects. Off by default.' ), keywords: [ - translate( - 'settings.editorFormatOnSave.keywords', - 'formatter format on save prettier biome gofmt' - ) + t('settings.editorFormatOnSave.keywords', 'formatter format on save prettier biome gofmt') ] } } @@ -26,7 +24,8 @@ export function EditorFormatOnSaveSetting({ settings: GlobalSettings updateSettings: (updates: Partial) => void }): React.JSX.Element { - const entry = getFormatOnSaveSearchEntry() + const t = useTranslate() + const entry = getFormatOnSaveSearchEntry(t) return ( ) => void }): React.JSX.Element { - const entry = getInsertFinalNewlineSearchEntry() + const t = useTranslate() + const entry = getInsertFinalNewlineSearchEntry(t) return ( updateSettings({ editorFontSize: value })} onClear={() => updateSettings({ editorFontSize: undefined })} /> @@ -80,10 +81,7 @@ export function EditorTypographySettings({ min={0} step={0.1} clearOnZero - placeholder={translate( - 'auto.components.settings.editorTypography.lineHeightAuto', - 'Auto' - )} + placeholder={t('auto.components.settings.editorTypography.lineHeightAuto', 'Auto')} onChange={(value) => updateSettings({ editorLineHeight: value })} onClear={() => updateSettings({ editorLineHeight: undefined })} /> @@ -95,12 +93,12 @@ export function EditorTypographySettings({ description={ ligatures === 'auto' ? fontFamilyHasKnownLigatures(editorFontFamily) - ? translate( + ? t( 'auto.components.settings.editorTypography.ligaturesAutoEnabled', 'Auto - enabled for "{{value0}}".', { value0: editorFontFamily } ) - : translate( + : t( 'auto.components.settings.editorTypography.ligaturesAutoDisabled', 'Auto - disabled for "{{value0}}".', { value0: editorFontFamily } @@ -115,18 +113,15 @@ export function EditorTypographySettings({ options={[ { value: 'off', - label: translate('auto.components.settings.editorTypography.ligaturesOff', 'Off') + label: t('auto.components.settings.editorTypography.ligaturesOff', 'Off') }, { value: 'auto', - label: translate( - 'auto.components.settings.editorTypography.ligaturesAuto', - 'Auto' - ) + label: t('auto.components.settings.editorTypography.ligaturesAuto', 'Auto') }, { value: 'on', - label: translate('auto.components.settings.editorTypography.ligaturesOn', 'On') + label: t('auto.components.settings.editorTypography.ligaturesOn', 'On') } ]} /> @@ -147,14 +142,14 @@ export function EditorTypographySettings({ options={[ { value: 'grayscale', - label: translate( + label: t( 'auto.components.settings.editorTypography.fontSmoothingGrayscale', 'Grayscale' ) }, { value: 'native', - label: translate( + label: t( 'auto.components.settings.editorTypography.fontSmoothingNative', 'Native' ) diff --git a/src/renderer/src/components/settings/editor/EditorWordWrapSetting.tsx b/src/renderer/src/components/settings/editor/EditorWordWrapSetting.tsx index e614bed8edd..0b51c567636 100644 --- a/src/renderer/src/components/settings/editor/EditorWordWrapSetting.tsx +++ b/src/renderer/src/components/settings/editor/EditorWordWrapSetting.tsx @@ -1,5 +1,5 @@ import type { GlobalSettings } from '../../../../../shared/global-settings-types' -import { translate } from '@/i18n/i18n' +import { useTranslate } from '@/i18n/use-translate' import { SearchableSetting } from '../section-layout/SearchableSetting' import { Label } from '../../ui/label' import { SettingsSegmentedControl } from '../form-controls/SettingsFormControls' @@ -13,13 +13,14 @@ export function EditorWordWrapSetting({ settings, updateSettings }: EditorWordWrapSettingProps): React.JSX.Element { + const t = useTranslate() return (

- {translate( + {t( 'auto.components.settings.GeneralEditorSettingsSection.9b18de6eea', 'Wrap long lines in file editors instead of requiring horizontal scrolling.' )}

diff --git a/src/renderer/src/components/settings/editor/GeneralEditorSettingsSection.tsx b/src/renderer/src/components/settings/editor/GeneralEditorSettingsSection.tsx index 164d6097fba..38bfe5ae548 100644 --- a/src/renderer/src/components/settings/editor/GeneralEditorSettingsSection.tsx +++ b/src/renderer/src/components/settings/editor/GeneralEditorSettingsSection.tsx @@ -12,7 +12,7 @@ import { Label } from '../../ui/label' import { SearchableSetting } from '../section-layout/SearchableSetting' import { CollapseUnchangedRegionsSetting } from './CollapseUnchangedRegionsSetting' import { SettingsSegmentedControl, SettingsSwitchRow } from '../form-controls/SettingsFormControls' -import { translate } from '@/i18n/i18n' +import { useTranslate } from '@/i18n/use-translate' import { RichMarkdownSpellcheckSetting } from './RichMarkdownSpellcheckSetting' import { MarkdownDefaultViewSetting } from './MarkdownDefaultViewSetting' import { DiffShowWhitespaceSetting } from './DiffShowWhitespaceSetting' @@ -41,6 +41,7 @@ export function GeneralEditorSettingsSection({ fontSuggestions, onRequestFontSuggestions }: GeneralEditorSettingsSectionProps): React.JSX.Element { + const t = useTranslate() const [autoSaveDelayDraftState, setAutoSaveDelayDraftState] = useState(() => createAutoSaveDelayDraftState(settings.editorAutoSaveDelayMs) ) @@ -88,11 +89,8 @@ export function GeneralEditorSettingsSection({ return (

- {translate( + {t( 'auto.components.settings.GeneralEditorSettingsSection.8112cd6dcf', 'How long Orca waits after your last edit before saving automatically. First launch defaults to' )}{' '} {DEFAULT_EDITOR_AUTO_SAVE_DELAY_MS}{' '} - {translate('auto.components.settings.GeneralEditorSettingsSection.fc5c5306ff', 'ms.')} + {t('auto.components.settings.GeneralEditorSettingsSection.fc5c5306ff', 'ms.')}

- {translate('auto.components.settings.GeneralEditorSettingsSection.a5db1d3975', 'ms')} + {t('auto.components.settings.GeneralEditorSettingsSection.a5db1d3975', 'ms')}

- {translate( + {t( 'auto.components.settings.GeneralEditorSettingsSection.b492397d34', 'Preferred presentation format for showing git diffs by default.' )}

- {translate( + {t( 'auto.components.settings.GeneralEditorSettingsSection.4aa4d9fb73', 'Wrap long lines in diff editors instead of requiring horizontal scrolling.' )}

- {translate( + {t( 'auto.components.settings.GeneralEditorSettingsSection.1b87897af9', 'Show or hide the file tree when opening combined diff views.' )}

s.setMarkdownDefaultViewMode) - const title = translate( + const title = t( 'auto.components.settings.GeneralEditorSettingsSection.markdownDefaultView', 'Default Markdown View' ) - const description = translate( + const description = t( 'auto.components.settings.GeneralEditorSettingsSection.markdownDefaultViewDesc', 'The view Markdown files open in. Right-click the view switcher in a Markdown tab to change it there.' ) @@ -46,11 +47,11 @@ export function MarkdownDefaultViewSetting({ options={[ { value: 'source', - label: translate('auto.components.editor.EditorViewToggle.4d6ccb7ba6', 'Source') + label: t('auto.components.editor.EditorViewToggle.4d6ccb7ba6', 'Source') }, { value: 'rich', - label: translate('auto.components.editor.EditorViewToggle.aff15f94f5', 'Rich Editor') + label: t('auto.components.editor.EditorViewToggle.aff15f94f5', 'Rich Editor') } ]} /> diff --git a/src/renderer/src/components/settings/editor/RichMarkdownSpellcheckSetting.tsx b/src/renderer/src/components/settings/editor/RichMarkdownSpellcheckSetting.tsx index a3718448255..359f8016e4f 100644 --- a/src/renderer/src/components/settings/editor/RichMarkdownSpellcheckSetting.tsx +++ b/src/renderer/src/components/settings/editor/RichMarkdownSpellcheckSetting.tsx @@ -1,6 +1,6 @@ import type React from 'react' import type { GlobalSettings } from '../../../../../shared/global-settings-types' -import { translate } from '@/i18n/i18n' +import { useTranslate } from '@/i18n/use-translate' import { SearchableSetting } from '../section-layout/SearchableSetting' import { SettingsSwitchRow } from '../form-controls/SettingsFormControls' @@ -13,26 +13,27 @@ export function RichMarkdownSpellcheckSetting({ settings, updateSettings }: RichMarkdownSpellcheckSettingProps): React.JSX.Element { + const t = useTranslate() const enabled = settings.richMarkdownSpellcheckEnabled ?? true return (
- {translate( - 'auto.components.settings.MobilePane.ee56f1c7e4', - 'When you leave the mobile app' - )} + {t('auto.components.settings.MobilePane.ee56f1c7e4', 'When you leave the mobile app')}

- {translate( + {t( 'auto.components.settings.MobilePane.5e2c7a1f84', "While you're using a terminal on your phone, Orca shrinks it to fit your phone screen. When you close the app or switch away, this controls whether it stays at phone size (so interactive CLI tools don't reflow) or resizes back to your desktop. You can always use Restore or Restore all on the banner to resize manually." )} diff --git a/src/renderer/src/components/settings/mobile/MobilePane.tsx b/src/renderer/src/components/settings/mobile/MobilePane.tsx index aa685583e5c..e5c1106d2f2 100644 --- a/src/renderer/src/components/settings/mobile/MobilePane.tsx +++ b/src/renderer/src/components/settings/mobile/MobilePane.tsx @@ -18,6 +18,7 @@ import { MobilePairingSetupSection } from './pairing/MobilePairingSetupSection' import { MobileRelayMintFailureNotice } from '../../mobile/mobile-relay-mint-failure-notice' import { WindowsFirewallNotice } from '../../mobile/WindowsFirewallNotice' import { translate } from '@/i18n/i18n' +import { useTranslate } from '@/i18n/use-translate' import { canMintMobilePairingOffer, type MobilePairingConnectionMode @@ -29,6 +30,7 @@ import { usePairingOfferExpiry } from '../../mobile/use-pairing-offer-expiry' import { shouldOpenMobilePairingAddress } from './mobile-pane-search' export function MobilePane(): React.JSX.Element { + const t = useTranslate() const autoRestoreFitMs = useAppStore((s) => s.settings?.mobileAutoRestoreFitMs ?? null) const updateSettings = useAppStore((s) => s.updateSettings) const [qrDataUrl, setQrDataUrl] = useState(null) @@ -389,10 +391,7 @@ export function MobilePane(): React.JSX.Element { {pairingUrl != null && !loading - ? translate( - 'auto.components.settings.MobilePane.pairingCodeReady', - 'Pairing code ready' - ) + ? t('auto.components.settings.MobilePane.pairingCodeReady', 'Pairing code ready') : ''} diff --git a/src/renderer/src/components/settings/mobile/MobileSettingsPane.tsx b/src/renderer/src/components/settings/mobile/MobileSettingsPane.tsx index d3e615c9c74..384a4d2e0de 100644 --- a/src/renderer/src/components/settings/mobile/MobileSettingsPane.tsx +++ b/src/renderer/src/components/settings/mobile/MobileSettingsPane.tsx @@ -6,7 +6,7 @@ import { getMobileOverviewSearchEntry, getMobileSidebarShortcutSearchEntry } from './mobile-settings-search' -import { translate } from '@/i18n/i18n' +import { useTranslate } from '@/i18n/use-translate' import { useAppStore } from '@/store/index' import { MobileRelayBetaNotice } from './pairing/MobileRelayBetaNotice' import { openAppHttpLink } from '@/lib/browser/navigation/app-http-link-open' @@ -15,6 +15,7 @@ const ORCA_IOS_APP_STORE_URL = 'https://apps.apple.com/app/orca-ide/id6766130217 const ORCA_ANDROID_APK_URL = 'https://www.onorca.dev/download/android' export function MobileSettingsPane(): React.JSX.Element { + const t = useTranslate() const showMobileButton = useAppStore((s) => s.settings?.showMobileButton !== false) const updateSettings = useAppStore((s) => s.updateSettings) @@ -22,8 +23,8 @@ export function MobileSettingsPane(): React.JSX.Element {

- {translate( + {t( 'auto.components.settings.MobileSettingsPane.installIntro', 'Install Orca Mobile from the' )}{' '} @@ -41,7 +42,7 @@ export function MobileSettingsPane(): React.JSX.Element { onClick={(event) => openAppHttpLink(ORCA_IOS_APP_STORE_URL, { event })} className="cursor-pointer underline underline-offset-2 hover:text-foreground" > - {translate('auto.components.settings.MobileSettingsPane.b5a2ed83ff', 'App Store')} + {t('auto.components.settings.MobileSettingsPane.b5a2ed83ff', 'App Store')} {' · '} - {translate( - 'auto.components.settings.MobileSettingsPane.installOutro', - ', then pair below.' - )} + {t('auto.components.settings.MobileSettingsPane.installOutro', ', then pair below.')}

{/* Why: the in-page removal toast points users to Settings > Mobile. */} ) => Promise } export function EmulatorOpeningSettings({ settings, devices, enabled, updateSettings }: Props) { + const t = useTranslate() const mode = getEmulatorOpeningMode(settings) const fixed = settings.mobileEmulatorDefaultDeviceUdid ?? '' const selected = devices.find((device) => device.udid === fixed) @@ -21,8 +22,8 @@ export function EmulatorOpeningSettings({ settings, devices, enabled, updateSett <> - {translate('emulator.opening.lastUsed', 'Last used')} - - - {translate('emulator.opening.ask', 'Ask every time')} + {t('emulator.opening.lastUsed', 'Last used')} + {t('emulator.opening.ask', 'Ask every time')} - {translate('emulator.opening.specific', 'Specific device')} + {t('emulator.opening.specific', 'Specific device')} @@ -56,8 +55,8 @@ export function EmulatorOpeningSettings({ settings, devices, enabled, updateSett {mode === 'specific' && ( - + {selected ? [selected.name, simulatorRuntimeLabel(selected.runtime)] .filter(Boolean) @@ -81,7 +80,7 @@ export function EmulatorOpeningSettings({ settings, devices, enabled, updateSett {missing && ( - {fixed} · {translate('emulator.opening.notFound', 'Not found')} + {fixed} · {t('emulator.opening.notFound', 'Not found')} )} {devices.map((device) => ( diff --git a/src/renderer/src/components/settings/mobile/emulator/MobileEmulatorAgentControlRow.tsx b/src/renderer/src/components/settings/mobile/emulator/MobileEmulatorAgentControlRow.tsx index d8acde4b0ed..ed696c8855a 100644 --- a/src/renderer/src/components/settings/mobile/emulator/MobileEmulatorAgentControlRow.tsx +++ b/src/renderer/src/components/settings/mobile/emulator/MobileEmulatorAgentControlRow.tsx @@ -3,7 +3,7 @@ import { ORCA_CLI_SKILL_NAME } from '@/lib/agent-feature-skill-names' import { useMobileEmulatorAgentSetupState } from '../../../emulator-pane/use-mobile-emulator-agent-setup-state' import { AgentSkillSetupPanel } from '../../../skills/setup/AgentSkillSetupPanel' import { MobileEmulatorExamples } from './MobileEmulatorExamples' -import { translate } from '@/i18n/i18n' +import { useTranslate } from '@/i18n/use-translate' import { SettingsGroup } from '../../section-layout/SettingsGroup' const EMULATOR_CLI_COMMANDS = [ @@ -14,16 +14,17 @@ const EMULATOR_CLI_COMMANDS = [ ] as const export function MobileEmulatorAgentControlRow(): React.JSX.Element { + const t = useTranslate() const setup = useMobileEmulatorAgentSetupState(true) const activeSkillRuntime = setup.skillRuntime return (

- {translate( + {t( 'auto.components.settings.MobileEmulatorAgentControlRow.c7f3fe0a6e', 'Common emulator commands' )}

- {translate( + {t( 'auto.components.settings.MobileEmulatorAgentControlRow.8af7a8bc38', 'Commands target the active emulator for the current worktree. Coordinates are normalized from 0..1.' )} diff --git a/src/renderer/src/components/settings/mobile/emulator/MobileEmulatorAvailabilityDetails.tsx b/src/renderer/src/components/settings/mobile/emulator/MobileEmulatorAvailabilityDetails.tsx index 8b629f3ca2c..fdb001de2f8 100644 --- a/src/renderer/src/components/settings/mobile/emulator/MobileEmulatorAvailabilityDetails.tsx +++ b/src/renderer/src/components/settings/mobile/emulator/MobileEmulatorAvailabilityDetails.tsx @@ -2,7 +2,7 @@ import { CheckCircle2, CircleAlert, FolderOpen, X } from 'lucide-react' import type React from 'react' import { toast } from 'sonner' import { Button } from '../../../ui/button' -import { translate } from '@/i18n/i18n' +import { useTranslate } from '@/i18n/use-translate' import { emulatorMessageText } from '@/lib/emulator-error-copy' import { openAppHttpLink } from '@/lib/browser/navigation/app-http-link-open' @@ -65,6 +65,7 @@ export function MobileEmulatorAvailabilityDetails({ configuredPath, onSetAndroidSdkPath }: MobileEmulatorAvailabilityDetailsProps): React.JSX.Element | null { + const t = useTranslate() if (!availability) { return null } @@ -84,7 +85,7 @@ export function MobileEmulatorAvailabilityDetails({ toast.error( error instanceof Error ? error.message - : translate( + : t( 'auto.components.settings.MobileEmulatorSdkStatus.63fe73a1ea', 'Could not update Android SDK folder.' ) @@ -99,7 +100,7 @@ export function MobileEmulatorAvailabilityDetails({ toast.error( error instanceof Error ? error.message - : translate( + : t( 'auto.components.settings.MobileEmulatorSdkStatus.63fe73a1ea', 'Could not update Android SDK folder.' ) @@ -112,19 +113,16 @@ export function MobileEmulatorAvailabilityDetails({

{configuredPath - ? translate( + ? t( 'auto.components.settings.MobileEmulatorSdkStatus.f6d080d128', 'Using configured path' ) - : translate( + : t( 'auto.components.settings.MobileEmulatorSdkStatus.7fe4bd5907', 'Detected at' )}{' '} @@ -132,7 +130,7 @@ export function MobileEmulatorAvailabilityDetails({ ) : ( emulatorMessageText(android) || - translate( + t( 'auto.components.settings.MobileEmulatorSdkStatus.2784f0b22d', 'Not found. Install Android Studio, then create a Virtual Device.' ) @@ -147,7 +145,7 @@ export function MobileEmulatorAvailabilityDetails({ variant="outline" onClick={(event) => openAppHttpLink(ANDROID_STUDIO_URL, { event })} > - {translate( + {t( 'auto.components.settings.MobileEmulatorSdkStatus.b94ff260e6', 'Download Android Studio' )} @@ -161,7 +159,7 @@ export function MobileEmulatorAvailabilityDetails({ className={sdkPathActionClassName} > - {translate( + {t( 'auto.components.settings.MobileEmulatorSdkStatus.18925b082d', 'Locate SDK folder' )} @@ -175,10 +173,7 @@ export function MobileEmulatorAvailabilityDetails({ className={sdkPathActionClassName} > - {translate( - 'auto.components.settings.MobileEmulatorSdkStatus.8c52684db8', - 'Clear' - )} + {t('auto.components.settings.MobileEmulatorSdkStatus.8c52684db8', 'Clear')} ) : null} @@ -188,18 +183,18 @@ export function MobileEmulatorAvailabilityDetails({ {showIos ? ( check?.message) ?? {} ) || - translate( + t( 'auto.components.settings.MobileEmulatorSdkStatus.e4f14b50d7', 'Install Xcode and add an iOS Simulator runtime.' ) diff --git a/src/renderer/src/components/settings/mobile/emulator/MobileEmulatorExamples.tsx b/src/renderer/src/components/settings/mobile/emulator/MobileEmulatorExamples.tsx index 1fba30c6d30..a21245ed6fc 100644 --- a/src/renderer/src/components/settings/mobile/emulator/MobileEmulatorExamples.tsx +++ b/src/renderer/src/components/settings/mobile/emulator/MobileEmulatorExamples.tsx @@ -4,6 +4,7 @@ import { cn } from '@/lib/utils' import { Button } from '../../../ui/button' import { Tooltip, TooltipContent, TooltipProvider, TooltipTrigger } from '../../../ui/tooltip' import { translate } from '@/i18n/i18n' +import { useTranslate } from '@/i18n/use-translate' const EMULATOR_EXAMPLE_PROMPTS = [ 'Using Orca CLI, attach to the active iPhone simulator, sign in with the test account, complete onboarding, and tell me where the flow feels confusing.', @@ -36,6 +37,7 @@ type MobileEmulatorExamplesProps = { export function MobileEmulatorExamples({ variant = 'card' }: MobileEmulatorExamplesProps): React.JSX.Element { + const t = useTranslate() return (

- {translate( + {t( 'auto.components.settings.MobileEmulatorExamples.0820b3f84f', 'Try it — example prompts' )}

- {translate( + {t( 'auto.components.settings.MobileEmulatorExamples.4daa95f25a', 'Paste any of these into Claude Code, Codex, or another agent in a project where the Orca CLI skill is installed.' )} @@ -64,9 +66,9 @@ export function MobileEmulatorExamples({ className="flex items-start gap-2 rounded-lg border border-border bg-background px-3 py-2" >

- {translate('auto.components.settings.MobileEmulatorExamples.b525ff2b12', '"')} + {t('auto.components.settings.MobileEmulatorExamples.b525ff2b12', '"')} {prompt} - {translate('auto.components.settings.MobileEmulatorExamples.d151e25078', '"')} + {t('auto.components.settings.MobileEmulatorExamples.d151e25078', '"')}

@@ -75,7 +77,7 @@ export function MobileEmulatorExamples({ type="button" variant="ghost" size="icon-xs" - aria-label={translate( + aria-label={t( 'auto.components.settings.MobileEmulatorExamples.c12b253997', 'Copy example prompt' )} @@ -85,7 +87,7 @@ export function MobileEmulatorExamples({ - {translate('auto.components.settings.MobileEmulatorExamples.edf13dd03b', 'Copy')} + {t('auto.components.settings.MobileEmulatorExamples.edf13dd03b', 'Copy')} diff --git a/src/renderer/src/components/settings/mobile/emulator/MobileEmulatorSettingsPane.tsx b/src/renderer/src/components/settings/mobile/emulator/MobileEmulatorSettingsPane.tsx index 45f292a2c32..4f6e7150848 100644 --- a/src/renderer/src/components/settings/mobile/emulator/MobileEmulatorSettingsPane.tsx +++ b/src/renderer/src/components/settings/mobile/emulator/MobileEmulatorSettingsPane.tsx @@ -14,6 +14,7 @@ import { SettingsGroup } from '../../section-layout/SettingsGroup' import { SettingsSwitchRow } from '../../form-controls/SettingsFormControls' import { getMobileEmulatorSearchEntries } from './mobile-emulator-search' import { translate } from '@/i18n/i18n' +import { useTranslate, type Translate } from '@/i18n/use-translate' import { emulatorErrorText, emulatorMessageText } from '@/lib/emulator-error-copy' import { useUiLanguageRevision } from '@/i18n/use-ui-language-revision' @@ -41,19 +42,20 @@ type MobileEmulatorSettingsPaneProps = { updateSettings: (updates: Partial) => Promise } -function statusText(availability: EmulatorAvailability | null, enabled: boolean): string { +function statusText( + availability: EmulatorAvailability | null, + enabled: boolean, + t: Translate = translate +): string { if (!enabled) { - return translate('auto.components.settings.MobileEmulatorSettingsPane.a4f1c82d90', 'Disabled') + return t('auto.components.settings.MobileEmulatorSettingsPane.a4f1c82d90', 'Disabled') } if (!availability) { - return translate( - 'auto.components.settings.MobileEmulatorSettingsPane.b5e2d93e01', - 'Checking...' - ) + return t('auto.components.settings.MobileEmulatorSettingsPane.b5e2d93e01', 'Checking...') } return availability.available - ? translate('auto.components.settings.MobileEmulatorSettingsPane.c6f3ea4f12', 'Ready') - : translate('auto.components.settings.MobileEmulatorSettingsPane.d704fb5023', 'Needs setup') + ? t('auto.components.settings.MobileEmulatorSettingsPane.c6f3ea4f12', 'Ready') + : t('auto.components.settings.MobileEmulatorSettingsPane.d704fb5023', 'Needs setup') } function statusBadgeClassName(availability: EmulatorAvailability | null, enabled: boolean): string { @@ -68,20 +70,23 @@ function statusBadgeClassName(availability: EmulatorAvailability | null, enabled : 'border-destructive/30 bg-destructive/10 text-destructive' } -function availabilityDetail(availability: EmulatorAvailability | null): string { +function availabilityDetail( + availability: EmulatorAvailability | null, + t: Translate = translate +): string { if (!availability) { - return translate( + return t( 'auto.components.settings.MobileEmulatorSettingsPane.06b06429c6', 'Checking Android SDK and iOS Simulator support.' ) } if (availability.available) { return availability.devices.length === 1 - ? translate( + ? t( 'auto.components.settings.MobileEmulatorSettingsPane.6d1483d4a0', '1 emulator device detected.' ) - : translate( + : t( 'auto.components.settings.MobileEmulatorSettingsPane.0a452d4d3b', '{{value0}} emulator devices detected.', { value0: availability.devices.length } @@ -95,6 +100,7 @@ export function MobileEmulatorSettingsPane({ settings, updateSettings }: MobileEmulatorSettingsPaneProps): React.JSX.Element { + const t = useTranslate() useUiLanguageRevision() const [availability, setAvailability] = useState(null) const [refreshing, setRefreshing] = useState(false) @@ -139,11 +145,11 @@ export function MobileEmulatorSettingsPane({
-

{availabilityDetail(availability)}

+

+ {availabilityDetail(availability, t)} +

{refreshing ? : null} - {statusText(availability, enabled)} + {statusText(availability, enabled, t)} @@ -174,7 +172,7 @@ export function KeybindingsFileActions(): React.JSX.Element { variant="ghost" size="icon-xs" className="rounded-none border-l border-border" - aria-label={translate( + aria-label={t( 'auto.components.settings.KeybindingsFileActions.400397a10d', 'Open keybindings file menu' )} @@ -185,39 +183,30 @@ export function KeybindingsFileActions(): React.JSX.Element { void openKeybindingsFile()}> - {translate( + {t( 'auto.components.settings.KeybindingsFileActions.98f1a23e1c', 'Open with Default App' )} void openKeybindingsInExternalEditor('vscode')}> - {translate( - 'auto.components.settings.KeybindingsFileActions.1637f64033', - 'Open in VS Code' - )} + {t('auto.components.settings.KeybindingsFileActions.1637f64033', 'Open in VS Code')} void openKeybindingsInExternalEditor('cursor')}> - {translate( - 'auto.components.settings.KeybindingsFileActions.9e24c0e858', - 'Open in Cursor' - )} + {t('auto.components.settings.KeybindingsFileActions.9e24c0e858', 'Open in Cursor')} void revealKeybindingsFile()}> - {translate( + {t( 'auto.components.settings.KeybindingsFileActions.a8a8d6b9d3', 'Reveal in File Manager' )} void reloadKeybindings()}> - {translate( - 'auto.components.settings.KeybindingsFileActions.abc49853fb', - 'Reload from Disk' - )} + {t('auto.components.settings.KeybindingsFileActions.abc49853fb', 'Reload from Disk')} diff --git a/src/renderer/src/components/settings/shortcuts/ShortcutCommandBlock.tsx b/src/renderer/src/components/settings/shortcuts/ShortcutCommandBlock.tsx index 7e09ae88b08..4f1a63ad3e4 100644 --- a/src/renderer/src/components/settings/shortcuts/ShortcutCommandBlock.tsx +++ b/src/renderer/src/components/settings/shortcuts/ShortcutCommandBlock.tsx @@ -16,7 +16,7 @@ import { ShortcutBindingSubRow } from './ShortcutBindingSubRow' import { ShortcutRecorderButton } from './ShortcutRecorderButton' import { ShortcutRemoveButton } from './ShortcutRemoveButton' import type { ShortcutTerminalStatus } from './shortcut-terminal-status' -import { translate } from '@/i18n/i18n' +import { useTranslate } from '@/i18n/use-translate' type ShortcutCommandBlockProps = { item: KeybindingDefinition @@ -63,6 +63,7 @@ export function ShortcutCommandBlock({ onDisableAction, onEnableAction }: ShortcutCommandBlockProps): React.JSX.Element { + const t = useTranslate() const hasBinding = effective.length > 0 const isMulti = effective.length >= 2 // An explicit empty override means the user turned the action off entirely. @@ -73,7 +74,7 @@ export function ShortcutCommandBlock({ const canEnable = isDisabled && previousBindings.length > 0 const doubleTapHint = platform === 'darwin' ? '⇧⇧' : '⇧ ⇧' - const recordingMessage = translate( + const recordingMessage = t( 'auto.components.settings.ShortcutCommandBlock.eb72c52c28', 'Press a shortcut, or double-tap a modifier (e.g. {{value0}}). Esc cancels.', { value0: doubleTapHint } @@ -108,7 +109,7 @@ export function ShortcutCommandBlock({ return ( {modified ? ( - {translate('auto.components.settings.ShortcutCommandBlock.287e07ddde', 'Modified')} + {t('auto.components.settings.ShortcutCommandBlock.287e07ddde', 'Modified')} ) : null} {isDisabled ? ( - {translate('auto.components.settings.ShortcutCommandBlock.3c83cd7d1c', 'Disabled')} + {t('auto.components.settings.ShortcutCommandBlock.3c83cd7d1c', 'Disabled')} ) : null} {terminalStatus && hasBinding ? ( @@ -164,7 +165,7 @@ export function ShortcutCommandBlock({ variant="ghost" size="icon-xs" className="text-muted-foreground hover:text-foreground" - aria-label={translate( + aria-label={t( 'auto.components.settings.ShortcutCommandBlock.a0e2ef0e61', 'Add another shortcut for {{value0}}', { value0: item.title } @@ -175,7 +176,7 @@ export function ShortcutCommandBlock({ - {translate( + {t( 'auto.components.settings.ShortcutCommandBlock.245c83af24', 'Add another shortcut' )} @@ -190,7 +191,7 @@ export function ShortcutCommandBlock({ variant="ghost" size="icon-xs" className="text-muted-foreground hover:text-foreground" - aria-label={translate( + aria-label={t( 'auto.components.settings.ShortcutCommandBlock.07939d084e', 'Reset {{value0}} to default', { value0: item.title } @@ -201,7 +202,7 @@ export function ShortcutCommandBlock({ - {translate( + {t( 'auto.components.settings.ShortcutCommandBlock.9b02917027', 'Reset to default' )} @@ -216,7 +217,7 @@ export function ShortcutCommandBlock({ variant="ghost" size="icon-xs" className="text-muted-foreground hover:text-destructive" - aria-label={translate( + aria-label={t( 'auto.components.settings.ShortcutCommandBlock.a799f90f82', 'Disable {{value0}}', { value0: item.title } @@ -227,7 +228,7 @@ export function ShortcutCommandBlock({ - {translate( + {t( 'auto.components.settings.ShortcutCommandBlock.25e6e76618', 'Disable shortcut' )} @@ -252,14 +253,14 @@ export function ShortcutCommandBlock({ variant="ghost" size="xs" className="text-muted-foreground hover:text-foreground" - aria-label={translate( + aria-label={t( 'auto.components.settings.ShortcutCommandBlock.482a60225d', 'Enable {{value0}}', { value0: item.title } )} onClick={() => onEnableAction(item.id)} > - {translate('auto.components.settings.ShortcutCommandBlock.6287677c37', 'Enable')} + {t('auto.components.settings.ShortcutCommandBlock.6287677c37', 'Enable')} ) : ( @@ -269,7 +270,7 @@ export function ShortcutCommandBlock({ variant="ghost" size="icon-xs" className="text-muted-foreground hover:text-foreground" - aria-label={translate( + aria-label={t( 'auto.components.settings.ShortcutCommandBlock.01481b964c', 'Add shortcut for {{value0}}', { value0: item.title } @@ -280,10 +281,7 @@ export function ShortcutCommandBlock({ - {translate( - 'auto.components.settings.ShortcutCommandBlock.035a822ef0', - 'Add shortcut' - )} + {t('auto.components.settings.ShortcutCommandBlock.035a822ef0', 'Add shortcut')} ) diff --git a/src/renderer/src/components/settings/shortcuts/ShortcutFilterRail.tsx b/src/renderer/src/components/settings/shortcuts/ShortcutFilterRail.tsx index 6370616cbbe..a0e907683c8 100644 --- a/src/renderer/src/components/settings/shortcuts/ShortcutFilterRail.tsx +++ b/src/renderer/src/components/settings/shortcuts/ShortcutFilterRail.tsx @@ -9,6 +9,7 @@ import { Input } from '../../ui/input' import type { ShortcutTerminalStatus } from './shortcut-terminal-status' import { matchesSettingsSearch, type SettingsSearchEntry } from '../search/settings-search' import { translate } from '@/i18n/i18n' +import { useTranslate } from '@/i18n/use-translate' export type ShortcutFilter = 'all' | 'modified' | 'unassigned' | 'conflicts' @@ -124,6 +125,7 @@ export function ShortcutFilterRail({ visibleCount: number totalCount: number }): React.JSX.Element { + const t = useTranslate() const filters = (Object.keys(SHORTCUT_FILTER_LABELS) as ShortcutFilter[]).map((id) => ({ id, label: SHORTCUT_FILTER_LABELS[id](), @@ -135,7 +137,7 @@ export function ShortcutFilterRail({
{visibleCount}/{totalCount} @@ -147,7 +149,7 @@ export function ShortcutFilterRail({ id="shortcut-filter-search" value={query} onChange={(event) => onQueryChange(event.target.value)} - placeholder={translate( + placeholder={t( 'auto.components.settings.ShortcutFilterRail.f733c4b89f', 'Search command or keys' )} @@ -158,7 +160,7 @@ export function ShortcutFilterRail({ type="button" variant="ghost" size="icon-xs" - aria-label={translate( + aria-label={t( 'auto.components.settings.ShortcutFilterRail.df8466f3fc', 'Clear shortcut search' )} @@ -172,14 +174,14 @@ export function ShortcutFilterRail({