diff --git a/components/mail/__tests__/mail-app-return-to-list.test.tsx b/components/mail/__tests__/mail-app-return-to-list.test.tsx index 6f64a44b3..84b280902 100644 --- a/components/mail/__tests__/mail-app-return-to-list.test.tsx +++ b/components/mail/__tests__/mail-app-return-to-list.test.tsx @@ -273,6 +273,75 @@ describe.each([ }); }); +// With the list beside the reading pane (split, horizontal) the setting, +// while nobody has changed it, opens the next message instead, as Outlook +// does. A choice the user made wins, on every layout and device. +describe('Archive with the list beside the reading pane', () => { + beforeEach(() => { + useSettingsStore.setState({ interfaceLayout: 'default', mailLayout: 'split', explicitSettings: [] } as never); + useEmailStore.setState({ listRows: null } as never); + }); + + it('opens the next message while the setting is untouched', async () => { + render(); + fireEvent.click(await screen.findByText('stub archive', {}, { timeout: 5000 })); + await waitFor(() => expect(client.moveEmail).toHaveBeenCalledWith('m1', 'archive', undefined)); + await waitFor(() => expect(selectedId()).toBe('m2')); + }); + + it('opens the next row in the order the list shows', async () => { + useEmailStore.setState({ + listRows: [{ key: 'm1', members: ['m1'] }, { key: 'm3', members: ['m3'] }, { key: 'm2', members: ['m2'] }], + } as never); + render(); + fireEvent.click(await screen.findByText('stub archive', {}, { timeout: 5000 })); + await waitFor(() => expect(client.moveEmail).toHaveBeenCalledWith('m1', 'archive', undefined)); + await waitFor(() => expect(selectedId()).toBe('m3')); + expect(opened).not.toContain('m2'); + }); + + it('goes back to the list when the user switched the setting on', async () => { + useSettingsStore.setState({ returnToListAfterAction: true, explicitSettings: ['returnToListAfterAction'] } as never); + render(); + fireEvent.click(await screen.findByText('stub archive', {}, { timeout: 5000 })); + await waitFor(() => expect(useEmailStore.getState().emails.map((e) => e.id)).toEqual(['m2', 'm3'])); + expect(selectedId()).toBeNull(); + expect(opened).not.toContain('m2'); + }); + + it('opens the archived message again on Undo', async () => { + client.restoreEmails = vi.fn().mockResolvedValue(undefined); + useEmailStore.setState({ + refreshCurrentMailbox: vi.fn(async () => { useEmailStore.setState({ emails: inbox() } as never); }), + } as never); + render(); + fireEvent.click(await screen.findByText('stub archive', {}, { timeout: 5000 })); + await waitFor(() => expect(selectedId()).toBe('m2')); + const archived = await waitFor(() => { + const found = useToastStore.getState().toasts.find((toast) => toast.title === 'notifications.email_archived'); + expect(found?.action).toBeTruthy(); + return found!; + }); + act(() => { archived.action!.onClick(); }); + await waitFor(() => expect(client.restoreEmails).toHaveBeenCalled()); + await waitFor(() => expect(selectedId()).toBe('m1')); + }); +}); + +describe('Delete with the list beside the reading pane', () => { + beforeEach(() => { + useSettingsStore.setState({ interfaceLayout: 'default', mailLayout: 'horizontal', explicitSettings: [] } as never); + useEmailStore.setState({ listRows: null } as never); + }); + + it('opens the next message while the setting is untouched', async () => { + render(); + fireEvent.click(await screen.findByText('stub delete', {}, { timeout: 5000 })); + await waitFor(() => expect(client.moveToTrash).toHaveBeenCalled()); + await waitFor(() => expect(selectedId()).toBe('m2')); + }); +}); + describe('A list collapsed under an open message (focused layout)', () => { const list = () => screen.getByText('message list').closest('section')!; diff --git a/components/mail/mail-app.tsx b/components/mail/mail-app.tsx index d3275629a..93e103158 100644 --- a/components/mail/mail-app.tsx +++ b/components/mail/mail-app.tsx @@ -81,6 +81,7 @@ import { cn, formatDateTime, getMailboxPath } from "@/lib/utils"; import { localizeMailboxName } from "@/lib/mailbox-label"; import { KEYWORD_PREFIX, KEYWORD_PREFIX_LEGACY, emailKeyFor, groupEmailsByThread, threadKeyFor } from "@/lib/thread-utils"; import { stepListEmail } from "@/lib/list-step"; +import { listBesideReader, returnToListAfterAction } from "@/lib/return-to-list"; import { resolveThreadRoute } from "@/lib/thread-routing"; import { ErrorBoundary, @@ -3341,12 +3342,41 @@ export function MailApp({ linkSegments: routeSegments }: MailAppProps = {}) { // holds (a quick filter dropped it) stays selected: the store finds it // through the selection (#695), and with no row to go on from it opens // nothing after it anyway. (#1458) + // + // Left alone, the setting follows the layout (lib/return-to-list): with the + // list beside the reading pane the next message opens instead, as in + // Outlook. With the conversation in the reading pane that is the next + // conversation down the list (the one above at the end), as its row opens it. + const returnsToList = () => returnToListAfterAction( + !fullscreenReading && listBesideReader({ mailLayout, interfaceLayout, isMobile, isTablet }), + ); const leaveIfOpen = (email: Email) => { - const { selectedEmail: open, emails: rows } = useEmailStore.getState(); + const { selectedEmail: open, emails: rows, listRows } = useEmailStore.getState(); const key = emailKeyFor(email); if (!open || emailKeyFor(open) !== key || (mvShell && cursorOnly)) return; if (!rows.some((row) => emailKeyFor(row) === key)) return; - if (useSettingsStore.getState().returnToListAfterAction) handleMobileBack(); + if (returnsToList()) { + handleMobileBack(); + return; + } + if (!paneConversationView || !listRows) return; + const below = stepListEmail(rows, key, 1, listRows); + const next = below && emailKeyFor(below) !== key ? below : stepListEmail(rows, key, -1, listRows); + if (!next || emailKeyFor(next) === key || threadKeyFor(next) === threadKeyFor(email)) return; + handleMobileBack(); + void handleEmailSelect(next); + }; + // Undo puts back the message that was open, and opens it again, where it + // was open in the reading pane. Phones stay in the list. + const reopenEmailRef = useRef<((email: Email) => Promise) | null>(null); + const reopenAfterUndo = (email: Email) => { + const { selectedEmail: open } = useEmailStore.getState(); + if (isMobile || !open || emailKeyFor(open) !== emailKeyFor(email)) return undefined; + const key = emailKeyFor(email); + return () => { + const restored = useEmailStore.getState().emails.find((e) => emailKeyFor(e) === key); + if (restored) void reopenEmailRef.current?.(restored); + }; }; const handleDelete = async (emailToDelete: Email | null = selectedEmail) => { @@ -3390,6 +3420,7 @@ export function MailApp({ linkSegments: routeSegments }: MailAppProps = {}) { return; } + const reopen = reopenAfterUndo(emailToDelete); leaveIfOpen(emailToDelete); // The store's delete reads the "Delete action" setting: move to the @@ -3398,7 +3429,7 @@ export function MailApp({ linkSegments: routeSegments }: MailAppProps = {}) { try { // false: a plugin stopped it and said why, so no "deleted" here. if (await deleteEmail(client, emailToDelete.id, isInTrash, emailKeyFor(emailToDelete))) { - toastWithUndo(t('notifications.email_deleted'), undoMoveAction(client, restore, undo)); + toastWithUndo(t('notifications.email_deleted'), undoMoveAction(client, restore, undo, undefined, reopen)); } } catch (error) { console.error("Failed to delete email:", error); @@ -3498,6 +3529,7 @@ export function MailApp({ linkSegments: routeSegments }: MailAppProps = {}) { } let restore: EmailRestoreBatch[] | null = null; + const reopen = reopenAfterUndo(emailToArchive); leaveIfOpen(emailToArchive); const archived = await moveThreadToMailbox(client, emailToArchive.id, destination.id, emailKeyFor(emailToArchive), (batches) => { restore = batches; }); if (!archived) return; @@ -3509,7 +3541,7 @@ export function MailApp({ linkSegments: routeSegments }: MailAppProps = {}) { setConversationEmails([]); } - toastWithUndo(t('notifications.email_archived'), undoMoveAction(client, restore, undo)); + toastWithUndo(t('notifications.email_archived'), undoMoveAction(client, restore, undo, undefined, reopen)); void refreshMailboxes(); } catch (error) { console.error("Failed to archive email:", error); @@ -5044,6 +5076,7 @@ export function MailApp({ linkSegments: routeSegments }: MailAppProps = {}) { }; pluginOpenEmailRef.current = handleEmailSelect; + reopenEmailRef.current = handleEmailSelect; // Handle back navigation from viewer on mobile. // Reset to list state directly. We can't just call window.history.back() @@ -6099,7 +6132,7 @@ export function MailApp({ linkSegments: routeSegments }: MailAppProps = {}) { // Marking the open message unread returns to the list // (Gmail-style, gated on returnToListAfterAction). Staying // in the reading pane would just re-mark it read on view. - if (!read && useSettingsStore.getState().returnToListAfterAction) { + if (!read && returnsToList()) { handleMobileBack(); } } diff --git a/components/settings/accessibility-settings.tsx b/components/settings/accessibility-settings.tsx index 3f6f606e2..515d9ea2e 100644 --- a/components/settings/accessibility-settings.tsx +++ b/components/settings/accessibility-settings.tsx @@ -12,6 +12,7 @@ import { useNoThirdPartyTraffic } from '@/hooks/use-third-party-mode'; import { openKeyboardShortcuts } from '@/lib/app-commands'; import { voicesForLocale } from '@/lib/read-aloud'; import { useSpeechVoices } from '@/hooks/use-read-aloud'; +import { useReturnToListAfterAction } from '@/hooks/use-return-to-list'; import { NOTIFICATION_SOUNDS, playNotificationSound, type NotificationSoundChoice } from '@/lib/notification-sound'; import { ACCESSIBILITY_KEYS, @@ -73,6 +74,8 @@ export function AccessibilitySettings() { const [resetDone, setResetDone] = useState(false); const locale = useLocale(); const speechVoices = useSpeechVoices(); + // As it applies in this layout: left alone, it follows the layout. + const returnToList = useReturnToListAfterAction(); const toastMin = getRestriction('toastDuration')?.min; const allowsValue = (key: string, value: unknown) => @@ -502,7 +505,7 @@ export function AccessibilitySettings() { )} - set('returnToListAfterAction', checked)} /> + set('returnToListAfterAction', checked)} /> diff --git a/components/settings/reading-settings.tsx b/components/settings/reading-settings.tsx index 0b5cf8222..3f9518ce5 100644 --- a/components/settings/reading-settings.tsx +++ b/components/settings/reading-settings.tsx @@ -11,6 +11,7 @@ import { usePolicyStore } from '@/stores/policy-store'; import { useContactStore } from '@/stores/contact-store'; import { useAuthStore } from '@/stores/auth-store'; import { useRemoteContentBlockedByOrg } from '@/hooks/use-third-party-mode'; +import { useReturnToListAfterAction } from '@/hooks/use-return-to-list'; /** * The Reading page: the open message. Opening it, how its body is drawn, @@ -26,12 +27,14 @@ export function ReadingSettings() { const tLock = useTranslations('remote_content_lock'); const t = useTranslations('settings.email_behavior'); const tPage = useTranslations('settings.reading_page'); + const tPane = useTranslations('conversation_pane'); + // As it applies in this layout: left alone, it follows the layout. + const returnToList = useReturnToListAfterAction(); const [showTrustedModal, setShowTrustedModal] = useState(false); const { isSettingLocked, isSettingHidden, isFeatureEnabled } = usePolicyStore(); const { markAsReadDelay, - returnToListAfterAction, mailAttachmentAction, attachmentPosition, hideInlineImageAttachments, @@ -78,9 +81,14 @@ export function ReadingSettings() { )} - + updateSetting('returnToListAfterAction', checked)} /> diff --git a/hooks/use-return-to-list.ts b/hooks/use-return-to-list.ts new file mode 100644 index 000000000..321dfeee0 --- /dev/null +++ b/hooks/use-return-to-list.ts @@ -0,0 +1,28 @@ +"use client"; + +import { useSettingsStore } from "@/stores/settings-store"; +import { usePolicyStore } from "@/stores/policy-store"; +import { useDeviceDetection } from "@/hooks/use-media-query"; +import { effectiveReturnToList, listBesideReader, returnToListSetByPolicy } from "@/lib/return-to-list"; + +/** + * The "Return to list after…" switch as it applies on this device and in this + * layout (lib/return-to-list), and whether that comes from the layout because + * nobody has set it yet. + */ +export function useReturnToListAfterAction(): { checked: boolean; followsLayout: boolean } { + const { isMobile, isTablet } = useDeviceDetection(); + const value = useSettingsStore((s) => s.returnToListAfterAction); + const explicitSettings = useSettingsStore((s) => s.explicitSettings); + const mailLayout = useSettingsStore((s) => s.mailLayout); + const interfaceLayout = useSettingsStore((s) => s.interfaceLayout); + const simpleMode = useSettingsStore((s) => s.simpleMode); + // Re-read when the policy arrives or changes. + usePolicyStore((s) => s.policy); + const policySet = returnToListSetByPolicy(); + const besideReader = listBesideReader({ mailLayout, interfaceLayout, simpleMode, isMobile, isTablet }); + return { + checked: effectiveReturnToList({ returnToListAfterAction: value, explicitSettings }, besideReader, policySet), + followsLayout: !policySet && !explicitSettings.includes("returnToListAfterAction"), + }; +} diff --git a/lib/__tests__/return-to-list.test.ts b/lib/__tests__/return-to-list.test.ts new file mode 100644 index 000000000..a19b16d2b --- /dev/null +++ b/lib/__tests__/return-to-list.test.ts @@ -0,0 +1,41 @@ +import { describe, it, expect } from 'vitest'; +import { effectiveReturnToList, listBesideReader } from '../return-to-list'; + +// "Return to list after…": left alone it follows the layout (next message +// with the list beside the reader, back to the list elsewhere); set by the +// user or the admin, it decides everywhere. + +const untouched = { returnToListAfterAction: true, explicitSettings: [] as string[] }; +const chosen = (value: boolean) => ({ returnToListAfterAction: value, explicitSettings: ['returnToListAfterAction'] }); + +describe('listBesideReader', () => { + const desktop = { isMobile: false, isTablet: false, interfaceLayout: 'default' as const }; + it('is true for the split and horizontal layouts on a desktop', () => { + expect(listBesideReader({ ...desktop, mailLayout: 'split' })).toBe(true); + expect(listBesideReader({ ...desktop, mailLayout: 'horizontal' })).toBe(true); + }); + it('is false where the message takes the list’s place', () => { + expect(listBesideReader({ ...desktop, mailLayout: 'focus' })).toBe(false); + expect(listBesideReader({ ...desktop, mailLayout: 'split', interfaceLayout: 'mountain-view' })).toBe(false); + expect(listBesideReader({ ...desktop, mailLayout: 'split', simpleMode: true })).toBe(false); + expect(listBesideReader({ ...desktop, mailLayout: 'split', isMobile: true })).toBe(false); + expect(listBesideReader({ ...desktop, mailLayout: 'split', isTablet: true })).toBe(false); + }); +}); + +describe('effectiveReturnToList', () => { + it('opens the next message beside the reader while nobody set it', () => { + expect(effectiveReturnToList(untouched, true, false)).toBe(false); + }); + it('goes back to the list elsewhere while nobody set it', () => { + expect(effectiveReturnToList(untouched, false, false)).toBe(true); + }); + it('follows the user’s choice on every layout', () => { + expect(effectiveReturnToList(chosen(true), true, false)).toBe(true); + expect(effectiveReturnToList(chosen(false), false, false)).toBe(false); + }); + it('follows an admin preset or lock', () => { + expect(effectiveReturnToList(untouched, true, true)).toBe(true); + expect(effectiveReturnToList({ ...untouched, returnToListAfterAction: false }, false, true)).toBe(false); + }); +}); diff --git a/lib/email-action-toast.ts b/lib/email-action-toast.ts index 663a3b417..02d987771 100644 --- a/lib/email-action-toast.ts +++ b/lib/email-action-toast.ts @@ -166,13 +166,14 @@ export async function setSelectionTagWithUndo( * * `receipts` (filled while the move runs) name the messages it took to * another account: Undo moves those back, the same way, into the folders - * their restore points name (undoMove). + * their restore points name (undoMove). `onUndone` runs once they are back. */ export function undoMoveAction( client: IJMAPClient | null | undefined, batches: EmailRestoreBatch[] | null | undefined, labels: UndoLabels, receipts?: CrossAccountMoveReceipt[], + onUndone?: () => void, ): UndoToastAction | undefined { if (!client || !batches || !batches.some(b => b.points.length > 0)) return undefined; return { @@ -183,6 +184,7 @@ export function undoMoveAction( try { await useEmailStore.getState().undoMove(client, batches, receipts ?? []); toast.success(labels.done); + onUndone?.(); } catch (error) { console.error("Failed to undo:", error); toast.error(labels.failed); diff --git a/lib/return-to-list.ts b/lib/return-to-list.ts new file mode 100644 index 000000000..f33055bcb --- /dev/null +++ b/lib/return-to-list.ts @@ -0,0 +1,50 @@ +import { effectiveMailLayout, useSettingsStore, type InterfaceLayout, type MailLayout } from '@/stores/settings-store'; +import { usePolicyStore } from '@/stores/policy-store'; + +const KEY = 'returnToListAfterAction'; + +/** + * Whether the message list and the reading pane are on screen side by side: + * the split and horizontal layouts on a desktop. Phones, tablets, the focused + * layout, Simple mode and Mountain View put the message in the list's place. + */ +export function listBesideReader(opts: { + mailLayout: MailLayout; + interfaceLayout: InterfaceLayout; + simpleMode?: boolean; + isMobile: boolean; + isTablet: boolean; +}): boolean { + if (opts.isMobile || opts.isTablet) return false; + return effectiveMailLayout(opts.mailLayout, opts.interfaceLayout, opts.simpleMode) !== 'focus'; +} + +/** + * "Return to list after delete, archive, move or mark unread", as it applies + * here. Set by the user (on any device: the choice syncs) or by the admin + * policy, the setting decides. Left alone, it follows the layout: with the + * list beside the reading pane the next message opens, as in Outlook, so a + * folder is worked through with e, e, e; where the message takes the list's + * place it goes back to the list. Each device judges its own layout. + */ +export function effectiveReturnToList( + settings: { returnToListAfterAction: boolean; explicitSettings: readonly string[] }, + besideReader: boolean, + policySet: boolean, +): boolean { + if (policySet || settings.explicitSettings.includes(KEY)) return settings.returnToListAfterAction; + return besideReader ? false : settings.returnToListAfterAction; +} + +/** Whether the admin policy presets or locks the setting. */ +export function returnToListSetByPolicy(): boolean { + const policy = usePolicyStore.getState(); + const defaults = policy.policy?.defaults; + return (!!defaults && Object.prototype.hasOwnProperty.call(defaults, KEY)) || policy.isSettingLocked(KEY); +} + +/** The setting as it applies now, read at the moment of the action. */ +export function returnToListAfterAction(besideReader: boolean): boolean { + return effectiveReturnToList(useSettingsStore.getState(), besideReader, returnToListSetByPolicy()); +} + diff --git a/locales/ar/common.json b/locales/ar/common.json index b2bc4e274..8c8405c01 100644 --- a/locales/ar/common.json +++ b/locales/ar/common.json @@ -3767,7 +3767,8 @@ "collapse_named": "طي المحادثة: {subject}" }, "conversation_pane": { - "show_older": "{count, plural, zero {عرض # رسالة أقدم} one {عرض رسالة أقدم واحدة} two {عرض رسالتين أقدم} few {عرض # رسائل أقدم} many {عرض # رسالة أقدم} other {عرض # رسالة أقدم}}" + "show_older": "{count, plural, zero {عرض # رسالة أقدم} one {عرض رسالة أقدم واحدة} two {عرض رسالتين أقدم} few {عرض # رسائل أقدم} many {عرض # رسالة أقدم} other {عرض # رسالة أقدم}}", + "return_to_list_follows_layout": "ما لم تغيّره، يتبع هذا التخطيط: متوقف عندما تكون القائمة والرسالة جنبًا إلى جنب، ومفعّل في غير ذلك." }, "identities": { "modal_title": "إدارة هويات الإرسال", diff --git a/locales/ca/common.json b/locales/ca/common.json index 40ad249c3..cafa38091 100644 --- a/locales/ca/common.json +++ b/locales/ca/common.json @@ -3767,7 +3767,8 @@ "collapse_named": "Replega la conversa: {subject}" }, "conversation_pane": { - "show_older": "{count, plural, one {Mostra # missatge anterior} other {Mostra # missatges anteriors}}" + "show_older": "{count, plural, one {Mostra # missatge anterior} other {Mostra # missatges anteriors}}", + "return_to_list_follows_layout": "Mentre no ho canviïs, segueix el disseny: desactivat quan la llista i el missatge són l'un al costat de l'altre, activat en els altres casos." }, "identities": { "modal_title": "Gestiona les identitats d'enviament", diff --git a/locales/cs/common.json b/locales/cs/common.json index 8a997fe35..a5fcd86e4 100644 --- a/locales/cs/common.json +++ b/locales/cs/common.json @@ -3767,7 +3767,8 @@ "collapse_named": "Sbalit konverzaci: {subject}" }, "conversation_pane": { - "show_older": "{count, plural, one {Zobrazit # starší zprávu} few {Zobrazit # starší zprávy} many {Zobrazit # starší zprávy} other {Zobrazit # starších zpráv}}" + "show_older": "{count, plural, one {Zobrazit # starší zprávu} few {Zobrazit # starší zprávy} many {Zobrazit # starší zprávy} other {Zobrazit # starších zpráv}}", + "return_to_list_follows_layout": "Dokud to nezměníte, řídí se to rozvržením: vypnuto, když jsou seznam a zpráva vedle sebe, jinak zapnuto." }, "identities": { "modal_title": "Správa identit odesílatele", diff --git a/locales/da/common.json b/locales/da/common.json index 34186500f..bf7e32cc4 100644 --- a/locales/da/common.json +++ b/locales/da/common.json @@ -3767,7 +3767,8 @@ "collapse_named": "Skjul samtale: {subject}" }, "conversation_pane": { - "show_older": "{count, plural, one {Vis # ældre besked} other {Vis # ældre beskeder}}" + "show_older": "{count, plural, one {Vis # ældre besked} other {Vis # ældre beskeder}}", + "return_to_list_follows_layout": "Indtil du ændrer det, følger det layoutet: fra, når listen og beskeden står side om side, ellers til." }, "identities": { "modal_title": "Administrér afsenderidentiteter", diff --git a/locales/de/common.json b/locales/de/common.json index d59fe5f83..da7a123ff 100644 --- a/locales/de/common.json +++ b/locales/de/common.json @@ -3767,7 +3767,8 @@ "collapse_named": "Konversation zuklappen: {subject}" }, "conversation_pane": { - "show_older": "{count, plural, one {# ältere Nachricht anzeigen} other {# ältere Nachrichten anzeigen}}" + "show_older": "{count, plural, one {# ältere Nachricht anzeigen} other {# ältere Nachrichten anzeigen}}", + "return_to_list_follows_layout": "Solange Sie es nicht ändern, richtet sich das nach dem Layout: aus, wenn Liste und Nachricht nebeneinander stehen, sonst an." }, "identities": { "modal_title": "Sendeidentitäten verwalten", diff --git a/locales/en/common.json b/locales/en/common.json index ee5c9a417..bdc783836 100644 --- a/locales/en/common.json +++ b/locales/en/common.json @@ -3767,7 +3767,8 @@ "collapse_named": "Collapse conversation: {subject}" }, "conversation_pane": { - "show_older": "{count, plural, one {Show # older message} other {Show # older messages}}" + "show_older": "{count, plural, one {Show # older message} other {Show # older messages}}", + "return_to_list_follows_layout": "Until you change it, this follows the layout: off when the list and the message are side by side, on otherwise." }, "identities": { "modal_title": "Manage sending identities", diff --git a/locales/es/common.json b/locales/es/common.json index 4f17170ac..e29fba479 100644 --- a/locales/es/common.json +++ b/locales/es/common.json @@ -3767,7 +3767,8 @@ "collapse_named": "Contraer conversación: {subject}" }, "conversation_pane": { - "show_older": "{count, plural, one {Mostrar # mensaje anterior} other {Mostrar # mensajes anteriores}}" + "show_older": "{count, plural, one {Mostrar # mensaje anterior} other {Mostrar # mensajes anteriores}}", + "return_to_list_follows_layout": "Mientras no lo cambies, sigue el diseño: desactivado cuando la lista y el mensaje están uno al lado del otro, activado en los demás casos." }, "identities": { "modal_title": "Administrar Identidades de Envío", diff --git a/locales/et/common.json b/locales/et/common.json index 093b43f0a..b6230b0df 100644 --- a/locales/et/common.json +++ b/locales/et/common.json @@ -3767,7 +3767,8 @@ "collapse_named": "Sulge vestlus: {subject}" }, "conversation_pane": { - "show_older": "{count, plural, one {Näita # vanemat sõnumit} other {Näita # vanemat sõnumit}}" + "show_older": "{count, plural, one {Näita # vanemat sõnumit} other {Näita # vanemat sõnumit}}", + "return_to_list_follows_layout": "Kuni te seda ei muuda, järgib see paigutust: väljas, kui loend ja sõnum on kõrvuti, muidu sees." }, "identities": { "modal_title": "Halda saatja identiteete", diff --git a/locales/fa/common.json b/locales/fa/common.json index 625dfe66a..8acc6a0dc 100644 --- a/locales/fa/common.json +++ b/locales/fa/common.json @@ -3767,7 +3767,8 @@ "collapse_named": "جمع کردن مکالمه: {subject}" }, "conversation_pane": { - "show_older": "{count, plural, one {نمایش # پیام قدیمی‌تر} other {نمایش # پیام قدیمی‌تر}}" + "show_older": "{count, plural, one {نمایش # پیام قدیمی‌تر} other {نمایش # پیام قدیمی‌تر}}", + "return_to_list_follows_layout": "تا وقتی آن را تغییر ندهید، از چیدمان پیروی می‌کند: وقتی فهرست و پیام کنار هم هستند خاموش، در غیر این صورت روشن." }, "identities": { "modal_title": "مدیریت هویت‌های ارسال", diff --git a/locales/fr/common.json b/locales/fr/common.json index dfb97587f..8fbf87d51 100644 --- a/locales/fr/common.json +++ b/locales/fr/common.json @@ -3767,7 +3767,8 @@ "collapse_named": "Réduire la conversation: {subject}" }, "conversation_pane": { - "show_older": "{count, plural, one {Afficher # message plus ancien} other {Afficher # messages plus anciens}}" + "show_older": "{count, plural, one {Afficher # message plus ancien} other {Afficher # messages plus anciens}}", + "return_to_list_follows_layout": "Tant que vous ne le modifiez pas, ce réglage suit la disposition : désactivé quand la liste et le message sont côte à côte, activé sinon." }, "identities": { "modal_title": "Gérer les identités d'envoi", diff --git a/locales/he/common.json b/locales/he/common.json index 78173a829..9892d2813 100644 --- a/locales/he/common.json +++ b/locales/he/common.json @@ -3664,7 +3664,8 @@ "collapse_named": "כווץ את השיחה: {subject}" }, "conversation_pane": { - "show_older": "{count, plural, one {הצגת הודעה ישנה אחת} two {הצגת שתי הודעות ישנות} other {הצגת # הודעות ישנות}}" + "show_older": "{count, plural, one {הצגת הודעה ישנה אחת} two {הצגת שתי הודעות ישנות} other {הצגת # הודעות ישנות}}", + "return_to_list_follows_layout": "כל עוד לא תשנו זאת, זה נקבע לפי הפריסה: כבוי כשהרשימה וההודעה זו לצד זו, ופועל בכל מצב אחר." }, "identities": { "modal_title": "ניהול זהויות שליחה", diff --git a/locales/hu/common.json b/locales/hu/common.json index 1b29ecf12..5d1e83f08 100644 --- a/locales/hu/common.json +++ b/locales/hu/common.json @@ -3767,7 +3767,8 @@ "collapse_named": "Beszélgetés összecsukása: {subject}" }, "conversation_pane": { - "show_older": "{count, plural, one {# régebbi üzenet megjelenítése} other {# régebbi üzenet megjelenítése}}" + "show_older": "{count, plural, one {# régebbi üzenet megjelenítése} other {# régebbi üzenet megjelenítése}}", + "return_to_list_follows_layout": "Amíg nem módosítja, az elrendezést követi: ki van kapcsolva, ha a lista és az üzenet egymás mellett van, egyébként be." }, "identities": { "modal_title": "Küldő azonosságok kezelése", diff --git a/locales/it/common.json b/locales/it/common.json index 557adfd78..d1e9d7bc0 100644 --- a/locales/it/common.json +++ b/locales/it/common.json @@ -3767,7 +3767,8 @@ "collapse_named": "Comprimi conversazione: {subject}" }, "conversation_pane": { - "show_older": "{count, plural, one {Mostra # messaggio precedente} other {Mostra # messaggi precedenti}}" + "show_older": "{count, plural, one {Mostra # messaggio precedente} other {Mostra # messaggi precedenti}}", + "return_to_list_follows_layout": "Finché non lo modifichi, segue il layout: disattivato quando l'elenco e il messaggio sono affiancati, attivato negli altri casi." }, "identities": { "modal_title": "Gestisci identità di invio", diff --git a/locales/ja/common.json b/locales/ja/common.json index ed70f18de..1d5a322f4 100644 --- a/locales/ja/common.json +++ b/locales/ja/common.json @@ -3767,7 +3767,8 @@ "collapse_named": "会話を折りたたむ: {subject}" }, "conversation_pane": { - "show_older": "{count, plural, other {以前のメッセージを # 件表示}}" + "show_older": "{count, plural, other {以前のメッセージを # 件表示}}", + "return_to_list_follows_layout": "変更するまでは、レイアウトに合わせて切り替わります。一覧とメッセージが横に並ぶ場合はオフ、それ以外はオンです。" }, "identities": { "modal_title": "送信者情報を管理", diff --git a/locales/ko/common.json b/locales/ko/common.json index f7a56f155..73e70e37b 100644 --- a/locales/ko/common.json +++ b/locales/ko/common.json @@ -3767,7 +3767,8 @@ "collapse_named": "대화 접기: {subject}" }, "conversation_pane": { - "show_older": "{count, plural, other {이전 메시지 #개 보기}}" + "show_older": "{count, plural, other {이전 메시지 #개 보기}}", + "return_to_list_follows_layout": "변경하기 전까지는 레이아웃을 따릅니다. 목록과 메시지가 나란히 있으면 꺼지고, 그 밖에는 켜집니다." }, "identities": { "modal_title": "보내는 사람 관리", diff --git a/locales/lv/common.json b/locales/lv/common.json index 76422c867..540a5447b 100644 --- a/locales/lv/common.json +++ b/locales/lv/common.json @@ -3767,7 +3767,8 @@ "collapse_named": "Sairt sarunu: {subject}" }, "conversation_pane": { - "show_older": "{count, plural, zero {Rādīt # vecākas ziņas} one {Rādīt # vecāku ziņu} other {Rādīt # vecākas ziņas}}" + "show_older": "{count, plural, zero {Rādīt # vecākas ziņas} one {Rādīt # vecāku ziņu} other {Rādīt # vecākas ziņas}}", + "return_to_list_follows_layout": "Kamēr to nemaināt, tas seko izkārtojumam: izslēgts, ja saraksts un ziņa ir blakus, citādi ieslēgts." }, "identities": { "modal_title": "Pārvaldīt sūtītāja identitātes", diff --git a/locales/mn/common.json b/locales/mn/common.json index 959626386..76b03b40c 100644 --- a/locales/mn/common.json +++ b/locales/mn/common.json @@ -3767,7 +3767,8 @@ "collapse_named": "Харилцан яриаг хураах: {subject}" }, "conversation_pane": { - "show_older": "{count, plural, one {Өмнөх # захиа харуулах} other {Өмнөх # захиа харуулах}}" + "show_older": "{count, plural, one {Өмнөх # захиа харуулах} other {Өмнөх # захиа харуулах}}", + "return_to_list_follows_layout": "Та өөрчлөхөөс нааш энэ нь байрлалаас хамаарна: жагсаалт ба захиа зэрэгцэн байвал унтраалттай, бусад үед асаалттай." }, "identities": { "modal_title": "Илгээж буй таниулбаруудыг удирдах", diff --git a/locales/nb/common.json b/locales/nb/common.json index be28a77a1..d49859663 100644 --- a/locales/nb/common.json +++ b/locales/nb/common.json @@ -3767,7 +3767,8 @@ "collapse_named": "Fold sammen samtalen: {subject}" }, "conversation_pane": { - "show_older": "{count, plural, one {Vis # eldre melding} other {Vis # eldre meldinger}}" + "show_older": "{count, plural, one {Vis # eldre melding} other {Vis # eldre meldinger}}", + "return_to_list_follows_layout": "Inntil du endrer det, følger det oppsettet: av når listen og meldingen står side om side, ellers på." }, "identities": { "modal_title": "Administrer avsenderidentiteter", diff --git a/locales/nl/common.json b/locales/nl/common.json index 78360310b..453708b0e 100644 --- a/locales/nl/common.json +++ b/locales/nl/common.json @@ -3767,7 +3767,8 @@ "collapse_named": "Gesprek inklappen: {subject}" }, "conversation_pane": { - "show_older": "{count, plural, one {# ouder bericht tonen} other {# oudere berichten tonen}}" + "show_older": "{count, plural, one {# ouder bericht tonen} other {# oudere berichten tonen}}", + "return_to_list_follows_layout": "Zolang je het niet wijzigt, volgt dit de indeling: uit als de lijst en het bericht naast elkaar staan, anders aan." }, "identities": { "modal_title": "Verzendidentiteiten beheren", diff --git a/locales/pl/common.json b/locales/pl/common.json index 122ee9b3b..a1966800e 100644 --- a/locales/pl/common.json +++ b/locales/pl/common.json @@ -3767,7 +3767,8 @@ "collapse_named": "Zwiń konwersację: {subject}" }, "conversation_pane": { - "show_older": "{count, plural, one {Pokaż # starszą wiadomość} few {Pokaż # starsze wiadomości} many {Pokaż # starszych wiadomości} other {Pokaż # starszej wiadomości}}" + "show_older": "{count, plural, one {Pokaż # starszą wiadomość} few {Pokaż # starsze wiadomości} many {Pokaż # starszych wiadomości} other {Pokaż # starszej wiadomości}}", + "return_to_list_follows_layout": "Dopóki tego nie zmienisz, ustawienie zależy od układu: wyłączone, gdy lista i wiadomość są obok siebie, w innych przypadkach włączone." }, "identities": { "modal_title": "Zarządzaj tożsamościami nadawcy", diff --git a/locales/pt/common.json b/locales/pt/common.json index 115f3dd10..6a9ec6cd6 100644 --- a/locales/pt/common.json +++ b/locales/pt/common.json @@ -3767,7 +3767,8 @@ "collapse_named": "Recolher conversa: {subject}" }, "conversation_pane": { - "show_older": "{count, plural, one {Mostrar # mensagem anterior} other {Mostrar # mensagens anteriores}}" + "show_older": "{count, plural, one {Mostrar # mensagem anterior} other {Mostrar # mensagens anteriores}}", + "return_to_list_follows_layout": "Enquanto não alterar, segue o layout: desligado quando a lista e a mensagem estão lado a lado, ligado nos outros casos." }, "identities": { "modal_title": "Gerenciar Identidades de Envio", diff --git a/locales/ro/common.json b/locales/ro/common.json index 9affac340..db173df66 100644 --- a/locales/ro/common.json +++ b/locales/ro/common.json @@ -3767,7 +3767,8 @@ "collapse_named": "Ascunde conversația: {subject}" }, "conversation_pane": { - "show_older": "{count, plural, one {Afișează # mesaj mai vechi} few {Afișează # mesaje mai vechi} other {Afișează # de mesaje mai vechi}}" + "show_older": "{count, plural, one {Afișează # mesaj mai vechi} few {Afișează # mesaje mai vechi} other {Afișează # de mesaje mai vechi}}", + "return_to_list_follows_layout": "Cât timp nu îl modificați, urmează aspectul: dezactivat când lista și mesajul sunt unul lângă altul, activat în rest." }, "identities": { "modal_title": "Gestionarea identităților de expediere", diff --git a/locales/ru/common.json b/locales/ru/common.json index c7477e0e3..1a38fa1b6 100644 --- a/locales/ru/common.json +++ b/locales/ru/common.json @@ -3767,7 +3767,8 @@ "collapse_named": "Свернуть разговор: {subject}" }, "conversation_pane": { - "show_older": "{count, plural, one {Показать # более раннее письмо} few {Показать # более ранних письма} many {Показать # более ранних писем} other {Показать # более раннего письма}}" + "show_older": "{count, plural, one {Показать # более раннее письмо} few {Показать # более ранних письма} many {Показать # более ранних писем} other {Показать # более раннего письма}}", + "return_to_list_follows_layout": "Пока вы это не измените, настройка зависит от макета: выключена, когда список и письмо стоят рядом, в остальных случаях включена." }, "identities": { "modal_title": "Управление идентификациями отправителя", diff --git a/locales/sk/common.json b/locales/sk/common.json index 855ed2980..157bce03b 100644 --- a/locales/sk/common.json +++ b/locales/sk/common.json @@ -3767,7 +3767,8 @@ "collapse_named": "Zbaliť konverzáciu: {subject}" }, "conversation_pane": { - "show_older": "{count, plural, one {Zobraziť # staršiu správu} few {Zobraziť # staršie správy} many {Zobraziť # staršej správy} other {Zobraziť # starších správ}}" + "show_older": "{count, plural, one {Zobraziť # staršiu správu} few {Zobraziť # staršie správy} many {Zobraziť # staršej správy} other {Zobraziť # starších správ}}", + "return_to_list_follows_layout": "Kým to nezmeníte, riadi sa to rozložením: vypnuté, keď sú zoznam a správa vedľa seba, inak zapnuté." }, "identities": { "modal_title": "Správa identít odosielateľa", diff --git a/locales/th/common.json b/locales/th/common.json index 7be585856..fed7cae1c 100644 --- a/locales/th/common.json +++ b/locales/th/common.json @@ -614,7 +614,8 @@ "collapse_named": "ย่อการสนทนา: {subject}" }, "conversation_pane": { - "show_older": "{count, plural, other {แสดงข้อความก่อนหน้า # ฉบับ}}" + "show_older": "{count, plural, other {แสดงข้อความก่อนหน้า # ฉบับ}}", + "return_to_list_follows_layout": "จนกว่าคุณจะเปลี่ยน ค่านี้จะเป็นไปตามเลย์เอาต์: ปิดเมื่อรายการและข้อความอยู่เคียงข้างกัน และเปิดในกรณีอื่น" }, "notifications": { "email_sent": "ส่งอีเมลแล้ว", diff --git a/locales/tr/common.json b/locales/tr/common.json index cdd2c6f4e..f78660132 100644 --- a/locales/tr/common.json +++ b/locales/tr/common.json @@ -3767,7 +3767,8 @@ "collapse_named": "Konuşmayı daralt: {subject}" }, "conversation_pane": { - "show_older": "{count, plural, one {# eski iletiyi göster} other {# eski iletiyi göster}}" + "show_older": "{count, plural, one {# eski iletiyi göster} other {# eski iletiyi göster}}", + "return_to_list_follows_layout": "Siz değiştirene kadar bu ayar düzene göre belirlenir: liste ve ileti yan yanaysa kapalı, değilse açık." }, "identities": { "modal_title": "Gönderme Kimliklerini Yönet", diff --git a/locales/uk/common.json b/locales/uk/common.json index 3cea7cf24..a4dbb9f74 100644 --- a/locales/uk/common.json +++ b/locales/uk/common.json @@ -3767,7 +3767,8 @@ "collapse_named": "Згорнути бесіду: {subject}" }, "conversation_pane": { - "show_older": "{count, plural, one {Показати # давніший лист} few {Показати # давніші листи} many {Показати # давніших листів} other {Показати # давнішого листа}}" + "show_older": "{count, plural, one {Показати # давніший лист} few {Показати # давніші листи} many {Показати # давніших листів} other {Показати # давнішого листа}}", + "return_to_list_follows_layout": "Поки ви це не зміните, налаштування залежить від макета: вимкнено, коли список і лист розташовані поруч, в інших випадках увімкнено." }, "identities": { "modal_title": "Керування ідентифікаторами надсилання", diff --git a/locales/vi/common.json b/locales/vi/common.json index a31ecfdb3..358ab13ce 100644 --- a/locales/vi/common.json +++ b/locales/vi/common.json @@ -3779,7 +3779,8 @@ "collapse_named": "Thu gọn cuộc trò chuyện: {subject}" }, "conversation_pane": { - "show_older": "{count, plural, other {Hiện # thư cũ hơn}}" + "show_older": "{count, plural, other {Hiện # thư cũ hơn}}", + "return_to_list_follows_layout": "Cho đến khi bạn thay đổi, cài đặt này theo bố cục: tắt khi danh sách và thư nằm cạnh nhau, bật trong các trường hợp khác." }, "identities": { "modal_title": "Quản lý danh tính gửi thư", diff --git a/locales/zh-TW/common.json b/locales/zh-TW/common.json index 1cc124fc6..fc3074406 100644 --- a/locales/zh-TW/common.json +++ b/locales/zh-TW/common.json @@ -3767,7 +3767,8 @@ "collapse_named": "收合對話串: {subject}" }, "conversation_pane": { - "show_older": "{count, plural, other {顯示 # 封較早的郵件}}" + "show_older": "{count, plural, other {顯示 # 封較早的郵件}}", + "return_to_list_follows_layout": "在您變更之前,此項會依版面配置而定:清單與郵件並排顯示時關閉,其他情況下開啟。" }, "identities": { "modal_title": "管理寄件身分", diff --git a/locales/zh/common.json b/locales/zh/common.json index 23cecd720..d3c3d661c 100644 --- a/locales/zh/common.json +++ b/locales/zh/common.json @@ -3767,7 +3767,8 @@ "collapse_named": "折叠会话: {subject}" }, "conversation_pane": { - "show_older": "{count, plural, other {显示 # 封较早的邮件}}" + "show_older": "{count, plural, other {显示 # 封较早的邮件}}", + "return_to_list_follows_layout": "在你更改之前,此项随布局而定:列表和邮件并排显示时关闭,其他情况下开启。" }, "identities": { "modal_title": "管理发送身份",