mirror of
https://github.com/stablyai/orca.git
synced 2026-10-31 00:57:45 +00:00
feat(mobile): localize app screens, settings, pairing and host lists (#35498)
Moves ~600 strings in the root screens, settings, pairing, onboarding, accounts, agent history, diagnostics, connection status, home and host workspace lists to the translation API, with all ten translations. - Connection verdicts carry an explicit `dialing` flag instead of matching the "…" ending of an English label - Update-failure copy and connection-path accessibility labels are built from data, not by editing translated text - Workspace status names stay the host's labels, as on desktop; transport errors stay English - The stale-key check keeps a plural family while any of its forms is referenced Co-authored-by: OrcaWin <293788423+OrcaWin@users.noreply.github.com>
This commit is contained in:
File diff suppressed because it is too large
Load Diff
@@ -100,7 +100,14 @@ export function collectExtractionProblems(references, englishCatalog, keyEvidenc
|
||||
}
|
||||
}
|
||||
const result = compareExtraction(nestCatalog(extracted), englishCatalog)
|
||||
const unreferenced = findUnreferencedKeys(result.orphans, result.extracted, keyEvidence)
|
||||
// A family is live when any form is named: a counted `items_one` call still resolves `items_other`.
|
||||
const familyOf = (key) => PLURAL_SUFFIX_RE.exec(key)?.[1]
|
||||
const liveFamilies = new Set(
|
||||
[...result.extracted.keys(), ...keyEvidence.literals].map(familyOf).filter(Boolean)
|
||||
)
|
||||
const unreferenced = findUnreferencedKeys(result.orphans, result.extracted, keyEvidence).filter(
|
||||
(key) => !liveFamilies.has(familyOf(key))
|
||||
)
|
||||
return {
|
||||
fallbackDrift: result.fallbackDrift.map(
|
||||
(key) =>
|
||||
|
||||
@@ -1,6 +1,7 @@
|
||||
import { describe, expect, it } from 'vitest'
|
||||
|
||||
import {
|
||||
collectExtractionProblems,
|
||||
collectIncompletePluralFamilies,
|
||||
collectLiveSwitchViolations
|
||||
} from './verify-mobile-localization.mjs'
|
||||
@@ -51,4 +52,20 @@ describe('verify-mobile-localization', () => {
|
||||
collectIncompletePluralFamilies(english, new Map([['files_other', '{{count}} 件']]), 'ja')
|
||||
).toEqual([])
|
||||
})
|
||||
|
||||
it('keeps a plural family while any of its forms is referenced', () => {
|
||||
const english = {
|
||||
items_one: '{{count}} item',
|
||||
items_other: '{{count}} items',
|
||||
gone_one: '{{count}} gone',
|
||||
gone_other: '{{count}} gone'
|
||||
}
|
||||
const references = [{ key: 'items_one', fallback: '{{count}} item' }]
|
||||
const evidence = { literals: new Set(), dynamicPrefixes: new Set() }
|
||||
|
||||
expect(collectExtractionProblems(references, english, evidence).unreferenced).toEqual([
|
||||
'gone_one',
|
||||
'gone_other'
|
||||
])
|
||||
})
|
||||
})
|
||||
|
||||
@@ -22,6 +22,8 @@ import { usePrimeHosts, useRefreshHostClient } from '../../../src/transport/host
|
||||
import type { HostProfile } from '../../../src/transport/types'
|
||||
import { hostOs } from '../../../src/platform/host-os'
|
||||
import { SideSafeArea } from '../../../src/layout/side-safe-area'
|
||||
import { translate } from '../../../src/i18n/mobile-i18n'
|
||||
import { useMobileTranslation } from '../../../src/i18n/use-mobile-translation'
|
||||
|
||||
// The host group leaves the Duo's status-bar rail to its pages; this native screen pads it.
|
||||
export default function EditHostRoute() {
|
||||
@@ -34,6 +36,7 @@ export default function EditHostRoute() {
|
||||
|
||||
function EditHostScreen() {
|
||||
useMobileColorScheme()
|
||||
const t = useMobileTranslation()
|
||||
const router = useRouter()
|
||||
const insets = useSafeAreaInsets()
|
||||
const { hostId } = useLocalSearchParams<{ hostId: string }>()
|
||||
@@ -57,7 +60,7 @@ function EditHostScreen() {
|
||||
const load = useCallback(
|
||||
async (revision: number) => {
|
||||
if (!hostId) {
|
||||
setLoadError('Missing host.')
|
||||
setLoadError(translate('host.edit.missingHost', 'Missing host.'))
|
||||
return
|
||||
}
|
||||
try {
|
||||
@@ -67,7 +70,7 @@ function EditHostScreen() {
|
||||
}
|
||||
const found = hosts.find((h) => h.id === hostId) ?? null
|
||||
if (!found) {
|
||||
setLoadError('This host was removed from this phone.')
|
||||
setLoadError(translate('host.edit.hostRemoved', 'This host was removed from this phone.'))
|
||||
setHost(null)
|
||||
return
|
||||
}
|
||||
@@ -80,7 +83,11 @@ function EditHostScreen() {
|
||||
if (revision !== routeRevision.current) {
|
||||
return
|
||||
}
|
||||
setLoadError(err instanceof Error ? err.message : 'Failed to load host.')
|
||||
setLoadError(
|
||||
err instanceof Error
|
||||
? err.message
|
||||
: translate('host.edit.loadFailed', 'Failed to load host.')
|
||||
)
|
||||
setHost(null)
|
||||
}
|
||||
},
|
||||
@@ -101,7 +108,7 @@ function EditHostScreen() {
|
||||
|
||||
const endpointEdit = useMemo(
|
||||
() => (host ? resolveHostEndpointEdit(host.endpoint, address) : null),
|
||||
[address, host]
|
||||
[address, host, t]
|
||||
)
|
||||
|
||||
const nameTrimmed = name.trim()
|
||||
@@ -143,7 +150,9 @@ function EditHostScreen() {
|
||||
})
|
||||
} catch (err) {
|
||||
if (revision === routeRevision.current) {
|
||||
setSaveError(err instanceof Error ? err.message : 'Failed to save host.')
|
||||
setSaveError(
|
||||
err instanceof Error ? err.message : t('host.edit.saveFailed', 'Failed to save host.')
|
||||
)
|
||||
savingRef.current = false
|
||||
setSaving(false)
|
||||
}
|
||||
@@ -177,11 +186,11 @@ function EditHostScreen() {
|
||||
style={styles.backButton}
|
||||
onPress={leave}
|
||||
accessibilityRole="button"
|
||||
accessibilityLabel="Back"
|
||||
accessibilityLabel={t('common.back', 'Back')}
|
||||
>
|
||||
<ChevronLeft size={22} color={colors.textSecondary} />
|
||||
</Pressable>
|
||||
<Text style={styles.heading}>Edit host</Text>
|
||||
<Text style={styles.heading}>{t('host.edit.title', 'Edit host')}</Text>
|
||||
<Pressable
|
||||
style={({ pressed }) => [
|
||||
styles.saveButton,
|
||||
@@ -190,12 +199,12 @@ function EditHostScreen() {
|
||||
onPress={() => void handleSave()}
|
||||
disabled={!canSave}
|
||||
accessibilityRole="button"
|
||||
accessibilityLabel="Save host"
|
||||
accessibilityLabel={t('host.edit.saveHost', 'Save host')}
|
||||
>
|
||||
{saving ? (
|
||||
<ActivityIndicator size="small" color={colors.bgBase} />
|
||||
) : (
|
||||
<Text style={styles.saveButtonText}>Save</Text>
|
||||
<Text style={styles.saveButtonText}>{t('common.save', 'Save')}</Text>
|
||||
)}
|
||||
</Pressable>
|
||||
</View>
|
||||
@@ -204,7 +213,7 @@ function EditHostScreen() {
|
||||
<View style={styles.errorState}>
|
||||
<Text style={styles.errorText}>{loadError}</Text>
|
||||
<Pressable style={styles.secondaryButton} onPress={leave}>
|
||||
<Text style={styles.secondaryButtonText}>Go back</Text>
|
||||
<Text style={styles.secondaryButtonText}>{t('host.edit.goBack', 'Go back')}</Text>
|
||||
</Pressable>
|
||||
</View>
|
||||
) : !host ? (
|
||||
@@ -221,32 +230,34 @@ function EditHostScreen() {
|
||||
keyboardShouldPersistTaps="handled"
|
||||
>
|
||||
<Text style={styles.help}>
|
||||
Change the display name or connection address. Leave the name empty to use the name
|
||||
the desktop reports. Address edits only switch where this phone connects — they do not
|
||||
re-pair. Use this when the same desktop is reachable at a different IP (for example
|
||||
home LAN vs Tailscale).
|
||||
{t(
|
||||
'host.edit.help',
|
||||
'Change the display name or connection address. Leave the name empty to use the name the desktop reports. Address edits only switch where this phone connects — they do not re-pair. Use this when the same desktop is reachable at a different IP (for example home LAN vs Tailscale).'
|
||||
)}
|
||||
</Text>
|
||||
|
||||
<Text style={styles.label}>Name</Text>
|
||||
<Text style={styles.label}>{t('host.edit.name', 'Name')}</Text>
|
||||
<TextInput
|
||||
style={styles.input}
|
||||
accessibilityLabel="Name"
|
||||
accessibilityLabel={t('host.edit.name', 'Name')}
|
||||
value={name}
|
||||
onChangeText={(value) => {
|
||||
setName(value)
|
||||
setSaveError(null)
|
||||
}}
|
||||
placeholder={host.lastKnownMachineName ?? 'Host name'}
|
||||
placeholder={
|
||||
host.lastKnownMachineName ?? t('host.edit.hostNamePlaceholder', 'Host name')
|
||||
}
|
||||
placeholderTextColor={colors.textMuted}
|
||||
autoCapitalize="words"
|
||||
autoCorrect={false}
|
||||
returnKeyType="next"
|
||||
/>
|
||||
|
||||
<Text style={styles.label}>Address</Text>
|
||||
<Text style={styles.label}>{t('host.edit.address', 'Address')}</Text>
|
||||
<TextInput
|
||||
style={styles.input}
|
||||
accessibilityLabel="Address"
|
||||
accessibilityLabel={t('host.edit.address', 'Address')}
|
||||
value={address}
|
||||
onChangeText={(value) => {
|
||||
setAddress(value)
|
||||
@@ -266,13 +277,17 @@ function EditHostScreen() {
|
||||
}}
|
||||
/>
|
||||
<Text style={styles.hint}>
|
||||
Accepts IP, host:port, or ws:// / wss://. A wss:// URL without a port uses 443. Bare
|
||||
addresses keep the current port (or 6768).
|
||||
{t(
|
||||
'host.edit.addressHint',
|
||||
'Accepts IP, host:port, or ws:// / wss://. A wss:// URL without a port uses 443. Bare addresses keep the current port (or 6768).'
|
||||
)}
|
||||
</Text>
|
||||
|
||||
{endpointEdit == null ? null : endpointEdit.kind !== 'invalid' ? (
|
||||
<Text style={styles.preview} numberOfLines={2}>
|
||||
Connects to {endpointEdit.endpoint}
|
||||
{t('host.edit.connectionPreview', 'Connects to {{endpoint}}', {
|
||||
endpoint: endpointEdit.endpoint
|
||||
})}
|
||||
</Text>
|
||||
) : address.trim().length > 0 ? (
|
||||
<Text style={styles.previewError}>{endpointEdit.error}</Text>
|
||||
|
||||
@@ -18,6 +18,7 @@ import {
|
||||
type MobileSessionView
|
||||
} from '../src/storage/session-view-preferences'
|
||||
import { setRemotePushEnabled } from '../src/notifications/push-registration'
|
||||
import { useMobileTranslation } from '../src/i18n/use-mobile-translation'
|
||||
|
||||
const SLIDE_DURATION_MS = 280
|
||||
|
||||
@@ -46,6 +47,7 @@ function MobileOnboardingFlow({
|
||||
rawSteps: string | undefined
|
||||
}) {
|
||||
useMobileColorScheme()
|
||||
const t = useMobileTranslation()
|
||||
const router = useRouter()
|
||||
const steps = useMemo(() => parseMobileOnboardingSteps(rawSteps), [rawSteps])
|
||||
// Measure pager viewport: iPad sheet is narrower than window; null waits for first layout.
|
||||
@@ -102,12 +104,14 @@ function MobileOnboardingFlow({
|
||||
await saveDefaultSessionView(view)
|
||||
advanceOrContinue()
|
||||
} catch {
|
||||
setError('Your choice could not be saved. Try again.')
|
||||
setError(
|
||||
t('app.mobileOnboarding.saveChoiceFailed', 'Your choice could not be saved. Try again.')
|
||||
)
|
||||
setBusyChoice(null)
|
||||
choiceInFlightRef.current = false
|
||||
}
|
||||
},
|
||||
[advanceOrContinue]
|
||||
[advanceOrContinue, t]
|
||||
)
|
||||
|
||||
const chooseNotifications = useCallback(
|
||||
@@ -123,12 +127,17 @@ function MobileOnboardingFlow({
|
||||
await setRemotePushEnabled(enabled)
|
||||
advanceOrContinue()
|
||||
} catch {
|
||||
setError('Notification settings could not be updated. Try again.')
|
||||
setError(
|
||||
t(
|
||||
'app.mobileOnboarding.notificationSettingsFailed',
|
||||
'Notification settings could not be updated. Try again.'
|
||||
)
|
||||
)
|
||||
setBusyChoice(null)
|
||||
choiceInFlightRef.current = false
|
||||
}
|
||||
},
|
||||
[advanceOrContinue]
|
||||
[advanceOrContinue, t]
|
||||
)
|
||||
|
||||
const translateX = useMemo(
|
||||
@@ -152,8 +161,13 @@ function MobileOnboardingFlow({
|
||||
<View
|
||||
accessible
|
||||
accessibilityRole="progressbar"
|
||||
accessibilityLabel="Onboarding progress"
|
||||
accessibilityValue={{ text: `Step ${activeIndex + 1} of ${steps.length}` }}
|
||||
accessibilityLabel={t('app.mobileOnboarding.progress', 'Onboarding progress')}
|
||||
accessibilityValue={{
|
||||
text: t('app.mobileOnboarding.stepProgress', 'Step {{step}} of {{total}}', {
|
||||
step: activeIndex + 1,
|
||||
total: steps.length
|
||||
})
|
||||
}}
|
||||
style={styles.progress}
|
||||
>
|
||||
{steps.map((step, index) => (
|
||||
|
||||
@@ -3,13 +3,19 @@ import { NativeNotificationDeliverySettings } from '../src/settings/native-notif
|
||||
import { useRouter } from 'expo-router'
|
||||
import NotificationsScreen from '../src/settings/notification-settings-screen'
|
||||
import { nativeNotificationSettingsOperations } from '../src/settings/native-notification-settings-operations'
|
||||
import { useMobileTranslation } from '../src/i18n/use-mobile-translation'
|
||||
|
||||
export default function NativeNotificationsRoute() {
|
||||
const t = useMobileTranslation()
|
||||
const router = useRouter()
|
||||
return (
|
||||
<NotificationsScreen
|
||||
operations={nativeNotificationSettingsOperations}
|
||||
onBack={() => router.back()}
|
||||
description="Get agent alerts even when the app is closed. Delivered through Orca’s push service and Apple or Google."
|
||||
description={t(
|
||||
'app.notifications.description',
|
||||
'Get agent alerts even when the app is closed. Delivered through Orca’s push service and Apple or Google.'
|
||||
)}
|
||||
>
|
||||
{(enabled) => (
|
||||
<>
|
||||
|
||||
+37
-11
@@ -16,6 +16,7 @@ import { useRefreshHostClient } from '../src/transport/host-client-hooks'
|
||||
import { colors, spacing, radii, typography } from '../src/theme/mobile-theme'
|
||||
import { themedStyleSheet } from '../src/theme/themed-styles'
|
||||
import { useMobileColorScheme } from '../src/theme/use-mobile-color-scheme'
|
||||
import { useMobileTranslation } from '../src/i18n/use-mobile-translation'
|
||||
import { ConnectionLog } from '../src/components/ConnectionLog'
|
||||
import {
|
||||
loadMobileOnboardingSteps,
|
||||
@@ -28,6 +29,7 @@ type Status = 'awaiting-confirm' | 'connecting' | 'error'
|
||||
const PAIRING_OVERALL_TIMEOUT_MS = 25_000
|
||||
|
||||
export default function PairConfirmScreen() {
|
||||
useMobileTranslation()
|
||||
const params = useLocalSearchParams<{ code?: string | string[] }>()
|
||||
return (
|
||||
<PairConfirmContent
|
||||
@@ -39,6 +41,7 @@ export default function PairConfirmScreen() {
|
||||
|
||||
function PairConfirmContent({ routeState }: { routeState: PairConfirmRouteState }) {
|
||||
useMobileColorScheme()
|
||||
const t = useMobileTranslation()
|
||||
const router = useRouter()
|
||||
const refreshHostClient = useRefreshHostClient()
|
||||
const insets = useSafeAreaInsets()
|
||||
@@ -140,8 +143,14 @@ function PairConfirmContent({ routeState }: { routeState: PairConfirmRouteState
|
||||
setStatus('error')
|
||||
setErrorMessage(
|
||||
timedOut
|
||||
? `Couldn't connect within ${PAIRING_OVERALL_TIMEOUT_MS / 1000}s — see log below for where it stalled`
|
||||
: `Pairing failed: ${err instanceof Error ? err.message : String(err)}`
|
||||
? t(
|
||||
'app.pairConfirm.connectTimedOut',
|
||||
"Couldn't connect within {{seconds}}s — see log below for where it stalled",
|
||||
{ seconds: PAIRING_OVERALL_TIMEOUT_MS / 1000 }
|
||||
)
|
||||
: t('app.pairConfirm.pairingFailed', 'Pairing failed: {{error}}', {
|
||||
error: err instanceof Error ? err.message : String(err)
|
||||
})
|
||||
)
|
||||
}
|
||||
}
|
||||
@@ -155,7 +164,7 @@ function PairConfirmContent({ routeState }: { routeState: PairConfirmRouteState
|
||||
onPress={cancel}
|
||||
hitSlop={8}
|
||||
accessibilityRole="button"
|
||||
accessibilityLabel="Back"
|
||||
accessibilityLabel={t('common.back', 'Back')}
|
||||
>
|
||||
<ChevronLeft size={22} color={colors.textSecondary} />
|
||||
</Pressable>
|
||||
@@ -163,16 +172,23 @@ function PairConfirmContent({ routeState }: { routeState: PairConfirmRouteState
|
||||
<View style={styles.content}>
|
||||
{offer && resolvedStatus === 'awaiting-confirm' && (
|
||||
<>
|
||||
<Text style={styles.title}>Pair with this desktop?</Text>
|
||||
<Text style={styles.title}>
|
||||
{t('app.pairConfirm.title', 'Pair with this desktop?')}
|
||||
</Text>
|
||||
<Text style={styles.subtitle}>
|
||||
You opened a pairing link from your desktop. Confirm to add it to your hosts.
|
||||
{t(
|
||||
'app.pairConfirm.description',
|
||||
'You opened a pairing link from your desktop. Confirm to add it to your hosts.'
|
||||
)}
|
||||
</Text>
|
||||
<View style={styles.actionStack}>
|
||||
<Pressable style={styles.primaryButton} onPress={() => void confirm()}>
|
||||
<Text style={styles.primaryButtonText}>Pair</Text>
|
||||
<Text style={styles.primaryButtonText}>
|
||||
{t('app.pairConfirm.pairButton', 'Pair')}
|
||||
</Text>
|
||||
</Pressable>
|
||||
<Pressable style={styles.secondaryButton} onPress={cancel}>
|
||||
<Text style={styles.secondaryButtonText}>Cancel</Text>
|
||||
<Text style={styles.secondaryButtonText}>{t('common.cancel', 'Cancel')}</Text>
|
||||
</Pressable>
|
||||
</View>
|
||||
</>
|
||||
@@ -181,9 +197,14 @@ function PairConfirmContent({ routeState }: { routeState: PairConfirmRouteState
|
||||
{resolvedStatus === 'connecting' && (
|
||||
<>
|
||||
<ActivityIndicator size="large" color={colors.textSecondary} />
|
||||
<Text style={styles.connectingText}>Connecting…</Text>
|
||||
<Text style={styles.connectingText}>
|
||||
{t('app.pairConfirm.connecting', 'Connecting…')}
|
||||
</Text>
|
||||
<View style={styles.logSlot}>
|
||||
<ConnectionLog entries={logs} title="Pairing log" />
|
||||
<ConnectionLog
|
||||
entries={logs}
|
||||
title={t('app.pairConfirm.pairingLog', 'Pairing log')}
|
||||
/>
|
||||
</View>
|
||||
</>
|
||||
)}
|
||||
@@ -193,12 +214,17 @@ function PairConfirmContent({ routeState }: { routeState: PairConfirmRouteState
|
||||
<Text style={styles.errorText}>{resolvedErrorMessage}</Text>
|
||||
{logs.length > 0 && (
|
||||
<View style={styles.logSlot}>
|
||||
<ConnectionLog entries={logs} title="Pairing log" />
|
||||
<ConnectionLog
|
||||
entries={logs}
|
||||
title={t('app.pairConfirm.pairingLog', 'Pairing log')}
|
||||
/>
|
||||
</View>
|
||||
)}
|
||||
<View style={styles.actionStack}>
|
||||
<Pressable style={styles.primaryButton} onPress={cancel}>
|
||||
<Text style={styles.primaryButtonText}>Back to home</Text>
|
||||
<Text style={styles.primaryButtonText}>
|
||||
{t('app.pairConfirm.backToHome', 'Back to home')}
|
||||
</Text>
|
||||
</Pressable>
|
||||
</View>
|
||||
</>
|
||||
|
||||
+90
-41
@@ -21,6 +21,7 @@ import type { ConnectionLogEntry, PairingOffer } from '../src/transport/types'
|
||||
import { useRefreshHostClient } from '../src/transport/host-client-hooks'
|
||||
import { colors, spacing } from '../src/theme/mobile-theme'
|
||||
import { useMobileColorScheme } from '../src/theme/use-mobile-color-scheme'
|
||||
import { useMobileTranslation } from '../src/i18n/use-mobile-translation'
|
||||
import { TextInputModal } from '../src/components/TextInputModal'
|
||||
import { ConnectionLog } from '../src/components/ConnectionLog'
|
||||
import {
|
||||
@@ -48,6 +49,7 @@ function Step({ number, text }: { number: number; text: string }) {
|
||||
|
||||
export default function PairScanScreen() {
|
||||
useMobileColorScheme()
|
||||
const t = useMobileTranslation()
|
||||
const router = useRouter()
|
||||
const refreshHostClient = useRefreshHostClient()
|
||||
const insets = useSafeAreaInsets()
|
||||
@@ -92,33 +94,41 @@ export default function PairScanScreen() {
|
||||
const offer = decodePairingUrl(data)
|
||||
if (!offer) {
|
||||
setStatus('error')
|
||||
setErrorMessage('Not a valid Orca QR code')
|
||||
setErrorMessage(t('app.pairScan.invalidQrCode', 'Not a valid Orca QR code'))
|
||||
processingRef.current = false
|
||||
return
|
||||
}
|
||||
|
||||
void testAndSave(offer)
|
||||
},
|
||||
[router]
|
||||
[router, t]
|
||||
)
|
||||
|
||||
const handlePasteSubmit = useCallback((input: string) => {
|
||||
setPasteVisible(false)
|
||||
if (processingRef.current) {
|
||||
return
|
||||
}
|
||||
processingRef.current = true
|
||||
const handlePasteSubmit = useCallback(
|
||||
(input: string) => {
|
||||
setPasteVisible(false)
|
||||
if (processingRef.current) {
|
||||
return
|
||||
}
|
||||
processingRef.current = true
|
||||
|
||||
const offer = parsePairingCode(input)
|
||||
if (!offer) {
|
||||
setStatus('error')
|
||||
setErrorMessage('Not a valid pairing code — copy it from your computer and paste again')
|
||||
processingRef.current = false
|
||||
return
|
||||
}
|
||||
const offer = parsePairingCode(input)
|
||||
if (!offer) {
|
||||
setStatus('error')
|
||||
setErrorMessage(
|
||||
t(
|
||||
'app.pairScan.invalidPairingCode',
|
||||
'Not a valid pairing code — copy it from your computer and paste again'
|
||||
)
|
||||
)
|
||||
processingRef.current = false
|
||||
return
|
||||
}
|
||||
|
||||
void testAndSave(offer)
|
||||
}, [])
|
||||
void testAndSave(offer)
|
||||
},
|
||||
[t]
|
||||
)
|
||||
|
||||
const handleCameraLayout = useCallback((event: LayoutChangeEvent) => {
|
||||
const { width, height } = event.nativeEvent.layout
|
||||
@@ -184,8 +194,14 @@ export default function PairScanScreen() {
|
||||
setStatus('error')
|
||||
setErrorMessage(
|
||||
timedOut
|
||||
? `Couldn't connect within ${PAIRING_OVERALL_TIMEOUT_MS / 1000}s — see log below for where it stalled`
|
||||
: `Pairing failed: ${err instanceof Error ? err.message : String(err)}`
|
||||
? t(
|
||||
'app.pairScan.connectTimedOut',
|
||||
"Couldn't connect within {{seconds}}s — see log below for where it stalled",
|
||||
{ seconds: PAIRING_OVERALL_TIMEOUT_MS / 1000 }
|
||||
)
|
||||
: t('app.pairScan.pairingFailed', 'Pairing failed: {{error}}', {
|
||||
error: err instanceof Error ? err.message : String(err)
|
||||
})
|
||||
)
|
||||
processingRef.current = false
|
||||
}
|
||||
@@ -227,18 +243,26 @@ export default function PairScanScreen() {
|
||||
onPress={() => router.back()}
|
||||
hitSlop={8}
|
||||
accessibilityRole="button"
|
||||
accessibilityLabel="Back"
|
||||
accessibilityLabel={t('common.back', 'Back')}
|
||||
>
|
||||
<ChevronLeft size={22} color={colors.textSecondary} />
|
||||
</Pressable>
|
||||
<View style={styles.centered}>
|
||||
<Text style={styles.title}>
|
||||
{canAskAgain ? 'Pair with desktop' : 'Camera Access Disabled'}
|
||||
{canAskAgain
|
||||
? t('app.pairScan.title', 'Pair with desktop')
|
||||
: t('app.pairScan.cameraDisabledTitle', 'Camera Access Disabled')}
|
||||
</Text>
|
||||
<Text style={styles.subtitle}>
|
||||
{canAskAgain
|
||||
? 'Scan the QR code from Orca on your desktop, or paste the pairing code instead.'
|
||||
: 'Enable camera access in Settings, or paste the pairing code instead.'}
|
||||
? t(
|
||||
'app.pairScan.cameraPermissionDescription',
|
||||
'Scan the QR code from Orca on your desktop, or paste the pairing code instead.'
|
||||
)
|
||||
: t(
|
||||
'app.pairScan.cameraDisabledDescription',
|
||||
'Enable camera access in Settings, or paste the pairing code instead.'
|
||||
)}
|
||||
</Text>
|
||||
<Pressable
|
||||
style={styles.primaryButton}
|
||||
@@ -246,7 +270,9 @@ export default function PairScanScreen() {
|
||||
>
|
||||
{canAskAgain && <QrCode size={16} color={colors.bgBase} />}
|
||||
<Text style={styles.primaryButtonText}>
|
||||
{canAskAgain ? 'Continue' : 'Open Settings'}
|
||||
{canAskAgain
|
||||
? t('common.continue', 'Continue')
|
||||
: t('app.pairScan.openSettings', 'Open Settings')}
|
||||
</Text>
|
||||
</Pressable>
|
||||
<Pressable
|
||||
@@ -254,14 +280,22 @@ export default function PairScanScreen() {
|
||||
onPress={() => setPasteVisible(true)}
|
||||
>
|
||||
<ClipboardIcon size={16} color={colors.textSecondary} />
|
||||
<Text style={styles.pasteButtonText}>Paste code instead</Text>
|
||||
<Text style={styles.pasteButtonText}>
|
||||
{t('app.pairScan.pasteInstead', 'Paste code instead')}
|
||||
</Text>
|
||||
</Pressable>
|
||||
</View>
|
||||
<TextInputModal
|
||||
visible={pasteVisible}
|
||||
title="Paste pairing code"
|
||||
message="Copy the code shown under the QR on your computer."
|
||||
placeholder="orca://pair?code=... or paste the code"
|
||||
title={t('app.pairScan.pasteCodeTitle', 'Paste pairing code')}
|
||||
message={t(
|
||||
'app.pairScan.pasteCodeMessage',
|
||||
'Copy the code shown under the QR on your computer.'
|
||||
)}
|
||||
placeholder={t(
|
||||
'app.pairScan.pasteCodePlaceholder',
|
||||
'orca://pair?code=... or paste the code'
|
||||
)}
|
||||
onSubmit={handlePasteSubmit}
|
||||
onCancel={() => setPasteVisible(false)}
|
||||
/>
|
||||
@@ -276,15 +310,18 @@ export default function PairScanScreen() {
|
||||
onPress={() => router.back()}
|
||||
hitSlop={8}
|
||||
accessibilityRole="button"
|
||||
accessibilityLabel="Back"
|
||||
accessibilityLabel={t('common.back', 'Back')}
|
||||
>
|
||||
<ChevronLeft size={22} color={colors.textSecondary} />
|
||||
</Pressable>
|
||||
|
||||
<View style={styles.steps}>
|
||||
<Step number={1} text="Open Orca on your computer" />
|
||||
<Step number={2} text="Go to Settings → Mobile" />
|
||||
<Step number={3} text="Scan the QR code" />
|
||||
<Step number={1} text={t('app.pairScan.openDesktopStep', 'Open Orca on your computer')} />
|
||||
<Step
|
||||
number={2}
|
||||
text={t('app.pairScan.openMobileSettingsStep', 'Go to Settings → Mobile')}
|
||||
/>
|
||||
<Step number={3} text={t('app.pairScan.scanCodeStep', 'Scan the QR code')} />
|
||||
</View>
|
||||
|
||||
{status === 'scanning' && (
|
||||
@@ -314,7 +351,9 @@ export default function PairScanScreen() {
|
||||
onPress={() => setPasteVisible(true)}
|
||||
>
|
||||
<ClipboardIcon size={16} color={colors.textSecondary} />
|
||||
<Text style={styles.pasteButtonText}>Or paste pairing code</Text>
|
||||
<Text style={styles.pasteButtonText}>
|
||||
{t('app.pairScan.pastePairingCode', 'Or paste pairing code')}
|
||||
</Text>
|
||||
</Pressable>
|
||||
</>
|
||||
)}
|
||||
@@ -322,9 +361,9 @@ export default function PairScanScreen() {
|
||||
{status === 'connecting' && (
|
||||
<View style={styles.centered}>
|
||||
<ActivityIndicator size="large" color={colors.textSecondary} />
|
||||
<Text style={styles.connectingText}>Connecting…</Text>
|
||||
<Text style={styles.connectingText}>{t('app.pairScan.connecting', 'Connecting…')}</Text>
|
||||
<View style={styles.logSlot}>
|
||||
<ConnectionLog entries={logs} title="Pairing log" />
|
||||
<ConnectionLog entries={logs} title={t('app.pairScan.pairingLog', 'Pairing log')} />
|
||||
</View>
|
||||
</View>
|
||||
)}
|
||||
@@ -334,12 +373,14 @@ export default function PairScanScreen() {
|
||||
<Text style={styles.errorText}>{errorMessage}</Text>
|
||||
{logs.length > 0 && (
|
||||
<View style={styles.logSlot}>
|
||||
<ConnectionLog entries={logs} title="Pairing log" />
|
||||
<ConnectionLog entries={logs} title={t('app.pairScan.pairingLog', 'Pairing log')} />
|
||||
</View>
|
||||
)}
|
||||
<View style={styles.errorActions}>
|
||||
<Pressable style={styles.primaryButton} onPress={retry}>
|
||||
<Text style={styles.primaryButtonText}>Try Again</Text>
|
||||
<Text style={styles.primaryButtonText}>
|
||||
{t('app.pairScan.tryAgain', 'Try Again')}
|
||||
</Text>
|
||||
</Pressable>
|
||||
<Pressable
|
||||
style={({ pressed }) => [
|
||||
@@ -351,7 +392,9 @@ export default function PairScanScreen() {
|
||||
setPasteVisible(true)
|
||||
}}
|
||||
>
|
||||
<Text style={styles.secondaryButtonText}>Paste code instead</Text>
|
||||
<Text style={styles.secondaryButtonText}>
|
||||
{t('app.pairScan.pasteInstead', 'Paste code instead')}
|
||||
</Text>
|
||||
</Pressable>
|
||||
</View>
|
||||
</View>
|
||||
@@ -359,9 +402,15 @@ export default function PairScanScreen() {
|
||||
|
||||
<TextInputModal
|
||||
visible={pasteVisible}
|
||||
title="Paste pairing code"
|
||||
message="Copy the code shown under the QR on your computer."
|
||||
placeholder="orca://pair?code=... or paste the code"
|
||||
title={t('app.pairScan.pasteCodeTitle', 'Paste pairing code')}
|
||||
message={t(
|
||||
'app.pairScan.pasteCodeMessage',
|
||||
'Copy the code shown under the QR on your computer.'
|
||||
)}
|
||||
placeholder={t(
|
||||
'app.pairScan.pasteCodePlaceholder',
|
||||
'orca://pair?code=... or paste the code'
|
||||
)}
|
||||
onSubmit={handlePasteSubmit}
|
||||
onCancel={() => setPasteVisible(false)}
|
||||
/>
|
||||
|
||||
+4
-2
@@ -4,10 +4,12 @@ import { useLocalSearchParams, useRouter } from 'expo-router'
|
||||
import { colors, radii, spacing, typography } from '../src/theme/mobile-theme'
|
||||
import { themedStyleSheet } from '../src/theme/themed-styles'
|
||||
import { useMobileColorScheme } from '../src/theme/use-mobile-color-scheme'
|
||||
import { useMobileTranslation } from '../src/i18n/use-mobile-translation'
|
||||
import { extractPairingCodeFromUrl } from '../src/transport/pairing'
|
||||
|
||||
export default function PairRedirectScreen() {
|
||||
useMobileColorScheme()
|
||||
const t = useMobileTranslation()
|
||||
const router = useRouter()
|
||||
const params = useLocalSearchParams<{ code?: string }>()
|
||||
const [missingCode, setMissingCode] = useState(false)
|
||||
@@ -48,9 +50,9 @@ export default function PairRedirectScreen() {
|
||||
<View style={styles.container}>
|
||||
{missingCode ? (
|
||||
<>
|
||||
<Text style={styles.errorText}>Missing pairing code</Text>
|
||||
<Text style={styles.errorText}>{t('app.pair.missingCode', 'Missing pairing code')}</Text>
|
||||
<Pressable style={styles.primaryButton} onPress={goHome}>
|
||||
<Text style={styles.primaryButtonText}>Back to home</Text>
|
||||
<Text style={styles.primaryButtonText}>{t('app.pair.backToHome', 'Back to home')}</Text>
|
||||
</Pressable>
|
||||
</>
|
||||
) : (
|
||||
|
||||
@@ -11,6 +11,8 @@ import { useRouter } from 'expo-router'
|
||||
import { ChevronLeft, ChevronRight, Smartphone, Type } from 'lucide-react-native'
|
||||
import { colors, spacing } from '../src/theme/mobile-theme'
|
||||
import { useMobileColorScheme } from '../src/theme/use-mobile-color-scheme'
|
||||
import type { Translate } from '../src/i18n/mobile-i18n'
|
||||
import { useMobileTranslation } from '../src/i18n/use-mobile-translation'
|
||||
import { loadHosts } from '../src/transport/host-store'
|
||||
import type { HostProfile } from '../src/transport/types'
|
||||
import { useFocusedSettingsHostClients } from '../src/transport/settings-host-client-connections'
|
||||
@@ -36,42 +38,68 @@ type RestoreValue = 'indefinite' | '60s' | '5m' | '30m'
|
||||
type TextSizeValue = 'smallest' | 'smaller' | 'default' | 'large' | 'larger' | 'largest'
|
||||
|
||||
// Baseline zoom sits above fit-to-width; keep presets in sync with TERMINAL_TEXT_SCALES and pinch snapping.
|
||||
const TEXT_SIZE_OPTIONS: (PickerOption<TextSizeValue> & { scale: number })[] = [
|
||||
{ value: 'smallest', label: 'Smallest (50%)', scale: 0.5 },
|
||||
{ value: 'smaller', label: 'Smaller (75%)', scale: 0.75 },
|
||||
{ value: 'default', label: 'Default (100%)', scale: 1 },
|
||||
{ value: 'large', label: 'Large (125%)', scale: 1.25 },
|
||||
{ value: 'larger', label: 'Larger (150%)', scale: 1.5 },
|
||||
{ value: 'largest', label: 'Largest (200%)', scale: 2 }
|
||||
]
|
||||
|
||||
function textSizeValueFromScale(scale: number): TextSizeValue {
|
||||
return TEXT_SIZE_OPTIONS.find((o) => o.scale === scale)?.value ?? 'default'
|
||||
function textSizeOptions(t: Translate): (PickerOption<TextSizeValue> & { scale: number })[] {
|
||||
return [
|
||||
{
|
||||
value: 'smallest',
|
||||
label: t('app.terminalSettings.smallestText', 'Smallest (50%)'),
|
||||
scale: 0.5
|
||||
},
|
||||
{
|
||||
value: 'smaller',
|
||||
label: t('app.terminalSettings.smallerText', 'Smaller (75%)'),
|
||||
scale: 0.75
|
||||
},
|
||||
{ value: 'default', label: t('app.terminalSettings.defaultText', 'Default (100%)'), scale: 1 },
|
||||
{ value: 'large', label: t('app.terminalSettings.largeText', 'Large (125%)'), scale: 1.25 },
|
||||
{ value: 'larger', label: t('app.terminalSettings.largerText', 'Larger (150%)'), scale: 1.5 },
|
||||
{ value: 'largest', label: t('app.terminalSettings.largestText', 'Largest (200%)'), scale: 2 }
|
||||
]
|
||||
}
|
||||
|
||||
function textSizeSummary(scale: number): string {
|
||||
return (TEXT_SIZE_OPTIONS.find((o) => o.scale === scale) ?? TEXT_SIZE_OPTIONS[0]!).label
|
||||
function textSizeValueFromScale(scale: number, t: Translate): TextSizeValue {
|
||||
return textSizeOptions(t).find((o) => o.scale === scale)?.value ?? 'default'
|
||||
}
|
||||
|
||||
const AUTO_RESTORE_FIT_OPTIONS: (PickerOption<RestoreValue> & { ms: number | null })[] = [
|
||||
{ value: 'indefinite', label: 'Keep at phone size (default)', ms: null },
|
||||
{ value: '60s', label: 'After 1 minute', ms: 60_000 },
|
||||
{ value: '5m', label: 'After 5 minutes', ms: 5 * 60_000 },
|
||||
{ value: '30m', label: 'After 30 minutes', ms: 30 * 60_000 }
|
||||
]
|
||||
function textSizeSummary(scale: number, t: Translate): string {
|
||||
const options = textSizeOptions(t)
|
||||
return (options.find((o) => o.scale === scale) ?? options[0]!).label
|
||||
}
|
||||
|
||||
function valueFromMs(ms: number | null | undefined): RestoreValue {
|
||||
function restoreAfterMinutes(t: Translate, count: number): string {
|
||||
return count === 1
|
||||
? t('app.terminalSettings.restoreAfterMinutes_one', 'After {{count}} minute', { count })
|
||||
: t('app.terminalSettings.restoreAfterMinutes_other', 'After {{count}} minutes', { count })
|
||||
}
|
||||
|
||||
function autoRestoreFitOptions(
|
||||
t: Translate
|
||||
): (PickerOption<RestoreValue> & { ms: number | null })[] {
|
||||
return [
|
||||
{
|
||||
value: 'indefinite',
|
||||
label: t('app.terminalSettings.keepPhoneSize', 'Keep at phone size (default)'),
|
||||
ms: null
|
||||
},
|
||||
{ value: '60s', label: restoreAfterMinutes(t, 1), ms: 60_000 },
|
||||
{ value: '5m', label: restoreAfterMinutes(t, 5), ms: 5 * 60_000 },
|
||||
{ value: '30m', label: restoreAfterMinutes(t, 30), ms: 30 * 60_000 }
|
||||
]
|
||||
}
|
||||
|
||||
function valueFromMs(ms: number | null | undefined, t: Translate): RestoreValue {
|
||||
if (ms == null) {
|
||||
return 'indefinite'
|
||||
}
|
||||
const exact = AUTO_RESTORE_FIT_OPTIONS.find((o) => o.ms === ms)
|
||||
const options = autoRestoreFitOptions(t)
|
||||
const exact = options.find((o) => o.ms === ms)
|
||||
if (exact) {
|
||||
return exact.value
|
||||
}
|
||||
// Snap custom/future/clamped server durations to nearest finite preset so picker agrees with autoRestoreSummary.
|
||||
let closest: (typeof AUTO_RESTORE_FIT_OPTIONS)[number] | null = null
|
||||
let closest: (typeof options)[number] | null = null
|
||||
let bestDelta = Infinity
|
||||
for (const opt of AUTO_RESTORE_FIT_OPTIONS) {
|
||||
for (const opt of options) {
|
||||
if (opt.ms == null) {
|
||||
continue
|
||||
}
|
||||
@@ -84,15 +112,21 @@ function valueFromMs(ms: number | null | undefined): RestoreValue {
|
||||
return closest ? closest.value : 'indefinite'
|
||||
}
|
||||
|
||||
function autoRestoreSummary(ms: number | null | undefined): string {
|
||||
function autoRestoreSummary(ms: number | null | undefined, t: Translate): string {
|
||||
if (ms === undefined) {
|
||||
return '…'
|
||||
}
|
||||
if (ms === null) {
|
||||
return AUTO_RESTORE_FIT_OPTIONS[0]!.label
|
||||
return autoRestoreFitOptions(t)[0]!.label
|
||||
}
|
||||
const exact = AUTO_RESTORE_FIT_OPTIONS.find((o) => o.ms === ms)
|
||||
return exact ? exact.label : `After ${Math.round(ms / 1000)}s`
|
||||
const exact = autoRestoreFitOptions(t).find((o) => o.ms === ms)
|
||||
if (exact) {
|
||||
return exact.label
|
||||
}
|
||||
const count = Math.round(ms / 1000)
|
||||
return count === 1
|
||||
? t('app.terminalSettings.restoreAfterSeconds_one', 'After {{count}}s', { count })
|
||||
: t('app.terminalSettings.restoreAfterSeconds_other', 'After {{count}}s', { count })
|
||||
}
|
||||
|
||||
function HostFitRow({
|
||||
@@ -107,6 +141,7 @@ function HostFitRow({
|
||||
onPress: () => void
|
||||
}): React.JSX.Element {
|
||||
useMobileColorScheme()
|
||||
const t = useMobileTranslation()
|
||||
return (
|
||||
<Pressable
|
||||
style={({ pressed }) => [styles.row, pressed && styles.rowPressed]}
|
||||
@@ -116,7 +151,7 @@ function HostFitRow({
|
||||
<Smartphone size={16} color={colors.textSecondary} />
|
||||
<View style={styles.rowContent}>
|
||||
<Text style={styles.rowLabel}>{hostName}</Text>
|
||||
<Text style={styles.rowSublabel}>{autoRestoreSummary(ms)}</Text>
|
||||
<Text style={styles.rowSublabel}>{autoRestoreSummary(ms, t)}</Text>
|
||||
</View>
|
||||
<ChevronRight size={16} color={colors.textMuted} />
|
||||
</Pressable>
|
||||
@@ -125,6 +160,7 @@ function HostFitRow({
|
||||
|
||||
export default function TerminalSettingsScreen() {
|
||||
useMobileColorScheme()
|
||||
const t = useMobileTranslation()
|
||||
const router = useRouter()
|
||||
const insets = useSafeAreaInsets()
|
||||
const [hosts, setHosts] = useState<HostProfile[]>([])
|
||||
@@ -147,14 +183,17 @@ export default function TerminalSettingsScreen() {
|
||||
useEffect(() => {
|
||||
void loadTerminalTextScale().then(setTextScale)
|
||||
}, [])
|
||||
const selectTextSize = useCallback((value: TextSizeValue) => {
|
||||
const opt = TEXT_SIZE_OPTIONS.find((o) => o.value === value)
|
||||
if (!opt) {
|
||||
return
|
||||
}
|
||||
setTextScale(opt.scale)
|
||||
void saveTerminalTextScale(opt.scale)
|
||||
}, [])
|
||||
const selectTextSize = useCallback(
|
||||
(value: TextSizeValue) => {
|
||||
const opt = textSizeOptions(t).find((o) => o.value === value)
|
||||
if (!opt) {
|
||||
return
|
||||
}
|
||||
setTextScale(opt.scale)
|
||||
void saveTerminalTextScale(opt.scale)
|
||||
},
|
||||
[t]
|
||||
)
|
||||
|
||||
const [autocompleteEnabled, setAutocompleteEnabled] = useState(false)
|
||||
// Fast toggle must win over delayed initial load.
|
||||
@@ -202,7 +241,7 @@ export default function TerminalSettingsScreen() {
|
||||
if (!client) {
|
||||
return
|
||||
}
|
||||
const opt = AUTO_RESTORE_FIT_OPTIONS.find((o) => o.value === value)
|
||||
const opt = autoRestoreFitOptions(t).find((o) => o.value === value)
|
||||
if (!opt) {
|
||||
return
|
||||
}
|
||||
@@ -248,11 +287,11 @@ export default function TerminalSettingsScreen() {
|
||||
onPress={() => router.back()}
|
||||
hitSlop={8}
|
||||
accessibilityRole="button"
|
||||
accessibilityLabel="Back"
|
||||
accessibilityLabel={t('common.back', 'Back')}
|
||||
>
|
||||
<ChevronLeft size={22} color={colors.textSecondary} />
|
||||
</Pressable>
|
||||
<Text style={styles.heading}>Terminal</Text>
|
||||
<Text style={styles.heading}>{t('app.terminalSettings.title', 'Terminal')}</Text>
|
||||
</View>
|
||||
|
||||
<Animated.ScrollView
|
||||
@@ -265,18 +304,23 @@ export default function TerminalSettingsScreen() {
|
||||
scrollContentHeight.value = height
|
||||
}}
|
||||
>
|
||||
<Text style={styles.groupHeading}>WHEN YOU LEAVE THE APP</Text>
|
||||
<Text style={styles.groupHeading}>
|
||||
{t('app.terminalSettings.leaveAppHeading', 'WHEN YOU LEAVE THE APP')}
|
||||
</Text>
|
||||
<Text style={styles.groupDescription}>
|
||||
While you're using a terminal on your phone, Orca shrinks it to fit your screen. When
|
||||
you close the app or switch away, this controls whether it stays at phone size (so
|
||||
interactive CLI tools don't reflow) or resizes back to your desktop. You can always
|
||||
use Restore on the desktop banner to resize manually.
|
||||
{t(
|
||||
'app.terminalSettings.leaveAppDescription',
|
||||
"While you're using a terminal on your phone, Orca shrinks it to fit your screen. When you close the app or switch away, this controls whether it stays at phone size (so interactive CLI tools don't reflow) or resizes back to your desktop. You can always use Restore on the desktop banner to resize manually."
|
||||
)}
|
||||
</Text>
|
||||
|
||||
{hosts.length === 0 ? (
|
||||
<View style={[styles.section, styles.sectionTopGap]}>
|
||||
<Text style={styles.emptyText}>
|
||||
No paired desktops yet. Pair one to control terminal behavior.
|
||||
{t(
|
||||
'app.terminalSettings.noPairedDesktops',
|
||||
'No paired desktops yet. Pair one to control terminal behavior.'
|
||||
)}
|
||||
</Text>
|
||||
</View>
|
||||
) : (
|
||||
@@ -298,12 +342,14 @@ export default function TerminalSettingsScreen() {
|
||||
</View>
|
||||
)}
|
||||
|
||||
<Text style={[styles.groupHeading, styles.inputGroupGap]}>TEXT SIZE</Text>
|
||||
<Text style={[styles.groupHeading, styles.inputGroupGap]}>
|
||||
{t('app.terminalSettings.textSizeHeading', 'TEXT SIZE')}
|
||||
</Text>
|
||||
<Text style={styles.groupDescription}>
|
||||
Scale the terminal text. Smaller sizes fit more columns with side margins; larger sizes
|
||||
show fewer columns — drag sideways to pan. You can also pinch to zoom in the terminal
|
||||
itself, which updates this setting. Per-device display only; doesn't change the
|
||||
desktop terminal.
|
||||
{t(
|
||||
'app.terminalSettings.textSizeDescription',
|
||||
"Scale the terminal text. Smaller sizes fit more columns with side margins; larger sizes show fewer columns — drag sideways to pan. You can also pinch to zoom in the terminal itself, which updates this setting. Per-device display only; doesn't change the desktop terminal."
|
||||
)}
|
||||
</Text>
|
||||
<View style={[styles.section, styles.sectionTopGap]}>
|
||||
<Pressable
|
||||
@@ -312,24 +358,33 @@ export default function TerminalSettingsScreen() {
|
||||
>
|
||||
<Type size={16} color={colors.textSecondary} />
|
||||
<View style={styles.rowContent}>
|
||||
<Text style={styles.rowLabel}>Text size</Text>
|
||||
<Text style={styles.rowSublabel}>{textSizeSummary(textScale)}</Text>
|
||||
<Text style={styles.rowLabel}>{t('app.terminalSettings.textSize', 'Text size')}</Text>
|
||||
<Text style={styles.rowSublabel}>{textSizeSummary(textScale, t)}</Text>
|
||||
</View>
|
||||
<ChevronRight size={16} color={colors.textMuted} />
|
||||
</Pressable>
|
||||
</View>
|
||||
|
||||
<Text style={[styles.groupHeading, styles.inputGroupGap]}>KEYBOARD INPUT</Text>
|
||||
<Text style={[styles.groupHeading, styles.inputGroupGap]}>
|
||||
{t('app.terminalSettings.keyboardInputHeading', 'KEYBOARD INPUT')}
|
||||
</Text>
|
||||
<Text style={styles.groupDescription}>
|
||||
Autocomplete, autocorrect, and spelling suggestions apply only to buffered command input.
|
||||
Use the double-chevron button above the keyboard to switch from live input, then edit your
|
||||
command before tapping Send. Off by default to avoid rewriting commands, flags, or paths.
|
||||
{t(
|
||||
'app.terminalSettings.keyboardInputDescription',
|
||||
'Autocomplete, autocorrect, and spelling suggestions apply only to buffered command input. Use the double-chevron button above the keyboard to switch from live input, then edit your command before tapping Send. Off by default to avoid rewriting commands, flags, or paths.'
|
||||
)}
|
||||
</Text>
|
||||
<View style={[styles.section, styles.sectionTopGap]}>
|
||||
<View style={styles.row}>
|
||||
<View style={styles.rowContent}>
|
||||
<Text style={styles.rowLabel}>Autocomplete & autocorrect</Text>
|
||||
<Text style={styles.rowSublabel}>{autocompleteEnabled ? 'On' : 'Off'}</Text>
|
||||
<Text style={styles.rowLabel}>
|
||||
{t('app.terminalSettings.autocomplete', 'Autocomplete & autocorrect')}
|
||||
</Text>
|
||||
<Text style={styles.rowSublabel}>
|
||||
{autocompleteEnabled
|
||||
? t('app.terminalSettings.autocompleteOn', 'On')
|
||||
: t('app.terminalSettings.autocompleteOff', 'Off')}
|
||||
</Text>
|
||||
</View>
|
||||
<Switch
|
||||
value={autocompleteEnabled}
|
||||
@@ -350,9 +405,13 @@ export default function TerminalSettingsScreen() {
|
||||
|
||||
<PickerModal<RestoreValue>
|
||||
visible={pickerHost != null}
|
||||
title={pickerHost ? `Restore ${pickerHost.name}` : ''}
|
||||
options={AUTO_RESTORE_FIT_OPTIONS}
|
||||
selected={valueFromMs(pickerHost ? hostMs[pickerHost.id] : null)}
|
||||
title={
|
||||
pickerHost
|
||||
? t('app.terminalSettings.restoreHost', 'Restore {{name}}', { name: pickerHost.name })
|
||||
: ''
|
||||
}
|
||||
options={autoRestoreFitOptions(t)}
|
||||
selected={valueFromMs(pickerHost ? hostMs[pickerHost.id] : null, t)}
|
||||
onSelect={(v) => {
|
||||
if (pickerHost) {
|
||||
void selectValue(pickerHost.id, v)
|
||||
@@ -363,9 +422,9 @@ export default function TerminalSettingsScreen() {
|
||||
|
||||
<PickerModal<TextSizeValue>
|
||||
visible={textSizePickerOpen}
|
||||
title="Terminal text size"
|
||||
options={TEXT_SIZE_OPTIONS}
|
||||
selected={textSizeValueFromScale(textScale)}
|
||||
title={t('app.terminalSettings.textSizePickerTitle', 'Terminal text size')}
|
||||
options={textSizeOptions(t)}
|
||||
selected={textSizeValueFromScale(textScale, t)}
|
||||
onSelect={selectTextSize}
|
||||
onClose={() => setTextSizePickerOpen(false)}
|
||||
/>
|
||||
|
||||
@@ -7,6 +7,7 @@ import { loadHosts } from '../transport/host-store'
|
||||
import { useHostClient } from '../transport/host-client-hooks'
|
||||
import { colors, spacing } from '../theme/mobile-theme'
|
||||
import { useMobileColorScheme } from '../theme/use-mobile-color-scheme'
|
||||
import { useMobileTranslation } from '../i18n/use-mobile-translation'
|
||||
import { styles } from './mobile-accounts-screen-styles'
|
||||
import { useNow } from '../hooks/use-now'
|
||||
import { ClaudeIcon, OpenAIIcon } from '../components/AgentIcons'
|
||||
@@ -35,6 +36,7 @@ type AccountsNotice = { title: string; message: string }
|
||||
// Use page ConfirmModal because RN Web Alert.alert is a no-op; Back must hand off to shell’s native stack.
|
||||
export function MobileAccountsScreen() {
|
||||
useMobileColorScheme()
|
||||
const t = useMobileTranslation()
|
||||
const router = useRouteHandoff()
|
||||
const insets = useSafeAreaInsets()
|
||||
const { hostId } = useLocalSearchParams<{ hostId: string }>()
|
||||
@@ -56,8 +58,8 @@ export function MobileAccountsScreen() {
|
||||
const rejectInvalidSnapshot = useCallback(() => {
|
||||
// Fail closed on unrecognized snapshot: stale data can expose finite reset for wrong account.
|
||||
setSnapshot(null)
|
||||
setError('Invalid accounts snapshot from host')
|
||||
}, [])
|
||||
setError(t('accounts.screen.invalidSnapshot', 'Invalid accounts snapshot from host'))
|
||||
}, [t])
|
||||
const {
|
||||
supported: codexResetSupported,
|
||||
resetting: resettingCodex,
|
||||
@@ -96,7 +98,7 @@ export function MobileAccountsScreen() {
|
||||
}
|
||||
const host = hosts.find((h) => h.id === hostId)
|
||||
if (!host) {
|
||||
setError('Host not found')
|
||||
setError(t('accounts.screen.hostNotFound', 'Host not found'))
|
||||
return
|
||||
}
|
||||
setHostName(host.name)
|
||||
@@ -104,7 +106,7 @@ export function MobileAccountsScreen() {
|
||||
return () => {
|
||||
stale = true
|
||||
}
|
||||
}, [hostId])
|
||||
}, [hostId, t])
|
||||
|
||||
// Stream desktop poll/account-switch updates, falling back to one-shot accounts.list on subscription error.
|
||||
useEffect(() => {
|
||||
@@ -166,27 +168,25 @@ export function MobileAccountsScreen() {
|
||||
: codexTarget?.runtime === 'wsl'
|
||||
? 'accounts.selectCodexForTarget'
|
||||
: 'accounts.selectCodex'
|
||||
const title = t('accounts.screen.switchFailed', 'Could not switch account')
|
||||
try {
|
||||
// Use distinct WSL-targeted RPC: older hosts silently strip unknown fields, so version skew must fail before mutation.
|
||||
const params =
|
||||
codexTarget?.runtime === 'wsl' ? { accountId, target: codexTarget } : { accountId }
|
||||
const res = await client.sendRequest(method, params)
|
||||
if (!res.ok) {
|
||||
setNotice({ title: 'Could not switch account', message: res.error.message })
|
||||
setNotice({ title, message: res.error.message })
|
||||
} else {
|
||||
// Refresh optimistically so disconnected subscription cannot delay feedback.
|
||||
await refresh()
|
||||
}
|
||||
} catch (e) {
|
||||
setNotice({
|
||||
title: 'Could not switch account',
|
||||
message: e instanceof Error ? e.message : String(e)
|
||||
})
|
||||
setNotice({ title, message: e instanceof Error ? e.message : String(e) })
|
||||
} finally {
|
||||
setBusyAccountId(null)
|
||||
}
|
||||
},
|
||||
[client, refresh, snapshot]
|
||||
[client, refresh, snapshot, t]
|
||||
)
|
||||
|
||||
const renderProviderSection = (provider: ProviderKey, title: string) => {
|
||||
@@ -217,20 +217,22 @@ export function MobileAccountsScreen() {
|
||||
disabled={busyAccountId !== null || resettingCodex || connState !== 'connected'}
|
||||
>
|
||||
<View style={styles.rowMain}>
|
||||
<Text style={styles.rowTitle}>System default</Text>
|
||||
<Text style={styles.rowSubtitle}>Use the agent's own login</Text>
|
||||
<Text style={styles.rowTitle}>{t('accounts.screen.default', 'System default')}</Text>
|
||||
<Text style={styles.rowSubtitle}>
|
||||
{t('accounts.screen.agentLogin', "Use the agent's own login")}
|
||||
</Text>
|
||||
{/* Show system-default login usage for users without managed accounts. */}
|
||||
{activeAccountId === null && hasActiveProviderUsage(activeUsage) ? (
|
||||
<View style={styles.usageRow}>
|
||||
<UsageBar
|
||||
label="5h"
|
||||
label={t('accounts.screen.sessionWindow', '5h')}
|
||||
usedPercent={activeSessionBar.usedPercent}
|
||||
unavailable={activeSessionBar.unavailable}
|
||||
loading={activeSessionBar.loading}
|
||||
resetText={getWindowResetLabel(activeUsage, 'session', now)}
|
||||
/>
|
||||
<UsageBar
|
||||
label="7d"
|
||||
label={t('accounts.screen.weeklyWindow', '7d')}
|
||||
usedPercent={activeWeeklyBar.usedPercent}
|
||||
unavailable={activeWeeklyBar.unavailable}
|
||||
loading={activeWeeklyBar.loading}
|
||||
@@ -278,14 +280,14 @@ export function MobileAccountsScreen() {
|
||||
</Text>
|
||||
<View style={styles.usageRow}>
|
||||
<UsageBar
|
||||
label="5h"
|
||||
label={t('accounts.screen.sessionWindow', '5h')}
|
||||
usedPercent={sessionBar.usedPercent}
|
||||
unavailable={sessionBar.unavailable}
|
||||
loading={sessionBar.loading}
|
||||
resetText={getWindowResetLabel(usage, 'session', now)}
|
||||
/>
|
||||
<UsageBar
|
||||
label="7d"
|
||||
label={t('accounts.screen.weeklyWindow', '7d')}
|
||||
usedPercent={weeklyBar.usedPercent}
|
||||
unavailable={weeklyBar.unavailable}
|
||||
loading={weeklyBar.loading}
|
||||
@@ -330,12 +332,12 @@ export function MobileAccountsScreen() {
|
||||
style={styles.backButton}
|
||||
onPress={() => router.back()}
|
||||
accessibilityRole="button"
|
||||
accessibilityLabel="Back"
|
||||
accessibilityLabel={t('common.back', 'Back')}
|
||||
>
|
||||
<ChevronLeft size={22} color={colors.textPrimary} />
|
||||
</Pressable>
|
||||
<View style={styles.titleWrap}>
|
||||
<Text style={styles.heading}>Accounts</Text>
|
||||
<Text style={styles.heading}>{t('accounts.screen.title', 'Accounts')}</Text>
|
||||
{hostName ? (
|
||||
<Text style={styles.subheading} numberOfLines={1}>
|
||||
{hostName}
|
||||
@@ -348,7 +350,7 @@ export function MobileAccountsScreen() {
|
||||
disabled={!client || refreshing || connState !== 'connected'}
|
||||
hitSlop={8}
|
||||
accessibilityRole="button"
|
||||
accessibilityLabel="Refresh accounts"
|
||||
accessibilityLabel={t('accounts.screen.refreshAccounts', 'Refresh accounts')}
|
||||
>
|
||||
{refreshing ? (
|
||||
<ActivityIndicator size="small" color={colors.textSecondary} />
|
||||
@@ -364,7 +366,11 @@ export function MobileAccountsScreen() {
|
||||
{connState !== 'connected' && !snapshot ? (
|
||||
<View style={styles.placeholder}>
|
||||
<ActivityIndicator color={colors.textSecondary} />
|
||||
<Text style={styles.placeholderText}>Connecting to {hostName || 'host'}…</Text>
|
||||
<Text style={styles.placeholderText}>
|
||||
{hostName
|
||||
? t('accounts.screen.connectingTo', 'Connecting to {{name}}…', { name: hostName })
|
||||
: t('accounts.screen.connecting', 'Connecting to host…')}
|
||||
</Text>
|
||||
</View>
|
||||
) : error && !snapshot ? (
|
||||
<View style={styles.placeholder}>
|
||||
@@ -373,7 +379,9 @@ export function MobileAccountsScreen() {
|
||||
) : !snapshot ? (
|
||||
<View style={styles.placeholder}>
|
||||
<ActivityIndicator color={colors.textSecondary} />
|
||||
<Text style={styles.placeholderText}>Loading accounts…</Text>
|
||||
<Text style={styles.placeholderText}>
|
||||
{t('accounts.screen.loadingAccounts', 'Loading accounts…')}
|
||||
</Text>
|
||||
</View>
|
||||
) : (
|
||||
<>
|
||||
@@ -382,7 +390,10 @@ export function MobileAccountsScreen() {
|
||||
<View style={styles.footerHint}>
|
||||
<User size={14} color={colors.textMuted} />
|
||||
<Text style={styles.footerHintText}>
|
||||
Add or re-authenticate accounts from desktop Settings → Accounts.
|
||||
{t(
|
||||
'accounts.screen.manageAccountsHint',
|
||||
'Add or re-authenticate accounts from desktop Settings → Accounts.'
|
||||
)}
|
||||
</Text>
|
||||
</View>
|
||||
</>
|
||||
@@ -392,7 +403,7 @@ export function MobileAccountsScreen() {
|
||||
visible={codexResetConfirmation !== null}
|
||||
title={codexResetConfirmation?.title ?? ''}
|
||||
message={codexResetConfirmation?.message}
|
||||
confirmLabel="Use reset"
|
||||
confirmLabel={t('accounts.screen.useReset', 'Use reset')}
|
||||
onConfirm={acceptCodexReset}
|
||||
onCancel={cancelCodexReset}
|
||||
/>
|
||||
@@ -400,7 +411,7 @@ export function MobileAccountsScreen() {
|
||||
visible={notice !== null}
|
||||
title={notice?.title ?? ''}
|
||||
message={notice?.message}
|
||||
confirmLabel="OK"
|
||||
confirmLabel={t('common.ok', 'OK')}
|
||||
cancelLabel={null}
|
||||
onCancel={() => setNotice(null)}
|
||||
/>
|
||||
|
||||
@@ -3,6 +3,7 @@ import { ActivityIndicator, Pressable, RefreshControl, SectionList, Text, View }
|
||||
import { Play } from 'lucide-react-native'
|
||||
import { colors } from '../theme/mobile-theme'
|
||||
import { useMobileColorScheme } from '../theme/use-mobile-color-scheme'
|
||||
import { useMobileTranslation } from '../i18n/use-mobile-translation'
|
||||
import { MobileAgentIcon } from '../components/MobileAgentIcon'
|
||||
import { recentSessionConversationTurns } from '../../../src/shared/ai-vault-session-display'
|
||||
import type { AiVaultSession } from '../../../src/shared/ai-vault-types'
|
||||
@@ -33,6 +34,7 @@ export function MobileAgentSessionHistoryList({
|
||||
onRefresh
|
||||
}: Props) {
|
||||
const colorScheme = useMobileColorScheme()
|
||||
useMobileTranslation()
|
||||
const [expandedId, setExpandedId] = useState<string | null>(null)
|
||||
|
||||
const toggleExpanded = useCallback((id: string) => {
|
||||
@@ -106,6 +108,8 @@ function AgentHistoryCardRow({
|
||||
onPress: () => void
|
||||
}) {
|
||||
useMobileColorScheme()
|
||||
const t = useMobileTranslation()
|
||||
const count = card.messageCount
|
||||
const previewTurns = useMemo(
|
||||
() => (expanded && session ? recentSessionConversationTurns(session, PREVIEW_TURN_LIMIT) : []),
|
||||
[expanded, session]
|
||||
@@ -131,11 +135,15 @@ function AgentHistoryCardRow({
|
||||
<View style={styles.cardMetaRow}>
|
||||
<Text style={styles.cardMetaText}>{card.agentLabel}</Text>
|
||||
<Text style={styles.cardMetaText}>
|
||||
{card.messageCount} {card.messageCount === 1 ? 'message' : 'messages'}
|
||||
{count === 1
|
||||
? t('agentHistory.sessionHistoryList.messages_one', '{{count}} message', { count })
|
||||
: t('agentHistory.sessionHistoryList.messages_other', '{{count}} messages', { count })}
|
||||
</Text>
|
||||
{showCurrentWorktreeBadge && card.isCurrentWorktree ? (
|
||||
<View style={styles.currentBadge}>
|
||||
<Text style={styles.currentBadgeText}>current worktree</Text>
|
||||
<Text style={styles.currentBadgeText}>
|
||||
{t('agentHistory.sessionHistoryList.currentWorktree', 'current worktree')}
|
||||
</Text>
|
||||
</View>
|
||||
) : null}
|
||||
{session && onResume ? (
|
||||
@@ -154,7 +162,10 @@ function AgentHistoryCardRow({
|
||||
disabled={resumeActionState?.disabled}
|
||||
hitSlop={8}
|
||||
accessibilityRole="button"
|
||||
accessibilityLabel="Resume agent session"
|
||||
accessibilityLabel={t(
|
||||
'agentHistory.sessionHistoryList.resumeSession',
|
||||
'Resume agent session'
|
||||
)}
|
||||
>
|
||||
{resumeActionState?.loading ? (
|
||||
<ActivityIndicator size="small" color={colors.textPrimary} />
|
||||
@@ -168,7 +179,13 @@ function AgentHistoryCardRow({
|
||||
<View style={styles.preview}>
|
||||
{previewTurns.map((turn, index) => (
|
||||
<View key={`${card.id}-turn-${index}`} style={styles.previewTurn}>
|
||||
<Text style={styles.previewRole}>{turn.role}</Text>
|
||||
<Text style={styles.previewRole}>
|
||||
{turn.role === 'user'
|
||||
? t('agentHistory.sessionHistoryList.userRole', 'user')
|
||||
: turn.role === 'assistant'
|
||||
? t('agentHistory.sessionHistoryList.assistantRole', 'assistant')
|
||||
: turn.role}
|
||||
</Text>
|
||||
<Text style={styles.previewText}>{turn.text}</Text>
|
||||
</View>
|
||||
))}
|
||||
|
||||
@@ -31,6 +31,8 @@ import { buildMobileAgentHistoryResumeActionState } from './agent-history-sessio
|
||||
import { styles } from './agent-history-styles'
|
||||
import { useNow } from '../hooks/use-now'
|
||||
import { useMobileResumeOperationOwnership } from './use-mobile-resume-operation-ownership'
|
||||
import type { Translate } from '../i18n/mobile-i18n'
|
||||
import { useMobileTranslation } from '../i18n/use-mobile-translation'
|
||||
|
||||
export type MobileAgentSessionHistoryPanelProps = {
|
||||
hostId: string
|
||||
@@ -39,11 +41,13 @@ export type MobileAgentSessionHistoryPanelProps = {
|
||||
origin?: string
|
||||
}
|
||||
|
||||
const SCOPE_TABS: { scope: AiVaultScope; label: string }[] = [
|
||||
{ scope: 'workspace', label: 'Workspace' },
|
||||
{ scope: 'project', label: 'Project' },
|
||||
{ scope: 'all', label: 'All' }
|
||||
]
|
||||
function scopeTabs(t: Translate): { scope: AiVaultScope; label: string }[] {
|
||||
return [
|
||||
{ scope: 'workspace', label: t('agentHistory.panel.workspace', 'Workspace') },
|
||||
{ scope: 'project', label: t('agentHistory.panel.project', 'Project') },
|
||||
{ scope: 'all', label: t('agentHistory.panel.all', 'All') }
|
||||
]
|
||||
}
|
||||
|
||||
export function MobileAgentSessionHistoryPanel(props: MobileAgentSessionHistoryPanelProps) {
|
||||
return <MobileAgentSessionHistoryPanelContent key={props.hostId} {...props} />
|
||||
@@ -56,6 +60,7 @@ function MobileAgentSessionHistoryPanelContent({
|
||||
origin = ''
|
||||
}: MobileAgentSessionHistoryPanelProps) {
|
||||
useMobileColorScheme()
|
||||
const t = useMobileTranslation()
|
||||
// Use handoff, not useRouter: resumed session is a native route outside this single-document page.
|
||||
const router = useWorkspaceRouteHandoff()
|
||||
const sessionBelow = useSessionScreenHandlers(hostId, worktreeId)
|
||||
@@ -125,6 +130,7 @@ function MobileAgentSessionHistoryPanelContent({
|
||||
|
||||
const sessions = screenState.kind === 'ready' ? screenState.sessions : EMPTY_SESSIONS
|
||||
const issues = screenState.kind === 'ready' ? screenState.issues : EMPTY_ISSUES
|
||||
const count = issues.length
|
||||
const sessionsById = useMemo(
|
||||
() => new Map(sessions.map((session) => [session.id, session])),
|
||||
[sessions]
|
||||
@@ -138,7 +144,7 @@ function MobileAgentSessionHistoryPanelContent({
|
||||
activeWorktreePath,
|
||||
now
|
||||
}),
|
||||
[sessions, query, scope, scopeFilterPaths, activeWorktreePath, now]
|
||||
[sessions, query, scope, scopeFilterPaths, activeWorktreePath, now, t]
|
||||
)
|
||||
|
||||
const resumeHost = useMemo(
|
||||
@@ -161,12 +167,14 @@ function MobileAgentSessionHistoryPanelContent({
|
||||
return
|
||||
}
|
||||
if (!client || connState !== 'connected') {
|
||||
setResumeMessage('Waiting for host...')
|
||||
setResumeMessage(t('agentHistory.panel.waitingForHost', 'Waiting for host...'))
|
||||
triggerError()
|
||||
return
|
||||
}
|
||||
if (!session.sessionId) {
|
||||
setResumeMessage('This session is missing a resume id.')
|
||||
setResumeMessage(
|
||||
t('agentHistory.panel.missingResumeId', 'This session is missing a resume id.')
|
||||
)
|
||||
triggerError()
|
||||
return
|
||||
}
|
||||
@@ -203,7 +211,9 @@ function MobileAgentSessionHistoryPanelContent({
|
||||
target.terminalPlatform
|
||||
)
|
||||
if (!platform) {
|
||||
setResumeMessage('Unable to determine host platform.')
|
||||
setResumeMessage(
|
||||
t('agentHistory.panel.unknownHostPlatform', 'Unable to determine host platform.')
|
||||
)
|
||||
triggerError()
|
||||
return
|
||||
}
|
||||
@@ -234,7 +244,7 @@ function MobileAgentSessionHistoryPanelContent({
|
||||
} catch {
|
||||
return
|
||||
}
|
||||
setResumeMessage('Agent session queued.')
|
||||
setResumeMessage(t('agentHistory.panel.sessionQueued', 'Agent session queued.'))
|
||||
// From a session screen the same workspace is the screen below; otherwise the session takes this screen's place.
|
||||
if (origin === 'session' && target.worktreeId === worktreeId) {
|
||||
sessionBelow?.onResumedTerminal(resumed.terminal)
|
||||
@@ -246,7 +256,11 @@ function MobileAgentSessionHistoryPanelContent({
|
||||
}
|
||||
} catch (err) {
|
||||
triggerError()
|
||||
setResumeMessage(err instanceof Error ? err.message : 'Failed to resume session.')
|
||||
setResumeMessage(
|
||||
err instanceof Error
|
||||
? err.message
|
||||
: t('agentHistory.panel.resumeFailed', 'Failed to resume session.')
|
||||
)
|
||||
} finally {
|
||||
resumeLaunchInFlightRef.current = false
|
||||
setResumingSessionId(null)
|
||||
@@ -263,7 +277,8 @@ function MobileAgentSessionHistoryPanelContent({
|
||||
router,
|
||||
worktreeId,
|
||||
worktrees,
|
||||
claimResumeOwnership
|
||||
claimResumeOwnership,
|
||||
t
|
||||
]
|
||||
)
|
||||
|
||||
@@ -276,13 +291,13 @@ function MobileAgentSessionHistoryPanelContent({
|
||||
onPress={() => router.back()}
|
||||
hitSlop={8}
|
||||
accessibilityRole="button"
|
||||
accessibilityLabel="Back"
|
||||
accessibilityLabel={t('common.back', 'Back')}
|
||||
>
|
||||
<ChevronLeft size={22} color={colors.textSecondary} strokeWidth={2.2} />
|
||||
</Pressable>
|
||||
<View style={styles.titleBlock}>
|
||||
<Text style={styles.title} numberOfLines={1}>
|
||||
Agent Session History
|
||||
{t('agentHistory.panel.title', 'Agent Session History')}
|
||||
</Text>
|
||||
<Text style={styles.meta} numberOfLines={1}>
|
||||
{worktreeLabel}
|
||||
@@ -292,7 +307,7 @@ function MobileAgentSessionHistoryPanelContent({
|
||||
style={({ pressed }) => [styles.refreshButton, pressed && styles.refreshButtonPressed]}
|
||||
onPress={() => void onRefresh()}
|
||||
hitSlop={8}
|
||||
accessibilityLabel="Refresh agent sessions"
|
||||
accessibilityLabel={t('agentHistory.panel.refreshSessions', 'Refresh agent sessions')}
|
||||
>
|
||||
<RefreshCw size={18} color={colors.textSecondary} strokeWidth={2.1} />
|
||||
</Pressable>
|
||||
@@ -305,25 +320,32 @@ function MobileAgentSessionHistoryPanelContent({
|
||||
</View>
|
||||
) : screenState.kind === 'unsupported' ? (
|
||||
<View style={styles.state}>
|
||||
<Text style={styles.stateTitle}>Agent Session History Unavailable</Text>
|
||||
<Text style={styles.stateTitle}>
|
||||
{t('agentHistory.panel.unavailable', 'Agent Session History Unavailable')}
|
||||
</Text>
|
||||
<Text style={styles.stateText}>
|
||||
Update Orca on this host to browse agent session history.
|
||||
{t(
|
||||
'agentHistory.panel.updateHost',
|
||||
'Update Orca on this host to browse agent session history.'
|
||||
)}
|
||||
</Text>
|
||||
</View>
|
||||
) : screenState.kind === 'error' ? (
|
||||
<View style={styles.state}>
|
||||
<Text style={styles.stateTitle}>Unable to Load</Text>
|
||||
<Text style={styles.stateTitle}>
|
||||
{t('agentHistory.panel.loadFailed', 'Unable to Load')}
|
||||
</Text>
|
||||
<Text style={styles.stateText}>{screenState.message}</Text>
|
||||
{retry ? (
|
||||
<Pressable style={styles.retryButton} onPress={retry}>
|
||||
<Text style={styles.retryText}>Retry</Text>
|
||||
<Text style={styles.retryText}>{t('common.retry', 'Retry')}</Text>
|
||||
</Pressable>
|
||||
) : null}
|
||||
</View>
|
||||
) : (
|
||||
<>
|
||||
<View style={styles.scopeTabs}>
|
||||
{SCOPE_TABS.map((tab) => {
|
||||
{scopeTabs(t).map((tab) => {
|
||||
const active = scope === tab.scope
|
||||
return (
|
||||
<Pressable
|
||||
@@ -343,7 +365,7 @@ function MobileAgentSessionHistoryPanelContent({
|
||||
style={styles.searchInput}
|
||||
value={query}
|
||||
onChangeText={setQuery}
|
||||
placeholder="Search sessions, repo:, path:"
|
||||
placeholder={t('agentHistory.panel.placeholder', 'Search sessions, repo:, path:')}
|
||||
placeholderTextColor={colors.textMuted}
|
||||
autoCapitalize="none"
|
||||
autoCorrect={false}
|
||||
@@ -352,7 +374,13 @@ function MobileAgentSessionHistoryPanelContent({
|
||||
{issues.length > 0 ? (
|
||||
<View style={styles.noticeBanner}>
|
||||
<Text style={styles.noticeText}>
|
||||
{issues.length} {issues.length === 1 ? 'transcript' : 'transcripts'} skipped
|
||||
{count === 1
|
||||
? t('agentHistory.panel.skipped_one', '{{count}} transcript skipped', {
|
||||
count
|
||||
})
|
||||
: t('agentHistory.panel.skipped_other', '{{count}} transcripts skipped', {
|
||||
count
|
||||
})}
|
||||
</Text>
|
||||
</View>
|
||||
) : null}
|
||||
@@ -363,9 +391,13 @@ function MobileAgentSessionHistoryPanelContent({
|
||||
) : null}
|
||||
{sections.length === 0 ? (
|
||||
<View style={styles.state}>
|
||||
<Text style={styles.stateTitle}>No agent sessions</Text>
|
||||
<Text style={styles.stateTitle}>
|
||||
{t('agentHistory.panel.noSessions', 'No agent sessions')}
|
||||
</Text>
|
||||
<Text style={styles.stateText}>
|
||||
{query ? 'No sessions match your search.' : 'No past agent sessions in this scope.'}
|
||||
{query
|
||||
? t('agentHistory.panel.noSearchResults', 'No sessions match your search.')
|
||||
: t('agentHistory.panel.emptyScope', 'No past agent sessions in this scope.')}
|
||||
</Text>
|
||||
</View>
|
||||
) : (
|
||||
|
||||
@@ -14,6 +14,7 @@ import { normalizeExecutionHostId } from '../../../src/shared/execution-host'
|
||||
import { getFolderWorkspaceWorktreeHostId } from '../../../src/shared/folder-workspace-worktree'
|
||||
import { parseWorkspaceKey } from '../../../src/shared/workspace-scope'
|
||||
import type { Worktree } from '../worktree/workspace-list-types'
|
||||
import { translate } from '../i18n/mobile-i18n'
|
||||
import {
|
||||
canJumpToAiVaultSessionWorktree,
|
||||
resolveAiVaultSessionWorktreeInfo,
|
||||
@@ -35,12 +36,21 @@ export type MobileAiVaultSessionResumeTarget =
|
||||
|
||||
function mobileAiVaultResumeTargetBlockMessage(status: AiVaultResumeTargetStatus): string {
|
||||
if (status === 'runtime') {
|
||||
return 'Resume from history is not available in runtime-hosted workspaces.'
|
||||
return translate(
|
||||
'agentHistory.resumeTarget.runtimeUnavailable',
|
||||
'Resume from history is not available in runtime-hosted workspaces.'
|
||||
)
|
||||
}
|
||||
if (status === 'ssh' || status === 'local') {
|
||||
return 'This session belongs to a different host. Open a workspace on the same host to resume it.'
|
||||
return translate(
|
||||
'agentHistory.resumeTarget.differentHost',
|
||||
'This session belongs to a different host. Open a workspace on the same host to resume it.'
|
||||
)
|
||||
}
|
||||
return 'Open a workspace before resuming a session.'
|
||||
return translate(
|
||||
'agentHistory.resumeTarget.openWorkspace',
|
||||
'Open a workspace before resuming a session.'
|
||||
)
|
||||
}
|
||||
|
||||
/** Desktop's namespace rule from what the phone sees: the host's terminal platform already reflects the project runtime. */
|
||||
|
||||
@@ -3,6 +3,7 @@ import { latestSessionConversationTurn } from '../../../src/shared/ai-vault-sess
|
||||
import { aiVaultAgentLabel } from '../../../src/shared/ai-vault-types'
|
||||
import type { AiVaultSession } from '../../../src/shared/ai-vault-types'
|
||||
import { formatTimeAgo } from '../worktree/agent-row-display'
|
||||
import { translate } from '../i18n/mobile-i18n'
|
||||
|
||||
export type MobileAgentHistoryCard = {
|
||||
id: string
|
||||
@@ -42,7 +43,8 @@ export function buildMobileAgentHistoryCard(
|
||||
id: session.id,
|
||||
agent: session.agent,
|
||||
agentLabel: aiVaultAgentLabel(session.agent),
|
||||
title: session.title || 'Untitled session',
|
||||
title:
|
||||
session.title || translate('agentHistory.sessionCard.untitledSession', 'Untitled session'),
|
||||
lastMessage: latestTurn?.text.trim() ?? '',
|
||||
messageCount: session.messageCount,
|
||||
timeAgo: Number.isFinite(updatedAtMs) ? formatTimeAgo(updatedAtMs, now) : '',
|
||||
|
||||
@@ -29,6 +29,7 @@ import {
|
||||
} from './mobile-agent-history-operations'
|
||||
import { interpretOrThrowRefusalMessage } from '../transport/rpc-refusal-message'
|
||||
import { MOBILE_AI_VAULT_CAPABILITY } from './agent-history-capability'
|
||||
import { useMobileTranslation } from '../i18n/use-mobile-translation'
|
||||
|
||||
export { MOBILE_AI_VAULT_CAPABILITY }
|
||||
|
||||
@@ -61,6 +62,7 @@ export type MobileAgentHistoryStateParams = {
|
||||
}
|
||||
|
||||
export function useMobileAgentHistoryState(params: MobileAgentHistoryStateParams) {
|
||||
const t = useMobileTranslation()
|
||||
const { hostId, worktreeId, worktrees, worktreesLoaded } = params
|
||||
const { client, state: connState } = useWorkspaceClient(hostId)
|
||||
const forceReconnect = useForceReconnect()
|
||||
@@ -145,10 +147,9 @@ export function useMobileAgentHistoryState(params: MobileAgentHistoryStateParams
|
||||
if (!client || connState !== 'connected') {
|
||||
if (isCurrent()) {
|
||||
setHostStatusProbe(null)
|
||||
const message = t('agentHistory.useAgentHistoryState.waitingForHost', 'Waiting for host…')
|
||||
// Keep stale rows visible during reconnect reloads instead of replacing them with full-screen error, matching host list.
|
||||
setScreenState((prev) =>
|
||||
prev.kind === 'ready' ? prev : { kind: 'error', message: 'Waiting for host…' }
|
||||
)
|
||||
setScreenState((prev) => (prev.kind === 'ready' ? prev : { kind: 'error', message }))
|
||||
}
|
||||
return
|
||||
}
|
||||
@@ -163,7 +164,7 @@ export function useMobileAgentHistoryState(params: MobileAgentHistoryStateParams
|
||||
}
|
||||
const status = interpretOrThrowRefusalMessage(
|
||||
() => agentHistoryHostStatusRead.interpret(statusReply),
|
||||
'Unable to reach host'
|
||||
t('agentHistory.useAgentHistoryState.hostUnreachable', 'Unable to reach host')
|
||||
)
|
||||
setHostStatusProbe({ hostId, client, generation: clientGeneration, result: status })
|
||||
if (!status.capabilities?.includes(MOBILE_AI_VAULT_CAPABILITY)) {
|
||||
@@ -192,7 +193,7 @@ export function useMobileAgentHistoryState(params: MobileAgentHistoryStateParams
|
||||
}
|
||||
const result = interpretOrThrowRefusalMessage(
|
||||
() => agentHistorySessionScan.interpret(reply),
|
||||
'Unable to load agent sessions'
|
||||
t('agentHistory.useAgentHistoryState.loadFailed', 'Unable to load agent sessions')
|
||||
)
|
||||
setScreenState({
|
||||
kind: 'ready',
|
||||
@@ -207,7 +208,10 @@ export function useMobileAgentHistoryState(params: MobileAgentHistoryStateParams
|
||||
if (!isCurrent()) {
|
||||
return
|
||||
}
|
||||
const message = err instanceof Error ? err.message : 'Unable to load agent sessions'
|
||||
const message =
|
||||
err instanceof Error
|
||||
? err.message
|
||||
: t('agentHistory.useAgentHistoryState.loadFailed', 'Unable to load agent sessions')
|
||||
setHostStatusProbe(null)
|
||||
setScreenState({ kind: 'error', message })
|
||||
} finally {
|
||||
@@ -219,7 +223,17 @@ export function useMobileAgentHistoryState(params: MobileAgentHistoryStateParams
|
||||
}
|
||||
}
|
||||
},
|
||||
[activeWorktree, client, clientGeneration, connState, feed, hostId, worktrees, worktreesLoaded]
|
||||
[
|
||||
activeWorktree,
|
||||
client,
|
||||
clientGeneration,
|
||||
connState,
|
||||
feed,
|
||||
hostId,
|
||||
worktrees,
|
||||
worktreesLoaded,
|
||||
t
|
||||
]
|
||||
)
|
||||
|
||||
// Initial/reconnect/scope loads reuse host’s 15s cache; only explicit refresh bypasses it.
|
||||
|
||||
@@ -5,6 +5,7 @@ import type { ExecutionHostId } from '../../../src/shared/execution-host'
|
||||
import { workspaceRouteHref } from '../navigation/workspace-execution-host'
|
||||
import { MOBILE_AI_VAULT_CAPABILITY } from './agent-history-capability'
|
||||
import { MobileAgentSessionHistoryIcon } from './MobileAgentSessionHistoryIcon'
|
||||
import { translate } from '../i18n/mobile-i18n'
|
||||
|
||||
type Args = {
|
||||
hostId: string
|
||||
@@ -25,7 +26,7 @@ export function buildWorktreeNavigationActions(args: Args): ActionSheetAction[]
|
||||
const executionHost = args.executionHost
|
||||
const actions: ActionSheetAction[] = [
|
||||
{
|
||||
label: 'Source Control',
|
||||
label: translate('agentHistory.worktreeNavigationActions.sourceControl', 'Source Control'),
|
||||
icon: GitBranch,
|
||||
onPress: () => {
|
||||
const params = new URLSearchParams({ name: args.worktreeName, origin: 'host' })
|
||||
@@ -41,7 +42,10 @@ export function buildWorktreeNavigationActions(args: Args): ActionSheetAction[]
|
||||
]
|
||||
if (args.hostCapabilities.includes(MOBILE_AI_VAULT_CAPABILITY)) {
|
||||
actions.push({
|
||||
label: 'Agent Session History',
|
||||
label: translate(
|
||||
'agentHistory.worktreeNavigationActions.agentSessionHistory',
|
||||
'Agent Session History'
|
||||
),
|
||||
renderIcon: () =>
|
||||
MobileAgentSessionHistoryIcon({ size: 16, color: colors.textSecondary, strokeWidth: 2 }),
|
||||
onPress: () => {
|
||||
|
||||
@@ -10,7 +10,7 @@ import type { RpcClient } from '../transport/rpc-client'
|
||||
import { triggerError, triggerSuccess } from '../platform/haptics'
|
||||
import { useDictationSetupPoller } from '../dictation/use-dictation-setup-poller'
|
||||
import {
|
||||
CLOUD_MODEL_NEEDS_KEY,
|
||||
cloudModelNeedsKey,
|
||||
dictationModelRowAction,
|
||||
downloadDictationModel,
|
||||
fetchDictationSetup,
|
||||
@@ -176,7 +176,7 @@ export function MobileDictationSetupSheet({ visible, client, onClose, onReady }:
|
||||
</Text>
|
||||
</View>
|
||||
{action === 'needs-api-key' ? (
|
||||
<Text style={styles.modelStateText}>{CLOUD_MODEL_NEEDS_KEY}</Text>
|
||||
<Text style={styles.modelStateText}>{cloudModelNeedsKey()}</Text>
|
||||
) : action === 'in-use' ? (
|
||||
<View style={styles.selectedTag}>
|
||||
<Check size={14} color={colors.statusGreen} strokeWidth={2.4} />
|
||||
|
||||
@@ -94,7 +94,7 @@ describe('MobileHostCard', () => {
|
||||
it('names the relay while the dial is still in flight', async () => {
|
||||
const lines = await renderCard(undefined, {
|
||||
state: 'connecting',
|
||||
verdict: { kind: 'normal', label: 'Connecting via Relay…' },
|
||||
verdict: { kind: 'normal', label: 'Connecting via Relay…', dialing: true },
|
||||
path: 'relay'
|
||||
})
|
||||
|
||||
@@ -105,7 +105,7 @@ describe('MobileHostCard', () => {
|
||||
it('names the relay while a failed direct dial is still retrying', async () => {
|
||||
const lines = await renderCard(undefined, {
|
||||
state: 'reconnecting',
|
||||
verdict: { kind: 'normal', label: 'Connecting via Relay…' },
|
||||
verdict: { kind: 'normal', label: 'Connecting via Relay…', dialing: true },
|
||||
path: 'relay'
|
||||
})
|
||||
|
||||
@@ -126,7 +126,7 @@ describe('MobileHostCard', () => {
|
||||
it('does not guess a direct path before the dial resolves', async () => {
|
||||
const lines = await renderCard(undefined, {
|
||||
state: 'connecting',
|
||||
verdict: { kind: 'normal', label: 'Connecting…' },
|
||||
verdict: { kind: 'normal', label: 'Connecting…', dialing: true },
|
||||
path: 'lan'
|
||||
})
|
||||
|
||||
|
||||
@@ -2,7 +2,10 @@ import { Monitor, MoreVertical } from 'lucide-react-native'
|
||||
import { Pressable, Text, View } from 'react-native'
|
||||
import type { ConnectionVerdict } from '../transport/connection-health'
|
||||
import { verdictDisplayLabel } from '../transport/connection-health'
|
||||
import { mobileConnectionPathLabel } from '../transport/mobile-connection-path-label'
|
||||
import {
|
||||
mobileConnectionPathAccessibilityLabel,
|
||||
mobileConnectionPathLabel
|
||||
} from '../transport/mobile-connection-path-label'
|
||||
import { useHostDisplay } from '../transport/use-host-display'
|
||||
import type { MobileConnectionPath } from '../transport/stable-logical-rpc-client'
|
||||
import type { ConnectionState, HostCatalogEntry, HostProfile } from '../transport/types'
|
||||
@@ -63,7 +66,7 @@ export function MobileHostCard(props: {
|
||||
`Open ${display.title}`,
|
||||
descriptorText,
|
||||
statusLabel,
|
||||
connectionPathLabel?.replace(' · ', ' via '),
|
||||
connectionPathLabel ? mobileConnectionPathAccessibilityLabel(props.path) : null,
|
||||
connected ? worktreeSummary?.replace(' · ', ', ') : null,
|
||||
discoveryHint,
|
||||
credentialHint,
|
||||
|
||||
@@ -10,9 +10,13 @@ import { statusDotColor } from './StatusDot'
|
||||
|
||||
describe('statusDotColor', () => {
|
||||
it('keeps Relay progress amber when the raw state is disconnected', () => {
|
||||
expect(statusDotColor('disconnected', { kind: 'normal', label: 'Connecting via Relay…' })).toBe(
|
||||
colors.statusAmber
|
||||
)
|
||||
expect(
|
||||
statusDotColor('disconnected', {
|
||||
kind: 'normal',
|
||||
label: 'Connecting via Relay…',
|
||||
dialing: true
|
||||
})
|
||||
).toBe(colors.statusAmber)
|
||||
})
|
||||
|
||||
it('keeps an idle disconnected host gray', () => {
|
||||
|
||||
@@ -4,7 +4,7 @@ import { colors, radii, spacing, typography } from '../theme/mobile-theme'
|
||||
import { themedStyleSheet } from '../theme/themed-styles'
|
||||
import { useMobileColorScheme } from '../theme/use-mobile-color-scheme'
|
||||
import {
|
||||
CLOUD_MODEL_NEEDS_KEY,
|
||||
cloudModelNeedsKey,
|
||||
dictationModelRowAction,
|
||||
isModelInFlight,
|
||||
type MobileSpeechModel,
|
||||
@@ -75,7 +75,7 @@ export function VoiceModelList({
|
||||
<Text style={styles.modelMeta}>{modelMeta(model)}</Text>
|
||||
</View>
|
||||
{action === 'needs-api-key' ? (
|
||||
<Text style={styles.modelStateText}>{CLOUD_MODEL_NEEDS_KEY}</Text>
|
||||
<Text style={styles.modelStateText}>{cloudModelNeedsKey()}</Text>
|
||||
) : action === 'in-use' || action === 'use' ? (
|
||||
<View style={styles.readyActions}>
|
||||
{action === 'in-use' ? (
|
||||
|
||||
@@ -24,7 +24,7 @@ vi.mock('lucide-react-native', () => ({
|
||||
|
||||
import { VoiceModelList } from './VoiceModelList'
|
||||
import {
|
||||
CLOUD_MODEL_NEEDS_KEY,
|
||||
cloudModelNeedsKey,
|
||||
type MobileSpeechModel,
|
||||
type MobileSpeechSetup
|
||||
} from '../dictation/mobile-dictation-setup'
|
||||
@@ -98,7 +98,7 @@ describe('VoiceModelList cloud models', () => {
|
||||
selectedModelId: '',
|
||||
models: [cloud('openai-gpt-4o-transcribe', 'not-downloaded')]
|
||||
})
|
||||
expect(texts).toContain(CLOUD_MODEL_NEEDS_KEY)
|
||||
expect(texts).toContain(cloudModelNeedsKey())
|
||||
expect(pressables).toHaveLength(0)
|
||||
})
|
||||
})
|
||||
|
||||
@@ -9,6 +9,7 @@ import { connectionDiagnosticsScreenStyles as styles } from './connection-diagno
|
||||
import type { ConnectionLogEntry, ConnectionState } from '../transport/types'
|
||||
import type { ConnectionDiagnosis } from './connection-diagnostics-analysis'
|
||||
import type { DiagnosticsSubmissionState } from './connection-diagnostics-screen-data'
|
||||
import { useMobileTranslation } from '../i18n/use-mobile-translation'
|
||||
|
||||
export function ConnectionDiagnosticsView({
|
||||
hostPicker,
|
||||
@@ -38,19 +39,30 @@ export function ConnectionDiagnosticsView({
|
||||
onBack: () => void
|
||||
}) {
|
||||
useMobileColorScheme()
|
||||
const t = useMobileTranslation()
|
||||
const insets = useSafeAreaInsets()
|
||||
const stateLabels: Record<ConnectionState, string> = {
|
||||
connecting: t('diagnostics.connectionDiagnosticsView.connecting', 'connecting'),
|
||||
handshaking: t('diagnostics.connectionDiagnosticsView.handshaking', 'handshaking'),
|
||||
connected: t('diagnostics.connectionDiagnosticsView.connected', 'connected'),
|
||||
disconnected: t('diagnostics.connectionDiagnosticsView.disconnected', 'disconnected'),
|
||||
reconnecting: t('diagnostics.connectionDiagnosticsView.reconnecting', 'reconnecting'),
|
||||
'auth-failed': t('diagnostics.connectionDiagnosticsView.authFailed', 'auth-failed')
|
||||
}
|
||||
return (
|
||||
<View style={[styles.container, { paddingTop: insets.top + spacing.sm }]}>
|
||||
<View style={styles.topRow}>
|
||||
<Pressable
|
||||
accessibilityRole="button"
|
||||
accessibilityLabel="Back"
|
||||
accessibilityLabel={t('common.back', 'Back')}
|
||||
style={styles.backButton}
|
||||
onPress={onBack}
|
||||
>
|
||||
<ChevronLeft size={22} color={colors.textSecondary} />
|
||||
</Pressable>
|
||||
<Text style={styles.heading}>Network diagnostics</Text>
|
||||
<Text style={styles.heading}>
|
||||
{t('diagnostics.connectionDiagnosticsView.title', 'Network diagnostics')}
|
||||
</Text>
|
||||
</View>
|
||||
|
||||
{hostPicker}
|
||||
@@ -58,8 +70,13 @@ export function ConnectionDiagnosticsView({
|
||||
<>
|
||||
<View style={styles.statusRow}>
|
||||
<Text style={styles.statusText}>
|
||||
{state}
|
||||
{reconnectAttempts > 0 ? ` · attempt ${reconnectAttempts}` : ''}
|
||||
{reconnectAttempts > 0
|
||||
? t(
|
||||
'diagnostics.connectionDiagnosticsView.stateWithAttempt',
|
||||
'{{state}} · attempt {{attempts}}',
|
||||
{ state: stateLabels[state], attempts: reconnectAttempts }
|
||||
)
|
||||
: stateLabels[state]}
|
||||
</Text>
|
||||
<Pressable style={styles.copyButton} onPress={() => void copyDiagnostics()}>
|
||||
{copied ? (
|
||||
@@ -67,19 +84,27 @@ export function ConnectionDiagnosticsView({
|
||||
) : (
|
||||
<Copy size={14} color={colors.textSecondary} />
|
||||
)}
|
||||
<Text style={styles.copyButtonText}>{copied ? 'Copied' : 'Copy report'}</Text>
|
||||
<Text style={styles.copyButtonText}>
|
||||
{copied
|
||||
? t('common.copied', 'Copied')
|
||||
: t('diagnostics.connectionDiagnosticsView.copyReport', 'Copy report')}
|
||||
</Text>
|
||||
</Pressable>
|
||||
</View>
|
||||
{diagnosis && (
|
||||
<View style={styles.diagnosisCard}>
|
||||
<Text style={styles.diagnosisHeading}>What this suggests</Text>
|
||||
<Text style={styles.diagnosisHeading}>
|
||||
{t('diagnostics.connectionDiagnosticsView.diagnosisHeading', 'What this suggests')}
|
||||
</Text>
|
||||
<Text style={styles.diagnosisText}>{diagnosis.likelyCause}</Text>
|
||||
<Text style={styles.diagnosisNext}>{diagnosis.nextStep}</Text>
|
||||
{diagnosis.reportability === 'orca-relay' && (
|
||||
<>
|
||||
<Text style={styles.privacyHint}>
|
||||
Sends a size-limited redacted report including host name, endpoint, versions,
|
||||
connection state, and events—never terminal contents or credentials.
|
||||
{t(
|
||||
'diagnostics.connectionDiagnosticsView.privacyHint',
|
||||
'Sends a size-limited redacted report including host name, endpoint, versions, connection state, and events—never terminal contents or credentials.'
|
||||
)}
|
||||
</Text>
|
||||
<Pressable
|
||||
style={styles.sendButton}
|
||||
@@ -93,12 +118,18 @@ export function ConnectionDiagnosticsView({
|
||||
)}
|
||||
<Text style={styles.sendButtonText}>
|
||||
{submissionState === 'sending'
|
||||
? 'Sending…'
|
||||
? t('diagnostics.connectionDiagnosticsView.sending', 'Sending…')
|
||||
: submissionState === 'sent'
|
||||
? 'Diagnostics sent'
|
||||
? t('diagnostics.connectionDiagnosticsView.sent', 'Diagnostics sent')
|
||||
: submissionState === 'failed'
|
||||
? 'Retry sending'
|
||||
: 'Send diagnostics to Orca'}
|
||||
? t(
|
||||
'diagnostics.connectionDiagnosticsView.retrySending',
|
||||
'Retry sending'
|
||||
)
|
||||
: t(
|
||||
'diagnostics.connectionDiagnosticsView.sendDiagnostics',
|
||||
'Send diagnostics to Orca'
|
||||
)}
|
||||
</Text>
|
||||
</Pressable>
|
||||
</>
|
||||
@@ -109,12 +140,17 @@ export function ConnectionDiagnosticsView({
|
||||
<ConnectionLog entries={[...entries]} title={hostName} fillAvailableHeight />
|
||||
) : (
|
||||
<Text style={styles.emptyText}>
|
||||
No connection events yet. Events appear as the app dials this host.
|
||||
{t(
|
||||
'diagnostics.connectionDiagnosticsView.noEvents',
|
||||
'No connection events yet. Events appear as the app dials this host.'
|
||||
)}
|
||||
</Text>
|
||||
)}
|
||||
</>
|
||||
) : (
|
||||
<Text style={styles.emptyText}>No paired hosts.</Text>
|
||||
<Text style={styles.emptyText}>
|
||||
{t('diagnostics.connectionDiagnosticsView.noHosts', 'No paired hosts.')}
|
||||
</Text>
|
||||
)}
|
||||
</View>
|
||||
)
|
||||
|
||||
@@ -10,12 +10,20 @@ import {
|
||||
type MobileWebBundleProbeState
|
||||
} from './use-mobile-web-bundle-probe'
|
||||
import type { HostProfile } from '../transport/types'
|
||||
import type { Translate } from '../i18n/mobile-i18n'
|
||||
import { useMobileTranslation } from '../i18n/use-mobile-translation'
|
||||
|
||||
function buttonLabel(state: MobileWebBundleProbeState, awaitingHost: boolean): string {
|
||||
function buttonLabel(
|
||||
state: MobileWebBundleProbeState,
|
||||
awaitingHost: boolean,
|
||||
t: Translate
|
||||
): string {
|
||||
if (state.status !== 'running') {
|
||||
return 'Fetch mobile web bundle'
|
||||
return t('diagnostics.mobileWebBundleProbeRow.fetchBundle', 'Fetch mobile web bundle')
|
||||
}
|
||||
return awaitingHost ? 'Connecting…' : 'Fetching bundle…'
|
||||
return awaitingHost
|
||||
? t('diagnostics.mobileWebBundleProbeRow.connecting', 'Connecting…')
|
||||
: t('diagnostics.mobileWebBundleProbeRow.fetching', 'Fetching bundle…')
|
||||
}
|
||||
|
||||
function ProbeLine({ label, detail, failed }: { label: string; detail: string; failed?: boolean }) {
|
||||
@@ -30,27 +38,46 @@ function ProbeLine({ label, detail, failed }: { label: string; detail: string; f
|
||||
|
||||
function ProbeResult({ state, hostName }: { state: MobileWebBundleProbeState; hostName: string }) {
|
||||
useMobileColorScheme()
|
||||
const t = useMobileTranslation()
|
||||
if (state.status === 'idle' || state.status === 'running') {
|
||||
return null
|
||||
}
|
||||
if (state.status === 'failed') {
|
||||
return (
|
||||
<View style={styles.section}>
|
||||
<ProbeLine label="Bundle" detail={state.detail} failed />
|
||||
<ProbeLine
|
||||
label={t('diagnostics.mobileWebBundleProbeRow.bundle', 'Bundle')}
|
||||
detail={state.detail}
|
||||
failed
|
||||
/>
|
||||
</View>
|
||||
)
|
||||
}
|
||||
return (
|
||||
<View style={styles.section}>
|
||||
<ProbeLine label="Host" detail={hostName} />
|
||||
<ProbeLine label={t('diagnostics.mobileWebBundleProbeRow.host', 'Host')} detail={hostName} />
|
||||
<View style={styles.separator} />
|
||||
<ProbeLine label="Build" detail={state.buildId.slice(0, 12)} />
|
||||
<ProbeLine
|
||||
label={t('diagnostics.mobileWebBundleProbeRow.build', 'Build')}
|
||||
detail={state.buildId.slice(0, 12)}
|
||||
/>
|
||||
<View style={styles.separator} />
|
||||
<ProbeLine label="Assets" detail={`${state.assetCount}`} />
|
||||
<ProbeLine
|
||||
label={t('diagnostics.mobileWebBundleProbeRow.assets', 'Assets')}
|
||||
detail={`${state.assetCount}`}
|
||||
/>
|
||||
<View style={styles.separator} />
|
||||
<ProbeLine label="Bytes" detail={`${state.totalBytes}`} />
|
||||
<ProbeLine
|
||||
label={t('diagnostics.mobileWebBundleProbeRow.bytes', 'Bytes')}
|
||||
detail={`${state.totalBytes}`}
|
||||
/>
|
||||
<View style={styles.separator} />
|
||||
<ProbeLine label="Elapsed" detail={`${state.elapsedMs} ms`} />
|
||||
<ProbeLine
|
||||
label={t('diagnostics.mobileWebBundleProbeRow.elapsed', 'Elapsed')}
|
||||
detail={t('diagnostics.mobileWebBundleProbeRow.elapsedMilliseconds', '{{elapsedMs}} ms', {
|
||||
elapsedMs: state.elapsedMs
|
||||
})}
|
||||
/>
|
||||
</View>
|
||||
)
|
||||
}
|
||||
@@ -59,6 +86,7 @@ function ProbeResult({ state, hostName }: { state: MobileWebBundleProbeState; ho
|
||||
// Troubleshoot has no selected host, so probe the first paired host and name it explicitly.
|
||||
export function MobileWebBundleProbeRow() {
|
||||
useMobileColorScheme()
|
||||
const t = useMobileTranslation()
|
||||
const [hosts, setHosts] = useState<readonly HostProfile[]>([])
|
||||
useEffect(() => {
|
||||
let stale = false
|
||||
@@ -91,9 +119,14 @@ export function MobileWebBundleProbeRow() {
|
||||
) : (
|
||||
<Package size={16} color={colors.textPrimary} />
|
||||
)}
|
||||
<Text style={styles.diagnosticButtonLabel}>{buttonLabel(state, awaitingHost)}</Text>
|
||||
<Text style={styles.diagnosticButtonLabel}>{buttonLabel(state, awaitingHost, t)}</Text>
|
||||
</Pressable>
|
||||
<ProbeResult state={state} hostName={host?.name ?? 'unknown host'} />
|
||||
<ProbeResult
|
||||
state={state}
|
||||
hostName={
|
||||
host?.name ?? t('diagnostics.mobileWebBundleProbeRow.unknownHost', 'unknown host')
|
||||
}
|
||||
/>
|
||||
</View>
|
||||
)
|
||||
}
|
||||
|
||||
@@ -7,6 +7,7 @@ import type {
|
||||
MobileWebShellUpdateFailureWall
|
||||
} from '../mobile-web-shell/mobile-web-shell-update-failure'
|
||||
import { formatTimeAgo } from '../worktree/agent-row-display'
|
||||
import { translate } from '../i18n/mobile-i18n'
|
||||
|
||||
// Describe only the recorded code; this row has no further evidence about the failure.
|
||||
const REASON_COPY: Record<MobileWebShellUpdateFailureReason, string> = {
|
||||
@@ -77,11 +78,20 @@ export function formatUpdateFailure(
|
||||
hostName: string,
|
||||
now: number
|
||||
): string {
|
||||
const ago = formatTimeAgo(failure.at, now)
|
||||
const when = ago === 'just now' ? ago : `${ago} ago`
|
||||
const reason =
|
||||
failure.hostCode === null ? REASON_COPY[failure.reason] : HOST_CODE_COPY[failure.hostCode]
|
||||
const offered =
|
||||
failure.offeredBuildId === null ? '' : ` (generation ${generation(failure.offeredBuildId)})`
|
||||
return `Last update from ${hostName} failed ${when}: ${reason}${offered}. ${outcomeCopy(failure)}`
|
||||
const options = { hostName, reason, offered, outcome: outcomeCopy(failure) }
|
||||
return now - failure.at < 60_000
|
||||
? translate(
|
||||
'diagnostics.webShellUpdateFailureCopy.failedJustNow',
|
||||
'Last update from {{hostName}} failed just now: {{reason}}{{offered}}. {{outcome}}',
|
||||
options
|
||||
)
|
||||
: translate(
|
||||
'diagnostics.webShellUpdateFailureCopy.failedAgo',
|
||||
'Last update from {{hostName}} failed {{time}} ago: {{reason}}{{offered}}. {{outcome}}',
|
||||
{ ...options, time: formatTimeAgo(failure.at, now) }
|
||||
)
|
||||
}
|
||||
|
||||
@@ -6,6 +6,7 @@ import { loadHosts } from '../transport/host-store'
|
||||
import { formatUpdateFailure } from './mobile-web-shell-update-failure-copy'
|
||||
import { useMobileColorScheme } from '../theme/use-mobile-color-scheme'
|
||||
import { troubleshootScreenStyles as styles } from './troubleshoot-screen-styles'
|
||||
import { useMobileTranslation } from '../i18n/use-mobile-translation'
|
||||
|
||||
type Line = { readonly hostId: string; readonly text: string }
|
||||
|
||||
@@ -26,6 +27,7 @@ export function updateFailureLines(
|
||||
// Release console output never reaches logcat; this row is the only update-failure evidence visible there. Mount only in shell-capable builds.
|
||||
export function MobileWebShellUpdateFailureRow() {
|
||||
useMobileColorScheme()
|
||||
const t = useMobileTranslation()
|
||||
const [lines, setLines] = useState<readonly Line[]>([])
|
||||
|
||||
useEffect(() => {
|
||||
@@ -41,14 +43,16 @@ export function MobileWebShellUpdateFailureRow() {
|
||||
return () => {
|
||||
stale = true
|
||||
}
|
||||
}, [])
|
||||
}, [t])
|
||||
|
||||
if (lines.length === 0) {
|
||||
return null
|
||||
}
|
||||
return (
|
||||
<View testID="mobile-web-shell-update-failures">
|
||||
<Text style={styles.sectionHeading}>Workspace updates</Text>
|
||||
<Text style={styles.sectionHeading}>
|
||||
{t('diagnostics.mobileWebShellUpdateFailureRow.title', 'Workspace updates')}
|
||||
</Text>
|
||||
<View style={styles.section}>
|
||||
<View style={styles.accordionBody}>
|
||||
{lines.map((line) => (
|
||||
|
||||
@@ -1,4 +1,5 @@
|
||||
import { WifiOff, Shield, Monitor, Clock, Globe, Bell, type LucideIcon } from 'lucide-react-native'
|
||||
import type { Translate } from '../i18n/mobile-i18n'
|
||||
|
||||
export type TroubleshootSection = {
|
||||
id: string
|
||||
@@ -7,76 +8,138 @@ export type TroubleshootSection = {
|
||||
steps: string[]
|
||||
}
|
||||
|
||||
export const troubleshootCommonIssues: TroubleshootSection[] = [
|
||||
{
|
||||
id: 'notifications',
|
||||
icon: Bell,
|
||||
title: 'Push Notifications',
|
||||
steps: [
|
||||
'Check that system settings allow Orca notifications and that Focus or Do Not Disturb is off.',
|
||||
'Try cellular or another Wi-Fi network. If alerts arrive after switching, your network may be delaying delivery.'
|
||||
]
|
||||
},
|
||||
export function troubleshootCommonIssues(t: Translate): TroubleshootSection[] {
|
||||
return [
|
||||
{
|
||||
id: 'notifications',
|
||||
icon: Bell,
|
||||
title: t('diagnostics.troubleshootCommonIssues.notificationsTitle', 'Push Notifications'),
|
||||
steps: [
|
||||
t(
|
||||
'diagnostics.troubleshootCommonIssues.allowNotifications',
|
||||
'Check that system settings allow Orca notifications and that Focus or Do Not Disturb is off.'
|
||||
),
|
||||
t(
|
||||
'diagnostics.troubleshootCommonIssues.notificationNetwork',
|
||||
'Try cellular or another Wi-Fi network. If alerts arrive after switching, your network may be delaying delivery.'
|
||||
)
|
||||
]
|
||||
},
|
||||
|
||||
{
|
||||
id: 'wifi',
|
||||
icon: WifiOff,
|
||||
title: 'Different WiFi Networks',
|
||||
steps: [
|
||||
'Both devices must be on the same LAN (unless connected through Tailscale).',
|
||||
'Ethernet and WiFi must share the same subnet.',
|
||||
'Try reconnecting WiFi on both devices.'
|
||||
]
|
||||
},
|
||||
{
|
||||
id: 'firewall',
|
||||
icon: Shield,
|
||||
title: 'Firewall Blocking Port 6768',
|
||||
steps: [
|
||||
'macOS: System Settings → Network → Firewall — allow Orca.',
|
||||
'Windows: Defender Firewall → Allow app — enable Orca for Private networks.',
|
||||
'Linux: sudo ufw allow 6768',
|
||||
'Corporate/school networks may block P2P — try a personal hotspot.'
|
||||
]
|
||||
},
|
||||
{
|
||||
id: 'desktop',
|
||||
icon: Monitor,
|
||||
title: 'Desktop App Not Running',
|
||||
steps: [
|
||||
'Orca must be open on your desktop to accept connections.',
|
||||
'Try restarting Orca — the companion server starts on launch.',
|
||||
'After an update, you may need to re-pair via QR code.'
|
||||
]
|
||||
},
|
||||
{
|
||||
id: 'timeout',
|
||||
icon: Clock,
|
||||
title: 'Connection Timeout',
|
||||
steps: [
|
||||
'Check WiFi signal strength on your phone.',
|
||||
'Go back to the host list and tap your host to retry.',
|
||||
'Restart both apps if timeouts persist.'
|
||||
]
|
||||
},
|
||||
{
|
||||
id: 'tailscale',
|
||||
icon: Globe,
|
||||
title: 'Tailscale Host Unreachable',
|
||||
steps: [
|
||||
'Host addresses like 100.x.x.x or *.ts.net connect through Tailscale — keep it ON.',
|
||||
'iOS/Android can silently wedge the tunnel: toggle Tailscale off and back on in the Tailscale app.',
|
||||
'Check the desktop is awake and shows as connected in your tailnet.',
|
||||
'Update the Tailscale app — recent releases fix reconnect bugs.'
|
||||
]
|
||||
},
|
||||
{
|
||||
id: 'vpn',
|
||||
icon: Shield,
|
||||
title: 'Other VPN Interference',
|
||||
steps: [
|
||||
'Non-Tailscale VPNs can route local traffic through a remote server.',
|
||||
'Disable that VPN or enable split tunneling / "Allow LAN".'
|
||||
]
|
||||
}
|
||||
]
|
||||
{
|
||||
id: 'wifi',
|
||||
icon: WifiOff,
|
||||
title: t('diagnostics.troubleshootCommonIssues.wifiTitle', 'Different WiFi Networks'),
|
||||
steps: [
|
||||
t(
|
||||
'diagnostics.troubleshootCommonIssues.sameNetwork',
|
||||
'Both devices must be on the same LAN (unless connected through Tailscale).'
|
||||
),
|
||||
t(
|
||||
'diagnostics.troubleshootCommonIssues.sameSubnet',
|
||||
'Ethernet and WiFi must share the same subnet.'
|
||||
),
|
||||
t(
|
||||
'diagnostics.troubleshootCommonIssues.reconnectWifi',
|
||||
'Try reconnecting WiFi on both devices.'
|
||||
)
|
||||
]
|
||||
},
|
||||
{
|
||||
id: 'firewall',
|
||||
icon: Shield,
|
||||
title: t('diagnostics.troubleshootCommonIssues.firewallTitle', 'Firewall Blocking Port 6768'),
|
||||
steps: [
|
||||
t(
|
||||
'diagnostics.troubleshootCommonIssues.macosFirewall',
|
||||
'macOS: System Settings → Network → Firewall — allow Orca.'
|
||||
),
|
||||
t(
|
||||
'diagnostics.troubleshootCommonIssues.windowsFirewall',
|
||||
'Windows: Defender Firewall → Allow app — enable Orca for Private networks.'
|
||||
),
|
||||
'Linux: sudo ufw allow 6768',
|
||||
t(
|
||||
'diagnostics.troubleshootCommonIssues.restrictedNetwork',
|
||||
'Corporate/school networks may block P2P — try a personal hotspot.'
|
||||
)
|
||||
]
|
||||
},
|
||||
{
|
||||
id: 'desktop',
|
||||
icon: Monitor,
|
||||
title: t('diagnostics.troubleshootCommonIssues.desktopTitle', 'Desktop App Not Running'),
|
||||
steps: [
|
||||
t(
|
||||
'diagnostics.troubleshootCommonIssues.openDesktop',
|
||||
'Orca must be open on your desktop to accept connections.'
|
||||
),
|
||||
t(
|
||||
'diagnostics.troubleshootCommonIssues.restartDesktop',
|
||||
'Try restarting Orca — the companion server starts on launch.'
|
||||
),
|
||||
t(
|
||||
'diagnostics.troubleshootCommonIssues.pairAfterUpdate',
|
||||
'After an update, you may need to re-pair via QR code.'
|
||||
)
|
||||
]
|
||||
},
|
||||
{
|
||||
id: 'timeout',
|
||||
icon: Clock,
|
||||
title: t('diagnostics.troubleshootCommonIssues.timeoutTitle', 'Connection Timeout'),
|
||||
steps: [
|
||||
t(
|
||||
'diagnostics.troubleshootCommonIssues.wifiSignal',
|
||||
'Check WiFi signal strength on your phone.'
|
||||
),
|
||||
t(
|
||||
'diagnostics.troubleshootCommonIssues.retryHost',
|
||||
'Go back to the host list and tap your host to retry.'
|
||||
),
|
||||
t(
|
||||
'diagnostics.troubleshootCommonIssues.restartBothApps',
|
||||
'Restart both apps if timeouts persist.'
|
||||
)
|
||||
]
|
||||
},
|
||||
{
|
||||
id: 'tailscale',
|
||||
icon: Globe,
|
||||
title: t('diagnostics.troubleshootCommonIssues.tailscaleTitle', 'Tailscale Host Unreachable'),
|
||||
steps: [
|
||||
t(
|
||||
'diagnostics.troubleshootCommonIssues.keepTailscaleOn',
|
||||
'Host addresses like 100.x.x.x or *.ts.net connect through Tailscale — keep it ON.'
|
||||
),
|
||||
t(
|
||||
'diagnostics.troubleshootCommonIssues.restartTailscale',
|
||||
'iOS/Android can silently wedge the tunnel: toggle Tailscale off and back on in the Tailscale app.'
|
||||
),
|
||||
t(
|
||||
'diagnostics.troubleshootCommonIssues.checkTailnet',
|
||||
'Check the desktop is awake and shows as connected in your tailnet.'
|
||||
),
|
||||
t(
|
||||
'diagnostics.troubleshootCommonIssues.updateTailscale',
|
||||
'Update the Tailscale app — recent releases fix reconnect bugs.'
|
||||
)
|
||||
]
|
||||
},
|
||||
{
|
||||
id: 'vpn',
|
||||
icon: Shield,
|
||||
title: t('diagnostics.troubleshootCommonIssues.vpnTitle', 'Other VPN Interference'),
|
||||
steps: [
|
||||
t(
|
||||
'diagnostics.troubleshootCommonIssues.vpnRouting',
|
||||
'Non-Tailscale VPNs can route local traffic through a remote server.'
|
||||
),
|
||||
t(
|
||||
'diagnostics.troubleshootCommonIssues.disableVpn',
|
||||
'Disable that VPN or enable split tunneling / "Allow LAN".'
|
||||
)
|
||||
]
|
||||
}
|
||||
]
|
||||
}
|
||||
|
||||
@@ -15,6 +15,7 @@ import { colors, spacing } from '../theme/mobile-theme'
|
||||
import { useMobileColorScheme } from '../theme/use-mobile-color-scheme'
|
||||
import { troubleshootCommonIssues } from './troubleshoot-common-issues'
|
||||
import { troubleshootScreenStyles as styles } from './troubleshoot-screen-styles'
|
||||
import { useMobileTranslation } from '../i18n/use-mobile-translation'
|
||||
export type DiagnosticStatus = 'idle' | 'running' | 'done'
|
||||
|
||||
export type CheckResult = {
|
||||
@@ -54,6 +55,7 @@ export function TroubleshootView({
|
||||
developerRow?: ReactNode
|
||||
}) {
|
||||
useMobileColorScheme()
|
||||
const t = useMobileTranslation()
|
||||
const insets = useSafeAreaInsets()
|
||||
const [expandedId, setExpandedId] = useState<string | null>(null)
|
||||
const toggleSection = useCallback(
|
||||
@@ -65,13 +67,15 @@ export function TroubleshootView({
|
||||
<View style={styles.topRow}>
|
||||
<Pressable
|
||||
accessibilityRole="button"
|
||||
accessibilityLabel="Back"
|
||||
accessibilityLabel={t('common.back', 'Back')}
|
||||
style={styles.backButton}
|
||||
onPress={onBack}
|
||||
>
|
||||
<ChevronLeft size={22} color={colors.textSecondary} />
|
||||
</Pressable>
|
||||
<Text style={styles.heading}>Troubleshooting</Text>
|
||||
<Text style={styles.heading}>
|
||||
{t('diagnostics.troubleshootView.title', 'Troubleshooting')}
|
||||
</Text>
|
||||
</View>
|
||||
|
||||
<ScrollView
|
||||
@@ -96,10 +100,10 @@ export function TroubleshootView({
|
||||
)}
|
||||
<Text style={styles.diagnosticButtonLabel}>
|
||||
{diagnosticStatus === 'running'
|
||||
? 'Running…'
|
||||
? t('diagnostics.troubleshootView.running', 'Running…')
|
||||
: diagnosticStatus === 'done'
|
||||
? 'Run again'
|
||||
: 'Run diagnostics'}
|
||||
? t('diagnostics.troubleshootView.runAgain', 'Run again')
|
||||
: t('diagnostics.troubleshootView.runDiagnostics', 'Run diagnostics')}
|
||||
</Text>
|
||||
</Pressable>
|
||||
|
||||
@@ -111,7 +115,9 @@ export function TroubleshootView({
|
||||
onPress={onConnectionLog}
|
||||
>
|
||||
<ScrollText size={16} color={colors.textPrimary} />
|
||||
<Text style={styles.diagnosticButtonLabel}>View network diagnostics</Text>
|
||||
<Text style={styles.diagnosticButtonLabel}>
|
||||
{t('diagnostics.troubleshootView.viewNetworkDiagnostics', 'View network diagnostics')}
|
||||
</Text>
|
||||
</Pressable>
|
||||
|
||||
{developerRow}
|
||||
@@ -135,10 +141,12 @@ export function TroubleshootView({
|
||||
</View>
|
||||
)}
|
||||
|
||||
<Text style={styles.sectionHeading}>Common issues</Text>
|
||||
<Text style={styles.sectionHeading}>
|
||||
{t('diagnostics.troubleshootView.commonIssues', 'Common issues')}
|
||||
</Text>
|
||||
|
||||
<View style={styles.section}>
|
||||
{troubleshootCommonIssues.map((section, i) => (
|
||||
{troubleshootCommonIssues(t).map((section, i) => (
|
||||
<View key={section.id}>
|
||||
{i > 0 && <View style={styles.separator} />}
|
||||
<Pressable
|
||||
|
||||
@@ -1,6 +1,7 @@
|
||||
import { useCallback, useRef, useState } from 'react'
|
||||
import type { View } from 'react-native'
|
||||
import { Platform } from 'react-native'
|
||||
import { useMobileTranslation } from '../i18n/use-mobile-translation'
|
||||
import { loadHosts } from '../transport/host-store'
|
||||
import {
|
||||
startDiagnosticFetchTimeout,
|
||||
@@ -10,6 +11,7 @@ import { formatEndpoint, testHostReachability, unreachableHostDetail } from './h
|
||||
import type { CheckResult, DiagnosticStatus } from './troubleshoot-view'
|
||||
|
||||
export function useTroubleshootDiagnostics() {
|
||||
const t = useMobileTranslation()
|
||||
const [diagnosticStatus, setDiagnosticStatus] = useState<DiagnosticStatus>('idle')
|
||||
const [checks, setChecks] = useState<CheckResult[]>([])
|
||||
const abortRef = useRef(false)
|
||||
@@ -43,11 +45,31 @@ export function useTroubleshootDiagnostics() {
|
||||
const hosts = await loadHosts()
|
||||
results.push(
|
||||
hosts.length > 0
|
||||
? { label: 'Paired hosts', status: 'pass', detail: `${hosts.length} paired` }
|
||||
: { label: 'Paired hosts', status: 'fail', detail: 'None — scan a QR to pair' }
|
||||
? {
|
||||
label: t('diagnostics.useTroubleshootDiagnostics.pairedHosts', 'Paired hosts'),
|
||||
status: 'pass',
|
||||
detail: t('diagnostics.useTroubleshootDiagnostics.pairedCount', '{{count}} paired', {
|
||||
count: hosts.length
|
||||
})
|
||||
}
|
||||
: {
|
||||
label: t('diagnostics.useTroubleshootDiagnostics.pairedHosts', 'Paired hosts'),
|
||||
status: 'fail',
|
||||
detail: t(
|
||||
'diagnostics.useTroubleshootDiagnostics.noPairedHosts',
|
||||
'None — scan a QR to pair'
|
||||
)
|
||||
}
|
||||
)
|
||||
} catch {
|
||||
results.push({ label: 'Paired hosts', status: 'warn', detail: 'Could not read host data' })
|
||||
results.push({
|
||||
label: t('diagnostics.useTroubleshootDiagnostics.pairedHosts', 'Paired hosts'),
|
||||
status: 'warn',
|
||||
detail: t(
|
||||
'diagnostics.useTroubleshootDiagnostics.hostDataFailed',
|
||||
'Could not read host data'
|
||||
)
|
||||
})
|
||||
}
|
||||
|
||||
if (!isCurrentRun()) {
|
||||
@@ -66,14 +88,29 @@ export function useTroubleshootDiagnostics() {
|
||||
}
|
||||
results.push(
|
||||
resp.ok
|
||||
? { label: 'Internet', status: 'pass', detail: 'Connected' }
|
||||
: { label: 'Internet', status: 'warn', detail: 'Unexpected response' }
|
||||
? {
|
||||
label: t('diagnostics.useTroubleshootDiagnostics.internet', 'Internet'),
|
||||
status: 'pass',
|
||||
detail: t('diagnostics.useTroubleshootDiagnostics.connected', 'Connected')
|
||||
}
|
||||
: {
|
||||
label: t('diagnostics.useTroubleshootDiagnostics.internet', 'Internet'),
|
||||
status: 'warn',
|
||||
detail: t(
|
||||
'diagnostics.useTroubleshootDiagnostics.unexpectedResponse',
|
||||
'Unexpected response'
|
||||
)
|
||||
}
|
||||
)
|
||||
} catch {
|
||||
if (!isCurrentRun()) {
|
||||
return
|
||||
}
|
||||
results.push({ label: 'Internet', status: 'fail', detail: 'No connection' })
|
||||
results.push({
|
||||
label: t('diagnostics.useTroubleshootDiagnostics.internet', 'Internet'),
|
||||
status: 'fail',
|
||||
detail: t('diagnostics.useTroubleshootDiagnostics.noConnection', 'No connection')
|
||||
})
|
||||
} finally {
|
||||
internetCheck.dispose()
|
||||
if (activeInternetCheckRef.current === internetCheck) {
|
||||
@@ -100,13 +137,19 @@ export function useTroubleshootDiagnostics() {
|
||||
label: host.name,
|
||||
status: reachable ? 'pass' : 'fail',
|
||||
detail: reachable
|
||||
? `Reachable at ${formatEndpoint(host.endpoint)}`
|
||||
? t('diagnostics.useTroubleshootDiagnostics.reachable', 'Reachable at {{endpoint}}', {
|
||||
endpoint: formatEndpoint(host.endpoint)
|
||||
})
|
||||
: unreachableHostDetail(host.endpoint)
|
||||
})
|
||||
setChecks([...results])
|
||||
}
|
||||
} catch {
|
||||
results.push({ label: 'Hosts', status: 'warn', detail: 'Could not test' })
|
||||
results.push({
|
||||
label: t('diagnostics.useTroubleshootDiagnostics.hosts', 'Hosts'),
|
||||
status: 'warn',
|
||||
detail: t('diagnostics.useTroubleshootDiagnostics.hostTestFailed', 'Could not test')
|
||||
})
|
||||
}
|
||||
|
||||
if (!isCurrentRun()) {
|
||||
@@ -114,14 +157,14 @@ export function useTroubleshootDiagnostics() {
|
||||
}
|
||||
|
||||
results.push({
|
||||
label: 'Platform',
|
||||
label: t('diagnostics.useTroubleshootDiagnostics.platform', 'Platform'),
|
||||
status: 'pass',
|
||||
detail: `${Platform.OS} ${Platform.Version ?? ''}`
|
||||
})
|
||||
|
||||
setChecks([...results])
|
||||
setDiagnosticStatus('done')
|
||||
}, [])
|
||||
}, [t])
|
||||
|
||||
return { rootRef, diagnosticStatus, checks, runDiagnostics }
|
||||
}
|
||||
|
||||
@@ -1,4 +1,5 @@
|
||||
import { hostDictationErrorMessage } from '../../../src/shared/host-dictation-error-messages'
|
||||
import { translate } from '../i18n/mobile-i18n'
|
||||
import type { RpcOperationSender } from '../transport/rpc-operation-sender'
|
||||
import type { RpcResponse } from '../transport/types'
|
||||
import { interpretOrThrowRefusalMessage } from '../transport/rpc-refusal-message'
|
||||
@@ -17,8 +18,6 @@ export type MobileSpeechModel = MobileSpeechModelReply
|
||||
|
||||
// Map setup errors to the setup sheet instead of leaving the mic entry point at a toast.
|
||||
const SETUP_REQUIRED_CODES = new Set(['voice_dictation_disabled', 'voice_model_not_selected'])
|
||||
const LEGACY_DESKTOP_SPEECH_SETUP_MESSAGE =
|
||||
'Update the paired desktop Orca app to use mobile voice settings.'
|
||||
|
||||
// Older runtimes predate models.list; show upgrade guidance. Keep the raw refusal because guidance needs both code and message.
|
||||
function isLegacyDesktopSpeechSetupReply(reply: RpcResponse): boolean {
|
||||
@@ -34,7 +33,12 @@ function isLegacyDesktopSpeechSetupReply(reply: RpcResponse): boolean {
|
||||
}
|
||||
|
||||
// Headless servers lacked speech from 1.4.229 until #31174; name the required fix.
|
||||
const SPEECHLESS_SERVER_MESSAGE = 'Update Orca on this server to use voice dictation.'
|
||||
function speechlessServerMessage(): string {
|
||||
return translate(
|
||||
'dictation.setup.serverUpdateRequired',
|
||||
'Update Orca on this server to use voice dictation.'
|
||||
)
|
||||
}
|
||||
|
||||
function isSpeechlessServerError(message: string): boolean {
|
||||
return message.startsWith('speech_unavailable')
|
||||
@@ -42,9 +46,53 @@ function isSpeechlessServerError(message: string): boolean {
|
||||
|
||||
export function dictationFailureMessage(message: string): string {
|
||||
if (isSpeechlessServerError(message)) {
|
||||
return SPEECHLESS_SERVER_MESSAGE
|
||||
return speechlessServerMessage()
|
||||
}
|
||||
const fallback = hostDictationErrorMessage(message)
|
||||
if (fallback === undefined) {
|
||||
return message
|
||||
}
|
||||
switch (message) {
|
||||
case 'dictation_already_active':
|
||||
return translate('dictation.setup.alreadyActive', fallback)
|
||||
case 'dictation_requires_mobile_client':
|
||||
return translate('dictation.setup.repairRequired', fallback)
|
||||
case 'dictation_owner_mismatch':
|
||||
case 'dictation_stream_not_started':
|
||||
case 'dictation_stream_closing':
|
||||
return translate('dictation.setup.sessionLost', fallback)
|
||||
default:
|
||||
return fallback
|
||||
}
|
||||
}
|
||||
|
||||
export function dictationSessionErrorMessage(
|
||||
kind: 'permission-denied' | 'unavailable' | 'no-speech' | 'interrupted' | 'input-closed'
|
||||
): string {
|
||||
switch (kind) {
|
||||
case 'permission-denied':
|
||||
return translate(
|
||||
'hooks.useMobileDictation.microphonePermissionDenied',
|
||||
'Microphone permission denied'
|
||||
)
|
||||
case 'unavailable':
|
||||
return translate(
|
||||
'hooks.useMobileDictation.microphoneInitFailed',
|
||||
'Failed to initialize microphone'
|
||||
)
|
||||
case 'no-speech':
|
||||
return translate('hooks.useMobileDictation.noSpeech', 'No speech detected.')
|
||||
case 'interrupted':
|
||||
return translate(
|
||||
'hooks.useMobileDictation.microphoneInterrupted',
|
||||
'Voice dictation stopped because the microphone was interrupted. Try again.'
|
||||
)
|
||||
case 'input-closed':
|
||||
return translate(
|
||||
'hooks.useMobileDictation.inputClosed',
|
||||
'Voice dictation stopped because this session is no longer accepting input. Try again.'
|
||||
)
|
||||
}
|
||||
return hostDictationErrorMessage(message) ?? message
|
||||
}
|
||||
|
||||
function isSpeechlessServerReply(reply: RpcResponse): boolean {
|
||||
@@ -58,14 +106,19 @@ export function isDictationSetupRequiredError(message: string): boolean {
|
||||
export async function fetchDictationSetup(client: RpcOperationSender): Promise<MobileSpeechSetup> {
|
||||
const reply = await requestDictationSetupReply(client)
|
||||
if (isLegacyDesktopSpeechSetupReply(reply)) {
|
||||
throw new Error(LEGACY_DESKTOP_SPEECH_SETUP_MESSAGE)
|
||||
throw new Error(
|
||||
translate(
|
||||
'dictation.setup.desktopUpdateRequired',
|
||||
'Update the paired desktop Orca app to use mobile voice settings.'
|
||||
)
|
||||
)
|
||||
}
|
||||
if (isSpeechlessServerReply(reply)) {
|
||||
throw new Error(SPEECHLESS_SERVER_MESSAGE)
|
||||
throw new Error(speechlessServerMessage())
|
||||
}
|
||||
return interpretOrThrowRefusalMessage(
|
||||
() => dictationSetupRead.interpret(reply),
|
||||
'Failed to load dictation models'
|
||||
translate('dictation.setup.loadFailed', 'Failed to load dictation models')
|
||||
)
|
||||
}
|
||||
|
||||
@@ -88,7 +141,7 @@ export async function downloadDictationModel(
|
||||
const reply = await dictationModelDownload.request(client, { modelId })
|
||||
interpretOrThrowRefusalMessage(
|
||||
() => dictationModelDownload.interpret(reply),
|
||||
'Failed to start download'
|
||||
translate('dictation.setup.downloadFailed', 'Failed to start download')
|
||||
)
|
||||
}
|
||||
|
||||
@@ -99,7 +152,7 @@ export async function deleteDictationModel(
|
||||
const reply = await dictationModelDelete.request(client, { modelId })
|
||||
return interpretOrThrowRefusalMessage(
|
||||
() => dictationModelDelete.interpret(reply),
|
||||
'Failed to delete model'
|
||||
translate('dictation.setup.deleteFailed', 'Failed to delete model')
|
||||
)
|
||||
}
|
||||
|
||||
@@ -110,7 +163,7 @@ export async function setDictationConfig(
|
||||
const reply = await dictationConfigWrite.request(client, params)
|
||||
return interpretOrThrowRefusalMessage(
|
||||
() => dictationConfigWrite.interpret(reply),
|
||||
'Failed to update dictation settings'
|
||||
translate('dictation.setup.updateFailed', 'Failed to update dictation settings')
|
||||
)
|
||||
}
|
||||
|
||||
@@ -133,7 +186,9 @@ export function dictationNeedsSetup(setup: MobileSpeechSetup): boolean {
|
||||
}
|
||||
|
||||
// Keep copy short and host-neutral: the paired computer may be a headless server.
|
||||
export const CLOUD_MODEL_NEEDS_KEY = 'Needs an OpenAI key'
|
||||
export function cloudModelNeedsKey(): string {
|
||||
return translate('dictation.setup.cloudModelNeedsKey', 'Needs an OpenAI key')
|
||||
}
|
||||
|
||||
export type DictationModelRowAction =
|
||||
| 'in-use'
|
||||
|
||||
@@ -1,5 +1,6 @@
|
||||
import { ArrowUpFromLine, X } from 'lucide-react-native'
|
||||
import { Pressable, Text, View } from 'react-native'
|
||||
import { useMobileTranslation } from '../i18n/use-mobile-translation'
|
||||
import { colors, radii, spacing } from '../theme/mobile-theme'
|
||||
import { themedStyleSheet } from '../theme/themed-styles'
|
||||
import { useMobileColorScheme } from '../theme/use-mobile-color-scheme'
|
||||
@@ -9,13 +10,20 @@ export function AppUpdateCard(props: {
|
||||
onPress: () => void
|
||||
onDismiss: () => void
|
||||
}) {
|
||||
const t = useMobileTranslation()
|
||||
useMobileColorScheme()
|
||||
const title = `Orca ${props.version} is available`
|
||||
const title = t('home.appUpdateCard.title', 'Orca {{version}} is available', {
|
||||
version: props.version
|
||||
})
|
||||
return (
|
||||
<View style={styles.card}>
|
||||
<Pressable
|
||||
accessibilityRole="button"
|
||||
accessibilityLabel={`${title}, tap to update`}
|
||||
accessibilityLabel={t(
|
||||
'home.appUpdateCard.updateAccessibilityLabel',
|
||||
'Orca {{version}} is available, tap to update',
|
||||
{ version: props.version }
|
||||
)}
|
||||
style={({ pressed }) => [styles.cardMain, pressed && styles.cardPressed]}
|
||||
onPress={props.onPress}
|
||||
>
|
||||
@@ -27,13 +35,17 @@ export function AppUpdateCard(props: {
|
||||
{title}
|
||||
</Text>
|
||||
<Text style={styles.subtitle} numberOfLines={1}>
|
||||
Tap to update
|
||||
{t('home.appUpdateCard.updateHint', 'Tap to update')}
|
||||
</Text>
|
||||
</View>
|
||||
</Pressable>
|
||||
<Pressable
|
||||
accessibilityRole="button"
|
||||
accessibilityLabel={`Dismiss Orca ${props.version} update`}
|
||||
accessibilityLabel={t(
|
||||
'home.appUpdateCard.dismissAccessibilityLabel',
|
||||
'Dismiss Orca {{version}} update',
|
||||
{ version: props.version }
|
||||
)}
|
||||
hitSlop={8}
|
||||
style={({ pressed }) => [styles.dismissButton, pressed && styles.dismissPressed]}
|
||||
onPress={props.onDismiss}
|
||||
|
||||
@@ -1,4 +1,5 @@
|
||||
import { Pressable, Text, View } from 'react-native'
|
||||
import { useMobileTranslation } from '../i18n/use-mobile-translation'
|
||||
import { ClaudeIcon, OpenAIIcon } from '../components/AgentIcons'
|
||||
import {
|
||||
getActiveProviderRateLimits,
|
||||
@@ -17,13 +18,16 @@ export function MobileHomeAccountUsageCards(props: {
|
||||
items: { host: HostProfile; snapshot: AccountsSnapshot }[]
|
||||
onOpen: (hostId: string) => void
|
||||
}) {
|
||||
const t = useMobileTranslation()
|
||||
useMobileColorScheme()
|
||||
if (props.items.length === 0) {
|
||||
return null
|
||||
}
|
||||
return (
|
||||
<>
|
||||
<Text style={styles.sectionHeading}>Account usage</Text>
|
||||
<Text style={styles.sectionHeading}>
|
||||
{t('home.accountUsageCards.sectionHeading', 'Account usage')}
|
||||
</Text>
|
||||
{props.items.map(({ host, snapshot }) => {
|
||||
const claudeActive =
|
||||
snapshot.claude.accounts.find(
|
||||
@@ -65,17 +69,17 @@ export function MobileHomeAccountUsageCards(props: {
|
||||
</View>
|
||||
<View style={styles.info}>
|
||||
<Text style={styles.email} numberOfLines={1}>
|
||||
{active?.email ?? 'System default'}
|
||||
{active?.email ?? t('home.accountUsageCards.systemDefault', 'System default')}
|
||||
</Text>
|
||||
<View style={styles.bars}>
|
||||
<UsageBar
|
||||
label="5h"
|
||||
label={t('home.accountUsageCards.sessionWindow', '5h')}
|
||||
usedPercent={sessionBar.usedPercent}
|
||||
unavailable={sessionBar.unavailable}
|
||||
loading={sessionBar.loading}
|
||||
/>
|
||||
<UsageBar
|
||||
label="7d"
|
||||
label={t('home.accountUsageCards.weeklyWindow', '7d')}
|
||||
usedPercent={weeklyBar.usedPercent}
|
||||
unavailable={weeklyBar.unavailable}
|
||||
loading={weeklyBar.loading}
|
||||
|
||||
@@ -1,23 +1,39 @@
|
||||
import { QrCode } from 'lucide-react-native'
|
||||
import { Pressable, ScrollView, Text, View } from 'react-native'
|
||||
import type { Translate } from '../i18n/mobile-i18n'
|
||||
import { useMobileTranslation } from '../i18n/use-mobile-translation'
|
||||
import { colors, radii, spacing } from '../theme/mobile-theme'
|
||||
import { themedStyleSheet } from '../theme/themed-styles'
|
||||
import { useMobileColorScheme } from '../theme/use-mobile-color-scheme'
|
||||
|
||||
const ONBOARDING_STEPS = [
|
||||
{
|
||||
title: 'Open Orca desktop',
|
||||
desc: 'Go to Settings → Mobile and generate a pairing QR code.'
|
||||
},
|
||||
{
|
||||
title: 'Scan the code',
|
||||
desc: 'Tap the button above to open the scanner. Point at the QR code on your screen.'
|
||||
},
|
||||
{
|
||||
title: "You're connected",
|
||||
desc: 'Your desktop will appear here. Everything is encrypted end-to-end.'
|
||||
}
|
||||
]
|
||||
function onboardingSteps(t: Translate) {
|
||||
return [
|
||||
{
|
||||
id: 'open-desktop',
|
||||
title: t('home.emptyState.openDesktopTitle', 'Open Orca desktop'),
|
||||
desc: t(
|
||||
'home.emptyState.openDesktopDescription',
|
||||
'Go to Settings → Mobile and generate a pairing QR code.'
|
||||
)
|
||||
},
|
||||
{
|
||||
id: 'scan-code',
|
||||
title: t('home.emptyState.scanCodeTitle', 'Scan the code'),
|
||||
desc: t(
|
||||
'home.emptyState.scanCodeDescription',
|
||||
'Tap the button above to open the scanner. Point at the QR code on your screen.'
|
||||
)
|
||||
},
|
||||
{
|
||||
id: 'connected',
|
||||
title: t('home.emptyState.connectedTitle', "You're connected"),
|
||||
desc: t(
|
||||
'home.emptyState.connectedDescription',
|
||||
'Your desktop will appear here. Everything is encrypted end-to-end.'
|
||||
)
|
||||
}
|
||||
]
|
||||
}
|
||||
|
||||
export function MobileHomeEmptyState(props: {
|
||||
bottomInset: number
|
||||
@@ -25,6 +41,7 @@ export function MobileHomeEmptyState(props: {
|
||||
isWideLayout: boolean
|
||||
onPairDesktop: () => void
|
||||
}) {
|
||||
const t = useMobileTranslation()
|
||||
useMobileColorScheme()
|
||||
// Scrolls because a phone in landscape is shorter than the hero and the steps together.
|
||||
return (
|
||||
@@ -40,20 +57,26 @@ export function MobileHomeEmptyState(props: {
|
||||
]}
|
||||
>
|
||||
<View style={styles.emptyHero}>
|
||||
<Text style={styles.emptyTitle}>Connect your desktop</Text>
|
||||
<Text style={styles.emptyTitle}>{t('home.emptyState.title', 'Connect your desktop')}</Text>
|
||||
<Text style={styles.emptyBody}>
|
||||
Pair with Orca on your computer to check on your agents, jump into any terminal, and drive
|
||||
work from your phone.
|
||||
{t(
|
||||
'home.emptyState.description',
|
||||
'Pair with Orca on your computer to check on your agents, jump into any terminal, and drive work from your phone.'
|
||||
)}
|
||||
</Text>
|
||||
<Pressable style={styles.primaryButton} onPress={props.onPairDesktop}>
|
||||
<QrCode size={17} color={colors.bgBase} />
|
||||
<Text style={styles.primaryButtonText}>Pair Desktop</Text>
|
||||
<Text style={styles.primaryButtonText}>
|
||||
{t('home.emptyState.pairDesktop', 'Pair Desktop')}
|
||||
</Text>
|
||||
</Pressable>
|
||||
</View>
|
||||
<View style={styles.stepsSection}>
|
||||
<Text style={styles.sectionHeading}>How it works</Text>
|
||||
{ONBOARDING_STEPS.map((step, index) => (
|
||||
<View key={step.title} style={[styles.stepRow, index > 0 && styles.stepRowBorder]}>
|
||||
<Text style={styles.sectionHeading}>
|
||||
{t('home.emptyState.stepsHeading', 'How it works')}
|
||||
</Text>
|
||||
{onboardingSteps(t).map((step, index) => (
|
||||
<View key={step.id} style={[styles.stepRow, index > 0 && styles.stepRowBorder]}>
|
||||
<View style={styles.stepNum}>
|
||||
<Text style={styles.stepNumText}>{index + 1}</Text>
|
||||
</View>
|
||||
|
||||
@@ -1,4 +1,5 @@
|
||||
import { Text, View } from 'react-native'
|
||||
import { useMobileTranslation } from '../i18n/use-mobile-translation'
|
||||
import type { AccountsSnapshot } from '../components/AccountUsage'
|
||||
import { MobileHomeQuickActions } from '../components/MobileHomeQuickActions'
|
||||
import { PickerModal } from '../components/PickerModal'
|
||||
@@ -26,6 +27,7 @@ export function MobileHomeListFooter(props: {
|
||||
onOpenTasks: (hostId: string, provider?: TaskProvider) => void
|
||||
onPairDesktop: () => void
|
||||
}) {
|
||||
const t = useMobileTranslation()
|
||||
useMobileColorScheme()
|
||||
const tasksHostPicker = useConnectedHostPicker(props.connectedHosts)
|
||||
const hostProviders = props.connectedHosts.map((host) => ({
|
||||
@@ -48,17 +50,20 @@ export function MobileHomeListFooter(props: {
|
||||
<View>
|
||||
{props.resumeCard ? (
|
||||
<>
|
||||
<Text style={styles.sectionHeading}>Resume</Text>
|
||||
<Text style={styles.sectionHeading}>{t('home.listFooter.resumeHeading', 'Resume')}</Text>
|
||||
<MobileHomeResumeCard card={props.resumeCard} onOpen={props.onOpenResume} />
|
||||
</>
|
||||
) : null}
|
||||
<Text style={styles.sectionHeading}>Tasks</Text>
|
||||
<Text style={styles.sectionHeading}>{t('home.listFooter.tasksHeading', 'Tasks')}</Text>
|
||||
<MobileHomeTasksCard
|
||||
enabled={props.connectedHosts.length > 0}
|
||||
providers={providers}
|
||||
onOpen={openTasks}
|
||||
/>
|
||||
<PickerModal title="Open Tasks On" {...tasksHostPicker.pickerProps} />
|
||||
<PickerModal
|
||||
title={t('home.listFooter.hostPickerTitle', 'Open Tasks On')}
|
||||
{...tasksHostPicker.pickerProps}
|
||||
/>
|
||||
<MobileHomeQuickActions
|
||||
connectedHosts={props.connectedHosts}
|
||||
onPairDesktop={props.onPairDesktop}
|
||||
|
||||
@@ -1,47 +1,59 @@
|
||||
import { Text, View } from 'react-native'
|
||||
import type { Translate } from '../i18n/mobile-i18n'
|
||||
import { getUiIntlLocale } from '../i18n/ui-intl-locale'
|
||||
import { useMobileTranslation } from '../i18n/use-mobile-translation'
|
||||
import type { HomeStatsSummary } from '../stats/home-stats-total'
|
||||
import { colors, spacing } from '../theme/mobile-theme'
|
||||
import { themedStyleSheet } from '../theme/themed-styles'
|
||||
import { useMobileColorScheme } from '../theme/use-mobile-color-scheme'
|
||||
import { MobileHomeAppUpdateCard } from './MobileHomeAppUpdateCard'
|
||||
|
||||
function formatDuration(ms: number): string {
|
||||
function formatDuration(ms: number, t: Translate): string {
|
||||
const totalMinutes = Math.floor(ms / 60_000)
|
||||
const totalHours = Math.floor(totalMinutes / 60)
|
||||
const days = Math.floor(totalHours / 24)
|
||||
const hours = totalHours % 24
|
||||
if (days > 0) {
|
||||
return `${days}d ${hours}h`
|
||||
return t('home.listHeader.daysHours', '{{days}}d {{hours}}h', { days, hours })
|
||||
}
|
||||
const minutes = totalMinutes % 60
|
||||
return totalHours > 0 ? `${totalHours}h ${minutes}m` : `${totalMinutes}m`
|
||||
return totalHours > 0
|
||||
? t('home.listHeader.hoursMinutes', '{{hours}}h {{minutes}}m', { hours: totalHours, minutes })
|
||||
: t('home.listHeader.minutes', '{{minutes}}m', { minutes: totalMinutes })
|
||||
}
|
||||
|
||||
export function MobileHomeListHeader({ stats }: { stats: HomeStatsSummary | null }) {
|
||||
const t = useMobileTranslation()
|
||||
useMobileColorScheme()
|
||||
return (
|
||||
<View>
|
||||
<View style={styles.hero}>
|
||||
<Text style={styles.heroTitle}>Welcome back</Text>
|
||||
<Text style={styles.heroTitle}>{t('home.listHeader.title', 'Welcome back')}</Text>
|
||||
</View>
|
||||
{stats ? (
|
||||
<View style={styles.statsRow}>
|
||||
<View style={styles.statCard}>
|
||||
<Text style={styles.statValue}>{stats.totalAgentsSpawned.toLocaleString()}</Text>
|
||||
<Text style={styles.statLabel}>Agents spawned</Text>
|
||||
<Text style={styles.statValue}>
|
||||
{stats.totalAgentsSpawned.toLocaleString(getUiIntlLocale())}
|
||||
</Text>
|
||||
<Text style={styles.statLabel}>
|
||||
{t('home.listHeader.agentsSpawned', 'Agents spawned')}
|
||||
</Text>
|
||||
</View>
|
||||
<View style={styles.statCard}>
|
||||
<Text style={styles.statValue}>{formatDuration(stats.totalAgentTimeMs)}</Text>
|
||||
<Text style={styles.statLabel}>Agent time</Text>
|
||||
<Text style={styles.statValue}>{formatDuration(stats.totalAgentTimeMs, t)}</Text>
|
||||
<Text style={styles.statLabel}>{t('home.listHeader.agentTime', 'Agent time')}</Text>
|
||||
</View>
|
||||
<View style={styles.statCard}>
|
||||
<Text style={styles.statValue}>{stats.totalPRsCreated.toLocaleString()}</Text>
|
||||
<Text style={styles.statLabel}>PRs created</Text>
|
||||
<Text style={styles.statValue}>
|
||||
{stats.totalPRsCreated.toLocaleString(getUiIntlLocale())}
|
||||
</Text>
|
||||
<Text style={styles.statLabel}>{t('home.listHeader.prsCreated', 'PRs created')}</Text>
|
||||
</View>
|
||||
</View>
|
||||
) : null}
|
||||
<MobileHomeAppUpdateCard />
|
||||
<Text style={styles.sectionHeading}>Desktops</Text>
|
||||
<Text style={styles.sectionHeading}>{t('home.listHeader.desktopsHeading', 'Desktops')}</Text>
|
||||
</View>
|
||||
)
|
||||
}
|
||||
|
||||
@@ -1,6 +1,7 @@
|
||||
import { useCallback, useState } from 'react'
|
||||
import { Alert } from 'react-native'
|
||||
import { SafeAreaView, useSafeAreaInsets } from 'react-native-safe-area-context'
|
||||
import { useMobileTranslation } from '../i18n/use-mobile-translation'
|
||||
import { useOpenMobileAccounts } from '../accounts/use-open-mobile-accounts'
|
||||
import { getProvenCachedWorktrees } from '../cache/worktree-cache'
|
||||
import { ActionSheetModal } from '../components/ActionSheetModal'
|
||||
@@ -35,6 +36,7 @@ import { useMobileHomeData } from './use-mobile-home-data'
|
||||
import { newWorkspaceOverlayHref } from '../new-workspace/new-workspace-overlay-route'
|
||||
|
||||
export function MobileHomeScreen() {
|
||||
const t = useMobileTranslation()
|
||||
useMobileColorScheme()
|
||||
const data = useMobileHomeData()
|
||||
const insets = useSafeAreaInsets()
|
||||
@@ -75,7 +77,12 @@ export function MobileHomeScreen() {
|
||||
} else if (host.credentialStatus === 'temporarily-unavailable') {
|
||||
void loadHostCatalog()
|
||||
.then(data.setHostCatalog)
|
||||
.catch(() => Alert.alert('Could not check pairing', 'Please try again.'))
|
||||
.catch(() =>
|
||||
Alert.alert(
|
||||
t('home.screen.pairingCheckFailed', 'Could not check pairing'),
|
||||
t('home.screen.tryAgain', 'Please try again.')
|
||||
)
|
||||
)
|
||||
} else {
|
||||
data.router.push(`/h/${host.id}`)
|
||||
}
|
||||
@@ -100,7 +107,10 @@ export function MobileHomeScreen() {
|
||||
data.setHostCatalog(await loadHostCatalog())
|
||||
} catch {
|
||||
setConfirmRemove(host)
|
||||
Alert.alert('Could not remove host', 'Please try again.')
|
||||
Alert.alert(
|
||||
t('home.screen.removeHostFailed', 'Could not remove host'),
|
||||
t('home.screen.tryAgain', 'Please try again.')
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
@@ -180,9 +190,11 @@ export function MobileHomeScreen() {
|
||||
/>
|
||||
<ConfirmModal
|
||||
visible={confirmRemove != null}
|
||||
title="Remove Host"
|
||||
message={`Remove "${confirmRemove?.name}"? You can re-pair later.`}
|
||||
confirmLabel="Remove"
|
||||
title={t('home.screen.removeHostTitle', 'Remove Host')}
|
||||
message={t('home.screen.removeHostMessage', 'Remove "{{name}}"? You can re-pair later.', {
|
||||
name: confirmRemove?.name
|
||||
})}
|
||||
confirmLabel={t('common.remove', 'Remove')}
|
||||
destructive
|
||||
onConfirm={() => void handleRemove()}
|
||||
onCancel={() => setConfirmRemove(null)}
|
||||
|
||||
@@ -1,5 +1,6 @@
|
||||
import { ChevronRight, ListTodo } from 'lucide-react-native'
|
||||
import { Pressable, Text, View } from 'react-native'
|
||||
import { useMobileTranslation } from '../i18n/use-mobile-translation'
|
||||
import { TaskProviderLogo } from '../components/TaskProviderLogo'
|
||||
import type { TaskProvider } from '../../../src/shared/task-providers'
|
||||
import { colors, radii, spacing } from '../theme/mobile-theme'
|
||||
@@ -18,6 +19,7 @@ export function MobileHomeTasksCard(props: {
|
||||
providers: TaskProvider[]
|
||||
onOpen: (provider?: TaskProvider) => void
|
||||
}) {
|
||||
const t = useMobileTranslation()
|
||||
useMobileColorScheme()
|
||||
return (
|
||||
<Pressable
|
||||
@@ -33,11 +35,11 @@ export function MobileHomeTasksCard(props: {
|
||||
<ListTodo size={18} color={colors.textSecondary} />
|
||||
</View>
|
||||
<View style={styles.main}>
|
||||
<Text style={styles.title}>Tasks</Text>
|
||||
<Text style={styles.title}>{t('home.tasksCard.title', 'Tasks')}</Text>
|
||||
<Text style={styles.subtitle} numberOfLines={1}>
|
||||
{props.providers.length > 0
|
||||
? props.providers.map((provider) => TASK_PROVIDER_LABELS[provider]).join(' · ')
|
||||
: 'No task sources connected'}
|
||||
: t('home.tasksCard.noSources', 'No task sources connected')}
|
||||
</Text>
|
||||
</View>
|
||||
<View style={styles.trailing}>
|
||||
@@ -51,7 +53,9 @@ export function MobileHomeTasksCard(props: {
|
||||
<Pressable
|
||||
key={provider}
|
||||
accessibilityRole="button"
|
||||
accessibilityLabel={`Open ${TASK_PROVIDER_LABELS[provider]} tasks`}
|
||||
accessibilityLabel={t('home.tasksCard.openProviderTasks', 'Open {{provider}} tasks', {
|
||||
provider: TASK_PROVIDER_LABELS[provider]
|
||||
})}
|
||||
hitSlop={8}
|
||||
style={({ pressed }) => [
|
||||
styles.providerButton,
|
||||
|
||||
@@ -1,11 +1,13 @@
|
||||
import { Settings } from 'lucide-react-native'
|
||||
import { Pressable, Text, View } from 'react-native'
|
||||
import { useMobileTranslation } from '../i18n/use-mobile-translation'
|
||||
import { OrcaLogo } from '../components/OrcaLogo'
|
||||
import { colors, spacing } from '../theme/mobile-theme'
|
||||
import { themedStyleSheet } from '../theme/themed-styles'
|
||||
import { useMobileColorScheme } from '../theme/use-mobile-color-scheme'
|
||||
|
||||
export function MobileHomeTopBar({ onOpenSettings }: { onOpenSettings: () => void }) {
|
||||
const t = useMobileTranslation()
|
||||
useMobileColorScheme()
|
||||
return (
|
||||
<View style={styles.topBar}>
|
||||
@@ -20,7 +22,7 @@ export function MobileHomeTopBar({ onOpenSettings }: { onOpenSettings: () => voi
|
||||
onPress={onOpenSettings}
|
||||
hitSlop={8}
|
||||
accessibilityRole="button"
|
||||
accessibilityLabel="Settings"
|
||||
accessibilityLabel={t('common.settings', 'Settings')}
|
||||
>
|
||||
<Settings size={18} color={colors.textSecondary} />
|
||||
</Pressable>
|
||||
|
||||
@@ -1,4 +1,5 @@
|
||||
import { isCurrentMobileDictationStart } from './mobile-dictation-session-state'
|
||||
import { translate } from '../i18n/mobile-i18n'
|
||||
import {
|
||||
dictationSessionCancel,
|
||||
dictationSessionStart
|
||||
@@ -100,7 +101,12 @@ export async function startMobileDictationDesktopSession(
|
||||
try {
|
||||
// Commit before returning so queued cancellation cannot resurrect recording after cleanup.
|
||||
if (!options.commitRecordingStart()) {
|
||||
throw new Error('Failed to start microphone recording')
|
||||
throw new Error(
|
||||
translate(
|
||||
'hooks.dictationDesktopStart.recordingFailed',
|
||||
'Failed to start microphone recording'
|
||||
)
|
||||
)
|
||||
}
|
||||
} catch (err) {
|
||||
const wasCurrent = isCurrentStart(options)
|
||||
|
||||
@@ -1,5 +1,7 @@
|
||||
import { useCallback, useEffect, useLayoutEffect, useRef, useState } from 'react'
|
||||
import { useDictationCapture } from '../platform/dictation-capture'
|
||||
import { dictationSessionErrorMessage } from '../dictation/mobile-dictation-setup'
|
||||
import { useMobileTranslation } from '../i18n/use-mobile-translation'
|
||||
import { MobileDictationPendingAudioBudget } from './mobile-dictation-pending-audio-budget'
|
||||
import {
|
||||
MobileDictationTranscriptSoFar,
|
||||
@@ -8,8 +10,6 @@ import {
|
||||
} from './mobile-dictation-audio-chunk'
|
||||
import {
|
||||
DICTATION_FINISH_TIMEOUT_MS,
|
||||
MOBILE_DICTATION_INPUT_CLOSED_ERROR_MESSAGE,
|
||||
MOBILE_DICTATION_INTERRUPTED_ERROR_MESSAGE,
|
||||
createMobileDictationId,
|
||||
isCurrentMobileDictationFinish
|
||||
} from './mobile-dictation-session-state'
|
||||
@@ -28,6 +28,7 @@ import type {
|
||||
export type { UseMobileDictationResult } from './mobile-dictation-session-state'
|
||||
|
||||
export function useMobileDictation(options: UseMobileDictationOptions): UseMobileDictationResult {
|
||||
useMobileTranslation()
|
||||
const { client, enabled, onTranscript, onError } = options
|
||||
// Both capture seams hold the screen while the microphone is open; native uses the device directly and page uses shell audio verbs.
|
||||
const capture = useDictationCapture()
|
||||
@@ -148,11 +149,7 @@ export function useMobileDictation(options: UseMobileDictationOptions): UseMobil
|
||||
}
|
||||
if (!opened.ok) {
|
||||
applyStatus('idle')
|
||||
throw new Error(
|
||||
opened.reason === 'permission-denied'
|
||||
? 'Microphone permission denied'
|
||||
: 'Failed to initialize microphone'
|
||||
)
|
||||
throw new Error(dictationSessionErrorMessage(opened.reason))
|
||||
}
|
||||
|
||||
const dictationId = createMobileDictationId()
|
||||
@@ -255,7 +252,10 @@ export function useMobileDictation(options: UseMobileDictationOptions): UseMobil
|
||||
[applyStatus, capture, failActiveDictation]
|
||||
)
|
||||
|
||||
const stop = useCallback(() => finishDictation('No speech detected.'), [finishDictation])
|
||||
const stop = useCallback(
|
||||
() => finishDictation(dictationSessionErrorMessage('no-speech')),
|
||||
[finishDictation]
|
||||
)
|
||||
|
||||
// Only the user’s own cancel (reason:null) ends silently; other interruptions call onError so the tap has a visible outcome.
|
||||
const abandonDictation = useCallback(
|
||||
@@ -290,9 +290,9 @@ export function useMobileDictation(options: UseMobileDictationOptions): UseMobil
|
||||
// Finish what was already streamed; only the user's own cancel may discard it.
|
||||
const sub = capture.onInterruption(() => {
|
||||
if (statusRef.current === 'recording') {
|
||||
void finishDictation(MOBILE_DICTATION_INTERRUPTED_ERROR_MESSAGE)
|
||||
void finishDictation(dictationSessionErrorMessage('interrupted'))
|
||||
} else if (statusRef.current === 'starting') {
|
||||
void abandonDictation(MOBILE_DICTATION_INTERRUPTED_ERROR_MESSAGE)
|
||||
void abandonDictation(dictationSessionErrorMessage('interrupted'))
|
||||
}
|
||||
})
|
||||
return () => sub.remove()
|
||||
@@ -300,7 +300,7 @@ export function useMobileDictation(options: UseMobileDictationOptions): UseMobil
|
||||
|
||||
useEffect(() => {
|
||||
if (!enabled) {
|
||||
void abandonDictation(MOBILE_DICTATION_INPUT_CLOSED_ERROR_MESSAGE)
|
||||
void abandonDictation(dictationSessionErrorMessage('input-closed'))
|
||||
}
|
||||
}, [abandonDictation, enabled])
|
||||
|
||||
|
||||
@@ -1,6 +1,7 @@
|
||||
import { Activity, Edit3, PowerOff, RefreshCw } from 'lucide-react-native'
|
||||
import type { ActionSheetAction } from './components/ActionSheetModal'
|
||||
import type { ConnectionState, HostProfile } from './transport/types'
|
||||
import { translate } from './i18n/mobile-i18n'
|
||||
|
||||
// Defer navigation and new drawers until this native Modal unmounts; presenting into a live one freezes iOS (#8791).
|
||||
export function getHostListActionSheetActions(args: {
|
||||
@@ -26,7 +27,10 @@ export function getHostListActionSheetActions(args: {
|
||||
|
||||
return [
|
||||
{
|
||||
label: args.hasEverConnected && isLive ? 'Reconnect' : 'Connect',
|
||||
label:
|
||||
args.hasEverConnected && isLive
|
||||
? translate('hostScreen.hostListActionSheetActions.reconnect', 'Reconnect')
|
||||
: translate('hostScreen.hostListActionSheetActions.connect', 'Connect'),
|
||||
icon: RefreshCw,
|
||||
onPress: () => {
|
||||
args.onDismiss()
|
||||
@@ -36,7 +40,7 @@ export function getHostListActionSheetActions(args: {
|
||||
...(isLive
|
||||
? [
|
||||
{
|
||||
label: 'Disconnect',
|
||||
label: translate('hostScreen.hostListActionSheetActions.disconnect', 'Disconnect'),
|
||||
icon: PowerOff,
|
||||
onPress: () => {
|
||||
args.onDismiss()
|
||||
@@ -46,7 +50,10 @@ export function getHostListActionSheetActions(args: {
|
||||
]
|
||||
: []),
|
||||
{
|
||||
label: 'Network diagnostics',
|
||||
label: translate(
|
||||
'hostScreen.hostListActionSheetActions.networkDiagnostics',
|
||||
'Network diagnostics'
|
||||
),
|
||||
icon: Activity,
|
||||
closeBeforePress: true,
|
||||
onPress: () => {
|
||||
@@ -54,7 +61,7 @@ export function getHostListActionSheetActions(args: {
|
||||
}
|
||||
},
|
||||
{
|
||||
label: 'Edit host',
|
||||
label: translate('hostScreen.hostListActionSheetActions.editHost', 'Edit host'),
|
||||
icon: Edit3,
|
||||
closeBeforePress: true,
|
||||
onPress: () => {
|
||||
@@ -63,7 +70,7 @@ export function getHostListActionSheetActions(args: {
|
||||
}
|
||||
},
|
||||
{
|
||||
label: 'Remove',
|
||||
label: translate('common.remove', 'Remove'),
|
||||
destructive: true,
|
||||
closeBeforePress: true,
|
||||
onPress: () => {
|
||||
|
||||
@@ -3,8 +3,10 @@ import { colors, spacing, typography } from '../theme/mobile-theme'
|
||||
import { themedStyleSheet } from '../theme/themed-styles'
|
||||
import { useMobileColorScheme } from '../theme/use-mobile-color-scheme'
|
||||
import type { SectionPendingCreation } from '../worktree/workspace-list-types'
|
||||
import { useMobileTranslation } from '../i18n/use-mobile-translation'
|
||||
|
||||
export function HostPendingCreationRow({ creation }: { creation: SectionPendingCreation }) {
|
||||
const t = useMobileTranslation()
|
||||
useMobileColorScheme()
|
||||
return (
|
||||
<View style={styles.row} accessibilityRole="progressbar">
|
||||
@@ -13,7 +15,7 @@ export function HostPendingCreationRow({ creation }: { creation: SectionPendingC
|
||||
</View>
|
||||
<View style={styles.main}>
|
||||
<Text style={styles.title} numberOfLines={1}>
|
||||
Creating workspace…
|
||||
{t('hostScreen.hostPendingCreationRow.creatingWorkspace', 'Creating workspace…')}
|
||||
</Text>
|
||||
{creation.repoName ? (
|
||||
<Text style={styles.meta} numberOfLines={1}>
|
||||
|
||||
@@ -12,6 +12,8 @@ import { themedStyleSheet } from '../theme/themed-styles'
|
||||
import { useMobileColorScheme } from '../theme/use-mobile-color-scheme'
|
||||
import { colors, spacing } from '../theme/mobile-theme'
|
||||
import type { HostScreenController } from './use-host-screen-controller'
|
||||
import type { Translate } from '../i18n/mobile-i18n'
|
||||
import { useMobileTranslation } from '../i18n/use-mobile-translation'
|
||||
|
||||
type HostListAction = {
|
||||
id: 'accounts' | 'tasks' | 'search' | 'floating' | 'new'
|
||||
@@ -22,21 +24,21 @@ type HostListAction = {
|
||||
}
|
||||
|
||||
// One list for the sidebar's toolbar row and the side rail, so both offer the same actions.
|
||||
export function hostListActions(controller: HostScreenController): HostListAction[] {
|
||||
export function hostListActions(controller: HostScreenController, t: Translate): HostListAction[] {
|
||||
const { actions, connState, floatingWorkspaceEnabled, hostId, state } = controller
|
||||
const offline = connState !== 'connected'
|
||||
const hostPath = `/h/${encodeURIComponent(hostId)}`
|
||||
return [
|
||||
{
|
||||
id: 'accounts',
|
||||
label: 'Accounts',
|
||||
label: t('hostScreen.hostRailActions.accounts', 'Accounts'),
|
||||
icon: UserCircle,
|
||||
onPress: () => actions.navigateFromHostList(`${hostPath}/accounts`),
|
||||
disabled: offline
|
||||
},
|
||||
{
|
||||
id: 'tasks',
|
||||
label: 'Tasks',
|
||||
label: t('hostScreen.hostRailActions.tasks', 'Tasks'),
|
||||
icon: List,
|
||||
onPress: () => actions.navigateFromHostList(`${hostPath}/tasks`),
|
||||
disabled: offline
|
||||
@@ -45,7 +47,7 @@ export function hostListActions(controller: HostScreenController): HostListActio
|
||||
? [
|
||||
{
|
||||
id: 'floating' as const,
|
||||
label: 'Floating Workspace',
|
||||
label: t('hostScreen.hostRailActions.floatingWorkspace', 'Floating Workspace'),
|
||||
icon: SquareTerminal,
|
||||
onPress: actions.openFloatingWorkspace,
|
||||
disabled: offline
|
||||
@@ -54,14 +56,16 @@ export function hostListActions(controller: HostScreenController): HostListActio
|
||||
: []),
|
||||
{
|
||||
id: 'new',
|
||||
label: 'New workspace',
|
||||
label: t('hostScreen.hostRailActions.newWorkspace', 'New workspace'),
|
||||
icon: Plus,
|
||||
onPress: actions.openNewWorktreeModal,
|
||||
disabled: offline
|
||||
},
|
||||
{
|
||||
id: 'search',
|
||||
label: state.showSearch ? 'Close search' : 'Search workspaces',
|
||||
label: state.showSearch
|
||||
? t('hostScreen.hostRailActions.closeSearch', 'Close search')
|
||||
: t('hostScreen.hostRailActions.searchWorkspaces', 'Search workspaces'),
|
||||
icon: state.showSearch ? X : Search,
|
||||
onPress: () => state.setShowSearch((shown) => !shown),
|
||||
disabled: false
|
||||
@@ -101,8 +105,9 @@ export function HostRailActions({
|
||||
controller: HostScreenController
|
||||
placement: 'split' | 'cover'
|
||||
}) {
|
||||
const t = useMobileTranslation()
|
||||
useMobileColorScheme()
|
||||
const all = hostListActions(controller)
|
||||
const all = hostListActions(controller, t)
|
||||
if (placement === 'cover') {
|
||||
return (
|
||||
<View style={styles.coverRail}>
|
||||
|
||||
@@ -1,8 +1,6 @@
|
||||
import { Pressable, Text, View } from 'react-native'
|
||||
import {
|
||||
workspaceOptionsAccessibilityLabel,
|
||||
WORKSPACE_OPTIONS_TITLE
|
||||
} from '../worktree/workspace-options-copy'
|
||||
import type { Translate } from '../i18n/mobile-i18n'
|
||||
import { useMobileTranslation } from '../i18n/use-mobile-translation'
|
||||
import { WorkspaceOptionsCountBadge } from './workspace-options-count-badge'
|
||||
import {
|
||||
ChevronLeft,
|
||||
@@ -27,7 +25,25 @@ function isErrorVerdict(v: ConnectionVerdict): boolean {
|
||||
return isConnectionOutage(v) || v.kind === 'auth-failed'
|
||||
}
|
||||
|
||||
function workspaceOptionsAccessibilityLabel(t: Translate, count: number): string {
|
||||
if (count <= 0) {
|
||||
return t('hostScreen.hostScreenHeader.workspaceOptions', 'Workspace options')
|
||||
}
|
||||
return count === 1
|
||||
? t(
|
||||
'hostScreen.hostScreenHeader.activeFilters_one',
|
||||
'Workspace options ({{count}} filter active)',
|
||||
{ count }
|
||||
)
|
||||
: t(
|
||||
'hostScreen.hostScreenHeader.activeFilters_other',
|
||||
'Workspace options ({{count}} filters active)',
|
||||
{ count }
|
||||
)
|
||||
}
|
||||
|
||||
export function HostScreenHeader({ controller }: { controller: HostScreenController }) {
|
||||
const t = useMobileTranslation()
|
||||
useMobileColorScheme()
|
||||
const {
|
||||
actions,
|
||||
@@ -60,7 +76,7 @@ export function HostScreenHeader({ controller }: { controller: HostScreenControl
|
||||
style={styles.backButton}
|
||||
onPress={actions.leaveHost}
|
||||
accessibilityRole="button"
|
||||
accessibilityLabel="Back to hosts"
|
||||
accessibilityLabel={t('hostScreen.hostScreenHeader.backToHosts', 'Back to hosts')}
|
||||
hitSlop={8}
|
||||
>
|
||||
<ChevronLeft size={22} color={colors.textPrimary} />
|
||||
@@ -88,10 +104,12 @@ export function HostScreenHeader({ controller }: { controller: HostScreenControl
|
||||
style={styles.reconnectButton}
|
||||
onPress={() => void forceReconnectHost(hostId)}
|
||||
accessibilityRole="button"
|
||||
accessibilityLabel="Reconnect"
|
||||
accessibilityLabel={t('hostScreen.hostScreenHeader.reconnect', 'Reconnect')}
|
||||
hitSlop={8}
|
||||
>
|
||||
<Text style={styles.reconnectButtonText}>Reconnect</Text>
|
||||
<Text style={styles.reconnectButtonText}>
|
||||
{t('hostScreen.hostScreenHeader.reconnect', 'Reconnect')}
|
||||
</Text>
|
||||
</Pressable>
|
||||
) : null}
|
||||
{!embedded && floatingWorkspaceEnabled && !railActions ? (
|
||||
@@ -103,7 +121,10 @@ export function HostScreenHeader({ controller }: { controller: HostScreenControl
|
||||
onPress={actions.openFloatingWorkspace}
|
||||
disabled={connState !== 'connected'}
|
||||
accessibilityRole="button"
|
||||
accessibilityLabel="Floating Workspace"
|
||||
accessibilityLabel={t(
|
||||
'hostScreen.hostScreenHeader.floatingWorkspace',
|
||||
'Floating Workspace'
|
||||
)}
|
||||
hitSlop={8}
|
||||
>
|
||||
<SquareTerminal
|
||||
@@ -117,7 +138,7 @@ export function HostScreenHeader({ controller }: { controller: HostScreenControl
|
||||
style={styles.sidebarCollapseButton}
|
||||
onPress={onHideSidebar}
|
||||
accessibilityRole="button"
|
||||
accessibilityLabel="Hide sidebar"
|
||||
accessibilityLabel={t('hostScreen.hostScreenHeader.hideSidebar', 'Hide sidebar')}
|
||||
hitSlop={8}
|
||||
>
|
||||
<PanelLeftClose size={14} color={colors.textSecondary} />
|
||||
@@ -136,7 +157,10 @@ export function HostScreenHeader({ controller }: { controller: HostScreenControl
|
||||
]}
|
||||
onPress={() => state.setShowWorkspaceOptions(true)}
|
||||
accessibilityRole="button"
|
||||
accessibilityLabel={workspaceOptionsAccessibilityLabel(controller.activeFilterCount)}
|
||||
accessibilityLabel={workspaceOptionsAccessibilityLabel(
|
||||
t,
|
||||
controller.activeFilterCount
|
||||
)}
|
||||
>
|
||||
<Text
|
||||
style={[
|
||||
@@ -145,7 +169,7 @@ export function HostScreenHeader({ controller }: { controller: HostScreenControl
|
||||
]}
|
||||
numberOfLines={1}
|
||||
>
|
||||
{WORKSPACE_OPTIONS_TITLE}
|
||||
{t('hostScreen.hostScreenHeader.workspaceOptions', 'Workspace options')}
|
||||
</Text>
|
||||
<WorkspaceOptionsCountBadge count={controller.activeFilterCount} />
|
||||
</Pressable>
|
||||
@@ -153,7 +177,7 @@ export function HostScreenHeader({ controller }: { controller: HostScreenControl
|
||||
|
||||
{railActions ? null : (
|
||||
<View style={styles.embeddedToolbarRow}>
|
||||
{hostListActions(controller).map((action) => (
|
||||
{hostListActions(controller, t).map((action) => (
|
||||
<Pressable
|
||||
key={action.id}
|
||||
style={[
|
||||
@@ -186,7 +210,7 @@ export function HostScreenHeader({ controller }: { controller: HostScreenControl
|
||||
style={[styles.filterChip, controller.activeFilterCount > 0 && styles.filterChipActive]}
|
||||
onPress={() => state.setShowWorkspaceOptions(true)}
|
||||
accessibilityRole="button"
|
||||
accessibilityLabel={workspaceOptionsAccessibilityLabel(controller.activeFilterCount)}
|
||||
accessibilityLabel={workspaceOptionsAccessibilityLabel(t, controller.activeFilterCount)}
|
||||
>
|
||||
<Text
|
||||
style={[
|
||||
@@ -194,7 +218,7 @@ export function HostScreenHeader({ controller }: { controller: HostScreenControl
|
||||
controller.activeFilterCount > 0 && styles.filterChipTextActive
|
||||
]}
|
||||
>
|
||||
{WORKSPACE_OPTIONS_TITLE}
|
||||
{t('hostScreen.hostScreenHeader.workspaceOptions', 'Workspace options')}
|
||||
</Text>
|
||||
<WorkspaceOptionsCountBadge count={controller.activeFilterCount} />
|
||||
</Pressable>
|
||||
@@ -209,7 +233,7 @@ export function HostScreenHeader({ controller }: { controller: HostScreenControl
|
||||
}
|
||||
disabled={connState !== 'connected'}
|
||||
accessibilityRole="button"
|
||||
accessibilityLabel="Accounts"
|
||||
accessibilityLabel={t('hostScreen.hostScreenHeader.accounts', 'Accounts')}
|
||||
>
|
||||
<UserCircle
|
||||
size={16}
|
||||
@@ -224,7 +248,7 @@ export function HostScreenHeader({ controller }: { controller: HostScreenControl
|
||||
}
|
||||
disabled={connState !== 'connected'}
|
||||
accessibilityRole="button"
|
||||
accessibilityLabel="Tasks"
|
||||
accessibilityLabel={t('hostScreen.hostScreenHeader.tasks', 'Tasks')}
|
||||
>
|
||||
<List
|
||||
size={16}
|
||||
@@ -236,7 +260,11 @@ export function HostScreenHeader({ controller }: { controller: HostScreenControl
|
||||
style={styles.searchToggle}
|
||||
onPress={() => state.setShowSearch((s) => !s)}
|
||||
accessibilityRole="button"
|
||||
accessibilityLabel={state.showSearch ? 'Close search' : 'Search workspaces'}
|
||||
accessibilityLabel={
|
||||
state.showSearch
|
||||
? t('hostScreen.hostScreenHeader.closeSearch', 'Close search')
|
||||
: t('hostScreen.hostScreenHeader.searchWorkspaces', 'Search workspaces')
|
||||
}
|
||||
>
|
||||
{state.showSearch ? (
|
||||
<X size={16} color={colors.textSecondary} />
|
||||
|
||||
@@ -15,8 +15,10 @@ import { hostScreenStyles as styles } from './host-screen-styles'
|
||||
import { WorkspaceOptionsSheet } from './workspace-options-sheet'
|
||||
import { WorkspaceDetailsSheet } from './workspace-details-sheet'
|
||||
import type { HostScreenController } from './use-host-screen-controller'
|
||||
import { useMobileTranslation } from '../i18n/use-mobile-translation'
|
||||
|
||||
export function HostScreenOverlays({ controller }: { controller: HostScreenController }) {
|
||||
const t = useMobileTranslation()
|
||||
useMobileColorScheme()
|
||||
const {
|
||||
actions,
|
||||
@@ -52,10 +54,18 @@ export function HostScreenOverlays({ controller }: { controller: HostScreenContr
|
||||
{state.confirmDelete ? (
|
||||
<View>
|
||||
<View style={styles.confirmContent}>
|
||||
<Text style={styles.confirmTitle}>Delete Worktree</Text>
|
||||
<Text style={styles.confirmTitle}>
|
||||
{t('hostScreen.hostScreenOverlays.deleteWorktree', 'Delete Worktree')}
|
||||
</Text>
|
||||
<Text style={styles.confirmMessage}>
|
||||
Delete "{state.confirmDelete.displayName || state.confirmDelete.repo}" (
|
||||
{state.confirmDelete.branch})?
|
||||
{t(
|
||||
'hostScreen.hostScreenOverlays.deleteConfirmMessage',
|
||||
'Delete "{{name}}" ({{branch}})?',
|
||||
{
|
||||
name: state.confirmDelete.displayName || state.confirmDelete.repo,
|
||||
branch: state.confirmDelete.branch
|
||||
}
|
||||
)}
|
||||
</Text>
|
||||
</View>
|
||||
<View style={styles.confirmButtons}>
|
||||
@@ -67,7 +77,7 @@ export function HostScreenOverlays({ controller }: { controller: HostScreenContr
|
||||
]}
|
||||
onPress={() => state.setConfirmDelete(null)}
|
||||
>
|
||||
<Text style={styles.confirmBtnCancelText}>Cancel</Text>
|
||||
<Text style={styles.confirmBtnCancelText}>{t('common.cancel', 'Cancel')}</Text>
|
||||
</Pressable>
|
||||
<Pressable
|
||||
style={({ pressed }) => [
|
||||
@@ -83,7 +93,7 @@ export function HostScreenOverlays({ controller }: { controller: HostScreenContr
|
||||
state.setActionTarget(null)
|
||||
}}
|
||||
>
|
||||
<Text style={styles.confirmBtnDestructiveText}>Delete</Text>
|
||||
<Text style={styles.confirmBtnDestructiveText}>{t('common.delete', 'Delete')}</Text>
|
||||
</Pressable>
|
||||
</View>
|
||||
</View>
|
||||
@@ -107,7 +117,7 @@ export function HostScreenOverlays({ controller }: { controller: HostScreenContr
|
||||
? []
|
||||
: [
|
||||
{
|
||||
label: 'Sleep',
|
||||
label: t('hostScreen.hostScreenOverlays.sleep', 'Sleep'),
|
||||
icon: Moon,
|
||||
onPress: () => {
|
||||
actions.putWorktreeToSleep(actionTarget)
|
||||
@@ -116,7 +126,9 @@ export function HostScreenOverlays({ controller }: { controller: HostScreenContr
|
||||
}
|
||||
]),
|
||||
{
|
||||
label: isWorktreePinned(actionTarget, state.pinnedIds) ? 'Unpin' : 'Pin',
|
||||
label: isWorktreePinned(actionTarget, state.pinnedIds)
|
||||
? t('hostScreen.hostScreenOverlays.unpin', 'Unpin')
|
||||
: t('hostScreen.hostScreenOverlays.pin', 'Pin'),
|
||||
icon: isWorktreePinned(actionTarget, state.pinnedIds) ? PinOff : Pin,
|
||||
onPress: () => {
|
||||
actions.togglePin(actionTarget)
|
||||
@@ -124,14 +136,17 @@ export function HostScreenOverlays({ controller }: { controller: HostScreenContr
|
||||
}
|
||||
},
|
||||
{
|
||||
label: 'Delete',
|
||||
label: t('common.delete', 'Delete'),
|
||||
destructive: true,
|
||||
onPress: () => state.setConfirmDelete(actionTarget),
|
||||
// Like desktop, git can't remove a primary checkout; older hosts omit the flag.
|
||||
...(actionTarget.isMainWorktree === true
|
||||
? {
|
||||
disabled: true,
|
||||
hint: "Primary worktree — can't be deleted. Remove the project instead."
|
||||
hint: t(
|
||||
'hostScreen.hostScreenOverlays.primaryWorktreeDeleteHint',
|
||||
"Primary worktree — can't be deleted. Remove the project instead."
|
||||
)
|
||||
}
|
||||
: {})
|
||||
}
|
||||
@@ -154,9 +169,13 @@ export function HostScreenOverlays({ controller }: { controller: HostScreenContr
|
||||
|
||||
<ConfirmModal
|
||||
visible={state.confirmRemoveHost}
|
||||
title="Remove Host"
|
||||
message={`Remove "${state.hostName}"? You can re-pair later.`}
|
||||
confirmLabel="Remove"
|
||||
title={t('hostScreen.hostScreenOverlays.removeHost', 'Remove Host')}
|
||||
message={t(
|
||||
'hostScreen.hostScreenOverlays.removeHostMessage',
|
||||
'Remove "{{name}}"? You can re-pair later.',
|
||||
{ name: state.hostName }
|
||||
)}
|
||||
confirmLabel={t('common.remove', 'Remove')}
|
||||
destructive
|
||||
onConfirm={() => void actions.handleRemoveHost()}
|
||||
onCancel={() => state.setConfirmRemoveHost(false)}
|
||||
|
||||
@@ -32,8 +32,10 @@ import { HostPendingCreationRow } from './host-pending-creation-row'
|
||||
import { useSlowPendingKeys } from './use-slow-pending-keys'
|
||||
import { projectGroupHeaderIndent } from '../worktree/project-group-indent'
|
||||
import type { HostScreenController } from './use-host-screen-controller'
|
||||
import { useMobileTranslation } from '../i18n/use-mobile-translation'
|
||||
|
||||
export function HostWorkspaceList({ controller }: { controller: HostScreenController }) {
|
||||
const t = useMobileTranslation()
|
||||
const colorScheme = useMobileColorScheme()
|
||||
const {
|
||||
actions,
|
||||
@@ -124,11 +126,14 @@ export function HostWorkspaceList({ controller }: { controller: HostScreenContro
|
||||
<MobileSearchField
|
||||
value={state.search}
|
||||
onChangeText={state.setSearch}
|
||||
placeholder="Search workspaces…"
|
||||
placeholder={t('hostScreen.hostWorkspaceList.searchPlaceholder', 'Search workspaces…')}
|
||||
autoFocus
|
||||
// Remount the focus effect per open so rapid search toggles reopen the keyboard.
|
||||
focusKey={state.showSearch}
|
||||
accessibilityLabel="Search workspaces"
|
||||
accessibilityLabel={t(
|
||||
'hostScreen.hostWorkspaceList.searchWorkspaces',
|
||||
'Search workspaces'
|
||||
)}
|
||||
/>
|
||||
</View>
|
||||
)}
|
||||
|
||||
@@ -1,4 +1,5 @@
|
||||
import { useCallback, useEffect, useMemo, useRef, useState } from 'react'
|
||||
import { useMobileTranslation } from '../i18n/use-mobile-translation'
|
||||
import { useLocalSearchParams, usePathname } from 'expo-router'
|
||||
import { useRouteHandoff } from '../navigation/route-handoff'
|
||||
import { useSafeAreaInsets } from 'react-native-safe-area-context'
|
||||
@@ -46,6 +47,7 @@ export function useHostScreenController({
|
||||
onHideSidebar,
|
||||
actionsInRail = false
|
||||
}: HostScreenProps = {}) {
|
||||
useMobileTranslation()
|
||||
const params = useLocalSearchParams<{ hostId: string; notice?: string }>()
|
||||
const hostId = hostIdProp ?? params.hostId
|
||||
const [dismissedNotice, setDismissedNotice] = useState<string | null>(null)
|
||||
|
||||
@@ -27,6 +27,8 @@ import {
|
||||
type WorkspaceOrigin
|
||||
} from '../worktree/workspace-details-model'
|
||||
import type { Worktree } from '../worktree/workspace-list-types'
|
||||
import type { Translate } from '../i18n/mobile-i18n'
|
||||
import { useMobileTranslation } from '../i18n/use-mobile-translation'
|
||||
|
||||
const ICON_SIZE = 16
|
||||
|
||||
@@ -39,19 +41,24 @@ const PROVIDER_LABELS: Record<WorkspaceDetailLink['provider'], string> = {
|
||||
'azure-devops': 'Azure DevOps',
|
||||
gitea: 'Gitea'
|
||||
}
|
||||
const STATE_LABELS: Record<string, string> = {
|
||||
open: 'Open',
|
||||
merged: 'Merged',
|
||||
closed: 'Closed',
|
||||
draft: 'Draft'
|
||||
function stateLabels(t: Translate): Record<string, string> {
|
||||
return {
|
||||
open: t('hostScreen.workspaceDetailsSheet.prOpen', 'Open'),
|
||||
merged: t('hostScreen.workspaceDetailsSheet.merged', 'Merged'),
|
||||
closed: t('hostScreen.workspaceDetailsSheet.closed', 'Closed'),
|
||||
draft: t('hostScreen.workspaceDetailsSheet.draft', 'Draft')
|
||||
}
|
||||
}
|
||||
const CHECK_LABELS: Record<string, string> = {
|
||||
success: 'Checks: Passing',
|
||||
failure: 'Checks: Failing',
|
||||
pending: 'Checks: Pending'
|
||||
function checkLabels(t: Translate): Record<string, string> {
|
||||
return {
|
||||
success: t('hostScreen.workspaceDetailsSheet.checksPassing', 'Checks: Passing'),
|
||||
failure: t('hostScreen.workspaceDetailsSheet.checksFailing', 'Checks: Failing'),
|
||||
pending: t('hostScreen.workspaceDetailsSheet.checksPending', 'Checks: Pending')
|
||||
}
|
||||
}
|
||||
|
||||
export function WorkspaceDetailsSheet(props: { row: Worktree; onOpenLink: (url: string) => void }) {
|
||||
const t = useMobileTranslation()
|
||||
useMobileColorScheme()
|
||||
const { row, onOpenLink } = props
|
||||
const identity = getWorktreeGitIdentityDisplay({ branch: row.branch })
|
||||
@@ -85,7 +92,7 @@ export function WorkspaceDetailsSheet(props: { row: Worktree; onOpenLink: (url:
|
||||
<View style={styles.origin} testID="workspace-origin">
|
||||
<OriginIcon size={12} color={colors.textMuted} style={styles.originIcon} />
|
||||
<Text style={styles.originText} numberOfLines={2}>
|
||||
{originLabel(origin)}
|
||||
{originLabel(origin, t)}
|
||||
</Text>
|
||||
</View>
|
||||
) : null}
|
||||
@@ -99,11 +106,19 @@ export function WorkspaceDetailsSheet(props: { row: Worktree; onOpenLink: (url:
|
||||
)
|
||||
}
|
||||
|
||||
function originLabel(origin: WorkspaceOrigin): string {
|
||||
function originLabel(origin: WorkspaceOrigin, t: Translate): string {
|
||||
if (origin.kind === 'cli') {
|
||||
return 'Created from Orca CLI'
|
||||
return t('hostScreen.workspaceDetailsSheet.createdFromCli', 'Created from Orca CLI')
|
||||
}
|
||||
return origin.name ? `Created by automation \u201c${origin.name}\u201d` : 'Created by automation'
|
||||
return origin.name
|
||||
? t(
|
||||
'hostScreen.workspaceDetailsSheet.createdByNamedAutomation',
|
||||
'Created by automation \u201c{{name}}\u201d',
|
||||
{
|
||||
name: origin.name
|
||||
}
|
||||
)
|
||||
: t('hostScreen.workspaceDetailsSheet.createdByAutomation', 'Created by automation')
|
||||
}
|
||||
|
||||
function DetailRow(props: {
|
||||
@@ -167,27 +182,35 @@ function LinkIcon({ item }: { item: WorkspaceDetailLink }) {
|
||||
return <CircleDot size={ICON_SIZE} color={colors.textSecondary} />
|
||||
}
|
||||
|
||||
function linkMeta(item: WorkspaceDetailLink): string {
|
||||
function linkMeta(item: WorkspaceDetailLink, t: Translate): string {
|
||||
const reference = item.title ? item.label : null
|
||||
if (item.type === 'issue') {
|
||||
return [reference, PROVIDER_LABELS[item.provider]].filter(Boolean).join(' · ')
|
||||
}
|
||||
// As the desktop's tone rule: checks only speak for a review that is still open.
|
||||
const checks = item.state === 'open' && item.checks ? CHECK_LABELS[item.checks] : null
|
||||
return [reference, item.state ? STATE_LABELS[item.state] : null, checks]
|
||||
const checks = item.state === 'open' && item.checks ? checkLabels(t)[item.checks] : null
|
||||
return [reference, item.state ? stateLabels(t)[item.state] : null, checks]
|
||||
.filter(Boolean)
|
||||
.join(' · ')
|
||||
}
|
||||
|
||||
function LinkRow({ item, onOpen }: { item: WorkspaceDetailLink; onOpen: (url: string) => void }) {
|
||||
const t = useMobileTranslation()
|
||||
useMobileColorScheme()
|
||||
const url = item.url
|
||||
const meta = linkMeta(item)
|
||||
const meta = linkMeta(item, t)
|
||||
return (
|
||||
<DetailRow
|
||||
icon={<LinkIcon item={item} />}
|
||||
onPress={url ? () => onOpen(url) : undefined}
|
||||
accessibilityLabel={`Open ${item.label}${item.title ? `, ${item.title}` : ''}`}
|
||||
accessibilityLabel={
|
||||
item.title
|
||||
? t('hostScreen.workspaceDetailsSheet.openLinkWithTitle', 'Open {{label}}, {{title}}', {
|
||||
label: item.label,
|
||||
title: item.title
|
||||
})
|
||||
: t('hostScreen.workspaceDetailsSheet.openLink', 'Open {{label}}', { label: item.label })
|
||||
}
|
||||
trailing={url ? <ExternalLink size={14} color={colors.textMuted} /> : undefined}
|
||||
>
|
||||
<Text style={styles.title} numberOfLines={1}>
|
||||
|
||||
@@ -0,0 +1,120 @@
|
||||
import * as viewOptions from '../../../src/shared/workspace-view/workspace-view-options'
|
||||
import type { WorkspaceViewChoice } from '../../../src/shared/workspace-view/workspace-view-options'
|
||||
import type { WorktreeCardProperty } from '../../../src/shared/ui-chrome-types'
|
||||
import type { Translate } from '../i18n/mobile-i18n'
|
||||
import { phoneCardPropertyChoices } from './phone-card-property-choices'
|
||||
import type { HostScreenController } from './use-host-screen-controller'
|
||||
|
||||
function workspaceOptionChoices<Id extends string>(
|
||||
choices: readonly WorkspaceViewChoice<Id>[],
|
||||
labels: Record<Id, string>,
|
||||
descriptions: Partial<Record<Id, string>> = {}
|
||||
): WorkspaceViewChoice<Id>[] {
|
||||
return choices.map((choice) => ({
|
||||
...choice,
|
||||
label: labels[choice.id],
|
||||
description: descriptions[choice.id] ?? choice.description
|
||||
}))
|
||||
}
|
||||
|
||||
export const groupByChoices = (t: Translate) =>
|
||||
workspaceOptionChoices(viewOptions.WORKSPACE_GROUP_BY_CHOICES, {
|
||||
none: t('hostScreen.workspaceOptionControls.none', 'None'),
|
||||
'workspace-status': t('hostScreen.workspaceOptionControls.status', 'Status'),
|
||||
'pr-status': t('hostScreen.workspaceOptionControls.pr', 'PR'),
|
||||
repo: t('hostScreen.workspaceOptionControls.project', 'Project')
|
||||
})
|
||||
|
||||
export const sortByChoices = (t: Translate) =>
|
||||
workspaceOptionChoices(
|
||||
viewOptions.WORKSPACE_SORT_BY_CHOICES,
|
||||
{
|
||||
name: t('hostScreen.workspaceOptionControls.name', 'Name'),
|
||||
smart: t('hostScreen.workspaceOptionControls.agentActivitySort', 'Agent Activity'),
|
||||
recent: t('hostScreen.workspaceOptionControls.recent', 'Recent'),
|
||||
repo: t('hostScreen.workspaceOptionControls.project', 'Project'),
|
||||
manual: t('hostScreen.workspaceOptionControls.manual', 'Manual')
|
||||
},
|
||||
{
|
||||
smart: t(
|
||||
'hostScreen.workspaceOptionControls.agentActivitySortDescription',
|
||||
'Agents that need attention, then most recent activity.'
|
||||
),
|
||||
manual: t(
|
||||
'hostScreen.workspaceOptionControls.manualWorkspaceOrderDescription',
|
||||
'Drag workspaces to arrange them within each group.'
|
||||
)
|
||||
}
|
||||
)
|
||||
|
||||
export const projectOrderChoices = (t: Translate) =>
|
||||
workspaceOptionChoices(
|
||||
viewOptions.WORKSPACE_PROJECT_ORDER_CHOICES,
|
||||
{
|
||||
manual: t('hostScreen.workspaceOptionControls.manual', 'Manual'),
|
||||
recent: t('hostScreen.workspaceOptionControls.recent', 'Recent')
|
||||
},
|
||||
{
|
||||
manual: t(
|
||||
'hostScreen.workspaceOptionControls.manualProjectOrderDescription',
|
||||
'Drag projects to arrange them'
|
||||
),
|
||||
recent: t(
|
||||
'hostScreen.workspaceOptionControls.recentProjectOrderDescription',
|
||||
'Most recent workspace activity'
|
||||
)
|
||||
}
|
||||
)
|
||||
|
||||
export const cardLayoutChoices = (t: Translate) =>
|
||||
workspaceOptionChoices(viewOptions.WORKSPACE_CARD_LAYOUT_CHOICES, {
|
||||
detailed: t('hostScreen.workspaceOptionControls.detailed', 'Detailed'),
|
||||
compact: t('hostScreen.workspaceOptionControls.compact', 'Compact')
|
||||
})
|
||||
|
||||
export const activityLayoutChoices = (t: Translate) =>
|
||||
workspaceOptionChoices(viewOptions.WORKSPACE_AGENT_ACTIVITY_LAYOUT_CHOICES, {
|
||||
compact: t('hostScreen.workspaceOptionControls.compact', 'Compact'),
|
||||
full: t('hostScreen.workspaceOptionControls.fullList', 'Full list')
|
||||
})
|
||||
|
||||
export const cardPropertyChoices = (t: Translate) =>
|
||||
phoneCardPropertyChoices(
|
||||
workspaceOptionChoices(viewOptions.WORKSPACE_CARD_PROPERTY_CHOICES, {
|
||||
issue: t('hostScreen.workspaceOptionControls.githubTicket', 'GitHub ticket'),
|
||||
'linear-issue': t('hostScreen.workspaceOptionControls.linearIssue', 'Linear issue'),
|
||||
'jira-issue': t('hostScreen.workspaceOptionControls.jiraIssue', 'Jira issue'),
|
||||
pr: t('hostScreen.workspaceOptionControls.prMrLink', 'PR/MR link'),
|
||||
automation: t('hostScreen.workspaceOptionControls.automation', 'Automation'),
|
||||
comment: t('hostScreen.workspaceOptionControls.notes', 'Notes'),
|
||||
ports: t('hostScreen.workspaceOptionControls.ports', 'Ports'),
|
||||
'inline-agents': t('hostScreen.workspaceOptionControls.agentActivity', 'Agent activity'),
|
||||
host: t('hostScreen.workspaceOptionControls.host', 'Host')
|
||||
})
|
||||
)
|
||||
|
||||
// Infer project-group availability from folder rows: hosts list folder workspaces only under a group.
|
||||
export function cardDisplayChoices(
|
||||
controller: HostScreenController,
|
||||
t: Translate
|
||||
): WorkspaceViewChoice<WorktreeCardProperty>[] {
|
||||
const hasProjectGroups = controller.state.worktrees.some(
|
||||
(row) => row.workspaceKind === 'folder-workspace'
|
||||
)
|
||||
return phoneCardPropertyChoices(
|
||||
workspaceOptionChoices(viewOptions.WORKSPACE_CARD_DISPLAY_CHOICES, {
|
||||
issue: t('hostScreen.workspaceOptionControls.githubIssues', 'GitHub issues'),
|
||||
'linear-issue': t('hostScreen.workspaceOptionControls.linearIssues', 'Linear issues'),
|
||||
'jira-issue': t('hostScreen.workspaceOptionControls.jiraIssues', 'Jira issues'),
|
||||
comment: t('hostScreen.workspaceOptionControls.notes', 'Notes'),
|
||||
automation: t('hostScreen.workspaceOptionControls.automation', 'Automation'),
|
||||
cli: 'Orca CLI',
|
||||
ports: t('hostScreen.workspaceOptionControls.ports', 'Ports'),
|
||||
'inline-agents': t('hostScreen.workspaceOptionControls.agentStatuses', 'Agent statuses'),
|
||||
host: t('hostScreen.workspaceOptionControls.host', 'Host'),
|
||||
branch: hasProjectGroups
|
||||
? t('hostScreen.workspaceOptionControls.branchFolderPath', 'Branch / folder path')
|
||||
: t('hostScreen.workspaceOptionControls.branchName', 'Branch name')
|
||||
})
|
||||
)
|
||||
}
|
||||
@@ -2,35 +2,31 @@ import { useState, type ComponentType } from 'react'
|
||||
import { Text, View } from 'react-native'
|
||||
import { MobileSearchField } from '../components/MobileSearchField'
|
||||
import { MobileSegmentedControl } from '../components/MobileSegmentedControl'
|
||||
import {
|
||||
WORKSPACE_AGENT_ACTIVITY_LAYOUT_CHOICES,
|
||||
WORKSPACE_CARD_DISPLAY_CHOICES,
|
||||
WORKSPACE_CARD_LAYOUT_CHOICES,
|
||||
WORKSPACE_CARD_PROPERTY_CHOICES,
|
||||
WORKSPACE_GROUP_BY_CHOICES,
|
||||
WORKSPACE_PROJECT_ORDER_CHOICES,
|
||||
WORKSPACE_SORT_BY_CHOICES,
|
||||
getWorkspaceCardDisplayChoiceLabel,
|
||||
getWorkspaceCardPropertiesValue,
|
||||
type WorkspaceViewChoice,
|
||||
type WorkspaceViewOptionKey
|
||||
import * as viewOptions from '../../../src/shared/workspace-view/workspace-view-options'
|
||||
import type {
|
||||
WorkspaceViewChoice,
|
||||
WorkspaceViewOptionKey
|
||||
} from '../../../src/shared/workspace-view/workspace-view-options'
|
||||
import {
|
||||
getCheckedHostRows,
|
||||
toggleAllVisibleHosts,
|
||||
toggleVisibleHostRow
|
||||
} from '../../../src/shared/workspace-view/workspace-host-visibility'
|
||||
import * as hostVisibility from '../../../src/shared/workspace-view/workspace-host-visibility'
|
||||
import type { ExecutionHostId } from '../../../src/shared/execution-host'
|
||||
import type { WorktreeCardProperty } from '../../../src/shared/ui-chrome-types'
|
||||
import { useMobileColorScheme } from '../theme/use-mobile-color-scheme'
|
||||
import { hostScreenStyles as styles } from './host-screen-styles'
|
||||
import {
|
||||
phoneCardPropertyChoices,
|
||||
withoutPhoneHiddenProperties
|
||||
} from './phone-card-property-choices'
|
||||
import { withoutPhoneHiddenProperties } from './phone-card-property-choices'
|
||||
import type { HostScreenController } from './use-host-screen-controller'
|
||||
import {
|
||||
groupByChoices,
|
||||
sortByChoices,
|
||||
projectOrderChoices,
|
||||
cardLayoutChoices,
|
||||
activityLayoutChoices,
|
||||
cardPropertyChoices,
|
||||
cardDisplayChoices
|
||||
} from './workspace-option-controls-copy'
|
||||
import { CheckRow, ChoiceRows, OptionRowList, SwitchRow } from './workspace-option-rows'
|
||||
import { choiceLabel, summarizeChecklist } from './workspace-option-values'
|
||||
import type { Translate } from '../i18n/mobile-i18n'
|
||||
import { useMobileTranslation } from '../i18n/use-mobile-translation'
|
||||
|
||||
export type WorkspaceOptionHost = { id: ExecutionHostId; label: string }
|
||||
|
||||
@@ -45,40 +41,47 @@ export type WorkspaceOptionControl =
|
||||
| { kind: 'inline'; framed: boolean; Row: ComponentType<WorkspaceOptionProps> }
|
||||
| {
|
||||
kind: 'page'
|
||||
value: (props: WorkspaceOptionProps) => string
|
||||
value: (props: WorkspaceOptionProps, t: Translate) => string
|
||||
Page: ComponentType<WorkspaceOptionProps>
|
||||
}
|
||||
|
||||
const CARD_PROPERTY_CHOICES = phoneCardPropertyChoices(WORKSPACE_CARD_PROPERTY_CHOICES)
|
||||
|
||||
// Infer project-group availability from folder rows: hosts list folder workspaces only under a group.
|
||||
function cardDisplayChoices(
|
||||
controller: HostScreenController
|
||||
): WorkspaceViewChoice<WorktreeCardProperty>[] {
|
||||
const hasProjectGroups = controller.state.worktrees.some(
|
||||
(row) => row.workspaceKind === 'folder-workspace'
|
||||
)
|
||||
return phoneCardPropertyChoices(WORKSPACE_CARD_DISPLAY_CHOICES).map((choice) => ({
|
||||
id: choice.id,
|
||||
label: getWorkspaceCardDisplayChoiceLabel(choice, hasProjectGroups)
|
||||
}))
|
||||
function checklistValue(
|
||||
checkedLabels: readonly string[],
|
||||
total: number,
|
||||
allLabel: string,
|
||||
t: Translate
|
||||
): string {
|
||||
const count = checkedLabels.length
|
||||
const summary = summarizeChecklist(checkedLabels, total, allLabel)
|
||||
return count === 0 && count < total
|
||||
? t('hostScreen.workspaceOptionControls.none', 'None')
|
||||
: count > 1 && count < total
|
||||
? t('hostScreen.workspaceOptionControls.selectedCount', '{{count}} of {{total}}', {
|
||||
count,
|
||||
total
|
||||
})
|
||||
: summary
|
||||
}
|
||||
|
||||
function hostsValue({ controller, hosts }: WorkspaceOptionProps): string {
|
||||
function hostsValue({ controller, hosts }: WorkspaceOptionProps, t: Translate): string {
|
||||
const visible = controller.state.view.filters.visibleHostIds
|
||||
const checked = visible ? getCheckedHostRows(hosts, visible) : hosts
|
||||
return summarizeChecklist(
|
||||
const checked = visible ? hostVisibility.getCheckedHostRows(hosts, visible) : hosts
|
||||
return checklistValue(
|
||||
checked.map((host) => host.label),
|
||||
hosts.length,
|
||||
'All hosts'
|
||||
t('hostScreen.workspaceOptionControls.allHosts', 'All hosts'),
|
||||
t
|
||||
)
|
||||
}
|
||||
|
||||
function HostsPage({ controller, hosts }: WorkspaceOptionProps) {
|
||||
const t = useMobileTranslation()
|
||||
useMobileColorScheme()
|
||||
const { settings, state } = controller
|
||||
const visible = state.view.filters.visibleHostIds
|
||||
const checked = new Set(visible ? getCheckedHostRows(hosts, visible).map((host) => host.id) : [])
|
||||
const checked = new Set(
|
||||
visible ? hostVisibility.getCheckedHostRows(hosts, visible).map((host) => host.id) : []
|
||||
)
|
||||
const write = (next: ExecutionHostId[] | null | undefined) => {
|
||||
if (next !== undefined) {
|
||||
settings.setFilter('visibleHostIds', next)
|
||||
@@ -90,12 +93,12 @@ function HostsPage({ controller, hosts }: WorkspaceOptionProps) {
|
||||
{[
|
||||
<CheckRow
|
||||
key="all"
|
||||
label="All hosts"
|
||||
label={t('hostScreen.workspaceOptionControls.allHosts', 'All hosts')}
|
||||
checked={!visible}
|
||||
onPress={() => write(toggleAllVisibleHosts(hosts, visible))}
|
||||
onPress={() => write(hostVisibility.toggleAllVisibleHosts(hosts, visible))}
|
||||
/>,
|
||||
...hosts.map((host) => {
|
||||
const next = toggleVisibleHostRow(hosts, visible, host)
|
||||
const next = hostVisibility.toggleVisibleHostRow(hosts, visible, host)
|
||||
return (
|
||||
<CheckRow
|
||||
key={host.id}
|
||||
@@ -112,18 +115,20 @@ function HostsPage({ controller, hosts }: WorkspaceOptionProps) {
|
||||
)
|
||||
}
|
||||
|
||||
function projectsValue({ controller }: WorkspaceOptionProps): string {
|
||||
function projectsValue({ controller }: WorkspaceOptionProps, t: Translate): string {
|
||||
const repos = controller.sectionsResult.uniqueRepos
|
||||
const selected = controller.state.view.filters.filterRepoIds
|
||||
const checked = selected.length === 0 ? repos : repos.filter((repo) => selected.includes(repo.id))
|
||||
return summarizeChecklist(
|
||||
return checklistValue(
|
||||
checked.map((repo) => repo.name),
|
||||
repos.length,
|
||||
'All projects'
|
||||
t('hostScreen.workspaceOptionControls.allProjects', 'All projects'),
|
||||
t
|
||||
)
|
||||
}
|
||||
|
||||
function ProjectsPage({ controller }: WorkspaceOptionProps) {
|
||||
const t = useMobileTranslation()
|
||||
useMobileColorScheme()
|
||||
const { settings, state } = controller
|
||||
const [query, setQuery] = useState('')
|
||||
@@ -138,15 +143,21 @@ function ProjectsPage({ controller }: WorkspaceOptionProps) {
|
||||
<MobileSearchField
|
||||
value={query}
|
||||
onChangeText={setQuery}
|
||||
placeholder="Search projects…"
|
||||
accessibilityLabel="Search projects"
|
||||
placeholder={t(
|
||||
'hostScreen.workspaceOptionControls.searchProjectsPlaceholder',
|
||||
'Search projects…'
|
||||
)}
|
||||
accessibilityLabel={t(
|
||||
'hostScreen.workspaceOptionControls.searchProjects',
|
||||
'Search projects'
|
||||
)}
|
||||
/>
|
||||
</View>
|
||||
<OptionRowList>
|
||||
{[
|
||||
<CheckRow
|
||||
key="all"
|
||||
label="All projects"
|
||||
label={t('hostScreen.workspaceOptionControls.allProjects', 'All projects')}
|
||||
checked={selected.length === 0}
|
||||
onPress={() => settings.setFilter('filterRepoIds', [])}
|
||||
/>,
|
||||
@@ -166,22 +177,25 @@ function ProjectsPage({ controller }: WorkspaceOptionProps) {
|
||||
}
|
||||
|
||||
function choicePage<Id extends string>(
|
||||
choices: readonly WorkspaceViewChoice<Id>[],
|
||||
choices: (t: Translate) => readonly WorkspaceViewChoice<Id>[],
|
||||
selected: (controller: HostScreenController) => Id,
|
||||
onSelect: (controller: HostScreenController) => (id: Id) => void
|
||||
): Extract<WorkspaceOptionControl, { kind: 'page' }> {
|
||||
return {
|
||||
kind: 'page',
|
||||
value: ({ controller }) => choiceLabel(choices, selected(controller)),
|
||||
Page: ({ controller }) => (
|
||||
<View style={styles.filterGroup}>
|
||||
<ChoiceRows
|
||||
choices={choices}
|
||||
selected={selected(controller)}
|
||||
onSelect={onSelect(controller)}
|
||||
/>
|
||||
</View>
|
||||
)
|
||||
value: ({ controller }, t) => choiceLabel(choices(t), selected(controller)),
|
||||
Page: function ChoicePage({ controller }) {
|
||||
const t = useMobileTranslation()
|
||||
return (
|
||||
<View style={styles.filterGroup}>
|
||||
<ChoiceRows
|
||||
choices={choices(t)}
|
||||
selected={selected(controller)}
|
||||
onSelect={onSelect(controller)}
|
||||
/>
|
||||
</View>
|
||||
)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -207,44 +221,60 @@ function CardPropertyRows(props: {
|
||||
)
|
||||
}
|
||||
|
||||
function showPropertiesValue({ controller }: WorkspaceOptionProps): string {
|
||||
const value = getWorkspaceCardPropertiesValue(
|
||||
function showPropertiesValue({ controller }: WorkspaceOptionProps, t: Translate): string {
|
||||
const value = viewOptions.getWorkspaceCardPropertiesValue(
|
||||
withoutPhoneHiddenProperties(controller.state.view.worktreeCardProperties),
|
||||
controller.state.view.compactWorktreeCards
|
||||
)
|
||||
return value === 'hover' ? 'Hover' : value === null ? '' : String(value)
|
||||
return value === 'hover'
|
||||
? t('hostScreen.workspaceOptionControls.hover', 'Hover')
|
||||
: value === null
|
||||
? ''
|
||||
: String(value)
|
||||
}
|
||||
|
||||
function propertiesPage(
|
||||
choices: (
|
||||
controller: HostScreenController
|
||||
controller: HostScreenController,
|
||||
t: Translate
|
||||
) => readonly WorkspaceViewChoice<WorktreeCardProperty>[],
|
||||
options: { value: (props: WorkspaceOptionProps) => string; withAgentLayout: boolean }
|
||||
options: {
|
||||
value: (props: WorkspaceOptionProps, t: Translate) => string
|
||||
withAgentLayout: boolean
|
||||
}
|
||||
): Extract<WorkspaceOptionControl, { kind: 'page' }> {
|
||||
const { withAgentLayout } = options
|
||||
return {
|
||||
kind: 'page',
|
||||
value: options.value,
|
||||
Page: ({ controller }) => (
|
||||
<>
|
||||
<CardPropertyRows controller={controller} choices={choices(controller)} />
|
||||
{withAgentLayout && (
|
||||
<>
|
||||
<Text style={styles.filterSectionLabel}>Agent activity layout</Text>
|
||||
<MobileSegmentedControl
|
||||
segments={WORKSPACE_AGENT_ACTIVITY_LAYOUT_CHOICES.map(({ id, label }) => ({
|
||||
value: id,
|
||||
label
|
||||
}))}
|
||||
selected={controller.state.view.agentActivityDisplayMode}
|
||||
onSelect={controller.settings.setAgentActivityDisplayMode}
|
||||
fill
|
||||
style={styles.optionSegmented}
|
||||
/>
|
||||
</>
|
||||
)}
|
||||
</>
|
||||
)
|
||||
Page: function PropertiesPage({ controller }) {
|
||||
const t = useMobileTranslation()
|
||||
return (
|
||||
<>
|
||||
<CardPropertyRows controller={controller} choices={choices(controller, t)} />
|
||||
{withAgentLayout && (
|
||||
<>
|
||||
<Text style={styles.filterSectionLabel}>
|
||||
{t(
|
||||
'hostScreen.workspaceOptionControls.agentActivityLayout',
|
||||
'Agent activity layout'
|
||||
)}
|
||||
</Text>
|
||||
<MobileSegmentedControl
|
||||
segments={activityLayoutChoices(t).map(({ id, label }) => ({
|
||||
value: id,
|
||||
label
|
||||
}))}
|
||||
selected={controller.state.view.agentActivityDisplayMode}
|
||||
onSelect={controller.settings.setAgentActivityDisplayMode}
|
||||
fill
|
||||
style={styles.optionSegmented}
|
||||
/>
|
||||
</>
|
||||
)}
|
||||
</>
|
||||
)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -277,32 +307,35 @@ export const WORKSPACE_OPTION_CONTROLS: Record<WorkspaceViewOptionKey, Workspace
|
||||
groupBy: {
|
||||
kind: 'inline',
|
||||
framed: false,
|
||||
Row: ({ controller }) => (
|
||||
<MobileSegmentedControl
|
||||
segments={WORKSPACE_GROUP_BY_CHOICES.map(({ id, label }) => ({ value: id, label }))}
|
||||
selected={controller.state.view.groupBy}
|
||||
onSelect={controller.settings.handleGroupChange}
|
||||
fill
|
||||
style={styles.optionSegmented}
|
||||
/>
|
||||
)
|
||||
Row: function GroupByRow({ controller }) {
|
||||
const t = useMobileTranslation()
|
||||
return (
|
||||
<MobileSegmentedControl
|
||||
segments={groupByChoices(t).map(({ id, label }) => ({ value: id, label }))}
|
||||
selected={controller.state.view.groupBy}
|
||||
onSelect={controller.settings.handleGroupChange}
|
||||
fill
|
||||
style={styles.optionSegmented}
|
||||
/>
|
||||
)
|
||||
}
|
||||
},
|
||||
sortBy: choicePage(
|
||||
WORKSPACE_SORT_BY_CHOICES,
|
||||
sortByChoices,
|
||||
(controller) => controller.state.view.sortBy,
|
||||
(controller) => controller.settings.handleSortChange
|
||||
),
|
||||
projectOrderBy: choicePage(
|
||||
WORKSPACE_PROJECT_ORDER_CHOICES,
|
||||
projectOrderChoices,
|
||||
(controller) => controller.projectOrderBy,
|
||||
(controller) => controller.settings.setProjectOrderBy
|
||||
),
|
||||
cardLayout: choicePage(
|
||||
WORKSPACE_CARD_LAYOUT_CHOICES,
|
||||
cardLayoutChoices,
|
||||
(controller) => (controller.state.view.compactWorktreeCards ? 'compact' : 'detailed'),
|
||||
(controller) => controller.settings.setCardLayout
|
||||
),
|
||||
cardProperties: propertiesPage(() => CARD_PROPERTY_CHOICES, {
|
||||
cardProperties: propertiesPage((_controller, t) => cardPropertyChoices(t), {
|
||||
value: showPropertiesValue,
|
||||
withAgentLayout: true
|
||||
}),
|
||||
|
||||
@@ -11,15 +11,13 @@ import {
|
||||
type ExecutionHostId
|
||||
} from '../../../src/shared/execution-host'
|
||||
import {
|
||||
WORKSPACE_VIEW_SECTION_LABELS,
|
||||
getShownWorkspaceViewSections,
|
||||
type WorkspaceViewOptionKey
|
||||
type WorkspaceViewOptionKey,
|
||||
type WorkspaceViewSection
|
||||
} from '../../../src/shared/workspace-view/workspace-view-options'
|
||||
import { getHostContextLabel } from '../../../src/shared/worktree/host-context-labels'
|
||||
import {
|
||||
WORKSPACE_OPTIONS_TITLE,
|
||||
WORKSPACE_VIEW_SHARED_NOTE
|
||||
} from '../worktree/workspace-options-copy'
|
||||
import type { Translate } from '../i18n/mobile-i18n'
|
||||
import { useMobileTranslation } from '../i18n/use-mobile-translation'
|
||||
import { resolveWorktreeHostId } from '../worktree/worktree-host-context-labels'
|
||||
import { withServerHosts } from '../worktree/server-workspaces'
|
||||
import { hostScreenStyles as styles } from './host-screen-styles'
|
||||
@@ -32,7 +30,55 @@ import {
|
||||
type WorkspaceOptionProps
|
||||
} from './workspace-option-controls'
|
||||
|
||||
function workspaceOptionLabels(t: Translate): Record<WorkspaceViewOptionKey, string> {
|
||||
return {
|
||||
hosts: t('hostScreen.workspaceOptionsSheet.hosts', 'Hosts'),
|
||||
projects: t('hostScreen.workspaceOptionsSheet.projects', 'Projects'),
|
||||
groupBy: t('hostScreen.workspaceOptionsSheet.groupBy', 'Group by'),
|
||||
sortBy: t('hostScreen.workspaceOptionsSheet.sortBy', 'Sort by'),
|
||||
projectOrderBy: t('hostScreen.workspaceOptionsSheet.projectOrder', 'Project order'),
|
||||
cardLayout: t('hostScreen.workspaceOptionsSheet.cardLayout', 'Card layout'),
|
||||
cardProperties: t('hostScreen.workspaceOptionsSheet.showProperties', 'Show properties'),
|
||||
cardDisplay: t('hostScreen.workspaceOptionsSheet.cardDisplay', 'Card display'),
|
||||
hideSleepingWorkspaces: t('hostScreen.workspaceOptionsSheet.hideSleeping', 'Hide sleeping'),
|
||||
alwaysShowDefaultBranchWorkspace: t(
|
||||
'hostScreen.workspaceOptionsSheet.exceptDefaultBranch',
|
||||
'Except default branch'
|
||||
),
|
||||
hideDefaultBranchWorkspace: t(
|
||||
'hostScreen.workspaceOptionsSheet.hideDefaultBranch',
|
||||
'Hide default branch'
|
||||
),
|
||||
hideAutomationGeneratedWorkspaces: t(
|
||||
'hostScreen.workspaceOptionsSheet.hideAutomationCreated',
|
||||
'Hide automation-created'
|
||||
),
|
||||
hideCliCreatedWorkspaces: t(
|
||||
'hostScreen.workspaceOptionsSheet.hideCliCreated',
|
||||
'Hide CLI-created'
|
||||
),
|
||||
hideWorkspacesFromOtherDevices: t(
|
||||
'hostScreen.workspaceOptionsSheet.hideOtherClientWorkspaces',
|
||||
'Hide other-client workspaces'
|
||||
),
|
||||
hideDetachedHeadWorkspaces: t(
|
||||
'hostScreen.workspaceOptionsSheet.hideDetachedHead',
|
||||
'Hide detached HEAD'
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
function workspaceSectionLabels(t: Translate): Record<WorkspaceViewSection, string | null> {
|
||||
return {
|
||||
show: t('hostScreen.workspaceOptionsSheet.show', 'Show'),
|
||||
group: t('hostScreen.workspaceOptionsSheet.groupBy', 'Group by'),
|
||||
arrange: null,
|
||||
filters: t('hostScreen.workspaceOptionsSheet.filters', 'Filters')
|
||||
}
|
||||
}
|
||||
|
||||
function useWorkspaceOptionHosts(controller: HostScreenController): WorkspaceOptionHost[] {
|
||||
const t = useMobileTranslation()
|
||||
const { displayWorktrees, state } = controller
|
||||
return useMemo(() => {
|
||||
const ids = new Set<ExecutionHostId>([LOCAL_EXECUTION_HOST_ID])
|
||||
@@ -44,16 +90,27 @@ function useWorkspaceOptionHosts(controller: HostScreenController): WorkspaceOpt
|
||||
state.serverWorkspaces.hosts,
|
||||
(host) => host.label
|
||||
)
|
||||
return [...ids].map((id) => ({
|
||||
id,
|
||||
label: getHostContextLabel(id, { hostLabelById, hostPlatform: state.hostPlatform })
|
||||
}))
|
||||
return [...ids].map((id) => {
|
||||
let label = getHostContextLabel(id, { hostLabelById, hostPlatform: state.hostPlatform })
|
||||
if (!hostLabelById.get(id)?.trim() && parseExecutionHostId(id)?.kind === 'local') {
|
||||
label =
|
||||
state.hostPlatform === 'darwin'
|
||||
? t('hostScreen.workspaceOptionsSheet.localMac', 'Local Mac')
|
||||
: state.hostPlatform === 'win32'
|
||||
? t('hostScreen.workspaceOptionsSheet.localWindows', 'Local Windows')
|
||||
: state.hostPlatform === 'linux'
|
||||
? t('hostScreen.workspaceOptionsSheet.localLinux', 'Local Linux')
|
||||
: t('hostScreen.workspaceOptionsSheet.thisComputer', 'This computer')
|
||||
}
|
||||
return { id, label }
|
||||
})
|
||||
}, [
|
||||
displayWorktrees,
|
||||
state.repoHostIdByRepoId,
|
||||
state.serverWorkspaces.hosts,
|
||||
state.hostLabelById,
|
||||
state.hostPlatform
|
||||
state.hostPlatform,
|
||||
t
|
||||
])
|
||||
}
|
||||
|
||||
@@ -73,6 +130,7 @@ export function WorkspaceOptionsSheet({ controller }: { controller: HostScreenCo
|
||||
}
|
||||
|
||||
function WorkspaceOptionsPending({ settings }: Pick<HostScreenController, 'settings'>) {
|
||||
const t = useMobileTranslation()
|
||||
useMobileColorScheme()
|
||||
const { pending, stale } = settings.pendingViewChanges
|
||||
if (!pending.some((change) => change.kind === 'view')) {
|
||||
@@ -82,7 +140,9 @@ function WorkspaceOptionsPending({ settings }: Pick<HostScreenController, 'setti
|
||||
<View style={styles.optionPending} accessibilityLiveRegion="polite">
|
||||
<ActivityIndicator size="small" color={colors.textMuted} />
|
||||
<Text style={styles.filterModalSubtitle}>
|
||||
{stale ? 'Still waiting for the desktop' : 'Saving to the desktop'}
|
||||
{stale
|
||||
? t('hostScreen.workspaceOptionsSheet.waitingForDesktop', 'Still waiting for the desktop')
|
||||
: t('hostScreen.workspaceOptionsSheet.savingToDesktop', 'Saving to the desktop')}
|
||||
</Text>
|
||||
</View>
|
||||
)
|
||||
@@ -96,9 +156,12 @@ function WorkspaceOptionsContent({
|
||||
controller: HostScreenController
|
||||
pager: OptionPagerControls<WorkspaceViewOptionKey>
|
||||
}) {
|
||||
const t = useMobileTranslation()
|
||||
useMobileColorScheme()
|
||||
const { settings, state } = controller
|
||||
const hosts = useWorkspaceOptionHosts(controller)
|
||||
const optionLabels = workspaceOptionLabels(t)
|
||||
const sectionLabels = workspaceSectionLabels(t)
|
||||
const sections = getShownWorkspaceViewSections({
|
||||
groupBy: state.view.groupBy,
|
||||
hideSleepingWorkspaces: state.view.filters.hideSleepingWorkspaces,
|
||||
@@ -109,7 +172,10 @@ function WorkspaceOptionsContent({
|
||||
// Why the host's say-so: a host that keeps the filter to itself cannot apply the phone's value.
|
||||
mayHaveServerHosts:
|
||||
state.view.sharesOtherClientFilter === true && state.serverWorkspaces.hosts.length > 0
|
||||
})
|
||||
}).map(({ section, options }) => ({
|
||||
section,
|
||||
options: options.map((option) => ({ ...option, label: optionLabels[option.key] }))
|
||||
}))
|
||||
|
||||
const pageOption = sections
|
||||
.flatMap(({ options }) => options)
|
||||
@@ -131,10 +197,15 @@ function WorkspaceOptionsContent({
|
||||
style={styles.optionBack}
|
||||
onPress={pager.close}
|
||||
accessibilityRole="button"
|
||||
accessibilityLabel={`Back to ${WORKSPACE_OPTIONS_TITLE}`}
|
||||
accessibilityLabel={t(
|
||||
'hostScreen.workspaceOptionsSheet.backToOptions',
|
||||
'Back to Workspace options'
|
||||
)}
|
||||
>
|
||||
<ChevronLeft size={16} color={colors.textSecondary} />
|
||||
<Text style={styles.optionBackText}>{WORKSPACE_OPTIONS_TITLE}</Text>
|
||||
<Text style={styles.optionBackText}>
|
||||
{t('hostScreen.workspaceOptionsSheet.workspaceOptions', 'Workspace options')}
|
||||
</Text>
|
||||
</Pressable>
|
||||
<Text style={[styles.filterModalTitle, styles.optionPageTitle]}>{pageOption.label}</Text>
|
||||
<pageControl.Page controller={controller} hosts={hosts} label={pageOption.label} />
|
||||
@@ -145,19 +216,28 @@ function WorkspaceOptionsContent({
|
||||
<>
|
||||
<View style={styles.filterModalHeader}>
|
||||
<View style={styles.filterModalHeading}>
|
||||
<Text style={styles.filterModalTitle}>{WORKSPACE_OPTIONS_TITLE}</Text>
|
||||
<Text style={styles.filterModalSubtitle}>{WORKSPACE_VIEW_SHARED_NOTE}</Text>
|
||||
<Text style={styles.filterModalTitle}>
|
||||
{t('hostScreen.workspaceOptionsSheet.workspaceOptions', 'Workspace options')}
|
||||
</Text>
|
||||
<Text style={styles.filterModalSubtitle}>
|
||||
{t(
|
||||
'hostScreen.workspaceOptionsSheet.syncedAcrossDevices',
|
||||
'Synced across your devices'
|
||||
)}
|
||||
</Text>
|
||||
<WorkspaceOptionsPending settings={settings} />
|
||||
</View>
|
||||
{controller.activeFilterCount > 0 && (
|
||||
<Pressable onPress={settings.clearFilters}>
|
||||
<Text style={styles.clearFiltersText}>Clear filters</Text>
|
||||
<Text style={styles.clearFiltersText}>
|
||||
{t('hostScreen.workspaceOptionsSheet.clearFilters', 'Clear filters')}
|
||||
</Text>
|
||||
</Pressable>
|
||||
)}
|
||||
</View>
|
||||
|
||||
{sections.map(({ section, options }) => {
|
||||
const sectionLabel = WORKSPACE_VIEW_SECTION_LABELS[section]
|
||||
const sectionLabel = sectionLabels[section]
|
||||
const bareRows: ReactNode[] = []
|
||||
const framedRows: ReactNode[] = []
|
||||
for (const option of options) {
|
||||
@@ -168,7 +248,7 @@ function WorkspaceOptionsContent({
|
||||
<DrillRow
|
||||
key={option.key}
|
||||
label={option.label}
|
||||
value={control.value(props)}
|
||||
value={control.value(props, t)}
|
||||
onPress={() => pager.open(option.key)}
|
||||
/>
|
||||
)
|
||||
|
||||
@@ -1 +1,57 @@
|
||||
{}
|
||||
{
|
||||
"agentHistory": {
|
||||
"panel": {
|
||||
"skipped_one": "{{count}} transcript skipped",
|
||||
"skipped_other": "{{count}} transcripts skipped"
|
||||
},
|
||||
"sessionHistoryList": {
|
||||
"messages_one": "{{count}} message",
|
||||
"messages_other": "{{count}} messages"
|
||||
}
|
||||
},
|
||||
"app": {
|
||||
"terminalSettings": {
|
||||
"restoreAfterMinutes_one": "After {{count}} minute",
|
||||
"restoreAfterMinutes_other": "After {{count}} minutes",
|
||||
"restoreAfterSeconds_one": "After {{count}}s",
|
||||
"restoreAfterSeconds_other": "After {{count}}s"
|
||||
}
|
||||
},
|
||||
"dictation": {
|
||||
"setup": {
|
||||
"alreadyActive": "Dictation is already in use on the computer or another phone.",
|
||||
"repairRequired": "Re-pair this phone with the computer to use dictation.",
|
||||
"sessionLost": "Dictation lost its connection to the computer. Try again."
|
||||
}
|
||||
},
|
||||
"hostScreen": {
|
||||
"hostScreenHeader": {
|
||||
"activeFilters_one": "Workspace options ({{count}} filter active)",
|
||||
"activeFilters_other": "Workspace options ({{count}} filters active)"
|
||||
}
|
||||
},
|
||||
"settings": {
|
||||
"appUpdateSettingsRows": {
|
||||
"lastCheckedDays_one": "Last checked {{count}}d ago",
|
||||
"lastCheckedDays_other": "Last checked {{count}}d ago",
|
||||
"lastCheckedHours_one": "Last checked {{count}}h ago",
|
||||
"lastCheckedHours_other": "Last checked {{count}}h ago",
|
||||
"lastCheckedMinutes_one": "Last checked {{count}}m ago",
|
||||
"lastCheckedMinutes_other": "Last checked {{count}}m ago"
|
||||
},
|
||||
"pendingCredentialCleanupCard": {
|
||||
"cleanupUnconfirmed_one": "Couldn't confirm cleanup for {{count}} credential on this device.",
|
||||
"cleanupUnconfirmed_other": "Couldn't confirm cleanup for {{count}} credentials on this device."
|
||||
}
|
||||
},
|
||||
"worktree": {
|
||||
"agentRowDisplay": {
|
||||
"days_one": "{{count}}d",
|
||||
"days_other": "{{count}}d",
|
||||
"hours_one": "{{count}}h",
|
||||
"hours_other": "{{count}}h",
|
||||
"minutes_one": "{{count}}m",
|
||||
"minutes_other": "{{count}}m"
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,8 +1,599 @@
|
||||
{
|
||||
"accounts": {
|
||||
"screen": {
|
||||
"agentLogin": "Agenteneigene Anmeldung verwenden",
|
||||
"connecting": "Verbindung zum Host wird hergestellt…",
|
||||
"connectingTo": "Verbindung zu {{name}} wird hergestellt…",
|
||||
"default": "Systemstandard",
|
||||
"hostNotFound": "Host nicht gefunden",
|
||||
"invalidSnapshot": "Ungültiger Kontodaten-Snapshot vom Host",
|
||||
"loadingAccounts": "Konten werden geladen…",
|
||||
"manageAccountsHint": "Konten auf dem Desktop unter Einstellungen → Konten hinzufügen oder erneut authentifizieren.",
|
||||
"refreshAccounts": "Konten aktualisieren",
|
||||
"sessionWindow": "5 h",
|
||||
"switchFailed": "Konto konnte nicht gewechselt werden",
|
||||
"title": "Konten",
|
||||
"useReset": "Jetzt zurücksetzen",
|
||||
"weeklyWindow": "7 Tage"
|
||||
}
|
||||
},
|
||||
"agentHistory": {
|
||||
"panel": {
|
||||
"all": "Alle",
|
||||
"emptyScope": "Keine früheren Agentensitzungen in diesem Bereich.",
|
||||
"loadFailed": "Laden fehlgeschlagen",
|
||||
"missingResumeId": "Dieser Sitzung fehlt eine ID zum Fortsetzen.",
|
||||
"noSearchResults": "Keine Sitzungen entsprechen der Suche.",
|
||||
"noSessions": "Keine Agentensitzungen",
|
||||
"placeholder": "Sitzungen suchen, repo:, path:",
|
||||
"project": "Projekt",
|
||||
"refreshSessions": "Agentensitzungen aktualisieren",
|
||||
"resumeFailed": "Sitzung konnte nicht fortgesetzt werden.",
|
||||
"sessionQueued": "Agentensitzung eingereiht.",
|
||||
"skipped_one": "{{count}} Transkript übersprungen",
|
||||
"skipped_other": "{{count}} Transkripte übersprungen",
|
||||
"title": "Verlauf der Agentensitzungen",
|
||||
"unavailable": "Verlauf der Agentensitzungen nicht verfügbar",
|
||||
"unknownHostPlatform": "Die Host-Plattform konnte nicht ermittelt werden.",
|
||||
"updateHost": "Orca auf diesem Host aktualisieren, um den Verlauf der Agentensitzungen anzuzeigen.",
|
||||
"waitingForHost": "Warten auf Host…",
|
||||
"workspace": "Arbeitsbereich"
|
||||
},
|
||||
"resumeTarget": {
|
||||
"differentHost": "Diese Sitzung gehört zu einem anderen Host. Zum Fortsetzen einen Arbeitsbereich auf demselben Host öffnen.",
|
||||
"openWorkspace": "Vor dem Fortsetzen einer Sitzung einen Arbeitsbereich öffnen.",
|
||||
"runtimeUnavailable": "Bei Arbeitsbereichen, die in einer Laufzeitumgebung gehostet werden, ist das Fortsetzen aus dem Verlauf nicht verfügbar."
|
||||
},
|
||||
"sessionCard": {
|
||||
"untitledSession": "Sitzung ohne Titel"
|
||||
},
|
||||
"sessionHistoryList": {
|
||||
"assistantRole": "Assistent",
|
||||
"currentWorktree": "Aktueller Worktree",
|
||||
"messages_one": "{{count}} Nachricht",
|
||||
"messages_other": "{{count}} Nachrichten",
|
||||
"resumeSession": "Agentensitzung fortsetzen",
|
||||
"userRole": "Benutzer"
|
||||
},
|
||||
"useAgentHistoryState": {
|
||||
"hostUnreachable": "Host nicht erreichbar",
|
||||
"loadFailed": "Agentensitzungen konnten nicht geladen werden",
|
||||
"waitingForHost": "Warten auf Host…"
|
||||
},
|
||||
"worktreeNavigationActions": {
|
||||
"agentSessionHistory": "Verlauf der Agentensitzungen",
|
||||
"sourceControl": "Quellcodeverwaltung"
|
||||
}
|
||||
},
|
||||
"app": {
|
||||
"mobileOnboarding": {
|
||||
"notificationSettingsFailed": "Die Benachrichtigungseinstellungen konnten nicht aktualisiert werden. Erneut versuchen.",
|
||||
"progress": "Einrichtungsfortschritt",
|
||||
"saveChoiceFailed": "Die Auswahl konnte nicht gespeichert werden. Erneut versuchen.",
|
||||
"stepProgress": "Schritt {{step}} von {{total}}"
|
||||
},
|
||||
"notifications": {
|
||||
"description": "Benachrichtigungen von Agenten auch bei geschlossener App erhalten. Die Zustellung erfolgt über den Push-Dienst von Orca und Apple oder Google."
|
||||
},
|
||||
"pair": {
|
||||
"backToHome": "Zurück zur Startseite",
|
||||
"missingCode": "Kopplungscode fehlt"
|
||||
},
|
||||
"pairConfirm": {
|
||||
"backToHome": "Zurück zur Startseite",
|
||||
"connectTimedOut": "Verbindung konnte nicht innerhalb von {{seconds}} s hergestellt werden – das Protokoll unten zeigt, wo der Vorgang hängen blieb",
|
||||
"connecting": "Verbindung wird hergestellt…",
|
||||
"description": "Ein Kopplungslink vom Desktop wurde geöffnet. Bestätigen, um diesen Desktop zu den Hosts hinzuzufügen.",
|
||||
"pairButton": "Koppeln",
|
||||
"pairingFailed": "Kopplung fehlgeschlagen: {{error}}",
|
||||
"pairingLog": "Kopplungsprotokoll",
|
||||
"title": "Mit diesem Desktop koppeln?"
|
||||
},
|
||||
"pairScan": {
|
||||
"cameraDisabledDescription": "Den Kamerazugriff in den Einstellungen aktivieren oder stattdessen den Kopplungscode einfügen.",
|
||||
"cameraDisabledTitle": "Kamerazugriff deaktiviert",
|
||||
"cameraPermissionDescription": "Den QR-Code in Orca auf dem Desktop scannen oder stattdessen den Kopplungscode einfügen.",
|
||||
"connectTimedOut": "Verbindung konnte nicht innerhalb von {{seconds}} s hergestellt werden – das Protokoll unten zeigt, wo der Vorgang hängen blieb",
|
||||
"connecting": "Verbindung wird hergestellt…",
|
||||
"invalidPairingCode": "Kein gültiger Kopplungscode – vom Computer kopieren und erneut einfügen",
|
||||
"invalidQrCode": "Kein gültiger Orca-QR-Code",
|
||||
"openDesktopStep": "Orca auf dem Computer öffnen",
|
||||
"openMobileSettingsStep": "Einstellungen → Mobil öffnen",
|
||||
"openSettings": "Einstellungen öffnen",
|
||||
"pairingFailed": "Kopplung fehlgeschlagen: {{error}}",
|
||||
"pairingLog": "Kopplungsprotokoll",
|
||||
"pasteCodeMessage": "Den Code kopieren, der auf dem Computer unter dem QR-Code angezeigt wird.",
|
||||
"pasteCodePlaceholder": "orca://pair?code=... oder den Code einfügen",
|
||||
"pasteCodeTitle": "Kopplungscode einfügen",
|
||||
"pasteInstead": "Stattdessen Code einfügen",
|
||||
"pastePairingCode": "Oder Kopplungscode einfügen",
|
||||
"scanCodeStep": "QR-Code scannen",
|
||||
"title": "Mit Desktop koppeln",
|
||||
"tryAgain": "Erneut versuchen"
|
||||
},
|
||||
"terminalSettings": {
|
||||
"autocomplete": "Autovervollständigung & Autokorrektur",
|
||||
"autocompleteOff": "Aus",
|
||||
"autocompleteOn": "Ein",
|
||||
"defaultText": "Standard (100 %)",
|
||||
"keepPhoneSize": "Smartphone-Größe beibehalten (Standard)",
|
||||
"keyboardInputDescription": "Autovervollständigung, Autokorrektur und Rechtschreibvorschläge gelten nur für die gepufferte Befehlseingabe. Mit der Doppelpfeil-Schaltfläche über der Tastatur von der direkten zur gepufferten Eingabe wechseln und den Befehl vor dem Tippen auf „Senden“ bearbeiten. Standardmäßig deaktiviert, damit Befehle, Flags und Pfade nicht verändert werden.",
|
||||
"keyboardInputHeading": "TASTATUREINGABE",
|
||||
"largeText": "Groß (125 %)",
|
||||
"largerText": "Größer (150 %)",
|
||||
"largestText": "Am größten (200 %)",
|
||||
"leaveAppDescription": "Während ein Terminal auf dem Smartphone verwendet wird, verkleinert Orca es auf die Bildschirmgröße des Smartphones. Diese Einstellung legt fest, ob es beim Schließen oder Verlassen der App in Smartphone-Größe bleibt (damit interaktive CLI-Tools ihr Layout beibehalten) oder wieder an den Desktop angepasst wird. Über „Wiederherstellen“ im Desktop-Banner lässt sich die Größe jederzeit manuell anpassen.",
|
||||
"leaveAppHeading": "BEIM VERLASSEN DER APP",
|
||||
"noPairedDesktops": "Noch keine Desktops gekoppelt. Einen Desktop koppeln, um das Terminalverhalten festzulegen.",
|
||||
"restoreAfterMinutes_one": "Nach {{count}} Minute",
|
||||
"restoreAfterMinutes_other": "Nach {{count}} Minuten",
|
||||
"restoreAfterSeconds_one": "Nach {{count}} s",
|
||||
"restoreAfterSeconds_other": "Nach {{count}} s",
|
||||
"restoreHost": "{{name}} wiederherstellen",
|
||||
"smallerText": "Kleiner (75 %)",
|
||||
"smallestText": "Am kleinsten (50 %)",
|
||||
"textSize": "Textgröße",
|
||||
"textSizeDescription": "Die Textgröße im Terminal anpassen. Bei kleinerer Schrift passen mehr Spalten mit Seitenrändern auf den Bildschirm; größere Schrift zeigt weniger Spalten – zum Verschieben seitlich ziehen. Die Größe lässt sich auch direkt im Terminal durch Zusammenziehen oder Spreizen zweier Finger ändern; dabei wird diese Einstellung aktualisiert. Gilt nur für die Anzeige auf diesem Gerät und ändert das Desktop-Terminal nicht.",
|
||||
"textSizeHeading": "TEXTGRÖSSE",
|
||||
"textSizePickerTitle": "Textgröße im Terminal",
|
||||
"title": "Terminal"
|
||||
}
|
||||
},
|
||||
"common": {
|
||||
"back": "Zurück"
|
||||
"back": "Zurück",
|
||||
"cancel": "Abbrechen",
|
||||
"continue": "Fortfahren",
|
||||
"copied": "Kopiert",
|
||||
"delete": "Löschen",
|
||||
"ok": "OK",
|
||||
"remove": "Entfernen",
|
||||
"retry": "Erneut versuchen",
|
||||
"save": "Speichern",
|
||||
"settings": "Einstellungen",
|
||||
"unknown": "Unbekannt"
|
||||
},
|
||||
"connection": {
|
||||
"connectionHealth": {
|
||||
"checkConnection": "Verbindung prüfen",
|
||||
"connectFailed": "Verbindung fehlgeschlagen",
|
||||
"connected": "Verbunden",
|
||||
"connecting": "Verbindung wird hergestellt…",
|
||||
"connectingViaRelay": "Verbindung über Relay wird hergestellt…",
|
||||
"desktopUnreachable": "Computer nicht erreichbar",
|
||||
"disconnected": "Getrennt",
|
||||
"hostFallback": "Host",
|
||||
"hostOffline": "{{host}} ist offline",
|
||||
"hostOfflineDetail": "Prüfen, ob der Computer wach ist, Orca läuft und du angemeldet bist",
|
||||
"labelWithHint": "{{label}} – {{hint}}",
|
||||
"pairingInvalid": "Kopplung ungültig – erneut mit dem Computer koppeln",
|
||||
"rePairDetail": "Erneut mit dem Computer koppeln",
|
||||
"reconnecting": "Verbindung wird wiederhergestellt…",
|
||||
"relayAccessExpired": "Relay-Zugriff für {{host}} abgelaufen",
|
||||
"relayConnectFailed": "Verbindung über Relay fehlgeschlagen",
|
||||
"relayUnreachable": "Relay nicht erreichbar",
|
||||
"signInDetail": "Auf dem Computer bei Orca anmelden, um die Verbindung wiederherzustellen",
|
||||
"signInRequired": "Anmeldung auf {{host}} erforderlich",
|
||||
"tailscaleHint": "Tailscale prüfen"
|
||||
},
|
||||
"connectionPathLabel": {
|
||||
"directLan": "Direkt · LAN",
|
||||
"directTailscale": "Direkt · Tailscale",
|
||||
"directViaLan": "Direkt über LAN",
|
||||
"directViaTailscale": "Direkt über Tailscale"
|
||||
},
|
||||
"hostEndpoint": {
|
||||
"addressRequired": "Host-Adresse eingeben.",
|
||||
"hostnameRequired": "Hostname fehlt.",
|
||||
"invalidAddress": "Keine gültige Adresse.",
|
||||
"invalidHostname": "Kein gültiger Hostname.",
|
||||
"invalidPort": "Port muss zwischen 1 und 65535 liegen.",
|
||||
"invalidScheme": "ws:// oder wss:// (oder host:port) verwenden.",
|
||||
"pathOrQueryNotAllowed": "Die Host-Adresse darf keinen Pfad und keine Abfrage enthalten."
|
||||
},
|
||||
"pairConfirmState": {
|
||||
"invalidCode": "Kein gültiger Kopplungscode",
|
||||
"missingCode": "Kopplungscode fehlt"
|
||||
},
|
||||
"rpcClientStreamRegistry": {
|
||||
"endedBeforeReady": "Streaming-Anfrage wurde vor Abschluss der Initialisierung beendet.",
|
||||
"interrupted": "Verbindung unterbrochen"
|
||||
},
|
||||
"rpcClientTerminalBinaryFrame": {
|
||||
"unsupportedFrame": "Nicht unterstützter Terminal-Stream-Frame (Opcode {{opcode}})."
|
||||
}
|
||||
},
|
||||
"diagnostics": {
|
||||
"connectionDiagnosticsView": {
|
||||
"authFailed": "Authentifizierung fehlgeschlagen",
|
||||
"connected": "Verbunden",
|
||||
"connecting": "Verbindung wird hergestellt",
|
||||
"copyReport": "Bericht kopieren",
|
||||
"diagnosisHeading": "Mögliche Ursache",
|
||||
"disconnected": "Getrennt",
|
||||
"handshaking": "Verbindung wird ausgehandelt",
|
||||
"noEvents": "Noch keine Verbindungsereignisse. Sie erscheinen, wenn die App versucht, diesen Host zu erreichen.",
|
||||
"noHosts": "Keine gekoppelten Hosts.",
|
||||
"privacyHint": "Sendet einen um sensible Daten bereinigten Bericht mit begrenzter Größe. Er enthält Host-Name, Endpunkt, Versionen, Verbindungsstatus und Ereignisse, niemals Terminalinhalte oder Zugangsdaten.",
|
||||
"reconnecting": "Verbindung wird wiederhergestellt",
|
||||
"retrySending": "Erneut senden",
|
||||
"sendDiagnostics": "Diagnosedaten an Orca senden",
|
||||
"sending": "Wird gesendet…",
|
||||
"sent": "Diagnosedaten gesendet",
|
||||
"stateWithAttempt": "{{state}} · Versuch {{attempts}}",
|
||||
"title": "Netzwerkdiagnose"
|
||||
},
|
||||
"mobileWebBundleProbeRow": {
|
||||
"assets": "Ressourcen",
|
||||
"build": "Build",
|
||||
"bundle": "Paket",
|
||||
"bytes": "Bytes",
|
||||
"connecting": "Verbindung wird hergestellt…",
|
||||
"elapsed": "Verstrichene Zeit",
|
||||
"elapsedMilliseconds": "{{elapsedMs}} ms",
|
||||
"fetchBundle": "Mobiles Webpaket abrufen",
|
||||
"fetching": "Paket wird abgerufen…",
|
||||
"host": "Host",
|
||||
"unknownHost": "Unbekannter Host"
|
||||
},
|
||||
"mobileWebShellUpdateFailureRow": {
|
||||
"title": "Arbeitsbereichsaktualisierungen"
|
||||
},
|
||||
"troubleshootCommonIssues": {
|
||||
"allowNotifications": "Prüfen, ob Orca-Benachrichtigungen in den Systemeinstellungen erlaubt sind und „Focus“ oder „Do Not Disturb“ deaktiviert ist.",
|
||||
"checkTailnet": "Prüfen, ob der Computer nicht im Ruhezustand ist und im Tailnet als verbunden angezeigt wird.",
|
||||
"desktopTitle": "Desktop-App läuft nicht",
|
||||
"disableVpn": "Dieses VPN deaktivieren oder Split-Tunneling / „Allow LAN“ aktivieren.",
|
||||
"firewallTitle": "Firewall blockiert Port 6768",
|
||||
"keepTailscaleOn": "Host-Adressen wie 100.x.x.x oder *.ts.net nutzen Tailscale – Tailscale eingeschaltet lassen.",
|
||||
"macosFirewall": "macOS: System Settings → Network → Firewall – Orca zulassen.",
|
||||
"notificationNetwork": "Mobilfunk oder ein anderes WLAN ausprobieren. Treffen Benachrichtigungen nach dem Wechsel ein, verzögert das ursprüngliche Netzwerk möglicherweise die Zustellung.",
|
||||
"notificationsTitle": "Push-Benachrichtigungen",
|
||||
"openDesktop": "Orca muss auf dem Computer geöffnet sein, um Verbindungen anzunehmen.",
|
||||
"pairAfterUpdate": "Nach einem Update ist möglicherweise eine erneute Kopplung per QR-Code nötig.",
|
||||
"reconnectWifi": "Die WLAN-Verbindung auf beiden Geräten erneut herstellen.",
|
||||
"restartBothApps": "Bei anhaltenden Zeitüberschreitungen beide Apps neu starten.",
|
||||
"restartDesktop": "Orca neu starten – der Server für die mobile App startet mit Orca.",
|
||||
"restartTailscale": "Unter iOS/Android kann der Tunnel unbemerkt hängen bleiben: Tailscale in der Tailscale-App aus- und wieder einschalten.",
|
||||
"restrictedNetwork": "Firmen- und Schulnetzwerke können P2P blockieren – einen persönlichen Hotspot ausprobieren.",
|
||||
"retryHost": "Zur Host-Liste zurückkehren und den Host antippen, um es erneut zu versuchen.",
|
||||
"sameNetwork": "Beide Geräte müssen sich im selben LAN befinden (außer bei einer Verbindung über Tailscale).",
|
||||
"sameSubnet": "Ethernet und WLAN müssen dasselbe Subnetz nutzen.",
|
||||
"tailscaleTitle": "Tailscale-Host nicht erreichbar",
|
||||
"timeoutTitle": "Zeitüberschreitung beim Verbinden",
|
||||
"updateTailscale": "Die Tailscale-App aktualisieren – neuere Versionen beheben Fehler beim Wiederverbinden.",
|
||||
"vpnRouting": "Andere VPNs als Tailscale können lokalen Datenverkehr über einen entfernten Server leiten.",
|
||||
"vpnTitle": "Störungen durch andere VPNs",
|
||||
"wifiSignal": "Die WLAN-Signalstärke auf dem Smartphone prüfen.",
|
||||
"wifiTitle": "Unterschiedliche WLANs",
|
||||
"windowsFirewall": "Windows: Defender Firewall → Allow app – Orca für Private networks aktivieren."
|
||||
},
|
||||
"troubleshootView": {
|
||||
"commonIssues": "Häufige Probleme",
|
||||
"runAgain": "Erneut ausführen",
|
||||
"runDiagnostics": "Diagnose ausführen",
|
||||
"running": "Läuft…",
|
||||
"title": "Fehlerbehebung",
|
||||
"viewNetworkDiagnostics": "Netzwerkdiagnose anzeigen"
|
||||
},
|
||||
"useTroubleshootDiagnostics": {
|
||||
"connected": "Verbunden",
|
||||
"hostDataFailed": "Host-Daten konnten nicht gelesen werden",
|
||||
"hostTestFailed": "Test nicht möglich",
|
||||
"hosts": "Hosts",
|
||||
"internet": "Internet",
|
||||
"noConnection": "Keine Verbindung",
|
||||
"noPairedHosts": "Keine – zum Koppeln einen QR-Code scannen",
|
||||
"pairedCount": "{{count}} gekoppelt",
|
||||
"pairedHosts": "Gekoppelte Hosts",
|
||||
"platform": "Plattform",
|
||||
"reachable": "Unter {{endpoint}} erreichbar",
|
||||
"unexpectedResponse": "Unerwartete Antwort"
|
||||
},
|
||||
"webShellUpdateFailureCopy": {
|
||||
"failedAgo": "Das letzte Update von {{hostName}} ist vor {{time}} fehlgeschlagen: {{reason}}{{offered}}. {{outcome}}",
|
||||
"failedJustNow": "Das letzte Update von {{hostName}} ist soeben fehlgeschlagen: {{reason}}{{offered}}. {{outcome}}"
|
||||
}
|
||||
},
|
||||
"dictation": {
|
||||
"setup": {
|
||||
"alreadyActive": "Auf dem Computer oder einem anderen Smartphone wird bereits diktiert.",
|
||||
"cloudModelNeedsKey": "OpenAI-Schlüssel erforderlich",
|
||||
"deleteFailed": "Modell konnte nicht gelöscht werden",
|
||||
"desktopUpdateRequired": "Die gekoppelte Orca-Desktop-App aktualisieren, um die Spracheingabeeinstellungen auf dem Smartphone zu nutzen.",
|
||||
"downloadFailed": "Download konnte nicht gestartet werden",
|
||||
"loadFailed": "Diktatmodelle konnten nicht geladen werden",
|
||||
"repairRequired": "Dieses Smartphone erneut mit dem Computer koppeln, um das Diktat zu nutzen.",
|
||||
"serverUpdateRequired": "Orca auf diesem Server aktualisieren, um das Diktat zu nutzen.",
|
||||
"sessionLost": "Die Verbindung zum Computer wurde beim Diktat unterbrochen. Erneut versuchen.",
|
||||
"updateFailed": "Diktateinstellungen konnten nicht aktualisiert werden"
|
||||
}
|
||||
},
|
||||
"home": {
|
||||
"accountUsageCards": {
|
||||
"sectionHeading": "Kontonutzung",
|
||||
"sessionWindow": "5 h",
|
||||
"systemDefault": "Systemstandard",
|
||||
"weeklyWindow": "7 Tage"
|
||||
},
|
||||
"appUpdateCard": {
|
||||
"dismissAccessibilityLabel": "Update-Hinweis für Orca {{version}} ausblenden",
|
||||
"title": "Orca {{version}} ist verfügbar",
|
||||
"updateAccessibilityLabel": "Orca {{version}} ist verfügbar, zum Aktualisieren antippen",
|
||||
"updateHint": "Zum Aktualisieren antippen"
|
||||
},
|
||||
"emptyState": {
|
||||
"connectedDescription": "Der Desktop erscheint hier. Alles ist Ende-zu-Ende-verschlüsselt.",
|
||||
"connectedTitle": "Verbunden",
|
||||
"description": "Mit Orca auf dem Computer koppeln, um Agenten im Blick zu behalten, beliebige Terminals zu öffnen und die Arbeit vom Smartphone aus zu steuern.",
|
||||
"openDesktopDescription": "Einstellungen → Mobil öffnen und einen QR-Code zum Koppeln erstellen.",
|
||||
"openDesktopTitle": "Orca-Desktop-App öffnen",
|
||||
"pairDesktop": "Desktop koppeln",
|
||||
"scanCodeDescription": "Die Schaltfläche oben antippen, um den Scanner zu öffnen. Die Kamera auf den QR-Code auf dem Bildschirm richten.",
|
||||
"scanCodeTitle": "Code scannen",
|
||||
"stepsHeading": "So funktioniert’s",
|
||||
"title": "Desktop verbinden"
|
||||
},
|
||||
"listFooter": {
|
||||
"hostPickerTitle": "Aufgaben auf Host öffnen",
|
||||
"resumeHeading": "Fortsetzen",
|
||||
"tasksHeading": "Aufgaben"
|
||||
},
|
||||
"listHeader": {
|
||||
"agentTime": "Agentenzeit",
|
||||
"agentsSpawned": "Gestartete Agenten",
|
||||
"daysHours": "{{days}} T {{hours}} Std.",
|
||||
"desktopsHeading": "Desktop-Computer",
|
||||
"hoursMinutes": "{{hours}} Std. {{minutes}} Min.",
|
||||
"minutes": "{{minutes}} Min.",
|
||||
"prsCreated": "Erstellte PRs",
|
||||
"title": "Willkommen zurück"
|
||||
},
|
||||
"screen": {
|
||||
"pairingCheckFailed": "Kopplung konnte nicht geprüft werden",
|
||||
"removeHostFailed": "Host konnte nicht entfernt werden",
|
||||
"removeHostMessage": "„{{name}}“ entfernen? Eine erneute Kopplung ist später möglich.",
|
||||
"removeHostTitle": "Host entfernen",
|
||||
"tryAgain": "Bitte erneut versuchen."
|
||||
},
|
||||
"tasksCard": {
|
||||
"noSources": "Keine Aufgabenquellen verbunden",
|
||||
"openProviderTasks": "{{provider}}-Aufgaben öffnen",
|
||||
"title": "Aufgaben"
|
||||
}
|
||||
},
|
||||
"hooks": {
|
||||
"dictationDesktopStart": {
|
||||
"recordingFailed": "Mikrofonaufnahme konnte nicht gestartet werden."
|
||||
},
|
||||
"useMobileDictation": {
|
||||
"inputClosed": "Das Diktat wurde beendet, weil diese Sitzung keine Eingaben mehr annimmt. Erneut versuchen.",
|
||||
"microphoneInitFailed": "Mikrofon konnte nicht initialisiert werden.",
|
||||
"microphoneInterrupted": "Das Diktat wurde beendet, weil die Mikrofonaufnahme unterbrochen wurde. Erneut versuchen.",
|
||||
"microphonePermissionDenied": "Mikrofonzugriff verweigert",
|
||||
"noSpeech": "Keine Sprache erkannt."
|
||||
}
|
||||
},
|
||||
"host": {
|
||||
"edit": {
|
||||
"address": "Adresse",
|
||||
"addressHint": "Akzeptiert IP-Adressen, host:port oder ws:// / wss://. Eine URL mit wss:// verwendet ohne Portangabe 443. Bei Adressen ohne Protokoll- und Portangabe bleibt der aktuelle Port erhalten (oder 6768).",
|
||||
"connectionPreview": "Verbindet mit {{endpoint}}",
|
||||
"goBack": "Zurück navigieren",
|
||||
"help": "Anzeigenamen oder Verbindungsadresse ändern. Das Namensfeld leer lassen, um den vom Desktop gemeldeten Namen zu verwenden. Eine Adressänderung ändert nur das Verbindungsziel dieses Smartphones – es wird keine neue Kopplung hergestellt. Nützlich, wenn derselbe Desktop unter einer anderen IP-Adresse erreichbar ist (z. B. im Heim-LAN statt über Tailscale).",
|
||||
"hostNamePlaceholder": "Host-Name",
|
||||
"hostRemoved": "Dieser Host wurde von diesem Smartphone entfernt.",
|
||||
"loadFailed": "Host konnte nicht geladen werden.",
|
||||
"missingHost": "Host fehlt.",
|
||||
"name": "Name",
|
||||
"saveFailed": "Host konnte nicht gespeichert werden.",
|
||||
"saveHost": "Host speichern",
|
||||
"title": "Host bearbeiten"
|
||||
}
|
||||
},
|
||||
"hostScreen": {
|
||||
"hostListActionSheetActions": {
|
||||
"connect": "Verbinden",
|
||||
"disconnect": "Verbindung trennen",
|
||||
"editHost": "Host bearbeiten",
|
||||
"networkDiagnostics": "Netzwerkdiagnose",
|
||||
"reconnect": "Erneut verbinden"
|
||||
},
|
||||
"hostPendingCreationRow": {
|
||||
"creatingWorkspace": "Arbeitsbereich wird erstellt…"
|
||||
},
|
||||
"hostRailActions": {
|
||||
"accounts": "Konten",
|
||||
"closeSearch": "Suche schließen",
|
||||
"floatingWorkspace": "Schwebender Arbeitsbereich",
|
||||
"newWorkspace": "Neuer Arbeitsbereich",
|
||||
"searchWorkspaces": "Arbeitsbereiche durchsuchen",
|
||||
"tasks": "Aufgaben"
|
||||
},
|
||||
"hostScreenHeader": {
|
||||
"accounts": "Konten",
|
||||
"activeFilters_one": "Arbeitsbereichsoptionen ({{count}} Filter aktiv)",
|
||||
"activeFilters_other": "Arbeitsbereichsoptionen ({{count}} Filter aktiv)",
|
||||
"backToHosts": "Zurück zu den Hosts",
|
||||
"closeSearch": "Suche schließen",
|
||||
"floatingWorkspace": "Schwebender Arbeitsbereich",
|
||||
"hideSidebar": "Seitenleiste ausblenden",
|
||||
"reconnect": "Erneut verbinden",
|
||||
"searchWorkspaces": "Arbeitsbereiche durchsuchen",
|
||||
"tasks": "Aufgaben",
|
||||
"workspaceOptions": "Arbeitsbereichsoptionen"
|
||||
},
|
||||
"hostScreenOverlays": {
|
||||
"deleteConfirmMessage": "„{{name}}“ ({{branch}}) löschen?",
|
||||
"deleteWorktree": "Worktree löschen",
|
||||
"pin": "Anheften",
|
||||
"primaryWorktreeDeleteHint": "Primärer Worktree — kann nicht gelöscht werden. Stattdessen das Projekt entfernen.",
|
||||
"removeHost": "Host entfernen",
|
||||
"removeHostMessage": "„{{name}}“ entfernen? Eine erneute Kopplung ist später möglich.",
|
||||
"sleep": "In Ruhezustand versetzen",
|
||||
"unpin": "Anheftung aufheben"
|
||||
},
|
||||
"hostWorkspaceList": {
|
||||
"searchPlaceholder": "Arbeitsbereiche durchsuchen…",
|
||||
"searchWorkspaces": "Arbeitsbereiche durchsuchen"
|
||||
},
|
||||
"workspaceDetailsSheet": {
|
||||
"checksFailing": "Checks: fehlgeschlagen",
|
||||
"checksPassing": "Checks: erfolgreich",
|
||||
"checksPending": "Checks: ausstehend",
|
||||
"closed": "Geschlossen",
|
||||
"createdByAutomation": "Durch Automatisierung erstellt",
|
||||
"createdByNamedAutomation": "Durch die Automatisierung „{{name}}“ erstellt",
|
||||
"createdFromCli": "Über die Orca CLI erstellt",
|
||||
"draft": "Entwurf",
|
||||
"merged": "Gemergt",
|
||||
"openLink": "{{label}} öffnen",
|
||||
"openLinkWithTitle": "{{label}} öffnen, {{title}}",
|
||||
"prOpen": "Offen"
|
||||
},
|
||||
"workspaceOptionControls": {
|
||||
"agentActivity": "Agentenaktivität",
|
||||
"agentActivityLayout": "Layout der Agentenaktivität",
|
||||
"agentActivitySort": "Agentenaktivität",
|
||||
"agentActivitySortDescription": "Zuerst Agenten, die Aufmerksamkeit erfordern, dann nach letzter Aktivität.",
|
||||
"agentStatuses": "Agentenstatus",
|
||||
"allHosts": "Alle Hosts",
|
||||
"allProjects": "Alle Projekte",
|
||||
"automation": "Automatisierung",
|
||||
"branchFolderPath": "Branch / Ordnerpfad",
|
||||
"branchName": "Branch-Name",
|
||||
"compact": "Kompakt",
|
||||
"detailed": "Detailliert",
|
||||
"fullList": "Vollständige Liste",
|
||||
"githubIssues": "GitHub-Issues",
|
||||
"githubTicket": "GitHub-Ticket",
|
||||
"host": "Host",
|
||||
"hover": "Bei Mauszeigerkontakt",
|
||||
"jiraIssue": "Jira-Issue",
|
||||
"jiraIssues": "Jira-Issues",
|
||||
"linearIssue": "Linear-Issue",
|
||||
"linearIssues": "Linear-Issues",
|
||||
"manual": "Manuell",
|
||||
"manualProjectOrderDescription": "Projekte zum Anordnen ziehen",
|
||||
"manualWorkspaceOrderDescription": "Arbeitsbereiche ziehen, um sie innerhalb der jeweiligen Gruppe anzuordnen.",
|
||||
"name": "Name",
|
||||
"none": "Keine",
|
||||
"notes": "Notizen",
|
||||
"ports": "Ports",
|
||||
"pr": "PR",
|
||||
"prMrLink": "PR/MR-Link",
|
||||
"project": "Projekt",
|
||||
"recent": "Zuletzt aktiv",
|
||||
"recentProjectOrderDescription": "Letzte Arbeitsbereichsaktivität",
|
||||
"searchProjects": "Projekte suchen",
|
||||
"searchProjectsPlaceholder": "Projekte suchen…",
|
||||
"selectedCount": "{{count}} von {{total}}",
|
||||
"status": "Status"
|
||||
},
|
||||
"workspaceOptionsSheet": {
|
||||
"backToOptions": "Zurück zu den Arbeitsbereichsoptionen",
|
||||
"cardDisplay": "Kartendarstellung",
|
||||
"cardLayout": "Kartenlayout",
|
||||
"clearFilters": "Filter zurücksetzen",
|
||||
"exceptDefaultBranch": "Außer Standard-Branch",
|
||||
"filters": "Filter",
|
||||
"groupBy": "Gruppieren nach",
|
||||
"hideAutomationCreated": "Durch Automatisierungen erstellte ausblenden",
|
||||
"hideCliCreated": "Per CLI erstellte ausblenden",
|
||||
"hideDefaultBranch": "Standard-Branch ausblenden",
|
||||
"hideDetachedHead": "Losgelösten HEAD ausblenden",
|
||||
"hideOtherClientWorkspaces": "Arbeitsbereiche anderer Clients ausblenden",
|
||||
"hideSleeping": "Ruhende Arbeitsbereiche ausblenden",
|
||||
"hosts": "Hosts",
|
||||
"localLinux": "Lokales Linux",
|
||||
"localMac": "Lokaler Mac",
|
||||
"localWindows": "Lokales Windows",
|
||||
"projectOrder": "Projektreihenfolge",
|
||||
"projects": "Projekte",
|
||||
"savingToDesktop": "Wird in der Desktop-App gespeichert",
|
||||
"show": "Anzeigen",
|
||||
"showProperties": "Eigenschaften anzeigen",
|
||||
"sortBy": "Sortieren nach",
|
||||
"syncedAcrossDevices": "Geräteübergreifend synchronisiert",
|
||||
"thisComputer": "Dieser Computer",
|
||||
"waitingForDesktop": "Es wird noch auf den Desktop gewartet",
|
||||
"workspaceOptions": "Arbeitsbereichsoptionen"
|
||||
}
|
||||
},
|
||||
"navigation": {
|
||||
"hostStackScreens": {
|
||||
"accounts": "Konten",
|
||||
"agentSessionHistory": "Verlauf der Agentensitzungen",
|
||||
"changes": "Änderungen",
|
||||
"editHost": "Host bearbeiten",
|
||||
"host": "Host",
|
||||
"pullRequest": "Pull Request",
|
||||
"sourceControl": "Quellcodeverwaltung",
|
||||
"tasks": "Aufgaben",
|
||||
"terminal": "Terminal",
|
||||
"workspace": "Arbeitsbereich"
|
||||
}
|
||||
},
|
||||
"notifications": {
|
||||
"notificationDeliverySection": {
|
||||
"footer": "Die Benachrichtigungsarten richten sich nach den Benachrichtigungseinstellungen des jeweiligen gekoppelten Computers. Nach 7 Tagen ohne Nutzung dieser App werden Benachrichtigungen pausiert; zum Fortsetzen die App öffnen und erneut verbinden.",
|
||||
"onlyWhenAway": "Nur bei Abwesenheit vom Computer",
|
||||
"onlyWhenAwayHint": "Nach 3 Minuten ohne Tastatur- oder Mausaktivität oder bei gesperrtem Computer.",
|
||||
"sound": "Benachrichtigungston",
|
||||
"suppressWhileFocused": "Geöffneten Arbeitsbereich ausnehmen",
|
||||
"suppressWhileFocusedHint": "Für den auf diesem Smartphone geöffneten Arbeitsbereich keine Benachrichtigungen senden."
|
||||
}
|
||||
},
|
||||
"onboarding": {
|
||||
"notificationOnboardingPreview": {
|
||||
"claudeNeedsInput": "Claude benötigt eine Eingabe",
|
||||
"codexFinished": "Codex ist fertig",
|
||||
"now": "jetzt",
|
||||
"testsPassing": "Tests bestanden.",
|
||||
"waitingOnYou": "Wartet auf dich."
|
||||
},
|
||||
"onboardingPage": {
|
||||
"enableAgentNotifications": "Agenten-Benachrichtigungen aktivieren",
|
||||
"enableNotifications": "Benachrichtigungen aktivieren",
|
||||
"keepTerminal": "Terminal beibehalten",
|
||||
"notNow": "Jetzt nicht",
|
||||
"notificationsDescription": "Auf diesem Smartphone benachrichtigt werden, wenn ein Agent fertig ist oder wartet – auch wenn die App gerade nicht genutzt wird.",
|
||||
"notificationsDisclosure": "Zustellung über den Push-Dienst von Orca, sobald der Computer 3 Minuten lang inaktiv ist. Jederzeit in den Einstellungen änderbar.",
|
||||
"notificationsTitle": "Nicht verpassen, wenn ein Agent dich braucht",
|
||||
"openInChatUi": "Sitzungen in der Chat-Oberfläche öffnen",
|
||||
"openInTerminal": "Sitzungen im Terminal öffnen",
|
||||
"sessionViewDescription": "Festlegen, ob unterstützte Agenten-Sitzungen auf diesem Gerät im Terminal oder in der Chat-Oberfläche geöffnet werden. Einen Sitzungs-Tab gedrückt halten, um die Ansicht zu wechseln, oder den Standard später in den Einstellungen ändern.",
|
||||
"sessionViewTitle": "Wie sollen Sitzungen geöffnet werden?",
|
||||
"skipNotifications": "Benachrichtigungen vorerst überspringen",
|
||||
"useChatUi": "Chat-Oberfläche verwenden"
|
||||
}
|
||||
},
|
||||
"settings": {
|
||||
"aboutScreen": {
|
||||
"openLinkFailed": "Der Link konnte nicht geöffnet werden. Erneut versuchen.",
|
||||
"socialProfile": "Orca auf X",
|
||||
"sourceCode": "Orca-Quellcode",
|
||||
"tagline": "Open-Source-Agenten-IDE für 100-mal produktivere Entwickler",
|
||||
"title": "Über Orca",
|
||||
"website": "Orca-Website"
|
||||
},
|
||||
"appUpdateSettingsRows": {
|
||||
"checkFailed": "Prüfung fehlgeschlagen",
|
||||
"checkUpdates": "Nach Updates suchen",
|
||||
"checkUpdatesStatus": "Nach Updates suchen, {{status}}",
|
||||
"checking": "Wird geprüft…",
|
||||
"installedVersion": "Version {{version}}",
|
||||
"lastCheckedDays_one": "Zuletzt vor {{count}} Tag geprüft",
|
||||
"lastCheckedDays_other": "Zuletzt vor {{count}} Tagen geprüft",
|
||||
"lastCheckedHours_one": "Zuletzt vor {{count}} Std. geprüft",
|
||||
"lastCheckedHours_other": "Zuletzt vor {{count}} Std. geprüft",
|
||||
"lastCheckedJustNow": "Zuletzt geprüft: gerade eben",
|
||||
"lastCheckedMinutes_one": "Zuletzt vor {{count}} Min. geprüft",
|
||||
"lastCheckedMinutes_other": "Zuletzt vor {{count}} Min. geprüft",
|
||||
"neverChecked": "Nie",
|
||||
"upToDate": "Aktuell",
|
||||
"update": "Aktualisieren",
|
||||
"updateToVersion": "Auf Orca {{version}} aktualisieren"
|
||||
},
|
||||
"appearance": {
|
||||
"language": {
|
||||
"description": "„System“ übernimmt die Gerätesprache. Berechtigungsabfragen verwenden immer die Gerätesprache.",
|
||||
@@ -17,6 +608,157 @@
|
||||
"system": "System"
|
||||
},
|
||||
"title": "Darstellung"
|
||||
},
|
||||
"browserSettingsScreen": {
|
||||
"desktopBrowser": "Orca-Browser auf dem Desktop",
|
||||
"desktopBrowserDescription": "Im gestreamten Browser des gekoppelten Desktops öffnen.",
|
||||
"linksDescription": "Auswählen, wo HTTP(S)-Links aus der Terminalausgabe beim Antippen geöffnet werden.",
|
||||
"linksHeading": "LINKS",
|
||||
"loadFailed": "Browser-Einstellungen konnten nicht geladen werden. Erneut versuchen.",
|
||||
"openTerminalLinks": "Terminal-Links öffnen",
|
||||
"phoneBrowser": "Browser auf dem Smartphone",
|
||||
"phoneBrowserDescription": "In Safari, Chrome oder einem anderen Browser auf diesem Smartphone öffnen.",
|
||||
"saveFailed": "Browser-Einstellungen konnten nicht gespeichert werden. Erneut versuchen.",
|
||||
"title": "Browser"
|
||||
},
|
||||
"menuItems": {
|
||||
"about": "Über Orca",
|
||||
"appearance": "Darstellung",
|
||||
"browser": "Browser",
|
||||
"chatUi": "Chat-Oberfläche",
|
||||
"notifications": "Benachrichtigungen",
|
||||
"terminal": "Terminal",
|
||||
"troubleshooting": "Fehlerbehebung",
|
||||
"voice": "Spracheingabe"
|
||||
},
|
||||
"menuScreen": {
|
||||
"privacyPolicy": "Datenschutzerklärung",
|
||||
"support": "Hilfe"
|
||||
},
|
||||
"nativeChatSettingsScreen": {
|
||||
"defaultViewDescription": "Festlegen, wie Sitzungen unterstützter Agenten (Claude, Codex und anderer chatfähiger Agenten) auf diesem Gerät geöffnet werden. Das Terminal zeigt die CLI direkt an; die Chat-Oberfläche zeigt eine Chatansicht wie in der Desktop-App. Die Ansicht jeder Sitzung lässt sich weiterhin über das Menü wechseln, das durch langes Drücken geöffnet wird.",
|
||||
"defaultViewHeading": "STANDARDANSICHT",
|
||||
"off": "Aus",
|
||||
"on": "Ein",
|
||||
"openInChatUi": "Sitzungen in der Chat-Oberfläche öffnen",
|
||||
"title": "Chat-Oberfläche"
|
||||
},
|
||||
"nativeNotificationDeliverySettings": {
|
||||
"loadFailed": "Zustellungseinstellungen konnten nicht geladen werden. Für einen neuen Versuch diese Ansicht erneut öffnen.",
|
||||
"saveFailed": "Zustellungseinstellungen konnten nicht gespeichert werden. Erneut versuchen.",
|
||||
"updateDesktopHint": "Einen Desktop mit aktueller Orca-Version koppeln, um auf diesem Smartphone Benachrichtigungen zu erhalten."
|
||||
},
|
||||
"notificationDisplayTest": {
|
||||
"accepted": "Vom Push-Dienst von Orca angenommen. Prüfen, ob die Benachrichtigung angekommen ist.",
|
||||
"connectDesktop": "Eine Verbindung zum Desktop herstellen und erneut versuchen.",
|
||||
"description": "Eine Testbenachrichtigung über den Push-Dienst von Orca senden.",
|
||||
"desktopUnreachable": "Der Desktop konnte nicht erreicht werden. Erneut versuchen.",
|
||||
"loadFailed": "Die gekoppelten Desktops konnten nicht geladen werden.",
|
||||
"pairDesktop": "Einen Desktop koppeln und erneut versuchen.",
|
||||
"pushServiceFailed": "Die Benachrichtigung konnte nicht über den Push-Dienst von Orca gesendet werden. Erneut versuchen.",
|
||||
"rateLimited": "Zu viele Benachrichtigungen. Später erneut versuchen.",
|
||||
"reconnectPhone": "Erneut verbinden, um dieses Smartphone für Benachrichtigungen zu registrieren.",
|
||||
"sendButton": "Testbenachrichtigung senden",
|
||||
"sendFailed": "Die Push-Testbenachrichtigung konnte nicht gesendet werden.",
|
||||
"sending": "Wird gesendet…",
|
||||
"troubleReceivingAlerts": "Probleme beim Empfang von Benachrichtigungen?",
|
||||
"troubleshooting": "Fehlerbehebung",
|
||||
"updateDesktop": "Orca auf dem Desktop aktualisieren, um diesen Test auszuführen."
|
||||
},
|
||||
"notificationSettingsScreen": {
|
||||
"description": "Auf diesem Gerät Benachrichtigungen erhalten, wenn ein Agent eine Eingabe benötigt oder eine Aufgabe abschließt.",
|
||||
"enableNotifications": "Benachrichtigungen aktivieren",
|
||||
"loadFailed": "Benachrichtigungseinstellungen konnten nicht geladen werden. Erneut versuchen.",
|
||||
"notificationsBlocked": "Benachrichtigungen sind in den Systemeinstellungen deaktiviert.",
|
||||
"openSettings": "Einstellungen öffnen",
|
||||
"openSettingsFailed": "Die Systemeinstellungen konnten nicht geöffnet werden. Erneut versuchen.",
|
||||
"saveFailed": "Benachrichtigungseinstellungen konnten nicht gespeichert werden. Erneut versuchen.",
|
||||
"title": "Benachrichtigungen"
|
||||
},
|
||||
"pendingCredentialCleanupCard": {
|
||||
"cleanupUnconfirmed_one": "Die Bereinigung von {{count}} Zugangsdaten-Eintrag auf diesem Gerät konnte nicht bestätigt werden.",
|
||||
"cleanupUnconfirmed_other": "Die Bereinigung von {{count}} Zugangsdaten-Einträgen auf diesem Gerät konnte nicht bestätigt werden.",
|
||||
"retryCleanup": "Löschen der Kopplungszugangsdaten erneut versuchen",
|
||||
"retryFailed": "Die Bereinigung konnte weiterhin nicht bestätigt werden. Später erneut versuchen.",
|
||||
"statusCheckFailed": "Der Bereinigungsstatus auf diesem Gerät konnte nicht geprüft werden. Zur Sicherheit erneut versuchen.",
|
||||
"title": "Kopplungszugangsdaten bereinigen"
|
||||
},
|
||||
"uiLanguageSettingsSection": {
|
||||
"pseudoLanguage": "Pseudosprache (en-XA)"
|
||||
},
|
||||
"voiceSettingsScreen": {
|
||||
"connectDesktop": "Mit einem Desktop verbinden, um die Einstellungen für die Spracheingabe zu verwalten.",
|
||||
"deleteFailed": "Löschen fehlgeschlagen",
|
||||
"dictationDescription": "Text in jeden fokussierten Bereich auf dem Desktop diktieren.",
|
||||
"dictationHeading": "DIKTAT",
|
||||
"dictationMode": "Diktatmodus",
|
||||
"dictationModeDescription": "Umschalten: einmal zum Starten und erneut zum Stoppen drücken. Gedrückt halten: währenddessen diktieren.",
|
||||
"downloadFailed": "Download fehlgeschlagen",
|
||||
"enableDictation": "Diktat aktivieren",
|
||||
"holdMode": "Gedrückt halten",
|
||||
"loadFailed": "Einstellungen für die Spracheingabe konnten nicht geladen werden.",
|
||||
"loadFailedMessage": "Die Einstellungen für die Spracheingabe konnten nicht geladen werden.",
|
||||
"noneSelected": "Keines ausgewählt",
|
||||
"selectModelFailed": "Modell konnte nicht ausgewählt werden",
|
||||
"speechModel": "Spracherkennungsmodell",
|
||||
"speechModelHeading": "SPRACHERKENNUNGSMODELL",
|
||||
"title": "Spracheingabe",
|
||||
"toggleMode": "Umschalten",
|
||||
"updateFailed": "Aktualisierung fehlgeschlagen"
|
||||
}
|
||||
},
|
||||
"webShell": {
|
||||
"shellStateScreen": {
|
||||
"checkingHost": "Host wird geprüft",
|
||||
"connectToDownload": "Mit diesem Host verbinden, um die neueste App-Version herunterzuladen",
|
||||
"documentLoadFailed": "Die heruntergeladene App konnte nicht geöffnet werden.",
|
||||
"downloadFailed": "Die neueste App-Version konnte von diesem Host nicht heruntergeladen werden.",
|
||||
"hostProfileUnreadable": "Die Daten dieses Hosts konnten auf dem Gerät nicht gelesen werden. Erneut versuchen.",
|
||||
"isolationUnavailable": "Die WebView dieses Geräts ist zu alt, um den Arbeitsbereich sicher zu öffnen.",
|
||||
"openingWorkspace": "Arbeitsbereich wird geöffnet",
|
||||
"renderProcessGone": "Der Arbeitsbereich reagiert nicht mehr.",
|
||||
"statusUnreadable": "Der Status dieses Hosts konnte nicht gelesen werden. Zurückgehen und den Host erneut öffnen.",
|
||||
"tryAgain": "Erneut versuchen"
|
||||
}
|
||||
},
|
||||
"worktree": {
|
||||
"agentRowDisplay": {
|
||||
"blocked": "Blockiert",
|
||||
"days_one": "{{count}} T.",
|
||||
"days_other": "{{count}} T.",
|
||||
"done": "Erledigt",
|
||||
"failed": "Fehlgeschlagen",
|
||||
"hours_one": "{{count}} Std.",
|
||||
"hours_other": "{{count}} Std.",
|
||||
"idle": "Inaktiv",
|
||||
"interrupted": "Unterbrochen",
|
||||
"justNow": "gerade eben",
|
||||
"minutes_one": "{{count}} Min.",
|
||||
"minutes_other": "{{count}} Min.",
|
||||
"monitoring": "Hintergrundaufgaben werden überwacht",
|
||||
"stopping": "Wird gestoppt…",
|
||||
"unconfirmed": "Bestätigung nicht möglich",
|
||||
"unverifiable": "Keine aktuelle Meldung",
|
||||
"waiting": "Wartet auf Eingabe",
|
||||
"working": "In Arbeit"
|
||||
},
|
||||
"hostWorkspaceListStates": {
|
||||
"checkHost": "Prüfen, ob der Computer wach ist und Orca läuft",
|
||||
"empty": "Keine Arbeitsbereiche",
|
||||
"loadFailed": "Arbeitsbereiche von diesem Host konnten nicht geladen werden.",
|
||||
"noFilterMatches": "Keine Arbeitsbereiche entsprechen den Filtern",
|
||||
"noMatches": "Keine passenden Arbeitsbereiche",
|
||||
"reopen": "Zum Aktualisieren erneut öffnen",
|
||||
"reopenDetail": "Dieser Host listet Arbeitsbereiche in einem Format auf, das diese Seite noch nicht unterstützt.",
|
||||
"retrying": "worktree.ps fehlgeschlagen ({{error}}) – wird automatisch erneut versucht"
|
||||
},
|
||||
"workspaceListSections": {
|
||||
"all": "Alle",
|
||||
"closed": "Geschlossen",
|
||||
"done": "Erledigt",
|
||||
"inProgress": "In Bearbeitung",
|
||||
"inReview": "Im Review",
|
||||
"pinned": "Angeheftet"
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -14,9 +14,751 @@
|
||||
"heading": "LANGUAGE",
|
||||
"description": "System follows this device’s language. Permission prompts always use the device language."
|
||||
}
|
||||
},
|
||||
"aboutScreen": {
|
||||
"title": "About",
|
||||
"tagline": "Open-source agent IDE for 100x builders",
|
||||
"website": "Orca website",
|
||||
"sourceCode": "Orca source code",
|
||||
"socialProfile": "Orca on X",
|
||||
"openLinkFailed": "Could not open the link. Try again."
|
||||
},
|
||||
"appUpdateSettingsRows": {
|
||||
"checking": "Checking…",
|
||||
"upToDate": "Up to date",
|
||||
"checkFailed": "Couldn't check",
|
||||
"neverChecked": "Never",
|
||||
"lastCheckedJustNow": "Last checked just now",
|
||||
"lastCheckedMinutes_one": "Last checked {{count}}m ago",
|
||||
"lastCheckedMinutes_other": "Last checked {{count}}m ago",
|
||||
"lastCheckedHours_one": "Last checked {{count}}h ago",
|
||||
"lastCheckedHours_other": "Last checked {{count}}h ago",
|
||||
"lastCheckedDays_one": "Last checked {{count}}d ago",
|
||||
"lastCheckedDays_other": "Last checked {{count}}d ago",
|
||||
"updateToVersion": "Update to Orca {{version}}",
|
||||
"update": "Update",
|
||||
"installedVersion": "Version {{version}}",
|
||||
"checkUpdatesStatus": "Check for updates, {{status}}",
|
||||
"checkUpdates": "Check for updates"
|
||||
},
|
||||
"browserSettingsScreen": {
|
||||
"desktopBrowser": "Orca browser on desktop",
|
||||
"desktopBrowserDescription": "Open in the streamed browser from your paired desktop.",
|
||||
"phoneBrowser": "Phone browser",
|
||||
"phoneBrowserDescription": "Open in Safari, Chrome, or another browser on this phone.",
|
||||
"title": "Browser",
|
||||
"linksHeading": "LINKS",
|
||||
"linksDescription": "Choose where HTTP(S) links tapped in terminal output open.",
|
||||
"loadFailed": "Could not load browser preferences. Try again.",
|
||||
"saveFailed": "Could not save browser preferences. Try again.",
|
||||
"openTerminalLinks": "Open terminal links"
|
||||
},
|
||||
"menuItems": {
|
||||
"appearance": "Appearance",
|
||||
"terminal": "Terminal",
|
||||
"chatUi": "Chat UI",
|
||||
"browser": "Browser",
|
||||
"voice": "Voice",
|
||||
"notifications": "Notifications",
|
||||
"troubleshooting": "Troubleshooting",
|
||||
"about": "About"
|
||||
},
|
||||
"nativeChatSettingsScreen": {
|
||||
"title": "Chat UI",
|
||||
"defaultViewHeading": "DEFAULT VIEW",
|
||||
"defaultViewDescription": "Choose how supported agent sessions (Claude, Codex, and other chat-capable agents) open on this device. Terminal shows the raw CLI; Chat UI shows a chat interface like the desktop app. You can still switch any individual session from its long-press menu.",
|
||||
"openInChatUi": "Open sessions in Chat UI",
|
||||
"on": "On",
|
||||
"off": "Off"
|
||||
},
|
||||
"nativeNotificationDeliverySettings": {
|
||||
"loadFailed": "Could not load delivery settings. Reopen this screen to retry.",
|
||||
"saveFailed": "Could not save delivery settings. Try again.",
|
||||
"updateDesktopHint": "Pair an updated desktop to receive notifications on this phone."
|
||||
},
|
||||
"notificationDisplayTest": {
|
||||
"loadFailed": "Could not load paired desktops.",
|
||||
"pairDesktop": "Pair a desktop and try again.",
|
||||
"connectDesktop": "Connect a desktop and try again.",
|
||||
"updateDesktop": "Update your desktop to run this test.",
|
||||
"desktopUnreachable": "Could not reach the desktop. Try again.",
|
||||
"accepted": "Accepted by Orca’s push service. Check for the notification.",
|
||||
"reconnectPhone": "Reconnect to register this phone for notifications.",
|
||||
"rateLimited": "Too many notifications. Try again later.",
|
||||
"pushServiceFailed": "Could not send through Orca’s push service. Try again.",
|
||||
"sendFailed": "Could not send push test.",
|
||||
"troubleReceivingAlerts": "Having trouble receiving alerts?",
|
||||
"description": "Send a test through Orca’s push service.",
|
||||
"sending": "Sending…",
|
||||
"sendButton": "Send test notification",
|
||||
"troubleshooting": "Troubleshooting"
|
||||
},
|
||||
"notificationSettingsScreen": {
|
||||
"loadFailed": "Could not load notification settings. Try again.",
|
||||
"saveFailed": "Could not save notification settings. Try again.",
|
||||
"notificationsBlocked": "Notifications are disabled in system settings.",
|
||||
"description": "Get notified on this device when an agent needs your input or finishes a task.",
|
||||
"title": "Notifications",
|
||||
"enableNotifications": "Enable notifications",
|
||||
"openSettingsFailed": "Could not open system settings. Try again.",
|
||||
"openSettings": "Open Settings"
|
||||
},
|
||||
"pendingCredentialCleanupCard": {
|
||||
"title": "Pairing credential cleanup",
|
||||
"retryFailed": "Cleanup still couldn't be confirmed. Try again later.",
|
||||
"cleanupUnconfirmed_one": "Couldn't confirm cleanup for {{count}} credential on this device.",
|
||||
"cleanupUnconfirmed_other": "Couldn't confirm cleanup for {{count}} credentials on this device.",
|
||||
"statusCheckFailed": "Couldn't check cleanup status on this device. Retry to be safe.",
|
||||
"retryCleanup": "Retry clearing pairing credentials"
|
||||
},
|
||||
"menuScreen": {
|
||||
"privacyPolicy": "Privacy Policy",
|
||||
"support": "Support"
|
||||
},
|
||||
"uiLanguageSettingsSection": {
|
||||
"pseudoLanguage": "Pseudo (en-XA)"
|
||||
},
|
||||
"voiceSettingsScreen": {
|
||||
"toggleMode": "Toggle",
|
||||
"holdMode": "Hold",
|
||||
"loadFailed": "Failed to load voice settings",
|
||||
"updateFailed": "Could not update",
|
||||
"selectModelFailed": "Could not select model",
|
||||
"downloadFailed": "Download failed",
|
||||
"deleteFailed": "Delete failed",
|
||||
"noneSelected": "None selected",
|
||||
"title": "Voice",
|
||||
"connectDesktop": "Connect to a desktop to manage voice settings.",
|
||||
"loadFailedMessage": "Failed to load voice settings.",
|
||||
"dictationHeading": "DICTATION",
|
||||
"enableDictation": "Enable Voice Dictation",
|
||||
"dictationDescription": "Dictate text into any focused pane on your desktop.",
|
||||
"dictationMode": "Dictation Mode",
|
||||
"dictationModeDescription": "Toggle: press once to start, again to stop. Hold: dictate while held.",
|
||||
"speechModelHeading": "SPEECH MODEL",
|
||||
"speechModel": "Speech Model"
|
||||
}
|
||||
},
|
||||
"common": {
|
||||
"back": "Back"
|
||||
"back": "Back",
|
||||
"save": "Save",
|
||||
"cancel": "Cancel",
|
||||
"continue": "Continue",
|
||||
"ok": "OK",
|
||||
"retry": "Retry",
|
||||
"copied": "Copied",
|
||||
"remove": "Remove",
|
||||
"settings": "Settings",
|
||||
"delete": "Delete",
|
||||
"unknown": "Unknown"
|
||||
},
|
||||
"host": {
|
||||
"edit": {
|
||||
"missingHost": "Missing host.",
|
||||
"hostRemoved": "This host was removed from this phone.",
|
||||
"loadFailed": "Failed to load host.",
|
||||
"saveFailed": "Failed to save host.",
|
||||
"title": "Edit host",
|
||||
"saveHost": "Save host",
|
||||
"goBack": "Go back",
|
||||
"help": "Change the display name or connection address. Leave the name empty to use the name the desktop reports. Address edits only switch where this phone connects — they do not re-pair. Use this when the same desktop is reachable at a different IP (for example home LAN vs Tailscale).",
|
||||
"name": "Name",
|
||||
"hostNamePlaceholder": "Host name",
|
||||
"address": "Address",
|
||||
"addressHint": "Accepts IP, host:port, or ws:// / wss://. A wss:// URL without a port uses 443. Bare addresses keep the current port (or 6768).",
|
||||
"connectionPreview": "Connects to {{endpoint}}"
|
||||
}
|
||||
},
|
||||
"app": {
|
||||
"mobileOnboarding": {
|
||||
"saveChoiceFailed": "Your choice could not be saved. Try again.",
|
||||
"notificationSettingsFailed": "Notification settings could not be updated. Try again.",
|
||||
"progress": "Onboarding progress",
|
||||
"stepProgress": "Step {{step}} of {{total}}"
|
||||
},
|
||||
"notifications": {
|
||||
"description": "Get agent alerts even when the app is closed. Delivered through Orca’s push service and Apple or Google."
|
||||
},
|
||||
"pairConfirm": {
|
||||
"connectTimedOut": "Couldn't connect within {{seconds}}s — see log below for where it stalled",
|
||||
"pairingFailed": "Pairing failed: {{error}}",
|
||||
"title": "Pair with this desktop?",
|
||||
"description": "You opened a pairing link from your desktop. Confirm to add it to your hosts.",
|
||||
"pairButton": "Pair",
|
||||
"connecting": "Connecting…",
|
||||
"pairingLog": "Pairing log",
|
||||
"backToHome": "Back to home"
|
||||
},
|
||||
"pairScan": {
|
||||
"invalidQrCode": "Not a valid Orca QR code",
|
||||
"invalidPairingCode": "Not a valid pairing code — copy it from your computer and paste again",
|
||||
"connectTimedOut": "Couldn't connect within {{seconds}}s — see log below for where it stalled",
|
||||
"pairingFailed": "Pairing failed: {{error}}",
|
||||
"title": "Pair with desktop",
|
||||
"cameraDisabledTitle": "Camera Access Disabled",
|
||||
"cameraPermissionDescription": "Scan the QR code from Orca on your desktop, or paste the pairing code instead.",
|
||||
"cameraDisabledDescription": "Enable camera access in Settings, or paste the pairing code instead.",
|
||||
"openSettings": "Open Settings",
|
||||
"pasteInstead": "Paste code instead",
|
||||
"pasteCodeTitle": "Paste pairing code",
|
||||
"pasteCodeMessage": "Copy the code shown under the QR on your computer.",
|
||||
"pasteCodePlaceholder": "orca://pair?code=... or paste the code",
|
||||
"openDesktopStep": "Open Orca on your computer",
|
||||
"openMobileSettingsStep": "Go to Settings → Mobile",
|
||||
"scanCodeStep": "Scan the QR code",
|
||||
"pastePairingCode": "Or paste pairing code",
|
||||
"connecting": "Connecting…",
|
||||
"pairingLog": "Pairing log",
|
||||
"tryAgain": "Try Again"
|
||||
},
|
||||
"pair": {
|
||||
"missingCode": "Missing pairing code",
|
||||
"backToHome": "Back to home"
|
||||
},
|
||||
"terminalSettings": {
|
||||
"smallestText": "Smallest (50%)",
|
||||
"smallerText": "Smaller (75%)",
|
||||
"defaultText": "Default (100%)",
|
||||
"largeText": "Large (125%)",
|
||||
"largerText": "Larger (150%)",
|
||||
"largestText": "Largest (200%)",
|
||||
"restoreAfterMinutes_one": "After {{count}} minute",
|
||||
"restoreAfterMinutes_other": "After {{count}} minutes",
|
||||
"keepPhoneSize": "Keep at phone size (default)",
|
||||
"restoreAfterSeconds_one": "After {{count}}s",
|
||||
"restoreAfterSeconds_other": "After {{count}}s",
|
||||
"title": "Terminal",
|
||||
"leaveAppHeading": "WHEN YOU LEAVE THE APP",
|
||||
"leaveAppDescription": "While you're using a terminal on your phone, Orca shrinks it to fit your screen. When you close the app or switch away, this controls whether it stays at phone size (so interactive CLI tools don't reflow) or resizes back to your desktop. You can always use Restore on the desktop banner to resize manually.",
|
||||
"noPairedDesktops": "No paired desktops yet. Pair one to control terminal behavior.",
|
||||
"textSizeHeading": "TEXT SIZE",
|
||||
"textSizeDescription": "Scale the terminal text. Smaller sizes fit more columns with side margins; larger sizes show fewer columns — drag sideways to pan. You can also pinch to zoom in the terminal itself, which updates this setting. Per-device display only; doesn't change the desktop terminal.",
|
||||
"textSize": "Text size",
|
||||
"keyboardInputHeading": "KEYBOARD INPUT",
|
||||
"keyboardInputDescription": "Autocomplete, autocorrect, and spelling suggestions apply only to buffered command input. Use the double-chevron button above the keyboard to switch from live input, then edit your command before tapping Send. Off by default to avoid rewriting commands, flags, or paths.",
|
||||
"autocomplete": "Autocomplete & autocorrect",
|
||||
"autocompleteOn": "On",
|
||||
"autocompleteOff": "Off",
|
||||
"restoreHost": "Restore {{name}}",
|
||||
"textSizePickerTitle": "Terminal text size"
|
||||
}
|
||||
},
|
||||
"accounts": {
|
||||
"screen": {
|
||||
"invalidSnapshot": "Invalid accounts snapshot from host",
|
||||
"hostNotFound": "Host not found",
|
||||
"switchFailed": "Could not switch account",
|
||||
"default": "System default",
|
||||
"agentLogin": "Use the agent's own login",
|
||||
"sessionWindow": "5h",
|
||||
"weeklyWindow": "7d",
|
||||
"title": "Accounts",
|
||||
"refreshAccounts": "Refresh accounts",
|
||||
"connectingTo": "Connecting to {{name}}…",
|
||||
"connecting": "Connecting to host…",
|
||||
"loadingAccounts": "Loading accounts…",
|
||||
"manageAccountsHint": "Add or re-authenticate accounts from desktop Settings → Accounts.",
|
||||
"useReset": "Use reset"
|
||||
}
|
||||
},
|
||||
"agentHistory": {
|
||||
"sessionHistoryList": {
|
||||
"messages_one": "{{count}} message",
|
||||
"messages_other": "{{count}} messages",
|
||||
"currentWorktree": "current worktree",
|
||||
"resumeSession": "Resume agent session",
|
||||
"userRole": "user",
|
||||
"assistantRole": "assistant"
|
||||
},
|
||||
"panel": {
|
||||
"workspace": "Workspace",
|
||||
"project": "Project",
|
||||
"all": "All",
|
||||
"waitingForHost": "Waiting for host...",
|
||||
"missingResumeId": "This session is missing a resume id.",
|
||||
"unknownHostPlatform": "Unable to determine host platform.",
|
||||
"sessionQueued": "Agent session queued.",
|
||||
"resumeFailed": "Failed to resume session.",
|
||||
"title": "Agent Session History",
|
||||
"refreshSessions": "Refresh agent sessions",
|
||||
"unavailable": "Agent Session History Unavailable",
|
||||
"updateHost": "Update Orca on this host to browse agent session history.",
|
||||
"loadFailed": "Unable to Load",
|
||||
"placeholder": "Search sessions, repo:, path:",
|
||||
"skipped_one": "{{count}} transcript skipped",
|
||||
"skipped_other": "{{count}} transcripts skipped",
|
||||
"noSessions": "No agent sessions",
|
||||
"noSearchResults": "No sessions match your search.",
|
||||
"emptyScope": "No past agent sessions in this scope."
|
||||
},
|
||||
"resumeTarget": {
|
||||
"runtimeUnavailable": "Resume from history is not available in runtime-hosted workspaces.",
|
||||
"differentHost": "This session belongs to a different host. Open a workspace on the same host to resume it.",
|
||||
"openWorkspace": "Open a workspace before resuming a session."
|
||||
},
|
||||
"sessionCard": {
|
||||
"untitledSession": "Untitled session"
|
||||
},
|
||||
"useAgentHistoryState": {
|
||||
"waitingForHost": "Waiting for host…",
|
||||
"hostUnreachable": "Unable to reach host",
|
||||
"loadFailed": "Unable to load agent sessions"
|
||||
},
|
||||
"worktreeNavigationActions": {
|
||||
"sourceControl": "Source Control",
|
||||
"agentSessionHistory": "Agent Session History"
|
||||
}
|
||||
},
|
||||
"diagnostics": {
|
||||
"connectionDiagnosticsView": {
|
||||
"connecting": "connecting",
|
||||
"handshaking": "handshaking",
|
||||
"connected": "connected",
|
||||
"disconnected": "disconnected",
|
||||
"reconnecting": "reconnecting",
|
||||
"authFailed": "auth-failed",
|
||||
"title": "Network diagnostics",
|
||||
"stateWithAttempt": "{{state}} · attempt {{attempts}}",
|
||||
"copyReport": "Copy report",
|
||||
"diagnosisHeading": "What this suggests",
|
||||
"privacyHint": "Sends a size-limited redacted report including host name, endpoint, versions, connection state, and events—never terminal contents or credentials.",
|
||||
"sending": "Sending…",
|
||||
"sent": "Diagnostics sent",
|
||||
"retrySending": "Retry sending",
|
||||
"sendDiagnostics": "Send diagnostics to Orca",
|
||||
"noEvents": "No connection events yet. Events appear as the app dials this host.",
|
||||
"noHosts": "No paired hosts."
|
||||
},
|
||||
"mobileWebBundleProbeRow": {
|
||||
"fetchBundle": "Fetch mobile web bundle",
|
||||
"connecting": "Connecting…",
|
||||
"fetching": "Fetching bundle…",
|
||||
"bundle": "Bundle",
|
||||
"host": "Host",
|
||||
"build": "Build",
|
||||
"assets": "Assets",
|
||||
"bytes": "Bytes",
|
||||
"elapsed": "Elapsed",
|
||||
"elapsedMilliseconds": "{{elapsedMs}} ms",
|
||||
"unknownHost": "unknown host"
|
||||
},
|
||||
"mobileWebShellUpdateFailureRow": {
|
||||
"title": "Workspace updates"
|
||||
},
|
||||
"troubleshootCommonIssues": {
|
||||
"notificationsTitle": "Push Notifications",
|
||||
"allowNotifications": "Check that system settings allow Orca notifications and that Focus or Do Not Disturb is off.",
|
||||
"notificationNetwork": "Try cellular or another Wi-Fi network. If alerts arrive after switching, your network may be delaying delivery.",
|
||||
"wifiTitle": "Different WiFi Networks",
|
||||
"sameNetwork": "Both devices must be on the same LAN (unless connected through Tailscale).",
|
||||
"sameSubnet": "Ethernet and WiFi must share the same subnet.",
|
||||
"reconnectWifi": "Try reconnecting WiFi on both devices.",
|
||||
"firewallTitle": "Firewall Blocking Port 6768",
|
||||
"macosFirewall": "macOS: System Settings → Network → Firewall — allow Orca.",
|
||||
"windowsFirewall": "Windows: Defender Firewall → Allow app — enable Orca for Private networks.",
|
||||
"restrictedNetwork": "Corporate/school networks may block P2P — try a personal hotspot.",
|
||||
"desktopTitle": "Desktop App Not Running",
|
||||
"openDesktop": "Orca must be open on your desktop to accept connections.",
|
||||
"restartDesktop": "Try restarting Orca — the companion server starts on launch.",
|
||||
"pairAfterUpdate": "After an update, you may need to re-pair via QR code.",
|
||||
"timeoutTitle": "Connection Timeout",
|
||||
"wifiSignal": "Check WiFi signal strength on your phone.",
|
||||
"retryHost": "Go back to the host list and tap your host to retry.",
|
||||
"restartBothApps": "Restart both apps if timeouts persist.",
|
||||
"tailscaleTitle": "Tailscale Host Unreachable",
|
||||
"keepTailscaleOn": "Host addresses like 100.x.x.x or *.ts.net connect through Tailscale — keep it ON.",
|
||||
"restartTailscale": "iOS/Android can silently wedge the tunnel: toggle Tailscale off and back on in the Tailscale app.",
|
||||
"checkTailnet": "Check the desktop is awake and shows as connected in your tailnet.",
|
||||
"updateTailscale": "Update the Tailscale app — recent releases fix reconnect bugs.",
|
||||
"vpnTitle": "Other VPN Interference",
|
||||
"vpnRouting": "Non-Tailscale VPNs can route local traffic through a remote server.",
|
||||
"disableVpn": "Disable that VPN or enable split tunneling / \"Allow LAN\"."
|
||||
},
|
||||
"troubleshootView": {
|
||||
"title": "Troubleshooting",
|
||||
"running": "Running…",
|
||||
"runAgain": "Run again",
|
||||
"runDiagnostics": "Run diagnostics",
|
||||
"viewNetworkDiagnostics": "View network diagnostics",
|
||||
"commonIssues": "Common issues"
|
||||
},
|
||||
"useTroubleshootDiagnostics": {
|
||||
"pairedHosts": "Paired hosts",
|
||||
"pairedCount": "{{count}} paired",
|
||||
"noPairedHosts": "None — scan a QR to pair",
|
||||
"hostDataFailed": "Could not read host data",
|
||||
"internet": "Internet",
|
||||
"connected": "Connected",
|
||||
"unexpectedResponse": "Unexpected response",
|
||||
"noConnection": "No connection",
|
||||
"reachable": "Reachable at {{endpoint}}",
|
||||
"hosts": "Hosts",
|
||||
"hostTestFailed": "Could not test",
|
||||
"platform": "Platform"
|
||||
},
|
||||
"webShellUpdateFailureCopy": {
|
||||
"failedJustNow": "Last update from {{hostName}} failed just now: {{reason}}{{offered}}. {{outcome}}",
|
||||
"failedAgo": "Last update from {{hostName}} failed {{time}} ago: {{reason}}{{offered}}. {{outcome}}"
|
||||
}
|
||||
},
|
||||
"dictation": {
|
||||
"setup": {
|
||||
"serverUpdateRequired": "Update Orca on this server to use voice dictation.",
|
||||
"alreadyActive": "Dictation is already in use on the computer or another phone.",
|
||||
"repairRequired": "Re-pair this phone with the computer to use dictation.",
|
||||
"sessionLost": "Dictation lost its connection to the computer. Try again.",
|
||||
"desktopUpdateRequired": "Update the paired desktop Orca app to use mobile voice settings.",
|
||||
"loadFailed": "Failed to load dictation models",
|
||||
"downloadFailed": "Failed to start download",
|
||||
"deleteFailed": "Failed to delete model",
|
||||
"updateFailed": "Failed to update dictation settings",
|
||||
"cloudModelNeedsKey": "Needs an OpenAI key"
|
||||
}
|
||||
},
|
||||
"home": {
|
||||
"appUpdateCard": {
|
||||
"title": "Orca {{version}} is available",
|
||||
"updateAccessibilityLabel": "Orca {{version}} is available, tap to update",
|
||||
"updateHint": "Tap to update",
|
||||
"dismissAccessibilityLabel": "Dismiss Orca {{version}} update"
|
||||
},
|
||||
"accountUsageCards": {
|
||||
"sectionHeading": "Account usage",
|
||||
"systemDefault": "System default",
|
||||
"sessionWindow": "5h",
|
||||
"weeklyWindow": "7d"
|
||||
},
|
||||
"emptyState": {
|
||||
"openDesktopTitle": "Open Orca desktop",
|
||||
"openDesktopDescription": "Go to Settings → Mobile and generate a pairing QR code.",
|
||||
"scanCodeTitle": "Scan the code",
|
||||
"scanCodeDescription": "Tap the button above to open the scanner. Point at the QR code on your screen.",
|
||||
"connectedTitle": "You're connected",
|
||||
"connectedDescription": "Your desktop will appear here. Everything is encrypted end-to-end.",
|
||||
"title": "Connect your desktop",
|
||||
"description": "Pair with Orca on your computer to check on your agents, jump into any terminal, and drive work from your phone.",
|
||||
"pairDesktop": "Pair Desktop",
|
||||
"stepsHeading": "How it works"
|
||||
},
|
||||
"listFooter": {
|
||||
"resumeHeading": "Resume",
|
||||
"tasksHeading": "Tasks",
|
||||
"hostPickerTitle": "Open Tasks On"
|
||||
},
|
||||
"listHeader": {
|
||||
"daysHours": "{{days}}d {{hours}}h",
|
||||
"hoursMinutes": "{{hours}}h {{minutes}}m",
|
||||
"minutes": "{{minutes}}m",
|
||||
"title": "Welcome back",
|
||||
"agentsSpawned": "Agents spawned",
|
||||
"agentTime": "Agent time",
|
||||
"prsCreated": "PRs created",
|
||||
"desktopsHeading": "Desktops"
|
||||
},
|
||||
"screen": {
|
||||
"pairingCheckFailed": "Could not check pairing",
|
||||
"tryAgain": "Please try again.",
|
||||
"removeHostFailed": "Could not remove host",
|
||||
"removeHostTitle": "Remove Host",
|
||||
"removeHostMessage": "Remove \"{{name}}\"? You can re-pair later."
|
||||
},
|
||||
"tasksCard": {
|
||||
"title": "Tasks",
|
||||
"noSources": "No task sources connected",
|
||||
"openProviderTasks": "Open {{provider}} tasks"
|
||||
}
|
||||
},
|
||||
"hooks": {
|
||||
"dictationDesktopStart": {
|
||||
"recordingFailed": "Failed to start microphone recording"
|
||||
},
|
||||
"useMobileDictation": {
|
||||
"microphonePermissionDenied": "Microphone permission denied",
|
||||
"microphoneInitFailed": "Failed to initialize microphone",
|
||||
"noSpeech": "No speech detected.",
|
||||
"microphoneInterrupted": "Voice dictation stopped because the microphone was interrupted. Try again.",
|
||||
"inputClosed": "Voice dictation stopped because this session is no longer accepting input. Try again."
|
||||
}
|
||||
},
|
||||
"hostScreen": {
|
||||
"hostListActionSheetActions": {
|
||||
"reconnect": "Reconnect",
|
||||
"connect": "Connect",
|
||||
"disconnect": "Disconnect",
|
||||
"networkDiagnostics": "Network diagnostics",
|
||||
"editHost": "Edit host"
|
||||
},
|
||||
"hostPendingCreationRow": {
|
||||
"creatingWorkspace": "Creating workspace…"
|
||||
},
|
||||
"hostRailActions": {
|
||||
"accounts": "Accounts",
|
||||
"tasks": "Tasks",
|
||||
"floatingWorkspace": "Floating Workspace",
|
||||
"newWorkspace": "New workspace",
|
||||
"closeSearch": "Close search",
|
||||
"searchWorkspaces": "Search workspaces"
|
||||
},
|
||||
"hostScreenHeader": {
|
||||
"workspaceOptions": "Workspace options",
|
||||
"activeFilters_one": "Workspace options ({{count}} filter active)",
|
||||
"activeFilters_other": "Workspace options ({{count}} filters active)",
|
||||
"backToHosts": "Back to hosts",
|
||||
"reconnect": "Reconnect",
|
||||
"floatingWorkspace": "Floating Workspace",
|
||||
"hideSidebar": "Hide sidebar",
|
||||
"accounts": "Accounts",
|
||||
"tasks": "Tasks",
|
||||
"closeSearch": "Close search",
|
||||
"searchWorkspaces": "Search workspaces"
|
||||
},
|
||||
"hostScreenOverlays": {
|
||||
"deleteWorktree": "Delete Worktree",
|
||||
"deleteConfirmMessage": "Delete \"{{name}}\" ({{branch}})?",
|
||||
"sleep": "Sleep",
|
||||
"unpin": "Unpin",
|
||||
"pin": "Pin",
|
||||
"primaryWorktreeDeleteHint": "Primary worktree — can't be deleted. Remove the project instead.",
|
||||
"removeHost": "Remove Host",
|
||||
"removeHostMessage": "Remove \"{{name}}\"? You can re-pair later."
|
||||
},
|
||||
"hostWorkspaceList": {
|
||||
"searchPlaceholder": "Search workspaces…",
|
||||
"searchWorkspaces": "Search workspaces"
|
||||
},
|
||||
"workspaceDetailsSheet": {
|
||||
"merged": "Merged",
|
||||
"closed": "Closed",
|
||||
"draft": "Draft",
|
||||
"checksPassing": "Checks: Passing",
|
||||
"checksFailing": "Checks: Failing",
|
||||
"checksPending": "Checks: Pending",
|
||||
"createdFromCli": "Created from Orca CLI",
|
||||
"createdByNamedAutomation": "Created by automation “{{name}}”",
|
||||
"createdByAutomation": "Created by automation",
|
||||
"openLinkWithTitle": "Open {{label}}, {{title}}",
|
||||
"openLink": "Open {{label}}",
|
||||
"prOpen": "Open"
|
||||
},
|
||||
"workspaceOptionControls": {
|
||||
"none": "None",
|
||||
"status": "Status",
|
||||
"pr": "PR",
|
||||
"project": "Project",
|
||||
"name": "Name",
|
||||
"agentActivitySort": "Agent Activity",
|
||||
"recent": "Recent",
|
||||
"manual": "Manual",
|
||||
"agentActivitySortDescription": "Agents that need attention, then most recent activity.",
|
||||
"manualWorkspaceOrderDescription": "Drag workspaces to arrange them within each group.",
|
||||
"manualProjectOrderDescription": "Drag projects to arrange them",
|
||||
"recentProjectOrderDescription": "Most recent workspace activity",
|
||||
"detailed": "Detailed",
|
||||
"compact": "Compact",
|
||||
"fullList": "Full list",
|
||||
"githubTicket": "GitHub ticket",
|
||||
"linearIssue": "Linear issue",
|
||||
"jiraIssue": "Jira issue",
|
||||
"prMrLink": "PR/MR link",
|
||||
"automation": "Automation",
|
||||
"notes": "Notes",
|
||||
"ports": "Ports",
|
||||
"agentActivity": "Agent activity",
|
||||
"host": "Host",
|
||||
"githubIssues": "GitHub issues",
|
||||
"linearIssues": "Linear issues",
|
||||
"jiraIssues": "Jira issues",
|
||||
"agentStatuses": "Agent statuses",
|
||||
"branchFolderPath": "Branch / folder path",
|
||||
"branchName": "Branch name",
|
||||
"selectedCount": "{{count}} of {{total}}",
|
||||
"allHosts": "All hosts",
|
||||
"allProjects": "All projects",
|
||||
"searchProjectsPlaceholder": "Search projects…",
|
||||
"searchProjects": "Search projects",
|
||||
"hover": "Hover",
|
||||
"agentActivityLayout": "Agent activity layout"
|
||||
},
|
||||
"workspaceOptionsSheet": {
|
||||
"hosts": "Hosts",
|
||||
"projects": "Projects",
|
||||
"groupBy": "Group by",
|
||||
"sortBy": "Sort by",
|
||||
"projectOrder": "Project order",
|
||||
"cardLayout": "Card layout",
|
||||
"showProperties": "Show properties",
|
||||
"cardDisplay": "Card display",
|
||||
"hideSleeping": "Hide sleeping",
|
||||
"exceptDefaultBranch": "Except default branch",
|
||||
"hideDefaultBranch": "Hide default branch",
|
||||
"hideAutomationCreated": "Hide automation-created",
|
||||
"hideCliCreated": "Hide CLI-created",
|
||||
"hideOtherClientWorkspaces": "Hide other-client workspaces",
|
||||
"hideDetachedHead": "Hide detached HEAD",
|
||||
"show": "Show",
|
||||
"filters": "Filters",
|
||||
"localMac": "Local Mac",
|
||||
"localWindows": "Local Windows",
|
||||
"localLinux": "Local Linux",
|
||||
"thisComputer": "This computer",
|
||||
"waitingForDesktop": "Still waiting for the desktop",
|
||||
"savingToDesktop": "Saving to the desktop",
|
||||
"backToOptions": "Back to Workspace options",
|
||||
"workspaceOptions": "Workspace options",
|
||||
"syncedAcrossDevices": "Synced across your devices",
|
||||
"clearFilters": "Clear filters"
|
||||
}
|
||||
},
|
||||
"webShell": {
|
||||
"shellStateScreen": {
|
||||
"isolationUnavailable": "This device's WebView is too old to open the workspace safely.",
|
||||
"downloadFailed": "The latest app could not be downloaded from this host.",
|
||||
"hostProfileUnreadable": "Could not read this host on your device. Try again.",
|
||||
"statusUnreadable": "Could not read this host's status. Go back and reopen it.",
|
||||
"renderProcessGone": "The workspace stopped responding.",
|
||||
"documentLoadFailed": "The downloaded app could not be opened.",
|
||||
"tryAgain": "Try again",
|
||||
"connectToDownload": "Connect to this host to download the latest app",
|
||||
"openingWorkspace": "Opening workspace",
|
||||
"checkingHost": "Checking host"
|
||||
}
|
||||
},
|
||||
"navigation": {
|
||||
"hostStackScreens": {
|
||||
"host": "Host",
|
||||
"editHost": "Edit host",
|
||||
"accounts": "Accounts",
|
||||
"tasks": "Tasks",
|
||||
"terminal": "Terminal",
|
||||
"sourceControl": "Source Control",
|
||||
"agentSessionHistory": "Agent Session History",
|
||||
"changes": "Changes",
|
||||
"pullRequest": "Pull Request",
|
||||
"workspace": "Workspace"
|
||||
}
|
||||
},
|
||||
"notifications": {
|
||||
"notificationDeliverySection": {
|
||||
"onlyWhenAway": "Only when away from desktop",
|
||||
"onlyWhenAwayHint": "After 3 minutes without keyboard or mouse activity, or when locked.",
|
||||
"sound": "Notification sound",
|
||||
"suppressWhileFocused": "Suppress while focused",
|
||||
"suppressWhileFocusedHint": "Skip alerts for the workspace open on this phone.",
|
||||
"footer": "Alert types follow each paired desktop’s notification settings. Notifications pause after 7 days without using this app; open it and reconnect to resume."
|
||||
}
|
||||
},
|
||||
"onboarding": {
|
||||
"onboardingPage": {
|
||||
"sessionViewTitle": "How should sessions open?",
|
||||
"notificationsTitle": "Don’t miss when an agent needs you",
|
||||
"sessionViewDescription": "Choose whether supported agent sessions open in the terminal or Chat UI on this device. Press and hold a session tab to switch its view, or change the default later in Settings.",
|
||||
"notificationsDescription": "Get a notification on this phone when an agent finishes or is waiting — even if you aren’t using the app.",
|
||||
"notificationsDisclosure": "Delivered through Orca’s push service after your desktop has been idle for 3 minutes. Change this anytime in Settings.",
|
||||
"useChatUi": "Use Chat UI",
|
||||
"openInChatUi": "Open sessions in Chat UI",
|
||||
"keepTerminal": "Keep terminal",
|
||||
"openInTerminal": "Open sessions in the terminal",
|
||||
"enableNotifications": "Enable notifications",
|
||||
"enableAgentNotifications": "Enable agent notifications",
|
||||
"notNow": "Not now",
|
||||
"skipNotifications": "Skip notifications for now"
|
||||
},
|
||||
"notificationOnboardingPreview": {
|
||||
"codexFinished": "Codex finished",
|
||||
"testsPassing": "Tests are passing.",
|
||||
"claudeNeedsInput": "Claude needs input",
|
||||
"waitingOnYou": "Waiting on you.",
|
||||
"now": "now"
|
||||
}
|
||||
},
|
||||
"connection": {
|
||||
"connectionHealth": {
|
||||
"signInRequired": "Sign-in required on {{host}}",
|
||||
"signInDetail": "Sign in to Orca on your desktop to reconnect",
|
||||
"hostOffline": "{{host}} is offline",
|
||||
"hostOfflineDetail": "Check it's awake, Orca is running, and you're signed in",
|
||||
"relayAccessExpired": "Relay access expired for {{host}}",
|
||||
"rePairDetail": "Re-pair with your desktop",
|
||||
"relayUnreachable": "Can't reach Relay",
|
||||
"checkConnection": "Check your connection",
|
||||
"tailscaleHint": "check Tailscale",
|
||||
"hostFallback": "Host",
|
||||
"pairingInvalid": "Pairing invalid — re-pair with your desktop",
|
||||
"connected": "Connected",
|
||||
"relayConnectFailed": "Can't connect via Relay",
|
||||
"connectingViaRelay": "Connecting via Relay…",
|
||||
"disconnected": "Disconnected",
|
||||
"desktopUnreachable": "Can't reach desktop",
|
||||
"connectFailed": "Can't connect",
|
||||
"reconnecting": "Reconnecting…",
|
||||
"connecting": "Connecting…",
|
||||
"labelWithHint": "{{label}} — {{hint}}"
|
||||
},
|
||||
"hostEndpoint": {
|
||||
"addressRequired": "Enter a host address.",
|
||||
"invalidPort": "Port must be 1–65535.",
|
||||
"invalidScheme": "Use ws:// or wss:// (or host:port).",
|
||||
"invalidAddress": "Not a valid address.",
|
||||
"pathOrQueryNotAllowed": "Host must not include a path or query.",
|
||||
"invalidHostname": "Not a valid hostname.",
|
||||
"hostnameRequired": "Missing hostname."
|
||||
},
|
||||
"connectionPathLabel": {
|
||||
"directTailscale": "Direct · Tailscale",
|
||||
"directLan": "Direct · LAN",
|
||||
"directViaTailscale": "Direct via Tailscale",
|
||||
"directViaLan": "Direct via LAN"
|
||||
},
|
||||
"pairConfirmState": {
|
||||
"missingCode": "Missing pairing code",
|
||||
"invalidCode": "Not a valid pairing code"
|
||||
},
|
||||
"rpcClientStreamRegistry": {
|
||||
"interrupted": "Connection interrupted",
|
||||
"endedBeforeReady": "Streaming request ended before it was ready."
|
||||
},
|
||||
"rpcClientTerminalBinaryFrame": {
|
||||
"unsupportedFrame": "Unsupported terminal stream frame (opcode {{opcode}})."
|
||||
}
|
||||
},
|
||||
"worktree": {
|
||||
"agentRowDisplay": {
|
||||
"working": "Working",
|
||||
"monitoring": "Monitoring background tasks",
|
||||
"blocked": "Blocked",
|
||||
"waiting": "Waiting for input",
|
||||
"interrupted": "Interrupted",
|
||||
"failed": "Failed",
|
||||
"unconfirmed": "Couldn’t confirm",
|
||||
"idle": "Idle",
|
||||
"unverifiable": "No recent update",
|
||||
"stopping": "Stopping…",
|
||||
"justNow": "just now",
|
||||
"minutes_one": "{{count}}m",
|
||||
"minutes_other": "{{count}}m",
|
||||
"hours_one": "{{count}}h",
|
||||
"hours_other": "{{count}}h",
|
||||
"days_one": "{{count}}d",
|
||||
"days_other": "{{count}}d",
|
||||
"done": "Done"
|
||||
},
|
||||
"hostWorkspaceListStates": {
|
||||
"checkHost": "Check that your computer is awake and Orca is running",
|
||||
"loadFailed": "Could not load workspaces from this host",
|
||||
"retrying": "worktree.ps failed ({{error}}) — retrying automatically",
|
||||
"noMatches": "No matching workspaces",
|
||||
"noFilterMatches": "No workspaces match filters",
|
||||
"empty": "No workspaces",
|
||||
"reopen": "Reopen to update",
|
||||
"reopenDetail": "This host lists workspaces in a way this page does not know yet."
|
||||
},
|
||||
"workspaceListSections": {
|
||||
"pinned": "Pinned",
|
||||
"all": "All",
|
||||
"inReview": "In review",
|
||||
"inProgress": "In progress",
|
||||
"closed": "Closed",
|
||||
"done": "Done"
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,8 +1,607 @@
|
||||
{
|
||||
"accounts": {
|
||||
"screen": {
|
||||
"agentLogin": "Usar el inicio de sesión del propio agente",
|
||||
"connecting": "Conectando al host…",
|
||||
"connectingTo": "Conectando a {{name}}…",
|
||||
"default": "Predeterminado del sistema",
|
||||
"hostNotFound": "Host no encontrado",
|
||||
"invalidSnapshot": "Instantánea de cuentas del host no válida",
|
||||
"loadingAccounts": "Cargando cuentas…",
|
||||
"manageAccountsHint": "Añade cuentas o vuelve a autenticarlas desde Ajustes → Cuentas en el escritorio.",
|
||||
"refreshAccounts": "Actualizar cuentas",
|
||||
"sessionWindow": "5h",
|
||||
"switchFailed": "No se pudo cambiar de cuenta",
|
||||
"title": "Cuentas",
|
||||
"useReset": "Usar un restablecimiento",
|
||||
"weeklyWindow": "7d"
|
||||
}
|
||||
},
|
||||
"agentHistory": {
|
||||
"panel": {
|
||||
"all": "Todo",
|
||||
"emptyScope": "No hay sesiones anteriores de agentes en este ámbito.",
|
||||
"loadFailed": "No se pudo cargar",
|
||||
"missingResumeId": "Esta sesión no tiene un identificador de reanudación.",
|
||||
"noSearchResults": "No hay sesiones que coincidan con tu búsqueda.",
|
||||
"noSessions": "No hay sesiones de agentes",
|
||||
"placeholder": "Buscar sesiones, repo:, path:",
|
||||
"project": "Proyecto",
|
||||
"refreshSessions": "Actualizar sesiones de agentes",
|
||||
"resumeFailed": "No se pudo reanudar la sesión.",
|
||||
"sessionQueued": "Sesión de agente en cola.",
|
||||
"skipped_many": "{{count}} transcripciones omitidas",
|
||||
"skipped_one": "{{count}} transcripción omitida",
|
||||
"skipped_other": "{{count}} transcripciones omitidas",
|
||||
"title": "Historial de sesiones de agentes",
|
||||
"unavailable": "Historial de sesiones de agentes no disponible",
|
||||
"unknownHostPlatform": "No se pudo determinar la plataforma del host.",
|
||||
"updateHost": "Actualiza Orca en este host para consultar el historial de sesiones de agentes.",
|
||||
"waitingForHost": "Esperando al host…",
|
||||
"workspace": "Espacio de trabajo"
|
||||
},
|
||||
"resumeTarget": {
|
||||
"differentHost": "Esta sesión pertenece a un host diferente. Abre un espacio de trabajo en el mismo host para reanudarla.",
|
||||
"openWorkspace": "Abre un espacio de trabajo antes de reanudar una sesión.",
|
||||
"runtimeUnavailable": "No se pueden reanudar sesiones desde el historial en espacios de trabajo alojados en entornos de ejecución."
|
||||
},
|
||||
"sessionCard": {
|
||||
"untitledSession": "Sesión sin título"
|
||||
},
|
||||
"sessionHistoryList": {
|
||||
"assistantRole": "asistente",
|
||||
"currentWorktree": "worktree actual",
|
||||
"messages_many": "{{count}} mensajes",
|
||||
"messages_one": "{{count}} mensaje",
|
||||
"messages_other": "{{count}} mensajes",
|
||||
"resumeSession": "Reanudar sesión de agente",
|
||||
"userRole": "usuario"
|
||||
},
|
||||
"useAgentHistoryState": {
|
||||
"hostUnreachable": "No se pudo contactar con el host",
|
||||
"loadFailed": "No se pudieron cargar las sesiones de agentes",
|
||||
"waitingForHost": "Esperando al host…"
|
||||
},
|
||||
"worktreeNavigationActions": {
|
||||
"agentSessionHistory": "Historial de sesiones de agentes",
|
||||
"sourceControl": "Control de código fuente"
|
||||
}
|
||||
},
|
||||
"app": {
|
||||
"mobileOnboarding": {
|
||||
"notificationSettingsFailed": "No se pudo actualizar la configuración de notificaciones. Inténtalo de nuevo.",
|
||||
"progress": "Progreso del onboarding",
|
||||
"saveChoiceFailed": "No se pudo guardar tu elección. Inténtalo de nuevo.",
|
||||
"stepProgress": "Paso {{step}} de {{total}}"
|
||||
},
|
||||
"notifications": {
|
||||
"description": "Recibe alertas de los agentes incluso cuando la app esté cerrada. Se envían a través del servicio de notificaciones push de Orca y de Apple o Google."
|
||||
},
|
||||
"pair": {
|
||||
"backToHome": "Volver al inicio",
|
||||
"missingCode": "Falta el código de emparejamiento"
|
||||
},
|
||||
"pairConfirm": {
|
||||
"backToHome": "Volver al inicio",
|
||||
"connectTimedOut": "No se pudo conectar en {{seconds}} s; consulta el registro de abajo para ver dónde se detuvo.",
|
||||
"connecting": "Conectando…",
|
||||
"description": "Has abierto un enlace de emparejamiento desde tu escritorio. Confirma para añadirlo a tus hosts.",
|
||||
"pairButton": "Emparejar",
|
||||
"pairingFailed": "Error de emparejamiento: {{error}}",
|
||||
"pairingLog": "Registro de emparejamiento",
|
||||
"title": "¿Emparejar con este escritorio?"
|
||||
},
|
||||
"pairScan": {
|
||||
"cameraDisabledDescription": "Activa el acceso a la cámara en Ajustes o pega el código de emparejamiento.",
|
||||
"cameraDisabledTitle": "Acceso a la cámara desactivado",
|
||||
"cameraPermissionDescription": "Escanea el código QR de Orca en tu escritorio o pega el código de emparejamiento.",
|
||||
"connectTimedOut": "No se pudo conectar en {{seconds}} s; consulta el registro de abajo para ver dónde se detuvo.",
|
||||
"connecting": "Conectando…",
|
||||
"invalidPairingCode": "No es un código de emparejamiento válido; cópialo de tu ordenador y vuelve a pegarlo.",
|
||||
"invalidQrCode": "No es un código QR de Orca válido",
|
||||
"openDesktopStep": "Abre Orca en tu ordenador",
|
||||
"openMobileSettingsStep": "Ve a Ajustes → Móvil",
|
||||
"openSettings": "Abrir Ajustes",
|
||||
"pairingFailed": "Error de emparejamiento: {{error}}",
|
||||
"pairingLog": "Registro de emparejamiento",
|
||||
"pasteCodeMessage": "Copia el código que aparece debajo del QR en tu ordenador.",
|
||||
"pasteCodePlaceholder": "orca://pair?code=... o pega el código",
|
||||
"pasteCodeTitle": "Pegar código de emparejamiento",
|
||||
"pasteInstead": "Pegar código en su lugar",
|
||||
"pastePairingCode": "O pegar código de emparejamiento",
|
||||
"scanCodeStep": "Escanea el código QR",
|
||||
"title": "Emparejar con un escritorio",
|
||||
"tryAgain": "Reintentar"
|
||||
},
|
||||
"terminalSettings": {
|
||||
"autocomplete": "Autocompletado y autocorrección",
|
||||
"autocompleteOff": "Desactivado",
|
||||
"autocompleteOn": "Activado",
|
||||
"defaultText": "Predeterminado (100%)",
|
||||
"keepPhoneSize": "Mantener en tamaño de teléfono (predeterminado)",
|
||||
"keyboardInputDescription": "El autocompletado, la autocorrección y las sugerencias ortográficas solo se aplican a los comandos que se editan antes de enviarlos. Usa el botón de doble flecha situado encima del teclado para salir del modo de entrada en directo y edita el comando antes de tocar Enviar. Estas funciones están desactivadas por defecto para evitar que se modifiquen comandos, opciones o rutas.",
|
||||
"keyboardInputHeading": "ENTRADA DE TECLADO",
|
||||
"largeText": "Grande (125%)",
|
||||
"largerText": "Más grande (150%)",
|
||||
"largestText": "Máximo (200%)",
|
||||
"leaveAppDescription": "Mientras usas un terminal en el teléfono, Orca lo reduce para ajustarlo a la pantalla. Al cerrar la aplicación o dejarla en segundo plano, este ajuste controla si el terminal mantiene el tamaño del teléfono (para que las herramientas CLI interactivas no redistribuyan su contenido) o recupera el tamaño del escritorio. Siempre puedes pulsar Restaurar en el banner del escritorio para cambiar el tamaño manualmente.",
|
||||
"leaveAppHeading": "AL SALIR DE LA APLICACIÓN",
|
||||
"noPairedDesktops": "Aún no hay escritorios emparejados. Empareja uno para controlar el comportamiento del terminal.",
|
||||
"restoreAfterMinutes_many": "Después de {{count}} minutos",
|
||||
"restoreAfterMinutes_one": "Después de {{count}} minuto",
|
||||
"restoreAfterMinutes_other": "Después de {{count}} minutos",
|
||||
"restoreAfterSeconds_many": "Después de {{count}}s",
|
||||
"restoreAfterSeconds_one": "Después de {{count}}s",
|
||||
"restoreAfterSeconds_other": "Después de {{count}}s",
|
||||
"restoreHost": "Restaurar {{name}}",
|
||||
"smallerText": "Más pequeño (75%)",
|
||||
"smallestText": "Mínimo (50%)",
|
||||
"textSize": "Tamaño del texto",
|
||||
"textSizeDescription": "Ajusta el tamaño del texto del terminal. Los tamaños pequeños permiten ver más columnas con márgenes laterales; los grandes muestran menos columnas. Arrastra hacia los lados para desplazarte. También puedes pellizcar la pantalla para hacer zoom en el propio terminal, lo que actualiza este ajuste. Solo afecta a la visualización en este dispositivo; no modifica el terminal del escritorio.",
|
||||
"textSizeHeading": "TAMAÑO DEL TEXTO",
|
||||
"textSizePickerTitle": "Tamaño del texto del terminal",
|
||||
"title": "Terminal"
|
||||
}
|
||||
},
|
||||
"common": {
|
||||
"back": "Atrás"
|
||||
"back": "Atrás",
|
||||
"cancel": "Cancelar",
|
||||
"continue": "Continuar",
|
||||
"copied": "Copiado",
|
||||
"delete": "Eliminar",
|
||||
"ok": "Aceptar",
|
||||
"remove": "Eliminar",
|
||||
"retry": "Reintentar",
|
||||
"save": "Guardar",
|
||||
"settings": "Ajustes",
|
||||
"unknown": "Desconocido"
|
||||
},
|
||||
"connection": {
|
||||
"connectionHealth": {
|
||||
"checkConnection": "Comprueba tu conexión.",
|
||||
"connectFailed": "No se puede conectar",
|
||||
"connected": "Conectado",
|
||||
"connecting": "Conectando…",
|
||||
"connectingViaRelay": "Conectando a través de Relay…",
|
||||
"desktopUnreachable": "No se puede acceder al escritorio",
|
||||
"disconnected": "Desconectado",
|
||||
"hostFallback": "Host",
|
||||
"hostOffline": "{{host}} está sin conexión",
|
||||
"hostOfflineDetail": "Comprueba que no esté en reposo, que Orca esté en ejecución y que hayas iniciado sesión.",
|
||||
"labelWithHint": "{{label}} — {{hint}}",
|
||||
"pairingInvalid": "Emparejamiento no válido — vuelve a emparejar tu escritorio",
|
||||
"rePairDetail": "Vuelve a emparejar tu escritorio.",
|
||||
"reconnecting": "Reconectando…",
|
||||
"relayAccessExpired": "El acceso a {{host}} por Relay ha caducado",
|
||||
"relayConnectFailed": "No se puede conectar a través de Relay",
|
||||
"relayUnreachable": "No se puede acceder a Relay",
|
||||
"signInDetail": "Inicia sesión en Orca en tu escritorio para volver a conectarte.",
|
||||
"signInRequired": "Debes iniciar sesión en {{host}}",
|
||||
"tailscaleHint": "comprueba Tailscale"
|
||||
},
|
||||
"connectionPathLabel": {
|
||||
"directLan": "Directa · LAN",
|
||||
"directTailscale": "Directa · Tailscale",
|
||||
"directViaLan": "Directa vía LAN",
|
||||
"directViaTailscale": "Directa vía Tailscale"
|
||||
},
|
||||
"hostEndpoint": {
|
||||
"addressRequired": "Introduce una dirección de host.",
|
||||
"hostnameRequired": "Falta el nombre de host.",
|
||||
"invalidAddress": "La dirección no es válida.",
|
||||
"invalidHostname": "El nombre de host no es válido.",
|
||||
"invalidPort": "El puerto debe estar entre 1 y 65535.",
|
||||
"invalidScheme": "Usa ws:// o wss:// (o host:port).",
|
||||
"pathOrQueryNotAllowed": "La dirección del host no debe incluir una ruta ni una consulta."
|
||||
},
|
||||
"pairConfirmState": {
|
||||
"invalidCode": "El código de emparejamiento no es válido",
|
||||
"missingCode": "Falta el código de emparejamiento"
|
||||
},
|
||||
"rpcClientStreamRegistry": {
|
||||
"endedBeforeReady": "La solicitud de transmisión finalizó antes de estar lista.",
|
||||
"interrupted": "Conexión interrumpida"
|
||||
},
|
||||
"rpcClientTerminalBinaryFrame": {
|
||||
"unsupportedFrame": "Trama no compatible en el flujo del terminal (opcode {{opcode}})."
|
||||
}
|
||||
},
|
||||
"diagnostics": {
|
||||
"connectionDiagnosticsView": {
|
||||
"authFailed": "autenticación fallida",
|
||||
"connected": "conectado",
|
||||
"connecting": "conectando",
|
||||
"copyReport": "Copiar informe",
|
||||
"diagnosisHeading": "Posible causa",
|
||||
"disconnected": "desconectado",
|
||||
"handshaking": "negociando la conexión",
|
||||
"noEvents": "Aún no hay eventos de conexión. Aparecerán a medida que la aplicación intente conectarse a este host.",
|
||||
"noHosts": "No hay hosts emparejados.",
|
||||
"privacyHint": "Envía un informe de tamaño limitado con los datos sensibles ocultos, que incluye el nombre del host, el punto de conexión, las versiones, el estado de la conexión y los eventos; nunca el contenido del terminal ni las credenciales.",
|
||||
"reconnecting": "reconectando",
|
||||
"retrySending": "Reintentar envío",
|
||||
"sendDiagnostics": "Enviar diagnósticos a Orca",
|
||||
"sending": "Enviando…",
|
||||
"sent": "Diagnósticos enviados",
|
||||
"stateWithAttempt": "{{state}} · intento {{attempts}}",
|
||||
"title": "Diagnóstico de red"
|
||||
},
|
||||
"mobileWebBundleProbeRow": {
|
||||
"assets": "Recursos",
|
||||
"build": "Compilación",
|
||||
"bundle": "Paquete",
|
||||
"bytes": "Bytes",
|
||||
"connecting": "Conectando…",
|
||||
"elapsed": "Tiempo transcurrido",
|
||||
"elapsedMilliseconds": "{{elapsedMs}} ms",
|
||||
"fetchBundle": "Obtener paquete web móvil",
|
||||
"fetching": "Obteniendo paquete…",
|
||||
"host": "Host",
|
||||
"unknownHost": "host desconocido"
|
||||
},
|
||||
"mobileWebShellUpdateFailureRow": {
|
||||
"title": "Actualizaciones del espacio de trabajo"
|
||||
},
|
||||
"troubleshootCommonIssues": {
|
||||
"allowNotifications": "Comprueba que los ajustes del sistema permitan las notificaciones de Orca y que los modos de concentración y No molestar estén desactivados.",
|
||||
"checkTailnet": "Comprueba que el escritorio esté activo y aparezca conectado en tu red de Tailscale.",
|
||||
"desktopTitle": "App de escritorio cerrada",
|
||||
"disableVpn": "Desactiva esa VPN o activa la tunelización dividida / \"Allow LAN\".",
|
||||
"firewallTitle": "El cortafuegos bloquea el puerto 6768",
|
||||
"keepTailscaleOn": "Las direcciones de host como 100.x.x.x o *.ts.net se conectan a través de Tailscale — mantenlo ACTIVADO.",
|
||||
"macosFirewall": "macOS: Ajustes del Sistema → Red → Firewall — permite las conexiones de Orca.",
|
||||
"notificationNetwork": "Prueba con datos móviles u otra red Wi-Fi. Si las alertas llegan tras cambiar de red, es posible que tu red esté retrasando la entrega.",
|
||||
"notificationsTitle": "Notificaciones push",
|
||||
"openDesktop": "Orca debe estar abierto en tu escritorio para aceptar conexiones.",
|
||||
"pairAfterUpdate": "Tras una actualización, puede que debas volver a emparejar mediante un código QR.",
|
||||
"reconnectWifi": "Prueba a volver a conectar el WiFi en ambos dispositivos.",
|
||||
"restartBothApps": "Reinicia ambas apps si se sigue agotando el tiempo de espera.",
|
||||
"restartDesktop": "Prueba a reiniciar Orca: el servidor de conexión móvil se inicia al abrir la app.",
|
||||
"restartTailscale": "iOS/Android puede bloquear el túnel sin avisar: desactiva Tailscale y vuelve a activarlo en la app de Tailscale.",
|
||||
"restrictedNetwork": "Las redes de empresas o centros educativos pueden bloquear P2P: prueba con un punto de acceso personal.",
|
||||
"retryHost": "Vuelve a la lista de hosts y toca tu host para reintentar.",
|
||||
"sameNetwork": "Ambos dispositivos deben estar en la misma LAN (salvo que se conecten a través de Tailscale).",
|
||||
"sameSubnet": "Ethernet y WiFi deben compartir la misma subred.",
|
||||
"tailscaleTitle": "Host de Tailscale inaccesible",
|
||||
"timeoutTitle": "Tiempo de espera de conexión agotado",
|
||||
"updateTailscale": "Actualiza la app de Tailscale: las versiones recientes corrigen errores de reconexión.",
|
||||
"vpnRouting": "Las VPN distintas de Tailscale pueden dirigir el tráfico local a través de un servidor remoto.",
|
||||
"vpnTitle": "Interferencias de otras VPN",
|
||||
"wifiSignal": "Comprueba la intensidad de la señal WiFi en tu teléfono.",
|
||||
"wifiTitle": "Redes WiFi diferentes",
|
||||
"windowsFirewall": "Windows: Firewall de Windows Defender → Permitir una aplicación — habilita Orca para redes privadas."
|
||||
},
|
||||
"troubleshootView": {
|
||||
"commonIssues": "Problemas comunes",
|
||||
"runAgain": "Volver a ejecutar",
|
||||
"runDiagnostics": "Ejecutar diagnósticos",
|
||||
"running": "Ejecutando…",
|
||||
"title": "Solución de problemas",
|
||||
"viewNetworkDiagnostics": "Ver diagnósticos de red"
|
||||
},
|
||||
"useTroubleshootDiagnostics": {
|
||||
"connected": "Conectado",
|
||||
"hostDataFailed": "No se pudieron leer los datos de los hosts",
|
||||
"hostTestFailed": "No se pudo realizar la prueba",
|
||||
"hosts": "Hosts",
|
||||
"internet": "Internet",
|
||||
"noConnection": "Sin conexión",
|
||||
"noPairedHosts": "Ninguno — escanea un QR para emparejar",
|
||||
"pairedCount": "Emparejados: {{count}}",
|
||||
"pairedHosts": "Hosts emparejados",
|
||||
"platform": "Plataforma",
|
||||
"reachable": "Accesible en {{endpoint}}",
|
||||
"unexpectedResponse": "Respuesta inesperada"
|
||||
},
|
||||
"webShellUpdateFailureCopy": {
|
||||
"failedAgo": "La última actualización de {{hostName}} falló hace {{time}}: {{reason}}{{offered}}. {{outcome}}",
|
||||
"failedJustNow": "La última actualización de {{hostName}} acaba de fallar: {{reason}}{{offered}}. {{outcome}}"
|
||||
}
|
||||
},
|
||||
"dictation": {
|
||||
"setup": {
|
||||
"alreadyActive": "El dictado ya se está usando en el ordenador o en otro teléfono.",
|
||||
"cloudModelNeedsKey": "Requiere una clave de OpenAI",
|
||||
"deleteFailed": "No se pudo eliminar el modelo",
|
||||
"desktopUpdateRequired": "Actualiza la app de Orca en el escritorio emparejado para usar los ajustes de voz del móvil.",
|
||||
"downloadFailed": "No se pudo iniciar la descarga",
|
||||
"loadFailed": "No se pudieron cargar los modelos de dictado",
|
||||
"repairRequired": "Vuelve a emparejar este teléfono con el ordenador para usar el dictado.",
|
||||
"serverUpdateRequired": "Actualiza Orca en este servidor para usar el dictado por voz.",
|
||||
"sessionLost": "El dictado perdió la conexión con el ordenador. Inténtalo de nuevo.",
|
||||
"updateFailed": "No se pudieron actualizar los ajustes de dictado"
|
||||
}
|
||||
},
|
||||
"home": {
|
||||
"accountUsageCards": {
|
||||
"sectionHeading": "Uso de la cuenta",
|
||||
"sessionWindow": "5h",
|
||||
"systemDefault": "Predeterminado del sistema",
|
||||
"weeklyWindow": "7d"
|
||||
},
|
||||
"appUpdateCard": {
|
||||
"dismissAccessibilityLabel": "Descartar la actualización de Orca {{version}}",
|
||||
"title": "Orca {{version}} está disponible",
|
||||
"updateAccessibilityLabel": "Orca {{version}} está disponible, toca para actualizar",
|
||||
"updateHint": "Toca para actualizar"
|
||||
},
|
||||
"emptyState": {
|
||||
"connectedDescription": "Tu escritorio aparecerá aquí. Todo está cifrado de extremo a extremo.",
|
||||
"connectedTitle": "Ya tienes conexión",
|
||||
"description": "Empareja tu teléfono con Orca en tu ordenador para supervisar a tus agentes, acceder a cualquier terminal y gestionar el trabajo desde el móvil.",
|
||||
"openDesktopDescription": "Ve a Ajustes → Móvil y genera un código QR de emparejamiento.",
|
||||
"openDesktopTitle": "Abre Orca en el escritorio",
|
||||
"pairDesktop": "Emparejar escritorio",
|
||||
"scanCodeDescription": "Toca el botón de arriba para abrir el escáner. Apunta al código QR de tu pantalla.",
|
||||
"scanCodeTitle": "Escanea el código",
|
||||
"stepsHeading": "Cómo funciona",
|
||||
"title": "Conecta tu escritorio"
|
||||
},
|
||||
"listFooter": {
|
||||
"hostPickerTitle": "Abrir tareas en",
|
||||
"resumeHeading": "Reanudar",
|
||||
"tasksHeading": "Tareas"
|
||||
},
|
||||
"listHeader": {
|
||||
"agentTime": "Tiempo de agentes",
|
||||
"agentsSpawned": "Agentes iniciados",
|
||||
"daysHours": "{{days}} d {{hours}} h",
|
||||
"desktopsHeading": "Escritorios",
|
||||
"hoursMinutes": "{{hours}} h {{minutes}} min",
|
||||
"minutes": "{{minutes}} min",
|
||||
"prsCreated": "PRs creados",
|
||||
"title": "Bienvenido de nuevo"
|
||||
},
|
||||
"screen": {
|
||||
"pairingCheckFailed": "No se pudo comprobar el emparejamiento",
|
||||
"removeHostFailed": "No se pudo eliminar el host",
|
||||
"removeHostMessage": "¿Eliminar \"{{name}}\"? Puedes volver a emparejarlo más tarde.",
|
||||
"removeHostTitle": "Eliminar host",
|
||||
"tryAgain": "Por favor, vuelve a intentarlo."
|
||||
},
|
||||
"tasksCard": {
|
||||
"noSources": "No hay fuentes de tareas conectadas",
|
||||
"openProviderTasks": "Abrir tareas de {{provider}}",
|
||||
"title": "Tareas"
|
||||
}
|
||||
},
|
||||
"hooks": {
|
||||
"dictationDesktopStart": {
|
||||
"recordingFailed": "No se pudo iniciar la grabación del micrófono"
|
||||
},
|
||||
"useMobileDictation": {
|
||||
"inputClosed": "El dictado de voz se detuvo porque esta sesión ya no acepta entradas. Vuelve a intentarlo.",
|
||||
"microphoneInitFailed": "No se pudo inicializar el micrófono",
|
||||
"microphoneInterrupted": "El dictado de voz se detuvo porque se interrumpió el uso del micrófono. Vuelve a intentarlo.",
|
||||
"microphonePermissionDenied": "Permiso de acceso al micrófono denegado",
|
||||
"noSpeech": "No se detectó ninguna voz."
|
||||
}
|
||||
},
|
||||
"host": {
|
||||
"edit": {
|
||||
"address": "Dirección",
|
||||
"addressHint": "Acepta IP, host:port o ws:// / wss://. Una URL wss:// sin puerto usa el puerto 443. Las direcciones sin protocolo ni puerto conservan el puerto actual (o 6768).",
|
||||
"connectionPreview": "Se conecta a {{endpoint}}",
|
||||
"goBack": "Volver",
|
||||
"help": "Cambia el nombre que se muestra o la dirección de conexión. Deja el nombre vacío para usar el que indica el escritorio. Cambiar la dirección solo modifica adónde se conecta este teléfono; no se repite el emparejamiento. Usa esta opción cuando el mismo escritorio sea accesible desde otra IP (por ejemplo, la LAN de casa o Tailscale).",
|
||||
"hostNamePlaceholder": "Nombre del host",
|
||||
"hostRemoved": "Este host se eliminó de este teléfono.",
|
||||
"loadFailed": "No se pudo cargar el host.",
|
||||
"missingHost": "Falta el host.",
|
||||
"name": "Nombre",
|
||||
"saveFailed": "No se pudo guardar el host.",
|
||||
"saveHost": "Guardar host",
|
||||
"title": "Editar host"
|
||||
}
|
||||
},
|
||||
"hostScreen": {
|
||||
"hostListActionSheetActions": {
|
||||
"connect": "Conectar",
|
||||
"disconnect": "Desconectar",
|
||||
"editHost": "Editar host",
|
||||
"networkDiagnostics": "Diagnóstico de red",
|
||||
"reconnect": "Reconectar"
|
||||
},
|
||||
"hostPendingCreationRow": {
|
||||
"creatingWorkspace": "Creando espacio de trabajo…"
|
||||
},
|
||||
"hostRailActions": {
|
||||
"accounts": "Cuentas",
|
||||
"closeSearch": "Cerrar búsqueda",
|
||||
"floatingWorkspace": "Espacio de trabajo flotante",
|
||||
"newWorkspace": "Nuevo espacio de trabajo",
|
||||
"searchWorkspaces": "Buscar espacios de trabajo",
|
||||
"tasks": "Tareas"
|
||||
},
|
||||
"hostScreenHeader": {
|
||||
"accounts": "Cuentas",
|
||||
"activeFilters_many": "Opciones de espacio de trabajo ({{count}} filtros activos)",
|
||||
"activeFilters_one": "Opciones de espacio de trabajo ({{count}} filtro activo)",
|
||||
"activeFilters_other": "Opciones de espacio de trabajo ({{count}} filtros activos)",
|
||||
"backToHosts": "Volver a los hosts",
|
||||
"closeSearch": "Cerrar búsqueda",
|
||||
"floatingWorkspace": "Espacio de trabajo flotante",
|
||||
"hideSidebar": "Ocultar barra lateral",
|
||||
"reconnect": "Reconectar",
|
||||
"searchWorkspaces": "Buscar espacios de trabajo",
|
||||
"tasks": "Tareas",
|
||||
"workspaceOptions": "Opciones de espacio de trabajo"
|
||||
},
|
||||
"hostScreenOverlays": {
|
||||
"deleteConfirmMessage": "¿Eliminar \"{{name}}\" ({{branch}})?",
|
||||
"deleteWorktree": "Eliminar worktree",
|
||||
"pin": "Fijar",
|
||||
"primaryWorktreeDeleteHint": "El worktree principal no se puede eliminar. Quita el proyecto en su lugar.",
|
||||
"removeHost": "Eliminar host",
|
||||
"removeHostMessage": "¿Eliminar \"{{name}}\"? Puedes volver a emparejarlo más tarde.",
|
||||
"sleep": "Suspender",
|
||||
"unpin": "Desanclar"
|
||||
},
|
||||
"hostWorkspaceList": {
|
||||
"searchPlaceholder": "Buscar espacios de trabajo…",
|
||||
"searchWorkspaces": "Buscar espacios de trabajo"
|
||||
},
|
||||
"workspaceDetailsSheet": {
|
||||
"checksFailing": "Checks: fallidos",
|
||||
"checksPassing": "Checks: superados",
|
||||
"checksPending": "Checks: pendientes",
|
||||
"closed": "Cerrado",
|
||||
"createdByAutomation": "Creado por automatización",
|
||||
"createdByNamedAutomation": "Creado por la automatización “{{name}}”",
|
||||
"createdFromCli": "Creado desde Orca CLI",
|
||||
"draft": "Borrador",
|
||||
"merged": "Fusionado",
|
||||
"openLink": "Abrir {{label}}",
|
||||
"openLinkWithTitle": "Abrir {{label}}, {{title}}",
|
||||
"prOpen": "Abierto"
|
||||
},
|
||||
"workspaceOptionControls": {
|
||||
"agentActivity": "Actividad de agentes",
|
||||
"agentActivityLayout": "Diseño de actividad del agente",
|
||||
"agentActivitySort": "Actividad de agentes",
|
||||
"agentActivitySortDescription": "Agentes que necesitan atención, luego actividad más reciente.",
|
||||
"agentStatuses": "Estados de agentes",
|
||||
"allHosts": "Todos los hosts",
|
||||
"allProjects": "Todos los proyectos",
|
||||
"automation": "Automatización",
|
||||
"branchFolderPath": "Rama / ruta de carpeta",
|
||||
"branchName": "Nombre de rama",
|
||||
"compact": "Compacto",
|
||||
"detailed": "Detallado",
|
||||
"fullList": "Lista completa",
|
||||
"githubIssues": "Issues de GitHub",
|
||||
"githubTicket": "Ticket de GitHub",
|
||||
"host": "Host",
|
||||
"hover": "Al pasar el cursor",
|
||||
"jiraIssue": "Issue de Jira",
|
||||
"jiraIssues": "Issues de Jira",
|
||||
"linearIssue": "Issue de Linear",
|
||||
"linearIssues": "Issues de Linear",
|
||||
"manual": "Manual",
|
||||
"manualProjectOrderDescription": "Arrastra proyectos para organizarlos",
|
||||
"manualWorkspaceOrderDescription": "Arrastra los espacios de trabajo para organizarlos dentro de cada grupo.",
|
||||
"name": "Nombre",
|
||||
"none": "Ninguno",
|
||||
"notes": "Notas",
|
||||
"ports": "Puertos",
|
||||
"pr": "PR",
|
||||
"prMrLink": "Enlace PR/MR",
|
||||
"project": "Proyecto",
|
||||
"recent": "Reciente",
|
||||
"recentProjectOrderDescription": "Actividad más reciente en el espacio de trabajo",
|
||||
"searchProjects": "Buscar proyectos",
|
||||
"searchProjectsPlaceholder": "Buscar proyectos…",
|
||||
"selectedCount": "{{count}} de {{total}}",
|
||||
"status": "Estado"
|
||||
},
|
||||
"workspaceOptionsSheet": {
|
||||
"backToOptions": "Volver a las opciones de espacio de trabajo",
|
||||
"cardDisplay": "Visualización de tarjeta",
|
||||
"cardLayout": "Diseño de tarjeta",
|
||||
"clearFilters": "Borrar filtros",
|
||||
"exceptDefaultBranch": "Excepto la rama predeterminada",
|
||||
"filters": "Filtros",
|
||||
"groupBy": "Agrupar por",
|
||||
"hideAutomationCreated": "Ocultar creados por automatizaciones",
|
||||
"hideCliCreated": "Ocultar creados por el CLI",
|
||||
"hideDefaultBranch": "Ocultar rama predeterminada",
|
||||
"hideDetachedHead": "Ocultar HEAD desacoplado",
|
||||
"hideOtherClientWorkspaces": "Ocultar espacios de trabajo de otros clientes",
|
||||
"hideSleeping": "Ocultar en reposo",
|
||||
"hosts": "Hosts",
|
||||
"localLinux": "Linux local",
|
||||
"localMac": "Mac local",
|
||||
"localWindows": "Windows local",
|
||||
"projectOrder": "Orden del proyecto",
|
||||
"projects": "Proyectos",
|
||||
"savingToDesktop": "Guardando en el escritorio",
|
||||
"show": "Mostrar",
|
||||
"showProperties": "Mostrar propiedades",
|
||||
"sortBy": "Ordenar por",
|
||||
"syncedAcrossDevices": "Sincronizadas entre tus dispositivos",
|
||||
"thisComputer": "Este equipo",
|
||||
"waitingForDesktop": "Aún esperando respuesta del escritorio",
|
||||
"workspaceOptions": "Opciones de espacio de trabajo"
|
||||
}
|
||||
},
|
||||
"navigation": {
|
||||
"hostStackScreens": {
|
||||
"accounts": "Cuentas",
|
||||
"agentSessionHistory": "Historial de sesiones de agentes",
|
||||
"changes": "Cambios",
|
||||
"editHost": "Editar host",
|
||||
"host": "Host",
|
||||
"pullRequest": "Solicitud de extracción",
|
||||
"sourceControl": "Control de código fuente",
|
||||
"tasks": "Tareas",
|
||||
"terminal": "Terminal",
|
||||
"workspace": "Espacio de trabajo"
|
||||
}
|
||||
},
|
||||
"notifications": {
|
||||
"notificationDeliverySection": {
|
||||
"footer": "Los tipos de alerta dependen de la configuración de notificaciones de cada escritorio emparejado. Las notificaciones se pausan tras 7 días sin usar esta aplicación; ábrela y vuelve a conectarte para reanudarlas.",
|
||||
"onlyWhenAway": "Solo cuando no uses el escritorio",
|
||||
"onlyWhenAwayHint": "Tras 3 minutos sin actividad del teclado o del ratón, o cuando el equipo esté bloqueado.",
|
||||
"sound": "Sonido de notificación",
|
||||
"suppressWhileFocused": "Omitir alertas del espacio de trabajo abierto",
|
||||
"suppressWhileFocusedHint": "Omite las alertas del espacio de trabajo abierto en este teléfono."
|
||||
}
|
||||
},
|
||||
"onboarding": {
|
||||
"notificationOnboardingPreview": {
|
||||
"claudeNeedsInput": "Claude necesita tu respuesta",
|
||||
"codexFinished": "Codex ha terminado",
|
||||
"now": "ahora",
|
||||
"testsPassing": "Las pruebas pasan.",
|
||||
"waitingOnYou": "Te está esperando."
|
||||
},
|
||||
"onboardingPage": {
|
||||
"enableAgentNotifications": "Activar notificaciones de agentes",
|
||||
"enableNotifications": "Activar notificaciones",
|
||||
"keepTerminal": "Mantener el terminal",
|
||||
"notNow": "Ahora no",
|
||||
"notificationsDescription": "Recibe una notificación en este teléfono cuando un agente termine o esté esperando, aunque no estés usando la aplicación.",
|
||||
"notificationsDisclosure": "Se envían a través del servicio de notificaciones push de Orca cuando tu escritorio lleva 3 minutos inactivo. Puedes cambiarlo cuando quieras en Ajustes.",
|
||||
"notificationsTitle": "Entérate cuando un agente te necesite",
|
||||
"openInChatUi": "Abrir sesiones en la interfaz de chat",
|
||||
"openInTerminal": "Abrir sesiones en el terminal",
|
||||
"sessionViewDescription": "Elige si las sesiones de agentes compatibles se abren en el terminal o en la interfaz de chat de este dispositivo. Mantén pulsada la pestaña de una sesión para cambiar su vista, o cambia la opción predeterminada más tarde en Ajustes.",
|
||||
"sessionViewTitle": "¿Cómo quieres abrir las sesiones?",
|
||||
"skipNotifications": "Omitir las notificaciones por ahora",
|
||||
"useChatUi": "Usar interfaz de chat"
|
||||
}
|
||||
},
|
||||
"settings": {
|
||||
"aboutScreen": {
|
||||
"openLinkFailed": "No se pudo abrir el enlace. Vuelve a intentarlo.",
|
||||
"socialProfile": "Orca en X",
|
||||
"sourceCode": "Código fuente de Orca",
|
||||
"tagline": "IDE de código abierto con agentes para creadores 100x",
|
||||
"title": "Acerca de",
|
||||
"website": "Sitio web de Orca"
|
||||
},
|
||||
"appUpdateSettingsRows": {
|
||||
"checkFailed": "No se pudo comprobar",
|
||||
"checkUpdates": "Buscar actualizaciones",
|
||||
"checkUpdatesStatus": "Buscar actualizaciones, {{status}}",
|
||||
"checking": "Comprobando…",
|
||||
"installedVersion": "Versión {{version}}",
|
||||
"lastCheckedDays_many": "Última comprobación: hace {{count}} d",
|
||||
"lastCheckedDays_one": "Última comprobación: hace {{count}} d",
|
||||
"lastCheckedDays_other": "Última comprobación: hace {{count}} d",
|
||||
"lastCheckedHours_many": "Última comprobación: hace {{count}} h",
|
||||
"lastCheckedHours_one": "Última comprobación: hace {{count}} h",
|
||||
"lastCheckedHours_other": "Última comprobación: hace {{count}} h",
|
||||
"lastCheckedJustNow": "Última comprobación: ahora mismo",
|
||||
"lastCheckedMinutes_many": "Última comprobación: hace {{count}} min",
|
||||
"lastCheckedMinutes_one": "Última comprobación: hace {{count}} min",
|
||||
"lastCheckedMinutes_other": "Última comprobación: hace {{count}} min",
|
||||
"neverChecked": "Nunca",
|
||||
"upToDate": "Actualizado",
|
||||
"update": "Actualizar",
|
||||
"updateToVersion": "Actualizar a Orca {{version}}"
|
||||
},
|
||||
"appearance": {
|
||||
"language": {
|
||||
"description": "La opción Sistema usa el idioma de este dispositivo. Las solicitudes de permiso siempre usan el idioma del dispositivo.",
|
||||
@@ -17,6 +616,161 @@
|
||||
"system": "Sistema"
|
||||
},
|
||||
"title": "Apariencia"
|
||||
},
|
||||
"browserSettingsScreen": {
|
||||
"desktopBrowser": "Navegador de Orca en el escritorio",
|
||||
"desktopBrowserDescription": "Abrir en el navegador de tu escritorio emparejado y verlo en el teléfono.",
|
||||
"linksDescription": "Elige dónde se abren los enlaces HTTP(S) que tocas en la salida de la terminal.",
|
||||
"linksHeading": "ENLACES",
|
||||
"loadFailed": "No se pudieron cargar las preferencias del navegador. Vuelve a intentarlo.",
|
||||
"openTerminalLinks": "Abrir enlaces de la terminal",
|
||||
"phoneBrowser": "Navegador del teléfono",
|
||||
"phoneBrowserDescription": "Abrir en Safari, Chrome u otro navegador de este teléfono.",
|
||||
"saveFailed": "No se pudieron guardar las preferencias del navegador. Vuelve a intentarlo.",
|
||||
"title": "Navegador"
|
||||
},
|
||||
"menuItems": {
|
||||
"about": "Acerca de",
|
||||
"appearance": "Apariencia",
|
||||
"browser": "Navegador",
|
||||
"chatUi": "Interfaz de chat",
|
||||
"notifications": "Notificaciones",
|
||||
"terminal": "Terminal",
|
||||
"troubleshooting": "Solución de problemas",
|
||||
"voice": "Voz"
|
||||
},
|
||||
"menuScreen": {
|
||||
"privacyPolicy": "Política de privacidad",
|
||||
"support": "Soporte"
|
||||
},
|
||||
"nativeChatSettingsScreen": {
|
||||
"defaultViewDescription": "Elige cómo se abren en este dispositivo las sesiones de agentes compatibles (Claude, Codex y otros agentes con funciones de chat). La terminal muestra la CLI tal cual; la interfaz de chat ofrece una vista como la de la app de escritorio. También puedes cambiar la vista de cada sesión desde el menú que aparece al mantenerla pulsada.",
|
||||
"defaultViewHeading": "VISTA PREDETERMINADA",
|
||||
"off": "Desactivado",
|
||||
"on": "Activado",
|
||||
"openInChatUi": "Abrir sesiones en la interfaz de chat",
|
||||
"title": "Interfaz de chat"
|
||||
},
|
||||
"nativeNotificationDeliverySettings": {
|
||||
"loadFailed": "No se pudo cargar la configuración de entrega. Vuelve a abrir esta pantalla para reintentarlo.",
|
||||
"saveFailed": "No se pudo guardar la configuración de entrega. Vuelve a intentarlo.",
|
||||
"updateDesktopHint": "Empareja un escritorio actualizado para recibir notificaciones en este teléfono."
|
||||
},
|
||||
"notificationDisplayTest": {
|
||||
"accepted": "Prueba aceptada por el servicio de notificaciones push de Orca. Comprueba si recibes la notificación.",
|
||||
"connectDesktop": "Conéctate a un escritorio y vuelve a intentarlo.",
|
||||
"description": "Envía una prueba a través del servicio de notificaciones push de Orca.",
|
||||
"desktopUnreachable": "No se pudo conectar con el escritorio. Vuelve a intentarlo.",
|
||||
"loadFailed": "No se pudieron cargar los escritorios emparejados.",
|
||||
"pairDesktop": "Empareja un escritorio y vuelve a intentarlo.",
|
||||
"pushServiceFailed": "No se pudo enviar a través del servicio de notificaciones push de Orca. Vuelve a intentarlo.",
|
||||
"rateLimited": "Demasiadas notificaciones. Vuelve a intentarlo más tarde.",
|
||||
"reconnectPhone": "Vuelve a conectarte para registrar este teléfono para recibir notificaciones.",
|
||||
"sendButton": "Enviar notificación de prueba",
|
||||
"sendFailed": "No se pudo enviar la notificación push de prueba.",
|
||||
"sending": "Enviando…",
|
||||
"troubleReceivingAlerts": "¿Tienes problemas para recibir alertas?",
|
||||
"troubleshooting": "Solución de problemas",
|
||||
"updateDesktop": "Actualiza tu escritorio para realizar esta prueba."
|
||||
},
|
||||
"notificationSettingsScreen": {
|
||||
"description": "Recibe notificaciones en este dispositivo cuando un agente necesite tu intervención o termine una tarea.",
|
||||
"enableNotifications": "Activar notificaciones",
|
||||
"loadFailed": "No se pudo cargar la configuración de notificaciones. Vuelve a intentarlo.",
|
||||
"notificationsBlocked": "Las notificaciones están desactivadas en los ajustes del sistema.",
|
||||
"openSettings": "Abrir Ajustes",
|
||||
"openSettingsFailed": "No se pudieron abrir los ajustes del sistema. Vuelve a intentarlo.",
|
||||
"saveFailed": "No se pudo guardar la configuración de notificaciones. Vuelve a intentarlo.",
|
||||
"title": "Notificaciones"
|
||||
},
|
||||
"pendingCredentialCleanupCard": {
|
||||
"cleanupUnconfirmed_many": "No se pudo confirmar la eliminación de {{count}} credenciales en este dispositivo.",
|
||||
"cleanupUnconfirmed_one": "No se pudo confirmar la eliminación de {{count}} credencial en este dispositivo.",
|
||||
"cleanupUnconfirmed_other": "No se pudo confirmar la eliminación de {{count}} credenciales en este dispositivo.",
|
||||
"retryCleanup": "Reintentar la eliminación de credenciales de emparejamiento",
|
||||
"retryFailed": "Aún no se ha podido confirmar la eliminación. Vuelve a intentarlo más tarde.",
|
||||
"statusCheckFailed": "No se pudo comprobar el estado de la eliminación en este dispositivo. Vuelve a intentarlo para asegurarte.",
|
||||
"title": "Eliminación de credenciales de emparejamiento"
|
||||
},
|
||||
"uiLanguageSettingsSection": {
|
||||
"pseudoLanguage": "Seudoidioma (en-XA)"
|
||||
},
|
||||
"voiceSettingsScreen": {
|
||||
"connectDesktop": "Conéctate a un escritorio para gestionar los ajustes de voz.",
|
||||
"deleteFailed": "Error al eliminar",
|
||||
"dictationDescription": "Dicta texto en cualquier panel que tenga el foco en tu escritorio.",
|
||||
"dictationHeading": "DICTADO",
|
||||
"dictationMode": "Modo de dictado",
|
||||
"dictationModeDescription": "Alternar: pulsa una vez para iniciar y otra para detener. Mantener pulsado: dicta sin soltar.",
|
||||
"downloadFailed": "Falló la descarga",
|
||||
"enableDictation": "Habilitar dictado de voz",
|
||||
"holdMode": "Mantener",
|
||||
"loadFailed": "No se pudo cargar la configuración de voz",
|
||||
"loadFailedMessage": "No se pudieron cargar los ajustes de voz.",
|
||||
"noneSelected": "Ninguno seleccionado",
|
||||
"selectModelFailed": "No se pudo seleccionar el modelo",
|
||||
"speechModel": "Modelo de voz",
|
||||
"speechModelHeading": "MODELO DE VOZ",
|
||||
"title": "Voz",
|
||||
"toggleMode": "Alternar",
|
||||
"updateFailed": "No se pudo actualizar"
|
||||
}
|
||||
},
|
||||
"webShell": {
|
||||
"shellStateScreen": {
|
||||
"checkingHost": "Comprobando el host",
|
||||
"connectToDownload": "Conéctate a este host para descargar la última versión de la aplicación",
|
||||
"documentLoadFailed": "No se pudo abrir la aplicación descargada.",
|
||||
"downloadFailed": "No se pudo descargar la última versión de la aplicación desde este host.",
|
||||
"hostProfileUnreadable": "No se pudo leer la información de este host en tu dispositivo. Inténtalo de nuevo.",
|
||||
"isolationUnavailable": "La versión de WebView de este dispositivo es demasiado antigua para abrir el espacio de trabajo de forma segura.",
|
||||
"openingWorkspace": "Abriendo espacio de trabajo",
|
||||
"renderProcessGone": "El espacio de trabajo dejó de responder.",
|
||||
"statusUnreadable": "No se pudo leer el estado de este host. Vuelve atrás y ábrelo de nuevo.",
|
||||
"tryAgain": "Reintentar"
|
||||
}
|
||||
},
|
||||
"worktree": {
|
||||
"agentRowDisplay": {
|
||||
"blocked": "Bloqueado",
|
||||
"days_many": "{{count}} d",
|
||||
"days_one": "{{count}} d",
|
||||
"days_other": "{{count}} d",
|
||||
"done": "Hecho",
|
||||
"failed": "Fallido",
|
||||
"hours_many": "{{count}} h",
|
||||
"hours_one": "{{count}} h",
|
||||
"hours_other": "{{count}} h",
|
||||
"idle": "Inactivo",
|
||||
"interrupted": "Interrumpido",
|
||||
"justNow": "ahora mismo",
|
||||
"minutes_many": "{{count}} min",
|
||||
"minutes_one": "{{count}} min",
|
||||
"minutes_other": "{{count}} min",
|
||||
"monitoring": "Supervisando tareas en segundo plano",
|
||||
"stopping": "Deteniendo…",
|
||||
"unconfirmed": "No se pudo confirmar",
|
||||
"unverifiable": "Sin actualizaciones recientes",
|
||||
"waiting": "Esperando entrada",
|
||||
"working": "Trabajando"
|
||||
},
|
||||
"hostWorkspaceListStates": {
|
||||
"checkHost": "Comprueba que tu equipo no esté en reposo y que Orca esté en ejecución.",
|
||||
"empty": "No hay espacios de trabajo",
|
||||
"loadFailed": "No se pudieron cargar los espacios de trabajo de este host",
|
||||
"noFilterMatches": "Ningún espacio de trabajo coincide con los filtros",
|
||||
"noMatches": "No hay espacios de trabajo coincidentes",
|
||||
"reopen": "Vuelve a abrir para actualizar",
|
||||
"reopenDetail": "Este host muestra los espacios de trabajo en un formato que esta página aún no reconoce.",
|
||||
"retrying": "worktree.ps falló ({{error}}) — reintentando automáticamente"
|
||||
},
|
||||
"workspaceListSections": {
|
||||
"all": "Todos",
|
||||
"closed": "Cerrado",
|
||||
"done": "Hecho",
|
||||
"inProgress": "En curso",
|
||||
"inReview": "En revisión",
|
||||
"pinned": "Fijados"
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,8 +1,607 @@
|
||||
{
|
||||
"accounts": {
|
||||
"screen": {
|
||||
"agentLogin": "Utiliser l'authentification de l'agent",
|
||||
"connecting": "Connexion à l'hôte…",
|
||||
"connectingTo": "Connexion à {{name}}…",
|
||||
"default": "Valeur par défaut du système",
|
||||
"hostNotFound": "Hôte introuvable",
|
||||
"invalidSnapshot": "État des comptes invalide reçu de l'hôte",
|
||||
"loadingAccounts": "Chargement des comptes…",
|
||||
"manageAccountsHint": "Ajoutez ou réauthentifiez des comptes dans Paramètres → Comptes sur l'ordinateur.",
|
||||
"refreshAccounts": "Actualiser les comptes",
|
||||
"sessionWindow": "5 h",
|
||||
"switchFailed": "Impossible de changer de compte",
|
||||
"title": "Comptes",
|
||||
"useReset": "Utiliser une réinitialisation",
|
||||
"weeklyWindow": "7 j"
|
||||
}
|
||||
},
|
||||
"agentHistory": {
|
||||
"panel": {
|
||||
"all": "Toutes",
|
||||
"emptyScope": "Aucune session d'agent passée dans ce périmètre.",
|
||||
"loadFailed": "Chargement impossible",
|
||||
"missingResumeId": "Cette session n'a pas d'identifiant de reprise.",
|
||||
"noSearchResults": "Aucune session ne correspond à votre recherche.",
|
||||
"noSessions": "Aucune session d'agent",
|
||||
"placeholder": "Rechercher des sessions, repo:, path:",
|
||||
"project": "Projet",
|
||||
"refreshSessions": "Actualiser les sessions d'agent",
|
||||
"resumeFailed": "Échec de la reprise de la session.",
|
||||
"sessionQueued": "Session d'agent ajoutée à la file d'attente.",
|
||||
"skipped_many": "{{count}} transcripts ignorés",
|
||||
"skipped_one": "{{count}} transcript ignoré",
|
||||
"skipped_other": "{{count}} transcripts ignorés",
|
||||
"title": "Historique des sessions d'agent",
|
||||
"unavailable": "Historique des sessions d'agent indisponible",
|
||||
"unknownHostPlatform": "Impossible de déterminer la plateforme de l'hôte.",
|
||||
"updateHost": "Mettez à jour Orca sur cet hôte pour consulter l'historique des sessions d'agent.",
|
||||
"waitingForHost": "En attente de l'hôte…",
|
||||
"workspace": "Espace de travail"
|
||||
},
|
||||
"resumeTarget": {
|
||||
"differentHost": "Cette session appartient à un autre hôte. Ouvrez un espace de travail sur le même hôte pour la reprendre.",
|
||||
"openWorkspace": "Ouvrez un espace de travail avant de reprendre une session.",
|
||||
"runtimeUnavailable": "La reprise depuis l'historique n'est pas disponible dans les espaces de travail hébergés dans un environnement d'exécution."
|
||||
},
|
||||
"sessionCard": {
|
||||
"untitledSession": "Session sans titre"
|
||||
},
|
||||
"sessionHistoryList": {
|
||||
"assistantRole": "assistant",
|
||||
"currentWorktree": "worktree actuel",
|
||||
"messages_many": "{{count}} messages",
|
||||
"messages_one": "{{count}} message",
|
||||
"messages_other": "{{count}} messages",
|
||||
"resumeSession": "Reprendre la session d'agent",
|
||||
"userRole": "utilisateur"
|
||||
},
|
||||
"useAgentHistoryState": {
|
||||
"hostUnreachable": "Impossible de joindre l'hôte",
|
||||
"loadFailed": "Impossible de charger les sessions d'agent",
|
||||
"waitingForHost": "En attente de l'hôte…"
|
||||
},
|
||||
"worktreeNavigationActions": {
|
||||
"agentSessionHistory": "Historique des sessions d'agent",
|
||||
"sourceControl": "Contrôle de code source"
|
||||
}
|
||||
},
|
||||
"app": {
|
||||
"mobileOnboarding": {
|
||||
"notificationSettingsFailed": "Impossible de mettre à jour les paramètres de notification. Réessayez.",
|
||||
"progress": "Progression de la prise en main",
|
||||
"saveChoiceFailed": "Impossible d'enregistrer votre choix. Réessayez.",
|
||||
"stepProgress": "Étape {{step}} sur {{total}}"
|
||||
},
|
||||
"notifications": {
|
||||
"description": "Recevez les alertes des agents même lorsque l'application est fermée. Elles passent par le service de notifications push d'Orca et par Apple ou Google."
|
||||
},
|
||||
"pair": {
|
||||
"backToHome": "Retour à l'accueil",
|
||||
"missingCode": "Code d'appairage manquant"
|
||||
},
|
||||
"pairConfirm": {
|
||||
"backToHome": "Retour à l'accueil",
|
||||
"connectTimedOut": "Impossible de se connecter en {{seconds}} s — consultez le journal ci-dessous pour identifier le blocage.",
|
||||
"connecting": "Connexion…",
|
||||
"description": "Vous avez ouvert un lien d'appairage depuis votre ordinateur. Confirmez pour l'ajouter à vos hôtes.",
|
||||
"pairButton": "Appairer",
|
||||
"pairingFailed": "Échec de l'appairage : {{error}}",
|
||||
"pairingLog": "Journal d'appairage",
|
||||
"title": "Appairer cet ordinateur ?"
|
||||
},
|
||||
"pairScan": {
|
||||
"cameraDisabledDescription": "Activez l'accès à la caméra dans les paramètres, ou collez le code d'appairage.",
|
||||
"cameraDisabledTitle": "Accès à la caméra désactivé",
|
||||
"cameraPermissionDescription": "Scannez le QR code affiché dans Orca sur votre ordinateur, ou collez le code d'appairage.",
|
||||
"connectTimedOut": "Impossible de se connecter en {{seconds}} s — consultez le journal ci-dessous pour identifier le blocage.",
|
||||
"connecting": "Connexion…",
|
||||
"invalidPairingCode": "Code d'appairage invalide — copiez-le depuis votre ordinateur et collez-le à nouveau.",
|
||||
"invalidQrCode": "QR code Orca invalide",
|
||||
"openDesktopStep": "Ouvrez Orca sur votre ordinateur",
|
||||
"openMobileSettingsStep": "Allez dans Paramètres → Mobile",
|
||||
"openSettings": "Ouvrir les paramètres",
|
||||
"pairingFailed": "Échec de l'appairage : {{error}}",
|
||||
"pairingLog": "Journal d'appairage",
|
||||
"pasteCodeMessage": "Copiez le code affiché sous le QR code sur votre ordinateur.",
|
||||
"pasteCodePlaceholder": "orca://pair?code=... ou collez le code",
|
||||
"pasteCodeTitle": "Coller le code d'appairage",
|
||||
"pasteInstead": "Coller plutôt le code",
|
||||
"pastePairingCode": "Ou coller le code d'appairage",
|
||||
"scanCodeStep": "Scannez le QR code",
|
||||
"title": "Appairer un ordinateur",
|
||||
"tryAgain": "Réessayer"
|
||||
},
|
||||
"terminalSettings": {
|
||||
"autocomplete": "Complétion et correction automatiques",
|
||||
"autocompleteOff": "Désactivé",
|
||||
"autocompleteOn": "Activé",
|
||||
"defaultText": "Par défaut (100 %)",
|
||||
"keepPhoneSize": "Conserver à la taille téléphone (par défaut)",
|
||||
"keyboardInputDescription": "La complétion, la correction automatique et les suggestions orthographiques s'appliquent uniquement à la saisie de commandes en mémoire tampon. Utilisez le bouton à double chevron au-dessus du clavier pour quitter la saisie en direct, puis modifiez votre commande avant d'appuyer sur Envoyer. Ces fonctions sont désactivées par défaut pour éviter de modifier les commandes, les options ou les chemins.",
|
||||
"keyboardInputHeading": "SAISIE AU CLAVIER",
|
||||
"largeText": "Grand (125 %)",
|
||||
"largerText": "Plus grand (150 %)",
|
||||
"largestText": "Le plus grand (200 %)",
|
||||
"leaveAppDescription": "Lorsque vous utilisez un terminal sur votre téléphone, Orca le réduit pour l'adapter à votre écran. Lorsque vous fermez l'application ou la mettez en arrière-plan, ce paramètre détermine s'il conserve la taille du téléphone (pour éviter que les outils CLI interactifs ne réorganisent leur affichage) ou reprend sa taille sur l'ordinateur. Vous pouvez à tout moment utiliser « Restaurer » dans la bannière sur l'ordinateur pour le redimensionner manuellement.",
|
||||
"leaveAppHeading": "LORSQUE VOUS QUITTEZ L'APPLICATION",
|
||||
"noPairedDesktops": "Aucun ordinateur appairé pour le moment. Appairez-en un pour régler le comportement du terminal.",
|
||||
"restoreAfterMinutes_many": "Après {{count}} minutes",
|
||||
"restoreAfterMinutes_one": "Après {{count}} minute",
|
||||
"restoreAfterMinutes_other": "Après {{count}} minutes",
|
||||
"restoreAfterSeconds_many": "Après {{count}} s",
|
||||
"restoreAfterSeconds_one": "Après {{count}} s",
|
||||
"restoreAfterSeconds_other": "Après {{count}} s",
|
||||
"restoreHost": "Restaurer {{name}}",
|
||||
"smallerText": "Plus petit (75 %)",
|
||||
"smallestText": "Le plus petit (50 %)",
|
||||
"textSize": "Taille du texte",
|
||||
"textSizeDescription": "Réglez la taille du texte du terminal. Une taille plus petite permet d'afficher plus de colonnes avec des marges latérales ; une taille plus grande en affiche moins — faites glisser horizontalement pour faire défiler. Vous pouvez aussi pincer pour zoomer directement dans le terminal, ce qui met à jour ce paramètre. Ce réglage ne concerne que l'affichage sur cet appareil ; il ne modifie pas le terminal sur l'ordinateur.",
|
||||
"textSizeHeading": "TAILLE DU TEXTE",
|
||||
"textSizePickerTitle": "Taille du texte du terminal",
|
||||
"title": "Terminal"
|
||||
}
|
||||
},
|
||||
"common": {
|
||||
"back": "Retour"
|
||||
"back": "Retour",
|
||||
"cancel": "Annuler",
|
||||
"continue": "Continuer",
|
||||
"copied": "Copié",
|
||||
"delete": "Supprimer",
|
||||
"ok": "OK",
|
||||
"remove": "Supprimer",
|
||||
"retry": "Réessayer",
|
||||
"save": "Enregistrer",
|
||||
"settings": "Paramètres",
|
||||
"unknown": "Inconnu"
|
||||
},
|
||||
"connection": {
|
||||
"connectionHealth": {
|
||||
"checkConnection": "Vérifiez votre connexion",
|
||||
"connectFailed": "Connexion impossible",
|
||||
"connected": "Connecté",
|
||||
"connecting": "Connexion…",
|
||||
"connectingViaRelay": "Connexion via Relay…",
|
||||
"desktopUnreachable": "Impossible de joindre l'ordinateur",
|
||||
"disconnected": "Déconnecté",
|
||||
"hostFallback": "Hôte",
|
||||
"hostOffline": "{{host}} est hors ligne",
|
||||
"hostOfflineDetail": "Vérifiez que l'ordinateur n'est pas en veille, qu'Orca est en cours d'exécution et que vous êtes connecté",
|
||||
"labelWithHint": "{{label}} — {{hint}}",
|
||||
"pairingInvalid": "Appairage invalide — refaites l'appairage avec votre ordinateur",
|
||||
"rePairDetail": "Refaites l'appairage avec votre ordinateur",
|
||||
"reconnecting": "Reconnexion…",
|
||||
"relayAccessExpired": "L'accès à Relay a expiré pour {{host}}",
|
||||
"relayConnectFailed": "Connexion impossible via Relay",
|
||||
"relayUnreachable": "Impossible de joindre Relay",
|
||||
"signInDetail": "Connectez-vous à Orca sur votre ordinateur pour rétablir la connexion",
|
||||
"signInRequired": "Connexion requise sur {{host}}",
|
||||
"tailscaleHint": "vérifiez Tailscale"
|
||||
},
|
||||
"connectionPathLabel": {
|
||||
"directLan": "Directe · LAN",
|
||||
"directTailscale": "Directe · Tailscale",
|
||||
"directViaLan": "Directe via LAN",
|
||||
"directViaTailscale": "Directe via Tailscale"
|
||||
},
|
||||
"hostEndpoint": {
|
||||
"addressRequired": "Saisissez une adresse d'hôte.",
|
||||
"hostnameRequired": "Nom d'hôte manquant.",
|
||||
"invalidAddress": "Adresse invalide.",
|
||||
"invalidHostname": "Nom d'hôte invalide.",
|
||||
"invalidPort": "Le port doit être compris entre 1 et 65535.",
|
||||
"invalidScheme": "Utilisez ws:// ou wss:// (ou host:port).",
|
||||
"pathOrQueryNotAllowed": "L'adresse de l'hôte ne doit contenir ni chemin ni chaîne de requête."
|
||||
},
|
||||
"pairConfirmState": {
|
||||
"invalidCode": "Code d'appairage invalide",
|
||||
"missingCode": "Code d'appairage manquant"
|
||||
},
|
||||
"rpcClientStreamRegistry": {
|
||||
"endedBeforeReady": "La requête de flux s'est terminée avant d'être prête.",
|
||||
"interrupted": "Connexion interrompue"
|
||||
},
|
||||
"rpcClientTerminalBinaryFrame": {
|
||||
"unsupportedFrame": "Trame de flux du terminal non prise en charge (opcode {{opcode}})."
|
||||
}
|
||||
},
|
||||
"diagnostics": {
|
||||
"connectionDiagnosticsView": {
|
||||
"authFailed": "échec de l'authentification",
|
||||
"connected": "connecté",
|
||||
"connecting": "connexion en cours",
|
||||
"copyReport": "Copier le rapport",
|
||||
"diagnosisHeading": "Cause probable",
|
||||
"disconnected": "déconnecté",
|
||||
"handshaking": "négociation en cours",
|
||||
"noEvents": "Aucun événement de connexion pour le moment. Les événements s'affichent lorsque l'application tente de se connecter à cet hôte.",
|
||||
"noHosts": "Aucun hôte appairé.",
|
||||
"privacyHint": "Envoie un rapport expurgé de taille limitée contenant le nom de l'hôte, le point de terminaison, les versions, l'état de la connexion et les événements — jamais le contenu des terminaux ni les identifiants de connexion.",
|
||||
"reconnecting": "reconnexion en cours",
|
||||
"retrySending": "Réessayer l'envoi",
|
||||
"sendDiagnostics": "Envoyer les diagnostics à Orca",
|
||||
"sending": "Envoi…",
|
||||
"sent": "Diagnostics envoyés",
|
||||
"stateWithAttempt": "{{state}} · tentative {{attempts}}",
|
||||
"title": "Diagnostics réseau"
|
||||
},
|
||||
"mobileWebBundleProbeRow": {
|
||||
"assets": "Ressources",
|
||||
"build": "Version",
|
||||
"bundle": "Paquet",
|
||||
"bytes": "Octets",
|
||||
"connecting": "Connexion…",
|
||||
"elapsed": "Temps écoulé",
|
||||
"elapsedMilliseconds": "{{elapsedMs}} ms",
|
||||
"fetchBundle": "Récupérer le paquet web mobile",
|
||||
"fetching": "Récupération du paquet…",
|
||||
"host": "Hôte",
|
||||
"unknownHost": "hôte inconnu"
|
||||
},
|
||||
"mobileWebShellUpdateFailureRow": {
|
||||
"title": "Mises à jour des espaces de travail"
|
||||
},
|
||||
"troubleshootCommonIssues": {
|
||||
"allowNotifications": "Vérifiez que les paramètres système autorisent les notifications Orca et que le mode Concentration ou Ne pas déranger est désactivé.",
|
||||
"checkTailnet": "Vérifiez que l'ordinateur n'est pas en veille et apparaît connecté à votre tailnet.",
|
||||
"desktopTitle": "Application de bureau arrêtée",
|
||||
"disableVpn": "Désactivez ce VPN ou activez le routage fractionné / « Autoriser le LAN ».",
|
||||
"firewallTitle": "Pare-feu bloquant le port 6768",
|
||||
"keepTailscaleOn": "Les adresses d'hôte comme 100.x.x.x ou *.ts.net passent par Tailscale — laissez-le ACTIVÉ.",
|
||||
"macosFirewall": "macOS : Réglages Système → Réseau → Pare-feu — autorisez Orca.",
|
||||
"notificationNetwork": "Essayez les données mobiles ou un autre réseau Wi-Fi. Si les alertes arrivent après ce changement, votre réseau retarde peut-être leur réception.",
|
||||
"notificationsTitle": "Notifications push",
|
||||
"openDesktop": "Orca doit être ouvert sur votre ordinateur pour accepter les connexions.",
|
||||
"pairAfterUpdate": "Après une mise à jour, vous devrez peut-être refaire l'appairage via un QR code.",
|
||||
"reconnectWifi": "Essayez de reconnecter les deux appareils au WiFi.",
|
||||
"restartBothApps": "Redémarrez les deux applications si les dépassements de délai persistent.",
|
||||
"restartDesktop": "Essayez de redémarrer Orca — le serveur compagnon démarre au lancement.",
|
||||
"restartTailscale": "Sous iOS/Android, le tunnel peut se bloquer sans avertissement : désactivez puis réactivez Tailscale dans l'application Tailscale.",
|
||||
"restrictedNetwork": "Les réseaux d'entreprise ou d'établissement scolaire peuvent bloquer le P2P — essayez un point d'accès personnel.",
|
||||
"retryHost": "Revenez à la liste des hôtes et touchez votre hôte pour réessayer.",
|
||||
"sameNetwork": "Les deux appareils doivent être sur le même LAN (sauf s'ils sont connectés via Tailscale).",
|
||||
"sameSubnet": "Ethernet et le WiFi doivent utiliser le même sous-réseau.",
|
||||
"tailscaleTitle": "Hôte Tailscale inaccessible",
|
||||
"timeoutTitle": "Délai de connexion dépassé",
|
||||
"updateTailscale": "Mettez à jour l'application Tailscale — les versions récentes corrigent des bugs de reconnexion.",
|
||||
"vpnRouting": "Les VPN autres que Tailscale peuvent acheminer le trafic local via un serveur distant.",
|
||||
"vpnTitle": "Interférences d'autres VPN",
|
||||
"wifiSignal": "Vérifiez la puissance du signal WiFi sur votre téléphone.",
|
||||
"wifiTitle": "Réseaux WiFi différents",
|
||||
"windowsFirewall": "Windows : Pare-feu Windows Defender → Autoriser une application — autorisez Orca sur les réseaux privés."
|
||||
},
|
||||
"troubleshootView": {
|
||||
"commonIssues": "Problèmes courants",
|
||||
"runAgain": "Relancer",
|
||||
"runDiagnostics": "Lancer les diagnostics",
|
||||
"running": "En cours…",
|
||||
"title": "Dépannage",
|
||||
"viewNetworkDiagnostics": "Voir les diagnostics réseau"
|
||||
},
|
||||
"useTroubleshootDiagnostics": {
|
||||
"connected": "Connecté",
|
||||
"hostDataFailed": "Impossible de lire les données des hôtes",
|
||||
"hostTestFailed": "Test impossible",
|
||||
"hosts": "Hôtes",
|
||||
"internet": "Internet",
|
||||
"noConnection": "Aucune connexion",
|
||||
"noPairedHosts": "Aucun — scannez un QR code pour appairer",
|
||||
"pairedCount": "Appairages : {{count}}",
|
||||
"pairedHosts": "Hôtes appairés",
|
||||
"platform": "Plateforme",
|
||||
"reachable": "Accessible à l'adresse {{endpoint}}",
|
||||
"unexpectedResponse": "Réponse inattendue"
|
||||
},
|
||||
"webShellUpdateFailureCopy": {
|
||||
"failedAgo": "La dernière mise à jour provenant de {{hostName}} a échoué il y a {{time}} : {{reason}}{{offered}}. {{outcome}}",
|
||||
"failedJustNow": "La dernière mise à jour provenant de {{hostName}} vient d'échouer : {{reason}}{{offered}}. {{outcome}}"
|
||||
}
|
||||
},
|
||||
"dictation": {
|
||||
"setup": {
|
||||
"alreadyActive": "La dictée est déjà utilisée sur l'ordinateur ou un autre téléphone.",
|
||||
"cloudModelNeedsKey": "Nécessite une clé OpenAI",
|
||||
"deleteFailed": "Impossible de supprimer le modèle",
|
||||
"desktopUpdateRequired": "Mettez à jour l'application Orca sur l'ordinateur appairé pour utiliser les paramètres vocaux du mobile.",
|
||||
"downloadFailed": "Impossible de démarrer le téléchargement",
|
||||
"loadFailed": "Impossible de charger les modèles de dictée",
|
||||
"repairRequired": "Appairez à nouveau ce téléphone avec l'ordinateur pour utiliser la dictée.",
|
||||
"serverUpdateRequired": "Mettez à jour Orca sur ce serveur pour utiliser la dictée vocale.",
|
||||
"sessionLost": "La dictée a perdu sa connexion à l'ordinateur. Réessayez.",
|
||||
"updateFailed": "Impossible de mettre à jour les paramètres de dictée"
|
||||
}
|
||||
},
|
||||
"home": {
|
||||
"accountUsageCards": {
|
||||
"sectionHeading": "Utilisation du compte",
|
||||
"sessionWindow": "5 h",
|
||||
"systemDefault": "Valeur par défaut du système",
|
||||
"weeklyWindow": "7 j"
|
||||
},
|
||||
"appUpdateCard": {
|
||||
"dismissAccessibilityLabel": "Ignorer la mise à jour Orca {{version}}",
|
||||
"title": "Orca {{version}} est disponible",
|
||||
"updateAccessibilityLabel": "Orca {{version}} est disponible, touchez pour mettre à jour",
|
||||
"updateHint": "Touchez pour mettre à jour"
|
||||
},
|
||||
"emptyState": {
|
||||
"connectedDescription": "Votre ordinateur apparaîtra ici. Tout est chiffré de bout en bout.",
|
||||
"connectedTitle": "Vous êtes connecté",
|
||||
"description": "Appairez votre téléphone à Orca sur votre ordinateur pour suivre vos agents, accéder à n'importe quel terminal et piloter votre travail depuis votre téléphone.",
|
||||
"openDesktopDescription": "Allez dans Paramètres → Mobile et générez un QR code d'appairage.",
|
||||
"openDesktopTitle": "Ouvrir Orca sur l'ordinateur",
|
||||
"pairDesktop": "Appairer l'ordinateur",
|
||||
"scanCodeDescription": "Touchez le bouton ci-dessus pour ouvrir le scanner. Visez le QR code affiché à l'écran.",
|
||||
"scanCodeTitle": "Scanner le code",
|
||||
"stepsHeading": "Fonctionnement",
|
||||
"title": "Connectez votre ordinateur"
|
||||
},
|
||||
"listFooter": {
|
||||
"hostPickerTitle": "Ouvrir les tâches sur",
|
||||
"resumeHeading": "Reprendre",
|
||||
"tasksHeading": "Tâches"
|
||||
},
|
||||
"listHeader": {
|
||||
"agentTime": "Temps agent",
|
||||
"agentsSpawned": "Agents lancés",
|
||||
"daysHours": "{{days}} j {{hours}} h",
|
||||
"desktopsHeading": "Ordinateurs",
|
||||
"hoursMinutes": "{{hours}} h {{minutes}} min",
|
||||
"minutes": "{{minutes}} min",
|
||||
"prsCreated": "PRs créées",
|
||||
"title": "Bon retour"
|
||||
},
|
||||
"screen": {
|
||||
"pairingCheckFailed": "Impossible de vérifier l'appairage",
|
||||
"removeHostFailed": "Impossible de supprimer l'hôte",
|
||||
"removeHostMessage": "Supprimer « {{name}} » ? Vous pourrez l'appairer à nouveau plus tard.",
|
||||
"removeHostTitle": "Supprimer l'hôte",
|
||||
"tryAgain": "Veuillez réessayer."
|
||||
},
|
||||
"tasksCard": {
|
||||
"noSources": "Aucune source de tâches connectée",
|
||||
"openProviderTasks": "Ouvrir les tâches de {{provider}}",
|
||||
"title": "Tâches"
|
||||
}
|
||||
},
|
||||
"hooks": {
|
||||
"dictationDesktopStart": {
|
||||
"recordingFailed": "Impossible de démarrer l'enregistrement au microphone"
|
||||
},
|
||||
"useMobileDictation": {
|
||||
"inputClosed": "La dictée vocale s'est arrêtée car cette session n'accepte plus de saisie. Réessayez.",
|
||||
"microphoneInitFailed": "Impossible d'initialiser le microphone",
|
||||
"microphoneInterrupted": "La dictée vocale s'est arrêtée car l'accès au microphone a été interrompu. Réessayez.",
|
||||
"microphonePermissionDenied": "Accès au microphone refusé",
|
||||
"noSpeech": "Aucune parole détectée."
|
||||
}
|
||||
},
|
||||
"host": {
|
||||
"edit": {
|
||||
"address": "Adresse",
|
||||
"addressHint": "Accepte une adresse IP, host:port ou ws:// / wss://. Une URL wss:// sans port utilise le port 443. Une adresse seule conserve le port actuel (ou 6768).",
|
||||
"connectionPreview": "Connexion à {{endpoint}}",
|
||||
"goBack": "Précédent",
|
||||
"help": "Modifiez le nom affiché ou l'adresse de connexion. Laissez le nom vide pour utiliser celui indiqué par l'ordinateur. Modifier l'adresse change uniquement l'adresse à laquelle ce téléphone se connecte, sans nouvel appairage. Utilisez cette option si le même ordinateur est accessible à une autre adresse IP (par exemple, via le LAN domestique ou Tailscale).",
|
||||
"hostNamePlaceholder": "Nom de l'hôte",
|
||||
"hostRemoved": "Cet hôte a été supprimé de ce téléphone.",
|
||||
"loadFailed": "Impossible de charger l'hôte.",
|
||||
"missingHost": "Hôte manquant.",
|
||||
"name": "Nom",
|
||||
"saveFailed": "Impossible d'enregistrer l'hôte.",
|
||||
"saveHost": "Enregistrer l'hôte",
|
||||
"title": "Modifier l'hôte"
|
||||
}
|
||||
},
|
||||
"hostScreen": {
|
||||
"hostListActionSheetActions": {
|
||||
"connect": "Se connecter",
|
||||
"disconnect": "Déconnecter",
|
||||
"editHost": "Modifier l'hôte",
|
||||
"networkDiagnostics": "Diagnostic réseau",
|
||||
"reconnect": "Reconnecter"
|
||||
},
|
||||
"hostPendingCreationRow": {
|
||||
"creatingWorkspace": "Création de l'espace de travail…"
|
||||
},
|
||||
"hostRailActions": {
|
||||
"accounts": "Comptes",
|
||||
"closeSearch": "Fermer la recherche",
|
||||
"floatingWorkspace": "Espace de travail flottant",
|
||||
"newWorkspace": "Nouvel espace de travail",
|
||||
"searchWorkspaces": "Rechercher des espaces de travail",
|
||||
"tasks": "Tâches"
|
||||
},
|
||||
"hostScreenHeader": {
|
||||
"accounts": "Comptes",
|
||||
"activeFilters_many": "Options des espaces de travail ({{count}} filtres actifs)",
|
||||
"activeFilters_one": "Options des espaces de travail ({{count}} filtre actif)",
|
||||
"activeFilters_other": "Options des espaces de travail ({{count}} filtres actifs)",
|
||||
"backToHosts": "Retour aux hôtes",
|
||||
"closeSearch": "Fermer la recherche",
|
||||
"floatingWorkspace": "Espace de travail flottant",
|
||||
"hideSidebar": "Masquer la barre latérale",
|
||||
"reconnect": "Reconnecter",
|
||||
"searchWorkspaces": "Rechercher des espaces de travail",
|
||||
"tasks": "Tâches",
|
||||
"workspaceOptions": "Options des espaces de travail"
|
||||
},
|
||||
"hostScreenOverlays": {
|
||||
"deleteConfirmMessage": "Supprimer « {{name}} » ({{branch}}) ?",
|
||||
"deleteWorktree": "Supprimer le worktree",
|
||||
"pin": "Épingler",
|
||||
"primaryWorktreeDeleteHint": "Worktree principal — impossible à supprimer. Retirez plutôt le projet.",
|
||||
"removeHost": "Supprimer l'hôte",
|
||||
"removeHostMessage": "Supprimer « {{name}} » ? Vous pourrez l'appairer à nouveau plus tard.",
|
||||
"sleep": "Mettre en veille",
|
||||
"unpin": "Désépingler"
|
||||
},
|
||||
"hostWorkspaceList": {
|
||||
"searchPlaceholder": "Rechercher des espaces de travail…",
|
||||
"searchWorkspaces": "Rechercher des espaces de travail"
|
||||
},
|
||||
"workspaceDetailsSheet": {
|
||||
"checksFailing": "Vérifications : en échec",
|
||||
"checksPassing": "Vérifications : réussies",
|
||||
"checksPending": "Vérifications : en attente",
|
||||
"closed": "Fermée",
|
||||
"createdByAutomation": "Créé par automatisation",
|
||||
"createdByNamedAutomation": "Créé par l'automatisation « {{name}} »",
|
||||
"createdFromCli": "Créé via Orca CLI",
|
||||
"draft": "Brouillon",
|
||||
"merged": "Fusionnée",
|
||||
"openLink": "Ouvrir {{label}}",
|
||||
"openLinkWithTitle": "Ouvrir {{label}}, {{title}}",
|
||||
"prOpen": "Ouverte"
|
||||
},
|
||||
"workspaceOptionControls": {
|
||||
"agentActivity": "Activité des agents",
|
||||
"agentActivityLayout": "Disposition de l'activité des agents",
|
||||
"agentActivitySort": "Activité des agents",
|
||||
"agentActivitySortDescription": "Agents nécessitant une attention, puis activité la plus récente.",
|
||||
"agentStatuses": "Statuts des agents",
|
||||
"allHosts": "Tous les hôtes",
|
||||
"allProjects": "Tous les projets",
|
||||
"automation": "Automatisation",
|
||||
"branchFolderPath": "Branche / chemin de dossier",
|
||||
"branchName": "Nom de branche",
|
||||
"compact": "Compact",
|
||||
"detailed": "Détaillé",
|
||||
"fullList": "Liste complète",
|
||||
"githubIssues": "Issues GitHub",
|
||||
"githubTicket": "Ticket GitHub",
|
||||
"host": "Hôte",
|
||||
"hover": "Survol",
|
||||
"jiraIssue": "Ticket Jira",
|
||||
"jiraIssues": "Tickets Jira",
|
||||
"linearIssue": "Ticket Linear",
|
||||
"linearIssues": "Issues Linear",
|
||||
"manual": "Manuel",
|
||||
"manualProjectOrderDescription": "Glissez les projets pour les ranger",
|
||||
"manualWorkspaceOrderDescription": "Glissez les espaces de travail pour les ranger au sein de chaque groupe.",
|
||||
"name": "Nom",
|
||||
"none": "Aucun",
|
||||
"notes": "Notes",
|
||||
"ports": "Ports",
|
||||
"pr": "PR",
|
||||
"prMrLink": "Lien PR/MR",
|
||||
"project": "Projet",
|
||||
"recent": "Récents",
|
||||
"recentProjectOrderDescription": "Activité d'espace de travail la plus récente",
|
||||
"searchProjects": "Rechercher des projets",
|
||||
"searchProjectsPlaceholder": "Rechercher des projets…",
|
||||
"selectedCount": "{{count}} sur {{total}}",
|
||||
"status": "Statut"
|
||||
},
|
||||
"workspaceOptionsSheet": {
|
||||
"backToOptions": "Retour aux options des espaces de travail",
|
||||
"cardDisplay": "Affichage des cartes",
|
||||
"cardLayout": "Disposition en cartes",
|
||||
"clearFilters": "Réinitialiser les filtres",
|
||||
"exceptDefaultBranch": "Sauf la branche par défaut",
|
||||
"filters": "Filtres",
|
||||
"groupBy": "Grouper par",
|
||||
"hideAutomationCreated": "Masquer ceux créés par l'automatisation",
|
||||
"hideCliCreated": "Masquer ceux créés par le CLI",
|
||||
"hideDefaultBranch": "Masquer la branche par défaut",
|
||||
"hideDetachedHead": "Masquer les HEAD détachés",
|
||||
"hideOtherClientWorkspaces": "Masquer les espaces de travail d'autres clients",
|
||||
"hideSleeping": "Masquer les espaces en veille",
|
||||
"hosts": "Hôtes",
|
||||
"localLinux": "Linux local",
|
||||
"localMac": "Mac local",
|
||||
"localWindows": "Windows local",
|
||||
"projectOrder": "Ordre des projets",
|
||||
"projects": "Projets",
|
||||
"savingToDesktop": "Enregistrement sur l'ordinateur",
|
||||
"show": "Afficher",
|
||||
"showProperties": "Afficher les propriétés",
|
||||
"sortBy": "Trier par",
|
||||
"syncedAcrossDevices": "Synchronisées sur vos appareils",
|
||||
"thisComputer": "Cet ordinateur",
|
||||
"waitingForDesktop": "Toujours en attente de l'ordinateur",
|
||||
"workspaceOptions": "Options des espaces de travail"
|
||||
}
|
||||
},
|
||||
"navigation": {
|
||||
"hostStackScreens": {
|
||||
"accounts": "Comptes",
|
||||
"agentSessionHistory": "Historique des sessions d'agent",
|
||||
"changes": "Modifications",
|
||||
"editHost": "Modifier l'hôte",
|
||||
"host": "Hôte",
|
||||
"pullRequest": "Pull Request",
|
||||
"sourceControl": "Contrôle de code source",
|
||||
"tasks": "Tâches",
|
||||
"terminal": "Terminal",
|
||||
"workspace": "Espace de travail"
|
||||
}
|
||||
},
|
||||
"notifications": {
|
||||
"notificationDeliverySection": {
|
||||
"footer": "Les types d'alertes suivent les paramètres de notification de chaque ordinateur appairé. Les notifications sont suspendues après 7 jours sans utilisation de cette application ; ouvrez-la et reconnectez-vous pour les réactiver.",
|
||||
"onlyWhenAway": "Uniquement si vous n'êtes pas à l'ordinateur",
|
||||
"onlyWhenAwayHint": "Après 3 minutes sans activité au clavier ou à la souris, ou si l'ordinateur est verrouillé.",
|
||||
"sound": "Son des notifications",
|
||||
"suppressWhileFocused": "Suspendre pendant la consultation",
|
||||
"suppressWhileFocusedHint": "Ignorer les alertes de l'espace de travail ouvert sur ce téléphone."
|
||||
}
|
||||
},
|
||||
"onboarding": {
|
||||
"notificationOnboardingPreview": {
|
||||
"claudeNeedsInput": "Claude attend votre intervention",
|
||||
"codexFinished": "Codex a terminé",
|
||||
"now": "maintenant",
|
||||
"testsPassing": "Les tests passent.",
|
||||
"waitingOnYou": "En attente de votre intervention."
|
||||
},
|
||||
"onboardingPage": {
|
||||
"enableAgentNotifications": "Activer les notifications des agents",
|
||||
"enableNotifications": "Activer les notifications",
|
||||
"keepTerminal": "Conserver le terminal",
|
||||
"notNow": "Pas maintenant",
|
||||
"notificationsDescription": "Recevez une notification sur ce téléphone lorsqu'un agent a terminé ou est en attente, même si vous n'utilisez pas l'application.",
|
||||
"notificationsDisclosure": "Envoyées via le service de notifications push d'Orca après 3 minutes d'inactivité de votre ordinateur. Modifiez ce réglage à tout moment dans les paramètres.",
|
||||
"notificationsTitle": "Soyez averti quand un agent a besoin de vous",
|
||||
"openInChatUi": "Ouvrir les sessions dans l'interface de chat",
|
||||
"openInTerminal": "Ouvrir les sessions dans le terminal",
|
||||
"sessionViewDescription": "Choisissez si les sessions d'agent compatibles s'ouvrent dans le terminal ou l'interface de chat sur cet appareil. Appuyez longuement sur l'onglet d'une session pour changer de vue, ou modifiez le choix par défaut plus tard dans les paramètres.",
|
||||
"sessionViewTitle": "Comment ouvrir les sessions ?",
|
||||
"skipNotifications": "Ignorer les notifications pour le moment",
|
||||
"useChatUi": "Utiliser le chat"
|
||||
}
|
||||
},
|
||||
"settings": {
|
||||
"aboutScreen": {
|
||||
"openLinkFailed": "Impossible d'ouvrir le lien. Réessayez.",
|
||||
"socialProfile": "Orca sur X",
|
||||
"sourceCode": "Code source d'Orca",
|
||||
"tagline": "IDE open source avec agents pour les développeurs 100x",
|
||||
"title": "À propos",
|
||||
"website": "Site web d'Orca"
|
||||
},
|
||||
"appUpdateSettingsRows": {
|
||||
"checkFailed": "Échec de la vérification",
|
||||
"checkUpdates": "Rechercher des mises à jour",
|
||||
"checkUpdatesStatus": "Rechercher des mises à jour, {{status}}",
|
||||
"checking": "Vérification…",
|
||||
"installedVersion": "Version {{version}}",
|
||||
"lastCheckedDays_many": "Dernière vérification il y a {{count}} j",
|
||||
"lastCheckedDays_one": "Dernière vérification il y a {{count}} j",
|
||||
"lastCheckedDays_other": "Dernière vérification il y a {{count}} j",
|
||||
"lastCheckedHours_many": "Dernière vérification il y a {{count}} h",
|
||||
"lastCheckedHours_one": "Dernière vérification il y a {{count}} h",
|
||||
"lastCheckedHours_other": "Dernière vérification il y a {{count}} h",
|
||||
"lastCheckedJustNow": "Dernière vérification à l'instant",
|
||||
"lastCheckedMinutes_many": "Dernière vérification il y a {{count}} min",
|
||||
"lastCheckedMinutes_one": "Dernière vérification il y a {{count}} min",
|
||||
"lastCheckedMinutes_other": "Dernière vérification il y a {{count}} min",
|
||||
"neverChecked": "Jamais",
|
||||
"upToDate": "À jour",
|
||||
"update": "Mettre à jour",
|
||||
"updateToVersion": "Mettre à jour vers Orca {{version}}"
|
||||
},
|
||||
"appearance": {
|
||||
"language": {
|
||||
"description": "L'option Système utilise la langue de cet appareil. Les demandes d'autorisation utilisent toujours la langue de l'appareil.",
|
||||
@@ -17,6 +616,161 @@
|
||||
"system": "Système"
|
||||
},
|
||||
"title": "Apparence"
|
||||
},
|
||||
"browserSettingsScreen": {
|
||||
"desktopBrowser": "Navigateur Orca sur l'ordinateur",
|
||||
"desktopBrowserDescription": "Ouvrir dans le navigateur diffusé depuis votre ordinateur appairé.",
|
||||
"linksDescription": "Choisissez où ouvrir les liens HTTP(S) que vous touchez dans la sortie du terminal.",
|
||||
"linksHeading": "LIENS",
|
||||
"loadFailed": "Impossible de charger les préférences du navigateur. Réessayez.",
|
||||
"openTerminalLinks": "Ouvrir les liens du terminal",
|
||||
"phoneBrowser": "Navigateur du téléphone",
|
||||
"phoneBrowserDescription": "Ouvrir dans Safari, Chrome ou un autre navigateur sur ce téléphone.",
|
||||
"saveFailed": "Impossible d'enregistrer les préférences du navigateur. Réessayez.",
|
||||
"title": "Navigateur"
|
||||
},
|
||||
"menuItems": {
|
||||
"about": "À propos",
|
||||
"appearance": "Apparence",
|
||||
"browser": "Navigateur",
|
||||
"chatUi": "Chat UI",
|
||||
"notifications": "Notifications",
|
||||
"terminal": "Terminal",
|
||||
"troubleshooting": "Dépannage",
|
||||
"voice": "Voix"
|
||||
},
|
||||
"menuScreen": {
|
||||
"privacyPolicy": "Politique de confidentialité",
|
||||
"support": "Assistance"
|
||||
},
|
||||
"nativeChatSettingsScreen": {
|
||||
"defaultViewDescription": "Choisissez comment ouvrir sur cet appareil les sessions des agents compatibles (Claude, Codex et les autres agents prenant en charge le chat). Le mode « Terminal » affiche la CLI brute ; Chat UI affiche une interface de chat similaire à celle de l'application pour ordinateur. Vous pouvez toujours changer l'affichage de chaque session via son menu accessible par un appui long.",
|
||||
"defaultViewHeading": "VUE PAR DÉFAUT",
|
||||
"off": "Désactivé",
|
||||
"on": "Activé",
|
||||
"openInChatUi": "Ouvrir les sessions dans Chat UI",
|
||||
"title": "Chat UI"
|
||||
},
|
||||
"nativeNotificationDeliverySettings": {
|
||||
"loadFailed": "Impossible de charger les paramètres de réception. Rouvrez cet écran pour réessayer.",
|
||||
"saveFailed": "Impossible d'enregistrer les paramètres de réception. Réessayez.",
|
||||
"updateDesktopHint": "Appairez un ordinateur sur lequel Orca est à jour pour recevoir des notifications sur ce téléphone."
|
||||
},
|
||||
"notificationDisplayTest": {
|
||||
"accepted": "Test accepté par le service de notifications push d'Orca. Vérifiez si vous avez reçu la notification.",
|
||||
"connectDesktop": "Connectez-vous à un ordinateur, puis réessayez.",
|
||||
"description": "Envoyez un test via le service de notifications push d'Orca.",
|
||||
"desktopUnreachable": "Impossible de joindre l'ordinateur. Réessayez.",
|
||||
"loadFailed": "Impossible de charger les ordinateurs appairés.",
|
||||
"pairDesktop": "Appairez un ordinateur, puis réessayez.",
|
||||
"pushServiceFailed": "Impossible d'envoyer le test via le service de notifications push d'Orca. Réessayez.",
|
||||
"rateLimited": "Trop de notifications. Réessayez plus tard.",
|
||||
"reconnectPhone": "Reconnectez-vous pour enregistrer ce téléphone auprès du service de notifications.",
|
||||
"sendButton": "Envoyer une notification de test",
|
||||
"sendFailed": "Impossible d'envoyer la notification push de test.",
|
||||
"sending": "Envoi…",
|
||||
"troubleReceivingAlerts": "Vous avez du mal à recevoir les alertes ?",
|
||||
"troubleshooting": "Dépannage",
|
||||
"updateDesktop": "Mettez Orca à jour sur votre ordinateur pour lancer ce test."
|
||||
},
|
||||
"notificationSettingsScreen": {
|
||||
"description": "Recevez une notification sur cet appareil lorsqu'un agent a besoin de votre intervention ou termine une tâche.",
|
||||
"enableNotifications": "Activer les notifications",
|
||||
"loadFailed": "Impossible de charger les paramètres de notification. Réessayez.",
|
||||
"notificationsBlocked": "Les notifications sont désactivées dans les paramètres système.",
|
||||
"openSettings": "Ouvrir les paramètres",
|
||||
"openSettingsFailed": "Impossible d'ouvrir les paramètres système. Réessayez.",
|
||||
"saveFailed": "Impossible d'enregistrer les paramètres de notification. Réessayez.",
|
||||
"title": "Notifications"
|
||||
},
|
||||
"pendingCredentialCleanupCard": {
|
||||
"cleanupUnconfirmed_many": "Impossible de confirmer la suppression de {{count}} identifiants sur cet appareil.",
|
||||
"cleanupUnconfirmed_one": "Impossible de confirmer la suppression de {{count}} identifiant sur cet appareil.",
|
||||
"cleanupUnconfirmed_other": "Impossible de confirmer la suppression de {{count}} identifiants sur cet appareil.",
|
||||
"retryCleanup": "Réessayer de supprimer les identifiants d'appairage",
|
||||
"retryFailed": "La suppression n'a toujours pas pu être confirmée. Réessayez plus tard.",
|
||||
"statusCheckFailed": "Impossible de vérifier l'état de la suppression sur cet appareil. Réessayez par précaution.",
|
||||
"title": "Suppression des identifiants d'appairage"
|
||||
},
|
||||
"uiLanguageSettingsSection": {
|
||||
"pseudoLanguage": "Pseudo-langue (en-XA)"
|
||||
},
|
||||
"voiceSettingsScreen": {
|
||||
"connectDesktop": "Connectez-vous à un ordinateur pour gérer les paramètres vocaux.",
|
||||
"deleteFailed": "Échec de la suppression",
|
||||
"dictationDescription": "Dictez du texte dans n'importe quel volet actif de votre ordinateur.",
|
||||
"dictationHeading": "DICTÉE",
|
||||
"dictationMode": "Mode de dictée",
|
||||
"dictationModeDescription": "Bascule : appuyez une fois pour démarrer, puis à nouveau pour arrêter. Maintien : dictez tant que vous maintenez l'appui.",
|
||||
"downloadFailed": "Échec du téléchargement",
|
||||
"enableDictation": "Activer la dictée vocale",
|
||||
"holdMode": "Maintien",
|
||||
"loadFailed": "Impossible de charger les paramètres de voix",
|
||||
"loadFailedMessage": "Impossible de charger les paramètres vocaux.",
|
||||
"noneSelected": "Aucun modèle sélectionné",
|
||||
"selectModelFailed": "Impossible de sélectionner le modèle",
|
||||
"speechModel": "Modèle vocal",
|
||||
"speechModelHeading": "MODÈLE VOCAL",
|
||||
"title": "Voix",
|
||||
"toggleMode": "Bascule",
|
||||
"updateFailed": "Impossible de mettre à jour"
|
||||
}
|
||||
},
|
||||
"webShell": {
|
||||
"shellStateScreen": {
|
||||
"checkingHost": "Vérification de l'hôte",
|
||||
"connectToDownload": "Connectez-vous à cet hôte pour télécharger la dernière version de l'application",
|
||||
"documentLoadFailed": "Impossible d'ouvrir l'application téléchargée.",
|
||||
"downloadFailed": "Impossible de télécharger la dernière version de l'application depuis cet hôte.",
|
||||
"hostProfileUnreadable": "Impossible de lire les informations de cet hôte sur votre appareil. Réessayez.",
|
||||
"isolationUnavailable": "La WebView de cet appareil est trop ancienne pour ouvrir l'espace de travail en toute sécurité.",
|
||||
"openingWorkspace": "Ouverture de l'espace de travail",
|
||||
"renderProcessGone": "L'espace de travail ne répond plus.",
|
||||
"statusUnreadable": "Impossible de lire l'état de cet hôte. Revenez en arrière, puis rouvrez-le.",
|
||||
"tryAgain": "Réessayer"
|
||||
}
|
||||
},
|
||||
"worktree": {
|
||||
"agentRowDisplay": {
|
||||
"blocked": "Bloqué",
|
||||
"days_many": "{{count}} j",
|
||||
"days_one": "{{count}} j",
|
||||
"days_other": "{{count}} j",
|
||||
"done": "Terminé",
|
||||
"failed": "Échec",
|
||||
"hours_many": "{{count}} h",
|
||||
"hours_one": "{{count}} h",
|
||||
"hours_other": "{{count}} h",
|
||||
"idle": "Inactif",
|
||||
"interrupted": "Interrompu",
|
||||
"justNow": "à l'instant",
|
||||
"minutes_many": "{{count}} m",
|
||||
"minutes_one": "{{count}} m",
|
||||
"minutes_other": "{{count}} m",
|
||||
"monitoring": "Surveillance des tâches en arrière-plan",
|
||||
"stopping": "Arrêt en cours…",
|
||||
"unconfirmed": "Impossible de confirmer",
|
||||
"unverifiable": "Aucune mise à jour récente",
|
||||
"waiting": "En attente de contribution",
|
||||
"working": "En activité"
|
||||
},
|
||||
"hostWorkspaceListStates": {
|
||||
"checkHost": "Vérifiez que votre ordinateur n'est pas en veille et qu'Orca est en cours d'exécution",
|
||||
"empty": "Aucun espace de travail",
|
||||
"loadFailed": "Impossible de charger les espaces de travail de cet hôte",
|
||||
"noFilterMatches": "Aucun espace de travail ne correspond aux filtres",
|
||||
"noMatches": "Aucun espace de travail correspondant",
|
||||
"reopen": "Rouvrir pour mettre à jour",
|
||||
"reopenDetail": "Cet hôte fournit une liste d'espaces de travail dans un format que cette page ne reconnaît pas encore.",
|
||||
"retrying": "Échec de worktree.ps ({{error}}) — nouvelle tentative automatique"
|
||||
},
|
||||
"workspaceListSections": {
|
||||
"all": "Tous",
|
||||
"closed": "Fermés",
|
||||
"done": "Terminés",
|
||||
"inProgress": "En cours",
|
||||
"inReview": "En revue",
|
||||
"pinned": "Épinglés"
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,8 +1,599 @@
|
||||
{
|
||||
"accounts": {
|
||||
"screen": {
|
||||
"agentLogin": "Agent 自体のログイン情報を使用",
|
||||
"connecting": "ホストに接続中…",
|
||||
"connectingTo": "「{{name}}」に接続中…",
|
||||
"default": "システムのデフォルト",
|
||||
"hostNotFound": "ホストが見つかりません",
|
||||
"invalidSnapshot": "ホストからのアカウントスナップショットが無効です",
|
||||
"loadingAccounts": "アカウントを読み込み中…",
|
||||
"manageAccountsHint": "デスクトップの「設定」→「アカウント」から、アカウントの追加や再認証を行ってください。",
|
||||
"refreshAccounts": "アカウントを更新",
|
||||
"sessionWindow": "5時間",
|
||||
"switchFailed": "アカウントを切り替えられませんでした",
|
||||
"title": "アカウント",
|
||||
"useReset": "リセット枠を使用",
|
||||
"weeklyWindow": "7日"
|
||||
}
|
||||
},
|
||||
"agentHistory": {
|
||||
"panel": {
|
||||
"all": "全て",
|
||||
"emptyScope": "この範囲には過去の Agent セッションがありません。",
|
||||
"loadFailed": "読み込めません",
|
||||
"missingResumeId": "このセッションには再開用の ID がありません。",
|
||||
"noSearchResults": "検索に一致するセッションはありません。",
|
||||
"noSessions": "Agent セッションはありません",
|
||||
"placeholder": "セッションを検索、repo:、path:",
|
||||
"project": "プロジェクト",
|
||||
"refreshSessions": "Agent セッションを更新",
|
||||
"resumeFailed": "セッションの再開に失敗しました。",
|
||||
"sessionQueued": "Agent セッションをキューに追加しました。",
|
||||
"skipped_one": "{{count}} 件のトランスクリプトをスキップしました",
|
||||
"skipped_other": "{{count}} 件のトランスクリプトをスキップしました",
|
||||
"title": "Agent セッション履歴",
|
||||
"unavailable": "Agent セッション履歴を利用できません",
|
||||
"unknownHostPlatform": "ホストのプラットフォームを判別できません。",
|
||||
"updateHost": "Agent セッション履歴を表示するには、このホストの Orca を更新してください。",
|
||||
"waitingForHost": "ホストの接続待ち…",
|
||||
"workspace": "ワークスペース"
|
||||
},
|
||||
"resumeTarget": {
|
||||
"differentHost": "このセッションは別のホストに属しています。再開するには同じホスト上のワークスペースを開いてください。",
|
||||
"openWorkspace": "セッションを再開する前に、ワークスペースを開いてください。",
|
||||
"runtimeUnavailable": "ランタイムで実行されるワークスペースでは、履歴からセッションを再開できません。"
|
||||
},
|
||||
"sessionCard": {
|
||||
"untitledSession": "無題のセッション"
|
||||
},
|
||||
"sessionHistoryList": {
|
||||
"assistantRole": "アシスタント",
|
||||
"currentWorktree": "現在のワークツリー",
|
||||
"messages_one": "{{count}} 件のメッセージ",
|
||||
"messages_other": "{{count}} 件のメッセージ",
|
||||
"resumeSession": "Agent セッションを再開",
|
||||
"userRole": "ユーザー"
|
||||
},
|
||||
"useAgentHistoryState": {
|
||||
"hostUnreachable": "ホストに接続できません",
|
||||
"loadFailed": "Agent セッションを読み込めません",
|
||||
"waitingForHost": "ホストの接続待ち…"
|
||||
},
|
||||
"worktreeNavigationActions": {
|
||||
"agentSessionHistory": "Agent セッション履歴",
|
||||
"sourceControl": "ソース管理"
|
||||
}
|
||||
},
|
||||
"app": {
|
||||
"mobileOnboarding": {
|
||||
"notificationSettingsFailed": "通知設定を更新できませんでした。もう一度お試しください。",
|
||||
"progress": "オンボーディングの進行状況",
|
||||
"saveChoiceFailed": "選択内容を保存できませんでした。もう一度お試しください。",
|
||||
"stepProgress": "{{total}} ステップ中 {{step}} ステップ目"
|
||||
},
|
||||
"notifications": {
|
||||
"description": "アプリを閉じていても Agent の通知を受け取れます。Orca のプッシュ通知サービスと Apple または Google を通じて配信されます。"
|
||||
},
|
||||
"pair": {
|
||||
"backToHome": "ホームに戻る",
|
||||
"missingCode": "ペアリングコードがありません"
|
||||
},
|
||||
"pairConfirm": {
|
||||
"backToHome": "ホームに戻る",
|
||||
"connectTimedOut": "{{seconds}} 秒以内に接続できませんでした。どこで処理が止まったか、下のログで確認してください。",
|
||||
"connecting": "接続中…",
|
||||
"description": "デスクトップのペアリングリンクを開きました。確認すると、このデスクトップがホスト一覧に追加されます。",
|
||||
"pairButton": "ペアリング",
|
||||
"pairingFailed": "ペアリングに失敗しました:{{error}}",
|
||||
"pairingLog": "ペアリングログ",
|
||||
"title": "このデスクトップとペアリングしますか?"
|
||||
},
|
||||
"pairScan": {
|
||||
"cameraDisabledDescription": "設定でカメラへのアクセスを有効にするか、ペアリングコードを貼り付けてください。",
|
||||
"cameraDisabledTitle": "カメラへのアクセスが無効",
|
||||
"cameraPermissionDescription": "デスクトップの Orca に表示された QR コードをスキャンするか、ペアリングコードを貼り付けてください。",
|
||||
"connectTimedOut": "{{seconds}} 秒以内に接続できませんでした。どこで処理が止まったか、下のログで確認してください。",
|
||||
"connecting": "接続中…",
|
||||
"invalidPairingCode": "ペアリングコードが無効です。コンピュータからコピーして、もう一度貼り付けてください。",
|
||||
"invalidQrCode": "Orca の有効な QR コードではありません",
|
||||
"openDesktopStep": "コンピュータで Orca を開く",
|
||||
"openMobileSettingsStep": "「設定」→「モバイル」を開く",
|
||||
"openSettings": "設定を開く",
|
||||
"pairingFailed": "ペアリングに失敗しました:{{error}}",
|
||||
"pairingLog": "ペアリングログ",
|
||||
"pasteCodeMessage": "コンピュータの QR コードの下に表示されているコードをコピーしてください。",
|
||||
"pasteCodePlaceholder": "orca://pair?code=... またはコードを貼り付け",
|
||||
"pasteCodeTitle": "ペアリングコードを貼り付け",
|
||||
"pasteInstead": "代わりにコードを貼り付け",
|
||||
"pastePairingCode": "またはペアリングコードを貼り付け",
|
||||
"scanCodeStep": "QR コードをスキャン",
|
||||
"title": "デスクトップのペアリング",
|
||||
"tryAgain": "再試行"
|
||||
},
|
||||
"terminalSettings": {
|
||||
"autocomplete": "自動補完と自動修正",
|
||||
"autocompleteOff": "オフ",
|
||||
"autocompleteOn": "オン",
|
||||
"defaultText": "標準(100%)",
|
||||
"keepPhoneSize": "スマートフォンサイズを維持(デフォルト)",
|
||||
"keyboardInputDescription": "自動補完、自動修正、スペル候補は、バッファー方式のコマンド入力にのみ適用されます。キーボード上部の二重矢印ボタンでライブ入力から切り替えると、「送信」をタップする前にコマンドを編集できます。コマンド、フラグ、パスが書き換わるのを防ぐため、デフォルトではオフです。",
|
||||
"keyboardInputHeading": "キーボード入力",
|
||||
"largeText": "大(125%)",
|
||||
"largerText": "特大(150%)",
|
||||
"largestText": "最大 (200%)",
|
||||
"leaveAppDescription": "スマートフォンでターミナルを使用している間、Orca は画面に合わせてターミナルを縮小します。アプリを閉じたり別のアプリに切り替えたりしたときに、スマートフォンサイズを維持して対話型 CLI ツールのレイアウトを保つか、デスクトップのサイズに戻すかを設定します。デスクトップのバナーにある「復元する」で、いつでも手動でサイズを戻せます。",
|
||||
"leaveAppHeading": "アプリを離れたとき",
|
||||
"noPairedDesktops": "ペアリング済みのデスクトップはまだありません。ペアリングすると、ターミナルの動作を設定できます。",
|
||||
"restoreAfterMinutes_one": "{{count}} 分後",
|
||||
"restoreAfterMinutes_other": "{{count}} 分後",
|
||||
"restoreAfterSeconds_one": "{{count}} 秒後",
|
||||
"restoreAfterSeconds_other": "{{count}} 秒後",
|
||||
"restoreHost": "「{{name}}」のサイズ復元",
|
||||
"smallerText": "小(75%)",
|
||||
"smallestText": "最小(50%)",
|
||||
"textSize": "テキストサイズ",
|
||||
"textSizeDescription": "ターミナルのテキストサイズを調整します。小さくすると左右に余白を残しながら多くの列を表示でき、大きくすると表示できる列が減ります。横にドラッグして表示範囲を移動できます。ターミナル上でピンチ操作をして拡大・縮小すると、この設定も更新されます。このデバイスの表示にのみ適用され、デスクトップのターミナルは変わりません。",
|
||||
"textSizeHeading": "テキストサイズ",
|
||||
"textSizePickerTitle": "ターミナルのテキストサイズ",
|
||||
"title": "ターミナル"
|
||||
}
|
||||
},
|
||||
"common": {
|
||||
"back": "戻る"
|
||||
"back": "戻る",
|
||||
"cancel": "キャンセル",
|
||||
"continue": "続行",
|
||||
"copied": "コピーしました",
|
||||
"delete": "削除",
|
||||
"ok": "OK",
|
||||
"remove": "削除",
|
||||
"retry": "再試行",
|
||||
"save": "保存",
|
||||
"settings": "設定",
|
||||
"unknown": "不明"
|
||||
},
|
||||
"connection": {
|
||||
"connectionHealth": {
|
||||
"checkConnection": "接続を確認してください。",
|
||||
"connectFailed": "接続できません",
|
||||
"connected": "接続済み",
|
||||
"connecting": "接続中…",
|
||||
"connectingViaRelay": "リレー経由で接続中…",
|
||||
"desktopUnreachable": "デスクトップに接続できません",
|
||||
"disconnected": "切断済み",
|
||||
"hostFallback": "ホスト",
|
||||
"hostOffline": "{{host}} はオフラインです",
|
||||
"hostOfflineDetail": "コンピュータがスリープしていないこと、Orca が起動していること、サインイン済みであることを確認してください。",
|
||||
"labelWithHint": "{{label}}({{hint}})",
|
||||
"pairingInvalid": "ペアリングが無効です。デスクトップと再度ペアリングしてください。",
|
||||
"rePairDetail": "デスクトップと再度ペアリングしてください。",
|
||||
"reconnecting": "再接続中…",
|
||||
"relayAccessExpired": "{{host}} のリレーアクセスの有効期限が切れています",
|
||||
"relayConnectFailed": "リレー経由で接続できません",
|
||||
"relayUnreachable": "リレーに接続できません",
|
||||
"signInDetail": "再接続するには、デスクトップで Orca にサインインしてください。",
|
||||
"signInRequired": "{{host}} でサインインが必要です",
|
||||
"tailscaleHint": "Tailscale を確認"
|
||||
},
|
||||
"connectionPathLabel": {
|
||||
"directLan": "直接接続 · LAN",
|
||||
"directTailscale": "直接接続 · Tailscale",
|
||||
"directViaLan": "LAN 経由で直接接続",
|
||||
"directViaTailscale": "Tailscale 経由で直接接続"
|
||||
},
|
||||
"hostEndpoint": {
|
||||
"addressRequired": "ホストのアドレスを入力してください。",
|
||||
"hostnameRequired": "ホスト名がありません。",
|
||||
"invalidAddress": "無効なアドレスです。",
|
||||
"invalidHostname": "無効なホスト名です。",
|
||||
"invalidPort": "ポート番号は1〜65535で指定してください。",
|
||||
"invalidScheme": "ws://、wss://、または host:port を使用してください。",
|
||||
"pathOrQueryNotAllowed": "ホストのアドレスにパスやクエリを含めないでください。"
|
||||
},
|
||||
"pairConfirmState": {
|
||||
"invalidCode": "無効なペアリングコードです。",
|
||||
"missingCode": "ペアリングコードがありません。"
|
||||
},
|
||||
"rpcClientStreamRegistry": {
|
||||
"endedBeforeReady": "準備が整う前にストリーミングリクエストが終了しました。",
|
||||
"interrupted": "接続が中断されました"
|
||||
},
|
||||
"rpcClientTerminalBinaryFrame": {
|
||||
"unsupportedFrame": "未対応のターミナルストリームフレームです(opcode {{opcode}})。"
|
||||
}
|
||||
},
|
||||
"diagnostics": {
|
||||
"connectionDiagnosticsView": {
|
||||
"authFailed": "認証失敗",
|
||||
"connected": "接続されています",
|
||||
"connecting": "接続中",
|
||||
"copyReport": "レポートをコピー",
|
||||
"diagnosisHeading": "考えられる原因",
|
||||
"disconnected": "未接続",
|
||||
"handshaking": "ハンドシェイク中",
|
||||
"noEvents": "接続イベントはまだありません。アプリがこのホストへの接続を試みると、イベントが表示されます。",
|
||||
"noHosts": "ペアリング済みのホストがありません。",
|
||||
"privacyHint": "ホスト名、エンドポイント、バージョン、接続状態、イベントを含む、機密情報を伏せてサイズを制限したレポートを送信します。ターミナルの内容や認証情報は含まれません。",
|
||||
"reconnecting": "再接続中",
|
||||
"retrySending": "送信を再試行",
|
||||
"sendDiagnostics": "診断情報を Orca に送信",
|
||||
"sending": "送信中…",
|
||||
"sent": "診断情報を送信しました",
|
||||
"stateWithAttempt": "{{state}} · 試行 {{attempts}} 回目",
|
||||
"title": "ネットワーク診断"
|
||||
},
|
||||
"mobileWebBundleProbeRow": {
|
||||
"assets": "アセット",
|
||||
"build": "ビルド",
|
||||
"bundle": "バンドル",
|
||||
"bytes": "バイト数",
|
||||
"connecting": "接続中…",
|
||||
"elapsed": "経過時間",
|
||||
"elapsedMilliseconds": "{{elapsedMs}} ミリ秒",
|
||||
"fetchBundle": "モバイル Web バンドルを取得",
|
||||
"fetching": "バンドルを取得中…",
|
||||
"host": "ホスト",
|
||||
"unknownHost": "不明なホスト"
|
||||
},
|
||||
"mobileWebShellUpdateFailureRow": {
|
||||
"title": "ワークスペースの更新"
|
||||
},
|
||||
"troubleshootCommonIssues": {
|
||||
"allowNotifications": "システム設定で Orca の通知が許可され、集中モードやおやすみモードがオフになっていることを確認してください。",
|
||||
"checkTailnet": "デスクトップがスリープ状態になっておらず、tailnet 内で接続済みと表示されていることを確認してください。",
|
||||
"desktopTitle": "デスクトップアプリが未起動",
|
||||
"disableVpn": "その VPN を無効にするか、スプリットトンネリングまたは「LAN を許可」を有効にしてください。",
|
||||
"firewallTitle": "ファイアウォールがポート 6768 をブロック",
|
||||
"keepTailscaleOn": "100.x.x.x や *.ts.net のようなホストアドレスには Tailscale 経由で接続します。Tailscale をオンにしておいてください。",
|
||||
"macosFirewall": "macOS: 「システム設定」→「ネットワーク」→「ファイアウォール」で Orca を許可してください。",
|
||||
"notificationNetwork": "モバイルデータ通信か別の Wi-Fi ネットワークを試してください。切り替え後に通知が届く場合は、元のネットワークで配信が遅れている可能性があります。",
|
||||
"notificationsTitle": "プッシュ通知",
|
||||
"openDesktop": "接続を受け付けるには、デスクトップで Orca を起動しておく必要があります。",
|
||||
"pairAfterUpdate": "更新後は、QR コードで再度ペアリングする必要がある場合があります。",
|
||||
"reconnectWifi": "両方のデバイスで Wi-Fi を接続し直してください。",
|
||||
"restartBothApps": "タイムアウトが続く場合は、両方のアプリを再起動してください。",
|
||||
"restartDesktop": "Orca を再起動してみてください。アプリの起動時にモバイル連携用サーバーも起動します。",
|
||||
"restartTailscale": "iOS/Android では、気づかないうちにトンネル接続が停止することがあります。Tailscale アプリで Tailscale を一度オフにしてから、再びオンにしてください。",
|
||||
"restrictedNetwork": "企業や学校のネットワークでは P2P 通信がブロックされる場合があります。スマートフォンのテザリングを試してください。",
|
||||
"retryHost": "ホスト一覧に戻り、ホストをタップして再試行してください。",
|
||||
"sameNetwork": "Tailscale 経由で接続する場合を除き、両方のデバイスを同じ LAN に接続する必要があります。",
|
||||
"sameSubnet": "Ethernet と Wi-Fi が同じサブネットに属している必要があります。",
|
||||
"tailscaleTitle": "Tailscale ホストに接続できない",
|
||||
"timeoutTitle": "接続タイムアウト",
|
||||
"updateTailscale": "Tailscale アプリを更新してください。最近のバージョンでは再接続の不具合が修正されています。",
|
||||
"vpnRouting": "Tailscale 以外の VPN では、ローカル通信がリモートサーバーを経由する場合があります。",
|
||||
"vpnTitle": "他の VPN の干渉",
|
||||
"wifiSignal": "スマートフォンの Wi-Fi の電波強度を確認してください。",
|
||||
"wifiTitle": "異なる Wi-Fi ネットワーク",
|
||||
"windowsFirewall": "Windows: 「Defender ファイアウォール」→「アプリの許可」で、プライベートネットワークでの Orca の通信を許可してください。"
|
||||
},
|
||||
"troubleshootView": {
|
||||
"commonIssues": "よくある問題",
|
||||
"runAgain": "再実行",
|
||||
"runDiagnostics": "診断を実行",
|
||||
"running": "実行中…",
|
||||
"title": "トラブルシューティング",
|
||||
"viewNetworkDiagnostics": "ネットワーク診断を表示"
|
||||
},
|
||||
"useTroubleshootDiagnostics": {
|
||||
"connected": "接続済み",
|
||||
"hostDataFailed": "ホストデータを読み取れませんでした。",
|
||||
"hostTestFailed": "テストできませんでした。",
|
||||
"hosts": "ホスト",
|
||||
"internet": "インターネット",
|
||||
"noConnection": "接続なし",
|
||||
"noPairedHosts": "なし(QR をスキャンしてペアリング)",
|
||||
"pairedCount": "{{count}} 台ペアリング済み",
|
||||
"pairedHosts": "ペアリング済みのホスト",
|
||||
"platform": "プラットフォーム",
|
||||
"reachable": "{{endpoint}} に到達可能",
|
||||
"unexpectedResponse": "予期しない応答"
|
||||
},
|
||||
"webShellUpdateFailureCopy": {
|
||||
"failedAgo": "{{hostName}} からの前回の更新は {{time}} 前に失敗しました:{{reason}}{{offered}}。{{outcome}}",
|
||||
"failedJustNow": "たった今、{{hostName}} からの前回の更新に失敗しました:{{reason}}{{offered}}。{{outcome}}"
|
||||
}
|
||||
},
|
||||
"dictation": {
|
||||
"setup": {
|
||||
"alreadyActive": "コンピュータまたは別のスマートフォンで、すでにディクテーションを使用中です。",
|
||||
"cloudModelNeedsKey": "OpenAI キーが必要",
|
||||
"deleteFailed": "モデルの削除に失敗しました。",
|
||||
"desktopUpdateRequired": "モバイルの音声設定を使用するには、ペアリング済みのデスクトップの Orca アプリを更新してください。",
|
||||
"downloadFailed": "ダウンロードの開始に失敗しました。",
|
||||
"loadFailed": "ディクテーションモデルの読み込みに失敗しました。",
|
||||
"repairRequired": "ディクテーションを使用するには、このスマートフォンをコンピュータと再度ペアリングしてください。",
|
||||
"serverUpdateRequired": "音声ディクテーションを使用するには、このサーバーの Orca を更新してください。",
|
||||
"sessionLost": "ディクテーション中にコンピュータとの接続が切れました。再試行してください。",
|
||||
"updateFailed": "ディクテーション設定の更新に失敗しました。"
|
||||
}
|
||||
},
|
||||
"home": {
|
||||
"accountUsageCards": {
|
||||
"sectionHeading": "アカウントの使用状況",
|
||||
"sessionWindow": "5時間",
|
||||
"systemDefault": "システムのデフォルト",
|
||||
"weeklyWindow": "7日"
|
||||
},
|
||||
"appUpdateCard": {
|
||||
"dismissAccessibilityLabel": "Orca {{version}} の更新通知を閉じる",
|
||||
"title": "Orca {{version}} が利用可能です",
|
||||
"updateAccessibilityLabel": "Orca {{version}} が利用可能です。タップして更新してください。",
|
||||
"updateHint": "タップして更新"
|
||||
},
|
||||
"emptyState": {
|
||||
"connectedDescription": "デスクトップがここに表示されます。すべての通信はエンドツーエンドで暗号化されます。",
|
||||
"connectedTitle": "接続完了",
|
||||
"description": "コンピュータ上の Orca とペアリングすると、スマートフォンから Agent の状況を確認したり、任意のターミナルを操作したりして、作業を進められます。",
|
||||
"openDesktopDescription": "「設定」→「モバイル」を開き、ペアリング用の QR コードを生成してください。",
|
||||
"openDesktopTitle": "デスクトップで Orca を開く",
|
||||
"pairDesktop": "デスクトップのペアリング",
|
||||
"scanCodeDescription": "上のボタンをタップしてスキャナーを開いてください。画面の QR コードにカメラを向けてください。",
|
||||
"scanCodeTitle": "コードをスキャン",
|
||||
"stepsHeading": "使い方",
|
||||
"title": "デスクトップに接続"
|
||||
},
|
||||
"listFooter": {
|
||||
"hostPickerTitle": "タスクを開くホスト",
|
||||
"resumeHeading": "再開",
|
||||
"tasksHeading": "タスク"
|
||||
},
|
||||
"listHeader": {
|
||||
"agentTime": "Agent 時間",
|
||||
"agentsSpawned": "Agent が生成されました",
|
||||
"daysHours": "{{days}}日 {{hours}}時間",
|
||||
"desktopsHeading": "デスクトップ",
|
||||
"hoursMinutes": "{{hours}}時間 {{minutes}}分",
|
||||
"minutes": "{{minutes}}分",
|
||||
"prsCreated": "作成された PR",
|
||||
"title": "おかえり"
|
||||
},
|
||||
"screen": {
|
||||
"pairingCheckFailed": "ペアリングを確認できませんでした",
|
||||
"removeHostFailed": "ホストを削除できませんでした",
|
||||
"removeHostMessage": "「{{name}}」を削除しますか?後で再度ペアリングできます。",
|
||||
"removeHostTitle": "ホストを削除",
|
||||
"tryAgain": "再試行してください。"
|
||||
},
|
||||
"tasksCard": {
|
||||
"noSources": "タスクの連携先が接続されていません",
|
||||
"openProviderTasks": "{{provider}} のタスクを開く",
|
||||
"title": "タスク"
|
||||
}
|
||||
},
|
||||
"hooks": {
|
||||
"dictationDesktopStart": {
|
||||
"recordingFailed": "マイクでの録音を開始できませんでした"
|
||||
},
|
||||
"useMobileDictation": {
|
||||
"inputClosed": "このセッションが入力を受け付けなくなったため、音声ディクテーションを停止しました。再試行してください。",
|
||||
"microphoneInitFailed": "マイクの初期化に失敗しました",
|
||||
"microphoneInterrupted": "マイクの使用が中断されたため、音声ディクテーションを停止しました。再試行してください。",
|
||||
"microphonePermissionDenied": "マイクの使用が許可されていません",
|
||||
"noSpeech": "音声が検出されませんでした。"
|
||||
}
|
||||
},
|
||||
"host": {
|
||||
"edit": {
|
||||
"address": "アドレス",
|
||||
"addressHint": "IP、host:port、ws:// / wss:// を使用できます。ポートを省略した wss:// URL は 443 を使用します。アドレスのみの場合は、現在のポート(未設定の場合は 6768)を引き継ぎます。",
|
||||
"connectionPreview": "{{endpoint}} に接続します",
|
||||
"goBack": "戻る",
|
||||
"help": "表示名や接続先アドレスを変更できます。名前を空欄にすると、デスクトップから取得した名前を使用します。アドレスを編集しても、このスマートフォンの接続先が変わるだけで、再ペアリングは行われません。同じデスクトップに別の IP で接続できる場合(自宅の LAN と Tailscale の切り替えなど)に使用してください。",
|
||||
"hostNamePlaceholder": "ホスト名",
|
||||
"hostRemoved": "このホストはこのスマートフォンから削除されました。",
|
||||
"loadFailed": "ホストの読み込みに失敗しました。",
|
||||
"missingHost": "ホストが指定されていません。",
|
||||
"name": "名前",
|
||||
"saveFailed": "ホストの保存に失敗しました。",
|
||||
"saveHost": "ホストを保存",
|
||||
"title": "ホストを編集"
|
||||
}
|
||||
},
|
||||
"hostScreen": {
|
||||
"hostListActionSheetActions": {
|
||||
"connect": "接続",
|
||||
"disconnect": "切断",
|
||||
"editHost": "ホストを編集",
|
||||
"networkDiagnostics": "ネットワーク診断",
|
||||
"reconnect": "再接続"
|
||||
},
|
||||
"hostPendingCreationRow": {
|
||||
"creatingWorkspace": "ワークスペースを作成中…"
|
||||
},
|
||||
"hostRailActions": {
|
||||
"accounts": "アカウント",
|
||||
"closeSearch": "検索を閉じる",
|
||||
"floatingWorkspace": "フローティングワークスペース",
|
||||
"newWorkspace": "新規ワークスペース",
|
||||
"searchWorkspaces": "ワークスペースの検索",
|
||||
"tasks": "タスク"
|
||||
},
|
||||
"hostScreenHeader": {
|
||||
"accounts": "アカウント",
|
||||
"activeFilters_one": "ワークスペースのオプション ({{count}} 件のフィルターが有効)",
|
||||
"activeFilters_other": "ワークスペースのオプション ({{count}} 件のフィルターが有効)",
|
||||
"backToHosts": "ホスト一覧に戻る",
|
||||
"closeSearch": "検索を閉じる",
|
||||
"floatingWorkspace": "フローティングワークスペース",
|
||||
"hideSidebar": "サイドバーを非表示",
|
||||
"reconnect": "再接続",
|
||||
"searchWorkspaces": "ワークスペースの検索",
|
||||
"tasks": "タスク",
|
||||
"workspaceOptions": "ワークスペースのオプション"
|
||||
},
|
||||
"hostScreenOverlays": {
|
||||
"deleteConfirmMessage": "「{{name}}」({{branch}})を削除しますか?",
|
||||
"deleteWorktree": "ワークツリーを削除",
|
||||
"pin": "ピン",
|
||||
"primaryWorktreeDeleteHint": "プライマリワークツリーは削除できません。代わりにプロジェクトを削除してください。",
|
||||
"removeHost": "ホストを削除",
|
||||
"removeHostMessage": "「{{name}}」を削除しますか?後で再度ペアリングできます。",
|
||||
"sleep": "スリープ",
|
||||
"unpin": "固定を解除する"
|
||||
},
|
||||
"hostWorkspaceList": {
|
||||
"searchPlaceholder": "ワークスペースを検索…",
|
||||
"searchWorkspaces": "ワークスペースの検索"
|
||||
},
|
||||
"workspaceDetailsSheet": {
|
||||
"checksFailing": "チェック:失敗",
|
||||
"checksPassing": "チェック:成功",
|
||||
"checksPending": "チェック:保留中",
|
||||
"closed": "クローズ",
|
||||
"createdByAutomation": "オートメーションによって作成",
|
||||
"createdByNamedAutomation": "オートメーション「{{name}}」によって作成",
|
||||
"createdFromCli": "Orca CLI から作成",
|
||||
"draft": "下書き",
|
||||
"merged": "マージ済み",
|
||||
"openLink": "{{label}} を開く",
|
||||
"openLinkWithTitle": "{{label}}、{{title}} を開く",
|
||||
"prOpen": "オープン"
|
||||
},
|
||||
"workspaceOptionControls": {
|
||||
"agentActivity": "Agent のアクティビティ",
|
||||
"agentActivityLayout": "Agent アクティビティのレイアウト",
|
||||
"agentActivitySort": "Agent のアクティビティ",
|
||||
"agentActivitySortDescription": "注意が必要な Agent、次に最新のアクティビティ。",
|
||||
"agentStatuses": "Agent ステータス",
|
||||
"allHosts": "すべてのホスト",
|
||||
"allProjects": "すべてのプロジェクト",
|
||||
"automation": "オートメーション",
|
||||
"branchFolderPath": "ブランチ / フォルダーパス",
|
||||
"branchName": "ブランチ名",
|
||||
"compact": "コンパクト",
|
||||
"detailed": "詳細",
|
||||
"fullList": "完全なリスト",
|
||||
"githubIssues": "GitHub Issue",
|
||||
"githubTicket": "GitHub チケット",
|
||||
"host": "ホスト",
|
||||
"hover": "ホバー",
|
||||
"jiraIssue": "Jira Issue",
|
||||
"jiraIssues": "Jira Issue",
|
||||
"linearIssue": "Linear Issue",
|
||||
"linearIssues": "Linear Issue",
|
||||
"manual": "手動",
|
||||
"manualProjectOrderDescription": "プロジェクトをドラッグして配置します",
|
||||
"manualWorkspaceOrderDescription": "ワークスペースをドラッグして、各グループ内に配置します。",
|
||||
"name": "名前",
|
||||
"none": "なし",
|
||||
"notes": "ノート",
|
||||
"ports": "ポート",
|
||||
"pr": "PR",
|
||||
"prMrLink": "PR/MR リンク",
|
||||
"project": "プロジェクト",
|
||||
"recent": "最近の",
|
||||
"recentProjectOrderDescription": "最新のワークスペースアクティビティ",
|
||||
"searchProjects": "プロジェクトを検索する",
|
||||
"searchProjectsPlaceholder": "プロジェクトを検索…",
|
||||
"selectedCount": "{{total}} 件中 {{count}} 件",
|
||||
"status": "状態"
|
||||
},
|
||||
"workspaceOptionsSheet": {
|
||||
"backToOptions": "ワークスペースのオプションに戻る",
|
||||
"cardDisplay": "カード表示",
|
||||
"cardLayout": "カードのレイアウト",
|
||||
"clearFilters": "フィルターをクリア",
|
||||
"exceptDefaultBranch": "デフォルトのブランチを除く",
|
||||
"filters": "フィルター",
|
||||
"groupBy": "グループ化",
|
||||
"hideAutomationCreated": "自動化で作成されたワークスペースを非表示",
|
||||
"hideCliCreated": "CLI が作成したものを非表示",
|
||||
"hideDefaultBranch": "デフォルトのブランチを非表示にする",
|
||||
"hideDetachedHead": "分離 HEAD を非表示",
|
||||
"hideOtherClientWorkspaces": "他のクライアントのワークスペースを非表示にする",
|
||||
"hideSleeping": "スリープ中を非表示",
|
||||
"hosts": "ホスト",
|
||||
"localLinux": "ローカル Linux",
|
||||
"localMac": "ローカル Mac",
|
||||
"localWindows": "ローカル Windows",
|
||||
"projectOrder": "プロジェクトの順序",
|
||||
"projects": "プロジェクト",
|
||||
"savingToDesktop": "デスクトップに保存中",
|
||||
"show": "表示",
|
||||
"showProperties": "プロパティを表示する",
|
||||
"sortBy": "並べ替え",
|
||||
"syncedAcrossDevices": "デバイス間で同期されます",
|
||||
"thisComputer": "このコンピュータ",
|
||||
"waitingForDesktop": "まだデスクトップの応答を待っています",
|
||||
"workspaceOptions": "ワークスペースのオプション"
|
||||
}
|
||||
},
|
||||
"navigation": {
|
||||
"hostStackScreens": {
|
||||
"accounts": "アカウント",
|
||||
"agentSessionHistory": "Agent セッション履歴",
|
||||
"changes": "変更点",
|
||||
"editHost": "ホストを編集",
|
||||
"host": "ホスト",
|
||||
"pullRequest": "PR",
|
||||
"sourceControl": "ソース管理",
|
||||
"tasks": "タスク",
|
||||
"terminal": "ターミナル",
|
||||
"workspace": "ワークスペース"
|
||||
}
|
||||
},
|
||||
"notifications": {
|
||||
"notificationDeliverySection": {
|
||||
"footer": "通知の種類は、ペアリング済みの各デスクトップの通知設定に従います。このアプリを7日間使用しないと通知が一時停止します。再開するには、アプリを開いて再接続してください。",
|
||||
"onlyWhenAway": "デスクトップから離れているときのみ",
|
||||
"onlyWhenAwayHint": "キーボードやマウスの操作が 3 分間ないとき、またはロック中に通知します。",
|
||||
"sound": "通知音",
|
||||
"suppressWhileFocused": "表示中は通知しない",
|
||||
"suppressWhileFocusedHint": "このスマートフォンで開いているワークスペースの通知を省略します。"
|
||||
}
|
||||
},
|
||||
"onboarding": {
|
||||
"notificationOnboardingPreview": {
|
||||
"claudeNeedsInput": "Claude が入力待ち",
|
||||
"codexFinished": "Codex の作業が完了",
|
||||
"now": "今",
|
||||
"testsPassing": "テストは成功しています。",
|
||||
"waitingOnYou": "対応を待っています。"
|
||||
},
|
||||
"onboardingPage": {
|
||||
"enableAgentNotifications": "Agent の通知を有効にする",
|
||||
"enableNotifications": "通知を有効にする",
|
||||
"keepTerminal": "ターミナルのままにする",
|
||||
"notNow": "後で",
|
||||
"notificationsDescription": "アプリを使用していないときでも、Agent の作業が完了したときや対応待ちになったときに、このスマートフォンに通知が届きます。",
|
||||
"notificationsDisclosure": "デスクトップで3分間操作がないと、Orca のプッシュ通知サービスを通じて配信されます。「設定」でいつでも変更できます。",
|
||||
"notificationsTitle": "Agent からの呼びかけを見逃さない",
|
||||
"openInChatUi": "セッションをチャット UI で開く",
|
||||
"openInTerminal": "セッションをターミナルで開く",
|
||||
"sessionViewDescription": "この端末で、対応する Agent セッションをターミナルとチャット UI のどちらで開くか選択してください。セッションタブを長押しすると表示を切り替えられます。デフォルトは後から「設定」で変更できます。",
|
||||
"sessionViewTitle": "セッションの表示方法は?",
|
||||
"skipNotifications": "今は通知を有効にしない",
|
||||
"useChatUi": "チャット UI を使用"
|
||||
}
|
||||
},
|
||||
"settings": {
|
||||
"aboutScreen": {
|
||||
"openLinkFailed": "リンクを開けませんでした。再試行してください。",
|
||||
"socialProfile": "Orca の X アカウント",
|
||||
"sourceCode": "Orca のソースコード",
|
||||
"tagline": "100 倍の生産性を目指す開発者向けのオープンソース Agent IDE",
|
||||
"title": "Orca について",
|
||||
"website": "Orca のウェブサイト"
|
||||
},
|
||||
"appUpdateSettingsRows": {
|
||||
"checkFailed": "確認できませんでした",
|
||||
"checkUpdates": "アップデートを確認",
|
||||
"checkUpdatesStatus": "アップデートを確認、{{status}}",
|
||||
"checking": "確認中…",
|
||||
"installedVersion": "バージョン {{version}}",
|
||||
"lastCheckedDays_one": "最終確認:{{count}} 日前",
|
||||
"lastCheckedDays_other": "最終確認:{{count}} 日前",
|
||||
"lastCheckedHours_one": "最終確認:{{count}} 時間前",
|
||||
"lastCheckedHours_other": "最終確認:{{count}} 時間前",
|
||||
"lastCheckedJustNow": "最終確認:たった今",
|
||||
"lastCheckedMinutes_one": "最終確認:{{count}} 分前",
|
||||
"lastCheckedMinutes_other": "最終確認:{{count}} 分前",
|
||||
"neverChecked": "未確認",
|
||||
"upToDate": "最新",
|
||||
"update": "更新",
|
||||
"updateToVersion": "Orca {{version}} に更新"
|
||||
},
|
||||
"appearance": {
|
||||
"language": {
|
||||
"description": "「システム」はこのデバイスの言語に従います。権限の確認画面には常にデバイスの言語が使われます。",
|
||||
@@ -17,6 +608,157 @@
|
||||
"system": "システム"
|
||||
},
|
||||
"title": "外観"
|
||||
},
|
||||
"browserSettingsScreen": {
|
||||
"desktopBrowser": "デスクトップの Orca ブラウザ",
|
||||
"desktopBrowserDescription": "ペアリング済みのデスクトップからストリーミングされるブラウザで開きます。",
|
||||
"linksDescription": "ターミナル出力でタップした HTTP(S) リンクを開く場所を選択してください。",
|
||||
"linksHeading": "リンク",
|
||||
"loadFailed": "ブラウザ設定を読み込めませんでした。再試行してください。",
|
||||
"openTerminalLinks": "ターミナルのリンクを開く",
|
||||
"phoneBrowser": "スマートフォンのブラウザ",
|
||||
"phoneBrowserDescription": "このスマートフォンの Safari、Chrome などのブラウザで開きます。",
|
||||
"saveFailed": "ブラウザ設定を保存できませんでした。再試行してください。",
|
||||
"title": "ブラウザ"
|
||||
},
|
||||
"menuItems": {
|
||||
"about": "Orca について",
|
||||
"appearance": "外観",
|
||||
"browser": "ブラウザ",
|
||||
"chatUi": "Chat UI",
|
||||
"notifications": "通知",
|
||||
"terminal": "ターミナル",
|
||||
"troubleshooting": "トラブルシューティング",
|
||||
"voice": "音声"
|
||||
},
|
||||
"menuScreen": {
|
||||
"privacyPolicy": "プライバシーポリシー",
|
||||
"support": "サポート"
|
||||
},
|
||||
"nativeChatSettingsScreen": {
|
||||
"defaultViewDescription": "対応する Agent セッション(Claude、Codex など、チャットに対応した Agent)をこのデバイスで開く際の表示方法を選択してください。「ターミナル」では CLI をそのまま表示し、「Chat UI」ではデスクトップアプリと同様のチャット画面を表示します。各セッションの表示方法は、長押しメニューから個別に切り替えられます。",
|
||||
"defaultViewHeading": "デフォルトの表示",
|
||||
"off": "オフ",
|
||||
"on": "オン",
|
||||
"openInChatUi": "セッションを Chat UI で開く",
|
||||
"title": "Chat UI"
|
||||
},
|
||||
"nativeNotificationDeliverySettings": {
|
||||
"loadFailed": "通知の配信設定を読み込めませんでした。再試行するには、この画面を開き直してください。",
|
||||
"saveFailed": "通知の配信設定を保存できませんでした。再試行してください。",
|
||||
"updateDesktopHint": "このスマートフォンで通知を受け取るには、更新済みのデスクトップとペアリングしてください。"
|
||||
},
|
||||
"notificationDisplayTest": {
|
||||
"accepted": "Orca のプッシュサービスが通知を受け付けました。通知が届いているか確認してください。",
|
||||
"connectDesktop": "デスクトップに接続してから再試行してください。",
|
||||
"description": "Orca のプッシュサービス経由でテスト通知を送信します。",
|
||||
"desktopUnreachable": "デスクトップに接続できませんでした。再試行してください。",
|
||||
"loadFailed": "ペアリング済みのデスクトップ一覧を読み込めませんでした。",
|
||||
"pairDesktop": "デスクトップとペアリングしてから再試行してください。",
|
||||
"pushServiceFailed": "Orca のプッシュサービス経由で送信できませんでした。再試行してください。",
|
||||
"rateLimited": "通知が多すぎます。しばらくしてから再試行してください。",
|
||||
"reconnectPhone": "このスマートフォンを通知の受信先として登録するには、再接続してください。",
|
||||
"sendButton": "テスト通知を送信する",
|
||||
"sendFailed": "テスト用のプッシュ通知を送信できませんでした。",
|
||||
"sending": "送信中…",
|
||||
"troubleReceivingAlerts": "通知が届きませんか?",
|
||||
"troubleshooting": "トラブルシューティング",
|
||||
"updateDesktop": "このテストを実行するには、デスクトップを更新してください。"
|
||||
},
|
||||
"notificationSettingsScreen": {
|
||||
"description": "Agent が入力を必要とするときやタスクを完了したときに、このデバイスに通知が届きます。",
|
||||
"enableNotifications": "通知を有効にする",
|
||||
"loadFailed": "通知設定を読み込めませんでした。再試行してください。",
|
||||
"notificationsBlocked": "システム設定で通知が無効になっています。",
|
||||
"openSettings": "設定を開く",
|
||||
"openSettingsFailed": "システム設定を開けませんでした。再試行してください。",
|
||||
"saveFailed": "通知設定を保存できませんでした。再試行してください。",
|
||||
"title": "通知"
|
||||
},
|
||||
"pendingCredentialCleanupCard": {
|
||||
"cleanupUnconfirmed_one": "このデバイスで、認証情報 {{count}} 件の削除完了を確認できませんでした。",
|
||||
"cleanupUnconfirmed_other": "このデバイスで、認証情報 {{count}} 件の削除完了を確認できませんでした。",
|
||||
"retryCleanup": "ペアリング認証情報の削除を再試行",
|
||||
"retryFailed": "削除の完了をまだ確認できません。しばらくしてから再試行してください。",
|
||||
"statusCheckFailed": "このデバイスでの認証情報の削除状況を確認できませんでした。念のため再試行してください。",
|
||||
"title": "ペアリング認証情報の削除"
|
||||
},
|
||||
"uiLanguageSettingsSection": {
|
||||
"pseudoLanguage": "疑似言語 (en-XA)"
|
||||
},
|
||||
"voiceSettingsScreen": {
|
||||
"connectDesktop": "音声設定を管理するには、デスクトップに接続してください。",
|
||||
"deleteFailed": "削除に失敗しました",
|
||||
"dictationDescription": "デスクトップでフォーカスされている任意のペインに、音声でテキストを入力できます。",
|
||||
"dictationHeading": "ディクテーション",
|
||||
"dictationMode": "ディクテーションモード",
|
||||
"dictationModeDescription": "トグル:1 回押すと開始し、もう一度押すと停止します。ホールド:押している間だけ音声入力します。",
|
||||
"downloadFailed": "ダウンロードに失敗しました",
|
||||
"enableDictation": "音声ディクテーションを有効にする",
|
||||
"holdMode": "ホールド",
|
||||
"loadFailed": "音声設定の読み込みに失敗しました。",
|
||||
"loadFailedMessage": "音声設定の読み込みに失敗しました。",
|
||||
"noneSelected": "未選択",
|
||||
"selectModelFailed": "モデルを選択できませんでした",
|
||||
"speechModel": "音声モデル",
|
||||
"speechModelHeading": "音声モデル",
|
||||
"title": "音声",
|
||||
"toggleMode": "トグル",
|
||||
"updateFailed": "更新できませんでした"
|
||||
}
|
||||
},
|
||||
"webShell": {
|
||||
"shellStateScreen": {
|
||||
"checkingHost": "ホストを確認中",
|
||||
"connectToDownload": "このホストに接続して、最新のアプリをダウンロードしてください。",
|
||||
"documentLoadFailed": "ダウンロードしたアプリを開けませんでした。",
|
||||
"downloadFailed": "このホストから最新のアプリをダウンロードできませんでした。",
|
||||
"hostProfileUnreadable": "このデバイスでこのホストの情報を読み取れませんでした。再試行してください。",
|
||||
"isolationUnavailable": "このデバイスの WebView が古すぎるため、ワークスペースを安全に開けません。",
|
||||
"openingWorkspace": "ワークスペースを開いています",
|
||||
"renderProcessGone": "ワークスペースが応答しなくなりました。",
|
||||
"statusUnreadable": "このホストの状態を読み取れませんでした。前の画面に戻り、もう一度開いてください。",
|
||||
"tryAgain": "再試行"
|
||||
}
|
||||
},
|
||||
"worktree": {
|
||||
"agentRowDisplay": {
|
||||
"blocked": "ブロック中",
|
||||
"days_one": "{{count}}日",
|
||||
"days_other": "{{count}}日",
|
||||
"done": "完了",
|
||||
"failed": "失敗",
|
||||
"hours_one": "{{count}}時間",
|
||||
"hours_other": "{{count}}時間",
|
||||
"idle": "アイドル",
|
||||
"interrupted": "中断",
|
||||
"justNow": "たった今",
|
||||
"minutes_one": "{{count}}分",
|
||||
"minutes_other": "{{count}}分",
|
||||
"monitoring": "バックグラウンドタスクを監視中",
|
||||
"stopping": "停止中…",
|
||||
"unconfirmed": "確認できませんでした",
|
||||
"unverifiable": "最近の更新なし",
|
||||
"waiting": "入力待ち",
|
||||
"working": "作業中"
|
||||
},
|
||||
"hostWorkspaceListStates": {
|
||||
"checkHost": "コンピュータがスリープしておらず、Orca が起動していることを確認してください。",
|
||||
"empty": "ワークスペースがありません",
|
||||
"loadFailed": "このホストからワークスペースを読み込めませんでした。",
|
||||
"noFilterMatches": "フィルターに一致するワークスペースがありません",
|
||||
"noMatches": "一致するワークスペースがありません",
|
||||
"reopen": "開き直して更新",
|
||||
"reopenDetail": "このページは、このホストのワークスペース一覧の形式にまだ対応していません。",
|
||||
"retrying": "worktree.ps に失敗しました({{error}})。自動で再試行中です。"
|
||||
},
|
||||
"workspaceListSections": {
|
||||
"all": "全て",
|
||||
"closed": "クローズ",
|
||||
"done": "完了",
|
||||
"inProgress": "進行中",
|
||||
"inReview": "レビュー中",
|
||||
"pinned": "ピン留め済み"
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,8 +1,599 @@
|
||||
{
|
||||
"accounts": {
|
||||
"screen": {
|
||||
"agentLogin": "에이전트 자체 로그인 사용",
|
||||
"connecting": "호스트에 연결 중…",
|
||||
"connectingTo": "{{name}}에 연결 중…",
|
||||
"default": "시스템 기본값",
|
||||
"hostNotFound": "호스트를 찾을 수 없습니다.",
|
||||
"invalidSnapshot": "호스트에서 받은 계정 스냅샷이 유효하지 않습니다.",
|
||||
"loadingAccounts": "계정 로드 중…",
|
||||
"manageAccountsHint": "데스크톱의 설정 → 계정에서 계정을 추가하거나 다시 인증하세요.",
|
||||
"refreshAccounts": "계정 새로고침",
|
||||
"sessionWindow": "5시간",
|
||||
"switchFailed": "계정을 전환하지 못했습니다.",
|
||||
"title": "계정",
|
||||
"useReset": "초기화권 사용",
|
||||
"weeklyWindow": "7일"
|
||||
}
|
||||
},
|
||||
"agentHistory": {
|
||||
"panel": {
|
||||
"all": "모두",
|
||||
"emptyScope": "이 범위에는 이전 에이전트 세션이 없습니다.",
|
||||
"loadFailed": "로드 실패",
|
||||
"missingResumeId": "이 세션에는 재개 ID가 없습니다.",
|
||||
"noSearchResults": "검색 조건에 맞는 세션이 없습니다.",
|
||||
"noSessions": "에이전트 세션 없음",
|
||||
"placeholder": "세션 검색, repo:, path:",
|
||||
"project": "프로젝트",
|
||||
"refreshSessions": "에이전트 세션 새로고침",
|
||||
"resumeFailed": "세션을 재개하지 못했습니다.",
|
||||
"sessionQueued": "에이전트 세션이 대기열에 추가되었습니다.",
|
||||
"skipped_one": "대화 기록 {{count}}개 건너뜀",
|
||||
"skipped_other": "대화 기록 {{count}}개 건너뜀",
|
||||
"title": "Agent 세션 기록",
|
||||
"unavailable": "에이전트 세션 기록 사용 불가",
|
||||
"unknownHostPlatform": "호스트 플랫폼을 확인할 수 없습니다.",
|
||||
"updateHost": "에이전트 세션 기록을 보려면 이 호스트의 Orca를 업데이트하세요.",
|
||||
"waitingForHost": "호스트 대기 중…",
|
||||
"workspace": "워크스페이스"
|
||||
},
|
||||
"resumeTarget": {
|
||||
"differentHost": "이 세션은 다른 호스트에 속합니다. 재개하려면 같은 호스트의 워크스페이스를 여세요.",
|
||||
"openWorkspace": "세션을 재개하기 전에 워크스페이스를 여세요.",
|
||||
"runtimeUnavailable": "런타임에서 호스팅되는 워크스페이스에서는 기록에서 세션을 재개할 수 없습니다."
|
||||
},
|
||||
"sessionCard": {
|
||||
"untitledSession": "제목 없는 세션"
|
||||
},
|
||||
"sessionHistoryList": {
|
||||
"assistantRole": "어시스턴트",
|
||||
"currentWorktree": "현재 워크트리",
|
||||
"messages_one": "메시지 {{count}}개",
|
||||
"messages_other": "메시지 {{count}}개",
|
||||
"resumeSession": "에이전트 세션 재개",
|
||||
"userRole": "사용자"
|
||||
},
|
||||
"useAgentHistoryState": {
|
||||
"hostUnreachable": "호스트에 연결할 수 없습니다.",
|
||||
"loadFailed": "에이전트 세션을 불러올 수 없습니다.",
|
||||
"waitingForHost": "호스트 대기 중…"
|
||||
},
|
||||
"worktreeNavigationActions": {
|
||||
"agentSessionHistory": "Agent 세션 기록",
|
||||
"sourceControl": "소스 제어"
|
||||
}
|
||||
},
|
||||
"app": {
|
||||
"mobileOnboarding": {
|
||||
"notificationSettingsFailed": "알림 설정을 업데이트하지 못했습니다. 다시 시도하세요.",
|
||||
"progress": "온보딩 진행 상황",
|
||||
"saveChoiceFailed": "선택 사항을 저장하지 못했습니다. 다시 시도하세요.",
|
||||
"stepProgress": "{{total}}단계 중 {{step}}단계"
|
||||
},
|
||||
"notifications": {
|
||||
"description": "앱을 닫아도 에이전트 알림을 받을 수 있습니다. 알림은 Orca의 푸시 서비스와 Apple 또는 Google을 통해 전달됩니다."
|
||||
},
|
||||
"pair": {
|
||||
"backToHome": "홈으로 돌아가기",
|
||||
"missingCode": "페어링 코드가 없습니다."
|
||||
},
|
||||
"pairConfirm": {
|
||||
"backToHome": "홈으로 돌아가기",
|
||||
"connectTimedOut": "{{seconds}}초 내에 연결하지 못했습니다. 어느 단계에서 멈췄는지 아래 로그를 확인하세요.",
|
||||
"connecting": "연결 중…",
|
||||
"description": "데스크톱의 페어링 링크를 열었습니다. 확인하면 이 데스크톱이 호스트 목록에 추가됩니다.",
|
||||
"pairButton": "페어링",
|
||||
"pairingFailed": "페어링 실패: {{error}}",
|
||||
"pairingLog": "페어링 로그",
|
||||
"title": "이 데스크톱과 페어링하시겠습니까?"
|
||||
},
|
||||
"pairScan": {
|
||||
"cameraDisabledDescription": "설정에서 카메라 접근을 허용하거나 페어링 코드를 붙여넣으세요.",
|
||||
"cameraDisabledTitle": "카메라 접근 비활성화",
|
||||
"cameraPermissionDescription": "데스크톱의 Orca에 표시된 QR 코드를 스캔하거나 페어링 코드를 붙여넣으세요.",
|
||||
"connectTimedOut": "{{seconds}}초 내에 연결하지 못했습니다. 어느 단계에서 멈췄는지 아래 로그를 확인하세요.",
|
||||
"connecting": "연결 중…",
|
||||
"invalidPairingCode": "유효한 페어링 코드가 아닙니다. 컴퓨터에서 코드를 복사해 다시 붙여넣으세요.",
|
||||
"invalidQrCode": "유효한 Orca QR 코드가 아닙니다.",
|
||||
"openDesktopStep": "컴퓨터에서 Orca를 실행하세요",
|
||||
"openMobileSettingsStep": "설정 → 모바일로 이동하세요",
|
||||
"openSettings": "설정 열기",
|
||||
"pairingFailed": "페어링 실패: {{error}}",
|
||||
"pairingLog": "페어링 로그",
|
||||
"pasteCodeMessage": "컴퓨터의 QR 코드 아래에 표시된 코드를 복사하세요.",
|
||||
"pasteCodePlaceholder": "orca://pair?code=... 또는 코드 붙여넣기",
|
||||
"pasteCodeTitle": "페어링 코드 붙여넣기",
|
||||
"pasteInstead": "대신 코드 붙여넣기",
|
||||
"pastePairingCode": "또는 페어링 코드 붙여넣기",
|
||||
"scanCodeStep": "QR 코드를 스캔하세요",
|
||||
"title": "데스크톱과 페어링",
|
||||
"tryAgain": "다시 시도"
|
||||
},
|
||||
"terminalSettings": {
|
||||
"autocomplete": "자동 완성 및 자동 수정",
|
||||
"autocompleteOff": "꺼짐",
|
||||
"autocompleteOn": "켜짐",
|
||||
"defaultText": "기본 (100%)",
|
||||
"keepPhoneSize": "휴대폰 크기로 유지(기본값)",
|
||||
"keyboardInputDescription": "자동 완성, 자동 수정, 맞춤법 제안은 버퍼 방식의 명령어 입력에만 적용됩니다. 키보드 위의 이중 화살표 버튼을 눌러 실시간 입력에서 전환한 다음, 명령어를 수정하고 '보내기'를 누르세요. 명령어, 플래그, 경로가 자동으로 바뀌지 않도록 기본적으로 꺼져 있습니다.",
|
||||
"keyboardInputHeading": "키보드 입력",
|
||||
"largeText": "크게 (125%)",
|
||||
"largerText": "더 크게 (150%)",
|
||||
"largestText": "가장 크게 (200%)",
|
||||
"leaveAppDescription": "휴대폰에서 터미널을 사용하는 동안 Orca는 화면에 맞게 터미널을 축소합니다. 앱을 닫거나 다른 앱으로 전환할 때, 대화형 CLI 도구의 화면 배치가 바뀌지 않도록 휴대폰 크기를 유지할지 데스크톱 크기로 되돌릴지 설정합니다. 데스크톱 배너의 '복원'을 눌러 언제든 직접 크기를 조정할 수 있습니다.",
|
||||
"leaveAppHeading": "앱을 벗어날 때",
|
||||
"noPairedDesktops": "아직 페어링된 데스크톱이 없습니다. 터미널 동작을 설정하려면 데스크톱을 페어링하세요.",
|
||||
"restoreAfterMinutes_one": "{{count}}분 후",
|
||||
"restoreAfterMinutes_other": "{{count}}분 후",
|
||||
"restoreAfterSeconds_one": "{{count}}초 후",
|
||||
"restoreAfterSeconds_other": "{{count}}초 후",
|
||||
"restoreHost": "{{name}} 복원",
|
||||
"smallerText": "작게 (75%)",
|
||||
"smallestText": "가장 작게 (50%)",
|
||||
"textSize": "텍스트 크기",
|
||||
"textSizeDescription": "터미널 텍스트 크기를 조절합니다. 크기를 줄이면 좌우 여백을 두고 더 많은 열이 표시되고, 크기를 키우면 표시되는 열이 줄어듭니다. 옆으로 드래그하면 가로로 이동할 수 있습니다. 터미널에서 두 손가락을 오므리거나 벌려 확대·축소해도 이 설정이 바뀝니다. 기기별 화면 표시에만 적용되며 데스크톱 터미널은 바뀌지 않습니다.",
|
||||
"textSizeHeading": "텍스트 크기",
|
||||
"textSizePickerTitle": "터미널 텍스트 크기",
|
||||
"title": "터미널"
|
||||
}
|
||||
},
|
||||
"common": {
|
||||
"back": "뒤로"
|
||||
"back": "뒤로",
|
||||
"cancel": "취소",
|
||||
"continue": "계속",
|
||||
"copied": "복사됨",
|
||||
"delete": "삭제",
|
||||
"ok": "확인",
|
||||
"remove": "제거",
|
||||
"retry": "다시 시도",
|
||||
"save": "저장",
|
||||
"settings": "설정",
|
||||
"unknown": "알 수 없음"
|
||||
},
|
||||
"connection": {
|
||||
"connectionHealth": {
|
||||
"checkConnection": "연결 상태를 확인하세요.",
|
||||
"connectFailed": "연결할 수 없음",
|
||||
"connected": "연결됨",
|
||||
"connecting": "연결 중…",
|
||||
"connectingViaRelay": "Relay를 통해 연결 중…",
|
||||
"desktopUnreachable": "데스크톱에 연결할 수 없음",
|
||||
"disconnected": "연결 끊김",
|
||||
"hostFallback": "호스트",
|
||||
"hostOffline": "{{host}} 오프라인",
|
||||
"hostOfflineDetail": "컴퓨터가 절전 모드가 아닌지, Orca가 실행 중인지, 로그인되어 있는지 확인하세요.",
|
||||
"labelWithHint": "{{label}} — {{hint}}",
|
||||
"pairingInvalid": "페어링이 유효하지 않습니다. 데스크톱과 다시 페어링하세요.",
|
||||
"rePairDetail": "데스크톱과 다시 페어링하세요.",
|
||||
"reconnecting": "다시 연결 중…",
|
||||
"relayAccessExpired": "{{host}}의 Relay 접근 권한 만료",
|
||||
"relayConnectFailed": "Relay를 통해 연결할 수 없음",
|
||||
"relayUnreachable": "Relay에 연결할 수 없음",
|
||||
"signInDetail": "다시 연결하려면 데스크톱에서 Orca에 로그인하세요.",
|
||||
"signInRequired": "{{host}}에서 로그인 필요",
|
||||
"tailscaleHint": "Tailscale 확인"
|
||||
},
|
||||
"connectionPathLabel": {
|
||||
"directLan": "직접 연결 · LAN",
|
||||
"directTailscale": "직접 연결 · Tailscale",
|
||||
"directViaLan": "LAN으로 직접 연결",
|
||||
"directViaTailscale": "Tailscale로 직접 연결"
|
||||
},
|
||||
"hostEndpoint": {
|
||||
"addressRequired": "호스트 주소를 입력하세요.",
|
||||
"hostnameRequired": "호스트 이름이 없습니다.",
|
||||
"invalidAddress": "유효한 주소가 아닙니다.",
|
||||
"invalidHostname": "유효한 호스트 이름이 아닙니다.",
|
||||
"invalidPort": "포트는 1–65535 사이여야 합니다.",
|
||||
"invalidScheme": "ws://, wss:// 또는 host:port 형식을 사용하세요.",
|
||||
"pathOrQueryNotAllowed": "호스트 주소에는 경로나 쿼리를 포함할 수 없습니다."
|
||||
},
|
||||
"pairConfirmState": {
|
||||
"invalidCode": "유효한 페어링 코드가 아닙니다.",
|
||||
"missingCode": "페어링 코드가 없습니다."
|
||||
},
|
||||
"rpcClientStreamRegistry": {
|
||||
"endedBeforeReady": "스트리밍 요청이 준비되기 전에 종료되었습니다.",
|
||||
"interrupted": "연결이 중단되었습니다"
|
||||
},
|
||||
"rpcClientTerminalBinaryFrame": {
|
||||
"unsupportedFrame": "지원하지 않는 터미널 스트림 프레임입니다(opcode {{opcode}})."
|
||||
}
|
||||
},
|
||||
"diagnostics": {
|
||||
"connectionDiagnosticsView": {
|
||||
"authFailed": "인증 실패",
|
||||
"connected": "연결됨",
|
||||
"connecting": "연결 중",
|
||||
"copyReport": "보고서 복사",
|
||||
"diagnosisHeading": "추정 원인",
|
||||
"disconnected": "연결 끊김",
|
||||
"handshaking": "핸드셰이크 중",
|
||||
"noEvents": "아직 연결 이벤트가 없습니다. 앱이 이 호스트에 연결을 시도하면 이벤트가 표시됩니다.",
|
||||
"noHosts": "페어링된 호스트가 없습니다.",
|
||||
"privacyHint": "호스트 이름, 엔드포인트, 버전, 연결 상태, 이벤트를 포함하되 민감한 정보를 가리고 크기를 제한한 보고서를 전송합니다. 터미널 내용이나 인증 정보는 절대 포함하지 않습니다.",
|
||||
"reconnecting": "재연결 중",
|
||||
"retrySending": "다시 보내기",
|
||||
"sendDiagnostics": "Orca에 진단 정보 보내기",
|
||||
"sending": "전송 중…",
|
||||
"sent": "진단 정보를 보냈습니다",
|
||||
"stateWithAttempt": "{{state}} · {{attempts}}번째 시도",
|
||||
"title": "네트워크 진단"
|
||||
},
|
||||
"mobileWebBundleProbeRow": {
|
||||
"assets": "에셋",
|
||||
"build": "빌드",
|
||||
"bundle": "번들",
|
||||
"bytes": "바이트",
|
||||
"connecting": "연결 중…",
|
||||
"elapsed": "경과 시간",
|
||||
"elapsedMilliseconds": "{{elapsedMs}}밀리초",
|
||||
"fetchBundle": "모바일 웹 번들 가져오기",
|
||||
"fetching": "번들 가져오는 중…",
|
||||
"host": "호스트",
|
||||
"unknownHost": "알 수 없는 호스트"
|
||||
},
|
||||
"mobileWebShellUpdateFailureRow": {
|
||||
"title": "워크스페이스 업데이트"
|
||||
},
|
||||
"troubleshootCommonIssues": {
|
||||
"allowNotifications": "시스템 설정에서 Orca 알림이 허용되어 있고 집중 모드나 방해 금지 모드가 꺼져 있는지 확인하세요.",
|
||||
"checkTailnet": "데스크톱이 절전 모드가 아니며 tailnet에 연결된 상태로 표시되는지 확인하세요.",
|
||||
"desktopTitle": "데스크톱 앱이 실행되지 않음",
|
||||
"disableVpn": "해당 VPN을 끄거나 분할 터널링 / \"LAN 허용\"을 켜세요.",
|
||||
"firewallTitle": "방화벽에서 포트 6768 차단",
|
||||
"keepTailscaleOn": "100.x.x.x나 *.ts.net 같은 호스트 주소는 Tailscale을 통해 연결되므로 Tailscale을 켜 두세요.",
|
||||
"macosFirewall": "macOS: 시스템 설정 → 네트워크 → 방화벽에서 Orca를 허용하세요.",
|
||||
"notificationNetwork": "모바일 데이터나 다른 Wi-Fi 네트워크를 사용해 보세요. 전환 후 알림이 도착한다면 기존 네트워크에서 알림 전달이 지연되고 있을 수 있습니다.",
|
||||
"notificationsTitle": "푸시 알림",
|
||||
"openDesktop": "연결을 받으려면 데스크톱에서 Orca가 실행 중이어야 합니다.",
|
||||
"pairAfterUpdate": "업데이트 후에는 QR 코드로 다시 페어링해야 할 수 있습니다.",
|
||||
"reconnectWifi": "두 기기에서 WiFi를 다시 연결해 보세요.",
|
||||
"restartBothApps": "시간 초과가 계속되면 두 앱을 모두 다시 시작하세요.",
|
||||
"restartDesktop": "Orca를 다시 시작해 보세요. 앱 실행 시 모바일 연동 서버가 시작됩니다.",
|
||||
"restartTailscale": "iOS/Android에서 별도 알림 없이 터널이 멈출 수 있습니다. Tailscale 앱에서 Tailscale을 껐다가 다시 켜세요.",
|
||||
"restrictedNetwork": "회사나 학교 네트워크는 P2P를 차단할 수 있습니다. 개인용 핫스팟을 사용해 보세요.",
|
||||
"retryHost": "호스트 목록으로 돌아가 호스트를 탭하여 다시 시도하세요.",
|
||||
"sameNetwork": "두 기기는 같은 LAN에 연결되어 있어야 합니다(Tailscale로 연결한 경우 제외).",
|
||||
"sameSubnet": "이더넷과 WiFi는 같은 서브넷을 사용해야 합니다.",
|
||||
"tailscaleTitle": "Tailscale 호스트에 연결할 수 없음",
|
||||
"timeoutTitle": "연결 시간 초과",
|
||||
"updateTailscale": "Tailscale 앱을 업데이트하세요. 최근 버전에는 재연결 버그 수정 사항이 포함되어 있습니다.",
|
||||
"vpnRouting": "Tailscale 이외의 VPN은 로컬 트래픽을 원격 서버로 우회시킬 수 있습니다.",
|
||||
"vpnTitle": "다른 VPN의 간섭",
|
||||
"wifiSignal": "휴대폰의 WiFi 신호 강도를 확인하세요.",
|
||||
"wifiTitle": "서로 다른 WiFi 네트워크",
|
||||
"windowsFirewall": "Windows: Defender 방화벽 → 앱 허용에서 개인 네트워크에 대해 Orca를 허용하세요."
|
||||
},
|
||||
"troubleshootView": {
|
||||
"commonIssues": "일반적인 문제",
|
||||
"runAgain": "다시 실행",
|
||||
"runDiagnostics": "진단 실행",
|
||||
"running": "실행 중…",
|
||||
"title": "문제 해결",
|
||||
"viewNetworkDiagnostics": "네트워크 진단 보기"
|
||||
},
|
||||
"useTroubleshootDiagnostics": {
|
||||
"connected": "연결됨",
|
||||
"hostDataFailed": "호스트 데이터를 읽지 못했습니다.",
|
||||
"hostTestFailed": "테스트하지 못했습니다.",
|
||||
"hosts": "호스트",
|
||||
"internet": "인터넷",
|
||||
"noConnection": "연결 안 됨",
|
||||
"noPairedHosts": "없음 — QR 코드를 스캔해 페어링하세요.",
|
||||
"pairedCount": "{{count}}개 페어링됨",
|
||||
"pairedHosts": "페어링된 호스트",
|
||||
"platform": "플랫폼",
|
||||
"reachable": "{{endpoint}}에 연결 가능",
|
||||
"unexpectedResponse": "예상치 못한 응답"
|
||||
},
|
||||
"webShellUpdateFailureCopy": {
|
||||
"failedAgo": "{{hostName}}에서 최신 업데이트를 받는 데 {{time}} 전에 실패했습니다: {{reason}}{{offered}}. {{outcome}}",
|
||||
"failedJustNow": "{{hostName}}에서 최신 업데이트를 받는 데 방금 실패했습니다: {{reason}}{{offered}}. {{outcome}}"
|
||||
}
|
||||
},
|
||||
"dictation": {
|
||||
"setup": {
|
||||
"alreadyActive": "컴퓨터나 다른 휴대폰에서 이미 받아쓰기를 사용 중입니다.",
|
||||
"cloudModelNeedsKey": "OpenAI 키 필요",
|
||||
"deleteFailed": "모델을 삭제하지 못했습니다.",
|
||||
"desktopUpdateRequired": "모바일 음성 설정을 사용하려면 페어링된 데스크톱의 Orca 앱을 업데이트하세요.",
|
||||
"downloadFailed": "다운로드를 시작하지 못했습니다.",
|
||||
"loadFailed": "받아쓰기 모델을 로드하지 못했습니다.",
|
||||
"repairRequired": "받아쓰기를 사용하려면 이 휴대폰을 컴퓨터와 다시 페어링하세요.",
|
||||
"serverUpdateRequired": "음성 받아쓰기를 사용하려면 이 서버의 Orca를 업데이트하세요.",
|
||||
"sessionLost": "받아쓰기 중 컴퓨터와의 연결이 끊어졌습니다. 다시 시도하세요.",
|
||||
"updateFailed": "받아쓰기 설정을 업데이트하지 못했습니다."
|
||||
}
|
||||
},
|
||||
"home": {
|
||||
"accountUsageCards": {
|
||||
"sectionHeading": "계정 사용량",
|
||||
"sessionWindow": "5시간",
|
||||
"systemDefault": "시스템 기본값",
|
||||
"weeklyWindow": "7일"
|
||||
},
|
||||
"appUpdateCard": {
|
||||
"dismissAccessibilityLabel": "Orca {{version}} 업데이트 알림 닫기",
|
||||
"title": "Orca {{version}} 업데이트 가능",
|
||||
"updateAccessibilityLabel": "Orca {{version}} 업데이트가 있습니다. 탭하여 업데이트하세요.",
|
||||
"updateHint": "탭하여 업데이트하세요"
|
||||
},
|
||||
"emptyState": {
|
||||
"connectedDescription": "데스크톱이 여기에 표시됩니다. 모든 데이터는 종단 간 암호화로 보호됩니다.",
|
||||
"connectedTitle": "연결 완료",
|
||||
"description": "컴퓨터의 Orca와 페어링하여 에이전트 상태를 확인하고, 원하는 터미널에 접속하고, 휴대폰으로 작업을 진행하세요.",
|
||||
"openDesktopDescription": "설정 → 모바일에서 페어링 QR 코드를 생성하세요.",
|
||||
"openDesktopTitle": "Orca 데스크톱 앱 열기",
|
||||
"pairDesktop": "데스크탑 페어링",
|
||||
"scanCodeDescription": "위의 버튼을 탭해 스캐너를 여세요. 데스크톱 화면의 QR 코드에 카메라를 맞추세요.",
|
||||
"scanCodeTitle": "코드 스캔",
|
||||
"stepsHeading": "사용 방법",
|
||||
"title": "데스크톱을 연결하세요"
|
||||
},
|
||||
"listFooter": {
|
||||
"hostPickerTitle": "작업을 열 호스트",
|
||||
"resumeHeading": "재개",
|
||||
"tasksHeading": "작업"
|
||||
},
|
||||
"listHeader": {
|
||||
"agentTime": "Agent 시간",
|
||||
"agentsSpawned": "시작된 Agents",
|
||||
"daysHours": "{{days}}일 {{hours}}시간",
|
||||
"desktopsHeading": "데스크탑",
|
||||
"hoursMinutes": "{{hours}}시간 {{minutes}}분",
|
||||
"minutes": "{{minutes}}분",
|
||||
"prsCreated": "생성된 PR",
|
||||
"title": "다시 오신 것을 환영합니다"
|
||||
},
|
||||
"screen": {
|
||||
"pairingCheckFailed": "페어링 확인 실패",
|
||||
"removeHostFailed": "호스트 제거 실패",
|
||||
"removeHostMessage": "\"{{name}}\"을(를) 제거하시겠습니까? 나중에 다시 페어링할 수 있습니다.",
|
||||
"removeHostTitle": "호스트 제거",
|
||||
"tryAgain": "다시 시도하세요."
|
||||
},
|
||||
"tasksCard": {
|
||||
"noSources": "연결된 작업 소스 없음",
|
||||
"openProviderTasks": "{{provider}} 작업 열기",
|
||||
"title": "작업"
|
||||
}
|
||||
},
|
||||
"hooks": {
|
||||
"dictationDesktopStart": {
|
||||
"recordingFailed": "마이크 녹음을 시작하지 못했습니다."
|
||||
},
|
||||
"useMobileDictation": {
|
||||
"inputClosed": "이 세션에서 더 이상 입력을 받지 않아 음성 받아쓰기가 중단되었습니다. 다시 시도하세요.",
|
||||
"microphoneInitFailed": "마이크를 초기화하지 못했습니다.",
|
||||
"microphoneInterrupted": "마이크 사용이 중단되어 음성 받아쓰기가 멈췄습니다. 다시 시도하세요.",
|
||||
"microphonePermissionDenied": "마이크 권한이 거부되었습니다.",
|
||||
"noSpeech": "음성이 감지되지 않았습니다."
|
||||
}
|
||||
},
|
||||
"host": {
|
||||
"edit": {
|
||||
"address": "주소",
|
||||
"addressHint": "IP, host:port 또는 ws:// / wss:// 형식을 사용할 수 있습니다. 포트가 없는 wss:// URL은 443을 사용합니다. 스킴과 포트 없이 주소만 입력하면 현재 포트(또는 6768)를 유지합니다.",
|
||||
"connectionPreview": "{{endpoint}}에 연결",
|
||||
"goBack": "돌아가기",
|
||||
"help": "표시 이름이나 연결 주소를 변경하세요. 이름을 비워 두면 데스크톱에서 보내는 이름을 사용합니다. 주소를 수정하면 이 휴대폰이 연결하는 주소만 바뀌며 다시 페어링하지 않습니다. 같은 데스크톱에 다른 IP로 연결할 때 사용하세요(예: 홈 LAN과 Tailscale 간 전환).",
|
||||
"hostNamePlaceholder": "호스트 이름",
|
||||
"hostRemoved": "이 호스트는 이 휴대폰에서 제거되었습니다.",
|
||||
"loadFailed": "호스트 정보를 불러오지 못했습니다.",
|
||||
"missingHost": "호스트가 없습니다.",
|
||||
"name": "이름",
|
||||
"saveFailed": "호스트를 저장하지 못했습니다.",
|
||||
"saveHost": "호스트 저장",
|
||||
"title": "호스트 편집"
|
||||
}
|
||||
},
|
||||
"hostScreen": {
|
||||
"hostListActionSheetActions": {
|
||||
"connect": "연결",
|
||||
"disconnect": "연결 해제",
|
||||
"editHost": "호스트 편집",
|
||||
"networkDiagnostics": "네트워크 진단",
|
||||
"reconnect": "다시 연결"
|
||||
},
|
||||
"hostPendingCreationRow": {
|
||||
"creatingWorkspace": "워크스페이스 만드는 중…"
|
||||
},
|
||||
"hostRailActions": {
|
||||
"accounts": "계정",
|
||||
"closeSearch": "검색 닫기",
|
||||
"floatingWorkspace": "플로팅 워크스페이스",
|
||||
"newWorkspace": "새로운 워크스페이스",
|
||||
"searchWorkspaces": "워크스페이스 검색",
|
||||
"tasks": "작업"
|
||||
},
|
||||
"hostScreenHeader": {
|
||||
"accounts": "계정",
|
||||
"activeFilters_one": "워크스페이스 옵션 (필터 {{count}}개 활성화됨)",
|
||||
"activeFilters_other": "워크스페이스 옵션 (필터 {{count}}개 활성화됨)",
|
||||
"backToHosts": "호스트 목록으로 돌아가기",
|
||||
"closeSearch": "검색 닫기",
|
||||
"floatingWorkspace": "플로팅 워크스페이스",
|
||||
"hideSidebar": "사이드바 숨기기",
|
||||
"reconnect": "다시 연결",
|
||||
"searchWorkspaces": "워크스페이스 검색",
|
||||
"tasks": "작업",
|
||||
"workspaceOptions": "워크스페이스 옵션"
|
||||
},
|
||||
"hostScreenOverlays": {
|
||||
"deleteConfirmMessage": "\"{{name}}\" ({{branch}})을(를) 삭제하시겠습니까?",
|
||||
"deleteWorktree": "작업 트리 삭제",
|
||||
"pin": "고정",
|
||||
"primaryWorktreeDeleteHint": "기본 작업 트리는 삭제할 수 없습니다. 대신 프로젝트를 제거하세요.",
|
||||
"removeHost": "호스트 제거",
|
||||
"removeHostMessage": "\"{{name}}\"을(를) 제거하시겠습니까? 나중에 다시 페어링할 수 있습니다.",
|
||||
"sleep": "슬립",
|
||||
"unpin": "고정 해제"
|
||||
},
|
||||
"hostWorkspaceList": {
|
||||
"searchPlaceholder": "워크스페이스 검색…",
|
||||
"searchWorkspaces": "워크스페이스 검색"
|
||||
},
|
||||
"workspaceDetailsSheet": {
|
||||
"checksFailing": "체크: 실패",
|
||||
"checksPassing": "체크: 통과",
|
||||
"checksPending": "체크: 대기 중",
|
||||
"closed": "닫힘",
|
||||
"createdByAutomation": "자동화가 생성함",
|
||||
"createdByNamedAutomation": "자동화 “{{name}}”이(가) 생성함",
|
||||
"createdFromCli": "Orca CLI에서 생성됨",
|
||||
"draft": "초안",
|
||||
"merged": "병합됨",
|
||||
"openLink": "{{label}} 열기",
|
||||
"openLinkWithTitle": "{{label}}, {{title}} 열기",
|
||||
"prOpen": "열림"
|
||||
},
|
||||
"workspaceOptionControls": {
|
||||
"agentActivity": "에이전트 활동",
|
||||
"agentActivityLayout": "에이전트 활동 레이아웃",
|
||||
"agentActivitySort": "Agent 활동",
|
||||
"agentActivitySortDescription": "주의가 필요한 에이전트를 우선 표시한 뒤 최근 활동순으로 정렬합니다.",
|
||||
"agentStatuses": "에이전트 상태",
|
||||
"allHosts": "모든 호스트",
|
||||
"allProjects": "모든 프로젝트",
|
||||
"automation": "자동화",
|
||||
"branchFolderPath": "브랜치 / 폴더 경로",
|
||||
"branchName": "브랜치 이름",
|
||||
"compact": "콤팩트",
|
||||
"detailed": "상세",
|
||||
"fullList": "전체 목록",
|
||||
"githubIssues": "GitHub 이슈",
|
||||
"githubTicket": "GitHub 티켓",
|
||||
"host": "호스트",
|
||||
"hover": "호버",
|
||||
"jiraIssue": "Jira 이슈",
|
||||
"jiraIssues": "Jira 이슈",
|
||||
"linearIssue": "Linear 이슈",
|
||||
"linearIssues": "Linear 이슈",
|
||||
"manual": "수동",
|
||||
"manualProjectOrderDescription": "프로젝트를 드래그하여 정렬하세요.",
|
||||
"manualWorkspaceOrderDescription": "워크스페이스를 드래그하여 각 그룹 내에서 정렬하세요.",
|
||||
"name": "이름",
|
||||
"none": "없음",
|
||||
"notes": "메모",
|
||||
"ports": "포트",
|
||||
"pr": "PR",
|
||||
"prMrLink": "PR/MR 링크",
|
||||
"project": "프로젝트",
|
||||
"recent": "최근",
|
||||
"recentProjectOrderDescription": "가장 최근의 워크스페이스 활동",
|
||||
"searchProjects": "프로젝트 검색",
|
||||
"searchProjectsPlaceholder": "프로젝트 검색…",
|
||||
"selectedCount": "{{total}}개 중 {{count}}개",
|
||||
"status": "상태"
|
||||
},
|
||||
"workspaceOptionsSheet": {
|
||||
"backToOptions": "워크스페이스 옵션으로 돌아가기",
|
||||
"cardDisplay": "카드 표시",
|
||||
"cardLayout": "카드 레이아웃",
|
||||
"clearFilters": "필터 지우기",
|
||||
"exceptDefaultBranch": "기본 브랜치는 제외",
|
||||
"filters": "필터",
|
||||
"groupBy": "그룹화 기준",
|
||||
"hideAutomationCreated": "자동화로 생성된 워크스페이스 숨기기",
|
||||
"hideCliCreated": "CLI로 생성된 워크스페이스 숨기기",
|
||||
"hideDefaultBranch": "기본 브랜치 숨기기",
|
||||
"hideDetachedHead": "분리된 HEAD 숨기기",
|
||||
"hideOtherClientWorkspaces": "다른 클라이언트 워크스페이스 숨기기",
|
||||
"hideSleeping": "슬립 중인 항목 숨기기",
|
||||
"hosts": "호스트",
|
||||
"localLinux": "로컬 Linux",
|
||||
"localMac": "로컬 Mac",
|
||||
"localWindows": "로컬 Windows",
|
||||
"projectOrder": "프로젝트 순서",
|
||||
"projects": "프로젝트",
|
||||
"savingToDesktop": "데스크톱에 저장 중",
|
||||
"show": "표시",
|
||||
"showProperties": "속성 표시",
|
||||
"sortBy": "정렬 기준",
|
||||
"syncedAcrossDevices": "기기 간 동기화됨",
|
||||
"thisComputer": "이 컴퓨터",
|
||||
"waitingForDesktop": "아직 데스크톱 응답을 기다리는 중",
|
||||
"workspaceOptions": "워크스페이스 옵션"
|
||||
}
|
||||
},
|
||||
"navigation": {
|
||||
"hostStackScreens": {
|
||||
"accounts": "계정",
|
||||
"agentSessionHistory": "에이전트 세션 기록",
|
||||
"changes": "변경 사항",
|
||||
"editHost": "호스트 편집",
|
||||
"host": "호스트",
|
||||
"pullRequest": "PR",
|
||||
"sourceControl": "소스 제어",
|
||||
"tasks": "작업",
|
||||
"terminal": "터미널",
|
||||
"workspace": "워크스페이스"
|
||||
}
|
||||
},
|
||||
"notifications": {
|
||||
"notificationDeliverySection": {
|
||||
"footer": "알림 유형은 페어링된 각 데스크톱의 알림 설정을 따릅니다. 이 앱을 7일 동안 사용하지 않으면 알림이 일시 중지됩니다. 다시 받으려면 앱을 열고 다시 연결하세요.",
|
||||
"onlyWhenAway": "데스크톱을 사용하지 않을 때만",
|
||||
"onlyWhenAwayHint": "키보드나 마우스를 3분간 사용하지 않거나 데스크톱이 잠겨 있을 때 알림을 보냅니다.",
|
||||
"sound": "알림음",
|
||||
"suppressWhileFocused": "보고 있는 동안 알림 끄기",
|
||||
"suppressWhileFocusedHint": "이 휴대폰에서 열어 둔 워크스페이스의 알림을 받지 않습니다."
|
||||
}
|
||||
},
|
||||
"onboarding": {
|
||||
"notificationOnboardingPreview": {
|
||||
"claudeNeedsInput": "Claude 입력 필요",
|
||||
"codexFinished": "Codex 작업 완료",
|
||||
"now": "지금",
|
||||
"testsPassing": "테스트를 통과했습니다.",
|
||||
"waitingOnYou": "응답을 기다리고 있습니다."
|
||||
},
|
||||
"onboardingPage": {
|
||||
"enableAgentNotifications": "에이전트 알림 켜기",
|
||||
"enableNotifications": "알림 켜기",
|
||||
"keepTerminal": "터미널 유지",
|
||||
"notNow": "나중에",
|
||||
"notificationsDescription": "앱을 사용하지 않아도 에이전트가 작업을 마치거나 대기 중이면 이 휴대폰으로 알림을 받습니다.",
|
||||
"notificationsDisclosure": "데스크톱을 3분 동안 사용하지 않으면 Orca 푸시 서비스를 통해 알림이 전달됩니다. 언제든지 설정에서 변경할 수 있습니다.",
|
||||
"notificationsTitle": "에이전트의 요청을 놓치지 마세요",
|
||||
"openInChatUi": "채팅 UI에서 세션 열기",
|
||||
"openInTerminal": "터미널에서 세션 열기",
|
||||
"sessionViewDescription": "지원되는 에이전트 세션을 이 기기에서 터미널로 열지, 채팅 UI로 열지 선택하세요. 세션 탭을 길게 눌러 보기를 전환하거나, 나중에 설정에서 기본값을 변경할 수 있습니다.",
|
||||
"sessionViewTitle": "세션을 어떻게 열까요?",
|
||||
"skipNotifications": "지금은 알림 설정 건너뛰기",
|
||||
"useChatUi": "채팅 UI 사용"
|
||||
}
|
||||
},
|
||||
"settings": {
|
||||
"aboutScreen": {
|
||||
"openLinkFailed": "링크를 열지 못했습니다. 다시 시도하세요.",
|
||||
"socialProfile": "Orca X 프로필",
|
||||
"sourceCode": "Orca 소스 코드",
|
||||
"tagline": "100배의 성과를 내는 개발자를 위한 오픈 소스 에이전트 IDE",
|
||||
"title": "정보",
|
||||
"website": "Orca 웹사이트"
|
||||
},
|
||||
"appUpdateSettingsRows": {
|
||||
"checkFailed": "확인 실패",
|
||||
"checkUpdates": "업데이트 확인",
|
||||
"checkUpdatesStatus": "업데이트 확인, {{status}}",
|
||||
"checking": "확인 중…",
|
||||
"installedVersion": "버전 {{version}}",
|
||||
"lastCheckedDays_one": "마지막 확인: {{count}}일 전",
|
||||
"lastCheckedDays_other": "마지막 확인: {{count}}일 전",
|
||||
"lastCheckedHours_one": "마지막 확인: {{count}}시간 전",
|
||||
"lastCheckedHours_other": "마지막 확인: {{count}}시간 전",
|
||||
"lastCheckedJustNow": "마지막 확인: 방금",
|
||||
"lastCheckedMinutes_one": "마지막 확인: {{count}}분 전",
|
||||
"lastCheckedMinutes_other": "마지막 확인: {{count}}분 전",
|
||||
"neverChecked": "확인한 적 없음",
|
||||
"upToDate": "최신 상태",
|
||||
"update": "업데이트",
|
||||
"updateToVersion": "Orca {{version}} 버전으로 업데이트"
|
||||
},
|
||||
"appearance": {
|
||||
"language": {
|
||||
"description": "시스템을 선택하면 이 기기의 언어를 따릅니다. 권한 요청은 항상 기기 언어로 표시됩니다.",
|
||||
@@ -17,6 +608,157 @@
|
||||
"system": "시스템"
|
||||
},
|
||||
"title": "모양"
|
||||
},
|
||||
"browserSettingsScreen": {
|
||||
"desktopBrowser": "데스크톱 Orca 브라우저",
|
||||
"desktopBrowserDescription": "페어링된 데스크톱에서 스트리밍되는 브라우저로 엽니다.",
|
||||
"linksDescription": "터미널 출력의 HTTP(S) 링크를 탭했을 때 열 브라우저를 선택하세요.",
|
||||
"linksHeading": "링크",
|
||||
"loadFailed": "브라우저 설정을 불러오지 못했습니다. 다시 시도하세요.",
|
||||
"openTerminalLinks": "터미널 링크 열기",
|
||||
"phoneBrowser": "휴대폰 브라우저",
|
||||
"phoneBrowserDescription": "이 휴대폰의 Safari, Chrome 또는 다른 브라우저에서 엽니다.",
|
||||
"saveFailed": "브라우저 설정을 저장하지 못했습니다. 다시 시도하세요.",
|
||||
"title": "브라우저"
|
||||
},
|
||||
"menuItems": {
|
||||
"about": "정보",
|
||||
"appearance": "모양",
|
||||
"browser": "브라우저",
|
||||
"chatUi": "채팅 UI",
|
||||
"notifications": "알림",
|
||||
"terminal": "터미널",
|
||||
"troubleshooting": "문제 해결",
|
||||
"voice": "음성"
|
||||
},
|
||||
"menuScreen": {
|
||||
"privacyPolicy": "개인정보 처리방침",
|
||||
"support": "지원"
|
||||
},
|
||||
"nativeChatSettingsScreen": {
|
||||
"defaultViewDescription": "지원되는 에이전트 세션(Claude, Codex 및 기타 채팅 지원 에이전트)을 이 기기에서 여는 방식을 선택하세요. 터미널은 CLI를 그대로 표시하며, 채팅 UI는 데스크톱 앱과 같은 채팅 인터페이스를 표시합니다. 각 세션의 보기 방식은 길게 눌러 여는 메뉴에서 개별적으로 전환할 수 있습니다.",
|
||||
"defaultViewHeading": "기본 보기",
|
||||
"off": "꺼짐",
|
||||
"on": "켜짐",
|
||||
"openInChatUi": "채팅 UI로 세션 열기",
|
||||
"title": "채팅 UI"
|
||||
},
|
||||
"nativeNotificationDeliverySettings": {
|
||||
"loadFailed": "알림 전송 설정을 불러오지 못했습니다. 다시 시도하려면 이 화면을 다시 여세요.",
|
||||
"saveFailed": "알림 전송 설정을 저장하지 못했습니다. 다시 시도하세요.",
|
||||
"updateDesktopHint": "이 휴대폰에서 알림을 받으려면 업데이트된 데스크톱을 페어링하세요."
|
||||
},
|
||||
"notificationDisplayTest": {
|
||||
"accepted": "Orca 푸시 서비스에서 요청을 접수했습니다. 알림이 도착했는지 확인하세요.",
|
||||
"connectDesktop": "데스크톱에 연결한 후 다시 시도하세요.",
|
||||
"description": "Orca 푸시 서비스를 통해 테스트 알림을 보내세요.",
|
||||
"desktopUnreachable": "데스크톱에 연결하지 못했습니다. 다시 시도하세요.",
|
||||
"loadFailed": "페어링된 데스크톱 목록을 불러오지 못했습니다.",
|
||||
"pairDesktop": "데스크톱을 페어링한 후 다시 시도하세요.",
|
||||
"pushServiceFailed": "Orca 푸시 서비스를 통해 전송하지 못했습니다. 다시 시도하세요.",
|
||||
"rateLimited": "알림이 너무 많습니다. 나중에 다시 시도하세요.",
|
||||
"reconnectPhone": "이 휴대폰을 알림 수신 기기로 등록하려면 다시 연결하세요.",
|
||||
"sendButton": "테스트 알림 보내기",
|
||||
"sendFailed": "테스트 푸시 알림을 전송하지 못했습니다.",
|
||||
"sending": "전송 중…",
|
||||
"troubleReceivingAlerts": "알림 수신에 문제가 있나요?",
|
||||
"troubleshooting": "문제 해결",
|
||||
"updateDesktop": "이 테스트를 실행하려면 데스크톱을 업데이트하세요."
|
||||
},
|
||||
"notificationSettingsScreen": {
|
||||
"description": "에이전트가 입력을 요청하거나 작업을 완료하면 이 기기에서 알림을 받으세요.",
|
||||
"enableNotifications": "알림 활성화",
|
||||
"loadFailed": "알림 설정을 불러오지 못했습니다. 다시 시도하세요.",
|
||||
"notificationsBlocked": "시스템 설정에서 알림이 비활성화되어 있습니다.",
|
||||
"openSettings": "설정 열기",
|
||||
"openSettingsFailed": "시스템 설정을 열지 못했습니다. 다시 시도하세요.",
|
||||
"saveFailed": "알림 설정을 저장하지 못했습니다. 다시 시도하세요.",
|
||||
"title": "알림"
|
||||
},
|
||||
"pendingCredentialCleanupCard": {
|
||||
"cleanupUnconfirmed_one": "이 기기의 자격 증명 {{count}}개가 정리되었는지 확인하지 못했습니다.",
|
||||
"cleanupUnconfirmed_other": "이 기기의 자격 증명 {{count}}개가 정리되었는지 확인하지 못했습니다.",
|
||||
"retryCleanup": "페어링 자격 증명 삭제 다시 시도",
|
||||
"retryFailed": "정리 완료 여부를 아직 확인하지 못했습니다. 나중에 다시 시도하세요.",
|
||||
"statusCheckFailed": "이 기기의 정리 상태를 확인하지 못했습니다. 안전을 위해 다시 시도하세요.",
|
||||
"title": "페어링 자격 증명 정리"
|
||||
},
|
||||
"uiLanguageSettingsSection": {
|
||||
"pseudoLanguage": "가상 언어 (en-XA)"
|
||||
},
|
||||
"voiceSettingsScreen": {
|
||||
"connectDesktop": "음성 설정을 관리하려면 데스크톱에 연결하세요.",
|
||||
"deleteFailed": "삭제 실패",
|
||||
"dictationDescription": "데스크톱에서 포커스가 있는 창에 음성으로 텍스트를 입력합니다.",
|
||||
"dictationHeading": "받아쓰기",
|
||||
"dictationMode": "받아쓰기 모드",
|
||||
"dictationModeDescription": "토글: 한 번 누르면 받아쓰기를 시작하고, 다시 누르면 중지합니다. 길게 누르기: 누르고 있는 동안 받아쓰기합니다.",
|
||||
"downloadFailed": "다운로드 실패",
|
||||
"enableDictation": "음성 받아쓰기 활성화",
|
||||
"holdMode": "길게 누르기",
|
||||
"loadFailed": "음성 설정을 불러오지 못했습니다.",
|
||||
"loadFailedMessage": "음성 설정을 불러오지 못했습니다.",
|
||||
"noneSelected": "선택 안 됨",
|
||||
"selectModelFailed": "모델을 선택하지 못했습니다.",
|
||||
"speechModel": "음성 모델",
|
||||
"speechModelHeading": "음성 모델",
|
||||
"title": "음성",
|
||||
"toggleMode": "토글",
|
||||
"updateFailed": "업데이트하지 못했습니다."
|
||||
}
|
||||
},
|
||||
"webShell": {
|
||||
"shellStateScreen": {
|
||||
"checkingHost": "호스트 확인 중",
|
||||
"connectToDownload": "최신 앱을 다운로드하려면 이 호스트에 연결하세요.",
|
||||
"documentLoadFailed": "다운로드한 앱을 열지 못했습니다.",
|
||||
"downloadFailed": "이 호스트에서 최신 앱을 다운로드하지 못했습니다.",
|
||||
"hostProfileUnreadable": "기기에서 이 호스트 정보를 읽지 못했습니다. 다시 시도하세요.",
|
||||
"isolationUnavailable": "이 기기의 WebView 버전이 너무 오래되어 워크스페이스를 안전하게 열 수 없습니다.",
|
||||
"openingWorkspace": "워크스페이스 여는 중",
|
||||
"renderProcessGone": "워크스페이스가 응답하지 않습니다.",
|
||||
"statusUnreadable": "이 호스트의 상태를 읽지 못했습니다. 뒤로 돌아가 다시 열어 보세요.",
|
||||
"tryAgain": "다시 시도"
|
||||
}
|
||||
},
|
||||
"worktree": {
|
||||
"agentRowDisplay": {
|
||||
"blocked": "차단됨",
|
||||
"days_one": "{{count}}일",
|
||||
"days_other": "{{count}}일",
|
||||
"done": "완료",
|
||||
"failed": "실패",
|
||||
"hours_one": "{{count}}시간",
|
||||
"hours_other": "{{count}}시간",
|
||||
"idle": "유휴",
|
||||
"interrupted": "중단됨",
|
||||
"justNow": "방금",
|
||||
"minutes_one": "{{count}}분",
|
||||
"minutes_other": "{{count}}분",
|
||||
"monitoring": "백그라운드 작업 모니터링 중",
|
||||
"stopping": "중지하는 중…",
|
||||
"unconfirmed": "확인하지 못함",
|
||||
"unverifiable": "최근 업데이트 없음",
|
||||
"waiting": "입력 대기 중",
|
||||
"working": "작업 중"
|
||||
},
|
||||
"hostWorkspaceListStates": {
|
||||
"checkHost": "컴퓨터가 절전 모드가 아니며 Orca가 실행 중인지 확인하세요.",
|
||||
"empty": "워크스페이스가 없습니다.",
|
||||
"loadFailed": "이 호스트에서 워크스페이스를 불러오지 못했습니다.",
|
||||
"noFilterMatches": "필터에 맞는 워크스페이스가 없습니다.",
|
||||
"noMatches": "일치하는 워크스페이스가 없습니다.",
|
||||
"reopen": "업데이트하려면 다시 여세요",
|
||||
"reopenDetail": "이 페이지는 아직 이 호스트의 워크스페이스 목록 형식을 지원하지 않습니다.",
|
||||
"retrying": "worktree.ps가 실패했습니다({{error}}). 자동으로 다시 시도합니다."
|
||||
},
|
||||
"workspaceListSections": {
|
||||
"all": "모두",
|
||||
"closed": "닫힘",
|
||||
"done": "완료",
|
||||
"inProgress": "진행중",
|
||||
"inReview": "리뷰 중",
|
||||
"pinned": "고정됨"
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,8 +1,607 @@
|
||||
{
|
||||
"accounts": {
|
||||
"screen": {
|
||||
"agentLogin": "Usar o login do próprio agente",
|
||||
"connecting": "Conectando ao host…",
|
||||
"connectingTo": "Conectando a {{name}}…",
|
||||
"default": "Padrão do sistema",
|
||||
"hostNotFound": "Host não encontrado",
|
||||
"invalidSnapshot": "Dados de contas inválidos recebidos do host",
|
||||
"loadingAccounts": "Carregando contas…",
|
||||
"manageAccountsHint": "Adicione contas ou autentique-as novamente em Configurações → Contas no desktop.",
|
||||
"refreshAccounts": "Atualizar contas",
|
||||
"sessionWindow": "5h",
|
||||
"switchFailed": "Não foi possível trocar de conta",
|
||||
"title": "Contas",
|
||||
"useReset": "Redefinir agora",
|
||||
"weeklyWindow": "7d"
|
||||
}
|
||||
},
|
||||
"agentHistory": {
|
||||
"panel": {
|
||||
"all": "Todos",
|
||||
"emptyScope": "Nenhuma sessão anterior de agente neste escopo.",
|
||||
"loadFailed": "Não foi possível carregar",
|
||||
"missingResumeId": "Esta sessão não tem um ID de retomada.",
|
||||
"noSearchResults": "Nenhuma sessão corresponde à sua busca.",
|
||||
"noSessions": "Nenhuma sessão de agente",
|
||||
"placeholder": "Buscar sessões, repo:, path:",
|
||||
"project": "Projeto",
|
||||
"refreshSessions": "Atualizar sessões de agente",
|
||||
"resumeFailed": "Falha ao retomar a sessão.",
|
||||
"sessionQueued": "Sessão do agente adicionada à fila.",
|
||||
"skipped_many": "{{count}} transcrições ignoradas",
|
||||
"skipped_one": "{{count}} transcrição ignorada",
|
||||
"skipped_other": "{{count}} transcrições ignoradas",
|
||||
"title": "Histórico de Sessões de Agente",
|
||||
"unavailable": "Histórico de Sessões de Agente Indisponível",
|
||||
"unknownHostPlatform": "Não foi possível determinar a plataforma do host.",
|
||||
"updateHost": "Atualize o Orca neste host para consultar o histórico de sessões de agente.",
|
||||
"waitingForHost": "Aguardando host…",
|
||||
"workspace": "Espaço de Trabalho"
|
||||
},
|
||||
"resumeTarget": {
|
||||
"differentHost": "Esta sessão pertence a um host diferente. Abra um espaço de trabalho no mesmo host para retomá-la.",
|
||||
"openWorkspace": "Abra um espaço de trabalho antes de retomar uma sessão.",
|
||||
"runtimeUnavailable": "Não é possível retomar sessões pelo histórico em espaços de trabalho hospedados em ambientes de execução."
|
||||
},
|
||||
"sessionCard": {
|
||||
"untitledSession": "Sessão sem título"
|
||||
},
|
||||
"sessionHistoryList": {
|
||||
"assistantRole": "assistente",
|
||||
"currentWorktree": "worktree atual",
|
||||
"messages_many": "{{count}} mensagens",
|
||||
"messages_one": "{{count}} mensagem",
|
||||
"messages_other": "{{count}} mensagens",
|
||||
"resumeSession": "Retomar sessão de agente",
|
||||
"userRole": "usuário"
|
||||
},
|
||||
"useAgentHistoryState": {
|
||||
"hostUnreachable": "Não foi possível acessar o host",
|
||||
"loadFailed": "Não foi possível carregar as sessões de agente",
|
||||
"waitingForHost": "Aguardando host…"
|
||||
},
|
||||
"worktreeNavigationActions": {
|
||||
"agentSessionHistory": "Histórico de Sessões de Agente",
|
||||
"sourceControl": "Controle de Código-Fonte"
|
||||
}
|
||||
},
|
||||
"app": {
|
||||
"mobileOnboarding": {
|
||||
"notificationSettingsFailed": "Não foi possível atualizar as configurações de notificações. Tente novamente.",
|
||||
"progress": "Progresso da integração",
|
||||
"saveChoiceFailed": "Não foi possível salvar sua escolha. Tente novamente.",
|
||||
"stepProgress": "Etapa {{step}} de {{total}}"
|
||||
},
|
||||
"notifications": {
|
||||
"description": "Receba alertas dos agentes mesmo com o app fechado. Enviados pelo serviço de notificações push do Orca e pela Apple ou pelo Google."
|
||||
},
|
||||
"pair": {
|
||||
"backToHome": "Voltar ao início",
|
||||
"missingCode": "Código de pareamento ausente"
|
||||
},
|
||||
"pairConfirm": {
|
||||
"backToHome": "Voltar ao início",
|
||||
"connectTimedOut": "Não foi possível conectar em {{seconds}}s — veja no log abaixo em que etapa a conexão parou",
|
||||
"connecting": "Conectando…",
|
||||
"description": "Você abriu um link de pareamento do seu desktop. Confirme para adicioná-lo aos seus hosts.",
|
||||
"pairButton": "Parear",
|
||||
"pairingFailed": "Falha no pareamento: {{error}}",
|
||||
"pairingLog": "Log de pareamento",
|
||||
"title": "Parear com este desktop?"
|
||||
},
|
||||
"pairScan": {
|
||||
"cameraDisabledDescription": "Ative o acesso à câmera nas Configurações ou cole o código de pareamento.",
|
||||
"cameraDisabledTitle": "Acesso à câmera desativado",
|
||||
"cameraPermissionDescription": "Escaneie o código QR do Orca no seu desktop ou cole o código de pareamento.",
|
||||
"connectTimedOut": "Não foi possível conectar em {{seconds}}s — veja no log abaixo em que etapa a conexão parou",
|
||||
"connecting": "Conectando…",
|
||||
"invalidPairingCode": "Código de pareamento inválido — copie-o do seu computador e cole novamente",
|
||||
"invalidQrCode": "Código QR do Orca inválido",
|
||||
"openDesktopStep": "Abra o Orca no seu computador",
|
||||
"openMobileSettingsStep": "Vá para Configurações → Mobile",
|
||||
"openSettings": "Abrir Configurações",
|
||||
"pairingFailed": "Falha no pareamento: {{error}}",
|
||||
"pairingLog": "Log de pareamento",
|
||||
"pasteCodeMessage": "Copie o código que aparece abaixo do QR no seu computador.",
|
||||
"pasteCodePlaceholder": "orca://pair?code=... ou cole o código",
|
||||
"pasteCodeTitle": "Colar código de pareamento",
|
||||
"pasteInstead": "Colar código em vez disso",
|
||||
"pastePairingCode": "Ou colar o código de pareamento",
|
||||
"scanCodeStep": "Escaneie o código QR",
|
||||
"title": "Parear com desktop",
|
||||
"tryAgain": "Tentar novamente"
|
||||
},
|
||||
"terminalSettings": {
|
||||
"autocomplete": "Preenchimento e correção automáticos",
|
||||
"autocompleteOff": "Desativados",
|
||||
"autocompleteOn": "Ativados",
|
||||
"defaultText": "Padrão (100%)",
|
||||
"keepPhoneSize": "Manter no tamanho do celular (padrão)",
|
||||
"keyboardInputDescription": "O preenchimento automático, a correção automática e as sugestões ortográficas só se aplicam à entrada de comandos em buffer. Use o botão com seta dupla acima do teclado para sair da entrada em tempo real e editar seu comando antes de tocar em Enviar. Desativados por padrão para evitar alterações em comandos, opções ou caminhos.",
|
||||
"keyboardInputHeading": "ENTRADA PELO TECLADO",
|
||||
"largeText": "Grande (125%)",
|
||||
"largerText": "Maior (150%)",
|
||||
"largestText": "Máximo (200%)",
|
||||
"leaveAppDescription": "Enquanto você usa um terminal no celular, o Orca o reduz para caber na tela. Ao fechar o app ou alternar para outro, esta opção define se o terminal mantém o tamanho do celular (evitando que ferramentas de CLI interativas reorganizem o conteúdo) ou volta ao tamanho do desktop. Você pode usar Restaurar no banner do desktop a qualquer momento para redimensioná-lo manualmente.",
|
||||
"leaveAppHeading": "AO SAIR DO APP",
|
||||
"noPairedDesktops": "Nenhum desktop pareado ainda. Pareie um para controlar o comportamento do terminal.",
|
||||
"restoreAfterMinutes_many": "Após {{count}} minutos",
|
||||
"restoreAfterMinutes_one": "Após {{count}} minuto",
|
||||
"restoreAfterMinutes_other": "Após {{count}} minutos",
|
||||
"restoreAfterSeconds_many": "Após {{count}}s",
|
||||
"restoreAfterSeconds_one": "Após {{count}}s",
|
||||
"restoreAfterSeconds_other": "Após {{count}}s",
|
||||
"restoreHost": "Restaurar {{name}}",
|
||||
"smallerText": "Menor (75%)",
|
||||
"smallestText": "Mínimo (50%)",
|
||||
"textSize": "Tamanho do texto",
|
||||
"textSizeDescription": "Ajuste o tamanho do texto do terminal. Tamanhos menores exibem mais colunas com margens laterais; tamanhos maiores exibem menos colunas — arraste para os lados para navegar. Você também pode usar o gesto de pinça no próprio terminal para ajustar o zoom e atualizar esta configuração. Afeta apenas a exibição neste dispositivo; não altera o terminal no desktop.",
|
||||
"textSizeHeading": "TAMANHO DO TEXTO",
|
||||
"textSizePickerTitle": "Tamanho do texto do terminal",
|
||||
"title": "Terminal"
|
||||
}
|
||||
},
|
||||
"common": {
|
||||
"back": "Voltar"
|
||||
"back": "Voltar",
|
||||
"cancel": "Cancelar",
|
||||
"continue": "Continuar",
|
||||
"copied": "Copiado",
|
||||
"delete": "Excluir",
|
||||
"ok": "OK",
|
||||
"remove": "Remover",
|
||||
"retry": "Tentar novamente",
|
||||
"save": "Salvar",
|
||||
"settings": "Configurações",
|
||||
"unknown": "Desconhecido"
|
||||
},
|
||||
"connection": {
|
||||
"connectionHealth": {
|
||||
"checkConnection": "Verifique sua conexão",
|
||||
"connectFailed": "Não foi possível conectar",
|
||||
"connected": "Conectado",
|
||||
"connecting": "Conectando…",
|
||||
"connectingViaRelay": "Conectando via Relay…",
|
||||
"desktopUnreachable": "Não foi possível acessar o desktop",
|
||||
"disconnected": "Desconectado",
|
||||
"hostFallback": "Host",
|
||||
"hostOffline": "{{host}} está off-line",
|
||||
"hostOfflineDetail": "Verifique se o computador não está suspenso, se o Orca está em execução e se você entrou na sua conta",
|
||||
"labelWithHint": "{{label}} — {{hint}}",
|
||||
"pairingInvalid": "Pareamento inválido — pareie novamente com seu desktop",
|
||||
"rePairDetail": "Pareie novamente com seu desktop",
|
||||
"reconnecting": "Reconectando…",
|
||||
"relayAccessExpired": "Acesso ao Relay expirado para {{host}}",
|
||||
"relayConnectFailed": "Não foi possível conectar via Relay",
|
||||
"relayUnreachable": "Não foi possível acessar o Relay",
|
||||
"signInDetail": "Entre no Orca no seu desktop para se reconectar",
|
||||
"signInRequired": "Login necessário em {{host}}",
|
||||
"tailscaleHint": "verifique o Tailscale"
|
||||
},
|
||||
"connectionPathLabel": {
|
||||
"directLan": "Direta · LAN",
|
||||
"directTailscale": "Direta · Tailscale",
|
||||
"directViaLan": "Direta via LAN",
|
||||
"directViaTailscale": "Direta via Tailscale"
|
||||
},
|
||||
"hostEndpoint": {
|
||||
"addressRequired": "Digite o endereço de um host.",
|
||||
"hostnameRequired": "Nome de host ausente.",
|
||||
"invalidAddress": "Endereço inválido.",
|
||||
"invalidHostname": "Nome de host inválido.",
|
||||
"invalidPort": "A porta deve estar entre 1 e 65535.",
|
||||
"invalidScheme": "Use ws:// ou wss:// (ou host:port).",
|
||||
"pathOrQueryNotAllowed": "O host não deve incluir caminho nem parâmetros de consulta."
|
||||
},
|
||||
"pairConfirmState": {
|
||||
"invalidCode": "Código de pareamento inválido",
|
||||
"missingCode": "Código de pareamento ausente"
|
||||
},
|
||||
"rpcClientStreamRegistry": {
|
||||
"endedBeforeReady": "A solicitação de transmissão foi encerrada antes de ficar pronta.",
|
||||
"interrupted": "Conexão interrompida"
|
||||
},
|
||||
"rpcClientTerminalBinaryFrame": {
|
||||
"unsupportedFrame": "Quadro de transmissão do terminal não suportado (opcode {{opcode}})."
|
||||
}
|
||||
},
|
||||
"diagnostics": {
|
||||
"connectionDiagnosticsView": {
|
||||
"authFailed": "falha na autenticação",
|
||||
"connected": "conectado",
|
||||
"connecting": "conectando",
|
||||
"copyReport": "Copiar relatório",
|
||||
"diagnosisHeading": "O que isso sugere",
|
||||
"disconnected": "desconectado",
|
||||
"handshaking": "negociando conexão",
|
||||
"noEvents": "Ainda não há eventos de conexão. Os eventos aparecem à medida que o app tenta se conectar a este host.",
|
||||
"noHosts": "Nenhum host pareado.",
|
||||
"privacyHint": "Envia um relatório de tamanho limitado com dados sensíveis ocultados, incluindo nome do host, endereço de conexão, versões, estado da conexão e eventos — nunca o conteúdo do terminal ou credenciais.",
|
||||
"reconnecting": "reconectando",
|
||||
"retrySending": "Tentar enviar novamente",
|
||||
"sendDiagnostics": "Enviar diagnóstico ao Orca",
|
||||
"sending": "Enviando…",
|
||||
"sent": "Diagnóstico enviado",
|
||||
"stateWithAttempt": "{{state}} · tentativa {{attempts}}",
|
||||
"title": "Diagnóstico de rede"
|
||||
},
|
||||
"mobileWebBundleProbeRow": {
|
||||
"assets": "Recursos",
|
||||
"build": "Build",
|
||||
"bundle": "Pacote",
|
||||
"bytes": "Bytes",
|
||||
"connecting": "Conectando…",
|
||||
"elapsed": "Tempo decorrido",
|
||||
"elapsedMilliseconds": "{{elapsedMs}} ms",
|
||||
"fetchBundle": "Obter pacote web para celular",
|
||||
"fetching": "Obtendo pacote…",
|
||||
"host": "Host",
|
||||
"unknownHost": "host desconhecido"
|
||||
},
|
||||
"mobileWebShellUpdateFailureRow": {
|
||||
"title": "Atualizações de espaços de trabalho"
|
||||
},
|
||||
"troubleshootCommonIssues": {
|
||||
"allowNotifications": "Verifique se as configurações do sistema permitem notificações do Orca e se o modo Foco ou Não Perturbe está desativado.",
|
||||
"checkTailnet": "Verifique se o desktop está fora do modo de suspensão e aparece como conectado na sua tailnet.",
|
||||
"desktopTitle": "App desktop não está em execução",
|
||||
"disableVpn": "Desative essa VPN ou ative o tunelamento dividido / \"Permitir LAN\".",
|
||||
"firewallTitle": "Firewall bloqueando a porta 6768",
|
||||
"keepTailscaleOn": "Hosts com endereços como 100.x.x.x ou *.ts.net se conectam via Tailscale — mantenha-o ATIVADO.",
|
||||
"macosFirewall": "macOS: Ajustes do Sistema → Rede → Firewall — permita o Orca.",
|
||||
"notificationNetwork": "Tente usar os dados móveis ou outra rede Wi-Fi. Se os alertas chegarem após a troca, sua rede pode estar atrasando a entrega.",
|
||||
"notificationsTitle": "Notificações push",
|
||||
"openDesktop": "O Orca precisa estar aberto no seu desktop para aceitar conexões.",
|
||||
"pairAfterUpdate": "Após uma atualização, pode ser necessário parear novamente usando um código QR.",
|
||||
"reconnectWifi": "Tente reconectar o Wi-Fi nos dois dispositivos.",
|
||||
"restartBothApps": "Reinicie os dois apps se os erros de tempo limite persistirem.",
|
||||
"restartDesktop": "Tente reiniciar o Orca — o servidor de conexão com o celular inicia junto com o app.",
|
||||
"restartTailscale": "O iOS e o Android podem travar o túnel sem aviso: desative e reative o Tailscale no app Tailscale.",
|
||||
"restrictedNetwork": "Redes corporativas ou escolares podem bloquear P2P — tente usar um ponto de acesso pessoal.",
|
||||
"retryHost": "Volte à lista de hosts e toque no seu host para tentar novamente.",
|
||||
"sameNetwork": "Os dois dispositivos devem estar na mesma LAN (a menos que estejam conectados via Tailscale).",
|
||||
"sameSubnet": "Ethernet e Wi-Fi devem estar na mesma sub-rede.",
|
||||
"tailscaleTitle": "Host Tailscale inacessível",
|
||||
"timeoutTitle": "Tempo de conexão esgotado",
|
||||
"updateTailscale": "Atualize o app Tailscale — as versões recentes corrigem falhas de reconexão.",
|
||||
"vpnRouting": "VPNs diferentes do Tailscale podem rotear o tráfego local por um servidor remoto.",
|
||||
"vpnTitle": "Interferência de outra VPN",
|
||||
"wifiSignal": "Verifique a intensidade do sinal Wi-Fi no seu celular.",
|
||||
"wifiTitle": "Redes Wi-Fi diferentes",
|
||||
"windowsFirewall": "Windows: Firewall do Windows Defender → Permitir um aplicativo — habilite o Orca para redes privadas."
|
||||
},
|
||||
"troubleshootView": {
|
||||
"commonIssues": "Problemas comuns",
|
||||
"runAgain": "Executar novamente",
|
||||
"runDiagnostics": "Executar diagnósticos",
|
||||
"running": "Executando…",
|
||||
"title": "Solução de problemas",
|
||||
"viewNetworkDiagnostics": "Ver diagnósticos de rede"
|
||||
},
|
||||
"useTroubleshootDiagnostics": {
|
||||
"connected": "Conectado",
|
||||
"hostDataFailed": "Não foi possível ler os dados dos hosts",
|
||||
"hostTestFailed": "Não foi possível testar",
|
||||
"hosts": "Hosts",
|
||||
"internet": "Internet",
|
||||
"noConnection": "Sem conexão",
|
||||
"noPairedHosts": "Nenhum — escaneie um código QR para parear",
|
||||
"pairedCount": "Pareados: {{count}}",
|
||||
"pairedHosts": "Hosts pareados",
|
||||
"platform": "Plataforma",
|
||||
"reachable": "Acessível em {{endpoint}}",
|
||||
"unexpectedResponse": "Resposta inesperada"
|
||||
},
|
||||
"webShellUpdateFailureCopy": {
|
||||
"failedAgo": "A última atualização de {{hostName}} falhou há {{time}}: {{reason}}{{offered}}. {{outcome}}",
|
||||
"failedJustNow": "A última atualização de {{hostName}} falhou agora mesmo: {{reason}}{{offered}}. {{outcome}}"
|
||||
}
|
||||
},
|
||||
"dictation": {
|
||||
"setup": {
|
||||
"alreadyActive": "O ditado já está em uso no computador ou em outro celular.",
|
||||
"cloudModelNeedsKey": "Requer uma chave da OpenAI",
|
||||
"deleteFailed": "Falha ao excluir o modelo",
|
||||
"desktopUpdateRequired": "Atualize o app Orca no desktop pareado para usar as configurações de voz do celular.",
|
||||
"downloadFailed": "Falha ao iniciar o download",
|
||||
"loadFailed": "Falha ao carregar os modelos de ditado",
|
||||
"repairRequired": "Pareie este celular novamente com o computador para usar o ditado.",
|
||||
"serverUpdateRequired": "Atualize o Orca neste servidor para usar o ditado por voz.",
|
||||
"sessionLost": "O ditado perdeu a conexão com o computador. Tente novamente.",
|
||||
"updateFailed": "Falha ao atualizar as configurações de ditado"
|
||||
}
|
||||
},
|
||||
"home": {
|
||||
"accountUsageCards": {
|
||||
"sectionHeading": "Uso da conta",
|
||||
"sessionWindow": "5h",
|
||||
"systemDefault": "Padrão do sistema",
|
||||
"weeklyWindow": "7d"
|
||||
},
|
||||
"appUpdateCard": {
|
||||
"dismissAccessibilityLabel": "Descartar atualização do Orca {{version}}",
|
||||
"title": "Orca {{version}} está disponível",
|
||||
"updateAccessibilityLabel": "Orca {{version}} está disponível, toque para atualizar",
|
||||
"updateHint": "Toque para atualizar"
|
||||
},
|
||||
"emptyState": {
|
||||
"connectedDescription": "Seu desktop aparecerá aqui. Tudo é criptografado de ponta a ponta.",
|
||||
"connectedTitle": "Você está conectado",
|
||||
"description": "Pareie com o Orca no seu computador para acompanhar seus agentes, acessar qualquer terminal e gerenciar o trabalho pelo celular.",
|
||||
"openDesktopDescription": "Vá para Configurações → Mobile e gere um código QR de pareamento.",
|
||||
"openDesktopTitle": "Abra o Orca no desktop",
|
||||
"pairDesktop": "Parear Desktop",
|
||||
"scanCodeDescription": "Toque no botão acima para abrir o leitor. Aponte a câmera para o código QR na tela.",
|
||||
"scanCodeTitle": "Escaneie o código",
|
||||
"stepsHeading": "Como funciona",
|
||||
"title": "Conecte seu desktop"
|
||||
},
|
||||
"listFooter": {
|
||||
"hostPickerTitle": "Abrir tarefas em",
|
||||
"resumeHeading": "Retomar",
|
||||
"tasksHeading": "Tarefas"
|
||||
},
|
||||
"listHeader": {
|
||||
"agentTime": "Tempo de agente",
|
||||
"agentsSpawned": "Agentes iniciados",
|
||||
"daysHours": "{{days}}d {{hours}}h",
|
||||
"desktopsHeading": "Desktops",
|
||||
"hoursMinutes": "{{hours}}h {{minutes}}min",
|
||||
"minutes": "{{minutes}}min",
|
||||
"prsCreated": "PRs criados",
|
||||
"title": "Bem-vindo de volta"
|
||||
},
|
||||
"screen": {
|
||||
"pairingCheckFailed": "Não foi possível verificar o pareamento",
|
||||
"removeHostFailed": "Não foi possível remover o host",
|
||||
"removeHostMessage": "Remover \"{{name}}\"? Você pode parear novamente mais tarde.",
|
||||
"removeHostTitle": "Remover host",
|
||||
"tryAgain": "Tente novamente."
|
||||
},
|
||||
"tasksCard": {
|
||||
"noSources": "Nenhuma fonte de tarefas conectada",
|
||||
"openProviderTasks": "Abrir tarefas do {{provider}}",
|
||||
"title": "Tarefas"
|
||||
}
|
||||
},
|
||||
"hooks": {
|
||||
"dictationDesktopStart": {
|
||||
"recordingFailed": "Falha ao iniciar a gravação pelo microfone"
|
||||
},
|
||||
"useMobileDictation": {
|
||||
"inputClosed": "O ditado por voz parou porque esta sessão não aceita mais entrada de dados. Tente novamente.",
|
||||
"microphoneInitFailed": "Falha ao inicializar o microfone",
|
||||
"microphoneInterrupted": "O ditado por voz parou porque o uso do microfone foi interrompido. Tente novamente.",
|
||||
"microphonePermissionDenied": "Permissão de acesso ao microfone negada",
|
||||
"noSpeech": "Nenhuma fala detectada."
|
||||
}
|
||||
},
|
||||
"host": {
|
||||
"edit": {
|
||||
"address": "Endereço",
|
||||
"addressHint": "Aceita IP, host:port ou ws:// / wss://. Uma URL wss:// sem porta usa a porta 443. Endereços sem protocolo mantêm a porta atual (ou 6768).",
|
||||
"connectionPreview": "Conecta a {{endpoint}}",
|
||||
"goBack": "Voltar",
|
||||
"help": "Altere o nome de exibição ou o endereço de conexão. Deixe o nome em branco para usar o nome informado pelo desktop. Alterar o endereço só muda onde este celular se conecta — não refaz o pareamento. Use esta opção quando o mesmo desktop estiver acessível por outro IP (por exemplo, pela LAN doméstica ou pelo Tailscale).",
|
||||
"hostNamePlaceholder": "Nome do host",
|
||||
"hostRemoved": "Este host foi removido deste celular.",
|
||||
"loadFailed": "Falha ao carregar o host.",
|
||||
"missingHost": "Host não informado.",
|
||||
"name": "Nome",
|
||||
"saveFailed": "Falha ao salvar o host.",
|
||||
"saveHost": "Salvar host",
|
||||
"title": "Editar host"
|
||||
}
|
||||
},
|
||||
"hostScreen": {
|
||||
"hostListActionSheetActions": {
|
||||
"connect": "Conectar",
|
||||
"disconnect": "Desconectar",
|
||||
"editHost": "Editar host",
|
||||
"networkDiagnostics": "Diagnóstico de rede",
|
||||
"reconnect": "Reconectar"
|
||||
},
|
||||
"hostPendingCreationRow": {
|
||||
"creatingWorkspace": "Criando espaço de trabalho…"
|
||||
},
|
||||
"hostRailActions": {
|
||||
"accounts": "Contas",
|
||||
"closeSearch": "Fechar busca",
|
||||
"floatingWorkspace": "Espaço de Trabalho Flutuante",
|
||||
"newWorkspace": "Novo espaço de trabalho",
|
||||
"searchWorkspaces": "Buscar espaços de trabalho",
|
||||
"tasks": "Tarefas"
|
||||
},
|
||||
"hostScreenHeader": {
|
||||
"accounts": "Contas",
|
||||
"activeFilters_many": "Opções do espaço de trabalho ({{count}} filtros ativos)",
|
||||
"activeFilters_one": "Opções do espaço de trabalho ({{count}} filtro ativo)",
|
||||
"activeFilters_other": "Opções do espaço de trabalho ({{count}} filtros ativos)",
|
||||
"backToHosts": "Voltar aos hosts",
|
||||
"closeSearch": "Fechar busca",
|
||||
"floatingWorkspace": "Espaço de Trabalho Flutuante",
|
||||
"hideSidebar": "Ocultar barra lateral",
|
||||
"reconnect": "Reconectar",
|
||||
"searchWorkspaces": "Buscar espaços de trabalho",
|
||||
"tasks": "Tarefas",
|
||||
"workspaceOptions": "Opções do espaço de trabalho"
|
||||
},
|
||||
"hostScreenOverlays": {
|
||||
"deleteConfirmMessage": "Excluir \"{{name}}\" ({{branch}})?",
|
||||
"deleteWorktree": "Excluir Worktree",
|
||||
"pin": "Fixar",
|
||||
"primaryWorktreeDeleteHint": "Worktree primária — não pode ser excluída. Remova o projeto em vez disso.",
|
||||
"removeHost": "Remover host",
|
||||
"removeHostMessage": "Remover \"{{name}}\"? Você pode parear novamente mais tarde.",
|
||||
"sleep": "Suspender",
|
||||
"unpin": "Desafixar"
|
||||
},
|
||||
"hostWorkspaceList": {
|
||||
"searchPlaceholder": "Buscar espaços de trabalho…",
|
||||
"searchWorkspaces": "Buscar espaços de trabalho"
|
||||
},
|
||||
"workspaceDetailsSheet": {
|
||||
"checksFailing": "Verificações: com falha",
|
||||
"checksPassing": "Verificações: aprovadas",
|
||||
"checksPending": "Verificações: pendentes",
|
||||
"closed": "Fechado",
|
||||
"createdByAutomation": "Criado por automação",
|
||||
"createdByNamedAutomation": "Criado pela automação “{{name}}”",
|
||||
"createdFromCli": "Criado via Orca CLI",
|
||||
"draft": "Rascunho",
|
||||
"merged": "Mergeado",
|
||||
"openLink": "Abrir {{label}}",
|
||||
"openLinkWithTitle": "Abrir {{label}}, {{title}}",
|
||||
"prOpen": "Aberto"
|
||||
},
|
||||
"workspaceOptionControls": {
|
||||
"agentActivity": "Atividade do agente",
|
||||
"agentActivityLayout": "Layout de atividade do agente",
|
||||
"agentActivitySort": "Atividade do Agente",
|
||||
"agentActivitySortDescription": "Agentes que precisam de atenção, depois a atividade mais recente.",
|
||||
"agentStatuses": "Status dos agentes",
|
||||
"allHosts": "Todos os hosts",
|
||||
"allProjects": "Todos os projetos",
|
||||
"automation": "Automação",
|
||||
"branchFolderPath": "Branch / caminho da pasta",
|
||||
"branchName": "Nome da branch",
|
||||
"compact": "Compacto",
|
||||
"detailed": "Detalhado",
|
||||
"fullList": "Lista completa",
|
||||
"githubIssues": "Issues do GitHub",
|
||||
"githubTicket": "Ticket do GitHub",
|
||||
"host": "Host",
|
||||
"hover": "Passar o mouse",
|
||||
"jiraIssue": "Issue do Jira",
|
||||
"jiraIssues": "Issues do Jira",
|
||||
"linearIssue": "Issue do Linear",
|
||||
"linearIssues": "Issues do Linear",
|
||||
"manual": "Manual",
|
||||
"manualProjectOrderDescription": "Arraste os projetos para organizá-los",
|
||||
"manualWorkspaceOrderDescription": "Arraste os espaços de trabalho para organizá-los dentro de cada grupo.",
|
||||
"name": "Nome",
|
||||
"none": "Nenhum",
|
||||
"notes": "Notas",
|
||||
"ports": "Portas",
|
||||
"pr": "PR",
|
||||
"prMrLink": "Link de PR/MR",
|
||||
"project": "Projeto",
|
||||
"recent": "Recente",
|
||||
"recentProjectOrderDescription": "Atividade mais recente do espaço de trabalho",
|
||||
"searchProjects": "Buscar projetos",
|
||||
"searchProjectsPlaceholder": "Buscar projetos…",
|
||||
"selectedCount": "{{count}} de {{total}}",
|
||||
"status": "Status"
|
||||
},
|
||||
"workspaceOptionsSheet": {
|
||||
"backToOptions": "Voltar às opções do espaço de trabalho",
|
||||
"cardDisplay": "Exibição do cartão",
|
||||
"cardLayout": "Layout do cartão",
|
||||
"clearFilters": "Limpar filtros",
|
||||
"exceptDefaultBranch": "Exceto a branch padrão",
|
||||
"filters": "Filtros",
|
||||
"groupBy": "Agrupar por",
|
||||
"hideAutomationCreated": "Ocultar criados por automação",
|
||||
"hideCliCreated": "Ocultar criados por CLI",
|
||||
"hideDefaultBranch": "Ocultar branch padrão",
|
||||
"hideDetachedHead": "Ocultar HEAD desanexado",
|
||||
"hideOtherClientWorkspaces": "Ocultar espaços de trabalho de outros clientes",
|
||||
"hideSleeping": "Ocultar em suspensão",
|
||||
"hosts": "Hosts",
|
||||
"localLinux": "Linux local",
|
||||
"localMac": "Mac local",
|
||||
"localWindows": "Windows local",
|
||||
"projectOrder": "Ordem do projeto",
|
||||
"projects": "Projetos",
|
||||
"savingToDesktop": "Salvando no desktop",
|
||||
"show": "Mostrar",
|
||||
"showProperties": "Mostrar propriedades",
|
||||
"sortBy": "Ordenar por",
|
||||
"syncedAcrossDevices": "Sincronizadas entre seus dispositivos",
|
||||
"thisComputer": "Este computador",
|
||||
"waitingForDesktop": "Ainda aguardando o desktop",
|
||||
"workspaceOptions": "Opções do espaço de trabalho"
|
||||
}
|
||||
},
|
||||
"navigation": {
|
||||
"hostStackScreens": {
|
||||
"accounts": "Contas",
|
||||
"agentSessionHistory": "Histórico de Sessões de Agente",
|
||||
"changes": "Alterações",
|
||||
"editHost": "Editar host",
|
||||
"host": "Host",
|
||||
"pullRequest": "Pull Request",
|
||||
"sourceControl": "Controle de Código-Fonte",
|
||||
"tasks": "Tarefas",
|
||||
"terminal": "Terminal",
|
||||
"workspace": "Espaço de Trabalho"
|
||||
}
|
||||
},
|
||||
"notifications": {
|
||||
"notificationDeliverySection": {
|
||||
"footer": "Os tipos de alerta seguem as configurações de notificação de cada desktop pareado. As notificações são pausadas após 7 dias sem usar este app; abra-o e reconecte-se para retomá-las.",
|
||||
"onlyWhenAway": "Só quando estiver longe do desktop",
|
||||
"onlyWhenAwayHint": "Após 3 minutos sem atividade do teclado ou mouse, ou quando o desktop estiver bloqueado.",
|
||||
"sound": "Som de notificação",
|
||||
"suppressWhileFocused": "Suprimir enquanto em foco",
|
||||
"suppressWhileFocusedHint": "Não enviar alertas do espaço de trabalho aberto neste celular."
|
||||
}
|
||||
},
|
||||
"onboarding": {
|
||||
"notificationOnboardingPreview": {
|
||||
"claudeNeedsInput": "Claude precisa da sua resposta",
|
||||
"codexFinished": "Codex terminou",
|
||||
"now": "agora",
|
||||
"testsPassing": "Os testes estão passando.",
|
||||
"waitingOnYou": "Aguardando você."
|
||||
},
|
||||
"onboardingPage": {
|
||||
"enableAgentNotifications": "Ativar notificações de agentes",
|
||||
"enableNotifications": "Ativar notificações",
|
||||
"keepTerminal": "Manter terminal",
|
||||
"notNow": "Agora não",
|
||||
"notificationsDescription": "Receba uma notificação neste celular quando um agente terminar ou estiver aguardando — mesmo sem usar o app.",
|
||||
"notificationsDisclosure": "Enviadas pelo serviço de notificações push do Orca após 3 minutos de inatividade do seu desktop. Altere isso a qualquer momento em Configurações.",
|
||||
"notificationsTitle": "Saiba quando um agente precisar de você",
|
||||
"openInChatUi": "Abrir sessões na interface de chat",
|
||||
"openInTerminal": "Abrir sessões no terminal",
|
||||
"sessionViewDescription": "Escolha se as sessões de agentes compatíveis devem abrir no terminal ou na interface de chat neste dispositivo. Toque e segure uma aba de sessão para alternar a visualização ou altere o padrão depois em Configurações.",
|
||||
"sessionViewTitle": "Como as sessões devem ser abertas?",
|
||||
"skipNotifications": "Pular notificações por enquanto",
|
||||
"useChatUi": "Usar interface de chat"
|
||||
}
|
||||
},
|
||||
"settings": {
|
||||
"aboutScreen": {
|
||||
"openLinkFailed": "Não foi possível abrir o link. Tente novamente.",
|
||||
"socialProfile": "Orca no X",
|
||||
"sourceCode": "Código-fonte do Orca",
|
||||
"tagline": "IDE de código aberto com agentes para quem cria 100x mais",
|
||||
"title": "Sobre",
|
||||
"website": "Site do Orca"
|
||||
},
|
||||
"appUpdateSettingsRows": {
|
||||
"checkFailed": "Falha ao verificar",
|
||||
"checkUpdates": "Verificar atualizações",
|
||||
"checkUpdatesStatus": "Verificar atualizações, {{status}}",
|
||||
"checking": "Verificando…",
|
||||
"installedVersion": "Versão {{version}}",
|
||||
"lastCheckedDays_many": "Última verificação há {{count}} d",
|
||||
"lastCheckedDays_one": "Última verificação há {{count}} d",
|
||||
"lastCheckedDays_other": "Última verificação há {{count}} d",
|
||||
"lastCheckedHours_many": "Última verificação há {{count}} h",
|
||||
"lastCheckedHours_one": "Última verificação há {{count}} h",
|
||||
"lastCheckedHours_other": "Última verificação há {{count}} h",
|
||||
"lastCheckedJustNow": "Última verificação agora mesmo",
|
||||
"lastCheckedMinutes_many": "Última verificação há {{count}} min",
|
||||
"lastCheckedMinutes_one": "Última verificação há {{count}} min",
|
||||
"lastCheckedMinutes_other": "Última verificação há {{count}} min",
|
||||
"neverChecked": "Nunca",
|
||||
"upToDate": "Atualizado",
|
||||
"update": "Atualizar",
|
||||
"updateToVersion": "Atualizar para o Orca {{version}}"
|
||||
},
|
||||
"appearance": {
|
||||
"language": {
|
||||
"description": "A opção Sistema segue o idioma deste dispositivo. As solicitações de permissão sempre usam o idioma do dispositivo.",
|
||||
@@ -17,6 +616,161 @@
|
||||
"system": "Sistema"
|
||||
},
|
||||
"title": "Aparência"
|
||||
},
|
||||
"browserSettingsScreen": {
|
||||
"desktopBrowser": "Navegador do Orca no desktop",
|
||||
"desktopBrowserDescription": "Abrir no navegador transmitido pelo seu desktop pareado.",
|
||||
"linksDescription": "Escolha onde abrir os links HTTP(S) ao tocar neles na saída do terminal.",
|
||||
"linksHeading": "LINKS",
|
||||
"loadFailed": "Não foi possível carregar as preferências do navegador. Tente novamente.",
|
||||
"openTerminalLinks": "Abrir links do terminal",
|
||||
"phoneBrowser": "Navegador do celular",
|
||||
"phoneBrowserDescription": "Abrir no Safari, Chrome ou outro navegador neste celular.",
|
||||
"saveFailed": "Não foi possível salvar as preferências do navegador. Tente novamente.",
|
||||
"title": "Navegador"
|
||||
},
|
||||
"menuItems": {
|
||||
"about": "Sobre",
|
||||
"appearance": "Aparência",
|
||||
"browser": "Navegador",
|
||||
"chatUi": "Interface de Chat",
|
||||
"notifications": "Notificações",
|
||||
"terminal": "Terminal",
|
||||
"troubleshooting": "Solução de problemas",
|
||||
"voice": "Voz"
|
||||
},
|
||||
"menuScreen": {
|
||||
"privacyPolicy": "Política de Privacidade",
|
||||
"support": "Suporte"
|
||||
},
|
||||
"nativeChatSettingsScreen": {
|
||||
"defaultViewDescription": "Escolha como as sessões de agentes compatíveis (Claude, Codex e outros agentes com suporte a chat) são abertas neste dispositivo. O Terminal exibe a CLI diretamente; a Interface de Chat exibe uma interface de chat como a do app desktop. Você ainda pode mudar a visualização de cada sessão pelo menu de toque prolongado.",
|
||||
"defaultViewHeading": "VISUALIZAÇÃO PADRÃO",
|
||||
"off": "Desativado",
|
||||
"on": "Ativado",
|
||||
"openInChatUi": "Abrir sessões na Interface de Chat",
|
||||
"title": "Interface de Chat"
|
||||
},
|
||||
"nativeNotificationDeliverySettings": {
|
||||
"loadFailed": "Não foi possível carregar as configurações de entrega. Reabra esta tela para tentar novamente.",
|
||||
"saveFailed": "Não foi possível salvar as configurações de entrega. Tente novamente.",
|
||||
"updateDesktopHint": "Pareie um desktop atualizado para receber notificações neste celular."
|
||||
},
|
||||
"notificationDisplayTest": {
|
||||
"accepted": "O serviço de notificações push do Orca aceitou o teste. Verifique se a notificação chegou.",
|
||||
"connectDesktop": "Conecte um desktop e tente novamente.",
|
||||
"description": "Envie um teste pelo serviço de notificações push do Orca.",
|
||||
"desktopUnreachable": "Não foi possível acessar o desktop. Tente novamente.",
|
||||
"loadFailed": "Não foi possível carregar os desktops pareados.",
|
||||
"pairDesktop": "Pareie um desktop e tente novamente.",
|
||||
"pushServiceFailed": "Não foi possível enviar pelo serviço de notificações push do Orca. Tente novamente.",
|
||||
"rateLimited": "Notificações demais. Tente novamente mais tarde.",
|
||||
"reconnectPhone": "Reconecte-se para registrar este celular para receber notificações.",
|
||||
"sendButton": "Enviar Notificação de Teste",
|
||||
"sendFailed": "Não foi possível enviar o teste de notificação push.",
|
||||
"sending": "Enviando…",
|
||||
"troubleReceivingAlerts": "Está com dificuldade para receber alertas?",
|
||||
"troubleshooting": "Solução de problemas",
|
||||
"updateDesktop": "Atualize seu desktop para executar este teste."
|
||||
},
|
||||
"notificationSettingsScreen": {
|
||||
"description": "Receba notificações neste dispositivo quando um agente precisar da sua resposta ou concluir uma tarefa.",
|
||||
"enableNotifications": "Ativar notificações",
|
||||
"loadFailed": "Não foi possível carregar as configurações de notificações. Tente novamente.",
|
||||
"notificationsBlocked": "As notificações estão desativadas nas configurações do sistema.",
|
||||
"openSettings": "Abrir Configurações",
|
||||
"openSettingsFailed": "Não foi possível abrir as configurações do sistema. Tente novamente.",
|
||||
"saveFailed": "Não foi possível salvar as configurações de notificações. Tente novamente.",
|
||||
"title": "Notificações"
|
||||
},
|
||||
"pendingCredentialCleanupCard": {
|
||||
"cleanupUnconfirmed_many": "Não foi possível confirmar a limpeza de {{count}} credenciais neste dispositivo.",
|
||||
"cleanupUnconfirmed_one": "Não foi possível confirmar a limpeza de {{count}} credencial neste dispositivo.",
|
||||
"cleanupUnconfirmed_other": "Não foi possível confirmar a limpeza de {{count}} credenciais neste dispositivo.",
|
||||
"retryCleanup": "Tentar limpar as credenciais de pareamento novamente",
|
||||
"retryFailed": "Ainda não foi possível confirmar a limpeza. Tente novamente mais tarde.",
|
||||
"statusCheckFailed": "Não foi possível verificar o status da limpeza neste dispositivo. Tente novamente por segurança.",
|
||||
"title": "Limpeza de credenciais de pareamento"
|
||||
},
|
||||
"uiLanguageSettingsSection": {
|
||||
"pseudoLanguage": "Pseudoidioma (en-XA)"
|
||||
},
|
||||
"voiceSettingsScreen": {
|
||||
"connectDesktop": "Conecte-se a um desktop para gerenciar as configurações de voz.",
|
||||
"deleteFailed": "Falha ao excluir",
|
||||
"dictationDescription": "Dite texto em qualquer painel em foco no seu desktop.",
|
||||
"dictationHeading": "DITADO",
|
||||
"dictationMode": "Modo de Ditado",
|
||||
"dictationModeDescription": "Alternar: pressione uma vez para iniciar e novamente para parar. Manter pressionado: pressione e segure para ditar.",
|
||||
"downloadFailed": "Falha no download",
|
||||
"enableDictation": "Ativar Ditado por Voz",
|
||||
"holdMode": "Manter Pressionado",
|
||||
"loadFailed": "Falha ao carregar as configurações de voz",
|
||||
"loadFailedMessage": "Falha ao carregar as configurações de voz.",
|
||||
"noneSelected": "Nenhum selecionado",
|
||||
"selectModelFailed": "Não foi possível selecionar o modelo",
|
||||
"speechModel": "Modelo de Voz",
|
||||
"speechModelHeading": "MODELO DE VOZ",
|
||||
"title": "Voz",
|
||||
"toggleMode": "Alternar",
|
||||
"updateFailed": "Não foi possível atualizar"
|
||||
}
|
||||
},
|
||||
"webShell": {
|
||||
"shellStateScreen": {
|
||||
"checkingHost": "Verificando host",
|
||||
"connectToDownload": "Conecte-se a este host para baixar a versão mais recente do app",
|
||||
"documentLoadFailed": "Não foi possível abrir o app baixado.",
|
||||
"downloadFailed": "Não foi possível baixar a versão mais recente do app deste host.",
|
||||
"hostProfileUnreadable": "Não foi possível ler os dados deste host no seu dispositivo. Tente novamente.",
|
||||
"isolationUnavailable": "O WebView deste dispositivo é antigo demais para abrir o espaço de trabalho com segurança.",
|
||||
"openingWorkspace": "Abrindo espaço de trabalho",
|
||||
"renderProcessGone": "O espaço de trabalho parou de responder.",
|
||||
"statusUnreadable": "Não foi possível ler o status deste host. Volte e abra-o novamente.",
|
||||
"tryAgain": "Tentar novamente"
|
||||
}
|
||||
},
|
||||
"worktree": {
|
||||
"agentRowDisplay": {
|
||||
"blocked": "Bloqueado",
|
||||
"days_many": "{{count}}d",
|
||||
"days_one": "{{count}}d",
|
||||
"days_other": "{{count}}d",
|
||||
"done": "Concluído",
|
||||
"failed": "Falhou",
|
||||
"hours_many": "{{count}}h",
|
||||
"hours_one": "{{count}}h",
|
||||
"hours_other": "{{count}}h",
|
||||
"idle": "Inativo",
|
||||
"interrupted": "Interrompido",
|
||||
"justNow": "agora mesmo",
|
||||
"minutes_many": "{{count}}m",
|
||||
"minutes_one": "{{count}}m",
|
||||
"minutes_other": "{{count}}m",
|
||||
"monitoring": "Monitorando tarefas em segundo plano",
|
||||
"stopping": "Parando…",
|
||||
"unconfirmed": "Não foi possível confirmar",
|
||||
"unverifiable": "Nenhuma atualização recente",
|
||||
"waiting": "Esperando entrada",
|
||||
"working": "Trabalhando"
|
||||
},
|
||||
"hostWorkspaceListStates": {
|
||||
"checkHost": "Verifique se o computador não está suspenso e se o Orca está em execução",
|
||||
"empty": "Nenhum espaço de trabalho",
|
||||
"loadFailed": "Não foi possível carregar os espaços de trabalho deste host",
|
||||
"noFilterMatches": "Nenhum espaço de trabalho corresponde aos filtros",
|
||||
"noMatches": "Nenhum espaço de trabalho encontrado",
|
||||
"reopen": "Reabrir para atualizar",
|
||||
"reopenDetail": "Este host lista os espaços de trabalho em um formato que esta página ainda não reconhece.",
|
||||
"retrying": "worktree.ps falhou ({{error}}) — tentando novamente automaticamente"
|
||||
},
|
||||
"workspaceListSections": {
|
||||
"all": "Todos",
|
||||
"closed": "Fechados",
|
||||
"done": "Concluídos",
|
||||
"inProgress": "Em andamento",
|
||||
"inReview": "Em revisão",
|
||||
"pinned": "Fixados"
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,8 +1,615 @@
|
||||
{
|
||||
"accounts": {
|
||||
"screen": {
|
||||
"agentLogin": "Использовать учётную запись самого агента",
|
||||
"connecting": "Подключение к серверу…",
|
||||
"connectingTo": "Подключение к {{name}}…",
|
||||
"default": "Как в системе",
|
||||
"hostNotFound": "Сервер не найден",
|
||||
"invalidSnapshot": "Некорректные данные учётных записей от сервера",
|
||||
"loadingAccounts": "Загрузка учётных записей…",
|
||||
"manageAccountsHint": "Для добавления учётных записей или повторного входа откройте «Настройки → Учётные записи» в приложении на компьютере.",
|
||||
"refreshAccounts": "Обновить учётные записи",
|
||||
"sessionWindow": "5 ч",
|
||||
"switchFailed": "Не удалось переключить учётную запись",
|
||||
"title": "Учётные записи",
|
||||
"useReset": "Сбросить лимиты",
|
||||
"weeklyWindow": "7 дн."
|
||||
}
|
||||
},
|
||||
"agentHistory": {
|
||||
"panel": {
|
||||
"all": "Все",
|
||||
"emptyScope": "В выбранной области нет предыдущих сессий агентов.",
|
||||
"loadFailed": "Не удалось загрузить",
|
||||
"missingResumeId": "У этой сессии нет идентификатора для продолжения.",
|
||||
"noSearchResults": "По вашему запросу сессий не найдено.",
|
||||
"noSessions": "Нет сессий агентов",
|
||||
"placeholder": "Поиск сессий, repo:, path:",
|
||||
"project": "Проект",
|
||||
"refreshSessions": "Обновить список сессий агентов",
|
||||
"resumeFailed": "Не удалось продолжить сессию.",
|
||||
"sessionQueued": "Сессия агента добавлена в очередь.",
|
||||
"skipped_few": "Пропущено историй: {{count}}",
|
||||
"skipped_many": "Пропущено историй: {{count}}",
|
||||
"skipped_one": "Пропущено историй: {{count}}",
|
||||
"skipped_other": "Пропущено историй: {{count}}",
|
||||
"title": "История сессий агентов",
|
||||
"unavailable": "История сессий агентов недоступна",
|
||||
"unknownHostPlatform": "Не удалось определить платформу сервера.",
|
||||
"updateHost": "Обновите Orca на этом сервере, чтобы просматривать историю сессий агентов.",
|
||||
"waitingForHost": "Ожидание сервера…",
|
||||
"workspace": "Рабочее пространство"
|
||||
},
|
||||
"resumeTarget": {
|
||||
"differentHost": "Эта сессия принадлежит другому серверу. Откройте рабочее пространство на том же сервере, чтобы продолжить её.",
|
||||
"openWorkspace": "Откройте рабочее пространство, прежде чем продолжить сессию.",
|
||||
"runtimeUnavailable": "Для рабочих пространств в среде выполнения продолжение сессий из истории недоступно."
|
||||
},
|
||||
"sessionCard": {
|
||||
"untitledSession": "Сессия без названия"
|
||||
},
|
||||
"sessionHistoryList": {
|
||||
"assistantRole": "ассистент",
|
||||
"currentWorktree": "текущее рабочее дерево",
|
||||
"messages_few": "{{count}} сообщения",
|
||||
"messages_many": "{{count}} сообщений",
|
||||
"messages_one": "{{count}} сообщение",
|
||||
"messages_other": "{{count}} сообщения",
|
||||
"resumeSession": "Продолжить сессию агента",
|
||||
"userRole": "пользователь"
|
||||
},
|
||||
"useAgentHistoryState": {
|
||||
"hostUnreachable": "Не удалось связаться с сервером",
|
||||
"loadFailed": "Не удалось загрузить сессии агентов",
|
||||
"waitingForHost": "Ожидание сервера…"
|
||||
},
|
||||
"worktreeNavigationActions": {
|
||||
"agentSessionHistory": "История сессий агентов",
|
||||
"sourceControl": "Управление версиями"
|
||||
}
|
||||
},
|
||||
"app": {
|
||||
"mobileOnboarding": {
|
||||
"notificationSettingsFailed": "Не удалось обновить настройки уведомлений. Повторите попытку.",
|
||||
"progress": "Ход первоначальной настройки",
|
||||
"saveChoiceFailed": "Не удалось сохранить ваш выбор. Повторите попытку.",
|
||||
"stepProgress": "Шаг {{step}} из {{total}}"
|
||||
},
|
||||
"notifications": {
|
||||
"description": "Получайте уведомления от агентов, даже когда приложение закрыто. Они доставляются через сервис push-уведомлений Orca и службы Apple или Google."
|
||||
},
|
||||
"pair": {
|
||||
"backToHome": "На главный экран",
|
||||
"missingCode": "Отсутствует код сопряжения"
|
||||
},
|
||||
"pairConfirm": {
|
||||
"backToHome": "На главный экран",
|
||||
"connectTimedOut": "Не удалось подключиться за {{seconds}} с — посмотрите в журнале ниже, на каком этапе остановилось подключение",
|
||||
"connecting": "Подключение…",
|
||||
"description": "Вы открыли ссылку сопряжения, полученную с компьютера. Подтвердите, чтобы добавить его в список серверов.",
|
||||
"pairButton": "Выполнить сопряжение",
|
||||
"pairingFailed": "Не удалось выполнить сопряжение: {{error}}",
|
||||
"pairingLog": "Журнал сопряжения",
|
||||
"title": "Выполнить сопряжение с этим компьютером?"
|
||||
},
|
||||
"pairScan": {
|
||||
"cameraDisabledDescription": "Разрешите доступ к камере в настройках или вставьте код сопряжения.",
|
||||
"cameraDisabledTitle": "Доступ к камере отключён",
|
||||
"cameraPermissionDescription": "Отсканируйте QR-код в Orca на компьютере или вставьте код сопряжения.",
|
||||
"connectTimedOut": "Не удалось подключиться за {{seconds}} с — посмотрите в журнале ниже, на каком этапе остановилось подключение",
|
||||
"connecting": "Подключение…",
|
||||
"invalidPairingCode": "Неверный код сопряжения — скопируйте его с компьютера и вставьте ещё раз",
|
||||
"invalidQrCode": "Неверный QR-код Orca",
|
||||
"openDesktopStep": "Откройте Orca на компьютере",
|
||||
"openMobileSettingsStep": "Перейдите в «Настройки → Мобильный доступ»",
|
||||
"openSettings": "Открыть настройки",
|
||||
"pairingFailed": "Не удалось выполнить сопряжение: {{error}}",
|
||||
"pairingLog": "Журнал сопряжения",
|
||||
"pasteCodeMessage": "Скопируйте код, показанный под QR-кодом на компьютере.",
|
||||
"pasteCodePlaceholder": "orca://pair?code=... или вставьте код",
|
||||
"pasteCodeTitle": "Вставить код сопряжения",
|
||||
"pasteInstead": "Вставить код",
|
||||
"pastePairingCode": "Или вставить код сопряжения",
|
||||
"scanCodeStep": "Отсканируйте QR-код",
|
||||
"title": "Сопряжение с компьютером",
|
||||
"tryAgain": "Повторить попытку"
|
||||
},
|
||||
"terminalSettings": {
|
||||
"autocomplete": "Автодополнение и автоисправление",
|
||||
"autocompleteOff": "Выключено",
|
||||
"autocompleteOn": "Включено",
|
||||
"defaultText": "По умолчанию (100%)",
|
||||
"keepPhoneSize": "Сохранять размер для телефона (по умолчанию)",
|
||||
"keyboardInputDescription": "Автодополнение, автоисправление и орфографические подсказки работают только в режиме ввода команд с предварительным редактированием. Нажмите кнопку с двойной стрелкой над клавиатурой, чтобы переключиться из режима прямого ввода, затем отредактируйте команду и нажмите «Отправить». По умолчанию эти функции отключены, чтобы не изменять команды, флаги и пути.",
|
||||
"keyboardInputHeading": "ВВОД С КЛАВИАТУРЫ",
|
||||
"largeText": "Крупный (125%)",
|
||||
"largerText": "Очень крупный (150%)",
|
||||
"largestText": "Самый крупный (200%)",
|
||||
"leaveAppDescription": "Когда вы используете терминал на телефоне, Orca уменьшает его до размеров экрана. Эта настройка определяет, что произойдёт при закрытии приложения или переходе в другое: терминал сохранит размер для телефона (чтобы интерфейс интерактивных CLI-инструментов не перестраивался) или вернётся к размеру для компьютера. Восстановить размер вручную можно в любой момент кнопкой «Восстановить» на баннере в приложении на компьютере.",
|
||||
"leaveAppHeading": "ПРИ ВЫХОДЕ ИЗ ПРИЛОЖЕНИЯ",
|
||||
"noPairedDesktops": "Пока нет сопряжённых компьютеров. Выполните сопряжение с компьютером, чтобы настроить поведение терминала.",
|
||||
"restoreAfterMinutes_few": "Через {{count}} минуты",
|
||||
"restoreAfterMinutes_many": "Через {{count}} минут",
|
||||
"restoreAfterMinutes_one": "Через {{count}} минуту",
|
||||
"restoreAfterMinutes_other": "Через {{count}} минуты",
|
||||
"restoreAfterSeconds_few": "Через {{count}} с",
|
||||
"restoreAfterSeconds_many": "Через {{count}} с",
|
||||
"restoreAfterSeconds_one": "Через {{count}} с",
|
||||
"restoreAfterSeconds_other": "Через {{count}} с",
|
||||
"restoreHost": "Восстановление размера: {{name}}",
|
||||
"smallerText": "Уменьшенный (75%)",
|
||||
"smallestText": "Минимальный (50%)",
|
||||
"textSize": "Размер текста",
|
||||
"textSizeDescription": "Изменяйте размер текста в терминале. При мелком тексте помещается больше столбцов и остаются поля по бокам; при крупном — столбцов меньше. Для прокрутки по горизонтали проведите пальцем в сторону. Размер также можно менять жестом сведения или разведения пальцев в самом терминале — значение этой настройки обновится. Настройка влияет только на отображение на этом устройстве и не меняет терминал на компьютере.",
|
||||
"textSizeHeading": "РАЗМЕР ТЕКСТА",
|
||||
"textSizePickerTitle": "Размер текста терминала",
|
||||
"title": "Терминал"
|
||||
}
|
||||
},
|
||||
"common": {
|
||||
"back": "Назад"
|
||||
"back": "Назад",
|
||||
"cancel": "Отмена",
|
||||
"continue": "Продолжить",
|
||||
"copied": "Скопировано",
|
||||
"delete": "Удалить",
|
||||
"ok": "ОК",
|
||||
"remove": "Удалить",
|
||||
"retry": "Повторить",
|
||||
"save": "Сохранить",
|
||||
"settings": "Настройки",
|
||||
"unknown": "Неизвестно"
|
||||
},
|
||||
"connection": {
|
||||
"connectionHealth": {
|
||||
"checkConnection": "Проверьте подключение",
|
||||
"connectFailed": "Не удаётся подключиться",
|
||||
"connected": "Подключён",
|
||||
"connecting": "Подключение…",
|
||||
"connectingViaRelay": "Подключение через Relay…",
|
||||
"desktopUnreachable": "Не удаётся связаться с компьютером",
|
||||
"disconnected": "Нет подключения",
|
||||
"hostFallback": "Сервер",
|
||||
"hostOffline": "{{host}} не в сети",
|
||||
"hostOfflineDetail": "Убедитесь, что компьютер не спит, Orca запущена и вы вошли в учётную запись",
|
||||
"labelWithHint": "{{label}} — {{hint}}",
|
||||
"pairingInvalid": "Сопряжение недействительно — повторите сопряжение с компьютером",
|
||||
"rePairDetail": "Повторно выполните сопряжение с компьютером",
|
||||
"reconnecting": "Повторное подключение…",
|
||||
"relayAccessExpired": "Истёк срок доступа к Relay для {{host}}",
|
||||
"relayConnectFailed": "Не удаётся подключиться через Relay",
|
||||
"relayUnreachable": "Не удаётся связаться с Relay",
|
||||
"signInDetail": "Войдите в Orca на компьютере, чтобы подключиться снова",
|
||||
"signInRequired": "Требуется вход на {{host}}",
|
||||
"tailscaleHint": "проверьте Tailscale"
|
||||
},
|
||||
"connectionPathLabel": {
|
||||
"directLan": "Напрямую · LAN",
|
||||
"directTailscale": "Напрямую · Tailscale",
|
||||
"directViaLan": "Напрямую через LAN",
|
||||
"directViaTailscale": "Напрямую через Tailscale"
|
||||
},
|
||||
"hostEndpoint": {
|
||||
"addressRequired": "Введите адрес сервера.",
|
||||
"hostnameRequired": "Не указано имя сервера.",
|
||||
"invalidAddress": "Некорректный адрес.",
|
||||
"invalidHostname": "Некорректное имя сервера.",
|
||||
"invalidPort": "Номер порта должен быть в диапазоне 1–65535.",
|
||||
"invalidScheme": "Используйте ws:// или wss:// (либо host:port).",
|
||||
"pathOrQueryNotAllowed": "Адрес сервера не должен содержать путь или параметры запроса."
|
||||
},
|
||||
"pairConfirmState": {
|
||||
"invalidCode": "Некорректный код сопряжения",
|
||||
"missingCode": "Не указан код сопряжения"
|
||||
},
|
||||
"rpcClientStreamRegistry": {
|
||||
"endedBeforeReady": "Потоковый запрос завершился до перехода в состояние готовности.",
|
||||
"interrupted": "Подключение прервано"
|
||||
},
|
||||
"rpcClientTerminalBinaryFrame": {
|
||||
"unsupportedFrame": "Неподдерживаемый кадр потока терминала (код операции {{opcode}})."
|
||||
}
|
||||
},
|
||||
"diagnostics": {
|
||||
"connectionDiagnosticsView": {
|
||||
"authFailed": "ошибка аутентификации",
|
||||
"connected": "подключён",
|
||||
"connecting": "подключение",
|
||||
"copyReport": "Скопировать отчёт",
|
||||
"diagnosisHeading": "Возможная причина",
|
||||
"disconnected": "отключён",
|
||||
"handshaking": "согласование соединения",
|
||||
"noEvents": "Событий соединения пока нет. Они появятся, когда приложение начнёт подключаться к этому серверу.",
|
||||
"noHosts": "Сопряжённых серверов нет.",
|
||||
"privacyHint": "Отправляется отчёт ограниченного размера, из которого удалены конфиденциальные данные. Он содержит имя сервера, адрес подключения, версии, состояние соединения и события. Содержимое терминала и учётные данные никогда не отправляются.",
|
||||
"reconnecting": "повторное подключение",
|
||||
"retrySending": "Повторить отправку",
|
||||
"sendDiagnostics": "Отправить диагностику в Orca",
|
||||
"sending": "Отправка…",
|
||||
"sent": "Диагностика отправлена",
|
||||
"stateWithAttempt": "{{state}} · попытка {{attempts}}",
|
||||
"title": "Диагностика сети"
|
||||
},
|
||||
"mobileWebBundleProbeRow": {
|
||||
"assets": "Ресурсы",
|
||||
"build": "Сборка",
|
||||
"bundle": "Пакет",
|
||||
"bytes": "Байты",
|
||||
"connecting": "Подключение…",
|
||||
"elapsed": "Затраченное время",
|
||||
"elapsedMilliseconds": "{{elapsedMs}} мс",
|
||||
"fetchBundle": "Загрузить пакет мобильной веб-версии",
|
||||
"fetching": "Загрузка пакета…",
|
||||
"host": "Сервер",
|
||||
"unknownHost": "неизвестный сервер"
|
||||
},
|
||||
"mobileWebShellUpdateFailureRow": {
|
||||
"title": "Обновления рабочих пространств"
|
||||
},
|
||||
"troubleshootCommonIssues": {
|
||||
"allowNotifications": "Проверьте, что уведомления Orca разрешены в системных настройках, а режимы «Фокусирование» и «Не беспокоить» выключены.",
|
||||
"checkTailnet": "Убедитесь, что компьютер не спит и отображается как подключённый в вашей сети tailnet.",
|
||||
"desktopTitle": "Приложение на компьютере не запущено",
|
||||
"disableVpn": "Отключите этот VPN или включите раздельное туннелирование / «Разрешить LAN».",
|
||||
"firewallTitle": "Брандмауэр блокирует порт 6768",
|
||||
"keepTailscaleOn": "Адреса серверов вида 100.x.x.x или *.ts.net доступны через Tailscale — оставьте его ВКЛЮЧЁННЫМ.",
|
||||
"macosFirewall": "macOS: Системные настройки → Сеть → Брандмауэр — разрешите подключения для Orca.",
|
||||
"notificationNetwork": "Переключитесь на мобильную сеть или другую сеть Wi-Fi. Если уведомления начнут приходить, возможно, ваша сеть задерживает их доставку.",
|
||||
"notificationsTitle": "Пуш-уведомления",
|
||||
"openDesktop": "Чтобы принимать подключения, Orca должна быть запущена на компьютере.",
|
||||
"pairAfterUpdate": "После обновления может потребоваться повторное сопряжение по QR-коду.",
|
||||
"reconnectWifi": "Попробуйте заново подключить оба устройства к WiFi.",
|
||||
"restartBothApps": "Если время ожидания истекает снова, перезапустите оба приложения.",
|
||||
"restartDesktop": "Попробуйте перезапустить Orca — сервер для мобильного приложения запускается вместе с ней.",
|
||||
"restartTailscale": "В iOS/Android туннель может незаметно зависнуть: откройте приложение Tailscale, выключите подключение и снова включите его.",
|
||||
"restrictedNetwork": "В корпоративных и учебных сетях могут блокироваться P2P-подключения — попробуйте подключиться через свою точку доступа.",
|
||||
"retryHost": "Вернитесь к списку серверов и нажмите на свой сервер, чтобы повторить попытку.",
|
||||
"sameNetwork": "Оба устройства должны быть в одной локальной сети (LAN), если они не подключены через Tailscale.",
|
||||
"sameSubnet": "Подключения Ethernet и WiFi должны быть в одной подсети.",
|
||||
"tailscaleTitle": "Сервер недоступен через Tailscale",
|
||||
"timeoutTitle": "Время ожидания подключения истекло",
|
||||
"updateTailscale": "Обновите приложение Tailscale — в последних версиях исправлены ошибки повторного подключения.",
|
||||
"vpnRouting": "VPN, кроме Tailscale, могут направлять локальный трафик через удалённый сервер.",
|
||||
"vpnTitle": "Конфликт с другим VPN",
|
||||
"wifiSignal": "Проверьте уровень сигнала WiFi на телефоне.",
|
||||
"wifiTitle": "Разные сети WiFi",
|
||||
"windowsFirewall": "Windows: Брандмауэр Защитника Windows → Разрешение взаимодействия с приложением — разрешите Orca для частных сетей."
|
||||
},
|
||||
"troubleshootView": {
|
||||
"commonIssues": "Частые неполадки",
|
||||
"runAgain": "Запустить снова",
|
||||
"runDiagnostics": "Запустить диагностику",
|
||||
"running": "Выполняется…",
|
||||
"title": "Устранение неполадок",
|
||||
"viewNetworkDiagnostics": "Открыть диагностику сети"
|
||||
},
|
||||
"useTroubleshootDiagnostics": {
|
||||
"connected": "Подключено",
|
||||
"hostDataFailed": "Не удалось прочитать данные серверов",
|
||||
"hostTestFailed": "Не удалось проверить",
|
||||
"hosts": "Серверы",
|
||||
"internet": "Интернет",
|
||||
"noConnection": "Нет подключения",
|
||||
"noPairedHosts": "Нет — отсканируйте QR-код для сопряжения",
|
||||
"pairedCount": "Сопряжено: {{count}}",
|
||||
"pairedHosts": "Сопряжённые серверы",
|
||||
"platform": "Платформа",
|
||||
"reachable": "Доступен по адресу {{endpoint}}",
|
||||
"unexpectedResponse": "Неожиданный ответ"
|
||||
},
|
||||
"webShellUpdateFailureCopy": {
|
||||
"failedAgo": "Последнее обновление с сервера {{hostName}} завершилось с ошибкой {{time}} назад: {{reason}}{{offered}}. {{outcome}}",
|
||||
"failedJustNow": "Последнее обновление с сервера {{hostName}} только что завершилось с ошибкой: {{reason}}{{offered}}. {{outcome}}"
|
||||
}
|
||||
},
|
||||
"dictation": {
|
||||
"setup": {
|
||||
"alreadyActive": "Диктовка уже используется на компьютере или другом телефоне.",
|
||||
"cloudModelNeedsKey": "Нужен ключ OpenAI",
|
||||
"deleteFailed": "Не удалось удалить модель",
|
||||
"desktopUpdateRequired": "Чтобы использовать настройки диктовки на телефоне, обновите Orca на сопряжённом компьютере.",
|
||||
"downloadFailed": "Не удалось начать загрузку",
|
||||
"loadFailed": "Не удалось загрузить модели для диктовки",
|
||||
"repairRequired": "Повторите сопряжение этого телефона с компьютером, чтобы использовать диктовку.",
|
||||
"serverUpdateRequired": "Обновите Orca на этом сервере, чтобы использовать диктовку.",
|
||||
"sessionLost": "Во время диктовки потеряно соединение с компьютером. Попробуйте ещё раз.",
|
||||
"updateFailed": "Не удалось обновить настройки диктовки"
|
||||
}
|
||||
},
|
||||
"home": {
|
||||
"accountUsageCards": {
|
||||
"sectionHeading": "Использование учётной записи",
|
||||
"sessionWindow": "5 ч",
|
||||
"systemDefault": "Как в системе",
|
||||
"weeklyWindow": "7 дн."
|
||||
},
|
||||
"appUpdateCard": {
|
||||
"dismissAccessibilityLabel": "Скрыть уведомление об обновлении Orca {{version}}",
|
||||
"title": "Доступна Orca {{version}}",
|
||||
"updateAccessibilityLabel": "Доступна Orca {{version}}. Нажмите, чтобы обновить",
|
||||
"updateHint": "Нажмите, чтобы обновить"
|
||||
},
|
||||
"emptyState": {
|
||||
"connectedDescription": "Здесь появится ваш компьютер. Все данные защищены сквозным шифрованием.",
|
||||
"connectedTitle": "Вы подключены",
|
||||
"description": "Подключитесь к Orca на компьютере, чтобы следить за агентами, открывать любой терминал и управлять работой с телефона.",
|
||||
"openDesktopDescription": "Откройте «Настройки» → «Мобильный доступ» и создайте QR-код для сопряжения.",
|
||||
"openDesktopTitle": "Откройте Orca на компьютере",
|
||||
"pairDesktop": "Подключить компьютер",
|
||||
"scanCodeDescription": "Нажмите кнопку выше, чтобы открыть сканер. Наведите камеру на QR-код на экране компьютера.",
|
||||
"scanCodeTitle": "Отсканируйте код",
|
||||
"stepsHeading": "Как это работает",
|
||||
"title": "Подключите компьютер"
|
||||
},
|
||||
"listFooter": {
|
||||
"hostPickerTitle": "Открыть задачи на сервере",
|
||||
"resumeHeading": "Продолжить",
|
||||
"tasksHeading": "Задачи"
|
||||
},
|
||||
"listHeader": {
|
||||
"agentTime": "Время агентов",
|
||||
"agentsSpawned": "Запущено агентов",
|
||||
"daysHours": "{{days}} дн. {{hours}} ч",
|
||||
"desktopsHeading": "Компьютеры",
|
||||
"hoursMinutes": "{{hours}} ч {{minutes}} мин",
|
||||
"minutes": "{{minutes}} мин",
|
||||
"prsCreated": "Создано PR",
|
||||
"title": "С возвращением"
|
||||
},
|
||||
"screen": {
|
||||
"pairingCheckFailed": "Не удалось проверить сопряжение",
|
||||
"removeHostFailed": "Не удалось удалить сервер",
|
||||
"removeHostMessage": "Удалить «{{name}}»? Позже его можно подключить снова.",
|
||||
"removeHostTitle": "Удалить сервер",
|
||||
"tryAgain": "Попробуйте ещё раз."
|
||||
},
|
||||
"tasksCard": {
|
||||
"noSources": "Источники задач не подключены",
|
||||
"openProviderTasks": "Открыть задачи {{provider}}",
|
||||
"title": "Задачи"
|
||||
}
|
||||
},
|
||||
"hooks": {
|
||||
"dictationDesktopStart": {
|
||||
"recordingFailed": "Не удалось начать запись с микрофона"
|
||||
},
|
||||
"useMobileDictation": {
|
||||
"inputClosed": "Диктовка остановлена: эта сессия больше не принимает ввод. Попробуйте ещё раз.",
|
||||
"microphoneInitFailed": "Не удалось инициализировать микрофон",
|
||||
"microphoneInterrupted": "Диктовка остановлена из-за прерывания работы микрофона. Попробуйте ещё раз.",
|
||||
"microphonePermissionDenied": "Доступ к микрофону запрещён",
|
||||
"noSpeech": "Речь не обнаружена."
|
||||
}
|
||||
},
|
||||
"host": {
|
||||
"edit": {
|
||||
"address": "Адрес",
|
||||
"addressHint": "Допустимы IP, host:port или ws:// / wss://. Для URL вида wss:// без порта используется порт 443. Если указать только адрес, сохранится текущий порт (или 6768).",
|
||||
"connectionPreview": "Подключение к {{endpoint}}",
|
||||
"goBack": "Назад",
|
||||
"help": "Измените отображаемое название или адрес подключения. Оставьте название пустым, чтобы использовать имя, полученное от компьютера. Изменение адреса меняет только адрес подключения телефона — повторное сопряжение не выполняется. Это удобно, если тот же компьютер доступен по другому IP (например, в домашней сети или через Tailscale).",
|
||||
"hostNamePlaceholder": "Название сервера",
|
||||
"hostRemoved": "Этот сервер удалён из списка на этом телефоне.",
|
||||
"loadFailed": "Не удалось загрузить данные сервера.",
|
||||
"missingHost": "Не указан сервер.",
|
||||
"name": "Название",
|
||||
"saveFailed": "Не удалось сохранить данные сервера.",
|
||||
"saveHost": "Сохранить сервер",
|
||||
"title": "Изменить сервер"
|
||||
}
|
||||
},
|
||||
"hostScreen": {
|
||||
"hostListActionSheetActions": {
|
||||
"connect": "Подключиться",
|
||||
"disconnect": "Отключить",
|
||||
"editHost": "Изменить сервер",
|
||||
"networkDiagnostics": "Диагностика сети",
|
||||
"reconnect": "Подключиться снова"
|
||||
},
|
||||
"hostPendingCreationRow": {
|
||||
"creatingWorkspace": "Создание рабочего пространства…"
|
||||
},
|
||||
"hostRailActions": {
|
||||
"accounts": "Учётные записи",
|
||||
"closeSearch": "Закрыть поиск",
|
||||
"floatingWorkspace": "Плавающее рабочее пространство",
|
||||
"newWorkspace": "Новое рабочее пространство",
|
||||
"searchWorkspaces": "Поиск рабочих пространств",
|
||||
"tasks": "Задачи"
|
||||
},
|
||||
"hostScreenHeader": {
|
||||
"accounts": "Учётные записи",
|
||||
"activeFilters_few": "Параметры рабочих пространств (активны {{count}} фильтра)",
|
||||
"activeFilters_many": "Параметры рабочих пространств (активно {{count}} фильтров)",
|
||||
"activeFilters_one": "Параметры рабочих пространств (активен {{count}} фильтр)",
|
||||
"activeFilters_other": "Параметры рабочих пространств (активно {{count}} фильтра)",
|
||||
"backToHosts": "Назад к серверам",
|
||||
"closeSearch": "Закрыть поиск",
|
||||
"floatingWorkspace": "Плавающее рабочее пространство",
|
||||
"hideSidebar": "Скрыть боковую панель",
|
||||
"reconnect": "Подключиться снова",
|
||||
"searchWorkspaces": "Поиск рабочих пространств",
|
||||
"tasks": "Задачи",
|
||||
"workspaceOptions": "Параметры рабочих пространств"
|
||||
},
|
||||
"hostScreenOverlays": {
|
||||
"deleteConfirmMessage": "Удалить «{{name}}» ({{branch}})?",
|
||||
"deleteWorktree": "Удалить рабочее дерево",
|
||||
"pin": "Закрепить",
|
||||
"primaryWorktreeDeleteHint": "Основное рабочее дерево нельзя удалить. Вместо этого уберите проект.",
|
||||
"removeHost": "Удалить сервер",
|
||||
"removeHostMessage": "Удалить «{{name}}»? Позже его можно подключить снова.",
|
||||
"sleep": "Перевести в сон",
|
||||
"unpin": "Открепить"
|
||||
},
|
||||
"hostWorkspaceList": {
|
||||
"searchPlaceholder": "Поиск рабочих пространств…",
|
||||
"searchWorkspaces": "Поиск рабочих пространств"
|
||||
},
|
||||
"workspaceDetailsSheet": {
|
||||
"checksFailing": "Проверки: не пройдены",
|
||||
"checksPassing": "Проверки: пройдены",
|
||||
"checksPending": "Проверки: в ожидании",
|
||||
"closed": "Закрыт",
|
||||
"createdByAutomation": "Создано автоматизацией",
|
||||
"createdByNamedAutomation": "Создано автоматизацией «{{name}}»",
|
||||
"createdFromCli": "Создано через Orca CLI",
|
||||
"draft": "Черновик",
|
||||
"merged": "Слит",
|
||||
"openLink": "Открыть {{label}}",
|
||||
"openLinkWithTitle": "Открыть {{label}}, {{title}}",
|
||||
"prOpen": "Открыт"
|
||||
},
|
||||
"workspaceOptionControls": {
|
||||
"agentActivity": "Активность агентов",
|
||||
"agentActivityLayout": "Вид активности агентов",
|
||||
"agentActivitySort": "Активность агентов",
|
||||
"agentActivitySortDescription": "Сначала агенты, требующие внимания, затем по недавней активности.",
|
||||
"agentStatuses": "Статусы агентов",
|
||||
"allHosts": "Все серверы",
|
||||
"allProjects": "Все проекты",
|
||||
"automation": "Автоматизация",
|
||||
"branchFolderPath": "Ветка / путь папки",
|
||||
"branchName": "Название ветки",
|
||||
"compact": "Компактно",
|
||||
"detailed": "Подробно",
|
||||
"fullList": "Полный список",
|
||||
"githubIssues": "Задачи GitHub",
|
||||
"githubTicket": "Задача GitHub",
|
||||
"host": "Сервер",
|
||||
"hover": "При наведении",
|
||||
"jiraIssue": "Задача Jira",
|
||||
"jiraIssues": "Задачи Jira",
|
||||
"linearIssue": "Задача Linear",
|
||||
"linearIssues": "Задачи Linear",
|
||||
"manual": "Вручную",
|
||||
"manualProjectOrderDescription": "Перетаскивайте проекты, чтобы менять их порядок",
|
||||
"manualWorkspaceOrderDescription": "Перетаскивайте рабочие пространства, чтобы менять их порядок в каждой группе.",
|
||||
"name": "Название",
|
||||
"none": "Нет",
|
||||
"notes": "Заметки",
|
||||
"ports": "Порты",
|
||||
"pr": "PR",
|
||||
"prMrLink": "Ссылка на PR/MR",
|
||||
"project": "Проект",
|
||||
"recent": "Недавние",
|
||||
"recentProjectOrderDescription": "Недавняя активность рабочих пространств",
|
||||
"searchProjects": "Поиск проектов",
|
||||
"searchProjectsPlaceholder": "Поиск проектов…",
|
||||
"selectedCount": "{{count}} из {{total}}",
|
||||
"status": "Статус"
|
||||
},
|
||||
"workspaceOptionsSheet": {
|
||||
"backToOptions": "Вернуться к параметрам рабочих пространств",
|
||||
"cardDisplay": "Отображение карточек",
|
||||
"cardLayout": "Вид карточек",
|
||||
"clearFilters": "Сбросить фильтры",
|
||||
"exceptDefaultBranch": "Кроме основной ветки",
|
||||
"filters": "Фильтры",
|
||||
"groupBy": "Группировать по",
|
||||
"hideAutomationCreated": "Скрыть созданные автоматизацией",
|
||||
"hideCliCreated": "Скрыть созданные через CLI",
|
||||
"hideDefaultBranch": "Скрыть основную ветку",
|
||||
"hideDetachedHead": "Скрыть с отсоединённым HEAD",
|
||||
"hideOtherClientWorkspaces": "Скрыть рабочие пространства других клиентов",
|
||||
"hideSleeping": "Скрыть спящие рабочие пространства",
|
||||
"hosts": "Серверы",
|
||||
"localLinux": "Локальный ПК с Linux",
|
||||
"localMac": "Локальный Mac",
|
||||
"localWindows": "Локальный ПК с Windows",
|
||||
"projectOrder": "Порядок проектов",
|
||||
"projects": "Проекты",
|
||||
"savingToDesktop": "Сохранение на компьютере",
|
||||
"show": "Показать",
|
||||
"showProperties": "Показывать свойства",
|
||||
"sortBy": "Сортировать по",
|
||||
"syncedAcrossDevices": "Синхронизация между вашими устройствами",
|
||||
"thisComputer": "Этот компьютер",
|
||||
"waitingForDesktop": "Всё ещё ждём ответа компьютера",
|
||||
"workspaceOptions": "Параметры рабочих пространств"
|
||||
}
|
||||
},
|
||||
"navigation": {
|
||||
"hostStackScreens": {
|
||||
"accounts": "Учётные записи",
|
||||
"agentSessionHistory": "История сессий агентов",
|
||||
"changes": "Изменения",
|
||||
"editHost": "Изменить сервер",
|
||||
"host": "Сервер",
|
||||
"pullRequest": "Пул-реквест",
|
||||
"sourceControl": "Управление версиями",
|
||||
"tasks": "Задачи",
|
||||
"terminal": "Терминал",
|
||||
"workspace": "Рабочее пространство"
|
||||
}
|
||||
},
|
||||
"notifications": {
|
||||
"notificationDeliverySection": {
|
||||
"footer": "Типы уведомлений зависят от настроек уведомлений на каждом сопряжённом компьютере. Если не пользоваться приложением 7 дней, уведомления приостанавливаются. Чтобы возобновить их, откройте приложение и подключитесь снова.",
|
||||
"onlyWhenAway": "Только когда вы не за компьютером",
|
||||
"onlyWhenAwayHint": "Если клавиатура и мышь не используются 3 минуты или компьютер заблокирован.",
|
||||
"sound": "Звук уведомлений",
|
||||
"suppressWhileFocused": "Не уведомлять при просмотре",
|
||||
"suppressWhileFocusedHint": "Не уведомлять о рабочем пространстве, открытом на этом телефоне."
|
||||
}
|
||||
},
|
||||
"onboarding": {
|
||||
"notificationOnboardingPreview": {
|
||||
"claudeNeedsInput": "Claude ждёт ответа",
|
||||
"codexFinished": "Codex завершил работу",
|
||||
"now": "сейчас",
|
||||
"testsPassing": "Тесты проходят успешно.",
|
||||
"waitingOnYou": "Ожидает ваших действий."
|
||||
},
|
||||
"onboardingPage": {
|
||||
"enableAgentNotifications": "Включить уведомления агентов",
|
||||
"enableNotifications": "Включить уведомления",
|
||||
"keepTerminal": "Оставить терминал",
|
||||
"notNow": "Не сейчас",
|
||||
"notificationsDescription": "Получайте уведомления на этом телефоне, когда агент завершает работу или ждёт ваших действий, даже если вы не пользуетесь приложением.",
|
||||
"notificationsDisclosure": "Уведомления доставляются через сервис push-уведомлений Orca после 3 минут бездействия на компьютере. Это можно в любой момент изменить в настройках.",
|
||||
"notificationsTitle": "Узнавайте, когда агенту нужна ваша помощь",
|
||||
"openInChatUi": "Открывать сессии в интерфейсе чата",
|
||||
"openInTerminal": "Открывать сессии в терминале",
|
||||
"sessionViewDescription": "Выберите, где на этом устройстве открывать поддерживаемые сессии агентов: в терминале или интерфейсе чата. Чтобы сменить режим просмотра, нажмите и удерживайте вкладку сессии. Режим по умолчанию можно позже изменить в настройках.",
|
||||
"sessionViewTitle": "Как открывать сессии?",
|
||||
"skipNotifications": "Пока не включать уведомления",
|
||||
"useChatUi": "Использовать чат"
|
||||
}
|
||||
},
|
||||
"settings": {
|
||||
"aboutScreen": {
|
||||
"openLinkFailed": "Не удалось открыть ссылку. Повторите попытку.",
|
||||
"socialProfile": "Orca в X",
|
||||
"sourceCode": "Исходный код Orca",
|
||||
"tagline": "IDE с открытым кодом и агентами: разработка в 100 раз быстрее",
|
||||
"title": "О приложении",
|
||||
"website": "Сайт Orca"
|
||||
},
|
||||
"appUpdateSettingsRows": {
|
||||
"checkFailed": "Не удалось проверить",
|
||||
"checkUpdates": "Проверить обновления",
|
||||
"checkUpdatesStatus": "Проверить обновления, {{status}}",
|
||||
"checking": "Проверка…",
|
||||
"installedVersion": "Версия {{version}}",
|
||||
"lastCheckedDays_few": "Проверено {{count}} дн. назад",
|
||||
"lastCheckedDays_many": "Проверено {{count}} дн. назад",
|
||||
"lastCheckedDays_one": "Проверено {{count}} дн. назад",
|
||||
"lastCheckedDays_other": "Проверено {{count}} дн. назад",
|
||||
"lastCheckedHours_few": "Проверено {{count}} ч назад",
|
||||
"lastCheckedHours_many": "Проверено {{count}} ч назад",
|
||||
"lastCheckedHours_one": "Проверено {{count}} ч назад",
|
||||
"lastCheckedHours_other": "Проверено {{count}} ч назад",
|
||||
"lastCheckedJustNow": "Проверено только что",
|
||||
"lastCheckedMinutes_few": "Проверено {{count}} мин назад",
|
||||
"lastCheckedMinutes_many": "Проверено {{count}} мин назад",
|
||||
"lastCheckedMinutes_one": "Проверено {{count}} мин назад",
|
||||
"lastCheckedMinutes_other": "Проверено {{count}} мин назад",
|
||||
"neverChecked": "Никогда",
|
||||
"upToDate": "Обновлений нет",
|
||||
"update": "Обновить",
|
||||
"updateToVersion": "Обновить Orca до {{version}}"
|
||||
},
|
||||
"appearance": {
|
||||
"language": {
|
||||
"description": "При выборе «Как в системе» используется язык устройства. Запросы разрешений всегда отображаются на языке устройства.",
|
||||
@@ -17,6 +624,165 @@
|
||||
"system": "Системная"
|
||||
},
|
||||
"title": "Внешний вид"
|
||||
},
|
||||
"browserSettingsScreen": {
|
||||
"desktopBrowser": "Браузер Orca на компьютере",
|
||||
"desktopBrowserDescription": "Открывать в браузере сопряжённого компьютера с трансляцией на телефон.",
|
||||
"linksDescription": "Выберите, где открывать HTTP(S)-ссылки при нажатии на них в выводе терминала.",
|
||||
"linksHeading": "ССЫЛКИ",
|
||||
"loadFailed": "Не удалось загрузить настройки браузера. Повторите попытку.",
|
||||
"openTerminalLinks": "Открывать ссылки из терминала",
|
||||
"phoneBrowser": "Браузер телефона",
|
||||
"phoneBrowserDescription": "Открывать в Safari, Chrome или другом браузере на этом телефоне.",
|
||||
"saveFailed": "Не удалось сохранить настройки браузера. Повторите попытку.",
|
||||
"title": "Браузер"
|
||||
},
|
||||
"menuItems": {
|
||||
"about": "О приложении",
|
||||
"appearance": "Внешний вид",
|
||||
"browser": "Браузер",
|
||||
"chatUi": "Интерфейс чата",
|
||||
"notifications": "Уведомления",
|
||||
"terminal": "Терминал",
|
||||
"troubleshooting": "Устранение неполадок",
|
||||
"voice": "Голос"
|
||||
},
|
||||
"menuScreen": {
|
||||
"privacyPolicy": "Политика конфиденциальности",
|
||||
"support": "Поддержка"
|
||||
},
|
||||
"nativeChatSettingsScreen": {
|
||||
"defaultViewDescription": "Выберите, как на этом устройстве открываются сессии поддерживаемых агентов (Claude, Codex и других агентов с поддержкой чата). В терминале отображается CLI в исходном виде, а в интерфейсе чата — чат, как в приложении для компьютера. Режим отдельной сессии можно изменить через меню, которое открывается долгим нажатием.",
|
||||
"defaultViewHeading": "ВИД ПО УМОЛЧАНИЮ",
|
||||
"off": "Выключено",
|
||||
"on": "Включено",
|
||||
"openInChatUi": "Открывать сессии в интерфейсе чата",
|
||||
"title": "Интерфейс чата"
|
||||
},
|
||||
"nativeNotificationDeliverySettings": {
|
||||
"loadFailed": "Не удалось загрузить настройки доставки уведомлений. Откройте этот экран заново, чтобы повторить попытку.",
|
||||
"saveFailed": "Не удалось сохранить настройки доставки уведомлений. Повторите попытку.",
|
||||
"updateDesktopHint": "Чтобы получать уведомления на этом телефоне, выполните сопряжение с компьютером, на котором установлена обновлённая версия Orca."
|
||||
},
|
||||
"notificationDisplayTest": {
|
||||
"accepted": "Служба push-уведомлений Orca приняла запрос. Проверьте, пришло ли уведомление.",
|
||||
"connectDesktop": "Подключитесь к компьютеру и повторите попытку.",
|
||||
"description": "Отправьте тестовое уведомление через службу push-уведомлений Orca.",
|
||||
"desktopUnreachable": "Не удалось связаться с компьютером. Повторите попытку.",
|
||||
"loadFailed": "Не удалось загрузить список сопряжённых компьютеров.",
|
||||
"pairDesktop": "Выполните сопряжение с компьютером и повторите попытку.",
|
||||
"pushServiceFailed": "Не удалось отправить через службу push-уведомлений Orca. Повторите попытку.",
|
||||
"rateLimited": "Слишком много уведомлений. Повторите попытку позже.",
|
||||
"reconnectPhone": "Подключитесь заново, чтобы зарегистрировать этот телефон для получения уведомлений.",
|
||||
"sendButton": "Отправить тестовое уведомление",
|
||||
"sendFailed": "Не удалось отправить тестовое push-уведомление.",
|
||||
"sending": "Отправка…",
|
||||
"troubleReceivingAlerts": "Не приходят уведомления?",
|
||||
"troubleshooting": "Устранение неполадок",
|
||||
"updateDesktop": "Обновите Orca на компьютере, чтобы выполнить эту проверку."
|
||||
},
|
||||
"notificationSettingsScreen": {
|
||||
"description": "Получайте уведомления на этом устройстве, когда агенту нужен ваш ответ или он завершает задачу.",
|
||||
"enableNotifications": "Включить уведомления",
|
||||
"loadFailed": "Не удалось загрузить настройки уведомлений. Повторите попытку.",
|
||||
"notificationsBlocked": "Уведомления отключены в системных настройках.",
|
||||
"openSettings": "Открыть настройки",
|
||||
"openSettingsFailed": "Не удалось открыть системные настройки. Повторите попытку.",
|
||||
"saveFailed": "Не удалось сохранить настройки уведомлений. Повторите попытку.",
|
||||
"title": "Уведомления"
|
||||
},
|
||||
"pendingCredentialCleanupCard": {
|
||||
"cleanupUnconfirmed_few": "Не удалось подтвердить удаление {{count}} наборов учётных данных с этого устройства.",
|
||||
"cleanupUnconfirmed_many": "Не удалось подтвердить удаление {{count}} наборов учётных данных с этого устройства.",
|
||||
"cleanupUnconfirmed_one": "Не удалось подтвердить удаление {{count}} набора учётных данных с этого устройства.",
|
||||
"cleanupUnconfirmed_other": "Не удалось подтвердить удаление {{count}} набора учётных данных с этого устройства.",
|
||||
"retryCleanup": "Повторить удаление учётных данных сопряжения",
|
||||
"retryFailed": "Подтвердить удаление по-прежнему не удалось. Повторите попытку позже.",
|
||||
"statusCheckFailed": "Не удалось проверить, удалены ли учётные данные с этого устройства. На всякий случай повторите попытку.",
|
||||
"title": "Удаление учётных данных сопряжения"
|
||||
},
|
||||
"uiLanguageSettingsSection": {
|
||||
"pseudoLanguage": "Псевдоязык (en-XA)"
|
||||
},
|
||||
"voiceSettingsScreen": {
|
||||
"connectDesktop": "Подключитесь к компьютеру, чтобы управлять настройками голоса.",
|
||||
"deleteFailed": "Не удалось удалить",
|
||||
"dictationDescription": "Диктуйте текст в любую активную панель на компьютере.",
|
||||
"dictationHeading": "ДИКТОВКА",
|
||||
"dictationMode": "Режим диктовки",
|
||||
"dictationModeDescription": "Переключение: нажмите один раз, чтобы начать, ещё раз — чтобы остановить. Удержание: диктуйте, пока удерживаете кнопку.",
|
||||
"downloadFailed": "Не удалось скачать",
|
||||
"enableDictation": "Включить голосовую диктовку",
|
||||
"holdMode": "Удержание",
|
||||
"loadFailed": "Не удалось загрузить настройки голоса",
|
||||
"loadFailedMessage": "Не удалось загрузить настройки голоса.",
|
||||
"noneSelected": "Не выбрана",
|
||||
"selectModelFailed": "Не удалось выбрать модель",
|
||||
"speechModel": "Речевая модель",
|
||||
"speechModelHeading": "РЕЧЕВАЯ МОДЕЛЬ",
|
||||
"title": "Голос",
|
||||
"toggleMode": "Переключение",
|
||||
"updateFailed": "Не удалось обновить"
|
||||
}
|
||||
},
|
||||
"webShell": {
|
||||
"shellStateScreen": {
|
||||
"checkingHost": "Проверка сервера",
|
||||
"connectToDownload": "Подключитесь к этому серверу, чтобы скачать последнюю версию приложения",
|
||||
"documentLoadFailed": "Не удалось открыть скачанное приложение.",
|
||||
"downloadFailed": "Не удалось скачать последнюю версию приложения с этого сервера.",
|
||||
"hostProfileUnreadable": "Не удалось прочитать данные этого сервера на вашем устройстве. Попробуйте ещё раз.",
|
||||
"isolationUnavailable": "WebView на этом устройстве слишком стар для безопасного открытия рабочего пространства.",
|
||||
"openingWorkspace": "Открытие рабочего пространства",
|
||||
"renderProcessGone": "Рабочее пространство перестало отвечать.",
|
||||
"statusUnreadable": "Не удалось прочитать статус этого сервера. Вернитесь назад и откройте его снова.",
|
||||
"tryAgain": "Повторить попытку"
|
||||
}
|
||||
},
|
||||
"worktree": {
|
||||
"agentRowDisplay": {
|
||||
"blocked": "Заблокирован",
|
||||
"days_few": "{{count}} д",
|
||||
"days_many": "{{count}} д",
|
||||
"days_one": "{{count}} д",
|
||||
"days_other": "{{count}} д",
|
||||
"done": "Готово",
|
||||
"failed": "Ошибка",
|
||||
"hours_few": "{{count}} ч",
|
||||
"hours_many": "{{count}} ч",
|
||||
"hours_one": "{{count}} ч",
|
||||
"hours_other": "{{count}} ч",
|
||||
"idle": "В ожидании",
|
||||
"interrupted": "Прервано",
|
||||
"justNow": "только что",
|
||||
"minutes_few": "{{count}} мин",
|
||||
"minutes_many": "{{count}} мин",
|
||||
"minutes_one": "{{count}} мин",
|
||||
"minutes_other": "{{count}} мин",
|
||||
"monitoring": "Наблюдает за фоновыми задачами",
|
||||
"stopping": "Останавливается…",
|
||||
"unconfirmed": "Не удалось подтвердить",
|
||||
"unverifiable": "Давно не было обновлений",
|
||||
"waiting": "Ожидает ввода",
|
||||
"working": "В работе"
|
||||
},
|
||||
"hostWorkspaceListStates": {
|
||||
"checkHost": "Убедитесь, что компьютер не спит и Orca запущена",
|
||||
"empty": "Нет рабочих пространств",
|
||||
"loadFailed": "Не удалось загрузить рабочие пространства с этого сервера",
|
||||
"noFilterMatches": "Нет рабочих пространств, соответствующих фильтрам",
|
||||
"noMatches": "Подходящие рабочие пространства не найдены",
|
||||
"reopen": "Откройте заново для обновления",
|
||||
"reopenDetail": "Этот сервер возвращает список рабочих пространств в формате, который эта страница пока не поддерживает.",
|
||||
"retrying": "Сбой worktree.ps ({{error}}) — автоматическая повторная попытка"
|
||||
},
|
||||
"workspaceListSections": {
|
||||
"all": "Все",
|
||||
"closed": "Закрыто",
|
||||
"done": "Готово",
|
||||
"inProgress": "В работе",
|
||||
"inReview": "На проверке",
|
||||
"pinned": "Закреплено"
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,8 +1,599 @@
|
||||
{
|
||||
"accounts": {
|
||||
"screen": {
|
||||
"agentLogin": "Ajanın kendi oturum açma bilgilerini kullan",
|
||||
"connecting": "Sunucuya bağlanılıyor…",
|
||||
"connectingTo": "{{name}} ile bağlantı kuruluyor…",
|
||||
"default": "Sistem varsayılanı",
|
||||
"hostNotFound": "Sunucu bulunamadı",
|
||||
"invalidSnapshot": "Sunucudan gelen hesap anlık görüntüsü geçersiz",
|
||||
"loadingAccounts": "Hesaplar yükleniyor…",
|
||||
"manageAccountsHint": "Masaüstünde Ayarlar → Hesaplar bölümünden hesap ekleyin veya hesaplarınızda yeniden oturum açın.",
|
||||
"refreshAccounts": "Hesapları yenile",
|
||||
"sessionWindow": "5 sa",
|
||||
"switchFailed": "Hesap değiştirilemedi",
|
||||
"title": "Hesaplar",
|
||||
"useReset": "Sıfırlama hakkını kullan",
|
||||
"weeklyWindow": "7g"
|
||||
}
|
||||
},
|
||||
"agentHistory": {
|
||||
"panel": {
|
||||
"all": "Tümü",
|
||||
"emptyScope": "Bu kapsamda geçmiş ajan oturumu yok.",
|
||||
"loadFailed": "Yüklenemedi",
|
||||
"missingResumeId": "Bu oturumun devam ettirme kimliği eksik.",
|
||||
"noSearchResults": "Aramanızla eşleşen oturum yok.",
|
||||
"noSessions": "Ajan oturumu yok",
|
||||
"placeholder": "Oturumlarda ara, repo:, path:",
|
||||
"project": "Proje",
|
||||
"refreshSessions": "Ajan oturumlarını yenile",
|
||||
"resumeFailed": "Oturuma devam edilemedi.",
|
||||
"sessionQueued": "Ajan oturumu sıraya alındı.",
|
||||
"skipped_one": "{{count}} transkript atlandı",
|
||||
"skipped_other": "{{count}} transkript atlandı",
|
||||
"title": "Ajan Oturum Geçmişi",
|
||||
"unavailable": "Ajan Oturum Geçmişi Kullanılamıyor",
|
||||
"unknownHostPlatform": "Sunucunun platformu belirlenemedi.",
|
||||
"updateHost": "Ajan oturum geçmişine göz atmak için bu sunucudaki Orca'yı güncelleyin.",
|
||||
"waitingForHost": "Sunucu bekleniyor…",
|
||||
"workspace": "Çalışma Alanı"
|
||||
},
|
||||
"resumeTarget": {
|
||||
"differentHost": "Bu oturum farklı bir sunucuya ait. Devam ettirmek için aynı sunucuda bir çalışma alanı açın.",
|
||||
"openWorkspace": "Bir oturuma devam etmeden önce bir çalışma alanı açın.",
|
||||
"runtimeUnavailable": "Çalışma zamanı ortamında barındırılan çalışma alanlarında geçmiş oturumlara devam edilemez."
|
||||
},
|
||||
"sessionCard": {
|
||||
"untitledSession": "Başlıksız oturum"
|
||||
},
|
||||
"sessionHistoryList": {
|
||||
"assistantRole": "asistan",
|
||||
"currentWorktree": "mevcut worktree",
|
||||
"messages_one": "{{count}} mesaj",
|
||||
"messages_other": "{{count}} mesaj",
|
||||
"resumeSession": "Ajan oturumuna devam et",
|
||||
"userRole": "kullanıcı"
|
||||
},
|
||||
"useAgentHistoryState": {
|
||||
"hostUnreachable": "Sunucuya ulaşılamadı",
|
||||
"loadFailed": "Ajan oturumları yüklenemedi",
|
||||
"waitingForHost": "Sunucu bekleniyor…"
|
||||
},
|
||||
"worktreeNavigationActions": {
|
||||
"agentSessionHistory": "Ajan Oturum Geçmişi",
|
||||
"sourceControl": "Kaynak Kontrolü"
|
||||
}
|
||||
},
|
||||
"app": {
|
||||
"mobileOnboarding": {
|
||||
"notificationSettingsFailed": "Bildirim ayarları güncellenemedi. Tekrar deneyin.",
|
||||
"progress": "Başlangıç adımlarındaki ilerleme",
|
||||
"saveChoiceFailed": "Seçiminiz kaydedilemedi. Tekrar deneyin.",
|
||||
"stepProgress": "{{total}} adımın {{step}}. adımı"
|
||||
},
|
||||
"notifications": {
|
||||
"description": "Uygulama kapalıyken bile ajan uyarıları alın. Uyarılar Orca'nın anlık bildirim hizmeti ve Apple veya Google üzerinden iletilir."
|
||||
},
|
||||
"pair": {
|
||||
"backToHome": "Ana ekrana dön",
|
||||
"missingCode": "Eşleştirme kodu eksik"
|
||||
},
|
||||
"pairConfirm": {
|
||||
"backToHome": "Ana ekrana dön",
|
||||
"connectTimedOut": "{{seconds}} saniye içinde bağlantı kurulamadı — işlemin nerede takıldığını görmek için aşağıdaki günlüğe bakın",
|
||||
"connecting": "Bağlanılıyor…",
|
||||
"description": "Masaüstünüzden bir eşleştirme bağlantısı açtınız. Bu masaüstünü sunucu listenize eklemek için onaylayın.",
|
||||
"pairButton": "Eşleştir",
|
||||
"pairingFailed": "Eşleştirme yapılamadı: {{error}}",
|
||||
"pairingLog": "Eşleştirme günlüğü",
|
||||
"title": "Bu masaüstüyle eşleştirilsin mi?"
|
||||
},
|
||||
"pairScan": {
|
||||
"cameraDisabledDescription": "Ayarlar bölümünde kamera erişimini etkinleştirin veya eşleştirme kodunu yapıştırın.",
|
||||
"cameraDisabledTitle": "Kamera Erişimi Devre Dışı",
|
||||
"cameraPermissionDescription": "Masaüstünüzdeki Orca'da gösterilen QR kodunu tarayın veya eşleştirme kodunu yapıştırın.",
|
||||
"connectTimedOut": "{{seconds}} saniye içinde bağlantı kurulamadı — işlemin nerede takıldığını görmek için aşağıdaki günlüğe bakın",
|
||||
"connecting": "Bağlanılıyor…",
|
||||
"invalidPairingCode": "Geçerli bir eşleştirme kodu değil — bilgisayarınızdan kopyalayıp yeniden yapıştırın",
|
||||
"invalidQrCode": "Geçerli bir Orca QR kodu değil",
|
||||
"openDesktopStep": "Bilgisayarınızda Orca'yı açın",
|
||||
"openMobileSettingsStep": "Ayarlar → Mobil bölümüne gidin",
|
||||
"openSettings": "Ayarları Aç",
|
||||
"pairingFailed": "Eşleştirme yapılamadı: {{error}}",
|
||||
"pairingLog": "Eşleştirme günlüğü",
|
||||
"pasteCodeMessage": "Bilgisayarınızda QR kodunun altında gösterilen kodu kopyalayın.",
|
||||
"pasteCodePlaceholder": "orca://pair?code=... veya kodu yapıştırın",
|
||||
"pasteCodeTitle": "Eşleştirme kodunu yapıştır",
|
||||
"pasteInstead": "Bunun yerine kod yapıştır",
|
||||
"pastePairingCode": "Veya eşleştirme kodunu yapıştır",
|
||||
"scanCodeStep": "QR kodunu tarayın",
|
||||
"title": "Masaüstüyle eşleştir",
|
||||
"tryAgain": "Tekrar Dene"
|
||||
},
|
||||
"terminalSettings": {
|
||||
"autocomplete": "Otomatik tamamlama ve düzeltme",
|
||||
"autocompleteOff": "Kapalı",
|
||||
"autocompleteOn": "Açık",
|
||||
"defaultText": "Varsayılan (%100)",
|
||||
"keepPhoneSize": "Telefon boyutunda tut (varsayılan)",
|
||||
"keyboardInputDescription": "Otomatik tamamlama, otomatik düzeltme ve yazım önerileri yalnızca arabelleğe alınan komut girişinde geçerlidir. Canlı girişten arabellekli girişe geçmek için klavyenin üzerindeki çift oklu düğmeyi kullanın; ardından Gönder düğmesine dokunmadan önce komutunuzu düzenleyin. Komutların, bayrakların veya yolların değiştirilmesini önlemek için varsayılan olarak kapalıdır.",
|
||||
"keyboardInputHeading": "KLAVYE GİRİŞİ",
|
||||
"largeText": "Büyük (%125)",
|
||||
"largerText": "Daha büyük (%150)",
|
||||
"largestText": "En Büyük (%200)",
|
||||
"leaveAppDescription": "Telefonunuzda terminal kullanırken Orca, terminali ekranınıza sığacak şekilde küçültür. Uygulamayı kapattığınızda veya başka bir uygulamaya geçtiğinizde bu ayar, terminalin telefon boyutunda kalmasını (etkileşimli CLI araçlarının düzeninin değişmemesi için) ya da masaüstü boyutuna geri dönmesini belirler. Boyutu elle değiştirmek için masaüstündeki bildirim çubuğunda Geri yükle seçeneğini her zaman kullanabilirsiniz.",
|
||||
"leaveAppHeading": "UYGULAMADAN AYRILDIĞINIZDA",
|
||||
"noPairedDesktops": "Henüz eşleştirilmiş masaüstü yok. Terminal davranışını ayarlamak için bir masaüstü eşleştirin.",
|
||||
"restoreAfterMinutes_one": "{{count}} dakika sonra",
|
||||
"restoreAfterMinutes_other": "{{count}} dakika sonra",
|
||||
"restoreAfterSeconds_one": "{{count}} sn sonra",
|
||||
"restoreAfterSeconds_other": "{{count}} sn sonra",
|
||||
"restoreHost": "{{name}} için geri yükleme",
|
||||
"smallerText": "Daha küçük (%75)",
|
||||
"smallestText": "En küçük (%50)",
|
||||
"textSize": "Metin boyutu",
|
||||
"textSizeDescription": "Terminal metninin boyutunu ayarlayın. Küçük boyutlarda, kenarlarda boşluk kalacak şekilde daha fazla sütun sığar; büyük boyutlarda daha az sütun görünür — yatay kaydırmak için yana sürükleyin. Terminalde iki parmakla yakınlaştırma da yapabilirsiniz; bu işlem bu ayarı günceller. Yalnızca bu cihazdaki görünümü etkiler; masaüstü terminalini değiştirmez.",
|
||||
"textSizeHeading": "METİN BOYUTU",
|
||||
"textSizePickerTitle": "Terminal metin boyutu",
|
||||
"title": "Terminal"
|
||||
}
|
||||
},
|
||||
"common": {
|
||||
"back": "Geri"
|
||||
"back": "Geri",
|
||||
"cancel": "İptal",
|
||||
"continue": "Devam Et",
|
||||
"copied": "Kopyalandı",
|
||||
"delete": "Sil",
|
||||
"ok": "Tamam",
|
||||
"remove": "Kaldır",
|
||||
"retry": "Yeniden dene",
|
||||
"save": "Kaydet",
|
||||
"settings": "Ayarlar",
|
||||
"unknown": "Bilinmiyor"
|
||||
},
|
||||
"connection": {
|
||||
"connectionHealth": {
|
||||
"checkConnection": "Bağlantınızı kontrol edin",
|
||||
"connectFailed": "Bağlanılamadı",
|
||||
"connected": "Bağlı",
|
||||
"connecting": "Bağlanılıyor…",
|
||||
"connectingViaRelay": "Relay üzerinden bağlanılıyor…",
|
||||
"desktopUnreachable": "Bilgisayara ulaşılamadı",
|
||||
"disconnected": "Bağlantı kesildi",
|
||||
"hostFallback": "Ana Makine",
|
||||
"hostOffline": "{{host}} çevrimdışı",
|
||||
"hostOfflineDetail": "Bilgisayarınızın uyanık, Orca'nın çalışır durumda ve oturumunuzun açık olduğunu kontrol edin",
|
||||
"labelWithHint": "{{label}} — {{hint}}",
|
||||
"pairingInvalid": "Eşleştirme geçersiz — bilgisayarınızla yeniden eşleştirin",
|
||||
"rePairDetail": "Bilgisayarınızla yeniden eşleştirin",
|
||||
"reconnecting": "Yeniden bağlanılıyor…",
|
||||
"relayAccessExpired": "{{host}} için Relay erişiminin süresi doldu",
|
||||
"relayConnectFailed": "Relay üzerinden bağlanılamadı",
|
||||
"relayUnreachable": "Relay'e ulaşılamadı",
|
||||
"signInDetail": "Yeniden bağlanmak için bilgisayarınızda Orca'da oturum açın",
|
||||
"signInRequired": "{{host}} üzerinde oturum açılması gerekiyor",
|
||||
"tailscaleHint": "Tailscale'i kontrol edin"
|
||||
},
|
||||
"connectionPathLabel": {
|
||||
"directLan": "Doğrudan · LAN",
|
||||
"directTailscale": "Doğrudan · Tailscale",
|
||||
"directViaLan": "LAN üzerinden doğrudan",
|
||||
"directViaTailscale": "Tailscale üzerinden doğrudan"
|
||||
},
|
||||
"hostEndpoint": {
|
||||
"addressRequired": "Bir ana makine adresi girin.",
|
||||
"hostnameRequired": "Ana makine adı eksik.",
|
||||
"invalidAddress": "Geçerli bir adres değil.",
|
||||
"invalidHostname": "Geçerli bir ana makine adı değil.",
|
||||
"invalidPort": "Port 1–65535 aralığında olmalıdır.",
|
||||
"invalidScheme": "ws:// veya wss:// (ya da host:port) kullanın.",
|
||||
"pathOrQueryNotAllowed": "Ana makine adresi yol veya sorgu içermemelidir."
|
||||
},
|
||||
"pairConfirmState": {
|
||||
"invalidCode": "Geçerli bir eşleştirme kodu değil",
|
||||
"missingCode": "Eşleştirme kodu eksik"
|
||||
},
|
||||
"rpcClientStreamRegistry": {
|
||||
"endedBeforeReady": "Akış isteği hazır olmadan sona erdi.",
|
||||
"interrupted": "Bağlantı kesildi"
|
||||
},
|
||||
"rpcClientTerminalBinaryFrame": {
|
||||
"unsupportedFrame": "Desteklenmeyen terminal akışı çerçevesi (opcode {{opcode}})."
|
||||
}
|
||||
},
|
||||
"diagnostics": {
|
||||
"connectionDiagnosticsView": {
|
||||
"authFailed": "kimlik doğrulanamadı",
|
||||
"connected": "bağlı",
|
||||
"connecting": "bağlanılıyor",
|
||||
"copyReport": "Raporu kopyala",
|
||||
"diagnosisHeading": "Olası neden",
|
||||
"disconnected": "bağlantı kesildi",
|
||||
"handshaking": "el sıkışması yapılıyor",
|
||||
"noEvents": "Henüz bağlantı olayı yok. Uygulama bu sunucuya bağlanmaya çalıştıkça olaylar görünür.",
|
||||
"noHosts": "Eşleştirilmiş sunucu yok.",
|
||||
"privacyHint": "Sunucu adını, uç noktayı, sürümleri, bağlantı durumunu ve olayları içeren, hassas bilgileri çıkarılmış ve boyutu sınırlı bir rapor gönderir. Terminal içeriklerini veya kimlik bilgilerini asla göndermez.",
|
||||
"reconnecting": "yeniden bağlanılıyor",
|
||||
"retrySending": "Göndermeyi yeniden dene",
|
||||
"sendDiagnostics": "Tanılama bilgilerini Orca'ya gönder",
|
||||
"sending": "Gönderiliyor…",
|
||||
"sent": "Tanılama bilgileri gönderildi",
|
||||
"stateWithAttempt": "{{state}} · {{attempts}}. deneme",
|
||||
"title": "Ağ tanılaması"
|
||||
},
|
||||
"mobileWebBundleProbeRow": {
|
||||
"assets": "Varlıklar",
|
||||
"build": "Derleme",
|
||||
"bundle": "Paket",
|
||||
"bytes": "Bayt",
|
||||
"connecting": "Bağlanıyor…",
|
||||
"elapsed": "Geçen Süre",
|
||||
"elapsedMilliseconds": "{{elapsedMs}} ms",
|
||||
"fetchBundle": "Mobil web paketini getir",
|
||||
"fetching": "Paket getiriliyor…",
|
||||
"host": "Sunucu",
|
||||
"unknownHost": "bilinmeyen sunucu"
|
||||
},
|
||||
"mobileWebShellUpdateFailureRow": {
|
||||
"title": "Çalışma alanı güncellemeleri"
|
||||
},
|
||||
"troubleshootCommonIssues": {
|
||||
"allowNotifications": "Sistem ayarlarında Orca bildirimlerine izin verildiğini ve Focus veya Do Not Disturb modunun kapalı olduğunu kontrol edin.",
|
||||
"checkTailnet": "Bilgisayarınızın uyku modunda olmadığını ve tailnet ağınızda bağlı göründüğünü kontrol edin.",
|
||||
"desktopTitle": "Masaüstü Uygulaması Çalışmıyor",
|
||||
"disableVpn": "Bu VPN'i devre dışı bırakın veya bölünmüş tünellemeyi / \"Allow LAN\" seçeneğini etkinleştirin.",
|
||||
"firewallTitle": "Güvenlik Duvarı 6768 Portunu Engelliyor",
|
||||
"keepTailscaleOn": "100.x.x.x veya *.ts.net gibi sunucu adreslerine Tailscale üzerinden bağlanılır — Tailscale'i AÇIK tutun.",
|
||||
"macosFirewall": "macOS: System Settings → Network → Firewall — Orca'ya izin verin.",
|
||||
"notificationNetwork": "Hücresel bağlantıyı veya başka bir Wi-Fi ağını deneyin. Ağ değiştirdikten sonra bildirimler geliyorsa ağınız iletimi geciktiriyor olabilir.",
|
||||
"notificationsTitle": "Anlık Bildirimler",
|
||||
"openDesktop": "Bağlantıları kabul edebilmesi için bilgisayarınızda Orca açık olmalıdır.",
|
||||
"pairAfterUpdate": "Güncellemeden sonra QR koduyla yeniden eşleştirme yapmanız gerekebilir.",
|
||||
"reconnectWifi": "Her iki cihazda da WiFi ağına yeniden bağlanmayı deneyin.",
|
||||
"restartBothApps": "Zaman aşımı devam ederse her iki uygulamayı da yeniden başlatın.",
|
||||
"restartDesktop": "Orca'yı yeniden başlatmayı deneyin — mobil bağlantı sunucusu uygulama açıldığında başlar.",
|
||||
"restartTailscale": "iOS/Android'de tünel bağlantısı fark edilmeden takılabilir: Tailscale uygulamasında Tailscale'i kapatıp yeniden açın.",
|
||||
"restrictedNetwork": "Kurum/okul ağları P2P bağlantılarını engelleyebilir — kişisel erişim noktasını deneyin.",
|
||||
"retryHost": "Sunucu listesine dönün ve yeniden denemek için sunucunuza dokunun.",
|
||||
"sameNetwork": "Her iki cihaz da aynı LAN üzerinde olmalıdır (Tailscale üzerinden bağlı değillerse).",
|
||||
"sameSubnet": "Ethernet ve WiFi aynı alt ağda olmalıdır.",
|
||||
"tailscaleTitle": "Tailscale Sunucusuna Ulaşılamıyor",
|
||||
"timeoutTitle": "Bağlantı Zaman Aşımı",
|
||||
"updateTailscale": "Tailscale uygulamasını güncelleyin — son sürümler yeniden bağlanma hatalarını giderir.",
|
||||
"vpnRouting": "Tailscale dışındaki VPN'ler yerel trafiği uzak bir sunucu üzerinden yönlendirebilir.",
|
||||
"vpnTitle": "Diğer VPN'lerden Kaynaklanan Sorunlar",
|
||||
"wifiSignal": "Telefonunuzdaki WiFi sinyal gücünü kontrol edin.",
|
||||
"wifiTitle": "Farklı WiFi Ağları",
|
||||
"windowsFirewall": "Windows: Defender Firewall → Allow app — Orca'yı Private ağlarda etkinleştirin."
|
||||
},
|
||||
"troubleshootView": {
|
||||
"commonIssues": "Yaygın sorunlar",
|
||||
"runAgain": "Yeniden çalıştır",
|
||||
"runDiagnostics": "Tanılamayı çalıştır",
|
||||
"running": "Çalıştırılıyor…",
|
||||
"title": "Sorun Giderme",
|
||||
"viewNetworkDiagnostics": "Ağ tanılamasını görüntüle"
|
||||
},
|
||||
"useTroubleshootDiagnostics": {
|
||||
"connected": "Bağlı",
|
||||
"hostDataFailed": "Sunucu verileri okunamadı",
|
||||
"hostTestFailed": "Test edilemedi",
|
||||
"hosts": "Sunucular",
|
||||
"internet": "İnternet",
|
||||
"noConnection": "Bağlantı yok",
|
||||
"noPairedHosts": "Yok — eşleştirmek için bir QR kodu tarayın",
|
||||
"pairedCount": "{{count}} sunucu eşleştirildi",
|
||||
"pairedHosts": "Eşleştirilmiş sunucular",
|
||||
"platform": "Platform",
|
||||
"reachable": "{{endpoint}} adresinden erişilebiliyor",
|
||||
"unexpectedResponse": "Beklenmeyen yanıt"
|
||||
},
|
||||
"webShellUpdateFailureCopy": {
|
||||
"failedAgo": "{{hostName}} üzerinden yapılan son güncelleme {{time}} önce tamamlanamadı: {{reason}}{{offered}}. {{outcome}}",
|
||||
"failedJustNow": "{{hostName}} üzerinden yapılan son güncelleme az önce tamamlanamadı: {{reason}}{{offered}}. {{outcome}}"
|
||||
}
|
||||
},
|
||||
"dictation": {
|
||||
"setup": {
|
||||
"alreadyActive": "Dikte şu anda bilgisayarda veya başka bir telefonda kullanılıyor.",
|
||||
"cloudModelNeedsKey": "OpenAI anahtarı gerekli",
|
||||
"deleteFailed": "Model silinemedi",
|
||||
"desktopUpdateRequired": "Mobil ses ayarlarını kullanmak için eşleştirilmiş bilgisayardaki Orca uygulamasını güncelleyin.",
|
||||
"downloadFailed": "İndirme başlatılamadı",
|
||||
"loadFailed": "Dikte modelleri yüklenemedi",
|
||||
"repairRequired": "Dikteyi kullanmak için bu telefonu bilgisayarla yeniden eşleştirin.",
|
||||
"serverUpdateRequired": "Sesli dikteyi kullanmak için bu sunucudaki Orca'yı güncelleyin.",
|
||||
"sessionLost": "Diktenin bilgisayarla bağlantısı kesildi. Yeniden deneyin.",
|
||||
"updateFailed": "Dikte ayarları güncellenemedi"
|
||||
}
|
||||
},
|
||||
"home": {
|
||||
"accountUsageCards": {
|
||||
"sectionHeading": "Hesap kullanımı",
|
||||
"sessionWindow": "5 sa",
|
||||
"systemDefault": "Sistem varsayılanı",
|
||||
"weeklyWindow": "7g"
|
||||
},
|
||||
"appUpdateCard": {
|
||||
"dismissAccessibilityLabel": "Orca {{version}} güncellemesini gizle",
|
||||
"title": "Orca {{version}} sürümü mevcut",
|
||||
"updateAccessibilityLabel": "Orca {{version}} sürümü mevcut, güncellemek için dokunun",
|
||||
"updateHint": "Güncellemek için dokunun"
|
||||
},
|
||||
"emptyState": {
|
||||
"connectedDescription": "Masaüstünüz burada görünecek. Her şey uçtan uca şifrelenir.",
|
||||
"connectedTitle": "Bağlandınız",
|
||||
"description": "Ajanlarınızı takip etmek, istediğiniz terminale erişmek ve işlerinizi telefonunuzdan yönetmek için bilgisayarınızdaki Orca ile eşleştirin.",
|
||||
"openDesktopDescription": "Ayarlar → Mobil bölümüne gidin ve eşleştirme için bir QR kodu oluşturun.",
|
||||
"openDesktopTitle": "Orca masaüstü uygulamasını açın",
|
||||
"pairDesktop": "Masaüstünü Eşleştir",
|
||||
"scanCodeDescription": "Tarayıcıyı açmak için yukarıdaki düğmeye dokunun. Kamerayı ekranınızdaki QR koduna doğrultun.",
|
||||
"scanCodeTitle": "Kodu tarayın",
|
||||
"stepsHeading": "Nasıl çalışır",
|
||||
"title": "Masaüstünüzü bağlayın"
|
||||
},
|
||||
"listFooter": {
|
||||
"hostPickerTitle": "Görevlerin Açılacağı Sunucu",
|
||||
"resumeHeading": "Devam et",
|
||||
"tasksHeading": "Görevler"
|
||||
},
|
||||
"listHeader": {
|
||||
"agentTime": "Ajan süresi",
|
||||
"agentsSpawned": "Başlatılan ajanlar",
|
||||
"daysHours": "{{days}} g {{hours}} sa",
|
||||
"desktopsHeading": "Masaüstleri",
|
||||
"hoursMinutes": "{{hours}} sa {{minutes}} dk",
|
||||
"minutes": "{{minutes}} dk",
|
||||
"prsCreated": "Oluşturulan PR'lar",
|
||||
"title": "Tekrar hoş geldiniz"
|
||||
},
|
||||
"screen": {
|
||||
"pairingCheckFailed": "Eşleştirme kontrol edilemedi",
|
||||
"removeHostFailed": "Sunucu kaldırılamadı",
|
||||
"removeHostMessage": "“{{name}}” kaldırılsın mı? Daha sonra yeniden eşleştirebilirsiniz.",
|
||||
"removeHostTitle": "Sunucuyu Kaldır",
|
||||
"tryAgain": "Lütfen tekrar deneyin."
|
||||
},
|
||||
"tasksCard": {
|
||||
"noSources": "Bağlı görev kaynağı yok",
|
||||
"openProviderTasks": "{{provider}} görevlerini aç",
|
||||
"title": "Görevler"
|
||||
}
|
||||
},
|
||||
"hooks": {
|
||||
"dictationDesktopStart": {
|
||||
"recordingFailed": "Mikrofon kaydı başlatılamadı"
|
||||
},
|
||||
"useMobileDictation": {
|
||||
"inputClosed": "Bu oturum artık girdi kabul etmediği için sesli dikte durdu. Tekrar deneyin.",
|
||||
"microphoneInitFailed": "Mikrofon başlatılamadı",
|
||||
"microphoneInterrupted": "Mikrofon kesintisi nedeniyle sesli dikte durdu. Tekrar deneyin.",
|
||||
"microphonePermissionDenied": "Mikrofon izni reddedildi",
|
||||
"noSpeech": "Konuşma algılanmadı."
|
||||
}
|
||||
},
|
||||
"host": {
|
||||
"edit": {
|
||||
"address": "Adres",
|
||||
"addressHint": "IP, host:port veya ws:// / wss:// kabul edilir. Bağlantı noktası belirtilmeyen wss:// URL'lerinde 443 kullanılır. Yalnızca adres girilirse mevcut bağlantı noktası (veya 6768) korunur.",
|
||||
"connectionPreview": "Bağlanılacak adres: {{endpoint}}",
|
||||
"goBack": "Geri git",
|
||||
"help": "Görünen adı veya bağlantı adresini değiştirin. Masaüstünün bildirdiği adı kullanmak için ad alanını boş bırakın. Adres değişikliği yalnızca bu telefonun bağlandığı yeri değiştirir; yeniden eşleştirme yapmaz. Aynı masaüstüne farklı bir IP adresinden erişilebildiğinde bunu kullanın (örneğin evdeki LAN veya Tailscale üzerinden).",
|
||||
"hostNamePlaceholder": "Sunucu adı",
|
||||
"hostRemoved": "Bu sunucu bu telefondan kaldırıldı.",
|
||||
"loadFailed": "Sunucu yüklenemedi.",
|
||||
"missingHost": "Sunucu bilgisi eksik.",
|
||||
"name": "Ad",
|
||||
"saveFailed": "Sunucu kaydedilemedi.",
|
||||
"saveHost": "Sunucuyu kaydet",
|
||||
"title": "Sunucuyu düzenle"
|
||||
}
|
||||
},
|
||||
"hostScreen": {
|
||||
"hostListActionSheetActions": {
|
||||
"connect": "Bağlan",
|
||||
"disconnect": "Bağlantıyı kes",
|
||||
"editHost": "Sunucuyu düzenle",
|
||||
"networkDiagnostics": "Ağ tanılama",
|
||||
"reconnect": "Yeniden bağlan"
|
||||
},
|
||||
"hostPendingCreationRow": {
|
||||
"creatingWorkspace": "Çalışma alanı oluşturuluyor…"
|
||||
},
|
||||
"hostRailActions": {
|
||||
"accounts": "Hesaplar",
|
||||
"closeSearch": "Aramayı kapat",
|
||||
"floatingWorkspace": "Yüzen Çalışma Alanı",
|
||||
"newWorkspace": "Yeni çalışma alanı",
|
||||
"searchWorkspaces": "Çalışma alanlarını ara",
|
||||
"tasks": "Görevler"
|
||||
},
|
||||
"hostScreenHeader": {
|
||||
"accounts": "Hesaplar",
|
||||
"activeFilters_one": "Çalışma alanı seçenekleri ({{count}} filtre etkin)",
|
||||
"activeFilters_other": "Çalışma alanı seçenekleri ({{count}} filtre etkin)",
|
||||
"backToHosts": "Sunuculara dön",
|
||||
"closeSearch": "Aramayı kapat",
|
||||
"floatingWorkspace": "Yüzen Çalışma Alanı",
|
||||
"hideSidebar": "Kenar çubuğunu gizle",
|
||||
"reconnect": "Yeniden bağlan",
|
||||
"searchWorkspaces": "Çalışma alanlarını ara",
|
||||
"tasks": "Görevler",
|
||||
"workspaceOptions": "Çalışma alanı seçenekleri"
|
||||
},
|
||||
"hostScreenOverlays": {
|
||||
"deleteConfirmMessage": "“{{name}}” ({{branch}}) silinsin mi?",
|
||||
"deleteWorktree": "Worktree'yi Sil",
|
||||
"pin": "Sabitle",
|
||||
"primaryWorktreeDeleteHint": "Birincil worktree, silinemez. Bunun yerine projeyi kaldırın.",
|
||||
"removeHost": "Sunucuyu Kaldır",
|
||||
"removeHostMessage": "“{{name}}” kaldırılsın mı? Daha sonra yeniden eşleştirebilirsiniz.",
|
||||
"sleep": "Uyut",
|
||||
"unpin": "Sabitlemeyi Kaldır"
|
||||
},
|
||||
"hostWorkspaceList": {
|
||||
"searchPlaceholder": "Çalışma alanlarını ara…",
|
||||
"searchWorkspaces": "Çalışma alanlarını ara"
|
||||
},
|
||||
"workspaceDetailsSheet": {
|
||||
"checksFailing": "Kontroller: Başarısız",
|
||||
"checksPassing": "Kontroller: Başarılı",
|
||||
"checksPending": "Kontroller: Beklemede",
|
||||
"closed": "Kapalı",
|
||||
"createdByAutomation": "Otomasyon tarafından oluşturuldu",
|
||||
"createdByNamedAutomation": "“{{name}}” otomasyonu tarafından oluşturuldu",
|
||||
"createdFromCli": "Orca CLI ile oluşturuldu",
|
||||
"draft": "Taslak",
|
||||
"merged": "Merge edildi",
|
||||
"openLink": "{{label}} bağlantısını aç",
|
||||
"openLinkWithTitle": "{{label}} bağlantısını aç: {{title}}",
|
||||
"prOpen": "Açık"
|
||||
},
|
||||
"workspaceOptionControls": {
|
||||
"agentActivity": "Ajan etkinliği",
|
||||
"agentActivityLayout": "Ajan etkinliği düzeni",
|
||||
"agentActivitySort": "Ajan Etkinliği",
|
||||
"agentActivitySortDescription": "Önce ilgilenilmesi gereken ajanlar, ardından en son etkinlik.",
|
||||
"agentStatuses": "Ajan durumları",
|
||||
"allHosts": "Tüm ana makineler",
|
||||
"allProjects": "Tüm projeler",
|
||||
"automation": "Otomasyon",
|
||||
"branchFolderPath": "Branch / klasör yolu",
|
||||
"branchName": "Branch adı",
|
||||
"compact": "Kompakt",
|
||||
"detailed": "Ayrıntılı",
|
||||
"fullList": "Tam liste",
|
||||
"githubIssues": "GitHub sorunları",
|
||||
"githubTicket": "GitHub sorunu",
|
||||
"host": "Ana Makine",
|
||||
"hover": "Üzerine gelme",
|
||||
"jiraIssue": "Jira sorunu",
|
||||
"jiraIssues": "Jira sorunları",
|
||||
"linearIssue": "Linear sorunu",
|
||||
"linearIssues": "Linear sorunları",
|
||||
"manual": "Manuel",
|
||||
"manualProjectOrderDescription": "Projeleri düzenlemek için sürükleyin",
|
||||
"manualWorkspaceOrderDescription": "Çalışma alanlarını her grup içinde düzenlemek için sürükleyin.",
|
||||
"name": "Ad",
|
||||
"none": "Yok",
|
||||
"notes": "Notlar",
|
||||
"ports": "Portlar",
|
||||
"pr": "PR",
|
||||
"prMrLink": "PR/MR bağlantısı",
|
||||
"project": "Proje",
|
||||
"recent": "Son",
|
||||
"recentProjectOrderDescription": "En son çalışma alanı etkinliği",
|
||||
"searchProjects": "Proje ara",
|
||||
"searchProjectsPlaceholder": "Proje ara…",
|
||||
"selectedCount": "{{total}} öğeden {{count}}",
|
||||
"status": "Durum"
|
||||
},
|
||||
"workspaceOptionsSheet": {
|
||||
"backToOptions": "Çalışma alanı seçenekleri bölümüne dön",
|
||||
"cardDisplay": "Kart görünümü",
|
||||
"cardLayout": "Kart düzeni",
|
||||
"clearFilters": "Filtreleri temizle",
|
||||
"exceptDefaultBranch": "Varsayılan branch hariç",
|
||||
"filters": "Filtreler",
|
||||
"groupBy": "Gruplama ölçütü",
|
||||
"hideAutomationCreated": "Otomasyonla oluşturulanları gizle",
|
||||
"hideCliCreated": "CLI ile oluşturulanları gizle",
|
||||
"hideDefaultBranch": "Varsayılan branch'i gizle",
|
||||
"hideDetachedHead": "Detached HEAD durumundakileri gizle",
|
||||
"hideOtherClientWorkspaces": "Diğer istemcilerin çalışma alanlarını gizle",
|
||||
"hideSleeping": "Uyuyanları gizle",
|
||||
"hosts": "Ana Makineler",
|
||||
"localLinux": "Yerel Linux",
|
||||
"localMac": "Yerel Mac",
|
||||
"localWindows": "Yerel Windows",
|
||||
"projectOrder": "Proje sırası",
|
||||
"projects": "Projeler",
|
||||
"savingToDesktop": "Bilgisayara kaydediliyor",
|
||||
"show": "Göster",
|
||||
"showProperties": "Özellikleri göster",
|
||||
"sortBy": "Sıralama ölçütü",
|
||||
"syncedAcrossDevices": "Cihazlarınız arasında eşitlenir",
|
||||
"thisComputer": "Bu bilgisayar",
|
||||
"waitingForDesktop": "Bilgisayardan hâlâ yanıt bekleniyor",
|
||||
"workspaceOptions": "Çalışma alanı seçenekleri"
|
||||
}
|
||||
},
|
||||
"navigation": {
|
||||
"hostStackScreens": {
|
||||
"accounts": "Hesaplar",
|
||||
"agentSessionHistory": "Ajan Oturum Geçmişi",
|
||||
"changes": "Değişiklikler",
|
||||
"editHost": "Sunucuyu düzenle",
|
||||
"host": "Sunucu",
|
||||
"pullRequest": "Pull Request",
|
||||
"sourceControl": "Kaynak Kontrolü",
|
||||
"tasks": "Görevler",
|
||||
"terminal": "Terminal",
|
||||
"workspace": "Çalışma Alanı"
|
||||
}
|
||||
},
|
||||
"notifications": {
|
||||
"notificationDeliverySection": {
|
||||
"footer": "Uyarı türleri, eşleştirilen her bilgisayarın bildirim ayarlarına göre belirlenir. Bu uygulama 7 gün kullanılmazsa bildirimler duraklatılır; sürdürmek için uygulamayı açıp yeniden bağlanın.",
|
||||
"onlyWhenAway": "Yalnızca bilgisayardan uzaktayken",
|
||||
"onlyWhenAwayHint": "3 dakika boyunca klavye veya fare etkinliği olmadığında ya da bilgisayar kilitliyken.",
|
||||
"sound": "Bildirim sesi",
|
||||
"suppressWhileFocused": "Görüntülerken bildirim gönderme",
|
||||
"suppressWhileFocusedHint": "Bu telefonda açık olan çalışma alanı için bildirim gönderilmez."
|
||||
}
|
||||
},
|
||||
"onboarding": {
|
||||
"notificationOnboardingPreview": {
|
||||
"claudeNeedsInput": "Claude girdi bekliyor",
|
||||
"codexFinished": "Codex işi tamamladı",
|
||||
"now": "şimdi",
|
||||
"testsPassing": "Testler geçiyor.",
|
||||
"waitingOnYou": "Sizden yanıt bekleniyor."
|
||||
},
|
||||
"onboardingPage": {
|
||||
"enableAgentNotifications": "Ajan bildirimlerini etkinleştir",
|
||||
"enableNotifications": "Bildirimleri etkinleştir",
|
||||
"keepTerminal": "Terminalle devam et",
|
||||
"notNow": "Şimdi değil",
|
||||
"notificationsDescription": "Uygulamayı kullanmasanız bile bir ajan işini bitirdiğinde veya beklediğinde bu telefonda bildirim alın.",
|
||||
"notificationsDisclosure": "Bilgisayarınız 3 dakika boşta kaldıktan sonra Orca'nın anlık bildirim hizmeti üzerinden iletilir. Bunu Ayarlar bölümünden istediğiniz zaman değiştirebilirsiniz.",
|
||||
"notificationsTitle": "Bir ajanın size ihtiyacı olduğunda haberdar olun",
|
||||
"openInChatUi": "Oturumları Sohbet Arayüzü ile aç",
|
||||
"openInTerminal": "Oturumları terminalde aç",
|
||||
"sessionViewDescription": "Bu cihazda desteklenen ajan oturumlarının terminalde mi yoksa Sohbet Arayüzü ile mi açılacağını seçin. Görünümünü değiştirmek için oturum sekmesine uzun basın veya varsayılanı daha sonra Ayarlar bölümünden değiştirin.",
|
||||
"sessionViewTitle": "Oturumlar nasıl açılsın?",
|
||||
"skipNotifications": "Bildirimleri şimdilik atla",
|
||||
"useChatUi": "Sohbet Arayüzünü kullan"
|
||||
}
|
||||
},
|
||||
"settings": {
|
||||
"aboutScreen": {
|
||||
"openLinkFailed": "Bağlantı açılamadı. Yeniden deneyin.",
|
||||
"socialProfile": "X'te Orca",
|
||||
"sourceCode": "Orca kaynak kodu",
|
||||
"tagline": "100 kat daha üretken geliştiriciler için açık kaynaklı ajan IDE'si",
|
||||
"title": "Hakkında",
|
||||
"website": "Orca web sitesi"
|
||||
},
|
||||
"appUpdateSettingsRows": {
|
||||
"checkFailed": "Kontrol edilemedi",
|
||||
"checkUpdates": "Güncellemeleri denetle",
|
||||
"checkUpdatesStatus": "Güncellemeleri denetle, {{status}}",
|
||||
"checking": "Kontrol ediliyor…",
|
||||
"installedVersion": "Sürüm {{version}}",
|
||||
"lastCheckedDays_one": "Son kontrol: {{count}} gün önce",
|
||||
"lastCheckedDays_other": "Son kontrol: {{count}} gün önce",
|
||||
"lastCheckedHours_one": "Son kontrol: {{count}} sa önce",
|
||||
"lastCheckedHours_other": "Son kontrol: {{count}} sa önce",
|
||||
"lastCheckedJustNow": "Son kontrol: az önce",
|
||||
"lastCheckedMinutes_one": "Son kontrol: {{count}} dk önce",
|
||||
"lastCheckedMinutes_other": "Son kontrol: {{count}} dk önce",
|
||||
"neverChecked": "Hiçbir zaman",
|
||||
"upToDate": "Güncel",
|
||||
"update": "Güncelle",
|
||||
"updateToVersion": "Orca {{version}} sürümüne güncelle"
|
||||
},
|
||||
"appearance": {
|
||||
"language": {
|
||||
"description": "Sistem seçeneği bu cihazın dilini kullanır. İzin istekleri her zaman cihazın dilinde gösterilir.",
|
||||
@@ -17,6 +608,157 @@
|
||||
"system": "Sistem"
|
||||
},
|
||||
"title": "Görünüm"
|
||||
},
|
||||
"browserSettingsScreen": {
|
||||
"desktopBrowser": "Masaüstündeki Orca tarayıcısı",
|
||||
"desktopBrowserDescription": "Eşleştirdiğiniz masaüstü bilgisayardan aktarılan tarayıcıda açın.",
|
||||
"linksDescription": "Terminal çıktısında dokunduğunuz HTTP(S) bağlantılarının nerede açılacağını seçin.",
|
||||
"linksHeading": "BAĞLANTILAR",
|
||||
"loadFailed": "Tarayıcı tercihleri yüklenemedi. Yeniden deneyin.",
|
||||
"openTerminalLinks": "Terminal bağlantılarını aç",
|
||||
"phoneBrowser": "Telefon tarayıcısı",
|
||||
"phoneBrowserDescription": "Bu telefondaki Safari, Chrome veya başka bir tarayıcıda açın.",
|
||||
"saveFailed": "Tarayıcı tercihleri kaydedilemedi. Yeniden deneyin.",
|
||||
"title": "Tarayıcı"
|
||||
},
|
||||
"menuItems": {
|
||||
"about": "Hakkında",
|
||||
"appearance": "Görünüm",
|
||||
"browser": "Tarayıcı",
|
||||
"chatUi": "Sohbet Arayüzü",
|
||||
"notifications": "Bildirimler",
|
||||
"terminal": "Terminal",
|
||||
"troubleshooting": "Sorun Giderme",
|
||||
"voice": "Ses"
|
||||
},
|
||||
"menuScreen": {
|
||||
"privacyPolicy": "Gizlilik Politikası",
|
||||
"support": "Destek"
|
||||
},
|
||||
"nativeChatSettingsScreen": {
|
||||
"defaultViewDescription": "Desteklenen ajan oturumlarının (Claude, Codex ve sohbet destekli diğer ajanlar) bu cihazda nasıl açılacağını seçin. Terminal, CLI'yi olduğu gibi gösterir; Sohbet Arayüzü ise masaüstü uygulamasındaki gibi bir sohbet arayüzü sunar. Her oturumun görünümünü, oturuma uzun basınca açılan menüden ayrıca değiştirebilirsiniz.",
|
||||
"defaultViewHeading": "VARSAYILAN GÖRÜNÜM",
|
||||
"off": "Kapalı",
|
||||
"on": "Açık",
|
||||
"openInChatUi": "Oturumları Sohbet Arayüzü ile aç",
|
||||
"title": "Sohbet Arayüzü"
|
||||
},
|
||||
"nativeNotificationDeliverySettings": {
|
||||
"loadFailed": "İletim ayarları yüklenemedi. Yeniden denemek için bu ekranı tekrar açın.",
|
||||
"saveFailed": "İletim ayarları kaydedilemedi. Yeniden deneyin.",
|
||||
"updateDesktopHint": "Bu telefonda bildirim almak için güncel bir masaüstü uygulamasıyla eşleştirin."
|
||||
},
|
||||
"notificationDisplayTest": {
|
||||
"accepted": "Orca'nın anlık bildirim hizmeti tarafından kabul edildi. Bildirimin gelip gelmediğini kontrol edin.",
|
||||
"connectDesktop": "Bir masaüstü bilgisayara bağlanın ve yeniden deneyin.",
|
||||
"description": "Orca'nın anlık bildirim hizmeti üzerinden bir test bildirimi gönderin.",
|
||||
"desktopUnreachable": "Masaüstü bilgisayara ulaşılamadı. Yeniden deneyin.",
|
||||
"loadFailed": "Eşleştirilmiş masaüstü bilgisayarlar yüklenemedi.",
|
||||
"pairDesktop": "Bir masaüstü bilgisayarla eşleştirin ve yeniden deneyin.",
|
||||
"pushServiceFailed": "Orca'nın anlık bildirim hizmeti üzerinden gönderilemedi. Yeniden deneyin.",
|
||||
"rateLimited": "Çok fazla bildirim gönderildi. Daha sonra yeniden deneyin.",
|
||||
"reconnectPhone": "Bu telefonu bildirimler için kaydetmek üzere yeniden bağlanın.",
|
||||
"sendButton": "Test bildirimi gönder",
|
||||
"sendFailed": "Anlık bildirim testi gönderilemedi.",
|
||||
"sending": "Gönderiliyor…",
|
||||
"troubleReceivingAlerts": "Bildirim almakta sorun mu yaşıyorsunuz?",
|
||||
"troubleshooting": "Sorun Giderme",
|
||||
"updateDesktop": "Bu testi çalıştırmak için masaüstü uygulamanızı güncelleyin."
|
||||
},
|
||||
"notificationSettingsScreen": {
|
||||
"description": "Bir ajan sizden yanıt beklediğinde veya bir görevi tamamladığında bu cihazda bildirim alın.",
|
||||
"enableNotifications": "Bildirimleri etkinleştir",
|
||||
"loadFailed": "Bildirim ayarları yüklenemedi. Yeniden deneyin.",
|
||||
"notificationsBlocked": "Sistem ayarlarında bildirimler devre dışı.",
|
||||
"openSettings": "Ayarları Aç",
|
||||
"openSettingsFailed": "Sistem ayarları açılamadı. Yeniden deneyin.",
|
||||
"saveFailed": "Bildirim ayarları kaydedilemedi. Yeniden deneyin.",
|
||||
"title": "Bildirimler"
|
||||
},
|
||||
"pendingCredentialCleanupCard": {
|
||||
"cleanupUnconfirmed_one": "Bu cihazdaki {{count}} kimlik bilgisinin temizlendiği doğrulanamadı.",
|
||||
"cleanupUnconfirmed_other": "Bu cihazdaki {{count}} kimlik bilgisinin temizlendiği doğrulanamadı.",
|
||||
"retryCleanup": "Eşleştirme kimlik bilgilerini temizlemeyi yeniden dene",
|
||||
"retryFailed": "Temizleme işlemi hâlâ doğrulanamadı. Daha sonra yeniden deneyin.",
|
||||
"statusCheckFailed": "Bu cihazdaki temizleme işleminin durumu kontrol edilemedi. Önlem olarak yeniden deneyin.",
|
||||
"title": "Eşleştirme kimlik bilgilerini temizleme"
|
||||
},
|
||||
"uiLanguageSettingsSection": {
|
||||
"pseudoLanguage": "Sözde dil (en-XA)"
|
||||
},
|
||||
"voiceSettingsScreen": {
|
||||
"connectDesktop": "Ses ayarlarını yönetmek için bir masaüstüne bağlanın.",
|
||||
"deleteFailed": "Silinemedi",
|
||||
"dictationDescription": "Masaüstünüzde odaktaki herhangi bir bölmeye metin dikte edin.",
|
||||
"dictationHeading": "DİKTE",
|
||||
"dictationMode": "Dikte Modu",
|
||||
"dictationModeDescription": "Aç/kapat: başlatmak için bir kez, durdurmak için tekrar basın. Basılı tut: basılı tuttuğunuz sürece dikte edin.",
|
||||
"downloadFailed": "İndirme tamamlanamadı",
|
||||
"enableDictation": "Sesli Dikteyi Etkinleştir",
|
||||
"holdMode": "Basılı Tut",
|
||||
"loadFailed": "Ses ayarları yüklenemedi",
|
||||
"loadFailedMessage": "Ses ayarları yüklenemedi.",
|
||||
"noneSelected": "Seçim yok",
|
||||
"selectModelFailed": "Model seçilemedi",
|
||||
"speechModel": "Konuşma Modeli",
|
||||
"speechModelHeading": "KONUŞMA MODELİ",
|
||||
"title": "Ses",
|
||||
"toggleMode": "Aç/kapat",
|
||||
"updateFailed": "Güncellenemedi"
|
||||
}
|
||||
},
|
||||
"webShell": {
|
||||
"shellStateScreen": {
|
||||
"checkingHost": "Sunucu kontrol ediliyor",
|
||||
"connectToDownload": "Uygulamanın son sürümünü indirmek için bu sunucuya bağlanın",
|
||||
"documentLoadFailed": "İndirilen uygulama açılamadı.",
|
||||
"downloadFailed": "Uygulamanın son sürümü bu sunucudan indirilemedi.",
|
||||
"hostProfileUnreadable": "Bu sunucunun bilgileri cihazınızda okunamadı. Tekrar deneyin.",
|
||||
"isolationUnavailable": "Bu cihazın WebView sürümü, çalışma alanını güvenle açmak için çok eski.",
|
||||
"openingWorkspace": "Çalışma alanı açılıyor",
|
||||
"renderProcessGone": "Çalışma alanı yanıt vermeyi durdurdu.",
|
||||
"statusUnreadable": "Bu sunucunun durumu okunamadı. Geri dönüp yeniden açın.",
|
||||
"tryAgain": "Tekrar dene"
|
||||
}
|
||||
},
|
||||
"worktree": {
|
||||
"agentRowDisplay": {
|
||||
"blocked": "Engellendi",
|
||||
"days_one": "{{count}}g",
|
||||
"days_other": "{{count}}g",
|
||||
"done": "Tamamlandı",
|
||||
"failed": "Başarısız",
|
||||
"hours_one": "{{count}}sa",
|
||||
"hours_other": "{{count}}sa",
|
||||
"idle": "Boşta",
|
||||
"interrupted": "Kesildi",
|
||||
"justNow": "az önce",
|
||||
"minutes_one": "{{count}}dk",
|
||||
"minutes_other": "{{count}}dk",
|
||||
"monitoring": "Arka plan görevlerini izliyor",
|
||||
"stopping": "Durduruluyor…",
|
||||
"unconfirmed": "Doğrulanamadı",
|
||||
"unverifiable": "Yakın zamanda güncelleme yok",
|
||||
"waiting": "Girdi bekleniyor",
|
||||
"working": "Çalışıyor"
|
||||
},
|
||||
"hostWorkspaceListStates": {
|
||||
"checkHost": "Bilgisayarınızın uyanık ve Orca'nın çalışır durumda olduğunu kontrol edin",
|
||||
"empty": "Çalışma alanı yok",
|
||||
"loadFailed": "Bu ana makineden çalışma alanları yüklenemedi",
|
||||
"noFilterMatches": "Filtrelerle eşleşen çalışma alanı yok",
|
||||
"noMatches": "Eşleşen çalışma alanı yok",
|
||||
"reopen": "Güncellemek için yeniden açın",
|
||||
"reopenDetail": "Bu ana bilgisayar, çalışma alanlarını bu sayfanın henüz tanımadığı bir biçimde listeliyor.",
|
||||
"retrying": "worktree.ps tamamlanamadı ({{error}}) — otomatik olarak yeniden deneniyor"
|
||||
},
|
||||
"workspaceListSections": {
|
||||
"all": "Tümü",
|
||||
"closed": "Kapalı",
|
||||
"done": "Tamamlandı",
|
||||
"inProgress": "Devam ediyor",
|
||||
"inReview": "İncelemede",
|
||||
"pinned": "Sabitlenenler"
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,8 +1,599 @@
|
||||
{
|
||||
"accounts": {
|
||||
"screen": {
|
||||
"agentLogin": "使用代理程式本身的登入資訊",
|
||||
"connecting": "正在連線至主機…",
|
||||
"connectingTo": "正在連線至 {{name}}…",
|
||||
"default": "系統預設",
|
||||
"hostNotFound": "找不到主機",
|
||||
"invalidSnapshot": "主機傳回的帳戶快照無效",
|
||||
"loadingAccounts": "正在載入帳戶…",
|
||||
"manageAccountsHint": "請至電腦版的「設定」→「帳戶」,新增帳戶或重新驗證。",
|
||||
"refreshAccounts": "重新整理帳戶",
|
||||
"sessionWindow": "5 小時",
|
||||
"switchFailed": "無法切換帳戶",
|
||||
"title": "帳戶",
|
||||
"useReset": "使用重設額度",
|
||||
"weeklyWindow": "7 天"
|
||||
}
|
||||
},
|
||||
"agentHistory": {
|
||||
"panel": {
|
||||
"all": "全部",
|
||||
"emptyScope": "此範圍內沒有過往的代理程式工作階段。",
|
||||
"loadFailed": "無法載入",
|
||||
"missingResumeId": "此工作階段缺少恢復識別碼。",
|
||||
"noSearchResults": "沒有符合搜尋條件的工作階段。",
|
||||
"noSessions": "沒有代理程式工作階段",
|
||||
"placeholder": "搜尋工作階段,repo:、path:",
|
||||
"project": "專案",
|
||||
"refreshSessions": "重新整理代理程式工作階段",
|
||||
"resumeFailed": "無法恢復工作階段。",
|
||||
"sessionQueued": "代理程式工作階段已排入佇列。",
|
||||
"skipped_one": "跳過了 {{count}} 個記錄",
|
||||
"skipped_other": "跳過了 {{count}} 個記錄",
|
||||
"title": "代理程式工作階段歷史",
|
||||
"unavailable": "無法使用代理程式工作階段歷史",
|
||||
"unknownHostPlatform": "無法判斷主機平台。",
|
||||
"updateHost": "請更新此主機上的 Orca,以瀏覽代理程式工作階段歷史。",
|
||||
"waitingForHost": "正在等待主機…",
|
||||
"workspace": "工作區"
|
||||
},
|
||||
"resumeTarget": {
|
||||
"differentHost": "此工作階段屬於其他主機。請開啟同一主機上的工作區以恢復它。",
|
||||
"openWorkspace": "恢復工作階段前請先開啟一個工作區。",
|
||||
"runtimeUnavailable": "由執行環境代管的工作區無法從歷史記錄恢復工作階段。"
|
||||
},
|
||||
"sessionCard": {
|
||||
"untitledSession": "未命名的工作階段"
|
||||
},
|
||||
"sessionHistoryList": {
|
||||
"assistantRole": "助理",
|
||||
"currentWorktree": "目前工作樹",
|
||||
"messages_one": "{{count}} 則訊息",
|
||||
"messages_other": "{{count}} 則訊息",
|
||||
"resumeSession": "恢復代理程式工作階段",
|
||||
"userRole": "使用者"
|
||||
},
|
||||
"useAgentHistoryState": {
|
||||
"hostUnreachable": "無法連上主機",
|
||||
"loadFailed": "無法載入代理程式工作階段",
|
||||
"waitingForHost": "正在等待主機…"
|
||||
},
|
||||
"worktreeNavigationActions": {
|
||||
"agentSessionHistory": "代理程式工作階段歷史",
|
||||
"sourceControl": "原始碼控制"
|
||||
}
|
||||
},
|
||||
"app": {
|
||||
"mobileOnboarding": {
|
||||
"notificationSettingsFailed": "無法更新通知設定,請重試。",
|
||||
"progress": "入門導覽進度",
|
||||
"saveChoiceFailed": "無法儲存您的選擇,請重試。",
|
||||
"stepProgress": "第 {{step}} 步,共 {{total}} 步"
|
||||
},
|
||||
"notifications": {
|
||||
"description": "即使關閉應用程式,仍可收到代理程式提醒。通知透過 Orca 的推播服務與 Apple 或 Google 傳送。"
|
||||
},
|
||||
"pair": {
|
||||
"backToHome": "返回首頁",
|
||||
"missingCode": "缺少配對碼"
|
||||
},
|
||||
"pairConfirm": {
|
||||
"backToHome": "返回首頁",
|
||||
"connectTimedOut": "未能在 {{seconds}} 秒內連線;請查看下方記錄,確認停在哪個步驟",
|
||||
"connecting": "連線中…",
|
||||
"description": "您開啟了來自電腦的配對連結。確認後,即可將該電腦加入主機清單。",
|
||||
"pairButton": "配對",
|
||||
"pairingFailed": "配對失敗:{{error}}",
|
||||
"pairingLog": "配對記錄",
|
||||
"title": "要與這台電腦配對嗎?"
|
||||
},
|
||||
"pairScan": {
|
||||
"cameraDisabledDescription": "請在「設定」中啟用相機存取權限,或改為貼上配對碼。",
|
||||
"cameraDisabledTitle": "相機存取權限已停用",
|
||||
"cameraPermissionDescription": "掃描電腦上 Orca 顯示的 QR Code,或改為貼上配對碼。",
|
||||
"connectTimedOut": "未能在 {{seconds}} 秒內連線;請查看下方記錄,確認停在哪個步驟",
|
||||
"connecting": "連線中…",
|
||||
"invalidPairingCode": "配對碼無效,請從電腦複製後重新貼上",
|
||||
"invalidQrCode": "無效的 Orca QR Code",
|
||||
"openDesktopStep": "在電腦上開啟 Orca",
|
||||
"openMobileSettingsStep": "前往「設定」→「行動版」",
|
||||
"openSettings": "開啟設定",
|
||||
"pairingFailed": "配對失敗:{{error}}",
|
||||
"pairingLog": "配對記錄",
|
||||
"pasteCodeMessage": "複製電腦上 QR Code 下方顯示的配對碼。",
|
||||
"pasteCodePlaceholder": "orca://pair?code=... 或貼上配對碼",
|
||||
"pasteCodeTitle": "貼上配對碼",
|
||||
"pasteInstead": "改為貼上配對碼",
|
||||
"pastePairingCode": "或貼上配對碼",
|
||||
"scanCodeStep": "掃描 QR Code",
|
||||
"title": "與電腦配對",
|
||||
"tryAgain": "重試"
|
||||
},
|
||||
"terminalSettings": {
|
||||
"autocomplete": "自動完成與自動修正",
|
||||
"autocompleteOff": "關閉",
|
||||
"autocompleteOn": "開啟",
|
||||
"defaultText": "預設(100%)",
|
||||
"keepPhoneSize": "保持手機尺寸(預設)",
|
||||
"keyboardInputDescription": "自動完成、自動修正和拼字建議僅適用於暫存指令輸入模式。點一下鍵盤上方的雙箭頭按鈕,從即時輸入切換至此模式,編輯好指令後再點「傳送」。預設為關閉,以免指令、旗標或路徑遭到改寫。",
|
||||
"keyboardInputHeading": "鍵盤輸入",
|
||||
"largeText": "大(125%)",
|
||||
"largerText": "較大(150%)",
|
||||
"largestText": "最大(200%)",
|
||||
"leaveAppDescription": "在手機上使用終端機時,Orca 會將終端機縮小以符合螢幕大小。關閉 App 或切換到其他 App 時,此設定決定終端機要保持手機尺寸(避免互動式 CLI 工具重新排版),還是還原為電腦上的尺寸。你隨時都能使用電腦版橫幅上的「還原」手動調整尺寸。",
|
||||
"leaveAppHeading": "離開 App 時",
|
||||
"noPairedDesktops": "尚未配對任何電腦。配對一台電腦後,即可設定終端機行為。",
|
||||
"restoreAfterMinutes_one": "{{count}} 分鐘後",
|
||||
"restoreAfterMinutes_other": "{{count}} 分鐘後",
|
||||
"restoreAfterSeconds_one": "{{count}} 秒後",
|
||||
"restoreAfterSeconds_other": "{{count}} 秒後",
|
||||
"restoreHost": "還原 {{name}}",
|
||||
"smallerText": "較小(75%)",
|
||||
"smallestText": "最小(50%)",
|
||||
"textSize": "文字大小",
|
||||
"textSizeDescription": "調整終端機文字大小。文字較小時可顯示更多欄,並保留兩側邊距;文字較大時顯示的欄數較少,可向左右拖曳來平移。你也可以直接在終端機中用雙指縮放,同時更新此設定。此設定僅影響此裝置的顯示,不會變更電腦上的終端機。",
|
||||
"textSizeHeading": "文字大小",
|
||||
"textSizePickerTitle": "終端機文字大小",
|
||||
"title": "終端機"
|
||||
}
|
||||
},
|
||||
"common": {
|
||||
"back": "返回"
|
||||
"back": "返回",
|
||||
"cancel": "取消",
|
||||
"continue": "繼續",
|
||||
"copied": "已複製",
|
||||
"delete": "刪除",
|
||||
"ok": "確定",
|
||||
"remove": "移除",
|
||||
"retry": "重試",
|
||||
"save": "儲存",
|
||||
"settings": "設定",
|
||||
"unknown": "未知"
|
||||
},
|
||||
"connection": {
|
||||
"connectionHealth": {
|
||||
"checkConnection": "請檢查網路連線",
|
||||
"connectFailed": "無法連線",
|
||||
"connected": "已連線",
|
||||
"connecting": "連線中…",
|
||||
"connectingViaRelay": "正在透過 Relay 連線…",
|
||||
"desktopUnreachable": "無法連上電腦",
|
||||
"disconnected": "已中斷連線",
|
||||
"hostFallback": "主機",
|
||||
"hostOffline": "{{host}} 已離線",
|
||||
"hostOfflineDetail": "確認電腦已喚醒、Orca 正在執行,且你已登入",
|
||||
"labelWithHint": "{{label}} — {{hint}}",
|
||||
"pairingInvalid": "配對已失效,請重新與電腦配對",
|
||||
"rePairDetail": "重新與電腦配對",
|
||||
"reconnecting": "正在重新連線…",
|
||||
"relayAccessExpired": "{{host}} 的 Relay 存取權已到期",
|
||||
"relayConnectFailed": "無法透過 Relay 連線",
|
||||
"relayUnreachable": "無法連上 Relay",
|
||||
"signInDetail": "在電腦上登入 Orca 即可重新連線",
|
||||
"signInRequired": "請在 {{host}} 上登入",
|
||||
"tailscaleHint": "檢查 Tailscale"
|
||||
},
|
||||
"connectionPathLabel": {
|
||||
"directLan": "直接連線 · LAN",
|
||||
"directTailscale": "直接連線 · Tailscale",
|
||||
"directViaLan": "透過 LAN 直接連線",
|
||||
"directViaTailscale": "透過 Tailscale 直接連線"
|
||||
},
|
||||
"hostEndpoint": {
|
||||
"addressRequired": "請輸入主機位址。",
|
||||
"hostnameRequired": "缺少主機名稱。",
|
||||
"invalidAddress": "位址無效。",
|
||||
"invalidHostname": "主機名稱無效。",
|
||||
"invalidPort": "連接埠號碼須為 1–65535。",
|
||||
"invalidScheme": "請使用 ws:// 或 wss://(或 host:port)。",
|
||||
"pathOrQueryNotAllowed": "主機位址不得包含路徑或查詢字串。"
|
||||
},
|
||||
"pairConfirmState": {
|
||||
"invalidCode": "配對碼無效",
|
||||
"missingCode": "缺少配對碼"
|
||||
},
|
||||
"rpcClientStreamRegistry": {
|
||||
"endedBeforeReady": "串流請求尚未就緒就已結束。",
|
||||
"interrupted": "連線中斷"
|
||||
},
|
||||
"rpcClientTerminalBinaryFrame": {
|
||||
"unsupportedFrame": "不支援的終端機串流訊框(操作碼 {{opcode}})。"
|
||||
}
|
||||
},
|
||||
"diagnostics": {
|
||||
"connectionDiagnosticsView": {
|
||||
"authFailed": "身分驗證失敗",
|
||||
"connected": "已連線",
|
||||
"connecting": "連線中",
|
||||
"copyReport": "複製報告",
|
||||
"diagnosisHeading": "可能原因",
|
||||
"disconnected": "已中斷連線",
|
||||
"handshaking": "交握中",
|
||||
"noEvents": "尚無連線事件。App 嘗試連線至此主機時,就會顯示事件。",
|
||||
"noHosts": "沒有已配對的主機。",
|
||||
"privacyHint": "傳送經過敏感資訊遮蔽且有大小上限的報告,包含主機名稱、端點、版本、連線狀態與事件,絕不包含終端機內容或憑證。",
|
||||
"reconnecting": "重新連線中",
|
||||
"retrySending": "重新傳送",
|
||||
"sendDiagnostics": "傳送診斷資訊至 Orca",
|
||||
"sending": "正在傳送…",
|
||||
"sent": "診斷資訊已傳送",
|
||||
"stateWithAttempt": "{{state}} · 第 {{attempts}} 次嘗試",
|
||||
"title": "網路診斷"
|
||||
},
|
||||
"mobileWebBundleProbeRow": {
|
||||
"assets": "資源檔",
|
||||
"build": "建置版本",
|
||||
"bundle": "套件",
|
||||
"bytes": "位元組",
|
||||
"connecting": "連線中…",
|
||||
"elapsed": "耗時",
|
||||
"elapsedMilliseconds": "{{elapsedMs}} 毫秒",
|
||||
"fetchBundle": "取得行動版網頁套件",
|
||||
"fetching": "正在取得套件…",
|
||||
"host": "主機",
|
||||
"unknownHost": "未知主機"
|
||||
},
|
||||
"mobileWebShellUpdateFailureRow": {
|
||||
"title": "工作區更新"
|
||||
},
|
||||
"troubleshootCommonIssues": {
|
||||
"allowNotifications": "確認系統設定允許 Orca 傳送通知,且「專注模式」或「勿擾模式」已關閉。",
|
||||
"checkTailnet": "確認電腦未進入睡眠,且在你的 tailnet 中顯示為已連線。",
|
||||
"desktopTitle": "桌面版應用程式未執行",
|
||||
"disableVpn": "停用該 VPN,或啟用分割通道/「允許 LAN」。",
|
||||
"firewallTitle": "防火牆封鎖連接埠 6768",
|
||||
"keepTailscaleOn": "100.x.x.x 或 *.ts.net 等主機位址會透過 Tailscale 連線,請保持 Tailscale 開啟。",
|
||||
"macosFirewall": "macOS:「系統設定」→「網路」→「防火牆」,允許 Orca 連線。",
|
||||
"notificationNetwork": "試著改用行動網路或其他 Wi-Fi 網路。若切換後能收到通知,原本的網路可能導致通知延遲送達。",
|
||||
"notificationsTitle": "推播通知",
|
||||
"openDesktop": "必須在電腦上開啟 Orca,才能接受連線。",
|
||||
"pairAfterUpdate": "更新後,可能需要透過 QR Code 重新配對。",
|
||||
"reconnectWifi": "試著讓兩部裝置重新連上 WiFi。",
|
||||
"restartBothApps": "若持續逾時,請重新啟動兩端的應用程式。",
|
||||
"restartDesktop": "試著重新啟動 Orca,行動版連線伺服器會隨之啟動。",
|
||||
"restartTailscale": "iOS/Android 可能導致通道卡住而沒有任何提示。請在 Tailscale 應用程式中關閉 Tailscale,再重新開啟。",
|
||||
"restrictedNetwork": "公司或學校的網路可能封鎖 P2P,請試著改用個人熱點。",
|
||||
"retryHost": "返回主機清單,點一下主機以重試。",
|
||||
"sameNetwork": "兩部裝置必須連上同一個區域網路(LAN),透過 Tailscale 連線時除外。",
|
||||
"sameSubnet": "乙太網路(Ethernet)和 WiFi 必須位於同一個子網路。",
|
||||
"tailscaleTitle": "無法連上 Tailscale 主機",
|
||||
"timeoutTitle": "連線逾時",
|
||||
"updateTailscale": "請更新 Tailscale 應用程式,近期版本已修正重新連線的錯誤。",
|
||||
"vpnRouting": "Tailscale 以外的 VPN 可能將本機網路流量導向遠端伺服器。",
|
||||
"vpnTitle": "其他 VPN 干擾",
|
||||
"wifiSignal": "檢查手機的 WiFi 訊號強度。",
|
||||
"wifiTitle": "不同的 WiFi 網路",
|
||||
"windowsFirewall": "Windows:「Defender 防火牆」→「允許應用程式」,勾選 Orca 的「私人」選項。"
|
||||
},
|
||||
"troubleshootView": {
|
||||
"commonIssues": "常見問題",
|
||||
"runAgain": "再次執行",
|
||||
"runDiagnostics": "執行診斷",
|
||||
"running": "執行中…",
|
||||
"title": "疑難排解",
|
||||
"viewNetworkDiagnostics": "查看網路診斷"
|
||||
},
|
||||
"useTroubleshootDiagnostics": {
|
||||
"connected": "已連線",
|
||||
"hostDataFailed": "無法讀取主機資料",
|
||||
"hostTestFailed": "無法測試",
|
||||
"hosts": "主機",
|
||||
"internet": "網際網路",
|
||||
"noConnection": "未連線",
|
||||
"noPairedHosts": "尚無主機,請掃描 QR Code 配對",
|
||||
"pairedCount": "已配對 {{count}} 台",
|
||||
"pairedHosts": "已配對的主機",
|
||||
"platform": "平台",
|
||||
"reachable": "可透過 {{endpoint}} 連線",
|
||||
"unexpectedResponse": "非預期的回應"
|
||||
},
|
||||
"webShellUpdateFailureCopy": {
|
||||
"failedAgo": "來自 {{hostName}} 的上次更新於 {{time}} 前失敗:{{reason}}{{offered}}。{{outcome}}",
|
||||
"failedJustNow": "來自 {{hostName}} 的上次更新剛剛失敗:{{reason}}{{offered}}。{{outcome}}"
|
||||
}
|
||||
},
|
||||
"dictation": {
|
||||
"setup": {
|
||||
"alreadyActive": "電腦或另一支手機正在使用聽寫功能。",
|
||||
"cloudModelNeedsKey": "需要 OpenAI 金鑰",
|
||||
"deleteFailed": "無法刪除模型",
|
||||
"desktopUpdateRequired": "請更新已配對電腦上的 Orca 桌面版,以使用行動版語音設定。",
|
||||
"downloadFailed": "無法開始下載",
|
||||
"loadFailed": "無法載入聽寫模型",
|
||||
"repairRequired": "請將這支手機與電腦重新配對,以使用聽寫功能。",
|
||||
"serverUpdateRequired": "請更新此伺服器上的 Orca,以使用語音聽寫。",
|
||||
"sessionLost": "聽寫功能與電腦的連線已中斷。請再試一次。",
|
||||
"updateFailed": "無法更新聽寫設定"
|
||||
}
|
||||
},
|
||||
"home": {
|
||||
"accountUsageCards": {
|
||||
"sectionHeading": "帳戶用量",
|
||||
"sessionWindow": "5 小時",
|
||||
"systemDefault": "系統預設",
|
||||
"weeklyWindow": "7 天"
|
||||
},
|
||||
"appUpdateCard": {
|
||||
"dismissAccessibilityLabel": "關閉 Orca {{version}} 更新提示",
|
||||
"title": "Orca {{version}} 已推出",
|
||||
"updateAccessibilityLabel": "Orca {{version}} 已推出,點一下即可更新",
|
||||
"updateHint": "點一下即可更新"
|
||||
},
|
||||
"emptyState": {
|
||||
"connectedDescription": "你的電腦會顯示在這裡。所有資料皆採端對端加密。",
|
||||
"connectedTitle": "已連線",
|
||||
"description": "與電腦上的 Orca 配對,即可用手機查看代理程式狀態、開啟任何終端機,並推進工作。",
|
||||
"openDesktopDescription": "前往「設定」→「行動版」,產生配對用的 QR Code。",
|
||||
"openDesktopTitle": "開啟 Orca 桌面版",
|
||||
"pairDesktop": "配對電腦",
|
||||
"scanCodeDescription": "點一下上方按鈕開啟掃描器,對準電腦螢幕上的 QR Code。",
|
||||
"scanCodeTitle": "掃描 QR Code",
|
||||
"stepsHeading": "使用方式",
|
||||
"title": "連線至你的電腦"
|
||||
},
|
||||
"listFooter": {
|
||||
"hostPickerTitle": "選擇開啟任務的主機",
|
||||
"resumeHeading": "繼續",
|
||||
"tasksHeading": "任務"
|
||||
},
|
||||
"listHeader": {
|
||||
"agentTime": "代理程式時間",
|
||||
"agentsSpawned": "已啟動代理程式",
|
||||
"daysHours": "{{days}} 天 {{hours}} 小時",
|
||||
"desktopsHeading": "電腦",
|
||||
"hoursMinutes": "{{hours}} 小時 {{minutes}} 分鐘",
|
||||
"minutes": "{{minutes}} 分鐘",
|
||||
"prsCreated": "已建立 PR",
|
||||
"title": "歡迎回來"
|
||||
},
|
||||
"screen": {
|
||||
"pairingCheckFailed": "無法檢查配對狀態",
|
||||
"removeHostFailed": "無法移除主機",
|
||||
"removeHostMessage": "要移除「{{name}}」嗎?之後可重新配對。",
|
||||
"removeHostTitle": "移除主機",
|
||||
"tryAgain": "請再試一次。"
|
||||
},
|
||||
"tasksCard": {
|
||||
"noSources": "尚未連結任務來源",
|
||||
"openProviderTasks": "開啟 {{provider}} 任務",
|
||||
"title": "任務"
|
||||
}
|
||||
},
|
||||
"hooks": {
|
||||
"dictationDesktopStart": {
|
||||
"recordingFailed": "無法啟動麥克風錄音"
|
||||
},
|
||||
"useMobileDictation": {
|
||||
"inputClosed": "此工作階段已不再接受輸入,語音聽寫已停止。請再試一次。",
|
||||
"microphoneInitFailed": "無法初始化麥克風",
|
||||
"microphoneInterrupted": "麥克風遭中斷,語音聽寫已停止。請再試一次。",
|
||||
"microphonePermissionDenied": "麥克風權限遭拒",
|
||||
"noSpeech": "未偵測到語音。"
|
||||
}
|
||||
},
|
||||
"host": {
|
||||
"edit": {
|
||||
"address": "位址",
|
||||
"addressHint": "可輸入 IP、host:port 或 ws:// / wss://。wss:// URL 未指定連接埠時會使用 443。僅輸入位址時,會保留目前的連接埠(或使用 6768)。",
|
||||
"connectionPreview": "將連線至 {{endpoint}}",
|
||||
"goBack": "返回",
|
||||
"help": "變更顯示名稱或連線位址。名稱留空時,會使用電腦回報的名稱。變更位址只會切換這支手機的連線位址,不會重新配對。適用於透過不同 IP 連線至同一台電腦的情況(例如家用區域網路或 Tailscale)。",
|
||||
"hostNamePlaceholder": "主機名稱",
|
||||
"hostRemoved": "此主機已從這支手機移除。",
|
||||
"loadFailed": "無法載入主機資料。",
|
||||
"missingHost": "未指定主機。",
|
||||
"name": "名稱",
|
||||
"saveFailed": "無法儲存主機資料。",
|
||||
"saveHost": "儲存主機",
|
||||
"title": "編輯主機"
|
||||
}
|
||||
},
|
||||
"hostScreen": {
|
||||
"hostListActionSheetActions": {
|
||||
"connect": "連線",
|
||||
"disconnect": "中斷連線",
|
||||
"editHost": "編輯主機",
|
||||
"networkDiagnostics": "網路診斷",
|
||||
"reconnect": "重新連線"
|
||||
},
|
||||
"hostPendingCreationRow": {
|
||||
"creatingWorkspace": "正在建立工作區…"
|
||||
},
|
||||
"hostRailActions": {
|
||||
"accounts": "帳戶",
|
||||
"closeSearch": "關閉搜尋",
|
||||
"floatingWorkspace": "浮動工作區",
|
||||
"newWorkspace": "新工作區",
|
||||
"searchWorkspaces": "搜尋工作區",
|
||||
"tasks": "任務"
|
||||
},
|
||||
"hostScreenHeader": {
|
||||
"accounts": "帳戶",
|
||||
"activeFilters_one": "工作區選項(已啟用 {{count}} 個篩選條件)",
|
||||
"activeFilters_other": "工作區選項(已啟用 {{count}} 個篩選條件)",
|
||||
"backToHosts": "返回主機清單",
|
||||
"closeSearch": "關閉搜尋",
|
||||
"floatingWorkspace": "浮動工作區",
|
||||
"hideSidebar": "隱藏側邊欄",
|
||||
"reconnect": "重新連線",
|
||||
"searchWorkspaces": "搜尋工作區",
|
||||
"tasks": "任務",
|
||||
"workspaceOptions": "工作區選項"
|
||||
},
|
||||
"hostScreenOverlays": {
|
||||
"deleteConfirmMessage": "要刪除「{{name}}」({{branch}})嗎?",
|
||||
"deleteWorktree": "刪除工作樹",
|
||||
"pin": "固定",
|
||||
"primaryWorktreeDeleteHint": "主工作樹不能刪除。請改為移除專案。",
|
||||
"removeHost": "移除主機",
|
||||
"removeHostMessage": "要移除「{{name}}」嗎?之後可重新配對。",
|
||||
"sleep": "休眠",
|
||||
"unpin": "取消固定"
|
||||
},
|
||||
"hostWorkspaceList": {
|
||||
"searchPlaceholder": "搜尋工作區…",
|
||||
"searchWorkspaces": "搜尋工作區"
|
||||
},
|
||||
"workspaceDetailsSheet": {
|
||||
"checksFailing": "檢查:未通過",
|
||||
"checksPassing": "檢查:已通過",
|
||||
"checksPending": "檢查:待處理",
|
||||
"closed": "已關閉",
|
||||
"createdByAutomation": "由自動化建立",
|
||||
"createdByNamedAutomation": "由自動化「{{name}}」建立",
|
||||
"createdFromCli": "透過 Orca CLI 建立",
|
||||
"draft": "草稿",
|
||||
"merged": "已合併",
|
||||
"openLink": "開啟 {{label}}",
|
||||
"openLinkWithTitle": "開啟 {{label}},{{title}}",
|
||||
"prOpen": "開放"
|
||||
},
|
||||
"workspaceOptionControls": {
|
||||
"agentActivity": "代理程式活動",
|
||||
"agentActivityLayout": "代理程式活動版面配置",
|
||||
"agentActivitySort": "代理程式活動",
|
||||
"agentActivitySortDescription": "優先顯示需要關注的代理程式,再依最近活動時間排序。",
|
||||
"agentStatuses": "代理程式狀態",
|
||||
"allHosts": "所有主機",
|
||||
"allProjects": "所有專案",
|
||||
"automation": "自動化",
|
||||
"branchFolderPath": "分支 / 資料夾路徑",
|
||||
"branchName": "分支名稱",
|
||||
"compact": "精簡",
|
||||
"detailed": "詳細",
|
||||
"fullList": "完整清單",
|
||||
"githubIssues": "GitHub 議題",
|
||||
"githubTicket": "GitHub 議題",
|
||||
"host": "主機",
|
||||
"hover": "游標暫留",
|
||||
"jiraIssue": "Jira 議題",
|
||||
"jiraIssues": "Jira 議題",
|
||||
"linearIssue": "Linear 議題",
|
||||
"linearIssues": "Linear 議題",
|
||||
"manual": "手動",
|
||||
"manualProjectOrderDescription": "拖曳專案進行排列",
|
||||
"manualWorkspaceOrderDescription": "拖曳工作區,即可在各群組內調整順序。",
|
||||
"name": "名稱",
|
||||
"none": "無",
|
||||
"notes": "筆記",
|
||||
"ports": "連接埠",
|
||||
"pr": "PR",
|
||||
"prMrLink": "PR/MR 連結",
|
||||
"project": "專案",
|
||||
"recent": "最近活動",
|
||||
"recentProjectOrderDescription": "最近的工作區活動",
|
||||
"searchProjects": "搜尋專案",
|
||||
"searchProjectsPlaceholder": "搜尋專案…",
|
||||
"selectedCount": "{{count}} / {{total}}",
|
||||
"status": "狀態"
|
||||
},
|
||||
"workspaceOptionsSheet": {
|
||||
"backToOptions": "返回工作區選項",
|
||||
"cardDisplay": "卡片顯示",
|
||||
"cardLayout": "卡片版面配置",
|
||||
"clearFilters": "清除篩選",
|
||||
"exceptDefaultBranch": "預設分支除外",
|
||||
"filters": "篩選條件",
|
||||
"groupBy": "分組依據",
|
||||
"hideAutomationCreated": "隱藏自動化建立的工作區",
|
||||
"hideCliCreated": "隱藏 CLI 建立的工作區",
|
||||
"hideDefaultBranch": "隱藏預設分支",
|
||||
"hideDetachedHead": "隱藏分離 HEAD",
|
||||
"hideOtherClientWorkspaces": "隱藏其他用戶端工作區",
|
||||
"hideSleeping": "隱藏休眠項",
|
||||
"hosts": "主機",
|
||||
"localLinux": "本機 Linux",
|
||||
"localMac": "本機 Mac",
|
||||
"localWindows": "本機 Windows",
|
||||
"projectOrder": "專案排序",
|
||||
"projects": "專案",
|
||||
"savingToDesktop": "正在儲存至電腦版",
|
||||
"show": "顯示",
|
||||
"showProperties": "顯示屬性",
|
||||
"sortBy": "排序方式",
|
||||
"syncedAcrossDevices": "已在你的裝置間同步",
|
||||
"thisComputer": "本機",
|
||||
"waitingForDesktop": "仍在等待電腦版回應",
|
||||
"workspaceOptions": "工作區選項"
|
||||
}
|
||||
},
|
||||
"navigation": {
|
||||
"hostStackScreens": {
|
||||
"accounts": "帳戶",
|
||||
"agentSessionHistory": "代理程式工作階段歷史",
|
||||
"changes": "變更",
|
||||
"editHost": "編輯主機",
|
||||
"host": "主機",
|
||||
"pullRequest": "PR",
|
||||
"sourceControl": "原始碼控制",
|
||||
"tasks": "任務",
|
||||
"terminal": "終端機",
|
||||
"workspace": "工作區"
|
||||
}
|
||||
},
|
||||
"notifications": {
|
||||
"notificationDeliverySection": {
|
||||
"footer": "通知類型依各台已配對電腦的通知設定而定。若連續 7 天未使用此應用程式,通知將暫停;開啟應用程式並重新連線即可恢復。",
|
||||
"onlyWhenAway": "僅在離開電腦時",
|
||||
"onlyWhenAwayHint": "鍵盤和滑鼠皆無操作達 3 分鐘後,或電腦鎖定時。",
|
||||
"sound": "通知音效",
|
||||
"suppressWhileFocused": "檢視時暫停通知",
|
||||
"suppressWhileFocusedHint": "略過此手機目前開啟的工作區通知。"
|
||||
}
|
||||
},
|
||||
"onboarding": {
|
||||
"notificationOnboardingPreview": {
|
||||
"claudeNeedsInput": "Claude 需要你的輸入",
|
||||
"codexFinished": "Codex 已完成",
|
||||
"now": "現在",
|
||||
"testsPassing": "測試已通過。",
|
||||
"waitingOnYou": "正在等待你回應。"
|
||||
},
|
||||
"onboardingPage": {
|
||||
"enableAgentNotifications": "啟用代理程式通知",
|
||||
"enableNotifications": "啟用通知",
|
||||
"keepTerminal": "繼續使用終端機",
|
||||
"notNow": "暫時不要",
|
||||
"notificationsDescription": "即使未使用應用程式,代理程式完成工作或等待你回應時,此手機也會收到通知。",
|
||||
"notificationsDisclosure": "電腦閒置 3 分鐘後,便會透過 Orca 推播服務傳送通知。你隨時可在「設定」中變更。",
|
||||
"notificationsTitle": "代理程式需要你時,別錯過通知",
|
||||
"openInChatUi": "以聊天介面開啟工作階段",
|
||||
"openInTerminal": "在終端機中開啟工作階段",
|
||||
"sessionViewDescription": "選擇在此裝置上以終端機或聊天介面開啟支援此功能的代理程式工作階段。長按工作階段分頁可切換檢視方式,也可稍後在「設定」中變更預設值。",
|
||||
"sessionViewTitle": "如何開啟工作階段?",
|
||||
"skipNotifications": "暫不啟用通知",
|
||||
"useChatUi": "使用聊天介面"
|
||||
}
|
||||
},
|
||||
"settings": {
|
||||
"aboutScreen": {
|
||||
"openLinkFailed": "無法開啟連結,請再試一次。",
|
||||
"socialProfile": "Orca 的 X 帳號",
|
||||
"sourceCode": "Orca 原始碼",
|
||||
"tagline": "助開發者發揮 100 倍效率的開源代理程式 IDE",
|
||||
"title": "關於",
|
||||
"website": "Orca 網站"
|
||||
},
|
||||
"appUpdateSettingsRows": {
|
||||
"checkFailed": "無法檢查",
|
||||
"checkUpdates": "檢查更新",
|
||||
"checkUpdatesStatus": "檢查更新,{{status}}",
|
||||
"checking": "檢查中…",
|
||||
"installedVersion": "版本 {{version}}",
|
||||
"lastCheckedDays_one": "上次檢查:{{count}} 天前",
|
||||
"lastCheckedDays_other": "上次檢查:{{count}} 天前",
|
||||
"lastCheckedHours_one": "上次檢查:{{count}} 小時前",
|
||||
"lastCheckedHours_other": "上次檢查:{{count}} 小時前",
|
||||
"lastCheckedJustNow": "上次檢查:剛剛",
|
||||
"lastCheckedMinutes_one": "上次檢查:{{count}} 分鐘前",
|
||||
"lastCheckedMinutes_other": "上次檢查:{{count}} 分鐘前",
|
||||
"neverChecked": "從未",
|
||||
"upToDate": "已是最新",
|
||||
"update": "更新",
|
||||
"updateToVersion": "更新至 Orca {{version}}"
|
||||
},
|
||||
"appearance": {
|
||||
"language": {
|
||||
"description": "「系統」會使用此裝置的語言。權限提示一律使用裝置語言。",
|
||||
@@ -17,6 +608,157 @@
|
||||
"system": "系統"
|
||||
},
|
||||
"title": "外觀"
|
||||
},
|
||||
"browserSettingsScreen": {
|
||||
"desktopBrowser": "電腦上的 Orca 瀏覽器",
|
||||
"desktopBrowserDescription": "在已配對電腦的瀏覽器中開啟,並串流至手機。",
|
||||
"linksDescription": "選擇點按終端機輸出中的 HTTP(S) 連結時,要在哪裡開啟。",
|
||||
"linksHeading": "連結",
|
||||
"loadFailed": "無法載入瀏覽器偏好設定,請再試一次。",
|
||||
"openTerminalLinks": "開啟終端機連結",
|
||||
"phoneBrowser": "手機瀏覽器",
|
||||
"phoneBrowserDescription": "在此手機上的 Safari、Chrome 或其他瀏覽器中開啟。",
|
||||
"saveFailed": "無法儲存瀏覽器偏好設定,請再試一次。",
|
||||
"title": "瀏覽器"
|
||||
},
|
||||
"menuItems": {
|
||||
"about": "關於",
|
||||
"appearance": "外觀",
|
||||
"browser": "瀏覽器",
|
||||
"chatUi": "聊天介面",
|
||||
"notifications": "通知",
|
||||
"terminal": "終端機",
|
||||
"troubleshooting": "疑難排解",
|
||||
"voice": "語音"
|
||||
},
|
||||
"menuScreen": {
|
||||
"privacyPolicy": "隱私權政策",
|
||||
"support": "支援"
|
||||
},
|
||||
"nativeChatSettingsScreen": {
|
||||
"defaultViewDescription": "選擇此裝置上受支援的代理程式工作階段(Claude、Codex 及其他支援聊天的代理程式)的開啟方式。「終端機」會顯示原始 CLI;「聊天介面」則提供類似電腦版的聊天介面。你仍可透過長按選單,切換個別工作階段的顯示方式。",
|
||||
"defaultViewHeading": "預設檢視",
|
||||
"off": "關閉",
|
||||
"on": "開啟",
|
||||
"openInChatUi": "以聊天介面開啟工作階段",
|
||||
"title": "聊天介面"
|
||||
},
|
||||
"nativeNotificationDeliverySettings": {
|
||||
"loadFailed": "無法載入通知傳送設定。請重新開啟此畫面再試一次。",
|
||||
"saveFailed": "無法儲存通知傳送設定,請再試一次。",
|
||||
"updateDesktopHint": "請配對已更新 Orca 的電腦,即可在此手機上接收通知。"
|
||||
},
|
||||
"notificationDisplayTest": {
|
||||
"accepted": "Orca 的推播服務已接受請求。請查看是否收到通知。",
|
||||
"connectDesktop": "請先連線至電腦,再試一次。",
|
||||
"description": "透過 Orca 的推播服務傳送測試通知。",
|
||||
"desktopUnreachable": "無法連線至電腦,請再試一次。",
|
||||
"loadFailed": "無法載入已配對的電腦。",
|
||||
"pairDesktop": "請先配對電腦,再試一次。",
|
||||
"pushServiceFailed": "無法透過 Orca 的推播服務傳送,請再試一次。",
|
||||
"rateLimited": "通知過多,請稍後再試。",
|
||||
"reconnectPhone": "請重新連線,將此手機註冊為通知接收裝置。",
|
||||
"sendButton": "傳送測試通知",
|
||||
"sendFailed": "無法傳送測試推播。",
|
||||
"sending": "正在傳送…",
|
||||
"troubleReceivingAlerts": "接收通知時遇到問題嗎?",
|
||||
"troubleshooting": "疑難排解",
|
||||
"updateDesktop": "請更新電腦上的 Orca,以執行此測試。"
|
||||
},
|
||||
"notificationSettingsScreen": {
|
||||
"description": "當代理程式需要你的回應或完成任務時,在此裝置上接收通知。",
|
||||
"enableNotifications": "啟用通知",
|
||||
"loadFailed": "無法載入通知設定,請再試一次。",
|
||||
"notificationsBlocked": "系統設定已停用通知。",
|
||||
"openSettings": "開啟設定",
|
||||
"openSettingsFailed": "無法開啟系統設定,請再試一次。",
|
||||
"saveFailed": "無法儲存通知設定,請再試一次。",
|
||||
"title": "通知"
|
||||
},
|
||||
"pendingCredentialCleanupCard": {
|
||||
"cleanupUnconfirmed_one": "無法確認此裝置上的 {{count}} 個憑證是否已清除。",
|
||||
"cleanupUnconfirmed_other": "無法確認此裝置上的 {{count}} 個憑證是否已清除。",
|
||||
"retryCleanup": "重試清除配對憑證",
|
||||
"retryFailed": "仍無法確認是否已清理完成,請稍後再試。",
|
||||
"statusCheckFailed": "無法檢查此裝置的清理狀態。為確保安全,請重試。",
|
||||
"title": "配對憑證清理"
|
||||
},
|
||||
"uiLanguageSettingsSection": {
|
||||
"pseudoLanguage": "偽語系(en-XA)"
|
||||
},
|
||||
"voiceSettingsScreen": {
|
||||
"connectDesktop": "連線至電腦以管理語音設定。",
|
||||
"deleteFailed": "刪除失敗",
|
||||
"dictationDescription": "將聽寫文字輸入電腦上目前取得焦點的任意窗格。",
|
||||
"dictationHeading": "聽寫",
|
||||
"dictationMode": "聽寫模式",
|
||||
"dictationModeDescription": "切換:按一次開始,再按一次停止。按住:按住期間進行聽寫。",
|
||||
"downloadFailed": "下載失敗",
|
||||
"enableDictation": "啟用語音聽寫",
|
||||
"holdMode": "按住",
|
||||
"loadFailed": "無法載入語音設定",
|
||||
"loadFailedMessage": "無法載入語音設定。",
|
||||
"noneSelected": "尚未選擇",
|
||||
"selectModelFailed": "無法選擇模型",
|
||||
"speechModel": "語音模型",
|
||||
"speechModelHeading": "語音模型",
|
||||
"title": "語音",
|
||||
"toggleMode": "切換",
|
||||
"updateFailed": "無法更新"
|
||||
}
|
||||
},
|
||||
"webShell": {
|
||||
"shellStateScreen": {
|
||||
"checkingHost": "正在檢查主機",
|
||||
"connectToDownload": "連線至此主機以下載最新版應用程式",
|
||||
"documentLoadFailed": "無法開啟已下載的應用程式。",
|
||||
"downloadFailed": "無法從此主機下載最新版應用程式。",
|
||||
"hostProfileUnreadable": "裝置無法讀取此主機的資料。請重試。",
|
||||
"isolationUnavailable": "此裝置的 WebView 版本過舊,無法安全開啟工作區。",
|
||||
"openingWorkspace": "正在開啟工作區",
|
||||
"renderProcessGone": "工作區已停止回應。",
|
||||
"statusUnreadable": "無法讀取此主機的狀態。請返回後重新開啟。",
|
||||
"tryAgain": "重試"
|
||||
}
|
||||
},
|
||||
"worktree": {
|
||||
"agentRowDisplay": {
|
||||
"blocked": "已阻塞",
|
||||
"days_one": "{{count}} 天",
|
||||
"days_other": "{{count}} 天",
|
||||
"done": "已完成",
|
||||
"failed": "失敗",
|
||||
"hours_one": "{{count}} 小時",
|
||||
"hours_other": "{{count}} 小時",
|
||||
"idle": "閒置",
|
||||
"interrupted": "已中斷",
|
||||
"justNow": "剛剛",
|
||||
"minutes_one": "{{count}} 分鐘",
|
||||
"minutes_other": "{{count}} 分鐘",
|
||||
"monitoring": "監控背景任務",
|
||||
"stopping": "正在停止…",
|
||||
"unconfirmed": "無法確認",
|
||||
"unverifiable": "暫無近期更新",
|
||||
"waiting": "等待輸入",
|
||||
"working": "執行中"
|
||||
},
|
||||
"hostWorkspaceListStates": {
|
||||
"checkHost": "確認電腦已喚醒,且 Orca 正在執行",
|
||||
"empty": "沒有工作區",
|
||||
"loadFailed": "無法從此主機載入工作區",
|
||||
"noFilterMatches": "沒有符合篩選條件的工作區",
|
||||
"noMatches": "找不到相符的工作區",
|
||||
"reopen": "重新開啟以更新",
|
||||
"reopenDetail": "此頁面尚不支援這台主機列出工作區的方式。",
|
||||
"retrying": "worktree.ps 執行失敗({{error}}),正在自動重試"
|
||||
},
|
||||
"workspaceListSections": {
|
||||
"all": "全部",
|
||||
"closed": "已關閉",
|
||||
"done": "已完成",
|
||||
"inProgress": "進行中",
|
||||
"inReview": "審查中",
|
||||
"pinned": "已固定"
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,8 +1,599 @@
|
||||
{
|
||||
"accounts": {
|
||||
"screen": {
|
||||
"agentLogin": "使用智能体自身的登录信息",
|
||||
"connecting": "正在连接到主机…",
|
||||
"connectingTo": "正在连接到 {{name}}…",
|
||||
"default": "系统默认",
|
||||
"hostNotFound": "未找到主机",
|
||||
"invalidSnapshot": "主机返回的账户快照无效",
|
||||
"loadingAccounts": "正在加载账户…",
|
||||
"manageAccountsHint": "请在桌面的“设置 → 账户”中添加账户或重新进行账户身份验证。",
|
||||
"refreshAccounts": "刷新账户",
|
||||
"sessionWindow": "5小时",
|
||||
"switchFailed": "无法切换账户",
|
||||
"title": "账户",
|
||||
"useReset": "使用重置机会",
|
||||
"weeklyWindow": "7天"
|
||||
}
|
||||
},
|
||||
"agentHistory": {
|
||||
"panel": {
|
||||
"all": "全部",
|
||||
"emptyScope": "此范围内暂无历史智能体会话。",
|
||||
"loadFailed": "无法加载",
|
||||
"missingResumeId": "此会话缺少恢复 ID。",
|
||||
"noSearchResults": "没有符合搜索条件的会话。",
|
||||
"noSessions": "暂无智能体会话",
|
||||
"placeholder": "搜索会话,repo:、path:",
|
||||
"project": "项目",
|
||||
"refreshSessions": "刷新智能体会话",
|
||||
"resumeFailed": "恢复会话失败。",
|
||||
"sessionQueued": "智能体会话已加入队列。",
|
||||
"skipped_one": "跳过了 {{count}} 个记录",
|
||||
"skipped_other": "跳过了 {{count}} 个记录",
|
||||
"title": "智能体会话历史",
|
||||
"unavailable": "智能体会话历史不可用",
|
||||
"unknownHostPlatform": "无法确定主机平台。",
|
||||
"updateHost": "请更新此主机上的 Orca,以浏览智能体会话历史。",
|
||||
"waitingForHost": "正在等待主机…",
|
||||
"workspace": "工作区"
|
||||
},
|
||||
"resumeTarget": {
|
||||
"differentHost": "此会话属于其他主机。请打开同一主机上的工作区以恢复它。",
|
||||
"openWorkspace": "恢复会话前请先打开一个工作区。",
|
||||
"runtimeUnavailable": "由运行时托管的工作区不支持从历史记录恢复会话。"
|
||||
},
|
||||
"sessionCard": {
|
||||
"untitledSession": "无标题会话"
|
||||
},
|
||||
"sessionHistoryList": {
|
||||
"assistantRole": "助手",
|
||||
"currentWorktree": "当前工作树",
|
||||
"messages_one": "{{count}} 条消息",
|
||||
"messages_other": "{{count}} 条消息",
|
||||
"resumeSession": "恢复智能体会话",
|
||||
"userRole": "用户"
|
||||
},
|
||||
"useAgentHistoryState": {
|
||||
"hostUnreachable": "无法连接到主机",
|
||||
"loadFailed": "无法加载智能体会话",
|
||||
"waitingForHost": "正在等待主机…"
|
||||
},
|
||||
"worktreeNavigationActions": {
|
||||
"agentSessionHistory": "智能体会话历史",
|
||||
"sourceControl": "源代码控制"
|
||||
}
|
||||
},
|
||||
"app": {
|
||||
"mobileOnboarding": {
|
||||
"notificationSettingsFailed": "无法更新通知设置。请重试。",
|
||||
"progress": "入门引导进度",
|
||||
"saveChoiceFailed": "无法保存选择。请重试。",
|
||||
"stepProgress": "第 {{step}} 步,共 {{total}} 步"
|
||||
},
|
||||
"notifications": {
|
||||
"description": "即使应用已关闭,也能收到智能体提醒。提醒通过 Orca 的推送服务及 Apple 或 Google 送达。"
|
||||
},
|
||||
"pair": {
|
||||
"backToHome": "返回首页",
|
||||
"missingCode": "缺少配对码"
|
||||
},
|
||||
"pairConfirm": {
|
||||
"backToHome": "返回首页",
|
||||
"connectTimedOut": "未能在 {{seconds}} 秒内连接,请查看下方日志,了解连接停在哪一步",
|
||||
"connecting": "正在连接…",
|
||||
"description": "已打开来自桌面的配对链接。确认后将此桌面添加到主机列表。",
|
||||
"pairButton": "配对",
|
||||
"pairingFailed": "配对失败:{{error}}",
|
||||
"pairingLog": "配对日志",
|
||||
"title": "要与此桌面配对吗?"
|
||||
},
|
||||
"pairScan": {
|
||||
"cameraDisabledDescription": "请在“设置”中启用相机访问权限,或粘贴配对码。",
|
||||
"cameraDisabledTitle": "相机访问已禁用",
|
||||
"cameraPermissionDescription": "扫描桌面上 Orca 显示的二维码,或粘贴配对码。",
|
||||
"connectTimedOut": "未能在 {{seconds}} 秒内连接,请查看下方日志,了解连接停在哪一步",
|
||||
"connecting": "正在连接…",
|
||||
"invalidPairingCode": "配对码无效,请从电脑上复制后重新粘贴",
|
||||
"invalidQrCode": "无效的 Orca 二维码",
|
||||
"openDesktopStep": "在电脑上打开 Orca",
|
||||
"openMobileSettingsStep": "前往“设置” → “移动端”",
|
||||
"openSettings": "打开设置",
|
||||
"pairingFailed": "配对失败:{{error}}",
|
||||
"pairingLog": "配对日志",
|
||||
"pasteCodeMessage": "复制电脑上二维码下方显示的配对码。",
|
||||
"pasteCodePlaceholder": "orca://pair?code=... 或粘贴配对码",
|
||||
"pasteCodeTitle": "粘贴配对码",
|
||||
"pasteInstead": "改为粘贴配对码",
|
||||
"pastePairingCode": "或粘贴配对码",
|
||||
"scanCodeStep": "扫描二维码",
|
||||
"title": "与桌面配对",
|
||||
"tryAgain": "重试"
|
||||
},
|
||||
"terminalSettings": {
|
||||
"autocomplete": "自动补全与自动纠正",
|
||||
"autocompleteOff": "关闭",
|
||||
"autocompleteOn": "开启",
|
||||
"defaultText": "默认(100%)",
|
||||
"keepPhoneSize": "保持手机尺寸(默认)",
|
||||
"keyboardInputDescription": "自动补全、自动纠正和拼写建议仅适用于缓冲命令输入。点击键盘上方的双箭头按钮,从实时输入切换到缓冲输入,即可先编辑命令,再点击“发送”。默认关闭,以免改写命令、选项或路径。",
|
||||
"keyboardInputHeading": "键盘输入",
|
||||
"largeText": "大(125%)",
|
||||
"largerText": "更大(150%)",
|
||||
"largestText": "最大(200%)",
|
||||
"leaveAppDescription": "在手机上使用终端时,Orca 会缩小终端尺寸以适应屏幕。关闭应用或切换到其他应用时,此设置决定终端是保持手机尺寸(避免交互式 CLI 工具的界面重新排版),还是恢复为桌面尺寸。您随时可以点击桌面横幅中的“恢复”手动调整尺寸。",
|
||||
"leaveAppHeading": "离开应用时",
|
||||
"noPairedDesktops": "尚未配对桌面。配对后即可设置终端行为。",
|
||||
"restoreAfterMinutes_one": "{{count}} 分钟后",
|
||||
"restoreAfterMinutes_other": "{{count}} 分钟后",
|
||||
"restoreAfterSeconds_one": "{{count}} 秒后",
|
||||
"restoreAfterSeconds_other": "{{count}} 秒后",
|
||||
"restoreHost": "恢复 {{name}}",
|
||||
"smallerText": "较小(75%)",
|
||||
"smallestText": "最小(50%)",
|
||||
"textSize": "文字大小",
|
||||
"textSizeDescription": "调整终端文字大小。文字较小时可显示更多列,两侧留有边距;文字较大时显示的列数较少,可横向拖动查看。也可以直接在终端中双指缩放,此设置会随之更新。仅影响当前设备的显示,不会改变桌面终端。",
|
||||
"textSizeHeading": "文字大小",
|
||||
"textSizePickerTitle": "终端文字大小",
|
||||
"title": "终端"
|
||||
}
|
||||
},
|
||||
"common": {
|
||||
"back": "返回"
|
||||
"back": "返回",
|
||||
"cancel": "取消",
|
||||
"continue": "继续",
|
||||
"copied": "已复制",
|
||||
"delete": "删除",
|
||||
"ok": "确定",
|
||||
"remove": "移除",
|
||||
"retry": "重试",
|
||||
"save": "保存",
|
||||
"settings": "设置",
|
||||
"unknown": "未知"
|
||||
},
|
||||
"connection": {
|
||||
"connectionHealth": {
|
||||
"checkConnection": "请检查网络连接",
|
||||
"connectFailed": "无法连接",
|
||||
"connected": "已连接",
|
||||
"connecting": "正在连接…",
|
||||
"connectingViaRelay": "正在通过 Relay 连接…",
|
||||
"desktopUnreachable": "无法连接到桌面",
|
||||
"disconnected": "已断开连接",
|
||||
"hostFallback": "主机",
|
||||
"hostOffline": "{{host}} 已离线",
|
||||
"hostOfflineDetail": "请确认电脑已唤醒、Orca 正在运行且已登录",
|
||||
"labelWithHint": "{{label}} — {{hint}}",
|
||||
"pairingInvalid": "配对无效,请重新与桌面配对",
|
||||
"rePairDetail": "请重新与桌面配对",
|
||||
"reconnecting": "正在重新连接…",
|
||||
"relayAccessExpired": "{{host}} 的 Relay 访问权限已过期",
|
||||
"relayConnectFailed": "无法通过 Relay 连接",
|
||||
"relayUnreachable": "无法访问 Relay",
|
||||
"signInDetail": "请在桌面上登录 Orca 以重新连接",
|
||||
"signInRequired": "需要在 {{host}} 上登录",
|
||||
"tailscaleHint": "检查 Tailscale"
|
||||
},
|
||||
"connectionPathLabel": {
|
||||
"directLan": "直连 · LAN",
|
||||
"directTailscale": "直连 · Tailscale",
|
||||
"directViaLan": "通过 LAN 直连",
|
||||
"directViaTailscale": "通过 Tailscale 直连"
|
||||
},
|
||||
"hostEndpoint": {
|
||||
"addressRequired": "请输入主机地址。",
|
||||
"hostnameRequired": "缺少主机名。",
|
||||
"invalidAddress": "地址无效。",
|
||||
"invalidHostname": "主机名无效。",
|
||||
"invalidPort": "端口号必须为 1–65535。",
|
||||
"invalidScheme": "请使用 ws:// 或 wss://(或 host:port)。",
|
||||
"pathOrQueryNotAllowed": "主机地址不得包含路径或查询参数。"
|
||||
},
|
||||
"pairConfirmState": {
|
||||
"invalidCode": "配对码无效",
|
||||
"missingCode": "缺少配对码"
|
||||
},
|
||||
"rpcClientStreamRegistry": {
|
||||
"endedBeforeReady": "流式请求尚未就绪便已结束。",
|
||||
"interrupted": "连接中断"
|
||||
},
|
||||
"rpcClientTerminalBinaryFrame": {
|
||||
"unsupportedFrame": "不支持的终端流帧(操作码 {{opcode}})。"
|
||||
}
|
||||
},
|
||||
"diagnostics": {
|
||||
"connectionDiagnosticsView": {
|
||||
"authFailed": "身份验证失败",
|
||||
"connected": "已连接",
|
||||
"connecting": "连接中",
|
||||
"copyReport": "复制报告",
|
||||
"diagnosisHeading": "可能的原因",
|
||||
"disconnected": "已断开连接",
|
||||
"handshaking": "握手中",
|
||||
"noEvents": "暂无连接事件。应用尝试连接此主机时,会显示相关事件。",
|
||||
"noHosts": "没有已配对的主机。",
|
||||
"privacyHint": "发送一份经过脱敏且大小受限的报告,包含主机名、端点、版本、连接状态和事件,绝不包含终端内容或凭据。",
|
||||
"reconnecting": "重新连接中",
|
||||
"retrySending": "重试发送",
|
||||
"sendDiagnostics": "向 Orca 发送诊断信息",
|
||||
"sending": "正在发送…",
|
||||
"sent": "诊断信息已发送",
|
||||
"stateWithAttempt": "{{state}} · 第 {{attempts}} 次尝试",
|
||||
"title": "网络诊断"
|
||||
},
|
||||
"mobileWebBundleProbeRow": {
|
||||
"assets": "资源",
|
||||
"build": "构建版本",
|
||||
"bundle": "资源包",
|
||||
"bytes": "字节数",
|
||||
"connecting": "正在连接…",
|
||||
"elapsed": "耗时",
|
||||
"elapsedMilliseconds": "{{elapsedMs}} 毫秒",
|
||||
"fetchBundle": "获取移动端网页资源包",
|
||||
"fetching": "正在获取资源包…",
|
||||
"host": "主机",
|
||||
"unknownHost": "未知主机"
|
||||
},
|
||||
"mobileWebShellUpdateFailureRow": {
|
||||
"title": "工作区更新"
|
||||
},
|
||||
"troubleshootCommonIssues": {
|
||||
"allowNotifications": "检查系统设置是否允许 Orca 发送通知,并确认专注模式或勿扰模式已关闭。",
|
||||
"checkTailnet": "确认桌面未休眠,并在 tailnet 中显示为已连接。",
|
||||
"desktopTitle": "桌面应用未运行",
|
||||
"disableVpn": "关闭该 VPN,或启用分流功能/“允许 LAN”选项。",
|
||||
"firewallTitle": "防火墙拦截端口 6768",
|
||||
"keepTailscaleOn": "100.x.x.x 或 *.ts.net 等主机地址通过 Tailscale 连接,请保持 Tailscale 开启。",
|
||||
"macosFirewall": "macOS:系统设置 → 网络 → 防火墙,允许 Orca 通过。",
|
||||
"notificationNetwork": "尝试切换到蜂窝网络或其他 Wi-Fi 网络。如果切换后能收到通知,则原网络可能导致通知延迟。",
|
||||
"notificationsTitle": "推送通知",
|
||||
"openDesktop": "桌面上的 Orca 必须保持运行才能接受连接。",
|
||||
"pairAfterUpdate": "更新后可能需要重新扫描二维码配对。",
|
||||
"reconnectWifi": "尝试在两台设备上重新连接 WiFi。",
|
||||
"restartBothApps": "如果持续超时,请重启手机端和桌面端应用。",
|
||||
"restartDesktop": "尝试重启 Orca,移动端连接服务会随应用启动。",
|
||||
"restartTailscale": "iOS/Android 可能在无提示的情况下导致隧道卡住:请在 Tailscale 应用中关闭 Tailscale 后再重新开启。",
|
||||
"restrictedNetwork": "公司或学校网络可能阻止 P2P 连接,请尝试使用个人热点。",
|
||||
"retryHost": "返回主机列表,点按要连接的主机以重试。",
|
||||
"sameNetwork": "两台设备必须处于同一 LAN(通过 Tailscale 连接时除外)。",
|
||||
"sameSubnet": "以太网和 WiFi 必须位于同一子网。",
|
||||
"tailscaleTitle": "无法访问 Tailscale 主机",
|
||||
"timeoutTitle": "连接超时",
|
||||
"updateTailscale": "请更新 Tailscale 应用,近期版本修复了重新连接时的问题。",
|
||||
"vpnRouting": "Tailscale 以外的 VPN 可能会将本地流量经由远程服务器转发。",
|
||||
"vpnTitle": "其他 VPN 干扰",
|
||||
"wifiSignal": "检查手机的 WiFi 信号强度。",
|
||||
"wifiTitle": "WiFi 网络不同",
|
||||
"windowsFirewall": "Windows:Defender 防火墙 → 允许应用,允许 Orca 通过专用网络。"
|
||||
},
|
||||
"troubleshootView": {
|
||||
"commonIssues": "常见问题",
|
||||
"runAgain": "重新运行",
|
||||
"runDiagnostics": "运行诊断",
|
||||
"running": "运行中…",
|
||||
"title": "故障排查",
|
||||
"viewNetworkDiagnostics": "查看网络诊断"
|
||||
},
|
||||
"useTroubleshootDiagnostics": {
|
||||
"connected": "已连接",
|
||||
"hostDataFailed": "无法读取主机数据",
|
||||
"hostTestFailed": "无法测试",
|
||||
"hosts": "主机",
|
||||
"internet": "互联网",
|
||||
"noConnection": "未连接",
|
||||
"noPairedHosts": "尚未配对,扫描二维码即可配对",
|
||||
"pairedCount": "已配对 {{count}} 台",
|
||||
"pairedHosts": "已配对的主机",
|
||||
"platform": "平台",
|
||||
"reachable": "可通过 {{endpoint}} 访问",
|
||||
"unexpectedResponse": "响应异常"
|
||||
},
|
||||
"webShellUpdateFailureCopy": {
|
||||
"failedAgo": "来自 {{hostName}} 的上次更新于 {{time}} 前失败:{{reason}}{{offered}}。{{outcome}}",
|
||||
"failedJustNow": "来自 {{hostName}} 的上次更新刚刚失败:{{reason}}{{offered}}。{{outcome}}"
|
||||
}
|
||||
},
|
||||
"dictation": {
|
||||
"setup": {
|
||||
"alreadyActive": "电脑或另一部手机正在使用听写。",
|
||||
"cloudModelNeedsKey": "需要 OpenAI 密钥",
|
||||
"deleteFailed": "删除模型失败",
|
||||
"desktopUpdateRequired": "请更新已配对的 Orca 桌面应用,以使用移动端语音设置。",
|
||||
"downloadFailed": "启动下载失败",
|
||||
"loadFailed": "加载听写模型失败",
|
||||
"repairRequired": "请将此手机与电脑重新配对,以使用听写。",
|
||||
"serverUpdateRequired": "请更新此服务器上的 Orca,以使用语音听写。",
|
||||
"sessionLost": "听写与电脑的连接已断开。请重试。",
|
||||
"updateFailed": "更新听写设置失败"
|
||||
}
|
||||
},
|
||||
"home": {
|
||||
"accountUsageCards": {
|
||||
"sectionHeading": "账户使用情况",
|
||||
"sessionWindow": "5小时",
|
||||
"systemDefault": "系统默认",
|
||||
"weeklyWindow": "7天"
|
||||
},
|
||||
"appUpdateCard": {
|
||||
"dismissAccessibilityLabel": "关闭 Orca {{version}} 更新提示",
|
||||
"title": "Orca {{version}} 已可用",
|
||||
"updateAccessibilityLabel": "Orca {{version}} 已可用,点按更新",
|
||||
"updateHint": "点按更新"
|
||||
},
|
||||
"emptyState": {
|
||||
"connectedDescription": "桌面将显示在这里。所有数据均采用端到端加密。",
|
||||
"connectedTitle": "已连接",
|
||||
"description": "与电脑上的 Orca 配对,即可在手机上查看智能体、进入任意终端并推进工作。",
|
||||
"openDesktopDescription": "前往“设置” → “移动端”,生成配对二维码。",
|
||||
"openDesktopTitle": "打开 Orca 桌面应用",
|
||||
"pairDesktop": "配对桌面",
|
||||
"scanCodeDescription": "点按上方按钮打开扫码器,然后对准屏幕上的二维码。",
|
||||
"scanCodeTitle": "扫描二维码",
|
||||
"stepsHeading": "使用方法",
|
||||
"title": "连接桌面"
|
||||
},
|
||||
"listFooter": {
|
||||
"hostPickerTitle": "选择打开任务的主机",
|
||||
"resumeHeading": "继续",
|
||||
"tasksHeading": "任务"
|
||||
},
|
||||
"listHeader": {
|
||||
"agentTime": "智能体时间",
|
||||
"agentsSpawned": "已启动智能体",
|
||||
"daysHours": "{{days}} 天 {{hours}} 小时",
|
||||
"desktopsHeading": "台式机",
|
||||
"hoursMinutes": "{{hours}} 小时 {{minutes}} 分钟",
|
||||
"minutes": "{{minutes}} 分钟",
|
||||
"prsCreated": "已创建 PR",
|
||||
"title": "欢迎回来"
|
||||
},
|
||||
"screen": {
|
||||
"pairingCheckFailed": "无法检查配对状态",
|
||||
"removeHostFailed": "无法移除主机",
|
||||
"removeHostMessage": "要移除“{{name}}”吗?之后可重新配对。",
|
||||
"removeHostTitle": "移除主机",
|
||||
"tryAgain": "请重试。"
|
||||
},
|
||||
"tasksCard": {
|
||||
"noSources": "未连接任务来源",
|
||||
"openProviderTasks": "打开 {{provider}} 任务",
|
||||
"title": "任务"
|
||||
}
|
||||
},
|
||||
"hooks": {
|
||||
"dictationDesktopStart": {
|
||||
"recordingFailed": "麦克风录音启动失败"
|
||||
},
|
||||
"useMobileDictation": {
|
||||
"inputClosed": "此会话已不再接受输入,语音听写已停止。请重试。",
|
||||
"microphoneInitFailed": "麦克风初始化失败",
|
||||
"microphoneInterrupted": "麦克风录音中断,语音听写已停止。请重试。",
|
||||
"microphonePermissionDenied": "麦克风权限被拒绝",
|
||||
"noSpeech": "未检测到语音。"
|
||||
}
|
||||
},
|
||||
"host": {
|
||||
"edit": {
|
||||
"address": "地址",
|
||||
"addressHint": "支持 IP、host:port 或 ws:// / wss://。wss:// URL 未指定端口时使用 443。仅输入地址时保留当前端口(或使用 6768)。",
|
||||
"connectionPreview": "连接到 {{endpoint}}",
|
||||
"goBack": "返回",
|
||||
"help": "更改显示名称或连接地址。名称留空时,将使用桌面提供的名称。更改地址只会切换此手机的连接地址,不会重新配对。当同一桌面可通过不同 IP 访问时(例如家庭局域网与 Tailscale),可使用此功能。",
|
||||
"hostNamePlaceholder": "主机名",
|
||||
"hostRemoved": "此主机已从这部手机上移除。",
|
||||
"loadFailed": "加载主机失败。",
|
||||
"missingHost": "缺少主机信息。",
|
||||
"name": "名称",
|
||||
"saveFailed": "保存主机失败。",
|
||||
"saveHost": "保存主机",
|
||||
"title": "编辑主机"
|
||||
}
|
||||
},
|
||||
"hostScreen": {
|
||||
"hostListActionSheetActions": {
|
||||
"connect": "连接",
|
||||
"disconnect": "断开连接",
|
||||
"editHost": "编辑主机",
|
||||
"networkDiagnostics": "网络诊断",
|
||||
"reconnect": "重新连接"
|
||||
},
|
||||
"hostPendingCreationRow": {
|
||||
"creatingWorkspace": "正在创建工作区…"
|
||||
},
|
||||
"hostRailActions": {
|
||||
"accounts": "账户",
|
||||
"closeSearch": "关闭搜索",
|
||||
"floatingWorkspace": "浮动工作区",
|
||||
"newWorkspace": "新工作区",
|
||||
"searchWorkspaces": "搜索工作区",
|
||||
"tasks": "任务"
|
||||
},
|
||||
"hostScreenHeader": {
|
||||
"accounts": "账户",
|
||||
"activeFilters_one": "工作区选项(已启用 {{count}} 个筛选条件)",
|
||||
"activeFilters_other": "工作区选项(已启用 {{count}} 个筛选条件)",
|
||||
"backToHosts": "返回主机列表",
|
||||
"closeSearch": "关闭搜索",
|
||||
"floatingWorkspace": "浮动工作区",
|
||||
"hideSidebar": "隐藏侧边栏",
|
||||
"reconnect": "重新连接",
|
||||
"searchWorkspaces": "搜索工作区",
|
||||
"tasks": "任务",
|
||||
"workspaceOptions": "工作区选项"
|
||||
},
|
||||
"hostScreenOverlays": {
|
||||
"deleteConfirmMessage": "要删除“{{name}}”({{branch}})吗?",
|
||||
"deleteWorktree": "删除工作树",
|
||||
"pin": "固定",
|
||||
"primaryWorktreeDeleteHint": "主工作树不能删除。请改为移除项目。",
|
||||
"removeHost": "移除主机",
|
||||
"removeHostMessage": "要移除“{{name}}”吗?之后可重新配对。",
|
||||
"sleep": "休眠",
|
||||
"unpin": "取消固定"
|
||||
},
|
||||
"hostWorkspaceList": {
|
||||
"searchPlaceholder": "搜索工作区…",
|
||||
"searchWorkspaces": "搜索工作区"
|
||||
},
|
||||
"workspaceDetailsSheet": {
|
||||
"checksFailing": "检查项:未通过",
|
||||
"checksPassing": "检查项:已通过",
|
||||
"checksPending": "检查项:等待中",
|
||||
"closed": "已关闭",
|
||||
"createdByAutomation": "由自动化创建",
|
||||
"createdByNamedAutomation": "由自动化“{{name}}”创建",
|
||||
"createdFromCli": "通过 Orca CLI 创建",
|
||||
"draft": "草稿",
|
||||
"merged": "已合并",
|
||||
"openLink": "打开 {{label}}",
|
||||
"openLinkWithTitle": "打开 {{label}},{{title}}",
|
||||
"prOpen": "开放"
|
||||
},
|
||||
"workspaceOptionControls": {
|
||||
"agentActivity": "智能体活动",
|
||||
"agentActivityLayout": "智能体活动布局",
|
||||
"agentActivitySort": "智能体活动",
|
||||
"agentActivitySortDescription": "需要关注的智能体,然后是最近的活动。",
|
||||
"agentStatuses": "智能体状态",
|
||||
"allHosts": "所有主机",
|
||||
"allProjects": "所有项目",
|
||||
"automation": "自动化",
|
||||
"branchFolderPath": "分支 / 文件夹路径",
|
||||
"branchName": "分支名称",
|
||||
"compact": "紧凑",
|
||||
"detailed": "详细的",
|
||||
"fullList": "完整列表",
|
||||
"githubIssues": "GitHub 议题",
|
||||
"githubTicket": "GitHub 票证",
|
||||
"host": "主机",
|
||||
"hover": "悬停",
|
||||
"jiraIssue": "Jira 议题",
|
||||
"jiraIssues": "Jira 议题",
|
||||
"linearIssue": "Linear 议题",
|
||||
"linearIssues": "Linear 议题",
|
||||
"manual": "手动的",
|
||||
"manualProjectOrderDescription": "拖动项目进行排列",
|
||||
"manualWorkspaceOrderDescription": "拖动工作区以将它们排列在每个组中。",
|
||||
"name": "名称",
|
||||
"none": "无",
|
||||
"notes": "笔记",
|
||||
"ports": "端口",
|
||||
"pr": "PR",
|
||||
"prMrLink": "PR/MR链接",
|
||||
"project": "项目",
|
||||
"recent": "最近的",
|
||||
"recentProjectOrderDescription": "最近的工作区活动",
|
||||
"searchProjects": "搜索项目",
|
||||
"searchProjectsPlaceholder": "搜索项目…",
|
||||
"selectedCount": "{{count}} / {{total}}",
|
||||
"status": "状态"
|
||||
},
|
||||
"workspaceOptionsSheet": {
|
||||
"backToOptions": "返回工作区选项",
|
||||
"cardDisplay": "卡片显示",
|
||||
"cardLayout": "卡片布局",
|
||||
"clearFilters": "清除筛选",
|
||||
"exceptDefaultBranch": "默认分支除外",
|
||||
"filters": "筛选条件",
|
||||
"groupBy": "分组依据",
|
||||
"hideAutomationCreated": "隐藏自动化创建的工作区",
|
||||
"hideCliCreated": "隐藏 CLI 创建的工作区",
|
||||
"hideDefaultBranch": "隐藏默认分支",
|
||||
"hideDetachedHead": "隐藏分离 HEAD",
|
||||
"hideOtherClientWorkspaces": "隐藏其他客户端工作区",
|
||||
"hideSleeping": "隐藏休眠项",
|
||||
"hosts": "主机",
|
||||
"localLinux": "本地 Linux",
|
||||
"localMac": "本地 Mac",
|
||||
"localWindows": "本地 Windows",
|
||||
"projectOrder": "项目排序",
|
||||
"projects": "项目",
|
||||
"savingToDesktop": "正在保存到桌面",
|
||||
"show": "显示",
|
||||
"showProperties": "显示属性",
|
||||
"sortBy": "排序方式",
|
||||
"syncedAcrossDevices": "已在各设备间同步",
|
||||
"thisComputer": "本机",
|
||||
"waitingForDesktop": "仍在等待桌面响应",
|
||||
"workspaceOptions": "工作区选项"
|
||||
}
|
||||
},
|
||||
"navigation": {
|
||||
"hostStackScreens": {
|
||||
"accounts": "账户",
|
||||
"agentSessionHistory": "智能体会话历史",
|
||||
"changes": "更改",
|
||||
"editHost": "编辑主机",
|
||||
"host": "主机",
|
||||
"pullRequest": "PR",
|
||||
"sourceControl": "源代码控制",
|
||||
"tasks": "任务",
|
||||
"terminal": "终端",
|
||||
"workspace": "工作区"
|
||||
}
|
||||
},
|
||||
"notifications": {
|
||||
"notificationDeliverySection": {
|
||||
"footer": "提醒类型遵循各个已配对桌面的通知设置。连续 7 天未使用此应用后,通知将暂停;打开应用并重新连接即可恢复。",
|
||||
"onlyWhenAway": "仅在离开桌面时",
|
||||
"onlyWhenAwayHint": "键盘和鼠标均无操作达 3 分钟后,或锁屏时。",
|
||||
"sound": "通知声音",
|
||||
"suppressWhileFocused": "查看时不提醒",
|
||||
"suppressWhileFocusedHint": "不发送此手机上当前打开的工作区的提醒。"
|
||||
}
|
||||
},
|
||||
"onboarding": {
|
||||
"notificationOnboardingPreview": {
|
||||
"claudeNeedsInput": "Claude 需要输入",
|
||||
"codexFinished": "Codex 已完成",
|
||||
"now": "现在",
|
||||
"testsPassing": "测试已通过。",
|
||||
"waitingOnYou": "等待您的回应。"
|
||||
},
|
||||
"onboardingPage": {
|
||||
"enableAgentNotifications": "启用智能体通知",
|
||||
"enableNotifications": "启用通知",
|
||||
"keepTerminal": "继续使用终端",
|
||||
"notNow": "暂不",
|
||||
"notificationsDescription": "智能体完成任务或等待响应时,即使未使用应用,此手机也会收到通知。",
|
||||
"notificationsDisclosure": "桌面空闲 3 分钟后,通过 Orca 的推送服务发送通知。可随时在“设置”中更改。",
|
||||
"notificationsTitle": "智能体需要您时,及时收到提醒",
|
||||
"openInChatUi": "在聊天界面中打开会话",
|
||||
"openInTerminal": "在终端中打开会话",
|
||||
"sessionViewDescription": "选择在此设备上以终端还是聊天界面打开受支持的智能体会话。长按会话标签页可切换视图,也可稍后在“设置”中更改默认方式。",
|
||||
"sessionViewTitle": "以何种方式打开会话?",
|
||||
"skipNotifications": "暂不启用通知",
|
||||
"useChatUi": "使用聊天界面"
|
||||
}
|
||||
},
|
||||
"settings": {
|
||||
"aboutScreen": {
|
||||
"openLinkFailed": "无法打开链接。请重试。",
|
||||
"socialProfile": "Orca 的 X 主页",
|
||||
"sourceCode": "Orca 源代码",
|
||||
"tagline": "助力开发者百倍提效的开源智能体 IDE",
|
||||
"title": "关于",
|
||||
"website": "Orca 网站"
|
||||
},
|
||||
"appUpdateSettingsRows": {
|
||||
"checkFailed": "检查失败",
|
||||
"checkUpdates": "检查更新",
|
||||
"checkUpdatesStatus": "检查更新,{{status}}",
|
||||
"checking": "检查中…",
|
||||
"installedVersion": "版本 {{version}}",
|
||||
"lastCheckedDays_one": "上次检查:{{count}} 天前",
|
||||
"lastCheckedDays_other": "上次检查:{{count}} 天前",
|
||||
"lastCheckedHours_one": "上次检查:{{count}} 小时前",
|
||||
"lastCheckedHours_other": "上次检查:{{count}} 小时前",
|
||||
"lastCheckedJustNow": "上次检查:刚刚",
|
||||
"lastCheckedMinutes_one": "上次检查:{{count}} 分钟前",
|
||||
"lastCheckedMinutes_other": "上次检查:{{count}} 分钟前",
|
||||
"neverChecked": "从未",
|
||||
"upToDate": "已是最新",
|
||||
"update": "更新",
|
||||
"updateToVersion": "更新至 Orca {{version}}"
|
||||
},
|
||||
"appearance": {
|
||||
"language": {
|
||||
"description": "选择“系统”时,将跟随此设备的语言。权限提示始终使用设备语言。",
|
||||
@@ -17,6 +608,157 @@
|
||||
"system": "系统"
|
||||
},
|
||||
"title": "外观"
|
||||
},
|
||||
"browserSettingsScreen": {
|
||||
"desktopBrowser": "桌面 Orca 浏览器",
|
||||
"desktopBrowserDescription": "在已配对桌面通过串流提供的浏览器中打开。",
|
||||
"linksDescription": "选择点按终端输出中的 HTTP(S) 链接时使用的浏览器。",
|
||||
"linksHeading": "链接",
|
||||
"loadFailed": "无法加载浏览器偏好设置。请重试。",
|
||||
"openTerminalLinks": "打开终端链接",
|
||||
"phoneBrowser": "手机浏览器",
|
||||
"phoneBrowserDescription": "在此手机的 Safari、Chrome 或其他浏览器中打开。",
|
||||
"saveFailed": "无法保存浏览器偏好设置。请重试。",
|
||||
"title": "浏览器"
|
||||
},
|
||||
"menuItems": {
|
||||
"about": "关于",
|
||||
"appearance": "外观",
|
||||
"browser": "浏览器",
|
||||
"chatUi": "聊天界面",
|
||||
"notifications": "通知",
|
||||
"terminal": "终端",
|
||||
"troubleshooting": "故障排查",
|
||||
"voice": "语音"
|
||||
},
|
||||
"menuScreen": {
|
||||
"privacyPolicy": "隐私政策",
|
||||
"support": "支持"
|
||||
},
|
||||
"nativeChatSettingsScreen": {
|
||||
"defaultViewDescription": "选择受支持的智能体会话(Claude、Codex 及其他支持聊天的智能体)在此设备上的打开方式。终端显示原始 CLI;聊天界面则显示与桌面应用类似的聊天界面。您仍可通过长按菜单切换单个会话的视图。",
|
||||
"defaultViewHeading": "默认视图",
|
||||
"off": "关闭",
|
||||
"on": "开启",
|
||||
"openInChatUi": "在聊天界面中打开会话",
|
||||
"title": "聊天界面"
|
||||
},
|
||||
"nativeNotificationDeliverySettings": {
|
||||
"loadFailed": "无法加载通知投递设置。请重新打开此页面重试。",
|
||||
"saveFailed": "无法保存通知投递设置。请重试。",
|
||||
"updateDesktopHint": "配对已更新的桌面,即可在此手机上接收通知。"
|
||||
},
|
||||
"notificationDisplayTest": {
|
||||
"accepted": "Orca 推送服务已接受请求。请查看通知。",
|
||||
"connectDesktop": "请先连接桌面,然后重试。",
|
||||
"description": "通过 Orca 推送服务发送测试通知。",
|
||||
"desktopUnreachable": "无法连接到桌面。请重试。",
|
||||
"loadFailed": "无法加载已配对的桌面。",
|
||||
"pairDesktop": "请先配对桌面,然后重试。",
|
||||
"pushServiceFailed": "无法通过 Orca 推送服务发送。请重试。",
|
||||
"rateLimited": "通知发送过于频繁。请稍后重试。",
|
||||
"reconnectPhone": "请重新连接,将此手机注册为通知接收设备。",
|
||||
"sendButton": "发送测试通知",
|
||||
"sendFailed": "无法发送测试推送。",
|
||||
"sending": "正在发送…",
|
||||
"troubleReceivingAlerts": "接收提醒遇到问题?",
|
||||
"troubleshooting": "故障排查",
|
||||
"updateDesktop": "请更新桌面以运行此测试。"
|
||||
},
|
||||
"notificationSettingsScreen": {
|
||||
"description": "智能体需要您提供输入或已完成任务时,在此设备上接收通知。",
|
||||
"enableNotifications": "启用通知",
|
||||
"loadFailed": "无法加载通知设置。请重试。",
|
||||
"notificationsBlocked": "系统设置中已禁用通知。",
|
||||
"openSettings": "打开设置",
|
||||
"openSettingsFailed": "无法打开系统设置。请重试。",
|
||||
"saveFailed": "无法保存通知设置。请重试。",
|
||||
"title": "通知"
|
||||
},
|
||||
"pendingCredentialCleanupCard": {
|
||||
"cleanupUnconfirmed_one": "无法确认此设备上的 {{count}} 个凭据是否已清理。",
|
||||
"cleanupUnconfirmed_other": "无法确认此设备上的 {{count}} 个凭据是否已清理。",
|
||||
"retryCleanup": "重试清除配对凭据",
|
||||
"retryFailed": "仍无法确认清理结果。请稍后重试。",
|
||||
"statusCheckFailed": "无法检查此设备上的清理状态。为确保安全,请重试。",
|
||||
"title": "配对凭据清理"
|
||||
},
|
||||
"uiLanguageSettingsSection": {
|
||||
"pseudoLanguage": "伪本地化(en-XA)"
|
||||
},
|
||||
"voiceSettingsScreen": {
|
||||
"connectDesktop": "连接到桌面后即可管理语音设置。",
|
||||
"deleteFailed": "删除失败",
|
||||
"dictationDescription": "通过听写向桌面上任意获得焦点的窗格输入文本。",
|
||||
"dictationHeading": "听写",
|
||||
"dictationMode": "听写模式",
|
||||
"dictationModeDescription": "切换:按一次开始,再按一次停止。按住:按住期间进行听写。",
|
||||
"downloadFailed": "下载失败",
|
||||
"enableDictation": "启用语音听写",
|
||||
"holdMode": "按住",
|
||||
"loadFailed": "加载语音设置失败",
|
||||
"loadFailedMessage": "加载语音设置失败。",
|
||||
"noneSelected": "未选择",
|
||||
"selectModelFailed": "无法选择模型",
|
||||
"speechModel": "语音模型",
|
||||
"speechModelHeading": "语音模型",
|
||||
"title": "语音",
|
||||
"toggleMode": "切换",
|
||||
"updateFailed": "无法更新"
|
||||
}
|
||||
},
|
||||
"webShell": {
|
||||
"shellStateScreen": {
|
||||
"checkingHost": "正在检查主机",
|
||||
"connectToDownload": "连接到此主机以下载最新版本的应用",
|
||||
"documentLoadFailed": "无法打开已下载的应用。",
|
||||
"downloadFailed": "无法从此主机下载最新版本的应用。",
|
||||
"hostProfileUnreadable": "无法在此设备上读取此主机的信息。请重试。",
|
||||
"isolationUnavailable": "此设备的 WebView 版本过旧,无法安全打开工作区。",
|
||||
"openingWorkspace": "正在打开工作区",
|
||||
"renderProcessGone": "工作区已停止响应。",
|
||||
"statusUnreadable": "无法读取此主机的状态。请返回后重新打开。",
|
||||
"tryAgain": "重试"
|
||||
}
|
||||
},
|
||||
"worktree": {
|
||||
"agentRowDisplay": {
|
||||
"blocked": "已阻塞",
|
||||
"days_one": "{{count}} 天",
|
||||
"days_other": "{{count}} 天",
|
||||
"done": "已完成",
|
||||
"failed": "失败",
|
||||
"hours_one": "{{count}} 小时",
|
||||
"hours_other": "{{count}} 小时",
|
||||
"idle": "空闲",
|
||||
"interrupted": "已中断",
|
||||
"justNow": "刚刚",
|
||||
"minutes_one": "{{count}} 分钟",
|
||||
"minutes_other": "{{count}} 分钟",
|
||||
"monitoring": "监控后台任务",
|
||||
"stopping": "正在停止…",
|
||||
"unconfirmed": "无法确认",
|
||||
"unverifiable": "暂无近期更新",
|
||||
"waiting": "等待输入",
|
||||
"working": "工作中"
|
||||
},
|
||||
"hostWorkspaceListStates": {
|
||||
"checkHost": "请确认电脑已唤醒且 Orca 正在运行",
|
||||
"empty": "暂无工作区",
|
||||
"loadFailed": "无法从此主机加载工作区",
|
||||
"noFilterMatches": "没有符合筛选条件的工作区",
|
||||
"noMatches": "没有匹配的工作区",
|
||||
"reopen": "重新打开以更新",
|
||||
"reopenDetail": "此页面尚无法识别此主机的工作区列表格式。",
|
||||
"retrying": "worktree.ps 失败({{error}}),正在自动重试"
|
||||
},
|
||||
"workspaceListSections": {
|
||||
"all": "全部",
|
||||
"closed": "已关闭",
|
||||
"done": "已完成",
|
||||
"inProgress": "进行中",
|
||||
"inReview": "评审中",
|
||||
"pinned": "已固定"
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -5,6 +5,8 @@ import { SideSafeArea } from '../layout/side-safe-area'
|
||||
import { colors, radii, spacing, typography } from '../theme/mobile-theme'
|
||||
import { themedStyleSheet } from '../theme/themed-styles'
|
||||
import { useMobileColorScheme } from '../theme/use-mobile-color-scheme'
|
||||
import type { Translate } from '../i18n/mobile-i18n'
|
||||
import { useMobileTranslation } from '../i18n/use-mobile-translation'
|
||||
import type { HostDisplaySource } from '../transport/use-host-display'
|
||||
import type { DownloadProgressFeed } from './download-progress-feed'
|
||||
import type {
|
||||
@@ -13,23 +15,38 @@ import type {
|
||||
} from './mobile-web-shell-session-contract'
|
||||
import { PageRouteUnavailableScreen } from './PageRouteUnavailableScreen'
|
||||
import { ShellDownloadProgress } from './ShellDownloadProgress'
|
||||
import { SHELL_OPENING_LABEL, ShellWaitingFrame } from './ShellWaitingFrame'
|
||||
import { ShellWaitingFrame } from './ShellWaitingFrame'
|
||||
|
||||
function failureMessage(reason: MobileWebShellFailureCause): string {
|
||||
function failureMessage(reason: MobileWebShellFailureCause, t: Translate): string {
|
||||
switch (reason) {
|
||||
case 'isolation-unavailable':
|
||||
return "This device's WebView is too old to open the workspace safely."
|
||||
return t(
|
||||
'webShell.shellStateScreen.isolationUnavailable',
|
||||
"This device's WebView is too old to open the workspace safely."
|
||||
)
|
||||
case 'download-failed':
|
||||
return 'The latest app could not be downloaded from this host.'
|
||||
return t(
|
||||
'webShell.shellStateScreen.downloadFailed',
|
||||
'The latest app could not be downloaded from this host.'
|
||||
)
|
||||
case 'host-profile-unreadable':
|
||||
return 'Could not read this host on your device. Try again.'
|
||||
return t(
|
||||
'webShell.shellStateScreen.hostProfileUnreadable',
|
||||
'Could not read this host on your device. Try again.'
|
||||
)
|
||||
case 'status-unreadable':
|
||||
return "Could not read this host's status. Go back and reopen it."
|
||||
return t(
|
||||
'webShell.shellStateScreen.statusUnreadable',
|
||||
"Could not read this host's status. Go back and reopen it."
|
||||
)
|
||||
case 'render-process-gone':
|
||||
return 'The workspace stopped responding.'
|
||||
return t('webShell.shellStateScreen.renderProcessGone', 'The workspace stopped responding.')
|
||||
case 'generation-unreadable':
|
||||
case 'document-load-failed':
|
||||
return 'The downloaded app could not be opened.'
|
||||
return t(
|
||||
'webShell.shellStateScreen.documentLoadFailed',
|
||||
'The downloaded app could not be opened.'
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
@@ -45,13 +62,14 @@ function Failed({
|
||||
state: Extract<MobileWebShellSessionState, { kind: 'failed' }>
|
||||
onRetry: () => void
|
||||
}) {
|
||||
const t = useMobileTranslation()
|
||||
useMobileColorScheme()
|
||||
// Retry cannot change a settled isolation gate or give an unsupported WebView the missing fence.
|
||||
const retryable = state.reason !== 'isolation-unavailable' && state.reason !== 'status-unreadable'
|
||||
return (
|
||||
<Centered>
|
||||
<Text style={styles.failedMessage} testID="mobile-web-shell-failed">
|
||||
{failureMessage(state.reason)}
|
||||
{failureMessage(state.reason, t)}
|
||||
</Text>
|
||||
{retryable ? (
|
||||
<Pressable
|
||||
@@ -59,7 +77,9 @@ function Failed({
|
||||
testID="mobile-web-shell-retry"
|
||||
onPress={onRetry}
|
||||
>
|
||||
<Text style={styles.retryLabel}>Try again</Text>
|
||||
<Text style={styles.retryLabel}>
|
||||
{t('webShell.shellStateScreen.tryAgain', 'Try again')}
|
||||
</Text>
|
||||
</Pressable>
|
||||
) : null}
|
||||
</Centered>
|
||||
@@ -79,6 +99,7 @@ function StateView({
|
||||
downloadProgress: DownloadProgressFeed
|
||||
host: HostDisplaySource | null
|
||||
}) {
|
||||
const t = useMobileTranslation()
|
||||
switch (state.kind) {
|
||||
case 'route-unavailable':
|
||||
return <PageRouteUnavailableScreen hostId={hostId} />
|
||||
@@ -90,7 +111,10 @@ function StateView({
|
||||
return (
|
||||
<Centered>
|
||||
<Text style={styles.waitingLabel} testID="mobile-web-shell-offline">
|
||||
Connect to this host to download the latest app
|
||||
{t(
|
||||
'webShell.shellStateScreen.connectToDownload',
|
||||
'Connect to this host to download the latest app'
|
||||
)}
|
||||
</Text>
|
||||
</Centered>
|
||||
)
|
||||
@@ -105,7 +129,11 @@ function StateView({
|
||||
return (
|
||||
<Centered>
|
||||
<ShellWaitingFrame
|
||||
label={state.kind === 'activating' ? SHELL_OPENING_LABEL : 'Checking host'}
|
||||
label={
|
||||
state.kind === 'activating'
|
||||
? t('webShell.shellStateScreen.openingWorkspace', 'Opening workspace')
|
||||
: t('webShell.shellStateScreen.checkingHost', 'Checking host')
|
||||
}
|
||||
/>
|
||||
</Centered>
|
||||
)
|
||||
|
||||
@@ -1,16 +1,68 @@
|
||||
import { translate } from '../i18n/mobile-i18n'
|
||||
|
||||
// Keep native/page stack declarations in sync.
|
||||
export const HOST_STACK_SCREENS = [
|
||||
{ name: '[hostId]/index', title: 'Host' },
|
||||
{ name: '[hostId]/edit', title: 'Edit host' },
|
||||
{ name: '[hostId]/accounts', title: 'Accounts' },
|
||||
{ name: '[hostId]/tasks', title: 'Tasks' },
|
||||
{ name: '[hostId]/session/[worktreeId]', title: 'Terminal' },
|
||||
{ name: '[hostId]/source-control/[worktreeId]', title: 'Source Control' },
|
||||
{ name: '[hostId]/agent-history/[worktreeId]', title: 'Agent Session History' },
|
||||
{ name: '[hostId]/review/[worktreeId]', title: 'Changes' },
|
||||
{ name: '[hostId]/pr/[worktreeId]', title: 'Pull Request' },
|
||||
{
|
||||
name: '[hostId]/index',
|
||||
get title() {
|
||||
return translate('navigation.hostStackScreens.host', 'Host')
|
||||
}
|
||||
},
|
||||
{
|
||||
name: '[hostId]/edit',
|
||||
get title() {
|
||||
return translate('navigation.hostStackScreens.editHost', 'Edit host')
|
||||
}
|
||||
},
|
||||
{
|
||||
name: '[hostId]/accounts',
|
||||
get title() {
|
||||
return translate('navigation.hostStackScreens.accounts', 'Accounts')
|
||||
}
|
||||
},
|
||||
{
|
||||
name: '[hostId]/tasks',
|
||||
get title() {
|
||||
return translate('navigation.hostStackScreens.tasks', 'Tasks')
|
||||
}
|
||||
},
|
||||
{
|
||||
name: '[hostId]/session/[worktreeId]',
|
||||
get title() {
|
||||
return translate('navigation.hostStackScreens.terminal', 'Terminal')
|
||||
}
|
||||
},
|
||||
{
|
||||
name: '[hostId]/source-control/[worktreeId]',
|
||||
get title() {
|
||||
return translate('navigation.hostStackScreens.sourceControl', 'Source Control')
|
||||
}
|
||||
},
|
||||
{
|
||||
name: '[hostId]/agent-history/[worktreeId]',
|
||||
get title() {
|
||||
return translate('navigation.hostStackScreens.agentSessionHistory', 'Agent Session History')
|
||||
}
|
||||
},
|
||||
{
|
||||
name: '[hostId]/review/[worktreeId]',
|
||||
get title() {
|
||||
return translate('navigation.hostStackScreens.changes', 'Changes')
|
||||
}
|
||||
},
|
||||
{
|
||||
name: '[hostId]/pr/[worktreeId]',
|
||||
get title() {
|
||||
return translate('navigation.hostStackScreens.pullRequest', 'Pull Request')
|
||||
}
|
||||
},
|
||||
// Declare catch-all last for its title; named children must match first. Undeclared routes would render with group options anyway.
|
||||
{ name: '[hostId]/[...page]', title: 'Workspace' }
|
||||
{
|
||||
name: '[hostId]/[...page]',
|
||||
get title() {
|
||||
return translate('navigation.hostStackScreens.workspace', 'Workspace')
|
||||
}
|
||||
}
|
||||
] as const
|
||||
|
||||
export type HostStackAnimation = 'none' | 'default'
|
||||
|
||||
@@ -1,9 +1,11 @@
|
||||
import { Stack } from 'expo-router'
|
||||
import { useMobileTranslation } from '../i18n/use-mobile-translation'
|
||||
import { colors } from '../theme/mobile-theme'
|
||||
import { useMobileColorScheme } from '../theme/use-mobile-color-scheme'
|
||||
import { HOST_STACK_SCREENS, type HostStackAnimation } from './host-stack-screens'
|
||||
|
||||
export function HostStack({ animation }: { animation: HostStackAnimation }) {
|
||||
useMobileTranslation()
|
||||
useMobileColorScheme()
|
||||
return (
|
||||
<Stack
|
||||
|
||||
@@ -1,6 +1,7 @@
|
||||
import { useCallback, useLayoutEffect, useRef, useState, type ReactNode } from 'react'
|
||||
import { StyleSheet, View } from 'react-native'
|
||||
import { Navigator, usePathname } from 'expo-router'
|
||||
import { useMobileTranslation } from '../i18n/use-mobile-translation'
|
||||
import { colors } from '../theme/mobile-theme'
|
||||
import { themedStyleSheet } from '../theme/themed-styles'
|
||||
import { useMobileColorScheme } from '../theme/use-mobile-color-scheme'
|
||||
@@ -10,6 +11,7 @@ import { SideRailOnTopContext } from '../layout/side-rail'
|
||||
|
||||
// Expo native-stack’s web view ignores animation and switches display; reuse StackRouter with a view that retains popped screens through their exit slide.
|
||||
export function HostStack({ animation }: { animation: HostStackAnimation }) {
|
||||
useMobileTranslation()
|
||||
return (
|
||||
<Navigator>
|
||||
{HOST_STACK_SCREENS.map(({ name, title }) => (
|
||||
|
||||
@@ -1,4 +1,5 @@
|
||||
import { Switch, Text, View } from 'react-native'
|
||||
import { useMobileTranslation } from '../i18n/use-mobile-translation'
|
||||
import { colors, radii, spacing, typography } from '../theme/mobile-theme'
|
||||
import { themedStyleSheet } from '../theme/themed-styles'
|
||||
import { useMobileColorScheme } from '../theme/use-mobile-color-scheme'
|
||||
@@ -11,6 +12,7 @@ type Props = {
|
||||
}
|
||||
|
||||
export function NotificationDeliverySection({ value, disabled, onChange }: Props) {
|
||||
const t = useMobileTranslation()
|
||||
useMobileColorScheme()
|
||||
const row = (key: keyof NotificationDeliveryPreferences, label: string, hint?: string) => {
|
||||
return (
|
||||
@@ -36,19 +38,33 @@ export function NotificationDeliverySection({ value, disabled, onChange }: Props
|
||||
<View style={styles.section}>
|
||||
{row(
|
||||
'onlyWhenDesktopAway',
|
||||
'Only when away from desktop',
|
||||
'After 3 minutes without keyboard or mouse activity, or when locked.'
|
||||
t(
|
||||
'notifications.notificationDeliverySection.onlyWhenAway',
|
||||
'Only when away from desktop'
|
||||
),
|
||||
t(
|
||||
'notifications.notificationDeliverySection.onlyWhenAwayHint',
|
||||
'After 3 minutes without keyboard or mouse activity, or when locked.'
|
||||
)
|
||||
)}
|
||||
{row('sound', 'Notification sound')}
|
||||
{row('sound', t('notifications.notificationDeliverySection.sound', 'Notification sound'))}
|
||||
{row(
|
||||
'suppressWhileViewing',
|
||||
'Suppress while focused',
|
||||
'Skip alerts for the workspace open on this phone.'
|
||||
t(
|
||||
'notifications.notificationDeliverySection.suppressWhileFocused',
|
||||
'Suppress while focused'
|
||||
),
|
||||
t(
|
||||
'notifications.notificationDeliverySection.suppressWhileFocusedHint',
|
||||
'Skip alerts for the workspace open on this phone.'
|
||||
)
|
||||
)}
|
||||
</View>
|
||||
<Text style={styles.footer}>
|
||||
Alert types follow each paired desktop’s notification settings. Notifications pause after 7
|
||||
days without using this app; open it and reconnect to resume.
|
||||
{t(
|
||||
'notifications.notificationDeliverySection.footer',
|
||||
'Alert types follow each paired desktop’s notification settings. Notifications pause after 7 days without using this app; open it and reconnect to resume.'
|
||||
)}
|
||||
</Text>
|
||||
</>
|
||||
)
|
||||
|
||||
@@ -1,5 +1,6 @@
|
||||
import { ActivityIndicator, Pressable, ScrollView, Text, View } from 'react-native'
|
||||
import { MessageSquare } from 'lucide-react-native'
|
||||
import { useMobileTranslation } from '../i18n/use-mobile-translation'
|
||||
import type { MobileOnboardingStep } from './mobile-onboarding-plan'
|
||||
import { mobileOnboardingStyles as styles } from './mobile-onboarding-styles'
|
||||
import { NotificationOnboardingPreview } from './NotificationOnboardingPreview'
|
||||
@@ -29,6 +30,7 @@ export function MobileOnboardingPage({
|
||||
onSessionChoice,
|
||||
onNotificationChoice
|
||||
}: Props) {
|
||||
const t = useMobileTranslation()
|
||||
useMobileColorScheme()
|
||||
const busy = busyChoice !== null
|
||||
const isSessionView = step === 'session-view'
|
||||
@@ -50,20 +52,33 @@ export function MobileOnboardingPage({
|
||||
<NotificationOnboardingPreview active={active} />
|
||||
)}
|
||||
<Text style={styles.title}>
|
||||
{isSessionView ? 'How should sessions open?' : 'Don’t miss when an agent needs you'}
|
||||
{isSessionView
|
||||
? t('onboarding.onboardingPage.sessionViewTitle', 'How should sessions open?')
|
||||
: t(
|
||||
'onboarding.onboardingPage.notificationsTitle',
|
||||
'Don’t miss when an agent needs you'
|
||||
)}
|
||||
</Text>
|
||||
<Text style={styles.body}>
|
||||
{isSessionView
|
||||
? 'Choose whether supported agent sessions open in the terminal or Chat UI on this device. Press and hold a session tab to switch its view, or change the default later in Settings.'
|
||||
: 'Get a notification on this phone when an agent finishes or is waiting — even if you aren’t using the app.'}
|
||||
? t(
|
||||
'onboarding.onboardingPage.sessionViewDescription',
|
||||
'Choose whether supported agent sessions open in the terminal or Chat UI on this device. Press and hold a session tab to switch its view, or change the default later in Settings.'
|
||||
)
|
||||
: t(
|
||||
'onboarding.onboardingPage.notificationsDescription',
|
||||
'Get a notification on this phone when an agent finishes or is waiting — even if you aren’t using the app.'
|
||||
)}
|
||||
</Text>
|
||||
</View>
|
||||
|
||||
<View style={styles.footer}>
|
||||
{!isSessionView ? (
|
||||
<Text style={styles.disclosure}>
|
||||
Delivered through Orca’s push service after your desktop has been idle for 3 minutes.
|
||||
Change this anytime in Settings.
|
||||
{t(
|
||||
'onboarding.onboardingPage.notificationsDisclosure',
|
||||
'Delivered through Orca’s push service after your desktop has been idle for 3 minutes. Change this anytime in Settings.'
|
||||
)}
|
||||
</Text>
|
||||
) : null}
|
||||
{error ? (
|
||||
@@ -94,19 +109,23 @@ function SessionViewChoices({
|
||||
disabled: boolean
|
||||
onChoice: (view: MobileSessionView) => void
|
||||
}) {
|
||||
const t = useMobileTranslation()
|
||||
return (
|
||||
<>
|
||||
<ChoiceButton
|
||||
label="Use Chat UI"
|
||||
accessibilityLabel="Open sessions in Chat UI"
|
||||
label={t('onboarding.onboardingPage.useChatUi', 'Use Chat UI')}
|
||||
accessibilityLabel={t('onboarding.onboardingPage.openInChatUi', 'Open sessions in Chat UI')}
|
||||
primary
|
||||
busy={busyChoice === 'chat'}
|
||||
disabled={disabled}
|
||||
onPress={() => onChoice('chat')}
|
||||
/>
|
||||
<ChoiceButton
|
||||
label="Keep terminal"
|
||||
accessibilityLabel="Open sessions in the terminal"
|
||||
label={t('onboarding.onboardingPage.keepTerminal', 'Keep terminal')}
|
||||
accessibilityLabel={t(
|
||||
'onboarding.onboardingPage.openInTerminal',
|
||||
'Open sessions in the terminal'
|
||||
)}
|
||||
busy={busyChoice === 'terminal'}
|
||||
disabled={disabled}
|
||||
onPress={() => onChoice('terminal')}
|
||||
@@ -124,19 +143,26 @@ function NotificationChoices({
|
||||
disabled: boolean
|
||||
onChoice: (choice: NotificationOnboardingChoice) => void
|
||||
}) {
|
||||
const t = useMobileTranslation()
|
||||
return (
|
||||
<>
|
||||
<ChoiceButton
|
||||
label="Enable notifications"
|
||||
accessibilityLabel="Enable agent notifications"
|
||||
label={t('onboarding.onboardingPage.enableNotifications', 'Enable notifications')}
|
||||
accessibilityLabel={t(
|
||||
'onboarding.onboardingPage.enableAgentNotifications',
|
||||
'Enable agent notifications'
|
||||
)}
|
||||
primary
|
||||
busy={busyChoice === 'enable'}
|
||||
disabled={disabled}
|
||||
onPress={() => onChoice('enable')}
|
||||
/>
|
||||
<ChoiceButton
|
||||
label="Not now"
|
||||
accessibilityLabel="Skip notifications for now"
|
||||
label={t('onboarding.onboardingPage.notNow', 'Not now')}
|
||||
accessibilityLabel={t(
|
||||
'onboarding.onboardingPage.skipNotifications',
|
||||
'Skip notifications for now'
|
||||
)}
|
||||
busy={busyChoice === 'skip'}
|
||||
disabled={disabled}
|
||||
onPress={() => onChoice('skip')}
|
||||
|
||||
@@ -1,15 +1,25 @@
|
||||
import { useEffect, useRef } from 'react'
|
||||
import { Animated, Easing, Text, View } from 'react-native'
|
||||
import { OrcaLogo } from '../components/OrcaLogo'
|
||||
import type { Translate } from '../i18n/mobile-i18n'
|
||||
import { useMobileTranslation } from '../i18n/use-mobile-translation'
|
||||
import { colors, radii, spacing, typography } from '../theme/mobile-theme'
|
||||
import { themedStyleSheet } from '../theme/themed-styles'
|
||||
import { useMobileColorScheme } from '../theme/use-mobile-color-scheme'
|
||||
import { useReducedMotionEnabled } from '../platform/use-reduced-motion'
|
||||
|
||||
const SAMPLE_NOTIFICATIONS = [
|
||||
{ title: 'Codex finished', body: 'Tests are passing.' },
|
||||
{ title: 'Claude needs input', body: 'Waiting on you.' }
|
||||
] as const
|
||||
function sampleNotifications(t: Translate) {
|
||||
return [
|
||||
{
|
||||
title: t('onboarding.notificationOnboardingPreview.codexFinished', 'Codex finished'),
|
||||
body: t('onboarding.notificationOnboardingPreview.testsPassing', 'Tests are passing.')
|
||||
},
|
||||
{
|
||||
title: t('onboarding.notificationOnboardingPreview.claudeNeedsInput', 'Claude needs input'),
|
||||
body: t('onboarding.notificationOnboardingPreview.waitingOnYou', 'Waiting on you.')
|
||||
}
|
||||
] as const
|
||||
}
|
||||
|
||||
const ENTER_MS = 676
|
||||
const EXIT_MS = 416
|
||||
@@ -24,7 +34,9 @@ type Props = {
|
||||
|
||||
/** Decorative banners; the surrounding copy is the accessible explanation. */
|
||||
export function NotificationOnboardingPreview({ active }: Props) {
|
||||
const t = useMobileTranslation()
|
||||
useMobileColorScheme()
|
||||
const notifications = sampleNotifications(t)
|
||||
const reduceMotion = useReducedMotionEnabled()
|
||||
const first = useRef(new Animated.Value(0)).current
|
||||
const second = useRef(new Animated.Value(0)).current
|
||||
@@ -79,16 +91,21 @@ export function NotificationOnboardingPreview({ active }: Props) {
|
||||
style={styles.stack}
|
||||
>
|
||||
<Animated.View style={bannerMotion(first)}>
|
||||
<SampleBanner notification={SAMPLE_NOTIFICATIONS[0]} />
|
||||
<SampleBanner notification={notifications[0]} />
|
||||
</Animated.View>
|
||||
<Animated.View style={bannerMotion(second)}>
|
||||
<SampleBanner notification={SAMPLE_NOTIFICATIONS[1]} />
|
||||
<SampleBanner notification={notifications[1]} />
|
||||
</Animated.View>
|
||||
</View>
|
||||
)
|
||||
}
|
||||
|
||||
function SampleBanner({ notification }: { notification: (typeof SAMPLE_NOTIFICATIONS)[number] }) {
|
||||
function SampleBanner({
|
||||
notification
|
||||
}: {
|
||||
notification: ReturnType<typeof sampleNotifications>[number]
|
||||
}) {
|
||||
const t = useMobileTranslation()
|
||||
useMobileColorScheme()
|
||||
return (
|
||||
<View style={styles.card}>
|
||||
@@ -98,7 +115,7 @@ function SampleBanner({ notification }: { notification: (typeof SAMPLE_NOTIFICAT
|
||||
<View style={styles.cardCopy}>
|
||||
<View style={styles.cardMeta}>
|
||||
<Text style={styles.appName}>Orca</Text>
|
||||
<Text style={styles.now}>now</Text>
|
||||
<Text style={styles.now}>{t('onboarding.notificationOnboardingPreview.now', 'now')}</Text>
|
||||
</View>
|
||||
<Text style={styles.cardTitle} numberOfLines={1}>
|
||||
{notification.title}
|
||||
|
||||
@@ -4,6 +4,7 @@ import { useSafeAreaInsets } from 'react-native-safe-area-context'
|
||||
import { ChevronLeft, Globe } from 'lucide-react-native'
|
||||
import Svg, { Path } from 'react-native-svg'
|
||||
import { OrcaLogo } from '../components/OrcaLogo'
|
||||
import { useMobileTranslation } from '../i18n/use-mobile-translation'
|
||||
import { colors, spacing, typography } from '../theme/mobile-theme'
|
||||
import { themedStyleSheet } from '../theme/themed-styles'
|
||||
import { useMobileColorScheme } from '../theme/use-mobile-color-scheme'
|
||||
@@ -35,11 +36,12 @@ export default function AboutScreen({
|
||||
openExternal: (url: string) => Promise<unknown>
|
||||
versionLabel: string
|
||||
}) {
|
||||
const t = useMobileTranslation()
|
||||
useMobileColorScheme()
|
||||
const [error, setError] = useState<string | null>(null)
|
||||
const [error, setError] = useState(false)
|
||||
const openLink = (url: string) => {
|
||||
setError(null)
|
||||
void openExternal(url).catch(() => setError('Could not open the link. Try again.'))
|
||||
setError(false)
|
||||
void openExternal(url).catch(() => setError(true))
|
||||
}
|
||||
const insets = useSafeAreaInsets()
|
||||
|
||||
@@ -49,25 +51,27 @@ export default function AboutScreen({
|
||||
<Pressable
|
||||
style={styles.backButton}
|
||||
accessibilityRole="button"
|
||||
accessibilityLabel="Back"
|
||||
accessibilityLabel={t('common.back', 'Back')}
|
||||
onPress={onBack}
|
||||
>
|
||||
<ChevronLeft size={22} color={colors.textSecondary} />
|
||||
</Pressable>
|
||||
<Text style={styles.heading}>About</Text>
|
||||
<Text style={styles.heading}>{t('settings.aboutScreen.title', 'About')}</Text>
|
||||
</View>
|
||||
|
||||
<View style={styles.brand}>
|
||||
<OrcaLogo size={28} />
|
||||
<Text style={styles.brandName}>Orca</Text>
|
||||
<Text style={styles.brandSub}>Open-source agent IDE for 100x builders</Text>
|
||||
<Text style={styles.brandSub}>
|
||||
{t('settings.aboutScreen.tagline', 'Open-source agent IDE for 100x builders')}
|
||||
</Text>
|
||||
</View>
|
||||
|
||||
<View style={styles.section}>
|
||||
<Pressable
|
||||
style={({ pressed }) => [styles.row, pressed && styles.rowPressed]}
|
||||
accessibilityRole="button"
|
||||
accessibilityLabel="Orca website"
|
||||
accessibilityLabel={t('settings.aboutScreen.website', 'Orca website')}
|
||||
onPress={() => openLink('https://onOrca.dev')}
|
||||
>
|
||||
<Globe size={16} color={colors.textSecondary} />
|
||||
@@ -77,7 +81,7 @@ export default function AboutScreen({
|
||||
<Pressable
|
||||
style={({ pressed }) => [styles.row, pressed && styles.rowPressed]}
|
||||
accessibilityRole="button"
|
||||
accessibilityLabel="Orca source code"
|
||||
accessibilityLabel={t('settings.aboutScreen.sourceCode', 'Orca source code')}
|
||||
onPress={() => openLink('https://github.com/stablyai/orca')}
|
||||
>
|
||||
<GithubIcon />
|
||||
@@ -87,7 +91,7 @@ export default function AboutScreen({
|
||||
<Pressable
|
||||
style={({ pressed }) => [styles.row, pressed && styles.rowPressed]}
|
||||
accessibilityRole="button"
|
||||
accessibilityLabel="Orca on X"
|
||||
accessibilityLabel={t('settings.aboutScreen.socialProfile', 'Orca on X')}
|
||||
onPress={() => openLink('https://x.com/orca_build')}
|
||||
>
|
||||
<XIcon />
|
||||
@@ -98,7 +102,7 @@ export default function AboutScreen({
|
||||
<Text style={styles.versionText}>{versionLabel}</Text>
|
||||
{error && (
|
||||
<Text accessibilityRole="alert" style={styles.errorText}>
|
||||
{error}
|
||||
{t('settings.aboutScreen.openLinkFailed', 'Could not open the link. Try again.')}
|
||||
</Text>
|
||||
)}
|
||||
</View>
|
||||
|
||||
@@ -1,27 +1,69 @@
|
||||
import { Pressable, StyleSheet, Text, View } from 'react-native'
|
||||
import type { Translate } from '../i18n/mobile-i18n'
|
||||
import { useMobileTranslation } from '../i18n/use-mobile-translation'
|
||||
import type { KnownAppUpdate } from '../storage/app-update-preferences'
|
||||
import { colors, spacing, typography } from '../theme/mobile-theme'
|
||||
import { themedStyleSheet } from '../theme/themed-styles'
|
||||
import { useMobileColorScheme } from '../theme/use-mobile-color-scheme'
|
||||
import { formatTimeAgo } from '../worktree/agent-row-display'
|
||||
|
||||
export type AppUpdateCheckRowStatus = 'idle' | 'checking' | 'up-to-date' | 'failed'
|
||||
|
||||
function checkRowValue(status: AppUpdateCheckRowStatus, lastCheckedAt: number | null, now: number) {
|
||||
function checkRowValue(
|
||||
t: Translate,
|
||||
status: AppUpdateCheckRowStatus,
|
||||
lastCheckedAt: number | null,
|
||||
now: number
|
||||
) {
|
||||
if (status === 'checking') {
|
||||
return 'Checking…'
|
||||
return t('settings.appUpdateSettingsRows.checking', 'Checking…')
|
||||
}
|
||||
if (status === 'up-to-date') {
|
||||
return 'Up to date'
|
||||
return t('settings.appUpdateSettingsRows.upToDate', 'Up to date')
|
||||
}
|
||||
if (status === 'failed') {
|
||||
return "Couldn't check"
|
||||
return t('settings.appUpdateSettingsRows.checkFailed', "Couldn't check")
|
||||
}
|
||||
if (lastCheckedAt === null) {
|
||||
return 'Never'
|
||||
return t('settings.appUpdateSettingsRows.neverChecked', 'Never')
|
||||
}
|
||||
const ago = formatTimeAgo(lastCheckedAt, now)
|
||||
return `Last checked ${ago === 'just now' ? ago : `${ago} ago`}`
|
||||
const delta = now - lastCheckedAt
|
||||
if (delta < 60_000) {
|
||||
return t('settings.appUpdateSettingsRows.lastCheckedJustNow', 'Last checked just now')
|
||||
}
|
||||
const minutes = Math.max(0, Math.floor(delta / 60_000))
|
||||
if (minutes < 60) {
|
||||
const count = minutes
|
||||
return count === 1
|
||||
? t('settings.appUpdateSettingsRows.lastCheckedMinutes_one', 'Last checked {{count}}m ago', {
|
||||
count
|
||||
})
|
||||
: t(
|
||||
'settings.appUpdateSettingsRows.lastCheckedMinutes_other',
|
||||
'Last checked {{count}}m ago',
|
||||
{
|
||||
count
|
||||
}
|
||||
)
|
||||
}
|
||||
const hours = Math.floor(minutes / 60)
|
||||
if (hours < 24) {
|
||||
const count = hours
|
||||
return count === 1
|
||||
? t('settings.appUpdateSettingsRows.lastCheckedHours_one', 'Last checked {{count}}h ago', {
|
||||
count
|
||||
})
|
||||
: t('settings.appUpdateSettingsRows.lastCheckedHours_other', 'Last checked {{count}}h ago', {
|
||||
count
|
||||
})
|
||||
}
|
||||
const count = Math.floor(hours / 24)
|
||||
return count === 1
|
||||
? t('settings.appUpdateSettingsRows.lastCheckedDays_one', 'Last checked {{count}}d ago', {
|
||||
count
|
||||
})
|
||||
: t('settings.appUpdateSettingsRows.lastCheckedDays_other', 'Last checked {{count}}d ago', {
|
||||
count
|
||||
})
|
||||
}
|
||||
|
||||
export function AppUpdateSettingsRows(props: {
|
||||
@@ -33,22 +75,37 @@ export function AppUpdateSettingsRows(props: {
|
||||
onUpdate: (url: string) => void
|
||||
onCheck: () => void
|
||||
}) {
|
||||
const t = useMobileTranslation()
|
||||
useMobileColorScheme()
|
||||
const checkValue = checkRowValue(props.checkStatus, props.lastCheckedAt, props.now)
|
||||
const checkValue = checkRowValue(t, props.checkStatus, props.lastCheckedAt, props.now)
|
||||
const { available, installedVersion, onUpdate } = props
|
||||
const versionRow = available ? (
|
||||
<Pressable
|
||||
accessibilityRole="button"
|
||||
accessibilityLabel={`Update to Orca ${available.version}`}
|
||||
accessibilityLabel={t(
|
||||
'settings.appUpdateSettingsRows.updateToVersion',
|
||||
'Update to Orca {{version}}',
|
||||
{
|
||||
version: available.version
|
||||
}
|
||||
)}
|
||||
style={({ pressed }) => [styles.row, pressed && styles.rowPressed]}
|
||||
onPress={() => onUpdate(available.url)}
|
||||
>
|
||||
<Text style={styles.rowLabel}>Update to Orca {available.version}</Text>
|
||||
<Text style={styles.actionValue}>Update</Text>
|
||||
<Text style={styles.rowLabel}>
|
||||
{t('settings.appUpdateSettingsRows.updateToVersion', 'Update to Orca {{version}}', {
|
||||
version: available.version
|
||||
})}
|
||||
</Text>
|
||||
<Text style={styles.actionValue}>{t('settings.appUpdateSettingsRows.update', 'Update')}</Text>
|
||||
</Pressable>
|
||||
) : installedVersion ? (
|
||||
<View style={styles.row}>
|
||||
<Text style={styles.rowLabel}>Version {installedVersion}</Text>
|
||||
<Text style={styles.rowLabel}>
|
||||
{t('settings.appUpdateSettingsRows.installedVersion', 'Version {{version}}', {
|
||||
version: installedVersion
|
||||
})}
|
||||
</Text>
|
||||
</View>
|
||||
) : null
|
||||
return (
|
||||
@@ -57,13 +114,21 @@ export function AppUpdateSettingsRows(props: {
|
||||
{versionRow && <View style={styles.separator} />}
|
||||
<Pressable
|
||||
accessibilityRole="button"
|
||||
accessibilityLabel={`Check for updates, ${checkValue}`}
|
||||
accessibilityLabel={t(
|
||||
'settings.appUpdateSettingsRows.checkUpdatesStatus',
|
||||
'Check for updates, {{status}}',
|
||||
{
|
||||
status: checkValue
|
||||
}
|
||||
)}
|
||||
accessibilityState={{ busy: props.checkStatus === 'checking' }}
|
||||
disabled={props.checkStatus === 'checking'}
|
||||
style={({ pressed }) => [styles.row, pressed && styles.rowPressed]}
|
||||
onPress={props.onCheck}
|
||||
>
|
||||
<Text style={styles.rowLabel}>Check for updates</Text>
|
||||
<Text style={styles.rowLabel}>
|
||||
{t('settings.appUpdateSettingsRows.checkUpdates', 'Check for updates')}
|
||||
</Text>
|
||||
<Text style={styles.rowValue}>{checkValue}</Text>
|
||||
</Pressable>
|
||||
</View>
|
||||
|
||||
@@ -4,6 +4,8 @@ import { useSafeAreaInsets } from 'react-native-safe-area-context'
|
||||
import { useRouter } from 'expo-router'
|
||||
import { ChevronLeft, ChevronRight, Globe } from 'lucide-react-native'
|
||||
import { PickerModal, type PickerOption } from '../components/PickerModal'
|
||||
import type { Translate } from '../i18n/mobile-i18n'
|
||||
import { useMobileTranslation } from '../i18n/use-mobile-translation'
|
||||
import {
|
||||
loadTerminalLinkOpenMode,
|
||||
saveTerminalLinkOpenMode,
|
||||
@@ -13,23 +15,32 @@ import { colors, radii, spacing, typography } from '../theme/mobile-theme'
|
||||
import { themedStyleSheet } from '../theme/themed-styles'
|
||||
import { useMobileColorScheme } from '../theme/use-mobile-color-scheme'
|
||||
|
||||
const LINK_MODE_OPTIONS: PickerOption<MobileTerminalLinkOpenMode>[] = [
|
||||
{
|
||||
value: 'orca-browser',
|
||||
label: 'Orca browser on desktop',
|
||||
subtitle: 'Open in the streamed browser from your paired desktop.'
|
||||
},
|
||||
{
|
||||
value: 'phone-browser',
|
||||
label: 'Phone browser',
|
||||
subtitle: 'Open in Safari, Chrome, or another browser on this phone.'
|
||||
}
|
||||
]
|
||||
function linkModeOptions(t: Translate): PickerOption<MobileTerminalLinkOpenMode>[] {
|
||||
return [
|
||||
{
|
||||
value: 'orca-browser',
|
||||
label: t('settings.browserSettingsScreen.desktopBrowser', 'Orca browser on desktop'),
|
||||
subtitle: t(
|
||||
'settings.browserSettingsScreen.desktopBrowserDescription',
|
||||
'Open in the streamed browser from your paired desktop.'
|
||||
)
|
||||
},
|
||||
{
|
||||
value: 'phone-browser',
|
||||
label: t('settings.browserSettingsScreen.phoneBrowser', 'Phone browser'),
|
||||
subtitle: t(
|
||||
'settings.browserSettingsScreen.phoneBrowserDescription',
|
||||
'Open in Safari, Chrome, or another browser on this phone.'
|
||||
)
|
||||
}
|
||||
]
|
||||
}
|
||||
|
||||
function linkModeLabel(mode: MobileTerminalLinkOpenMode): string {
|
||||
return (
|
||||
LINK_MODE_OPTIONS.find((option) => option.value === mode)?.label ?? LINK_MODE_OPTIONS[0]!.label
|
||||
)
|
||||
function linkModeLabel(
|
||||
mode: MobileTerminalLinkOpenMode,
|
||||
options: PickerOption<MobileTerminalLinkOpenMode>[]
|
||||
): string {
|
||||
return options.find((option) => option.value === mode)?.label ?? options[0]!.label
|
||||
}
|
||||
|
||||
export default function BrowserSettingsScreen({
|
||||
@@ -37,12 +48,14 @@ export default function BrowserSettingsScreen({
|
||||
}: {
|
||||
onBack?: () => void
|
||||
}): React.JSX.Element {
|
||||
const t = useMobileTranslation()
|
||||
useMobileColorScheme()
|
||||
const router = useRouter()
|
||||
const insets = useSafeAreaInsets()
|
||||
const [linkMode, setLinkMode] = useState<MobileTerminalLinkOpenMode>('orca-browser')
|
||||
const [pickerOpen, setPickerOpen] = useState(false)
|
||||
const [error, setError] = useState<string | null>(null)
|
||||
const [error, setError] = useState<'load' | 'save' | null>(null)
|
||||
const options = linkModeOptions(t)
|
||||
const selectionRevision = useRef(0)
|
||||
const failedSelectionRevision = useRef<number | null>(null)
|
||||
const savedMode = useRef<MobileTerminalLinkOpenMode>('orca-browser')
|
||||
@@ -65,7 +78,7 @@ export default function BrowserSettingsScreen({
|
||||
},
|
||||
() => {
|
||||
if (active) {
|
||||
setError('Could not load browser preferences. Try again.')
|
||||
setError('load')
|
||||
}
|
||||
}
|
||||
)
|
||||
@@ -87,7 +100,7 @@ export default function BrowserSettingsScreen({
|
||||
if (revision === selectionRevision.current) {
|
||||
failedSelectionRevision.current = revision
|
||||
setLinkMode(savedMode.current)
|
||||
setError('Could not save browser preferences. Try again.')
|
||||
setError('save')
|
||||
}
|
||||
}
|
||||
)
|
||||
@@ -98,36 +111,54 @@ export default function BrowserSettingsScreen({
|
||||
<View style={styles.topRow}>
|
||||
<Pressable
|
||||
accessibilityRole="button"
|
||||
accessibilityLabel="Back"
|
||||
accessibilityLabel={t('common.back', 'Back')}
|
||||
style={styles.backButton}
|
||||
onPress={onBack ?? (() => router.back())}
|
||||
>
|
||||
<ChevronLeft size={22} color={colors.textSecondary} />
|
||||
</Pressable>
|
||||
<Text style={styles.heading}>Browser</Text>
|
||||
<Text style={styles.heading}>{t('settings.browserSettingsScreen.title', 'Browser')}</Text>
|
||||
</View>
|
||||
|
||||
<ScrollView contentContainerStyle={styles.scrollContent} showsVerticalScrollIndicator={false}>
|
||||
<Text style={styles.groupHeading}>LINKS</Text>
|
||||
<Text style={styles.groupHeading}>
|
||||
{t('settings.browserSettingsScreen.linksHeading', 'LINKS')}
|
||||
</Text>
|
||||
<Text style={styles.groupDescription}>
|
||||
Choose where HTTP(S) links tapped in terminal output open.
|
||||
{t(
|
||||
'settings.browserSettingsScreen.linksDescription',
|
||||
'Choose where HTTP(S) links tapped in terminal output open.'
|
||||
)}
|
||||
</Text>
|
||||
{error && (
|
||||
<Text accessibilityRole="alert" style={styles.groupDescription}>
|
||||
{error}
|
||||
{error === 'load'
|
||||
? t(
|
||||
'settings.browserSettingsScreen.loadFailed',
|
||||
'Could not load browser preferences. Try again.'
|
||||
)
|
||||
: t(
|
||||
'settings.browserSettingsScreen.saveFailed',
|
||||
'Could not save browser preferences. Try again.'
|
||||
)}
|
||||
</Text>
|
||||
)}
|
||||
<View style={[styles.section, styles.sectionTopGap]}>
|
||||
<Pressable
|
||||
accessibilityRole="button"
|
||||
accessibilityLabel="Open terminal links"
|
||||
accessibilityLabel={t(
|
||||
'settings.browserSettingsScreen.openTerminalLinks',
|
||||
'Open terminal links'
|
||||
)}
|
||||
style={({ pressed }) => [styles.row, pressed && styles.rowPressed]}
|
||||
onPress={() => setPickerOpen(true)}
|
||||
>
|
||||
<Globe size={16} color={colors.textSecondary} />
|
||||
<View style={styles.rowContent}>
|
||||
<Text style={styles.rowLabel}>Open terminal links</Text>
|
||||
<Text style={styles.rowSublabel}>{linkModeLabel(linkMode)}</Text>
|
||||
<Text style={styles.rowLabel}>
|
||||
{t('settings.browserSettingsScreen.openTerminalLinks', 'Open terminal links')}
|
||||
</Text>
|
||||
<Text style={styles.rowSublabel}>{linkModeLabel(linkMode, options)}</Text>
|
||||
</View>
|
||||
<ChevronRight size={16} color={colors.textMuted} />
|
||||
</Pressable>
|
||||
@@ -136,8 +167,8 @@ export default function BrowserSettingsScreen({
|
||||
|
||||
<PickerModal<MobileTerminalLinkOpenMode>
|
||||
visible={pickerOpen}
|
||||
title="Open terminal links"
|
||||
options={LINK_MODE_OPTIONS}
|
||||
title={t('settings.browserSettingsScreen.openTerminalLinks', 'Open terminal links')}
|
||||
options={options}
|
||||
selected={linkMode}
|
||||
onSelect={selectLinkMode}
|
||||
onClose={() => setPickerOpen(false)}
|
||||
|
||||
@@ -9,16 +9,57 @@ import {
|
||||
Wrench
|
||||
} from 'lucide-react-native'
|
||||
import type { MobileSettingsMenuItem } from './mobile-settings-menu'
|
||||
import { translate } from '../i18n/mobile-i18n'
|
||||
|
||||
export function mobileSettingsMenuItems(push: (route: string) => void): MobileSettingsMenuItem[] {
|
||||
return [
|
||||
{ label: 'Appearance', icon: SunMoon, onPress: () => push('/appearance-settings') },
|
||||
{ label: 'Terminal', icon: Terminal, onPress: () => push('/terminal-settings') },
|
||||
{ label: 'Chat UI', icon: MessageSquare, onPress: () => push('/native-chat-settings') },
|
||||
{ label: 'Browser', icon: Globe, onPress: () => push('/browser-settings') },
|
||||
{ label: 'Voice', icon: Mic, onPress: () => push('/voice-settings') },
|
||||
{ label: 'Notifications', icon: Bell, onPress: () => push('/notifications') },
|
||||
{ label: 'Troubleshooting', icon: Wrench, onPress: () => push('/troubleshoot') },
|
||||
{ label: 'About', icon: Info, onPress: () => push('/about') }
|
||||
{
|
||||
id: 'appearance',
|
||||
label: translate('settings.menuItems.appearance', 'Appearance'),
|
||||
icon: SunMoon,
|
||||
onPress: () => push('/appearance-settings')
|
||||
},
|
||||
{
|
||||
id: 'terminal',
|
||||
label: translate('settings.menuItems.terminal', 'Terminal'),
|
||||
icon: Terminal,
|
||||
onPress: () => push('/terminal-settings')
|
||||
},
|
||||
{
|
||||
id: 'nativeChat',
|
||||
label: translate('settings.menuItems.chatUi', 'Chat UI'),
|
||||
icon: MessageSquare,
|
||||
onPress: () => push('/native-chat-settings')
|
||||
},
|
||||
{
|
||||
id: 'browser',
|
||||
label: translate('settings.menuItems.browser', 'Browser'),
|
||||
icon: Globe,
|
||||
onPress: () => push('/browser-settings')
|
||||
},
|
||||
{
|
||||
id: 'voice',
|
||||
label: translate('settings.menuItems.voice', 'Voice'),
|
||||
icon: Mic,
|
||||
onPress: () => push('/voice-settings')
|
||||
},
|
||||
{
|
||||
id: 'notifications',
|
||||
label: translate('settings.menuItems.notifications', 'Notifications'),
|
||||
icon: Bell,
|
||||
onPress: () => push('/notifications')
|
||||
},
|
||||
{
|
||||
id: 'troubleshoot',
|
||||
label: translate('settings.menuItems.troubleshooting', 'Troubleshooting'),
|
||||
icon: Wrench,
|
||||
onPress: () => push('/troubleshoot')
|
||||
},
|
||||
{
|
||||
id: 'about',
|
||||
label: translate('settings.menuItems.about', 'About'),
|
||||
icon: Info,
|
||||
onPress: () => push('/about')
|
||||
}
|
||||
]
|
||||
}
|
||||
|
||||
@@ -6,6 +6,7 @@ import { ChevronLeft, ChevronRight, type LucideIcon } from 'lucide-react-native'
|
||||
import { colors, spacing, typography } from '../theme/mobile-theme'
|
||||
import { themedStyleSheet } from '../theme/themed-styles'
|
||||
import { useMobileColorScheme } from '../theme/use-mobile-color-scheme'
|
||||
import { useMobileTranslation } from '../i18n/use-mobile-translation'
|
||||
|
||||
export function MobileSettingsFrame({
|
||||
children,
|
||||
@@ -15,6 +16,7 @@ export function MobileSettingsFrame({
|
||||
onBack?: () => void
|
||||
}) {
|
||||
useMobileColorScheme()
|
||||
const t = useMobileTranslation()
|
||||
const router = useRouter()
|
||||
const insets = useSafeAreaInsets()
|
||||
return (
|
||||
@@ -22,13 +24,13 @@ export function MobileSettingsFrame({
|
||||
<View style={styles.topRow}>
|
||||
<Pressable
|
||||
accessibilityRole="button"
|
||||
accessibilityLabel="Back"
|
||||
accessibilityLabel={t('common.back', 'Back')}
|
||||
style={styles.backButton}
|
||||
onPress={onBack ?? (() => router.back())}
|
||||
>
|
||||
<ChevronLeft size={22} color={colors.textSecondary} />
|
||||
</Pressable>
|
||||
<Text style={styles.heading}>Settings</Text>
|
||||
<Text style={styles.heading}>{t('common.settings', 'Settings')}</Text>
|
||||
</View>
|
||||
<ScrollView
|
||||
contentContainerStyle={{ paddingBottom: insets.bottom + spacing.lg }}
|
||||
@@ -41,6 +43,7 @@ export function MobileSettingsFrame({
|
||||
}
|
||||
|
||||
export type MobileSettingsMenuItem = {
|
||||
id: string
|
||||
label: string
|
||||
icon: LucideIcon
|
||||
onPress: () => void
|
||||
@@ -58,8 +61,8 @@ export function MobileSettingsSection({
|
||||
useMobileColorScheme()
|
||||
return (
|
||||
<View style={[styles.section, spaced && styles.sectionSpacer]}>
|
||||
{items.map(({ label, icon: Icon, onPress, external, disabled }, index) => (
|
||||
<View key={label}>
|
||||
{items.map(({ id, label, icon: Icon, onPress, external, disabled }, index) => (
|
||||
<View key={id}>
|
||||
{index > 0 && <View style={styles.separator} />}
|
||||
<Pressable
|
||||
accessibilityRole="button"
|
||||
|
||||
@@ -6,9 +6,11 @@ import { colors, radii, spacing, typography } from '../theme/mobile-theme'
|
||||
import { themedStyleSheet } from '../theme/themed-styles'
|
||||
import { useMobileColorScheme } from '../theme/use-mobile-color-scheme'
|
||||
import { useMobileDefaultSessionViewPreference } from '../session/use-mobile-default-session-view-preference'
|
||||
import { useMobileTranslation } from '../i18n/use-mobile-translation'
|
||||
|
||||
export default function NativeChatSettingsScreen({ onBack }: { onBack?: () => void }) {
|
||||
useMobileColorScheme()
|
||||
const t = useMobileTranslation()
|
||||
const router = useRouter()
|
||||
const insets = useSafeAreaInsets()
|
||||
|
||||
@@ -20,33 +22,47 @@ export default function NativeChatSettingsScreen({ onBack }: { onBack?: () => vo
|
||||
<View style={styles.topRow}>
|
||||
<Pressable
|
||||
accessibilityRole="button"
|
||||
accessibilityLabel="Back"
|
||||
accessibilityLabel={t('common.back', 'Back')}
|
||||
style={styles.backButton}
|
||||
onPress={onBack ?? (() => router.back())}
|
||||
>
|
||||
<ChevronLeft size={22} color={colors.textSecondary} />
|
||||
</Pressable>
|
||||
<Text style={styles.heading}>Chat UI</Text>
|
||||
<Text style={styles.heading}>
|
||||
{t('settings.nativeChatSettingsScreen.title', 'Chat UI')}
|
||||
</Text>
|
||||
</View>
|
||||
|
||||
<ScrollView
|
||||
contentContainerStyle={{ paddingBottom: insets.bottom + spacing.lg }}
|
||||
showsVerticalScrollIndicator={false}
|
||||
>
|
||||
<Text style={styles.groupHeading}>DEFAULT VIEW</Text>
|
||||
<Text style={styles.groupHeading}>
|
||||
{t('settings.nativeChatSettingsScreen.defaultViewHeading', 'DEFAULT VIEW')}
|
||||
</Text>
|
||||
<Text style={styles.groupDescription}>
|
||||
Choose how supported agent sessions (Claude, Codex, and other chat-capable agents) open on
|
||||
this device. Terminal shows the raw CLI; Chat UI shows a chat interface like the desktop
|
||||
app. You can still switch any individual session from its long-press menu.
|
||||
{t(
|
||||
'settings.nativeChatSettingsScreen.defaultViewDescription',
|
||||
'Choose how supported agent sessions (Claude, Codex, and other chat-capable agents) open on this device. Terminal shows the raw CLI; Chat UI shows a chat interface like the desktop app. You can still switch any individual session from its long-press menu.'
|
||||
)}
|
||||
</Text>
|
||||
<View style={[styles.section, styles.sectionTopGap]}>
|
||||
<View style={styles.row}>
|
||||
<View style={styles.rowContent}>
|
||||
<Text style={styles.rowLabel}>Open sessions in Chat UI</Text>
|
||||
<Text style={styles.rowSublabel}>{chatDefault ? 'On' : 'Off'}</Text>
|
||||
<Text style={styles.rowLabel}>
|
||||
{t('settings.nativeChatSettingsScreen.openInChatUi', 'Open sessions in Chat UI')}
|
||||
</Text>
|
||||
<Text style={styles.rowSublabel}>
|
||||
{chatDefault
|
||||
? t('settings.nativeChatSettingsScreen.on', 'On')
|
||||
: t('settings.nativeChatSettingsScreen.off', 'Off')}
|
||||
</Text>
|
||||
</View>
|
||||
<Switch
|
||||
accessibilityLabel="Open sessions in Chat UI"
|
||||
accessibilityLabel={t(
|
||||
'settings.nativeChatSettingsScreen.openInChatUi',
|
||||
'Open sessions in Chat UI'
|
||||
)}
|
||||
value={chatDefault}
|
||||
onValueChange={(next) => setDefaultView(next ? 'chat' : 'terminal')}
|
||||
trackColor={{ false: colors.bgRaised, true: colors.textSecondary }}
|
||||
|
||||
@@ -11,9 +11,11 @@ import { setNotificationDeliveryPreferences } from '../notifications/push-regist
|
||||
import { useRemotePushCapableHosts } from '../notifications/use-remote-push-capable-hosts'
|
||||
import { colors, spacing, typography } from '../theme/mobile-theme'
|
||||
import { useMobileColorScheme } from '../theme/use-mobile-color-scheme'
|
||||
import { useMobileTranslation } from '../i18n/use-mobile-translation'
|
||||
|
||||
export function NativeNotificationDeliverySettings({ enabled }: { enabled: boolean }) {
|
||||
useMobileColorScheme()
|
||||
const t = useMobileTranslation()
|
||||
const [delivery, setDelivery] = useState(DEFAULT_NOTIFICATION_DELIVERY)
|
||||
const [loaded, setLoaded] = useState(false)
|
||||
const [saving, setSaving] = useState(false)
|
||||
@@ -38,9 +40,14 @@ export function NativeNotificationDeliverySettings({ enabled }: { enabled: boole
|
||||
if (revision !== refreshRevision.current) {
|
||||
return
|
||||
}
|
||||
setError('Could not load delivery settings. Reopen this screen to retry.')
|
||||
setError(
|
||||
t(
|
||||
'settings.nativeNotificationDeliverySettings.loadFailed',
|
||||
'Could not load delivery settings. Reopen this screen to retry.'
|
||||
)
|
||||
)
|
||||
}
|
||||
}, [])
|
||||
}, [t])
|
||||
useFocusEffect(
|
||||
useCallback(() => {
|
||||
void refresh()
|
||||
@@ -66,7 +73,12 @@ export function NativeNotificationDeliverySettings({ enabled }: { enabled: boole
|
||||
await setNotificationDeliveryPreferences(value)
|
||||
setDelivery(value)
|
||||
} catch {
|
||||
setError('Could not save delivery settings. Try again.')
|
||||
setError(
|
||||
t(
|
||||
'settings.nativeNotificationDeliverySettings.saveFailed',
|
||||
'Could not save delivery settings. Try again.'
|
||||
)
|
||||
)
|
||||
} finally {
|
||||
saveInProgress.current = false
|
||||
setSaving(false)
|
||||
@@ -91,7 +103,10 @@ export function NativeNotificationDeliverySettings({ enabled }: { enabled: boole
|
||||
)}
|
||||
{support.resolved && !support.supported && (
|
||||
<Text style={hintStyle}>
|
||||
Pair an updated desktop to receive notifications on this phone.
|
||||
{t(
|
||||
'settings.nativeNotificationDeliverySettings.updateDesktopHint',
|
||||
'Pair an updated desktop to receive notifications on this phone.'
|
||||
)}
|
||||
</Text>
|
||||
)}
|
||||
</>
|
||||
|
||||
@@ -6,9 +6,11 @@ import { pushDeliveryTest } from '../notifications/mobile-push-delivery-test-ope
|
||||
import { colors, spacing, typography } from '../theme/mobile-theme'
|
||||
import { themedStyleSheet } from '../theme/themed-styles'
|
||||
import { useMobileColorScheme } from '../theme/use-mobile-color-scheme'
|
||||
import { useMobileTranslation } from '../i18n/use-mobile-translation'
|
||||
|
||||
export function NotificationDisplayTest({ onTroubleshoot }: { onTroubleshoot: () => void }) {
|
||||
useMobileColorScheme()
|
||||
const t = useMobileTranslation()
|
||||
const busy = useRef(false)
|
||||
const [hostIds, setHostIds] = useState<string[]>([])
|
||||
const [sending, setSending] = useState(false)
|
||||
@@ -17,8 +19,12 @@ export function NotificationDisplayTest({ onTroubleshoot }: { onTroubleshoot: ()
|
||||
useEffect(() => {
|
||||
void loadHostCatalog()
|
||||
.then((hosts) => setHostIds(hosts.map((host) => host.id)))
|
||||
.catch(() => setMessage('Could not load paired desktops.'))
|
||||
}, [])
|
||||
.catch(() =>
|
||||
setMessage(
|
||||
t('settings.notificationDisplayTest.loadFailed', 'Could not load paired desktops.')
|
||||
)
|
||||
)
|
||||
}, [t])
|
||||
const run = async () => {
|
||||
if (busy.current) {
|
||||
return
|
||||
@@ -28,13 +34,20 @@ export function NotificationDisplayTest({ onTroubleshoot }: { onTroubleshoot: ()
|
||||
setMessage(null)
|
||||
try {
|
||||
if (hostIds.length === 0) {
|
||||
throw new Error('Pair a desktop and try again.')
|
||||
throw new Error(
|
||||
t('settings.notificationDisplayTest.pairDesktop', 'Pair a desktop and try again.')
|
||||
)
|
||||
}
|
||||
const connected = clients.filter((entry) => entry.state === 'connected')
|
||||
if (connected.length === 0) {
|
||||
throw new Error('Connect a desktop and try again.')
|
||||
throw new Error(
|
||||
t('settings.notificationDisplayTest.connectDesktop', 'Connect a desktop and try again.')
|
||||
)
|
||||
}
|
||||
let unavailable = 'Update your desktop to run this test.'
|
||||
let unavailable = t(
|
||||
'settings.notificationDisplayTest.updateDesktop',
|
||||
'Update your desktop to run this test.'
|
||||
)
|
||||
for (const { client } of connected) {
|
||||
const reply = await pushDeliveryTest.request(client, null, {
|
||||
timeoutMs: 20000,
|
||||
@@ -46,26 +59,49 @@ export function NotificationDisplayTest({ onTroubleshoot }: { onTroubleshoot: ()
|
||||
if (code === 'forbidden' || code === 'method_not_found') {
|
||||
continue
|
||||
}
|
||||
throw new Error('Could not reach the desktop. Try again.')
|
||||
throw new Error(
|
||||
t(
|
||||
'settings.notificationDisplayTest.desktopUnreachable',
|
||||
'Could not reach the desktop. Try again.'
|
||||
)
|
||||
)
|
||||
}
|
||||
const result = delivered.value
|
||||
if (result?.accepted) {
|
||||
setMessage('Accepted by Orca’s push service. Check for the notification.')
|
||||
setMessage(
|
||||
t(
|
||||
'settings.notificationDisplayTest.accepted',
|
||||
'Accepted by Orca’s push service. Check for the notification.'
|
||||
)
|
||||
)
|
||||
return
|
||||
}
|
||||
if (result?.reason === 'not_registered') {
|
||||
unavailable = 'Reconnect to register this phone for notifications.'
|
||||
unavailable = t(
|
||||
'settings.notificationDisplayTest.reconnectPhone',
|
||||
'Reconnect to register this phone for notifications.'
|
||||
)
|
||||
continue
|
||||
}
|
||||
throw new Error(
|
||||
result?.reason === 'rate_limited'
|
||||
? 'Too many notifications. Try again later.'
|
||||
: 'Could not send through Orca’s push service. Try again.'
|
||||
? t(
|
||||
'settings.notificationDisplayTest.rateLimited',
|
||||
'Too many notifications. Try again later.'
|
||||
)
|
||||
: t(
|
||||
'settings.notificationDisplayTest.pushServiceFailed',
|
||||
'Could not send through Orca’s push service. Try again.'
|
||||
)
|
||||
)
|
||||
}
|
||||
throw new Error(unavailable)
|
||||
} catch (error) {
|
||||
setMessage(error instanceof Error ? error.message : 'Could not send push test.')
|
||||
setMessage(
|
||||
error instanceof Error
|
||||
? error.message
|
||||
: t('settings.notificationDisplayTest.sendFailed', 'Could not send push test.')
|
||||
)
|
||||
} finally {
|
||||
busy.current = false
|
||||
setSending(false)
|
||||
@@ -73,11 +109,25 @@ export function NotificationDisplayTest({ onTroubleshoot }: { onTroubleshoot: ()
|
||||
}
|
||||
return (
|
||||
<View style={styles.container}>
|
||||
<Text style={styles.label}>Having trouble receiving alerts?</Text>
|
||||
<Text style={styles.detail}>Send a test through Orca’s push service.</Text>
|
||||
<Text style={styles.label}>
|
||||
{t(
|
||||
'settings.notificationDisplayTest.troubleReceivingAlerts',
|
||||
'Having trouble receiving alerts?'
|
||||
)}
|
||||
</Text>
|
||||
<Text style={styles.detail}>
|
||||
{t(
|
||||
'settings.notificationDisplayTest.description',
|
||||
'Send a test through Orca’s push service.'
|
||||
)}
|
||||
</Text>
|
||||
<Pressable
|
||||
accessibilityRole="button"
|
||||
accessibilityLabel={sending ? 'Sending…' : 'Send test notification'}
|
||||
accessibilityLabel={
|
||||
sending
|
||||
? t('settings.notificationDisplayTest.sending', 'Sending…')
|
||||
: t('settings.notificationDisplayTest.sendButton', 'Send test notification')
|
||||
}
|
||||
disabled={sending}
|
||||
accessibilityState={{ disabled: sending }}
|
||||
style={({ pressed }) => [styles.button, pressed && styles.pressed]}
|
||||
@@ -85,17 +135,21 @@ export function NotificationDisplayTest({ onTroubleshoot }: { onTroubleshoot: ()
|
||||
>
|
||||
<View>
|
||||
<Text accessible={false} style={[styles.buttonText, styles.sizingLabel]}>
|
||||
Send test notification
|
||||
{t('settings.notificationDisplayTest.sendButton', 'Send test notification')}
|
||||
</Text>
|
||||
<View pointerEvents="none" style={styles.buttonLabel}>
|
||||
<Text accessible={false} style={styles.buttonText}>
|
||||
{sending ? 'Sending…' : 'Send test notification'}
|
||||
{sending
|
||||
? t('settings.notificationDisplayTest.sending', 'Sending…')
|
||||
: t('settings.notificationDisplayTest.sendButton', 'Send test notification')}
|
||||
</Text>
|
||||
</View>
|
||||
</View>
|
||||
</Pressable>
|
||||
<Pressable accessibilityRole="link" onPress={onTroubleshoot} style={styles.troubleshootLink}>
|
||||
<Text style={styles.linkText}>Troubleshooting</Text>
|
||||
<Text style={styles.linkText}>
|
||||
{t('settings.notificationDisplayTest.troubleshooting', 'Troubleshooting')}
|
||||
</Text>
|
||||
</Pressable>
|
||||
{message && (
|
||||
<Text accessibilityRole="alert" style={styles.detail}>
|
||||
|
||||
@@ -8,6 +8,7 @@ import { colors, spacing, typography } from '../theme/mobile-theme'
|
||||
import { themedStyleSheet } from '../theme/themed-styles'
|
||||
import { useMobileColorScheme } from '../theme/use-mobile-color-scheme'
|
||||
import type { NotificationPermissionState } from '../notifications/notification-permissions'
|
||||
import { useMobileTranslation } from '../i18n/use-mobile-translation'
|
||||
|
||||
const DEFAULT_PERMISSION_STATE: NotificationPermissionState = {
|
||||
granted: false,
|
||||
@@ -28,6 +29,7 @@ export default function NotificationsScreen({
|
||||
children?: (enabled: boolean) => ReactNode
|
||||
}) {
|
||||
useMobileColorScheme()
|
||||
const t = useMobileTranslation()
|
||||
const insets = useSafeAreaInsets()
|
||||
const [saving, setSaving] = useState(false)
|
||||
const savingRef = useRef(false)
|
||||
@@ -53,10 +55,15 @@ export default function NotificationsScreen({
|
||||
}
|
||||
} catch {
|
||||
if (revision === requestRevision.current) {
|
||||
setError('Could not load notification settings. Try again.')
|
||||
setError(
|
||||
t(
|
||||
'settings.notificationSettingsScreen.loadFailed',
|
||||
'Could not load notification settings. Try again.'
|
||||
)
|
||||
)
|
||||
}
|
||||
}
|
||||
}, [operations])
|
||||
}, [operations, t])
|
||||
|
||||
useFocusEffect(
|
||||
useCallback(() => {
|
||||
@@ -90,7 +97,12 @@ export default function NotificationsScreen({
|
||||
const saved = await operations.preference(value && permission.granted)
|
||||
setPushEnabled(saved.enabled)
|
||||
} catch {
|
||||
setError('Could not save notification settings. Try again.')
|
||||
setError(
|
||||
t(
|
||||
'settings.notificationSettingsScreen.saveFailed',
|
||||
'Could not save notification settings. Try again.'
|
||||
)
|
||||
)
|
||||
} finally {
|
||||
savingRef.current = false
|
||||
setSaving(false)
|
||||
@@ -100,9 +112,15 @@ export default function NotificationsScreen({
|
||||
const switchEnabled = pushEnabled && permissionState.granted
|
||||
const notificationsBlocked = permissionState.status === 'denied'
|
||||
const hint = notificationsBlocked
|
||||
? 'Notifications are disabled in system settings.'
|
||||
? t(
|
||||
'settings.notificationSettingsScreen.notificationsBlocked',
|
||||
'Notifications are disabled in system settings.'
|
||||
)
|
||||
: (description ??
|
||||
'Get notified on this device when an agent needs your input or finishes a task.')
|
||||
t(
|
||||
'settings.notificationSettingsScreen.description',
|
||||
'Get notified on this device when an agent needs your input or finishes a task.'
|
||||
))
|
||||
|
||||
return (
|
||||
<ScrollView
|
||||
@@ -115,13 +133,15 @@ export default function NotificationsScreen({
|
||||
<View style={styles.topRow}>
|
||||
<Pressable
|
||||
accessibilityRole="button"
|
||||
accessibilityLabel="Back"
|
||||
accessibilityLabel={t('common.back', 'Back')}
|
||||
style={styles.backButton}
|
||||
onPress={onBack}
|
||||
>
|
||||
<ChevronLeft size={22} color={colors.textSecondary} />
|
||||
</Pressable>
|
||||
<Text style={styles.heading}>Notifications</Text>
|
||||
<Text style={styles.heading}>
|
||||
{t('settings.notificationSettingsScreen.title', 'Notifications')}
|
||||
</Text>
|
||||
</View>
|
||||
|
||||
{error && (
|
||||
@@ -131,11 +151,16 @@ export default function NotificationsScreen({
|
||||
)}
|
||||
<View style={styles.section}>
|
||||
<View style={styles.row}>
|
||||
<Text style={styles.rowLabel}>Enable notifications</Text>
|
||||
<Text style={styles.rowLabel}>
|
||||
{t('settings.notificationSettingsScreen.enableNotifications', 'Enable notifications')}
|
||||
</Text>
|
||||
<Switch
|
||||
value={switchEnabled}
|
||||
testID="notification-enabled"
|
||||
accessibilityLabel="Enable notifications"
|
||||
accessibilityLabel={t(
|
||||
'settings.notificationSettingsScreen.enableNotifications',
|
||||
'Enable notifications'
|
||||
)}
|
||||
disabled={notificationsBlocked || saving}
|
||||
onValueChange={(v) => void togglePush(v)}
|
||||
trackColor={{ false: colors.bgRaised, true: colors.textSecondary }}
|
||||
@@ -153,10 +178,19 @@ export default function NotificationsScreen({
|
||||
onPress={() =>
|
||||
void operations
|
||||
.openSettings()
|
||||
.catch(() => setError('Could not open system settings. Try again.'))
|
||||
.catch(() =>
|
||||
setError(
|
||||
t(
|
||||
'settings.notificationSettingsScreen.openSettingsFailed',
|
||||
'Could not open system settings. Try again.'
|
||||
)
|
||||
)
|
||||
)
|
||||
}
|
||||
>
|
||||
<Text style={styles.settingsButtonText}>Open Settings</Text>
|
||||
<Text style={styles.settingsButtonText}>
|
||||
{t('settings.notificationSettingsScreen.openSettings', 'Open Settings')}
|
||||
</Text>
|
||||
</Pressable>
|
||||
)}
|
||||
</View>
|
||||
|
||||
@@ -10,9 +10,11 @@ import {
|
||||
subscribePendingHostCredentialCleanup
|
||||
} from '../transport/host-credential-cleanup'
|
||||
import { retryPendingHostCredentialCleanup } from '../transport/host-store'
|
||||
import { useMobileTranslation } from '../i18n/use-mobile-translation'
|
||||
|
||||
export function PendingCredentialCleanupCard() {
|
||||
useMobileColorScheme()
|
||||
const t = useMobileTranslation()
|
||||
const [pendingCredentialIds, setPendingCredentialIds] = useState<string[]>([])
|
||||
const [credentialStorageUnreadable, setCredentialStorageUnreadable] = useState(false)
|
||||
const [retryingCredentialCleanup, setRetryingCredentialCleanup] = useState(false)
|
||||
@@ -64,9 +66,9 @@ export function PendingCredentialCleanupCard() {
|
||||
}
|
||||
}, [retryingCredentialCleanup])
|
||||
|
||||
const pendingCredentialCount = pendingCredentialIds.length
|
||||
const count = pendingCredentialIds.length
|
||||
// Unreadable durable queues may hide orphaned tokens; keep cleanup and Retry visible.
|
||||
if (pendingCredentialCount === 0 && !credentialStorageUnreadable) {
|
||||
if (count === 0 && !credentialStorageUnreadable) {
|
||||
return null
|
||||
}
|
||||
|
||||
@@ -75,18 +77,39 @@ export function PendingCredentialCleanupCard() {
|
||||
<View style={styles.credentialCleanupRow}>
|
||||
<KeyRound size={16} color={colors.statusAmber} />
|
||||
<View style={styles.credentialCleanupCopy}>
|
||||
<Text style={styles.credentialCleanupTitle}>Pairing credential cleanup</Text>
|
||||
<Text style={styles.credentialCleanupTitle}>
|
||||
{t('settings.pendingCredentialCleanupCard.title', 'Pairing credential cleanup')}
|
||||
</Text>
|
||||
<Text accessibilityLiveRegion="polite" style={styles.rowHint}>
|
||||
{credentialRetryFailed
|
||||
? "Cleanup still couldn't be confirmed. Try again later."
|
||||
: pendingCredentialCount > 0
|
||||
? `Couldn't confirm cleanup for ${pendingCredentialCount} credential${pendingCredentialCount === 1 ? '' : 's'} on this device.`
|
||||
: "Couldn't check cleanup status on this device. Retry to be safe."}
|
||||
? t(
|
||||
'settings.pendingCredentialCleanupCard.retryFailed',
|
||||
"Cleanup still couldn't be confirmed. Try again later."
|
||||
)
|
||||
: count > 0
|
||||
? count === 1
|
||||
? t(
|
||||
'settings.pendingCredentialCleanupCard.cleanupUnconfirmed_one',
|
||||
"Couldn't confirm cleanup for {{count}} credential on this device.",
|
||||
{ count }
|
||||
)
|
||||
: t(
|
||||
'settings.pendingCredentialCleanupCard.cleanupUnconfirmed_other',
|
||||
"Couldn't confirm cleanup for {{count}} credentials on this device.",
|
||||
{ count }
|
||||
)
|
||||
: t(
|
||||
'settings.pendingCredentialCleanupCard.statusCheckFailed',
|
||||
"Couldn't check cleanup status on this device. Retry to be safe."
|
||||
)}
|
||||
</Text>
|
||||
</View>
|
||||
<Pressable
|
||||
accessibilityRole="button"
|
||||
accessibilityLabel="Retry clearing pairing credentials"
|
||||
accessibilityLabel={t(
|
||||
'settings.pendingCredentialCleanupCard.retryCleanup',
|
||||
'Retry clearing pairing credentials'
|
||||
)}
|
||||
accessibilityState={{
|
||||
busy: retryingCredentialCleanup,
|
||||
disabled: retryingCredentialCleanup
|
||||
@@ -102,7 +125,7 @@ export function PendingCredentialCleanupCard() {
|
||||
{retryingCredentialCleanup ? (
|
||||
<ActivityIndicator size="small" color={colors.textSecondary} />
|
||||
) : (
|
||||
<Text style={styles.retryButtonText}>Retry</Text>
|
||||
<Text style={styles.retryButtonText}>{t('common.retry', 'Retry')}</Text>
|
||||
)}
|
||||
</Pressable>
|
||||
</View>
|
||||
|
||||
@@ -2,6 +2,7 @@ import type { ReactNode } from 'react'
|
||||
import { Shield, LifeBuoy } from 'lucide-react-native'
|
||||
import { MobileSettingsFrame, MobileSettingsSection } from './mobile-settings-menu'
|
||||
import { mobileSettingsMenuItems } from './mobile-settings-menu-items'
|
||||
import { useMobileTranslation } from '../i18n/use-mobile-translation'
|
||||
|
||||
export default function SettingsMenuScreen({
|
||||
push,
|
||||
@@ -14,6 +15,7 @@ export default function SettingsMenuScreen({
|
||||
openExternal: (url: string) => Promise<unknown>
|
||||
children?: ReactNode
|
||||
}) {
|
||||
const t = useMobileTranslation()
|
||||
return (
|
||||
<MobileSettingsFrame onBack={onBack}>
|
||||
<MobileSettingsSection items={mobileSettingsMenuItems(push)} />
|
||||
@@ -24,13 +26,15 @@ export default function SettingsMenuScreen({
|
||||
spaced
|
||||
items={[
|
||||
{
|
||||
label: 'Privacy Policy',
|
||||
id: 'privacyPolicy',
|
||||
label: t('settings.menuScreen.privacyPolicy', 'Privacy Policy'),
|
||||
icon: Shield,
|
||||
external: true,
|
||||
onPress: () => void openExternal('https://www.onorca.dev/privacy')
|
||||
},
|
||||
{
|
||||
label: 'Support',
|
||||
id: 'support',
|
||||
label: t('settings.menuScreen.support', 'Support'),
|
||||
icon: LifeBuoy,
|
||||
external: true,
|
||||
onPress: () => void openExternal('https://github.com/stablyai/orca/issues')
|
||||
|
||||
@@ -79,7 +79,9 @@ export function UiLanguageSettingsSection() {
|
||||
}}
|
||||
>
|
||||
{/* Development-only layout check; never shipped. */}
|
||||
<Text style={styles.rowLabel}>Pseudo (en-XA)</Text>
|
||||
<Text style={styles.rowLabel}>
|
||||
{t('settings.uiLanguageSettingsSection.pseudoLanguage', 'Pseudo (en-XA)')}
|
||||
</Text>
|
||||
{pseudo && <Check size={18} color={colors.textPrimary} />}
|
||||
</Pressable>
|
||||
</View>
|
||||
|
||||
@@ -6,6 +6,8 @@ import { voiceSettingsStyles as styles } from './voice-settings-styles'
|
||||
import { ChevronLeft, ChevronRight } from 'lucide-react-native'
|
||||
import { colors, spacing } from '../theme/mobile-theme'
|
||||
import { useMobileColorScheme } from '../theme/use-mobile-color-scheme'
|
||||
import type { Translate } from '../i18n/mobile-i18n'
|
||||
import { useMobileTranslation } from '../i18n/use-mobile-translation'
|
||||
import { BottomDrawer } from '../components/BottomDrawer'
|
||||
import { MobileSegmentedControl } from '../components/MobileSegmentedControl'
|
||||
import { VoiceModelList } from '../components/VoiceModelList'
|
||||
@@ -18,12 +20,42 @@ import {
|
||||
|
||||
const POLL_INTERVAL_MS = 1500
|
||||
|
||||
const DICTATION_MODES = [
|
||||
{ value: 'toggle', label: 'Toggle' },
|
||||
{ value: 'hold', label: 'Hold' }
|
||||
] as const
|
||||
function dictationModes(t: Translate) {
|
||||
return [
|
||||
{ value: 'toggle', label: t('settings.voiceSettingsScreen.toggleMode', 'Toggle') },
|
||||
{ value: 'hold', label: t('settings.voiceSettingsScreen.holdMode', 'Hold') }
|
||||
] as const
|
||||
}
|
||||
|
||||
type ModelBusyAction = { modelId: string; type: 'download' | 'select' | 'delete' }
|
||||
type VoiceSettingsError =
|
||||
| { message: string }
|
||||
| 'loadFailed'
|
||||
| 'updateFailed'
|
||||
| 'selectModelFailed'
|
||||
| 'downloadFailed'
|
||||
| 'deleteFailed'
|
||||
|
||||
function voiceSettingsErrorMessage(t: Translate, error: VoiceSettingsError | null): string | null {
|
||||
if (error === null) {
|
||||
return null
|
||||
}
|
||||
if (typeof error === 'object') {
|
||||
return error.message
|
||||
}
|
||||
switch (error) {
|
||||
case 'loadFailed':
|
||||
return t('settings.voiceSettingsScreen.loadFailed', 'Failed to load voice settings')
|
||||
case 'updateFailed':
|
||||
return t('settings.voiceSettingsScreen.updateFailed', 'Could not update')
|
||||
case 'selectModelFailed':
|
||||
return t('settings.voiceSettingsScreen.selectModelFailed', 'Could not select model')
|
||||
case 'downloadFailed':
|
||||
return t('settings.voiceSettingsScreen.downloadFailed', 'Download failed')
|
||||
case 'deleteFailed':
|
||||
return t('settings.voiceSettingsScreen.deleteFailed', 'Delete failed')
|
||||
}
|
||||
}
|
||||
|
||||
export default function VoiceSettingsScreen({
|
||||
operations,
|
||||
@@ -35,12 +67,13 @@ export default function VoiceSettingsScreen({
|
||||
onBack: () => void
|
||||
}): React.JSX.Element {
|
||||
useMobileColorScheme()
|
||||
const t = useMobileTranslation()
|
||||
const insets = useSafeAreaInsets()
|
||||
const [setup, setSetup] = useState<MobileSpeechSetup | null>(null)
|
||||
const [loading, setLoading] = useState(true)
|
||||
// A read clears only its own failure, so a background re-read cannot hide why a change failed.
|
||||
const [loadError, setLoadError] = useState<string | null>(null)
|
||||
const [error, setError] = useState<string | null>(null)
|
||||
const [loadError, setLoadError] = useState<VoiceSettingsError | null>(null)
|
||||
const [error, setError] = useState<VoiceSettingsError | null>(null)
|
||||
const [busyAction, setBusyAction] = useState<ModelBusyAction | null>(null)
|
||||
const requestEpoch = useRef(0)
|
||||
const pendingChanges = useRef(0)
|
||||
@@ -67,7 +100,7 @@ export default function VoiceSettingsScreen({
|
||||
return next.models.some(isModelInFlight)
|
||||
} catch (err) {
|
||||
if (epoch === requestEpoch.current) {
|
||||
setLoadError(err instanceof Error ? err.message : 'Failed to load voice settings')
|
||||
setLoadError(err instanceof Error ? { message: err.message } : 'loadFailed')
|
||||
}
|
||||
return undefined
|
||||
} finally {
|
||||
@@ -134,7 +167,7 @@ export default function VoiceSettingsScreen({
|
||||
if (result.ok) {
|
||||
setSetup(result.reply)
|
||||
} else {
|
||||
setError(result.error instanceof Error ? result.error.message : 'Could not update')
|
||||
setError(result.error instanceof Error ? { message: result.error.message } : 'updateFailed')
|
||||
}
|
||||
},
|
||||
[operations, sendChange]
|
||||
@@ -151,7 +184,9 @@ export default function VoiceSettingsScreen({
|
||||
operations.configure({ enabled: true, modelId: model.id })
|
||||
)
|
||||
if (!result.ok) {
|
||||
setError(result.error instanceof Error ? result.error.message : 'Could not select model')
|
||||
setError(
|
||||
result.error instanceof Error ? { message: result.error.message } : 'selectModelFailed'
|
||||
)
|
||||
} else if (result.latest) {
|
||||
setSetup(result.reply)
|
||||
setModelDrawerOpen(false)
|
||||
@@ -173,7 +208,7 @@ export default function VoiceSettingsScreen({
|
||||
await operations.download(model.id)
|
||||
await refreshSetup()
|
||||
} catch (err) {
|
||||
setError(err instanceof Error ? err.message : 'Download failed')
|
||||
setError(err instanceof Error ? { message: err.message } : 'downloadFailed')
|
||||
} finally {
|
||||
setBusyAction(null)
|
||||
}
|
||||
@@ -191,7 +226,7 @@ export default function VoiceSettingsScreen({
|
||||
setError(null)
|
||||
const result = await sendChange(() => operations.delete(model.id))
|
||||
if (!result.ok) {
|
||||
setError(result.error instanceof Error ? result.error.message : 'Delete failed')
|
||||
setError(result.error instanceof Error ? { message: result.error.message } : 'deleteFailed')
|
||||
} else if (result.latest) {
|
||||
setSetup(result.reply)
|
||||
if (deletedSelectedModel) {
|
||||
@@ -205,25 +240,33 @@ export default function VoiceSettingsScreen({
|
||||
|
||||
const enabled = setup?.enabled ?? false
|
||||
const selectedModel = setup?.models.find((m) => m.id === setup.selectedModelId)
|
||||
const selectedModelLabel = selectedModel?.label ?? 'None selected'
|
||||
const selectedModelLabel =
|
||||
selectedModel?.label ?? t('settings.voiceSettingsScreen.noneSelected', 'None selected')
|
||||
const loadErrorMessage = voiceSettingsErrorMessage(t, loadError)
|
||||
const errorMessage = voiceSettingsErrorMessage(t, error)
|
||||
|
||||
return (
|
||||
<View style={[styles.container, { paddingTop: insets.top + spacing.sm }]}>
|
||||
<View style={styles.topRow}>
|
||||
<Pressable
|
||||
accessibilityRole="button"
|
||||
accessibilityLabel="Back"
|
||||
accessibilityLabel={t('common.back', 'Back')}
|
||||
style={styles.backButton}
|
||||
onPress={onBack}
|
||||
>
|
||||
<ChevronLeft size={22} color={colors.textSecondary} />
|
||||
</Pressable>
|
||||
<Text style={styles.heading}>Voice</Text>
|
||||
<Text style={styles.heading}>{t('settings.voiceSettingsScreen.title', 'Voice')}</Text>
|
||||
</View>
|
||||
|
||||
{!operations ? (
|
||||
<View style={[styles.section, styles.sectionTopGap]}>
|
||||
<Text style={styles.emptyText}>Connect to a desktop to manage voice settings.</Text>
|
||||
<Text style={styles.emptyText}>
|
||||
{t(
|
||||
'settings.voiceSettingsScreen.connectDesktop',
|
||||
'Connect to a desktop to manage voice settings.'
|
||||
)}
|
||||
</Text>
|
||||
</View>
|
||||
) : loading && setup === null ? (
|
||||
<View style={styles.loading}>
|
||||
@@ -231,25 +274,38 @@ export default function VoiceSettingsScreen({
|
||||
</View>
|
||||
) : setup === null ? (
|
||||
<View style={[styles.section, styles.sectionTopGap]}>
|
||||
<Text style={styles.errorText}>{loadError ?? 'Failed to load voice settings.'}</Text>
|
||||
<Text style={styles.errorText}>
|
||||
{loadErrorMessage ??
|
||||
t('settings.voiceSettingsScreen.loadFailedMessage', 'Failed to load voice settings.')}
|
||||
</Text>
|
||||
</View>
|
||||
) : (
|
||||
<ScrollView
|
||||
contentContainerStyle={styles.scrollContent}
|
||||
showsVerticalScrollIndicator={false}
|
||||
>
|
||||
<Text style={styles.groupHeading}>DICTATION</Text>
|
||||
<Text style={styles.groupHeading}>
|
||||
{t('settings.voiceSettingsScreen.dictationHeading', 'DICTATION')}
|
||||
</Text>
|
||||
<View style={[styles.section, styles.sectionTopGap]}>
|
||||
<View style={styles.row}>
|
||||
<View style={styles.rowContent}>
|
||||
<Text style={styles.rowLabel}>Enable Voice Dictation</Text>
|
||||
<Text style={styles.rowLabel}>
|
||||
{t('settings.voiceSettingsScreen.enableDictation', 'Enable Voice Dictation')}
|
||||
</Text>
|
||||
<Text style={styles.rowSublabel}>
|
||||
Dictate text into any focused pane on your desktop.
|
||||
{t(
|
||||
'settings.voiceSettingsScreen.dictationDescription',
|
||||
'Dictate text into any focused pane on your desktop.'
|
||||
)}
|
||||
</Text>
|
||||
</View>
|
||||
<Switch
|
||||
testID="voice-enabled"
|
||||
accessibilityLabel="Enable Voice Dictation"
|
||||
accessibilityLabel={t(
|
||||
'settings.voiceSettingsScreen.enableDictation',
|
||||
'Enable Voice Dictation'
|
||||
)}
|
||||
value={enabled}
|
||||
onValueChange={(enabled) => void configure({ enabled })}
|
||||
trackColor={{ false: colors.bgRaised, true: colors.textSecondary }}
|
||||
@@ -264,13 +320,18 @@ export default function VoiceSettingsScreen({
|
||||
pointerEvents={enabled ? 'auto' : 'none'}
|
||||
>
|
||||
<View style={styles.rowContent}>
|
||||
<Text style={styles.rowLabel}>Dictation Mode</Text>
|
||||
<Text style={styles.rowLabel}>
|
||||
{t('settings.voiceSettingsScreen.dictationMode', 'Dictation Mode')}
|
||||
</Text>
|
||||
<Text style={styles.rowSublabel}>
|
||||
Toggle: press once to start, again to stop. Hold: dictate while held.
|
||||
{t(
|
||||
'settings.voiceSettingsScreen.dictationModeDescription',
|
||||
'Toggle: press once to start, again to stop. Hold: dictate while held.'
|
||||
)}
|
||||
</Text>
|
||||
</View>
|
||||
<MobileSegmentedControl
|
||||
segments={DICTATION_MODES}
|
||||
segments={dictationModes(t)}
|
||||
selected={setup.dictationMode}
|
||||
onSelect={(dictationMode) => void configure({ dictationMode })}
|
||||
testIDPrefix="voice-mode-"
|
||||
@@ -278,7 +339,9 @@ export default function VoiceSettingsScreen({
|
||||
</View>
|
||||
</View>
|
||||
|
||||
<Text style={[styles.groupHeading, styles.inputGroupGap]}>SPEECH MODEL</Text>
|
||||
<Text style={[styles.groupHeading, styles.inputGroupGap]}>
|
||||
{t('settings.voiceSettingsScreen.speechModelHeading', 'SPEECH MODEL')}
|
||||
</Text>
|
||||
<View style={[styles.section, styles.sectionTopGap]}>
|
||||
<Pressable
|
||||
style={({ pressed }) => [
|
||||
@@ -291,7 +354,9 @@ export default function VoiceSettingsScreen({
|
||||
onPress={() => setModelDrawerOpen(true)}
|
||||
>
|
||||
<View style={styles.rowContent}>
|
||||
<Text style={styles.rowLabel}>Speech Model</Text>
|
||||
<Text style={styles.rowLabel}>
|
||||
{t('settings.voiceSettingsScreen.speechModel', 'Speech Model')}
|
||||
</Text>
|
||||
<Text style={styles.rowSublabel} numberOfLines={1}>
|
||||
{selectedModelLabel}
|
||||
</Text>
|
||||
@@ -300,12 +365,16 @@ export default function VoiceSettingsScreen({
|
||||
</Pressable>
|
||||
</View>
|
||||
|
||||
{(error ?? loadError) ? <Text style={styles.error}>{error ?? loadError}</Text> : null}
|
||||
{(errorMessage ?? loadErrorMessage) ? (
|
||||
<Text style={styles.error}>{errorMessage ?? loadErrorMessage}</Text>
|
||||
) : null}
|
||||
</ScrollView>
|
||||
)}
|
||||
|
||||
<BottomDrawer visible={modelDrawerOpen} onClose={() => setModelDrawerOpen(false)}>
|
||||
<Text style={styles.drawerTitle}>Speech Model</Text>
|
||||
<Text style={styles.drawerTitle}>
|
||||
{t('settings.voiceSettingsScreen.speechModel', 'Speech Model')}
|
||||
</Text>
|
||||
{setup ? (
|
||||
<VoiceModelList
|
||||
setup={setup}
|
||||
|
||||
@@ -1,8 +1,10 @@
|
||||
import type { ProviderViewProjectionModel } from './use-mobile-tasks-provider-view-projection'
|
||||
import { useMobileTranslation } from '../i18n/use-mobile-translation'
|
||||
import { classifyConnection } from './mobile-tasks-dependencies'
|
||||
import { PROVIDER_EMPTY_LABELS } from './mobile-task-view-options'
|
||||
|
||||
export function useMobileTasksConnectionPresentation(model: ProviderViewProjectionModel) {
|
||||
useMobileTranslation()
|
||||
const {
|
||||
connState,
|
||||
githubMode,
|
||||
|
||||
@@ -101,7 +101,7 @@ describe('classifyConnection Tailscale hint', () => {
|
||||
|
||||
for (const state of ['connecting', 'handshaking', 'reconnecting', 'disconnected'] as const) {
|
||||
const verdict = classifyConnection({ ...incident, state })
|
||||
expect(verdict).toEqual({ kind: 'normal', label: 'Connecting via Relay…' })
|
||||
expect(verdict).toEqual({ kind: 'normal', label: 'Connecting via Relay…', dialing: true })
|
||||
expect(verdictDisplayLabel(verdict)).not.toContain('Tailscale')
|
||||
}
|
||||
})
|
||||
@@ -163,12 +163,12 @@ describe('classifyConnection while dialing (issue #10119)', () => {
|
||||
|
||||
it('still shows Connecting… before any failures', () => {
|
||||
const verdict = classifyConnection({ ...base, state: 'connecting', reconnectAttempts: 0 })
|
||||
expect(verdict).toEqual({ kind: 'normal', label: 'Connecting…' })
|
||||
expect(verdict).toEqual({ kind: 'normal', label: 'Connecting…', dialing: true })
|
||||
})
|
||||
|
||||
it('still shows Connecting… below the warning gate', () => {
|
||||
const verdict = classifyConnection({ ...base, state: 'handshaking', reconnectAttempts: 2 })
|
||||
expect(verdict).toEqual({ kind: 'normal', label: 'Connecting…' })
|
||||
expect(verdict).toEqual({ kind: 'normal', label: 'Connecting…', dialing: true })
|
||||
})
|
||||
})
|
||||
|
||||
|
||||
@@ -1,4 +1,5 @@
|
||||
import { isTailscaleEndpoint } from '../../../src/shared/remote-runtime/remote-runtime-tailscale-hint'
|
||||
import { translate } from '../i18n/mobile-i18n'
|
||||
import type { RelayHostReachability } from './relay-host-reachability'
|
||||
import type { MobileConnectionPath } from './stable-logical-rpc-client'
|
||||
import type { ConnectionState } from './types'
|
||||
@@ -9,40 +10,56 @@ const WARNING_ATTEMPTS = 3
|
||||
const UNREACHABLE_ATTEMPTS = 12
|
||||
const STALE_SINCE_LAST_CONNECT_MS = 60_000
|
||||
|
||||
// Repeated Tailscale endpoint failures can require a phone tunnel reset; show that remedy rather than assuming the host moved.
|
||||
const TAILSCALE_HINT = 'check Tailscale'
|
||||
|
||||
// Relay verdicts describe the desktop directly; Tailscale advice would misdiagnose offline/signed-out hosts.
|
||||
const RELAY_HOST_COPY: Record<
|
||||
function relayHostCopy(): Record<
|
||||
Exclude<RelayHostReachability, 'connecting'>,
|
||||
{ kind: 'warning' | 'unreachable'; label: (host: string) => string; detail: string }
|
||||
> = {
|
||||
'signed-out': {
|
||||
kind: 'unreachable',
|
||||
label: (host) => `Sign-in required on ${host}`,
|
||||
detail: 'Sign in to Orca on your desktop to reconnect'
|
||||
},
|
||||
'host-offline': {
|
||||
kind: 'unreachable',
|
||||
label: (host) => `${host} is offline`,
|
||||
detail: "Check it's awake, Orca is running, and you're signed in"
|
||||
},
|
||||
// Re-pairing repairs refused credentials even when direct rotation is unavailable.
|
||||
'credential-refused': {
|
||||
kind: 'unreachable',
|
||||
label: (host) => `Relay access expired for ${host}`,
|
||||
detail: 'Re-pair with your desktop'
|
||||
},
|
||||
// Cell reachability failure says nothing about desktop health, so keep it amber.
|
||||
unreachable: {
|
||||
kind: 'warning',
|
||||
label: () => "Can't reach Relay",
|
||||
detail: 'Check your connection'
|
||||
> {
|
||||
return {
|
||||
'signed-out': {
|
||||
kind: 'unreachable',
|
||||
label: (host) =>
|
||||
translate('connection.connectionHealth.signInRequired', 'Sign-in required on {{host}}', {
|
||||
host
|
||||
}),
|
||||
detail: translate(
|
||||
'connection.connectionHealth.signInDetail',
|
||||
'Sign in to Orca on your desktop to reconnect'
|
||||
)
|
||||
},
|
||||
'host-offline': {
|
||||
kind: 'unreachable',
|
||||
label: (host) =>
|
||||
translate('connection.connectionHealth.hostOffline', '{{host}} is offline', { host }),
|
||||
detail: translate(
|
||||
'connection.connectionHealth.hostOfflineDetail',
|
||||
"Check it's awake, Orca is running, and you're signed in"
|
||||
)
|
||||
},
|
||||
// Re-pairing repairs refused credentials even when direct rotation is unavailable.
|
||||
'credential-refused': {
|
||||
kind: 'unreachable',
|
||||
label: (host) =>
|
||||
translate(
|
||||
'connection.connectionHealth.relayAccessExpired',
|
||||
'Relay access expired for {{host}}',
|
||||
{
|
||||
host
|
||||
}
|
||||
),
|
||||
detail: translate('connection.connectionHealth.rePairDetail', 'Re-pair with your desktop')
|
||||
},
|
||||
// Cell reachability failure says nothing about desktop health, so keep it amber.
|
||||
unreachable: {
|
||||
kind: 'warning',
|
||||
label: () => translate('connection.connectionHealth.relayUnreachable', "Can't reach Relay"),
|
||||
detail: translate('connection.connectionHealth.checkConnection', 'Check your connection')
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
export type ConnectionVerdict =
|
||||
| { kind: 'normal'; label: string }
|
||||
| { kind: 'normal'; label: string; dialing?: boolean }
|
||||
| { kind: 'warning'; label: string; hint?: string; detail?: string }
|
||||
| {
|
||||
kind: 'unreachable'
|
||||
@@ -72,23 +89,35 @@ export function classifyConnection(args: {
|
||||
}): ConnectionVerdict {
|
||||
const { state, reconnectAttempts, lastConnectedAt } = args
|
||||
const now = args.nowMs ?? Date.now()
|
||||
const hint = isTailscaleEndpoint(args.endpoint) ? TAILSCALE_HINT : undefined
|
||||
const host = args.hostName?.trim() || 'Host'
|
||||
const hint = isTailscaleEndpoint(args.endpoint)
|
||||
? translate('connection.connectionHealth.tailscaleHint', 'check Tailscale')
|
||||
: undefined
|
||||
const host =
|
||||
args.hostName?.trim() || translate('connection.connectionHealth.hostFallback', 'Host')
|
||||
const staleReason = lastConnectedAt == null ? 'never-connected' : 'stale'
|
||||
|
||||
// An unrecognized pairing needs re-pairing, not more retries.
|
||||
if (state === 'auth-failed' || (args.pairingRejected && state !== 'connected')) {
|
||||
return { kind: 'auth-failed', label: 'Pairing invalid — re-pair with your desktop' }
|
||||
return {
|
||||
kind: 'auth-failed',
|
||||
label: translate(
|
||||
'connection.connectionHealth.pairingInvalid',
|
||||
'Pairing invalid — re-pair with your desktop'
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
if (state === 'connected') {
|
||||
return { kind: 'normal', label: 'Connected' }
|
||||
return {
|
||||
kind: 'normal',
|
||||
label: translate('connection.connectionHealth.connected', 'Connected')
|
||||
}
|
||||
}
|
||||
|
||||
// Revoked pairing outranks sign-in advice; explicit relay verdicts must survive a cleared pending path.
|
||||
const reachability = args.relayHostReachability ?? 'connecting'
|
||||
if (reachability !== 'connecting') {
|
||||
const copy = RELAY_HOST_COPY[reachability]
|
||||
const copy = relayHostCopy()[reachability]
|
||||
return copy.kind === 'warning'
|
||||
? { kind: 'warning', label: copy.label(host), detail: copy.detail }
|
||||
: { kind: 'unreachable', label: copy.label(host), reason: staleReason, detail: copy.detail }
|
||||
@@ -98,17 +127,38 @@ export function classifyConnection(args: {
|
||||
if (args.pendingPath === 'relay' && (state !== 'disconnected' || reconnectAttempts > 0)) {
|
||||
if (reconnectAttempts >= UNREACHABLE_ATTEMPTS) {
|
||||
if (lastConnectedAt == null) {
|
||||
return { kind: 'unreachable', label: "Can't connect via Relay", reason: 'never-connected' }
|
||||
return {
|
||||
kind: 'unreachable',
|
||||
label: translate(
|
||||
'connection.connectionHealth.relayConnectFailed',
|
||||
"Can't connect via Relay"
|
||||
),
|
||||
reason: 'never-connected'
|
||||
}
|
||||
}
|
||||
if (now - lastConnectedAt >= STALE_SINCE_LAST_CONNECT_MS) {
|
||||
return { kind: 'unreachable', label: "Can't connect via Relay", reason: 'stale' }
|
||||
return {
|
||||
kind: 'unreachable',
|
||||
label: translate(
|
||||
'connection.connectionHealth.relayConnectFailed',
|
||||
"Can't connect via Relay"
|
||||
),
|
||||
reason: 'stale'
|
||||
}
|
||||
}
|
||||
}
|
||||
return { kind: 'normal', label: 'Connecting via Relay…' }
|
||||
return {
|
||||
kind: 'normal',
|
||||
label: translate('connection.connectionHealth.connectingViaRelay', 'Connecting via Relay…'),
|
||||
dialing: true
|
||||
}
|
||||
}
|
||||
|
||||
if (state === 'disconnected') {
|
||||
return { kind: 'normal', label: 'Disconnected' }
|
||||
return {
|
||||
kind: 'normal',
|
||||
label: translate('connection.connectionHealth.disconnected', 'Disconnected')
|
||||
}
|
||||
}
|
||||
|
||||
// Apply escalation through every redial state so failures cannot revert to Connecting during each attempt.
|
||||
@@ -116,7 +166,7 @@ export function classifyConnection(args: {
|
||||
if (lastConnectedAt == null) {
|
||||
return {
|
||||
kind: 'unreachable',
|
||||
label: "Can't reach desktop",
|
||||
label: translate('connection.connectionHealth.desktopUnreachable', "Can't reach desktop"),
|
||||
reason: 'never-connected',
|
||||
hint
|
||||
}
|
||||
@@ -124,7 +174,7 @@ export function classifyConnection(args: {
|
||||
if (now - lastConnectedAt >= STALE_SINCE_LAST_CONNECT_MS) {
|
||||
return {
|
||||
kind: 'unreachable',
|
||||
label: "Can't reach desktop",
|
||||
label: translate('connection.connectionHealth.desktopUnreachable', "Can't reach desktop"),
|
||||
reason: 'stale',
|
||||
hint
|
||||
}
|
||||
@@ -132,10 +182,21 @@ export function classifyConnection(args: {
|
||||
}
|
||||
|
||||
if (reconnectAttempts >= WARNING_ATTEMPTS) {
|
||||
return { kind: 'warning', label: "Can't connect", hint }
|
||||
return {
|
||||
kind: 'warning',
|
||||
label: translate('connection.connectionHealth.connectFailed', "Can't connect"),
|
||||
hint
|
||||
}
|
||||
}
|
||||
|
||||
return { kind: 'normal', label: state === 'reconnecting' ? 'Reconnecting…' : 'Connecting…' }
|
||||
return {
|
||||
kind: 'normal',
|
||||
label:
|
||||
state === 'reconnecting'
|
||||
? translate('connection.connectionHealth.reconnecting', 'Reconnecting…')
|
||||
: translate('connection.connectionHealth.connecting', 'Connecting…'),
|
||||
dialing: true
|
||||
}
|
||||
}
|
||||
|
||||
// Retries the user should hear about; auth-failed has its own re-pair remedy.
|
||||
@@ -145,14 +206,16 @@ export function isConnectionOutage(
|
||||
return verdict.kind === 'warning' || verdict.kind === 'unreachable'
|
||||
}
|
||||
|
||||
// A normal label ending in an ellipsis is a dial still in progress.
|
||||
export function isConnectionDialing(verdict: ConnectionVerdict): boolean {
|
||||
return verdict.kind === 'normal' && verdict.label.endsWith('…')
|
||||
return verdict.kind === 'normal' && verdict.dialing === true
|
||||
}
|
||||
|
||||
export function verdictDisplayLabel(verdict: ConnectionVerdict): string {
|
||||
if (isConnectionOutage(verdict) && verdict.hint) {
|
||||
return `${verdict.label} — ${verdict.hint}`
|
||||
return translate('connection.connectionHealth.labelWithHint', '{{label}} — {{hint}}', {
|
||||
label: verdict.label,
|
||||
hint: verdict.hint
|
||||
})
|
||||
}
|
||||
return verdict.label
|
||||
}
|
||||
|
||||
@@ -0,0 +1,18 @@
|
||||
import { translate } from '../i18n/mobile-i18n'
|
||||
|
||||
export const hostEndpointCopy = {
|
||||
addressRequired: () =>
|
||||
translate('connection.hostEndpoint.addressRequired', 'Enter a host address.'),
|
||||
invalidPort: () => translate('connection.hostEndpoint.invalidPort', 'Port must be 1–65535.'),
|
||||
invalidScheme: () =>
|
||||
translate('connection.hostEndpoint.invalidScheme', 'Use ws:// or wss:// (or host:port).'),
|
||||
invalidAddress: () => translate('connection.hostEndpoint.invalidAddress', 'Not a valid address.'),
|
||||
pathOrQueryNotAllowed: () =>
|
||||
translate(
|
||||
'connection.hostEndpoint.pathOrQueryNotAllowed',
|
||||
'Host must not include a path or query.'
|
||||
),
|
||||
invalidHostname: () =>
|
||||
translate('connection.hostEndpoint.invalidHostname', 'Not a valid hostname.'),
|
||||
hostnameRequired: () => translate('connection.hostEndpoint.hostnameRequired', 'Missing hostname.')
|
||||
}
|
||||
@@ -1,3 +1,5 @@
|
||||
import { hostEndpointCopy } from './host-endpoint-copy'
|
||||
|
||||
// Normalize Edit Host input to a websocket authority without requiring re-pairing.
|
||||
|
||||
export type NormalizeHostEndpointResult =
|
||||
@@ -97,7 +99,7 @@ export function normalizeHostEndpoint(
|
||||
): NormalizeHostEndpointResult {
|
||||
const trimmed = input.trim()
|
||||
if (!trimmed) {
|
||||
return { ok: false, error: 'Enter a host address.' }
|
||||
return { ok: false, error: hostEndpointCopy.addressRequired() }
|
||||
}
|
||||
|
||||
const fallbackPort = resolveFallbackPort(options?.fallbackPort)
|
||||
@@ -124,53 +126,53 @@ function resolveFallbackPort(value: string | number | undefined): string {
|
||||
function normalizeSchemeUrl(input: string, fallbackPort: string): NormalizeHostEndpointResult {
|
||||
const explicitPort = resolveWebsocketUrlPort(input)
|
||||
if (explicitPort.kind === 'invalid') {
|
||||
return { ok: false, error: 'Port must be 1–65535.' }
|
||||
return { ok: false, error: hostEndpointCopy.invalidPort() }
|
||||
}
|
||||
const scheme = /^([a-zA-Z][a-zA-Z0-9+.-]*):\/\//.exec(input)?.[1]?.toLowerCase()
|
||||
if (scheme !== 'ws' && scheme !== 'wss') {
|
||||
return { ok: false, error: 'Use ws:// or wss:// (or host:port).' }
|
||||
return { ok: false, error: hostEndpointCopy.invalidScheme() }
|
||||
}
|
||||
|
||||
const rawAuthority = parseRawSchemeAuthority(input)
|
||||
if (rawAuthority.hasUserInfo) {
|
||||
return { ok: false, error: 'Not a valid address.' }
|
||||
return { ok: false, error: hostEndpointCopy.invalidAddress() }
|
||||
}
|
||||
if (rawAuthority.hasPathOrQuery) {
|
||||
return { ok: false, error: 'Host must not include a path or query.' }
|
||||
return { ok: false, error: hostEndpointCopy.pathOrQueryNotAllowed() }
|
||||
}
|
||||
if (
|
||||
rawAuthority.hostname &&
|
||||
validateNumericIpv4Candidate(normalizeRawNumericIpv4Candidate(rawAuthority.hostname))
|
||||
) {
|
||||
return { ok: false, error: 'Not a valid hostname.' }
|
||||
return { ok: false, error: hostEndpointCopy.invalidHostname() }
|
||||
}
|
||||
|
||||
let url: URL
|
||||
try {
|
||||
url = new URL(input)
|
||||
} catch {
|
||||
return { ok: false, error: 'Not a valid address.' }
|
||||
return { ok: false, error: hostEndpointCopy.invalidAddress() }
|
||||
}
|
||||
|
||||
if (url.protocol !== 'ws:' && url.protocol !== 'wss:') {
|
||||
return { ok: false, error: 'Use ws:// or wss:// (or host:port).' }
|
||||
return { ok: false, error: hostEndpointCopy.invalidScheme() }
|
||||
}
|
||||
if (!url.hostname) {
|
||||
return { ok: false, error: 'Missing hostname.' }
|
||||
return { ok: false, error: hostEndpointCopy.hostnameRequired() }
|
||||
}
|
||||
|
||||
// Reject path/query/userinfo instead of silently stripping address typos.
|
||||
if (url.username || url.password) {
|
||||
return { ok: false, error: 'Not a valid address.' }
|
||||
return { ok: false, error: hostEndpointCopy.invalidAddress() }
|
||||
}
|
||||
if ((url.pathname && url.pathname !== '/') || url.search || url.hash) {
|
||||
return { ok: false, error: 'Host must not include a path or query.' }
|
||||
return { ok: false, error: hostEndpointCopy.pathOrQueryNotAllowed() }
|
||||
}
|
||||
|
||||
const hostname = unwrapHostname(url.hostname)
|
||||
// Reject legacy IPv4 aliases that WHATWG URL would rewrite to a different address.
|
||||
if (rawAuthority.hostname && isCanonicalIpv4(hostname) && rawAuthority.hostname !== hostname) {
|
||||
return { ok: false, error: 'Not a valid hostname.' }
|
||||
return { ok: false, error: hostEndpointCopy.invalidHostname() }
|
||||
}
|
||||
const hostError = validateHostname(hostname)
|
||||
if (hostError) {
|
||||
@@ -180,7 +182,7 @@ function normalizeSchemeUrl(input: string, fallbackPort: string): NormalizeHostE
|
||||
// Preserve explicit default ports; URL.port would otherwise replace them with fallbackPort.
|
||||
const resolvedPort = resolveWebsocketUrlPort(input, url)
|
||||
if (resolvedPort.kind === 'invalid') {
|
||||
return { ok: false, error: 'Port must be 1–65535.' }
|
||||
return { ok: false, error: hostEndpointCopy.invalidPort() }
|
||||
}
|
||||
const port = resolvedPort.kind === 'valid' ? resolvedPort.port : fallbackPort
|
||||
|
||||
@@ -226,14 +228,14 @@ function normalizeHostPort(
|
||||
if (input.startsWith('[')) {
|
||||
const close = input.indexOf(']')
|
||||
if (close <= 1) {
|
||||
return { ok: false, error: 'Not a valid address.' }
|
||||
return { ok: false, error: hostEndpointCopy.invalidAddress() }
|
||||
}
|
||||
host = input.slice(1, close)
|
||||
const rest = input.slice(close + 1)
|
||||
if (rest.startsWith(':')) {
|
||||
port = rest.slice(1)
|
||||
} else if (rest.length > 0) {
|
||||
return { ok: false, error: 'Not a valid address.' }
|
||||
return { ok: false, error: hostEndpointCopy.invalidAddress() }
|
||||
}
|
||||
} else {
|
||||
const firstColon = input.indexOf(':')
|
||||
@@ -248,7 +250,7 @@ function normalizeHostPort(
|
||||
|
||||
host = host.trim()
|
||||
if (!host) {
|
||||
return { ok: false, error: 'Missing hostname.' }
|
||||
return { ok: false, error: hostEndpointCopy.hostnameRequired() }
|
||||
}
|
||||
|
||||
// Validate bare hosts too; URL-only delimiters must never become hostname bytes.
|
||||
@@ -260,7 +262,7 @@ function normalizeHostPort(
|
||||
if (port !== undefined) {
|
||||
port = port.trim()
|
||||
if (!isValidPort(port)) {
|
||||
return { ok: false, error: 'Port must be 1–65535.' }
|
||||
return { ok: false, error: hostEndpointCopy.invalidPort() }
|
||||
}
|
||||
}
|
||||
|
||||
@@ -274,10 +276,10 @@ function formatHostForUrl(host: string): string {
|
||||
|
||||
function validateHostname(host: string): string | null {
|
||||
if (!host) {
|
||||
return 'Missing hostname.'
|
||||
return hostEndpointCopy.hostnameRequired()
|
||||
}
|
||||
if (/[\s/?#@[\]]/.test(host)) {
|
||||
return 'Not a valid hostname.'
|
||||
return hostEndpointCopy.invalidHostname()
|
||||
}
|
||||
const numericIpv4Error = validateNumericIpv4Candidate(host)
|
||||
if (numericIpv4Error) {
|
||||
@@ -286,12 +288,12 @@ function validateHostname(host: string): string | null {
|
||||
if (host.includes(':')) {
|
||||
// Use the WebSocket URL parser to reject malformed IPv6 that a hex/colon regex accepts.
|
||||
if (!/^[0-9a-fA-F:]+$/.test(host)) {
|
||||
return 'Not a valid hostname.'
|
||||
return hostEndpointCopy.invalidHostname()
|
||||
}
|
||||
try {
|
||||
new URL(`ws://[${host}]:${DEFAULT_PORT}`)
|
||||
} catch {
|
||||
return 'Not a valid hostname.'
|
||||
return hostEndpointCopy.invalidHostname()
|
||||
}
|
||||
return null
|
||||
}
|
||||
@@ -300,7 +302,7 @@ function validateHostname(host: string): string | null {
|
||||
host
|
||||
)
|
||||
) {
|
||||
return 'Not a valid hostname.'
|
||||
return hostEndpointCopy.invalidHostname()
|
||||
}
|
||||
return null
|
||||
}
|
||||
@@ -310,7 +312,7 @@ function validateNumericIpv4Candidate(host: string): string | null {
|
||||
return null
|
||||
}
|
||||
if (!isCanonicalIpv4(host)) {
|
||||
return 'Not a valid hostname.'
|
||||
return hostEndpointCopy.invalidHostname()
|
||||
}
|
||||
return null
|
||||
}
|
||||
|
||||
@@ -1,8 +1,20 @@
|
||||
import { translate } from '../i18n/mobile-i18n'
|
||||
import type { MobileConnectionPath } from './stable-logical-rpc-client'
|
||||
|
||||
export function mobileConnectionPathLabel(path: MobileConnectionPath): string {
|
||||
if (path === 'relay') {
|
||||
return 'Orca Relay'
|
||||
}
|
||||
return path === 'tailscale' ? 'Direct · Tailscale' : 'Direct · LAN'
|
||||
return path === 'tailscale'
|
||||
? translate('connection.connectionPathLabel.directTailscale', 'Direct · Tailscale')
|
||||
: translate('connection.connectionPathLabel.directLan', 'Direct · LAN')
|
||||
}
|
||||
|
||||
export function mobileConnectionPathAccessibilityLabel(path: MobileConnectionPath): string {
|
||||
if (path === 'relay') {
|
||||
return 'Orca Relay'
|
||||
}
|
||||
return path === 'tailscale'
|
||||
? translate('connection.connectionPathLabel.directViaTailscale', 'Direct via Tailscale')
|
||||
: translate('connection.connectionPathLabel.directViaLan', 'Direct via LAN')
|
||||
}
|
||||
|
||||
@@ -1,3 +1,4 @@
|
||||
import { translate } from '../i18n/mobile-i18n'
|
||||
import { parsePairingCode } from './pairing'
|
||||
import type { PairingOffer } from './types'
|
||||
|
||||
@@ -9,12 +10,20 @@ export function resolvePairConfirmRouteState(
|
||||
code: string | string[] | undefined
|
||||
): PairConfirmRouteState {
|
||||
if (!code) {
|
||||
return { kind: 'error', offer: null, errorMessage: 'Missing pairing code' }
|
||||
return {
|
||||
kind: 'error',
|
||||
offer: null,
|
||||
errorMessage: translate('connection.pairConfirmState.missingCode', 'Missing pairing code')
|
||||
}
|
||||
}
|
||||
|
||||
const offer = typeof code === 'string' ? parsePairingCode(code) : null
|
||||
if (!offer) {
|
||||
return { kind: 'error', offer: null, errorMessage: 'Not a valid pairing code' }
|
||||
return {
|
||||
kind: 'error',
|
||||
offer: null,
|
||||
errorMessage: translate('connection.pairConfirmState.invalidCode', 'Not a valid pairing code')
|
||||
}
|
||||
}
|
||||
|
||||
return { kind: 'ready', offer, errorMessage: '' }
|
||||
|
||||
@@ -17,6 +17,7 @@ import { RpcClientTerminalStreamRouter } from './rpc-client-terminal-stream-rout
|
||||
import * as sessionTabsStream from './rpc-client-session-tabs-stream'
|
||||
import type { RpcEnvelopeFields, RpcRequestFrame } from './rpc-envelope-fields'
|
||||
import type { ConnectionState, RpcResponse, RpcSuccess } from './types'
|
||||
import { translate } from '../i18n/mobile-i18n'
|
||||
|
||||
export type RpcStreamingListener = (result: unknown) => void
|
||||
|
||||
@@ -73,7 +74,13 @@ export class RpcClientStreamRegistry {
|
||||
if (this.send(id, stream)) {
|
||||
stream.sent = true
|
||||
} else {
|
||||
this.finish(id, stream, { type: 'error', message: 'Connection interrupted' })
|
||||
this.finish(id, stream, {
|
||||
type: 'error',
|
||||
message: translate(
|
||||
'connection.rpcClientStreamRegistry.interrupted',
|
||||
'Connection interrupted'
|
||||
)
|
||||
})
|
||||
}
|
||||
} else {
|
||||
console.log('[net] subscribe queued — waiting for connected', {
|
||||
@@ -142,7 +149,10 @@ export class RpcClientStreamRegistry {
|
||||
this.finish(response.id, stream, {
|
||||
type: 'error',
|
||||
message: response.ok
|
||||
? 'Streaming request ended before it was ready.'
|
||||
? translate(
|
||||
'connection.rpcClientStreamRegistry.endedBeforeReady',
|
||||
'Streaming request ended before it was ready.'
|
||||
)
|
||||
: response.error.message,
|
||||
error: response.ok ? undefined : response.error
|
||||
})
|
||||
|
||||
@@ -6,6 +6,7 @@ import {
|
||||
readTerminalStreamFrameId
|
||||
} from '../../../src/shared/terminal-stream-protocol'
|
||||
import type { TerminalUnreadableFrameResets } from './terminal-unreadable-frame-resets'
|
||||
import { translate } from '../i18n/mobile-i18n'
|
||||
|
||||
export type TerminalSnapshotState = {
|
||||
streamId: number
|
||||
@@ -145,7 +146,11 @@ function reportUnreadableTerminalFrame(
|
||||
listener({
|
||||
type: 'error',
|
||||
streamId,
|
||||
message: `Unsupported terminal stream frame (opcode ${opcode}).`
|
||||
message: translate(
|
||||
'connection.rpcClientTerminalBinaryFrame.unsupportedFrame',
|
||||
'Unsupported terminal stream frame (opcode {{opcode}}).',
|
||||
{ opcode }
|
||||
)
|
||||
})
|
||||
})
|
||||
}
|
||||
|
||||
@@ -1,10 +1,8 @@
|
||||
import type { RuntimeWorktreeAgentRow } from '../../../src/shared/runtime-types'
|
||||
import { isAgentStatusFresh } from '../../../src/shared/agent-status-freshness'
|
||||
import { agentVerdictDisplayMark } from '../../../src/shared/agent-main-agent-verdict'
|
||||
import {
|
||||
formatCompactDuration,
|
||||
resolveDecayedAgentRowState
|
||||
} from '../../../src/shared/agent-row-decay-state'
|
||||
import { resolveDecayedAgentRowState } from '../../../src/shared/agent-row-decay-state'
|
||||
import { translate } from '../i18n/mobile-i18n'
|
||||
|
||||
// Mirror desktop AgentStateDot vocabulary.
|
||||
export type AgentDotState =
|
||||
@@ -51,35 +49,32 @@ export function agentDotState(
|
||||
export function agentStateLabel(state: AgentDotState): string {
|
||||
switch (state) {
|
||||
case 'working':
|
||||
return 'Working'
|
||||
return translate('worktree.agentRowDisplay.working', 'Working')
|
||||
case 'monitoring':
|
||||
return 'Monitoring background tasks'
|
||||
return translate('worktree.agentRowDisplay.monitoring', 'Monitoring background tasks')
|
||||
case 'blocked':
|
||||
return 'Blocked'
|
||||
return translate('worktree.agentRowDisplay.blocked', 'Blocked')
|
||||
case 'waiting':
|
||||
return 'Waiting for input'
|
||||
return translate('worktree.agentRowDisplay.waiting', 'Waiting for input')
|
||||
case 'interrupted':
|
||||
return 'Interrupted'
|
||||
return translate('worktree.agentRowDisplay.interrupted', 'Interrupted')
|
||||
case 'failed':
|
||||
return 'Failed'
|
||||
return translate('worktree.agentRowDisplay.failed', 'Failed')
|
||||
case 'unconfirmed':
|
||||
return 'Couldn’t confirm'
|
||||
return translate('worktree.agentRowDisplay.unconfirmed', 'Couldn’t confirm')
|
||||
case 'done':
|
||||
return 'Done'
|
||||
return translate('worktree.agentRowDisplay.done', 'Done')
|
||||
case 'idle':
|
||||
return 'Idle'
|
||||
return translate('worktree.agentRowDisplay.idle', 'Idle')
|
||||
case 'unverifiable':
|
||||
return 'No recent update'
|
||||
return translate('worktree.agentRowDisplay.unverifiable', 'No recent update')
|
||||
}
|
||||
}
|
||||
|
||||
// Mirrors NATIVE_CHAT_TURN_STATUS_COPY.stopping.
|
||||
const STOPPING_LABEL = 'Stopping…'
|
||||
|
||||
// Match DashboardAgentRow fallback order; a user-requested Stop takes precedence while ending the turn.
|
||||
export function agentDisplayLabel(row: RuntimeWorktreeAgentRow, now: number): string {
|
||||
if (row.state === 'working' && row.mainAgent?.stopping === true) {
|
||||
return STOPPING_LABEL
|
||||
return translate('worktree.agentRowDisplay.stopping', 'Stopping…')
|
||||
}
|
||||
const message = row.lastAssistantMessage?.trim()
|
||||
if (message) {
|
||||
@@ -104,5 +99,25 @@ export function agentRowTimeAt(
|
||||
|
||||
export function formatTimeAgo(ts: number, now: number): string {
|
||||
const delta = now - ts
|
||||
return delta < 60_000 ? 'just now' : formatCompactDuration(delta)
|
||||
if (delta < 60_000) {
|
||||
return translate('worktree.agentRowDisplay.justNow', 'just now')
|
||||
}
|
||||
const minutes = Math.max(0, Math.floor(delta / 60_000))
|
||||
if (minutes < 60) {
|
||||
const count = minutes
|
||||
return count === 1
|
||||
? translate('worktree.agentRowDisplay.minutes_one', '{{count}}m', { count })
|
||||
: translate('worktree.agentRowDisplay.minutes_other', '{{count}}m', { count })
|
||||
}
|
||||
const hours = Math.floor(minutes / 60)
|
||||
if (hours < 24) {
|
||||
const count = hours
|
||||
return count === 1
|
||||
? translate('worktree.agentRowDisplay.hours_one', '{{count}}h', { count })
|
||||
: translate('worktree.agentRowDisplay.hours_other', '{{count}}h', { count })
|
||||
}
|
||||
const count = Math.floor(hours / 24)
|
||||
return count === 1
|
||||
? translate('worktree.agentRowDisplay.days_one', '{{count}}d', { count })
|
||||
: translate('worktree.agentRowDisplay.days_other', '{{count}}d', { count })
|
||||
}
|
||||
|
||||
@@ -82,7 +82,7 @@ describe('selectHostWorkspaceListState', () => {
|
||||
selectHostWorkspaceListState(
|
||||
input({
|
||||
connState: 'disconnected',
|
||||
connectionVerdict: { kind: 'normal', label: 'Connecting via Relay…' },
|
||||
connectionVerdict: { kind: 'normal', label: 'Connecting via Relay…', dialing: true },
|
||||
worktreesLoaded: false
|
||||
})
|
||||
)
|
||||
|
||||
@@ -3,6 +3,7 @@ import { colors, spacing, typography } from '../theme/mobile-theme'
|
||||
import { themedStyleSheet } from '../theme/themed-styles'
|
||||
import { useMobileColorScheme } from '../theme/use-mobile-color-scheme'
|
||||
import { isConnectionOutage, verdictDisplayLabel } from '../transport/connection-health'
|
||||
import { useMobileTranslation } from '../i18n/use-mobile-translation'
|
||||
import {
|
||||
selectHostWorkspaceListState,
|
||||
type HostWorkspaceListStateInput
|
||||
@@ -14,6 +15,7 @@ export function HostWorkspaceListStates(
|
||||
activeFilterCount: number
|
||||
}
|
||||
) {
|
||||
const t = useMobileTranslation()
|
||||
useMobileColorScheme()
|
||||
const state = selectHostWorkspaceListState(props)
|
||||
if (state === 'loading') {
|
||||
@@ -30,7 +32,10 @@ export function HostWorkspaceListStates(
|
||||
<Text style={styles.emptyText}>{verdictDisplayLabel(connectionVerdict)}</Text>
|
||||
<Text style={styles.catalogErrorDetail}>
|
||||
{(isConnectionOutage(connectionVerdict) ? connectionVerdict.detail : undefined) ??
|
||||
'Check that your computer is awake and Orca is running'}
|
||||
t(
|
||||
'worktree.hostWorkspaceListStates.checkHost',
|
||||
'Check that your computer is awake and Orca is running'
|
||||
)}
|
||||
</Text>
|
||||
</View>
|
||||
)
|
||||
@@ -38,9 +43,18 @@ export function HostWorkspaceListStates(
|
||||
if (state === 'catalog-error') {
|
||||
return (
|
||||
<View style={styles.centered}>
|
||||
<Text style={styles.emptyText}>Could not load workspaces from this host</Text>
|
||||
<Text style={styles.emptyText}>
|
||||
{t(
|
||||
'worktree.hostWorkspaceListStates.loadFailed',
|
||||
'Could not load workspaces from this host'
|
||||
)}
|
||||
</Text>
|
||||
<Text style={styles.catalogErrorDetail}>
|
||||
{`worktree.ps failed (${props.catalogError}) — retrying automatically`}
|
||||
{t(
|
||||
'worktree.hostWorkspaceListStates.retrying',
|
||||
'worktree.ps failed ({{error}}) — retrying automatically',
|
||||
{ error: props.catalogError }
|
||||
)}
|
||||
</Text>
|
||||
</View>
|
||||
)
|
||||
@@ -50,10 +64,10 @@ export function HostWorkspaceListStates(
|
||||
<View style={styles.centered}>
|
||||
<Text style={styles.emptyText}>
|
||||
{props.search
|
||||
? 'No matching workspaces'
|
||||
? t('worktree.hostWorkspaceListStates.noMatches', 'No matching workspaces')
|
||||
: props.activeFilterCount > 0
|
||||
? 'No workspaces match filters'
|
||||
: 'No workspaces'}
|
||||
? t('worktree.hostWorkspaceListStates.noFilterMatches', 'No workspaces match filters')
|
||||
: t('worktree.hostWorkspaceListStates.empty', 'No workspaces')}
|
||||
</Text>
|
||||
</View>
|
||||
)
|
||||
@@ -63,12 +77,18 @@ export function HostWorkspaceListStates(
|
||||
|
||||
/** A list this page cannot build; shown above the rows it already holds, so it never blanks. */
|
||||
export function HostWorkspaceListReopen({ aboveRows }: { aboveRows: boolean }) {
|
||||
const t = useMobileTranslation()
|
||||
useMobileColorScheme()
|
||||
return (
|
||||
<View style={aboveRows ? styles.aboveRows : styles.centered}>
|
||||
<Text style={styles.emptyText}>Reopen to update</Text>
|
||||
<Text style={styles.emptyText}>
|
||||
{t('worktree.hostWorkspaceListStates.reopen', 'Reopen to update')}
|
||||
</Text>
|
||||
<Text style={styles.catalogErrorDetail}>
|
||||
This host lists workspaces in a way this page does not know yet.
|
||||
{t(
|
||||
'worktree.hostWorkspaceListStates.reopenDetail',
|
||||
'This host lists workspaces in a way this page does not know yet.'
|
||||
)}
|
||||
</Text>
|
||||
</View>
|
||||
)
|
||||
|
||||
@@ -1,4 +1,5 @@
|
||||
import { useMemo } from 'react'
|
||||
import { useMobileTranslation } from '../i18n/use-mobile-translation'
|
||||
import type { WorkspaceStatusDefinition } from '../../../src/shared/worktree/types'
|
||||
import type { ExecutionHostId } from '../../../src/shared/execution-host'
|
||||
import type {
|
||||
@@ -37,6 +38,7 @@ export function useWorkspaceSections(args: {
|
||||
uniqueRepos: WorkspaceSectionRepo[]
|
||||
repoById: ReadonlyMap<string, SectionRepo>
|
||||
} {
|
||||
const t = useMobileTranslation()
|
||||
const {
|
||||
displayWorktrees,
|
||||
sortMode,
|
||||
@@ -54,12 +56,12 @@ export function useWorkspaceSections(args: {
|
||||
|
||||
const uniqueRepos = useMemo(
|
||||
() => buildFilterRepos(filterCatalogRepos, displayWorktrees),
|
||||
[filterCatalogRepos, displayWorktrees]
|
||||
[filterCatalogRepos, displayWorktrees, t]
|
||||
)
|
||||
|
||||
const repoById = useMemo(
|
||||
() => buildSectionRepoMap(projectCatalog.repos, displayWorktrees),
|
||||
[projectCatalog.repos, displayWorktrees]
|
||||
[projectCatalog.repos, displayWorktrees, t]
|
||||
)
|
||||
|
||||
const plan = useMemo(
|
||||
@@ -87,7 +89,8 @@ export function useWorkspaceSections(args: {
|
||||
pinnedIds,
|
||||
projectCatalog,
|
||||
workspaceStatuses,
|
||||
showPinnedInGroups
|
||||
showPinnedInGroups,
|
||||
t
|
||||
]
|
||||
)
|
||||
|
||||
|
||||
@@ -18,7 +18,6 @@ import {
|
||||
} from './mobile-workspace-lineage'
|
||||
import {
|
||||
getPRLaneKey,
|
||||
PR_GROUP_LABELS,
|
||||
PR_GROUP_ORDER
|
||||
} from '../../../src/shared/workspace-view/workspace-pr-groups'
|
||||
import type { FilterState, Section, SectionRepo, Worktree } from './workspace-list-types'
|
||||
@@ -42,6 +41,7 @@ import {
|
||||
filterWorkspaceRows,
|
||||
type WorkspaceRowFactReader
|
||||
} from '../../../src/shared/workspace-view/workspace-row-filters'
|
||||
import { translate } from '../i18n/mobile-i18n'
|
||||
|
||||
export type { FilterState, Section, SectionRepo, Worktree } from './workspace-list-types'
|
||||
|
||||
@@ -71,7 +71,7 @@ export function buildSectionRepoMap(
|
||||
if (!repoMap.has(w.repoId)) {
|
||||
repoMap.set(w.repoId, {
|
||||
id: w.repoId,
|
||||
displayName: w.repo || 'Unknown',
|
||||
displayName: w.repo || translate('common.unknown', 'Unknown'),
|
||||
// Why: labels key on host + path, so a shared '' would let one stand-in rename another.
|
||||
path: w.repoId,
|
||||
executionHostId: w.hostId
|
||||
@@ -103,19 +103,9 @@ export function isWorktreeActive(
|
||||
unread?: boolean
|
||||
}
|
||||
): boolean {
|
||||
if (w.hasHostSidebarActivity !== undefined) {
|
||||
return w.hasHostSidebarActivity
|
||||
}
|
||||
if (w.unread) {
|
||||
return true
|
||||
}
|
||||
if (w.status) {
|
||||
return w.status !== 'inactive'
|
||||
}
|
||||
if (w.liveTerminalCount > 0) {
|
||||
return true
|
||||
}
|
||||
return false
|
||||
return w.hasHostSidebarActivity !== undefined
|
||||
? w.hasHostSidebarActivity
|
||||
: Boolean(w.unread) || (w.status ? w.status !== 'inactive' : w.liveTerminalCount > 0)
|
||||
}
|
||||
|
||||
// Older hosts omit the main-worktree flag; use the legacy branch heuristic.
|
||||
@@ -182,6 +172,14 @@ export function buildMobileWorkspacePlan(
|
||||
showPinnedInGroups = false,
|
||||
repoHostIdByRepoId: ReadonlyMap<string, ExecutionHostId> = new Map()
|
||||
) {
|
||||
const groupLabels = {
|
||||
pinned: translate('worktree.workspaceListSections.pinned', 'Pinned'),
|
||||
all: translate('worktree.workspaceListSections.all', 'All'),
|
||||
done: translate('worktree.workspaceListSections.done', 'Done'),
|
||||
'in-review': translate('worktree.workspaceListSections.inReview', 'In review'),
|
||||
'in-progress': translate('worktree.workspaceListSections.inProgress', 'In progress'),
|
||||
closed: translate('worktree.workspaceListSections.closed', 'Closed')
|
||||
}
|
||||
const filtered = filterWorktrees(worktrees, filters, search, repoHostIdByRepoId)
|
||||
const sorted = sortWorktrees(filtered, sortMode, Date.now(), hostSortsByStamps(worktrees))
|
||||
const childrenByParentIdentity = getMobileWorkspaceLineageChildren(
|
||||
@@ -203,12 +201,12 @@ export function buildMobileWorkspacePlan(
|
||||
sectionsOf: ({ pinned, natural: canonicalGroupWorktrees, displayRootByIdentity }) => {
|
||||
const sections: Section[] = []
|
||||
if (pinned.length > 0) {
|
||||
sections.push(makeSection('pinned', 'Pinned', pinned, { icon: 'pin' }))
|
||||
sections.push(makeSection('pinned', groupLabels.pinned, pinned, { icon: 'pin' }))
|
||||
}
|
||||
if (groupMode === 'none') {
|
||||
if (canonicalGroupWorktrees.length > 0) {
|
||||
sections.push(
|
||||
makeSection('all', 'All', withFolderWorkspacesLast(canonicalGroupWorktrees))
|
||||
makeSection('all', groupLabels.all, withFolderWorkspacesLast(canonicalGroupWorktrees))
|
||||
)
|
||||
}
|
||||
} else if (groupMode === 'repo') {
|
||||
@@ -260,6 +258,7 @@ export function buildMobileWorkspacePlan(
|
||||
sections.push(
|
||||
makeSection(
|
||||
getWorkspaceStatusGroupKey(status.id),
|
||||
// Status names are the host's (user-editable) labels, as on desktop.
|
||||
status.label,
|
||||
withFolderWorkspacesLast(items)
|
||||
)
|
||||
@@ -283,7 +282,7 @@ export function buildMobileWorkspacePlan(
|
||||
sections.push(
|
||||
makeSection(
|
||||
getPRLaneKey(groupKey),
|
||||
PR_GROUP_LABELS[groupKey],
|
||||
groupLabels[groupKey],
|
||||
withFolderWorkspacesLast(items)
|
||||
)
|
||||
)
|
||||
|
||||
Reference in New Issue
Block a user