diff --git a/components/email/__tests__/list-a11y.test.tsx b/components/email/__tests__/list-a11y.test.tsx new file mode 100644 index 000000000..002ac396d --- /dev/null +++ b/components/email/__tests__/list-a11y.test.tsx @@ -0,0 +1,119 @@ +import { useRef } from 'react'; +import { act, fireEvent, render, screen } from '@testing-library/react'; +import { describe, expect, it, vi } from 'vitest'; +import { rowA11yProps, rowLabel, useListboxFocus } from '../list-a11y'; + +function Row({ id, selected, onToggle = () => {}, onOpenMenu, onClick }: { + id: string; selected?: boolean; onToggle?: () => void; onOpenMenu?: () => void; onClick?: () => void; +}) { + return ( +
+
+ + +
+
+ ); +} + +function List({ ids, initial: active }: { ids: string[]; initial?: string }) { + const ref = useRef(null); + useListboxFocus(ref, active, `${ids.length}:${active}`); + return ( + <> + +
+
+ {ids.map((id) => )} +
+
+ + ); +} + +const option = (name: string) => screen.getByRole('row', { name }); + +describe('message list as one widget', () => { + it('names a row in one sentence and skips what does not apply', () => { + expect(rowLabel(['unread', 'Anna Berg', 'Budget', false, null, '', 'Has attachment'])).toBe('unread, Anna Berg, Budget, Has attachment'); + }); + + it('keeps one Tab stop: the open message, and takes the row controls out of the Tab order', () => { + render(); + expect(option('row b')).toHaveAttribute('tabindex', '0'); + expect(option('row a')).toHaveAttribute('tabindex', '-1'); + expect(screen.getByText('star a')).toHaveAttribute('tabindex', '-1'); + expect(document.querySelector('input[aria-label="check c"]')).toHaveAttribute('tabindex', '-1'); + expect(option('row b')).toHaveAttribute('aria-selected', 'true'); + expect(option('row b')).toHaveAttribute('aria-current', 'true'); + expect(option('row a')).not.toHaveAttribute('aria-selected'); + }); + + it('settles when a row control is set to -1 again (no observer loop)', async () => { + render(); + const star = screen.getByText('star a'); + star.setAttribute('tabindex', '-1'); + star.setAttribute('tabindex', '0'); + // A loop of observer callbacks would never let this timer run. + await act(() => new Promise((r) => setTimeout(r, 20))); + expect(star).toHaveAttribute('tabindex', '-1'); + }, 2000); + + it('hands the focus from the list itself to the row with the Tab stop', () => { + render(); + act(() => screen.getByTestId('scroller').focus()); + expect(document.activeElement).toBe(option('row b')); + }); + + it('falls back to the first row when nothing is open', () => { + render(); + expect(option('row a')).toHaveAttribute('tabindex', '0'); + }); + + it('moves the focus with the selection while the focus is in the list', () => { + const { rerender } = render(); + act(() => option('row a').focus()); + rerender(); + expect(document.activeElement).toBe(option('row b')); + expect(option('row b')).toHaveAttribute('tabindex', '0'); + expect(option('row a')).toHaveAttribute('tabindex', '-1'); + }); + + it('never pulls the focus out of another part of the page', () => { + const { rerender } = render(); + act(() => screen.getByText('outside').focus()); + rerender(); + expect(document.activeElement).toBe(screen.getByText('outside')); + expect(option('row b')).toHaveAttribute('tabindex', '0'); + }); + + it('writes each row\'s position and the list size for screen readers', () => { + render(); + expect(option('row a')).toHaveAttribute('aria-posinset', '1'); + expect(option('row a')).toHaveAttribute('aria-setsize', '3'); + }); + + it('opens with Enter, ticks with Space and opens the actions with Shift+F10', () => { + const onClick = vi.fn(); + const onToggle = vi.fn(); + const onOpenMenu = vi.fn(); + render(
); + fireEvent.keyDown(option('row a'), { key: 'Enter' }); + fireEvent.keyDown(option('row a'), { key: ' ' }); + fireEvent.keyDown(option('row a'), { key: 'F10', shiftKey: true }); + fireEvent.keyDown(option('row a'), { key: 'ContextMenu' }); + expect(onClick).toHaveBeenCalledTimes(1); + expect(onToggle).toHaveBeenCalledTimes(1); + expect(onOpenMenu).toHaveBeenCalledTimes(2); + }); + + it('leaves keys from a control inside the row to that control', () => { + const onToggle = vi.fn(); + render(
); + fireEvent.keyDown(screen.getByText('star a'), { key: ' ' }); + expect(onToggle).not.toHaveBeenCalled(); + }); +}); diff --git a/components/email/__tests__/thread-list-item.test.tsx b/components/email/__tests__/thread-list-item.test.tsx index 4804e41c3..af8e265a9 100644 --- a/components/email/__tests__/thread-list-item.test.tsx +++ b/components/email/__tests__/thread-list-item.test.tsx @@ -846,7 +846,8 @@ describe('ThreadListItem open message', () => { const { container } = render( {}} onEmailSelect={() => {}} />, ); - expect(container.querySelector('[aria-current="true"]') !== null).toBe(open); + // The open message's row is marked current in the list (SR-01). + expect(container.querySelector('[role="row"][aria-current="true"]') !== null).toBe(open); }); }); @@ -900,3 +901,68 @@ describe('ThreadListItem text direction', () => { } }); }); + +describe('ThreadListItem for screen readers and keyboards (SR-01, MO-18)', () => { + beforeEach(() => { + useSettingsStore.setState({ showPreview: false, announceRowPreview: false, showRowActionButton: false, interfaceLayout: 'default' } as never); + useEmailStore.setState({ selectedEmailKeys: new Set(), selectedMailbox: '' }); + }); + + it('is one row named after sender, subject and state', () => { + renderRow(makeEmail({ keywords: {}, from: [{ name: 'Anna Berg', email: 'anna@example.com' }], subject: 'Budget' })); + const row = screen.getByRole('row'); + const name = row.getAttribute('aria-label') ?? ''; + expect(name).toMatch(/^unread, Anna Berg, Budget, /); + }); + + it('reads the preview only when that setting is on', () => { + const email = makeEmail({ preview: 'Numbers for Q3 attached' }); + const { unmount } = renderRow(email); + expect(screen.getByRole('row').getAttribute('aria-label')).not.toContain('Numbers for Q3'); + unmount(); + useSettingsStore.setState({ announceRowPreview: true } as never); + renderRow(email); + expect(screen.getByRole('row').getAttribute('aria-label')).toContain('Numbers for Q3 attached'); + }); + + it('ticks the row with Space', () => { + renderRow(makeEmail({ id: 'm1' })); + fireEvent.keyDown(screen.getByRole('row'), { key: ' ' }); + expect(useEmailStore.getState().selectedEmailKeys.size).toBe(1); + }); + + it('shows an actions button that opens the row menu when that setting is on', () => { + const onContextMenu = vi.fn(); + const [thread] = groupEmailsByThread([makeEmail()]); + const ui = ( + {}} onEmailSelect={() => {}} onContextMenu={onContextMenu} /> + ); + const { rerender } = render(ui); + expect(screen.queryByRole('button', { name: 'row_actions' })).toBeNull(); + act(() => { useSettingsStore.setState({ showRowActionButton: true } as never); }); + rerender(ui); + fireEvent.click(screen.getByRole('button', { name: 'row_actions' })); + expect(onContextMenu).toHaveBeenCalledTimes(1); + }); + + it('tells screen readers whether a conversation is open and puts its messages a level down', () => { + const first = makeEmail({ id: 'a', threadId: 't', receivedAt: '2024-01-15T09:00:00Z' }); + const second = makeEmail({ id: 'b', threadId: 't', receivedAt: '2024-01-15T10:00:00Z' }); + const [thread] = groupEmailsByThread([first, second]); + render( {}} onEmailSelect={() => {}} />); + const rows = screen.getAllByRole('row'); + expect(rows[0]).toHaveAttribute('aria-expanded', 'true'); + expect(rows[0]).toHaveAttribute('aria-level', '1'); + expect(rows.slice(1).every((r) => r.getAttribute('aria-level') === '2')).toBe(true); + // Each row holds its content in a cell, so its controls are allowed there. + expect(rows[0].querySelector('[role="gridcell"]')).not.toBeNull(); + }); + + it('names the conversation toggle after its subject', () => { + const first = makeEmail({ id: 'a', threadId: 't', subject: 'Trip', receivedAt: '2024-01-15T09:00:00Z' }); + const second = makeEmail({ id: 'b', threadId: 't', subject: 'Trip', receivedAt: '2024-01-15T10:00:00Z' }); + const [thread] = groupEmailsByThread([first, second]); + render( {}} onEmailSelect={() => {}} />); + expect(screen.getByRole('button', { name: /expand_named/ })).toBeTruthy(); + }); +}); diff --git a/components/email/email-list.tsx b/components/email/email-list.tsx index a446b859d..3183e4bc0 100644 --- a/components/email/email-list.tsx +++ b/components/email/email-list.tsx @@ -3,6 +3,7 @@ import { Email, ThreadGroup } from "@/lib/jmap/types"; import { MemoizedThreadListItem, listMailboxRole } from "./thread-list-item"; import { ListColumnsHeader } from "./list-columns"; +import { useListboxFocus } from "./list-a11y"; import { useStableHandlers } from "@/hooks/use-stable-handlers"; import type { Attachment } from "@/lib/jmap/types"; import type { LoadListAttachments } from "@/lib/list-attachments"; @@ -487,6 +488,8 @@ export function EmailList({ [items], ); + // Each row's place among the rows (headers and notes don't count). + const rowPosOfItem = useMemo(() => new Map(itemOfThread.map((item, i) => [item, i + 1])), [itemOfThread]); const virtualizer = useVirtualizer({ count: items.length, getScrollElement: () => parentRef.current, @@ -500,6 +503,14 @@ export function EmailList({ entry?.borderBoxSize?.[0]?.blockSize ?? element.getBoundingClientRect().height, }); + // One Tab stop in the list, and the focus going along with j/k (SR-01/02). + const renderedRows = virtualizer.getVirtualItems(); + useListboxFocus( + parentRef, + selectedEmailKey, + `${items.length}:${renderedRows[0]?.index ?? -1}-${renderedRows[renderedRows.length - 1]?.index ?? -1}:${expandedThreadIds.size}`, + ); + // Another folder, tag, account, unified view or plugin tab - or another // search - opens at the top. The scroll container outlives the switch, so // the new list would otherwise open wherever the last one was scrolled to. @@ -1119,6 +1130,13 @@ export function EmailList({ ) : ( <>
): string { + return parts.filter((p): p is string => typeof p === "string" && p.trim().length > 0).join(", "); +} + +interface RowA11yOptions { + rowKey: string; + label: string; + /** The open message's row. */ + selected: boolean; + /** A conversation row: whether it shows its messages. */ + expanded?: boolean; + /** 2 for a message inside an expanded conversation. */ + level?: number; + /** Ticked for a batch action; only set while something is ticked. */ + checked?: boolean; + selectionMode: boolean; + onToggleCheck: () => void; + /** Opens the row's actions menu, as a right-click would. */ + onOpenMenu?: (e: ReactMouseEvent) => void; +} + +/** Props for a row's outer element. */ +export function rowA11yProps({ rowKey, label, selected, expanded, level = 1, checked, selectionMode, onToggleCheck, onOpenMenu }: RowA11yOptions) { + return { + role: "row" as const, + // The list moves the single Tab stop (useListboxFocus); React leaves an + // attribute it didn't change alone, so -1 here doesn't undo that. + tabIndex: -1, + [ROW_KEY_ATTR]: rowKey, + "aria-label": label, + // Ticked rows are the selection while something is ticked; otherwise the + // open message is. aria-current marks the open message either way. + "aria-selected": selectionMode ? !!checked : selected ? true : undefined, + "aria-current": selected ? ("true" as const) : undefined, + "aria-expanded": expanded, + "aria-level": level, + onKeyDown: (e: ReactKeyboardEvent) => { + // Keys from a control inside the row are that control's. + if (e.target !== e.currentTarget) return; + if (e.key === " ") { + e.preventDefault(); + onToggleCheck(); + } else if (e.key === "Enter" && !e.ctrlKey && !e.metaKey && !e.altKey) { + e.preventDefault(); + e.currentTarget.click(); + } else if (onOpenMenu && (e.key === "ContextMenu" || (e.key === "F10" && e.shiftKey))) { + e.preventDefault(); + const rect = e.currentTarget.getBoundingClientRect(); + onOpenMenu({ + clientX: rect.left + Math.min(48, rect.width / 2), + clientY: rect.top + rect.height / 2, + preventDefault: () => {}, + stopPropagation: () => {}, + currentTarget: e.currentTarget, + target: e.currentTarget, + } as unknown as ReactMouseEvent); + } + }, + }; +} + +const FOCUSABLE_IN_ROW = 'button, a[href], input, select, textarea, [tabindex]:not([tabindex="-1"])'; + +function rowFor(list: HTMLElement, key: string | undefined): HTMLElement | null { + if (!key) return null; + const k = CSS.escape(key); + // A message's own row, else the collapsed conversation that holds it. + return list.querySelector(`[role="row"][${ROW_KEY_ATTR}="${k}"]`) + ?? list.querySelector(`[role="row"][data-thread-keys~="${k}"]`); +} + +/** Writes each rendered row's place in the whole list onto it. */ +function applyPositions(list: HTMLElement): void { + list.querySelectorAll("[data-pos]").forEach((wrapper) => { + const row = wrapper.querySelector('[role="row"]'); + const setSize = wrapper.closest("[data-setsize]")?.dataset.setsize; + if (!row || !setSize) return; + if (row.getAttribute("aria-posinset") !== wrapper.dataset.pos) row.setAttribute("aria-posinset", wrapper.dataset.pos ?? ""); + if (row.getAttribute("aria-setsize") !== setSize) row.setAttribute("aria-setsize", setSize); + }); +} + +/** + * Keeps one Tab stop in the list (the open message's row, else the first + * row), moves the focus with the selection while the focus is in the list, + * and takes the controls inside rows out of the Tab order, also the ones + * that mount later (hover actions appear on pointer-over). + */ +export function useListboxFocus(listRef: RefObject, activeKey: string | undefined, renderVersion: unknown): void { + // Inner controls out of the Tab order. + useEffect(() => { + const list = listRef.current; + if (!list) return; + const demote = (root: ParentNode) => { + root.querySelectorAll('[role="row"] ' + FOCUSABLE_IN_ROW.split(", ").join(', [role="row"] ')).forEach((el) => { + if (el.getAttribute("tabindex") !== "-1") el.setAttribute("tabindex", "-1"); + }); + }; + demote(list); + const observer = new MutationObserver((records) => { + for (const r of records) { + if (r.type === "childList" && r.addedNodes.length) { demote(list); return; } + if (r.type === "attributes" && r.target instanceof HTMLElement && r.target.closest('[role="row"]') && r.target.getAttribute("role") !== "row") { + // Only change what isn't -1 yet: writing the same value again is a + // mutation too, and would call this observer forever. + if (r.target.matches(FOCUSABLE_IN_ROW) && r.target.getAttribute("tabindex") !== "-1") r.target.setAttribute("tabindex", "-1"); + } + } + }); + observer.observe(list, { childList: true, subtree: true, attributes: true, attributeFilter: ["tabindex"] }); + // The list itself takes the focus from the skip link and from the focus + // rescue after a delete; hand it on to the row that holds the Tab stop. + const onFocus = (e: FocusEvent) => { + if (e.target !== list) return; + list.querySelector('[role="row"][tabindex="0"]')?.focus(); + }; + list.addEventListener("focus", onFocus); + return () => { + observer.disconnect(); + list.removeEventListener("focus", onFocus); + }; + }, [listRef]); + + // The single Tab stop, and the focus following the selection. + useEffect(() => { + const list = listRef.current; + if (!list) return; + applyPositions(list); + const target = rowFor(list, activeKey) ?? list.querySelector('[role="row"]'); + list.querySelectorAll('[role="row"][tabindex="0"]').forEach((el) => { + if (el !== target) el.setAttribute("tabindex", "-1"); + }); + if (!target) return; + target.setAttribute("tabindex", "0"); + const active = document.activeElement; + // Only while the keyboard is working the list: never pull the focus out + // of the reader, the composer or a dialog. + if (active && active !== target && list.contains(active)) { + target.focus({ preventScroll: false }); + } + }, [listRef, activeKey, renderVersion]); +} diff --git a/components/email/thread-email-item.tsx b/components/email/thread-email-item.tsx index 64fe672f8..7e80d1945 100644 --- a/components/email/thread-email-item.tsx +++ b/components/email/thread-email-item.tsx @@ -21,6 +21,7 @@ import { useTagDisplay } from "@/hooks/use-tag-display"; import { TagBadge } from "./tag-badge"; import { useListRowShape, LIST_COLUMN_CLASS } from "@/hooks/use-list-row-shape"; import { SizeCell, listLeadClass } from "./list-columns"; +import { rowA11yProps, rowLabel } from "./list-a11y"; interface ThreadEmailItemProps { email: Email; @@ -40,6 +41,8 @@ export function ThreadEmailItem({ onContextMenu, }: ThreadEmailItemProps) { const t = useTranslations('email_viewer'); + const tBatch = useTranslations('email_list.batch_actions'); + const tStatus = useTranslations('email_list'); const isUnread = !email.keywords?.$seen; const isStarred = email.keywords?.$flagged; const isAnswered = email.keywords?.$answered; @@ -103,6 +106,29 @@ export function ThreadEmailItem({ } }; + const senderName = sender?.name || sender?.email?.split('@')[0] || t('unknown_sender'); + const selectLabel = tBatch('select_row', { sender: senderName, subject: email.subject || t('no_subject') }); + const a11y = rowA11yProps({ + rowKey: emailKeyFor(email), + label: rowLabel([ + isUnread && t('unread'), + senderName, + formatDate(emailDisplayDate(email)), + isStarred && tStatus('starred'), + isAnswered && tStatus('replied'), + isForwarded && tStatus('forwarded'), + email.hasAttachment && tStatus('has_attachment'), + // The row shows the message's text where the subject would be. + cleanPreview(email.preview), + ]), + selected: !!selected, + level: 2, + checked: isChecked, + selectionMode: selectedEmailKeys.size > 0, + onToggleCheck: () => toggleEmailSelection(email), + onOpenMenu: onContextMenu ? handleContextMenu : undefined, + }); + const rowHandlers = { onClick: handleClick, onDoubleClick: (e: React.MouseEvent) => { @@ -125,6 +151,7 @@ export function ThreadEmailItem({ {...dragHandlers} {...longPressHandlers} {...rowHandlers} + {...a11y} data-testid="thread-email-row" className={cn( "relative cursor-pointer select-none px-3 transition-colors duration-150", @@ -137,7 +164,7 @@ export function ThreadEmailItem({ )} style={{ paddingBlock: 'var(--density-item-py)' }} > -
+
{lead && ( {selecting ? ( @@ -145,6 +172,7 @@ export function ThreadEmailItem({ onClick={handleCheckboxClick} role="checkbox" aria-checked={isChecked} + aria-label={selectLabel} className={cn("rounded p-1 hover:bg-muted/50", isChecked && "text-primary")} > {isChecked ? : } @@ -198,6 +226,7 @@ export function ThreadEmailItem({
-
+
{/* Checkbox - only visible when in selection mode */} {selectedEmailKeys.size > 0 && ( +
+ )} {swipeEnabled && swipeMeta && ( // Decorative reveal on the physically exposed edge. That edge // follows the (physical) translate direction, not text direction, @@ -603,7 +644,8 @@ const SingleEmailItem = React.forwardRef(
)}
{/* Checkbox - only for extra-compact density (no avatar) while in selection mode */} @@ -891,6 +933,8 @@ const SingleEmailItem = React.forwardRef( {/* Hover Quick Actions */} {hoverActionsArmed && !email.isScheduled && ( + // A cell of its own: a row holds cells, not buttons (SR-01). +
( mailboxRole={currentMailboxRole ?? null} pinToFirstLine={mvRow} /> +
)}
); @@ -1089,6 +1134,8 @@ export const ThreadListItem = React.forwardRef emailKeyFor(e) === selectedEmailKey); const isChecked = thread.emails.some(e => selectedEmailKeys.has(emailKeyFor(e))); + const announceRowPreview = useSettingsStore((state) => state.announceRowPreview); + const showRowActionButton = useSettingsStore((state) => state.showRowActionButton); if (emailCount === 1) { return ( @@ -1194,8 +1241,23 @@ export const ThreadListItem = React.forwardRef { if (onPrefetch && isMousePressOnRow(e)) onPrefetch(latestEmail); }} - aria-current={isSelected ? 'true' : undefined} + {...rowA11yProps({ + rowKey: emailKeyFor(latestEmail), + label: a11yLabel, + selected: isSelected && !isExpanded, + expanded: isExpanded, + checked: isChecked, + selectionMode: selectedEmailKeys.size > 0, + onToggleCheck: toggleThreadSelection, + onOpenMenu: onContextMenu ? handleContextMenu : undefined, + })} + // Every message in the conversation: the open one may be any of them. + data-thread-keys={thread.emails.map((e) => emailKeyFor(e)).join(' ')} data-unread={hasUnread ? 'true' : 'false'} data-mv-mobile-row={mvMobileRow ? "" : undefined} data-mv-row={mvRow && !rowTint ? "" : undefined} @@ -1241,6 +1314,22 @@ export const ThreadListItem = React.forwardRef + {showRowActionButton && onContextMenu && ( + // For pointers that can't hover or right-click (MO-18); keyboards use + // Shift+F10 on the row, so it stays out of the Tab order. +
+ +
+ )} {threadSwipeEnabled && threadSwipeMeta && ( // As on a single message's row: the physically exposed edge.
)}
{/* Checkbox for thread selection - only for extra-compact density (no avatar) while in selection mode */} @@ -1319,7 +1409,7 @@ export const ThreadListItem = React.forwardRef {isLoading ? ( @@ -1364,7 +1454,7 @@ export const ThreadListItem = React.forwardRef {isLoading ? ( @@ -1620,6 +1710,7 @@ export const ThreadListItem = React.forwardRef +
)}
{isExpanded && !isMobile && (
{isLoading ? ( -
- - {t('loading')} +
+
+
) : ( emailsToShow.map((email, index) => ( diff --git a/locales/ar/common.json b/locales/ar/common.json index 4243faa07..9e2c57596 100644 --- a/locales/ar/common.json +++ b/locales/ar/common.json @@ -379,7 +379,9 @@ "folder_loaded": "{folder}: {count, plural, =0 {لا توجد رسائل} one {رسالة واحدة} other {# رسائل}}", "search_results": "{count, plural, =0 {لا توجد نتائج} one {نتيجة واحدة} other {# نتائج}}" }, - "category_tabs": "فئات البريد الوارد" + "category_tabs": "فئات البريد الوارد", + "listbox_label": "الرسائل", + "row_actions": "الإجراءات" }, "shared_tags": { "title": "الوسوم المشتركة", @@ -3569,7 +3571,9 @@ "star": "تمييز المحادثة بنجمة", "unstar": "إزالة النجمة عن المحادثة", "toggle_thread": "تبديل عرض المحادثة", - "messages_count": "{count, plural, zero {لا رسائل} one {رسالة واحدة} two {رسالتان} few {# رسائل} many {# رسالة} other {# رسالة}}" + "messages_count": "{count, plural, zero {لا رسائل} one {رسالة واحدة} two {رسالتان} few {# رسائل} many {# رسالة} other {# رسالة}}", + "expand_named": "توسيع المحادثة: {subject}", + "collapse_named": "طي المحادثة: {subject}" }, "identities": { "modal_title": "إدارة هويات الإرسال", diff --git a/locales/ca/common.json b/locales/ca/common.json index 0c5a08163..f69ea59ce 100644 --- a/locales/ca/common.json +++ b/locales/ca/common.json @@ -379,7 +379,9 @@ "folder_loaded": "{folder}: {count, plural, =0 {cap missatge} one {# missatge} other {# missatges}}", "search_results": "{count, plural, =0 {Cap resultat} one {# resultat} other {# resultats}}" }, - "category_tabs": "Categories de la safata d'entrada" + "category_tabs": "Categories de la safata d'entrada", + "listbox_label": "Missatges", + "row_actions": "Accions" }, "shared_tags": { "title": "Etiquetes compartides", @@ -3569,7 +3571,9 @@ "star": "Marca la conversa amb estrella", "unstar": "Treu l'estrella de la conversa", "toggle_thread": "Commuta el fil", - "messages_count": "{count, plural, one {# missatge} other {# missatges}}" + "messages_count": "{count, plural, one {# missatge} other {# missatges}}", + "expand_named": "Desplega la conversa: {subject}", + "collapse_named": "Replega la conversa: {subject}" }, "identities": { "modal_title": "Gestiona les identitats d'enviament", diff --git a/locales/cs/common.json b/locales/cs/common.json index 16797cc6e..b9562adc5 100644 --- a/locales/cs/common.json +++ b/locales/cs/common.json @@ -379,7 +379,9 @@ "folder_loaded": "{folder}: {count, plural, =0 {žádné zprávy} one {# zpráva} few {# zprávy} other {# zpráv}}", "search_results": "{count, plural, =0 {Žádné výsledky} one {# výsledek} few {# výsledky} other {# výsledků}}" }, - "category_tabs": "Kategorie doručené pošty" + "category_tabs": "Kategorie doručené pošty", + "listbox_label": "Zprávy", + "row_actions": "Akce" }, "shared_tags": { "title": "Sdílené štítky", @@ -3569,7 +3571,9 @@ "star": "Označit konverzaci hvězdičkou", "unstar": "Odebrat hvězdičku z konverzace", "toggle_thread": "Přepnout zobrazení vlákna", - "messages_count": "{count, plural, one {# zpráva} few {# zprávy} many {# zprávy} other {# zpráv}}" + "messages_count": "{count, plural, one {# zpráva} few {# zprávy} many {# zprávy} other {# zpráv}}", + "expand_named": "Rozbalit konverzaci: {subject}", + "collapse_named": "Sbalit konverzaci: {subject}" }, "identities": { "modal_title": "Správa identit odesílatele", diff --git a/locales/da/common.json b/locales/da/common.json index 1e96b2a9a..8a2f58589 100644 --- a/locales/da/common.json +++ b/locales/da/common.json @@ -379,7 +379,9 @@ "folder_loaded": "{folder}: {count, plural, =0 {ingen beskeder} one {# besked} other {# beskeder}}", "search_results": "{count, plural, =0 {Ingen resultater} one {# resultat} other {# resultater}}" }, - "category_tabs": "Indbakkekategorier" + "category_tabs": "Indbakkekategorier", + "listbox_label": "Beskeder", + "row_actions": "Handlinger" }, "shared_tags": { "title": "Delte tags", @@ -3569,7 +3571,9 @@ "star": "Stjernemarkér samtale", "unstar": "Fjern stjernemarkering", "toggle_thread": "Slå tråd til/fra", - "messages_count": "{count, plural, one {# besked} other {# beskeder}}" + "messages_count": "{count, plural, one {# besked} other {# beskeder}}", + "expand_named": "Udvid samtale: {subject}", + "collapse_named": "Skjul samtale: {subject}" }, "identities": { "modal_title": "Administrér afsenderidentiteter", diff --git a/locales/de/common.json b/locales/de/common.json index d08ffdc69..28da5a9d9 100644 --- a/locales/de/common.json +++ b/locales/de/common.json @@ -379,7 +379,9 @@ "folder_loaded": "{folder}: {count, plural, =0 {keine Nachrichten} one {# Nachricht} other {# Nachrichten}}", "search_results": "{count, plural, =0 {Keine Ergebnisse} one {# Ergebnis} other {# Ergebnisse}}" }, - "category_tabs": "Posteingangskategorien" + "category_tabs": "Posteingangskategorien", + "listbox_label": "Nachrichten", + "row_actions": "Aktionen" }, "shared_tags": { "title": "Geteilte Labels", @@ -3569,7 +3571,9 @@ "star": "Unterhaltung mit Stern markieren", "unstar": "Stern von Unterhaltung entfernen", "toggle_thread": "Unterhaltung ein-/ausklappen", - "messages_count": "{count, plural, one {# Nachricht} other {# Nachrichten}}" + "messages_count": "{count, plural, one {# Nachricht} other {# Nachrichten}}", + "expand_named": "Konversation aufklappen: {subject}", + "collapse_named": "Konversation zuklappen: {subject}" }, "identities": { "modal_title": "Sendeidentitäten verwalten", diff --git a/locales/en/common.json b/locales/en/common.json index 69583b6d7..97f087c10 100644 --- a/locales/en/common.json +++ b/locales/en/common.json @@ -379,7 +379,9 @@ "folder_loaded": "{folder}: {count, plural, =0 {no messages} one {# message} other {# messages}}", "search_results": "{count, plural, =0 {No results} one {# result} other {# results}}" }, - "category_tabs": "Inbox categories" + "category_tabs": "Inbox categories", + "listbox_label": "Messages", + "row_actions": "Actions" }, "shared_tags": { "title": "Shared tags", @@ -3569,7 +3571,9 @@ "star": "Star conversation", "unstar": "Unstar conversation", "toggle_thread": "Toggle thread", - "messages_count": "{count, plural, one {# message} other {# messages}}" + "messages_count": "{count, plural, one {# message} other {# messages}}", + "expand_named": "Expand conversation: {subject}", + "collapse_named": "Collapse conversation: {subject}" }, "identities": { "modal_title": "Manage sending identities", diff --git a/locales/es/common.json b/locales/es/common.json index d68b758a7..c39b30f55 100644 --- a/locales/es/common.json +++ b/locales/es/common.json @@ -379,7 +379,9 @@ "folder_loaded": "{folder}: {count, plural, =0 {ningún mensaje} one {# mensaje} other {# mensajes}}", "search_results": "{count, plural, =0 {Sin resultados} one {# resultado} other {# resultados}}" }, - "category_tabs": "Categorías de la bandeja de entrada" + "category_tabs": "Categorías de la bandeja de entrada", + "listbox_label": "Mensajes", + "row_actions": "Acciones" }, "shared_tags": { "title": "Etiquetas compartidas", @@ -3569,7 +3571,9 @@ "star": "Destacar conversación", "unstar": "Quitar destacado de conversación", "toggle_thread": "Expandir/contraer hilo", - "messages_count": "{count, plural, one {# mensaje} other {# mensajes}}" + "messages_count": "{count, plural, one {# mensaje} other {# mensajes}}", + "expand_named": "Expandir conversación: {subject}", + "collapse_named": "Contraer conversación: {subject}" }, "identities": { "modal_title": "Administrar Identidades de Envío", diff --git a/locales/et/common.json b/locales/et/common.json index d62b534de..d2e5a37cc 100644 --- a/locales/et/common.json +++ b/locales/et/common.json @@ -379,7 +379,9 @@ "folder_loaded": "{folder}: {count, plural, =0 {kirju pole} one {# kiri} other {# kirja}}", "search_results": "{count, plural, =0 {Tulemusi pole} one {# tulemus} other {# tulemust}}" }, - "category_tabs": "Postkasti kategooriad" + "category_tabs": "Postkasti kategooriad", + "listbox_label": "Sõnumid", + "row_actions": "Toimingud" }, "shared_tags": { "title": "Jagatud sildid", @@ -3569,7 +3571,9 @@ "star": "Tähista vestlus", "unstar": "Eemalda vestluse tähistus", "toggle_thread": "Lülita vestlus", - "messages_count": "{count, plural, one {# sõnum} other {# sõnumit}}" + "messages_count": "{count, plural, one {# sõnum} other {# sõnumit}}", + "expand_named": "Ava vestlus: {subject}", + "collapse_named": "Sulge vestlus: {subject}" }, "identities": { "modal_title": "Halda saatja identiteete", diff --git a/locales/fa/common.json b/locales/fa/common.json index 9513c8d51..0b543f9c1 100644 --- a/locales/fa/common.json +++ b/locales/fa/common.json @@ -379,7 +379,9 @@ "folder_loaded": "{folder}: {count, plural, =0 {بدون پیام} other {# پیام}}", "search_results": "{count, plural, =0 {نتیجه‌ای نیست} other {# نتیجه}}" }, - "category_tabs": "دسته‌های صندوق ورودی" + "category_tabs": "دسته‌های صندوق ورودی", + "listbox_label": "پیام‌ها", + "row_actions": "اقدامات" }, "shared_tags": { "title": "برچسب‌های مشترک", @@ -3569,7 +3571,9 @@ "star": "ستاره‌دار کردن مکالمه", "unstar": "حذف ستاره مکالمه", "toggle_thread": "تغییر وضعیت رشته", - "messages_count": "{count, plural, one {# پیام} other {# پیام}}" + "messages_count": "{count, plural, one {# پیام} other {# پیام}}", + "expand_named": "باز کردن مکالمه: {subject}", + "collapse_named": "جمع کردن مکالمه: {subject}" }, "identities": { "modal_title": "مدیریت هویت‌های ارسال", diff --git a/locales/fr/common.json b/locales/fr/common.json index 6479c48e7..01aecfc64 100644 --- a/locales/fr/common.json +++ b/locales/fr/common.json @@ -379,7 +379,9 @@ "folder_loaded": "{folder} : {count, plural, =0 {aucun message} one {# message} other {# messages}}", "search_results": "{count, plural, =0 {Aucun résultat} one {# résultat} other {# résultats}}" }, - "category_tabs": "Catégories de la boîte de réception" + "category_tabs": "Catégories de la boîte de réception", + "listbox_label": "Messages", + "row_actions": "Actions" }, "shared_tags": { "title": "Étiquettes partagées", @@ -3569,7 +3571,9 @@ "star": "Marquer la conversation comme favorite", "unstar": "Retirer des favoris", "toggle_thread": "Déplier/replier la discussion", - "messages_count": "{count, plural, one {# message} other {# messages}}" + "messages_count": "{count, plural, one {# message} other {# messages}}", + "expand_named": "Développer la conversation: {subject}", + "collapse_named": "Réduire la conversation: {subject}" }, "identities": { "modal_title": "Gérer les identités d'envoi", diff --git a/locales/he/common.json b/locales/he/common.json index 5694a6c94..f8fc4e385 100644 --- a/locales/he/common.json +++ b/locales/he/common.json @@ -344,7 +344,9 @@ "folder_loaded": "{folder}: {count, plural, =0 {אין הודעות} one {הודעה אחת} other {# הודעות}}", "search_results": "{count, plural, =0 {אין תוצאות} one {תוצאה אחת} other {# תוצאות}}" }, - "category_tabs": "קטגוריות של תיבת הדואר הנכנס" + "category_tabs": "קטגוריות של תיבת הדואר הנכנס", + "listbox_label": "הודעות", + "row_actions": "פעולות" }, "shared_tags": { "title": "תגים משותפים", @@ -3487,7 +3489,9 @@ "star": "שיחת כוכב", "unstar": "הסר שיחה בכוכב", "toggle_thread": "החלף חוט", - "messages_count": "{count, plural, one {הודעה אחת} two {שתי הודעות} other {# הודעות}}" + "messages_count": "{count, plural, one {הודעה אחת} two {שתי הודעות} other {# הודעות}}", + "expand_named": "הרחב את השיחה: {subject}", + "collapse_named": "כווץ את השיחה: {subject}" }, "identities": { "modal_title": "ניהול זהויות שליחה", diff --git a/locales/hu/common.json b/locales/hu/common.json index 383453b1f..9e44b7875 100644 --- a/locales/hu/common.json +++ b/locales/hu/common.json @@ -379,7 +379,9 @@ "folder_loaded": "{folder}: {count, plural, =0 {nincs üzenet} other {# üzenet}}", "search_results": "{count, plural, =0 {Nincs találat} other {# találat}}" }, - "category_tabs": "Beérkezett üzenetek kategóriái" + "category_tabs": "Beérkezett üzenetek kategóriái", + "listbox_label": "Üzenetek", + "row_actions": "Műveletek" }, "shared_tags": { "title": "Megosztott címkék", @@ -3569,7 +3571,9 @@ "star": "Beszélgetés csillagozása", "unstar": "Beszélgetés csillagozásának megszüntetése", "toggle_thread": "Beszélgetés váltása", - "messages_count": "{count, plural, one {# üzenet} other {# üzenet}}" + "messages_count": "{count, plural, one {# üzenet} other {# üzenet}}", + "expand_named": "Beszélgetés kibontása: {subject}", + "collapse_named": "Beszélgetés összecsukása: {subject}" }, "identities": { "modal_title": "Küldő azonosságok kezelése", diff --git a/locales/it/common.json b/locales/it/common.json index 117760a22..6d12e9113 100644 --- a/locales/it/common.json +++ b/locales/it/common.json @@ -379,7 +379,9 @@ "folder_loaded": "{folder}: {count, plural, =0 {nessun messaggio} one {# messaggio} other {# messaggi}}", "search_results": "{count, plural, =0 {Nessun risultato} one {# risultato} other {# risultati}}" }, - "category_tabs": "Categorie della posta in arrivo" + "category_tabs": "Categorie della posta in arrivo", + "listbox_label": "Messaggi", + "row_actions": "Azioni" }, "shared_tags": { "title": "Etichette condivise", @@ -3569,7 +3571,9 @@ "star": "Aggiungi stella alla conversazione", "unstar": "Rimuovi stella dalla conversazione", "toggle_thread": "Espandi/comprimi discussione", - "messages_count": "{count, plural, one {# messaggio} other {# messaggi}}" + "messages_count": "{count, plural, one {# messaggio} other {# messaggi}}", + "expand_named": "Espandi conversazione: {subject}", + "collapse_named": "Comprimi conversazione: {subject}" }, "identities": { "modal_title": "Gestisci identità di invio", diff --git a/locales/ja/common.json b/locales/ja/common.json index fb47fe8fb..b9db693ac 100644 --- a/locales/ja/common.json +++ b/locales/ja/common.json @@ -379,7 +379,9 @@ "folder_loaded": "{folder}: {count, plural, =0 {メッセージはありません} other {#件のメッセージ}}", "search_results": "{count, plural, =0 {結果はありません} other {#件の結果}}" }, - "category_tabs": "受信トレイのカテゴリ" + "category_tabs": "受信トレイのカテゴリ", + "listbox_label": "メッセージ", + "row_actions": "アクション" }, "shared_tags": { "title": "共有タグ", @@ -3569,7 +3571,9 @@ "star": "会話にスターを付ける", "unstar": "会話のスターを外す", "toggle_thread": "スレッドの展開/折りたたみ", - "messages_count": "{count, plural, other {#件のメッセージ}}" + "messages_count": "{count, plural, other {#件のメッセージ}}", + "expand_named": "会話を展開: {subject}", + "collapse_named": "会話を折りたたむ: {subject}" }, "identities": { "modal_title": "送信者情報を管理", diff --git a/locales/ko/common.json b/locales/ko/common.json index 75d658c51..b3e6d53da 100644 --- a/locales/ko/common.json +++ b/locales/ko/common.json @@ -379,7 +379,9 @@ "folder_loaded": "{folder}: {count, plural, =0 {메시지 없음} other {메시지 #개}}", "search_results": "{count, plural, =0 {결과 없음} other {결과 #개}}" }, - "category_tabs": "받은편지함 카테고리" + "category_tabs": "받은편지함 카테고리", + "listbox_label": "메시지", + "row_actions": "작업" }, "shared_tags": { "title": "공유 태그", @@ -3569,7 +3571,9 @@ "star": "대화 전체에 별표 달기", "unstar": "대화 전체 별표 해제", "toggle_thread": "스레드 켜기/끄기", - "messages_count": "{count, plural, other {메시지 #개}}" + "messages_count": "{count, plural, other {메시지 #개}}", + "expand_named": "대화 펼치기: {subject}", + "collapse_named": "대화 접기: {subject}" }, "identities": { "modal_title": "보내는 사람 관리", diff --git a/locales/lv/common.json b/locales/lv/common.json index bc658849c..5c2a4aee9 100644 --- a/locales/lv/common.json +++ b/locales/lv/common.json @@ -379,7 +379,9 @@ "folder_loaded": "{folder}: {count, plural, =0 {nav vēstuļu} zero {# vēstules} one {# vēstule} other {# vēstules}}", "search_results": "{count, plural, =0 {Nav rezultātu} zero {# rezultāti} one {# rezultāts} other {# rezultāti}}" }, - "category_tabs": "Iesūtnes kategorijas" + "category_tabs": "Iesūtnes kategorijas", + "listbox_label": "Vēstules", + "row_actions": "Darbības" }, "shared_tags": { "title": "Kopīgotie tagi", @@ -3569,7 +3571,9 @@ "star": "Pievienot zvaigznīti sarunai", "unstar": "Noņemt zvaigznīti sarunai", "toggle_thread": "Pārslēgt sarunu", - "messages_count": "{count, plural, zero {# ziņojumu} one {# ziņojums} other {# ziņojumi}}" + "messages_count": "{count, plural, zero {# ziņojumu} one {# ziņojums} other {# ziņojumi}}", + "expand_named": "Izvērst sarunu: {subject}", + "collapse_named": "Sairt sarunu: {subject}" }, "identities": { "modal_title": "Pārvaldīt sūtītāja identitātes", diff --git a/locales/mn/common.json b/locales/mn/common.json index adefdebf3..3c0cbe69b 100644 --- a/locales/mn/common.json +++ b/locales/mn/common.json @@ -379,7 +379,9 @@ "folder_loaded": "{folder}: {count, plural, =0 {зурвас алга} other {# зурвас}}", "search_results": "{count, plural, =0 {Илэрц алга} other {# илэрц}}" }, - "category_tabs": "Ирсэн шуудангийн ангиллууд" + "category_tabs": "Ирсэн шуудангийн ангиллууд", + "listbox_label": "Зурвасууд", + "row_actions": "Үйлдлүүд" }, "shared_tags": { "title": "Хуваалцсан тагууд", @@ -3569,7 +3571,9 @@ "star": "Харилцан яриаг одтой болгох", "unstar": "Харилцан ярианы одыг авах", "toggle_thread": "Хэлхээсийг дэлгэх/хумих", - "messages_count": "{count, plural, one {# мессеж} other {# мессеж}}" + "messages_count": "{count, plural, one {# мессеж} other {# мессеж}}", + "expand_named": "Харилцан яриаг өргөжүүлэх: {subject}", + "collapse_named": "Харилцан яриаг хураах: {subject}" }, "identities": { "modal_title": "Илгээж буй таниулбаруудыг удирдах", diff --git a/locales/nb/common.json b/locales/nb/common.json index 45c41b6bd..325b52024 100644 --- a/locales/nb/common.json +++ b/locales/nb/common.json @@ -379,7 +379,9 @@ "folder_loaded": "{folder}: {count, plural, =0 {ingen meldinger} one {# melding} other {# meldinger}}", "search_results": "{count, plural, =0 {Ingen treff} one {# treff} other {# treff}}" }, - "category_tabs": "Innbokskategorier" + "category_tabs": "Innbokskategorier", + "listbox_label": "Meldinger", + "row_actions": "Handlinger" }, "shared_tags": { "title": "Delte etiketter", @@ -3569,7 +3571,9 @@ "star": "Stjernemerk samtalen", "unstar": "Fjern stjernemerking fra samtalen", "toggle_thread": "Utvid eller fold sammen tråden", - "messages_count": "{count, plural, one {# melding} other {# meldinger}}" + "messages_count": "{count, plural, one {# melding} other {# meldinger}}", + "expand_named": "Utvid samtalen: {subject}", + "collapse_named": "Fold sammen samtalen: {subject}" }, "identities": { "modal_title": "Administrer avsenderidentiteter", diff --git a/locales/nl/common.json b/locales/nl/common.json index 54f8a9210..0e128ab96 100644 --- a/locales/nl/common.json +++ b/locales/nl/common.json @@ -379,7 +379,9 @@ "folder_loaded": "{folder}: {count, plural, =0 {geen berichten} one {# bericht} other {# berichten}}", "search_results": "{count, plural, =0 {Geen resultaten} one {# resultaat} other {# resultaten}}" }, - "category_tabs": "Inboxcategorieën" + "category_tabs": "Inboxcategorieën", + "listbox_label": "Berichten", + "row_actions": "Acties" }, "shared_tags": { "title": "Gedeelde labels", @@ -3569,7 +3571,9 @@ "star": "Ster toevoegen aan gesprek", "unstar": "Ster verwijderen van gesprek", "toggle_thread": "Gesprek in-/uitvouwen", - "messages_count": "{count, plural, one {# bericht} other {# berichten}}" + "messages_count": "{count, plural, one {# bericht} other {# berichten}}", + "expand_named": "Gesprek uitklappen: {subject}", + "collapse_named": "Gesprek inklappen: {subject}" }, "identities": { "modal_title": "Verzendidentiteiten beheren", diff --git a/locales/pl/common.json b/locales/pl/common.json index 22f8ae787..0d46166a8 100644 --- a/locales/pl/common.json +++ b/locales/pl/common.json @@ -379,7 +379,9 @@ "folder_loaded": "{folder}: {count, plural, =0 {brak wiadomości} one {# wiadomość} few {# wiadomości} many {# wiadomości} other {# wiadomości}}", "search_results": "{count, plural, =0 {Brak wyników} one {# wynik} few {# wyniki} many {# wyników} other {# wyników}}" }, - "category_tabs": "Kategorie skrzynki odbiorczej" + "category_tabs": "Kategorie skrzynki odbiorczej", + "listbox_label": "Wiadomości", + "row_actions": "Akcje" }, "shared_tags": { "title": "Wspólne etykiety", @@ -3569,7 +3571,9 @@ "star": "Oznacz konwersację gwiazdką", "unstar": "Usuń gwiazdkę z konwersacji", "toggle_thread": "Przełącz widok wątku", - "messages_count": "{count, plural, one {# wiadomość} few {# wiadomości} many {# wiadomości} other {# wiadomości}}" + "messages_count": "{count, plural, one {# wiadomość} few {# wiadomości} many {# wiadomości} other {# wiadomości}}", + "expand_named": "Rozwiń konwersację: {subject}", + "collapse_named": "Zwiń konwersację: {subject}" }, "identities": { "modal_title": "Zarządzaj tożsamościami nadawcy", diff --git a/locales/pt/common.json b/locales/pt/common.json index 14f67317c..825c854c0 100644 --- a/locales/pt/common.json +++ b/locales/pt/common.json @@ -379,7 +379,9 @@ "folder_loaded": "{folder}: {count, plural, =0 {nenhuma mensagem} one {# mensagem} other {# mensagens}}", "search_results": "{count, plural, =0 {Nenhum resultado} one {# resultado} other {# resultados}}" }, - "category_tabs": "Categorias da caixa de entrada" + "category_tabs": "Categorias da caixa de entrada", + "listbox_label": "Mensagens", + "row_actions": "Ações" }, "shared_tags": { "title": "Etiquetas compartilhadas", @@ -3569,7 +3571,9 @@ "star": "Adicionar estrela à conversa", "unstar": "Remover estrela da conversa", "toggle_thread": "Expandir/recolher conversa", - "messages_count": "{count, plural, one {# mensagem} other {# mensagens}}" + "messages_count": "{count, plural, one {# mensagem} other {# mensagens}}", + "expand_named": "Expandir conversa: {subject}", + "collapse_named": "Recolher conversa: {subject}" }, "identities": { "modal_title": "Gerenciar Identidades de Envio", diff --git a/locales/ro/common.json b/locales/ro/common.json index b01a7395a..3a07eb439 100644 --- a/locales/ro/common.json +++ b/locales/ro/common.json @@ -379,7 +379,9 @@ "folder_loaded": "{folder}: {count, plural, =0 {niciun mesaj} one {# mesaj} few {# mesaje} other {# de mesaje}}", "search_results": "{count, plural, =0 {Niciun rezultat} one {# rezultat} few {# rezultate} other {# de rezultate}}" }, - "category_tabs": "Categoriile căsuței de primire" + "category_tabs": "Categoriile căsuței de primire", + "listbox_label": "Mesaje", + "row_actions": "Acțiuni" }, "shared_tags": { "title": "Etichete partajate", @@ -3569,7 +3571,9 @@ "star": "Marcați conversația cu stea", "unstar": "Dezactivează marcajul conversației", "toggle_thread": "Comutare fir de discuție", - "messages_count": "{count, plural, one {# mesaj} few {# mesaje} other {# de mesaje}}" + "messages_count": "{count, plural, one {# mesaj} few {# mesaje} other {# de mesaje}}", + "expand_named": "Extinde conversația: {subject}", + "collapse_named": "Ascunde conversația: {subject}" }, "identities": { "modal_title": "Gestionarea identităților de expediere", diff --git a/locales/ru/common.json b/locales/ru/common.json index 1b972388c..ffb8d0c9b 100644 --- a/locales/ru/common.json +++ b/locales/ru/common.json @@ -379,7 +379,9 @@ "folder_loaded": "{folder}: {count, plural, =0 {нет сообщений} one {# сообщение} few {# сообщения} many {# сообщений} other {# сообщения}}", "search_results": "{count, plural, =0 {Ничего не найдено} one {# результат} few {# результата} many {# результатов} other {# результата}}" }, - "category_tabs": "Категории входящих" + "category_tabs": "Категории входящих", + "listbox_label": "Сообщения", + "row_actions": "Действия" }, "shared_tags": { "title": "Общие теги", @@ -3569,7 +3571,9 @@ "star": "Добавить звёздочку к разговору", "unstar": "Убрать звёздочку у разговора", "toggle_thread": "Переключить цепочку", - "messages_count": "{count, plural, one {# сообщение} few {# сообщения} many {# сообщений} other {# сообщения}}" + "messages_count": "{count, plural, one {# сообщение} few {# сообщения} many {# сообщений} other {# сообщения}}", + "expand_named": "Развернуть разговор: {subject}", + "collapse_named": "Свернуть разговор: {subject}" }, "identities": { "modal_title": "Управление идентификациями отправителя", diff --git a/locales/sk/common.json b/locales/sk/common.json index c2f2c292d..c8db6efb2 100644 --- a/locales/sk/common.json +++ b/locales/sk/common.json @@ -379,7 +379,9 @@ "folder_loaded": "{folder}: {count, plural, =0 {žiadne správy} one {# správa} few {# správy} other {# správ}}", "search_results": "{count, plural, =0 {Žiadne výsledky} one {# výsledok} few {# výsledky} other {# výsledkov}}" }, - "category_tabs": "Kategórie doručenej pošty" + "category_tabs": "Kategórie doručenej pošty", + "listbox_label": "Správy", + "row_actions": "Akcie" }, "shared_tags": { "title": "Zdieľané štítky", @@ -3569,7 +3571,9 @@ "star": "Označiť konverzáciu hviezdičkou", "unstar": "Odstrániť hviezdičku z konverzácie", "toggle_thread": "Prepnúť zobrazenie vlákna", - "messages_count": "{count, plural, one {# správa} few {# správy} many {# správy} other {# správ}}" + "messages_count": "{count, plural, one {# správa} few {# správy} many {# správy} other {# správ}}", + "expand_named": "Rozbaliť konverzáciu: {subject}", + "collapse_named": "Zbaliť konverzáciu: {subject}" }, "identities": { "modal_title": "Správa identít odosielateľa", diff --git a/locales/th/common.json b/locales/th/common.json index 348df9fef..ecd9f1886 100644 --- a/locales/th/common.json +++ b/locales/th/common.json @@ -456,7 +456,9 @@ "star": "ติดดาวการสนทนา", "unstar": "ยกเลิกติดดาวการสนทนา", "toggle_thread": "สลับมุมมองการสนทนา", - "messages_count": "{count, plural, other {# ข้อความ}}" + "messages_count": "{count, plural, other {# ข้อความ}}", + "expand_named": "ขยายการสนทนา: {subject}", + "collapse_named": "ย่อการสนทนา: {subject}" }, "notifications": { "email_sent": "ส่งอีเมลแล้ว", @@ -641,7 +643,9 @@ "folder_loaded": "{folder}: {count, plural, =0 {ไม่มีข้อความ} other {# ข้อความ}}", "search_results": "{count, plural, =0 {ไม่พบผลลัพธ์} other {# ผลลัพธ์}}" }, - "category_tabs": "หมวดหมู่กล่องจดหมายเข้า" + "category_tabs": "หมวดหมู่กล่องจดหมายเข้า", + "listbox_label": "ข้อความ", + "row_actions": "การดำเนินการ" }, "shared_tags": { "title": "ป้ายกำกับที่แชร์", diff --git a/locales/tr/common.json b/locales/tr/common.json index 9e4fa3e02..fea738ca7 100644 --- a/locales/tr/common.json +++ b/locales/tr/common.json @@ -379,7 +379,9 @@ "folder_loaded": "{folder}: {count, plural, =0 {ileti yok} other {# ileti}}", "search_results": "{count, plural, =0 {Sonuç yok} other {# sonuç}}" }, - "category_tabs": "Gelen kutusu kategorileri" + "category_tabs": "Gelen kutusu kategorileri", + "listbox_label": "Mesajlar", + "row_actions": "Eylemler" }, "shared_tags": { "title": "Paylaşılan etiketler", @@ -3569,7 +3571,9 @@ "star": "Konuşmaya yıldız ekle", "unstar": "Konuşmanın yıldızını kaldır", "toggle_thread": "İleti dizisini aç/kapat", - "messages_count": "{count, plural, one {# ileti} other {# ileti}}" + "messages_count": "{count, plural, one {# ileti} other {# ileti}}", + "expand_named": "Konuşmayı genişlet: {subject}", + "collapse_named": "Konuşmayı daralt: {subject}" }, "identities": { "modal_title": "Gönderme Kimliklerini Yönet", diff --git a/locales/uk/common.json b/locales/uk/common.json index 0ffebff5d..081f1d42b 100644 --- a/locales/uk/common.json +++ b/locales/uk/common.json @@ -379,7 +379,9 @@ "folder_loaded": "{folder}: {count, plural, =0 {немає повідомлень} one {# повідомлення} few {# повідомлення} many {# повідомлень} other {# повідомлення}}", "search_results": "{count, plural, =0 {Нічого не знайдено} one {# результат} few {# результати} many {# результатів} other {# результату}}" }, - "category_tabs": "Категорії вхідних" + "category_tabs": "Категорії вхідних", + "listbox_label": "Повідомлення", + "row_actions": "Дії" }, "shared_tags": { "title": "Спільні теги", @@ -3569,7 +3571,9 @@ "star": "Зоряна розмова", "unstar": "Зняти зірочку з розмови", "toggle_thread": "Перемкнути потік", - "messages_count": "{count, plural, one {# повідомлення} few {# повідомлення} many {# повідомлень} other {# повідомлення}}" + "messages_count": "{count, plural, one {# повідомлення} few {# повідомлення} many {# повідомлень} other {# повідомлення}}", + "expand_named": "Розгорнути бесіду: {subject}", + "collapse_named": "Згорнути бесіду: {subject}" }, "identities": { "modal_title": "Керування ідентифікаторами надсилання", diff --git a/locales/zh-TW/common.json b/locales/zh-TW/common.json index 1f09fe781..14ca046f6 100644 --- a/locales/zh-TW/common.json +++ b/locales/zh-TW/common.json @@ -379,7 +379,9 @@ "folder_loaded": "{folder}:{count, plural, =0 {沒有郵件} other {# 封郵件}}", "search_results": "{count, plural, =0 {沒有結果} other {# 個結果}}" }, - "category_tabs": "收件匣分類" + "category_tabs": "收件匣分類", + "listbox_label": "郵件", + "row_actions": "動作" }, "shared_tags": { "title": "共用標籤", @@ -3569,7 +3571,9 @@ "star": "為對話串加上星號", "unstar": "移除對話串星號", "toggle_thread": "展開/收合對話串", - "messages_count": "{count, plural, other {# 封郵件}}" + "messages_count": "{count, plural, other {# 封郵件}}", + "expand_named": "展開對話串: {subject}", + "collapse_named": "收合對話串: {subject}" }, "identities": { "modal_title": "管理寄件身分", diff --git a/locales/zh/common.json b/locales/zh/common.json index 41b20e2e4..162f86c82 100644 --- a/locales/zh/common.json +++ b/locales/zh/common.json @@ -379,7 +379,9 @@ "folder_loaded": "{folder}:{count, plural, =0 {没有邮件} other {# 封邮件}}", "search_results": "{count, plural, =0 {没有结果} other {# 个结果}}" }, - "category_tabs": "收件箱分类" + "category_tabs": "收件箱分类", + "listbox_label": "邮件", + "row_actions": "操作" }, "shared_tags": { "title": "共享标签", @@ -3569,7 +3571,9 @@ "star": "会话加星标", "unstar": "取消会话星标", "toggle_thread": "展开/折叠会话", - "messages_count": "{count, plural, other {# 条消息}}" + "messages_count": "{count, plural, other {# 条消息}}", + "expand_named": "展开会话: {subject}", + "collapse_named": "折叠会话: {subject}" }, "identities": { "modal_title": "管理发送身份",