mirror of
https://github.com/stablyai/orca.git
synced 2026-11-14 18:07:45 +00:00
refactor(i18n): read render-path translations from useTranslate in pull request page, agent, onboarding, stats, new workspace and feature tips (#39421)
Applies pnpm i18n:reactive-translate --helpers. Output is unchanged: t delegates to translate() and helpers default the threaded parameter. Co-authored-by: OrcaWin <293788423+OrcaWin@users.noreply.github.com>
This commit is contained in:
@@ -64,26 +64,8 @@
|
||||
1 src/renderer/src/components/activity/activity-thread-virtual-row.tsx
|
||||
21 src/renderer/src/components/agent-session-continuation/AgentSessionContinuationDialog.tsx
|
||||
1 src/renderer/src/components/agent-session-continuation/AgentSessionContinuationMenuItem.tsx
|
||||
8 src/renderer/src/components/agent/AgentCombobox.tsx
|
||||
3 src/renderer/src/components/agent/AgentLaunchPicker.tsx
|
||||
2 src/renderer/src/components/agent/AgentLaunchPickerMenu.tsx
|
||||
2 src/renderer/src/components/agent/AgentLaunchPromptComposer.tsx
|
||||
1 src/renderer/src/components/agent/AgentLaunchSetupActions.tsx
|
||||
3 src/renderer/src/components/agent/AgentLaunchSetupButton.tsx
|
||||
2 src/renderer/src/components/agent/AgentLaunchSetupFooter.tsx
|
||||
9 src/renderer/src/components/agent/AgentLaunchSetupPanel.tsx
|
||||
1 src/renderer/src/components/agent/AgentLaunchTextModelDefault.tsx
|
||||
2 src/renderer/src/components/agent/AgentSettingsDialog.tsx
|
||||
1 src/renderer/src/components/agent/AgentSettingsMenuItem.tsx
|
||||
2 src/renderer/src/components/agent/agent-combobox-items.tsx
|
||||
1 src/renderer/src/components/agent/agent-combobox-surface-items.tsx
|
||||
6 src/renderer/src/components/agent/launch-settings/AgentChatChoiceControls.tsx
|
||||
5 src/renderer/src/components/agent/launch-settings/AgentChoiceControls.tsx
|
||||
1 src/renderer/src/components/agent/launch-settings/AgentDefaultSurfacePreviews.tsx
|
||||
1 src/renderer/src/components/agent/launch-settings/AgentLaunchChoiceEditor.tsx
|
||||
11 src/renderer/src/components/agent/launch-settings/AgentLaunchDefaultsEditor.tsx
|
||||
19 src/renderer/src/components/agent/launch-settings/AgentPermissionChoiceControls.tsx
|
||||
4 src/renderer/src/components/agent/launch-settings/codex-session-source-home-control.tsx
|
||||
6 src/renderer/src/components/artifacts/ArtifactActions.tsx
|
||||
2 src/renderer/src/components/artifacts/ArtifactCollection.tsx
|
||||
1 src/renderer/src/components/artifacts/ArtifactDetailDrawer.tsx
|
||||
@@ -138,23 +120,6 @@
|
||||
10 src/renderer/src/components/emulator-pane/emulator-device-picker.tsx
|
||||
9 src/renderer/src/components/emulator-pane/emulator-pane-toolbar.tsx
|
||||
5 src/renderer/src/components/emulator-pane/emulator-screen-stream-content.tsx
|
||||
3 src/renderer/src/components/feature-tips/ChatUiHereCornerCard.tsx
|
||||
2 src/renderer/src/components/feature-tips/ChatUiHereFeatureTipVisual.tsx
|
||||
3 src/renderer/src/components/feature-tips/ChatUiHereTipDialog.tsx
|
||||
5 src/renderer/src/components/feature-tips/ChatUiSwitchedCornerNote.tsx
|
||||
2 src/renderer/src/components/feature-tips/CliFeatureTipVisual.tsx
|
||||
6 src/renderer/src/components/feature-tips/CliSetupTipDialog.tsx
|
||||
2 src/renderer/src/components/feature-tips/CliSkillSetupPanel.tsx
|
||||
7 src/renderer/src/components/feature-tips/CliSkillSetupTerminal.tsx
|
||||
1 src/renderer/src/components/feature-tips/CmdJPaletteFeatureTipVisual.tsx
|
||||
1 src/renderer/src/components/feature-tips/CmdJPaletteTipDialog.tsx
|
||||
1 src/renderer/src/components/feature-tips/FeatureTipActions.tsx
|
||||
5 src/renderer/src/components/feature-tips/FeatureTipsModal.tsx
|
||||
2 src/renderer/src/components/feature-tips/SessionSearchTipDialog.tsx
|
||||
4 src/renderer/src/components/feature-tips/VoiceDictationFeatureTipVisual.tsx
|
||||
3 src/renderer/src/components/feature-tips/VoiceDictationTipDialog.tsx
|
||||
1 src/renderer/src/components/feature-tips/feature-tip-session-panel-demo.tsx
|
||||
1 src/renderer/src/components/feature-tips/use-session-search-tip-setup.ts
|
||||
2 src/renderer/src/components/file-actions/OpenInContextSubmenu.tsx
|
||||
5 src/renderer/src/components/floating-terminal/FloatingTerminalEmptyState.tsx
|
||||
3 src/renderer/src/components/floating-terminal/FloatingTerminalIconContextMenu.tsx
|
||||
@@ -244,44 +209,7 @@
|
||||
18 src/renderer/src/components/mobile/slides/HomeSlide.tsx
|
||||
35 src/renderer/src/components/mobile/slides/TerminalSlide.tsx
|
||||
7 src/renderer/src/components/mobile/slides/WorktreeListSlide.tsx
|
||||
1 src/renderer/src/components/new-workspace/ComposerBaseRefPicker.tsx
|
||||
9 src/renderer/src/components/new-workspace/ComposerBranchChoice.tsx
|
||||
6 src/renderer/src/components/new-workspace/ComposerParentWorktreePicker.tsx
|
||||
4 src/renderer/src/components/new-workspace/DirectWorkItemSetupDialog.tsx
|
||||
8 src/renderer/src/components/new-workspace/NewWorkspaceComposerAdvancedSection.tsx
|
||||
6 src/renderer/src/components/new-workspace/NewWorkspaceComposerAgentSection.tsx
|
||||
1 src/renderer/src/components/new-workspace/NewWorkspaceComposerFooter.tsx
|
||||
4 src/renderer/src/components/new-workspace/NewWorkspaceComposerNameSection.tsx
|
||||
8 src/renderer/src/components/new-workspace/NewWorkspaceComposerProjectSection.tsx
|
||||
6 src/renderer/src/components/new-workspace/ProjectCombobox.tsx
|
||||
2 src/renderer/src/components/new-workspace/RunTargetCombobox.tsx
|
||||
3 src/renderer/src/components/new-workspace/RunTargetField.tsx
|
||||
5 src/renderer/src/components/new-workspace/RunTargetRowActions.tsx
|
||||
6 src/renderer/src/components/new-workspace/RunTargetSubmenus.tsx
|
||||
6 src/renderer/src/components/new-workspace/SetProjectLocationDialog.tsx
|
||||
13 src/renderer/src/components/new-workspace/SetProjectLocationForms.tsx
|
||||
4 src/renderer/src/components/new-workspace/SmartWorkspaceSourceResultList.tsx
|
||||
2 src/renderer/src/components/new-workspace/smart-workspace-source-row-content.tsx
|
||||
9 src/renderer/src/components/notifications/mac-notification-permission-card.tsx
|
||||
11 src/renderer/src/components/onboarding/AgentStep.tsx
|
||||
12 src/renderer/src/components/onboarding/AgentStepSurfaceSections.tsx
|
||||
10 src/renderer/src/components/onboarding/AgentSurfaceStep.tsx
|
||||
3 src/renderer/src/components/onboarding/FeatureSetupInlineTerminal.tsx
|
||||
7 src/renderer/src/components/onboarding/GhosttyDiscoveryRow.tsx
|
||||
25 src/renderer/src/components/onboarding/IntegrationsStep.tsx
|
||||
9 src/renderer/src/components/onboarding/NotificationStep.tsx
|
||||
7 src/renderer/src/components/onboarding/OnboardingFlow.tsx
|
||||
2 src/renderer/src/components/onboarding/OnboardingFooter.tsx
|
||||
1 src/renderer/src/components/onboarding/OnboardingInlineCommandTerminal.tsx
|
||||
4 src/renderer/src/components/onboarding/OnboardingLanguageLoading.tsx
|
||||
18 src/renderer/src/components/onboarding/OnboardingLanguageSwitcher.tsx
|
||||
4 src/renderer/src/components/onboarding/TerminalLaunchDemo.tsx
|
||||
4 src/renderer/src/components/onboarding/ThemeLookCarousel.tsx
|
||||
8 src/renderer/src/components/onboarding/ThemeLookGallery.tsx
|
||||
6 src/renderer/src/components/onboarding/ThemeModeTiles.tsx
|
||||
13 src/renderer/src/components/onboarding/ThemeStep.tsx
|
||||
27 src/renderer/src/components/onboarding/WindowsTerminalStep.tsx
|
||||
8 src/renderer/src/components/onboarding/YoloPermissionsControl.tsx
|
||||
4 src/renderer/src/components/orca-profiles/OrcaProfileSignOutConfirmDialog.tsx
|
||||
6 src/renderer/src/components/orchestration-crew/CrewRowDetails.tsx
|
||||
5 src/renderer/src/components/orchestration-crew/CrewRowMenu.tsx
|
||||
@@ -298,27 +226,7 @@
|
||||
2 src/renderer/src/components/provider-accounts/AntigravityProviderSignInArea.tsx
|
||||
2 src/renderer/src/components/provider-accounts/ProviderHostScopeControl.tsx
|
||||
1 src/renderer/src/components/provider-accounts/UnsealedCredentialNotice.tsx
|
||||
5 src/renderer/src/components/pull-request-page/actions/panel.tsx
|
||||
14 src/renderer/src/components/pull-request-page/checks/details.tsx
|
||||
3 src/renderer/src/components/pull-request-page/checks/fix-dialog.tsx
|
||||
1 src/renderer/src/components/pull-request-page/checks/toolbar.tsx
|
||||
3 src/renderer/src/components/pull-request-page/comments/composer.tsx
|
||||
3 src/renderer/src/components/pull-request-page/comments/reply-form.tsx
|
||||
8 src/renderer/src/components/pull-request-page/conversation/comment-card.tsx
|
||||
2 src/renderer/src/components/pull-request-page/conversation/comment-group.tsx
|
||||
2 src/renderer/src/components/pull-request-page/conversation/comments-list.tsx
|
||||
7 src/renderer/src/components/pull-request-page/conversation/description.tsx
|
||||
1 src/renderer/src/components/pull-request-page/conversation/tab.tsx
|
||||
6 src/renderer/src/components/pull-request-page/edit/section.tsx
|
||||
3 src/renderer/src/components/pull-request-page/files/combined-diff-viewer.tsx
|
||||
7 src/renderer/src/components/pull-request-page/files/toolbar.tsx
|
||||
16 src/renderer/src/components/pull-request-page/page/header.tsx
|
||||
1 src/renderer/src/components/pull-request-page/page/surface.tsx
|
||||
6 src/renderer/src/components/pull-request-page/page/tabs-shell.tsx
|
||||
1 src/renderer/src/components/pull-request-page/reviewers/panel.tsx
|
||||
3 src/renderer/src/components/pull-request-page/reviewers/picker-row.tsx
|
||||
9 src/renderer/src/components/pull-request-page/reviewers/picker.tsx
|
||||
4 src/renderer/src/components/pull-request-page/reviewers/requested-list.tsx
|
||||
5 src/renderer/src/components/quick-open-install-rg-guidance.tsx
|
||||
9 src/renderer/src/components/repo/CreateFromPicker.tsx
|
||||
5 src/renderer/src/components/repo/NestedRepoChecklist.tsx
|
||||
@@ -342,24 +250,7 @@
|
||||
15 src/renderer/src/components/ssh-authentication/SshPassphraseDialog.tsx
|
||||
10 src/renderer/src/components/star-nag/StarNagToastHost.tsx
|
||||
12 src/renderer/src/components/stats/ClaudeUsageDailyChart.tsx
|
||||
15 src/renderer/src/components/stats/ClaudeUsageDetails.tsx
|
||||
1 src/renderer/src/components/stats/ClaudeUsageLoadingState.tsx
|
||||
22 src/renderer/src/components/stats/ClaudeUsagePane.tsx
|
||||
12 src/renderer/src/components/stats/CodexUsageDailyChart.tsx
|
||||
14 src/renderer/src/components/stats/CodexUsageDetails.tsx
|
||||
20 src/renderer/src/components/stats/CodexUsagePane.tsx
|
||||
9 src/renderer/src/components/stats/GrokUsagePane.tsx
|
||||
14 src/renderer/src/components/stats/MuseUsageDetails.tsx
|
||||
20 src/renderer/src/components/stats/MuseUsagePane.tsx
|
||||
14 src/renderer/src/components/stats/OpenCodeUsageDetails.tsx
|
||||
20 src/renderer/src/components/stats/OpenCodeUsagePane.tsx
|
||||
6 src/renderer/src/components/stats/ShareUsageButton.tsx
|
||||
9 src/renderer/src/components/stats/ShareUsageCard.tsx
|
||||
8 src/renderer/src/components/stats/StatsPane.tsx
|
||||
2 src/renderer/src/components/stats/UsageBreakdownSection.tsx
|
||||
20 src/renderer/src/components/stats/UsageOverviewPane.tsx
|
||||
3 src/renderer/src/components/stats/share-card-utils.tsx
|
||||
23 src/renderer/src/components/stats/usage-overview-sections.tsx
|
||||
1 src/renderer/src/components/tab-group/TabGroupDropOverlay.tsx
|
||||
3 src/renderer/src/components/tab-group/TabGroupPaneActionsMenu.tsx
|
||||
1 src/renderer/src/components/tab-group/TabGroupPanel.tsx
|
||||
|
||||
@@ -18,7 +18,7 @@ import {
|
||||
AgentIconLabel,
|
||||
renderAgentComboboxItem
|
||||
} from './agent-combobox-items'
|
||||
import { translate } from '@/i18n/i18n'
|
||||
import { useTranslate } from '@/i18n/use-translate'
|
||||
import { rowLabel } from '@/components/tab-bar/tab-agent-row-presentation'
|
||||
import {
|
||||
SurfaceAgentItems,
|
||||
@@ -76,8 +76,9 @@ export default function AgentCombobox({
|
||||
surfaceRows,
|
||||
...selection
|
||||
}: AgentComboboxProps): React.JSX.Element {
|
||||
const t = useTranslate()
|
||||
const blankText =
|
||||
blankLabel ?? translate('auto.components.agent.AgentCombobox.986f946354', 'Blank Terminal')
|
||||
blankLabel ?? t('auto.components.agent.AgentCombobox.986f946354', 'Blank Terminal')
|
||||
const allowBlankTerminal = selection.allowBlankTerminal === true
|
||||
const [open, setOpen] = useState(false)
|
||||
const [query, setQuery] = useState('')
|
||||
@@ -102,7 +103,7 @@ export default function AgentCombobox({
|
||||
[allowBlankTerminal, query]
|
||||
)
|
||||
const pickerValues = { agents, query, value, blankValue: BLANK_VALUE, blankMatchesQuery }
|
||||
const surface = surfaceRows ? surfaceCommandValues(surfaceRows.model, pickerValues) : null
|
||||
const surface = surfaceRows ? surfaceCommandValues(surfaceRows.model, pickerValues, t) : null
|
||||
const surfaceList = surface?.rows ?? []
|
||||
const openCommandValue = selectionCleared
|
||||
? ''
|
||||
@@ -279,17 +280,14 @@ export default function AgentCombobox({
|
||||
<span className={cn('inline-flex min-w-0 flex-1', canClear && 'pr-8')}>
|
||||
{selectionCleared ? (
|
||||
<span className="truncate text-muted-foreground">
|
||||
{translate(
|
||||
'components.agentSessionContinuation.selectAgent',
|
||||
'Select an Agent'
|
||||
)}
|
||||
{t('components.agentSessionContinuation.selectAgent', 'Select an Agent')}
|
||||
</span>
|
||||
) : selectedAgent ? (
|
||||
<AgentIconLabel
|
||||
icon={<AgentIcon agent={selectedAgent.id} size={14} />}
|
||||
label={
|
||||
picksCli
|
||||
? rowLabel({ kind: 'cli', agent: selectedAgent.id })
|
||||
? rowLabel({ kind: 'cli', agent: selectedAgent.id }, t)
|
||||
: selectedAgent.label
|
||||
}
|
||||
/>
|
||||
@@ -299,10 +297,7 @@ export default function AgentCombobox({
|
||||
label={
|
||||
allowBlankTerminal
|
||||
? blankText
|
||||
: translate(
|
||||
'components.agentSessionContinuation.selectAgent',
|
||||
'Select an Agent'
|
||||
)
|
||||
: t('components.agentSessionContinuation.selectAgent', 'Select an Agent')
|
||||
}
|
||||
/>
|
||||
)}
|
||||
@@ -331,16 +326,13 @@ export default function AgentCombobox({
|
||||
>
|
||||
<CommandInput
|
||||
ref={setInputNode}
|
||||
placeholder={translate(
|
||||
'auto.components.agent.AgentCombobox.48c6a5a9b4',
|
||||
'Search agents...'
|
||||
)}
|
||||
placeholder={t('auto.components.agent.AgentCombobox.48c6a5a9b4', 'Search agents...')}
|
||||
value={query}
|
||||
onValueChange={setQuery}
|
||||
/>
|
||||
<CommandList>
|
||||
<CommandEmpty>
|
||||
{translate(
|
||||
{t(
|
||||
'auto.components.agent.AgentCombobox.579c768bde',
|
||||
'No agents match your search.'
|
||||
)}
|
||||
@@ -390,7 +382,7 @@ export default function AgentCombobox({
|
||||
onMouseEnter={() => setCommandValue('')}
|
||||
className="h-9 w-full justify-start rounded-none px-3 text-xs font-normal text-muted-foreground"
|
||||
>
|
||||
{translate('auto.components.agent.AgentCombobox.19522e25ee', 'Manage agents')}
|
||||
{t('auto.components.agent.AgentCombobox.19522e25ee', 'Manage agents')}
|
||||
<ArrowRight className="ml-auto size-3" />
|
||||
</Button>
|
||||
</div>
|
||||
@@ -407,10 +399,7 @@ export default function AgentCombobox({
|
||||
size="icon-xs"
|
||||
disabled={disabled}
|
||||
className="absolute right-8 top-1/2 -translate-y-1/2"
|
||||
aria-label={translate(
|
||||
'components.agentCombobox.clearSelection',
|
||||
'Clear agent selection'
|
||||
)}
|
||||
aria-label={t('components.agentCombobox.clearSelection', 'Clear agent selection')}
|
||||
onClick={() => {
|
||||
clearSelection.onClear()
|
||||
closePicker()
|
||||
@@ -421,7 +410,7 @@ export default function AgentCombobox({
|
||||
</Button>
|
||||
</TooltipTrigger>
|
||||
<TooltipContent side="top" sideOffset={4}>
|
||||
{translate('components.agentCombobox.clearSelection', 'Clear agent selection')}
|
||||
{t('components.agentCombobox.clearSelection', 'Clear agent selection')}
|
||||
</TooltipContent>
|
||||
</Tooltip>
|
||||
)}
|
||||
|
||||
@@ -22,7 +22,7 @@ import {
|
||||
DropdownMenuTrigger
|
||||
} from '@/components/ui/dropdown-menu'
|
||||
import { AgentIcon, getAgentLabel } from '@/lib/agent-catalog'
|
||||
import { translate } from '@/i18n/i18n'
|
||||
import { useTranslate } from '@/i18n/use-translate'
|
||||
import { rowLabel } from '../tab-bar/tab-agent-row-presentation'
|
||||
import type { NewTabAgentMenuModel } from '../tab-bar/use-new-tab-agent-menu-rows'
|
||||
import { useAgentPickerDraftScope } from './AgentLaunchSetupButton'
|
||||
@@ -92,6 +92,7 @@ export const AgentLaunchPicker = memo(
|
||||
surface?: AgentPickerDraft['surface']
|
||||
) => void
|
||||
}) {
|
||||
const t = useTranslate()
|
||||
useUiLanguageRevision()
|
||||
const ownDraftScope = useAgentPickerDraftScope()
|
||||
const [settingsOpen, setSettingsOpen] = useState(false)
|
||||
@@ -175,9 +176,9 @@ export const AgentLaunchPicker = memo(
|
||||
const summary = [modelLabel, choices?.options?.effort].filter(Boolean).join(' · ')
|
||||
const agentLabel = agent
|
||||
? cli
|
||||
? rowLabel({ kind: 'cli', agent })
|
||||
? rowLabel({ kind: 'cli', agent }, t)
|
||||
: getAgentLabel(agent)
|
||||
: (emptyLabel ?? translate('agentPicker.select', 'Select agent'))
|
||||
: (emptyLabel ?? t('agentPicker.select', 'Select agent'))
|
||||
return (
|
||||
<div className="space-y-2">
|
||||
<DropdownMenu open={open} onOpenChange={setOpen}>
|
||||
@@ -186,7 +187,7 @@ export const AgentLaunchPicker = memo(
|
||||
ref={trigger}
|
||||
type="button"
|
||||
data-agent-launch-picker-trigger="true"
|
||||
aria-label={translate('agentPicker.select', 'Select agent')}
|
||||
aria-label={t('agentPicker.select', 'Select agent')}
|
||||
variant="outline"
|
||||
className="w-full min-w-0"
|
||||
disabled={disabled || model.state !== 'settled'}
|
||||
@@ -260,7 +261,7 @@ export const AgentLaunchPicker = memo(
|
||||
/>
|
||||
) : (
|
||||
<DropdownMenuItem disabled>
|
||||
{translate('agentPicker.select', 'Select agent')}
|
||||
{t('agentPicker.select', 'Select agent')}
|
||||
</DropdownMenuItem>
|
||||
)}
|
||||
<AgentSettingsMenuItem onSelect={openAgentSettings} />
|
||||
|
||||
@@ -8,7 +8,7 @@ import { parseExecutionHostId } from '../../../../shared/execution-host'
|
||||
import { Input } from '@/components/ui/input'
|
||||
import { AgentPickerMenuRow, type AgentPickerMenuLaunch } from './AgentLaunchPickerMenuRow'
|
||||
import { DropdownMenuItem } from '@/components/ui/dropdown-menu'
|
||||
import { translate } from '@/i18n/i18n'
|
||||
import { useTranslate } from '@/i18n/use-translate'
|
||||
import { retireAgentPickerDrafts, type AgentPickerSetupIdentity } from './AgentLaunchSetupButton'
|
||||
import type { NewTabAgentMenuModel } from '../tab-bar/use-new-tab-agent-menu-rows'
|
||||
import {
|
||||
@@ -58,6 +58,7 @@ export function AgentLaunchPickerMenu({
|
||||
defaultAgent?: AgentPickerEntry['agent'] | 'blank' | null
|
||||
onSetDefault?: (agent: AgentPickerEntry['agent']) => void
|
||||
}) {
|
||||
const t = useTranslate()
|
||||
useEffect(() => {
|
||||
if (setupDisabled) {
|
||||
return
|
||||
@@ -154,7 +155,7 @@ export function AgentLaunchPickerMenu({
|
||||
const end = event.currentTarget.value.length
|
||||
event.currentTarget.setSelectionRange(end, end)
|
||||
}}
|
||||
placeholder={translate('agentPicker.search', 'Search agents and setups…')}
|
||||
placeholder={t('agentPicker.search', 'Search agents and setups…')}
|
||||
onChange={(event) => setQuery(event.target.value)}
|
||||
onKeyDown={(event) => {
|
||||
if (event.key === 'Escape') {
|
||||
@@ -228,10 +229,7 @@ export function AgentLaunchPickerMenu({
|
||||
})}
|
||||
{!visible.length && !setups.length ? (
|
||||
<DropdownMenuItem disabled>
|
||||
{translate(
|
||||
'auto.components.agent.AgentCombobox.579c768bde',
|
||||
'No agents match your search.'
|
||||
)}
|
||||
{t('auto.components.agent.AgentCombobox.579c768bde', 'No agents match your search.')}
|
||||
</DropdownMenuItem>
|
||||
) : null}
|
||||
</div>
|
||||
|
||||
@@ -19,6 +19,7 @@ import {
|
||||
type AgentPickerEntry,
|
||||
type AgentPickerSurface
|
||||
} from '../../../../shared/agent-launch/picker/agent-launch-picker'
|
||||
import { useTranslate } from '@/i18n/use-translate'
|
||||
|
||||
function pickerLaunchRow(entry: AgentPickerEntry, surface: AgentPickerSurface): NewTabAgentMenuRow {
|
||||
return {
|
||||
@@ -89,6 +90,7 @@ export const AgentPickerMenuRow = memo(function AgentPickerMenuRow({
|
||||
/** Base agent rows only; setup rows are presets, not default-agent choices. */
|
||||
onSetDefault?: (agent: AgentPickerEntry['agent']) => void
|
||||
}) {
|
||||
const t = useTranslate()
|
||||
useUiLanguageRevision()
|
||||
const setupId = useId()
|
||||
const preset = initialModel ? { model: initialModel, effort: initialEffort } : undefined
|
||||
@@ -115,7 +117,7 @@ export const AgentPickerMenuRow = memo(function AgentPickerMenuRow({
|
||||
<DropdownMenuItem
|
||||
disabled={disabled || launchBlocked || !entry.surfaces.length}
|
||||
data-search-default={searchDefault || undefined}
|
||||
aria-description={launchBlocked ? terminalPendingLabel() : undefined}
|
||||
aria-description={launchBlocked ? terminalPendingLabel(t) : undefined}
|
||||
onSelect={(event) => {
|
||||
onSelectAgent?.(entry.agent)
|
||||
if (
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
import { Button } from '@/components/ui/button'
|
||||
import { translate } from '@/i18n/i18n'
|
||||
import { useTranslate } from '@/i18n/use-translate'
|
||||
import type {
|
||||
AgentPickerEntry,
|
||||
AgentPickerSurface
|
||||
@@ -18,11 +18,12 @@ export function AgentLaunchSetupActions({
|
||||
surface: AgentPickerSurface
|
||||
onUse: (surface: AgentPickerSurface) => void
|
||||
}): React.JSX.Element {
|
||||
const t = useTranslate()
|
||||
return (
|
||||
<div className="flex items-center justify-end gap-2 border-t border-border px-2.5 py-2">
|
||||
{entry.textGeneration ? (
|
||||
<Button type="button" size="xs" disabled={disabled} onClick={() => onUse(surface)}>
|
||||
{translate('agentPicker.use', 'Use')}
|
||||
{t('agentPicker.use', 'Use')}
|
||||
</Button>
|
||||
) : (
|
||||
<AgentLaunchSurfaceButtons
|
||||
|
||||
@@ -5,7 +5,7 @@ import { Button } from '@/components/ui/button'
|
||||
import { Separator } from '@/components/ui/separator'
|
||||
import { AgentLaunchSetupSubmenu } from './AgentLaunchSetupSubmenu'
|
||||
import { Popover, PopoverAnchor, PopoverContent, PopoverTrigger } from '@/components/ui/popover'
|
||||
import { translate } from '@/i18n/i18n'
|
||||
import { useTranslate } from '@/i18n/use-translate'
|
||||
import { AgentLaunchSetupPanel } from './AgentLaunchSetupPanel'
|
||||
import type {
|
||||
AgentPickerDraft,
|
||||
@@ -86,6 +86,7 @@ export function AgentLaunchSetupButton({
|
||||
onOpenChange?: (open: boolean) => void
|
||||
onUse: (draft: AgentPickerDraft) => boolean | void | Promise<boolean>
|
||||
}) {
|
||||
const t = useTranslate()
|
||||
const [localOpen, setOpen] = useState(false)
|
||||
const open = controlledOpen ?? localOpen
|
||||
const setup = useAgentPickerSetup({
|
||||
@@ -185,7 +186,7 @@ export function AgentLaunchSetupButton({
|
||||
) : null}
|
||||
</div>
|
||||
)
|
||||
const label = translate('agentPicker.configure', 'Configure {{agent}}', { agent: entry.label })
|
||||
const label = t('agentPicker.configure', 'Configure {{agent}}', { agent: entry.label })
|
||||
if (submenu && renderAnchor) {
|
||||
return (
|
||||
<AgentLaunchSetupSubmenu
|
||||
@@ -209,7 +210,7 @@ export function AgentLaunchSetupButton({
|
||||
variant="ghost"
|
||||
size="icon-xs"
|
||||
disabled={disabled}
|
||||
aria-label={translate('agentPicker.configure', 'Configure {{agent}}', {
|
||||
aria-label={t('agentPicker.configure', 'Configure {{agent}}', {
|
||||
agent: entry.label
|
||||
})}
|
||||
onClick={(event) => event.stopPropagation()}
|
||||
@@ -235,7 +236,7 @@ export function AgentLaunchSetupButton({
|
||||
sticky="always"
|
||||
align="start"
|
||||
sideOffset={6}
|
||||
aria-label={translate('agentPicker.configure', 'Configure {{agent}}', {
|
||||
aria-label={t('agentPicker.configure', 'Configure {{agent}}', {
|
||||
agent: entry.label
|
||||
})}
|
||||
// Why: base popovers sit below menus (z-60), so this menu-launched panel would render under the menu's shadow; sharing the menu layer stacks it by open order, like a submenu.
|
||||
|
||||
@@ -4,6 +4,7 @@ import { CornerDownLeft } from 'lucide-react'
|
||||
import { Button } from '@/components/ui/button'
|
||||
import { ShortcutKeyCombo } from '@/components/ShortcutKeyCombo'
|
||||
import { translate } from '@/i18n/i18n'
|
||||
import { useTranslate, type Translate } from '@/i18n/use-translate'
|
||||
import { getShortcutPlatform } from '@/lib/shortcut-platform'
|
||||
import type { NativeChatComposerHandle } from '../native-chat/composer/native-chat-composer-types'
|
||||
import { AgentLaunchPromptComposer } from './AgentLaunchPromptComposer'
|
||||
@@ -18,20 +19,21 @@ import { formatKeybinding } from '../../../../shared/keybindings'
|
||||
export function agentLaunchSetupArgumentsNote(
|
||||
label: string,
|
||||
options: ReturnType<typeof agentPickerEntryOptions>,
|
||||
noModelFlag: boolean
|
||||
noModelFlag: boolean,
|
||||
t: Translate = translate
|
||||
) {
|
||||
const composed = options.some((option) => option.apply.composedIntoModel)
|
||||
const noOptionFlag = options.some(
|
||||
(option) => !option.apply.cli?.length && !option.apply.composedIntoModel
|
||||
)
|
||||
return noModelFlag || noOptionFlag
|
||||
? translate(
|
||||
? t(
|
||||
'agentPicker.chatChoicesNote',
|
||||
'{{agent}} model or effort choices without a CLI flag apply to chats.',
|
||||
{ agent: label }
|
||||
)
|
||||
: composed
|
||||
? translate(
|
||||
? t(
|
||||
'agentPicker.composedChoicesNote',
|
||||
'{{agent}} effort and speed choices are included in the model argument.',
|
||||
{ agent: label }
|
||||
@@ -55,6 +57,7 @@ export function AgentLaunchSurfaceButtons({
|
||||
primary: AgentPickerSurface
|
||||
onSelect: (surface: AgentPickerSurface) => void
|
||||
}): React.JSX.Element {
|
||||
const t = useTranslate()
|
||||
const platform = getShortcutPlatform()
|
||||
return (
|
||||
<div className="grid max-w-full auto-cols-fr grid-flow-col gap-2">
|
||||
@@ -62,8 +65,8 @@ export function AgentLaunchSurfaceButtons({
|
||||
const isPrimary = next === primary
|
||||
const label =
|
||||
next === 'chat'
|
||||
? translate('agentPicker.launchChat', 'Launch chat')
|
||||
: translate('agentPicker.launchTerminal', 'Launch terminal')
|
||||
? t('agentPicker.launchChat', 'Launch chat')
|
||||
: t('agentPicker.launchTerminal', 'Launch terminal')
|
||||
return (
|
||||
<Button
|
||||
key={next}
|
||||
@@ -76,9 +79,9 @@ export function AgentLaunchSurfaceButtons({
|
||||
}
|
||||
disabled={disabled || (next === 'terminal' && terminalBlocked)}
|
||||
aria-description={
|
||||
next === 'terminal' && terminalBlocked ? terminalPendingLabel() : undefined
|
||||
next === 'terminal' && terminalBlocked ? terminalPendingLabel(t) : undefined
|
||||
}
|
||||
title={next === 'terminal' && terminalBlocked ? terminalPendingLabel() : undefined}
|
||||
title={next === 'terminal' && terminalBlocked ? terminalPendingLabel(t) : undefined}
|
||||
variant={isPrimary ? 'default' : 'outline'}
|
||||
onClick={() => onSelect(next)}
|
||||
>
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
import { CommandItem } from '@/components/ui/command'
|
||||
import { translate } from '@/i18n/i18n'
|
||||
import { useTranslate } from '@/i18n/use-translate'
|
||||
import type { AgentPickerEntry } from '../../../../shared/agent-launch/picker/agent-launch-picker'
|
||||
import { TEXT_ACTION_DEFAULT_MODEL } from '../../../../shared/agent-launch/picker/agent-launch-picker-text-action'
|
||||
import type { AgentPickerEdit } from '../../../../shared/agent-launch/picker/agent-launch-picker-edits'
|
||||
@@ -15,6 +15,7 @@ export function AgentLaunchTextModelDefault({
|
||||
modelInArguments?: boolean
|
||||
onSelect: (edit: AgentPickerEdit) => void
|
||||
}) {
|
||||
const t = useTranslate()
|
||||
if (!entry.textGeneration) {
|
||||
return null
|
||||
}
|
||||
@@ -31,7 +32,7 @@ export function AgentLaunchTextModelDefault({
|
||||
) : null}
|
||||
{modelInArguments ? (
|
||||
<div className="px-2 py-1.5 text-xs text-muted-foreground">
|
||||
{translate('agentPicker.setByArguments', 'Set by your Arguments')}
|
||||
{t('agentPicker.setByArguments', 'Set by your Arguments')}
|
||||
</div>
|
||||
) : null}
|
||||
</>
|
||||
|
||||
@@ -10,7 +10,7 @@ import {
|
||||
DialogTitle
|
||||
} from '@/components/ui/dialog'
|
||||
import { AgentsPane } from '@/components/settings/agents/AgentsPane'
|
||||
import { translate } from '@/i18n/i18n'
|
||||
import { useTranslate } from '@/i18n/use-translate'
|
||||
import {
|
||||
isWindowsTerminalCapabilityHost,
|
||||
useLocalWindowsTerminalCapabilities
|
||||
@@ -30,6 +30,7 @@ export default function AgentSettingsDialog({
|
||||
onOpenChange,
|
||||
onCloseAutoFocus
|
||||
}: AgentSettingsDialogProps): React.JSX.Element | null {
|
||||
const t = useTranslate()
|
||||
const viewActive = useAppStateViewActive()
|
||||
const settings = useAppStateValue((s) => s.settings)
|
||||
const updateSettings = useCallback(
|
||||
@@ -77,10 +78,10 @@ export default function AgentSettingsDialog({
|
||||
<DialogContent className="sm:max-w-2xl" onCloseAutoFocus={onCloseAutoFocus}>
|
||||
<DialogHeader>
|
||||
<DialogTitle className="text-sm">
|
||||
{translate('auto.components.agent.AgentSettingsDialog.fc0268e4ed', 'Agents')}
|
||||
{t('auto.components.agent.AgentSettingsDialog.fc0268e4ed', 'Agents')}
|
||||
</DialogTitle>
|
||||
<DialogDescription className="text-xs">
|
||||
{translate(
|
||||
{t(
|
||||
'auto.components.agent.AgentSettingsDialog.50cdb57c03',
|
||||
'Manage AI agents, set a default, and customize commands.'
|
||||
)}
|
||||
|
||||
@@ -1,14 +1,15 @@
|
||||
import type { JSX } from 'react'
|
||||
import { Settings as SettingsIcon } from 'lucide-react'
|
||||
import { DropdownMenuItem } from '@/components/ui/dropdown-menu'
|
||||
import { translate } from '@/i18n/i18n'
|
||||
import { useTranslate } from '@/i18n/use-translate'
|
||||
|
||||
/** The new-tab menu's footer. Form pickers use the same row. */
|
||||
export function AgentSettingsMenuItem({ onSelect }: { onSelect: () => void }): JSX.Element {
|
||||
const t = useTranslate()
|
||||
return (
|
||||
<DropdownMenuItem onSelect={onSelect} variant="launch">
|
||||
<SettingsIcon className="size-4" />
|
||||
{translate('auto.components.tab.bar.QuickLaunchButton.348a04c1ad', 'Agent settings…')}
|
||||
{t('auto.components.tab.bar.QuickLaunchButton.348a04c1ad', 'Agent settings…')}
|
||||
</DropdownMenuItem>
|
||||
)
|
||||
}
|
||||
|
||||
@@ -8,7 +8,7 @@ import {
|
||||
ContextMenuTrigger
|
||||
} from '@/components/ui/context-menu'
|
||||
import { cn } from '@/lib/utils'
|
||||
import { translate } from '@/i18n/i18n'
|
||||
import { useTranslate } from '@/i18n/use-translate'
|
||||
|
||||
type ItemRenderArgs = {
|
||||
key: string
|
||||
@@ -53,6 +53,7 @@ export function AgentDefaultContextMenu({
|
||||
isDefault,
|
||||
onSetDefault
|
||||
}: AgentDefaultContextMenuProps): React.ReactNode {
|
||||
const t = useTranslate()
|
||||
const [open, setOpen] = React.useState(false)
|
||||
if (!onSetDefault) {
|
||||
// Unmounting an open menu skips onOpenChange; reset so a remount is not marked open.
|
||||
@@ -75,8 +76,8 @@ export function AgentDefaultContextMenu({
|
||||
<ContextMenuItem onSelect={onSetDefault} disabled={isDefault}>
|
||||
<Star className="size-3.5" />
|
||||
{isDefault
|
||||
? translate('auto.components.agent.AgentCombobox.1b0d6965fa', 'Current default')
|
||||
: translate('auto.components.agent.AgentCombobox.9c6b59fe58', 'Set as default')}
|
||||
? t('auto.components.agent.AgentCombobox.1b0d6965fa', 'Current default')
|
||||
: t('auto.components.agent.AgentCombobox.9c6b59fe58', 'Set as default')}
|
||||
</ContextMenuItem>
|
||||
</ContextMenuContent>
|
||||
</ContextMenu>
|
||||
|
||||
@@ -14,6 +14,7 @@ import {
|
||||
} from '@/components/tab-bar/tab-agent-row-presentation'
|
||||
import { cn } from '@/lib/utils'
|
||||
import { translate } from '@/i18n/i18n'
|
||||
import { useTranslate, type Translate } from '@/i18n/use-translate'
|
||||
import type { TuiAgent } from '../../../../shared/agent-launch/catalog/tui-agent'
|
||||
import type { resolveAgentMenuSelection } from '@/lib/agent-menu-selection'
|
||||
import { renderAgentComboboxItem } from './agent-combobox-items'
|
||||
@@ -36,8 +37,8 @@ export function surfaceAnswerPending(rows: AgentComboboxSurfaceRows | undefined)
|
||||
}
|
||||
|
||||
// Nothing launches until the form is submitted, so the picker says "start", not "launch".
|
||||
function terminalPickTitle(label: string): string {
|
||||
return translate('components.agentCombobox.startInTerminal', 'Start {{value0}} in a terminal', {
|
||||
function terminalPickTitle(label: string, t: Translate = translate): string {
|
||||
return t('components.agentCombobox.startInTerminal', 'Start {{value0}} in a terminal', {
|
||||
value0: label
|
||||
})
|
||||
}
|
||||
@@ -66,7 +67,8 @@ export function surfaceCommandValues(
|
||||
value: TuiAgent | null
|
||||
blankValue: string
|
||||
blankMatchesQuery: boolean
|
||||
}
|
||||
},
|
||||
t?: Translate
|
||||
): { rows: NewTabAgentMenuRow[]; selected: string | null; highlighted: string } {
|
||||
// Disabled rows are listed with their reason, so every agent is searched, not only pickable ones.
|
||||
const rows = visibleSurfaceRows(model, getAgentCatalog(), picker.query)
|
||||
@@ -74,7 +76,7 @@ export function surfaceCommandValues(
|
||||
surfaceRowValue(visibleSurfaceRows(model, getAgentCatalog(), ''), picker.value) ?? null
|
||||
const pending = model.state === 'settled' && model.terminalPending
|
||||
const first = rows.find(
|
||||
(row) => row.kind !== 'chat-unavailable' && !terminalPendingReason(pending, row)
|
||||
(row) => row.kind !== 'chat-unavailable' && !terminalPendingReason(pending, row, t)
|
||||
)
|
||||
const highlighted = !picker.query.trim()
|
||||
? (selected ?? picker.blankValue)
|
||||
@@ -161,6 +163,7 @@ export function SurfaceAgentItems({
|
||||
defaultAgent?: TuiAgent | 'blank' | null
|
||||
onSetDefault?: (agent: TuiAgent) => void
|
||||
}): React.ReactNode {
|
||||
const t = useTranslate()
|
||||
const { model, onPick } = surfaceRows
|
||||
if (model.state === 'loading') {
|
||||
// Why: rows fill in once every answer is known, instead of reshuffling under the pointer.
|
||||
@@ -169,10 +172,7 @@ export function SurfaceAgentItems({
|
||||
key={width}
|
||||
value={`loading:${width}`}
|
||||
disabled
|
||||
aria-label={translate(
|
||||
'auto.components.tab.bar.QuickLaunchButton.agentsLoading',
|
||||
'Loading agents'
|
||||
)}
|
||||
aria-label={t('auto.components.tab.bar.QuickLaunchButton.agentsLoading', 'Loading agents')}
|
||||
>
|
||||
<span aria-hidden="true" className="flex h-[1lh] flex-1 items-center">
|
||||
<span className={cn('h-2 rounded-full bg-muted', width)} />
|
||||
@@ -184,12 +184,12 @@ export function SurfaceAgentItems({
|
||||
const terminalPicked = surfaceRows.selection.cli
|
||||
return rows.map((row, index) => {
|
||||
const id = rowId(row)
|
||||
const label = rowLabel(row)
|
||||
const label = rowLabel(row, t)
|
||||
const startsCliGroup = row.kind === 'cli' && index > 0 && rows[index - 1]?.kind !== 'cli'
|
||||
const pendingReason = terminalPendingReason(model.terminalPending, row)
|
||||
const pendingReason = terminalPendingReason(model.terminalPending, row, t)
|
||||
const unavailable = row.kind === 'chat-unavailable' || !!pendingReason
|
||||
const pressed = row.kind === 'chat' && id === pickedValue && terminalPicked
|
||||
const agentLabel = rowLabel({ ...row, kind: 'chat' })
|
||||
const agentLabel = rowLabel({ ...row, kind: 'chat' }, t)
|
||||
return (
|
||||
<React.Fragment key={id}>
|
||||
{startsCliGroup ? <CommandSeparator /> : null}
|
||||
@@ -206,17 +206,19 @@ export function SurfaceAgentItems({
|
||||
title: unavailable
|
||||
? undefined
|
||||
: row.kind === 'chat'
|
||||
? rowTitle(row, agentLabel)
|
||||
: terminalPickTitle(agentLabel),
|
||||
? rowTitle(row, agentLabel, t)
|
||||
: terminalPickTitle(agentLabel, t),
|
||||
trailing: unavailable ? (
|
||||
<span className="shrink-0 text-muted-foreground">
|
||||
{row.kind === 'chat-unavailable' ? chatUnavailableLabel(row.reason) : pendingReason}
|
||||
{row.kind === 'chat-unavailable'
|
||||
? chatUnavailableLabel(row.reason, t)
|
||||
: pendingReason}
|
||||
</span>
|
||||
) : rowOffersTerminal(model, row) ? (
|
||||
<AgentSurfaceLaunchButton
|
||||
surface="terminal"
|
||||
reason={model.terminalPending ? terminalPendingLabel() : undefined}
|
||||
title={terminalPickTitle(agentLabel)}
|
||||
reason={model.terminalPending ? terminalPendingLabel(t) : undefined}
|
||||
title={terminalPickTitle(agentLabel, t)}
|
||||
aria-pressed={pressed}
|
||||
className={cn(
|
||||
'hover:bg-background [[data-selected=true]_&]:opacity-100',
|
||||
|
||||
@@ -3,6 +3,7 @@ import {
|
||||
textActionArgumentsOwnEffort
|
||||
} from '../../../../shared/agent-launch/picker/agent-launch-picker-text-action'
|
||||
import { translate } from '@/i18n/i18n'
|
||||
import type { Translate } from '@/i18n/use-translate'
|
||||
import type { SessionOptionDescriptor } from '../../../../shared/native-chat/input/native-chat-session-options'
|
||||
import type {
|
||||
agentPickerEntryOptions,
|
||||
@@ -29,7 +30,8 @@ type AgentLaunchSetupOptionRowsInput = {
|
||||
function argumentsOwnedRow(
|
||||
id: string,
|
||||
label: string,
|
||||
category: SessionOptionDescriptor['category']
|
||||
category: SessionOptionDescriptor['category'],
|
||||
t: Translate = translate
|
||||
): SessionOptionDescriptor {
|
||||
return {
|
||||
id,
|
||||
@@ -40,7 +42,7 @@ function argumentsOwnedRow(
|
||||
choices: [
|
||||
{
|
||||
value: 'arguments',
|
||||
label: translate('agentPicker.setByArguments', 'Set by your Arguments')
|
||||
label: t('agentPicker.setByArguments', 'Set by your Arguments')
|
||||
}
|
||||
],
|
||||
currentValue: 'arguments'
|
||||
@@ -52,32 +54,40 @@ function argumentsOwnedRow(
|
||||
}
|
||||
|
||||
/** Clearing Effort keeps the selected model, so its default is that model's inherited effort. */
|
||||
function textEffortDefaultLabel(kind: {
|
||||
choices: readonly { value: string; label: string }[]
|
||||
defaultValue: string
|
||||
}): string {
|
||||
function textEffortDefaultLabel(
|
||||
kind: {
|
||||
choices: readonly { value: string; label: string }[]
|
||||
defaultValue: string
|
||||
},
|
||||
t: Translate = translate
|
||||
): string {
|
||||
const effort = kind.choices.find((choice) => choice.value === kind.defaultValue)?.label
|
||||
return effort
|
||||
? translate('agentPicker.sameAsTextDefault', 'Same as default · {{value0}}', {
|
||||
? t('agentPicker.sameAsTextDefault', 'Same as default · {{value0}}', {
|
||||
value0: effort
|
||||
})
|
||||
: translate('settings.sourceControl.actionModel.effortDefault', 'Default')
|
||||
: t('settings.sourceControl.actionModel.effortDefault', 'Default')
|
||||
}
|
||||
|
||||
export function agentLaunchSetupOptionRows({
|
||||
options,
|
||||
models,
|
||||
entry,
|
||||
draft,
|
||||
currentModel,
|
||||
argumentsView,
|
||||
defaultsPending,
|
||||
disabled,
|
||||
argumentsKnown
|
||||
}: AgentLaunchSetupOptionRowsInput): SessionOptionDescriptor[] {
|
||||
export function agentLaunchSetupOptionRows(
|
||||
{
|
||||
options,
|
||||
models,
|
||||
entry,
|
||||
draft,
|
||||
currentModel,
|
||||
argumentsView,
|
||||
defaultsPending,
|
||||
disabled,
|
||||
argumentsKnown
|
||||
}: AgentLaunchSetupOptionRowsInput,
|
||||
t: Translate = translate
|
||||
): SessionOptionDescriptor[] {
|
||||
// Shown even when the menu's model has no levels, so Effort explains where it is set.
|
||||
if (textActionArgumentsOwnEffort(entry, draft)) {
|
||||
return [argumentsOwnedRow('effort', options[0]?.label ?? 'Reasoning effort', 'thought_level')]
|
||||
return [
|
||||
argumentsOwnedRow('effort', options[0]?.label ?? 'Reasoning effort', 'thought_level', t)
|
||||
]
|
||||
}
|
||||
return options.map((option) => {
|
||||
const effortInArguments =
|
||||
@@ -96,7 +106,7 @@ export function agentLaunchSetupOptionRows({
|
||||
: undefined
|
||||
: undefined)
|
||||
if (argumentsView.context.analysis.opaqueKeys.has(option.id)) {
|
||||
return argumentsOwnedRow(option.id, option.label, option.category)
|
||||
return argumentsOwnedRow(option.id, option.label, option.category, t)
|
||||
}
|
||||
const valueKnown =
|
||||
current !== undefined ||
|
||||
@@ -118,7 +128,7 @@ export function agentLaunchSetupOptionRows({
|
||||
? [
|
||||
{
|
||||
value: 'arguments',
|
||||
label: translate('agentPicker.setByArguments', 'Set by your Arguments')
|
||||
label: t('agentPicker.setByArguments', 'Set by your Arguments')
|
||||
}
|
||||
]
|
||||
: []),
|
||||
@@ -126,7 +136,7 @@ export function agentLaunchSetupOptionRows({
|
||||
? [
|
||||
{
|
||||
value: TEXT_ACTION_DEFAULT_MODEL,
|
||||
label: textEffortDefaultLabel(option.kind)
|
||||
label: textEffortDefaultLabel(option.kind, t)
|
||||
}
|
||||
]
|
||||
: []),
|
||||
@@ -156,7 +166,8 @@ export function agentLaunchSetupOptionRows({
|
||||
export function agentLaunchSetupCatalogState(
|
||||
entry: AgentPickerEntry,
|
||||
models: readonly { id: string; hidden?: true }[],
|
||||
currentModel: string | undefined
|
||||
currentModel: string | undefined,
|
||||
t: Translate = translate
|
||||
) {
|
||||
const hasModels = models.some((model) => !model.hidden)
|
||||
const hasUsableModels =
|
||||
@@ -167,45 +178,45 @@ export function agentLaunchSetupCatalogState(
|
||||
? null
|
||||
: entry.catalogRead === 'unsupported' ||
|
||||
(!hasModels && entry.catalog.origin === 'unknown' && entry.catalogRead === undefined)
|
||||
? translate(
|
||||
? t(
|
||||
'agentPicker.catalogUnsupported',
|
||||
'{{agent}} models are unavailable on this host. You can keep your current choices or use Arguments.',
|
||||
{ agent: entry.label }
|
||||
)
|
||||
: entry.catalogRead === 'failed'
|
||||
? translate(
|
||||
? t(
|
||||
'agentPicker.catalogFailed',
|
||||
'Could not load {{agent}} models. Close and reopen the agent picker to try again.',
|
||||
{ agent: entry.label }
|
||||
)
|
||||
: entry.catalog.unavailable?.reason === 'notSignedIn'
|
||||
? translate(
|
||||
? t(
|
||||
'agentPicker.catalogSignIn',
|
||||
'Sign in to {{agent}} to load its models, then close and reopen the agent picker.',
|
||||
{ agent: entry.label }
|
||||
)
|
||||
: entry.catalog.unavailable?.reason === 'cliMissing'
|
||||
? translate(
|
||||
? t(
|
||||
'agentPicker.catalogInstall',
|
||||
'Install {{agent}} on this host to load its models, then close and reopen the agent picker.',
|
||||
{ agent: entry.label }
|
||||
)
|
||||
: entry.catalog.unavailable
|
||||
? translate(
|
||||
? t(
|
||||
'agentPicker.catalogUnresolved',
|
||||
'{{agent}} models have not loaded. Close and reopen the agent picker to try again.',
|
||||
{ agent: entry.label }
|
||||
)
|
||||
: entry.catalog.origin === 'unknown' || entry.catalog.listingInProgress
|
||||
? !hasModels
|
||||
? translate(
|
||||
? t(
|
||||
'agentPicker.catalogUnresolved',
|
||||
'{{agent}} models have not loaded. Close and reopen the agent picker to try again.',
|
||||
{ agent: entry.label }
|
||||
)
|
||||
: null
|
||||
: !hasModels
|
||||
? translate('agentPicker.catalogEmpty', '{{agent}} has no available models.', {
|
||||
? t('agentPicker.catalogEmpty', '{{agent}} has no available models.', {
|
||||
agent: entry.label
|
||||
})
|
||||
: null
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
import type { RuntimeClientTarget } from '../../../../../shared/runtime-client-target'
|
||||
import type { TuiAgent } from '../../../../../shared/agent-launch/catalog/tui-agent'
|
||||
import { translate } from '@/i18n/i18n'
|
||||
import { useTranslate } from '@/i18n/use-translate'
|
||||
import { antigravityChatSignInRunning } from '@/runtime/provider-accounts/sign-in/antigravity-chat-sign-in-status'
|
||||
import { useManagedRuntimeInstall } from '@/runtime/hosts/installation/use-managed-runtime-install'
|
||||
import { ManagedRuntimeInstallRow } from '../../managed-runtime/ManagedRuntimeInstallRow'
|
||||
@@ -28,22 +28,23 @@ export function AgentOpensAsControl({
|
||||
label: string
|
||||
opensAs: AgentOpensAs
|
||||
}): React.JSX.Element {
|
||||
const t = useTranslate()
|
||||
return (
|
||||
<SettingsSegmentedControl<'chat' | 'cli'>
|
||||
value={opensAs.value}
|
||||
onChange={opensAs.onChange}
|
||||
ariaLabel={translate('settings.agents.defaultOpensAs', 'Open {{value0}} as', {
|
||||
ariaLabel={t('settings.agents.defaultOpensAs', 'Open {{value0}} as', {
|
||||
value0: label
|
||||
})}
|
||||
size="sm"
|
||||
options={[
|
||||
{ value: 'chat', label: translate('settings.agents.defaultOpensAsChat', 'Chat') },
|
||||
{ value: 'chat', label: t('settings.agents.defaultOpensAsChat', 'Chat') },
|
||||
{
|
||||
value: 'cli',
|
||||
label: translate('settings.agents.defaultOpensAsCli', 'CLI'),
|
||||
label: t('settings.agents.defaultOpensAsCli', 'CLI'),
|
||||
disabled: opensAs.cliMissing,
|
||||
tooltip: opensAs.cliMissing
|
||||
? translate(
|
||||
? t(
|
||||
'settings.agents.installCliForTerminal',
|
||||
'Install the {{value0}} CLI to open it in a terminal.',
|
||||
{ value0: label }
|
||||
@@ -62,9 +63,10 @@ export function chatUnavailableOnThisDesktop(agent: string): boolean {
|
||||
|
||||
/** Why an agent opens in a terminal on a Windows host; the Chat | CLI choice still routes remote hosts. */
|
||||
export function ChatUnavailableOnWindowsNote({ label }: { label: string }): React.JSX.Element {
|
||||
const t = useTranslate()
|
||||
return (
|
||||
<p className="text-xs text-muted-foreground">
|
||||
{translate(
|
||||
{t(
|
||||
'settings.agents.chatUnavailableOnWindows',
|
||||
"{{value0}} Chat isn't available on Windows yet, so {{value0}} opens in a terminal on this host. Chats on a macOS or Linux host still work.",
|
||||
{ value0: label }
|
||||
@@ -81,6 +83,7 @@ export function AgentChatRuntimeArea({
|
||||
agent: TuiAgent
|
||||
label: string
|
||||
}): React.JSX.Element | null {
|
||||
const t = useTranslate()
|
||||
const runtime = useManagedRuntimeInstall(CHAT_HOST, agent, true)
|
||||
// Install and Remove wait for a running sign-in, which runs the installed server.
|
||||
const signIn = useAntigravityChatSignIn(
|
||||
@@ -103,7 +106,7 @@ export function AgentChatRuntimeArea({
|
||||
<div className="flex items-center gap-3">
|
||||
<div className="min-w-0 flex-1">
|
||||
<p className="text-xs font-medium leading-none">
|
||||
{translate('settings.agents.chatRuntime', 'Chat runtime')}
|
||||
{t('settings.agents.chatRuntime', 'Chat runtime')}
|
||||
</p>
|
||||
<div className="mt-1">{line}</div>
|
||||
</div>
|
||||
@@ -114,7 +117,7 @@ export function AgentChatRuntimeArea({
|
||||
{agent === 'antigravity' && antigravityProviderSignInShown(runtime) ? (
|
||||
<AntigravityProviderSignInArea
|
||||
owner={CHAT_HOST}
|
||||
label={getRuntimeSentenceLabel({ runtime: 'host', label: getHostRuntimeLabel() })}
|
||||
label={getRuntimeSentenceLabel({ runtime: 'host', label: getHostRuntimeLabel(t) })}
|
||||
install={runtime}
|
||||
/>
|
||||
) : null}
|
||||
|
||||
@@ -17,7 +17,7 @@ import { encodeCatalogOptionValue } from '../../../../../shared/agent-session/op
|
||||
import type { TuiAgent } from '../../../../../shared/agent-launch/catalog/tui-agent'
|
||||
import { AgentChoiceMenu } from './AgentChoiceMenu'
|
||||
import { sourceControlActionRouteHint } from './AgentChoiceOptionField'
|
||||
import { translate } from '@/i18n/i18n'
|
||||
import { useTranslate } from '@/i18n/use-translate'
|
||||
|
||||
const AGENT_DEFAULT_VALUE = '__agent_default__'
|
||||
|
||||
@@ -67,6 +67,7 @@ export function AgentChoiceFields({
|
||||
}: Omit<AgentChoiceControlsProps, 'onChange'> & {
|
||||
onSelect: (patch: AgentChoicePatch) => void
|
||||
}): React.JSX.Element | null {
|
||||
const t = useTranslate()
|
||||
const picksModel = sourceControlActionAgentPicksModel(agent)
|
||||
if (!agent || !picksModel) {
|
||||
return null
|
||||
@@ -81,12 +82,12 @@ export function AgentChoiceFields({
|
||||
(Boolean(model) || option.category !== 'mode') &&
|
||||
(!optionIds || optionIds.includes(option.id))
|
||||
)
|
||||
const defaultLabel = translate('settings.sourceControl.actionModel.effortDefault', 'Default')
|
||||
const defaultLabel = t('settings.sourceControl.actionModel.effortDefault', 'Default')
|
||||
return (
|
||||
<div className={layout === 'contents' ? 'contents' : 'grid min-w-0 grid-cols-1 gap-2'}>
|
||||
{catalog ? (
|
||||
<AgentChoiceMenu
|
||||
label={translate('settings.sourceControl.actionModel.model', 'Model')}
|
||||
label={t('settings.sourceControl.actionModel.model', 'Model')}
|
||||
row={layout === 'rows'}
|
||||
value={selected?.model ?? AGENT_DEFAULT_VALUE}
|
||||
disabled={disabled}
|
||||
@@ -96,7 +97,7 @@ export function AgentChoiceFields({
|
||||
value: AGENT_DEFAULT_VALUE,
|
||||
label:
|
||||
defaultLabels?.model ??
|
||||
translate('settings.sourceControl.actionModel.agentDefault', 'Agent default')
|
||||
t('settings.sourceControl.actionModel.agentDefault', 'Agent default')
|
||||
},
|
||||
...catalog.models.map(({ id, label }) => ({ value: id, label })),
|
||||
...(unlistedModel ? [{ value: unlistedModel, label: unlistedModel }] : [])
|
||||
@@ -111,11 +112,11 @@ export function AgentChoiceFields({
|
||||
: [
|
||||
{
|
||||
value: 'true',
|
||||
label: translate('settings.sourceControl.actionModel.on', 'On')
|
||||
label: t('settings.sourceControl.actionModel.on', 'On')
|
||||
},
|
||||
{
|
||||
value: 'false',
|
||||
label: translate('settings.sourceControl.actionModel.off', 'Off')
|
||||
label: t('settings.sourceControl.actionModel.off', 'Off')
|
||||
}
|
||||
]
|
||||
const current = selected?.options?.[option.id]
|
||||
@@ -124,7 +125,7 @@ export function AgentChoiceFields({
|
||||
key={option.id}
|
||||
label={option.label}
|
||||
row={layout === 'rows'}
|
||||
hint={sourceControlActionRouteHint(route)}
|
||||
hint={sourceControlActionRouteHint(route, t)}
|
||||
value={current ?? AGENT_DEFAULT_VALUE}
|
||||
disabled={disabled}
|
||||
onOpenChange={onOpenChange}
|
||||
|
||||
@@ -3,6 +3,7 @@ import { Label } from '../../ui/label'
|
||||
import { SelectTrigger, SelectValue } from '../../ui/select'
|
||||
import { Tooltip, TooltipContent, TooltipTrigger } from '../../ui/tooltip'
|
||||
import { translate } from '@/i18n/i18n'
|
||||
import type { Translate } from '@/i18n/use-translate'
|
||||
import type { SourceControlActionChoiceRoute } from '../../../../../shared/source-control/ai/recipes/source-control-action-options'
|
||||
|
||||
export function SourceControlActionOptionField({
|
||||
@@ -24,11 +25,14 @@ export function SourceControlActionOptionField({
|
||||
}
|
||||
|
||||
/** Says which launches a saved choice reaches when it is not both. */
|
||||
export function sourceControlActionRouteHint(route: SourceControlActionChoiceRoute): string | null {
|
||||
export function sourceControlActionRouteHint(
|
||||
route: SourceControlActionChoiceRoute,
|
||||
t: Translate = translate
|
||||
): string | null {
|
||||
return route === 'chats'
|
||||
? translate('settings.sourceControl.actionModel.chatsOnly', 'Applies to chats only')
|
||||
? t('settings.sourceControl.actionModel.chatsOnly', 'Applies to chats only')
|
||||
: route === 'terminals'
|
||||
? translate('settings.sourceControl.actionModel.terminalsOnly', 'Applies to terminals only')
|
||||
? t('settings.sourceControl.actionModel.terminalsOnly', 'Applies to terminals only')
|
||||
: null
|
||||
}
|
||||
|
||||
|
||||
@@ -15,7 +15,7 @@ import {
|
||||
} from '@/components/tab-bar/drop-indicator'
|
||||
import { composeActiveTerminalTheme } from '@/components/terminal-pane/appearance/terminal-appearance'
|
||||
import { useSystemPrefersDark } from '@/hooks/theme/use-system-prefers-dark'
|
||||
import { translate } from '@/i18n/i18n'
|
||||
import { useIntlLocale, useTranslate } from '@/i18n/use-translate'
|
||||
import { AgentIcon } from '@/lib/agent-catalog'
|
||||
import { buildFontFamily } from '@/lib/monospace-font-family'
|
||||
import { resolveEffectiveTerminalAppearance } from '@/lib/terminal-theme'
|
||||
@@ -65,7 +65,9 @@ function MiniWindow({
|
||||
}
|
||||
|
||||
export function ChatSurfacePreview(): JSX.Element {
|
||||
const copy = getChatDemoCopy()
|
||||
const t = useTranslate()
|
||||
const intlLocale = useIntlLocale()
|
||||
const copy = getChatDemoCopy(t, intlLocale)
|
||||
return (
|
||||
<MiniWindow title={copy.tabTitle} className="bg-chat-canvas">
|
||||
<div
|
||||
@@ -80,7 +82,7 @@ export function ChatSurfacePreview(): JSX.Element {
|
||||
</div>
|
||||
<div className="flex items-center gap-[5px] whitespace-nowrap text-chat-foreground-faint">
|
||||
<Eye className="size-[11px] shrink-0" />
|
||||
{nativeChatToolRowVerb('read')}
|
||||
{nativeChatToolRowVerb('read', t)}
|
||||
<span className="text-chat-foreground-strong">{CHAT_DEMO_FILE}</span>
|
||||
</div>
|
||||
<p className="line-clamp-4">
|
||||
@@ -103,7 +105,7 @@ export function ChatSurfacePreview(): JSX.Element {
|
||||
<div className="shrink-0 px-3 pt-2 pb-3">
|
||||
<div className="rounded-[10px] border border-chat-composer-border bg-chat-composer-surface px-1.5 pt-1.5 pb-[5px] shadow-xs">
|
||||
<div className="truncate px-1 pb-1.5 text-[9.5px] leading-[13px] text-chat-foreground-faint">
|
||||
{translate(
|
||||
{t(
|
||||
'components.native-chat.composer.placeholder',
|
||||
'Ask anything, @ to mention files, / for commands'
|
||||
)}
|
||||
@@ -112,7 +114,7 @@ export function ChatSurfacePreview(): JSX.Element {
|
||||
<Plus className="size-[11px] shrink-0" />
|
||||
<span className="flex min-w-0 items-center gap-[3px] px-[3px]">
|
||||
<Hand className="size-[9px] shrink-0" />
|
||||
<span className="truncate">{nativeChatPermissionModeLabel('ask')}</span>
|
||||
<span className="truncate">{nativeChatPermissionModeLabel('ask', t)}</span>
|
||||
<ChevronDown className="size-[9px] shrink-0" />
|
||||
</span>
|
||||
<span className="flex-1" />
|
||||
@@ -154,7 +156,9 @@ function Mascot(): JSX.Element {
|
||||
|
||||
/** Claude Code's own interface, in the user's terminal theme and font. */
|
||||
export function TerminalSurfacePreview({ settings }: { settings: GlobalSettings }): JSX.Element {
|
||||
const copy = getChatDemoCopy()
|
||||
const t = useTranslate()
|
||||
const intlLocale = useIntlLocale()
|
||||
const copy = getChatDemoCopy(t, intlLocale)
|
||||
const systemPrefersDark = useSystemPrefersDark()
|
||||
const customThemes = JSON.stringify(settings.terminalCustomThemes)
|
||||
const pluginPalettes = JSON.stringify(settings.terminalPluginPalettes)
|
||||
|
||||
@@ -12,7 +12,7 @@ import { reconcileAgentChoiceArgs } from '../../../../../shared/agent-launch/cho
|
||||
|
||||
import type { TuiAgent } from '../../../../../shared/agent-launch/catalog/tui-agent'
|
||||
|
||||
import { translate } from '@/i18n/i18n'
|
||||
import { useTranslate } from '@/i18n/use-translate'
|
||||
import { AgentChoiceFields } from './AgentChoiceControls'
|
||||
|
||||
export function AgentLaunchChoiceEditor({
|
||||
@@ -34,6 +34,7 @@ export function AgentLaunchChoiceEditor({
|
||||
permissionControlRef?: Ref<HTMLDivElement>
|
||||
save: (patch: AgentChoicePatch | undefined, userArgs: string) => Promise<void>
|
||||
}): React.JSX.Element {
|
||||
const t = useTranslate()
|
||||
const menu = useRef({ open: false, committed: false })
|
||||
const editorRef = useRef<HTMLDivElement>(null)
|
||||
const [draft, setDraft] = useState<string | null>(null)
|
||||
@@ -135,7 +136,7 @@ export function AgentLaunchChoiceEditor({
|
||||
choiceChips={chips}
|
||||
note={
|
||||
permission?.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 }
|
||||
|
||||
@@ -2,7 +2,7 @@ import { useId, useLayoutEffect, useRef, useState } from 'react'
|
||||
import { Button } from '../../ui/button'
|
||||
import { Input } from '../../ui/input'
|
||||
import { Textarea } from '../../ui/textarea'
|
||||
import { translate } from '@/i18n/i18n'
|
||||
import { useTranslate } from '@/i18n/use-translate'
|
||||
import {
|
||||
parseAgentDefaultEnvDraft,
|
||||
stringifyAgentDefaultEnvDraft,
|
||||
@@ -20,6 +20,7 @@ export function AgentCommandOverrideInput({
|
||||
cmdOverride: string | undefined
|
||||
onSaveOverride: (value: string) => void | Promise<void>
|
||||
}): React.JSX.Element {
|
||||
const t = useTranslate()
|
||||
const draftSeed = cmdOverride ?? defaultCmd
|
||||
const [cmdDraft, setCmdDraft] = useState(draftSeed)
|
||||
const { pending, write } = useAgentLaunchFieldWrite()
|
||||
@@ -39,7 +40,7 @@ export function AgentCommandOverrideInput({
|
||||
return (
|
||||
<div className="flex flex-col gap-1">
|
||||
<span className="text-xs text-muted-foreground">
|
||||
{translate('auto.components.settings.AgentsPane.2e45ca29b6', 'Command')}
|
||||
{t('auto.components.settings.AgentsPane.2e45ca29b6', 'Command')}
|
||||
</span>
|
||||
<div className="flex items-center gap-2">
|
||||
<Input
|
||||
@@ -75,7 +76,7 @@ export function AgentCommandOverrideInput({
|
||||
}}
|
||||
className="h-7 shrink-0 text-xs text-muted-foreground hover:text-foreground"
|
||||
>
|
||||
{translate('auto.components.settings.AgentsPane.5200dac9da', 'Reset')}
|
||||
{t('auto.components.settings.AgentsPane.5200dac9da', 'Reset')}
|
||||
</Button>
|
||||
)}
|
||||
</div>
|
||||
@@ -111,6 +112,7 @@ export function AgentDefaultArgsInput({
|
||||
/** The caller already labels the field, e.g. with a disclosure row. */
|
||||
hideLabel?: boolean
|
||||
}): React.JSX.Element {
|
||||
const t = useTranslate()
|
||||
const [draft, setDraft] = useState<string | null>(null)
|
||||
const inputRef = useRef<HTMLTextAreaElement>(null)
|
||||
const chipRef = useRef<HTMLSpanElement>(null)
|
||||
@@ -157,7 +159,7 @@ export function AgentDefaultArgsInput({
|
||||
<div className="flex min-w-0 flex-col gap-1">
|
||||
{hideLabel ? null : (
|
||||
<span className="text-xs font-medium leading-none text-muted-foreground">
|
||||
{translate('auto.components.settings.AgentsPane.cfb3f35775', 'Arguments')}
|
||||
{t('auto.components.settings.AgentsPane.cfb3f35775', 'Arguments')}
|
||||
</span>
|
||||
)}
|
||||
<div className="flex min-w-0 items-center gap-2">
|
||||
@@ -203,7 +205,7 @@ export function AgentDefaultArgsInput({
|
||||
value={argsDraft}
|
||||
disabled={disabled}
|
||||
readOnly={pending}
|
||||
aria-label={translate('auto.components.settings.AgentsPane.cfb3f35775', 'Arguments')}
|
||||
aria-label={t('auto.components.settings.AgentsPane.cfb3f35775', 'Arguments')}
|
||||
onChange={(event) =>
|
||||
(onDraftChange ?? setDraft)(event.target.value.replace(/[\r\n]/g, ''))
|
||||
}
|
||||
@@ -239,10 +241,7 @@ export function AgentDefaultArgsInput({
|
||||
? undefined
|
||||
: placeholder ||
|
||||
defaultArgs ||
|
||||
translate(
|
||||
'auto.components.settings.AgentsPane.6f99bf5dd0',
|
||||
'No default arguments'
|
||||
)
|
||||
t('auto.components.settings.AgentsPane.6f99bf5dd0', 'No default arguments')
|
||||
}
|
||||
spellCheck={false}
|
||||
className="block min-h-5 max-w-full [field-sizing:content] [overflow-wrap:anywhere]"
|
||||
@@ -268,7 +267,7 @@ export function AgentDefaultArgsInput({
|
||||
}}
|
||||
className="h-7 shrink-0 text-xs text-muted-foreground hover:text-foreground"
|
||||
>
|
||||
{translate('auto.components.settings.AgentsPane.5200dac9da', 'Reset')}
|
||||
{t('auto.components.settings.AgentsPane.5200dac9da', 'Reset')}
|
||||
</Button>
|
||||
)}
|
||||
</div>
|
||||
@@ -286,6 +285,7 @@ export function AgentDefaultEnvInput({
|
||||
envOverride: Record<string, string>
|
||||
onSaveEnv: (value: Record<string, string>) => void | Promise<void>
|
||||
}): React.JSX.Element {
|
||||
const t = useTranslate()
|
||||
const defaultEnvText = stringifyAgentDefaultEnvDraft(defaultEnv)
|
||||
const draftSeed = stringifyAgentDefaultEnvDraft(envOverride)
|
||||
const [draft, setDraft] = useState<string | null>(null)
|
||||
@@ -316,7 +316,7 @@ export function AgentDefaultEnvInput({
|
||||
return (
|
||||
<div className="flex flex-col gap-1">
|
||||
<span className="text-xs text-muted-foreground">
|
||||
{translate('auto.components.settings.AgentsPane.8fbe1f37c1', 'Environment')}
|
||||
{t('auto.components.settings.AgentsPane.8fbe1f37c1', 'Environment')}
|
||||
</span>
|
||||
<div className="flex items-center gap-2">
|
||||
<Input
|
||||
@@ -341,7 +341,7 @@ export function AgentDefaultEnvInput({
|
||||
}}
|
||||
placeholder={
|
||||
defaultEnvText ||
|
||||
translate('auto.components.settings.AgentsPane.2d133152fa', 'No default environment')
|
||||
t('auto.components.settings.AgentsPane.2d133152fa', 'No default environment')
|
||||
}
|
||||
spellCheck={false}
|
||||
aria-invalid={envDraftError ? true : undefined}
|
||||
@@ -366,18 +366,18 @@ export function AgentDefaultEnvInput({
|
||||
}}
|
||||
className="h-7 shrink-0 text-xs text-muted-foreground hover:text-foreground"
|
||||
>
|
||||
{translate('auto.components.settings.AgentsPane.5200dac9da', 'Reset')}
|
||||
{t('auto.components.settings.AgentsPane.5200dac9da', 'Reset')}
|
||||
</Button>
|
||||
)}
|
||||
</div>
|
||||
{envDraftError && (
|
||||
<p id={envDraftErrorId} className="mt-1 text-[11px] text-destructive">
|
||||
{envDraftError === 'too-large'
|
||||
? translate(
|
||||
? t(
|
||||
'auto.components.settings.AgentsPane.3f1bdf3cb4',
|
||||
'Environment text is too large to parse safely.'
|
||||
)
|
||||
: translate(
|
||||
: t(
|
||||
'settings.agents.environmentUnclosedQuote',
|
||||
'Close the quote to save the environment.'
|
||||
)}
|
||||
|
||||
+25
-24
@@ -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 { useTranslate } from '@/i18n/use-translate'
|
||||
import type { Translate } from '@/i18n/use-translate'
|
||||
import { cn } from '@/lib/utils'
|
||||
import { Button } from '../../ui/button'
|
||||
@@ -48,9 +49,10 @@ export function AllAgentsPermissionSetting({
|
||||
onChange: (choice: AgentPermissionChoice) => void
|
||||
onChangeWithoutAuto: (choice: AgentPermissionChoice) => void
|
||||
}): React.JSX.Element {
|
||||
const title = translate('settings.agents.permissionsTitle', 'Permissions')
|
||||
const ownLabel = translate('settings.agents.permissionEachOwn', "Each agent's own settings")
|
||||
const withoutAutoTitle = translate(
|
||||
const t = useTranslate()
|
||||
const title = t('settings.agents.permissionsTitle', 'Permissions')
|
||||
const ownLabel = t('settings.agents.permissionEachOwn', "Each agent's own settings")
|
||||
const withoutAutoTitle = t(
|
||||
'settings.agents.otherAgentsPermissionsLabel',
|
||||
'Permissions for other agents'
|
||||
)
|
||||
@@ -88,7 +90,7 @@ export function AllAgentsPermissionSetting({
|
||||
</DropdownMenu>
|
||||
</div>
|
||||
<p className="text-xs text-muted-foreground">
|
||||
{translate(
|
||||
{t(
|
||||
'settings.agents.permissionsChoiceExplanation',
|
||||
'What every agent may do without asking. You can set one agent differently below.'
|
||||
)}
|
||||
@@ -97,7 +99,7 @@ export function AllAgentsPermissionSetting({
|
||||
{choice === 'auto' ? (
|
||||
<div className="flex items-center gap-2">
|
||||
<span className="text-xs text-muted-foreground">
|
||||
{translate('settings.agents.permissionWithoutAuto', 'Agents without Approve for me')}
|
||||
{t('settings.agents.permissionWithoutAuto', 'Agents without Approve for me')}
|
||||
</span>
|
||||
<DropdownMenu>
|
||||
<DropdownMenuTrigger asChild>
|
||||
@@ -110,7 +112,7 @@ export function AllAgentsPermissionSetting({
|
||||
>
|
||||
{withoutAutoChoice === null ? (
|
||||
<span className="truncate">
|
||||
{translate('settings.agents.permissionMixed', 'Mixed')}
|
||||
{t('settings.agents.permissionMixed', 'Mixed')}
|
||||
</span>
|
||||
) : (
|
||||
<AgentPermissionChoiceName
|
||||
@@ -183,9 +185,9 @@ export function AgentPermissionMenu({
|
||||
showInherited?: boolean
|
||||
rawValue?: string
|
||||
}): React.JSX.Element {
|
||||
const followAll =
|
||||
inheritLabel ?? translate('settings.agents.permissionSameAsAll', 'Same as all agents')
|
||||
const ownLabel = agentPermissionChoiceLabel('own', label)
|
||||
const t = useTranslate()
|
||||
const followAll = inheritLabel ?? t('settings.agents.permissionSameAsAll', 'Same as all agents')
|
||||
const ownLabel = agentPermissionChoiceLabel('own', label, t)
|
||||
return (
|
||||
<div
|
||||
className={cn(
|
||||
@@ -199,15 +201,13 @@ export function AgentPermissionMenu({
|
||||
>
|
||||
{layout === 'row' ? (
|
||||
<span className="text-xs text-muted-foreground">
|
||||
{translate('settings.agents.rowPermissions', 'Permissions')}
|
||||
{t('settings.agents.rowPermissions', 'Permissions')}
|
||||
</span>
|
||||
) : layout === 'stacked' ? (
|
||||
<Label variant="settings">
|
||||
{translate('settings.agents.rowPermissions', 'Permissions')}
|
||||
</Label>
|
||||
<Label variant="settings">{t('settings.agents.rowPermissions', 'Permissions')}</Label>
|
||||
) : (
|
||||
<span className="text-xs font-medium">
|
||||
{translate('settings.agents.rowPermissions', 'Permissions')}
|
||||
{t('settings.agents.rowPermissions', 'Permissions')}
|
||||
</span>
|
||||
)}
|
||||
<DropdownMenu onOpenChange={onOpenChange}>
|
||||
@@ -216,7 +216,7 @@ export function AgentPermissionMenu({
|
||||
type="button"
|
||||
variant="outline"
|
||||
size={size}
|
||||
aria-label={translate('settings.agents.rowPermissionsLabel', '{{agent}} permissions', {
|
||||
aria-label={t('settings.agents.rowPermissionsLabel', '{{agent}} permissions', {
|
||||
agent: label
|
||||
})}
|
||||
className={cn('min-w-0 max-w-full', layout === 'stacked' && 'w-full justify-between')}
|
||||
@@ -246,10 +246,10 @@ export function AgentPermissionMenu({
|
||||
</DropdownMenu>
|
||||
{permission.terminalChoice === 'own' && permission.chatChoice ? (
|
||||
<span className="text-xs text-muted-foreground">
|
||||
{translate(
|
||||
{t(
|
||||
'settings.agents.permissionSurfaceDifference',
|
||||
'Terminals use the agent’s own settings; new chats use {{mode}}.',
|
||||
{ mode: nativeChatPermissionModeLabel(permission.chatChoice) }
|
||||
{ mode: nativeChatPermissionModeLabel(permission.chatChoice, t) }
|
||||
)}
|
||||
</span>
|
||||
) : null}
|
||||
@@ -276,6 +276,7 @@ function AgentArgsChoiceChip({
|
||||
choiceKey: string
|
||||
source?: 'arguments'
|
||||
}): React.JSX.Element {
|
||||
const t = useTranslate()
|
||||
return (
|
||||
<Tooltip>
|
||||
<TooltipTrigger asChild>
|
||||
@@ -305,8 +306,8 @@ function AgentArgsChoiceChip({
|
||||
}}
|
||||
aria-label={
|
||||
source === 'arguments' || choiceKey !== 'permissionMode'
|
||||
? translate('settings.agents.choiceChipRemove', 'Remove {{flag}}', { flag })
|
||||
: translate(
|
||||
? t('settings.agents.choiceChipRemove', 'Remove {{flag}}', { flag })
|
||||
: t(
|
||||
'settings.agents.permissionChipRemove',
|
||||
"Remove {{flag}} and use {{agent}}'s own settings",
|
||||
{ flag, agent: label }
|
||||
@@ -320,19 +321,19 @@ function AgentArgsChoiceChip({
|
||||
</TooltipTrigger>
|
||||
<TooltipContent>
|
||||
{source === 'arguments'
|
||||
? translate('agentPicker.argumentsHint', 'Applies only to CLI launches.')
|
||||
? t('agentPicker.argumentsHint', 'Applies only to CLI launches.')
|
||||
: choiceKey !== 'permissionMode'
|
||||
? translate(
|
||||
? t(
|
||||
'settings.agents.choiceChipTooltip',
|
||||
'Added by {{choice}}. Remove it to use the default.',
|
||||
{
|
||||
choice:
|
||||
choiceKey === 'model'
|
||||
? translate('settings.sourceControl.actionModel.model', 'Model')
|
||||
: translate('settings.sourceControl.actionModel.effort', 'Effort')
|
||||
? t('settings.sourceControl.actionModel.model', 'Model')
|
||||
: t('settings.sourceControl.actionModel.effort', 'Effort')
|
||||
}
|
||||
)
|
||||
: translate(
|
||||
: t(
|
||||
'settings.agents.permissionChipTooltip',
|
||||
'Added by Permissions. Remove it to use {{agent}}’s own settings.',
|
||||
{ agent: label }
|
||||
|
||||
@@ -1,20 +1,21 @@
|
||||
import { Terminal } from 'lucide-react'
|
||||
import type { AgentPermissionChoice } from '../../../../../shared/agent-launch/permissions/tui-agent-permission-choice'
|
||||
import { translate } from '@/i18n/i18n'
|
||||
import { useTranslate, type Translate } from '@/i18n/use-translate'
|
||||
import { DropdownMenuRadioItem, DropdownMenuSeparator } from '../../ui/dropdown-menu'
|
||||
import {
|
||||
NativeChatPermissionModeItems,
|
||||
NativeChatPermissionModeName
|
||||
} from '../../native-chat/session/options/controls/NativeChatPermissionModePicker'
|
||||
|
||||
function ownDescription(chatOwnSupported: boolean): string {
|
||||
function ownDescription(chatOwnSupported: boolean, t: Translate = translate): string {
|
||||
if (!chatOwnSupported) {
|
||||
return translate(
|
||||
return t(
|
||||
'settings.agents.permissionOwnLegacyDescription',
|
||||
'Orca adds no permission flag in terminals; chats use Settings → Chat.'
|
||||
)
|
||||
}
|
||||
return translate(
|
||||
return t(
|
||||
'settings.agents.permissionOwnDescription',
|
||||
'Terminals and chats follow the agent’s own permission settings.'
|
||||
)
|
||||
@@ -57,6 +58,7 @@ export function AgentPermissionChoiceItems({
|
||||
disabled?: boolean
|
||||
onSelect: (choice: AgentPermissionChoice) => void
|
||||
}): React.JSX.Element {
|
||||
const t = useTranslate()
|
||||
const modes = choices.filter((choice) => choice !== 'own')
|
||||
return (
|
||||
<>
|
||||
@@ -73,7 +75,7 @@ export function AgentPermissionChoiceItems({
|
||||
<div className="min-w-0 py-0.5">
|
||||
<AgentPermissionChoiceName choice="own" ownLabel={ownLabel} />
|
||||
<div className="text-xs font-normal text-muted-foreground">
|
||||
{ownDescription(chatOwnSupported)}
|
||||
{ownDescription(chatOwnSupported, t)}
|
||||
</div>
|
||||
</div>
|
||||
</DropdownMenuRadioItem>
|
||||
|
||||
+6
-8
@@ -5,7 +5,7 @@ import { normalizeGlobalWindowsRuntimeDefault } from '../../../../../shared/proj
|
||||
import { Button } from '../../ui/button'
|
||||
import { Input } from '../../ui/input'
|
||||
import { Tooltip, TooltipContent, TooltipTrigger } from '../../ui/tooltip'
|
||||
import { translate } from '@/i18n/i18n'
|
||||
import { useTranslate } from '@/i18n/use-translate'
|
||||
|
||||
export type AgentSessionSourceHomeControl = {
|
||||
runtimeLabel: string
|
||||
@@ -85,6 +85,7 @@ export function AgentSessionSourceHomeInput({
|
||||
value,
|
||||
onSave
|
||||
}: AgentSessionSourceHomeControl): React.JSX.Element {
|
||||
const t = useTranslate()
|
||||
const [draft, setDraft] = useState(value)
|
||||
|
||||
const commit = (): void => {
|
||||
@@ -94,15 +95,12 @@ export function AgentSessionSourceHomeInput({
|
||||
return (
|
||||
<div className="flex flex-col gap-1">
|
||||
<span className="flex items-center gap-1.5 text-xs text-muted-foreground">
|
||||
{translate(
|
||||
'auto.components.settings.AgentsPane.codexSessionSource',
|
||||
'Codex home to import from'
|
||||
)}
|
||||
{t('auto.components.settings.AgentsPane.codexSessionSource', 'Codex home to import from')}
|
||||
<Tooltip>
|
||||
<TooltipTrigger asChild>
|
||||
<button
|
||||
type="button"
|
||||
aria-label={translate(
|
||||
aria-label={t(
|
||||
'auto.components.settings.AgentsPane.codexSessionSourceInfo',
|
||||
'About importing Codex history'
|
||||
)}
|
||||
@@ -112,7 +110,7 @@ export function AgentSessionSourceHomeInput({
|
||||
</button>
|
||||
</TooltipTrigger>
|
||||
<TooltipContent side="top" sideOffset={6} className="max-w-xs">
|
||||
{translate(
|
||||
{t(
|
||||
'auto.components.settings.AgentsPane.codexSessionSourceTooltip',
|
||||
'Orca runs Codex in an isolated home. Point this at your existing Codex home to import that session history. Empty uses ~/.codex.'
|
||||
)}
|
||||
@@ -149,7 +147,7 @@ export function AgentSessionSourceHomeInput({
|
||||
}}
|
||||
className="h-7 shrink-0 text-xs text-muted-foreground hover:text-foreground"
|
||||
>
|
||||
{translate('auto.components.settings.AgentsPane.5200dac9da', 'Reset')}
|
||||
{t('auto.components.settings.AgentsPane.5200dac9da', 'Reset')}
|
||||
</Button>
|
||||
)}
|
||||
</div>
|
||||
|
||||
@@ -4,7 +4,7 @@ import { FEATURE_TIPS } from '../../../../shared/feature-tips'
|
||||
import { Button } from '@/components/ui/button'
|
||||
import { Card, CardContent, CardDescription, CardHeader, CardTitle } from '@/components/ui/card'
|
||||
import { useMountedRef } from '@/hooks/useMountedRef'
|
||||
import { translate } from '@/i18n/i18n'
|
||||
import { useTranslate } from '@/i18n/use-translate'
|
||||
import { useAppStore } from '@/store'
|
||||
import { ChatSurfacePreview } from '../agent/launch-settings/AgentDefaultSurfacePreviews'
|
||||
import { getChatUiHereVariantCopy } from './chat-ui-here-copy'
|
||||
@@ -23,6 +23,7 @@ type CornerCardState =
|
||||
|
||||
/** The nonblocking launch card for terminal-default profiles, sharing the tip's copy and a compact chat preview. */
|
||||
export function ChatUiHereCornerCard({ onClose }: { onClose: () => void }): JSX.Element | null {
|
||||
const t = useTranslate()
|
||||
const mountedRef = useMountedRef()
|
||||
const settings = useAppStore((s) => s.settings)
|
||||
const [state, setState] = useState<CornerCardState>({ stage: 'prompt' })
|
||||
@@ -51,7 +52,7 @@ export function ChatUiHereCornerCard({ onClose }: { onClose: () => void }): JSX.
|
||||
if (state.stage === 'switched') {
|
||||
return <ChatUiSwitchedCornerNote onClose={onClose} focusSettings={state.focusSettings} />
|
||||
}
|
||||
const { paragraphs, secondaryLabel, primaryLabel } = getChatUiHereVariantCopy('chat-ui-off')
|
||||
const { paragraphs, secondaryLabel, primaryLabel } = getChatUiHereVariantCopy('chat-ui-off', t)
|
||||
const [intro = ''] = paragraphs
|
||||
|
||||
const handleUseChatUi = async (event: MouseEvent<HTMLButtonElement>): Promise<void> => {
|
||||
@@ -95,13 +96,13 @@ export function ChatUiHereCornerCard({ onClose }: { onClose: () => void }): JSX.
|
||||
<CardHeader>
|
||||
<div className="flex items-center justify-between gap-2">
|
||||
<CardTitle id="chat-ui-here-corner-heading">
|
||||
{translate('featureTips.chatUiHere.title', 'Chat UI is here')}
|
||||
{t('featureTips.chatUiHere.title', 'Chat UI is here')}
|
||||
</CardTitle>
|
||||
<Button
|
||||
variant="ghost"
|
||||
size="icon-xs"
|
||||
onClick={handleDismiss}
|
||||
aria-label={translate('featureTips.chatUiHere.dismiss', 'Dismiss')}
|
||||
aria-label={t('featureTips.chatUiHere.dismiss', 'Dismiss')}
|
||||
>
|
||||
<X className="size-3.5" />
|
||||
</Button>
|
||||
@@ -121,7 +122,7 @@ export function ChatUiHereCornerCard({ onClose }: { onClose: () => void }): JSX.
|
||||
<span className="block space-y-2">
|
||||
<span className="block">{intro}</span>
|
||||
<FeatureTipSettingsLine
|
||||
text={translate(
|
||||
text={t(
|
||||
'featureTips.chatUiHere.changeInSettings',
|
||||
'Change anytime in <link>Settings → Chat UI</link>.'
|
||||
)}
|
||||
|
||||
@@ -4,7 +4,7 @@ import { LoadingSpinner } from '@/components/LoadingSpinner'
|
||||
import { usePrefersReducedMotion } from '@/components/feature-wall/feature-wall-modal-helpers'
|
||||
import { nativeChatPermissionModeLabel } from '@/components/native-chat/session/options/controls/native-chat-permission-mode-labels'
|
||||
import { nativeChatToolRowVerb } from '@/components/native-chat/tools/lines/native-chat-tool-line-label'
|
||||
import { translate } from '@/i18n/i18n'
|
||||
import { useIntlLocale, useTranslate } from '@/i18n/use-translate'
|
||||
import { AgentIcon } from '@/lib/agent-catalog'
|
||||
import { cn } from '@/lib/utils'
|
||||
import {
|
||||
@@ -81,6 +81,7 @@ export function FlowDiagram({
|
||||
}
|
||||
|
||||
function Transcript({ copy, frame }: { copy: ChatDemoCopy; frame: ChatDemoFrame }): JSX.Element {
|
||||
const t = useTranslate()
|
||||
return (
|
||||
<div
|
||||
className="flex min-h-full shrink-0 flex-col transition-opacity duration-700 ease-in-out"
|
||||
@@ -104,7 +105,7 @@ function Transcript({ copy, frame }: { copy: ChatDemoCopy; frame: ChatDemoFrame
|
||||
<span className="flex size-4 items-center justify-center text-chat-foreground-faint">
|
||||
<Eye className="size-3.5" />
|
||||
</span>
|
||||
<span className="text-chat-foreground-faint">{nativeChatToolRowVerb('read')}</span>
|
||||
<span className="text-chat-foreground-faint">{nativeChatToolRowVerb('read', t)}</span>
|
||||
<span>{CHAT_DEMO_FILE}</span>
|
||||
<ChevronRight className="ml-auto size-3.5 text-chat-foreground-faint" />
|
||||
</div>
|
||||
@@ -113,7 +114,7 @@ function Transcript({ copy, frame }: { copy: ChatDemoCopy; frame: ChatDemoFrame
|
||||
<div className="mt-3 flex min-h-6 items-center gap-1.5 text-chat-foreground-faint">
|
||||
<LoadingSpinner className="size-4 shrink-0" />
|
||||
<span className="text-chat-foreground">
|
||||
{translate('components.native-chat.status.working', 'Working…')}
|
||||
{t('components.native-chat.status.working', 'Working…')}
|
||||
</span>
|
||||
</div>
|
||||
</Row>
|
||||
@@ -153,6 +154,7 @@ function Transcript({ copy, frame }: { copy: ChatDemoCopy; frame: ChatDemoFrame
|
||||
}
|
||||
|
||||
function Composer({ prompt, frame }: { prompt: string; frame: ChatDemoFrame }): JSX.Element {
|
||||
const t = useTranslate()
|
||||
const typing = frame.typed > 0
|
||||
return (
|
||||
<div className="shrink-0 px-3 pt-1.5 pb-3">
|
||||
@@ -165,7 +167,7 @@ function Composer({ prompt, frame }: { prompt: string; frame: ChatDemoFrame }):
|
||||
</span>
|
||||
) : (
|
||||
<span className="text-chat-foreground-faint">
|
||||
{translate(
|
||||
{t(
|
||||
'components.native-chat.composer.placeholder',
|
||||
'Ask anything, @ to mention files, / for commands'
|
||||
)}
|
||||
@@ -179,7 +181,7 @@ function Composer({ prompt, frame }: { prompt: string; frame: ChatDemoFrame }):
|
||||
</span>
|
||||
<span className="flex h-5.5 min-w-0 items-center gap-1 px-1.5 text-xs font-medium whitespace-nowrap">
|
||||
<Hand className="size-3 shrink-0" />
|
||||
<span className="truncate">{nativeChatPermissionModeLabel('ask')}</span>
|
||||
<span className="truncate">{nativeChatPermissionModeLabel('ask', t)}</span>
|
||||
<ChevronDown className="size-3 shrink-0" />
|
||||
</span>
|
||||
</div>
|
||||
@@ -224,8 +226,10 @@ export function ChatUiDemoCard({
|
||||
loop?: boolean
|
||||
className?: string
|
||||
}): JSX.Element {
|
||||
const t = useTranslate()
|
||||
const intlLocale = useIntlLocale()
|
||||
const reducedMotion = usePrefersReducedMotion()
|
||||
const copy = getChatDemoCopy()
|
||||
const copy = getChatDemoCopy(t, intlLocale)
|
||||
const promptLength = Array.from(copy.prompt).length
|
||||
const timeline = useMemo(
|
||||
() => createChatDemoTimeline(promptLength, copy.reply.length),
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
import { useRef, type JSX } from 'react'
|
||||
import type { FeatureTip } from '../../../../shared/feature-tips'
|
||||
import { DialogDescription, DialogFooter, DialogHeader, DialogTitle } from '@/components/ui/dialog'
|
||||
import { translate } from '@/i18n/i18n'
|
||||
import { useTranslate } from '@/i18n/use-translate'
|
||||
import { ChatUiHereFeatureTipVisual } from './ChatUiHereFeatureTipVisual'
|
||||
import { getChatUiHereVariantCopy, type ShownChatUiLaunchTipVariant } from './chat-ui-here-copy'
|
||||
import { FeatureTipActions } from './FeatureTipActions'
|
||||
@@ -34,8 +34,9 @@ export function ChatUiHereTipDialog({
|
||||
onSkip: () => void
|
||||
onSettingsClick: () => void
|
||||
}): JSX.Element {
|
||||
const t = useTranslate()
|
||||
const primaryButtonRef = useRef<HTMLButtonElement>(null)
|
||||
const { paragraphs, secondaryLabel, primaryLabel } = getChatUiHereVariantCopy(variant)
|
||||
const { paragraphs, secondaryLabel, primaryLabel } = getChatUiHereVariantCopy(variant, t)
|
||||
|
||||
return (
|
||||
<FeatureTipDialogFrame
|
||||
@@ -51,9 +52,9 @@ export function ChatUiHereTipDialog({
|
||||
>
|
||||
<DialogHeader className="text-left">
|
||||
<div>
|
||||
<FeatureTipEyebrow label={translate('featureTips.eyebrowNew', 'New')} />
|
||||
<FeatureTipEyebrow label={t('featureTips.eyebrowNew', 'New')} />
|
||||
<DialogTitle size="display">
|
||||
{translate('featureTips.chatUiHere.title', 'Chat UI is here')}
|
||||
{t('featureTips.chatUiHere.title', 'Chat UI is here')}
|
||||
</DialogTitle>
|
||||
<DialogDescription className="mt-3 max-w-2xl">
|
||||
<span className="block space-y-3 leading-relaxed">
|
||||
@@ -63,7 +64,7 @@ export function ChatUiHereTipDialog({
|
||||
</span>
|
||||
))}
|
||||
<FeatureTipSettingsLine
|
||||
text={translate(
|
||||
text={t(
|
||||
'featureTips.chatUiHere.changeInSettings',
|
||||
'Change anytime in <link>Settings → Chat UI</link>.'
|
||||
)}
|
||||
|
||||
@@ -3,7 +3,8 @@ import { Trans } from 'react-i18next'
|
||||
import { Check, X } from 'lucide-react'
|
||||
import { Button } from '@/components/ui/button'
|
||||
import { Card, CardContent, CardDescription, CardHeader, CardTitle } from '@/components/ui/card'
|
||||
import { i18n, translate } from '@/i18n/i18n'
|
||||
import { i18n } from '@/i18n/i18n'
|
||||
import { useTranslate } from '@/i18n/use-translate'
|
||||
import { openChatUiFeatureTipSettings } from './chat-ui-here-actions'
|
||||
|
||||
const AUTO_DISMISS_MS = 12_000
|
||||
@@ -15,6 +16,7 @@ export function ChatUiSwitchedCornerNote({
|
||||
onClose: () => void
|
||||
focusSettings?: boolean
|
||||
}): JSX.Element {
|
||||
const t = useTranslate()
|
||||
const settingsButtonRef = useRef<HTMLButtonElement>(null)
|
||||
|
||||
useLayoutEffect(() => {
|
||||
@@ -63,17 +65,14 @@ export function ChatUiSwitchedCornerNote({
|
||||
<Check className="size-3" />
|
||||
</span>
|
||||
<CardTitle id="chat-ui-switched-note-heading">
|
||||
{translate(
|
||||
'featureTips.chatUiHere.switchedNoteTitle',
|
||||
'New agents will open in Chat UI'
|
||||
)}
|
||||
{t('featureTips.chatUiHere.switchedNoteTitle', 'New agents will open in Chat UI')}
|
||||
</CardTitle>
|
||||
</div>
|
||||
<Button
|
||||
variant="ghost"
|
||||
size="icon-xs"
|
||||
onClick={onClose}
|
||||
aria-label={translate('featureTips.chatUiHere.dismiss', 'Dismiss')}
|
||||
aria-label={t('featureTips.chatUiHere.dismiss', 'Dismiss')}
|
||||
>
|
||||
<X className="size-3.5" />
|
||||
</Button>
|
||||
@@ -84,7 +83,7 @@ export function ChatUiSwitchedCornerNote({
|
||||
<CardDescription>
|
||||
<Trans
|
||||
i18n={i18n}
|
||||
defaults={translate(
|
||||
defaults={t(
|
||||
'featureTips.chatUiHere.switchedNoteBody',
|
||||
'Prefer the terminal? Switch back anytime: search <term>Chat UI</term> in Settings.'
|
||||
)}
|
||||
@@ -93,7 +92,7 @@ export function ChatUiSwitchedCornerNote({
|
||||
</CardDescription>
|
||||
<div className="flex items-center gap-2">
|
||||
<Button variant="ghost" size="sm" onClick={onClose}>
|
||||
{translate('featureTips.gotIt', 'Got it')}
|
||||
{t('featureTips.gotIt', 'Got it')}
|
||||
</Button>
|
||||
<Button
|
||||
ref={settingsButtonRef}
|
||||
@@ -102,7 +101,7 @@ export function ChatUiSwitchedCornerNote({
|
||||
className="flex-1"
|
||||
onClick={handleOpenSettings}
|
||||
>
|
||||
{translate('featureTips.chatUiHere.openSettings', 'Open Settings')}
|
||||
{t('featureTips.chatUiHere.openSettings', 'Open Settings')}
|
||||
</Button>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
@@ -5,7 +5,7 @@ import {
|
||||
ORCHESTRATION_CLI_COMMAND_TIMINGS_MS
|
||||
} from '@/components/feature-wall/agents-orchestration/orchestration-types'
|
||||
import { usePrefersReducedMotion } from '@/components/feature-wall/feature-wall-modal-helpers'
|
||||
import { translate } from '@/i18n/i18n'
|
||||
import { useTranslate } from '@/i18n/use-translate'
|
||||
|
||||
const CLI_AGENT_COMMANDS = [
|
||||
'orca worktree create --name auth-pr-1',
|
||||
@@ -15,6 +15,7 @@ const CLI_AGENT_COMMANDS = [
|
||||
]
|
||||
|
||||
export function CliFeatureTipVisual(): JSX.Element {
|
||||
const t = useTranslate()
|
||||
const reducedMotion = usePrefersReducedMotion()
|
||||
const [animatedVisibleCommandCount, setAnimatedVisibleCommandCount] = useState(0)
|
||||
// Show the reduced-motion end state directly; only animation needs timer-backed state.
|
||||
@@ -63,7 +64,7 @@ export function CliFeatureTipVisual(): JSX.Element {
|
||||
<div className="space-y-1.5 px-3 py-3 font-mono text-[10.5px] leading-[1.35] text-foreground">
|
||||
<div className="truncate text-muted-foreground">
|
||||
<span className="mr-1.5 text-foreground">●</span>
|
||||
{translate(
|
||||
{t(
|
||||
'auto.components.feature.tips.CliFeatureTipVisual.22e62f3bab',
|
||||
'Claude Code session started'
|
||||
)}
|
||||
@@ -77,7 +78,7 @@ export function CliFeatureTipVisual(): JSX.Element {
|
||||
className={`truncate ${isVisible ? 'animate-cli-tip-command-line' : 'invisible'}`}
|
||||
>
|
||||
<span className="text-foreground">
|
||||
{translate('auto.components.feature.tips.CliFeatureTipVisual.badb4fc342', '>')}
|
||||
{t('auto.components.feature.tips.CliFeatureTipVisual.badb4fc342', '>')}
|
||||
</span>
|
||||
<span>{command}</span>
|
||||
{isCurrentLine ? (
|
||||
|
||||
@@ -9,7 +9,8 @@ import {
|
||||
DialogHeader,
|
||||
DialogTitle
|
||||
} from '@/components/ui/dialog'
|
||||
import { i18n, translate } from '@/i18n/i18n'
|
||||
import { i18n } from '@/i18n/i18n'
|
||||
import { useTranslate } from '@/i18n/use-translate'
|
||||
import { Trans } from 'react-i18next'
|
||||
import { CliFeatureTipVisual } from './CliFeatureTipVisual'
|
||||
import { CliSkillSetupPanel } from './CliSkillSetupPanel'
|
||||
@@ -41,6 +42,7 @@ export function CliSetupTipDialog({
|
||||
onPrimaryAction: () => void
|
||||
onSkip: () => void
|
||||
}): JSX.Element {
|
||||
const t = useTranslate()
|
||||
const copy = getLocalizedFeatureTipCopy(tip)
|
||||
return (
|
||||
<Dialog open={open} onOpenChange={onOpenChange}>
|
||||
@@ -75,15 +77,12 @@ export function CliSetupTipDialog({
|
||||
}`}
|
||||
>
|
||||
<p className="font-medium text-foreground">
|
||||
{translate(
|
||||
'auto.components.feature.tips.FeatureTipsModal.4795ac2d4a',
|
||||
'Try asking:'
|
||||
)}
|
||||
{t('auto.components.feature.tips.FeatureTipsModal.4795ac2d4a', 'Try asking:')}
|
||||
</p>
|
||||
<p>
|
||||
<Trans
|
||||
i18n={i18n}
|
||||
defaults={translate(
|
||||
defaults={t(
|
||||
'auto.components.feature.tips.FeatureTipsModal.splitPrPrompt',
|
||||
'“Split this PR into two <term>worktrees</term> and create PRs for each.”'
|
||||
)}
|
||||
@@ -91,17 +90,14 @@ export function CliSetupTipDialog({
|
||||
/>
|
||||
</p>
|
||||
<p>
|
||||
{translate(
|
||||
{t(
|
||||
'auto.components.feature.tips.FeatureTipsModal.864e2db28f',
|
||||
'“When the agent in'
|
||||
)}
|
||||
<WorktreePromptTerm>
|
||||
{translate(
|
||||
'auto.components.feature.tips.FeatureTipsModal.298301b7a0',
|
||||
'worktree'
|
||||
)}
|
||||
{t('auto.components.feature.tips.FeatureTipsModal.298301b7a0', 'worktree')}
|
||||
</WorktreePromptTerm>{' '}
|
||||
{translate(
|
||||
{t(
|
||||
'auto.components.feature.tips.FeatureTipsModal.3c6c478462',
|
||||
'X finishes, send it the review task.”'
|
||||
)}
|
||||
@@ -114,7 +110,7 @@ export function CliSetupTipDialog({
|
||||
<DialogFooter className="mt-8 flex sm:justify-stretch">
|
||||
{skillTerminalOpen ? (
|
||||
<Button className="w-full" onClick={onSkip}>
|
||||
{translate('auto.components.feature.tips.FeatureTipsModal.c169298e4d', 'Done')}
|
||||
{t('auto.components.feature.tips.FeatureTipsModal.c169298e4d', 'Done')}
|
||||
</Button>
|
||||
) : (
|
||||
<FeatureTipActions
|
||||
|
||||
@@ -7,11 +7,12 @@ import {
|
||||
hasInstalledAgentSkill,
|
||||
useInstalledAgentSkillNames
|
||||
} from '@/hooks/useInstalledAgentSkills'
|
||||
import { translate } from '@/i18n/i18n'
|
||||
import { useTranslate } from '@/i18n/use-translate'
|
||||
|
||||
const CLI_TIP_SKILL_NAMES = [ORCA_CLI_SKILL_NAME, ORCHESTRATION_SKILL_NAME] as const
|
||||
|
||||
function BundledCliSkillSetupPanel(): React.JSX.Element {
|
||||
const t = useTranslate()
|
||||
const activeSkillRuntime = useActiveProjectSkillRuntime()
|
||||
// Why: a repair-required runtime has no distro to scan; Install stays disabled for it.
|
||||
const agentRuntime = activeSkillRuntime.installDisabledReason
|
||||
@@ -30,8 +31,8 @@ function BundledCliSkillSetupPanel(): React.JSX.Element {
|
||||
variant="inline"
|
||||
hideHeader
|
||||
className="min-w-0"
|
||||
title={translate('auto.components.feature.tips.CliSkillSetupPanel.title', 'Skill setup')}
|
||||
description={translate(
|
||||
title={t('auto.components.feature.tips.CliSkillSetupPanel.title', 'Skill setup')}
|
||||
description={t(
|
||||
'auto.components.feature.tips.CliSkillSetupPanel.description',
|
||||
'Install the Orca CLI and orchestration skills for your agents.'
|
||||
)}
|
||||
|
||||
@@ -10,9 +10,10 @@ import {
|
||||
} from '@/lib/skill-runtime-command'
|
||||
import { ORCA_CLI_ORCHESTRATION_SKILL_INSTALL_COMMAND } from '@/lib/agent-feature-install-commands'
|
||||
import { useActiveProjectSkillRuntime } from '@/hooks/useActiveProjectSkillRuntime'
|
||||
import { translate } from '@/i18n/i18n'
|
||||
import { useTranslate } from '@/i18n/use-translate'
|
||||
|
||||
export function CliSkillSetupTerminal(): React.JSX.Element {
|
||||
const t = useTranslate()
|
||||
const activeSkillRuntime = useActiveProjectSkillRuntime()
|
||||
// Missing WSL distros fall back to the Windows host; this ungated auto-paste still needs npx preflight.
|
||||
const skillCommand = buildSkillCommandForRuntime(
|
||||
@@ -28,7 +29,7 @@ export function CliSkillSetupTerminal(): React.JSX.Element {
|
||||
try {
|
||||
await window.api.ui.writeClipboardText(skillCommand)
|
||||
toast.success(
|
||||
translate(
|
||||
t(
|
||||
'auto.components.feature.tips.CliSkillSetupTerminal.b8ad063571',
|
||||
'Copied the skill install command.'
|
||||
)
|
||||
@@ -37,7 +38,7 @@ export function CliSkillSetupTerminal(): React.JSX.Element {
|
||||
toast.error(
|
||||
error instanceof Error
|
||||
? error.message
|
||||
: translate(
|
||||
: t(
|
||||
'auto.components.feature.tips.CliSkillSetupTerminal.6ff813fc1d',
|
||||
'Failed to copy skill command.'
|
||||
)
|
||||
@@ -58,7 +59,7 @@ export function CliSkillSetupTerminal(): React.JSX.Element {
|
||||
size="icon-sm"
|
||||
className="shrink-0"
|
||||
onClick={() => void handleCopySkillCommand()}
|
||||
aria-label={translate(
|
||||
aria-label={t(
|
||||
'auto.components.feature.tips.CliSkillSetupTerminal.5eca672aac',
|
||||
'Copy skill install command'
|
||||
)}
|
||||
@@ -67,25 +68,19 @@ export function CliSkillSetupTerminal(): React.JSX.Element {
|
||||
</Button>
|
||||
</TooltipTrigger>
|
||||
<TooltipContent side="top" sideOffset={4}>
|
||||
{translate(
|
||||
'auto.components.feature.tips.CliSkillSetupTerminal.5c3aee22c0',
|
||||
'Copy command'
|
||||
)}
|
||||
{t('auto.components.feature.tips.CliSkillSetupTerminal.5c3aee22c0', 'Copy command')}
|
||||
</TooltipContent>
|
||||
</Tooltip>
|
||||
</div>
|
||||
<OnboardingInlineCommandTerminal
|
||||
command={skillCommand}
|
||||
prepareCommandForShell={prepareCommandForShell}
|
||||
title={translate(
|
||||
'auto.components.feature.tips.CliSkillSetupTerminal.84e9576dac',
|
||||
'Skill setup'
|
||||
)}
|
||||
ariaLabel={translate(
|
||||
title={t('auto.components.feature.tips.CliSkillSetupTerminal.84e9576dac', 'Skill setup')}
|
||||
ariaLabel={t(
|
||||
'auto.components.feature.tips.CliSkillSetupTerminal.43b60ec5c3',
|
||||
'Orca CLI and orchestration skill install terminal'
|
||||
)}
|
||||
description={translate(
|
||||
description={t(
|
||||
'auto.components.feature.tips.CliSkillSetupTerminal.1953e90447',
|
||||
'Press Enter to install the Orca CLI orchestration skill for your agents.'
|
||||
)}
|
||||
|
||||
@@ -2,7 +2,7 @@ import { Fragment, useEffect, useState, type JSX } from 'react'
|
||||
import { Plus, Search } from 'lucide-react'
|
||||
import { usePrefersReducedMotion } from '@/components/feature-wall/feature-wall-modal-helpers'
|
||||
import { formatShortcutKeyComboDetails, useShortcutKeyDetails } from '@/hooks/useShortcutLabel'
|
||||
import { translate } from '@/i18n/i18n'
|
||||
import { useTranslate } from '@/i18n/use-translate'
|
||||
|
||||
const TYPED_QUERY = 'auth'
|
||||
// Mirror the real palette’s recent worktrees before typing narrows them.
|
||||
@@ -40,6 +40,7 @@ const HOLD_BEFORE_TYPING_MS = 700
|
||||
const TYPE_INTERVAL_MS = 120
|
||||
|
||||
export function CmdJPaletteFeatureTipVisual(): JSX.Element {
|
||||
const t = useTranslate()
|
||||
const reducedMotion = usePrefersReducedMotion()
|
||||
// Read the live binding to reflect rebindings and platform defaults.
|
||||
const shortcut = useShortcutKeyDetails('worktree.palette')
|
||||
@@ -200,7 +201,7 @@ export function CmdJPaletteFeatureTipVisual(): JSX.Element {
|
||||
<Plus size={13} aria-hidden="true" />
|
||||
</div>
|
||||
<div className="min-w-0 flex-1 truncate text-[12.5px] font-semibold tracking-[-0.01em] text-foreground">
|
||||
{translate(
|
||||
{t(
|
||||
'auto.components.feature.tips.CmdJPaletteFeatureTipVisual.ab94e16d44',
|
||||
'Create worktree "{{value0}}"',
|
||||
{ value0: renderQuery.trim() }
|
||||
|
||||
@@ -10,7 +10,7 @@ import {
|
||||
FeatureTipEyebrow,
|
||||
FeatureTipSettingsLine
|
||||
} from './FeatureTipDialogFrame'
|
||||
import { translate } from '@/i18n/i18n'
|
||||
import { useTranslate } from '@/i18n/use-translate'
|
||||
|
||||
export function CmdJPaletteTipDialog({
|
||||
open,
|
||||
@@ -29,6 +29,7 @@ export function CmdJPaletteTipDialog({
|
||||
onSkip: () => void
|
||||
onRebindClick: () => void
|
||||
}): JSX.Element {
|
||||
const t = useTranslate()
|
||||
// Use the live binding for rebindings and platform-specific shortcuts.
|
||||
const worktreePaletteShortcutLabel = useShortcutLabel('worktree.palette')
|
||||
const displayShortcutLabel =
|
||||
@@ -69,7 +70,7 @@ export function CmdJPaletteTipDialog({
|
||||
<DialogDescription className="mt-3 max-w-2xl space-y-3 text-sm leading-relaxed">
|
||||
<span className="block">{copy.description}</span>
|
||||
<FeatureTipSettingsLine
|
||||
text={translate(
|
||||
text={t(
|
||||
'auto.components.feature.tips.CmdJPaletteTipDialog.rebindInSettings',
|
||||
'Rebind the shortcut anytime in <link>Settings → Shortcuts</link>.'
|
||||
)}
|
||||
|
||||
@@ -3,16 +3,17 @@ import { Loader2 } from 'lucide-react'
|
||||
import type { FeatureTip, FeatureTipAction } from '../../../../shared/feature-tips'
|
||||
import { Button } from '@/components/ui/button'
|
||||
import { translate } from '@/i18n/i18n'
|
||||
import { useTranslate, type Translate } from '@/i18n/use-translate'
|
||||
import { getLocalizedFeatureTipCopy } from './feature-tip-localized-copy'
|
||||
|
||||
function getPrimaryBusyLabel(action: FeatureTipAction): string {
|
||||
function getPrimaryBusyLabel(action: FeatureTipAction, t: Translate = translate): string {
|
||||
if (action === 'setup-cli') {
|
||||
return translate('auto.components.feature.tips.FeatureTipActions.installing', 'Installing...')
|
||||
return t('auto.components.feature.tips.FeatureTipActions.installing', 'Installing...')
|
||||
}
|
||||
if (action === 'enable-session-search') {
|
||||
return translate('auto.components.feature.tips.FeatureTipActions.turningOn', 'Turning on...')
|
||||
return t('auto.components.feature.tips.FeatureTipActions.turningOn', 'Turning on...')
|
||||
}
|
||||
return translate('auto.components.feature.tips.FeatureTipActions.working', 'Working...')
|
||||
return t('auto.components.feature.tips.FeatureTipActions.working', 'Working...')
|
||||
}
|
||||
|
||||
export function FeatureTipActions({
|
||||
@@ -41,6 +42,7 @@ export function FeatureTipActions({
|
||||
/** Overrides the secondary button's default "Maybe Later". */
|
||||
secondaryLabel?: string
|
||||
}): JSX.Element {
|
||||
const t = useTranslate()
|
||||
const skipButton = showSkip ? (
|
||||
<Button
|
||||
ref={swapButtons ? primaryButtonRef : undefined}
|
||||
@@ -50,7 +52,7 @@ export function FeatureTipActions({
|
||||
disabled={primaryBusy}
|
||||
>
|
||||
{secondaryLabel ??
|
||||
translate('auto.components.feature.tips.FeatureTipActions.eb04abece8', 'Maybe Later')}
|
||||
t('auto.components.feature.tips.FeatureTipActions.eb04abece8', 'Maybe Later')}
|
||||
</Button>
|
||||
) : null
|
||||
|
||||
@@ -67,7 +69,7 @@ export function FeatureTipActions({
|
||||
{primaryBusy ? (
|
||||
<>
|
||||
<Loader2 className="size-4 animate-spin" />
|
||||
{getPrimaryBusyLabel(currentTip.action)}
|
||||
{getPrimaryBusyLabel(currentTip.action, t)}
|
||||
</>
|
||||
) : (
|
||||
(label ?? getLocalizedFeatureTipCopy(currentTip).ctaLabel)
|
||||
|
||||
@@ -25,12 +25,13 @@ import {
|
||||
} from './feature-tip-telemetry'
|
||||
import { useMountedRef } from '@/hooks/useMountedRef'
|
||||
import { isWebClientLocation } from '@/lib/web-client-location'
|
||||
import { translate } from '@/i18n/i18n'
|
||||
import { useTranslate } from '@/i18n/use-translate'
|
||||
import { SessionSearchTipDialog } from './SessionSearchTipDialog'
|
||||
import { useSessionSearchTipSetup } from './use-session-search-tip-setup'
|
||||
import { VoiceDictationTipDialog } from './VoiceDictationTipDialog'
|
||||
|
||||
export default function FeatureTipsModal(): JSX.Element | null {
|
||||
const t = useTranslate()
|
||||
const activeModal = useAppStore((s) => s.activeModal)
|
||||
const closeModal = useAppStore((s) => s.closeModal)
|
||||
const openSettingsPage = useAppStore((s) => s.openSettingsPage)
|
||||
@@ -213,7 +214,7 @@ export default function FeatureTipsModal(): JSX.Element | null {
|
||||
}
|
||||
enableOrchestrationSkillSetup()
|
||||
toast.success(
|
||||
translate(
|
||||
t(
|
||||
'auto.components.feature.tips.FeatureTipsModal.ce13a742d0',
|
||||
'Registered `orca` in PATH.'
|
||||
)
|
||||
@@ -227,14 +228,14 @@ export default function FeatureTipsModal(): JSX.Element | null {
|
||||
return
|
||||
}
|
||||
toast.warning(
|
||||
translate(
|
||||
t(
|
||||
'auto.components.feature.tips.FeatureTipsModal.1da82af45b',
|
||||
'Orca CLI needs attention'
|
||||
),
|
||||
{
|
||||
description:
|
||||
result.status.detail ??
|
||||
translate(
|
||||
t(
|
||||
'auto.components.feature.tips.FeatureTipsModal.d1a86c7eb5',
|
||||
'Open Settings to finish CLI setup.'
|
||||
)
|
||||
@@ -246,7 +247,7 @@ export default function FeatureTipsModal(): JSX.Element | null {
|
||||
const message =
|
||||
error instanceof Error
|
||||
? error.message
|
||||
: translate(
|
||||
: t(
|
||||
'auto.components.feature.tips.FeatureTipsModal.cliInstallFailed',
|
||||
'Failed to install Orca CLI.'
|
||||
)
|
||||
@@ -260,7 +261,7 @@ export default function FeatureTipsModal(): JSX.Element | null {
|
||||
}
|
||||
enableOrchestrationSkillSetup()
|
||||
toast.info(
|
||||
translate(
|
||||
t(
|
||||
'auto.components.feature.tips.FeatureTipsModal.53905bd076',
|
||||
'Development preview: opening skills setup terminal.'
|
||||
)
|
||||
|
||||
@@ -5,6 +5,7 @@ import {
|
||||
getSessionSearchDemoRecentRows,
|
||||
getSessionSearchDemoScenes
|
||||
} from './session-search-feature-tip-demo'
|
||||
import { useTranslate } from '@/i18n/use-translate'
|
||||
|
||||
type DemoPhase = 'idle' | 'typing' | 'searching' | 'results' | 'focused' | 'erasing'
|
||||
type DemoState = { scene: number; phase: DemoPhase; chars: number }
|
||||
@@ -40,9 +41,10 @@ function nextDemoState(state: DemoState, queryLength: number, sceneCount: number
|
||||
}
|
||||
|
||||
export function SessionSearchFeatureTipVisual(): JSX.Element {
|
||||
const t = useTranslate()
|
||||
const reducedMotion = usePrefersReducedMotion()
|
||||
const scenes = getSessionSearchDemoScenes()
|
||||
const recentRows = getSessionSearchDemoRecentRows()
|
||||
const scenes = getSessionSearchDemoScenes(t)
|
||||
const recentRows = getSessionSearchDemoRecentRows(t)
|
||||
const [state, setState] = useState<DemoState>({ scene: 0, phase: 'idle', chars: 0 })
|
||||
const shown: DemoState = reducedMotion
|
||||
? { scene: 0, phase: 'results', chars: scenes[0].query.length }
|
||||
|
||||
@@ -7,6 +7,7 @@ import {
|
||||
} from '@/components/settings/session-history/session-history-status-copy'
|
||||
import { DialogDescription, DialogFooter, DialogHeader, DialogTitle } from '@/components/ui/dialog'
|
||||
import { translate } from '@/i18n/i18n'
|
||||
import { useIntlLocale, useTranslate, type Translate } from '@/i18n/use-translate'
|
||||
import { FeatureTipActions } from './FeatureTipActions'
|
||||
import { getLocalizedFeatureTipCopy } from './feature-tip-localized-copy'
|
||||
import {
|
||||
@@ -19,7 +20,8 @@ import type { SessionSearchTipStage } from './use-session-search-tip-setup'
|
||||
|
||||
function stageCopy(
|
||||
tip: FeatureTip,
|
||||
stage: SessionSearchTipStage
|
||||
stage: SessionSearchTipStage,
|
||||
t: Translate = translate
|
||||
): { title: string; description: string; cta: string } {
|
||||
switch (stage) {
|
||||
case 'offer': {
|
||||
@@ -28,31 +30,35 @@ function stageCopy(
|
||||
}
|
||||
case 'indexing':
|
||||
return {
|
||||
title: translate('featureTips.sessionSearch.indexingTitle', 'Indexing your agent sessions'),
|
||||
description: translate(
|
||||
title: t('featureTips.sessionSearch.indexingTitle', 'Indexing your agent sessions'),
|
||||
description: t(
|
||||
'featureTips.sessionSearch.indexingDescription',
|
||||
'Orca is reading your past agent transcripts so you can search them. This can take a few minutes.'
|
||||
),
|
||||
cta: translate('featureTips.sessionSearch.continueInBackground', 'Continue in background')
|
||||
cta: t('featureTips.sessionSearch.continueInBackground', 'Continue in background')
|
||||
}
|
||||
case 'ready':
|
||||
return {
|
||||
title: translate('featureTips.sessionSearch.readyTitle', 'Session search is ready'),
|
||||
description: translate(
|
||||
title: t('featureTips.sessionSearch.readyTitle', 'Session search is ready'),
|
||||
description: t(
|
||||
'featureTips.sessionSearch.readyDescription',
|
||||
'Every agent session on this computer can now be found by what was said in it.'
|
||||
),
|
||||
cta: translate('featureTips.sessionSearch.startSearching', 'Start searching')
|
||||
cta: t('featureTips.sessionSearch.startSearching', 'Start searching')
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
function progressMessage(status: AiVaultSearchStatus | null): string {
|
||||
function progressMessage(
|
||||
status: AiVaultSearchStatus | null,
|
||||
t: Translate = translate,
|
||||
intlLocale?: string
|
||||
): string {
|
||||
// Why: right after turning on, the indexer may not have started; the shared copy would call that unavailable.
|
||||
if (!status?.enabled || status.phase === 'idle' || status.phase === 'closed') {
|
||||
return translate('featureTips.sessionSearch.progressStarting', 'Finding your sessions…')
|
||||
return t('featureTips.sessionSearch.progressStarting', 'Finding your sessions…')
|
||||
}
|
||||
return sessionSearchStatusMessage(status)
|
||||
return sessionSearchStatusMessage(status, t, intlLocale)
|
||||
}
|
||||
|
||||
function IndexProgress({
|
||||
@@ -62,6 +68,8 @@ function IndexProgress({
|
||||
indexing: boolean
|
||||
status: AiVaultSearchStatus | null
|
||||
}): JSX.Element {
|
||||
const t = useTranslate()
|
||||
const intlLocale = useIntlLocale()
|
||||
return (
|
||||
<span className="block space-y-2" role="status" aria-live="polite">
|
||||
{indexing ? (
|
||||
@@ -71,9 +79,9 @@ function IndexProgress({
|
||||
</span>
|
||||
) : null}
|
||||
<span className="block text-xs font-medium tabular-nums text-foreground">
|
||||
{progressMessage(status)}
|
||||
{progressMessage(status, t, intlLocale)}
|
||||
</span>
|
||||
{sessionSearchStatusDetails(status).map((line) => (
|
||||
{sessionSearchStatusDetails(status, t).map((line) => (
|
||||
<span key={line} className="block text-xs text-muted-foreground">
|
||||
{line}
|
||||
</span>
|
||||
@@ -101,8 +109,9 @@ export function SessionSearchTipDialog({
|
||||
stage: SessionSearchTipStage
|
||||
status: AiVaultSearchStatus | null
|
||||
}): JSX.Element {
|
||||
const t = useTranslate()
|
||||
const primaryButtonRef = useRef<HTMLButtonElement>(null)
|
||||
const copy = stageCopy(tip, stage)
|
||||
const copy = stageCopy(tip, stage, t)
|
||||
|
||||
return (
|
||||
<FeatureTipDialogFrame
|
||||
@@ -124,7 +133,7 @@ export function SessionSearchTipDialog({
|
||||
<span className="block">{copy.description}</span>
|
||||
{stage === 'offer' ? (
|
||||
<span className="block">
|
||||
{translate(
|
||||
{t(
|
||||
'featureTips.sessionSearch.agentsInstruction',
|
||||
'Your agents can search it too. Ask one to “find the session where we fixed the login timeout.”'
|
||||
)}
|
||||
@@ -133,7 +142,7 @@ export function SessionSearchTipDialog({
|
||||
<IndexProgress indexing={stage === 'indexing'} status={status} />
|
||||
)}
|
||||
<FeatureTipSettingsLine
|
||||
text={translate(
|
||||
text={t(
|
||||
'featureTips.sessionSearch.changeInSettings',
|
||||
'Change or turn it off anytime in <link>Settings → Agent Session Search</link>.'
|
||||
)}
|
||||
|
||||
@@ -3,16 +3,17 @@ import { Mic, Square } from 'lucide-react'
|
||||
import { ShortcutKeyCombo } from '@/components/ShortcutKeyCombo'
|
||||
import { usePrefersReducedMotion } from '@/components/feature-wall/feature-wall-modal-helpers'
|
||||
import { useShortcutKeyDetails } from '@/hooks/useShortcutLabel'
|
||||
import { translate } from '@/i18n/i18n'
|
||||
import { useTranslate } from '@/i18n/use-translate'
|
||||
|
||||
const TYPE_START_DELAY_MS = 650
|
||||
const TYPE_INTERVAL_MS = 36
|
||||
const WAVEFORM_BAR_HEIGHTS = [38, 70, 100, 58, 82]
|
||||
|
||||
export function VoiceDictationFeatureTipVisual(): JSX.Element {
|
||||
const t = useTranslate()
|
||||
const reducedMotion = usePrefersReducedMotion()
|
||||
const shortcut = useShortcutKeyDetails('voice.dictation')
|
||||
const dictatedPrompt = translate(
|
||||
const dictatedPrompt = t(
|
||||
'featureTips.voice.demoPrompt',
|
||||
'Review this diff for edge cases and add tests for anything you find.'
|
||||
)
|
||||
@@ -61,7 +62,7 @@ export function VoiceDictationFeatureTipVisual(): JSX.Element {
|
||||
keyCapClassName="h-7 min-w-7 bg-foreground/[0.08] px-2 font-semibold shadow-xs"
|
||||
/>
|
||||
<span className="text-[10px] font-semibold uppercase tracking-[0.05em] text-muted-foreground">
|
||||
{translate('featureTips.voice.startDictation', 'Start dictation')}
|
||||
{t('featureTips.voice.startDictation', 'Start dictation')}
|
||||
</span>
|
||||
</div>
|
||||
) : null}
|
||||
@@ -69,7 +70,7 @@ export function VoiceDictationFeatureTipVisual(): JSX.Element {
|
||||
<div className="relative mt-4 h-56 w-full max-w-[21rem] overflow-hidden rounded-xl border border-border/80 bg-card text-left shadow-xs">
|
||||
<div className="flex h-10 items-center gap-2 border-b border-border px-3 text-[11px] text-muted-foreground">
|
||||
<span className="size-2 rounded-full bg-foreground/35" />
|
||||
<span>{translate('featureTips.voice.agentPromptTitle', 'Agent prompt')}</span>
|
||||
<span>{t('featureTips.voice.agentPromptTitle', 'Agent prompt')}</span>
|
||||
</div>
|
||||
|
||||
<div className="absolute inset-x-0 bottom-0 top-10 bg-[var(--editor-surface)] px-4 py-5 font-mono text-xs leading-relaxed">
|
||||
@@ -99,7 +100,7 @@ export function VoiceDictationFeatureTipVisual(): JSX.Element {
|
||||
))}
|
||||
</span>
|
||||
<span className="min-w-0 flex-1 truncate">
|
||||
{translate('featureTips.voice.listening', 'Listening...')}
|
||||
{t('featureTips.voice.listening', 'Listening...')}
|
||||
</span>
|
||||
<Square className="size-2.5 shrink-0 fill-current opacity-55" />
|
||||
</div>
|
||||
|
||||
@@ -3,7 +3,8 @@ import type { FeatureTip } from '../../../../shared/feature-tips'
|
||||
import { ShortcutKeyCombo } from '@/components/ShortcutKeyCombo'
|
||||
import { DialogDescription, DialogFooter, DialogHeader, DialogTitle } from '@/components/ui/dialog'
|
||||
import { useShortcutKeyDetails } from '@/hooks/useShortcutLabel'
|
||||
import { i18n, translate } from '@/i18n/i18n'
|
||||
import { i18n } from '@/i18n/i18n'
|
||||
import { useTranslate } from '@/i18n/use-translate'
|
||||
import { Trans } from 'react-i18next'
|
||||
import { FeatureTipActions } from './FeatureTipActions'
|
||||
import { getLocalizedFeatureTipCopy } from './feature-tip-localized-copy'
|
||||
@@ -31,6 +32,7 @@ export function VoiceDictationTipDialog({
|
||||
onSkip: () => void
|
||||
onVoiceSettingsClick: () => void
|
||||
}): JSX.Element {
|
||||
const t = useTranslate()
|
||||
const shortcut = useShortcutKeyDetails('voice.dictation')
|
||||
const primaryButtonRef = useRef<HTMLButtonElement>(null)
|
||||
const copy = getLocalizedFeatureTipCopy(tip)
|
||||
@@ -56,7 +58,7 @@ export function VoiceDictationTipDialog({
|
||||
<span className="block">
|
||||
<Trans
|
||||
i18n={i18n}
|
||||
defaults={translate(
|
||||
defaults={t(
|
||||
'featureTips.voice.shortcutInstruction',
|
||||
'Focus a terminal, editor, or agent prompt, then press <shortcut/> to start voice dictation. Press <shortcut/> again to stop.'
|
||||
)}
|
||||
@@ -74,14 +76,14 @@ export function VoiceDictationTipDialog({
|
||||
</span>
|
||||
) : (
|
||||
<span className="block">
|
||||
{translate(
|
||||
{t(
|
||||
'featureTips.voice.unassignedInstruction',
|
||||
'Assign a dictation shortcut before starting voice dictation in a focused pane.'
|
||||
)}
|
||||
</span>
|
||||
)}
|
||||
<FeatureTipSettingsLine
|
||||
text={translate(
|
||||
text={t(
|
||||
'featureTips.voice.changeInSettings',
|
||||
'Change the model, dictation mode, or shortcut anytime in <link>Settings → Voice</link>.'
|
||||
)}
|
||||
|
||||
@@ -1,9 +1,13 @@
|
||||
import type { ChatUiLaunchTipVariant } from '../../../../shared/native-chat/chat-ui-launch-tip-audience'
|
||||
import { translate } from '@/i18n/i18n'
|
||||
import type { Translate } from '@/i18n/use-translate'
|
||||
|
||||
export type ShownChatUiLaunchTipVariant = Exclude<ChatUiLaunchTipVariant, 'none'>
|
||||
|
||||
export function getChatUiHereVariantCopy(variant: ShownChatUiLaunchTipVariant): {
|
||||
export function getChatUiHereVariantCopy(
|
||||
variant: ShownChatUiLaunchTipVariant,
|
||||
t: Translate = translate
|
||||
): {
|
||||
paragraphs: string[]
|
||||
secondaryLabel: string | null
|
||||
primaryLabel: string
|
||||
@@ -11,31 +15,31 @@ export function getChatUiHereVariantCopy(variant: ShownChatUiLaunchTipVariant):
|
||||
if (variant === 'chat-ui-off') {
|
||||
return {
|
||||
paragraphs: [
|
||||
translate(
|
||||
t(
|
||||
'featureTips.chatUiHere.offIntro',
|
||||
'Agents can now open as a chat, with rich messages, visuals and images.'
|
||||
),
|
||||
translate(
|
||||
t(
|
||||
'featureTips.chatUiHere.offTerminal',
|
||||
'Prefer the terminal? It stays fully supported. Your agents keep opening there until you choose Chat.'
|
||||
)
|
||||
],
|
||||
secondaryLabel: translate('featureTips.chatUiHere.stayInTerminal', 'Stay in terminal'),
|
||||
primaryLabel: translate('featureTips.chatUiHere.cta', 'Use Chat UI')
|
||||
secondaryLabel: t('featureTips.chatUiHere.stayInTerminal', 'Stay in terminal'),
|
||||
primaryLabel: t('featureTips.chatUiHere.cta', 'Use Chat UI')
|
||||
}
|
||||
}
|
||||
return {
|
||||
paragraphs: [
|
||||
translate(
|
||||
t(
|
||||
'featureTips.chatUiHere.onIntro',
|
||||
'Chat UI is out of Experimental and now part of Orca. Your agents keep opening as chats, with rich messages, visuals and images.'
|
||||
),
|
||||
translate(
|
||||
t(
|
||||
'featureTips.chatUiHere.onTerminal',
|
||||
'The terminal stays fully supported too. Switch any agent back anytime.'
|
||||
)
|
||||
],
|
||||
secondaryLabel: translate('featureTips.chatUiHere.switchToTerminal', 'Switch to terminal'),
|
||||
primaryLabel: translate('featureTips.gotIt', 'Got it')
|
||||
secondaryLabel: t('featureTips.chatUiHere.switchToTerminal', 'Switch to terminal'),
|
||||
primaryLabel: t('featureTips.gotIt', 'Got it')
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,4 +1,5 @@
|
||||
import { getIntlLocale, translate } from '@/i18n/i18n'
|
||||
import type { Translate } from '@/i18n/use-translate'
|
||||
|
||||
export const CHAT_DEMO_FILE = 'auth.ts'
|
||||
export const CHAT_DEMO_MODEL = 'Opus'
|
||||
@@ -15,11 +16,12 @@ export type ChatDemoCopy = {
|
||||
flow: { name: string; arrow: string | null }[]
|
||||
}
|
||||
|
||||
export function chatDemoRevealTokens(text: string): ChatDemoToken[] {
|
||||
export function chatDemoRevealTokens(
|
||||
text: string,
|
||||
intlLocale: string = getIntlLocale()
|
||||
): ChatDemoToken[] {
|
||||
const tokens: ChatDemoToken[] = []
|
||||
for (const { segment } of new Intl.Segmenter(getIntlLocale(), { granularity: 'word' }).segment(
|
||||
text
|
||||
)) {
|
||||
for (const { segment } of new Intl.Segmenter(intlLocale, { granularity: 'word' }).segment(text)) {
|
||||
const last = tokens.at(-1)
|
||||
if (last && !last.link && segment.trim() === '') {
|
||||
last.text += segment
|
||||
@@ -30,32 +32,35 @@ export function chatDemoRevealTokens(text: string): ChatDemoToken[] {
|
||||
return tokens
|
||||
}
|
||||
|
||||
export function getChatDemoCopy(): ChatDemoCopy {
|
||||
const [before, ...after] = translate(
|
||||
export function getChatDemoCopy(t: Translate = translate, intlLocale?: string): ChatDemoCopy {
|
||||
const [before, ...after] = t(
|
||||
'featureTips.chatUiHere.demoReply',
|
||||
'Login starts in {{file}}: the browser posts to /login, the server checks the password and creates a session, then redirects you to the app. Here’s the flow:',
|
||||
{ file: FILE_SLOT }
|
||||
).split(FILE_SLOT)
|
||||
return {
|
||||
tabTitle: translate('featureTips.chatUiHere.demoTabTitle', 'Explain login'),
|
||||
prompt: translate('featureTips.chatUiHere.demoPrompt', 'Map out how login works'),
|
||||
tabTitle: t('featureTips.chatUiHere.demoTabTitle', 'Explain login'),
|
||||
prompt: t('featureTips.chatUiHere.demoPrompt', 'Map out how login works'),
|
||||
reply: [
|
||||
...chatDemoRevealTokens(before),
|
||||
...chatDemoRevealTokens(before, intlLocale),
|
||||
...(after.length > 0
|
||||
? [{ text: CHAT_DEMO_FILE, link: true as const }, ...chatDemoRevealTokens(after.join(''))]
|
||||
? [
|
||||
{ text: CHAT_DEMO_FILE, link: true as const },
|
||||
...chatDemoRevealTokens(after.join(''), intlLocale)
|
||||
]
|
||||
: [])
|
||||
],
|
||||
flow: [
|
||||
{
|
||||
name: translate('featureTips.chatUiHere.demoBrowser', 'Browser'),
|
||||
arrow: translate('featureTips.chatUiHere.demoPassword', 'password')
|
||||
name: t('featureTips.chatUiHere.demoBrowser', 'Browser'),
|
||||
arrow: t('featureTips.chatUiHere.demoPassword', 'password')
|
||||
},
|
||||
{ name: '/login', arrow: '' },
|
||||
{
|
||||
name: translate('featureTips.chatUiHere.demoSession', 'Session'),
|
||||
arrow: translate('featureTips.chatUiHere.demoCookie', 'cookie')
|
||||
name: t('featureTips.chatUiHere.demoSession', 'Session'),
|
||||
arrow: t('featureTips.chatUiHere.demoCookie', 'cookie')
|
||||
},
|
||||
{ name: translate('featureTips.chatUiHere.demoApp', 'App'), arrow: null }
|
||||
{ name: t('featureTips.chatUiHere.demoApp', 'App'), arrow: null }
|
||||
]
|
||||
}
|
||||
}
|
||||
|
||||
@@ -17,7 +17,7 @@ import {
|
||||
DEFAULT_AI_VAULT_HIDE_EMPTY_SESSIONS
|
||||
} from '@/components/right-sidebar/ai-vault/scope/ai-vault-view-defaults'
|
||||
import { AgentIcon } from '@/lib/agent-catalog'
|
||||
import { translate } from '@/i18n/i18n'
|
||||
import { useTranslate } from '@/i18n/use-translate'
|
||||
import type { SessionSearchDemoRow } from './session-search-feature-tip-demo'
|
||||
|
||||
const ROW_STAGGER_MS = 90
|
||||
@@ -136,6 +136,7 @@ export function DemoSessionRow({
|
||||
index: number
|
||||
children?: ReactNode
|
||||
}): JSX.Element {
|
||||
const t = useTranslate()
|
||||
return (
|
||||
<div
|
||||
data-focused={focused}
|
||||
@@ -153,11 +154,9 @@ export function DemoSessionRow({
|
||||
<AgentIcon agent={row.agent} size={14} />
|
||||
<span className="truncate">{AI_VAULT_AGENT_LABELS[row.agent]}</span>
|
||||
<span className="shrink-0 tabular-nums">
|
||||
{translate(
|
||||
'auto.components.right.sidebar.AiVaultSessionRow.messageCount',
|
||||
'{{value0}} msgs',
|
||||
{ value0: row.messages }
|
||||
)}
|
||||
{t('auto.components.right.sidebar.AiVaultSessionRow.messageCount', '{{value0}} msgs', {
|
||||
value0: row.messages
|
||||
})}
|
||||
</span>
|
||||
<span className="shrink-0 text-muted-foreground/55">·</span>
|
||||
<span className="shrink-0">{row.age}</span>
|
||||
|
||||
@@ -1,4 +1,5 @@
|
||||
import { translate } from '@/i18n/i18n'
|
||||
import type { Translate } from '@/i18n/use-translate'
|
||||
|
||||
export type SessionSearchDemoRow = {
|
||||
agent: 'claude' | 'codex'
|
||||
@@ -12,131 +13,128 @@ export type SessionSearchDemoRow = {
|
||||
|
||||
export type SessionSearchDemoScene = { query: string; hits: SessionSearchDemoRow[] }
|
||||
|
||||
function assistantRole(): string {
|
||||
return translate('featureTips.sessionSearch.demoRoleAssistant', 'Assistant')
|
||||
function assistantRole(t: Translate = translate): string {
|
||||
return t('featureTips.sessionSearch.demoRoleAssistant', 'Assistant')
|
||||
}
|
||||
|
||||
function userRole(): string {
|
||||
return translate('featureTips.sessionSearch.demoRoleUser', 'User')
|
||||
function userRole(t: Translate = translate): string {
|
||||
return t('featureTips.sessionSearch.demoRoleUser', 'User')
|
||||
}
|
||||
|
||||
/** What the panel lists before anything is typed. */
|
||||
export function getSessionSearchDemoRecentRows(): SessionSearchDemoRow[] {
|
||||
export function getSessionSearchDemoRecentRows(t: Translate = translate): SessionSearchDemoRow[] {
|
||||
return [
|
||||
{
|
||||
agent: 'claude',
|
||||
title: translate('featureTips.sessionSearch.recent1Title', 'Tidy the settings sidebar'),
|
||||
role: assistantRole(),
|
||||
text: translate(
|
||||
title: t('featureTips.sessionSearch.recent1Title', 'Tidy the settings sidebar'),
|
||||
role: assistantRole(t),
|
||||
text: t(
|
||||
'featureTips.sessionSearch.recent1Text',
|
||||
'Moved the search entries under Workflows and updated the tests.'
|
||||
),
|
||||
messages: 42,
|
||||
age: translate('featureTips.sessionSearch.ageMinutes', '12m')
|
||||
age: t('featureTips.sessionSearch.ageMinutes', '12m')
|
||||
},
|
||||
{
|
||||
agent: 'codex',
|
||||
title: translate('featureTips.sessionSearch.recent2Title', 'Bump the lockfile'),
|
||||
role: userRole(),
|
||||
text: translate(
|
||||
title: t('featureTips.sessionSearch.recent2Title', 'Bump the lockfile'),
|
||||
role: userRole(t),
|
||||
text: t(
|
||||
'featureTips.sessionSearch.recent2Text',
|
||||
'Update the dependencies and make sure the build still passes.'
|
||||
),
|
||||
messages: 18,
|
||||
age: translate('featureTips.sessionSearch.ageHours', '2h')
|
||||
age: t('featureTips.sessionSearch.ageHours', '2h')
|
||||
},
|
||||
{
|
||||
agent: 'claude',
|
||||
title: translate('featureTips.sessionSearch.recent3Title', 'Release notes draft'),
|
||||
role: assistantRole(),
|
||||
text: translate(
|
||||
title: t('featureTips.sessionSearch.recent3Title', 'Release notes draft'),
|
||||
role: assistantRole(t),
|
||||
text: t(
|
||||
'featureTips.sessionSearch.recent3Text',
|
||||
'Here is a first pass at the notes, grouped by area.'
|
||||
),
|
||||
messages: 9,
|
||||
age: translate('featureTips.sessionSearch.ageYesterday', '1d')
|
||||
age: t('featureTips.sessionSearch.ageYesterday', '1d')
|
||||
}
|
||||
]
|
||||
}
|
||||
|
||||
export function getSessionSearchDemoScenes(): SessionSearchDemoScene[] {
|
||||
export function getSessionSearchDemoScenes(t: Translate = translate): SessionSearchDemoScene[] {
|
||||
return [
|
||||
{
|
||||
query: translate('featureTips.sessionSearch.demoQuery', 'login timeout'),
|
||||
query: t('featureTips.sessionSearch.demoQuery', 'login timeout'),
|
||||
hits: [
|
||||
{
|
||||
agent: 'claude',
|
||||
title: translate('featureTips.sessionSearch.demoHit1Title', 'Fix flaky auth redirect'),
|
||||
role: assistantRole(),
|
||||
text: translate(
|
||||
title: t('featureTips.sessionSearch.demoHit1Title', 'Fix flaky auth redirect'),
|
||||
role: assistantRole(t),
|
||||
text: t(
|
||||
'featureTips.sessionSearch.demoHit1Snippet',
|
||||
'Raised the [[login timeout]] to 30s and added a retry on the token refresh.'
|
||||
),
|
||||
messages: 64,
|
||||
age: translate('featureTips.sessionSearch.demoHit1Age', '3d')
|
||||
age: t('featureTips.sessionSearch.demoHit1Age', '3d')
|
||||
},
|
||||
{
|
||||
agent: 'codex',
|
||||
title: translate(
|
||||
'featureTips.sessionSearch.demoHit2Title',
|
||||
'Session refresh after sleep'
|
||||
),
|
||||
role: userRole(),
|
||||
text: translate(
|
||||
title: t('featureTips.sessionSearch.demoHit2Title', 'Session refresh after sleep'),
|
||||
role: userRole(t),
|
||||
text: t(
|
||||
'featureTips.sessionSearch.demoHit2Snippet',
|
||||
'Why does the [[login]] page hang after a [[timeout]] on wake?'
|
||||
),
|
||||
messages: 27,
|
||||
age: translate('featureTips.sessionSearch.demoHit2Age', '1w')
|
||||
age: t('featureTips.sessionSearch.demoHit2Age', '1w')
|
||||
},
|
||||
{
|
||||
agent: 'claude',
|
||||
title: translate('featureTips.sessionSearch.demoHit3Title', 'Auth e2e hardening'),
|
||||
role: assistantRole(),
|
||||
text: translate(
|
||||
title: t('featureTips.sessionSearch.demoHit3Title', 'Auth e2e hardening'),
|
||||
role: assistantRole(t),
|
||||
text: t(
|
||||
'featureTips.sessionSearch.demoHit3Snippet',
|
||||
'Added a test that covers the [[login timeout]] path end to end.'
|
||||
),
|
||||
messages: 31,
|
||||
age: translate('featureTips.sessionSearch.demoHit3Age', '2w')
|
||||
age: t('featureTips.sessionSearch.demoHit3Age', '2w')
|
||||
}
|
||||
]
|
||||
},
|
||||
{
|
||||
query: translate('featureTips.sessionSearch.demoQuery2', 'migration rollback'),
|
||||
query: t('featureTips.sessionSearch.demoQuery2', 'migration rollback'),
|
||||
hits: [
|
||||
{
|
||||
agent: 'codex',
|
||||
title: translate('featureTips.sessionSearch.demoHit4Title', 'Orders table migration'),
|
||||
role: assistantRole(),
|
||||
text: translate(
|
||||
title: t('featureTips.sessionSearch.demoHit4Title', 'Orders table migration'),
|
||||
role: assistantRole(t),
|
||||
text: t(
|
||||
'featureTips.sessionSearch.demoHit4Snippet',
|
||||
'The [[rollback]] drops the new index first, then restores the old column.'
|
||||
),
|
||||
messages: 53,
|
||||
age: translate('featureTips.sessionSearch.demoHit4Age', '5d')
|
||||
age: t('featureTips.sessionSearch.demoHit4Age', '5d')
|
||||
},
|
||||
{
|
||||
agent: 'claude',
|
||||
title: translate('featureTips.sessionSearch.demoHit5Title', 'Staging deploy failed'),
|
||||
role: userRole(),
|
||||
text: translate(
|
||||
title: t('featureTips.sessionSearch.demoHit5Title', 'Staging deploy failed'),
|
||||
role: userRole(t),
|
||||
text: t(
|
||||
'featureTips.sessionSearch.demoHit5Snippet',
|
||||
'Can you write a [[migration]] [[rollback]] plan before we retry?'
|
||||
),
|
||||
messages: 22,
|
||||
age: translate('featureTips.sessionSearch.demoHit5Age', '3w')
|
||||
age: t('featureTips.sessionSearch.demoHit5Age', '3w')
|
||||
},
|
||||
{
|
||||
agent: 'claude',
|
||||
title: translate('featureTips.sessionSearch.demoHit6Title', 'Schema review notes'),
|
||||
role: assistantRole(),
|
||||
text: translate(
|
||||
title: t('featureTips.sessionSearch.demoHit6Title', 'Schema review notes'),
|
||||
role: assistantRole(t),
|
||||
text: t(
|
||||
'featureTips.sessionSearch.demoHit6Snippet',
|
||||
'Every [[migration]] here is reversible except the enum rename.'
|
||||
),
|
||||
messages: 14,
|
||||
age: translate('featureTips.sessionSearch.demoHit6Age', '1mo')
|
||||
age: t('featureTips.sessionSearch.demoHit6Age', '1mo')
|
||||
}
|
||||
]
|
||||
}
|
||||
|
||||
@@ -9,6 +9,7 @@ import {
|
||||
} from '@/components/settings/session-history/session-history-status-copy'
|
||||
import { useSessionSearchStatus } from '@/components/settings/session-history/use-session-search-status'
|
||||
import { translate } from '@/i18n/i18n'
|
||||
import { useTranslate } from '@/i18n/use-translate'
|
||||
import { useAppStore } from '@/store'
|
||||
|
||||
export type SessionSearchTipStage = 'offer' | 'indexing' | 'ready'
|
||||
@@ -26,6 +27,7 @@ export function useSessionSearchTipSetup({
|
||||
}: {
|
||||
dialogOpen: boolean
|
||||
}): SessionSearchTipSetup {
|
||||
const t = useTranslate()
|
||||
const showAiVaultSearch = useAppStore((s) => s.showAiVaultSearch)
|
||||
const searchEnabled = useAppStore((s) => resolveAiVaultSearchSettings(s.settings).enabled)
|
||||
const [wasOpen, setWasOpen] = useState(dialogOpen)
|
||||
@@ -79,7 +81,7 @@ export function useSessionSearchTipSetup({
|
||||
})
|
||||
} catch {
|
||||
toast.error(
|
||||
translate(
|
||||
t(
|
||||
'featureTips.sessionSearch.enableFailed',
|
||||
'Could not turn on session search. Try again.'
|
||||
)
|
||||
|
||||
@@ -5,7 +5,7 @@ import { useRepoMap } from '@/store/repos/use-repository-catalog'
|
||||
import { useWorktreesForRepo } from '@/store/slices/worktrees/listing/use-worktree-catalog'
|
||||
import { getRepoExecutionHostId } from '../../../../shared/execution-host'
|
||||
import { toast } from 'sonner'
|
||||
import { translate } from '@/i18n/i18n'
|
||||
import { useTranslate } from '@/i18n/use-translate'
|
||||
|
||||
type ComposerBaseRefPickerProps = {
|
||||
repoId: string
|
||||
@@ -23,6 +23,7 @@ export function ComposerBaseRefPicker({
|
||||
resetHint,
|
||||
readOnly = false
|
||||
}: ComposerBaseRefPickerProps): React.JSX.Element {
|
||||
const t = useTranslate()
|
||||
const repoMap = useRepoMap()
|
||||
const repo = repoMap.get(repoId)
|
||||
const repoWorktrees = useWorktreesForRepo(repoId)
|
||||
@@ -50,7 +51,7 @@ export function ComposerBaseRefPicker({
|
||||
)
|
||||
if (!updated) {
|
||||
toast.error(
|
||||
translate(
|
||||
t(
|
||||
'auto.components.NewWorkspaceComposerCard.defaultBranchUpdateFailed',
|
||||
'Could not update the project default branch. Try again.'
|
||||
)
|
||||
|
||||
@@ -3,7 +3,7 @@ import { ArrowRight } from 'lucide-react'
|
||||
import { Button } from '@/components/ui/button'
|
||||
import { Input } from '@/components/ui/input'
|
||||
import { RadioGroup, RadioGroupItem } from '@/components/ui/radio-group'
|
||||
import { translate } from '@/i18n/i18n'
|
||||
import { useTranslate } from '@/i18n/use-translate'
|
||||
import { cn } from '@/lib/utils'
|
||||
|
||||
type Props = {
|
||||
@@ -25,16 +25,17 @@ export function ComposerBranchChoice({
|
||||
onOpenSelectedBranchWorkspace,
|
||||
disabled
|
||||
}: Props) {
|
||||
const t = useTranslate()
|
||||
const id = useId()
|
||||
const branchNameInputRef = useRef<HTMLInputElement>(null)
|
||||
const showOpenWorkspace = reuseSelectedBranch && Boolean(onOpenSelectedBranchWorkspace)
|
||||
return (
|
||||
<fieldset className="min-w-0 space-y-2" disabled={disabled}>
|
||||
<legend className="text-xs font-medium text-muted-foreground">
|
||||
{translate('workspace.branchChoice.title', 'Branch for this worktree')}
|
||||
{t('workspace.branchChoice.title', 'Branch for this worktree')}
|
||||
</legend>
|
||||
<RadioGroup
|
||||
aria-label={translate('workspace.branchChoice.title', 'Branch for this worktree')}
|
||||
aria-label={t('workspace.branchChoice.title', 'Branch for this worktree')}
|
||||
value={reuseSelectedBranch ? 'existing' : 'new'}
|
||||
onValueChange={(value) => onReuseSelectedBranchChange(value === 'existing')}
|
||||
disabled={disabled}
|
||||
@@ -59,7 +60,7 @@ export function ComposerBranchChoice({
|
||||
<span className="min-w-0 space-y-1">
|
||||
<span className="flex min-w-0 flex-wrap items-baseline gap-x-2 gap-y-1">
|
||||
<span id={`${id}-existing`} className="shrink-0 text-xs font-medium">
|
||||
{translate('workspace.branchChoice.existing', 'Existing branch')}
|
||||
{t('workspace.branchChoice.existing', 'Existing branch')}
|
||||
</span>
|
||||
{existingBranchName ? (
|
||||
<span
|
||||
@@ -74,7 +75,7 @@ export function ComposerBranchChoice({
|
||||
id={`${id}-existing-description`}
|
||||
className="block text-xs text-muted-foreground"
|
||||
>
|
||||
{translate('workspace.branchChoice.existingHint', 'New commits go on this branch.')}
|
||||
{t('workspace.branchChoice.existingHint', 'New commits go on this branch.')}
|
||||
</span>
|
||||
</span>
|
||||
</label>
|
||||
@@ -86,7 +87,7 @@ export function ComposerBranchChoice({
|
||||
size="xs"
|
||||
onClick={onOpenSelectedBranchWorkspace}
|
||||
>
|
||||
{translate(
|
||||
{t(
|
||||
'auto.components.NewWorkspaceComposerCard.openExistingWorkspace',
|
||||
'Open existing workspace'
|
||||
)}
|
||||
@@ -111,17 +112,17 @@ export function ComposerBranchChoice({
|
||||
/>
|
||||
<span className="min-w-0 space-y-1">
|
||||
<span id={`${id}-new`} className="block text-xs font-medium">
|
||||
{translate('workspace.branchChoice.new', 'New branch')}
|
||||
{t('workspace.branchChoice.new', 'New branch')}
|
||||
</span>
|
||||
<span id={`${id}-new-description`} className="block text-xs text-muted-foreground">
|
||||
{translate('workspace.branchChoice.newHint', 'Create a new local branch')}
|
||||
{t('workspace.branchChoice.newHint', 'Create a new local branch')}
|
||||
</span>
|
||||
</span>
|
||||
</label>
|
||||
{!reuseSelectedBranch ? (
|
||||
<div className="space-y-1 px-3 pb-3 pl-9">
|
||||
<label htmlFor={`${id}-name`} className="text-xs font-medium">
|
||||
{translate('workspace.branchChoice.newName', 'New branch name')}
|
||||
{t('workspace.branchChoice.newName', 'New branch name')}
|
||||
</label>
|
||||
<Input
|
||||
id={`${id}-name`}
|
||||
@@ -131,7 +132,7 @@ export function ComposerBranchChoice({
|
||||
autoFocus
|
||||
value={branchNameOverride ?? ''}
|
||||
onChange={(event) => onBranchNameOverrideChange(event.target.value)}
|
||||
placeholder={translate('workspace.branchChoice.automaticName', 'Automatic name')}
|
||||
placeholder={t('workspace.branchChoice.automaticName', 'Automatic name')}
|
||||
autoCapitalize="none"
|
||||
spellCheck={false}
|
||||
/>
|
||||
|
||||
@@ -6,7 +6,7 @@ import { Button } from '@/components/ui/button'
|
||||
import { Command, CommandInput, CommandList } from '@/components/ui/command'
|
||||
import { Popover, PopoverContent, PopoverTrigger } from '@/components/ui/popover'
|
||||
import { cn } from '@/lib/utils'
|
||||
import { translate } from '@/i18n/i18n'
|
||||
import { useTranslate } from '@/i18n/use-translate'
|
||||
import { useAppStore } from '@/store'
|
||||
import {
|
||||
getIndexedAllWorktrees,
|
||||
@@ -87,6 +87,7 @@ function ComposerParentWorktreePickerImpl({
|
||||
disabled = false,
|
||||
activeFolderWorkspaceId = null
|
||||
}: ComposerParentWorktreePickerProps): React.JSX.Element {
|
||||
const t = useTranslate()
|
||||
// Subscribe so translate() refreshes on language changes.
|
||||
useTranslation()
|
||||
const [open, setOpen] = useState(false)
|
||||
@@ -122,7 +123,7 @@ function ComposerParentWorktreePickerImpl({
|
||||
return (
|
||||
<div className="space-y-1.5">
|
||||
<span id={labelId} className="block text-xs font-medium text-muted-foreground">
|
||||
{translate('auto.components.ComposerParentWorktreePicker.label', 'Parent worktree')}
|
||||
{t('auto.components.ComposerParentWorktreePicker.label', 'Parent worktree')}
|
||||
</span>
|
||||
<Popover open={open} onOpenChange={setOpen}>
|
||||
<PopoverTrigger asChild>
|
||||
@@ -137,7 +138,7 @@ function ComposerParentWorktreePickerImpl({
|
||||
>
|
||||
<span className="truncate">
|
||||
{parentName ??
|
||||
translate('auto.components.ComposerParentWorktreePicker.noParent', 'No parent')}
|
||||
t('auto.components.ComposerParentWorktreePicker.noParent', 'No parent')}
|
||||
</span>
|
||||
<ChevronsUpDown className="size-3.5 shrink-0 opacity-50" />
|
||||
</Button>
|
||||
@@ -168,7 +169,7 @@ function ComposerParentWorktreePickerImpl({
|
||||
</PopoverContent>
|
||||
</Popover>
|
||||
<p className="text-[11px] text-muted-foreground">
|
||||
{translate(
|
||||
{t(
|
||||
'auto.components.ComposerParentWorktreePicker.description',
|
||||
'Nests this workspace under another in the sidebar. Does not change the base branch.'
|
||||
)}
|
||||
@@ -217,6 +218,7 @@ function ParentWorktreeCandidateList({
|
||||
activeFolderWorkspaceId,
|
||||
onSelect
|
||||
}: ParentWorktreeCandidateListProps): React.JSX.Element {
|
||||
const t = useTranslate()
|
||||
const worktreesByRepo = useAppStore((s) => selectWorktreesByRepo(s))
|
||||
const repoMap = useRepoMap()
|
||||
const worktreeLineageById = useAppStore((s) => s.worktreeLineageById)
|
||||
@@ -357,7 +359,7 @@ function ParentWorktreeCandidateList({
|
||||
onValueChange={handleSearchChange}
|
||||
onKeyDown={handleKeyDown}
|
||||
wrapperClassName="shrink-0"
|
||||
placeholder={translate(
|
||||
placeholder={t(
|
||||
'auto.components.ComposerParentWorktreePicker.searchPlaceholder',
|
||||
'Search workspaces...'
|
||||
)}
|
||||
@@ -413,10 +415,7 @@ function ParentWorktreeCandidateList({
|
||||
</div>
|
||||
) : (
|
||||
<span className="truncate">
|
||||
{translate(
|
||||
'auto.components.ComposerParentWorktreePicker.noParent',
|
||||
'No parent'
|
||||
)}
|
||||
{t('auto.components.ComposerParentWorktreePicker.noParent', 'No parent')}
|
||||
</span>
|
||||
)}
|
||||
</div>
|
||||
@@ -425,7 +424,7 @@ function ParentWorktreeCandidateList({
|
||||
</div>
|
||||
{filtered.length === 0 && search.trim().length > 0 ? (
|
||||
<div className="py-6 text-center text-sm text-muted-foreground">
|
||||
{translate('auto.components.ComposerParentWorktreePicker.noMatches', 'No matches.')}
|
||||
{t('auto.components.ComposerParentWorktreePicker.noMatches', 'No matches.')}
|
||||
</div>
|
||||
) : null}
|
||||
</CommandList>
|
||||
|
||||
@@ -8,7 +8,7 @@ import {
|
||||
DialogFooter
|
||||
} from '@/components/ui/dialog'
|
||||
import { Button } from '@/components/ui/button'
|
||||
import { translate } from '@/i18n/i18n'
|
||||
import { useTranslate } from '@/i18n/use-translate'
|
||||
import type { DirectWorkItemSetupRequest } from '@/lib/workspace/creation/work-item/direct-work-item-setup'
|
||||
import { WorkspaceSetupSection } from './WorkspaceSetupSection'
|
||||
import { selectWorkspaceSetupCopy } from './workspace-setup-section-model'
|
||||
@@ -19,6 +19,7 @@ export function DirectWorkItemSetupDialog({
|
||||
}: {
|
||||
request: DirectWorkItemSetupRequest
|
||||
}): React.JSX.Element {
|
||||
const t = useTranslate()
|
||||
const [inspection, setInspection] = useState(request.inspection)
|
||||
const [checking, setChecking] = useState(false)
|
||||
const retry = async (): Promise<void> => {
|
||||
@@ -49,14 +50,14 @@ export function DirectWorkItemSetupDialog({
|
||||
<DialogContent surface="security" scrollable className="sm:max-w-lg">
|
||||
<DialogHeader>
|
||||
<DialogTitle>
|
||||
{translate('components.newWorkspace.directSetup.run', 'Run setup')} · {request.repoName}
|
||||
{t('components.newWorkspace.directSetup.run', 'Run setup')} · {request.repoName}
|
||||
</DialogTitle>
|
||||
</DialogHeader>
|
||||
<WorkspaceSetupSection
|
||||
setupConfig={inspection.kind === 'needs-modal' ? inspection.setupConfig : null}
|
||||
copy={selectWorkspaceSetupCopy(
|
||||
inspection.kind === 'needs-modal' ? inspection.setupConfig : null,
|
||||
getDirectWorkspaceSetupLabels()
|
||||
getDirectWorkspaceSetupLabels(t)
|
||||
)}
|
||||
requiresExplicitSetupChoice
|
||||
resolvedSetupDecision={null}
|
||||
@@ -66,11 +67,11 @@ export function DirectWorkItemSetupDialog({
|
||||
unavailable={
|
||||
inspection.kind === 'unavailable'
|
||||
? {
|
||||
message: translate(
|
||||
message: t(
|
||||
'components.newWorkspace.setupCheckFailed',
|
||||
'Could not check setup configuration. Try again.'
|
||||
),
|
||||
retryLabel: translate('components.newWorkspace.directSetup.retry', 'Retry'),
|
||||
retryLabel: t('components.newWorkspace.directSetup.retry', 'Retry'),
|
||||
checking,
|
||||
retry: () => void retry()
|
||||
}
|
||||
@@ -79,7 +80,7 @@ export function DirectWorkItemSetupDialog({
|
||||
/>
|
||||
<DialogFooter>
|
||||
<Button variant="ghost" onClick={request.cancel}>
|
||||
{translate('components.newWorkspace.directSetup.cancel', 'Cancel')}
|
||||
{t('components.newWorkspace.directSetup.cancel', 'Cancel')}
|
||||
</Button>
|
||||
</DialogFooter>
|
||||
</DialogContent>
|
||||
|
||||
@@ -15,6 +15,7 @@ import {
|
||||
shouldHandleTextControlPaste
|
||||
} from '@/lib/text-control-paste'
|
||||
import { translate } from '@/i18n/i18n'
|
||||
import { useTranslate } from '@/i18n/use-translate'
|
||||
import { ComposerParentWorktreePicker } from './ComposerParentWorktreePicker'
|
||||
import type { NewWorkspaceComposerCardProps } from './new-workspace-composer-card-props'
|
||||
|
||||
@@ -95,6 +96,7 @@ export function NewWorkspaceComposerAdvancedSection({
|
||||
onSparseSelectPreset,
|
||||
canUseSparseCheckout
|
||||
}: NewWorkspaceComposerAdvancedSectionProps): React.JSX.Element {
|
||||
const t = useTranslate()
|
||||
const handleNotePaste = React.useCallback((event: React.ClipboardEvent<HTMLTextAreaElement>) => {
|
||||
const text = event.clipboardData.getData('text/plain')
|
||||
const byteLengthMeasurement = measureTextControlPasteByteLength(text, {
|
||||
@@ -150,13 +152,13 @@ export function NewWorkspaceComposerAdvancedSection({
|
||||
{smartNameSelection ? (
|
||||
<div className="space-y-1">
|
||||
<label className="text-xs font-medium text-muted-foreground">
|
||||
{translate('auto.components.NewWorkspaceComposerCard.2688050e4b', 'Name')}
|
||||
{t('auto.components.NewWorkspaceComposerCard.2688050e4b', 'Name')}
|
||||
</label>
|
||||
<input
|
||||
type="text"
|
||||
value={name}
|
||||
onChange={(event) => onNameValueChange(event.target.value)}
|
||||
placeholder={translate(
|
||||
placeholder={t(
|
||||
'auto.components.NewWorkspaceComposerCard.0ee17638fe',
|
||||
'Workspace name'
|
||||
)}
|
||||
@@ -174,14 +176,14 @@ export function NewWorkspaceComposerAdvancedSection({
|
||||
htmlFor={branchNameInputId}
|
||||
className="text-xs font-medium text-muted-foreground"
|
||||
>
|
||||
{translate('auto.components.NewWorkspaceComposerCard.branchName', 'Branch name')}
|
||||
{t('auto.components.NewWorkspaceComposerCard.branchName', 'Branch name')}
|
||||
</label>
|
||||
<input
|
||||
id={branchNameInputId}
|
||||
type="text"
|
||||
value={branchNameOverride ?? ''}
|
||||
onChange={(event) => onBranchNameOverrideChange(event.target.value)}
|
||||
placeholder={translate(
|
||||
placeholder={t(
|
||||
'auto.components.NewWorkspaceComposerCard.branchNamePlaceholder',
|
||||
'feature/my-branch'
|
||||
)}
|
||||
@@ -204,16 +206,13 @@ export function NewWorkspaceComposerAdvancedSection({
|
||||
|
||||
<div className="space-y-1">
|
||||
<label className="text-xs font-medium text-muted-foreground">
|
||||
{translate('auto.components.NewWorkspaceComposerCard.f8728aa4f9', 'Note')}
|
||||
{t('auto.components.NewWorkspaceComposerCard.f8728aa4f9', 'Note')}
|
||||
</label>
|
||||
<textarea
|
||||
value={note}
|
||||
onChange={(event) => onNoteChange(event.target.value)}
|
||||
onPaste={handleNotePaste}
|
||||
placeholder={translate(
|
||||
'auto.components.NewWorkspaceComposerCard.090cfedeb4',
|
||||
'Write a note'
|
||||
)}
|
||||
placeholder={t('auto.components.NewWorkspaceComposerCard.090cfedeb4', 'Write a note')}
|
||||
rows={1}
|
||||
className="w-full min-w-0 resize-none overflow-y-auto scrollbar-sleek rounded-md border border-input bg-transparent px-3 py-1.5 text-sm shadow-xs transition-[color,box-shadow] outline-none placeholder:text-muted-foreground focus-visible:border-ring focus-visible:ring-[3px] focus-visible:ring-ring/50 [field-sizing:content] max-h-40"
|
||||
/>
|
||||
@@ -222,7 +221,7 @@ export function NewWorkspaceComposerAdvancedSection({
|
||||
{setupControlsEnabled && setupConfig ? (
|
||||
<WorkspaceSetupSection
|
||||
setupConfig={setupConfig}
|
||||
copy={selectWorkspaceSetupCopy(setupConfig, getComposerWorkspaceSetupLabels())}
|
||||
copy={selectWorkspaceSetupCopy(setupConfig, getComposerWorkspaceSetupLabels(t))}
|
||||
requiresExplicitSetupChoice={requiresExplicitSetupChoice}
|
||||
resolvedSetupDecision={resolvedSetupDecision}
|
||||
setupDecision={setupDecision}
|
||||
@@ -233,7 +232,7 @@ export function NewWorkspaceComposerAdvancedSection({
|
||||
? {
|
||||
value: setupAgentStartupPolicy,
|
||||
onChange: onSetupAgentStartupPolicyChange,
|
||||
label: getComposerSetupStartupLabel()
|
||||
label: getComposerSetupStartupLabel(t)
|
||||
}
|
||||
: undefined
|
||||
}
|
||||
@@ -243,10 +242,7 @@ export function NewWorkspaceComposerAdvancedSection({
|
||||
{sparseControlsEnabled ? (
|
||||
<div className="space-y-1.5">
|
||||
<label className="text-xs font-medium text-muted-foreground">
|
||||
{translate(
|
||||
'auto.components.NewWorkspaceComposerCard.d861de981b',
|
||||
'Sparse checkout'
|
||||
)}
|
||||
{t('auto.components.NewWorkspaceComposerCard.d861de981b', 'Sparse checkout')}
|
||||
</label>
|
||||
<SparseCheckoutPresetSelect
|
||||
key={repoId}
|
||||
@@ -259,7 +255,7 @@ export function NewWorkspaceComposerAdvancedSection({
|
||||
/>
|
||||
{!canUseSparseCheckout ? (
|
||||
<p className="text-[11px] text-muted-foreground">
|
||||
{translate(
|
||||
{t(
|
||||
'auto.components.NewWorkspaceComposerCard.cbb47ee0dc',
|
||||
'Only available for local Git projects.'
|
||||
)}
|
||||
|
||||
@@ -7,7 +7,7 @@ import { DropdownMenuItem, DropdownMenuSeparator } from '@/components/ui/dropdow
|
||||
import { Button } from '@/components/ui/button'
|
||||
import { Tooltip, TooltipContent, TooltipTrigger } from '@/components/ui/tooltip'
|
||||
import { cn } from '@/lib/utils'
|
||||
import { translate } from '@/i18n/i18n'
|
||||
import { useTranslate } from '@/i18n/use-translate'
|
||||
import type { NewWorkspaceComposerCardProps } from './new-workspace-composer-card-props'
|
||||
|
||||
type NewWorkspaceComposerAgentSectionProps = Pick<
|
||||
@@ -53,12 +53,13 @@ export function NewWorkspaceComposerAgentSection({
|
||||
defaultTuiAgent,
|
||||
handleSetDefaultAgent
|
||||
}: NewWorkspaceComposerAgentSectionProps): React.JSX.Element {
|
||||
const t = useTranslate()
|
||||
return (
|
||||
<>
|
||||
<div className="min-w-0 space-y-1" data-contextual-tour-target="workspace-creation-agent">
|
||||
<div className="flex items-center justify-between gap-2">
|
||||
<label className="text-xs font-medium text-muted-foreground">
|
||||
{translate('components.newWorkspace.agentOptional', 'Agent [Optional]')}
|
||||
{t('components.newWorkspace.agentOptional', 'Agent [Optional]')}
|
||||
</label>
|
||||
<Tooltip>
|
||||
<TooltipTrigger asChild>
|
||||
@@ -69,7 +70,7 @@ export function NewWorkspaceComposerAgentSection({
|
||||
onClick={(event) => onOpenAgentSettings(event.currentTarget)}
|
||||
tabIndex={-1}
|
||||
className="size-5 shrink-0 rounded-sm text-muted-foreground hover:text-foreground"
|
||||
aria-label={translate(
|
||||
aria-label={t(
|
||||
'auto.components.NewWorkspaceComposerCard.ab63f25397',
|
||||
'Open agent settings'
|
||||
)}
|
||||
@@ -78,7 +79,7 @@ export function NewWorkspaceComposerAgentSection({
|
||||
</Button>
|
||||
</TooltipTrigger>
|
||||
<TooltipContent side="top" sideOffset={6}>
|
||||
{translate('auto.components.NewWorkspaceComposerCard.ba64270bdb', 'Configure agents')}
|
||||
{t('auto.components.NewWorkspaceComposerCard.ba64270bdb', 'Configure agents')}
|
||||
</TooltipContent>
|
||||
</Tooltip>
|
||||
</div>
|
||||
@@ -95,7 +96,7 @@ export function NewWorkspaceComposerAgentSection({
|
||||
}
|
||||
onOpenAgentSettings={onOpenAgentSettings}
|
||||
onTriggerEnter={createDisabled ? undefined : onCreate}
|
||||
emptyLabel={translate('auto.components.agent.AgentCombobox.986f946354', 'Blank Terminal')}
|
||||
emptyLabel={t('auto.components.agent.AgentCombobox.986f946354', 'Blank Terminal')}
|
||||
defaultAgent={defaultTuiAgent}
|
||||
onSetDefault={handleSetDefaultAgent}
|
||||
onPick={(agent, draft, entry) => onQuickAgentChange(agent, draft?.surface, draft, entry)}
|
||||
@@ -106,7 +107,7 @@ export function NewWorkspaceComposerAgentSection({
|
||||
>
|
||||
<DropdownMenuItem onSelect={() => onQuickAgentChange(null)}>
|
||||
<Terminal className="size-3.5" />
|
||||
{translate('auto.components.agent.AgentCombobox.986f946354', 'Blank Terminal')}
|
||||
{t('auto.components.agent.AgentCombobox.986f946354', 'Blank Terminal')}
|
||||
</DropdownMenuItem>
|
||||
</AgentDefaultContextMenu>
|
||||
<DropdownMenuSeparator />
|
||||
@@ -123,7 +124,7 @@ export function NewWorkspaceComposerAgentSection({
|
||||
aria-expanded={advancedOpen}
|
||||
className="-ml-2 text-xs focus-visible:ring-inset"
|
||||
>
|
||||
{translate('auto.components.NewWorkspaceComposerCard.f0470c7383', 'Advanced')}
|
||||
{t('auto.components.NewWorkspaceComposerCard.f0470c7383', 'Advanced')}
|
||||
<ChevronDown
|
||||
className={cn('size-4 transition-transform', advancedOpen && 'rotate-180')}
|
||||
/>
|
||||
|
||||
@@ -3,7 +3,7 @@ import { LoaderCircle } from 'lucide-react'
|
||||
import { Button } from '@/components/ui/button'
|
||||
import { SwitchIndicator } from '@/components/ui/switch'
|
||||
import { cn } from '@/lib/utils'
|
||||
import { translate } from '@/i18n/i18n'
|
||||
import { useTranslate } from '@/i18n/use-translate'
|
||||
import { ButtonKeyHint } from '@/components/ButtonKeyHint'
|
||||
import type { NewWorkspaceComposerCardProps } from './new-workspace-composer-card-props'
|
||||
|
||||
@@ -32,6 +32,7 @@ export function NewWorkspaceComposerFooter({
|
||||
primaryActionLabel,
|
||||
submitShortcutModifierLabel
|
||||
}: NewWorkspaceComposerFooterProps): React.JSX.Element {
|
||||
const t = useTranslate()
|
||||
return (
|
||||
<>
|
||||
{createError ? (
|
||||
@@ -67,7 +68,7 @@ export function NewWorkspaceComposerFooter({
|
||||
>
|
||||
<SwitchIndicator checked={createMultiple} />
|
||||
<span className="text-muted-foreground transition-colors group-hover:text-foreground">
|
||||
{translate('auto.components.NewWorkspaceComposerCard.createMultiple', 'Create more')}
|
||||
{t('auto.components.NewWorkspaceComposerCard.createMultiple', 'Create more')}
|
||||
</span>
|
||||
</button>
|
||||
) : null}
|
||||
|
||||
@@ -2,7 +2,7 @@ import React from 'react'
|
||||
import { AlertTriangle } from 'lucide-react'
|
||||
import SmartWorkspaceNameField from '@/components/new-workspace/SmartWorkspaceNameField'
|
||||
import { cn } from '@/lib/utils'
|
||||
import { translate } from '@/i18n/i18n'
|
||||
import { useTranslate } from '@/i18n/use-translate'
|
||||
import { shouldShowComposerBaseRefPicker } from './base-ref-picker-visibility'
|
||||
import { ComposerBaseRefPicker } from './ComposerBaseRefPicker'
|
||||
import { ComposerBranchChoice } from './ComposerBranchChoice'
|
||||
@@ -91,6 +91,7 @@ export function NewWorkspaceComposerNameSection({
|
||||
onBaseBranchChange,
|
||||
startFromResetHint
|
||||
}: NewWorkspaceComposerNameSectionProps): React.JSX.Element {
|
||||
const t = useTranslate()
|
||||
const showBaseRefPicker =
|
||||
Boolean(onBaseBranchChange) &&
|
||||
shouldShowComposerBaseRefPicker({
|
||||
@@ -104,13 +105,10 @@ export function NewWorkspaceComposerNameSection({
|
||||
<div className="flex items-center justify-between gap-2">
|
||||
<label className="min-w-0 truncate text-xs font-medium text-muted-foreground">
|
||||
{selectedRepoIsGit
|
||||
? translate('auto.components.NewWorkspaceComposerCard.ac3748dcda', 'Create From')
|
||||
: translate(
|
||||
'auto.components.NewWorkspaceComposerCard.0ee17638fe',
|
||||
'Workspace name'
|
||||
)}{' '}
|
||||
? t('auto.components.NewWorkspaceComposerCard.ac3748dcda', 'Create From')
|
||||
: t('auto.components.NewWorkspaceComposerCard.0ee17638fe', 'Workspace name')}{' '}
|
||||
<span className="text-muted-foreground/70">
|
||||
{translate('auto.components.NewWorkspaceComposerCard.0c5d6a479c', '[Optional]')}
|
||||
{t('auto.components.NewWorkspaceComposerCard.0c5d6a479c', '[Optional]')}
|
||||
</span>
|
||||
</label>
|
||||
{showBaseRefPicker && onBaseBranchChange ? (
|
||||
@@ -142,7 +140,7 @@ export function NewWorkspaceComposerNameSection({
|
||||
githubSourceContext={smartNameGitHubSourceContext}
|
||||
jiraSourceContext={smartNameJiraSourceContext}
|
||||
disabled={selectedRepoRequiresConnection}
|
||||
disabledPlaceholder={translate(
|
||||
disabledPlaceholder={t(
|
||||
'auto.components.NewWorkspaceComposerCard.connectProjectFirst',
|
||||
'Connect this project first'
|
||||
)}
|
||||
|
||||
@@ -4,7 +4,7 @@ import { Button } from '@/components/ui/button'
|
||||
import { Tooltip, TooltipContent, TooltipTrigger } from '@/components/ui/tooltip'
|
||||
import ProjectCombobox from '@/components/new-workspace/ProjectCombobox'
|
||||
import RunTargetCombobox from '@/components/new-workspace/RunTargetCombobox'
|
||||
import { translate } from '@/i18n/i18n'
|
||||
import { useTranslate } from '@/i18n/use-translate'
|
||||
import { cn } from '@/lib/utils'
|
||||
import { getSelectedNewWorkspaceProjectOptionId } from '@/lib/new-workspace-project-options'
|
||||
import type {
|
||||
@@ -85,14 +85,14 @@ export function NewWorkspaceComposerProjectSection({
|
||||
connectButtonLabel,
|
||||
selectedProjectName
|
||||
}: NewWorkspaceComposerProjectSectionProps): React.JSX.Element {
|
||||
const t = useTranslate()
|
||||
return (
|
||||
<fieldset disabled={disabled} className="space-y-1">
|
||||
<div className={cn('grid gap-3', shouldShowRunTargetPicker && 'grid-cols-2')}>
|
||||
<div className="min-w-0 space-y-1">
|
||||
<div className="flex h-5 items-center justify-between gap-2">
|
||||
<label className="text-xs font-medium text-muted-foreground">
|
||||
{projectLabel ??
|
||||
translate('auto.components.NewWorkspaceComposerCard.969a8bff66', 'Project')}
|
||||
{projectLabel ?? t('auto.components.NewWorkspaceComposerCard.969a8bff66', 'Project')}
|
||||
</label>
|
||||
{showAddProjectButton ? (
|
||||
<Tooltip>
|
||||
@@ -103,7 +103,7 @@ export function NewWorkspaceComposerProjectSection({
|
||||
size="icon-xs"
|
||||
onClick={onAddProject}
|
||||
className="size-5 shrink-0 rounded-sm text-muted-foreground hover:text-foreground"
|
||||
aria-label={translate(
|
||||
aria-label={t(
|
||||
'auto.components.NewWorkspaceComposerCard.d6b0a96f32',
|
||||
'Add project'
|
||||
)}
|
||||
@@ -112,7 +112,7 @@ export function NewWorkspaceComposerProjectSection({
|
||||
</Button>
|
||||
</TooltipTrigger>
|
||||
<TooltipContent side="top" sideOffset={6}>
|
||||
{translate('auto.components.NewWorkspaceComposerCard.d6b0a96f32', 'Add project')}
|
||||
{t('auto.components.NewWorkspaceComposerCard.d6b0a96f32', 'Add project')}
|
||||
</TooltipContent>
|
||||
</Tooltip>
|
||||
) : null}
|
||||
@@ -131,7 +131,7 @@ export function NewWorkspaceComposerProjectSection({
|
||||
onAddProject={onAddProject}
|
||||
placeholder={
|
||||
projectPlaceholder ??
|
||||
translate('auto.components.NewWorkspaceComposerCard.dccd26d4e4', 'Choose project')
|
||||
t('auto.components.NewWorkspaceComposerCard.dccd26d4e4', 'Choose project')
|
||||
}
|
||||
triggerClassName="h-9 w-full border-input text-sm focus:border-ring focus:ring-[3px] focus:ring-ring/50"
|
||||
invalid={Boolean(projectError)}
|
||||
@@ -144,7 +144,7 @@ export function NewWorkspaceComposerProjectSection({
|
||||
) : projectOptions.length === 0 ? (
|
||||
<p id={projectDescriptionId} className="text-[11px] text-muted-foreground">
|
||||
{emptyProjectMessage ??
|
||||
translate(
|
||||
t(
|
||||
'auto.components.NewWorkspaceComposerCard.addProjectBeforeWorkspace',
|
||||
'Add a project before creating a workspace.'
|
||||
)}
|
||||
@@ -155,7 +155,7 @@ export function NewWorkspaceComposerProjectSection({
|
||||
{shouldShowRunTargetPicker ? (
|
||||
<div className="min-w-0 space-y-1">
|
||||
<label className="flex h-5 min-w-0 items-center truncate text-xs font-medium text-muted-foreground">
|
||||
{translate('auto.components.NewWorkspaceComposerCard.runOn', 'Run on')}
|
||||
{t('auto.components.NewWorkspaceComposerCard.runOn', 'Run on')}
|
||||
</label>
|
||||
<RunTargetCombobox
|
||||
hostOptions={projectHostSetupOptions ?? []}
|
||||
@@ -189,11 +189,9 @@ export function NewWorkspaceComposerProjectSection({
|
||||
>
|
||||
<div className="min-w-0">
|
||||
<div className="truncate text-xs font-medium text-foreground">
|
||||
{translate(
|
||||
'auto.components.NewWorkspaceComposerCard.connectProject',
|
||||
'Connect {{name}}',
|
||||
{ name: selectedProjectName }
|
||||
)}
|
||||
{t('auto.components.NewWorkspaceComposerCard.connectProject', 'Connect {{name}}', {
|
||||
name: selectedProjectName
|
||||
})}
|
||||
</div>
|
||||
<div className="mt-0.5 text-[11px] text-muted-foreground">{sshStatusLabel}</div>
|
||||
</div>
|
||||
@@ -211,7 +209,7 @@ export function NewWorkspaceComposerProjectSection({
|
||||
<PlugZap className="size-3.5" />
|
||||
)}
|
||||
{selectedRepoConnectInProgress
|
||||
? translate('auto.components.NewWorkspaceComposerCard.f660aa1454', 'Connecting')
|
||||
? t('auto.components.NewWorkspaceComposerCard.f660aa1454', 'Connecting')
|
||||
: connectButtonLabel}
|
||||
</Button>
|
||||
</div>
|
||||
|
||||
@@ -5,7 +5,7 @@ import { Popover, PopoverAnchor, PopoverContent } from '@/components/ui/popover'
|
||||
import { cn } from '@/lib/utils'
|
||||
import { Tooltip, TooltipContent, TooltipTrigger } from '@/components/ui/tooltip'
|
||||
import type { NewWorkspaceProjectOption } from '@/lib/new-workspace-project-options'
|
||||
import { translate } from '@/i18n/i18n'
|
||||
import { useTranslate } from '@/i18n/use-translate'
|
||||
import {
|
||||
getAmbiguousProjectOptionIds,
|
||||
rankProjectOptions,
|
||||
@@ -43,6 +43,7 @@ export default function ProjectCombobox({
|
||||
invalid = false,
|
||||
describedBy
|
||||
}: ProjectComboboxProps): React.JSX.Element {
|
||||
const t = useTranslate()
|
||||
const [tooltipOpen, setTooltipOpen] = useState(false)
|
||||
const recentIds = useRecentProjectIds()
|
||||
// Derive keys from the displayed sections: folder grouping changes raw rank order, so keyboard selection must follow what users see.
|
||||
@@ -155,10 +156,7 @@ export default function ProjectCombobox({
|
||||
type="text"
|
||||
role="combobox"
|
||||
data-project-combobox-root="true"
|
||||
aria-label={translate(
|
||||
'auto.components.new.workspace.ProjectCombobox.label',
|
||||
'Project'
|
||||
)}
|
||||
aria-label={t('auto.components.new.workspace.ProjectCombobox.label', 'Project')}
|
||||
aria-expanded={open}
|
||||
aria-controls={listId}
|
||||
aria-autocomplete="list"
|
||||
@@ -190,7 +188,7 @@ export default function ProjectCombobox({
|
||||
<button
|
||||
type="button"
|
||||
tabIndex={-1}
|
||||
aria-label={translate(
|
||||
aria-label={t(
|
||||
'auto.components.new.workspace.ProjectCombobox.browse',
|
||||
'Browse projects'
|
||||
)}
|
||||
@@ -245,10 +243,7 @@ export default function ProjectCombobox({
|
||||
<div
|
||||
id={listId}
|
||||
role="listbox"
|
||||
aria-label={translate(
|
||||
'auto.components.new.workspace.ProjectCombobox.listLabel',
|
||||
'Projects'
|
||||
)}
|
||||
aria-label={t('auto.components.new.workspace.ProjectCombobox.listLabel', 'Projects')}
|
||||
className="flex min-h-0 flex-col"
|
||||
>
|
||||
{/* Presentation preserves listbox ownership through this scrolling wrapper. */}
|
||||
@@ -261,11 +256,11 @@ export default function ProjectCombobox({
|
||||
// Match row height so empty results stay visually neutral.
|
||||
<p className="flex h-8 items-center justify-center px-2 text-sm text-muted-foreground">
|
||||
{options.length === 0
|
||||
? translate(
|
||||
? t(
|
||||
'auto.components.new.workspace.ProjectCombobox.noProjects',
|
||||
'No projects yet.'
|
||||
)
|
||||
: translate(
|
||||
: t(
|
||||
'auto.components.new.workspace.ProjectCombobox.empty',
|
||||
'No projects match your search.'
|
||||
)}
|
||||
@@ -317,10 +312,7 @@ export default function ProjectCombobox({
|
||||
<FolderPlus className="size-3.5 text-muted-foreground" />
|
||||
</span>
|
||||
<span className="truncate">
|
||||
{translate(
|
||||
'auto.components.new.workspace.ProjectCombobox.addProject',
|
||||
'Add project'
|
||||
)}
|
||||
{t('auto.components.new.workspace.ProjectCombobox.addProject', 'Add project')}
|
||||
</span>
|
||||
</div>
|
||||
) : null}
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
import React, { useCallback, useMemo, useState } from 'react'
|
||||
import { Popover, PopoverContent } from '@/components/ui/popover'
|
||||
import { cn } from '@/lib/utils'
|
||||
import { translate } from '@/i18n/i18n'
|
||||
import { useTranslate } from '@/i18n/use-translate'
|
||||
import {
|
||||
getProjectHostSetupSelectionKey,
|
||||
type NeedsSetupProjectHostOption,
|
||||
@@ -53,6 +53,7 @@ export default function RunTargetCombobox({
|
||||
onConnectHost,
|
||||
onSetLocation
|
||||
}: RunTargetComboboxProps): React.JSX.Element {
|
||||
const t = useTranslate()
|
||||
const [submenu, setSubmenu] = useState<'recipes' | 'add-host' | null>(null)
|
||||
// Track in-flight connects per host so one stalling connect never blocks the others.
|
||||
const [connectingHostIds, setConnectingHostIds] = useState<ReadonlySet<string>>(() => new Set())
|
||||
@@ -202,9 +203,11 @@ export default function RunTargetCombobox({
|
||||
)
|
||||
|
||||
const fieldLabel = selectedRecipe
|
||||
? `${getEphemeralVmLabel()} / ${selectedRecipe.name}`
|
||||
? `${getEphemeralVmLabel(t)} / ${selectedRecipe.name}`
|
||||
: (selectedHost?.label ?? '')
|
||||
const fieldDetail = selectedRecipe ? getRecipeDetail(selectedRecipe) : (selectedHost?.path ?? '')
|
||||
const fieldDetail = selectedRecipe
|
||||
? getRecipeDetail(selectedRecipe, t)
|
||||
: (selectedHost?.path ?? '')
|
||||
|
||||
return (
|
||||
<Popover open={open} onOpenChange={handleOpenChange}>
|
||||
@@ -252,10 +255,7 @@ export default function RunTargetCombobox({
|
||||
<div
|
||||
id={listId}
|
||||
role="listbox"
|
||||
aria-label={translate(
|
||||
'auto.components.new.workspace.RunTargetCombobox.listLabel',
|
||||
'Run targets'
|
||||
)}
|
||||
aria-label={t('auto.components.new.workspace.RunTargetCombobox.listLabel', 'Run targets')}
|
||||
className="flex min-h-0 flex-col"
|
||||
>
|
||||
<div
|
||||
@@ -265,7 +265,7 @@ export default function RunTargetCombobox({
|
||||
>
|
||||
{rows.filter((row) => row.kind !== 'add-host').length === 0 ? (
|
||||
<p className="flex h-8 items-center justify-center px-2 text-sm text-muted-foreground">
|
||||
{translate(
|
||||
{t(
|
||||
'auto.components.NewWorkspaceComposerCard.noRunTargets',
|
||||
'No run targets are ready for this project.'
|
||||
)}
|
||||
|
||||
@@ -4,7 +4,7 @@ import { ChevronDown, Cloud } from 'lucide-react'
|
||||
import { PopoverAnchor } from '@/components/ui/popover'
|
||||
import { cn } from '@/lib/utils'
|
||||
import { Tooltip, TooltipContent, TooltipTrigger } from '@/components/ui/tooltip'
|
||||
import { translate } from '@/i18n/i18n'
|
||||
import { useTranslate } from '@/i18n/use-translate'
|
||||
import type { ExecutionHostId } from '../../../../shared/execution-host'
|
||||
import { HostRowIcon } from './RunTargetComboboxRow'
|
||||
import { COMBOBOX_FIELD_SHELL } from './type-ahead-combobox-styles'
|
||||
@@ -41,6 +41,7 @@ export default function RunTargetField({
|
||||
inputRef: React.RefObject<HTMLInputElement | null>
|
||||
onKeyDown: (event: React.KeyboardEvent<HTMLInputElement>) => void
|
||||
}): React.JSX.Element {
|
||||
const t = useTranslate()
|
||||
const [tooltipOpen, setTooltipOpen] = useState(false)
|
||||
return (
|
||||
<Tooltip open={tooltipOpen && committed && !open} onOpenChange={setTooltipOpen}>
|
||||
@@ -69,10 +70,7 @@ export default function RunTargetField({
|
||||
type="text"
|
||||
role="combobox"
|
||||
data-run-target-combobox-root="true"
|
||||
aria-label={translate(
|
||||
'auto.components.new.workspace.RunTargetCombobox.label',
|
||||
'Run on'
|
||||
)}
|
||||
aria-label={t('auto.components.new.workspace.RunTargetCombobox.label', 'Run on')}
|
||||
aria-expanded={open}
|
||||
aria-controls={listId}
|
||||
aria-autocomplete="list"
|
||||
@@ -81,10 +79,7 @@ export default function RunTargetField({
|
||||
placeholder={
|
||||
committed
|
||||
? ''
|
||||
: translate(
|
||||
'auto.components.NewWorkspaceComposerCard.chooseRunTarget',
|
||||
'Choose target'
|
||||
)
|
||||
: t('auto.components.NewWorkspaceComposerCard.chooseRunTarget', 'Choose target')
|
||||
}
|
||||
onChange={(event) => onQueryChange(event.target.value)}
|
||||
onFocus={onOpenRequest}
|
||||
@@ -106,7 +101,7 @@ export default function RunTargetField({
|
||||
<button
|
||||
type="button"
|
||||
tabIndex={-1}
|
||||
aria-label={translate(
|
||||
aria-label={t(
|
||||
'auto.components.new.workspace.RunTargetCombobox.browse',
|
||||
'Browse run targets'
|
||||
)}
|
||||
|
||||
@@ -3,7 +3,7 @@ import { AlertTriangle, LoaderCircle } from 'lucide-react'
|
||||
import { Button } from '@/components/ui/button'
|
||||
import { Tooltip, TooltipContent, TooltipTrigger } from '@/components/ui/tooltip'
|
||||
import type { ExecutionHostId } from '../../../../shared/execution-host'
|
||||
import { translate } from '@/i18n/i18n'
|
||||
import { useTranslate } from '@/i18n/use-translate'
|
||||
import { HostRowIcon } from '../host-row-icon'
|
||||
|
||||
/** Glyph for a host that can't run yet: connecting, broken, or merely dormant. */
|
||||
@@ -32,6 +32,7 @@ export function ConnectHostButton({
|
||||
connecting: boolean
|
||||
onConnect: () => void
|
||||
}): React.JSX.Element {
|
||||
const t = useTranslate()
|
||||
return (
|
||||
<Button
|
||||
type="button"
|
||||
@@ -53,10 +54,10 @@ export function ConnectHostButton({
|
||||
{connecting ? (
|
||||
<>
|
||||
<LoaderCircle className="size-3 animate-spin" />
|
||||
{translate('auto.components.NewWorkspaceComposerCard.connectingHost', 'Connecting…')}
|
||||
{t('auto.components.NewWorkspaceComposerCard.connectingHost', 'Connecting…')}
|
||||
</>
|
||||
) : (
|
||||
translate('auto.components.NewWorkspaceComposerCard.connectHost', 'Connect')
|
||||
t('auto.components.NewWorkspaceComposerCard.connectHost', 'Connect')
|
||||
)}
|
||||
</Button>
|
||||
)
|
||||
@@ -69,10 +70,8 @@ export function SetLocationButton({
|
||||
hostLabel: string
|
||||
onSetLocation: () => void
|
||||
}): React.JSX.Element {
|
||||
const label = translate(
|
||||
'auto.components.NewWorkspaceComposerCard.setLocation',
|
||||
'Set project location'
|
||||
)
|
||||
const t = useTranslate()
|
||||
const label = t('auto.components.NewWorkspaceComposerCard.setLocation', 'Set project location')
|
||||
return (
|
||||
<Tooltip>
|
||||
<TooltipTrigger asChild>
|
||||
@@ -81,7 +80,7 @@ export function SetLocationButton({
|
||||
variant="outline"
|
||||
size="xs"
|
||||
className="ml-auto shrink-0 self-center"
|
||||
aria-label={translate(
|
||||
aria-label={t(
|
||||
'auto.components.NewWorkspaceComposerCard.setLocationOnHost',
|
||||
'Set project location on {{host}}',
|
||||
{ host: hostLabel }
|
||||
@@ -100,7 +99,7 @@ export function SetLocationButton({
|
||||
</Button>
|
||||
</TooltipTrigger>
|
||||
<TooltipContent side="top" sideOffset={6}>
|
||||
{translate(
|
||||
{t(
|
||||
'auto.components.NewWorkspaceComposerCard.setLocationTooltip',
|
||||
'Choose a folder or clone this project onto {{host}}.',
|
||||
{ host: hostLabel }
|
||||
|
||||
@@ -2,7 +2,7 @@ import React from 'react'
|
||||
import { ChevronDown, Cloud, Plus, Server } from 'lucide-react'
|
||||
import { Popover, PopoverAnchor, PopoverContent } from '@/components/ui/popover'
|
||||
import { cn } from '@/lib/utils'
|
||||
import { translate } from '@/i18n/i18n'
|
||||
import { useTranslate } from '@/i18n/use-translate'
|
||||
import { RunTargetRow } from './RunTargetComboboxRow'
|
||||
import {
|
||||
getEphemeralVmLabel,
|
||||
@@ -34,6 +34,7 @@ export function RecipesSubmenuRow({
|
||||
onArm: () => void
|
||||
onSelectRecipe: (recipeId: string) => void
|
||||
}): React.JSX.Element {
|
||||
const t = useTranslate()
|
||||
const [hoveredKey, setHoveredKey] = React.useState<string | null>(null)
|
||||
return (
|
||||
<Popover open={open} onOpenChange={onOpenChange}>
|
||||
@@ -41,8 +42,8 @@ export function RecipesSubmenuRow({
|
||||
<div>
|
||||
<RunTargetRow
|
||||
icon={<Cloud className="size-3.5 shrink-0 text-muted-foreground" />}
|
||||
label={getEphemeralVmLabel()}
|
||||
detail={translate(
|
||||
label={getEphemeralVmLabel(t)}
|
||||
detail={t(
|
||||
'auto.components.NewWorkspaceComposerCard.perWorkspaceEnvHint',
|
||||
'Provision an on-demand environment from a recipe'
|
||||
)}
|
||||
@@ -65,7 +66,7 @@ export function RecipesSubmenuRow({
|
||||
{/* Nested rows own hover so pointer movement updates their highlight too. */}
|
||||
<div
|
||||
role="listbox"
|
||||
aria-label={getEphemeralVmLabel()}
|
||||
aria-label={getEphemeralVmLabel(t)}
|
||||
onMouseLeave={() => setHoveredKey(null)}
|
||||
>
|
||||
{recipes.map((recipe) => (
|
||||
@@ -73,7 +74,7 @@ export function RecipesSubmenuRow({
|
||||
key={recipe.id}
|
||||
icon={<Cloud className="size-3.5 shrink-0 text-muted-foreground" />}
|
||||
label={recipe.name}
|
||||
detail={getRecipeDetail(recipe)}
|
||||
detail={getRecipeDetail(recipe, t)}
|
||||
armed={hoveredKey === recipe.id}
|
||||
current={recipe.id === selectedRecipeId}
|
||||
optionId={undefined}
|
||||
@@ -105,8 +106,9 @@ export function AddHostSubmenuRow({
|
||||
onAddSshHost?: () => void
|
||||
onAddRemoteServer?: () => void
|
||||
}): React.JSX.Element {
|
||||
const t = useTranslate()
|
||||
const [hoveredKey, setHoveredKey] = React.useState<string | null>(null)
|
||||
const addHostLabel = translate('auto.components.NewWorkspaceComposerCard.addHost', 'Add host')
|
||||
const addHostLabel = t('auto.components.NewWorkspaceComposerCard.addHost', 'Add host')
|
||||
return (
|
||||
<Popover open={open} onOpenChange={onOpenChange}>
|
||||
<PopoverAnchor asChild>
|
||||
@@ -146,11 +148,8 @@ export function AddHostSubmenuRow({
|
||||
{onAddSshHost ? (
|
||||
<RunTargetRow
|
||||
icon={<Server className="size-3.5 shrink-0 text-muted-foreground" />}
|
||||
label={translate(
|
||||
'auto.components.NewWorkspaceComposerCard.addSshHost',
|
||||
'Add SSH host'
|
||||
)}
|
||||
detail={translate(
|
||||
label={t('auto.components.NewWorkspaceComposerCard.addSshHost', 'Add SSH host')}
|
||||
detail={t(
|
||||
'auto.components.NewWorkspaceComposerCard.addSshHostHint',
|
||||
'Use an existing machine over SSH'
|
||||
)}
|
||||
@@ -165,11 +164,11 @@ export function AddHostSubmenuRow({
|
||||
{onAddRemoteServer ? (
|
||||
<RunTargetRow
|
||||
icon={<Cloud className="size-3.5 shrink-0 text-muted-foreground" />}
|
||||
label={translate(
|
||||
label={t(
|
||||
'auto.components.NewWorkspaceComposerCard.addRemoteOrcaServer',
|
||||
'Add Remote Orca Server'
|
||||
)}
|
||||
detail={translate(
|
||||
detail={t(
|
||||
'auto.components.NewWorkspaceComposerCard.addRemoteOrcaServerHint',
|
||||
'Pair another Orca runtime'
|
||||
)}
|
||||
|
||||
@@ -7,7 +7,7 @@ import {
|
||||
DialogHeader,
|
||||
DialogTitle
|
||||
} from '@/components/ui/dialog'
|
||||
import { translate } from '@/i18n/i18n'
|
||||
import { useTranslate } from '@/i18n/use-translate'
|
||||
import { useAppStore } from '@/store'
|
||||
import { CreateProjectParentBrowser } from '@/components/sidebar/project-admission/create/CreateProjectLocationField'
|
||||
import {
|
||||
@@ -99,6 +99,7 @@ function SetProjectLocationDialogBody({
|
||||
exitHostBrowser: RefObject<(() => boolean) | null>
|
||||
onReady: (setupId: string) => void
|
||||
}): React.JSX.Element {
|
||||
const t = useTranslate()
|
||||
const setupProjectExistingFolder = useAppStore((state) => state.setupProjectExistingFolder)
|
||||
const setupProjectClone = useAppStore((state) => state.setupProjectClone)
|
||||
const [view, setView] = useState<DialogView>('choose')
|
||||
@@ -206,13 +207,13 @@ function SetProjectLocationDialogBody({
|
||||
<>
|
||||
<DialogHeader>
|
||||
<DialogTitle>
|
||||
{translate(
|
||||
{t(
|
||||
'auto.components.new.workspace.SetProjectLocationDialog.title',
|
||||
'Set project location'
|
||||
)}
|
||||
</DialogTitle>
|
||||
<DialogDescription>
|
||||
{translate(
|
||||
{t(
|
||||
'auto.components.new.workspace.SetProjectLocationDialog.description',
|
||||
'Choose where {{project}} lives on {{host}}.',
|
||||
{ project: projectName, host: option.label }
|
||||
@@ -223,11 +224,11 @@ function SetProjectLocationDialogBody({
|
||||
<div className="space-y-2">
|
||||
<LocationActionButton
|
||||
icon={FolderOpen}
|
||||
title={translate(
|
||||
title={t(
|
||||
'auto.components.new.workspace.SetProjectLocationDialog.browseFolder',
|
||||
'Browse folder'
|
||||
)}
|
||||
description={translate(
|
||||
description={t(
|
||||
'auto.components.new.workspace.SetProjectLocationDialog.browseFolderHelp',
|
||||
'Use an existing checkout or folder on this host.'
|
||||
)}
|
||||
@@ -242,11 +243,11 @@ function SetProjectLocationDialogBody({
|
||||
{canClone ? (
|
||||
<LocationActionButton
|
||||
icon={Download}
|
||||
title={translate(
|
||||
title={t(
|
||||
'auto.components.new.workspace.SetProjectLocationDialog.cloneFromUrl',
|
||||
'Clone from URL'
|
||||
)}
|
||||
description={translate(
|
||||
description={t(
|
||||
'auto.components.new.workspace.SetProjectLocationDialog.cloneFromUrlHelp',
|
||||
'Clone this repository onto {{host}}.',
|
||||
{ host: option.label }
|
||||
|
||||
@@ -8,7 +8,7 @@ import {
|
||||
SelectTrigger,
|
||||
SelectValue
|
||||
} from '@/components/ui/select'
|
||||
import { translate } from '@/i18n/i18n'
|
||||
import { useTranslate } from '@/i18n/use-translate'
|
||||
import { cn } from '@/lib/utils'
|
||||
import type { RepoKind } from '../../../../shared/repo-types'
|
||||
|
||||
@@ -31,20 +31,18 @@ export function ExistingFolderForm({
|
||||
onBrowse: () => void
|
||||
onSubmit: () => void
|
||||
}): React.JSX.Element {
|
||||
const t = useTranslate()
|
||||
return (
|
||||
<div className="space-y-3">
|
||||
<StepBackButton
|
||||
onBack={onBack}
|
||||
label={translate(
|
||||
'auto.components.settings.RepositoryPane.existingFolder',
|
||||
'Existing folder'
|
||||
)}
|
||||
label={t('auto.components.settings.RepositoryPane.existingFolder', 'Existing folder')}
|
||||
/>
|
||||
<div className="flex gap-2">
|
||||
<Input
|
||||
value={setupPath}
|
||||
onChange={(event) => onPathChange(event.target.value)}
|
||||
placeholder={translate(
|
||||
placeholder={t(
|
||||
'auto.components.settings.RepositoryPane.setupExistingFolderPathPlaceholder',
|
||||
'/path/to/project/on/host'
|
||||
)}
|
||||
@@ -57,7 +55,7 @@ export function ExistingFolderForm({
|
||||
size="icon"
|
||||
className="size-9 shrink-0"
|
||||
onClick={onBrowse}
|
||||
aria-label={translate(
|
||||
aria-label={t(
|
||||
'auto.components.sidebar.CreateProjectLocationField.f520f83a97',
|
||||
'Browse host filesystem'
|
||||
)}
|
||||
@@ -71,10 +69,10 @@ export function ExistingFolderForm({
|
||||
</SelectTrigger>
|
||||
<SelectContent>
|
||||
<SelectItem value="git">
|
||||
{translate('auto.components.settings.RepositoryPane.setupKindGit', 'Git repo')}
|
||||
{t('auto.components.settings.RepositoryPane.setupKindGit', 'Git repo')}
|
||||
</SelectItem>
|
||||
<SelectItem value="folder">
|
||||
{translate('auto.components.settings.RepositoryPane.setupKindFolder', 'Folder')}
|
||||
{t('auto.components.settings.RepositoryPane.setupKindFolder', 'Folder')}
|
||||
</SelectItem>
|
||||
</SelectContent>
|
||||
</Select>
|
||||
@@ -87,8 +85,8 @@ export function ExistingFolderForm({
|
||||
>
|
||||
{isSubmitting ? <LoaderCircle className="size-3.5 animate-spin" /> : null}
|
||||
{isSubmitting
|
||||
? translate('auto.components.settings.RepositoryPane.settingUpHost', 'Importing...')
|
||||
: translate(
|
||||
? t('auto.components.settings.RepositoryPane.settingUpHost', 'Importing...')
|
||||
: t(
|
||||
'auto.components.new.workspace.SetProjectLocationDialog.saveLocation',
|
||||
'Set location'
|
||||
)}
|
||||
@@ -117,16 +115,17 @@ export function CloneForm({
|
||||
onBrowse: () => void
|
||||
onSubmit: () => void
|
||||
}): React.JSX.Element {
|
||||
const t = useTranslate()
|
||||
return (
|
||||
<div className="space-y-3">
|
||||
<StepBackButton
|
||||
onBack={onBack}
|
||||
label={translate('auto.components.settings.RepositoryPane.cloneFromUrl', 'Clone from URL')}
|
||||
label={t('auto.components.settings.RepositoryPane.cloneFromUrl', 'Clone from URL')}
|
||||
/>
|
||||
<Input
|
||||
value={cloneUrl}
|
||||
onChange={(event) => onCloneUrlChange(event.target.value)}
|
||||
placeholder={translate(
|
||||
placeholder={t(
|
||||
'auto.components.settings.RepositoryPane.cloneUrlPlaceholder',
|
||||
'Repository URL'
|
||||
)}
|
||||
@@ -137,7 +136,7 @@ export function CloneForm({
|
||||
<Input
|
||||
value={cloneDestination}
|
||||
onChange={(event) => onCloneDestinationChange(event.target.value)}
|
||||
placeholder={translate(
|
||||
placeholder={t(
|
||||
'auto.components.settings.RepositoryPane.cloneDestinationPlaceholder',
|
||||
'/destination/on/host'
|
||||
)}
|
||||
@@ -150,7 +149,7 @@ export function CloneForm({
|
||||
size="icon"
|
||||
className="size-9 shrink-0"
|
||||
onClick={onBrowse}
|
||||
aria-label={translate(
|
||||
aria-label={t(
|
||||
'auto.components.sidebar.CreateProjectLocationField.f520f83a97',
|
||||
'Browse host filesystem'
|
||||
)}
|
||||
@@ -167,8 +166,8 @@ export function CloneForm({
|
||||
>
|
||||
{isSubmitting ? <LoaderCircle className="size-3.5 animate-spin" /> : null}
|
||||
{isSubmitting
|
||||
? translate('auto.components.settings.RepositoryPane.cloningHost', 'Cloning...')
|
||||
: translate('auto.components.settings.RepositoryPane.cloneHost', 'Clone')}
|
||||
? t('auto.components.settings.RepositoryPane.cloningHost', 'Cloning...')
|
||||
: t('auto.components.settings.RepositoryPane.cloneHost', 'Clone')}
|
||||
</Button>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
@@ -2,10 +2,12 @@ import React from 'react'
|
||||
import { renderSmartWorkspaceNameField } from './smart-workspace-name-field-surface'
|
||||
import type { SmartWorkspaceNameFieldProps } from './smart-workspace-name-field-model'
|
||||
import { useSmartWorkspaceNameFieldController } from './use-smart-workspace-name-field-controller'
|
||||
import { useTranslate } from '../../i18n/use-translate'
|
||||
|
||||
export default function SmartWorkspaceNameField(
|
||||
props: SmartWorkspaceNameFieldProps
|
||||
): React.JSX.Element {
|
||||
const t = useTranslate()
|
||||
const controller = useSmartWorkspaceNameFieldController(props)
|
||||
return renderSmartWorkspaceNameField(controller)
|
||||
return renderSmartWorkspaceNameField(controller, t)
|
||||
}
|
||||
|
||||
@@ -2,7 +2,7 @@ import React from 'react'
|
||||
import { isIntegrationCredentialDecryptionError } from '../../../../shared/integration-credential-errors'
|
||||
import { Button } from '@/components/ui/button'
|
||||
import { CommandGroup, CommandItem, CommandList } from '@/components/ui/command'
|
||||
import { translate } from '@/i18n/i18n'
|
||||
import { useTranslate } from '@/i18n/use-translate'
|
||||
import { cn } from '@/lib/utils'
|
||||
import { getSmartWorkspaceEmptyHint } from '../../../../shared/new-workspace/smart-workspace-source-results'
|
||||
import { RowIcon, RowLabel } from './smart-workspace-source-row-content'
|
||||
@@ -43,6 +43,7 @@ export function SmartWorkspaceSourceResultList({
|
||||
isRowDisabled?: (row: RowEntry) => boolean
|
||||
focusBehavior?: 'page'
|
||||
}): React.JSX.Element {
|
||||
const t = useTranslate()
|
||||
const {
|
||||
mode,
|
||||
mrStateFilters,
|
||||
@@ -114,13 +115,13 @@ export function SmartWorkspaceSourceResultList({
|
||||
{credentialError ? (
|
||||
<>
|
||||
<p className="font-medium text-foreground">
|
||||
{translate(
|
||||
{t(
|
||||
'auto.components.new.workspace.SmartWorkspaceNameField.linearLoginUnreadableTitle',
|
||||
'Reconnect Linear'
|
||||
)}
|
||||
</p>
|
||||
<p className="mt-0.5 text-muted-foreground">
|
||||
{translate(
|
||||
{t(
|
||||
'auto.components.new.workspace.SmartWorkspaceNameField.linearLoginUnreadableBody',
|
||||
'Unable to read saved Linear login. Reconnect it in Settings.'
|
||||
)}
|
||||
@@ -157,7 +158,7 @@ export function SmartWorkspaceSourceResultList({
|
||||
{jiraSource.intent
|
||||
? null
|
||||
: mode === 'linear' && linearStatusChecked && !linearStatus.connected
|
||||
? translate(
|
||||
? t(
|
||||
'auto.components.new.workspace.SmartWorkspaceNameField.3e8bb1176a',
|
||||
'Connect Linear in Settings to search issues.'
|
||||
)
|
||||
@@ -188,7 +189,7 @@ export function SmartWorkspaceSourceResultList({
|
||||
/>
|
||||
{isLinked?.(row) ? (
|
||||
<span className="ml-auto shrink-0 text-xs text-muted-foreground">
|
||||
{translate('workspace.links.linkedResult', 'Linked')}
|
||||
{t('workspace.links.linkedResult', 'Linked')}
|
||||
</span>
|
||||
) : null}
|
||||
</CommandItem>
|
||||
|
||||
@@ -1,24 +1,25 @@
|
||||
import { translate } from '@/i18n/i18n'
|
||||
import type { Translate } from '@/i18n/use-translate'
|
||||
import type { WorkspaceSetupLabels } from './workspace-setup-section-model'
|
||||
|
||||
export function getDirectWorkspaceSetupLabels(): WorkspaceSetupLabels {
|
||||
const run = translate('components.newWorkspace.directSetup.run', 'Run setup')
|
||||
const skip = translate('components.newWorkspace.directSetup.skip', 'Skip setup')
|
||||
export function getDirectWorkspaceSetupLabels(t: Translate = translate): WorkspaceSetupLabels {
|
||||
const run = t('components.newWorkspace.directSetup.run', 'Run setup')
|
||||
const skip = t('components.newWorkspace.directSetup.skip', 'Skip setup')
|
||||
return {
|
||||
kinds: {
|
||||
setup: {
|
||||
configLabel: translate('components.newWorkspace.directSetup.setupScript', 'Setup script'),
|
||||
askLabel: translate('components.newWorkspace.directSetup.askRunSetup', 'Run setup now?'),
|
||||
configLabel: t('components.newWorkspace.directSetup.setupScript', 'Setup script'),
|
||||
askLabel: t('components.newWorkspace.directSetup.askRunSetup', 'Run setup now?'),
|
||||
runLabel: run,
|
||||
runButtonLabel: run,
|
||||
skipButtonLabel: skip
|
||||
},
|
||||
'default-tabs': {
|
||||
configLabel: translate(
|
||||
configLabel: t(
|
||||
'components.newWorkspace.directSetup.defaultTabCommands',
|
||||
'Default tab commands'
|
||||
),
|
||||
askLabel: translate(
|
||||
askLabel: t(
|
||||
'components.newWorkspace.directSetup.askRunDefaultTabs',
|
||||
'Run default tab commands now?'
|
||||
),
|
||||
@@ -27,11 +28,11 @@ export function getDirectWorkspaceSetupLabels(): WorkspaceSetupLabels {
|
||||
skipButtonLabel: skip
|
||||
},
|
||||
'setup-and-default-tabs': {
|
||||
configLabel: translate(
|
||||
configLabel: t(
|
||||
'components.newWorkspace.directSetup.setupAndDefaultTabs',
|
||||
'Setup and default tab commands'
|
||||
),
|
||||
askLabel: translate(
|
||||
askLabel: t(
|
||||
'components.newWorkspace.directSetup.askRunSetupAndDefaultTabs',
|
||||
'Run setup and default tab commands now?'
|
||||
),
|
||||
@@ -41,15 +42,15 @@ export function getDirectWorkspaceSetupLabels(): WorkspaceSetupLabels {
|
||||
}
|
||||
},
|
||||
sources: {
|
||||
yaml: translate('components.newWorkspace.directSetup.yamlSource', 'orca.yaml'),
|
||||
both: translate('components.newWorkspace.directSetup.bothSources', 'orca.yaml + local'),
|
||||
local: translate('components.newWorkspace.directSetup.localSource', 'local settings')
|
||||
yaml: t('components.newWorkspace.directSetup.yamlSource', 'orca.yaml'),
|
||||
both: t('components.newWorkspace.directSetup.bothSources', 'orca.yaml + local'),
|
||||
local: t('components.newWorkspace.directSetup.localSource', 'local settings')
|
||||
},
|
||||
help: translate(
|
||||
help: t(
|
||||
'components.newWorkspace.directSetup.description',
|
||||
'Choose whether to run setup before creating this workspace.'
|
||||
),
|
||||
checking: translate(
|
||||
checking: t(
|
||||
'components.newWorkspace.directSetup.description',
|
||||
'Choose whether to run setup before creating this workspace.'
|
||||
)
|
||||
|
||||
@@ -1,6 +1,7 @@
|
||||
import type { NewWorkspaceProjectOption } from '@/lib/new-workspace-project-options'
|
||||
import { isNewWorkspaceProjectOptionQueryTooLarge } from '@/lib/new-workspace-project-options'
|
||||
import { translate } from '@/i18n/i18n'
|
||||
import type { Translate } from '@/i18n/use-translate'
|
||||
|
||||
export type ScoredProjectOption = {
|
||||
option: NewWorkspaceProjectOption
|
||||
@@ -94,7 +95,8 @@ const RECENT_LIMIT = 4
|
||||
export function sectionProjectOptions(
|
||||
matches: readonly ScoredProjectOption[],
|
||||
query: string,
|
||||
recentIds: readonly string[]
|
||||
recentIds: readonly string[],
|
||||
t: Translate = translate
|
||||
): ProjectOptionSection[] {
|
||||
if (query.trim() !== '' || matches.length < SECTION_THRESHOLD) {
|
||||
return [{ key: 'results', heading: null, items: [...matches] }]
|
||||
@@ -108,20 +110,17 @@ export function sectionProjectOptions(
|
||||
const sections: ProjectOptionSection[] = [
|
||||
{
|
||||
key: 'recent',
|
||||
heading: translate('auto.components.new.workspace.projectCombobox.recentHeading', 'Recent'),
|
||||
heading: t('auto.components.new.workspace.projectCombobox.recentHeading', 'Recent'),
|
||||
items: recent
|
||||
},
|
||||
{
|
||||
key: 'projects',
|
||||
heading: translate(
|
||||
'auto.components.new.workspace.projectCombobox.projectsHeading',
|
||||
'Projects'
|
||||
),
|
||||
heading: t('auto.components.new.workspace.projectCombobox.projectsHeading', 'Projects'),
|
||||
items: matches.filter((m) => m.option.kind === 'project' && !recentSet.has(m.option.id))
|
||||
},
|
||||
{
|
||||
key: 'folders',
|
||||
heading: translate('auto.components.new.workspace.projectCombobox.foldersHeading', 'Folders'),
|
||||
heading: t('auto.components.new.workspace.projectCombobox.foldersHeading', 'Folders'),
|
||||
// Exclude recent rows from folders too; duplicate options would desynchronize arming/rendering.
|
||||
items: matches.filter((m) => m.option.kind === 'project-group' && !recentSet.has(m.option.id))
|
||||
}
|
||||
|
||||
@@ -6,6 +6,7 @@ import {
|
||||
type ReadyProjectHostSetupOption
|
||||
} from '@/lib/project-host-setup-options'
|
||||
import { translate } from '@/i18n/i18n'
|
||||
import type { Translate } from '@/i18n/use-translate'
|
||||
import { boundHostList } from '@/components/host-picker/bounded-host-list'
|
||||
|
||||
export type EphemeralVmRecipeOption = NonNullable<OrcaHooks['environmentRecipes']>[number]
|
||||
@@ -20,11 +21,8 @@ export type RunTargetRowModel =
|
||||
| { key: typeof RUN_TARGET_RECIPES_KEY; kind: 'recipes' }
|
||||
| { key: typeof RUN_TARGET_ADD_HOST_KEY; kind: 'add-host' }
|
||||
|
||||
export function getEphemeralVmLabel(): string {
|
||||
return translate(
|
||||
'auto.components.NewWorkspaceComposerCard.ephemeralVm',
|
||||
'Per-Workspace Environment'
|
||||
)
|
||||
export function getEphemeralVmLabel(t: Translate = translate): string {
|
||||
return t('auto.components.NewWorkspaceComposerCard.ephemeralVm', 'Per-Workspace Environment')
|
||||
}
|
||||
|
||||
export function getRecipeCommandDisplay(command: string): string {
|
||||
@@ -33,21 +31,21 @@ export function getRecipeCommandDisplay(command: string): string {
|
||||
return quoted?.[1] ?? trimmed.split(/\s+/)[0] ?? trimmed
|
||||
}
|
||||
|
||||
export function getRecipeDestroyLabel(recipe: EphemeralVmRecipeOption): string {
|
||||
export function getRecipeDestroyLabel(
|
||||
recipe: EphemeralVmRecipeOption,
|
||||
t: Translate = translate
|
||||
): string {
|
||||
if (recipe.destroyDisabled) {
|
||||
return translate('auto.components.NewWorkspaceComposerCard.destroyDisabled', 'destroy disabled')
|
||||
return t('auto.components.NewWorkspaceComposerCard.destroyDisabled', 'destroy disabled')
|
||||
}
|
||||
if (recipe.destroy) {
|
||||
return translate(
|
||||
'auto.components.NewWorkspaceComposerCard.destroyConfigured',
|
||||
'destroy configured'
|
||||
)
|
||||
return t('auto.components.NewWorkspaceComposerCard.destroyConfigured', 'destroy configured')
|
||||
}
|
||||
return translate('auto.components.NewWorkspaceComposerCard.noDestroyConfigured', 'no destroy')
|
||||
return t('auto.components.NewWorkspaceComposerCard.noDestroyConfigured', 'no destroy')
|
||||
}
|
||||
|
||||
export function getRecipeDetail(recipe: EphemeralVmRecipeOption): string {
|
||||
return `${getRecipeCommandDisplay(recipe.create)} · ${getRecipeDestroyLabel(recipe)}`
|
||||
export function getRecipeDetail(recipe: EphemeralVmRecipeOption, t?: Translate): string {
|
||||
return `${getRecipeCommandDisplay(recipe.create)} · ${getRecipeDestroyLabel(recipe, t)}`
|
||||
}
|
||||
|
||||
function matches(haystack: string, query: string): boolean {
|
||||
@@ -101,13 +99,14 @@ function buildHostRows(
|
||||
|
||||
function matchRecipes(
|
||||
recipes: readonly EphemeralVmRecipeOption[],
|
||||
query: string
|
||||
query: string,
|
||||
t?: Translate
|
||||
): EphemeralVmRecipeOption[] {
|
||||
return recipes.filter(
|
||||
(recipe) =>
|
||||
query === '' ||
|
||||
matches(recipe.name, query) ||
|
||||
matches(getEphemeralVmLabel(), query) ||
|
||||
matches(getEphemeralVmLabel(t), query) ||
|
||||
matches(recipe.description ?? '', query)
|
||||
)
|
||||
}
|
||||
@@ -124,24 +123,27 @@ function buildSubmenuRows(hasRecipes: boolean, hasAddHost: boolean): RunTargetRo
|
||||
}
|
||||
|
||||
/** Filter host/recipe rows but always keep Add host reachable; other submenus require matching contents. */
|
||||
export function buildRunTargetRows({
|
||||
hostOptions,
|
||||
recipes,
|
||||
query,
|
||||
hasAddHost
|
||||
}: {
|
||||
hostOptions: readonly ProjectHostSetupOption[]
|
||||
recipes: readonly EphemeralVmRecipeOption[]
|
||||
query: string
|
||||
hasAddHost: boolean
|
||||
}): {
|
||||
export function buildRunTargetRows(
|
||||
{
|
||||
hostOptions,
|
||||
recipes,
|
||||
query,
|
||||
hasAddHost
|
||||
}: {
|
||||
hostOptions: readonly ProjectHostSetupOption[]
|
||||
recipes: readonly EphemeralVmRecipeOption[]
|
||||
query: string
|
||||
hasAddHost: boolean
|
||||
},
|
||||
t?: Translate
|
||||
): {
|
||||
rows: RunTargetRowModel[]
|
||||
matchedRecipes: EphemeralVmRecipeOption[]
|
||||
/** Hosts the row limit left out; typing narrows them in. */
|
||||
hiddenHostCount: number
|
||||
} {
|
||||
const trimmed = query.trim().toLowerCase()
|
||||
const matchedRecipes = matchRecipes(recipes, trimmed)
|
||||
const matchedRecipes = matchRecipes(recipes, trimmed, t)
|
||||
const hostRows = buildHostRows(hostOptions, trimmed)
|
||||
const rows: RunTargetRowModel[] = [
|
||||
...hostRows.rows,
|
||||
|
||||
@@ -9,10 +9,12 @@ import {
|
||||
DialogTitle
|
||||
} from '@/components/ui/dialog'
|
||||
import { translate } from '@/i18n/i18n'
|
||||
import type { Translate } from '@/i18n/use-translate'
|
||||
import type { SmartWorkspaceNameFieldController } from './use-smart-workspace-name-field-controller'
|
||||
|
||||
export function renderSmartWorkspaceCrossRepoDialog(
|
||||
controller: SmartWorkspaceNameFieldController
|
||||
controller: SmartWorkspaceNameFieldController,
|
||||
t: Translate = translate
|
||||
): React.JSX.Element {
|
||||
const {
|
||||
crossRepoPrompt,
|
||||
@@ -43,10 +45,7 @@ export function renderSmartWorkspaceCrossRepoDialog(
|
||||
</DialogHeader>
|
||||
<DialogFooter className="min-w-0 sm:flex-wrap">
|
||||
<Button variant="outline" onClick={dismissCrossRepoPrompt}>
|
||||
{translate(
|
||||
'auto.components.new.workspace.SmartWorkspaceNameField.6859e2896c',
|
||||
'Cancel'
|
||||
)}
|
||||
{t('auto.components.new.workspace.SmartWorkspaceNameField.6859e2896c', 'Cancel')}
|
||||
</Button>
|
||||
<Button
|
||||
variant="outline"
|
||||
@@ -54,7 +53,7 @@ export function renderSmartWorkspaceCrossRepoDialog(
|
||||
onClick={() => void handleUseCurrentRepo()}
|
||||
>
|
||||
<span className="min-w-0 truncate">
|
||||
{translate(
|
||||
{t(
|
||||
'auto.components.new.workspace.SmartWorkspaceNameField.keepRepo',
|
||||
'Keep {{name}}',
|
||||
{ name: selectedRepo?.displayName ?? crossRepoSwitchFallbackLabel }
|
||||
@@ -67,7 +66,7 @@ export function renderSmartWorkspaceCrossRepoDialog(
|
||||
onClick={() => void acceptGitHubLink(crossRepoPrompt.matchingRepo!)}
|
||||
>
|
||||
<span className="min-w-0 truncate">
|
||||
{translate(
|
||||
{t(
|
||||
'auto.components.new.workspace.SmartWorkspaceNameField.switchToRepo',
|
||||
'Switch to {{name}}',
|
||||
{ name: crossRepoPrompt.matchingRepo.displayName }
|
||||
@@ -76,7 +75,7 @@ export function renderSmartWorkspaceCrossRepoDialog(
|
||||
</Button>
|
||||
) : allowCrossRepoProjectAdd ? (
|
||||
<Button onClick={() => void handleAddMatchingRepo()}>
|
||||
{translate(
|
||||
{t(
|
||||
'auto.components.new.workspace.SmartWorkspaceNameField.e57c53727c',
|
||||
'Add project...'
|
||||
)}
|
||||
|
||||
@@ -3,6 +3,7 @@ import { CaseSensitive, GitBranch, Github, Gitlab, Sparkles } from 'lucide-react
|
||||
|
||||
import { JiraIcon } from '@/components/icons/JiraIcon'
|
||||
import { translate } from '@/i18n/i18n'
|
||||
import type { Translate } from '@/i18n/use-translate'
|
||||
import type { SmartNameMode } from '../../../../shared/new-workspace/smart-workspace-source-results'
|
||||
|
||||
export type MrStateFilter = 'opened' | 'merged' | 'closed' | 'all'
|
||||
@@ -21,74 +22,66 @@ function LinearModeIcon({ className }: { className?: string }): React.JSX.Elemen
|
||||
)
|
||||
}
|
||||
|
||||
export function getMrStateFilters(): { id: MrStateFilter; label: string }[] {
|
||||
export function getMrStateFilters(
|
||||
t: Translate = translate
|
||||
): { id: MrStateFilter; label: string }[] {
|
||||
return [
|
||||
{
|
||||
id: 'opened',
|
||||
label: translate('auto.components.new.workspace.SmartWorkspaceNameField.622864b52a', 'Open')
|
||||
label: t('auto.components.new.workspace.SmartWorkspaceNameField.622864b52a', 'Open')
|
||||
},
|
||||
{
|
||||
id: 'merged',
|
||||
label: translate('auto.components.new.workspace.SmartWorkspaceNameField.2319d87718', 'Merged')
|
||||
label: t('auto.components.new.workspace.SmartWorkspaceNameField.2319d87718', 'Merged')
|
||||
},
|
||||
{
|
||||
id: 'closed',
|
||||
label: translate('auto.components.new.workspace.SmartWorkspaceNameField.6fad211c66', 'Closed')
|
||||
label: t('auto.components.new.workspace.SmartWorkspaceNameField.6fad211c66', 'Closed')
|
||||
},
|
||||
{
|
||||
id: 'all',
|
||||
label: translate('auto.components.new.workspace.SmartWorkspaceNameField.26824f60dd', 'All')
|
||||
label: t('auto.components.new.workspace.SmartWorkspaceNameField.26824f60dd', 'All')
|
||||
}
|
||||
]
|
||||
}
|
||||
|
||||
export function getSmartWorkspaceNameModes(): SmartWorkspaceNameModeOption[] {
|
||||
export function getSmartWorkspaceNameModes(
|
||||
t: Translate = translate
|
||||
): SmartWorkspaceNameModeOption[] {
|
||||
return [
|
||||
{
|
||||
id: 'smart',
|
||||
label: translate('auto.components.new.workspace.SmartWorkspaceNameField.b3c60c2b7c', 'Smart'),
|
||||
label: t('auto.components.new.workspace.SmartWorkspaceNameField.b3c60c2b7c', 'Smart'),
|
||||
Icon: Sparkles
|
||||
},
|
||||
{
|
||||
id: 'github',
|
||||
label: translate(
|
||||
'auto.components.new.workspace.SmartWorkspaceNameField.0a180280bd',
|
||||
'GitHub'
|
||||
),
|
||||
label: t('auto.components.new.workspace.SmartWorkspaceNameField.0a180280bd', 'GitHub'),
|
||||
Icon: Github
|
||||
},
|
||||
{
|
||||
id: 'linear',
|
||||
label: translate(
|
||||
'auto.components.new.workspace.SmartWorkspaceNameField.7a47af0565',
|
||||
'Linear'
|
||||
),
|
||||
label: t('auto.components.new.workspace.SmartWorkspaceNameField.7a47af0565', 'Linear'),
|
||||
Icon: LinearModeIcon
|
||||
},
|
||||
{
|
||||
id: 'jira',
|
||||
label: translate('auto.components.new.workspace.SmartWorkspaceNameField.jiraMode', 'Jira'),
|
||||
label: t('auto.components.new.workspace.SmartWorkspaceNameField.jiraMode', 'Jira'),
|
||||
Icon: JiraIcon
|
||||
},
|
||||
{
|
||||
id: 'gitlab',
|
||||
label: translate(
|
||||
'auto.components.new.workspace.SmartWorkspaceNameField.2cfc6be192',
|
||||
'GitLab'
|
||||
),
|
||||
label: t('auto.components.new.workspace.SmartWorkspaceNameField.2cfc6be192', 'GitLab'),
|
||||
Icon: Gitlab
|
||||
},
|
||||
{
|
||||
id: 'branches',
|
||||
label: translate(
|
||||
'auto.components.new.workspace.SmartWorkspaceNameField.2e4c7c95fe',
|
||||
'Branch'
|
||||
),
|
||||
label: t('auto.components.new.workspace.SmartWorkspaceNameField.2e4c7c95fe', 'Branch'),
|
||||
Icon: GitBranch
|
||||
},
|
||||
{
|
||||
id: 'text',
|
||||
label: translate('auto.components.new.workspace.SmartWorkspaceNameField.6f07a18604', 'Name'),
|
||||
label: t('auto.components.new.workspace.SmartWorkspaceNameField.6f07a18604', 'Name'),
|
||||
Icon: CaseSensitive
|
||||
}
|
||||
]
|
||||
|
||||
@@ -1,114 +1,112 @@
|
||||
import { translate } from '@/i18n/i18n'
|
||||
import type { Translate } from '@/i18n/use-translate'
|
||||
import type { SmartNameMode } from '../../../../shared/new-workspace/smart-workspace-source-results'
|
||||
|
||||
export function getSmartWorkspaceNameFieldCopy({
|
||||
repoBackedSourcesDisabled,
|
||||
linearAvailable,
|
||||
branchesEnabled,
|
||||
crossRepoSwitchTarget,
|
||||
disabled,
|
||||
disabledPlaceholder,
|
||||
mode
|
||||
}: {
|
||||
repoBackedSourcesDisabled: boolean
|
||||
linearAvailable: boolean
|
||||
branchesEnabled: boolean
|
||||
crossRepoSwitchTarget: 'project' | 'task-source'
|
||||
disabled: boolean
|
||||
disabledPlaceholder?: string
|
||||
mode: SmartNameMode
|
||||
}) {
|
||||
export function getSmartWorkspaceNameFieldCopy(
|
||||
{
|
||||
repoBackedSourcesDisabled,
|
||||
linearAvailable,
|
||||
branchesEnabled,
|
||||
crossRepoSwitchTarget,
|
||||
disabled,
|
||||
disabledPlaceholder,
|
||||
mode
|
||||
}: {
|
||||
repoBackedSourcesDisabled: boolean
|
||||
linearAvailable: boolean
|
||||
branchesEnabled: boolean
|
||||
crossRepoSwitchTarget: 'project' | 'task-source'
|
||||
disabled: boolean
|
||||
disabledPlaceholder?: string
|
||||
mode: SmartNameMode
|
||||
},
|
||||
t: Translate = translate
|
||||
) {
|
||||
const smartPlaceholder = repoBackedSourcesDisabled
|
||||
? linearAvailable
|
||||
? translate(
|
||||
? t(
|
||||
'auto.components.new.workspace.SmartWorkspaceNameField.placeholderNameOrLinearUrl',
|
||||
'Type a name, Linear URL, or Jira URL'
|
||||
)
|
||||
: translate(
|
||||
: t(
|
||||
'auto.components.new.workspace.SmartWorkspaceNameField.placeholderWorkspaceName',
|
||||
'Type a workspace name'
|
||||
)
|
||||
: linearAvailable
|
||||
? branchesEnabled
|
||||
? translate(
|
||||
? t(
|
||||
'auto.components.new.workspace.SmartWorkspaceNameField.placeholderSmartWithBranchGitLabLinear',
|
||||
'Type a name, #1234, branch, GitHub/GitLab, Linear, or Jira URL'
|
||||
)
|
||||
: translate(
|
||||
: t(
|
||||
'auto.components.new.workspace.SmartWorkspaceNameField.placeholderSmartGitLabLinear',
|
||||
'Type a name, #1234, GitHub/GitLab, Linear, or Jira URL'
|
||||
)
|
||||
: branchesEnabled
|
||||
? translate(
|
||||
? t(
|
||||
'auto.components.new.workspace.SmartWorkspaceNameField.placeholderSmartWithBranchGitLab',
|
||||
'Type a name, #1234, branch, GitHub, GitLab, or Jira URL'
|
||||
)
|
||||
: translate(
|
||||
: t(
|
||||
'auto.components.new.workspace.SmartWorkspaceNameField.placeholderSmartGitLab',
|
||||
'Type a name, #1234, GitHub, GitLab, or Jira URL'
|
||||
)
|
||||
const crossRepoSwitchIsTaskSource = crossRepoSwitchTarget === 'task-source'
|
||||
const crossRepoSwitchTitle = crossRepoSwitchIsTaskSource
|
||||
? translate(
|
||||
? t(
|
||||
'auto.components.new.workspace.SmartWorkspaceNameField.switchTaskSourceTitle',
|
||||
'Switch task source?'
|
||||
)
|
||||
: translate(
|
||||
'auto.components.new.workspace.SmartWorkspaceNameField.4bd98f1091',
|
||||
'Switch project?'
|
||||
)
|
||||
: t('auto.components.new.workspace.SmartWorkspaceNameField.4bd98f1091', 'Switch project?')
|
||||
const formatCrossRepoSwitchDescription = (repoSlug: string): string =>
|
||||
crossRepoSwitchIsTaskSource
|
||||
? translate(
|
||||
? t(
|
||||
'auto.components.new.workspace.SmartWorkspaceNameField.crossRepoDifferentTaskSource',
|
||||
'The GitHub URL points to {{repo}}, which is different from the selected task source.',
|
||||
{ repo: repoSlug }
|
||||
)
|
||||
: translate(
|
||||
: t(
|
||||
'auto.components.new.workspace.SmartWorkspaceNameField.crossRepoDifferentProject',
|
||||
'The GitHub URL points to {{repo}}, which is different from the selected project.',
|
||||
{ repo: repoSlug }
|
||||
)
|
||||
const crossRepoSwitchFallbackLabel = crossRepoSwitchIsTaskSource
|
||||
? translate(
|
||||
? t(
|
||||
'auto.components.new.workspace.SmartWorkspaceNameField.currentTaskSource',
|
||||
'current task source'
|
||||
)
|
||||
: translate(
|
||||
'auto.components.new.workspace.SmartWorkspaceNameField.fda67f0b61',
|
||||
'current project'
|
||||
)
|
||||
: t('auto.components.new.workspace.SmartWorkspaceNameField.fda67f0b61', 'current project')
|
||||
const placeholder = disabled
|
||||
? (disabledPlaceholder ??
|
||||
translate('auto.components.new.workspace.SmartWorkspaceNameField.unavailable', 'Unavailable'))
|
||||
t('auto.components.new.workspace.SmartWorkspaceNameField.unavailable', 'Unavailable'))
|
||||
: mode === 'smart'
|
||||
? smartPlaceholder
|
||||
: mode === 'github'
|
||||
? translate(
|
||||
? t(
|
||||
'auto.components.new.workspace.SmartWorkspaceNameField.searchGitHub',
|
||||
'Search GitHub PRs and issues'
|
||||
)
|
||||
: mode === 'gitlab'
|
||||
? translate(
|
||||
? t(
|
||||
'auto.components.new.workspace.SmartWorkspaceNameField.searchGitLab',
|
||||
'Search GitLab MRs and issues'
|
||||
)
|
||||
: mode === 'branches'
|
||||
? translate(
|
||||
? t(
|
||||
'auto.components.new.workspace.SmartWorkspaceNameField.searchBranches',
|
||||
'Search branches'
|
||||
)
|
||||
: mode === 'linear'
|
||||
? translate(
|
||||
? t(
|
||||
'auto.components.new.workspace.SmartWorkspaceNameField.searchLinear',
|
||||
'Search Linear issues'
|
||||
)
|
||||
: mode === 'jira'
|
||||
? translate(
|
||||
? t(
|
||||
'auto.components.new.workspace.SmartWorkspaceNameField.searchJira',
|
||||
'Search Jira issues or paste an issue URL'
|
||||
)
|
||||
: translate(
|
||||
: t(
|
||||
'auto.components.new.workspace.SmartWorkspaceNameField.workspaceName',
|
||||
'Workspace name'
|
||||
)
|
||||
|
||||
@@ -5,6 +5,7 @@ import { Popover, PopoverAnchor } from '@/components/ui/popover'
|
||||
import { SmartWorkspaceSourceTabs } from './SmartWorkspaceSourceTabs'
|
||||
import { cn } from '@/lib/utils'
|
||||
import { translate } from '@/i18n/i18n'
|
||||
import type { Translate } from '@/i18n/use-translate'
|
||||
import { WorkspaceEmojiSuggestionPopover } from '@/components/workspace-emoji/WorkspaceEmojiSuggestionPopover'
|
||||
import { renderSmartWorkspaceCrossRepoDialog } from './smart-workspace-cross-repo-dialog'
|
||||
import { renderSmartWorkspaceNameInput } from './smart-workspace-name-input-surface'
|
||||
@@ -15,7 +16,8 @@ import { JiraProjectPicker } from '../jira-project-picker'
|
||||
import { LinearWorkspacePicker } from '../linear-workspace-picker'
|
||||
|
||||
export function renderSmartWorkspaceNameField(
|
||||
controller: SmartWorkspaceNameFieldController
|
||||
controller: SmartWorkspaceNameFieldController,
|
||||
t: Translate = translate
|
||||
): React.JSX.Element {
|
||||
const {
|
||||
mode,
|
||||
@@ -55,8 +57,8 @@ export function renderSmartWorkspaceNameField(
|
||||
projects={controller.jiraProjectInventory.projects}
|
||||
value={controller.jiraProject}
|
||||
onChange={controller.setJiraProject}
|
||||
label={translate('components.jiraProjectPicker.label', 'Jira project')}
|
||||
emptyLabel={translate('components.jiraProjectPicker.all', 'All Jira projects')}
|
||||
label={t('components.jiraProjectPicker.label', 'Jira project')}
|
||||
emptyLabel={t('components.jiraProjectPicker.all', 'All Jira projects')}
|
||||
disabled={disabled || controller.jiraProjectInventory.loading}
|
||||
/>
|
||||
) : null}
|
||||
@@ -85,7 +87,7 @@ export function renderSmartWorkspaceNameField(
|
||||
className="overflow-visible bg-transparent"
|
||||
>
|
||||
<PopoverAnchor asChild>
|
||||
<div className="relative min-w-0">{renderSmartWorkspaceNameInput(controller)}</div>
|
||||
<div className="relative min-w-0">{renderSmartWorkspaceNameInput(controller, t)}</div>
|
||||
</PopoverAnchor>
|
||||
{renderSmartWorkspaceSourceResults(controller)}
|
||||
</Command>
|
||||
@@ -103,27 +105,21 @@ export function renderSmartWorkspaceNameField(
|
||||
'sr-only'
|
||||
)}
|
||||
>
|
||||
<span>{getJiraSourceStatusMessage(jiraSource)}</span>
|
||||
<span>{getJiraSourceStatusMessage(jiraSource, t)}</span>
|
||||
{jiraSource.errorKind === 'disconnected' && onOpenJiraSettings ? (
|
||||
<Button type="button" variant="link" size="xs" onClick={onOpenJiraSettings}>
|
||||
{translate(
|
||||
'auto.components.new.workspace.SmartWorkspaceNameField.openSettings',
|
||||
'Settings'
|
||||
)}
|
||||
{t('auto.components.new.workspace.SmartWorkspaceNameField.openSettings', 'Settings')}
|
||||
</Button>
|
||||
) : jiraSource.errorKind === 'read-failed' ? (
|
||||
<Button type="button" variant="link" size="xs" onClick={jiraSource.retry}>
|
||||
{translate(
|
||||
'auto.components.new.workspace.SmartWorkspaceNameField.retryJira',
|
||||
'Retry'
|
||||
)}
|
||||
{t('auto.components.new.workspace.SmartWorkspaceNameField.retryJira', 'Retry')}
|
||||
</Button>
|
||||
) : null}
|
||||
</div>
|
||||
) : null}
|
||||
{unresolvedLinearUrlIntent ? (
|
||||
<div id={linearStatusId} role="status" aria-live="polite" className="sr-only">
|
||||
{translate(
|
||||
{t(
|
||||
'auto.components.new.workspace.SmartWorkspaceNameField.loadingLinearIssue',
|
||||
'Loading Linear issue…'
|
||||
)}
|
||||
@@ -133,7 +129,7 @@ export function renderSmartWorkspaceNameField(
|
||||
anchorRef={localInputRef}
|
||||
open={emojiMenuOpen}
|
||||
commandValue={resolvedEmojiCommandValue}
|
||||
heading={translate('auto.components.new.workspace.SmartWorkspaceNameField.emoji', 'Emoji')}
|
||||
heading={t('auto.components.new.workspace.SmartWorkspaceNameField.emoji', 'Emoji')}
|
||||
suggestions={emojiSuggestions}
|
||||
onCommandValueChange={setEmojiCommandValue}
|
||||
onSelect={handleEmojiSelect}
|
||||
@@ -143,7 +139,7 @@ export function renderSmartWorkspaceNameField(
|
||||
}
|
||||
}}
|
||||
/>
|
||||
{renderSmartWorkspaceCrossRepoDialog(controller)}
|
||||
{renderSmartWorkspaceCrossRepoDialog(controller, t)}
|
||||
</div>
|
||||
)
|
||||
}
|
||||
|
||||
@@ -11,11 +11,13 @@ import { isComposerFieldToFieldFocus } from './smart-workspace-source-popover-fo
|
||||
import { replaceCompletedWorkspaceEmojiShortcode } from '@/lib/workspace-emoji-shortcodes'
|
||||
import { cn } from '@/lib/utils'
|
||||
import { translate } from '@/i18n/i18n'
|
||||
import type { Translate } from '@/i18n/use-translate'
|
||||
import { SelectionIcon } from './smart-workspace-source-row-content'
|
||||
import type { SmartWorkspaceNameFieldController } from './use-smart-workspace-name-field-controller'
|
||||
|
||||
export function renderSmartWorkspaceNameInput(
|
||||
controller: SmartWorkspaceNameFieldController
|
||||
controller: SmartWorkspaceNameFieldController,
|
||||
t: Translate = translate
|
||||
): React.JSX.Element {
|
||||
const {
|
||||
selectedSource,
|
||||
@@ -128,7 +130,7 @@ export function renderSmartWorkspaceNameInput(
|
||||
tabIndex={-1}
|
||||
onClick={openSelectedSource}
|
||||
className="size-6 shrink-0 rounded-sm text-muted-foreground hover:text-foreground"
|
||||
aria-label={translate(
|
||||
aria-label={t(
|
||||
'auto.components.new.workspace.SmartWorkspaceNameField.2c69728c2a',
|
||||
'Open link in browser'
|
||||
)}
|
||||
@@ -137,7 +139,7 @@ export function renderSmartWorkspaceNameInput(
|
||||
</Button>
|
||||
</TooltipTrigger>
|
||||
<TooltipContent side="top" sideOffset={6}>
|
||||
{translate(
|
||||
{t(
|
||||
'auto.components.new.workspace.SmartWorkspaceNameField.370a1faf67',
|
||||
'Open in browser'
|
||||
)}
|
||||
@@ -153,7 +155,7 @@ export function renderSmartWorkspaceNameInput(
|
||||
tabIndex={-1}
|
||||
onClick={onClearSelectedSource}
|
||||
className="size-6 shrink-0 rounded-sm text-muted-foreground hover:text-foreground"
|
||||
aria-label={translate(
|
||||
aria-label={t(
|
||||
'auto.components.new.workspace.SmartWorkspaceNameField.7199ff19c7',
|
||||
'Clear selected source'
|
||||
)}
|
||||
@@ -162,7 +164,7 @@ export function renderSmartWorkspaceNameInput(
|
||||
</Button>
|
||||
</TooltipTrigger>
|
||||
<TooltipContent side="top" sideOffset={6}>
|
||||
{translate('auto.components.new.workspace.SmartWorkspaceNameField.0c9e668e3a', 'Clear')}
|
||||
{t('auto.components.new.workspace.SmartWorkspaceNameField.0c9e668e3a', 'Clear')}
|
||||
</TooltipContent>
|
||||
</Tooltip>
|
||||
</div>
|
||||
|
||||
@@ -10,7 +10,8 @@ import {
|
||||
import { LinearIcon } from '@/components/icons/LinearIcon'
|
||||
import { JiraIcon } from '@/components/icons/JiraIcon'
|
||||
import { Trans } from 'react-i18next'
|
||||
import { i18n, translate } from '@/i18n/i18n'
|
||||
import { i18n } from '@/i18n/i18n'
|
||||
import { useTranslate } from '@/i18n/use-translate'
|
||||
import type { JiraSite } from '../../../../shared/jira/jira-types'
|
||||
import type { RowEntry, SmartWorkspaceNameSelection } from './smart-workspace-name-field-model'
|
||||
|
||||
@@ -76,12 +77,13 @@ export function RowLabel({
|
||||
jiraSite?: JiraSite | null
|
||||
showJiraSiteContext?: boolean
|
||||
}): React.JSX.Element {
|
||||
const t = useTranslate()
|
||||
if (row.kind === 'use-name') {
|
||||
return (
|
||||
<span className="min-w-0 truncate">
|
||||
<Trans
|
||||
i18n={i18n}
|
||||
defaults={translate(
|
||||
defaults={t(
|
||||
'auto.components.new.workspace.SmartWorkspaceNameField.useAsWorkspaceName',
|
||||
'Use <name>"{{name}}"</name> as workspace name'
|
||||
)}
|
||||
@@ -96,7 +98,7 @@ export function RowLabel({
|
||||
<span className="min-w-0 truncate">
|
||||
<Trans
|
||||
i18n={i18n}
|
||||
defaults={translate(
|
||||
defaults={t(
|
||||
'auto.components.new.workspace.SmartWorkspaceNameField.createNewBranchNamed',
|
||||
'Create new branch <name>{{name}}</name>'
|
||||
)}
|
||||
|
||||
@@ -8,6 +8,7 @@ import {
|
||||
} from './smart-workspace-name-field-model'
|
||||
import { canUseGitLabSmartSource } from './smart-workspace-provider-availability'
|
||||
import type { useWorkItemSourceState } from './use-work-item-source-state'
|
||||
import { useTranslate } from '../../i18n/use-translate'
|
||||
|
||||
type FieldState = ReturnType<typeof useWorkItemSourceState>
|
||||
|
||||
@@ -43,6 +44,7 @@ export function useSmartWorkspaceFieldAvailability({
|
||||
checkLinearConnection: () => Promise<void>
|
||||
jiraSourceConnected: boolean
|
||||
}) {
|
||||
const t = useTranslate()
|
||||
const { disabled, textOnly, repoBackedSourcesDisabled, branchesEnabled, value } = props
|
||||
const { mode: storedMode, setMode, setDebouncedQuery } = state
|
||||
const preflightStatusCurrent = preflightStatusContextKey === expectedPreflightContextKey
|
||||
@@ -63,7 +65,7 @@ export function useSmartWorkspaceFieldAvailability({
|
||||
[gitlabSourceAvailable, linearStatus.connected]
|
||||
)
|
||||
const linearAvailable = availableTaskProviders.includes('linear')
|
||||
const availableModes = getSmartWorkspaceNameModes().filter((item) => {
|
||||
const availableModes = getSmartWorkspaceNameModes(t).filter((item) => {
|
||||
if (textOnly) {
|
||||
return item.id === 'text'
|
||||
}
|
||||
@@ -95,7 +97,7 @@ export function useSmartWorkspaceFieldAvailability({
|
||||
setMode(mode)
|
||||
}
|
||||
}, [storedMode, setMode, mode])
|
||||
const mrStateFilters = getMrStateFilters()
|
||||
const mrStateFilters = getMrStateFilters(t)
|
||||
|
||||
useEffect(() => {
|
||||
if (disabled || textOnly) {
|
||||
|
||||
+9
-4
@@ -10,8 +10,10 @@ import { useSmartWorkspaceFieldFocusControls } from './use-smart-workspace-field
|
||||
import { useSmartWorkspaceNameFieldActions } from './use-smart-workspace-name-field-actions'
|
||||
import { getSmartWorkspaceNameFieldCopy } from './smart-workspace-name-field-copy'
|
||||
import { useLatestHandler } from '@/lib/use-latest-handler'
|
||||
import { useTranslate } from '@/i18n/use-translate'
|
||||
|
||||
export function useSmartWorkspaceNameFieldController(props: SmartWorkspaceNameFieldProps) {
|
||||
const t = useTranslate()
|
||||
const search = useWorkItemSourceSearch({
|
||||
...props,
|
||||
defaultLinearWorkspaceId: 'all',
|
||||
@@ -80,10 +82,13 @@ export function useSmartWorkspaceNameFieldController(props: SmartWorkspaceNameFi
|
||||
allowCrossRepoProjectAdd: props.allowCrossRepoProjectAdd ?? true
|
||||
}
|
||||
const actions = useSmartWorkspaceNameFieldActions(foundation, { ...search, activeEmojiShortcode })
|
||||
const copy = getSmartWorkspaceNameFieldCopy({
|
||||
...search,
|
||||
disabledPlaceholder: props.disabledPlaceholder
|
||||
})
|
||||
const copy = getSmartWorkspaceNameFieldCopy(
|
||||
{
|
||||
...search,
|
||||
disabledPlaceholder: props.disabledPlaceholder
|
||||
},
|
||||
t
|
||||
)
|
||||
return {
|
||||
...foundation,
|
||||
...actions,
|
||||
|
||||
@@ -1,94 +1,80 @@
|
||||
import { translate } from '@/i18n/i18n'
|
||||
import type { Translate } from '@/i18n/use-translate'
|
||||
import type { WorkspaceSetupLabels } from './workspace-setup-section-model'
|
||||
|
||||
export function getComposerWorkspaceSetupLabels(): WorkspaceSetupLabels {
|
||||
export function getComposerWorkspaceSetupLabels(t: Translate = translate): WorkspaceSetupLabels {
|
||||
return {
|
||||
kinds: {
|
||||
setup: {
|
||||
configLabel: translate(
|
||||
configLabel: t(
|
||||
'auto.components.NewWorkspaceComposerCard.setupConfigSetupScript',
|
||||
'Setup script'
|
||||
),
|
||||
runLabel: translate(
|
||||
runLabel: t(
|
||||
'auto.components.NewWorkspaceComposerCard.runSetupCommand',
|
||||
'Run setup command'
|
||||
),
|
||||
askLabel: translate(
|
||||
'auto.components.NewWorkspaceComposerCard.askRunSetup',
|
||||
'Run setup now?'
|
||||
),
|
||||
runButtonLabel: translate(
|
||||
'auto.components.NewWorkspaceComposerCard.runSetupNow',
|
||||
'Run setup now'
|
||||
),
|
||||
skipButtonLabel: translate(
|
||||
'auto.components.NewWorkspaceComposerCard.skipForNow',
|
||||
'Skip for now'
|
||||
)
|
||||
askLabel: t('auto.components.NewWorkspaceComposerCard.askRunSetup', 'Run setup now?'),
|
||||
runButtonLabel: t('auto.components.NewWorkspaceComposerCard.runSetupNow', 'Run setup now'),
|
||||
skipButtonLabel: t('auto.components.NewWorkspaceComposerCard.skipForNow', 'Skip for now')
|
||||
},
|
||||
'default-tabs': {
|
||||
configLabel: translate(
|
||||
configLabel: t(
|
||||
'auto.components.NewWorkspaceComposerCard.setupConfigDefaultTabs',
|
||||
'Default tab commands'
|
||||
),
|
||||
runLabel: translate(
|
||||
runLabel: t(
|
||||
'auto.components.NewWorkspaceComposerCard.runDefaultTabCommands',
|
||||
'Run default tab commands'
|
||||
),
|
||||
askLabel: translate(
|
||||
askLabel: t(
|
||||
'auto.components.NewWorkspaceComposerCard.askRunDefaultTabCommands',
|
||||
'Run default tab commands now?'
|
||||
),
|
||||
runButtonLabel: translate(
|
||||
runButtonLabel: t(
|
||||
'auto.components.NewWorkspaceComposerCard.runCommandsNow',
|
||||
'Run commands now'
|
||||
),
|
||||
skipButtonLabel: translate(
|
||||
'auto.components.NewWorkspaceComposerCard.skipCommands',
|
||||
'Skip commands'
|
||||
)
|
||||
skipButtonLabel: t('auto.components.NewWorkspaceComposerCard.skipCommands', 'Skip commands')
|
||||
},
|
||||
'setup-and-default-tabs': {
|
||||
configLabel: translate(
|
||||
configLabel: t(
|
||||
'auto.components.NewWorkspaceComposerCard.setupConfigSetupAndDefaultTabs',
|
||||
'Setup and default tab commands'
|
||||
),
|
||||
runLabel: translate(
|
||||
runLabel: t(
|
||||
'auto.components.NewWorkspaceComposerCard.runSetupAndDefaultTabCommands',
|
||||
'Run setup and default tab commands'
|
||||
),
|
||||
askLabel: translate(
|
||||
askLabel: t(
|
||||
'auto.components.NewWorkspaceComposerCard.askRunSetupAndDefaultTabCommands',
|
||||
'Run setup and default tab commands now?'
|
||||
),
|
||||
runButtonLabel: translate(
|
||||
runButtonLabel: t(
|
||||
'auto.components.NewWorkspaceComposerCard.runCommandsNow',
|
||||
'Run commands now'
|
||||
),
|
||||
skipButtonLabel: translate(
|
||||
'auto.components.NewWorkspaceComposerCard.skipCommands',
|
||||
'Skip commands'
|
||||
)
|
||||
skipButtonLabel: t('auto.components.NewWorkspaceComposerCard.skipCommands', 'Skip commands')
|
||||
}
|
||||
},
|
||||
sources: {
|
||||
yaml: translate('auto.components.NewWorkspaceComposerCard.23bb365554', 'orca.yaml'),
|
||||
both: translate('auto.components.NewWorkspaceComposerCard.326a578923', 'orca.yaml + local'),
|
||||
local: translate('auto.components.NewWorkspaceComposerCard.92e34f0311', 'local settings')
|
||||
yaml: t('auto.components.NewWorkspaceComposerCard.23bb365554', 'orca.yaml'),
|
||||
both: t('auto.components.NewWorkspaceComposerCard.326a578923', 'orca.yaml + local'),
|
||||
local: t('auto.components.NewWorkspaceComposerCard.92e34f0311', 'local settings')
|
||||
},
|
||||
help: translate(
|
||||
help: t(
|
||||
'auto.components.NewWorkspaceComposerCard.9a70e4859e',
|
||||
'Choose whether to run setup before creating this workspace.'
|
||||
),
|
||||
checking: translate(
|
||||
checking: t(
|
||||
'auto.components.NewWorkspaceComposerCard.803b7fe72f',
|
||||
'Checking setup configuration...'
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
export function getComposerSetupStartupLabel(): string {
|
||||
return translate(
|
||||
export function getComposerSetupStartupLabel(t: Translate = translate): string {
|
||||
return t(
|
||||
'auto.components.NewWorkspaceComposerCard.waitForSetupBeforeAgent',
|
||||
'Wait for setup to complete before starting agent'
|
||||
)
|
||||
|
||||
@@ -7,7 +7,8 @@ import { Collapsible, CollapsibleContent, CollapsibleTrigger } from '@/component
|
||||
import type { TuiAgent } from '../../../../shared/agent-launch/catalog/tui-agent'
|
||||
import type { GlobalSettings } from '../../../../shared/global-settings-types'
|
||||
import { nativeChatPreferenceFor } from '../../../../shared/structured-native-chat-launch-route'
|
||||
import { i18n, translate } from '@/i18n/i18n'
|
||||
import { i18n } from '@/i18n/i18n'
|
||||
import { useTranslate } from '@/i18n/use-translate'
|
||||
import { openAppHttpLink } from '@/lib/browser/navigation/app-http-link-open'
|
||||
import { useScrollEdgeFades } from '@/lib/use-scroll-edge-fades'
|
||||
import { cn } from '@/lib/utils'
|
||||
@@ -57,6 +58,7 @@ export function AgentStep({
|
||||
yoloPermissions = true,
|
||||
onYoloPermissionsChange
|
||||
}: AgentStepProps) {
|
||||
const t = useTranslate()
|
||||
const agentCatalog = getAgentCatalog()
|
||||
const gridFades = useScrollEdgeFades<HTMLElement>()
|
||||
const chatMode = agentSurface === 'chat'
|
||||
@@ -117,19 +119,20 @@ export function AgentStep({
|
||||
}
|
||||
}
|
||||
const fallbackRestLabel = openState
|
||||
? translate('auto.components.onboarding.AgentStep.hideAgents', 'Hide agents')
|
||||
: translate(
|
||||
'auto.components.onboarding.AgentStep.showMoreAgents',
|
||||
'Show {{count}} more agents →',
|
||||
{ count: fallbackRest.length, defaultValue_one: 'Show {{count}} more agent →' }
|
||||
)
|
||||
? t('auto.components.onboarding.AgentStep.hideAgents', 'Hide agents')
|
||||
: t('auto.components.onboarding.AgentStep.showMoreAgents', 'Show {{count}} more agents →', {
|
||||
count: fallbackRest.length,
|
||||
defaultValue_one: 'Show {{count}} more agent →'
|
||||
})
|
||||
const useTerminal = (): void => onAgentSurfaceChange?.('terminal')
|
||||
const renderTile = (agent: AgentCatalogEntry, fromCollapsedSection: boolean) => (
|
||||
<AgentPickerButton
|
||||
key={agent.id}
|
||||
agent={agent}
|
||||
selected={selectedAgent === agent.id}
|
||||
detail={agentSurface === 'terminal' ? chatAvailableDetail(opensAsChat(agent.id)) : undefined}
|
||||
detail={
|
||||
agentSurface === 'terminal' ? chatAvailableDetail(opensAsChat(agent.id), t) : undefined
|
||||
}
|
||||
onClick={() => onSelect(agent.id, fromCollapsedSection)}
|
||||
/>
|
||||
)
|
||||
@@ -144,7 +147,7 @@ export function AgentStep({
|
||||
)}
|
||||
{!hasDetected && !isDetecting && !chatMode && (
|
||||
<div className="shrink-0 rounded-lg border border-border px-4 py-3 text-xs text-muted-foreground">
|
||||
{translate(
|
||||
{t(
|
||||
'auto.components.onboarding.AgentStep.1eee1c7bd8',
|
||||
'No agents detected on your PATH. Pick one to install later, or continue with a blank terminal.'
|
||||
)}
|
||||
@@ -167,7 +170,7 @@ export function AgentStep({
|
||||
<span>
|
||||
<Trans
|
||||
i18n={i18n}
|
||||
defaults={translate(
|
||||
defaults={t(
|
||||
'auto.components.onboarding.AgentStep.agentNotOnPath',
|
||||
"<agent>{{agent}}</agent> isn't on your PATH yet. Orca will set it as your default and you can install it any time.",
|
||||
{ agent: selectedEntry.label }
|
||||
@@ -182,7 +185,7 @@ export function AgentStep({
|
||||
className="shrink-0"
|
||||
onClick={(event) => openAppHttpLink(selectedEntry.homepageUrl, { event })}
|
||||
>
|
||||
{translate('auto.components.onboarding.AgentStep.9c163bb0e0', 'Install instructions')}
|
||||
{t('auto.components.onboarding.AgentStep.9c163bb0e0', 'Install instructions')}
|
||||
<ExternalLink className="size-3" />
|
||||
</Button>
|
||||
</div>
|
||||
@@ -213,14 +216,11 @@ export function AgentStep({
|
||||
<AgentSectionHeader
|
||||
label={
|
||||
hasDetected
|
||||
? translate(
|
||||
? t(
|
||||
'auto.components.onboarding.AgentStep.d7b3ef168b',
|
||||
'Detected on your system'
|
||||
)
|
||||
: translate(
|
||||
'auto.components.onboarding.AgentStep.e6a369bd04',
|
||||
'Popular agents'
|
||||
)
|
||||
: t('auto.components.onboarding.AgentStep.e6a369bd04', 'Popular agents')
|
||||
}
|
||||
count={primary.length}
|
||||
/>
|
||||
@@ -234,8 +234,8 @@ export function AgentStep({
|
||||
<AgentSectionHeader
|
||||
label={
|
||||
noChatAgents
|
||||
? translate('components.onboarding.agentStep.worksInChat', 'Works in Chat UI')
|
||||
: translate('components.onboarding.agentStep.chatAgents', 'Chat UI agents')
|
||||
? t('components.onboarding.agentStep.worksInChat', 'Works in Chat UI')
|
||||
: t('components.onboarding.agentStep.chatAgents', 'Chat UI agents')
|
||||
}
|
||||
count={chatSection.length}
|
||||
/>
|
||||
@@ -247,11 +247,11 @@ export function AgentStep({
|
||||
{terminalSection.length > 0 && (
|
||||
<div className="space-y-3">
|
||||
<AgentSectionHeader
|
||||
label={translate(
|
||||
label={t(
|
||||
'components.onboarding.agentStep.terminalAgents',
|
||||
'Available as terminal agents'
|
||||
)}
|
||||
hint={translate(
|
||||
hint={t(
|
||||
'components.onboarding.agentStep.terminalAgentsHint',
|
||||
'No Chat UI support, so these fall back to a terminal.'
|
||||
)}
|
||||
|
||||
@@ -6,17 +6,21 @@ import { Tooltip, TooltipContent, TooltipTrigger } from '@/components/ui/tooltip
|
||||
import { cn } from '@/lib/utils'
|
||||
import { isWebClientLocation } from '@/lib/web-client-location'
|
||||
import { i18n, translate } from '@/i18n/i18n'
|
||||
import { useTranslate, type Translate } from '@/i18n/use-translate'
|
||||
import type { AgentSurface } from './use-onboarding-flow-types'
|
||||
import { TerminalLaunchDemo } from './TerminalLaunchDemo'
|
||||
|
||||
const emphasis = { em: <span className="font-medium text-foreground" /> }
|
||||
|
||||
/** Terminal mode marks which agents could also open in Chat UI. */
|
||||
export function chatAvailableDetail(opensAsChat: boolean): React.ReactNode {
|
||||
export function chatAvailableDetail(
|
||||
opensAsChat: boolean,
|
||||
t: Translate = translate
|
||||
): React.ReactNode {
|
||||
return opensAsChat ? (
|
||||
<>
|
||||
<MessagesSquare />
|
||||
{translate('components.onboarding.agentStep.chatAvailable', 'Chat available')}
|
||||
{t('components.onboarding.agentStep.chatAvailable', 'Chat available')}
|
||||
</>
|
||||
) : undefined
|
||||
}
|
||||
@@ -25,15 +29,16 @@ const surfacePillClass =
|
||||
'inline-flex h-6 items-center gap-1.5 rounded-full border px-2.5 font-medium text-foreground'
|
||||
|
||||
function AgentSurfaceLabel({ surface }: { surface: AgentSurface }) {
|
||||
const t = useTranslate()
|
||||
return surface === 'chat' ? (
|
||||
<>
|
||||
<MessagesSquare className="size-3.5" />
|
||||
{translate('components.onboarding.agentSurface.chat.label', 'Chat UI')}
|
||||
{t('components.onboarding.agentSurface.chat.label', 'Chat UI')}
|
||||
</>
|
||||
) : (
|
||||
<>
|
||||
<SquareTerminal className="size-3.5" />
|
||||
{translate('components.onboarding.agentStep.terminalUi', 'Terminal UI')}
|
||||
{t('components.onboarding.agentStep.terminalUi', 'Terminal UI')}
|
||||
</>
|
||||
)
|
||||
}
|
||||
@@ -46,7 +51,8 @@ export function AgentSurfaceModeRow({
|
||||
surface: AgentSurface
|
||||
onChange?: (surface: AgentSurface) => void
|
||||
}) {
|
||||
const label = translate('components.onboarding.agentStep.agentsOpenIn', 'Agents open in')
|
||||
const t = useTranslate()
|
||||
const label = t('components.onboarding.agentStep.agentsOpenIn', 'Agents open in')
|
||||
if (!onChange) {
|
||||
return (
|
||||
<div className="flex shrink-0 items-center gap-2.5 text-xs text-muted-foreground">
|
||||
@@ -118,12 +124,13 @@ export function NoChatAgentsNotice({
|
||||
chatAgentLabels: readonly string[]
|
||||
onUseTerminal: () => void
|
||||
}) {
|
||||
const t = useTranslate()
|
||||
if (!anyDetected) {
|
||||
return (
|
||||
<AgentStepNotice>
|
||||
<Trans
|
||||
i18n={i18n}
|
||||
defaults={translate(
|
||||
defaults={t(
|
||||
'components.onboarding.agentStep.noAgentsForChat',
|
||||
'<em>No agents found on your PATH.</em> Chat UI works with {{agents}}. Install one, or continue and add an agent later.',
|
||||
{
|
||||
@@ -138,10 +145,10 @@ export function NoChatAgentsNotice({
|
||||
)
|
||||
}
|
||||
return (
|
||||
<AgentStepNotice tone="warning" actionLabel={terminalInsteadLabel()} onAction={onUseTerminal}>
|
||||
<AgentStepNotice tone="warning" actionLabel={terminalInsteadLabel(t)} onAction={onUseTerminal}>
|
||||
<Trans
|
||||
i18n={i18n}
|
||||
defaults={translate(
|
||||
defaults={t(
|
||||
'components.onboarding.agentStep.noChatAgentsDetected',
|
||||
'<em>None of your agents work in Chat UI.</em> They would all open in a terminal, so Terminal fits better.'
|
||||
)}
|
||||
@@ -158,11 +165,12 @@ export function TerminalFitNotice({
|
||||
agentLabel: string
|
||||
onUseTerminal: () => void
|
||||
}) {
|
||||
const t = useTranslate()
|
||||
return (
|
||||
<AgentStepNotice tone="warning" actionLabel={terminalInsteadLabel()} onAction={onUseTerminal}>
|
||||
<AgentStepNotice tone="warning" actionLabel={terminalInsteadLabel(t)} onAction={onUseTerminal}>
|
||||
<Trans
|
||||
i18n={i18n}
|
||||
defaults={translate(
|
||||
defaults={t(
|
||||
'components.onboarding.agentStep.opensInTerminalFit',
|
||||
'<em>{{agent}} opens in a terminal</em>, so Terminal fits better.',
|
||||
{ agent: agentLabel }
|
||||
@@ -173,13 +181,14 @@ export function TerminalFitNotice({
|
||||
)
|
||||
}
|
||||
|
||||
function terminalInsteadLabel(): string {
|
||||
return translate('components.onboarding.agentStep.useTerminalInstead', 'Use Terminal instead')
|
||||
function terminalInsteadLabel(t: Translate = translate): string {
|
||||
return t('components.onboarding.agentStep.useTerminalInstead', 'Use Terminal instead')
|
||||
}
|
||||
|
||||
/** Chat UI users can still start any agent's CLI from the + menu; the hover card shows how. */
|
||||
export function ChatModeTerminalTip() {
|
||||
const howLabel = translate(
|
||||
const t = useTranslate()
|
||||
const howLabel = t(
|
||||
'components.onboarding.agentStep.terminalTipHow',
|
||||
'Show how to start an agent in a terminal'
|
||||
)
|
||||
@@ -191,11 +200,11 @@ export function ChatModeTerminalTip() {
|
||||
defaults={
|
||||
// The browser client has no Chat UI launch switch; its default lives in the checklist.
|
||||
isWebClientLocation()
|
||||
? translate(
|
||||
? t(
|
||||
'components.onboarding.agentStep.terminalTipWeb',
|
||||
'Switch back any time in <em>Settings → Onboarding checklist</em>. You can also start any agent in a terminal from the <em>+</em> menu.'
|
||||
)
|
||||
: translate(
|
||||
: t(
|
||||
'components.onboarding.agentStep.terminalTip',
|
||||
'Switch back any time in <em>Settings → Chat UI</em>. You can also start any agent in a terminal from the <em>+</em> menu.'
|
||||
)
|
||||
@@ -227,7 +236,7 @@ export function ChatModeTerminalTip() {
|
||||
<p className="max-w-[300px] text-xs leading-relaxed text-muted-foreground">
|
||||
<Trans
|
||||
i18n={i18n}
|
||||
defaults={translate(
|
||||
defaults={t(
|
||||
'components.onboarding.agentStep.terminalTipCaption',
|
||||
"Click <em>+</em> in the tab bar, then pick an agent marked <em>(CLI)</em>. If a chat agent's CLI is installed, hover its row and click the terminal icon to use the CLI."
|
||||
)}
|
||||
@@ -242,13 +251,14 @@ export function ChatModeTerminalTip() {
|
||||
}
|
||||
|
||||
export function TerminalModeChatTip({ onUseChat }: { onUseChat: () => void }) {
|
||||
const t = useTranslate()
|
||||
return (
|
||||
<div className="flex shrink-0 flex-wrap items-center gap-x-2 gap-y-1 text-xs text-muted-foreground">
|
||||
<MessagesSquare className="size-3.5" />
|
||||
<span>
|
||||
<Trans
|
||||
i18n={i18n}
|
||||
defaults={translate(
|
||||
defaults={t(
|
||||
'components.onboarding.agentStep.chatTip',
|
||||
'Want chat too? Turn it on any time in <em>Settings → Chat UI</em>.'
|
||||
)}
|
||||
@@ -260,7 +270,7 @@ export function TerminalModeChatTip({ onUseChat }: { onUseChat: () => void }) {
|
||||
className="font-medium text-foreground underline underline-offset-2 outline-none focus-visible:ring-2 focus-visible:ring-ring"
|
||||
onClick={onUseChat}
|
||||
>
|
||||
{translate('components.onboarding.agentStep.turnOnChat', 'Turn on now')}
|
||||
{t('components.onboarding.agentStep.turnOnChat', 'Turn on now')}
|
||||
</button>
|
||||
</div>
|
||||
)
|
||||
|
||||
@@ -10,7 +10,8 @@ import {
|
||||
import { Trans } from 'react-i18next'
|
||||
import { cn } from '@/lib/utils'
|
||||
import { RadioGroup, RadioGroupItem } from '@/components/ui/radio-group'
|
||||
import { i18n, translate } from '@/i18n/i18n'
|
||||
import { i18n } from '@/i18n/i18n'
|
||||
import { useTranslate } from '@/i18n/use-translate'
|
||||
import { nativeChatToolRowVerb } from '@/components/native-chat/tools/lines/native-chat-tool-line-label'
|
||||
import { DiffLineCounts } from '@/components/right-sidebar/source-control/listing/diff-line-counts'
|
||||
import type { AgentSurface } from './use-onboarding-flow-types'
|
||||
@@ -27,6 +28,7 @@ export function AgentSurfaceStep({
|
||||
onSelect,
|
||||
noChatAgentDetected = false
|
||||
}: AgentSurfaceStepProps) {
|
||||
const t = useTranslate()
|
||||
return (
|
||||
<div className="space-y-6">
|
||||
<RadioGroup
|
||||
@@ -41,18 +43,18 @@ export function AgentSurfaceStep({
|
||||
<SurfaceOption
|
||||
value="chat"
|
||||
icon={MessagesSquare}
|
||||
label={translate('components.onboarding.agentSurface.chat.label', 'Chat UI')}
|
||||
description={translate(
|
||||
label={t('components.onboarding.agentSurface.chat.label', 'Chat UI')}
|
||||
description={t(
|
||||
'components.onboarding.agentSurface.chat.description',
|
||||
'Agents reply in a chat, with rendered messages and diffs.'
|
||||
)}
|
||||
note={translate(
|
||||
note={t(
|
||||
'components.onboarding.agentSurface.chat.terminalFallback',
|
||||
'Agents without chat support, and SSH or WSL projects, still open in a terminal.'
|
||||
)}
|
||||
warning={
|
||||
noChatAgentDetected
|
||||
? translate(
|
||||
? t(
|
||||
'components.onboarding.agentSurface.chat.noneDetected',
|
||||
'None of your detected agents work in Chat UI.'
|
||||
)
|
||||
@@ -64,8 +66,8 @@ export function AgentSurfaceStep({
|
||||
<SurfaceOption
|
||||
value="terminal"
|
||||
icon={SquareTerminal}
|
||||
label={translate('components.onboarding.agentSurface.terminal.label', 'Terminal')}
|
||||
description={translate(
|
||||
label={t('components.onboarding.agentSurface.terminal.label', 'Terminal')}
|
||||
description={t(
|
||||
'components.onboarding.agentSurface.terminal.description',
|
||||
"Each agent's own CLI, exactly as it ships. Works with every CLI agent."
|
||||
)}
|
||||
@@ -78,7 +80,7 @@ export function AgentSurfaceStep({
|
||||
<span>
|
||||
<Trans
|
||||
i18n={i18n}
|
||||
defaults={translate(
|
||||
defaults={t(
|
||||
'components.onboarding.agentSurface.switchNote',
|
||||
'Not a permanent choice. Switch any time in <em>Settings → Chat UI</em>.'
|
||||
)}
|
||||
@@ -166,19 +168,17 @@ const CHAT_DIFF_LINES = [
|
||||
|
||||
// Mirrors the real native chat: bordered user bubble, edit row with a rendered diff, plain reply.
|
||||
function ChatPreview() {
|
||||
const t = useTranslate()
|
||||
return (
|
||||
<div className="native-chat-appearance flex size-full flex-col bg-chat-canvas text-[10.5px] leading-[15px] text-chat-foreground">
|
||||
<div className="flex min-h-0 flex-1 flex-col gap-2 overflow-hidden px-3.5 pt-2.5">
|
||||
<div className="ml-auto max-w-[80%] truncate rounded-[10px] border border-chat-user-border bg-chat-user-surface px-2.5 py-1 text-chat-foreground-strong">
|
||||
{translate(
|
||||
'components.onboarding.agentSurface.preview.userMessage',
|
||||
'Add retry to fetchUser'
|
||||
)}
|
||||
{t('components.onboarding.agentSurface.preview.userMessage', 'Add retry to fetchUser')}
|
||||
</div>
|
||||
<div>
|
||||
<div className="flex items-center gap-1.5 whitespace-nowrap">
|
||||
<FilePen className="size-3 shrink-0 text-chat-foreground-faint" />
|
||||
<span className="text-chat-foreground-faint">{nativeChatToolRowVerb('edited')}</span>
|
||||
<span className="text-chat-foreground-faint">{nativeChatToolRowVerb('edited', t)}</span>
|
||||
<span className="text-chat-foreground-strong">{PREVIEW_FILE}</span>
|
||||
<DiffLineCounts added={1} removed={1} />
|
||||
</div>
|
||||
@@ -210,7 +210,7 @@ function ChatPreview() {
|
||||
</div>
|
||||
</div>
|
||||
<p className="truncate text-chat-foreground-strong">
|
||||
{translate(
|
||||
{t(
|
||||
'components.onboarding.agentSurface.preview.agentMessage',
|
||||
'Failed requests now retry up to 3 times.'
|
||||
)}
|
||||
@@ -219,7 +219,7 @@ function ChatPreview() {
|
||||
<div className="shrink-0 px-3 pt-1.5 pb-2.5">
|
||||
<div className="flex items-center gap-2 rounded-[10px] border border-chat-composer-border bg-chat-composer-surface py-1 pr-1 pl-2.5 shadow-xs">
|
||||
<span className="truncate text-chat-foreground-faint">
|
||||
{translate(
|
||||
{t(
|
||||
'components.onboarding.agentSurface.preview.composerPlaceholder',
|
||||
'Ask a follow-up…'
|
||||
)}
|
||||
|
||||
@@ -16,7 +16,7 @@ import {
|
||||
onboardingFeatureSetupTelemetrySelection,
|
||||
type OnboardingFeatureSetupSelection
|
||||
} from './onboarding-feature-setup'
|
||||
import { translate } from '@/i18n/i18n'
|
||||
import { useTranslate } from '@/i18n/use-translate'
|
||||
|
||||
type FeatureSetupInlineTerminalProps = {
|
||||
command: string
|
||||
@@ -29,6 +29,7 @@ export function FeatureSetupInlineTerminal({
|
||||
runtimeContext,
|
||||
selection
|
||||
}: FeatureSetupInlineTerminalProps): React.JSX.Element {
|
||||
const t = useTranslate()
|
||||
const terminalOpenedTrackedRef = useRef(false)
|
||||
const terminalInteractedTrackedRef = useRef(false)
|
||||
const activeSkillRuntime = useActiveProjectSkillRuntime()
|
||||
@@ -80,15 +81,12 @@ export function FeatureSetupInlineTerminal({
|
||||
prepareCommandForShell={prepareCommandForShell}
|
||||
shellOverride={setupRuntime.terminalShellOverride}
|
||||
forceHostRuntime={Boolean(setupRuntime.installDisabledReason)}
|
||||
title={translate(
|
||||
'auto.components.onboarding.FeatureSetupInlineTerminal.c767ab7061',
|
||||
'Skill setup'
|
||||
)}
|
||||
ariaLabel={translate(
|
||||
title={t('auto.components.onboarding.FeatureSetupInlineTerminal.c767ab7061', 'Skill setup')}
|
||||
ariaLabel={t(
|
||||
'auto.components.onboarding.FeatureSetupInlineTerminal.47fc6cc6dc',
|
||||
'Skill setup command'
|
||||
)}
|
||||
description={translate(
|
||||
description={t(
|
||||
'auto.components.onboarding.FeatureSetupInlineTerminal.789b59936e',
|
||||
'Press Enter to run the command and confirm npx if asked. You can also set this up later in Settings.'
|
||||
)}
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
import { Check } from 'lucide-react'
|
||||
import type { TerminalConfigImportPreview } from '../../../../shared/terminal/appearance/terminal-config-import-preview'
|
||||
import ghosttyIcon from '../../../../../resources/ghostty.svg'
|
||||
import { translate } from '@/i18n/i18n'
|
||||
import { useTranslate } from '@/i18n/use-translate'
|
||||
import type { DiscoveryState } from './ThemeStep'
|
||||
|
||||
export function GhosttyDiscoveryRow({
|
||||
@@ -15,6 +15,7 @@ export function GhosttyDiscoveryRow({
|
||||
disabled: boolean
|
||||
onImport: (preview: TerminalConfigImportPreview) => void
|
||||
}) {
|
||||
const t = useTranslate()
|
||||
// Non-Mac discovery remains idle; hide it instead of leaving a permanent looking placeholder.
|
||||
if (discovery.status === 'absent' || discovery.status === 'idle') {
|
||||
return null
|
||||
@@ -24,10 +25,7 @@ export function GhosttyDiscoveryRow({
|
||||
return (
|
||||
<div className="flex items-center gap-2.5 rounded-lg border border-dashed border-border bg-transparent px-3.5 py-2.5 text-[12px] text-muted-foreground">
|
||||
<span className="size-1.5 animate-pulse rounded-full bg-muted-foreground/60" />
|
||||
{translate(
|
||||
'auto.components.onboarding.ThemeStep.2c3aa538f8',
|
||||
'Looking for a Ghostty config…'
|
||||
)}
|
||||
{t('auto.components.onboarding.ThemeStep.2c3aa538f8', 'Looking for a Ghostty config…')}
|
||||
</div>
|
||||
)
|
||||
}
|
||||
@@ -38,7 +36,7 @@ export function GhosttyDiscoveryRow({
|
||||
<Check className="size-3.5" />
|
||||
<span className="flex-1">
|
||||
<span className="font-medium">
|
||||
{translate('auto.components.onboarding.ThemeStep.78b6386140', 'Imported from Ghostty.')}
|
||||
{t('auto.components.onboarding.ThemeStep.78b6386140', 'Imported from Ghostty.')}
|
||||
</span>
|
||||
{discovery.fields.length > 0 && (
|
||||
<span className="text-muted-foreground"> {discovery.fields.join(' · ')}</span>
|
||||
@@ -55,16 +53,13 @@ export function GhosttyDiscoveryRow({
|
||||
<div className="min-w-0 flex-1">
|
||||
<div className="text-[12px] text-foreground">
|
||||
<span className="font-medium">
|
||||
{translate(
|
||||
'auto.components.onboarding.ThemeStep.7ee9234e54',
|
||||
'Ghostty config detected.'
|
||||
)}
|
||||
{t('auto.components.onboarding.ThemeStep.7ee9234e54', 'Ghostty config detected.')}
|
||||
</span>{' '}
|
||||
<span className="text-muted-foreground">
|
||||
{translate('auto.components.onboarding.ThemeStep.248c812283', 'Import')}{' '}
|
||||
{t('auto.components.onboarding.ThemeStep.248c812283', 'Import')}{' '}
|
||||
{fields.length > 0
|
||||
? fields.map((f) => f.toLowerCase()).join(', ')
|
||||
: translate('auto.components.onboarding.ThemeStep.906c4373fe', 'settings')}
|
||||
: t('auto.components.onboarding.ThemeStep.906c4373fe', 'settings')}
|
||||
?
|
||||
</span>
|
||||
</div>
|
||||
@@ -83,8 +78,8 @@ export function GhosttyDiscoveryRow({
|
||||
onClick={() => onImport(preview)}
|
||||
>
|
||||
{importing
|
||||
? translate('auto.components.onboarding.ThemeStep.ad19e5c916', 'Importing…')
|
||||
: translate('auto.components.onboarding.ThemeStep.248c812283', 'Import')}
|
||||
? t('auto.components.onboarding.ThemeStep.ad19e5c916', 'Importing…')
|
||||
: t('auto.components.onboarding.ThemeStep.248c812283', 'Import')}
|
||||
</button>
|
||||
</div>
|
||||
)
|
||||
|
||||
@@ -7,7 +7,7 @@ import { useAppStore } from '@/store'
|
||||
import { IntegrationStatusPill } from '@/components/integration-status-pill'
|
||||
import { cn } from '@/lib/utils'
|
||||
import { OnboardingInlineCommandTerminal } from './OnboardingInlineCommandTerminal'
|
||||
import { translate } from '@/i18n/i18n'
|
||||
import { useTranslate } from '@/i18n/use-translate'
|
||||
import { openAppHttpLink } from '@/lib/browser/navigation/app-http-link-open'
|
||||
|
||||
type GitHubSetupState = 'checking' | 'connected' | 'not-installed' | 'not-authenticated'
|
||||
@@ -25,6 +25,7 @@ function getGitHubSetupState(
|
||||
}
|
||||
|
||||
export function GitHubRow(props: { compact?: boolean } = {}): React.JSX.Element {
|
||||
const t = useTranslate()
|
||||
const { compact = false } = props
|
||||
const preflightStatus = useAppStore((s) => s.preflightStatus)
|
||||
const preflightStatusLoading = useAppStore((s) => s.preflightStatusLoading)
|
||||
@@ -45,34 +46,28 @@ export function GitHubRow(props: { compact?: boolean } = {}): React.JSX.Element
|
||||
<div className="min-w-0 flex-1">
|
||||
<div className="flex flex-wrap items-center gap-2">
|
||||
<h3 className="text-[15px] font-semibold leading-tight text-foreground">
|
||||
{translate('auto.components.onboarding.IntegrationsStep.217beb0658', 'GitHub')}
|
||||
{t('auto.components.onboarding.IntegrationsStep.217beb0658', 'GitHub')}
|
||||
</h3>
|
||||
{state === 'connected' ? (
|
||||
<IntegrationStatusPill tone="connected">
|
||||
{translate('auto.components.onboarding.IntegrationsStep.c91a5782f1', 'Connected')}
|
||||
{t('auto.components.onboarding.IntegrationsStep.c91a5782f1', 'Connected')}
|
||||
</IntegrationStatusPill>
|
||||
) : state === 'not-installed' ? (
|
||||
<IntegrationStatusPill tone="attention">
|
||||
{translate(
|
||||
'auto.components.onboarding.IntegrationsStep.5c115cb713',
|
||||
'CLI not installed'
|
||||
)}
|
||||
{t('auto.components.onboarding.IntegrationsStep.5c115cb713', 'CLI not installed')}
|
||||
</IntegrationStatusPill>
|
||||
) : state === 'not-authenticated' ? (
|
||||
<IntegrationStatusPill tone="attention">
|
||||
{translate(
|
||||
'auto.components.onboarding.IntegrationsStep.8405043962',
|
||||
'Sign in needed'
|
||||
)}
|
||||
{t('auto.components.onboarding.IntegrationsStep.8405043962', 'Sign in needed')}
|
||||
</IntegrationStatusPill>
|
||||
) : (
|
||||
<IntegrationStatusPill tone="neutral">
|
||||
{translate('auto.components.onboarding.IntegrationsStep.c1547656f0', 'Checking…')}
|
||||
{t('auto.components.onboarding.IntegrationsStep.c1547656f0', 'Checking…')}
|
||||
</IntegrationStatusPill>
|
||||
)}
|
||||
</div>
|
||||
<p className="mt-1 text-[13px] leading-relaxed text-muted-foreground">
|
||||
{translate(
|
||||
{t(
|
||||
'auto.components.onboarding.IntegrationsStep.50db38cf4b',
|
||||
'Pull requests, issues, and check status.'
|
||||
)}
|
||||
@@ -87,7 +82,7 @@ export function GitHubRow(props: { compact?: boolean } = {}): React.JSX.Element
|
||||
onClick={(event) => openAppHttpLink('https://cli.github.com', { event })}
|
||||
>
|
||||
<ExternalLink className="size-3.5" />
|
||||
{translate('auto.components.onboarding.IntegrationsStep.bd5d976fb2', 'Install gh')}
|
||||
{t('auto.components.onboarding.IntegrationsStep.bd5d976fb2', 'Install gh')}
|
||||
</Button>
|
||||
) : null}
|
||||
{state === 'not-authenticated' ? (
|
||||
@@ -99,8 +94,8 @@ export function GitHubRow(props: { compact?: boolean } = {}): React.JSX.Element
|
||||
>
|
||||
<Terminal className="size-3.5" />
|
||||
{githubTerminalOpen
|
||||
? translate('auto.components.onboarding.IntegrationsStep.0b4a7d23ab', 'Signing in')
|
||||
: translate('auto.components.onboarding.IntegrationsStep.d6e5dba05a', 'Sign in')}
|
||||
? t('auto.components.onboarding.IntegrationsStep.0b4a7d23ab', 'Signing in')
|
||||
: t('auto.components.onboarding.IntegrationsStep.d6e5dba05a', 'Sign in')}
|
||||
</Button>
|
||||
) : null}
|
||||
{state !== 'connected' ? (
|
||||
@@ -109,7 +104,7 @@ export function GitHubRow(props: { compact?: boolean } = {}): React.JSX.Element
|
||||
size="sm"
|
||||
onClick={() => void refreshPreflightStatus({ force: true, wakeWslDistro: true })}
|
||||
>
|
||||
{translate('auto.components.onboarding.IntegrationsStep.80e3ce0bc9', 'Re-check')}
|
||||
{t('auto.components.onboarding.IntegrationsStep.80e3ce0bc9', 'Re-check')}
|
||||
</Button>
|
||||
) : null}
|
||||
</div>
|
||||
@@ -118,15 +113,12 @@ export function GitHubRow(props: { compact?: boolean } = {}): React.JSX.Element
|
||||
<div className={cn(compact ? 'px-4 pb-4' : 'px-5 pb-5')}>
|
||||
<OnboardingInlineCommandTerminal
|
||||
command="gh auth login"
|
||||
title={translate(
|
||||
'auto.components.onboarding.IntegrationsStep.6d469169f2',
|
||||
'GitHub setup'
|
||||
)}
|
||||
ariaLabel={translate(
|
||||
title={t('auto.components.onboarding.IntegrationsStep.6d469169f2', 'GitHub setup')}
|
||||
ariaLabel={t(
|
||||
'auto.components.onboarding.IntegrationsStep.f9d2e12d17',
|
||||
'GitHub sign in command'
|
||||
)}
|
||||
description={translate(
|
||||
description={t(
|
||||
'auto.components.onboarding.IntegrationsStep.af69f42372',
|
||||
'Press Enter to run GitHub CLI auth. Re-check GitHub after the browser or device flow finishes.'
|
||||
)}
|
||||
@@ -138,6 +130,7 @@ export function GitHubRow(props: { compact?: boolean } = {}): React.JSX.Element
|
||||
}
|
||||
|
||||
export function LinearRow(props: { compact?: boolean } = {}): React.JSX.Element {
|
||||
const t = useTranslate()
|
||||
const { compact = false } = props
|
||||
const linearStatus = useAppStore((s) => s.linearStatus)
|
||||
const checkLinearConnection = useAppStore((s) => s.checkLinearConnection)
|
||||
@@ -157,31 +150,28 @@ export function LinearRow(props: { compact?: boolean } = {}): React.JSX.Element
|
||||
<div className="min-w-0 flex-1">
|
||||
<div className="flex flex-wrap items-center gap-2">
|
||||
<h3 className="text-[15px] font-semibold leading-tight text-foreground">
|
||||
{translate('auto.components.onboarding.IntegrationsStep.27743304b1', 'Linear')}
|
||||
{t('auto.components.onboarding.IntegrationsStep.27743304b1', 'Linear')}
|
||||
</h3>
|
||||
{linearStatus.connected ? (
|
||||
<IntegrationStatusPill tone="connected">
|
||||
{translate(
|
||||
'auto.components.onboarding.IntegrationsStep.c91a5782f1',
|
||||
'Connected'
|
||||
)}
|
||||
{t('auto.components.onboarding.IntegrationsStep.c91a5782f1', 'Connected')}
|
||||
</IntegrationStatusPill>
|
||||
) : null}
|
||||
</div>
|
||||
<p className="mt-1 text-[13px] leading-relaxed text-muted-foreground">
|
||||
{linearStatus.connected
|
||||
? workspaceCount === 1
|
||||
? translate(
|
||||
? t(
|
||||
'auto.components.onboarding.IntegrationsStep.linearWorkspacesLinkedSingular',
|
||||
'{{count}} workspace linked. Add another workspace or replace a restricted key any time.',
|
||||
{ count: workspaceCount }
|
||||
)
|
||||
: translate(
|
||||
: t(
|
||||
'auto.components.onboarding.IntegrationsStep.linearWorkspacesLinkedPlural',
|
||||
'{{count}} workspaces linked. Add another workspace or replace a restricted key any time.',
|
||||
{ count: workspaceCount }
|
||||
)
|
||||
: translate(
|
||||
: t(
|
||||
'auto.components.onboarding.IntegrationsStep.4983ae7433',
|
||||
'Add Linear access with a Personal API key. Full-access keys can show every team the key owner can access.'
|
||||
)}
|
||||
@@ -191,22 +181,19 @@ export function LinearRow(props: { compact?: boolean } = {}): React.JSX.Element
|
||||
<div className={cn('flex items-center gap-2', compact ? 'flex-wrap' : 'shrink-0')}>
|
||||
{linearStatus.connected ? (
|
||||
<Button variant="outline" size="sm" onClick={() => setDialogOpen(true)}>
|
||||
{translate(
|
||||
{t(
|
||||
'auto.components.onboarding.IntegrationsStep.dd9c186a8b',
|
||||
'Add workspace access'
|
||||
)}
|
||||
</Button>
|
||||
) : (
|
||||
<Button size="sm" onClick={() => setDialogOpen(true)}>
|
||||
{translate(
|
||||
'auto.components.onboarding.IntegrationsStep.04ef416712',
|
||||
'Add Linear access'
|
||||
)}
|
||||
{t('auto.components.onboarding.IntegrationsStep.04ef416712', 'Add Linear access')}
|
||||
</Button>
|
||||
)}
|
||||
{!linearStatus.connected ? (
|
||||
<Button variant="ghost" size="sm" onClick={() => void checkLinearConnection(true)}>
|
||||
{translate('auto.components.onboarding.IntegrationsStep.80e3ce0bc9', 'Re-check')}
|
||||
{t('auto.components.onboarding.IntegrationsStep.80e3ce0bc9', 'Re-check')}
|
||||
</Button>
|
||||
) : null}
|
||||
</div>
|
||||
@@ -218,7 +205,7 @@ export function LinearRow(props: { compact?: boolean } = {}): React.JSX.Element
|
||||
onOpenChange={setDialogOpen}
|
||||
overlayClassName="z-[110]"
|
||||
contentClassName="z-[120]"
|
||||
connectLabel={translate(
|
||||
connectLabel={t(
|
||||
'auto.components.onboarding.IntegrationsStep.04ef416712',
|
||||
'Add Linear access'
|
||||
)}
|
||||
@@ -248,6 +235,7 @@ const CAPABILITIES = [
|
||||
] as const
|
||||
|
||||
export function IntegrationsStep(): React.JSX.Element {
|
||||
const t = useTranslate()
|
||||
const refreshPreflightStatus = useAppStore((s) => s.refreshPreflightStatus)
|
||||
|
||||
useEffect(() => {
|
||||
@@ -260,7 +248,7 @@ export function IntegrationsStep(): React.JSX.Element {
|
||||
{CAPABILITIES.map(({ key, fallback }) => (
|
||||
<li key={key} className="flex gap-2.5">
|
||||
<span className="mt-2 size-1 shrink-0 rounded-full bg-muted-foreground" aria-hidden />
|
||||
<span>{translate(key, fallback)}</span>
|
||||
<span>{t(key, fallback)}</span>
|
||||
</li>
|
||||
))}
|
||||
</ul>
|
||||
@@ -270,13 +258,10 @@ export function IntegrationsStep(): React.JSX.Element {
|
||||
<div className="mt-4 rounded-xl border border-border bg-muted/10 px-5 py-4">
|
||||
<div className="flex flex-col gap-1 sm:flex-row sm:items-center sm:justify-between">
|
||||
<span className="text-[14px] font-medium text-foreground/70">
|
||||
{translate(
|
||||
'auto.components.onboarding.IntegrationsStep.3a3e360289',
|
||||
'More task sources'
|
||||
)}
|
||||
{t('auto.components.onboarding.IntegrationsStep.3a3e360289', 'More task sources')}
|
||||
</span>
|
||||
<span className="text-[13px] leading-relaxed text-muted-foreground">
|
||||
{translate(
|
||||
{t(
|
||||
'auto.components.onboarding.IntegrationsStep.277f30eb34',
|
||||
'Linear, GitLab, Bitbucket, Azure DevOps, Gitea, and Jira live in Settings > Integrations.'
|
||||
)}
|
||||
|
||||
@@ -18,7 +18,7 @@ import {
|
||||
useMacNotificationPermissionState
|
||||
} from '@/components/notifications/mac-notification-permission-card'
|
||||
import { useMountedRef } from '@/hooks/useMountedRef'
|
||||
import { translate } from '@/i18n/i18n'
|
||||
import { useTranslate } from '@/i18n/use-translate'
|
||||
|
||||
type NotificationStepProps = {
|
||||
settings: GlobalSettings | null
|
||||
@@ -31,6 +31,7 @@ export function NotificationStep({
|
||||
settings,
|
||||
updateSettings
|
||||
}: NotificationStepProps): React.JSX.Element {
|
||||
const t = useTranslate()
|
||||
const notificationSettings = settings?.notifications
|
||||
const notificationSettingsRef = useRef(notificationSettings)
|
||||
// Unloaded settings use enabled’s default so fresh-install permission checks can start immediately.
|
||||
@@ -86,7 +87,7 @@ export function NotificationStep({
|
||||
if (!result.played) {
|
||||
if (mountedRef.current) {
|
||||
toast.error(
|
||||
translate(
|
||||
t(
|
||||
'auto.components.onboarding.NotificationStep.b6a994e36e',
|
||||
'Notification sound could not be played'
|
||||
)
|
||||
@@ -128,7 +129,7 @@ export function NotificationStep({
|
||||
const handleSendTestNotification = async (): Promise<void> => {
|
||||
if (!notificationSettings) {
|
||||
toast.error(
|
||||
translate(
|
||||
t(
|
||||
'auto.components.onboarding.NotificationStep.3cd5374e22',
|
||||
'Notification settings are still loading'
|
||||
)
|
||||
@@ -155,7 +156,7 @@ export function NotificationStep({
|
||||
if (!notificationSettings) {
|
||||
return (
|
||||
<div className="rounded-xl border border-border bg-muted/20 px-5 py-4 text-sm text-muted-foreground">
|
||||
{translate(
|
||||
{t(
|
||||
'auto.components.onboarding.NotificationStep.e52aacf380',
|
||||
'Loading notification settings…'
|
||||
)}
|
||||
@@ -174,10 +175,10 @@ export function NotificationStep({
|
||||
<section className="space-y-3">
|
||||
<div className="space-y-1">
|
||||
<h2 className="text-sm font-semibold text-foreground">
|
||||
{translate('auto.components.onboarding.NotificationStep.0af746e41f', 'Choose a sound')}
|
||||
{t('auto.components.onboarding.NotificationStep.0af746e41f', 'Choose a sound')}
|
||||
</h2>
|
||||
<p className="text-[13px] leading-relaxed text-muted-foreground">
|
||||
{translate(
|
||||
{t(
|
||||
'auto.components.onboarding.NotificationStep.0fe570690c',
|
||||
'Pick the alert Orca plays after a desktop notification is delivered.'
|
||||
)}
|
||||
@@ -192,7 +193,7 @@ export function NotificationStep({
|
||||
>
|
||||
<SelectTrigger className="w-56 max-w-full" size="sm">
|
||||
<SelectValue
|
||||
placeholder={translate(
|
||||
placeholder={t(
|
||||
'auto.components.onboarding.NotificationStep.dc897423e1',
|
||||
'Choose notification sound'
|
||||
)}
|
||||
@@ -217,11 +218,11 @@ export function NotificationStep({
|
||||
<Upload className="size-4" />
|
||||
<span>
|
||||
{customPath
|
||||
? translate(
|
||||
? t(
|
||||
'auto.components.onboarding.NotificationStep.ac80d97e02',
|
||||
'Change custom file'
|
||||
)
|
||||
: translate(
|
||||
: t(
|
||||
'auto.components.onboarding.NotificationStep.c0692baa52',
|
||||
'Choose custom file'
|
||||
)}
|
||||
@@ -237,10 +238,7 @@ export function NotificationStep({
|
||||
onClick={() => void handleSendTestNotification()}
|
||||
>
|
||||
<BellRing className="size-3.5" />
|
||||
{translate(
|
||||
'auto.components.onboarding.NotificationStep.3bede04483',
|
||||
'Send test notification'
|
||||
)}
|
||||
{t('auto.components.onboarding.NotificationStep.3bede04483', 'Send test notification')}
|
||||
</Button>
|
||||
</div>
|
||||
</section>
|
||||
|
||||
@@ -15,7 +15,7 @@ import { OnboardingSkipConfirmationDialog } from './OnboardingSkipConfirmationDi
|
||||
import { OnboardingFooter } from './OnboardingFooter'
|
||||
import { shouldRequestOnboardingSkipConfirmation } from './onboarding-dismiss-target'
|
||||
import logo from '../../../../../resources/logo.svg'
|
||||
import { translate } from '@/i18n/i18n'
|
||||
import { useTranslate } from '@/i18n/use-translate'
|
||||
import { addAppKeyListener, removeAppKeyListener } from '@/lib/app-keyboard-router'
|
||||
import { useScrollEdgeFades } from '@/lib/use-scroll-edge-fades'
|
||||
import { AgentSurfaceStep } from './AgentSurfaceStep'
|
||||
@@ -30,6 +30,7 @@ export default function OnboardingFlow({
|
||||
onboarding,
|
||||
onOnboardingChange
|
||||
}: OnboardingFlowProps): React.JSX.Element {
|
||||
const t = useTranslate()
|
||||
const flow = useOnboardingFlow(onboarding, onOnboardingChange)
|
||||
const stepFades = useScrollEdgeFades<HTMLDivElement>()
|
||||
const continueShortcutModifierLabel = getScreenSubmitModifierLabel()
|
||||
@@ -43,8 +44,8 @@ export default function OnboardingFlow({
|
||||
const footerPrimaryLabel =
|
||||
busyLabel ??
|
||||
(currentStep.id === 'notifications' && !flow.hasSelectedWorkspace
|
||||
? translate('components.onboarding.flow.actions.addFirstProject', 'Add your first project')
|
||||
: translate('components.onboarding.flow.actions.continue', 'Continue'))
|
||||
? t('components.onboarding.flow.actions.addFirstProject', 'Add your first project')
|
||||
: t('components.onboarding.flow.actions.continue', 'Continue'))
|
||||
const [languagePickerOpen, setLanguagePickerOpen] = useState(false)
|
||||
const [skipConfirmOpen, setSkipConfirmOpen] = useState(false)
|
||||
const skipConfirmAdvancedViaRef = useRef<'button' | 'keyboard'>('button')
|
||||
@@ -120,10 +121,7 @@ export default function OnboardingFlow({
|
||||
<section
|
||||
ref={flow.setLifecycleRootRef}
|
||||
role="dialog"
|
||||
aria-label={translate(
|
||||
'auto.components.onboarding.OnboardingFlow.277ba45540',
|
||||
'Orca onboarding'
|
||||
)}
|
||||
aria-label={t('auto.components.onboarding.OnboardingFlow.277ba45540', 'Orca onboarding')}
|
||||
aria-modal="true"
|
||||
data-onboarding-modal
|
||||
className={cn(
|
||||
@@ -140,9 +138,7 @@ export default function OnboardingFlow({
|
||||
aria-hidden="true"
|
||||
className="h-7 w-auto shrink-0 invert dark:invert-0"
|
||||
/>
|
||||
<span>
|
||||
{translate('auto.components.onboarding.OnboardingFlow.a249f81538', 'Orca')}
|
||||
</span>
|
||||
<span>{t('auto.components.onboarding.OnboardingFlow.a249f81538', 'Orca')}</span>
|
||||
</div>
|
||||
<OnboardingLanguageSwitcher
|
||||
open={languagePickerOpen}
|
||||
@@ -169,7 +165,7 @@ export default function OnboardingFlow({
|
||||
? 'w-6 bg-muted-foreground/70 hover:bg-foreground/80'
|
||||
: 'w-6 bg-muted-foreground/25 hover:bg-muted-foreground/45'
|
||||
)}
|
||||
aria-label={translate(
|
||||
aria-label={t(
|
||||
'auto.components.onboarding.OnboardingFlow.adaa0aa627',
|
||||
'Go to onboarding step {{value0}}: {{value1}}',
|
||||
{ value0: progressIdx + 1, value1: stepTooltipLabel }
|
||||
@@ -185,7 +181,7 @@ export default function OnboardingFlow({
|
||||
)
|
||||
})}
|
||||
<span className="ml-3 text-xs font-medium text-muted-foreground">
|
||||
{translate(
|
||||
{t(
|
||||
'auto.components.onboarding.OnboardingFlow.stepOfTotal',
|
||||
'{{current}} of {{total}}',
|
||||
{ current: flow.progressStepIndex + 1, total: flow.progressSteps.length }
|
||||
@@ -196,10 +192,7 @@ export default function OnboardingFlow({
|
||||
<div className="mt-8 shrink-0">
|
||||
{stepIndex === 0 && (
|
||||
<div className="mb-2 text-xs font-medium uppercase tracking-[0.18em] text-muted-foreground">
|
||||
{translate(
|
||||
'auto.components.onboarding.OnboardingFlow.1b5e182e9f',
|
||||
'Welcome to Orca'
|
||||
)}
|
||||
{t('auto.components.onboarding.OnboardingFlow.1b5e182e9f', 'Welcome to Orca')}
|
||||
</div>
|
||||
)}
|
||||
<h1 className="text-[34px] font-semibold leading-[1.15] tracking-tight text-foreground">
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
import { ChevronLeft, CornerDownLeft, Loader2 } from 'lucide-react'
|
||||
import { translate } from '@/i18n/i18n'
|
||||
import { useTranslate } from '@/i18n/use-translate'
|
||||
|
||||
type OnboardingFooterProps = {
|
||||
shouldShowSetUpLater: boolean
|
||||
@@ -26,6 +26,7 @@ export function OnboardingFooter({
|
||||
shortcutModifierLabel,
|
||||
onPrimary
|
||||
}: OnboardingFooterProps): React.JSX.Element {
|
||||
const t = useTranslate()
|
||||
return (
|
||||
<footer className="mt-6 flex flex-none items-center justify-between border-t border-border pt-5">
|
||||
{shouldShowSetUpLater ? (
|
||||
@@ -34,7 +35,7 @@ export function OnboardingFooter({
|
||||
disabled={Boolean(busyLabel)}
|
||||
onClick={onSetUpLater}
|
||||
>
|
||||
{translate('auto.components.onboarding.OnboardingFooter.111d3f8d92', 'Set up later')}
|
||||
{t('auto.components.onboarding.OnboardingFooter.111d3f8d92', 'Set up later')}
|
||||
</button>
|
||||
) : (
|
||||
<span />
|
||||
@@ -47,7 +48,7 @@ export function OnboardingFooter({
|
||||
onClick={onBack}
|
||||
>
|
||||
<ChevronLeft className="size-4" />
|
||||
{translate('auto.components.onboarding.OnboardingFooter.ba58547306', 'Back')}
|
||||
{t('auto.components.onboarding.OnboardingFooter.ba58547306', 'Back')}
|
||||
</button>
|
||||
)}
|
||||
{showPrimary && (
|
||||
|
||||
@@ -9,7 +9,7 @@ import {
|
||||
type TerminalCommandFinishedEventDetail
|
||||
} from '@/hooks/terminal-command-finished-event'
|
||||
import { useAppStore } from '@/store'
|
||||
import { translate } from '@/i18n/i18n'
|
||||
import { useTranslate } from '@/i18n/use-translate'
|
||||
import { brandEphemeralSetupTerminalWorktreeId } from '../../../../shared/ephemeral-setup-terminal-worktree-id'
|
||||
|
||||
const ONBOARDING_INLINE_TERMINAL_WORKTREE_ID = 'onboarding-inline-terminal'
|
||||
@@ -58,6 +58,7 @@ export function OnboardingInlineCommandTerminal({
|
||||
onTerminalExit,
|
||||
onCommandFinished
|
||||
}: OnboardingInlineCommandTerminalProps): React.JSX.Element {
|
||||
const t = useTranslate()
|
||||
// Brand synthetic IDs so remote runtimes recognize the floating terminal scope.
|
||||
const worktreeId = useMemo(
|
||||
() => brandEphemeralSetupTerminalWorktreeId(worktreeIdProp),
|
||||
@@ -329,7 +330,7 @@ export function OnboardingInlineCommandTerminal({
|
||||
) : (
|
||||
<div className="flex h-full items-center justify-center gap-2 text-xs text-muted-foreground">
|
||||
<Loader2 className="size-4 animate-spin" />
|
||||
{translate(
|
||||
{t(
|
||||
'auto.components.onboarding.OnboardingInlineCommandTerminal.4123609efd',
|
||||
'Starting terminal...'
|
||||
)}
|
||||
|
||||
@@ -1,10 +1,11 @@
|
||||
import { useState } from 'react'
|
||||
import { Button } from '@/components/ui/button'
|
||||
import { useAppStore } from '@/store'
|
||||
import { translate } from '@/i18n/i18n'
|
||||
import { useTranslate } from '@/i18n/use-translate'
|
||||
import { LoadingSpinner } from '@/components/LoadingSpinner'
|
||||
|
||||
export function OnboardingLanguageLoading(): React.JSX.Element {
|
||||
const t = useTranslate()
|
||||
const settings = useAppStore((state) => state.settings)
|
||||
const fetchSettings = useAppStore((state) => state.fetchSettings)
|
||||
const [retrying, setRetrying] = useState(false)
|
||||
@@ -16,7 +17,7 @@ export function OnboardingLanguageLoading(): React.JSX.Element {
|
||||
{settings === null && !retrying ? (
|
||||
<div className="space-y-3 text-center">
|
||||
<p className="text-sm">
|
||||
{translate('onboarding.language.settingsError', 'Could not load your settings.')}
|
||||
{t('onboarding.language.settingsError', 'Could not load your settings.')}
|
||||
</p>
|
||||
<Button
|
||||
disabled={retrying}
|
||||
@@ -27,15 +28,15 @@ export function OnboardingLanguageLoading(): React.JSX.Element {
|
||||
)
|
||||
}}
|
||||
>
|
||||
{translate('onboarding.language.retry', 'Retry')}
|
||||
{t('onboarding.language.retry', 'Retry')}
|
||||
</Button>
|
||||
</div>
|
||||
) : (
|
||||
<div role="status" className="flex items-center gap-2 text-sm text-muted-foreground">
|
||||
<LoadingSpinner className="size-4" />
|
||||
{settings === null
|
||||
? translate('onboarding.language.loadingSettings', 'Loading settings…')
|
||||
: translate('onboarding.language.loading', 'Loading language…')}
|
||||
? t('onboarding.language.loadingSettings', 'Loading settings…')
|
||||
: t('onboarding.language.loading', 'Loading language…')}
|
||||
</div>
|
||||
)}
|
||||
</div>
|
||||
|
||||
@@ -5,7 +5,8 @@ import { Input } from '@/components/ui/input'
|
||||
import { Label } from '@/components/ui/label'
|
||||
import { Popover, PopoverContent, PopoverTrigger } from '@/components/ui/popover'
|
||||
import { RadioGroup, RadioGroupItem } from '@/components/ui/radio-group'
|
||||
import { i18n, translate, loadRendererUiLanguage } from '@/i18n/i18n'
|
||||
import { i18n, loadRendererUiLanguage } from '@/i18n/i18n'
|
||||
import { useTranslate } from '@/i18n/use-translate'
|
||||
import { getUiLanguageNativeLabel } from '@/i18n/supported-languages'
|
||||
import { useLanguageReadiness } from '@/i18n/language-readiness'
|
||||
import { track } from '@/lib/telemetry'
|
||||
@@ -25,6 +26,7 @@ export function OnboardingLanguageSwitcher({
|
||||
open: boolean
|
||||
onOpenChange: (open: boolean) => void
|
||||
}): React.JSX.Element | null {
|
||||
const t = useTranslate()
|
||||
const cohort = useAppStore((state) => state.settings?.telemetry?.onboardingLanguageCohort)
|
||||
const language = useAppStore((state) => state.settings?.uiLanguage ?? 'system')
|
||||
const updateSettings = useAppStore((state) => state.updateSettingsOrThrow)
|
||||
@@ -144,7 +146,7 @@ export function OnboardingLanguageSwitcher({
|
||||
<PopoverContent
|
||||
ref={contentRef}
|
||||
align="end"
|
||||
aria-label={translate('onboarding.language.title', 'Display language')}
|
||||
aria-label={t('onboarding.language.title', 'Display language')}
|
||||
className="z-[110] w-80 max-w-[calc(100vw-2rem)]"
|
||||
wheelScroll
|
||||
onOpenAutoFocus={(event) => {
|
||||
@@ -179,10 +181,10 @@ export function OnboardingLanguageSwitcher({
|
||||
}}
|
||||
>
|
||||
<ArrowLeft />
|
||||
{translate('onboarding.language.back', 'Languages')}
|
||||
{t('onboarding.language.back', 'Languages')}
|
||||
</Button>
|
||||
<Label htmlFor="onboarding-language-request">
|
||||
{translate(
|
||||
{t(
|
||||
'onboarding.language.requestPrompt',
|
||||
'Which language would you like Orca to support?'
|
||||
)}
|
||||
@@ -200,7 +202,7 @@ export function OnboardingLanguageSwitcher({
|
||||
/>
|
||||
{requestState === 'success' ? (
|
||||
<p role="status" className="text-xs text-muted-foreground">
|
||||
{translate(
|
||||
{t(
|
||||
'onboarding.language.requestSuccess',
|
||||
'Language request recorded. You can continue onboarding.'
|
||||
)}
|
||||
@@ -209,7 +211,7 @@ export function OnboardingLanguageSwitcher({
|
||||
<>
|
||||
{requestState === 'error' && (
|
||||
<p role="alert" className="text-xs text-destructive">
|
||||
{translate(
|
||||
{t(
|
||||
'onboarding.language.requestError',
|
||||
'Could not record your request. Try again.'
|
||||
)}
|
||||
@@ -223,8 +225,8 @@ export function OnboardingLanguageSwitcher({
|
||||
}
|
||||
>
|
||||
{requestState === 'error'
|
||||
? translate('onboarding.language.retry', 'Retry')
|
||||
: translate('onboarding.language.requestAction', 'Request language')}
|
||||
? t('onboarding.language.retry', 'Retry')
|
||||
: t('onboarding.language.requestAction', 'Request language')}
|
||||
</Button>
|
||||
</>
|
||||
)}
|
||||
@@ -233,20 +235,20 @@ export function OnboardingLanguageSwitcher({
|
||||
<div className="space-y-3">
|
||||
<div className="space-y-1">
|
||||
<p className="text-sm font-medium">
|
||||
{translate('onboarding.language.title', 'Display language')}
|
||||
{t('onboarding.language.title', 'Display language')}
|
||||
</p>
|
||||
<p className="text-xs text-muted-foreground">
|
||||
{detection.status === 'supported'
|
||||
? translate('onboarding.language.device', 'System language: {{language}}', {
|
||||
? t('onboarding.language.device', 'System language: {{language}}', {
|
||||
language: getUiLanguageNativeLabel(detection.locale)
|
||||
})
|
||||
: detection.status === 'unsupported'
|
||||
? translate(
|
||||
? t(
|
||||
'onboarding.language.unsupported',
|
||||
'System language: {{language}}. This language is not supported yet.',
|
||||
{ language: detection.detectedTag }
|
||||
)
|
||||
: translate(
|
||||
: t(
|
||||
'onboarding.language.unknown',
|
||||
'Your system language could not be detected. Choose a language below.'
|
||||
)}
|
||||
@@ -280,7 +282,7 @@ export function OnboardingLanguageSwitcher({
|
||||
void chooseLanguage(locale)
|
||||
}
|
||||
}}
|
||||
aria-label={translate('onboarding.language.title', 'Display language')}
|
||||
aria-label={t('onboarding.language.title', 'Display language')}
|
||||
>
|
||||
{SUPPORTED_UI_LOCALES.map((locale) => (
|
||||
<div key={locale} className="rounded-md px-2 py-2 hover:bg-accent">
|
||||
@@ -300,20 +302,20 @@ export function OnboardingLanguageSwitcher({
|
||||
</RadioGroup>
|
||||
<p role="status" className="h-4 text-xs text-muted-foreground">
|
||||
{selectionPhase === 'save'
|
||||
? translate('onboarding.language.saving', 'Saving language…')
|
||||
? t('onboarding.language.saving', 'Saving language…')
|
||||
: busy
|
||||
? translate('onboarding.language.loading', 'Loading language…')
|
||||
? t('onboarding.language.loading', 'Loading language…')
|
||||
: null}
|
||||
</p>
|
||||
{(selectionFailure !== null || catalogError) && (
|
||||
<div role="alert" className="space-y-2">
|
||||
<p className="text-xs text-destructive">
|
||||
{selectionFailure?.stage === 'save'
|
||||
? translate(
|
||||
? t(
|
||||
'onboarding.language.saveError',
|
||||
'Could not save your language preference. Try again.'
|
||||
)
|
||||
: translate(
|
||||
: t(
|
||||
'onboarding.language.loadError',
|
||||
'Could not load this language. Try again.'
|
||||
)}
|
||||
@@ -331,7 +333,7 @@ export function OnboardingLanguageSwitcher({
|
||||
}
|
||||
}}
|
||||
>
|
||||
{translate('onboarding.language.retry', 'Retry')}
|
||||
{t('onboarding.language.retry', 'Retry')}
|
||||
</Button>
|
||||
</div>
|
||||
)}
|
||||
@@ -344,7 +346,7 @@ export function OnboardingLanguageSwitcher({
|
||||
disabled={busy}
|
||||
onClick={() => setRequestView(true)}
|
||||
>
|
||||
{translate('onboarding.language.other', 'Other language…')}
|
||||
{t('onboarding.language.other', 'Other language…')}
|
||||
</Button>
|
||||
</div>
|
||||
)}
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
import { MessagesSquare, SquareTerminal } from 'lucide-react'
|
||||
import { AgentIcon, getAgentLabel } from '@/lib/agent-catalog'
|
||||
import { translate } from '@/i18n/i18n'
|
||||
import { useTranslate } from '@/i18n/use-translate'
|
||||
|
||||
// Shell text, not prose: what a terminal shows when the agent's CLI starts.
|
||||
const DEMO_PROMPT = '~/uploader ❯'
|
||||
@@ -8,7 +8,8 @@ const DEMO_COMMAND = 'gemini'
|
||||
|
||||
// Pixel positions here are the keyframe targets in main.css (.onboarding-launch-demo).
|
||||
export function TerminalLaunchDemo(): React.JSX.Element {
|
||||
const cli = translate('auto.components.tab.bar.QuickLaunchButton.cliTag', '(CLI)')
|
||||
const t = useTranslate()
|
||||
const cli = t('auto.components.tab.bar.QuickLaunchButton.cliTag', '(CLI)')
|
||||
return (
|
||||
<div
|
||||
aria-hidden
|
||||
@@ -35,10 +36,10 @@ export function TerminalLaunchDemo(): React.JSX.Element {
|
||||
</div>
|
||||
<div className="absolute inset-x-0 bottom-0 top-7 flex flex-col gap-1.5 p-2.5">
|
||||
<span className="max-w-[75%] self-end rounded-lg bg-foreground px-2 py-1 text-background">
|
||||
{translate('components.onboarding.launchDemo.userMessage', 'Fix the flaky upload test')}
|
||||
{t('components.onboarding.launchDemo.userMessage', 'Fix the flaky upload test')}
|
||||
</span>
|
||||
<span className="max-w-[80%] rounded-lg border border-border px-2 py-1">
|
||||
{translate(
|
||||
{t(
|
||||
'components.onboarding.launchDemo.agentMessage',
|
||||
"Found it. The retry timer wasn't mocked."
|
||||
)}
|
||||
@@ -52,7 +53,7 @@ export function TerminalLaunchDemo(): React.JSX.Element {
|
||||
<span className="text-status-success">{DEMO_PROMPT}</span> {DEMO_COMMAND}
|
||||
</div>
|
||||
<div className="text-muted-foreground">
|
||||
{translate('components.onboarding.launchDemo.cliReady', '{{agent}} CLI ready.', {
|
||||
{t('components.onboarding.launchDemo.cliReady', '{{agent}} CLI ready.', {
|
||||
agent: getAgentLabel('gemini')
|
||||
})}
|
||||
</div>
|
||||
|
||||
@@ -2,7 +2,7 @@ import type React from 'react'
|
||||
import { useEffect, useRef, useState } from 'react'
|
||||
import { Check, ChevronLeft, ChevronRight, ExternalLink, Puzzle } from 'lucide-react'
|
||||
import { cn } from '@/lib/utils'
|
||||
import { translate } from '@/i18n/i18n'
|
||||
import { useTranslate } from '@/i18n/use-translate'
|
||||
import { Button } from '@/components/ui/button'
|
||||
import type { ThemeLook } from './onboarding-theme-looks'
|
||||
import { ThemeLookCardPreview } from './ThemeLookPreview'
|
||||
@@ -37,6 +37,7 @@ export function ThemeLookCarousel({
|
||||
ariaLabel: string
|
||||
header: React.ReactNode
|
||||
}): React.JSX.Element {
|
||||
const t = useTranslate()
|
||||
const trackRef = useRef<HTMLDivElement>(null)
|
||||
const [edges, setEdges] = useState<ScrollEdges>({ start: true, end: false })
|
||||
const tabStopId = looks.some((look) => look.id === selectedId) ? selectedId : looks[0]?.id
|
||||
@@ -90,7 +91,7 @@ export function ThemeLookCarousel({
|
||||
variant="ghost"
|
||||
size="icon-sm"
|
||||
disabled={edges.start}
|
||||
aria-label={translate('components.onboarding.theme.looks.previous', 'Previous looks')}
|
||||
aria-label={t('components.onboarding.theme.looks.previous', 'Previous looks')}
|
||||
onClick={() => page(-1)}
|
||||
>
|
||||
<ChevronLeft />
|
||||
@@ -99,7 +100,7 @@ export function ThemeLookCarousel({
|
||||
variant="ghost"
|
||||
size="icon-sm"
|
||||
disabled={edges.end}
|
||||
aria-label={translate('components.onboarding.theme.looks.next', 'More looks')}
|
||||
aria-label={t('components.onboarding.theme.looks.next', 'More looks')}
|
||||
onClick={() => page(1)}
|
||||
>
|
||||
<ChevronRight />
|
||||
@@ -162,10 +163,10 @@ export function ThemeLookCarousel({
|
||||
>
|
||||
<Puzzle className="size-4" />
|
||||
<span className="text-xs font-medium">
|
||||
{translate('components.onboarding.theme.looks.moreThemes', 'More themes')}
|
||||
{t('components.onboarding.theme.looks.moreThemes', 'More themes')}
|
||||
</span>
|
||||
<span className="flex items-center gap-1 text-2xs">
|
||||
{translate('components.onboarding.theme.looks.browsePlugins', 'Browse plugins')}
|
||||
{t('components.onboarding.theme.looks.browsePlugins', 'Browse plugins')}
|
||||
<ExternalLink className="size-2.5" />
|
||||
</span>
|
||||
</button>
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
import type React from 'react'
|
||||
import { useMemo } from 'react'
|
||||
import { translate } from '@/i18n/i18n'
|
||||
import { useTranslate } from '@/i18n/use-translate'
|
||||
import { useUiLanguageRevision } from '@/i18n/use-ui-language-revision'
|
||||
import { usePluginTerminalThemes, usePluginThemes } from '@/store/plugins/appearance/plugin-themes'
|
||||
import type { GlobalSettings } from '../../../../shared/global-settings-types'
|
||||
@@ -19,6 +19,7 @@ export function useThemeLooks(
|
||||
selected: Record<ThemeLookBase, string | null>
|
||||
pluginTerminalThemes: ReturnType<typeof usePluginTerminalThemes>
|
||||
} {
|
||||
const t = useTranslate()
|
||||
const pluginThemes = usePluginThemes()
|
||||
const pluginTerminalThemes = usePluginTerminalThemes()
|
||||
const languageRevision = useUiLanguageRevision()
|
||||
@@ -27,18 +28,18 @@ export function useThemeLooks(
|
||||
return { dark: [], light: [] }
|
||||
}
|
||||
const labels = {
|
||||
orca: translate('components.onboarding.theme.looks.sourceBuiltIn', 'Built in'),
|
||||
community: translate('components.onboarding.theme.looks.sourceCommunity', 'Terminal palette'),
|
||||
orca: t('components.onboarding.theme.looks.sourceBuiltIn', 'Built in'),
|
||||
community: t('components.onboarding.theme.looks.sourceCommunity', 'Terminal palette'),
|
||||
builtIn: (base: ThemeLookBase) =>
|
||||
base === 'dark'
|
||||
? translate('components.onboarding.theme.looks.orcaDark', 'Orca Dark')
|
||||
: translate('components.onboarding.theme.looks.orcaLight', 'Orca Light')
|
||||
? t('components.onboarding.theme.looks.orcaDark', 'Orca Dark')
|
||||
: t('components.onboarding.theme.looks.orcaLight', 'Orca Light')
|
||||
}
|
||||
const build = (base: ThemeLookBase): ThemeLook[] =>
|
||||
buildThemeLooks({ base, pluginThemes, pluginTerminalThemes, baseline, labels })
|
||||
buildThemeLooks({ base, pluginThemes, pluginTerminalThemes, baseline, labels }, t)
|
||||
return { dark: build('dark'), light: build('light') }
|
||||
// eslint-disable-next-line react-hooks/exhaustive-deps -- languageRevision re-reads translated labels
|
||||
}, [baseline, pluginThemes, pluginTerminalThemes, languageRevision])
|
||||
}, [baseline, pluginThemes, pluginTerminalThemes, languageRevision, t])
|
||||
const selected = {
|
||||
dark: settings ? selectedThemeLookId(settings, 'dark', looks.dark) : null,
|
||||
light: settings ? selectedThemeLookId(settings, 'light', looks.light) : null
|
||||
@@ -56,21 +57,22 @@ export function ThemeSlotControl({
|
||||
osBase: ThemeLookBase
|
||||
onChange: (base: ThemeLookBase) => void
|
||||
}): React.JSX.Element {
|
||||
const t = useTranslate()
|
||||
const label = (base: ThemeLookBase): React.ReactNode => (
|
||||
<span className="flex items-center gap-1.5 whitespace-nowrap">
|
||||
{base === 'dark'
|
||||
? translate('components.onboarding.theme.looks.darkLook', 'Dark look')
|
||||
: translate('components.onboarding.theme.looks.lightLook', 'Light look')}
|
||||
? t('components.onboarding.theme.looks.darkLook', 'Dark look')
|
||||
: t('components.onboarding.theme.looks.lightLook', 'Light look')}
|
||||
{base === osBase && (
|
||||
<span className="text-2xs font-normal text-muted-foreground">
|
||||
{translate('components.onboarding.theme.looks.activeNow', 'now')}
|
||||
{t('components.onboarding.theme.looks.activeNow', 'now')}
|
||||
</span>
|
||||
)}
|
||||
</span>
|
||||
)
|
||||
return (
|
||||
<SettingsSegmentedControl
|
||||
ariaLabel={translate('components.onboarding.theme.looks.slotLabel', 'Look to edit')}
|
||||
ariaLabel={t('components.onboarding.theme.looks.slotLabel', 'Look to edit')}
|
||||
value={value}
|
||||
onChange={onChange}
|
||||
equalWidth
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
import type React from 'react'
|
||||
import { Check, Monitor, Moon, Sun } from 'lucide-react'
|
||||
import { cn } from '@/lib/utils'
|
||||
import { translate } from '@/i18n/i18n'
|
||||
import { useTranslate } from '@/i18n/use-translate'
|
||||
import type { GlobalSettings } from '../../../../shared/global-settings-types'
|
||||
import { ChromePreview } from './theme-chrome-preview'
|
||||
|
||||
@@ -13,6 +13,7 @@ export function ThemeModeTiles({
|
||||
value: GlobalSettings['theme']
|
||||
onChange: (theme: GlobalSettings['theme']) => void
|
||||
}): React.JSX.Element {
|
||||
const t = useTranslate()
|
||||
const themes: {
|
||||
id: GlobalSettings['theme']
|
||||
label: string
|
||||
@@ -21,20 +22,20 @@ export function ThemeModeTiles({
|
||||
}[] = [
|
||||
{
|
||||
id: 'system',
|
||||
label: translate('auto.components.onboarding.ThemeStep.827ea7b4a2', 'System'),
|
||||
hint: translate('components.onboarding.theme.hints.system', 'Match OS'),
|
||||
label: t('auto.components.onboarding.ThemeStep.827ea7b4a2', 'System'),
|
||||
hint: t('components.onboarding.theme.hints.system', 'Match OS'),
|
||||
icon: Monitor
|
||||
},
|
||||
{
|
||||
id: 'dark',
|
||||
label: translate('auto.components.onboarding.ThemeStep.fa7b673ea9', 'Dark'),
|
||||
hint: translate('components.onboarding.theme.hints.dark', 'Easy on the eyes'),
|
||||
label: t('auto.components.onboarding.ThemeStep.fa7b673ea9', 'Dark'),
|
||||
hint: t('components.onboarding.theme.hints.dark', 'Easy on the eyes'),
|
||||
icon: Moon
|
||||
},
|
||||
{
|
||||
id: 'light',
|
||||
label: translate('auto.components.onboarding.ThemeStep.ad192706e6', 'Light'),
|
||||
hint: translate('components.onboarding.theme.hints.light', 'Bright & crisp'),
|
||||
label: t('auto.components.onboarding.ThemeStep.ad192706e6', 'Light'),
|
||||
hint: t('components.onboarding.theme.hints.light', 'Bright & crisp'),
|
||||
icon: Sun
|
||||
}
|
||||
]
|
||||
|
||||
@@ -8,6 +8,7 @@ import { GhosttyDiscoveryRow } from './GhosttyDiscoveryRow'
|
||||
import type { GlobalSettings } from '../../../../shared/global-settings-types'
|
||||
import type { DiscoveryStatusEmitted } from '../../../../shared/onboarding-state-types'
|
||||
import { i18n, translate } from '@/i18n/i18n'
|
||||
import { useTranslate } from '@/i18n/use-translate'
|
||||
import { ThemeSlotControl, useThemeLooks } from './ThemeLookGallery'
|
||||
import { ThemeLookCarousel, openThemePluginDirectory } from './ThemeLookCarousel'
|
||||
import { ThemeModeTiles } from './ThemeModeTiles'
|
||||
@@ -77,6 +78,7 @@ export function ThemeStep({
|
||||
lookBaseline,
|
||||
onLookBaselineChange
|
||||
}: ThemeStepProps) {
|
||||
const t = useTranslate()
|
||||
const [importing, setImporting] = useState(false)
|
||||
const [discovery, setDiscovery] = useState<DiscoveryState>({ status: 'idle' })
|
||||
const mountedRef = useMountedRef()
|
||||
@@ -138,7 +140,7 @@ export function ThemeStep({
|
||||
if (!resolved.found || Object.keys(resolved.diff).length === 0) {
|
||||
if (mountedRef.current) {
|
||||
toast.info(
|
||||
translate(
|
||||
t(
|
||||
'auto.components.onboarding.ThemeStep.16a9f0446a',
|
||||
'No Ghostty settings found to import'
|
||||
)
|
||||
@@ -172,10 +174,7 @@ export function ThemeStep({
|
||||
} catch (err) {
|
||||
if (mountedRef.current) {
|
||||
toast.error(
|
||||
translate(
|
||||
'auto.components.onboarding.ThemeStep.699ddf83c2',
|
||||
'Failed to import Ghostty settings'
|
||||
),
|
||||
t('auto.components.onboarding.ThemeStep.699ddf83c2', 'Failed to import Ghostty settings'),
|
||||
{
|
||||
description: err instanceof Error ? err.message : String(err)
|
||||
}
|
||||
@@ -208,8 +207,8 @@ export function ThemeStep({
|
||||
<div className="space-y-5">
|
||||
<section className="space-y-2.5">
|
||||
<ThemeSectionHeading
|
||||
title={translate('components.onboarding.theme.looks.colorMode', 'Color mode')}
|
||||
caption={translate(
|
||||
title={t('components.onboarding.theme.looks.colorMode', 'Color mode')}
|
||||
caption={t(
|
||||
'components.onboarding.theme.looks.colorModeCaption',
|
||||
'Light, dark, or follow your OS.'
|
||||
)}
|
||||
@@ -229,25 +228,25 @@ export function ThemeStep({
|
||||
onPick={pickLook}
|
||||
ariaLabel={
|
||||
base === 'dark'
|
||||
? translate('components.onboarding.theme.looks.darkLooks', 'Dark looks')
|
||||
: translate('components.onboarding.theme.looks.lightLooks', 'Light looks')
|
||||
? t('components.onboarding.theme.looks.darkLooks', 'Dark looks')
|
||||
: t('components.onboarding.theme.looks.lightLooks', 'Light looks')
|
||||
}
|
||||
header={
|
||||
<div className="flex flex-wrap items-center gap-x-3 gap-y-1.5">
|
||||
<ThemeSectionHeading
|
||||
title={translate('components.onboarding.theme.looks.themes', 'Theme')}
|
||||
title={t('components.onboarding.theme.looks.themes', 'Theme')}
|
||||
caption={
|
||||
theme === 'system' && base !== osBase
|
||||
? base === 'dark'
|
||||
? translate(
|
||||
? t(
|
||||
'components.onboarding.theme.looks.showsWhenDark',
|
||||
'Shows when your system is dark'
|
||||
)
|
||||
: translate(
|
||||
: t(
|
||||
'components.onboarding.theme.looks.showsWhenLight',
|
||||
'Shows when your system is light'
|
||||
)
|
||||
: translate(
|
||||
: t(
|
||||
'components.onboarding.theme.looks.themesCaption',
|
||||
'Optional. Go further with app themes and terminal palettes.'
|
||||
)
|
||||
@@ -273,7 +272,7 @@ export function ThemeStep({
|
||||
<div className="flex items-center gap-2">
|
||||
<Puzzle className="size-3.5" />
|
||||
<span>
|
||||
{translate(
|
||||
{t(
|
||||
'components.onboarding.theme.looks.pluginsHint',
|
||||
'More themes are available as plugins.'
|
||||
)}
|
||||
@@ -283,10 +282,7 @@ export function ThemeStep({
|
||||
className="inline-flex items-center gap-1 font-medium text-foreground underline-offset-4 hover:underline"
|
||||
onClick={openThemePluginDirectory}
|
||||
>
|
||||
{translate(
|
||||
'components.onboarding.theme.looks.browseThemePlugins',
|
||||
'Browse theme plugins'
|
||||
)}
|
||||
{t('components.onboarding.theme.looks.browseThemePlugins', 'Browse theme plugins')}
|
||||
<ExternalLink className="size-3" />
|
||||
</button>
|
||||
</div>
|
||||
@@ -295,7 +291,7 @@ export function ThemeStep({
|
||||
<span>
|
||||
<Trans
|
||||
i18n={i18n}
|
||||
defaults={translate(
|
||||
defaults={t(
|
||||
'auto.components.onboarding.ThemeStep.moreTerminalOptions',
|
||||
'More terminal options, including font, cursor, and palette, in <settings>Settings → Terminal</settings>'
|
||||
)}
|
||||
|
||||
@@ -14,7 +14,7 @@ import {
|
||||
SelectTrigger,
|
||||
SelectValue
|
||||
} from '@/components/ui/select'
|
||||
import { translate } from '@/i18n/i18n'
|
||||
import { useTranslate } from '@/i18n/use-translate'
|
||||
|
||||
type WindowsTerminalStepProps = {
|
||||
settings: GlobalSettings | null
|
||||
@@ -52,6 +52,7 @@ export function WindowsTerminalStep({
|
||||
settings,
|
||||
updateSettings
|
||||
}: WindowsTerminalStepProps): React.JSX.Element {
|
||||
const t = useTranslate()
|
||||
const capabilities = useWindowsTerminalCapabilities(Boolean(settings), true)
|
||||
const [selectPortalRoot, setSelectPortalRoot] = useState<HTMLElement | null>(null)
|
||||
const windowsShell = normalizeWindowsShell(settings?.terminalWindowsShell)
|
||||
@@ -72,24 +73,21 @@ export function WindowsTerminalStep({
|
||||
const shellOptions: ShellOption[] = [
|
||||
{
|
||||
value: 'powershell.exe',
|
||||
label: translate('auto.components.onboarding.WindowsTerminalStep.powerShell', 'PowerShell'),
|
||||
label: t('auto.components.onboarding.WindowsTerminalStep.powerShell', 'PowerShell'),
|
||||
description: capabilities.pwshAvailable
|
||||
? translate(
|
||||
? t(
|
||||
'auto.components.onboarding.WindowsTerminalStep.powerShellPwsh',
|
||||
'Uses PowerShell 7+ when available, with Windows PowerShell as fallback.'
|
||||
)
|
||||
: translate(
|
||||
: t(
|
||||
'auto.components.onboarding.WindowsTerminalStep.powerShellInbox',
|
||||
'Uses the Windows PowerShell available on every supported Windows install.'
|
||||
)
|
||||
},
|
||||
{
|
||||
value: 'cmd.exe',
|
||||
label: translate(
|
||||
'auto.components.onboarding.WindowsTerminalStep.commandPrompt',
|
||||
'Command Prompt'
|
||||
),
|
||||
description: translate(
|
||||
label: t('auto.components.onboarding.WindowsTerminalStep.commandPrompt', 'Command Prompt'),
|
||||
description: t(
|
||||
'auto.components.onboarding.WindowsTerminalStep.commandPromptDescription',
|
||||
'Opens new terminal panes with classic cmd.exe behavior.'
|
||||
)
|
||||
@@ -98,13 +96,13 @@ export function WindowsTerminalStep({
|
||||
? [
|
||||
{
|
||||
value: WINDOWS_GIT_BASH_SHELL,
|
||||
label: translate('auto.components.onboarding.WindowsTerminalStep.gitBash', 'Git Bash'),
|
||||
label: t('auto.components.onboarding.WindowsTerminalStep.gitBash', 'Git Bash'),
|
||||
description: capabilities.gitBashAvailable
|
||||
? translate(
|
||||
? t(
|
||||
'auto.components.onboarding.WindowsTerminalStep.gitBashDescription',
|
||||
'Uses Git for Windows bash.exe for Unix-style shell workflows.'
|
||||
)
|
||||
: translate(
|
||||
: t(
|
||||
'auto.components.onboarding.WindowsTerminalStep.gitBashUnavailable',
|
||||
'Selected, but Git Bash was not detected on this machine.'
|
||||
),
|
||||
@@ -116,13 +114,13 @@ export function WindowsTerminalStep({
|
||||
? [
|
||||
{
|
||||
value: 'wsl.exe',
|
||||
label: translate('auto.components.onboarding.WindowsTerminalStep.wsl', 'WSL'),
|
||||
label: t('auto.components.onboarding.WindowsTerminalStep.wsl', 'WSL'),
|
||||
description: capabilities.wslAvailable
|
||||
? translate(
|
||||
? t(
|
||||
'auto.components.onboarding.WindowsTerminalStep.wslDescription',
|
||||
'Starts new terminal panes inside your Windows Subsystem for Linux default.'
|
||||
)
|
||||
: translate(
|
||||
: t(
|
||||
'auto.components.onboarding.WindowsTerminalStep.wslUnavailable',
|
||||
'Selected, but WSL was not detected on this machine.'
|
||||
),
|
||||
@@ -135,22 +133,22 @@ export function WindowsTerminalStep({
|
||||
const rightClickOptions: RightClickOption[] = [
|
||||
{
|
||||
value: 'paste',
|
||||
label: translate(
|
||||
label: t(
|
||||
'auto.components.onboarding.WindowsTerminalStep.rightClickPaste',
|
||||
'Paste on right-click'
|
||||
),
|
||||
description: translate(
|
||||
description: t(
|
||||
'auto.components.onboarding.WindowsTerminalStep.rightClickPasteDescription',
|
||||
'Right-click pastes the clipboard. Ctrl+right-click opens the context menu.'
|
||||
)
|
||||
},
|
||||
{
|
||||
value: 'menu',
|
||||
label: translate(
|
||||
label: t(
|
||||
'auto.components.onboarding.WindowsTerminalStep.rightClickMenu',
|
||||
'Open context menu'
|
||||
),
|
||||
description: translate(
|
||||
description: t(
|
||||
'auto.components.onboarding.WindowsTerminalStep.rightClickMenuDescription',
|
||||
'Right-click opens the terminal menu. Paste from the menu or keyboard.'
|
||||
)
|
||||
@@ -160,7 +158,7 @@ export function WindowsTerminalStep({
|
||||
if (!settings) {
|
||||
return (
|
||||
<div className="rounded-xl border border-border bg-muted/20 px-5 py-4 text-sm text-muted-foreground">
|
||||
{translate(
|
||||
{t(
|
||||
'auto.components.onboarding.WindowsTerminalStep.loading',
|
||||
'Loading terminal settings...'
|
||||
)}
|
||||
@@ -178,13 +176,10 @@ export function WindowsTerminalStep({
|
||||
<section className="space-y-3">
|
||||
<div className="space-y-1">
|
||||
<h2 className="text-sm font-semibold text-foreground">
|
||||
{translate(
|
||||
'auto.components.onboarding.WindowsTerminalStep.defaultShell',
|
||||
'Default Shell'
|
||||
)}
|
||||
{t('auto.components.onboarding.WindowsTerminalStep.defaultShell', 'Default Shell')}
|
||||
</h2>
|
||||
<p className="text-[13px] leading-relaxed text-muted-foreground">
|
||||
{translate(
|
||||
{t(
|
||||
'auto.components.onboarding.WindowsTerminalStep.defaultShellDescription',
|
||||
'Choose the shell Orca opens for new Windows terminal panes.'
|
||||
)}
|
||||
@@ -210,13 +205,13 @@ export function WindowsTerminalStep({
|
||||
<div className="flex flex-col gap-3 sm:flex-row sm:items-center sm:justify-between">
|
||||
<div className="min-w-0 space-y-1">
|
||||
<div className="text-sm font-medium text-foreground">
|
||||
{translate(
|
||||
{t(
|
||||
'auto.components.onboarding.WindowsTerminalStep.wslDistribution',
|
||||
'WSL Distribution'
|
||||
)}
|
||||
</div>
|
||||
<p className="text-[13px] leading-relaxed text-muted-foreground">
|
||||
{translate(
|
||||
{t(
|
||||
'auto.components.onboarding.WindowsTerminalStep.wslDistributionDescription',
|
||||
'Use the Windows default distribution or choose a specific installed distro.'
|
||||
)}
|
||||
@@ -233,7 +228,7 @@ export function WindowsTerminalStep({
|
||||
>
|
||||
<SelectTrigger
|
||||
size="sm"
|
||||
aria-label={translate(
|
||||
aria-label={t(
|
||||
'auto.components.onboarding.WindowsTerminalStep.wslDistribution',
|
||||
'WSL Distribution'
|
||||
)}
|
||||
@@ -242,11 +237,11 @@ export function WindowsTerminalStep({
|
||||
<SelectValue
|
||||
placeholder={
|
||||
capabilities.isLoading
|
||||
? translate(
|
||||
? t(
|
||||
'auto.components.onboarding.WindowsTerminalStep.loadingDistros',
|
||||
'Loading distributions'
|
||||
)
|
||||
: translate(
|
||||
: t(
|
||||
'auto.components.onboarding.WindowsTerminalStep.windowsDefault',
|
||||
'Windows default'
|
||||
)
|
||||
@@ -259,7 +254,7 @@ export function WindowsTerminalStep({
|
||||
className="z-[120] w-[--radix-select-trigger-width]"
|
||||
>
|
||||
<SelectItem value={DEFAULT_WSL_DISTRO_VALUE}>
|
||||
{translate(
|
||||
{t(
|
||||
'auto.components.onboarding.WindowsTerminalStep.windowsDefault',
|
||||
'Windows default'
|
||||
)}
|
||||
@@ -279,13 +274,13 @@ export function WindowsTerminalStep({
|
||||
<section className="space-y-3">
|
||||
<div className="space-y-1">
|
||||
<h2 className="text-sm font-semibold text-foreground">
|
||||
{translate(
|
||||
{t(
|
||||
'auto.components.onboarding.WindowsTerminalStep.rightClickBehavior',
|
||||
'Right-click behavior'
|
||||
)}
|
||||
</h2>
|
||||
<p className="text-[13px] leading-relaxed text-muted-foreground">
|
||||
{translate(
|
||||
{t(
|
||||
'auto.components.onboarding.WindowsTerminalStep.rightClickBehaviorDescription',
|
||||
'Pick the terminal mouse behavior that matches your Windows muscle memory.'
|
||||
)}
|
||||
@@ -299,7 +294,7 @@ export function WindowsTerminalStep({
|
||||
void updateSettings({ terminalRightClickToPaste: value === 'paste' })
|
||||
}
|
||||
options={rightClickOptions}
|
||||
ariaLabel={translate(
|
||||
ariaLabel={t(
|
||||
'auto.components.onboarding.WindowsTerminalStep.rightClickBehavior',
|
||||
'Right-click behavior'
|
||||
)}
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
import { useId, useState } from 'react'
|
||||
import { Button } from '@/components/ui/button'
|
||||
import { Switch } from '@/components/ui/switch'
|
||||
import { translate } from '@/i18n/i18n'
|
||||
import { useTranslate } from '@/i18n/use-translate'
|
||||
import { getAgentLabel } from '@/lib/agent-catalog'
|
||||
import { useAppStore } from '@/store'
|
||||
import {
|
||||
@@ -17,6 +17,7 @@ export function YoloPermissionsControl({
|
||||
yoloPermissions: boolean
|
||||
onYoloPermissionsChange?: (enabled: boolean) => void
|
||||
}): React.JSX.Element {
|
||||
const t = useTranslate()
|
||||
const switchId = useId()
|
||||
const overrides = useAppStore((s) => s.settings?.agentPermissionOverrides)
|
||||
const [saving, setSaving] = useState(false)
|
||||
@@ -45,10 +46,10 @@ export function YoloPermissionsControl({
|
||||
<div className="flex items-center justify-between gap-6">
|
||||
<label htmlFor={switchId} className="min-w-0 cursor-pointer space-y-0.5">
|
||||
<span className="block text-sm font-medium text-foreground">
|
||||
{translate('auto.components.onboarding.AgentStep.yoloModeLabel', 'Yolo mode')}
|
||||
{t('auto.components.onboarding.AgentStep.yoloModeLabel', 'Yolo mode')}
|
||||
</span>
|
||||
<span className="block text-xs text-muted-foreground">
|
||||
{translate(
|
||||
{t(
|
||||
'auto.components.onboarding.AgentStep.yoloPermissionsDescription',
|
||||
'Agents run commands and edit files without asking, and some also bypass their sandbox. Use only in projects you trust.'
|
||||
)}
|
||||
@@ -67,7 +68,7 @@ export function YoloPermissionsControl({
|
||||
className="scrollbar-sleek max-h-48 space-y-3 overflow-y-auto rounded-lg border border-status-warning-border bg-status-warning-background px-4 py-3 text-xs text-foreground"
|
||||
>
|
||||
<p>
|
||||
{translate(
|
||||
{t(
|
||||
'components.onboarding.yoloPermissions.overrideDescription',
|
||||
'These agents can still run commands and edit files without asking. Their individual settings override Yolo mode.'
|
||||
)}
|
||||
@@ -75,7 +76,7 @@ export function YoloPermissionsControl({
|
||||
{fullAccessAgents.map((agent) => (
|
||||
<div key={agent} className="flex flex-wrap items-center justify-between gap-2">
|
||||
<span className="font-medium">
|
||||
{translate(
|
||||
{t(
|
||||
'components.onboarding.yoloPermissions.fullAccessOverride',
|
||||
'{{agent}} still has full access',
|
||||
{ agent: getAgentLabel(agent) }
|
||||
@@ -86,17 +87,14 @@ export function YoloPermissionsControl({
|
||||
variant="outline"
|
||||
size="xs"
|
||||
disabled={saving}
|
||||
aria-label={translate(
|
||||
aria-label={t(
|
||||
'components.onboarding.yoloPermissions.useAgentOwnSettings',
|
||||
'Use {{agent}}’s own settings',
|
||||
{ agent: getAgentLabel(agent) }
|
||||
)}
|
||||
onClick={() => void handleUseOwnSettings(agent)}
|
||||
>
|
||||
{translate(
|
||||
'components.onboarding.yoloPermissions.useOwnSettings',
|
||||
'Use own settings'
|
||||
)}
|
||||
{t('components.onboarding.yoloPermissions.useOwnSettings', 'Use own settings')}
|
||||
</Button>
|
||||
</div>
|
||||
))}
|
||||
@@ -104,14 +102,14 @@ export function YoloPermissionsControl({
|
||||
)}
|
||||
{error && (
|
||||
<p role="alert" className="text-xs text-destructive">
|
||||
{translate(
|
||||
{t(
|
||||
'components.onboarding.yoloPermissions.saveError',
|
||||
'Could not change permissions. Try again.'
|
||||
)}
|
||||
</p>
|
||||
)}
|
||||
<p className="text-xs text-muted-foreground">
|
||||
{translate(
|
||||
{t(
|
||||
'components.onboarding.yoloPermissions.ownSettingsDescription',
|
||||
'Off uses each agent’s own settings. Individual agent permissions can override this switch.'
|
||||
)}
|
||||
|
||||
@@ -11,6 +11,7 @@ import {
|
||||
} from '../plugins/presentation/plugin-name-disambiguation'
|
||||
import { resolveTerminalPalette } from '../../../../shared/terminal-themes/terminal-theme-selection'
|
||||
import { TERMINAL_THEME_CATALOG } from '../../../../shared/terminal-themes/terminal-theme-catalog'
|
||||
import type { Translate } from '../../i18n/use-translate'
|
||||
|
||||
export type ThemeLookBase = 'light' | 'dark'
|
||||
|
||||
@@ -213,13 +214,16 @@ function terminalLook(name: string, base: ThemeLookBase, source: string): ThemeL
|
||||
|
||||
/** Every look the theme step offers for one mode: Orca's own first, then installed plugin
|
||||
* themes, then popular terminal palettes painted through Match Terminal. */
|
||||
export function buildThemeLooks(options: {
|
||||
base: ThemeLookBase
|
||||
pluginThemes: readonly PluginThemeRegistration[]
|
||||
pluginTerminalThemes: readonly PluginTerminalThemeRegistration[]
|
||||
baseline: GlobalSettings
|
||||
labels: { orca: string; builtIn: (base: ThemeLookBase) => string; community: string }
|
||||
}): ThemeLook[] {
|
||||
export function buildThemeLooks(
|
||||
options: {
|
||||
base: ThemeLookBase
|
||||
pluginThemes: readonly PluginThemeRegistration[]
|
||||
pluginTerminalThemes: readonly PluginTerminalThemeRegistration[]
|
||||
baseline: GlobalSettings
|
||||
labels: { orca: string; builtIn: (base: ThemeLookBase) => string; community: string }
|
||||
},
|
||||
t?: Translate
|
||||
): ThemeLook[] {
|
||||
const { base, pluginThemes, pluginTerminalThemes, baseline, labels } = options
|
||||
const builtIn: ThemeLook = {
|
||||
kind: 'built-in',
|
||||
@@ -230,8 +234,8 @@ export function buildThemeLooks(options: {
|
||||
palette: { ...BUILT_IN_CHROME[base], terminal: currentTerminal(baseline, base) }
|
||||
}
|
||||
const modeThemes = pluginThemes.filter((theme) => theme.base === base)
|
||||
const themeLabels = disambiguatedPluginContentLabels(modeThemes)
|
||||
const pluginNames = disambiguatedContentPluginNames(modeThemes)
|
||||
const themeLabels = disambiguatedPluginContentLabels(modeThemes, undefined, t)
|
||||
const pluginNames = disambiguatedContentPluginNames(modeThemes, t)
|
||||
const plugins = modeThemes.map((theme) =>
|
||||
pluginLook(theme, pluginTerminalThemes, baseline, {
|
||||
label: themeLabels.get(theme.id) ?? theme.label,
|
||||
|
||||
@@ -25,7 +25,7 @@ import { getGitHubRepoRoutingTarget } from '@/lib/github-source-runtime-context'
|
||||
import { presentGitHubPRMergeState } from '@/components/github-pr-merge-state'
|
||||
import { resolveGitHubPRMergeMethods } from '../../../../../shared/github/pull-request-merge-methods'
|
||||
import { resolvePullRequestRepo } from '@/components/github/github-work-item-identity'
|
||||
import { translate } from '@/i18n/i18n'
|
||||
import { useTranslate } from '@/i18n/use-translate'
|
||||
import { getPullRequestMergeMethodLabel } from '@/components/github/pull-request-confirmation-copy'
|
||||
import type { GitHubWorkItem } from '../../../../../shared/github/work-item-types'
|
||||
import type { TaskSourceContext } from '../../../../../shared/task-source-context'
|
||||
@@ -53,13 +53,14 @@ export function PRActionsPanel({
|
||||
onStateChange: (state: GitHubWorkItem['state']) => void
|
||||
onMutated: () => void
|
||||
}): React.JSX.Element {
|
||||
const t = useTranslate()
|
||||
const [statePending, setStatePending] = useState(false)
|
||||
const [mergePending, setMergePending] = useState(false)
|
||||
const patchWorkItem = useAppStore((s) => s.patchWorkItem)
|
||||
const patchProjectRowContent = useAppStore((s) => s.patchProjectRowContent)
|
||||
const confirm = useConfirmationDialog()
|
||||
const actionItem = { ...item, state: localState }
|
||||
const mergePresentation = presentGitHubPRMergeState(actionItem)
|
||||
const mergePresentation = presentGitHubPRMergeState(actionItem, t)
|
||||
const mergeMethods = resolveGitHubPRMergeMethods(actionItem.mergeMethodSettings)
|
||||
const mergeTarget = useAppStore(
|
||||
useShallow((s) => getGitHubRepoRoutingTarget(s, item.repoId ?? repoId ?? null, sourceContext))
|
||||
@@ -98,7 +99,7 @@ export function PRActionsPanel({
|
||||
<div className="flex items-center gap-2">
|
||||
<GitPullRequest className="size-3.5 text-muted-foreground" />
|
||||
<span className="text-[13px] font-medium text-foreground">
|
||||
{translate('auto.components.PullRequestPage.1939d0f663', 'Pull request')}
|
||||
{t('auto.components.PullRequestPage.1939d0f663', 'Pull request')}
|
||||
</span>
|
||||
</div>
|
||||
<WorkItemStateBadge item={actionItem} />
|
||||
@@ -124,7 +125,7 @@ export function PRActionsPanel({
|
||||
)}
|
||||
{mergePresentation.autoMergeAction?.label ??
|
||||
(mergePresentation.directMergeAvailable
|
||||
? getPullRequestMergeMethodLabel(mergeMethods.defaultMethod)
|
||||
? getPullRequestMergeMethodLabel(mergeMethods.defaultMethod, t)
|
||||
: mergePresentation.label)}
|
||||
<ChevronDown className="size-3 opacity-60" />
|
||||
</Button>
|
||||
@@ -132,7 +133,7 @@ export function PRActionsPanel({
|
||||
</TooltipTrigger>
|
||||
<TooltipContent side="bottom" sideOffset={6}>
|
||||
{!canMergeWithRepoContext
|
||||
? translate(
|
||||
? t(
|
||||
'auto.components.PullRequestPage.eca289e593',
|
||||
'Merge requires a registered local repo'
|
||||
)
|
||||
@@ -186,12 +187,12 @@ export function PRActionsPanel({
|
||||
}
|
||||
>
|
||||
<GitMerge className="size-4" />
|
||||
{getPullRequestMergeMethodLabel(method)}
|
||||
{getPullRequestMergeMethodLabel(method, t)}
|
||||
</DropdownMenuItem>
|
||||
))}
|
||||
<DropdownMenuItem onSelect={() => openAppHttpLink(item.url)}>
|
||||
<ExternalLink className="size-4" />
|
||||
{translate('auto.components.PullRequestPage.7df8d5fc60', 'Open GitHub merge box')}
|
||||
{t('auto.components.PullRequestPage.7df8d5fc60', 'Open GitHub merge box')}
|
||||
</DropdownMenuItem>
|
||||
</DropdownMenuContent>
|
||||
</DropdownMenu>
|
||||
@@ -233,8 +234,8 @@ export function PRActionsPanel({
|
||||
<CircleDot className="size-3.5" />
|
||||
)}
|
||||
{nextState === 'closed'
|
||||
? translate('auto.components.PullRequestPage.96d013ed28', 'Close pull request')
|
||||
: translate('auto.components.PullRequestPage.9d5425918e', 'Reopen PR')}
|
||||
? t('auto.components.PullRequestPage.96d013ed28', 'Close pull request')
|
||||
: t('auto.components.PullRequestPage.9d5425918e', 'Reopen PR')}
|
||||
</Button>
|
||||
</div>
|
||||
</aside>
|
||||
|
||||
@@ -10,7 +10,7 @@ import {
|
||||
getCheckStatusLabel
|
||||
} from '@/components/github/pr-check-presentation'
|
||||
import type { CheckDetailsLoadState } from '@/components/github-checks-tab-state'
|
||||
import { translate } from '@/i18n/i18n'
|
||||
import { useIntlLocale, useTranslate } from '@/i18n/use-translate'
|
||||
import type { PRCheckDetail } from '../../../../../shared/github/check-types'
|
||||
import { assignUniqueListKeys } from './details-list-keys'
|
||||
import { openAppHttpLink } from '@/lib/browser/navigation/app-http-link-open'
|
||||
@@ -24,10 +24,12 @@ export function CheckDetailsPanel({
|
||||
state: CheckDetailsLoadState | undefined
|
||||
onRetry: (check: PRCheckDetail, key: string) => void
|
||||
}): React.JSX.Element {
|
||||
const t = useTranslate()
|
||||
const intlLocale = useIntlLocale()
|
||||
const details = state?.details
|
||||
const openUrl = details?.detailsUrl ?? details?.url ?? check.url
|
||||
const startedAt = formatCheckTimestamp(details?.startedAt)
|
||||
const completedAt = formatCheckTimestamp(details?.completedAt)
|
||||
const startedAt = formatCheckTimestamp(details?.startedAt, intlLocale)
|
||||
const completedAt = formatCheckTimestamp(details?.completedAt, intlLocale)
|
||||
const detailsStatusCheck: PRCheckDetail = {
|
||||
...check,
|
||||
status: (details?.status as PRCheckDetail['status'] | undefined) ?? check.status,
|
||||
@@ -55,28 +57,28 @@ export function CheckDetailsPanel({
|
||||
{state?.loading && !state.error ? (
|
||||
<div className="flex items-center gap-2 py-2 text-[12px] text-muted-foreground">
|
||||
<LoaderCircle className="size-3.5 animate-spin" />
|
||||
{translate('auto.components.PullRequestPage.d8e82b7f15', 'Loading check details…')}
|
||||
{t('auto.components.PullRequestPage.d8e82b7f15', 'Loading check details…')}
|
||||
</div>
|
||||
) : (
|
||||
<div className="flex min-w-0 flex-col gap-2">
|
||||
<div className="flex min-w-0 flex-wrap items-center gap-x-3 gap-y-1 text-[11px] text-muted-foreground">
|
||||
<span>
|
||||
{translate('auto.components.PullRequestPage.662bc2998d', 'Status:')}{' '}
|
||||
{details ? getCheckStatusLabel(detailsStatusCheck) : getCheckStatusLabel(check)}
|
||||
{t('auto.components.PullRequestPage.662bc2998d', 'Status:')}{' '}
|
||||
{details ? getCheckStatusLabel(detailsStatusCheck, t) : getCheckStatusLabel(check, t)}
|
||||
</span>
|
||||
{startedAt && (
|
||||
<span>
|
||||
{translate('auto.components.PullRequestPage.76551b1161', 'Started')} {startedAt}
|
||||
{t('auto.components.PullRequestPage.76551b1161', 'Started')} {startedAt}
|
||||
</span>
|
||||
)}
|
||||
{completedAt && (
|
||||
<span>
|
||||
{translate('auto.components.PullRequestPage.000f90afcf', 'Completed')} {completedAt}
|
||||
{t('auto.components.PullRequestPage.000f90afcf', 'Completed')} {completedAt}
|
||||
</span>
|
||||
)}
|
||||
{check.checkRunId && (
|
||||
<span className="font-mono">
|
||||
{translate('auto.components.PullRequestPage.f01bf79a79', 'check #')}
|
||||
{t('auto.components.PullRequestPage.f01bf79a79', 'check #')}
|
||||
{check.checkRunId}
|
||||
</span>
|
||||
)}
|
||||
@@ -96,8 +98,8 @@ export function CheckDetailsPanel({
|
||||
>
|
||||
<RefreshCw className={cn('size-3', state.loading && 'animate-spin')} />
|
||||
{state.loading
|
||||
? translate('githubChecks.retrying', 'Retrying…')
|
||||
: translate('auto.components.PullRequestPage.5df7c41d2a', 'Retry')}
|
||||
? t('githubChecks.retrying', 'Retrying…')
|
||||
: t('auto.components.PullRequestPage.5df7c41d2a', 'Retry')}
|
||||
</Button>
|
||||
</div>
|
||||
)}
|
||||
@@ -127,7 +129,7 @@ export function CheckDetailsPanel({
|
||||
{hasAnnotations && (
|
||||
<div className="min-w-0 rounded-md border border-border/40 bg-background/70">
|
||||
<div className="border-b border-border/40 px-2.5 py-1.5 text-[11px] font-medium text-foreground">
|
||||
{translate('auto.components.PullRequestPage.8432d17901', 'Annotations')}
|
||||
{t('auto.components.PullRequestPage.8432d17901', 'Annotations')}
|
||||
</div>
|
||||
<div className="flex flex-col">
|
||||
{annotationRows.map(({ item: annotation, key }, index) => (
|
||||
@@ -141,7 +143,7 @@ export function CheckDetailsPanel({
|
||||
<div className="flex min-w-0 items-center gap-2">
|
||||
<span className="min-w-0 truncate font-mono text-[11px] text-muted-foreground">
|
||||
{annotation.path ??
|
||||
translate('auto.components.PullRequestPage.35a0573f41', 'Annotation')}
|
||||
t('auto.components.PullRequestPage.35a0573f41', 'Annotation')}
|
||||
{annotation.startLine ? `:${annotation.startLine}` : ''}
|
||||
</span>
|
||||
{annotation.annotationLevel && (
|
||||
@@ -172,7 +174,7 @@ export function CheckDetailsPanel({
|
||||
{hasJobs && (
|
||||
<div className="min-w-0 rounded-md border border-border/40 bg-background/70">
|
||||
<div className="border-b border-border/40 px-2.5 py-1.5 text-[11px] font-medium text-foreground">
|
||||
{translate('auto.components.PullRequestPage.7720c9c3f5', 'Jobs')}
|
||||
{t('auto.components.PullRequestPage.7720c9c3f5', 'Jobs')}
|
||||
</div>
|
||||
<div className="flex flex-col">
|
||||
{jobRows.map(({ item: job, key }, index) => (
|
||||
@@ -187,7 +189,7 @@ export function CheckDetailsPanel({
|
||||
<span className="shrink-0 text-[11px] text-muted-foreground">
|
||||
{job.conclusion ??
|
||||
job.status ??
|
||||
translate('auto.components.PullRequestPage.77d9388fb0', 'unknown')}
|
||||
t('auto.components.PullRequestPage.77d9388fb0', 'unknown')}
|
||||
</span>
|
||||
</div>
|
||||
{job.steps.length > 0 && (
|
||||
@@ -212,11 +214,11 @@ export function CheckDetailsPanel({
|
||||
{!state?.error && !hasOutput && !hasAnnotations && !hasJobs && (
|
||||
<div className="text-[12px] text-muted-foreground">
|
||||
{getCheckConclusion(check) === 'action_required'
|
||||
? translate(
|
||||
? t(
|
||||
'auto.components.PullRequestPage.checkActionRequiredHint',
|
||||
'Needs a manual action on GitHub (e.g. approving the run) to unblock merging.'
|
||||
)
|
||||
: translate(
|
||||
: t(
|
||||
'auto.components.PullRequestPage.1550675e5f',
|
||||
'No inline output is available for this check.'
|
||||
)}
|
||||
@@ -232,7 +234,7 @@ export function CheckDetailsPanel({
|
||||
className="h-7 gap-1 px-2 text-[11px]"
|
||||
onClick={(event) => openAppHttpLink(openUrl, { event })}
|
||||
>
|
||||
{translate('auto.components.PullRequestPage.1b14d0a69c', 'Open in GitHub')}
|
||||
{t('auto.components.PullRequestPage.1b14d0a69c', 'Open in GitHub')}
|
||||
<ExternalLink className="size-3" />
|
||||
</Button>
|
||||
</div>
|
||||
|
||||
@@ -4,7 +4,7 @@ import type { useAppStore } from '@/store'
|
||||
import { SourceControlAgentActionDialog } from '@/components/right-sidebar/source-control/ai/SourceControlAgentActionDialog'
|
||||
import { readSourceControlLaunchRecipeAgentId } from '../../../../../shared/source-control/ai/source-control-launch-agent-selection'
|
||||
import type { SourceControlActionRecipe } from '../../../../../shared/source-control/ai/recipes/source-control-ai-actions'
|
||||
import { translate } from '@/i18n/i18n'
|
||||
import { useTranslate } from '@/i18n/use-translate'
|
||||
import type { GitHubWorkItem } from '../../../../../shared/github/work-item-types'
|
||||
import { saveFixChecksActionDefault, startFixChecksFromDialog } from './fix-launch'
|
||||
|
||||
@@ -30,6 +30,7 @@ export function ChecksFixDialog({
|
||||
updateSettings: ReturnType<typeof useAppStore.getState>['updateSettings']
|
||||
updateRepo: ReturnType<typeof useAppStore.getState>['updateRepo']
|
||||
}): React.JSX.Element {
|
||||
const t = useTranslate()
|
||||
return (
|
||||
<SourceControlAgentActionDialog
|
||||
open={fixChecksComposerPrompt !== null}
|
||||
@@ -39,8 +40,8 @@ export function ChecksFixDialog({
|
||||
}
|
||||
}}
|
||||
actionId="fixChecks"
|
||||
title={translate('auto.components.PullRequestPage.a053bdd082', 'Fix Broken Checks With AI')}
|
||||
description={translate(
|
||||
title={t('auto.components.PullRequestPage.a053bdd082', 'Fix Broken Checks With AI')}
|
||||
description={t(
|
||||
'auto.components.PullRequestPage.ddfd42f460',
|
||||
'Review the prompt before starting an agent.'
|
||||
)}
|
||||
@@ -58,7 +59,7 @@ export function ChecksFixDialog({
|
||||
}
|
||||
onLaunched={() => {
|
||||
toast.success(
|
||||
translate(
|
||||
t(
|
||||
'auto.components.PullRequestPage.85e62c5266',
|
||||
'Started an AI agent for the broken checks.'
|
||||
)
|
||||
|
||||
@@ -7,6 +7,7 @@ import { CHECK_COLOR } from '@/components/right-sidebar/checks-panel/presentatio
|
||||
import { getCheckCountChips, type getCheckCounts } from '@/components/pr-check-counts'
|
||||
import { getCheckDetailsKey } from '@/components/github/pr-check-presentation'
|
||||
import type { PRCheckDetail } from '../../../../../shared/github/check-types'
|
||||
import { useTranslate } from '@/i18n/use-translate'
|
||||
|
||||
export function ChecksTabLayouts({
|
||||
owner,
|
||||
@@ -39,6 +40,7 @@ export function ChecksTabLayouts({
|
||||
renderCheckRow: (check: PRCheckDetail) => React.JSX.Element
|
||||
fixChecksAgentDialog: React.JSX.Element
|
||||
}): React.JSX.Element {
|
||||
const t = useTranslate()
|
||||
if (loading && listLength === 0) {
|
||||
return (
|
||||
<>
|
||||
@@ -63,7 +65,7 @@ export function ChecksTabLayouts({
|
||||
)
|
||||
}
|
||||
if (variant === 'page') {
|
||||
const countChips = getCheckCountChips(counts)
|
||||
const countChips = getCheckCountChips(counts, t)
|
||||
return (
|
||||
<>
|
||||
<div className="flex flex-col gap-3 px-4 py-3">
|
||||
|
||||
@@ -10,6 +10,7 @@ import { getCheckDetailsKey, getCheckStatusLabel } from '@/components/github/pr-
|
||||
import type { CheckDetailsLoadState } from '@/components/github-checks-tab-state'
|
||||
import type { PRCheckDetail } from '../../../../../shared/github/check-types'
|
||||
import { CheckDetailsPanel } from './details'
|
||||
import { useTranslate } from '@/i18n/use-translate'
|
||||
|
||||
export function CheckRow({
|
||||
check,
|
||||
@@ -26,10 +27,11 @@ export function CheckRow({
|
||||
onToggle: (check: PRCheckDetail) => void
|
||||
onRetryDetails: (check: PRCheckDetail, key: string) => void
|
||||
}): React.JSX.Element {
|
||||
const t = useTranslate()
|
||||
const conclusion = getCheckConclusion(check)
|
||||
const Icon = CHECK_ICON[conclusion] ?? CircleDashed
|
||||
const color = CHECK_COLOR[conclusion] ?? 'text-muted-foreground'
|
||||
const statusLabel = getCheckStatusLabel(check)
|
||||
const statusLabel = getCheckStatusLabel(check, t)
|
||||
const key = getCheckDetailsKey(check)
|
||||
return (
|
||||
<div key={key} className="min-w-0">
|
||||
|
||||
@@ -43,6 +43,7 @@ import { ChecksCompactHeader, buildChecksToolbar } from './toolbar'
|
||||
import { CheckRow } from './row'
|
||||
import { ChecksFixDialog } from './fix-dialog'
|
||||
import { ChecksTabLayouts } from './layouts'
|
||||
import { useTranslate } from '@/i18n/use-translate'
|
||||
|
||||
export function ChecksTab({
|
||||
item,
|
||||
@@ -65,6 +66,7 @@ export function ChecksTab({
|
||||
variant?: 'compact' | 'page'
|
||||
onChecksUpdated: (checks: PRCheckDetail[]) => void
|
||||
}): React.JSX.Element {
|
||||
const t = useTranslate()
|
||||
const targetRepoId = repoId ?? item.repoId
|
||||
const settings = useAppStore((s) => s.settings)
|
||||
const updateSettingsOrThrow = useAppStore((s) => s.updateSettingsOrThrow)
|
||||
@@ -175,7 +177,8 @@ export function ChecksTab({
|
||||
const summaryLabel = getChecksSummaryLabel(
|
||||
list,
|
||||
checkOwner,
|
||||
resolvedChecksState.checkObservationOwner
|
||||
resolvedChecksState.checkObservationOwner,
|
||||
t
|
||||
)
|
||||
const { SummaryIcon, summaryColor } = getChecksTabSummaryPresentation(overallCounts)
|
||||
const canFixBrokenChecks = Boolean((repoId ?? item.repoId) && failedChecks.length > 0)
|
||||
@@ -288,33 +291,36 @@ export function ChecksTab({
|
||||
[detailsByCheckKey, requestCheckDetails]
|
||||
)
|
||||
|
||||
const { refreshAction, rerunAction, secondaryActions, actions } = buildChecksToolbar({
|
||||
variant,
|
||||
canUseChecksRepoContext,
|
||||
refreshing,
|
||||
rerunning,
|
||||
fixingChecks,
|
||||
canFixBrokenChecks,
|
||||
failedChecksCount: failedChecks.length,
|
||||
listLength: list.length,
|
||||
onRefresh: () => {
|
||||
void handleRefresh()
|
||||
const { refreshAction, rerunAction, secondaryActions, actions } = buildChecksToolbar(
|
||||
{
|
||||
variant,
|
||||
canUseChecksRepoContext,
|
||||
refreshing,
|
||||
rerunning,
|
||||
fixingChecks,
|
||||
canFixBrokenChecks,
|
||||
failedChecksCount: failedChecks.length,
|
||||
listLength: list.length,
|
||||
onRefresh: () => {
|
||||
void handleRefresh()
|
||||
},
|
||||
onFix: () => {
|
||||
void fixBrokenPullRequestChecks({
|
||||
targetRepoId,
|
||||
fixingChecks,
|
||||
failedChecks,
|
||||
item,
|
||||
list,
|
||||
setFixingChecks,
|
||||
setFixChecksComposerPrompt
|
||||
})
|
||||
},
|
||||
onRerun: (failedOnly) => {
|
||||
void handleRerun(failedOnly)
|
||||
}
|
||||
},
|
||||
onFix: () => {
|
||||
void fixBrokenPullRequestChecks({
|
||||
targetRepoId,
|
||||
fixingChecks,
|
||||
failedChecks,
|
||||
item,
|
||||
list,
|
||||
setFixingChecks,
|
||||
setFixChecksComposerPrompt
|
||||
})
|
||||
},
|
||||
onRerun: (failedOnly) => {
|
||||
void handleRerun(failedOnly)
|
||||
}
|
||||
})
|
||||
t
|
||||
)
|
||||
|
||||
const compactHeader = (
|
||||
<ChecksCompactHeader
|
||||
|
||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user