fix(i18n): review translations and fix i18n library misuse (#2750)

This commit is contained in:
Hampus authored and GitHub committed 2026-09-13 22:58:09 +02:00
1 parent 78f783f0c4
commit 8cb097f954
575 files changed
+192671 -160540

No files matched your search

@@ -155,8 +155,8 @@ const KeybindEditorPopout: React.FC<KeybindEditorPopoutProps> = ({
const [recording, setRecording] = useState(true);
const [previewCombo, setPreviewCombo] = useState<KeyCombo | null>(null);
const currentCombo = previewCombo ?? value;
const displayValue = formatKeyCombo(currentCombo);
const defaultDisplayValue = defaultValue ? formatKeyCombo(defaultValue) : null;
const displayValue = formatKeyCombo(i18n, currentCombo);
const defaultDisplayValue = defaultValue ? formatKeyCombo(i18n, defaultValue) : null;
const currentHasValue = !!(
currentCombo?.key ||
currentCombo?.code ||
@@ -490,7 +490,7 @@ export const KeybindRecorder: React.FC<KeybindRecorderProps> = ({
const triggerRef = useRef<HTMLButtonElement | null>(null);
const isEmpty = !value?.key && !value?.code && value?.mouseButton === undefined && value?.gamepadButton === undefined;
const hasValue = !isEmpty;
const displayValue = formatKeyCombo(value);
const displayValue = formatKeyCombo(i18n, value);
return (
<div
className={clsx(styles.field, labelPlacement === 'inline' && styles.fieldInline)}
@@ -3,7 +3,7 @@
import * as Modal from '@app/features/app/components/dialogs/Modal';
import {PRODUCT_NAME} from '@app/features/app/config/I18nDisplayConstants';
import styles from '@app/features/input/components/modals/KeyboardModeIntroModal.module.css';
import {SHIFT_KEY_LABEL} from '@app/features/input/utils/KeyboardUtils';
import {SHIFT_KEY_DESCRIPTOR} from '@app/features/input/utils/KeyboardUtils';
import * as ModalCommands from '@app/features/ui/commands/ModalCommands';
import KeyboardMode from '@app/features/ui/state/KeyboardMode';
import {isNativeMacOS} from '@app/features/ui/utils/NativeUtils';
@@ -69,7 +69,7 @@ export function KeyboardModeIntroModal() {
{i18n._(OR_DESCRIPTOR)}
</span>
<kbd className={styles.kbd} data-flx="input.keyboard-mode-intro-modal.kbd--2">
{SHIFT_KEY_LABEL}
{i18n._(SHIFT_KEY_DESCRIPTOR)}
</kbd>
<span className={styles.separator} data-flx="input.keyboard-mode-intro-modal.separator--2">
+
@@ -3,16 +3,17 @@
import * as Modal from '@app/features/app/components/dialogs/Modal';
import {useTextOverflow} from '@app/features/app/hooks/useTextOverflow';
import styles from '@app/features/input/components/modals/KeyboardShortcutsCheatsheetModal.module.css';
import Keybind, {type KeybindConfig, type KeybindSection, type KeyCombo} from '@app/features/input/state/InputKeybind';
import {formatKeyCombo} from '@app/features/input/utils/KeybindUtils';
import Keybind, {type KeybindConfig, type KeybindSection} from '@app/features/input/state/InputKeybind';
import {formatKeyComboParts} from '@app/features/input/utils/KeybindUtils';
import {Tooltip} from '@app/features/ui/tooltip/Tooltip';
import {
chipsForDefaultEntry,
DEFAULT_KEYBIND_SECTIONS,
sortBySectionDisplayOrder,
} from '@app/features/user/components/modals/tabs/keybinds_tab/shared';
import type {I18n} from '@lingui/core';
import {msg} from '@lingui/core/macro';
import {Trans, useLingui} from '@lingui/react/macro';
import {useLingui} from '@lingui/react/macro';
import {observer} from 'mobx-react-lite';
import type React from 'react';
import {useMemo, useRef} from 'react';
@@ -56,18 +57,13 @@ interface CheatsheetShortcut {
chipGroups: Array<Array<string>>;
}
function chipsForCombo(combo: KeyCombo): Array<string> {
const formatted = formatKeyCombo(combo);
return formatted ? formatted.split(' + ') : [];
}
function getShortcutChipGroups(entry: KeybindConfig, hasCustomBinding: boolean): Array<Array<string>> {
function getShortcutChipGroups(i18n: I18n, entry: KeybindConfig, hasCustomBinding: boolean): Array<Array<string>> {
if (!hasCustomBinding) {
const chips = chipsForDefaultEntry(entry);
const chips = chipsForDefaultEntry(i18n, entry);
return chips.length > 0 ? [chips] : [];
}
return Keybind.getActiveCombosForAction(entry.action)
.map(chipsForCombo)
.map((combo) => formatKeyComboParts(i18n, combo))
.filter((chips) => chips.length > 0);
}
@@ -97,7 +93,7 @@ const ShortcutChipGroups: React.FC<{groups: Array<Array<string>>}> = ({groups})
key={`${chipIndex}-${chip}`}
data-flx="input.keyboard-shortcuts-cheatsheet-modal.chip"
>
{chip === 'ANY KEY' ? <Trans>Any key</Trans> : chip}
{chip}
</kbd>
))}
</div>
@@ -138,7 +134,7 @@ export const KeyboardShortcutsCheatsheetModal = observer(() => {
for (const sectionId of DEFAULT_KEYBIND_SECTIONS) {
const entries = defaults.filter((entry) => entry.section === sectionId && !entry.hideFromDefaults);
for (const entry of sortBySectionDisplayOrder(sectionId, entries)) {
const chipGroups = getShortcutChipGroups(entry, customActions.has(entry.action));
const chipGroups = getShortcutChipGroups(i18n, entry, customActions.has(entry.action));
if (chipGroups.length === 0) continue;
result[sectionId].push({
action: entry.action,
@@ -148,7 +144,7 @@ export const KeyboardShortcutsCheatsheetModal = observer(() => {
}
}
return result;
}, [customActions, defaults]);
}, [customActions, defaults, i18n, i18n.locale]);
return (
<Modal.Root size="xlarge" data-flx="input.keyboard-shortcuts-cheatsheet-modal.modal-root">
<Modal.ScreenReaderLabel
@@ -1,7 +1,10 @@
// SPDX-License-Identifier: AGPL-3.0-or-later
import type {KeyCombo} from '@app/features/input/state/InputKeybind';
import {SHIFT_KEY_LABEL} from '@app/features/input/utils/KeyboardUtils';
import {SHIFT_KEY_DESCRIPTOR} from '@app/features/input/utils/KeyboardUtils';
import type {I18n, MessageDescriptor} from '@lingui/core';
import {msg} from '@lingui/core/macro';
const isMac = () => /Mac|iPod|iPhone|iPad/.test(navigator.platform);
const CONTROL_KEY_SYMBOL = '⌃';
@@ -31,163 +34,196 @@ export function isPrimaryKeyAlreadyRepresentedByModifier(
return false;
}
export function formatMouseButton(button: number): string {
switch (button) {
case 0:
return 'Mouse Left';
case 1:
return 'Mouse Middle';
case 2:
return 'Mouse Right';
case 3:
return 'Mouse Back';
case 4:
return 'Mouse Forward';
default:
return `Mouse ${button}`;
}
const CTRL_DESCRIPTOR = msg({message: 'Ctrl'});
const WIN_DESCRIPTOR = msg({message: 'Win'});
const ALT_DESCRIPTOR = msg({message: 'Alt'});
const SPACE_DESCRIPTOR = msg({message: 'Space'});
const ENTER_DESCRIPTOR = msg({message: 'Enter'});
const ESC_DESCRIPTOR = msg({message: 'Esc'});
const TAB_DESCRIPTOR = msg({message: 'Tab'});
const MOUSE_BUTTON_DESCRIPTOR = msg({message: 'Mouse button {button}', context: 'mouse-button-name'});
const GAMEPAD_BUTTON_DESCRIPTOR = msg({message: 'Gamepad button {button}', context: 'gamepad-button-name'});
const LEFT_KEY_DESCRIPTOR = msg({message: 'Left {keyName}', context: 'keyboard-key-name'});
const RIGHT_KEY_DESCRIPTOR = msg({message: 'Right {keyName}', context: 'keyboard-key-name'});
const NUMPAD_KEY_DESCRIPTOR = msg({message: 'Numpad {keyName}', context: 'keyboard-key-name'});
const LAUNCH_APP_DESCRIPTOR = msg({message: 'Launch app {appNumber}', context: 'keyboard-key-name'});
const MOUSE_BUTTON_DESCRIPTORS: Partial<Record<number, MessageDescriptor>> = {
0: msg({message: 'Left mouse button', context: 'mouse-button-name'}),
1: msg({message: 'Middle mouse button', context: 'mouse-button-name'}),
2: msg({message: 'Right mouse button', context: 'mouse-button-name'}),
3: msg({message: 'Mouse back button', context: 'mouse-button-name'}),
4: msg({message: 'Mouse forward button', context: 'mouse-button-name'}),
};
const GAMEPAD_BUTTON_DESCRIPTORS: Partial<Record<number, MessageDescriptor>> = {
0: msg({message: 'Gamepad A / Cross', context: 'gamepad-button-name'}),
1: msg({message: 'Gamepad B / Circle', context: 'gamepad-button-name'}),
2: msg({message: 'Gamepad X / Square', context: 'gamepad-button-name'}),
3: msg({message: 'Gamepad Y / Triangle', context: 'gamepad-button-name'}),
4: msg({message: 'Gamepad left bumper', context: 'gamepad-button-name'}),
5: msg({message: 'Gamepad right bumper', context: 'gamepad-button-name'}),
6: msg({message: 'Gamepad left trigger', context: 'gamepad-button-name'}),
7: msg({message: 'Gamepad right trigger', context: 'gamepad-button-name'}),
8: msg({message: 'Gamepad Back / Select', context: 'gamepad-button-name'}),
9: msg({message: 'Gamepad Start / Options', context: 'gamepad-button-name'}),
10: msg({message: 'Gamepad left stick click', context: 'gamepad-button-name'}),
11: msg({message: 'Gamepad right stick click', context: 'gamepad-button-name'}),
12: msg({message: 'Gamepad D-pad up', context: 'gamepad-button-name'}),
13: msg({message: 'Gamepad D-pad down', context: 'gamepad-button-name'}),
14: msg({message: 'Gamepad D-pad left', context: 'gamepad-button-name'}),
15: msg({message: 'Gamepad D-pad right', context: 'gamepad-button-name'}),
16: msg({message: 'Gamepad Guide', context: 'gamepad-button-name'}),
};
const KEY_DESCRIPTORS: Partial<Record<string, MessageDescriptor>> = {
PageUp: msg({message: 'Page up', context: 'keyboard-key-name'}),
PageDown: msg({message: 'Page down', context: 'keyboard-key-name'}),
PrintScreen: msg({message: 'Print screen', context: 'keyboard-key-name'}),
ScrollLock: msg({message: 'Scroll lock', context: 'keyboard-key-name'}),
Pause: msg({message: 'Pause / Break', context: 'keyboard-key-name'}),
Break: msg({message: 'Pause / Break', context: 'keyboard-key-name'}),
NumLock: msg({message: 'Num lock', context: 'keyboard-key-name'}),
ContextMenu: msg({message: 'Menu', context: 'keyboard-key-name'}),
Home: msg({message: 'Home', context: 'keyboard-key-name'}),
End: msg({message: 'End', context: 'keyboard-key-name'}),
Insert: msg({message: 'Insert', context: 'keyboard-key-name'}),
Delete: msg({message: 'Delete', context: 'keyboard-key-name'}),
AudioVolumeMute: msg({message: 'Volume mute', context: 'keyboard-key-name'}),
AudioVolumeDown: msg({message: 'Volume down', context: 'keyboard-key-name'}),
AudioVolumeUp: msg({message: 'Volume up', context: 'keyboard-key-name'}),
MediaTrackNext: msg({message: 'Media next', context: 'keyboard-key-name'}),
MediaTrackPrevious: msg({message: 'Media previous', context: 'keyboard-key-name'}),
MediaStop: msg({message: 'Media stop', context: 'keyboard-key-name'}),
MediaPlayPause: msg({message: 'Media play/pause', context: 'keyboard-key-name'}),
BrowserBack: msg({message: 'Browser back', context: 'keyboard-key-name'}),
BrowserForward: msg({message: 'Browser forward', context: 'keyboard-key-name'}),
BrowserRefresh: msg({message: 'Browser refresh', context: 'keyboard-key-name'}),
BrowserStop: msg({message: 'Browser stop', context: 'keyboard-key-name'}),
BrowserSearch: msg({message: 'Browser search', context: 'keyboard-key-name'}),
BrowserFavorites: msg({message: 'Browser favorites', context: 'keyboard-key-name'}),
BrowserHome: msg({message: 'Browser home', context: 'keyboard-key-name'}),
LaunchMail: msg({message: 'Launch mail', context: 'keyboard-key-name'}),
LaunchMediaPlayer: msg({message: 'Launch media', context: 'keyboard-key-name'}),
Power: msg({message: 'Power', context: 'keyboard-key-name'}),
Sleep: msg({message: 'Sleep', context: 'keyboard-key-name'}),
WakeUp: msg({message: 'Wake up', context: 'keyboard-key-name'}),
Convert: msg({message: 'Convert', context: 'keyboard-key-name'}),
NonConvert: msg({message: 'Non-convert', context: 'keyboard-key-name'}),
KanaMode: msg({message: 'Kana', context: 'keyboard-key-name'}),
};
export function formatMouseButton(i18n: I18n, button: number): string {
const descriptor = MOUSE_BUTTON_DESCRIPTORS[button];
return descriptor ? i18n._(descriptor) : i18n._(MOUSE_BUTTON_DESCRIPTOR, {button});
}
export function formatGamepadButton(button: number): string {
const map: Record<number, string> = {
0: 'A / Cross',
1: 'B / Circle',
2: 'X / Square',
3: 'Y / Triangle',
4: 'Left Bumper',
5: 'Right Bumper',
6: 'Left Trigger',
7: 'Right Trigger',
8: 'Back / Select',
9: 'Start / Options',
10: 'Left Stick Click',
11: 'Right Stick Click',
12: 'D-Pad Up',
13: 'D-Pad Down',
14: 'D-Pad Left',
15: 'D-Pad Right',
16: 'Guide',
};
const label = map[button];
return `Gamepad ${label ?? `Button ${button}`}`;
export function formatGamepadButton(i18n: I18n, button: number): string {
const descriptor = GAMEPAD_BUTTON_DESCRIPTORS[button];
return descriptor ? i18n._(descriptor) : i18n._(GAMEPAD_BUTTON_DESCRIPTOR, {button});
}
function formatBothSidesModifier(combo: KeyCombo): string | null {
let label: string | null = null;
if (combo.shift) label = SHIFT_KEY_LABEL;
else if (combo.ctrl) label = isMac() ? CONTROL_KEY_SYMBOL : 'Ctrl';
else if (combo.ctrlOrMeta) label = isMac() ? '⌘' : 'Ctrl';
else if (combo.alt) label = isMac() ? '⌥' : 'Alt';
else if (combo.meta) label = isMac() ? '⌘' : 'Win';
if (!label) return null;
return `Left ${label} + Right ${label}`;
function formatBothSidesModifier(i18n: I18n, combo: KeyCombo): Array<string> | null {
let keyName: string | null = null;
if (combo.shift) keyName = i18n._(SHIFT_KEY_DESCRIPTOR);
else if (combo.ctrl) keyName = isMac() ? CONTROL_KEY_SYMBOL : i18n._(CTRL_DESCRIPTOR);
else if (combo.ctrlOrMeta) keyName = isMac() ? '⌘' : i18n._(CTRL_DESCRIPTOR);
else if (combo.alt) keyName = isMac() ? '⌥' : i18n._(ALT_DESCRIPTOR);
else if (combo.meta) keyName = isMac() ? '⌘' : i18n._(WIN_DESCRIPTOR);
if (!keyName) return null;
return [i18n._(LEFT_KEY_DESCRIPTOR, {keyName}), i18n._(RIGHT_KEY_DESCRIPTOR, {keyName})];
}
export function formatKeyCombo(combo: KeyCombo): string {
export function formatKeyCombo(i18n: I18n, combo: KeyCombo): string {
return formatKeyComboParts(i18n, combo).join(' + ');
}
export function formatKeyComboParts(i18n: I18n, combo: KeyCombo): Array<string> {
const parts: Array<string> = [];
if (combo.modifierOnly && combo.bothSides) {
const formatted = formatBothSidesModifier(combo);
const formatted = formatBothSidesModifier(i18n, combo);
if (formatted) return formatted;
}
if (combo.ctrl) {
parts.push(isMac() ? CONTROL_KEY_SYMBOL : 'Ctrl');
parts.push(isMac() ? CONTROL_KEY_SYMBOL : i18n._(CTRL_DESCRIPTOR));
} else if (combo.ctrlOrMeta) {
parts.push(isMac() ? '⌘' : 'Ctrl');
parts.push(isMac() ? '⌘' : i18n._(CTRL_DESCRIPTOR));
}
if (combo.meta && !combo.ctrlOrMeta) {
parts.push(isMac() ? '⌘' : 'Win');
parts.push(isMac() ? '⌘' : i18n._(WIN_DESCRIPTOR));
}
if (combo.shift) {
parts.push(SHIFT_KEY_LABEL);
parts.push(i18n._(SHIFT_KEY_DESCRIPTOR));
}
if (combo.alt) parts.push(isMac() ? '⌥' : 'Alt');
if (combo.alt) parts.push(isMac() ? '⌥' : i18n._(ALT_DESCRIPTOR));
if (combo.mouseButton !== undefined && combo.mouseButton !== null) {
parts.push(formatMouseButton(combo.mouseButton));
return parts.join(' + ');
parts.push(formatMouseButton(i18n, combo.mouseButton));
return parts;
}
if (combo.gamepadButton !== undefined && combo.gamepadButton !== null) {
parts.push(formatGamepadButton(combo.gamepadButton));
return parts.join(' + ');
parts.push(formatGamepadButton(i18n, combo.gamepadButton));
return parts;
}
const rawKey = getPrimaryKeyForComboDisplay(combo);
if (
isPrimaryKeyAlreadyRepresentedByModifier(combo) ||
(combo.modifierOnly && isModifierKeyName(rawKey) && parts.length > 0)
) {
return parts.join(' + ');
return parts;
}
const primarySymbol = formatPrimaryKeySymbol(rawKey);
parts.push(primarySymbol);
return parts.join(' + ');
const primarySymbol = formatPrimaryKeySymbol(i18n, rawKey);
if (primarySymbol) parts.push(primarySymbol);
return parts;
}
function formatPrimaryKeySymbol(rawKey: string): string {
if (rawKey === ' ' || rawKey === 'Spacebar') return 'Spacebar';
if (isShiftKeyName(rawKey)) return SHIFT_KEY_LABEL;
function formatNumpadKey(i18n: I18n, rawKey: string): string | null {
let keyName: string;
if (/^Numpad[0-9]$/.test(rawKey)) {
keyName = rawKey.slice(6);
} else if (rawKey === 'NumpadEnter') {
keyName = isMac() ? '⏎' : i18n._(ENTER_DESCRIPTOR);
} else {
const symbols: Partial<Record<string, string>> = {
NumpadDecimal: '.',
NumpadAdd: '+',
NumpadSubtract: '-',
NumpadMultiply: '*',
NumpadDivide: '/',
NumpadEqual: '=',
NumpadComma: ',',
};
const symbol = Object.hasOwn(symbols, rawKey) ? symbols[rawKey] : undefined;
if (!symbol) return null;
keyName = symbol;
}
return i18n._(NUMPAD_KEY_DESCRIPTOR, {keyName});
}
export function formatPrimaryKeySymbol(i18n: I18n, rawKey: string): string {
if (rawKey === ' ' || rawKey === 'Spacebar' || rawKey === 'Space') return i18n._(SPACE_DESCRIPTOR);
if (isShiftKeyName(rawKey)) return i18n._(SHIFT_KEY_DESCRIPTOR);
if (isControlKeyName(rawKey)) return isMac() ? CONTROL_KEY_SYMBOL : i18n._(CTRL_DESCRIPTOR);
if (isAltKeyName(rawKey)) return isMac() ? '⌥' : i18n._(ALT_DESCRIPTOR);
if (isMetaKeyName(rawKey)) return isMac() ? '⌘' : i18n._(WIN_DESCRIPTOR);
if (rawKey === 'ArrowUp') return '▲';
if (rawKey === 'ArrowDown') return '▼';
if (rawKey === 'ArrowLeft') return '◀';
if (rawKey === 'ArrowRight') return '▶';
if (rawKey === 'PageUp') return 'Page Up';
if (rawKey === 'PageDown') return 'Page Down';
if (rawKey === 'Escape') return 'Esc';
if (rawKey === 'PrintScreen') return 'Print Screen';
if (rawKey === 'ScrollLock') return 'Scroll Lock';
if (rawKey === 'Pause' || rawKey === 'Break') return 'Break';
if (rawKey === 'NumLock') return 'Num Lock';
if (rawKey === 'ContextMenu') return 'Menu';
if (rawKey === 'Escape') return i18n._(ESC_DESCRIPTOR);
if (rawKey === 'Backspace') return '⌫';
if (rawKey === 'Enter' || rawKey === 'Return') return isMac() ? '⏎' : 'Enter';
if (rawKey === 'Tab') return 'Tab';
if (rawKey === 'Enter' || rawKey === 'Return') return isMac() ? '⏎' : i18n._(ENTER_DESCRIPTOR);
if (rawKey === 'Tab') return i18n._(TAB_DESCRIPTOR);
if (rawKey === 'Backquote' || rawKey === '`') return '`';
if (rawKey === '[' || rawKey === ']') return rawKey;
if (/^Key[A-Z]$/.test(rawKey)) {
return rawKey.slice(3);
if (/^Key[A-Z]$/.test(rawKey)) return rawKey.slice(3);
if (/^Digit[0-9]$/.test(rawKey)) return rawKey.slice(5);
const numpadKey = formatNumpadKey(i18n, rawKey);
if (numpadKey) return numpadKey;
if (rawKey === 'LaunchApp1' || rawKey === 'LaunchApp2') {
return i18n._(LAUNCH_APP_DESCRIPTOR, {appNumber: rawKey.slice(-1)});
}
if (/^Digit[0-9]$/.test(rawKey)) {
return rawKey.slice(5);
}
if (/^Numpad[0-9]$/.test(rawKey)) {
return `Numpad ${rawKey.slice(6)}`;
}
if (rawKey === 'NumpadDecimal') return 'Numpad .';
if (rawKey === 'NumpadAdd') return 'Numpad +';
if (rawKey === 'NumpadSubtract') return 'Numpad -';
if (rawKey === 'NumpadMultiply') return 'Numpad *';
if (rawKey === 'NumpadDivide') return 'Numpad /';
if (rawKey === 'NumpadEnter') return isMac() ? 'Numpad ⏎' : 'Numpad Enter';
if (rawKey === 'NumpadEqual') return 'Numpad =';
if (rawKey === 'NumpadComma') return 'Numpad ,';
if (rawKey === 'AudioVolumeMute') return 'Volume Mute';
if (rawKey === 'AudioVolumeDown') return 'Volume Down';
if (rawKey === 'AudioVolumeUp') return 'Volume Up';
if (rawKey === 'MediaTrackNext') return 'Media Next';
if (rawKey === 'MediaTrackPrevious') return 'Media Previous';
if (rawKey === 'MediaStop') return 'Media Stop';
if (rawKey === 'MediaPlayPause') return 'Media Play/Pause';
if (rawKey === 'BrowserBack') return 'Browser Back';
if (rawKey === 'BrowserForward') return 'Browser Forward';
if (rawKey === 'BrowserRefresh') return 'Browser Refresh';
if (rawKey === 'BrowserStop') return 'Browser Stop';
if (rawKey === 'BrowserSearch') return 'Browser Search';
if (rawKey === 'BrowserFavorites') return 'Browser Favorites';
if (rawKey === 'BrowserHome') return 'Browser Home';
if (rawKey === 'LaunchMail') return 'Launch Mail';
if (rawKey === 'LaunchMediaPlayer') return 'Launch Media';
if (rawKey === 'LaunchApp1') return 'Launch App 1';
if (rawKey === 'LaunchApp2') return 'Launch App 2';
if (rawKey === 'Power') return 'Power';
if (rawKey === 'Sleep') return 'Sleep';
if (rawKey === 'WakeUp') return 'Wake Up';
if (rawKey === 'Convert') return 'Convert';
if (rawKey === 'NonConvert') return 'NonConvert';
if (rawKey === 'KanaMode') return 'Kana';
if (/^Lang[1-5]$/.test(rawKey)) return rawKey;
const descriptor = Object.hasOwn(KEY_DESCRIPTORS, rawKey) ? KEY_DESCRIPTORS[rawKey] : undefined;
if (descriptor) return i18n._(descriptor);
if (rawKey === 'IntlBackslash') return 'Intl \\';
if (rawKey === 'IntlRo') return 'Intl Ro';
if (rawKey === 'IntlYen') return 'Intl Yen';
if (rawKey.length === 1) {
return rawKey.toUpperCase();
}
if (rawKey.length === 1) return rawKey.toUpperCase();
return rawKey;
}
@@ -1,7 +1,9 @@
// SPDX-License-Identifier: AGPL-3.0-or-later
import {isKeyboardActivationKey} from '@app/features/input/utils/KeyboardUtils';
import {describe, expect, it} from 'vitest';
import {describe, expect, it, vi} from 'vitest';
vi.mock('@lingui/core/macro', () => ({msg: (descriptor: unknown) => descriptor}));
describe('KeyboardUtils', () => {
it('recognizes common keyboard activation key values', () => {
@@ -1,8 +1,9 @@
// SPDX-License-Identifier: AGPL-3.0-or-later
import {msg} from '@lingui/core/macro';
import type React from 'react';
export const SHIFT_KEY_LABEL = 'Shift';
export const SHIFT_KEY_DESCRIPTOR = msg({message: 'Shift'});
export function isKeyboardActivationKey(key: string): boolean {
return key === 'Enter' || key === ' ' || key === 'Space' || key === 'Spacebar';