@umituz/react-native-localization 3.5.9 → 3.5.11
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/package.json +7 -2
- package/src/index.ts +3 -0
- package/src/infrastructure/components/LanguageSwitcher.tsx +13 -31
- package/src/infrastructure/components/LocalizationProvider.tsx +16 -95
- package/src/infrastructure/components/useLanguageSwitcher.ts +39 -0
- package/src/infrastructure/components/useLocalizationProvider.ts +101 -0
- package/src/infrastructure/config/DeviceLocale.ts +46 -0
- package/src/infrastructure/config/LanguageQuery.ts +30 -0
- package/src/infrastructure/config/LocaleMapping.ts +78 -0
- package/src/infrastructure/config/languages.ts +18 -167
- package/src/infrastructure/hooks/useLanguageSelection.ts +32 -0
- package/src/infrastructure/hooks/useTranslation.ts +53 -17
- package/src/presentation/screens/LanguageSelectionScreen.tsx +18 -49
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@umituz/react-native-localization",
|
|
3
|
-
"version": "3.5.
|
|
3
|
+
"version": "3.5.11",
|
|
4
4
|
"description": "Generic localization system for React Native apps with i18n support",
|
|
5
5
|
"main": "./src/index.ts",
|
|
6
6
|
"types": "./src/index.ts",
|
|
@@ -42,6 +42,7 @@
|
|
|
42
42
|
"peerDependencies": {
|
|
43
43
|
"@expo/vector-icons": ">=14.0.0",
|
|
44
44
|
"@react-navigation/native": ">=6.0.0",
|
|
45
|
+
"@umituz/react-native-design-system": ">=2.0.0",
|
|
45
46
|
"@umituz/react-native-filesystem": "latest",
|
|
46
47
|
"@umituz/react-native-storage": "latest",
|
|
47
48
|
"expo-localization": ">=15.0.0",
|
|
@@ -59,14 +60,17 @@
|
|
|
59
60
|
"@babel/preset-typescript": "^7.28.5",
|
|
60
61
|
"@expo/vector-icons": "^15.0.3",
|
|
61
62
|
"@react-native-async-storage/async-storage": "^2.2.0",
|
|
63
|
+
"@react-native-community/datetimepicker": "^8.5.1",
|
|
64
|
+
"@react-navigation/native": "^6.1.17",
|
|
62
65
|
"@testing-library/jest-native": "^5.4.3",
|
|
63
66
|
"@testing-library/react": "^16.3.0",
|
|
64
67
|
"@testing-library/react-hooks": "^8.0.1",
|
|
65
|
-
"@react-navigation/native": "^6.1.17",
|
|
66
68
|
"@testing-library/react-native": "^13.3.3",
|
|
67
69
|
"@types/react": "^18.2.45",
|
|
68
70
|
"@types/react-native": "^0.73.0",
|
|
71
|
+
"@umituz/react-native-design-system": "latest",
|
|
69
72
|
"@umituz/react-native-filesystem": "^1.4.0",
|
|
73
|
+
"@umituz/react-native-icons": "^1.1.2",
|
|
70
74
|
"@umituz/react-native-storage": "^2.4.0",
|
|
71
75
|
"expo-localization": "~15.0.0",
|
|
72
76
|
"i18next": "^23.0.0",
|
|
@@ -77,6 +81,7 @@
|
|
|
77
81
|
"react-i18next": "^14.0.0",
|
|
78
82
|
"react-native": ">=0.74.0",
|
|
79
83
|
"react-native-gesture-handler": "^2.29.1",
|
|
84
|
+
"react-native-safe-area-context": "^5.6.2",
|
|
80
85
|
"react-test-renderer": "^19.2.0",
|
|
81
86
|
"ts-jest": "^29.4.6",
|
|
82
87
|
"ts-node": "^10.9.2",
|
package/src/index.ts
CHANGED
|
@@ -7,6 +7,9 @@
|
|
|
7
7
|
export { useLocalization } from './infrastructure/hooks/useLocalization';
|
|
8
8
|
export { useLocalizationStore } from './infrastructure/storage/LocalizationStore';
|
|
9
9
|
export { useTranslationFunction } from './infrastructure/hooks/useTranslation';
|
|
10
|
+
export { useLanguageSelection } from './infrastructure/hooks/useLanguageSelection';
|
|
11
|
+
export { useLanguageSwitcher } from './infrastructure/components/useLanguageSwitcher';
|
|
12
|
+
export { useLocalizationProvider } from './infrastructure/components/useLocalizationProvider';
|
|
10
13
|
|
|
11
14
|
// Components
|
|
12
15
|
export { LocalizationProvider } from './infrastructure/components/LocalizationProvider';
|
|
@@ -1,8 +1,11 @@
|
|
|
1
|
-
|
|
1
|
+
/**
|
|
2
|
+
* Language Switcher Component
|
|
3
|
+
* Displays current language and allows switching
|
|
4
|
+
*/
|
|
5
|
+
|
|
6
|
+
import React, { useMemo } from 'react';
|
|
2
7
|
import { TouchableOpacity, Text, StyleSheet } from 'react-native';
|
|
3
|
-
import {
|
|
4
|
-
import { languageRegistry } from '../config/languagesData';
|
|
5
|
-
import type { Language } from '../storage/types/LocalizationState';
|
|
8
|
+
import { useLanguageSwitcher } from './useLanguageSwitcher';
|
|
6
9
|
|
|
7
10
|
export interface LanguageSwitcherProps {
|
|
8
11
|
showName?: boolean;
|
|
@@ -36,23 +39,7 @@ export const LanguageSwitcher: React.FC<LanguageSwitcherProps> = ({
|
|
|
36
39
|
disabled = false,
|
|
37
40
|
accessibilityLabel,
|
|
38
41
|
}) => {
|
|
39
|
-
const {
|
|
40
|
-
|
|
41
|
-
const currentLang = useMemo((): Language => {
|
|
42
|
-
return languageRegistry.getLanguageByCode(currentLanguage) || languageRegistry.getDefaultLanguage();
|
|
43
|
-
}, [currentLanguage]);
|
|
44
|
-
|
|
45
|
-
const handlePress = useCallback(() => {
|
|
46
|
-
if (disabled) {
|
|
47
|
-
return;
|
|
48
|
-
}
|
|
49
|
-
|
|
50
|
-
if (__DEV__) {
|
|
51
|
-
console.log('[LanguageSwitcher] Pressed, current language:', currentLanguage);
|
|
52
|
-
}
|
|
53
|
-
|
|
54
|
-
onPress?.();
|
|
55
|
-
}, [disabled, onPress, currentLanguage]);
|
|
42
|
+
const { currentLang, handlePress } = useLanguageSwitcher({ onPress, disabled });
|
|
56
43
|
|
|
57
44
|
const iconColor = useMemo(() => color || DEFAULT_CONFIG.defaultColor, [color]);
|
|
58
45
|
|
|
@@ -63,7 +50,7 @@ export const LanguageSwitcher: React.FC<LanguageSwitcherProps> = ({
|
|
|
63
50
|
accessible: true,
|
|
64
51
|
}), [accessibilityLabel, currentLang.nativeName, disabled]);
|
|
65
52
|
|
|
66
|
-
const
|
|
53
|
+
const renderContent = () => {
|
|
67
54
|
if (showFlag && showName) {
|
|
68
55
|
return (
|
|
69
56
|
<>
|
|
@@ -76,9 +63,7 @@ export const LanguageSwitcher: React.FC<LanguageSwitcherProps> = ({
|
|
|
76
63
|
}
|
|
77
64
|
|
|
78
65
|
if (showFlag) {
|
|
79
|
-
return
|
|
80
|
-
<Text style={[styles.flag, iconStyle]}>{currentLang.flag}</Text>
|
|
81
|
-
);
|
|
66
|
+
return <Text style={[styles.flag, iconStyle]}>{currentLang.flag}</Text>;
|
|
82
67
|
}
|
|
83
68
|
|
|
84
69
|
if (showName) {
|
|
@@ -89,10 +74,8 @@ export const LanguageSwitcher: React.FC<LanguageSwitcherProps> = ({
|
|
|
89
74
|
);
|
|
90
75
|
}
|
|
91
76
|
|
|
92
|
-
return
|
|
93
|
-
|
|
94
|
-
);
|
|
95
|
-
}, [showFlag, showName, currentLang, iconColor, textStyle, iconStyle]);
|
|
77
|
+
return <Text style={[styles.icon, { color: iconColor }, iconStyle]}>🌐</Text>;
|
|
78
|
+
};
|
|
96
79
|
|
|
97
80
|
return (
|
|
98
81
|
<TouchableOpacity
|
|
@@ -104,7 +87,7 @@ export const LanguageSwitcher: React.FC<LanguageSwitcherProps> = ({
|
|
|
104
87
|
disabled={disabled}
|
|
105
88
|
{...accessibilityProps}
|
|
106
89
|
>
|
|
107
|
-
{
|
|
90
|
+
{renderContent()}
|
|
108
91
|
</TouchableOpacity>
|
|
109
92
|
);
|
|
110
93
|
};
|
|
@@ -136,4 +119,3 @@ const styles = StyleSheet.create({
|
|
|
136
119
|
});
|
|
137
120
|
|
|
138
121
|
export default LanguageSwitcher;
|
|
139
|
-
|
|
@@ -1,15 +1,13 @@
|
|
|
1
1
|
/**
|
|
2
2
|
* LocalizationProvider Component
|
|
3
3
|
* Initializes localization system with app translations
|
|
4
|
-
* Includes memory leak prevention and performance optimizations
|
|
5
4
|
*/
|
|
6
5
|
|
|
7
|
-
import React
|
|
8
|
-
import {
|
|
9
|
-
import { I18nInitializer } from '../config/I18nInitializer';
|
|
6
|
+
import React from 'react';
|
|
7
|
+
import { useLocalizationProvider } from './useLocalizationProvider';
|
|
10
8
|
|
|
11
9
|
export interface LocalizationProviderProps {
|
|
12
|
-
children: ReactNode;
|
|
10
|
+
children: React.ReactNode;
|
|
13
11
|
translations: Record<string, any>;
|
|
14
12
|
defaultLanguage?: string;
|
|
15
13
|
onLanguageChange?: (languageCode: string) => void;
|
|
@@ -17,97 +15,27 @@ export interface LocalizationProviderProps {
|
|
|
17
15
|
enableCache?: boolean;
|
|
18
16
|
}
|
|
19
17
|
|
|
18
|
+
// Context for language change handling
|
|
19
|
+
const LocalizationContext = React.createContext<{
|
|
20
|
+
handleLanguageChange: (languageCode: string) => Promise<void>;
|
|
21
|
+
isInitialized: boolean;
|
|
22
|
+
currentLanguage: string;
|
|
23
|
+
} | null>(null);
|
|
24
|
+
|
|
20
25
|
export const LocalizationProvider: React.FC<LocalizationProviderProps> = ({
|
|
21
26
|
children,
|
|
22
27
|
translations,
|
|
23
28
|
defaultLanguage = 'en-US',
|
|
24
29
|
onLanguageChange,
|
|
25
30
|
onError,
|
|
26
|
-
enableCache = true,
|
|
27
31
|
}) => {
|
|
28
|
-
const
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
const isInitializingRef = useRef(false);
|
|
35
|
-
const previousLanguageRef = useRef(currentLanguage);
|
|
36
|
-
|
|
37
|
-
// Memoize translations to prevent unnecessary re-renders
|
|
38
|
-
const memoizedTranslations = useRef(translations);
|
|
39
|
-
|
|
40
|
-
// Update memoized translations when they change
|
|
41
|
-
useEffect(() => {
|
|
42
|
-
memoizedTranslations.current = translations;
|
|
43
|
-
}, [translations]);
|
|
44
|
-
|
|
45
|
-
// Initialize localization system
|
|
46
|
-
useEffect(() => {
|
|
47
|
-
if (isInitializingRef.current || isInitialized) {
|
|
48
|
-
return;
|
|
49
|
-
}
|
|
50
|
-
|
|
51
|
-
isInitializingRef.current = true;
|
|
52
|
-
|
|
53
|
-
const initializeLocalization = async () => {
|
|
54
|
-
try {
|
|
55
|
-
if (__DEV__) {
|
|
56
|
-
console.log('[LocalizationProvider] Initializing with language:', defaultLanguage);
|
|
57
|
-
}
|
|
58
|
-
|
|
59
|
-
await I18nInitializer.initialize(memoizedTranslations.current, defaultLanguage);
|
|
60
|
-
await initialize();
|
|
32
|
+
const { isInitialized, currentLanguage, handleLanguageChange } = useLocalizationProvider({
|
|
33
|
+
translations,
|
|
34
|
+
defaultLanguage,
|
|
35
|
+
onLanguageChange,
|
|
36
|
+
onError,
|
|
37
|
+
});
|
|
61
38
|
|
|
62
|
-
if (__DEV__) {
|
|
63
|
-
console.log('[LocalizationProvider] Initialization complete');
|
|
64
|
-
}
|
|
65
|
-
} catch (error) {
|
|
66
|
-
if (__DEV__) {
|
|
67
|
-
console.error('[LocalizationProvider] Initialization failed:', error);
|
|
68
|
-
}
|
|
69
|
-
onError?.(error instanceof Error ? error : new Error('Initialization failed'));
|
|
70
|
-
} finally {
|
|
71
|
-
isInitializingRef.current = false;
|
|
72
|
-
}
|
|
73
|
-
};
|
|
74
|
-
|
|
75
|
-
initializeLocalization();
|
|
76
|
-
|
|
77
|
-
// Cleanup function
|
|
78
|
-
return () => {
|
|
79
|
-
if (__DEV__) {
|
|
80
|
-
console.log('[LocalizationProvider] Cleanup');
|
|
81
|
-
}
|
|
82
|
-
};
|
|
83
|
-
}, [defaultLanguage, initialize, onError, isInitialized]);
|
|
84
|
-
|
|
85
|
-
// Handle language changes
|
|
86
|
-
useEffect(() => {
|
|
87
|
-
if (previousLanguageRef.current !== currentLanguage && currentLanguage !== previousLanguageRef.current) {
|
|
88
|
-
previousLanguageRef.current = currentLanguage;
|
|
89
|
-
onLanguageChange?.(currentLanguage);
|
|
90
|
-
|
|
91
|
-
if (__DEV__) {
|
|
92
|
-
console.log('[LocalizationProvider] Language changed to:', currentLanguage);
|
|
93
|
-
}
|
|
94
|
-
}
|
|
95
|
-
}, [currentLanguage, onLanguageChange]);
|
|
96
|
-
|
|
97
|
-
// Handle language change with error handling
|
|
98
|
-
const handleLanguageChange = useCallback(async (languageCode: string) => {
|
|
99
|
-
try {
|
|
100
|
-
await setLanguage(languageCode);
|
|
101
|
-
} catch (error) {
|
|
102
|
-
if (__DEV__) {
|
|
103
|
-
console.error('[LocalizationProvider] Language change failed:', error);
|
|
104
|
-
}
|
|
105
|
-
onError?.(error instanceof Error ? error : new Error('Language change failed'));
|
|
106
|
-
throw error;
|
|
107
|
-
}
|
|
108
|
-
}, [setLanguage, onError]);
|
|
109
|
-
|
|
110
|
-
// Context value with memoized functions
|
|
111
39
|
const contextValue = React.useMemo(() => ({
|
|
112
40
|
handleLanguageChange,
|
|
113
41
|
isInitialized,
|
|
@@ -121,13 +49,6 @@ export const LocalizationProvider: React.FC<LocalizationProviderProps> = ({
|
|
|
121
49
|
);
|
|
122
50
|
};
|
|
123
51
|
|
|
124
|
-
// Context for language change handling
|
|
125
|
-
const LocalizationContext = React.createContext<{
|
|
126
|
-
handleLanguageChange: (languageCode: string) => Promise<void>;
|
|
127
|
-
isInitialized: boolean;
|
|
128
|
-
currentLanguage: string;
|
|
129
|
-
} | null>(null);
|
|
130
|
-
|
|
131
52
|
export const useLocalizationContext = () => {
|
|
132
53
|
const context = React.useContext(LocalizationContext);
|
|
133
54
|
if (!context) {
|
|
@@ -0,0 +1,39 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Language Switcher Hook
|
|
3
|
+
* Manages the logic for the LanguageSwitcher component
|
|
4
|
+
*/
|
|
5
|
+
|
|
6
|
+
import { useMemo, useCallback } from 'react';
|
|
7
|
+
import { useLocalization } from '../hooks/useLocalization';
|
|
8
|
+
import { languageRegistry } from '../config/languagesData';
|
|
9
|
+
import type { Language } from '../storage/types/LocalizationState';
|
|
10
|
+
|
|
11
|
+
export interface UseLanguageSwitcherProps {
|
|
12
|
+
onPress?: () => void;
|
|
13
|
+
disabled?: boolean;
|
|
14
|
+
}
|
|
15
|
+
|
|
16
|
+
export const useLanguageSwitcher = ({ onPress, disabled }: UseLanguageSwitcherProps) => {
|
|
17
|
+
const { currentLanguage } = useLocalization();
|
|
18
|
+
|
|
19
|
+
const currentLang = useMemo((): Language => {
|
|
20
|
+
return languageRegistry.getLanguageByCode(currentLanguage) || languageRegistry.getDefaultLanguage();
|
|
21
|
+
}, [currentLanguage]);
|
|
22
|
+
|
|
23
|
+
const handlePress = useCallback(() => {
|
|
24
|
+
if (disabled) {
|
|
25
|
+
return;
|
|
26
|
+
}
|
|
27
|
+
|
|
28
|
+
if (__DEV__) {
|
|
29
|
+
console.log('[LanguageSwitcher] Pressed, current language:', currentLanguage);
|
|
30
|
+
}
|
|
31
|
+
|
|
32
|
+
onPress?.();
|
|
33
|
+
}, [disabled, onPress, currentLanguage]);
|
|
34
|
+
|
|
35
|
+
return {
|
|
36
|
+
currentLang,
|
|
37
|
+
handlePress,
|
|
38
|
+
};
|
|
39
|
+
};
|
|
@@ -0,0 +1,101 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Localization Provider Hook
|
|
3
|
+
* Manages localization initialization and language changes
|
|
4
|
+
*/
|
|
5
|
+
|
|
6
|
+
import { useEffect, useRef, useCallback } from 'react';
|
|
7
|
+
import { useLocalizationStore } from '../storage/LocalizationStore';
|
|
8
|
+
import { I18nInitializer } from '../config/I18nInitializer';
|
|
9
|
+
|
|
10
|
+
export interface UseLocalizationProviderProps {
|
|
11
|
+
translations: Record<string, any>;
|
|
12
|
+
defaultLanguage: string;
|
|
13
|
+
onLanguageChange?: (languageCode: string) => void;
|
|
14
|
+
onError?: (error: Error) => void;
|
|
15
|
+
}
|
|
16
|
+
|
|
17
|
+
export const useLocalizationProvider = ({
|
|
18
|
+
translations,
|
|
19
|
+
defaultLanguage,
|
|
20
|
+
onLanguageChange,
|
|
21
|
+
onError,
|
|
22
|
+
}: UseLocalizationProviderProps) => {
|
|
23
|
+
const store = useLocalizationStore();
|
|
24
|
+
const { initialize, setLanguage, isInitialized, currentLanguage } = store;
|
|
25
|
+
|
|
26
|
+
const isInitializingRef = useRef(false);
|
|
27
|
+
const previousLanguageRef = useRef(currentLanguage);
|
|
28
|
+
|
|
29
|
+
// Memoize translations
|
|
30
|
+
const memoizedTranslations = useRef(translations);
|
|
31
|
+
|
|
32
|
+
useEffect(() => {
|
|
33
|
+
memoizedTranslations.current = translations;
|
|
34
|
+
}, [translations]);
|
|
35
|
+
|
|
36
|
+
// Initialization
|
|
37
|
+
useEffect(() => {
|
|
38
|
+
if (isInitializingRef.current || isInitialized) {
|
|
39
|
+
return;
|
|
40
|
+
}
|
|
41
|
+
|
|
42
|
+
isInitializingRef.current = true;
|
|
43
|
+
|
|
44
|
+
const initializeLocalization = async () => {
|
|
45
|
+
try {
|
|
46
|
+
if (__DEV__) {
|
|
47
|
+
console.log('[LocalizationProvider] Initializing with language:', defaultLanguage);
|
|
48
|
+
}
|
|
49
|
+
|
|
50
|
+
await I18nInitializer.initialize(memoizedTranslations.current, defaultLanguage);
|
|
51
|
+
await initialize();
|
|
52
|
+
|
|
53
|
+
if (__DEV__) {
|
|
54
|
+
console.log('[LocalizationProvider] Initialization complete');
|
|
55
|
+
}
|
|
56
|
+
} catch (error) {
|
|
57
|
+
const initializationError = error instanceof Error ? error : new Error('Initialization failed');
|
|
58
|
+
if (__DEV__) {
|
|
59
|
+
console.error('[LocalizationProvider] Initialization failed:', initializationError);
|
|
60
|
+
}
|
|
61
|
+
onError?.(initializationError);
|
|
62
|
+
} finally {
|
|
63
|
+
isInitializingRef.current = false;
|
|
64
|
+
}
|
|
65
|
+
};
|
|
66
|
+
|
|
67
|
+
initializeLocalization();
|
|
68
|
+
}, [defaultLanguage, initialize, onError, isInitialized]);
|
|
69
|
+
|
|
70
|
+
// Language Change Listener
|
|
71
|
+
useEffect(() => {
|
|
72
|
+
if (previousLanguageRef.current !== currentLanguage) {
|
|
73
|
+
previousLanguageRef.current = currentLanguage;
|
|
74
|
+
onLanguageChange?.(currentLanguage);
|
|
75
|
+
|
|
76
|
+
if (__DEV__) {
|
|
77
|
+
console.log('[LocalizationProvider] Language changed to:', currentLanguage);
|
|
78
|
+
}
|
|
79
|
+
}
|
|
80
|
+
}, [currentLanguage, onLanguageChange]);
|
|
81
|
+
|
|
82
|
+
// Manual Language Change
|
|
83
|
+
const handleLanguageChange = useCallback(async (languageCode: string) => {
|
|
84
|
+
try {
|
|
85
|
+
await setLanguage(languageCode);
|
|
86
|
+
} catch (error) {
|
|
87
|
+
const changeError = error instanceof Error ? error : new Error('Language change failed');
|
|
88
|
+
if (__DEV__) {
|
|
89
|
+
console.error('[LocalizationProvider] Language change failed:', changeError);
|
|
90
|
+
}
|
|
91
|
+
onError?.(changeError);
|
|
92
|
+
throw changeError;
|
|
93
|
+
}
|
|
94
|
+
}, [setLanguage, onError]);
|
|
95
|
+
|
|
96
|
+
return {
|
|
97
|
+
isInitialized,
|
|
98
|
+
currentLanguage,
|
|
99
|
+
handleLanguageChange,
|
|
100
|
+
};
|
|
101
|
+
};
|
|
@@ -0,0 +1,46 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Device Locale Detection
|
|
3
|
+
* Detects and maps device locale to supported language
|
|
4
|
+
*/
|
|
5
|
+
|
|
6
|
+
import * as Localization from 'expo-localization';
|
|
7
|
+
import { LOCALE_MAPPING } from './LocaleMapping';
|
|
8
|
+
import { languageRegistry } from './languagesData';
|
|
9
|
+
|
|
10
|
+
export const DEFAULT_LANGUAGE = 'en-US';
|
|
11
|
+
|
|
12
|
+
/**
|
|
13
|
+
* Get device locale and map it to supported language
|
|
14
|
+
* Called ONLY on first launch (when no saved language preference exists)
|
|
15
|
+
*/
|
|
16
|
+
export const getDeviceLocale = (): string => {
|
|
17
|
+
try {
|
|
18
|
+
const deviceLocale = Localization.getLocales()[0]?.languageTag;
|
|
19
|
+
|
|
20
|
+
if (!deviceLocale) {
|
|
21
|
+
return DEFAULT_LANGUAGE;
|
|
22
|
+
}
|
|
23
|
+
|
|
24
|
+
// Check exact match
|
|
25
|
+
if (LOCALE_MAPPING[deviceLocale]) {
|
|
26
|
+
return LOCALE_MAPPING[deviceLocale];
|
|
27
|
+
}
|
|
28
|
+
|
|
29
|
+
// Extract language code
|
|
30
|
+
const languageCode = deviceLocale.split('-')[0];
|
|
31
|
+
|
|
32
|
+
// Check language code
|
|
33
|
+
if (LOCALE_MAPPING[languageCode]) {
|
|
34
|
+
return LOCALE_MAPPING[languageCode];
|
|
35
|
+
}
|
|
36
|
+
|
|
37
|
+
// Check if directly supported
|
|
38
|
+
if (languageRegistry.isLanguageSupported(deviceLocale)) {
|
|
39
|
+
return deviceLocale;
|
|
40
|
+
}
|
|
41
|
+
|
|
42
|
+
return DEFAULT_LANGUAGE;
|
|
43
|
+
} catch {
|
|
44
|
+
return DEFAULT_LANGUAGE;
|
|
45
|
+
}
|
|
46
|
+
};
|
|
@@ -0,0 +1,30 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Language Query Functions
|
|
3
|
+
* Provides functions to query and search languages
|
|
4
|
+
*/
|
|
5
|
+
|
|
6
|
+
import { languageRegistry } from './languagesData';
|
|
7
|
+
import type { Language } from '../storage/types/LocalizationState';
|
|
8
|
+
|
|
9
|
+
export const getSupportedLanguages = () => languageRegistry.getLanguages();
|
|
10
|
+
|
|
11
|
+
export const getLanguageByCode = (code: string): Language | undefined => {
|
|
12
|
+
return languageRegistry.getLanguageByCode(code);
|
|
13
|
+
};
|
|
14
|
+
|
|
15
|
+
export const isLanguageSupported = (code: string): boolean => {
|
|
16
|
+
return languageRegistry.isLanguageSupported(code);
|
|
17
|
+
};
|
|
18
|
+
|
|
19
|
+
export const getDefaultLanguage = (): Language => {
|
|
20
|
+
return languageRegistry.getLanguages()[0];
|
|
21
|
+
};
|
|
22
|
+
|
|
23
|
+
export const searchLanguages = (query: string): Language[] => {
|
|
24
|
+
const lowerQuery = query.toLowerCase();
|
|
25
|
+
return languageRegistry.getLanguages().filter(
|
|
26
|
+
(lang) =>
|
|
27
|
+
lang.name.toLowerCase().includes(lowerQuery) ||
|
|
28
|
+
lang.nativeName.toLowerCase().includes(lowerQuery)
|
|
29
|
+
);
|
|
30
|
+
};
|
|
@@ -0,0 +1,78 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Locale Mapping Configuration
|
|
3
|
+
* Maps device locales to supported app locales
|
|
4
|
+
*/
|
|
5
|
+
|
|
6
|
+
export const LOCALE_MAPPING: Record<string, string> = {
|
|
7
|
+
// English variants map to en-US
|
|
8
|
+
'en': 'en-US',
|
|
9
|
+
'en-US': 'en-US',
|
|
10
|
+
'en-GB': 'en-US',
|
|
11
|
+
'en-AU': 'en-US',
|
|
12
|
+
'en-CA': 'en-US',
|
|
13
|
+
'en-NZ': 'en-US',
|
|
14
|
+
'en-IE': 'en-US',
|
|
15
|
+
'en-ZA': 'en-US',
|
|
16
|
+
'en-SG': 'en-US',
|
|
17
|
+
'en-IN': 'en-US',
|
|
18
|
+
|
|
19
|
+
// Portuguese mappings
|
|
20
|
+
'pt': 'pt-PT',
|
|
21
|
+
'pt-BR': 'pt-BR',
|
|
22
|
+
'pt-PT': 'pt-PT',
|
|
23
|
+
|
|
24
|
+
// Spanish variants
|
|
25
|
+
'es': 'es-ES',
|
|
26
|
+
'es-ES': 'es-ES',
|
|
27
|
+
'es-MX': 'es-ES',
|
|
28
|
+
'es-AR': 'es-ES',
|
|
29
|
+
'es-US': 'es-ES',
|
|
30
|
+
|
|
31
|
+
// French variants
|
|
32
|
+
'fr': 'fr-FR',
|
|
33
|
+
'fr-FR': 'fr-FR',
|
|
34
|
+
'fr-CA': 'fr-FR',
|
|
35
|
+
'fr-BE': 'fr-FR',
|
|
36
|
+
'fr-CH': 'fr-FR',
|
|
37
|
+
|
|
38
|
+
// Norwegian
|
|
39
|
+
'no': 'no-NO',
|
|
40
|
+
'nb': 'no-NO',
|
|
41
|
+
'nn': 'no-NO',
|
|
42
|
+
|
|
43
|
+
// Chinese variants
|
|
44
|
+
'zh': 'zh-CN',
|
|
45
|
+
'zh-CN': 'zh-CN',
|
|
46
|
+
'zh-Hans': 'zh-CN',
|
|
47
|
+
'zh-Hans-CN': 'zh-CN',
|
|
48
|
+
'zh-Hant': 'zh-TW',
|
|
49
|
+
'zh-TW': 'zh-TW',
|
|
50
|
+
'zh-HK': 'zh-TW',
|
|
51
|
+
|
|
52
|
+
// Others
|
|
53
|
+
'ar': 'ar-SA',
|
|
54
|
+
'bg': 'bg-BG',
|
|
55
|
+
'cs': 'cs-CZ',
|
|
56
|
+
'da': 'da-DK',
|
|
57
|
+
'de': 'de-DE',
|
|
58
|
+
'el': 'el-GR',
|
|
59
|
+
'fi': 'fi-FI',
|
|
60
|
+
'hi': 'hi-IN',
|
|
61
|
+
'hr': 'hr-HR',
|
|
62
|
+
'hu': 'hu-HU',
|
|
63
|
+
'id': 'id-ID',
|
|
64
|
+
'it': 'it-IT',
|
|
65
|
+
'ja': 'ja-JP',
|
|
66
|
+
'ko': 'ko-KR',
|
|
67
|
+
'ms': 'ms-MY',
|
|
68
|
+
'nl': 'nl-NL',
|
|
69
|
+
'pl': 'pl-PL',
|
|
70
|
+
'ro': 'ro-RO',
|
|
71
|
+
'ru': 'ru-RU',
|
|
72
|
+
'sk': 'sk-SK',
|
|
73
|
+
'sv': 'sv-SE',
|
|
74
|
+
'th': 'th-TH',
|
|
75
|
+
'tr': 'tr-TR',
|
|
76
|
+
'uk': 'uk-UA',
|
|
77
|
+
'vi': 'vi-VN',
|
|
78
|
+
};
|
|
@@ -1,177 +1,28 @@
|
|
|
1
1
|
/**
|
|
2
|
-
*
|
|
3
|
-
*
|
|
4
|
-
*
|
|
5
|
-
* SINGLE SOURCE OF TRUTH: Imports from constants/languages.ts
|
|
6
|
-
* - All language definitions come from one central location
|
|
7
|
-
* - Ensures consistency across app.json, i18n config, and UI
|
|
8
|
-
* - Automatic synchronization between all language configurations
|
|
9
|
-
*
|
|
10
|
-
* DEVICE LOCALE DETECTION:
|
|
11
|
-
* - First launch: Automatically detects device locale
|
|
12
|
-
* - Fallback: English (en-US) if device locale not supported
|
|
13
|
-
* - User choice: Persists after manual language selection
|
|
2
|
+
* Languages Configuration - Main Export
|
|
3
|
+
* Central export point for all language-related functionality
|
|
14
4
|
*/
|
|
15
5
|
|
|
16
|
-
import * as Localization from 'expo-localization';
|
|
17
6
|
import { languageRegistry } from './languagesData';
|
|
18
7
|
import type { Language } from '../storage/types/LocalizationState';
|
|
19
8
|
|
|
20
|
-
//
|
|
9
|
+
// Re-export from DeviceLocale
|
|
10
|
+
export { DEFAULT_LANGUAGE, getDeviceLocale } from './DeviceLocale';
|
|
11
|
+
|
|
12
|
+
// Re-export from LanguageQuery
|
|
13
|
+
export {
|
|
14
|
+
getSupportedLanguages,
|
|
15
|
+
getLanguageByCode,
|
|
16
|
+
isLanguageSupported,
|
|
17
|
+
getDefaultLanguage,
|
|
18
|
+
searchLanguages,
|
|
19
|
+
} from './LanguageQuery';
|
|
20
|
+
|
|
21
|
+
// Re-export from LocaleMapping
|
|
22
|
+
export { LOCALE_MAPPING } from './LocaleMapping';
|
|
23
|
+
|
|
24
|
+
// Backward compatibility
|
|
21
25
|
export const getSUPPORTED_LANGUAGES = () => languageRegistry.getLanguages();
|
|
22
26
|
export const getLANGUAGES = () => languageRegistry.getLanguages();
|
|
23
|
-
|
|
24
|
-
// For backward compatibility while keeping it dynamic
|
|
25
27
|
export const SUPPORTED_LANGUAGES: Language[] = languageRegistry.getLanguages();
|
|
26
28
|
export const LANGUAGES = SUPPORTED_LANGUAGES;
|
|
27
|
-
|
|
28
|
-
export const DEFAULT_LANGUAGE = 'en-US';
|
|
29
|
-
|
|
30
|
-
/**
|
|
31
|
-
* Locale mapping for device locales to supported app locales
|
|
32
|
-
* Maps short codes and regional variants to en-US
|
|
33
|
-
*/
|
|
34
|
-
const LOCALE_MAPPING: Record<string, string> = {
|
|
35
|
-
// English variants map to en-US
|
|
36
|
-
'en': 'en-US',
|
|
37
|
-
'en-US': 'en-US',
|
|
38
|
-
'en-GB': 'en-US',
|
|
39
|
-
'en-AU': 'en-US',
|
|
40
|
-
'en-CA': 'en-US',
|
|
41
|
-
'en-NZ': 'en-US',
|
|
42
|
-
'en-IE': 'en-US',
|
|
43
|
-
'en-ZA': 'en-US',
|
|
44
|
-
'en-SG': 'en-US',
|
|
45
|
-
'en-IN': 'en-US',
|
|
46
|
-
|
|
47
|
-
// Portuguese mappings
|
|
48
|
-
'pt': 'pt-PT',
|
|
49
|
-
'pt-BR': 'pt-BR',
|
|
50
|
-
'pt-PT': 'pt-PT',
|
|
51
|
-
|
|
52
|
-
// Spanish variants
|
|
53
|
-
'es': 'es-ES',
|
|
54
|
-
'es-ES': 'es-ES',
|
|
55
|
-
'es-MX': 'es-ES',
|
|
56
|
-
'es-AR': 'es-ES',
|
|
57
|
-
'es-US': 'es-ES',
|
|
58
|
-
|
|
59
|
-
// French variants
|
|
60
|
-
'fr': 'fr-FR',
|
|
61
|
-
'fr-FR': 'fr-FR',
|
|
62
|
-
'fr-CA': 'fr-FR',
|
|
63
|
-
'fr-BE': 'fr-FR',
|
|
64
|
-
'fr-CH': 'fr-FR',
|
|
65
|
-
|
|
66
|
-
// Norwegian
|
|
67
|
-
'no': 'no-NO',
|
|
68
|
-
'nb': 'no-NO',
|
|
69
|
-
'nn': 'no-NO',
|
|
70
|
-
|
|
71
|
-
// Chinese variants
|
|
72
|
-
'zh': 'zh-CN',
|
|
73
|
-
'zh-CN': 'zh-CN',
|
|
74
|
-
'zh-Hans': 'zh-CN',
|
|
75
|
-
'zh-Hans-CN': 'zh-CN',
|
|
76
|
-
'zh-Hant': 'zh-TW',
|
|
77
|
-
'zh-TW': 'zh-TW',
|
|
78
|
-
'zh-HK': 'zh-TW',
|
|
79
|
-
|
|
80
|
-
// Others
|
|
81
|
-
'ar': 'ar-SA',
|
|
82
|
-
'bg': 'bg-BG',
|
|
83
|
-
'cs': 'cs-CZ',
|
|
84
|
-
'da': 'da-DK',
|
|
85
|
-
'de': 'de-DE',
|
|
86
|
-
'el': 'el-GR',
|
|
87
|
-
'fi': 'fi-FI',
|
|
88
|
-
'hi': 'hi-IN',
|
|
89
|
-
'hr': 'hr-HR',
|
|
90
|
-
'hu': 'hu-HU',
|
|
91
|
-
'id': 'id-ID',
|
|
92
|
-
'it': 'it-IT',
|
|
93
|
-
'ja': 'ja-JP',
|
|
94
|
-
'ko': 'ko-KR',
|
|
95
|
-
'ms': 'ms-MY',
|
|
96
|
-
'nl': 'nl-NL',
|
|
97
|
-
'pl': 'pl-PL',
|
|
98
|
-
'ro': 'ro-RO',
|
|
99
|
-
'ru': 'ru-RU',
|
|
100
|
-
'sk': 'sk-SK',
|
|
101
|
-
'sv': 'sv-SE',
|
|
102
|
-
'th': 'th-TH',
|
|
103
|
-
'tr': 'tr-TR',
|
|
104
|
-
'uk': 'uk-UA',
|
|
105
|
-
'vi': 'vi-VN',
|
|
106
|
-
};
|
|
107
|
-
|
|
108
|
-
export const getLanguageByCode = (code: string): Language | undefined => {
|
|
109
|
-
return languageRegistry.getLanguageByCode(code);
|
|
110
|
-
};
|
|
111
|
-
|
|
112
|
-
export const isLanguageSupported = (code: string): boolean => {
|
|
113
|
-
return languageRegistry.isLanguageSupported(code);
|
|
114
|
-
};
|
|
115
|
-
|
|
116
|
-
export const getDefaultLanguage = (): Language => {
|
|
117
|
-
return SUPPORTED_LANGUAGES[0]; // en-US
|
|
118
|
-
};
|
|
119
|
-
|
|
120
|
-
/**
|
|
121
|
-
* Get device locale and map it to supported language
|
|
122
|
-
* Called ONLY on first launch (when no saved language preference exists)
|
|
123
|
-
*
|
|
124
|
-
* @returns Supported language code or DEFAULT_LANGUAGE
|
|
125
|
-
*
|
|
126
|
-
* Examples:
|
|
127
|
-
* - Device: "en" → Returns: "en-US"
|
|
128
|
-
* - Device: "en-GB" → Returns: "en-US"
|
|
129
|
-
* - Device: "tr" → Returns: "tr-TR" (if supported)
|
|
130
|
-
* - Device: "de" → Returns: "en-US" (not supported, fallback)
|
|
131
|
-
*/
|
|
132
|
-
export const getDeviceLocale = (): string => {
|
|
133
|
-
try {
|
|
134
|
-
// Get device locale (e.g., "en-US", "tr-TR", or just "en")
|
|
135
|
-
const deviceLocale = Localization.locale;
|
|
136
|
-
|
|
137
|
-
if (!deviceLocale) {
|
|
138
|
-
return DEFAULT_LANGUAGE;
|
|
139
|
-
}
|
|
140
|
-
|
|
141
|
-
// Check if exact match exists in LOCALE_MAPPING
|
|
142
|
-
if (LOCALE_MAPPING[deviceLocale]) {
|
|
143
|
-
return LOCALE_MAPPING[deviceLocale];
|
|
144
|
-
}
|
|
145
|
-
|
|
146
|
-
// Extract language code (e.g., "en" from "en-US")
|
|
147
|
-
const languageCode = deviceLocale.split('-')[0];
|
|
148
|
-
|
|
149
|
-
// Check if language code exists in LOCALE_MAPPING
|
|
150
|
-
if (LOCALE_MAPPING[languageCode]) {
|
|
151
|
-
return LOCALE_MAPPING[languageCode];
|
|
152
|
-
}
|
|
153
|
-
|
|
154
|
-
// Check if device locale is directly supported
|
|
155
|
-
if (isLanguageSupported(deviceLocale)) {
|
|
156
|
-
return deviceLocale;
|
|
157
|
-
}
|
|
158
|
-
|
|
159
|
-
// Fallback to default language
|
|
160
|
-
return DEFAULT_LANGUAGE;
|
|
161
|
-
} catch (error) {
|
|
162
|
-
// If any error occurs, fallback to default
|
|
163
|
-
return DEFAULT_LANGUAGE;
|
|
164
|
-
}
|
|
165
|
-
};
|
|
166
|
-
|
|
167
|
-
/**
|
|
168
|
-
* Search languages by name or native name
|
|
169
|
-
*/
|
|
170
|
-
export const searchLanguages = (query: string): Language[] => {
|
|
171
|
-
const lowerQuery = query.toLowerCase();
|
|
172
|
-
return SUPPORTED_LANGUAGES.filter(
|
|
173
|
-
(lang) =>
|
|
174
|
-
lang.name.toLowerCase().includes(lowerQuery) ||
|
|
175
|
-
lang.nativeName.toLowerCase().includes(lowerQuery)
|
|
176
|
-
);
|
|
177
|
-
};
|
|
@@ -0,0 +1,32 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Language Selection Hook
|
|
3
|
+
* Manages language selection state and filtering
|
|
4
|
+
*/
|
|
5
|
+
|
|
6
|
+
import { useState, useMemo } from 'react';
|
|
7
|
+
import { useLocalization } from './useLocalization';
|
|
8
|
+
import { searchLanguages } from '../config/LanguageQuery';
|
|
9
|
+
|
|
10
|
+
export const useLanguageSelection = () => {
|
|
11
|
+
const { currentLanguage, setLanguage } = useLocalization();
|
|
12
|
+
const [searchQuery, setSearchQuery] = useState('');
|
|
13
|
+
const [selectedCode, setSelectedCode] = useState(currentLanguage);
|
|
14
|
+
|
|
15
|
+
const filteredLanguages = useMemo(() => {
|
|
16
|
+
return searchLanguages(searchQuery);
|
|
17
|
+
}, [searchQuery]);
|
|
18
|
+
|
|
19
|
+
const handleLanguageSelect = async (code: string, onComplete?: () => void) => {
|
|
20
|
+
setSelectedCode(code);
|
|
21
|
+
await setLanguage(code);
|
|
22
|
+
onComplete?.();
|
|
23
|
+
};
|
|
24
|
+
|
|
25
|
+
return {
|
|
26
|
+
searchQuery,
|
|
27
|
+
setSearchQuery,
|
|
28
|
+
selectedCode,
|
|
29
|
+
filteredLanguages,
|
|
30
|
+
handleLanguageSelect,
|
|
31
|
+
};
|
|
32
|
+
};
|
|
@@ -35,35 +35,71 @@ export const useTranslationFunction = () => {
|
|
|
35
35
|
: defaultValueOrOptions || {};
|
|
36
36
|
|
|
37
37
|
if (!ready || !i18n.isInitialized) {
|
|
38
|
+
if (__DEV__) {
|
|
39
|
+
console.log(`[i18n] ⏳ Not ready - Key: "${key}" → Fallback: "${options.defaultValue || key}"`);
|
|
40
|
+
}
|
|
38
41
|
return options.defaultValue || key;
|
|
39
42
|
}
|
|
40
43
|
|
|
44
|
+
let finalResult: string;
|
|
45
|
+
let translationFound = false;
|
|
46
|
+
let usedKey = key;
|
|
47
|
+
|
|
41
48
|
// If key already has namespace separator (:), use as-is
|
|
42
49
|
if (key.includes(':')) {
|
|
43
50
|
const result = i18nextT(key, options);
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
|
|
51
|
+
finalResult = typeof result === 'string' ? result : key;
|
|
52
|
+
translationFound = finalResult !== key && finalResult !== options.defaultValue;
|
|
53
|
+
usedKey = key;
|
|
54
|
+
} else {
|
|
55
|
+
// Auto-detect namespace from first dot segment
|
|
56
|
+
const firstDotIndex = key.indexOf('.');
|
|
57
|
+
if (firstDotIndex > 0) {
|
|
58
|
+
const potentialNamespace = key.substring(0, firstDotIndex);
|
|
59
|
+
const restOfKey = key.substring(firstDotIndex + 1);
|
|
60
|
+
const hasNamespace = i18n.hasResourceBundle(i18n.language, potentialNamespace);
|
|
53
61
|
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
|
|
62
|
+
if (hasNamespace) {
|
|
63
|
+
const namespacedKey = `${potentialNamespace}:${restOfKey}`;
|
|
64
|
+
const namespacedResult = i18nextT(namespacedKey, options);
|
|
57
65
|
|
|
58
|
-
|
|
59
|
-
|
|
66
|
+
if (namespacedResult !== namespacedKey && namespacedResult !== restOfKey) {
|
|
67
|
+
finalResult = typeof namespacedResult === 'string' ? namespacedResult : key;
|
|
68
|
+
translationFound = true;
|
|
69
|
+
usedKey = namespacedKey;
|
|
70
|
+
} else {
|
|
71
|
+
// Fallback to original key
|
|
72
|
+
const result = i18nextT(key, options);
|
|
73
|
+
finalResult = typeof result === 'string' ? result : key;
|
|
74
|
+
translationFound = finalResult !== key && finalResult !== options.defaultValue;
|
|
75
|
+
usedKey = key;
|
|
76
|
+
}
|
|
77
|
+
} else {
|
|
78
|
+
// Fallback to original key
|
|
79
|
+
const result = i18nextT(key, options);
|
|
80
|
+
finalResult = typeof result === 'string' ? result : key;
|
|
81
|
+
translationFound = finalResult !== key && finalResult !== options.defaultValue;
|
|
82
|
+
usedKey = key;
|
|
60
83
|
}
|
|
84
|
+
} else {
|
|
85
|
+
// Fallback to original key
|
|
86
|
+
const result = i18nextT(key, options);
|
|
87
|
+
finalResult = typeof result === 'string' ? result : key;
|
|
88
|
+
translationFound = finalResult !== key && finalResult !== options.defaultValue;
|
|
89
|
+
usedKey = key;
|
|
90
|
+
}
|
|
91
|
+
}
|
|
92
|
+
|
|
93
|
+
// Development mode logging
|
|
94
|
+
if (__DEV__) {
|
|
95
|
+
if (translationFound) {
|
|
96
|
+
console.log(`[i18n] ✅ Found - Key: "${usedKey}" → "${finalResult}"`);
|
|
97
|
+
} else {
|
|
98
|
+
console.warn(`[i18n] ⚠️ Missing - Key: "${usedKey}" → Fallback: "${finalResult}"`);
|
|
61
99
|
}
|
|
62
100
|
}
|
|
63
101
|
|
|
64
|
-
|
|
65
|
-
const result = i18nextT(key, options);
|
|
66
|
-
return typeof result === 'string' ? result : key;
|
|
102
|
+
return finalResult;
|
|
67
103
|
}, [i18nextT, ready]);
|
|
68
104
|
|
|
69
105
|
return {
|
|
@@ -1,40 +1,22 @@
|
|
|
1
1
|
/**
|
|
2
2
|
* Language Selection Screen
|
|
3
|
-
*
|
|
4
|
-
* Language picker with search functionality
|
|
5
|
-
*
|
|
6
|
-
* Generic language selector that can be customized by consuming applications
|
|
3
|
+
* Generic language selector with search functionality
|
|
7
4
|
*/
|
|
8
5
|
|
|
9
|
-
import React
|
|
10
|
-
import {
|
|
11
|
-
View,
|
|
12
|
-
StyleSheet,
|
|
13
|
-
FlatList,
|
|
14
|
-
} from 'react-native';
|
|
6
|
+
import React from 'react';
|
|
7
|
+
import { View, StyleSheet, FlatList } from 'react-native';
|
|
15
8
|
// @ts-ignore - Optional peer dependency
|
|
16
9
|
import { useNavigation } from '@react-navigation/native';
|
|
17
10
|
import { useAppDesignTokens } from '@umituz/react-native-design-system';
|
|
18
|
-
import {
|
|
11
|
+
import { useLanguageSelection } from '../../infrastructure/hooks/useLanguageSelection';
|
|
19
12
|
import { LanguageItem } from '../components/LanguageItem';
|
|
20
13
|
import { SearchInput } from '../components/SearchInput';
|
|
14
|
+
import type { Language } from '../../infrastructure/storage/types/LocalizationState';
|
|
21
15
|
|
|
22
16
|
interface LanguageSelectionScreenProps {
|
|
23
|
-
/**
|
|
24
|
-
* Custom component for rendering language items
|
|
25
|
-
*/
|
|
26
17
|
renderLanguageItem?: (item: Language, isSelected: boolean, onSelect: (code: string) => void) => React.ReactNode;
|
|
27
|
-
/**
|
|
28
|
-
* Custom component for search input
|
|
29
|
-
*/
|
|
30
18
|
renderSearchInput?: (value: string, onChange: (value: string) => void, placeholder: string) => React.ReactNode;
|
|
31
|
-
/**
|
|
32
|
-
* Custom component for container
|
|
33
|
-
*/
|
|
34
19
|
containerComponent?: React.ComponentType<{ children: React.ReactNode }>;
|
|
35
|
-
/**
|
|
36
|
-
* Custom styles
|
|
37
|
-
*/
|
|
38
20
|
styles?: {
|
|
39
21
|
container?: any;
|
|
40
22
|
searchContainer?: any;
|
|
@@ -48,20 +30,10 @@ interface LanguageSelectionScreenProps {
|
|
|
48
30
|
clearButton?: any;
|
|
49
31
|
listContent?: any;
|
|
50
32
|
};
|
|
51
|
-
/**
|
|
52
|
-
* Search placeholder text
|
|
53
|
-
*/
|
|
54
33
|
searchPlaceholder: string;
|
|
55
|
-
/**
|
|
56
|
-
* Test ID for testing
|
|
57
|
-
*/
|
|
58
34
|
testID?: string;
|
|
59
35
|
}
|
|
60
36
|
|
|
61
|
-
/**
|
|
62
|
-
* Language Selection Screen Component
|
|
63
|
-
* Generic language selector that can be customized by consuming applications
|
|
64
|
-
*/
|
|
65
37
|
export const LanguageSelectionScreen: React.FC<LanguageSelectionScreenProps> = ({
|
|
66
38
|
renderLanguageItem,
|
|
67
39
|
renderSearchInput,
|
|
@@ -72,39 +44,36 @@ export const LanguageSelectionScreen: React.FC<LanguageSelectionScreenProps> = (
|
|
|
72
44
|
}) => {
|
|
73
45
|
const navigation = useNavigation();
|
|
74
46
|
const tokens = useAppDesignTokens();
|
|
75
|
-
const {
|
|
76
|
-
|
|
77
|
-
|
|
78
|
-
|
|
79
|
-
|
|
80
|
-
|
|
81
|
-
}
|
|
47
|
+
const {
|
|
48
|
+
searchQuery,
|
|
49
|
+
setSearchQuery,
|
|
50
|
+
selectedCode,
|
|
51
|
+
filteredLanguages,
|
|
52
|
+
handleLanguageSelect,
|
|
53
|
+
} = useLanguageSelection();
|
|
82
54
|
|
|
83
|
-
const
|
|
84
|
-
|
|
85
|
-
await setLanguage(code);
|
|
86
|
-
navigation.goBack();
|
|
55
|
+
const onSelect = (code: string) => {
|
|
56
|
+
handleLanguageSelect(code, () => navigation.goBack());
|
|
87
57
|
};
|
|
88
58
|
|
|
89
59
|
const renderItem = ({ item }: { item: Language }) => {
|
|
90
60
|
const isSelected = selectedCode === item.code;
|
|
91
61
|
|
|
92
62
|
if (renderLanguageItem) {
|
|
93
|
-
|
|
94
|
-
return <>{customItem}</>;
|
|
63
|
+
return <>{renderLanguageItem(item, isSelected, onSelect)}</>;
|
|
95
64
|
}
|
|
96
65
|
|
|
97
66
|
return (
|
|
98
67
|
<LanguageItem
|
|
99
68
|
item={item}
|
|
100
69
|
isSelected={isSelected}
|
|
101
|
-
onSelect={
|
|
70
|
+
onSelect={onSelect}
|
|
102
71
|
customStyles={customStyles}
|
|
103
72
|
/>
|
|
104
73
|
);
|
|
105
74
|
};
|
|
106
75
|
|
|
107
|
-
const
|
|
76
|
+
const renderSearchComponent = () => {
|
|
108
77
|
if (renderSearchInput) {
|
|
109
78
|
return renderSearchInput(searchQuery, setSearchQuery, searchPlaceholder);
|
|
110
79
|
}
|
|
@@ -121,7 +90,7 @@ export const LanguageSelectionScreen: React.FC<LanguageSelectionScreenProps> = (
|
|
|
121
90
|
|
|
122
91
|
const content = (
|
|
123
92
|
<View style={[styles.container, { backgroundColor: tokens.colors.backgroundPrimary }, customStyles?.container]} testID={testID}>
|
|
124
|
-
{
|
|
93
|
+
{renderSearchComponent()}
|
|
125
94
|
<FlatList
|
|
126
95
|
data={filteredLanguages}
|
|
127
96
|
renderItem={renderItem}
|