mirror of
https://github.com/bulwarkmail/webmail.git
synced 2026-11-06 00:15:32 +00:00
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:
@@ -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'));
|
||||
|
||||
@@ -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])}
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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": "المرشحات",
|
||||
|
||||
@@ -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",
|
||||
|
||||
@@ -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",
|
||||
|
||||
@@ -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",
|
||||
|
||||
@@ -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",
|
||||
|
||||
@@ -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",
|
||||
|
||||
@@ -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",
|
||||
|
||||
@@ -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",
|
||||
|
||||
@@ -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": "فیلترها",
|
||||
|
||||
@@ -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",
|
||||
|
||||
@@ -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": "מסננים",
|
||||
|
||||
@@ -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",
|
||||
|
||||
@@ -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",
|
||||
|
||||
@@ -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": "フィルター",
|
||||
|
||||
@@ -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": "필터",
|
||||
|
||||
@@ -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",
|
||||
|
||||
@@ -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": "Шүүлтүүр",
|
||||
|
||||
@@ -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",
|
||||
|
||||
@@ -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",
|
||||
|
||||
@@ -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",
|
||||
|
||||
@@ -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",
|
||||
|
||||
@@ -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",
|
||||
|
||||
@@ -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": "Фильтры",
|
||||
|
||||
@@ -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",
|
||||
|
||||
@@ -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": "ตัวกรอง",
|
||||
|
||||
@@ -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",
|
||||
|
||||
@@ -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": "Фільтри",
|
||||
|
||||
@@ -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",
|
||||
|
||||
@@ -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": "篩選",
|
||||
|
||||
@@ -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": "筛选",
|
||||
|
||||
Reference in New Issue
Block a user