feat(mobile): localize shared components, pull requests and diff review (#35532)

Moves ~600 strings in the shared components (modals, drawers, host cards, dictation,
accounts), the pull request sidebar and diff review to the translation API, with all
ten translations.

- Invalid account snapshots are detected by error type, not by matching English text
- Credit expiry wording uses separate keys for one and several credits instead of
  plural rules, which would pick the wrong sentence in Japanese or Russian
- The PR chip memo recomputes on a language change

Co-authored-by: OrcaWin <293788423+OrcaWin@users.noreply.github.com>
This commit is contained in:
OrcaWin
2026-10-28 18:17:58 -07:00
committed by GitHub
co-authored by OrcaWin
parent 8c3551bde3
commit 5ed3c8c072
88 changed files with 10632 additions and 3523 deletions
File diff suppressed because it is too large Load Diff
+2 -2
View File
@@ -11,10 +11,10 @@ import { useMobileTranslation } from '../i18n/use-mobile-translation'
import { styles } from './mobile-accounts-screen-styles'
import { useNow } from '../hooks/use-now'
import { ClaudeIcon, OpenAIIcon } from '../components/AgentIcons'
import { AccountsSnapshotError, decodeAccountsSnapshot } from '../components/accounts-snapshot'
import {
type AccountsSnapshot,
type ProviderKey,
decodeAccountsSnapshot,
getActiveProviderRateLimits,
getInactiveProviderUsage,
getUsageBarState,
@@ -142,7 +142,7 @@ export function MobileAccountsScreen() {
setError(res.error.message)
}
} catch (e) {
if (e instanceof Error && e.message === 'Invalid accounts snapshot from host') {
if (e instanceof AccountsSnapshotError) {
rejectInvalidSnapshot()
} else {
setError(e instanceof Error ? e.message : String(e))
+22 -11
View File
@@ -1,10 +1,7 @@
import { useState } from 'react'
import { Pressable, Text, View, type StyleProp, type TextStyle } from 'react-native'
import { useClipboardWriter } from '../platform/clipboard'
import {
AGENT_LAUNCH_STATUS_UNREADABLE_MESSAGE,
AGENT_LAUNCH_UPDATE_REQUIRED_MESSAGE
} from '../session/mobile-existing-agent-launch'
import { useMobileTranslation } from '../i18n/use-mobile-translation'
import type { MobileAgentLaunchAvailability } from '../session/mobile-agent-launch-availability'
import { colors, spacing, typography } from '../theme/mobile-theme'
import { themedStyleSheet } from '../theme/themed-styles'
@@ -31,14 +28,24 @@ export function AgentLaunchNotice({
undeliveredPrompt,
errorStyle
}: Props) {
const t = useMobileTranslation()
useMobileColorScheme()
const clipboard = useClipboardWriter()
const [copyState, setCopyState] = useState<{ prompt: string; label: string } | null>(null)
const [copyState, setCopyState] = useState<{
prompt: string
status: 'copied' | 'failed'
} | null>(null)
const availabilityMessage =
availability === 'update-required'
? AGENT_LAUNCH_UPDATE_REQUIRED_MESSAGE
? t(
'components.agentLaunchNotice.updateRequired',
'Update Orca on your computer to start an agent from your phone.'
)
: availability === 'unverified'
? AGENT_LAUNCH_STATUS_UNREADABLE_MESSAGE
? t(
'components.agentLaunchNotice.statusUnreadable',
"Could not read this host's status. Retrying…"
)
: null
const message = availabilityMessage ?? error
const note = availabilityMessage ? null : warning
@@ -47,7 +54,11 @@ export function AgentLaunchNotice({
return null
}
const copyLabel =
copyState && copyState.prompt === undeliveredPrompt ? copyState.label : 'Copy prompt'
copyState && copyState.prompt === undeliveredPrompt
? copyState.status === 'copied'
? t('common.copied', 'Copied')
: t('components.agentLaunchNotice.copyFailed', "Couldn't copy")
: t('components.agentLaunchNotice.copyPrompt', 'Copy prompt')
return (
<View style={styles.notice}>
{confirmation ? <Text style={styles.successText}>{confirmation}</Text> : null}
@@ -57,12 +68,12 @@ export function AgentLaunchNotice({
<Pressable
onPress={() => {
clipboard.writeText(undeliveredPrompt).then(
() => setCopyState({ prompt: undeliveredPrompt, label: 'Copied' }),
() => setCopyState({ prompt: undeliveredPrompt, label: "Couldn't copy" })
() => setCopyState({ prompt: undeliveredPrompt, status: 'copied' }),
() => setCopyState({ prompt: undeliveredPrompt, status: 'failed' })
)
}}
accessibilityRole="button"
accessibilityLabel="Copy prompt"
accessibilityLabel={t('components.agentLaunchNotice.copyPrompt', 'Copy prompt')}
hitSlop={spacing.sm}
>
<Text style={styles.copyText}>{copyLabel}</Text>
+10 -2
View File
@@ -2,6 +2,7 @@ import { Activity } from 'lucide-react-native'
import { Animated, View } from 'react-native'
import type { AgentWorkingMode } from '../../../src/shared/agent-status-types'
import type { WorktreeStatus } from '../../../src/shared/worktree-status-rollup'
import { useMobileTranslation } from '../i18n/use-mobile-translation'
import { colors } from '../theme/mobile-theme'
import { themedStyleSheet } from '../theme/themed-styles'
import { useMobileColorScheme } from '../theme/use-mobile-color-scheme'
@@ -27,6 +28,7 @@ export function AgentSpinner({
status: WorktreeStatus
workingMode?: AgentWorkingMode
}) {
const t = useMobileTranslation()
useMobileColorScheme()
const monitoring =
status === 'monitoring' || (status === 'working' && workingMode === 'monitoring')
@@ -37,7 +39,10 @@ export function AgentSpinner({
if (monitoring) {
return (
<View style={styles.wrapper} accessibilityLabel="Monitoring background tasks">
<View
style={styles.wrapper}
accessibilityLabel={t('components.agentSpinner.monitoring', 'Monitoring background tasks')}
>
<Activity size={12} color={palette.working} />
</View>
)
@@ -45,7 +50,10 @@ export function AgentSpinner({
if (status === 'unconfirmed') {
return (
<View style={styles.wrapper} accessibilityLabel="Couldn’t confirm">
<View
style={styles.wrapper}
accessibilityLabel={t('components.agentSpinner.unconfirmed', 'Couldn’t confirm')}
>
<View style={styles.dashedRing} />
</View>
)
+10 -2
View File
@@ -1,6 +1,7 @@
import { Activity } from 'lucide-react-native'
import { Animated, View } from 'react-native'
import type { AgentDotState } from '../worktree/agent-row-display'
import { useMobileTranslation } from '../i18n/use-mobile-translation'
import { colors } from '../theme/mobile-theme'
import { themedStyleSheet } from '../theme/themed-styles'
import { useMobileColorScheme } from '../theme/use-mobile-color-scheme'
@@ -25,6 +26,7 @@ export const AGENT_WORKING_COLOR = '#eab308'
/** `unverifiable` is a child row's lost contact: desktop's dashed amber ring, a missing-evidence
* mark rather than a state claim. */
export function AgentStateDot({ state }: { state: AgentDotState | 'unverifiable' }) {
const t = useMobileTranslation()
useMobileColorScheme()
const rotate = useWorkingRingRotation(state === 'working')
@@ -38,7 +40,10 @@ export function AgentStateDot({ state }: { state: AgentDotState | 'unverifiable'
if (state === 'monitoring') {
return (
<View style={styles.wrapper} accessibilityLabel="Monitoring background tasks">
<View
style={styles.wrapper}
accessibilityLabel={t('components.agentStateDot.monitoring', 'Monitoring background tasks')}
>
<Activity size={10} color={AGENT_WORKING_COLOR} />
</View>
)
@@ -46,7 +51,10 @@ export function AgentStateDot({ state }: { state: AgentDotState | 'unverifiable'
if (state === 'unverifiable') {
return (
<View style={styles.wrapper} accessibilityLabel="No recent update">
<View
style={styles.wrapper}
accessibilityLabel={t('components.agentStateDot.unverifiable', 'No recent update')}
>
<View style={styles.dashedRing} />
</View>
)
+6 -1
View File
@@ -1,4 +1,5 @@
import { View, Text } from 'react-native'
import { useMobileTranslation } from '../i18n/use-mobile-translation'
import { AuthFailedBannerActions } from './AuthFailedBannerActions'
import { authFailedBannerStyles as styles } from './auth-failed-banner-styles'
import { useMobileColorScheme } from '../theme/use-mobile-color-scheme'
@@ -15,11 +16,15 @@ export function AuthFailedBanner({
onRepair: () => void
onRemove: () => void
}) {
const t = useMobileTranslation()
useMobileColorScheme()
return (
<View style={styles.banner}>
<Text style={styles.text}>
Authentication failed — try reconnecting first; if it keeps failing, re-pair from desktop.
{t(
'components.authFailedBanner.message',
'Authentication failed — try reconnecting first; if it keeps failing, re-pair from desktop.'
)}
</Text>
<View style={styles.actions}>
<AuthFailedBannerActions
@@ -1,4 +1,5 @@
import { Pressable, Text } from 'react-native'
import { useMobileTranslation } from '../i18n/use-mobile-translation'
import { colors } from '../theme/mobile-theme'
import { useMobileColorScheme } from '../theme/use-mobile-color-scheme'
import { authFailedBannerStyles as styles } from './auth-failed-banner-styles'
@@ -15,19 +16,24 @@ export function AuthFailedBannerActions({
onRepair: () => void
onRemove: () => void
}) {
const t = useMobileTranslation()
useMobileColorScheme()
return (
<>
{canRetry && (
<Pressable style={styles.action} onPress={onRetry}>
<Text style={styles.actionText}>Retry</Text>
<Text style={styles.actionText}>{t('common.retry', 'Retry')}</Text>
</Pressable>
)}
<Pressable style={styles.action} onPress={onRepair}>
<Text style={styles.actionText}>Re-pair</Text>
<Text style={styles.actionText}>
{t('components.authFailedBannerActions.repair', 'Re-pair')}
</Text>
</Pressable>
<Pressable style={styles.action} onPress={onRemove}>
<Text style={[styles.actionText, { color: colors.statusRed }]}>Remove</Text>
<Text style={[styles.actionText, { color: colors.statusRed }]}>
{t('common.remove', 'Remove')}
</Text>
</Pressable>
</>
)
@@ -1,4 +1,5 @@
import { Pressable, Text } from 'react-native'
import { useMobileTranslation } from '../i18n/use-mobile-translation'
import { authFailedBannerStyles as styles } from './auth-failed-banner-styles'
import { useMobileColorScheme } from '../theme/use-mobile-color-scheme'
@@ -11,13 +12,21 @@ export function AuthFailedBannerActions({
onRepair: () => void
onRemove: () => void
}) {
const t = useMobileTranslation()
useMobileColorScheme()
return (
<>
<Pressable style={styles.action} onPress={onRepair}>
<Text style={styles.actionText}>Re-pair</Text>
<Text style={styles.actionText}>
{t('components.authFailedBannerActions.repair', 'Re-pair')}
</Text>
</Pressable>
<Text style={styles.note}>Reconnect or remove this host from the Orca app.</Text>
<Text style={styles.note}>
{t(
'components.authFailedBannerActions.webNote',
'Reconnect or remove this host from the Orca app.'
)}
</Text>
</>
)
}
@@ -1,5 +1,6 @@
import { ActivityIndicator, Pressable, StyleSheet, Text, View } from 'react-native'
import { RotateCcw } from 'lucide-react-native'
import { useMobileTranslation } from '../i18n/use-mobile-translation'
import { colors, radii, spacing, typography } from '../theme/mobile-theme'
import { themedStyleSheet } from '../theme/themed-styles'
import { useMobileColorScheme } from '../theme/use-mobile-color-scheme'
@@ -18,6 +19,7 @@ export function CodexResetCreditAction({
disabled: boolean
onPress: () => void
}) {
const t = useMobileTranslation()
useMobileColorScheme()
return (
<>
@@ -27,7 +29,7 @@ export function CodexResetCreditAction({
<Text style={styles.title}>{summary.availabilityLabel}</Text>
<Text style={styles.subtitle}>
{[summary.expiryLabel, scopeLabel].filter(Boolean).join(' · ') ||
'Earned Codex rate-limit reset'}
t('components.codexResetCreditAction.earnedReset', 'Earned Codex rate-limit reset')}
</Text>
</View>
<Pressable
@@ -39,11 +41,22 @@ export function CodexResetCreditAction({
onPress={onPress}
disabled={disabled}
accessibilityRole="button"
accessibilityLabel={busy ? 'Resetting Codex rate limits' : 'Use Codex rate-limit reset'}
accessibilityLabel={
busy
? t('components.codexResetCreditAction.resettingLabel', 'Resetting Codex rate limits')
: t('components.codexResetCreditAction.useResetLabel', 'Use Codex rate-limit reset')
}
accessibilityHint={
scopeLabel
? `Uses one earned reset for ${scopeLabel}`
: 'Uses one earned reset for the active Codex account'
? t(
'components.codexResetCreditAction.scopedHint',
'Uses one earned reset for {{scopeLabel}}',
{ scopeLabel }
)
: t(
'components.codexResetCreditAction.activeAccountHint',
'Uses one earned reset for the active Codex account'
)
}
accessibilityState={{ busy, disabled }}
hitSlop={8}
@@ -53,7 +66,11 @@ export function CodexResetCreditAction({
) : (
<RotateCcw size={14} color={colors.textPrimary} />
)}
<Text style={styles.buttonText}>{busy ? 'Resetting…' : 'Use reset'}</Text>
<Text style={styles.buttonText}>
{busy
? t('components.codexResetCreditAction.resetting', 'Resetting…')
: t('components.codexResetCreditAction.useReset', 'Use reset')}
</Text>
</Pressable>
</View>
</>
+6 -4
View File
@@ -1,4 +1,5 @@
import { View, Text, Pressable } from 'react-native'
import { useMobileTranslation } from '../i18n/use-mobile-translation'
import { colors, spacing, radii, typography } from '../theme/mobile-theme'
import { themedStyleSheet } from '../theme/themed-styles'
import { useMobileColorScheme } from '../theme/use-mobile-color-scheme'
@@ -29,12 +30,13 @@ export function ConfirmModal({ visible, ...content }: Props) {
export function ConfirmContent({
title,
message,
confirmLabel = 'Confirm',
cancelLabel = 'Cancel',
confirmLabel,
cancelLabel,
destructive = false,
onConfirm,
onCancel
}: ContentProps) {
const t = useMobileTranslation()
useMobileColorScheme()
return (
<>
@@ -48,7 +50,7 @@ export function ConfirmContent({
style={({ pressed }) => [styles.button, styles.cancelButton, pressed && styles.pressed]}
onPress={onCancel}
>
<Text style={styles.cancelText}>{cancelLabel}</Text>
<Text style={styles.cancelText}>{cancelLabel ?? t('common.cancel', 'Cancel')}</Text>
</Pressable>
)}
<Pressable
@@ -63,7 +65,7 @@ export function ConfirmContent({
}}
>
<Text style={destructive ? styles.destructiveText : styles.confirmText}>
{confirmLabel}
{confirmLabel ?? t('common.confirm', 'Confirm')}
</Text>
</Pressable>
</View>
+75 -40
View File
@@ -13,6 +13,8 @@ import {
} from '../terminal/terminal-accessory-keys'
import { customKeyModalStyles as styles } from './CustomKeyModal.styles'
import { updateStoredCustomKeys, type CustomKey } from '../terminal/terminal-custom-keys'
import type { Translate } from '../i18n/mobile-i18n'
import { useMobileTranslation } from '../i18n/use-mobile-translation'
type Step = 'choose-type' | 'shortcut-combo' | 'special-keys' | 'text-macro'
@@ -24,23 +26,28 @@ const SHORTCUT_MODIFIERS: { id: TerminalShortcutModifier; label: string; glyph?:
]
// Group keys into three fixed grids; ragged wrapping clipped F7–F12.
const SPECIAL_KEY_GROUPS: { title: string; ids: string[]; columns: number }[] = [
{
title: 'Editing',
ids: ['escape', 'tab', 'enter', 'backspace', 'delete', 'insert', 'space'],
columns: 4
},
{
title: 'Navigation',
ids: ['arrowUp', 'arrowDown', 'arrowLeft', 'arrowRight', 'home', 'end', 'pageUp', 'pageDown'],
columns: 4
},
{
title: 'Function',
ids: ['f1', 'f2', 'f3', 'f4', 'f5', 'f6', 'f7', 'f8', 'f9', 'f10', 'f11', 'f12'],
columns: 6
}
]
function specialKeyGroups(t: Translate) {
return [
{
id: 'editing',
title: t('components.customKeyModal.editing', 'Editing'),
ids: ['escape', 'tab', 'enter', 'backspace', 'delete', 'insert', 'space'],
columns: 4
},
{
id: 'navigation',
title: t('components.customKeyModal.navigation', 'Navigation'),
ids: ['arrowUp', 'arrowDown', 'arrowLeft', 'arrowRight', 'home', 'end', 'pageUp', 'pageDown'],
columns: 4
},
{
id: 'function',
title: t('components.customKeyModal.function', 'Function'),
ids: ['f1', 'f2', 'f3', 'f4', 'f5', 'f6', 'f7', 'f8', 'f9', 'f10', 'f11', 'f12'],
columns: 6
}
]
}
const SPECIAL_KEY_BY_ID: Record<string, TerminalShortcutSpecialKey> = Object.fromEntries(
TERMINAL_SHORTCUT_SPECIAL_KEYS.map((key) => [key.id, key])
@@ -55,6 +62,7 @@ type Props = {
export function CustomKeyModal({ visible, onClose, onKeysChanged, onManageShortcuts }: Props) {
useMobileColorScheme()
const t = useMobileTranslation()
const [step, setStep] = useState<Step>('choose-type')
const [shortcutKey, setShortcutKey] = useState('c')
const [shortcutModifiers, setShortcutModifiers] = useState<TerminalShortcutModifier[]>(['ctrl'])
@@ -173,7 +181,7 @@ export function CustomKeyModal({ visible, onClose, onKeysChanged, onManageShortc
style={({ pressed }) => [styles.backButton, pressed && styles.backButtonPressed]}
onPress={onBack}
accessibilityRole="button"
accessibilityLabel="Back"
accessibilityLabel={t('common.back', 'Back')}
>
<ChevronLeft size={18} color={colors.textSecondary} />
</Pressable>
@@ -181,10 +189,11 @@ export function CustomKeyModal({ visible, onClose, onKeysChanged, onManageShortc
<View style={styles.backSpacer} />
)}
<Text style={styles.title}>
{step === 'choose-type' && 'Add Shortcut'}
{step === 'shortcut-combo' && 'Shortcut Combo'}
{step === 'special-keys' && 'Pick a key'}
{step === 'text-macro' && 'Text Macro'}
{step === 'choose-type' && t('components.customKeyModal.addShortcut', 'Add Shortcut')}
{step === 'shortcut-combo' &&
t('components.customKeyModal.shortcutCombo', 'Shortcut Combo')}
{step === 'special-keys' && t('components.customKeyModal.pickKey', 'Pick a key')}
{step === 'text-macro' && t('components.customKeyModal.textMacro', 'Text Macro')}
</Text>
<View style={styles.backSpacer} />
</View>
@@ -195,16 +204,27 @@ export function CustomKeyModal({ visible, onClose, onKeysChanged, onManageShortc
style={({ pressed }) => [styles.row, pressed && styles.rowPressed]}
onPress={() => setStep('shortcut-combo')}
>
<Text style={styles.rowLabel}>Shortcut Combo</Text>
<Text style={styles.rowHint}>Build Ctrl, Alt, and Shift key chords</Text>
<Text style={styles.rowLabel}>
{t('components.customKeyModal.shortcutCombo', 'Shortcut Combo')}
</Text>
<Text style={styles.rowHint}>
{t(
'components.customKeyModal.shortcutComboHint',
'Build Ctrl, Alt, and Shift key chords'
)}
</Text>
</Pressable>
<View style={styles.separator} />
<Pressable
style={({ pressed }) => [styles.row, pressed && styles.rowPressed]}
onPress={() => setStep('text-macro')}
>
<Text style={styles.rowLabel}>Text Macro</Text>
<Text style={styles.rowHint}>Send custom text command</Text>
<Text style={styles.rowLabel}>
{t('components.customKeyModal.textMacro', 'Text Macro')}
</Text>
<Text style={styles.rowHint}>
{t('components.customKeyModal.textMacroHint', 'Send custom text command')}
</Text>
</Pressable>
{onManageShortcuts ? (
<>
@@ -213,8 +233,15 @@ export function CustomKeyModal({ visible, onClose, onKeysChanged, onManageShortc
style={({ pressed }) => [styles.row, pressed && styles.rowPressed]}
onPress={onManageShortcuts}
>
<Text style={styles.rowLabel}>Manage Shortcuts</Text>
<Text style={styles.rowHint}>Show, hide, or reorder shortcut keys</Text>
<Text style={styles.rowLabel}>
{t('components.customKeyModal.manageShortcuts', 'Manage Shortcuts')}
</Text>
<Text style={styles.rowHint}>
{t(
'components.customKeyModal.manageShortcutsHint',
'Show, hide, or reorder shortcut keys'
)}
</Text>
</Pressable>
</>
) : null}
@@ -241,7 +268,9 @@ export function CustomKeyModal({ visible, onClose, onKeysChanged, onManageShortc
</View>
<View style={styles.section}>
<Text style={styles.sectionLabel}>Modifiers</Text>
<Text style={styles.sectionLabel}>
{t('components.customKeyModal.modifiers', 'Modifiers')}
</Text>
<View style={styles.mods}>
{SHORTCUT_MODIFIERS.map((modifier) => {
const selected = shortcutModifiers.includes(modifier.id)
@@ -272,7 +301,7 @@ export function CustomKeyModal({ visible, onClose, onKeysChanged, onManageShortc
</View>
<View style={styles.section}>
<Text style={styles.sectionLabel}>Key</Text>
<Text style={styles.sectionLabel}>{t('components.customKeyModal.key', 'Key')}</Text>
<TextInput
style={styles.keyInput}
value={shortcutKey.length === 1 ? shortcutKey.toUpperCase() : ''}
@@ -287,7 +316,9 @@ export function CustomKeyModal({ visible, onClose, onKeysChanged, onManageShortc
style={({ pressed }) => [styles.moreLink, pressed && styles.moreLinkPressed]}
onPress={() => setStep('special-keys')}
>
<Text style={styles.moreLinkText}>More keys — Tab, arrows, F1–F12…</Text>
<Text style={styles.moreLinkText}>
{t('components.customKeyModal.moreKeys', 'More keys — Tab, arrows, F1–F12…')}
</Text>
</Pressable>
</View>
@@ -299,7 +330,7 @@ export function CustomKeyModal({ visible, onClose, onKeysChanged, onManageShortc
<Text
style={[styles.saveButtonText, !shortcutPreview && styles.saveButtonTextDisabled]}
>
Add
{t('components.customKeyModal.add', 'Add')}
</Text>
</Pressable>
</View>
@@ -307,8 +338,8 @@ export function CustomKeyModal({ visible, onClose, onKeysChanged, onManageShortc
{step === 'special-keys' && (
<View style={styles.specialKeysForm}>
{SPECIAL_KEY_GROUPS.map((group) => (
<View key={group.title} style={styles.specialGroup}>
{specialKeyGroups(t).map((group) => (
<View key={group.id} style={styles.specialGroup}>
<Text style={styles.specialGroupTitle}>{group.title}</Text>
<View style={styles.keyGrid}>
{group.ids.map((id) => {
@@ -346,28 +377,32 @@ export function CustomKeyModal({ visible, onClose, onKeysChanged, onManageShortc
{step === 'text-macro' && (
<View style={styles.group}>
<View style={styles.macroForm}>
<Text style={styles.fieldLabel}>Label</Text>
<Text style={styles.fieldLabel}>{t('components.customKeyModal.label', 'Label')}</Text>
<TextInput
style={styles.fieldInput}
value={macroLabel}
onChangeText={setMacroLabel}
placeholder="e.g. Build"
placeholder={t('components.customKeyModal.labelPlaceholder', 'e.g. Build')}
placeholderTextColor={colors.textMuted}
autoCapitalize="none"
autoCorrect={false}
/>
<Text style={styles.fieldLabel}>Command</Text>
<Text style={styles.fieldLabel}>
{t('components.customKeyModal.command', 'Command')}
</Text>
<TextInput
style={styles.fieldInput}
value={macroText}
onChangeText={setMacroText}
placeholder="e.g. pnpm build"
placeholder={t('components.customKeyModal.commandPlaceholder', 'e.g. pnpm build')}
placeholderTextColor={colors.textMuted}
autoCapitalize="none"
autoCorrect={false}
/>
<View style={styles.switchRow}>
<Text style={styles.switchLabel}>Press Enter</Text>
<Text style={styles.switchLabel}>
{t('components.customKeyModal.pressEnter', 'Press Enter')}
</Text>
<Switch
value={macroEnter}
onValueChange={setMacroEnter}
@@ -383,7 +418,7 @@ export function CustomKeyModal({ visible, onClose, onKeysChanged, onManageShortc
<Text
style={[styles.saveButtonText, !macroText.trim() && styles.saveButtonTextDisabled]}
>
Add Shortcut
{t('components.customKeyModal.addShortcut', 'Add Shortcut')}
</Text>
</Pressable>
</View>
+9 -4
View File
@@ -14,6 +14,7 @@ import Animated, {
import { colors, spacing } from '../theme/mobile-theme'
import { themedStyleSheet } from '../theme/themed-styles'
import { useMobileColorScheme } from '../theme/use-mobile-color-scheme'
import { useMobileTranslation } from '../i18n/use-mobile-translation'
import { triggerMediumImpact } from '../platform/haptics'
import {
type DragReorderLayout,
@@ -69,6 +70,7 @@ export function DragReorderRow({
renderContent: (handle: ReactNode) => ReactNode
}): React.JSX.Element {
useMobileColorScheme()
const t = useMobileTranslation()
const {
layout,
initialPositions,
@@ -183,11 +185,14 @@ export function DragReorderRow({
accessible
accessibilityRole="button"
accessibilityState={{ disabled }}
accessibilityLabel="Drag to reorder"
accessibilityHint="Use the move up and move down actions to reorder without dragging"
accessibilityLabel={t('components.dragReorderRow.dragToReorder', 'Drag to reorder')}
accessibilityHint={t(
'components.dragReorderRow.reorderHint',
'Use the move up and move down actions to reorder without dragging'
)}
accessibilityActions={[
{ name: 'moveUp', label: 'Move up' },
{ name: 'moveDown', label: 'Move down' }
{ name: 'moveUp', label: t('components.dragReorderRow.moveUp', 'Move up') },
{ name: 'moveDown', label: t('components.dragReorderRow.moveDown', 'Move down') }
]}
onAccessibilityAction={(event) => {
if (disabled) {
@@ -3,17 +3,24 @@ import { Pressable, Text } from 'react-native'
import { colors, radii, spacing, typography } from '../theme/mobile-theme'
import { themedStyleSheet } from '../theme/themed-styles'
import { useMobileColorScheme } from '../theme/use-mobile-color-scheme'
import { useMobileTranslation } from '../i18n/use-mobile-translation'
export function HostDiagnosticsLink({ onPress }: { onPress: () => void }): React.JSX.Element {
useMobileColorScheme()
const t = useMobileTranslation()
return (
<Pressable
style={styles.link}
onPress={onPress}
accessibilityRole="button"
accessibilityLabel="View network diagnostics"
accessibilityLabel={t(
'components.hostDiagnosticsLink.viewDiagnostics',
'View network diagnostics'
)}
>
<Text style={styles.text}>View network diagnostics</Text>
<Text style={styles.text}>
{t('components.hostDiagnosticsLink.viewDiagnostics', 'View network diagnostics')}
</Text>
<ChevronRight size={16} color={colors.textSecondary} />
</Pressable>
)
+10 -2
View File
@@ -5,6 +5,7 @@ import { useHostStatusGates } from '../transport/host-status-gates'
import { colors } from '../theme/mobile-theme'
import { themedStyleSheet } from '../theme/themed-styles'
import { useMobileColorScheme } from '../theme/use-mobile-color-scheme'
import { useMobileTranslation } from '../i18n/use-mobile-translation'
import { HostStatusGatesContext } from './host-protocol-gates-context'
import { ProtocolBlockScreen } from './ProtocolBlockScreen'
@@ -25,6 +26,7 @@ export function HostProtocolGate({
blocked: blockedView
}: Props) {
useMobileColorScheme()
const t = useMobileTranslation()
const { client, state } = useHostClient(hostId)
const gates = useHostStatusGates({ hostId, client, connState: state })
const { compatVerdict, statusPending } = gates
@@ -59,7 +61,10 @@ export function HostProtocolGate({
<View style={styles.pending}>
<ActivityIndicator
color={colors.textSecondary}
accessibilityLabel="Checking host compatibility"
accessibilityLabel={t(
'components.hostProtocolGate.checkingCompatibility',
'Checking host compatibility'
)}
/>
</View>
)
@@ -89,7 +94,10 @@ export function HostProtocolGate({
>
<ActivityIndicator
color={colors.textSecondary}
accessibilityLabel="Checking host compatibility"
accessibilityLabel={t(
'components.hostProtocolGate.checkingCompatibility',
'Checking host compatibility'
)}
/>
</View>
) : null}
@@ -3,6 +3,7 @@ import { X } from 'lucide-react-native'
import { colors, spacing } from '../theme/mobile-theme'
import { themedStyleSheet } from '../theme/themed-styles'
import { useMobileColorScheme } from '../theme/use-mobile-color-scheme'
import { useMobileTranslation } from '../i18n/use-mobile-translation'
// Monochrome notice reports vanished target; failure uses one red rule for action refusal without taking screen.
// Announce inserted banners: failure uses alert/assertive, notice uses polite live region (RN has no status role).
@@ -16,6 +17,7 @@ export function HostRouteNoticeBanner({
onDismiss: () => void
}) {
useMobileColorScheme()
const t = useMobileTranslation()
return (
<View
style={[styles.banner, tone === 'failure' && styles.failure]}
@@ -26,7 +28,7 @@ export function HostRouteNoticeBanner({
<Pressable
onPress={onDismiss}
accessibilityRole="button"
accessibilityLabel="Dismiss notice"
accessibilityLabel={t('components.hostRouteNoticeBanner.dismissNotice', 'Dismiss notice')}
hitSlop={spacing.sm}
style={styles.dismiss}
>
+17 -4
View File
@@ -2,6 +2,7 @@ import { Pressable, Text, View } from 'react-native'
import { colors, radii, spacing, typography } from '../theme/mobile-theme'
import { themedStyleSheet } from '../theme/themed-styles'
import { useMobileColorScheme } from '../theme/use-mobile-color-scheme'
import { useMobileTranslation } from '../i18n/use-mobile-translation'
import { TaskProviderLogo } from './TaskProviderLogo'
type Props = {
@@ -19,17 +20,27 @@ export function JiraConnectPrompt({
onCheckAgain
}: Props) {
useMobileColorScheme()
const t = useMobileTranslation()
return (
<View style={styles.centered}>
<TaskProviderLogo provider="jira" size={32} color={colors.textSecondary} />
<Text style={styles.emptyText}>
{unavailableReason ? 'Update Orca desktop' : 'Connect Jira on your desktop'}
{unavailableReason
? t('components.jiraConnectPrompt.updateDesktop', 'Update Orca desktop')
: t('components.jiraConnectPrompt.connectOnDesktop', 'Connect Jira on your desktop')}
</Text>
<Text style={styles.centeredHint}>
{unavailableReason ??
(credentialError
? `Jira is saved but unreadable: ${credentialError}`
: 'Add your Jira site in Orca desktop under Settings → Integrations, then check again.')}
? t(
'components.jiraConnectPrompt.credentialUnreadable',
'Jira is saved but unreadable: {{credentialError}}',
{ credentialError }
)
: t(
'components.jiraConnectPrompt.connectHint',
'Add your Jira site in Orca desktop under Settings → Integrations, then check again.'
))}
</Text>
<Pressable
style={styles.checkAgainButton}
@@ -40,7 +51,9 @@ export function JiraConnectPrompt({
}
}}
>
<Text style={styles.checkAgainText}>Check again</Text>
<Text style={styles.checkAgainText}>
{t('components.jiraConnectPrompt.checkAgain', 'Check again')}
</Text>
</Pressable>
</View>
)
@@ -6,6 +6,9 @@ import { MobileSwitch } from './MobileSwitch'
import { colors, radii, spacing, typography } from '../theme/mobile-theme'
import { themedStyleSheet } from '../theme/themed-styles'
import { useMobileColorScheme } from '../theme/use-mobile-color-scheme'
import type { Translate } from '../i18n/mobile-i18n'
import { useMobileTranslation } from '../i18n/use-mobile-translation'
import { getUiIntlLocale } from '../i18n/ui-intl-locale'
import type { RpcClient } from '../transport/rpc-client'
import { triggerError, triggerSuccess } from '../platform/haptics'
import { useDictationSetupPoller } from '../dictation/use-dictation-setup-poller'
@@ -30,18 +33,52 @@ type Props = {
onReady?: () => void
}
function formatSize(bytes: number | null | undefined): string {
type SetupError = { message: string } | { kind: 'load' | 'download' | 'select' | 'update' }
function setupErrorMessage(error: SetupError, t: Translate): string {
if ('message' in error) {
return error.message
}
switch (error.kind) {
case 'load':
return t('components.dictationSetupSheet.loadFailed', 'Failed to load')
case 'download':
return t('components.dictationSetupSheet.downloadFailed', 'Download failed')
case 'select':
return t('components.dictationSetupSheet.selectFailed', 'Could not select model')
case 'update':
return t('components.dictationSetupSheet.updateFailed', 'Could not update')
}
}
function formatSize(bytes: number | null | undefined, t: Translate): string {
if (!bytes) {
return ''
}
return `${Math.round(bytes / 1_000_000)} MB`
return t('components.dictationSetupSheet.modelSize', '{{size}} MB', {
size: Math.round(bytes / 1_000_000).toLocaleString(getUiIntlLocale())
})
}
function modelMetadata(model: MobileSpeechModel, t: Translate): string {
const details = model.provider === 'openai' ? 'OpenAI API' : formatSize(model.sizeBytes, t)
if (isModelInFlight(model) && model.progress != null) {
return t('components.dictationSetupSheet.modelProgress', '{{details}} · {{progress}}%', {
details,
progress: Math.round(model.progress * 100).toLocaleString(getUiIntlLocale())
})
}
return model.status === 'extracting'
? t('components.dictationSetupSheet.modelExtracting', '{{details}} · extracting…', { details })
: details
}
// Configure paired computer’s dictation/model from phone; poll while download is in flight.
export function MobileDictationSetupSheet({ visible, client, onClose, onReady }: Props) {
useMobileColorScheme()
const t = useMobileTranslation()
const [setup, setSetup] = useState<MobileSpeechSetup | null>(null)
const [error, setError] = useState<string | null>(null)
const [error, setError] = useState<SetupError | null>(null)
const [busy, setBusy] = useState<string | null>(null)
const refresh = useCallback(async (): Promise<boolean | undefined> => {
if (!client) {
@@ -53,7 +90,7 @@ export function MobileDictationSetupSheet({ visible, client, onClose, onReady }:
setError(null)
return next.models.some(isModelInFlight)
} catch (err) {
setError(err instanceof Error ? err.message : 'Failed to load')
setError(err instanceof Error ? { message: err.message } : { kind: 'load' })
return undefined
}
}, [client])
@@ -84,7 +121,7 @@ export function MobileDictationSetupSheet({ visible, client, onClose, onReady }:
await refreshSetup()
} catch (err) {
triggerError()
setError(err instanceof Error ? err.message : 'Download failed')
setError(err instanceof Error ? { message: err.message } : { kind: 'download' })
} finally {
setBusy(null)
}
@@ -106,7 +143,7 @@ export function MobileDictationSetupSheet({ visible, client, onClose, onReady }:
onReady?.()
} catch (err) {
triggerError()
setError(err instanceof Error ? err.message : 'Could not select model')
setError(err instanceof Error ? { message: err.message } : { kind: 'select' })
} finally {
setBusy(null)
}
@@ -123,7 +160,7 @@ export function MobileDictationSetupSheet({ visible, client, onClose, onReady }:
try {
setSetup(await setDictationConfig(client, { enabled }))
} catch (err) {
setError(err instanceof Error ? err.message : 'Could not update')
setError(err instanceof Error ? { message: err.message } : { kind: 'update' })
}
},
[client]
@@ -133,9 +170,14 @@ export function MobileDictationSetupSheet({ visible, client, onClose, onReady }:
<BottomDrawer visible={visible} onClose={onClose}>
{/* BottomDrawer already scrolls above keyboard; nested capped ScrollView clipped lower controls. */}
<View>
<Text style={styles.heading}>Set up voice dictation</Text>
<Text style={styles.heading}>
{t('components.dictationSetupSheet.title', 'Set up voice dictation')}
</Text>
<Text style={styles.subtitle}>
Download or pick a model and enable dictation on the paired computer — all from here.
{t(
'components.dictationSetupSheet.description',
'Download or pick a model and enable dictation on the paired computer — all from here.'
)}
</Text>
{setup === null ? (
@@ -145,16 +187,20 @@ export function MobileDictationSetupSheet({ visible, client, onClose, onReady }:
) : (
<>
<View style={styles.enableRow}>
<Text style={styles.enableLabel}>Dictation enabled</Text>
<Text style={styles.enableLabel}>
{t('components.dictationSetupSheet.enabled', 'Dictation enabled')}
</Text>
<MobileSwitch
accessibilityLabel="Dictation enabled"
accessibilityLabel={t(
'components.dictationSetupSheet.enabled',
'Dictation enabled'
)}
value={setup.enabled === true}
onValueChange={(v) => void handleToggleEnabled(v)}
/>
</View>
{setup.models.map((model) => {
const inFlight = isModelInFlight(model)
const action = dictationModelRowAction(model, setup.selectedModelId)
const rowBusy = busy === model.id
return (
@@ -163,24 +209,21 @@ export function MobileDictationSetupSheet({ visible, client, onClose, onReady }:
<View style={styles.modelTitleRow}>
<Text style={styles.modelLabel}>{model.label}</Text>
{model.recommended ? (
<Text style={styles.recommended}>Recommended</Text>
<Text style={styles.recommended}>
{t('components.dictationSetupSheet.recommended', 'Recommended')}
</Text>
) : null}
</View>
<Text style={styles.modelMeta}>
{model.provider === 'openai' ? 'OpenAI API' : formatSize(model.sizeBytes)}
{inFlight && model.progress != null
? ` · ${Math.round(model.progress * 100)}%`
: model.status === 'extracting'
? ' · extracting…'
: ''}
</Text>
<Text style={styles.modelMeta}>{modelMetadata(model, t)}</Text>
</View>
{action === 'needs-api-key' ? (
<Text style={styles.modelStateText}>{cloudModelNeedsKey()}</Text>
) : action === 'in-use' ? (
<View style={styles.selectedTag}>
<Check size={14} color={colors.statusGreen} strokeWidth={2.4} />
<Text style={styles.selectedText}>In use</Text>
<Text style={styles.selectedText}>
{t('components.dictationSetupSheet.inUse', 'In use')}
</Text>
</View>
) : action === 'use' ? (
<Pressable
@@ -190,11 +233,17 @@ export function MobileDictationSetupSheet({ visible, client, onClose, onReady }:
]}
disabled={rowBusy}
accessibilityRole="button"
accessibilityLabel={`Use ${model.label}`}
accessibilityLabel={t(
'components.dictationSetupSheet.useModel',
'Use {{model}}',
{ model: model.label }
)}
accessibilityState={{ disabled: rowBusy }}
onPress={() => void handleUseModel(model)}
>
<Text style={styles.actionText}>Use</Text>
<Text style={styles.actionText}>
{t('components.dictationSetupSheet.use', 'Use')}
</Text>
</Pressable>
) : action === 'downloading' ? (
<ActivityIndicator size="small" color={colors.textSecondary} />
@@ -206,7 +255,11 @@ export function MobileDictationSetupSheet({ visible, client, onClose, onReady }:
]}
disabled={rowBusy}
accessibilityRole="button"
accessibilityLabel={`Download ${model.label}`}
accessibilityLabel={t(
'components.dictationSetupSheet.downloadModel',
'Download {{model}}',
{ model: model.label }
)}
accessibilityState={{ disabled: rowBusy }}
onPress={() => void handleDownload(model)}
>
@@ -215,7 +268,9 @@ export function MobileDictationSetupSheet({ visible, client, onClose, onReady }:
) : (
<>
<Download size={13} color={colors.textSecondary} strokeWidth={2.2} />
<Text style={styles.actionText}>Download</Text>
<Text style={styles.actionText}>
{t('components.dictationSetupSheet.download', 'Download')}
</Text>
</>
)}
</Pressable>
@@ -225,7 +280,7 @@ export function MobileDictationSetupSheet({ visible, client, onClose, onReady }:
})}
</>
)}
{error ? <Text style={styles.error}>{error}</Text> : null}
{error ? <Text style={styles.error}>{setupErrorMessage(error, t)}</Text> : null}
</View>
</BottomDrawer>
)
+50 -15
View File
@@ -4,6 +4,7 @@ import type { RefObject } from 'react'
import type { DiffComment } from '../../../src/shared/diff-comment-types'
import { colors } from '../theme/mobile-theme'
import { useMobileColorScheme } from '../theme/use-mobile-color-scheme'
import { useMobileTranslation } from '../i18n/use-mobile-translation'
import { MobileDiffReviewLine } from './MobileDiffReviewLine'
import type {
ReviewDiffLine,
@@ -41,23 +42,44 @@ export function MobileDiffReviewBody({
onRetry
}: Props) {
useMobileColorScheme()
const t = useMobileTranslation()
if (screenState.kind === 'loading') {
return <CenteredState text="Loading review..." busy />
return (
<CenteredState
text={t('components.diffReviewBody.loadingReview', 'Loading review...')}
busy
/>
)
}
if (screenState.kind === 'error' || screenState.kind === 'unavailable') {
return (
<CenteredState
title={screenState.kind === 'unavailable' ? 'Review Unavailable' : 'Unable to Load Review'}
title={
screenState.kind === 'unavailable'
? t('components.diffReviewBody.reviewUnavailable', 'Review Unavailable')
: t('components.diffReviewBody.loadFailed', 'Unable to Load Review')
}
text={screenState.message}
onRetry={onRetry}
/>
)
}
if (filteredCount === 0) {
return <CenteredState title="No Reviewable Changes" text="Try a different review filter." />
return (
<CenteredState
title={t('components.diffReviewBody.noReviewableChanges', 'No Reviewable Changes')}
text={t('components.diffReviewBody.tryDifferentFilter', 'Try a different review filter.')}
/>
)
}
if (diffState.kind === 'loading') {
return <CenteredState text="Loading diff..." busy muted />
return (
<CenteredState
text={t('components.diffReviewBody.loadingDiff', 'Loading diff...')}
busy
muted
/>
)
}
if (diffState.kind !== 'ready') {
return <DiffUnavailableState diffState={diffState} onRetry={onRetry} />
@@ -93,7 +115,9 @@ export function MobileDiffReviewBody({
}}
ListFooterComponent={
diffState.truncated ? (
<Text style={styles.truncatedText}>Diff truncated for mobile preview.</Text>
<Text style={styles.truncatedText}>
{t('components.diffReviewBody.diffTruncated', 'Diff truncated for mobile preview.')}
</Text>
) : null
}
/>
@@ -107,24 +131,34 @@ function DiffUnavailableState({
diffState: ReviewDiffState
onRetry: (() => void) | undefined
}) {
const t = useMobileTranslation()
const title =
diffState.kind === 'binary'
? 'Binary Diff'
? t('components.diffReviewBody.binaryDiff', 'Binary Diff')
: diffState.kind === 'too-large'
? 'Diff Too Large'
? t('components.diffReviewBody.diffTooLarge', 'Diff Too Large')
: diffState.kind === 'deleted'
? 'Deleted File'
: 'Diff Unavailable'
? t('components.diffReviewBody.deletedFile', 'Deleted File')
: t('components.diffReviewBody.diffUnavailable', 'Diff Unavailable')
const text =
diffState.kind === 'binary'
? 'This file cannot be rendered as text on mobile.'
? t(
'components.diffReviewBody.binaryMessage',
'This file cannot be rendered as text on mobile.'
)
: diffState.kind === 'too-large'
? 'This diff is too large for the mobile preview.'
? t(
'components.diffReviewBody.tooLargeMessage',
'This diff is too large for the mobile preview.'
)
: diffState.kind === 'deleted'
? 'This file was deleted. Add a file note or mark it reviewed.'
? t(
'components.diffReviewBody.deletedMessage',
'This file was deleted. Add a file note or mark it reviewed.'
)
: diffState.kind === 'error'
? diffState.message
: 'Select a file to review.'
: t('components.diffReviewBody.selectFile', 'Select a file to review.')
return <CenteredState title={title} text={text} onRetry={onRetry} />
}
@@ -142,6 +176,7 @@ function CenteredState({
onRetry?: () => void
}) {
useMobileColorScheme()
const t = useMobileTranslation()
return (
<View style={styles.state}>
{busy ? (
@@ -154,10 +189,10 @@ function CenteredState({
style={({ pressed }) => [styles.retryButton, pressed && styles.buttonPressed]}
onPress={onRetry}
accessibilityRole="button"
accessibilityLabel="Retry loading review"
accessibilityLabel={t('components.diffReviewBody.retryLabel', 'Retry loading review')}
>
<RefreshCw size={14} color={colors.textPrimary} strokeWidth={2.2} />
<Text style={styles.retryText}>Retry</Text>
<Text style={styles.retryText}>{t('common.retry', 'Retry')}</Text>
</Pressable>
) : null}
</View>
+125 -50
View File
@@ -4,16 +4,13 @@ import { Check, Copy, FileText, Plus, Send, Trash2, X } from 'lucide-react-nativ
import type { DiffComment } from '../../../src/shared/diff-comment-types'
import { colors } from '../theme/mobile-theme'
import { useMobileColorScheme } from '../theme/use-mobile-color-scheme'
import type { Translate } from '../i18n/mobile-i18n'
import { useMobileTranslation } from '../i18n/use-mobile-translation'
import type { ActionSheetAction } from './ActionSheetModal'
import { ActionSheetContent } from './ActionSheetModal'
import { ConfirmContent } from './ConfirmModal'
import { KeyedBottomDrawer } from './keyed-bottom-drawer'
import { AGENT_LAUNCH_STATUS_UNREADABLE_MESSAGE } from '../session/mobile-existing-agent-launch'
import {
mobileReviewCountLabel,
type ComposerState,
type SendSheetState
} from '../session/mobile-diff-review-screen-model'
import type { ComposerState, SendSheetState } from '../session/mobile-diff-review-screen-model'
import { reviewSheetKey, type ReviewSheet } from '../session/mobile-diff-review-sheets'
import type { useMobileDiffReviewController } from '../session/use-mobile-diff-review-controller'
import { mobileDiffReviewStyles as styles } from './mobile-diff-review-screen-styles'
@@ -38,6 +35,7 @@ export function MobileDiffReviewDrawers({ controller }: Props) {
}
function ReviewSheetContent({ controller, sheet }: Props & { sheet: ReviewSheet }) {
const t = useMobileTranslation()
switch (sheet.kind) {
case 'actions':
return <ReviewActionsContent controller={controller} />
@@ -46,9 +44,13 @@ function ReviewSheetContent({ controller, sheet }: Props & { sheet: ReviewSheet
case 'discard':
return (
<ConfirmContent
title="Discard File"
message={`Discard changes to "${sheet.target.filePath}"? This cannot be undone.`}
confirmLabel="Discard"
title={t('components.diffReviewDrawers.discardTitle', 'Discard File')}
message={t(
'components.diffReviewDrawers.discardMessage',
'Discard changes to "{{filePath}}"? This cannot be undone.',
{ filePath: sheet.target.filePath }
)}
confirmLabel={t('components.diffReviewDrawers.discard', 'Discard')}
destructive
onConfirm={() => {
controller.closeSheet('discard')
@@ -65,13 +67,25 @@ function ReviewSheetContent({ controller, sheet }: Props & { sheet: ReviewSheet
}
function ReviewActionsContent({ controller }: Props) {
const t = useMobileTranslation()
const overflowActions = useOverflowActions(controller)
const count = controller.reviewedUnstagedCount
return (
<ActionSheetContent
title="Review Actions"
title={t('components.diffReviewDrawers.actionsTitle', 'Review Actions')}
message={
controller.reviewedUnstagedCount > 0
? `${controller.reviewedUnstagedCount} reviewed unstaged files can be staged`
count > 0
? count === 1
? t(
'components.diffReviewDrawers.stageableFiles_one',
'{{count}} reviewed unstaged files can be staged',
{ count }
)
: t(
'components.diffReviewDrawers.stageableFiles_other',
'{{count}} reviewed unstaged files can be staged',
{ count }
)
: undefined
}
actions={overflowActions}
@@ -81,11 +95,12 @@ function ReviewActionsContent({ controller }: Props) {
}
function SendNotesContent({ controller, load }: Props & { load: SendSheetState }) {
const t = useMobileTranslation()
const sendActions = useSendActions(controller, load)
return (
<ActionSheetContent
title="Send Notes"
message={sendSheetMessage(controller, load)}
title={t('components.diffReviewDrawers.sendNotes', 'Send Notes')}
message={sendSheetMessage(t, controller, load)}
actions={sendActions}
onClose={() => controller.closeSheet('send')}
/>
@@ -96,12 +111,16 @@ function useSendActions(
controller: ReturnType<typeof useMobileDiffReviewController>,
load: SendSheetState
) {
const t = useMobileTranslation()
return useMemo<ActionSheetAction[]>(() => {
const comments = controller.unsentComments
const terminalActions =
load.kind === 'ready' || load.kind === 'error'
? load.terminals.map((terminal) => ({
label: `${terminal.title || 'Terminal'} (${terminal.terminal.slice(0, 6)})`,
label: t('components.diffReviewDrawers.terminalLabel', '{{title}} ({{id}})', {
title: terminal.title || t('components.diffReviewDrawers.terminal', 'Terminal'),
id: terminal.terminal.slice(0, 6)
}),
icon: Send,
disabled: comments.length === 0,
skipAutoClose: true,
@@ -111,47 +130,55 @@ function useSendActions(
return [
...terminalActions,
{
label: 'New Agent Session',
label: t('components.diffReviewDrawers.newAgentSession', 'New Agent Session'),
icon: Plus,
disabled: comments.length === 0 || controller.agentLaunchAvailability !== 'available',
...(controller.agentLaunchAvailability === 'update-required'
? { hint: 'Update Orca on your computer' }
? {
hint: t('components.diffReviewDrawers.updateRequired', 'Update Orca on your computer')
}
: controller.agentLaunchAvailability === 'unverified'
? { hint: AGENT_LAUNCH_STATUS_UNREADABLE_MESSAGE }
? {
hint: t(
'components.diffReviewDrawers.statusUnreadable',
"Could not read this host's status. Retrying…"
)
}
: {}),
skipAutoClose: true,
onPress: () => void controller.createTerminalAndSend(comments)
},
{
label: 'Copy Notes',
label: t('components.diffReviewDrawers.copyNotes', 'Copy Notes'),
icon: Copy,
disabled:
controller.screenState.kind !== 'ready' || controller.screenState.comments.length === 0,
onPress: () => void controller.copyNotes()
}
]
}, [controller, load])
}, [controller, load, t])
}
function useOverflowActions(controller: ReturnType<typeof useMobileDiffReviewController>) {
const t = useMobileTranslation()
const sessionTabsAvailable = useWorkspaceOnPairedHost()
return useMemo<ActionSheetAction[]>(
() => [
{
label: 'Copy Notes',
label: t('components.diffReviewDrawers.copyNotes', 'Copy Notes'),
icon: Copy,
disabled:
controller.screenState.kind !== 'ready' || controller.screenState.comments.length === 0,
onPress: () => void controller.copyNotes()
},
{
label: 'Send Unsent Notes',
label: t('components.diffReviewDrawers.sendUnsentNotes', 'Send Unsent Notes'),
icon: Send,
disabled: controller.unsentComments.length === 0,
onPress: () => void controller.openSendSheet()
},
{
label: 'Clear Sent Notes',
label: t('components.diffReviewDrawers.clearSentNotes', 'Clear Sent Notes'),
icon: Trash2,
disabled:
controller.screenState.kind !== 'ready' ||
@@ -160,14 +187,14 @@ function useOverflowActions(controller: ReturnType<typeof useMobileDiffReviewCon
onPress: () => void controller.clearSentNotes()
},
{
label: 'Stage Reviewed Files',
label: t('components.diffReviewDrawers.stageReviewedFiles', 'Stage Reviewed Files'),
icon: Check,
disabled: controller.reviewedUnstagedCount === 0 || controller.busyAction !== null,
skipAutoClose: true,
onPress: () => void controller.stageReviewedFiles()
},
{
label: 'Mark Unreviewed',
label: t('components.diffReviewDrawers.markUnreviewed', 'Mark Unreviewed'),
icon: X,
disabled:
controller.screenState.kind !== 'ready' ||
@@ -179,7 +206,7 @@ function useOverflowActions(controller: ReturnType<typeof useMobileDiffReviewCon
...(sessionTabsAvailable
? [
{
label: 'Open in Session',
label: t('components.diffReviewDrawers.openInSession', 'Open in Session'),
icon: FileText,
disabled: !controller.currentItem || controller.currentItem.scope === 'branch',
onPress: () => void controller.openInSession()
@@ -187,41 +214,50 @@ function useOverflowActions(controller: ReturnType<typeof useMobileDiffReviewCon
]
: [])
],
[controller, sessionTabsAvailable]
[controller, sessionTabsAvailable, t]
)
}
function sendSheetMessage(
t: Translate,
controller: ReturnType<typeof useMobileDiffReviewController>,
load: SendSheetState
): string | undefined {
const count = controller.unsentComments.length
return load.kind === 'loading'
? 'Loading agent sessions...'
? t('components.diffReviewDrawers.loadingSessions', 'Loading agent sessions...')
: load.kind === 'error'
? load.message
: `${controller.unsentComments.length} unsent notes`
: count === 1
? t('components.diffReviewDrawers.unsentNotes_one', '{{count}} unsent notes', { count })
: t('components.diffReviewDrawers.unsentNotes_other', '{{count}} unsent notes', { count })
}
function NoteComposerContent({ controller, composer }: Props & { composer: ComposerState }) {
useMobileColorScheme()
const t = useMobileTranslation()
return (
<KeyboardAvoidingView behavior={hostOs() === 'ios' ? 'padding' : undefined}>
<View style={styles.composerHeader}>
<View>
<Text style={styles.drawerTitle}>
{composer.mode === 'edit' ? 'Edit Note' : 'Add Note'}
{composer.mode === 'edit'
? t('components.diffReviewDrawers.editNote', 'Edit Note')
: t('components.diffReviewDrawers.addNote', 'Add Note')}
</Text>
<Text style={styles.drawerSubtitle}>
{composer.mode === 'create' && composer.lineNumber > 0
? `Line ${composer.lineNumber}`
: 'File note'}
? t('components.diffReviewDrawers.lineNumber', 'Line {{lineNumber}}', {
lineNumber: composer.lineNumber
})
: t('components.diffReviewDrawers.fileNote', 'File note')}
</Text>
</View>
<Pressable
style={({ pressed }) => [styles.iconButton, pressed && styles.iconButtonPressed]}
onPress={controller.closeComposer}
accessibilityRole="button"
accessibilityLabel="Cancel note"
accessibilityLabel={t('components.diffReviewDrawers.cancelNote', 'Cancel note')}
>
<X size={18} color={colors.textPrimary} strokeWidth={2.2} />
</Pressable>
@@ -232,9 +268,9 @@ function NoteComposerContent({ controller, composer }: Props & { composer: Compo
onChangeText={controller.setComposerBody}
multiline
autoFocus
placeholder="Review note"
placeholder={t('components.diffReviewDrawers.reviewNote', 'Review note')}
placeholderTextColor={colors.textMuted}
accessibilityLabel={composerLabel(composer)}
accessibilityLabel={composerLabel(t, composer)}
/>
<View style={styles.drawerButtonRow}>
{composer.mode === 'edit' ? <DeleteNoteButton onPress={controller.deleteComment} /> : null}
@@ -245,24 +281,28 @@ function NoteComposerContent({ controller, composer }: Props & { composer: Compo
}
function composerLabel(
t: Translate,
composer: { mode: 'create'; lineNumber: number } | { mode: 'edit'; comment: DiffComment } | null
): string {
return composer?.mode === 'create' && composer.lineNumber > 0
? `Save note on line ${composer.lineNumber}`
: 'Review note'
? t('components.diffReviewDrawers.saveLineNote', 'Save note on line {{lineNumber}}', {
lineNumber: composer.lineNumber
})
: t('components.diffReviewDrawers.reviewNote', 'Review note')
}
function DeleteNoteButton({ onPress }: { onPress: () => Promise<void> }) {
useMobileColorScheme()
const t = useMobileTranslation()
return (
<Pressable
style={({ pressed }) => [styles.secondaryButton, pressed && styles.buttonPressed]}
onPress={() => void onPress()}
accessibilityRole="button"
accessibilityLabel="Delete note"
accessibilityLabel={t('components.diffReviewDrawers.deleteNote', 'Delete note')}
>
<Trash2 size={14} color={colors.statusRed} strokeWidth={2.2} />
<Text style={styles.destructiveText}>Delete</Text>
<Text style={styles.destructiveText}>{t('common.delete', 'Delete')}</Text>
</Pressable>
)
}
@@ -275,6 +315,7 @@ function SaveNoteButton({
composer: ReturnType<typeof useMobileDiffReviewController>['composer']
}) {
useMobileColorScheme()
const t = useMobileTranslation()
const disabled = controller.composerBody.trim().length === 0
return (
<Pressable
@@ -286,24 +327,48 @@ function SaveNoteButton({
disabled={disabled}
onPress={() => void controller.saveComposer()}
accessibilityRole="button"
accessibilityLabel={composerLabel(composer)}
accessibilityLabel={composerLabel(t, composer)}
>
<Check size={14} color={colors.bgBase} strokeWidth={2.2} />
<Text style={styles.primaryButtonText}>Save</Text>
<Text style={styles.primaryButtonText}>{t('common.save', 'Save')}</Text>
</Pressable>
)
}
function CompletionContent({ controller }: Props) {
useMobileColorScheme()
const noteCount =
controller.screenState.kind === 'ready' ? controller.screenState.comments.length : 0
const t = useMobileTranslation()
const fileCount = controller.queue.length
const count = controller.screenState.kind === 'ready' ? controller.screenState.comments.length : 0
return (
<>
<Text style={styles.drawerTitle}>Review Complete</Text>
<Text style={styles.drawerTitle}>
{t('components.diffReviewDrawers.completeTitle', 'Review Complete')}
</Text>
<Text style={styles.drawerSubtitle}>
{mobileReviewCountLabel(controller.queue.length, 'file', 'files')} reviewed,{' '}
{mobileReviewCountLabel(noteCount, 'note', 'notes')}
{fileCount === 1
? count === 1
? t(
'components.diffReviewDrawers.fileSummary_one',
'{{fileCount}} file reviewed, {{count}} note',
{ fileCount, count }
)
: t(
'components.diffReviewDrawers.fileSummary_other',
'{{fileCount}} file reviewed, {{count}} notes',
{ fileCount, count }
)
: count === 1
? t(
'components.diffReviewDrawers.filesSummary_one',
'{{fileCount}} files reviewed, {{count}} note',
{ fileCount, count }
)
: t(
'components.diffReviewDrawers.filesSummary_other',
'{{fileCount}} files reviewed, {{count}} notes',
{ fileCount, count }
)}
</Text>
<View style={styles.drawerButtonRow}>
<Pressable
@@ -311,20 +376,30 @@ function CompletionContent({ controller }: Props) {
disabled={controller.reviewedUnstagedCount === 0}
onPress={() => void controller.stageReviewedFiles()}
accessibilityRole="button"
accessibilityLabel="Stage reviewed files"
accessibilityLabel={t(
'components.diffReviewDrawers.stageReviewedLabel',
'Stage reviewed files'
)}
>
<Check size={14} color={colors.textSecondary} strokeWidth={2.2} />
<Text style={styles.secondaryButtonText}>Stage Reviewed</Text>
<Text style={styles.secondaryButtonText}>
{t('components.diffReviewDrawers.stageReviewed', 'Stage Reviewed')}
</Text>
</Pressable>
<Pressable
style={({ pressed }) => [styles.primaryButton, pressed && styles.buttonPressed]}
disabled={controller.unsentComments.length === 0}
onPress={() => void controller.openSendSheet()}
accessibilityRole="button"
accessibilityLabel="Send notes to agent"
accessibilityLabel={t(
'components.diffReviewDrawers.sendNotesLabel',
'Send notes to agent'
)}
>
<Send size={14} color={colors.bgBase} strokeWidth={2.2} />
<Text style={styles.primaryButtonText}>Send Notes</Text>
<Text style={styles.primaryButtonText}>
{t('components.diffReviewDrawers.sendNotes', 'Send Notes')}
</Text>
</Pressable>
</View>
</>
@@ -3,13 +3,11 @@ import { ArrowDown, ArrowUp } from 'lucide-react-native'
import type { DiffComment } from '../../../src/shared/diff-comment-types'
import { colors } from '../theme/mobile-theme'
import { useMobileColorScheme } from '../theme/use-mobile-color-scheme'
import type { Translate } from '../i18n/mobile-i18n'
import { useMobileTranslation } from '../i18n/use-mobile-translation'
import type { MobileDiffReviewQueueItem } from '../session/mobile-diff-review-queue'
import { STATUS_LABELS } from '../../../src/shared/git-status-labels'
import {
mobileReviewCountLabel,
mobileReviewScopeLabel,
type ReviewDiffState
} from '../session/mobile-diff-review-screen-model'
import type { ReviewDiffState } from '../session/mobile-diff-review-screen-model'
import { mobileDiffReviewStyles as styles } from './mobile-diff-review-screen-styles'
type Props = {
@@ -40,6 +38,22 @@ function statusColor(status: MobileDiffReviewQueueItem['status']): string {
}
}
function fileScopeLabel(t: Translate, item: MobileDiffReviewQueueItem): string {
if (item.oldPath) {
const options = { oldPath: item.oldPath }
return item.scope === 'branch'
? t('components.diffReviewFileSummary.branchFrom', 'Branch from {{oldPath}}', options)
: item.scope === 'staged'
? t('components.diffReviewFileSummary.stagedFrom', 'Staged from {{oldPath}}', options)
: t('components.diffReviewFileSummary.unstagedFrom', 'Unstaged from {{oldPath}}', options)
}
return item.scope === 'branch'
? t('components.diffReviewFileSummary.branch', 'Branch')
: item.scope === 'staged'
? t('components.diffReviewFileSummary.staged', 'Staged')
: t('components.diffReviewFileSummary.unstaged', 'Unstaged')
}
export function MobileDiffReviewFileSummary({
currentIndex,
diffState,
@@ -51,6 +65,8 @@ export function MobileDiffReviewFileSummary({
onJumpHunk
}: Props) {
useMobileColorScheme()
const t = useMobileTranslation()
const count = item.noteCount
const hunkDisabled = diffState.kind !== 'ready' || diffState.hunks.length === 0
const badgeColor = statusColor(item.status)
return (
@@ -66,8 +82,7 @@ export function MobileDiffReviewFileSummary({
{item.filePath}
</Text>
<Text style={styles.fileMeta} numberOfLines={1}>
{mobileReviewScopeLabel(item)}
{item.oldPath ? ` from ${item.oldPath}` : ''}
{fileScopeLabel(t, item)}
</Text>
</View>
</View>
@@ -75,15 +90,29 @@ export function MobileDiffReviewFileSummary({
<Text style={styles.fileMeta}>
{currentIndex + 1}/{filteredCount}
</Text>
{item.isReviewed ? <Text style={styles.reviewedPill}>Reviewed</Text> : null}
{item.changedSinceReview ? <Text style={styles.stalePill}>Changed</Text> : null}
{item.isReviewed ? (
<Text style={styles.reviewedPill}>
{t('components.diffReviewFileSummary.reviewed', 'Reviewed')}
</Text>
) : null}
{item.changedSinceReview ? (
<Text style={styles.stalePill}>
{t('components.diffReviewFileSummary.changed', 'Changed')}
</Text>
) : null}
{item.noteCount > 0 ? (
<Text style={styles.fileMeta}>
{mobileReviewCountLabel(item.noteCount, 'note', 'notes')}
{count === 1
? t('components.diffReviewFileSummary.notes_one', '{{count}} note', { count })
: t('components.diffReviewFileSummary.notes_other', '{{count}} notes', { count })}
</Text>
) : null}
{item.staleNoteCount > 0 ? (
<Text style={styles.staleText}>{item.staleNoteCount} stale</Text>
<Text style={styles.staleText}>
{t('components.diffReviewFileSummary.staleCount', '{{count}} stale', {
count: item.staleNoteCount
})}
</Text>
) : null}
</View>
{fileNotes.length > 0 ? (
@@ -94,12 +123,19 @@ export function MobileDiffReviewFileSummary({
style={({ pressed }) => [styles.fileNote, pressed && styles.fileNotePressed]}
onPress={() => onEditNote(note)}
accessibilityRole="button"
accessibilityLabel="Edit file note"
accessibilityLabel={t(
'components.diffReviewFileSummary.editFileNote',
'Edit file note'
)}
>
<Text style={styles.fileNoteText} numberOfLines={2}>
{note.body}
</Text>
{staleCommentIds.has(note.id) ? <Text style={styles.staleText}>Stale</Text> : null}
{staleCommentIds.has(note.id) ? (
<Text style={styles.staleText}>
{t('components.diffReviewFileSummary.stale', 'Stale')}
</Text>
) : null}
</Pressable>
))}
</View>
@@ -110,20 +146,24 @@ export function MobileDiffReviewFileSummary({
disabled={hunkDisabled}
onPress={() => onJumpHunk('previous')}
accessibilityRole="button"
accessibilityLabel="Previous hunk"
accessibilityLabel={t('components.diffReviewFileSummary.previousHunk', 'Previous hunk')}
>
<ArrowUp size={14} color={colors.textSecondary} strokeWidth={2.2} />
<Text style={styles.hunkButtonText}>Hunk</Text>
<Text style={styles.hunkButtonText}>
{t('components.diffReviewFileSummary.hunk', 'Hunk')}
</Text>
</Pressable>
<Pressable
style={({ pressed }) => [styles.hunkButton, pressed && styles.hunkButtonPressed]}
disabled={hunkDisabled}
onPress={() => onJumpHunk('next')}
accessibilityRole="button"
accessibilityLabel="Next hunk"
accessibilityLabel={t('components.diffReviewFileSummary.nextHunk', 'Next hunk')}
>
<ArrowDown size={14} color={colors.textSecondary} strokeWidth={2.2} />
<Text style={styles.hunkButtonText}>Hunk</Text>
<Text style={styles.hunkButtonText}>
{t('components.diffReviewFileSummary.hunk', 'Hunk')}
</Text>
</Pressable>
</View>
</View>
@@ -9,6 +9,7 @@ import {
Undo2
} from 'lucide-react-native'
import { useSafeAreaInsets } from 'react-native-safe-area-context'
import { useMobileTranslation } from '../i18n/use-mobile-translation'
import type { MobileDiffReviewQueueItem } from '../session/mobile-diff-review-queue'
import type { GitMutationMethod } from '../session/mobile-diff-review-screen-model'
import { colors, spacing } from '../theme/mobile-theme'
@@ -34,6 +35,7 @@ export function MobileDiffReviewFooter({
onMarkReviewed,
onMoveFile
}: Props) {
const t = useMobileTranslation()
useMobileColorScheme()
const insets = useSafeAreaInsets()
return (
@@ -45,10 +47,12 @@ export function MobileDiffReviewFooter({
disabled={busyAction !== null}
onPress={() => onGitMutation('git.stage', item)}
accessibilityRole="button"
accessibilityLabel="Stage file"
accessibilityLabel={t('components.diffReviewFooter.stageFile', 'Stage file')}
>
<Plus size={14} color={colors.textSecondary} strokeWidth={2.2} />
<Text style={styles.secondaryButtonText}>Stage</Text>
<Text style={styles.secondaryButtonText}>
{t('components.diffReviewFooter.stage', 'Stage')}
</Text>
</Pressable>
) : null}
{item.canUnstage ? (
@@ -57,10 +61,12 @@ export function MobileDiffReviewFooter({
disabled={busyAction !== null}
onPress={() => onGitMutation('git.unstage', item)}
accessibilityRole="button"
accessibilityLabel="Unstage file"
accessibilityLabel={t('components.diffReviewFooter.unstageFile', 'Unstage file')}
>
<Undo2 size={14} color={colors.textSecondary} strokeWidth={2.2} />
<Text style={styles.secondaryButtonText}>Unstage</Text>
<Text style={styles.secondaryButtonText}>
{t('components.diffReviewFooter.unstage', 'Unstage')}
</Text>
</Pressable>
) : null}
{item.canDiscard ? (
@@ -69,10 +75,12 @@ export function MobileDiffReviewFooter({
disabled={busyAction !== null}
onPress={() => onDiscard(item)}
accessibilityRole="button"
accessibilityLabel="Discard file"
accessibilityLabel={t('components.diffReviewFooter.discardFile', 'Discard file')}
>
<Trash2 size={14} color={colors.statusRed} strokeWidth={2.2} />
<Text style={styles.destructiveText}>Discard</Text>
<Text style={styles.destructiveText}>
{t('components.diffReviewFooter.discard', 'Discard')}
</Text>
</Pressable>
) : null}
</View>
@@ -81,7 +89,7 @@ export function MobileDiffReviewFooter({
style={({ pressed }) => [styles.navButton, pressed && styles.buttonPressed]}
onPress={() => onMoveFile('previous')}
accessibilityRole="button"
accessibilityLabel="Previous file"
accessibilityLabel={t('components.diffReviewFooter.previousFile', 'Previous file')}
>
<ChevronLeft size={17} color={colors.textPrimary} strokeWidth={2.2} />
</Pressable>
@@ -89,10 +97,12 @@ export function MobileDiffReviewFooter({
style={({ pressed }) => [styles.footerButton, pressed && styles.buttonPressed]}
onPress={onAddFileNote}
accessibilityRole="button"
accessibilityLabel="Add file note"
accessibilityLabel={t('components.diffReviewFooter.addFileNote', 'Add file note')}
>
<FileText size={14} color={colors.textSecondary} strokeWidth={2.2} />
<Text style={styles.footerButtonText}>Note</Text>
<Text style={styles.footerButtonText}>
{t('components.diffReviewFooter.note', 'Note')}
</Text>
</Pressable>
<Pressable
style={({ pressed }) => [
@@ -102,18 +112,23 @@ export function MobileDiffReviewFooter({
]}
onPress={onMarkReviewed}
accessibilityRole="button"
accessibilityLabel="Mark file reviewed"
accessibilityLabel={t(
'components.diffReviewFooter.markFileReviewed',
'Mark file reviewed'
)}
>
<Check size={14} color={colors.bgBase} strokeWidth={2.2} />
<Text style={styles.primaryButtonText}>
{item.isReviewed ? 'Reviewed' : 'Mark Reviewed'}
{item.isReviewed
? t('components.diffReviewFooter.reviewed', 'Reviewed')
: t('components.diffReviewFooter.markReviewed', 'Mark Reviewed')}
</Text>
</Pressable>
<Pressable
style={({ pressed }) => [styles.navButton, pressed && styles.buttonPressed]}
onPress={() => onMoveFile('next')}
accessibilityRole="button"
accessibilityLabel="Next file"
accessibilityLabel={t('components.diffReviewFooter.nextFile', 'Next file')}
>
<ChevronRight size={17} color={colors.textPrimary} strokeWidth={2.2} />
</Pressable>
@@ -1,9 +1,10 @@
import { FlatList, Pressable, Text, View } from 'react-native'
import { ChevronLeft, ListChecks, MoreHorizontal } from 'lucide-react-native'
import { useMobileTranslation } from '../i18n/use-mobile-translation'
import { colors } from '../theme/mobile-theme'
import { useMobileColorScheme } from '../theme/use-mobile-color-scheme'
import type { MobileDiffReviewQueueFilter } from '../session/mobile-diff-review-queue'
import { REVIEW_FILTERS, mobileReviewCountLabel } from '../session/mobile-diff-review-screen-model'
import { REVIEW_FILTERS } from '../session/mobile-diff-review-screen-model'
import { shouldShowTrigger } from './mobile-pr-sidebar-presentation'
import { mobileDiffReviewStyles as styles } from './mobile-diff-review-screen-styles'
@@ -36,7 +37,50 @@ export function MobileDiffReviewHeader({
onOpenPRSidebar,
onSelectFilter
}: Props) {
const t = useMobileTranslation()
const colorScheme = useMobileColorScheme()
const count = unsentCount
const filters: Record<
MobileDiffReviewQueueFilter,
{ label: string; accessibilityLabel: string }
> = {
all: {
label: t('components.diffReviewHeader.filterAll', 'All'),
accessibilityLabel: t('components.diffReviewHeader.showAllFiles', 'Show all review files')
},
unreviewed: {
label: t('components.diffReviewHeader.filterUnreviewed', 'Unreviewed'),
accessibilityLabel: t(
'components.diffReviewHeader.showUnreviewedFiles',
'Show unreviewed review files'
)
},
notes: {
label: t('components.diffReviewHeader.filterNotes', 'Notes'),
accessibilityLabel: t('components.diffReviewHeader.showNotesFiles', 'Show notes review files')
},
unstaged: {
label: t('components.diffReviewHeader.filterUnstaged', 'Unstaged'),
accessibilityLabel: t(
'components.diffReviewHeader.showUnstagedFiles',
'Show unstaged review files'
)
},
staged: {
label: t('components.diffReviewHeader.filterStaged', 'Staged'),
accessibilityLabel: t(
'components.diffReviewHeader.showStagedFiles',
'Show staged review files'
)
},
branch: {
label: t('components.diffReviewHeader.filterBranch', 'Branch'),
accessibilityLabel: t(
'components.diffReviewHeader.showBranchFiles',
'Show branch review files'
)
}
}
// Show PR icon on narrow/overlay GitHub repos; wide docked sidebar needs no trigger.
const showPRTrigger = shouldShowTrigger({
isGithubRepo: prSidebarIsGithubRepo,
@@ -50,13 +94,13 @@ export function MobileDiffReviewHeader({
style={({ pressed }) => [styles.iconButton, pressed && styles.iconButtonPressed]}
onPress={onBack}
accessibilityRole="button"
accessibilityLabel="Back"
accessibilityLabel={t('common.back', 'Back')}
>
<ChevronLeft size={19} color={colors.textPrimary} strokeWidth={2.2} />
</Pressable>
<View style={styles.titleBlock}>
<Text style={styles.title} numberOfLines={1}>
Changes
{t('components.diffReviewHeader.title', 'Changes')}
</Text>
<Text style={styles.subtitle} numberOfLines={1}>
{worktreeLabel}
@@ -67,7 +111,10 @@ export function MobileDiffReviewHeader({
style={({ pressed }) => [styles.iconButton, pressed && styles.iconButtonPressed]}
onPress={onOpenPRSidebar}
accessibilityRole="button"
accessibilityLabel="Open pull request sidebar"
accessibilityLabel={t(
'components.diffReviewHeader.openPullRequestSidebar',
'Open pull request sidebar'
)}
>
<ListChecks size={19} color={colors.textPrimary} strokeWidth={2.2} />
</Pressable>
@@ -76,17 +123,31 @@ export function MobileDiffReviewHeader({
style={({ pressed }) => [styles.iconButton, pressed && styles.iconButtonPressed]}
onPress={onOpenActions}
accessibilityRole="button"
accessibilityLabel="Open review actions"
accessibilityLabel={t(
'components.diffReviewHeader.openReviewActions',
'Open review actions'
)}
>
<MoreHorizontal size={19} color={colors.textPrimary} strokeWidth={2.2} />
</Pressable>
</View>
<View style={styles.progressRow}>
<Text style={styles.progressText}>
{reviewedCount}/{queueLength} reviewed
{t(
'components.diffReviewHeader.reviewedProgress',
'{{reviewedCount}}/{{queueLength}} reviewed',
{
reviewedCount,
queueLength
}
)}
</Text>
<Text style={styles.progressText}>
{mobileReviewCountLabel(unsentCount, 'unsent note', 'unsent notes')}
{count === 1
? t('components.diffReviewHeader.unsentNotes_one', '{{count}} unsent note', { count })
: t('components.diffReviewHeader.unsentNotes_other', '{{count}} unsent notes', {
count
})}
</Text>
</View>
<FlatList
@@ -94,7 +155,7 @@ export function MobileDiffReviewHeader({
horizontal
showsHorizontalScrollIndicator={false}
keyExtractor={(item) => item}
extraData={colorScheme}
extraData={[colorScheme, t]}
contentContainerStyle={styles.filterRow}
renderItem={({ item }) => (
<Pressable
@@ -106,10 +167,10 @@ export function MobileDiffReviewHeader({
onPress={() => onSelectFilter(item)}
accessibilityRole="button"
accessibilityState={{ selected: filter === item }}
accessibilityLabel={`Show ${item} review files`}
accessibilityLabel={filters[item].accessibilityLabel}
>
<Text style={[styles.filterText, filter === item && styles.filterTextActive]}>
{item === 'all' ? 'All' : item[0]?.toUpperCase() + item.slice(1)}
{filters[item].label}
</Text>
</Pressable>
)}
+29 -7
View File
@@ -1,5 +1,7 @@
import { Pressable, StyleSheet, Text, View } from 'react-native'
import { MessageSquare } from 'lucide-react-native'
import type { Translate } from '../i18n/mobile-i18n'
import { useMobileTranslation } from '../i18n/use-mobile-translation'
import type { DiffComment } from '../../../src/shared/diff-comment-types'
import type { MobileDiffLine } from '../session/mobile-diff-lines'
import type { MobileHighlightedDiffLine } from '../session/mobile-file-syntax'
@@ -18,10 +20,21 @@ type Props = {
onEditNote: (comment: DiffComment) => void
}
function accessibilityLabelForLine(line: MobileDiffLine): string {
function accessibilityLabelForLine(line: MobileDiffLine, t: Translate): string {
const number = mobileDiffLineNumber(line)
const label = line.kind === 'add' ? 'Added' : line.kind === 'delete' ? 'Deleted' : 'Context'
return number ? `${label} line ${number}` : `${label} line`
if (line.kind === 'add') {
return number
? t('components.diffReviewLine.addedLineNumber', 'Added line {{number}}', { number })
: t('components.diffReviewLine.addedLine', 'Added line')
}
if (line.kind === 'delete') {
return number
? t('components.diffReviewLine.deletedLineNumber', 'Deleted line {{number}}', { number })
: t('components.diffReviewLine.deletedLine', 'Deleted line')
}
return number
? t('components.diffReviewLine.contextLineNumber', 'Context line {{number}}', { number })
: t('components.diffReviewLine.contextLine', 'Context line')
}
function canCommentOnLine(line: MobileDiffLine): boolean {
@@ -36,6 +49,7 @@ export function MobileDiffReviewLine({
onAddNote,
onEditNote
}: Props) {
const t = useMobileTranslation()
useMobileColorScheme()
const lineNumber = mobileDiffLineNumber(line)
const canComment = canCommentOnLine(line)
@@ -49,7 +63,7 @@ export function MobileDiffReviewLine({
active && styles.activeRow
]}
accessible
accessibilityLabel={accessibilityLabelForLine(line)}
accessibilityLabel={accessibilityLabelForLine(line, t)}
>
<Text style={styles.prefix}>{mobileDiffLinePrefix(line.kind)}</Text>
<Text style={styles.lineNumber}>{lineNumber ? String(lineNumber) : ''}</Text>
@@ -64,8 +78,10 @@ export function MobileDiffReviewLine({
accessibilityRole={canComment ? 'button' : 'text'}
accessibilityLabel={
canComment && line.newLineNumber !== undefined
? `Add note on line ${line.newLineNumber}`
: accessibilityLabelForLine(line)
? t('components.diffReviewLine.addNoteOnLine', 'Add note on line {{number}}', {
number: line.newLineNumber
})
: accessibilityLabelForLine(line, t)
}
>
<Text style={styles.codeText}>
@@ -80,7 +96,13 @@ export function MobileDiffReviewLine({
style={({ pressed }) => [styles.noteButton, pressed && styles.noteButtonPressed]}
onPress={() => onEditNote(comment)}
accessibilityRole="button"
accessibilityLabel={`Edit note on line ${comment.lineNumber}`}
accessibilityLabel={t(
'components.diffReviewLine.editNoteOnLine',
'Edit note on line {{number}}',
{
number: comment.lineNumber
}
)}
>
<MessageSquare
size={13}
@@ -1,5 +1,6 @@
import { Plus, QrCode } from 'lucide-react-native'
import { Pressable, Text, View } from 'react-native'
import { useMobileTranslation } from '../i18n/use-mobile-translation'
import type { HostProfile } from '../transport/types'
import { colors, radii, spacing } from '../theme/mobile-theme'
import { themedStyleSheet } from '../theme/themed-styles'
@@ -12,12 +13,15 @@ type Props = {
}
export function MobileHomeQuickActions(props: Props) {
const t = useMobileTranslation()
useMobileColorScheme()
const canCreateWorkspace = props.connectedHosts.length > 0
return (
<>
<Text style={styles.sectionHeading}>Quick Actions</Text>
<Text style={styles.sectionHeading}>
{t('components.homeQuickActions.title', 'Quick Actions')}
</Text>
<View style={styles.quickActions}>
<Pressable
accessibilityRole="button"
@@ -27,7 +31,9 @@ export function MobileHomeQuickActions(props: Props) {
<View style={styles.quickActionIcon}>
<QrCode size={16} color={colors.textSecondary} />
</View>
<Text style={styles.quickActionLabel}>Pair Desktop</Text>
<Text style={styles.quickActionLabel}>
{t('components.homeQuickActions.pairDesktop', 'Pair Desktop')}
</Text>
</Pressable>
<Pressable
accessibilityRole="button"
@@ -43,7 +49,9 @@ export function MobileHomeQuickActions(props: Props) {
<View style={styles.quickActionIcon}>
<Plus size={16} color={colors.textSecondary} />
</View>
<Text style={styles.quickActionLabel}>New Workspace</Text>
<Text style={styles.quickActionLabel}>
{t('components.homeQuickActions.newWorkspace', 'New Workspace')}
</Text>
</Pressable>
</View>
</>
+36 -11
View File
@@ -1,5 +1,7 @@
import { Monitor, MoreVertical } from 'lucide-react-native'
import { Pressable, Text, View } from 'react-native'
import type { Translate } from '../i18n/mobile-i18n'
import { useMobileTranslation } from '../i18n/use-mobile-translation'
import type { ConnectionVerdict } from '../transport/connection-health'
import { verdictDisplayLabel } from '../transport/connection-health'
import {
@@ -15,6 +17,23 @@ import { useMobileColorScheme } from '../theme/use-mobile-color-scheme'
import { homeHostWorktreeSummary, type HostWorktreeInfo } from '../worktree/home-worktree-info'
import { StatusDot } from './StatusDot'
function hostCardWorktreeSummaries(info: HostWorktreeInfo | undefined, t: Translate) {
const now = Date.now()
const visual = homeHostWorktreeSummary(info, now)
const accessibility =
info && info.activeCount > 0 && (!info.catalogUnavailable || info.staleCounts)
? t(
'components.hostCard.worktreeAccessibilitySummary',
'{{summary}}, {{activeCount}} active',
{
summary: homeHostWorktreeSummary({ ...info, activeCount: 0 }, now),
activeCount: info.activeCount
}
)
: visual
return { visual, accessibility }
}
export function MobileHostCard(props: {
host: HostProfile | HostCatalogEntry
credentialStatus?: HostCatalogEntry['credentialStatus']
@@ -27,6 +46,7 @@ export function MobileHostCard(props: {
onLongPress: () => void
onOpenActions: () => void
}) {
const t = useMobileTranslation()
useMobileColorScheme()
const credentialUnavailable = props.credentialStatus === 'temporarily-unavailable'
const credentialMissing = props.credentialStatus === 'missing'
@@ -34,16 +54,16 @@ export function MobileHostCard(props: {
const isError =
credentialMissing || ['warning', 'unreachable', 'auth-failed'].includes(props.verdict.kind)
const statusLabel = credentialMissing
? 'Pairing invalid'
? t('components.hostCard.pairingInvalid', 'Pairing invalid')
: credentialUnavailable
? 'Pairing temporarily unavailable'
? t('components.hostCard.pairingTemporarilyUnavailable', 'Pairing temporarily unavailable')
: verdictDisplayLabel(props.verdict)
const statusVerdict: ConnectionVerdict = credentialMissing
? { kind: 'auth-failed', label: statusLabel }
: credentialUnavailable
? { kind: 'warning', label: statusLabel }
: props.verdict
const worktreeSummary = homeHostWorktreeSummary(props.worktreeInfo)
const worktreeSummary = hostCardWorktreeSummaries(props.worktreeInfo, t)
const display = useHostDisplay(props.host)
const descriptorText = display.descriptorLine
const connectionPathLabel =
@@ -52,22 +72,25 @@ export function MobileHostCard(props: {
: null
const discoveryHint =
props.verdict.kind === 'unreachable' && !props.host.relay
? 'Update desktop Orca and sign in to connect from anywhere'
? t(
'components.hostCard.updateDesktopHint',
'Update desktop Orca and sign in to connect from anywhere'
)
: null
const credentialHint = credentialMissing
? 'Tap to re-pair with your desktop'
? t('components.hostCard.repairHint', 'Tap to re-pair with your desktop')
: credentialUnavailable
? 'Unlock your phone, then tap to retry'
? t('components.hostCard.unlockHint', 'Unlock your phone, then tap to retry')
: null
// The verdict's own second line (what to check on the desktop); credential copy wins.
const verdictDetail =
credentialHint === null && 'detail' in props.verdict ? (props.verdict.detail ?? null) : null
const accessibilityLabel = [
`Open ${display.title}`,
t('components.hostCard.openHost', 'Open {{title}}', { title: display.title }),
descriptorText,
statusLabel,
connectionPathLabel ? mobileConnectionPathAccessibilityLabel(props.path) : null,
connected ? worktreeSummary?.replace(' · ', ', ') : null,
connected ? worktreeSummary.accessibility : null,
discoveryHint,
credentialHint,
verdictDetail
@@ -113,9 +136,9 @@ export function MobileHostCard(props: {
{connectionPathLabel ? ` · ${connectionPathLabel}` : ''}
</Text>
</View>
{connected && worktreeSummary ? (
{connected && worktreeSummary.visual ? (
<Text style={styles.worktreeMetaText} numberOfLines={1}>
{worktreeSummary}
{worktreeSummary.visual}
</Text>
) : null}
{discoveryHint ? (
@@ -137,7 +160,9 @@ export function MobileHostCard(props: {
</Pressable>
<Pressable
accessibilityRole="button"
accessibilityLabel={`Actions for ${display.title}`}
accessibilityLabel={t('components.hostCard.hostActions', 'Actions for {{title}}', {
title: display.title
})}
hitSlop={8}
style={({ pressed }) => [styles.actionButton, pressed && styles.actionButtonPressed]}
onPress={props.onOpenActions}
+11 -5
View File
@@ -1,6 +1,7 @@
import { useMemo, useState } from 'react'
import { Pressable, Text, View } from 'react-native'
import { Code, Eye } from 'lucide-react-native'
import { useMobileTranslation } from '../i18n/use-mobile-translation'
import { colors, spacing, typography } from '../theme/mobile-theme'
import { themedStyleSheet } from '../theme/themed-styles'
import { useMobileColorScheme } from '../theme/use-mobile-color-scheme'
@@ -25,6 +26,7 @@ export const MOBILE_HTML_PREVIEW_SEALED_SANDBOX = ''
// Inherited img/font/connect/script policies still constrain artifact; https images can report view through fixed author URL, but scripts cannot add dynamic data.
// Shell Referrer-Policy:no-referrer is required: WebKit ignores iframe referrerPolicy for srcdoc images, unlike Chromium.
export function MobileHtmlPreview({ html, renderSource }: MobileHtmlPreviewProps) {
const t = useMobileTranslation()
useMobileColorScheme()
const [mode, setMode] = useState<'preview' | 'source'>('preview')
// Read immutable session grants once after init, before page mounts.
@@ -46,10 +48,13 @@ export function MobileHtmlPreview({ html, renderSource }: MobileHtmlPreviewProps
// Set both native accessibilityState and web aria-selected: RN Web drops former without emitting latter.
accessibilityState={{ selected: mode === 'preview' }}
aria-selected={mode === 'preview'}
accessibilityLabel="Preview rendered HTML"
accessibilityLabel={t(
'components.htmlPreview.previewRenderedHtml',
'Preview rendered HTML'
)}
>
<Eye size={13} color={colors.textSecondary} strokeWidth={2.2} />
<Text style={styles.toggleText}>Preview</Text>
<Text style={styles.toggleText}>{t('components.htmlPreview.preview', 'Preview')}</Text>
</Pressable>
<Pressable
style={[styles.toggle, mode === 'source' && styles.toggleActive]}
@@ -57,10 +62,10 @@ export function MobileHtmlPreview({ html, renderSource }: MobileHtmlPreviewProps
accessibilityRole="tab"
accessibilityState={{ selected: mode === 'source' }}
aria-selected={mode === 'source'}
accessibilityLabel="View HTML source"
accessibilityLabel={t('components.htmlPreview.viewHtmlSource', 'View HTML source')}
>
<Code size={13} color={colors.textSecondary} strokeWidth={2.2} />
<Text style={styles.toggleText}>Source</Text>
<Text style={styles.toggleText}>{t('components.htmlPreview.source', 'Source')}</Text>
</Pressable>
</View>
{/* Show author’s original source; inert-link rewrite is only this shell’s rendering policy. */}
@@ -71,11 +76,12 @@ export function MobileHtmlPreview({ html, renderSource }: MobileHtmlPreviewProps
// Use DOM iframe: RN has no primitive. Set srcdoc attribute to parse untrusted content only inside sealed frame.
function PreviewFrame({ html, linksOpen }: { html: string; linksOpen: boolean }) {
const t = useMobileTranslation()
useMobileColorScheme()
return (
<View style={styles.frame}>
<iframe
title="HTML preview"
title={t('components.htmlPreview.title', 'HTML preview')}
sandbox={linksOpen ? MOBILE_HTML_PREVIEW_SANDBOX : MOBILE_HTML_PREVIEW_SEALED_SANDBOX}
srcDoc={html}
style={IFRAME_STYLE}
@@ -1,4 +1,5 @@
import { Pressable, StyleSheet, 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,11 +12,12 @@ export function MobileLoadErrorState({
message: string
onRetry?: (() => void) | null
}) {
const t = useMobileTranslation()
useMobileColorScheme()
return (
<View style={styles.state}>
<Text style={styles.errorText}>{message}</Text>
{onRetry ? <MobileStateButton label="Retry" onPress={onRetry} /> : null}
{onRetry ? <MobileStateButton label={t('common.retry', 'Retry')} onPress={onRetry} /> : null}
</View>
)
}
+15 -5
View File
@@ -1,5 +1,6 @@
import { ActivityIndicator, Pressable, ScrollView, Text, View } from 'react-native'
import { RotateCw } from 'lucide-react-native'
import { useMobileTranslation } from '../i18n/use-mobile-translation'
import { colors } from '../theme/mobile-theme'
import { useMobileColorScheme } from '../theme/use-mobile-color-scheme'
import type { PrSidebarState } from '../session/mobile-pr-sidebar-state'
@@ -179,17 +180,23 @@ function PrSidebarContent({
showOpenOnWeb: boolean
botAuthorOverrides: ReadonlySet<string>
}) {
const t = useMobileTranslation()
useMobileColorScheme()
if (branch === 'loading') {
return (
<View style={styles.stateArea}>
<ActivityIndicator color={colors.textSecondary} />
<Text style={styles.stateText}>Loading pull request…</Text>
<Text style={styles.stateText}>
{t('components.prSidebar.loading', 'Loading pull request…')}
</Text>
</View>
)
}
if (branch === 'error') {
const message = state.kind === 'error' ? state.message : 'Something went wrong.'
const message =
state.kind === 'error'
? state.message
: t('components.prSidebar.loadFailed', 'Something went wrong.')
return (
<View style={styles.stateArea}>
<Text style={styles.stateText}>{message}</Text>
@@ -197,10 +204,10 @@ function PrSidebarContent({
style={styles.retryButton}
onPress={onRetry}
accessibilityRole="button"
accessibilityLabel="Retry loading pull request"
accessibilityLabel={t('components.prSidebar.retryLoading', 'Retry loading pull request')}
>
<RotateCw size={14} color={colors.textPrimary} strokeWidth={2.2} />
<Text style={styles.retryText}>Retry</Text>
<Text style={styles.retryText}>{t('common.retry', 'Retry')}</Text>
</Pressable>
</View>
)
@@ -211,7 +218,10 @@ function PrSidebarContent({
actions.blocked ??
(state.kind === 'blocked'
? state.message
: 'Not permitted — your GitHub account is not connected.')
: t(
'components.prSidebar.notPermitted',
'Not permitted — your GitHub account is not connected.'
))
return (
<View style={styles.stateArea}>
<Text style={styles.blockedText}>{message}</Text>
@@ -10,6 +10,7 @@ import {
type ForwardedRef
} from 'react'
import { View } from 'react-native'
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'
@@ -21,7 +22,6 @@ import {
mountRichMarkdownWebDocument,
type RichMarkdownWebDocument
} from './rich-markdown/rich-markdown-web-document-mount'
import { RICH_MARKDOWN_URL_PROMPT_LABELS } from './rich-markdown/document-host-seams'
import { useMobileRichMarkdownEditorController } from './use-mobile-rich-markdown-editor-controller'
import type {
RichMarkdownEditorApi,
@@ -50,6 +50,7 @@ function MobileRichMarkdownEditorWebInner(
{ content, editable, onChange, onOpenLink }: MobileRichMarkdownEditorComponentProps,
ref: ForwardedRef<MobileRichMarkdownEditorHandle>
) {
const t = useMobileTranslation()
const colorScheme = useMobileColorScheme()
const hostRef = useRef<View>(null)
const documentRef = useRef<RichMarkdownWebDocument | null>(null)
@@ -189,9 +190,15 @@ function MobileRichMarkdownEditorWebInner(
<View ref={hostRef} style={styles.host} />
<TextInputModal
visible={urlPromptKind !== null}
title={urlPromptKind === null ? '' : RICH_MARKDOWN_URL_PROMPT_LABELS[urlPromptKind]}
title={
urlPromptKind === null
? ''
: urlPromptKind === 'link'
? t('components.richMarkdownEditor.linkUrl', 'Link URL')
: t('components.richMarkdownEditor.imageUrl', 'Image URL')
}
placeholder="https://"
submitLabel="Insert"
submitLabel={t('components.richMarkdownEditor.insert', 'Insert')}
keyboardType="url"
onSubmit={answerUrlPrompt}
onCancel={() => answerUrlPrompt(null)}
@@ -18,33 +18,95 @@ import {
Strikethrough
} from 'lucide-react-native'
import { colors, radii, spacing } from '../theme/mobile-theme'
import type { Translate } from '../i18n/mobile-i18n'
import { useMobileTranslation } from '../i18n/use-mobile-translation'
import { themedStyleSheet } from '../theme/themed-styles'
import { useMobileColorScheme } from '../theme/use-mobile-color-scheme'
import type { MobileRichMarkdownCommand } from './mobile-rich-markdown-editor-contract'
type ToolbarItem = {
command: MobileRichMarkdownCommand
label: string
label: (t: Translate) => string
icon: ComponentType<{ size?: number; color?: string }>
}
// Share command row across native injected runCommand and direct page calls so contract additions appear on both surfaces.
const TOOLBAR_ITEMS: ToolbarItem[] = [
{ command: 'paragraph', label: 'Body', icon: Pilcrow },
{ command: 'heading1', label: 'H1', icon: Heading1 },
{ command: 'heading2', label: 'H2', icon: Heading2 },
{ command: 'heading3', label: 'H3', icon: Heading3 },
{ command: 'bold', label: 'Bold', icon: Bold },
{ command: 'italic', label: 'Italic', icon: Italic },
{ command: 'strike', label: 'Strike', icon: Strikethrough },
{ command: 'bulletList', label: 'Bullet list', icon: List },
{ command: 'orderedList', label: 'Numbered list', icon: ListOrdered },
{ command: 'taskList', label: 'Checklist', icon: ListTodo },
{ command: 'quote', label: 'Quote', icon: Quote },
{ command: 'link', label: 'Link', icon: Link },
{ command: 'image', label: 'Image', icon: ImageIcon },
{ command: 'inlineCode', label: 'Inline code', icon: Code2 },
{ command: 'codeBlock', label: 'Code block', icon: FileCode2 }
{
command: 'paragraph',
label: (t) => t('components.richMarkdownToolbar.body', 'Body'),
icon: Pilcrow
},
{
command: 'heading1',
label: (t) => t('components.richMarkdownToolbar.heading1', 'H1'),
icon: Heading1
},
{
command: 'heading2',
label: (t) => t('components.richMarkdownToolbar.heading2', 'H2'),
icon: Heading2
},
{
command: 'heading3',
label: (t) => t('components.richMarkdownToolbar.heading3', 'H3'),
icon: Heading3
},
{
command: 'bold',
label: (t) => t('components.richMarkdownToolbar.bold', 'Bold'),
icon: Bold
},
{
command: 'italic',
label: (t) => t('components.richMarkdownToolbar.italic', 'Italic'),
icon: Italic
},
{
command: 'strike',
label: (t) => t('components.richMarkdownToolbar.strike', 'Strike'),
icon: Strikethrough
},
{
command: 'bulletList',
label: (t) => t('components.richMarkdownToolbar.bulletList', 'Bullet list'),
icon: List
},
{
command: 'orderedList',
label: (t) => t('components.richMarkdownToolbar.numberedList', 'Numbered list'),
icon: ListOrdered
},
{
command: 'taskList',
label: (t) => t('components.richMarkdownToolbar.checklist', 'Checklist'),
icon: ListTodo
},
{
command: 'quote',
label: (t) => t('components.richMarkdownToolbar.quote', 'Quote'),
icon: Quote
},
{
command: 'link',
label: (t) => t('components.richMarkdownToolbar.link', 'Link'),
icon: Link
},
{
command: 'image',
label: (t) => t('components.richMarkdownToolbar.image', 'Image'),
icon: ImageIcon
},
{
command: 'inlineCode',
label: (t) => t('components.richMarkdownToolbar.inlineCode', 'Inline code'),
icon: Code2
},
{
command: 'codeBlock',
label: (t) => t('components.richMarkdownToolbar.codeBlock', 'Code block'),
icon: FileCode2
}
]
/** The commands alone, for a caller that drives the row rather than renders it. */
@@ -57,6 +119,7 @@ export const MobileRichMarkdownToolbar = memo(function MobileRichMarkdownToolbar
editable: boolean
onCommand: (command: MobileRichMarkdownCommand) => void
}) {
const t = useMobileTranslation()
useMobileColorScheme()
return (
<View style={styles.toolbar}>
@@ -73,7 +136,7 @@ export const MobileRichMarkdownToolbar = memo(function MobileRichMarkdownToolbar
key={item.command}
disabled={!editable}
accessibilityRole="button"
accessibilityLabel={item.label}
accessibilityLabel={item.label(t)}
onPress={() => onCommand(item.command)}
style={({ pressed }) => [
styles.toolbarButton,
+6 -2
View File
@@ -8,6 +8,7 @@ import {
type TextInputProps
} from 'react-native'
import { Search, X } from 'lucide-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'
@@ -41,7 +42,7 @@ export function MobileSearchField({
placeholder,
onClear,
showClear,
clearAccessibilityLabel = 'Clear search',
clearAccessibilityLabel,
autoFocus = false,
focusKey,
returnKeyType = 'search',
@@ -50,6 +51,7 @@ export function MobileSearchField({
editable = true,
accessibilityLabel
}: MobileSearchFieldProps) {
const t = useMobileTranslation()
useMobileColorScheme()
const inputRef = useRef<TextInput>(null)
const [focused, setFocused] = useState(false)
@@ -125,7 +127,9 @@ export function MobileSearchField({
onPress={handleClear}
hitSlop={8}
accessibilityRole="button"
accessibilityLabel={clearAccessibilityLabel}
accessibilityLabel={
clearAccessibilityLabel ?? t('components.searchField.clearSearch', 'Clear search')
}
style={({ pressed }) => [styles.clearButton, pressed && styles.clearButtonPressed]}
>
{/* Use chip and large hit target: bare 14px X looked decorative and was hard to tap. */}
@@ -1,6 +1,8 @@
import { useLayoutEffect, useRef, useState } from 'react'
import { Alert, Platform, Pressable, Text } from 'react-native'
import { Check, Copy } from 'lucide-react-native'
import { translate } from '../i18n/mobile-i18n'
import { useMobileTranslation } from '../i18n/use-mobile-translation'
import { useClipboardWriter } from '../platform/clipboard'
import { colors, radii, spacing, typography } from '../theme/mobile-theme'
import { themedStyleSheet } from '../theme/themed-styles'
@@ -18,6 +20,7 @@ export function MobileSourceCopyButton({
partial?: boolean
}): React.JSX.Element {
useMobileColorScheme()
const t = useMobileTranslation()
const clipboard = useClipboardWriter()
const [feedback, setFeedback] = useState<CopyFeedback | null>(null)
const latestAttempt = useRef<{ sourceVersion: number } | null>(null)
@@ -76,8 +79,13 @@ export function MobileSourceCopyButton({
showFeedback({ status: 'failed' }, attempt)
} else {
Alert.alert(
'Copy failed',
error instanceof Error ? error.message : 'The clipboard rejected the text.'
translate('components.sourceCopyButton.copyFailed', 'Copy failed'),
error instanceof Error
? error.message
: translate(
'components.sourceCopyButton.clipboardRejected',
'The clipboard rejected the text.'
)
)
}
return
@@ -90,14 +98,15 @@ export function MobileSourceCopyButton({
const copied = feedback?.status === 'copied' && feedback.text === text
const failed = feedback?.status === 'failed'
const label = failed
? 'Failed'
? t('components.sourceCopyButton.failed', 'Failed')
: copied
? partial
? 'Copied loaded'
: 'Copied'
? t('components.sourceCopyButton.copiedLoaded', 'Copied loaded')
: t('common.copied', 'Copied')
: partial
? 'Copy loaded'
: 'Copy'
? t('components.sourceCopyButton.copyLoaded', 'Copy loaded')
: t('common.copy', 'Copy')
const failureLabel = t('components.sourceCopyButton.couldNotCopy', "Couldn't copy")
const Icon = copied ? Check : Copy
const iconColor = failed ? colors.statusRed : copied ? colors.statusGreen : colors.textMuted
return (
@@ -105,10 +114,10 @@ export function MobileSourceCopyButton({
style={({ pressed }) => [styles.button, partial && styles.partial, pressed && styles.pressed]}
hitSlop={10}
accessibilityRole="button"
accessibilityLabel={failed ? "Couldn't copy" : accessibilityLabel}
accessibilityLabel={failed ? failureLabel : accessibilityLabel}
accessibilityLiveRegion={failed ? 'polite' : undefined}
accessibilityValue={{ text: failed ? "Couldn't copy" : copied ? label : undefined }}
aria-valuetext={failed ? "Couldn't copy" : copied ? label : undefined}
accessibilityValue={{ text: failed ? failureLabel : copied ? label : undefined }}
aria-valuetext={failed ? failureLabel : copied ? label : undefined}
accessibilityState={{ disabled: text.length === 0 }}
disabled={text.length === 0}
onPress={() => void copy()}
@@ -1,5 +1,6 @@
import { useEffect, useRef } from 'react'
import { TextInput, type TextInputProps } from 'react-native'
import { useMobileTranslation } from '../i18n/use-mobile-translation'
const MOBILE_WORKSPACE_NAME_FOCUS_DELAY_MS = 220
@@ -13,6 +14,7 @@ export function MobileWorkspaceNameInput({
focusKey,
...props
}: MobileWorkspaceNameInputProps) {
const t = useMobileTranslation()
const inputRef = useRef<TextInput>(null)
useEffect(() => {
@@ -31,7 +33,7 @@ export function MobileWorkspaceNameInput({
return (
<TextInput
ref={inputRef}
placeholder="Workspace name"
placeholder={t('components.workspaceNameInput.placeholder', 'Workspace name')}
autoCapitalize="none"
autoCorrect={false}
showSoftInputOnFocus
+3 -1
View File
@@ -1,6 +1,7 @@
import { Pressable } from 'react-native'
import { useSafeAreaInsets } from 'react-native-safe-area-context'
import { Plus } from 'lucide-react-native'
import { useMobileTranslation } from '../i18n/use-mobile-translation'
import { colors, spacing } from '../theme/mobile-theme'
import { themedStyleSheet } from '../theme/themed-styles'
import { useMobileColorScheme } from '../theme/use-mobile-color-scheme'
@@ -16,6 +17,7 @@ type NewWorkspaceFabProps = {
// Phone-only FAB sits above home indicator and outside list-row hit area.
export function NewWorkspaceFab({ onPress, disabled }: NewWorkspaceFabProps): React.JSX.Element {
useMobileColorScheme()
const t = useMobileTranslation()
const insets = useSafeAreaInsets()
return (
<Pressable
@@ -28,7 +30,7 @@ export function NewWorkspaceFab({ onPress, disabled }: NewWorkspaceFabProps): Re
onPress={onPress}
disabled={disabled}
accessibilityRole="button"
accessibilityLabel="New workspace"
accessibilityLabel={t('components.newWorkspaceFab.newWorkspace', 'New workspace')}
hitSlop={8}
>
<Plus size={24} color={colors.bgBase} strokeWidth={2.75} />
@@ -1,4 +1,5 @@
import { Pressable, Switch, Text, View } from 'react-native'
import { useMobileTranslation } from '../i18n/use-mobile-translation'
import type { WorkspaceCreateSetupDecision } from '../tasks/workspace-create-params'
import { colors } from '../theme/mobile-theme'
import { useMobileColorScheme } from '../theme/use-mobile-color-scheme'
@@ -23,14 +24,19 @@ export function NewWorkspaceSetupScriptField({
onRunSetupChange: (run: boolean) => void
}) {
useMobileColorScheme()
const t = useMobileTranslation()
return (
<View style={styles.field}>
<View style={styles.setupHeader}>
<Text style={styles.label}>Setup script</Text>
<Text style={styles.label}>
{t('components.newWorkspaceSetupScriptField.label', 'Setup script')}
</Text>
{source ? (
<View style={styles.sourceBadge}>
<Text style={styles.sourceBadgeText}>
{source === 'orca.yaml' ? 'ORCA.YAML' : 'HOOKS'}
{source === 'orca.yaml'
? 'ORCA.YAML'
: t('components.newWorkspaceSetupScriptField.hooks', 'HOOKS')}
</Text>
</View>
) : null}
@@ -45,7 +51,9 @@ export function NewWorkspaceSetupScriptField({
]}
onPress={() => onDecisionChange('run')}
>
<Text style={styles.setupChoiceText}>Run</Text>
<Text style={styles.setupChoiceText}>
{t('components.newWorkspaceSetupScriptField.run', 'Run')}
</Text>
</Pressable>
<Pressable
style={[
@@ -54,12 +62,16 @@ export function NewWorkspaceSetupScriptField({
]}
onPress={() => onDecisionChange('skip')}
>
<Text style={styles.setupChoiceText}>Skip</Text>
<Text style={styles.setupChoiceText}>
{t('components.newWorkspaceSetupScriptField.skip', 'Skip')}
</Text>
</Pressable>
</View>
) : (
<View style={styles.setupToggleRow}>
<Text style={styles.setupToggleLabel}>Run setup command</Text>
<Text style={styles.setupToggleLabel}>
{t('components.newWorkspaceSetupScriptField.runSetupCommand', 'Run setup command')}
</Text>
<Switch
value={runSetup}
onValueChange={onRunSetupChange}
@@ -1,4 +1,5 @@
import { Pressable, Text, View } from 'react-native'
import { useMobileTranslation } from '../i18n/use-mobile-translation'
import type { WorkspaceSshGate } from '../tasks/workspace-ssh-gate'
import { workspaceSshStatusLabel } from '../tasks/workspace-ssh-gate'
import { newWorktreeFormStyles as styles } from './new-worktree-form-styles'
@@ -14,9 +15,12 @@ export function NewWorkspaceSshConnectionField({
onConnect: () => void
}) {
useMobileColorScheme()
const t = useMobileTranslation()
return (
<View style={styles.field}>
<Text style={styles.label}>SSH Connection</Text>
<Text style={styles.label}>
{t('components.newWorkspaceSshConnectionField.label', 'SSH Connection')}
</Text>
<View style={styles.sshBox}>
<View style={styles.sshRow}>
<View
@@ -42,7 +46,9 @@ export function NewWorkspaceSshConnectionField({
onPress={onConnect}
>
<Text style={styles.sshConnectText}>
{sshGate.connectInProgress ? 'Connecting...' : 'Connect'}
{sshGate.connectInProgress
? t('components.newWorkspaceSshConnectionField.connecting', 'Connecting...')
: t('components.newWorkspaceSshConnectionField.connect', 'Connect')}
</Text>
</Pressable>
)}
@@ -50,7 +56,10 @@ export function NewWorkspaceSshConnectionField({
{sshGate.error ? <Text style={styles.errorInline}>{sshGate.error}</Text> : null}
{sshGate.needsDesktopServerRecovery ? (
<Text style={styles.sshSubtitle}>
Its server needs recovering from the desktop: Settings → SSH hosts → Recover server.
{t(
'components.newWorkspaceSshConnectionField.serverRecovery',
'Its server needs recovering from the desktop: Settings → SSH hosts → Recover server.'
)}
</Text>
) : null}
</View>
+25 -9
View File
@@ -1,5 +1,6 @@
import { ActivityIndicator, Pressable, Text, TextInput, View } from 'react-native'
import { ChevronDown, ChevronUp } from 'lucide-react-native'
import { useMobileTranslation } from '../i18n/use-mobile-translation'
import type { WorkspaceCreateSetupDecision } from '../tasks/workspace-create-params'
import type { WorkspaceSshGate } from '../tasks/workspace-ssh-gate'
import type { useMobileComposerSource } from '../tasks/use-mobile-composer-source'
@@ -58,10 +59,13 @@ export function NewWorktreeFormSheet(props: {
onCreate: () => void
}) {
useMobileColorScheme()
const t = useMobileTranslation()
return (
<BottomDrawer visible={props.visible} interactive={props.interactive} onClose={props.onClose}>
<View style={styles.header}>
<Text style={styles.title}>Create worktree</Text>
<Text style={styles.title}>
{t('components.newWorktreeFormSheet.createWorktree', 'Create worktree')}
</Text>
</View>
{props.loading ? (
@@ -70,7 +74,9 @@ export function NewWorktreeFormSheet(props: {
</View>
) : !props.hasRepos ? (
<View style={styles.loadingContainer}>
<Text style={styles.emptyText}>No projects found</Text>
<Text style={styles.emptyText}>
{t('components.newWorktreeFormSheet.noProjectsFound', 'No projects found')}
</Text>
</View>
) : (
<>
@@ -84,7 +90,11 @@ export function NewWorktreeFormSheet(props: {
<SmartWorkspaceSourceField
composer={props.composer}
label={props.selectedRepoIsGit ? "Name or 'Create From'" : 'Workspace name'}
label={
props.selectedRepoIsGit
? t('components.newWorktreeFormSheet.nameOrCreateFrom', "Name or 'Create From'")
: t('components.newWorktreeFormSheet.workspaceName', 'Workspace name')
}
disabled={props.sshGate.requiresConnection}
interactive={props.interactive}
onOpenExternalUrl={props.onOpenExternalUrl}
@@ -105,7 +115,7 @@ export function NewWorktreeFormSheet(props: {
) : null}
<View style={styles.field}>
<Text style={styles.label}>Agent</Text>
<Text style={styles.label}>{t('components.newWorktreeFormSheet.agent', 'Agent')}</Text>
<Pressable
style={[styles.fieldButton, props.sshGate.requiresConnection && styles.disabled]}
disabled={props.sshGate.requiresConnection}
@@ -114,7 +124,7 @@ export function NewWorktreeFormSheet(props: {
<MobileAgentIcon agentId={props.selectedAgent.id} size={16} />
<Text style={styles.fieldButtonText} numberOfLines={1}>
{props.sshGate.requiresConnection
? 'Connect target first'
? t('components.newWorktreeFormSheet.connectTargetFirst', 'Connect target first')
: props.selectedAgent.label}
</Text>
<ChevronDown size={14} color={colors.textMuted} />
@@ -125,7 +135,9 @@ export function NewWorktreeFormSheet(props: {
style={styles.advancedToggle}
onPress={() => props.onShowAdvancedChange(!props.showAdvanced)}
>
<Text style={styles.advancedText}>Advanced</Text>
<Text style={styles.advancedText}>
{t('components.newWorktreeFormSheet.advanced', 'Advanced')}
</Text>
{props.showAdvanced ? (
<ChevronUp size={14} color={colors.textSecondary} />
) : (
@@ -140,12 +152,14 @@ export function NewWorktreeFormSheet(props: {
selectedRepoIsGit={props.selectedRepoIsGit}
/>
<View style={styles.field}>
<Text style={styles.label}>Note</Text>
<Text style={styles.label}>
{t('components.newWorktreeFormSheet.note', 'Note')}
</Text>
<TextInput
style={styles.input}
value={props.note}
onChangeText={props.onNoteChange}
placeholder="Write a note"
placeholder={t('components.newWorktreeFormSheet.notePlaceholder', 'Write a note')}
placeholderTextColor={colors.textMuted}
autoCapitalize="none"
autoCorrect={false}
@@ -176,7 +190,9 @@ export function NewWorktreeFormSheet(props: {
<ActivityIndicator size="small" color={colors.bgBase} />
) : (
<Text style={styles.createText}>
{props.sshGate.requiresConnection ? 'Connect target' : 'Create worktree'}
{props.sshGate.requiresConnection
? t('components.newWorktreeFormSheet.connectTarget', 'Connect target')
: t('components.newWorktreeFormSheet.createWorktree', 'Create worktree')}
</Text>
)}
</Pressable>
+8 -3
View File
@@ -4,6 +4,7 @@ import { getComposerRepoWorktreeBranches } from '../../../src/shared/composer-br
import { getRepoExecutionHostId, parseExecutionHostId } from '../../../src/shared/execution-host'
import { getProjectIdentityKey } from '../../../src/shared/project-host-setup-projection'
import { shouldPreserveWorkspaceSourceOnRepoChange } from '../../../src/shared/new-workspace/workspace-source'
import { useMobileTranslation } from '../i18n/use-mobile-translation'
import type { SmartModeAvailabilityInput } from '../tasks/mobile-smart-source-modes'
import { deriveRepoSlug, type PasteRepoCandidate } from '../tasks/smart-source-paste-intent'
import { useMobileComposerSource } from '../tasks/use-mobile-composer-source'
@@ -57,6 +58,7 @@ export function NewWorktreeModal(props: NewWorktreeModalProps) {
}
function NewWorktreeModalContent(props: NewWorktreeModalProps) {
const t = useMobileTranslation()
const {
visible,
client: desktopClient,
@@ -179,13 +181,13 @@ function NewWorktreeModalContent(props: NewWorktreeModalProps) {
})),
[hostRepos]
)
const projectPickerItems = useMemo(() => buildNewWorkspaceProjectOptions(repos), [repos])
const projectPickerItems = useMemo(() => buildNewWorkspaceProjectOptions(repos), [repos, t])
const selectedProjectId = selectedRepo ? getProjectIdentityKey(selectedRepo) : null
const selectedProject =
projectPickerItems.find((project) => project.id === selectedProjectId) ?? null
const runTargetPickerItems = useMemo(
() => buildNewWorkspaceRunTargetOptions(repos, selectedProjectId, hostPlatform, hostLabelById),
[hostLabelById, hostPlatform, repos, selectedProjectId]
[hostLabelById, hostPlatform, repos, selectedProjectId, t]
)
const selectedRunTarget = selectedRepo
? getNewWorkspaceRunTarget(selectedRepo, hostPlatform, hostLabelById)
@@ -236,7 +238,10 @@ function NewWorktreeModalContent(props: NewWorktreeModalProps) {
projectBadgeColor={selectedRepo ? getMobileWorkspaceRepoBadgeColor(selectedRepo) : null}
selectedRepoIsGit={selectedRepoIsGit}
selectedRepoConnectionId={selectedRepoConnectionId}
selectedRepoName={selectedRepo?.displayName ?? 'Remote repository'}
selectedRepoName={
selectedRepo?.displayName ??
t('components.newWorktreeModal.remoteRepository', 'Remote repository')
}
sshGate={executionTarget.sshGate}
composer={composer}
selectedAgent={agentSelection.selectedAgent}
@@ -1,5 +1,6 @@
import { View } from 'react-native'
import { getRepoExecutionHostId } from '../../../src/shared/execution-host'
import { useMobileTranslation } from '../i18n/use-mobile-translation'
import type { SmartModeAvailabilityInput } from '../tasks/mobile-smart-source-modes'
import type { PasteRepoCandidate } from '../tasks/smart-source-paste-intent'
import type { useMobileComposerSource } from '../tasks/use-mobile-composer-source'
@@ -49,6 +50,7 @@ export function NewWorktreeModalDrawers(props: {
onSkipSetupTrust: () => void
onCloseSetupTrust: () => void
}) {
const t = useMobileTranslation()
return (
<>
<SmartWorkspaceSourceDrawer
@@ -72,7 +74,7 @@ export function NewWorktreeModalDrawers(props: {
<PickerListDrawer
visible={props.visible && props.drawerView === 'project'}
title="Project"
title={t('components.newWorktreeModalDrawers.project', 'Project')}
items={props.projectPickerItems}
selectedId={props.selectedProjectId ?? ''}
onSelect={(item) => props.onRepoChange(item.repo)}
@@ -89,7 +91,7 @@ export function NewWorktreeModalDrawers(props: {
<PickerListDrawer
visible={props.visible && props.drawerView === 'runTarget'}
title="Run on"
title={t('components.newWorktreeModalDrawers.runOn', 'Run on')}
items={props.runTargetPickerItems}
selectedId={props.selectedRepo ? getRepoExecutionHostId(props.selectedRepo) : ''}
onSelect={(item) => props.onRepoChange(item.repo)}
@@ -99,7 +101,7 @@ export function NewWorktreeModalDrawers(props: {
<PickerListDrawer
visible={props.visible && props.drawerView === 'agent'}
title="Agent"
title={t('components.newWorktreeModalDrawers.agent', 'Agent')}
items={props.pickerAgentOptions}
selectedId={props.selectedAgent.id}
onSelect={props.onAgentChange}
@@ -1,5 +1,6 @@
import { View, Text, Pressable, Platform } from 'react-native'
import { ChevronDown } from 'lucide-react-native'
import { useMobileTranslation } from '../i18n/use-mobile-translation'
import { HostKindIcon } from './HostKindIcon'
import { colors, radii, spacing, typography } from '../theme/mobile-theme'
import { themedStyleSheet } from '../theme/themed-styles'
@@ -21,24 +22,41 @@ export function NewWorktreeProjectTargetFields({
onOpenRunTarget: () => void
}) {
useMobileColorScheme()
const t = useMobileTranslation()
return (
<>
<View style={styles.field}>
<Text style={styles.label}>Project</Text>
<Text style={styles.label}>
{t('components.newWorktreeProjectTargetFields.project', 'Project')}
</Text>
<Pressable style={styles.fieldButton} onPress={onOpenProject}>
{projectBadgeColor ? (
<View style={[styles.projectDot, { backgroundColor: projectBadgeColor }]} />
) : null}
<SelectionCopy selection={project} placeholder="Select a project" />
<SelectionCopy
selection={project}
placeholder={t(
'components.newWorktreeProjectTargetFields.selectProject',
'Select a project'
)}
/>
<ChevronDown size={14} color={colors.textMuted} />
</Pressable>
</View>
<View style={styles.field}>
<Text style={styles.label}>Run on</Text>
<Text style={styles.label}>
{t('components.newWorktreeProjectTargetFields.runOn', 'Run on')}
</Text>
<Pressable style={styles.fieldButton} onPress={onOpenRunTarget}>
<HostKindIcon hostId={runTarget?.hostId} size={14} />
<SelectionCopy selection={runTarget} placeholder="Select a run target" />
<SelectionCopy
selection={runTarget}
placeholder={t(
'components.newWorktreeProjectTargetFields.selectRunTarget',
'Select a run target'
)}
/>
<ChevronDown size={14} color={colors.textMuted} />
</Pressable>
</View>
+68 -21
View File
@@ -5,6 +5,8 @@ import { Platform, Pressable, Text, View } from 'react-native'
import { colors, radii, spacing, typography } from '../theme/mobile-theme'
import { themedStyleSheet } from '../theme/themed-styles'
import { useMobileColorScheme } from '../theme/use-mobile-color-scheme'
import type { Translate } from '../i18n/mobile-i18n'
import { useMobileTranslation } from '../i18n/use-mobile-translation'
import type { CompatVerdict } from '../transport/protocol-compat'
import type { MobileWebBundleCompatVerdict } from '../transport/mobile-web-bundle-compat'
import type { PageRouteRefusalVerdict } from '../mobile-web-shell/page-route-policy'
@@ -22,9 +24,6 @@ type Props = {
verdict: BlockedVerdict
}
const DESKTOP_TOO_OLD_BODY =
'This paired desktop app is too old for your current Orca Mobile app. Update Orca on your computer, then try this host again.'
// refresh-bundle fixes cached workspace older than host floor; store app update cannot replace that cache.
type BlockRemedy = 'update-mobile' | 'update-desktop' | 'refresh-bundle'
@@ -43,42 +42,79 @@ function blockRemedy(verdict: BlockedVerdict): BlockRemedy {
}
}
function blockTitle(remedy: BlockRemedy): string {
function blockTitle(remedy: BlockRemedy, t: Translate): string {
switch (remedy) {
case 'update-mobile':
return 'Update Orca Mobile'
return t('components.protocolBlockScreen.updateMobileTitle', 'Update Orca Mobile')
case 'update-desktop':
return 'Update Orca on your computer'
return t('components.protocolBlockScreen.updateDesktopTitle', 'Update Orca on your computer')
case 'refresh-bundle':
return 'Refresh the mobile workspace'
return t(
'components.protocolBlockScreen.refreshWorkspaceTitle',
'Refresh the mobile workspace'
)
}
}
function blockBody(verdict: BlockedVerdict, remedy: BlockRemedy, storeName: string): string {
function blockBody(verdict: BlockedVerdict, remedy: BlockRemedy, t: Translate): string {
if (remedy === 'refresh-bundle') {
return 'The workspace cached for this host is older than the desktop expects. Reconnect to this host to download the current one.'
return t(
'components.protocolBlockScreen.refreshWorkspaceBody',
'The workspace cached for this host is older than the desktop expects. Reconnect to this host to download the current one.'
)
}
if (verdict.reason === 'mobile-too-old') {
return `This desktop needs a newer Orca Mobile app. Update Orca Mobile from ${storeName}, then try this host again.`
return Platform.OS === 'ios'
? t(
'components.protocolBlockScreen.mobileTooOldAppStore',
'This desktop needs a newer Orca Mobile app. Update Orca Mobile from the App Store, then try this host again.'
)
: t(
'components.protocolBlockScreen.mobileTooOldGitHubReleases',
'This desktop needs a newer Orca Mobile app. Update Orca Mobile from GitHub Releases, then try this host again.'
)
}
if (remedy === 'update-mobile') {
return `This desktop's mobile workspace needs a newer Orca Mobile app. Update Orca Mobile from ${storeName}, then try this host again.`
return Platform.OS === 'ios'
? t(
'components.protocolBlockScreen.workspaceNeedsUpdateAppStore',
"This desktop's mobile workspace needs a newer Orca Mobile app. Update Orca Mobile from the App Store, then try this host again."
)
: t(
'components.protocolBlockScreen.workspaceNeedsUpdateGitHubReleases',
"This desktop's mobile workspace needs a newer Orca Mobile app. Update Orca Mobile from GitHub Releases, then try this host again."
)
}
return DESKTOP_TOO_OLD_BODY
return t(
'components.protocolBlockScreen.desktopTooOldBody',
'This paired desktop app is too old for your current Orca Mobile app. Update Orca on your computer, then try this host again.'
)
}
export function ProtocolBlockScreen({ verdict }: Props) {
useMobileColorScheme()
const t = useMobileTranslation()
const router = useRouteHandoff()
const mobileUpdate = useWallAppUpdate()
const remedy = blockRemedy(verdict)
// Why: Android APKs ship through GitHub Releases until a Play Store listing exists.
const mobileUpdateTarget =
Platform.OS === 'ios'
? { label: 'Open App Store', url: IOS_APP_STORE_URL, storeName: 'the App Store' }
: { label: 'Open GitHub Releases', url: RELEASES_URL, storeName: 'GitHub Releases' }
? {
label: t('components.protocolBlockScreen.openAppStore', 'Open App Store'),
url: IOS_APP_STORE_URL
}
: {
label: t('components.protocolBlockScreen.openGitHubReleases', 'Open GitHub Releases'),
url: RELEASES_URL
}
const mobileAction = mobileUpdate
? { label: `Get Orca ${mobileUpdate.version}`, url: mobileUpdate.url }
? {
label: t('components.protocolBlockScreen.getVersion', 'Get Orca {{version}}', {
version: mobileUpdate.version
}),
url: mobileUpdate.url
}
: { label: mobileUpdateTarget.label, url: mobileUpdateTarget.url }
// No download to offer when the fix is a refetch: reconnecting is what this screen leaves you to do.
const primaryAction =
@@ -86,14 +122,23 @@ export function ProtocolBlockScreen({ verdict }: Props) {
? null
: remedy === 'update-mobile'
? mobileAction
: { label: 'Open GitHub Releases', url: RELEASES_URL }
: {
label: t('components.protocolBlockScreen.openGitHubReleases', 'Open GitHub Releases'),
url: RELEASES_URL
}
const title = blockTitle(remedy)
const body = blockBody(verdict, remedy, mobileUpdateTarget.storeName)
const title = blockTitle(remedy, t)
const body = blockBody(verdict, remedy, t)
const recoveryNote =
remedy === 'refresh-bundle'
? 'If this message stays, remove this host and pair it again.'
: 'Already updated? Go back to Hosts and refresh the connection. If this message stays, remove this host and pair it again.'
? t(
'components.protocolBlockScreen.refreshRecoveryNote',
'If this message stays, remove this host and pair it again.'
)
: t(
'components.protocolBlockScreen.updateRecoveryNote',
'Already updated? Go back to Hosts and refresh the connection. If this message stays, remove this host and pair it again.'
)
return (
<View style={styles.container}>
@@ -118,7 +163,9 @@ export function ProtocolBlockScreen({ verdict }: Props) {
router.replace('/')
}}
>
<Text style={styles.secondaryButtonText}>Back to hosts</Text>
<Text style={styles.secondaryButtonText}>
{t('components.protocolBlockScreen.backToHosts', 'Back to hosts')}
</Text>
</Pressable>
<Text style={styles.recoveryNote}>{recoveryNote}</Text>
</View>
+30 -8
View File
@@ -3,6 +3,7 @@ import { Check } from 'lucide-react-native'
import { colors, radii, spacing, typography } from '../theme/mobile-theme'
import { themedStyleSheet } from '../theme/themed-styles'
import { useMobileColorScheme } from '../theme/use-mobile-color-scheme'
import { useMobileTranslation } from '../i18n/use-mobile-translation'
import { BottomDrawer } from './BottomDrawer'
export type SetupTrustPrompt = {
@@ -34,6 +35,7 @@ export function SetupHookTrustDrawer({
onClose
}: Props) {
useMobileColorScheme()
const t = useMobileTranslation()
return (
<BottomDrawer visible={visible && prompt != null} onClose={onClose}>
{prompt ? (
@@ -41,18 +43,32 @@ export function SetupHookTrustDrawer({
<View style={styles.trustHeader}>
<Text style={styles.title}>
{prompt.previouslyApproved
? `${prompt.repoName}'s setup script changed`
: `Run setup from ${prompt.repoName}?`}
? t(
'components.setupHookTrustDrawer.scriptChangedTitle',
"{{repoName}}'s setup script changed",
{ repoName: prompt.repoName }
)
: t(
'components.setupHookTrustDrawer.runSetupTitle',
'Run setup from {{repoName}}?',
{
repoName: prompt.repoName
}
)}
</Text>
<Text style={styles.subtitle}>
This repository's orca.yaml runs before the workspace starts. Only run it if you trust
this repository.
{t(
'components.setupHookTrustDrawer.description',
"This repository's orca.yaml runs before the workspace starts. Only run it if you trust this repository."
)}
</Text>
</View>
<View style={styles.trustScriptBox}>
<Text style={styles.trustScriptLabel}>
{prompt.previouslyApproved ? 'New setup script' : 'Setup script'}
{prompt.previouslyApproved
? t('components.setupHookTrustDrawer.newScriptLabel', 'New setup script')
: t('components.setupHookTrustDrawer.scriptLabel', 'Setup script')}
</Text>
<Text style={styles.trustScriptText}>{prompt.scriptContent}</Text>
</View>
@@ -60,16 +76,22 @@ export function SetupHookTrustDrawer({
<View style={styles.trustActionGroup}>
<Pressable style={styles.trustActionRow} disabled={busy} onPress={onRunOnce}>
<Check size={16} color={colors.textPrimary} />
<Text style={styles.trustActionText}>Run hooks</Text>
<Text style={styles.trustActionText}>
{t('components.setupHookTrustDrawer.runHooks', 'Run hooks')}
</Text>
</Pressable>
<View style={styles.trustActionSeparator} />
<Pressable style={styles.trustActionRow} disabled={busy} onPress={onAlwaysTrust}>
<Check size={16} color={colors.textPrimary} />
<Text style={styles.trustActionText}>Always trust and run</Text>
<Text style={styles.trustActionText}>
{t('components.setupHookTrustDrawer.alwaysTrustAndRun', 'Always trust and run')}
</Text>
</Pressable>
<View style={styles.trustActionSeparator} />
<Pressable style={styles.trustActionRow} disabled={busy} onPress={onDontRun}>
<Text style={styles.trustActionText}>Don't run</Text>
<Text style={styles.trustActionText}>
{t('components.setupHookTrustDrawer.dontRun', "Don't run")}
</Text>
</Pressable>
</View>
</View>
@@ -3,6 +3,7 @@ import type { MobileComposerSource } from '../tasks/use-mobile-composer-source'
import { colors, radii, spacing } from '../theme/mobile-theme'
import { themedStyleSheet } from '../theme/themed-styles'
import { useMobileColorScheme } from '../theme/use-mobile-color-scheme'
import { useMobileTranslation } from '../i18n/use-mobile-translation'
import { TEXT_INPUT_FONT_SIZE } from '../platform/text-input-font-size'
type Props = {
@@ -13,18 +14,24 @@ type Props = {
// Show editable Name after source pill; branch override/reuse follow desktop’s advanced composer fields.
export function SmartWorkspaceAdvancedFields({ composer, selectedRepoIsGit }: Props) {
useMobileColorScheme()
const t = useMobileTranslation()
const selection = composer.smartNameSelection
const showBranchOverride = selectedRepoIsGit && (!selection || selection.kind === 'branch')
return (
<>
{selection ? (
<View style={styles.field}>
<Text style={styles.label}>Name</Text>
<Text style={styles.label}>
{t('components.smartWorkspaceAdvancedFields.name', 'Name')}
</Text>
<TextInput
style={styles.input}
value={composer.name}
onChangeText={composer.setName}
placeholder="Workspace name"
placeholder={t(
'components.smartWorkspaceAdvancedFields.workspaceName',
'Workspace name'
)}
placeholderTextColor={colors.textMuted}
autoCapitalize="none"
autoCorrect={false}
@@ -34,12 +41,17 @@ export function SmartWorkspaceAdvancedFields({ composer, selectedRepoIsGit }: Pr
{showBranchOverride ? (
<View style={styles.field}>
<Text style={styles.label}>Branch name</Text>
<Text style={styles.label}>
{t('components.smartWorkspaceAdvancedFields.branchName', 'Branch name')}
</Text>
<TextInput
style={styles.input}
value={composer.branchNameOverride ?? ''}
onChangeText={composer.handleBranchNameOverrideChange}
placeholder="Derived from name"
placeholder={t(
'components.smartWorkspaceAdvancedFields.derivedFromName',
'Derived from name'
)}
placeholderTextColor={colors.textMuted}
autoCapitalize="none"
autoCorrect={false}
@@ -51,7 +63,13 @@ export function SmartWorkspaceAdvancedFields({ composer, selectedRepoIsGit }: Pr
<View style={styles.field}>
<View style={styles.reuseRow}>
<Text style={styles.reuseLabel} numberOfLines={1}>
Reuse branch “{composer.reuseEligibleBranch}”
{t(
'components.smartWorkspaceAdvancedFields.reuseBranch',
'Reuse branch “{{branch}}”',
{
branch: composer.reuseEligibleBranch
}
)}
</Text>
<Switch
value={composer.reuseSelectedBranch}
@@ -29,6 +29,7 @@ import { useSmartWorkspaceSource } from '../tasks/use-smart-workspace-source'
import type { MobileComposerSource } from '../tasks/use-mobile-composer-source'
import { colors } from '../theme/mobile-theme'
import { useMobileColorScheme } from '../theme/use-mobile-color-scheme'
import { useMobileTranslation } from '../i18n/use-mobile-translation'
import { BottomDrawer } from './BottomDrawer'
import { smartWorkspaceSourceDrawerStyles as styles } from './smart-workspace-source-drawer-styles'
import { SmartSourceModeIcon } from './SmartSourceModeIcon'
@@ -69,6 +70,7 @@ export function SmartWorkspaceSourceDrawer({
onClose
}: Props) {
useMobileColorScheme()
const t = useMobileTranslation()
const availableModes = useMemo(() => resolveAvailableSmartModes(availability), [availability])
const [mode, setMode] = useState<SmartNameMode>(() => resolveDefaultSmartMode(availability))
const [mrStateFilter, setMrStateFilter] = useState<MrStateFilter>('opened')
@@ -206,9 +208,11 @@ export function SmartWorkspaceSourceDrawer({
{/* Fill column ends at keyboard top; non-flex dock stays beside flex results so FlatList cannot clip input. */}
<View style={styles.root}>
<View style={styles.header}>
<Text style={styles.title}>Name or &apos;Create From&apos;</Text>
<Text style={styles.title}>
{t('components.smartWorkspaceSourceDrawer.title', "Name or 'Create From'")}
</Text>
<Pressable onPress={onClose} hitSlop={8}>
<Text style={styles.done}>Done</Text>
<Text style={styles.done}>{t('common.done', 'Done')}</Text>
</Pressable>
</View>
@@ -216,19 +220,31 @@ export function SmartWorkspaceSourceDrawer({
{crossRepoPrompt ? (
<View style={styles.crossRepo}>
<Text style={styles.crossRepoText}>
This item lives in {crossRepoPrompt.link.slug.owner}/
{crossRepoPrompt.link.slug.repo}.
{t(
'components.smartWorkspaceSourceDrawer.crossRepoMessage',
'This item lives in {{owner}}/{{repo}}.',
{
owner: crossRepoPrompt.link.slug.owner,
repo: crossRepoPrompt.link.slug.repo
}
)}
</Text>
<View style={styles.crossRepoActions}>
<Pressable style={styles.crossRepoDismiss} onPress={dismissCrossRepoPrompt}>
<Text style={styles.crossRepoDismissText}>Cancel</Text>
<Text style={styles.crossRepoDismissText}>{t('common.cancel', 'Cancel')}</Text>
</Pressable>
<Pressable
style={styles.crossRepoSwitch}
onPress={() => void handleAcceptCrossRepo()}
>
<Text style={styles.crossRepoSwitchText}>
Switch to {crossRepoPrompt.matchingRepo.displayName}
{t(
'components.smartWorkspaceSourceDrawer.switchRepo',
'Switch to {{repoName}}',
{
repoName: crossRepoPrompt.matchingRepo.displayName
}
)}
</Text>
</Pressable>
</View>
@@ -240,10 +256,18 @@ export function SmartWorkspaceSourceDrawer({
effectiveMode !== 'linear' &&
effectiveMode !== 'jira' &&
!jiraUrlIntent ? (
<Text style={styles.notice}>Connect the repository to search sources.</Text>
<Text style={styles.notice}>
{t(
'components.smartWorkspaceSourceDrawer.connectRepository',
'Connect the repository to search sources.'
)}
</Text>
) : needsGitHubRemote ? (
<Text style={styles.notice}>
This SSH repo needs a GitHub remote to list issues and PRs.
{t(
'components.smartWorkspaceSourceDrawer.gitHubRemoteRequired',
'This SSH repo needs a GitHub remote to list issues and PRs.'
)}
</Text>
) : error ? (
<Text style={styles.errorNotice}>{error}</Text>
@@ -263,9 +287,17 @@ export function SmartWorkspaceSourceDrawer({
<ActivityIndicator size="small" color={colors.textSecondary} />
</View>
) : showEmpty ? (
<Text style={styles.empty}>{emptyHint || 'No results found.'}</Text>
<Text style={styles.empty}>
{emptyHint ||
t('components.smartWorkspaceSourceDrawer.noResults', 'No results found.')}
</Text>
) : rows.length === 0 && effectiveMode === 'text' ? (
<Text style={styles.empty}>Type a workspace name in the field below.</Text>
<Text style={styles.empty}>
{t(
'components.smartWorkspaceSourceDrawer.typeWorkspaceName',
'Type a workspace name in the field below.'
)}
</Text>
) : null
}
renderItem={({ item }) => (
@@ -318,7 +350,10 @@ export function SmartWorkspaceSourceDrawer({
style={styles.search}
value={composer.name}
onChangeText={composer.setName}
placeholder="Type a name or search a source"
placeholder={t(
'components.smartWorkspaceSourceDrawer.searchPlaceholder',
'Type a name or search a source'
)}
placeholderTextColor={colors.textMuted}
autoCapitalize="none"
autoCorrect={false}
@@ -12,6 +12,7 @@ import type { MobileComposerSource } from '../tasks/use-mobile-composer-source'
import { colors, radii, spacing, typography } from '../theme/mobile-theme'
import { themedStyleSheet } from '../theme/themed-styles'
import { useMobileColorScheme } from '../theme/use-mobile-color-scheme'
import { useMobileTranslation } from '../i18n/use-mobile-translation'
import { TaskProviderLogo } from './TaskProviderLogo'
import { TEXT_INPUT_FONT_SIZE } from '../platform/text-input-font-size'
@@ -53,6 +54,7 @@ export function SmartWorkspaceSourceField({
onOpenDrawer
}: Props) {
useMobileColorScheme()
const t = useMobileTranslation()
const selection = composer.smartNameSelection
function openDrawer(): void {
@@ -66,7 +68,10 @@ export function SmartWorkspaceSourceField({
return (
<View style={styles.field}>
<Text style={styles.label}>
{label} <Text style={styles.labelHint}>[Optional]</Text>
{label}{' '}
<Text style={styles.labelHint}>
{t('components.smartWorkspaceSourceField.optional', '[Optional]')}
</Text>
</Text>
{selection ? (
<View style={styles.pill}>
@@ -77,7 +82,10 @@ export function SmartWorkspaceSourceField({
{selection.url ? (
<Pressable
accessibilityRole="link"
accessibilityLabel="Open selected source"
accessibilityLabel={t(
'components.smartWorkspaceSourceField.openSelectedSource',
'Open selected source'
)}
hitSlop={6}
onPress={() => {
if (selection.url) {
@@ -100,7 +108,10 @@ export function SmartWorkspaceSourceField({
onChangeText={composer.setName}
onFocus={openDrawer}
editable={!disabled && interactive}
placeholder="Type a name or search a source"
placeholder={t(
'components.smartWorkspaceSourceField.searchPlaceholder',
'Type a name or search a source'
)}
placeholderTextColor={colors.textMuted}
autoCapitalize="none"
autoCorrect={false}
@@ -4,6 +4,8 @@ import type { SmartWorkspaceSourceRow as SourceRow } from '../../../src/shared/n
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 { TaskProviderLogo } from './TaskProviderLogo'
type Props = {
@@ -18,32 +20,50 @@ type RowContent = {
status?: string
}
function resolveRowContent(row: SourceRow): RowContent {
function resolveRowContent(row: SourceRow, t: Translate): RowContent {
switch (row.kind) {
case 'use-name':
return {
icon: <Sparkles size={16} color={colors.textSecondary} />,
title: `Use "${row.name}"`,
subtitle: 'Name this workspace'
title: t('components.smartWorkspaceSourceRow.useName', 'Use "{{name}}"', {
name: row.name
}),
subtitle: t('components.smartWorkspaceSourceRow.nameWorkspace', 'Name this workspace')
}
case 'create-branch':
return {
icon: <GitBranch size={16} color={colors.accentBlue} />,
title: `Create branch "${row.name}"`,
subtitle: 'New branch'
title: t('components.smartWorkspaceSourceRow.createBranch', 'Create branch "{{name}}"', {
name: row.name
}),
subtitle: t('components.smartWorkspaceSourceRow.newBranch', 'New branch')
}
case 'github':
return {
icon: <TaskProviderLogo provider="github" size={16} color={colors.textSecondary} />,
title: row.item.title,
subtitle: `${row.item.type === 'pr' ? 'PR #' : 'Issue #'}${row.item.number}`,
subtitle:
row.item.type === 'pr'
? t('components.smartWorkspaceSourceRow.pullRequest', 'PR #{{number}}', {
number: row.item.number
})
: t('components.smartWorkspaceSourceRow.issue', 'Issue #{{number}}', {
number: row.item.number
}),
status: row.item.state
}
case 'gitlab':
return {
icon: <TaskProviderLogo provider="gitlab" size={16} color={colors.textSecondary} />,
title: row.item.title,
subtitle: `${row.item.type === 'mr' ? 'MR !' : 'Issue #'}${row.item.number}`,
subtitle:
row.item.type === 'mr'
? t('components.smartWorkspaceSourceRow.mergeRequest', 'MR !{{number}}', {
number: row.item.number
})
: t('components.smartWorkspaceSourceRow.issue', 'Issue #{{number}}', {
number: row.item.number
}),
status: row.item.state
}
case 'branch':
@@ -73,7 +93,8 @@ function resolveRowContent(row: SourceRow): RowContent {
export function SmartWorkspaceSourceRow({ row, onPress }: Props) {
useMobileColorScheme()
const content = resolveRowContent(row)
const t = useMobileTranslation()
const content = resolveRowContent(row, t)
return (
<Pressable
style={({ pressed }) => [styles.row, pressed && styles.rowPressed]}
@@ -19,6 +19,7 @@ import { useSerializedStoredState } from '../storage/use-serialized-stored-state
import { colors, radii, spacing, typography } from '../theme/mobile-theme'
import { themedStyleSheet } from '../theme/themed-styles'
import { useMobileColorScheme } from '../theme/use-mobile-color-scheme'
import { useMobileTranslation } from '../i18n/use-mobile-translation'
import {
TERMINAL_ACCESSORY_KEYS,
type TerminalAccessoryKey
@@ -82,6 +83,7 @@ export function TerminalShortcutSettings({
onDragActiveChange
}: Props): React.JSX.Element {
useMobileColorScheme()
const t = useMobileTranslation()
const {
value: customKeys,
setValue: setCustomKeys,
@@ -189,10 +191,14 @@ export function TerminalShortcutSettings({
return (
<>
<Text style={[styles.groupHeading, styles.groupTopGap]}>SHORTCUT BAR</Text>
<Text style={[styles.groupHeading, styles.groupTopGap]}>
{t('components.terminalShortcutSettings.shortcutBar', 'SHORTCUT BAR')}
</Text>
<Text style={styles.groupDescription}>
Toggle keys to show or hide them, and hold the grip to drag a key into the order you want on
the terminal shortcut bar.
{t(
'components.terminalShortcutSettings.shortcutBarDescription',
'Toggle keys to show or hide them, and hold the grip to drag a key into the order you want on the terminal shortcut bar.'
)}
</Text>
<View style={[styles.section, styles.sectionTopGap]}>
<DragReorderList
@@ -217,20 +223,29 @@ export function TerminalShortcutSettings({
onPress={resetBuiltInKeys}
>
<View style={styles.rowContent}>
<Text style={styles.rowLabel}>Reset Defaults</Text>
<Text style={styles.rowLabel}>
{t('components.terminalShortcutSettings.resetDefaults', 'Reset Defaults')}
</Text>
<Text style={styles.rowSublabel}>
Show every built-in shortcut key in the original order
{t(
'components.terminalShortcutSettings.resetDescription',
'Show every built-in shortcut key in the original order'
)}
</Text>
</View>
</Pressable>
</View>
<Text style={[styles.groupHeading, styles.groupTopGap]}>CUSTOM SHORTCUTS</Text>
<Text style={[styles.groupHeading, styles.groupTopGap]}>
{t('components.terminalShortcutSettings.customShortcuts', 'CUSTOM SHORTCUTS')}
</Text>
<View style={[styles.section, styles.sectionTopGap]}>
{customKeys.length === 0 ? (
<>
<View style={styles.emptyContainer}>
<Text style={styles.emptyText}>No custom shortcuts defined yet.</Text>
<Text style={styles.emptyText}>
{t('components.terminalShortcutSettings.empty', 'No custom shortcuts defined yet.')}
</Text>
</View>
<View style={styles.separator} />
</>
@@ -263,7 +278,11 @@ export function TerminalShortcutSettings({
onPress={() => handleDeleteCustomKey(key)}
hitSlop={8}
accessibilityRole="button"
accessibilityLabel={`Remove ${key.label}`}
accessibilityLabel={t(
'components.terminalShortcutSettings.removeShortcut',
'Remove {{label}}',
{ label: key.label }
)}
>
<X size={16} color={colors.statusRed} />
</Pressable>
@@ -276,8 +295,15 @@ export function TerminalShortcutSettings({
onPress={() => setShowCustomKeyModal(true)}
>
<View style={styles.rowContent}>
<Text style={styles.rowLabel}>Add Custom Shortcut…</Text>
<Text style={styles.rowSublabel}>Create key combo or text macro</Text>
<Text style={styles.rowLabel}>
{t('components.terminalShortcutSettings.addShortcut', 'Add Custom Shortcut…')}
</Text>
<Text style={styles.rowSublabel}>
{t(
'components.terminalShortcutSettings.addDescription',
'Create key combo or text macro'
)}
</Text>
</View>
<ChevronRight size={16} color={colors.textMuted} />
</Pressable>
+5 -3
View File
@@ -3,6 +3,7 @@ import { View, Text, TextInput, Pressable, Platform, type KeyboardTypeOptions }
import { colors, spacing, radii, typography } from '../theme/mobile-theme'
import { themedStyleSheet } from '../theme/themed-styles'
import { useMobileColorScheme } from '../theme/use-mobile-color-scheme'
import { useMobileTranslation } from '../i18n/use-mobile-translation'
import { TEXT_INPUT_FONT_SIZE } from '../platform/text-input-font-size'
import { BottomDrawer } from './BottomDrawer'
@@ -28,7 +29,7 @@ export function TextInputModal({
message,
defaultValue = '',
placeholder,
submitLabel = 'Save',
submitLabel,
selectTextOnFocus = false,
allowEmpty = false,
keyboardType,
@@ -37,6 +38,7 @@ export function TextInputModal({
onAfterClose
}: Props) {
useMobileColorScheme()
const t = useMobileTranslation()
const [value, setValue] = useState(defaultValue)
const [previousVisible, setPreviousVisible] = useState(visible)
const [previousDefaultValue, setPreviousDefaultValue] = useState(defaultValue)
@@ -88,7 +90,7 @@ export function TextInputModal({
style={({ pressed }) => [styles.cancelButton, pressed && styles.buttonPressed]}
onPress={onCancel}
>
<Text style={styles.cancelText}>Cancel</Text>
<Text style={styles.cancelText}>{t('common.cancel', 'Cancel')}</Text>
</Pressable>
<Pressable
style={({ pressed }) => [
@@ -99,7 +101,7 @@ export function TextInputModal({
disabled={!canSubmit}
onPress={handleSubmit}
>
<Text style={styles.submitText}>{submitLabel}</Text>
<Text style={styles.submitText}>{submitLabel ?? t('common.save', 'Save')}</Text>
</Pressable>
</View>
</BottomDrawer>
+46 -13
View File
@@ -3,6 +3,9 @@ import { Check, Download, Trash2 } from 'lucide-react-native'
import { colors, radii, spacing, typography } from '../theme/mobile-theme'
import { themedStyleSheet } from '../theme/themed-styles'
import { useMobileColorScheme } from '../theme/use-mobile-color-scheme'
import type { Translate } from '../i18n/mobile-i18n'
import { getUiIntlLocale } from '../i18n/ui-intl-locale'
import { useMobileTranslation } from '../i18n/use-mobile-translation'
import {
cloudModelNeedsKey,
dictationModelRowAction,
@@ -21,25 +24,32 @@ type Props = {
onDelete: (model: MobileSpeechModel) => void
}
function formatSize(bytes: number | null | undefined): string {
function formatSize(bytes: number | null | undefined, t: Translate): string {
if (!bytes) {
return ''
}
return `${Math.round(bytes / 1_000_000)} MB`
return t('components.voiceModelList.modelSize', '{{size}} MB', {
size: Math.round(bytes / 1_000_000).toLocaleString(getUiIntlLocale())
})
}
function modelMeta(model: MobileSpeechModel): string {
function modelMeta(model: MobileSpeechModel, t: Translate): string {
if (model.provider === 'openai') {
return 'OpenAI API'
}
const inFlight = isModelInFlight(model)
if (inFlight && model.progress != null) {
return `${formatSize(model.sizeBytes)} · ${Math.round(model.progress * 100)}%`
return t('components.voiceModelList.modelProgress', '{{size}} · {{progress}}%', {
size: formatSize(model.sizeBytes, t),
progress: Math.round(model.progress * 100).toLocaleString(getUiIntlLocale())
})
}
if (model.status === 'extracting') {
return `${formatSize(model.sizeBytes)} · extracting…`
return t('components.voiceModelList.modelExtracting', '{{size}} · extracting…', {
size: formatSize(model.sizeBytes, t)
})
}
return formatSize(model.sizeBytes)
return formatSize(model.sizeBytes, t)
}
// Share model size/progress/recommendation/selection/download/delete rows between setup and Voice settings.
@@ -52,6 +62,7 @@ export function VoiceModelList({
onDelete
}: Props): React.JSX.Element {
useMobileColorScheme()
const t = useMobileTranslation()
return (
<View style={disabled ? styles.disabled : undefined} pointerEvents={disabled ? 'none' : 'auto'}>
{setup.models.map((model, idx) => {
@@ -70,9 +81,13 @@ export function VoiceModelList({
<Text style={styles.modelLabel} numberOfLines={1}>
{model.label}
</Text>
{model.recommended ? <Text style={styles.recommended}>Recommended</Text> : null}
{model.recommended ? (
<Text style={styles.recommended}>
{t('components.voiceModelList.recommended', 'Recommended')}
</Text>
) : null}
</View>
<Text style={styles.modelMeta}>{modelMeta(model)}</Text>
<Text style={styles.modelMeta}>{modelMeta(model, t)}</Text>
</View>
{action === 'needs-api-key' ? (
<Text style={styles.modelStateText}>{cloudModelNeedsKey()}</Text>
@@ -81,7 +96,9 @@ export function VoiceModelList({
{action === 'in-use' ? (
<View style={styles.selectedTag}>
<Check size={14} color={colors.statusGreen} strokeWidth={2.4} />
<Text style={styles.selectedText}>In use</Text>
<Text style={styles.selectedText}>
{t('components.voiceModelList.inUse', 'In use')}
</Text>
</View>
) : (
<Pressable
@@ -91,14 +108,18 @@ export function VoiceModelList({
]}
disabled={anyBusy}
accessibilityRole="button"
accessibilityLabel={`Use ${model.label}`}
accessibilityLabel={t('components.voiceModelList.useModel', 'Use {{label}}', {
label: model.label
})}
accessibilityState={{ disabled: anyBusy }}
onPress={() => onUseModel(model)}
>
{selectBusy ? (
<ActivityIndicator size="small" color={colors.textSecondary} />
) : (
<Text style={styles.actionText}>Use</Text>
<Text style={styles.actionText}>
{t('components.voiceModelList.use', 'Use')}
</Text>
)}
</Pressable>
)}
@@ -108,7 +129,13 @@ export function VoiceModelList({
style={({ pressed }) => [styles.iconButton, pressed && styles.actionPressed]}
disabled={anyBusy}
onPress={() => onDelete(model)}
accessibilityLabel={`Delete ${model.label}`}
accessibilityLabel={t(
'components.voiceModelList.deleteModel',
'Delete {{label}}',
{
label: model.label
}
)}
>
{deleteBusy ? (
<ActivityIndicator size="small" color={colors.statusRed} />
@@ -126,7 +153,13 @@ export function VoiceModelList({
disabled={anyBusy}
onPress={() => onDownload(model)}
accessibilityRole="button"
accessibilityLabel={`Download ${model.label}`}
accessibilityLabel={t(
'components.voiceModelList.downloadModel',
'Download {{label}}',
{
label: model.label
}
)}
accessibilityState={{ disabled: anyBusy }}
>
{downloadBusy ? (
@@ -3,17 +3,26 @@ import { SquareTerminal } from 'lucide-react-native'
import { colors, spacing } from '../theme/mobile-theme'
import { themedStyleSheet } from '../theme/themed-styles'
import { useMobileColorScheme } from '../theme/use-mobile-color-scheme'
import { useMobileTranslation } from '../i18n/use-mobile-translation'
// Wide tablet/foldable detail stays empty beside list until workspace opens.
export function WorkspaceDetailPlaceholder() {
useMobileColorScheme()
const t = useMobileTranslation()
return (
<View style={styles.container}>
<View style={styles.icon}>
<SquareTerminal size={28} color={colors.textMuted} />
</View>
<Text style={styles.title}>No workspace open</Text>
<Text style={styles.body}>Pick a workspace from the sidebar to open its terminal here.</Text>
<Text style={styles.title}>
{t('components.workspaceDetailPlaceholder.title', 'No workspace open')}
</Text>
<Text style={styles.body}>
{t(
'components.workspaceDetailPlaceholder.description',
'Pick a workspace from the sidebar to open its terminal here.'
)}
</Text>
</View>
)
}
+26 -8
View File
@@ -6,6 +6,7 @@ import { triggerMediumImpact } from '../platform/haptics'
import { colors, radii, spacing, typography } from '../theme/mobile-theme'
import { themedStyleSheet } from '../theme/themed-styles'
import { useMobileColorScheme } from '../theme/use-mobile-color-scheme'
import { useMobileTranslation } from '../i18n/use-mobile-translation'
import type { Worktree } from '../worktree/workspace-list-types'
import type { WorktreeCardDetails } from '../../../src/shared/worktree/card-properties'
import { isRemoteExecutionHostId } from '../../../src/shared/execution-host'
@@ -61,12 +62,14 @@ function WorktreeListRowComponent({
onToggleLineage
}: Props) {
useMobileColorScheme()
const t = useMobileTranslation()
const isFolderWorkspace = item.workspaceKind === 'folder-workspace'
const folderMeta = item.comment?.trim() || item.path || 'Folder'
const folderMeta =
item.comment?.trim() || item.path || t('components.worktreeListRow.folder', 'Folder')
const metaText = isFolderWorkspace ? folderMeta : displayBranch(item.branch)
const lineageDepth = Math.max(0, item.lineageDepth ?? 0)
const indent = projectGroupRowIndent(item) + lineageDepth * 18
const lineageChildCount = item.lineageChildCount ?? 0
const count = item.lineageChildCount ?? 0
const shouldHideRepo = hideRepo && !item.showProjectName
const showRepoChip = cardDetails.repoInTitle && !shouldHideRepo && !isFolderWorkspace
const showRepoInMetaRow = !cardDetails.repoInTitle && !shouldHideRepo
@@ -112,12 +115,21 @@ function WorktreeListRowComponent({
<View style={styles.worktreeMain}>
<View style={styles.worktreeNameRow}>
{showHostGlyph && (
<View accessibilityLabel={hostAccessibilityLabel || 'Remote host'}>
<View
accessibilityLabel={
hostAccessibilityLabel || t('components.worktreeListRow.remoteHost', 'Remote host')
}
>
<HostKindIcon hostId={hostId} size={11} />
</View>
)}
{showRepoChip && (
<View style={styles.repoChip} accessibilityLabel={`Project ${item.repo}`}>
<View
style={styles.repoChip}
accessibilityLabel={t('components.worktreeListRow.project', 'Project {{repo}}', {
repo: item.repo
})}
>
<MobileRepoIcon repoIcon={repoIcon} size={11} color={repoColor} />
</View>
)}
@@ -141,7 +153,9 @@ function WorktreeListRowComponent({
)}
{isFolderWorkspace && (
<View style={styles.folderBadge}>
<Text style={styles.folderBadgeText}>Folder</Text>
<Text style={styles.folderBadgeText}>
{t('components.worktreeListRow.folder', 'Folder')}
</Text>
</View>
)}
<WorktreeMetaGlyphs presence={getWorktreeMetaGlyphPresence(item, cardDetails)} />
@@ -151,7 +165,9 @@ function WorktreeListRowComponent({
{lineageDepth > 0 && (
<View style={styles.childBadge}>
<GitBranch size={10} color={colors.textMuted} />
<Text style={styles.childBadgeText}>Child</Text>
<Text style={styles.childBadgeText}>
{t('components.worktreeListRow.child', 'Child')}
</Text>
</View>
)}
{cardDetails.host && (item.hostContextLabel || item.hostContextHealthLabel) ? (
@@ -201,7 +217,7 @@ function WorktreeListRowComponent({
fullList={agentActivityDisplayMode === 'full'}
/>
) : null}
{lineageChildCount > 0 && onToggleLineage ? (
{count > 0 && onToggleLineage ? (
<Pressable
style={styles.lineageToggle}
onPress={(event) => {
@@ -216,7 +232,9 @@ function WorktreeListRowComponent({
)}
<GitBranch size={12} color={colors.textSecondary} />
<Text style={styles.lineageToggleText}>
{lineageChildCount} {lineageChildCount === 1 ? 'child' : 'children'}
{count === 1
? t('components.worktreeListRow.children_one', '{{count}} child', { count })
: t('components.worktreeListRow.children_other', '{{count}} children', { count })}
</Text>
</Pressable>
) : null}
+14 -1
View File
@@ -1,4 +1,5 @@
import { z } from 'zod'
import { translate } from '../i18n/mobile-i18n'
const TimestampSchema = z.number().int().nonnegative().max(Number.MAX_SAFE_INTEGER)
const AccountIdSchema = z.string().min(1)
@@ -227,10 +228,22 @@ export type ClaudeAccountSummary = z.infer<typeof ClaudeAccountSummarySchema>
export type CodexAccountSummary = z.infer<typeof CodexAccountSummarySchema>
export type AccountsSnapshot = z.infer<typeof AccountsSnapshotSchema>
export class AccountsSnapshotError extends Error {
constructor() {
super(
translate(
'components.accountsSnapshot.invalidSnapshot',
'Invalid accounts snapshot from host'
)
)
this.name = 'AccountsSnapshotError'
}
}
export function decodeAccountsSnapshot(value: unknown): AccountsSnapshot {
const result = AccountsSnapshotSchema.safeParse(value)
if (!result.success) {
throw new Error('Invalid accounts snapshot from host')
throw new AccountsSnapshotError()
}
return result.data
}
+106 -17
View File
@@ -1,4 +1,3 @@
import { formatResetCountdown } from '../../../src/shared/rate-limit-reset-format'
import {
buildCodexResetCreditExpectedScope,
type CodexResetCreditExpectedScope
@@ -10,6 +9,7 @@ import {
} from './codex-reset-credit-consume-operations'
import { CodexResetCreditExpectedScopeSchema } from './codex-reset-credit-reply-schema'
import { randomV4Uuid } from '../platform/random-v4-uuid'
import { translate } from '../i18n/mobile-i18n'
import {
decodeAccountsSnapshot,
type AccountsSnapshot,
@@ -52,6 +52,74 @@ export type CodexResetCreditSummary = {
const RESET_RPC_TIMEOUT_MS = 90_000
function resetCreditExpiryLabel(ms: number, count: number): string {
if (ms <= 0) {
return count === 1
? translate('components.codexResetCredit.expiryNowSingle', 'Expires now')
: translate('components.codexResetCredit.expiryNowMultiple', 'Next expires now')
}
const totalMinutes = Math.floor(ms / 60_000)
if (totalMinutes < 60) {
return count === 1
? translate('components.codexResetCredit.expiryMinutesSingle', 'Expires in {{minutes}}m', {
minutes: totalMinutes
})
: translate(
'components.codexResetCredit.expiryMinutesMultiple',
'Next expires in {{minutes}}m',
{ minutes: totalMinutes }
)
}
const totalHours = Math.floor(totalMinutes / 60)
const minutes = totalMinutes % 60
if (totalHours < 24) {
const options = { hours: totalHours, minutes }
if (minutes > 0) {
return count === 1
? translate(
'components.codexResetCredit.expiryHoursMinutesSingle',
'Expires in {{hours}}h {{minutes}}m',
options
)
: translate(
'components.codexResetCredit.expiryHoursMinutesMultiple',
'Next expires in {{hours}}h {{minutes}}m',
options
)
}
return count === 1
? translate('components.codexResetCredit.expiryHoursSingle', 'Expires in {{hours}}h', options)
: translate(
'components.codexResetCredit.expiryHoursMultiple',
'Next expires in {{hours}}h',
options
)
}
const days = Math.floor(totalHours / 24)
const hours = totalHours % 24
const options = { days, hours }
if (hours > 0) {
return count === 1
? translate(
'components.codexResetCredit.expiryDaysHoursSingle',
'Expires in {{days}}d {{hours}}h',
options
)
: translate(
'components.codexResetCredit.expiryDaysHoursMultiple',
'Next expires in {{days}}d {{hours}}h',
options
)
}
return count === 1
? translate('components.codexResetCredit.expiryDaysSingle', 'Expires in {{days}}d', options)
: translate(
'components.codexResetCredit.expiryDaysMultiple',
'Next expires in {{days}}d',
options
)
}
export function getCodexResetCreditSummary(
limits: ProviderRateLimits | null,
now: number
@@ -64,14 +132,18 @@ export function getCodexResetCreditSummary(
const expiry = credits?.nextExpiresAt
const expiryLabel =
typeof expiry === 'number' && Number.isFinite(expiry)
? formatResetCountdown(expiry - now).replace(
/^Resets/,
count === 1 ? 'Expires' : 'Next expires'
)
? resetCreditExpiryLabel(expiry - now, count)
: null
return {
availableCount: count,
availabilityLabel: `${count} ${count === 1 ? 'reset' : 'resets'} available`,
availabilityLabel:
count === 1
? translate('components.codexResetCredit.available_one', '{{count}} reset available', {
count
})
: translate('components.codexResetCredit.available_other', '{{count}} resets available', {
count
}),
expiryLabel
}
}
@@ -82,18 +154,29 @@ export function getCodexResetCreditOutcomeCopy(outcome: CodexResetCreditOutcome)
} {
switch (outcome) {
case 'reset':
return { title: 'Rate limits reset', message: 'Codex usage has been refreshed.' }
case 'alreadyRedeemed':
return { title: 'Reset already applied', message: 'Codex usage has been refreshed.' }
return {
title:
outcome === 'reset'
? translate('components.codexResetCredit.resetTitle', 'Rate limits reset')
: translate('components.codexResetCredit.alreadyAppliedTitle', 'Reset already applied'),
message: translate('components.codexResetCredit.refresh', 'Codex usage has been refreshed.')
}
case 'nothingToReset':
return {
title: 'Nothing to reset',
message: 'No eligible Codex rate-limit window is exhausted.'
title: translate('components.codexResetCredit.nothingToResetTitle', 'Nothing to reset'),
message: translate(
'components.codexResetCredit.nothingToResetMessage',
'No eligible Codex rate-limit window is exhausted.'
)
}
case 'noCredit':
return {
title: 'No reset available',
message: 'This account has no earned reset credits available.'
title: translate('components.codexResetCredit.noResetTitle', 'No reset available'),
message: translate(
'components.codexResetCredit.noResetMessage',
'This account has no earned reset credits available.'
)
}
}
}
@@ -145,17 +228,23 @@ function sameAccountScope(
)
}
function invalidResetResponse(): Error {
return new Error(
translate('components.codexResetCredit.invalidResponse', 'Invalid reset response from host')
)
}
function decodeResetResult(
value: unknown,
expectedScope: CodexResetCreditExpectedScope
): CodexResetCreditRpcResult {
if (!value || typeof value !== 'object' || Array.isArray(value)) {
throw new Error('Invalid reset response from host')
throw invalidResetResponse()
}
const result = value as Record<string, unknown>
const scope = CodexResetCreditExpectedScopeSchema.safeParse(result.scope)
if (!scope.success || !sameAccountScope(scope.data, expectedScope)) {
throw new Error('Invalid reset response from host')
throw invalidResetResponse()
}
const snapshot = decodeAccountsSnapshot(result.snapshot)
if (result.status === 'rejectedBeforeProvider') {
@@ -170,7 +259,7 @@ function decodeResetResult(
reason !== 'offerUnavailable' &&
reason !== 'offerChanged')
) {
throw new Error('Invalid reset response from host')
throw invalidResetResponse()
}
return {
status: 'rejectedBeforeProvider',
@@ -189,7 +278,7 @@ function decodeResetResult(
outcome !== 'noCredit' &&
outcome !== 'alreadyRedeemed')
) {
throw new Error('Invalid reset response from host')
throw invalidResetResponse()
}
const snapshotAccount = snapshot.codex.accounts.find(
(account) => account.id === scope.data.accountId
@@ -200,7 +289,7 @@ function decodeResetResult(
getActiveCodexAccountIdForRateLimitTarget(snapshot) !== scope.data.accountId ||
snapshotAccount?.updatedAt !== scope.data.accountRevision
) {
throw new Error('Invalid reset response from host')
throw invalidResetResponse()
}
return {
outcome,
@@ -22,6 +22,7 @@ import { MarkdownText } from './mobile-markdown-text'
import { codeFenceClosed, plainText } from './mobile-markdown-tree'
import { MermaidDiagram } from './pr-sidebar/MermaidDiagram'
import { MobileSourceCopyButton } from './MobileSourceCopyButton'
import { translate } from '../i18n/mobile-i18n'
/** Prose base size — passed to MermaidDiagram fallback mono text. */
const MERMAID_BASE = 13
@@ -65,7 +66,10 @@ function renderCode(node: Code, key: number, ctx: MarkdownBlockContext): ReactNo
</MarkdownText>
</View>
{node.value ? (
<MobileSourceCopyButton text={node.value} accessibilityLabel="Copy code" />
<MobileSourceCopyButton
text={node.value}
accessibilityLabel={translate('components.markdownBlocks.copyCode', 'Copy code')}
/>
) : null}
</View>
)
@@ -169,7 +173,9 @@ function renderBlock(
onPress={() => openMarkdownHref(only.url, ctx.onOpenFile)}
onLongPress={ctx.rowLongPress}
>
<NativeText style={styles.link}>{only.alt || 'Open image'}</NativeText>
<NativeText style={styles.link}>
{only.alt || translate('components.markdownBlocks.openImage', 'Open image')}
</NativeText>
<NativeText style={styles.imageCaption} numberOfLines={1}>
{only.url}
</NativeText>
@@ -19,6 +19,7 @@ import Animated, {
interpolate,
Extrapolation
} from 'react-native-reanimated'
import { useMobileTranslation } from '../i18n/use-mobile-translation'
import { spacing } from '../theme/mobile-theme'
import { useMobileColorScheme } from '../theme/use-mobile-color-scheme'
import { resolveBottomDrawerFillHeight } from './bottom-drawer-fill-height'
@@ -72,6 +73,7 @@ export function MountedBottomDrawer({
scrollResetKey = null
}: MountedBottomDrawerProps) {
useMobileColorScheme()
const t = useMobileTranslation()
const translateY = useSharedValue(0)
const progress = useSharedValue(0)
const keyboardOffset = useSharedValue(0)
@@ -308,7 +310,7 @@ export function MountedBottomDrawer({
<Animated.View
style={styles.handleHitArea}
accessibilityRole="button"
accessibilityLabel="Dismiss drawer"
accessibilityLabel={t('components.mountedBottomDrawer.dismissDrawer', 'Dismiss drawer')}
>
<View style={styles.handle} />
</Animated.View>
@@ -1,4 +1,5 @@
import type { TuiAgent } from '../../../src/shared/tui-agent'
import { translate } from '../i18n/mobile-i18n'
import { MOBILE_AGENT_CATALOG } from '../tasks/mobile-agent-catalog'
import { isMobileTuiAgentEnabled } from '../tasks/mobile-tui-agents'
import { pickWorkspaceAgent } from '../tasks/workspace-agent-selection'
@@ -18,7 +19,9 @@ export const NEW_WORKTREE_AGENT_OPTIONS: NewWorktreeAgentOption[] = MOBILE_AGENT
export const NEW_WORKTREE_BLANK_AGENT: NewWorktreeAgentOption = {
id: '__blank__',
label: 'Blank Terminal'
get label() {
return translate('components.newWorktreeAgentSelection.blankTerminal', 'Blank Terminal')
}
}
export function newWorktreeAgentOptionFor(id: string | null | undefined): NewWorktreeAgentOption {
@@ -11,6 +11,7 @@ import {
} from '../../../../src/shared/github/links'
import { linkMobilePr } from '../../source-control/mobile-pr-link'
import { TEXT_INPUT_FONT_SIZE } from '../../platform/text-input-font-size'
import { useMobileTranslation } from '../../i18n/use-mobile-translation'
type Props = {
client: RpcClient | null
@@ -31,6 +32,7 @@ function parsePrReference(input: string): number | null {
// Use inline View inside sidebar ScrollView; nested BottomDrawer overlay clips, matching compose-form constraint.
export function MobileLinkPrForm({ client, worktreeId, onCancel, onLinked }: Props) {
useMobileColorScheme()
const t = useMobileTranslation()
const [input, setInput] = useState('')
const [submitting, setSubmitting] = useState(false)
const [error, setError] = useState<string | null>(null)
@@ -60,23 +62,30 @@ export function MobileLinkPrForm({ client, worktreeId, onCancel, onLinked }: Pro
return (
<View>
<View style={styles.headingRow}>
<Text style={styles.heading}>Link existing pull request</Text>
<Text style={styles.heading}>
{t('pullRequest.linkPrForm.title', 'Link existing pull request')}
</Text>
<Pressable
onPress={onCancel}
disabled={submitting}
accessibilityRole="button"
accessibilityLabel="Cancel"
accessibilityLabel={t('common.cancel', 'Cancel')}
hitSlop={8}
>
<Text style={styles.cancelText}>Cancel</Text>
<Text style={styles.cancelText}>{t('common.cancel', 'Cancel')}</Text>
</Pressable>
</View>
<Text style={styles.label}>PR number or GitHub URL</Text>
<Text style={styles.label}>
{t('pullRequest.linkPrForm.referenceLabel', 'PR number or GitHub URL')}
</Text>
<TextInput
style={styles.input}
value={input}
onChangeText={setInput}
placeholder="#123 or https://github.com/owner/repo/pull/123"
placeholder={t(
'pullRequest.linkPrForm.referencePlaceholder',
'#123 or https://github.com/owner/repo/pull/123'
)}
placeholderTextColor={colors.textMuted}
autoCapitalize="none"
autoCorrect={false}
@@ -95,7 +104,11 @@ export function MobileLinkPrForm({ client, worktreeId, onCancel, onLinked }: Pro
{submitting ? (
<ActivityIndicator size="small" color={colors.bgBase} />
) : (
<Text style={styles.submitText}>{parsed ? `Link #${parsed}` : 'Link pull request'}</Text>
<Text style={styles.submitText}>
{parsed
? t('pullRequest.linkPrForm.linkNumber', 'Link #{{number}}', { number: parsed })
: t('pullRequest.linkPrForm.linkButton', 'Link pull request')}
</Text>
)}
</Pressable>
</View>
@@ -8,6 +8,7 @@ import type { RpcClient } from '../../transport/rpc-client'
import type { MobileGitStatusResult } from '../../source-control/mobile-git-status'
import type { MobilePrSidebarController } from '../../session/use-mobile-pr-sidebar-controller'
import { MobilePRSidebar } from '../MobilePRSidebar'
import { useMobileTranslation } from '../../i18n/use-mobile-translation'
type Props = {
client: RpcClient | null
@@ -36,6 +37,7 @@ export function MobilePrViewPanelBody({
workspaceLabel
}: Props) {
useMobileColorScheme()
const t = useMobileTranslation()
const insets = useSafeAreaInsets()
const sidebarState = !branchContextLoaded
@@ -43,12 +45,15 @@ export function MobilePrViewPanelBody({
: !isGithubRepo
? ({
kind: 'blocked',
message: 'Hosted review panel unavailable for this provider.'
message: t(
'pullRequest.prViewPanel.providerUnavailable',
'Hosted review panel unavailable for this provider.'
)
} as const)
: branch === null
? ({
kind: 'error',
message: 'Current branch unavailable.'
message: t('pullRequest.prViewPanel.branchUnavailable', 'Current branch unavailable.')
} as const)
: controller.prSidebarState
@@ -13,6 +13,7 @@ import { MOBILE_GITHUB_MARK_PR_READY_RUNTIME_CAPABILITY } from '../../../../src/
import { useRouteCapability } from '../../transport/use-workspace-client'
import { presentPrMerge, resolvePrActionAvailability } from './pr-actions-state'
import { prActionsStyles as styles } from './pr-actions-styles'
import { useMobileTranslation } from '../../i18n/use-mobile-translation'
type Props = {
pr: PRInfo
@@ -28,6 +29,8 @@ type Confirm =
| { kind: 'merge'; method: GitHubPRMergeMethod }
| { kind: 'state'; state: 'open' | 'closed' }
type UnlinkError = { kind: 'host'; message: string } | { kind: 'failed' }
// Merge leads; Close/Reopen+Unlink share secondary row. Self-explanatory buttons need no mobile section header.
export function PRActionsSection({
pr,
@@ -38,10 +41,11 @@ export function PRActionsSection({
onRecheck
}: Props) {
useMobileColorScheme()
const t = useMobileTranslation()
const [confirm, setConfirm] = useState<Confirm | null>(null)
const [unlinking, setUnlinking] = useState(false)
// Local unlink errors — unlink is not routed through the actions engine.
const [unlinkError, setUnlinkError] = useState<string | null>(null)
const [unlinkError, setUnlinkError] = useState<UnlinkError | null>(null)
// Use repo’s default merge method to keep mobile one-tap without narrow method picker.
const mergeMethods = resolveGitHubPRMergeMethods(pr.mergeMethodSettings ?? null)
@@ -62,7 +66,11 @@ export function PRActionsSection({
const unlinkBusy = unlinking || mergeBusy || autoMergeBusy || stateBusy
const showAutoMerge = merge.showAutoMerge
const showSecondary = avail.canClose || avail.canReopen || avail.canUnlink
const actionError = unlinkError ?? actions.error
const actionError = unlinkError
? unlinkError.kind === 'host'
? unlinkError.message
: t('pullRequest.prActionsSection.unlinkFailed', 'Failed to unlink pull request.')
: actions.error
const unlink = useCallback(async (): Promise<void> => {
if (!client || unlinking) {
@@ -75,10 +83,12 @@ export function PRActionsSection({
if (outcome.ok) {
onUnlinked()
} else {
setUnlinkError(outcome.error)
setUnlinkError({ kind: 'host', message: outcome.error })
}
} catch (err) {
setUnlinkError(err instanceof Error ? err.message : 'Failed to unlink pull request.')
setUnlinkError(
err instanceof Error ? { kind: 'host', message: err.message } : { kind: 'failed' }
)
} finally {
setUnlinking(false)
}
@@ -87,22 +97,30 @@ export function PRActionsSection({
const confirmCopy = (): { title: string; message: string; confirmLabel: string } => {
if (confirm?.kind === 'merge') {
return {
title: 'Merge pull request?',
title: t('pullRequest.prActionsSection.mergeConfirmTitle', 'Merge pull request?'),
message: merge.confirmMessage,
confirmLabel: 'Merge'
confirmLabel: t('pullRequest.prActionsSection.mergeButton', 'Merge')
}
}
if (confirm?.kind === 'state' && confirm.state === 'closed') {
return {
title: 'Close pull request?',
message: `#${pr.number} will be closed without merging.`,
confirmLabel: 'Close'
title: t('pullRequest.prActionsSection.closeConfirmTitle', 'Close pull request?'),
message: t(
'pullRequest.prActionsSection.closeConfirmMessage',
'#{{number}} will be closed without merging.',
{ number: pr.number }
),
confirmLabel: t('common.close', 'Close')
}
}
return {
title: 'Reopen pull request?',
message: `#${pr.number} will be reopened.`,
confirmLabel: 'Reopen'
title: t('pullRequest.prActionsSection.reopenConfirmTitle', 'Reopen pull request?'),
message: t(
'pullRequest.prActionsSection.reopenConfirmMessage',
'#{{number}} will be reopened.',
{ number: pr.number }
),
confirmLabel: t('pullRequest.prActionsSection.reopenButton', 'Reopen')
}
}
@@ -136,7 +154,10 @@ export function PRActionsSection({
}}
disabled={mergeBusy || !merge.enabled}
accessibilityRole="button"
accessibilityLabel="Merge pull request"
accessibilityLabel={t(
'pullRequest.prActionsSection.mergeAccessibilityLabel',
'Merge pull request'
)}
accessibilityHint={merge.hint ?? undefined}
>
{mergeBusy ? (
@@ -155,9 +176,14 @@ export function PRActionsSection({
onPress={() => onRecheck()}
hitSlop={8}
accessibilityRole="button"
accessibilityLabel="Recheck merge status"
accessibilityLabel={t(
'pullRequest.prActionsSection.recheckAccessibilityLabel',
'Recheck merge status'
)}
>
<Text style={styles.recheckText}>Recheck</Text>
<Text style={styles.recheckText}>
{t('pullRequest.prActionsSection.recheckButton', 'Recheck')}
</Text>
</Pressable>
</View>
) : null}
@@ -175,16 +201,23 @@ export function PRActionsSection({
}}
disabled={stateBusy}
accessibilityRole="button"
accessibilityLabel="Mark pull request ready for review"
accessibilityLabel={t(
'pullRequest.prActionsSection.markReadyAccessibilityLabel',
'Mark pull request ready for review'
)}
>
{stateBusy ? <ActivityIndicator color={colors.textSecondary} /> : null}
<Text style={styles.actionButtonText}>Ready for review</Text>
<Text style={styles.actionButtonText}>
{t('pullRequest.prActionsSection.markReadyButton', 'Ready for review')}
</Text>
</Pressable>
) : null}
{showAutoMerge ? (
<View style={styles.toggleRow}>
<Text style={styles.toggleLabel}>Auto-merge when ready</Text>
<Text style={styles.toggleLabel}>
{t('pullRequest.prActionsSection.autoMergeLabel', 'Auto-merge when ready')}
</Text>
<Pressable
style={[styles.togglePill, autoMerge && styles.togglePillOn]}
onPress={() => {
@@ -194,13 +227,18 @@ export function PRActionsSection({
disabled={autoMergeBusy}
accessibilityRole="switch"
accessibilityState={{ checked: autoMerge }}
accessibilityLabel="Toggle auto-merge"
accessibilityLabel={t(
'pullRequest.prActionsSection.autoMergeAccessibilityLabel',
'Toggle auto-merge'
)}
>
{autoMergeBusy ? (
<ActivityIndicator color={colors.textSecondary} />
) : (
<Text style={[styles.togglePillText, autoMerge && styles.togglePillTextOn]}>
{autoMerge ? 'On' : 'Off'}
{autoMerge
? t('pullRequest.prActionsSection.autoMergeOn', 'On')
: t('pullRequest.prActionsSection.autoMergeOff', 'Off')}
</Text>
)}
</Pressable>
@@ -222,7 +260,14 @@ export function PRActionsSection({
}}
disabled={stateBusy}
accessibilityRole="button"
accessibilityLabel={avail.canClose ? 'Close pull request' : 'Reopen pull request'}
accessibilityLabel={
avail.canClose
? t('pullRequest.prActionsSection.closeAccessibilityLabel', 'Close pull request')
: t(
'pullRequest.prActionsSection.reopenAccessibilityLabel',
'Reopen pull request'
)
}
>
{stateBusy ? <ActivityIndicator color={colors.textSecondary} /> : null}
<Text
@@ -231,7 +276,9 @@ export function PRActionsSection({
avail.canClose && styles.actionButtonDestructiveText
]}
>
{avail.canClose ? 'Close' : 'Reopen'}
{avail.canClose
? t('common.close', 'Close')
: t('pullRequest.prActionsSection.reopenButton', 'Reopen')}
</Text>
</Pressable>
) : null}
@@ -245,14 +292,19 @@ export function PRActionsSection({
onPress={() => void unlink()}
disabled={unlinkBusy}
accessibilityRole="button"
accessibilityLabel="Unlink pull request"
accessibilityLabel={t(
'pullRequest.prActionsSection.unlinkAccessibilityLabel',
'Unlink pull request'
)}
>
{unlinking ? (
<ActivityIndicator color={colors.textSecondary} />
) : (
<Link2Off size={16} color={colors.textSecondary} strokeWidth={2.2} />
)}
<Text style={styles.actionButtonText}>Unlink</Text>
<Text style={styles.actionButtonText}>
{t('pullRequest.prActionsSection.unlinkButton', 'Unlink')}
</Text>
</Pressable>
) : null}
</View>
@@ -4,6 +4,7 @@ import { useMobileColorScheme } from '../../theme/use-mobile-color-scheme'
import type { PRCheckRunDetails } from '../../../../src/shared/github/check-types'
import { presentCheckDetail, type CheckDetailJob } from './pr-check-detail-content'
import { mobilePrSidebarStyles as styles } from './mobile-pr-sidebar-styles'
import { useMobileTranslation } from '../../i18n/use-mobile-translation'
// Cache detail after first expansion; loading/error track only in-flight read.
export type DetailEntry =
@@ -14,6 +15,7 @@ export type DetailEntry =
// Match desktop check conclusion/summary/annotations/failed jobs; keep output muted and scrollable to bound sidebar layout.
export function PRCheckDetailView({ entry }: { entry: DetailEntry | undefined }) {
useMobileColorScheme()
const t = useMobileTranslation()
if (!entry || entry.status === 'loading') {
return (
<View style={styles.checkDetailArea}>
@@ -31,7 +33,9 @@ export function PRCheckDetailView({ entry }: { entry: DetailEntry | undefined })
if (!entry.details) {
return (
<View style={styles.checkDetailArea}>
<Text style={styles.checkDetailText}>No details available.</Text>
<Text style={styles.checkDetailText}>
{t('pullRequest.prCheckDetail.noDetails', 'No details available.')}
</Text>
</View>
)
}
@@ -45,7 +49,9 @@ export function PRCheckDetailView({ entry }: { entry: DetailEntry | undefined })
return (
<View style={styles.checkDetailArea}>
{isEmpty ? (
<Text style={styles.checkDetailText}>No details available.</Text>
<Text style={styles.checkDetailText}>
{t('pullRequest.prCheckDetail.noDetails', 'No details available.')}
</Text>
) : (
<>
{content.summaryLines.map((line, index) => (
@@ -55,7 +61,9 @@ export function PRCheckDetailView({ entry }: { entry: DetailEntry | undefined })
))}
{content.annotations.length > 0 ? (
<View style={styles.checkDetailGroup}>
<Text style={styles.checkDetailGroupLabel}>Annotations</Text>
<Text style={styles.checkDetailGroupLabel}>
{t('pullRequest.prCheckDetail.annotations', 'Annotations')}
</Text>
{content.annotations.map((annotation, index) => (
<View key={index}>
<Text style={styles.checkDetailLocator} numberOfLines={1}>
@@ -69,7 +77,12 @@ export function PRCheckDetailView({ entry }: { entry: DetailEntry | undefined })
</View>
))}
{content.annotationsTruncated ? (
<Text style={styles.checkDetailText}>Showing first 20 annotations</Text>
<Text style={styles.checkDetailText}>
{t(
'pullRequest.prCheckDetail.annotationsTruncated',
'Showing first 20 annotations'
)}
</Text>
) : null}
</View>
) : null}
@@ -80,7 +93,9 @@ export function PRCheckDetailView({ entry }: { entry: DetailEntry | undefined })
<JobRow key={index} job={job} />
))}
{content.jobsTruncated ? (
<Text style={styles.checkDetailText}>Showing first 100 jobs</Text>
<Text style={styles.checkDetailText}>
{t('pullRequest.prCheckDetail.jobsTruncated', 'Showing first 100 jobs')}
</Text>
) : null}
</View>
) : null}
@@ -4,6 +4,9 @@ import { openExternalLink } from '../../platform/external-link'
import { ChevronDown, ChevronRight, ExternalLink, RotateCw, Sparkles } from 'lucide-react-native'
import { colors } from '../../theme/mobile-theme'
import { useMobileColorScheme } from '../../theme/use-mobile-color-scheme'
import { translate } from '../../i18n/mobile-i18n'
import type { Translate } from '../../i18n/mobile-i18n'
import { useMobileTranslation } from '../../i18n/use-mobile-translation'
import type { PRCheckDetail } from '../../../../src/shared/github/check-types'
import type { RpcClient } from '../../transport/rpc-client'
import { fetchPRCheckDetails, type GitHubPrRepoSlug } from '../../session/github-pr-rpc'
@@ -13,12 +16,12 @@ import {
checkOutcomeToken,
firstFailingCheckKey,
prCheckKey,
prChecksSummaryLabel,
summarizePRChecks
summarizePRChecks,
type PRChecksSummary
} from './pr-checks-presentation'
import { sortChecksBySeverity } from '../../../../src/shared/pr-check-severity-order'
import { getBrokenChecks } from '../../../../src/shared/pr-checks-fix-prompt'
import { getCheckStatusLabel } from '../../../../src/shared/pr-check-status-label'
import { getCheckConclusion } from '../../../../src/shared/pr-check-status-label'
import { statusColor } from './pr-sidebar-status-color'
import { PRSection } from './PRSection'
import { PRCheckDetailView, type DetailEntry } from './PRCheckDetail'
@@ -50,6 +53,62 @@ type Props = {
triage?: PrChecksTriage
}
function checksSummaryLabel(summary: PRChecksSummary, checksError: string | null, t: Translate) {
if (checksError !== null) {
return t('pullRequest.checksSection.unavailable', 'Checks unavailable')
}
if (summary.total === 0) {
return t('pullRequest.checksSection.noChecks', 'No checks')
}
const parts: string[] = []
if (summary.failed > 0) {
parts.push(
t('pullRequest.checksSection.failing', '{{count}} failing', { count: summary.failed })
)
}
if (summary.pending > 0) {
parts.push(
t('pullRequest.checksSection.pendingCount', '{{count}} pending', { count: summary.pending })
)
}
if (summary.passed > 0) {
parts.push(t('pullRequest.checksSection.passed', '{{count}} passed', { count: summary.passed }))
}
const neutral = summary.total - summary.failed - summary.pending - summary.passed
if (neutral > 0) {
parts.push(t('pullRequest.checksSection.neutralCount', '{{count}} neutral', { count: neutral }))
}
return parts.join(' · ')
}
function checkStatusLabel(check: PRCheckDetail, t: Translate): string {
switch (getCheckConclusion(check)) {
case 'success':
return t('pullRequest.checksSection.successful', 'Successful')
case 'failure':
return t('pullRequest.checksSection.failed', 'Failed')
case 'cancelled':
return t('pullRequest.checksSection.cancelled', 'Cancelled')
case 'timed_out':
return t('pullRequest.checksSection.timedOut', 'Timed out')
case 'action_required':
return t('pullRequest.checksSection.actionRequired', 'Action required')
case 'neutral':
return t('pullRequest.checksSection.neutral', 'Neutral')
case 'skipped':
return t('pullRequest.checksSection.skipped', 'Skipped')
case 'pending':
break
}
if (check.status === 'queued') {
return t('pullRequest.checksSection.queued', 'Queued')
}
if (check.status === 'in_progress') {
return t('pullRequest.checksSection.inProgress', 'In progress')
}
return t('pullRequest.checksSection.pending', 'Pending')
}
// Sort summary/check rows; lazily cache expanded github.prCheckDetails per check key. Rerun action is separate.
export function PRChecksSection({
checks,
@@ -61,9 +120,10 @@ export function PRChecksSection({
triage
}: Props) {
useMobileColorScheme()
const t = useMobileTranslation()
const sorted = sortChecksBySeverity(checks)
// Desktop gates Fix and Rerun on the checks the host reruns and the prompt lists.
const brokenCount = getBrokenChecks(checks).length
const count = getBrokenChecks(checks).length
const summary = summarizePRChecks(checks)
const rerunBusy = actions?.isBusy({ kind: 'rerun' }) ?? false
const [expanded, setExpanded] = useState<Set<string>>(new Set())
@@ -90,7 +150,13 @@ export function PRChecksSection({
// Set error detail on rejection so loading cannot spin forever.
entry = {
status: 'error',
message: err instanceof Error ? err.message : 'Failed to load check details'
message:
err instanceof Error
? err.message
: translate(
'pullRequest.checksSection.loadDetailsFailed',
'Failed to load check details'
)
}
}
setDetailCache((prev) => ({ ...prev, [key]: entry }))
@@ -150,7 +216,7 @@ export function PRChecksSection({
return (
<PRSection
title="Checks"
title={t('pullRequest.checksSection.title', 'Checks')}
trailing={
<>
<Text
@@ -159,16 +225,19 @@ export function PRChecksSection({
{ color: statusColor(checkOutcomeToken(summary.outcome)) }
]}
>
{prChecksSummaryLabel(summary, checksError)}
{checksSummaryLabel(summary, checksError, t)}
</Text>
{/* Rerun is offered only when something failed; spinner-in-place while in-flight. */}
{actions && brokenCount > 0 ? (
{actions && count > 0 ? (
<Pressable
style={styles.iconButton}
onPress={() => actions.rerunFailingChecks()}
disabled={rerunBusy}
accessibilityRole="button"
accessibilityLabel="Rerun failing checks"
accessibilityLabel={t(
'pullRequest.checksSection.rerunFailing',
'Rerun failing checks'
)}
>
{rerunBusy ? (
<ActivityIndicator color={colors.textSecondary} />
@@ -181,14 +250,23 @@ export function PRChecksSection({
}
>
{/* Lead with desktop-style failing summary and Fix action so actionable state comes first. */}
{triage && brokenCount > 0 ? (
{triage && count > 0 ? (
<View style={triageStyles.triageStrip}>
<View style={triageStyles.triageStripText}>
<Text style={triageStyles.triageStripTitle} numberOfLines={1}>
{brokenCount} failing check{brokenCount === 1 ? '' : 's'}
{count === 1
? t('pullRequest.checksSection.failingChecks_one', '{{count}} failing check', {
count
})
: t('pullRequest.checksSection.failingChecks_other', '{{count}} failing checks', {
count
})}
</Text>
<Text style={triageStyles.triageStripSubtitle} numberOfLines={1}>
Inspect details or start an AI fix pass.
{t(
'pullRequest.checksSection.inspectOrFix',
'Inspect details or start an AI fix pass.'
)}
</Text>
</View>
<Pressable
@@ -196,14 +274,19 @@ export function PRChecksSection({
onPress={triage.fixChecks}
disabled={triage.isBusy || triage.availability !== 'available'}
accessibilityRole="button"
accessibilityLabel="Fix failing checks with AI"
accessibilityLabel={t(
'pullRequest.checksSection.fixFailing',
'Fix failing checks with AI'
)}
>
{triage.isBusy ? (
<ActivityIndicator color={colors.textSecondary} />
) : (
<Sparkles size={13} color={colors.textSecondary} strokeWidth={2.2} />
)}
<Text style={triageStyles.triageStripButtonText}>Fix</Text>
<Text style={triageStyles.triageStripButtonText}>
{t('pullRequest.checksSection.fix', 'Fix')}
</Text>
</Pressable>
</View>
) : null}
@@ -230,7 +313,11 @@ export function PRChecksSection({
style={styles.row}
onPress={() => toggle(check)}
accessibilityRole="button"
accessibilityLabel={`${check.name} check details`}
accessibilityLabel={t(
'pullRequest.checksSection.checkDetails',
'{{name}} check details',
{ name: check.name }
)}
>
<Chevron size={14} color={colors.textSecondary} strokeWidth={2.2} />
<View style={[styles.statusDot, { backgroundColor: statusColor(token) }]} />
@@ -241,7 +328,7 @@ export function PRChecksSection({
</View>
{/* Show status word/open-on-host without requiring expansion, matching desktop. */}
<Text style={[styles.rowStatus, { color: statusColor(token) }]} numberOfLines={1}>
{getCheckStatusLabel(check)}
{checkStatusLabel(check, t)}
</Text>
{url ? (
<Pressable
@@ -249,7 +336,11 @@ export function PRChecksSection({
onPress={() => openExternalLink(url)}
hitSlop={6}
accessibilityRole="button"
accessibilityLabel={`Open ${check.name} on the web`}
accessibilityLabel={t(
'pullRequest.checksSection.openOnWeb',
'Open {{name}} on the web',
{ name: check.name }
)}
>
<ExternalLink size={13} color={colors.textSecondary} strokeWidth={2.2} />
</Pressable>
@@ -9,11 +9,12 @@ import type {
} from '../../../../src/shared/github/comment-types'
import { colors } from '../../theme/mobile-theme'
import { useMobileColorScheme } from '../../theme/use-mobile-color-scheme'
import type { Translate } from '../../i18n/mobile-i18n'
import { useMobileTranslation } from '../../i18n/use-mobile-translation'
import { canEditComment, isResolvableComment } from '../../session/pr-comment-actions'
import { ConfirmModal } from '../ConfirmModal'
import { CommentMarkdown } from './CommentMarkdown'
import { PRCommentComposer } from './PRCommentComposer'
import { formatPrCommentRelativeTime } from '../../../../src/shared/pr-comment-time'
import { prCommentsStyles as styles } from './pr-comments-styles'
export type PRCommentRepoSlug = { owner: string; repo: string; host?: string }
@@ -43,6 +44,49 @@ const REACTION_EMOJI: Record<GitHubReactionContent, string> = {
eyes: '👀'
}
function commentRelativeTime(iso: string, now: number, t: Translate): string {
const timestamp = Date.parse(iso)
if (Number.isNaN(timestamp)) {
return ''
}
const delta = now - timestamp
if (delta < 60_000) {
return t('pullRequest.commentCard.justNow', 'just now')
}
const minutes = Math.floor(delta / 60_000)
if (minutes < 60) {
const count = minutes
return count === 1
? t('pullRequest.commentCard.minutesAgo_one', '{{count}}m ago', { count })
: t('pullRequest.commentCard.minutesAgo_other', '{{count}}m ago', { count })
}
const hours = Math.floor(minutes / 60)
if (hours < 24) {
const count = hours
return count === 1
? t('pullRequest.commentCard.hoursAgo_one', '{{count}}h ago', { count })
: t('pullRequest.commentCard.hoursAgo_other', '{{count}}h ago', { count })
}
const days = Math.floor(hours / 24)
if (days < 30) {
const count = days
return count === 1
? t('pullRequest.commentCard.daysAgo_one', '{{count}}d ago', { count })
: t('pullRequest.commentCard.daysAgo_other', '{{count}}d ago', { count })
}
const months = Math.floor(days / 30)
if (months < 12) {
const count = months
return count === 1
? t('pullRequest.commentCard.monthsAgo_one', '{{count}}mo ago', { count })
: t('pullRequest.commentCard.monthsAgo_other', '{{count}}mo ago', { count })
}
const count = Math.floor(months / 12)
return count === 1
? t('pullRequest.commentCard.yearsAgo_one', '{{count}}y ago', { count })
: t('pullRequest.commentCard.yearsAgo_other', '{{count}}y ago', { count })
}
function Reactions({ reactions }: { reactions?: GitHubReaction[] }) {
useMobileColorScheme()
const visible = (reactions ?? []).filter((r) => r.count > 0)
@@ -74,6 +118,7 @@ export const PRCommentCard = memo(function PRCommentCard({
now: number
}) {
useMobileColorScheme()
const t = useMobileTranslation()
const [replyOpen, setReplyOpen] = useState(false)
const [editOpen, setEditOpen] = useState(false)
const [confirmDelete, setConfirmDelete] = useState(false)
@@ -126,7 +171,7 @@ export const PRCommentCard = memo(function PRCommentCard({
>
{comment.author}
</Text>
<Text style={styles.time}>· {formatPrCommentRelativeTime(comment.createdAt, now)}</Text>
<Text style={styles.time}>· {commentRelativeTime(comment.createdAt, now, t)}</Text>
{fileLabel ? (
<Text style={styles.path} numberOfLines={1}>
{fileLabel}
@@ -134,7 +179,9 @@ export const PRCommentCard = memo(function PRCommentCard({
) : null}
{comment.isResolved ? (
<View style={styles.resolvedChip}>
<Text style={styles.resolvedChipText}>resolved</Text>
<Text style={styles.resolvedChipText}>
{t('pullRequest.commentCard.resolved', 'resolved')}
</Text>
</View>
) : null}
{comment.url ? (
@@ -143,7 +190,7 @@ export const PRCommentCard = memo(function PRCommentCard({
onPress={() => openExternalLink(comment.url)}
hitSlop={8}
accessibilityRole="button"
accessibilityLabel="Open comment on GitHub"
accessibilityLabel={t('pullRequest.commentCard.openOnGitHub', 'Open comment on GitHub')}
>
<ExternalLink size={14} color={colors.textSecondary} strokeWidth={2.2} />
</Pressable>
@@ -152,8 +199,8 @@ export const PRCommentCard = memo(function PRCommentCard({
{editOpen && actions ? (
<View style={styles.composer}>
<PRCommentComposer
placeholder="Edit comment…"
submitLabel="Save"
placeholder={t('pullRequest.commentCard.editPlaceholder', 'Edit comment…')}
submitLabel={t('common.save', 'Save')}
submitting={editBusy}
initialBody={comment.body}
onSubmit={submitEdit}
@@ -175,10 +222,12 @@ export const PRCommentCard = memo(function PRCommentCard({
disabled={replyBusy}
hitSlop={6}
accessibilityRole="button"
accessibilityLabel="Reply to comment"
accessibilityLabel={t('pullRequest.commentCard.replyToComment', 'Reply to comment')}
>
<CornerDownRight size={13} color={colors.textSecondary} strokeWidth={2.2} />
<Text style={styles.actionButtonText}>Reply</Text>
<Text style={styles.actionButtonText}>
{t('pullRequest.commentCard.reply', 'Reply')}
</Text>
</Pressable>
{canMutate ? (
<Pressable
@@ -191,10 +240,10 @@ export const PRCommentCard = memo(function PRCommentCard({
disabled={editBusy}
hitSlop={6}
accessibilityRole="button"
accessibilityLabel="Edit comment"
accessibilityLabel={t('pullRequest.commentCard.editComment', 'Edit comment')}
>
<Pencil size={13} color={colors.textSecondary} strokeWidth={2.2} />
<Text style={styles.actionButtonText}>Edit</Text>
<Text style={styles.actionButtonText}>{t('common.edit', 'Edit')}</Text>
</Pressable>
) : null}
{canMutate ? (
@@ -204,10 +253,12 @@ export const PRCommentCard = memo(function PRCommentCard({
disabled={deleteBusy}
hitSlop={6}
accessibilityRole="button"
accessibilityLabel="Delete comment"
accessibilityLabel={t('pullRequest.commentCard.deleteComment', 'Delete comment')}
>
<Trash2 size={13} color={colors.textSecondary} strokeWidth={2.2} />
<Text style={styles.actionButtonText}>{deleteBusy ? '…' : 'Delete'}</Text>
<Text style={styles.actionButtonText}>
{deleteBusy ? '…' : t('common.delete', 'Delete')}
</Text>
</Pressable>
) : null}
{canResolve ? (
@@ -217,7 +268,11 @@ export const PRCommentCard = memo(function PRCommentCard({
disabled={resolveBusy}
hitSlop={6}
accessibilityRole="button"
accessibilityLabel={comment.isResolved ? 'Unresolve thread' : 'Resolve thread'}
accessibilityLabel={
comment.isResolved
? t('pullRequest.commentCard.unresolveThread', 'Unresolve thread')
: t('pullRequest.commentCard.resolveThread', 'Resolve thread')
}
>
{comment.isResolved ? (
<Undo2 size={13} color={colors.textSecondary} strokeWidth={2.2} />
@@ -225,7 +280,11 @@ export const PRCommentCard = memo(function PRCommentCard({
<Check size={13} color={colors.textSecondary} strokeWidth={2.2} />
)}
<Text style={styles.actionButtonText}>
{resolveBusy ? '…' : comment.isResolved ? 'Unresolve' : 'Resolve'}
{resolveBusy
? '…'
: comment.isResolved
? t('pullRequest.commentCard.unresolve', 'Unresolve')
: t('pullRequest.commentCard.resolve', 'Resolve')}
</Text>
</Pressable>
) : null}
@@ -234,8 +293,8 @@ export const PRCommentCard = memo(function PRCommentCard({
{replyOpen && !editOpen && actions ? (
<View style={styles.composer}>
<PRCommentComposer
placeholder="Write a reply…"
submitLabel="Reply"
placeholder={t('pullRequest.commentCard.replyPlaceholder', 'Write a reply…')}
submitLabel={t('pullRequest.commentCard.reply', 'Reply')}
submitting={replyBusy}
onSubmit={submitReply}
onCancel={() => setReplyOpen(false)}
@@ -246,9 +305,12 @@ export const PRCommentCard = memo(function PRCommentCard({
{actions ? (
<ConfirmModal
visible={confirmDelete}
title="Delete comment?"
message="This permanently deletes the comment on GitHub."
confirmLabel="Delete"
title={t('pullRequest.commentCard.deleteConfirmTitle', 'Delete comment?')}
message={t(
'pullRequest.commentCard.deleteConfirmMessage',
'This permanently deletes the comment on GitHub.'
)}
confirmLabel={t('common.delete', 'Delete')}
destructive
onConfirm={() => void actions.deleteComment(comment.id)}
onCancel={() => setConfirmDelete(false)}
@@ -2,6 +2,7 @@ import { useRef, useState } from 'react'
import { ActivityIndicator, Pressable, Text, TextInput, View } from 'react-native'
import { colors } from '../../theme/mobile-theme'
import { useMobileColorScheme } from '../../theme/use-mobile-color-scheme'
import { useMobileTranslation } from '../../i18n/use-mobile-translation'
import { isSubmittableCommentBody } from '../../session/pr-comment-actions'
import { prCommentComposerStyles as styles } from './pr-comment-composer-styles'
@@ -28,6 +29,7 @@ export function PRCommentComposer({
autoFocus
}: Props) {
useMobileColorScheme()
const t = useMobileTranslation()
const [body, setBody] = useState(initialBody ?? '')
// Guard synchronously against double-submit before parent’s async submitting state rerenders.
const inFlightRef = useRef(false)
@@ -67,9 +69,9 @@ export function PRCommentComposer({
onPress={onCancel}
disabled={submitting}
accessibilityRole="button"
accessibilityLabel="Cancel"
accessibilityLabel={t('common.cancel', 'Cancel')}
>
<Text style={styles.cancelText}>Cancel</Text>
<Text style={styles.cancelText}>{t('common.cancel', 'Cancel')}</Text>
</Pressable>
) : null}
<Pressable
@@ -6,6 +6,8 @@ import type { GitHubWorkItemDetails } from '../../../../src/shared/github/work-i
import type { GitHubPrRepoSlug } from '../../session/github-pr-rpc'
import { colors } from '../../theme/mobile-theme'
import { useMobileColorScheme } from '../../theme/use-mobile-color-scheme'
import type { Translate } from '../../i18n/mobile-i18n'
import { useMobileTranslation } from '../../i18n/use-mobile-translation'
import { canAddRootComment } from '../../session/pr-comment-actions'
import { isPrSidebarDetailsPlaceholder } from '../../session/mobile-pr-sidebar-state'
import type { MobilePrCommentActions } from '../../session/use-mobile-pr-comment-actions'
@@ -18,10 +20,6 @@ import {
getPRCommentAudienceCounts,
type PRCommentAudienceFilter
} from '../../../../src/shared/pr-comment-audience'
import {
PR_COMMENT_AUDIENCE_FILTERS,
getPRCommentAudienceEmptyLabel
} from './pr-comment-audience-labels'
import {
getPRCommentGroupCount,
getPRCommentGroupId,
@@ -49,6 +47,25 @@ type Props = {
// Page comments in chunks: nested ScrollView cannot virtualize, and eager Markdown parse of whole timeline can ANR JS thread.
const COMMENT_PAGE = 12
function audienceFilters(t: Translate): { value: PRCommentAudienceFilter; label: string }[] {
return [
{ value: 'all', label: t('pullRequest.commentsSection.all', 'All') },
{ value: 'human', label: t('pullRequest.commentsSection.humans', 'Humans') },
{ value: 'bot', label: t('pullRequest.commentsSection.bots', 'Bots') }
]
}
function audienceEmptyLabel(filter: PRCommentAudienceFilter, t: Translate): string {
switch (filter) {
case 'bot':
return t('pullRequest.commentsSection.noBotComments', 'No bot comments.')
case 'human':
return t('pullRequest.commentsSection.noHumanComments', 'No human comments.')
case 'all':
return t('pullRequest.commentsSection.noComments', 'No comments yet.')
}
}
// Match desktop description/timeline with audience filter, threaded review replies, reactions and collapsible resolved threads.
export function PRCommentsSection({
details,
@@ -58,6 +75,7 @@ export function PRCommentsSection({
botAuthorOverrides
}: Props) {
useMobileColorScheme()
const t = useMobileTranslation()
// Null details means phase 2 loading; synthetic placeholder means failure, never empty success.
const loadingDetails = details === null
const detailsFailed = details != null && isPrSidebarDetailsPlaceholder(details)
@@ -111,25 +129,31 @@ export function PRCommentsSection({
}
const shownGroups = groups.slice(0, limit)
const remaining = groups.length - shownGroups.length
const count = Math.min(remaining, COMMENT_PAGE)
return (
<>
<PRSection title="Description">
<PRSection title={t('pullRequest.commentsSection.description', 'Description')}>
{loadingDetails ? (
<ActivityIndicator color={colors.textSecondary} />
) : detailsFailed ? (
<Text style={styles.noDescription}>
Could not load description. Tap refresh to try again.
{t(
'pullRequest.commentsSection.descriptionLoadFailed',
'Could not load description. Tap refresh to try again.'
)}
</Text>
) : body.trim() ? (
<CommentMarkdown content={body} variant="document" />
) : (
<Text style={styles.noDescription}>No description provided.</Text>
<Text style={styles.noDescription}>
{t('pullRequest.commentsSection.noDescription', 'No description provided.')}
</Text>
)}
</PRSection>
<PRSection
title="Comments"
title={t('pullRequest.commentsSection.title', 'Comments')}
trailing={
comments.length > 0 ? (
<View style={styles.countChip}>
@@ -141,16 +165,23 @@ export function PRCommentsSection({
{loadingDetails ? (
<ActivityIndicator color={colors.textSecondary} />
) : detailsFailed ? (
<Text style={styles.empty}>Could not load comments. Tap refresh to try again.</Text>
<Text style={styles.empty}>
{t(
'pullRequest.commentsSection.commentsLoadFailed',
'Could not load comments. Tap refresh to try again.'
)}
</Text>
) : (
<View style={styles.list}>
{comments.length === 0 ? (
<Text style={styles.empty}>No comments yet.</Text>
<Text style={styles.empty}>
{t('pullRequest.commentsSection.noComments', 'No comments yet.')}
</Text>
) : (
<>
{isPr ? (
<View style={styles.audienceTabs}>
{PR_COMMENT_AUDIENCE_FILTERS.map((tab) => {
{audienceFilters(t).map((tab) => {
const active = tab.value === filter
return (
<Pressable
@@ -176,7 +207,7 @@ export function PRCommentsSection({
</View>
) : null}
{visible.length === 0 ? (
<Text style={styles.empty}>{getPRCommentAudienceEmptyLabel(filter)}</Text>
<Text style={styles.empty}>{audienceEmptyLabel(filter, t)}</Text>
) : (
<>
{shownGroups.map((group) => (
@@ -194,8 +225,15 @@ export function PRCommentsSection({
accessibilityRole="button"
>
<Text style={styles.showMoreText}>
Show {Math.min(remaining, COMMENT_PAGE)} more
{remaining > COMMENT_PAGE ? ` of ${remaining}` : ''}
{remaining > COMMENT_PAGE
? t(
'pullRequest.commentsSection.showMoreOf',
'Show {{count}} more of {{remaining}}',
{ count, remaining }
)
: t('pullRequest.commentsSection.showMore', 'Show {{count}} more', {
count
})}
</Text>
</Pressable>
) : null}
@@ -207,8 +245,11 @@ export function PRCommentsSection({
{canComment && actions ? (
<View style={styles.rootComposer}>
<PRCommentComposer
placeholder="Add a comment…"
submitLabel="Comment"
placeholder={t(
'pullRequest.commentsSection.addCommentPlaceholder',
'Add a comment…'
)}
submitLabel={t('pullRequest.commentsSection.comment', 'Comment')}
submitting={actions.isRootBusy}
onSubmit={actions.addRootComment}
/>
@@ -231,6 +272,7 @@ function CommentGroupView({
now: number
}) {
useMobileColorScheme()
const t = useMobileTranslation()
const [expanded, setExpanded] = useState(false)
const cards =
group.kind === 'thread'
@@ -266,8 +308,29 @@ function CommentGroupView({
>
<Chevron size={14} color={colors.textSecondary} strokeWidth={2.2} />
<Text style={styles.resolvedHeaderText} numberOfLines={1}>
Resolved {group.kind === 'thread' ? 'thread' : 'comment'} by {root.author}
{count > 1 ? ` (${count})` : ''}
{group.kind === 'thread'
? count === 1
? t(
'pullRequest.commentsSection.resolvedThread_one',
'Resolved thread by {{author}}',
{ author: root.author, count }
)
: t(
'pullRequest.commentsSection.resolvedThread_other',
'Resolved thread by {{author}} ({{count}})',
{ author: root.author, count }
)
: count === 1
? t(
'pullRequest.commentsSection.resolvedComment_one',
'Resolved comment by {{author}}',
{ author: root.author, count }
)
: t(
'pullRequest.commentsSection.resolvedComment_other',
'Resolved comment by {{author}} ({{count}})',
{ author: root.author, count }
)}
</Text>
</Pressable>
{expanded ? <View style={shared.sectionBody}>{cards}</View> : null}
@@ -4,6 +4,7 @@ import { Check, Copy, FileWarning, Sparkles } from 'lucide-react-native'
import { useClipboardWriter } from '../../platform/clipboard'
import { colors } from '../../theme/mobile-theme'
import { useMobileColorScheme } from '../../theme/use-mobile-color-scheme'
import { useMobileTranslation } from '../../i18n/use-mobile-translation'
import type { PRInfo } from '../../../../src/shared/github/pull-request-types'
import { PRSection } from './PRSection'
import { resolveConflictDisplay } from './pr-conflict-presentation'
@@ -34,6 +35,7 @@ type Props = {
// Show hosted conflicts as paths or missing-list fallback, matching desktop conflict/notice components.
export function PRConflictingFilesSection({ pr, isRefreshing = false, triage }: Props) {
useMobileColorScheme()
const t = useMobileTranslation()
// Use clipboard seam: iOS custom scheme lacks page secure context, while Android https provides one.
const clipboard = useClipboardWriter()
// Track idle/success/failure: dropping pasteboard refusal used to look like successful copy, unlike Tasks error reporting.
@@ -52,12 +54,17 @@ export function PRConflictingFilesSection({ pr, isRefreshing = false, triage }:
if (!conflict) {
return null
}
let noticeBody = 'Conflict file details are unavailable'
let noticeBody = t(
'pullRequest.conflictingFilesSection.detailsUnavailable',
'Conflict file details are unavailable'
)
if (isRefreshing) {
noticeBody = 'Refreshing conflict details…'
noticeBody = t('pullRequest.conflictingFilesSection.refreshing', 'Refreshing conflict details…')
} else if (conflict.localMergeClean) {
noticeBody =
noticeBody = t(
'pullRequest.conflictingFilesSection.localMergeClean',
'GitHub reports conflicts, but local Git did not reproduce them. Refresh the PR or push the branch to recalculate mergeability.'
)
}
const copyRefreshCommands = async () => {
@@ -82,28 +89,56 @@ export function PRConflictingFilesSection({ pr, isRefreshing = false, triage }:
const copyLabel =
copyState === 'copied'
? 'Copied'
? t('common.copied', 'Copied')
: copyState === 'failed'
? 'Failed to copy text'
: 'Copy commands'
? t('pullRequest.conflictingFilesSection.copyFailed', 'Failed to copy text')
: t('pullRequest.conflictingFilesSection.copyCommands', 'Copy commands')
const count = conflict.commitsBehind
// Preserve the commit's styling when a translation moves its placeholder.
const behindParts =
count !== null && conflict.baseCommit !== null
? (count === 1
? t(
'pullRequest.conflictingFilesSection.commitsBehind_one',
'{{count}} commit behind (base commit: {{baseCommit}})',
{ count, baseCommit: '\uFFFC' }
)
: t(
'pullRequest.conflictingFilesSection.commitsBehind_other',
'{{count}} commits behind (base commit: {{baseCommit}})',
{ count, baseCommit: '\uFFFC' }
)
).split('\uFFFC')
: null
return (
<PRSection title="Conflicts">
{conflict.commitsBehind !== null && conflict.baseCommit !== null ? (
<PRSection title={t('pullRequest.conflictingFilesSection.title', 'Conflicts')}>
{behindParts ? (
<Text style={styles.meta}>
{conflict.commitsBehind} commit{conflict.commitsBehind === 1 ? '' : 's'} behind (base
commit: <Text style={styles.metaMono}>{conflict.baseCommit}</Text>)
{behindParts[0]}
<Text style={styles.metaMono}>{conflict.baseCommit}</Text>
{behindParts.slice(1).join('')}
</Text>
) : null}
{conflict.fileDetailsUnavailable ? (
<View>
<Text style={styles.noticeTitle}>This branch has conflicts that must be resolved</Text>
<Text style={styles.noticeTitle}>
{t(
'pullRequest.conflictingFilesSection.mustResolve',
'This branch has conflicts that must be resolved'
)}
</Text>
<Text style={styles.noticeBody}>{noticeBody}</Text>
{conflict.mergeabilityRefreshCommands ? (
<View style={styles.commandBox}>
<View style={styles.commandHeader}>
<Text style={styles.commandLabel}>Run from this worktree</Text>
<Text style={styles.commandLabel}>
{t(
'pullRequest.conflictingFilesSection.runFromWorktree',
'Run from this worktree'
)}
</Text>
<Pressable
style={({ pressed }) => [
styles.copyCommandButton,
@@ -111,7 +146,10 @@ export function PRConflictingFilesSection({ pr, isRefreshing = false, triage }:
]}
onPress={() => void copyRefreshCommands()}
accessibilityRole="button"
accessibilityLabel="Copy mergeability refresh commands"
accessibilityLabel={t(
'pullRequest.conflictingFilesSection.copyRefreshCommands',
'Copy mergeability refresh commands'
)}
>
{copyState === 'copied' ? (
<Check size={13} color={colors.textPrimary} strokeWidth={2.2} />
@@ -131,7 +169,9 @@ export function PRConflictingFilesSection({ pr, isRefreshing = false, triage }:
<View>
<View style={styles.filesHeader}>
<FileWarning size={14} color={colors.textSecondary} strokeWidth={2} />
<Text style={styles.filesHeaderText}>Conflicting files</Text>
<Text style={styles.filesHeaderText}>
{t('pullRequest.conflictingFilesSection.filesHeading', 'Conflicting files')}
</Text>
</View>
<ScrollView
style={styles.fileList}
@@ -159,14 +199,19 @@ export function PRConflictingFilesSection({ pr, isRefreshing = false, triage }:
onPress={triage.resolveConflicts}
disabled={triage.isBusy || triage.availability !== 'available'}
accessibilityRole="button"
accessibilityLabel="Resolve conflicts with AI"
accessibilityLabel={t(
'pullRequest.conflictingFilesSection.resolveWithAi',
'Resolve conflicts with AI'
)}
>
{triage.isBusy ? (
<ActivityIndicator color={colors.textSecondary} />
) : (
<Sparkles size={14} color={colors.textSecondary} strokeWidth={2.2} />
)}
<Text style={triageStyles.triageButtonText}>Resolve conflicts with AI</Text>
<Text style={triageStyles.triageButtonText}>
{t('pullRequest.conflictingFilesSection.resolveWithAi', 'Resolve conflicts with AI')}
</Text>
</Pressable>
<AgentLaunchNotice
availability={triage.availability}
@@ -3,6 +3,8 @@ import { ActivityIndicator, Pressable, Text, View } from 'react-native'
import { UserPlus, X } from 'lucide-react-native'
import { colors } from '../../theme/mobile-theme'
import { useMobileColorScheme } from '../../theme/use-mobile-color-scheme'
import type { Translate } from '../../i18n/mobile-i18n'
import { useMobileTranslation } from '../../i18n/use-mobile-translation'
import type { GitHubWorkItemDetails } from '../../../../src/shared/github/work-item-types'
import type { RpcClient } from '../../transport/rpc-client'
import type { MobilePrActions } from '../../session/use-mobile-pr-actions'
@@ -19,18 +21,52 @@ type Props = {
worktreeId: string
}
function reviewerStateLabel(
item: GitHubWorkItemDetails['item'],
login: string,
t: Translate
): string {
const normalizedLogin = login.toLowerCase()
if (item.reviewRequests?.some((user) => user.login.trim().toLowerCase() === normalizedLogin)) {
return t('pullRequest.reviewersSection.requested', 'Requested')
}
const review = item.latestReviews?.find(
(entry) => entry.login.trim().toLowerCase() === normalizedLogin
)
switch (review?.state) {
case 'APPROVED':
return t('pullRequest.reviewersSection.approved', 'Approved')
case 'CHANGES_REQUESTED':
return t('pullRequest.reviewersSection.changesRequested', 'Changes requested')
case 'COMMENTED':
return t('pullRequest.reviewersSection.commented', 'Commented')
case 'DISMISSED':
return t('pullRequest.reviewersSection.dismissed', 'Dismissed')
case 'PENDING':
return t('pullRequest.reviewersSection.pending', 'Pending')
case undefined:
case null:
default:
return t('pullRequest.reviewersSection.reviewed', 'Reviewed')
}
}
// Requested reviewers/latest verdicts with optimistic request/remove picker.
export function PRReviewersSection({ details, actions, client, worktreeId }: Props) {
useMobileColorScheme()
const t = useMobileTranslation()
// Null details means heavy phase 2 still loading; synthetic placeholder means failure, neither means no reviewers.
const loadingDetails = details === null
const detailsFailed = details != null && isPrSidebarDetailsPlaceholder(details)
const authoritativeRows = useMemo(
() =>
details?.item && !isPrSidebarDetailsPlaceholder(details)
? getGitHubPRReviewerRows(details.item)
? getGitHubPRReviewerRows(details.item).map((row) => ({
...row,
stateLabel: reviewerStateLabel(details.item, row.login, t)
}))
: [],
[details]
[details, t]
)
const [pickerOpen, setPickerOpen] = useState(false)
@@ -56,23 +92,32 @@ export function PRReviewersSection({ details, actions, client, worktreeId }: Pro
style={styles.iconButton}
onPress={() => setPickerOpen(true)}
accessibilityRole="button"
accessibilityLabel="Add or remove reviewers"
accessibilityLabel={t('pullRequest.reviewersSection.addOrRemove', 'Add or remove reviewers')}
>
<UserPlus size={16} color={colors.textSecondary} strokeWidth={2.2} />
</Pressable>
)
return (
<PRSection title="Reviewers" trailing={addButton}>
<PRSection title={t('pullRequest.reviewersSection.title', 'Reviewers')} trailing={addButton}>
{loadingDetails ? (
<View style={styles.reviewersStatus}>
<ActivityIndicator color={colors.textSecondary} />
<Text style={styles.emptyText}>Loading reviewers…</Text>
<Text style={styles.emptyText}>
{t('pullRequest.reviewersSection.loading', 'Loading reviewers…')}
</Text>
</View>
) : detailsFailed ? (
<Text style={styles.emptyText}>Could not load reviewers. Tap refresh to try again.</Text>
<Text style={styles.emptyText}>
{t(
'pullRequest.reviewersSection.loadFailed',
'Could not load reviewers. Tap refresh to try again.'
)}
</Text>
) : rows.length === 0 ? (
<Text style={styles.emptyText}>No reviewers requested</Text>
<Text style={styles.emptyText}>
{t('pullRequest.reviewersSection.noReviewers', 'No reviewers requested')}
</Text>
) : (
rows.map((row) => {
const busy = actions.isBusy({ kind: 'reviewer', login: row.login })
@@ -92,7 +137,11 @@ export function PRReviewersSection({ details, actions, client, worktreeId }: Pro
onPress={() => actions.removeReviewer(row.login)}
disabled={busy}
accessibilityRole="button"
accessibilityLabel={`Remove ${row.login}`}
accessibilityLabel={t(
'pullRequest.reviewersSection.removeReviewer',
'Remove {{login}}',
{ login: row.login }
)}
>
{busy ? (
<ActivityIndicator color={colors.textSecondary} />
@@ -3,6 +3,7 @@ import { ActivityIndicator, Pressable, Text, TextInput, View } from 'react-nativ
import { ArrowRight, ExternalLink, Pencil } 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 type { PRInfo } from '../../../../src/shared/github/pull-request-types'
import type { GitHubWorkItemDetails } from '../../../../src/shared/github/work-item-types'
import type { MobilePrTitleAction } from '../../session/use-mobile-pr-title-action'
@@ -33,6 +34,7 @@ export function PRSidebarHeader({
bare = false
}: Props) {
useMobileColorScheme()
const t = useMobileTranslation()
const item = details?.item
const badge = prStateBadge(pr.state)
const badgeColor = statusColor(badge.token)
@@ -51,7 +53,11 @@ export function PRSidebarHeader({
onPress={openPr}
disabled={!openPr}
accessibilityRole="link"
accessibilityLabel={`Open pull request #${pr.number} on the web`}
accessibilityLabel={t(
'pullRequest.prSidebarHeader.openOnWeb',
'Open pull request #{{number}} on the web',
{ number: pr.number }
)}
style={({ pressed }) => [
styles.badge,
{ borderColor: badgeColor },
@@ -64,7 +70,11 @@ export function PRSidebarHeader({
style={styles.prMetaStrong}
onPress={openPr}
accessibilityRole="link"
accessibilityLabel={`Open pull request #${pr.number} on the web`}
accessibilityLabel={t(
'pullRequest.prSidebarHeader.openOnWeb',
'Open pull request #{{number}} on the web',
{ number: pr.number }
)}
>
#{pr.number}
</Text>
@@ -75,7 +85,11 @@ export function PRSidebarHeader({
onPress={openPr}
hitSlop={8}
accessibilityRole="link"
accessibilityLabel={`Open pull request #${pr.number} in browser`}
accessibilityLabel={t(
'pullRequest.prSidebarHeader.openInBrowser',
'Open pull request #{{number}} in browser',
{ number: pr.number }
)}
style={({ pressed }) => [styles.iconButton, pressed && { opacity: 0.6 }]}
>
<ExternalLink size={16} color={colors.textSecondary} strokeWidth={2.2} />
@@ -117,6 +131,7 @@ function PRTitle({
titleAction: MobilePrTitleAction
}) {
useMobileColorScheme()
const t = useMobileTranslation()
const [editing, setEditing] = useState(false)
const [draft, setDraft] = useState(title)
@@ -155,21 +170,24 @@ function PRTitle({
onPress={cancel}
disabled={titleAction.saving}
accessibilityRole="button"
accessibilityLabel="Cancel editing title"
accessibilityLabel={t(
'pullRequest.prSidebarHeader.cancelEditingTitle',
'Cancel editing title'
)}
>
<Text style={composerStyles.cancelText}>Cancel</Text>
<Text style={composerStyles.cancelText}>{t('common.cancel', 'Cancel')}</Text>
</Pressable>
<Pressable
style={({ pressed }) => [composerStyles.submit, pressed && composerStyles.pressed]}
onPress={() => void save()}
disabled={titleAction.saving}
accessibilityRole="button"
accessibilityLabel="Save title"
accessibilityLabel={t('pullRequest.prSidebarHeader.saveTitle', 'Save title')}
>
{titleAction.saving ? (
<ActivityIndicator size="small" color={colors.bgBase} />
) : (
<Text style={composerStyles.submitText}>Save</Text>
<Text style={composerStyles.submitText}>{t('common.save', 'Save')}</Text>
)}
</Pressable>
</View>
@@ -183,7 +201,9 @@ function PRTitle({
onPress={editable ? startEdit : undefined}
disabled={!editable}
accessibilityRole={editable ? 'button' : undefined}
accessibilityLabel={editable ? 'Edit pull request title' : undefined}
accessibilityLabel={
editable ? t('pullRequest.prSidebarHeader.editTitle', 'Edit pull request title') : undefined
}
>
<Text style={styles.prTitle}>{title}</Text>
{editable ? (
@@ -3,6 +3,7 @@ import { ActivityIndicator, Pressable, Text, View } from 'react-native'
import { GitPullRequestArrow, Link2, RefreshCw } 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 type { RpcClient } from '../../transport/rpc-client'
import type { ConnectionState } from '../../transport/types'
import type { MobileGitStatusResult } from '../../source-control/mobile-git-status'
@@ -48,6 +49,7 @@ export function PrSidebarCreateEmptyState({
onCreated
}: Props) {
useMobileColorScheme()
const t = useMobileTranslation()
const [mode, setMode] = useState<Mode>('choose')
const [loading, setLoading] = useState(false)
const [createWarning, setCreateWarning] = useState<string | null>(null)
@@ -100,7 +102,12 @@ export function PrSidebarCreateEmptyState({
setLoading(true)
try {
if (!gitBranch) {
setCreateWarning('Check out a branch before creating a pull request.')
setCreateWarning(
t(
'pullRequest.prSidebarCreateEmptyState.branchRequired',
'Check out a branch before creating a pull request.'
)
)
return
}
// Skip local compose and use hosted create flow to match automated review path.
@@ -133,7 +140,14 @@ export function PrSidebarCreateEmptyState({
openMobilePrUrl(outcome.url)
onCreated()
} catch (err) {
setCreateWarning(err instanceof Error ? err.message : 'Failed to create pull request.')
setCreateWarning(
err instanceof Error
? err.message
: t(
'pullRequest.prSidebarCreateEmptyState.createFailed',
'Failed to create pull request.'
)
)
} finally {
setLoading(false)
}
@@ -162,14 +176,19 @@ export function PrSidebarCreateEmptyState({
<View style={styles.header}>
<View style={styles.headerTitle}>
<GitPullRequestArrow size={14} color={colors.textSecondary} strokeWidth={2.2} />
<Text style={styles.headerLabel}>Pull request</Text>
<Text style={styles.headerLabel}>
{t('pullRequest.prSidebarCreateEmptyState.title', 'Pull request')}
</Text>
</View>
<View style={styles.headerActions}>
<Pressable
style={({ pressed }) => [styles.iconButton, pressed && styles.iconButtonPressed]}
onPress={refreshPrState}
accessibilityRole="button"
accessibilityLabel="Refresh pull request"
accessibilityLabel={t(
'pullRequest.prSidebarCreateEmptyState.refreshPullRequest',
'Refresh pull request'
)}
hitSlop={6}
>
<RefreshCw size={16} color={colors.textSecondary} strokeWidth={2.2} />
@@ -179,27 +198,48 @@ export function PrSidebarCreateEmptyState({
onPress={() => void openComposer()}
disabled={!canCreate || loading}
accessibilityRole="button"
accessibilityLabel="Create pull request"
accessibilityLabel={t(
'pullRequest.prSidebarCreateEmptyState.createPullRequest',
'Create pull request'
)}
>
{loading ? (
<ActivityIndicator color={colors.bgBase} />
) : (
<GitPullRequestArrow size={14} color={colors.bgBase} strokeWidth={2.2} />
)}
<Text style={styles.createButtonText}>Create PR</Text>
<Text style={styles.createButtonText}>
{t('pullRequest.prSidebarCreateEmptyState.createPr', 'Create PR')}
</Text>
</Pressable>
</View>
</View>
<View style={styles.body}>
<Text style={styles.bodyTitle}>
{orphanLinkedPR ? `Linked PR #${orphanLinkedPR} unavailable` : 'No open pull request'}
{orphanLinkedPR
? t(
'pullRequest.prSidebarCreateEmptyState.linkedPrUnavailable',
'Linked PR #{{number}} unavailable',
{ number: orphanLinkedPR }
)
: t('pullRequest.prSidebarCreateEmptyState.noOpenPullRequest', 'No open pull request')}
</Text>
<Text style={styles.bodyText}>
{orphanLinkedPR
? 'Refresh to check again, or create a new PR for this branch.'
? t(
'pullRequest.prSidebarCreateEmptyState.refreshOrCreate',
'Refresh to check again, or create a new PR for this branch.'
)
: gitBranch
? `${gitBranch} is not linked to an open PR.`
: 'The current branch is not linked to an open PR.'}
? t(
'pullRequest.prSidebarCreateEmptyState.branchNotLinked',
'{{branch}} is not linked to an open PR.',
{ branch: gitBranch }
)
: t(
'pullRequest.prSidebarCreateEmptyState.currentBranchNotLinked',
'The current branch is not linked to an open PR.'
)}
</Text>
{commitFailureRecovery ? (
<MobileCommitFailurePanel
@@ -218,7 +258,10 @@ export function PrSidebarCreateEmptyState({
onPress={() => setMode('link')}
disabled={!client}
accessibilityRole="button"
accessibilityLabel="Link an existing pull request"
accessibilityLabel={t(
'pullRequest.prSidebarCreateEmptyState.linkExistingPullRequest',
'Link an existing pull request'
)}
accessibilityState={{ disabled: !client }}
hitSlop={6}
>
@@ -227,7 +270,9 @@ export function PrSidebarCreateEmptyState({
color={client ? colors.textSecondary : colors.textMuted}
strokeWidth={2.2}
/>
<Text style={styles.linkButtonText}>Link an existing PR</Text>
<Text style={styles.linkButtonText}>
{t('pullRequest.prSidebarCreateEmptyState.linkExistingPr', 'Link an existing PR')}
</Text>
</Pressable>
</View>
</View>
@@ -3,6 +3,7 @@ import { ActivityIndicator, Pressable, Text, TextInput, View } from 'react-nativ
import { Check } 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 type { GitHubAssignableUser } from '../../../../src/shared/github/pull-request-types'
import type { RpcClient } from '../../transport/rpc-client'
import { fetchAssignableUsers } from '../../session/github-pr-rpc'
@@ -24,7 +25,7 @@ type Props = {
type LoadState =
| { status: 'idle' }
| { status: 'loading' }
| { status: 'error'; message: string }
| { status: 'error'; message: string | null }
| { status: 'loaded'; users: GitHubAssignableUser[] }
// Use mapped rows: VirtualizedList nested in PR ScrollView throws and can blank section. Seed reviewers/author first for easy unrequest.
@@ -38,6 +39,7 @@ export function ReviewerPickerDrawer({
onToggle
}: Props) {
useMobileColorScheme()
const t = useMobileTranslation()
const [load, setLoad] = useState<LoadState>({ status: 'idle' })
const [query, setQuery] = useState('')
@@ -60,7 +62,7 @@ export function ReviewerPickerDrawer({
})
.catch(() => {
if (!cancelled) {
setLoad({ status: 'error', message: 'Failed to load people' })
setLoad({ status: 'error', message: null })
}
})
return () => {
@@ -89,12 +91,14 @@ export function ReviewerPickerDrawer({
return (
<BottomDrawer visible={visible} onClose={onClose} dragContentToDismiss={false}>
<Text style={styles.pickerTitle}>Reviewers</Text>
<Text style={styles.pickerTitle}>
{t('pullRequest.reviewerPickerDrawer.title', 'Reviewers')}
</Text>
<TextInput
style={styles.pickerSearch}
value={query}
onChangeText={setQuery}
placeholder="Search people"
placeholder={t('pullRequest.reviewerPickerDrawer.searchPeople', 'Search people')}
placeholderTextColor={colors.textMuted}
autoCapitalize="none"
autoCorrect={false}
@@ -105,11 +109,16 @@ export function ReviewerPickerDrawer({
</View>
) : load.status === 'error' ? (
<View style={styles.pickerStateArea}>
<Text style={styles.emptyText}>{load.message}</Text>
<Text style={styles.emptyText}>
{load.message ??
t('pullRequest.reviewerPickerDrawer.loadFailed', 'Failed to load people')}
</Text>
</View>
) : ordered.length === 0 ? (
<View style={styles.pickerStateArea}>
<Text style={styles.emptyText}>No matching people</Text>
<Text style={styles.emptyText}>
{t('pullRequest.reviewerPickerDrawer.noMatches', 'No matching people')}
</Text>
</View>
) : (
<View style={styles.pickerList}>
@@ -122,7 +131,15 @@ export function ReviewerPickerDrawer({
onPress={() => onToggle(item.login)}
accessibilityRole="button"
accessibilityState={{ selected: requested }}
accessibilityLabel={`${requested ? 'Remove' : 'Request'} ${item.login}`}
accessibilityLabel={
requested
? t('pullRequest.reviewerPickerDrawer.removeReviewer', 'Remove {{login}}', {
login: item.login
})
: t('pullRequest.reviewerPickerDrawer.requestReviewer', 'Request {{login}}', {
login: item.login
})
}
>
<View style={styles.rowTrailing}>
{requested ? (
@@ -1,3 +1,4 @@
import { translate } from '../../i18n/mobile-i18n'
import { createMarkdownInlineMatcher } from '../markdown-inline-matcher'
import { splitTableRow } from '../rich-markdown/markdown-table-rows'
@@ -66,9 +67,16 @@ function parseSegment(text: string): MarkdownBlock[] {
}
if (m[1].toLowerCase() === 'details') {
const sm = SUMMARY.exec(m[2])
const summary = sm ? stripHtmlTags(sm[1]).trim() : 'Details'
const summary = sm ? stripHtmlTags(sm[1]).trim() : ''
const body = m[2].replace(SUMMARY, '')
blocks.push({ kind: 'details', summary: summary || 'Details', body: parseSegment(body) })
blocks.push({
kind: 'details',
// Parsed blocks can outlive the active language.
get summary() {
return summary || translate('pullRequest.markdownBlocks.details', 'Details')
},
body: parseSegment(body)
})
} else {
blocks.push({ kind: 'quote', text: stripHtmlTags(m[2]).trim() })
}
@@ -11,6 +11,7 @@ import {
resolveGitHubPRStackMergeDecision,
type GitHubPRStackMergeBlocker
} from '../../../../src/shared/github/pull-request-stack-merge'
import { translate } from '../../i18n/mobile-i18n'
// Match desktop: drafts offer Ready for review and Close, never Merge; merged PRs only unlink.
export type PrActionAvailability = {
@@ -31,75 +32,156 @@ export function resolvePrActionAvailability(state: PRState): PrActionAvailabilit
}
}
// English copy for desktop's merge-state kinds; the shared rule decides which one applies.
const PR_MERGE_STATE_COPY: Record<GitHubPRMergeStateKind, { label: string; hint: string }> = {
merged: { label: 'Merged', hint: 'This pull request is already merged' },
closed: { label: 'Closed', hint: 'This pull request is closed' },
draft: { label: 'Draft', hint: 'This pull request is still a draft' },
review_required: {
label: 'Approval required',
hint: 'GitHub requires review approval before this pull request can merge'
},
changes_requested: {
label: 'Changes requested',
hint: 'GitHub reports requested changes on this pull request'
},
conflicts: { label: 'Conflicts', hint: 'GitHub reports merge conflicts' },
merge_queue: {
label: 'Merge when ready',
hint: 'This base branch uses GitHub merge queue'
},
merge_queue_auto_merge_on: {
label: 'Auto-merge on',
hint: 'This base branch uses GitHub merge queue'
},
merge_status_unavailable: {
label: 'Merge',
hint: 'Merge status is unavailable for this PR'
},
behind: { label: 'Behind', hint: 'Update the branch before merging' },
blocked: {
label: 'Blocked',
hint: 'GitHub reports this pull request is blocked'
},
mergeable_checks_failed: {
label: 'Checks failed',
hint: 'GitHub says this PR can merge, but some checks failed'
},
mergeable_checks_pending: {
label: 'Checks pending',
hint: 'GitHub says this PR can merge, but checks are still running'
},
mergeable_checks_passed: {
label: 'Able to merge',
hint: 'GitHub says this PR can merge and checks passed'
},
mergeable: { label: 'Able to merge', hint: 'GitHub says this PR can merge' },
checks_passed: {
label: 'Checks passed',
hint: 'Checks passed. Merge eligibility will be checked again before merging.'
},
checking: {
label: 'Checking',
hint: 'GitHub is still computing this pull request merge status'
// Copy for desktop’s merge-state kinds; the shared rule decides which one applies.
function prMergeStateCopy(): Record<GitHubPRMergeStateKind, { label: string; hint: string }> {
return {
merged: {
label: translate('pullRequest.prActionsState.mergedLabel', 'Merged'),
hint: translate(
'pullRequest.prActionsState.mergedHint',
'This pull request is already merged'
)
},
closed: {
label: translate('pullRequest.prActionsState.closedLabel', 'Closed'),
hint: translate('pullRequest.prActionsState.closedHint', 'This pull request is closed')
},
draft: {
label: translate('pullRequest.prActionsState.draftLabel', 'Draft'),
hint: translate('pullRequest.prActionsState.draftHint', 'This pull request is still a draft')
},
review_required: {
label: translate('pullRequest.prActionsState.reviewRequiredLabel', 'Approval required'),
hint: translate(
'pullRequest.prActionsState.reviewRequiredHint',
'GitHub requires review approval before this pull request can merge'
)
},
changes_requested: {
label: translate('pullRequest.prActionsState.changesRequestedLabel', 'Changes requested'),
hint: translate(
'pullRequest.prActionsState.changesRequestedHint',
'GitHub reports requested changes on this pull request'
)
},
conflicts: {
label: translate('pullRequest.prActionsState.conflictsLabel', 'Conflicts'),
hint: translate('pullRequest.prActionsState.conflictsHint', 'GitHub reports merge conflicts')
},
merge_queue: {
label: translate('pullRequest.prActionsState.mergeQueueLabel', 'Merge when ready'),
hint: translate(
'pullRequest.prActionsState.mergeQueueHint',
'This base branch uses GitHub merge queue'
)
},
merge_queue_auto_merge_on: {
label: translate('pullRequest.prActionsState.mergeQueueAutoMergeOnLabel', 'Auto-merge on'),
hint: translate(
'pullRequest.prActionsState.mergeQueueAutoMergeOnHint',
'This base branch uses GitHub merge queue'
)
},
merge_status_unavailable: {
label: translate('pullRequest.prActionsState.mergeStatusUnavailableLabel', 'Merge'),
hint: translate(
'pullRequest.prActionsState.mergeStatusUnavailableHint',
'Merge status is unavailable for this PR'
)
},
behind: {
label: translate('pullRequest.prActionsState.behindLabel', 'Behind'),
hint: translate('pullRequest.prActionsState.behindHint', 'Update the branch before merging')
},
blocked: {
label: translate('pullRequest.prActionsState.blockedLabel', 'Blocked'),
hint: translate(
'pullRequest.prActionsState.blockedHint',
'GitHub reports this pull request is blocked'
)
},
mergeable_checks_failed: {
label: translate('pullRequest.prActionsState.mergeableChecksFailedLabel', 'Checks failed'),
hint: translate(
'pullRequest.prActionsState.mergeableChecksFailedHint',
'GitHub says this PR can merge, but some checks failed'
)
},
mergeable_checks_pending: {
label: translate('pullRequest.prActionsState.mergeableChecksPendingLabel', 'Checks pending'),
hint: translate(
'pullRequest.prActionsState.mergeableChecksPendingHint',
'GitHub says this PR can merge, but checks are still running'
)
},
mergeable_checks_passed: {
label: translate('pullRequest.prActionsState.mergeableChecksPassedLabel', 'Able to merge'),
hint: translate(
'pullRequest.prActionsState.mergeableChecksPassedHint',
'GitHub says this PR can merge and checks passed'
)
},
mergeable: {
label: translate('pullRequest.prActionsState.mergeableLabel', 'Able to merge'),
hint: translate('pullRequest.prActionsState.mergeableHint', 'GitHub says this PR can merge')
},
checks_passed: {
label: translate('pullRequest.prActionsState.checksPassedLabel', 'Checks passed'),
hint: translate(
'pullRequest.prActionsState.checksPassedHint',
'Checks passed. Merge eligibility will be checked again before merging.'
)
},
checking: {
label: translate('pullRequest.prActionsState.checkingLabel', 'Checking'),
hint: translate(
'pullRequest.prActionsState.checkingHint',
'GitHub is still computing this pull request merge status'
)
}
}
}
const STACK_BLOCKER_COPY: Record<GitHubPRStackMergeBlocker['reason'], string> = {
draft: 'is still a draft.',
closed: 'is closed.',
conflicts: 'has merge conflicts.',
changes_requested: 'has requested changes.',
review_required: 'still needs review approval.',
behind: 'must be updated.',
blocked: 'is blocked.'
function stackBlockerCopy({ prNumber: number, reason }: GitHubPRStackMergeBlocker): string {
const copy: Record<GitHubPRStackMergeBlocker['reason'], string> = {
draft: translate('pullRequest.prActionsState.stackDraft', '#{{number}} is still a draft.', {
number
}),
closed: translate('pullRequest.prActionsState.stackClosed', '#{{number}} is closed.', {
number
}),
conflicts: translate(
'pullRequest.prActionsState.stackConflicts',
'#{{number}} has merge conflicts.',
{ number }
),
changes_requested: translate(
'pullRequest.prActionsState.stackChangesRequested',
'#{{number}} has requested changes.',
{ number }
),
review_required: translate(
'pullRequest.prActionsState.stackReviewRequired',
'#{{number}} still needs review approval.',
{ number }
),
behind: translate('pullRequest.prActionsState.stackBehind', '#{{number}} must be updated.', {
number
}),
blocked: translate('pullRequest.prActionsState.stackBlocked', '#{{number}} is blocked.', {
number
})
}
return copy[reason]
}
// Desktop's merge-method labels; the desktop renderer translates the same three.
const MERGE_METHOD_LABELS: Record<GitHubPRMergeMethod, string> = {
merge: 'Create merge commit',
squash: 'Squash and merge',
rebase: 'Rebase and merge'
function mergeMethodLabels(): Record<GitHubPRMergeMethod, string> {
return {
merge: translate('pullRequest.prActionsState.methodMerge', 'Create merge commit'),
squash: translate('pullRequest.prActionsState.methodSquash', 'Squash and merge'),
rebase: translate('pullRequest.prActionsState.methodRebase', 'Rebase and merge')
}
}
export type PrMergePresentation = {
@@ -116,13 +198,17 @@ export function presentPrMerge(
defaultMethod: GitHubPRMergeMethod
): PrMergePresentation {
const decision = resolveGitHubPRMergeState(pr)
const blockedCopy = PR_MERGE_STATE_COPY[decision.kind]
const blockedCopy = prMergeStateCopy()[decision.kind]
if (!pr.stack) {
return {
enabled: decision.directMergeAvailable,
label: decision.directMergeAvailable ? MERGE_METHOD_LABELS[defaultMethod] : blockedCopy.label,
label: decision.directMergeAvailable ? mergeMethodLabels()[defaultMethod] : blockedCopy.label,
hint: decision.directMergeAvailable ? null : blockedCopy.hint,
confirmMessage: `This will merge #${pr.number} into its base branch.`,
confirmMessage: translate(
'pullRequest.prActionsState.mergeConfirmMessage',
'This will merge #{{number}} into its base branch.',
{ number: pr.number }
),
showAutoMerge: decision.autoMergeAction !== null
}
}
@@ -137,18 +223,43 @@ export function presentPrMerge(
stackMergeQueueRequired: undefined,
directMergeAvailable: decision.directMergeAvailable
})
const stackLabel = `${usesMergeQueue ? 'Queue' : 'Merge'} through #${pr.number} · ${scope.count} ${scope.count === 1 ? 'PR' : 'PRs'}`
const count = scope.count
const stackLabel = usesMergeQueue
? count === 1
? translate(
'pullRequest.prActionsState.queueThrough_one',
'Queue through #{{number}} · {{count}} PR',
{ number: pr.number, count }
)
: translate(
'pullRequest.prActionsState.queueThrough_other',
'Queue through #{{number}} · {{count}} PRs',
{ number: pr.number, count }
)
: count === 1
? translate(
'pullRequest.prActionsState.mergeThrough_one',
'Merge through #{{number}} · {{count}} PR',
{ number: pr.number, count }
)
: translate(
'pullRequest.prActionsState.mergeThrough_other',
'Merge through #{{number}} · {{count}} PRs',
{ number: pr.number, count }
)
const stackScope = usesMergeQueue
? 'GitHub will add this pull request and every pull request below it to the merge queue.'
: 'GitHub will merge this pull request and every pull request below it in the stack.'
? translate(
'pullRequest.prActionsState.queueStackMessage',
'GitHub will add this pull request and every pull request below it to the merge queue.'
)
: translate(
'pullRequest.prActionsState.mergeStackMessage',
'GitHub will merge this pull request and every pull request below it in the stack.'
)
return {
enabled,
label: stackLabel,
hint: blocker
? `#${blocker.prNumber} ${STACK_BLOCKER_COPY[blocker.reason]}`
: enabled
? null
: stackScope,
hint: blocker ? stackBlockerCopy(blocker) : enabled ? null : stackScope,
confirmMessage: stackScope,
showAutoMerge: false
}
@@ -4,6 +4,7 @@ import type {
PRCheckRunDetails
} from '../../../../src/shared/github/check-types'
import { resolveStepOutcome, summarizeJobSteps } from '../../../../src/shared/pr-check-step-status'
import { translate } from '../../i18n/mobile-i18n'
// Map check payload to desktop-equivalent summary/annotations/failed jobs without React/RN imports for Node tests.
@@ -34,13 +35,14 @@ export type CheckDetailContent = {
summaryLines: string[]
annotations: CheckDetailAnnotation[]
annotationsTruncated: boolean
jobsLabel: 'Failed jobs' | 'Jobs'
jobsLabel: string
jobs: CheckDetailJob[]
jobsTruncated: boolean
}
function mapAnnotation(annotation: PRCheckAnnotation): CheckDetailAnnotation {
const path = annotation.path ?? 'Annotation'
const path =
annotation.path ?? translate('pullRequest.prCheckDetailContent.annotation', 'Annotation')
const locator = annotation.startLine ? `${path}:${annotation.startLine}` : path
return {
locator,
@@ -53,11 +55,17 @@ function mapAnnotation(annotation: PRCheckAnnotation): CheckDetailAnnotation {
function mapJob(job: PRCheckJob): CheckDetailJob {
const failedSteps = summarizeJobSteps(job).failed.map((step) => ({
name: step.name,
state: step.conclusion ?? step.status ?? 'unknown'
state:
step.conclusion ??
step.status ??
translate('pullRequest.prCheckDetailContent.unknown', 'unknown')
}))
return {
name: job.name,
state: job.conclusion ?? job.status ?? 'unknown',
state:
job.conclusion ??
job.status ??
translate('pullRequest.prCheckDetailContent.unknown', 'unknown'),
failedSteps,
logTail: job.logTail
}
@@ -78,7 +86,10 @@ export function presentCheckDetail(details: PRCheckRunDetails): CheckDetailConte
summaryLines,
annotations: details.annotations.slice(0, MAX_ANNOTATIONS).map(mapAnnotation),
annotationsTruncated: details.annotations.length > MAX_ANNOTATIONS,
jobsLabel: failedJobs.length > 0 ? 'Failed jobs' : 'Jobs',
jobsLabel:
failedJobs.length > 0
? translate('pullRequest.prCheckDetailContent.failedJobs', 'Failed jobs')
: translate('pullRequest.prCheckDetailContent.jobs', 'Jobs'),
jobs: visibleJobs.slice(0, MAX_JOBS).map(mapJob),
jobsTruncated: details.jobs.length > MAX_JOBS
}
@@ -9,6 +9,7 @@ import {
type CheckOutcome as SharedCheckOutcome
} from '../../../../src/shared/provider-check-summary'
import { prStateToken } from '../pr-state-token'
import { translate } from '../../i18n/mobile-i18n'
// Pure RN-free checks/badge logic follows desktop merge-state and reviewer presenters.
@@ -53,23 +54,56 @@ const OUTCOME_BY_STATE: Record<ProviderCheckSummary['state'], CheckOutcome | 'no
export function summarizePRChecks(checks: readonly PRCheckDetail[]): PRChecksSummary {
if (checks.length === 0) {
return { total: 0, passed: 0, pending: 0, failed: 0, outcome: 'none', label: 'No checks' }
return {
total: 0,
passed: 0,
pending: 0,
failed: 0,
outcome: 'none',
label: translate('pullRequest.prChecksPresentation.noChecks', 'No checks')
}
}
// Counts and the worst-case rollup come from the shared summarizer; only the label wording is mobile's.
const { total, passed, pending, failed, neutral, state } = summarizeProviderChecks(checks)
const outcome = OUTCOME_BY_STATE[state]
const parts: string[] = []
if (failed > 0) {
parts.push(`${failed} failing`)
const count = failed
parts.push(
count === 1
? translate('pullRequest.prChecksPresentation.failing_one', '{{count}} failing', { count })
: translate('pullRequest.prChecksPresentation.failing_other', '{{count}} failing', {
count
})
)
}
if (pending > 0) {
parts.push(`${pending} pending`)
const count = pending
parts.push(
count === 1
? translate('pullRequest.prChecksPresentation.pending_one', '{{count}} pending', { count })
: translate('pullRequest.prChecksPresentation.pending_other', '{{count}} pending', {
count
})
)
}
if (passed > 0) {
parts.push(`${passed} passed`)
const count = passed
parts.push(
count === 1
? translate('pullRequest.prChecksPresentation.passed_one', '{{count}} passed', { count })
: translate('pullRequest.prChecksPresentation.passed_other', '{{count}} passed', { count })
)
}
if (neutral > 0) {
parts.push(`${neutral} neutral`)
const count = neutral
parts.push(
count === 1
? translate('pullRequest.prChecksPresentation.neutral_one', '{{count}} neutral', { count })
: translate('pullRequest.prChecksPresentation.neutral_other', '{{count}} neutral', {
count
})
)
}
return {
total,
@@ -83,7 +117,9 @@ export function summarizePRChecks(checks: readonly PRCheckDetail[]): PRChecksSum
/** An unreadable checks reply is not an absent one, so the header must not read "No checks". */
export function prChecksSummaryLabel(summary: PRChecksSummary, checksError: string | null): string {
return checksError === null ? summary.label : 'Checks unavailable'
return checksError === null
? summary.label
: translate('pullRequest.prChecksPresentation.unavailable', 'Checks unavailable')
}
export function checkOutcomeToken(outcome: CheckOutcome | 'none'): MobileStatusToken {
@@ -125,14 +161,16 @@ export type PRStateBadge = {
token: MobileStatusToken
}
const PR_STATE_LABELS: Record<PRState, string> = {
open: 'Open',
merged: 'Merged',
draft: 'Draft',
closed: 'Closed'
function prStateLabels(): Record<PRState, string> {
return {
open: translate('pullRequest.prChecksPresentation.stateOpen', 'Open'),
merged: translate('pullRequest.prChecksPresentation.stateMerged', 'Merged'),
draft: translate('pullRequest.prChecksPresentation.stateDraft', 'Draft'),
closed: translate('pullRequest.prChecksPresentation.stateClosed', 'Closed')
}
}
// Use shared prStateToken so sidebar and workspace badge agree on merged/open/closed/draft colors.
export function prStateBadge(state: PRState): PRStateBadge {
return { label: PR_STATE_LABELS[state] ?? state, token: prStateToken(state) }
return { label: prStateLabels()[state] ?? state, token: prStateToken(state) }
}
@@ -1,18 +1,34 @@
import type { PRCommentAudienceFilter } from '../../../../src/shared/pr-comment-audience'
import { translate } from '../../i18n/mobile-i18n'
export const PR_COMMENT_AUDIENCE_FILTERS = [
{ value: 'all', label: 'All' },
{ value: 'human', label: 'Humans' },
{ value: 'bot', label: 'Bots' }
{
value: 'all',
get label() {
return translate('pullRequest.prCommentAudienceLabels.all', 'All')
}
},
{
value: 'human',
get label() {
return translate('pullRequest.prCommentAudienceLabels.humans', 'Humans')
}
},
{
value: 'bot',
get label() {
return translate('pullRequest.prCommentAudienceLabels.bots', 'Bots')
}
}
] satisfies { value: PRCommentAudienceFilter; label: string }[]
export function getPRCommentAudienceEmptyLabel(filter: PRCommentAudienceFilter): string {
switch (filter) {
case 'bot':
return 'No bot comments.'
return translate('pullRequest.prCommentAudienceLabels.noBotComments', 'No bot comments.')
case 'human':
return 'No human comments.'
return translate('pullRequest.prCommentAudienceLabels.noHumanComments', 'No human comments.')
case 'all':
return 'No comments yet.'
return translate('pullRequest.prCommentAudienceLabels.noComments', 'No comments yet.')
}
}
@@ -1,6 +1,9 @@
import { useCallback, useMemo, useRef, useState } from 'react'
import type { CodexResetCreditExpectedScope } from '../../../src/shared/codex-reset-credit-scope'
import type { RpcClient } from '../transport/rpc-client'
import { translate } from '../i18n/mobile-i18n'
import type { Translate } from '../i18n/mobile-i18n'
import { useMobileTranslation } from '../i18n/use-mobile-translation'
import type { AccountsSnapshot } from './account-usage-state'
import {
getCodexResetCreditOutcomeCopy,
@@ -11,13 +14,62 @@ import { useCodexResetCreditCapability } from './codex-reset-credit-capability'
type CodexResetDialog = { title: string; message: string }
function describeScope(snapshot: AccountsSnapshot, scope: CodexResetCreditExpectedScope): string {
function describeScope(
snapshot: AccountsSnapshot,
scope: CodexResetCreditExpectedScope,
t: Translate
): { label: string; message: string } {
const account = snapshot.codex.accounts.find((candidate) => candidate.id === scope.accountId)
const identity = account?.email ?? 'the selected managed account'
const identity = account?.email
if (scope.target.runtime === 'host') {
return `${identity} on the host`
return identity != null
? {
label: t('components.useCodexResetCreditAction.hostAccount', '{{identity}} on the host', {
identity
}),
message: t(
'components.useCodexResetCreditAction.hostAccountConfirmMessage',
'This spends one earned reset for {{identity}} on the host and immediately resets eligible rate-limit windows.',
{ identity }
)
}
: {
label: t(
'components.useCodexResetCreditAction.hostSelectedAccount',
'the selected managed account on the host'
),
message: t(
'components.useCodexResetCreditAction.hostSelectedAccountConfirmMessage',
'This spends one earned reset for the selected managed account on the host and immediately resets eligible rate-limit windows.'
)
}
}
return `${identity} on WSL ${scope.target.wslDistro}`
const options = { identity, distro: scope.target.wslDistro }
return identity != null
? {
label: t(
'components.useCodexResetCreditAction.wslAccount',
'{{identity}} on WSL {{distro}}',
options
),
message: t(
'components.useCodexResetCreditAction.wslAccountConfirmMessage',
'This spends one earned reset for {{identity}} on WSL {{distro}} and immediately resets eligible rate-limit windows.',
options
)
}
: {
label: t(
'components.useCodexResetCreditAction.wslSelectedAccount',
'the selected managed account on WSL {{distro}}',
options
),
message: t(
'components.useCodexResetCreditAction.wslSelectedAccountConfirmMessage',
'This spends one earned reset for the selected managed account on WSL {{distro}} and immediately resets eligible rate-limit windows.',
options
)
}
}
export function useCodexResetCreditAction({
@@ -45,11 +97,12 @@ export function useCodexResetCreditAction({
acceptReset: () => void
cancelReset: () => void
} {
const t = useMobileTranslation()
const supported = useCodexResetCreditCapability(client, connected)
const [resetting, setResetting] = useState(false)
const [confirming, setConfirming] = useState<{
scope: CodexResetCreditExpectedScope
label: string
snapshot: AccountsSnapshot
} | null>(null)
const inFlightRef = useRef(false)
const resetScope = useMemo(
@@ -57,8 +110,8 @@ export function useCodexResetCreditAction({
[snapshot]
)
const scopeLabel = useMemo(
() => (snapshot && resetScope ? describeScope(snapshot, resetScope) : null),
[resetScope, snapshot]
() => (snapshot && resetScope ? describeScope(snapshot, resetScope, t).label : null),
[resetScope, snapshot, t]
)
const consume = useCallback(
@@ -73,16 +126,24 @@ export function useCodexResetCreditAction({
onSnapshot(result.snapshot)
if ('status' in result) {
onNotice({
title: 'Reset details changed',
message:
title: translate(
'components.useCodexResetCreditAction.detailsChanged',
'Reset details changed'
),
message: translate(
'components.useCodexResetCreditAction.detailsChangedMessage',
'The account or reset offer changed before the host contacted Codex. Review the updated details, then confirm again.'
)
})
return
}
onNotice(getCodexResetCreditOutcomeCopy(result.outcome))
} catch (error) {
onNotice({
title: 'Could not reset rate limits',
title: translate(
'components.useCodexResetCreditAction.resetFailed',
'Could not reset rate limits'
),
message: error instanceof Error ? error.message : String(error)
})
} finally {
@@ -98,7 +159,7 @@ export function useCodexResetCreditAction({
return
}
// The scope is pinned when the prompt opens, so a snapshot arriving under it changes nothing.
setConfirming({ scope: resetScope, label: describeScope(snapshot, resetScope) })
setConfirming({ scope: resetScope, snapshot })
}, [accountMutationBusy, connected, resetScope, resetting, snapshot, supported])
const acceptReset = useCallback(() => {
@@ -110,8 +171,8 @@ export function useCodexResetCreditAction({
const cancelReset = useCallback(() => setConfirming(null), [])
const confirmation = confirming
? {
title: 'Use a rate-limit reset?',
message: `This spends one earned reset for ${confirming.label} and immediately resets eligible rate-limit windows.`
title: t('components.useCodexResetCreditAction.confirmTitle', 'Use a rate-limit reset?'),
message: describeScope(confirming.snapshot, confirming.scope, t).message
}
: null
+72
View File
@@ -17,6 +17,34 @@
"restoreAfterSeconds_other": "After {{count}}s"
}
},
"components": {
"codexResetCredit": {
"available_one": "{{count}} reset available",
"available_other": "{{count}} resets available"
},
"diffReviewDrawers": {
"fileSummary_one": "{{fileCount}} file reviewed, {{count}} note",
"fileSummary_other": "{{fileCount}} file reviewed, {{count}} notes",
"filesSummary_one": "{{fileCount}} files reviewed, {{count}} note",
"filesSummary_other": "{{fileCount}} files reviewed, {{count}} notes",
"stageableFiles_one": "{{count}} reviewed unstaged files can be staged",
"stageableFiles_other": "{{count}} reviewed unstaged files can be staged",
"unsentNotes_one": "{{count}} unsent notes",
"unsentNotes_other": "{{count}} unsent notes"
},
"diffReviewFileSummary": {
"notes_one": "{{count}} note",
"notes_other": "{{count}} notes"
},
"diffReviewHeader": {
"unsentNotes_one": "{{count}} unsent note",
"unsentNotes_other": "{{count}} unsent notes"
},
"worktreeListRow": {
"children_one": "{{count}} child",
"children_other": "{{count}} children"
}
},
"dictation": {
"setup": {
"alreadyActive": "Dictation is already in use on the computer or another phone.",
@@ -30,6 +58,50 @@
"activeFilters_other": "Workspace options ({{count}} filters active)"
}
},
"pullRequest": {
"checksSection": {
"failingChecks_one": "{{count}} failing check",
"failingChecks_other": "{{count}} failing checks"
},
"commentCard": {
"daysAgo_one": "{{count}}d ago",
"daysAgo_other": "{{count}}d ago",
"hoursAgo_one": "{{count}}h ago",
"hoursAgo_other": "{{count}}h ago",
"minutesAgo_one": "{{count}}m ago",
"minutesAgo_other": "{{count}}m ago",
"monthsAgo_one": "{{count}}mo ago",
"monthsAgo_other": "{{count}}mo ago",
"yearsAgo_one": "{{count}}y ago",
"yearsAgo_other": "{{count}}y ago"
},
"commentsSection": {
"resolvedComment_one": "Resolved comment by {{author}}",
"resolvedComment_other": "Resolved comment by {{author}} ({{count}})",
"resolvedThread_one": "Resolved thread by {{author}}",
"resolvedThread_other": "Resolved thread by {{author}} ({{count}})"
},
"conflictingFilesSection": {
"commitsBehind_one": "{{count}} commit behind (base commit: {{baseCommit}})",
"commitsBehind_other": "{{count}} commits behind (base commit: {{baseCommit}})"
},
"prActionsState": {
"mergeThrough_one": "Merge through #{{number}} · {{count}} PR",
"mergeThrough_other": "Merge through #{{number}} · {{count}} PRs",
"queueThrough_one": "Queue through #{{number}} · {{count}} PR",
"queueThrough_other": "Queue through #{{number}} · {{count}} PRs"
},
"prChecksPresentation": {
"failing_one": "{{count}} failing",
"failing_other": "{{count}} failing",
"neutral_one": "{{count}} neutral",
"neutral_other": "{{count}} neutral",
"passed_one": "{{count}} passed",
"passed_other": "{{count}} passed",
"pending_one": "{{count}} pending",
"pending_other": "{{count}} pending"
}
},
"settings": {
"appUpdateSettingsRows": {
"lastCheckedDays_one": "Last checked {{count}}d ago",
+732
View File
@@ -142,9 +142,14 @@
"common": {
"back": "Zurück",
"cancel": "Abbrechen",
"close": "Schließen",
"confirm": "Bestätigen",
"continue": "Fortfahren",
"copied": "Kopiert",
"copy": "Kopieren",
"delete": "Löschen",
"done": "Abschließen",
"edit": "Bearbeiten",
"ok": "OK",
"remove": "Entfernen",
"retry": "Erneut versuchen",
@@ -152,6 +157,469 @@
"settings": "Einstellungen",
"unknown": "Unbekannt"
},
"components": {
"accountsSnapshot": {
"invalidSnapshot": "Ungültige Momentaufnahme der Konten vom Host"
},
"agentLaunchNotice": {
"copyFailed": "Kopieren fehlgeschlagen",
"copyPrompt": "Prompt kopieren",
"statusUnreadable": "Der Status dieses Hosts konnte nicht gelesen werden. Erneuter Versuch…",
"updateRequired": "Orca auf dem Computer aktualisieren, um einen Agenten vom Smartphone aus zu starten."
},
"agentSpinner": {
"monitoring": "Hintergrundaufgaben werden überwacht",
"unconfirmed": "Bestätigung nicht möglich"
},
"agentStateDot": {
"monitoring": "Hintergrundaufgaben werden überwacht",
"unverifiable": "Keine aktuelle Meldung"
},
"authFailedBanner": {
"message": "Authentifizierung fehlgeschlagen – zuerst erneut verbinden; bei weiteren Fehlern vom Computer aus erneut koppeln."
},
"authFailedBannerActions": {
"repair": "Erneut koppeln",
"webNote": "Diesen Host über die Orca-App erneut verbinden oder entfernen."
},
"codexResetCredit": {
"alreadyAppliedTitle": "Zurücksetzung bereits erfolgt",
"available_one": "{{count}} Zurücksetzung verfügbar",
"available_other": "{{count}} Zurücksetzungen verfügbar",
"expiryDaysHoursMultiple": "Die nächste läuft in {{days}} T. {{hours}} Std. ab",
"expiryDaysHoursSingle": "Läuft in {{days}} T. {{hours}} Std. ab",
"expiryDaysMultiple": "Die nächste läuft in {{days}} T. ab",
"expiryDaysSingle": "Läuft in {{days}} T. ab",
"expiryHoursMinutesMultiple": "Die nächste läuft in {{hours}} Std. {{minutes}} Min. ab",
"expiryHoursMinutesSingle": "Läuft in {{hours}} Std. {{minutes}} Min. ab",
"expiryHoursMultiple": "Die nächste läuft in {{hours}} Std. ab",
"expiryHoursSingle": "Läuft in {{hours}} Std. ab",
"expiryMinutesMultiple": "Die nächste läuft in {{minutes}} Min. ab",
"expiryMinutesSingle": "Läuft in {{minutes}} Min. ab",
"expiryNowMultiple": "Die nächste läuft jetzt ab",
"expiryNowSingle": "Läuft jetzt ab",
"invalidResponse": "Ungültige Antwort des Hosts zur Zurücksetzung",
"noResetMessage": "Für dieses Konto sind keine erworbenen Credits zum Zurücksetzen verfügbar.",
"noResetTitle": "Keine Zurücksetzung verfügbar",
"nothingToResetMessage": "In keinem zurücksetzbaren Codex-Zeitfenster ist das Ratenlimit ausgeschöpft.",
"nothingToResetTitle": "Nichts zum Zurücksetzen",
"refresh": "Die Codex-Nutzung wurde aktualisiert.",
"resetTitle": "Ratenlimits zurückgesetzt"
},
"codexResetCreditAction": {
"activeAccountHint": "Verbraucht einen verdienten Credit zum Zurücksetzen des Ratenlimits für das aktive Codex-Konto",
"earnedReset": "Verdienter Credit zum Zurücksetzen des Codex-Ratenlimits",
"resetting": "Einlösen…",
"resettingLabel": "Codex-Ratenlimits werden zurückgesetzt",
"scopedHint": "Verbraucht einen verdienten Credit zum Zurücksetzen des Ratenlimits für {{scopeLabel}}",
"useReset": "Einlösen",
"useResetLabel": "Zurücksetzung des Codex-Ratenlimits verwenden"
},
"customKeyModal": {
"add": "Hinzufügen",
"addShortcut": "Tastenkürzel hinzufügen",
"command": "Befehl",
"commandPlaceholder": "z. B. pnpm build",
"editing": "Bearbeitung",
"function": "Funktionstasten",
"key": "Taste",
"label": "Bezeichnung",
"labelPlaceholder": "z. B. Kompilieren",
"manageShortcuts": "Tastenkürzel verwalten",
"manageShortcutsHint": "Tastenkürzel einblenden, ausblenden oder neu anordnen",
"modifiers": "Modifikatortasten",
"moreKeys": "Weitere Tasten – Tab, Pfeiltasten, F1–F12…",
"navigation": "Navigation",
"pickKey": "Taste auswählen",
"pressEnter": "Enter drücken",
"shortcutCombo": "Tastenkombination",
"shortcutComboHint": "Tastenkombinationen mit Ctrl, Alt und Shift erstellen",
"textMacro": "Textmakro",
"textMacroHint": "Benutzerdefinierten Textbefehl senden"
},
"dictationSetupSheet": {
"description": "Ein Modell herunterladen oder auswählen und das Diktat auf dem gekoppelten Computer aktivieren – alles von hier aus.",
"download": "Herunterladen",
"downloadFailed": "Download fehlgeschlagen",
"downloadModel": "{{model}} herunterladen",
"enabled": "Diktat aktiviert",
"inUse": "In Verwendung",
"loadFailed": "Laden fehlgeschlagen",
"modelExtracting": "{{details}} · wird entpackt…",
"modelProgress": "{{details}} · {{progress}} %",
"modelSize": "{{size}} MB",
"recommended": "Empfohlen",
"selectFailed": "Modellauswahl fehlgeschlagen",
"title": "Diktat einrichten",
"updateFailed": "Aktualisierung fehlgeschlagen",
"use": "Verwenden",
"useModel": "{{model}} verwenden"
},
"diffReviewBody": {
"binaryDiff": "Binärer Diff",
"binaryMessage": "Diese Datei kann auf Mobilgeräten nicht als Text angezeigt werden.",
"deletedFile": "Gelöschte Datei",
"deletedMessage": "Diese Datei wurde gelöscht. Eine Dateinotiz hinzufügen oder die Datei als geprüft markieren.",
"diffTooLarge": "Diff zu groß",
"diffTruncated": "Diff für die mobile Vorschau gekürzt.",
"diffUnavailable": "Diff nicht verfügbar",
"loadFailed": "Review konnte nicht geladen werden",
"loadingDiff": "Diff wird geladen…",
"loadingReview": "Review wird geladen…",
"noReviewableChanges": "Keine Änderungen für ein Review",
"retryLabel": "Review erneut laden",
"reviewUnavailable": "Review nicht verfügbar",
"selectFile": "Eine Datei für das Review auswählen.",
"tooLargeMessage": "Dieser Diff ist zu groß für die mobile Vorschau.",
"tryDifferentFilter": "Einen anderen Review-Filter ausprobieren."
},
"diffReviewDrawers": {
"actionsTitle": "Review-Aktionen",
"addNote": "Notiz hinzufügen",
"cancelNote": "Notizeingabe abbrechen",
"clearSentNotes": "Gesendete Notizen löschen",
"completeTitle": "Review abgeschlossen",
"copyNotes": "Notizen kopieren",
"deleteNote": "Notiz löschen",
"discard": "Verwerfen",
"discardMessage": "Änderungen an „{{filePath}}“ verwerfen? Dies kann nicht rückgängig gemacht werden.",
"discardTitle": "Änderungen verwerfen?",
"editNote": "Notiz bearbeiten",
"fileNote": "Dateinotiz",
"fileSummary_one": "{{fileCount}} Datei geprüft, {{count}} Notiz",
"fileSummary_other": "{{fileCount}} Datei geprüft, {{count}} Notizen",
"filesSummary_one": "{{fileCount}} Dateien geprüft, {{count}} Notiz",
"filesSummary_other": "{{fileCount}} Dateien geprüft, {{count}} Notizen",
"lineNumber": "Zeile {{lineNumber}}",
"loadingSessions": "Agentensitzungen werden geladen…",
"markUnreviewed": "Als ungeprüft markieren",
"newAgentSession": "Neue Agentensitzung",
"openInSession": "In Sitzung öffnen",
"reviewNote": "Review-Notiz",
"saveLineNote": "Notiz zu Zeile {{lineNumber}} speichern",
"sendNotes": "Notizen senden",
"sendNotesLabel": "Notizen an den Agenten senden",
"sendUnsentNotes": "Ungesendete Notizen senden",
"stageReviewed": "Geprüfte vormerken",
"stageReviewedFiles": "Geprüfte Dateien vormerken",
"stageReviewedLabel": "Geprüfte Dateien vormerken",
"stageableFiles_one": "{{count}} geprüfte, nicht vorgemerkte Datei kann vorgemerkt werden",
"stageableFiles_other": "{{count}} geprüfte, nicht vorgemerkte Dateien können vorgemerkt werden",
"statusUnreadable": "Der Status dieses Hosts konnte nicht gelesen werden. Erneuter Versuch…",
"terminal": "Terminal",
"terminalLabel": "{{title}} ({{id}})",
"unsentNotes_one": "{{count}} ungesendete Notiz",
"unsentNotes_other": "{{count}} ungesendete Notizen",
"updateRequired": "Orca auf dem Computer aktualisieren"
},
"diffReviewFileSummary": {
"branch": "Branch",
"branchFrom": "Branch · zuvor {{oldPath}}",
"changed": "Geändert",
"editFileNote": "Dateinotiz bearbeiten",
"hunk": "Änderungsblock",
"nextHunk": "Nächster Änderungsblock",
"notes_one": "{{count}} Notiz",
"notes_other": "{{count}} Notizen",
"previousHunk": "Vorheriger Änderungsblock",
"reviewed": "Geprüft",
"staged": "Vorgemerkt",
"stagedFrom": "Vorgemerkt · zuvor {{oldPath}}",
"stale": "Veraltet",
"staleCount": "{{count}} veraltet",
"unstaged": "Nicht vorgemerkt",
"unstagedFrom": "Nicht vorgemerkt · zuvor {{oldPath}}"
},
"diffReviewFooter": {
"addFileNote": "Dateinotiz hinzufügen",
"discard": "Verwerfen",
"discardFile": "Datei verwerfen",
"markFileReviewed": "Datei als geprüft markieren",
"markReviewed": "Als geprüft markieren",
"nextFile": "Nächste Datei",
"note": "Notiz",
"previousFile": "Vorherige Datei",
"reviewed": "Geprüft",
"stage": "Vormerken",
"stageFile": "Datei vormerken",
"unstage": "Vormerkung aufheben",
"unstageFile": "Vormerkung der Datei aufheben"
},
"diffReviewHeader": {
"filterAll": "Alle",
"filterBranch": "Branch",
"filterNotes": "Notizen",
"filterStaged": "Vorgemerkt",
"filterUnreviewed": "Ungeprüft",
"filterUnstaged": "Nicht vorgemerkt",
"openPullRequestSidebar": "Pull-Request-Seitenleiste öffnen",
"openReviewActions": "Review-Aktionen öffnen",
"reviewedProgress": "{{reviewedCount}}/{{queueLength}} geprüft",
"showAllFiles": "Alle Dateien im Review anzeigen",
"showBranchFiles": "Dateien des Branches im Review anzeigen",
"showNotesFiles": "Dateien mit Notizen im Review anzeigen",
"showStagedFiles": "Vorgemerkte Dateien im Review anzeigen",
"showUnreviewedFiles": "Ungeprüfte Dateien im Review anzeigen",
"showUnstagedFiles": "Nicht vorgemerkte Dateien im Review anzeigen",
"title": "Änderungen",
"unsentNotes_one": "{{count}} ungesendete Notiz",
"unsentNotes_other": "{{count}} ungesendete Notizen"
},
"diffReviewLine": {
"addNoteOnLine": "Notiz zu Zeile {{number}} hinzufügen",
"addedLine": "Hinzugefügte Zeile",
"addedLineNumber": "Hinzugefügte Zeile {{number}}",
"contextLine": "Kontextzeile",
"contextLineNumber": "Kontextzeile {{number}}",
"deletedLine": "Gelöschte Zeile",
"deletedLineNumber": "Gelöschte Zeile {{number}}",
"editNoteOnLine": "Notiz zu Zeile {{number}} bearbeiten"
},
"dragReorderRow": {
"dragToReorder": "Zum Neuordnen ziehen",
"moveDown": "Nach unten verschieben",
"moveUp": "Nach oben verschieben",
"reorderHint": "Mit den Aktionen „Nach oben verschieben“ und „Nach unten verschieben“ lässt sich die Reihenfolge ohne Ziehen ändern"
},
"homeQuickActions": {
"newWorkspace": "Arbeitsbereich erstellen",
"pairDesktop": "Desktop koppeln",
"title": "Schnellaktionen"
},
"hostCard": {
"hostActions": "Aktionen für {{title}}",
"openHost": "{{title}} öffnen",
"pairingInvalid": "Kopplung ungültig",
"pairingTemporarilyUnavailable": "Kopplung vorübergehend nicht verfügbar",
"repairHint": "Zum erneuten Koppeln mit dem Desktop tippen",
"unlockHint": "Telefon entsperren, dann zum erneuten Versuch tippen",
"updateDesktopHint": "Orca auf dem Desktop aktualisieren und sich dort anmelden, um von überall eine Verbindung herzustellen",
"worktreeAccessibilitySummary": "{{summary}}, {{activeCount}} aktiv"
},
"hostDiagnosticsLink": {
"viewDiagnostics": "Netzwerkdiagnose anzeigen"
},
"hostProtocolGate": {
"checkingCompatibility": "Host-Kompatibilität wird geprüft"
},
"hostRouteNoticeBanner": {
"dismissNotice": "Hinweis verwerfen"
},
"htmlPreview": {
"preview": "Vorschau",
"previewRenderedHtml": "Gerendertes HTML in der Vorschau anzeigen",
"source": "Quelltext",
"title": "HTML-Vorschau",
"viewHtmlSource": "HTML-Quelltext anzeigen"
},
"jiraConnectPrompt": {
"checkAgain": "Erneut prüfen",
"connectHint": "Die Jira-Site in Orca auf dem Computer unter Einstellungen → Integrationen hinzufügen und anschließend erneut prüfen.",
"connectOnDesktop": "Jira auf dem Computer verbinden",
"credentialUnreadable": "Die Jira-Zugangsdaten sind gespeichert, können aber nicht gelesen werden: {{credentialError}}",
"updateDesktop": "Orca auf dem Computer aktualisieren"
},
"markdownBlocks": {
"copyCode": "Code kopieren",
"openImage": "Bild öffnen"
},
"mountedBottomDrawer": {
"dismissDrawer": "Bereich schließen"
},
"newWorkspaceFab": {
"newWorkspace": "Arbeitsbereich erstellen"
},
"newWorkspaceSetupScriptField": {
"hooks": "HOOKS",
"label": "Einrichtungsskript",
"run": "Ausführen",
"runSetupCommand": "Einrichtungsbefehl ausführen",
"skip": "Überspringen"
},
"newWorkspaceSshConnectionField": {
"connect": "Verbinden",
"connecting": "Verbindung wird hergestellt…",
"label": "SSH-Verbindung",
"serverRecovery": "Der Server muss vom Desktop aus wiederhergestellt werden: Einstellungen → SSH-Hosts → Server wiederherstellen."
},
"newWorktreeAgentSelection": {
"blankTerminal": "Leeres Terminal"
},
"newWorktreeFormSheet": {
"advanced": "Erweitert",
"agent": "Agent",
"connectTarget": "Mit Ziel verbinden",
"connectTargetFirst": "Zuerst mit dem Ziel verbinden",
"createWorktree": "Worktree erstellen",
"nameOrCreateFrom": "Name oder „Erstellen aus“",
"noProjectsFound": "Keine Projekte gefunden",
"note": "Notiz",
"notePlaceholder": "Notiz schreiben",
"workspaceName": "Name des Arbeitsbereichs"
},
"newWorktreeModal": {
"remoteRepository": "Entferntes Repository"
},
"newWorktreeModalDrawers": {
"agent": "Agent",
"project": "Projekt",
"runOn": "Ausführen auf"
},
"newWorktreeProjectTargetFields": {
"project": "Projekt",
"runOn": "Ausführen auf",
"selectProject": "Projekt auswählen",
"selectRunTarget": "Ausführungsziel auswählen"
},
"prSidebar": {
"loadFailed": "Ein Fehler ist aufgetreten.",
"loading": "Pull Request wird geladen…",
"notPermitted": "Nicht erlaubt – das GitHub-Konto ist nicht verbunden.",
"retryLoading": "Pull Request erneut laden"
},
"protocolBlockScreen": {
"backToHosts": "Zurück zu Hosts",
"desktopTooOldBody": "Die gekoppelte Desktop-App ist für die aktuelle Version von Orca Mobile zu alt. Orca auf dem Computer aktualisieren und dann erneut mit diesem Host verbinden.",
"getVersion": "Orca {{version}} herunterladen",
"mobileTooOldAppStore": "Diese Desktop-App benötigt eine neuere Version von Orca Mobile. Orca Mobile über den App Store aktualisieren und dann erneut mit diesem Host verbinden.",
"mobileTooOldGitHubReleases": "Diese Desktop-App benötigt eine neuere Version von Orca Mobile. Orca Mobile über GitHub Releases aktualisieren und dann erneut mit diesem Host verbinden.",
"openAppStore": "App Store öffnen",
"openGitHubReleases": "GitHub Releases öffnen",
"refreshRecoveryNote": "Wenn diese Meldung weiterhin erscheint, diesen Host entfernen und erneut koppeln.",
"refreshWorkspaceBody": "Der für diesen Host zwischengespeicherte Arbeitsbereich ist älter als von der Desktop-App erwartet. Erneut mit diesem Host verbinden, um die aktuelle Version herunterzuladen.",
"refreshWorkspaceTitle": "Mobilen Arbeitsbereich aktualisieren",
"updateDesktopTitle": "Orca auf dem Computer aktualisieren",
"updateMobileTitle": "Orca Mobile aktualisieren",
"updateRecoveryNote": "Bereits aktualisiert? Zu „Hosts“ zurückkehren und die Verbindung erneuern. Wenn diese Meldung weiterhin erscheint, diesen Host entfernen und erneut koppeln.",
"workspaceNeedsUpdateAppStore": "Der mobile Arbeitsbereich dieser Desktop-App benötigt eine neuere Version von Orca Mobile. Orca Mobile über den App Store aktualisieren und dann erneut mit diesem Host verbinden.",
"workspaceNeedsUpdateGitHubReleases": "Der mobile Arbeitsbereich dieser Desktop-App benötigt eine neuere Version von Orca Mobile. Orca Mobile über GitHub Releases aktualisieren und dann erneut mit diesem Host verbinden."
},
"richMarkdownEditor": {
"imageUrl": "Bild-URL",
"insert": "Einfügen",
"linkUrl": "Link-URL"
},
"richMarkdownToolbar": {
"body": "Fließtext",
"bold": "Fett formatieren",
"bulletList": "Aufzählung",
"checklist": "Checkliste",
"codeBlock": "Codeblock",
"heading1": "H1",
"heading2": "H2",
"heading3": "H3",
"image": "Bild",
"inlineCode": "Inline-Code",
"italic": "Kursiv formatieren",
"link": "Link",
"numberedList": "Nummerierte Liste",
"quote": "Zitat einfügen",
"strike": "Durchstreichen"
},
"searchField": {
"clearSearch": "Suche leeren"
},
"setupHookTrustDrawer": {
"alwaysTrustAndRun": "Immer vertrauen und ausführen",
"description": "Die orca.yaml dieses Repositorys wird vor dem Start des Arbeitsbereichs ausgeführt. Nur ausführen, wenn das Repository vertrauenswürdig ist.",
"dontRun": "Nicht ausführen",
"newScriptLabel": "Neues Einrichtungsskript",
"runHooks": "Hooks ausführen",
"runSetupTitle": "Einrichtungsskript aus {{repoName}} ausführen?",
"scriptChangedTitle": "Einrichtungsskript von {{repoName}} geändert",
"scriptLabel": "Einrichtungsskript"
},
"smartWorkspaceAdvancedFields": {
"branchName": "Branch-Name",
"derivedFromName": "Vom Namen abgeleitet",
"name": "Name",
"reuseBranch": "Branch „{{branch}}“ wiederverwenden",
"workspaceName": "Name des Arbeitsbereichs"
},
"smartWorkspaceSourceDrawer": {
"connectRepository": "Repository verbinden, um Quellen zu durchsuchen.",
"crossRepoMessage": "Dieser Eintrag befindet sich in {{owner}}/{{repo}}.",
"gitHubRemoteRequired": "Dieses SSH-Repo benötigt ein GitHub-Remote, um Issues und PRs aufzulisten.",
"noResults": "Keine Ergebnisse gefunden.",
"searchPlaceholder": "Namen eingeben oder Quelle suchen",
"switchRepo": "Zu {{repoName}} wechseln",
"title": "Name oder „Erstellen aus“",
"typeWorkspaceName": "Im Feld unten einen Namen für den Arbeitsbereich eingeben."
},
"smartWorkspaceSourceField": {
"openSelectedSource": "Ausgewählte Quelle öffnen",
"optional": "[Optional]",
"searchPlaceholder": "Namen eingeben oder Quelle suchen"
},
"smartWorkspaceSourceRow": {
"createBranch": "Branch „{{name}}“ erstellen",
"issue": "Issue #{{number}}",
"mergeRequest": "MR !{{number}}",
"nameWorkspace": "Diesen Arbeitsbereich benennen",
"newBranch": "Neuer Branch",
"pullRequest": "PR #{{number}}",
"useName": "„{{name}}“ verwenden"
},
"sourceCopyButton": {
"clipboardRejected": "Die Zwischenablage hat den Text abgelehnt.",
"copiedLoaded": "Geladenen Text kopiert",
"copyFailed": "Kopieren fehlgeschlagen",
"copyLoaded": "Geladenen Text kopieren",
"couldNotCopy": "Kopieren fehlgeschlagen",
"failed": "Fehlgeschlagen"
},
"terminalShortcutSettings": {
"addDescription": "Tastenkombination oder Textmakro erstellen",
"addShortcut": "Eigenes Tastenkürzel hinzufügen…",
"customShortcuts": "EIGENE TASTENKÜRZEL",
"empty": "Noch keine eigenen Tastenkürzel definiert.",
"removeShortcut": "{{label}} entfernen",
"resetDefaults": "Auf Standard zurücksetzen",
"resetDescription": "Alle integrierten Tastenkürzel in der ursprünglichen Reihenfolge anzeigen",
"shortcutBar": "TASTENKÜRZELLEISTE",
"shortcutBarDescription": "Tasten ein- oder ausblenden. Zum Anordnen in der Terminal-Tastenkürzelleiste den Ziehgriff einer Taste gedrückt halten und die Taste an die gewünschte Position ziehen."
},
"useCodexResetCreditAction": {
"confirmTitle": "Ratenlimit-Zurücksetzung verwenden?",
"detailsChanged": "Details zur Zurücksetzung geändert",
"detailsChangedMessage": "Das Konto oder das Angebot zur Zurücksetzung hat sich geändert, bevor der Host Codex kontaktiert hat. Die aktualisierten Details prüfen und erneut bestätigen.",
"hostAccount": "{{identity}} auf dem Host",
"hostAccountConfirmMessage": "Dabei wird ein verdienter Credit für {{identity}} auf dem Host verbraucht und die Ratenlimits aller zurücksetzbaren Zeitfenster werden sofort zurückgesetzt.",
"hostSelectedAccount": "das ausgewählte verwaltete Konto auf dem Host",
"hostSelectedAccountConfirmMessage": "Dabei wird ein verdienter Credit für das ausgewählte verwaltete Konto auf dem Host verbraucht und die Ratenlimits aller zurücksetzbaren Zeitfenster werden sofort zurückgesetzt.",
"resetFailed": "Ratenlimits konnten nicht zurückgesetzt werden",
"wslAccount": "{{identity}} in WSL {{distro}}",
"wslAccountConfirmMessage": "Dabei wird ein verdienter Credit für {{identity}} in WSL {{distro}} verbraucht und die Ratenlimits aller zurücksetzbaren Zeitfenster werden sofort zurückgesetzt.",
"wslSelectedAccount": "das ausgewählte verwaltete Konto in WSL {{distro}}",
"wslSelectedAccountConfirmMessage": "Dabei wird ein verdienter Credit für das ausgewählte verwaltete Konto in WSL {{distro}} verbraucht und die Ratenlimits aller zurücksetzbaren Zeitfenster werden sofort zurückgesetzt."
},
"voiceModelList": {
"deleteModel": "{{label}} löschen",
"downloadModel": "{{label}} herunterladen",
"inUse": "In Verwendung",
"modelExtracting": "{{size}} · wird entpackt…",
"modelProgress": "{{size}} · {{progress}} %",
"modelSize": "{{size}} MB",
"recommended": "Empfohlen",
"use": "Verwenden",
"useModel": "{{label}} verwenden"
},
"workspaceDetailPlaceholder": {
"description": "Einen Arbeitsbereich in der Seitenleiste auswählen, um dessen Terminal hier zu öffnen.",
"title": "Kein Arbeitsbereich geöffnet"
},
"workspaceNameInput": {
"placeholder": "Name des Arbeitsbereichs"
},
"worktreeListRow": {
"child": "Untergeordnet",
"children_one": "{{count}} untergeordneter Arbeitsbereich",
"children_other": "{{count}} untergeordnete Arbeitsbereiche",
"folder": "Ordner",
"project": "Projekt {{repo}}",
"remoteHost": "Remote-Host"
}
},
"connection": {
"connectionHealth": {
"checkConnection": "Verbindung prüfen",
@@ -567,6 +1035,270 @@
"useChatUi": "Chat-Oberfläche verwenden"
}
},
"pullRequest": {
"checksSection": {
"actionRequired": "Aktion erforderlich",
"cancelled": "Abgebrochen",
"checkDetails": "Details zum Check {{name}}",
"failed": "Fehlgeschlagen",
"failing": "{{count}} fehlgeschlagen",
"failingChecks_one": "{{count}} fehlgeschlagener Check",
"failingChecks_other": "{{count}} fehlgeschlagene Checks",
"fix": "Beheben",
"fixFailing": "Fehlgeschlagene Checks mit KI beheben",
"inProgress": "In Bearbeitung",
"inspectOrFix": "Details prüfen oder einen KI-Korrekturdurchlauf starten.",
"loadDetailsFailed": "Check-Details konnten nicht geladen werden.",
"neutral": "Neutral",
"neutralCount": "{{count}} neutral",
"noChecks": "Keine Checks",
"openOnWeb": "{{name}} im Web öffnen",
"passed": "{{count}} bestanden",
"pending": "Ausstehend",
"pendingCount": "{{count}} ausstehend",
"queued": "In der Warteschlange",
"rerunFailing": "Fehlgeschlagene Checks erneut ausführen",
"skipped": "Übersprungen",
"successful": "Erfolgreich",
"timedOut": "Zeitüberschreitung",
"title": "Checks",
"unavailable": "Checks nicht verfügbar"
},
"commentCard": {
"daysAgo_one": "vor {{count}} Tag",
"daysAgo_other": "vor {{count}} Tagen",
"deleteComment": "Kommentar löschen",
"deleteConfirmMessage": "Dadurch wird der Kommentar auf GitHub dauerhaft gelöscht.",
"deleteConfirmTitle": "Kommentar löschen?",
"editComment": "Kommentar bearbeiten",
"editPlaceholder": "Kommentar bearbeiten…",
"hoursAgo_one": "vor {{count}} Std.",
"hoursAgo_other": "vor {{count}} Std.",
"justNow": "gerade eben",
"minutesAgo_one": "vor {{count}} Min.",
"minutesAgo_other": "vor {{count}} Min.",
"monthsAgo_one": "vor {{count}} Monat",
"monthsAgo_other": "vor {{count}} Monaten",
"openOnGitHub": "Kommentar auf GitHub öffnen",
"reply": "Antworten",
"replyPlaceholder": "Antwort schreiben…",
"replyToComment": "Auf Kommentar antworten",
"resolve": "Erledigen",
"resolveThread": "Thread als erledigt markieren",
"resolved": "erledigt",
"unresolve": "Als ungelöst markieren",
"unresolveThread": "Thread als ungelöst markieren",
"yearsAgo_one": "vor {{count}} Jahr",
"yearsAgo_other": "vor {{count}} Jahren"
},
"commentsSection": {
"addCommentPlaceholder": "Kommentar hinzufügen…",
"all": "Alle",
"bots": "Bots",
"comment": "Kommentieren",
"commentsLoadFailed": "Kommentare konnten nicht geladen werden. Zum erneuten Laden auf „Aktualisieren“ tippen.",
"description": "Beschreibung",
"descriptionLoadFailed": "Beschreibung konnte nicht geladen werden. Zum erneuten Laden auf „Aktualisieren“ tippen.",
"humans": "Menschen",
"noBotComments": "Keine Kommentare von Bots.",
"noComments": "Noch keine Kommentare.",
"noDescription": "Keine Beschreibung angegeben.",
"noHumanComments": "Keine Kommentare von Menschen.",
"resolvedComment_one": "Erledigter Kommentar von {{author}}",
"resolvedComment_other": "Erledigter Kommentar von {{author}} ({{count}})",
"resolvedThread_one": "Erledigter Thread von {{author}}",
"resolvedThread_other": "Erledigter Thread von {{author}} ({{count}})",
"showMore": "{{count}} weitere anzeigen",
"showMoreOf": "{{count}} weitere von {{remaining}} anzeigen",
"title": "Kommentare"
},
"conflictingFilesSection": {
"commitsBehind_one": "{{count}} Commit im Rückstand (Basis-Commit: {{baseCommit}})",
"commitsBehind_other": "{{count}} Commits im Rückstand (Basis-Commit: {{baseCommit}})",
"copyCommands": "Befehle kopieren",
"copyFailed": "Text konnte nicht kopiert werden",
"copyRefreshCommands": "Befehle zum Aktualisieren der Merge-Fähigkeit kopieren",
"detailsUnavailable": "Details zu Dateien mit Konflikten sind nicht verfügbar",
"filesHeading": "Dateien mit Konflikten",
"localMergeClean": "GitHub meldet Konflikte, die mit lokalem Git nicht reproduziert werden konnten. PR aktualisieren oder Branch pushen, um die Merge-Fähigkeit neu zu berechnen.",
"mustResolve": "Dieser Branch enthält Konflikte, die gelöst werden müssen",
"refreshing": "Konfliktdetails werden aktualisiert…",
"resolveWithAi": "Konflikte mit KI lösen",
"runFromWorktree": "Aus diesem Worktree ausführen",
"title": "Konflikte"
},
"linkPrForm": {
"linkButton": "Pull Request verknüpfen",
"linkNumber": "#{{number}} verknüpfen",
"referenceLabel": "PR-Nummer oder GitHub-URL",
"referencePlaceholder": "#123 oder https://github.com/owner/repo/pull/123",
"title": "Bestehenden Pull Request verknüpfen"
},
"markdownBlocks": {
"details": "Details"
},
"prActionsSection": {
"autoMergeAccessibilityLabel": "Automatisches Mergen umschalten",
"autoMergeLabel": "Automatisch mergen, sobald bereit",
"autoMergeOff": "Aus",
"autoMergeOn": "Ein",
"closeAccessibilityLabel": "Pull Request schließen",
"closeConfirmMessage": "#{{number}} wird ohne Merge geschlossen.",
"closeConfirmTitle": "Pull Request schließen?",
"markReadyAccessibilityLabel": "Pull Request als bereit für das Review markieren",
"markReadyButton": "Für das Review freigeben",
"mergeAccessibilityLabel": "Pull Request mergen",
"mergeButton": "Mergen",
"mergeConfirmTitle": "Pull Request mergen?",
"recheckAccessibilityLabel": "Merge-Status erneut prüfen",
"recheckButton": "Erneut prüfen",
"reopenAccessibilityLabel": "Pull Request erneut öffnen",
"reopenButton": "Erneut öffnen",
"reopenConfirmMessage": "#{{number}} wird erneut geöffnet.",
"reopenConfirmTitle": "Pull Request erneut öffnen?",
"unlinkAccessibilityLabel": "Verknüpfung zum Pull Request aufheben",
"unlinkButton": "Verknüpfung aufheben",
"unlinkFailed": "Die Verknüpfung zum Pull Request konnte nicht aufgehoben werden."
},
"prActionsState": {
"behindHint": "Branch vor dem Mergen aktualisieren",
"behindLabel": "Im Rückstand",
"blockedHint": "GitHub meldet diesen Pull Request als blockiert",
"blockedLabel": "Blockiert",
"changesRequestedHint": "GitHub meldet angeforderte Änderungen an diesem Pull Request",
"changesRequestedLabel": "Änderungen angefordert",
"checkingHint": "GitHub ermittelt noch den Merge-Status dieses Pull Requests",
"checkingLabel": "Wird geprüft",
"checksPassedHint": "Checks bestanden. Vor dem Mergen wird erneut geprüft, ob ein Merge möglich ist.",
"checksPassedLabel": "Checks bestanden",
"closedHint": "Dieser Pull Request ist geschlossen",
"closedLabel": "Geschlossen",
"conflictsHint": "GitHub meldet Merge-Konflikte",
"conflictsLabel": "Konflikte",
"draftHint": "Dieser Pull Request ist noch ein Entwurf",
"draftLabel": "Entwurf",
"mergeConfirmMessage": "Damit wird #{{number}} in seinen Basis-Branch gemergt.",
"mergeQueueAutoMergeOnHint": "Dieser Basis-Branch verwendet die GitHub-Merge-Warteschlange",
"mergeQueueAutoMergeOnLabel": "Auto-Merge aktiviert",
"mergeQueueHint": "Dieser Basis-Branch verwendet die GitHub-Merge-Warteschlange",
"mergeQueueLabel": "Mergen, sobald möglich",
"mergeStackMessage": "GitHub mergt diesen Pull Request und alle darunterliegenden Pull Requests im Stack.",
"mergeStatusUnavailableHint": "Merge-Status für diesen PR nicht verfügbar",
"mergeStatusUnavailableLabel": "Mergen",
"mergeThrough_one": "Bis #{{number}} mergen · {{count}} PR",
"mergeThrough_other": "Bis #{{number}} mergen · {{count}} PRs",
"mergeableChecksFailedHint": "Laut GitHub ist ein Merge dieses PRs möglich, aber einige Checks sind fehlgeschlagen",
"mergeableChecksFailedLabel": "Checks fehlgeschlagen",
"mergeableChecksPassedHint": "Laut GitHub ist ein Merge dieses PRs möglich und die Checks sind bestanden",
"mergeableChecksPassedLabel": "Merge möglich",
"mergeableChecksPendingHint": "Laut GitHub ist ein Merge dieses PRs möglich, aber es laufen noch Checks",
"mergeableChecksPendingLabel": "Checks ausstehend",
"mergeableHint": "Laut GitHub ist ein Merge dieses PRs möglich",
"mergeableLabel": "Merge möglich",
"mergedHint": "Dieser Pull Request ist bereits gemergt",
"mergedLabel": "Gemergt",
"methodMerge": "Merge-Commit erstellen",
"methodRebase": "Rebasen und mergen",
"methodSquash": "Squashen und mergen",
"queueStackMessage": "GitHub fügt diesen Pull Request und alle darunterliegenden Pull Requests zur Merge-Warteschlange hinzu.",
"queueThrough_one": "Bis #{{number}} einreihen · {{count}} PR",
"queueThrough_other": "Bis #{{number}} einreihen · {{count}} PRs",
"reviewRequiredHint": "GitHub erfordert eine Review-Genehmigung, bevor dieser Pull Request gemergt werden kann",
"reviewRequiredLabel": "Genehmigung erforderlich",
"stackBehind": "#{{number}} muss aktualisiert werden.",
"stackBlocked": "#{{number}} ist blockiert.",
"stackChangesRequested": "Für #{{number}} wurden Änderungen angefordert.",
"stackClosed": "#{{number}} ist geschlossen.",
"stackConflicts": "#{{number}} hat Merge-Konflikte.",
"stackDraft": "#{{number}} ist noch ein Entwurf.",
"stackReviewRequired": "#{{number}} benötigt noch eine Review-Genehmigung."
},
"prCheckDetail": {
"annotations": "Anmerkungen",
"annotationsTruncated": "Die ersten 20 Anmerkungen werden angezeigt",
"jobsTruncated": "Die ersten 100 Jobs werden angezeigt",
"noDetails": "Keine Details verfügbar."
},
"prCheckDetailContent": {
"annotation": "Anmerkung",
"failedJobs": "Fehlgeschlagene Jobs",
"jobs": "Jobs",
"unknown": "unbekannt"
},
"prChecksPresentation": {
"failing_one": "{{count}} fehlgeschlagen",
"failing_other": "{{count}} fehlgeschlagen",
"neutral_one": "{{count}} neutral",
"neutral_other": "{{count}} neutral",
"noChecks": "Keine Checks",
"passed_one": "{{count}} bestanden",
"passed_other": "{{count}} bestanden",
"pending_one": "{{count}} ausstehend",
"pending_other": "{{count}} ausstehend",
"stateClosed": "Geschlossen",
"stateDraft": "Entwurf",
"stateMerged": "Gemergt",
"stateOpen": "Offen",
"unavailable": "Checks nicht verfügbar"
},
"prCommentAudienceLabels": {
"all": "Alle",
"bots": "Bots",
"humans": "Menschen",
"noBotComments": "Keine Kommentare von Bots.",
"noComments": "Noch keine Kommentare.",
"noHumanComments": "Keine Kommentare von Menschen."
},
"prSidebarCreateEmptyState": {
"branchNotLinked": "{{branch}} ist mit keinem offenen PR verknüpft.",
"branchRequired": "Vor dem Erstellen eines Pull Requests einen Branch auschecken.",
"createFailed": "Pull Request konnte nicht erstellt werden.",
"createPr": "PR erstellen",
"createPullRequest": "Pull Request erstellen",
"currentBranchNotLinked": "Der aktuelle Branch ist mit keinem offenen PR verknüpft.",
"linkExistingPr": "Bestehenden PR verknüpfen",
"linkExistingPullRequest": "Bestehenden Pull Request verknüpfen",
"linkedPrUnavailable": "Verknüpfter PR #{{number}} nicht verfügbar",
"noOpenPullRequest": "Kein offener Pull Request",
"refreshOrCreate": "Zum erneuten Prüfen aktualisieren oder einen neuen PR für diesen Branch erstellen.",
"refreshPullRequest": "Pull Request aktualisieren",
"title": "Pull Request"
},
"prSidebarHeader": {
"cancelEditingTitle": "Titelbearbeitung abbrechen",
"editTitle": "PR-Titel bearbeiten",
"openInBrowser": "Pull Request #{{number}} im Browser öffnen",
"openOnWeb": "Pull Request #{{number}} im Web öffnen",
"saveTitle": "Titel speichern"
},
"prViewPanel": {
"branchUnavailable": "Aktueller Branch nicht verfügbar.",
"providerUnavailable": "Gehostetes Review-Panel für diesen Anbieter nicht verfügbar."
},
"reviewerPickerDrawer": {
"loadFailed": "Personen konnten nicht geladen werden.",
"noMatches": "Keine passenden Personen",
"removeReviewer": "{{login}} entfernen",
"requestReviewer": "Review von {{login}} anfordern",
"searchPeople": "Personen suchen",
"title": "Prüfer"
},
"reviewersSection": {
"addOrRemove": "Prüfer hinzufügen oder entfernen",
"approved": "Genehmigt",
"changesRequested": "Änderungen angefordert",
"commented": "Kommentiert",
"dismissed": "Verworfen",
"loadFailed": "Prüfer konnten nicht geladen werden. Zum erneuten Laden auf „Aktualisieren“ tippen.",
"loading": "Prüfer werden geladen…",
"noReviewers": "Keine Prüfer angefragt",
"pending": "Ausstehend",
"removeReviewer": "{{login}} entfernen",
"requested": "Angefragt",
"reviewed": "Geprüft",
"title": "Prüfer"
}
},
"settings": {
"aboutScreen": {
"openLinkFailed": "Der Link konnte nicht geöffnet werden. Erneut versuchen.",
+733 -1
View File
@@ -150,7 +150,12 @@
"remove": "Remove",
"settings": "Settings",
"delete": "Delete",
"unknown": "Unknown"
"unknown": "Unknown",
"confirm": "Confirm",
"copy": "Copy",
"done": "Done",
"close": "Close",
"edit": "Edit"
},
"host": {
"edit": {
@@ -760,5 +765,732 @@
"closed": "Closed",
"done": "Done"
}
},
"components": {
"agentLaunchNotice": {
"updateRequired": "Update Orca on your computer to start an agent from your phone.",
"statusUnreadable": "Could not read this host's status. Retrying…",
"copyFailed": "Couldn't copy",
"copyPrompt": "Copy prompt"
},
"agentSpinner": {
"monitoring": "Monitoring background tasks",
"unconfirmed": "Couldn’t confirm"
},
"agentStateDot": {
"monitoring": "Monitoring background tasks",
"unverifiable": "No recent update"
},
"authFailedBanner": {
"message": "Authentication failed — try reconnecting first; if it keeps failing, re-pair from desktop."
},
"authFailedBannerActions": {
"repair": "Re-pair",
"webNote": "Reconnect or remove this host from the Orca app."
},
"codexResetCreditAction": {
"earnedReset": "Earned Codex rate-limit reset",
"resettingLabel": "Resetting Codex rate limits",
"useResetLabel": "Use Codex rate-limit reset",
"scopedHint": "Uses one earned reset for {{scopeLabel}}",
"activeAccountHint": "Uses one earned reset for the active Codex account",
"resetting": "Resetting…",
"useReset": "Use reset"
},
"customKeyModal": {
"editing": "Editing",
"navigation": "Navigation",
"function": "Function",
"addShortcut": "Add Shortcut",
"shortcutCombo": "Shortcut Combo",
"pickKey": "Pick a key",
"textMacro": "Text Macro",
"shortcutComboHint": "Build Ctrl, Alt, and Shift key chords",
"textMacroHint": "Send custom text command",
"manageShortcuts": "Manage Shortcuts",
"manageShortcutsHint": "Show, hide, or reorder shortcut keys",
"modifiers": "Modifiers",
"key": "Key",
"moreKeys": "More keys — Tab, arrows, F1–F12…",
"add": "Add",
"label": "Label",
"labelPlaceholder": "e.g. Build",
"command": "Command",
"commandPlaceholder": "e.g. pnpm build",
"pressEnter": "Press Enter"
},
"dragReorderRow": {
"dragToReorder": "Drag to reorder",
"reorderHint": "Use the move up and move down actions to reorder without dragging",
"moveUp": "Move up",
"moveDown": "Move down"
},
"hostDiagnosticsLink": {
"viewDiagnostics": "View network diagnostics"
},
"hostProtocolGate": {
"checkingCompatibility": "Checking host compatibility"
},
"hostRouteNoticeBanner": {
"dismissNotice": "Dismiss notice"
},
"jiraConnectPrompt": {
"updateDesktop": "Update Orca desktop",
"connectOnDesktop": "Connect Jira on your desktop",
"credentialUnreadable": "Jira is saved but unreadable: {{credentialError}}",
"connectHint": "Add your Jira site in Orca desktop under Settings → Integrations, then check again.",
"checkAgain": "Check again"
},
"dictationSetupSheet": {
"loadFailed": "Failed to load",
"downloadFailed": "Download failed",
"selectFailed": "Could not select model",
"updateFailed": "Could not update",
"modelSize": "{{size}} MB",
"modelProgress": "{{details}} · {{progress}}%",
"modelExtracting": "{{details}} · extracting…",
"title": "Set up voice dictation",
"description": "Download or pick a model and enable dictation on the paired computer — all from here.",
"enabled": "Dictation enabled",
"recommended": "Recommended",
"inUse": "In use",
"useModel": "Use {{model}}",
"use": "Use",
"downloadModel": "Download {{model}}",
"download": "Download"
},
"diffReviewBody": {
"loadingReview": "Loading review...",
"reviewUnavailable": "Review Unavailable",
"loadFailed": "Unable to Load Review",
"noReviewableChanges": "No Reviewable Changes",
"tryDifferentFilter": "Try a different review filter.",
"loadingDiff": "Loading diff...",
"diffTruncated": "Diff truncated for mobile preview.",
"binaryDiff": "Binary Diff",
"diffTooLarge": "Diff Too Large",
"deletedFile": "Deleted File",
"diffUnavailable": "Diff Unavailable",
"binaryMessage": "This file cannot be rendered as text on mobile.",
"tooLargeMessage": "This diff is too large for the mobile preview.",
"deletedMessage": "This file was deleted. Add a file note or mark it reviewed.",
"selectFile": "Select a file to review.",
"retryLabel": "Retry loading review"
},
"diffReviewDrawers": {
"discardTitle": "Discard File",
"discardMessage": "Discard changes to \"{{filePath}}\"? This cannot be undone.",
"discard": "Discard",
"actionsTitle": "Review Actions",
"stageableFiles_one": "{{count}} reviewed unstaged files can be staged",
"stageableFiles_other": "{{count}} reviewed unstaged files can be staged",
"sendNotes": "Send Notes",
"terminalLabel": "{{title}} ({{id}})",
"terminal": "Terminal",
"newAgentSession": "New Agent Session",
"updateRequired": "Update Orca on your computer",
"statusUnreadable": "Could not read this host's status. Retrying…",
"copyNotes": "Copy Notes",
"sendUnsentNotes": "Send Unsent Notes",
"clearSentNotes": "Clear Sent Notes",
"stageReviewedFiles": "Stage Reviewed Files",
"markUnreviewed": "Mark Unreviewed",
"openInSession": "Open in Session",
"loadingSessions": "Loading agent sessions...",
"unsentNotes_one": "{{count}} unsent notes",
"unsentNotes_other": "{{count}} unsent notes",
"editNote": "Edit Note",
"addNote": "Add Note",
"lineNumber": "Line {{lineNumber}}",
"fileNote": "File note",
"cancelNote": "Cancel note",
"reviewNote": "Review note",
"saveLineNote": "Save note on line {{lineNumber}}",
"deleteNote": "Delete note",
"completeTitle": "Review Complete",
"fileSummary_one": "{{fileCount}} file reviewed, {{count}} note",
"fileSummary_other": "{{fileCount}} file reviewed, {{count}} notes",
"filesSummary_one": "{{fileCount}} files reviewed, {{count}} note",
"filesSummary_other": "{{fileCount}} files reviewed, {{count}} notes",
"stageReviewedLabel": "Stage reviewed files",
"stageReviewed": "Stage Reviewed",
"sendNotesLabel": "Send notes to agent"
},
"diffReviewFileSummary": {
"branchFrom": "Branch from {{oldPath}}",
"stagedFrom": "Staged from {{oldPath}}",
"unstagedFrom": "Unstaged from {{oldPath}}",
"branch": "Branch",
"staged": "Staged",
"unstaged": "Unstaged",
"reviewed": "Reviewed",
"changed": "Changed",
"notes_one": "{{count}} note",
"notes_other": "{{count}} notes",
"staleCount": "{{count}} stale",
"editFileNote": "Edit file note",
"stale": "Stale",
"previousHunk": "Previous hunk",
"hunk": "Hunk",
"nextHunk": "Next hunk"
},
"diffReviewFooter": {
"stageFile": "Stage file",
"stage": "Stage",
"unstageFile": "Unstage file",
"unstage": "Unstage",
"discardFile": "Discard file",
"discard": "Discard",
"previousFile": "Previous file",
"addFileNote": "Add file note",
"note": "Note",
"markFileReviewed": "Mark file reviewed",
"reviewed": "Reviewed",
"markReviewed": "Mark Reviewed",
"nextFile": "Next file"
},
"diffReviewHeader": {
"filterAll": "All",
"showAllFiles": "Show all review files",
"filterUnreviewed": "Unreviewed",
"showUnreviewedFiles": "Show unreviewed review files",
"filterNotes": "Notes",
"showNotesFiles": "Show notes review files",
"filterUnstaged": "Unstaged",
"showUnstagedFiles": "Show unstaged review files",
"filterStaged": "Staged",
"showStagedFiles": "Show staged review files",
"filterBranch": "Branch",
"showBranchFiles": "Show branch review files",
"title": "Changes",
"openPullRequestSidebar": "Open pull request sidebar",
"openReviewActions": "Open review actions",
"reviewedProgress": "{{reviewedCount}}/{{queueLength}} reviewed",
"unsentNotes_one": "{{count}} unsent note",
"unsentNotes_other": "{{count}} unsent notes"
},
"diffReviewLine": {
"addedLineNumber": "Added line {{number}}",
"addedLine": "Added line",
"deletedLineNumber": "Deleted line {{number}}",
"deletedLine": "Deleted line",
"contextLineNumber": "Context line {{number}}",
"contextLine": "Context line",
"addNoteOnLine": "Add note on line {{number}}",
"editNoteOnLine": "Edit note on line {{number}}"
},
"homeQuickActions": {
"title": "Quick Actions",
"pairDesktop": "Pair Desktop",
"newWorkspace": "New Workspace"
},
"hostCard": {
"worktreeAccessibilitySummary": "{{summary}}, {{activeCount}} active",
"pairingInvalid": "Pairing invalid",
"pairingTemporarilyUnavailable": "Pairing temporarily unavailable",
"updateDesktopHint": "Update desktop Orca and sign in to connect from anywhere",
"repairHint": "Tap to re-pair with your desktop",
"unlockHint": "Unlock your phone, then tap to retry",
"openHost": "Open {{title}}",
"hostActions": "Actions for {{title}}"
},
"htmlPreview": {
"previewRenderedHtml": "Preview rendered HTML",
"preview": "Preview",
"viewHtmlSource": "View HTML source",
"source": "Source",
"title": "HTML preview"
},
"prSidebar": {
"loading": "Loading pull request…",
"loadFailed": "Something went wrong.",
"retryLoading": "Retry loading pull request",
"notPermitted": "Not permitted — your GitHub account is not connected."
},
"richMarkdownEditor": {
"linkUrl": "Link URL",
"imageUrl": "Image URL",
"insert": "Insert"
},
"richMarkdownToolbar": {
"body": "Body",
"heading1": "H1",
"heading2": "H2",
"heading3": "H3",
"bold": "Bold",
"italic": "Italic",
"strike": "Strike",
"bulletList": "Bullet list",
"numberedList": "Numbered list",
"checklist": "Checklist",
"quote": "Quote",
"link": "Link",
"image": "Image",
"inlineCode": "Inline code",
"codeBlock": "Code block"
},
"searchField": {
"clearSearch": "Clear search"
},
"sourceCopyButton": {
"copyFailed": "Copy failed",
"clipboardRejected": "The clipboard rejected the text.",
"failed": "Failed",
"copiedLoaded": "Copied loaded",
"copyLoaded": "Copy loaded",
"couldNotCopy": "Couldn't copy"
},
"workspaceNameInput": {
"placeholder": "Workspace name"
},
"newWorkspaceFab": {
"newWorkspace": "New workspace"
},
"newWorkspaceSetupScriptField": {
"label": "Setup script",
"hooks": "HOOKS",
"run": "Run",
"skip": "Skip",
"runSetupCommand": "Run setup command"
},
"newWorkspaceSshConnectionField": {
"label": "SSH Connection",
"connecting": "Connecting...",
"connect": "Connect",
"serverRecovery": "Its server needs recovering from the desktop: Settings → SSH hosts → Recover server."
},
"newWorktreeFormSheet": {
"createWorktree": "Create worktree",
"noProjectsFound": "No projects found",
"nameOrCreateFrom": "Name or 'Create From'",
"workspaceName": "Workspace name",
"agent": "Agent",
"connectTargetFirst": "Connect target first",
"advanced": "Advanced",
"note": "Note",
"notePlaceholder": "Write a note",
"connectTarget": "Connect target"
},
"newWorktreeModal": {
"remoteRepository": "Remote repository"
},
"newWorktreeModalDrawers": {
"project": "Project",
"runOn": "Run on",
"agent": "Agent"
},
"newWorktreeProjectTargetFields": {
"project": "Project",
"selectProject": "Select a project",
"runOn": "Run on",
"selectRunTarget": "Select a run target"
},
"protocolBlockScreen": {
"updateMobileTitle": "Update Orca Mobile",
"updateDesktopTitle": "Update Orca on your computer",
"refreshWorkspaceTitle": "Refresh the mobile workspace",
"refreshWorkspaceBody": "The workspace cached for this host is older than the desktop expects. Reconnect to this host to download the current one.",
"mobileTooOldAppStore": "This desktop needs a newer Orca Mobile app. Update Orca Mobile from the App Store, then try this host again.",
"mobileTooOldGitHubReleases": "This desktop needs a newer Orca Mobile app. Update Orca Mobile from GitHub Releases, then try this host again.",
"workspaceNeedsUpdateAppStore": "This desktop's mobile workspace needs a newer Orca Mobile app. Update Orca Mobile from the App Store, then try this host again.",
"workspaceNeedsUpdateGitHubReleases": "This desktop's mobile workspace needs a newer Orca Mobile app. Update Orca Mobile from GitHub Releases, then try this host again.",
"desktopTooOldBody": "This paired desktop app is too old for your current Orca Mobile app. Update Orca on your computer, then try this host again.",
"openAppStore": "Open App Store",
"openGitHubReleases": "Open GitHub Releases",
"getVersion": "Get Orca {{version}}",
"refreshRecoveryNote": "If this message stays, remove this host and pair it again.",
"updateRecoveryNote": "Already updated? Go back to Hosts and refresh the connection. If this message stays, remove this host and pair it again.",
"backToHosts": "Back to hosts"
},
"setupHookTrustDrawer": {
"scriptChangedTitle": "{{repoName}}'s setup script changed",
"runSetupTitle": "Run setup from {{repoName}}?",
"description": "This repository's orca.yaml runs before the workspace starts. Only run it if you trust this repository.",
"newScriptLabel": "New setup script",
"scriptLabel": "Setup script",
"runHooks": "Run hooks",
"alwaysTrustAndRun": "Always trust and run",
"dontRun": "Don't run"
},
"smartWorkspaceAdvancedFields": {
"name": "Name",
"workspaceName": "Workspace name",
"branchName": "Branch name",
"derivedFromName": "Derived from name",
"reuseBranch": "Reuse branch “{{branch}}”"
},
"smartWorkspaceSourceDrawer": {
"title": "Name or 'Create From'",
"crossRepoMessage": "This item lives in {{owner}}/{{repo}}.",
"switchRepo": "Switch to {{repoName}}",
"connectRepository": "Connect the repository to search sources.",
"gitHubRemoteRequired": "This SSH repo needs a GitHub remote to list issues and PRs.",
"noResults": "No results found.",
"typeWorkspaceName": "Type a workspace name in the field below.",
"searchPlaceholder": "Type a name or search a source"
},
"smartWorkspaceSourceField": {
"optional": "[Optional]",
"openSelectedSource": "Open selected source",
"searchPlaceholder": "Type a name or search a source"
},
"smartWorkspaceSourceRow": {
"useName": "Use \"{{name}}\"",
"nameWorkspace": "Name this workspace",
"createBranch": "Create branch \"{{name}}\"",
"newBranch": "New branch",
"pullRequest": "PR #{{number}}",
"issue": "Issue #{{number}}",
"mergeRequest": "MR !{{number}}"
},
"terminalShortcutSettings": {
"shortcutBar": "SHORTCUT BAR",
"shortcutBarDescription": "Toggle keys to show or hide them, and hold the grip to drag a key into the order you want on the terminal shortcut bar.",
"resetDefaults": "Reset Defaults",
"resetDescription": "Show every built-in shortcut key in the original order",
"customShortcuts": "CUSTOM SHORTCUTS",
"empty": "No custom shortcuts defined yet.",
"removeShortcut": "Remove {{label}}",
"addShortcut": "Add Custom Shortcut…",
"addDescription": "Create key combo or text macro"
},
"voiceModelList": {
"modelSize": "{{size}} MB",
"modelProgress": "{{size}} · {{progress}}%",
"modelExtracting": "{{size}} · extracting…",
"recommended": "Recommended",
"inUse": "In use",
"useModel": "Use {{label}}",
"use": "Use",
"deleteModel": "Delete {{label}}",
"downloadModel": "Download {{label}}"
},
"workspaceDetailPlaceholder": {
"title": "No workspace open",
"description": "Pick a workspace from the sidebar to open its terminal here."
},
"worktreeListRow": {
"folder": "Folder",
"remoteHost": "Remote host",
"project": "Project {{repo}}",
"child": "Child",
"children_one": "{{count}} child",
"children_other": "{{count}} children"
},
"accountsSnapshot": {
"invalidSnapshot": "Invalid accounts snapshot from host"
},
"codexResetCredit": {
"available_one": "{{count}} reset available",
"available_other": "{{count}} resets available",
"resetTitle": "Rate limits reset",
"alreadyAppliedTitle": "Reset already applied",
"refresh": "Codex usage has been refreshed.",
"nothingToResetTitle": "Nothing to reset",
"nothingToResetMessage": "No eligible Codex rate-limit window is exhausted.",
"noResetTitle": "No reset available",
"noResetMessage": "This account has no earned reset credits available.",
"invalidResponse": "Invalid reset response from host",
"expiryNowSingle": "Expires now",
"expiryNowMultiple": "Next expires now",
"expiryMinutesSingle": "Expires in {{minutes}}m",
"expiryMinutesMultiple": "Next expires in {{minutes}}m",
"expiryHoursMinutesSingle": "Expires in {{hours}}h {{minutes}}m",
"expiryHoursMinutesMultiple": "Next expires in {{hours}}h {{minutes}}m",
"expiryHoursSingle": "Expires in {{hours}}h",
"expiryHoursMultiple": "Next expires in {{hours}}h",
"expiryDaysHoursSingle": "Expires in {{days}}d {{hours}}h",
"expiryDaysHoursMultiple": "Next expires in {{days}}d {{hours}}h",
"expiryDaysSingle": "Expires in {{days}}d",
"expiryDaysMultiple": "Next expires in {{days}}d"
},
"markdownBlocks": {
"copyCode": "Copy code",
"openImage": "Open image"
},
"mountedBottomDrawer": {
"dismissDrawer": "Dismiss drawer"
},
"newWorktreeAgentSelection": {
"blankTerminal": "Blank Terminal"
},
"useCodexResetCreditAction": {
"hostAccount": "{{identity}} on the host",
"hostAccountConfirmMessage": "This spends one earned reset for {{identity}} on the host and immediately resets eligible rate-limit windows.",
"hostSelectedAccount": "the selected managed account on the host",
"hostSelectedAccountConfirmMessage": "This spends one earned reset for the selected managed account on the host and immediately resets eligible rate-limit windows.",
"wslAccount": "{{identity}} on WSL {{distro}}",
"wslAccountConfirmMessage": "This spends one earned reset for {{identity}} on WSL {{distro}} and immediately resets eligible rate-limit windows.",
"wslSelectedAccount": "the selected managed account on WSL {{distro}}",
"wslSelectedAccountConfirmMessage": "This spends one earned reset for the selected managed account on WSL {{distro}} and immediately resets eligible rate-limit windows.",
"detailsChanged": "Reset details changed",
"detailsChangedMessage": "The account or reset offer changed before the host contacted Codex. Review the updated details, then confirm again.",
"resetFailed": "Could not reset rate limits",
"confirmTitle": "Use a rate-limit reset?"
}
},
"pullRequest": {
"linkPrForm": {
"title": "Link existing pull request",
"referenceLabel": "PR number or GitHub URL",
"referencePlaceholder": "#123 or https://github.com/owner/repo/pull/123",
"linkNumber": "Link #{{number}}",
"linkButton": "Link pull request"
},
"prViewPanel": {
"providerUnavailable": "Hosted review panel unavailable for this provider.",
"branchUnavailable": "Current branch unavailable."
},
"prActionsSection": {
"unlinkFailed": "Failed to unlink pull request.",
"mergeConfirmTitle": "Merge pull request?",
"mergeButton": "Merge",
"closeConfirmTitle": "Close pull request?",
"closeConfirmMessage": "#{{number}} will be closed without merging.",
"reopenConfirmTitle": "Reopen pull request?",
"reopenConfirmMessage": "#{{number}} will be reopened.",
"reopenButton": "Reopen",
"mergeAccessibilityLabel": "Merge pull request",
"recheckAccessibilityLabel": "Recheck merge status",
"recheckButton": "Recheck",
"markReadyAccessibilityLabel": "Mark pull request ready for review",
"markReadyButton": "Ready for review",
"autoMergeLabel": "Auto-merge when ready",
"autoMergeAccessibilityLabel": "Toggle auto-merge",
"autoMergeOn": "On",
"autoMergeOff": "Off",
"closeAccessibilityLabel": "Close pull request",
"reopenAccessibilityLabel": "Reopen pull request",
"unlinkAccessibilityLabel": "Unlink pull request",
"unlinkButton": "Unlink"
},
"prCheckDetail": {
"noDetails": "No details available.",
"annotations": "Annotations",
"annotationsTruncated": "Showing first 20 annotations",
"jobsTruncated": "Showing first 100 jobs"
},
"checksSection": {
"unavailable": "Checks unavailable",
"noChecks": "No checks",
"failing": "{{count}} failing",
"pendingCount": "{{count}} pending",
"passed": "{{count}} passed",
"neutralCount": "{{count}} neutral",
"successful": "Successful",
"failed": "Failed",
"cancelled": "Cancelled",
"timedOut": "Timed out",
"actionRequired": "Action required",
"neutral": "Neutral",
"skipped": "Skipped",
"queued": "Queued",
"inProgress": "In progress",
"pending": "Pending",
"loadDetailsFailed": "Failed to load check details",
"title": "Checks",
"rerunFailing": "Rerun failing checks",
"failingChecks_one": "{{count}} failing check",
"failingChecks_other": "{{count}} failing checks",
"inspectOrFix": "Inspect details or start an AI fix pass.",
"fixFailing": "Fix failing checks with AI",
"fix": "Fix",
"checkDetails": "{{name}} check details",
"openOnWeb": "Open {{name}} on the web"
},
"commentCard": {
"justNow": "just now",
"minutesAgo_one": "{{count}}m ago",
"minutesAgo_other": "{{count}}m ago",
"hoursAgo_one": "{{count}}h ago",
"hoursAgo_other": "{{count}}h ago",
"daysAgo_one": "{{count}}d ago",
"daysAgo_other": "{{count}}d ago",
"monthsAgo_one": "{{count}}mo ago",
"monthsAgo_other": "{{count}}mo ago",
"yearsAgo_one": "{{count}}y ago",
"yearsAgo_other": "{{count}}y ago",
"resolved": "resolved",
"openOnGitHub": "Open comment on GitHub",
"editPlaceholder": "Edit comment…",
"replyToComment": "Reply to comment",
"reply": "Reply",
"editComment": "Edit comment",
"deleteComment": "Delete comment",
"unresolveThread": "Unresolve thread",
"resolveThread": "Resolve thread",
"unresolve": "Unresolve",
"resolve": "Resolve",
"replyPlaceholder": "Write a reply…",
"deleteConfirmTitle": "Delete comment?",
"deleteConfirmMessage": "This permanently deletes the comment on GitHub."
},
"commentsSection": {
"all": "All",
"humans": "Humans",
"bots": "Bots",
"noBotComments": "No bot comments.",
"noHumanComments": "No human comments.",
"noComments": "No comments yet.",
"description": "Description",
"descriptionLoadFailed": "Could not load description. Tap refresh to try again.",
"noDescription": "No description provided.",
"title": "Comments",
"commentsLoadFailed": "Could not load comments. Tap refresh to try again.",
"showMoreOf": "Show {{count}} more of {{remaining}}",
"showMore": "Show {{count}} more",
"addCommentPlaceholder": "Add a comment…",
"comment": "Comment",
"resolvedThread_one": "Resolved thread by {{author}}",
"resolvedThread_other": "Resolved thread by {{author}} ({{count}})",
"resolvedComment_one": "Resolved comment by {{author}}",
"resolvedComment_other": "Resolved comment by {{author}} ({{count}})"
},
"conflictingFilesSection": {
"detailsUnavailable": "Conflict file details are unavailable",
"refreshing": "Refreshing conflict details…",
"localMergeClean": "GitHub reports conflicts, but local Git did not reproduce them. Refresh the PR or push the branch to recalculate mergeability.",
"copyFailed": "Failed to copy text",
"copyCommands": "Copy commands",
"commitsBehind_one": "{{count}} commit behind (base commit: {{baseCommit}})",
"commitsBehind_other": "{{count}} commits behind (base commit: {{baseCommit}})",
"title": "Conflicts",
"mustResolve": "This branch has conflicts that must be resolved",
"runFromWorktree": "Run from this worktree",
"copyRefreshCommands": "Copy mergeability refresh commands",
"filesHeading": "Conflicting files",
"resolveWithAi": "Resolve conflicts with AI"
},
"reviewersSection": {
"requested": "Requested",
"approved": "Approved",
"changesRequested": "Changes requested",
"commented": "Commented",
"dismissed": "Dismissed",
"pending": "Pending",
"reviewed": "Reviewed",
"addOrRemove": "Add or remove reviewers",
"title": "Reviewers",
"loading": "Loading reviewers…",
"loadFailed": "Could not load reviewers. Tap refresh to try again.",
"noReviewers": "No reviewers requested",
"removeReviewer": "Remove {{login}}"
},
"prSidebarHeader": {
"openOnWeb": "Open pull request #{{number}} on the web",
"openInBrowser": "Open pull request #{{number}} in browser",
"cancelEditingTitle": "Cancel editing title",
"saveTitle": "Save title",
"editTitle": "Edit pull request title"
},
"prSidebarCreateEmptyState": {
"branchRequired": "Check out a branch before creating a pull request.",
"createFailed": "Failed to create pull request.",
"title": "Pull request",
"refreshPullRequest": "Refresh pull request",
"createPullRequest": "Create pull request",
"createPr": "Create PR",
"linkedPrUnavailable": "Linked PR #{{number}} unavailable",
"noOpenPullRequest": "No open pull request",
"refreshOrCreate": "Refresh to check again, or create a new PR for this branch.",
"branchNotLinked": "{{branch}} is not linked to an open PR.",
"currentBranchNotLinked": "The current branch is not linked to an open PR.",
"linkExistingPullRequest": "Link an existing pull request",
"linkExistingPr": "Link an existing PR"
},
"reviewerPickerDrawer": {
"title": "Reviewers",
"searchPeople": "Search people",
"loadFailed": "Failed to load people",
"noMatches": "No matching people",
"removeReviewer": "Remove {{login}}",
"requestReviewer": "Request {{login}}"
},
"markdownBlocks": {
"details": "Details"
},
"prActionsState": {
"mergedLabel": "Merged",
"mergedHint": "This pull request is already merged",
"closedLabel": "Closed",
"closedHint": "This pull request is closed",
"draftLabel": "Draft",
"draftHint": "This pull request is still a draft",
"reviewRequiredLabel": "Approval required",
"reviewRequiredHint": "GitHub requires review approval before this pull request can merge",
"changesRequestedLabel": "Changes requested",
"changesRequestedHint": "GitHub reports requested changes on this pull request",
"conflictsLabel": "Conflicts",
"conflictsHint": "GitHub reports merge conflicts",
"mergeQueueLabel": "Merge when ready",
"mergeQueueHint": "This base branch uses GitHub merge queue",
"mergeQueueAutoMergeOnLabel": "Auto-merge on",
"mergeQueueAutoMergeOnHint": "This base branch uses GitHub merge queue",
"mergeStatusUnavailableLabel": "Merge",
"mergeStatusUnavailableHint": "Merge status is unavailable for this PR",
"behindLabel": "Behind",
"behindHint": "Update the branch before merging",
"blockedLabel": "Blocked",
"blockedHint": "GitHub reports this pull request is blocked",
"mergeableChecksFailedLabel": "Checks failed",
"mergeableChecksFailedHint": "GitHub says this PR can merge, but some checks failed",
"mergeableChecksPendingLabel": "Checks pending",
"mergeableChecksPendingHint": "GitHub says this PR can merge, but checks are still running",
"mergeableChecksPassedLabel": "Able to merge",
"mergeableChecksPassedHint": "GitHub says this PR can merge and checks passed",
"mergeableLabel": "Able to merge",
"mergeableHint": "GitHub says this PR can merge",
"checksPassedLabel": "Checks passed",
"checksPassedHint": "Checks passed. Merge eligibility will be checked again before merging.",
"checkingLabel": "Checking",
"checkingHint": "GitHub is still computing this pull request merge status",
"stackDraft": "#{{number}} is still a draft.",
"stackClosed": "#{{number}} is closed.",
"stackConflicts": "#{{number}} has merge conflicts.",
"stackChangesRequested": "#{{number}} has requested changes.",
"stackReviewRequired": "#{{number}} still needs review approval.",
"stackBehind": "#{{number}} must be updated.",
"stackBlocked": "#{{number}} is blocked.",
"methodMerge": "Create merge commit",
"methodSquash": "Squash and merge",
"methodRebase": "Rebase and merge",
"mergeConfirmMessage": "This will merge #{{number}} into its base branch.",
"queueThrough_one": "Queue through #{{number}} · {{count}} PR",
"queueThrough_other": "Queue through #{{number}} · {{count}} PRs",
"mergeThrough_one": "Merge through #{{number}} · {{count}} PR",
"mergeThrough_other": "Merge through #{{number}} · {{count}} PRs",
"queueStackMessage": "GitHub will add this pull request and every pull request below it to the merge queue.",
"mergeStackMessage": "GitHub will merge this pull request and every pull request below it in the stack."
},
"prCheckDetailContent": {
"annotation": "Annotation",
"unknown": "unknown",
"failedJobs": "Failed jobs",
"jobs": "Jobs"
},
"prChecksPresentation": {
"noChecks": "No checks",
"failing_one": "{{count}} failing",
"failing_other": "{{count}} failing",
"pending_one": "{{count}} pending",
"pending_other": "{{count}} pending",
"passed_one": "{{count}} passed",
"passed_other": "{{count}} passed",
"neutral_one": "{{count}} neutral",
"neutral_other": "{{count}} neutral",
"unavailable": "Checks unavailable",
"stateOpen": "Open",
"stateMerged": "Merged",
"stateDraft": "Draft",
"stateClosed": "Closed"
},
"prCommentAudienceLabels": {
"all": "All",
"humans": "Humans",
"bots": "Bots",
"noBotComments": "No bot comments.",
"noHumanComments": "No human comments.",
"noComments": "No comments yet."
}
}
}
+755
View File
@@ -146,9 +146,14 @@
"common": {
"back": "Atrás",
"cancel": "Cancelar",
"close": "Cerrar",
"confirm": "Confirmar",
"continue": "Continuar",
"copied": "Copiado",
"copy": "Copiar",
"delete": "Eliminar",
"done": "Listo",
"edit": "Editar",
"ok": "Aceptar",
"remove": "Eliminar",
"retry": "Reintentar",
@@ -156,6 +161,477 @@
"settings": "Ajustes",
"unknown": "Desconocido"
},
"components": {
"accountsSnapshot": {
"invalidSnapshot": "Instantánea de cuentas del host no válida"
},
"agentLaunchNotice": {
"copyFailed": "No se pudo copiar",
"copyPrompt": "Copiar prompt",
"statusUnreadable": "No se pudo leer el estado de este host. Reintentando…",
"updateRequired": "Actualiza Orca en tu equipo para iniciar un agente desde tu teléfono."
},
"agentSpinner": {
"monitoring": "Supervisando tareas en segundo plano",
"unconfirmed": "No se pudo confirmar"
},
"agentStateDot": {
"monitoring": "Supervisando tareas en segundo plano",
"unverifiable": "Sin actualizaciones recientes"
},
"authFailedBanner": {
"message": "Falló la autenticación: primero intenta volver a conectarte; si sigue fallando, vuelve a emparejar desde el escritorio."
},
"authFailedBannerActions": {
"repair": "Volver a emparejar",
"webNote": "Vuelve a conectarte o elimina este host desde la app de Orca."
},
"codexResetCredit": {
"alreadyAppliedTitle": "Restablecimiento ya aplicado",
"available_many": "{{count}} restablecimientos disponibles",
"available_one": "{{count}} restablecimiento disponible",
"available_other": "{{count}} restablecimientos disponibles",
"expiryDaysHoursMultiple": "El siguiente vence en {{days}} d {{hours}} h",
"expiryDaysHoursSingle": "Vence en {{days}} d {{hours}} h",
"expiryDaysMultiple": "El siguiente vence en {{days}} d",
"expiryDaysSingle": "Vence en {{days}} d",
"expiryHoursMinutesMultiple": "El siguiente vence en {{hours}} h {{minutes}} min",
"expiryHoursMinutesSingle": "Vence en {{hours}} h {{minutes}} min",
"expiryHoursMultiple": "El siguiente vence en {{hours}} h",
"expiryHoursSingle": "Vence en {{hours}} h",
"expiryMinutesMultiple": "El siguiente vence en {{minutes}} min",
"expiryMinutesSingle": "Vence en {{minutes}} min",
"expiryNowMultiple": "El siguiente vence ahora",
"expiryNowSingle": "Vence ahora",
"invalidResponse": "Respuesta de restablecimiento no válida del host",
"noResetMessage": "Esta cuenta no tiene créditos ganados para restablecer los límites.",
"noResetTitle": "No hay restablecimientos disponibles",
"nothingToResetMessage": "No se ha agotado el límite de ningún período de uso de Codex que se pueda restablecer.",
"nothingToResetTitle": "No hay nada que restablecer",
"refresh": "Se ha actualizado el uso de Codex.",
"resetTitle": "Límites de uso restablecidos"
},
"codexResetCreditAction": {
"activeAccountHint": "Usa un restablecimiento obtenido para la cuenta activa de Codex",
"earnedReset": "Restablecimiento de los límites de uso de Codex obtenido",
"resetting": "Restableciendo…",
"resettingLabel": "Restableciendo los límites de uso de Codex",
"scopedHint": "Usa un restablecimiento obtenido para {{scopeLabel}}",
"useReset": "Usar restablecimiento",
"useResetLabel": "Usar un restablecimiento de los límites de uso de Codex"
},
"customKeyModal": {
"add": "Añadir",
"addShortcut": "Añadir atajo",
"command": "Comando",
"commandPlaceholder": "p. ej., pnpm build",
"editing": "Edición",
"function": "Función",
"key": "Tecla",
"label": "Etiqueta",
"labelPlaceholder": "p. ej., Compilar",
"manageShortcuts": "Gestionar atajos",
"manageShortcutsHint": "Muestra, oculta o reordena los atajos de teclado",
"modifiers": "Modificadores",
"moreKeys": "Más teclas: Tab, flechas, F1–F12…",
"navigation": "Navegación",
"pickKey": "Elige una tecla",
"pressEnter": "Pulsar Enter",
"shortcutCombo": "Combinación de teclas",
"shortcutComboHint": "Crea combinaciones de teclas con Ctrl, Alt y Shift",
"textMacro": "Macro de texto",
"textMacroHint": "Envía un comando de texto personalizado"
},
"dictationSetupSheet": {
"description": "Descarga o elige un modelo y activa el dictado en el equipo emparejado. Todo desde aquí.",
"download": "Descargar",
"downloadFailed": "Falló la descarga",
"downloadModel": "Descargar {{model}}",
"enabled": "Dictado activado",
"inUse": "En uso",
"loadFailed": "No se pudo cargar",
"modelExtracting": "{{details}} · extrayendo…",
"modelProgress": "{{details}} · {{progress}} %",
"modelSize": "{{size}} MB",
"recommended": "Recomendado",
"selectFailed": "No se pudo seleccionar el modelo",
"title": "Configurar el dictado por voz",
"updateFailed": "No se pudo actualizar",
"use": "Usar",
"useModel": "Usar {{model}}"
},
"diffReviewBody": {
"binaryDiff": "Diff binario",
"binaryMessage": "Este archivo no se puede mostrar como texto en el móvil.",
"deletedFile": "Archivo eliminado",
"deletedMessage": "Este archivo se eliminó. Añade una nota al archivo o márcalo como revisado.",
"diffTooLarge": "Diff demasiado grande",
"diffTruncated": "Diff truncado para la vista previa móvil.",
"diffUnavailable": "Diff no disponible",
"loadFailed": "No se pudo cargar la revisión",
"loadingDiff": "Cargando diff…",
"loadingReview": "Cargando revisión…",
"noReviewableChanges": "No hay cambios para revisar",
"retryLabel": "Reintentar cargar la revisión",
"reviewUnavailable": "Revisión no disponible",
"selectFile": "Selecciona un archivo para revisar.",
"tooLargeMessage": "Este diff es demasiado grande para la vista previa del móvil.",
"tryDifferentFilter": "Prueba otro filtro de revisión."
},
"diffReviewDrawers": {
"actionsTitle": "Acciones de revisión",
"addNote": "Añadir nota",
"cancelNote": "Cancelar nota",
"clearSentNotes": "Eliminar notas enviadas",
"completeTitle": "Revisión completada",
"copyNotes": "Copiar notas",
"deleteNote": "Eliminar nota",
"discard": "Descartar",
"discardMessage": "¿Descartar los cambios de \"{{filePath}}\"? Esta acción no se puede deshacer.",
"discardTitle": "Descartar archivo",
"editNote": "Editar nota",
"fileNote": "Nota del archivo",
"fileSummary_many": "{{fileCount}} archivo revisado, {{count}} notas",
"fileSummary_one": "{{fileCount}} archivo revisado, {{count}} nota",
"fileSummary_other": "{{fileCount}} archivo revisado, {{count}} notas",
"filesSummary_many": "{{fileCount}} archivos revisados, {{count}} notas",
"filesSummary_one": "{{fileCount}} archivos revisados, {{count}} nota",
"filesSummary_other": "{{fileCount}} archivos revisados, {{count}} notas",
"lineNumber": "Línea {{lineNumber}}",
"loadingSessions": "Cargando sesiones de agentes…",
"markUnreviewed": "Marcar como no revisado",
"newAgentSession": "Nueva sesión de agente",
"openInSession": "Abrir en sesión",
"reviewNote": "Nota de revisión",
"saveLineNote": "Guardar nota en la línea {{lineNumber}}",
"sendNotes": "Enviar notas",
"sendNotesLabel": "Enviar notas al agente",
"sendUnsentNotes": "Enviar notas pendientes",
"stageReviewed": "Agregar revisados al stage",
"stageReviewedFiles": "Agregar archivos revisados al stage",
"stageReviewedLabel": "Agregar archivos revisados al stage",
"stageableFiles_many": "Se pueden agregar al stage {{count}} archivos revisados no preparados",
"stageableFiles_one": "Se puede agregar al stage {{count}} archivo revisado no preparado",
"stageableFiles_other": "Se pueden agregar al stage {{count}} archivos revisados no preparados",
"statusUnreadable": "No se pudo leer el estado de este host. Reintentando…",
"terminal": "Terminal",
"terminalLabel": "{{title}} ({{id}})",
"unsentNotes_many": "{{count}} notas sin enviar",
"unsentNotes_one": "{{count}} nota sin enviar",
"unsentNotes_other": "{{count}} notas sin enviar",
"updateRequired": "Actualiza Orca en tu equipo"
},
"diffReviewFileSummary": {
"branch": "Rama",
"branchFrom": "Rama, origen: {{oldPath}}",
"changed": "Modificado",
"editFileNote": "Editar nota del archivo",
"hunk": "Bloque",
"nextHunk": "Bloque siguiente",
"notes_many": "{{count}} notas",
"notes_one": "{{count}} nota",
"notes_other": "{{count}} notas",
"previousHunk": "Bloque anterior",
"reviewed": "Revisado",
"staged": "En el área de preparación",
"stagedFrom": "En el área de preparación, origen: {{oldPath}}",
"stale": "Desactualizada",
"staleCount": "Desactualizadas: {{count}}",
"unstaged": "Fuera del área de preparación",
"unstagedFrom": "Fuera del área de preparación, origen: {{oldPath}}"
},
"diffReviewFooter": {
"addFileNote": "Añadir nota al archivo",
"discard": "Descartar",
"discardFile": "Descartar archivo",
"markFileReviewed": "Marcar archivo como revisado",
"markReviewed": "Marcar como revisado",
"nextFile": "Archivo siguiente",
"note": "Nota",
"previousFile": "Archivo anterior",
"reviewed": "Revisado",
"stage": "Agregar al stage",
"stageFile": "Agregar archivo al stage",
"unstage": "Quitar del stage",
"unstageFile": "Quitar archivo del stage"
},
"diffReviewHeader": {
"filterAll": "Todos",
"filterBranch": "Rama",
"filterNotes": "Notas",
"filterStaged": "En el área de preparación",
"filterUnreviewed": "Sin revisar",
"filterUnstaged": "Fuera del área de preparación",
"openPullRequestSidebar": "Abrir la barra lateral de la pull request",
"openReviewActions": "Abrir las acciones de revisión",
"reviewedProgress": "Revisados: {{reviewedCount}}/{{queueLength}}",
"showAllFiles": "Mostrar todos los archivos de la revisión",
"showBranchFiles": "Mostrar archivos de la rama en la revisión",
"showNotesFiles": "Mostrar archivos de la revisión con notas",
"showStagedFiles": "Mostrar archivos preparados de la revisión",
"showUnreviewedFiles": "Mostrar archivos sin revisar de esta revisión",
"showUnstagedFiles": "Mostrar archivos no preparados de la revisión",
"title": "Cambios",
"unsentNotes_many": "{{count}} notas sin enviar",
"unsentNotes_one": "{{count}} nota sin enviar",
"unsentNotes_other": "{{count}} notas sin enviar"
},
"diffReviewLine": {
"addNoteOnLine": "Añadir una nota en la línea {{number}}",
"addedLine": "Línea añadida",
"addedLineNumber": "Línea {{number}} añadida",
"contextLine": "Línea de contexto",
"contextLineNumber": "Línea {{number}} de contexto",
"deletedLine": "Línea eliminada",
"deletedLineNumber": "Línea {{number}} eliminada",
"editNoteOnLine": "Editar la nota en la línea {{number}}"
},
"dragReorderRow": {
"dragToReorder": "Arrastrar para reordenar",
"moveDown": "Bajar",
"moveUp": "Subir",
"reorderHint": "Usa las acciones \"Subir\" y \"Bajar\" para reordenar sin arrastrar."
},
"homeQuickActions": {
"newWorkspace": "Nuevo espacio de trabajo",
"pairDesktop": "Emparejar escritorio",
"title": "Acciones rápidas"
},
"hostCard": {
"hostActions": "Acciones para {{title}}",
"openHost": "Abrir {{title}}",
"pairingInvalid": "Emparejamiento no válido",
"pairingTemporarilyUnavailable": "Emparejamiento no disponible temporalmente",
"repairHint": "Toca para volver a emparejar tu escritorio.",
"unlockHint": "Desbloquea tu teléfono y toca para reintentar.",
"updateDesktopHint": "Actualiza Orca en el escritorio e inicia sesión para conectarte desde cualquier lugar.",
"worktreeAccessibilitySummary": "{{summary}}, activos: {{activeCount}}"
},
"hostDiagnosticsLink": {
"viewDiagnostics": "Ver diagnósticos de red"
},
"hostProtocolGate": {
"checkingCompatibility": "Comprobando la compatibilidad del host"
},
"hostRouteNoticeBanner": {
"dismissNotice": "Descartar aviso"
},
"htmlPreview": {
"preview": "Vista previa",
"previewRenderedHtml": "Previsualizar HTML renderizado",
"source": "Código fuente",
"title": "Vista previa de HTML",
"viewHtmlSource": "Ver el código fuente HTML"
},
"jiraConnectPrompt": {
"checkAgain": "Verificar de nuevo",
"connectHint": "Añade tu sitio de Jira en Ajustes → Integraciones de Orca de escritorio y verifica de nuevo.",
"connectOnDesktop": "Conecta Jira en tu escritorio",
"credentialUnreadable": "La conexión de Jira está guardada, pero no se puede leer: {{credentialError}}",
"updateDesktop": "Actualiza Orca de escritorio"
},
"markdownBlocks": {
"copyCode": "Copiar código",
"openImage": "Abrir imagen"
},
"mountedBottomDrawer": {
"dismissDrawer": "Cerrar panel"
},
"newWorkspaceFab": {
"newWorkspace": "Nuevo espacio de trabajo"
},
"newWorkspaceSetupScriptField": {
"hooks": "HOOKS",
"label": "Script de configuración",
"run": "Ejecutar",
"runSetupCommand": "Ejecutar comando de configuración",
"skip": "Omitir"
},
"newWorkspaceSshConnectionField": {
"connect": "Conectar",
"connecting": "Conectando…",
"label": "Conexión SSH",
"serverRecovery": "Su servidor debe recuperarse desde el escritorio: Ajustes → Hosts SSH → Recuperar servidor."
},
"newWorktreeAgentSelection": {
"blankTerminal": "Terminal en blanco"
},
"newWorktreeFormSheet": {
"advanced": "Avanzado",
"agent": "Agente",
"connectTarget": "Conectar destino",
"connectTargetFirst": "Conecta primero el destino",
"createWorktree": "Crear worktree",
"nameOrCreateFrom": "Nombre o 'Crear desde'",
"noProjectsFound": "No se encontraron proyectos",
"note": "Nota",
"notePlaceholder": "Escribe una nota",
"workspaceName": "Nombre del espacio de trabajo"
},
"newWorktreeModal": {
"remoteRepository": "Repositorio remoto"
},
"newWorktreeModalDrawers": {
"agent": "Agente",
"project": "Proyecto",
"runOn": "Ejecutar en"
},
"newWorktreeProjectTargetFields": {
"project": "Proyecto",
"runOn": "Ejecutar en",
"selectProject": "Selecciona un proyecto",
"selectRunTarget": "Elige dónde ejecutar"
},
"prSidebar": {
"loadFailed": "Algo salió mal.",
"loading": "Cargando pull request…",
"notPermitted": "Acceso no permitido: tu cuenta de GitHub no está conectada.",
"retryLoading": "Reintentar cargar la pull request"
},
"protocolBlockScreen": {
"backToHosts": "Volver a hosts",
"desktopTooOldBody": "La aplicación del escritorio emparejado es demasiado antigua para tu versión actual de Orca Mobile. Actualiza Orca en tu equipo y vuelve a intentarlo con este host.",
"getVersion": "Obtener Orca {{version}}",
"mobileTooOldAppStore": "Este escritorio necesita una versión más reciente de Orca Mobile. Actualiza Orca Mobile desde la App Store y vuelve a intentarlo con este host.",
"mobileTooOldGitHubReleases": "Este escritorio necesita una versión más reciente de Orca Mobile. Actualiza Orca Mobile desde GitHub Releases y vuelve a intentarlo con este host.",
"openAppStore": "Abrir App Store",
"openGitHubReleases": "Abrir GitHub Releases",
"refreshRecoveryNote": "Si este mensaje persiste, elimina este host y vuelve a emparejarlo.",
"refreshWorkspaceBody": "La versión en caché del espacio de trabajo de este host es anterior a la que espera el escritorio. Vuelve a conectarte a este host para descargar la versión actual.",
"refreshWorkspaceTitle": "Actualiza el espacio de trabajo móvil",
"updateDesktopTitle": "Actualiza Orca en tu equipo",
"updateMobileTitle": "Actualiza Orca Mobile",
"updateRecoveryNote": "¿Ya has actualizado? Vuelve a Hosts y actualiza la conexión. Si este mensaje persiste, elimina este host y vuelve a emparejarlo.",
"workspaceNeedsUpdateAppStore": "El espacio de trabajo móvil de este escritorio necesita una versión más reciente de Orca Mobile. Actualiza Orca Mobile desde la App Store y vuelve a intentarlo con este host.",
"workspaceNeedsUpdateGitHubReleases": "El espacio de trabajo móvil de este escritorio necesita una versión más reciente de Orca Mobile. Actualiza Orca Mobile desde GitHub Releases y vuelve a intentarlo con este host."
},
"richMarkdownEditor": {
"imageUrl": "URL de la imagen",
"insert": "Insertar",
"linkUrl": "URL del enlace"
},
"richMarkdownToolbar": {
"body": "Cuerpo",
"bold": "Negrita",
"bulletList": "Lista con viñetas",
"checklist": "Lista de verificación",
"codeBlock": "Bloque de código",
"heading1": "H1",
"heading2": "H2",
"heading3": "H3",
"image": "Imagen",
"inlineCode": "Código inline",
"italic": "Cursiva",
"link": "Enlace",
"numberedList": "Lista numerada",
"quote": "Cita",
"strike": "Tachado"
},
"searchField": {
"clearSearch": "Borrar búsqueda"
},
"setupHookTrustDrawer": {
"alwaysTrustAndRun": "Confiar siempre y ejecutar",
"description": "El archivo orca.yaml de este repositorio se ejecuta antes de iniciar el espacio de trabajo. Ejecútalo solo si confías en este repositorio.",
"dontRun": "No ejecutar",
"newScriptLabel": "Nuevo script de configuración",
"runHooks": "Ejecutar hooks",
"runSetupTitle": "¿Ejecutar la configuración de {{repoName}}?",
"scriptChangedTitle": "El script de configuración de {{repoName}} ha cambiado",
"scriptLabel": "Script de configuración"
},
"smartWorkspaceAdvancedFields": {
"branchName": "Nombre de rama",
"derivedFromName": "Derivado del nombre",
"name": "Nombre",
"reuseBranch": "Reutilizar la rama “{{branch}}”",
"workspaceName": "Nombre del espacio de trabajo"
},
"smartWorkspaceSourceDrawer": {
"connectRepository": "Conecta el repositorio para buscar fuentes.",
"crossRepoMessage": "Este elemento está en {{owner}}/{{repo}}.",
"gitHubRemoteRequired": "Este repositorio SSH necesita un remoto de GitHub para mostrar issues y PR.",
"noResults": "No se han encontrado resultados.",
"searchPlaceholder": "Escribe un nombre o busca una fuente",
"switchRepo": "Cambiar a {{repoName}}",
"title": "Nombre o 'Crear desde'",
"typeWorkspaceName": "Escribe un nombre para el espacio de trabajo en el campo de abajo."
},
"smartWorkspaceSourceField": {
"openSelectedSource": "Abrir la fuente seleccionada",
"optional": "[Opcional]",
"searchPlaceholder": "Escribe un nombre o busca una fuente"
},
"smartWorkspaceSourceRow": {
"createBranch": "Crear la rama \"{{name}}\"",
"issue": "Issue #{{number}}",
"mergeRequest": "MR !{{number}}",
"nameWorkspace": "Nombrar este espacio de trabajo",
"newBranch": "Nueva rama",
"pullRequest": "PR #{{number}}",
"useName": "Usar \"{{name}}\""
},
"sourceCopyButton": {
"clipboardRejected": "El portapapeles rechazó el texto.",
"copiedLoaded": "Copiado lo cargado",
"copyFailed": "Error al copiar",
"copyLoaded": "Copiar lo cargado",
"couldNotCopy": "No se pudo copiar",
"failed": "Falló"
},
"terminalShortcutSettings": {
"addDescription": "Crear una combinación de teclas o una macro de texto",
"addShortcut": "Añadir atajo personalizado…",
"customShortcuts": "ATAJOS PERSONALIZADOS",
"empty": "Aún no hay atajos personalizados definidos.",
"removeShortcut": "Eliminar {{label}}",
"resetDefaults": "Restablecer valores predeterminados",
"resetDescription": "Mostrar todas las teclas de atajo integradas en su orden original",
"shortcutBar": "BARRA DE ATAJOS",
"shortcutBarDescription": "Activa o desactiva las teclas para mostrarlas u ocultarlas y mantén pulsado el control de arrastre para colocarlas en el orden que quieras en la barra de atajos de la terminal."
},
"useCodexResetCreditAction": {
"confirmTitle": "¿Usar un restablecimiento de límites de uso?",
"detailsChanged": "Detalles del restablecimiento modificados",
"detailsChangedMessage": "La cuenta o la oferta de restablecimiento cambió antes de que el host contactara con Codex. Revisa los detalles actualizados y vuelve a confirmar.",
"hostAccount": "{{identity}} en el host",
"hostAccountConfirmMessage": "Esto consume un restablecimiento ganado para {{identity}} en el host y restablece de inmediato los límites de los períodos de uso que cumplan los requisitos.",
"hostSelectedAccount": "la cuenta administrada seleccionada en el host",
"hostSelectedAccountConfirmMessage": "Esto consume un restablecimiento ganado para la cuenta administrada seleccionada en el host y restablece de inmediato los límites de los períodos de uso que cumplan los requisitos.",
"resetFailed": "No se pudieron restablecer los límites de uso",
"wslAccount": "{{identity}} en WSL {{distro}}",
"wslAccountConfirmMessage": "Esto consume un restablecimiento ganado para {{identity}} en WSL {{distro}} y restablece de inmediato los límites de los períodos de uso que cumplan los requisitos.",
"wslSelectedAccount": "la cuenta administrada seleccionada en WSL {{distro}}",
"wslSelectedAccountConfirmMessage": "Esto consume un restablecimiento ganado para la cuenta administrada seleccionada en WSL {{distro}} y restablece de inmediato los límites de los períodos de uso que cumplan los requisitos."
},
"voiceModelList": {
"deleteModel": "Eliminar {{label}}",
"downloadModel": "Descargar {{label}}",
"inUse": "En uso",
"modelExtracting": "{{size}} · extrayendo…",
"modelProgress": "{{size}} · {{progress}} %",
"modelSize": "{{size}} MB",
"recommended": "Recomendado",
"use": "Usar",
"useModel": "Usar {{label}}"
},
"workspaceDetailPlaceholder": {
"description": "Elige un espacio de trabajo de la barra lateral para abrir su terminal aquí.",
"title": "No hay ningún espacio de trabajo abierto"
},
"workspaceNameInput": {
"placeholder": "Nombre del espacio de trabajo"
},
"worktreeListRow": {
"child": "Secundario",
"children_many": "{{count}} secundarios",
"children_one": "{{count}} secundario",
"children_other": "{{count}} secundarios",
"folder": "Carpeta",
"project": "Proyecto {{repo}}",
"remoteHost": "Host remoto"
}
},
"connection": {
"connectionHealth": {
"checkConnection": "Comprueba tu conexión.",
@@ -572,6 +1048,285 @@
"useChatUi": "Usar interfaz de chat"
}
},
"pullRequest": {
"checksSection": {
"actionRequired": "Acción requerida",
"cancelled": "Cancelado",
"checkDetails": "Detalles del check {{name}}",
"failed": "Falló",
"failing": "{{count}} con errores",
"failingChecks_many": "{{count}} comprobaciones fallidas",
"failingChecks_one": "{{count}} comprobación fallida",
"failingChecks_other": "{{count}} comprobaciones fallidas",
"fix": "Arreglar",
"fixFailing": "Arreglar los checks fallidos con IA",
"inProgress": "En curso",
"inspectOrFix": "Inspecciona los detalles o inicia una pasada de corrección con IA.",
"loadDetailsFailed": "No se pudieron cargar los detalles del check",
"neutral": "Neutral",
"neutralCount": "{{count}} con resultado neutral",
"noChecks": "Sin checks",
"openOnWeb": "Abrir {{name}} en la web",
"passed": "{{count}} con éxito",
"pending": "Pendiente",
"pendingCount": "{{count}} por completar",
"queued": "En cola",
"rerunFailing": "Volver a ejecutar los checks fallidos",
"skipped": "Omitido",
"successful": "Correcto",
"timedOut": "Tiempo agotado",
"title": "Checks",
"unavailable": "Checks no disponibles"
},
"commentCard": {
"daysAgo_many": "hace {{count}} días",
"daysAgo_one": "hace {{count}} día",
"daysAgo_other": "hace {{count}} días",
"deleteComment": "Eliminar comentario",
"deleteConfirmMessage": "Esto elimina permanentemente el comentario en GitHub.",
"deleteConfirmTitle": "¿Eliminar comentario?",
"editComment": "Editar comentario",
"editPlaceholder": "Edita el comentario…",
"hoursAgo_many": "hace {{count}} h",
"hoursAgo_one": "hace {{count}} h",
"hoursAgo_other": "hace {{count}} h",
"justNow": "ahora mismo",
"minutesAgo_many": "hace {{count}} min",
"minutesAgo_one": "hace {{count}} min",
"minutesAgo_other": "hace {{count}} min",
"monthsAgo_many": "hace {{count}} meses",
"monthsAgo_one": "hace {{count}} mes",
"monthsAgo_other": "hace {{count}} meses",
"openOnGitHub": "Abrir comentario en GitHub",
"reply": "Responder",
"replyPlaceholder": "Escribe una respuesta…",
"replyToComment": "Responder al comentario",
"resolve": "Resolver",
"resolveThread": "Resolver hilo",
"resolved": "resuelto",
"unresolve": "Marcar como no resuelto",
"unresolveThread": "Marcar el hilo como no resuelto",
"yearsAgo_many": "hace {{count}} años",
"yearsAgo_one": "hace {{count}} año",
"yearsAgo_other": "hace {{count}} años"
},
"commentsSection": {
"addCommentPlaceholder": "Añade un comentario…",
"all": "Todos",
"bots": "Bots",
"comment": "Comentar",
"commentsLoadFailed": "No se pudieron cargar los comentarios. Toca Actualizar para reintentarlo.",
"description": "Descripción",
"descriptionLoadFailed": "No se pudo cargar la descripción. Toca Actualizar para reintentarlo.",
"humans": "Humanos",
"noBotComments": "Sin comentarios de robots.",
"noComments": "Aún no hay comentarios.",
"noDescription": "No se proporcionó descripción.",
"noHumanComments": "Sin comentarios humanos.",
"resolvedComment_many": "Comentario resuelto de {{author}} ({{count}})",
"resolvedComment_one": "Comentario resuelto de {{author}}",
"resolvedComment_other": "Comentario resuelto de {{author}} ({{count}})",
"resolvedThread_many": "Hilo resuelto de {{author}} ({{count}})",
"resolvedThread_one": "Hilo resuelto de {{author}}",
"resolvedThread_other": "Hilo resuelto de {{author}} ({{count}})",
"showMore": "Mostrar {{count}} más",
"showMoreOf": "Mostrar {{count}} más ({{remaining}} restantes)",
"title": "Comentarios"
},
"conflictingFilesSection": {
"commitsBehind_many": "{{count}} commits por detrás (commit base: {{baseCommit}})",
"commitsBehind_one": "{{count}} commit por detrás (commit base: {{baseCommit}})",
"commitsBehind_other": "{{count}} commits por detrás (commit base: {{baseCommit}})",
"copyCommands": "Copiar comandos",
"copyFailed": "No se pudo copiar el texto",
"copyRefreshCommands": "Copiar comandos para actualizar la capacidad de merge",
"detailsUnavailable": "Los detalles del archivo en conflicto no están disponibles",
"filesHeading": "Archivos con conflictos",
"localMergeClean": "GitHub indica que hay conflictos, pero Git no los ha reproducido en local. Actualiza la PR o haz push de la rama para recalcular la capacidad de merge.",
"mustResolve": "Esta rama tiene conflictos que deben resolverse",
"refreshing": "Actualizando detalles del conflicto…",
"resolveWithAi": "Resolver conflictos con IA",
"runFromWorktree": "Ejecutar desde este worktree",
"title": "Conflictos"
},
"linkPrForm": {
"linkButton": "Vincular pull request",
"linkNumber": "Vincular #{{number}}",
"referenceLabel": "Número de PR o URL de GitHub",
"referencePlaceholder": "#123 o https://github.com/owner/repo/pull/123",
"title": "Vincular pull request existente"
},
"markdownBlocks": {
"details": "Detalles"
},
"prActionsSection": {
"autoMergeAccessibilityLabel": "Activar o desactivar auto-merge",
"autoMergeLabel": "Auto-merge cuando esté listo",
"autoMergeOff": "Desactivado",
"autoMergeOn": "Activado",
"closeAccessibilityLabel": "Cerrar pull request",
"closeConfirmMessage": "Se cerrará #{{number}} sin hacer merge.",
"closeConfirmTitle": "¿Cerrar el pull request?",
"markReadyAccessibilityLabel": "Marcar pull request como listo para revisión",
"markReadyButton": "Listo para revisión",
"mergeAccessibilityLabel": "Hacer merge del pull request",
"mergeButton": "Merge",
"mergeConfirmTitle": "¿Hacer merge del pull request?",
"recheckAccessibilityLabel": "Volver a comprobar el estado del merge",
"recheckButton": "Comprobar de nuevo",
"reopenAccessibilityLabel": "Reabrir pull request",
"reopenButton": "Reabrir",
"reopenConfirmMessage": "Se reabrirá #{{number}}.",
"reopenConfirmTitle": "¿Reabrir el pull request?",
"unlinkAccessibilityLabel": "Desvincular pull request",
"unlinkButton": "Desvincular",
"unlinkFailed": "No se pudo desvincular el pull request."
},
"prActionsState": {
"behindHint": "Actualiza la rama antes de hacer merge",
"behindLabel": "Por detrás",
"blockedHint": "GitHub informa que este pull request está bloqueado",
"blockedLabel": "Bloqueado",
"changesRequestedHint": "GitHub informa cambios solicitados en este pull request",
"changesRequestedLabel": "Cambios solicitados",
"checkingHint": "GitHub todavía está calculando el estado de merge de este pull request",
"checkingLabel": "Verificando",
"checksPassedHint": "Checks aprobados. La elegibilidad de merge se volverá a verificar antes de hacer merge.",
"checksPassedLabel": "Checks aprobados",
"closedHint": "Este pull request está cerrado",
"closedLabel": "Cerrado",
"conflictsHint": "GitHub informa conflictos de merge",
"conflictsLabel": "Conflictos",
"draftHint": "Este pull request sigue en borrador",
"draftLabel": "Borrador",
"mergeConfirmMessage": "Se hará merge de #{{number}} en su rama base.",
"mergeQueueAutoMergeOnHint": "Esta rama base usa la merge queue de GitHub",
"mergeQueueAutoMergeOnLabel": "Merge automático activado",
"mergeQueueHint": "Esta rama base usa la merge queue de GitHub",
"mergeQueueLabel": "Mergear cuando esté listo",
"mergeStackMessage": "GitHub fusionará esta solicitud de extracción y todas las solicitudes de extracción que se encuentren debajo de ella en la pila.",
"mergeStatusUnavailableHint": "El estado de merge no está disponible para este PR",
"mergeStatusUnavailableLabel": "Merge",
"mergeThrough_many": "Hacer merge hasta #{{number}} · {{count}} PR",
"mergeThrough_one": "Hacer merge hasta #{{number}} · {{count}} PR",
"mergeThrough_other": "Hacer merge hasta #{{number}} · {{count}} PR",
"mergeableChecksFailedHint": "GitHub dice que este PR se puede mergear, pero algunos checks fallaron",
"mergeableChecksFailedLabel": "Checks fallidos",
"mergeableChecksPassedHint": "GitHub dice que este PR se puede mergear y los checks pasaron",
"mergeableChecksPassedLabel": "Se puede mergear",
"mergeableChecksPendingHint": "GitHub dice que este PR se puede mergear, pero los checks siguen ejecutándose",
"mergeableChecksPendingLabel": "Checks pendientes",
"mergeableHint": "GitHub dice que este PR se puede mergear",
"mergeableLabel": "Se puede mergear",
"mergedHint": "Este pull request ya está mergeado",
"mergedLabel": "Mergeado",
"methodMerge": "Crear commit de merge",
"methodRebase": "Rebase y merge",
"methodSquash": "Squash y merge",
"queueStackMessage": "GitHub agregará esta solicitud de extracción y todas las solicitudes de extracción debajo de ella a la cola de combinación.",
"queueThrough_many": "Encolar hasta #{{number}} · {{count}} PR",
"queueThrough_one": "Encolar hasta #{{number}} · {{count}} PR",
"queueThrough_other": "Encolar hasta #{{number}} · {{count}} PR",
"reviewRequiredHint": "GitHub requiere aprobación de revisión antes de que este pull request pueda mergearse",
"reviewRequiredLabel": "Se requiere aprobación",
"stackBehind": "#{{number}} debe actualizarse.",
"stackBlocked": "#{{number}} está bloqueado.",
"stackChangesRequested": "Se han solicitado cambios en #{{number}}.",
"stackClosed": "#{{number}} está cerrado.",
"stackConflicts": "#{{number}} tiene conflictos de merge.",
"stackDraft": "#{{number}} sigue en borrador.",
"stackReviewRequired": "#{{number}} aún necesita la aprobación de un revisor."
},
"prCheckDetail": {
"annotations": "Anotaciones",
"annotationsTruncated": "Mostrando las primeras 20 anotaciones",
"jobsTruncated": "Mostrando los primeros 100 jobs",
"noDetails": "No hay detalles disponibles."
},
"prCheckDetailContent": {
"annotation": "Anotación",
"failedJobs": "Tareas fallidas",
"jobs": "Tareas",
"unknown": "desconocido"
},
"prChecksPresentation": {
"failing_many": "{{count}} fallidos",
"failing_one": "{{count}} fallido",
"failing_other": "{{count}} fallidos",
"neutral_many": "{{count}} neutrales",
"neutral_one": "{{count}} neutral",
"neutral_other": "{{count}} neutrales",
"noChecks": "Sin checks",
"passed_many": "{{count}} aprobados",
"passed_one": "{{count}} aprobado",
"passed_other": "{{count}} aprobados",
"pending_many": "{{count}} pendientes",
"pending_one": "{{count}} pendiente",
"pending_other": "{{count}} pendientes",
"stateClosed": "Cerrado",
"stateDraft": "Borrador",
"stateMerged": "Fusionado",
"stateOpen": "Abierto",
"unavailable": "Checks no disponibles"
},
"prCommentAudienceLabels": {
"all": "Todos",
"bots": "Bots",
"humans": "Humanos",
"noBotComments": "Sin comentarios de robots.",
"noComments": "Aún no hay comentarios.",
"noHumanComments": "Sin comentarios humanos."
},
"prSidebarCreateEmptyState": {
"branchNotLinked": "La rama {{branch}} no está vinculada a ningún PR abierto.",
"branchRequired": "Haz checkout de una rama antes de crear un pull request.",
"createFailed": "No se pudo crear el pull request.",
"createPr": "Crear PR",
"createPullRequest": "Crear pull request",
"currentBranchNotLinked": "La rama actual no está vinculada a ningún PR abierto.",
"linkExistingPr": "Vincular un PR existente",
"linkExistingPullRequest": "Vincular un pull request existente",
"linkedPrUnavailable": "PR #{{number}} vinculado no disponible",
"noOpenPullRequest": "Ningún pull request abierto",
"refreshOrCreate": "Actualiza para volver a comprobarlo o crea un nuevo PR para esta rama.",
"refreshPullRequest": "Actualizar pull request",
"title": "Pull request"
},
"prSidebarHeader": {
"cancelEditingTitle": "Cancelar la edición del título",
"editTitle": "Editar el título de la pull request",
"openInBrowser": "Abrir la pull request #{{number}} en el navegador",
"openOnWeb": "Abrir la pull request #{{number}} en la web",
"saveTitle": "Guardar título"
},
"prViewPanel": {
"branchUnavailable": "La rama actual no está disponible.",
"providerUnavailable": "El panel de revisión alojada no está disponible para este proveedor."
},
"reviewerPickerDrawer": {
"loadFailed": "No se pudieron cargar las personas",
"noMatches": "No hay personas que coincidan",
"removeReviewer": "Quitar a {{login}}",
"requestReviewer": "Solicitar revisión a {{login}}",
"searchPeople": "Buscar personas",
"title": "Revisores"
},
"reviewersSection": {
"addOrRemove": "Añadir o eliminar revisores",
"approved": "Aprobado",
"changesRequested": "Cambios solicitados",
"commented": "Comentado",
"dismissed": "Descartado",
"loadFailed": "No se pudieron cargar los revisores. Toca Actualizar para reintentarlo.",
"loading": "Cargando revisores…",
"noReviewers": "No se han solicitado revisores",
"pending": "Pendiente",
"removeReviewer": "Eliminar a {{login}}",
"requested": "Solicitado",
"reviewed": "Revisado",
"title": "Revisores"
}
},
"settings": {
"aboutScreen": {
"openLinkFailed": "No se pudo abrir el enlace. Vuelve a intentarlo.",
+755
View File
@@ -146,9 +146,14 @@
"common": {
"back": "Retour",
"cancel": "Annuler",
"close": "Fermer",
"confirm": "Confirmer",
"continue": "Continuer",
"copied": "Copié",
"copy": "Copier",
"delete": "Supprimer",
"done": "Terminé",
"edit": "Modifier",
"ok": "OK",
"remove": "Supprimer",
"retry": "Réessayer",
@@ -156,6 +161,477 @@
"settings": "Paramètres",
"unknown": "Inconnu"
},
"components": {
"accountsSnapshot": {
"invalidSnapshot": "Instantané des comptes invalide reçu de l'hôte"
},
"agentLaunchNotice": {
"copyFailed": "Impossible de copier",
"copyPrompt": "Copier le prompt",
"statusUnreadable": "Impossible de lire l'état de cet hôte. Nouvelle tentative…",
"updateRequired": "Mettez à jour Orca sur votre ordinateur pour démarrer un agent depuis votre téléphone."
},
"agentSpinner": {
"monitoring": "Surveillance des tâches en arrière-plan",
"unconfirmed": "Impossible de confirmer"
},
"agentStateDot": {
"monitoring": "Surveillance des tâches en arrière-plan",
"unverifiable": "Aucune mise à jour récente"
},
"authFailedBanner": {
"message": "Échec de l'authentification — essayez d'abord de vous reconnecter ; si l'échec persiste, recommencez l'appairage depuis votre ordinateur."
},
"authFailedBannerActions": {
"repair": "Appairer à nouveau",
"webNote": "Reconnectez cet hôte ou supprimez-le depuis l'application Orca."
},
"codexResetCredit": {
"alreadyAppliedTitle": "Réinitialisation déjà appliquée",
"available_many": "{{count}} réinitialisations disponibles",
"available_one": "{{count}} réinitialisation disponible",
"available_other": "{{count}} réinitialisations disponibles",
"expiryDaysHoursMultiple": "La prochaine expire dans {{days}} j {{hours}} h",
"expiryDaysHoursSingle": "Expire dans {{days}} j {{hours}} h",
"expiryDaysMultiple": "La prochaine expire dans {{days}} j",
"expiryDaysSingle": "Expire dans {{days}} j",
"expiryHoursMinutesMultiple": "La prochaine expire dans {{hours}} h {{minutes}} min",
"expiryHoursMinutesSingle": "Expire dans {{hours}} h {{minutes}} min",
"expiryHoursMultiple": "La prochaine expire dans {{hours}} h",
"expiryHoursSingle": "Expire dans {{hours}} h",
"expiryMinutesMultiple": "La prochaine expire dans {{minutes}} min",
"expiryMinutesSingle": "Expire dans {{minutes}} min",
"expiryNowMultiple": "La prochaine expire maintenant",
"expiryNowSingle": "Expire maintenant",
"invalidResponse": "Réponse de réinitialisation invalide de l'hôte",
"noResetMessage": "Aucun crédit de réinitialisation acquis n'est disponible pour ce compte.",
"noResetTitle": "Aucune réinitialisation disponible",
"nothingToResetMessage": "Aucune fenêtre d'utilisation Codex éligible n'a atteint sa limite.",
"nothingToResetTitle": "Rien à réinitialiser",
"refresh": "Les données d'utilisation de Codex ont été actualisées.",
"resetTitle": "Limites de débit réinitialisées"
},
"codexResetCreditAction": {
"activeAccountHint": "Utilise une réinitialisation obtenue pour le compte Codex actif",
"earnedReset": "Réinitialisation de limite de débit Codex obtenue",
"resetting": "En cours…",
"resettingLabel": "Réinitialisation des limites de débit Codex",
"scopedHint": "Utilise une réinitialisation obtenue pour {{scopeLabel}}",
"useReset": "Utiliser",
"useResetLabel": "Utiliser une réinitialisation de limite de débit Codex"
},
"customKeyModal": {
"add": "Ajouter",
"addShortcut": "Ajouter un raccourci",
"command": "Commande",
"commandPlaceholder": "Ex. : pnpm build",
"editing": "Édition",
"function": "Fonction",
"key": "Touche",
"label": "Libellé",
"labelPlaceholder": "Ex. : Compilation",
"manageShortcuts": "Gérer les raccourcis",
"manageShortcutsHint": "Afficher, masquer ou réordonner les touches de raccourci",
"modifiers": "Modificateurs",
"moreKeys": "Autres touches — Tab, flèches, F1–F12…",
"navigation": "Navigation",
"pickKey": "Choisir une touche",
"pressEnter": "Appuyer sur Enter",
"shortcutCombo": "Combinaison de touches",
"shortcutComboHint": "Créer des combinaisons avec Ctrl, Alt et Shift",
"textMacro": "Macro de texte",
"textMacroHint": "Envoyer une commande texte personnalisée"
},
"dictationSetupSheet": {
"description": "Téléchargez ou choisissez un modèle et activez la dictée sur l'ordinateur appairé, le tout depuis cet écran.",
"download": "Télécharger",
"downloadFailed": "Échec du téléchargement",
"downloadModel": "Télécharger {{model}}",
"enabled": "Dictée activée",
"inUse": "Utilisé",
"loadFailed": "Échec du chargement",
"modelExtracting": "{{details}} · extraction…",
"modelProgress": "{{details}} · {{progress}} %",
"modelSize": "{{size}} Mo",
"recommended": "Recommandé",
"selectFailed": "Impossible de sélectionner le modèle",
"title": "Configurer la dictée vocale",
"updateFailed": "Impossible de mettre à jour",
"use": "Utiliser",
"useModel": "Utiliser {{model}}"
},
"diffReviewBody": {
"binaryDiff": "Diff binaire",
"binaryMessage": "Ce fichier ne peut pas être affiché sous forme de texte sur mobile.",
"deletedFile": "Fichier supprimé",
"deletedMessage": "Ce fichier a été supprimé. Ajoutez une note sur le fichier ou marquez-le comme examiné.",
"diffTooLarge": "Diff trop volumineux",
"diffTruncated": "Diff tronqué pour l'aperçu mobile.",
"diffUnavailable": "Diff indisponible",
"loadFailed": "Impossible de charger la revue",
"loadingDiff": "Chargement du diff…",
"loadingReview": "Chargement de la revue…",
"noReviewableChanges": "Aucune modification à examiner",
"retryLabel": "Réessayer de charger la revue",
"reviewUnavailable": "Revue indisponible",
"selectFile": "Sélectionnez un fichier à examiner.",
"tooLargeMessage": "Ce diff est trop volumineux pour l'aperçu sur mobile.",
"tryDifferentFilter": "Essayez un autre filtre de revue."
},
"diffReviewDrawers": {
"actionsTitle": "Actions de revue",
"addNote": "Ajouter une note",
"cancelNote": "Annuler la note",
"clearSentNotes": "Effacer les notes envoyées",
"completeTitle": "Revue terminée",
"copyNotes": "Copier les notes",
"deleteNote": "Supprimer la note",
"discard": "Abandonner",
"discardMessage": "Abandonner les modifications de « {{filePath}} » ? Cette action est irréversible.",
"discardTitle": "Abandonner les modifications",
"editNote": "Modifier la note",
"fileNote": "Note sur le fichier",
"fileSummary_many": "{{fileCount}} fichier examiné, {{count}} notes",
"fileSummary_one": "{{fileCount}} fichier examiné, {{count}} note",
"fileSummary_other": "{{fileCount}} fichier examiné, {{count}} notes",
"filesSummary_many": "{{fileCount}} fichiers examinés, {{count}} notes",
"filesSummary_one": "{{fileCount}} fichiers examinés, {{count}} note",
"filesSummary_other": "{{fileCount}} fichiers examinés, {{count}} notes",
"lineNumber": "Ligne {{lineNumber}}",
"loadingSessions": "Chargement des sessions d'agent…",
"markUnreviewed": "Marquer comme non examiné",
"newAgentSession": "Nouvelle session d'agent",
"openInSession": "Ouvrir dans une session",
"reviewNote": "Note de revue",
"saveLineNote": "Enregistrer la note sur la ligne {{lineNumber}}",
"sendNotes": "Envoyer les notes",
"sendNotesLabel": "Envoyer les notes à l'agent",
"sendUnsentNotes": "Envoyer les notes non envoyées",
"stageReviewed": "Stager ces fichiers",
"stageReviewedFiles": "Stager les fichiers examinés",
"stageReviewedLabel": "Stager les fichiers examinés",
"stageableFiles_many": "{{count}} fichiers examinés non indexés peuvent être stagés",
"stageableFiles_one": "{{count}} fichier examiné non indexé peut être stagé",
"stageableFiles_other": "{{count}} fichiers examinés non indexés peuvent être stagés",
"statusUnreadable": "Impossible de lire l'état de cet hôte. Nouvelle tentative…",
"terminal": "Terminal",
"terminalLabel": "{{title}} ({{id}})",
"unsentNotes_many": "{{count}} notes non envoyées",
"unsentNotes_one": "{{count}} note non envoyée",
"unsentNotes_other": "{{count}} notes non envoyées",
"updateRequired": "Mettez à jour Orca sur votre ordinateur"
},
"diffReviewFileSummary": {
"branch": "Branche",
"branchFrom": "Branche · depuis {{oldPath}}",
"changed": "Modifié",
"editFileNote": "Modifier la note sur le fichier",
"hunk": "Bloc",
"nextHunk": "Bloc suivant",
"notes_many": "{{count}} notes",
"notes_one": "{{count}} note",
"notes_other": "{{count}} notes",
"previousHunk": "Bloc précédent",
"reviewed": "Examiné",
"staged": "Dans l’index",
"stagedFrom": "Dans l’index · depuis {{oldPath}}",
"stale": "Obsolète",
"staleCount": "Obsolètes : {{count}}",
"unstaged": "Hors de l’index",
"unstagedFrom": "Hors de l’index · depuis {{oldPath}}"
},
"diffReviewFooter": {
"addFileNote": "Ajouter une note sur le fichier",
"discard": "Abandonner",
"discardFile": "Abandonner les modifications du fichier",
"markFileReviewed": "Marquer le fichier comme examiné",
"markReviewed": "Terminer l'examen",
"nextFile": "Fichier suivant",
"note": "Note",
"previousFile": "Fichier précédent",
"reviewed": "Examiné",
"stage": "Stager",
"stageFile": "Stager le fichier",
"unstage": "Déstager",
"unstageFile": "Déstager le fichier"
},
"diffReviewHeader": {
"filterAll": "Tous",
"filterBranch": "Branche",
"filterNotes": "Notes",
"filterStaged": "Dans l’index",
"filterUnreviewed": "Non examinés",
"filterUnstaged": "Hors de l’index",
"openPullRequestSidebar": "Ouvrir la barre latérale de la pull request",
"openReviewActions": "Ouvrir les actions de revue",
"reviewedProgress": "Fichiers examinés : {{reviewedCount}}/{{queueLength}}",
"showAllFiles": "Afficher tous les fichiers de la revue",
"showBranchFiles": "Afficher les fichiers de la branche dans la revue",
"showNotesFiles": "Afficher les fichiers de la revue avec des notes",
"showStagedFiles": "Afficher les fichiers indexés de la revue",
"showUnreviewedFiles": "Afficher les fichiers non examinés de la revue",
"showUnstagedFiles": "Afficher les fichiers non indexés de la revue",
"title": "Modifications",
"unsentNotes_many": "{{count}} notes non envoyées",
"unsentNotes_one": "{{count}} note non envoyée",
"unsentNotes_other": "{{count}} notes non envoyées"
},
"diffReviewLine": {
"addNoteOnLine": "Ajouter une note à la ligne {{number}}",
"addedLine": "Ligne ajoutée",
"addedLineNumber": "Ligne {{number}} ajoutée",
"contextLine": "Ligne de contexte",
"contextLineNumber": "Ligne de contexte {{number}}",
"deletedLine": "Ligne supprimée",
"deletedLineNumber": "Ligne {{number}} supprimée",
"editNoteOnLine": "Modifier la note à la ligne {{number}}"
},
"dragReorderRow": {
"dragToReorder": "Faire glisser pour réordonner",
"moveDown": "Descendre",
"moveUp": "Monter",
"reorderHint": "Utilisez les actions « Monter » et « Descendre » pour réordonner sans faire glisser"
},
"homeQuickActions": {
"newWorkspace": "Nouvel espace de travail",
"pairDesktop": "Appairer l'ordinateur",
"title": "Actions rapides"
},
"hostCard": {
"hostActions": "Actions pour {{title}}",
"openHost": "Ouvrir {{title}}",
"pairingInvalid": "Appairage invalide",
"pairingTemporarilyUnavailable": "Appairage temporairement indisponible",
"repairHint": "Touchez pour appairer à nouveau votre ordinateur.",
"unlockHint": "Déverrouillez votre téléphone, puis touchez pour réessayer.",
"updateDesktopHint": "Mettez à jour Orca sur votre ordinateur et connectez-vous pour y accéder où que vous soyez.",
"worktreeAccessibilitySummary": "{{summary}}, {{activeCount}} en activité"
},
"hostDiagnosticsLink": {
"viewDiagnostics": "Voir les diagnostics réseau"
},
"hostProtocolGate": {
"checkingCompatibility": "Vérification de la compatibilité de l'hôte"
},
"hostRouteNoticeBanner": {
"dismissNotice": "Masquer l'avis"
},
"htmlPreview": {
"preview": "Aperçu",
"previewRenderedHtml": "Afficher un aperçu du rendu HTML",
"source": "Source",
"title": "Aperçu HTML",
"viewHtmlSource": "Afficher le code source HTML"
},
"jiraConnectPrompt": {
"checkAgain": "Revérifier",
"connectHint": "Ajoutez votre site Jira dans Orca sur votre ordinateur, sous Paramètres → Intégrations, puis revérifiez.",
"connectOnDesktop": "Connecter Jira sur votre ordinateur",
"credentialUnreadable": "Les identifiants Jira sont enregistrés mais illisibles : {{credentialError}}",
"updateDesktop": "Mettre à jour Orca sur votre ordinateur"
},
"markdownBlocks": {
"copyCode": "Copier le code",
"openImage": "Ouvrir l'image"
},
"mountedBottomDrawer": {
"dismissDrawer": "Fermer le panneau"
},
"newWorkspaceFab": {
"newWorkspace": "Nouvel espace de travail"
},
"newWorkspaceSetupScriptField": {
"hooks": "HOOKS",
"label": "Script de setup",
"run": "Exécuter",
"runSetupCommand": "Exécuter la commande de setup",
"skip": "Ignorer"
},
"newWorkspaceSshConnectionField": {
"connect": "Se connecter",
"connecting": "Connexion…",
"label": "Connexion SSH",
"serverRecovery": "Son serveur doit être récupéré depuis l'ordinateur : Paramètres → Hôtes SSH → Récupérer le serveur."
},
"newWorktreeAgentSelection": {
"blankTerminal": "Terminal vierge"
},
"newWorktreeFormSheet": {
"advanced": "Avancé",
"agent": "Agent",
"connectTarget": "Se connecter à la cible",
"connectTargetFirst": "Connectez-vous d'abord à la cible",
"createWorktree": "Créer un worktree",
"nameOrCreateFrom": "Nom ou source",
"noProjectsFound": "Aucun projet trouvé",
"note": "Note",
"notePlaceholder": "Écrire une note",
"workspaceName": "Nom de l'espace de travail"
},
"newWorktreeModal": {
"remoteRepository": "Dépôt distant"
},
"newWorktreeModalDrawers": {
"agent": "Agent",
"project": "Projet",
"runOn": "Exécuter sur"
},
"newWorktreeProjectTargetFields": {
"project": "Projet",
"runOn": "Exécuter sur",
"selectProject": "Choisir un projet",
"selectRunTarget": "Choisir une cible d'exécution"
},
"prSidebar": {
"loadFailed": "Une erreur s'est produite.",
"loading": "Chargement de la pull request…",
"notPermitted": "Accès refusé — votre compte GitHub n'est pas connecté.",
"retryLoading": "Réessayer de charger la pull request"
},
"protocolBlockScreen": {
"backToHosts": "Retour aux hôtes",
"desktopTooOldBody": "La version d'Orca sur cet ordinateur appairé est trop ancienne pour votre version actuelle d'Orca Mobile. Mettez Orca à jour sur votre ordinateur, puis réessayez de vous connecter à cet hôte.",
"getVersion": "Obtenir Orca {{version}}",
"mobileTooOldAppStore": "Cet ordinateur nécessite une version plus récente d'Orca Mobile. Mettez Orca Mobile à jour depuis l'App Store, puis réessayez de vous connecter à cet hôte.",
"mobileTooOldGitHubReleases": "Cet ordinateur nécessite une version plus récente d'Orca Mobile. Mettez Orca Mobile à jour depuis GitHub Releases, puis réessayez de vous connecter à cet hôte.",
"openAppStore": "Ouvrir l'App Store",
"openGitHubReleases": "Ouvrir GitHub Releases",
"refreshRecoveryNote": "Si ce message persiste, supprimez cet hôte et appairez-le à nouveau.",
"refreshWorkspaceBody": "La version de l'espace de travail en cache pour cet hôte est antérieure à celle attendue par l'ordinateur. Reconnectez-vous à cet hôte pour télécharger la version actuelle.",
"refreshWorkspaceTitle": "Actualiser l'espace de travail mobile",
"updateDesktopTitle": "Mettre à jour Orca sur votre ordinateur",
"updateMobileTitle": "Mettre à jour Orca Mobile",
"updateRecoveryNote": "Déjà à jour ? Revenez à l'écran Hôtes et actualisez la connexion. Si ce message persiste, supprimez cet hôte et appairez-le à nouveau.",
"workspaceNeedsUpdateAppStore": "L'espace de travail mobile de cet ordinateur nécessite une version plus récente d'Orca Mobile. Mettez Orca Mobile à jour depuis l'App Store, puis réessayez de vous connecter à cet hôte.",
"workspaceNeedsUpdateGitHubReleases": "L'espace de travail mobile de cet ordinateur nécessite une version plus récente d'Orca Mobile. Mettez Orca Mobile à jour depuis GitHub Releases, puis réessayez de vous connecter à cet hôte."
},
"richMarkdownEditor": {
"imageUrl": "URL de l'image",
"insert": "Insérer",
"linkUrl": "URL du lien"
},
"richMarkdownToolbar": {
"body": "Corps de texte",
"bold": "Gras",
"bulletList": "Liste à puces",
"checklist": "Liste de tâches",
"codeBlock": "Bloc de code",
"heading1": "H1",
"heading2": "H2",
"heading3": "H3",
"image": "Image",
"inlineCode": "Code en ligne",
"italic": "Italique",
"link": "Lien",
"numberedList": "Liste numérotée",
"quote": "Citation",
"strike": "Barré"
},
"searchField": {
"clearSearch": "Effacer la recherche"
},
"setupHookTrustDrawer": {
"alwaysTrustAndRun": "Toujours faire confiance et exécuter",
"description": "Le fichier orca.yaml de ce dépôt s'exécute avant le démarrage de l'espace de travail. Ne l'exécutez que si vous faites confiance à ce dépôt.",
"dontRun": "Ne pas exécuter",
"newScriptLabel": "Nouveau script de setup",
"runHooks": "Exécuter les hooks",
"runSetupTitle": "Exécuter le setup de {{repoName}} ?",
"scriptChangedTitle": "Le script de setup de {{repoName}} a changé",
"scriptLabel": "Script de setup"
},
"smartWorkspaceAdvancedFields": {
"branchName": "Nom de branche",
"derivedFromName": "Dérivé du nom",
"name": "Nom",
"reuseBranch": "Réutiliser la branche « {{branch}} »",
"workspaceName": "Nom de l'espace de travail"
},
"smartWorkspaceSourceDrawer": {
"connectRepository": "Connectez le dépôt pour rechercher des sources.",
"crossRepoMessage": "Cet élément se trouve dans {{owner}}/{{repo}}.",
"gitHubRemoteRequired": "Ce dépôt SSH nécessite un remote GitHub pour afficher les issues et les PR.",
"noResults": "Aucun résultat trouvé.",
"searchPlaceholder": "Saisissez un nom ou recherchez une source",
"switchRepo": "Passer à {{repoName}}",
"title": "Nom ou source",
"typeWorkspaceName": "Saisissez un nom d'espace de travail dans le champ ci-dessous."
},
"smartWorkspaceSourceField": {
"openSelectedSource": "Ouvrir la source sélectionnée",
"optional": "[Optionnel]",
"searchPlaceholder": "Saisissez un nom ou recherchez une source"
},
"smartWorkspaceSourceRow": {
"createBranch": "Créer la branche « {{name}} »",
"issue": "Issue #{{number}}",
"mergeRequest": "MR !{{number}}",
"nameWorkspace": "Nommer cet espace de travail",
"newBranch": "Nouvelle branche",
"pullRequest": "PR #{{number}}",
"useName": "Utiliser « {{name}} »"
},
"sourceCopyButton": {
"clipboardRejected": "Le presse-papiers a rejeté le texte.",
"copiedLoaded": "Extrait copié",
"copyFailed": "Échec de la copie",
"copyLoaded": "Copier l'extrait",
"couldNotCopy": "Impossible de copier",
"failed": "Échec"
},
"terminalShortcutSettings": {
"addDescription": "Créer une combinaison de touches ou une macro de texte",
"addShortcut": "Ajouter un raccourci personnalisé…",
"customShortcuts": "RACCOURCIS PERSONNALISÉS",
"empty": "Aucun raccourci personnalisé défini pour le moment.",
"removeShortcut": "Supprimer {{label}}",
"resetDefaults": "Rétablir les valeurs par défaut",
"resetDescription": "Afficher toutes les touches de raccourci intégrées dans l'ordre initial",
"shortcutBar": "BARRE DE RACCOURCIS",
"shortcutBarDescription": "Activez ou désactivez les touches pour les afficher ou les masquer. Maintenez le doigt sur la poignée pour faire glisser une touche à la position souhaitée dans la barre de raccourcis du terminal."
},
"useCodexResetCreditAction": {
"confirmTitle": "Utiliser une réinitialisation des limites de débit ?",
"detailsChanged": "Détails de réinitialisation modifiés",
"detailsChangedMessage": "Le compte ou l'offre de réinitialisation a changé avant que l'hôte ne contacte Codex. Consultez les détails mis à jour, puis confirmez à nouveau.",
"hostAccount": "{{identity}} sur l'hôte",
"hostAccountConfirmMessage": "Cela consomme un crédit de réinitialisation acquis pour {{identity}} sur l'hôte et réinitialise immédiatement les fenêtres d'utilisation éligibles.",
"hostSelectedAccount": "le compte géré sélectionné sur l'hôte",
"hostSelectedAccountConfirmMessage": "Cela consomme un crédit de réinitialisation acquis pour le compte géré sélectionné sur l'hôte et réinitialise immédiatement les fenêtres d'utilisation éligibles.",
"resetFailed": "Impossible de réinitialiser les limites de débit",
"wslAccount": "{{identity}} sous WSL {{distro}}",
"wslAccountConfirmMessage": "Cela consomme un crédit de réinitialisation acquis pour {{identity}} sous WSL {{distro}} et réinitialise immédiatement les fenêtres d'utilisation éligibles.",
"wslSelectedAccount": "le compte géré sélectionné sous WSL {{distro}}",
"wslSelectedAccountConfirmMessage": "Cela consomme un crédit de réinitialisation acquis pour le compte géré sélectionné sous WSL {{distro}} et réinitialise immédiatement les fenêtres d'utilisation éligibles."
},
"voiceModelList": {
"deleteModel": "Supprimer {{label}}",
"downloadModel": "Télécharger {{label}}",
"inUse": "Utilisé",
"modelExtracting": "{{size}} · extraction…",
"modelProgress": "{{size}} · {{progress}} %",
"modelSize": "{{size}} Mo",
"recommended": "Recommandé",
"use": "Utiliser",
"useModel": "Utiliser {{label}}"
},
"workspaceDetailPlaceholder": {
"description": "Choisissez un espace de travail dans la barre latérale pour ouvrir son terminal ici.",
"title": "Aucun espace de travail ouvert"
},
"workspaceNameInput": {
"placeholder": "Nom de l'espace de travail"
},
"worktreeListRow": {
"child": "Enfant",
"children_many": "{{count}} enfants",
"children_one": "{{count}} enfant",
"children_other": "{{count}} enfants",
"folder": "Dossier",
"project": "Projet {{repo}}",
"remoteHost": "Hôte distant"
}
},
"connection": {
"connectionHealth": {
"checkConnection": "Vérifiez votre connexion",
@@ -572,6 +1048,285 @@
"useChatUi": "Utiliser le chat"
}
},
"pullRequest": {
"checksSection": {
"actionRequired": "Action requise",
"cancelled": "Annulée",
"checkDetails": "Détails de la vérification {{name}}",
"failed": "Échec",
"failing": "Échec : {{count}}",
"failingChecks_many": "{{count}} vérifications en échec",
"failingChecks_one": "{{count}} vérification en échec",
"failingChecks_other": "{{count}} vérifications en échec",
"fix": "Corriger",
"fixFailing": "Corriger les vérifications en échec avec l'IA",
"inProgress": "En cours",
"inspectOrFix": "Inspectez les détails ou lancez une passe de correction IA.",
"loadDetailsFailed": "Impossible de charger les détails de la vérification",
"neutral": "Neutre",
"neutralCount": "Neutre : {{count}}",
"noChecks": "Aucune vérification",
"openOnWeb": "Ouvrir {{name}} sur le Web",
"passed": "Succès : {{count}}",
"pending": "En attente",
"pendingCount": "En attente : {{count}}",
"queued": "En file d'attente",
"rerunFailing": "Relancer les vérifications en échec",
"skipped": "Ignorée",
"successful": "Succès",
"timedOut": "Délai dépassé",
"title": "Vérifications",
"unavailable": "Checks indisponibles"
},
"commentCard": {
"daysAgo_many": "il y a {{count}} j",
"daysAgo_one": "il y a {{count}} j",
"daysAgo_other": "il y a {{count}} j",
"deleteComment": "Supprimer le commentaire",
"deleteConfirmMessage": "Cette action supprime définitivement le commentaire sur GitHub.",
"deleteConfirmTitle": "Supprimer le commentaire ?",
"editComment": "Modifier le commentaire",
"editPlaceholder": "Modifier le commentaire…",
"hoursAgo_many": "il y a {{count}} h",
"hoursAgo_one": "il y a {{count}} h",
"hoursAgo_other": "il y a {{count}} h",
"justNow": "à l'instant",
"minutesAgo_many": "il y a {{count}} min",
"minutesAgo_one": "il y a {{count}} min",
"minutesAgo_other": "il y a {{count}} min",
"monthsAgo_many": "il y a {{count}} mois",
"monthsAgo_one": "il y a {{count}} mois",
"monthsAgo_other": "il y a {{count}} mois",
"openOnGitHub": "Ouvrir le commentaire sur GitHub",
"reply": "Répondre",
"replyPlaceholder": "Écrire une réponse…",
"replyToComment": "Répondre au commentaire",
"resolve": "Résoudre",
"resolveThread": "Résoudre le fil",
"resolved": "résolu",
"unresolve": "Marquer comme non résolu",
"unresolveThread": "Marquer le fil comme non résolu",
"yearsAgo_many": "il y a {{count}} ans",
"yearsAgo_one": "il y a {{count}} an",
"yearsAgo_other": "il y a {{count}} ans"
},
"commentsSection": {
"addCommentPlaceholder": "Ajouter un commentaire…",
"all": "Tous",
"bots": "Bots",
"comment": "Commenter",
"commentsLoadFailed": "Impossible de charger les commentaires. Appuyez sur Actualiser pour réessayer.",
"description": "Description",
"descriptionLoadFailed": "Impossible de charger la description. Appuyez sur Actualiser pour réessayer.",
"humans": "Humains",
"noBotComments": "Aucun commentaire de bot.",
"noComments": "Aucun commentaire pour le moment.",
"noDescription": "Aucune description fournie.",
"noHumanComments": "Aucun commentaire humain.",
"resolvedComment_many": "Commentaire de {{author}} résolu ({{count}})",
"resolvedComment_one": "Commentaire de {{author}} résolu",
"resolvedComment_other": "Commentaire de {{author}} résolu ({{count}})",
"resolvedThread_many": "Fil de {{author}} résolu ({{count}})",
"resolvedThread_one": "Fil de {{author}} résolu",
"resolvedThread_other": "Fil de {{author}} résolu ({{count}})",
"showMore": "Afficher {{count}} de plus",
"showMoreOf": "Afficher {{count}} de plus sur {{remaining}}",
"title": "Commentaires"
},
"conflictingFilesSection": {
"commitsBehind_many": "{{count}} commits de retard (commit de base : {{baseCommit}})",
"commitsBehind_one": "{{count}} commit de retard (commit de base : {{baseCommit}})",
"commitsBehind_other": "{{count}} commits de retard (commit de base : {{baseCommit}})",
"copyCommands": "Copier les commandes",
"copyFailed": "Impossible de copier le texte",
"copyRefreshCommands": "Copier les commandes de recalcul du merge",
"detailsUnavailable": "Les détails du fichier en conflit sont indisponibles",
"filesHeading": "Fichiers en conflit",
"localMergeClean": "GitHub signale des conflits, mais Git ne les a pas reproduits en local. Actualisez la PR ou poussez la branche pour réévaluer la possibilité de fusion.",
"mustResolve": "Cette branche contient des conflits qui doivent être résolus",
"refreshing": "Actualisation des détails de conflit…",
"resolveWithAi": "Résoudre les conflits avec l'IA",
"runFromWorktree": "Exécuter depuis ce worktree",
"title": "Conflits"
},
"linkPrForm": {
"linkButton": "Lier la pull request",
"linkNumber": "Lier #{{number}}",
"referenceLabel": "Numéro de PR ou URL GitHub",
"referencePlaceholder": "#123 ou https://github.com/owner/repo/pull/123",
"title": "Lier une pull request existante"
},
"markdownBlocks": {
"details": "Détails"
},
"prActionsSection": {
"autoMergeAccessibilityLabel": "Activer ou désactiver l'auto-merge",
"autoMergeLabel": "Auto-merge dès que possible",
"autoMergeOff": "Désactivé",
"autoMergeOn": "Activé",
"closeAccessibilityLabel": "Fermer la pull request",
"closeConfirmMessage": "#{{number}} sera fermée sans merge.",
"closeConfirmTitle": "Fermer la pull request ?",
"markReadyAccessibilityLabel": "Marquer la pull request comme prête à être examinée",
"markReadyButton": "Prête pour la revue",
"mergeAccessibilityLabel": "Merger la pull request",
"mergeButton": "Merge",
"mergeConfirmTitle": "Merger la pull request ?",
"recheckAccessibilityLabel": "Revérifier l'état du merge",
"recheckButton": "Revérifier",
"reopenAccessibilityLabel": "Rouvrir la pull request",
"reopenButton": "Rouvrir",
"reopenConfirmMessage": "#{{number}} sera rouverte.",
"reopenConfirmTitle": "Rouvrir la pull request ?",
"unlinkAccessibilityLabel": "Dissocier la pull request",
"unlinkButton": "Dissocier",
"unlinkFailed": "Impossible de dissocier la pull request."
},
"prActionsState": {
"behindHint": "Mettez à jour la branche avant de merger",
"behindLabel": "En retard",
"blockedHint": "GitHub signale que cette pull request est bloquée",
"blockedLabel": "Bloquée",
"changesRequestedHint": "GitHub signale des changements demandés sur cette pull request",
"changesRequestedLabel": "Modifications demandées",
"checkingHint": "GitHub calcule encore le statut de merge de cette pull request",
"checkingLabel": "Vérification",
"checksPassedHint": "Checks réussis. L'éligibilité au merge sera revérifiée avant le merge.",
"checksPassedLabel": "Checks réussis",
"closedHint": "Cette pull request est fermée",
"closedLabel": "Fermée",
"conflictsHint": "GitHub signale des conflits de merge",
"conflictsLabel": "Conflits",
"draftHint": "Cette pull request est toujours en brouillon",
"draftLabel": "Brouillon",
"mergeConfirmMessage": "Cette action mergera #{{number}} dans sa branche de base.",
"mergeQueueAutoMergeOnHint": "Cette branche de base utilise la merge queue GitHub",
"mergeQueueAutoMergeOnLabel": "Merge auto activé",
"mergeQueueHint": "Cette branche de base utilise la merge queue GitHub",
"mergeQueueLabel": "Merger dès que possible",
"mergeStackMessage": "GitHub mergera cette pull request et toutes celles situées en dessous dans la pile.",
"mergeStatusUnavailableHint": "Statut de merge indisponible pour cette PR",
"mergeStatusUnavailableLabel": "Merge",
"mergeThrough_many": "Merger jusqu'à #{{number}} · {{count}} PR",
"mergeThrough_one": "Merger jusqu'à #{{number}} · {{count}} PR",
"mergeThrough_other": "Merger jusqu'à #{{number}} · {{count}} PR",
"mergeableChecksFailedHint": "GitHub indique que cette PR peut merger, mais certains checks ont échoué",
"mergeableChecksFailedLabel": "Vérifications échouées",
"mergeableChecksPassedHint": "GitHub indique que cette PR peut merger et que les checks ont réussi",
"mergeableChecksPassedLabel": "Peut merger",
"mergeableChecksPendingHint": "GitHub indique que cette PR peut merger, mais des checks sont encore en cours",
"mergeableChecksPendingLabel": "Vérifications en attente",
"mergeableHint": "GitHub indique que cette PR peut merger",
"mergeableLabel": "Peut merger",
"mergedHint": "Cette pull request est déjà mergée",
"mergedLabel": "Fusionnée",
"methodMerge": "Créer un commit de merge",
"methodRebase": "Rebaser et merger",
"methodSquash": "Squasher et merger",
"queueStackMessage": "GitHub ajoutera cette pull request et toutes celles situées en dessous à la file de merge.",
"queueThrough_many": "Mettre en file jusqu'à #{{number}} · {{count}} PR",
"queueThrough_one": "Mettre en file jusqu'à #{{number}} · {{count}} PR",
"queueThrough_other": "Mettre en file jusqu'à #{{number}} · {{count}} PR",
"reviewRequiredHint": "GitHub exige une review approuvée avant que cette pull request puisse merger",
"reviewRequiredLabel": "Approbation requise",
"stackBehind": "#{{number}} doit être mise à jour.",
"stackBlocked": "#{{number}} est bloquée.",
"stackChangesRequested": "Des modifications sont demandées pour #{{number}}.",
"stackClosed": "#{{number}} est fermée.",
"stackConflicts": "#{{number}} présente des conflits de merge.",
"stackDraft": "#{{number}} est toujours en brouillon.",
"stackReviewRequired": "#{{number}} doit encore être approuvée en revue."
},
"prCheckDetail": {
"annotations": "Annotations",
"annotationsTruncated": "Affichage des 20 premières annotations",
"jobsTruncated": "Affichage des 100 premiers jobs",
"noDetails": "Aucun détail disponible."
},
"prCheckDetailContent": {
"annotation": "Annotation",
"failedJobs": "Jobs en échec",
"jobs": "Jobs",
"unknown": "inconnu"
},
"prChecksPresentation": {
"failing_many": "{{count}} en échec",
"failing_one": "{{count}} en échec",
"failing_other": "{{count}} en échec",
"neutral_many": "{{count}} neutres",
"neutral_one": "{{count}} neutre",
"neutral_other": "{{count}} neutres",
"noChecks": "Aucune vérification",
"passed_many": "{{count}} réussies",
"passed_one": "{{count}} réussie",
"passed_other": "{{count}} réussies",
"pending_many": "{{count}} en attente",
"pending_one": "{{count}} en attente",
"pending_other": "{{count}} en attente",
"stateClosed": "Fermée",
"stateDraft": "Brouillon",
"stateMerged": "Fusionnée",
"stateOpen": "Ouverte",
"unavailable": "Checks indisponibles"
},
"prCommentAudienceLabels": {
"all": "Tous",
"bots": "Bots",
"humans": "Humains",
"noBotComments": "Aucun commentaire de bot.",
"noComments": "Aucun commentaire pour le moment.",
"noHumanComments": "Aucun commentaire humain."
},
"prSidebarCreateEmptyState": {
"branchNotLinked": "{{branch}} n'est pas liée à une PR ouverte.",
"branchRequired": "Effectuez un checkout d'une branche avant de créer une pull request.",
"createFailed": "Échec de la création de la pull request.",
"createPr": "Créer une PR",
"createPullRequest": "Créer une pull request",
"currentBranchNotLinked": "La branche actuelle n'est pas liée à une PR ouverte.",
"linkExistingPr": "Lier une PR existante",
"linkExistingPullRequest": "Lier une pull request existante",
"linkedPrUnavailable": "PR liée #{{number}} indisponible",
"noOpenPullRequest": "Aucune pull request ouverte",
"refreshOrCreate": "Actualisez pour vérifier à nouveau, ou créez une nouvelle PR pour cette branche.",
"refreshPullRequest": "Actualiser la pull request",
"title": "Pull request"
},
"prSidebarHeader": {
"cancelEditingTitle": "Annuler la modification du titre",
"editTitle": "Modifier le titre de la pull request",
"openInBrowser": "Ouvrir la pull request #{{number}} dans le navigateur",
"openOnWeb": "Ouvrir la pull request #{{number}} sur le Web",
"saveTitle": "Enregistrer le titre"
},
"prViewPanel": {
"branchUnavailable": "Branche actuelle indisponible.",
"providerUnavailable": "Panneau de revue hébergée indisponible pour ce fournisseur."
},
"reviewerPickerDrawer": {
"loadFailed": "Échec du chargement des personnes",
"noMatches": "Aucune personne correspondante",
"removeReviewer": "Retirer {{login}}",
"requestReviewer": "Demander une revue à {{login}}",
"searchPeople": "Rechercher des personnes",
"title": "Reviewers"
},
"reviewersSection": {
"addOrRemove": "Ajouter ou retirer des reviewers",
"approved": "A approuvé",
"changesRequested": "Modifications demandées",
"commented": "A commenté",
"dismissed": "Revue révoquée",
"loadFailed": "Impossible de charger les reviewers. Appuyez sur Actualiser pour réessayer.",
"loading": "Chargement des reviewers…",
"noReviewers": "Aucun reviewer sollicité",
"pending": "En attente",
"removeReviewer": "Retirer {{login}}",
"requested": "Revue demandée",
"reviewed": "Revue effectuée",
"title": "Reviewers"
}
},
"settings": {
"aboutScreen": {
"openLinkFailed": "Impossible d'ouvrir le lien. Réessayez.",
+730
View File
@@ -142,9 +142,14 @@
"common": {
"back": "戻る",
"cancel": "キャンセル",
"close": "閉じる",
"confirm": "確認する",
"continue": "続行",
"copied": "コピーしました",
"copy": "コピー",
"delete": "削除",
"done": "完了",
"edit": "編集",
"ok": "OK",
"remove": "削除",
"retry": "再試行",
@@ -152,6 +157,469 @@
"settings": "設定",
"unknown": "不明"
},
"components": {
"accountsSnapshot": {
"invalidSnapshot": "ホストからのアカウントスナップショットが無効です。"
},
"agentLaunchNotice": {
"copyFailed": "コピーできませんでした",
"copyPrompt": "プロンプトをコピー",
"statusUnreadable": "このホストの状態を読み取れませんでした。再試行中…",
"updateRequired": "スマートフォンから Agent を起動するには、コンピュータの Orca を更新してください。"
},
"agentSpinner": {
"monitoring": "バックグラウンドタスクを監視中",
"unconfirmed": "確認できませんでした"
},
"agentStateDot": {
"monitoring": "バックグラウンドタスクを監視中",
"unverifiable": "最近の更新なし"
},
"authFailedBanner": {
"message": "認証に失敗しました。まず再接続してください。失敗が続く場合は、デスクトップから再ペアリングしてください。"
},
"authFailedBannerActions": {
"repair": "再ペアリング",
"webNote": "Orca アプリで、このホストに再接続するか、削除してください。"
},
"codexResetCredit": {
"alreadyAppliedTitle": "リセット適用済み",
"available_one": "リセットが {{count}} 回利用可能",
"available_other": "リセットが {{count}} 回利用可能",
"expiryDaysHoursMultiple": "次は {{days}} 日 {{hours}} 時間後に期限切れ",
"expiryDaysHoursSingle": "{{days}} 日 {{hours}} 時間後に期限切れ",
"expiryDaysMultiple": "次は {{days}} 日後に期限切れ",
"expiryDaysSingle": "{{days}} 日後に期限切れ",
"expiryHoursMinutesMultiple": "次は {{hours}} 時間 {{minutes}} 分後に期限切れ",
"expiryHoursMinutesSingle": "{{hours}} 時間 {{minutes}} 分後に期限切れ",
"expiryHoursMultiple": "次は {{hours}} 時間後に期限切れ",
"expiryHoursSingle": "{{hours}} 時間後に期限切れ",
"expiryMinutesMultiple": "次は {{minutes}} 分後に期限切れ",
"expiryMinutesSingle": "{{minutes}} 分後に期限切れ",
"expiryNowMultiple": "次は今期限切れ",
"expiryNowSingle": "今期限切れ",
"invalidResponse": "ホストからのリセット応答が無効です",
"noResetMessage": "このアカウントには、利用できる獲得済みのリセット枠がありません。",
"noResetTitle": "利用可能なリセットなし",
"nothingToResetMessage": "対象となる Codex の使用量ウィンドウは、いずれもレート制限に達していません。",
"nothingToResetTitle": "リセット対象なし",
"refresh": "Codex の使用量を更新しました。",
"resetTitle": "レート制限をリセットしました"
},
"codexResetCreditAction": {
"activeAccountHint": "アクティブな Codex アカウントに対し、獲得したリセット枠を 1 回使用します",
"earnedReset": "獲得済みの Codex レート制限リセット枠",
"resetting": "リセット中…",
"resettingLabel": "Codex のレート制限をリセット中",
"scopedHint": "{{scopeLabel}} に対し、獲得したリセット枠を 1 回使用します",
"useReset": "リセット枠を使用",
"useResetLabel": "Codex レート制限リセット枠を使用"
},
"customKeyModal": {
"add": "追加",
"addShortcut": "ショートカットを追加",
"command": "コマンド",
"commandPlaceholder": "例:pnpm build",
"editing": "編集",
"function": "ファンクションキー",
"key": "キー",
"label": "ラベル",
"labelPlaceholder": "例:ビルド",
"manageShortcuts": "ショートカットを管理",
"manageShortcutsHint": "ショートカットキーの表示・非表示・並べ替え",
"modifiers": "修飾キー",
"moreKeys": "その他のキー:Tab、矢印キー、F1–F12…",
"navigation": "ナビゲーション",
"pickKey": "キーを選択",
"pressEnter": "Enter を押す",
"shortcutCombo": "キーの組み合わせ",
"shortcutComboHint": "Ctrl、Alt、Shift を使ったキーの組み合わせを作成",
"textMacro": "テキストマクロ",
"textMacroHint": "カスタムのテキストコマンドを送信"
},
"dictationSetupSheet": {
"description": "ここからモデルをダウンロードまたは選択し、ペアリング済みのコンピュータでディクテーションを有効にできます。",
"download": "ダウンロード",
"downloadFailed": "ダウンロードに失敗しました",
"downloadModel": "{{model}} をダウンロード",
"enabled": "ディクテーションを有効にする",
"inUse": "使用中",
"loadFailed": "読み込みに失敗しました",
"modelExtracting": "{{details}} · 展開中…",
"modelProgress": "{{details}} · {{progress}}%",
"modelSize": "{{size}} MB",
"recommended": "推奨",
"selectFailed": "モデルを選択できませんでした",
"title": "音声ディクテーションを設定",
"updateFailed": "更新できませんでした",
"use": "使用",
"useModel": "{{model}} を使用"
},
"diffReviewBody": {
"binaryDiff": "バイナリ差分",
"binaryMessage": "モバイルではこのファイルをテキストで表示できません。",
"deletedFile": "削除済みファイル",
"deletedMessage": "このファイルは削除されました。ファイルにメモを追加するか、レビュー済みにしてください。",
"diffTooLarge": "差分が大きすぎます",
"diffTruncated": "モバイルプレビュー用に差分の一部を省略しています。",
"diffUnavailable": "差分を表示できません",
"loadFailed": "レビューを読み込めません",
"loadingDiff": "差分を読み込み中…",
"loadingReview": "レビューを読み込み中…",
"noReviewableChanges": "レビュー可能な変更がありません",
"retryLabel": "レビューの読み込みを再試行",
"reviewUnavailable": "レビューを利用できません",
"selectFile": "レビューするファイルを選択してください。",
"tooLargeMessage": "この差分は大きすぎるため、モバイルではプレビューできません。",
"tryDifferentFilter": "別のレビューフィルターを試してください。"
},
"diffReviewDrawers": {
"actionsTitle": "レビュー操作",
"addNote": "メモを追加",
"cancelNote": "メモの入力をキャンセル",
"clearSentNotes": "送信済みのメモをクリア",
"completeTitle": "レビュー完了",
"copyNotes": "メモをコピー",
"deleteNote": "メモの削除",
"discard": "破棄",
"discardMessage": "「{{filePath}}」の変更を破棄しますか?この操作は元に戻せません。",
"discardTitle": "ファイルの変更を破棄",
"editNote": "メモを編集",
"fileNote": "ファイルのメモ",
"fileSummary_one": "レビュー済みファイル {{fileCount}} 件、メモ {{count}} 件",
"fileSummary_other": "レビュー済みファイル {{fileCount}} 件、メモ {{count}} 件",
"filesSummary_one": "レビュー済みファイル {{fileCount}} 件、メモ {{count}} 件",
"filesSummary_other": "レビュー済みファイル {{fileCount}} 件、メモ {{count}} 件",
"lineNumber": "{{lineNumber}} 行目",
"loadingSessions": "Agent セッションを読み込み中…",
"markUnreviewed": "未レビューにする",
"newAgentSession": "新規 Agent セッション",
"openInSession": "セッションで開く",
"reviewNote": "レビューメモ",
"saveLineNote": "{{lineNumber}} 行目のメモを保存",
"sendNotes": "メモを送信",
"sendNotesLabel": "Agent にメモを送信",
"sendUnsentNotes": "未送信のメモを送信",
"stageReviewed": "レビュー済みをステージ",
"stageReviewedFiles": "レビュー済みファイルをステージ",
"stageReviewedLabel": "レビュー済みファイルをステージ",
"stageableFiles_one": "レビュー済みの未ステージファイル {{count}} 件をステージできます。",
"stageableFiles_other": "レビュー済みの未ステージファイル {{count}} 件をステージできます。",
"statusUnreadable": "このホストの状態を読み取れませんでした。再試行中…",
"terminal": "ターミナル",
"terminalLabel": "{{title}} ({{id}})",
"unsentNotes_one": "未送信のメモ {{count}} 件",
"unsentNotes_other": "未送信のメモ {{count}} 件",
"updateRequired": "コンピュータの Orca を更新してください。"
},
"diffReviewFileSummary": {
"branch": "ブランチ",
"branchFrom": "ブランチ(変更元: {{oldPath}})",
"changed": "変更あり",
"editFileNote": "ファイルのメモを編集",
"hunk": "ハンク",
"nextHunk": "次のハンク",
"notes_one": "メモ {{count}} 件",
"notes_other": "メモ {{count}} 件",
"previousHunk": "前のハンク",
"reviewed": "レビュー済み",
"staged": "ステージ済み",
"stagedFrom": "ステージ済み(変更元: {{oldPath}})",
"stale": "古いメモ",
"staleCount": "古いメモ {{count}} 件",
"unstaged": "未ステージ",
"unstagedFrom": "未ステージ(変更元: {{oldPath}})"
},
"diffReviewFooter": {
"addFileNote": "ファイルにメモを追加",
"discard": "破棄",
"discardFile": "ファイルの変更を破棄",
"markFileReviewed": "ファイルをレビュー済みにする",
"markReviewed": "レビュー済みにする",
"nextFile": "次のファイル",
"note": "メモ",
"previousFile": "前のファイル",
"reviewed": "レビュー済み",
"stage": "ステージ",
"stageFile": "ファイルをステージ",
"unstage": "アンステージ",
"unstageFile": "ファイルをアンステージ"
},
"diffReviewHeader": {
"filterAll": "全て",
"filterBranch": "ブランチ",
"filterNotes": "メモ",
"filterStaged": "ステージ済み",
"filterUnreviewed": "未レビュー",
"filterUnstaged": "未ステージ",
"openPullRequestSidebar": "PR サイドバーを開く",
"openReviewActions": "レビュー操作を開く",
"reviewedProgress": "{{reviewedCount}}/{{queueLength}} レビュー済み",
"showAllFiles": "レビュー対象の全ファイルを表示",
"showBranchFiles": "ブランチのレビュー対象ファイルを表示",
"showNotesFiles": "メモのあるレビュー対象ファイルを表示",
"showStagedFiles": "ステージ済みのレビュー対象ファイルを表示",
"showUnreviewedFiles": "未レビューのファイルを表示",
"showUnstagedFiles": "未ステージのレビュー対象ファイルを表示",
"title": "変更点",
"unsentNotes_one": "未送信のメモ {{count}} 件",
"unsentNotes_other": "未送信のメモ {{count}} 件"
},
"diffReviewLine": {
"addNoteOnLine": "{{number}} 行目にメモを追加",
"addedLine": "追加された行",
"addedLineNumber": "追加された行({{number}} 行目)",
"contextLine": "変更のない行",
"contextLineNumber": "変更のない行({{number}} 行目)",
"deletedLine": "削除された行",
"deletedLineNumber": "削除された行({{number}} 行目)",
"editNoteOnLine": "{{number}} 行目のメモを編集"
},
"dragReorderRow": {
"dragToReorder": "ドラッグして並べ替え",
"moveDown": "下に移動",
"moveUp": "上に移動",
"reorderHint": "「上に移動」「下に移動」の操作で、ドラッグせずに並べ替えられます。"
},
"homeQuickActions": {
"newWorkspace": "新規ワークスペース",
"pairDesktop": "デスクトップのペアリング",
"title": "クイック操作"
},
"hostCard": {
"hostActions": "「{{title}}」の操作",
"openHost": "「{{title}}」を開く",
"pairingInvalid": "ペアリングが無効",
"pairingTemporarilyUnavailable": "ペアリングが一時的に利用できません",
"repairHint": "タップしてデスクトップと再ペアリングしてください。",
"unlockHint": "スマートフォンのロックを解除してから、タップして再試行してください。",
"updateDesktopHint": "デスクトップの Orca を更新してサインインすると、どこからでも接続できます。",
"worktreeAccessibilitySummary": "{{summary}}、{{activeCount}} 件がアクティブ"
},
"hostDiagnosticsLink": {
"viewDiagnostics": "ネットワーク診断を表示"
},
"hostProtocolGate": {
"checkingCompatibility": "ホストの互換性を確認中"
},
"hostRouteNoticeBanner": {
"dismissNotice": "通知を閉じる"
},
"htmlPreview": {
"preview": "プレビュー",
"previewRenderedHtml": "レンダリングした HTML をプレビュー",
"source": "ソース",
"title": "HTMLプレビュー",
"viewHtmlSource": "HTML ソースを表示"
},
"jiraConnectPrompt": {
"checkAgain": "再確認",
"connectHint": "デスクトップの Orca で「設定 → 連携」から Jira サイトを追加し、再確認してください。",
"connectOnDesktop": "デスクトップで Jira に接続",
"credentialUnreadable": "Jira の認証情報は保存されていますが、読み取れません:{{credentialError}}",
"updateDesktop": "デスクトップの Orca を更新"
},
"markdownBlocks": {
"copyCode": "コードをコピーする",
"openImage": "画像を開く"
},
"mountedBottomDrawer": {
"dismissDrawer": "ドロワーを閉じる"
},
"newWorkspaceFab": {
"newWorkspace": "新規ワークスペース"
},
"newWorkspaceSetupScriptField": {
"hooks": "フック",
"label": "セットアップスクリプト",
"run": "実行",
"runSetupCommand": "セットアップコマンドを実行",
"skip": "スキップ"
},
"newWorkspaceSshConnectionField": {
"connect": "接続",
"connecting": "接続中…",
"label": "SSH 接続",
"serverRecovery": "このサーバーはデスクトップでの復旧が必要です。「設定」→「SSH ホスト」→「サーバーを復旧」を選択してください。"
},
"newWorktreeAgentSelection": {
"blankTerminal": "ブランクターミナル"
},
"newWorktreeFormSheet": {
"advanced": "詳細設定",
"agent": "Agent",
"connectTarget": "接続先に接続",
"connectTargetFirst": "先に接続先へ接続",
"createWorktree": "ワークツリーを作成する",
"nameOrCreateFrom": "名前または「作成元」",
"noProjectsFound": "プロジェクトが見つかりません",
"note": "注記",
"notePlaceholder": "メモを書く",
"workspaceName": "ワークスペース名"
},
"newWorktreeModal": {
"remoteRepository": "リモートリポジトリ"
},
"newWorktreeModalDrawers": {
"agent": "Agent",
"project": "プロジェクト",
"runOn": "実行先"
},
"newWorktreeProjectTargetFields": {
"project": "プロジェクト",
"runOn": "実行先",
"selectProject": "プロジェクトを選択",
"selectRunTarget": "実行先を選択"
},
"prSidebar": {
"loadFailed": "問題が発生しました。",
"loading": "PR を読み込み中…",
"notPermitted": "GitHub アカウントが接続されていないため、アクセスできません。",
"retryLoading": "PR の読み込みを再試行"
},
"protocolBlockScreen": {
"backToHosts": "ホストに戻る",
"desktopTooOldBody": "ペアリング済みのデスクトップアプリは、現在の Orca Mobile に対応していない古いバージョンです。コンピュータの Orca を更新して、このホストへの接続を再試行してください。",
"getVersion": "Orca {{version}} を入手",
"mobileTooOldAppStore": "このデスクトップには、より新しいバージョンの Orca Mobile が必要です。App Store から Orca Mobile を更新して、このホストへの接続を再試行してください。",
"mobileTooOldGitHubReleases": "このデスクトップには、より新しいバージョンの Orca Mobile が必要です。GitHub Releases から Orca Mobile を更新して、このホストへの接続を再試行してください。",
"openAppStore": "App Store を開く",
"openGitHubReleases": "GitHub Releases を開く",
"refreshRecoveryNote": "このメッセージが表示され続ける場合は、このホストを削除して、再度ペアリングしてください。",
"refreshWorkspaceBody": "このホスト用にキャッシュされたワークスペースは、デスクトップが必要とするバージョンより古くなっています。このホストに再接続して、最新のワークスペースをダウンロードしてください。",
"refreshWorkspaceTitle": "モバイルワークスペースを更新",
"updateDesktopTitle": "コンピュータの Orca を更新",
"updateMobileTitle": "Orca Mobile を更新",
"updateRecoveryNote": "すでに更新済みですか?「ホスト」に戻り、接続を更新してください。このメッセージが表示され続ける場合は、このホストを削除して、再度ペアリングしてください。",
"workspaceNeedsUpdateAppStore": "このデスクトップのモバイルワークスペースには、より新しいバージョンの Orca Mobile が必要です。App Store から Orca Mobile を更新して、このホストへの接続を再試行してください。",
"workspaceNeedsUpdateGitHubReleases": "このデスクトップのモバイルワークスペースには、より新しいバージョンの Orca Mobile が必要です。GitHub Releases から Orca Mobile を更新して、このホストへの接続を再試行してください。"
},
"richMarkdownEditor": {
"imageUrl": "画像 URL",
"insert": "入れる",
"linkUrl": "リンクURL"
},
"richMarkdownToolbar": {
"body": "本文",
"bold": "太字",
"bulletList": "箇条書きリスト",
"checklist": "チェックリスト",
"codeBlock": "コードブロック",
"heading1": "見出し 1",
"heading2": "見出し 2",
"heading3": "見出し 3",
"image": "画像",
"inlineCode": "インラインコード",
"italic": "イタリック",
"link": "リンク",
"numberedList": "番号付きリスト",
"quote": "引用",
"strike": "取り消し線"
},
"searchField": {
"clearSearch": "検索をクリア"
},
"setupHookTrustDrawer": {
"alwaysTrustAndRun": "常に信頼して実行",
"description": "このリポジトリの orca.yaml は、ワークスペースの起動前に実行されます。このリポジトリを信頼できる場合にのみ実行してください。",
"dontRun": "実行しない",
"newScriptLabel": "新しいセットアップスクリプト",
"runHooks": "フックを実行する",
"runSetupTitle": "{{repoName}} のセットアップを実行しますか?",
"scriptChangedTitle": "{{repoName}} のセットアップスクリプトが変更されました",
"scriptLabel": "セットアップスクリプト"
},
"smartWorkspaceAdvancedFields": {
"branchName": "ブランチ名",
"derivedFromName": "名前から生成",
"name": "名前",
"reuseBranch": "ブランチ「{{branch}}」を再利用",
"workspaceName": "ワークスペース名"
},
"smartWorkspaceSourceDrawer": {
"connectRepository": "作成元を検索するには、リポジトリを接続してください。",
"crossRepoMessage": "この項目は {{owner}}/{{repo}} にあります。",
"gitHubRemoteRequired": "この SSH リポジトリで Issue と PR を一覧表示するには、GitHub リモートが必要です。",
"noResults": "検索結果が見つかりませんでした。",
"searchPlaceholder": "名前を入力、または作成元を検索",
"switchRepo": "{{repoName}} に切り替え",
"title": "名前または「作成元」",
"typeWorkspaceName": "下のフィールドにワークスペース名を入力してください。"
},
"smartWorkspaceSourceField": {
"openSelectedSource": "選択した作成元を開く",
"optional": "[オプション]",
"searchPlaceholder": "名前を入力、または作成元を検索"
},
"smartWorkspaceSourceRow": {
"createBranch": "ブランチ「{{name}}」を作成",
"issue": "Issue #{{number}}",
"mergeRequest": "MR !{{number}}",
"nameWorkspace": "このワークスペースに名前を付ける",
"newBranch": "新しいブランチ",
"pullRequest": "PR #{{number}}",
"useName": "「{{name}}」を使用"
},
"sourceCopyButton": {
"clipboardRejected": "クリップボードがテキストを受け付けませんでした。",
"copiedLoaded": "読み込み分をコピーしました",
"copyFailed": "コピーに失敗しました",
"copyLoaded": "読み込み分をコピー",
"couldNotCopy": "コピーできませんでした",
"failed": "失敗"
},
"terminalShortcutSettings": {
"addDescription": "キーの組み合わせやテキストマクロを作成",
"addShortcut": "カスタムショートカットを追加…",
"customShortcuts": "カスタムショートカット",
"empty": "カスタムショートカットはまだ設定されていません。",
"removeShortcut": "{{label}} を削除",
"resetDefaults": "初期設定に戻す",
"resetDescription": "すべての組み込みショートカットキーを元の順序で表示",
"shortcutBar": "ショートカットバー",
"shortcutBarDescription": "キーの表示・非表示を切り替えられます。ドラッグハンドルを長押ししてキーをドラッグすると、ターミナルのショートカットバーのキーを好みの順序に並べ替えられます。"
},
"useCodexResetCreditAction": {
"confirmTitle": "レート制限のリセット枠を使用しますか?",
"detailsChanged": "リセット内容が変更されました",
"detailsChangedMessage": "ホストが Codex に接続する前に、アカウントまたはリセットの提供条件が変更されました。更新された内容を確認してから、もう一度確定してください。",
"hostAccount": "ホスト上の {{identity}}",
"hostAccountConfirmMessage": "ホスト上の {{identity}} の獲得済みリセット枠を 1 回分使用し、対象の使用量ウィンドウを直ちにリセットします。",
"hostSelectedAccount": "ホスト上で選択されている管理アカウント",
"hostSelectedAccountConfirmMessage": "ホスト上で選択されている管理アカウントの獲得済みリセット枠を 1 回分使用し、対象の使用量ウィンドウを直ちにリセットします。",
"resetFailed": "レート制限をリセットできませんでした",
"wslAccount": "WSL {{distro}} 上の {{identity}}",
"wslAccountConfirmMessage": "WSL {{distro}} 上の {{identity}} の獲得済みリセット枠を 1 回分使用し、対象の使用量ウィンドウを直ちにリセットします。",
"wslSelectedAccount": "WSL {{distro}} 上で選択されている管理アカウント",
"wslSelectedAccountConfirmMessage": "WSL {{distro}} 上で選択されている管理アカウントの獲得済みリセット枠を 1 回分使用し、対象の使用量ウィンドウを直ちにリセットします。"
},
"voiceModelList": {
"deleteModel": "{{label}} を削除",
"downloadModel": "{{label}} をダウンロード",
"inUse": "使用中",
"modelExtracting": "{{size}} · 展開中…",
"modelProgress": "{{size}} · {{progress}}%",
"modelSize": "{{size}} MB",
"recommended": "推奨",
"use": "使用",
"useModel": "{{label}} を使用"
},
"workspaceDetailPlaceholder": {
"description": "サイドバーからワークスペースを選択すると、そのターミナルがここに開きます。",
"title": "開いているワークスペースはありません"
},
"workspaceNameInput": {
"placeholder": "ワークスペース名"
},
"worktreeListRow": {
"child": "子ワークスペース",
"children_one": "{{count}} 件の子ワークスペース",
"children_other": "{{count}} 件の子ワークスペース",
"folder": "フォルダー",
"project": "プロジェクト {{repo}}",
"remoteHost": "リモートホスト"
}
},
"connection": {
"connectionHealth": {
"checkConnection": "接続を確認してください。",
@@ -567,6 +1035,268 @@
"useChatUi": "チャット UI を使用"
}
},
"pullRequest": {
"checksSection": {
"actionRequired": "操作が必要です",
"cancelled": "キャンセル済み",
"checkDetails": "{{name}} のチェックの詳細",
"failed": "失敗",
"failing": "失敗 {{count}} 件",
"failingChecks_one": "失敗したチェック {{count}} 件",
"failingChecks_other": "失敗したチェック {{count}} 件",
"fix": "修正",
"fixFailing": "失敗したチェックを AI で修正",
"inProgress": "進行中",
"inspectOrFix": "詳細を確認するか、AI に修正を依頼してください。",
"loadDetailsFailed": "チェックの詳細を読み込めませんでした。",
"neutral": "中立",
"neutralCount": "中立 {{count}} 件",
"noChecks": "チェックなし",
"openOnWeb": "{{name}} をウェブで開く",
"passed": "成功 {{count}} 件",
"pending": "保留中",
"pendingCount": "保留中 {{count}} 件",
"queued": "キュー待ち",
"rerunFailing": "失敗したチェックを再実行",
"skipped": "スキップ済み",
"successful": "成功",
"timedOut": "タイムアウト",
"title": "チェック",
"unavailable": "チェックは利用できません"
},
"commentCard": {
"daysAgo_one": "{{count}} 日前",
"daysAgo_other": "{{count}} 日前",
"deleteComment": "コメントを削除",
"deleteConfirmMessage": "GitHub 上のコメントを完全に削除します。",
"deleteConfirmTitle": "コメントを削除しますか?",
"editComment": "コメントを編集",
"editPlaceholder": "コメントを編集…",
"hoursAgo_one": "{{count}} 時間前",
"hoursAgo_other": "{{count}} 時間前",
"justNow": "たった今",
"minutesAgo_one": "{{count}} 分前",
"minutesAgo_other": "{{count}} 分前",
"monthsAgo_one": "{{count}} か月前",
"monthsAgo_other": "{{count}} か月前",
"openOnGitHub": "GitHub でコメントを開く",
"reply": "返事",
"replyPlaceholder": "返信を入力…",
"replyToComment": "コメントに返信",
"resolve": "解決",
"resolveThread": "スレッドを解決",
"resolved": "解決済み",
"unresolve": "未解決に戻す",
"unresolveThread": "スレッドを未解決に戻す",
"yearsAgo_one": "{{count}} 年前",
"yearsAgo_other": "{{count}} 年前"
},
"commentsSection": {
"addCommentPlaceholder": "コメントを追加…",
"all": "全て",
"bots": "ボット",
"comment": "コメント",
"commentsLoadFailed": "コメントを読み込めませんでした。「更新」をタップして再試行してください。",
"description": "説明",
"descriptionLoadFailed": "説明を読み込めませんでした。「更新」をタップして再試行してください。",
"humans": "人間",
"noBotComments": "ボットのコメントはありません。",
"noComments": "コメントはまだありません。",
"noDescription": "説明はありません。",
"noHumanComments": "ユーザーによるコメントはありません。",
"resolvedComment_other": "{{author}} の解決済みコメント({{count}} 件)",
"resolvedThread_other": "{{author}} の解決済みスレッド({{count}} 件)",
"showMore": "さらに {{count}} 件を表示",
"showMoreOf": "残り {{remaining}} 件のうち {{count}} 件を表示",
"title": "コメント"
},
"conflictingFilesSection": {
"commitsBehind_one": "{{count}} コミット遅れ(ベースコミット: {{baseCommit}})",
"commitsBehind_other": "{{count}} コミット遅れ(ベースコミット: {{baseCommit}})",
"copyCommands": "コマンドをコピー",
"copyFailed": "テキストのコピーに失敗しました",
"copyRefreshCommands": "マージ可否を再取得するコマンドをコピー",
"detailsUnavailable": "競合ファイルの詳細は利用できません",
"filesHeading": "競合するファイル",
"localMergeClean": "GitHub では競合が報告されていますが、ローカルの Git では再現されませんでした。PR を更新するか、ブランチをプッシュしてマージ可否を再計算してください。",
"mustResolve": "このブランチには解決する必要のある競合があります",
"refreshing": "競合の詳細を更新しています…",
"resolveWithAi": "AI で競合を解決",
"runFromWorktree": "このワークツリーから実行",
"title": "競合"
},
"linkPrForm": {
"linkButton": "PR をリンク",
"linkNumber": "#{{number}} をリンク",
"referenceLabel": "PR 番号または GitHub URL",
"referencePlaceholder": "#123 または https://github.com/owner/repo/pull/123",
"title": "既存の PR をリンク"
},
"markdownBlocks": {
"details": "詳細"
},
"prActionsSection": {
"autoMergeAccessibilityLabel": "自動マージを切り替え",
"autoMergeLabel": "準備が整ったら自動マージ",
"autoMergeOff": "オフ",
"autoMergeOn": "オン",
"closeAccessibilityLabel": "PR を閉じる",
"closeConfirmMessage": "#{{number}} をマージせずに閉じます。",
"closeConfirmTitle": "PR を閉じますか?",
"markReadyAccessibilityLabel": "PR をレビュー可能にする",
"markReadyButton": "レビュー可能にする",
"mergeAccessibilityLabel": "PR をマージ",
"mergeButton": "マージ",
"mergeConfirmTitle": "PR をマージしますか?",
"recheckAccessibilityLabel": "マージ状態を再確認",
"recheckButton": "再確認",
"reopenAccessibilityLabel": "PR を再オープン",
"reopenButton": "再オープン",
"reopenConfirmMessage": "#{{number}} を再オープンします。",
"reopenConfirmTitle": "PR を再オープンしますか?",
"unlinkAccessibilityLabel": "PR のリンクを解除",
"unlinkButton": "リンクを解除",
"unlinkFailed": "PR のリンク解除に失敗しました。"
},
"prActionsState": {
"behindHint": "マージする前にブランチを更新する",
"behindLabel": "遅れ",
"blockedHint": "GitHub がこの PR がブロックされていると報告しています",
"blockedLabel": "ブロック中",
"changesRequestedHint": "GitHub によると、この PR では変更が要求されています",
"changesRequestedLabel": "変更要求",
"checkingHint": "GitHub はまだこの PR のマージステータスを計算中です",
"checkingLabel": "確認中",
"checksPassedHint": "チェックは通過しました。マージ可否はマージ前に再確認されます。",
"checksPassedLabel": "チェックに合格しました",
"closedHint": "この PR は終了しました",
"closedLabel": "クローズ",
"conflictsHint": "GitHub がマージ競合を報告",
"conflictsLabel": "競合",
"draftHint": "この PR はまだドラフトです",
"draftLabel": "下書き",
"mergeConfirmMessage": "#{{number}} をベースブランチにマージします。",
"mergeQueueAutoMergeOnHint": "このベースブランチは GitHub マージキューを使用します",
"mergeQueueAutoMergeOnLabel": "自動マージ有効",
"mergeQueueHint": "このベースブランチは GitHub マージキューを使用します",
"mergeQueueLabel": "準備ができたらマージ",
"mergeStackMessage": "GitHub は、この PR と、同じスタック内でその下にあるすべての PR をマージします。",
"mergeStatusUnavailableHint": "この PR ではマージステータスを利用できません",
"mergeStatusUnavailableLabel": "マージ",
"mergeThrough_one": "#{{number}} までマージ · {{count}} 件の PR",
"mergeThrough_other": "#{{number}} までマージ · {{count}} 件の PR",
"mergeableChecksFailedHint": "GitHub は、この PR はマージできると言っていますが、一部のチェックが失敗しました",
"mergeableChecksFailedLabel": "チェックに失敗しました",
"mergeableChecksPassedHint": "GitHub によると、この PR はマージ可能で、チェックも成功しています",
"mergeableChecksPassedLabel": "マージ可能",
"mergeableChecksPendingHint": "GitHub によると、この PR はマージ可能ですが、チェックはまだ実行中です",
"mergeableChecksPendingLabel": "保留中のチェック",
"mergeableHint": "GitHub によると、この PR はマージ可能です",
"mergeableLabel": "マージ可能",
"mergedHint": "この PR はすでにマージされています",
"mergedLabel": "マージ済み",
"methodMerge": "マージコミットを作成",
"methodRebase": "リベースしてマージ",
"methodSquash": "スカッシュしてマージ",
"queueStackMessage": "GitHub は、この PR とその下のすべての PR をマージキューに追加します。",
"queueThrough_one": "#{{number}} までキューに追加 · {{count}} 件の PR",
"queueThrough_other": "#{{number}} までキューに追加 · {{count}} 件の PR",
"reviewRequiredHint": "この PR をマージするには、GitHub でレビューの承認が必要です",
"reviewRequiredLabel": "承認が必要です",
"stackBehind": "#{{number}} を更新する必要があります。",
"stackBlocked": "#{{number}} はブロックされています。",
"stackChangesRequested": "#{{number}} で変更が要求されています。",
"stackClosed": "#{{number}} はクローズされています。",
"stackConflicts": "#{{number}} にマージの競合があります。",
"stackDraft": "#{{number}} はまだドラフトです。",
"stackReviewRequired": "#{{number}} はまだレビューの承認が必要です。"
},
"prCheckDetail": {
"annotations": "注釈",
"annotationsTruncated": "最初の 20 個の注釈を表示",
"jobsTruncated": "最初の 100 件のジョブを表示しています",
"noDetails": "表示できる詳細がありません。"
},
"prCheckDetailContent": {
"annotation": "注釈",
"failedJobs": "失敗したジョブ",
"jobs": "ジョブ",
"unknown": "不明"
},
"prChecksPresentation": {
"failing_one": "{{count}} 件失敗",
"failing_other": "{{count}} 件失敗",
"neutral_one": "{{count}} 件中立",
"neutral_other": "{{count}} 件中立",
"noChecks": "チェックなし",
"passed_one": "{{count}} 件成功",
"passed_other": "{{count}} 件成功",
"pending_one": "{{count}} 件保留中",
"pending_other": "{{count}} 件保留中",
"stateClosed": "クローズ",
"stateDraft": "下書き",
"stateMerged": "マージ済み",
"stateOpen": "オープン",
"unavailable": "チェックは利用できません"
},
"prCommentAudienceLabels": {
"all": "全て",
"bots": "ボット",
"humans": "人間",
"noBotComments": "ボットのコメントはありません。",
"noComments": "コメントはまだありません。",
"noHumanComments": "ユーザーによるコメントはありません。"
},
"prSidebarCreateEmptyState": {
"branchNotLinked": "{{branch}} はオープンな PR にリンクされていません。",
"branchRequired": "PR を作成する前にブランチをチェックアウトしてください。",
"createFailed": "PR の作成に失敗しました。",
"createPr": "PR を作成",
"createPullRequest": "PR を作成",
"currentBranchNotLinked": "現在のブランチはオープンな PR にリンクされていません。",
"linkExistingPr": "既存の PR をリンク",
"linkExistingPullRequest": "既存の PR をリンク",
"linkedPrUnavailable": "リンク済みの PR #{{number}} は利用できません",
"noOpenPullRequest": "オープンな PR はありません",
"refreshOrCreate": "更新して再確認するか、このブランチの新しい PR を作成してください。",
"refreshPullRequest": "PR を更新",
"title": "PR"
},
"prSidebarHeader": {
"cancelEditingTitle": "タイトルの編集をキャンセル",
"editTitle": "PR のタイトルを編集",
"openInBrowser": "PR #{{number}} をブラウザで開く",
"openOnWeb": "PR #{{number}} をウェブで開く",
"saveTitle": "タイトルを保存"
},
"prViewPanel": {
"branchUnavailable": "現在のブランチを取得できません。",
"providerUnavailable": "このプロバイダーではホスト型レビューパネルを利用できません。"
},
"reviewerPickerDrawer": {
"loadFailed": "ユーザーの読み込みに失敗しました",
"noMatches": "一致するユーザーはいません",
"removeReviewer": "{{login}} をレビュアーから削除",
"requestReviewer": "{{login}} にレビューを依頼",
"searchPeople": "ユーザーを検索",
"title": "レビュアー"
},
"reviewersSection": {
"addOrRemove": "レビュアーを追加・削除",
"approved": "承認された",
"changesRequested": "変更要求",
"commented": "コメント済み",
"dismissed": "却下済み",
"loadFailed": "レビュアーを読み込めませんでした。「更新」をタップして再試行してください。",
"loading": "レビュアーを読み込み中…",
"noReviewers": "レビューの依頼はありません",
"pending": "保留中",
"removeReviewer": "{{login}} を削除",
"requested": "依頼済み",
"reviewed": "レビュー済み",
"title": "レビュアー"
}
},
"settings": {
"aboutScreen": {
"openLinkFailed": "リンクを開けませんでした。再試行してください。",
+730
View File
@@ -142,9 +142,14 @@
"common": {
"back": "뒤로",
"cancel": "취소",
"close": "닫기",
"confirm": "확인",
"continue": "계속",
"copied": "복사됨",
"copy": "복사",
"delete": "삭제",
"done": "완료",
"edit": "편집",
"ok": "확인",
"remove": "제거",
"retry": "다시 시도",
@@ -152,6 +157,469 @@
"settings": "설정",
"unknown": "알 수 없음"
},
"components": {
"accountsSnapshot": {
"invalidSnapshot": "호스트에서 받은 계정 스냅샷이 유효하지 않습니다."
},
"agentLaunchNotice": {
"copyFailed": "복사하지 못함",
"copyPrompt": "프롬프트 복사",
"statusUnreadable": "이 호스트의 상태를 읽을 수 없습니다. 다시 시도 중…",
"updateRequired": "휴대폰에서 에이전트를 시작하려면 컴퓨터의 Orca를 업데이트하세요."
},
"agentSpinner": {
"monitoring": "백그라운드 작업 모니터링 중",
"unconfirmed": "확인하지 못함"
},
"agentStateDot": {
"monitoring": "백그라운드 작업 모니터링 중",
"unverifiable": "최근 업데이트 없음"
},
"authFailedBanner": {
"message": "인증에 실패했습니다. 먼저 다시 연결하고, 계속 실패하면 데스크톱에서 다시 페어링하세요."
},
"authFailedBannerActions": {
"repair": "다시 페어링",
"webNote": "Orca 앱에서 이 호스트에 다시 연결하거나 호스트를 제거하세요."
},
"codexResetCredit": {
"alreadyAppliedTitle": "이미 재설정됨",
"available_one": "재설정 {{count}}회 사용 가능",
"available_other": "재설정 {{count}}회 사용 가능",
"expiryDaysHoursMultiple": "다음 항목이 {{days}}일 {{hours}}시간 후 만료됨",
"expiryDaysHoursSingle": "{{days}}일 {{hours}}시간 후 만료됨",
"expiryDaysMultiple": "다음 항목이 {{days}}일 후 만료됨",
"expiryDaysSingle": "{{days}}일 후 만료됨",
"expiryHoursMinutesMultiple": "다음 항목이 {{hours}}시간 {{minutes}}분 후 만료됨",
"expiryHoursMinutesSingle": "{{hours}}시간 {{minutes}}분 후 만료됨",
"expiryHoursMultiple": "다음 항목이 {{hours}}시간 후 만료됨",
"expiryHoursSingle": "{{hours}}시간 후 만료됨",
"expiryMinutesMultiple": "다음 항목이 {{minutes}}분 후 만료됨",
"expiryMinutesSingle": "{{minutes}}분 후 만료됨",
"expiryNowMultiple": "다음 항목이 지금 만료됨",
"expiryNowSingle": "지금 만료됨",
"invalidResponse": "호스트의 재설정 응답이 올바르지 않습니다.",
"noResetMessage": "이 계정에는 사용 가능한 재설정 크레딧이 적립되어 있지 않습니다.",
"noResetTitle": "사용 가능한 재설정 없음",
"nothingToResetMessage": "재설정 가능한 Codex 기간별 사용량 한도 중 소진된 항목이 없습니다.",
"nothingToResetTitle": "재설정할 항목 없음",
"refresh": "Codex 사용량이 새로고침되었습니다.",
"resetTitle": "사용량 한도 재설정 완료"
},
"codexResetCreditAction": {
"activeAccountHint": "현재 사용 중인 Codex 계정에 획득한 재설정권 1개를 사용합니다.",
"earnedReset": "획득한 Codex 사용 한도 재설정권",
"resetting": "재설정 중…",
"resettingLabel": "Codex 사용 한도 재설정 중",
"scopedHint": "획득한 재설정권 1개를 {{scopeLabel}}에 사용합니다.",
"useReset": "재설정권 사용",
"useResetLabel": "Codex 사용 한도 재설정권 사용"
},
"customKeyModal": {
"add": "추가",
"addShortcut": "단축키 추가",
"command": "명령",
"commandPlaceholder": "예: pnpm build",
"editing": "편집",
"function": "기능 키",
"key": "키",
"label": "레이블",
"labelPlaceholder": "예: 빌드",
"manageShortcuts": "단축키 관리",
"manageShortcutsHint": "단축키 표시, 숨기기 및 순서 변경",
"modifiers": "보조 키",
"moreKeys": "더 많은 키 — Tab, 방향키, F1–F12…",
"navigation": "탐색",
"pickKey": "키 선택",
"pressEnter": "Enter 누르기",
"shortcutCombo": "단축키 조합",
"shortcutComboHint": "Ctrl, Alt, Shift 키 조합 만들기",
"textMacro": "텍스트 매크로",
"textMacroHint": "사용자 지정 텍스트 명령 전송"
},
"dictationSetupSheet": {
"description": "여기서 모델을 다운로드하거나 선택하고 페어링된 컴퓨터의 받아쓰기를 활성화할 수 있습니다.",
"download": "다운로드",
"downloadFailed": "다운로드 실패",
"downloadModel": "{{model}} 다운로드",
"enabled": "받아쓰기 활성화",
"inUse": "사용 중",
"loadFailed": "로드 실패",
"modelExtracting": "{{details}} · 압축 해제 중…",
"modelProgress": "{{details}} · {{progress}}%",
"modelSize": "{{size}} MB",
"recommended": "권장",
"selectFailed": "모델 선택 실패",
"title": "음성 받아쓰기 설정",
"updateFailed": "업데이트 실패",
"use": "사용",
"useModel": "{{model}} 사용"
},
"diffReviewBody": {
"binaryDiff": "바이너리 차이점",
"binaryMessage": "이 파일은 모바일에서 텍스트로 표시할 수 없습니다.",
"deletedFile": "삭제된 파일",
"deletedMessage": "이 파일은 삭제되었습니다. 파일 메모를 추가하거나 리뷰 완료로 표시하세요.",
"diffTooLarge": "차이점이 너무 큼",
"diffTruncated": "모바일 미리보기를 위해 차이점 일부가 생략되었습니다.",
"diffUnavailable": "차이점 표시 불가",
"loadFailed": "리뷰 로드 실패",
"loadingDiff": "차이점 로드 중…",
"loadingReview": "리뷰 로드 중…",
"noReviewableChanges": "리뷰할 변경 사항 없음",
"retryLabel": "리뷰 다시 불러오기",
"reviewUnavailable": "리뷰 사용 불가",
"selectFile": "리뷰할 파일을 선택하세요.",
"tooLargeMessage": "이 차이점은 너무 커서 모바일에서 미리 볼 수 없습니다.",
"tryDifferentFilter": "다른 리뷰 필터를 사용해 보세요."
},
"diffReviewDrawers": {
"actionsTitle": "리뷰 작업",
"addNote": "메모 추가",
"cancelNote": "메모 취소",
"clearSentNotes": "전송한 메모 지우기",
"completeTitle": "리뷰 완료",
"copyNotes": "메모 복사",
"deleteNote": "메모 삭제",
"discard": "버리기",
"discardMessage": "\"{{filePath}}\"의 변경사항을 취소하시겠습니까? 이 작업은 되돌릴 수 없습니다.",
"discardTitle": "파일 변경사항 취소",
"editNote": "메모 편집",
"fileNote": "파일 메모",
"fileSummary_one": "파일 {{fileCount}}개 리뷰 완료, 메모 {{count}}개",
"fileSummary_other": "파일 {{fileCount}}개 리뷰 완료, 메모 {{count}}개",
"filesSummary_one": "파일 {{fileCount}}개 리뷰 완료, 메모 {{count}}개",
"filesSummary_other": "파일 {{fileCount}}개 리뷰 완료, 메모 {{count}}개",
"lineNumber": "{{lineNumber}}행",
"loadingSessions": "에이전트 세션 로드 중…",
"markUnreviewed": "리뷰 안 됨으로 표시",
"newAgentSession": "새 에이전트 세션",
"openInSession": "세션에서 열기",
"reviewNote": "리뷰 메모",
"saveLineNote": "{{lineNumber}}행에 메모 저장",
"sendNotes": "메모 보내기",
"sendNotesLabel": "에이전트에 메모 보내기",
"sendUnsentNotes": "미전송 메모 보내기",
"stageReviewed": "리뷰한 파일 스테이지",
"stageReviewedFiles": "리뷰한 파일 스테이지",
"stageReviewedLabel": "리뷰한 파일 스테이지",
"stageableFiles_one": "리뷰를 마쳤지만 스테이징되지 않은 파일 {{count}}개를 스테이지할 수 있습니다.",
"stageableFiles_other": "리뷰를 마쳤지만 스테이징되지 않은 파일 {{count}}개를 스테이지할 수 있습니다.",
"statusUnreadable": "이 호스트의 상태를 읽지 못했습니다. 다시 시도 중…",
"terminal": "터미널",
"terminalLabel": "{{title}} ({{id}})",
"unsentNotes_one": "미전송 메모 {{count}}개",
"unsentNotes_other": "미전송 메모 {{count}}개",
"updateRequired": "컴퓨터에서 Orca를 업데이트하세요"
},
"diffReviewFileSummary": {
"branch": "브랜치",
"branchFrom": "브랜치 · 이전 경로: {{oldPath}}",
"changed": "변경됨",
"editFileNote": "파일 메모 편집",
"hunk": "변경 블록",
"nextHunk": "다음 변경 블록",
"notes_one": "메모 {{count}}개",
"notes_other": "메모 {{count}}개",
"previousHunk": "이전 변경 블록",
"reviewed": "리뷰 완료",
"staged": "스테이징됨",
"stagedFrom": "스테이징됨 · 이전 경로: {{oldPath}}",
"stale": "오래됨",
"staleCount": "오래된 메모 {{count}}개",
"unstaged": "스테이징 안 됨",
"unstagedFrom": "스테이징 안 됨 · 이전 경로: {{oldPath}}"
},
"diffReviewFooter": {
"addFileNote": "파일 메모 추가",
"discard": "버리기",
"discardFile": "파일 변경사항 취소",
"markFileReviewed": "파일을 리뷰 완료로 표시",
"markReviewed": "리뷰 완료로 표시",
"nextFile": "다음 파일",
"note": "메모",
"previousFile": "이전 파일",
"reviewed": "리뷰 완료",
"stage": "스테이지",
"stageFile": "파일 스테이지",
"unstage": "언스테이지",
"unstageFile": "파일 언스테이지"
},
"diffReviewHeader": {
"filterAll": "모두",
"filterBranch": "브랜치",
"filterNotes": "메모",
"filterStaged": "스테이징됨",
"filterUnreviewed": "리뷰 안 됨",
"filterUnstaged": "스테이징 안 됨",
"openPullRequestSidebar": "PR 사이드바 열기",
"openReviewActions": "리뷰 작업 메뉴 열기",
"reviewedProgress": "{{reviewedCount}}/{{queueLength}} 리뷰 완료",
"showAllFiles": "모든 리뷰 대상 파일 표시",
"showBranchFiles": "브랜치의 리뷰 대상 파일 표시",
"showNotesFiles": "메모가 있는 리뷰 대상 파일 표시",
"showStagedFiles": "스테이징된 리뷰 대상 파일 표시",
"showUnreviewedFiles": "리뷰하지 않은 파일 표시",
"showUnstagedFiles": "스테이징되지 않은 리뷰 대상 파일 표시",
"title": "변경 사항",
"unsentNotes_one": "미전송 메모 {{count}}개",
"unsentNotes_other": "미전송 메모 {{count}}개"
},
"diffReviewLine": {
"addNoteOnLine": "{{number}}번 줄에 메모 추가",
"addedLine": "추가된 줄",
"addedLineNumber": "추가된 {{number}}번 줄",
"contextLine": "변경 없는 줄",
"contextLineNumber": "변경 없는 {{number}}번 줄",
"deletedLine": "삭제된 줄",
"deletedLineNumber": "삭제된 {{number}}번 줄",
"editNoteOnLine": "{{number}}번 줄의 메모 편집"
},
"dragReorderRow": {
"dragToReorder": "드래그하여 순서 변경",
"moveDown": "아래로 이동",
"moveUp": "위로 이동",
"reorderHint": "드래그하지 않고 순서를 바꾸려면 '위로 이동'과 '아래로 이동' 동작을 사용하세요."
},
"homeQuickActions": {
"newWorkspace": "새 워크스페이스",
"pairDesktop": "데스크탑 페어링",
"title": "빠른 작업"
},
"hostCard": {
"hostActions": "{{title}} 관련 작업",
"openHost": "{{title}} 열기",
"pairingInvalid": "페어링이 유효하지 않음",
"pairingTemporarilyUnavailable": "페어링 일시 사용 불가",
"repairHint": "탭하여 데스크톱과 다시 페어링하세요.",
"unlockHint": "휴대폰 잠금을 해제한 후 탭하여 다시 시도하세요.",
"updateDesktopHint": "어디서나 연결하려면 데스크톱 Orca를 업데이트하고 로그인하세요.",
"worktreeAccessibilitySummary": "{{summary}}, 활성 {{activeCount}}개"
},
"hostDiagnosticsLink": {
"viewDiagnostics": "네트워크 진단 보기"
},
"hostProtocolGate": {
"checkingCompatibility": "호스트 호환성 확인 중"
},
"hostRouteNoticeBanner": {
"dismissNotice": "알림 닫기"
},
"htmlPreview": {
"preview": "미리보기",
"previewRenderedHtml": "렌더링된 HTML 미리보기",
"source": "소스",
"title": "HTML 미리보기",
"viewHtmlSource": "HTML 소스 보기"
},
"jiraConnectPrompt": {
"checkAgain": "다시 확인",
"connectHint": "Orca 데스크톱의 설정 → 연동에서 Jira 사이트를 추가한 뒤 다시 확인하세요.",
"connectOnDesktop": "데스크톱에서 Jira 연결",
"credentialUnreadable": "Jira 인증 정보가 저장되어 있지만 읽을 수 없습니다: {{credentialError}}",
"updateDesktop": "Orca 데스크톱 업데이트"
},
"markdownBlocks": {
"copyCode": "코드 복사",
"openImage": "이미지 열기"
},
"mountedBottomDrawer": {
"dismissDrawer": "하단 패널 닫기"
},
"newWorkspaceFab": {
"newWorkspace": "새로운 워크스페이스"
},
"newWorkspaceSetupScriptField": {
"hooks": "훅",
"label": "설정 스크립트",
"run": "실행",
"runSetupCommand": "설정 명령 실행",
"skip": "건너뛰기"
},
"newWorkspaceSshConnectionField": {
"connect": "연결",
"connecting": "연결 중…",
"label": "SSH 연결",
"serverRecovery": "데스크톱의 설정 → SSH 호스트 → 서버 복구에서 서버를 복구해야 합니다."
},
"newWorktreeAgentSelection": {
"blankTerminal": "빈 Terminal"
},
"newWorktreeFormSheet": {
"advanced": "고급",
"agent": "에이전트",
"connectTarget": "대상 연결",
"connectTargetFirst": "먼저 대상에 연결하세요",
"createWorktree": "작업 트리 만들기",
"nameOrCreateFrom": "이름 또는 '다음에서 만들기'",
"noProjectsFound": "프로젝트를 찾을 수 없습니다.",
"note": "메모",
"notePlaceholder": "메모 쓰기",
"workspaceName": "워크스페이스 이름"
},
"newWorktreeModal": {
"remoteRepository": "원격 저장소"
},
"newWorktreeModalDrawers": {
"agent": "에이전트",
"project": "프로젝트",
"runOn": "실행 위치"
},
"newWorktreeProjectTargetFields": {
"project": "프로젝트",
"runOn": "실행 위치",
"selectProject": "프로젝트 선택",
"selectRunTarget": "실행 대상 선택"
},
"prSidebar": {
"loadFailed": "문제가 발생했습니다.",
"loading": "PR 로드 중…",
"notPermitted": "GitHub 계정이 연결되지 않아 접근이 허용되지 않습니다.",
"retryLoading": "PR 로드 다시 시도"
},
"protocolBlockScreen": {
"backToHosts": "호스트로 돌아가기",
"desktopTooOldBody": "페어링된 데스크톱 앱의 버전이 오래되어 현재 Orca 모바일 앱과 호환되지 않습니다. 컴퓨터에서 Orca를 업데이트한 후 이 호스트에 다시 연결하세요.",
"getVersion": "Orca {{version}} 받기",
"mobileTooOldAppStore": "이 데스크톱에는 더 최신 버전의 Orca 모바일 앱이 필요합니다. App Store에서 Orca 모바일을 업데이트한 후 이 호스트에 다시 연결하세요.",
"mobileTooOldGitHubReleases": "이 데스크톱에는 더 최신 버전의 Orca 모바일 앱이 필요합니다. GitHub Releases에서 Orca 모바일을 업데이트한 후 이 호스트에 다시 연결하세요.",
"openAppStore": "App Store 열기",
"openGitHubReleases": "GitHub Releases 열기",
"refreshRecoveryNote": "이 메시지가 계속 표시되면 이 호스트를 제거한 후 다시 페어링하세요.",
"refreshWorkspaceBody": "이 호스트용으로 캐시된 워크스페이스가 데스크톱에서 요구하는 버전보다 오래되었습니다. 이 호스트에 다시 연결하여 최신 버전을 다운로드하세요.",
"refreshWorkspaceTitle": "모바일 워크스페이스 새로고침",
"updateDesktopTitle": "컴퓨터의 Orca 업데이트",
"updateMobileTitle": "Orca 모바일 업데이트",
"updateRecoveryNote": "이미 업데이트하셨습니까? 호스트 화면으로 돌아가 연결을 새로고침하세요. 이 메시지가 계속 표시되면 이 호스트를 제거한 후 다시 페어링하세요.",
"workspaceNeedsUpdateAppStore": "이 데스크톱의 모바일 워크스페이스에는 더 최신 버전의 Orca 모바일 앱이 필요합니다. App Store에서 Orca 모바일을 업데이트한 후 이 호스트에 다시 연결하세요.",
"workspaceNeedsUpdateGitHubReleases": "이 데스크톱의 모바일 워크스페이스에는 더 최신 버전의 Orca 모바일 앱이 필요합니다. GitHub Releases에서 Orca 모바일을 업데이트한 후 이 호스트에 다시 연결하세요."
},
"richMarkdownEditor": {
"imageUrl": "이미지 URL",
"insert": "삽입",
"linkUrl": "링크 URL"
},
"richMarkdownToolbar": {
"body": "본문",
"bold": "굵게",
"bulletList": "글머리 기호 목록",
"checklist": "체크리스트",
"codeBlock": "코드 블록",
"heading1": "제목 1",
"heading2": "제목 2",
"heading3": "제목 3",
"image": "이미지",
"inlineCode": "인라인 코드",
"italic": "이탤릭체",
"link": "링크",
"numberedList": "번호가 매겨진 목록",
"quote": "인용",
"strike": "취소선"
},
"searchField": {
"clearSearch": "검색 지우기"
},
"setupHookTrustDrawer": {
"alwaysTrustAndRun": "항상 신뢰하고 실행",
"description": "워크스페이스가 시작되기 전에 이 저장소의 orca.yaml이 실행됩니다. 이 저장소를 신뢰하는 경우에만 실행하세요.",
"dontRun": "실행 안 함",
"newScriptLabel": "새 설정 스크립트",
"runHooks": "후크 실행",
"runSetupTitle": "{{repoName}}의 설정 스크립트를 실행하시겠습니까?",
"scriptChangedTitle": "{{repoName}}의 설정 스크립트가 변경됨",
"scriptLabel": "설정 스크립트"
},
"smartWorkspaceAdvancedFields": {
"branchName": "브랜치 이름",
"derivedFromName": "이름을 바탕으로 생성",
"name": "이름",
"reuseBranch": "“{{branch}}” 브랜치 재사용",
"workspaceName": "워크스페이스 이름"
},
"smartWorkspaceSourceDrawer": {
"connectRepository": "소스를 검색하려면 저장소를 연결하세요.",
"crossRepoMessage": "이 항목은 {{owner}}/{{repo}}에 있습니다.",
"gitHubRemoteRequired": "이 SSH 저장소에서 이슈와 PR 목록을 보려면 GitHub 원격 저장소가 필요합니다.",
"noResults": "검색 결과가 없습니다.",
"searchPlaceholder": "이름 입력 또는 소스 검색",
"switchRepo": "{{repoName}}(으)로 전환",
"title": "이름 또는 '다음에서 만들기'",
"typeWorkspaceName": "아래 입력란에 워크스페이스 이름을 입력하세요."
},
"smartWorkspaceSourceField": {
"openSelectedSource": "선택한 소스 열기",
"optional": "[선택 사항]",
"searchPlaceholder": "이름 입력 또는 소스 검색"
},
"smartWorkspaceSourceRow": {
"createBranch": "\"{{name}}\" 브랜치 생성",
"issue": "이슈 #{{number}}",
"mergeRequest": "MR !{{number}}",
"nameWorkspace": "워크스페이스 이름 지정",
"newBranch": "새 브랜치",
"pullRequest": "PR #{{number}}",
"useName": "\"{{name}}\" 사용"
},
"sourceCopyButton": {
"clipboardRejected": "클립보드가 텍스트 복사를 거부했습니다.",
"copiedLoaded": "불러온 내용 복사됨",
"copyFailed": "복사 실패",
"copyLoaded": "불러온 내용 복사",
"couldNotCopy": "복사할 수 없습니다.",
"failed": "실패"
},
"terminalShortcutSettings": {
"addDescription": "키 조합 또는 텍스트 매크로 만들기",
"addShortcut": "맞춤 단축키 추가…",
"customShortcuts": "맞춤 단축키",
"empty": "아직 설정된 맞춤 단축키가 없습니다.",
"removeShortcut": "{{label}} 제거",
"resetDefaults": "기본값 복원",
"resetDescription": "모든 기본 단축키를 원래 순서로 표시",
"shortcutBar": "단축키 바",
"shortcutBarDescription": "키를 켜거나 꺼서 표시 여부를 설정하고, 손잡이를 누른 채 드래그하여 터미널 단축키 바의 키를 원하는 순서로 배치하세요."
},
"useCodexResetCreditAction": {
"confirmTitle": "사용량 한도를 재설정하시겠습니까?",
"detailsChanged": "재설정 세부 정보 변경됨",
"detailsChangedMessage": "호스트가 Codex에 연결하기 전에 계정 또는 재설정 제공 조건이 변경되었습니다. 변경된 세부 정보를 검토한 후 다시 확인하세요.",
"hostAccount": "호스트의 {{identity}}",
"hostAccountConfirmMessage": "호스트의 {{identity}} 계정에 적립된 재설정 크레딧 1회를 사용해 재설정 가능한 기간별 사용량 한도를 즉시 초기화합니다.",
"hostSelectedAccount": "호스트에서 선택한 관리 계정",
"hostSelectedAccountConfirmMessage": "호스트에서 선택한 관리 계정에 적립된 재설정 크레딧 1회를 사용해 재설정 가능한 기간별 사용량 한도를 즉시 초기화합니다.",
"resetFailed": "사용량 한도 재설정 실패",
"wslAccount": "WSL {{distro}}의 {{identity}}",
"wslAccountConfirmMessage": "WSL {{distro}}의 {{identity}} 계정에 적립된 재설정 크레딧 1회를 사용해 재설정 가능한 기간별 사용량 한도를 즉시 초기화합니다.",
"wslSelectedAccount": "WSL {{distro}}에서 선택한 관리 계정",
"wslSelectedAccountConfirmMessage": "WSL {{distro}}에서 선택한 관리 계정에 적립된 재설정 크레딧 1회를 사용해 재설정 가능한 기간별 사용량 한도를 즉시 초기화합니다."
},
"voiceModelList": {
"deleteModel": "{{label}} 삭제",
"downloadModel": "{{label}} 다운로드",
"inUse": "사용 중",
"modelExtracting": "{{size}} · 압축 해제 중…",
"modelProgress": "{{size}} · {{progress}}%",
"modelSize": "{{size}} MB",
"recommended": "권장",
"use": "사용",
"useModel": "{{label}} 사용"
},
"workspaceDetailPlaceholder": {
"description": "사이드바에서 워크스페이스를 선택하면 해당 터미널이 여기에 열립니다.",
"title": "열린 워크스페이스 없음"
},
"workspaceNameInput": {
"placeholder": "워크스페이스 이름"
},
"worktreeListRow": {
"child": "하위",
"children_one": "하위 {{count}}개",
"children_other": "하위 {{count}}개",
"folder": "폴더",
"project": "프로젝트 {{repo}}",
"remoteHost": "원격 호스트"
}
},
"connection": {
"connectionHealth": {
"checkConnection": "연결 상태를 확인하세요.",
@@ -567,6 +1035,268 @@
"useChatUi": "채팅 UI 사용"
}
},
"pullRequest": {
"checksSection": {
"actionRequired": "조치 필요",
"cancelled": "취소됨",
"checkDetails": "{{name}} 체크 세부 정보",
"failed": "실패",
"failing": "{{count}}개 실패",
"failingChecks_one": "실패한 검사 {{count}}개",
"failingChecks_other": "실패한 검사 {{count}}개",
"fix": "수정",
"fixFailing": "AI로 실패한 체크 수정",
"inProgress": "진행 중",
"inspectOrFix": "세부 정보를 확인하거나 AI로 수정을 시작하세요.",
"loadDetailsFailed": "체크 세부 정보를 불러오지 못했습니다.",
"neutral": "중립",
"neutralCount": "{{count}}개 중립",
"noChecks": "체크 없음",
"openOnWeb": "웹에서 {{name}} 열기",
"passed": "{{count}}개 통과",
"pending": "대기 중",
"pendingCount": "{{count}}개 대기 중",
"queued": "대기 중",
"rerunFailing": "실패한 체크 다시 실행",
"skipped": "건너뜀",
"successful": "성공",
"timedOut": "시간 초과",
"title": "체크",
"unavailable": "검사를 사용할 수 없습니다."
},
"commentCard": {
"daysAgo_one": "{{count}}일 전",
"daysAgo_other": "{{count}}일 전",
"deleteComment": "댓글 삭제",
"deleteConfirmMessage": "GitHub에서 댓글이 영구적으로 삭제됩니다.",
"deleteConfirmTitle": "댓글을 삭제하시겠습니까?",
"editComment": "댓글 수정",
"editPlaceholder": "댓글 수정…",
"hoursAgo_one": "{{count}}시간 전",
"hoursAgo_other": "{{count}}시간 전",
"justNow": "방금 전",
"minutesAgo_one": "{{count}}분 전",
"minutesAgo_other": "{{count}}분 전",
"monthsAgo_one": "{{count}}개월 전",
"monthsAgo_other": "{{count}}개월 전",
"openOnGitHub": "GitHub에서 댓글 열기",
"reply": "답장",
"replyPlaceholder": "답장 작성…",
"replyToComment": "댓글에 답장하기",
"resolve": "해결",
"resolveThread": "스레드 해결",
"resolved": "해결됨",
"unresolve": "해결 취소",
"unresolveThread": "스레드 해결 취소",
"yearsAgo_one": "{{count}}년 전",
"yearsAgo_other": "{{count}}년 전"
},
"commentsSection": {
"addCommentPlaceholder": "댓글 추가…",
"all": "모두",
"bots": "봇",
"comment": "댓글",
"commentsLoadFailed": "댓글을 불러오지 못했습니다. 새로고침을 눌러 다시 시도하세요.",
"description": "설명",
"descriptionLoadFailed": "설명을 불러오지 못했습니다. 새로고침을 눌러 다시 시도하세요.",
"humans": "사람",
"noBotComments": "봇 댓글이 없습니다.",
"noComments": "아직 댓글이 없습니다.",
"noDescription": "설명이 제공되지 않았습니다.",
"noHumanComments": "사람 댓글이 없습니다.",
"resolvedComment_other": "{{author}}님의 댓글 · 해결됨 ({{count}})",
"resolvedThread_other": "{{author}}님의 스레드 · 해결됨 ({{count}})",
"showMore": "{{count}}개 더 보기",
"showMoreOf": "남은 {{remaining}}개 중 {{count}}개 더 보기",
"title": "댓글"
},
"conflictingFilesSection": {
"commitsBehind_one": "커밋 {{count}}개 뒤처짐 (기준 커밋: {{baseCommit}})",
"commitsBehind_other": "커밋 {{count}}개 뒤처짐 (기준 커밋: {{baseCommit}})",
"copyCommands": "명령 복사",
"copyFailed": "텍스트 복사 실패",
"copyRefreshCommands": "병합 가능성 새로 고침 명령 복사",
"detailsUnavailable": "충돌 파일 세부정보를 사용할 수 없습니다.",
"filesHeading": "충돌하는 파일",
"localMergeClean": "GitHub에서는 충돌이 있다고 보고하지만 로컬 Git에서는 충돌이 재현되지 않았습니다. PR을 새로고침하거나 브랜치를 푸시해 병합 가능성을 다시 계산하세요.",
"mustResolve": "이 브랜치에는 해결해야 할 충돌이 있습니다.",
"refreshing": "충돌 세부정보 새로고침 중…",
"resolveWithAi": "AI로 충돌 해결",
"runFromWorktree": "이 워크트리에서 실행",
"title": "충돌"
},
"linkPrForm": {
"linkButton": "PR 연결",
"linkNumber": "#{{number}} 연결",
"referenceLabel": "PR 번호 또는 GitHub URL",
"referencePlaceholder": "#123 또는 https://github.com/owner/repo/pull/123",
"title": "기존 PR 연결"
},
"markdownBlocks": {
"details": "세부 정보"
},
"prActionsSection": {
"autoMergeAccessibilityLabel": "자동 병합 켜기/끄기",
"autoMergeLabel": "준비되면 자동 병합",
"autoMergeOff": "꺼짐",
"autoMergeOn": "켜짐",
"closeAccessibilityLabel": "PR 닫기",
"closeConfirmMessage": "병합하지 않고 #{{number}}을(를) 닫습니다.",
"closeConfirmTitle": "PR을 닫으시겠습니까?",
"markReadyAccessibilityLabel": "PR을 리뷰 준비 완료로 표시",
"markReadyButton": "리뷰 준비 완료",
"mergeAccessibilityLabel": "PR 병합",
"mergeButton": "병합",
"mergeConfirmTitle": "PR을 병합하시겠습니까?",
"recheckAccessibilityLabel": "병합 상태 다시 확인",
"recheckButton": "다시 확인",
"reopenAccessibilityLabel": "PR 다시 열기",
"reopenButton": "다시 열기",
"reopenConfirmMessage": "#{{number}}을(를) 다시 엽니다.",
"reopenConfirmTitle": "PR을 다시 여시겠습니까?",
"unlinkAccessibilityLabel": "PR 연결 해제",
"unlinkButton": "연결 해제",
"unlinkFailed": "PR 연결을 해제하지 못했습니다."
},
"prActionsState": {
"behindHint": "병합하기 전에 브랜치 업데이트",
"behindLabel": "뒤처짐",
"blockedHint": "GitHub에서 이 PR이 차단되었다고 보고합니다.",
"blockedLabel": "차단됨",
"changesRequestedHint": "GitHub에 따르면 이 PR에 변경 요청이 있습니다.",
"changesRequestedLabel": "변경 요청됨",
"checkingHint": "GitHub는 여전히 이 PR 병합 상태를 계산하고 있습니다.",
"checkingLabel": "확인 중",
"checksPassedHint": "체크를 통과했습니다. 병합 전에 병합 가능 여부를 다시 확인합니다.",
"checksPassedLabel": "체크 통과",
"closedHint": "이 PR이 종료되었습니다.",
"closedLabel": "닫힘",
"conflictsHint": "GitHub에서 병합 충돌이 보고됨",
"conflictsLabel": "충돌",
"draftHint": "이 PR은 아직 초안입니다.",
"draftLabel": "초안",
"mergeConfirmMessage": "#{{number}}을(를) 기본 브랜치에 병합합니다.",
"mergeQueueAutoMergeOnHint": "이 기본 브랜치는 GitHub 병합 대기열을 사용합니다.",
"mergeQueueAutoMergeOnLabel": "자동 병합 켜짐",
"mergeQueueHint": "이 기본 브랜치는 GitHub 병합 대기열을 사용합니다.",
"mergeQueueLabel": "준비되면 병합",
"mergeStackMessage": "GitHub는 스택에 있는 이 PR과 그 아래의 모든 PR을 병합합니다.",
"mergeStatusUnavailableHint": "이 PR의 병합 상태를 확인할 수 없습니다.",
"mergeStatusUnavailableLabel": "병합",
"mergeThrough_one": "#{{number}}까지 병합 · PR {{count}}개",
"mergeThrough_other": "#{{number}}까지 병합 · PR {{count}}개",
"mergeableChecksFailedHint": "GitHub에서는 이 PR을 병합할 수 있다고 하지만 일부 체크가 실패했습니다.",
"mergeableChecksFailedLabel": "체크 실패",
"mergeableChecksPassedHint": "GitHub에서는 이 PR을 병합할 수 있으며 체크를 통과했다고 합니다",
"mergeableChecksPassedLabel": "병합 가능",
"mergeableChecksPendingHint": "GitHub에서는 이 PR을 병합할 수 있다고 하지만 체크가 아직 실행 중입니다.",
"mergeableChecksPendingLabel": "체크 대기 중",
"mergeableHint": "GitHub에서는 이 PR을 병합할 수 있다고 합니다",
"mergeableLabel": "병합 가능",
"mergedHint": "이 PR은 이미 병합되었습니다.",
"mergedLabel": "병합됨",
"methodMerge": "병합 커밋 생성",
"methodRebase": "리베이스 및 병합",
"methodSquash": "스쿼시 및 병합",
"queueStackMessage": "GitHub는 이 PR과 그 아래의 모든 PR을 병합 대기열에 추가합니다.",
"queueThrough_one": "#{{number}}까지 대기열에 추가 · PR {{count}}개",
"queueThrough_other": "#{{number}}까지 대기열에 추가 · PR {{count}}개",
"reviewRequiredHint": "이 PR을 병합하려면 GitHub에서 리뷰 승인이 필요합니다.",
"reviewRequiredLabel": "승인 필요",
"stackBehind": "#{{number}}을(를) 업데이트해야 합니다.",
"stackBlocked": "#{{number}}이(가) 차단되었습니다.",
"stackChangesRequested": "#{{number}}에 변경 요청이 있습니다.",
"stackClosed": "#{{number}}이(가) 종료되었습니다.",
"stackConflicts": "#{{number}}에 병합 충돌이 있습니다.",
"stackDraft": "#{{number}}은(는) 아직 초안입니다.",
"stackReviewRequired": "#{{number}}은(는) 아직 리뷰 승인이 필요합니다."
},
"prCheckDetail": {
"annotations": "주석",
"annotationsTruncated": "처음 20개의 주석 표시 중",
"jobsTruncated": "처음 100개 작업 표시",
"noDetails": "세부 정보가 없습니다."
},
"prCheckDetailContent": {
"annotation": "주석",
"failedJobs": "실패한 작업",
"jobs": "작업",
"unknown": "알 수 없음"
},
"prChecksPresentation": {
"failing_one": "{{count}}개 실패",
"failing_other": "{{count}}개 실패",
"neutral_one": "{{count}}개 중립",
"neutral_other": "{{count}}개 중립",
"noChecks": "체크 없음",
"passed_one": "{{count}}개 통과",
"passed_other": "{{count}}개 통과",
"pending_one": "{{count}}개 대기 중",
"pending_other": "{{count}}개 대기 중",
"stateClosed": "닫힘",
"stateDraft": "초안",
"stateMerged": "병합됨",
"stateOpen": "열림",
"unavailable": "검사를 사용할 수 없습니다."
},
"prCommentAudienceLabels": {
"all": "모두",
"bots": "봇",
"humans": "사람",
"noBotComments": "봇 댓글이 없습니다.",
"noComments": "아직 댓글이 없습니다.",
"noHumanComments": "사람 댓글이 없습니다."
},
"prSidebarCreateEmptyState": {
"branchNotLinked": "{{branch}} 브랜치는 열려 있는 PR에 연결되어 있지 않습니다.",
"branchRequired": "PR을 작성하기 전에 브랜치를 체크아웃하세요.",
"createFailed": "PR을 작성하지 못했습니다.",
"createPr": "PR 작성",
"createPullRequest": "PR 작성",
"currentBranchNotLinked": "현재 브랜치는 열려 있는 PR에 연결되어 있지 않습니다.",
"linkExistingPr": "기존 PR 연결",
"linkExistingPullRequest": "기존 PR 연결",
"linkedPrUnavailable": "연결된 PR #{{number}} 사용 불가",
"noOpenPullRequest": "열려 있는 PR 없음",
"refreshOrCreate": "새로고침하여 다시 확인하거나 이 브랜치의 새 PR을 작성하세요.",
"refreshPullRequest": "PR 새로고침",
"title": "PR"
},
"prSidebarHeader": {
"cancelEditingTitle": "제목 편집 취소",
"editTitle": "PR 제목 편집",
"openInBrowser": "브라우저에서 PR #{{number}} 열기",
"openOnWeb": "웹에서 PR #{{number}} 열기",
"saveTitle": "제목 저장"
},
"prViewPanel": {
"branchUnavailable": "현재 브랜치를 확인할 수 없습니다.",
"providerUnavailable": "이 제공자에서는 호스팅 PR 패널을 사용할 수 없습니다."
},
"reviewerPickerDrawer": {
"loadFailed": "사용자 목록을 불러오지 못했습니다.",
"noMatches": "일치하는 사용자가 없습니다.",
"removeReviewer": "{{login}} 제거",
"requestReviewer": "{{login}}에게 리뷰 요청",
"searchPeople": "사용자 검색",
"title": "리뷰어"
},
"reviewersSection": {
"addOrRemove": "리뷰어 추가 또는 제거",
"approved": "승인됨",
"changesRequested": "변경 요청됨",
"commented": "댓글 작성됨",
"dismissed": "해제됨",
"loadFailed": "리뷰어를 불러오지 못했습니다. 새로고침을 눌러 다시 시도하세요.",
"loading": "리뷰어 로드 중…",
"noReviewers": "리뷰 요청 없음",
"pending": "대기 중",
"removeReviewer": "{{login}} 제거",
"requested": "요청됨",
"reviewed": "리뷰 완료",
"title": "리뷰어"
}
},
"settings": {
"aboutScreen": {
"openLinkFailed": "링크를 열지 못했습니다. 다시 시도하세요.",
+755
View File
@@ -146,9 +146,14 @@
"common": {
"back": "Voltar",
"cancel": "Cancelar",
"close": "Fechar",
"confirm": "Confirmar",
"continue": "Continuar",
"copied": "Copiado",
"copy": "Copiar",
"delete": "Excluir",
"done": "Concluído",
"edit": "Editar",
"ok": "OK",
"remove": "Remover",
"retry": "Tentar novamente",
@@ -156,6 +161,477 @@
"settings": "Configurações",
"unknown": "Desconhecido"
},
"components": {
"accountsSnapshot": {
"invalidSnapshot": "Snapshot de contas inválido recebido do host"
},
"agentLaunchNotice": {
"copyFailed": "Não foi possível copiar",
"copyPrompt": "Copiar prompt",
"statusUnreadable": "Não foi possível ler o status deste host. Tentando novamente…",
"updateRequired": "Atualize o Orca no seu computador para iniciar um agente pelo celular."
},
"agentSpinner": {
"monitoring": "Monitorando tarefas em segundo plano",
"unconfirmed": "Não foi possível confirmar"
},
"agentStateDot": {
"monitoring": "Monitorando tarefas em segundo plano",
"unverifiable": "Nenhuma atualização recente"
},
"authFailedBanner": {
"message": "Falha na autenticação — tente se reconectar primeiro; se a falha persistir, faça o pareamento novamente pelo desktop."
},
"authFailedBannerActions": {
"repair": "Parear novamente",
"webNote": "Reconecte ou remova este host pelo aplicativo Orca."
},
"codexResetCredit": {
"alreadyAppliedTitle": "Redefinição já aplicada",
"available_many": "{{count}} redefinições disponíveis",
"available_one": "{{count}} redefinição disponível",
"available_other": "{{count}} redefinições disponíveis",
"expiryDaysHoursMultiple": "O próximo expira em {{days}}d {{hours}}h",
"expiryDaysHoursSingle": "Expira em {{days}}d {{hours}}h",
"expiryDaysMultiple": "O próximo expira em {{days}}d",
"expiryDaysSingle": "Expira em {{days}}d",
"expiryHoursMinutesMultiple": "O próximo expira em {{hours}}h {{minutes}}m",
"expiryHoursMinutesSingle": "Expira em {{hours}}h {{minutes}}m",
"expiryHoursMultiple": "O próximo expira em {{hours}}h",
"expiryHoursSingle": "Expira em {{hours}}h",
"expiryMinutesMultiple": "O próximo expira em {{minutes}}m",
"expiryMinutesSingle": "Expira em {{minutes}}m",
"expiryNowMultiple": "O próximo expira agora",
"expiryNowSingle": "Expira agora",
"invalidResponse": "Resposta de redefinição inválida do host",
"noResetMessage": "Esta conta não tem créditos de redefinição ganhos disponíveis.",
"noResetTitle": "Nenhuma redefinição disponível",
"nothingToResetMessage": "Nenhum dos limites de taxa do Codex que podem ser redefinidos foi atingido.",
"nothingToResetTitle": "Nada para redefinir",
"refresh": "O uso do Codex foi atualizado.",
"resetTitle": "Limites de taxa redefinidos"
},
"codexResetCreditAction": {
"activeAccountHint": "Usa uma redefinição obtida para a conta ativa do Codex",
"earnedReset": "Redefinição de limite de taxa do Codex obtida",
"resetting": "Redefinindo…",
"resettingLabel": "Redefinindo limites de taxa do Codex",
"scopedHint": "Usa uma redefinição obtida para {{scopeLabel}}",
"useReset": "Usar redefinição",
"useResetLabel": "Usar redefinição de limite de taxa do Codex"
},
"customKeyModal": {
"add": "Adicionar",
"addShortcut": "Adicionar atalho",
"command": "Comando",
"commandPlaceholder": "Ex.: pnpm build",
"editing": "Edição",
"function": "Função",
"key": "Tecla",
"label": "Rótulo",
"labelPlaceholder": "Ex.: Compilar",
"manageShortcuts": "Gerenciar atalhos",
"manageShortcutsHint": "Mostrar, ocultar ou reordenar teclas de atalho",
"modifiers": "Modificadores",
"moreKeys": "Mais teclas — Tab, setas, F1–F12…",
"navigation": "Navegação",
"pickKey": "Selecione uma tecla",
"pressEnter": "Pressionar Enter",
"shortcutCombo": "Combinação de teclas",
"shortcutComboHint": "Crie combinações com Ctrl, Alt e Shift",
"textMacro": "Macro de texto",
"textMacroHint": "Envie um comando de texto personalizado"
},
"dictationSetupSheet": {
"description": "Baixe ou selecione um modelo e ative o ditado no computador pareado — tudo por aqui.",
"download": "Baixar",
"downloadFailed": "Falha no download",
"downloadModel": "Baixar {{model}}",
"enabled": "Ditado ativado",
"inUse": "Em uso",
"loadFailed": "Falha ao carregar",
"modelExtracting": "{{details}} · extraindo…",
"modelProgress": "{{details}} · {{progress}}%",
"modelSize": "{{size}} MB",
"recommended": "Recomendado",
"selectFailed": "Não foi possível selecionar o modelo",
"title": "Configurar ditado por voz",
"updateFailed": "Não foi possível atualizar",
"use": "Usar",
"useModel": "Usar {{model}}"
},
"diffReviewBody": {
"binaryDiff": "Diff binário",
"binaryMessage": "Este arquivo não pode ser exibido como texto no celular.",
"deletedFile": "Arquivo excluído",
"deletedMessage": "Este arquivo foi excluído. Adicione uma nota ao arquivo ou marque-o como revisado.",
"diffTooLarge": "Diff muito grande",
"diffTruncated": "Diff truncado para prévia no celular.",
"diffUnavailable": "Diff indisponível",
"loadFailed": "Não foi possível carregar a revisão",
"loadingDiff": "Carregando diff…",
"loadingReview": "Carregando revisão…",
"noReviewableChanges": "Nenhuma alteração para revisar",
"retryLabel": "Tentar carregar a revisão novamente",
"reviewUnavailable": "Revisão indisponível",
"selectFile": "Selecione um arquivo para revisar.",
"tooLargeMessage": "Este diff é grande demais para a prévia no celular.",
"tryDifferentFilter": "Tente outro filtro de revisão."
},
"diffReviewDrawers": {
"actionsTitle": "Ações de revisão",
"addNote": "Adicionar nota",
"cancelNote": "Cancelar nota",
"clearSentNotes": "Limpar notas enviadas",
"completeTitle": "Revisão concluída",
"copyNotes": "Copiar notas",
"deleteNote": "Excluir nota",
"discard": "Descartar",
"discardMessage": "Descartar alterações em \"{{filePath}}\"? Isso não pode ser desfeito.",
"discardTitle": "Descartar arquivo",
"editNote": "Editar nota",
"fileNote": "Nota do arquivo",
"fileSummary_many": "{{fileCount}} arquivo revisado, {{count}} notas",
"fileSummary_one": "{{fileCount}} arquivo revisado, {{count}} nota",
"fileSummary_other": "{{fileCount}} arquivo revisado, {{count}} notas",
"filesSummary_many": "{{fileCount}} arquivos revisados, {{count}} notas",
"filesSummary_one": "{{fileCount}} arquivos revisados, {{count}} nota",
"filesSummary_other": "{{fileCount}} arquivos revisados, {{count}} notas",
"lineNumber": "Linha {{lineNumber}}",
"loadingSessions": "Carregando sessões de agentes…",
"markUnreviewed": "Marcar como não revisado",
"newAgentSession": "Nova sessão de agente",
"openInSession": "Abrir na sessão",
"reviewNote": "Nota de revisão",
"saveLineNote": "Salvar nota na linha {{lineNumber}}",
"sendNotes": "Enviar notas",
"sendNotesLabel": "Enviar notas ao agente",
"sendUnsentNotes": "Enviar notas não enviadas",
"stageReviewed": "Adicionar revisados ao stage",
"stageReviewedFiles": "Adicionar arquivos revisados ao stage",
"stageReviewedLabel": "Adicionar arquivos revisados ao stage",
"stageableFiles_many": "{{count}} arquivos revisados e não preparados podem ser adicionados ao stage",
"stageableFiles_one": "{{count}} arquivo revisado e não preparado pode ser adicionado ao stage",
"stageableFiles_other": "{{count}} arquivos revisados e não preparados podem ser adicionados ao stage",
"statusUnreadable": "Não foi possível ler o status deste host. Tentando novamente…",
"terminal": "Terminal",
"terminalLabel": "{{title}} ({{id}})",
"unsentNotes_many": "{{count}} notas não enviadas",
"unsentNotes_one": "{{count}} nota não enviada",
"unsentNotes_other": "{{count}} notas não enviadas",
"updateRequired": "Atualize o Orca no seu computador"
},
"diffReviewFileSummary": {
"branch": "Branch",
"branchFrom": "Branch, origem: {{oldPath}}",
"changed": "Alterado",
"editFileNote": "Editar nota do arquivo",
"hunk": "Trecho",
"nextHunk": "Próximo trecho",
"notes_many": "{{count}} notas",
"notes_one": "{{count}} nota",
"notes_other": "{{count}} notas",
"previousHunk": "Trecho anterior",
"reviewed": "Revisado",
"staged": "Preparado",
"stagedFrom": "Preparado, origem: {{oldPath}}",
"stale": "Desatualizada",
"staleCount": "Desatualizadas: {{count}}",
"unstaged": "Não preparado",
"unstagedFrom": "Não preparado, origem: {{oldPath}}"
},
"diffReviewFooter": {
"addFileNote": "Adicionar nota ao arquivo",
"discard": "Descartar",
"discardFile": "Descartar arquivo",
"markFileReviewed": "Marcar arquivo como revisado",
"markReviewed": "Marcar como revisado",
"nextFile": "Próximo arquivo",
"note": "Nota",
"previousFile": "Arquivo anterior",
"reviewed": "Revisado",
"stage": "Adicionar ao stage",
"stageFile": "Adicionar arquivo ao stage",
"unstage": "Remover do stage",
"unstageFile": "Remover arquivo do stage"
},
"diffReviewHeader": {
"filterAll": "Todos",
"filterBranch": "Branch",
"filterNotes": "Notas",
"filterStaged": "Preparados",
"filterUnreviewed": "Não revisados",
"filterUnstaged": "Não preparados",
"openPullRequestSidebar": "Abrir barra lateral do pull request",
"openReviewActions": "Abrir ações de revisão",
"reviewedProgress": "Revisados: {{reviewedCount}}/{{queueLength}}",
"showAllFiles": "Mostrar todos os arquivos da revisão",
"showBranchFiles": "Mostrar arquivos da branch nesta revisão",
"showNotesFiles": "Mostrar arquivos com notas nesta revisão",
"showStagedFiles": "Mostrar arquivos preparados nesta revisão",
"showUnreviewedFiles": "Mostrar arquivos ainda não revisados nesta revisão",
"showUnstagedFiles": "Mostrar arquivos não preparados nesta revisão",
"title": "Alterações",
"unsentNotes_many": "{{count}} notas não enviadas",
"unsentNotes_one": "{{count}} nota não enviada",
"unsentNotes_other": "{{count}} notas não enviadas"
},
"diffReviewLine": {
"addNoteOnLine": "Adicionar nota na linha {{number}}",
"addedLine": "Linha adicionada",
"addedLineNumber": "Linha {{number}} adicionada",
"contextLine": "Linha de contexto",
"contextLineNumber": "Linha {{number}} de contexto",
"deletedLine": "Linha excluída",
"deletedLineNumber": "Linha {{number}} excluída",
"editNoteOnLine": "Editar nota na linha {{number}}"
},
"dragReorderRow": {
"dragToReorder": "Arraste para reordenar",
"moveDown": "Mover para baixo",
"moveUp": "Mover para cima",
"reorderHint": "Use as ações mover para cima e mover para baixo para reordenar sem arrastar"
},
"homeQuickActions": {
"newWorkspace": "Novo Espaço de Trabalho",
"pairDesktop": "Parear Desktop",
"title": "Ações Rápidas"
},
"hostCard": {
"hostActions": "Ações para {{title}}",
"openHost": "Abrir {{title}}",
"pairingInvalid": "Pareamento inválido",
"pairingTemporarilyUnavailable": "Pareamento temporariamente indisponível",
"repairHint": "Toque para parear novamente com seu desktop",
"unlockHint": "Desbloqueie seu celular e depois toque para tentar novamente",
"updateDesktopHint": "Atualize o Orca no desktop e entre na sua conta para se conectar de qualquer lugar",
"worktreeAccessibilitySummary": "{{summary}}, {{activeCount}} em atividade"
},
"hostDiagnosticsLink": {
"viewDiagnostics": "Ver diagnósticos de rede"
},
"hostProtocolGate": {
"checkingCompatibility": "Verificando a compatibilidade do host"
},
"hostRouteNoticeBanner": {
"dismissNotice": "Descartar aviso"
},
"htmlPreview": {
"preview": "Pré-visualização",
"previewRenderedHtml": "Pré-visualizar HTML renderizado",
"source": "Código-fonte",
"title": "Pré-visualização de HTML",
"viewHtmlSource": "Ver código-fonte HTML"
},
"jiraConnectPrompt": {
"checkAgain": "Verificar novamente",
"connectHint": "Adicione seu site do Jira no Orca para desktop em Configurações → Integrações e verifique novamente.",
"connectOnDesktop": "Conecte o Jira no seu desktop",
"credentialUnreadable": "Não foi possível ler as credenciais salvas do Jira: {{credentialError}}",
"updateDesktop": "Atualize o Orca no desktop"
},
"markdownBlocks": {
"copyCode": "Copiar código",
"openImage": "Abrir imagem"
},
"mountedBottomDrawer": {
"dismissDrawer": "Fechar painel inferior"
},
"newWorkspaceFab": {
"newWorkspace": "Novo espaço de trabalho"
},
"newWorkspaceSetupScriptField": {
"hooks": "HOOKS",
"label": "Script de configuração",
"run": "Executar",
"runSetupCommand": "Executar comando de configuração",
"skip": "Pular"
},
"newWorkspaceSshConnectionField": {
"connect": "Conectar",
"connecting": "Conectando…",
"label": "Conexão SSH",
"serverRecovery": "O servidor precisa ser recuperado pelo desktop: Configurações → Hosts SSH → Recuperar servidor."
},
"newWorktreeAgentSelection": {
"blankTerminal": "Terminal em Branco"
},
"newWorktreeFormSheet": {
"advanced": "Avançado",
"agent": "Agente",
"connectTarget": "Conectar destino",
"connectTargetFirst": "Conecte o destino primeiro",
"createWorktree": "Criar worktree",
"nameOrCreateFrom": "Nome ou 'Criar A Partir De'",
"noProjectsFound": "Nenhum projeto encontrado",
"note": "Nota",
"notePlaceholder": "Escreva uma nota",
"workspaceName": "Nome do espaço de trabalho"
},
"newWorktreeModal": {
"remoteRepository": "Repositório remoto"
},
"newWorktreeModalDrawers": {
"agent": "Agente",
"project": "Projeto",
"runOn": "Executar em"
},
"newWorktreeProjectTargetFields": {
"project": "Projeto",
"runOn": "Executar em",
"selectProject": "Selecione um projeto",
"selectRunTarget": "Selecione um destino de execução"
},
"prSidebar": {
"loadFailed": "Algo deu errado.",
"loading": "Carregando pull request…",
"notPermitted": "Não permitido — sua conta do GitHub não está conectada.",
"retryLoading": "Tentar carregar o pull request novamente"
},
"protocolBlockScreen": {
"backToHosts": "Voltar para hosts",
"desktopTooOldBody": "A versão do app desktop pareado é antiga demais para a sua versão atual do Orca Mobile. Atualize o Orca no seu computador e tente se conectar a este host novamente.",
"getVersion": "Baixar o Orca {{version}}",
"mobileTooOldAppStore": "Este desktop precisa de uma versão mais recente do Orca Mobile. Atualize o Orca Mobile pela App Store e tente se conectar a este host novamente.",
"mobileTooOldGitHubReleases": "Este desktop precisa de uma versão mais recente do Orca Mobile. Atualize o Orca Mobile pelo GitHub Releases e tente se conectar a este host novamente.",
"openAppStore": "Abrir a App Store",
"openGitHubReleases": "Abrir GitHub Releases",
"refreshRecoveryNote": "Se esta mensagem persistir, remova este host e faça o pareamento novamente.",
"refreshWorkspaceBody": "O espaço de trabalho em cache deste host é mais antigo do que o desktop espera. Reconecte-se a este host para baixar a versão atual.",
"refreshWorkspaceTitle": "Atualizar o espaço de trabalho mobile",
"updateDesktopTitle": "Atualizar o Orca no seu computador",
"updateMobileTitle": "Atualizar o Orca Mobile",
"updateRecoveryNote": "Já atualizou? Volte para Hosts e atualize a conexão. Se esta mensagem persistir, remova este host e faça o pareamento novamente.",
"workspaceNeedsUpdateAppStore": "O espaço de trabalho mobile deste desktop precisa de uma versão mais recente do Orca Mobile. Atualize o Orca Mobile pela App Store e tente se conectar a este host novamente.",
"workspaceNeedsUpdateGitHubReleases": "O espaço de trabalho mobile deste desktop precisa de uma versão mais recente do Orca Mobile. Atualize o Orca Mobile pelo GitHub Releases e tente se conectar a este host novamente."
},
"richMarkdownEditor": {
"imageUrl": "URL da imagem",
"insert": "Inserir",
"linkUrl": "URL do link"
},
"richMarkdownToolbar": {
"body": "Texto normal",
"bold": "Negrito",
"bulletList": "Lista com marcadores",
"checklist": "Lista de verificação",
"codeBlock": "Bloco de código",
"heading1": "H1",
"heading2": "H2",
"heading3": "H3",
"image": "Imagem",
"inlineCode": "Código inline",
"italic": "Itálico",
"link": "Link",
"numberedList": "Lista numerada",
"quote": "Citação",
"strike": "Tachado"
},
"searchField": {
"clearSearch": "Limpar busca"
},
"setupHookTrustDrawer": {
"alwaysTrustAndRun": "Sempre confiar e executar",
"description": "O orca.yaml deste repositório é executado antes de o espaço de trabalho iniciar. Só o execute se você confiar neste repositório.",
"dontRun": "Não executar",
"newScriptLabel": "Novo script de configuração",
"runHooks": "Executar hooks",
"runSetupTitle": "Executar a configuração de {{repoName}}?",
"scriptChangedTitle": "O script de configuração de {{repoName}} foi alterado",
"scriptLabel": "Script de configuração"
},
"smartWorkspaceAdvancedFields": {
"branchName": "Nome da branch",
"derivedFromName": "Derivado do nome",
"name": "Nome",
"reuseBranch": "Reutilizar branch “{{branch}}”",
"workspaceName": "Nome do espaço de trabalho"
},
"smartWorkspaceSourceDrawer": {
"connectRepository": "Conecte o repositório para buscar origens.",
"crossRepoMessage": "Este item está em {{owner}}/{{repo}}.",
"gitHubRemoteRequired": "Este repositório SSH precisa de um remote do GitHub para listar issues e PRs.",
"noResults": "Nenhum resultado encontrado.",
"searchPlaceholder": "Digite um nome ou busque uma origem",
"switchRepo": "Mudar para {{repoName}}",
"title": "Nome ou 'Criar A Partir De'",
"typeWorkspaceName": "Digite um nome para o espaço de trabalho no campo abaixo."
},
"smartWorkspaceSourceField": {
"openSelectedSource": "Abrir origem selecionada",
"optional": "[Opcional]",
"searchPlaceholder": "Digite um nome ou busque uma origem"
},
"smartWorkspaceSourceRow": {
"createBranch": "Criar branch \"{{name}}\"",
"issue": "Issue #{{number}}",
"mergeRequest": "MR !{{number}}",
"nameWorkspace": "Nomear este espaço de trabalho",
"newBranch": "Nova branch",
"pullRequest": "PR #{{number}}",
"useName": "Usar \"{{name}}\""
},
"sourceCopyButton": {
"clipboardRejected": "A área de transferência rejeitou o texto.",
"copiedLoaded": "Parte carregada copiada",
"copyFailed": "Falha ao copiar",
"copyLoaded": "Copiar parte carregada",
"couldNotCopy": "Não foi possível copiar",
"failed": "Falhou"
},
"terminalShortcutSettings": {
"addDescription": "Criar combinação de teclas ou macro de texto",
"addShortcut": "Adicionar Atalho Personalizado…",
"customShortcuts": "ATALHOS PERSONALIZADOS",
"empty": "Nenhum atalho personalizado definido ainda.",
"removeShortcut": "Remover {{label}}",
"resetDefaults": "Restaurar Padrões",
"resetDescription": "Mostrar todas as teclas de atalho integradas na ordem original",
"shortcutBar": "BARRA DE ATALHOS",
"shortcutBarDescription": "Ative ou desative as teclas para exibi-las ou ocultá-las e mantenha a alça pressionada para arrastar uma tecla até a posição desejada na barra de atalhos do terminal."
},
"useCodexResetCreditAction": {
"confirmTitle": "Usar uma redefinição de limite de taxa?",
"detailsChanged": "Detalhes da redefinição alterados",
"detailsChangedMessage": "A conta ou a oferta de redefinição mudou antes de o host entrar em contato com o Codex. Revise os detalhes atualizados e confirme novamente.",
"hostAccount": "{{identity}} no host",
"hostAccountConfirmMessage": "Isso usa um crédito de redefinição ganho por {{identity}} no host e redefine imediatamente as janelas de limite de taxa elegíveis.",
"hostSelectedAccount": "a conta gerenciada selecionada no host",
"hostSelectedAccountConfirmMessage": "Isso usa um crédito de redefinição ganho pela conta gerenciada selecionada no host e redefine imediatamente as janelas de limite de taxa elegíveis.",
"resetFailed": "Não foi possível redefinir os limites de taxa",
"wslAccount": "{{identity}} no WSL {{distro}}",
"wslAccountConfirmMessage": "Isso usa um crédito de redefinição ganho por {{identity}} no WSL {{distro}} e redefine imediatamente as janelas de limite de taxa elegíveis.",
"wslSelectedAccount": "a conta gerenciada selecionada no WSL {{distro}}",
"wslSelectedAccountConfirmMessage": "Isso usa um crédito de redefinição ganho pela conta gerenciada selecionada no WSL {{distro}} e redefine imediatamente as janelas de limite de taxa elegíveis."
},
"voiceModelList": {
"deleteModel": "Excluir {{label}}",
"downloadModel": "Baixar {{label}}",
"inUse": "Em uso",
"modelExtracting": "{{size}} · extraindo…",
"modelProgress": "{{size}} · {{progress}}%",
"modelSize": "{{size}} MB",
"recommended": "Recomendado",
"use": "Usar",
"useModel": "Usar {{label}}"
},
"workspaceDetailPlaceholder": {
"description": "Selecione um espaço de trabalho na barra lateral para abrir seu terminal aqui.",
"title": "Nenhum espaço de trabalho aberto"
},
"workspaceNameInput": {
"placeholder": "Nome do espaço de trabalho"
},
"worktreeListRow": {
"child": "Filho",
"children_many": "{{count}} filhos",
"children_one": "{{count}} filho",
"children_other": "{{count}} filhos",
"folder": "Pasta",
"project": "Projeto {{repo}}",
"remoteHost": "Host remoto"
}
},
"connection": {
"connectionHealth": {
"checkConnection": "Verifique sua conexão",
@@ -572,6 +1048,285 @@
"useChatUi": "Usar interface de chat"
}
},
"pullRequest": {
"checksSection": {
"actionRequired": "Ação necessária",
"cancelled": "Cancelado",
"checkDetails": "Detalhes da verificação {{name}}",
"failed": "Com Falha",
"failing": "Com falha: {{count}}",
"failingChecks_many": "{{count}} verificações com falha",
"failingChecks_one": "{{count}} verificação com falha",
"failingChecks_other": "{{count}} verificações com falha",
"fix": "Corrigir",
"fixFailing": "Corrigir com IA as verificações com falha",
"inProgress": "Em andamento",
"inspectOrFix": "Inspecione os detalhes ou inicie uma correção por IA.",
"loadDetailsFailed": "Falha ao carregar os detalhes da verificação",
"neutral": "Neutro",
"neutralCount": "Neutras: {{count}}",
"noChecks": "Nenhuma verificação",
"openOnWeb": "Abrir {{name}} na web",
"passed": "Com sucesso: {{count}}",
"pending": "Pendente",
"pendingCount": "Pendentes: {{count}}",
"queued": "Na fila",
"rerunFailing": "Executar novamente as verificações com falha",
"skipped": "Ignorada",
"successful": "Com sucesso",
"timedOut": "Tempo esgotado",
"title": "Verificações",
"unavailable": "Verificações indisponíveis"
},
"commentCard": {
"daysAgo_many": "há {{count}} d",
"daysAgo_one": "há {{count}} d",
"daysAgo_other": "há {{count}} d",
"deleteComment": "Excluir comentário",
"deleteConfirmMessage": "Isso exclui permanentemente o comentário no GitHub.",
"deleteConfirmTitle": "Excluir comentário?",
"editComment": "Editar comentário",
"editPlaceholder": "Edite o comentário…",
"hoursAgo_many": "há {{count}} h",
"hoursAgo_one": "há {{count}} h",
"hoursAgo_other": "há {{count}} h",
"justNow": "agora mesmo",
"minutesAgo_many": "há {{count}} min",
"minutesAgo_one": "há {{count}} min",
"minutesAgo_other": "há {{count}} min",
"monthsAgo_many": "há {{count}} meses",
"monthsAgo_one": "há {{count}} mês",
"monthsAgo_other": "há {{count}} meses",
"openOnGitHub": "Abrir comentário no GitHub",
"reply": "Responder",
"replyPlaceholder": "Escreva uma resposta…",
"replyToComment": "Responder ao comentário",
"resolve": "Resolver",
"resolveThread": "Resolver thread",
"resolved": "resolvido",
"unresolve": "Reabrir",
"unresolveThread": "Reabrir thread",
"yearsAgo_many": "há {{count}} anos",
"yearsAgo_one": "há {{count}} ano",
"yearsAgo_other": "há {{count}} anos"
},
"commentsSection": {
"addCommentPlaceholder": "Adicione um comentário…",
"all": "Todos",
"bots": "Bots",
"comment": "Comentar",
"commentsLoadFailed": "Não foi possível carregar os comentários. Toque em Atualizar para tentar novamente.",
"description": "Descrição",
"descriptionLoadFailed": "Não foi possível carregar a descrição. Toque em Atualizar para tentar novamente.",
"humans": "Humanos",
"noBotComments": "Nenhum comentário de bot.",
"noComments": "Ainda não há comentários.",
"noDescription": "Nenhuma descrição fornecida.",
"noHumanComments": "Nenhum comentário humano.",
"resolvedComment_many": "Comentário de {{author}} resolvido ({{count}})",
"resolvedComment_one": "Comentário de {{author}} resolvido",
"resolvedComment_other": "Comentário de {{author}} resolvido ({{count}})",
"resolvedThread_many": "Thread de {{author}} resolvida ({{count}})",
"resolvedThread_one": "Thread de {{author}} resolvida",
"resolvedThread_other": "Thread de {{author}} resolvida ({{count}})",
"showMore": "Mostrar mais {{count}}",
"showMoreOf": "Mostrar mais {{count}} de {{remaining}}",
"title": "Comentários"
},
"conflictingFilesSection": {
"commitsBehind_many": "{{count}} commits atrás (commit base: {{baseCommit}})",
"commitsBehind_one": "{{count}} commit atrás (commit base: {{baseCommit}})",
"commitsBehind_other": "{{count}} commits atrás (commit base: {{baseCommit}})",
"copyCommands": "Copiar comandos",
"copyFailed": "Falha ao copiar o texto",
"copyRefreshCommands": "Copiar comandos de atualização de possibilidade de merge",
"detailsUnavailable": "Os detalhes dos arquivos em conflito não estão disponíveis",
"filesHeading": "Arquivos em conflito",
"localMergeClean": "O GitHub informa conflitos, mas o Git local não os reproduziu. Atualize o PR ou faça push da branch para recalcular a possibilidade de merge.",
"mustResolve": "Esta branch tem conflitos que precisam ser resolvidos",
"refreshing": "Atualizando os detalhes do conflito…",
"resolveWithAi": "Resolver conflitos com IA",
"runFromWorktree": "Executar a partir desta worktree",
"title": "Conflitos"
},
"linkPrForm": {
"linkButton": "Vincular pull request",
"linkNumber": "Vincular #{{number}}",
"referenceLabel": "Número do PR ou URL do GitHub",
"referencePlaceholder": "#123 ou https://github.com/owner/repo/pull/123",
"title": "Vincular pull request existente"
},
"markdownBlocks": {
"details": "Detalhes"
},
"prActionsSection": {
"autoMergeAccessibilityLabel": "Alternar merge automático",
"autoMergeLabel": "Merge automático quando pronto",
"autoMergeOff": "Desativado",
"autoMergeOn": "Ativado",
"closeAccessibilityLabel": "Fechar pull request",
"closeConfirmMessage": "#{{number}} será fechado sem fazer merge.",
"closeConfirmTitle": "Fechar pull request?",
"markReadyAccessibilityLabel": "Marcar pull request como pronto para revisão",
"markReadyButton": "Pronto para revisão",
"mergeAccessibilityLabel": "Fazer merge do pull request",
"mergeButton": "Merge",
"mergeConfirmTitle": "Fazer merge do pull request?",
"recheckAccessibilityLabel": "Verificar novamente o status do merge",
"recheckButton": "Verificar novamente",
"reopenAccessibilityLabel": "Reabrir pull request",
"reopenButton": "Reabrir",
"reopenConfirmMessage": "#{{number}} será reaberto.",
"reopenConfirmTitle": "Reabrir pull request?",
"unlinkAccessibilityLabel": "Desvincular pull request",
"unlinkButton": "Desvincular",
"unlinkFailed": "Falha ao desvincular o pull request."
},
"prActionsState": {
"behindHint": "Atualize a branch antes de fazer o merge",
"behindLabel": "Atrás",
"blockedHint": "O GitHub relata que este pull request está bloqueado",
"blockedLabel": "Bloqueado",
"changesRequestedHint": "O GitHub relata alterações solicitadas neste pull request",
"changesRequestedLabel": "Alterações solicitadas",
"checkingHint": "O GitHub ainda está calculando o status de merge deste pull request",
"checkingLabel": "Verificando",
"checksPassedHint": "Verificações aprovadas. A elegibilidade para merge será verificada novamente antes do merge.",
"checksPassedLabel": "Verificações aprovadas",
"closedHint": "Este pull request está fechado",
"closedLabel": "Fechado",
"conflictsHint": "O GitHub relata conflitos de merge",
"conflictsLabel": "Conflitos",
"draftHint": "Este pull request ainda é um rascunho",
"draftLabel": "Rascunho",
"mergeConfirmMessage": "Isso fará o merge de #{{number}} na branch base.",
"mergeQueueAutoMergeOnHint": "Esta branch base usa a fila de merge do GitHub",
"mergeQueueAutoMergeOnLabel": "Merge automático ativado",
"mergeQueueHint": "Esta branch base usa a fila de merge do GitHub",
"mergeQueueLabel": "Mergear quando pronto",
"mergeStackMessage": "O GitHub vai mergear este pull request e todos os pull requests abaixo dele na pilha.",
"mergeStatusUnavailableHint": "O status de merge não está disponível para este PR",
"mergeStatusUnavailableLabel": "Merge",
"mergeThrough_many": "Mergear até #{{number}} · {{count}} PRs",
"mergeThrough_one": "Mergear até #{{number}} · {{count}} PR",
"mergeThrough_other": "Mergear até #{{number}} · {{count}} PRs",
"mergeableChecksFailedHint": "O GitHub diz que este PR pode ser mergeado, mas algumas verificações falharam",
"mergeableChecksFailedLabel": "Verificações falharam",
"mergeableChecksPassedHint": "O GitHub diz que este PR pode ser mergeado e as verificações passaram",
"mergeableChecksPassedLabel": "Pode ser mergeado",
"mergeableChecksPendingHint": "O GitHub diz que este PR pode ser mergeado, mas as verificações ainda estão em execução",
"mergeableChecksPendingLabel": "Verificações pendentes",
"mergeableHint": "O GitHub diz que este PR pode ser mergeado",
"mergeableLabel": "Pode ser mergeado",
"mergedHint": "Este pull request já foi mergeado",
"mergedLabel": "Mergeado",
"methodMerge": "Criar commit de merge",
"methodRebase": "Rebase e merge",
"methodSquash": "Squash e merge",
"queueStackMessage": "O GitHub vai adicionar este pull request e todos os pull requests abaixo dele à fila de merge.",
"queueThrough_many": "Enfileirar até #{{number}} · {{count}} PRs",
"queueThrough_one": "Enfileirar até #{{number}} · {{count}} PR",
"queueThrough_other": "Enfileirar até #{{number}} · {{count}} PRs",
"reviewRequiredHint": "O GitHub exige aprovação de revisão antes que este pull request possa ser mergeado",
"reviewRequiredLabel": "Aprovação necessária",
"stackBehind": "#{{number}} precisa ser atualizado.",
"stackBlocked": "#{{number}} está bloqueado.",
"stackChangesRequested": "Há alterações solicitadas em #{{number}}.",
"stackClosed": "#{{number}} está fechado.",
"stackConflicts": "#{{number}} tem conflitos de merge.",
"stackDraft": "#{{number}} ainda é um rascunho.",
"stackReviewRequired": "#{{number}} ainda precisa ser aprovado na revisão."
},
"prCheckDetail": {
"annotations": "Anotações",
"annotationsTruncated": "Mostrando as primeiras 20 anotações",
"jobsTruncated": "Mostrando os primeiros 100 jobs",
"noDetails": "Nenhum detalhe disponível."
},
"prCheckDetailContent": {
"annotation": "Anotação",
"failedJobs": "Jobs com falha",
"jobs": "Jobs",
"unknown": "desconhecido"
},
"prChecksPresentation": {
"failing_many": "{{count}} com falha",
"failing_one": "{{count}} com falha",
"failing_other": "{{count}} com falha",
"neutral_many": "{{count}} neutras",
"neutral_one": "{{count}} neutra",
"neutral_other": "{{count}} neutras",
"noChecks": "Nenhuma verificação",
"passed_many": "{{count}} aprovadas",
"passed_one": "{{count}} aprovada",
"passed_other": "{{count}} aprovadas",
"pending_many": "{{count}} pendentes",
"pending_one": "{{count}} pendente",
"pending_other": "{{count}} pendentes",
"stateClosed": "Fechado",
"stateDraft": "Rascunho",
"stateMerged": "Mergeado",
"stateOpen": "Aberto",
"unavailable": "Verificações indisponíveis"
},
"prCommentAudienceLabels": {
"all": "Todos",
"bots": "Bots",
"humans": "Humanos",
"noBotComments": "Nenhum comentário de bot.",
"noComments": "Ainda não há comentários.",
"noHumanComments": "Nenhum comentário humano."
},
"prSidebarCreateEmptyState": {
"branchNotLinked": "{{branch}} não está vinculada a um PR aberto.",
"branchRequired": "Faça checkout de uma branch antes de criar um pull request.",
"createFailed": "Falha ao criar o pull request.",
"createPr": "Criar PR",
"createPullRequest": "Criar pull request",
"currentBranchNotLinked": "A branch atual não está vinculada a um PR aberto.",
"linkExistingPr": "Vincular um PR existente",
"linkExistingPullRequest": "Vincular um pull request existente",
"linkedPrUnavailable": "PR vinculado #{{number}} indisponível",
"noOpenPullRequest": "Nenhum pull request aberto",
"refreshOrCreate": "Atualize para verificar novamente ou crie um novo PR para esta branch.",
"refreshPullRequest": "Atualizar pull request",
"title": "Pull request"
},
"prSidebarHeader": {
"cancelEditingTitle": "Cancelar edição do título",
"editTitle": "Editar título do pull request",
"openInBrowser": "Abrir pull request #{{number}} no navegador",
"openOnWeb": "Abrir pull request #{{number}} na web",
"saveTitle": "Salvar título"
},
"prViewPanel": {
"branchUnavailable": "Branch atual indisponível.",
"providerUnavailable": "Painel de revisão hospedada indisponível para este provedor."
},
"reviewerPickerDrawer": {
"loadFailed": "Falha ao carregar pessoas",
"noMatches": "Nenhuma pessoa encontrada",
"removeReviewer": "Remover {{login}}",
"requestReviewer": "Solicitar revisão de {{login}}",
"searchPeople": "Buscar pessoas",
"title": "Revisores"
},
"reviewersSection": {
"addOrRemove": "Adicionar ou remover revisores",
"approved": "Aprovado",
"changesRequested": "Alterações solicitadas",
"commented": "Comentou",
"dismissed": "Revisão descartada",
"loadFailed": "Não foi possível carregar os revisores. Toque em Atualizar para tentar novamente.",
"loading": "Carregando revisores…",
"noReviewers": "Nenhum revisor solicitado",
"pending": "Pendente",
"removeReviewer": "Remover {{login}}",
"requested": "Solicitado",
"reviewed": "Revisado",
"title": "Revisores"
}
},
"settings": {
"aboutScreen": {
"openLinkFailed": "Não foi possível abrir o link. Tente novamente.",
+778
View File
@@ -150,9 +150,14 @@
"common": {
"back": "Назад",
"cancel": "Отмена",
"close": "Закрыть",
"confirm": "Подтвердить",
"continue": "Продолжить",
"copied": "Скопировано",
"copy": "Копировать",
"delete": "Удалить",
"done": "Готово",
"edit": "Редактировать",
"ok": "ОК",
"remove": "Удалить",
"retry": "Повторить",
@@ -160,6 +165,485 @@
"settings": "Настройки",
"unknown": "Неизвестно"
},
"components": {
"accountsSnapshot": {
"invalidSnapshot": "Сервер передал некорректные данные учётных записей"
},
"agentLaunchNotice": {
"copyFailed": "Не удалось скопировать",
"copyPrompt": "Копировать запрос",
"statusUnreadable": "Не удалось получить статус этого сервера. Повторная попытка…",
"updateRequired": "Обновите Orca на компьютере, чтобы запустить агента с телефона."
},
"agentSpinner": {
"monitoring": "Наблюдает за фоновыми задачами",
"unconfirmed": "Не удалось подтвердить"
},
"agentStateDot": {
"monitoring": "Наблюдает за фоновыми задачами",
"unverifiable": "Давно не было обновлений"
},
"authFailedBanner": {
"message": "Ошибка аутентификации — попробуйте подключиться заново. Если ошибка повторяется, повторите сопряжение с компьютера."
},
"authFailedBannerActions": {
"repair": "Повторить сопряжение",
"webNote": "Подключитесь заново или удалите этот сервер из приложения Orca."
},
"codexResetCredit": {
"alreadyAppliedTitle": "Сброс уже выполнен",
"available_few": "Доступно {{count}} сброса",
"available_many": "Доступно {{count}} сбросов",
"available_one": "Доступен {{count}} сброс",
"available_other": "Доступно {{count}} сброса",
"expiryDaysHoursMultiple": "Ближайший срок истекает через {{days}} дн. {{hours}} ч",
"expiryDaysHoursSingle": "Срок истекает через {{days}} дн. {{hours}} ч",
"expiryDaysMultiple": "Ближайший срок истекает через {{days}} дн.",
"expiryDaysSingle": "Срок истекает через {{days}} дн.",
"expiryHoursMinutesMultiple": "Ближайший срок истекает через {{hours}} ч {{minutes}} мин",
"expiryHoursMinutesSingle": "Срок истекает через {{hours}} ч {{minutes}} мин",
"expiryHoursMultiple": "Ближайший срок истекает через {{hours}} ч",
"expiryHoursSingle": "Срок истекает через {{hours}} ч",
"expiryMinutesMultiple": "Ближайший срок истекает через {{minutes}} мин",
"expiryMinutesSingle": "Срок истекает через {{minutes}} мин",
"expiryNowMultiple": "Ближайший срок истекает сейчас",
"expiryNowSingle": "Срок истекает сейчас",
"invalidResponse": "Некорректный ответ сервера на запрос сброса",
"noResetMessage": "У этой учётной записи нет доступных бонусных сбросов лимитов.",
"noResetTitle": "Сброс недоступен",
"nothingToResetMessage": "Ни один из доступных для сброса лимитов Codex не исчерпан.",
"nothingToResetTitle": "Нечего сбрасывать",
"refresh": "Данные об использовании Codex обновлены.",
"resetTitle": "Лимиты сброшены"
},
"codexResetCreditAction": {
"activeAccountHint": "Использует один бонусный сброс для активной учётной записи Codex",
"earnedReset": "Бонусный сброс лимитов Codex",
"resetting": "Сброс…",
"resettingLabel": "Сброс лимитов Codex",
"scopedHint": "Использует один бонусный сброс для {{scopeLabel}}",
"useReset": "Сбросить",
"useResetLabel": "Использовать сброс лимитов Codex"
},
"customKeyModal": {
"add": "Добавить",
"addShortcut": "Добавить кнопку",
"command": "Команда",
"commandPlaceholder": "Например, pnpm build",
"editing": "Редактирование",
"function": "Функциональные клавиши",
"key": "Клавиша",
"label": "Название",
"labelPlaceholder": "Например, Сборка",
"manageShortcuts": "Управление кнопками",
"manageShortcutsHint": "Показывайте, скрывайте и меняйте порядок кнопок",
"modifiers": "Модификаторы",
"moreKeys": "Другие клавиши — Tab, стрелки, F1–F12…",
"navigation": "Навигация",
"pickKey": "Выберите клавишу",
"pressEnter": "Нажимать Enter",
"shortcutCombo": "Сочетание клавиш",
"shortcutComboHint": "Создайте сочетания с Ctrl, Alt и Shift",
"textMacro": "Текстовый макрос",
"textMacroHint": "Отправка заданной текстовой команды"
},
"dictationSetupSheet": {
"description": "Скачайте или выберите модель и включите диктовку на сопряжённом компьютере прямо здесь.",
"download": "Скачать",
"downloadFailed": "Не удалось скачать",
"downloadModel": "Скачать {{model}}",
"enabled": "Диктовка включена",
"inUse": "Используется",
"loadFailed": "Не удалось загрузить",
"modelExtracting": "{{details}} · распаковка…",
"modelProgress": "{{details}} · {{progress}}%",
"modelSize": "{{size}} МБ",
"recommended": "Рекомендуется",
"selectFailed": "Не удалось выбрать модель",
"title": "Настройка диктовки",
"updateFailed": "Не удалось обновить",
"use": "Использовать",
"useModel": "Использовать {{model}}"
},
"diffReviewBody": {
"binaryDiff": "Различия двоичных файлов",
"binaryMessage": "Этот файл нельзя отобразить как текст на мобильном устройстве.",
"deletedFile": "Файл удалён",
"deletedMessage": "Этот файл удалён. Добавьте заметку к файлу или отметьте его как проверенный.",
"diffTooLarge": "Слишком большой объём различий",
"diffTruncated": "В мобильном предпросмотре показана только часть различий.",
"diffUnavailable": "Сравнение недоступно",
"loadFailed": "Не удалось загрузить данные проверки",
"loadingDiff": "Загрузка различий…",
"loadingReview": "Загрузка данных проверки…",
"noReviewableChanges": "Нет изменений для проверки",
"retryLabel": "Повторить загрузку данных проверки",
"reviewUnavailable": "Проверка недоступна",
"selectFile": "Выберите файл для проверки.",
"tooLargeMessage": "Объём изменений слишком велик для предпросмотра на мобильном устройстве.",
"tryDifferentFilter": "Попробуйте другой фильтр проверки."
},
"diffReviewDrawers": {
"actionsTitle": "Действия при проверке",
"addNote": "Добавить заметку",
"cancelNote": "Отменить ввод заметки",
"clearSentNotes": "Удалить отправленные заметки",
"completeTitle": "Проверка завершена",
"copyNotes": "Скопировать заметки",
"deleteNote": "Удалить заметку",
"discard": "Отменить изменения",
"discardMessage": "Отменить изменения в файле «{{filePath}}»? Это действие необратимо.",
"discardTitle": "Отменить изменения в файле",
"editNote": "Изменить заметку",
"fileNote": "Заметка к файлу",
"fileSummary_few": "Проверен {{fileCount}} файл; {{count}} заметки",
"fileSummary_many": "Проверен {{fileCount}} файл; {{count}} заметок",
"fileSummary_one": "Проверен {{fileCount}} файл; {{count}} заметка",
"fileSummary_other": "Проверен {{fileCount}} файл; {{count}} заметки",
"filesSummary_few": "Проверено файлов: {{fileCount}}; {{count}} заметки",
"filesSummary_many": "Проверено файлов: {{fileCount}}; {{count}} заметок",
"filesSummary_one": "Проверено файлов: {{fileCount}}; {{count}} заметка",
"filesSummary_other": "Проверено файлов: {{fileCount}}; {{count}} заметки",
"lineNumber": "Строка {{lineNumber}}",
"loadingSessions": "Загрузка сессий агентов…",
"markUnreviewed": "Отметить непроверенным",
"newAgentSession": "Новая сессия агента",
"openInSession": "Открыть в сессии",
"reviewNote": "Заметка по проверке",
"saveLineNote": "Сохранить заметку к строке {{lineNumber}}",
"sendNotes": "Отправить заметки",
"sendNotesLabel": "Отправить заметки агенту",
"sendUnsentNotes": "Отправить неотправленные заметки",
"stageReviewed": "Добавить проверенные в индекс",
"stageReviewedFiles": "Добавить проверенные файлы в индекс",
"stageReviewedLabel": "Добавить проверенные файлы в индекс",
"stageableFiles_few": "В индекс можно добавить {{count}} проверенных файла с изменениями не в индексе",
"stageableFiles_many": "В индекс можно добавить {{count}} проверенных файлов с изменениями не в индексе",
"stageableFiles_one": "В индекс можно добавить {{count}} проверенный файл с изменениями не в индексе",
"stageableFiles_other": "В индекс можно добавить {{count}} проверенного файла с изменениями не в индексе",
"statusUnreadable": "Не удалось получить статус этого сервера. Повторная попытка…",
"terminal": "Терминал",
"terminalLabel": "{{title}} ({{id}})",
"unsentNotes_few": "{{count}} неотправленные заметки",
"unsentNotes_many": "{{count}} неотправленных заметок",
"unsentNotes_one": "{{count}} неотправленная заметка",
"unsentNotes_other": "{{count}} неотправленной заметки",
"updateRequired": "Обновите Orca на компьютере"
},
"diffReviewFileSummary": {
"branch": "Ветка",
"branchFrom": "Ветка · из {{oldPath}}",
"changed": "Изменён",
"editFileNote": "Изменить заметку к файлу",
"hunk": "Фрагмент",
"nextHunk": "Следующий фрагмент изменений",
"notes_few": "{{count}} заметки",
"notes_many": "{{count}} заметок",
"notes_one": "{{count}} заметка",
"notes_other": "{{count}} заметки",
"previousHunk": "Предыдущий фрагмент изменений",
"reviewed": "Проверен",
"staged": "В индексе",
"stagedFrom": "В индексе · из {{oldPath}}",
"stale": "Устарела",
"staleCount": "Устаревших: {{count}}",
"unstaged": "Не в индексе",
"unstagedFrom": "Не в индексе · из {{oldPath}}"
},
"diffReviewFooter": {
"addFileNote": "Добавить заметку к файлу",
"discard": "Отменить изменения",
"discardFile": "Отменить изменения в файле",
"markFileReviewed": "Отметить файл как проверенный",
"markReviewed": "Отметить проверенным",
"nextFile": "Следующий файл",
"note": "Заметка",
"previousFile": "Предыдущий файл",
"reviewed": "Проверен",
"stage": "Добавить в индекс",
"stageFile": "Добавить файл в индекс",
"unstage": "Убрать из индекса",
"unstageFile": "Убрать файл из индекса"
},
"diffReviewHeader": {
"filterAll": "Все",
"filterBranch": "Ветка",
"filterNotes": "Заметки",
"filterStaged": "В индексе",
"filterUnreviewed": "Непроверенные",
"filterUnstaged": "Не в индексе",
"openPullRequestSidebar": "Открыть боковую панель пул-реквеста",
"openReviewActions": "Открыть меню проверки",
"reviewedProgress": "Проверено {{reviewedCount}}/{{queueLength}}",
"showAllFiles": "Показать все файлы для проверки",
"showBranchFiles": "Показать файлы с изменениями в ветке",
"showNotesFiles": "Показать файлы с заметками",
"showStagedFiles": "Показать файлы с изменениями в индексе",
"showUnreviewedFiles": "Показать непроверенные файлы",
"showUnstagedFiles": "Показать файлы с изменениями не в индексе",
"title": "Изменения",
"unsentNotes_few": "{{count}} неотправленные заметки",
"unsentNotes_many": "{{count}} неотправленных заметок",
"unsentNotes_one": "{{count}} неотправленная заметка",
"unsentNotes_other": "{{count}} неотправленной заметки"
},
"diffReviewLine": {
"addNoteOnLine": "Добавить заметку к строке {{number}}",
"addedLine": "Добавленная строка",
"addedLineNumber": "Добавленная строка {{number}}",
"contextLine": "Строка контекста",
"contextLineNumber": "Строка контекста {{number}}",
"deletedLine": "Удалённая строка",
"deletedLineNumber": "Удалённая строка {{number}}",
"editNoteOnLine": "Изменить заметку к строке {{number}}"
},
"dragReorderRow": {
"dragToReorder": "Перетащите, чтобы изменить порядок",
"moveDown": "Переместить вниз",
"moveUp": "Переместить вверх",
"reorderHint": "Чтобы изменить порядок без перетаскивания, используйте действия «Переместить вверх» и «Переместить вниз»"
},
"homeQuickActions": {
"newWorkspace": "Новое рабочее пространство",
"pairDesktop": "Подключить компьютер",
"title": "Быстрые действия"
},
"hostCard": {
"hostActions": "Действия для {{title}}",
"openHost": "Открыть {{title}}",
"pairingInvalid": "Сопряжение недействительно",
"pairingTemporarilyUnavailable": "Сопряжение временно недоступно",
"repairHint": "Нажмите, чтобы повторно выполнить сопряжение с компьютером",
"unlockHint": "Разблокируйте телефон, затем нажмите, чтобы повторить попытку",
"updateDesktopHint": "Обновите Orca на компьютере и войдите в учётную запись, чтобы подключаться откуда угодно",
"worktreeAccessibilitySummary": "{{summary}}, активных: {{activeCount}}"
},
"hostDiagnosticsLink": {
"viewDiagnostics": "Открыть диагностику сети"
},
"hostProtocolGate": {
"checkingCompatibility": "Проверка совместимости сервера"
},
"hostRouteNoticeBanner": {
"dismissNotice": "Закрыть сообщение"
},
"htmlPreview": {
"preview": "Предпросмотр",
"previewRenderedHtml": "Предпросмотр HTML-страницы",
"source": "Исходный код",
"title": "Предпросмотр HTML",
"viewHtmlSource": "Показать исходный код HTML"
},
"jiraConnectPrompt": {
"checkAgain": "Проверить снова",
"connectHint": "Добавьте сайт Jira в Orca на компьютере в разделе «Настройки → Интеграции», затем проверьте снова.",
"connectOnDesktop": "Подключите Jira на компьютере",
"credentialUnreadable": "Не удалось прочитать сохранённые данные Jira: {{credentialError}}",
"updateDesktop": "Обновите Orca на компьютере"
},
"markdownBlocks": {
"copyCode": "Копировать код",
"openImage": "Открыть изображение"
},
"mountedBottomDrawer": {
"dismissDrawer": "Закрыть панель"
},
"newWorkspaceFab": {
"newWorkspace": "Новое рабочее пространство"
},
"newWorkspaceSetupScriptField": {
"hooks": "ХУКИ",
"label": "Скрипт настройки",
"run": "Запустить",
"runSetupCommand": "Выполнить команду настройки",
"skip": "Пропустить"
},
"newWorkspaceSshConnectionField": {
"connect": "Подключиться",
"connecting": "Подключение…",
"label": "Подключение по SSH",
"serverRecovery": "Восстановите сервер в Orca на компьютере: Настройки → SSH-серверы → Восстановить сервер."
},
"newWorktreeAgentSelection": {
"blankTerminal": "Пустой терминал"
},
"newWorktreeFormSheet": {
"advanced": "Дополнительно",
"agent": "Агент",
"connectTarget": "Подключить целевой сервер",
"connectTargetFirst": "Сначала подключите целевой сервер",
"createWorktree": "Создать рабочее дерево",
"nameOrCreateFrom": "Название или источник",
"noProjectsFound": "Проекты не найдены",
"note": "Заметка",
"notePlaceholder": "Написать заметку",
"workspaceName": "Название рабочего пространства"
},
"newWorktreeModal": {
"remoteRepository": "Удалённый репозиторий"
},
"newWorktreeModalDrawers": {
"agent": "Агент",
"project": "Проект",
"runOn": "Запустить на"
},
"newWorktreeProjectTargetFields": {
"project": "Проект",
"runOn": "Запустить на",
"selectProject": "Выберите проект",
"selectRunTarget": "Выберите сервер для запуска"
},
"prSidebar": {
"loadFailed": "Что-то пошло не так.",
"loading": "Загрузка пул-реквеста…",
"notPermitted": "Доступ запрещён — ваша учётная запись GitHub не подключена.",
"retryLoading": "Повторить загрузку пул-реквеста"
},
"protocolBlockScreen": {
"backToHosts": "Вернуться к серверам",
"desktopTooOldBody": "Версия Orca на сопряжённом компьютере устарела и несовместима с текущей версией Orca Mobile. Обновите Orca на компьютере, затем снова подключитесь к этому серверу.",
"getVersion": "Скачать Orca {{version}}",
"mobileTooOldAppStore": "Для работы с Orca на этом компьютере нужна более новая версия Orca Mobile. Обновите Orca Mobile через App Store, затем снова подключитесь к этому серверу.",
"mobileTooOldGitHubReleases": "Для работы с Orca на этом компьютере нужна более новая версия Orca Mobile. Обновите Orca Mobile через GitHub Releases, затем снова подключитесь к этому серверу.",
"openAppStore": "Открыть App Store",
"openGitHubReleases": "Открыть GitHub Releases",
"refreshRecoveryNote": "Если сообщение не исчезнет, удалите этот сервер и выполните сопряжение заново.",
"refreshWorkspaceBody": "Версия рабочего пространства этого сервера в кэше старее той, которую ожидает Orca на компьютере. Подключитесь к этому серверу заново, чтобы загрузить актуальную версию.",
"refreshWorkspaceTitle": "Обновите мобильное рабочее пространство",
"updateDesktopTitle": "Обновите Orca на компьютере",
"updateMobileTitle": "Обновите Orca Mobile",
"updateRecoveryNote": "Уже обновили? Вернитесь в раздел «Серверы» и обновите подключение. Если сообщение не исчезнет, удалите этот сервер и выполните сопряжение заново.",
"workspaceNeedsUpdateAppStore": "Для мобильного рабочего пространства этого компьютера нужна более новая версия Orca Mobile. Обновите Orca Mobile через App Store, затем снова подключитесь к этому серверу.",
"workspaceNeedsUpdateGitHubReleases": "Для мобильного рабочего пространства этого компьютера нужна более новая версия Orca Mobile. Обновите Orca Mobile через GitHub Releases, затем снова подключитесь к этому серверу."
},
"richMarkdownEditor": {
"imageUrl": "URL изображения",
"insert": "Вставить",
"linkUrl": "URL ссылки"
},
"richMarkdownToolbar": {
"body": "Обычный текст",
"bold": "Полужирный",
"bulletList": "Маркированный список",
"checklist": "Список задач",
"codeBlock": "Блок кода",
"heading1": "H1",
"heading2": "H2",
"heading3": "H3",
"image": "Изображение",
"inlineCode": "Код в строке",
"italic": "Курсив",
"link": "Ссылка",
"numberedList": "Нумерованный список",
"quote": "Цитата",
"strike": "Зачёркнутый"
},
"searchField": {
"clearSearch": "Очистить поиск"
},
"setupHookTrustDrawer": {
"alwaysTrustAndRun": "Всегда доверять и выполнять",
"description": "Команды из orca.yaml этого репозитория выполняются перед запуском рабочего пространства. Запускайте их, только если доверяете этому репозиторию.",
"dontRun": "Не выполнять",
"newScriptLabel": "Новый скрипт настройки",
"runHooks": "Выполнить хуки",
"runSetupTitle": "Запустить скрипт настройки из {{repoName}}?",
"scriptChangedTitle": "Изменился скрипт настройки в {{repoName}}",
"scriptLabel": "Скрипт настройки"
},
"smartWorkspaceAdvancedFields": {
"branchName": "Название ветки",
"derivedFromName": "На основе названия",
"name": "Название",
"reuseBranch": "Повторно использовать ветку «{{branch}}»",
"workspaceName": "Название рабочего пространства"
},
"smartWorkspaceSourceDrawer": {
"connectRepository": "Подключите репозиторий для поиска источников.",
"crossRepoMessage": "Этот элемент находится в репозитории {{owner}}/{{repo}}.",
"gitHubRemoteRequired": "Для просмотра задач и PR этому репозиторию на SSH-сервере нужен удалённый репозиторий на GitHub.",
"noResults": "Ничего не найдено.",
"searchPlaceholder": "Введите название или найдите источник",
"switchRepo": "Переключиться на {{repoName}}",
"title": "Название или источник",
"typeWorkspaceName": "Введите название рабочего пространства в поле ниже."
},
"smartWorkspaceSourceField": {
"openSelectedSource": "Открыть выбранный источник",
"optional": "[Необязательно]",
"searchPlaceholder": "Введите название или найдите источник"
},
"smartWorkspaceSourceRow": {
"createBranch": "Создать ветку «{{name}}»",
"issue": "Задача #{{number}}",
"mergeRequest": "MR !{{number}}",
"nameWorkspace": "Назвать это рабочее пространство",
"newBranch": "Новая ветка",
"pullRequest": "PR #{{number}}",
"useName": "Использовать «{{name}}»"
},
"sourceCopyButton": {
"clipboardRejected": "Не удалось записать текст в буфер обмена.",
"copiedLoaded": "Загруженное скопировано",
"copyFailed": "Не удалось скопировать",
"copyLoaded": "Скопировать загруженное",
"couldNotCopy": "Не удалось скопировать",
"failed": "Ошибка"
},
"terminalShortcutSettings": {
"addDescription": "Создать сочетание клавиш или текстовый макрос",
"addShortcut": "Добавить своё сочетание клавиш…",
"customShortcuts": "СВОИ СОЧЕТАНИЯ КЛАВИШ",
"empty": "Своих сочетаний клавиш пока нет.",
"removeShortcut": "Удалить {{label}}",
"resetDefaults": "Сбросить настройки",
"resetDescription": "Показать все встроенные клавиши в исходном порядке",
"shortcutBar": "ПАНЕЛЬ СОЧЕТАНИЙ КЛАВИШ",
"shortcutBarDescription": "Используйте переключатели для показа или скрытия клавиш. Чтобы изменить их порядок на панели терминала, удерживайте значок перетаскивания и перемещайте клавишу."
},
"useCodexResetCreditAction": {
"confirmTitle": "Использовать сброс лимитов?",
"detailsChanged": "Параметры сброса изменились",
"detailsChangedMessage": "Учётная запись или условия сброса изменились до того, как сервер связался с Codex. Проверьте обновлённые данные и подтвердите сброс ещё раз.",
"hostAccount": "{{identity}} на сервере",
"hostAccountConfirmMessage": "Будет использован один бонусный сброс лимитов для учётной записи {{identity}} на сервере. Все лимиты, для которых доступен сброс, будут сброшены сразу.",
"hostSelectedAccount": "выбранная управляемая учётная запись на сервере",
"hostSelectedAccountConfirmMessage": "Будет использован один бонусный сброс лимитов для выбранной управляемой учётной записи на сервере. Все лимиты, для которых доступен сброс, будут сброшены сразу.",
"resetFailed": "Не удалось сбросить лимиты",
"wslAccount": "{{identity}} в WSL {{distro}}",
"wslAccountConfirmMessage": "Будет использован один бонусный сброс лимитов для учётной записи {{identity}} в WSL {{distro}}. Все лимиты, для которых доступен сброс, будут сброшены сразу.",
"wslSelectedAccount": "выбранная управляемая учётная запись в WSL {{distro}}",
"wslSelectedAccountConfirmMessage": "Будет использован один бонусный сброс лимитов для выбранной управляемой учётной записи в WSL {{distro}}. Все лимиты, для которых доступен сброс, будут сброшены сразу."
},
"voiceModelList": {
"deleteModel": "Удалить {{label}}",
"downloadModel": "Скачать {{label}}",
"inUse": "Используется",
"modelExtracting": "{{size}} · распаковка…",
"modelProgress": "{{size}} · {{progress}}%",
"modelSize": "{{size}} МБ",
"recommended": "Рекомендуется",
"use": "Использовать",
"useModel": "Использовать {{label}}"
},
"workspaceDetailPlaceholder": {
"description": "Выберите рабочее пространство на боковой панели, чтобы открыть его терминал здесь.",
"title": "Рабочее пространство не открыто"
},
"workspaceNameInput": {
"placeholder": "Название рабочего пространства"
},
"worktreeListRow": {
"child": "Дочернее",
"children_few": "{{count}} дочерних",
"children_many": "{{count}} дочерних",
"children_one": "{{count}} дочернее",
"children_other": "{{count}} дочернего",
"folder": "Папка",
"project": "Проект {{repo}}",
"remoteHost": "Удалённый сервер"
}
},
"connection": {
"connectionHealth": {
"checkConnection": "Проверьте подключение",
@@ -577,6 +1061,300 @@
"useChatUi": "Использовать чат"
}
},
"pullRequest": {
"checksSection": {
"actionRequired": "Требуется действие",
"cancelled": "Отменено",
"checkDetails": "Сведения о проверке {{name}}",
"failed": "Ошибка",
"failing": "С ошибкой: {{count}}",
"failingChecks_few": "{{count}} проверки с ошибками",
"failingChecks_many": "{{count}} проверок с ошибками",
"failingChecks_one": "{{count}} проверка с ошибками",
"failingChecks_other": "{{count}} проверки с ошибками",
"fix": "Исправить",
"fixFailing": "Исправить ошибки проверок с ИИ",
"inProgress": "Выполняется",
"inspectOrFix": "Изучите сведения или запустите исправление с ИИ.",
"loadDetailsFailed": "Не удалось загрузить сведения о проверке",
"neutral": "Нейтрально",
"neutralCount": "Нейтрально: {{count}}",
"noChecks": "Нет проверок",
"openOnWeb": "Открыть {{name}} в браузере",
"passed": "Успешно: {{count}}",
"pending": "Ожидает",
"pendingCount": "В ожидании: {{count}}",
"queued": "В очереди",
"rerunFailing": "Перезапустить проверки с ошибками",
"skipped": "Пропущена",
"successful": "Успешно",
"timedOut": "Время ожидания истекло",
"title": "Проверки",
"unavailable": "Проверки недоступны"
},
"commentCard": {
"daysAgo_few": "{{count}} дн. назад",
"daysAgo_many": "{{count}} дн. назад",
"daysAgo_one": "{{count}} дн. назад",
"daysAgo_other": "{{count}} дн. назад",
"deleteComment": "Удалить комментарий",
"deleteConfirmMessage": "Комментарий на GitHub будет удалён безвозвратно.",
"deleteConfirmTitle": "Удалить комментарий?",
"editComment": "Изменить комментарий",
"editPlaceholder": "Измените комментарий…",
"hoursAgo_few": "{{count}} ч назад",
"hoursAgo_many": "{{count}} ч назад",
"hoursAgo_one": "{{count}} ч назад",
"hoursAgo_other": "{{count}} ч назад",
"justNow": "только что",
"minutesAgo_few": "{{count}} мин. назад",
"minutesAgo_many": "{{count}} мин. назад",
"minutesAgo_one": "{{count}} мин. назад",
"minutesAgo_other": "{{count}} мин. назад",
"monthsAgo_few": "{{count}} мес. назад",
"monthsAgo_many": "{{count}} мес. назад",
"monthsAgo_one": "{{count}} мес. назад",
"monthsAgo_other": "{{count}} мес. назад",
"openOnGitHub": "Открыть комментарий на GitHub",
"reply": "Ответить",
"replyPlaceholder": "Напишите ответ…",
"replyToComment": "Ответить на комментарий",
"resolve": "Закрыть",
"resolveThread": "Закрыть обсуждение",
"resolved": "обсуждение закрыто",
"unresolve": "Открыть заново",
"unresolveThread": "Открыть обсуждение заново",
"yearsAgo_few": "{{count}} года назад",
"yearsAgo_many": "{{count}} лет назад",
"yearsAgo_one": "{{count}} год назад",
"yearsAgo_other": "{{count}} года назад"
},
"commentsSection": {
"addCommentPlaceholder": "Добавить комментарий…",
"all": "Все",
"bots": "Боты",
"comment": "Добавить комментарий",
"commentsLoadFailed": "Не удалось загрузить комментарии. Нажмите кнопку обновления, чтобы повторить попытку.",
"description": "Описание",
"descriptionLoadFailed": "Не удалось загрузить описание. Нажмите кнопку обновления, чтобы повторить попытку.",
"humans": "Люди",
"noBotComments": "Комментариев ботов нет.",
"noComments": "Комментариев пока нет.",
"noDescription": "Описание не добавлено.",
"noHumanComments": "Комментариев людей нет.",
"resolvedComment_few": "Закрытый комментарий от {{author}} ({{count}})",
"resolvedComment_many": "Закрытый комментарий от {{author}} ({{count}})",
"resolvedComment_one": "Закрытый комментарий от {{author}}",
"resolvedComment_other": "Закрытый комментарий от {{author}} ({{count}})",
"resolvedThread_few": "Закрытое обсуждение от {{author}} ({{count}})",
"resolvedThread_many": "Закрытое обсуждение от {{author}} ({{count}})",
"resolvedThread_one": "Закрытое обсуждение от {{author}}",
"resolvedThread_other": "Закрытое обсуждение от {{author}} ({{count}})",
"showMore": "Показать ещё {{count}}",
"showMoreOf": "Показать ещё {{count}} из {{remaining}}",
"title": "Комментарии"
},
"conflictingFilesSection": {
"commitsBehind_few": "Отставание на {{count}} коммита (базовый коммит: {{baseCommit}})",
"commitsBehind_many": "Отставание на {{count}} коммитов (базовый коммит: {{baseCommit}})",
"commitsBehind_one": "Отставание на {{count}} коммит (базовый коммит: {{baseCommit}})",
"commitsBehind_other": "Отставание на {{count}} коммита (базовый коммит: {{baseCommit}})",
"copyCommands": "Скопировать команды",
"copyFailed": "Не удалось скопировать текст",
"copyRefreshCommands": "Скопировать команды для повторной проверки возможности слияния",
"detailsUnavailable": "Сведения о конфликтующих файлах недоступны",
"filesHeading": "Конфликтующие файлы",
"localMergeClean": "GitHub сообщает о конфликтах, но локальный Git их не воспроизвёл. Обновите PR или отправьте ветку, чтобы повторно проверить возможность слияния.",
"mustResolve": "В этой ветке есть конфликты, которые нужно разрешить",
"refreshing": "Обновление сведений о конфликтах…",
"resolveWithAi": "Разрешить конфликты с помощью ИИ",
"runFromWorktree": "Выполните в этом рабочем дереве",
"title": "Конфликты"
},
"linkPrForm": {
"linkButton": "Привязать пул-реквест",
"linkNumber": "Привязать #{{number}}",
"referenceLabel": "Номер PR или URL GitHub",
"referencePlaceholder": "#123 или https://github.com/owner/repo/pull/123",
"title": "Привязать существующий пул-реквест"
},
"markdownBlocks": {
"details": "Подробности"
},
"prActionsSection": {
"autoMergeAccessibilityLabel": "Переключить автослияние",
"autoMergeLabel": "Автослияние по готовности",
"autoMergeOff": "Выключено",
"autoMergeOn": "Включено",
"closeAccessibilityLabel": "Закрыть пул-реквест",
"closeConfirmMessage": "Пул-реквест #{{number}} будет закрыт без слияния.",
"closeConfirmTitle": "Закрыть пул-реквест?",
"markReadyAccessibilityLabel": "Отметить пул-реквест как готовый к проверке",
"markReadyButton": "Передать на проверку",
"mergeAccessibilityLabel": "Слить пул-реквест",
"mergeButton": "Слить",
"mergeConfirmTitle": "Слить пул-реквест?",
"recheckAccessibilityLabel": "Повторно проверить статус слияния",
"recheckButton": "Проверить снова",
"reopenAccessibilityLabel": "Открыть пул-реквест заново",
"reopenButton": "Открыть заново",
"reopenConfirmMessage": "Пул-реквест #{{number}} будет открыт заново.",
"reopenConfirmTitle": "Открыть пул-реквест заново?",
"unlinkAccessibilityLabel": "Отвязать пул-реквест",
"unlinkButton": "Отвязать",
"unlinkFailed": "Не удалось отвязать пул-реквест."
},
"prActionsState": {
"behindHint": "Обновите ветку перед слиянием",
"behindLabel": "Отстаёт",
"blockedHint": "По данным GitHub, этот пул-реквест заблокирован",
"blockedLabel": "Заблокировано",
"changesRequestedHint": "В этом пул-реквесте запрошены изменения на GitHub",
"changesRequestedLabel": "Запрошены изменения",
"checkingHint": "GitHub ещё определяет статус слияния этого пул-реквеста",
"checkingLabel": "Проверка",
"checksPassedHint": "Проверки пройдены. Перед слиянием возможность слияния будет проверена ещё раз.",
"checksPassedLabel": "Проверки пройдены",
"closedHint": "Этот пул-реквест закрыт",
"closedLabel": "Закрыт",
"conflictsHint": "GitHub сообщает о конфликтах слияния",
"conflictsLabel": "Конфликты",
"draftHint": "Этот пул-реквест ещё в черновике",
"draftLabel": "Черновик",
"mergeConfirmMessage": "Пул-реквест #{{number}} будет слит с его базовой веткой.",
"mergeQueueAutoMergeOnHint": "Эта базовая ветка использует очередь слияния GitHub",
"mergeQueueAutoMergeOnLabel": "Автослияние включено",
"mergeQueueHint": "Эта базовая ветка использует очередь слияния GitHub",
"mergeQueueLabel": "Слить, когда будет готово",
"mergeStackMessage": "GitHub выполнит слияние этого PR и всех PR ниже него в стеке.",
"mergeStatusUnavailableHint": "Статус слияния этого PR недоступен",
"mergeStatusUnavailableLabel": "Слить",
"mergeThrough_few": "Слить до #{{number}} включительно · {{count}} PR",
"mergeThrough_many": "Слить до #{{number}} включительно · {{count}} PR",
"mergeThrough_one": "Слить до #{{number}} включительно · {{count}} PR",
"mergeThrough_other": "Слить до #{{number}} включительно · {{count}} PR",
"mergeableChecksFailedHint": "По данным GitHub, этот PR можно слить, но некоторые проверки завершились ошибками",
"mergeableChecksFailedLabel": "Проверки не пройдены",
"mergeableChecksPassedHint": "По данным GitHub, PR можно слить и проверки пройдены",
"mergeableChecksPassedLabel": "Можно слить",
"mergeableChecksPendingHint": "По данным GitHub, этот PR можно слить, но проверки ещё выполняются",
"mergeableChecksPendingLabel": "Проверки не завершены",
"mergeableHint": "По данным GitHub, PR можно слить",
"mergeableLabel": "Можно слить",
"mergedHint": "Этот пул-реквест уже слит",
"mergedLabel": "Слит",
"methodMerge": "Создать коммит слияния",
"methodRebase": "Перебазировать и слить",
"methodSquash": "Объединить коммиты и слить",
"queueStackMessage": "GitHub добавит этот PR и все PR ниже него в очередь слияния.",
"queueThrough_few": "Поставить в очередь до #{{number}} включительно · {{count}} PR",
"queueThrough_many": "Поставить в очередь до #{{number}} включительно · {{count}} PR",
"queueThrough_one": "Поставить в очередь до #{{number}} включительно · {{count}} PR",
"queueThrough_other": "Поставить в очередь до #{{number}} включительно · {{count}} PR",
"reviewRequiredHint": "Перед слиянием этого пул-реквеста GitHub требует одобрения проверяющего",
"reviewRequiredLabel": "Требуется одобрение",
"stackBehind": "#{{number}} необходимо обновить.",
"stackBlocked": "#{{number}} заблокирован.",
"stackChangesRequested": "В #{{number}} запрошены изменения.",
"stackClosed": "#{{number}} закрыт.",
"stackConflicts": "В #{{number}} есть конфликты слияния.",
"stackDraft": "#{{number}} ещё в черновике.",
"stackReviewRequired": "Для #{{number}} ещё требуется одобрение проверяющего."
},
"prCheckDetail": {
"annotations": "Аннотации",
"annotationsTruncated": "Показаны первые 20 аннотаций",
"jobsTruncated": "Показаны первые 100 заданий",
"noDetails": "Подробности недоступны."
},
"prCheckDetailContent": {
"annotation": "Примечание",
"failedJobs": "Неудачные задания",
"jobs": "Задания",
"unknown": "неизвестно"
},
"prChecksPresentation": {
"failing_few": "{{count}} не пройдены",
"failing_many": "{{count}} не пройдено",
"failing_one": "{{count}} не пройдена",
"failing_other": "{{count}} не пройдено",
"neutral_few": "{{count}} нейтральные",
"neutral_many": "{{count}} нейтральных",
"neutral_one": "{{count}} нейтральная",
"neutral_other": "{{count}} нейтральной",
"noChecks": "Нет проверок",
"passed_few": "{{count}} пройдены",
"passed_many": "{{count}} пройдено",
"passed_one": "{{count}} пройдена",
"passed_other": "{{count}} пройдено",
"pending_few": "{{count}} в ожидании",
"pending_many": "{{count}} в ожидании",
"pending_one": "{{count}} в ожидании",
"pending_other": "{{count}} в ожидании",
"stateClosed": "Закрыт",
"stateDraft": "Черновик",
"stateMerged": "Слит",
"stateOpen": "Открыт",
"unavailable": "Проверки недоступны"
},
"prCommentAudienceLabels": {
"all": "Все",
"bots": "Боты",
"humans": "Люди",
"noBotComments": "Комментариев ботов нет.",
"noComments": "Комментариев пока нет.",
"noHumanComments": "Комментариев людей нет."
},
"prSidebarCreateEmptyState": {
"branchNotLinked": "Ветка {{branch}} не связана с открытым PR.",
"branchRequired": "Переключитесь на ветку перед созданием пул-реквеста.",
"createFailed": "Не удалось создать пул-реквест.",
"createPr": "Создать PR",
"createPullRequest": "Создать пул-реквест",
"currentBranchNotLinked": "Текущая ветка не связана с открытым PR.",
"linkExistingPr": "Связать с существующим PR",
"linkExistingPullRequest": "Связать с существующим пул-реквестом",
"linkedPrUnavailable": "Связанный PR #{{number}} недоступен",
"noOpenPullRequest": "Нет открытого пул-реквеста",
"refreshOrCreate": "Обновите данные, чтобы проверить ещё раз, или создайте новый PR для этой ветки.",
"refreshPullRequest": "Обновить пул-реквест",
"title": "Пул-реквест"
},
"prSidebarHeader": {
"cancelEditingTitle": "Отменить изменение заголовка",
"editTitle": "Изменить заголовок пул-реквеста",
"openInBrowser": "Открыть пул-реквест #{{number}} в браузере",
"openOnWeb": "Открыть пул-реквест #{{number}} в браузере",
"saveTitle": "Сохранить заголовок"
},
"prViewPanel": {
"branchUnavailable": "Текущая ветка недоступна.",
"providerUnavailable": "Панель проверки пул-реквестов недоступна для этого провайдера."
},
"reviewerPickerDrawer": {
"loadFailed": "Не удалось загрузить пользователей",
"noMatches": "Подходящие пользователи не найдены",
"removeReviewer": "Убрать {{login}} из рецензентов",
"requestReviewer": "Запросить проверку у {{login}}",
"searchPeople": "Поиск пользователей",
"title": "Рецензенты"
},
"reviewersSection": {
"addOrRemove": "Добавить или удалить рецензентов",
"approved": "Одобрено",
"changesRequested": "Запрошены изменения",
"commented": "Оставлен комментарий",
"dismissed": "Проверка отозвана",
"loadFailed": "Не удалось загрузить рецензентов. Нажмите кнопку обновления, чтобы повторить попытку.",
"loading": "Загрузка рецензентов…",
"noReviewers": "Нет запросов на проверку",
"pending": "Ожидает отправки",
"removeReviewer": "Удалить {{login}}",
"requested": "Проверка запрошена",
"reviewed": "Проверено",
"title": "Рецензенты"
}
},
"settings": {
"aboutScreen": {
"openLinkFailed": "Не удалось открыть ссылку. Повторите попытку.",
+732
View File
@@ -142,9 +142,14 @@
"common": {
"back": "Geri",
"cancel": "İptal",
"close": "Kapat",
"confirm": "Onayla",
"continue": "Devam Et",
"copied": "Kopyalandı",
"copy": "Kopyala",
"delete": "Sil",
"done": "Bitti",
"edit": "Düzenle",
"ok": "Tamam",
"remove": "Kaldır",
"retry": "Yeniden dene",
@@ -152,6 +157,469 @@
"settings": "Ayarlar",
"unknown": "Bilinmiyor"
},
"components": {
"accountsSnapshot": {
"invalidSnapshot": "Sunucudan gelen hesap anlık görüntüsü geçersiz"
},
"agentLaunchNotice": {
"copyFailed": "Kopyalanamadı",
"copyPrompt": "İstemi kopyala",
"statusUnreadable": "Bu ana makinenin durumu okunamadı. Yeniden deneniyor…",
"updateRequired": "Telefonunuzdan ajan başlatmak için bilgisayarınızdaki Orca'yı güncelleyin."
},
"agentSpinner": {
"monitoring": "Arka plan görevleri izleniyor",
"unconfirmed": "Doğrulanamadı"
},
"agentStateDot": {
"monitoring": "Arka plan görevleri izleniyor",
"unverifiable": "Yakın zamanda güncelleme yok"
},
"authFailedBanner": {
"message": "Kimlik doğrulanamadı — önce yeniden bağlanmayı deneyin; sorun sürerse masaüstü uygulamasından yeniden eşleştirin."
},
"authFailedBannerActions": {
"repair": "Yeniden eşleştir",
"webNote": "Orca uygulamasından bu ana makineye yeniden bağlanın veya ana makineyi kaldırın."
},
"codexResetCredit": {
"alreadyAppliedTitle": "Sıfırlama zaten uygulandı",
"available_one": "{{count}} sıfırlama hakkı var",
"available_other": "{{count}} sıfırlama hakkı var",
"expiryDaysHoursMultiple": "Sıradaki {{days}} gün {{hours}} sa sonra sona eriyor",
"expiryDaysHoursSingle": "{{days}} gün {{hours}} sa sonra sona eriyor",
"expiryDaysMultiple": "Sıradaki {{days}} gün sonra sona eriyor",
"expiryDaysSingle": "{{days}} gün sonra sona eriyor",
"expiryHoursMinutesMultiple": "Sıradaki {{hours}} sa {{minutes}} dk sonra sona eriyor",
"expiryHoursMinutesSingle": "{{hours}} sa {{minutes}} dk sonra sona eriyor",
"expiryHoursMultiple": "Sıradaki {{hours}} sa sonra sona eriyor",
"expiryHoursSingle": "{{hours}} sa sonra sona eriyor",
"expiryMinutesMultiple": "Sıradaki {{minutes}} dk sonra sona eriyor",
"expiryMinutesSingle": "{{minutes}} dk sonra sona eriyor",
"expiryNowMultiple": "Sıradaki şimdi sona eriyor",
"expiryNowSingle": "Şimdi sona eriyor",
"invalidResponse": "Sunucudan geçersiz sıfırlama yanıtı",
"noResetMessage": "Bu hesabın kullanabileceği kazanılmış sıfırlama kredisi yok.",
"noResetTitle": "Sıfırlama hakkı yok",
"nothingToResetMessage": "Sıfırlamaya uygun hiçbir Codex hız sınırı penceresinde kullanım hakkı tükenmedi.",
"nothingToResetTitle": "Sıfırlanacak bir şey yok",
"refresh": "Codex kullanım bilgileri güncellendi.",
"resetTitle": "Hız sınırları sıfırlandı"
},
"codexResetCreditAction": {
"activeAccountHint": "Etkin Codex hesabı için kazanılmış bir sıfırlama hakkı kullanır",
"earnedReset": "Kazanılmış Codex hız sınırı sıfırlama hakkı",
"resetting": "Sıfırlanıyor…",
"resettingLabel": "Codex hız sınırları sıfırlanıyor",
"scopedHint": "{{scopeLabel}} için kazanılmış bir sıfırlama hakkı kullanır",
"useReset": "Sıfırla",
"useResetLabel": "Codex hız sınırı sıfırlama hakkını kullan"
},
"customKeyModal": {
"add": "Ekle",
"addShortcut": "Kısayol Ekle",
"command": "Komut",
"commandPlaceholder": "Örn. pnpm build",
"editing": "Düzenleme",
"function": "İşlev",
"key": "Tuş",
"label": "Etiket",
"labelPlaceholder": "Örn. Derle",
"manageShortcuts": "Kısayolları Yönet",
"manageShortcutsHint": "Kısayol tuşlarını gösterin, gizleyin veya yeniden sıralayın",
"modifiers": "Değiştirici Tuşlar",
"moreKeys": "Diğer tuşlar — Tab, ok tuşları, F1–F12…",
"navigation": "Gezinme",
"pickKey": "Bir tuş seçin",
"pressEnter": "Enter Tuşuna Bas",
"shortcutCombo": "Tuş Birleşimi",
"shortcutComboHint": "Ctrl, Alt ve Shift ile tuş birleşimleri oluşturun",
"textMacro": "Metin Makrosu",
"textMacroHint": "Özel metin komutu gönderin"
},
"dictationSetupSheet": {
"description": "Bir model indirin veya seçin ve eşleştirilmiş bilgisayarda sesli yazmayı etkinleştirin — tümünü buradan yapabilirsiniz.",
"download": "İndir",
"downloadFailed": "İndirme tamamlanamadı",
"downloadModel": "{{model}} modelini indir",
"enabled": "Sesli yazma etkin",
"inUse": "Kullanımda",
"loadFailed": "Yüklenemedi",
"modelExtracting": "{{details}} · arşivden çıkarılıyor…",
"modelProgress": "{{details}} · %{{progress}}",
"modelSize": "{{size}} MB",
"recommended": "Önerilen",
"selectFailed": "Model seçilemedi",
"title": "Sesli yazmayı ayarlayın",
"updateFailed": "Güncellenemedi",
"use": "Kullan",
"useModel": "{{model}} modelini kullan"
},
"diffReviewBody": {
"binaryDiff": "İkili Diff",
"binaryMessage": "Bu dosya mobilde metin olarak görüntülenemez.",
"deletedFile": "Silinmiş Dosya",
"deletedMessage": "Bu dosya silindi. Dosya notu ekleyin veya incelendi olarak işaretleyin.",
"diffTooLarge": "Diff Çok Büyük",
"diffTruncated": "Diff mobil önizleme için kısaltıldı.",
"diffUnavailable": "Diff Kullanılamıyor",
"loadFailed": "İnceleme Yüklenemedi",
"loadingDiff": "Diff yükleniyor…",
"loadingReview": "İnceleme yükleniyor…",
"noReviewableChanges": "İncelenebilir Değişiklik Yok",
"retryLabel": "İncelemeyi yeniden yükle",
"reviewUnavailable": "İnceleme Kullanılamıyor",
"selectFile": "İncelemek için bir dosya seçin.",
"tooLargeMessage": "Bu diff, mobil önizleme için çok büyük.",
"tryDifferentFilter": "Farklı bir inceleme filtresi deneyin."
},
"diffReviewDrawers": {
"actionsTitle": "İnceleme İşlemleri",
"addNote": "Not Ekle",
"cancelNote": "Notu iptal et",
"clearSentNotes": "Gönderilen Notları Temizle",
"completeTitle": "İnceleme Tamamlandı",
"copyNotes": "Notları Kopyala",
"deleteNote": "Notu sil",
"discard": "At",
"discardMessage": "“{{filePath}}” dosyasındaki değişiklikler atılsın mı? Bu işlem geri alınamaz.",
"discardTitle": "Dosya Değişikliklerini At",
"editNote": "Notu Düzenle",
"fileNote": "Dosya notu",
"fileSummary_one": "{{fileCount}} dosya incelendi, {{count}} not",
"fileSummary_other": "{{fileCount}} dosya incelendi, {{count}} not",
"filesSummary_one": "{{fileCount}} dosya incelendi, {{count}} not",
"filesSummary_other": "{{fileCount}} dosya incelendi, {{count}} not",
"lineNumber": "Satır {{lineNumber}}",
"loadingSessions": "Ajan oturumları yükleniyor…",
"markUnreviewed": "İncelenmedi Olarak İşaretle",
"newAgentSession": "Yeni Ajan Oturumu",
"openInSession": "Oturumda Aç",
"reviewNote": "İnceleme notu",
"saveLineNote": "{{lineNumber}}. satırdaki notu kaydet",
"sendNotes": "Notları Gönder",
"sendNotesLabel": "Notları ajana gönder",
"sendUnsentNotes": "Gönderilmemiş Notları Gönder",
"stageReviewed": "İncelenenleri Stage Et",
"stageReviewedFiles": "İncelenen Dosyaları Stage Et",
"stageReviewedLabel": "İncelenen dosyaları stage et",
"stageableFiles_one": "İncelenen ve henüz stage edilmemiş {{count}} dosya stage edilebilir",
"stageableFiles_other": "İncelenen ve henüz stage edilmemiş {{count}} dosya stage edilebilir",
"statusUnreadable": "Bu sunucunun durumu okunamadı. Yeniden deneniyor…",
"terminal": "Terminal",
"terminalLabel": "{{title}} ({{id}})",
"unsentNotes_one": "{{count}} gönderilmemiş not",
"unsentNotes_other": "{{count}} gönderilmemiş not",
"updateRequired": "Bilgisayarınızdaki Orca'yı güncelleyin"
},
"diffReviewFileSummary": {
"branch": "Branch",
"branchFrom": "Branch · önceki yol: {{oldPath}}",
"changed": "Değişti",
"editFileNote": "Dosya notunu düzenle",
"hunk": "Blok",
"nextHunk": "Sonraki değişiklik bloğu",
"notes_one": "{{count}} not",
"notes_other": "{{count}} not",
"previousHunk": "Önceki değişiklik bloğu",
"reviewed": "İncelendi",
"staged": "Hazırlanmış",
"stagedFrom": "Hazırlanmış · önceki yol: {{oldPath}}",
"stale": "Güncel Değil",
"staleCount": "{{count}} güncel olmayan not",
"unstaged": "Hazırlanmamış",
"unstagedFrom": "Hazırlanmamış · önceki yol: {{oldPath}}"
},
"diffReviewFooter": {
"addFileNote": "Dosya notu ekle",
"discard": "At",
"discardFile": "Dosya değişikliklerini at",
"markFileReviewed": "Dosyayı incelendi olarak işaretle",
"markReviewed": "İncelenmiş Say",
"nextFile": "Sonraki dosya",
"note": "Not",
"previousFile": "Önceki dosya",
"reviewed": "İncelendi",
"stage": "Stage Et",
"stageFile": "Dosyayı stage et",
"unstage": "Unstage Et",
"unstageFile": "Dosyayı unstage et"
},
"diffReviewHeader": {
"filterAll": "Tümü",
"filterBranch": "Branch",
"filterNotes": "Notlar",
"filterStaged": "Hazırlanmış",
"filterUnreviewed": "İncelenmedi",
"filterUnstaged": "Hazırlanmamış",
"openPullRequestSidebar": "Pull request kenar çubuğunu aç",
"openReviewActions": "İnceleme işlemlerini aç",
"reviewedProgress": "{{reviewedCount}}/{{queueLength}} incelendi",
"showAllFiles": "İncelemedeki tüm dosyaları göster",
"showBranchFiles": "Branch'teki dosyaları göster",
"showNotesFiles": "Not içeren dosyaları göster",
"showStagedFiles": "Hazırlanmış dosyaları göster",
"showUnreviewedFiles": "İncelenmemiş dosyaları göster",
"showUnstagedFiles": "Hazırlanmamış dosyaları göster",
"title": "Değişiklikler",
"unsentNotes_one": "{{count}} gönderilmemiş not",
"unsentNotes_other": "{{count}} gönderilmemiş not"
},
"diffReviewLine": {
"addNoteOnLine": "{{number}}. satıra not ekle",
"addedLine": "Eklenen satır",
"addedLineNumber": "Eklenen satır: {{number}}",
"contextLine": "Bağlam satırı",
"contextLineNumber": "Bağlam satırı: {{number}}",
"deletedLine": "Silinen satır",
"deletedLineNumber": "Silinen satır: {{number}}",
"editNoteOnLine": "{{number}}. satırdaki notu düzenle"
},
"dragReorderRow": {
"dragToReorder": "Yeniden sıralamak için sürükle",
"moveDown": "Aşağı taşı",
"moveUp": "Yukarı taşı",
"reorderHint": "Sürüklemeden yeniden sıralamak için yukarı taşı ve aşağı taşı eylemlerini kullanın"
},
"homeQuickActions": {
"newWorkspace": "Yeni Çalışma Alanı",
"pairDesktop": "Masaüstünü Eşleştir",
"title": "Hızlı İşlemler"
},
"hostCard": {
"hostActions": "{{title}} için işlemler",
"openHost": "Aç: {{title}}",
"pairingInvalid": "Eşleştirme geçersiz",
"pairingTemporarilyUnavailable": "Eşleştirme geçici olarak kullanılamıyor",
"repairHint": "Masaüstünüzle yeniden eşleştirmek için dokunun",
"unlockHint": "Telefonunuzun kilidini açın, ardından yeniden denemek için dokunun",
"updateDesktopHint": "Her yerden bağlanmak için masaüstündeki Orca'yı güncelleyin ve oturum açın",
"worktreeAccessibilitySummary": "{{summary}}, {{activeCount}} etkin"
},
"hostDiagnosticsLink": {
"viewDiagnostics": "Ağ tanılamasını görüntüle"
},
"hostProtocolGate": {
"checkingCompatibility": "Ana makine uyumluluğu kontrol ediliyor"
},
"hostRouteNoticeBanner": {
"dismissNotice": "Bildirimi kapat"
},
"htmlPreview": {
"preview": "Önizleme",
"previewRenderedHtml": "İşlenmiş HTML'yi önizle",
"source": "Kaynak",
"title": "HTML önizlemesi",
"viewHtmlSource": "HTML kaynak kodunu görüntüle"
},
"jiraConnectPrompt": {
"checkAgain": "Yeniden kontrol et",
"connectHint": "Orca masaüstü uygulamasında Ayarlar → Entegrasyonlar bölümüne Jira sitenizi ekleyin, ardından yeniden kontrol edin.",
"connectOnDesktop": "Bilgisayarınızda Jira'yı bağlayın",
"credentialUnreadable": "Jira bağlantısı kayıtlı ancak okunamıyor: {{credentialError}}",
"updateDesktop": "Bilgisayarınızdaki Orca'yı güncelleyin"
},
"markdownBlocks": {
"copyCode": "Kodu kopyala",
"openImage": "Görseli aç"
},
"mountedBottomDrawer": {
"dismissDrawer": "Alt paneli kapat"
},
"newWorkspaceFab": {
"newWorkspace": "Yeni çalışma alanı"
},
"newWorkspaceSetupScriptField": {
"hooks": "HOOK'LAR",
"label": "Kurulum betiği",
"run": "Çalıştır",
"runSetupCommand": "Kurulum komutunu çalıştır",
"skip": "Atla"
},
"newWorkspaceSshConnectionField": {
"connect": "Bağlan",
"connecting": "Bağlanılıyor…",
"label": "SSH Bağlantısı",
"serverRecovery": "Sunucusunun masaüstünden kurtarılması gerekiyor: Ayarlar → SSH sunucuları → Sunucuyu kurtar."
},
"newWorktreeAgentSelection": {
"blankTerminal": "Boş Terminal"
},
"newWorktreeFormSheet": {
"advanced": "Gelişmiş",
"agent": "Ajan",
"connectTarget": "Hedefe bağlan",
"connectTargetFirst": "Önce hedefe bağlanın",
"createWorktree": "Worktree oluştur",
"nameOrCreateFrom": "Ad veya “Şuradan Oluştur”",
"noProjectsFound": "Proje bulunamadı",
"note": "Not",
"notePlaceholder": "Bir not yazın",
"workspaceName": "Çalışma alanı adı"
},
"newWorktreeModal": {
"remoteRepository": "Uzak repository"
},
"newWorktreeModalDrawers": {
"agent": "Ajan",
"project": "Proje",
"runOn": "Çalıştırma yeri"
},
"newWorktreeProjectTargetFields": {
"project": "Proje",
"runOn": "Çalıştırma yeri",
"selectProject": "Bir proje seçin",
"selectRunTarget": "Çalıştırma yerini seçin"
},
"prSidebar": {
"loadFailed": "Bir sorun oluştu.",
"loading": "Pull request yükleniyor…",
"notPermitted": "İzin verilmiyor — GitHub hesabınız bağlı değil.",
"retryLoading": "Pull request'i yüklemeyi yeniden dene"
},
"protocolBlockScreen": {
"backToHosts": "Sunucular bölümüne dön",
"desktopTooOldBody": "Eşleştirilmiş bu masaüstü uygulaması, mevcut Orca Mobile sürümünüz için çok eski. Bilgisayarınızdaki Orca'yı güncelleyin, ardından bu sunucuya bağlanmayı tekrar deneyin.",
"getVersion": "Orca {{version}} Sürümünü İndir",
"mobileTooOldAppStore": "Bu masaüstü uygulaması, Orca Mobile'ın daha yeni bir sürümünü gerektiriyor. Orca Mobile'ı App Store üzerinden güncelleyin, ardından bu sunucuya bağlanmayı tekrar deneyin.",
"mobileTooOldGitHubReleases": "Bu masaüstü uygulaması, Orca Mobile'ın daha yeni bir sürümünü gerektiriyor. Orca Mobile'ı GitHub Releases üzerinden güncelleyin, ardından bu sunucuya bağlanmayı tekrar deneyin.",
"openAppStore": "App Store'u Aç",
"openGitHubReleases": "GitHub Releases Sayfasını Aç",
"refreshRecoveryNote": "Bu mesaj görünmeye devam ederse bu sunucuyu kaldırıp yeniden eşleştirin.",
"refreshWorkspaceBody": "Bu sunucu için önbelleğe alınan çalışma alanı, masaüstü uygulamasının beklediğinden daha eski. Güncel sürümü indirmek için bu sunucuya yeniden bağlanın.",
"refreshWorkspaceTitle": "Mobil çalışma alanını yenileyin",
"updateDesktopTitle": "Bilgisayarınızdaki Orca'yı güncelleyin",
"updateMobileTitle": "Orca Mobile'ı güncelleyin",
"updateRecoveryNote": "Zaten güncellediniz mi? Sunucular bölümüne dönüp bağlantıyı yenileyin. Bu mesaj görünmeye devam ederse bu sunucuyu kaldırıp yeniden eşleştirin.",
"workspaceNeedsUpdateAppStore": "Bu masaüstü uygulamasının mobil çalışma alanı, Orca Mobile'ın daha yeni bir sürümünü gerektiriyor. Orca Mobile'ı App Store üzerinden güncelleyin, ardından bu sunucuya bağlanmayı tekrar deneyin.",
"workspaceNeedsUpdateGitHubReleases": "Bu masaüstü uygulamasının mobil çalışma alanı, Orca Mobile'ın daha yeni bir sürümünü gerektiriyor. Orca Mobile'ı GitHub Releases üzerinden güncelleyin, ardından bu sunucuya bağlanmayı tekrar deneyin."
},
"richMarkdownEditor": {
"imageUrl": "Görsel URL'si",
"insert": "Ekle",
"linkUrl": "Bağlantı URL'si"
},
"richMarkdownToolbar": {
"body": "Gövde metni",
"bold": "Kalın",
"bulletList": "Madde işaretli liste",
"checklist": "Kontrol listesi",
"codeBlock": "Kod bloğu",
"heading1": "H1",
"heading2": "H2",
"heading3": "H3",
"image": "Görsel",
"inlineCode": "Satır içi kod",
"italic": "İtalik",
"link": "Bağlantı",
"numberedList": "Numaralı liste",
"quote": "Alıntı",
"strike": "Üstü çizili"
},
"searchField": {
"clearSearch": "Aramayı temizle"
},
"setupHookTrustDrawer": {
"alwaysTrustAndRun": "Her zaman güven ve çalıştır",
"description": "Bu repository'deki orca.yaml, çalışma alanı başlamadan önce çalıştırılır. Yalnızca bu repository'ye güveniyorsanız çalıştırın.",
"dontRun": "Çalıştırma",
"newScriptLabel": "Yeni kurulum betiği",
"runHooks": "Hook'ları çalıştır",
"runSetupTitle": "{{repoName}} reposunun kurulum betiği çalıştırılsın mı?",
"scriptChangedTitle": "{{repoName}} reposunun kurulum betiği değişti",
"scriptLabel": "Kurulum betiği"
},
"smartWorkspaceAdvancedFields": {
"branchName": "Branch adı",
"derivedFromName": "Addan türetilir",
"name": "Ad",
"reuseBranch": "“{{branch}}” branch'ini yeniden kullan",
"workspaceName": "Çalışma alanı adı"
},
"smartWorkspaceSourceDrawer": {
"connectRepository": "Kaynakları aramak için repository'yi bağlayın.",
"crossRepoMessage": "Bu öğe {{owner}}/{{repo}} reposunda bulunuyor.",
"gitHubRemoteRequired": "Bu SSH reposunda sorunları ve PR'ları listelemek için bir GitHub remote'u gerekir.",
"noResults": "Sonuç bulunamadı.",
"searchPlaceholder": "Bir ad yazın veya kaynak arayın",
"switchRepo": "{{repoName}} reposuna geç",
"title": "Ad veya ‘Şuradan Oluştur’",
"typeWorkspaceName": "Aşağıdaki alana bir çalışma alanı adı yazın."
},
"smartWorkspaceSourceField": {
"openSelectedSource": "Seçili kaynağı aç",
"optional": "[İsteğe bağlı]",
"searchPlaceholder": "Bir ad yazın veya kaynak arayın"
},
"smartWorkspaceSourceRow": {
"createBranch": "“{{name}}” branch'ini oluştur",
"issue": "Sorun #{{number}}",
"mergeRequest": "MR !{{number}}",
"nameWorkspace": "Bu çalışma alanını adlandır",
"newBranch": "Yeni branch",
"pullRequest": "PR #{{number}}",
"useName": "“{{name}}” adını kullan"
},
"sourceCopyButton": {
"clipboardRejected": "Metin panoya kopyalanamadı.",
"copiedLoaded": "Yüklenen kopyalandı",
"copyFailed": "Kopyalanamadı",
"copyLoaded": "Yükleneni kopyala",
"couldNotCopy": "Kopyalanamadı",
"failed": "Kopyalanamadı"
},
"terminalShortcutSettings": {
"addDescription": "Tuş birleşimi veya metin makrosu oluştur",
"addShortcut": "Özel Kısayol Ekle…",
"customShortcuts": "ÖZEL KISAYOLLAR",
"empty": "Henüz özel kısayol tanımlanmadı.",
"removeShortcut": "{{label}} kısayolunu kaldır",
"resetDefaults": "Varsayılanlara Sıfırla",
"resetDescription": "Tüm yerleşik kısayol tuşlarını özgün sırayla göster",
"shortcutBar": "KISAYOL ÇUBUĞU",
"shortcutBarDescription": "Tuşları gösterip gizlemek için düğmeleri açıp kapatın. Terminal kısayol çubuğunda bir tuşun sırasını değiştirmek için tutamacını basılı tutup sürükleyin."
},
"useCodexResetCreditAction": {
"confirmTitle": "Hız sınırı sıfırlama kredisi kullanılsın mı?",
"detailsChanged": "Sıfırlama ayrıntıları değişti",
"detailsChangedMessage": "Sunucu Codex ile iletişime geçmeden önce hesap veya sıfırlama teklifi değişti. Güncellenen ayrıntıları inceleyin, ardından yeniden onaylayın.",
"hostAccount": "Sunucudaki {{identity}}",
"hostAccountConfirmMessage": "Bu işlem, sunucudaki {{identity}} hesabı için kazanılmış bir sıfırlama kredisi kullanır ve uygun hız sınırı pencerelerini hemen sıfırlar.",
"hostSelectedAccount": "sunucudaki seçili yönetilen hesap",
"hostSelectedAccountConfirmMessage": "Bu işlem, sunucudaki seçili yönetilen hesap için kazanılmış bir sıfırlama kredisi kullanır ve uygun hız sınırı pencerelerini hemen sıfırlar.",
"resetFailed": "Hız sınırları sıfırlanamadı",
"wslAccount": "WSL {{distro}} üzerindeki {{identity}}",
"wslAccountConfirmMessage": "Bu işlem, WSL {{distro}} üzerindeki {{identity}} hesabı için kazanılmış bir sıfırlama kredisi kullanır ve uygun hız sınırı pencerelerini hemen sıfırlar.",
"wslSelectedAccount": "WSL {{distro}} üzerindeki seçili yönetilen hesap",
"wslSelectedAccountConfirmMessage": "Bu işlem, WSL {{distro}} üzerindeki seçili yönetilen hesap için kazanılmış bir sıfırlama kredisi kullanır ve uygun hız sınırı pencerelerini hemen sıfırlar."
},
"voiceModelList": {
"deleteModel": "{{label}} modelini sil",
"downloadModel": "{{label}} modelini indir",
"inUse": "Kullanımda",
"modelExtracting": "{{size}} · çıkarılıyor…",
"modelProgress": "{{size}} · %{{progress}}",
"modelSize": "{{size}} MB",
"recommended": "Önerilen",
"use": "Kullan",
"useModel": "{{label}} modelini kullan"
},
"workspaceDetailPlaceholder": {
"description": "Terminalini burada açmak için kenar çubuğundan bir çalışma alanı seçin.",
"title": "Açık çalışma alanı yok"
},
"workspaceNameInput": {
"placeholder": "Çalışma alanı adı"
},
"worktreeListRow": {
"child": "Alt çalışma alanı",
"children_one": "{{count}} alt çalışma alanı",
"children_other": "{{count}} alt çalışma alanı",
"folder": "Klasör",
"project": "{{repo}} projesi",
"remoteHost": "Uzak sunucu"
}
},
"connection": {
"connectionHealth": {
"checkConnection": "Bağlantınızı kontrol edin",
@@ -567,6 +1035,270 @@
"useChatUi": "Sohbet Arayüzünü kullan"
}
},
"pullRequest": {
"checksSection": {
"actionRequired": "Eylem gerekli",
"cancelled": "İptal edildi",
"checkDetails": "{{name}} kontrolünün ayrıntıları",
"failed": "Başarısız",
"failing": "{{count}} başarısız",
"failingChecks_one": "{{count}} başarısız kontrol",
"failingChecks_other": "{{count}} başarısız kontrol",
"fix": "Düzelt",
"fixFailing": "Başarısız kontrolleri AI ile düzelt",
"inProgress": "Devam ediyor",
"inspectOrFix": "Ayrıntıları inceleyin veya AI ile düzeltme başlatın.",
"loadDetailsFailed": "Kontrol ayrıntıları yüklenemedi",
"neutral": "Nötr",
"neutralCount": "{{count}} nötr",
"noChecks": "Kontrol yok",
"openOnWeb": "{{name}} kontrolünü web'de aç",
"passed": "{{count}} başarılı",
"pending": "Beklemede",
"pendingCount": "{{count}} beklemede",
"queued": "Kuyrukta",
"rerunFailing": "Başarısız kontrolleri yeniden çalıştır",
"skipped": "Atlandı",
"successful": "Başarılı",
"timedOut": "Zaman aşımına uğradı",
"title": "Kontroller",
"unavailable": "Kontroller kullanılamıyor"
},
"commentCard": {
"daysAgo_one": "{{count}} gün önce",
"daysAgo_other": "{{count}} gün önce",
"deleteComment": "Yorumu sil",
"deleteConfirmMessage": "Bu işlem GitHub'daki yorumu kalıcı olarak siler.",
"deleteConfirmTitle": "Yorum silinsin mi?",
"editComment": "Yorumu düzenle",
"editPlaceholder": "Yorumu düzenleyin…",
"hoursAgo_one": "{{count}} saat önce",
"hoursAgo_other": "{{count}} saat önce",
"justNow": "az önce",
"minutesAgo_one": "{{count}} dk önce",
"minutesAgo_other": "{{count}} dk önce",
"monthsAgo_one": "{{count}} ay önce",
"monthsAgo_other": "{{count}} ay önce",
"openOnGitHub": "Yorumu GitHub'da aç",
"reply": "Yanıtla",
"replyPlaceholder": "Bir yanıt yazın…",
"replyToComment": "Yoruma yanıt ver",
"resolve": "Çöz",
"resolveThread": "Yorum dizisini çöz",
"resolved": "çözüldü",
"unresolve": "Çözümü geri al",
"unresolveThread": "Yorum dizisinin çözümünü geri al",
"yearsAgo_one": "{{count}} yıl önce",
"yearsAgo_other": "{{count}} yıl önce"
},
"commentsSection": {
"addCommentPlaceholder": "Yorum ekleyin…",
"all": "Tümü",
"bots": "Botlar",
"comment": "Yorum yap",
"commentsLoadFailed": "Yorumlar yüklenemedi. Yeniden denemek için yenile düğmesine dokunun.",
"description": "Açıklama",
"descriptionLoadFailed": "Açıklama yüklenemedi. Yeniden denemek için yenile düğmesine dokunun.",
"humans": "İnsanlar",
"noBotComments": "Bot yorumu yok.",
"noComments": "Henüz yorum yok.",
"noDescription": "Açıklama girilmemiş.",
"noHumanComments": "İnsan yorumu yok.",
"resolvedComment_one": "Çözülmüş yorum · {{author}}",
"resolvedComment_other": "Çözülmüş yorum · {{author}} ({{count}})",
"resolvedThread_one": "Çözülmüş yorum dizisi · {{author}}",
"resolvedThread_other": "Çözülmüş yorum dizisi · {{author}} ({{count}})",
"showMore": "{{count}} tane daha göster",
"showMoreOf": "Kalan {{remaining}} yorumdan {{count}} tane daha göster",
"title": "Yorumlar"
},
"conflictingFilesSection": {
"commitsBehind_one": "{{count}} commit geride (temel commit: {{baseCommit}})",
"commitsBehind_other": "{{count}} commit geride (temel commit: {{baseCommit}})",
"copyCommands": "Komutları kopyala",
"copyFailed": "Metin kopyalanamadı",
"copyRefreshCommands": "Merge edilebilirlik yenileme komutlarını kopyala",
"detailsUnavailable": "Conflict'li dosyaların ayrıntıları kullanılamıyor",
"filesHeading": "Conflict'li dosyalar",
"localMergeClean": "GitHub conflict bildiriyor ama yerel Git bunları yeniden oluşturamadı. Merge edilebilirliği yeniden hesaplamak için PR görünümünü yenileyin veya branch'i push edin.",
"mustResolve": "Bu branch'in çözülmesi gereken conflict'leri var",
"refreshing": "Conflict ayrıntıları yenileniyor…",
"resolveWithAi": "Conflict'leri AI ile çöz",
"runFromWorktree": "Bu worktree'den çalıştır",
"title": "Conflict'ler"
},
"linkPrForm": {
"linkButton": "Pull request'i bağla",
"linkNumber": "Bağla: #{{number}}",
"referenceLabel": "PR numarası veya GitHub URL'si",
"referencePlaceholder": "#123 veya https://github.com/owner/repo/pull/123",
"title": "Mevcut pull request'i bağla"
},
"markdownBlocks": {
"details": "Ayrıntılar"
},
"prActionsSection": {
"autoMergeAccessibilityLabel": "Otomatik merge işlemini aç/kapat",
"autoMergeLabel": "Hazır olduğunda otomatik merge et",
"autoMergeOff": "Kapalı",
"autoMergeOn": "Açık",
"closeAccessibilityLabel": "Pull request'i kapat",
"closeConfirmMessage": "#{{number}} merge edilmeden kapatılacak.",
"closeConfirmTitle": "Pull request kapatılsın mı?",
"markReadyAccessibilityLabel": "Pull request'i incelemeye hazır olarak işaretle",
"markReadyButton": "İncelemeye hazır",
"mergeAccessibilityLabel": "Pull request'i merge et",
"mergeButton": "Merge et",
"mergeConfirmTitle": "Pull request merge edilsin mi?",
"recheckAccessibilityLabel": "Merge durumunu yeniden kontrol et",
"recheckButton": "Yeniden kontrol et",
"reopenAccessibilityLabel": "Pull request'i yeniden aç",
"reopenButton": "Yeniden aç",
"reopenConfirmMessage": "#{{number}} yeniden açılacak.",
"reopenConfirmTitle": "Pull request yeniden açılsın mı?",
"unlinkAccessibilityLabel": "Pull request bağlantısını kaldır",
"unlinkButton": "Bağlantıyı kaldır",
"unlinkFailed": "Pull request bağlantısı kaldırılamadı."
},
"prActionsState": {
"behindHint": "Merge etmeden önce branch'i güncelleyin",
"behindLabel": "Geride",
"blockedHint": "GitHub bu pull request'in engellendiğini bildiriyor",
"blockedLabel": "Engellendi",
"changesRequestedHint": "GitHub bu pull request'te değişiklik istendiğini bildiriyor",
"changesRequestedLabel": "Değişiklik istendi",
"checkingHint": "GitHub bu pull request'in merge durumunu hâlâ hesaplıyor",
"checkingLabel": "Kontrol ediliyor",
"checksPassedHint": "Kontroller geçti. Merge uygunluğu, merge etmeden önce tekrar kontrol edilecek.",
"checksPassedLabel": "Kontroller geçti",
"closedHint": "Bu pull request kapatıldı",
"closedLabel": "Kapalı",
"conflictsHint": "GitHub merge conflict'leri bildiriyor",
"conflictsLabel": "Conflict'ler",
"draftHint": "Bu pull request hâlâ taslak",
"draftLabel": "Taslak",
"mergeConfirmMessage": "#{{number}} numaralı PR, temel branch'ine merge edilecek.",
"mergeQueueAutoMergeOnHint": "Bu temel branch, GitHub merge queue'sunu kullanıyor",
"mergeQueueAutoMergeOnLabel": "Otomatik merge açık",
"mergeQueueHint": "Bu temel branch, GitHub merge queue'sunu kullanıyor",
"mergeQueueLabel": "Hazır olunca merge et",
"mergeStackMessage": "GitHub bu pull request'i ve yığında onun altındaki tüm pull request'leri merge edecek.",
"mergeStatusUnavailableHint": "Bu PR için merge durumu kullanılamıyor",
"mergeStatusUnavailableLabel": "Merge et",
"mergeThrough_one": "#{{number}} dahil merge et · {{count}} PR",
"mergeThrough_other": "#{{number}} dahil merge et · {{count}} PR",
"mergeableChecksFailedHint": "GitHub bu PR'ın merge edilebileceğini söylüyor, ancak bazı kontroller başarısız",
"mergeableChecksFailedLabel": "Kontroller başarısız",
"mergeableChecksPassedHint": "GitHub bu PR'ın merge edilebileceğini ve kontrollerin başarıyla tamamlandığını söylüyor",
"mergeableChecksPassedLabel": "Merge edilebilir",
"mergeableChecksPendingHint": "GitHub bu PR'ın merge edilebileceğini söylüyor, ancak kontroller hâlâ çalışıyor",
"mergeableChecksPendingLabel": "Kontroller bekliyor",
"mergeableHint": "GitHub bu PR'ın merge edilebileceğini söylüyor",
"mergeableLabel": "Merge edilebilir",
"mergedHint": "Bu pull request zaten merge edildi",
"mergedLabel": "Merge edildi",
"methodMerge": "Merge commit'i oluştur",
"methodRebase": "Rebase et ve merge et",
"methodSquash": "Squash et ve merge et",
"queueStackMessage": "GitHub bu pull request'i ve altındaki tüm pull request'leri merge sırasına ekleyecek.",
"queueThrough_one": "#{{number}} dahil sıraya al · {{count}} PR",
"queueThrough_other": "#{{number}} dahil sıraya al · {{count}} PR",
"reviewRequiredHint": "GitHub, bu pull request'in merge edilebilmesi için inceleme onayı gerektiriyor",
"reviewRequiredLabel": "Onay gerekli",
"stackBehind": "#{{number}} güncellenmeli.",
"stackBlocked": "#{{number}} engellendi.",
"stackChangesRequested": "#{{number}} için değişiklik istendi.",
"stackClosed": "#{{number}} kapalı.",
"stackConflicts": "#{{number}} numaralı PR'da merge conflict'leri var.",
"stackDraft": "#{{number}} hâlâ taslak.",
"stackReviewRequired": "#{{number}} için hâlâ inceleme onayı gerekiyor."
},
"prCheckDetail": {
"annotations": "Ek notlar",
"annotationsTruncated": "İlk 20 ek not gösteriliyor",
"jobsTruncated": "İlk 100 iş gösteriliyor",
"noDetails": "Ayrıntı yok."
},
"prCheckDetailContent": {
"annotation": "Ek not",
"failedJobs": "Başarısız işler",
"jobs": "İşler",
"unknown": "bilinmiyor"
},
"prChecksPresentation": {
"failing_one": "{{count}} başarısız",
"failing_other": "{{count}} başarısız",
"neutral_one": "{{count}} nötr",
"neutral_other": "{{count}} nötr",
"noChecks": "Kontrol yok",
"passed_one": "{{count}} geçti",
"passed_other": "{{count}} geçti",
"pending_one": "{{count}} beklemede",
"pending_other": "{{count}} beklemede",
"stateClosed": "Kapalı",
"stateDraft": "Taslak",
"stateMerged": "Merge edildi",
"stateOpen": "Açık",
"unavailable": "Kontroller kullanılamıyor"
},
"prCommentAudienceLabels": {
"all": "Tümü",
"bots": "Botlar",
"humans": "İnsanlar",
"noBotComments": "Bot yorumu yok.",
"noComments": "Henüz yorum yok.",
"noHumanComments": "İnsan yorumu yok."
},
"prSidebarCreateEmptyState": {
"branchNotLinked": "{{branch}} branch'i açık bir PR'a bağlı değil.",
"branchRequired": "Bir pull request oluşturmadan önce bir branch'e checkout yapın.",
"createFailed": "Pull request oluşturulamadı.",
"createPr": "PR Oluştur",
"createPullRequest": "Pull request oluştur",
"currentBranchNotLinked": "Geçerli branch açık bir PR'a bağlı değil.",
"linkExistingPr": "Mevcut bir PR'ı bağla",
"linkExistingPullRequest": "Mevcut bir pull request'i bağla",
"linkedPrUnavailable": "Bağlı PR #{{number}} kullanılamıyor",
"noOpenPullRequest": "Açık pull request yok",
"refreshOrCreate": "Tekrar kontrol etmek için yenileyin veya bu branch için yeni bir PR oluşturun.",
"refreshPullRequest": "Pull request'i yenile",
"title": "Pull request"
},
"prSidebarHeader": {
"cancelEditingTitle": "Başlığı düzenlemeyi iptal et",
"editTitle": "Pull request başlığını düzenle",
"openInBrowser": "#{{number}} numaralı pull request'i tarayıcıda aç",
"openOnWeb": "#{{number}} numaralı pull request'i web'de aç",
"saveTitle": "Başlığı kaydet"
},
"prViewPanel": {
"branchUnavailable": "Geçerli branch kullanılamıyor.",
"providerUnavailable": "Bu sağlayıcı için barındırılan inceleme paneli kullanılamıyor."
},
"reviewerPickerDrawer": {
"loadFailed": "Kişiler yüklenemedi",
"noMatches": "Eşleşen kişi yok",
"removeReviewer": "{{login}} adlı kişiyi kaldır",
"requestReviewer": "{{login}} adlı kişiden inceleme iste",
"searchPeople": "Kişi ara",
"title": "İnceleyenler"
},
"reviewersSection": {
"addOrRemove": "İnceleyen ekle veya kaldır",
"approved": "Onaylandı",
"changesRequested": "Değişiklik istendi",
"commented": "Yorum yapıldı",
"dismissed": "Geçersiz kılındı",
"loadFailed": "İnceleyenler yüklenemedi. Yeniden denemek için yenile düğmesine dokunun.",
"loading": "İnceleyenler yükleniyor…",
"noReviewers": "İnceleme istenmedi",
"pending": "Beklemede",
"removeReviewer": "{{login}} kullanıcısını kaldır",
"requested": "İstendi",
"reviewed": "İncelendi",
"title": "İnceleyenler"
}
},
"settings": {
"aboutScreen": {
"openLinkFailed": "Bağlantı açılamadı. Yeniden deneyin.",
+730
View File
@@ -142,9 +142,14 @@
"common": {
"back": "返回",
"cancel": "取消",
"close": "關閉",
"confirm": "確認",
"continue": "繼續",
"copied": "已複製",
"copy": "複製",
"delete": "刪除",
"done": "完成",
"edit": "編輯",
"ok": "確定",
"remove": "移除",
"retry": "重試",
@@ -152,6 +157,469 @@
"settings": "設定",
"unknown": "未知"
},
"components": {
"accountsSnapshot": {
"invalidSnapshot": "主機傳回的帳戶快照無效"
},
"agentLaunchNotice": {
"copyFailed": "無法複製",
"copyPrompt": "複製提示詞",
"statusUnreadable": "無法讀取此主機的狀態。正在重試…",
"updateRequired": "更新電腦上的 Orca 後,即可從手機啟動代理程式。"
},
"agentSpinner": {
"monitoring": "監控背景任務",
"unconfirmed": "無法確認"
},
"agentStateDot": {
"monitoring": "監控背景任務",
"unverifiable": "暫無近期更新"
},
"authFailedBanner": {
"message": "身分驗證失敗,請先嘗試重新連線;若仍失敗,請在電腦端重新配對。"
},
"authFailedBannerActions": {
"repair": "重新配對",
"webNote": "請在 Orca 應用程式中重新連線或移除此主機。"
},
"codexResetCredit": {
"alreadyAppliedTitle": "已套用重設",
"available_one": "可重設 {{count}} 次",
"available_other": "可重設 {{count}} 次",
"expiryDaysHoursMultiple": "下一個將於 {{days}} 天 {{hours}} 小時後過期",
"expiryDaysHoursSingle": "{{days}} 天 {{hours}} 小時後過期",
"expiryDaysMultiple": "下一個將於 {{days}} 天後過期",
"expiryDaysSingle": "{{days}} 天後過期",
"expiryHoursMinutesMultiple": "下一個將於 {{hours}} 小時 {{minutes}} 分鐘後過期",
"expiryHoursMinutesSingle": "{{hours}} 小時 {{minutes}} 分鐘後過期",
"expiryHoursMultiple": "下一個將於 {{hours}} 小時後過期",
"expiryHoursSingle": "{{hours}} 小時後過期",
"expiryMinutesMultiple": "下一個將於 {{minutes}} 分鐘後過期",
"expiryMinutesSingle": "{{minutes}} 分鐘後過期",
"expiryNowMultiple": "下一筆額度已過期",
"expiryNowSingle": "已過期",
"invalidResponse": "主機傳回的重設回應無效",
"noResetMessage": "此帳戶沒有已獲得且可用的重設額度。",
"noResetTitle": "無可用的重設額度",
"nothingToResetMessage": "符合重設條件的 Codex 用量週期皆未用盡額度。",
"nothingToResetTitle": "無需重設",
"refresh": "Codex 用量已更新。",
"resetTitle": "速率限制已重設"
},
"codexResetCreditAction": {
"activeAccountHint": "為目前使用中的 Codex 帳戶使用一次已獲得的重設額度",
"earnedReset": "已獲得的 Codex 速率限制重設額度",
"resetting": "正在重設…",
"resettingLabel": "正在重設 Codex 速率限制",
"scopedHint": "為 {{scopeLabel}} 使用一次已獲得的重設額度",
"useReset": "使用重設額度",
"useResetLabel": "使用 Codex 速率限制重設額度"
},
"customKeyModal": {
"add": "新增",
"addShortcut": "新增快速鍵",
"command": "命令",
"commandPlaceholder": "例如:pnpm build",
"editing": "編輯",
"function": "功能鍵",
"key": "按鍵",
"label": "顯示名稱",
"labelPlaceholder": "例如:建置",
"manageShortcuts": "管理快速鍵",
"manageShortcutsHint": "顯示、隱藏或調整快速鍵順序",
"modifiers": "修飾鍵",
"moreKeys": "更多按鍵:Tab、方向鍵、F1–F12…",
"navigation": "導覽",
"pickKey": "選擇按鍵",
"pressEnter": "按下 Enter",
"shortcutCombo": "快速鍵組合",
"shortcutComboHint": "建立 Ctrl、Alt 和 Shift 組合鍵",
"textMacro": "文字巨集",
"textMacroHint": "傳送自訂文字命令"
},
"dictationSetupSheet": {
"description": "可直接在此下載或選擇模型,並在已配對的電腦上啟用聽寫。",
"download": "下載",
"downloadFailed": "下載失敗",
"downloadModel": "下載 {{model}}",
"enabled": "啟用聽寫",
"inUse": "使用中",
"loadFailed": "載入失敗",
"modelExtracting": "{{details}} · 正在解壓縮…",
"modelProgress": "{{details}} · {{progress}}%",
"modelSize": "{{size}} MB",
"recommended": "推薦",
"selectFailed": "無法選擇模型",
"title": "設定語音聽寫",
"updateFailed": "無法更新",
"use": "使用",
"useModel": "使用 {{model}}"
},
"diffReviewBody": {
"binaryDiff": "二進位差異",
"binaryMessage": "此檔案無法在手機上以文字顯示。",
"deletedFile": "檔案已刪除",
"deletedMessage": "此檔案已刪除。可新增檔案備註或標記為已審查。",
"diffTooLarge": "差異過大",
"diffTruncated": "行動版預覽僅顯示部分差異。",
"diffUnavailable": "無法顯示差異",
"loadFailed": "無法載入審查",
"loadingDiff": "正在載入差異…",
"loadingReview": "正在載入審查…",
"noReviewableChanges": "沒有可審查的變更",
"retryLabel": "重新載入審查內容",
"reviewUnavailable": "無法使用審查功能",
"selectFile": "請選取要審查的檔案。",
"tooLargeMessage": "此差異過大,無法在手機上預覽。",
"tryDifferentFilter": "請嘗試其他審查篩選條件。"
},
"diffReviewDrawers": {
"actionsTitle": "審查操作",
"addNote": "新增備註",
"cancelNote": "取消編輯備註",
"clearSentNotes": "清除已傳送的備註",
"completeTitle": "審查完成",
"copyNotes": "複製備註",
"deleteNote": "刪除備註",
"discard": "放棄",
"discardMessage": "要放棄「{{filePath}}」的變更嗎?此操作無法復原。",
"discardTitle": "放棄檔案變更",
"editNote": "編輯備註",
"fileNote": "檔案備註",
"fileSummary_one": "已審查 {{fileCount}} 個檔案,{{count}} 則備註",
"fileSummary_other": "已審查 {{fileCount}} 個檔案,{{count}} 則備註",
"filesSummary_one": "已審查 {{fileCount}} 個檔案,{{count}} 則備註",
"filesSummary_other": "已審查 {{fileCount}} 個檔案,{{count}} 則備註",
"lineNumber": "第 {{lineNumber}} 行",
"loadingSessions": "正在載入代理程式工作階段…",
"markUnreviewed": "標記為未審查",
"newAgentSession": "新增代理程式工作階段",
"openInSession": "在工作階段中開啟",
"reviewNote": "審查備註",
"saveLineNote": "儲存第 {{lineNumber}} 行的備註",
"sendNotes": "傳送備註",
"sendNotesLabel": "將備註傳送給代理程式",
"sendUnsentNotes": "傳送未傳送的備註",
"stageReviewed": "暫存已審查檔案",
"stageReviewedFiles": "暫存已審查的檔案",
"stageReviewedLabel": "暫存已審查的檔案",
"stageableFiles_one": "{{count}} 個已審查但未暫存的檔案可供暫存",
"stageableFiles_other": "{{count}} 個已審查但未暫存的檔案可供暫存",
"statusUnreadable": "無法讀取此主機的狀態。正在重試…",
"terminal": "終端機",
"terminalLabel": "{{title}} ({{id}})",
"unsentNotes_one": "{{count}} 則未傳送的備註",
"unsentNotes_other": "{{count}} 則未傳送的備註",
"updateRequired": "請更新電腦上的 Orca"
},
"diffReviewFileSummary": {
"branch": "分支",
"branchFrom": "分支,原路徑:{{oldPath}}",
"changed": "已變更",
"editFileNote": "編輯檔案備註",
"hunk": "差異區塊",
"nextHunk": "下一個差異區塊",
"notes_one": "{{count}} 則筆記",
"notes_other": "{{count}} 則筆記",
"previousHunk": "上一個差異區塊",
"reviewed": "已審查",
"staged": "已暫存",
"stagedFrom": "已暫存,原路徑:{{oldPath}}",
"stale": "已過時",
"staleCount": "{{count}} 則已過時",
"unstaged": "未暫存",
"unstagedFrom": "未暫存,原路徑:{{oldPath}}"
},
"diffReviewFooter": {
"addFileNote": "新增檔案備註",
"discard": "放棄",
"discardFile": "放棄檔案變更",
"markFileReviewed": "將檔案標記為已審查",
"markReviewed": "標記為已審查",
"nextFile": "下一個檔案",
"note": "備註",
"previousFile": "上一個檔案",
"reviewed": "已審查",
"stage": "暫存",
"stageFile": "暫存檔案",
"unstage": "取消暫存",
"unstageFile": "取消暫存檔案"
},
"diffReviewHeader": {
"filterAll": "全部",
"filterBranch": "分支",
"filterNotes": "筆記",
"filterStaged": "已暫存",
"filterUnreviewed": "未審查",
"filterUnstaged": "未暫存",
"openPullRequestSidebar": "開啟 PR 側邊欄",
"openReviewActions": "開啟審查操作選單",
"reviewedProgress": "已審查 {{reviewedCount}}/{{queueLength}}",
"showAllFiles": "顯示所有審查檔案",
"showBranchFiles": "顯示分支的審查檔案",
"showNotesFiles": "顯示有筆記的檔案",
"showStagedFiles": "顯示已暫存的檔案",
"showUnreviewedFiles": "顯示未審查的檔案",
"showUnstagedFiles": "顯示未暫存的檔案",
"title": "變更",
"unsentNotes_one": "{{count}} 則未傳送的備註",
"unsentNotes_other": "{{count}} 則未傳送的備註"
},
"diffReviewLine": {
"addNoteOnLine": "在第 {{number}} 行新增備註",
"addedLine": "新增行",
"addedLineNumber": "新增的第 {{number}} 行",
"contextLine": "上下文行",
"contextLineNumber": "上下文第 {{number}} 行",
"deletedLine": "刪除行",
"deletedLineNumber": "刪除的第 {{number}} 行",
"editNoteOnLine": "編輯第 {{number}} 行的備註"
},
"dragReorderRow": {
"dragToReorder": "拖曳以調整順序",
"moveDown": "下移",
"moveUp": "上移",
"reorderHint": "使用「上移」和「下移」動作即可調整順序,無須拖曳"
},
"homeQuickActions": {
"newWorkspace": "新工作區",
"pairDesktop": "配對電腦",
"title": "快速操作"
},
"hostCard": {
"hostActions": "{{title}} 的操作",
"openHost": "開啟 {{title}}",
"pairingInvalid": "配對已失效",
"pairingTemporarilyUnavailable": "配對暫時無法使用",
"repairHint": "點一下以重新與電腦配對",
"unlockHint": "解鎖手機後,點一下以重試",
"updateDesktopHint": "更新電腦版 Orca 並登入,即可隨時隨地連線",
"worktreeAccessibilitySummary": "{{summary}},{{activeCount}} 個使用中"
},
"hostDiagnosticsLink": {
"viewDiagnostics": "檢視網路診斷"
},
"hostProtocolGate": {
"checkingCompatibility": "正在檢查主機相容性"
},
"hostRouteNoticeBanner": {
"dismissNotice": "關閉通知"
},
"htmlPreview": {
"preview": "預覽",
"previewRenderedHtml": "預覽 HTML 呈現結果",
"source": "原始碼",
"title": "HTML 預覽",
"viewHtmlSource": "檢視 HTML 原始碼"
},
"jiraConnectPrompt": {
"checkAgain": "再次檢查",
"connectHint": "在 Orca 電腦版的「設定 → 整合」中新增 Jira 網站,然後再次檢查。",
"connectOnDesktop": "在電腦上連線至 Jira",
"credentialUnreadable": "Jira 憑證已儲存,但無法讀取:{{credentialError}}",
"updateDesktop": "更新 Orca 電腦版"
},
"markdownBlocks": {
"copyCode": "複製程式碼",
"openImage": "開啟圖片"
},
"mountedBottomDrawer": {
"dismissDrawer": "關閉抽屜面板"
},
"newWorkspaceFab": {
"newWorkspace": "新工作區"
},
"newWorkspaceSetupScriptField": {
"hooks": "鉤子",
"label": "設定指令碼",
"run": "執行",
"runSetupCommand": "執行設定指令",
"skip": "跳過"
},
"newWorkspaceSshConnectionField": {
"connect": "連線",
"connecting": "連線中…",
"label": "SSH 連線",
"serverRecovery": "需要從電腦版復原此伺服器:「設定」→「SSH 主機」→「復原伺服器」。"
},
"newWorktreeAgentSelection": {
"blankTerminal": "空白終端機"
},
"newWorktreeFormSheet": {
"advanced": "進階",
"agent": "代理程式",
"connectTarget": "連線至目標",
"connectTargetFirst": "請先連線至目標",
"createWorktree": "建立工作樹",
"nameOrCreateFrom": "名稱或「建立來源」",
"noProjectsFound": "找不到專案",
"note": "備註",
"notePlaceholder": "寫備註",
"workspaceName": "工作區名稱"
},
"newWorktreeModal": {
"remoteRepository": "遠端存放庫"
},
"newWorktreeModalDrawers": {
"agent": "代理程式",
"project": "專案",
"runOn": "執行於"
},
"newWorktreeProjectTargetFields": {
"project": "專案",
"runOn": "執行於",
"selectProject": "選擇專案",
"selectRunTarget": "選擇執行目標"
},
"prSidebar": {
"loadFailed": "發生錯誤。",
"loading": "正在載入 PR…",
"notPermitted": "無存取權限:尚未連結 GitHub 帳戶。",
"retryLoading": "重新嘗試載入 PR"
},
"protocolBlockScreen": {
"backToHosts": "返回主機清單",
"desktopTooOldBody": "已配對的桌面版版本過舊,無法搭配目前的 Orca Mobile 使用。請更新電腦上的 Orca,再重新連線至此主機。",
"getVersion": "取得 Orca {{version}}",
"mobileTooOldAppStore": "此桌面版需要較新版本的 Orca Mobile。請從 App Store 更新 Orca Mobile,再重新連線至此主機。",
"mobileTooOldGitHubReleases": "此桌面版需要較新版本的 Orca Mobile。請從 GitHub Releases 更新 Orca Mobile,再重新連線至此主機。",
"openAppStore": "開啟 App Store",
"openGitHubReleases": "開啟 GitHub Releases",
"refreshRecoveryNote": "如果仍顯示此訊息,請移除此主機並重新配對。",
"refreshWorkspaceBody": "此主機的工作區快取版本低於桌面版要求的版本。請重新連線至此主機,以下載目前版本。",
"refreshWorkspaceTitle": "重新整理行動版工作區",
"updateDesktopTitle": "更新電腦上的 Orca",
"updateMobileTitle": "更新 Orca Mobile",
"updateRecoveryNote": "已經更新了嗎?請返回「主機」並重新整理連線。如果仍顯示此訊息,請移除此主機並重新配對。",
"workspaceNeedsUpdateAppStore": "此桌面版的行動版工作區需要較新版本的 Orca Mobile。請從 App Store 更新 Orca Mobile,再重新連線至此主機。",
"workspaceNeedsUpdateGitHubReleases": "此桌面版的行動版工作區需要較新版本的 Orca Mobile。請從 GitHub Releases 更新 Orca Mobile,再重新連線至此主機。"
},
"richMarkdownEditor": {
"imageUrl": "影像網址",
"insert": "插入",
"linkUrl": "連結網址"
},
"richMarkdownToolbar": {
"body": "內文",
"bold": "粗體",
"bulletList": "項目符號清單",
"checklist": "檢查清單",
"codeBlock": "程式碼區塊",
"heading1": "標題 1",
"heading2": "標題 2",
"heading3": "標題 3",
"image": "影像",
"inlineCode": "行內程式碼",
"italic": "斜體",
"link": "連結",
"numberedList": "編號清單",
"quote": "引用",
"strike": "刪除線"
},
"searchField": {
"clearSearch": "清除搜尋"
},
"setupHookTrustDrawer": {
"alwaysTrustAndRun": "一律信任並執行",
"description": "此存放庫的 orca.yaml 會在工作區啟動前執行。請僅在信任此存放庫時執行。",
"dontRun": "不執行",
"newScriptLabel": "新的設定指令碼",
"runHooks": "執行鉤子",
"runSetupTitle": "要執行 {{repoName}} 的設定指令碼嗎?",
"scriptChangedTitle": "{{repoName}} 的設定指令碼已變更",
"scriptLabel": "設定指令碼"
},
"smartWorkspaceAdvancedFields": {
"branchName": "分支名稱",
"derivedFromName": "依名稱產生",
"name": "名稱",
"reuseBranch": "重複使用分支「{{branch}}」",
"workspaceName": "工作區名稱"
},
"smartWorkspaceSourceDrawer": {
"connectRepository": "請連接存放庫以搜尋來源。",
"crossRepoMessage": "此項目位於 {{owner}}/{{repo}}。",
"gitHubRemoteRequired": "此 SSH 存放庫需要 GitHub 遠端存放庫,才能列出議題和 PR。",
"noResults": "找不到結果。",
"searchPlaceholder": "輸入名稱或搜尋來源",
"switchRepo": "切換至 {{repoName}}",
"title": "名稱或「建立來源」",
"typeWorkspaceName": "請在下方欄位輸入工作區名稱。"
},
"smartWorkspaceSourceField": {
"openSelectedSource": "開啟所選來源",
"optional": "[可選]",
"searchPlaceholder": "輸入名稱或搜尋來源"
},
"smartWorkspaceSourceRow": {
"createBranch": "建立分支「{{name}}」",
"issue": "議題 #{{number}}",
"mergeRequest": "MR !{{number}}",
"nameWorkspace": "為此工作區命名",
"newBranch": "新分支",
"pullRequest": "PR #{{number}}",
"useName": "使用「{{name}}」"
},
"sourceCopyButton": {
"clipboardRejected": "剪貼簿拒絕接收文字。",
"copiedLoaded": "已複製載入的內容",
"copyFailed": "複製失敗",
"copyLoaded": "複製已載入內容",
"couldNotCopy": "無法複製",
"failed": "失敗"
},
"terminalShortcutSettings": {
"addDescription": "建立組合鍵或文字巨集",
"addShortcut": "新增自訂快速鍵…",
"customShortcuts": "自訂快速鍵",
"empty": "尚未設定自訂快速鍵。",
"removeShortcut": "移除 {{label}}",
"resetDefaults": "還原預設值",
"resetDescription": "依原始順序顯示所有內建快速鍵",
"shortcutBar": "快速鍵列",
"shortcutBarDescription": "使用開關顯示或隱藏按鍵,並長按拖曳控點,調整按鍵在終端機快速鍵列上的順序。"
},
"useCodexResetCreditAction": {
"confirmTitle": "要使用速率限制重設額度嗎?",
"detailsChanged": "重設詳情已變更",
"detailsChangedMessage": "在主機向 Codex 傳送請求前,帳戶或重設條件已變更。請檢視更新後的詳情,再次確認。",
"hostAccount": "主機上的 {{identity}}",
"hostAccountConfirmMessage": "這會為主機上的 {{identity}} 使用一次已獲得的重設額度,並立即重設符合條件的用量週期。",
"hostSelectedAccount": "主機上所選的受管理帳戶",
"hostSelectedAccountConfirmMessage": "這會為主機上所選的受管理帳戶使用一次已獲得的重設額度,並立即重設符合條件的用量週期。",
"resetFailed": "無法重設速率限制",
"wslAccount": "WSL {{distro}} 上的 {{identity}}",
"wslAccountConfirmMessage": "這會為 WSL {{distro}} 上的 {{identity}} 使用一次已獲得的重設額度,並立即重設符合條件的用量週期。",
"wslSelectedAccount": "WSL {{distro}} 上所選的受管理帳戶",
"wslSelectedAccountConfirmMessage": "這會為 WSL {{distro}} 上所選的受管理帳戶使用一次已獲得的重設額度,並立即重設符合條件的用量週期。"
},
"voiceModelList": {
"deleteModel": "刪除 {{label}}",
"downloadModel": "下載 {{label}}",
"inUse": "使用中",
"modelExtracting": "{{size}} · 解壓縮中…",
"modelProgress": "{{size}} · {{progress}}%",
"modelSize": "{{size}} MB",
"recommended": "推薦",
"use": "使用",
"useModel": "使用 {{label}}"
},
"workspaceDetailPlaceholder": {
"description": "從側邊欄選擇工作區,即可在這裡開啟其終端機。",
"title": "尚未開啟工作區"
},
"workspaceNameInput": {
"placeholder": "工作區名稱"
},
"worktreeListRow": {
"child": "子工作區",
"children_one": "{{count}} 個子工作區",
"children_other": "{{count}} 個子工作區",
"folder": "資料夾",
"project": "專案 {{repo}}",
"remoteHost": "遠端主機"
}
},
"connection": {
"connectionHealth": {
"checkConnection": "請檢查網路連線",
@@ -567,6 +1035,268 @@
"useChatUi": "使用聊天介面"
}
},
"pullRequest": {
"checksSection": {
"actionRequired": "需要操作",
"cancelled": "已取消",
"checkDetails": "{{name}} 檢查詳細資訊",
"failed": "失敗",
"failing": "{{count}} 項失敗",
"failingChecks_one": "{{count}} 項檢查失敗",
"failingChecks_other": "{{count}} 項檢查失敗",
"fix": "修復",
"fixFailing": "使用 AI 修復失敗的檢查",
"inProgress": "進行中",
"inspectOrFix": "檢視詳細資訊或啟動 AI 修復。",
"loadDetailsFailed": "無法載入檢查詳細資訊",
"neutral": "中性",
"neutralCount": "{{count}} 項中性",
"noChecks": "無檢查",
"openOnWeb": "在網頁上開啟 {{name}}",
"passed": "{{count}} 項通過",
"pending": "等待中",
"pendingCount": "{{count}} 項待處理",
"queued": "已加入佇列",
"rerunFailing": "重新執行失敗的檢查",
"skipped": "已略過",
"successful": "成功",
"timedOut": "已逾時",
"title": "檢查項目",
"unavailable": "無法取得檢查結果"
},
"commentCard": {
"daysAgo_one": "{{count}} 天前",
"daysAgo_other": "{{count}} 天前",
"deleteComment": "刪除留言",
"deleteConfirmMessage": "這會永久刪除 GitHub 上的留言。",
"deleteConfirmTitle": "刪除留言?",
"editComment": "編輯留言",
"editPlaceholder": "編輯留言…",
"hoursAgo_one": "{{count}} 小時前",
"hoursAgo_other": "{{count}} 小時前",
"justNow": "剛剛",
"minutesAgo_one": "{{count}} 分鐘前",
"minutesAgo_other": "{{count}} 分鐘前",
"monthsAgo_one": "{{count}} 個月前",
"monthsAgo_other": "{{count}} 個月前",
"openOnGitHub": "在 GitHub 上開啟留言",
"reply": "回覆",
"replyPlaceholder": "撰寫回覆…",
"replyToComment": "回覆留言",
"resolve": "解決",
"resolveThread": "將討論串標示為已解決",
"resolved": "已解決",
"unresolve": "標示為未解決",
"unresolveThread": "將討論串標示為未解決",
"yearsAgo_one": "{{count}} 年前",
"yearsAgo_other": "{{count}} 年前"
},
"commentsSection": {
"addCommentPlaceholder": "新增留言…",
"all": "全部",
"bots": "機器人",
"comment": "留言",
"commentsLoadFailed": "無法載入留言。請點選「重新整理」再試一次。",
"description": "描述",
"descriptionLoadFailed": "無法載入描述。請點選「重新整理」再試一次。",
"humans": "人類",
"noBotComments": "沒有機器人留言。",
"noComments": "還沒有留言。",
"noDescription": "沒有提供描述。",
"noHumanComments": "沒有人類留言。",
"resolvedComment_other": "{{author}} 的已解決留言({{count}})",
"resolvedThread_other": "{{author}} 的已解決討論串({{count}})",
"showMore": "再顯示 {{count}} 則",
"showMoreOf": "再顯示 {{count}} 則(剩餘 {{remaining}} 則)",
"title": "留言"
},
"conflictingFilesSection": {
"commitsBehind_one": "落後 {{count}} 個提交(基底提交:{{baseCommit}})",
"commitsBehind_other": "落後 {{count}} 個提交(基底提交:{{baseCommit}})",
"copyCommands": "複製命令",
"copyFailed": "無法複製文字",
"copyRefreshCommands": "複製重新判斷是否可合併的命令",
"detailsUnavailable": "無法取得衝突檔案的詳細資訊",
"filesHeading": "衝突的檔案",
"localMergeClean": "GitHub 回報有衝突,但本機 Git 未重現這些衝突。請重新整理 PR 或推送分支,以重新判斷是否可合併。",
"mustResolve": "此分支有必須解決的衝突",
"refreshing": "正在重新整理衝突詳細資訊…",
"resolveWithAi": "使用 AI 解決衝突",
"runFromWorktree": "從此工作樹執行",
"title": "衝突"
},
"linkPrForm": {
"linkButton": "連結 PR",
"linkNumber": "連結 #{{number}}",
"referenceLabel": "PR 編號或 GitHub 網址",
"referencePlaceholder": "#123 或 https://github.com/owner/repo/pull/123",
"title": "連結現有 PR"
},
"markdownBlocks": {
"details": "詳情"
},
"prActionsSection": {
"autoMergeAccessibilityLabel": "切換自動合併",
"autoMergeLabel": "就緒時自動合併",
"autoMergeOff": "關閉",
"autoMergeOn": "開啟",
"closeAccessibilityLabel": "關閉 PR",
"closeConfirmMessage": "將關閉 #{{number}},不會進行合併。",
"closeConfirmTitle": "要關閉 PR 嗎?",
"markReadyAccessibilityLabel": "將 PR 標示為可供審查",
"markReadyButton": "標示為可供審查",
"mergeAccessibilityLabel": "合併 PR",
"mergeButton": "合併",
"mergeConfirmTitle": "要合併 PR 嗎?",
"recheckAccessibilityLabel": "重新檢查合併狀態",
"recheckButton": "重新檢查",
"reopenAccessibilityLabel": "重新開啟 PR",
"reopenButton": "重新開啟",
"reopenConfirmMessage": "將重新開啟 #{{number}}。",
"reopenConfirmTitle": "要重新開啟 PR 嗎?",
"unlinkAccessibilityLabel": "取消連結 PR",
"unlinkButton": "取消連結",
"unlinkFailed": "無法取消連結 PR。"
},
"prActionsState": {
"behindHint": "合併前更新分支",
"behindLabel": "落後",
"blockedHint": "GitHub 回報此 PR 的合併作業受阻",
"blockedLabel": "合併受阻",
"changesRequestedHint": "GitHub 回報此 PR 有變更要求",
"changesRequestedLabel": "要求變更",
"checkingHint": "GitHub 仍在計算此 PR 合併狀態",
"checkingLabel": "檢查中",
"checksPassedHint": "檢查已通過。合併資格將在合併前再次驗證。",
"checksPassedLabel": "檢查已通過",
"closedHint": "此 PR 已關閉",
"closedLabel": "已關閉",
"conflictsHint": "GitHub 報告合併衝突",
"conflictsLabel": "衝突",
"draftHint": "這個 PR 仍然是一個草稿",
"draftLabel": "草稿",
"mergeConfirmMessage": "這會將 #{{number}} 合併至其基底分支。",
"mergeQueueAutoMergeOnHint": "此基底分支使用 GitHub 合併佇列",
"mergeQueueAutoMergeOnLabel": "已開啟自動合併",
"mergeQueueHint": "此基底分支使用 GitHub 合併佇列",
"mergeQueueLabel": "準備好後合併",
"mergeStackMessage": "GitHub 將合併此 PR 以及堆疊中其下方的每個 PR。",
"mergeStatusUnavailableHint": "無法取得此 PR 的合併狀態",
"mergeStatusUnavailableLabel": "合併",
"mergeThrough_one": "合併到 #{{number}} 為止 · {{count}} 個 PR",
"mergeThrough_other": "合併到 #{{number}} 為止 · {{count}} 個 PR",
"mergeableChecksFailedHint": "GitHub 說這個 PR 可以合併,但有些檢查失敗",
"mergeableChecksFailedLabel": "檢查失敗",
"mergeableChecksPassedHint": "GitHub 表示此 PR 可以合併,且檢查已通過",
"mergeableChecksPassedLabel": "可以合併",
"mergeableChecksPendingHint": "GitHub 表示此 PR 可以合併,但檢查仍在執行",
"mergeableChecksPendingLabel": "檢查待處理",
"mergeableHint": "GitHub 表示此 PR 可以合併",
"mergeableLabel": "可以合併",
"mergedHint": "此 PR 已合併",
"mergedLabel": "已合併",
"methodMerge": "建立合併提交",
"methodRebase": "變基並合併",
"methodSquash": "壓縮並合併",
"queueStackMessage": "GitHub 會將此 PR 及其下方的每個 PR 新增至合併佇列。",
"queueThrough_one": "排入佇列到 #{{number}} 為止 · {{count}} 個 PR",
"queueThrough_other": "排入佇列到 #{{number}} 為止 · {{count}} 個 PR",
"reviewRequiredHint": "GitHub 需要審查核准才能合併此 PR",
"reviewRequiredLabel": "需要核准",
"stackBehind": "#{{number}} 必須更新。",
"stackBlocked": "#{{number}} 的合併作業受阻。",
"stackChangesRequested": "#{{number}} 有變更要求。",
"stackClosed": "#{{number}} 已關閉。",
"stackConflicts": "#{{number}} 有合併衝突。",
"stackDraft": "#{{number}} 仍是草稿。",
"stackReviewRequired": "#{{number}} 仍需審查核准。"
},
"prCheckDetail": {
"annotations": "註解",
"annotationsTruncated": "顯示前 20 個註解",
"jobsTruncated": "顯示前 100 個作業",
"noDetails": "無詳細資訊。"
},
"prCheckDetailContent": {
"annotation": "註解",
"failedJobs": "失敗的作業",
"jobs": "作業",
"unknown": "未知"
},
"prChecksPresentation": {
"failing_one": "{{count}} 項失敗",
"failing_other": "{{count}} 項失敗",
"neutral_one": "{{count}} 項中性",
"neutral_other": "{{count}} 項中性",
"noChecks": "沒有檢查",
"passed_one": "{{count}} 項通過",
"passed_other": "{{count}} 項通過",
"pending_one": "{{count}} 項待處理",
"pending_other": "{{count}} 項待處理",
"stateClosed": "已關閉",
"stateDraft": "草稿",
"stateMerged": "已合併",
"stateOpen": "開啟中",
"unavailable": "無法取得檢查結果"
},
"prCommentAudienceLabels": {
"all": "全部",
"bots": "機器人",
"humans": "人類",
"noBotComments": "沒有機器人留言。",
"noComments": "還沒有留言。",
"noHumanComments": "沒有人類留言。"
},
"prSidebarCreateEmptyState": {
"branchNotLinked": "{{branch}} 未連結至開啟中的 PR。",
"branchRequired": "建立 PR 前,請先檢出分支。",
"createFailed": "建立 PR 失敗。",
"createPr": "建立 PR",
"createPullRequest": "建立 PR",
"currentBranchNotLinked": "目前分支未連結至開啟中的 PR。",
"linkExistingPr": "連結現有 PR",
"linkExistingPullRequest": "連結現有 PR",
"linkedPrUnavailable": "無法取得已連結的 PR #{{number}}",
"noOpenPullRequest": "沒有開啟中的 PR",
"refreshOrCreate": "請重新整理以再次檢查,或為此分支建立新的 PR。",
"refreshPullRequest": "重新整理 PR",
"title": "PR"
},
"prSidebarHeader": {
"cancelEditingTitle": "取消編輯標題",
"editTitle": "編輯 PR 標題",
"openInBrowser": "在瀏覽器中開啟 PR #{{number}}",
"openOnWeb": "在網頁上開啟 PR #{{number}}",
"saveTitle": "儲存標題"
},
"prViewPanel": {
"branchUnavailable": "無法取得目前分支。",
"providerUnavailable": "此提供者無法使用託管審查面板。"
},
"reviewerPickerDrawer": {
"loadFailed": "載入人員失敗",
"noMatches": "找不到符合的人員",
"removeReviewer": "移除 {{login}}",
"requestReviewer": "要求 {{login}} 審查",
"searchPeople": "搜尋人員",
"title": "審查人"
},
"reviewersSection": {
"addOrRemove": "新增或移除審查人",
"approved": "已核准",
"changesRequested": "要求變更",
"commented": "已留言",
"dismissed": "已撤銷",
"loadFailed": "無法載入審查人。請點選「重新整理」再試一次。",
"loading": "正在載入審查人…",
"noReviewers": "尚未要求任何人審查",
"pending": "待處理",
"removeReviewer": "移除 {{login}}",
"requested": "已要求審查",
"reviewed": "已審查",
"title": "審查人"
}
},
"settings": {
"aboutScreen": {
"openLinkFailed": "無法開啟連結,請再試一次。",
+730
View File
@@ -142,9 +142,14 @@
"common": {
"back": "返回",
"cancel": "取消",
"close": "关闭",
"confirm": "确认",
"continue": "继续",
"copied": "已复制",
"copy": "复制",
"delete": "删除",
"done": "完成",
"edit": "编辑",
"ok": "确定",
"remove": "移除",
"retry": "重试",
@@ -152,6 +157,469 @@
"settings": "设置",
"unknown": "未知"
},
"components": {
"accountsSnapshot": {
"invalidSnapshot": "来自主机的账户快照无效"
},
"agentLaunchNotice": {
"copyFailed": "无法复制",
"copyPrompt": "复制提示",
"statusUnreadable": "无法读取此主机的状态。正在重试…",
"updateRequired": "请先更新电脑上的 Orca,才能从手机启动智能体。"
},
"agentSpinner": {
"monitoring": "监控后台任务",
"unconfirmed": "无法确认"
},
"agentStateDot": {
"monitoring": "监控后台任务",
"unverifiable": "暂无近期更新"
},
"authFailedBanner": {
"message": "身份验证失败。请先尝试重新连接;若仍失败,请在桌面端重新配对。"
},
"authFailedBannerActions": {
"repair": "重新配对",
"webNote": "请在 Orca 应用中重新连接或移除此主机。"
},
"codexResetCredit": {
"alreadyAppliedTitle": "已执行重置",
"available_one": "可重置 {{count}} 次",
"available_other": "可重置 {{count}} 次",
"expiryDaysHoursMultiple": "下一个将在 {{days}} 天 {{hours}} 小时后过期",
"expiryDaysHoursSingle": "{{days}} 天 {{hours}} 小时后过期",
"expiryDaysMultiple": "下一个将在 {{days}} 天后过期",
"expiryDaysSingle": "{{days}} 天后过期",
"expiryHoursMinutesMultiple": "下一个将在 {{hours}} 小时 {{minutes}} 分钟后过期",
"expiryHoursMinutesSingle": "{{hours}} 小时 {{minutes}} 分钟后过期",
"expiryHoursMultiple": "下一个将在 {{hours}} 小时后过期",
"expiryHoursSingle": "{{hours}} 小时后过期",
"expiryMinutesMultiple": "下一个将在 {{minutes}} 分钟后过期",
"expiryMinutesSingle": "{{minutes}} 分钟后过期",
"expiryNowMultiple": "下一个现在过期",
"expiryNowSingle": "现在过期",
"invalidResponse": "主机返回的重置响应无效",
"noResetMessage": "此账户没有已获得的可用重置额度。",
"noResetTitle": "无可用重置次数",
"nothingToResetMessage": "符合重置条件的 Codex 用量窗口均未耗尽额度。",
"nothingToResetTitle": "无需重置",
"refresh": "Codex 用量已刷新。",
"resetTitle": "速率限制已重置"
},
"codexResetCreditAction": {
"activeAccountHint": "为当前 Codex 账户使用一次已获得的重置额度",
"earnedReset": "已获得的 Codex 速率限制重置额度",
"resetting": "正在重置…",
"resettingLabel": "正在重置 Codex 速率限制",
"scopedHint": "为 {{scopeLabel}} 使用一次已获得的重置额度",
"useReset": "使用重置额度",
"useResetLabel": "使用 Codex 速率限制重置额度"
},
"customKeyModal": {
"add": "添加",
"addShortcut": "添加快捷键",
"command": "命令",
"commandPlaceholder": "例如:pnpm build",
"editing": "编辑键",
"function": "功能键",
"key": "按键",
"label": "标签",
"labelPlaceholder": "例如:构建",
"manageShortcuts": "管理快捷键",
"manageShortcutsHint": "显示、隐藏快捷键或调整其顺序",
"modifiers": "修饰键",
"moreKeys": "更多按键:Tab、方向键、F1–F12…",
"navigation": "导航键",
"pickKey": "选择按键",
"pressEnter": "按下 Enter",
"shortcutCombo": "快捷键组合",
"shortcutComboHint": "使用 Ctrl、Alt 和 Shift 设置组合键",
"textMacro": "文本宏",
"textMacroHint": "发送自定义文本命令"
},
"dictationSetupSheet": {
"description": "在此即可下载或选择模型,并在已配对的电脑上启用听写。",
"download": "下载",
"downloadFailed": "下载失败",
"downloadModel": "下载 {{model}}",
"enabled": "已启用听写",
"inUse": "使用中",
"loadFailed": "加载失败",
"modelExtracting": "{{details}} · 正在解压…",
"modelProgress": "{{details}} · {{progress}}%",
"modelSize": "{{size}} MB",
"recommended": "推荐",
"selectFailed": "无法选择模型",
"title": "设置语音听写",
"updateFailed": "无法更新",
"use": "使用",
"useModel": "使用 {{model}}"
},
"diffReviewBody": {
"binaryDiff": "二进制差异",
"binaryMessage": "无法在移动端将此文件显示为文本。",
"deletedFile": "文件已删除",
"deletedMessage": "此文件已删除。可添加文件备注或将其标记为已评审。",
"diffTooLarge": "差异过大",
"diffTruncated": "差异已截断,以便在移动端预览。",
"diffUnavailable": "差异不可用",
"loadFailed": "无法加载评审",
"loadingDiff": "加载差异…",
"loadingReview": "正在加载评审…",
"noReviewableChanges": "没有可评审的更改",
"retryLabel": "重试加载评审内容",
"reviewUnavailable": "评审不可用",
"selectFile": "请选择要评审的文件。",
"tooLargeMessage": "此差异过大,无法在移动端预览。",
"tryDifferentFilter": "请尝试其他评审筛选条件。"
},
"diffReviewDrawers": {
"actionsTitle": "评审操作",
"addNote": "添加备注",
"cancelNote": "取消备注编辑",
"clearSentNotes": "清除已发送的备注",
"completeTitle": "评审完成",
"copyNotes": "复制备注",
"deleteNote": "删除注释",
"discard": "放弃",
"discardMessage": "要放弃对“{{filePath}}”的更改吗?此操作无法撤销。",
"discardTitle": "放弃文件更改",
"editNote": "编辑备注",
"fileNote": "文件备注",
"fileSummary_one": "已评审 {{fileCount}} 个文件,{{count}} 条备注",
"fileSummary_other": "已评审 {{fileCount}} 个文件,{{count}} 条备注",
"filesSummary_one": "已评审 {{fileCount}} 个文件,{{count}} 条备注",
"filesSummary_other": "已评审 {{fileCount}} 个文件,{{count}} 条备注",
"lineNumber": "第 {{lineNumber}} 行",
"loadingSessions": "正在加载智能体会话…",
"markUnreviewed": "标记为未评审",
"newAgentSession": "新建智能体会话",
"openInSession": "在会话中打开",
"reviewNote": "评审备注",
"saveLineNote": "保存第 {{lineNumber}} 行的备注",
"sendNotes": "发送备注",
"sendNotesLabel": "向智能体发送备注",
"sendUnsentNotes": "发送未发送的备注",
"stageReviewed": "暂存已评审文件",
"stageReviewedFiles": "暂存已评审文件",
"stageReviewedLabel": "暂存已评审文件",
"stageableFiles_one": "{{count}} 个已评审的未暂存文件可暂存",
"stageableFiles_other": "{{count}} 个已评审的未暂存文件可暂存",
"statusUnreadable": "无法读取此主机的状态。正在重试…",
"terminal": "终端",
"terminalLabel": "{{title}} ({{id}})",
"unsentNotes_one": "{{count}} 条未发送的备注",
"unsentNotes_other": "{{count}} 条未发送的备注",
"updateRequired": "请更新电脑上的 Orca"
},
"diffReviewFileSummary": {
"branch": "分支",
"branchFrom": "分支,原路径:{{oldPath}}",
"changed": "已更改",
"editFileNote": "编辑文件备注",
"hunk": "差异块",
"nextHunk": "下一个差异块",
"notes_one": "{{count}} 条备注",
"notes_other": "{{count}} 条备注",
"previousHunk": "上一个差异块",
"reviewed": "已评审",
"staged": "已暂存",
"stagedFrom": "已暂存,原路径:{{oldPath}}",
"stale": "已过时",
"staleCount": "{{count}} 条已过时",
"unstaged": "未暂存",
"unstagedFrom": "未暂存,原路径:{{oldPath}}"
},
"diffReviewFooter": {
"addFileNote": "添加文件备注",
"discard": "放弃",
"discardFile": "放弃文件更改",
"markFileReviewed": "将文件标记为已评审",
"markReviewed": "标记为已评审",
"nextFile": "下一个文件",
"note": "备注",
"previousFile": "上一个文件",
"reviewed": "已评审",
"stage": "暂存",
"stageFile": "暂存文件",
"unstage": "取消暂存",
"unstageFile": "取消暂存文件"
},
"diffReviewHeader": {
"filterAll": "全部",
"filterBranch": "分支",
"filterNotes": "备注",
"filterStaged": "已暂存",
"filterUnreviewed": "未评审",
"filterUnstaged": "未暂存",
"openPullRequestSidebar": "打开 PR 侧边栏",
"openReviewActions": "打开评审操作",
"reviewedProgress": "已评审 {{reviewedCount}}/{{queueLength}}",
"showAllFiles": "显示所有评审文件",
"showBranchFiles": "显示分支评审文件",
"showNotesFiles": "显示有备注的评审文件",
"showStagedFiles": "显示已暂存的评审文件",
"showUnreviewedFiles": "显示尚未评审的文件",
"showUnstagedFiles": "显示未暂存的评审文件",
"title": "更改",
"unsentNotes_one": "{{count}} 条未发送备注",
"unsentNotes_other": "{{count}} 条未发送备注"
},
"diffReviewLine": {
"addNoteOnLine": "在第 {{number}} 行添加备注",
"addedLine": "新增行",
"addedLineNumber": "第 {{number}} 行(新增)",
"contextLine": "上下文行",
"contextLineNumber": "第 {{number}} 行(上下文)",
"deletedLine": "已删除行",
"deletedLineNumber": "第 {{number}} 行(已删除)",
"editNoteOnLine": "编辑第 {{number}} 行的备注"
},
"dragReorderRow": {
"dragToReorder": "拖动以调整顺序",
"moveDown": "下移",
"moveUp": "上移",
"reorderHint": "使用“上移”和“下移”操作调整顺序,无需拖动"
},
"homeQuickActions": {
"newWorkspace": "新工作区",
"pairDesktop": "配对桌面",
"title": "快捷操作"
},
"hostCard": {
"hostActions": "{{title}} 的操作",
"openHost": "打开 {{title}}",
"pairingInvalid": "配对无效",
"pairingTemporarilyUnavailable": "配对暂时不可用",
"repairHint": "轻点以重新与桌面配对",
"unlockHint": "解锁手机后,轻点重试",
"updateDesktopHint": "更新桌面端 Orca 并登录,即可随时随地连接",
"worktreeAccessibilitySummary": "{{summary}},{{activeCount}} 个活跃"
},
"hostDiagnosticsLink": {
"viewDiagnostics": "查看网络诊断"
},
"hostProtocolGate": {
"checkingCompatibility": "正在检查主机兼容性"
},
"hostRouteNoticeBanner": {
"dismissNotice": "关闭通知"
},
"htmlPreview": {
"preview": "预览",
"previewRenderedHtml": "预览渲染后的 HTML",
"source": "源码",
"title": "HTML 预览",
"viewHtmlSource": "查看 HTML 源码"
},
"jiraConnectPrompt": {
"checkAgain": "再次检查",
"connectHint": "请在 Orca 桌面端的“设置 → 集成”中添加 Jira 站点,然后再次检查。",
"connectOnDesktop": "在桌面端连接 Jira",
"credentialUnreadable": "Jira 凭据已保存,但无法读取:{{credentialError}}",
"updateDesktop": "更新 Orca 桌面端"
},
"markdownBlocks": {
"copyCode": "复制代码",
"openImage": "打开图片"
},
"mountedBottomDrawer": {
"dismissDrawer": "关闭抽屉"
},
"newWorkspaceFab": {
"newWorkspace": "新工作区"
},
"newWorkspaceSetupScriptField": {
"hooks": "钩子",
"label": "设置脚本",
"run": "执行",
"runSetupCommand": "执行设置命令",
"skip": "跳过"
},
"newWorkspaceSshConnectionField": {
"connect": "连接",
"connecting": "连接中…",
"label": "SSH 连接",
"serverRecovery": "需要在桌面端恢复其服务器:设置 → SSH 主机 → 恢复服务器。"
},
"newWorktreeAgentSelection": {
"blankTerminal": "空白终端"
},
"newWorktreeFormSheet": {
"advanced": "高级",
"agent": "智能体",
"connectTarget": "连接目标主机",
"connectTargetFirst": "请先连接目标主机",
"createWorktree": "创建工作树",
"nameOrCreateFrom": "名称或“创建来源”",
"noProjectsFound": "未找到项目",
"note": "备注",
"notePlaceholder": "写备注",
"workspaceName": "工作区名称"
},
"newWorktreeModal": {
"remoteRepository": "远程仓库"
},
"newWorktreeModalDrawers": {
"agent": "智能体",
"project": "项目",
"runOn": "运行于"
},
"newWorktreeProjectTargetFields": {
"project": "项目",
"runOn": "运行于",
"selectProject": "选择项目",
"selectRunTarget": "选择运行目标"
},
"prSidebar": {
"loadFailed": "发生错误。",
"loading": "正在加载 PR…",
"notPermitted": "无权访问:您的 GitHub 账户尚未连接。",
"retryLoading": "重新加载 PR"
},
"protocolBlockScreen": {
"backToHosts": "返回主机列表",
"desktopTooOldBody": "已配对的桌面应用版本过旧,无法与当前的 Orca Mobile 兼容。请更新电脑上的 Orca,然后重新连接此主机。",
"getVersion": "获取 Orca {{version}}",
"mobileTooOldAppStore": "此桌面需要更新版本的 Orca Mobile。请从 App Store 更新 Orca Mobile,然后重新连接此主机。",
"mobileTooOldGitHubReleases": "此桌面需要更新版本的 Orca Mobile。请从 GitHub Releases 更新 Orca Mobile,然后重新连接此主机。",
"openAppStore": "打开 App Store",
"openGitHubReleases": "打开 GitHub Releases",
"refreshRecoveryNote": "如果仍显示此消息,请移除此主机并重新配对。",
"refreshWorkspaceBody": "此主机的工作区缓存版本低于桌面要求。请重新连接此主机,下载当前版本。",
"refreshWorkspaceTitle": "刷新移动端工作区",
"updateDesktopTitle": "更新电脑上的 Orca",
"updateMobileTitle": "更新 Orca Mobile",
"updateRecoveryNote": "已经更新?请返回“主机”并刷新连接。如果仍显示此消息,请移除此主机并重新配对。",
"workspaceNeedsUpdateAppStore": "此桌面的移动端工作区需要更新版本的 Orca Mobile。请从 App Store 更新 Orca Mobile,然后重新连接此主机。",
"workspaceNeedsUpdateGitHubReleases": "此桌面的移动端工作区需要更新版本的 Orca Mobile。请从 GitHub Releases 更新 Orca Mobile,然后重新连接此主机。"
},
"richMarkdownEditor": {
"imageUrl": "图像 URL",
"insert": "插入",
"linkUrl": "链接网址"
},
"richMarkdownToolbar": {
"body": "正文",
"bold": "粗体",
"bulletList": "项目符号列表",
"checklist": "清单",
"codeBlock": "代码块",
"heading1": "H1",
"heading2": "H2",
"heading3": "H3",
"image": "图像",
"inlineCode": "内联代码",
"italic": "斜体",
"link": "链接",
"numberedList": "编号列表",
"quote": "引用",
"strike": "删除线"
},
"searchField": {
"clearSearch": "清除搜索"
},
"setupHookTrustDrawer": {
"alwaysTrustAndRun": "始终信任并运行",
"description": "此仓库的 orca.yaml 会在工作区启动前运行。请仅在信任此仓库时运行。",
"dontRun": "不运行",
"newScriptLabel": "新设置脚本",
"runHooks": "运行钩子",
"runSetupTitle": "运行 {{repoName}} 的设置脚本?",
"scriptChangedTitle": "{{repoName}} 的设置脚本已更改",
"scriptLabel": "设置脚本"
},
"smartWorkspaceAdvancedFields": {
"branchName": "分支名称",
"derivedFromName": "根据名称生成",
"name": "名称",
"reuseBranch": "复用分支“{{branch}}”",
"workspaceName": "工作区名称"
},
"smartWorkspaceSourceDrawer": {
"connectRepository": "连接仓库以搜索来源。",
"crossRepoMessage": "此条目位于 {{owner}}/{{repo}}。",
"gitHubRemoteRequired": "此 SSH 仓库需要配置 GitHub 远程仓库,才能列出议题和 PR。",
"noResults": "未找到结果。",
"searchPlaceholder": "输入名称或搜索来源",
"switchRepo": "切换到 {{repoName}}",
"title": "名称或“创建来源”",
"typeWorkspaceName": "请在下方输入框中输入工作区名称。"
},
"smartWorkspaceSourceField": {
"openSelectedSource": "打开所选来源",
"optional": "[可选]",
"searchPlaceholder": "输入名称或搜索来源"
},
"smartWorkspaceSourceRow": {
"createBranch": "创建分支“{{name}}”",
"issue": "议题 #{{number}}",
"mergeRequest": "MR !{{number}}",
"nameWorkspace": "为此工作区命名",
"newBranch": "新分支",
"pullRequest": "PR #{{number}}",
"useName": "使用“{{name}}”"
},
"sourceCopyButton": {
"clipboardRejected": "剪贴板拒绝接收该文本。",
"copiedLoaded": "已复制已加载内容",
"copyFailed": "复制失败",
"copyLoaded": "复制已加载内容",
"couldNotCopy": "无法复制",
"failed": "失败"
},
"terminalShortcutSettings": {
"addDescription": "创建组合键或文本宏",
"addShortcut": "添加自定义快捷键…",
"customShortcuts": "自定义快捷键",
"empty": "尚未设置自定义快捷键。",
"removeShortcut": "移除 {{label}}",
"resetDefaults": "恢复默认设置",
"resetDescription": "按原始顺序显示所有内置快捷键",
"shortcutBar": "快捷键栏",
"shortcutBarDescription": "切换按键开关可显示或隐藏按键,按住拖动手柄可调整终端快捷键栏中的按键顺序。"
},
"useCodexResetCreditAction": {
"confirmTitle": "使用一次速率限制重置额度?",
"detailsChanged": "重置详情已变更",
"detailsChangedMessage": "主机联系 Codex 前,账户或重置额度信息已发生变化。请查看更新后的详情,然后再次确认。",
"hostAccount": "主机上的 {{identity}}",
"hostAccountConfirmMessage": "这会为主机上的 {{identity}} 消耗一次已获得的重置额度,并立即重置符合条件的用量窗口。",
"hostSelectedAccount": "主机上选定的托管账户",
"hostSelectedAccountConfirmMessage": "这会为主机上选定的托管账户消耗一次已获得的重置额度,并立即重置符合条件的用量窗口。",
"resetFailed": "无法重置速率限制",
"wslAccount": "WSL {{distro}} 上的 {{identity}}",
"wslAccountConfirmMessage": "这会为 WSL {{distro}} 上的 {{identity}} 消耗一次已获得的重置额度,并立即重置符合条件的用量窗口。",
"wslSelectedAccount": "WSL {{distro}} 上选定的托管账户",
"wslSelectedAccountConfirmMessage": "这会为 WSL {{distro}} 上选定的托管账户消耗一次已获得的重置额度,并立即重置符合条件的用量窗口。"
},
"voiceModelList": {
"deleteModel": "删除 {{label}}",
"downloadModel": "下载 {{label}}",
"inUse": "使用中",
"modelExtracting": "{{size}} · 解压中…",
"modelProgress": "{{size}} · {{progress}}%",
"modelSize": "{{size}} MB",
"recommended": "推荐",
"use": "使用",
"useModel": "使用 {{label}}"
},
"workspaceDetailPlaceholder": {
"description": "从侧边栏选择一个工作区,即可在此处打开其终端。",
"title": "未打开工作区"
},
"workspaceNameInput": {
"placeholder": "工作区名称"
},
"worktreeListRow": {
"child": "子工作区",
"children_one": "{{count}} 个子工作区",
"children_other": "{{count}} 个子工作区",
"folder": "文件夹",
"project": "项目 {{repo}}",
"remoteHost": "远程主机"
}
},
"connection": {
"connectionHealth": {
"checkConnection": "请检查网络连接",
@@ -567,6 +1035,268 @@
"useChatUi": "使用聊天界面"
}
},
"pullRequest": {
"checksSection": {
"actionRequired": "需要操作",
"cancelled": "已取消",
"checkDetails": "{{name}} 检查详情",
"failed": "失败",
"failing": "{{count}} 项失败",
"failingChecks_one": "{{count}} 项检查失败",
"failingChecks_other": "{{count}} 项检查失败",
"fix": "修复",
"fixFailing": "使用 AI 修复失败的检查",
"inProgress": "进行中",
"inspectOrFix": "检查详细信息或启动 AI 修复过程。",
"loadDetailsFailed": "加载检查详情失败",
"neutral": "中性",
"neutralCount": "{{count}} 项中性",
"noChecks": "无检查项",
"openOnWeb": "在网页上打开 {{name}}",
"passed": "{{count}} 项通过",
"pending": "待处理",
"pendingCount": "{{count}} 项待完成",
"queued": "已加入队列",
"rerunFailing": "重新运行失败的检查",
"skipped": "已跳过",
"successful": "成功",
"timedOut": "已超时",
"title": "检查项",
"unavailable": "检查不可用"
},
"commentCard": {
"daysAgo_one": "{{count}} 天前",
"daysAgo_other": "{{count}} 天前",
"deleteComment": "删除评论",
"deleteConfirmMessage": "这将永久删除 GitHub 上的这条评论。",
"deleteConfirmTitle": "删除评论?",
"editComment": "编辑评论",
"editPlaceholder": "编辑评论…",
"hoursAgo_one": "{{count}} 小时前",
"hoursAgo_other": "{{count}} 小时前",
"justNow": "刚刚",
"minutesAgo_one": "{{count}} 分钟前",
"minutesAgo_other": "{{count}} 分钟前",
"monthsAgo_one": "{{count}} 个月前",
"monthsAgo_other": "{{count}} 个月前",
"openOnGitHub": "在 GitHub 上打开评论",
"reply": "回复",
"replyPlaceholder": "撰写回复…",
"replyToComment": "回复评论",
"resolve": "解决",
"resolveThread": "解决讨论串",
"resolved": "已解决",
"unresolve": "标记为未解决",
"unresolveThread": "将讨论串标记为未解决",
"yearsAgo_one": "{{count}} 年前",
"yearsAgo_other": "{{count}} 年前"
},
"commentsSection": {
"addCommentPlaceholder": "添加评论…",
"all": "全部",
"bots": "机器人",
"comment": "评论",
"commentsLoadFailed": "无法加载评论。请点按刷新重试。",
"description": "描述",
"descriptionLoadFailed": "无法加载描述。请点按刷新重试。",
"humans": "人类",
"noBotComments": "没有机器人评论。",
"noComments": "还没有评论。",
"noDescription": "没有提供描述。",
"noHumanComments": "没有人类评论。",
"resolvedComment_other": "{{author}} 的已解决评论({{count}})",
"resolvedThread_other": "{{author}} 的已解决讨论串({{count}})",
"showMore": "再显示 {{count}} 条",
"showMoreOf": "再显示 {{count}} 条(剩余 {{remaining}} 条)",
"title": "评论"
},
"conflictingFilesSection": {
"commitsBehind_one": "落后 {{count}} 个提交(基础提交:{{baseCommit}})",
"commitsBehind_other": "落后 {{count}} 个提交(基础提交:{{baseCommit}})",
"copyCommands": "复制命令",
"copyFailed": "复制文本失败",
"copyRefreshCommands": "复制可合并性刷新命令",
"detailsUnavailable": "冲突文件详细信息不可用",
"filesHeading": "冲突的文件",
"localMergeClean": "GitHub 提示存在冲突,但本地 Git 未能复现。请刷新 PR 或推送分支,以重新计算可合并性。",
"mustResolve": "该分支存在必须解决的冲突",
"refreshing": "刷新冲突细节…",
"resolveWithAi": "使用 AI 解决冲突",
"runFromWorktree": "从此工作树运行",
"title": "冲突"
},
"linkPrForm": {
"linkButton": "关联 PR",
"linkNumber": "关联 #{{number}}",
"referenceLabel": "PR 编号或 GitHub URL",
"referencePlaceholder": "#123 或 https://github.com/owner/repo/pull/123",
"title": "关联现有 PR"
},
"markdownBlocks": {
"details": "详情"
},
"prActionsSection": {
"autoMergeAccessibilityLabel": "切换自动合并",
"autoMergeLabel": "满足条件后自动合并",
"autoMergeOff": "关闭",
"autoMergeOn": "开启",
"closeAccessibilityLabel": "关闭PR",
"closeConfirmMessage": "#{{number}} 将被关闭,不会合并。",
"closeConfirmTitle": "关闭 PR?",
"markReadyAccessibilityLabel": "将 PR 标记为已准备好接受评审",
"markReadyButton": "标记为可评审",
"mergeAccessibilityLabel": "合并 PR",
"mergeButton": "合并",
"mergeConfirmTitle": "合并 PR?",
"recheckAccessibilityLabel": "重新检查合并状态",
"recheckButton": "重新检查",
"reopenAccessibilityLabel": "重新开启 PR",
"reopenButton": "重新开启",
"reopenConfirmMessage": "#{{number}} 将被重新开启。",
"reopenConfirmTitle": "重新开启 PR?",
"unlinkAccessibilityLabel": "取消 PR 关联",
"unlinkButton": "取消关联",
"unlinkFailed": "取消 PR 关联失败。"
},
"prActionsState": {
"behindHint": "合并前更新分支",
"behindLabel": "落后",
"blockedHint": "GitHub 报告此拉取请求已被阻塞",
"blockedLabel": "已阻塞",
"changesRequestedHint": "GitHub 显示评审人要求更改此 PR",
"changesRequestedLabel": "要求更改",
"checkingHint": "GitHub 仍在计算此PR合并状态",
"checkingLabel": "检查中",
"checksPassedHint": "检查已通过。合并资格将在合并前再次验证。",
"checksPassedLabel": "检查已通过",
"closedHint": "此PR已关闭",
"closedLabel": "已关闭",
"conflictsHint": "GitHub 报告合并冲突",
"conflictsLabel": "冲突",
"draftHint": "这个PR仍然是一个草稿",
"draftLabel": "草稿",
"mergeConfirmMessage": "这将把 #{{number}} 合并到其基础分支。",
"mergeQueueAutoMergeOnHint": "该基础分支使用 GitHub 合并队列",
"mergeQueueAutoMergeOnLabel": "自动合并已开启",
"mergeQueueHint": "该基础分支使用 GitHub 合并队列",
"mergeQueueLabel": "准备好后合并",
"mergeStackMessage": "GitHub 将合并此拉取请求以及堆栈中其下方的每个拉取请求。",
"mergeStatusUnavailableHint": "此 PR 的合并状态不可用",
"mergeStatusUnavailableLabel": "合并",
"mergeThrough_one": "合并 #{{number}} 及下方 PR · 共 {{count}} 个",
"mergeThrough_other": "合并 #{{number}} 及下方 PR · 共 {{count}} 个",
"mergeableChecksFailedHint": "GitHub 说这个 PR 可以合并,但有些检查失败",
"mergeableChecksFailedLabel": "检查失败",
"mergeableChecksPassedHint": "GitHub 显示此 PR 可以合并且检查已通过",
"mergeableChecksPassedLabel": "可以合并",
"mergeableChecksPendingHint": "GitHub 表示此 PR 可以合并,但检查仍在运行",
"mergeableChecksPendingLabel": "检查待处理",
"mergeableHint": "GitHub 显示此 PR 可以合并",
"mergeableLabel": "可以合并",
"mergedHint": "此PR已合并",
"mergedLabel": "已合并",
"methodMerge": "创建合并提交",
"methodRebase": "变基合并",
"methodSquash": "压缩合并",
"queueStackMessage": "GitHub 会将此拉取请求及其下面的每个拉取请求添加到合并队列中。",
"queueThrough_one": "将 #{{number}} 及下方 PR 入队 · 共 {{count}} 个",
"queueThrough_other": "将 #{{number}} 及下方 PR 入队 · 共 {{count}} 个",
"reviewRequiredHint": "GitHub 需要评审批准才能合并此拉取请求",
"reviewRequiredLabel": "需要批准",
"stackBehind": "#{{number}} 必须更新。",
"stackBlocked": "#{{number}} 已阻塞。",
"stackChangesRequested": "#{{number}} 被要求更改。",
"stackClosed": "#{{number}} 已关闭。",
"stackConflicts": "#{{number}} 存在合并冲突。",
"stackDraft": "#{{number}} 仍为草稿。",
"stackReviewRequired": "#{{number}} 仍需评审批准。"
},
"prCheckDetail": {
"annotations": "注释",
"annotationsTruncated": "显示前 20 个注释",
"jobsTruncated": "显示前 100 个作业",
"noDetails": "暂无详细信息。"
},
"prCheckDetailContent": {
"annotation": "批注",
"failedJobs": "失败的任务",
"jobs": "任务",
"unknown": "未知"
},
"prChecksPresentation": {
"failing_one": "{{count}} 项失败",
"failing_other": "{{count}} 项失败",
"neutral_one": "{{count}} 项中性",
"neutral_other": "{{count}} 项中性",
"noChecks": "无检查项",
"passed_one": "{{count}} 项已通过",
"passed_other": "{{count}} 项已通过",
"pending_one": "{{count}} 项待处理",
"pending_other": "{{count}} 项待处理",
"stateClosed": "已关闭",
"stateDraft": "草稿",
"stateMerged": "已合并",
"stateOpen": "开放",
"unavailable": "检查不可用"
},
"prCommentAudienceLabels": {
"all": "全部",
"bots": "机器人",
"humans": "人类",
"noBotComments": "没有机器人评论。",
"noComments": "还没有评论。",
"noHumanComments": "没有人类评论。"
},
"prSidebarCreateEmptyState": {
"branchNotLinked": "{{branch}} 未关联到开放的 PR。",
"branchRequired": "创建 PR 前,请先检出分支。",
"createFailed": "创建 PR 失败。",
"createPr": "创建PR",
"createPullRequest": "创建 PR",
"currentBranchNotLinked": "当前分支未关联到开放的 PR。",
"linkExistingPr": "关联现有 PR",
"linkExistingPullRequest": "关联现有 PR",
"linkedPrUnavailable": "关联的 PR #{{number}} 不可用",
"noOpenPullRequest": "暂无开放的 PR",
"refreshOrCreate": "刷新以重新检查,或为此分支创建新的 PR。",
"refreshPullRequest": "刷新 PR",
"title": "PR"
},
"prSidebarHeader": {
"cancelEditingTitle": "取消编辑标题",
"editTitle": "编辑 PR 标题",
"openInBrowser": "在浏览器中打开 PR #{{number}}",
"openOnWeb": "在网页上打开 PR #{{number}}",
"saveTitle": "保存标题"
},
"prViewPanel": {
"branchUnavailable": "当前分支不可用。",
"providerUnavailable": "此提供商的托管评审面板不可用。"
},
"reviewerPickerDrawer": {
"loadFailed": "加载人员失败",
"noMatches": "没有匹配的人员",
"removeReviewer": "移除 {{login}}",
"requestReviewer": "请求 {{login}} 评审",
"searchPeople": "搜索人员",
"title": "评审人"
},
"reviewersSection": {
"addOrRemove": "添加或移除评审人",
"approved": "已批准",
"changesRequested": "要求更改",
"commented": "已评论",
"dismissed": "已撤销",
"loadFailed": "无法加载评审人。请点按刷新重试。",
"loading": "正在加载评审人…",
"noReviewers": "尚未请求评审",
"pending": "待处理",
"removeReviewer": "移除 {{login}}",
"requested": "已请求评审",
"reviewed": "已评审",
"title": "评审人"
}
},
"settings": {
"aboutScreen": {
"openLinkFailed": "无法打开链接。请重试。",
@@ -3,6 +3,7 @@ import { ActivityIndicator, Pressable, Text, View } from 'react-native'
import { SafeAreaView } from 'react-native-safe-area-context'
import { colors } from '../theme/mobile-theme'
import { useMobileColorScheme } from '../theme/use-mobile-color-scheme'
import { useMobileTranslation } from '../i18n/use-mobile-translation'
import { connectionRetryAction } from '../transport/connection-retry-action'
import { useMobileSourceControlState } from './use-mobile-source-control-state'
import { useMobileSourceControlActionSheet } from './use-mobile-source-control-action-sheet'
@@ -50,6 +51,7 @@ export function MobileSourceControlPanel({
onOpenedFileDiff
}: MobileSourceControlPanelProps) {
useMobileColorScheme()
const t = useMobileTranslation()
const [activeTab, setActiveTab] = useState<SourceControlHubTab>(initialTab)
const [visitedTabs, setVisitedTabs] = useState<ReadonlySet<SourceControlHubTab>>(
() => new Set<SourceControlHubTab>([initialTab])
@@ -205,7 +207,7 @@ export function MobileSourceControlPanel({
? countUnresolvedReviewThreads(prController.prSidebarState.data.details?.comments)
: null
return buildMobilePrChipSummary(prController.prSidebarState, commentCount)
}, [isHostedRepo, prBranch, prController.prSidebarState])
}, [isHostedRepo, prBranch, prController.prSidebarState, t])
const onRefresh = useCallback(() => {
void loadStatus({ preserveReadyOnFailure: true, rereadSettings: true })