diff --git a/apps/mobile/src/components/agents/chat-composer-attachment-send.test.ts b/apps/mobile/src/components/agents/chat-composer-attachment-send.test.ts index 5b731ffc60..ab3553485d 100644 --- a/apps/mobile/src/components/agents/chat-composer-attachment-send.test.ts +++ b/apps/mobile/src/components/agents/chat-composer-attachment-send.test.ts @@ -60,13 +60,21 @@ vi.mock('react', async () => { }); // ── react-native and native bridges ──────────────────────────────────────── +vi.mock('@rn-primitives/slot', () => ({ Text: 'SlotText' })); + vi.mock('react-native', () => ({ Alert: { alert: vi.fn() }, AppState: { addEventListener: () => ({ remove: vi.fn() }), }, - Keyboard: { dismiss: vi.fn() }, + Keyboard: { + addListener: vi.fn(() => ({ remove: vi.fn() })), + dismiss: vi.fn(), + }, Platform: { OS: 'ios' }, + Pressable: 'Pressable', + Text: 'Text', + useWindowDimensions: () => ({ fontScale: 1, height: 800, scale: 1, width: 400 }), View: 'View', })); @@ -103,6 +111,11 @@ vi.mock('react-native-reanimated', () => ({ default: { View: 'Animated.View' }, FadeIn: { duration: vi.fn(() => ({})) }, FadeOut: { duration: vi.fn(() => ({})) }, + useReducedMotion: () => false, +})); + +vi.mock('react-native-safe-area-context', () => ({ + useSafeAreaInsets: () => ({ bottom: 0, left: 0, right: 0, top: 0 }), })); vi.mock('expo-haptics', () => ({ @@ -186,6 +199,14 @@ vi.mock('@/lib/hooks/use-current-user-id', () => ({ useCurrentUserId: () => ({ userId: 'u1' }), })); +vi.mock('@/lib/hooks/use-return-sends-message-preference', () => ({ + useReturnSendsMessagePreference: () => ({ + returnSendsMessage: false, + hasLoaded: true, + setReturnSendsMessage: vi.fn(), + }), +})); + vi.mock('@/lib/agent-attachments/use-agent-attachment-upload', () => ({ useAgentAttachmentUpload: () => ({ attachments: uploadState.attachments, diff --git a/apps/mobile/src/components/agents/chat-composer-input-height.test.ts b/apps/mobile/src/components/agents/chat-composer-input-height.test.ts index b1b0679683..4a66573a69 100644 --- a/apps/mobile/src/components/agents/chat-composer-input-height.test.ts +++ b/apps/mobile/src/components/agents/chat-composer-input-height.test.ts @@ -1,14 +1,28 @@ import { describe, expect, it } from 'vitest'; import { + COMPOSER_CHROME_HEIGHT, COMPOSER_INPUT_PADDING_HORIZONTAL, + NEW_SESSION_PROMPT_CHROME_HEIGHT, + resolveComposerMaxHeight, resolveComposerTextContentWidth, shouldEnableComposerInputScroll, + STARTER_ROW_HEIGHT, } from './chat-composer-input-height'; const MIN = 44; const MAX = 124; +const MAX_HEIGHT_ARGS = { + windowHeight: 1000, + safeAreaInsetTop: 44, + safeAreaInsetBottom: 34, + keyboardHeight: 336, + sessionHeaderHeight: 92, + composerChromeHeight: 120, + minHeight: MIN, +} as const; + describe('shouldEnableComposerInputScroll', () => { it('is true at or above max and false below', () => { expect(shouldEnableComposerInputScroll(MAX, MAX)).toBe(true); @@ -29,3 +43,38 @@ describe('resolveComposerTextContentWidth', () => { ); }); }); + +describe('composer chrome budgets', () => { + it('reserve the starter-row height inside both chrome budgets', () => { + // 120 (composer chrome) + STARTER_ROW_HEIGHT = 176, and 176 + STARTER_ROW_HEIGHT = 232. + expect(COMPOSER_CHROME_HEIGHT - STARTER_ROW_HEIGHT).toBe(120); + expect(NEW_SESSION_PROMPT_CHROME_HEIGHT - STARTER_ROW_HEIGHT).toBe(176); + }); +}); + +describe('resolveComposerMaxHeight', () => { + it('subtracts safe areas, keyboard, header, and chrome from the window height', () => { + // 1000 - 44 - 34 - 336 - 92 - 120 = 374 + expect(resolveComposerMaxHeight(MAX_HEIGHT_ARGS)).toBe(374); + }); + + it('floors at minHeight when the remaining space is smaller', () => { + expect( + resolveComposerMaxHeight({ + ...MAX_HEIGHT_ARGS, + windowHeight: 400, + keyboardHeight: 100, + }) + ).toBe(MIN); + }); + + it('never returns a negative height on a degenerate window', () => { + expect( + resolveComposerMaxHeight({ + ...MAX_HEIGHT_ARGS, + windowHeight: 300, + keyboardHeight: 400, + }) + ).toBe(MIN); + }); +}); diff --git a/apps/mobile/src/components/agents/chat-composer-input-height.ts b/apps/mobile/src/components/agents/chat-composer-input-height.ts index 979e28c576..243dc7b13f 100644 --- a/apps/mobile/src/components/agents/chat-composer-input-height.ts +++ b/apps/mobile/src/components/agents/chat-composer-input-height.ts @@ -9,6 +9,39 @@ export const COMPOSER_INPUT_PADDING_HORIZONTAL = 16; /** … and the 1px `border` on the wrapper View that `onLayout` measures. */ const COMPOSER_INPUT_BORDER_WIDTH = 1; +/** + * Screen chrome above the composer that the remaining-space cap must not eat: + * the `ScreenHeader` (back-button row ~44 + padding ~20) and the + * `SessionConnectionIndicator` (~28), excluding the safe-area top inset which + * the cap subtracts separately. + */ +export const SESSION_HEADER_HEIGHT = 92; + +/** + * Starter chips: the empty session shows four chips that wrap to two rows. + * Budget two rows of ~48 min height each plus the 8px gap and 8px bottom + * padding (112). Kept out of the input's remaining-space budget so the input + * stays clear of the keyboard with starters shown. + */ +export const STARTER_ROW_HEIGHT = 112; + +/** + * Composer chrome other than the input itself, kept out of the input's + * remaining-space budget: the control-row padding (20), the toolbar (44), the + * counter (16), the starter rows (112), and a reserve for the attachment strip + * (40). Conservative so the input stays clear of the keyboard at every Dynamic + * Type scale. + */ +export const COMPOSER_CHROME_HEIGHT = 120 + STARTER_ROW_HEIGHT; + +/** + * New-session prompt chrome other than the input: the control row + toolbar + + * attachment strip + Start button + starter row. The prompt lives in a + * scrollable form, so the cap is a soft bound that keeps the input from + * pushing the Start control off-screen at large text. + */ +export const NEW_SESSION_PROMPT_CHROME_HEIGHT = 176 + STARTER_ROW_HEIGHT; + /** * Width of the real text area inside the composer input row. * @@ -25,3 +58,39 @@ export function resolveComposerTextContentWidth(wrapperWidth: number): number { export function shouldEnableComposerInputScroll(height: number, maxHeight: number): boolean { return height >= maxHeight; } + +/** + * Remaining-space cap for the composer input, replacing the fixed 124/160pt + * caps. The input may grow only into the space left after the keyboard, the + * safe areas, the session header, and every other piece of composer chrome + * (attachment strip, send/stop, mic, newline control, starters, counter) are + * subtracted from the window height. The result is floored at `minHeight` so + * a single-line input is always readable, and a degenerate window (keyboard + + * chrome exceeding the window) can never return a negative height. + */ +export function resolveComposerMaxHeight({ + windowHeight, + safeAreaInsetTop, + safeAreaInsetBottom, + keyboardHeight, + sessionHeaderHeight, + composerChromeHeight, + minHeight, +}: { + windowHeight: number; + safeAreaInsetTop: number; + safeAreaInsetBottom: number; + keyboardHeight: number; + sessionHeaderHeight: number; + composerChromeHeight: number; + minHeight: number; +}): number { + const remaining = + windowHeight - + safeAreaInsetTop - + safeAreaInsetBottom - + keyboardHeight - + sessionHeaderHeight - + composerChromeHeight; + return Math.max(minHeight, Math.floor(remaining)); +} diff --git a/apps/mobile/src/components/agents/chat-composer-input-row.mounted.test.tsx b/apps/mobile/src/components/agents/chat-composer-input-row.mounted.test.tsx index de2cccf1d8..10ab433cd0 100644 --- a/apps/mobile/src/components/agents/chat-composer-input-row.mounted.test.tsx +++ b/apps/mobile/src/components/agents/chat-composer-input-row.mounted.test.tsx @@ -1,16 +1,23 @@ /* eslint-disable typescript-eslint/no-deprecated -- react-test-renderer is the DOM-free renderer used to mount React/RN trees under vitest (same pattern as composer-paste-button.mounted.test.tsx) */ import { createElement } from 'react'; import TestRenderer, { act } from 'react-test-renderer'; -import { describe, expect, it, vi } from 'vitest'; +import { beforeEach, describe, expect, it, vi } from 'vitest'; import { ChatComposerInputRow } from './chat-composer-input-row'; -vi.mock('react-native', () => ({ - ActivityIndicator: 'ActivityIndicator', - Pressable: 'Pressable', - TextInput: 'TextInput', - View: 'View', -})); +const platformOS = vi.hoisted(() => ({ os: 'ios' })); + +function reactNativeMock() { + return { + ActivityIndicator: 'ActivityIndicator', + Platform: { OS: platformOS.os }, + Pressable: 'Pressable', + TextInput: 'TextInput', + View: 'View', + }; +} + +vi.mock('react-native', reactNativeMock); vi.mock('react-native-reanimated', () => ({ default: { View: 'Animated.View' }, FadeIn: { duration: vi.fn(() => ({})) }, @@ -19,6 +26,7 @@ vi.mock('react-native-reanimated', () => ({ })); vi.mock('@/components/ui/icons', () => ({ ArrowUp: 'ArrowUp', + CornerDownLeft: 'CornerDownLeft', Paperclip: 'Paperclip', Square: 'Square', })); @@ -37,6 +45,8 @@ type RenderProps = { hasSendableContent?: boolean; inputEditable: boolean; isStreaming?: boolean; + onSubmit?: () => void; + returnSendsMessage?: boolean; voiceInputAvailable?: boolean; }; @@ -58,12 +68,14 @@ function makeProps(overrides: Partial = {}) { onInputBlur: () => undefined, onInputFocus: () => undefined, onInputLayout: () => undefined, + onInsertNewline: () => undefined, onSelectionChange: () => undefined, onStop: () => undefined, onSubmit: () => undefined, onToggleVoice: () => undefined, paperclipDisabled: false, placeholder: 'Message the agent', + returnSendsMessage: false, textInputStyle: {}, voiceDisabled: false, voiceInputAvailable: false, @@ -107,6 +119,10 @@ async function renderRow(props: RenderProps): Promise { + beforeEach(() => { + platformOS.os = 'ios'; + }); + it('blocks writing tools and selection when the input is not editable', async () => { const renderer = await renderRow({ inputEditable: false }); @@ -174,6 +190,105 @@ describe('ChatComposerInputRow mounted — iOS writing-tools lock', () => { renderer.unmount(); }); + it('renders the newline control and wires return-submit when Return sends', async () => { + const onSubmit = vi.fn<() => void>(); + const renderer = await renderRow({ + inputEditable: true, + returnSendsMessage: true, + onSubmit, + }); + + expect(findAllByType(renderer.root, 'CornerDownLeft')).toHaveLength(1); + expect(findByAccessibilityLabel(renderer.root, 'Insert newline')).not.toBeNull(); + + const input = findTextInput(renderer.root); + expect(input.props.returnKeyType).toBe('send'); + expect(input.props.submitBehavior).toBe('submit'); + + (input.props.onSubmitEditing as () => void)(); + expect(onSubmit).toHaveBeenCalledTimes(1); + + renderer.unmount(); + }); + + it('omits the newline control and keeps newline submit when Return does not send', async () => { + const renderer = await renderRow({ inputEditable: true, returnSendsMessage: false }); + + expect(findAllByType(renderer.root, 'CornerDownLeft')).toHaveLength(0); + + const input = findTextInput(renderer.root); + expect(input.props.returnKeyType).toBe('default'); + expect(input.props.submitBehavior).toBe('newline'); + expect(input.props.onSubmitEditing).toBeUndefined(); + + renderer.unmount(); + }); + + it('sizes the send and stop pressables to the 44pt iOS hit target', async () => { + const sendRenderer = await renderRow({ inputEditable: true }); + const send = findByAccessibilityLabel(sendRenderer.root, 'Send message'); + const sendStyle = send?.props.style as { height: number; width: number } | undefined; + expect(sendStyle?.height).toBe(44); + expect(sendStyle?.width).toBe(44); + sendRenderer.unmount(); + + const stopRenderer = await renderRow({ + inputEditable: true, + isStreaming: true, + canSend: false, + hasSendableContent: false, + }); + const stop = findByAccessibilityLabel(stopRenderer.root, 'Stop generating'); + const stopStyle = stop?.props.style as { height: number; width: number } | undefined; + expect(stopStyle?.height).toBe(44); + expect(stopStyle?.width).toBe(44); + stopRenderer.unmount(); + }); + + it('sizes the send and stop pressables to the 48dp Android hit target', async () => { + platformOS.os = 'android'; + // `CONTROL_HIT_TARGET` is a module-level constant, so the row must be + // re-imported after the platform flips to pick up the Android size. The + // persistent `vi.mock` factory is cached, so `doMock` re-registers it for + // the fresh import and `resetModules` clears the module cache. + vi.doMock('react-native', reactNativeMock); + vi.resetModules(); + const { ChatComposerInputRow: AndroidRow } = await import('./chat-composer-input-row'); + const { createElement: createElementAndroid } = await import('react'); + const { default: Renderer, act: actFresh } = await import('react-test-renderer'); + + const renderAndroid = async (props: RenderProps): Promise => { + const holder: { current?: TestRenderer.ReactTestRenderer } = {}; + await actFresh(async () => { + await Promise.resolve(); + holder.current = Renderer.create(createElementAndroid(AndroidRow, makeProps(props))); + }); + if (!holder.current) { + throw new Error('renderer was not created'); + } + return holder.current; + }; + + const sendRenderer = await renderAndroid({ inputEditable: true }); + const send = findByAccessibilityLabel(sendRenderer.root, 'Send message'); + const sendStyle = send?.props.style as { height: number; width: number } | undefined; + expect(sendStyle?.height).toBe(48); + expect(sendStyle?.width).toBe(48); + sendRenderer.unmount(); + + const stopRenderer = await renderAndroid({ + inputEditable: true, + isStreaming: true, + canSend: false, + hasSendableContent: false, + }); + const stop = findByAccessibilityLabel(stopRenderer.root, 'Stop generating'); + const stopStyle = stop?.props.style as { height: number; width: number } | undefined; + expect(stopStyle?.height).toBe(48); + expect(stopStyle?.width).toBe(48); + stopRenderer.unmount(); + }); + it('renders the mic at the lg size so it reaches the 48dp Android target', async () => { const renderer = await renderRow({ inputEditable: true, diff --git a/apps/mobile/src/components/agents/chat-composer-input-row.tsx b/apps/mobile/src/components/agents/chat-composer-input-row.tsx index 5f87a34766..1c0a6f54e7 100644 --- a/apps/mobile/src/components/agents/chat-composer-input-row.tsx +++ b/apps/mobile/src/components/agents/chat-composer-input-row.tsx @@ -1,4 +1,4 @@ -import { ArrowUp, Paperclip, Square } from '@/components/ui/icons'; +import { ArrowUp, CornerDownLeft, Paperclip, Square } from '@/components/ui/icons'; import { CLOUD_AGENT_PROMPT_MAX_LENGTH } from '@kilocode/cloud-agent-sdk/limits'; import { type RefObject } from 'react'; import Animated, { FadeIn, FadeOut, useReducedMotion } from 'react-native-reanimated'; @@ -6,6 +6,7 @@ import { useTranslation } from 'react-i18next'; import { ActivityIndicator, type LayoutChangeEvent, + Platform, Pressable, TextInput, type TextInputSelectionChangeEvent, @@ -21,6 +22,8 @@ import { type VoiceInputStatus } from '@/lib/voice-input/voice-input-state'; const PAPERCLIP_HIT_SLOP = { top: 8, bottom: 8, left: 8, right: 8 } as const; const CONTROL_HIT_SLOP = 6; +/** Minimum pressable size: 44pt on iOS, 48dp on Android (WCAG 2.5.8 AA). */ +const CONTROL_HIT_TARGET = Platform.OS === 'android' ? 48 : 44; type ChatComposerInputRowProps = { attachmentsEnabled: boolean; @@ -39,12 +42,15 @@ type ChatComposerInputRowProps = { onInputBlur: () => void; onInputFocus: () => void; onInputLayout: (event: LayoutChangeEvent) => void; + onInsertNewline: () => void; onSelectionChange: (event: TextInputSelectionChangeEvent) => void; onStop: () => void; onSubmit: () => void; onToggleVoice: () => void; paperclipDisabled: boolean; placeholder: string; + /** Return submits the message instead of inserting a newline. */ + returnSendsMessage: boolean; textInputStyle: TextStyle; voiceDisabled: boolean; voiceInputAvailable: boolean; @@ -75,12 +81,14 @@ export function ChatComposerInputRow({ onInputBlur, onInputFocus, onInputLayout, + onInsertNewline, onSelectionChange, onStop, onSubmit, onToggleVoice, paperclipDisabled, placeholder, + returnSendsMessage, textInputStyle, voiceDisabled, voiceInputAvailable, @@ -133,13 +141,35 @@ export function ChatComposerInputRow({ contextMenuHidden={!inputEditable} pointerEvents={inputEditable ? 'auto' : 'none'} accessibilityState={{ disabled: inputAccessibilityDisabled }} - returnKeyType="default" - submitBehavior="newline" + returnKeyType={returnSendsMessage ? 'send' : 'default'} + submitBehavior={returnSendsMessage ? 'submit' : 'newline'} + onSubmitEditing={returnSendsMessage ? onSubmit : undefined} + maxFontSizeMultiplier={1} autoCapitalize="sentences" autoCorrect /> + {returnSendsMessage ? ( + + + + + + ) : null} + {voiceInputAvailable ? ( @@ -185,7 +216,8 @@ export function ChatComposerInputRow({ accessibilityRole="button" accessibilityLabel={t('agentChat.composer.sendMessage')} accessibilityState={{ disabled: !canSend, busy: isSending }} - className={`h-8 w-8 items-center justify-center rounded-full active:opacity-70 ${ + style={{ height: CONTROL_HIT_TARGET, width: CONTROL_HIT_TARGET }} + className={`items-center justify-center rounded-full active:opacity-70 ${ canSend ? 'bg-accent-soft' : 'bg-muted' }`} > diff --git a/apps/mobile/src/components/agents/chat-composer.test.ts b/apps/mobile/src/components/agents/chat-composer.test.ts index 7e079c8e9a..4af406088f 100644 --- a/apps/mobile/src/components/agents/chat-composer.test.ts +++ b/apps/mobile/src/components/agents/chat-composer.test.ts @@ -6,8 +6,18 @@ import * as React from 'react'; import { beforeEach, describe, expect, it, vi } from 'vitest'; import { type AgentMode } from '@/components/agents/mode-selector'; +import { Text as renderText } from '@/components/ui/text'; +import { CLOUD_AGENT_PROMPT_MAX_LENGTH } from '@kilocode/cloud-agent-sdk/limits'; import { type ChatComposer } from './chat-composer'; +const layoutDirection = vi.hoisted(() => ({ isRTL: false })); +const TEXT_DIRECTIONS = [ + { direction: 'LTR', isRTL: false, style: undefined }, + { direction: 'RTL', isRTL: true, style: [{ writingDirection: 'rtl' }, undefined] }, +]; + +vi.mock('@rn-primitives/slot', () => ({ Text: 'SlotText' })); + // The composer's uncontrolled input is covered by Appium E2E; this suite pins // the draft-restore contract that a native E2E cannot easily prove: a restored // draft must be readable by an immediate send (before any keystroke). The @@ -23,6 +33,12 @@ const onSendMock = vi.fn(async () => undefined); // re-render (`rerender`) with the refs — the composer's live text and its // applied-draft flag — intact, and start a fresh instance with `mount`. const refSlots = vi.hoisted(() => ({ slots: [] as { current: unknown }[], cursor: 0 })); +// `useState` is stateful per call-order slot (mirroring `refSlots`) so the +// starter/`hasText` gating can be observed across a re-render, not just the +// mount-time initial value. +const stateSlots = vi.hoisted(() => ({ slots: [] as { value: unknown }[], cursor: 0 })); +const returnSendsPref = vi.hoisted(() => ({ returnSendsMessage: false })); +const reducedMotionOn = vi.hoisted(() => ({ value: false })); // The strip wire-lock test asserts the composer forwards the upload hook's // move/reorder callbacks by identity, so both mocks must be hoisted and shared. @@ -34,6 +50,7 @@ vi.mock('react', async () => { return { ...actual, useCallback: vi.fn( unknown>(fn: T) => fn), + useContext: vi.fn(() => undefined), useEffect: vi.fn((fn: React.EffectCallback) => { fn(); }), @@ -45,7 +62,18 @@ vi.mock('react', async () => { refSlots.slots[index] ??= { current: initial }; return refSlots.slots[index] as React.RefObject; }), - useState: vi.fn((initial: T) => [initial, vi.fn() as () => void] as [T, (value: T) => void]), + useState: vi.fn((initial: T) => { + const index = stateSlots.cursor; + stateSlots.cursor += 1; + const slot = (stateSlots.slots[index] ??= { value: initial as unknown }); + return [ + slot.value as T, + (next: T | ((prev: T) => T)) => { + slot.value = + typeof next === 'function' ? (next as (prev: T) => T)(slot.value as T) : next; + }, + ] as [T, (value: T | ((prev: T) => T)) => void]; + }), }; }); @@ -54,11 +82,22 @@ vi.mock('react-native', () => ({ AppState: { addEventListener: () => ({ remove: vi.fn() }), }, - Keyboard: { dismiss: vi.fn() }, + Keyboard: { + addListener: vi.fn(() => ({ remove: vi.fn() })), + dismiss: vi.fn(), + }, + I18nManager: layoutDirection, Platform: { OS: 'ios' }, + Pressable: 'Pressable', + Text: 'Text', + useWindowDimensions: () => ({ fontScale: 1, height: 800, scale: 1, width: 400 }), View: 'View', })); +vi.mock('react-native-safe-area-context', () => ({ + useSafeAreaInsets: () => ({ bottom: 0, left: 0, right: 0, top: 0 }), +})); + vi.mock('react-native-gesture-handler', () => ({ Gesture: { Pan: () => ({ @@ -92,6 +131,7 @@ vi.mock('react-native-reanimated', () => ({ default: { View: 'Animated.View' }, FadeIn: { duration: vi.fn(() => ({})) }, FadeOut: { duration: vi.fn(() => ({})) }, + useReducedMotion: () => reducedMotionOn.value, })); vi.mock('expo-haptics', () => ({ @@ -261,6 +301,14 @@ vi.mock('@/lib/voice-input/voice-input-draft', () => ({ applyVoiceDraftToInput: vi.fn(), })); +vi.mock('@/lib/hooks/use-return-sends-message-preference', () => ({ + useReturnSendsMessagePreference: () => ({ + returnSendsMessage: returnSendsPref.returnSendsMessage, + hasLoaded: true, + setReturnSendsMessage: vi.fn(), + }), +})); + type ComposerProps = Parameters[0]; function makeProps(overrides: Partial = {}): ComposerProps { @@ -337,13 +385,46 @@ function requireInputRowOnChangeText(render: React.ReactElement): (text: string) return onChangeText; } +function findNode( + node: Node, + predicate: (type: unknown, props: Record) => boolean +): { type: unknown; props: Record } | null { + if (node === null || typeof node !== 'object') { + return null; + } + const { type, props } = node as { type?: unknown; props?: Record }; + if (props !== undefined && predicate(type, props)) { + return { type, props }; + } + if (type === renderText && props !== undefined) { + return findNode(renderText(props as React.ComponentProps), predicate); + } + const children = props?.children; + for (const child of Array.isArray(children) ? children : [children]) { + const found = findNode(child as Node, predicate); + if (found) { + return found; + } + } + return null; +} + +function findStarterChip(render: React.ReactElement, label: string) { + return findNode( + render, + (type, props) => type === 'Pressable' && props.accessibilityLabel === label + ); +} + async function mount(props: ComposerProps): Promise { refSlots.slots.length = 0; + stateSlots.slots.length = 0; return rerender(props); } async function rerender(props: ComposerProps): Promise { refSlots.cursor = 0; + stateSlots.cursor = 0; const { ChatComposer } = await import('./chat-composer'); return ChatComposer(props); } @@ -358,6 +439,11 @@ beforeEach(() => { vi.clearAllMocks(); refSlots.slots.length = 0; refSlots.cursor = 0; + stateSlots.slots.length = 0; + stateSlots.cursor = 0; + returnSendsPref.returnSendsMessage = false; + reducedMotionOn.value = false; + layoutDirection.isRTL = false; }); // The restore contract has one axis: whether the host resolved a draft. Both @@ -439,6 +525,121 @@ describe('ChatComposer draft restore', () => { }); }); }); +describe('ChatComposer return-sends wiring', () => { + it('wires the return-sends preference and an insert-newline handler to the input row', async () => { + returnSendsPref.returnSendsMessage = true; + const withReturnSend = findInputRowProps(await mount(makeProps({}))); + expect(withReturnSend?.returnSendsMessage).toBe(true); + expect(typeof withReturnSend?.onInsertNewline).toBe('function'); + + returnSendsPref.returnSendsMessage = false; + const withNewline = findInputRowProps(await mount(makeProps({}))); + expect(withNewline?.returnSendsMessage).toBe(false); + }); + + it('inserts a newline into the draft without submitting', async () => { + returnSendsPref.returnSendsMessage = true; + const render = await mount(makeProps({})); + const rowProps = findInputRowProps(render); + const onInsertNewline = rowProps?.onInsertNewline as (() => void) | undefined; + if (onInsertNewline === undefined) { + throw new Error('ChatComposerInputRow element did not carry an onInsertNewline handler'); + } + + onInsertNewline(); + + expect(refSlots.slots[0]?.current).toBe('\n'); + expect(onSendMock).not.toHaveBeenCalled(); + }); +}); + +describe('ChatComposer starter chips', () => { + it('hides starters once the input holds text', async () => { + const render = await mount(makeProps({})); + expect(findStarterChip(render, 'Build a feature')).not.toBeNull(); + + requireInputRowOnChangeText(render)('typed text'); + await settle(); + + const rerendered = await rerender(makeProps({})); + expect(findStarterChip(rerendered, 'Build a feature')).toBeNull(); + }); + + it.each(TEXT_DIRECTIONS)( + 'preserves starter text and inserts it without submitting in $direction', + async ({ isRTL, style }) => { + layoutDirection.isRTL = isRTL; + const render = await mount(makeProps({})); + const chip = findStarterChip(render, 'Build a feature'); + if (!chip) { + throw new Error('starter chip not found'); + } + expect(findNode(chip, type => type === 'Text')?.props).toMatchObject({ + children: 'Build a feature', + className: 'text-sm font-normal text-muted-foreground', + style, + }); + const onPress = chip.props.onPress as (() => void) | undefined; + if (!onPress) { + throw new Error('starter chip missing onPress'); + } + + onPress(); + await settle(); + + expect(refSlots.slots[0]?.current).toBe('Build a feature'); + expect(onSendMock).not.toHaveBeenCalled(); + } + ); +}); + +describe('ChatComposer counter', () => { + it.each(TEXT_DIRECTIONS)( + 'shows the remaining-character counter only once the draft nears the limit in $direction', + async ({ isRTL, style }) => { + layoutDirection.isRTL = isRTL; + const render = await mount(makeProps({})); + expect( + findNode(render, (type, props) => type === 'Text' && typeof props.children === 'number') + ).toBeNull(); + + requireInputRowOnChangeText(render)('hello'); + await settle(); + expect( + findNode( + await rerender(makeProps({})), + (type, props) => type === 'Text' && typeof props.children === 'number' + ) + ).toBeNull(); + + requireInputRowOnChangeText(render)('x'.repeat(CLOUD_AGENT_PROMPT_MAX_LENGTH - 5)); + await settle(); + + const rerendered = await rerender(makeProps({})); + const counter = findNode( + rerendered, + (type, props) => type === 'Text' && props.children === 5 + ); + expect(counter).not.toBeNull(); + expect(counter?.props).toMatchObject({ + accessibilityLabel: '5 characters remaining', + className: 'text-xs font-normal text-muted-foreground', + style, + }); + } + ); +}); + +describe('ChatComposer reduced motion', () => { + it('drops the toolbar entrance animation under reduced motion', async () => { + reducedMotionOn.value = true; + const render = await mount(makeProps({})); + + const toolbarView = findNode(render, type => type === 'Animated.View'); + expect(toolbarView).not.toBeNull(); + expect(toolbarView?.props.entering).toBeUndefined(); + }); +}); describe('ChatComposer attachment strip wiring', () => { it('wires onMove and onReorder from the upload hook into the attachment strip', async () => { diff --git a/apps/mobile/src/components/agents/chat-composer.tsx b/apps/mobile/src/components/agents/chat-composer.tsx index df77ee17ac..e0168e00b3 100644 --- a/apps/mobile/src/components/agents/chat-composer.tsx +++ b/apps/mobile/src/components/agents/chat-composer.tsx @@ -25,14 +25,17 @@ import { Keyboard, type LayoutChangeEvent, Platform, + Pressable, type TextInput, type TextInputSelectionChangeEvent, type TextStyle, + useWindowDimensions, View, } from 'react-native'; import { Gesture, GestureDetector } from 'react-native-gesture-handler'; import Animated, { FadeIn, FadeOut } from 'react-native-reanimated'; import { useNavigation } from 'expo-router'; +import { useSafeAreaInsets } from 'react-native-safe-area-context'; import { toast } from 'sonner-native'; import { i18n } from '@/i18n'; @@ -41,6 +44,7 @@ import { ChatToolbar } from '@/components/agents/chat-toolbar'; import { type AgentMode } from '@/components/agents/mode-selector'; import { pickAgentAttachments } from '@/components/agents/attachment-picker'; import { AccessibleStatus } from '@/components/ui/accessible-status'; +import { Text } from '@/components/ui/text'; import { usePreventRemove } from '@/lib/navigation/prevent-remove'; import { createMobileSlashCommandList, @@ -54,14 +58,19 @@ import { pasteTextIntoComposer, } from '@/components/agents/composer-paste-text'; import { + COMPOSER_CHROME_HEIGHT, COMPOSER_INPUT_PADDING_HORIZONTAL, + resolveComposerMaxHeight, resolveComposerTextContentWidth, + SESSION_HEADER_HEIGHT, shouldEnableComposerInputScroll, } from '@/components/agents/chat-composer-input-height'; import { showRemoteSessionExitConfirmation } from '@/components/agents/remote-session-exit-alert'; import { SlashCommandSuggestions } from '@/components/agents/slash-command-suggestions'; import { useTextHeight } from '@/components/agents/use-text-height'; import { resolveChatComposerControlState } from '@/components/agents/chat-composer-input-state'; +import { useReturnSendsMessagePreference } from '@/lib/hooks/use-return-sends-message-preference'; +import { selectReducedMotionEntrance, useMotionPolicy } from '@/lib/a11y/motion'; import { nextStopRemountPhase, type StopRemountPhase, @@ -108,17 +117,15 @@ import { } from '@/lib/voice-input/voice-input-draft'; import { settleVoiceInputBeforeSubmit } from '@/lib/voice-input/voice-input-submit'; -const TEXT_INPUT_MAX_LINES = 5; const TEXT_INPUT_LINE_HEIGHT = 20; const TEXT_INPUT_VERTICAL_PADDING = 24; -const TEXT_INPUT_MIN_HEIGHT = TEXT_INPUT_LINE_HEIGHT + TEXT_INPUT_VERTICAL_PADDING; -const TEXT_INPUT_MAX_HEIGHT = - TEXT_INPUT_LINE_HEIGHT * TEXT_INPUT_MAX_LINES + TEXT_INPUT_VERTICAL_PADDING; const TEXT_INPUT_FONT_SIZE = 16; const COMPOSER_FOCUS_RESTORE_DELAY_MS = 100; /** Match RNGH pan activeOffsetY / failOffsetX so Android JS path and iOS pan agree. */ const DISMISS_KEYBOARD_ACTIVE_OFFSET_Y = 24; const DISMISS_KEYBOARD_FAIL_OFFSET_X = 16; +/** Hide the remaining-characters counter until the draft nears the limit. */ +const COMPOSER_COUNTER_VISIBLE_REMAINING = 1000; type AndroidDismissKeyboardGesture = { identifier: string; @@ -198,10 +205,15 @@ type ChatComposerProps = { initialDraft?: string; /** Active session id, used to scope the Android picker launch context. */ sessionId?: string | null; + /** Session lifecycle phase; drives contextual starter chips. Defaults to `message`. */ + sessionState?: ChatComposerSessionState; /** Imperative handle the host binds to call `setText`. */ controlRef?: Ref; }; +/** Lifecycle phases that change the composer placeholder and starter chips. */ +export type ChatComposerSessionState = 'empty' | 'preparing' | 'finalizing' | 'message'; + export function ChatComposer({ onSend, onSendCommand, @@ -231,10 +243,15 @@ export function ChatComposer({ draftKey, initialDraft, sessionId = null, + sessionState = 'message', controlRef, }: Readonly) { const colors = useThemeColors(); const { showActionSheetWithOptions } = useActionSheet(); + const { height: windowHeight, fontScale } = useWindowDimensions(); + const insets = useSafeAreaInsets(); + const { reducedMotion } = useMotionPolicy(); + const { returnSendsMessage } = useReturnSendsMessagePreference(); // Draft persistence is fenced on identity: while the user id is unknown, // drafts neither save nor flush (the drafts module no-ops on an empty id). const { userId } = useCurrentUserId(); @@ -259,6 +276,8 @@ export function ChatComposer({ const restoreFocusOnActiveRef = useRef(false); const restoreFocusTimeoutRef = useRef | null>(null); const [hasText, setHasText] = useState(false); + const [characterCount, setCharacterCount] = useState(0); + const [keyboardHeight, setKeyboardHeight] = useState(0); const [slashCommandInput, setSlashCommandInput] = useState(null); const [inputWidth, setInputWidth] = useState(0); const [isFocused, setIsFocused] = useState(false); @@ -342,13 +361,43 @@ export function ChatComposer({ ); }); + const fontSize = TEXT_INPUT_FONT_SIZE * fontScale; + const lineHeight = TEXT_INPUT_LINE_HEIGHT * fontScale; + const inputMinHeight = lineHeight + TEXT_INPUT_VERTICAL_PADDING; + const inputMaxHeight = resolveComposerMaxHeight({ + windowHeight, + safeAreaInsetTop: insets.top, + safeAreaInsetBottom: insets.bottom, + keyboardHeight, + sessionHeaderHeight: SESSION_HEADER_HEIGHT * fontScale, + composerChromeHeight: COMPOSER_CHROME_HEIGHT * fontScale, + minHeight: inputMinHeight, + }); + + // Track the keyboard's reported height so the remaining-space cap follows it. + useEffect(() => { + const showEvent = Platform.OS === 'ios' ? 'keyboardWillShow' : 'keyboardDidShow'; + const hideEvent = Platform.OS === 'ios' ? 'keyboardWillHide' : 'keyboardDidHide'; + const show = Keyboard.addListener(showEvent, event => { + setKeyboardHeight(Math.max(event.endCoordinates.height, 0)); + }); + const hide = Keyboard.addListener(hideEvent, () => { + setKeyboardHeight(0); + }); + return () => { + show.remove(); + hide.remove(); + }; + }, []); + const measure = useTextHeight({ - minHeight: TEXT_INPUT_MIN_HEIGHT, - maxHeight: TEXT_INPUT_MAX_HEIGHT, + minHeight: inputMinHeight, + maxHeight: inputMaxHeight, verticalPadding: TEXT_INPUT_VERTICAL_PADDING, textContentWidth: resolveComposerTextContentWidth(inputWidth), fontSize: TEXT_INPUT_FONT_SIZE, lineHeight: TEXT_INPUT_LINE_HEIGHT, + fontScale, }); // useTextHeight() returns a new object every render. Hold the latest // measure in a ref so the draft-restore effect only runs after an @@ -367,6 +416,7 @@ export function ChatComposer({ composerFrameCoalescerRef.current ??= createFrameCoalescer(value => { measureRef.current.setText(value); setHasText(value.trim().length > 0); + setCharacterCount(value.length); setSlashCommandInput(getSlashCommandCandidate(value)); }); const composerFrameCoalescer = composerFrameCoalescerRef.current; @@ -401,6 +451,7 @@ export function ChatComposer({ }); selectionRef.current = { start: draft.length, end: draft.length }; setHasText(draft.trim().length > 0); + setCharacterCount(draft.length); setSlashCommandInput(getSlashCommandCandidate(draft)); measureRef.current.setText(draft); }, []); @@ -468,6 +519,7 @@ export function ChatComposer({ textRef.current = value; measure.setText(value); setHasText(value.trim().length > 0); + setCharacterCount(value.length); setSlashCommandInput(null); inputRef.current?.setNativeProps({ text: value, @@ -692,7 +744,7 @@ export function ChatComposer({ }; }, []); - const inputScrollable = shouldEnableComposerInputScroll(measure.height, TEXT_INPUT_MAX_HEIGHT); + const inputScrollable = shouldEnableComposerInputScroll(measure.height, inputMaxHeight); const dismissKeyboardPan = useMemo( () => // eslint-disable-next-line new-cap -- RNGH's gesture builder API is Gesture.Pan(). @@ -795,6 +847,7 @@ export function ChatComposer({ composerFrameCoalescer.flush(); textRef.current = ''; setHasText(false); + setCharacterCount(0); setSlashCommandInput(null); measure.reset(); inputRef.current?.clear(); @@ -938,6 +991,45 @@ export function ChatComposer({ applyComposerText(`/${command.name} `); } + // Visible newline control for the Return-sends preference: inserts `\n` at + // the caret the same way the platform paste does, without ever submitting. + function handleInsertNewline() { + if (sendLockRef.current.isLocked() || !control.inputEditable) { + return; + } + selectionRef.current = pasteTextIntoComposer('\n', { + input: inputRef.current, + draft: textRef.current, + selection: selectionRef.current, + maxLength: CLOUD_AGENT_PROMPT_MAX_LENGTH, + onChangeText: handleChangeText, + }); + } + + // Starter chips are static, localized prompt ideas plus a contextual chip for + // the session lifecycle phase. They insert and focus only; they never submit. + const starterChips = useMemo(() => { + const staticChips = [ + i18n.t('agentChat.composer.starterChipBuild'), + i18n.t('agentChat.composer.starterChipFix'), + i18n.t('agentChat.composer.starterChipWriteTests'), + ]; + if (sessionState === 'preparing') { + return [i18n.t('agentChat.composer.starterChipPrepare'), ...staticChips]; + } + if (sessionState === 'finalizing') { + return [i18n.t('agentChat.composer.starterChipWrapUp'), ...staticChips]; + } + if (sessionState === 'empty') { + return [i18n.t('agentChat.composer.starterChipEmpty'), ...staticChips]; + } + return staticChips; + }, [sessionState]); + const showStarters = + !hasText && + !isSending && + (!disabled || sessionState === 'preparing' || sessionState === 'finalizing'); + async function submit() { // Commit any coalesced derived state (hasText, measure, slash command) // before the send decision, so a submit in the same frame as the last @@ -1029,10 +1121,10 @@ export function ChatComposer({ const textInputStyle: TextStyle = { color: colors.foreground, - fontSize: TEXT_INPUT_FONT_SIZE, + fontSize, height: measure.height, includeFontPadding: false, - lineHeight: TEXT_INPUT_LINE_HEIGHT, + lineHeight, paddingHorizontal: COMPOSER_INPUT_PADDING_HORIZONTAL, paddingVertical: 12, textAlignVertical: 'top', @@ -1044,7 +1136,10 @@ export function ChatComposer({ {measure.measureElement} {control.showToolbar ? ( - + 0 && !isSending ? ( - + + {showStarters ? ( + + {starterChips.map(chip => ( + { + applyComposerText(chip); + }} + accessibilityRole="button" + accessibilityLabel={chip} + hitSlop={{ top: 8, bottom: 8 }} + className="items-center justify-center rounded-full border border-border bg-card px-3 py-1.5 active:opacity-70" + > + {chip} + + ))} + + ) : null} + + {CLOUD_AGENT_PROMPT_MAX_LENGTH - characterCount <= COMPOSER_COUNTER_VISIBLE_REMAINING ? ( + + + {CLOUD_AGENT_PROMPT_MAX_LENGTH - characterCount} + + + ) : null} + { void handleAddAttachment(); @@ -1121,6 +1251,7 @@ export function ChatComposer({ setIsFocused(true); }} onInputLayout={handleInputLayout} + onInsertNewline={handleInsertNewline} onSelectionChange={handleSelectionChange} onStop={handleStop} onSubmit={() => { @@ -1131,6 +1262,7 @@ export function ChatComposer({ }} paperclipDisabled={control.paperclipDisabled} placeholder={placeholder} + returnSendsMessage={returnSendsMessage} textInputStyle={textInputStyle} voiceDisabled={control.voiceDisabled} voiceInputAvailable={voiceInput.available} diff --git a/apps/mobile/src/components/agents/mobile-session-manager.ts b/apps/mobile/src/components/agents/mobile-session-manager.ts index 2c1aadefb9..04ecc61fd3 100644 --- a/apps/mobile/src/components/agents/mobile-session-manager.ts +++ b/apps/mobile/src/components/agents/mobile-session-manager.ts @@ -94,14 +94,6 @@ export function isCancelQueuedUpgradeRequired(error: unknown): boolean { return readFetchSessionErrorCode(error) === CANCEL_QUEUED_UPGRADE_REQUIRED_CODE; } -export type CancelQueuedRestoreOutcome = 'restore' | 'keep-restore'; - -export function resolveCancelQueuedRestoreOutcome( - composerHasContent: boolean -): CancelQueuedRestoreOutcome { - return composerHasContent ? 'keep-restore' : 'restore'; -} - /* eslint-disable @typescript-eslint/promise-function-async, require-await -- thin tRPC passthrough */ async function defaultFetchSessionQuery( sessionId: KiloSessionId diff --git a/apps/mobile/src/components/agents/new-session-configure-form.tsx b/apps/mobile/src/components/agents/new-session-configure-form.tsx index cc7669dd96..83e188cea2 100644 --- a/apps/mobile/src/components/agents/new-session-configure-form.tsx +++ b/apps/mobile/src/components/agents/new-session-configure-form.tsx @@ -222,6 +222,7 @@ export function NewSessionConfigureForm({ shareId={shareId} voiceInputSettlerRef={voiceInputSettlerRef} initialPrompt={initialPrompt} + onStartSession={isStartDisabled ? undefined : onStartSession} isCloneEntry={isCloneEntry} /> diff --git a/apps/mobile/src/components/agents/new-session-prompt-controls.tsx b/apps/mobile/src/components/agents/new-session-prompt-controls.tsx index 7a00c6aaac..10a9b707b7 100644 --- a/apps/mobile/src/components/agents/new-session-prompt-controls.tsx +++ b/apps/mobile/src/components/agents/new-session-prompt-controls.tsx @@ -1,3 +1,4 @@ +import { type ReactNode } from 'react'; import { Pressable, View } from 'react-native'; import { useTranslation } from 'react-i18next'; @@ -14,6 +15,7 @@ type NewSessionPromptControlsProps = { voiceInput: ReturnType; onAddAttachment: () => void; pasteClipboard: () => void; + children?: ReactNode; }; export function NewSessionPromptControls({ @@ -21,6 +23,7 @@ export function NewSessionPromptControls({ voiceInput, onAddAttachment, pasteClipboard, + children, }: Readonly) { const colors = useThemeColors(); const { t } = useTranslation(); @@ -60,6 +63,7 @@ export function NewSessionPromptControls({ ) : null} + {children} {voiceInput.available ? ( ({ isRTL: false })); +const TEXT_DIRECTIONS = [ + { direction: 'LTR', isRTL: false, style: undefined }, + { direction: 'RTL', isRTL: true, style: [{ writingDirection: 'rtl' }, undefined] }, +]; + +vi.mock('@rn-primitives/slot', () => ({ Text: 'SlotText' })); vi.mock('react-i18next', async importOriginal => { const actual = await importOriginal(); @@ -33,6 +43,7 @@ vi.mock('react', async () => { return { ...actual, useCallback: vi.fn( unknown>(fn: T) => fn), + useContext: vi.fn(() => undefined), useEffect: vi.fn((fn: React.EffectCallback) => { fn(); }), @@ -40,15 +51,30 @@ vi.mock('react', async () => { const ref: React.RefObject = { current: initial }; return ref; }), - useState: vi.fn((initial: T) => [initial, vi.fn() as () => void] as [T, (value: T) => void]), + useState: vi.fn( + (initial: T | (() => T)) => + [ + typeof initial === 'function' ? (initial as () => T)() : initial, + vi.fn() as () => void, + ] as [T, (value: T) => void] + ), }; }); // ── react-native ──────────────────────────────────────────────────── vi.mock('react-native', () => ({ + AccessibilityInfo: { + announceForAccessibility: vi.fn(), + }, + Keyboard: { + addListener: vi.fn(() => ({ remove: vi.fn() })), + }, + I18nManager: layoutDirection, Platform: { OS: 'ios' }, Pressable: 'Pressable', + Text: 'Text', TextInput: 'TextInput', + useWindowDimensions: () => ({ fontScale: 1, height: 800, scale: 1, width: 400 }), View: 'View', })); @@ -58,9 +84,14 @@ vi.mock('react-native-reanimated', () => ({ FadeOut: { duration: vi.fn() }, })); +vi.mock('react-native-safe-area-context', () => ({ + useSafeAreaInsets: () => ({ bottom: 0, left: 0, right: 0, top: 0 }), +})); + // ── icons ────────────────────────────────────────────────────────── vi.mock('@/components/ui/icons', () => ({ ClipboardPaste: () => null, + CornerDownLeft: () => null, Paperclip: () => null, })); @@ -76,6 +107,10 @@ vi.mock('@/components/agents/attachment-preview-strip', () => ({ AttachmentPreviewStrip: () => null, })); +vi.mock('@/components/ui/accessible-status', () => ({ + AccessibleStatus: () => null, +})); + vi.mock('@/components/agents/chat-toolbar', () => ({ ChatToolbar: () => null, })); @@ -109,6 +144,16 @@ vi.mock('@/lib/hooks/use-theme-colors', () => ({ }), })); +const returnSendsMessage = vi.hoisted(() => ({ current: false })); + +vi.mock('@/lib/hooks/use-return-sends-message-preference', () => ({ + useReturnSendsMessagePreference: () => ({ + returnSendsMessage: returnSendsMessage.current, + hasLoaded: true, + setReturnSendsMessage: vi.fn(), + }), +})); + vi.mock('@/lib/agent-attachments/use-clipboard-paste', () => ({ useClipboardPaste: (options: { addText?: (text: string) => void }) => { clipboardHintOptions.current = options; @@ -145,24 +190,39 @@ vi.mock('@/lib/voice-input/voice-input-draft', () => ({ type Node = { props?: Record } | null | undefined | string | number | boolean; type ElementType = string | ((...args: never[]) => unknown); -function findElementByType(node: Node, target: ElementType): Record | null { +function findElementByType( + node: Node, + target: ElementType, + accessibilityLabel?: string +): Record | null { if (node === null || typeof node !== 'object') { return null; } const props = node.props ?? {}; const children = props.children; const nodeType = (node as { type?: unknown }).type; - if (nodeType === target) { + if ( + nodeType === target && + (accessibilityLabel === undefined || props.accessibilityLabel === accessibilityLabel) + ) { return node.props ?? {}; } + if (nodeType === renderText) { + return findElementByType( + renderText(props as React.ComponentProps), + target, + accessibilityLabel + ); + } if (nodeType === renderPromptControls) { return findElementByType( renderPromptControls(props as React.ComponentProps), - target + target, + accessibilityLabel ); } for (const child of Array.isArray(children) ? children : [children]) { - const found = findElementByType(child as Node, target); + const found = findElementByType(child as Node, target, accessibilityLabel); if (found) { return found; } @@ -201,8 +261,67 @@ function defaultProps() { describe('NewSessionPrompt initialPrompt seed', () => { beforeEach(() => { voiceInputAvailable.current = false; + returnSendsMessage.current = false; + layoutDirection.isRTL = false; }); + it.each(TEXT_DIRECTIONS)( + 'preserves starter text and insertion in $direction', + async ({ isRTL, style }) => { + const { NewSessionPrompt: renderPrompt } = await import('./new-session-prompt'); + layoutDirection.isRTL = isRTL; + let draft = ''; + let started = false; + const element = renderPrompt({ + ...defaultProps(), + onChangeText: text => { + draft = text; + }, + onStartSession: () => { + started = true; + }, + }); + const chip = findElementByType(element, 'Pressable', 'Build a feature'); + if (chip === null) { + throw new Error('starter chip not found'); + } + expect(findElementByType(chip.children as Node, 'Text')).toMatchObject({ + children: 'Build a feature', + className: 'text-sm font-normal text-muted-foreground', + style, + }); + expect(findElementByType(element, 'Text', '100000 characters remaining')).toBeNull(); + + const onPress = chip.onPress as () => void; + onPress(); + + expect(draft).toBe('Build a feature'); + expect(started).toBe(false); + } + ); + + it.each(TEXT_DIRECTIONS)( + 'shows the seeded counter and its accessible label only near the limit in $direction', + async ({ isRTL, style }) => { + const { NewSessionPrompt: renderPrompt } = await import('./new-session-prompt'); + layoutDirection.isRTL = isRTL; + const shortSeed = renderPrompt({ ...defaultProps(), initialPrompt: 'hello' }); + expect(findElementByType(shortSeed, 'Text', '99995 characters remaining')).toBeNull(); + expect(findElementByType(shortSeed, 'Pressable', 'Build a feature')).toBeNull(); + + const element = renderPrompt({ + ...defaultProps(), + initialPrompt: 'x'.repeat(100_000 - 5), + }); + expect(findElementByType(element, 'Text', '5 characters remaining')).toMatchObject({ + children: 5, + className: 'text-xs font-normal text-muted-foreground', + style, + }); + expect(findElementByType(element, 'Pressable', 'Build a feature')).toBeNull(); + } + ); + it('does not invoke onChangeText when initialPrompt seeds the uncontrolled input on mount', async () => { const { NewSessionPrompt } = await import('./new-session-prompt'); @@ -320,7 +439,13 @@ describe('NewSessionPrompt initialPrompt seed', () => { voiceInputAvailable.current = voiceAvailable; const element = renderPrompt({ ...defaultProps(), isCreating, attachmentMax }); - expect(findElementByType(element, 'Pressable')).toMatchObject({ + expect( + findElementByType( + element, + 'Pressable', + ReactI18next.getI18n().t('agentChat.newSession.addAttachment') + ) + ).toMatchObject({ disabled: paperclipDisabled, accessibilityState: { disabled: paperclipDisabled }, hitSlop: { top: 8, bottom: 8, left: 8, right: 8 }, @@ -334,6 +459,30 @@ describe('NewSessionPrompt initialPrompt seed', () => { ); }); + it('keeps the newline button between the voice status and voice button', async () => { + const { NewSessionPrompt: renderPrompt } = await import('./new-session-prompt'); + voiceInputAvailable.current = true; + returnSendsMessage.current = true; + const element = renderPrompt(defaultProps()); + const controlsProps = findElementByType(element, renderPromptControls); + expect(controlsProps).not.toBeNull(); + + const controls: React.ReactElement<{ children: Node[] }> = renderPromptControls( + controlsProps as React.ComponentProps + ); + const [, status, newline, voice] = controls.props.children; + + expect(findElementByType(status, VoiceInputStatus)).not.toBeNull(); + expect( + findElementByType( + newline, + 'Pressable', + ReactI18next.getI18n().t('agentChat.composer.insertNewline') + ) + ).not.toBeNull(); + expect(findElementByType(voice, VoiceInputButton)).not.toBeNull(); + }); + it('keeps the extracted controls unmounted for clone entry', async () => { const { NewSessionPrompt: renderPrompt } = await import('./new-session-prompt'); voiceInputAvailable.current = true; diff --git a/apps/mobile/src/components/agents/new-session-prompt.tsx b/apps/mobile/src/components/agents/new-session-prompt.tsx index 275f4e7daa..d42e33f4aa 100644 --- a/apps/mobile/src/components/agents/new-session-prompt.tsx +++ b/apps/mobile/src/components/agents/new-session-prompt.tsx @@ -1,25 +1,39 @@ +/* eslint-disable max-lines -- The new-session prompt owns its uncontrolled input, starter chips, counter, newline control, voice dictation, and attachment strip end-to-end in one cohesive surface. */ import { CLOUD_AGENT_PROMPT_MAX_LENGTH } from '@kilocode/cloud-agent-sdk/limits'; import { useCallback, useEffect, useRef, useState } from 'react'; import { AccessibilityInfo, + Keyboard, type LayoutChangeEvent, Platform, + Pressable, TextInput as RNTextInput, type TextInput, type TextInputSelectionChangeEvent, type TextStyle, + useWindowDimensions, View, } from 'react-native'; +import { CornerDownLeft } from '@/components/ui/icons'; import { toast } from 'sonner-native'; +import { useSafeAreaInsets } from 'react-native-safe-area-context'; import { useTranslation } from 'react-i18next'; import { AttachmentPreviewStrip } from '@/components/agents/attachment-preview-strip'; +import { AccessibleStatus } from '@/components/ui/accessible-status'; +import { Text } from '@/components/ui/text'; import { type ComposerSelection, pasteTextIntoComposer, } from '@/components/agents/composer-paste-text'; import { ChatToolbar } from '@/components/agents/chat-toolbar'; import { useTextHeight } from '@/components/agents/use-text-height'; +import { + NEW_SESSION_PROMPT_CHROME_HEIGHT, + resolveComposerMaxHeight, + SESSION_HEADER_HEIGHT, +} from '@/components/agents/chat-composer-input-height'; +import { useReturnSendsMessagePreference } from '@/lib/hooks/use-return-sends-message-preference'; import { resolveNewSessionPromptControlState } from '@/components/agents/new-session-prompt-state'; import { NewSessionPromptClone } from '@/components/agents/new-session-prompt-clone'; import { NewSessionPromptControls } from '@/components/agents/new-session-prompt-controls'; @@ -35,30 +49,37 @@ import { import { useVoiceInput } from '@/lib/voice-input/use-voice-input'; import { describeClassificationFailure } from '@/lib/agent-attachments/validate'; import { AGENT_ATTACHMENT_MAX_BYTES } from '@/lib/agent-attachments/constants'; +import { + hasAnyFailedAttachment, + isAnyAttachmentUploading, +} from '@/lib/agent-attachments/agent-attachment-types'; import { clipboardPasteEmptyMessage, useClipboardPaste, } from '@/lib/agent-attachments/use-clipboard-paste'; const PROMPT_INPUT_DEFAULT_LINES = 3; -const PROMPT_INPUT_MAX_LINES = 6; const PROMPT_INPUT_LINE_HEIGHT = 24; +const PROMPT_INPUT_FONT_SIZE = 16; // Must mirror the TextInput's actual padding: py-2 (16 total) and px-2 on // iOS (16 total) / the 24pt-per-side Android inset (48 total). const PROMPT_INPUT_VERTICAL_PADDING = 16; const PROMPT_INPUT_HORIZONTAL_PADDING = Platform.OS === 'android' ? 48 : 16; const PROMPT_INPUT_ANDROID_HORIZONTAL_INSET = 24; const PROMPT_INPUT_MAX_CHARS = CLOUD_AGENT_PROMPT_MAX_LENGTH; -const PROMPT_INPUT_MIN_HEIGHT = - PROMPT_INPUT_LINE_HEIGHT * PROMPT_INPUT_DEFAULT_LINES + PROMPT_INPUT_VERTICAL_PADDING; -const PROMPT_INPUT_MAX_HEIGHT = - PROMPT_INPUT_LINE_HEIGHT * PROMPT_INPUT_MAX_LINES + PROMPT_INPUT_VERTICAL_PADDING; +/** Hide the remaining-characters counter until the draft nears the limit. */ +const PROMPT_COUNTER_VISIBLE_REMAINING = 1000; +/** Minimum pressable size: 44pt on iOS, 48dp on Android (WCAG 2.5.8 AA). */ +const PROMPT_HIT_TARGET = Platform.OS === 'android' ? 48 : 44; -const promptInputStyle = { - includeFontPadding: false, - lineHeight: PROMPT_INPUT_LINE_HEIGHT, - textAlignVertical: 'top', -} satisfies TextStyle; +type NewSessionPromptComponentProps = NewSessionPromptProps & { + /** + * Return (when the Return-sends preference is on) triggers the host's Start + * flow. Omitted by hosts that have not wired the preference, so Return is a + * no-op there rather than starting on its own. + */ + onStartSession?: () => void; +}; /** * New-session prompt surface: attachment strip, full-width multiline text @@ -96,10 +117,17 @@ export function NewSessionPrompt({ shareId, voiceInputSettlerRef, initialPrompt, + onStartSession, isCloneEntry = false, -}: Readonly) { +}: Readonly) { const colors = useThemeColors(); const { t } = useTranslation(); + const { height: windowHeight, fontScale } = useWindowDimensions(); + const insets = useSafeAreaInsets(); + const { returnSendsMessage } = useReturnSendsMessagePreference(); + const [keyboardHeight, setKeyboardHeight] = useState(0); + const [hasPromptText, setHasPromptText] = useState((initialPrompt ?? '').trim().length > 0); + const [promptCharacterCount, setPromptCharacterCount] = useState(initialPrompt?.length ?? 0); const promptRef = useRef(initialPrompt ?? ''); const initialPromptRef = useRef(initialPrompt ?? ''); const promptInputRef = useRef(null); @@ -119,13 +147,50 @@ export function NewSessionPrompt({ const isComposingRef = useRef(false); const abortVoiceInputRef = useRef<(() => Promise) | null>(null); const [promptInputWidth, setPromptInputWidth] = useState(0); + const promptLineHeight = PROMPT_INPUT_LINE_HEIGHT * fontScale; + const promptMinHeight = + promptLineHeight * PROMPT_INPUT_DEFAULT_LINES + PROMPT_INPUT_VERTICAL_PADDING; + const promptMaxHeight = resolveComposerMaxHeight({ + windowHeight, + safeAreaInsetTop: insets.top, + safeAreaInsetBottom: insets.bottom, + keyboardHeight, + sessionHeaderHeight: SESSION_HEADER_HEIGHT * fontScale, + composerChromeHeight: NEW_SESSION_PROMPT_CHROME_HEIGHT * fontScale, + minHeight: promptMinHeight, + }); + + // Track the keyboard's reported height so the remaining-space cap follows it. + useEffect(() => { + const showEvent = Platform.OS === 'ios' ? 'keyboardWillShow' : 'keyboardDidShow'; + const hideEvent = Platform.OS === 'ios' ? 'keyboardWillHide' : 'keyboardDidHide'; + const show = Keyboard.addListener(showEvent, event => { + setKeyboardHeight(Math.max(event.endCoordinates.height, 0)); + }); + const hide = Keyboard.addListener(hideEvent, () => { + setKeyboardHeight(0); + }); + return () => { + show.remove(); + hide.remove(); + }; + }, []); + + const promptInputStyle = { + includeFontPadding: false, + fontSize: PROMPT_INPUT_FONT_SIZE * fontScale, + lineHeight: promptLineHeight, + textAlignVertical: 'top', + } satisfies TextStyle; + const promptMeasure = useTextHeight({ - minHeight: PROMPT_INPUT_MIN_HEIGHT, - maxHeight: PROMPT_INPUT_MAX_HEIGHT, + minHeight: promptMinHeight, + maxHeight: promptMaxHeight, verticalPadding: PROMPT_INPUT_VERTICAL_PADDING, textContentWidth: promptInputWidth - PROMPT_INPUT_HORIZONTAL_PADDING, - fontSize: 16, + fontSize: PROMPT_INPUT_FONT_SIZE, lineHeight: PROMPT_INPUT_LINE_HEIGHT, + fontScale, }); const promptMeasureSetTextRef = useRef(promptMeasure.setText); @@ -137,6 +202,8 @@ export function NewSessionPrompt({ (text: string) => { promptRef.current = text; promptMeasure.setText(text); + setHasPromptText(text.trim().length > 0); + setPromptCharacterCount(text.length); onChangeText(text); }, [onChangeText, promptMeasure] @@ -237,6 +304,56 @@ export function NewSessionPrompt({ promptSelectionRef.current = event.nativeEvent.selection; } + // Starter chips insert text and focus the input; they never start a session. + const starterChips = [ + t('agentChat.composer.starterChipBuild'), + t('agentChat.composer.starterChipFix'), + t('agentChat.composer.starterChipWriteTests'), + ]; + const showStarters = !hasPromptText && !isCreating; + + function applyStarter(chip: string) { + promptInputRef.current?.setNativeProps({ + text: chip, + selection: { start: chip.length, end: chip.length }, + }); + promptInputRef.current?.focus(); + handlePromptChange(chip); + } + + function handleInsertNewline() { + if (!control.inputEditable) { + return; + } + promptSelectionRef.current = pasteTextIntoComposer('\n', { + input: promptInputRef.current, + draft: promptRef.current, + selection: promptSelectionRef.current, + maxLength: PROMPT_INPUT_MAX_CHARS, + onChangeText: handlePromptChange, + }); + } + + // Return (with the Return-sends preference on) starts the session with the + // same gates as the Start button: a non-empty prompt, an idle form, and no + // failed or in-flight attachments. A blocked start shows the same error the + // composer shows for a failed attachment. Voice settlement and the host's + // model/repository/profile gates run inside `onStartSession` itself. + function handleReturnSubmit() { + if (control.createDisabled || promptRef.current.trim().length === 0) { + return; + } + if (hasAnyFailedAttachment(attachments)) { + toast.error(t('agentChat.composer.removeOrRetryFailed')); + return; + } + if (isAnyAttachmentUploading(attachments)) { + toast.error(t('agentChat.composer.waitForUploads')); + return; + } + onStartSession?.(); + } + // Clone entry has no composer: render the models error or the toolbar as a // standalone block, never an empty rounded composer card. The input, the // attachment strip, the paperclip, the paste button, and voice are unmounted. @@ -269,6 +386,13 @@ export function NewSessionPrompt({ onMove={onMoveAttachment} onReorder={onReorderAttachments} /> + {attachments.some(attachment => attachment.metadataStripFailed === true) ? ( + + ) : null} {promptMeasure.measureElement} = PROMPT_INPUT_MAX_HEIGHT} + scrollEnabled={promptMeasure.height >= promptMaxHeight} editable={control.inputEditable} maxLength={PROMPT_INPUT_MAX_CHARS} accessibilityState={{ disabled: control.inputAccessibilityDisabled }} + returnKeyType={returnSendsMessage ? 'send' : 'default'} + submitBehavior={returnSendsMessage ? 'submit' : 'newline'} + onSubmitEditing={returnSendsMessage ? handleReturnSubmit : undefined} + maxFontSizeMultiplier={1} // A shared payload prefills this input, so raising the keyboard on // arrival hides the attachment strip and the Start button. autoFocus={shareId === undefined || shareId === ''} /> + {showStarters ? ( + + {starterChips.map(chip => ( + { + applyStarter(chip); + }} + accessibilityRole="button" + accessibilityLabel={chip} + hitSlop={{ top: 8, bottom: 8 }} + className="items-center justify-center rounded-full border border-border bg-card px-3 py-1.5 active:opacity-70" + > + {chip} + + ))} + + ) : null} + {PROMPT_INPUT_MAX_CHARS - promptCharacterCount <= PROMPT_COUNTER_VISIBLE_REMAINING ? ( + + + {PROMPT_INPUT_MAX_CHARS - promptCharacterCount} + + + ) : null} + > + {returnSendsMessage ? ( + + + + + + ) : null} + {isModelsError && modelOptions.length === 0 ? ( (() => { + if (cloudStatus?.type === 'preparing') { + return 'preparing'; + } + if (cloudStatus?.type === 'finalizing') { + return 'finalizing'; + } + if (messages.length === 0) { + return 'empty'; + } + return 'message'; + }, [cloudStatus, messages.length]); const keyboardContainerKind = getSessionKeyboardContainerKind(Platform.OS); const handleSendCommand = useCallback( @@ -1517,6 +1529,7 @@ export function SessionDetailContent({ draftKey={userId ? sessionComposerDraftKey : undefined} initialDraft={composerDraft.settled ? (composerDraft.value ?? '') : undefined} sessionId={sessionId} + sessionState={composerSessionState} controlRef={composerControlRef} /> diff --git a/apps/mobile/src/components/agents/session-message-list.mounted.test.tsx b/apps/mobile/src/components/agents/session-message-list.mounted.test.tsx index 857c4d6583..8e2faa9367 100644 --- a/apps/mobile/src/components/agents/session-message-list.mounted.test.tsx +++ b/apps/mobile/src/components/agents/session-message-list.mounted.test.tsx @@ -15,6 +15,8 @@ vi.mock('@shopify/flash-list', () => ({ })); vi.mock('react-native', () => ({ AccessibilityInfo: { announceForAccessibility: vi.fn() }, + Keyboard: { addListener: vi.fn(() => ({ remove: vi.fn() })) }, + Platform: { OS: 'ios' }, Pressable: 'Pressable', View: 'View', })); @@ -33,6 +35,7 @@ vi.mock('@/components/agents/use-session-list-auto-scroll', () => ({ listRef: { current: null }, scrollToLatestAnimated: vi.fn(), handleContentSizeChange: vi.fn(), + handleKeyboardShow: vi.fn(), handleListLayout: vi.fn(), handleScroll: vi.fn(), handleScrollBeginDrag: vi.fn(), diff --git a/apps/mobile/src/components/agents/session-message-list.tsx b/apps/mobile/src/components/agents/session-message-list.tsx index cbb4abc8f7..07231cbe86 100644 --- a/apps/mobile/src/components/agents/session-message-list.tsx +++ b/apps/mobile/src/components/agents/session-message-list.tsx @@ -2,7 +2,14 @@ import { FlashList, type ListRenderItem } from '@shopify/flash-list'; import { type OlderMessagesError } from '@kilocode/cloud-agent-sdk'; import { ChevronDown } from '@/components/ui/icons'; import { useCallback, useEffect, useMemo, useRef } from 'react'; -import { AccessibilityInfo, Pressable, View, type ViewStyle } from 'react-native'; +import { + AccessibilityInfo, + Keyboard, + Platform, + Pressable, + View, + type ViewStyle, +} from 'react-native'; import { useTranslation } from 'react-i18next'; import Animated, { FadeIn, FadeOut } from 'react-native-reanimated'; @@ -76,6 +83,7 @@ export function SessionMessageList({ listRef, scrollToLatestAnimated, handleContentSizeChange, + handleKeyboardShow, handleListLayout, handleScroll, handleScrollBeginDrag, @@ -123,6 +131,27 @@ export function SessionMessageList({ inFlightRef.current = false; }, [sessionId]); + // Keep the newest message visible when the keyboard opens, but only while + // the follow guard is true (the user is still at the bottom). On iOS, + // `keyboardWillShow` fires before the animation and `keyboardDidShow` fires + // after it: the first starts the scroll immediately and the second lands it + // once the viewport has actually shrunk. Android has no will-show event, so + // `keyboardDidShow` is the only show signal there. + useEffect(() => { + if (Platform.OS === 'ios') { + const willShow = Keyboard.addListener('keyboardWillShow', handleKeyboardShow); + const didShow = Keyboard.addListener('keyboardDidShow', handleKeyboardShow); + return () => { + willShow.remove(); + didShow.remove(); + }; + } + const didShow = Keyboard.addListener('keyboardDidShow', handleKeyboardShow); + return () => { + didShow.remove(); + }; + }, [handleKeyboardShow]); + // Fire `onReachedBottom` only on the false→true transition of // `isAtBottom`. The previous-value ref prevents a fire on mount (the list // starts at the bottom) and on repeat renders while already at the bottom. diff --git a/apps/mobile/src/components/agents/use-session-list-auto-scroll.ts b/apps/mobile/src/components/agents/use-session-list-auto-scroll.ts index 5f4b62ae8e..03c3dfe73a 100644 --- a/apps/mobile/src/components/agents/use-session-list-auto-scroll.ts +++ b/apps/mobile/src/components/agents/use-session-list-auto-scroll.ts @@ -252,11 +252,20 @@ export function useSessionListAutoScroll({ } }, [scheduleScrollToLatestMessage]); + const handleKeyboardShow = useCallback(() => { + // Reuse the guarded scheduler so a keyboard opening never yanks the list + // back to the bottom when the user has scrolled away. The guard inside + // `scheduleScrollToLatestMessage` honours the follow, user-scroll, and + // in-flight programmatic-scroll refs. + scheduleScrollToLatestMessage(); + }, [scheduleScrollToLatestMessage]); + return { isAtBottom, listRef, scrollToLatestAnimated, handleContentSizeChange, + handleKeyboardShow, handleListLayout, handleScroll, handleScrollBeginDrag, diff --git a/apps/mobile/src/components/agents/use-text-height.tsx b/apps/mobile/src/components/agents/use-text-height.tsx index 790d6ddcad..d04772a610 100644 --- a/apps/mobile/src/components/agents/use-text-height.tsx +++ b/apps/mobile/src/components/agents/use-text-height.tsx @@ -1,4 +1,4 @@ -import { useState } from 'react'; +import { useEffect, useState } from 'react'; import { type LayoutChangeEvent, Text, type TextStyle, View, type ViewStyle } from 'react-native'; type UseTextHeightOptions = { @@ -8,6 +8,13 @@ type UseTextHeightOptions = { textContentWidth: number; fontSize: number; lineHeight: number; + /** + * System Dynamic Type scale. When set, the caller pre-scales `fontSize` and + * `lineHeight` here and native scaling is disabled on the measure node, so + * the scale applies exactly once. When omitted, the base sizes are rendered + * and native scaling applies (the kilo-chat message input path). + */ + fontScale?: number; initialText?: string; }; @@ -22,43 +29,63 @@ export function useTextHeight({ textContentWidth, fontSize, lineHeight, + fontScale, initialText = '', }: UseTextHeightOptions) { const [text, setMeasuredText] = useState(initialText); + // The padded, unclamped mirror height, measured by the hidden Text node. + const [contentHeight, setContentHeight] = useState(minHeight); + // The clamped height published to the caller, re-clamped whenever the + // content, the minimum, or the remaining-space cap changes. const [height, setHeight] = useState(minHeight); const measuredText = text.length === 0 || text.endsWith('\n') ? `${text} ` : text; const measurementWidth = Math.max(textContentWidth, 0); + // Pre-scale only when the caller opted in (the agent composers). The + // kilo-chat message input omits `fontScale` and relies on native scaling. + const scaledFontSize = fontScale == null ? fontSize : fontSize * fontScale; + const scaledLineHeight = fontScale == null ? lineHeight : lineHeight * fontScale; + // Disable native scaling only when the caller pre-scaled, so the scale is + // applied exactly once instead of twice. + const maxFontSizeMultiplier = fontScale == null ? undefined : 1; function handleMeasureLayout(event: LayoutChangeEvent) { const textHeight = event.nativeEvent.layout.height; const paddedHeight = Math.ceil(textHeight + verticalPadding); - const nextHeight = Math.min(Math.max(paddedHeight, minHeight), maxHeight); - setHeight(current => (current === nextHeight ? current : nextHeight)); + setContentHeight(paddedHeight); } + useEffect(() => { + const nextHeight = Math.min(Math.max(contentHeight, minHeight), maxHeight); + setHeight(current => (current === nextHeight ? current : nextHeight)); + }, [contentHeight, minHeight, maxHeight]); + function setText(nextText: string) { setMeasuredText(nextText); if (nextText.length === 0) { - setHeight(minHeight); + setContentHeight(minHeight); } } function reset() { setMeasuredText(''); - setHeight(minHeight); + setContentHeight(minHeight); } const textStyle: TextStyle = { - fontSize, + fontSize: scaledFontSize, includeFontPadding: false, - lineHeight, + lineHeight: scaledLineHeight, width: measurementWidth, }; const measureElement = measurementWidth > 0 ? ( - + {measuredText} diff --git a/apps/mobile/src/components/preferences-screen.mounted.test.tsx b/apps/mobile/src/components/preferences-screen.mounted.test.tsx index c675dbcf6a..db07dad637 100644 --- a/apps/mobile/src/components/preferences-screen.mounted.test.tsx +++ b/apps/mobile/src/components/preferences-screen.mounted.test.tsx @@ -19,6 +19,7 @@ vi.mock('expo-router', () => ({ vi.mock('@/components/ui/icons', () => ({ Bell: 'Bell', Brain: 'Brain', + CornerDownLeft: 'CornerDownLeft', Globe: 'Globe', MessageSquare: 'MessageSquare', Shield: 'Shield', @@ -70,6 +71,13 @@ vi.mock('@/lib/hooks/use-theme-preference', () => ({ setThemePreference: vi.fn(), useThemePreference: () => ({ preference: 'system' }), })); +vi.mock('@/lib/hooks/use-return-sends-message-preference', () => ({ + useReturnSendsMessagePreference: () => ({ + returnSendsMessage: false, + hasLoaded: true, + setReturnSendsMessage: vi.fn(), + }), +})); vi.mock('@/lib/hooks/use-theme-colors', () => ({ useThemeColors: () => ({ secondaryForeground: '#000000', mutedForeground: '#000000' }), })); @@ -132,3 +140,32 @@ describe('PreferencesScreen account rows', () => { renderer.unmount(); }); }); + +describe('PreferencesScreen Return-sends switch', () => { + it('renders the Return-sends switch off by default with its title and subtitle', async () => { + const renderer = await mountPreferences(); + + const switches = renderer.root.findAll( + node => typeof node.type === 'string' && (node.type as string) === 'Switch' + ); + const returnSends = switches.find( + sw => sw.props.accessibilityLabel === 'Return key sends message' + ); + + expect(returnSends).toBeDefined(); + expect(returnSends?.props.value).toBe(false); + + const texts = renderer.root.findAll( + node => + typeof node.type === 'string' && + (node.type as string) === 'Text' && + typeof node.props.children === 'string' + ); + expect(texts.some(t => t.props.children === 'Return key sends message')).toBe(true); + expect( + texts.some(t => t.props.children === 'When off, Return inserts a newline in agent composers.') + ).toBe(true); + + renderer.unmount(); + }); +}); diff --git a/apps/mobile/src/components/preferences-screen.tsx b/apps/mobile/src/components/preferences-screen.tsx index 00a1b42e9b..486ddd7e39 100644 --- a/apps/mobile/src/components/preferences-screen.tsx +++ b/apps/mobile/src/components/preferences-screen.tsx @@ -2,6 +2,7 @@ import { type Href, useRouter } from 'expo-router'; import { Bell, Brain, + CornerDownLeft, Globe, type LucideIcon, MessageSquare, @@ -22,6 +23,7 @@ import { getResolvedLanguage, useLanguagePreference } from '@/lib/hooks/use-lang import { useKeepScreenOnPreference } from '@/lib/hooks/use-keep-screen-on-preference'; import { usePrReviewFooterPreference } from '@/lib/hooks/use-pr-review-footer-preference'; import { useReasoningPreference } from '@/lib/hooks/use-reasoning-preference'; +import { useReturnSendsMessagePreference } from '@/lib/hooks/use-return-sends-message-preference'; import { useTrustedHosts } from '@/lib/hooks/use-trusted-hosts'; import { cn } from '@/lib/utils'; import { LANGUAGE_ENDONYMS } from '@/i18n/languages'; @@ -94,6 +96,8 @@ export function PreferencesScreen() { hasLoaded: prReviewFooterLoaded, setPrReviewFooter, } = usePrReviewFooterPreference(); + const { returnSendsMessage, hasLoaded, setReturnSendsMessage } = + useReturnSendsMessagePreference(); const { t } = useTranslation(); const { userId } = useCurrentUserId(); const { preference: languagePreference } = useLanguagePreference(); @@ -136,6 +140,14 @@ export function PreferencesScreen() { disabled={!prReviewFooterLoaded} onValueChange={setPrReviewFooter} /> + {/* Appearance */} diff --git a/apps/mobile/src/components/ui/icons.ts b/apps/mobile/src/components/ui/icons.ts index 4773877622..8d2ccbc41e 100644 --- a/apps/mobile/src/components/ui/icons.ts +++ b/apps/mobile/src/components/ui/icons.ts @@ -37,6 +37,7 @@ export { default as Clock3 } from 'lucide-react-native/icons/clock-3'; export { default as Cloud } from 'lucide-react-native/icons/cloud'; export { default as Code } from 'lucide-react-native/icons/code'; export { default as Columns2 } from 'lucide-react-native/icons/columns-2'; +export { default as CornerDownLeft } from 'lucide-react-native/icons/corner-down-left'; export { default as Cpu } from 'lucide-react-native/icons/cpu'; export { default as CreditCard } from 'lucide-react-native/icons/credit-card'; export { default as Crown } from 'lucide-react-native/icons/crown'; diff --git a/apps/mobile/src/i18n/locales/af.json b/apps/mobile/src/i18n/locales/af.json index 30dc538a8c..11b6d9c647 100644 --- a/apps/mobile/src/i18n/locales/af.json +++ b/apps/mobile/src/i18n/locales/af.json @@ -152,7 +152,9 @@ "appearanceDark": "Donker", "account": "Rekening", "prReviewAttribution": "Voeg app-toeskrywing by PR-hersienings toe", - "prReviewAttributionSubtitle": "Voeg 'n \"Reviewed via Kilo\"-voetskrif by wanneer jy 'n hersiening indien" + "prReviewAttributionSubtitle": "Voeg 'n \"Reviewed via Kilo\"-voetskrif by wanneer jy 'n hersiening indien", + "returnSendsMessage": "Terugkeersleutel stuur boodskap", + "returnSendsMessageSubtitle": "Wanneer dit af is, voeg die terugkeersleutel 'n nuwe reël in agente se boodskapinvoer in." }, "prReview": { "pullRequestUnavailable": "Trekversoek onbeskikbaar", @@ -2589,7 +2591,15 @@ "cannotReceiveFiles": "Hierdie sessie kan nie lêers ontvang nie. Verwyder die aanhegsels om jou boodskap te stuur.", "couldNotAttachFiles": "Kon nie lêers aanheg nie. Tik stuur om weer te probeer.", "discardAttachmentsMessage": "Opgelaaide lêers wat nie aan gestuurde boodskappe geheg is nie, sal verwyder word.", - "discardAttachmentsTitle": "Aanhegsels weggooi?" + "discardAttachmentsTitle": "Aanhegsels weggooi?", + "charactersRemaining": "{{count}} karakters oor", + "insertNewline": "Voeg nuwe reël in", + "starterChipBuild": "Bou 'n funksie", + "starterChipEmpty": "Begin met 'n idee", + "starterChipFix": "Maak 'n fout reg", + "starterChipPrepare": "Stel my omgewing op", + "starterChipWrapUp": "Voltooi my sessie", + "starterChipWriteTests": "Skryf toetse" }, "chatLink": { "linkCopied": "Skakel gekopieer", diff --git a/apps/mobile/src/i18n/locales/am.json b/apps/mobile/src/i18n/locales/am.json index 6d271016b0..7fb4829f04 100644 --- a/apps/mobile/src/i18n/locales/am.json +++ b/apps/mobile/src/i18n/locales/am.json @@ -152,7 +152,9 @@ "appearanceDark": "ጨለማ", "account": "መለያ", "prReviewAttribution": "የመተግበሪያ መለያን ወደ PR ግምገማዎች ያክሉ", - "prReviewAttributionSubtitle": "ግምገማ ሲያስገቡ የ'በKilo ተገምግሟል' ግርጌ ያክሉ።" + "prReviewAttributionSubtitle": "ግምገማ ሲያስገቡ የ'በKilo ተገምግሟል' ግርጌ ያክሉ።", + "returnSendsMessage": "የመመለሻ ቁልፍ መልእክት ይልካል", + "returnSendsMessageSubtitle": "ሲጠፋ፣ የመመለሻ ቁልፍ በወኪል መልእክት ማስገቢያዎች ውስጥ አዲስ መስመር ያስገባል።" }, "prReview": { "pullRequestUnavailable": "የጎተራ ጥያቄ የለም", @@ -2589,7 +2591,15 @@ "cannotReceiveFiles": "ይህ ክፍለ-ጊዜ ፋይሎች መቀበል አይችልም። መልእክትዎን ለመላክ አባሪዎቹን ያስወግዱ።", "couldNotAttachFiles": "ፋይሎችን ማያያዝ አልተቻለም። እንደገና ለመሞከር ላክ ይንኩ።", "discardAttachmentsMessage": "ከተላኩ መልእክቶች ጋር ያልተያያዙ የተጫኑ ፋይሎች ይሰረዛሉ።", - "discardAttachmentsTitle": "አባሪዎችን ያስወግዱ?" + "discardAttachmentsTitle": "አባሪዎችን ያስወግዱ?", + "charactersRemaining": "{{count}} ቁምፊዎች ይቀራሉ", + "insertNewline": "አዲስ መስመር ያስገቡ", + "starterChipBuild": "ባህሪ ይገንቡ", + "starterChipEmpty": "በሀሳብ ይጀምሩ", + "starterChipFix": "ስህተት ያስተካክሉ", + "starterChipPrepare": "የስራ አካባቢዬን ያዋቅሩ", + "starterChipWrapUp": "ክፍለ ጊዜዬን ያጠናቅቁ", + "starterChipWriteTests": "ሙከራዎችን ይፃፉ" }, "chatLink": { "linkCopied": "አገናኝ ተቀድቷል", diff --git a/apps/mobile/src/i18n/locales/ar.json b/apps/mobile/src/i18n/locales/ar.json index 80bdc7e17b..a36d8f3f31 100644 --- a/apps/mobile/src/i18n/locales/ar.json +++ b/apps/mobile/src/i18n/locales/ar.json @@ -152,7 +152,9 @@ "appearanceDark": "داكن", "account": "الحساب", "prReviewAttribution": "أضِف إسناد التطبيق إلى مراجعات PR", - "prReviewAttributionSubtitle": "ألحق تذييل \"تمت المراجعة عبر Kilo\" عند إرسال مراجعة." + "prReviewAttributionSubtitle": "ألحق تذييل \"تمت المراجعة عبر Kilo\" عند إرسال مراجعة.", + "returnSendsMessage": "مفتاح الإدخال يرسل الرسالة", + "returnSendsMessageSubtitle": "عند إيقاف هذا الخيار، يُدرج مفتاح الإدخال سطرًا جديدًا في حقول إدخال رسائل الوكلاء." }, "notifications": { "title": "الإشعارات", @@ -2183,7 +2185,15 @@ "cannotReceiveFiles": "لا يمكن لهذه الجلسة استقبال الملفات. أزل المرفقات لإرسال رسالتك.", "couldNotAttachFiles": "تعذّر إرفاق الملفات. اضغط على إرسال للمحاولة مرة أخرى.", "discardAttachmentsMessage": "سيتم حذف الملفات المرفوعة غير المرفقة برسائل مرسلة.", - "discardAttachmentsTitle": "تجاهل المرفقات؟" + "discardAttachmentsTitle": "تجاهل المرفقات؟", + "charactersRemaining": "الأحرف المتبقية: {{count}}", + "insertNewline": "إدراج سطر جديد", + "starterChipBuild": "إنشاء ميزة", + "starterChipEmpty": "البدء بفكرة", + "starterChipFix": "إصلاح خلل", + "starterChipPrepare": "إعداد بيئتي", + "starterChipWrapUp": "إنهاء جلستي", + "starterChipWriteTests": "كتابة اختبارات" }, "chatLink": { "linkCopied": "تم نسخ الرابط", diff --git a/apps/mobile/src/i18n/locales/az.json b/apps/mobile/src/i18n/locales/az.json index ab8a2dbbe6..3dbc150a91 100644 --- a/apps/mobile/src/i18n/locales/az.json +++ b/apps/mobile/src/i18n/locales/az.json @@ -152,7 +152,9 @@ "appearanceDark": "Qaranlıq", "account": "Hesab", "prReviewAttribution": "PR icmallarına proqram atribusiyası əlavə et", - "prReviewAttributionSubtitle": "İcmal təqdim edərkən 'Reviewed via Kilo' altbilgisini əlavə et." + "prReviewAttributionSubtitle": "İcmal təqdim edərkən 'Reviewed via Kilo' altbilgisini əlavə et.", + "returnSendsMessage": "Sətirsonu düyməsi mesaj göndərir", + "returnSendsMessageSubtitle": "Söndürüldükdə sətirsonu düyməsi agent kompozitorlarında yeni sətir daxil edir." }, "prReview": { "pullRequestUnavailable": "Pull request əlçatan deyil", @@ -2589,7 +2591,15 @@ "cannotReceiveFiles": "Bu sessiya fayl qəbul edə bilmir. Mesajınızı göndərmək üçün əlavələri silin.", "couldNotAttachFiles": "Fayllar əlavə edilə bilmədi. Yenidən cəhd etmək üçün göndər düyməsinə toxunun.", "discardAttachmentsMessage": "Göndərilmiş mesajlara əlavə edilməmiş yükləmələr silinəcək.", - "discardAttachmentsTitle": "Əlavələr ləğv edilsin?" + "discardAttachmentsTitle": "Əlavələr ləğv edilsin?", + "charactersRemaining": "{{count}} simvol qalıb", + "insertNewline": "Yeni sətir daxil edin", + "starterChipBuild": "Funksiya yaradın", + "starterChipEmpty": "Bir ideya ilə başlayın", + "starterChipFix": "Səhvi düzəldin", + "starterChipPrepare": "Mühitimi qurun", + "starterChipWrapUp": "Sessiyamı tamamlayın", + "starterChipWriteTests": "Testlər yazın" }, "chatLink": { "linkCopied": "Keçid kopyalandı", diff --git a/apps/mobile/src/i18n/locales/be.json b/apps/mobile/src/i18n/locales/be.json index ca56905734..2ee209a87a 100644 --- a/apps/mobile/src/i18n/locales/be.json +++ b/apps/mobile/src/i18n/locales/be.json @@ -152,7 +152,9 @@ "appearanceDark": "Цёмная", "account": "Уліковы запіс", "prReviewAttribution": "Дадаваць атрыбуцыю дадатку да рэцэнзій PR", - "prReviewAttributionSubtitle": "Дадаваць футэр «Reviewed via Kilo» пры адпраўцы рэцэнзіі." + "prReviewAttributionSubtitle": "Дадаваць футэр «Reviewed via Kilo» пры адпраўцы рэцэнзіі.", + "returnSendsMessage": "Клавіша ўводу адпраўляе паведамленне", + "returnSendsMessageSubtitle": "Калі выключана, клавіша ўводу ўстаўляе новы радок у кампазітарах агентаў." }, "prReview": { "pullRequestUnavailable": "Pull request недаступны", @@ -2627,7 +2629,15 @@ "cannotReceiveFiles": "Гэты сеанс не можа атрымліваць файлы. Прыбярыце ўкладанні, каб адправіць паведамленне.", "couldNotAttachFiles": "Не ўдалося прымацаваць файлы. Націсніце «Адправіць», каб паспрабаваць яшчэ раз.", "discardAttachmentsMessage": "Загружаныя файлы, не прымацаваныя да адпраўленых паведамленняў, будуць выдаленыя.", - "discardAttachmentsTitle": "Выдаліць укладанні?" + "discardAttachmentsTitle": "Выдаліць укладанні?", + "charactersRemaining": "Засталося сімвалаў: {{count}}", + "insertNewline": "Уставіць новы радок", + "starterChipBuild": "Стварыць функцыю", + "starterChipEmpty": "Пачаць з ідэі", + "starterChipFix": "Выправіць памылку", + "starterChipPrepare": "Наладзіць маё асяроддзе", + "starterChipWrapUp": "Завяршыць мой сеанс", + "starterChipWriteTests": "Напісаць тэсты" }, "chatLink": { "linkCopied": "Спасылка скапіраваная", diff --git a/apps/mobile/src/i18n/locales/bg.json b/apps/mobile/src/i18n/locales/bg.json index 1a408c359a..effcefc11d 100644 --- a/apps/mobile/src/i18n/locales/bg.json +++ b/apps/mobile/src/i18n/locales/bg.json @@ -152,7 +152,9 @@ "appearanceDark": "Тъмен", "account": "Акаунт", "prReviewAttribution": "Добави приписване на приложението към прегледите на PR", - "prReviewAttributionSubtitle": "Добави футър „Reviewed via Kilo“, когато изпратиш преглед." + "prReviewAttributionSubtitle": "Добави футър „Reviewed via Kilo“, когато изпратиш преглед.", + "returnSendsMessage": "Клавишът за нов ред изпраща съобщение", + "returnSendsMessageSubtitle": "Когато настройката е изключена, клавишът за нов ред вмъква нов ред в полетата за въвеждане на съобщения до агенти." }, "prReview": { "pullRequestUnavailable": "Pull request-ът е недостъпен", @@ -2589,7 +2591,15 @@ "cannotReceiveFiles": "Тази сесия не може да получава файлове. Премахнете прикачените файлове, за да изпратите съобщението си.", "couldNotAttachFiles": "Не можаха да се прикачат файлове. Докоснете изпращане, за да опитате отново.", "discardAttachmentsMessage": "Качените файлове, които не са прикачени към изпратени съобщения, ще бъдат изтрити.", - "discardAttachmentsTitle": "Да отхвърлим прикачените файлове?" + "discardAttachmentsTitle": "Да отхвърлим прикачените файлове?", + "charactersRemaining": "Остават {{count}} знака", + "insertNewline": "Вмъкнете нов ред", + "starterChipBuild": "Разработете функция", + "starterChipEmpty": "Започнете с идея", + "starterChipFix": "Отстранете грешка", + "starterChipPrepare": "Настройте моята среда", + "starterChipWrapUp": "Приключете моята сесия", + "starterChipWriteTests": "Напишете тестове" }, "chatLink": { "linkCopied": "Линкът е копиран", diff --git a/apps/mobile/src/i18n/locales/bn.json b/apps/mobile/src/i18n/locales/bn.json index 90df9e74f2..7a0d187f0e 100644 --- a/apps/mobile/src/i18n/locales/bn.json +++ b/apps/mobile/src/i18n/locales/bn.json @@ -152,7 +152,9 @@ "appearanceDark": "ডার্ক", "account": "অ্যাকাউন্ট", "prReviewAttribution": "PR পর্যালোচনায় অ্যাপ অ্যাট্রিবিউশন যোগ করুন", - "prReviewAttributionSubtitle": "আপনি একটি পর্যালোচনা জমা দিলে একটি Reviewed via Kilo ফুটার যুক্ত করুন।" + "prReviewAttributionSubtitle": "আপনি একটি পর্যালোচনা জমা দিলে একটি Reviewed via Kilo ফুটার যুক্ত করুন।", + "returnSendsMessage": "রিটার্ন কী বার্তা পাঠায়", + "returnSendsMessageSubtitle": "বন্ধ থাকলে, রিটার্ন কী এজেন্টের বার্তা লেখার ঘরে নতুন লাইন যোগ করে।" }, "prReview": { "pullRequestUnavailable": "পুল রিকোয়েস্ট অনুপলব্ধ", @@ -2589,7 +2591,15 @@ "cannotReceiveFiles": "এই সেশনটি ফাইল গ্রহণ করতে পারে না। আপনার বার্তা পাঠাতে সংযুক্তিগুলি সরান।", "couldNotAttachFiles": "ফাইল সংযুক্ত করা যায়নি। আবার চেষ্টা করতে পাঠান ট্যাপ করুন।", "discardAttachmentsMessage": "পাঠানো বার্তায় সংযুক্ত নয় এমন আপলোড করা ফাইলগুলি মুছে ফেলা হবে।", - "discardAttachmentsTitle": "সংযুক্তিগুলি বাতিল করবেন?" + "discardAttachmentsTitle": "সংযুক্তিগুলি বাতিল করবেন?", + "charactersRemaining": "{{count}}টি অক্ষর বাকি", + "insertNewline": "নতুন লাইন যোগ করুন", + "starterChipBuild": "একটি ফিচার তৈরি করুন", + "starterChipEmpty": "একটি ধারণা দিয়ে শুরু করুন", + "starterChipFix": "একটি ত্রুটি ঠিক করুন", + "starterChipPrepare": "আমার পরিবেশ প্রস্তুত করুন", + "starterChipWrapUp": "আমার সেশন শেষ করুন", + "starterChipWriteTests": "পরীক্ষা লিখুন" }, "chatLink": { "linkCopied": "লিঙ্ক কপি হয়েছে", diff --git a/apps/mobile/src/i18n/locales/bs.json b/apps/mobile/src/i18n/locales/bs.json index 4436b4da6e..28f92cd91e 100644 --- a/apps/mobile/src/i18n/locales/bs.json +++ b/apps/mobile/src/i18n/locales/bs.json @@ -152,7 +152,9 @@ "appearanceDark": "Tamno", "account": "Nalog", "prReviewAttribution": "Dodajte atribuciju aplikacije PR pregledima.", - "prReviewAttributionSubtitle": "Dodajte podnožje \"Pregledano putem Kilo-a\" kada pošaljete pregled." + "prReviewAttributionSubtitle": "Dodajte podnožje \"Pregledano putem Kilo-a\" kada pošaljete pregled.", + "returnSendsMessage": "Tipka za novi red šalje poruku", + "returnSendsMessageSubtitle": "Kada je isključeno, tipka za novi red umeće novi red u polja za unos poruke agentima." }, "prReview": { "pullRequestUnavailable": "Pull request nedostupan", @@ -2608,7 +2610,15 @@ "cannotReceiveFiles": "Ova sesija ne može primiti datoteke. Uklonite privitke da pošaljete svoju poruku.", "couldNotAttachFiles": "Datoteke nisu mogle biti priložene. Dodirnite pošalji za ponovni pokušaj.", "discardAttachmentsMessage": "Otpremljene datoteke koje nisu priložene poslanim porukama bit će obrisane.", - "discardAttachmentsTitle": "Odbaciti privitke?" + "discardAttachmentsTitle": "Odbaciti privitke?", + "charactersRemaining": "Preostalo znakova: {{count}}", + "insertNewline": "Umetnite novi red", + "starterChipBuild": "Izradite funkciju", + "starterChipEmpty": "Počnite s idejom", + "starterChipFix": "Ispravite grešku", + "starterChipPrepare": "Postavite moje okruženje", + "starterChipWrapUp": "Završite moju sesiju", + "starterChipWriteTests": "Napišite testove" }, "chatLink": { "linkCopied": "Veza kopirana", diff --git a/apps/mobile/src/i18n/locales/ca.json b/apps/mobile/src/i18n/locales/ca.json index bfc623adff..62bff22b16 100644 --- a/apps/mobile/src/i18n/locales/ca.json +++ b/apps/mobile/src/i18n/locales/ca.json @@ -152,7 +152,9 @@ "appearanceDark": "Fosc", "account": "Compte", "prReviewAttribution": "Afegiu l'atribució de l'aplicació a les revisions de PR", - "prReviewAttributionSubtitle": "Afegiu un peu de pàgina «Reviewed via Kilo» quan envieu una revisió." + "prReviewAttributionSubtitle": "Afegiu un peu de pàgina «Reviewed via Kilo» quan envieu una revisió.", + "returnSendsMessage": "La tecla de retorn envia el missatge", + "returnSendsMessageSubtitle": "Quan està desactivat, la tecla de retorn insereix un salt de línia als camps de missatge dels agents." }, "prReview": { "pullRequestUnavailable": "Pull request no disponible", @@ -2608,7 +2610,15 @@ "cannotReceiveFiles": "Aquesta sessió no pot rebre fitxers. Treu els fitxers adjunts per enviar el teu missatge.", "couldNotAttachFiles": "No s'han pogut adjuntar els fitxers. Toqueu Envia per tornar-ho a provar.", "discardAttachmentsMessage": "S'eliminaran els fitxers penjats que no s'hagin adjuntat a missatges enviats.", - "discardAttachmentsTitle": "Descartar els adjunts?" + "discardAttachmentsTitle": "Descartar els adjunts?", + "charactersRemaining": "Queden {{count}} caràcters", + "insertNewline": "Insereix un salt de línia", + "starterChipBuild": "Crea una funció", + "starterChipEmpty": "Comença amb una idea", + "starterChipFix": "Corregeix un error", + "starterChipPrepare": "Configura el meu entorn", + "starterChipWrapUp": "Finalitza la meva sessió", + "starterChipWriteTests": "Escriu proves" }, "chatLink": { "linkCopied": "Enllaç copiat", diff --git a/apps/mobile/src/i18n/locales/ckb.json b/apps/mobile/src/i18n/locales/ckb.json index c7fc036660..183c1b51ef 100644 --- a/apps/mobile/src/i18n/locales/ckb.json +++ b/apps/mobile/src/i18n/locales/ckb.json @@ -152,7 +152,9 @@ "appearanceDark": "تاریک", "account": "هەژمار", "prReviewAttribution": "زیادکردنی خاوەندارییەتی ئەپ بۆ پێداچوونەوەکانی PR", - "prReviewAttributionSubtitle": "لە کاتی ناردنی پێداچوونەوەیەک، دەقی «پێداچوونەوە لە ڕێگەی Kilo» لە کۆتاییدا زیاد بکە." + "prReviewAttributionSubtitle": "لە کاتی ناردنی پێداچوونەوەیەک، دەقی «پێداچوونەوە لە ڕێگەی Kilo» لە کۆتاییدا زیاد بکە.", + "returnSendsMessage": "کلیلی گەڕانەوە پەیام دەنێرێت", + "returnSendsMessageSubtitle": "کاتێک ناچالاکە، کلیلی گەڕانەوە دێڕێکی نوێ لە خانەکانی نووسینی پەیامی ئەرکەکان تێدەخات." }, "prReview": { "pullRequestUnavailable": "داواکاری ڕاکێشان بەردەست نییە", @@ -2589,7 +2591,15 @@ "cannotReceiveFiles": "ئەم دانیشتنە ناتوانێت فایل وەربگرێت. پاشگرەکان لاببە بۆ ناردنی نامەکەت.", "couldNotAttachFiles": "نەتوانرا فایلەکان بلکێنرێت. دابکە لەسەر ناردن بۆ دووبارە هەوڵدان.", "discardAttachmentsMessage": "ئەو پەڕگە بارکراوانەی هاوپێچی پەیامی نێردراو نەکراون دەسڕێنەوە.", - "discardAttachmentsTitle": "فڕێدانی هاوپێچەکان؟" + "discardAttachmentsTitle": "فڕێدانی هاوپێچەکان؟", + "charactersRemaining": "{{count}} پیت ماوە", + "insertNewline": "تێخستنی دێڕێکی نوێ", + "starterChipBuild": "دروستکردنی تایبەتمەندییەک", + "starterChipEmpty": "دەستپێکردن بە بیرۆکەیەک", + "starterChipFix": "چاککردنەوەی هەڵەیەک", + "starterChipPrepare": "ڕێکخستنی ژینگەکەم", + "starterChipWrapUp": "تەواوکردنی سێشنەکەم", + "starterChipWriteTests": "نووسینی تاقیکردنەوەکان" }, "chatLink": { "linkCopied": "بەستەرەکە کۆپی کرا", diff --git a/apps/mobile/src/i18n/locales/cs.json b/apps/mobile/src/i18n/locales/cs.json index aec854112e..ed02407c1e 100644 --- a/apps/mobile/src/i18n/locales/cs.json +++ b/apps/mobile/src/i18n/locales/cs.json @@ -152,7 +152,9 @@ "appearanceDark": "Tmavý", "account": "Účet", "prReviewAttribution": "Přidat atribuci aplikace k recenzím PR", - "prReviewAttributionSubtitle": "Při odeslání recenze přidejte zápatí „Reviewed via Kilo“." + "prReviewAttributionSubtitle": "Při odeslání recenze přidejte zápatí „Reviewed via Kilo“.", + "returnSendsMessage": "Klávesa Enter odesílá zprávu", + "returnSendsMessageSubtitle": "Když je tato volba vypnutá, klávesa Enter vloží nový řádek do polí pro zprávu agentům." }, "prReview": { "pullRequestUnavailable": "Pull request není dostupný", @@ -2627,7 +2629,15 @@ "cannotReceiveFiles": "Tato relace nemůže přijímat soubory. Odeberte přílohy, abyste mohli odeslat zprávu.", "couldNotAttachFiles": "Soubory se nepodařilo připojit. Klepnutím na odeslat to zkuste znovu.", "discardAttachmentsMessage": "Nahrané soubory nepřipojené k odeslaným zprávám budou smazány.", - "discardAttachmentsTitle": "Zahodit přílohy?" + "discardAttachmentsTitle": "Zahodit přílohy?", + "charactersRemaining": "Zbývající počet znaků: {{count}}", + "insertNewline": "Vložit nový řádek", + "starterChipBuild": "Vytvořit funkci", + "starterChipEmpty": "Začít nápadem", + "starterChipFix": "Opravit chybu", + "starterChipPrepare": "Nastavit mé prostředí", + "starterChipWrapUp": "Dokončit mou relaci", + "starterChipWriteTests": "Napsat testy" }, "chatLink": { "linkCopied": "Odkaz zkopírován", diff --git a/apps/mobile/src/i18n/locales/cy.json b/apps/mobile/src/i18n/locales/cy.json index d08fe76772..4ec0fde036 100644 --- a/apps/mobile/src/i18n/locales/cy.json +++ b/apps/mobile/src/i18n/locales/cy.json @@ -152,7 +152,9 @@ "appearanceDark": "Tywyll", "account": "Cyfrif", "prReviewAttribution": "Ychwanegu priodoli ap at adolygiadau PR", - "prReviewAttributionSubtitle": "Atodwch droedyn 'Adolygwyd drwy Kilo' pan fyddwch yn cyflwyno adolygiad" + "prReviewAttributionSubtitle": "Atodwch droedyn 'Adolygwyd drwy Kilo' pan fyddwch yn cyflwyno adolygiad", + "returnSendsMessage": "Mae'r fysell Dychwelyd yn anfon neges", + "returnSendsMessageSubtitle": "Pan fydd wedi'i ddiffodd, mae Dychwelyd yn mewnosod llinell newydd ym meysydd mewnbwn neges asiantau." }, "prReview": { "pullRequestUnavailable": "Ni ellir cael y cais tynnu", @@ -2665,7 +2667,15 @@ "cannotReceiveFiles": "Ni all y sesiwn hon dderbyn ffeiliau. Dileuwch yr atodiadau i anfon eich neges.", "couldNotAttachFiles": "Methwyd atodi ffeiliau. Tapiwch anfon i geisio eto.", "discardAttachmentsMessage": "Bydd ffeiliau wedi'u huwchlwytho nad ydynt wedi'u hatodi i negeseuon a anfonwyd yn cael eu dileu.", - "discardAttachmentsTitle": "Taflu atodiadau?" + "discardAttachmentsTitle": "Taflu atodiadau?", + "charactersRemaining": "{{count}} nod yn weddill", + "insertNewline": "Mewnosod llinell newydd", + "starterChipBuild": "Adeiladu nodwedd", + "starterChipEmpty": "Dechrau gyda syniad", + "starterChipFix": "Trwsio nam", + "starterChipPrepare": "Gosod fy amgylchedd", + "starterChipWrapUp": "Gorffen fy sesiwn", + "starterChipWriteTests": "Ysgrifennu profion" }, "chatLink": { "linkCopied": "Copïwyd y ddolen", diff --git a/apps/mobile/src/i18n/locales/da.json b/apps/mobile/src/i18n/locales/da.json index e0e2d62376..ee1b00a0e9 100644 --- a/apps/mobile/src/i18n/locales/da.json +++ b/apps/mobile/src/i18n/locales/da.json @@ -152,7 +152,9 @@ "appearanceDark": "Mørk", "account": "Konto", "prReviewAttribution": "Tilføj app-tilskrivning til PR-anmeldelser", - "prReviewAttributionSubtitle": "Tilføj en \"Reviewed via Kilo\"-sidefod, når du sender en anmeldelse." + "prReviewAttributionSubtitle": "Tilføj en \"Reviewed via Kilo\"-sidefod, når du sender en anmeldelse.", + "returnSendsMessage": "Returtasten sender beskeden", + "returnSendsMessageSubtitle": "Når dette er slået fra, indsætter returtasten et linjeskift i agenternes beskedfelter." }, "prReview": { "pullRequestUnavailable": "Pull request utilgængelig", @@ -2589,7 +2591,15 @@ "cannotReceiveFiles": "Denne session kan ikke modtage filer. Fjern vedhæftningerne for at sende din besked.", "couldNotAttachFiles": "Kunne ikke vedhæfte filer. Tryk send for at prøve igen.", "discardAttachmentsMessage": "Uploadede filer, der ikke er vedhæftet sendte beskeder, bliver slettet.", - "discardAttachmentsTitle": "Kassér vedhæftninger?" + "discardAttachmentsTitle": "Kassér vedhæftninger?", + "charactersRemaining": "{{count}} tegn tilbage", + "insertNewline": "Indsæt linjeskift", + "starterChipBuild": "Byg en funktion", + "starterChipEmpty": "Start med en idé", + "starterChipFix": "Ret en fejl", + "starterChipPrepare": "Konfigurér mit miljø", + "starterChipWrapUp": "Afslut min session", + "starterChipWriteTests": "Skriv test" }, "chatLink": { "linkCopied": "Link kopieret", diff --git a/apps/mobile/src/i18n/locales/de.json b/apps/mobile/src/i18n/locales/de.json index 5db681c473..df7a3b9da5 100644 --- a/apps/mobile/src/i18n/locales/de.json +++ b/apps/mobile/src/i18n/locales/de.json @@ -152,7 +152,9 @@ "appearanceDark": "Dunkel", "account": "Konto", "prReviewAttribution": "App-Zuordnung zu PR-Reviews hinzufügen", - "prReviewAttributionSubtitle": "Hänge beim Einreichen eines Reviews eine „Reviewed via Kilo“-Fußzeile an." + "prReviewAttributionSubtitle": "Hänge beim Einreichen eines Reviews eine „Reviewed via Kilo“-Fußzeile an.", + "returnSendsMessage": "Eingabetaste sendet Nachricht", + "returnSendsMessageSubtitle": "Wenn deaktiviert, fügt die Eingabetaste einen Zeilenumbruch in die Nachrichteneingabe für Agenten ein." }, "notifications": { "title": "Benachrichtigungen", @@ -2135,7 +2137,15 @@ "cannotReceiveFiles": "Diese Sitzung kann keine Dateien empfangen. Entferne die Anhänge, um deine Nachricht zu senden.", "couldNotAttachFiles": "Dateien konnten nicht angehängt werden. Tippe auf Senden, um es erneut zu versuchen.", "discardAttachmentsMessage": "Hochgeladene Dateien, die keiner gesendeten Nachricht angehängt sind, werden gelöscht.", - "discardAttachmentsTitle": "Anhänge verwerfen?" + "discardAttachmentsTitle": "Anhänge verwerfen?", + "charactersRemaining": "Noch {{count}} Zeichen", + "insertNewline": "Zeilenumbruch einfügen", + "starterChipBuild": "Eine Funktion entwickeln", + "starterChipEmpty": "Mit einer Idee beginnen", + "starterChipFix": "Einen Fehler beheben", + "starterChipPrepare": "Meine Umgebung einrichten", + "starterChipWrapUp": "Meine Sitzung abschließen", + "starterChipWriteTests": "Tests schreiben" }, "chatLink": { "linkCopied": "Link kopiert", diff --git a/apps/mobile/src/i18n/locales/el.json b/apps/mobile/src/i18n/locales/el.json index c5bb5def25..8fd2396c33 100644 --- a/apps/mobile/src/i18n/locales/el.json +++ b/apps/mobile/src/i18n/locales/el.json @@ -152,7 +152,9 @@ "appearanceDark": "Σκούρο", "account": "Λογαριασμός", "prReviewAttribution": "Προσθήκη αναφοράς της εφαρμογής στις ανασκοπήσεις PR", - "prReviewAttributionSubtitle": "Προσθέστε μια υποσημείωση «Reviewed via Kilo» όταν υποβάλλετε μια ανασκόπηση." + "prReviewAttributionSubtitle": "Προσθέστε μια υποσημείωση «Reviewed via Kilo» όταν υποβάλλετε μια ανασκόπηση.", + "returnSendsMessage": "Το πλήκτρο επιστροφής στέλνει το μήνυμα", + "returnSendsMessageSubtitle": "Όταν είναι απενεργοποιημένο, το πλήκτρο επιστροφής εισάγει νέα γραμμή στα πεδία εισαγωγής μηνύματος των πρακτόρων." }, "prReview": { "pullRequestUnavailable": "Το pull request δεν είναι διαθέσιμο", @@ -2589,7 +2591,15 @@ "cannotReceiveFiles": "Αυτή η συνεδρία δεν μπορεί να λάβει αρχεία. Αφαιρέστε τα συνημμένα για να στείλετε το μήνυμά σας.", "couldNotAttachFiles": "Δεν ήταν δυνατή η προσάρτηση αρχείων. Πατήστε αποστολή για να δοκιμάσετε ξανά.", "discardAttachmentsMessage": "Τα μεταφορτωμένα αρχεία που δεν έχουν προσαρτηθεί σε απεσταλμένα μηνύματα θα διαγραφούν.", - "discardAttachmentsTitle": "Απόρριψη συνημμένων;" + "discardAttachmentsTitle": "Απόρριψη συνημμένων;", + "charactersRemaining": "Απομένουν {{count}} χαρακτήρες", + "insertNewline": "Εισαγωγή νέας γραμμής", + "starterChipBuild": "Δημιουργία λειτουργίας", + "starterChipEmpty": "Ξεκινήστε με μια ιδέα", + "starterChipFix": "Διόρθωση σφάλματος", + "starterChipPrepare": "Ρύθμιση του περιβάλλοντός μου", + "starterChipWrapUp": "Ολοκλήρωση της συνεδρίας μου", + "starterChipWriteTests": "Σύνταξη δοκιμών" }, "chatLink": { "linkCopied": "Ο σύνδεσμος αντιγράφηκε", diff --git a/apps/mobile/src/i18n/locales/en.json b/apps/mobile/src/i18n/locales/en.json index c30d72deeb..1666ebb5e2 100644 --- a/apps/mobile/src/i18n/locales/en.json +++ b/apps/mobile/src/i18n/locales/en.json @@ -152,7 +152,9 @@ "appearanceDark": "Dark", "account": "Account", "prReviewAttribution": "Add app attribution to PR reviews", - "prReviewAttributionSubtitle": "Append a Reviewed via Kilo footer when you submit a review." + "prReviewAttributionSubtitle": "Append a Reviewed via Kilo footer when you submit a review.", + "returnSendsMessage": "Return key sends message", + "returnSendsMessageSubtitle": "When off, Return inserts a newline in agent composers." }, "prReview": { "pullRequestUnavailable": "Pull request unavailable", @@ -2608,12 +2610,20 @@ "removeOrRetryFailed": "Remove or retry failed attachments first.", "attachmentsWithSlashCommands": "Attachments cannot be sent with slash commands.", "photoMetadataNotRemoved": "Photo metadata could not be removed from an attachment.", + "charactersRemaining": "{{count}} characters remaining", "discardAttachmentsTitle": "Discard attachments?", "discardAttachmentsMessage": "Unclaimed uploads will be deleted.", "waitForUploads": "Wait for attachments to finish uploading.", "pasteFromClipboard": "Paste from clipboard", "stopGenerating": "Stop generating", "sendMessage": "Send message", + "insertNewline": "Insert newline", + "starterChipBuild": "Build a feature", + "starterChipFix": "Fix a bug", + "starterChipWriteTests": "Write tests", + "starterChipPrepare": "Set up my environment", + "starterChipWrapUp": "Wrap up my session", + "starterChipEmpty": "Start with an idea", "selectModelBeforeSending": "Select a model before sending", "preparingPlaceholder": "Setting up environment...", "finalizingPlaceholder": "Wrapping up...", diff --git a/apps/mobile/src/i18n/locales/es.json b/apps/mobile/src/i18n/locales/es.json index 4380f94e50..65ca36417b 100644 --- a/apps/mobile/src/i18n/locales/es.json +++ b/apps/mobile/src/i18n/locales/es.json @@ -152,7 +152,9 @@ "appearanceDark": "Oscuro", "account": "Cuenta", "prReviewAttribution": "Añadir atribución de la app a las revisiones de PR", - "prReviewAttributionSubtitle": "Añade un pie de página Revisado vía Kilo cuando envías una revisión." + "prReviewAttributionSubtitle": "Añade un pie de página Revisado vía Kilo cuando envías una revisión.", + "returnSendsMessage": "La tecla Intro envía el mensaje", + "returnSendsMessageSubtitle": "Si está desactivado, Intro inserta un salto de línea en los campos de mensaje del agente." }, "notifications": { "channel": { @@ -2147,7 +2149,15 @@ "cannotReceiveFiles": "Esta sesión no puede recibir archivos. Quita los archivos adjuntos para enviar tu mensaje.", "couldNotAttachFiles": "No se pudieron adjuntar los archivos. Toca enviar para reintentar.", "discardAttachmentsMessage": "Se eliminarán los archivos subidos que no estén adjuntos a mensajes enviados.", - "discardAttachmentsTitle": "¿Descartar adjuntos?" + "discardAttachmentsTitle": "¿Descartar adjuntos?", + "charactersRemaining": "Quedan {{count}} caracteres", + "insertNewline": "Insertar salto de línea", + "starterChipBuild": "Crear una función", + "starterChipEmpty": "Empezar con una idea", + "starterChipFix": "Corregir un error", + "starterChipPrepare": "Configurar mi entorno", + "starterChipWrapUp": "Finalizar mi sesión", + "starterChipWriteTests": "Escribir pruebas" }, "chatLink": { "linkCopied": "Enlace copiado", diff --git a/apps/mobile/src/i18n/locales/et.json b/apps/mobile/src/i18n/locales/et.json index 572770f34e..5f759d2cd6 100644 --- a/apps/mobile/src/i18n/locales/et.json +++ b/apps/mobile/src/i18n/locales/et.json @@ -152,7 +152,9 @@ "appearanceDark": "Tumedas", "account": "Konto", "prReviewAttribution": "Lisa rakenduse viide PR-ülevaatustesse", - "prReviewAttributionSubtitle": "Kui esitad ülevaate, lisa jalusesse 'Reviewed via Kilo'." + "prReviewAttributionSubtitle": "Kui esitad ülevaate, lisa jalusesse 'Reviewed via Kilo'.", + "returnSendsMessage": "Sisestusklahv saadab sõnumi", + "returnSendsMessageSubtitle": "Kui see on välja lülitatud, lisab sisestusklahv agentide sõnumi sisestusväljadel reavahetuse." }, "prReview": { "pullRequestUnavailable": "Pull request ei ole saadaval", @@ -2589,7 +2591,15 @@ "cannotReceiveFiles": "See seanss ei saa faile vastu võtta. Sõnumi saatmiseks eemaldage manused.", "couldNotAttachFiles": "Failide lisamine ebaõnnestus. Uuesti proovimiseks puudutage saatmist.", "discardAttachmentsMessage": "Üles laaditud failid, mida pole saadetud sõnumitele lisatud, kustutatakse.", - "discardAttachmentsTitle": "Kas loobuda manustest?" + "discardAttachmentsTitle": "Kas loobuda manustest?", + "charactersRemaining": "{{count}} tähemärki jäänud", + "insertNewline": "Lisage reavahetus", + "starterChipBuild": "Looge funktsioon", + "starterChipEmpty": "Alustage ideest", + "starterChipFix": "Parandage viga", + "starterChipPrepare": "Seadistage minu keskkond", + "starterChipWrapUp": "Lõpetage minu seanss", + "starterChipWriteTests": "Kirjutage testid" }, "chatLink": { "linkCopied": "Link kopeeritud", diff --git a/apps/mobile/src/i18n/locales/eu.json b/apps/mobile/src/i18n/locales/eu.json index ecbcbf7788..27adaace53 100644 --- a/apps/mobile/src/i18n/locales/eu.json +++ b/apps/mobile/src/i18n/locales/eu.json @@ -152,7 +152,9 @@ "appearanceDark": "Iluna", "account": "Kontua", "prReviewAttribution": "Gehitu aplikazioaren atribuzioa PR berrikuspenetan", - "prReviewAttributionSubtitle": "Erantsi 'Kilo-ren bidez berrikusia' orri-oina berrikuspen bat bidaltzen duzunean." + "prReviewAttributionSubtitle": "Erantsi 'Kilo-ren bidez berrikusia' orri-oina berrikuspen bat bidaltzen duzunean.", + "returnSendsMessage": "Sartu teklak mezua bidaltzen du", + "returnSendsMessageSubtitle": "Desaktibatuta dagoenean, Sartu teklak lerro-jauzia txertatzen du agenteen konpositoreetan." }, "prReview": { "pullRequestUnavailable": "Pull request-a ez dago eskuragarri", @@ -2589,7 +2591,15 @@ "cannotReceiveFiles": "Saio honek ezin du fitxategirik jaso. Kendu eranskinak zure mezua bidaltzeko.", "couldNotAttachFiles": "Ezin izan dira fitxategiak erantsi. Sakatu bidali berriro saiatzeko.", "discardAttachmentsMessage": "Bidalitako mezuei erantsi gabeko igotako fitxategiak ezabatuko dira.", - "discardAttachmentsTitle": "Baztertu eranskinak?" + "discardAttachmentsTitle": "Baztertu eranskinak?", + "charactersRemaining": "{{count}} karaktere geratzen dira", + "insertNewline": "Txertatu lerro-jauzia", + "starterChipBuild": "Garatu eginbide bat", + "starterChipEmpty": "Hasi ideia batekin", + "starterChipFix": "Konpondu akats bat", + "starterChipPrepare": "Konfiguratu nire ingurunea", + "starterChipWrapUp": "Amaitu nire saioa", + "starterChipWriteTests": "Idatzi probak" }, "chatLink": { "linkCopied": "Esteka kopiatuta", diff --git a/apps/mobile/src/i18n/locales/fa.json b/apps/mobile/src/i18n/locales/fa.json index c4d17830eb..418b4d6092 100644 --- a/apps/mobile/src/i18n/locales/fa.json +++ b/apps/mobile/src/i18n/locales/fa.json @@ -152,7 +152,9 @@ "appearanceDark": "تیره", "account": "حساب", "prReviewAttribution": "افزودن انتساب برنامه به مرورهای PR", - "prReviewAttributionSubtitle": "هنگام ارسال یک مرور، یک پانوشت «بازبینی شده از طریق Kilo» اضافه کنید." + "prReviewAttributionSubtitle": "هنگام ارسال یک مرور، یک پانوشت «بازبینی شده از طریق Kilo» اضافه کنید.", + "returnSendsMessage": "کلید بازگشت پیام را ارسال می‌کند", + "returnSendsMessageSubtitle": "وقتی خاموش است، کلید بازگشت در کادرهای ورودی پیام عامل یک خط جدید درج می‌کند." }, "prReview": { "pullRequestUnavailable": "درخواست pull در دسترس نیست", @@ -2589,7 +2591,15 @@ "cannotReceiveFiles": "این جلسه نمی‌تواند فایل دریافت کند. برای ارسال پیام، پیوست‌ها را حذف کنید.", "couldNotAttachFiles": "پیوست کردن فایل‌ها ممکن نشد. برای تلاش مجدد، ارسال را لمس کنید.", "discardAttachmentsMessage": "فایل‌های بارگذاری‌شده‌ای که به پیام‌های ارسال‌شده پیوست نشده‌اند حذف خواهند شد.", - "discardAttachmentsTitle": "پیوست‌ها دور انداخته شوند؟" + "discardAttachmentsTitle": "پیوست‌ها دور انداخته شوند؟", + "charactersRemaining": "{{count}} نویسه باقی مانده", + "insertNewline": "درج خط جدید", + "starterChipBuild": "ساخت یک ویژگی", + "starterChipEmpty": "شروع با یک ایده", + "starterChipFix": "رفع یک اشکال", + "starterChipPrepare": "راه‌اندازی محیط من", + "starterChipWrapUp": "پایان دادن به نشست من", + "starterChipWriteTests": "نوشتن آزمون‌ها" }, "chatLink": { "linkCopied": "پیوند کپی شد", diff --git a/apps/mobile/src/i18n/locales/fi.json b/apps/mobile/src/i18n/locales/fi.json index caed2dee6e..a1d4801da1 100644 --- a/apps/mobile/src/i18n/locales/fi.json +++ b/apps/mobile/src/i18n/locales/fi.json @@ -152,7 +152,9 @@ "appearanceDark": "Tumma", "account": "Tili", "prReviewAttribution": "Lisää sovelluksen määrittäminen PR-arvosteluihin", - "prReviewAttributionSubtitle": "Liitä Reviewed via Kilo -alaviite, kun lähetät arvostelun." + "prReviewAttributionSubtitle": "Liitä Reviewed via Kilo -alaviite, kun lähetät arvostelun.", + "returnSendsMessage": "Enter-näppäin lähettää viestin", + "returnSendsMessageSubtitle": "Kun asetus on pois päältä, Enter lisää rivinvaihdon agenttien viestikenttiin." }, "prReview": { "pullRequestUnavailable": "Pull request ei ole käytettävissä", @@ -2589,7 +2591,15 @@ "cannotReceiveFiles": "Tämä istunto ei voi vastaanottaa tiedostoja. Poista liitteet lähettääksesi viestisi.", "couldNotAttachFiles": "Tiedostoja ei voitu liittää. Napauta lähetä yrittääksesi uudelleen.", "discardAttachmentsMessage": "Ladatut tiedostot, joita ei ole liitetty lähetettyihin viesteihin, poistetaan.", - "discardAttachmentsTitle": "Hylätäänkö liitteet?" + "discardAttachmentsTitle": "Hylätäänkö liitteet?", + "charactersRemaining": "{{count}} merkkiä jäljellä", + "insertNewline": "Lisää rivinvaihto", + "starterChipBuild": "Toteuta ominaisuus", + "starterChipEmpty": "Aloita ideasta", + "starterChipFix": "Korjaa virhe", + "starterChipPrepare": "Määritä ympäristöni", + "starterChipWrapUp": "Viimeistele istuntoni", + "starterChipWriteTests": "Kirjoita testejä" }, "chatLink": { "linkCopied": "Linkki kopioitu", diff --git a/apps/mobile/src/i18n/locales/fil.json b/apps/mobile/src/i18n/locales/fil.json index c95c78289b..d3a2052343 100644 --- a/apps/mobile/src/i18n/locales/fil.json +++ b/apps/mobile/src/i18n/locales/fil.json @@ -152,7 +152,9 @@ "appearanceDark": "Dark", "account": "Account", "prReviewAttribution": "Magdagdag ng app attribution sa mga PR review", - "prReviewAttributionSubtitle": "Magdagdag ng footer na Reviewed via Kilo kapag nagsumite ka ng review." + "prReviewAttributionSubtitle": "Magdagdag ng footer na Reviewed via Kilo kapag nagsumite ka ng review.", + "returnSendsMessage": "Nagpapadala ng mensahe ang Return key", + "returnSendsMessageSubtitle": "Kapag naka-off, naglalagay ang Return ng bagong linya sa mga input ng mensahe ng agent." }, "prReview": { "pullRequestUnavailable": "Hindi available ang pull request", @@ -2589,7 +2591,15 @@ "cannotReceiveFiles": "Hindi makakatanggap ng file ang session na ito. Alisin ang mga attachment para maipadala ang iyong mensahe.", "couldNotAttachFiles": "Hindi mailakip ang mga file. I-tap ang send para subukang muli.", "discardAttachmentsMessage": "Ide-delete ang mga na-upload na file na hindi nakalakip sa mga naipadalang mensahe.", - "discardAttachmentsTitle": "I-discard ang mga attachment?" + "discardAttachmentsTitle": "I-discard ang mga attachment?", + "charactersRemaining": "{{count}} na character ang natitira", + "insertNewline": "Magpasok ng bagong linya", + "starterChipBuild": "Gumawa ng feature", + "starterChipEmpty": "Magsimula sa isang ideya", + "starterChipFix": "Ayusin ang depekto", + "starterChipPrepare": "Ihanda ang aking environment", + "starterChipWrapUp": "Tapusin ang aking session", + "starterChipWriteTests": "Sumulat ng mga pagsubok" }, "chatLink": { "linkCopied": "Na-kopya ang link", diff --git a/apps/mobile/src/i18n/locales/fr.json b/apps/mobile/src/i18n/locales/fr.json index 12d249cc47..0ad79fd706 100644 --- a/apps/mobile/src/i18n/locales/fr.json +++ b/apps/mobile/src/i18n/locales/fr.json @@ -2065,7 +2065,15 @@ "cannotReceiveFiles": "Cette session ne peut pas recevoir de fichiers. Supprimez les pièces jointes pour envoyer votre message.", "couldNotAttachFiles": "Impossible de joindre les fichiers. Touchez Envoyer pour réessayer.", "discardAttachmentsMessage": "Les fichiers téléversés non joints à des messages envoyés seront supprimés.", - "discardAttachmentsTitle": "Abandonner les pièces jointes ?" + "discardAttachmentsTitle": "Abandonner les pièces jointes ?", + "charactersRemaining": "{{count}} caractères restants", + "insertNewline": "Insérer un saut de ligne", + "starterChipBuild": "Créer une fonctionnalité", + "starterChipEmpty": "Commencer par une idée", + "starterChipFix": "Corriger un bogue", + "starterChipPrepare": "Configurer mon environnement", + "starterChipWrapUp": "Terminer ma session", + "starterChipWriteTests": "Écrire des tests" }, "chatLink": { "linkCopied": "Lien copié", @@ -2195,7 +2203,9 @@ "appearanceDark": "Sombre", "account": "Compte", "prReviewAttribution": "Ajouter l'attribution de l'application aux revues de PR", - "prReviewAttributionSubtitle": "Ajouter un pied de page « Revue via Kilo » lorsque vous soumettez une revue." + "prReviewAttributionSubtitle": "Ajouter un pied de page « Revue via Kilo » lorsque vous soumettez une revue.", + "returnSendsMessage": "La touche Entrée envoie le message", + "returnSendsMessageSubtitle": "Si cette option est désactivée, la touche Entrée insère un saut de ligne dans les champs de saisie des agents." }, "addCredits": { "cta": "Ajouter des crédits", diff --git a/apps/mobile/src/i18n/locales/ga.json b/apps/mobile/src/i18n/locales/ga.json index 4f8fc3df82..0376018c85 100644 --- a/apps/mobile/src/i18n/locales/ga.json +++ b/apps/mobile/src/i18n/locales/ga.json @@ -152,7 +152,9 @@ "appearanceDark": "Dorcha", "account": "Cuntas", "prReviewAttribution": "Cuir sonrú an aip le hathbhreithnithe PR", - "prReviewAttributionSubtitle": "Cuir an chúlscríbhinn 'Athbhreithnithe trí Kilo' leis nuair a chuireann tú isteach athbhreithniú." + "prReviewAttributionSubtitle": "Cuir an chúlscríbhinn 'Athbhreithnithe trí Kilo' leis nuair a chuireann tú isteach athbhreithniú.", + "returnSendsMessage": "Seolann an eochair Aisfhillte teachtaireacht", + "returnSendsMessageSubtitle": "Nuair atá sé seo as, cuireann an eochair Aisfhillte líne nua isteach i réimsí ionchuir teachtaireachta na ngníomhairí." }, "prReview": { "pullRequestUnavailable": "Iarratas tarraingthe ar fáil", @@ -2646,7 +2648,15 @@ "cannotReceiveFiles": "Ní féidir leis an seisiún seo comhaid a fháil. Bain na ceangaltáin le do theachtaireacht a sheoladh.", "couldNotAttachFiles": "Níorbh fhéidir comhaid a cheangal. Tapáil seol le triail arís.", "discardAttachmentsMessage": "Scriosfar comhaid uaslódáilte nach bhfuil ceangailte le teachtaireachtaí seolta.", - "discardAttachmentsTitle": "An gcaithfear na ceangaltáin ar lár?" + "discardAttachmentsTitle": "An gcaithfear na ceangaltáin ar lár?", + "charactersRemaining": "{{count}} carachtar fágtha", + "insertNewline": "Cuir líne nua isteach", + "starterChipBuild": "Tóg gné", + "starterChipEmpty": "Tosaigh le smaoineamh", + "starterChipFix": "Deisigh fabht", + "starterChipPrepare": "Cuir mo thimpeallacht ar bun", + "starterChipWrapUp": "Críochnaigh mo sheisiún", + "starterChipWriteTests": "Scríobh tástálacha" }, "chatLink": { "linkCopied": "Nasc cóipeáilte", diff --git a/apps/mobile/src/i18n/locales/gl.json b/apps/mobile/src/i18n/locales/gl.json index ef460146ac..70953d2c73 100644 --- a/apps/mobile/src/i18n/locales/gl.json +++ b/apps/mobile/src/i18n/locales/gl.json @@ -152,7 +152,9 @@ "appearanceDark": "Escuro", "account": "Conta", "prReviewAttribution": "Engadir atribución da app ás revisións de PR", - "prReviewAttributionSubtitle": "Engade un pé de páxina \"Reviewed via Kilo\" cando envíes unha revisión." + "prReviewAttributionSubtitle": "Engade un pé de páxina \"Reviewed via Kilo\" cando envíes unha revisión.", + "returnSendsMessage": "A tecla Intro envía a mensaxe", + "returnSendsMessageSubtitle": "Cando está desactivado, Intro insire un salto de liña nos campos de mensaxe dos axentes." }, "prReview": { "pullRequestUnavailable": "Pull request non dispoñible", @@ -2589,7 +2591,15 @@ "cannotReceiveFiles": "Esta sesión non pode recibir ficheiros. Elimina os anexos para enviar a túa mensaxe.", "couldNotAttachFiles": "Non se puideron anexar os ficheiros. Toca enviar para reintentar.", "discardAttachmentsMessage": "Eliminaranse os arquivos subidos que non estean anexados a mensaxes enviadas.", - "discardAttachmentsTitle": "Descartar os anexos?" + "discardAttachmentsTitle": "Descartar os anexos?", + "charactersRemaining": "Quedan {{count}} caracteres", + "insertNewline": "Inserir un salto de liña", + "starterChipBuild": "Crear unha funcionalidade", + "starterChipEmpty": "Comezar cunha idea", + "starterChipFix": "Corrixir un erro", + "starterChipPrepare": "Configurar o meu ambiente", + "starterChipWrapUp": "Rematar a miña sesión", + "starterChipWriteTests": "Escribir probas" }, "chatLink": { "linkCopied": "Ligazón copiada", diff --git a/apps/mobile/src/i18n/locales/gu.json b/apps/mobile/src/i18n/locales/gu.json index 4657d1c4fa..381bb61400 100644 --- a/apps/mobile/src/i18n/locales/gu.json +++ b/apps/mobile/src/i18n/locales/gu.json @@ -152,7 +152,9 @@ "appearanceDark": "ડાર્ક", "account": "એકાઉન્ટ", "prReviewAttribution": "PR સમીક્ષાઓમાં એપ એટ્રિબ્યુશન ઉમેરો", - "prReviewAttributionSubtitle": "સમીક્ષા સબમિટ કરતી વખતે Reviewed via Kilo ફૂટર જોડો." + "prReviewAttributionSubtitle": "સમીક્ષા સબમિટ કરતી વખતે Reviewed via Kilo ફૂટર જોડો.", + "returnSendsMessage": "રિટર્ન કી સંદેશ મોકલે છે", + "returnSendsMessageSubtitle": "બંધ હોય ત્યારે, રિટર્ન એજન્ટના સંદેશ ઇનપુટમાં નવી લાઇન દાખલ કરે છે." }, "prReview": { "pullRequestUnavailable": "પુલ રિક્વેસ્ટ ઉપલબ્ધ નથી", @@ -2589,7 +2591,15 @@ "cannotReceiveFiles": "આ સત્ર ફાઇલો મેળવી શકતું નથી. તમારો સંદેશ મોકલવા માટે એટેચમેન્ટ્સ દૂર કરો.", "couldNotAttachFiles": "ફાઇલો એટેચ કરી શકાઈ નહીં. ફરી પ્રયાસ કરવા મોકલો ટેપ કરો.", "discardAttachmentsMessage": "મોકલેલા સંદેશોમાં એટેચ ન કરેલી અપલોડ થયેલી ફાઇલો કાઢી નાખવામાં આવશે.", - "discardAttachmentsTitle": "એટેચમેન્ટ્સ કાઢી નાખવા છે?" + "discardAttachmentsTitle": "એટેચમેન્ટ્સ કાઢી નાખવા છે?", + "charactersRemaining": "{{count}} અક્ષરો બાકી", + "insertNewline": "નવી લાઇન દાખલ કરો", + "starterChipBuild": "સુવિધા બનાવો", + "starterChipEmpty": "વિચારથી શરૂઆત કરો", + "starterChipFix": "ભૂલ સુધારો", + "starterChipPrepare": "મારું એન્વાયર્નમેન્ટ સેટ કરો", + "starterChipWrapUp": "મારું સત્ર પૂર્ણ કરો", + "starterChipWriteTests": "પરીક્ષણો લખો" }, "chatLink": { "linkCopied": "લિંક કૉપિ થઈ", diff --git a/apps/mobile/src/i18n/locales/ha.json b/apps/mobile/src/i18n/locales/ha.json index 1170bd7569..fd68cd53a0 100644 --- a/apps/mobile/src/i18n/locales/ha.json +++ b/apps/mobile/src/i18n/locales/ha.json @@ -152,7 +152,9 @@ "appearanceDark": "Duhu", "account": "Asusu", "prReviewAttribution": "Ƙara asalin app a cikin bitar PR", - "prReviewAttributionSubtitle": "Ƙara rubutun ƙasa na \"An bita ta Kilo\" lokacin da ka ƙaddamar da bita." + "prReviewAttributionSubtitle": "Ƙara rubutun ƙasa na \"An bita ta Kilo\" lokacin da ka ƙaddamar da bita.", + "returnSendsMessage": "Maɓallin Komawa yana aika saƙo", + "returnSendsMessageSubtitle": "Idan a kashe yake, Komawa yana saka sabon layi a wuraren shigar da saƙo na wakilai." }, "prReview": { "pullRequestUnavailable": "Buƙatar jan ba ta samuwa", @@ -2589,7 +2591,15 @@ "cannotReceiveFiles": "Wannan zaman ba zai iya karɓar fayiloli ba. Cire abubuwan da aka haɗa don aika sakonka.", "couldNotAttachFiles": "An kasa haɗa fayiloli. Danna aika don sake gwadawa.", "discardAttachmentsMessage": "Za a share fayilolin da aka ɗora amma ba a haɗa su da saƙonnin da aka aika ba.", - "discardAttachmentsTitle": "Watsi da haɗe-haɗe?" + "discardAttachmentsTitle": "Watsi da haɗe-haɗe?", + "charactersRemaining": "Haruffa {{count}} suka rage", + "insertNewline": "Saka sabon layi", + "starterChipBuild": "Gina fasali", + "starterChipEmpty": "Fara da ra'ayi", + "starterChipFix": "Gyara kuskure", + "starterChipPrepare": "Shirya muhallina", + "starterChipWrapUp": "Kammala zamana", + "starterChipWriteTests": "Rubuta gwaje-gwaje" }, "chatLink": { "linkCopied": "An kwafa hanyar", diff --git a/apps/mobile/src/i18n/locales/he.json b/apps/mobile/src/i18n/locales/he.json index 00b44f36d4..927a3cd3a9 100644 --- a/apps/mobile/src/i18n/locales/he.json +++ b/apps/mobile/src/i18n/locales/he.json @@ -152,7 +152,9 @@ "appearanceDark": "כהה", "account": "חשבון", "prReviewAttribution": "הוסף שיוך אפליקציה לסקירות PR", - "prReviewAttributionSubtitle": "הוסף כותרת תחתונה של 'נסקר דרך Kilo' כשאתה שולח סקירה." + "prReviewAttributionSubtitle": "הוסף כותרת תחתונה של 'נסקר דרך Kilo' כשאתה שולח סקירה.", + "returnSendsMessage": "מקש החזרה שולח הודעה", + "returnSendsMessageSubtitle": "כשהאפשרות כבויה, מקש החזרה מוסיף שורה חדשה בשדות ההודעה של הסוכנים." }, "notifications": { "title": "התראות", @@ -2147,7 +2149,15 @@ "cannotReceiveFiles": "סשן זה אינו יכול לקבל קבצים. הסר את הקבצים המצורפים כדי לשלוח את ההודעה.", "couldNotAttachFiles": "לא ניתן היה לצרף קבצים. הקש על שליחה כדי לנסות שוב.", "discardAttachmentsMessage": "קבצים שהועלו ולא צורפו להודעות שנשלחו יימחקו.", - "discardAttachmentsTitle": "למחוק את הקבצים המצורפים?" + "discardAttachmentsTitle": "למחוק את הקבצים המצורפים?", + "charactersRemaining": "נותרו {{count}} תווים", + "insertNewline": "הוסף שורה חדשה", + "starterChipBuild": "פתח תכונה", + "starterChipEmpty": "התחל ברעיון", + "starterChipFix": "תקן באג", + "starterChipPrepare": "הגדר את הסביבה שלי", + "starterChipWrapUp": "סיים את ההפעלה שלי", + "starterChipWriteTests": "כתוב בדיקות" }, "chatLink": { "linkCopied": "הקישור הועתק", diff --git a/apps/mobile/src/i18n/locales/hi.json b/apps/mobile/src/i18n/locales/hi.json index 2815e52feb..5c188e273b 100644 --- a/apps/mobile/src/i18n/locales/hi.json +++ b/apps/mobile/src/i18n/locales/hi.json @@ -152,7 +152,9 @@ "appearanceDark": "गहरा", "account": "खाता", "prReviewAttribution": "PR समीक्षाओं में ऐप एट्रिब्यूशन जोड़ें", - "prReviewAttributionSubtitle": "समीक्षा सबमिट करने पर Reviewed via Kilo फ़ुटर जोड़ें।" + "prReviewAttributionSubtitle": "समीक्षा सबमिट करने पर Reviewed via Kilo फ़ुटर जोड़ें।", + "returnSendsMessage": "रिटर्न कुंजी संदेश भेजती है", + "returnSendsMessageSubtitle": "बंद होने पर, रिटर्न कुंजी एजेंट के संदेश इनपुट में नई पंक्ति डालती है।" }, "notifications": { "title": "सूचनाएँ", @@ -2101,7 +2103,15 @@ "cannotReceiveFiles": "यह सत्र फ़ाइलें प्राप्त नहीं कर सकता। अपना संदेश भेजने के लिए अटैचमेंट हटाएँ।", "couldNotAttachFiles": "फ़ाइलें संलग्न नहीं की जा सकीं। फिर से कोशिश करने के लिए भेजें पर टैप करें।", "discardAttachmentsMessage": "भेजे गए संदेशों से न जुड़ी अपलोड की गई फ़ाइलें हटा दी जाएँगी।", - "discardAttachmentsTitle": "संलग्नक ख़ारिज करें?" + "discardAttachmentsTitle": "संलग्नक ख़ारिज करें?", + "charactersRemaining": "{{count}} अक्षर शेष", + "insertNewline": "नई पंक्ति डालें", + "starterChipBuild": "एक सुविधा बनाएँ", + "starterChipEmpty": "एक विचार से शुरू करें", + "starterChipFix": "एक त्रुटि ठीक करें", + "starterChipPrepare": "मेरा परिवेश तैयार करें", + "starterChipWrapUp": "मेरा सत्र समाप्त करें", + "starterChipWriteTests": "परीक्षण लिखें" }, "chatLink": { "linkCopied": "लिंक कॉपी किया गया", diff --git a/apps/mobile/src/i18n/locales/hr.json b/apps/mobile/src/i18n/locales/hr.json index ae50ddba29..406841f33b 100644 --- a/apps/mobile/src/i18n/locales/hr.json +++ b/apps/mobile/src/i18n/locales/hr.json @@ -152,7 +152,9 @@ "appearanceDark": "Tamno", "account": "Račun", "prReviewAttribution": "Dodaj atribuciju aplikacije u preglede PR-a", - "prReviewAttributionSubtitle": "Dodajte potpis „Reviewed via Kilo\" kada podnesete pregled." + "prReviewAttributionSubtitle": "Dodajte potpis „Reviewed via Kilo\" kada podnesete pregled.", + "returnSendsMessage": "Tipka za novi redak šalje poruku", + "returnSendsMessageSubtitle": "Kad je isključeno, tipka za novi redak umeće novi redak u polja za unos poruke agentima." }, "prReview": { "pullRequestUnavailable": "Pull request nedostupan", @@ -2608,7 +2610,15 @@ "cannotReceiveFiles": "Ova sesija ne može primati datoteke. Uklonite privitke da biste poslali poruku.", "couldNotAttachFiles": "Nije moguće priviti datoteke. Dodirnite Pošalji za ponovni pokušaj.", "discardAttachmentsMessage": "Učitane datoteke koje nisu priložene poslanim porukama bit će obrisane.", - "discardAttachmentsTitle": "Odbaciti privitke?" + "discardAttachmentsTitle": "Odbaciti privitke?", + "charactersRemaining": "Preostalo znakova: {{count}}", + "insertNewline": "Umetnite novi redak", + "starterChipBuild": "Izradite značajku", + "starterChipEmpty": "Započnite s idejom", + "starterChipFix": "Ispravite pogrešku", + "starterChipPrepare": "Postavite moje okruženje", + "starterChipWrapUp": "Dovršite moju sesiju", + "starterChipWriteTests": "Napišite testove" }, "chatLink": { "linkCopied": "Poveznica kopirana", diff --git a/apps/mobile/src/i18n/locales/ht.json b/apps/mobile/src/i18n/locales/ht.json index 2829259b05..68ec02e66a 100644 --- a/apps/mobile/src/i18n/locales/ht.json +++ b/apps/mobile/src/i18n/locales/ht.json @@ -152,7 +152,9 @@ "appearanceDark": "Fènwa", "account": "Kont", "prReviewAttribution": "Ajoute atribisyon aplikasyon nan revizyon PR", - "prReviewAttributionSubtitle": "Ajoute yon footer Reviewed via Kilo lè ou soumèt yon revizyon." + "prReviewAttributionSubtitle": "Ajoute yon footer Reviewed via Kilo lè ou soumèt yon revizyon.", + "returnSendsMessage": "Kle Retou a voye mesaj", + "returnSendsMessageSubtitle": "Lè li dezaktive, kle Retou a antre yon nouvo liy nan chan mesaj ajan yo." }, "prReview": { "pullRequestUnavailable": "Demand rale pa disponib", @@ -2589,7 +2591,15 @@ "cannotReceiveFiles": "Sesyon sa a pa ka resevwa fichye. Retire pyès jwenn yo pou voye mesaj ou.", "couldNotAttachFiles": "Pa t ka kole fichye. Tape voye pou eseye ankò.", "discardAttachmentsMessage": "Fichye telechaje ki pa atache ak mesaj voye yo pral efase.", - "discardAttachmentsTitle": "Jete atachman yo?" + "discardAttachmentsTitle": "Jete atachman yo?", + "charactersRemaining": "{{count}} karaktè rete", + "insertNewline": "Antre yon nouvo liy", + "starterChipBuild": "Kreye yon fonksyon", + "starterChipEmpty": "Kòmanse ak yon lide", + "starterChipFix": "Korije yon erè", + "starterChipPrepare": "Konfigire anviwònman mwen", + "starterChipWrapUp": "Fini sesyon mwen", + "starterChipWriteTests": "Ekri tès" }, "chatLink": { "linkCopied": "Lyen kopye", diff --git a/apps/mobile/src/i18n/locales/hu.json b/apps/mobile/src/i18n/locales/hu.json index bddea81371..09ef7728e9 100644 --- a/apps/mobile/src/i18n/locales/hu.json +++ b/apps/mobile/src/i18n/locales/hu.json @@ -152,7 +152,9 @@ "appearanceDark": "Sötét", "account": "Fiók", "prReviewAttribution": "Alkalmazás-hozzárendelés hozzáadása a PR-ellenőrzésekhez", - "prReviewAttributionSubtitle": "A 'Reviewed via Kilo' lábléc hozzáfűzése, amikor ellenőrzést küld el." + "prReviewAttributionSubtitle": "A 'Reviewed via Kilo' lábléc hozzáfűzése, amikor ellenőrzést küld el.", + "returnSendsMessage": "Az Enter billentyű elküldi az üzenetet", + "returnSendsMessageSubtitle": "Kikapcsolva az Enter új sort szúr be az ügynökök üzenetbeviteli mezőibe." }, "prReview": { "pullRequestUnavailable": "Pull request nem érhető el", @@ -2589,7 +2591,15 @@ "cannotReceiveFiles": "Ez a munkamenet nem fogadhat fájlokat. Az üzenet elküldéséhez távolítsd el a csatolmányokat.", "couldNotAttachFiles": "A fájlok csatolása nem sikerült. Az újrapróbáláshoz koppints a küldésre.", "discardAttachmentsMessage": "Az elküldött üzenetekhez nem csatolt feltöltések törlődnek.", - "discardAttachmentsTitle": "Elveti a csatolmányokat?" + "discardAttachmentsTitle": "Elveti a csatolmányokat?", + "charactersRemaining": "{{count}} karakter maradt", + "insertNewline": "Új sor beszúrása", + "starterChipBuild": "Funkció fejlesztése", + "starterChipEmpty": "Kezdés egy ötlettel", + "starterChipFix": "Hiba javítása", + "starterChipPrepare": "A környezetem beállítása", + "starterChipWrapUp": "A munkamenetem lezárása", + "starterChipWriteTests": "Tesztek írása" }, "chatLink": { "linkCopied": "Hivatkozás másolva", diff --git a/apps/mobile/src/i18n/locales/hy.json b/apps/mobile/src/i18n/locales/hy.json index 050688736a..922a7b1cea 100644 --- a/apps/mobile/src/i18n/locales/hy.json +++ b/apps/mobile/src/i18n/locales/hy.json @@ -152,7 +152,9 @@ "appearanceDark": "Մուգ", "account": "Հաշիվ", "prReviewAttribution": "Ավելացնել հավելվածի վերագրումը PR ակնարկներին", - "prReviewAttributionSubtitle": "Ավելացնել «Վերանայվել է Kilo-ի միջոցով» ստորագիրը, երբ ներկայացնում եք ակնարկ:" + "prReviewAttributionSubtitle": "Ավելացնել «Վերանայվել է Kilo-ի միջոցով» ստորագիրը, երբ ներկայացնում եք ակնարկ:", + "returnSendsMessage": "Վերադարձի ստեղնն ուղարկում է հաղորդագրությունը", + "returnSendsMessageSubtitle": "Երբ անջատված է, վերադարձի ստեղնը նոր տող է տեղադրում գործակալների հաղորդագրության մուտքագրման դաշտերում։" }, "prReview": { "pullRequestUnavailable": "Pull request-ը հասանելի չէ", @@ -2589,7 +2591,15 @@ "cannotReceiveFiles": "Այս նիստը չի կարող ընդունել ֆայլեր: Հեռացրեք կցումները՝ հաղորդագրությունն ուղարկելու համար:", "couldNotAttachFiles": "Հնարավոր չեղավ կցել ֆայլերը: Հպեք «Ուղարկել»՝ նորից փորձելու համար:", "discardAttachmentsMessage": "Ուղարկված հաղորդագրություններին չկցված վերբեռնված ֆայլերը կջնջվեն։", - "discardAttachmentsTitle": "Մերժե՞լ կցորդները" + "discardAttachmentsTitle": "Մերժե՞լ կցորդները", + "charactersRemaining": "Մնացել է {{count}} նիշ", + "insertNewline": "Տեղադրել նոր տող", + "starterChipBuild": "Ստեղծել գործառույթ", + "starterChipEmpty": "Սկսել գաղափարից", + "starterChipFix": "Շտկել սխալը", + "starterChipPrepare": "Կարգավորել իմ միջավայրը", + "starterChipWrapUp": "Ավարտել իմ նիստը", + "starterChipWriteTests": "Գրել թեստեր" }, "chatLink": { "linkCopied": "Հղումը պատճենված է", diff --git a/apps/mobile/src/i18n/locales/id.json b/apps/mobile/src/i18n/locales/id.json index 9af271c2d5..08c739fa45 100644 --- a/apps/mobile/src/i18n/locales/id.json +++ b/apps/mobile/src/i18n/locales/id.json @@ -152,7 +152,9 @@ "appearanceDark": "Gelap", "account": "Akun", "prReviewAttribution": "Tambahkan atribusi aplikasi ke ulasan PR", - "prReviewAttributionSubtitle": "Tambahkan footer Ditinjau melalui Kilo saat Anda mengirimkan ulasan." + "prReviewAttributionSubtitle": "Tambahkan footer Ditinjau melalui Kilo saat Anda mengirimkan ulasan.", + "returnSendsMessage": "Tombol Enter mengirim pesan", + "returnSendsMessageSubtitle": "Saat nonaktif, Enter menyisipkan baris baru di kolom pesan agen." }, "notifications": { "title": "Notifikasi", @@ -2135,7 +2137,15 @@ "cannotReceiveFiles": "Sesi ini tidak dapat menerima file. Hapus lampiran untuk mengirim pesan Anda.", "couldNotAttachFiles": "Tidak dapat melampirkan file. Ketuk Kirim untuk mencoba lagi.", "discardAttachmentsMessage": "Unggahan yang tidak dilampirkan ke pesan terkirim akan dihapus.", - "discardAttachmentsTitle": "Buang lampiran?" + "discardAttachmentsTitle": "Buang lampiran?", + "charactersRemaining": "{{count}} karakter tersisa", + "insertNewline": "Sisipkan baris baru", + "starterChipBuild": "Buat fitur", + "starterChipEmpty": "Mulai dengan ide", + "starterChipFix": "Perbaiki kesalahan", + "starterChipPrepare": "Siapkan lingkungan saya", + "starterChipWrapUp": "Akhiri sesi saya", + "starterChipWriteTests": "Tulis pengujian" }, "chatLink": { "linkCopied": "Tautan disalin", diff --git a/apps/mobile/src/i18n/locales/ig.json b/apps/mobile/src/i18n/locales/ig.json index d4ec33271c..b0cf531424 100644 --- a/apps/mobile/src/i18n/locales/ig.json +++ b/apps/mobile/src/i18n/locales/ig.json @@ -152,7 +152,9 @@ "appearanceDark": "Ọchịchịrị", "account": "Akaụntụ", "prReviewAttribution": "Tinye nzere ngwa na nyocha PR", - "prReviewAttributionSubtitle": "Gbakwunye nkebi 'Reviewed via Kilo' n'ala mgbe ị bipụtara nyocha." + "prReviewAttributionSubtitle": "Gbakwunye nkebi 'Reviewed via Kilo' n'ala mgbe ị bipụtara nyocha.", + "returnSendsMessage": "Igodo nloghachi na-eziga ozi", + "returnSendsMessageSubtitle": "Mgbe agbanyụrụ ya, igodo nloghachi na-etinye ahịrị ọhụrụ n'ebe ntinye ozi ndị ọrụ." }, "prReview": { "pullRequestUnavailable": "Arịrịọ dọrọ adịghị", @@ -2589,7 +2591,15 @@ "cannotReceiveFiles": "Nnọkọ a enweghị ike ịnata faịlụ. Wepụ mgbakwunye ndị ahụ ka iziga ozi gị.", "couldNotAttachFiles": "Enweghị ike itinye faịlụ. Pịa ziga ka ị nwaa ọzọ.", "discardAttachmentsMessage": "A ga-ehichapụ faịlụ ebugotere ndị a na-ejikọtaghị na ozi ezigara.", - "discardAttachmentsTitle": "Tụfuo mgbakwunye?" + "discardAttachmentsTitle": "Tụfuo mgbakwunye?", + "charactersRemaining": "Mkpụrụedemede {{count}} fọdụrụ", + "insertNewline": "Tinye ahịrị ọhụrụ", + "starterChipBuild": "Mepụta atụmatụ", + "starterChipEmpty": "Malite na echiche", + "starterChipFix": "Dozie njehie", + "starterChipPrepare": "Hazie gburugburu m", + "starterChipWrapUp": "Mechie oge nnọkọ m na ngwa", + "starterChipWriteTests": "Dee nnwale" }, "chatLink": { "linkCopied": "Ederela njikọ", diff --git a/apps/mobile/src/i18n/locales/is.json b/apps/mobile/src/i18n/locales/is.json index e4ccfa975f..41a365ed4e 100644 --- a/apps/mobile/src/i18n/locales/is.json +++ b/apps/mobile/src/i18n/locales/is.json @@ -152,7 +152,9 @@ "appearanceDark": "Dökkt", "account": "Reikningur", "prReviewAttribution": "Bæta við app-eignarmerki í PR-úttektir", - "prReviewAttributionSubtitle": "Bæta við „Reviewed via Kilo“-fótlið þegar þú skilar úttekt." + "prReviewAttributionSubtitle": "Bæta við „Reviewed via Kilo“-fótlið þegar þú skilar úttekt.", + "returnSendsMessage": "Færslulykill sendir skilaboð", + "returnSendsMessageSubtitle": "Þegar slökkt er setur færslulykillinn inn línuskil í ritlum umboða." }, "prReview": { "pullRequestUnavailable": "Pull request ótiltækur", @@ -2589,7 +2591,15 @@ "cannotReceiveFiles": "Þessi lota getur ekki tekið við skrám. Fjarlægðu viðhengin til að senda skilaboðin.", "couldNotAttachFiles": "Ekki tókst að hengja skrár við. Bankaðu á senda til að reyna aftur.", "discardAttachmentsMessage": "Upphlöðnum skrám sem ekki fylgja sendum skilaboðum verður eytt.", - "discardAttachmentsTitle": "Farga viðhengjum?" + "discardAttachmentsTitle": "Farga viðhengjum?", + "charactersRemaining": "{{count}} stafir eftir", + "insertNewline": "Setja inn línuskil", + "starterChipBuild": "Búa til eiginleika", + "starterChipEmpty": "Byrja með hugmynd", + "starterChipFix": "Laga villu", + "starterChipPrepare": "Setja upp umhverfið mitt", + "starterChipWrapUp": "Ljúka lotunni minni", + "starterChipWriteTests": "Skrifa próf" }, "chatLink": { "linkCopied": "Krækja afrituð", diff --git a/apps/mobile/src/i18n/locales/it.json b/apps/mobile/src/i18n/locales/it.json index 69fda127dd..9dd438e667 100644 --- a/apps/mobile/src/i18n/locales/it.json +++ b/apps/mobile/src/i18n/locales/it.json @@ -2131,7 +2131,15 @@ "cannotReceiveFiles": "Questa sessione non può ricevere file. Rimuovi gli allegati per inviare il messaggio.", "couldNotAttachFiles": "Impossibile allegare i file. Tocca Invia per riprovare.", "discardAttachmentsMessage": "I file caricati non allegati a messaggi inviati verranno eliminati.", - "discardAttachmentsTitle": "Scartare gli allegati?" + "discardAttachmentsTitle": "Scartare gli allegati?", + "charactersRemaining": "{{count}} caratteri rimanenti", + "insertNewline": "Inserisci un'interruzione di riga", + "starterChipBuild": "Crea una funzionalità", + "starterChipEmpty": "Inizia con un'idea", + "starterChipFix": "Correggi un errore", + "starterChipPrepare": "Configura il mio ambiente", + "starterChipWrapUp": "Concludi la mia sessione", + "starterChipWriteTests": "Scrivi test" }, "chatLink": { "linkCopied": "Link copiato", @@ -2261,7 +2269,9 @@ "appearanceDark": "Scuro", "account": "Account", "prReviewAttribution": "Aggiungi l'attribuzione dell'app alle revisioni PR", - "prReviewAttributionSubtitle": "Aggiungi un footer \"Recensito tramite Kilo\" quando invii una recensione." + "prReviewAttributionSubtitle": "Aggiungi un footer \"Recensito tramite Kilo\" quando invii una recensione.", + "returnSendsMessage": "Il tasto Invio invia il messaggio", + "returnSendsMessageSubtitle": "Se disattivato, Invio inserisce un'interruzione di riga nei campi di inserimento dei messaggi degli agenti." }, "addCredits": { "cta": "Aggiungi crediti", diff --git a/apps/mobile/src/i18n/locales/ja.json b/apps/mobile/src/i18n/locales/ja.json index 846a07490c..066c365ba5 100644 --- a/apps/mobile/src/i18n/locales/ja.json +++ b/apps/mobile/src/i18n/locales/ja.json @@ -152,7 +152,9 @@ "appearanceDark": "ダーク", "account": "アカウント", "prReviewAttribution": "PRレビューにアプリの帰属を追加", - "prReviewAttributionSubtitle": "レビューを送信すると、Reviewed via Kiloフッターが追加されます。" + "prReviewAttributionSubtitle": "レビューを送信すると、Reviewed via Kiloフッターが追加されます。", + "returnSendsMessage": "リターンキーでメッセージを送信", + "returnSendsMessageSubtitle": "オフの場合、リターンキーでエージェントのメッセージ入力欄に改行を挿入します。" }, "notifications": { "title": "通知", @@ -2135,7 +2137,15 @@ "cannotReceiveFiles": "このセッションはファイルを受信できません。メッセージを送信するには添付ファイルを削除してください。", "couldNotAttachFiles": "ファイルを添付できませんでした。再試行するには送信をタップしてください。", "discardAttachmentsMessage": "送信済みメッセージに添付されていないアップロード済みファイルは削除されます。", - "discardAttachmentsTitle": "添付ファイルを破棄しますか?" + "discardAttachmentsTitle": "添付ファイルを破棄しますか?", + "charactersRemaining": "残り{{count}}文字", + "insertNewline": "改行を挿入", + "starterChipBuild": "機能を作成", + "starterChipEmpty": "アイデアから開始", + "starterChipFix": "バグを修正", + "starterChipPrepare": "環境をセットアップ", + "starterChipWrapUp": "セッションをまとめる", + "starterChipWriteTests": "テストを作成" }, "chatLink": { "linkCopied": "リンクをコピーしました", diff --git a/apps/mobile/src/i18n/locales/ka.json b/apps/mobile/src/i18n/locales/ka.json index 1455184a48..59d7fae7ed 100644 --- a/apps/mobile/src/i18n/locales/ka.json +++ b/apps/mobile/src/i18n/locales/ka.json @@ -152,7 +152,9 @@ "appearanceDark": "მუქი", "account": "ანგარიში", "prReviewAttribution": "აპლიკაციის მითითების დამატება PR რეცენზიებში", - "prReviewAttributionSubtitle": "დაამატეთ „განხილულია Kilo-ს მეშვეობით“ ქვედა წარწერა, როცა რეცენზიას გაგზავნით." + "prReviewAttributionSubtitle": "დაამატეთ „განხილულია Kilo-ს მეშვეობით“ ქვედა წარწერა, როცა რეცენზიას გაგზავნით.", + "returnSendsMessage": "დაბრუნების კლავიში აგზავნის შეტყობინებას", + "returnSendsMessageSubtitle": "როცა გამორთულია, დაბრუნების კლავიში აგენტის შეტყობინების შეყვანის ველებში ახალ ხაზს ამატებს." }, "prReview": { "pullRequestUnavailable": "პულ-რექვესთი მიუწვდომელია", @@ -2589,7 +2591,15 @@ "cannotReceiveFiles": "ამ სესიას ფაილების მიღება არ შეუძლია. შეტყობინების გასაგზავნად ამოიღეთ დანართები.", "couldNotAttachFiles": "ფაილების მიმაგრება ვერ მოხერხდა. ხელახლა სცადეთ.", "discardAttachmentsMessage": "აიტვირთული ფაილები, რომლებიც გაგზავნილ შეტყობინებებს არ ერთვის, წაიშლება.", - "discardAttachmentsTitle": "გავაუქმოთ დანართები?" + "discardAttachmentsTitle": "გავაუქმოთ დანართები?", + "charactersRemaining": "დარჩენილია {{count}} სიმბოლო", + "insertNewline": "ახალი ხაზის ჩასმა", + "starterChipBuild": "შექმენით ფუნქცია", + "starterChipEmpty": "დაიწყეთ იდეით", + "starterChipFix": "შეასწორეთ შეცდომა", + "starterChipPrepare": "გამართეთ ჩემი გარემო", + "starterChipWrapUp": "დაასრულეთ ჩემი სესია", + "starterChipWriteTests": "დაწერეთ ტესტები" }, "chatLink": { "linkCopied": "ბმული დაკოპირდა", diff --git a/apps/mobile/src/i18n/locales/kk.json b/apps/mobile/src/i18n/locales/kk.json index fe419187aa..a34eb094cc 100644 --- a/apps/mobile/src/i18n/locales/kk.json +++ b/apps/mobile/src/i18n/locales/kk.json @@ -152,7 +152,9 @@ "appearanceDark": "Қараңғы", "account": "Тіркелгі", "prReviewAttribution": "PR рецензияларына қосымшаның атрибуциясын қосу", - "prReviewAttributionSubtitle": "Рецензия жібергенде төменге Reviewed via Kilo жолын қосыңыз." + "prReviewAttributionSubtitle": "Рецензия жібергенде төменге Reviewed via Kilo жолын қосыңыз.", + "returnSendsMessage": "Қайтару пернесі хабарламаны жібереді", + "returnSendsMessageSubtitle": "Өшірулі кезде Қайтару пернесі агенттердің хабарлама енгізу өрістеріне жаңа жол енгізеді." }, "prReview": { "pullRequestUnavailable": "Pull request қолжетімсіз", @@ -2589,7 +2591,15 @@ "cannotReceiveFiles": "Бұл сессия файл қабылдай алмайды. Хабарлама жіберу үшін қосымшаларды алып тастаңыз.", "couldNotAttachFiles": "Файлдарды қосу мүмкін болмады. Қайталау үшін жіберуді түртіңіз.", "discardAttachmentsMessage": "Жіберілген хабарламаларға қосылмаған жүктелген файлдар жойылады.", - "discardAttachmentsTitle": "Қосымшаларды жою керек пе?" + "discardAttachmentsTitle": "Қосымшаларды жою керек пе?", + "charactersRemaining": "{{count}} таңба қалды", + "insertNewline": "Жаңа жол енгізу", + "starterChipBuild": "Мүмкіндік жасау", + "starterChipEmpty": "Идеядан бастау", + "starterChipFix": "Қатені түзету", + "starterChipPrepare": "Ортамды баптау", + "starterChipWrapUp": "Сессиямды аяқтау", + "starterChipWriteTests": "Тесттер жазу" }, "chatLink": { "linkCopied": "Сілтеме көшірілді", diff --git a/apps/mobile/src/i18n/locales/km.json b/apps/mobile/src/i18n/locales/km.json index 6141194567..af63971fa5 100644 --- a/apps/mobile/src/i18n/locales/km.json +++ b/apps/mobile/src/i18n/locales/km.json @@ -152,7 +152,9 @@ "appearanceDark": "ងងឹត", "account": "គណនី", "prReviewAttribution": "បន្ថែមការបញ្ជាក់កម្មវិធីទៅការពិនិត្យ PR", - "prReviewAttributionSubtitle": "បន្ថែមបាតកថា Reviewed via Kilo នៅពេលអ្នកដាក់ស្នើការពិនិត្យ។" + "prReviewAttributionSubtitle": "បន្ថែមបាតកថា Reviewed via Kilo នៅពេលអ្នកដាក់ស្នើការពិនិត្យ។", + "returnSendsMessage": "គ្រាប់ចុចបញ្ចូលផ្ញើសារ", + "returnSendsMessageSubtitle": "នៅពេលបិទ គ្រាប់ចុចបញ្ចូលនឹងបញ្ចូលបន្ទាត់ថ្មីក្នុងអ្នកតែងរបស់ភ្នាក់ងារ។" }, "prReview": { "pullRequestUnavailable": "សំណើទាញ (pull request) មិនអាចប្រើបានទេ", @@ -2589,7 +2591,15 @@ "cannotReceiveFiles": "វគ្គនេះមិនអាចទទួលឯកសារបានទេ។ ដកឯកសារភ្ជាប់ចេញដើម្បីផ្ញើសាររបស់អ្នក។", "couldNotAttachFiles": "មិនអាចភ្ជាប់ឯកសារបានទេ។ ប៉ះផ្ញើដើម្បីព្យាយាមម្តងទៀត។", "discardAttachmentsMessage": "ឯកសារដែលបានផ្ទុកឡើង ហើយមិនបានភ្ជាប់ទៅសារដែលបានផ្ញើ នឹងត្រូវបានលុប។", - "discardAttachmentsTitle": "បោះបង់ឯកសារភ្ជាប់?" + "discardAttachmentsTitle": "បោះបង់ឯកសារភ្ជាប់?", + "charactersRemaining": "នៅសល់ {{count}} តួអក្សរ", + "insertNewline": "បញ្ចូលបន្ទាត់ថ្មី", + "starterChipBuild": "បង្កើតមុខងារ", + "starterChipEmpty": "ចាប់ផ្តើមជាមួយគំនិត", + "starterChipFix": "ជួសជុលកំហុស", + "starterChipPrepare": "រៀបចំបរិស្ថានរបស់ខ្ញុំ", + "starterChipWrapUp": "បញ្ចប់វគ្គរបស់ខ្ញុំ", + "starterChipWriteTests": "សរសេរការធ្វើតេស្ត" }, "chatLink": { "linkCopied": "បានចម្លងតំណ", diff --git a/apps/mobile/src/i18n/locales/kn.json b/apps/mobile/src/i18n/locales/kn.json index 79bd3a1023..9826d3dad0 100644 --- a/apps/mobile/src/i18n/locales/kn.json +++ b/apps/mobile/src/i18n/locales/kn.json @@ -152,7 +152,9 @@ "appearanceDark": "ಡಾರ್ಕ್", "account": "ಖಾತೆ", "prReviewAttribution": "PR ವಿಮರ್ಶೆಗಳಿಗೆ ಅಪ್ಲಿಕೇಶನ್ ಗುಣಲಕ್ಷಣವನ್ನು ಸೇರಿಸಿ", - "prReviewAttributionSubtitle": "ನೀವು ವಿಮರ್ಶೆ ಸಲ್ಲಿಸಿದಾಗ Kilo ಮೂಲಕ ವಿಮರ್ಶಿಸಲಾಗಿದೆ ಫೂಟರ್ ಅನ್ನು ಸೇರಿಸಿ." + "prReviewAttributionSubtitle": "ನೀವು ವಿಮರ್ಶೆ ಸಲ್ಲಿಸಿದಾಗ Kilo ಮೂಲಕ ವಿಮರ್ಶಿಸಲಾಗಿದೆ ಫೂಟರ್ ಅನ್ನು ಸೇರಿಸಿ.", + "returnSendsMessage": "ರಿಟರ್ನ್ ಕೀ ಸಂದೇಶವನ್ನು ಕಳುಹಿಸುತ್ತದೆ", + "returnSendsMessageSubtitle": "ಆಫ್ ಆಗಿದ್ದಾಗ, ರಿಟರ್ನ್ ಕೀ ಏಜೆಂಟ್ ಸಂಯೋಜಕಗಳಲ್ಲಿ ಹೊಸ ಸಾಲನ್ನು ಸೇರಿಸುತ್ತದೆ." }, "prReview": { "pullRequestUnavailable": "ಪುಲ್ ರಿಕ್ವೆಸ್ಟ್ ಲಭ್ಯವಿಲ್ಲ", @@ -2589,7 +2591,15 @@ "cannotReceiveFiles": "ಈ ಸೆಷನ್ ಫೈಲ್‌ಗಳನ್ನು ಸ್ವೀಕರಿಸಲು ಸಾಧ್ಯವಿಲ್ಲ. ನಿಮ್ಮ ಸಂದೇಶವನ್ನು ಕಳುಹಿಸಲು ಲಗತ್ತುಗಳನ್ನು ತೆಗೆದುಹಾಕಿ.", "couldNotAttachFiles": "ಫೈಲ್‌ಗಳನ್ನು ಲಗತ್ತಿಸಲು ಸಾಧ್ಯವಾಗಲಿಲ್ಲ. ಮತ್ತೆ ಪ್ರಯತ್ನಿಸಲು ಕಳುಹಿಸಿ ಟ್ಯಾಪ್ ಮಾಡಿ.", "discardAttachmentsMessage": "ಕಳುಹಿಸಿದ ಸಂದೇಶಗಳಿಗೆ ಲಗತ್ತಿಸದ ಅಪ್‌ಲೋಡ್‌ಗಳನ್ನು ಅಳಿಸಲಾಗುತ್ತದೆ.", - "discardAttachmentsTitle": "ಲಗತ್ತುಗಳನ್ನು ತೆಗೆದುಹಾಕಬೇಕೆ?" + "discardAttachmentsTitle": "ಲಗತ್ತುಗಳನ್ನು ತೆಗೆದುಹಾಕಬೇಕೆ?", + "charactersRemaining": "{{count}} ಅಕ್ಷರಗಳು ಉಳಿದಿವೆ", + "insertNewline": "ಹೊಸ ಸಾಲನ್ನು ಸೇರಿಸಿ", + "starterChipBuild": "ವೈಶಿಷ್ಟ್ಯವನ್ನು ನಿರ್ಮಿಸಿ", + "starterChipEmpty": "ಒಂದು ಆಲೋಚನೆಯೊಂದಿಗೆ ಪ್ರಾರಂಭಿಸಿ", + "starterChipFix": "ದೋಷವನ್ನು ಸರಿಪಡಿಸಿ", + "starterChipPrepare": "ನನ್ನ ಪರಿಸರವನ್ನು ಹೊಂದಿಸಿ", + "starterChipWrapUp": "ನನ್ನ ಸೆಶನ್ ಮುಕ್ತಾಯಗೊಳಿಸಿ", + "starterChipWriteTests": "ಪರೀಕ್ಷೆಗಳನ್ನು ಬರೆಯಿರಿ" }, "chatLink": { "linkCopied": "ಲಿಂಕ್ ನಕಲಿಸಲಾಗಿದೆ", diff --git a/apps/mobile/src/i18n/locales/ko.json b/apps/mobile/src/i18n/locales/ko.json index 82c75402f7..c02c591379 100644 --- a/apps/mobile/src/i18n/locales/ko.json +++ b/apps/mobile/src/i18n/locales/ko.json @@ -152,7 +152,9 @@ "appearanceDark": "어두움", "account": "계정", "prReviewAttribution": "PR 리뷰에 앱 표시 정보 추가", - "prReviewAttributionSubtitle": "리뷰를 제출할 때 Reviewed via Kilo 푸터를 추가합니다." + "prReviewAttributionSubtitle": "리뷰를 제출할 때 Reviewed via Kilo 푸터를 추가합니다.", + "returnSendsMessage": "리턴 키로 메시지 보내기", + "returnSendsMessageSubtitle": "끄면 에이전트의 메시지 입력란에서 리턴 키로 줄바꿈을 삽입합니다." }, "notifications": { "title": "알림", @@ -2135,7 +2137,15 @@ "cannotReceiveFiles": "이 세션은 파일을 받을 수 없습니다. 메시지를 보내려면 첨부 파일을 제거하세요.", "couldNotAttachFiles": "파일을 첨부하지 못했습니다. 다시 시도하려면 보내기를 탭하세요.", "discardAttachmentsMessage": "보낸 메시지에 첨부되지 않은 업로드 파일이 삭제됩니다.", - "discardAttachmentsTitle": "첨부 파일을 삭제할까요?" + "discardAttachmentsTitle": "첨부 파일을 삭제할까요?", + "charactersRemaining": "{{count}}자 남음", + "insertNewline": "줄바꿈 삽입", + "starterChipBuild": "기능 구현", + "starterChipEmpty": "아이디어로 시작", + "starterChipFix": "버그 수정", + "starterChipPrepare": "내 환경 설정", + "starterChipWrapUp": "내 세션 마무리", + "starterChipWriteTests": "테스트 작성" }, "chatLink": { "linkCopied": "링크 복사됨", diff --git a/apps/mobile/src/i18n/locales/lo.json b/apps/mobile/src/i18n/locales/lo.json index 0c851b5c08..6fd95c59f7 100644 --- a/apps/mobile/src/i18n/locales/lo.json +++ b/apps/mobile/src/i18n/locales/lo.json @@ -152,7 +152,9 @@ "appearanceDark": "ມືດ", "account": "ບັນຊີ", "prReviewAttribution": "ຕື່ມການລະບຸແຫຼ່ງຂອງແອັບລົງໃນການທົບທວນ PR", - "prReviewAttributionSubtitle": "ຕື່ມ footer ທີ່ລະບຸວ່າ \"ທົບທວນຜ່ານ Kilo\" ເມື່ອທ່ານສົ່ງການທົບທວນ." + "prReviewAttributionSubtitle": "ຕື່ມ footer ທີ່ລະບຸວ່າ \"ທົບທວນຜ່ານ Kilo\" ເມື່ອທ່ານສົ່ງການທົບທວນ.", + "returnSendsMessage": "ປຸ່ມຂຶ້ນແຖວໃໝ່ສົ່ງຂໍ້ຄວາມ", + "returnSendsMessageSubtitle": "ເມື່ອປິດ, ປຸ່ມຂຶ້ນແຖວໃໝ່ຈະແຊກແຖວໃໝ່ໃນຊ່ອງປ້ອນຂໍ້ຄວາມຂອງຕົວແທນ." }, "prReview": { "pullRequestUnavailable": "pull request ບໍ່ພ້ອມໃຊ້", @@ -2589,7 +2591,15 @@ "cannotReceiveFiles": "ເຊດຊັນນີ້ບໍ່ສາມາດຮັບໄຟລ໌. ລຶບໄຟລ໌ແນບອອກເພື່ອສົ່ງຂໍ້ຄວາມ.", "couldNotAttachFiles": "ບໍ່ສາມາດແນບໄຟລ໌. ແຕະສົ່ງເພື່ອລອງອີກ.", "discardAttachmentsMessage": "ໄຟລ໌ທີ່ອັບໂຫລດແຕ່ບໍ່ໄດ້ແນບກັບຂໍ້ຄວາມທີ່ສົ່ງແລ້ວຈະຖືກລຶບ.", - "discardAttachmentsTitle": "ຍົກເລີກໄຟລ໌ແນບ?" + "discardAttachmentsTitle": "ຍົກເລີກໄຟລ໌ແນບ?", + "charactersRemaining": "ເຫຼືອ {{count}} ຕົວອັກສອນ", + "insertNewline": "ແຊກແຖວໃໝ່", + "starterChipBuild": "ສ້າງຄຸນສົມບັດ", + "starterChipEmpty": "ເລີ່ມດ້ວຍແນວຄິດ", + "starterChipFix": "ແກ້ໄຂຂໍ້ຜິດພາດ", + "starterChipPrepare": "ຕັ້ງສະພາບແວດລ້ອມຂອງຂ້ອຍ", + "starterChipWrapUp": "ສິ້ນສຸດເຊດຊັນຂອງຂ້ອຍ", + "starterChipWriteTests": "ຂຽນການທົດສອບ" }, "chatLink": { "linkCopied": "ສຳເນົາລິ້ງແລ້ວ", diff --git a/apps/mobile/src/i18n/locales/lt.json b/apps/mobile/src/i18n/locales/lt.json index 81e9424c2d..48af32d676 100644 --- a/apps/mobile/src/i18n/locales/lt.json +++ b/apps/mobile/src/i18n/locales/lt.json @@ -152,7 +152,9 @@ "appearanceDark": "Tamsi", "account": "Paskyra", "prReviewAttribution": "Pridėti programėlės atribuciją prie PR peržiūrų", - "prReviewAttributionSubtitle": "Kai pateikiate peržiūrą, apačioje pridėkite poraštę „Peržiūrėta per Kilo“." + "prReviewAttributionSubtitle": "Kai pateikiate peržiūrą, apačioje pridėkite poraštę „Peržiūrėta per Kilo“.", + "returnSendsMessage": "Grįžimo klavišas siunčia žinutę", + "returnSendsMessageSubtitle": "Kai išjungta, grįžimo klavišas įterpia naują eilutę agentų žinučių įvesties laukuose." }, "prReview": { "pullRequestUnavailable": "Pull request nepasiekiamas", @@ -2627,7 +2629,15 @@ "cannotReceiveFiles": "Ši sesija negali gauti failų. Pašalinkite priedus, kad išsiųstumėte žinutę.", "couldNotAttachFiles": "Nepavyko pridėti failų. Bakstelėkite siųsti, kad bandytumėte dar kartą.", "discardAttachmentsMessage": "Prie išsiųstų žinučių nepridėtos įkeltos rinkmenos bus ištrintos.", - "discardAttachmentsTitle": "Atmesti priedus?" + "discardAttachmentsTitle": "Atmesti priedus?", + "charactersRemaining": "Liko simbolių: {{count}}", + "insertNewline": "Įterpti naują eilutę", + "starterChipBuild": "Sukurti funkciją", + "starterChipEmpty": "Pradėti nuo idėjos", + "starterChipFix": "Ištaisyti klaidą", + "starterChipPrepare": "Paruošti mano aplinką", + "starterChipWrapUp": "Užbaigti mano sesiją", + "starterChipWriteTests": "Parašyti testus" }, "chatLink": { "linkCopied": "Nuoroda nukopijuota", diff --git a/apps/mobile/src/i18n/locales/lv.json b/apps/mobile/src/i18n/locales/lv.json index d4257924a2..1fdaa3d77b 100644 --- a/apps/mobile/src/i18n/locales/lv.json +++ b/apps/mobile/src/i18n/locales/lv.json @@ -152,7 +152,9 @@ "appearanceDark": "Tumšs", "account": "Konts", "prReviewAttribution": "Pievienot lietotnes attiecinājumu PR pārskatiem", - "prReviewAttributionSubtitle": "Pievienot \"Reviewed via Kilo\" kājeni, kad iesniedzat pārskatu." + "prReviewAttributionSubtitle": "Pievienot \"Reviewed via Kilo\" kājeni, kad iesniedzat pārskatu.", + "returnSendsMessage": "Ievades taustiņš nosūta ziņojumu", + "returnSendsMessageSubtitle": "Ja izslēgts, ievades taustiņš ievieto jaunu rindu aģenta ziņojuma ievades laukā." }, "prReview": { "pullRequestUnavailable": "Pull request nav pieejams", @@ -2608,7 +2610,15 @@ "cannotReceiveFiles": "Šī sesija nevar saņemt failus. Noņem pielikumus, lai nosūtītu savu ziņojumu.", "couldNotAttachFiles": "Neizdevās pievienot failus. Pieskarieties Sūtīt, lai mēģinātu vēlreiz.", "discardAttachmentsMessage": "Augšupielādētie faili, kas nav pievienoti nosūtītiem ziņojumiem, tiks dzēsti.", - "discardAttachmentsTitle": "Atmest pielikumus?" + "discardAttachmentsTitle": "Atmest pielikumus?", + "charactersRemaining": "Atlikušās rakstzīmes: {{count}}", + "insertNewline": "Ievietot jaunu rindu", + "starterChipBuild": "Izveidot funkciju", + "starterChipEmpty": "Sākt ar ideju", + "starterChipFix": "Izlabot kļūdu", + "starterChipPrepare": "Iestatīt manu vidi", + "starterChipWrapUp": "Pabeigt manu sesiju", + "starterChipWriteTests": "Rakstīt testus" }, "chatLink": { "linkCopied": "Saite kopēta", diff --git a/apps/mobile/src/i18n/locales/mg.json b/apps/mobile/src/i18n/locales/mg.json index 1841b55a56..408eaba91b 100644 --- a/apps/mobile/src/i18n/locales/mg.json +++ b/apps/mobile/src/i18n/locales/mg.json @@ -152,7 +152,9 @@ "appearanceDark": "Maizina", "account": "Kaonty", "prReviewAttribution": "Ampio ny attribution ny app amin'ny fandinihana PR", - "prReviewAttributionSubtitle": "Asio footer \"Reviewed via Kilo\" rehefa mandefa fandinihana ianao." + "prReviewAttributionSubtitle": "Asio footer \"Reviewed via Kilo\" rehefa mandefa fandinihana ianao.", + "returnSendsMessage": "Mandefa hafatra ny bokotra Fiverenana", + "returnSendsMessageSubtitle": "Rehefa tsy alefa, ny bokotra Fiverenana dia mampiditra andalana vaovao ao amin'ny fampidirana hafatry ny mpanampy." }, "prReview": { "pullRequestUnavailable": "Tsy misy ny pull request", @@ -2589,7 +2591,15 @@ "cannotReceiveFiles": "Tsy afaka mandray rakitra ity session ity. Esory ny rakitra vao alefaso ny hafatrao.", "couldNotAttachFiles": "Tsy afaka nampidirina ny rakitra. Tsindrio ny send raha handramana indray.", "discardAttachmentsMessage": "Hofafana ny rakitra nampidirina nefa tsy nampiarahina tamin'ny hafatra nalefa.", - "discardAttachmentsTitle": "Ario ve ny firaiketana?" + "discardAttachmentsTitle": "Ario ve ny firaiketana?", + "charactersRemaining": "Soratra {{count}} sisa", + "insertNewline": "Ampidiro andalana vaovao", + "starterChipBuild": "Mamoròna fiasa", + "starterChipEmpty": "Atombohy amin'ny hevitra iray", + "starterChipFix": "Ahitsio ny hadisoana", + "starterChipPrepare": "Amboary ny tontolo iainako", + "starterChipWrapUp": "Farano ny session-ko", + "starterChipWriteTests": "Manorata fitsapana" }, "chatLink": { "linkCopied": "Adika ny rohy", diff --git a/apps/mobile/src/i18n/locales/mi.json b/apps/mobile/src/i18n/locales/mi.json index 3eee173363..986c2f7842 100644 --- a/apps/mobile/src/i18n/locales/mi.json +++ b/apps/mobile/src/i18n/locales/mi.json @@ -152,7 +152,9 @@ "appearanceDark": "Pōuri", "account": "Pūkete", "prReviewAttribution": "Tāpiri te tautohu taupānga ki ngā arotake PR", - "prReviewAttributionSubtitle": "Tāpiri he hiku 'Kua arotakea mā Kilo' ina tuku koe i tētahi arotake." + "prReviewAttributionSubtitle": "Tāpiri he hiku 'Kua arotakea mā Kilo' ina tuku koe i tētahi arotake.", + "returnSendsMessage": "Ka tuku karere te pātuhi Whakahoki", + "returnSendsMessageSubtitle": "Ina weto, ka whakauru te pātuhi Whakahoki i tētahi rārangi hōu ki ngā kaitito māngai." }, "prReview": { "pullRequestUnavailable": "Kāore i te wātea te tono huna", @@ -2589,7 +2591,15 @@ "cannotReceiveFiles": "Kāore e taea te whiwhi kōnae i tēnei wātū. Tangohia ngā whakapirihanga kia tukua tō karere.", "couldNotAttachFiles": "Kāore i taea te whakapiri kōnae. Pāwhiri ki te tuku kia whakamātau anō.", "discardAttachmentsMessage": "Ka mukua ngā kōnae kua tukua ake kāore anō kia whakapirihia ki ngā karere kua tukua.", - "discardAttachmentsTitle": "Whakarere i ngā whakapirihanga?" + "discardAttachmentsTitle": "Whakarere i ngā whakapirihanga?", + "charactersRemaining": "E {{count}} ngā pū e toe ana", + "insertNewline": "Whakauru rārangi hōu", + "starterChipBuild": "Hangaia he āhuatanga", + "starterChipEmpty": "Tīmata ki tētahi whakaaro", + "starterChipFix": "Whakatikahia he hapa", + "starterChipPrepare": "Whakaritea taku taiao", + "starterChipWrapUp": "Whakakapihia taku wātūtū", + "starterChipWriteTests": "Tuhia ngā whakamātau" }, "chatLink": { "linkCopied": "Kua tāruatia te hononga", diff --git a/apps/mobile/src/i18n/locales/mk.json b/apps/mobile/src/i18n/locales/mk.json index 2cb26610a9..1240e310c3 100644 --- a/apps/mobile/src/i18n/locales/mk.json +++ b/apps/mobile/src/i18n/locales/mk.json @@ -152,7 +152,9 @@ "appearanceDark": "Темно", "account": "Сметка", "prReviewAttribution": "Додајте припишување на апликацијата во прегледите на PR", - "prReviewAttributionSubtitle": "Додајте потпис „Прегледано преку Kilo“ кога поднесувате преглед." + "prReviewAttributionSubtitle": "Додајте потпис „Прегледано преку Kilo“ кога поднесувате преглед.", + "returnSendsMessage": "Копчето за нов ред испраќа порака", + "returnSendsMessageSubtitle": "Кога е исклучено, копчето за нов ред вметнува нов ред во полињата за внес на пораки на агентите." }, "prReview": { "pullRequestUnavailable": "Барањето за повлекување е недостапно", @@ -2589,7 +2591,15 @@ "cannotReceiveFiles": "Оваа сесија не може да прима датотеки. Отстранете ги прилозите за да ја испратите пораката.", "couldNotAttachFiles": "Не можев да прикачам датотеки. Допрете испрати за да се обидете повторно.", "discardAttachmentsMessage": "Прикачените датотеки што не се приложени кон испратени пораки ќе бидат избришани.", - "discardAttachmentsTitle": "Да се отфрлат прилозите?" + "discardAttachmentsTitle": "Да се отфрлат прилозите?", + "charactersRemaining": "Преостануваат {{count}} знаци", + "insertNewline": "Вметнете нов ред", + "starterChipBuild": "Изградете функционалност", + "starterChipEmpty": "Започнете со идеја", + "starterChipFix": "Поправете грешка", + "starterChipPrepare": "Поставете ја мојата околина", + "starterChipWrapUp": "Завршете ја мојата сесија", + "starterChipWriteTests": "Напишете тестови" }, "chatLink": { "linkCopied": "Линкот е копиран", diff --git a/apps/mobile/src/i18n/locales/ml.json b/apps/mobile/src/i18n/locales/ml.json index 0afd97baf0..4ef5937bec 100644 --- a/apps/mobile/src/i18n/locales/ml.json +++ b/apps/mobile/src/i18n/locales/ml.json @@ -152,7 +152,9 @@ "appearanceDark": "ഡാർക്ക്", "account": "അക്കൗണ്ട്", "prReviewAttribution": "PR അവലോകനങ്ങളിലേക്ക് ആപ്പ് ആട്രിബ്യൂഷൻ ചേർക്കുക", - "prReviewAttributionSubtitle": "നിങ്ങൾ ഒരു അവലോകനം സമർപ്പിക്കുമ്പോൾ Reviewed via Kilo ഫൂട്ടർ കൂട്ടിച്ചേർക്കുക." + "prReviewAttributionSubtitle": "നിങ്ങൾ ഒരു അവലോകനം സമർപ്പിക്കുമ്പോൾ Reviewed via Kilo ഫൂട്ടർ കൂട്ടിച്ചേർക്കുക.", + "returnSendsMessage": "റിട്ടേൺ കീ സന്ദേശം അയയ്ക്കുന്നു", + "returnSendsMessageSubtitle": "ഓഫായിരിക്കുമ്പോൾ, റിട്ടേൺ ഏജന്റ് സന്ദേശ ഇൻപുട്ടുകളിൽ പുതിയ വരി ചേർക്കുന്നു." }, "prReview": { "pullRequestUnavailable": "പുൾ റിക്വസ്റ്റ് ലഭ്യമല്ല", @@ -2589,7 +2591,15 @@ "cannotReceiveFiles": "ഈ സെഷന് ഫയലുകൾ സ്വീകരിക്കാൻ കഴിയില്ല. നിങ്ങളുടെ സന്ദേശം അയയ്ക്കാൻ അറ്റാച്ച്മെന്റുകൾ നീക്കം ചെയ്യുക.", "couldNotAttachFiles": "ഫയലുകൾ അറ്റാച്ച് ചെയ്യാൻ കഴിഞ്ഞില്ല. വീണ്ടും ശ്രമിക്കാൻ അയയ്ക്കുക ടാപ്പ് ചെയ്യുക.", "discardAttachmentsMessage": "അയച്ച സന്ദേശങ്ങളിൽ അറ്റാച്ച് ചെയ്യാത്ത അപ്‌ലോഡുകൾ ഇല്ലാതാക്കും.", - "discardAttachmentsTitle": "അറ്റാച്ച്മെന്റുകൾ ഉപേക്ഷിക്കണോ?" + "discardAttachmentsTitle": "അറ്റാച്ച്മെന്റുകൾ ഉപേക്ഷിക്കണോ?", + "charactersRemaining": "{{count}} അക്ഷരങ്ങൾ ശേഷിക്കുന്നു", + "insertNewline": "പുതിയ വരി ചേർക്കുക", + "starterChipBuild": "ഒരു ഫീച്ചർ നിർമ്മിക്കുക", + "starterChipEmpty": "ഒരു ആശയത്തോടെ ആരംഭിക്കുക", + "starterChipFix": "ഒരു ബഗ് പരിഹരിക്കുക", + "starterChipPrepare": "എന്റെ എൻവയോൺമെന്റ് സജ്ജീകരിക്കുക", + "starterChipWrapUp": "എന്റെ സെഷൻ പൂർത്തിയാക്കുക", + "starterChipWriteTests": "ടെസ്റ്റുകൾ എഴുതുക" }, "chatLink": { "linkCopied": "ലിങ്ക് പകർത്തി", diff --git a/apps/mobile/src/i18n/locales/mn.json b/apps/mobile/src/i18n/locales/mn.json index 7e24411d15..ecf46866e6 100644 --- a/apps/mobile/src/i18n/locales/mn.json +++ b/apps/mobile/src/i18n/locales/mn.json @@ -152,7 +152,9 @@ "appearanceDark": "Харанхуй", "account": "Бүртгэл", "prReviewAttribution": "PR хяналтуудад аппын хамааралыг нэмэх", - "prReviewAttributionSubtitle": "Үнэлгээ илгээхдээ 'Kilo-гаар хянасан' тайлбарыг нэмнэ." + "prReviewAttributionSubtitle": "Үнэлгээ илгээхдээ 'Kilo-гаар хянасан' тайлбарыг нэмнэ.", + "returnSendsMessage": "Мөр шилжүүлэх товч мессеж илгээнэ", + "returnSendsMessageSubtitle": "Унтраалттай үед мөр шилжүүлэх товч агентын мессеж оруулах талбарт шинэ мөр оруулна." }, "prReview": { "pullRequestUnavailable": "Pull request боломжгүй", @@ -2589,7 +2591,15 @@ "cannotReceiveFiles": "Энэ сесси файл хүлээн авах боломжгүй. Мэдээлэл илгээхийн тулд хавсралтуудыг арилгана уу.", "couldNotAttachFiles": "Файлуудыг хавсаргаж чадсангүй. Дахин оролдохын тулд илгээх товчийг дарна уу.", "discardAttachmentsMessage": "Илгээсэн мессежид хавсаргаагүй байршуулсан файлууд устах болно.", - "discardAttachmentsTitle": "Хавсралтуудыг хаях уу?" + "discardAttachmentsTitle": "Хавсралтуудыг хаях уу?", + "charactersRemaining": "{{count}} тэмдэгт үлдсэн", + "insertNewline": "Шинэ мөр оруулах", + "starterChipBuild": "Шинэ функц бүтээх", + "starterChipEmpty": "Санаанаас эхлэх", + "starterChipFix": "Алдаа засах", + "starterChipPrepare": "Орчноо тохируулах", + "starterChipWrapUp": "Сессээ дуусгах", + "starterChipWriteTests": "Тест бичих" }, "chatLink": { "linkCopied": "Холбоос хуулагдлаа", diff --git a/apps/mobile/src/i18n/locales/mr.json b/apps/mobile/src/i18n/locales/mr.json index 471f169279..13bb7e3f7e 100644 --- a/apps/mobile/src/i18n/locales/mr.json +++ b/apps/mobile/src/i18n/locales/mr.json @@ -152,7 +152,9 @@ "appearanceDark": "डार्क", "account": "खाते", "prReviewAttribution": "PR पुनरावलोकनांमध्ये अ‍ॅप श्रेय जोडा", - "prReviewAttributionSubtitle": "पुनरावलोकन सबमिट करताना Reviewed via Kilo फूटर जोडा" + "prReviewAttributionSubtitle": "पुनरावलोकन सबमिट करताना Reviewed via Kilo फूटर जोडा", + "returnSendsMessage": "रिटर्न कळ संदेश पाठवते", + "returnSendsMessageSubtitle": "बंद असताना, रिटर्न कळ एजंट कंपोझरमध्ये नवीन ओळ घालते." }, "prReview": { "pullRequestUnavailable": "पुल रिक्वेस्ट अनुपलब्ध", @@ -2589,7 +2591,15 @@ "cannotReceiveFiles": "या सत्राला फाइल्स मिळू शकत नाहीत. तुमचा संदेश पाठवण्यासाठी संलग्नता काढा.", "couldNotAttachFiles": "फाइल्स जोडता आल्या नाहीत. पुन्हा प्रयत्न करण्यासाठी पाठवा टॅप करा.", "discardAttachmentsMessage": "पाठवलेल्या संदेशांना संलग्न नसलेल्या अपलोड केलेल्या फाइल्स हटवल्या जातील.", - "discardAttachmentsTitle": "संलग्नता टाकून द्यायच्या?" + "discardAttachmentsTitle": "संलग्नता टाकून द्यायच्या?", + "charactersRemaining": "{{count}} अक्षरे शिल्लक", + "insertNewline": "नवीन ओळ घाला", + "starterChipBuild": "वैशिष्ट्य तयार करा", + "starterChipEmpty": "कल्पनेपासून सुरुवात करा", + "starterChipFix": "त्रुटी दुरुस्त करा", + "starterChipPrepare": "माझे वातावरण तयार करा", + "starterChipWrapUp": "माझे सत्र पूर्ण करा", + "starterChipWriteTests": "चाचण्या लिहा" }, "chatLink": { "linkCopied": "लिंक कॉपी केली", diff --git a/apps/mobile/src/i18n/locales/ms.json b/apps/mobile/src/i18n/locales/ms.json index c59066d7cb..0654dce1f1 100644 --- a/apps/mobile/src/i18n/locales/ms.json +++ b/apps/mobile/src/i18n/locales/ms.json @@ -152,7 +152,9 @@ "appearanceDark": "Gelap", "account": "Akaun", "prReviewAttribution": "Tambahkan atribusi aplikasi kepada semakan PR", - "prReviewAttributionSubtitle": "Tambahkan pengaki Reviewed via Kilo apabila anda menghantar semakan." + "prReviewAttributionSubtitle": "Tambahkan pengaki Reviewed via Kilo apabila anda menghantar semakan.", + "returnSendsMessage": "Kekunci Kembali menghantar mesej", + "returnSendsMessageSubtitle": "Apabila dimatikan, kekunci Kembali menyisipkan baris baharu dalam komposer ejen." }, "prReview": { "pullRequestUnavailable": "Permintaan tarik tidak tersedia", @@ -2589,7 +2591,15 @@ "cannotReceiveFiles": "Sesi ini tidak boleh menerima fail. Buang lampiran untuk menghantar mesej anda.", "couldNotAttachFiles": "Tidak dapat melampirkan fail. Ketik hantar untuk mencuba semula.", "discardAttachmentsMessage": "Fail yang dimuat naik tetapi tidak dilampirkan pada mesej yang dihantar akan dipadam.", - "discardAttachmentsTitle": "Buang lampiran?" + "discardAttachmentsTitle": "Buang lampiran?", + "charactersRemaining": "{{count}} aksara berbaki", + "insertNewline": "Sisipkan baris baharu", + "starterChipBuild": "Bina ciri", + "starterChipEmpty": "Mulakan dengan idea", + "starterChipFix": "Baiki pepijat", + "starterChipPrepare": "Sediakan persekitaran saya", + "starterChipWrapUp": "Tamatkan sesi saya", + "starterChipWriteTests": "Tulis ujian" }, "chatLink": { "linkCopied": "Pautan disalin", diff --git a/apps/mobile/src/i18n/locales/mt.json b/apps/mobile/src/i18n/locales/mt.json index b5d108c7ba..9f372d4362 100644 --- a/apps/mobile/src/i18n/locales/mt.json +++ b/apps/mobile/src/i18n/locales/mt.json @@ -152,7 +152,9 @@ "appearanceDark": "Skur", "account": "Kont", "prReviewAttribution": "Żid attribuzzjoni tal-app mar-reviżjonijiet tal-PR", - "prReviewAttributionSubtitle": "Żid footer 'Reviewed via Kilo' meta tissottometti reviżjoni." + "prReviewAttributionSubtitle": "Żid footer 'Reviewed via Kilo' meta tissottometti reviżjoni.", + "returnSendsMessage": "It-tast tar-ritorn jibgħat il-messaġġ", + "returnSendsMessageSubtitle": "Meta jkun mitfi, it-tast tar-ritorn idaħħal linja ġdida fl-oqsma tad-dħul tal-messaġġi tal-aġenti." }, "prReview": { "pullRequestUnavailable": "Pull request mhux disponibbli", @@ -2646,7 +2648,15 @@ "cannotReceiveFiles": "Din is-sessjoni ma tistax tirċievi fajls. Neħħi l-annessi biex tibgħat il-messaġġ tiegħek.", "couldNotAttachFiles": "Ma setgħux jintrabtu l-fajls. Tektek biex tibgħat u erġa' ipprova.", "discardAttachmentsMessage": "Il-fajls imtellgħin li mhumiex mehmuża ma' messaġġi mibgħuta se jitħassru.", - "discardAttachmentsTitle": "Twarrab l-annessi?" + "discardAttachmentsTitle": "Twarrab l-annessi?", + "charactersRemaining": "Fadal {{count}} karattru", + "insertNewline": "Daħħal linja ġdida", + "starterChipBuild": "Ibni funzjoni", + "starterChipEmpty": "Ibda b'idea", + "starterChipFix": "Irranġa żball", + "starterChipPrepare": "Ħejji l-ambjent tiegħi", + "starterChipWrapUp": "Temm is-sessjoni tiegħi", + "starterChipWriteTests": "Ikteb testijiet" }, "chatLink": { "linkCopied": "Ir-rabta ġiet ikkupjata", diff --git a/apps/mobile/src/i18n/locales/my.json b/apps/mobile/src/i18n/locales/my.json index 66c17e89ef..fd9f15da7d 100644 --- a/apps/mobile/src/i18n/locales/my.json +++ b/apps/mobile/src/i18n/locales/my.json @@ -152,7 +152,9 @@ "appearanceDark": "Dark", "account": "အကောင့်", "prReviewAttribution": "PR သုံးသပ်ချက်များတွင် app ရင်းမြစ် သတ်မှတ်ခြင်း ထည့်ပါ", - "prReviewAttributionSubtitle": "သင်သုံးသပ်ချက် တင်သွင်းသည့်အခါ Reviewed via Kilo footer ကို ထည့်ပါ။" + "prReviewAttributionSubtitle": "သင်သုံးသပ်ချက် တင်သွင်းသည့်အခါ Reviewed via Kilo footer ကို ထည့်ပါ။", + "returnSendsMessage": "စာကြောင်းပြန်ခလုတ်က စာပို့သည်", + "returnSendsMessageSubtitle": "ပိတ်ထားပါက စာကြောင်းပြန်ခလုတ်သည် အေးဂျင့်၏ စာထည့်သွင်းရာအကွက်များတွင် စာကြောင်းအသစ် ထည့်သည်။" }, "prReview": { "pullRequestUnavailable": "Pull request မရနိုင်ပါ", @@ -2589,7 +2591,15 @@ "cannotReceiveFiles": "ဤ session သည် ဖိုင်များကို မလက်ခံနိုင်ပါ။ စာပို့ရန် ပူးတွဲဖိုင်များကို ဖယ်ရှားပါ။", "couldNotAttachFiles": "ဖိုင်များကို မပူးတွဲနိုင်ပါ။ ထပ်ကြိုးစားရန် ပို့ရန် နှိပ်ပါ။", "discardAttachmentsMessage": "ပို့ပြီးသော စာများတွင် မပူးတွဲထားသော တင်ထားသည့်ဖိုင်များကို ဖျက်ပါမည်။", - "discardAttachmentsTitle": "ပူးတွဲဖိုင်များကို ပယ်ဖျက်မည်လား။" + "discardAttachmentsTitle": "ပူးတွဲဖိုင်များကို ပယ်ဖျက်မည်လား။", + "charactersRemaining": "အက္ခရာ {{count}} လုံး ကျန်သည်", + "insertNewline": "စာကြောင်းအသစ် ထည့်ပါ", + "starterChipBuild": "လုပ်ဆောင်ချက်တစ်ခု တည်ဆောက်ပါ", + "starterChipEmpty": "စိတ်ကူးတစ်ခုဖြင့် စတင်ပါ", + "starterChipFix": "ချို့ယွင်းချက်တစ်ခု ပြင်ဆင်ပါ", + "starterChipPrepare": "ကျွန်ုပ်၏ လုပ်ငန်းပတ်ဝန်းကျင်ကို ပြင်ဆင်ပါ", + "starterChipWrapUp": "ကျွန်ုပ်၏ ဆက်ရှင်ကို အဆုံးသတ်ပါ", + "starterChipWriteTests": "စမ်းသပ်မှုများ ရေးပါ" }, "chatLink": { "linkCopied": "link ကို ကူးယူပြီးပါပြီ", diff --git a/apps/mobile/src/i18n/locales/nb.json b/apps/mobile/src/i18n/locales/nb.json index eef7c3bb58..93cc514725 100644 --- a/apps/mobile/src/i18n/locales/nb.json +++ b/apps/mobile/src/i18n/locales/nb.json @@ -152,7 +152,9 @@ "appearanceDark": "Mørk", "account": "Konto", "prReviewAttribution": "Legg til appattribusjon i PR-anmeldelser", - "prReviewAttributionSubtitle": "Legg til en «Anmeldt via Kilo»-bunntekst når du sender inn en anmeldelse." + "prReviewAttributionSubtitle": "Legg til en «Anmeldt via Kilo»-bunntekst når du sender inn en anmeldelse.", + "returnSendsMessage": "Returtasten sender meldingen", + "returnSendsMessageSubtitle": "Når dette er av, setter returtasten inn et linjeskift i agentenes meldingsfelt." }, "prReview": { "pullRequestUnavailable": "Pull request utilgjengelig", @@ -2589,7 +2591,15 @@ "cannotReceiveFiles": "Denne økten kan ikke motta filer. Fjern vedleggene for å sende meldingen din.", "couldNotAttachFiles": "Kunne ikke legge til filer. Trykk send for å prøve igjen.", "discardAttachmentsMessage": "Opplastede filer som ikke er vedlagt sendte meldinger, blir slettet.", - "discardAttachmentsTitle": "Forkaste vedlegg?" + "discardAttachmentsTitle": "Forkaste vedlegg?", + "charactersRemaining": "{{count}} tegn igjen", + "insertNewline": "Sett inn linjeskift", + "starterChipBuild": "Lag en funksjon", + "starterChipEmpty": "Start med en idé", + "starterChipFix": "Fiks en feil", + "starterChipPrepare": "Sett opp miljøet mitt", + "starterChipWrapUp": "Avslutt økten min", + "starterChipWriteTests": "Skriv tester" }, "chatLink": { "linkCopied": "Lenke kopiert", diff --git a/apps/mobile/src/i18n/locales/ne.json b/apps/mobile/src/i18n/locales/ne.json index 28a17024a8..82207fb601 100644 --- a/apps/mobile/src/i18n/locales/ne.json +++ b/apps/mobile/src/i18n/locales/ne.json @@ -152,7 +152,9 @@ "appearanceDark": "गाढा", "account": "खाता", "prReviewAttribution": "PR समीक्षाहरूमा एप विशेषता थप्नुहोस्", - "prReviewAttributionSubtitle": "समीक्षा पेश गर्दा Reviewed via Kilo फुटर थप्नुहोस्।" + "prReviewAttributionSubtitle": "समीक्षा पेश गर्दा Reviewed via Kilo फुटर थप्नुहोस्।", + "returnSendsMessage": "रिटर्न कुञ्जीले सन्देश पठाउँछ", + "returnSendsMessageSubtitle": "बन्द हुँदा, रिटर्न कुञ्जीले एजेन्टको सन्देश लेख्ने ठाउँमा नयाँ पङ्क्ति घुसाउँछ।" }, "prReview": { "pullRequestUnavailable": "पुल अनुरोध उपलब्ध छैन", @@ -2589,7 +2591,15 @@ "cannotReceiveFiles": "यो सत्रले फाइल प्राप्त गर्न सक्दैन। आफ्नो सन्देश पठाउन संलग्नहरू हटाउनुहोस्।", "couldNotAttachFiles": "फाइलहरू संलग्न गर्न सकिएन। फेरि प्रयास गर्न पठाउनुहोस् ट्याप गर्नुहोस्।", "discardAttachmentsMessage": "पठाइएका सन्देशहरूमा संलग्न नभएका अपलोडहरू मेटिनेछन्।", - "discardAttachmentsTitle": "संलग्नकहरू खारेज गर्ने?" + "discardAttachmentsTitle": "संलग्नकहरू खारेज गर्ने?", + "charactersRemaining": "{{count}} अक्षर बाँकी छन्", + "insertNewline": "नयाँ पङ्क्ति घुसाउनुहोस्", + "starterChipBuild": "सुविधा बनाउनुहोस्", + "starterChipEmpty": "एउटा विचारबाट सुरु गर्नुहोस्", + "starterChipFix": "त्रुटि सच्याउनुहोस्", + "starterChipPrepare": "मेरो वातावरण तयार गर्नुहोस्", + "starterChipWrapUp": "मेरो सत्र समाप्त गर्नुहोस्", + "starterChipWriteTests": "परीक्षणहरू लेख्नुहोस्" }, "chatLink": { "linkCopied": "लिंक प्रतिलिपि गरियो", diff --git a/apps/mobile/src/i18n/locales/nl.json b/apps/mobile/src/i18n/locales/nl.json index 4e34137278..6871ef7f42 100644 --- a/apps/mobile/src/i18n/locales/nl.json +++ b/apps/mobile/src/i18n/locales/nl.json @@ -152,7 +152,9 @@ "appearanceDark": "Donker", "account": "Account", "prReviewAttribution": "App-attributie aan PR-reviews toevoegen", - "prReviewAttributionSubtitle": "Voeg een 'Reviewed via Kilo'-voettekst toe wanneer je een review indient." + "prReviewAttributionSubtitle": "Voeg een 'Reviewed via Kilo'-voettekst toe wanneer je een review indient.", + "returnSendsMessage": "Return-toets verzendt bericht", + "returnSendsMessageSubtitle": "Als dit uitstaat, voegt Return een nieuwe regel in de berichtinvoervelden van agents in." }, "notifications": { "channel": { @@ -2135,7 +2137,15 @@ "cannotReceiveFiles": "Deze sessie kan geen bestanden ontvangen. Verwijder de bijlagen om je bericht te verzenden.", "couldNotAttachFiles": "Bestanden konden niet worden bijgevoegd. Tik op Verzenden om opnieuw te proberen.", "discardAttachmentsMessage": "Geüploade bestanden die niet aan verzonden berichten zijn bijgevoegd, worden verwijderd.", - "discardAttachmentsTitle": "Bijlagen weggooien?" + "discardAttachmentsTitle": "Bijlagen weggooien?", + "charactersRemaining": "{{count}} tekens resterend", + "insertNewline": "Nieuwe regel invoegen", + "starterChipBuild": "Een functie bouwen", + "starterChipEmpty": "Beginnen met een idee", + "starterChipFix": "Een fout oplossen", + "starterChipPrepare": "Mijn omgeving instellen", + "starterChipWrapUp": "Mijn sessie afronden", + "starterChipWriteTests": "Tests schrijven" }, "chatLink": { "linkCopied": "Link gekopieerd", diff --git a/apps/mobile/src/i18n/locales/om.json b/apps/mobile/src/i18n/locales/om.json index bc5da32d98..c18ece8f9d 100644 --- a/apps/mobile/src/i18n/locales/om.json +++ b/apps/mobile/src/i18n/locales/om.json @@ -152,7 +152,9 @@ "appearanceDark": "Dukkana", "account": "Herrega", "prReviewAttribution": "App attribution PR sakatta'ootti dabali", - "prReviewAttributionSubtitle": "Kilo ttiin 'Reviewed via Kilo' footar dabali, sakatta'a yeroo galchitu." + "prReviewAttributionSubtitle": "Kilo ttiin 'Reviewed via Kilo' footar dabali, sakatta'a yeroo galchitu.", + "returnSendsMessage": "Furtuun deebi'uu ergaa ergiti", + "returnSendsMessageSubtitle": "Yeroo cufamu, furtuun deebi'uu iddoo ergaa ejentii galchuu keessatti sarara haaraa galchiti." }, "prReview": { "pullRequestUnavailable": "Pull request hin jiraatu", @@ -2589,7 +2591,15 @@ "cannotReceiveFiles": "Shumarri kun faayiloota fudhachuu hin danda'u. Ergaa kee erguuf faayiloota baasi.", "couldNotAttachFiles": "Faayiloota qabachiisuu hin danda'ine. Erguuf ergi cuqaasa.", "discardAttachmentsMessage": "Faayiloonni ol fe'aman kan ergaawwan ergamanitti hin qabachiifamne ni haqamu.", - "discardAttachmentsTitle": "Faayiloota qabachiifaman haqnaa?" + "discardAttachmentsTitle": "Faayiloota qabachiifaman haqnaa?", + "charactersRemaining": "Qubeelaa {{count}} hafe", + "insertNewline": "Sarara haaraa galchaa", + "starterChipBuild": "Amala haaraa ijaaraa", + "starterChipEmpty": "Yaada tokkoon jalqabaa", + "starterChipFix": "Dogoggora sirreessaa", + "starterChipPrepare": "Naannoo koo qopheessaa", + "starterChipWrapUp": "Xalayyii koo xumuraa", + "starterChipWriteTests": "Qormaata barreessaa" }, "chatLink": { "linkCopied": "Geessituun maxxanameera", diff --git a/apps/mobile/src/i18n/locales/or.json b/apps/mobile/src/i18n/locales/or.json index 6d1aa03a2f..7f67d92e90 100644 --- a/apps/mobile/src/i18n/locales/or.json +++ b/apps/mobile/src/i18n/locales/or.json @@ -152,7 +152,9 @@ "appearanceDark": "ଗାଢ଼", "account": "ଆକାଉଣ୍ଟ", "prReviewAttribution": "PR ସମୀକ୍ଷାରେ ଆପ୍ ଆଟ୍ରିବ୍ୟୁଶନ୍ ଯୋଡନ୍ତୁ", - "prReviewAttributionSubtitle": "ଆପଣ ଏକ ସମୀକ୍ଷା ଦାଖଲ କଲେ \"Kilo ମାଧ୍ୟମରେ ସମୀକ୍ଷା ହୋଇଛି\" footer ସଂଲଗ୍ନ କରନ୍ତୁ।" + "prReviewAttributionSubtitle": "ଆପଣ ଏକ ସମୀକ୍ଷା ଦାଖଲ କଲେ \"Kilo ମାଧ୍ୟମରେ ସମୀକ୍ଷା ହୋଇଛି\" footer ସଂଲଗ୍ନ କରନ୍ତୁ।", + "returnSendsMessage": "ରିଟର୍ନ କୀ ବାର୍ତ୍ତା ପଠାଏ", + "returnSendsMessageSubtitle": "ବନ୍ଦ ଥିଲେ, ରିଟର୍ନ କୀ ଏଜେଣ୍ଟର ବାର୍ତ୍ତା ଲେଖିବା ସ୍ଥାନରେ ନୂଆ ଧାଡ଼ି ଯୋଡ଼େ।" }, "prReview": { "pullRequestUnavailable": "ପୁଲ୍ ରିକ୍ୱେଷ୍ଟ ଉପଲବ୍ଧ ନାହିଁ", @@ -2589,7 +2591,15 @@ "cannotReceiveFiles": "ଏହି ସେସନ୍ ଫାଇଲ ଗ୍ରହଣ କରିପାରିବ ନାହିଁ। ଆପଣଙ୍କ ବାର୍ତ୍ତା ପଠାଇବାକୁ ଆଟାଚମେଣ୍ଟ ଅପସାରଣ କରନ୍ତୁ।", "couldNotAttachFiles": "ଫାଇଲ ଆଟାଚ୍ ହୋଇପାରିଲା ନାହିଁ। ପୁନଃଚେଷ୍ଟା କରିବାକୁ ପଠାନ୍ତୁ ଟାପ୍ କରନ୍ତୁ।", "discardAttachmentsMessage": "ପଠାଯାଇଥିବା ବାର୍ତ୍ତାରେ ସଂଲଗ୍ନ ହୋଇନଥିବା ଅପଲୋଡ୍ ହୋଇଥିବା ଫାଇଲ୍ ଗୁଡ଼ିକ ବିଲୋପ ହେବ।", - "discardAttachmentsTitle": "ସଂଲଗ୍ନକଗୁଡ଼ିକ ପରିତ୍ୟାଗ କରିବେ?" + "discardAttachmentsTitle": "ସଂଲଗ୍ନକଗୁଡ଼ିକ ପରିତ୍ୟାଗ କରିବେ?", + "charactersRemaining": "{{count}}ଟି ଅକ୍ଷର ବାକି ଅଛି", + "insertNewline": "ନୂଆ ଧାଡ଼ି ଯୋଡ଼ନ୍ତୁ", + "starterChipBuild": "ଏକ ବୈଶିଷ୍ଟ୍ୟ ତିଆରି କରନ୍ତୁ", + "starterChipEmpty": "ଏକ ଧାରଣାରୁ ଆରମ୍ଭ କରନ୍ତୁ", + "starterChipFix": "ଏକ ତ୍ରୁଟି ଠିକ୍ କରନ୍ତୁ", + "starterChipPrepare": "ମୋ ପରିବେଶ ପ୍ରସ୍ତୁତ କରନ୍ତୁ", + "starterChipWrapUp": "ମୋ ସେସନ୍ ସମାପ୍ତ କରନ୍ତୁ", + "starterChipWriteTests": "ପରୀକ୍ଷା ଲେଖନ୍ତୁ" }, "chatLink": { "linkCopied": "ଲିଙ୍କ କପି ହେଲା", diff --git a/apps/mobile/src/i18n/locales/pa.json b/apps/mobile/src/i18n/locales/pa.json index 9ae74efe57..7e84449973 100644 --- a/apps/mobile/src/i18n/locales/pa.json +++ b/apps/mobile/src/i18n/locales/pa.json @@ -152,7 +152,9 @@ "appearanceDark": "ਗੂੜ੍ਹਾ", "account": "ਖਾਤਾ", "prReviewAttribution": "PR ਸਮੀਖਿਆਵਾਂ ਵਿੱਚ ਐਪ ਐਟ੍ਰੀਬਿਊਸ਼ਨ ਸ਼ਾਮਲ ਕਰੋ", - "prReviewAttributionSubtitle": "ਸਮੀਖਿਆ ਜਮ੍ਹਾਂ ਕਰਨ ਵੇਲੇ Kilo ਰਾਹੀਂ ਸਮੀਖਿਆ ਕੀਤੀ ਫੁੱਟਰ ਜੋੜੋ।" + "prReviewAttributionSubtitle": "ਸਮੀਖਿਆ ਜਮ੍ਹਾਂ ਕਰਨ ਵੇਲੇ Kilo ਰਾਹੀਂ ਸਮੀਖਿਆ ਕੀਤੀ ਫੁੱਟਰ ਜੋੜੋ।", + "returnSendsMessage": "ਰਿਟਰਨ ਕੁੰਜੀ ਸੁਨੇਹਾ ਭੇਜਦੀ ਹੈ", + "returnSendsMessageSubtitle": "ਬੰਦ ਹੋਣ 'ਤੇ, ਰਿਟਰਨ ਏਜੰਟ ਕੰਪੋਜ਼ਰਾਂ ਵਿੱਚ ਨਵੀਂ ਲਾਈਨ ਪਾਉਂਦੀ ਹੈ।" }, "prReview": { "pullRequestUnavailable": "ਪੁੱਲ ਰਿਕਵੈਸਟ ਉਪਲਬਧ ਨਹੀਂ ਹੈ", @@ -2589,7 +2591,15 @@ "cannotReceiveFiles": "ਇਹ ਸੈਸ਼ਨ ਫਾਈਲਾਂ ਪ੍ਰਾਪਤ ਨਹੀਂ ਕਰ ਸਕਦਾ। ਆਪਣਾ ਸੁਨੇਹਾ ਭੇਜਣ ਲਈ ਅਟੈਚਮੈਂਟ ਹਟਾਓ।", "couldNotAttachFiles": "ਫਾਈਲਾਂ ਅਟੈਚ ਨਹੀਂ ਹੋ ਸਕੀਆਂ। ਦੁਬਾਰਾ ਕੋਸ਼ਿਸ਼ ਕਰਨ ਲਈ ਭੇਜੋ ਟੈਪ ਕਰੋ।", "discardAttachmentsMessage": "ਭੇਜੇ ਗਏ ਸੁਨੇਹਿਆਂ ਨਾਲ ਅਟੈਚ ਨਾ ਕੀਤੀਆਂ ਅੱਪਲੋਡ ਕੀਤੀਆਂ ਫਾਈਲਾਂ ਮਿਟਾ ਦਿੱਤੀਆਂ ਜਾਣਗੀਆਂ।", - "discardAttachmentsTitle": "ਅਟੈਚਮੈਂਟਾਂ ਮਿਟਾਈਏ?" + "discardAttachmentsTitle": "ਅਟੈਚਮੈਂਟਾਂ ਮਿਟਾਈਏ?", + "charactersRemaining": "{{count}} ਅੱਖਰ ਬਾਕੀ ਹਨ", + "insertNewline": "ਨਵੀਂ ਲਾਈਨ ਪਾਓ", + "starterChipBuild": "ਇੱਕ ਵਿਸ਼ੇਸ਼ਤਾ ਬਣਾਓ", + "starterChipEmpty": "ਇੱਕ ਵਿਚਾਰ ਨਾਲ ਸ਼ੁਰੂ ਕਰੋ", + "starterChipFix": "ਇੱਕ ਤਰੁੱਟੀ ਠੀਕ ਕਰੋ", + "starterChipPrepare": "ਮੇਰਾ ਵਾਤਾਵਰਨ ਸੈੱਟ ਕਰੋ", + "starterChipWrapUp": "ਮੇਰਾ ਸੈਸ਼ਨ ਮੁਕੰਮਲ ਕਰੋ", + "starterChipWriteTests": "ਟੈਸਟ ਲਿਖੋ" }, "chatLink": { "linkCopied": "ਲਿੰਕ ਕਾਪੀ ਹੋ ਗਿਆ", diff --git a/apps/mobile/src/i18n/locales/pl.json b/apps/mobile/src/i18n/locales/pl.json index 6cdc7c8bb4..664b35c345 100644 --- a/apps/mobile/src/i18n/locales/pl.json +++ b/apps/mobile/src/i18n/locales/pl.json @@ -152,7 +152,9 @@ "appearanceDark": "Ciemny", "account": "Konto", "prReviewAttribution": "Dodaj atrybucję aplikacji do recenzji PR", - "prReviewAttributionSubtitle": "Dodaj stopkę „Reviewed via Kilo”, gdy wysyłasz recenzję." + "prReviewAttributionSubtitle": "Dodaj stopkę „Reviewed via Kilo”, gdy wysyłasz recenzję.", + "returnSendsMessage": "Klawisz Enter wysyła wiadomość", + "returnSendsMessageSubtitle": "Gdy ta opcja jest wyłączona, klawisz Enter wstawia nowy wiersz w polach wiadomości agentów." }, "notifications": { "title": "Powiadomienia", @@ -2159,7 +2161,15 @@ "cannotReceiveFiles": "Ta sesja nie może odbierać plików. Usuń załączniki, aby wysłać wiadomość.", "couldNotAttachFiles": "Nie udało się dołączyć plików. Dotknij Wyślij, aby spróbować ponownie.", "discardAttachmentsMessage": "Przesłane pliki niedołączone do wysłanych wiadomości zostaną usunięte.", - "discardAttachmentsTitle": "Odrzucić załączniki?" + "discardAttachmentsTitle": "Odrzucić załączniki?", + "charactersRemaining": "Pozostała liczba znaków: {{count}}", + "insertNewline": "Wstaw nowy wiersz", + "starterChipBuild": "Utwórz funkcję", + "starterChipEmpty": "Zacznij od pomysłu", + "starterChipFix": "Napraw błąd", + "starterChipPrepare": "Skonfiguruj moje środowisko", + "starterChipWrapUp": "Zakończ moją sesję", + "starterChipWriteTests": "Napisz testy" }, "chatLink": { "linkCopied": "Link skopiowany", diff --git a/apps/mobile/src/i18n/locales/ps.json b/apps/mobile/src/i18n/locales/ps.json index b7e9b0d2aa..c37af2b68b 100644 --- a/apps/mobile/src/i18n/locales/ps.json +++ b/apps/mobile/src/i18n/locales/ps.json @@ -152,7 +152,9 @@ "appearanceDark": "تیاره", "account": "حساب", "prReviewAttribution": "د PR بیاکتنو ته د ایپ انتساب اضافه کړئ", - "prReviewAttributionSubtitle": "کله چې تاسو یوه بیاکتنه وړاندې کوئ، د Reviewed via Kilo فوټر اضافه کړئ." + "prReviewAttributionSubtitle": "کله چې تاسو یوه بیاکتنه وړاندې کوئ، د Reviewed via Kilo فوټر اضافه کړئ.", + "returnSendsMessage": "د بېرته ستنېدو کیلي پیغام لېږي", + "returnSendsMessageSubtitle": "کله چې بند وي، د بېرته ستنېدو کیلي د ایجنټ د پیغام لیکلو په ځایونو کې نوې کرښه دننه کوي." }, "prReview": { "pullRequestUnavailable": "پل ریکویسټ شتون نلري", @@ -2589,7 +2591,15 @@ "cannotReceiveFiles": "دا غونډه فایلونه نه شي ترلاسه کولای. د پیغام لیږلو لپاره ضمیمې لرې کړئ.", "couldNotAttachFiles": "فایلونه ونه لګېدل. د بیا هڅې لپاره لیږل ټایپ کړئ.", "discardAttachmentsMessage": "هغه پورته شوي فایلونه به ړنګ شي چې له لېږل شویو پیغامونو سره ضمیمه نه دي.", - "discardAttachmentsTitle": "ضمیمې لغوه کړئ؟" + "discardAttachmentsTitle": "ضمیمې لغوه کړئ؟", + "charactersRemaining": "{{count}} توري پاتې دي", + "insertNewline": "نوې کرښه دننه کړئ", + "starterChipBuild": "یوه ځانګړتیا جوړه کړئ", + "starterChipEmpty": "له یوې مفکورې پیل وکړئ", + "starterChipFix": "یوه تېروتنه سمه کړئ", + "starterChipPrepare": "زما چاپېریال چمتو کړئ", + "starterChipWrapUp": "زما ناسته پای ته ورسوئ", + "starterChipWriteTests": "ازموینې ولیکئ" }, "chatLink": { "linkCopied": "لینک کاپي شو", diff --git a/apps/mobile/src/i18n/locales/pt-BR.json b/apps/mobile/src/i18n/locales/pt-BR.json index 6f3948b54f..f06babff94 100644 --- a/apps/mobile/src/i18n/locales/pt-BR.json +++ b/apps/mobile/src/i18n/locales/pt-BR.json @@ -2131,7 +2131,15 @@ "cannotReceiveFiles": "Esta sessão não pode receber arquivos. Remova os anexos para enviar sua mensagem.", "couldNotAttachFiles": "Não foi possível anexar os arquivos. Toque em enviar para tentar novamente.", "discardAttachmentsMessage": "Os arquivos enviados que não estiverem anexados a mensagens enviadas serão excluídos.", - "discardAttachmentsTitle": "Descartar anexos?" + "discardAttachmentsTitle": "Descartar anexos?", + "charactersRemaining": "{{count}} caracteres restantes", + "insertNewline": "Inserir quebra de linha", + "starterChipBuild": "Criar uma funcionalidade", + "starterChipEmpty": "Começar com uma ideia", + "starterChipFix": "Corrigir um erro", + "starterChipPrepare": "Configurar meu ambiente", + "starterChipWrapUp": "Encerrar minha sessão", + "starterChipWriteTests": "Escrever testes" }, "chatLink": { "linkCopied": "Link copiado", @@ -2261,7 +2269,9 @@ "appearanceDark": "Escuro", "account": "Conta", "prReviewAttribution": "Adicionar atribuição do app às avaliações de PR", - "prReviewAttributionSubtitle": "Anexe um rodapé \"Reviewed via Kilo\" ao enviar uma avaliação." + "prReviewAttributionSubtitle": "Anexe um rodapé \"Reviewed via Kilo\" ao enviar uma avaliação.", + "returnSendsMessage": "A tecla Enter envia a mensagem", + "returnSendsMessageSubtitle": "Quando desativada, a tecla Enter insere uma quebra de linha nos campos de mensagem dos agentes." }, "addCredits": { "cta": "Adicionar créditos", diff --git a/apps/mobile/src/i18n/locales/pt.json b/apps/mobile/src/i18n/locales/pt.json index 9ba477fcd5..9c8f8de570 100644 --- a/apps/mobile/src/i18n/locales/pt.json +++ b/apps/mobile/src/i18n/locales/pt.json @@ -152,7 +152,9 @@ "appearanceDark": "Escuro", "account": "Conta", "prReviewAttribution": "Adicionar atribuição da app às revisões de PR", - "prReviewAttributionSubtitle": "Adicionar um rodapé «Revisto via Kilo» quando submeter uma revisão." + "prReviewAttributionSubtitle": "Adicionar um rodapé «Revisto via Kilo» quando submeter uma revisão.", + "returnSendsMessage": "A tecla de retorno envia a mensagem", + "returnSendsMessageSubtitle": "Quando desativado, a tecla de retorno insere uma nova linha nos campos de mensagem dos agentes." }, "prReview": { "pullRequestUnavailable": "Pull request indisponível", @@ -2608,7 +2610,15 @@ "cannotReceiveFiles": "Esta sessão não pode receber ficheiros. Remova os anexos para enviar a sua mensagem.", "couldNotAttachFiles": "Não foi possível anexar ficheiros. Toque em enviar para tentar novamente.", "discardAttachmentsMessage": "Os ficheiros carregados que não estejam anexados a mensagens enviadas serão eliminados.", - "discardAttachmentsTitle": "Descartar anexos?" + "discardAttachmentsTitle": "Descartar anexos?", + "charactersRemaining": "{{count}} caracteres restantes", + "insertNewline": "Inserir nova linha", + "starterChipBuild": "Criar uma funcionalidade", + "starterChipEmpty": "Começar com uma ideia", + "starterChipFix": "Corrigir um erro", + "starterChipPrepare": "Configurar o meu ambiente", + "starterChipWrapUp": "Concluir a minha sessão", + "starterChipWriteTests": "Escrever testes" }, "chatLink": { "linkCopied": "Ligação copiada", diff --git a/apps/mobile/src/i18n/locales/ro.json b/apps/mobile/src/i18n/locales/ro.json index fdce33874a..3a0a011fee 100644 --- a/apps/mobile/src/i18n/locales/ro.json +++ b/apps/mobile/src/i18n/locales/ro.json @@ -152,7 +152,9 @@ "appearanceDark": "Întunecat", "account": "Cont", "prReviewAttribution": "Adaugă atribuirea aplicației la revizuirile PR", - "prReviewAttributionSubtitle": "Adaugă un subsol „Revizuit prin Kilo” când trimiți o revizuire." + "prReviewAttributionSubtitle": "Adaugă un subsol „Revizuit prin Kilo” când trimiți o revizuire.", + "returnSendsMessage": "Tasta Enter trimite mesajul", + "returnSendsMessageSubtitle": "Când opțiunea este dezactivată, tasta Enter inserează o linie nouă în câmpurile de mesaj ale agenților." }, "prReview": { "pullRequestUnavailable": "Pull request indisponibil", @@ -2608,7 +2610,15 @@ "cannotReceiveFiles": "Această sesiune nu poate primi fișiere. Elimină atașamentele pentru a trimite mesajul.", "couldNotAttachFiles": "Fișierele nu au putut fi atașate. Atinge trimitere pentru a încerca din nou.", "discardAttachmentsMessage": "Fișierele încărcate care nu sunt atașate la mesaje trimise vor fi șterse.", - "discardAttachmentsTitle": "Renunți la atașamente?" + "discardAttachmentsTitle": "Renunți la atașamente?", + "charactersRemaining": "{{count}} caractere rămase", + "insertNewline": "Inserează o linie nouă", + "starterChipBuild": "Dezvoltă o funcționalitate", + "starterChipEmpty": "Începe cu o idee", + "starterChipFix": "Remediază o eroare", + "starterChipPrepare": "Configurează mediul meu", + "starterChipWrapUp": "Încheie sesiunea mea", + "starterChipWriteTests": "Scrie teste" }, "chatLink": { "linkCopied": "Link copiat", diff --git a/apps/mobile/src/i18n/locales/ru.json b/apps/mobile/src/i18n/locales/ru.json index fa417d4e0e..d9c70b082c 100644 --- a/apps/mobile/src/i18n/locales/ru.json +++ b/apps/mobile/src/i18n/locales/ru.json @@ -152,7 +152,9 @@ "appearanceDark": "Темная", "account": "Аккаунт", "prReviewAttribution": "Добавить атрибуцию приложения в проверки PR", - "prReviewAttributionSubtitle": "Добавлять нижний колонтитул «Проверено с помощью Kilo» при отправке проверки." + "prReviewAttributionSubtitle": "Добавлять нижний колонтитул «Проверено с помощью Kilo» при отправке проверки.", + "returnSendsMessage": "Клавиша ввода отправляет сообщение", + "returnSendsMessageSubtitle": "Если выключено, клавиша ввода вставляет новую строку в полях ввода сообщений агентам." }, "notifications": { "title": "Уведомления", @@ -2159,7 +2161,15 @@ "cannotReceiveFiles": "Эта сессия не может получать файлы. Удалите вложения, чтобы отправить сообщение.", "couldNotAttachFiles": "Не удалось прикрепить файлы. Нажмите «Отправить», чтобы повторить.", "discardAttachmentsMessage": "Загруженные файлы, не прикрепленные к отправленным сообщениям, будут удалены.", - "discardAttachmentsTitle": "Удалить вложения?" + "discardAttachmentsTitle": "Удалить вложения?", + "charactersRemaining": "Осталось символов: {{count}}", + "insertNewline": "Вставить новую строку", + "starterChipBuild": "Создать функцию", + "starterChipEmpty": "Начать с идеи", + "starterChipFix": "Исправить ошибку", + "starterChipPrepare": "Настроить мое окружение", + "starterChipWrapUp": "Завершить мой сеанс", + "starterChipWriteTests": "Написать тесты" }, "chatLink": { "linkCopied": "Ссылка скопирована", diff --git a/apps/mobile/src/i18n/locales/si.json b/apps/mobile/src/i18n/locales/si.json index 5fbff715a5..e3f4466725 100644 --- a/apps/mobile/src/i18n/locales/si.json +++ b/apps/mobile/src/i18n/locales/si.json @@ -152,7 +152,9 @@ "appearanceDark": "අඳුරු", "account": "ගිණුම", "prReviewAttribution": "PR සමාලෝචන සඳහා යෙදුම් ආරෝපණය එක් කරන්න", - "prReviewAttributionSubtitle": "සමාලෝචනයක් ඉදිරිපත් කරන විට Kilo හරහා සමාලෝචනය යන පාදකය එකතු කරන්න" + "prReviewAttributionSubtitle": "සමාලෝචනයක් ඉදිරිපත් කරන විට Kilo හරහා සමාලෝචනය යන පාදකය එකතු කරන්න", + "returnSendsMessage": "ආපසු යතුර පණිවිඩය යවයි", + "returnSendsMessageSubtitle": "ක්‍රියා විරහිත විට, ආපසු යතුර නියෝජිත පණිවිඩ ආදාන කොටුවල නව පේළියක් ඇතුළත් කරයි." }, "prReview": { "pullRequestUnavailable": "Pull request ලබා ගත නොහැක", @@ -2589,7 +2591,15 @@ "cannotReceiveFiles": "මෙම සැසියට ගොනු ලබා ගත නොහැක. ඔබගේ පණිවිඩය යැවීමට ඇමුණුම් ඉවත් කරන්න.", "couldNotAttachFiles": "ගොනු ඇමිණිය නොහැකි විය. නැවත උත්සාහ කිරීමට යැවීම තට්ටු කරන්න.", "discardAttachmentsMessage": "යැවූ පණිවිඩවලට අමුණා නැති උඩුගත කළ ගොනු මකා දමනු ඇත.", - "discardAttachmentsTitle": "ඇමුණුම් ඉවතලන්නද?" + "discardAttachmentsTitle": "ඇමුණුම් ඉවතලන්නද?", + "charactersRemaining": "අක්ෂර {{count}} ක් ඉතිරිව ඇත", + "insertNewline": "නව පේළියක් ඇතුළත් කරන්න", + "starterChipBuild": "විශේෂාංගයක් සාදන්න", + "starterChipEmpty": "අදහසකින් ආරම්භ කරන්න", + "starterChipFix": "දෝෂයක් නිවැරදි කරන්න", + "starterChipPrepare": "මගේ පරිසරය සකසන්න", + "starterChipWrapUp": "මගේ සැසිය අවසන් කරන්න", + "starterChipWriteTests": "පරීක්ෂණ ලියන්න" }, "chatLink": { "linkCopied": "සබැඳිය පිටපත් කරන ලදී", diff --git a/apps/mobile/src/i18n/locales/sk.json b/apps/mobile/src/i18n/locales/sk.json index c24c6ab10c..20909d41ff 100644 --- a/apps/mobile/src/i18n/locales/sk.json +++ b/apps/mobile/src/i18n/locales/sk.json @@ -152,7 +152,9 @@ "appearanceDark": "Tmavý", "account": "Účet", "prReviewAttribution": "Pridať atribúciu aplikácie do kontrol PR", - "prReviewAttributionSubtitle": "Keď odošlete kontrolu, pridajte pätičku Reviewed via Kilo." + "prReviewAttributionSubtitle": "Keď odošlete kontrolu, pridajte pätičku Reviewed via Kilo.", + "returnSendsMessage": "Kláves Enter odošle správu", + "returnSendsMessageSubtitle": "Keď je táto možnosť vypnutá, kláves Enter vloží nový riadok do polí na zadávanie správy agentom." }, "prReview": { "pullRequestUnavailable": "Pull request nie je dostupný", @@ -2627,7 +2629,15 @@ "cannotReceiveFiles": "Táto relácia nemôže prijímať súbory. Odstráňte prílohy, aby ste mohli odoslať správu.", "couldNotAttachFiles": "Súbory sa nepodarilo pripojiť. Klepnutím na odoslať to skúsite znova.", "discardAttachmentsMessage": "Nahrané súbory nepriložené k odoslaným správam sa odstránia.", - "discardAttachmentsTitle": "Zahodiť prílohy?" + "discardAttachmentsTitle": "Zahodiť prílohy?", + "charactersRemaining": "Počet zostávajúcich znakov: {{count}}", + "insertNewline": "Vložiť nový riadok", + "starterChipBuild": "Vytvoriť funkciu", + "starterChipEmpty": "Začať nápadom", + "starterChipFix": "Opraviť chybu", + "starterChipPrepare": "Nastaviť moje prostredie", + "starterChipWrapUp": "Dokončiť moje sedenie", + "starterChipWriteTests": "Napísať testy" }, "chatLink": { "linkCopied": "Odkaz skopírovaný", diff --git a/apps/mobile/src/i18n/locales/sl.json b/apps/mobile/src/i18n/locales/sl.json index fadac2890f..b815564aa9 100644 --- a/apps/mobile/src/i18n/locales/sl.json +++ b/apps/mobile/src/i18n/locales/sl.json @@ -152,7 +152,9 @@ "appearanceDark": "Temno", "account": "Račun", "prReviewAttribution": "Dodaj pripis aplikacije k pregledom PR", - "prReviewAttributionSubtitle": "K oddaji pregleda dodaj nogo »Reviewed via Kilo«." + "prReviewAttributionSubtitle": "K oddaji pregleda dodaj nogo »Reviewed via Kilo«.", + "returnSendsMessage": "Tipka Enter pošlje sporočilo", + "returnSendsMessageSubtitle": "Ko je možnost izklopljena, tipka Enter vstavi novo vrstico v polja za vnos sporočil agentom." }, "prReview": { "pullRequestUnavailable": "Pull request ni na voljo", @@ -2627,7 +2629,15 @@ "cannotReceiveFiles": "Ta seja ne more prejemati datotek. Odstrani priponke za pošiljanje sporočila.", "couldNotAttachFiles": "Priponk ni bilo mogoče priložiti. Dotakni se pošlji za ponovni poskus.", "discardAttachmentsMessage": "Naložene datoteke, ki niso priložene poslanim sporočilom, bodo izbrisane.", - "discardAttachmentsTitle": "Zavreči priloge?" + "discardAttachmentsTitle": "Zavreči priloge?", + "charactersRemaining": "Preostalo znakov: {{count}}", + "insertNewline": "Vstavite novo vrstico", + "starterChipBuild": "Razvijte funkcijo", + "starterChipEmpty": "Začnite z zamislijo", + "starterChipFix": "Odpravite napako", + "starterChipPrepare": "Nastavite moje okolje", + "starterChipWrapUp": "Zaključite mojo sejo", + "starterChipWriteTests": "Napišite preizkuse" }, "chatLink": { "linkCopied": "Povezava kopirana", diff --git a/apps/mobile/src/i18n/locales/so.json b/apps/mobile/src/i18n/locales/so.json index 8972426c1f..a2c7c3866b 100644 --- a/apps/mobile/src/i18n/locales/so.json +++ b/apps/mobile/src/i18n/locales/so.json @@ -152,7 +152,9 @@ "appearanceDark": "Madow", "account": "Xisaabta", "prReviewAttribution": "Ku dar astaanta app dib-u-eegista PR", - "prReviewAttributionSubtitle": "Ku dheji qoraal 'Reviewed via Kilo' oo hoose markaad gudbiso dib-u-eegis." + "prReviewAttributionSubtitle": "Ku dheji qoraal 'Reviewed via Kilo' oo hoose markaad gudbiso dib-u-eegis.", + "returnSendsMessage": "Furaha soo noqoshada wuxuu diraa fariinta", + "returnSendsMessageSubtitle": "Marka uu dansan yahay, furaha soo noqoshada wuxuu geliyaa sadar cusub composer-ka wakiilka." }, "prReview": { "pullRequestUnavailable": "Pull request ma heli karo", @@ -2589,7 +2591,15 @@ "cannotReceiveFiles": "Kalfadhigan ma heli karo faylasha. Ka saar lifaaqyada si aad u dirto fariintaada.", "couldNotAttachFiles": "Faylasha lama lifaaqi karin. Taabo dir si aad mar kale isku daydo.", "discardAttachmentsMessage": "Faylasha la soo gudbiyay ee aan lagu lifaaqin fariimo la diray waa la tirtiri doonaa.", - "discardAttachmentsTitle": "Tuur lifaaqyada?" + "discardAttachmentsTitle": "Tuur lifaaqyada?", + "charactersRemaining": "{{count}} xaraf ayaa harsan", + "insertNewline": "Geli sadar cusub", + "starterChipBuild": "Dhis sifo", + "starterChipEmpty": "Ku bilow fikrad", + "starterChipFix": "Hagaaji cilad", + "starterChipPrepare": "Diyaari deegaankayga", + "starterChipWrapUp": "Dhammee kalfadhigayga", + "starterChipWriteTests": "Qor tijaabooyin" }, "chatLink": { "linkCopied": "Xiriirka waa la koobiyay", diff --git a/apps/mobile/src/i18n/locales/sq.json b/apps/mobile/src/i18n/locales/sq.json index 02db74e18c..dfcd5f5a81 100644 --- a/apps/mobile/src/i18n/locales/sq.json +++ b/apps/mobile/src/i18n/locales/sq.json @@ -152,7 +152,9 @@ "appearanceDark": "E errët", "account": "Llogaria", "prReviewAttribution": "Shtoni atributin e aplikacionit në rishikimet PR", - "prReviewAttributionSubtitle": "Shtoni një fundshënim \"Reviewed via Kilo\" kur paraqisni një rishikim." + "prReviewAttributionSubtitle": "Shtoni një fundshënim \"Reviewed via Kilo\" kur paraqisni një rishikim.", + "returnSendsMessage": "Tasti i kthimit dërgon mesazhin", + "returnSendsMessageSubtitle": "Kur çaktivizohet, tasti i kthimit shton një rresht të ri në fushat e mesazheve të agjentëve." }, "prReview": { "pullRequestUnavailable": "Kërkesa e tërheqjes e padisponueshme", @@ -2589,7 +2591,15 @@ "cannotReceiveFiles": "Kjo seancë nuk mund të marrë skedarë. Hiqni bashkëngjitjet për të dërguar mesazhin tuaj.", "couldNotAttachFiles": "Skedarët nuk u bashkëngjitën. Prekni Dërgo për të riprovuar.", "discardAttachmentsMessage": "Skedarët e ngarkuar që nuk janë bashkëngjitur në mesazhet e dërguara do të fshihen.", - "discardAttachmentsTitle": "Të hiqen bashkëngjitjet?" + "discardAttachmentsTitle": "Të hiqen bashkëngjitjet?", + "charactersRemaining": "Mbeten {{count}} karaktere", + "insertNewline": "Shtoni një rresht të ri", + "starterChipBuild": "Krijoni një veçori", + "starterChipEmpty": "Filloni me një ide", + "starterChipFix": "Rregulloni një gabim", + "starterChipPrepare": "Konfiguroni mjedisin tim", + "starterChipWrapUp": "Përfundoni seancën time", + "starterChipWriteTests": "Shkruani teste" }, "chatLink": { "linkCopied": "Lidhja u kopjua", diff --git a/apps/mobile/src/i18n/locales/sr.json b/apps/mobile/src/i18n/locales/sr.json index 0981f409fe..d939f1c204 100644 --- a/apps/mobile/src/i18n/locales/sr.json +++ b/apps/mobile/src/i18n/locales/sr.json @@ -152,7 +152,9 @@ "appearanceDark": "Tamno", "account": "Nalog", "prReviewAttribution": "Dodaj atribuciju aplikacije u PR preglede", - "prReviewAttributionSubtitle": "Dodaj 'Reviewed via Kilo' podnožje kada pošalješ pregled." + "prReviewAttributionSubtitle": "Dodaj 'Reviewed via Kilo' podnožje kada pošalješ pregled.", + "returnSendsMessage": "Taster za novi red šalje poruku", + "returnSendsMessageSubtitle": "Kada je isključeno, taster za novi red unosi novi red u kompozitore agenata." }, "prReview": { "pullRequestUnavailable": "Pull request nije dostupan", @@ -2608,7 +2610,15 @@ "cannotReceiveFiles": "Ova sesija ne može da prima datoteke. Uklonite priloge da biste poslali poruku.", "couldNotAttachFiles": "Dodavanje datoteka nije uspelo. Dodirnite pošalji za ponovni pokušaj.", "discardAttachmentsMessage": "Otpremljeni fajlovi koji nisu priloženi poslatim porukama biće obrisani.", - "discardAttachmentsTitle": "Odbaciti priloge?" + "discardAttachmentsTitle": "Odbaciti priloge?", + "charactersRemaining": "Preostalo znakova: {{count}}", + "insertNewline": "Unesite novi red", + "starterChipBuild": "Napravite funkciju", + "starterChipEmpty": "Počnite od ideje", + "starterChipFix": "Ispravite grešku", + "starterChipPrepare": "Podesite moje okruženje", + "starterChipWrapUp": "Završite moju sesiju", + "starterChipWriteTests": "Napišite testove" }, "chatLink": { "linkCopied": "Link kopiran", diff --git a/apps/mobile/src/i18n/locales/sv.json b/apps/mobile/src/i18n/locales/sv.json index 70f4d92023..d9449bbafc 100644 --- a/apps/mobile/src/i18n/locales/sv.json +++ b/apps/mobile/src/i18n/locales/sv.json @@ -152,7 +152,9 @@ "appearanceDark": "Mörkt", "account": "Konto", "prReviewAttribution": "Lägg till appattribution i PR-granskningar", - "prReviewAttributionSubtitle": "Lägg till en Reviewed via Kilo-sidfot när du skickar en granskning." + "prReviewAttributionSubtitle": "Lägg till en Reviewed via Kilo-sidfot när du skickar en granskning.", + "returnSendsMessage": "Returtangenten skickar meddelanden", + "returnSendsMessageSubtitle": "När detta är avstängt infogar returtangenten en radbrytning i agenternas meddelandefält." }, "prReview": { "pullRequestUnavailable": "Pull request inte tillgänglig", @@ -2589,7 +2591,15 @@ "cannotReceiveFiles": "Denna session kan inte ta emot filer. Ta bort bilagorna för att skicka ditt meddelande.", "couldNotAttachFiles": "Det gick inte att bifoga filer. Tryck på skicka för att försöka igen.", "discardAttachmentsMessage": "Uppladdade filer som inte har bifogats i skickade meddelanden tas bort.", - "discardAttachmentsTitle": "Kassera bilagor?" + "discardAttachmentsTitle": "Kassera bilagor?", + "charactersRemaining": "{{count}} tecken kvar", + "insertNewline": "Infoga radbrytning", + "starterChipBuild": "Bygg en funktion", + "starterChipEmpty": "Börja med en idé", + "starterChipFix": "Åtgärda ett fel", + "starterChipPrepare": "Konfigurera min miljö", + "starterChipWrapUp": "Avsluta min session", + "starterChipWriteTests": "Skriv tester" }, "chatLink": { "linkCopied": "Länk kopierad", diff --git a/apps/mobile/src/i18n/locales/sw.json b/apps/mobile/src/i18n/locales/sw.json index 97f45844ef..c4ae81cd92 100644 --- a/apps/mobile/src/i18n/locales/sw.json +++ b/apps/mobile/src/i18n/locales/sw.json @@ -152,7 +152,9 @@ "appearanceDark": "Nyeusi", "account": "Akaunti", "prReviewAttribution": "Ongeza sifa ya app kwenye ukaguzi wa PR", - "prReviewAttributionSubtitle": "Ongeza 'Reviewed via Kilo' kwenye sehemu ya chini unapowasilisha ukaguzi." + "prReviewAttributionSubtitle": "Ongeza 'Reviewed via Kilo' kwenye sehemu ya chini unapowasilisha ukaguzi.", + "returnSendsMessage": "Kitufe cha kurudi hutuma ujumbe", + "returnSendsMessageSubtitle": "Kikiwa kimezimwa, kitufe cha kurudi huingiza mstari mpya kwenye sehemu za kuandika ujumbe kwa mawakala." }, "prReview": { "pullRequestUnavailable": "Pull request haipatikani", @@ -2589,7 +2591,15 @@ "cannotReceiveFiles": "Kikao hiki hakiwezi kupokea faili. Ondoa viambatisho ili utume ujumbe wako.", "couldNotAttachFiles": "Haikuweza kuambatisha faili. Gusa tuma ili ujaribu tena.", "discardAttachmentsMessage": "Faili zilizopakiwa ambazo hazijaambatishwa kwenye ujumbe uliotumwa zitafutwa.", - "discardAttachmentsTitle": "Tupa viambatisho?" + "discardAttachmentsTitle": "Tupa viambatisho?", + "charactersRemaining": "Herufi {{count}} zimesalia", + "insertNewline": "Ingiza mstari mpya", + "starterChipBuild": "Unda kipengele", + "starterChipEmpty": "Anza na wazo", + "starterChipFix": "Rekebisha hitilafu", + "starterChipPrepare": "Sanidi mazingira yangu", + "starterChipWrapUp": "Kamilisha kikao changu", + "starterChipWriteTests": "Andika majaribio" }, "chatLink": { "linkCopied": "Kiungo kimenakiliwa", diff --git a/apps/mobile/src/i18n/locales/ta.json b/apps/mobile/src/i18n/locales/ta.json index 7eb033f306..f07a750bfb 100644 --- a/apps/mobile/src/i18n/locales/ta.json +++ b/apps/mobile/src/i18n/locales/ta.json @@ -152,7 +152,9 @@ "appearanceDark": "இருள்", "account": "கணக்கு", "prReviewAttribution": "PR மதிப்பாய்வுகளில் பயன்பாட்டு பங்களிப்பைச் சேர்க்கவும்", - "prReviewAttributionSubtitle": "நீங்கள் ஒரு மதிப்பாய்வை சமர்ப்பிக்கும்போது Reviewed via Kilo அடிக்குறிப்பைச் சேர்க்கவும்" + "prReviewAttributionSubtitle": "நீங்கள் ஒரு மதிப்பாய்வை சமர்ப்பிக்கும்போது Reviewed via Kilo அடிக்குறிப்பைச் சேர்க்கவும்", + "returnSendsMessage": "திரும்பு விசை செய்தியை அனுப்பும்", + "returnSendsMessageSubtitle": "முடக்கப்பட்டால், திரும்பு விசை முகவர்களின் செய்தி உள்ளீடுகளில் புதிய வரியைச் செருகும்." }, "prReview": { "pullRequestUnavailable": "இழுப்பு கோரிக்கை கிடைக்கவில்லை", @@ -2589,7 +2591,15 @@ "cannotReceiveFiles": "இந்த அமர்வு கோப்புகளைப் பெற முடியாது. உங்கள் செய்தியை அனுப்ப இணைப்புகளை அகற்றவும்.", "couldNotAttachFiles": "கோப்புகளை இணைக்க முடியவில்லை. மீண்டும் முயற்சிக்க அனுப்பு என்பதைத் தட்டவும்.", "discardAttachmentsMessage": "அனுப்பிய செய்திகளில் இணைக்கப்படாத பதிவேற்றங்கள் நீக்கப்படும்.", - "discardAttachmentsTitle": "இணைப்புகளை நிராகரிக்கவா?" + "discardAttachmentsTitle": "இணைப்புகளை நிராகரிக்கவா?", + "charactersRemaining": "{{count}} எழுத்துகள் மீதமுள்ளன", + "insertNewline": "புதிய வரியைச் செருகவும்", + "starterChipBuild": "ஓர் அம்சத்தை உருவாக்கவும்", + "starterChipEmpty": "ஓர் யோசனையுடன் தொடங்கவும்", + "starterChipFix": "ஒரு பிழையைச் சரிசெய்யவும்", + "starterChipPrepare": "எனது சூழலை அமைக்கவும்", + "starterChipWrapUp": "எனது அமர்வை முடிக்கவும்", + "starterChipWriteTests": "சோதனைகளை எழுதவும்" }, "chatLink": { "linkCopied": "இணைப்பு நகலெடுக்கப்பட்டது", diff --git a/apps/mobile/src/i18n/locales/te.json b/apps/mobile/src/i18n/locales/te.json index caf955bf39..c81e10369d 100644 --- a/apps/mobile/src/i18n/locales/te.json +++ b/apps/mobile/src/i18n/locales/te.json @@ -152,7 +152,9 @@ "appearanceDark": "డార్క్", "account": "ఖాతా", "prReviewAttribution": "PR సమీక్షలకు యాప్ ఆపాదనను జోడించండి", - "prReviewAttributionSubtitle": "మీరు సమీక్షను సమర్పించినప్పుడు Kilo ద్వారా సమీక్షించబడింది అనే ఫుటర్ను జోడించండి." + "prReviewAttributionSubtitle": "మీరు సమీక్షను సమర్పించినప్పుడు Kilo ద్వారా సమీక్షించబడింది అనే ఫుటర్ను జోడించండి.", + "returnSendsMessage": "రిటర్న్ కీ సందేశాన్ని పంపుతుంది", + "returnSendsMessageSubtitle": "ఆఫ్లో ఉన్నప్పుడు, రిటర్న్ ఏజెంట్ సందేశ ఇన్పుట్లలో కొత్త పంక్తిని చేర్చుతుంది." }, "prReview": { "pullRequestUnavailable": "పుల్ రిక్వెస్ట్ అందుబాటులో లేదు", @@ -2589,7 +2591,15 @@ "cannotReceiveFiles": "ఈ సెషన్ ఫైల్లను స్వీకరించలేదు. మీ సందేశాన్ని పంపడానికి అటాచ్మెంట్లను తీసివేయండి.", "couldNotAttachFiles": "ఫైల్లను అటాచ్ చేయలేకపోయాము. మళ్లీ ప్రయత్నించడానికి పంపు నొక్కండి.", "discardAttachmentsMessage": "పంపిన సందేశాలకు జోడించని అప్లోడ్ చేసిన ఫైల్లు తొలగించబడతాయి.", - "discardAttachmentsTitle": "జోడింపులను విస్మరించాలా?" + "discardAttachmentsTitle": "జోడింపులను విస్మరించాలా?", + "charactersRemaining": "{{count}} అక్షరాలు మిగిలి ఉన్నాయి", + "insertNewline": "కొత్త పంక్తిని చేర్చండి", + "starterChipBuild": "ఒక ఫీచర్ను రూపొందించండి", + "starterChipEmpty": "ఒక ఆలోచనతో ప్రారంభించండి", + "starterChipFix": "ఒక లోపాన్ని పరిష్కరించండి", + "starterChipPrepare": "నా వాతావరణాన్ని సిద్ధం చేయండి", + "starterChipWrapUp": "నా సెషన్ను ముగించండి", + "starterChipWriteTests": "పరీక్షలు రాయండి" }, "chatLink": { "linkCopied": "లింక్ కాపీ చేయబడింది", diff --git a/apps/mobile/src/i18n/locales/th.json b/apps/mobile/src/i18n/locales/th.json index 3bbdbe63c3..c1b86df667 100644 --- a/apps/mobile/src/i18n/locales/th.json +++ b/apps/mobile/src/i18n/locales/th.json @@ -152,7 +152,9 @@ "appearanceDark": "มืด", "account": "บัญชี", "prReviewAttribution": "เพิ่มการระบุแหล่งที่มาของแอปในรีวิว PR", - "prReviewAttributionSubtitle": "ผนวกส่วนท้าย \"Reviewed via Kilo\" เมื่อคุณส่งรีวิว" + "prReviewAttributionSubtitle": "ผนวกส่วนท้าย \"Reviewed via Kilo\" เมื่อคุณส่งรีวิว", + "returnSendsMessage": "ปุ่มขึ้นบรรทัดใหม่ใช้ส่งข้อความ", + "returnSendsMessageSubtitle": "เมื่อปิด ปุ่มขึ้นบรรทัดใหม่จะแทรกบรรทัดใหม่ในช่องป้อนข้อความของเอเจนต์" }, "prReview": { "pullRequestUnavailable": "ไม่สามารถใช้ pull request ได้", @@ -2589,7 +2591,15 @@ "cannotReceiveFiles": "เซสชันนี้ไม่สามารถรับไฟล์ได้ นำไฟล์แนบออกเพื่อส่งข้อความของคุณ", "couldNotAttachFiles": "แนบไฟล์ไม่สำเร็จ แตะส่งเพื่อลองอีกครั้ง", "discardAttachmentsMessage": "ไฟล์ที่อัปโหลดแต่ยังไม่ได้แนบกับข้อความที่ส่งแล้วจะถูกลบ", - "discardAttachmentsTitle": "ทิ้งไฟล์แนบหรือไม่?" + "discardAttachmentsTitle": "ทิ้งไฟล์แนบหรือไม่?", + "charactersRemaining": "เหลือ {{count}} อักขระ", + "insertNewline": "แทรกบรรทัดใหม่", + "starterChipBuild": "สร้างฟีเจอร์", + "starterChipEmpty": "เริ่มต้นด้วยแนวคิด", + "starterChipFix": "แก้บั๊ก", + "starterChipPrepare": "ตั้งค่าสภาพแวดล้อมของฉัน", + "starterChipWrapUp": "จบเซสชันของฉัน", + "starterChipWriteTests": "เขียนการทดสอบ" }, "chatLink": { "linkCopied": "คัดลอกลิงก์แล้ว", diff --git a/apps/mobile/src/i18n/locales/tr.json b/apps/mobile/src/i18n/locales/tr.json index f33cce9789..86099f15e2 100644 --- a/apps/mobile/src/i18n/locales/tr.json +++ b/apps/mobile/src/i18n/locales/tr.json @@ -152,7 +152,9 @@ "appearanceDark": "Koyu", "account": "Hesap", "prReviewAttribution": "PR incelemelerine uygulama atfı ekle", - "prReviewAttributionSubtitle": "İnceleme gönderdiğinde 'Kilo ile incelendi' alt bilgisi ekle" + "prReviewAttributionSubtitle": "İnceleme gönderdiğinde 'Kilo ile incelendi' alt bilgisi ekle", + "returnSendsMessage": "Enter tuşu mesaj gönderir", + "returnSendsMessageSubtitle": "Kapalıyken Enter tuşu, ajan mesaj giriş alanlarına yeni satır ekler." }, "notifications": { "title": "Bildirimler", @@ -2135,7 +2137,15 @@ "cannotReceiveFiles": "Bu oturum dosya alamaz. Mesajınızı göndermek için ekleri kaldırın.", "couldNotAttachFiles": "Dosyalar eklenemedi. Yeniden denemek için Gönder’e dokunun.", "discardAttachmentsMessage": "Gönderilen mesajlara eklenmemiş yüklemeler silinecek.", - "discardAttachmentsTitle": "Ekler silinsin mi?" + "discardAttachmentsTitle": "Ekler silinsin mi?", + "charactersRemaining": "{{count}} karakter kaldı", + "insertNewline": "Yeni satır ekleyin", + "starterChipBuild": "Bir özellik geliştirin", + "starterChipEmpty": "Bir fikirle başlayın", + "starterChipFix": "Bir hatayı düzeltin", + "starterChipPrepare": "Ortamımı kurun", + "starterChipWrapUp": "Oturumumu tamamlayın", + "starterChipWriteTests": "Testler yazın" }, "chatLink": { "linkCopied": "Bağlantı kopyalandı", diff --git a/apps/mobile/src/i18n/locales/uk.json b/apps/mobile/src/i18n/locales/uk.json index 9885d8858d..4098da4768 100644 --- a/apps/mobile/src/i18n/locales/uk.json +++ b/apps/mobile/src/i18n/locales/uk.json @@ -152,7 +152,9 @@ "appearanceDark": "Темна", "account": "Обліковий запис", "prReviewAttribution": "Додати атрибуцію застосунку до перевірок PR", - "prReviewAttributionSubtitle": "Додавати нижній колонтитул Reviewed via Kilo, коли надсилаєте перевірку." + "prReviewAttributionSubtitle": "Додавати нижній колонтитул Reviewed via Kilo, коли надсилаєте перевірку.", + "returnSendsMessage": "Клавіша повернення надсилає повідомлення", + "returnSendsMessageSubtitle": "Коли вимкнено, клавіша повернення вставляє новий рядок у полях введення повідомлень агентам." }, "notifications": { "title": "Сповіщення", @@ -2159,7 +2161,15 @@ "cannotReceiveFiles": "Ця сесія не може отримувати файли. Видаліть вкладення, щоб надіслати повідомлення.", "couldNotAttachFiles": "Не вдалося прикріпити файли. Торкніться «Надіслати», щоб спробувати ще раз.", "discardAttachmentsMessage": "Завантажені файли, не прикріплені до надісланих повідомлень, буде видалено.", - "discardAttachmentsTitle": "Відкинути вкладення?" + "discardAttachmentsTitle": "Відкинути вкладення?", + "charactersRemaining": "Залишилося символів: {{count}}", + "insertNewline": "Вставити новий рядок", + "starterChipBuild": "Створити функцію", + "starterChipEmpty": "Почати з ідеї", + "starterChipFix": "Виправити помилку", + "starterChipPrepare": "Налаштувати моє середовище", + "starterChipWrapUp": "Завершити мій сеанс", + "starterChipWriteTests": "Написати тести" }, "chatLink": { "linkCopied": "Посилання скопійовано", diff --git a/apps/mobile/src/i18n/locales/ur.json b/apps/mobile/src/i18n/locales/ur.json index 748d6d5d75..76ee2035ac 100644 --- a/apps/mobile/src/i18n/locales/ur.json +++ b/apps/mobile/src/i18n/locales/ur.json @@ -152,7 +152,9 @@ "appearanceDark": "تاریک", "account": "اکاؤنٹ", "prReviewAttribution": "PR جائزوں میں ایپ انتساب شامل کریں", - "prReviewAttributionSubtitle": "جائزہ جمع کرتے وقت Reviewed via Kilo فوٹر جوڑیں۔" + "prReviewAttributionSubtitle": "جائزہ جمع کرتے وقت Reviewed via Kilo فوٹر جوڑیں۔", + "returnSendsMessage": "ریٹرن کلید پیغام بھیجتی ہے", + "returnSendsMessageSubtitle": "بند ہونے پر، ریٹرن ایجنٹ کے پیغام لکھنے کے خانوں میں نئی سطر داخل کرتی ہے۔" }, "prReview": { "pullRequestUnavailable": "پل ریکویسٹ دستیاب نہیں", @@ -2589,7 +2591,15 @@ "cannotReceiveFiles": "یہ سیشن فائلیں وصول نہیں کر سکتا۔ اپنا پیغام بھیجنے کے لیے اٹیچمنٹ ہٹائیں۔", "couldNotAttachFiles": "فائلیں منسلک نہیں ہو سکیں۔ دوبارہ کوشش کے لیے بھیجنے پر تھپتھپائیں۔", "discardAttachmentsMessage": "بھیجے گئے پیغامات سے غیر منسلک اپ لوڈز حذف ہو جائیں گے۔", - "discardAttachmentsTitle": "اٹیچمنٹس ضائع کریں؟" + "discardAttachmentsTitle": "اٹیچمنٹس ضائع کریں؟", + "charactersRemaining": "{{count}} حروف باقی ہیں", + "insertNewline": "نئی سطر داخل کریں", + "starterChipBuild": "ایک فیچر بنائیں", + "starterChipEmpty": "ایک خیال سے شروع کریں", + "starterChipFix": "ایک خرابی درست کریں", + "starterChipPrepare": "میرا ماحول ترتیب دیں", + "starterChipWrapUp": "میرا سیشن مکمل کریں", + "starterChipWriteTests": "ٹیسٹ لکھیں" }, "chatLink": { "linkCopied": "لنک کاپی ہو گیا", diff --git a/apps/mobile/src/i18n/locales/uz.json b/apps/mobile/src/i18n/locales/uz.json index 9f0abd55f1..6d33e07cdb 100644 --- a/apps/mobile/src/i18n/locales/uz.json +++ b/apps/mobile/src/i18n/locales/uz.json @@ -152,7 +152,9 @@ "appearanceDark": "Qorong'i", "account": "Hisob", "prReviewAttribution": "PR sharhlariga ilova atributini qo'shish", - "prReviewAttributionSubtitle": "Sharh yuborganingizda Reviewed via Kilo taglavhasini qo'shing." + "prReviewAttributionSubtitle": "Sharh yuborganingizda Reviewed via Kilo taglavhasini qo'shing.", + "returnSendsMessage": "Qaytarish tugmasi xabar yuboradi", + "returnSendsMessageSubtitle": "O'chirilganda, Qaytarish tugmasi agentlarning xabar kiritish maydonlarida yangi qator kiritadi." }, "prReview": { "pullRequestUnavailable": "Pull request mavjud emas", @@ -2589,7 +2591,15 @@ "cannotReceiveFiles": "Bu sessiya fayllarni qabul qila olmaydi. Xabarni yuborish uchun biriktirishlarni olib tashlang.", "couldNotAttachFiles": "Fayllarni biriktirib bo'lmadi. Qayta urinish uchun yuborishga bosing.", "discardAttachmentsMessage": "Yuborilgan xabarlarga biriktirilmagan yuklangan fayllar o'chiriladi.", - "discardAttachmentsTitle": "Biriktirilgan fayllar bekor qilinsinmi?" + "discardAttachmentsTitle": "Biriktirilgan fayllar bekor qilinsinmi?", + "charactersRemaining": "{{count}} ta belgi qoldi", + "insertNewline": "Yangi qator kiritish", + "starterChipBuild": "Funksiya yaratish", + "starterChipEmpty": "G'oya bilan boshlash", + "starterChipFix": "Xatoni tuzatish", + "starterChipPrepare": "Muhitimni sozlash", + "starterChipWrapUp": "Sessiyamni yakunlash", + "starterChipWriteTests": "Sinovlar yozish" }, "chatLink": { "linkCopied": "Havola nusxalandi", diff --git a/apps/mobile/src/i18n/locales/vi.json b/apps/mobile/src/i18n/locales/vi.json index fe8357b683..aa7ad381c5 100644 --- a/apps/mobile/src/i18n/locales/vi.json +++ b/apps/mobile/src/i18n/locales/vi.json @@ -152,7 +152,9 @@ "appearanceDark": "Tối", "account": "Tài khoản", "prReviewAttribution": "Thêm ghi công ứng dụng vào các đánh giá PR", - "prReviewAttributionSubtitle": "Thêm chân trang Reviewed via Kilo khi bạn gửi đánh giá." + "prReviewAttributionSubtitle": "Thêm chân trang Reviewed via Kilo khi bạn gửi đánh giá.", + "returnSendsMessage": "Phím xuống dòng gửi tin nhắn", + "returnSendsMessageSubtitle": "Khi tắt, phím xuống dòng chèn dòng mới trong ô nhập tin nhắn của tác nhân." }, "notifications": { "title": "Thông báo", @@ -2135,7 +2137,15 @@ "cannotReceiveFiles": "Phiên này không thể nhận tệp. Hãy gỡ tệp đính kèm để gửi tin nhắn của bạn.", "couldNotAttachFiles": "Không thể đính kèm tệp. Chạm vào Gửi để thử lại.", "discardAttachmentsMessage": "Các tệp đã tải lên nhưng chưa đính kèm vào tin nhắn đã gửi sẽ bị xóa.", - "discardAttachmentsTitle": "Hủy bỏ tệp đính kèm?" + "discardAttachmentsTitle": "Hủy bỏ tệp đính kèm?", + "charactersRemaining": "Còn {{count}} ký tự", + "insertNewline": "Chèn dòng mới", + "starterChipBuild": "Xây dựng một tính năng", + "starterChipEmpty": "Bắt đầu với một ý tưởng", + "starterChipFix": "Sửa lỗi", + "starterChipPrepare": "Thiết lập môi trường của bạn", + "starterChipWrapUp": "Kết thúc phiên của bạn", + "starterChipWriteTests": "Viết kiểm thử" }, "chatLink": { "linkCopied": "Đã sao chép liên kết", diff --git a/apps/mobile/src/i18n/locales/yo.json b/apps/mobile/src/i18n/locales/yo.json index ad89c0202a..5474648dc2 100644 --- a/apps/mobile/src/i18n/locales/yo.json +++ b/apps/mobile/src/i18n/locales/yo.json @@ -152,7 +152,9 @@ "appearanceDark": "Dudu", "account": "Àkọọ́lẹ̀", "prReviewAttribution": "Fi àtọ̀tọ̀ app kun si àwọn àtúnyẹ̀wò PR", - "prReviewAttributionSubtitle": "Fi àtẹ̀lẹ̀ 'Reviewed via Kilo' kun nígbà tí o bá fi àtúnyẹ̀wò sílẹ̀." + "prReviewAttributionSubtitle": "Fi àtẹ̀lẹ̀ 'Reviewed via Kilo' kun nígbà tí o bá fi àtúnyẹ̀wò sílẹ̀.", + "returnSendsMessage": "Bọtini ipadabọ n fi ifiranṣẹ ranṣẹ", + "returnSendsMessageSubtitle": "Nigbati o ba wa ni pipa, bọtini ipadabọ n fi ila tuntun sii ni awọn aaye kikọ ifiranṣẹ aṣoju." }, "prReview": { "pullRequestUnavailable": "Ibeere fifa ko si", @@ -2589,7 +2591,15 @@ "cannotReceiveFiles": "Igba-ijiroro yii ko le gba awọn faili. Yọ awọn asomọ lati fi ifiranṣẹ rẹ ranṣẹ.", "couldNotAttachFiles": "Ko le so awọn faili. Tẹ firanṣẹ lati tun gbiyanju.", "discardAttachmentsMessage": "A yoo paarẹ awọn faili ti a gbe soke ti a ko so mọ awọn ifiranṣẹ ti a fi ranṣẹ.", - "discardAttachmentsTitle": "Jù awọn asomọ silẹ?" + "discardAttachmentsTitle": "Jù awọn asomọ silẹ?", + "charactersRemaining": "Ohun kikọ {{count}} ku", + "insertNewline": "Fi ila tuntun sii", + "starterChipBuild": "Ṣẹda ẹya kan", + "starterChipEmpty": "Bẹrẹ pẹlu imọran kan", + "starterChipFix": "Ṣatunṣe aṣiṣe kan", + "starterChipPrepare": "Ṣeto ayika mi", + "starterChipWrapUp": "Pari akoko mi", + "starterChipWriteTests": "Kọ awọn idanwo" }, "chatLink": { "linkCopied": "Ọna asopọ ti daakọ", diff --git a/apps/mobile/src/i18n/locales/zh-Hans.json b/apps/mobile/src/i18n/locales/zh-Hans.json index 881c6bea8f..2ff6b25fd0 100644 --- a/apps/mobile/src/i18n/locales/zh-Hans.json +++ b/apps/mobile/src/i18n/locales/zh-Hans.json @@ -152,7 +152,9 @@ "appearanceDark": "深色", "account": "账户", "prReviewAttribution": "在 PR 审查中添加应用署名", - "prReviewAttributionSubtitle": "提交审查时,追加 Reviewed via Kilo 页脚。" + "prReviewAttributionSubtitle": "提交审查时,追加 Reviewed via Kilo 页脚。", + "returnSendsMessage": "回车键发送消息", + "returnSendsMessageSubtitle": "关闭后,回车键会在代理编辑器中插入换行。" }, "notifications": { "title": "通知", @@ -2135,7 +2137,15 @@ "cannotReceiveFiles": "此会话无法接收文件。请移除附件后再发送消息。", "couldNotAttachFiles": "无法附加文件。点击发送以重试。", "discardAttachmentsMessage": "未附加到已发送消息的上传文件将被删除。", - "discardAttachmentsTitle": "丢弃附件?" + "discardAttachmentsTitle": "丢弃附件?", + "charactersRemaining": "还可输入 {{count}} 个字符", + "insertNewline": "插入换行", + "starterChipBuild": "开发功能", + "starterChipEmpty": "从一个想法开始", + "starterChipFix": "修复错误", + "starterChipPrepare": "设置我的环境", + "starterChipWrapUp": "为我的会话收尾", + "starterChipWriteTests": "编写测试" }, "chatLink": { "linkCopied": "链接已复制", diff --git a/apps/mobile/src/i18n/locales/zh-Hant.json b/apps/mobile/src/i18n/locales/zh-Hant.json index 5b745371a1..f18fdd30e7 100644 --- a/apps/mobile/src/i18n/locales/zh-Hant.json +++ b/apps/mobile/src/i18n/locales/zh-Hant.json @@ -152,7 +152,9 @@ "appearanceDark": "深色", "account": "帳戶", "prReviewAttribution": "在 PR 審查中加入應用程式歸因", - "prReviewAttributionSubtitle": "提交審查時,附加「Reviewed via Kilo」頁尾。" + "prReviewAttributionSubtitle": "提交審查時,附加「Reviewed via Kilo」頁尾。", + "returnSendsMessage": "換行鍵傳送訊息", + "returnSendsMessageSubtitle": "關閉時,換行鍵會在代理撰寫器中插入換行。" }, "notifications": { "title": "通知", @@ -2135,7 +2137,15 @@ "cannotReceiveFiles": "此工作階段無法接收檔案。請移除附件後再傳送訊息。", "couldNotAttachFiles": "無法附加檔案。點選傳送以重試。", "discardAttachmentsMessage": "未附加至已傳送訊息的上傳檔案將遭刪除。", - "discardAttachmentsTitle": "捨棄附件?" + "discardAttachmentsTitle": "捨棄附件?", + "charactersRemaining": "剩餘 {{count}} 個字元", + "insertNewline": "插入換行", + "starterChipBuild": "建立功能", + "starterChipEmpty": "從一個想法開始", + "starterChipFix": "修正錯誤", + "starterChipPrepare": "設定我的環境", + "starterChipWrapUp": "為我的工作階段收尾", + "starterChipWriteTests": "撰寫測試" }, "chatLink": { "linkCopied": "連結已複製", diff --git a/apps/mobile/src/i18n/locales/zu.json b/apps/mobile/src/i18n/locales/zu.json index 3e59c14aa9..bda9d03bec 100644 --- a/apps/mobile/src/i18n/locales/zu.json +++ b/apps/mobile/src/i18n/locales/zu.json @@ -152,7 +152,9 @@ "appearanceDark": "Ubumnyama", "account": "Akhawunti", "prReviewAttribution": "Engeza ukukhonjwa kohlelo lokusebenza ezibuyekezweni ze-PR", - "prReviewAttributionSubtitle": "Engeza umugqa 'Reviewed via Kilo' uma uthumela isibuyekezo." + "prReviewAttributionSubtitle": "Engeza umugqa 'Reviewed via Kilo' uma uthumela isibuyekezo.", + "returnSendsMessage": "Ukhiye wokubuyela uthumela umlayezo", + "returnSendsMessageSubtitle": "Uma kuvaliwe, ukhiye wokubuyela ufaka umugqa omusha kuma-composer ama-ejenti." }, "prReview": { "pullRequestUnavailable": "Isicelo sokudonsa asitholakali", @@ -2589,7 +2591,15 @@ "cannotReceiveFiles": "Lesi seshini asikwazi ukwamukela amafayela. Susa okunamathiselwe ukuze uthumele umlayezo wakho.", "couldNotAttachFiles": "Asikwazanga ukunamathisela amafayela. Thepha thumela ukuzama futhi.", "discardAttachmentsMessage": "Amafayela alayishiwe anganamathiselwe emilayezweni ethunyelwe azosuswa.", - "discardAttachmentsTitle": "Lahla okunamathiselwe?" + "discardAttachmentsTitle": "Lahla okunamathiselwe?", + "charactersRemaining": "Kusele izinhlamvu ezingu-{{count}}", + "insertNewline": "Faka umugqa omusha", + "starterChipBuild": "Yakha isici", + "starterChipEmpty": "Qala ngombono", + "starterChipFix": "Lungisa iphutha", + "starterChipPrepare": "Setha imvelo yami", + "starterChipWrapUp": "Phetha iseshini yami", + "starterChipWriteTests": "Bhala izivivinyo" }, "chatLink": { "linkCopied": "Isixhumanisi sikopiwe", diff --git a/apps/mobile/src/lib/a11y/motion.ts b/apps/mobile/src/lib/a11y/motion.ts index 4d28adc0dc..5a5c007228 100644 --- a/apps/mobile/src/lib/a11y/motion.ts +++ b/apps/mobile/src/lib/a11y/motion.ts @@ -4,13 +4,26 @@ import { useReducedMotion } from 'react-native-reanimated'; // immediate when the system Reduce Motion setting is on. Action-sheet // presentation stays with the library and the OS: a sheet's own slide-in is // essential presentation motion, outside WCAG 2.3.3's non-essential scope. +// The composer's springs, pulses, shake, send-to-stop morph, and height +// transitions must become instant or a plain crossfade under the same policy. export type MotionPolicy = { /** `false` when reduce motion is on, so imperative scrolls go immediate. */ scrollAnimated: boolean; + /** `true` when the system Reduce Motion setting is on. */ + reducedMotion: boolean; }; /** Wrap Reanimated's `useReducedMotion` in the app's motion vocabulary. */ export function useMotionPolicy(): MotionPolicy { - return { scrollAnimated: !useReducedMotion() }; + const reducedMotion = useReducedMotion(); + return { scrollAnimated: !reducedMotion, reducedMotion }; +} + +/** Pick an instant or crossfade entrance for an optional overlay under the policy. */ +export function selectReducedMotionEntrance( + reducedMotion: boolean, + crossfade: T +): T | undefined { + return reducedMotion ? undefined : crossfade; } diff --git a/apps/mobile/src/lib/agent-attachments/use-agent-attachment-upload.test.ts b/apps/mobile/src/lib/agent-attachments/use-agent-attachment-upload.test.ts index 1110bac549..f867dc0653 100644 --- a/apps/mobile/src/lib/agent-attachments/use-agent-attachment-upload.test.ts +++ b/apps/mobile/src/lib/agent-attachments/use-agent-attachment-upload.test.ts @@ -747,6 +747,33 @@ describe('restoreFileParts — cancel/restore re-send admission', () => { expect(hookApi().attachments).toHaveLength(0); renderer.unmount(); }); + + it('replaces occupied-composer chips with the restored set instead of merging', async () => { + const renderer = await mountHook(); + // Seed an occupied composer with an existing chip. + await act(async () => { + hookApi().restoreChips([makeAttachment({ id: 'existing', filename: 'existing.pdf' })]); + await settle(); + }); + expect(hookApi().attachments.map(item => item.filename)).toEqual(['existing.pdf']); + + const restoredName = '8f14e45f-ceea-4b2a-8c6d-1a2b3c4d5e6f.pdf'; + await act(async () => { + hookApi().restoreFileParts([ + { + filename: restoredName, + mime: 'application/pdf', + url: `file:///tmp/attachments/session-1/user-1/msg-uuid-1/${restoredName}`, + }, + ]); + await settle(); + }); + + // The restored set replaces the seeded chip: no merge, no cap overrun. + expect(hookApi().attachments.map(item => item.filename)).toEqual([restoredName]); + expect(hookApi().attachments[0]?.id).not.toBe('existing'); + renderer.unmount(); + }); }); describe('useAgentAttachmentUpload — attachment reorder (attachment reorder)', () => { @@ -1171,6 +1198,46 @@ describe('useAgentAttachmentUpload — announcement ownership (Row 3.3)', () => renderer.unmount(); }); + it('never announces or writes back a chip discarded by restoreFileParts', async () => { + const renderer = await mountHook(); + await addDocument(); + const discardedId = hookApi().attachments[0]?.id; + if (!discardedId) { + throw new Error('attachment id missing'); + } + expect(hookApi().attachments[0]?.status).toBe('uploading'); + + const restoredName = '8f14e45f-ceea-4b2a-8c6d-1a2b3c4d5e6f.pdf'; + await act(async () => { + hookApi().restoreFileParts([ + { + filename: restoredName, + mime: 'application/pdf', + url: `file:///tmp/attachments/session-1/user-1/msg-uuid-1/${restoredName}`, + }, + ]); + await settle(); + }); + + // Restore replaces the occupied chip and cancels its in-flight upload. + expect(hookApi().attachments.map(item => item.filename)).toEqual([restoredName]); + expect(hookApi().attachments[0]?.id).not.toBe(discardedId); + expect(hoisted.cancelAsync).toHaveBeenCalledTimes(1); + + // The stale upload later resolves: it must not announce, toast, or write + // the discarded chip back into the attachments list. + await act(async () => { + resolveUpload?.({ key: 'org/2026/08/uuid/doc.pdf' }); + await settle(); + }); + + expect(hoisted.announceForA11y).not.toHaveBeenCalled(); + expect(hoisted.announcingToastError).not.toHaveBeenCalled(); + expect(hookApi().attachments.map(item => item.filename)).toEqual([restoredName]); + expect(hookApi().attachments.map(item => item.id)).not.toContain(discardedId); + renderer.unmount(); + }); + it('adds no stale candidates or uploads when reset runs during measurement', async () => { let resolveMeasure: ((size: number) => void) | undefined = undefined; hoisted.measureLocalSize.mockReturnValueOnce( diff --git a/apps/mobile/src/lib/agent-attachments/use-agent-attachment-upload.ts b/apps/mobile/src/lib/agent-attachments/use-agent-attachment-upload.ts index d8cf99c5ed..3ca798ea47 100644 --- a/apps/mobile/src/lib/agent-attachments/use-agent-attachment-upload.ts +++ b/apps/mobile/src/lib/agent-attachments/use-agent-attachment-upload.ts @@ -789,9 +789,21 @@ export function useAgentAttachmentUpload( progress: null, }; }); - commitAttachments(current => [...current, ...additions]); + // Restore replaces the current chips: the restored set is the canceled + // message's own file parts, so it already sits at or under the max-file + // cap and a merge with the occupied composer's chips would both overrun + // the cap and send the wrong file set. Replacing also discards any + // occupied-composer chips still uploading, so invalidate them exactly + // like `reset`/`clearOptimistic`: a stale async completion must not + // announce/toast or re-append a chip the user no longer sees. + generationRef.current += 1; + for (const id of liveIdsRef.current) { + cancelUpload(id); + } + liveIdsRef.current.clear(); + commitAttachments(() => additions); }, - [commitAttachments] + [cancelUpload, commitAttachments] ); // Optimistic-send chip clear: like `reset` but keeps the local cache files diff --git a/apps/mobile/src/lib/hooks/use-return-sends-message-preference.ts b/apps/mobile/src/lib/hooks/use-return-sends-message-preference.ts new file mode 100644 index 0000000000..7275963983 --- /dev/null +++ b/apps/mobile/src/lib/hooks/use-return-sends-message-preference.ts @@ -0,0 +1,26 @@ +import { useSyncExternalStore } from 'react'; + +import { createSecureStorePreference } from '@/lib/hooks/secure-store-preference'; +import { RETURN_SENDS_MESSAGE_KEY } from '@/lib/storage-keys'; + +/** + * Default-off preference: Return inserts a newline unless the user turns + * "Return sends" on, so the multiline composer keeps its standard newline + * behavior out of the box. + */ +const store = createSecureStorePreference({ + key: RETURN_SENDS_MESSAGE_KEY, + defaultValue: false, + parse: raw => raw === 'true', + serialize: value => (value ? 'true' : 'false'), +}); + +function setReturnSendsMessage(value: boolean) { + store.set(value); +} + +export function useReturnSendsMessagePreference() { + const returnSendsMessage = useSyncExternalStore(store.subscribe, store.get); + const hasLoaded = useSyncExternalStore(store.subscribe, store.getHasLoaded); + return { returnSendsMessage, hasLoaded, setReturnSendsMessage }; +} diff --git a/apps/mobile/src/lib/storage-keys.ts b/apps/mobile/src/lib/storage-keys.ts index 550e5acb9b..17f3bf431e 100644 --- a/apps/mobile/src/lib/storage-keys.ts +++ b/apps/mobile/src/lib/storage-keys.ts @@ -28,6 +28,8 @@ export const LOGIN_EMAIL_DRAFT_KEY = 'login-email-draft'; /** Login SSO-recovery banner draft, persisted before an RTL language reload. */ export const LOGIN_SSO_RECOVERY_DRAFT_KEY = 'login-sso-recovery-draft'; export const KEEP_SCREEN_ON_KEY = 'keep-session-screen-on'; +/** Return key in the agent composer sends/start instead of inserting a newline. */ +export const RETURN_SENDS_MESSAGE_KEY = 'return-sends-message'; /** Revocable per-host list of markdown link hosts that open without an Alert. */ export const TRUSTED_HOSTS_KEY = 'trusted-hosts'; export const PR_REVIEW_FOOTER_KEY = 'pr-review-footer-enabled';