fix: name the message body and tie its error to it for screen readers

This commit is contained in:
Linus Rath
2026-10-29 17:53:40 +01:00
parent 0aa751a3a0
commit caee90facc
33 changed files with 178 additions and 32 deletions
@@ -325,3 +325,24 @@ describe('moving a recipient by keyboard (MO-07)', () => {
expect(await screen.findByTestId('recipient-move-bcc')).toBeInTheDocument();
});
});
describe('message body (SR-07)', () => {
it('labels the plain-text body', () => {
settings.plainTextMode = true;
render(<EmailComposer initialData={DRAFT} />);
expect(screen.getByLabelText('body_label').tagName).toBe('TEXTAREA');
});
it('marks an empty body invalid and links the error text', async () => {
render(<EmailComposer initialData={{ ...DRAFT, to: 'bob@example.com', body: '' }} />);
const editor = screen.getByTestId('rich-text-editor');
expect(editor).not.toHaveAttribute('data-invalid');
fireEvent.keyDown(screen.getByTestId('composer-subject'), { key: 'Enter', ctrlKey: true });
await act(async () => {});
expect(editor).toHaveAttribute('data-invalid', 'true');
const error = document.getElementById(editor.getAttribute('data-describedby')!);
expect(error).toHaveTextContent('validation.body_required');
});
});
@@ -0,0 +1,51 @@
import { render, waitFor } from '@testing-library/react';
import { describe, expect, it } from 'vitest';
import React from 'react';
import type { Editor } from '@tiptap/core';
import { RichTextEditor } from '../rich-text-editor';
type Props = Partial<React.ComponentProps<typeof RichTextEditor>>;
async function setup(props: Props = {}) {
let editor: Editor | null = null;
const view = render(
<RichTextEditor
content="<p>Hello</p>"
onChange={() => {}}
onEditorReady={(ed) => { editor = ed as unknown as Editor; }}
{...props}
/>
);
await waitFor(() => expect(editor).not.toBeNull());
const rerender = (next: Props) => view.rerender(
<RichTextEditor content="<p>Hello</p>" onChange={() => {}} {...props} {...next} />
);
return { ...view, editor: editor as unknown as Editor, rerender };
}
describe('RichTextEditor editing area (SR-07)', () => {
it('is a named multi-line textbox', async () => {
const { editor } = await setup({ ariaLabel: 'Message' });
const dom = editor.view.dom;
expect(dom).toHaveAttribute('role', 'textbox');
expect(dom).toHaveAttribute('aria-multiline', 'true');
expect(dom).toHaveAttribute('aria-label', 'Message');
expect(dom).not.toHaveAttribute('aria-invalid');
});
it('marks the body invalid and points at the error once validation fails', async () => {
const { editor, rerender } = await setup({ ariaLabel: 'Message', ariaDescribedBy: 'body-error' });
expect(editor.view.dom).not.toHaveAttribute('aria-describedby');
rerender({ ariaInvalid: true });
await waitFor(() => expect(editor.view.dom).toHaveAttribute('aria-invalid', 'true'));
expect(editor.view.dom).toHaveAttribute('aria-describedby', 'body-error');
// Still the editor's own class and name.
expect(editor.view.dom).toHaveClass('tiptap');
expect(editor.view.dom).toHaveAttribute('aria-label', 'Message');
rerender({ ariaInvalid: false });
await waitFor(() => expect(editor.view.dom).not.toHaveAttribute('aria-invalid'));
});
});
+14
View File
@@ -4394,7 +4394,10 @@ export function EmailComposer({
)}
{/* Body */}
{plainTextMode ? (
<>
<label htmlFor={`${fieldIdBase}-body`} className="sr-only">{t('body_label')}</label>
<textarea
id={`${fieldIdBase}-body`}
ref={bodyRef}
value={body}
onChange={(e) => {
@@ -4408,8 +4411,10 @@ export function EmailComposer({
)}
style={{ height: 'calc(100vh - 350px)' }}
aria-invalid={validationErrors.body || undefined}
aria-describedby={validationErrors.body ? `${fieldIdBase}-body-error` : undefined}
onContextMenu={onBodyContextMenu}
/>
</>
) : (
<div ref={editorContainerRef} onContextMenu={onBodyContextMenu}>
<RichTextEditor
@@ -4421,6 +4426,9 @@ export function EmailComposer({
onImageUpload={handleImageUpload}
placeholder={t('body_placeholder')}
hasError={validationErrors.body}
ariaLabel={t('body_label')}
ariaInvalid={validationErrors.body}
ariaDescribedBy={`${fieldIdBase}-body-error`}
collapseToolbarOnMobile={!showFormattingOnMobile}
toolbarPlacement={mvWindow ? (mvShowFormatting ? 'bottom' : 'hidden') : 'top'}
onEditorReady={handleEditorReady}
@@ -4431,6 +4439,12 @@ export function EmailComposer({
/>
</div>
)}
{validationErrors.body && (
<p id={`${fieldIdBase}-body-error`} className="flex items-center gap-1 px-4 py-1 text-xs text-red-600 dark:text-red-400">
<AlertCircle className="w-3.5 h-3.5 shrink-0" aria-hidden />
{t('validation.body_required')}
</p>
)}
{/* Hide the visual signature preview when the signature has already been
embedded into the body (compose, above-quote replies, re-opened
+34 -3
View File
@@ -126,6 +126,24 @@ interface RichTextEditorProps {
* away entirely.
*/
toolbarPlacement?: 'top' | 'bottom' | 'hidden';
/** The editing area's accessible name ("Message"). */
ariaLabel?: string;
/** The body failed validation: exposed as aria-invalid on the editing area. */
ariaInvalid?: boolean;
/** The id of the text that explains the error. */
ariaDescribedBy?: string;
}
/** What the editing area tells assistive technology, besides TipTap's role=textbox. */
export function editorA11yAttributes({ ariaLabel, ariaInvalid, ariaDescribedBy }: Pick<RichTextEditorProps, 'ariaLabel' | 'ariaInvalid' | 'ariaDescribedBy'>): Record<string, string> {
return {
class: "tiptap min-h-[100px] px-4 py-3 text-sm text-foreground",
role: "textbox",
"aria-multiline": "true",
...(ariaLabel ? { "aria-label": ariaLabel } : {}),
...(ariaInvalid ? { "aria-invalid": "true" } : {}),
...(ariaInvalid && ariaDescribedBy ? { "aria-describedby": ariaDescribedBy } : {}),
};
}
function ToolbarButton({
@@ -223,6 +241,9 @@ export function RichTextEditor({
onMentionAdd,
collapseToolbarOnMobile = false,
toolbarPlacement = 'top',
ariaLabel,
ariaInvalid,
ariaDescribedBy,
}: RichTextEditorProps) {
const rtlEditingSupport = useSettingsStore((st) => st.rtlEditingSupport);
const tComposer = useTranslations("email_composer");
@@ -329,9 +350,7 @@ export function RichTextEditor({
},
content,
editorProps: {
attributes: {
class: "tiptap min-h-[100px] px-4 py-3 text-sm text-foreground",
},
attributes: editorA11yAttributes({ ariaLabel, ariaInvalid, ariaDescribedBy }),
handleDrop: (view, event) => {
if (!event.dataTransfer?.files?.length) return false;
const upload = onImageUploadRef.current;
@@ -397,6 +416,18 @@ export function RichTextEditor({
}
}, [content, editor]);
// The name and error state change after the editor is built (a failed
// send marks the body invalid): hand them to the editing area.
useEffect(() => {
if (!editor || editor.isDestroyed) return;
editor.setOptions({
editorProps: {
...editor.options.editorProps,
attributes: editorA11yAttributes({ ariaLabel, ariaInvalid, ariaDescribedBy }),
},
});
}, [editor, ariaLabel, ariaInvalid, ariaDescribedBy]);
// Expose the editor instance once it's ready so parents can target
// specific nodes (e.g. swap the embedded signature on identity change).
useEffect(() => {
+2 -1
View File
@@ -1009,7 +1009,8 @@
"recipient_moved": "تم نقل {name} إلى {field}",
"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}"
"recipients_moved_count": "{count, plural, zero {لم يتم نقل أي مستلم} one {تم نقل مستلم واحد} two {تم نقل مستلمَين} few {تم نقل # مستلمين} many {تم نقل # مستلمًا} other {تم نقل # مستلم}} إلى {field}",
"body_label": "الرسالة"
},
"confirm_dialog": {
"confirm": "تأكيد",
+2 -1
View File
@@ -1009,7 +1009,8 @@
"recipient_moved": "S'ha mogut {name} a {field}",
"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}"
"recipients_moved_count": "{count, plural, one {S'ha mogut # destinatari} other {S'han mogut # destinataris}} a {field}",
"body_label": "Missatge"
},
"confirm_dialog": {
"confirm": "Confirma",
+2 -1
View File
@@ -1009,7 +1009,8 @@
"recipient_moved": "{name} přesunut do {field}",
"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ů}}"
"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"
},
"confirm_dialog": {
"confirm": "Potvrdit",
+2 -1
View File
@@ -1009,7 +1009,8 @@
"recipient_moved": "{name} flyttet til {field}",
"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}"
"recipients_moved_count": "{count, plural, one {# modtager} other {# modtagere}} flyttet til {field}",
"body_label": "Besked"
},
"confirm_dialog": {
"confirm": "Bekræft",
+2 -1
View File
@@ -1009,7 +1009,8 @@
"recipient_moved": "{name} nach {field} verschoben",
"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"
"recipients_moved_count": "{count, plural, one {# Empfänger} other {# Empfänger}} nach {field} verschoben",
"body_label": "Nachricht"
},
"confirm_dialog": {
"confirm": "Bestätigen",
+2 -1
View File
@@ -1009,7 +1009,8 @@
"recipient_moved": "Moved {name} to {field}",
"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}"
"recipients_moved_count": "Moved {count, plural, one {# recipient} other {# recipients}} to {field}",
"body_label": "Message"
},
"confirm_dialog": {
"confirm": "Confirm",
+2 -1
View File
@@ -1009,7 +1009,8 @@
"recipient_moved": "Se movió a {name} a {field}",
"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}"
"recipients_moved_count": "{count, plural, one {Se movió # destinatario} other {Se movieron # destinatarios}} a {field}",
"body_label": "Mensaje"
},
"confirm_dialog": {
"confirm": "Confirmar",
+2 -1
View File
@@ -1009,7 +1009,8 @@
"recipient_moved": "{name} teisaldati väljale {field}",
"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}}"
"recipients_moved_count": "Väljale {field} teisaldati {count, plural, one {# saaja} other {# saajat}}",
"body_label": "Sõnum"
},
"confirm_dialog": {
"confirm": "Kinnita",
+2 -1
View File
@@ -1009,7 +1009,8 @@
"recipient_moved": "{name} به {field} منتقل شد",
"recipients_added_count": "{count, plural, one {# گیرنده} other {# گیرنده}} به {field} افزوده شد",
"recipients_removed_count": "{count, plural, one {# گیرنده} other {# گیرنده}} از {field} حذف شد",
"recipients_moved_count": "{count, plural, one {# گیرنده} other {# گیرنده}} به {field} منتقل شد"
"recipients_moved_count": "{count, plural, one {# گیرنده} other {# گیرنده}} به {field} منتقل شد",
"body_label": "پیام"
},
"confirm_dialog": {
"confirm": "تأیید",
+2 -1
View File
@@ -1009,7 +1009,8 @@
"recipient_moved": "{name} déplacé vers {field}",
"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}"
"recipients_moved_count": "{count, plural, one {# destinataire déplacé} other {# destinataires déplacés}} vers {field}",
"body_label": "Message"
},
"confirm_dialog": {
"confirm": "Confirmer",
+2 -1
View File
@@ -974,7 +974,8 @@
"recipient_moved": "{name} הועבר אל {field}",
"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}"
"recipients_moved_count": "{count, plural, one {נמען אחד הועבר} two {שני נמענים הועברו} other {# נמענים הועברו}} אל {field}",
"body_label": "הודעה"
},
"confirm_dialog": {
"confirm": "אשר",
+2 -1
View File
@@ -1009,7 +1009,8 @@
"recipient_moved": "{name} áthelyezve ide: {field}",
"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}"
"recipients_moved_count": "{count, plural, one {# címzett} other {# címzett}} áthelyezve ide: {field}",
"body_label": "Üzenet"
},
"confirm_dialog": {
"confirm": "Megerősítés",
+2 -1
View File
@@ -1009,7 +1009,8 @@
"recipient_moved": "{name} spostato in {field}",
"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}"
"recipients_moved_count": "{count, plural, one {# destinatario spostato} other {# destinatari spostati}} in {field}",
"body_label": "Messaggio"
},
"confirm_dialog": {
"confirm": "Conferma",
+2 -1
View File
@@ -1009,7 +1009,8 @@
"recipient_moved": "{name} を {field} に移動しました",
"recipients_added_count": "{count, plural, other {# 件の宛先}}を {field} に追加しました",
"recipients_removed_count": "{count, plural, other {# 件の宛先}}を {field} から削除しました",
"recipients_moved_count": "{count, plural, other {# 件の宛先}}を {field} に移動しました"
"recipients_moved_count": "{count, plural, other {# 件の宛先}}を {field} に移動しました",
"body_label": "メッセージ"
},
"confirm_dialog": {
"confirm": "確認",
+2 -1
View File
@@ -1009,7 +1009,8 @@
"recipient_moved": "{name}을(를) {field}(으)로 옮겼어요",
"recipients_added_count": "{field}에 {count, plural, other {받는 사람 #명}}을 추가했어요",
"recipients_removed_count": "{field}에서 {count, plural, other {받는 사람 #명}}을 제거했어요",
"recipients_moved_count": "{field}(으)로 {count, plural, other {받는 사람 #명}}을 옮겼어요"
"recipients_moved_count": "{field}(으)로 {count, plural, other {받는 사람 #명}}을 옮겼어요",
"body_label": "메시지"
},
"confirm_dialog": {
"confirm": "확인",
+2 -1
View File
@@ -1009,7 +1009,8 @@
"recipient_moved": "{name} pārvietots uz {field}",
"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}}"
"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"
},
"confirm_dialog": {
"confirm": "Apstiprināt",
+2 -1
View File
@@ -1009,7 +1009,8 @@
"recipient_moved": "{name}-г {field} руу зөөлөө",
"recipients_added_count": "{field}-д {count, plural, one {# хүлээн авагч} other {# хүлээн авагч}} нэмлээ",
"recipients_removed_count": "{field}-ээс {count, plural, one {# хүлээн авагч} other {# хүлээн авагч}} хаслаа",
"recipients_moved_count": "{field} руу {count, plural, one {# хүлээн авагч} other {# хүлээн авагч}} зөөлөө"
"recipients_moved_count": "{field} руу {count, plural, one {# хүлээн авагч} other {# хүлээн авагч}} зөөлөө",
"body_label": "Мессеж"
},
"confirm_dialog": {
"confirm": "Баталгаажуулах",
+2 -1
View File
@@ -1009,7 +1009,8 @@
"recipient_moved": "{name} flyttet til {field}",
"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}"
"recipients_moved_count": "{count, plural, one {# mottaker} other {# mottakere}} flyttet til {field}",
"body_label": "Melding"
},
"confirm_dialog": {
"confirm": "Bekreft",
+2 -1
View File
@@ -1009,7 +1009,8 @@
"recipient_moved": "{name} verplaatst naar {field}",
"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}"
"recipients_moved_count": "{count, plural, one {# ontvanger} other {# ontvangers}} verplaatst naar {field}",
"body_label": "Bericht"
},
"confirm_dialog": {
"confirm": "Bevestigen",
+2 -1
View File
@@ -1009,7 +1009,8 @@
"recipient_moved": "Przeniesiono {name} do {field}",
"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}"
"recipients_moved_count": "Przeniesiono {count, plural, one {# odbiorcę} few {# odbiorców} many {# odbiorców} other {# odbiorcy}} do {field}",
"body_label": "Wiadomość"
},
"confirm_dialog": {
"confirm": "Potwierdź",
+2 -1
View File
@@ -1009,7 +1009,8 @@
"recipient_moved": "{name} movido para {field}",
"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}"
"recipients_moved_count": "{count, plural, one {# destinatário movido} other {# destinatários movidos}} para {field}",
"body_label": "Mensagem"
},
"confirm_dialog": {
"confirm": "Confirmar",
+2 -1
View File
@@ -1009,7 +1009,8 @@
"recipient_moved": "{name} a fost mutat la {field}",
"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}"
"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"
},
"confirm_dialog": {
"confirm": "Confirmare",
+2 -1
View File
@@ -1009,7 +1009,8 @@
"recipient_moved": "{name}: перемещён в «{field}»",
"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 {перемещено # получателя}}"
"recipients_moved_count": "В «{field}» {count, plural, one {перемещён # получатель} few {перемещено # получателя} many {перемещено # получателей} other {перемещено # получателя}}",
"body_label": "Сообщение"
},
"confirm_dialog": {
"confirm": "Подтвердить",
+2 -1
View File
@@ -1009,7 +1009,8 @@
"recipient_moved": "{name} presunutý do {field}",
"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}}"
"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"
},
"confirm_dialog": {
"confirm": "Potvrdiť",
+2 -1
View File
@@ -1257,7 +1257,8 @@
"recipient_moved": "ย้าย {name} ไปที่ {field} แล้ว",
"recipients_added_count": "เพิ่มผู้รับ {count, plural, other {# คน}} ใน {field} แล้ว",
"recipients_removed_count": "นำผู้รับ {count, plural, other {# คน}} ออกจาก {field} แล้ว",
"recipients_moved_count": "ย้ายผู้รับ {count, plural, other {# คน}} ไปที่ {field} แล้ว"
"recipients_moved_count": "ย้ายผู้รับ {count, plural, other {# คน}} ไปที่ {field} แล้ว",
"body_label": "ข้อความ"
},
"email_viewer": {
"read_receipt": {
+2 -1
View File
@@ -1009,7 +1009,8 @@
"recipient_moved": "{name}, {field} alanına taşındı",
"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ı"
"recipients_moved_count": "{field} alanına {count, plural, one {# alıcı} other {# alıcı}} taşındı",
"body_label": "İleti"
},
"confirm_dialog": {
"confirm": "Onayla",
+2 -1
View File
@@ -1009,7 +1009,8 @@
"recipient_moved": "{name}: переміщено в «{field}»",
"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 {переміщено # одержувача}}"
"recipients_moved_count": "У «{field}» {count, plural, one {переміщено # одержувача} few {переміщено # одержувачі} many {переміщено # одержувачів} other {переміщено # одержувача}}",
"body_label": "Повідомлення"
},
"confirm_dialog": {
"confirm": "Підтвердити",
+2 -1
View File
@@ -1009,7 +1009,8 @@
"recipient_moved": "已將 {name} 移到{field}",
"recipients_added_count": "已將 {count, plural, other {# 位收件者}}新增到{field}",
"recipients_removed_count": "已將 {count, plural, other {# 位收件者}}從{field}移除",
"recipients_moved_count": "已將 {count, plural, other {# 位收件者}}移到{field}"
"recipients_moved_count": "已將 {count, plural, other {# 位收件者}}移到{field}",
"body_label": "郵件內容"
},
"confirm_dialog": {
"confirm": "確認",
+2 -1
View File
@@ -1009,7 +1009,8 @@
"recipient_moved": "已将 {name} 移到{field}",
"recipients_added_count": "已将 {count, plural, other {# 位收件人}}添加到{field}",
"recipients_removed_count": "已将 {count, plural, other {# 位收件人}}从{field}移除",
"recipients_moved_count": "已将 {count, plural, other {# 位收件人}}移到{field}"
"recipients_moved_count": "已将 {count, plural, other {# 位收件人}}移到{field}",
"body_label": "邮件正文"
},
"confirm_dialog": {
"confirm": "确认",