@umituz/react-native-localization 1.2.2 → 1.3.1
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/lib/index.d.ts +2 -0
- package/lib/index.d.ts.map +1 -1
- package/lib/index.js +2 -0
- package/lib/index.js.map +1 -1
- package/lib/infrastructure/components/LanguageSwitcher.d.ts +12 -0
- package/lib/infrastructure/components/LanguageSwitcher.d.ts.map +1 -0
- package/lib/infrastructure/components/LanguageSwitcher.js +49 -0
- package/lib/infrastructure/components/LanguageSwitcher.js.map +1 -0
- package/lib/infrastructure/components/useLanguageNavigation.d.ts +6 -0
- package/lib/infrastructure/components/useLanguageNavigation.d.ts.map +1 -0
- package/lib/infrastructure/components/useLanguageNavigation.js +16 -0
- package/lib/infrastructure/components/useLanguageNavigation.js.map +1 -0
- package/lib/infrastructure/storage/LocalizationStore.d.ts.map +1 -1
- package/lib/infrastructure/storage/LocalizationStore.js +137 -40
- package/lib/infrastructure/storage/LocalizationStore.js.map +1 -1
- package/package.json +8 -2
- package/scripts/setup-languages.js +34 -4
- package/scripts/utils/findLocalesDir.js +9 -12
- package/src/index.ts +2 -0
- package/src/infrastructure/components/LanguageSwitcher.tsx +86 -0
- package/src/infrastructure/components/useLanguageNavigation.ts +20 -0
- package/src/infrastructure/storage/LocalizationStore.ts +131 -46
package/lib/index.d.ts
CHANGED
|
@@ -4,6 +4,8 @@
|
|
|
4
4
|
*/
|
|
5
5
|
export { useLocalization, useLocalizationStore } from './infrastructure/storage/LocalizationStore';
|
|
6
6
|
export { LocalizationProvider } from './infrastructure/components/LocalizationProvider';
|
|
7
|
+
export { LanguageSwitcher } from './infrastructure/components/LanguageSwitcher';
|
|
8
|
+
export { useLanguageNavigation } from './infrastructure/components/useLanguageNavigation';
|
|
7
9
|
export { default as i18n } from './infrastructure/config/i18n';
|
|
8
10
|
export { SUPPORTED_LANGUAGES, DEFAULT_LANGUAGE, getLanguageByCode, isLanguageSupported, getDefaultLanguage, getDeviceLocale, } from './infrastructure/config/languages';
|
|
9
11
|
export type { Language, ILocalizationRepository } from './domain/repositories/ILocalizationRepository';
|
package/lib/index.d.ts.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"AAAA;;;GAGG;AAGH,OAAO,EAAE,eAAe,EAAE,oBAAoB,EAAE,MAAM,4CAA4C,CAAC;AAGnG,OAAO,EAAE,oBAAoB,EAAE,MAAM,kDAAkD,CAAC;
|
|
1
|
+
{"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"AAAA;;;GAGG;AAGH,OAAO,EAAE,eAAe,EAAE,oBAAoB,EAAE,MAAM,4CAA4C,CAAC;AAGnG,OAAO,EAAE,oBAAoB,EAAE,MAAM,kDAAkD,CAAC;AACxF,OAAO,EAAE,gBAAgB,EAAE,MAAM,8CAA8C,CAAC;AAChF,OAAO,EAAE,qBAAqB,EAAE,MAAM,mDAAmD,CAAC;AAG1F,OAAO,EAAE,OAAO,IAAI,IAAI,EAAE,MAAM,8BAA8B,CAAC;AAC/D,OAAO,EACL,mBAAmB,EACnB,gBAAgB,EAChB,iBAAiB,EACjB,mBAAmB,EACnB,kBAAkB,EAClB,eAAe,GAChB,MAAM,mCAAmC,CAAC;AAG3C,YAAY,EAAE,QAAQ,EAAE,uBAAuB,EAAE,MAAM,+CAA+C,CAAC"}
|
package/lib/index.js
CHANGED
|
@@ -6,6 +6,8 @@
|
|
|
6
6
|
export { useLocalization, useLocalizationStore } from './infrastructure/storage/LocalizationStore';
|
|
7
7
|
// Components
|
|
8
8
|
export { LocalizationProvider } from './infrastructure/components/LocalizationProvider';
|
|
9
|
+
export { LanguageSwitcher } from './infrastructure/components/LanguageSwitcher';
|
|
10
|
+
export { useLanguageNavigation } from './infrastructure/components/useLanguageNavigation';
|
|
9
11
|
// Configuration
|
|
10
12
|
export { default as i18n } from './infrastructure/config/i18n';
|
|
11
13
|
export { SUPPORTED_LANGUAGES, DEFAULT_LANGUAGE, getLanguageByCode, isLanguageSupported, getDefaultLanguage, getDeviceLocale, } from './infrastructure/config/languages';
|
package/lib/index.js.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"index.js","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"AAAA;;;GAGG;AAEH,QAAQ;AACR,OAAO,EAAE,eAAe,EAAE,oBAAoB,EAAE,MAAM,4CAA4C,CAAC;AAEnG,aAAa;AACb,OAAO,EAAE,oBAAoB,EAAE,MAAM,kDAAkD,CAAC;
|
|
1
|
+
{"version":3,"file":"index.js","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"AAAA;;;GAGG;AAEH,QAAQ;AACR,OAAO,EAAE,eAAe,EAAE,oBAAoB,EAAE,MAAM,4CAA4C,CAAC;AAEnG,aAAa;AACb,OAAO,EAAE,oBAAoB,EAAE,MAAM,kDAAkD,CAAC;AACxF,OAAO,EAAE,gBAAgB,EAAE,MAAM,8CAA8C,CAAC;AAChF,OAAO,EAAE,qBAAqB,EAAE,MAAM,mDAAmD,CAAC;AAE1F,gBAAgB;AAChB,OAAO,EAAE,OAAO,IAAI,IAAI,EAAE,MAAM,8BAA8B,CAAC;AAC/D,OAAO,EACL,mBAAmB,EACnB,gBAAgB,EAChB,iBAAiB,EACjB,mBAAmB,EACnB,kBAAkB,EAClB,eAAe,GAChB,MAAM,mCAAmC,CAAC"}
|
|
@@ -0,0 +1,12 @@
|
|
|
1
|
+
import React from 'react';
|
|
2
|
+
interface LanguageSwitcherProps {
|
|
3
|
+
showName?: boolean;
|
|
4
|
+
showFlag?: boolean;
|
|
5
|
+
color?: string;
|
|
6
|
+
navigationScreen?: string;
|
|
7
|
+
style?: any;
|
|
8
|
+
textStyle?: any;
|
|
9
|
+
}
|
|
10
|
+
export declare const LanguageSwitcher: React.FC<LanguageSwitcherProps>;
|
|
11
|
+
export default LanguageSwitcher;
|
|
12
|
+
//# sourceMappingURL=LanguageSwitcher.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"LanguageSwitcher.d.ts","sourceRoot":"","sources":["../../../src/infrastructure/components/LanguageSwitcher.tsx"],"names":[],"mappings":"AAAA,OAAO,KAAK,MAAM,OAAO,CAAC;AAQ1B,UAAU,qBAAqB;IAC7B,QAAQ,CAAC,EAAE,OAAO,CAAC;IACnB,QAAQ,CAAC,EAAE,OAAO,CAAC;IACnB,KAAK,CAAC,EAAE,MAAM,CAAC;IACf,gBAAgB,CAAC,EAAE,MAAM,CAAC;IAC1B,KAAK,CAAC,EAAE,GAAG,CAAC;IACZ,SAAS,CAAC,EAAE,GAAG,CAAC;CACjB;AAQD,eAAO,MAAM,gBAAgB,EAAE,KAAK,CAAC,EAAE,CAAC,qBAAqB,CAwC5D,CAAC;AAqBF,eAAe,gBAAgB,CAAC"}
|
|
@@ -0,0 +1,49 @@
|
|
|
1
|
+
import React from 'react';
|
|
2
|
+
import { TouchableOpacity, Text, StyleSheet } from 'react-native';
|
|
3
|
+
// @ts-ignore - Optional peer dependency
|
|
4
|
+
import { useNavigation } from '@react-navigation/native';
|
|
5
|
+
import { useLocalization } from '../storage/LocalizationStore';
|
|
6
|
+
import { getLanguageByCode, getDefaultLanguage } from '../config/languages';
|
|
7
|
+
const languageSwitcherConfig = {
|
|
8
|
+
defaultIconSize: 20,
|
|
9
|
+
defaultNavigationScreen: 'LanguageSelection',
|
|
10
|
+
hitSlop: { top: 10, bottom: 10, left: 10, right: 10 },
|
|
11
|
+
};
|
|
12
|
+
export const LanguageSwitcher = ({ showName = false, showFlag = true, color, navigationScreen = languageSwitcherConfig.defaultNavigationScreen, style, textStyle, }) => {
|
|
13
|
+
const navigation = useNavigation();
|
|
14
|
+
const { currentLanguage } = useLocalization();
|
|
15
|
+
const currentLang = getLanguageByCode(currentLanguage) || getDefaultLanguage();
|
|
16
|
+
const navigateToLanguageSelection = () => {
|
|
17
|
+
if (navigation && navigationScreen) {
|
|
18
|
+
navigation.navigate(navigationScreen);
|
|
19
|
+
}
|
|
20
|
+
};
|
|
21
|
+
const iconColor = color || '#000000';
|
|
22
|
+
return (<TouchableOpacity style={[styles.container, style]} onPress={navigateToLanguageSelection} activeOpacity={0.7} hitSlop={languageSwitcherConfig.hitSlop}>
|
|
23
|
+
{showFlag && (<Text style={[styles.flag, textStyle]}>{currentLang.flag}</Text>)}
|
|
24
|
+
{showName && (<Text style={[styles.languageName, { color: iconColor }, textStyle]}>
|
|
25
|
+
{currentLang.nativeName}
|
|
26
|
+
</Text>)}
|
|
27
|
+
{!showName && !showFlag && (<Text style={[styles.icon, { color: iconColor }]}>🌐</Text>)}
|
|
28
|
+
</TouchableOpacity>);
|
|
29
|
+
};
|
|
30
|
+
const styles = StyleSheet.create({
|
|
31
|
+
container: {
|
|
32
|
+
flexDirection: 'row',
|
|
33
|
+
alignItems: 'center',
|
|
34
|
+
gap: 8,
|
|
35
|
+
paddingHorizontal: 4,
|
|
36
|
+
},
|
|
37
|
+
flag: {
|
|
38
|
+
fontSize: 20,
|
|
39
|
+
},
|
|
40
|
+
languageName: {
|
|
41
|
+
fontSize: 14,
|
|
42
|
+
fontWeight: '600',
|
|
43
|
+
},
|
|
44
|
+
icon: {
|
|
45
|
+
fontSize: 20,
|
|
46
|
+
},
|
|
47
|
+
});
|
|
48
|
+
export default LanguageSwitcher;
|
|
49
|
+
//# sourceMappingURL=LanguageSwitcher.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"LanguageSwitcher.js","sourceRoot":"","sources":["../../../src/infrastructure/components/LanguageSwitcher.tsx"],"names":[],"mappings":"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"}
|
|
@@ -0,0 +1,6 @@
|
|
|
1
|
+
import { Language } from '../../domain/repositories/ILocalizationRepository';
|
|
2
|
+
export declare const useLanguageNavigation: (navigationScreen: string) => {
|
|
3
|
+
currentLang: Language;
|
|
4
|
+
navigateToLanguageSelection: () => void;
|
|
5
|
+
};
|
|
6
|
+
//# sourceMappingURL=useLanguageNavigation.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"useLanguageNavigation.d.ts","sourceRoot":"","sources":["../../../src/infrastructure/components/useLanguageNavigation.ts"],"names":[],"mappings":"AAIA,OAAO,EAAE,QAAQ,EAAE,MAAM,mDAAmD,CAAC;AAE7E,eAAO,MAAM,qBAAqB,GAAI,kBAAkB,MAAM;;;CAY7D,CAAC"}
|
|
@@ -0,0 +1,16 @@
|
|
|
1
|
+
// @ts-ignore - Optional peer dependency
|
|
2
|
+
import { useNavigation } from '@react-navigation/native';
|
|
3
|
+
import { useLocalization } from '../storage/LocalizationStore';
|
|
4
|
+
import { getLanguageByCode, getDefaultLanguage } from '../config/languages';
|
|
5
|
+
export const useLanguageNavigation = (navigationScreen) => {
|
|
6
|
+
const navigation = useNavigation();
|
|
7
|
+
const { currentLanguage } = useLocalization();
|
|
8
|
+
const currentLang = getLanguageByCode(currentLanguage) || getDefaultLanguage();
|
|
9
|
+
const navigateToLanguageSelection = () => {
|
|
10
|
+
if (navigation && navigationScreen) {
|
|
11
|
+
navigation.navigate(navigationScreen);
|
|
12
|
+
}
|
|
13
|
+
};
|
|
14
|
+
return { currentLang, navigateToLanguageSelection };
|
|
15
|
+
};
|
|
16
|
+
//# sourceMappingURL=useLanguageNavigation.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"useLanguageNavigation.js","sourceRoot":"","sources":["../../../src/infrastructure/components/useLanguageNavigation.ts"],"names":[],"mappings":"AAAA,wCAAwC;AACxC,OAAO,EAAE,aAAa,EAAE,MAAM,0BAA0B,CAAC;AACzD,OAAO,EAAE,eAAe,EAAE,MAAM,8BAA8B,CAAC;AAC/D,OAAO,EAAE,iBAAiB,EAAE,kBAAkB,EAAE,MAAM,qBAAqB,CAAC;AAG5E,MAAM,CAAC,MAAM,qBAAqB,GAAG,CAAC,gBAAwB,EAAE,EAAE;IAChE,MAAM,UAAU,GAAG,aAAa,EAAE,CAAC;IACnC,MAAM,EAAE,eAAe,EAAE,GAAG,eAAe,EAAE,CAAC;IAC9C,MAAM,WAAW,GAAG,iBAAiB,CAAC,eAAe,CAAC,IAAI,kBAAkB,EAAE,CAAC;IAE/E,MAAM,2BAA2B,GAAG,GAAG,EAAE;QACvC,IAAI,UAAU,IAAI,gBAAgB,EAAE,CAAC;YACnC,UAAU,CAAC,QAAQ,CAAC,gBAAyB,CAAC,CAAC;QACjD,CAAC;IACH,CAAC,CAAC;IAEF,OAAO,EAAE,WAAW,EAAE,2BAA2B,EAAE,CAAC;AACtD,CAAC,CAAC"}
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"LocalizationStore.d.ts","sourceRoot":"","sources":["../../../src/infrastructure/storage/LocalizationStore.ts"],"names":[],"mappings":"AAAA;;;GAGG;AAMH,OAAO,KAAK,EAAE,QAAQ,EAAE,MAAM,mDAAmD,CAAC;AAElF,UAAU,iBAAiB;IACzB,eAAe,EAAE,MAAM,CAAC;IACxB,KAAK,EAAE,OAAO,CAAC;IACf,aAAa,EAAE,OAAO,CAAC;IACvB,kBAAkB,EAAE,QAAQ,EAAE,CAAC;IAC/B,WAAW,EAAE,CAAC,YAAY,EAAE,MAAM,KAAK,OAAO,CAAC,IAAI,CAAC,CAAC;IACrD,UAAU,EAAE,MAAM,OAAO,CAAC,IAAI,CAAC,CAAC;CACjC;AAED,eAAO,MAAM,oBAAoB,
|
|
1
|
+
{"version":3,"file":"LocalizationStore.d.ts","sourceRoot":"","sources":["../../../src/infrastructure/storage/LocalizationStore.ts"],"names":[],"mappings":"AAAA;;;GAGG;AAMH,OAAO,KAAK,EAAE,QAAQ,EAAE,MAAM,mDAAmD,CAAC;AAElF,UAAU,iBAAiB;IACzB,eAAe,EAAE,MAAM,CAAC;IACxB,KAAK,EAAE,OAAO,CAAC;IACf,aAAa,EAAE,OAAO,CAAC;IACvB,kBAAkB,EAAE,QAAQ,EAAE,CAAC;IAC/B,WAAW,EAAE,CAAC,YAAY,EAAE,MAAM,KAAK,OAAO,CAAC,IAAI,CAAC,CAAC;IACrD,UAAU,EAAE,MAAM,OAAO,CAAC,IAAI,CAAC,CAAC;CACjC;AAED,eAAO,MAAM,oBAAoB,gFA0J9B,CAAC;AAEJ;;;GAGG;AACH,eAAO,MAAM,eAAe;;;;;;;gCApKE,MAAM,KAAK,OAAO,CAAC,IAAI,CAAC;sBAClC,OAAO,CAAC,IAAI,CAAC;CAyLhC,CAAC"}
|
|
@@ -19,54 +19,151 @@ export const useLocalizationStore = create((set, get) => ({
|
|
|
19
19
|
* - Fallback: English (en-US) if device locale not supported
|
|
20
20
|
*/
|
|
21
21
|
initialize: async () => {
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
|
|
22
|
+
/* eslint-disable-next-line no-console */
|
|
23
|
+
if (__DEV__)
|
|
24
|
+
console.log('[LocalizationStore] Starting initialization...');
|
|
25
|
+
try {
|
|
26
|
+
// ✅ CRITICAL FIX: Don't reset isInitialized if already initialized
|
|
27
|
+
// This prevents UI flash on re-initialization
|
|
28
|
+
const { isInitialized: alreadyInitialized } = get();
|
|
29
|
+
if (alreadyInitialized) {
|
|
30
|
+
/* eslint-disable-next-line no-console */
|
|
31
|
+
if (__DEV__)
|
|
32
|
+
console.log('[LocalizationStore] Already initialized, skipping...');
|
|
33
|
+
return;
|
|
34
|
+
}
|
|
35
|
+
/* eslint-disable-next-line no-console */
|
|
36
|
+
if (__DEV__)
|
|
37
|
+
console.log('[LocalizationStore] Getting saved language preference...');
|
|
38
|
+
// Get saved language preference
|
|
39
|
+
const savedLanguage = await StorageWrapper.getString(STORAGE_KEYS.LANGUAGE, DEFAULT_LANGUAGE);
|
|
40
|
+
/* eslint-disable-next-line no-console */
|
|
41
|
+
if (__DEV__)
|
|
42
|
+
console.log('[LocalizationStore] Saved language:', savedLanguage);
|
|
43
|
+
// ✅ DEVICE LOCALE DETECTION: Use device locale on first launch
|
|
44
|
+
let languageCode;
|
|
45
|
+
if (savedLanguage && savedLanguage !== DEFAULT_LANGUAGE) {
|
|
46
|
+
// User has previously selected a language → Use their choice
|
|
47
|
+
/* eslint-disable-next-line no-console */
|
|
48
|
+
if (__DEV__)
|
|
49
|
+
console.log('[LocalizationStore] Using saved language preference:', savedLanguage);
|
|
50
|
+
languageCode = savedLanguage;
|
|
51
|
+
}
|
|
52
|
+
else {
|
|
53
|
+
// First launch → Detect device locale automatically
|
|
54
|
+
/* eslint-disable-next-line no-console */
|
|
55
|
+
if (__DEV__)
|
|
56
|
+
console.log('[LocalizationStore] First launch, detecting device locale...');
|
|
57
|
+
languageCode = getDeviceLocale();
|
|
58
|
+
/* eslint-disable-next-line no-console */
|
|
59
|
+
if (__DEV__)
|
|
60
|
+
console.log('[LocalizationStore] Detected device locale:', languageCode);
|
|
61
|
+
// Save detected locale for future launches
|
|
62
|
+
await StorageWrapper.setString(STORAGE_KEYS.LANGUAGE, languageCode);
|
|
63
|
+
/* eslint-disable-next-line no-console */
|
|
64
|
+
if (__DEV__)
|
|
65
|
+
console.log('[LocalizationStore] Saved detected locale to storage');
|
|
66
|
+
}
|
|
67
|
+
// ✅ DEFENSIVE: Validate language exists, fallback to default
|
|
68
|
+
/* eslint-disable-next-line no-console */
|
|
69
|
+
if (__DEV__)
|
|
70
|
+
console.log('[LocalizationStore] Validating language code:', languageCode);
|
|
71
|
+
const language = getLanguageByCode(languageCode);
|
|
72
|
+
const finalLanguage = language ? languageCode : DEFAULT_LANGUAGE;
|
|
73
|
+
const finalLanguageObj = getLanguageByCode(finalLanguage);
|
|
74
|
+
if (!language) {
|
|
75
|
+
/* eslint-disable-next-line no-console */
|
|
76
|
+
console.warn('[LocalizationStore] ⚠️ Language not found:', languageCode, 'falling back to:', DEFAULT_LANGUAGE);
|
|
77
|
+
}
|
|
78
|
+
/* eslint-disable-next-line no-console */
|
|
79
|
+
if (__DEV__)
|
|
80
|
+
console.log('[LocalizationStore] Changing i18n language to:', finalLanguage);
|
|
81
|
+
await i18n.changeLanguage(finalLanguage);
|
|
82
|
+
/* eslint-disable-next-line no-console */
|
|
83
|
+
if (__DEV__)
|
|
84
|
+
console.log('[LocalizationStore] Setting store state...');
|
|
85
|
+
set({
|
|
86
|
+
currentLanguage: finalLanguage,
|
|
87
|
+
isRTL: finalLanguageObj?.rtl || false,
|
|
88
|
+
isInitialized: true, // ✅ Always set true to unblock UI
|
|
89
|
+
});
|
|
90
|
+
/* eslint-disable-next-line no-console */
|
|
91
|
+
if (__DEV__)
|
|
92
|
+
console.log('[LocalizationStore] ✅ Initialization complete. Language:', finalLanguage, 'RTL:', finalLanguageObj?.rtl || false);
|
|
34
93
|
}
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
|
|
94
|
+
catch (error) {
|
|
95
|
+
/* eslint-disable-next-line no-console */
|
|
96
|
+
console.error('[LocalizationStore] ❌ FATAL: Initialization failed:', error);
|
|
97
|
+
/* eslint-disable-next-line no-console */
|
|
98
|
+
if (error instanceof Error) {
|
|
99
|
+
/* eslint-disable-next-line no-console */
|
|
100
|
+
console.error('[LocalizationStore] Error name:', error.name);
|
|
101
|
+
/* eslint-disable-next-line no-console */
|
|
102
|
+
console.error('[LocalizationStore] Error message:', error.message);
|
|
103
|
+
/* eslint-disable-next-line no-console */
|
|
104
|
+
console.error('[LocalizationStore] Error stack:', error.stack);
|
|
105
|
+
}
|
|
106
|
+
// Set to default language even on error to prevent app from breaking
|
|
107
|
+
try {
|
|
108
|
+
await i18n.changeLanguage(DEFAULT_LANGUAGE);
|
|
109
|
+
set({
|
|
110
|
+
currentLanguage: DEFAULT_LANGUAGE,
|
|
111
|
+
isRTL: false,
|
|
112
|
+
isInitialized: true, // Set true even on error to unblock UI
|
|
113
|
+
});
|
|
114
|
+
/* eslint-disable-next-line no-console */
|
|
115
|
+
console.warn('[LocalizationStore] ⚠️ Fallback to default language due to error');
|
|
116
|
+
}
|
|
117
|
+
catch (fallbackError) {
|
|
118
|
+
/* eslint-disable-next-line no-console */
|
|
119
|
+
console.error('[LocalizationStore] ❌ CRITICAL: Even fallback failed:', fallbackError);
|
|
120
|
+
throw fallbackError;
|
|
121
|
+
}
|
|
40
122
|
}
|
|
41
|
-
// ✅ DEFENSIVE: Validate language exists, fallback to default
|
|
42
|
-
const language = getLanguageByCode(languageCode);
|
|
43
|
-
const finalLanguage = language ? languageCode : DEFAULT_LANGUAGE;
|
|
44
|
-
const finalLanguageObj = getLanguageByCode(finalLanguage);
|
|
45
|
-
await i18n.changeLanguage(finalLanguage);
|
|
46
|
-
set({
|
|
47
|
-
currentLanguage: finalLanguage,
|
|
48
|
-
isRTL: finalLanguageObj?.rtl || false,
|
|
49
|
-
isInitialized: true, // ✅ Always set true to unblock UI
|
|
50
|
-
});
|
|
51
123
|
},
|
|
52
124
|
/**
|
|
53
125
|
* Change language
|
|
54
126
|
* Updates i18n, state, and persists to AsyncStorage
|
|
55
127
|
*/
|
|
56
128
|
setLanguage: async (languageCode) => {
|
|
57
|
-
|
|
58
|
-
|
|
59
|
-
|
|
60
|
-
|
|
61
|
-
|
|
62
|
-
|
|
63
|
-
|
|
64
|
-
|
|
65
|
-
|
|
66
|
-
|
|
67
|
-
|
|
68
|
-
|
|
69
|
-
|
|
129
|
+
/* eslint-disable-next-line no-console */
|
|
130
|
+
if (__DEV__)
|
|
131
|
+
console.log('[LocalizationStore] Changing language to:', languageCode);
|
|
132
|
+
try {
|
|
133
|
+
const language = getLanguageByCode(languageCode);
|
|
134
|
+
// ✅ DEFENSIVE: Early return if unsupported language
|
|
135
|
+
if (!language) {
|
|
136
|
+
/* eslint-disable-next-line no-console */
|
|
137
|
+
console.warn('[LocalizationStore] ⚠️ Unsupported language code:', languageCode);
|
|
138
|
+
return;
|
|
139
|
+
}
|
|
140
|
+
/* eslint-disable-next-line no-console */
|
|
141
|
+
if (__DEV__)
|
|
142
|
+
console.log('[LocalizationStore] Updating i18n language...');
|
|
143
|
+
// Update i18n
|
|
144
|
+
await i18n.changeLanguage(languageCode);
|
|
145
|
+
/* eslint-disable-next-line no-console */
|
|
146
|
+
if (__DEV__)
|
|
147
|
+
console.log('[LocalizationStore] Updating store state...');
|
|
148
|
+
// Update state
|
|
149
|
+
set({
|
|
150
|
+
currentLanguage: languageCode,
|
|
151
|
+
isRTL: language.rtl || false,
|
|
152
|
+
});
|
|
153
|
+
/* eslint-disable-next-line no-console */
|
|
154
|
+
if (__DEV__)
|
|
155
|
+
console.log('[LocalizationStore] Persisting language preference...');
|
|
156
|
+
// Persist language preference
|
|
157
|
+
await StorageWrapper.setString(STORAGE_KEYS.LANGUAGE, languageCode);
|
|
158
|
+
/* eslint-disable-next-line no-console */
|
|
159
|
+
if (__DEV__)
|
|
160
|
+
console.log('[LocalizationStore] ✅ Language changed successfully to:', languageCode);
|
|
161
|
+
}
|
|
162
|
+
catch (error) {
|
|
163
|
+
/* eslint-disable-next-line no-console */
|
|
164
|
+
console.error('[LocalizationStore] ❌ Error changing language:', error);
|
|
165
|
+
throw error;
|
|
166
|
+
}
|
|
70
167
|
},
|
|
71
168
|
}));
|
|
72
169
|
/**
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"LocalizationStore.js","sourceRoot":"","sources":["../../../src/infrastructure/storage/LocalizationStore.ts"],"names":[],"mappings":"AAAA;;;GAGG;AAEH,OAAO,EAAE,MAAM,EAAE,MAAM,SAAS,CAAC;AACjC,OAAO,EAAE,cAAc,EAAE,YAAY,EAAE,MAAM,uBAAuB,CAAC;AACrE,OAAO,IAAI,MAAM,gBAAgB,CAAC;AAClC,OAAO,EAAE,mBAAmB,EAAE,gBAAgB,EAAE,iBAAiB,EAAE,eAAe,EAAE,MAAM,qBAAqB,CAAC;AAYhH,MAAM,CAAC,MAAM,oBAAoB,GAAG,MAAM,CAAoB,CAAC,GAAG,EAAE,GAAG,EAAE,EAAE,CAAC,CAAC;IAC3E,eAAe,EAAE,gBAAgB;IACjC,KAAK,EAAE,KAAK;IACZ,aAAa,EAAE,KAAK;IACpB,kBAAkB,EAAE,mBAAmB;IAEvC;;;;;;OAMG;IACH,UAAU,EAAE,KAAK,IAAI,EAAE;QACrB,mEAAmE;
|
|
1
|
+
{"version":3,"file":"LocalizationStore.js","sourceRoot":"","sources":["../../../src/infrastructure/storage/LocalizationStore.ts"],"names":[],"mappings":"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"}
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@umituz/react-native-localization",
|
|
3
|
-
"version": "1.
|
|
3
|
+
"version": "1.3.1",
|
|
4
4
|
"description": "Universal localization system for React Native apps with i18n support",
|
|
5
5
|
"main": "./lib/index.js",
|
|
6
6
|
"types": "./lib/index.d.ts",
|
|
@@ -46,7 +46,13 @@
|
|
|
46
46
|
},
|
|
47
47
|
"peerDependencies": {
|
|
48
48
|
"react": ">=18.2.0",
|
|
49
|
-
"react-native": ">=0.74.0"
|
|
49
|
+
"react-native": ">=0.74.0",
|
|
50
|
+
"@react-navigation/native": "^6.0.0"
|
|
51
|
+
},
|
|
52
|
+
"peerDependenciesMeta": {
|
|
53
|
+
"@react-navigation/native": {
|
|
54
|
+
"optional": true
|
|
55
|
+
}
|
|
50
56
|
},
|
|
51
57
|
"devDependencies": {
|
|
52
58
|
"typescript": "^5.3.3",
|
|
@@ -54,11 +54,40 @@ async function setupLanguages() {
|
|
|
54
54
|
|
|
55
55
|
console.log("🚀 Setting up language directories and files...\n");
|
|
56
56
|
|
|
57
|
-
//
|
|
57
|
+
// Create en-US directory if it doesn't exist
|
|
58
58
|
if (!fs.existsSync(enUSDir)) {
|
|
59
|
-
|
|
60
|
-
console.
|
|
61
|
-
|
|
59
|
+
fs.mkdirSync(enUSDir, { recursive: true });
|
|
60
|
+
console.log(`📁 Created en-US directory: ${enUSDir}`);
|
|
61
|
+
}
|
|
62
|
+
|
|
63
|
+
// Get default files from localization package
|
|
64
|
+
const packageRoot = path.dirname(require.resolve('@umituz/react-native-localization/package.json'));
|
|
65
|
+
const packageEnUSDir = path.join(packageRoot, 'src/infrastructure/locales/en-US');
|
|
66
|
+
const defaultFiles = [
|
|
67
|
+
'auth.json',
|
|
68
|
+
'branding.json',
|
|
69
|
+
'datetime.json',
|
|
70
|
+
'errors.json',
|
|
71
|
+
'general.json',
|
|
72
|
+
'navigation.json',
|
|
73
|
+
'onboarding.json',
|
|
74
|
+
'settings.json',
|
|
75
|
+
];
|
|
76
|
+
|
|
77
|
+
// Copy default files from package if they don't exist in project
|
|
78
|
+
let copiedFiles = 0;
|
|
79
|
+
if (fs.existsSync(packageEnUSDir)) {
|
|
80
|
+
for (const file of defaultFiles) {
|
|
81
|
+
const packageFile = path.join(packageEnUSDir, file);
|
|
82
|
+
const projectFile = path.join(enUSDir, file);
|
|
83
|
+
|
|
84
|
+
if (fs.existsSync(packageFile) && !fs.existsSync(projectFile)) {
|
|
85
|
+
const content = fs.readFileSync(packageFile, "utf8");
|
|
86
|
+
fs.writeFileSync(projectFile, content);
|
|
87
|
+
console.log(`📄 Copied default file: en-US/${file}`);
|
|
88
|
+
copiedFiles++;
|
|
89
|
+
}
|
|
90
|
+
}
|
|
62
91
|
}
|
|
63
92
|
|
|
64
93
|
// Automatically discover all JSON files in en-US directory
|
|
@@ -69,6 +98,7 @@ async function setupLanguages() {
|
|
|
69
98
|
|
|
70
99
|
if (files.length === 0) {
|
|
71
100
|
console.error("❌ No JSON files found in en-US directory!");
|
|
101
|
+
console.error(" Please add translation files to:", enUSDir);
|
|
72
102
|
process.exit(1);
|
|
73
103
|
}
|
|
74
104
|
|
|
@@ -14,16 +14,16 @@ const path = require('path');
|
|
|
14
14
|
function findLocalesDir() {
|
|
15
15
|
const projectRoot = process.cwd();
|
|
16
16
|
|
|
17
|
-
// Common paths to search
|
|
17
|
+
// Common paths to search (prefer simple structure first)
|
|
18
18
|
const possiblePaths = [
|
|
19
|
+
// Simple structure (preferred)
|
|
20
|
+
path.join(projectRoot, 'src/locales'),
|
|
21
|
+
path.join(projectRoot, 'locales'),
|
|
22
|
+
path.join(projectRoot, 'translations'),
|
|
19
23
|
// DDD structure
|
|
20
24
|
path.join(projectRoot, 'src/domains/localization/infrastructure/locales'),
|
|
21
25
|
path.join(projectRoot, 'src/domains/i18n/infrastructure/locales'),
|
|
22
26
|
path.join(projectRoot, 'src/infrastructure/localization/locales'),
|
|
23
|
-
// Simple structure
|
|
24
|
-
path.join(projectRoot, 'src/locales'),
|
|
25
|
-
path.join(projectRoot, 'locales'),
|
|
26
|
-
path.join(projectRoot, 'translations'),
|
|
27
27
|
// Alternative DDD
|
|
28
28
|
path.join(projectRoot, 'src/features/localization/locales'),
|
|
29
29
|
];
|
|
@@ -49,25 +49,22 @@ function getLocalesDir(createIfNotExists = false) {
|
|
|
49
49
|
|
|
50
50
|
if (!localesDir) {
|
|
51
51
|
if (createIfNotExists) {
|
|
52
|
-
// Try to create in most common location
|
|
52
|
+
// Try to create in most common location (prefer simple structure)
|
|
53
53
|
const projectRoot = process.cwd();
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
|
|
54
|
+
// Always prefer simple structure first
|
|
55
|
+
localesDir = path.join(projectRoot, 'src/locales');
|
|
57
56
|
if (!fs.existsSync(localesDir)) {
|
|
58
57
|
fs.mkdirSync(localesDir, { recursive: true });
|
|
59
|
-
fs.mkdirSync(enUSDir, { recursive: true });
|
|
60
58
|
console.log(`✅ Created locales directory: ${localesDir}`);
|
|
61
59
|
}
|
|
62
|
-
|
|
63
60
|
return localesDir;
|
|
64
61
|
}
|
|
65
62
|
|
|
66
63
|
console.error('❌ Locales directory not found!');
|
|
67
64
|
console.error('\nPlease create a locales directory in one of these locations:');
|
|
68
|
-
console.error(' - src/domains/localization/infrastructure/locales');
|
|
69
65
|
console.error(' - src/locales');
|
|
70
66
|
console.error(' - locales');
|
|
67
|
+
console.error(' - src/domains/localization/infrastructure/locales');
|
|
71
68
|
console.error('\nOr run: npm run i18n:setup');
|
|
72
69
|
process.exit(1);
|
|
73
70
|
}
|
package/src/index.ts
CHANGED
|
@@ -8,6 +8,8 @@ export { useLocalization, useLocalizationStore } from './infrastructure/storage/
|
|
|
8
8
|
|
|
9
9
|
// Components
|
|
10
10
|
export { LocalizationProvider } from './infrastructure/components/LocalizationProvider';
|
|
11
|
+
export { LanguageSwitcher } from './infrastructure/components/LanguageSwitcher';
|
|
12
|
+
export { useLanguageNavigation } from './infrastructure/components/useLanguageNavigation';
|
|
11
13
|
|
|
12
14
|
// Configuration
|
|
13
15
|
export { default as i18n } from './infrastructure/config/i18n';
|
|
@@ -0,0 +1,86 @@
|
|
|
1
|
+
import React from 'react';
|
|
2
|
+
import { TouchableOpacity, Text, View, StyleSheet } from 'react-native';
|
|
3
|
+
// @ts-ignore - Optional peer dependency
|
|
4
|
+
import { useNavigation } from '@react-navigation/native';
|
|
5
|
+
import { useLocalization } from '../storage/LocalizationStore';
|
|
6
|
+
import { getLanguageByCode, getDefaultLanguage } from '../config/languages';
|
|
7
|
+
import { Language } from '../../domain/repositories/ILocalizationRepository';
|
|
8
|
+
|
|
9
|
+
interface LanguageSwitcherProps {
|
|
10
|
+
showName?: boolean;
|
|
11
|
+
showFlag?: boolean;
|
|
12
|
+
color?: string;
|
|
13
|
+
navigationScreen?: string;
|
|
14
|
+
style?: any;
|
|
15
|
+
textStyle?: any;
|
|
16
|
+
}
|
|
17
|
+
|
|
18
|
+
const languageSwitcherConfig = {
|
|
19
|
+
defaultIconSize: 20,
|
|
20
|
+
defaultNavigationScreen: 'LanguageSelection',
|
|
21
|
+
hitSlop: { top: 10, bottom: 10, left: 10, right: 10 },
|
|
22
|
+
};
|
|
23
|
+
|
|
24
|
+
export const LanguageSwitcher: React.FC<LanguageSwitcherProps> = ({
|
|
25
|
+
showName = false,
|
|
26
|
+
showFlag = true,
|
|
27
|
+
color,
|
|
28
|
+
navigationScreen = languageSwitcherConfig.defaultNavigationScreen,
|
|
29
|
+
style,
|
|
30
|
+
textStyle,
|
|
31
|
+
}) => {
|
|
32
|
+
const navigation = useNavigation();
|
|
33
|
+
const { currentLanguage } = useLocalization();
|
|
34
|
+
const currentLang = getLanguageByCode(currentLanguage) || getDefaultLanguage();
|
|
35
|
+
|
|
36
|
+
const navigateToLanguageSelection = () => {
|
|
37
|
+
if (navigation && navigationScreen) {
|
|
38
|
+
navigation.navigate(navigationScreen as never);
|
|
39
|
+
}
|
|
40
|
+
};
|
|
41
|
+
|
|
42
|
+
const iconColor = color || '#000000';
|
|
43
|
+
|
|
44
|
+
return (
|
|
45
|
+
<TouchableOpacity
|
|
46
|
+
style={[styles.container, style]}
|
|
47
|
+
onPress={navigateToLanguageSelection}
|
|
48
|
+
activeOpacity={0.7}
|
|
49
|
+
hitSlop={languageSwitcherConfig.hitSlop}
|
|
50
|
+
>
|
|
51
|
+
{showFlag && (
|
|
52
|
+
<Text style={[styles.flag, textStyle]}>{currentLang.flag}</Text>
|
|
53
|
+
)}
|
|
54
|
+
{showName && (
|
|
55
|
+
<Text style={[styles.languageName, { color: iconColor }, textStyle]}>
|
|
56
|
+
{currentLang.nativeName}
|
|
57
|
+
</Text>
|
|
58
|
+
)}
|
|
59
|
+
{!showName && !showFlag && (
|
|
60
|
+
<Text style={[styles.icon, { color: iconColor }]}>🌐</Text>
|
|
61
|
+
)}
|
|
62
|
+
</TouchableOpacity>
|
|
63
|
+
);
|
|
64
|
+
};
|
|
65
|
+
|
|
66
|
+
const styles = StyleSheet.create({
|
|
67
|
+
container: {
|
|
68
|
+
flexDirection: 'row',
|
|
69
|
+
alignItems: 'center',
|
|
70
|
+
gap: 8,
|
|
71
|
+
paddingHorizontal: 4,
|
|
72
|
+
},
|
|
73
|
+
flag: {
|
|
74
|
+
fontSize: 20,
|
|
75
|
+
},
|
|
76
|
+
languageName: {
|
|
77
|
+
fontSize: 14,
|
|
78
|
+
fontWeight: '600',
|
|
79
|
+
},
|
|
80
|
+
icon: {
|
|
81
|
+
fontSize: 20,
|
|
82
|
+
},
|
|
83
|
+
});
|
|
84
|
+
|
|
85
|
+
export default LanguageSwitcher;
|
|
86
|
+
|
|
@@ -0,0 +1,20 @@
|
|
|
1
|
+
// @ts-ignore - Optional peer dependency
|
|
2
|
+
import { useNavigation } from '@react-navigation/native';
|
|
3
|
+
import { useLocalization } from '../storage/LocalizationStore';
|
|
4
|
+
import { getLanguageByCode, getDefaultLanguage } from '../config/languages';
|
|
5
|
+
import { Language } from '../../domain/repositories/ILocalizationRepository';
|
|
6
|
+
|
|
7
|
+
export const useLanguageNavigation = (navigationScreen: string) => {
|
|
8
|
+
const navigation = useNavigation();
|
|
9
|
+
const { currentLanguage } = useLocalization();
|
|
10
|
+
const currentLang = getLanguageByCode(currentLanguage) || getDefaultLanguage();
|
|
11
|
+
|
|
12
|
+
const navigateToLanguageSelection = () => {
|
|
13
|
+
if (navigation && navigationScreen) {
|
|
14
|
+
navigation.navigate(navigationScreen as never);
|
|
15
|
+
}
|
|
16
|
+
};
|
|
17
|
+
|
|
18
|
+
return { currentLang, navigateToLanguageSelection };
|
|
19
|
+
};
|
|
20
|
+
|
|
@@ -32,37 +32,100 @@ export const useLocalizationStore = create<LocalizationState>((set, get) => ({
|
|
|
32
32
|
* - Fallback: English (en-US) if device locale not supported
|
|
33
33
|
*/
|
|
34
34
|
initialize: async () => {
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
|
|
35
|
+
/* eslint-disable-next-line no-console */
|
|
36
|
+
if (__DEV__) console.log('[LocalizationStore] Starting initialization...');
|
|
37
|
+
|
|
38
|
+
try {
|
|
39
|
+
// ✅ CRITICAL FIX: Don't reset isInitialized if already initialized
|
|
40
|
+
// This prevents UI flash on re-initialization
|
|
41
|
+
const { isInitialized: alreadyInitialized } = get();
|
|
42
|
+
if (alreadyInitialized) {
|
|
43
|
+
/* eslint-disable-next-line no-console */
|
|
44
|
+
if (__DEV__) console.log('[LocalizationStore] Already initialized, skipping...');
|
|
45
|
+
return;
|
|
46
|
+
}
|
|
47
|
+
|
|
48
|
+
/* eslint-disable-next-line no-console */
|
|
49
|
+
if (__DEV__) console.log('[LocalizationStore] Getting saved language preference...');
|
|
50
|
+
// Get saved language preference
|
|
51
|
+
const savedLanguage = await StorageWrapper.getString(STORAGE_KEYS.LANGUAGE, DEFAULT_LANGUAGE);
|
|
52
|
+
/* eslint-disable-next-line no-console */
|
|
53
|
+
if (__DEV__) console.log('[LocalizationStore] Saved language:', savedLanguage);
|
|
54
|
+
|
|
55
|
+
// ✅ DEVICE LOCALE DETECTION: Use device locale on first launch
|
|
56
|
+
let languageCode: string;
|
|
57
|
+
if (savedLanguage && savedLanguage !== DEFAULT_LANGUAGE) {
|
|
58
|
+
// User has previously selected a language → Use their choice
|
|
59
|
+
/* eslint-disable-next-line no-console */
|
|
60
|
+
if (__DEV__) console.log('[LocalizationStore] Using saved language preference:', savedLanguage);
|
|
61
|
+
languageCode = savedLanguage;
|
|
62
|
+
} else {
|
|
63
|
+
// First launch → Detect device locale automatically
|
|
64
|
+
/* eslint-disable-next-line no-console */
|
|
65
|
+
if (__DEV__) console.log('[LocalizationStore] First launch, detecting device locale...');
|
|
66
|
+
languageCode = getDeviceLocale();
|
|
67
|
+
/* eslint-disable-next-line no-console */
|
|
68
|
+
if (__DEV__) console.log('[LocalizationStore] Detected device locale:', languageCode);
|
|
69
|
+
// Save detected locale for future launches
|
|
70
|
+
await StorageWrapper.setString(STORAGE_KEYS.LANGUAGE, languageCode);
|
|
71
|
+
/* eslint-disable-next-line no-console */
|
|
72
|
+
if (__DEV__) console.log('[LocalizationStore] Saved detected locale to storage');
|
|
73
|
+
}
|
|
74
|
+
|
|
75
|
+
// ✅ DEFENSIVE: Validate language exists, fallback to default
|
|
76
|
+
/* eslint-disable-next-line no-console */
|
|
77
|
+
if (__DEV__) console.log('[LocalizationStore] Validating language code:', languageCode);
|
|
78
|
+
const language = getLanguageByCode(languageCode);
|
|
79
|
+
const finalLanguage = language ? languageCode : DEFAULT_LANGUAGE;
|
|
80
|
+
const finalLanguageObj = getLanguageByCode(finalLanguage);
|
|
81
|
+
|
|
82
|
+
if (!language) {
|
|
83
|
+
/* eslint-disable-next-line no-console */
|
|
84
|
+
console.warn('[LocalizationStore] ⚠️ Language not found:', languageCode, 'falling back to:', DEFAULT_LANGUAGE);
|
|
85
|
+
}
|
|
86
|
+
|
|
87
|
+
/* eslint-disable-next-line no-console */
|
|
88
|
+
if (__DEV__) console.log('[LocalizationStore] Changing i18n language to:', finalLanguage);
|
|
89
|
+
await i18n.changeLanguage(finalLanguage);
|
|
90
|
+
|
|
91
|
+
/* eslint-disable-next-line no-console */
|
|
92
|
+
if (__DEV__) console.log('[LocalizationStore] Setting store state...');
|
|
93
|
+
set({
|
|
94
|
+
currentLanguage: finalLanguage,
|
|
95
|
+
isRTL: finalLanguageObj?.rtl || false,
|
|
96
|
+
isInitialized: true, // ✅ Always set true to unblock UI
|
|
97
|
+
});
|
|
98
|
+
|
|
99
|
+
/* eslint-disable-next-line no-console */
|
|
100
|
+
if (__DEV__) console.log('[LocalizationStore] ✅ Initialization complete. Language:', finalLanguage, 'RTL:', finalLanguageObj?.rtl || false);
|
|
101
|
+
} catch (error) {
|
|
102
|
+
/* eslint-disable-next-line no-console */
|
|
103
|
+
console.error('[LocalizationStore] ❌ FATAL: Initialization failed:', error);
|
|
104
|
+
/* eslint-disable-next-line no-console */
|
|
105
|
+
if (error instanceof Error) {
|
|
106
|
+
/* eslint-disable-next-line no-console */
|
|
107
|
+
console.error('[LocalizationStore] Error name:', error.name);
|
|
108
|
+
/* eslint-disable-next-line no-console */
|
|
109
|
+
console.error('[LocalizationStore] Error message:', error.message);
|
|
110
|
+
/* eslint-disable-next-line no-console */
|
|
111
|
+
console.error('[LocalizationStore] Error stack:', error.stack);
|
|
112
|
+
}
|
|
113
|
+
// Set to default language even on error to prevent app from breaking
|
|
114
|
+
try {
|
|
115
|
+
await i18n.changeLanguage(DEFAULT_LANGUAGE);
|
|
116
|
+
set({
|
|
117
|
+
currentLanguage: DEFAULT_LANGUAGE,
|
|
118
|
+
isRTL: false,
|
|
119
|
+
isInitialized: true, // Set true even on error to unblock UI
|
|
120
|
+
});
|
|
121
|
+
/* eslint-disable-next-line no-console */
|
|
122
|
+
console.warn('[LocalizationStore] ⚠️ Fallback to default language due to error');
|
|
123
|
+
} catch (fallbackError) {
|
|
124
|
+
/* eslint-disable-next-line no-console */
|
|
125
|
+
console.error('[LocalizationStore] ❌ CRITICAL: Even fallback failed:', fallbackError);
|
|
126
|
+
throw fallbackError;
|
|
127
|
+
}
|
|
53
128
|
}
|
|
54
|
-
|
|
55
|
-
// ✅ DEFENSIVE: Validate language exists, fallback to default
|
|
56
|
-
const language = getLanguageByCode(languageCode);
|
|
57
|
-
const finalLanguage = language ? languageCode : DEFAULT_LANGUAGE;
|
|
58
|
-
const finalLanguageObj = getLanguageByCode(finalLanguage);
|
|
59
|
-
|
|
60
|
-
await i18n.changeLanguage(finalLanguage);
|
|
61
|
-
set({
|
|
62
|
-
currentLanguage: finalLanguage,
|
|
63
|
-
isRTL: finalLanguageObj?.rtl || false,
|
|
64
|
-
isInitialized: true, // ✅ Always set true to unblock UI
|
|
65
|
-
});
|
|
66
129
|
},
|
|
67
130
|
|
|
68
131
|
/**
|
|
@@ -70,22 +133,44 @@ export const useLocalizationStore = create<LocalizationState>((set, get) => ({
|
|
|
70
133
|
* Updates i18n, state, and persists to AsyncStorage
|
|
71
134
|
*/
|
|
72
135
|
setLanguage: async (languageCode: string) => {
|
|
73
|
-
|
|
74
|
-
|
|
75
|
-
|
|
76
|
-
|
|
77
|
-
|
|
78
|
-
|
|
79
|
-
|
|
80
|
-
|
|
81
|
-
|
|
82
|
-
|
|
83
|
-
|
|
84
|
-
|
|
85
|
-
|
|
86
|
-
|
|
87
|
-
|
|
88
|
-
|
|
136
|
+
/* eslint-disable-next-line no-console */
|
|
137
|
+
if (__DEV__) console.log('[LocalizationStore] Changing language to:', languageCode);
|
|
138
|
+
|
|
139
|
+
try {
|
|
140
|
+
const language = getLanguageByCode(languageCode);
|
|
141
|
+
|
|
142
|
+
// ✅ DEFENSIVE: Early return if unsupported language
|
|
143
|
+
if (!language) {
|
|
144
|
+
/* eslint-disable-next-line no-console */
|
|
145
|
+
console.warn('[LocalizationStore] ⚠️ Unsupported language code:', languageCode);
|
|
146
|
+
return;
|
|
147
|
+
}
|
|
148
|
+
|
|
149
|
+
/* eslint-disable-next-line no-console */
|
|
150
|
+
if (__DEV__) console.log('[LocalizationStore] Updating i18n language...');
|
|
151
|
+
// Update i18n
|
|
152
|
+
await i18n.changeLanguage(languageCode);
|
|
153
|
+
|
|
154
|
+
/* eslint-disable-next-line no-console */
|
|
155
|
+
if (__DEV__) console.log('[LocalizationStore] Updating store state...');
|
|
156
|
+
// Update state
|
|
157
|
+
set({
|
|
158
|
+
currentLanguage: languageCode,
|
|
159
|
+
isRTL: language.rtl || false,
|
|
160
|
+
});
|
|
161
|
+
|
|
162
|
+
/* eslint-disable-next-line no-console */
|
|
163
|
+
if (__DEV__) console.log('[LocalizationStore] Persisting language preference...');
|
|
164
|
+
// Persist language preference
|
|
165
|
+
await StorageWrapper.setString(STORAGE_KEYS.LANGUAGE, languageCode);
|
|
166
|
+
|
|
167
|
+
/* eslint-disable-next-line no-console */
|
|
168
|
+
if (__DEV__) console.log('[LocalizationStore] ✅ Language changed successfully to:', languageCode);
|
|
169
|
+
} catch (error) {
|
|
170
|
+
/* eslint-disable-next-line no-console */
|
|
171
|
+
console.error('[LocalizationStore] ❌ Error changing language:', error);
|
|
172
|
+
throw error;
|
|
173
|
+
}
|
|
89
174
|
},
|
|
90
175
|
}));
|
|
91
176
|
|