mirror of
https://github.com/bulwarkmail/webmail.git
synced 2026-10-31 00:12:39 +00:00
feat: turn off every single-key shortcut when the setting is off
This commit is contained in:
@@ -14,6 +14,7 @@ vi.mock('@/lib/keyboard-shortcuts/contacts', () => ({ CONTACTS_SECTIONS: [] }));
|
||||
vi.mock('@/lib/keyboard-shortcuts/files', () => ({ FILES_SECTIONS: [] }));
|
||||
|
||||
const { KeyboardShortcutsModal } = await import('../keyboard-shortcuts-modal');
|
||||
const { useSettingsStore } = await import('@/stores/settings-store');
|
||||
|
||||
describe('keyboard shortcuts help per app', () => {
|
||||
it("opens on the current app's tab and lists the general keys there too", () => {
|
||||
@@ -39,3 +40,26 @@ describe('keyboard shortcuts help per app', () => {
|
||||
expect(screen.getAllByText('Ctrl + K').length).toBe(1);
|
||||
});
|
||||
});
|
||||
|
||||
// MO-04: the help says when single-key shortcuts are off.
|
||||
describe('keyboard shortcuts help with single-key shortcuts off', () => {
|
||||
it('says so and drops the "press ?" tip', () => {
|
||||
nav.pathname = '/';
|
||||
useSettingsStore.setState({ singleKeyShortcuts: false });
|
||||
try {
|
||||
render(<KeyboardShortcutsModal isOpen onClose={() => {}} />);
|
||||
expect(screen.getByText('shortcuts.single_keys_off')).toBeInTheDocument();
|
||||
expect(screen.getByText('shortcuts.tip_single_keys_off')).toBeInTheDocument();
|
||||
expect(screen.queryByText('shortcuts.tip')).toBeNull();
|
||||
} finally {
|
||||
useSettingsStore.setState({ singleKeyShortcuts: true });
|
||||
}
|
||||
});
|
||||
|
||||
it('says nothing with them on', () => {
|
||||
nav.pathname = '/';
|
||||
render(<KeyboardShortcutsModal isOpen onClose={() => {}} />);
|
||||
expect(screen.queryByText('shortcuts.single_keys_off')).toBeNull();
|
||||
expect(screen.getByText('shortcuts.tip')).toBeInTheDocument();
|
||||
});
|
||||
});
|
||||
|
||||
@@ -1586,7 +1586,7 @@ export function CalendarApp({ linkSegments: routeSegments }: CalendarAppProps =
|
||||
if (isContextMenuOpen()) return;
|
||||
if (showEventModal || showTaskModal || detailPinned) return;
|
||||
|
||||
const action = calendarShortcutAction(e);
|
||||
const action = calendarShortcutAction(e, useSettingsStore.getState().singleKeyShortcuts !== false);
|
||||
if (!action) return;
|
||||
// A slider, tabs or a menu keeps its own arrows.
|
||||
if ((action === "prev" || action === "next") && isArrowKeyWidgetTarget(e)) return;
|
||||
|
||||
@@ -31,6 +31,7 @@ import { PluginSlot } from "@/components/plugins/plugin-slot";
|
||||
import { eventToView } from "@/lib/plugin-projection";
|
||||
import { ParticipantStatusBadge, RsvpButtons } from "./rsvp-buttons";
|
||||
import { isEditableEventTarget } from "@/lib/keyboard";
|
||||
import { isSingleKeyShortcutOff } from "@/lib/single-key-shortcuts";
|
||||
|
||||
interface EventDetailPopoverProps {
|
||||
event: CalendarEvent;
|
||||
@@ -300,6 +301,7 @@ export function EventDetailPopover({
|
||||
if (e.key === "Escape") onClose();
|
||||
if (isEditableEventTarget(e)) return;
|
||||
if (e.ctrlKey || e.metaKey || e.altKey || e.shiftKey) return;
|
||||
if (isSingleKeyShortcutOff(e)) return;
|
||||
if (e.code === "KeyE" && !noteExpanded && canEditBody) {
|
||||
e.preventDefault();
|
||||
onEdit();
|
||||
|
||||
@@ -242,3 +242,34 @@ describe('command palette and plugin pages', () => {
|
||||
pages.resetPages();
|
||||
});
|
||||
});
|
||||
|
||||
// MO-04 / MO-05: "?" opens the help, except with single-key shortcuts off or
|
||||
// with a menu or dialog open.
|
||||
describe('the ? key', () => {
|
||||
const pressQuestion = () =>
|
||||
act(() => { fireEvent.keyDown(document.body, { key: '?', code: 'Slash', shiftKey: true }); });
|
||||
|
||||
it('opens the shortcuts help', () => {
|
||||
render(<CommandPaletteHost />);
|
||||
pressQuestion();
|
||||
expect(screen.getByText('shortcuts help')).toBeInTheDocument();
|
||||
});
|
||||
|
||||
it('does nothing with single-key shortcuts off', async () => {
|
||||
const { useSettingsStore } = await import('@/stores/settings-store');
|
||||
useSettingsStore.setState({ singleKeyShortcuts: false });
|
||||
try {
|
||||
render(<CommandPaletteHost />);
|
||||
pressQuestion();
|
||||
expect(screen.queryByText('shortcuts help')).toBeNull();
|
||||
} finally {
|
||||
useSettingsStore.setState({ singleKeyShortcuts: true });
|
||||
}
|
||||
});
|
||||
|
||||
it('does nothing while a menu is open', () => {
|
||||
render(<><div role="menu" /><CommandPaletteHost /></>);
|
||||
pressQuestion();
|
||||
expect(screen.queryByText('shortcuts help')).toBeNull();
|
||||
});
|
||||
});
|
||||
|
||||
@@ -38,6 +38,7 @@ import { commandMatches, useRegisteredCommandGroups } from "@/lib/command-regist
|
||||
import { shortcutAlternatives } from "@/lib/shortcut-keys";
|
||||
import { toUnicodeEmail } from "@/lib/idn";
|
||||
import { isEditableEventTarget, isKeyboardLayerOpen } from "@/lib/keyboard";
|
||||
import { isSingleKeyShortcutOff } from "@/lib/single-key-shortcuts";
|
||||
import { useFocusTrap } from "@/hooks/use-focus-trap";
|
||||
import { useMediaQuery } from "@/hooks/use-media-query";
|
||||
import { IconButton } from "@/components/ui/icon-button";
|
||||
@@ -110,6 +111,9 @@ export function CommandPaletteHost() {
|
||||
if (!question || isEditableEventTarget(event) || isContextMenuOpen()) return;
|
||||
// Another dialog, a menu or a popup is open: the key belongs to it.
|
||||
if (event.defaultPrevented || isKeyboardLayerOpen()) return;
|
||||
// Turned off with the other single-key shortcuts; the menu and the
|
||||
// command palette still open the help.
|
||||
if (isSingleKeyShortcutOff(event)) return;
|
||||
event.preventDefault();
|
||||
event.stopImmediatePropagation();
|
||||
setShowShortcuts(true);
|
||||
|
||||
@@ -20,6 +20,7 @@ import type { AnniversaryDate, ContactCard } from "@/lib/jmap/types";
|
||||
import { getContactDisplayName, getContactSortName, hasContactPhoto } from "@/stores/contact-store";
|
||||
import { useSettingsStore } from "@/stores/settings-store";
|
||||
import { isAppShortcutBlocked } from "@/lib/keyboard";
|
||||
import { isSingleKeyShortcutOff } from "@/lib/single-key-shortcuts";
|
||||
|
||||
type TriState = boolean | null;
|
||||
|
||||
@@ -376,7 +377,7 @@ export function ContactList({
|
||||
useEffect(() => {
|
||||
const onKey = (e: KeyboardEvent) => {
|
||||
if (e.key !== "/" || e.ctrlKey || e.metaKey || e.altKey) return;
|
||||
if (isAppShortcutBlocked(e)) return;
|
||||
if (isAppShortcutBlocked(e) || isSingleKeyShortcutOff(e)) return;
|
||||
if (document.querySelector('[role="dialog"], [role="alertdialog"]')) return;
|
||||
e.preventDefault();
|
||||
searchRef.current?.focus();
|
||||
|
||||
@@ -57,6 +57,7 @@ import { sanitizeSignatureHtml, sanitizeSignatureHtmlForDisplay, sanitizeEmailHt
|
||||
import { buildReplySubject, buildForwardSubject } from "@/lib/subject-prefix";
|
||||
import { isFilePreviewable } from "@/lib/file-preview";
|
||||
import { isEditableEventTarget } from "@/lib/keyboard";
|
||||
import { isSingleKeyShortcutOff } from "@/lib/single-key-shortcuts";
|
||||
import { markReplyQuoteHeader } from "@/components/email/reply-quote-collapse";
|
||||
import { buildQuotedHtmlBlock, serializeEditorContent } from "@/components/email/quoted-html";
|
||||
import { containsEmbeddedSignature } from "@/components/email/signature-block";
|
||||
@@ -2077,6 +2078,7 @@ export function EmailComposer({
|
||||
// island doesn't trigger the picker (#654).
|
||||
if (isEditableEventTarget(e)) return;
|
||||
if (!templatesEnabled) return;
|
||||
if (isSingleKeyShortcutOff(e)) return;
|
||||
if (e.key === 't' && !e.ctrlKey && !e.metaKey && !e.altKey) {
|
||||
e.preventDefault();
|
||||
setShowTemplatePicker(true);
|
||||
|
||||
@@ -39,6 +39,7 @@ import type { IJMAPClient } from "@/lib/jmap/client-interface";
|
||||
import type { FileNodeRights } from "@/lib/jmap/types";
|
||||
import type { FileResourceView } from "@/lib/plugin-types";
|
||||
import { isAppShortcutBlocked, isArrowKeyWidgetTarget } from "@/lib/keyboard";
|
||||
import { isSingleKeyShortcutOff } from "@/lib/single-key-shortcuts";
|
||||
|
||||
const NO_FORMATS: string[] = [];
|
||||
|
||||
@@ -726,7 +727,7 @@ export function FileBrowser({
|
||||
const selected = selectedList.map((r) => r.name);
|
||||
|
||||
// "/" as in Mail and Contacts; Ctrl/⌘+F as in a file manager.
|
||||
if ((mod && e.key.toLowerCase() === "f") || (e.key === "/" && !mod && !e.altKey)) {
|
||||
if ((mod && e.key.toLowerCase() === "f") || (e.key === "/" && !mod && !e.altKey && !isSingleKeyShortcutOff(e))) {
|
||||
e.preventDefault();
|
||||
searchInputRef.current?.focus();
|
||||
return;
|
||||
|
||||
@@ -42,6 +42,7 @@ export function KeyboardShortcutsModal({ isOpen, onClose, app }: KeyboardShortcu
|
||||
// scroll to the tour link at the bottom when there are no tabs.
|
||||
const contentRef = useRef<HTMLDivElement>(null);
|
||||
const interfaceLayout = useSettingsStore((state) => state.interfaceLayout);
|
||||
const singleKeyShortcuts = useSettingsStore((state) => state.singleKeyShortcuts);
|
||||
const shell = useProInterfaceActive() ? "pro" : "standard";
|
||||
const features = usePolicyStore((s) => s.policy.features);
|
||||
// Plugin buttons in the composer that have a shortcut (composer-toolbar-actions).
|
||||
@@ -91,6 +92,11 @@ export function KeyboardShortcutsModal({ isOpen, onClose, app }: KeyboardShortcu
|
||||
initialFocus={contentRef}
|
||||
>
|
||||
<div ref={contentRef} tabIndex={-1} className="outline-none">
|
||||
{!singleKeyShortcuts && (
|
||||
<p className="mt-4 rounded-md border border-border bg-muted/50 px-3 py-2 text-sm text-foreground" data-testid="single-keys-off">
|
||||
{t("shortcuts.single_keys_off")}
|
||||
</p>
|
||||
)}
|
||||
{apps.length > 1 && (
|
||||
<div role="tablist" aria-label={t("shortcuts.title")} className="mt-4 flex flex-wrap gap-1 border-b border-border">
|
||||
{apps.map((a) => (
|
||||
@@ -150,7 +156,7 @@ export function KeyboardShortcutsModal({ isOpen, onClose, app }: KeyboardShortcu
|
||||
{/* Footer tip */}
|
||||
<div className="mt-6 pt-4 border-t border-border">
|
||||
<p className="text-sm text-muted-foreground text-center">
|
||||
{t("shortcuts.tip")}
|
||||
{singleKeyShortcuts ? t("shortcuts.tip") : t("shortcuts.tip_single_keys_off")}
|
||||
</p>
|
||||
<p className="text-sm text-center mt-2">
|
||||
<button
|
||||
|
||||
@@ -1138,8 +1138,10 @@ export function MailApp({ linkSegments: routeSegments }: MailAppProps = {}) {
|
||||
// shortcuts then act on the message on screen, as its toolbar does, and
|
||||
// reach the selection again back in the list.
|
||||
const shortcutsTargetSelection = selectedEmailKeys.size > 0 && !(mvShell && !cursorOnly && selectedEmail);
|
||||
const singleKeyShortcuts = useSettingsStore((s) => s.singleKeyShortcuts);
|
||||
useKeyboardShortcuts({
|
||||
enabled: isAuthenticated && !showComposer,
|
||||
singleKeyShortcuts,
|
||||
emails: activeEmails,
|
||||
selectedEmailId: selectedEmail?.id,
|
||||
selectionCount: selectedEmailKeys.size,
|
||||
|
||||
@@ -4,6 +4,7 @@ import { useKeyboardShortcuts } from '../use-keyboard-shortcuts';
|
||||
|
||||
// MO-05: with focus inside the shortcuts help or the Create folder dialog,
|
||||
// Delete, #, e, j, k and ArrowDown acted on the message behind the dialog.
|
||||
// MO-04: single-character shortcuts can be turned off (WCAG 2.1.4).
|
||||
|
||||
function handlers() {
|
||||
return {
|
||||
@@ -152,3 +153,48 @@ describe('useKeyboardShortcuts: arrows only in the message list', () => {
|
||||
expect(h.onNextEmail).toHaveBeenCalledTimes(1);
|
||||
});
|
||||
});
|
||||
|
||||
describe('useKeyboardShortcuts: single-key shortcuts off', () => {
|
||||
it.each([
|
||||
['j', 'KeyJ', {}, 'onNextEmail'],
|
||||
['e', 'KeyE', {}, 'onArchive'],
|
||||
['#', 'Digit3', { shiftKey: true }, 'onDelete'],
|
||||
['c', 'KeyC', {}, 'onCompose'],
|
||||
['?', 'Slash', { shiftKey: true }, 'onShowHelp'],
|
||||
] as [string, string, KeyboardEventInit, keyof Handlers][])('turns off %s', (key, code, init, handler) => {
|
||||
const h = handlers();
|
||||
renderHook(() => useKeyboardShortcuts({ emails: [], selectedEmailId: 'e1', singleKeyShortcuts: false, handlers: h }));
|
||||
press(document, key, code, init);
|
||||
expect(h[handler]).not.toHaveBeenCalled();
|
||||
});
|
||||
|
||||
it('turns off the g sequences', () => {
|
||||
const h = handlers();
|
||||
renderHook(() => useKeyboardShortcuts({ emails: [], mountainViewKeys: true, singleKeyShortcuts: false, handlers: h }));
|
||||
press(document, 'g', 'KeyG');
|
||||
press(document, 'i', 'KeyI');
|
||||
expect(h.onGoToMailbox).not.toHaveBeenCalled();
|
||||
});
|
||||
|
||||
it('keeps Delete, Escape, arrows in the list and Ctrl+A', () => {
|
||||
const h = handlers();
|
||||
renderHook(() => useKeyboardShortcuts({ emails: [], selectedEmailId: 'e1', singleKeyShortcuts: false, handlers: h }));
|
||||
const list = messageList();
|
||||
press(document, 'Delete', 'Delete');
|
||||
press(document, 'Escape', 'Escape');
|
||||
press(list, 'ArrowDown', 'ArrowDown');
|
||||
press(document, 'a', 'KeyA', { ctrlKey: true });
|
||||
expect(h.onDelete).toHaveBeenCalledTimes(1);
|
||||
expect(h.onCloseEmail).toHaveBeenCalledTimes(1);
|
||||
expect(h.onNextEmail).toHaveBeenCalledTimes(1);
|
||||
expect(h.onSelectAll).toHaveBeenCalledTimes(1);
|
||||
});
|
||||
|
||||
it('keeps the g sequences with the setting on', () => {
|
||||
const h = handlers();
|
||||
renderHook(() => useKeyboardShortcuts({ emails: [], mountainViewKeys: true, singleKeyShortcuts: true, handlers: h }));
|
||||
press(document, 'g', 'KeyG');
|
||||
press(document, 'i', 'KeyI');
|
||||
expect(h.onGoToMailbox).toHaveBeenCalledWith('inbox');
|
||||
});
|
||||
});
|
||||
|
||||
@@ -2,7 +2,7 @@
|
||||
|
||||
import { useEffect, useCallback, useRef } from "react";
|
||||
import { Email } from "@/lib/jmap/types";
|
||||
import { isActivatableEventTarget, isAppShortcutBlocked, isEventInside } from "@/lib/keyboard";
|
||||
import { isActivatableEventTarget, isAppShortcutBlocked, isEventInside, isSingleCharacterKey } from "@/lib/keyboard";
|
||||
import { keyboardHooks } from "@/lib/plugin-hooks";
|
||||
|
||||
export interface KeyboardShortcutHandlers {
|
||||
@@ -67,6 +67,12 @@ export interface UseKeyboardShortcutsOptions {
|
||||
* and enables the `g` sequences.
|
||||
*/
|
||||
mountainViewKeys?: boolean;
|
||||
/**
|
||||
* False turns off every shortcut that is a single character without Ctrl,
|
||||
* Alt or ⌘ (WCAG 2.1.4): j, k, e, #, the `g` sequences. Delete, the arrows,
|
||||
* Enter, Escape and Ctrl+A keep working.
|
||||
*/
|
||||
singleKeyShortcuts?: boolean;
|
||||
/**
|
||||
* Where the arrow keys move the selection: only while the focus is in the
|
||||
* message list, so they still scroll the reading pane or move a resize
|
||||
@@ -105,6 +111,7 @@ function physicalShortcutKey(event: KeyboardEvent): string {
|
||||
export function useKeyboardShortcuts({
|
||||
enabled = true,
|
||||
mountainViewKeys = false,
|
||||
singleKeyShortcuts = true,
|
||||
listSelector = "#message-list",
|
||||
emails,
|
||||
selectedEmailId,
|
||||
@@ -130,6 +137,13 @@ export function useKeyboardShortcuts({
|
||||
// Nor while a dialog, menu or popup is open: Delete in the shortcuts
|
||||
// help deleted the message behind it.
|
||||
if (isAppShortcutBlocked(event)) return;
|
||||
if (!singleKeyShortcuts && isSingleCharacterKey(event)) {
|
||||
if (pendingGoTo.current !== null) {
|
||||
clearTimeout(pendingGoTo.current);
|
||||
pendingGoTo.current = null;
|
||||
}
|
||||
return;
|
||||
}
|
||||
if ((event.key === "ArrowDown" || event.key === "ArrowUp") && !isEventInside(event, listSelector)) return;
|
||||
|
||||
const h = handlersRef.current;
|
||||
@@ -342,7 +356,7 @@ export function useKeyboardShortcuts({
|
||||
break;
|
||||
}
|
||||
},
|
||||
[selectedEmailId, selectionCount, mountainViewKeys, listSelector]
|
||||
[selectedEmailId, selectionCount, mountainViewKeys, singleKeyShortcuts, listSelector]
|
||||
);
|
||||
|
||||
// A pending sequence must not outlive the hook.
|
||||
|
||||
@@ -1,6 +1,7 @@
|
||||
import { useCallback, useMemo, useState, type KeyboardEvent, type RefObject } from "react";
|
||||
import { dayKey } from "@/lib/calendar-scroll-window";
|
||||
import { calendarShortcutAction } from "@/lib/calendar-shortcuts";
|
||||
import { useSettingsStore } from "@/stores/settings-store";
|
||||
import { displayNow } from "@/lib/timezone";
|
||||
import type { DisplayHours } from "@/lib/calendar-display-range";
|
||||
|
||||
@@ -83,7 +84,7 @@ export function useTimeGridFocus({
|
||||
if (e.altKey || e.ctrlKey || e.metaKey || e.shiftKey) return;
|
||||
const key = dayKey(day);
|
||||
const index = dayIndexByKey.get(key) ?? 0;
|
||||
if (e.key === "Enter" || calendarShortcutAction(e.nativeEvent) === "new") {
|
||||
if (e.key === "Enter" || calendarShortcutAction(e.nativeEvent, useSettingsStore.getState().singleKeyShortcuts !== false) === "new") {
|
||||
// The slot's own create, not the calendar's `n` at the selected day.
|
||||
e.preventDefault();
|
||||
const start = new Date(day);
|
||||
|
||||
@@ -42,3 +42,21 @@ describe('Calendar keyboard shortcuts', () => {
|
||||
expect(calendarShortcutAction({ ...pressed('ArrowLeft'), altKey: true })).toBeNull();
|
||||
});
|
||||
});
|
||||
|
||||
// MO-04: with single-key shortcuts off only the arrows are left.
|
||||
describe('Calendar shortcuts with single keys off', () => {
|
||||
it('ignores letters and /', () => {
|
||||
expect(calendarShortcutAction(pressed('t', 'KeyT'), false)).toBeNull();
|
||||
expect(calendarShortcutAction(pressed('n', 'KeyN'), false)).toBeNull();
|
||||
expect(calendarShortcutAction(pressed('/', 'Slash'), false)).toBeNull();
|
||||
});
|
||||
|
||||
it('keeps the arrows', () => {
|
||||
expect(calendarShortcutAction(pressed('ArrowLeft', 'ArrowLeft'), false)).toBe('prev');
|
||||
expect(calendarShortcutAction(pressed('ArrowRight', 'ArrowRight'), false)).toBe('next');
|
||||
});
|
||||
|
||||
it('keeps letters by default', () => {
|
||||
expect(calendarShortcutAction(pressed('t', 'KeyT'))).toBe('today');
|
||||
});
|
||||
});
|
||||
|
||||
@@ -25,13 +25,19 @@ const LETTERS: Record<string, CalendarShortcutAction> = {
|
||||
c: "new",
|
||||
};
|
||||
|
||||
/** The action a key press asks for, or null when it isn't a calendar shortcut. */
|
||||
/**
|
||||
* The action a key press asks for, or null when it isn't a calendar shortcut.
|
||||
* With `singleKeys` false (single-key shortcuts turned off, WCAG 2.1.4) only
|
||||
* the arrows are left.
|
||||
*/
|
||||
export function calendarShortcutAction(
|
||||
event: Pick<KeyboardEvent, "key" | "code" | "ctrlKey" | "metaKey" | "altKey" | "shiftKey">,
|
||||
singleKeys = true,
|
||||
): CalendarShortcutAction | null {
|
||||
if (event.ctrlKey || event.metaKey || event.altKey || event.shiftKey) return null;
|
||||
if (event.key === "ArrowLeft") return "prev";
|
||||
if (event.key === "ArrowRight") return "next";
|
||||
if (!singleKeys) return null;
|
||||
// `/` focuses the search, as in Mail (R8); on any layout, by its key.
|
||||
if (event.code === "Slash" || (!event.code && event.key === "/")) return "search";
|
||||
const code = event.code;
|
||||
|
||||
@@ -0,0 +1,12 @@
|
||||
import { isSingleCharacterKey } from "@/lib/keyboard";
|
||||
import { useSettingsStore } from "@/stores/settings-store";
|
||||
|
||||
/**
|
||||
* True when the user turned single-key shortcuts off (Settings >
|
||||
* Accessibility) and this key press is one: a printable character without
|
||||
* Ctrl, Alt or ⌘. Read at the time of the key press, so a window listener
|
||||
* needs no re-subscribe when the setting changes.
|
||||
*/
|
||||
export function isSingleKeyShortcutOff(event: Pick<KeyboardEvent, "key" | "ctrlKey" | "metaKey" | "altKey">): boolean {
|
||||
return isSingleCharacterKey(event) && useSettingsStore.getState().singleKeyShortcuts === false;
|
||||
}
|
||||
@@ -3459,7 +3459,9 @@
|
||||
"send": "إرسال الرسالة",
|
||||
"schedule_send": "جدولة الإرسال",
|
||||
"template_picker": "فتح منتقي القوالب"
|
||||
}
|
||||
},
|
||||
"single_keys_off": "اختصارات المفتاح الواحد متوقفة. ما زالت الاختصارات مع Ctrl أو Alt أو ⌘ تعمل، وكذلك Delete وEnter وEscape ومفاتيح الأسهم.",
|
||||
"tip_single_keys_off": "يمكنك إعادة تشغيلها من الإعدادات > إمكانية الوصول."
|
||||
},
|
||||
"threads": {
|
||||
"messages_tooltip": "{count, plural, zero {لا رسائل في هذه المحادثة} one {رسالة واحدة في هذه المحادثة} two {رسالتان في هذه المحادثة} few {# رسائل في هذه المحادثة} many {# رسالة في هذه المحادثة} other {# رسالة في هذه المحادثة}}",
|
||||
|
||||
@@ -3459,7 +3459,9 @@
|
||||
"send": "Envia el correu",
|
||||
"schedule_send": "Programa l'enviament",
|
||||
"template_picker": "Obre el selector de plantilles"
|
||||
}
|
||||
},
|
||||
"single_keys_off": "Les dreceres d'una sola tecla estan desactivades. Les dreceres amb Ctrl, Alt o ⌘ continuen funcionant, igual que Supr, Retorn, Esc i les fletxes.",
|
||||
"tip_single_keys_off": "Les podeu tornar a activar a Configuració > Accessibilitat."
|
||||
},
|
||||
"threads": {
|
||||
"messages_tooltip": "{count, plural, one {# missatge en aquesta conversa} other {# missatges en aquesta conversa}}",
|
||||
|
||||
@@ -3459,7 +3459,9 @@
|
||||
"send": "Odeslat e-mail",
|
||||
"schedule_send": "Naplánovat odeslání",
|
||||
"template_picker": "Otevřít výběr šablon"
|
||||
}
|
||||
},
|
||||
"single_keys_off": "Zkratky jednou klávesou jsou vypnuté. Zkratky s Ctrl, Alt nebo ⌘ dál fungují, stejně jako Delete, Enter, Esc a šipky.",
|
||||
"tip_single_keys_off": "Znovu je zapnete v Nastavení > Přístupnost."
|
||||
},
|
||||
"threads": {
|
||||
"messages_tooltip": "{count, plural, one {# zpráva v této konverzaci} few {# zprávy v této konverzaci} many {# zprávy v této konverzaci} other {# zpráv v této konverzaci}}",
|
||||
|
||||
@@ -3459,7 +3459,9 @@
|
||||
"send": "Send e-mail",
|
||||
"schedule_send": "Planlæg afsendelse",
|
||||
"template_picker": "Åbn skabelonvælger"
|
||||
}
|
||||
},
|
||||
"single_keys_off": "Genveje med én tast er slået fra. Genveje med Ctrl, Alt eller ⌘ virker stadig, og det gør Delete, Enter, Escape og piletasterne også.",
|
||||
"tip_single_keys_off": "Du kan slå dem til igen under Indstillinger > Tilgængelighed."
|
||||
},
|
||||
"threads": {
|
||||
"messages_tooltip": "{count, plural, one {# besked i denne samtale} other {# beskeder i denne samtale}}",
|
||||
|
||||
@@ -3459,7 +3459,9 @@
|
||||
"send": "E-Mail senden",
|
||||
"schedule_send": "Senden planen",
|
||||
"template_picker": "Vorlagenauswahl öffnen"
|
||||
}
|
||||
},
|
||||
"single_keys_off": "Tastenkürzel mit einer Taste sind ausgeschaltet. Kürzel mit Strg, Alt oder ⌘ funktionieren weiter, ebenso Entf, Eingabe, Esc und die Pfeiltasten.",
|
||||
"tip_single_keys_off": "Sie können sie unter Einstellungen > Barrierefreiheit wieder einschalten."
|
||||
},
|
||||
"threads": {
|
||||
"messages_tooltip": "{count, plural, one {# Nachricht in dieser Unterhaltung} other {# Nachrichten in dieser Unterhaltung}}",
|
||||
|
||||
@@ -3459,7 +3459,9 @@
|
||||
"send": "Send email",
|
||||
"schedule_send": "Schedule send",
|
||||
"template_picker": "Open template picker"
|
||||
}
|
||||
},
|
||||
"single_keys_off": "Single-key shortcuts are off. Shortcuts with Ctrl, Alt or ⌘ still work, and so do Delete, Enter, Escape and the arrow keys.",
|
||||
"tip_single_keys_off": "You can turn single-key shortcuts back on in Settings > Accessibility."
|
||||
},
|
||||
"threads": {
|
||||
"messages_tooltip": "{count, plural, one {# message in this conversation} other {# messages in this conversation}}",
|
||||
|
||||
@@ -3459,7 +3459,9 @@
|
||||
"send": "Enviar correo",
|
||||
"schedule_send": "Programar envío",
|
||||
"template_picker": "Abrir selector de plantillas"
|
||||
}
|
||||
},
|
||||
"single_keys_off": "Los atajos de una sola tecla están desactivados. Los atajos con Ctrl, Alt o ⌘ siguen funcionando, igual que Supr, Intro, Esc y las flechas.",
|
||||
"tip_single_keys_off": "Puedes volver a activarlos en Configuración > Accesibilidad."
|
||||
},
|
||||
"threads": {
|
||||
"messages_tooltip": "{count, plural, one {# mensaje en esta conversación} other {# mensajes en esta conversación}}",
|
||||
|
||||
@@ -3459,7 +3459,9 @@
|
||||
"send": "Saada e-kiri",
|
||||
"schedule_send": "Ajasta saatmine",
|
||||
"template_picker": "Ava mallivalija"
|
||||
}
|
||||
},
|
||||
"single_keys_off": "Ühe klahviga otseteed on välja lülitatud. Otseteed Ctrl-, Alt- või ⌘-klahviga töötavad edasi, samuti Delete, Enter, Escape ja nooleklahvid.",
|
||||
"tip_single_keys_off": "Saate need uuesti sisse lülitada jaotises Seaded > Juurdepääsetavus."
|
||||
},
|
||||
"threads": {
|
||||
"messages_tooltip": "{count, plural, one {# sõnum selles vestluses} other {# sõnumit selles vestluses}}",
|
||||
|
||||
@@ -3459,7 +3459,9 @@
|
||||
"send": "ارسال ایمیل",
|
||||
"schedule_send": "زمانبندی ارسال",
|
||||
"template_picker": "انتخابگر قالب"
|
||||
}
|
||||
},
|
||||
"single_keys_off": "میانبرهای تککلیدی خاموش هستند. میانبرهای با Ctrl، Alt یا ⌘ همچنان کار میکنند، و همچنین Delete، Enter، Escape و کلیدهای جهتنما.",
|
||||
"tip_single_keys_off": "میتوانید آنها را در تنظیمات > دسترسپذیری دوباره روشن کنید."
|
||||
},
|
||||
"threads": {
|
||||
"messages_tooltip": "{count, plural, one {# پیام در این مکالمه} other {# پیام در این مکالمه}}",
|
||||
|
||||
@@ -3459,7 +3459,9 @@
|
||||
"send": "Envoyer l'e-mail",
|
||||
"schedule_send": "Planifier l'envoi",
|
||||
"template_picker": "Ouvrir le sélecteur de modèles"
|
||||
}
|
||||
},
|
||||
"single_keys_off": "Les raccourcis à une touche sont désactivés. Les raccourcis avec Ctrl, Alt ou ⌘ fonctionnent toujours, tout comme Suppr, Entrée, Échap et les flèches.",
|
||||
"tip_single_keys_off": "Vous pouvez les réactiver dans Paramètres > Accessibilité."
|
||||
},
|
||||
"threads": {
|
||||
"messages_tooltip": "{count, plural, one {# message dans cette conversation} other {# messages dans cette conversation}}",
|
||||
|
||||
@@ -3377,7 +3377,9 @@
|
||||
"template_picker": "פתח את בוחר התבניות",
|
||||
"send": "שלח דוא״ל",
|
||||
"schedule_send": "תזמן שליחה"
|
||||
}
|
||||
},
|
||||
"single_keys_off": "קיצורי מקש יחיד כבויים. קיצורים עם Ctrl, Alt או ⌘ עדיין עובדים, וכך גם Delete, Enter, Escape ומקשי החיצים.",
|
||||
"tip_single_keys_off": "אפשר להפעיל אותם שוב בהגדרות > נגישות."
|
||||
},
|
||||
"threads": {
|
||||
"messages_tooltip": "{count, plural, one {הודעה אחת בשיחה זו} two {שתי הודעות בשיחה זו} other {# הודעות בשיחה זו}}",
|
||||
|
||||
@@ -3459,7 +3459,9 @@
|
||||
"send": "E-mail küldése",
|
||||
"schedule_send": "Késleltetett küldés",
|
||||
"template_picker": "Sablonválasztó megnyitása"
|
||||
}
|
||||
},
|
||||
"single_keys_off": "Az egybillentyűs gyorsbillentyűk ki vannak kapcsolva. A Ctrl, Alt vagy ⌘ billentyűvel kombinált gyorsbillentyűk továbbra is működnek, ahogy a Delete, az Enter, az Esc és a nyílbillentyűk is.",
|
||||
"tip_single_keys_off": "A Beállítások > Kisegítő lehetőségek alatt újra bekapcsolhatja őket."
|
||||
},
|
||||
"threads": {
|
||||
"messages_tooltip": "{count, plural, one {# üzenet ebben a beszélgetésben} other {# üzenet ebben a beszélgetésben}}",
|
||||
|
||||
@@ -3459,7 +3459,9 @@
|
||||
"send": "Invia email",
|
||||
"schedule_send": "Programma invio",
|
||||
"template_picker": "Apri selettore modelli"
|
||||
}
|
||||
},
|
||||
"single_keys_off": "Le scorciatoie a tasto singolo sono disattivate. Le scorciatoie con Ctrl, Alt o ⌘ funzionano ancora, come anche Canc, Invio, Esc e le frecce.",
|
||||
"tip_single_keys_off": "Puoi riattivarle in Impostazioni > Accessibilità."
|
||||
},
|
||||
"threads": {
|
||||
"messages_tooltip": "{count, plural, one {# messaggio in questa conversazione} other {# messaggi in questa conversazione}}",
|
||||
|
||||
@@ -3459,7 +3459,9 @@
|
||||
"send": "メールを送信",
|
||||
"schedule_send": "送信を予約",
|
||||
"template_picker": "テンプレートピッカーを開く"
|
||||
}
|
||||
},
|
||||
"single_keys_off": "1キーのショートカットはオフです。Ctrl、Alt、⌘ を使うショートカットと、Delete、Enter、Esc、矢印キーは引き続き使えます。",
|
||||
"tip_single_keys_off": "設定 > アクセシビリティでオンに戻せます。"
|
||||
},
|
||||
"threads": {
|
||||
"messages_tooltip": "{count, plural, other {この会話の#件のメッセージ}}",
|
||||
|
||||
@@ -3459,7 +3459,9 @@
|
||||
"send": "이메일 보내기",
|
||||
"schedule_send": "보내기 예약",
|
||||
"template_picker": "템플릿 선택기 열기"
|
||||
}
|
||||
},
|
||||
"single_keys_off": "단일 키 단축키가 꺼져 있습니다. Ctrl, Alt, ⌘를 쓰는 단축키와 Delete, Enter, Esc, 화살표 키는 계속 작동합니다.",
|
||||
"tip_single_keys_off": "설정 > 접근성에서 다시 켤 수 있습니다."
|
||||
},
|
||||
"threads": {
|
||||
"messages_tooltip": "{count, plural, other {이 대화에 #개의 메시지가 있어요}}",
|
||||
|
||||
@@ -3459,7 +3459,9 @@
|
||||
"send": "Nosūtīt e-pastu",
|
||||
"schedule_send": "Ieplānot nosūtīšanu",
|
||||
"template_picker": "Atvērt veidņu izvēli"
|
||||
}
|
||||
},
|
||||
"single_keys_off": "Īsinājumtaustiņi ar vienu taustiņu ir izslēgti. Īsinājumtaustiņi ar Ctrl, Alt vai ⌘ joprojām darbojas, tāpat Delete, Enter, Escape un bulttaustiņi.",
|
||||
"tip_single_keys_off": "Tos var atkal ieslēgt sadaļā Iestatījumi > Pieejamība."
|
||||
},
|
||||
"threads": {
|
||||
"messages_tooltip": "{count, plural, zero {# ziņojumu šajā sarunā} one {# ziņojums šajā sarunā} other {# ziņojumi šajā sarunā}}",
|
||||
|
||||
@@ -3459,7 +3459,9 @@
|
||||
"send": "Имэйл илгээх",
|
||||
"schedule_send": "Хуваарь илгээх",
|
||||
"template_picker": "Загвар сонгогчийг нээх"
|
||||
}
|
||||
},
|
||||
"single_keys_off": "Нэг товчтой товчлол унтарсан байна. Ctrl, Alt эсвэл ⌘-тэй товчлол, мөн Delete, Enter, Escape болон сум товчнууд ажилласаар байна.",
|
||||
"tip_single_keys_off": "Тохиргоо > Хүртээмж хэсэгт дахин асааж болно."
|
||||
},
|
||||
"threads": {
|
||||
"messages_tooltip": "{count, plural, one {Энэ харилцан ярианд # зурвас байна} other {Энэ харилцан ярианд # зурвас байна}}",
|
||||
|
||||
@@ -3459,7 +3459,9 @@
|
||||
"send": "Send e-post",
|
||||
"schedule_send": "Planlegg sending",
|
||||
"template_picker": "Åpne malvelgeren"
|
||||
}
|
||||
},
|
||||
"single_keys_off": "Hurtigtaster med én tast er slått av. Hurtigtaster med Ctrl, Alt eller ⌘ virker fortsatt, og det gjør også Delete, Enter, Escape og piltastene.",
|
||||
"tip_single_keys_off": "Du kan slå dem på igjen under Innstillinger > Tilgjengelighet."
|
||||
},
|
||||
"threads": {
|
||||
"messages_tooltip": "{count, plural, one {# melding i denne samtalen} other {# meldinger i denne samtalen}}",
|
||||
|
||||
@@ -3459,7 +3459,9 @@
|
||||
"send": "E-mail verzenden",
|
||||
"schedule_send": "Verzenden plannen",
|
||||
"template_picker": "Sjabloonkiezer openen"
|
||||
}
|
||||
},
|
||||
"single_keys_off": "Sneltoetsen met één toets staan uit. Sneltoetsen met Ctrl, Alt of ⌘ werken nog, net als Delete, Enter, Escape en de pijltjestoetsen.",
|
||||
"tip_single_keys_off": "Je kunt ze weer aanzetten via Instellingen > Toegankelijkheid."
|
||||
},
|
||||
"threads": {
|
||||
"messages_tooltip": "{count, plural, one {# bericht in dit gesprek} other {# berichten in dit gesprek}}",
|
||||
|
||||
@@ -3459,7 +3459,9 @@
|
||||
"send": "Wyślij e-mail",
|
||||
"schedule_send": "Zaplanuj wysyłkę",
|
||||
"template_picker": "Otwórz wybór szablonu"
|
||||
}
|
||||
},
|
||||
"single_keys_off": "Skróty jednoklawiszowe są wyłączone. Skróty z Ctrl, Alt lub ⌘ nadal działają, podobnie jak Delete, Enter, Esc i strzałki.",
|
||||
"tip_single_keys_off": "Możesz je ponownie włączyć w Ustawienia > Dostępność."
|
||||
},
|
||||
"threads": {
|
||||
"messages_tooltip": "{count, plural, one {# wiadomość w tej konwersacji} few {# wiadomości w tej konwersacji} many {# wiadomości w tej konwersacji} other {# wiadomości w tej konwersacji}}",
|
||||
|
||||
@@ -3459,7 +3459,9 @@
|
||||
"send": "Enviar e-mail",
|
||||
"schedule_send": "Agendar envio",
|
||||
"template_picker": "Abrir seletor de modelos"
|
||||
}
|
||||
},
|
||||
"single_keys_off": "Os atalhos de uma tecla estão desativados. Os atalhos com Ctrl, Alt ou ⌘ continuam funcionando, assim como Delete, Enter, Esc e as setas.",
|
||||
"tip_single_keys_off": "Você pode reativá-los em Configurações > Acessibilidade."
|
||||
},
|
||||
"threads": {
|
||||
"messages_tooltip": "{count, plural, one {# mensagem nesta conversa} other {# mensagens nesta conversa}}",
|
||||
|
||||
@@ -3459,7 +3459,9 @@
|
||||
"send": "Trimite e-mail",
|
||||
"schedule_send": "Programează trimiterea",
|
||||
"template_picker": "Deschide selectorul de șabloane"
|
||||
}
|
||||
},
|
||||
"single_keys_off": "Comenzile rapide cu o singură tastă sunt dezactivate. Comenzile cu Ctrl, Alt sau ⌘ funcționează în continuare, la fel Delete, Enter, Escape și săgețile.",
|
||||
"tip_single_keys_off": "Le puteți reactiva din Setări > Accesibilitate."
|
||||
},
|
||||
"threads": {
|
||||
"messages_tooltip": "{count, plural, one {# mesaj în această conversație} few {# mesaje în această conversație} other {# de mesaje în această conversație}}",
|
||||
|
||||
@@ -3459,7 +3459,9 @@
|
||||
"send": "Отправить письмо",
|
||||
"schedule_send": "Запланировать отправку",
|
||||
"template_picker": "Открыть выбор шаблонов"
|
||||
}
|
||||
},
|
||||
"single_keys_off": "Сочетания из одной клавиши выключены. Сочетания с Ctrl, Alt или ⌘ по-прежнему работают, как и Delete, Enter, Esc и стрелки.",
|
||||
"tip_single_keys_off": "Их можно снова включить в разделе Настройки > Специальные возможности."
|
||||
},
|
||||
"threads": {
|
||||
"messages_tooltip": "{count, plural, one {# сообщение в этом разговоре} few {# сообщения в этом разговоре} many {# сообщений в этом разговоре} other {# сообщения в этом разговоре}}",
|
||||
|
||||
@@ -3459,7 +3459,9 @@
|
||||
"send": "Odoslať e-mail",
|
||||
"schedule_send": "Naplánovať odoslanie",
|
||||
"template_picker": "Otvoriť výber šablón"
|
||||
}
|
||||
},
|
||||
"single_keys_off": "Skratky na jednu klávesu sú vypnuté. Skratky s Ctrl, Alt alebo ⌘ naďalej fungujú, rovnako ako Delete, Enter, Esc a šípky.",
|
||||
"tip_single_keys_off": "Znova ich zapnete v Nastavenia > Prístupnosť."
|
||||
},
|
||||
"threads": {
|
||||
"messages_tooltip": "{count, plural, one {# správa v tejto konverzácii} few {# správy v tejto konverzácii} many {# správy v tejto konverzácii} other {# správ v tejto konverzácii}}",
|
||||
|
||||
@@ -777,7 +777,9 @@
|
||||
"send": "ส่งอีเมล",
|
||||
"schedule_send": "กำหนดเวลาส่ง",
|
||||
"template_picker": "เปิดตัวเลือกแม่แบบข้อความ"
|
||||
}
|
||||
},
|
||||
"single_keys_off": "ปิดแป้นพิมพ์ลัดแบบปุ่มเดียวอยู่ แป้นพิมพ์ลัดที่ใช้ Ctrl, Alt หรือ ⌘ ยังใช้ได้ รวมถึง Delete, Enter, Escape และปุ่มลูกศร",
|
||||
"tip_single_keys_off": "เปิดกลับได้ที่ การตั้งค่า > การเข้าถึง"
|
||||
},
|
||||
"identities": {
|
||||
"modal_title": "จัดการข้อมูลประจำตัวผู้ส่ง",
|
||||
|
||||
@@ -3459,7 +3459,9 @@
|
||||
"send": "E-posta gönder",
|
||||
"schedule_send": "Göndermeyi planla",
|
||||
"template_picker": "Şablon seçiciyi aç"
|
||||
}
|
||||
},
|
||||
"single_keys_off": "Tek tuşlu kısayollar kapalı. Ctrl, Alt veya ⌘ ile kullanılan kısayollar çalışmaya devam eder; Delete, Enter, Escape ve ok tuşları da çalışır.",
|
||||
"tip_single_keys_off": "Bunları Ayarlar > Erişilebilirlik bölümünden yeniden açabilirsiniz."
|
||||
},
|
||||
"threads": {
|
||||
"messages_tooltip": "{count, plural, one {Bu konuşmada # ileti} other {Bu konuşmada # ileti}}",
|
||||
|
||||
@@ -3459,7 +3459,9 @@
|
||||
"send": "Надіслати лист",
|
||||
"schedule_send": "Запланувати надсилання",
|
||||
"template_picker": "Відкрити засіб вибору шаблону"
|
||||
}
|
||||
},
|
||||
"single_keys_off": "Комбінації з однієї клавіші вимкнено. Комбінації з Ctrl, Alt або ⌘ і далі працюють, як і Delete, Enter, Esc та стрілки.",
|
||||
"tip_single_keys_off": "Їх можна знову ввімкнути в Налаштування > Доступність."
|
||||
},
|
||||
"threads": {
|
||||
"messages_tooltip": "{count, plural, one {# повідомлення в цій розмові} few {# повідомлення в цій розмові} many {# повідомлень у цій розмові} other {# повідомлень у цій розмові}}",
|
||||
|
||||
@@ -3459,7 +3459,9 @@
|
||||
"send": "傳送郵件",
|
||||
"schedule_send": "排程寄送",
|
||||
"template_picker": "開啟範本選取器"
|
||||
}
|
||||
},
|
||||
"single_keys_off": "單鍵快速鍵已關閉。搭配 Ctrl、Alt 或 ⌘ 的快速鍵仍可使用,Delete、Enter、Esc 和方向鍵也一樣。",
|
||||
"tip_single_keys_off": "可以在 設定 > 無障礙 中重新開啟。"
|
||||
},
|
||||
"threads": {
|
||||
"messages_tooltip": "{count, plural, one {此對話串有 # 封郵件} other {此對話串有 # 封郵件}}",
|
||||
|
||||
@@ -3459,7 +3459,9 @@
|
||||
"send": "发送邮件",
|
||||
"schedule_send": "定时发送",
|
||||
"template_picker": "打开模板选择器"
|
||||
}
|
||||
},
|
||||
"single_keys_off": "单键快捷键已关闭。带 Ctrl、Alt 或 ⌘ 的快捷键仍然可用,Delete、Enter、Esc 和方向键也一样。",
|
||||
"tip_single_keys_off": "可以在 设置 > 无障碍 中重新打开。"
|
||||
},
|
||||
"threads": {
|
||||
"messages_tooltip": "{count, plural, one {此会话中有 # 条消息} other {此会话中有 # 条消息}}",
|
||||
|
||||
Reference in New Issue
Block a user