feat: turn off every single-key shortcut when the setting is off

This commit is contained in:
Linus Rath
2026-10-29 17:53:34 +01:00
parent 368acca121
commit 13d697ddfb
45 changed files with 265 additions and 37 deletions
@@ -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();
});
});
+1 -1
View File
@@ -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);
+2 -1
View File
@@ -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();
+2
View File
@@ -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);
+2 -1
View File
@@ -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;
+7 -1
View File
@@ -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
+2
View File
@@ -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');
});
});
+16 -2
View File
@@ -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.
+2 -1
View File
@@ -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);
+18
View File
@@ -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');
});
});
+7 -1
View File
@@ -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;
+12
View File
@@ -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;
}
+3 -1
View File
@@ -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 {# رسالة في هذه المحادثة}}",
+3 -1
View File
@@ -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}}",
+3 -1
View File
@@ -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}}",
+3 -1
View File
@@ -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}}",
+3 -1
View File
@@ -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}}",
+3 -1
View File
@@ -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}}",
+3 -1
View File
@@ -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}}",
+3 -1
View File
@@ -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}}",
+3 -1
View File
@@ -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 {# پیام در این مکالمه}}",
+3 -1
View File
@@ -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}}",
+3 -1
View File
@@ -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 {# הודעות בשיחה זו}}",
+3 -1
View File
@@ -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}}",
+3 -1
View File
@@ -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}}",
+3 -1
View File
@@ -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 {この会話の#件のメッセージ}}",
+3 -1
View File
@@ -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 {이 대화에 #개의 메시지가 있어요}}",
+3 -1
View File
@@ -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ā}}",
+3 -1
View File
@@ -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 {Энэ харилцан ярианд # зурвас байна}}",
+3 -1
View File
@@ -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}}",
+3 -1
View File
@@ -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}}",
+3 -1
View File
@@ -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}}",
+3 -1
View File
@@ -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}}",
+3 -1
View File
@@ -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}}",
+3 -1
View File
@@ -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 {# сообщения в этом разговоре}}",
+3 -1
View File
@@ -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}}",
+3 -1
View File
@@ -777,7 +777,9 @@
"send": "ส่งอีเมล",
"schedule_send": "กำหนดเวลาส่ง",
"template_picker": "เปิดตัวเลือกแม่แบบข้อความ"
}
},
"single_keys_off": "ปิดแป้นพิมพ์ลัดแบบปุ่มเดียวอยู่ แป้นพิมพ์ลัดที่ใช้ Ctrl, Alt หรือ ⌘ ยังใช้ได้ รวมถึง Delete, Enter, Escape และปุ่มลูกศร",
"tip_single_keys_off": "เปิดกลับได้ที่ การตั้งค่า > การเข้าถึง"
},
"identities": {
"modal_title": "จัดการข้อมูลประจำตัวผู้ส่ง",
+3 -1
View File
@@ -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}}",
+3 -1
View File
@@ -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 {# повідомлень у цій розмові}}",
+3 -1
View File
@@ -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 {此對話串有 # 封郵件}}",
+3 -1
View File
@@ -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 {此会话中有 # 条消息}}",