feat: add Cancel send for scheduled messages, which puts them back into Drafts, and show their actions once

This commit is contained in:
Linus Rath
2026-10-28 04:48:13 +01:00
parent acdebe6cda
commit f7adbf1efe
35 changed files with 153 additions and 83 deletions
@@ -99,3 +99,19 @@ describe('EmailContextMenu batch forward as attachment', () => {
expect((screen.getByTestId('ctx-batch-forward-as-attachment') as HTMLButtonElement).disabled).toBe(true);
});
});
describe('EmailContextMenu on a scheduled message', () => {
const scheduled = { ...email, isScheduled: true, scheduledUndoStatus: 'pending' } as unknown as Email;
it('offers Cancel send', () => {
const onCancelScheduledSend = vi.fn();
renderMenu({ email: scheduled, onCancelScheduledSend, onCancelScheduledForEdit: vi.fn(), onRescheduleScheduled: vi.fn() });
fireEvent.click(screen.getByTestId('ctx-cancel-scheduled-send'));
expect(onCancelScheduledSend).toHaveBeenCalledTimes(1);
});
it('leaves it out for an encrypted message, which cannot go back to Drafts', () => {
renderMenu({ email: { ...scheduled, isSmimeScheduled: true } as Email, onCancelScheduledSend: vi.fn(), onCancelScheduledForEdit: vi.fn() });
expect(screen.queryByTestId('ctx-cancel-scheduled-send')).toBeNull();
});
});
@@ -138,6 +138,28 @@ describe('viewer send failures', () => {
expect(onRescheduleScheduled).toHaveBeenCalledTimes(1);
});
it('offers Cancel send, and shows one set of scheduled actions and no draft bar', () => {
const onCancelScheduledSend = vi.fn();
render(
<EmailViewer
email={makeEmail({ ...scheduled(), keywords: { $seen: true, $draft: true } })}
mailboxes={mailboxes}
selectedMailbox="inbox"
currentMailboxRole="inbox"
onCancelScheduledSend={onCancelScheduledSend}
onCancelScheduledForEdit={vi.fn()}
/>,
);
fireEvent.click(screen.getByTestId('cancel-scheduled-send'));
expect(onCancelScheduledSend).toHaveBeenCalledTimes(1);
// The banner's buttons are for the phone only, where the toolbar has none.
const bannerReschedule = screen.getAllByText('reschedule_send').map(el => el.closest('div.flex-wrap')).find(Boolean);
expect(bannerReschedule?.className).toContain('sm:hidden');
expect(screen.getAllByText('send_now')).toHaveLength(1);
expect(screen.queryByText('draft_banner')).toBeNull();
});
it('reschedules through the schedule dialog, not a browser prompt', async () => {
const prompt = vi.spyOn(window, 'prompt');
const onRescheduleScheduled = vi.fn().mockResolvedValue(undefined);
+13
View File
@@ -36,6 +36,7 @@ import {
ShieldCheck,
EditIcon,
CalendarClock,
CalendarX,
Paperclip,
Link as LinkIcon,
MessagesSquare,
@@ -106,6 +107,7 @@ interface EmailContextMenuProps {
onUndoSpam?: () => void;
onEditDraft?: () => void;
onCancelScheduledForEdit?: () => void;
onCancelScheduledSend?: () => void;
onRescheduleScheduled?: () => void;
// Batch actions
onBatchMarkAsRead?: (read: boolean) => void;
@@ -190,6 +192,7 @@ export function EmailContextMenu({
onBatchForwardAsAttachment,
onEditDraft,
onCancelScheduledForEdit,
onCancelScheduledSend,
onRescheduleScheduled,
copyTargets,
onCopyToAccount,
@@ -575,6 +578,16 @@ export function EmailContextMenu({
onClick={() => handleAction(onCancelScheduledForEdit!)}
disabled={!onCancelScheduledForEdit}
/>
{/* Back into Drafts. An S/MIME message can't go back (it is
encrypted), so it only offers "Cancel and compose again". */}
{onCancelScheduledSend && !email.isSmimeScheduled && (
<ContextMenuItem
icon={CalendarX}
label={t("cancel_scheduled_send")}
testId="ctx-cancel-scheduled-send"
onClick={() => handleAction(onCancelScheduledSend)}
/>
)}
</>
)}
+4
View File
@@ -85,6 +85,8 @@ interface EmailListProps {
isScheduledView?: boolean;
onLoadMoreScheduled?: () => void;
onCancelScheduledForEdit?: (email: Email) => void | Promise<void>;
/** Stop a scheduled message from going out; it goes back to Drafts. */
onCancelScheduledSend?: (email: Email) => void | Promise<void>;
onRescheduleScheduled?: (email: Email) => void | Promise<void>;
/**
* Mountain View folds the batch verbs into the single toolbar above the
@@ -164,6 +166,7 @@ export function EmailList({
hideBatchToolbar = false,
onLoadMoreScheduled,
onCancelScheduledForEdit,
onCancelScheduledSend,
onRescheduleScheduled,
}: EmailListProps) {
const t = useTranslations('email_list');
@@ -1204,6 +1207,7 @@ export function EmailList({
onUndoSpam={() => onUndoSpam?.(contextMenuEmail!)}
onEditDraft={() => onEditDraft?.(contextMenuEmail!)}
onCancelScheduledForEdit={onCancelScheduledForEdit ? () => onCancelScheduledForEdit(contextMenuEmail!) : undefined}
onCancelScheduledSend={onCancelScheduledSend ? () => void onCancelScheduledSend(contextMenuEmail!) : undefined}
onRescheduleScheduled={onRescheduleScheduled ? () => onRescheduleScheduled(contextMenuEmail!) : undefined}
copyTargets={copyTargets}
onCopyToAccount={async (accountId, mailboxId) => {
+20 -24
View File
@@ -93,6 +93,7 @@ import {
PlayCircle,
PenSquare,
CalendarClock,
CalendarX,
Link as LinkIcon,
Maximize2,
Minimize2,
@@ -214,6 +215,8 @@ interface EmailViewerProps {
onShowShortcuts?: () => void;
onEditDraft?: () => void;
onCancelScheduledForEdit?: () => void;
/** Stop a scheduled message from going out; it goes back to Drafts. */
onCancelScheduledSend?: () => void;
onRescheduleScheduled?: (delayedUntil: string) => void | Promise<void>;
onCompose?: () => void;
/**
@@ -1064,6 +1067,7 @@ export function EmailViewer({
onShowShortcuts,
onEditDraft,
onCancelScheduledForEdit,
onCancelScheduledSend,
onRescheduleScheduled,
onCompose,
onToggleFullscreen,
@@ -3864,6 +3868,12 @@ export function EmailViewer({
<EditIcon className="w-4 h-4" />
{showToolbarLabels && <span className="hidden sm:inline text-sm">{email.isSmimeScheduled ? t('cancel_and_compose_again') : t('cancel_and_edit')}</span>}
</Button>
{onCancelScheduledSend && !email.isSmimeScheduled && (
<Button variant="ghost" size="sm" onClick={onCancelScheduledSend} className="hidden sm:flex sm:h-8 sm:gap-1.5 sm:py-0" title={t('cancel_scheduled_send')} data-testid="cancel-scheduled-send">
<CalendarX className="w-4 h-4" />
{showToolbarLabels && <span className="hidden sm:inline text-sm">{t('cancel_scheduled_send')}</span>}
</Button>
)}
</>
)}
{!isScheduled && isDraft && onEditDraft && (
@@ -5746,18 +5756,10 @@ export function EmailViewer({
{t('scheduled_banner', { date: email.scheduledSendAt ? formatScheduledSendAt(email.scheduledSendAt, timeFormat) : '' })}
</span>
</div>
<div className="flex flex-wrap items-center gap-2">
{/* The toolbar has these from sm up; on the phone it has no room. */}
<div className="flex flex-wrap items-center gap-2 sm:hidden">
{canCancelScheduled && (
<>
<Button
size="sm"
variant="outline"
onClick={() => void rescheduleScheduled(new Date(Date.now() + 1000).toISOString())}
className="gap-1.5"
>
<Send className="w-3.5 h-3.5" />
{t('send_now')}
</Button>
<Button
size="sm"
variant="outline"
@@ -5773,6 +5775,12 @@ export function EmailViewer({
<EditIcon className="w-3.5 h-3.5" />
{email.isSmimeScheduled ? t('cancel_and_compose_again') : t('cancel_and_edit')}
</Button>
{onCancelScheduledSend && !email.isSmimeScheduled && (
<Button size="sm" variant="outline" onClick={onCancelScheduledSend} className="gap-1.5">
<CalendarX className="w-3.5 h-3.5" />
{t('cancel_scheduled_send')}
</Button>
)}
</>
)}
</div>
@@ -5780,26 +5788,14 @@ export function EmailViewer({
</div>
)}
{/* Draft Banner */}
{isDraft && (
{/* Draft Banner: a scheduled message has its own. */}
{isDraft && !isScheduled && (
<div className="border-b border-border bg-warning/10">
<div className="px-6 py-2.5 flex items-center justify-between">
<div className="flex items-center gap-2 text-warning">
<File className="w-4 h-4" />
<span className="text-sm font-medium">{t('draft_banner')}</span>
</div>
{/* The toolbar already has Edit, except for a scheduled message. */}
{onEditDraft && isScheduled && (
<Button
size="sm"
variant="outline"
onClick={() => onEditDraft()}
className="gap-1.5"
>
<EditIcon className="w-3.5 h-3.5" />
{t('edit_draft')}
</Button>
)}
</div>
</div>
)}
+20 -1
View File
@@ -932,7 +932,11 @@ export function MailApp({ linkSegments: routeSegments }: MailAppProps = {}) {
}
} : undefined,
onDelete: async () => {
if (isScheduledView) return;
// Delete on a scheduled message cancels the send, back into Drafts.
if (isScheduledView) {
if (selectedEmail) await handleCancelScheduledSend(selectedEmail);
return;
}
if (shortcutsTargetSelection && client) {
const currentMailbox = mailboxes.find(m => m.id === selectedMailbox);
const permanent = bulkDeleteIsPermanent(currentMailbox);
@@ -2436,6 +2440,19 @@ export function MailApp({ linkSegments: routeSegments }: MailAppProps = {}) {
}
};
// "Cancel send": nothing goes out, and the message is back in Drafts. An
// S/MIME message can't go back (it is encrypted) and only offers "Cancel
// and compose again".
const handleCancelScheduledSend = async (email: Email) => {
if (email.isSmimeScheduled || email.scheduledUndoStatus !== 'pending') return;
const restored = await cancelScheduledForEdit(email);
if (restored === undefined) return;
toast.success(
tViewer('send_cancelled'),
t('notifications.moved_to_mailbox', { mailbox: t('sidebar.mailboxes.drafts') }),
);
};
useEffect(() => {
if (!pendingUndoSend || !client) return;
if (lastUndoToastSubmissionRef.current === pendingUndoSend.submissionId) return;
@@ -5158,6 +5175,7 @@ export function MailApp({ linkSegments: routeSegments }: MailAppProps = {}) {
if (isMobile) setActiveView('viewer');
}}
onRescheduleScheduled={(email) => setRescheduleTarget(email)}
onCancelScheduledSend={handleCancelScheduledSend}
onEmailSelect={handleEmailSelect}
onOpenSearchResult={handleEmailSelect}
onEmailPrefetch={handleEmailPrefetch}
@@ -5468,6 +5486,7 @@ export function MailApp({ linkSegments: routeSegments }: MailAppProps = {}) {
// Restored through the active login's client (see the list's Cancel and edit).
if (restored) await handleEditDraft(restored, { accountId: selectedEmail.scheduledAccountId, localAccountId: activeAccountId ?? undefined }).catch(reportDraftOpenFailure);
}}
onCancelScheduledSend={() => { if (selectedEmail) void handleCancelScheduledSend(selectedEmail); }}
onRescheduleScheduled={async (delayedUntil) => {
if (client && selectedEmail?.emailSubmissionId && selectedEmail.scheduledIdentityId) {
const result = await rescheduleScheduledEmail(client, selectedEmail.emailSubmissionId, selectedEmail.id, selectedEmail.scheduledIdentityId, delayedUntil, selectedEmail.scheduledAccountId);
+2 -2
View File
@@ -311,8 +311,7 @@
"cancel_scheduled_send": "إلغاء الإرسال",
"reschedule_send": "إعادة الجدولة",
"cancel_and_edit": "إلغاء والتعديل",
"cancel_and_compose_again": "إلغاء وإعادة الكتابة",
"reschedule_prompt": "أدخل تاريخًا/وقتًا جديدًا، مثل 2026-05-04T15:30"
"cancel_and_compose_again": "إلغاء وإعادة الكتابة"
},
"email_viewer": {
"read_receipt": {
@@ -628,6 +627,7 @@
"send_cancelled_restore_failed": "أُلغي الإرسال، لكن تعذّرت إعادة الرسالة إلى المسودات. لا تزال في المرسل.",
"send_cancelled_remove_failed": "أُلغي الإرسال، لكن تعذّرت إزالة الرسالة غير المرسلة من المرسل.",
"cancel_scheduled_failed": "تعذّر إلغاء الإرسال المجدول",
"send_cancelled": "أُلغي الإرسال",
"still_scheduled_at": "لا تزال الرسالة مجدولة للإرسال في {date}.",
"still_scheduled": "لا تزال الرسالة مجدولة.",
"already_sent": "أُرسلت هذه الرسالة بالفعل.",
+2 -2
View File
@@ -311,8 +311,7 @@
"cancel_scheduled_send": "Cancel·la l'enviament",
"reschedule_send": "Reprograma",
"cancel_and_edit": "Cancel·la i edita",
"cancel_and_compose_again": "Cancel·la i torna a redactar",
"reschedule_prompt": "Introduïu una data i hora noves, p. ex. 2026-05-04T15:30"
"cancel_and_compose_again": "Cancel·la i torna a redactar"
},
"email_viewer": {
"read_receipt": {
@@ -628,6 +627,7 @@
"send_cancelled_restore_failed": "S'ha cancel·lat l'enviament, però el missatge no s'ha pogut tornar a Esborranys. Encara és a Enviats.",
"send_cancelled_remove_failed": "S'ha cancel·lat l'enviament, però el missatge no enviat no s'ha pogut treure d'Enviats.",
"cancel_scheduled_failed": "No s'ha pogut cancel·lar l'enviament programat",
"send_cancelled": "S'ha cancel·lat l'enviament",
"still_scheduled_at": "El missatge encara està programat per al {date}.",
"still_scheduled": "El missatge encara està programat.",
"already_sent": "Aquest missatge ja s'ha enviat.",
+2 -2
View File
@@ -311,8 +311,7 @@
"cancel_scheduled_send": "Zrušit odeslání",
"reschedule_send": "Přeplánovat",
"cancel_and_edit": "Zrušit a upravit",
"cancel_and_compose_again": "Zrušit a napsat znovu",
"reschedule_prompt": "Zadejte nové datum/čas, např. 2026-05-04T15:30"
"cancel_and_compose_again": "Zrušit a napsat znovu"
},
"email_viewer": {
"read_receipt": {
@@ -628,6 +627,7 @@
"send_cancelled_restore_failed": "Odeslání bylo zrušeno, ale zprávu se nepodařilo vrátit do Konceptů. Je stále v Odeslaných.",
"send_cancelled_remove_failed": "Odeslání bylo zrušeno, ale neodeslanou zprávu se nepodařilo odstranit z Odeslaných.",
"cancel_scheduled_failed": "Naplánované odeslání se nepodařilo zrušit",
"send_cancelled": "Odeslání zrušeno",
"still_scheduled_at": "Zpráva je stále naplánována na {date}.",
"still_scheduled": "Zpráva je stále naplánována.",
"already_sent": "Tato zpráva již byla odeslána.",
+2 -2
View File
@@ -311,8 +311,7 @@
"cancel_scheduled_send": "Annuller afsendelse",
"reschedule_send": "Planlæg om",
"cancel_and_edit": "Annuller og redigér",
"cancel_and_compose_again": "Annuller og skriv igen",
"reschedule_prompt": "Indtast en ny dato/tid, f.eks. 2026-05-04T15:30"
"cancel_and_compose_again": "Annuller og skriv igen"
},
"email_viewer": {
"read_receipt": {
@@ -628,6 +627,7 @@
"send_cancelled_restore_failed": "Afsendelsen blev annulleret, men beskeden kunne ikke lægges tilbage i Kladder. Den ligger stadig i Sendt.",
"send_cancelled_remove_failed": "Afsendelsen blev annulleret, men den ikke-sendte besked kunne ikke fjernes fra Sendt.",
"cancel_scheduled_failed": "Den planlagte afsendelse kunne ikke annulleres",
"send_cancelled": "Afsendelse annulleret",
"still_scheduled_at": "Beskeden er stadig planlagt til {date}.",
"still_scheduled": "Beskeden er stadig planlagt.",
"already_sent": "Denne besked er allerede sendt.",
+2 -2
View File
@@ -311,8 +311,7 @@
"cancel_scheduled_send": "Senden abbrechen",
"reschedule_send": "Neu planen",
"cancel_and_edit": "Abbrechen und bearbeiten",
"cancel_and_compose_again": "Abbrechen und neu verfassen",
"reschedule_prompt": "Neues Datum/Uhrzeit eingeben, z. B. 2026-05-04T15:30"
"cancel_and_compose_again": "Abbrechen und neu verfassen"
},
"email_viewer": {
"read_receipt": {
@@ -628,6 +627,7 @@
"send_cancelled_restore_failed": "Das Senden wurde abgebrochen, aber die Nachricht konnte nicht zurück in die Entwürfe gelegt werden. Sie liegt noch im Ordner „Gesendet“.",
"send_cancelled_remove_failed": "Das Senden wurde abgebrochen, aber die nicht gesendete Nachricht konnte nicht aus dem Ordner „Gesendet“ entfernt werden.",
"cancel_scheduled_failed": "Das geplante Senden konnte nicht abgebrochen werden",
"send_cancelled": "Senden abgebrochen",
"still_scheduled_at": "Die Nachricht ist weiterhin für {date} geplant.",
"still_scheduled": "Die Nachricht ist weiterhin geplant.",
"already_sent": "Diese Nachricht wurde bereits gesendet.",
+2 -2
View File
@@ -311,8 +311,7 @@
"cancel_scheduled_send": "Cancel send",
"reschedule_send": "Reschedule",
"cancel_and_edit": "Cancel and edit",
"cancel_and_compose_again": "Cancel and compose again",
"reschedule_prompt": "Enter a new date/time, e.g. 2026-05-04T15:30"
"cancel_and_compose_again": "Cancel and compose again"
},
"email_viewer": {
"read_receipt": {
@@ -628,6 +627,7 @@
"send_cancelled_restore_failed": "Sending was cancelled, but the message could not be put back in Drafts. It is still in Sent.",
"send_cancelled_remove_failed": "Sending was cancelled, but the unsent message could not be removed from Sent.",
"cancel_scheduled_failed": "Couldn't cancel the scheduled send",
"send_cancelled": "Sending cancelled",
"still_scheduled_at": "The message is still scheduled for {date}.",
"still_scheduled": "The message is still scheduled.",
"already_sent": "This message was already sent.",
+2 -2
View File
@@ -311,8 +311,7 @@
"cancel_scheduled_send": "Cancelar envío",
"reschedule_send": "Reprogramar",
"cancel_and_edit": "Cancelar y editar",
"cancel_and_compose_again": "Cancelar y redactar de nuevo",
"reschedule_prompt": "Introduce una nueva fecha/hora, p. ej. 2026-05-04T15:30"
"cancel_and_compose_again": "Cancelar y redactar de nuevo"
},
"email_viewer": {
"read_receipt": {
@@ -628,6 +627,7 @@
"send_cancelled_restore_failed": "Se canceló el envío, pero el mensaje no se pudo devolver a Borradores. Sigue en Enviados.",
"send_cancelled_remove_failed": "Se canceló el envío, pero el mensaje no enviado no se pudo quitar de Enviados.",
"cancel_scheduled_failed": "No se pudo cancelar el envío programado",
"send_cancelled": "Envío cancelado",
"still_scheduled_at": "El mensaje sigue programado para el {date}.",
"still_scheduled": "El mensaje sigue programado.",
"already_sent": "Este mensaje ya se envió.",
+2 -2
View File
@@ -311,8 +311,7 @@
"cancel_scheduled_send": "Tühista saatmine",
"reschedule_send": "Ajasta uuesti",
"cancel_and_edit": "Tühista ja muuda",
"cancel_and_compose_again": "Tühista ja koosta uuesti",
"reschedule_prompt": "Sisesta uus kuupäev/kellaaeg, nt 2026-05-04T15:30"
"cancel_and_compose_again": "Tühista ja koosta uuesti"
},
"email_viewer": {
"read_receipt": {
@@ -628,6 +627,7 @@
"send_cancelled_restore_failed": "Saatmine tühistati, kuid kirja ei õnnestunud mustanditesse tagasi panna. See on endiselt kaustas Saadetud.",
"send_cancelled_remove_failed": "Saatmine tühistati, kuid saatmata kirja ei õnnestunud kaustast Saadetud eemaldada.",
"cancel_scheduled_failed": "Ajastatud saatmist ei õnnestunud tühistada",
"send_cancelled": "Saatmine tühistatud",
"still_scheduled_at": "Kiri on endiselt ajastatud ajale {date}.",
"still_scheduled": "Kiri on endiselt ajastatud.",
"already_sent": "See kiri on juba saadetud.",
+2 -2
View File
@@ -311,8 +311,7 @@
"cancel_scheduled_send": "لغو ارسال",
"reschedule_send": "باززمان‌بندی",
"cancel_and_edit": "لغو و ویرایش",
"cancel_and_compose_again": "لغو و نوشتن مجدد",
"reschedule_prompt": "تاریخ/زمان جدید را وارد کنید"
"cancel_and_compose_again": "لغو و نوشتن مجدد"
},
"email_viewer": {
"read_receipt": {
@@ -628,6 +627,7 @@
"send_cancelled_restore_failed": "ارسال لغو شد، اما پیام به پیش‌نویس‌ها بازگردانده نشد. هنوز در ارسال شده است.",
"send_cancelled_remove_failed": "ارسال لغو شد، اما پیام ارسال‌نشده از ارسال شده حذف نشد.",
"cancel_scheduled_failed": "لغو ارسال زمان‌بندی‌شده ممکن نشد",
"send_cancelled": "ارسال لغو شد",
"still_scheduled_at": "پیام همچنان برای {date} زمان‌بندی شده است.",
"still_scheduled": "پیام همچنان زمان‌بندی شده است.",
"already_sent": "این پیام قبلاً ارسال شده است.",
+2 -2
View File
@@ -311,8 +311,7 @@
"cancel_scheduled_send": "Annuler l’envoi",
"reschedule_send": "Replanifier",
"cancel_and_edit": "Annuler et modifier",
"cancel_and_compose_again": "Annuler et rédiger à nouveau",
"reschedule_prompt": "Saisissez une nouvelle date/heure, p. ex. 2026-05-04T15:30"
"cancel_and_compose_again": "Annuler et rédiger à nouveau"
},
"email_viewer": {
"read_receipt": {
@@ -628,6 +627,7 @@
"send_cancelled_restore_failed": "L’envoi a été annulé, mais le message n’a pas pu être remis dans Brouillons. Il est toujours dans Envoyés.",
"send_cancelled_remove_failed": "L’envoi a été annulé, mais le message non envoyé n’a pas pu être retiré d’Envoyés.",
"cancel_scheduled_failed": "Impossible d’annuler l’envoi planifié",
"send_cancelled": "Envoi annulé",
"still_scheduled_at": "Le message reste planifié pour le {date}.",
"still_scheduled": "Le message reste planifié.",
"already_sent": "Ce message a déjà été envoyé.",
+2 -2
View File
@@ -276,8 +276,7 @@
"cancel_scheduled_send": "ביטול שליחה",
"reschedule_send": "תזמן מחדש",
"cancel_and_edit": "בטל וערוך",
"cancel_and_compose_again": "בטל והרכיב שוב",
"reschedule_prompt": "הזן תאריך/שעה חדש, למשל 2026-05-04T15:30"
"cancel_and_compose_again": "בטל והרכיב שוב"
},
"email_viewer": {
"no_email_selected": "לא נבחר אימייל",
@@ -593,6 +592,7 @@
"send_cancelled_restore_failed": "השליחה בוטלה, אך לא ניתן היה להחזיר את ההודעה לטיוטות. היא עדיין בנשלח.",
"send_cancelled_remove_failed": "השליחה בוטלה, אך לא ניתן היה להסיר את ההודעה שלא נשלחה מנשלח.",
"cancel_scheduled_failed": "לא ניתן היה לבטל את השליחה המתוזמנת",
"send_cancelled": "השליחה בוטלה",
"still_scheduled_at": "ההודעה עדיין מתוזמנת ל־{date}.",
"still_scheduled": "ההודעה עדיין מתוזמנת.",
"already_sent": "ההודעה הזו כבר נשלחה.",
+2 -2
View File
@@ -311,8 +311,7 @@
"cancel_scheduled_send": "Küldés megszakítása",
"reschedule_send": "Átütemezés",
"cancel_and_edit": "Mégse és szerkesztés",
"cancel_and_compose_again": "Mégse és újraírás",
"reschedule_prompt": "Adj meg egy új dátumot/időpontot, pl. 2026-05-04T15:30"
"cancel_and_compose_again": "Mégse és újraírás"
},
"email_viewer": {
"read_receipt": {
@@ -628,6 +627,7 @@
"send_cancelled_restore_failed": "A küldés megszakadt, de az üzenetet nem sikerült visszatenni a Piszkozatok közé. Még az Elküldött mappában van.",
"send_cancelled_remove_failed": "A küldés megszakadt, de az el nem küldött üzenetet nem sikerült eltávolítani az Elküldött mappából.",
"cancel_scheduled_failed": "Az ütemezett küldést nem sikerült megszakítani",
"send_cancelled": "Küldés megszakítva",
"still_scheduled_at": "Az üzenet továbbra is ütemezve van: {date}.",
"still_scheduled": "Az üzenet továbbra is ütemezve van.",
"already_sent": "Ez az üzenet már el lett küldve.",
+2 -2
View File
@@ -311,8 +311,7 @@
"cancel_scheduled_send": "Annulla invio",
"reschedule_send": "Riprogramma",
"cancel_and_edit": "Annulla e modifica",
"cancel_and_compose_again": "Annulla e componi di nuovo",
"reschedule_prompt": "Inserisci una nuova data/ora, ad es. 2026-05-04T15:30"
"cancel_and_compose_again": "Annulla e componi di nuovo"
},
"email_viewer": {
"read_receipt": {
@@ -628,6 +627,7 @@
"send_cancelled_restore_failed": "L'invio è stato annullato, ma non è stato possibile riportare il messaggio in Bozze. Si trova ancora in Inviati.",
"send_cancelled_remove_failed": "L'invio è stato annullato, ma non è stato possibile rimuovere il messaggio non inviato da Inviati.",
"cancel_scheduled_failed": "Impossibile annullare l'invio programmato",
"send_cancelled": "Invio annullato",
"still_scheduled_at": "Il messaggio è ancora programmato per il {date}.",
"still_scheduled": "Il messaggio è ancora programmato.",
"already_sent": "Questo messaggio è già stato inviato.",
+2 -2
View File
@@ -311,8 +311,7 @@
"cancel_scheduled_send": "送信をキャンセル",
"reschedule_send": "再予約",
"cancel_and_edit": "キャンセルして編集",
"cancel_and_compose_again": "キャンセルして新規作成",
"reschedule_prompt": "新しい日時を入力してください。例: 2026-05-04T15:30"
"cancel_and_compose_again": "キャンセルして新規作成"
},
"email_viewer": {
"read_receipt": {
@@ -628,6 +627,7 @@
"send_cancelled_restore_failed": "送信はキャンセルされましたが、メッセージを下書きに戻せませんでした。メッセージは送信済みに残っています。",
"send_cancelled_remove_failed": "送信はキャンセルされましたが、未送信のメッセージを送信済みから削除できませんでした。",
"cancel_scheduled_failed": "予約送信をキャンセルできませんでした",
"send_cancelled": "送信を取り消しました",
"still_scheduled_at": "メッセージは引き続き {date} に送信予定です。",
"still_scheduled": "メッセージは引き続き送信予定です。",
"already_sent": "このメッセージはすでに送信されています。",
+2 -2
View File
@@ -311,8 +311,7 @@
"cancel_scheduled_send": "보내기 취소",
"reschedule_send": "다시 예약",
"cancel_and_edit": "취소하고 편집",
"cancel_and_compose_again": "취소하고 다시 작성",
"reschedule_prompt": "새 날짜/시간을 입력하세요. 예: 2026-05-04T15:30"
"cancel_and_compose_again": "취소하고 다시 작성"
},
"email_viewer": {
"read_receipt": {
@@ -628,6 +627,7 @@
"send_cancelled_restore_failed": "보내기가 취소되었지만 메시지를 임시보관함으로 되돌리지 못했습니다. 메시지는 아직 보낸편지함에 있습니다.",
"send_cancelled_remove_failed": "보내기가 취소되었지만 보내지 않은 메시지를 보낸편지함에서 제거하지 못했습니다.",
"cancel_scheduled_failed": "예약된 보내기를 취소하지 못했습니다",
"send_cancelled": "보내기를 취소했어요",
"still_scheduled_at": "메시지는 여전히 {date}에 보내도록 예약되어 있습니다.",
"still_scheduled": "메시지는 여전히 예약되어 있습니다.",
"already_sent": "이 메시지는 이미 보냈습니다.",
+2 -2
View File
@@ -311,8 +311,7 @@
"cancel_scheduled_send": "Atcelt sūtīšanu",
"reschedule_send": "Pārplānot",
"cancel_and_edit": "Atcelt un rediģēt",
"cancel_and_compose_again": "Atcelt un rakstīt no jauna",
"reschedule_prompt": "Ievadiet jaunu datumu/laiku, piem. 2026-05-04T15:30"
"cancel_and_compose_again": "Atcelt un rakstīt no jauna"
},
"email_viewer": {
"read_receipt": {
@@ -628,6 +627,7 @@
"send_cancelled_restore_failed": "Sūtīšana tika atcelta, bet ziņojumu neizdevās atgriezt Melnrakstos. Tas joprojām ir Nosūtītajos.",
"send_cancelled_remove_failed": "Sūtīšana tika atcelta, bet nenosūtīto ziņojumu neizdevās noņemt no Nosūtītajiem.",
"cancel_scheduled_failed": "Ieplānoto sūtīšanu neizdevās atcelt",
"send_cancelled": "Sūtīšana atcelta",
"still_scheduled_at": "Ziņojums joprojām ir ieplānots uz {date}.",
"still_scheduled": "Ziņojums joprojām ir ieplānots.",
"already_sent": "Šis ziņojums jau ir nosūtīts.",
+2 -2
View File
@@ -311,8 +311,7 @@
"cancel_scheduled_send": "Илгээхийг цуцлах",
"reschedule_send": "Хуваарь солих",
"cancel_and_edit": "Цуцлах, засах",
"cancel_and_compose_again": "Цуцлаад дахин бичих",
"reschedule_prompt": "Шинэ огноо/цаг оруулна, ж.нь. 2026-05-04Т15:30"
"cancel_and_compose_again": "Цуцлаад дахин бичих"
},
"email_viewer": {
"read_receipt": {
@@ -628,6 +627,7 @@
"send_cancelled_restore_failed": "Илгээлтийг цуцалсан ч мессежийг Ноорог руу буцааж чадсангүй. Энэ одоо ч Илгээсэн хэсэгт байна.",
"send_cancelled_remove_failed": "Илгээлтийг цуцалсан ч илгээгээгүй мессежийг Илгээсэн хэсгээс устгаж чадсангүй.",
"cancel_scheduled_failed": "Товлосон илгээлтийг цуцалж чадсангүй",
"send_cancelled": "Илгээхийг цуцаллаа",
"still_scheduled_at": "Мессеж {date}-нд илгээгдэхээр товлогдсон хэвээр байна.",
"still_scheduled": "Мессеж товлогдсон хэвээр байна.",
"already_sent": "Энэ мессеж аль хэдийн илгээгдсэн.",
+2 -2
View File
@@ -311,8 +311,7 @@
"cancel_scheduled_send": "Avbryt sending",
"reschedule_send": "Planlegg på nytt",
"cancel_and_edit": "Avbryt og rediger",
"cancel_and_compose_again": "Avbryt og skriv meldingen på nytt",
"reschedule_prompt": "Skriv inn ny dato og klokkeslett, for eksempel 2026-05-04T15:30"
"cancel_and_compose_again": "Avbryt og skriv meldingen på nytt"
},
"email_viewer": {
"read_receipt": {
@@ -628,6 +627,7 @@
"send_cancelled_restore_failed": "Sendingen ble avbrutt, men meldingen kunne ikke legges tilbake i Utkast. Den ligger fortsatt i Sendt.",
"send_cancelled_remove_failed": "Sendingen ble avbrutt, men den usendte meldingen kunne ikke fjernes fra Sendt.",
"cancel_scheduled_failed": "Den planlagte sendingen kunne ikke avbrytes",
"send_cancelled": "Sendingen er avbrutt",
"still_scheduled_at": "Meldingen er fortsatt planlagt til {date}.",
"still_scheduled": "Meldingen er fortsatt planlagt.",
"already_sent": "Denne meldingen er allerede sendt.",
+2 -2
View File
@@ -311,8 +311,7 @@
"cancel_scheduled_send": "Verzenden annuleren",
"reschedule_send": "Opnieuw plannen",
"cancel_and_edit": "Annuleren en bewerken",
"cancel_and_compose_again": "Annuleren en opnieuw opstellen",
"reschedule_prompt": "Voer een nieuwe datum/tijd in, bijv. 2026-05-04T15:30"
"cancel_and_compose_again": "Annuleren en opnieuw opstellen"
},
"email_viewer": {
"read_receipt": {
@@ -628,6 +627,7 @@
"send_cancelled_restore_failed": "Het verzenden is geannuleerd, maar het bericht kon niet terug naar Concepten. Het staat nog in Verzonden.",
"send_cancelled_remove_failed": "Het verzenden is geannuleerd, maar het niet-verzonden bericht kon niet uit Verzonden worden verwijderd.",
"cancel_scheduled_failed": "De geplande verzending kon niet worden geannuleerd",
"send_cancelled": "Verzenden geannuleerd",
"still_scheduled_at": "Het bericht staat nog steeds gepland voor {date}.",
"still_scheduled": "Het bericht staat nog steeds gepland.",
"already_sent": "Dit bericht is al verzonden.",
+2 -2
View File
@@ -311,8 +311,7 @@
"cancel_scheduled_send": "Anuluj wysyłkę",
"reschedule_send": "Przełóż",
"cancel_and_edit": "Anuluj i edytuj",
"cancel_and_compose_again": "Anuluj i napisz ponownie",
"reschedule_prompt": "Wprowadź nową datę/godzinę, np. 2026-05-04T15:30"
"cancel_and_compose_again": "Anuluj i napisz ponownie"
},
"email_viewer": {
"read_receipt": {
@@ -628,6 +627,7 @@
"send_cancelled_restore_failed": "Wysyłka została anulowana, ale nie udało się przenieść wiadomości z powrotem do Szkiców. Nadal jest w Wysłanych.",
"send_cancelled_remove_failed": "Wysyłka została anulowana, ale nie udało się usunąć niewysłanej wiadomości z Wysłanych.",
"cancel_scheduled_failed": "Nie udało się anulować zaplanowanej wysyłki",
"send_cancelled": "Anulowano wysyłanie",
"still_scheduled_at": "Wiadomość jest nadal zaplanowana na {date}.",
"still_scheduled": "Wiadomość jest nadal zaplanowana.",
"already_sent": "Ta wiadomość została już wysłana.",
+2 -2
View File
@@ -311,8 +311,7 @@
"cancel_scheduled_send": "Cancelar envio",
"reschedule_send": "Reagendar",
"cancel_and_edit": "Cancelar e editar",
"cancel_and_compose_again": "Cancelar e escrever novamente",
"reschedule_prompt": "Informe uma nova data/hora, ex. 2026-05-04T15:30"
"cancel_and_compose_again": "Cancelar e escrever novamente"
},
"email_viewer": {
"read_receipt": {
@@ -628,6 +627,7 @@
"send_cancelled_restore_failed": "O envio foi cancelado, mas não foi possível devolver a mensagem para Rascunhos. Ela ainda está em Enviados.",
"send_cancelled_remove_failed": "O envio foi cancelado, mas não foi possível remover a mensagem não enviada de Enviados.",
"cancel_scheduled_failed": "Não foi possível cancelar o envio agendado",
"send_cancelled": "Envio cancelado",
"still_scheduled_at": "A mensagem continua agendada para {date}.",
"still_scheduled": "A mensagem continua agendada.",
"already_sent": "Esta mensagem já foi enviada.",
+2 -2
View File
@@ -311,8 +311,7 @@
"cancel_scheduled_send": "Anulează trimiterea",
"reschedule_send": "Reprogramare",
"cancel_and_edit": "Anulează și editează",
"cancel_and_compose_again": "Anulează și redactează din nou",
"reschedule_prompt": "Introduceți o nouă dată/oră, de exemplu 2026-05-04T15:30"
"cancel_and_compose_again": "Anulează și redactează din nou"
},
"email_viewer": {
"read_receipt": {
@@ -628,6 +627,7 @@
"send_cancelled_restore_failed": "Trimiterea a fost anulată, dar mesajul nu a putut fi mutat înapoi în Ciorne. Se află încă în Trimise.",
"send_cancelled_remove_failed": "Trimiterea a fost anulată, dar mesajul netrimis nu a putut fi eliminat din Trimise.",
"cancel_scheduled_failed": "Trimiterea programată nu a putut fi anulată",
"send_cancelled": "Trimiterea a fost anulată",
"still_scheduled_at": "Mesajul este încă programat pentru {date}.",
"still_scheduled": "Mesajul este încă programat.",
"already_sent": "Acest mesaj a fost deja trimis.",
+2 -2
View File
@@ -311,8 +311,7 @@
"cancel_scheduled_send": "Отменить отправку",
"reschedule_send": "Перенести",
"cancel_and_edit": "Отменить и изменить",
"cancel_and_compose_again": "Отменить и написать заново",
"reschedule_prompt": "Введите новую дату/время, например 2026-05-04T15:30"
"cancel_and_compose_again": "Отменить и написать заново"
},
"email_viewer": {
"read_receipt": {
@@ -628,6 +627,7 @@
"send_cancelled_restore_failed": "Отправка отменена, но вернуть письмо в Черновики не удалось. Оно по-прежнему в Отправленных.",
"send_cancelled_remove_failed": "Отправка отменена, но удалить неотправленное письмо из Отправленных не удалось.",
"cancel_scheduled_failed": "Не удалось отменить запланированную отправку",
"send_cancelled": "Отправка отменена",
"still_scheduled_at": "Письмо по-прежнему запланировано на {date}.",
"still_scheduled": "Письмо по-прежнему запланировано.",
"already_sent": "Это письмо уже отправлено.",
+2 -2
View File
@@ -311,8 +311,7 @@
"cancel_scheduled_send": "Zrušiť odoslanie",
"reschedule_send": "Prenaplánovať",
"cancel_and_edit": "Zrušiť a upraviť",
"cancel_and_compose_again": "Zrušiť a napísať znova",
"reschedule_prompt": "Zadajte nový dátum/čas, napr. 2026-05-04T15:30"
"cancel_and_compose_again": "Zrušiť a napísať znova"
},
"email_viewer": {
"read_receipt": {
@@ -628,6 +627,7 @@
"send_cancelled_restore_failed": "Odoslanie bolo zrušené, ale správu sa nepodarilo vrátiť do Konceptov. Stále je v Odoslaných.",
"send_cancelled_remove_failed": "Odoslanie bolo zrušené, ale neodoslanú správu sa nepodarilo odstrániť z Odoslaných.",
"cancel_scheduled_failed": "Naplánované odoslanie sa nepodarilo zrušiť",
"send_cancelled": "Odoslanie zrušené",
"still_scheduled_at": "Správa je stále naplánovaná na {date}.",
"still_scheduled": "Správa je stále naplánovaná.",
"already_sent": "Táto správa už bola odoslaná.",
+2 -2
View File
@@ -494,8 +494,7 @@
"cancel_scheduled_send": "ยกเลิกการส่ง",
"reschedule_send": "เปลี่ยนเวลาส่งใหม่",
"cancel_and_edit": "ยกเลิกและแก้ไข",
"cancel_and_compose_again": "ยกเลิกและเขียนใหม่",
"reschedule_prompt": "ระบุวันและเวลาใหม่ เช่น 2026-05-04T15:30"
"cancel_and_compose_again": "ยกเลิกและเขียนใหม่"
},
"context_menu": {
"reply": "ตอบกลับ",
@@ -1360,6 +1359,7 @@
"send_cancelled_restore_failed": "ยกเลิกการส่งแล้ว แต่ไม่สามารถนำข้อความกลับไปยังจดหมายร่างได้ ข้อความยังคงอยู่ในโฟลเดอร์ส่งแล้ว",
"send_cancelled_remove_failed": "ยกเลิกการส่งแล้ว แต่ไม่สามารถนำข้อความที่ไม่ได้ส่งออกจากโฟลเดอร์ส่งแล้วได้",
"cancel_scheduled_failed": "ไม่สามารถยกเลิกการกำหนดเวลาส่งได้",
"send_cancelled": "ยกเลิกการส่งแล้ว",
"still_scheduled_at": "ข้อความยังคงมีกำหนดส่งในวันที่ {date}",
"still_scheduled": "ข้อความยังคงมีกำหนดเวลาส่งอยู่",
"already_sent": "ข้อความนี้ถูกส่งออกไปแล้ว",
+2 -2
View File
@@ -311,8 +311,7 @@
"cancel_scheduled_send": "Göndermeyi iptal et",
"reschedule_send": "Yeniden zamanla",
"cancel_and_edit": "İptal et ve düzenle",
"cancel_and_compose_again": "İptal et ve yeniden yaz",
"reschedule_prompt": "Yeni tarih/saat girin, ör. 2026-05-04T15:30"
"cancel_and_compose_again": "İptal et ve yeniden yaz"
},
"email_viewer": {
"read_receipt": {
@@ -628,6 +627,7 @@
"send_cancelled_restore_failed": "Gönderim iptal edildi, ancak ileti Taslaklar'a geri taşınamadı. Hâlâ Gönderilenler'de.",
"send_cancelled_remove_failed": "Gönderim iptal edildi, ancak gönderilmemiş ileti Gönderilenler'den kaldırılamadı.",
"cancel_scheduled_failed": "Zamanlanmış gönderim iptal edilemedi",
"send_cancelled": "Gönderim iptal edildi",
"still_scheduled_at": "İleti hâlâ {date} için zamanlanmış durumda.",
"still_scheduled": "İleti hâlâ zamanlanmış durumda.",
"already_sent": "Bu ileti zaten gönderildi.",
+2 -2
View File
@@ -311,8 +311,7 @@
"cancel_scheduled_send": "Скасувати надсилання",
"reschedule_send": "Перенести",
"cancel_and_edit": "Скасувати й редагувати",
"cancel_and_compose_again": "Скасувати й написати знову",
"reschedule_prompt": "Введіть нову дату/час, напр. 2026-05-04T15:30"
"cancel_and_compose_again": "Скасувати й написати знову"
},
"email_viewer": {
"read_receipt": {
@@ -628,6 +627,7 @@
"send_cancelled_restore_failed": "Надсилання скасовано, але повернути лист до Чернеток не вдалося. Він досі в Надісланих.",
"send_cancelled_remove_failed": "Надсилання скасовано, але видалити ненадісланий лист із Надісланих не вдалося.",
"cancel_scheduled_failed": "Не вдалося скасувати заплановане надсилання",
"send_cancelled": "Надсилання скасовано",
"still_scheduled_at": "Лист і далі заплановано на {date}.",
"still_scheduled": "Лист і далі заплановано.",
"already_sent": "Цей лист уже надіслано.",
+2 -2
View File
@@ -311,8 +311,7 @@
"cancel_scheduled_send": "取消傳送",
"reschedule_send": "重新排程",
"cancel_and_edit": "取消並編輯",
"cancel_and_compose_again": "取消並重新撰寫",
"reschedule_prompt": "輸入新的日期與時間,例如 2026-05-04T15:30"
"cancel_and_compose_again": "取消並重新撰寫"
},
"email_viewer": {
"read_receipt": {
@@ -628,6 +627,7 @@
"send_cancelled_restore_failed": "已取消傳送,但無法將郵件放回草稿。郵件仍在「寄件備份」中。",
"send_cancelled_remove_failed": "已取消傳送,但無法從「寄件備份」移除未寄出的郵件。",
"cancel_scheduled_failed": "無法取消排定的傳送",
"send_cancelled": "已取消傳送",
"still_scheduled_at": "郵件仍排定於 {date} 傳送。",
"still_scheduled": "郵件仍在排定傳送中。",
"already_sent": "此郵件已寄出。",
+2 -2
View File
@@ -311,8 +311,7 @@
"cancel_scheduled_send": "取消发送",
"reschedule_send": "重新计划",
"cancel_and_edit": "取消并编辑",
"cancel_and_compose_again": "取消并重新撰写",
"reschedule_prompt": "输入新的日期/时间,例如 2026-05-04T15:30"
"cancel_and_compose_again": "取消并重新撰写"
},
"email_viewer": {
"read_receipt": {
@@ -628,6 +627,7 @@
"send_cancelled_restore_failed": "已取消发送,但无法将邮件放回草稿。它仍在“已发送”中。",
"send_cancelled_remove_failed": "已取消发送,但无法从“已发送”中移除未发送的邮件。",
"cancel_scheduled_failed": "无法取消定时发送",
"send_cancelled": "已取消发送",
"still_scheduled_at": "邮件仍计划于 {date} 发送。",
"still_scheduled": "邮件仍处于定时发送状态。",
"already_sent": "此邮件已发送。",