@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 CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@umituz/react-native-localization",
3
- "version": "3.5.9",
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
- import React, { useCallback, useMemo } from 'react';
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 { useLocalization } from '../hooks/useLocalization';
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 { currentLanguage } = useLocalization();
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 content = useMemo(() => {
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
- <Text style={[styles.icon, { color: iconColor }, iconStyle]}>🌐</Text>
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
- {content}
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, { useEffect, useRef, ReactNode, useCallback } from 'react';
8
- import { useLocalizationStore } from '../storage/LocalizationStore';
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 store = useLocalizationStore();
29
- const initialize = store.initialize;
30
- const setLanguage = store.setLanguage;
31
- const isInitialized = store.isInitialized;
32
- const currentLanguage = store.currentLanguage;
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
- * Supported Languages Configuration
3
- * Complete list of 29 languages supported by the app
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
- // SUPPORTED_LANGUAGES and LANGUAGES are now dynamic getters to ensure they always reflect the registry
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
- return typeof result === 'string' ? result : key;
45
- }
46
-
47
- // Auto-detect namespace from first dot segment
48
- const firstDotIndex = key.indexOf('.');
49
- if (firstDotIndex > 0) {
50
- const potentialNamespace = key.substring(0, firstDotIndex);
51
- const restOfKey = key.substring(firstDotIndex + 1);
52
- const hasNamespace = i18n.hasResourceBundle(i18n.language, potentialNamespace);
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
- if (hasNamespace) {
55
- const namespacedKey = `${potentialNamespace}:${restOfKey}`;
56
- const namespacedResult = i18nextT(namespacedKey, options);
62
+ if (hasNamespace) {
63
+ const namespacedKey = `${potentialNamespace}:${restOfKey}`;
64
+ const namespacedResult = i18nextT(namespacedKey, options);
57
65
 
58
- if (namespacedResult !== namespacedKey && namespacedResult !== restOfKey) {
59
- return typeof namespacedResult === 'string' ? namespacedResult : key;
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
- // Fallback to original key
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, { useState, useMemo } from '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 { useLocalization, searchLanguages, Language } from '../../index';
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 { t, currentLanguage, setLanguage } = useLocalization();
76
- const [searchQuery, setSearchQuery] = useState('');
77
- const [selectedCode, setSelectedCode] = useState(currentLanguage);
78
-
79
- const filteredLanguages = useMemo(() => {
80
- return searchLanguages(searchQuery);
81
- }, [searchQuery]);
47
+ const {
48
+ searchQuery,
49
+ setSearchQuery,
50
+ selectedCode,
51
+ filteredLanguages,
52
+ handleLanguageSelect,
53
+ } = useLanguageSelection();
82
54
 
83
- const handleLanguageSelect = async (code: string) => {
84
- setSelectedCode(code);
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
- const customItem = renderLanguageItem(item, isSelected, handleLanguageSelect);
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={handleLanguageSelect}
70
+ onSelect={onSelect}
102
71
  customStyles={customStyles}
103
72
  />
104
73
  );
105
74
  };
106
75
 
107
- const renderSearchInputComponent = () => {
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
- {renderSearchInputComponent()}
93
+ {renderSearchComponent()}
125
94
  <FlatList
126
95
  data={filteredLanguages}
127
96
  renderItem={renderItem}