fix: show attachment upload progress and failures in words for screen readers

This commit is contained in:
Linus Rath
2026-10-29 17:53:41 +01:00
parent 447a3fa474
commit 4f35aa13fa
31 changed files with 196 additions and 32 deletions
@@ -68,9 +68,10 @@ vi.mock('@/stores/settings-store', () => {
return { useSettingsStore: hook };
});
const auth = vi.hoisted(() => ({ client: null as unknown }));
vi.mock('@/stores/auth-store', () => {
const state = {
client: null,
get client() { return auth.client; },
identities: [],
primaryIdentity: null,
isAuthenticated: false,
@@ -151,6 +152,9 @@ vi.mock('@/lib/plugin-hooks', () => ({
onTemplateApply: { size: 0, emit: async () => {} },
},
emailHooks: {
onBeforeAttachmentUpload: { intercept: async () => true },
onBeforeBlobUpload: { transform: async (id: unknown) => id },
onAfterAttachmentUpload: { emit: () => {} },
onComposerOpen: { call: async () => [] },
onRecipientChange: { call: async () => [] },
getRecipientSuggestions: { call: async () => [] },
@@ -165,6 +169,7 @@ vi.mock('@/lib/plugin-hooks', () => ({
search: { call: async () => [] },
onProvideRecipientSuggestions: { transform: async (initial: unknown) => initial },
},
isExternalAttachmentResult: () => false,
}));
vi.mock('@/lib/email-sanitization', () => ({
@@ -175,6 +180,16 @@ vi.mock('@/lib/email-sanitization', () => ({
escapeHtml: (v: string) => v,
parseHtmlSafely: (html: string) => new DOMParser().parseFromString(html, 'text/html'),
}));
const progress = vi.hoisted(() => ({ report: null as null | ((loaded: number, total: number) => void) }));
vi.mock('@/lib/upload-progress', () => ({
onUploadProgress: (_id: string, report: (loaded: number, total: number) => void) => {
progress.report = report;
return () => {};
},
}));
vi.mock('@/lib/plugin-storage', () => ({
fileStorage: { saveFile: async () => {}, getFile: async () => null, deleteFile: async () => {} },
}));
vi.mock('@/lib/email-threading', () => ({
computeReplyThreadingHeaders: () => ({ inReplyTo: [], references: [] }),
}));
@@ -205,8 +220,27 @@ const toInput = () => screen.getByPlaceholderText('to_placeholder') as HTMLInput
beforeEach(() => {
Object.assign(settings, DEFAULT_SETTINGS);
auth.client = null;
});
/** A client whose upload is held: report progress, then fail or never finish. */
function uploadClient(upload: () => Promise<{ blobId: string }>) {
auth.client = {
uploadBlob: vi.fn(upload),
hasDelayedSend: () => false,
getMaxDelayedSend: () => 0,
};
}
async function attach(file: File) {
const input = document.querySelector('input[type="file"]') as HTMLInputElement;
await act(async () => {
fireEvent.change(input, { target: { files: [file] } });
await Promise.resolve();
await new Promise((r) => setTimeout(r, 0));
});
}
describe('recipient fields (SR-06, CV-02)', () => {
it('names the To input by its visible label, also once it holds chips', () => {
render(<EmailComposer initialData={{ ...DRAFT, to: 'Anna Berg <anna@example.com>' }} />);
@@ -346,3 +380,27 @@ describe('message body (SR-07)', () => {
expect(error).toHaveTextContent('validation.body_required');
});
});
describe('attachment uploads (SR-16)', () => {
it('shows upload progress as a named progress bar with a text value', async () => {
uploadClient(() => new Promise(() => {}));
render(<EmailComposer initialData={DRAFT} />);
await attach(new File(['%PDF'], 'report.pdf', { type: 'application/pdf' }));
const bar = screen.getByRole('progressbar', { name: 'report.pdf' });
expect(bar).toHaveAttribute('aria-valuetext', 'attachment_uploading');
act(() => progress.report?.(45, 100));
expect(bar).toHaveAttribute('aria-valuenow', '45');
expect(bar).toHaveAttribute('aria-valuetext', 'attachment_progress:45');
});
it('says in words that an upload failed and announces it', async () => {
uploadClient(() => Promise.reject(new Error('boom')));
render(<EmailComposer initialData={DRAFT} />);
await attach(new File(['%PDF'], 'report.pdf', { type: 'application/pdf' }));
await waitFor(() => expect(screen.getByText('attachment_upload_failed')).toBeInTheDocument());
expect(screen.queryByRole('progressbar')).toBeNull();
expect(liveRegion()).toHaveTextContent('upload_failed:report.pdf');
});
});
+21 -2
View File
@@ -2267,6 +2267,7 @@ export function EmailComposer({
if (controller?.signal.aborted) continue;
debug.error(`Failed to upload ${file.name}:`, error);
toast.error(t('upload_failed', { filename: file.name }));
announce(t('upload_failed', { filename: file.name }));
setAttachments(prev =>
prev.map(att =>
@@ -2277,7 +2278,7 @@ export function EmailComposer({
);
}
}
}, [client, t, plainTextMode]);
}, [client, t, plainTextMode, announce]);
const handleImageUpload = useCallback(async (
file: File,
@@ -4499,6 +4500,14 @@ export function EmailComposer({
<span className="text-xs text-muted-foreground whitespace-nowrap">
({formatFileSize(att.size)})
</span>
{/* State in words, not only a tint or a bar. */}
{att.error ? (
<span className="text-xs font-medium whitespace-nowrap">{t('attachment_upload_failed')}</span>
) : att.uploading ? (
<span className="text-xs text-muted-foreground whitespace-nowrap tabular-nums" aria-hidden>
{typeof att.progress === 'number' ? `${att.progress}%` : t('attachment_uploading')}
</span>
) : null}
</>
);
return (
@@ -4510,7 +4519,17 @@ export function EmailComposer({
)}
>
{att.uploading && (
<div className="absolute inset-0 pointer-events-none">
<div
className="absolute inset-0 pointer-events-none"
role="progressbar"
aria-label={att.name}
aria-valuemin={0}
aria-valuemax={100}
aria-valuenow={typeof att.progress === 'number' ? att.progress : undefined}
aria-valuetext={typeof att.progress === 'number'
? t('attachment_progress', { percent: att.progress })
: t('attachment_uploading')}
>
<div className="h-full bg-primary/10 animate-pulse" />
{typeof att.progress === 'number' ? (
// Real byte progress from the transport; see
+4 -1
View File
@@ -1029,7 +1029,10 @@
"recipients_added_count": "{count, plural, zero {لم تتم إضافة أي مستلم} one {تمت إضافة مستلم واحد} two {تمت إضافة مستلمَين} few {تمت إضافة # مستلمين} many {تمت إضافة # مستلمًا} other {تمت إضافة # مستلم}} إلى {field}",
"recipients_removed_count": "{count, plural, zero {لم تتم إزالة أي مستلم} one {تمت إزالة مستلم واحد} two {تمت إزالة مستلمَين} few {تمت إزالة # مستلمين} many {تمت إزالة # مستلمًا} other {تمت إزالة # مستلم}} من {field}",
"recipients_moved_count": "{count, plural, zero {لم يتم نقل أي مستلم} one {تم نقل مستلم واحد} two {تم نقل مستلمَين} few {تم نقل # مستلمين} many {تم نقل # مستلمًا} other {تم نقل # مستلم}} إلى {field}",
"body_label": "الرسالة"
"body_label": "الرسالة",
"attachment_uploading": "جارٍ الرفع",
"attachment_upload_failed": "فشل الرفع",
"attachment_progress": "تم رفع {percent}%"
},
"confirm_dialog": {
"confirm": "تأكيد",
+4 -1
View File
@@ -1029,7 +1029,10 @@
"recipients_added_count": "{count, plural, one {S'ha afegit # destinatari} other {S'han afegit # destinataris}} a {field}",
"recipients_removed_count": "{count, plural, one {S'ha tret # destinatari} other {S'han tret # destinataris}} de {field}",
"recipients_moved_count": "{count, plural, one {S'ha mogut # destinatari} other {S'han mogut # destinataris}} a {field}",
"body_label": "Missatge"
"body_label": "Missatge",
"attachment_uploading": "S'està pujant",
"attachment_upload_failed": "No s'ha pogut pujar",
"attachment_progress": "{percent}% pujat"
},
"confirm_dialog": {
"confirm": "Confirma",
+4 -1
View File
@@ -1029,7 +1029,10 @@
"recipients_added_count": "Do {field} {count, plural, one {přidán # příjemce} few {přidáni # příjemci} many {přidáno # příjemce} other {přidáno # příjemců}}",
"recipients_removed_count": "Z {field} {count, plural, one {odebrán # příjemce} few {odebráni # příjemci} many {odebráno # příjemce} other {odebráno # příjemců}}",
"recipients_moved_count": "Do {field} {count, plural, one {přesunut # příjemce} few {přesunuti # příjemci} many {přesunuto # příjemce} other {přesunuto # příjemců}}",
"body_label": "Zpráva"
"body_label": "Zpráva",
"attachment_uploading": "Nahrává se",
"attachment_upload_failed": "Nahrání se nezdařilo",
"attachment_progress": "Nahráno {percent} %"
},
"confirm_dialog": {
"confirm": "Potvrdit",
+4 -1
View File
@@ -1029,7 +1029,10 @@
"recipients_added_count": "{count, plural, one {# modtager} other {# modtagere}} tilføjet til {field}",
"recipients_removed_count": "{count, plural, one {# modtager} other {# modtagere}} fjernet fra {field}",
"recipients_moved_count": "{count, plural, one {# modtager} other {# modtagere}} flyttet til {field}",
"body_label": "Besked"
"body_label": "Besked",
"attachment_uploading": "Uploader",
"attachment_upload_failed": "Upload mislykkedes",
"attachment_progress": "{percent} % uploadet"
},
"confirm_dialog": {
"confirm": "Bekræft",
+4 -1
View File
@@ -1029,7 +1029,10 @@
"recipients_added_count": "{count, plural, one {# Empfänger} other {# Empfänger}} zu {field} hinzugefügt",
"recipients_removed_count": "{count, plural, one {# Empfänger} other {# Empfänger}} aus {field} entfernt",
"recipients_moved_count": "{count, plural, one {# Empfänger} other {# Empfänger}} nach {field} verschoben",
"body_label": "Nachricht"
"body_label": "Nachricht",
"attachment_uploading": "Wird hochgeladen",
"attachment_upload_failed": "Hochladen fehlgeschlagen",
"attachment_progress": "{percent} % hochgeladen"
},
"confirm_dialog": {
"confirm": "Bestätigen",
+4 -1
View File
@@ -1029,7 +1029,10 @@
"recipients_added_count": "Added {count, plural, one {# recipient} other {# recipients}} to {field}",
"recipients_removed_count": "Removed {count, plural, one {# recipient} other {# recipients}} from {field}",
"recipients_moved_count": "Moved {count, plural, one {# recipient} other {# recipients}} to {field}",
"body_label": "Message"
"body_label": "Message",
"attachment_uploading": "Uploading",
"attachment_upload_failed": "Upload failed",
"attachment_progress": "{percent}% uploaded"
},
"confirm_dialog": {
"confirm": "Confirm",
+4 -1
View File
@@ -1029,7 +1029,10 @@
"recipients_added_count": "{count, plural, one {Se añadió # destinatario} other {Se añadieron # destinatarios}} en {field}",
"recipients_removed_count": "{count, plural, one {Se quitó # destinatario} other {Se quitaron # destinatarios}} de {field}",
"recipients_moved_count": "{count, plural, one {Se movió # destinatario} other {Se movieron # destinatarios}} a {field}",
"body_label": "Mensaje"
"body_label": "Mensaje",
"attachment_uploading": "Subiendo",
"attachment_upload_failed": "Error al subir",
"attachment_progress": "{percent} % subido"
},
"confirm_dialog": {
"confirm": "Confirmar",
+4 -1
View File
@@ -1029,7 +1029,10 @@
"recipients_added_count": "Väljale {field} lisati {count, plural, one {# saaja} other {# saajat}}",
"recipients_removed_count": "Väljalt {field} eemaldati {count, plural, one {# saaja} other {# saajat}}",
"recipients_moved_count": "Väljale {field} teisaldati {count, plural, one {# saaja} other {# saajat}}",
"body_label": "Sõnum"
"body_label": "Sõnum",
"attachment_uploading": "Üleslaadimine",
"attachment_upload_failed": "Üleslaadimine ebaõnnestus",
"attachment_progress": "{percent}% üles laaditud"
},
"confirm_dialog": {
"confirm": "Kinnita",
+4 -1
View File
@@ -1029,7 +1029,10 @@
"recipients_added_count": "{count, plural, one {# گیرنده} other {# گیرنده}} به {field} افزوده شد",
"recipients_removed_count": "{count, plural, one {# گیرنده} other {# گیرنده}} از {field} حذف شد",
"recipients_moved_count": "{count, plural, one {# گیرنده} other {# گیرنده}} به {field} منتقل شد",
"body_label": "پیام"
"body_label": "پیام",
"attachment_uploading": "در حال بارگذاری",
"attachment_upload_failed": "بارگذاری ناموفق بود",
"attachment_progress": "{percent}٪ بارگذاری شد"
},
"confirm_dialog": {
"confirm": "تأیید",
+4 -1
View File
@@ -1029,7 +1029,10 @@
"recipients_added_count": "{count, plural, one {# destinataire ajouté} other {# destinataires ajoutés}} à {field}",
"recipients_removed_count": "{count, plural, one {# destinataire retiré} other {# destinataires retirés}} de {field}",
"recipients_moved_count": "{count, plural, one {# destinataire déplacé} other {# destinataires déplacés}} vers {field}",
"body_label": "Message"
"body_label": "Message",
"attachment_uploading": "Téléversement",
"attachment_upload_failed": "Échec du téléversement",
"attachment_progress": "{percent} % téléversé"
},
"confirm_dialog": {
"confirm": "Confirmer",
+4 -1
View File
@@ -994,7 +994,10 @@
"recipients_added_count": "{count, plural, one {נמען אחד נוסף} two {שני נמענים נוספו} other {# נמענים נוספו}} אל {field}",
"recipients_removed_count": "{count, plural, one {נמען אחד הוסר} two {שני נמענים הוסרו} other {# נמענים הוסרו}} מ{field}",
"recipients_moved_count": "{count, plural, one {נמען אחד הועבר} two {שני נמענים הועברו} other {# נמענים הועברו}} אל {field}",
"body_label": "הודעה"
"body_label": "הודעה",
"attachment_uploading": "מעלה",
"attachment_upload_failed": "ההעלאה נכשלה",
"attachment_progress": "{percent}% הועלו"
},
"confirm_dialog": {
"confirm": "אשר",
+4 -1
View File
@@ -1029,7 +1029,10 @@
"recipients_added_count": "{count, plural, one {# címzett} other {# címzett}} hozzáadva ide: {field}",
"recipients_removed_count": "{count, plural, one {# címzett} other {# címzett}} eltávolítva innen: {field}",
"recipients_moved_count": "{count, plural, one {# címzett} other {# címzett}} áthelyezve ide: {field}",
"body_label": "Üzenet"
"body_label": "Üzenet",
"attachment_uploading": "Feltöltés",
"attachment_upload_failed": "A feltöltés nem sikerült",
"attachment_progress": "{percent}% feltöltve"
},
"confirm_dialog": {
"confirm": "Megerősítés",
+4 -1
View File
@@ -1029,7 +1029,10 @@
"recipients_added_count": "{count, plural, one {# destinatario aggiunto} other {# destinatari aggiunti}} a {field}",
"recipients_removed_count": "{count, plural, one {# destinatario rimosso} other {# destinatari rimossi}} da {field}",
"recipients_moved_count": "{count, plural, one {# destinatario spostato} other {# destinatari spostati}} in {field}",
"body_label": "Messaggio"
"body_label": "Messaggio",
"attachment_uploading": "Caricamento",
"attachment_upload_failed": "Caricamento non riuscito",
"attachment_progress": "{percent}% caricato"
},
"confirm_dialog": {
"confirm": "Conferma",
+4 -1
View File
@@ -1029,7 +1029,10 @@
"recipients_added_count": "{count, plural, other {# 件の宛先}}を {field} に追加しました",
"recipients_removed_count": "{count, plural, other {# 件の宛先}}を {field} から削除しました",
"recipients_moved_count": "{count, plural, other {# 件の宛先}}を {field} に移動しました",
"body_label": "メッセージ"
"body_label": "メッセージ",
"attachment_uploading": "アップロード中",
"attachment_upload_failed": "アップロードに失敗しました",
"attachment_progress": "{percent}% アップロード済み"
},
"confirm_dialog": {
"confirm": "確認",
+4 -1
View File
@@ -1029,7 +1029,10 @@
"recipients_added_count": "{field}에 {count, plural, other {받는 사람 #명}}을 추가했어요",
"recipients_removed_count": "{field}에서 {count, plural, other {받는 사람 #명}}을 제거했어요",
"recipients_moved_count": "{field}(으)로 {count, plural, other {받는 사람 #명}}을 옮겼어요",
"body_label": "메시지"
"body_label": "메시지",
"attachment_uploading": "업로드 중",
"attachment_upload_failed": "업로드 실패",
"attachment_progress": "{percent}% 업로드됨"
},
"confirm_dialog": {
"confirm": "확인",
+4 -1
View File
@@ -1029,7 +1029,10 @@
"recipients_added_count": "Laukam {field} {count, plural, zero {pievienoti # adresāti} one {pievienots # adresāts} other {pievienoti # adresāti}}",
"recipients_removed_count": "No lauka {field} {count, plural, zero {noņemti # adresāti} one {noņemts # adresāts} other {noņemti # adresāti}}",
"recipients_moved_count": "Uz {field} {count, plural, zero {pārvietoti # adresāti} one {pārvietots # adresāts} other {pārvietoti # adresāti}}",
"body_label": "Ziņojums"
"body_label": "Ziņojums",
"attachment_uploading": "Augšupielādē",
"attachment_upload_failed": "Augšupielāde neizdevās",
"attachment_progress": "Augšupielādēti {percent}%"
},
"confirm_dialog": {
"confirm": "Apstiprināt",
+4 -1
View File
@@ -1029,7 +1029,10 @@
"recipients_added_count": "{field}-д {count, plural, one {# хүлээн авагч} other {# хүлээн авагч}} нэмлээ",
"recipients_removed_count": "{field}-ээс {count, plural, one {# хүлээн авагч} other {# хүлээн авагч}} хаслаа",
"recipients_moved_count": "{field} руу {count, plural, one {# хүлээн авагч} other {# хүлээн авагч}} зөөлөө",
"body_label": "Мессеж"
"body_label": "Мессеж",
"attachment_uploading": "Байршуулж байна",
"attachment_upload_failed": "Байршуулж чадсангүй",
"attachment_progress": "{percent}% байршуулсан"
},
"confirm_dialog": {
"confirm": "Баталгаажуулах",
+4 -1
View File
@@ -1029,7 +1029,10 @@
"recipients_added_count": "{count, plural, one {# mottaker} other {# mottakere}} lagt til i {field}",
"recipients_removed_count": "{count, plural, one {# mottaker} other {# mottakere}} fjernet fra {field}",
"recipients_moved_count": "{count, plural, one {# mottaker} other {# mottakere}} flyttet til {field}",
"body_label": "Melding"
"body_label": "Melding",
"attachment_uploading": "Laster opp",
"attachment_upload_failed": "Opplastingen mislyktes",
"attachment_progress": "{percent} % lastet opp"
},
"confirm_dialog": {
"confirm": "Bekreft",
+4 -1
View File
@@ -1029,7 +1029,10 @@
"recipients_added_count": "{count, plural, one {# ontvanger} other {# ontvangers}} toegevoegd aan {field}",
"recipients_removed_count": "{count, plural, one {# ontvanger} other {# ontvangers}} verwijderd uit {field}",
"recipients_moved_count": "{count, plural, one {# ontvanger} other {# ontvangers}} verplaatst naar {field}",
"body_label": "Bericht"
"body_label": "Bericht",
"attachment_uploading": "Uploaden",
"attachment_upload_failed": "Uploaden mislukt",
"attachment_progress": "{percent}% geüpload"
},
"confirm_dialog": {
"confirm": "Bevestigen",
+4 -1
View File
@@ -1029,7 +1029,10 @@
"recipients_added_count": "Dodano {count, plural, one {# odbiorcę} few {# odbiorców} many {# odbiorców} other {# odbiorcy}} do {field}",
"recipients_removed_count": "Usunięto {count, plural, one {# odbiorcę} few {# odbiorców} many {# odbiorców} other {# odbiorcy}} z {field}",
"recipients_moved_count": "Przeniesiono {count, plural, one {# odbiorcę} few {# odbiorców} many {# odbiorców} other {# odbiorcy}} do {field}",
"body_label": "Wiadomość"
"body_label": "Wiadomość",
"attachment_uploading": "Przesyłanie",
"attachment_upload_failed": "Przesyłanie nie powiodło się",
"attachment_progress": "Przesłano {percent}%"
},
"confirm_dialog": {
"confirm": "Potwierdź",
+4 -1
View File
@@ -1029,7 +1029,10 @@
"recipients_added_count": "{count, plural, one {# destinatário adicionado} other {# destinatários adicionados}} a {field}",
"recipients_removed_count": "{count, plural, one {# destinatário removido} other {# destinatários removidos}} de {field}",
"recipients_moved_count": "{count, plural, one {# destinatário movido} other {# destinatários movidos}} para {field}",
"body_label": "Mensagem"
"body_label": "Mensagem",
"attachment_uploading": "Enviando",
"attachment_upload_failed": "Falha no envio",
"attachment_progress": "{percent}% enviado"
},
"confirm_dialog": {
"confirm": "Confirmar",
+4 -1
View File
@@ -1029,7 +1029,10 @@
"recipients_added_count": "{count, plural, one {A fost adăugat # destinatar} few {Au fost adăugați # destinatari} other {Au fost adăugați # de destinatari}} la {field}",
"recipients_removed_count": "{count, plural, one {A fost eliminat # destinatar} few {Au fost eliminați # destinatari} other {Au fost eliminați # de destinatari}} din {field}",
"recipients_moved_count": "{count, plural, one {A fost mutat # destinatar} few {Au fost mutați # destinatari} other {Au fost mutați # de destinatari}} la {field}",
"body_label": "Mesaj"
"body_label": "Mesaj",
"attachment_uploading": "Se încarcă",
"attachment_upload_failed": "Încărcarea a eșuat",
"attachment_progress": "{percent}% încărcat"
},
"confirm_dialog": {
"confirm": "Confirmare",
+4 -1
View File
@@ -1029,7 +1029,10 @@
"recipients_added_count": "В «{field}» {count, plural, one {добавлен # получатель} few {добавлено # получателя} many {добавлено # получателей} other {добавлено # получателя}}",
"recipients_removed_count": "Из «{field}» {count, plural, one {убран # получатель} few {убрано # получателя} many {убрано # получателей} other {убрано # получателя}}",
"recipients_moved_count": "В «{field}» {count, plural, one {перемещён # получатель} few {перемещено # получателя} many {перемещено # получателей} other {перемещено # получателя}}",
"body_label": "Сообщение"
"body_label": "Сообщение",
"attachment_uploading": "Загрузка",
"attachment_upload_failed": "Не удалось загрузить",
"attachment_progress": "Загружено {percent}%"
},
"confirm_dialog": {
"confirm": "Подтвердить",
+4 -1
View File
@@ -1029,7 +1029,10 @@
"recipients_added_count": "Do {field} {count, plural, one {pridaný # príjemca} few {pridaní # príjemcovia} many {pridaných # príjemcov} other {pridaných # príjemcov}}",
"recipients_removed_count": "Z {field} {count, plural, one {odstránený # príjemca} few {odstránení # príjemcovia} many {odstránených # príjemcov} other {odstránených # príjemcov}}",
"recipients_moved_count": "Do {field} {count, plural, one {presunutý # príjemca} few {presunutí # príjemcovia} many {presunutých # príjemcov} other {presunutých # príjemcov}}",
"body_label": "Správa"
"body_label": "Správa",
"attachment_uploading": "Nahráva sa",
"attachment_upload_failed": "Nahrávanie zlyhalo",
"attachment_progress": "Nahraté {percent} %"
},
"confirm_dialog": {
"confirm": "Potvrdiť",
+4 -1
View File
@@ -1277,7 +1277,10 @@
"recipients_added_count": "เพิ่มผู้รับ {count, plural, other {# คน}} ใน {field} แล้ว",
"recipients_removed_count": "นำผู้รับ {count, plural, other {# คน}} ออกจาก {field} แล้ว",
"recipients_moved_count": "ย้ายผู้รับ {count, plural, other {# คน}} ไปที่ {field} แล้ว",
"body_label": "ข้อความ"
"body_label": "ข้อความ",
"attachment_uploading": "กำลังอัปโหลด",
"attachment_upload_failed": "อัปโหลดไม่สำเร็จ",
"attachment_progress": "อัปโหลดแล้ว {percent}%"
},
"email_viewer": {
"read_receipt": {
+4 -1
View File
@@ -1029,7 +1029,10 @@
"recipients_added_count": "{field} alanına {count, plural, one {# alıcı} other {# alıcı}} eklendi",
"recipients_removed_count": "{field} alanından {count, plural, one {# alıcı} other {# alıcı}} kaldırıldı",
"recipients_moved_count": "{field} alanına {count, plural, one {# alıcı} other {# alıcı}} taşındı",
"body_label": "İleti"
"body_label": "İleti",
"attachment_uploading": "Yükleniyor",
"attachment_upload_failed": "Yükleme başarısız",
"attachment_progress": "%{percent} yüklendi"
},
"confirm_dialog": {
"confirm": "Onayla",
+4 -1
View File
@@ -1029,7 +1029,10 @@
"recipients_added_count": "У «{field}» {count, plural, one {додано # одержувача} few {додано # одержувачі} many {додано # одержувачів} other {додано # одержувача}}",
"recipients_removed_count": "З «{field}» {count, plural, one {прибрано # одержувача} few {прибрано # одержувачі} many {прибрано # одержувачів} other {прибрано # одержувача}}",
"recipients_moved_count": "У «{field}» {count, plural, one {переміщено # одержувача} few {переміщено # одержувачі} many {переміщено # одержувачів} other {переміщено # одержувача}}",
"body_label": "Повідомлення"
"body_label": "Повідомлення",
"attachment_uploading": "Завантаження",
"attachment_upload_failed": "Не вдалося завантажити",
"attachment_progress": "Завантажено {percent}%"
},
"confirm_dialog": {
"confirm": "Підтвердити",
+4 -1
View File
@@ -1029,7 +1029,10 @@
"recipients_added_count": "已將 {count, plural, other {# 位收件者}}新增到{field}",
"recipients_removed_count": "已將 {count, plural, other {# 位收件者}}從{field}移除",
"recipients_moved_count": "已將 {count, plural, other {# 位收件者}}移到{field}",
"body_label": "郵件內容"
"body_label": "郵件內容",
"attachment_uploading": "正在上傳",
"attachment_upload_failed": "上傳失敗",
"attachment_progress": "已上傳 {percent}%"
},
"confirm_dialog": {
"confirm": "確認",
+4 -1
View File
@@ -1029,7 +1029,10 @@
"recipients_added_count": "已将 {count, plural, other {# 位收件人}}添加到{field}",
"recipients_removed_count": "已将 {count, plural, other {# 位收件人}}从{field}移除",
"recipients_moved_count": "已将 {count, plural, other {# 位收件人}}移到{field}",
"body_label": "邮件正文"
"body_label": "邮件正文",
"attachment_uploading": "正在上传",
"attachment_upload_failed": "上传失败",
"attachment_progress": "已上传 {percent}%"
},
"confirm_dialog": {
"confirm": "确认",