From 41fcbba61c63d119f0a31763b336f0a0ddfc848a Mon Sep 17 00:00:00 2001 From: Linus Rath <139418639+rathlinus@users.noreply.github.com> Date: Tue, 3 Nov 2026 12:24:14 +0100 Subject: [PATCH] fix: keep sender and subject off the OS until they're known to be allowed, leave nothing on it after sign-out, and take files only where they're meant --- components/desktop/desktop-os-host.tsx | 10 +- components/email/email-composer.tsx | 7 +- components/email/email-viewer.tsx | 22 ++- .../protocol-launch-handler-provider.tsx | 2 + lib/desktop/__tests__/os-commands.test.ts | 30 +++- lib/desktop/__tests__/os-mail.test.ts | 11 ++ lib/desktop/__tests__/os-store.test.ts | 11 ++ lib/desktop/__tests__/os-window.test.tsx | 168 +++++++++++++++++- lib/desktop/bridge.ts | 5 + lib/desktop/mail-notifications.ts | 12 +- lib/desktop/os-commands.ts | 9 + lib/desktop/os-summary.ts | 26 ++- lib/desktop/power.ts | 24 ++- lib/desktop/types.ts | 11 +- lib/desktop/window-files.ts | 45 ++++- lib/offline/offline-mode.ts | 3 + lib/sign-out-cleanup.ts | 2 + locales/ar/common.json | 31 ++++ locales/ca/common.json | 31 ++++ locales/cs/common.json | 31 ++++ locales/da/common.json | 31 ++++ locales/de/common.json | 31 ++++ locales/en/common.json | 31 ++++ locales/es/common.json | 31 ++++ locales/et/common.json | 31 ++++ locales/fa/common.json | 31 ++++ locales/fr/common.json | 31 ++++ locales/he/common.json | 31 ++++ locales/hu/common.json | 31 ++++ locales/it/common.json | 31 ++++ locales/ja/common.json | 31 ++++ locales/ko/common.json | 31 ++++ locales/lv/common.json | 31 ++++ locales/mn/common.json | 31 ++++ locales/nb/common.json | 31 ++++ locales/nl/common.json | 31 ++++ locales/pl/common.json | 31 ++++ locales/pt/common.json | 31 ++++ locales/ro/common.json | 31 ++++ locales/ru/common.json | 31 ++++ locales/sk/common.json | 31 ++++ locales/th/common.json | 31 ++++ locales/tr/common.json | 31 ++++ locales/uk/common.json | 31 ++++ locales/vi/common.json | 31 ++++ locales/zh-TW/common.json | 31 ++++ locales/zh/common.json | 31 ++++ 47 files changed, 1277 insertions(+), 51 deletions(-) diff --git a/components/desktop/desktop-os-host.tsx b/components/desktop/desktop-os-host.tsx index c8d2755d8..2e4ea7fd6 100644 --- a/components/desktop/desktop-os-host.tsx +++ b/components/desktop/desktop-os-host.tsx @@ -4,7 +4,8 @@ import { useEffect, useRef } from "react"; import { useLocale, useTranslations } from "next-intl"; import { useArchiveActionEnabled } from "@/lib/archive-gate"; import { getDesktopBridge } from "@/lib/desktop/bridge"; -import { startMailNotifications, type MailNotificationLabels, type MailNotifications } from "@/lib/desktop/mail-notifications"; +import { isAppLocked } from "@/lib/desktop/app-lock-state"; +import { contentHiddenFor, startMailNotifications, type MailNotificationLabels, type MailNotifications } from "@/lib/desktop/mail-notifications"; import { startOsSummary } from "@/lib/desktop/os-summary"; import { startPowerWatch } from "@/lib/desktop/power"; import { startWindowFiles } from "@/lib/desktop/window-files"; @@ -49,7 +50,10 @@ export function DesktopOsHost() { labelsRef.current = labels; useEffect(() => { - if (!getDesktopBridge()?.os) return; + const os = getDesktopBridge()?.os; + if (!os) return; + // Main shows no content anywhere until this page has said it may. + os.setLocked(isAppLocked()); const mail = startMailNotifications(labelsRef.current); mailRef.current = mail; const summary = startOsSummary(() => localeRef.current); @@ -64,7 +68,7 @@ export function DesktopOsHost() { const event = "eventId" in view; void getDesktopBridge()?.os?.notify({ id: `reminder:${event ? view.eventId : view.taskId}:${view.id}:${view.triggerTime}`, - title: (event ? view.eventTitle : view.taskTitle) || tCalendar("alert_title"), + title: (contentHiddenFor(useAuthStore.getState().activeAccountId) ? "" : event ? view.eventTitle : view.taskTitle) || tCalendar("alert_title"), body: "", context: "{}", }); diff --git a/components/email/email-composer.tsx b/components/email/email-composer.tsx index 4c02f4eac..f0dab3ae6 100644 --- a/components/email/email-composer.tsx +++ b/components/email/email-composer.tsx @@ -2650,8 +2650,11 @@ export function EmailComposer({ resetDragTimeout(); }, [resetDragTimeout]); - // The desktop app: files dropped or pasted anywhere on the window come here. - useEffect(() => setComposerFileTarget((files) => void addFiles(files)), [addFiles]); + // The desktop app: files dropped or pasted anywhere on the window come here, if this one's on screen. + useEffect(() => setComposerFileTarget( + (files) => void addFiles(files), + () => !!composerRootEl?.isConnected && composerRootEl.getClientRects().length > 0, + ), [addFiles, composerRootEl]); const handleDrop = useCallback((e: React.DragEvent) => { e.preventDefault(); diff --git a/components/email/email-viewer.tsx b/components/email/email-viewer.tsx index 9dbde37b4..22d9f7730 100644 --- a/components/email/email-viewer.tsx +++ b/components/email/email-viewer.tsx @@ -685,15 +685,19 @@ function DraggableAttachmentChip({ attachment, client, accountId, enabled, downl const source = useMemo(() => ({ name: downloadName || attachment.name || 'download', type: attachment.type || 'application/octet-stream', - getBlob: async () => { - if (attachment.blobId && client) { - return client.fetchBlob(attachment.blobId, downloadName || attachment.name || undefined, attachment.type, accountId).catch(() => null); - } - const bytes = attachment.tnefData ?? (attachment.decryptedAttachment ? getAttachmentContentBytes(attachment.decryptedAttachment) : null); - if (!bytes || bytes.byteLength === 0) return null; - const buffer = bytes.buffer.slice(bytes.byteOffset, bytes.byteOffset + bytes.byteLength) as ArrayBuffer; - return new Blob([buffer], { type: inertBlobType(attachment.type) }); - }, + // A decrypted attachment doesn't go through the app's temporary folder or the clipboard: it + // stays with the browser's drag, which only writes it where the user drops it. + ...(attachment.decryptedAttachment ? {} : { + getBlob: async () => { + if (attachment.blobId && client) { + return client.fetchBlob(attachment.blobId, downloadName || attachment.name || undefined, attachment.type, accountId).catch(() => null); + } + const bytes = attachment.tnefData; + if (!bytes || bytes.byteLength === 0) return null; + const buffer = bytes.buffer.slice(bytes.byteOffset, bytes.byteOffset + bytes.byteLength) as ArrayBuffer; + return new Blob([buffer], { type: inertBlobType(attachment.type) }); + }, + }), getBlobUrl: async () => { if (attachment.blobId && client) { try { diff --git a/components/protocol/protocol-launch-handler-provider.tsx b/components/protocol/protocol-launch-handler-provider.tsx index 0078cd8bf..f3940efaa 100644 --- a/components/protocol/protocol-launch-handler-provider.tsx +++ b/components/protocol/protocol-launch-handler-provider.tsx @@ -111,6 +111,7 @@ export function ProtocolLaunchHandlerProvider({ children }: ProtocolLaunchHandle const router = useRouter(); const pathname = usePathname(); const tLaunch = useTranslations("pwaLaunch"); + const tDesktop = useTranslations("desktopOs"); const sentToTarget = useRef(new Set()); const { dialogProps, confirm } = useConfirmDialog(); const [offer, setOffer] = useState(null); @@ -213,6 +214,7 @@ export function ProtocolLaunchHandlerProvider({ children }: ProtocolLaunchHandle if (osEnv.current.pathname !== path) osEnv.current.push(path); }, notifyFailed: () => toast.error(tLaunch("open_failed")), + notifyReplyLost: () => toast.error(tDesktop("reply_lost")), }); // eslint-disable-next-line react-hooks/exhaustive-deps }, []); diff --git a/lib/desktop/__tests__/os-commands.test.ts b/lib/desktop/__tests__/os-commands.test.ts index 465180c64..d3f717f64 100644 --- a/lib/desktop/__tests__/os-commands.test.ts +++ b/lib/desktop/__tests__/os-commands.test.ts @@ -33,7 +33,8 @@ async function fingerprint() { const leaveFor = vi.fn(); const push = vi.fn(); const notifyFailed = vi.fn(); -const env = { leaveFor, push, notifyFailed }; +const notifyReplyLost = vi.fn(); +const env = { leaveFor, push, notifyFailed, notifyReplyLost }; const focus = vi.fn(); async function run(command: DesktopOsCommand) { @@ -191,6 +192,33 @@ describe('a toast\'s buttons', () => { stop(); }); + it('behind the app lock an old toast acts on nothing, and the window comes up', async () => { + const { setAppLockCheck } = await import('../app-lock-state'); + const { setMailLinkOpener } = await import('../mail-links'); + const opener = vi.fn(); + const stop = setMailLinkOpener(opener); + setAppLockCheck(() => true); + for (const action of ['delete', 'archive', 'mark-read', 'open', 'reply']) { + await run({ type: 'notification', action, context: await context(), reply: 'x' }); + } + expect(runNotificationAction).not.toHaveBeenCalled(); + expect(opener).not.toHaveBeenCalled(); + expect(focus).toHaveBeenCalled(); + setAppLockCheck(null); + stop(); + }); + + it('typed text that has nowhere to go says so', async () => { + const { setMailLinkOpener } = await import('../mail-links'); + const stop = setMailLinkOpener(vi.fn()); + await run({ type: 'notification', action: 'reply', context: await context({ login: 'someone-else' }), reply: 'On my way' }); + expect(notifyReplyLost).toHaveBeenCalled(); + notifyReplyLost.mockClear(); + await run({ type: 'notification', action: 'reply', context: await context({ login: 'someone-else' }) }); + expect(notifyReplyLost).not.toHaveBeenCalled(); + stop(); + }); + it('a click on an old toast whose slot went to someone else opens the Inbox, not their message', async () => { const { setMailLinkOpener } = await import('../mail-links'); const opener = vi.fn(); diff --git a/lib/desktop/__tests__/os-mail.test.ts b/lib/desktop/__tests__/os-mail.test.ts index ca3f25bf1..da28971f6 100644 --- a/lib/desktop/__tests__/os-mail.test.ts +++ b/lib/desktop/__tests__/os-mail.test.ts @@ -87,6 +87,8 @@ beforeEach(async () => { }); const { setAppLockCheck } = await import('../app-lock-state'); setAppLockCheck(null); + const { usePolicyStore } = await import('@/stores/policy-store'); + usePolicyStore.setState({ loaded: true }); }); afterEach(() => { @@ -266,6 +268,15 @@ describe('what the notification says', () => { expect(JSON.stringify(notification)).not.toContain('Alice Sender'); }); + it('before the policy is known: the same', async () => { + const { buildMailNotification } = await import('../mail-notifications'); + const { usePolicyStore } = await import('@/stores/policy-store'); + usePolicyStore.setState({ loaded: false }); + const notification = await buildMailNotification([one()], labels); + expect(JSON.stringify(notification)).not.toContain('Secret'); + expect(JSON.stringify(notification)).not.toContain('Alice Sender'); + }); + it('several at once are told about once, and that one has no message to act on', async () => { const { buildMailNotification } = await import('../mail-notifications'); const notification = await buildMailNotification([one('a'), one('b'), one('c')], labels); diff --git a/lib/desktop/__tests__/os-store.test.ts b/lib/desktop/__tests__/os-store.test.ts index cd876cfd2..e09aee90d 100644 --- a/lib/desktop/__tests__/os-store.test.ts +++ b/lib/desktop/__tests__/os-store.test.ts @@ -49,6 +49,17 @@ describe('the store and the OS integration', () => { expect(listener).toHaveBeenCalledWith(client, 'acc', ['e1', 'e2']); }); + it('a wipe clears what the OS shows, and a browser has nothing to clear', async () => { + const { clearDesktopSurfaces } = await import('../bridge'); + expect(() => clearDesktopSurfaces()).not.toThrow(); + const clearSurfaces = vi.fn(); + window.bulwarkDesktop = { version: 1, platform: 'win32', os: { clearSurfaces } } as never; + const { wipeAccount } = await import('@/lib/offline/offline-mode'); + await wipeAccount('alice@one').catch(() => {}); + expect(clearSurfaces).toHaveBeenCalled(); + window.bulwarkDesktop = undefined; + }); + it('other collections are not mail', async () => { const { setStoreCreatedListener } = await import('../system-events'); const listener = vi.fn(); diff --git a/lib/desktop/__tests__/os-window.test.tsx b/lib/desktop/__tests__/os-window.test.tsx index 2e005476f..b6df496bb 100644 --- a/lib/desktop/__tests__/os-window.test.tsx +++ b/lib/desktop/__tests__/os-window.test.tsx @@ -159,7 +159,7 @@ describe('files on the window', () => { setBridge({}); const stop = startWindowFiles(); const target = vi.fn(); - const clear = setComposerFileTarget(target); + const clear = setComposerFileTarget(target, () => true); window.dispatchEvent(dropEvent('drop', [file])); expect(target).toHaveBeenCalledWith([file]); expect(takeHandoff('compose')).toBeNull(); @@ -167,6 +167,20 @@ describe('files on the window', () => { stop(); }); + it('a composer out of sight on a hidden tab takes nothing', async () => { + const { setComposerFileTarget, startWindowFiles } = await import('../window-files'); + const { takeHandoff } = await import('@/lib/launch-handoff'); + setBridge({}); + const stop = startWindowFiles(); + const hidden = vi.fn(); + const clear = setComposerFileTarget(hidden, () => false); + window.dispatchEvent(dropEvent('drop', [file])); + expect(hidden).not.toHaveBeenCalled(); + expect(takeHandoff('compose')?.files).toEqual([file]); + clear(); + stop(); + }); + it('a pasted file is taken; pasted text is not', async () => { const { startWindowFiles } = await import('../window-files'); const { takeHandoff } = await import('@/lib/launch-handoff'); @@ -174,7 +188,7 @@ describe('files on the window', () => { const stop = startWindowFiles(); const paste = (files: File[]) => { const event = new Event('paste', { bubbles: true, cancelable: true }); - Object.defineProperty(event, 'clipboardData', { value: { files } }); + Object.defineProperty(event, 'clipboardData', { value: { files, types: files.length ? ['Files'] : ['text/plain'] } }); window.dispatchEvent(event); return event; }; @@ -185,6 +199,54 @@ describe('files on the window', () => { stop(); }); + describe('a paste that is not for us', () => { + const pasteOn = (target: EventTarget, types: string[]) => { + const event = new Event('paste', { bubbles: true, cancelable: true }); + Object.defineProperty(event, 'clipboardData', { value: { files: [file], types } }); + target.dispatchEvent(event); + return event; + }; + + it('text with a file, as a spreadsheet copies, stays text', async () => { + const { startWindowFiles } = await import('../window-files'); + const { takeHandoff } = await import('@/lib/launch-handoff'); + setBridge({}); + const stop = startWindowFiles(); + expect(pasteOn(document.body, ['text/plain', 'Files']).defaultPrevented).toBe(false); + expect(takeHandoff('compose')).toBeNull(); + stop(); + }); + + it('in a search field or any other input, a file is not an attachment', async () => { + const { startWindowFiles } = await import('../window-files'); + const { takeHandoff } = await import('@/lib/launch-handoff'); + setBridge({}); + const stop = startWindowFiles(); + const input = document.body.appendChild(document.createElement('input')); + expect(pasteOn(input, ['Files']).defaultPrevented).toBe(false); + expect(takeHandoff('compose')).toBeNull(); + input.remove(); + stop(); + }); + + it('in the body of a composer on screen, a bare file attaches', async () => { + const { setComposerFileTarget, startWindowFiles } = await import('../window-files'); + setBridge({}); + const stop = startWindowFiles(); + const target = vi.fn(); + const clear = setComposerFileTarget(target, () => true); + const composer = document.body.appendChild(document.createElement('div')); + composer.setAttribute('data-testid', 'email-composer'); + const body = composer.appendChild(document.createElement('div')); + body.setAttribute('contenteditable', 'true'); + pasteOn(body, ['Files']); + expect(target).toHaveBeenCalledWith([file]); + composer.remove(); + clear(); + stop(); + }); + }); + it('our own native drag over our own window is not a new file', async () => { const { setOwnDragActive, startWindowFiles } = await import('../window-files'); const { takeHandoff } = await import('@/lib/launch-handoff'); @@ -247,6 +309,23 @@ describe('dragging an attachment out', () => { await waitFor(() => expect(startDrag).toHaveBeenCalled()); }); + it('in the app, without getBlob (a decrypted attachment): the browser drag, and no copy button', async () => { + const startDrag = vi.fn(async () => true); + setBridge({ startDrag, copyFiles: vi.fn() as never }); + const { useAttachmentDrag } = await import('@/hooks/use-attachment-drag'); + const { name, type, getBlobUrl } = source; + const hook = renderHook(() => useAttachmentDrag({ name, type, getBlobUrl }, true)); + act(() => hook.result.current.onPointerEnter()); + await act(async () => { + await new Promise((resolve) => setTimeout(resolve, 0)); + }); + const event = { preventDefault: vi.fn(), dataTransfer: { setData: vi.fn(), effectAllowed: '' } }; + hook.result.current.onDragStart(event as never); + expect(startDrag).not.toHaveBeenCalled(); + expect(event.dataTransfer.setData).toHaveBeenCalledWith('DownloadURL', expect.stringContaining(blobUrl)); + expect(hook.result.current.onCopyAsFile).toBeUndefined(); + }); + it('in a browser: still DownloadURL, and no copy button', async () => { const hook = await ready(); const event = { preventDefault: vi.fn(), dataTransfer: { setData: vi.fn(), effectAllowed: '' } }; @@ -313,15 +392,52 @@ describe('waking up', () => { stop(); }); - it('a network change counts as waking', async () => { - const connection = new EventTarget(); - vi.stubGlobal('navigator', Object.assign(Object.create(navigator), { connection })); + it('a new kind of network counts as waking; a change in its speed estimate does not', async () => { + const connection = Object.assign(new EventTarget(), { type: 'wifi' }); + vi.stubGlobal('navigator', Object.create(navigator, { connection: { value: connection }, onLine: { value: true } })); const { power, stop } = await started(); const wake = vi.fn(); const stopWake = power.addWakeHandler(wake); - await new Promise((resolve) => setTimeout(resolve, 0)); connection.dispatchEvent(new Event('change')); - expect(wake).toHaveBeenCalled(); + expect(wake).not.toHaveBeenCalled(); + connection.type = 'ethernet'; + connection.dispatchEvent(new Event('change')); + expect(wake).toHaveBeenCalledTimes(1); + stopWake(); + stop(); + }); + + it('where Chromium gives no type, a change is nothing to go on', async () => { + const connection = new EventTarget(); + vi.stubGlobal('navigator', Object.create(navigator, { connection: { value: connection }, onLine: { value: true } })); + const { power, stop } = await started(); + const wake = vi.fn(); + const stopWake = power.addWakeHandler(wake); + connection.dispatchEvent(new Event('change')); + expect(wake).not.toHaveBeenCalled(); + stopWake(); + stop(); + }); + + it('woken up with no network, it does not say the network is back, and a wake within 30 s is the same wake', async () => { + const online = vi.fn(); + window.addEventListener('online', online); + vi.stubGlobal('navigator', Object.create(navigator, { onLine: { value: false } })); + const { fire, power, stop } = await started(); + const wake = vi.fn(); + const stopWake = power.addWakeHandler(wake); + fire('resume'); + expect(wake).toHaveBeenCalledTimes(1); + expect(online).not.toHaveBeenCalled(); + const now = Date.now(); + const clock = vi.spyOn(Date, 'now').mockReturnValue(now + 29_000); + fire('unlock-screen'); + expect(wake).toHaveBeenCalledTimes(1); + clock.mockReturnValue(now + 31_000); + fire('unlock-screen'); + expect(wake).toHaveBeenCalledTimes(2); + clock.mockRestore(); + window.removeEventListener('online', online); stopWake(); stop(); }); @@ -357,6 +473,8 @@ describe('the summary', () => { useAuthStore.setState({ isAuthenticated: true, client: fake as never, activeAccountId: 'alice@one' }); useAccountStore.setState({ accounts: [{ id: 'alice@one', cookieSlot: 3, serverUrl: 'https://one.example', username: 'alice@example.org' }] as never }); useEmailStore.setState({ mailboxes: [{ id: 'in', role: 'inbox', name: 'Inbox' }] as never }); + const { usePolicyStore } = await import('@/stores/policy-store'); + usePolicyStore.setState({ loaded: true }); }); afterEach(async () => { @@ -374,6 +492,42 @@ describe('the summary', () => { expect(getEmails).toHaveBeenCalledWith('in', undefined, 5, 0, undefined, false, { notKeyword: '$seen' }); }); + it('before the policy is known it is empty', async () => { + const { usePolicyStore } = await import('@/stores/policy-store'); + const { readSummary, EMPTY_SUMMARY } = await import('../os-summary'); + usePolicyStore.setState({ loaded: false }); + await expect(readSummary('en')).resolves.toEqual(EMPTY_SUMMARY); + expect(getEmails).not.toHaveBeenCalled(); + }); + + it('a read under way when the login goes doesn\'t bring it back; a policy change reads again', async () => { + const setSummary = vi.fn(); + setBridge({ setSummary }); + const { startOsSummary, EMPTY_SUMMARY } = await import('../os-summary'); + const { useAuthStore } = await import('@/stores/auth-store'); + const { usePolicyStore } = await import('@/stores/policy-store'); + let answer: (value: unknown) => void = () => {}; + getEmails.mockImplementationOnce(() => new Promise((resolve) => { answer = resolve as never; }) as never); + vi.useFakeTimers({ toFake: ['setTimeout', 'clearTimeout', 'setInterval', 'clearInterval'] }); + const summary = startOsSummary(() => 'en'); + await vi.advanceTimersByTimeAsync(2100); + useAuthStore.setState({ isAuthenticated: false, client: null, activeAccountId: null }); + expect(setSummary).toHaveBeenLastCalledWith(EMPTY_SUMMARY); + answer({ emails: [row], hasMore: false, total: 1 }); + await vi.advanceTimersByTimeAsync(10); + await new Promise((resolve) => setImmediate(resolve)); + await vi.advanceTimersByTimeAsync(2100); + expect(setSummary).toHaveBeenLastCalledWith(EMPTY_SUMMARY); + expect(JSON.stringify(setSummary.mock.calls)).not.toContain('Lunch'); + + setSummary.mockClear(); + usePolicyStore.setState({ policy: { ...usePolicyStore.getState().policy } }); + await vi.advanceTimersByTimeAsync(2100); + expect(setSummary).toHaveBeenCalled(); + summary.stop(); + vi.useRealTimers(); + }); + it('while locked it is empty, without asking anyone anything', async () => { const { setAppLockCheck } = await import('../app-lock-state'); const { readSummary, EMPTY_SUMMARY } = await import('../os-summary'); diff --git a/lib/desktop/bridge.ts b/lib/desktop/bridge.ts index 922a5c1b5..33e36bd81 100644 --- a/lib/desktop/bridge.ts +++ b/lib/desktop/bridge.ts @@ -19,6 +19,11 @@ export function forgetDesktopServer(serverUrl: string, reason: 'failed' | 'signe getDesktopBridge()?.servers?.forget?.(serverUrl, reason); } +/** Nothing of an account stays on the OS's surfaces after a sign-out or a wipe. */ +export function clearDesktopSurfaces(): void { + getDesktopBridge()?.os?.clearSurfaces?.(); +} + /** True in any window of the desktop app, standalone or on an instance. */ export function isDesktopApp(): boolean { return getDesktopBridge() !== null; diff --git a/lib/desktop/mail-notifications.ts b/lib/desktop/mail-notifications.ts index 05b3303ba..12cd12e5d 100644 --- a/lib/desktop/mail-notifications.ts +++ b/lib/desktop/mail-notifications.ts @@ -21,6 +21,7 @@ import { isPopoutWindow } from '@/lib/popout/window-role'; import { loginFingerprint } from '@/lib/pwa-notify/login-fingerprint'; import { useAccountStore } from '@/stores/account-store'; import { useAuthStore } from '@/stores/auth-store'; +import { usePolicyStore } from '@/stores/policy-store'; import { useSettingsStore } from '@/stores/settings-store'; import type { IJMAPClient } from '@/lib/jmap/client-interface'; @@ -72,17 +73,22 @@ async function senderInfo(client: IJMAPClient): Promise { }; } -function withoutContent(sender: Sender): boolean { +/** Sender and subject stay off everything outside the app. Not knowing yet counts as yes. */ +export function contentHiddenFor(accountEntryId: string | null | undefined): boolean { if (isAppLocked()) return true; // Unknown counts as on, as on the server's route. - if (!sender.entryId) return true; + if (!accountEntryId || !usePolicyStore.getState().loaded) return true; try { - return devicesForAccount(sender.entryId).pushWithoutContent; + return devicesForAccount(accountEntryId).pushWithoutContent; } catch { return true; } } +function withoutContent(sender: Sender): boolean { + return contentHiddenFor(sender.entryId); +} + function senderName(mail: NewMail): string { const first = mail.email.from?.[0]; return first?.name || first?.email || ''; diff --git a/lib/desktop/os-commands.ts b/lib/desktop/os-commands.ts index 8c7332225..534ff7544 100644 --- a/lib/desktop/os-commands.ts +++ b/lib/desktop/os-commands.ts @@ -5,6 +5,7 @@ * user's own OS, so it skips the question a web share gets (decision 17). */ +import { isAppLocked } from '@/lib/desktop/app-lock-state'; import { getDesktopBridge } from '@/lib/desktop/bridge'; import type { DesktopOsCommand } from '@/lib/desktop/types'; import { openMailLink } from '@/lib/desktop/mail-links'; @@ -25,6 +26,8 @@ export interface OsCommandEnv { push: (path: string) => void; /** Says something couldn't be opened. */ notifyFailed: (names: readonly string[]) => void; + /** A typed reply that has nowhere to go: its message's login is gone from here. */ + notifyReplyLost: () => void; } /** How long a toast's button waits for a cold start to sign in. */ @@ -77,6 +80,7 @@ async function openFromNotification(context: MailNotificationContext, env: OsCom openMailLink({ kind: 'message', id: context.emailId, slot: context.slot, ownerAccountId: context.accountId, ...(reply ? { reply: true } : {}) }, reply ? { replyText: reply.text } : undefined, env.leaveFor); return; } + if (reply?.text) env.notifyReplyLost(); openMailLink({ kind: 'folder', ref: 'inbox', ...(known ? { slot: context.slot } : {}) }, undefined, env.leaveFor); } @@ -89,6 +93,11 @@ function runAction(request: NotificationActionRequest): Promise { + // An old toast's Delete doesn't act behind the lock, and its message doesn't open over it. + if (isAppLocked()) { + showWindow(); + return; + } const context = parseContext(rawContext); if (action === 'reply') { await openFromNotification(context, env, { text: reply ?? '' }); diff --git a/lib/desktop/os-summary.ts b/lib/desktop/os-summary.ts index 12d014051..599dd942a 100644 --- a/lib/desktop/os-summary.ts +++ b/lib/desktop/os-summary.ts @@ -6,9 +6,8 @@ */ import { getDesktopBridge } from '@/lib/desktop/bridge'; -import { isAppLocked } from '@/lib/desktop/app-lock-state'; +import { contentHiddenFor } from '@/lib/desktop/mail-notifications'; import type { DesktopOsSummary, DesktopOsTrayMessage } from '@/lib/desktop/types'; -import { devicesForAccount } from '@/lib/device-policy'; import type { CalendarEvent, Email } from '@/lib/jmap/types'; import type { IJMAPClient } from '@/lib/jmap/client-interface'; import { loginFingerprint } from '@/lib/pwa-notify/login-fingerprint'; @@ -16,6 +15,7 @@ import { useAccountStore } from '@/stores/account-store'; import { useAuthStore } from '@/stores/auth-store'; import { useCalendarStore } from '@/stores/calendar-store'; import { useEmailStore } from '@/stores/email-store'; +import { usePolicyStore } from '@/stores/policy-store'; const ROWS = 5; const REFRESH_DELAY_MS = 2000; @@ -46,12 +46,10 @@ export function formatEventTime(start: number, now: number, locale: string | und } export async function readSummary(locale: string | undefined, now = Date.now()): Promise { - if (isAppLocked()) return EMPTY_SUMMARY; const auth = useAuthStore.getState(); const client: IJMAPClient | null = auth.client; const entry = useAccountStore.getState().accounts.find((account) => account.id === auth.activeAccountId); - if (!auth.isAuthenticated || !client || !entry) return EMPTY_SUMMARY; - if (devicesForAccount(entry.id).pushWithoutContent) return EMPTY_SUMMARY; + if (!auth.isAuthenticated || !client || !entry || contentHiddenFor(entry.id)) return EMPTY_SUMMARY; const inbox = useEmailStore.getState().mailboxes.find((mailbox) => mailbox.role === 'inbox' && !mailbox.isShared); let unread: DesktopOsTrayMessage[] = []; @@ -91,12 +89,14 @@ export function startOsSummary(locale: () => string | undefined): { refresh: () const os = getDesktopBridge()?.os; if (!os) return { refresh: () => {}, stop: () => {} }; let timer: ReturnType | null = null; - let stale = false; + /** A read under way when the account changed answers for the old one, and is dropped. */ + let generation = 0; const run = () => { timer = null; + const mine = ++generation; void readSummary(locale()).then((summary) => { - if (!stale) os.setSummary(summary); + if (mine === generation) os.setSummary(summary); }, (error) => { console.warn('Could not read the tray summary:', error); }); @@ -113,16 +113,24 @@ export function startOsSummary(locale: () => string | undefined): { refresh: () refresh(); }); const stopAuth = useAuthStore.subscribe((state, previous) => { - if (state.activeAccountId !== previous.activeAccountId || state.isAuthenticated !== previous.isAuthenticated) refresh(); + if (state.activeAccountId === previous.activeAccountId && state.isAuthenticated === previous.isAuthenticated) return; + generation += 1; + if (!state.isAuthenticated) os.setSummary(EMPTY_SUMMARY); + refresh(); + }); + // An organisation turning sender and subject off goes for what's already showing, too. + const stopPolicy = usePolicyStore.subscribe((state, previous) => { + if (state.loaded !== previous.loaded || state.policy !== previous.policy) refresh(); }); const interval = setInterval(refresh, REFRESH_EVERY_MS); refresh(); return { refresh, stop() { - stale = true; + generation += 1; stopEmail(); stopAuth(); + stopPolicy(); clearInterval(interval); if (timer) clearTimeout(timer); }, diff --git a/lib/desktop/power.ts b/lib/desktop/power.ts index 44d429dfc..1e36e52ae 100644 --- a/lib/desktop/power.ts +++ b/lib/desktop/power.ts @@ -13,12 +13,13 @@ import { getDesktopBridge } from '@/lib/desktop/bridge'; import { runSleepHandlers, runWakeHandlers } from '@/lib/desktop/system-events'; import { usePolicyStore } from '@/stores/policy-store'; -/** A resume and an unlock usually come together. */ -const WAKE_REPEAT_MS = 2000; +/** A resume, an unlock and a network change come together, and each would mean a round of requests. */ +const WAKE_REPEAT_MS = 30_000; function wake(): void { document.dispatchEvent(new Event('visibilitychange')); - window.dispatchEvent(new Event('online')); + // Woken up with no network: the browser's own event comes when there is one. + if (navigator.onLine !== false) window.dispatchEvent(new Event('online')); const policy = usePolicyStore.getState(); if (policy.loaded && !policy.fetching) void policy.fetchPolicy(); runWakeHandlers(); @@ -38,11 +39,20 @@ export function startPowerWatch(): () => void { if (event === 'resume' || event === 'unlock-screen') wakeOnce(); else if (event === 'suspend') runSleepHandlers(); }); - // Wi-Fi to cable is a new network with no offline moment in between. - const connection = (navigator as unknown as { connection?: EventTarget }).connection; - connection?.addEventListener?.('change', wakeOnce); + // Wi-Fi to cable is a new network with no offline moment in between. This event also fires + // for every shift in its speed estimate, which isn't news. Where Chromium doesn't report the + // type, that leaves the stream's own ping check and the online event. + const connection = (navigator as unknown as { connection?: EventTarget & { type?: string } }).connection; + let lastType = connection?.type; + const onConnection = () => { + const type = connection?.type; + if (!type || type === lastType) return; + lastType = type; + wakeOnce(); + }; + connection?.addEventListener?.('change', onConnection); return () => { stopPower(); - connection?.removeEventListener?.('change', wakeOnce); + connection?.removeEventListener?.('change', onConnection); }; } diff --git a/lib/desktop/types.ts b/lib/desktop/types.ts index 501cc503a..859f2ea97 100644 --- a/lib/desktop/types.ts +++ b/lib/desktop/types.ts @@ -168,7 +168,7 @@ export interface DesktopOsNotification { export interface DesktopOsNotifyResult { shown: boolean; - reason?: 'duplicate' | 'paused' | 'quiet-hours' | 'focused' | 'unsupported' | 'invalid'; + reason?: 'duplicate' | 'paused' | 'quiet-hours' | 'focused' | 'unsupported' | 'invalid' | 'failed'; /** The page should play the sound the user picked. */ playSound: boolean; } @@ -214,8 +214,13 @@ export interface DesktopOsBridge { setSummary: (summary: DesktopOsSummary) => void; /** The taskbar's progress bar: lib/desktop/file-drag.ts setTaskbarProgress. */ setProgress: (progress: number | null, mode?: 'normal' | 'indeterminate' | 'paused' | 'error') => void; - /** App lock's state: main clears the tray, jump list and toasts and strips what comes through. */ + /** + * App lock's state. Main counts as locked until the main window's page says otherwise, + * and clears the tray, jump list and toasts and strips what comes through. + */ setLocked: (locked: boolean) => void; + /** Sign-out or a wipe: delivered toasts, badge, tray and jump list go, and temporary files. */ + clearSurfaces: () => void; notify: (notification: DesktopOsNotification) => Promise; onPower: (listener: (event: DesktopOsPowerEvent) => void) => () => void; /** Writes the files to a private folder and starts a drag. Only with the mouse held. */ @@ -225,6 +230,8 @@ export interface DesktopOsBridge { defaultAppStatus: () => Promise; /** Offers the app to the OS and opens where the user chooses. */ openDefaultAppSettings: () => Promise; +} + export type DesktopKeystoreHash = 'SHA-1' | 'SHA-256' | 'SHA-384' | 'SHA-512'; export interface DesktopKeystoreCertificate { diff --git a/lib/desktop/window-files.ts b/lib/desktop/window-files.ts index c9b95221a..63a67630b 100644 --- a/lib/desktop/window-files.ts +++ b/lib/desktop/window-files.ts @@ -8,29 +8,58 @@ import { getDesktopBridge } from '@/lib/desktop/bridge'; import { handOffDraft } from '@/lib/launch-handoff'; -type ComposerTarget = (files: File[]) => unknown; +interface ComposerTarget { + take: (files: File[]) => unknown; + /** The Pro shell keeps composers mounted on tabs out of sight. */ + visible: () => boolean; +} -let composerTarget: ComposerTarget | null = null; +const composerTargets: ComposerTarget[] = []; /** Our own native drag passing over our own window isn't a new file. */ let ownDrag = false; -/** The open composer takes files itself. No effect outside the desktop app: only this file reads it. */ -export function setComposerFileTarget(target: ComposerTarget): () => void { - composerTarget = target; +/** A composer on screen takes files itself. No effect outside the desktop app: only this file reads it. */ +export function setComposerFileTarget(take: ComposerTarget['take'], visible: ComposerTarget['visible']): () => void { + const target = { take, visible }; + composerTargets.push(target); return () => { - if (composerTarget === target) composerTarget = null; + const at = composerTargets.indexOf(target); + if (at !== -1) composerTargets.splice(at, 1); }; } +function visibleComposer(): ComposerTarget | undefined { + return [...composerTargets].reverse().find((target) => target.visible()); +} + export function setOwnDragActive(active: boolean): void { ownDrag = active; } function take(files: File[]): void { - if (composerTarget) composerTarget(files); + const composer = visibleComposer(); + if (composer) composer.take(files); else handOffDraft({ files }); } +function isEditable(target: EventTarget | null): boolean { + const element = target as Partial & { closest?: (selector: string) => Element | null } | null; + return !!element?.closest?.('input, textarea, select, [contenteditable=""], [contenteditable="true"]'); +} + +/** + * Pasting a file is only meant where text isn't. A spreadsheet's cells also come as a + * picture of them, and a field that takes text wants the text. The one editable place a + * bare file belongs in is the composer it's being pasted into. + */ +function pasteIsForUs(event: ClipboardEvent): boolean { + const types = [...(event.clipboardData?.types ?? [])]; + if (types.includes('text/plain') || types.includes('text/html')) return false; + if (!isEditable(event.target)) return true; + const inComposer = !!(event.target as Partial | null)?.closest?.('[data-testid="email-composer"]'); + return inComposer && !!visibleComposer(); +} + function hasFiles(event: DragEvent): boolean { return !!event.dataTransfer && [...event.dataTransfer.types].includes('Files'); } @@ -51,7 +80,7 @@ export function startWindowFiles(): () => void { const onPaste = (event: ClipboardEvent) => { if (event.defaultPrevented) return; const files = Array.from(event.clipboardData?.files ?? []); - if (files.length === 0) return; + if (files.length === 0 || !pasteIsForUs(event)) return; event.preventDefault(); take(files); }; diff --git a/lib/offline/offline-mode.ts b/lib/offline/offline-mode.ts index d71993589..e138070f1 100644 --- a/lib/offline/offline-mode.ts +++ b/lib/offline/offline-mode.ts @@ -62,6 +62,7 @@ import { flushQueue, LOOKUP_WAIT_MS, SENDING_STALE_MS, settleStaleSends } from ' import { LEGACY_DB_NAME, moveLegacyOutbox, strandedLegacyMessages } from './migrate-legacy'; import { deleteShellCaches, tellServiceWorker } from './shell'; import { rememberLost } from './lost'; +import { clearDesktopSurfaces } from '@/lib/desktop/bridge'; import { addSleepHandler, reportStoreCreatedMail } from '@/lib/desktop/system-events'; export type OfflineNotice = @@ -807,6 +808,7 @@ async function nameLost(scope: string, stillSignedIn: boolean): Promise { } export async function wipeAccount(account: string): Promise { + clearDesktopSurfaces(); const timer = syncTimers.get(account); if (timer) clearTimeout(timer); syncTimers.delete(account); @@ -838,6 +840,7 @@ export async function wipeAccount(account: string): Promise { /** Nothing of offline mode stays on this device. */ export async function wipeEverything(): Promise { + clearDesktopSurfaces(); for (const timer of syncTimers.values()) clearTimeout(timer); syncTimers.clear(); writeEnabled([]); diff --git a/lib/sign-out-cleanup.ts b/lib/sign-out-cleanup.ts index 531c99f7f..67ff5ea9c 100644 --- a/lib/sign-out-cleanup.ts +++ b/lib/sign-out-cleanup.ts @@ -1,6 +1,7 @@ import { fileStorage } from '@/lib/plugin-storage'; import { clearHandoffs } from '@/lib/launch-handoff'; import { clearAllCheckpoints } from '@/lib/mail-import/checkpoints'; +import { clearDesktopSurfaces } from '@/lib/desktop/bridge'; import { sweepPrivateExports } from '@/lib/mail-export/sinks'; import { purgeStoreFiles } from '@/lib/store/client/purge'; @@ -138,6 +139,7 @@ export function purgeSignedOutData(options: SignOutCleanupOptions = {}): void { } // Files and links on their way into a draft or an import. clearHandoffs(); + clearDesktopSurfaces(); for (const cleanup of signedOutCleanups) { try { cleanup(options); diff --git a/locales/ar/common.json b/locales/ar/common.json index 654025307..911ff01cf 100644 --- a/locales/ar/common.json +++ b/locales/ar/common.json @@ -6545,6 +6545,37 @@ "message_moved_to": "تم نقل هذه الرسالة إلى {folder}.", "window_not_ready": "لم تفتح النافذة الجديدة في الوقت المناسب. تبقى مسودتك هنا." }, + "desktopOs": { + "section_title": "تطبيق سطح المكتب", + "section_description": "طريقة عمل التطبيق مع حاسوبك.", + "close_to_tray_label": "الاستمرار في العمل في شريط النظام عند الإغلاق", + "close_to_tray_description": "يستمر وصول البريد الجديد والتذكيرات بعد إغلاق النافذة.", + "start_at_login_label": "البدء عند تسجيل الدخول إلى الحاسوب", + "start_at_login_description": "يبدأ التطبيق تلقائيًا عند تسجيل الدخول.", + "start_minimized_label": "البدء في شريط النظام", + "start_minimized_description": "لا تُفتح نافذة عند تسجيل الدخول. استخدم أيقونة شريط النظام لفتحه.", + "attention_label": "وميض شريط المهام عند وصول بريد جديد", + "attention_mac_label": "قفز أيقونة Dock عند وصول بريد جديد", + "attention_description": "فقط عندما لا يكون التطبيق في المقدمة.", + "quiet_hours_label": "ساعات الهدوء", + "quiet_hours_description": "لا إشعارات ولا أصوات في هذه الأوقات. يستمر وصول البريد.", + "quiet_hours_from": "من", + "quiet_hours_to": "إلى", + "show_shortcut_label": "إظهار التطبيق باختصار لوحة المفاتيح", + "show_shortcut_description": "{keys} يُظهر التطبيق في المقدمة من أي تطبيق آخر.", + "show_shortcut_taken": "يستخدم تطبيق آخر {keys}. أغلق ذلك التطبيق، أو أوقف هذا الخيار ثم شغّله مرة أخرى.", + "default_app_label": "تطبيق البريد الافتراضي", + "default_app_description": "فتح روابط البريد وملفات ‎.eml و‎.ics و‎.vcf بهذا التطبيق.", + "default_app_is_default": "هذا التطبيق يفتح روابط البريد لديك.", + "default_app_button": "اختيار التطبيق الافتراضي…", + "new_mail": "بريد جديد", + "no_subject": "(بدون موضوع)", + "action_delete": "حذف", + "copy_as_file": "نسخ كملف", + "copied": "تم نسخ الملف. يمكنك لصقه في مجلد.", + "copy_failed": "تعذّر نسخ الملف.", + "reply_lost": "تعذّر الاحتفاظ بالرد الذي كتبته. افتح الرسالة وأجب من هناك." + }, "desktop_sign_in": { "browser_button": "سجّل الدخول باستخدام المتصفح", "browser_waiting": "أكمل تسجيل الدخول في المتصفح", diff --git a/locales/ca/common.json b/locales/ca/common.json index 0a9eb6fb2..275c62df5 100644 --- a/locales/ca/common.json +++ b/locales/ca/common.json @@ -6545,6 +6545,37 @@ "message_moved_to": "Aquest missatge s’ha mogut a {folder}.", "window_not_ready": "La finestra nova no s’ha obert a temps. L’esborrany es queda aquí." }, + "desktopOs": { + "section_title": "Aplicació d'escriptori", + "section_description": "Com funciona l'aplicació amb l'ordinador.", + "close_to_tray_label": "Continua a la safata en tancar", + "close_to_tray_description": "El correu nou i els recordatoris continuen arribant després de tancar la finestra.", + "start_at_login_label": "Inicia en entrar a l'ordinador", + "start_at_login_description": "L'aplicació s'inicia sola quan entreu.", + "start_minimized_label": "Inicia a la safata", + "start_minimized_description": "No s'obre cap finestra en entrar. Obriu-la amb la icona de la safata.", + "attention_label": "Fes parpellejar la barra de tasques amb correu nou", + "attention_mac_label": "Fes botar la icona del Dock amb correu nou", + "attention_description": "Només quan l'aplicació no és al davant.", + "quiet_hours_label": "Hores de silenci", + "quiet_hours_description": "Cap notificació ni so en aquestes hores. El correu continua arribant.", + "quiet_hours_from": "De", + "quiet_hours_to": "a", + "show_shortcut_label": "Mostra l'aplicació amb una drecera de teclat", + "show_shortcut_description": "{keys} porta l'aplicació al davant des de qualsevol altra aplicació.", + "show_shortcut_taken": "Una altra aplicació fa servir {keys}. Tanqueu-la o desactiveu i torneu a activar això.", + "default_app_label": "Aplicació de correu predeterminada", + "default_app_description": "Obre els enllaços de correu i els fitxers .eml, .ics i .vcf amb aquesta aplicació.", + "default_app_is_default": "Aquesta aplicació obre els vostres enllaços de correu.", + "default_app_button": "Tria l'aplicació predeterminada…", + "new_mail": "Correu nou", + "no_subject": "(sense assumpte)", + "action_delete": "Suprimeix", + "copy_as_file": "Copia com a fitxer", + "copied": "Fitxer copiat. El podeu enganxar en una carpeta.", + "copy_failed": "No s'ha pogut copiar el fitxer.", + "reply_lost": "No s'ha pogut conservar la resposta que heu escrit. Obriu el missatge i responeu des d'allà." + }, "desktop_sign_in": { "browser_button": "Inicia la sessió amb el navegador", "browser_waiting": "Acaba d'iniciar la sessió al navegador", diff --git a/locales/cs/common.json b/locales/cs/common.json index 18f0c03ee..d818f1a13 100644 --- a/locales/cs/common.json +++ b/locales/cs/common.json @@ -6545,6 +6545,37 @@ "message_moved_to": "Tato zpráva byla přesunuta do {folder}.", "window_not_ready": "Nové okno se neotevřelo včas. Koncept zůstává zde." }, + "desktopOs": { + "section_title": "Desktopová aplikace", + "section_description": "Jak aplikace spolupracuje s vaším počítačem.", + "close_to_tray_label": "Po zavření běžet dál v oznamovací oblasti", + "close_to_tray_description": "Nová pošta a připomenutí chodí i po zavření okna.", + "start_at_login_label": "Spustit po přihlášení k počítači", + "start_at_login_description": "Aplikace se po přihlášení spustí sama.", + "start_minimized_label": "Spustit v oznamovací oblasti", + "start_minimized_description": "Po přihlášení se neotevře žádné okno. Otevřete ji ikonou v oznamovací oblasti.", + "attention_label": "Při nové poště blikat na hlavním panelu", + "attention_mac_label": "Při nové poště poskakovat ikonou v Docku", + "attention_description": "Jen když aplikace není v popředí.", + "quiet_hours_label": "Doba klidu", + "quiet_hours_description": "V tuto dobu žádná oznámení ani zvuky. Pošta dál přichází.", + "quiet_hours_from": "Od", + "quiet_hours_to": "do", + "show_shortcut_label": "Zobrazit aplikaci klávesovou zkratkou", + "show_shortcut_description": "{keys} přenese aplikaci do popředí z jakékoli jiné aplikace.", + "show_shortcut_taken": "{keys} používá jiná aplikace. Zavřete ji, nebo tuto volbu vypněte a znovu zapněte.", + "default_app_label": "Výchozí poštovní aplikace", + "default_app_description": "Otevírat poštovní odkazy a soubory .eml, .ics a .vcf v této aplikaci.", + "default_app_is_default": "Tato aplikace otevírá vaše poštovní odkazy.", + "default_app_button": "Vybrat výchozí aplikaci…", + "new_mail": "Nová pošta", + "no_subject": "(bez předmětu)", + "action_delete": "Smazat", + "copy_as_file": "Kopírovat jako soubor", + "copied": "Soubor zkopírován. Můžete ho vložit do složky.", + "copy_failed": "Soubor se nepodařilo zkopírovat.", + "reply_lost": "Napsanou odpověď se nepodařilo zachovat. Otevřete zprávu a odpovězte v ní." + }, "desktop_sign_in": { "browser_button": "Přihlásit se v prohlížeči", "browser_waiting": "Dokončete přihlášení v prohlížeči", diff --git a/locales/da/common.json b/locales/da/common.json index 5578eeca0..fa38d0449 100644 --- a/locales/da/common.json +++ b/locales/da/common.json @@ -6545,6 +6545,37 @@ "message_moved_to": "Denne besked er flyttet til {folder}.", "window_not_ready": "Det nye vindue åbnede ikke i tide. Din kladde bliver her." }, + "desktopOs": { + "section_title": "Skrivebordsapp", + "section_description": "Sådan arbejder appen sammen med din computer.", + "close_to_tray_label": "Fortsæt i systembakken ved lukning", + "close_to_tray_description": "Ny post og påmindelser bliver ved med at komme, når du lukker vinduet.", + "start_at_login_label": "Start, når du logger på computeren", + "start_at_login_description": "Appen starter selv, når du logger på.", + "start_minimized_label": "Start i systembakken", + "start_minimized_description": "Der åbnes intet vindue ved logon. Åbn appen med ikonet i systembakken.", + "attention_label": "Blink i proceslinjen ved ny post", + "attention_mac_label": "Lad Dock-ikonet hoppe ved ny post", + "attention_description": "Kun når appen ikke er forrest.", + "quiet_hours_label": "Stille timer", + "quiet_hours_description": "Ingen notifikationer eller lyde i dette tidsrum. Post kommer stadig.", + "quiet_hours_from": "Fra", + "quiet_hours_to": "til", + "show_shortcut_label": "Vis appen med en tastaturgenvej", + "show_shortcut_description": "{keys} henter appen frem fra enhver anden app.", + "show_shortcut_taken": "En anden app bruger {keys}. Luk den app, eller slå dette fra og til igen.", + "default_app_label": "Standardmailapp", + "default_app_description": "Åbn maillinks og .eml-, .ics- og .vcf-filer med denne app.", + "default_app_is_default": "Denne app åbner dine maillinks.", + "default_app_button": "Vælg standardapp…", + "new_mail": "Ny post", + "no_subject": "(intet emne)", + "action_delete": "Slet", + "copy_as_file": "Kopiér som fil", + "copied": "Filen er kopieret. Du kan indsætte den i en mappe.", + "copy_failed": "Filen kunne ikke kopieres.", + "reply_lost": "Dit skrevne svar kunne ikke gemmes. Åbn beskeden, og svar der." + }, "desktop_sign_in": { "browser_button": "Log ind med din browser", "browser_waiting": "Gør login færdigt i din browser", diff --git a/locales/de/common.json b/locales/de/common.json index efb641f2b..336c82000 100644 --- a/locales/de/common.json +++ b/locales/de/common.json @@ -6545,6 +6545,37 @@ "message_moved_to": "Diese Nachricht wurde nach {folder} verschoben.", "window_not_ready": "Das neue Fenster wurde nicht rechtzeitig geöffnet. Ihr Entwurf bleibt hier." }, + "desktopOs": { + "section_title": "Desktop-App", + "section_description": "Wie die App mit Ihrem Computer zusammenarbeitet.", + "close_to_tray_label": "Beim Schließen im Infobereich weiterlaufen", + "close_to_tray_description": "Neue E-Mails und Erinnerungen kommen auch dann an, wenn Sie das Fenster schließen.", + "start_at_login_label": "Beim Anmelden am Computer starten", + "start_at_login_description": "Die App startet von selbst, wenn Sie sich anmelden.", + "start_minimized_label": "Im Infobereich starten", + "start_minimized_description": "Beim Anmelden öffnet sich kein Fenster. Öffnen Sie die App über das Symbol im Infobereich.", + "attention_label": "Taskleiste bei neuen E-Mails blinken lassen", + "attention_mac_label": "Dock-Symbol bei neuen E-Mails hüpfen lassen", + "attention_description": "Nur wenn die App nicht im Vordergrund ist.", + "quiet_hours_label": "Ruhezeiten", + "quiet_hours_description": "In dieser Zeit keine Benachrichtigungen und Töne. E-Mails kommen trotzdem an.", + "quiet_hours_from": "Von", + "quiet_hours_to": "bis", + "show_shortcut_label": "App mit einer Tastenkombination anzeigen", + "show_shortcut_description": "{keys} holt die App aus jeder anderen App nach vorne.", + "show_shortcut_taken": "Eine andere App verwendet {keys}. Schließen Sie diese App oder schalten Sie das hier aus und wieder ein.", + "default_app_label": "Standard-E-Mail-App", + "default_app_description": "Mail-Links und .eml-, .ics- und .vcf-Dateien mit dieser App öffnen.", + "default_app_is_default": "Diese App öffnet Ihre Mail-Links.", + "default_app_button": "Standard-App auswählen…", + "new_mail": "Neue E-Mail", + "no_subject": "(kein Betreff)", + "action_delete": "Löschen", + "copy_as_file": "Als Datei kopieren", + "copied": "Datei kopiert. Sie können sie in einen Ordner einfügen.", + "copy_failed": "Die Datei konnte nicht kopiert werden.", + "reply_lost": "Ihre eingegebene Antwort konnte nicht übernommen werden. Öffnen Sie die Nachricht und antworten Sie dort." + }, "desktop_sign_in": { "browser_button": "Mit dem Browser anmelden", "browser_waiting": "Schließen Sie die Anmeldung im Browser ab", diff --git a/locales/en/common.json b/locales/en/common.json index 7728594e1..7ec268e1c 100644 --- a/locales/en/common.json +++ b/locales/en/common.json @@ -6545,6 +6545,37 @@ "message_moved_to": "This message was moved to {folder}.", "window_not_ready": "The new window did not open in time. Your draft stays here." }, + "desktopOs": { + "section_title": "Desktop app", + "section_description": "How the app works with your computer.", + "close_to_tray_label": "Keep running in the tray when closed", + "close_to_tray_description": "New mail and reminders keep coming after you close the window.", + "start_at_login_label": "Start when you sign in to your computer", + "start_at_login_description": "The app starts by itself when you sign in.", + "start_minimized_label": "Start in the tray", + "start_minimized_description": "No window opens at sign-in. Use the tray icon to open it.", + "attention_label": "Flash the taskbar on new mail", + "attention_mac_label": "Bounce the Dock icon on new mail", + "attention_description": "Only when the app is not in front.", + "quiet_hours_label": "Quiet hours", + "quiet_hours_description": "No notifications or sounds at these times. Mail still arrives.", + "quiet_hours_from": "From", + "quiet_hours_to": "to", + "show_shortcut_label": "Show the app with a keyboard shortcut", + "show_shortcut_description": "{keys} brings the app to the front, from any other app.", + "show_shortcut_taken": "Another app is using {keys}. Close that app, or turn this off and on again.", + "default_app_label": "Default mail app", + "default_app_description": "Open mail links and .eml, .ics and .vcf files with this app.", + "default_app_is_default": "This app opens your mail links.", + "default_app_button": "Choose default app…", + "new_mail": "New mail", + "no_subject": "(no subject)", + "action_delete": "Delete", + "copy_as_file": "Copy as file", + "copied": "File copied. You can paste it into a folder.", + "copy_failed": "The file could not be copied.", + "reply_lost": "Your typed reply could not be kept. Open the message and reply there." + }, "desktop_sign_in": { "browser_button": "Sign in with your browser", "browser_waiting": "Finish signing in in your browser", diff --git a/locales/es/common.json b/locales/es/common.json index 5a2af15a4..217aecbe4 100644 --- a/locales/es/common.json +++ b/locales/es/common.json @@ -6545,6 +6545,37 @@ "message_moved_to": "Este mensaje se movió a {folder}.", "window_not_ready": "La ventana nueva no se abrió a tiempo. Tu borrador se queda aquí." }, + "desktopOs": { + "section_title": "Aplicación de escritorio", + "section_description": "Cómo funciona la aplicación con tu ordenador.", + "close_to_tray_label": "Seguir en la bandeja al cerrar", + "close_to_tray_description": "El correo nuevo y los recordatorios siguen llegando después de cerrar la ventana.", + "start_at_login_label": "Iniciar al entrar en el ordenador", + "start_at_login_description": "La aplicación se inicia sola cuando entras.", + "start_minimized_label": "Iniciar en la bandeja", + "start_minimized_description": "No se abre ninguna ventana al entrar. Ábrela con el icono de la bandeja.", + "attention_label": "Hacer parpadear la barra de tareas con correo nuevo", + "attention_mac_label": "Hacer rebotar el icono del Dock con correo nuevo", + "attention_description": "Solo cuando la aplicación no está delante.", + "quiet_hours_label": "Horas de silencio", + "quiet_hours_description": "Sin notificaciones ni sonidos en estas horas. El correo sigue llegando.", + "quiet_hours_from": "De", + "quiet_hours_to": "a", + "show_shortcut_label": "Mostrar la aplicación con un atajo de teclado", + "show_shortcut_description": "{keys} trae la aplicación al frente desde cualquier otra.", + "show_shortcut_taken": "Otra aplicación está usando {keys}. Ciérrala, o desactiva y vuelve a activar esta opción.", + "default_app_label": "Aplicación de correo predeterminada", + "default_app_description": "Abrir los enlaces de correo y los archivos .eml, .ics y .vcf con esta aplicación.", + "default_app_is_default": "Esta aplicación abre tus enlaces de correo.", + "default_app_button": "Elegir aplicación predeterminada…", + "new_mail": "Correo nuevo", + "no_subject": "(sin asunto)", + "action_delete": "Eliminar", + "copy_as_file": "Copiar como archivo", + "copied": "Archivo copiado. Puedes pegarlo en una carpeta.", + "copy_failed": "No se pudo copiar el archivo.", + "reply_lost": "No se pudo conservar la respuesta que escribiste. Abre el mensaje y responde desde allí." + }, "desktop_sign_in": { "browser_button": "Iniciar sesión con el navegador", "browser_waiting": "Termina de iniciar sesión en el navegador", diff --git a/locales/et/common.json b/locales/et/common.json index b5ce8e291..82feaa2d2 100644 --- a/locales/et/common.json +++ b/locales/et/common.json @@ -6545,6 +6545,37 @@ "message_moved_to": "See kiri teisaldati kausta {folder}.", "window_not_ready": "Uus aken ei avanenud õigeks ajaks. Mustand jääb siia." }, + "desktopOs": { + "section_title": "Töölauarakendus", + "section_description": "Kuidas rakendus sinu arvutiga koos töötab.", + "close_to_tray_label": "Sulgemisel jätka salves", + "close_to_tray_description": "Uus kiri ja meeldetuletused saabuvad ka pärast akna sulgemist.", + "start_at_login_label": "Käivita arvutisse sisselogimisel", + "start_at_login_description": "Rakendus käivitub sisselogimisel ise.", + "start_minimized_label": "Käivita salves", + "start_minimized_description": "Sisselogimisel akent ei avata. Ava rakendus salve ikoonist.", + "attention_label": "Vilguta tegumiriba uue kirja korral", + "attention_mac_label": "Põrgata Docki ikooni uue kirja korral", + "attention_description": "Ainult siis, kui rakendus pole esiplaanil.", + "quiet_hours_label": "Vaikne aeg", + "quiet_hours_description": "Sel ajal teavitusi ega helisid pole. Kirjad saabuvad ikka.", + "quiet_hours_from": "Alates", + "quiet_hours_to": "kuni", + "show_shortcut_label": "Näita rakendust kiirklahviga", + "show_shortcut_description": "{keys} toob rakenduse esiplaanile igast teisest rakendusest.", + "show_shortcut_taken": "{keys} on teise rakenduse kasutuses. Sulge see rakendus või lülita see valik välja ja uuesti sisse.", + "default_app_label": "Vaikimisi e-posti rakendus", + "default_app_description": "Ava e-posti lingid ning .eml-, .ics- ja .vcf-failid selle rakendusega.", + "default_app_is_default": "See rakendus avab sinu e-posti lingid.", + "default_app_button": "Vali vaikerakendus…", + "new_mail": "Uus kiri", + "no_subject": "(teema puudub)", + "action_delete": "Kustuta", + "copy_as_file": "Kopeeri failina", + "copied": "Fail on kopeeritud. Saad selle kausta kleepida.", + "copy_failed": "Faili ei saanud kopeerida.", + "reply_lost": "Kirjutatud vastust ei saanud säilitada. Ava kiri ja vasta seal." + }, "desktop_sign_in": { "browser_button": "Logi sisse brauseriga", "browser_waiting": "Lõpeta sisselogimine brauseris", diff --git a/locales/fa/common.json b/locales/fa/common.json index 22a2c1d43..1b1b3e88f 100644 --- a/locales/fa/common.json +++ b/locales/fa/common.json @@ -6545,6 +6545,37 @@ "message_moved_to": "این پیام به {folder} منتقل شد.", "window_not_ready": "پنجرهٔ جدید به‌موقع باز نشد. پیش‌نویس شما همین‌جا می‌ماند." }, + "desktopOs": { + "section_title": "برنامه دسکتاپ", + "section_description": "نحوه کار برنامه با رایانه شما.", + "close_to_tray_label": "هنگام بستن در سینی سیستم اجرا بماند", + "close_to_tray_description": "نامه‌های جدید و یادآوری‌ها پس از بستن پنجره هم می‌رسند.", + "start_at_login_label": "هنگام ورود به رایانه شروع شود", + "start_at_login_description": "برنامه هنگام ورود خودش شروع می‌شود.", + "start_minimized_label": "شروع در سینی سیستم", + "start_minimized_description": "هنگام ورود پنجره‌ای باز نمی‌شود. با نماد سینی سیستم آن را باز کنید.", + "attention_label": "چشمک نوار وظیفه هنگام نامه جدید", + "attention_mac_label": "جهش نماد Dock هنگام نامه جدید", + "attention_description": "فقط وقتی برنامه در جلو نیست.", + "quiet_hours_label": "ساعات سکوت", + "quiet_hours_description": "در این ساعات اعلان و صدایی نیست. نامه‌ها همچنان می‌رسند.", + "quiet_hours_from": "از", + "quiet_hours_to": "تا", + "show_shortcut_label": "نمایش برنامه با میان‌بر صفحه‌کلید", + "show_shortcut_description": "{keys} برنامه را از هر برنامه دیگری به جلو می‌آورد.", + "show_shortcut_taken": "برنامه دیگری از {keys} استفاده می‌کند. آن را ببندید، یا این گزینه را خاموش و دوباره روشن کنید.", + "default_app_label": "برنامه پیش‌فرض ایمیل", + "default_app_description": "باز کردن پیوندهای ایمیل و پرونده‌های ‎.eml، ‎.ics و ‎.vcf با این برنامه.", + "default_app_is_default": "این برنامه پیوندهای ایمیل شما را باز می‌کند.", + "default_app_button": "انتخاب برنامه پیش‌فرض…", + "new_mail": "نامه جدید", + "no_subject": "(بدون موضوع)", + "action_delete": "حذف", + "copy_as_file": "رونوشت به‌صورت پرونده", + "copied": "پرونده رونوشت شد. می‌توانید آن را در یک پوشه بچسبانید.", + "copy_failed": "رونوشت پرونده ممکن نشد.", + "reply_lost": "پاسخی که نوشتید نگه داشته نشد. پیام را باز کنید و از آنجا پاسخ دهید." + }, "desktop_sign_in": { "browser_button": "ورود با مرورگر", "browser_waiting": "ورود را در مرورگر کامل کنید", diff --git a/locales/fr/common.json b/locales/fr/common.json index e30800b16..864c7c969 100644 --- a/locales/fr/common.json +++ b/locales/fr/common.json @@ -6545,6 +6545,37 @@ "message_moved_to": "Ce message a été déplacé vers {folder}.", "window_not_ready": "La nouvelle fenêtre ne s’est pas ouverte à temps. Votre brouillon reste ici." }, + "desktopOs": { + "section_title": "Application de bureau", + "section_description": "Comment l'application fonctionne avec votre ordinateur.", + "close_to_tray_label": "Rester dans la zone de notification à la fermeture", + "close_to_tray_description": "Le courrier et les rappels continuent d'arriver après la fermeture de la fenêtre.", + "start_at_login_label": "Démarrer à l'ouverture de session", + "start_at_login_description": "L'application démarre seule quand vous ouvrez votre session.", + "start_minimized_label": "Démarrer dans la zone de notification", + "start_minimized_description": "Aucune fenêtre ne s'ouvre à l'ouverture de session. Ouvrez-la avec l'icône de la zone de notification.", + "attention_label": "Faire clignoter la barre des tâches à l'arrivée de courrier", + "attention_mac_label": "Faire rebondir l'icône du Dock à l'arrivée de courrier", + "attention_description": "Seulement quand l'application n'est pas au premier plan.", + "quiet_hours_label": "Heures calmes", + "quiet_hours_description": "Ni notifications ni sons pendant ces heures. Le courrier continue d'arriver.", + "quiet_hours_from": "De", + "quiet_hours_to": "à", + "show_shortcut_label": "Afficher l'application avec un raccourci clavier", + "show_shortcut_description": "{keys} amène l'application au premier plan depuis n'importe quelle autre.", + "show_shortcut_taken": "Une autre application utilise {keys}. Fermez-la, ou désactivez puis réactivez cette option.", + "default_app_label": "Application de messagerie par défaut", + "default_app_description": "Ouvrir les liens de messagerie et les fichiers .eml, .ics et .vcf avec cette application.", + "default_app_is_default": "Cette application ouvre vos liens de messagerie.", + "default_app_button": "Choisir l'application par défaut…", + "new_mail": "Nouveau courrier", + "no_subject": "(sans objet)", + "action_delete": "Supprimer", + "copy_as_file": "Copier en tant que fichier", + "copied": "Fichier copié. Vous pouvez le coller dans un dossier.", + "copy_failed": "Le fichier n'a pas pu être copié.", + "reply_lost": "Votre réponse saisie n'a pas pu être conservée. Ouvrez le message et répondez depuis celui-ci." + }, "desktop_sign_in": { "browser_button": "Se connecter avec le navigateur", "browser_waiting": "Terminez la connexion dans votre navigateur", diff --git a/locales/he/common.json b/locales/he/common.json index 571f30e34..8c3d28f99 100644 --- a/locales/he/common.json +++ b/locales/he/common.json @@ -6546,6 +6546,37 @@ "message_moved_to": "ההודעה הזו הועברה אל {folder}.", "window_not_ready": "החלון החדש לא נפתח בזמן. הטיוטה נשארת כאן." }, + "desktopOs": { + "section_title": "יישום שולחן עבודה", + "section_description": "איך היישום עובד עם המחשב שלך.", + "close_to_tray_label": "להמשיך לפעול במגש המערכת בסגירה", + "close_to_tray_description": "דואר חדש ותזכורות ממשיכים להגיע אחרי סגירת החלון.", + "start_at_login_label": "להפעיל בכניסה למחשב", + "start_at_login_description": "היישום מופעל מעצמו כשנכנסים.", + "start_minimized_label": "להפעיל במגש המערכת", + "start_minimized_description": "בכניסה לא נפתח חלון. פותחים אותו מהסמל במגש המערכת.", + "attention_label": "הבהוב בשורת המשימות בדואר חדש", + "attention_mac_label": "קפיצת סמל ה-Dock בדואר חדש", + "attention_description": "רק כשהיישום לא בחזית.", + "quiet_hours_label": "שעות שקט", + "quiet_hours_description": "בשעות אלה אין התראות וצלילים. הדואר ממשיך להגיע.", + "quiet_hours_from": "מ-", + "quiet_hours_to": "עד", + "show_shortcut_label": "הצגת היישום בקיצור מקשים", + "show_shortcut_description": "{keys} מביא את היישום לחזית מכל יישום אחר.", + "show_shortcut_taken": "יישום אחר משתמש ב-{keys}. סגרו אותו, או כבו והפעילו מחדש את האפשרות הזו.", + "default_app_label": "יישום הדואר המוגדר כברירת מחדל", + "default_app_description": "פתיחת קישורי דואר וקובצי ‎.eml,‏ ‎.ics ו-‎.vcf ביישום זה.", + "default_app_is_default": "יישום זה פותח את קישורי הדואר שלך.", + "default_app_button": "בחירת יישום ברירת מחדל…", + "new_mail": "דואר חדש", + "no_subject": "(ללא נושא)", + "action_delete": "מחיקה", + "copy_as_file": "העתקה כקובץ", + "copied": "הקובץ הועתק. אפשר להדביק אותו בתיקייה.", + "copy_failed": "לא ניתן להעתיק את הקובץ.", + "reply_lost": "לא ניתן היה לשמור את התשובה שהקלדת. פתחו את ההודעה והשיבו משם." + }, "desktop_sign_in": { "browser_button": "כניסה באמצעות הדפדפן", "browser_waiting": "יש להשלים את הכניסה בדפדפן", diff --git a/locales/hu/common.json b/locales/hu/common.json index 2ab59129e..60c3ee5f3 100644 --- a/locales/hu/common.json +++ b/locales/hu/common.json @@ -6545,6 +6545,37 @@ "message_moved_to": "Ezt az üzenetet ide helyezték át: {folder}.", "window_not_ready": "Az új ablak nem nyílt meg időben. A piszkozat itt marad." }, + "desktopOs": { + "section_title": "Asztali alkalmazás", + "section_description": "Hogyan működik az alkalmazás a számítógépével.", + "close_to_tray_label": "Bezáráskor fusson tovább a tálcán", + "close_to_tray_description": "Az új levelek és emlékeztetők az ablak bezárása után is megérkeznek.", + "start_at_login_label": "Indítás a számítógépre való bejelentkezéskor", + "start_at_login_description": "Az alkalmazás bejelentkezéskor magától elindul.", + "start_minimized_label": "Indítás a tálcán", + "start_minimized_description": "Bejelentkezéskor nem nyílik ablak. A tálcaikonnal nyithatja meg.", + "attention_label": "Tálca villogtatása új levélnél", + "attention_mac_label": "Dock-ikon ugráltatása új levélnél", + "attention_description": "Csak ha az alkalmazás nincs előtérben.", + "quiet_hours_label": "Csendes órák", + "quiet_hours_description": "Ekkor nincs értesítés és hang. A levelek továbbra is megérkeznek.", + "quiet_hours_from": "Ettől", + "quiet_hours_to": "eddig", + "show_shortcut_label": "Az alkalmazás megjelenítése billentyűparanccsal", + "show_shortcut_description": "{keys} bármely más alkalmazásból előtérbe hozza az alkalmazást.", + "show_shortcut_taken": "Egy másik alkalmazás használja ezt: {keys}. Zárja be azt, vagy kapcsolja ki és be ezt a beállítást.", + "default_app_label": "Alapértelmezett levelezőalkalmazás", + "default_app_description": "A levélhivatkozások és a .eml, .ics és .vcf fájlok megnyitása ezzel az alkalmazással.", + "default_app_is_default": "Ez az alkalmazás nyitja meg a levélhivatkozásait.", + "default_app_button": "Alapértelmezett alkalmazás kiválasztása…", + "new_mail": "Új levél", + "no_subject": "(nincs tárgy)", + "action_delete": "Törlés", + "copy_as_file": "Másolás fájlként", + "copied": "Fájl másolva. Beillesztheti egy mappába.", + "copy_failed": "A fájlt nem sikerült másolni.", + "reply_lost": "A beírt válasz nem maradt meg. Nyissa meg az üzenetet, és válaszoljon ott." + }, "desktop_sign_in": { "browser_button": "Bejelentkezés böngészővel", "browser_waiting": "Fejezze be a bejelentkezést a böngészőben", diff --git a/locales/it/common.json b/locales/it/common.json index a861d3390..c7e482c6d 100644 --- a/locales/it/common.json +++ b/locales/it/common.json @@ -6545,6 +6545,37 @@ "message_moved_to": "Questo messaggio è stato spostato in {folder}.", "window_not_ready": "La nuova finestra non si è aperta in tempo. La bozza rimane qui." }, + "desktopOs": { + "section_title": "App desktop", + "section_description": "Come l'app funziona con il tuo computer.", + "close_to_tray_label": "Resta nell'area di notifica alla chiusura", + "close_to_tray_description": "La nuova posta e i promemoria continuano ad arrivare dopo la chiusura della finestra.", + "start_at_login_label": "Avvia all'accesso al computer", + "start_at_login_description": "L'app si avvia da sola quando accedi.", + "start_minimized_label": "Avvia nell'area di notifica", + "start_minimized_description": "All'accesso non si apre alcuna finestra. Aprila con l'icona nell'area di notifica.", + "attention_label": "Fai lampeggiare la barra delle applicazioni con nuova posta", + "attention_mac_label": "Fai rimbalzare l'icona del Dock con nuova posta", + "attention_description": "Solo quando l'app non è in primo piano.", + "quiet_hours_label": "Ore di silenzio", + "quiet_hours_description": "Nessuna notifica o suono in queste ore. La posta continua ad arrivare.", + "quiet_hours_from": "Da", + "quiet_hours_to": "a", + "show_shortcut_label": "Mostra l'app con una scorciatoia da tastiera", + "show_shortcut_description": "{keys} porta l'app in primo piano da qualsiasi altra app.", + "show_shortcut_taken": "Un'altra app usa {keys}. Chiudila, oppure disattiva e riattiva questa opzione.", + "default_app_label": "App di posta predefinita", + "default_app_description": "Apri i link di posta e i file .eml, .ics e .vcf con questa app.", + "default_app_is_default": "Questa app apre i tuoi link di posta.", + "default_app_button": "Scegli app predefinita…", + "new_mail": "Nuova posta", + "no_subject": "(nessun oggetto)", + "action_delete": "Elimina", + "copy_as_file": "Copia come file", + "copied": "File copiato. Puoi incollarlo in una cartella.", + "copy_failed": "Impossibile copiare il file.", + "reply_lost": "Non è stato possibile conservare la risposta digitata. Apri il messaggio e rispondi da lì." + }, "desktop_sign_in": { "browser_button": "Accedi con il browser", "browser_waiting": "Completa l'accesso nel browser", diff --git a/locales/ja/common.json b/locales/ja/common.json index 0606617f1..6927afe1a 100644 --- a/locales/ja/common.json +++ b/locales/ja/common.json @@ -6545,6 +6545,37 @@ "message_moved_to": "このメッセージは {folder} に移動されました。", "window_not_ready": "新しいウィンドウが時間内に開きませんでした。下書きはここに残ります。" }, + "desktopOs": { + "section_title": "デスクトップアプリ", + "section_description": "アプリとパソコンの連携方法です。", + "close_to_tray_label": "閉じてもトレイで実行を続ける", + "close_to_tray_description": "ウィンドウを閉じても新着メールとリマインダーは届きます。", + "start_at_login_label": "パソコンへのサインイン時に起動", + "start_at_login_description": "サインインするとアプリが自動で起動します。", + "start_minimized_label": "トレイで起動", + "start_minimized_description": "サインイン時にウィンドウは開きません。トレイのアイコンから開いてください。", + "attention_label": "新着メールでタスクバーを点滅", + "attention_mac_label": "新着メールで Dock アイコンを跳ねさせる", + "attention_description": "アプリが前面にないときのみ。", + "quiet_hours_label": "おやすみ時間", + "quiet_hours_description": "この時間は通知も音も出ません。メールは届きます。", + "quiet_hours_from": "開始", + "quiet_hours_to": "終了", + "show_shortcut_label": "キーボードショートカットでアプリを表示", + "show_shortcut_description": "{keys} で、ほかのアプリからこのアプリを前面に表示します。", + "show_shortcut_taken": "別のアプリが {keys} を使っています。そのアプリを閉じるか、この設定をオフにしてからオンに戻してください。", + "default_app_label": "既定のメールアプリ", + "default_app_description": "メールのリンクと .eml、.ics、.vcf ファイルをこのアプリで開きます。", + "default_app_is_default": "メールのリンクはこのアプリで開きます。", + "default_app_button": "既定のアプリを選択…", + "new_mail": "新着メール", + "no_subject": "(件名なし)", + "action_delete": "削除", + "copy_as_file": "ファイルとしてコピー", + "copied": "ファイルをコピーしました。フォルダーに貼り付けられます。", + "copy_failed": "ファイルをコピーできませんでした。", + "reply_lost": "入力した返信を保持できませんでした。メッセージを開いて返信してください。" + }, "desktop_sign_in": { "browser_button": "ブラウザでサインイン", "browser_waiting": "ブラウザでサインインを完了してください", diff --git a/locales/ko/common.json b/locales/ko/common.json index 7e0daa574..ad9c104f7 100644 --- a/locales/ko/common.json +++ b/locales/ko/common.json @@ -6545,6 +6545,37 @@ "message_moved_to": "이 메시지는 {folder}(으)로 이동되었습니다.", "window_not_ready": "새 창이 제때 열리지 않았습니다. 임시 보관 메일은 여기에 남아 있습니다." }, + "desktopOs": { + "section_title": "데스크톱 앱", + "section_description": "앱이 컴퓨터와 함께 작동하는 방식입니다.", + "close_to_tray_label": "닫을 때 트레이에서 계속 실행", + "close_to_tray_description": "창을 닫아도 새 메일과 리마인더가 계속 도착합니다.", + "start_at_login_label": "컴퓨터에 로그인할 때 시작", + "start_at_login_description": "로그인하면 앱이 자동으로 시작됩니다.", + "start_minimized_label": "트레이에서 시작", + "start_minimized_description": "로그인할 때 창이 열리지 않습니다. 트레이 아이콘으로 여세요.", + "attention_label": "새 메일이 오면 작업 표시줄 깜박이기", + "attention_mac_label": "새 메일이 오면 Dock 아이콘 튀기기", + "attention_description": "앱이 앞에 있지 않을 때만 작동합니다.", + "quiet_hours_label": "방해 금지 시간", + "quiet_hours_description": "이 시간에는 알림과 소리가 없습니다. 메일은 계속 도착합니다.", + "quiet_hours_from": "시작", + "quiet_hours_to": "종료", + "show_shortcut_label": "키보드 단축키로 앱 표시", + "show_shortcut_description": "{keys} 키로 다른 앱에서도 이 앱을 앞으로 가져옵니다.", + "show_shortcut_taken": "다른 앱이 {keys} 키를 사용 중입니다. 그 앱을 닫거나 이 옵션을 껐다가 다시 켜세요.", + "default_app_label": "기본 메일 앱", + "default_app_description": "메일 링크와 .eml, .ics, .vcf 파일을 이 앱으로 엽니다.", + "default_app_is_default": "이 앱이 메일 링크를 엽니다.", + "default_app_button": "기본 앱 선택…", + "new_mail": "새 메일", + "no_subject": "(제목 없음)", + "action_delete": "삭제", + "copy_as_file": "파일로 복사", + "copied": "파일을 복사했습니다. 폴더에 붙여넣을 수 있습니다.", + "copy_failed": "파일을 복사할 수 없습니다.", + "reply_lost": "입력한 답장을 유지할 수 없습니다. 메시지를 열어 답장하세요." + }, "desktop_sign_in": { "browser_button": "브라우저로 로그인", "browser_waiting": "브라우저에서 로그인을 마치세요", diff --git a/locales/lv/common.json b/locales/lv/common.json index 05939fbc0..a5295665f 100644 --- a/locales/lv/common.json +++ b/locales/lv/common.json @@ -6545,6 +6545,37 @@ "message_moved_to": "Šis ziņojums tika pārvietots uz {folder}.", "window_not_ready": "Jaunais logs neatvērās laikus. Melnraksts paliek šeit." }, + "desktopOs": { + "section_title": "Darbvirsmas lietotne", + "section_description": "Kā lietotne darbojas ar jūsu datoru.", + "close_to_tray_label": "Aizverot turpināt darbu teknē", + "close_to_tray_description": "Jauns pasts un atgādinājumi turpina pienākt arī pēc loga aizvēršanas.", + "start_at_login_label": "Sākt, piesakoties datorā", + "start_at_login_description": "Lietotne sākas pati, kad piesakāties.", + "start_minimized_label": "Sākt teknē", + "start_minimized_description": "Piesakoties logs neatveras. Atveriet to ar teknes ikonu.", + "attention_label": "Mirgot uzdevumjoslā, pienākot pastam", + "attention_mac_label": "Lēkāt Dock ikonai, pienākot pastam", + "attention_description": "Tikai tad, ja lietotne nav priekšplānā.", + "quiet_hours_label": "Klusuma stundas", + "quiet_hours_description": "Šajā laikā nav paziņojumu un skaņu. Pasts turpina pienākt.", + "quiet_hours_from": "No", + "quiet_hours_to": "līdz", + "show_shortcut_label": "Rādīt lietotni ar īsinājumtaustiņu", + "show_shortcut_description": "{keys} parāda lietotni priekšplānā no jebkuras citas lietotnes.", + "show_shortcut_taken": "{keys} izmanto cita lietotne. Aizveriet to vai izslēdziet un atkal ieslēdziet šo iestatījumu.", + "default_app_label": "Noklusējuma pasta lietotne", + "default_app_description": "Atvērt pasta saites un .eml, .ics un .vcf failus ar šo lietotni.", + "default_app_is_default": "Šī lietotne atver jūsu pasta saites.", + "default_app_button": "Izvēlēties noklusējuma lietotni…", + "new_mail": "Jauns pasts", + "no_subject": "(nav tēmas)", + "action_delete": "Dzēst", + "copy_as_file": "Kopēt kā failu", + "copied": "Fails nokopēts. Varat to ielīmēt mapē.", + "copy_failed": "Failu neizdevās nokopēt.", + "reply_lost": "Ievadīto atbildi neizdevās saglabāt. Atveriet ziņojumu un atbildiet tur." + }, "desktop_sign_in": { "browser_button": "Pierakstīties ar pārlūku", "browser_waiting": "Pabeidziet pierakstīšanos pārlūkā", diff --git a/locales/mn/common.json b/locales/mn/common.json index dfa942d34..7fe52244f 100644 --- a/locales/mn/common.json +++ b/locales/mn/common.json @@ -6545,6 +6545,37 @@ "message_moved_to": "Энэ мессежийг {folder} руу зөөсөн.", "window_not_ready": "Шинэ цонх цагтаа нээгдсэнгүй. Таны ноорог энд үлдэнэ." }, + "desktopOs": { + "section_title": "Ширээний апп", + "section_description": "Апп таны компьютертэй хэрхэн ажиллах.", + "close_to_tray_label": "Хаахад тавиур дээр үргэлжлүүлэн ажиллах", + "close_to_tray_description": "Цонхыг хаасны дараа ч шинэ захиа, сануулга ирсээр байна.", + "start_at_login_label": "Компьютерт нэвтрэхэд эхлүүлэх", + "start_at_login_description": "Нэвтрэхэд апп өөрөө эхэлнэ.", + "start_minimized_label": "Тавиур дээр эхлүүлэх", + "start_minimized_description": "Нэвтрэхэд цонх нээгдэхгүй. Тавиурын дүрсээр нээнэ үү.", + "attention_label": "Шинэ захиа ирэхэд ажлын самбарыг анивчуулах", + "attention_mac_label": "Шинэ захиа ирэхэд Dock дүрсийг үсрүүлэх", + "attention_description": "Зөвхөн апп урд талд байхгүй үед.", + "quiet_hours_label": "Чимээгүй цаг", + "quiet_hours_description": "Энэ үед мэдэгдэл, дуу гарахгүй. Захиа ирсээр байна.", + "quiet_hours_from": "Эхлэх", + "quiet_hours_to": "хүртэл", + "show_shortcut_label": "Гарын товчлолоор аппыг харуулах", + "show_shortcut_description": "{keys} нь аль ч аппаас энэ аппыг урд гаргана.", + "show_shortcut_taken": "{keys}-г өөр апп ашиглаж байна. Тэр аппыг хаах, эсвэл үүнийг унтраагаад дахин асаана уу.", + "default_app_label": "Үндсэн шуудангийн апп", + "default_app_description": "Шуудангийн холбоос болон .eml, .ics, .vcf файлыг энэ аппаар нээх.", + "default_app_is_default": "Энэ апп таны шуудангийн холбоосыг нээдэг.", + "default_app_button": "Үндсэн апп сонгох…", + "new_mail": "Шинэ захиа", + "no_subject": "(гарчиггүй)", + "action_delete": "Устгах", + "copy_as_file": "Файл болгож хуулах", + "copied": "Файлыг хууллаа. Хавтсанд буулгаж болно.", + "copy_failed": "Файлыг хуулж чадсангүй.", + "reply_lost": "Бичсэн хариуг хадгалж чадсангүй. Захиаг нээгээд тэндээс хариулна уу." + }, "desktop_sign_in": { "browser_button": "Хөтчөөр нэвтрэх", "browser_waiting": "Нэвтрэлтээ хөтөч дээр дуусгана уу", diff --git a/locales/nb/common.json b/locales/nb/common.json index a0002acca..9dbd11a2c 100644 --- a/locales/nb/common.json +++ b/locales/nb/common.json @@ -6545,6 +6545,37 @@ "message_moved_to": "Denne meldingen ble flyttet til {folder}.", "window_not_ready": "Det nye vinduet åpnet seg ikke i tide. Utkastet blir her." }, + "desktopOs": { + "section_title": "Skrivebordsapp", + "section_description": "Slik samarbeider appen med datamaskinen din.", + "close_to_tray_label": "Fortsett i systemstatusfeltet ved lukking", + "close_to_tray_description": "Ny e-post og påminnelser fortsetter å komme etter at du lukker vinduet.", + "start_at_login_label": "Start når du logger på datamaskinen", + "start_at_login_description": "Appen starter av seg selv når du logger på.", + "start_minimized_label": "Start i systemstatusfeltet", + "start_minimized_description": "Det åpnes ikke noe vindu ved pålogging. Åpne appen med ikonet i systemstatusfeltet.", + "attention_label": "Blink i oppgavelinjen ved ny e-post", + "attention_mac_label": "La Dock-ikonet sprette ved ny e-post", + "attention_description": "Bare når appen ikke er foran.", + "quiet_hours_label": "Stille timer", + "quiet_hours_description": "Ingen varsler eller lyder i dette tidsrommet. E-post kommer fortsatt.", + "quiet_hours_from": "Fra", + "quiet_hours_to": "til", + "show_shortcut_label": "Vis appen med en hurtigtast", + "show_shortcut_description": "{keys} henter appen frem fra hvilken som helst annen app.", + "show_shortcut_taken": "En annen app bruker {keys}. Lukk den appen, eller slå dette av og på igjen.", + "default_app_label": "Standard e-postapp", + "default_app_description": "Åpne e-postlenker og .eml-, .ics- og .vcf-filer med denne appen.", + "default_app_is_default": "Denne appen åpner e-postlenkene dine.", + "default_app_button": "Velg standardapp…", + "new_mail": "Ny e-post", + "no_subject": "(uten emne)", + "action_delete": "Slett", + "copy_as_file": "Kopier som fil", + "copied": "Filen er kopiert. Du kan lime den inn i en mappe.", + "copy_failed": "Filen kunne ikke kopieres.", + "reply_lost": "Svaret du skrev, kunne ikke beholdes. Åpne meldingen og svar der." + }, "desktop_sign_in": { "browser_button": "Logg på med nettleseren", "browser_waiting": "Fullfør påloggingen i nettleseren", diff --git a/locales/nl/common.json b/locales/nl/common.json index 5524a36da..ca060609f 100644 --- a/locales/nl/common.json +++ b/locales/nl/common.json @@ -6545,6 +6545,37 @@ "message_moved_to": "Dit bericht is verplaatst naar {folder}.", "window_not_ready": "Het nieuwe venster is niet op tijd geopend. Je concept blijft hier." }, + "desktopOs": { + "section_title": "Desktop-app", + "section_description": "Hoe de app samenwerkt met je computer.", + "close_to_tray_label": "Bij sluiten doorgaan in het systeemvak", + "close_to_tray_description": "Nieuwe mail en herinneringen blijven komen nadat je het venster sluit.", + "start_at_login_label": "Starten als je je aanmeldt op de computer", + "start_at_login_description": "De app start vanzelf als je je aanmeldt.", + "start_minimized_label": "Starten in het systeemvak", + "start_minimized_description": "Bij het aanmelden opent geen venster. Open de app met het pictogram in het systeemvak.", + "attention_label": "Taakbalk laten knipperen bij nieuwe mail", + "attention_mac_label": "Dock-pictogram laten stuiteren bij nieuwe mail", + "attention_description": "Alleen als de app niet vooraan staat.", + "quiet_hours_label": "Stille uren", + "quiet_hours_description": "Geen meldingen of geluiden op deze tijden. Mail blijft binnenkomen.", + "quiet_hours_from": "Van", + "quiet_hours_to": "tot", + "show_shortcut_label": "App tonen met een sneltoets", + "show_shortcut_description": "{keys} haalt de app naar voren vanuit elke andere app.", + "show_shortcut_taken": "Een andere app gebruikt {keys}. Sluit die app, of zet dit uit en weer aan.", + "default_app_label": "Standaard-mailapp", + "default_app_description": "Maillinks en .eml-, .ics- en .vcf-bestanden openen met deze app.", + "default_app_is_default": "Deze app opent je maillinks.", + "default_app_button": "Standaard-app kiezen…", + "new_mail": "Nieuwe mail", + "no_subject": "(geen onderwerp)", + "action_delete": "Verwijderen", + "copy_as_file": "Kopiëren als bestand", + "copied": "Bestand gekopieerd. Je kunt het in een map plakken.", + "copy_failed": "Het bestand kon niet worden gekopieerd.", + "reply_lost": "Je getypte antwoord kon niet worden bewaard. Open het bericht en antwoord daar." + }, "desktop_sign_in": { "browser_button": "Inloggen met je browser", "browser_waiting": "Rond het inloggen af in je browser", diff --git a/locales/pl/common.json b/locales/pl/common.json index fa5b5481e..155963c34 100644 --- a/locales/pl/common.json +++ b/locales/pl/common.json @@ -6545,6 +6545,37 @@ "message_moved_to": "Ta wiadomość została przeniesiona do {folder}.", "window_not_ready": "Nowe okno nie otworzyło się na czas. Twój szkic zostaje tutaj." }, + "desktopOs": { + "section_title": "Aplikacja na komputer", + "section_description": "Jak aplikacja współpracuje z Twoim komputerem.", + "close_to_tray_label": "Po zamknięciu działaj dalej w zasobniku", + "close_to_tray_description": "Nowa poczta i przypomnienia docierają też po zamknięciu okna.", + "start_at_login_label": "Uruchamiaj po zalogowaniu do komputera", + "start_at_login_description": "Aplikacja uruchamia się sama po zalogowaniu.", + "start_minimized_label": "Uruchamiaj w zasobniku", + "start_minimized_description": "Po zalogowaniu nie otwiera się okno. Otwórz je ikoną w zasobniku.", + "attention_label": "Migaj na pasku zadań przy nowej poczcie", + "attention_mac_label": "Podskakuj ikoną w Docku przy nowej poczcie", + "attention_description": "Tylko gdy aplikacja nie jest na wierzchu.", + "quiet_hours_label": "Godziny ciszy", + "quiet_hours_description": "W tym czasie bez powiadomień i dźwięków. Poczta nadal dociera.", + "quiet_hours_from": "Od", + "quiet_hours_to": "do", + "show_shortcut_label": "Pokazuj aplikację skrótem klawiszowym", + "show_shortcut_description": "{keys} przywołuje aplikację z każdej innej aplikacji.", + "show_shortcut_taken": "Skrót {keys} jest używany przez inną aplikację. Zamknij ją albo wyłącz i włącz tę opcję.", + "default_app_label": "Domyślna aplikacja poczty", + "default_app_description": "Otwieraj linki pocztowe i pliki .eml, .ics i .vcf w tej aplikacji.", + "default_app_is_default": "Ta aplikacja otwiera Twoje linki pocztowe.", + "default_app_button": "Wybierz domyślną aplikację…", + "new_mail": "Nowa poczta", + "no_subject": "(bez tematu)", + "action_delete": "Usuń", + "copy_as_file": "Kopiuj jako plik", + "copied": "Plik skopiowany. Możesz wkleić go do folderu.", + "copy_failed": "Nie udało się skopiować pliku.", + "reply_lost": "Nie udało się zachować wpisanej odpowiedzi. Otwórz wiadomość i odpowiedz w niej." + }, "desktop_sign_in": { "browser_button": "Zaloguj się w przeglądarce", "browser_waiting": "Dokończ logowanie w przeglądarce", diff --git a/locales/pt/common.json b/locales/pt/common.json index 87a2e2ff1..9940ba25c 100644 --- a/locales/pt/common.json +++ b/locales/pt/common.json @@ -6545,6 +6545,37 @@ "message_moved_to": "Esta mensagem foi movida para {folder}.", "window_not_ready": "A nova janela não abriu a tempo. O seu rascunho fica aqui." }, + "desktopOs": { + "section_title": "Aplicação de ambiente de trabalho", + "section_description": "Como a aplicação funciona com o seu computador.", + "close_to_tray_label": "Continuar na bandeja ao fechar", + "close_to_tray_description": "O correio novo e os lembretes continuam a chegar depois de fechar a janela.", + "start_at_login_label": "Iniciar ao entrar no computador", + "start_at_login_description": "A aplicação inicia sozinha quando entra.", + "start_minimized_label": "Iniciar na bandeja", + "start_minimized_description": "Não abre nenhuma janela ao entrar. Abra-a com o ícone da bandeja.", + "attention_label": "Piscar a barra de tarefas com correio novo", + "attention_mac_label": "Saltar o ícone da Dock com correio novo", + "attention_description": "Só quando a aplicação não está à frente.", + "quiet_hours_label": "Horas de silêncio", + "quiet_hours_description": "Sem notificações nem sons nestas horas. O correio continua a chegar.", + "quiet_hours_from": "De", + "quiet_hours_to": "até", + "show_shortcut_label": "Mostrar a aplicação com um atalho de teclado", + "show_shortcut_description": "{keys} traz a aplicação para a frente a partir de qualquer outra.", + "show_shortcut_taken": "Outra aplicação está a usar {keys}. Feche-a, ou desative e volte a ativar esta opção.", + "default_app_label": "Aplicação de correio predefinida", + "default_app_description": "Abrir ligações de correio e ficheiros .eml, .ics e .vcf com esta aplicação.", + "default_app_is_default": "Esta aplicação abre as suas ligações de correio.", + "default_app_button": "Escolher aplicação predefinida…", + "new_mail": "Correio novo", + "no_subject": "(sem assunto)", + "action_delete": "Eliminar", + "copy_as_file": "Copiar como ficheiro", + "copied": "Ficheiro copiado. Pode colá-lo numa pasta.", + "copy_failed": "Não foi possível copiar o ficheiro.", + "reply_lost": "Não foi possível guardar a resposta escrita. Abra a mensagem e responda a partir dela." + }, "desktop_sign_in": { "browser_button": "Entrar com o navegador", "browser_waiting": "Termine de entrar no navegador", diff --git a/locales/ro/common.json b/locales/ro/common.json index 80c31fa28..b12f0f293 100644 --- a/locales/ro/common.json +++ b/locales/ro/common.json @@ -6545,6 +6545,37 @@ "message_moved_to": "Acest mesaj a fost mutat în {folder}.", "window_not_ready": "Fereastra nouă nu s-a deschis la timp. Ciorna rămâne aici." }, + "desktopOs": { + "section_title": "Aplicație desktop", + "section_description": "Cum funcționează aplicația cu computerul tău.", + "close_to_tray_label": "Rămâne în zona de notificări la închidere", + "close_to_tray_description": "E-mailurile noi și mementourile continuă să sosească după închiderea ferestrei.", + "start_at_login_label": "Pornește la conectarea în computer", + "start_at_login_description": "Aplicația pornește singură când te conectezi.", + "start_minimized_label": "Pornește în zona de notificări", + "start_minimized_description": "La conectare nu se deschide nicio fereastră. Deschide-o din pictograma din zona de notificări.", + "attention_label": "Clipește în bara de activități la e-mail nou", + "attention_mac_label": "Sare pictograma din Dock la e-mail nou", + "attention_description": "Doar când aplicația nu este în față.", + "quiet_hours_label": "Ore de liniște", + "quiet_hours_description": "Fără notificări sau sunete în aceste ore. E-mailurile sosesc în continuare.", + "quiet_hours_from": "De la", + "quiet_hours_to": "până la", + "show_shortcut_label": "Arată aplicația cu o comandă rapidă de la tastatură", + "show_shortcut_description": "{keys} aduce aplicația în față din orice altă aplicație.", + "show_shortcut_taken": "Altă aplicație folosește {keys}. Închide-o sau dezactivează și reactivează această opțiune.", + "default_app_label": "Aplicația de e-mail implicită", + "default_app_description": "Deschide linkurile de e-mail și fișierele .eml, .ics și .vcf cu această aplicație.", + "default_app_is_default": "Această aplicație îți deschide linkurile de e-mail.", + "default_app_button": "Alege aplicația implicită…", + "new_mail": "E-mail nou", + "no_subject": "(fără subiect)", + "action_delete": "Șterge", + "copy_as_file": "Copiază ca fișier", + "copied": "Fișier copiat. Îl poți lipi într-un dosar.", + "copy_failed": "Fișierul nu a putut fi copiat.", + "reply_lost": "Răspunsul scris nu a putut fi păstrat. Deschide mesajul și răspunde de acolo." + }, "desktop_sign_in": { "browser_button": "Conectează-te cu browserul", "browser_waiting": "Finalizează conectarea în browser", diff --git a/locales/ru/common.json b/locales/ru/common.json index 55510f4c8..2679de84a 100644 --- a/locales/ru/common.json +++ b/locales/ru/common.json @@ -6545,6 +6545,37 @@ "message_moved_to": "Это письмо перемещено в {folder}.", "window_not_ready": "Новое окно не открылось вовремя. Черновик остаётся здесь." }, + "desktopOs": { + "section_title": "Настольное приложение", + "section_description": "Как приложение работает с вашим компьютером.", + "close_to_tray_label": "При закрытии продолжать работу в трее", + "close_to_tray_description": "Новая почта и напоминания приходят и после закрытия окна.", + "start_at_login_label": "Запускать при входе в систему", + "start_at_login_description": "Приложение запускается само, когда вы входите.", + "start_minimized_label": "Запускать в трее", + "start_minimized_description": "При входе окно не открывается. Откройте его значком в трее.", + "attention_label": "Мигать на панели задач при новой почте", + "attention_mac_label": "Подпрыгивать значком в Dock при новой почте", + "attention_description": "Только когда приложение не на переднем плане.", + "quiet_hours_label": "Тихие часы", + "quiet_hours_description": "В это время нет уведомлений и звуков. Почта продолжает приходить.", + "quiet_hours_from": "С", + "quiet_hours_to": "до", + "show_shortcut_label": "Показывать приложение сочетанием клавиш", + "show_shortcut_description": "{keys} выводит приложение на передний план из любого другого.", + "show_shortcut_taken": "{keys} использует другое приложение. Закройте его или выключите и снова включите этот параметр.", + "default_app_label": "Почтовое приложение по умолчанию", + "default_app_description": "Открывать почтовые ссылки и файлы .eml, .ics и .vcf в этом приложении.", + "default_app_is_default": "Это приложение открывает ваши почтовые ссылки.", + "default_app_button": "Выбрать приложение по умолчанию…", + "new_mail": "Новая почта", + "no_subject": "(без темы)", + "action_delete": "Удалить", + "copy_as_file": "Копировать как файл", + "copied": "Файл скопирован. Его можно вставить в папку.", + "copy_failed": "Не удалось скопировать файл.", + "reply_lost": "Не удалось сохранить введённый ответ. Откройте письмо и ответьте из него." + }, "desktop_sign_in": { "browser_button": "Войти через браузер", "browser_waiting": "Завершите вход в браузере", diff --git a/locales/sk/common.json b/locales/sk/common.json index 9e12b12d2..8d23419cf 100644 --- a/locales/sk/common.json +++ b/locales/sk/common.json @@ -6545,6 +6545,37 @@ "message_moved_to": "Táto správa bola presunutá do {folder}.", "window_not_ready": "Nové okno sa neotvorilo včas. Koncept zostáva tu." }, + "desktopOs": { + "section_title": "Počítačová aplikácia", + "section_description": "Ako aplikácia spolupracuje s vaším počítačom.", + "close_to_tray_label": "Po zatvorení bežať ďalej v oblasti oznámení", + "close_to_tray_description": "Nová pošta a pripomienky prichádzajú aj po zatvorení okna.", + "start_at_login_label": "Spustiť po prihlásení do počítača", + "start_at_login_description": "Aplikácia sa po prihlásení spustí sama.", + "start_minimized_label": "Spustiť v oblasti oznámení", + "start_minimized_description": "Po prihlásení sa neotvorí žiadne okno. Otvorte ju ikonou v oblasti oznámení.", + "attention_label": "Pri novej pošte blikať na paneli úloh", + "attention_mac_label": "Pri novej pošte poskakovať ikonou v Docku", + "attention_description": "Len keď aplikácia nie je v popredí.", + "quiet_hours_label": "Čas pokoja", + "quiet_hours_description": "V tomto čase žiadne oznámenia ani zvuky. Pošta prichádza ďalej.", + "quiet_hours_from": "Od", + "quiet_hours_to": "do", + "show_shortcut_label": "Zobraziť aplikáciu klávesovou skratkou", + "show_shortcut_description": "{keys} prenesie aplikáciu do popredia z akejkoľvek inej aplikácie.", + "show_shortcut_taken": "{keys} používa iná aplikácia. Zatvorte ju alebo túto možnosť vypnite a znova zapnite.", + "default_app_label": "Predvolená poštová aplikácia", + "default_app_description": "Otvárať poštové odkazy a súbory .eml, .ics a .vcf v tejto aplikácii.", + "default_app_is_default": "Táto aplikácia otvára vaše poštové odkazy.", + "default_app_button": "Vybrať predvolenú aplikáciu…", + "new_mail": "Nová pošta", + "no_subject": "(bez predmetu)", + "action_delete": "Odstrániť", + "copy_as_file": "Kopírovať ako súbor", + "copied": "Súbor skopírovaný. Môžete ho vložiť do priečinka.", + "copy_failed": "Súbor sa nepodarilo skopírovať.", + "reply_lost": "Napísanú odpoveď sa nepodarilo zachovať. Otvorte správu a odpovedzte v nej." + }, "desktop_sign_in": { "browser_button": "Prihlásiť sa v prehliadači", "browser_waiting": "Dokončite prihlásenie v prehliadači", diff --git a/locales/th/common.json b/locales/th/common.json index 0be3db9db..0f81a68cb 100644 --- a/locales/th/common.json +++ b/locales/th/common.json @@ -6553,6 +6553,37 @@ "message_moved_to": "ข้อความนี้ถูกย้ายไปยัง {folder}", "window_not_ready": "หน้าต่างใหม่เปิดไม่ทัน แบบร่างของคุณยังอยู่ที่นี่" }, + "desktopOs": { + "section_title": "แอปเดสก์ท็อป", + "section_description": "วิธีที่แอปทำงานกับคอมพิวเตอร์ของคุณ", + "close_to_tray_label": "ทำงานต่อในถาดระบบเมื่อปิด", + "close_to_tray_description": "เมลใหม่และการแจ้งเตือนยังเข้ามาหลังปิดหน้าต่าง", + "start_at_login_label": "เริ่มเมื่อลงชื่อเข้าใช้คอมพิวเตอร์", + "start_at_login_description": "แอปเริ่มเองเมื่อคุณลงชื่อเข้าใช้", + "start_minimized_label": "เริ่มในถาดระบบ", + "start_minimized_description": "ไม่มีหน้าต่างเปิดเมื่อลงชื่อเข้าใช้ เปิดด้วยไอคอนในถาดระบบ", + "attention_label": "กะพริบแถบงานเมื่อมีเมลใหม่", + "attention_mac_label": "ให้ไอคอน Dock เด้งเมื่อมีเมลใหม่", + "attention_description": "เฉพาะเมื่อแอปไม่ได้อยู่ด้านหน้า", + "quiet_hours_label": "ช่วงเวลาเงียบ", + "quiet_hours_description": "ไม่มีการแจ้งเตือนและเสียงในช่วงนี้ เมลยังเข้ามาตามปกติ", + "quiet_hours_from": "จาก", + "quiet_hours_to": "ถึง", + "show_shortcut_label": "แสดงแอปด้วยแป้นพิมพ์ลัด", + "show_shortcut_description": "{keys} นำแอปขึ้นมาด้านหน้าจากแอปอื่นใดก็ได้", + "show_shortcut_taken": "แอปอื่นใช้ {keys} อยู่ ปิดแอปนั้น หรือปิดแล้วเปิดตัวเลือกนี้อีกครั้ง", + "default_app_label": "แอปเมลเริ่มต้น", + "default_app_description": "เปิดลิงก์เมลและไฟล์ .eml, .ics และ .vcf ด้วยแอปนี้", + "default_app_is_default": "แอปนี้เปิดลิงก์เมลของคุณ", + "default_app_button": "เลือกแอปเริ่มต้น…", + "new_mail": "เมลใหม่", + "no_subject": "(ไม่มีหัวเรื่อง)", + "action_delete": "ลบ", + "copy_as_file": "คัดลอกเป็นไฟล์", + "copied": "คัดลอกไฟล์แล้ว คุณวางลงในโฟลเดอร์ได้", + "copy_failed": "คัดลอกไฟล์ไม่ได้", + "reply_lost": "ไม่สามารถเก็บคำตอบที่พิมพ์ไว้ได้ เปิดข้อความแล้วตอบจากที่นั่น" + }, "desktop_sign_in": { "browser_button": "ลงชื่อเข้าใช้ด้วยเบราว์เซอร์", "browser_waiting": "ลงชื่อเข้าใช้ให้เสร็จในเบราว์เซอร์", diff --git a/locales/tr/common.json b/locales/tr/common.json index fa74d9864..a9e5517e7 100644 --- a/locales/tr/common.json +++ b/locales/tr/common.json @@ -6545,6 +6545,37 @@ "message_moved_to": "Bu ileti {folder} klasörüne taşındı.", "window_not_ready": "Yeni pencere zamanında açılmadı. Taslağınız burada kalıyor." }, + "desktopOs": { + "section_title": "Masaüstü uygulaması", + "section_description": "Uygulamanın bilgisayarınızla nasıl çalıştığı.", + "close_to_tray_label": "Kapatınca sistem tepsisinde çalışmaya devam et", + "close_to_tray_description": "Pencereyi kapattıktan sonra da yeni postalar ve anımsatıcılar gelmeye devam eder.", + "start_at_login_label": "Bilgisayarda oturum açınca başlat", + "start_at_login_description": "Oturum açtığınızda uygulama kendiliğinden başlar.", + "start_minimized_label": "Sistem tepsisinde başlat", + "start_minimized_description": "Oturum açarken pencere açılmaz. Sistem tepsisindeki simgeyle açın.", + "attention_label": "Yeni postada görev çubuğunu yanıp söndür", + "attention_mac_label": "Yeni postada Dock simgesini zıplat", + "attention_description": "Yalnızca uygulama önde değilken.", + "quiet_hours_label": "Sessiz saatler", + "quiet_hours_description": "Bu saatlerde bildirim ve ses yok. Postalar gelmeye devam eder.", + "quiet_hours_from": "Başlangıç", + "quiet_hours_to": "bitiş", + "show_shortcut_label": "Uygulamayı klavye kısayoluyla göster", + "show_shortcut_description": "{keys} uygulamayı başka herhangi bir uygulamadan öne getirir.", + "show_shortcut_taken": "{keys} başka bir uygulama tarafından kullanılıyor. O uygulamayı kapatın ya da bunu kapatıp yeniden açın.", + "default_app_label": "Varsayılan posta uygulaması", + "default_app_description": "Posta bağlantılarını ve .eml, .ics, .vcf dosyalarını bu uygulamayla aç.", + "default_app_is_default": "Posta bağlantılarınızı bu uygulama açıyor.", + "default_app_button": "Varsayılan uygulamayı seç…", + "new_mail": "Yeni posta", + "no_subject": "(konu yok)", + "action_delete": "Sil", + "copy_as_file": "Dosya olarak kopyala", + "copied": "Dosya kopyalandı. Bir klasöre yapıştırabilirsiniz.", + "copy_failed": "Dosya kopyalanamadı.", + "reply_lost": "Yazdığınız yanıt korunamadı. İletiyi açıp oradan yanıtlayın." + }, "desktop_sign_in": { "browser_button": "Tarayıcıyla oturum aç", "browser_waiting": "Oturum açmayı tarayıcınızda tamamlayın", diff --git a/locales/uk/common.json b/locales/uk/common.json index 391a5c203..f393aaf5a 100644 --- a/locales/uk/common.json +++ b/locales/uk/common.json @@ -6545,6 +6545,37 @@ "message_moved_to": "Цього листа переміщено в {folder}.", "window_not_ready": "Нове вікно не відкрилося вчасно. Чернетка залишається тут." }, + "desktopOs": { + "section_title": "Настільний застосунок", + "section_description": "Як застосунок працює з вашим комп’ютером.", + "close_to_tray_label": "Під час закриття працювати далі в треї", + "close_to_tray_description": "Нова пошта й нагадування надходять і після закриття вікна.", + "start_at_login_label": "Запускати під час входу в систему", + "start_at_login_description": "Застосунок запускається сам, коли ви входите.", + "start_minimized_label": "Запускати в треї", + "start_minimized_description": "Під час входу вікно не відкривається. Відкрийте його значком у треї.", + "attention_label": "Блимати на панелі завдань за нової пошти", + "attention_mac_label": "Підстрибувати значком у Dock за нової пошти", + "attention_description": "Лише коли застосунок не на передньому плані.", + "quiet_hours_label": "Тихі години", + "quiet_hours_description": "У цей час немає сповіщень і звуків. Пошта надходить далі.", + "quiet_hours_from": "З", + "quiet_hours_to": "до", + "show_shortcut_label": "Показувати застосунок сполученням клавіш", + "show_shortcut_description": "{keys} виводить застосунок на передній план із будь-якого іншого.", + "show_shortcut_taken": "{keys} використовує інший застосунок. Закрийте його або вимкніть і знову увімкніть цей параметр.", + "default_app_label": "Поштовий застосунок за замовчуванням", + "default_app_description": "Відкривати поштові посилання та файли .eml, .ics і .vcf у цьому застосунку.", + "default_app_is_default": "Цей застосунок відкриває ваші поштові посилання.", + "default_app_button": "Вибрати застосунок за замовчуванням…", + "new_mail": "Нова пошта", + "no_subject": "(без теми)", + "action_delete": "Видалити", + "copy_as_file": "Копіювати як файл", + "copied": "Файл скопійовано. Його можна вставити в папку.", + "copy_failed": "Не вдалося скопіювати файл.", + "reply_lost": "Не вдалося зберегти введену відповідь. Відкрийте лист і дайте відповідь у ньому." + }, "desktop_sign_in": { "browser_button": "Увійти через браузер", "browser_waiting": "Завершіть вхід у браузері", diff --git a/locales/vi/common.json b/locales/vi/common.json index 347fe2cd0..4d83b90c8 100644 --- a/locales/vi/common.json +++ b/locales/vi/common.json @@ -6557,6 +6557,37 @@ "message_moved_to": "Thư này đã được chuyển đến {folder}.", "window_not_ready": "Cửa sổ mới không mở kịp. Bản nháp của bạn vẫn ở đây." }, + "desktopOs": { + "section_title": "Ứng dụng máy tính", + "section_description": "Cách ứng dụng hoạt động với máy tính của bạn.", + "close_to_tray_label": "Tiếp tục chạy ở khay hệ thống khi đóng", + "close_to_tray_description": "Thư mới và lời nhắc vẫn đến sau khi bạn đóng cửa sổ.", + "start_at_login_label": "Khởi động khi đăng nhập vào máy tính", + "start_at_login_description": "Ứng dụng tự khởi động khi bạn đăng nhập.", + "start_minimized_label": "Khởi động ở khay hệ thống", + "start_minimized_description": "Không có cửa sổ nào mở khi đăng nhập. Mở bằng biểu tượng ở khay hệ thống.", + "attention_label": "Nhấp nháy thanh tác vụ khi có thư mới", + "attention_mac_label": "Làm biểu tượng Dock nảy khi có thư mới", + "attention_description": "Chỉ khi ứng dụng không ở phía trước.", + "quiet_hours_label": "Giờ yên tĩnh", + "quiet_hours_description": "Không có thông báo hay âm thanh vào giờ này. Thư vẫn đến.", + "quiet_hours_from": "Từ", + "quiet_hours_to": "đến", + "show_shortcut_label": "Hiện ứng dụng bằng phím tắt", + "show_shortcut_description": "{keys} đưa ứng dụng lên trước từ bất kỳ ứng dụng nào khác.", + "show_shortcut_taken": "Ứng dụng khác đang dùng {keys}. Hãy đóng ứng dụng đó, hoặc tắt rồi bật lại tùy chọn này.", + "default_app_label": "Ứng dụng thư mặc định", + "default_app_description": "Mở liên kết thư và tệp .eml, .ics, .vcf bằng ứng dụng này.", + "default_app_is_default": "Ứng dụng này mở các liên kết thư của bạn.", + "default_app_button": "Chọn ứng dụng mặc định…", + "new_mail": "Thư mới", + "no_subject": "(không có chủ đề)", + "action_delete": "Xóa", + "copy_as_file": "Sao chép dưới dạng tệp", + "copied": "Đã sao chép tệp. Bạn có thể dán vào một thư mục.", + "copy_failed": "Không thể sao chép tệp.", + "reply_lost": "Không thể giữ câu trả lời bạn đã nhập. Hãy mở thư và trả lời ở đó." + }, "desktop_sign_in": { "browser_button": "Đăng nhập bằng trình duyệt", "browser_waiting": "Hoàn tất đăng nhập trong trình duyệt", diff --git a/locales/zh-TW/common.json b/locales/zh-TW/common.json index 97dacbb06..ecd9e0cce 100644 --- a/locales/zh-TW/common.json +++ b/locales/zh-TW/common.json @@ -6545,6 +6545,37 @@ "message_moved_to": "這封郵件已移到 {folder}。", "window_not_ready": "新視窗未能及時開啟。你的草稿仍保留在這裡。" }, + "desktopOs": { + "section_title": "桌面應用程式", + "section_description": "應用程式如何與您的電腦配合。", + "close_to_tray_label": "關閉時繼續在系統匣中執行", + "close_to_tray_description": "關閉視窗後,新郵件和提醒仍會送達。", + "start_at_login_label": "登入電腦時啟動", + "start_at_login_description": "您登入時應用程式會自動啟動。", + "start_minimized_label": "在系統匣中啟動", + "start_minimized_description": "登入時不開啟視窗。請透過系統匣圖示開啟。", + "attention_label": "有新郵件時閃爍工作列", + "attention_mac_label": "有新郵件時讓 Dock 圖示跳動", + "attention_description": "僅在應用程式不在前景時。", + "quiet_hours_label": "勿擾時段", + "quiet_hours_description": "此時段沒有通知和聲音。郵件仍會送達。", + "quiet_hours_from": "從", + "quiet_hours_to": "到", + "show_shortcut_label": "使用鍵盤快速鍵顯示應用程式", + "show_shortcut_description": "{keys} 可從任何其他應用程式將此應用程式帶到前景。", + "show_shortcut_taken": "其他應用程式正在使用 {keys}。請關閉該應用程式,或關閉此選項後再開啟。", + "default_app_label": "預設郵件應用程式", + "default_app_description": "使用此應用程式開啟郵件連結以及 .eml、.ics 和 .vcf 檔案。", + "default_app_is_default": "此應用程式會開啟您的郵件連結。", + "default_app_button": "選擇預設應用程式…", + "new_mail": "新郵件", + "no_subject": "(無主旨)", + "action_delete": "刪除", + "copy_as_file": "複製為檔案", + "copied": "檔案已複製。您可以將其貼到資料夾中。", + "copy_failed": "無法複製檔案。", + "reply_lost": "無法保留您輸入的回覆。請開啟郵件並在其中回覆。" + }, "desktop_sign_in": { "browser_button": "使用瀏覽器登入", "browser_waiting": "請在瀏覽器中完成登入", diff --git a/locales/zh/common.json b/locales/zh/common.json index de9e40f3c..5650920a2 100644 --- a/locales/zh/common.json +++ b/locales/zh/common.json @@ -6545,6 +6545,37 @@ "message_moved_to": "这封邮件已移动到 {folder}。", "window_not_ready": "新窗口未能及时打开。你的草稿仍保留在这里。" }, + "desktopOs": { + "section_title": "桌面应用", + "section_description": "应用如何与你的电脑配合。", + "close_to_tray_label": "关闭时继续在托盘中运行", + "close_to_tray_description": "关闭窗口后,新邮件和提醒仍会到达。", + "start_at_login_label": "登录电脑时启动", + "start_at_login_description": "你登录时应用会自动启动。", + "start_minimized_label": "在托盘中启动", + "start_minimized_description": "登录时不打开窗口。请通过托盘图标打开。", + "attention_label": "有新邮件时闪烁任务栏", + "attention_mac_label": "有新邮件时让 Dock 图标跳动", + "attention_description": "仅在应用不在前台时。", + "quiet_hours_label": "免打扰时段", + "quiet_hours_description": "此时段没有通知和声音。邮件仍会到达。", + "quiet_hours_from": "从", + "quiet_hours_to": "到", + "show_shortcut_label": "使用键盘快捷键显示应用", + "show_shortcut_description": "{keys} 可从任何其他应用把此应用带到前台。", + "show_shortcut_taken": "其他应用正在使用 {keys}。请关闭该应用,或关闭此选项后重新打开。", + "default_app_label": "默认邮件应用", + "default_app_description": "用此应用打开邮件链接以及 .eml、.ics 和 .vcf 文件。", + "default_app_is_default": "此应用会打开你的邮件链接。", + "default_app_button": "选择默认应用…", + "new_mail": "新邮件", + "no_subject": "(无主题)", + "action_delete": "删除", + "copy_as_file": "复制为文件", + "copied": "文件已复制。你可以将其粘贴到文件夹中。", + "copy_failed": "无法复制文件。", + "reply_lost": "无法保留你输入的回复。请打开邮件并在其中回复。" + }, "desktop_sign_in": { "browser_button": "使用浏览器登录", "browser_waiting": "请在浏览器中完成登录",