Adds client-side i18n support, language settings, translated UI copy, and locale parity coverage.
106 lines
3.3 KiB
TypeScript
106 lines
3.3 KiB
TypeScript
import { useCallback, useMemo } from "react";
|
|
import { useTranslation } from "react-i18next";
|
|
import { Text, View } from "react-native";
|
|
import { StyleSheet } from "react-native-unistyles";
|
|
import { getIsElectronRuntime } from "@/constants/layout";
|
|
import { AdaptiveModalSheet, type SheetHeader } from "@/components/adaptive-modal-sheet";
|
|
import { Shortcut } from "@/components/ui/shortcut";
|
|
import { useKeyboardShortcutsStore } from "@/stores/keyboard-shortcuts-store";
|
|
import { getShortcutOs } from "@/utils/shortcut-platform";
|
|
import { buildKeyboardShortcutHelpSections } from "@/keyboard/keyboard-shortcuts";
|
|
|
|
const SNAP_POINTS: string[] = ["70%", "92%"];
|
|
|
|
export function KeyboardShortcutsDialog() {
|
|
const { t } = useTranslation();
|
|
const open = useKeyboardShortcutsStore((s) => s.shortcutsDialogOpen);
|
|
const setOpen = useKeyboardShortcutsStore((s) => s.setShortcutsDialogOpen);
|
|
|
|
const isMac = getShortcutOs() === "mac";
|
|
const isDesktopApp = getIsElectronRuntime();
|
|
const sections = useMemo(
|
|
() => buildKeyboardShortcutHelpSections({ isMac, isDesktop: isDesktopApp }),
|
|
[isDesktopApp, isMac],
|
|
);
|
|
|
|
const handleClose = useCallback(() => setOpen(false), [setOpen]);
|
|
const header = useMemo<SheetHeader>(() => ({ title: t("settings.shortcuts.dialogTitle") }), [t]);
|
|
|
|
return (
|
|
<AdaptiveModalSheet
|
|
header={header}
|
|
visible={open}
|
|
onClose={handleClose}
|
|
testID="keyboard-shortcuts-dialog"
|
|
snapPoints={SNAP_POINTS}
|
|
>
|
|
<View testID="keyboard-shortcuts-dialog-content" style={styles.content}>
|
|
{sections.map((section) => (
|
|
<View key={section.title} style={styles.section}>
|
|
<Text style={styles.sectionTitle}>{t(section.titleKey)}</Text>
|
|
<View style={styles.rows}>
|
|
{section.rows.map((row) => (
|
|
<View key={row.id} style={styles.row}>
|
|
<View style={styles.rowText}>
|
|
<Text style={styles.rowLabel}>{t(row.labelKey)}</Text>
|
|
{row.note ? (
|
|
<Text style={styles.rowNote}>{row.noteKey ? t(row.noteKey) : row.note}</Text>
|
|
) : null}
|
|
</View>
|
|
<Shortcut keys={row.keys} style={styles.rowShortcut} />
|
|
</View>
|
|
))}
|
|
</View>
|
|
</View>
|
|
))}
|
|
</View>
|
|
</AdaptiveModalSheet>
|
|
);
|
|
}
|
|
|
|
const styles = StyleSheet.create((theme) => ({
|
|
content: {
|
|
gap: theme.spacing[4],
|
|
},
|
|
section: {
|
|
gap: theme.spacing[2],
|
|
},
|
|
sectionTitle: {
|
|
fontSize: theme.fontSize.xs,
|
|
fontWeight: theme.fontWeight.medium,
|
|
color: theme.colors.foregroundMuted,
|
|
},
|
|
rows: {
|
|
borderWidth: theme.borderWidth[1],
|
|
borderColor: theme.colors.surface2,
|
|
borderRadius: theme.borderRadius.lg,
|
|
overflow: "hidden",
|
|
},
|
|
row: {
|
|
flexDirection: "row",
|
|
alignItems: "center",
|
|
justifyContent: "space-between",
|
|
gap: theme.spacing[3],
|
|
paddingHorizontal: theme.spacing[4],
|
|
paddingVertical: theme.spacing[3],
|
|
borderBottomWidth: theme.borderWidth[1],
|
|
borderBottomColor: theme.colors.surface2,
|
|
},
|
|
rowText: {
|
|
flex: 1,
|
|
minWidth: 0,
|
|
},
|
|
rowLabel: {
|
|
fontSize: theme.fontSize.sm,
|
|
color: theme.colors.foreground,
|
|
},
|
|
rowNote: {
|
|
marginTop: 2,
|
|
fontSize: theme.fontSize.xs,
|
|
color: theme.colors.foregroundMuted,
|
|
},
|
|
rowShortcut: {
|
|
alignSelf: "flex-start",
|
|
},
|
|
}));
|