From 43c9ef9ad1e48a5c2bfd0844697948aad6ef329b Mon Sep 17 00:00:00 2001 From: Linus Rath <139418639+rathlinus@users.noreply.github.com> Date: Wed, 4 Nov 2026 07:11:03 +0100 Subject: [PATCH] fix: name the TLS-only delivery option plainly and point to encryption add-ons --- .../composer-encryption-options.test.tsx | 280 ++++++++++++++++++ components/email/composer-toolbar-menu.tsx | 15 +- components/email/email-composer.tsx | 26 +- locales/ar/common.json | 6 +- locales/ca/common.json | 6 +- locales/cs/common.json | 6 +- locales/da/common.json | 6 +- locales/de/common.json | 6 +- locales/en/common.json | 6 +- locales/es/common.json | 6 +- locales/et/common.json | 6 +- locales/fa/common.json | 6 +- locales/fr/common.json | 6 +- locales/he/common.json | 6 +- locales/hu/common.json | 6 +- locales/it/common.json | 6 +- locales/ja/common.json | 6 +- locales/ko/common.json | 6 +- locales/lv/common.json | 6 +- locales/mn/common.json | 6 +- locales/nb/common.json | 6 +- locales/nl/common.json | 6 +- locales/pl/common.json | 6 +- locales/pt/common.json | 6 +- locales/ro/common.json | 6 +- locales/ru/common.json | 6 +- locales/sk/common.json | 6 +- locales/th/common.json | 6 +- locales/tr/common.json | 6 +- locales/uk/common.json | 6 +- locales/vi/common.json | 6 +- locales/zh-TW/common.json | 6 +- locales/zh/common.json | 6 +- 33 files changed, 464 insertions(+), 37 deletions(-) create mode 100644 components/email/__tests__/composer-encryption-options.test.tsx diff --git a/components/email/__tests__/composer-encryption-options.test.tsx b/components/email/__tests__/composer-encryption-options.test.tsx new file mode 100644 index 000000000..95f74169d --- /dev/null +++ b/components/email/__tests__/composer-encryption-options.test.tsx @@ -0,0 +1,280 @@ +import { render, screen, fireEvent } from '@testing-library/react'; +import { describe, it, expect, vi, afterEach, beforeEach } from 'vitest'; +import React from 'react'; +import { EmailComposer } from '../email-composer'; +import { useAuthStore } from '@/stores/auth-store'; +import { useSettingsStore } from '@/stores/settings-store'; +import { usePolicyStore } from '@/stores/policy-store'; +import { emailHooks } from '@/lib/plugin-hooks'; + +// "Require encrypted delivery" read like encryption but only asks the servers +// for TLS on the way. It is now named for what it does and is an advanced +// option, and without a crypto plugin an "Encrypt…" item says where to get one. + +// ─── Heavy component mocks (mirrors composer-close-guard.test.tsx) ────────── + +vi.mock('@/components/email/rich-text-editor', () => ({ + RichTextEditor: () => React.createElement('div', { 'data-testid': 'rich-text-editor' }), +})); + +vi.mock('@/components/plugins/plugin-slot', () => ({ PluginSlot: () => null })); +vi.mock('@/components/identity/sub-address-helper', () => ({ SubAddressHelper: () => null })); +vi.mock('@/components/templates/template-picker', () => ({ TemplatePicker: () => null })); +vi.mock('@/components/templates/template-form', () => ({ TemplateForm: () => null })); +vi.mock('@/components/files/file-preview-modal', () => ({ FilePreviewModal: () => null })); +vi.mock('@/hooks/use-focus-trap', () => ({ + useFocusTrap: () => ({ current: null }), +})); +vi.mock('@/hooks/use-pro-multi-account-identities', () => ({ + useProMultiAccountIdentities: () => ({ enabled: false, groups: [], allIdentities: [] }), + stripCrossAccountIdentityPrefix: (id: string) => ({ localAccountId: null, rawId: id }), +})); + +const push = vi.fn(); +vi.mock('@/i18n/navigation', () => ({ + useRouter: () => ({ push, back: vi.fn(), replace: vi.fn() }), + usePathname: () => '/en', +})); + +// ─── Store mocks ────────────────────────────────────────────────────────────── + +vi.mock('@/stores/auth-store', () => { + const state = { + client: null, + identities: [], + primaryIdentity: null, + isAuthenticated: false, + isDemoMode: false, + activeAccountId: null, + connectionLost: false, + getClientForAccount: () => undefined, + getAllConnectedClients: () => new Map(), + syncIdentities: () => {}, + refreshIdentities: async () => {}, + }; + const hook = (sel?: (s: typeof state) => unknown) => + typeof sel === 'function' ? sel(state) : state; + hook.getState = () => state; + hook.setState = (p: Partial) => Object.assign(state, p); + return { useAuthStore: hook }; +}); + +vi.mock('@/stores/identity-store', () => { + const state = { + identities: [{ id: 'id-me', email: 'me@example.com', name: 'Me' }], + defaultIdentityId: 'id-me', + }; + const hook = (sel?: (s: typeof state) => unknown) => + typeof sel === 'function' ? sel(state) : state; + hook.getState = () => state; + hook.setState = (p: Partial) => Object.assign(state, p); + return { useIdentityStore: hook }; +}); + +vi.mock('@/stores/account-store', () => { + const state = { accounts: [], getAccountById: () => undefined }; + const hook = (sel?: (s: typeof state) => unknown) => + typeof sel === 'function' ? sel(state) : state; + hook.getState = () => state; + hook.setState = (p: Partial) => Object.assign(state, p); + return { useAccountStore: hook }; +}); + +vi.mock('@/stores/email-store', () => { + const state = { + draftSaveEnabled: false, + sendRawEmail: async () => ({ sent: true }), + }; + const hook = (sel?: (s: typeof state) => unknown) => + typeof sel === 'function' ? sel(state) : state; + hook.getState = () => state; + hook.setState = (p: Partial) => Object.assign(state, p); + return { useEmailStore: hook }; +}); + +const updateSetting = vi.fn(); + +vi.mock('@/stores/settings-store', () => { + const state = { + timeFormat: '24h', + plainTextMode: false, + subAddressDelimiter: '+', + autoSelectReplyIdentity: true, + attachmentReminderEnabled: false, + attachmentReminderKeywords: [], + emptySubjectWarningEnabled: true, + sendDelaySeconds: 0, + showAdvancedSettings: false, + signaturePosition: 'above_quote', + signatureSeparatorEnabled: false, + requestReadReceiptDefault: false, + addTrustedSender: () => {}, + trustedSendersAddressBook: null, + updateSetting: (...args: unknown[]) => updateSetting(...args), + }; + const hook = (sel?: (s: typeof state) => unknown) => + typeof sel === 'function' ? sel(state) : state; + hook.getState = () => state; + hook.setState = (p: Partial) => Object.assign(state, p); + return { useSettingsStore: hook }; +}); + +vi.mock('@/stores/contact-store', () => { + const state = { + contacts: [], + getAutocomplete: async () => [], + addToTrustedSendersBook: async () => {}, + }; + const hook = (sel?: (s: typeof state) => unknown) => + typeof sel === 'function' ? sel(state) : state; + hook.getState = () => state; + hook.setState = (p: Partial) => Object.assign(state, p); + return { useContactStore: hook }; +}); + +vi.mock('@/stores/template-store', () => { + const state = { templates: [], addTemplate: async () => {} }; + const hook = (sel?: (s: typeof state) => unknown) => + typeof sel === 'function' ? sel(state) : state; + hook.getState = () => state; + hook.setState = (p: Partial) => Object.assign(state, p); + return { useTemplateStore: hook }; +}); + +// ─── Misc dependency mocks ──────────────────────────────────────────────────── + +vi.mock('@/stores/toast-store', () => ({ + toast: { info: () => {}, error: () => {}, success: () => {} }, +})); + +vi.mock('@/lib/plugin-hooks', () => ({ + identityHooks: { + onSignatureRender: { size: 0, transform: async (value: unknown) => value }, + onIdentitySelect: { size: 0, emit: async () => {} }, + }, + templateHooks: { + onTemplateRender: { size: 0, transform: async (value: unknown) => value }, + onTemplateApply: { size: 0, emit: async () => {} }, + }, + emailHooks: { + onComposerOpen: { call: async () => [] }, + onRecipientChange: { call: async () => [] }, + getRecipientSuggestions: { call: async () => [] }, + onRecipientChipsChange: { transform: async (chips: unknown) => chips }, + onDraftChange: { emit: () => {} }, + onBeforeDraftAutoSave: { transform: async (draft: unknown) => draft }, + onBeforeEmailSend: { intercept: async () => true, interceptOutcome: async () => 'allowed' }, + onComposeSend: { size: 0, takeOver: async () => 'default' }, + onTransformOutgoingEmail: { transform: async (email: unknown) => email }, + }, + contactHooks: { + search: { call: async () => [] }, + onProvideRecipientSuggestions: { transform: async (initial: unknown) => initial }, + }, +})); + +vi.mock('@/lib/email-sanitization', () => ({ + sanitizeSignatureHtml: (v: string) => v, + sanitizeEmailHtml: (v: string) => v, + sanitizeSignatureHtmlForDisplay: (v: string) => v, + sanitizePluginBodyHtml: (v: string) => v, + escapeHtml: (v: string) => v, + parseHtmlSafely: (html: string) => new DOMParser().parseFromString(html, 'text/html'), +})); + +vi.mock('@/lib/email-threading', () => ({ + computeReplyThreadingHeaders: () => ({ inReplyTo: [], references: [] }), +})); +vi.mock('@/lib/signature-utils', () => ({ + appendPlainTextSignature: (body: string) => body, + getPlainTextSignature: () => '', +})); +vi.mock('@/lib/sub-addressing', () => ({ generateSubAddress: () => '' })); +vi.mock('@/lib/debug', () => ({ debug: { log: () => {}, warn: () => {}, error: () => {} } })); +vi.mock('@/components/email/quoted-html', () => ({ + buildQuotedHtmlBlock: () => '', + serializeEditorContent: () => '', +})); +vi.mock('@/lib/template-utils', () => ({ substitutePlaceholders: (s: string) => s })); + +// ─── Tests ──────────────────────────────────────────────────────────────────── + +const DRAFT = { + to: 'bob@example.com', + cc: '', + bcc: '', + subject: 'Plans', + body: '

Hello

', + showCc: false, + showBcc: false, + selectedIdentityId: 'id-me', + subAddressTag: '', + mode: 'compose' as const, + draftId: null, +}; + +const openOptions = () => fireEvent.click(screen.getByTestId('composer-options-menu')); +const setComposeSendHandlers = (size: number) => { + (emailHooks.onComposeSend as unknown as { size: number }).size = size; +}; + +describe('composer encryption options', () => { + beforeEach(() => { + useAuthStore.setState({ + client: { supportsSubmissionExtension: (name: string) => name === 'REQUIRETLS', hasDelayedSend: () => false, getMaxDelayedSend: () => 0 } as never, + }); + usePolicyStore.setState({ demoFeatureOverrides: { pluginsEnabled: true } } as never); + setComposeSendHandlers(0); + }); + afterEach(() => { + useAuthStore.setState({ client: null }); + useSettingsStore.setState({ showAdvancedSettings: false } as never); + usePolicyStore.setState({ demoFeatureOverrides: {} } as never); + vi.clearAllMocks(); + }); + + it('names the TLS option for what it does and keeps it to advanced settings', () => { + const { unmount } = render(); + openOptions(); + expect(screen.queryByTestId('composer-require-tls-toggle')).toBeNull(); + unmount(); + + useSettingsStore.setState({ showAdvancedSettings: true } as never); + render(); + openOptions(); + const item = screen.getByTestId('composer-require-tls-toggle'); + expect(item).toHaveTextContent('require_tls'); + expect(item).toHaveTextContent('require_tls_hint'); + expect(item).not.toHaveTextContent('option_require_tls'); + }); + + it('keeps a ticked TLS option visible so it can be unticked', () => { + render(); + openOptions(); + expect(screen.getByTestId('composer-require-tls-toggle')).toHaveAttribute('aria-checked', 'true'); + }); + + it('offers "Encrypt…" without a crypto plugin, opening the add-ons', () => { + render(); + openOptions(); + const item = screen.getByTestId('composer-encrypt-setup'); + expect(item).toHaveTextContent('encrypt'); + expect(item).toHaveTextContent('encrypt_hint'); + fireEvent.click(item); + expect(push).toHaveBeenCalledWith('/settings/addons'); + }); + + it('leaves "Encrypt…" out once a crypto plugin takes over sending', () => { + setComposeSendHandlers(1); + render(); + openOptions(); + expect(screen.queryByTestId('composer-encrypt-setup')).toBeNull(); + }); + + it('leaves "Encrypt…" out where add-ons are switched off', () => { + usePolicyStore.setState({ demoFeatureOverrides: { pluginsEnabled: false } } as never); + render(); + openOptions(); + expect(screen.queryByTestId('composer-encrypt-setup')).toBeNull(); + }); +}); diff --git a/components/email/composer-toolbar-menu.tsx b/components/email/composer-toolbar-menu.tsx index 639d758cb..07abf0e97 100644 --- a/components/email/composer-toolbar-menu.tsx +++ b/components/email/composer-toolbar-menu.tsx @@ -67,21 +67,28 @@ export function ComposerToolbarMenu({ icon: Icon, label, activeCount = 0, testId interface ItemProps { icon?: AppIcon; onSelect: () => void; + /** A line under the label, as on the check items. */ + description?: string; + /** Greyed out: the item can't do its job here yet, and says where to set it up. */ + muted?: boolean; testId?: string; children: React.ReactNode; } -export function ComposerToolbarMenuItem({ icon: Icon, onSelect, testId, children }: ItemProps) { +export function ComposerToolbarMenuItem({ icon: Icon, onSelect, description, muted, testId, children }: ItemProps) { return ( ); } diff --git a/components/email/email-composer.tsx b/components/email/email-composer.tsx index 9dcb154ca..3b0003861 100644 --- a/components/email/email-composer.tsx +++ b/components/email/email-composer.tsx @@ -41,7 +41,9 @@ import { Reply, Forward, BookUser, + Lock, } from "@/components/icons"; +import { useRouter } from "@/i18n/navigation"; import { ComposerPluginMenuItems, ComposerPluginToolbarButtons, ComposerToolbarMenu, ComposerToolbarMenuItem, ComposerToolbarMenuCheckItem } from "@/components/email/composer-toolbar-menu"; import { ComposerToolbarMenuRadioItem } from "@/components/email/composer-toolbar-menu"; import type { MessagePriority } from "@/lib/message-priority"; @@ -603,6 +605,9 @@ export function EmailComposer({ const tOffline = useTranslations('offline'); const tOfflineNotes = useTranslations('offlineConflicts'); const tSendGuards = useTranslations('send_guards'); + const tWords = useTranslations('compose_words'); + const router = useRouter(); + const showAdvancedSettings = useSettingsStore((state) => state.showAdvancedSettings); const timeFormat = useSettingsStore((state) => state.timeFormat); const plainTextModeDefault = useSettingsStore((state) => state.plainTextMode); // Per-message format (#1022). Seeded from the "plain text only" setting - @@ -5632,16 +5637,31 @@ export function EmailComposer({ {t('option_dsn')} )} - {composerClient?.supportsSubmissionExtension?.('REQUIRETLS') && ( + {/* REQUIRETLS only secures the way there, so it is an advanced + option; one already ticked stays visible to untick. */} + {composerClient?.supportsSubmissionExtension?.('REQUIRETLS') && (showAdvancedSettings || requireTls) && ( setRequireTls(v => !v)} - description={t('option_require_tls_hint')} + description={tWords('require_tls_hint')} testId="composer-require-tls-toggle" > - {t('option_require_tls')} + {tWords('require_tls')} )} + {/* Encrypting is a crypto plugin's job (S/MIME, PGP). Without + one, say where to get it rather than offer nothing. */} + {emailHooks.onComposeSend.size === 0 && isFeatureEnabled('pluginsEnabled') && !ownWindow && ( + { close(); router.push('/settings/addons'); }} + testId="composer-encrypt-setup" + > + {tWords('encrypt')} + + )}
diff --git a/locales/ar/common.json b/locales/ar/common.json index 3554362e3..ed6682970 100644 --- a/locales/ar/common.json +++ b/locales/ar/common.json @@ -6392,7 +6392,11 @@ "reply_from_received": { "label": "الرد من العنوان المستلَم إليه", "description": "الرد من العنوان الذي أُرسلت إليه الرسالة. مفيد إذا كانت لديك عدة عناوين." - } + }, + "require_tls": "التسليم عبر اتصال آمن فقط", + "require_tls_hint": "الرسالة نفسها غير مشفّرة. تُرَد إذا لم يتمكن خادم من استخدام TLS.", + "encrypt": "تشفير…", + "encrypt_hint": "يتطلب إضافة S/MIME أو PGP. يفتح الإعدادات." }, "plugin_list": { "filters": "المرشحات", diff --git a/locales/ca/common.json b/locales/ca/common.json index 3c756c200..7836a4f0e 100644 --- a/locales/ca/common.json +++ b/locales/ca/common.json @@ -6392,7 +6392,11 @@ "reply_from_received": { "label": "Respon des de l'adreça de recepció", "description": "Respon des de l'adreça a la qual es va enviar el correu. Útil si teniu diverses adreces." - } + }, + "require_tls": "Lliura només per una connexió segura", + "require_tls_hint": "El missatge en si no està xifrat. Es retorna si un servidor no pot fer servir TLS.", + "encrypt": "Xifra…", + "encrypt_hint": "Cal un complement S/MIME o PGP. Obre la configuració." }, "plugin_list": { "filters": "Filtres", diff --git a/locales/cs/common.json b/locales/cs/common.json index 20bd5cb05..301fbdacc 100644 --- a/locales/cs/common.json +++ b/locales/cs/common.json @@ -6392,7 +6392,11 @@ "reply_from_received": { "label": "Automaticky vybírat adresu pro odpověď", "description": "Odpovídat z adresy, na kterou zpráva přišla. Hodí se, pokud máte více adres." - } + }, + "require_tls": "Doručit jen přes zabezpečené spojení", + "require_tls_hint": "Samotná zpráva šifrovaná není. Vrátí se, pokud server nemůže použít TLS.", + "encrypt": "Šifrovat…", + "encrypt_hint": "Vyžaduje doplněk S/MIME nebo PGP. Otevře Nastavení." }, "plugin_list": { "filters": "Filtry", diff --git a/locales/da/common.json b/locales/da/common.json index 1c2118e3a..fd0a954df 100644 --- a/locales/da/common.json +++ b/locales/da/common.json @@ -6392,7 +6392,11 @@ "reply_from_received": { "label": "Svar fra modtaget adresse", "description": "Svar fra den adresse, mailen blev sendt til. Nyttigt, hvis du har flere adresser." - } + }, + "require_tls": "Lever kun via en sikker forbindelse", + "require_tls_hint": "Selve beskeden er ikke krypteret. Den sendes retur, hvis en server ikke kan bruge TLS.", + "encrypt": "Krypter…", + "encrypt_hint": "Kræver en S/MIME- eller PGP-tilføjelse. Åbner Indstillinger." }, "plugin_list": { "filters": "Filtre", diff --git a/locales/de/common.json b/locales/de/common.json index 77323e258..03b98526d 100644 --- a/locales/de/common.json +++ b/locales/de/common.json @@ -6392,7 +6392,11 @@ "reply_from_received": { "label": "Antwortadresse automatisch wählen", "description": "Die Antwort geht von der Adresse aus, an die die E-Mail ging. Nützlich, wenn Sie mehrere Adressen haben." - } + }, + "require_tls": "Nur über eine sichere Verbindung zustellen", + "require_tls_hint": "Die Nachricht selbst wird nicht verschlüsselt. Sie kommt zurück, wenn ein Server kein TLS nutzen kann.", + "encrypt": "Verschlüsseln…", + "encrypt_hint": "Dafür brauchen Sie eine S/MIME- oder PGP-Erweiterung. Öffnet die Einstellungen." }, "plugin_list": { "filters": "Filter", diff --git a/locales/en/common.json b/locales/en/common.json index 465dca018..4138b9b32 100644 --- a/locales/en/common.json +++ b/locales/en/common.json @@ -6392,7 +6392,11 @@ "reply_from_received": { "label": "Reply from the address it was sent to", "description": "Reply from the address the mail was sent to. Useful if you have several addresses." - } + }, + "require_tls": "Only deliver over a secure connection", + "require_tls_hint": "The message itself isn't encrypted. It bounces if a server can't use TLS.", + "encrypt": "Encrypt…", + "encrypt_hint": "Needs an S/MIME or PGP add-on. Opens Settings." }, "plugin_list": { "filters": "Filters", diff --git a/locales/es/common.json b/locales/es/common.json index 650e646a1..fda797ee8 100644 --- a/locales/es/common.json +++ b/locales/es/common.json @@ -6392,7 +6392,11 @@ "reply_from_received": { "label": "Seleccionar dirección de respuesta automáticamente", "description": "Responder desde la dirección a la que se envió el correo. Útil si tiene varias direcciones." - } + }, + "require_tls": "Entregar solo por una conexión segura", + "require_tls_hint": "El mensaje en sí no se cifra. Se devuelve si un servidor no puede usar TLS.", + "encrypt": "Cifrar…", + "encrypt_hint": "Necesita un complemento S/MIME o PGP. Abre la Configuración." }, "plugin_list": { "filters": "Filtros", diff --git a/locales/et/common.json b/locales/et/common.json index 8224fd630..5793da0ee 100644 --- a/locales/et/common.json +++ b/locales/et/common.json @@ -6392,7 +6392,11 @@ "reply_from_received": { "label": "Vasta kirja saanud aadressilt", "description": "Vasta aadressilt, kuhu kiri saadeti. Kasulik, kui sul on mitu aadressi." - } + }, + "require_tls": "Toimeta kohale ainult turvalise ühenduse kaudu", + "require_tls_hint": "Kiri ise ei ole krüpteeritud. See tuleb tagasi, kui server ei saa TLS-i kasutada.", + "encrypt": "Krüpteeri…", + "encrypt_hint": "Vajab S/MIME või PGP lisa. Avab seaded." }, "plugin_list": { "filters": "Filtrid", diff --git a/locales/fa/common.json b/locales/fa/common.json index fff0e36aa..730598745 100644 --- a/locales/fa/common.json +++ b/locales/fa/common.json @@ -6392,7 +6392,11 @@ "reply_from_received": { "label": "پاسخ از آدرس دریافتی", "description": "پاسخ از نشانی‌ای که نامه به آن فرستاده شده بود. اگر چند نشانی دارید مفید است." - } + }, + "require_tls": "تحویل فقط از راه اتصال امن", + "require_tls_hint": "خود پیام رمزگذاری نمی‌شود. اگر سروری نتواند از TLS استفاده کند، برگشت می‌خورد.", + "encrypt": "رمزگذاری…", + "encrypt_hint": "به افزونهٔ S/MIME یا PGP نیاز دارد. تنظیمات را باز می‌کند." }, "plugin_list": { "filters": "فیلترها", diff --git a/locales/fr/common.json b/locales/fr/common.json index 58293cf34..f76c76c4f 100644 --- a/locales/fr/common.json +++ b/locales/fr/common.json @@ -6392,7 +6392,11 @@ "reply_from_received": { "label": "Sélection automatique de l'adresse de réponse", "description": "Répondre depuis l'adresse à laquelle le message a été envoyé. Utile si vous avez plusieurs adresses." - } + }, + "require_tls": "Livrer uniquement par une connexion sécurisée", + "require_tls_hint": "Le message lui-même n'est pas chiffré. Il est renvoyé si un serveur ne peut pas utiliser TLS.", + "encrypt": "Chiffrer…", + "encrypt_hint": "Nécessite une extension S/MIME ou PGP. Ouvre les Paramètres." }, "plugin_list": { "filters": "Filtres", diff --git a/locales/he/common.json b/locales/he/common.json index 8d94cfcb4..3662a919d 100644 --- a/locales/he/common.json +++ b/locales/he/common.json @@ -6393,7 +6393,11 @@ "reply_from_received": { "label": "בחירה אוטומטית בכתובת תשובה", "description": "השב מהכתובת שאליה נשלח הדואר. שימושי אם יש לך כמה כתובות." - } + }, + "require_tls": "מסירה רק דרך חיבור מאובטח", + "require_tls_hint": "ההודעה עצמה אינה מוצפנת. היא תוחזר אם שרת לא יכול להשתמש ב-TLS.", + "encrypt": "הצפנה…", + "encrypt_hint": "דורש תוסף S/MIME או PGP. פותח את ההגדרות." }, "plugin_list": { "filters": "מסננים", diff --git a/locales/hu/common.json b/locales/hu/common.json index 458d6c725..7dedc7a49 100644 --- a/locales/hu/common.json +++ b/locales/hu/common.json @@ -6392,7 +6392,11 @@ "reply_from_received": { "label": "Válasz a fogadott címről", "description": "Válasz arról a címről, amelyre a levél érkezett. Hasznos, ha több címe van." - } + }, + "require_tls": "Kézbesítés csak biztonságos kapcsolaton át", + "require_tls_hint": "Maga az üzenet nincs titkosítva. Visszapattan, ha egy szerver nem tud TLS-t használni.", + "encrypt": "Titkosítás…", + "encrypt_hint": "S/MIME vagy PGP bővítmény kell hozzá. Megnyitja a Beállításokat." }, "plugin_list": { "filters": "Szűrők", diff --git a/locales/it/common.json b/locales/it/common.json index bd0ad832a..ad285a749 100644 --- a/locales/it/common.json +++ b/locales/it/common.json @@ -6392,7 +6392,11 @@ "reply_from_received": { "label": "Seleziona automaticamente l'indirizzo di risposta", "description": "Rispondi dall'indirizzo a cui è stata inviata l'email. Utile se hai più indirizzi." - } + }, + "require_tls": "Consegna solo tramite una connessione sicura", + "require_tls_hint": "Il messaggio in sé non è cifrato. Viene respinto se un server non può usare TLS.", + "encrypt": "Cifra…", + "encrypt_hint": "Serve un componente aggiuntivo S/MIME o PGP. Apre le Impostazioni." }, "plugin_list": { "filters": "Filtri", diff --git a/locales/ja/common.json b/locales/ja/common.json index 7fcdb4d04..c5f44ee3e 100644 --- a/locales/ja/common.json +++ b/locales/ja/common.json @@ -6392,7 +6392,11 @@ "reply_from_received": { "label": "返信元アドレスを自動選択", "description": "メールの宛先になったアドレスから返信します。複数のアドレスがある場合に便利です。" - } + }, + "require_tls": "安全な接続でのみ配信", + "require_tls_hint": "メッセージ自体は暗号化されません。サーバーが TLS を使えない場合は返送されます。", + "encrypt": "暗号化…", + "encrypt_hint": "S/MIME または PGP のアドオンが必要です。設定を開きます。" }, "plugin_list": { "filters": "フィルター", diff --git a/locales/ko/common.json b/locales/ko/common.json index a43e71e92..d7a56c111 100644 --- a/locales/ko/common.json +++ b/locales/ko/common.json @@ -6392,7 +6392,11 @@ "reply_from_received": { "label": "답장 시 보내는 사람 자동 선택", "description": "메일을 받은 주소로 답장합니다. 주소가 여러 개일 때 유용합니다." - } + }, + "require_tls": "보안 연결로만 배달", + "require_tls_hint": "메시지 자체는 암호화되지 않습니다. 서버가 TLS를 사용할 수 없으면 반송됩니다.", + "encrypt": "암호화…", + "encrypt_hint": "S/MIME 또는 PGP 추가 기능이 필요합니다. 설정을 엽니다." }, "plugin_list": { "filters": "필터", diff --git a/locales/lv/common.json b/locales/lv/common.json index 44a79e5a9..bcfadf592 100644 --- a/locales/lv/common.json +++ b/locales/lv/common.json @@ -6392,7 +6392,11 @@ "reply_from_received": { "label": "Automātiski izvēlēties atbildes adresi", "description": "Atbildēt no adreses, uz kuru vēstule tika nosūtīta. Noder, ja jums ir vairākas adreses." - } + }, + "require_tls": "Piegādāt tikai pa drošu savienojumu", + "require_tls_hint": "Pats ziņojums nav šifrēts. Tas tiek atgriezts, ja serveris nevar izmantot TLS.", + "encrypt": "Šifrēt…", + "encrypt_hint": "Nepieciešams S/MIME vai PGP papildinājums. Atver iestatījumus." }, "plugin_list": { "filters": "Filtri", diff --git a/locales/mn/common.json b/locales/mn/common.json index 1173ac93b..8d03df472 100644 --- a/locales/mn/common.json +++ b/locales/mn/common.json @@ -6392,7 +6392,11 @@ "reply_from_received": { "label": "Хүлээн авсан хаягаас хариу бичих", "description": "Захидал илгээгдсэн хаягаас хариу бичнэ. Олон хаягтай бол хэрэгтэй." - } + }, + "require_tls": "Зөвхөн аюулгүй холболтоор хүргэх", + "require_tls_hint": "Мессеж өөрөө шифрлэгдэхгүй. Сервер TLS ашиглаж чадахгүй бол буцаагдана.", + "encrypt": "Шифрлэх…", + "encrypt_hint": "S/MIME эсвэл PGP нэмэлт хэрэгтэй. Тохиргоог нээнэ." }, "plugin_list": { "filters": "Шүүлтүүр", diff --git a/locales/nb/common.json b/locales/nb/common.json index 09160a027..589082828 100644 --- a/locales/nb/common.json +++ b/locales/nb/common.json @@ -6392,7 +6392,11 @@ "reply_from_received": { "label": "Svar fra mottatt adresse", "description": "Svar fra adressen e-posten ble sendt til. Nyttig hvis du har flere adresser." - } + }, + "require_tls": "Lever bare over en sikker tilkobling", + "require_tls_hint": "Selve meldingen er ikke kryptert. Den sendes i retur hvis en server ikke kan bruke TLS.", + "encrypt": "Krypter…", + "encrypt_hint": "Krever et S/MIME- eller PGP-tillegg. Åpner Innstillinger." }, "plugin_list": { "filters": "Filtre", diff --git a/locales/nl/common.json b/locales/nl/common.json index 83c019f2e..3fd8ddc41 100644 --- a/locales/nl/common.json +++ b/locales/nl/common.json @@ -6392,7 +6392,11 @@ "reply_from_received": { "label": "Antwoordadres automatisch selecteren", "description": "Antwoord vanaf het adres waar de mail naartoe is gestuurd. Handig als je meerdere adressen hebt." - } + }, + "require_tls": "Alleen via een beveiligde verbinding bezorgen", + "require_tls_hint": "Het bericht zelf is niet versleuteld. Het komt terug als een server geen TLS kan gebruiken.", + "encrypt": "Versleutelen…", + "encrypt_hint": "Vereist een S/MIME- of PGP-add-on. Opent Instellingen." }, "plugin_list": { "filters": "Filters", diff --git a/locales/pl/common.json b/locales/pl/common.json index 9d4185978..1db35978a 100644 --- a/locales/pl/common.json +++ b/locales/pl/common.json @@ -6392,7 +6392,11 @@ "reply_from_received": { "label": "Automatycznie wybieraj adres odpowiedzi", "description": "Odpowiadaj z adresu, na który wysłano wiadomość. Przydatne, jeśli masz kilka adresów." - } + }, + "require_tls": "Doręczaj tylko przez bezpieczne połączenie", + "require_tls_hint": "Sama wiadomość nie jest szyfrowana. Zostanie zwrócona, jeśli serwer nie może użyć TLS.", + "encrypt": "Zaszyfruj…", + "encrypt_hint": "Wymaga dodatku S/MIME lub PGP. Otwiera Ustawienia." }, "plugin_list": { "filters": "Filtry", diff --git a/locales/pt/common.json b/locales/pt/common.json index 05c89d668..24db7fd80 100644 --- a/locales/pt/common.json +++ b/locales/pt/common.json @@ -6392,7 +6392,11 @@ "reply_from_received": { "label": "Selecionar automaticamente o endereço de resposta", "description": "Responder do endereço para o qual o e-mail foi enviado. Útil se você tiver vários endereços." - } + }, + "require_tls": "Entregar só por uma conexão segura", + "require_tls_hint": "A mensagem em si não é cifrada. Ela volta se um servidor não puder usar TLS.", + "encrypt": "Cifrar…", + "encrypt_hint": "Precisa de um extra S/MIME ou PGP. Abre as Configurações." }, "plugin_list": { "filters": "Filtros", diff --git a/locales/ro/common.json b/locales/ro/common.json index b07b5a184..6b46ce602 100644 --- a/locales/ro/common.json +++ b/locales/ro/common.json @@ -6392,7 +6392,11 @@ "reply_from_received": { "label": "Răspunde de la adresa de la care a fost primit mesajul", "description": "Răspunde de la adresa la care a fost trimis e-mailul. Util dacă ai mai multe adrese." - } + }, + "require_tls": "Livrează doar printr-o conexiune sigură", + "require_tls_hint": "Mesajul în sine nu este criptat. Este returnat dacă un server nu poate folosi TLS.", + "encrypt": "Criptează…", + "encrypt_hint": "Necesită un supliment S/MIME sau PGP. Deschide Setările." }, "plugin_list": { "filters": "Filtre", diff --git a/locales/ru/common.json b/locales/ru/common.json index ad41eefb2..4ea47ede2 100644 --- a/locales/ru/common.json +++ b/locales/ru/common.json @@ -6392,7 +6392,11 @@ "reply_from_received": { "label": "Автоматически выбирать адрес для ответа", "description": "Отвечать с адреса, на который пришло письмо. Удобно, если у вас несколько адресов." - } + }, + "require_tls": "Доставлять только по защищённому соединению", + "require_tls_hint": "Само письмо не шифруется. Оно вернётся, если сервер не может использовать TLS.", + "encrypt": "Зашифровать…", + "encrypt_hint": "Нужно дополнение S/MIME или PGP. Открывает настройки." }, "plugin_list": { "filters": "Фильтры", diff --git a/locales/sk/common.json b/locales/sk/common.json index ec18d119b..c532d76e1 100644 --- a/locales/sk/common.json +++ b/locales/sk/common.json @@ -6392,7 +6392,11 @@ "reply_from_received": { "label": "Odpovedať z prijatej adresy", "description": "Odpovedať z adresy, na ktorú prišla správa. Hodí sa, ak máte viac adries." - } + }, + "require_tls": "Doručiť len cez zabezpečené spojenie", + "require_tls_hint": "Samotná správa nie je šifrovaná. Vráti sa, ak server nemôže použiť TLS.", + "encrypt": "Šifrovať…", + "encrypt_hint": "Vyžaduje doplnok S/MIME alebo PGP. Otvorí Nastavenia." }, "plugin_list": { "filters": "Filtre", diff --git a/locales/th/common.json b/locales/th/common.json index a46a03907..9a5932b1a 100644 --- a/locales/th/common.json +++ b/locales/th/common.json @@ -3313,7 +3313,11 @@ "reply_from_received": { "label": "ตอบกลับจากที่อยู่ที่ได้รับ", "description": "ตอบกลับจากที่อยู่ที่อีเมลถูกส่งมา มีประโยชน์หากคุณมีหลายที่อยู่" - } + }, + "require_tls": "ส่งผ่านการเชื่อมต่อที่ปลอดภัยเท่านั้น", + "require_tls_hint": "ตัวข้อความเองไม่ได้ถูกเข้ารหัส ข้อความจะถูกตีกลับหากเซิร์ฟเวอร์ใช้ TLS ไม่ได้", + "encrypt": "เข้ารหัส…", + "encrypt_hint": "ต้องใช้ส่วนเสริม S/MIME หรือ PGP จะเปิดการตั้งค่า" }, "plugin_list": { "filters": "ตัวกรอง", diff --git a/locales/tr/common.json b/locales/tr/common.json index 55f8ac366..38db522bf 100644 --- a/locales/tr/common.json +++ b/locales/tr/common.json @@ -6392,7 +6392,11 @@ "reply_from_received": { "label": "Yanıt Adresini Otomatik Seç", "description": "Postanın gönderildiği adresten yanıtlayın. Birden çok adresiniz varsa kullanışlıdır." - } + }, + "require_tls": "Yalnızca güvenli bağlantı üzerinden teslim et", + "require_tls_hint": "İletinin kendisi şifrelenmez. Bir sunucu TLS kullanamazsa geri döner.", + "encrypt": "Şifrele…", + "encrypt_hint": "S/MIME veya PGP eklentisi gerekir. Ayarlar'ı açar." }, "plugin_list": { "filters": "Filtreler", diff --git a/locales/uk/common.json b/locales/uk/common.json index 7d672778a..946065a5f 100644 --- a/locales/uk/common.json +++ b/locales/uk/common.json @@ -6392,7 +6392,11 @@ "reply_from_received": { "label": "Автоматичний вибір адреси для відповіді", "description": "Відповідати з адреси, на яку надійшов лист. Зручно, якщо у вас кілька адрес." - } + }, + "require_tls": "Доставляти лише захищеним з'єднанням", + "require_tls_hint": "Сам лист не шифрується. Він повернеться, якщо сервер не може використати TLS.", + "encrypt": "Зашифрувати…", + "encrypt_hint": "Потрібен додаток S/MIME або PGP. Відкриває налаштування." }, "plugin_list": { "filters": "Фільтри", diff --git a/locales/vi/common.json b/locales/vi/common.json index f48c8f8e7..0919080c0 100644 --- a/locales/vi/common.json +++ b/locales/vi/common.json @@ -6404,7 +6404,11 @@ "reply_from_received": { "label": "Trả lời bằng địa chỉ nhận thư", "description": "Trả lời từ địa chỉ mà thư đã được gửi đến. Hữu ích nếu bạn có nhiều địa chỉ." - } + }, + "require_tls": "Chỉ gửi qua kết nối an toàn", + "require_tls_hint": "Bản thân thư không được mã hóa. Thư sẽ bị trả lại nếu máy chủ không dùng được TLS.", + "encrypt": "Mã hóa…", + "encrypt_hint": "Cần tiện ích S/MIME hoặc PGP. Mở Cài đặt." }, "plugin_list": { "filters": "Bộ lọc", diff --git a/locales/zh-TW/common.json b/locales/zh-TW/common.json index 3ea326a1e..1234d09e6 100644 --- a/locales/zh-TW/common.json +++ b/locales/zh-TW/common.json @@ -6392,7 +6392,11 @@ "reply_from_received": { "label": "使用收件地址回覆", "description": "從郵件寄達的地址回覆。適合擁有多個地址時使用。" - } + }, + "require_tls": "僅透過安全連線投遞", + "require_tls_hint": "郵件本身不會加密。如果伺服器無法使用 TLS,郵件會被退回。", + "encrypt": "加密…", + "encrypt_hint": "需要 S/MIME 或 PGP 附加元件。將開啟設定。" }, "plugin_list": { "filters": "篩選", diff --git a/locales/zh/common.json b/locales/zh/common.json index 937b1ba57..fd354b278 100644 --- a/locales/zh/common.json +++ b/locales/zh/common.json @@ -6392,7 +6392,11 @@ "reply_from_received": { "label": "自动选择回复地址", "description": "从邮件发往的地址回复。适合拥有多个地址的情况。" - } + }, + "require_tls": "仅通过安全连接投递", + "require_tls_hint": "邮件本身不会加密。如果服务器无法使用 TLS,邮件会被退回。", + "encrypt": "加密…", + "encrypt_hint": "需要 S/MIME 或 PGP 附加组件。将打开设置。" }, "plugin_list": { "filters": "筛选",