From e6f8c7a241af1f2af9432f98df81ca8e1adc8cb1 Mon Sep 17 00:00:00 2001 From: Linus Rath <139418639+rathlinus@users.noreply.github.com> Date: Wed, 4 Nov 2026 07:25:05 +0100 Subject: [PATCH] fix: show who a quick reply goes to and offer the full editor by name The quick reply tests find the box by its new placeholder key, which names the recipients instead of "Write a quick reply...". --- .../viewer-quick-reply-back.test.tsx | 4 +- .../viewer-quick-reply-words.test.tsx | 87 +++++++++++++++++++ .../__tests__/viewer-send-failures.test.tsx | 10 +-- components/email/email-viewer.tsx | 22 +++-- lib/outgoing/quick-reply-envelope.ts | 16 +++- locales/ar/common.json | 4 +- locales/ca/common.json | 4 +- locales/cs/common.json | 4 +- locales/da/common.json | 4 +- locales/de/common.json | 4 +- locales/en/common.json | 4 +- locales/es/common.json | 4 +- locales/et/common.json | 4 +- locales/fa/common.json | 4 +- locales/fr/common.json | 4 +- locales/he/common.json | 4 +- locales/hu/common.json | 4 +- locales/it/common.json | 4 +- locales/ja/common.json | 4 +- locales/ko/common.json | 4 +- locales/lv/common.json | 4 +- locales/mn/common.json | 4 +- locales/nb/common.json | 4 +- locales/nl/common.json | 4 +- locales/pl/common.json | 4 +- locales/pt/common.json | 4 +- locales/ro/common.json | 4 +- locales/ru/common.json | 4 +- locales/sk/common.json | 4 +- locales/th/common.json | 4 +- locales/tr/common.json | 4 +- locales/uk/common.json | 4 +- locales/vi/common.json | 4 +- locales/zh-TW/common.json | 4 +- locales/zh/common.json | 4 +- 35 files changed, 211 insertions(+), 48 deletions(-) create mode 100644 components/email/__tests__/viewer-quick-reply-words.test.tsx diff --git a/components/email/__tests__/viewer-quick-reply-back.test.tsx b/components/email/__tests__/viewer-quick-reply-back.test.tsx index 39fae9a39..b4717d125 100644 --- a/components/email/__tests__/viewer-quick-reply-back.test.tsx +++ b/components/email/__tests__/viewer-quick-reply-back.test.tsx @@ -42,7 +42,7 @@ beforeEach(() => clearAllHooks()); async function typeAndSend(onQuickReply: (body: string) => Promise) { render(); await act(async () => { await new Promise((r) => setTimeout(r, 10)); }); - const box = screen.getByPlaceholderText('quick_reply_placeholder') as HTMLTextAreaElement; + const box = screen.getByPlaceholderText('quick_reply_to') as HTMLTextAreaElement; fireEvent.focus(box); fireEvent.change(box, { target: { value: 'My answer' } }); fireEvent.click(screen.getByRole('button', { name: 'send' })); @@ -77,7 +77,7 @@ describe('automatic sign-out while the external recipient question is open', () render(); await act(async () => { await new Promise((r) => setTimeout(r, 10)); }); - const box = screen.getByPlaceholderText('quick_reply_placeholder') as HTMLTextAreaElement; + const box = screen.getByPlaceholderText('quick_reply_to') as HTMLTextAreaElement; fireEvent.focus(box); fireEvent.change(box, { target: { value: 'My answer' } }); fireEvent.click(screen.getByRole('button', { name: 'send' })); diff --git a/components/email/__tests__/viewer-quick-reply-words.test.tsx b/components/email/__tests__/viewer-quick-reply-words.test.tsx new file mode 100644 index 000000000..f980a7c93 --- /dev/null +++ b/components/email/__tests__/viewer-quick-reply-words.test.tsx @@ -0,0 +1,87 @@ +import { render, act, fireEvent, screen } from '@testing-library/react'; +import { describe, it, expect, vi, beforeEach } from 'vitest'; +import type { Email, Mailbox } from '@/lib/jmap/types'; +import { clearAllHooks } from '@/lib/plugin-hooks'; +import { useAuthStore } from '@/stores/auth-store'; + +// The quick reply box counted characters, hid the full editor behind "More +// options" and didn't say who the reply goes to. It now names the +// recipients in its placeholder and offers "Open full editor". + +vi.mock('next-intl', () => ({ + useTranslations: () => (key: string, values?: Record) => + values ? `${key}:${JSON.stringify(values)}` : key, + useLocale: () => 'en', + useFormatter: () => ({ + dateTime: (d: Date | string) => String(d), + relativeTime: (d: Date | string) => String(d), + number: (n: number) => String(n), + }), +})); +vi.mock('@/hooks/use-media-query', () => ({ + useDeviceDetection: () => ({ isMobile: false, isTablet: false, isDesktop: true }), + useMediaQuery: () => false, +})); +vi.mock('@/components/tour/tour-provider', () => ({ + useTour: () => ({ activeTour: null, startTour: vi.fn(), endTour: vi.fn(), registerStep: vi.fn() }), +})); +vi.mock('@/components/plugins/plugin-slot', () => ({ PluginSlot: () => null })); +vi.mock('@/hooks/use-plugin-slot-offers', () => ({ usePluginSlotOffers: () => [] })); + +class NoopResizeObserver { + observe() {} + unobserve() {} + disconnect() {} +} +vi.stubGlobal('ResizeObserver', NoopResizeObserver); + +const { EmailViewer } = await import('../email-viewer'); + +const mailboxes = [ + { id: 'inbox', name: 'Inbox', role: 'inbox', parentId: null, myRights: { mayAddItems: true } }, +] as unknown as Mailbox[]; + +const message = { + id: 'm1', blobId: 'b1', threadId: 't1', mailboxIds: { inbox: true }, keywords: { $seen: true }, + from: [{ name: 'Ben Carter', email: 'ben.carter@example.com' }], to: [{ name: 'Me', email: 'me@example.org' }], + subject: 'Hello', receivedAt: '2026-01-01T00:00:00Z', preview: 'Hello', hasAttachment: false, size: 100, + textBody: [{ partId: '1', type: 'text/plain' }], htmlBody: [{ partId: '1', type: 'text/plain' }], attachments: [], + bodyValues: { '1': { value: 'Plain body' } }, +} as unknown as Email; + +beforeEach(() => { + clearAllHooks(); + useAuthStore.setState({ identities: [{ id: 'me', email: 'me@example.org', name: 'Me' }] } as never); +}); + +async function renderViewer(email: Email, onReply = vi.fn()) { + render(); + await act(async () => { await new Promise((r) => setTimeout(r, 10)); }); +} + +describe('quick reply wording', () => { + it('says who the reply goes to', async () => { + await renderViewer(message); + expect(screen.getByPlaceholderText('quick_reply_to:{"names":"Ben Carter"}')).toBeTruthy(); + }); + + it('names the Reply-To address over the sender, by address when it has no name', async () => { + await renderViewer({ ...message, replyTo: [{ email: 'team@example.com' }] } as unknown as Email); + expect(screen.getByPlaceholderText('quick_reply_to:{"names":"team@example.com"}')).toBeTruthy(); + }); + + it('drops the character count and opens the full editor with the typed text', async () => { + const onReply = vi.fn(); + await renderViewer(message, onReply); + const box = screen.getByPlaceholderText(/^quick_reply_to/); + fireEvent.focus(box); + fireEvent.change(box, { target: { value: 'Thanks, will do' } }); + + expect(screen.queryByText(/characters_count/)).toBeNull(); + expect(screen.queryByText('more_options')).toBeNull(); + const full = screen.getByTestId('quick-reply-full-editor'); + expect(full).toHaveTextContent('open_full_editor'); + fireEvent.click(full); + expect(onReply).toHaveBeenCalledWith('Thanks, will do'); + }); +}); diff --git a/components/email/__tests__/viewer-send-failures.test.tsx b/components/email/__tests__/viewer-send-failures.test.tsx index fd95d5e73..b733976e1 100644 --- a/components/email/__tests__/viewer-send-failures.test.tsx +++ b/components/email/__tests__/viewer-send-failures.test.tsx @@ -92,9 +92,9 @@ describe('viewer send failures', () => { useSettingsStore.setState({ quickReplyEnabled: false }); render(); - expect(screen.queryByPlaceholderText('quick_reply_placeholder')).not.toBeInTheDocument(); + expect(screen.queryByPlaceholderText('quick_reply_to')).not.toBeInTheDocument(); act(() => useSettingsStore.getState().updateSetting('quickReplyEnabled', true)); - expect(screen.getByPlaceholderText('quick_reply_placeholder')).toBeInTheDocument(); + expect(screen.getByPlaceholderText('quick_reply_to')).toBeInTheDocument(); }); it('reports a failed quick reply and keeps the text on Stalwart 1.0', async () => { @@ -103,7 +103,7 @@ describe('viewer send failures', () => { , ); - const box = screen.getByPlaceholderText('quick_reply_placeholder'); + const box = screen.getByPlaceholderText('quick_reply_to'); fireEvent.change(box, { target: { value: 'Thanks!' } }); fireEvent.keyDown(box, { key: 'Enter', ctrlKey: true }); @@ -122,7 +122,7 @@ describe('viewer send failures', () => { await act(async () => { await flushComposers(1000); }); expect(onSaveQuickReplyDraft).not.toHaveBeenCalled(); - fireEvent.change(screen.getByPlaceholderText('quick_reply_placeholder'), { target: { value: 'On it.' } }); + fireEvent.change(screen.getByPlaceholderText('quick_reply_to'), { target: { value: 'On it.' } }); await act(async () => { await flushComposers(1000); }); expect(onSaveQuickReplyDraft).toHaveBeenCalledWith('On it.'); }); @@ -133,7 +133,7 @@ describe('viewer send failures', () => { render(); expect(useUIStore.getState().composingCount).toBe(0); - fireEvent.focus(screen.getByPlaceholderText('quick_reply_placeholder')); + fireEvent.focus(screen.getByPlaceholderText('quick_reply_to')); expect(useUIStore.getState().composingCount).toBe(1); fireEvent.click(screen.getByText('cancel')); diff --git a/components/email/email-viewer.tsx b/components/email/email-viewer.tsx index 0b1b5fac7..8bea1337e 100644 --- a/components/email/email-viewer.tsx +++ b/components/email/email-viewer.tsx @@ -46,7 +46,7 @@ import { onPanelRequest } from "@/lib/plugin-sandbox/panel-requests"; import { PluginPanelToggle, panelLabelOf, panelShortcutOf } from "@/components/plugins/app-side-panel"; import type { ComposerMetadata } from "@/lib/plugin-sandbox/composer-registry"; import { QuickReplyPluginActions } from "./quick-reply-plugin-actions"; -import { quickReplyEnvelope } from "@/lib/outgoing/quick-reply-envelope"; +import { quickReplyEnvelope, quickReplyRecipients } from "@/lib/outgoing/quick-reply-envelope"; import { formatFileSize, cn, formatDate, formatDateTime, formatRelativeAgo, generateUUID, cleanPreview, type MailboxNode } from "@/lib/utils"; import { getEffectiveTimeZone, getWallClock } from "@/lib/timezone"; import { itemsToHide } from "@/lib/toolbar-overflow"; @@ -1363,6 +1363,7 @@ export function EmailViewer({ const archiveActionEnabled = useArchiveActionEnabled(); const tWindows = useTranslations('pwaWindows'); const tComposer = useTranslations('email_composer'); + const tWords = useTranslations('compose_words'); const tNotifications = useTranslations('notifications'); const tImport = useTranslations('mail_import'); const tCommon = useTranslations('common'); @@ -1648,6 +1649,13 @@ export function EmailViewer({ if (isQuickReplyFocused) quickReplyBoxRef.current?.scrollIntoView?.({ block: 'nearest' }); }, [isQuickReplyFocused]); const [isSendingQuickReply, setIsSendingQuickReply] = useState(false); + // "Reply to Ben Carter…": who the quick reply goes to, by name. + const quickReplyNames = email + ? quickReplyRecipients(email, identities).map((r) => r.name || r.email).join(', ') + : ''; + const quickReplyPlaceholder = quickReplyNames + ? tWords('quick_reply_to', { names: quickReplyNames }) + : t('quick_reply_placeholder'); /** The recipients and identity the quick reply would be sent with. */ const quickMetadata = (): ComposerMetadata => { if (!email) return { subject: '', to: [], attachments: [] }; @@ -6096,7 +6104,7 @@ ${printWatermark ? printWatermark.html : ''} void handleSendQuickReply(); } }} - placeholder={t('quick_reply_placeholder')} + placeholder={quickReplyPlaceholder} className={cn( "w-full px-3 py-2 text-sm border border-border bg-background text-foreground rounded-lg", "hover:border-accent focus:border-primary focus:outline-hidden transition-all", @@ -6108,10 +6116,7 @@ ${printWatermark ? printWatermark.html : ''} {/* Action buttons - show when focused or has text */} {(isQuickReplyFocused || quickReplyText) && ( -
-
- {t('characters_count', { count: quickReplyText.length })} -
+
i.email).filter(Boolean); const replySource = { from: email.from, replyToAddresses: email.replyTo, to: email.to, cc: email.cc }; - const recipients = buildReplyRecipients(replySource, 'reply', ownIdentityEmails).to - .map((r) => r.email) - .filter((address): address is string => Boolean(address)); + const recipients = quickReplyRecipients(email, identities).map((r) => r.email); const primaryIdentity = identities[0]; const selfSentIdentityId = isSelfSent(replySource, ownIdentityEmails) ? findDraftIdentityId(identities, email.from?.[0]) @@ -46,6 +44,18 @@ export function quickReplyEnvelope(email: ReplySource, identities: Identity[]): return { recipients, sendingIdentity, primaryIdentity }; } +/** + * The people a quick reply goes to, with their names, by the same rules as + * quickReplyEnvelope: for the box's "Reply to Ben Carter…" placeholder. + */ +export function quickReplyRecipients(email: ReplySource, identities: Identity[]): { name?: string; email: string }[] { + const ownIdentityEmails = identities.map((i) => i.email).filter(Boolean); + const replySource = { from: email.from, replyToAddresses: email.replyTo, to: email.to, cc: email.cc }; + return buildReplyRecipients(replySource, 'reply', ownIdentityEmails).to + .filter((r): r is typeof r & { email: string } => Boolean(r.email)) + .map((r) => ({ name: r.name || undefined, email: r.email })); +} + /** * Saves the text typed into a quick reply as a draft reply, before an * automatic sign-out (lib/compose-flush-registry.ts). Plain text, threaded diff --git a/locales/ar/common.json b/locales/ar/common.json index ed6682970..08b672c45 100644 --- a/locales/ar/common.json +++ b/locales/ar/common.json @@ -6396,7 +6396,9 @@ "require_tls": "التسليم عبر اتصال آمن فقط", "require_tls_hint": "الرسالة نفسها غير مشفّرة. تُرَد إذا لم يتمكن خادم من استخدام TLS.", "encrypt": "تشفير…", - "encrypt_hint": "يتطلب إضافة S/MIME أو PGP. يفتح الإعدادات." + "encrypt_hint": "يتطلب إضافة S/MIME أو PGP. يفتح الإعدادات.", + "quick_reply_to": "الرد على {names}…", + "open_full_editor": "فتح المحرر الكامل" }, "plugin_list": { "filters": "المرشحات", diff --git a/locales/ca/common.json b/locales/ca/common.json index 7836a4f0e..79db8efdb 100644 --- a/locales/ca/common.json +++ b/locales/ca/common.json @@ -6396,7 +6396,9 @@ "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ó." + "encrypt_hint": "Cal un complement S/MIME o PGP. Obre la configuració.", + "quick_reply_to": "Respon a {names}…", + "open_full_editor": "Obre l'editor complet" }, "plugin_list": { "filters": "Filtres", diff --git a/locales/cs/common.json b/locales/cs/common.json index 301fbdacc..8bb772b79 100644 --- a/locales/cs/common.json +++ b/locales/cs/common.json @@ -6396,7 +6396,9 @@ "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í." + "encrypt_hint": "Vyžaduje doplněk S/MIME nebo PGP. Otevře Nastavení.", + "quick_reply_to": "Odpověď pro {names}…", + "open_full_editor": "Otevřít v plném editoru" }, "plugin_list": { "filters": "Filtry", diff --git a/locales/da/common.json b/locales/da/common.json index fd0a954df..a21a64b6a 100644 --- a/locales/da/common.json +++ b/locales/da/common.json @@ -6396,7 +6396,9 @@ "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." + "encrypt_hint": "Kræver en S/MIME- eller PGP-tilføjelse. Åbner Indstillinger.", + "quick_reply_to": "Svar til {names}…", + "open_full_editor": "Åbn fuld editor" }, "plugin_list": { "filters": "Filtre", diff --git a/locales/de/common.json b/locales/de/common.json index 03b98526d..118e73984 100644 --- a/locales/de/common.json +++ b/locales/de/common.json @@ -6396,7 +6396,9 @@ "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." + "encrypt_hint": "Dafür brauchen Sie eine S/MIME- oder PGP-Erweiterung. Öffnet die Einstellungen.", + "quick_reply_to": "Antwort an {names}…", + "open_full_editor": "Im vollen Editor öffnen" }, "plugin_list": { "filters": "Filter", diff --git a/locales/en/common.json b/locales/en/common.json index 4138b9b32..5c156b566 100644 --- a/locales/en/common.json +++ b/locales/en/common.json @@ -6396,7 +6396,9 @@ "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." + "encrypt_hint": "Needs an S/MIME or PGP add-on. Opens Settings.", + "quick_reply_to": "Reply to {names}…", + "open_full_editor": "Open full editor" }, "plugin_list": { "filters": "Filters", diff --git a/locales/es/common.json b/locales/es/common.json index fda797ee8..e2c5abe2c 100644 --- a/locales/es/common.json +++ b/locales/es/common.json @@ -6396,7 +6396,9 @@ "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." + "encrypt_hint": "Necesita un complemento S/MIME o PGP. Abre la Configuración.", + "quick_reply_to": "Responder a {names}…", + "open_full_editor": "Abrir el editor completo" }, "plugin_list": { "filters": "Filtros", diff --git a/locales/et/common.json b/locales/et/common.json index 5793da0ee..7c10b5be3 100644 --- a/locales/et/common.json +++ b/locales/et/common.json @@ -6396,7 +6396,9 @@ "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." + "encrypt_hint": "Vajab S/MIME või PGP lisa. Avab seaded.", + "quick_reply_to": "Vastus saajale {names}…", + "open_full_editor": "Ava täielik redaktor" }, "plugin_list": { "filters": "Filtrid", diff --git a/locales/fa/common.json b/locales/fa/common.json index 730598745..6245bfe57 100644 --- a/locales/fa/common.json +++ b/locales/fa/common.json @@ -6396,7 +6396,9 @@ "require_tls": "تحویل فقط از راه اتصال امن", "require_tls_hint": "خود پیام رمزگذاری نمی‌شود. اگر سروری نتواند از TLS استفاده کند، برگشت می‌خورد.", "encrypt": "رمزگذاری…", - "encrypt_hint": "به افزونهٔ S/MIME یا PGP نیاز دارد. تنظیمات را باز می‌کند." + "encrypt_hint": "به افزونهٔ S/MIME یا PGP نیاز دارد. تنظیمات را باز می‌کند.", + "quick_reply_to": "پاسخ به {names}…", + "open_full_editor": "باز کردن ویرایشگر کامل" }, "plugin_list": { "filters": "فیلترها", diff --git a/locales/fr/common.json b/locales/fr/common.json index f76c76c4f..d1f479d10 100644 --- a/locales/fr/common.json +++ b/locales/fr/common.json @@ -6396,7 +6396,9 @@ "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." + "encrypt_hint": "Nécessite une extension S/MIME ou PGP. Ouvre les Paramètres.", + "quick_reply_to": "Répondre à {names}…", + "open_full_editor": "Ouvrir l'éditeur complet" }, "plugin_list": { "filters": "Filtres", diff --git a/locales/he/common.json b/locales/he/common.json index 3662a919d..d71e21f53 100644 --- a/locales/he/common.json +++ b/locales/he/common.json @@ -6397,7 +6397,9 @@ "require_tls": "מסירה רק דרך חיבור מאובטח", "require_tls_hint": "ההודעה עצמה אינה מוצפנת. היא תוחזר אם שרת לא יכול להשתמש ב-TLS.", "encrypt": "הצפנה…", - "encrypt_hint": "דורש תוסף S/MIME או PGP. פותח את ההגדרות." + "encrypt_hint": "דורש תוסף S/MIME או PGP. פותח את ההגדרות.", + "quick_reply_to": "תשובה אל {names}…", + "open_full_editor": "פתיחה בעורך המלא" }, "plugin_list": { "filters": "מסננים", diff --git a/locales/hu/common.json b/locales/hu/common.json index 7dedc7a49..a1224176a 100644 --- a/locales/hu/common.json +++ b/locales/hu/common.json @@ -6396,7 +6396,9 @@ "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." + "encrypt_hint": "S/MIME vagy PGP bővítmény kell hozzá. Megnyitja a Beállításokat.", + "quick_reply_to": "Válasz neki: {names}…", + "open_full_editor": "Megnyitás a teljes szerkesztőben" }, "plugin_list": { "filters": "Szűrők", diff --git a/locales/it/common.json b/locales/it/common.json index ad285a749..cbe010633 100644 --- a/locales/it/common.json +++ b/locales/it/common.json @@ -6396,7 +6396,9 @@ "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." + "encrypt_hint": "Serve un componente aggiuntivo S/MIME o PGP. Apre le Impostazioni.", + "quick_reply_to": "Rispondi a {names}…", + "open_full_editor": "Apri l'editor completo" }, "plugin_list": { "filters": "Filtri", diff --git a/locales/ja/common.json b/locales/ja/common.json index c5f44ee3e..14d4a3fc7 100644 --- a/locales/ja/common.json +++ b/locales/ja/common.json @@ -6396,7 +6396,9 @@ "require_tls": "安全な接続でのみ配信", "require_tls_hint": "メッセージ自体は暗号化されません。サーバーが TLS を使えない場合は返送されます。", "encrypt": "暗号化…", - "encrypt_hint": "S/MIME または PGP のアドオンが必要です。設定を開きます。" + "encrypt_hint": "S/MIME または PGP のアドオンが必要です。設定を開きます。", + "quick_reply_to": "{names} に返信…", + "open_full_editor": "フルエディターで開く" }, "plugin_list": { "filters": "フィルター", diff --git a/locales/ko/common.json b/locales/ko/common.json index d7a56c111..dd10f83c1 100644 --- a/locales/ko/common.json +++ b/locales/ko/common.json @@ -6396,7 +6396,9 @@ "require_tls": "보안 연결로만 배달", "require_tls_hint": "메시지 자체는 암호화되지 않습니다. 서버가 TLS를 사용할 수 없으면 반송됩니다.", "encrypt": "암호화…", - "encrypt_hint": "S/MIME 또는 PGP 추가 기능이 필요합니다. 설정을 엽니다." + "encrypt_hint": "S/MIME 또는 PGP 추가 기능이 필요합니다. 설정을 엽니다.", + "quick_reply_to": "{names}에게 답장…", + "open_full_editor": "전체 편집기에서 열기" }, "plugin_list": { "filters": "필터", diff --git a/locales/lv/common.json b/locales/lv/common.json index bcfadf592..f42e200e7 100644 --- a/locales/lv/common.json +++ b/locales/lv/common.json @@ -6396,7 +6396,9 @@ "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." + "encrypt_hint": "Nepieciešams S/MIME vai PGP papildinājums. Atver iestatījumus.", + "quick_reply_to": "Atbilde: {names}…", + "open_full_editor": "Atvērt pilnajā redaktorā" }, "plugin_list": { "filters": "Filtri", diff --git a/locales/mn/common.json b/locales/mn/common.json index 8d03df472..00f75758f 100644 --- a/locales/mn/common.json +++ b/locales/mn/common.json @@ -6396,7 +6396,9 @@ "require_tls": "Зөвхөн аюулгүй холболтоор хүргэх", "require_tls_hint": "Мессеж өөрөө шифрлэгдэхгүй. Сервер TLS ашиглаж чадахгүй бол буцаагдана.", "encrypt": "Шифрлэх…", - "encrypt_hint": "S/MIME эсвэл PGP нэмэлт хэрэгтэй. Тохиргоог нээнэ." + "encrypt_hint": "S/MIME эсвэл PGP нэмэлт хэрэгтэй. Тохиргоог нээнэ.", + "quick_reply_to": "{names} руу хариу бичих…", + "open_full_editor": "Бүтэн засварлагчид нээх" }, "plugin_list": { "filters": "Шүүлтүүр", diff --git a/locales/nb/common.json b/locales/nb/common.json index 589082828..c3ad75ca1 100644 --- a/locales/nb/common.json +++ b/locales/nb/common.json @@ -6396,7 +6396,9 @@ "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." + "encrypt_hint": "Krever et S/MIME- eller PGP-tillegg. Åpner Innstillinger.", + "quick_reply_to": "Svar til {names}…", + "open_full_editor": "Åpne i full redigering" }, "plugin_list": { "filters": "Filtre", diff --git a/locales/nl/common.json b/locales/nl/common.json index 3fd8ddc41..e2cb76904 100644 --- a/locales/nl/common.json +++ b/locales/nl/common.json @@ -6396,7 +6396,9 @@ "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." + "encrypt_hint": "Vereist een S/MIME- of PGP-add-on. Opent Instellingen.", + "quick_reply_to": "Antwoord aan {names}…", + "open_full_editor": "Openen in volledige editor" }, "plugin_list": { "filters": "Filters", diff --git a/locales/pl/common.json b/locales/pl/common.json index 1db35978a..d59272edd 100644 --- a/locales/pl/common.json +++ b/locales/pl/common.json @@ -6396,7 +6396,9 @@ "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." + "encrypt_hint": "Wymaga dodatku S/MIME lub PGP. Otwiera Ustawienia.", + "quick_reply_to": "Odpowiedź do: {names}…", + "open_full_editor": "Otwórz w pełnym edytorze" }, "plugin_list": { "filters": "Filtry", diff --git a/locales/pt/common.json b/locales/pt/common.json index 24db7fd80..b1ea6d35a 100644 --- a/locales/pt/common.json +++ b/locales/pt/common.json @@ -6396,7 +6396,9 @@ "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." + "encrypt_hint": "Precisa de um extra S/MIME ou PGP. Abre as Configurações.", + "quick_reply_to": "Responder a {names}…", + "open_full_editor": "Abrir no editor completo" }, "plugin_list": { "filters": "Filtros", diff --git a/locales/ro/common.json b/locales/ro/common.json index 6b46ce602..f810811fa 100644 --- a/locales/ro/common.json +++ b/locales/ro/common.json @@ -6396,7 +6396,9 @@ "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." + "encrypt_hint": "Necesită un supliment S/MIME sau PGP. Deschide Setările.", + "quick_reply_to": "Răspuns către {names}…", + "open_full_editor": "Deschide în editorul complet" }, "plugin_list": { "filters": "Filtre", diff --git a/locales/ru/common.json b/locales/ru/common.json index 4ea47ede2..74a021762 100644 --- a/locales/ru/common.json +++ b/locales/ru/common.json @@ -6396,7 +6396,9 @@ "require_tls": "Доставлять только по защищённому соединению", "require_tls_hint": "Само письмо не шифруется. Оно вернётся, если сервер не может использовать TLS.", "encrypt": "Зашифровать…", - "encrypt_hint": "Нужно дополнение S/MIME или PGP. Открывает настройки." + "encrypt_hint": "Нужно дополнение S/MIME или PGP. Открывает настройки.", + "quick_reply_to": "Ответ для {names}…", + "open_full_editor": "Открыть в полном редакторе" }, "plugin_list": { "filters": "Фильтры", diff --git a/locales/sk/common.json b/locales/sk/common.json index c532d76e1..c491884a9 100644 --- a/locales/sk/common.json +++ b/locales/sk/common.json @@ -6396,7 +6396,9 @@ "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." + "encrypt_hint": "Vyžaduje doplnok S/MIME alebo PGP. Otvorí Nastavenia.", + "quick_reply_to": "Odpoveď pre {names}…", + "open_full_editor": "Otvoriť v plnom editore" }, "plugin_list": { "filters": "Filtre", diff --git a/locales/th/common.json b/locales/th/common.json index 9a5932b1a..f079d2742 100644 --- a/locales/th/common.json +++ b/locales/th/common.json @@ -3317,7 +3317,9 @@ "require_tls": "ส่งผ่านการเชื่อมต่อที่ปลอดภัยเท่านั้น", "require_tls_hint": "ตัวข้อความเองไม่ได้ถูกเข้ารหัส ข้อความจะถูกตีกลับหากเซิร์ฟเวอร์ใช้ TLS ไม่ได้", "encrypt": "เข้ารหัส…", - "encrypt_hint": "ต้องใช้ส่วนเสริม S/MIME หรือ PGP จะเปิดการตั้งค่า" + "encrypt_hint": "ต้องใช้ส่วนเสริม S/MIME หรือ PGP จะเปิดการตั้งค่า", + "quick_reply_to": "ตอบกลับถึง {names}…", + "open_full_editor": "เปิดในตัวแก้ไขเต็มรูปแบบ" }, "plugin_list": { "filters": "ตัวกรอง", diff --git a/locales/tr/common.json b/locales/tr/common.json index 38db522bf..8c060f63d 100644 --- a/locales/tr/common.json +++ b/locales/tr/common.json @@ -6396,7 +6396,9 @@ "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." + "encrypt_hint": "S/MIME veya PGP eklentisi gerekir. Ayarlar'ı açar.", + "quick_reply_to": "Yanıtla: {names}…", + "open_full_editor": "Tam düzenleyicide aç" }, "plugin_list": { "filters": "Filtreler", diff --git a/locales/uk/common.json b/locales/uk/common.json index 946065a5f..b0d78303e 100644 --- a/locales/uk/common.json +++ b/locales/uk/common.json @@ -6396,7 +6396,9 @@ "require_tls": "Доставляти лише захищеним з'єднанням", "require_tls_hint": "Сам лист не шифрується. Він повернеться, якщо сервер не може використати TLS.", "encrypt": "Зашифрувати…", - "encrypt_hint": "Потрібен додаток S/MIME або PGP. Відкриває налаштування." + "encrypt_hint": "Потрібен додаток S/MIME або PGP. Відкриває налаштування.", + "quick_reply_to": "Відповідь для {names}…", + "open_full_editor": "Відкрити в повному редакторі" }, "plugin_list": { "filters": "Фільтри", diff --git a/locales/vi/common.json b/locales/vi/common.json index 0919080c0..6c864887b 100644 --- a/locales/vi/common.json +++ b/locales/vi/common.json @@ -6408,7 +6408,9 @@ "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." + "encrypt_hint": "Cần tiện ích S/MIME hoặc PGP. Mở Cài đặt.", + "quick_reply_to": "Trả lời {names}…", + "open_full_editor": "Mở trong trình soạn thảo đầy đủ" }, "plugin_list": { "filters": "Bộ lọc", diff --git a/locales/zh-TW/common.json b/locales/zh-TW/common.json index 1234d09e6..3ba374dd0 100644 --- a/locales/zh-TW/common.json +++ b/locales/zh-TW/common.json @@ -6396,7 +6396,9 @@ "require_tls": "僅透過安全連線投遞", "require_tls_hint": "郵件本身不會加密。如果伺服器無法使用 TLS,郵件會被退回。", "encrypt": "加密…", - "encrypt_hint": "需要 S/MIME 或 PGP 附加元件。將開啟設定。" + "encrypt_hint": "需要 S/MIME 或 PGP 附加元件。將開啟設定。", + "quick_reply_to": "回覆給 {names}…", + "open_full_editor": "在完整編輯器中開啟" }, "plugin_list": { "filters": "篩選", diff --git a/locales/zh/common.json b/locales/zh/common.json index fd354b278..4138f943a 100644 --- a/locales/zh/common.json +++ b/locales/zh/common.json @@ -6396,7 +6396,9 @@ "require_tls": "仅通过安全连接投递", "require_tls_hint": "邮件本身不会加密。如果服务器无法使用 TLS,邮件会被退回。", "encrypt": "加密…", - "encrypt_hint": "需要 S/MIME 或 PGP 附加组件。将打开设置。" + "encrypt_hint": "需要 S/MIME 或 PGP 附加组件。将打开设置。", + "quick_reply_to": "回复 {names}…", + "open_full_editor": "在完整编辑器中打开" }, "plugin_list": { "filters": "筛选",