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...".
This commit is contained in:
Linus Rath
2026-11-04 07:25:05 +01:00
parent 242d0bacfc
commit e6f8c7a241
35 changed files with 211 additions and 48 deletions
@@ -42,7 +42,7 @@ beforeEach(() => clearAllHooks());
async function typeAndSend(onQuickReply: (body: string) => Promise<boolean | void>) {
render(<EmailViewer email={message} isLoading={false} mailboxes={mailboxes} selectedMailbox="inbox" currentMailboxRole="inbox" onQuickReply={onQuickReply} />);
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(<EmailViewer email={message} isLoading={false} mailboxes={mailboxes} selectedMailbox="inbox" currentMailboxRole="inbox"
onQuickReply={onQuickReply} onSaveQuickReplyDraft={onSaveQuickReplyDraft} />);
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' }));
@@ -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<string, unknown>) =>
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(<EmailViewer email={email} isLoading={false} mailboxes={mailboxes} selectedMailbox="inbox" currentMailboxRole="inbox" onQuickReply={vi.fn()} onReply={onReply} />);
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');
});
});
@@ -92,9 +92,9 @@ describe('viewer send failures', () => {
useSettingsStore.setState({ quickReplyEnabled: false });
render(<EmailViewer email={makeEmail()} mailboxes={mailboxes} selectedMailbox="inbox" currentMailboxRole="inbox" />);
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', () => {
<EmailViewer email={makeEmail()} mailboxes={mailboxes} selectedMailbox="inbox" currentMailboxRole="inbox" onQuickReply={onQuickReply} />,
);
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(<EmailViewer email={makeEmail()} mailboxes={mailboxes} selectedMailbox="inbox" currentMailboxRole="inbox" onQuickReply={vi.fn()} />);
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'));
+14 -8
View File
@@ -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) && (
<div className="flex flex-wrap items-center justify-between gap-2 animate-in fade-in slide-in-from-top-1 duration-200">
<div className="text-xs text-muted-foreground">
{t('characters_count', { count: quickReplyText.length })}
</div>
<div className="flex flex-wrap items-center justify-end gap-2 animate-in fade-in slide-in-from-top-1 duration-200">
<div className="flex flex-wrap items-center gap-2 min-w-0">
<Button
variant="ghost"
@@ -6134,9 +6139,10 @@ ${printWatermark ? printWatermark.html : ''}
}}
disabled={isSendingQuickReply}
className="text-muted-foreground"
data-testid="quick-reply-full-editor"
>
<MoreVertical className="w-4 h-4 me-1" />
{t('more_options')}
<Maximize2 className="w-4 h-4 me-1" />
{tWords('open_full_editor')}
</Button>
<QuickReplyPluginActions
key={JSON.stringify([email.id, activeAccountId, email.sourceClientAccountId, email.sourceAccountId])}
+13 -3
View File
@@ -29,9 +29,7 @@ export interface QuickReplyEnvelope {
export function quickReplyEnvelope(email: ReplySource, identities: Identity[]): QuickReplyEnvelope {
const ownIdentityEmails = identities.map((i) => 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
+3 -1
View File
@@ -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": "المرشحات",
+3 -1
View File
@@ -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",
+3 -1
View File
@@ -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",
+3 -1
View File
@@ -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",
+3 -1
View File
@@ -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",
+3 -1
View File
@@ -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",
+3 -1
View File
@@ -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",
+3 -1
View File
@@ -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",
+3 -1
View File
@@ -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": "فیلترها",
+3 -1
View File
@@ -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",
+3 -1
View File
@@ -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": "מסננים",
+3 -1
View File
@@ -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",
+3 -1
View File
@@ -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",
+3 -1
View File
@@ -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": "フィルター",
+3 -1
View File
@@ -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": "필터",
+3 -1
View File
@@ -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",
+3 -1
View File
@@ -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": "Шүүлтүүр",
+3 -1
View File
@@ -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",
+3 -1
View File
@@ -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",
+3 -1
View File
@@ -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",
+3 -1
View File
@@ -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",
+3 -1
View File
@@ -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",
+3 -1
View File
@@ -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": "Фильтры",
+3 -1
View File
@@ -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",
+3 -1
View File
@@ -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": "ตัวกรอง",
+3 -1
View File
@@ -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",
+3 -1
View File
@@ -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": "Фільтри",
+3 -1
View File
@@ -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",
+3 -1
View File
@@ -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": "篩選",
+3 -1
View File
@@ -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": "筛选",