feat: list To and Cc in the header of a forwarded message

This commit is contained in:
Linus Rath
2026-11-04 07:34:29 +01:00
parent e6f8c7a241
commit 48df0db1af
34 changed files with 203 additions and 35 deletions
@@ -379,3 +379,39 @@ describe('reply quote attribution markers', () => {
expect(html).toContain('<p data-reply-quote-start="true"><b>From:</b> Bob</p>');
});
});
// The forwarded header had From, Date and Subject only; Gmail and Outlook
// also say who the mail went to.
describe('forwarded header recipients', () => {
afterEach(() => {
useSettingsStore.setState({ plainTextMode: false, signaturePosition: 'below_quote' });
setIdentities([ME]);
});
const FORWARDED = {
...REPLY_TO,
to: [{ name: 'Anna Becker', email: 'anna@example.com' }, { email: 'team@example.com' }],
cc: [{ name: 'Carol', email: 'carol@example.com' }],
};
it('lists To and Cc in a plain text forward', async () => {
useSettingsStore.setState({ plainTextMode: true });
const onSend = vi.fn();
render(<EmailComposer mode="forward" replyTo={FORWARDED} prefill={{ to: ['dave@example.com'] }} onSend={onSend} />);
const sent = await send(onSend);
expect(sent.body).toContain('subject_label: Question\nforward_to_label: Anna Becker <anna@example.com>, team@example.com\nforward_cc_label: Carol <carol@example.com>\n\nOriginal text');
});
// escapeHtml is mocked to pass values through in this file.
it('lists To and Cc in an HTML forward', () => {
render(<EmailComposer mode="forward" replyTo={FORWARDED} />);
const html = screen.getByTestId('rich-text-editor').getAttribute('data-html')!;
expect(html).toContain('subject_label: Question<br>forward_to_label: Anna Becker <anna@example.com>, team@example.com<br>forward_cc_label: Carol <carol@example.com><br><br>');
});
it('leaves out a Cc line when nobody was copied', () => {
render(<EmailComposer mode="forward" replyTo={{ ...FORWARDED, cc: [] }} />);
const html = screen.getByTestId('rich-text-editor').getAttribute('data-html')!;
expect(html).toContain('forward_to_label: ');
expect(html).not.toContain('forward_cc_label');
});
});
+7 -3
View File
@@ -53,6 +53,7 @@ import { plainOwnRange, readOwnContent, readSelection as readEditorSelection, re
import { actionsFor, runAction } from "@/lib/plugin-sandbox/actions";
import { matchesCombo } from "@/lib/plugin-sandbox/shortcuts";
import { cn, formatFileSize, formatDateTime, generateUUID } from "@/lib/utils";
import { forwardRecipientLines } from "@/lib/quote-header";
import { ScheduleSendDialog } from "./schedule-send-dialog";
import { debug } from "@/lib/debug";
import { toast } from "@/stores/toast-store";
@@ -839,6 +840,7 @@ export function EmailComposer({
? `${from.name} <${from.email}>`
: (from.email || from.name || tCommon('unknown')))
: tCommon('unknown');
const forwardLines = forwardRecipientLines(replyTo, { toLabel: tWords('forward_to_label'), ccLabel: tWords('forward_cc_label') });
const originalText = replyTo.body || (replyTo.htmlBody ? htmlToPlainText(replyTo.htmlBody) : '');
const quotedText = originalText.split('\n').map(line => `> ${line}`).join('\n');
@@ -859,7 +861,7 @@ export function EmailComposer({
}
if (mode === 'forward') {
return `${prefix}${signatureBlock}\n\n${tQuote('forwarded_separator')}\n${tQuote('from_label')}: ${fromStrFull}\n${tQuote('date_label')}: ${date}\n${tQuote('subject_label')}: ${replyTo.subject || ''}\n\n${originalText}`;
return `${prefix}${signatureBlock}\n\n${tQuote('forwarded_separator')}\n${tQuote('from_label')}: ${fromStrFull}\n${tQuote('date_label')}: ${date}\n${tQuote('subject_label')}: ${replyTo.subject || ''}${forwardLines.map((line) => `\n${line.label}: ${line.value}`).join('')}\n\n${originalText}`;
} else if (mode === 'reply' || mode === 'replyAll') {
return `${prefix}${signatureBlock}\n\n${tQuote('reply_line', { date, from: fromStrFull })}\n${quotedText}`;
}
@@ -889,6 +891,8 @@ export function EmailComposer({
? `${from.name} <${from.email}>`
: (from.email || from.name || tCommon('unknown')))
: tCommon('unknown');
const forwardLinesHtml = forwardRecipientLines(replyTo, { toLabel: tWords('forward_to_label'), ccLabel: tWords('forward_cc_label') })
.map((line) => `${line.label}: ${escapeHtml(line.value)}<br>`).join('');
// Like a new mail, an empty line above the signature gives the cursor a
// place for the reply. Without it the reply started on the "-- " line (#1503).
@@ -937,7 +941,7 @@ export function EmailComposer({
// HTML-escape user-controlled values: an unescaped sender "Name <email>"
// has its "<email>" eaten as a bogus HTML tag by the rich-text editor (#482).
const quoteHeader = mode === 'forward'
? `${tQuote('forwarded_separator')}<br>${tQuote('from_label')}: ${escapeHtml(fromStrFull)}<br>${tQuote('date_label')}: ${escapeHtml(date)}<br>${tQuote('subject_label')}: ${escapeHtml(replyTo.subject || '')}<br><br>`
? `${tQuote('forwarded_separator')}<br>${tQuote('from_label')}: ${escapeHtml(fromStrFull)}<br>${tQuote('date_label')}: ${escapeHtml(date)}<br>${tQuote('subject_label')}: ${escapeHtml(replyTo.subject || '')}<br>${forwardLinesHtml}<br>`
: `${tQuote('reply_line', { date: escapeHtml(date), from: escapeHtml(fromStrFull) })}<br>`;
// Embed the original as a QuotedHtml island (verbatim, schema-free) so
// its layout survives the editor round-trip. Sanitize first to strip
@@ -953,7 +957,7 @@ export function EmailComposer({
if (replyTo.body) {
const escapedOriginal = replyTo.body.replace(/&/g, '&amp;').replace(/</g, '&lt;').replace(/>/g, '&gt;').replace(/\n/g, '<br>');
if (mode === 'forward') {
return `${head}<br><br>${tQuote('forwarded_separator')}<br>${tQuote('from_label')}: ${escapeHtml(fromStrFull)}<br>${tQuote('date_label')}: ${escapeHtml(date)}<br>${tQuote('subject_label')}: ${escapeHtml(replyTo.subject || '')}<br><br>${escapedOriginal}`;
return `${head}<br><br>${tQuote('forwarded_separator')}<br>${tQuote('from_label')}: ${escapeHtml(fromStrFull)}<br>${tQuote('date_label')}: ${escapeHtml(date)}<br>${tQuote('subject_label')}: ${escapeHtml(replyTo.subject || '')}<br>${forwardLinesHtml}<br>${escapedOriginal}`;
} else if (mode === 'reply' || mode === 'replyAll') {
return `${head}<br><br>${replyHeader(`<p>${tQuote('reply_line', { date: escapeHtml(date), from: escapeHtml(fromStrFull) })}<br></p>`)}<blockquote style="margin:0 0 0 0.8ex;border-left:2px solid #ccc;padding-left:1ex">${escapedOriginal}</blockquote>`;
}
+32
View File
@@ -90,3 +90,35 @@ describe('buildQuoteHeader (#482 — sender address survives HTML rendering)', (
expect(h.text).not.toContain('<noname@x.tld>');
});
});
// Gmail and Outlook list who the forwarded mail went to.
describe('buildQuoteHeader forward recipients', () => {
const email = {
from: [sender],
to: [{ name: 'Anna Becker', email: 'anna@example.com' }, { email: 'team@example.com' }],
cc: [{ name: 'Carol <x>', email: 'carol@example.com' }],
subject: 'Hello',
receivedAt: '2026-01-01T10:00:00Z',
};
it('adds To and Cc lines after the subject', async () => {
const h = await buildQuoteHeader({ mode: 'forward', email, ...base });
expect(h.text).toContain('Subject: Hello\nTo: Anna Becker <anna@example.com>, team@example.com\nCc: Carol <x> <carol@example.com>\n');
expect(h.html).toContain('To: Anna Becker &lt;anna@example.com&gt;, team@example.com<br>Cc: Carol &lt;x&gt; &lt;carol@example.com&gt;<br>');
});
it('uses the caller\'s To and Cc labels', async () => {
const labels = {
formatReplyLine: () => '', forwardedSeparator: '--', fromLabel: 'Von', dateLabel: 'Datum', subjectLabel: 'Betreff',
toLabel: 'An', ccLabel: 'Kopie',
};
const h = await buildQuoteHeader({ mode: 'forward', email, labels, ...base });
expect(h.text).toContain('\nAn: Anna Becker <anna@example.com>, team@example.com\nKopie: Carol <x> <carol@example.com>\n');
});
it('leaves out a line for an empty list', async () => {
const h = await buildQuoteHeader({ mode: 'forward', email: { ...email, cc: [] }, ...base });
expect(h.text).toContain('\nTo: ');
expect(h.text).not.toContain('Cc:');
});
});
+38 -2
View File
@@ -20,6 +20,36 @@ export interface QuoteHeaderLabels {
fromLabel: string;
dateLabel: string;
subjectLabel: string;
/** The forward header's To and Cc lines; left out when not given. */
toLabel?: string;
ccLabel?: string;
}
type HeaderAddress = { email?: string; name?: string };
/** "Name <address>, address", as the forward header's From line writes one. */
function formatAddressList(list: HeaderAddress[] | undefined): string {
return (list ?? [])
.filter((r) => r.email)
.map((r) => (r.name && r.name !== r.email ? `${r.name} <${r.email}>` : r.email as string))
.join(", ");
}
/**
* The To and Cc lines of a forward header, as Gmail and Outlook write them:
* one per list that has anyone on it, for a label the caller gave. Values
* are plain text; escape them for HTML.
*/
export function forwardRecipientLines(
email: { to?: HeaderAddress[]; cc?: HeaderAddress[] },
labels: { toLabel?: string; ccLabel?: string },
): { label: string; value: string }[] {
const lines: { label: string; value: string }[] = [];
const to = formatAddressList(email.to);
const cc = formatAddressList(email.cc);
if (labels.toLabel && to) lines.push({ label: labels.toLabel, value: to });
if (labels.ccLabel && cc) lines.push({ label: labels.ccLabel, value: cc });
return lines;
}
interface BuildArgs {
@@ -49,6 +79,8 @@ const DEFAULT_LABELS: QuoteHeaderLabels = {
fromLabel: "From",
dateLabel: "Date",
subjectLabel: "Subject",
toLabel: "To",
ccLabel: "Cc",
};
function defaultHeader(args: BuildArgs): QuoteHeader {
@@ -73,12 +105,16 @@ function defaultHeader(args: BuildArgs): QuoteHeader {
const subject = email.subject || "";
if (mode === "forward") {
const text = `${labels.forwardedSeparator}\n${labels.fromLabel}: ${fromStrFull}\n${labels.dateLabel}: ${date}\n${labels.subjectLabel}: ${subject}\n`;
const recipients = forwardRecipientLines(email, labels);
const text = `${labels.forwardedSeparator}\n${labels.fromLabel}: ${fromStrFull}\n${labels.dateLabel}: ${date}\n${labels.subjectLabel}: ${subject}\n`
+ recipients.map((line) => `${line.label}: ${line.value}\n`).join("");
// Escape the interpolated values for the HTML variant: the sender string is
// "Name <email>", and the unescaped "<email>" would be parsed as an HTML tag
// by the rich-text composer and silently dropped (#482). Subject/name are
// likewise user-controlled. Label/separator strings are trusted i18n text.
const html = `<div>${labels.forwardedSeparator}<br>${labels.fromLabel}: ${escapeHtml(fromStrFull)}<br>${labels.dateLabel}: ${escapeHtml(date)}<br>${labels.subjectLabel}: ${escapeHtml(subject)}<br><br></div>`;
const html = `<div>${labels.forwardedSeparator}<br>${labels.fromLabel}: ${escapeHtml(fromStrFull)}<br>${labels.dateLabel}: ${escapeHtml(date)}<br>${labels.subjectLabel}: ${escapeHtml(subject)}<br>`
+ recipients.map((line) => `${line.label}: ${escapeHtml(line.value)}<br>`).join("")
+ `<br></div>`;
return { html, text, wrapInBlockquote: false };
}
+3 -1
View File
@@ -6398,7 +6398,9 @@
"encrypt": "تشفير…",
"encrypt_hint": "يتطلب إضافة S/MIME أو PGP. يفتح الإعدادات.",
"quick_reply_to": "الرد على {names}…",
"open_full_editor": "فتح المحرر الكامل"
"open_full_editor": "فتح المحرر الكامل",
"forward_to_label": "إلى",
"forward_cc_label": "نسخة"
},
"plugin_list": {
"filters": "المرشحات",
+3 -1
View File
@@ -6398,7 +6398,9 @@
"encrypt": "Xifra…",
"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"
"open_full_editor": "Obre l'editor complet",
"forward_to_label": "Per a",
"forward_cc_label": "CC"
},
"plugin_list": {
"filters": "Filtres",
+3 -1
View File
@@ -6398,7 +6398,9 @@
"encrypt": "Šifrovat…",
"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"
"open_full_editor": "Otevřít v plném editoru",
"forward_to_label": "Komu",
"forward_cc_label": "Kopie"
},
"plugin_list": {
"filters": "Filtry",
+3 -1
View File
@@ -6398,7 +6398,9 @@
"encrypt": "Krypter…",
"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"
"open_full_editor": "Åbn fuld editor",
"forward_to_label": "Til",
"forward_cc_label": "Cc"
},
"plugin_list": {
"filters": "Filtre",
+3 -1
View File
@@ -6398,7 +6398,9 @@
"encrypt": "Verschlüsseln…",
"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"
"open_full_editor": "Im vollen Editor öffnen",
"forward_to_label": "An",
"forward_cc_label": "Cc"
},
"plugin_list": {
"filters": "Filter",
+3 -1
View File
@@ -6398,7 +6398,9 @@
"encrypt": "Encrypt…",
"encrypt_hint": "Needs an S/MIME or PGP add-on. Opens Settings.",
"quick_reply_to": "Reply to {names}…",
"open_full_editor": "Open full editor"
"open_full_editor": "Open full editor",
"forward_to_label": "To",
"forward_cc_label": "Cc"
},
"plugin_list": {
"filters": "Filters",
+3 -1
View File
@@ -6398,7 +6398,9 @@
"encrypt": "Cifrar…",
"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"
"open_full_editor": "Abrir el editor completo",
"forward_to_label": "Para",
"forward_cc_label": "CC"
},
"plugin_list": {
"filters": "Filtros",
+3 -1
View File
@@ -6398,7 +6398,9 @@
"encrypt": "Krüpteeri…",
"encrypt_hint": "Vajab S/MIME või PGP lisa. Avab seaded.",
"quick_reply_to": "Vastus saajale {names}…",
"open_full_editor": "Ava täielik redaktor"
"open_full_editor": "Ava täielik redaktor",
"forward_to_label": "Saajale",
"forward_cc_label": "Koopia"
},
"plugin_list": {
"filters": "Filtrid",
+3 -1
View File
@@ -6398,7 +6398,9 @@
"encrypt": "رمزگذاری…",
"encrypt_hint": "به افزونهٔ S/MIME یا PGP نیاز دارد. تنظیمات را باز می‌کند.",
"quick_reply_to": "پاسخ به {names}…",
"open_full_editor": "باز کردن ویرایشگر کامل"
"open_full_editor": "باز کردن ویرایشگر کامل",
"forward_to_label": "به",
"forward_cc_label": "رونوشت"
},
"plugin_list": {
"filters": "فیلترها",
+3 -1
View File
@@ -6398,7 +6398,9 @@
"encrypt": "Chiffrer…",
"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"
"open_full_editor": "Ouvrir l'éditeur complet",
"forward_to_label": "À",
"forward_cc_label": "Cc"
},
"plugin_list": {
"filters": "Filtres",
+3 -1
View File
@@ -6399,7 +6399,9 @@
"encrypt": "הצפנה…",
"encrypt_hint": "דורש תוסף S/MIME או PGP. פותח את ההגדרות.",
"quick_reply_to": "תשובה אל {names}…",
"open_full_editor": "פתיחה בעורך המלא"
"open_full_editor": "פתיחה בעורך המלא",
"forward_to_label": "אל",
"forward_cc_label": "עותק"
},
"plugin_list": {
"filters": "מסננים",
+3 -1
View File
@@ -6398,7 +6398,9 @@
"encrypt": "Titkosítás…",
"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"
"open_full_editor": "Megnyitás a teljes szerkesztőben",
"forward_to_label": "Címzett",
"forward_cc_label": "Másolat"
},
"plugin_list": {
"filters": "Szűrők",
+3 -1
View File
@@ -6398,7 +6398,9 @@
"encrypt": "Cifra…",
"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"
"open_full_editor": "Apri l'editor completo",
"forward_to_label": "A",
"forward_cc_label": "Cc"
},
"plugin_list": {
"filters": "Filtri",
+3 -1
View File
@@ -6398,7 +6398,9 @@
"encrypt": "暗号化…",
"encrypt_hint": "S/MIME または PGP のアドオンが必要です。設定を開きます。",
"quick_reply_to": "{names} に返信…",
"open_full_editor": "フルエディターで開く"
"open_full_editor": "フルエディターで開く",
"forward_to_label": "宛先",
"forward_cc_label": "CC"
},
"plugin_list": {
"filters": "フィルター",
+3 -1
View File
@@ -6398,7 +6398,9 @@
"encrypt": "암호화…",
"encrypt_hint": "S/MIME 또는 PGP 추가 기능이 필요합니다. 설정을 엽니다.",
"quick_reply_to": "{names}에게 답장…",
"open_full_editor": "전체 편집기에서 열기"
"open_full_editor": "전체 편집기에서 열기",
"forward_to_label": "받는 사람",
"forward_cc_label": "참조"
},
"plugin_list": {
"filters": "필터",
+3 -1
View File
@@ -6398,7 +6398,9 @@
"encrypt": "Šifrēt…",
"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ā"
"open_full_editor": "Atvērt pilnajā redaktorā",
"forward_to_label": "Kam",
"forward_cc_label": "Kopija"
},
"plugin_list": {
"filters": "Filtri",
+3 -1
View File
@@ -6398,7 +6398,9 @@
"encrypt": "Шифрлэх…",
"encrypt_hint": "S/MIME эсвэл PGP нэмэлт хэрэгтэй. Тохиргоог нээнэ.",
"quick_reply_to": "{names} руу хариу бичих…",
"open_full_editor": "Бүтэн засварлагчид нээх"
"open_full_editor": "Бүтэн засварлагчид нээх",
"forward_to_label": "Хэнд",
"forward_cc_label": "Хуулбар"
},
"plugin_list": {
"filters": "Шүүлтүүр",
+3 -1
View File
@@ -6398,7 +6398,9 @@
"encrypt": "Krypter…",
"encrypt_hint": "Krever et S/MIME- eller PGP-tillegg. Åpner Innstillinger.",
"quick_reply_to": "Svar til {names}…",
"open_full_editor": "Åpne i full redigering"
"open_full_editor": "Åpne i full redigering",
"forward_to_label": "Til",
"forward_cc_label": "Kopi"
},
"plugin_list": {
"filters": "Filtre",
+3 -1
View File
@@ -6398,7 +6398,9 @@
"encrypt": "Versleutelen…",
"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"
"open_full_editor": "Openen in volledige editor",
"forward_to_label": "Aan",
"forward_cc_label": "CC"
},
"plugin_list": {
"filters": "Filters",
+3 -1
View File
@@ -6398,7 +6398,9 @@
"encrypt": "Zaszyfruj…",
"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"
"open_full_editor": "Otwórz w pełnym edytorze",
"forward_to_label": "Do",
"forward_cc_label": "DW"
},
"plugin_list": {
"filters": "Filtry",
+3 -1
View File
@@ -6398,7 +6398,9 @@
"encrypt": "Cifrar…",
"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"
"open_full_editor": "Abrir no editor completo",
"forward_to_label": "Para",
"forward_cc_label": "Cc"
},
"plugin_list": {
"filters": "Filtros",
+3 -1
View File
@@ -6398,7 +6398,9 @@
"encrypt": "Criptează…",
"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"
"open_full_editor": "Deschide în editorul complet",
"forward_to_label": "Către",
"forward_cc_label": "Cc"
},
"plugin_list": {
"filters": "Filtre",
+3 -1
View File
@@ -6398,7 +6398,9 @@
"encrypt": "Зашифровать…",
"encrypt_hint": "Нужно дополнение S/MIME или PGP. Открывает настройки.",
"quick_reply_to": "Ответ для {names}…",
"open_full_editor": "Открыть в полном редакторе"
"open_full_editor": "Открыть в полном редакторе",
"forward_to_label": "Кому",
"forward_cc_label": "Копия"
},
"plugin_list": {
"filters": "Фильтры",
+3 -1
View File
@@ -6398,7 +6398,9 @@
"encrypt": "Šifrovať…",
"encrypt_hint": "Vyžaduje doplnok S/MIME alebo PGP. Otvorí Nastavenia.",
"quick_reply_to": "Odpoveď pre {names}…",
"open_full_editor": "Otvoriť v plnom editore"
"open_full_editor": "Otvoriť v plnom editore",
"forward_to_label": "Komu",
"forward_cc_label": "Kópia"
},
"plugin_list": {
"filters": "Filtre",
+3 -1
View File
@@ -3319,7 +3319,9 @@
"encrypt": "เข้ารหัส…",
"encrypt_hint": "ต้องใช้ส่วนเสริม S/MIME หรือ PGP จะเปิดการตั้งค่า",
"quick_reply_to": "ตอบกลับถึง {names}…",
"open_full_editor": "เปิดในตัวแก้ไขเต็มรูปแบบ"
"open_full_editor": "เปิดในตัวแก้ไขเต็มรูปแบบ",
"forward_to_label": "ถึง",
"forward_cc_label": "สำเนา"
},
"plugin_list": {
"filters": "ตัวกรอง",
+3 -1
View File
@@ -6398,7 +6398,9 @@
"encrypt": "Şifrele…",
"encrypt_hint": "S/MIME veya PGP eklentisi gerekir. Ayarlar'ı açar.",
"quick_reply_to": "Yanıtla: {names}…",
"open_full_editor": "Tam düzenleyicide aç"
"open_full_editor": "Tam düzenleyicide aç",
"forward_to_label": "Kime",
"forward_cc_label": "Cc"
},
"plugin_list": {
"filters": "Filtreler",
+3 -1
View File
@@ -6398,7 +6398,9 @@
"encrypt": "Зашифрувати…",
"encrypt_hint": "Потрібен додаток S/MIME або PGP. Відкриває налаштування.",
"quick_reply_to": "Відповідь для {names}…",
"open_full_editor": "Відкрити в повному редакторі"
"open_full_editor": "Відкрити в повному редакторі",
"forward_to_label": "Кому",
"forward_cc_label": "Копія"
},
"plugin_list": {
"filters": "Фільтри",
+3 -1
View File
@@ -6410,7 +6410,9 @@
"encrypt": "Mã hóa…",
"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 đủ"
"open_full_editor": "Mở trong trình soạn thảo đầy đủ",
"forward_to_label": "Đến",
"forward_cc_label": "CC"
},
"plugin_list": {
"filters": "Bộ lọc",
+3 -1
View File
@@ -6398,7 +6398,9 @@
"encrypt": "加密…",
"encrypt_hint": "需要 S/MIME 或 PGP 附加元件。將開啟設定。",
"quick_reply_to": "回覆給 {names}…",
"open_full_editor": "在完整編輯器中開啟"
"open_full_editor": "在完整編輯器中開啟",
"forward_to_label": "收件者",
"forward_cc_label": "副本"
},
"plugin_list": {
"filters": "篩選",
+3 -1
View File
@@ -6398,7 +6398,9 @@
"encrypt": "加密…",
"encrypt_hint": "需要 S/MIME 或 PGP 附加组件。将打开设置。",
"quick_reply_to": "回复 {names}…",
"open_full_editor": "在完整编辑器中打开"
"open_full_editor": "在完整编辑器中打开",
"forward_to_label": "收件人",
"forward_cc_label": "抄送"
},
"plugin_list": {
"filters": "筛选",