diff --git a/components/email/__tests__/rich-text-editor-toolbar-overflow.test.tsx b/components/email/__tests__/rich-text-editor-toolbar-overflow.test.tsx new file mode 100644 index 000000000..c45bc217c --- /dev/null +++ b/components/email/__tests__/rich-text-editor-toolbar-overflow.test.tsx @@ -0,0 +1,114 @@ +import { act, fireEvent, render, screen, waitFor, within } from '@testing-library/react'; +import { afterEach, describe, expect, it, vi } from 'vitest'; +import React from 'react'; + +import { RichTextEditor, formattingFoldCount } from '../rich-text-editor'; + +// The formatting toolbar keeps one row: in a narrow composer (585 px at +// 1280x720) it wrapped to two and took the body's room. What doesn't fit goes +// behind "More formatting", the least used first. + +let toolbarWidth = 1200; +const observers = new Set(); +class ReportingResizeObserver { + constructor(private cb: ResizeObserverCallback) {} + observe() { observers.add(this.cb); } + unobserve() {} + disconnect() { observers.delete(this.cb); } +} +vi.stubGlobal('ResizeObserver', ReportingResizeObserver); + +const realClientWidth = Object.getOwnPropertyDescriptor(HTMLElement.prototype, 'clientWidth')!; +Object.defineProperty(HTMLElement.prototype, 'clientWidth', { + configurable: true, + get(this: HTMLElement) { + return this.getAttribute('role') === 'toolbar' ? toolbarWidth : realClientWidth.get!.call(this); + }, +}); + +afterEach(() => { + toolbarWidth = 1200; +}); + +async function setup() { + let ready = false; + render( {}} onEditorReady={() => { ready = true; }} />); + await waitFor(() => expect(ready).toBe(true)); +} + +async function resize(width: number) { + toolbarWidth = width; + await act(async () => { + for (const cb of observers) cb([], {} as ResizeObserver); + }); +} + +const toolbar = () => screen.getByRole('toolbar', { name: 'formatting' }); +const rowItem = (name: string) => toolbar().querySelector(`[data-toolbar-item="${name}"]`); + +describe('formatting toolbar in a narrow composer', () => { + it('shows every button in one row when there is room, without a More button', async () => { + await setup(); + for (const name of ['heading-1', 'quote', 'align-left', 'table', 'undo']) expect(rowItem(name)).not.toBeNull(); + expect(rowItem('more-formatting')).toBeNull(); + }); + + it('moves what does not fit behind "More formatting", and shows it there', async () => { + await setup(); + await resize(561); + + // The least used go first: undo, redo, clear formatting, then alignment. + for (const name of ['undo', 'redo', 'clear-formatting', 'align-left', 'align-right']) expect(rowItem(name)).toBeNull(); + for (const name of ['bold', 'bullet-list', 'link', 'heading-1', 'table']) expect(rowItem(name)).not.toBeNull(); + + const more = rowItem('more-formatting') as HTMLButtonElement; + expect(more).toHaveAccessibleName('more_formatting'); + expect(more).toHaveAttribute('aria-expanded', 'false'); + fireEvent.click(more); + const panel = screen.getByRole('dialog', { name: 'more_formatting' }); + expect(more).toHaveAttribute('aria-expanded', 'true'); + for (const name of ['align-left', 'align-center', 'align-right', 'clear-formatting', 'undo', 'redo']) { + expect(within(panel).getByRole('button', { name: new RegExp(`^${name.replace('-', '_')}`) })).toBeInTheDocument(); + } + }); + + it('keeps the arrows in the row: the panel buttons are no Tab stops of the toolbar', async () => { + await setup(); + await resize(561); + fireEvent.click(rowItem('more-formatting')!); + const panel = screen.getByRole('dialog', { name: 'more_formatting' }); + const rowStops = Array.from(toolbar().querySelectorAll('[data-toolbar-item]')) + .filter((el) => !panel.contains(el) && el.tabIndex === 0); + expect(rowStops).toHaveLength(1); + }); + + it('brings everything back into the row when the composer gets wider', async () => { + await setup(); + await resize(400); + expect(rowItem('heading-1')).toBeNull(); + await resize(1000); + expect(rowItem('heading-1')).not.toBeNull(); + expect(rowItem('undo')).not.toBeNull(); + expect(rowItem('more-formatting')).toBeNull(); + }); +}); + +describe('formattingFoldCount', () => { + const sizes = { button: 28, gap: 2, separator: 5 }; + + it('folds nothing for a toolbar that is not laid out', () => { + expect(formattingFoldCount(0, sizes)).toBe(0); + }); + + it('folds more groups the narrower the row', () => { + const counts = [900, 700, 561, 480, 400, 300].map((width) => formattingFoldCount(width, sizes)); + expect(counts[0]).toBe(0); + for (let i = 1; i < counts.length; i++) expect(counts[i]).toBeGreaterThanOrEqual(counts[i - 1]); + expect(counts[counts.length - 1]).toBe(5); + }); + + it('makes room for the text direction button', () => { + const width = 700; + expect(formattingFoldCount(width, { ...sizes, directionButton: true })).toBeGreaterThanOrEqual(formattingFoldCount(width, sizes)); + }); +}); diff --git a/components/email/rich-text-editor.tsx b/components/email/rich-text-editor.tsx index 3448897cd..d4e4ac6cd 100644 --- a/components/email/rich-text-editor.tsx +++ b/components/email/rich-text-editor.tsx @@ -70,8 +70,56 @@ import { Rows3, Columns3, ImageIcon, + Ellipsis, } from "@/components/icons"; +/** + * Groups of the formatting toolbar that move behind "More formatting" when one + * row is too narrow for all of them, the least used first. Each with its + * buttons and the separators that go with it. + */ +const FOLDABLE_GROUPS = [ + { id: "history", buttons: 3, separators: 2 }, + { id: "align", buttons: 3, separators: 1 }, + { id: "headings", buttons: 2, separators: 1 }, + { id: "table", buttons: 1, separators: 0 }, + { id: "blocks", buttons: 2, separators: 0 }, +] as const; +type FoldableGroup = (typeof FOLDABLE_GROUPS)[number]["id"]; +/** + * Always in the row: font size, bold, italic, underline, strikethrough, the + * two colours, the lists, link, an image's description and More (12), with + * their 3 separators. + */ +const FIXED_TOOLBAR = { buttons: 12, separators: 3 }; + +/** + * How many of FOLDABLE_GROUPS go behind "More formatting" for one row of + * `width` px. 0 for a toolbar that isn't laid out (hidden, or not measured). + */ +export function formattingFoldCount(width: number, sizes: { button: number; gap: number; separator: number; directionButton?: boolean }): number { + if (!(width > 0)) return 0; + const span = (buttons: number, separators: number) => buttons * (sizes.button + sizes.gap) + separators * (sizes.separator + sizes.gap); + const groups = FOLDABLE_GROUPS.map((group) => span(group.buttons + (group.id === "align" && sizes.directionButton ? 1 : 0), group.separators)); + let needed = span(FIXED_TOOLBAR.buttons, FIXED_TOOLBAR.separators) + groups.reduce((sum, group) => sum + group, 0); + // All of it fits without the More button. + if (needed - (sizes.button + sizes.gap) <= width + 1) return 0; + for (let count = 1; count <= groups.length; count++) { + needed -= groups[count - 1]; + if (needed <= width + 1) return count; + } + return groups.length; +} + +/** The toolbar's inner width and the sizes of what is in it, in px. */ +function measureFormattingToolbar(toolbar: HTMLElement, directionButton: boolean): number { + const rem = parseFloat(getComputedStyle(document.documentElement).fontSize) || 16; + const style = getComputedStyle(toolbar); + const width = toolbar.clientWidth - (parseFloat(style.paddingLeft) || 0) - (parseFloat(style.paddingRight) || 0); + const button = toolbar.querySelector(":scope > [data-toolbar-item], :scope > div > [data-toolbar-item]")?.offsetWidth || 1.75 * rem; + return formattingFoldCount(width, { button, gap: 0.125 * rem, separator: 1 + 0.25 * rem, directionButton }); +} + export interface InlineImageUpload { src: string; cid?: string; @@ -699,6 +747,7 @@ export function RichTextEditor({ }, [editor, linkDialog]); const tToolbar = useTranslations("email_composer.toolbar"); + const tWindows = useTranslations("compose_windows"); const tCommon = useTranslations("common"); const [tableMenuOpen, setTableMenuOpen] = useState(false); const tableWrapperRef = useRef(null); @@ -717,11 +766,13 @@ export function RichTextEditor({ // focus last keeps it, the arrows, Home and End move inside. const toolbarRef = useRef(null); const rovingItemRef = useRef(null); + // Buttons in the "More formatting" panel are the panel's, with its own keys. + const inRow = (el: HTMLElement) => !el.closest("[data-toolbar-more-panel]"); const toolbarItems = () => Array.from(toolbarRef.current?.querySelectorAll("[data-toolbar-item]") ?? []) - .filter((el) => !el.disabled); + .filter((el) => !el.disabled && inRow(el)); const syncToolbarTabStops = (focus?: HTMLButtonElement) => { - const all = Array.from(toolbarRef.current?.querySelectorAll("[data-toolbar-item]") ?? []); + const all = Array.from(toolbarRef.current?.querySelectorAll("[data-toolbar-item]") ?? []).filter(inRow); const enabled = all.filter((el) => !el.disabled); const current = focus ?? enabled.find((el) => el.dataset.toolbarItem === rovingItemRef.current) ?? enabled[0]; if (current) rovingItemRef.current = current.dataset.toolbarItem ?? null; @@ -740,6 +791,36 @@ export function RichTextEditor({ return !!stop && document.activeElement === stop; }; + // One row: what doesn't fit goes behind "More formatting" (a wrapped + // second row took the body's room in a narrow composer). + const [foldCount, setFoldCount] = useState(0); + const [moreOpen, setMoreOpen] = useState(false); + const moreButtonRef = useRef(null); + const moreWrapperRef = useRef(null); + const hasEditor = !!editor; + useLayoutEffect(() => { + const toolbar = toolbarRef.current; + if (!toolbar) return; + const measure = () => setFoldCount(measureFormattingToolbar(toolbar, !!rtlEditingSupport)); + measure(); + if (typeof ResizeObserver === "undefined") return; + const observer = new ResizeObserver(() => { + measure(); + setMoreOpen(false); + }); + observer.observe(toolbar); + return () => observer.disconnect(); + }, [hasEditor, rtlEditingSupport]); + + useEffect(() => { + if (!moreOpen) return; + const handler = (e: MouseEvent) => { + if (moreWrapperRef.current && !moreWrapperRef.current.contains(e.target as Node)) setMoreOpen(false); + }; + document.addEventListener("mousedown", handler); + return () => document.removeEventListener("mousedown", handler); + }, [moreOpen]); + useEffect(() => { if (!fontSizeMenuOpen) return; const handler = (e: MouseEvent) => { @@ -799,6 +880,15 @@ export function RichTextEditor({ const altTarget = findAltTarget(editor, altRequest); const imageSelected = editor.isActive("image"); + // A group that doesn't fit renders in the "More formatting" panel instead, in row order. + const folded = new Set(FOLDABLE_GROUPS.slice(0, foldCount).map((group) => group.id)); + const overflow: React.ReactNode[] = []; + const place = (group: FoldableGroup, node: React.ReactNode) => { + if (!folded.has(group)) return node; + overflow.push({node}); + return null; + }; + const onToolbarKeyDown = (e: React.KeyboardEvent) => { const target = e.target as HTMLElement; // Keys inside a popover are the popover's. @@ -819,7 +909,7 @@ export function RichTextEditor({ }; const onToolbarFocus = (e: React.FocusEvent) => { const target = e.target as HTMLElement; - if (target instanceof HTMLButtonElement && target.hasAttribute("data-toolbar-item")) syncToolbarTabStops(target); + if (target instanceof HTMLButtonElement && target.hasAttribute("data-toolbar-item") && inRow(target)) syncToolbarTabStops(target); }; const renderPalette = (current: string | null, apply: (color: string) => void, removeLabel: string, remove: () => void) => ( @@ -1084,6 +1174,7 @@ export function RichTextEditor({ + {place("headings", <> + )} + {place("blocks", <> + )} + {place("align", <> + )} )} -
+ {place("table",
) )} -
+
)} + {place("history", <> + )} + + {overflow.length > 0 && ( +
+ setMoreOpen((v) => !v)} + title={tWindows("more_formatting")} + > + + + {moreOpen && ( + setMoreOpen(false)} + className="p-1 flex w-max max-w-[16rem] flex-wrap items-center gap-0.5" + > +
+ {overflow} +
+
+ )} +
+ )} {/* Editor */} diff --git a/locales/ar/common.json b/locales/ar/common.json index 5bf41a9d5..47150985b 100644 --- a/locales/ar/common.json +++ b/locales/ar/common.json @@ -7066,6 +7066,9 @@ "types_character": "تكتب هذه المفاتيح حرفًا على لوحة مفاتيحك، وسيتوقف عن العمل في كل التطبيقات. جرّب مفاتيح أخرى.", "reserved": "يستخدم النظام أو معظم التطبيقات هذه المفاتيح. جرّب مفاتيح أخرى." }, + "compose_windows": { + "more_formatting": "مزيد من التنسيق" + }, "desktopReminders": { "generic_title": "تذكير", "snooze": "تأجيل {count} د", diff --git a/locales/ca/common.json b/locales/ca/common.json index eca36135b..ec7b404e9 100644 --- a/locales/ca/common.json +++ b/locales/ca/common.json @@ -7066,6 +7066,9 @@ "types_character": "Aquestes tecles escriuen un caràcter al teu teclat, i deixaria de funcionar a totes les aplicacions. Prova altres tecles.", "reserved": "El sistema o la majoria d'aplicacions fan servir aquestes tecles. Prova altres tecles." }, + "compose_windows": { + "more_formatting": "Més format" + }, "desktopReminders": { "generic_title": "Recordatori", "snooze": "Posposa {count} min", diff --git a/locales/cs/common.json b/locales/cs/common.json index f1f5076dd..2201071a4 100644 --- a/locales/cs/common.json +++ b/locales/cs/common.json @@ -7066,6 +7066,9 @@ "types_character": "Tyto klávesy na vaší klávesnici píší znak, který by pak nefungoval v žádné aplikaci. Zkuste jiné klávesy.", "reserved": "Tyto klávesy používá systém nebo většina aplikací. Zkuste jiné klávesy." }, + "compose_windows": { + "more_formatting": "Další formátování" + }, "desktopReminders": { "generic_title": "Připomenutí", "snooze": "Odložit o {count} min", diff --git a/locales/da/common.json b/locales/da/common.json index 7461d652b..9c2068085 100644 --- a/locales/da/common.json +++ b/locales/da/common.json @@ -7066,6 +7066,9 @@ "types_character": "Disse taster skriver et tegn på dit tastatur, og det ville holde op med at virke i alle apps. Prøv andre taster.", "reserved": "Dit system eller de fleste apps bruger disse taster. Prøv andre taster." }, + "compose_windows": { + "more_formatting": "Mere formatering" + }, "desktopReminders": { "generic_title": "Påmindelse", "snooze": "Udsæt {count} min.", diff --git a/locales/de/common.json b/locales/de/common.json index 31d15c0ed..eb87601a1 100644 --- a/locales/de/common.json +++ b/locales/de/common.json @@ -7066,6 +7066,9 @@ "types_character": "Diese Tasten erzeugen auf Ihrer Tastatur ein Zeichen, das dann in keiner App mehr funktionieren würde. Versuchen Sie andere Tasten.", "reserved": "Ihr System oder die meisten Apps verwenden diese Tasten. Versuchen Sie andere Tasten." }, + "compose_windows": { + "more_formatting": "Weitere Formatierung" + }, "desktopReminders": { "generic_title": "Erinnerung", "snooze": "{count} Min. schlummern", diff --git a/locales/en/common.json b/locales/en/common.json index e0491ee32..df18634ac 100644 --- a/locales/en/common.json +++ b/locales/en/common.json @@ -7066,6 +7066,9 @@ "types_character": "These keys type a character on your keyboard, and it would stop working in every app. Try other keys.", "reserved": "Your system or most apps use these keys. Try other keys." }, + "compose_windows": { + "more_formatting": "More formatting" + }, "desktopReminders": { "generic_title": "Reminder", "snooze": "Snooze {count} min", diff --git a/locales/es/common.json b/locales/es/common.json index 1050f0f56..4fdecd792 100644 --- a/locales/es/common.json +++ b/locales/es/common.json @@ -7066,6 +7066,9 @@ "types_character": "Estas teclas escriben un carácter en tu teclado, y dejaría de funcionar en todas las aplicaciones. Prueba otras teclas.", "reserved": "Tu sistema o la mayoría de las aplicaciones usan estas teclas. Prueba otras teclas." }, + "compose_windows": { + "more_formatting": "Más formato" + }, "desktopReminders": { "generic_title": "Recordatorio", "snooze": "Posponer {count} min", diff --git a/locales/et/common.json b/locales/et/common.json index df425b8bd..cde6d1575 100644 --- a/locales/et/common.json +++ b/locales/et/common.json @@ -7066,6 +7066,9 @@ "types_character": "Need klahvid kirjutavad teie klaviatuuril märgi, mis lakkaks kõigis rakendustes töötamast. Proovige teisi klahve.", "reserved": "Neid klahve kasutab süsteem või enamik rakendusi. Proovige teisi klahve." }, + "compose_windows": { + "more_formatting": "Rohkem vormindust" + }, "desktopReminders": { "generic_title": "Meeldetuletus", "snooze": "Lükka {count} min edasi", diff --git a/locales/fa/common.json b/locales/fa/common.json index bf242d98d..fead83ed0 100644 --- a/locales/fa/common.json +++ b/locales/fa/common.json @@ -7066,6 +7066,9 @@ "types_character": "این کلیدها روی صفحه‌کلید شما نویسه‌ای می‌نویسند و آن نویسه دیگر در هیچ برنامه‌ای کار نمی‌کند. کلیدهای دیگری را امتحان کنید.", "reserved": "سیستم یا بیشتر برنامه‌ها از این کلیدها استفاده می‌کنند. کلیدهای دیگری را امتحان کنید." }, + "compose_windows": { + "more_formatting": "قالب‌بندی بیشتر" + }, "desktopReminders": { "generic_title": "یادآوری", "snooze": "تعویق {count} دقیقه", diff --git a/locales/fr/common.json b/locales/fr/common.json index 03545491c..2405729a9 100644 --- a/locales/fr/common.json +++ b/locales/fr/common.json @@ -7066,6 +7066,9 @@ "types_character": "Ces touches produisent un caractère sur votre clavier, et il ne fonctionnerait plus dans aucune application. Essayez d'autres touches.", "reserved": "Votre système ou la plupart des applications utilisent ces touches. Essayez d'autres touches." }, + "compose_windows": { + "more_formatting": "Plus de mise en forme" + }, "desktopReminders": { "generic_title": "Rappel", "snooze": "Reporter de {count} min", diff --git a/locales/he/common.json b/locales/he/common.json index f8258a747..f3c8a59bb 100644 --- a/locales/he/common.json +++ b/locales/he/common.json @@ -7067,6 +7067,9 @@ "types_character": "המקשים האלה מקלידים תו במקלדת שלכם, והוא יפסיק לעבוד בכל האפליקציות. נסו מקשים אחרים.", "reserved": "המערכת או רוב האפליקציות משתמשות במקשים האלה. נסו מקשים אחרים." }, + "compose_windows": { + "more_formatting": "עוד עיצוב" + }, "desktopReminders": { "generic_title": "תזכורת", "snooze": "דחייה ב-{count} דק'", diff --git a/locales/hu/common.json b/locales/hu/common.json index bd65a8ec0..52ce79571 100644 --- a/locales/hu/common.json +++ b/locales/hu/common.json @@ -7066,6 +7066,9 @@ "types_character": "Ezek a billentyűk karaktert írnak az Ön billentyűzetén, amely többé egyik alkalmazásban sem működne. Próbáljon más billentyűket.", "reserved": "Ezeket a billentyűket a rendszer vagy a legtöbb alkalmazás használja. Próbáljon más billentyűket." }, + "compose_windows": { + "more_formatting": "További formázás" + }, "desktopReminders": { "generic_title": "Emlékeztető", "snooze": "Halasztás {count} perccel", diff --git a/locales/it/common.json b/locales/it/common.json index b79013a3a..7fbfffd54 100644 --- a/locales/it/common.json +++ b/locales/it/common.json @@ -7066,6 +7066,9 @@ "types_character": "Questi tasti scrivono un carattere sulla tua tastiera, che smetterebbe di funzionare in tutte le app. Prova altri tasti.", "reserved": "Il sistema o la maggior parte delle app usano questi tasti. Prova altri tasti." }, + "compose_windows": { + "more_formatting": "Altre opzioni di formattazione" + }, "desktopReminders": { "generic_title": "Promemoria", "snooze": "Rimanda di {count} min", diff --git a/locales/ja/common.json b/locales/ja/common.json index 0fc511808..7e2153f1e 100644 --- a/locales/ja/common.json +++ b/locales/ja/common.json @@ -7066,6 +7066,9 @@ "types_character": "このキーはお使いのキーボードで文字を入力します。割り当てると、すべてのアプリでその文字を入力できなくなります。別のキーをお試しください。", "reserved": "このキーはシステムまたは多くのアプリが使用しています。別のキーをお試しください。" }, + "compose_windows": { + "more_formatting": "その他の書式" + }, "desktopReminders": { "generic_title": "リマインダー", "snooze": "{count} 分後に再通知", diff --git a/locales/ko/common.json b/locales/ko/common.json index 21bc7b936..184b4c591 100644 --- a/locales/ko/common.json +++ b/locales/ko/common.json @@ -7066,6 +7066,9 @@ "types_character": "이 키는 사용 중인 키보드에서 문자를 입력하며, 지정하면 모든 앱에서 그 문자를 입력할 수 없게 됩니다. 다른 키를 사용해 보세요.", "reserved": "시스템 또는 대부분의 앱이 이 키를 사용합니다. 다른 키를 사용해 보세요." }, + "compose_windows": { + "more_formatting": "추가 서식" + }, "desktopReminders": { "generic_title": "알림", "snooze": "{count}분 후 다시 알림", diff --git a/locales/lv/common.json b/locales/lv/common.json index 2958f811c..53457abe5 100644 --- a/locales/lv/common.json +++ b/locales/lv/common.json @@ -7066,6 +7066,9 @@ "types_character": "Šie taustiņi jūsu tastatūrā raksta rakstzīmi, kas pārstātu darboties visās lietotnēs. Izmēģiniet citus taustiņus.", "reserved": "Šos taustiņus izmanto sistēma vai lielākā daļa lietotņu. Izmēģiniet citus taustiņus." }, + "compose_windows": { + "more_formatting": "Vairāk formatēšanas" + }, "desktopReminders": { "generic_title": "Atgādinājums", "snooze": "Atlikt par {count} min", diff --git a/locales/mn/common.json b/locales/mn/common.json index 57128748e..1d6eefc33 100644 --- a/locales/mn/common.json +++ b/locales/mn/common.json @@ -7066,6 +7066,9 @@ "types_character": "Эдгээр товч таны гар дээр тэмдэгт бичдэг тул тэр тэмдэгт бүх аппад ажиллахаа болино. Өөр товч туршина уу.", "reserved": "Систем эсвэл ихэнх апп эдгээр товчийг ашигладаг. Өөр товч туршина уу." }, + "compose_windows": { + "more_formatting": "Бусад хэлбэржүүлэлт" + }, "desktopReminders": { "generic_title": "Сануулга", "snooze": "{count} минут хойшлуулах", diff --git a/locales/nb/common.json b/locales/nb/common.json index 27c9f7642..9952353ed 100644 --- a/locales/nb/common.json +++ b/locales/nb/common.json @@ -7066,6 +7066,9 @@ "types_character": "Disse tastene skriver et tegn på tastaturet ditt, og det ville slutte å virke i alle apper. Prøv andre taster.", "reserved": "Systemet ditt eller de fleste apper bruker disse tastene. Prøv andre taster." }, + "compose_windows": { + "more_formatting": "Mer formatering" + }, "desktopReminders": { "generic_title": "Påminnelse", "snooze": "Utsett {count} min", diff --git a/locales/nl/common.json b/locales/nl/common.json index 69e5afbcc..8a57c3d58 100644 --- a/locales/nl/common.json +++ b/locales/nl/common.json @@ -7066,6 +7066,9 @@ "types_character": "Deze toetsen typen een teken op je toetsenbord, en dat zou in geen enkele app meer werken. Probeer andere toetsen.", "reserved": "Je systeem of de meeste apps gebruiken deze toetsen. Probeer andere toetsen." }, + "compose_windows": { + "more_formatting": "Meer opmaak" + }, "desktopReminders": { "generic_title": "Herinnering", "snooze": "{count} min uitstellen", diff --git a/locales/pl/common.json b/locales/pl/common.json index 35e69ac26..7c63c556d 100644 --- a/locales/pl/common.json +++ b/locales/pl/common.json @@ -7066,6 +7066,9 @@ "types_character": "Te klawisze wpisują znak na Twojej klawiaturze i przestałby on działać we wszystkich aplikacjach. Wypróbuj inne klawisze.", "reserved": "Te klawisze są używane przez system lub większość aplikacji. Wypróbuj inne klawisze." }, + "compose_windows": { + "more_formatting": "Więcej formatowania" + }, "desktopReminders": { "generic_title": "Przypomnienie", "snooze": "Odłóż o {count} min", diff --git a/locales/pt/common.json b/locales/pt/common.json index 7a094b8ea..784fe41f3 100644 --- a/locales/pt/common.json +++ b/locales/pt/common.json @@ -7066,6 +7066,9 @@ "types_character": "Estas teclas escrevem um carácter no seu teclado, e ele deixaria de funcionar em todas as aplicações. Experimente outras teclas.", "reserved": "O seu sistema ou a maioria das aplicações usam estas teclas. Experimente outras teclas." }, + "compose_windows": { + "more_formatting": "Mais formatação" + }, "desktopReminders": { "generic_title": "Lembrete", "snooze": "Adiar {count} min", diff --git a/locales/ro/common.json b/locales/ro/common.json index 1c3d817ce..859167031 100644 --- a/locales/ro/common.json +++ b/locales/ro/common.json @@ -7066,6 +7066,9 @@ "types_character": "Aceste taste scriu un caracter pe tastatura ta, iar el n-ar mai funcționa în nicio aplicație. Încearcă alte taste.", "reserved": "Sistemul sau majoritatea aplicațiilor folosesc aceste taste. Încearcă alte taste." }, + "compose_windows": { + "more_formatting": "Mai multe opțiuni de formatare" + }, "desktopReminders": { "generic_title": "Memento", "snooze": "Amână {count} min", diff --git a/locales/ru/common.json b/locales/ru/common.json index c5c112ea2..a69ddb27f 100644 --- a/locales/ru/common.json +++ b/locales/ru/common.json @@ -7066,6 +7066,9 @@ "types_character": "На вашей клавиатуре эти клавиши вводят символ, и он перестал бы работать во всех приложениях. Попробуйте другие клавиши.", "reserved": "Эти клавиши использует система или большинство приложений. Попробуйте другие клавиши." }, + "compose_windows": { + "more_formatting": "Дополнительное форматирование" + }, "desktopReminders": { "generic_title": "Напоминание", "snooze": "Отложить на {count} мин", diff --git a/locales/sk/common.json b/locales/sk/common.json index 81d50e05d..a2d4e899d 100644 --- a/locales/sk/common.json +++ b/locales/sk/common.json @@ -7066,6 +7066,9 @@ "types_character": "Tieto klávesy na vašej klávesnici píšu znak, ktorý by potom nefungoval v žiadnej aplikácii. Skúste iné klávesy.", "reserved": "Tieto klávesy používa systém alebo väčšina aplikácií. Skúste iné klávesy." }, + "compose_windows": { + "more_formatting": "Ďalšie formátovanie" + }, "desktopReminders": { "generic_title": "Pripomienka", "snooze": "Odložiť o {count} min", diff --git a/locales/th/common.json b/locales/th/common.json index c34c371f1..0f75e53a6 100644 --- a/locales/th/common.json +++ b/locales/th/common.json @@ -7074,6 +7074,9 @@ "types_character": "ปุ่มเหล่านี้พิมพ์อักขระบนแป้นพิมพ์ของคุณ และอักขระนั้นจะใช้ไม่ได้ในทุกแอป ลองใช้ปุ่มอื่น", "reserved": "ระบบหรือแอปส่วนใหญ่ใช้ปุ่มเหล่านี้ ลองใช้ปุ่มอื่น" }, + "compose_windows": { + "more_formatting": "การจัดรูปแบบเพิ่มเติม" + }, "desktopReminders": { "generic_title": "การแจ้งเตือน", "snooze": "เลื่อน {count} นาที", diff --git a/locales/tr/common.json b/locales/tr/common.json index 43380e45c..6b6e6b1d6 100644 --- a/locales/tr/common.json +++ b/locales/tr/common.json @@ -7066,6 +7066,9 @@ "types_character": "Bu tuşlar klavyenizde bir karakter yazar ve bu karakter hiçbir uygulamada çalışmaz olur. Başka tuşlar deneyin.", "reserved": "Bu tuşları sisteminiz veya çoğu uygulama kullanıyor. Başka tuşlar deneyin." }, + "compose_windows": { + "more_formatting": "Daha fazla biçimlendirme" + }, "desktopReminders": { "generic_title": "Anımsatıcı", "snooze": "{count} dk ertele", diff --git a/locales/uk/common.json b/locales/uk/common.json index d8d7ecf49..9fce99633 100644 --- a/locales/uk/common.json +++ b/locales/uk/common.json @@ -7066,6 +7066,9 @@ "types_character": "На вашій клавіатурі ці клавіші вводять символ, і він перестав би працювати в усіх застосунках. Спробуйте інші клавіші.", "reserved": "Ці клавіші використовує система або більшість застосунків. Спробуйте інші клавіші." }, + "compose_windows": { + "more_formatting": "Додаткове форматування" + }, "desktopReminders": { "generic_title": "Нагадування", "snooze": "Відкласти на {count} хв", diff --git a/locales/vi/common.json b/locales/vi/common.json index ccb97537d..73d2113ab 100644 --- a/locales/vi/common.json +++ b/locales/vi/common.json @@ -7078,6 +7078,9 @@ "types_character": "Các phím này gõ một ký tự trên bàn phím của bạn, và ký tự đó sẽ không dùng được trong mọi ứng dụng. Hãy thử phím khác.", "reserved": "Hệ thống hoặc hầu hết ứng dụng dùng các phím này. Hãy thử phím khác." }, + "compose_windows": { + "more_formatting": "Thêm định dạng" + }, "desktopReminders": { "generic_title": "Lời nhắc", "snooze": "Báo lại sau {count} phút", diff --git a/locales/zh-TW/common.json b/locales/zh-TW/common.json index 3bc47f02b..599c9c3b5 100644 --- a/locales/zh-TW/common.json +++ b/locales/zh-TW/common.json @@ -7066,6 +7066,9 @@ "types_character": "這些鍵在您的鍵盤上會輸入一個字元,占用後該字元在所有應用程式中都無法輸入。請試試其他按鍵。", "reserved": "系統或大多數應用程式正在使用這些鍵。請試試其他按鍵。" }, + "compose_windows": { + "more_formatting": "更多格式" + }, "desktopReminders": { "generic_title": "提醒", "snooze": "{count} 分鐘後再提醒", diff --git a/locales/zh/common.json b/locales/zh/common.json index d4646cf11..d558b39e7 100644 --- a/locales/zh/common.json +++ b/locales/zh/common.json @@ -7066,6 +7066,9 @@ "types_character": "这些键在您的键盘上会输入一个字符,占用后该字符在所有应用中都无法输入。请试试其他按键。", "reserved": "系统或大多数应用在使用这些键。请试试其他按键。" }, + "compose_windows": { + "more_formatting": "更多格式" + }, "desktopReminders": { "generic_title": "提醒", "snooze": "{count} 分钟后再提醒",