diff --git a/components/email/__tests__/rich-text-editor-a11y.test.tsx b/components/email/__tests__/rich-text-editor-a11y.test.tsx index 2334fbc4e..ad0ddfbb9 100644 --- a/components/email/__tests__/rich-text-editor-a11y.test.tsx +++ b/components/email/__tests__/rich-text-editor-a11y.test.tsx @@ -1,4 +1,4 @@ -import { render, waitFor } from '@testing-library/react'; +import { act, fireEvent, render, screen, waitFor, within } from '@testing-library/react'; import { describe, expect, it } from 'vitest'; import React from 'react'; import type { Editor } from '@tiptap/core'; @@ -49,3 +49,80 @@ describe('RichTextEditor editing area (SR-07)', () => { await waitFor(() => expect(editor.view.dom).not.toHaveAttribute('aria-invalid')); }); }); + +describe('RichTextEditor formatting toolbar (SR-08)', () => { + const toolbar = () => screen.getByRole('toolbar', { name: 'formatting' }); + const item = (name: string) => toolbar().querySelector(`[data-toolbar-item="${name}"]`)!; + const tabStops = () => Array.from(toolbar().querySelectorAll('[data-toolbar-item]')).filter((el) => el.tabIndex === 0); + + it('is one Tab stop and moves with the arrows, Home and End', async () => { + await setup(); + expect(tabStops()).toEqual([item('font-size')]); + + item('font-size').focus(); + fireEvent.keyDown(item('font-size'), { key: 'ArrowRight' }); + expect(document.activeElement).toBe(item('bold')); + expect(tabStops()).toEqual([item('bold')]); + fireEvent.keyDown(item('bold'), { key: 'ArrowLeft' }); + expect(document.activeElement).toBe(item('font-size')); + // End skips undo and redo while they are disabled. + fireEvent.keyDown(item('font-size'), { key: 'End' }); + expect(document.activeElement).toBe(item('clear-formatting')); + fireEvent.keyDown(item('clear-formatting'), { key: 'Home' }); + expect(document.activeElement).toBe(item('font-size')); + }); + + it('says which toggles are on', async () => { + // As in the composer, a change re-renders the editor through its parent. + let editor: Editor | null = null; + function Host() { + const [html, setHtml] = React.useState('

Hello

'); + return { editor = ed as unknown as Editor; }} />; + } + render(); + await waitFor(() => expect(editor).not.toBeNull()); + expect(item('bold')).toHaveAttribute('aria-pressed', 'false'); + act(() => { editor!.chain().selectAll().toggleBold().run(); }); + await waitFor(() => expect(item('bold')).toHaveAttribute('aria-pressed', 'true')); + expect(item('italic')).toHaveAttribute('aria-pressed', 'false'); + }); + + it('opens the font size menu by keyboard, moves inside it and gives the focus back on Escape', async () => { + await setup(); + const button = item('font-size'); + expect(button).toHaveAttribute('aria-haspopup', 'menu'); + expect(button).toHaveAttribute('aria-expanded', 'false'); + + fireEvent.click(button); + expect(button).toHaveAttribute('aria-expanded', 'true'); + const menu = screen.getByRole('menu', { name: 'font_size' }); + const entries = within(menu).getAllByRole('menuitemradio'); + await waitFor(() => expect(document.activeElement).toBe(entries[0])); + fireEvent.keyDown(entries[0], { key: 'ArrowDown' }); + expect(document.activeElement).toBe(entries[1]); + + fireEvent.keyDown(entries[1], { key: 'Escape' }); + expect(screen.queryByRole('menu', { name: 'font_size' })).toBeNull(); + expect(document.activeElement).toBe(button); + }); + + it('names the colour swatches and moves through them as a grid', async () => { + await setup(); + fireEvent.click(item('text-color')); + const menu = screen.getByRole('menu', { name: 'text_color' }); + const swatches = within(menu).getAllByRole('menuitemradio'); + expect(swatches[0]).toHaveAccessibleName('colors.black'); + await waitFor(() => expect(document.activeElement).toBe(swatches[0])); + fireEvent.keyDown(swatches[0], { key: 'ArrowDown' }); + expect(document.activeElement).toBe(swatches[8]); + fireEvent.keyDown(swatches[8], { key: 'ArrowRight' }); + expect(document.activeElement).toBe(swatches[9]); + }); + + it('jumps from the text to the toolbar with Alt+F10', async () => { + const { editor } = await setup(); + editor.commands.focus(); + fireEvent.keyDown(editor.view.dom, { key: 'F10', altKey: true }); + expect(document.activeElement).toBe(item('font-size')); + }); +}); diff --git a/components/email/rich-text-editor.tsx b/components/email/rich-text-editor.tsx index 2d07390e0..07fdfecdb 100644 --- a/components/email/rich-text-editor.tsx +++ b/components/email/rich-text-editor.tsx @@ -1,6 +1,6 @@ "use client"; -import React, { useEffect, useCallback, useState, useRef } from "react"; +import React, { useEffect, useCallback, useLayoutEffect, useState, useRef } from "react"; import { createPortal } from "react-dom"; import { useEditor, EditorContent, type Editor } from "@tiptap/react"; import type { SuggestionProps } from "@tiptap/suggestion"; @@ -146,35 +146,142 @@ export function editorA11yAttributes({ ariaLabel, ariaInvalid, ariaDescribedBy } }; } -function ToolbarButton({ - active, - onClick, - children, - title, - disabled, -}: { +/** + * One button of the formatting toolbar. `item` names it for the toolbar's + * roving focus (one Tab stop, arrows inside). A toggle passes `pressed`, a + * button that opens a popover passes `popup` and `expanded`. + */ +const ToolbarButton = React.forwardRef void; children: React.ReactNode; title: string; disabled?: boolean; -}) { +}>(function ToolbarButton({ item, active, pressed, popup, expanded, onClick, children, title, disabled }, ref) { return ( ); +}); + +/** + * A toolbar popover (font size, colours, table) that works from the + * keyboard: it takes the focus when it opens (the checked entry, else the + * first), the arrows move inside it (in a grid with `columns`), and Escape + * or Tab close it and put the focus back on its button. + */ +function ToolbarPopover({ + label, + role, + columns, + onClose, + triggerRef, + className, + children, +}: { + label: string; + role: "menu" | "dialog"; + /** Arrange the leading `[data-grid-item]` entries in rows of this many. */ + columns?: number; + onClose: () => void; + triggerRef: React.RefObject; + className?: string; + children: React.ReactNode; +}) { + const ref = useRef(null); + const items = () => + Array.from(ref.current?.querySelectorAll("button:not([disabled])") ?? []); + + useEffect(() => { + const list = items(); + const checked = list.find((el) => el.getAttribute("aria-checked") === "true"); + (checked ?? list[0])?.focus(); + }, []); + + const close = (returnFocus: boolean) => { + onClose(); + if (returnFocus) triggerRef.current?.focus(); + }; + + const onKeyDown = (e: React.KeyboardEvent) => { + const list = items(); + const i = list.indexOf(document.activeElement as HTMLElement); + const rtl = ref.current ? getComputedStyle(ref.current).direction === "rtl" : false; + const gridCount = columns ? list.filter((el) => el.hasAttribute("data-grid-item")).length : 0; + const inGrid = columns && i >= 0 && i < gridCount; + let next = -1; + switch (e.key) { + case "Escape": + case "Tab": + e.preventDefault(); + e.stopPropagation(); + close(true); + return; + case "ArrowDown": + next = inGrid ? (i + columns! < gridCount ? i + columns! : gridCount < list.length ? gridCount : i) : Math.min(i + 1, list.length - 1); + break; + case "ArrowUp": + next = inGrid ? (i - columns! >= 0 ? i - columns! : i) : i === gridCount && gridCount > 0 ? gridCount - 1 : Math.max(i - 1, 0); + break; + case "ArrowRight": + case "ArrowLeft": { + if (!columns) return; + const forward = (e.key === "ArrowRight") !== rtl; + next = forward ? Math.min(i + 1, list.length - 1) : Math.max(i - 1, 0); + break; + } + case "Home": + next = 0; + break; + case "End": + next = list.length - 1; + break; + default: + return; + } + e.preventDefault(); + e.stopPropagation(); + list[next]?.focus(); + }; + + return ( +
{ + const to = e.relatedTarget as Node | null; + if (to && !ref.current?.contains(to) && !triggerRef.current?.contains(to)) close(false); + }} + className={cn("absolute z-50 top-full start-0 mt-1 bg-popover border border-border rounded-md shadow-md", className)} + > + {children} +
+ ); } function ToolbarSeparator() { @@ -191,6 +298,14 @@ const TEXT_COLORS = [ "#000000", "#5f6368", "#9aa0a6", "#c5221f", "#e8710a", "#f9ab00", "#188038", "#1967d2", "#7627bb", "#c2185b", "#795548", "#fa5252", "#fd7e14", "#40c057", "#4dabf7", "#e64980", ]; +// What each swatch is called, for screen readers and the tooltip +// (email_composer.toolbar.colors.*). +const TEXT_COLOR_NAMES: Record = { + "#000000": "black", "#5f6368": "dark_gray", "#9aa0a6": "gray", "#c5221f": "dark_red", + "#e8710a": "orange", "#f9ab00": "yellow", "#188038": "green", "#1967d2": "blue", + "#7627bb": "purple", "#c2185b": "dark_pink", "#795548": "brown", "#fa5252": "red", + "#fd7e14": "light_orange", "#40c057": "light_green", "#4dabf7": "light_blue", "#e64980": "pink", +}; function TableSizePicker({ onPick }: { onPick: (rows: number, cols: number) => void }) { const t = useTranslations("email_composer.toolbar"); @@ -210,7 +325,10 @@ function TableSizePicker({ onPick }: { onPick: (rows: number, cols: number) => v + + ); + + const tableMenuItem = (icon: React.ReactNode, label: string, run: () => void, destructive = false) => ( + + ); + return (
{/* Toolbar - sticky within the composer's scroll container so it stays @@ -545,7 +777,12 @@ export function RichTextEditor({ through, which a backdrop blur alone did not guarantee (browsers that skip backdrop-filter drew the text sharp under the buttons). */}
setFontSizeMenuOpen((v) => !v)} - title={tToolbar("font_size")} + title={currentFontSize ? `${tToolbar("font_size")}: ${currentFontSize}` : tToolbar("font_size")} > {currentFontSize ? ( @@ -575,17 +816,25 @@ export function RichTextEditor({ )} {fontSizeMenuOpen && ( -
+ setFontSizeMenuOpen(false)} + className="p-1 min-w-[64px]" + > {FONT_SIZES.map((size) => ( ))} -
+
-
+ )}
editor.chain().focus().toggleBold().run()} title={tToolbar("bold")} > editor.chain().focus().toggleItalic().run()} title={tToolbar("italic")} > editor.chain().focus().toggleUnderline().run()} title={tToolbar("underline")} > editor.chain().focus().toggleStrike().run()} title={tToolbar("strikethrough")} > @@ -640,45 +894,39 @@ export function RichTextEditor({
setColorMenuOpen((v) => !v)} title={tToolbar("text_color")} > {/* The icon itself previews the active colour - no layout shift. */} - + {colorMenuOpen && ( -
-
- {TEXT_COLORS.map((color) => ( -
-
- -
+ } + )} + )}
{/* Background color - same palette, same grid and same swatch @@ -688,7 +936,11 @@ export function RichTextEditor({ survives email round-trips like `color` does. */}
setBgColorMenuOpen((v) => !v)} title={tToolbar("background_color")} > @@ -702,51 +954,43 @@ export function RichTextEditor({ {bgColorMenuOpen && ( -
-
- {TEXT_COLORS.map((color) => ( -
-
- -
+ } + )} + )}
editor.chain().focus().toggleHeading({ level: 1 }).run()} title={tToolbar("heading_1")} > editor.chain().focus().toggleHeading({ level: 2 }).run()} title={tToolbar("heading_2")} > @@ -756,28 +1000,32 @@ export function RichTextEditor({ editor.chain().focus().toggleBulletList().run()} title={tToolbar("bullet_list")} > editor.chain().focus().toggleOrderedList().run()} title={tToolbar("ordered_list")} > editor.chain().focus().toggleBlockquote().run()} title={tToolbar("quote")} > editor.chain().focus().toggleCodeBlock().run()} title={tToolbar("code_block")} > @@ -787,21 +1035,24 @@ export function RichTextEditor({ editor.chain().focus().setTextAlign("left").run()} title={tToolbar("align_left")} > editor.chain().focus().setTextAlign("center").run()} title={tToolbar("align_center")} > editor.chain().focus().setTextAlign("right").run()} title={tToolbar("align_right")} > @@ -810,7 +1061,8 @@ export function RichTextEditor({ {rtlEditingSupport && ( { @@ -827,7 +1079,10 @@ export function RichTextEditor({ @@ -836,90 +1091,62 @@ export function RichTextEditor({
setTableMenuOpen((v) => !v)} title={tToolbar("table")} > {tableMenuOpen && ( -
- {editor.isActive("table") ? ( + editor.isActive("table") ? ( + setTableMenuOpen(false)} + className="p-2 min-w-[200px]" + >
- - - - -
- - - -
- + {tableMenuItem(, tToolbar("add_row_above"), () => editor.chain().focus().addRowBefore().run())} + {tableMenuItem(, tToolbar("add_row_below"), () => editor.chain().focus().addRowAfter().run())} + {tableMenuItem(, tToolbar("add_column_before"), () => editor.chain().focus().addColumnBefore().run())} + {tableMenuItem(, tToolbar("add_column_after"), () => editor.chain().focus().addColumnAfter().run())} +
+ {tableMenuItem(, tToolbar("delete_row"), () => editor.chain().focus().deleteRow().run())} + {tableMenuItem(, tToolbar("delete_column"), () => editor.chain().focus().deleteColumn().run())} + {tableMenuItem(, tToolbar("toggle_header_row"), () => editor.chain().focus().toggleHeaderRow().run())} +
+ {tableMenuItem(, tToolbar("delete_table"), () => editor.chain().focus().deleteTable().run(), true)}
- ) : ( + + ) : ( + setTableMenuOpen(false)} + className="p-2 min-w-[200px]" + > { editor.chain().focus().insertTable({ rows, cols, withHeaderRow: true }).run(); setTableMenuOpen(false); }} /> - )} -
+ + ) )}
editor.chain().focus().clearNodes().unsetAllMarks().run()} title={tToolbar("clear_formatting")} > @@ -929,6 +1156,7 @@ export function RichTextEditor({ editor.chain().focus().undo().run()} disabled={!editor.can().undo()} title={tToolbar("undo")} @@ -936,6 +1164,7 @@ export function RichTextEditor({ editor.chain().focus().redo().run()} disabled={!editor.can().redo()} title={tToolbar("redo")} diff --git a/locales/ar/common.json b/locales/ar/common.json index 5db0358a3..c2c6df773 100644 --- a/locales/ar/common.json +++ b/locales/ar/common.json @@ -994,7 +994,26 @@ "font_size": "حجم الخط", "font_size_default": "افتراضي", "background_color": "لون الخلفية", - "remove_background_color": "إزالة لون الخلفية" + "remove_background_color": "إزالة لون الخلفية", + "table_size": "جدول {rows} × {cols}", + "colors": { + "black": "أسود", + "dark_gray": "رمادي داكن", + "gray": "رمادي", + "dark_red": "أحمر داكن", + "orange": "برتقالي", + "yellow": "أصفر", + "green": "أخضر", + "blue": "أزرق", + "purple": "بنفسجي", + "dark_pink": "وردي داكن", + "brown": "بني", + "red": "أحمر", + "light_orange": "برتقالي فاتح", + "light_green": "أخضر فاتح", + "light_blue": "أزرق فاتح", + "pink": "وردي" + } }, "send_filing_warning": "تم الإرسال - لكن التنظيف بعد الإرسال فشل، وقد تبقى مسودة قديمة.", "pop_out": "فتح الرد في نافذة منفصلة", diff --git a/locales/ca/common.json b/locales/ca/common.json index 75904f1b9..f1dc5d073 100644 --- a/locales/ca/common.json +++ b/locales/ca/common.json @@ -994,7 +994,26 @@ "font_size": "Mida de la lletra", "font_size_default": "Per defecte", "background_color": "Color de fons", - "remove_background_color": "Elimina el color de fons" + "remove_background_color": "Elimina el color de fons", + "table_size": "Taula de {rows} × {cols}", + "colors": { + "black": "Negre", + "dark_gray": "Gris fosc", + "gray": "Gris", + "dark_red": "Vermell fosc", + "orange": "Taronja", + "yellow": "Groc", + "green": "Verd", + "blue": "Blau", + "purple": "Morat", + "dark_pink": "Rosa fosc", + "brown": "Marró", + "red": "Vermell", + "light_orange": "Taronja clar", + "light_green": "Verd clar", + "light_blue": "Blau clar", + "pink": "Rosa" + } }, "send_filing_warning": "Enviat, però la neteja posterior a l'enviament ha fallat; és possible que quedi un esborrany obsolet.", "pop_out": "Obre la resposta en una finestra", diff --git a/locales/cs/common.json b/locales/cs/common.json index ee57be7a1..c852b15b4 100644 --- a/locales/cs/common.json +++ b/locales/cs/common.json @@ -994,7 +994,26 @@ "font_size": "Velikost písma", "font_size_default": "Výchozí", "background_color": "Barva pozadí", - "remove_background_color": "Odebrat barvu pozadí" + "remove_background_color": "Odebrat barvu pozadí", + "table_size": "Tabulka {rows} × {cols}", + "colors": { + "black": "Černá", + "dark_gray": "Tmavě šedá", + "gray": "Šedá", + "dark_red": "Tmavě červená", + "orange": "Oranžová", + "yellow": "Žlutá", + "green": "Zelená", + "blue": "Modrá", + "purple": "Fialová", + "dark_pink": "Tmavě růžová", + "brown": "Hnědá", + "red": "Červená", + "light_orange": "Světle oranžová", + "light_green": "Světle zelená", + "light_blue": "Světle modrá", + "pink": "Růžová" + } }, "send_filing_warning": "Odesláno - ale následný úklid selhal, může zůstat zastaralý koncept.", "pop_out": "Otevřít odpověď v okně", diff --git a/locales/da/common.json b/locales/da/common.json index 39b3b2692..5276d0309 100644 --- a/locales/da/common.json +++ b/locales/da/common.json @@ -994,7 +994,26 @@ "font_size": "Skriftstørrelse", "font_size_default": "Standard", "background_color": "Baggrundsfarve", - "remove_background_color": "Fjern baggrundsfarve" + "remove_background_color": "Fjern baggrundsfarve", + "table_size": "Tabel med {rows} × {cols}", + "colors": { + "black": "Sort", + "dark_gray": "Mørkegrå", + "gray": "Grå", + "dark_red": "Mørkerød", + "orange": "Orange", + "yellow": "Gul", + "green": "Grøn", + "blue": "Blå", + "purple": "Lilla", + "dark_pink": "Mørk pink", + "brown": "Brun", + "red": "Rød", + "light_orange": "Lys orange", + "light_green": "Lysegrøn", + "light_blue": "Lyseblå", + "pink": "Pink" + } }, "send_filing_warning": "Sendt - men oprydningen bagefter mislykkedes, en forældet kladde kan blive stående.", "pop_out": "Åbn svaret i et vindue", diff --git a/locales/de/common.json b/locales/de/common.json index a6910dc46..b6487ef9b 100644 --- a/locales/de/common.json +++ b/locales/de/common.json @@ -994,7 +994,26 @@ "font_size": "Schriftgröße", "font_size_default": "Standard", "background_color": "Hintergrundfarbe", - "remove_background_color": "Hintergrundfarbe entfernen" + "remove_background_color": "Hintergrundfarbe entfernen", + "table_size": "Tabelle mit {rows} × {cols} Zellen", + "colors": { + "black": "Schwarz", + "dark_gray": "Dunkelgrau", + "gray": "Grau", + "dark_red": "Dunkelrot", + "orange": "Orange", + "yellow": "Gelb", + "green": "Grün", + "blue": "Blau", + "purple": "Lila", + "dark_pink": "Dunkelrosa", + "brown": "Braun", + "red": "Rot", + "light_orange": "Hellorange", + "light_green": "Hellgrün", + "light_blue": "Hellblau", + "pink": "Rosa" + } }, "send_filing_warning": "Gesendet - aber das Aufräumen danach schlug fehl, evtl. bleibt ein alter Entwurf sichtbar.", "pop_out": "Antwort in eigenem Fenster öffnen", diff --git a/locales/en/common.json b/locales/en/common.json index c7a1f80b8..dc4f03c84 100644 --- a/locales/en/common.json +++ b/locales/en/common.json @@ -996,7 +996,26 @@ "font_size": "Font size", "font_size_default": "Default", "background_color": "Background color", - "remove_background_color": "Remove background color" + "remove_background_color": "Remove background color", + "table_size": "{rows} by {cols} table", + "colors": { + "black": "Black", + "dark_gray": "Dark gray", + "gray": "Gray", + "dark_red": "Dark red", + "orange": "Orange", + "yellow": "Yellow", + "green": "Green", + "blue": "Blue", + "purple": "Purple", + "dark_pink": "Dark pink", + "brown": "Brown", + "red": "Red", + "light_orange": "Light orange", + "light_green": "Light green", + "light_blue": "Light blue", + "pink": "Pink" + } }, "send_filing_warning": "Sent - but the post-send cleanup failed, a stale draft may remain.", "pop_out": "Pop out reply", diff --git a/locales/es/common.json b/locales/es/common.json index 91af2be98..2f3d75fff 100644 --- a/locales/es/common.json +++ b/locales/es/common.json @@ -994,7 +994,26 @@ "font_size": "Tamaño de fuente", "font_size_default": "Predeterminado", "background_color": "Color de fondo", - "remove_background_color": "Quitar color de fondo" + "remove_background_color": "Quitar color de fondo", + "table_size": "Tabla de {rows} × {cols}", + "colors": { + "black": "Negro", + "dark_gray": "Gris oscuro", + "gray": "Gris", + "dark_red": "Rojo oscuro", + "orange": "Naranja", + "yellow": "Amarillo", + "green": "Verde", + "blue": "Azul", + "purple": "Morado", + "dark_pink": "Rosa oscuro", + "brown": "Marrón", + "red": "Rojo", + "light_orange": "Naranja claro", + "light_green": "Verde claro", + "light_blue": "Azul claro", + "pink": "Rosa" + } }, "send_filing_warning": "Enviado - pero la limpieza posterior falló, puede quedar un borrador obsoleto.", "pop_out": "Abrir la respuesta en una ventana", diff --git a/locales/et/common.json b/locales/et/common.json index dea77cb44..4736beb2c 100644 --- a/locales/et/common.json +++ b/locales/et/common.json @@ -994,7 +994,26 @@ "font_size": "Fondi suurus", "font_size_default": "Vaikimisi", "background_color": "Taustavärv", - "remove_background_color": "Eemalda taustavärv" + "remove_background_color": "Eemalda taustavärv", + "table_size": "Tabel {rows} × {cols}", + "colors": { + "black": "Must", + "dark_gray": "Tumehall", + "gray": "Hall", + "dark_red": "Tumepunane", + "orange": "Oranž", + "yellow": "Kollane", + "green": "Roheline", + "blue": "Sinine", + "purple": "Lilla", + "dark_pink": "Tumeroosa", + "brown": "Pruun", + "red": "Punane", + "light_orange": "Heleoranž", + "light_green": "Heleroheline", + "light_blue": "Helesinine", + "pink": "Roosa" + } }, "send_filing_warning": "Saadetud - kuid saatmisjärgne koristus ebaõnnestus, mustand võib alles jääda.", "pop_out": "Ava vastus eraldi aknas", diff --git a/locales/fa/common.json b/locales/fa/common.json index 2ea7c1e50..bd553ce61 100644 --- a/locales/fa/common.json +++ b/locales/fa/common.json @@ -994,7 +994,26 @@ "font_size": "اندازه فونت", "font_size_default": "پیش‌فرض", "background_color": "رنگ پس‌زمینه", - "remove_background_color": "حذف رنگ پس‌زمینه" + "remove_background_color": "حذف رنگ پس‌زمینه", + "table_size": "جدول {rows} × {cols}", + "colors": { + "black": "سیاه", + "dark_gray": "خاکستری تیره", + "gray": "خاکستری", + "dark_red": "قرمز تیره", + "orange": "نارنجی", + "yellow": "زرد", + "green": "سبز", + "blue": "آبی", + "purple": "بنفش", + "dark_pink": "صورتی تیره", + "brown": "قهوه‌ای", + "red": "قرمز", + "light_orange": "نارنجی روشن", + "light_green": "سبز روشن", + "light_blue": "آبی روشن", + "pink": "صورتی" + } }, "send_filing_warning": "ارسال شد - اما پاک‌سازی پس از ارسال ناموفق بود، ممکن است پیش‌نویس قدیمی باقی بماند.", "pop_out": "باز کردن پاسخ در پنجره جدا", diff --git a/locales/fr/common.json b/locales/fr/common.json index b437e76f0..dcc0845f4 100644 --- a/locales/fr/common.json +++ b/locales/fr/common.json @@ -994,7 +994,26 @@ "font_size": "Taille de police", "font_size_default": "Par défaut", "background_color": "Couleur d'arrière-plan", - "remove_background_color": "Supprimer la couleur d'arrière-plan" + "remove_background_color": "Supprimer la couleur d'arrière-plan", + "table_size": "Tableau de {rows} × {cols}", + "colors": { + "black": "Noir", + "dark_gray": "Gris foncé", + "gray": "Gris", + "dark_red": "Rouge foncé", + "orange": "Orange", + "yellow": "Jaune", + "green": "Vert", + "blue": "Bleu", + "purple": "Violet", + "dark_pink": "Rose foncé", + "brown": "Marron", + "red": "Rouge", + "light_orange": "Orange clair", + "light_green": "Vert clair", + "light_blue": "Bleu clair", + "pink": "Rose" + } }, "send_filing_warning": "Envoyé - mais le nettoyage après envoi a échoué, un ancien brouillon peut subsister.", "pop_out": "Ouvrir la réponse dans une fenêtre", diff --git a/locales/he/common.json b/locales/he/common.json index a9bd0abf7..30d02eef5 100644 --- a/locales/he/common.json +++ b/locales/he/common.json @@ -959,7 +959,26 @@ "font_size": "גודל גופן", "font_size_default": "ברירת מחדל", "background_color": "צבע רקע", - "remove_background_color": "הסר צבע רקע" + "remove_background_color": "הסר צבע רקע", + "table_size": "טבלה {rows} × {cols}", + "colors": { + "black": "שחור", + "dark_gray": "אפור כהה", + "gray": "אפור", + "dark_red": "אדום כהה", + "orange": "כתום", + "yellow": "צהוב", + "green": "ירוק", + "blue": "כחול", + "purple": "סגול", + "dark_pink": "ורוד כהה", + "brown": "חום", + "red": "אדום", + "light_orange": "כתום בהיר", + "light_green": "ירוק בהיר", + "light_blue": "כחול בהיר", + "pink": "ורוד" + } }, "send_filing_warning": "נשלח - אך הניקוי שלאחר השליחה נכשל, ייתכן שתישאר טיוטה ישנה.", "pop_out": "פתיחת התשובה בחלון נפרד", diff --git a/locales/hu/common.json b/locales/hu/common.json index 39373404c..92f603d84 100644 --- a/locales/hu/common.json +++ b/locales/hu/common.json @@ -994,7 +994,26 @@ "font_size": "Betűméret", "font_size_default": "Alapértelmezett", "background_color": "Háttérszín", - "remove_background_color": "Háttérszín eltávolítása" + "remove_background_color": "Háttérszín eltávolítása", + "table_size": "{rows} × {cols} méretű táblázat", + "colors": { + "black": "Fekete", + "dark_gray": "Sötétszürke", + "gray": "Szürke", + "dark_red": "Sötétvörös", + "orange": "Narancs", + "yellow": "Sárga", + "green": "Zöld", + "blue": "Kék", + "purple": "Lila", + "dark_pink": "Sötét rózsaszín", + "brown": "Barna", + "red": "Piros", + "light_orange": "Világos narancs", + "light_green": "Világoszöld", + "light_blue": "Világoskék", + "pink": "Rózsaszín" + } }, "send_filing_warning": "Elküldve - de az utólagos rendrakás nem sikerült, egy elavult piszkozat megmaradhat.", "pop_out": "Válasz megnyitása külön ablakban", diff --git a/locales/it/common.json b/locales/it/common.json index ad41259cc..16c5d34f6 100644 --- a/locales/it/common.json +++ b/locales/it/common.json @@ -994,7 +994,26 @@ "font_size": "Dimensione carattere", "font_size_default": "Predefinita", "background_color": "Colore di sfondo", - "remove_background_color": "Rimuovi colore di sfondo" + "remove_background_color": "Rimuovi colore di sfondo", + "table_size": "Tabella {rows} × {cols}", + "colors": { + "black": "Nero", + "dark_gray": "Grigio scuro", + "gray": "Grigio", + "dark_red": "Rosso scuro", + "orange": "Arancione", + "yellow": "Giallo", + "green": "Verde", + "blue": "Blu", + "purple": "Viola", + "dark_pink": "Rosa scuro", + "brown": "Marrone", + "red": "Rosso", + "light_orange": "Arancione chiaro", + "light_green": "Verde chiaro", + "light_blue": "Azzurro", + "pink": "Rosa" + } }, "send_filing_warning": "Inviato - ma la pulizia successiva non è riuscita, potrebbe restare una bozza obsoleta.", "pop_out": "Apri la risposta in una finestra", diff --git a/locales/ja/common.json b/locales/ja/common.json index 3e33b342a..8baff04cc 100644 --- a/locales/ja/common.json +++ b/locales/ja/common.json @@ -994,7 +994,26 @@ "font_size": "文字サイズ", "font_size_default": "デフォルト", "background_color": "背景色", - "remove_background_color": "背景色を削除" + "remove_background_color": "背景色を削除", + "table_size": "{rows} × {cols} の表", + "colors": { + "black": "黒", + "dark_gray": "濃い灰色", + "gray": "灰色", + "dark_red": "濃い赤", + "orange": "オレンジ", + "yellow": "黄", + "green": "緑", + "blue": "青", + "purple": "紫", + "dark_pink": "濃いピンク", + "brown": "茶", + "red": "赤", + "light_orange": "明るいオレンジ", + "light_green": "明るい緑", + "light_blue": "水色", + "pink": "ピンク" + } }, "send_filing_warning": "送信されましたが、送信後の整理に失敗しました。古い下書きが残る場合があります。", "pop_out": "返信を別ウィンドウで開く", diff --git a/locales/ko/common.json b/locales/ko/common.json index 482a3690d..213103b1a 100644 --- a/locales/ko/common.json +++ b/locales/ko/common.json @@ -994,7 +994,26 @@ "font_size": "글자 크기", "font_size_default": "기본값", "background_color": "배경색", - "remove_background_color": "배경색 제거" + "remove_background_color": "배경색 제거", + "table_size": "{rows} × {cols} 표", + "colors": { + "black": "검정", + "dark_gray": "진한 회색", + "gray": "회색", + "dark_red": "진한 빨강", + "orange": "주황", + "yellow": "노랑", + "green": "초록", + "blue": "파랑", + "purple": "보라", + "dark_pink": "진한 분홍", + "brown": "갈색", + "red": "빨강", + "light_orange": "연한 주황", + "light_green": "연한 초록", + "light_blue": "하늘색", + "pink": "분홍" + } }, "send_filing_warning": "보냈지만 전송 후 정리에 실패했습니다. 오래된 임시 보관 메일이 남아 있을 수 있습니다.", "pop_out": "답장을 별도 창에서 열기", diff --git a/locales/lv/common.json b/locales/lv/common.json index a6d3d8fe5..2bfaf5861 100644 --- a/locales/lv/common.json +++ b/locales/lv/common.json @@ -994,7 +994,26 @@ "font_size": "Fonta izmērs", "font_size_default": "Noklusējums", "background_color": "Fona krāsa", - "remove_background_color": "Noņemt fona krāsu" + "remove_background_color": "Noņemt fona krāsu", + "table_size": "Tabula {rows} × {cols}", + "colors": { + "black": "Melna", + "dark_gray": "Tumši pelēka", + "gray": "Pelēka", + "dark_red": "Tumši sarkana", + "orange": "Oranža", + "yellow": "Dzeltena", + "green": "Zaļa", + "blue": "Zila", + "purple": "Violeta", + "dark_pink": "Tumši rozā", + "brown": "Brūna", + "red": "Sarkana", + "light_orange": "Gaiši oranža", + "light_green": "Gaiši zaļa", + "light_blue": "Gaiši zila", + "pink": "Rozā" + } }, "send_filing_warning": "Nosūtīts - bet pēcapstrāde neizdevās, var palikt novecojis melnraksts.", "pop_out": "Atvērt atbildi atsevišķā logā", diff --git a/locales/mn/common.json b/locales/mn/common.json index 1e7650392..c7fe01c33 100644 --- a/locales/mn/common.json +++ b/locales/mn/common.json @@ -994,7 +994,26 @@ "font_size": "Фонтын хэмжээ", "font_size_default": "Үндсэн", "background_color": "Дэвсгэр өнгө", - "remove_background_color": "Дэвсгэр өнгийг арилгах" + "remove_background_color": "Дэвсгэр өнгийг арилгах", + "table_size": "{rows} × {cols} хүснэгт", + "colors": { + "black": "Хар", + "dark_gray": "Бараан саарал", + "gray": "Саарал", + "dark_red": "Бараан улаан", + "orange": "Улбар шар", + "yellow": "Шар", + "green": "Ногоон", + "blue": "Цэнхэр", + "purple": "Нил ягаан", + "dark_pink": "Бараан ягаан", + "brown": "Хүрэн", + "red": "Улаан", + "light_orange": "Цайвар улбар шар", + "light_green": "Цайвар ногоон", + "light_blue": "Цайвар цэнхэр", + "pink": "Ягаан" + } }, "send_filing_warning": "Илгээсэн - гэхдээ илгээсний дараах цэвэрлэгээ амжилтгүй болсон тул хуучирсан ноорог үлдэж болзошгүй.", "pop_out": "Хариуг тусдаа цонхонд нээх", diff --git a/locales/nb/common.json b/locales/nb/common.json index 82c20ba60..51e19706f 100644 --- a/locales/nb/common.json +++ b/locales/nb/common.json @@ -994,7 +994,26 @@ "font_size": "Skriftstørrelse", "font_size_default": "Standard", "background_color": "Bakgrunnsfarge", - "remove_background_color": "Fjern bakgrunnsfarge" + "remove_background_color": "Fjern bakgrunnsfarge", + "table_size": "Tabell med {rows} × {cols}", + "colors": { + "black": "Svart", + "dark_gray": "Mørkegrå", + "gray": "Grå", + "dark_red": "Mørkerød", + "orange": "Oransje", + "yellow": "Gul", + "green": "Grønn", + "blue": "Blå", + "purple": "Lilla", + "dark_pink": "Mørk rosa", + "brown": "Brun", + "red": "Rød", + "light_orange": "Lys oransje", + "light_green": "Lysegrønn", + "light_blue": "Lyseblå", + "pink": "Rosa" + } }, "send_filing_warning": "Meldingen ble sendt, men oppryddingen etterpå mislyktes. Et gammelt utkast kan bli liggende.", "pop_out": "Åpne svaret i et eget vindu", diff --git a/locales/nl/common.json b/locales/nl/common.json index a1f0d6b51..489fae773 100644 --- a/locales/nl/common.json +++ b/locales/nl/common.json @@ -994,7 +994,26 @@ "font_size": "Lettergrootte", "font_size_default": "Standaard", "background_color": "Achtergrondkleur", - "remove_background_color": "Achtergrondkleur verwijderen" + "remove_background_color": "Achtergrondkleur verwijderen", + "table_size": "Tabel van {rows} × {cols}", + "colors": { + "black": "Zwart", + "dark_gray": "Donkergrijs", + "gray": "Grijs", + "dark_red": "Donkerrood", + "orange": "Oranje", + "yellow": "Geel", + "green": "Groen", + "blue": "Blauw", + "purple": "Paars", + "dark_pink": "Donkerroze", + "brown": "Bruin", + "red": "Rood", + "light_orange": "Lichtoranje", + "light_green": "Lichtgroen", + "light_blue": "Lichtblauw", + "pink": "Roze" + } }, "send_filing_warning": "Verzonden - maar het opruimen daarna is mislukt, mogelijk blijft een oud concept staan.", "pop_out": "Antwoord in een apart venster openen", diff --git a/locales/pl/common.json b/locales/pl/common.json index 5d90e28b9..11e4f05a2 100644 --- a/locales/pl/common.json +++ b/locales/pl/common.json @@ -994,7 +994,26 @@ "font_size": "Rozmiar czcionki", "font_size_default": "Domyślny", "background_color": "Kolor tła", - "remove_background_color": "Usuń kolor tła" + "remove_background_color": "Usuń kolor tła", + "table_size": "Tabela {rows} × {cols}", + "colors": { + "black": "Czarny", + "dark_gray": "Ciemnoszary", + "gray": "Szary", + "dark_red": "Ciemnoczerwony", + "orange": "Pomarańczowy", + "yellow": "Żółty", + "green": "Zielony", + "blue": "Niebieski", + "purple": "Fioletowy", + "dark_pink": "Ciemnoróżowy", + "brown": "Brązowy", + "red": "Czerwony", + "light_orange": "Jasnopomarańczowy", + "light_green": "Jasnozielony", + "light_blue": "Jasnoniebieski", + "pink": "Różowy" + } }, "send_filing_warning": "Wysłano - ale późniejsze porządkowanie nie powiodło się, może pozostać nieaktualna wersja robocza.", "pop_out": "Otwórz odpowiedź w oknie", diff --git a/locales/pt/common.json b/locales/pt/common.json index 5a5836e1f..24ca7ff30 100644 --- a/locales/pt/common.json +++ b/locales/pt/common.json @@ -994,7 +994,26 @@ "font_size": "Tamanho da fonte", "font_size_default": "Padrão", "background_color": "Cor de fundo", - "remove_background_color": "Remover cor de fundo" + "remove_background_color": "Remover cor de fundo", + "table_size": "Tabela de {rows} × {cols}", + "colors": { + "black": "Preto", + "dark_gray": "Cinza-escuro", + "gray": "Cinza", + "dark_red": "Vermelho-escuro", + "orange": "Laranja", + "yellow": "Amarelo", + "green": "Verde", + "blue": "Azul", + "purple": "Roxo", + "dark_pink": "Rosa-escuro", + "brown": "Marrom", + "red": "Vermelho", + "light_orange": "Laranja-claro", + "light_green": "Verde-claro", + "light_blue": "Azul-claro", + "pink": "Rosa" + } }, "send_filing_warning": "Enviado - mas a limpeza posterior falhou, um rascunho antigo pode permanecer.", "pop_out": "Abrir a resposta numa janela", diff --git a/locales/ro/common.json b/locales/ro/common.json index f52b3e6e6..10f9357ad 100644 --- a/locales/ro/common.json +++ b/locales/ro/common.json @@ -994,7 +994,26 @@ "font_size": "Dimensiune font", "font_size_default": "Implicit", "background_color": "Culoarea fundalului", - "remove_background_color": "Elimină culoarea fundalului" + "remove_background_color": "Elimină culoarea fundalului", + "table_size": "Tabel {rows} × {cols}", + "colors": { + "black": "Negru", + "dark_gray": "Gri închis", + "gray": "Gri", + "dark_red": "Roșu închis", + "orange": "Portocaliu", + "yellow": "Galben", + "green": "Verde", + "blue": "Albastru", + "purple": "Mov", + "dark_pink": "Roz închis", + "brown": "Maro", + "red": "Roșu", + "light_orange": "Portocaliu deschis", + "light_green": "Verde deschis", + "light_blue": "Albastru deschis", + "pink": "Roz" + } }, "send_filing_warning": "Trimis - dar curățarea ulterioară a eșuat, poate rămâne o ciornă veche.", "pop_out": "Deschide răspunsul într-o fereastră", diff --git a/locales/ru/common.json b/locales/ru/common.json index e9a827ad6..d2babb8b7 100644 --- a/locales/ru/common.json +++ b/locales/ru/common.json @@ -994,7 +994,26 @@ "font_size": "Размер шрифта", "font_size_default": "По умолчанию", "background_color": "Цвет фона", - "remove_background_color": "Убрать цвет фона" + "remove_background_color": "Убрать цвет фона", + "table_size": "Таблица {rows} × {cols}", + "colors": { + "black": "Чёрный", + "dark_gray": "Тёмно-серый", + "gray": "Серый", + "dark_red": "Тёмно-красный", + "orange": "Оранжевый", + "yellow": "Жёлтый", + "green": "Зелёный", + "blue": "Синий", + "purple": "Фиолетовый", + "dark_pink": "Тёмно-розовый", + "brown": "Коричневый", + "red": "Красный", + "light_orange": "Светло-оранжевый", + "light_green": "Светло-зелёный", + "light_blue": "Голубой", + "pink": "Розовый" + } }, "send_filing_warning": "Отправлено - но последующая очистка не удалась, может остаться устаревший черновик.", "pop_out": "Открыть ответ в отдельном окне", diff --git a/locales/sk/common.json b/locales/sk/common.json index cf9801a20..d1d83dd98 100644 --- a/locales/sk/common.json +++ b/locales/sk/common.json @@ -994,7 +994,26 @@ "font_size": "Veľkosť písma", "font_size_default": "Predvolené", "background_color": "Farba pozadia", - "remove_background_color": "Odstrániť farbu pozadia" + "remove_background_color": "Odstrániť farbu pozadia", + "table_size": "Tabuľka {rows} × {cols}", + "colors": { + "black": "Čierna", + "dark_gray": "Tmavosivá", + "gray": "Sivá", + "dark_red": "Tmavočervená", + "orange": "Oranžová", + "yellow": "Žltá", + "green": "Zelená", + "blue": "Modrá", + "purple": "Fialová", + "dark_pink": "Tmavoružová", + "brown": "Hnedá", + "red": "Červená", + "light_orange": "Svetlooranžová", + "light_green": "Svetlozelená", + "light_blue": "Svetlomodrá", + "pink": "Ružová" + } }, "send_filing_warning": "Odoslané - ale následné upratovanie zlyhalo, môže zostať zastaraný koncept.", "pop_out": "Otvoriť odpoveď v okne", diff --git a/locales/th/common.json b/locales/th/common.json index 39caae761..62801a6cc 100644 --- a/locales/th/common.json +++ b/locales/th/common.json @@ -1233,7 +1233,26 @@ "font_size": "ขนาดตัวอักษร", "font_size_default": "ค่าเริ่มต้น", "background_color": "สีพื้นหลังข้อความ", - "remove_background_color": "ล้างสีพื้นหลัง" + "remove_background_color": "ล้างสีพื้นหลัง", + "table_size": "ตาราง {rows} × {cols}", + "colors": { + "black": "ดำ", + "dark_gray": "เทาเข้ม", + "gray": "เทา", + "dark_red": "แดงเข้ม", + "orange": "ส้ม", + "yellow": "เหลือง", + "green": "เขียว", + "blue": "น้ำเงิน", + "purple": "ม่วง", + "dark_pink": "ชมพูเข้ม", + "brown": "น้ำตาล", + "red": "แดง", + "light_orange": "ส้มอ่อน", + "light_green": "เขียวอ่อน", + "light_blue": "ฟ้า", + "pink": "ชมพู" + } }, "send_filing_warning": "ส่งแล้ว - แต่การล้างข้อมูลหลังการส่งล้มเหลว อาจมีข้อความร่างค้างอยู่", "pop_out": "เปิดการตอบกลับในหน้าต่างใหม่", diff --git a/locales/tr/common.json b/locales/tr/common.json index 7e622f045..acd3fbd42 100644 --- a/locales/tr/common.json +++ b/locales/tr/common.json @@ -994,7 +994,26 @@ "font_size": "Yazı boyutu", "font_size_default": "Varsayılan", "background_color": "Arka plan rengi", - "remove_background_color": "Arka plan rengini kaldır" + "remove_background_color": "Arka plan rengini kaldır", + "table_size": "{rows} × {cols} tablo", + "colors": { + "black": "Siyah", + "dark_gray": "Koyu gri", + "gray": "Gri", + "dark_red": "Koyu kırmızı", + "orange": "Turuncu", + "yellow": "Sarı", + "green": "Yeşil", + "blue": "Mavi", + "purple": "Mor", + "dark_pink": "Koyu pembe", + "brown": "Kahverengi", + "red": "Kırmızı", + "light_orange": "Açık turuncu", + "light_green": "Açık yeşil", + "light_blue": "Açık mavi", + "pink": "Pembe" + } }, "send_filing_warning": "Gönderildi - ancak sonrasındaki temizleme başarısız oldu, eski bir taslak kalabilir.", "pop_out": "Yanıtı ayrı pencerede aç", diff --git a/locales/uk/common.json b/locales/uk/common.json index 9a5d612e4..04da4506e 100644 --- a/locales/uk/common.json +++ b/locales/uk/common.json @@ -994,7 +994,26 @@ "font_size": "Розмір шрифту", "font_size_default": "За замовчуванням", "background_color": "Колір фону", - "remove_background_color": "Прибрати колір фону" + "remove_background_color": "Прибрати колір фону", + "table_size": "Таблиця {rows} × {cols}", + "colors": { + "black": "Чорний", + "dark_gray": "Темно-сірий", + "gray": "Сірий", + "dark_red": "Темно-червоний", + "orange": "Помаранчевий", + "yellow": "Жовтий", + "green": "Зелений", + "blue": "Синій", + "purple": "Фіолетовий", + "dark_pink": "Темно-рожевий", + "brown": "Коричневий", + "red": "Червоний", + "light_orange": "Світло-помаранчевий", + "light_green": "Світло-зелений", + "light_blue": "Блакитний", + "pink": "Рожевий" + } }, "send_filing_warning": "Надіслано - але подальше очищення не вдалося, може залишитися застаріла чернетка.", "pop_out": "Відкрити відповідь в окремому вікні", diff --git a/locales/zh-TW/common.json b/locales/zh-TW/common.json index d1ecdfe11..442f401e4 100644 --- a/locales/zh-TW/common.json +++ b/locales/zh-TW/common.json @@ -994,7 +994,26 @@ "font_size": "字型大小", "font_size_default": "預設", "background_color": "背景顏色", - "remove_background_color": "移除背景顏色" + "remove_background_color": "移除背景顏色", + "table_size": "{rows} × {cols} 表格", + "colors": { + "black": "黑色", + "dark_gray": "深灰色", + "gray": "灰色", + "dark_red": "深紅色", + "orange": "橘色", + "yellow": "黃色", + "green": "綠色", + "blue": "藍色", + "purple": "紫色", + "dark_pink": "深粉紅色", + "brown": "棕色", + "red": "紅色", + "light_orange": "淺橘色", + "light_green": "淺綠色", + "light_blue": "淺藍色", + "pink": "粉紅色" + } }, "send_filing_warning": "郵件已傳送,但寄送後清理失敗,可能仍保留舊草稿。", "pop_out": "在獨立視窗中開啟回覆", diff --git a/locales/zh/common.json b/locales/zh/common.json index 9bfcb397c..a9b379986 100644 --- a/locales/zh/common.json +++ b/locales/zh/common.json @@ -994,7 +994,26 @@ "font_size": "字号", "font_size_default": "默认", "background_color": "背景颜色", - "remove_background_color": "移除背景颜色" + "remove_background_color": "移除背景颜色", + "table_size": "{rows} × {cols} 表格", + "colors": { + "black": "黑色", + "dark_gray": "深灰色", + "gray": "灰色", + "dark_red": "深红色", + "orange": "橙色", + "yellow": "黄色", + "green": "绿色", + "blue": "蓝色", + "purple": "紫色", + "dark_pink": "深粉色", + "brown": "棕色", + "red": "红色", + "light_orange": "浅橙色", + "light_green": "浅绿色", + "light_blue": "浅蓝色", + "pink": "粉色" + } }, "send_filing_warning": "已发送,但发送后的清理失败,可能会残留旧草稿。", "pop_out": "在单独窗口中打开回复",