Complete client UI i18n migration
Adds client-side i18n support, language settings, translated UI copy, and locale parity coverage.
This commit is contained in:
@@ -10,6 +10,7 @@ import {
|
||||
type PressableStateCallbackType,
|
||||
} from "react-native";
|
||||
import { StyleSheet, useUnistyles } from "react-native-unistyles";
|
||||
import { useTranslation } from "react-i18next";
|
||||
import { File, Folder } from "lucide-react-native";
|
||||
import type { Theme } from "@/styles/theme";
|
||||
import { getAutocompleteScrollOffset } from "./autocomplete-utils";
|
||||
@@ -122,11 +123,14 @@ export function Autocomplete({
|
||||
onSelect,
|
||||
isLoading = false,
|
||||
errorMessage,
|
||||
loadingText = "Loading...",
|
||||
emptyText = "No results found",
|
||||
loadingText,
|
||||
emptyText,
|
||||
maxHeight = 220,
|
||||
}: AutocompleteProps) {
|
||||
const { t } = useTranslation();
|
||||
const { theme } = useUnistyles();
|
||||
const resolvedLoadingText = loadingText ?? t("common.states.loading");
|
||||
const resolvedEmptyText = emptyText ?? t("common.empty.noResults");
|
||||
const scrollRef = useRef<ScrollView>(null);
|
||||
const rowLayoutsRef = useRef<Map<number, { top: number; height: number }>>(new Map());
|
||||
const viewportHeightRef = useRef(0);
|
||||
@@ -213,7 +217,7 @@ export function Autocomplete({
|
||||
return (
|
||||
<View style={containerStyle}>
|
||||
<View style={styles.emptyItem}>
|
||||
<Text style={styles.emptyText}>{loadingText}</Text>
|
||||
<Text style={styles.emptyText}>{resolvedLoadingText}</Text>
|
||||
</View>
|
||||
</View>
|
||||
);
|
||||
@@ -233,7 +237,7 @@ export function Autocomplete({
|
||||
return (
|
||||
<View style={containerStyle}>
|
||||
<View style={styles.emptyItem}>
|
||||
<Text style={styles.emptyText}>{emptyText}</Text>
|
||||
<Text style={styles.emptyText}>{resolvedEmptyText}</Text>
|
||||
</View>
|
||||
</View>
|
||||
);
|
||||
|
||||
@@ -23,6 +23,7 @@ import {
|
||||
type StyleProp,
|
||||
type ViewStyle,
|
||||
} from "react-native";
|
||||
import { useTranslation } from "react-i18next";
|
||||
import { StyleSheet, useUnistyles } from "react-native-unistyles";
|
||||
import { useIsCompactFormFactor } from "@/constants/layout";
|
||||
import {
|
||||
@@ -1240,15 +1241,15 @@ export function Combobox({
|
||||
renderOption,
|
||||
onSearchQueryChange,
|
||||
searchable = true,
|
||||
placeholder = "Search...",
|
||||
placeholder,
|
||||
searchPlaceholder,
|
||||
emptyText = "No options match your search.",
|
||||
emptyText,
|
||||
allowCustomValue = false,
|
||||
customValuePrefix = "Use",
|
||||
customValueDescription,
|
||||
customValueKind,
|
||||
optionsPosition = "below-search",
|
||||
title = "Select",
|
||||
title,
|
||||
header,
|
||||
mobileChildrenScrollEnabled = true,
|
||||
presentation,
|
||||
@@ -1263,7 +1264,11 @@ export function Combobox({
|
||||
anchorRef,
|
||||
children,
|
||||
}: ComboboxProps): ReactElement | null {
|
||||
const { t } = useTranslation();
|
||||
const { theme } = useUnistyles();
|
||||
const resolvedPlaceholder = placeholder ?? t("common.placeholders.search");
|
||||
const resolvedEmptyText = emptyText ?? t("common.empty.noOptionsMatchSearch");
|
||||
const resolvedTitle = title ?? t("common.actions.select");
|
||||
const isMobile = useIsCompactFormFactor();
|
||||
const titleColor = theme.colors.foreground;
|
||||
const effectiveOptionsPosition = resolveEffectiveOptionsPosition(isMobile, optionsPosition);
|
||||
@@ -1519,7 +1524,7 @@ export function Combobox({
|
||||
[],
|
||||
);
|
||||
|
||||
const effectiveSearchPlaceholder = searchPlaceholder ?? placeholder;
|
||||
const effectiveSearchPlaceholder = searchPlaceholder ?? resolvedPlaceholder;
|
||||
const hasChildren = Boolean(children);
|
||||
|
||||
if (isMobile) {
|
||||
@@ -1531,7 +1536,7 @@ export function Combobox({
|
||||
handleSheetDismiss={handleSheetDismiss}
|
||||
handleIndicatorStyle={handleIndicatorStyle}
|
||||
titleColor={titleColor}
|
||||
title={title}
|
||||
title={resolvedTitle}
|
||||
header={header}
|
||||
onClose={handleClose}
|
||||
stickyHeader={stickyHeader}
|
||||
@@ -1547,7 +1552,7 @@ export function Combobox({
|
||||
orderedVisibleOptions={orderedVisibleOptions}
|
||||
value={value}
|
||||
activeIndex={activeIndex}
|
||||
emptyText={emptyText}
|
||||
emptyText={resolvedEmptyText}
|
||||
handleSelect={handleSelect}
|
||||
renderOption={renderOption}
|
||||
>
|
||||
@@ -1580,7 +1585,7 @@ export function Combobox({
|
||||
orderedVisibleOptions={orderedVisibleOptions}
|
||||
value={value}
|
||||
activeIndex={activeIndex}
|
||||
emptyText={emptyText}
|
||||
emptyText={resolvedEmptyText}
|
||||
handleSelect={handleSelect}
|
||||
renderOption={renderOption}
|
||||
hasChildren={hasChildren}
|
||||
|
||||
@@ -12,6 +12,7 @@ import {
|
||||
type ReactNode,
|
||||
type Ref,
|
||||
} from "react";
|
||||
import { useTranslation } from "react-i18next";
|
||||
import {
|
||||
ActivityIndicator,
|
||||
Dimensions,
|
||||
@@ -385,6 +386,7 @@ export function ContextMenuContent({
|
||||
mobileMode?: MobileMenuMode;
|
||||
testID?: string;
|
||||
}>): ReactElement | null {
|
||||
const { t } = useTranslation();
|
||||
const context = useContextMenuContext("ContextMenuContent");
|
||||
const { theme } = useUnistyles();
|
||||
const webScrollbarStyle = useWebScrollbarStyle();
|
||||
@@ -560,7 +562,7 @@ export function ContextMenuContent({
|
||||
<View style={styles.overlay}>
|
||||
<Pressable
|
||||
accessibilityRole="button"
|
||||
accessibilityLabel="Menu backdrop"
|
||||
accessibilityLabel={t("menu.backdrop")}
|
||||
style={styles.backdrop}
|
||||
onPress={handleClose}
|
||||
testID={testID ? `${testID}-backdrop` : undefined}
|
||||
|
||||
@@ -11,6 +11,7 @@ import {
|
||||
type ReactElement,
|
||||
type ReactNode,
|
||||
} from "react";
|
||||
import { useTranslation } from "react-i18next";
|
||||
import {
|
||||
ActivityIndicator,
|
||||
Modal,
|
||||
@@ -442,6 +443,7 @@ export function DropdownMenuContent({
|
||||
scrollable?: boolean;
|
||||
testID?: string;
|
||||
}>): ReactElement | null {
|
||||
const { t } = useTranslation();
|
||||
const { open, setOpen, triggerRef, flushPendingSelect } =
|
||||
useDropdownMenuContext("DropdownMenuContent");
|
||||
const [modalVisible, setModalVisible] = useState(false);
|
||||
@@ -625,7 +627,7 @@ export function DropdownMenuContent({
|
||||
<View style={styles.overlay}>
|
||||
<Pressable
|
||||
accessibilityRole="button"
|
||||
accessibilityLabel="Menu backdrop"
|
||||
accessibilityLabel={t("menu.backdrop")}
|
||||
style={styles.backdrop}
|
||||
onPress={handleClose}
|
||||
testID={testID ? `${testID}-backdrop` : undefined}
|
||||
|
||||
Reference in New Issue
Block a user