@umituz/react-native-localization 1.2.0 → 1.2.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/infrastructure/config/i18n.d.ts +1 -4
- package/lib/infrastructure/config/i18n.d.ts.map +1 -1
- package/lib/infrastructure/config/i18n.js +27 -204
- package/lib/infrastructure/config/i18n.js.map +1 -1
- package/lib/infrastructure/config/languages.d.ts +1 -1
- package/lib/infrastructure/config/languages.d.ts.map +1 -1
- package/lib/infrastructure/config/languages.js +2 -2
- package/lib/infrastructure/config/languages.js.map +1 -1
- package/lib/infrastructure/config/languagesData.d.ts +22 -3
- package/lib/infrastructure/config/languagesData.d.ts.map +1 -1
- package/lib/infrastructure/config/languagesData.js +53 -31
- package/lib/infrastructure/config/languagesData.js.map +1 -1
- package/lib/infrastructure/storage/AsyncStorageWrapper.d.ts.map +1 -1
- package/lib/infrastructure/storage/AsyncStorageWrapper.js +2 -6
- package/lib/infrastructure/storage/AsyncStorageWrapper.js.map +1 -1
- package/lib/infrastructure/storage/LocalizationStore.d.ts +2 -7
- package/lib/infrastructure/storage/LocalizationStore.d.ts.map +1 -1
- package/lib/infrastructure/storage/LocalizationStore.js +35 -116
- package/lib/infrastructure/storage/LocalizationStore.js.map +1 -1
- package/package.json +7 -5
- package/src/infrastructure/config/i18n.ts +27 -195
- package/src/infrastructure/config/languages.ts +3 -3
- package/src/infrastructure/config/languagesData.ts +64 -32
- package/src/infrastructure/storage/AsyncStorageWrapper.ts +2 -4
- package/src/infrastructure/storage/LocalizationStore.ts +39 -140
|
@@ -1,16 +1,11 @@
|
|
|
1
1
|
/**
|
|
2
2
|
* Localization Store
|
|
3
3
|
* Zustand state management for language preferences with AsyncStorage persistence
|
|
4
|
-
*
|
|
5
|
-
* REFACTORED: Now uses centralized AsyncStorageWrapper for DRY compliance
|
|
6
|
-
* - Eliminates duplicate AsyncStorage calls
|
|
7
|
-
* - Consistent error handling and logging
|
|
8
|
-
* - Type-safe storage operations
|
|
9
4
|
*/
|
|
10
|
-
import { create } from
|
|
11
|
-
import { StorageWrapper, STORAGE_KEYS } from
|
|
12
|
-
import i18n from
|
|
13
|
-
import { SUPPORTED_LANGUAGES, DEFAULT_LANGUAGE, getLanguageByCode, getDeviceLocale
|
|
5
|
+
import { create } from 'zustand';
|
|
6
|
+
import { StorageWrapper, STORAGE_KEYS } from './AsyncStorageWrapper';
|
|
7
|
+
import i18n from '../config/i18n';
|
|
8
|
+
import { SUPPORTED_LANGUAGES, DEFAULT_LANGUAGE, getLanguageByCode, getDeviceLocale } from '../config/languages';
|
|
14
9
|
export const useLocalizationStore = create((set, get) => ({
|
|
15
10
|
currentLanguage: DEFAULT_LANGUAGE,
|
|
16
11
|
isRTL: false,
|
|
@@ -24,99 +19,35 @@ export const useLocalizationStore = create((set, get) => ({
|
|
|
24
19
|
* - Fallback: English (en-US) if device locale not supported
|
|
25
20
|
*/
|
|
26
21
|
initialize: async () => {
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
}
|
|
40
|
-
/* eslint-disable-next-line no-console */
|
|
41
|
-
if (__DEV__)
|
|
42
|
-
console.log("[LocalizationStore] Getting saved language preference...");
|
|
43
|
-
// ✅ Get saved language preference (returns null if not exists)
|
|
44
|
-
const savedLanguage = await StorageWrapper.getString(STORAGE_KEYS.LANGUAGE, DEFAULT_LANGUAGE);
|
|
45
|
-
/* eslint-disable-next-line no-console */
|
|
46
|
-
if (__DEV__)
|
|
47
|
-
console.log("[LocalizationStore] Saved language:", savedLanguage);
|
|
48
|
-
// ✅ DEVICE LOCALE DETECTION: Use device locale on first launch
|
|
49
|
-
let languageCode;
|
|
50
|
-
if (savedLanguage && savedLanguage !== DEFAULT_LANGUAGE) {
|
|
51
|
-
// User has previously selected a language → Use their choice
|
|
52
|
-
languageCode = savedLanguage;
|
|
53
|
-
/* eslint-disable-next-line no-console */
|
|
54
|
-
if (__DEV__)
|
|
55
|
-
console.log("[LocalizationStore] Using saved language:", languageCode);
|
|
56
|
-
}
|
|
57
|
-
else {
|
|
58
|
-
/* eslint-disable-next-line no-console */
|
|
59
|
-
if (__DEV__)
|
|
60
|
-
console.log("[LocalizationStore] First launch, detecting device locale...");
|
|
61
|
-
// First launch → Detect device locale automatically
|
|
62
|
-
languageCode = getDeviceLocale();
|
|
63
|
-
/* eslint-disable-next-line no-console */
|
|
64
|
-
if (__DEV__)
|
|
65
|
-
console.log("[LocalizationStore] Detected device locale:", languageCode);
|
|
66
|
-
// Save detected locale for future launches
|
|
67
|
-
await StorageWrapper.setString(STORAGE_KEYS.LANGUAGE, languageCode);
|
|
68
|
-
}
|
|
69
|
-
/* eslint-disable-next-line no-console */
|
|
70
|
-
if (__DEV__)
|
|
71
|
-
console.log("[LocalizationStore] Validating language code...");
|
|
72
|
-
// ✅ DEFENSIVE: Validate language exists, fallback to default
|
|
73
|
-
const language = getLanguageByCode(languageCode);
|
|
74
|
-
const finalLanguage = language ? languageCode : DEFAULT_LANGUAGE;
|
|
75
|
-
const finalLanguageObj = getLanguageByCode(finalLanguage);
|
|
76
|
-
/* eslint-disable-next-line no-console */
|
|
77
|
-
if (__DEV__)
|
|
78
|
-
console.log("[LocalizationStore] Final language:", finalLanguage);
|
|
79
|
-
/* eslint-disable-next-line no-console */
|
|
80
|
-
if (__DEV__)
|
|
81
|
-
console.log("[LocalizationStore] Changing i18n language...");
|
|
82
|
-
// Only change language if i18n is available
|
|
83
|
-
if (i18n && typeof i18n.changeLanguage === "function") {
|
|
84
|
-
await i18n.changeLanguage(finalLanguage);
|
|
85
|
-
/* eslint-disable-next-line no-console */
|
|
86
|
-
if (__DEV__)
|
|
87
|
-
console.log("[LocalizationStore] i18n language changed");
|
|
88
|
-
}
|
|
89
|
-
else {
|
|
90
|
-
/* eslint-disable-next-line no-console */
|
|
91
|
-
if (__DEV__)
|
|
92
|
-
console.warn("[LocalizationStore] i18n not available, skipping language change");
|
|
93
|
-
}
|
|
94
|
-
set({
|
|
95
|
-
currentLanguage: finalLanguage,
|
|
96
|
-
isRTL: finalLanguageObj?.rtl || false,
|
|
97
|
-
isInitialized: true, // ✅ Always set true to unblock UI
|
|
98
|
-
});
|
|
99
|
-
/* eslint-disable-next-line no-console */
|
|
100
|
-
if (__DEV__)
|
|
101
|
-
console.log("[LocalizationStore] Initialization completed successfully");
|
|
22
|
+
// ✅ CRITICAL FIX: Don't reset isInitialized if already initialized
|
|
23
|
+
// This prevents UI flash on re-initialization
|
|
24
|
+
const { isInitialized: alreadyInitialized } = get();
|
|
25
|
+
if (alreadyInitialized)
|
|
26
|
+
return;
|
|
27
|
+
// Get saved language preference
|
|
28
|
+
const savedLanguage = await StorageWrapper.getString(STORAGE_KEYS.LANGUAGE, DEFAULT_LANGUAGE);
|
|
29
|
+
// ✅ DEVICE LOCALE DETECTION: Use device locale on first launch
|
|
30
|
+
let languageCode;
|
|
31
|
+
if (savedLanguage && savedLanguage !== DEFAULT_LANGUAGE) {
|
|
32
|
+
// User has previously selected a language → Use their choice
|
|
33
|
+
languageCode = savedLanguage;
|
|
102
34
|
}
|
|
103
|
-
|
|
104
|
-
|
|
105
|
-
|
|
106
|
-
|
|
107
|
-
|
|
108
|
-
if (__DEV__)
|
|
109
|
-
console.error("[LocalizationStore] Error details:", {
|
|
110
|
-
message: error instanceof Error ? error.message : String(error),
|
|
111
|
-
stack: error instanceof Error ? error.stack : "No stack",
|
|
112
|
-
name: error instanceof Error ? error.name : "Unknown",
|
|
113
|
-
});
|
|
114
|
-
// Set initialized even on error to prevent blocking UI
|
|
115
|
-
set({
|
|
116
|
-
isInitialized: true,
|
|
117
|
-
});
|
|
118
|
-
throw error;
|
|
35
|
+
else {
|
|
36
|
+
// First launch → Detect device locale automatically
|
|
37
|
+
languageCode = getDeviceLocale();
|
|
38
|
+
// Save detected locale for future launches
|
|
39
|
+
await StorageWrapper.setString(STORAGE_KEYS.LANGUAGE, languageCode);
|
|
119
40
|
}
|
|
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
|
+
});
|
|
120
51
|
},
|
|
121
52
|
/**
|
|
122
53
|
* Change language
|
|
@@ -127,16 +58,14 @@ export const useLocalizationStore = create((set, get) => ({
|
|
|
127
58
|
// ✅ DEFENSIVE: Early return if unsupported language
|
|
128
59
|
if (!language)
|
|
129
60
|
return;
|
|
130
|
-
// Update i18n
|
|
131
|
-
|
|
132
|
-
await i18n.changeLanguage(languageCode);
|
|
133
|
-
}
|
|
61
|
+
// Update i18n
|
|
62
|
+
await i18n.changeLanguage(languageCode);
|
|
134
63
|
// Update state
|
|
135
64
|
set({
|
|
136
65
|
currentLanguage: languageCode,
|
|
137
66
|
isRTL: language.rtl || false,
|
|
138
67
|
});
|
|
139
|
-
//
|
|
68
|
+
// Persist language preference
|
|
140
69
|
await StorageWrapper.setString(STORAGE_KEYS.LANGUAGE, languageCode);
|
|
141
70
|
},
|
|
142
71
|
}));
|
|
@@ -147,18 +76,8 @@ export const useLocalizationStore = create((set, get) => ({
|
|
|
147
76
|
export const useLocalization = () => {
|
|
148
77
|
const { currentLanguage, isRTL, isInitialized, supportedLanguages, setLanguage, initialize, } = useLocalizationStore();
|
|
149
78
|
const currentLanguageObject = getLanguageByCode(currentLanguage);
|
|
150
|
-
// Safe translation function - returns key if i18n not available
|
|
151
|
-
const t = (key, options) => {
|
|
152
|
-
if (i18n && typeof i18n.t === "function") {
|
|
153
|
-
const result = i18n.t(key, options);
|
|
154
|
-
// i18n.t can return string or object, ensure we return string
|
|
155
|
-
return typeof result === "string" ? result : key;
|
|
156
|
-
}
|
|
157
|
-
// Fallback: return key if i18n not available
|
|
158
|
-
return key;
|
|
159
|
-
};
|
|
160
79
|
return {
|
|
161
|
-
t,
|
|
80
|
+
t: i18n.t.bind(i18n), // Translation function
|
|
162
81
|
currentLanguage,
|
|
163
82
|
currentLanguageObject,
|
|
164
83
|
isRTL,
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"LocalizationStore.js","sourceRoot":"","sources":["../../../src/infrastructure/storage/LocalizationStore.ts"],"names":[],"mappings":"AAAA
|
|
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.2.
|
|
3
|
+
"version": "1.2.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",
|
|
@@ -37,15 +37,17 @@
|
|
|
37
37
|
"type": "git",
|
|
38
38
|
"url": "https://github.com/umituz/react-native-localization"
|
|
39
39
|
},
|
|
40
|
-
"
|
|
41
|
-
"react": ">=18.2.0",
|
|
42
|
-
"react-native": ">=0.74.0",
|
|
43
|
-
"zustand": "^5.0.2",
|
|
40
|
+
"dependencies": {
|
|
44
41
|
"i18next": "^23.0.0",
|
|
45
42
|
"react-i18next": "^14.0.0",
|
|
43
|
+
"zustand": "^5.0.2",
|
|
46
44
|
"expo-localization": "~15.0.0",
|
|
47
45
|
"@react-native-async-storage/async-storage": "^1.21.0"
|
|
48
46
|
},
|
|
47
|
+
"peerDependencies": {
|
|
48
|
+
"react": ">=18.2.0",
|
|
49
|
+
"react-native": ">=0.74.0"
|
|
50
|
+
},
|
|
49
51
|
"devDependencies": {
|
|
50
52
|
"typescript": "^5.3.3",
|
|
51
53
|
"@types/react": "^18.2.45",
|
|
@@ -1,225 +1,57 @@
|
|
|
1
1
|
/**
|
|
2
2
|
* i18next Configuration
|
|
3
3
|
* Nested translation structure - common translations spread, domain translations nested
|
|
4
|
-
*
|
|
5
|
-
* CRITICAL: Package must be installed for i18n to work
|
|
6
|
-
* If package is not installed, i18n will gracefully degrade
|
|
7
4
|
*/
|
|
8
5
|
|
|
9
|
-
import i18n from
|
|
10
|
-
import { initReactI18next } from
|
|
11
|
-
import { SUPPORTED_LANGUAGES, DEFAULT_LANGUAGE } from
|
|
12
|
-
|
|
13
|
-
/**
|
|
14
|
-
* Check if required dependencies are available
|
|
15
|
-
*/
|
|
16
|
-
const checkDependencies = (): boolean => {
|
|
17
|
-
try {
|
|
18
|
-
// Check if i18next and react-i18next are available
|
|
19
|
-
if (!i18n || !initReactI18next) {
|
|
20
|
-
/* eslint-disable-next-line no-console */
|
|
21
|
-
if (__DEV__) console.warn("[i18n] Required dependencies not found. i18n will not work.");
|
|
22
|
-
return false;
|
|
23
|
-
}
|
|
24
|
-
return true;
|
|
25
|
-
} catch (error) {
|
|
26
|
-
/* eslint-disable-next-line no-console */
|
|
27
|
-
if (__DEV__) console.warn("[i18n] Error checking dependencies:", error);
|
|
28
|
-
return false;
|
|
29
|
-
}
|
|
30
|
-
};
|
|
6
|
+
import i18n from 'i18next';
|
|
7
|
+
import { initReactI18next } from 'react-i18next';
|
|
8
|
+
import { SUPPORTED_LANGUAGES, DEFAULT_LANGUAGE } from './languages';
|
|
31
9
|
|
|
32
10
|
/**
|
|
33
11
|
* Import all translations from modular folder structure
|
|
34
12
|
* Each locale has an index.ts that merges all JSON files
|
|
35
13
|
*
|
|
36
|
-
* Structure:
|
|
14
|
+
* Structure (OFFLINE-ONLY):
|
|
37
15
|
* locales/
|
|
38
16
|
* en-US/ (universal translations in localization domain)
|
|
39
|
-
* index.ts (merges
|
|
40
|
-
*
|
|
41
|
-
*
|
|
42
|
-
* ... (all 39 languages)
|
|
17
|
+
* index.ts (merges common.json, navigation.json, settings.json, onboarding.json, errors.json)
|
|
18
|
+
*
|
|
19
|
+
* All translations are offline-compatible and work without backend.
|
|
43
20
|
*/
|
|
44
21
|
|
|
45
|
-
// Import
|
|
46
|
-
import
|
|
47
|
-
import bgBG from "../locales/bg-BG";
|
|
48
|
-
import csCZ from "../locales/cs-CZ";
|
|
49
|
-
import daDK from "../locales/da-DK";
|
|
50
|
-
import deDE from "../locales/de-DE";
|
|
51
|
-
import elGR from "../locales/el-GR";
|
|
52
|
-
import enAU from "../locales/en-AU";
|
|
53
|
-
import enCA from "../locales/en-CA";
|
|
54
|
-
import enGB from "../locales/en-GB";
|
|
55
|
-
import enUS from "../locales/en-US";
|
|
56
|
-
import esES from "../locales/es-ES";
|
|
57
|
-
import esMX from "../locales/es-MX";
|
|
58
|
-
import fiFI from "../locales/fi-FI";
|
|
59
|
-
import frCA from "../locales/fr-CA";
|
|
60
|
-
import frFR from "../locales/fr-FR";
|
|
61
|
-
import hiIN from "../locales/hi-IN";
|
|
62
|
-
import hrHR from "../locales/hr-HR";
|
|
63
|
-
import huHU from "../locales/hu-HU";
|
|
64
|
-
import idID from "../locales/id-ID";
|
|
65
|
-
import itIT from "../locales/it-IT";
|
|
66
|
-
import jaJP from "../locales/ja-JP";
|
|
67
|
-
import koKR from "../locales/ko-KR";
|
|
68
|
-
import msMY from "../locales/ms-MY";
|
|
69
|
-
import nlNL from "../locales/nl-NL";
|
|
70
|
-
import noNO from "../locales/no-NO";
|
|
71
|
-
import plPL from "../locales/pl-PL";
|
|
72
|
-
import ptBR from "../locales/pt-BR";
|
|
73
|
-
import ptPT from "../locales/pt-PT";
|
|
74
|
-
import roRO from "../locales/ro-RO";
|
|
75
|
-
import ruRU from "../locales/ru-RU";
|
|
76
|
-
import skSK from "../locales/sk-SK";
|
|
77
|
-
import svSE from "../locales/sv-SE";
|
|
78
|
-
import thTH from "../locales/th-TH";
|
|
79
|
-
import tlPH from "../locales/tl-PH";
|
|
80
|
-
import trTR from "../locales/tr-TR";
|
|
81
|
-
import ukUA from "../locales/uk-UA";
|
|
82
|
-
import viVN from "../locales/vi-VN";
|
|
83
|
-
import zhCN from "../locales/zh-CN";
|
|
84
|
-
import zhTW from "../locales/zh-TW";
|
|
22
|
+
// Import universal translations from localization domain
|
|
23
|
+
import localizationEnUS from '../locales/en-US';
|
|
85
24
|
|
|
86
25
|
/**
|
|
87
26
|
* Translation Resources
|
|
88
|
-
* All 39 supported languages loaded
|
|
89
27
|
* Nested structure - domain-based organization with direct key access
|
|
90
|
-
* Example: settings.theme.title, onboarding.welcome.title
|
|
28
|
+
* Example: settings.theme.title, onboarding.welcome.title
|
|
91
29
|
*/
|
|
92
30
|
const resources = {
|
|
93
|
-
|
|
94
|
-
|
|
95
|
-
|
|
96
|
-
|
|
97
|
-
|
|
98
|
-
"el-GR": { translation: elGR },
|
|
99
|
-
"en-AU": { translation: enAU },
|
|
100
|
-
"en-CA": { translation: enCA },
|
|
101
|
-
"en-GB": { translation: enGB },
|
|
102
|
-
"en-US": { translation: enUS },
|
|
103
|
-
"es-ES": { translation: esES },
|
|
104
|
-
"es-MX": { translation: esMX },
|
|
105
|
-
"fi-FI": { translation: fiFI },
|
|
106
|
-
"fr-CA": { translation: frCA },
|
|
107
|
-
"fr-FR": { translation: frFR },
|
|
108
|
-
"hi-IN": { translation: hiIN },
|
|
109
|
-
"hr-HR": { translation: hrHR },
|
|
110
|
-
"hu-HU": { translation: huHU },
|
|
111
|
-
"id-ID": { translation: idID },
|
|
112
|
-
"it-IT": { translation: itIT },
|
|
113
|
-
"ja-JP": { translation: jaJP },
|
|
114
|
-
"ko-KR": { translation: koKR },
|
|
115
|
-
"ms-MY": { translation: msMY },
|
|
116
|
-
"nl-NL": { translation: nlNL },
|
|
117
|
-
"no-NO": { translation: noNO },
|
|
118
|
-
"pl-PL": { translation: plPL },
|
|
119
|
-
"pt-BR": { translation: ptBR },
|
|
120
|
-
"pt-PT": { translation: ptPT },
|
|
121
|
-
"ro-RO": { translation: roRO },
|
|
122
|
-
"ru-RU": { translation: ruRU },
|
|
123
|
-
"sk-SK": { translation: skSK },
|
|
124
|
-
"sv-SE": { translation: svSE },
|
|
125
|
-
"th-TH": { translation: thTH },
|
|
126
|
-
"tl-PH": { translation: tlPH },
|
|
127
|
-
"tr-TR": { translation: trTR },
|
|
128
|
-
"uk-UA": { translation: ukUA },
|
|
129
|
-
"vi-VN": { translation: viVN },
|
|
130
|
-
"zh-CN": { translation: zhCN },
|
|
131
|
-
"zh-TW": { translation: zhTW },
|
|
31
|
+
'en-US': {
|
|
32
|
+
translation: {
|
|
33
|
+
...localizationEnUS,
|
|
34
|
+
},
|
|
35
|
+
},
|
|
132
36
|
};
|
|
133
37
|
|
|
134
38
|
/**
|
|
135
39
|
* Initialize i18next
|
|
136
|
-
* Deferred initialization to avoid React Native renderer conflicts
|
|
137
|
-
* Only initializes if dependencies are available
|
|
138
40
|
*/
|
|
139
|
-
|
|
140
|
-
|
|
141
|
-
|
|
142
|
-
|
|
143
|
-
|
|
144
|
-
// Check if dependencies are available
|
|
145
|
-
if (!checkDependencies()) {
|
|
146
|
-
/* eslint-disable-next-line no-console */
|
|
147
|
-
if (__DEV__) console.warn("[i18n] Dependencies not available. Skipping initialization.");
|
|
148
|
-
return;
|
|
149
|
-
}
|
|
41
|
+
i18n.use(initReactI18next).init({
|
|
42
|
+
resources,
|
|
43
|
+
lng: DEFAULT_LANGUAGE,
|
|
44
|
+
fallbackLng: DEFAULT_LANGUAGE,
|
|
150
45
|
|
|
151
|
-
|
|
152
|
-
|
|
153
|
-
|
|
154
|
-
/* eslint-disable-next-line no-console */
|
|
155
|
-
if (__DEV__)
|
|
156
|
-
console.log("[i18n] Resources count:", Object.keys(resources).length);
|
|
157
|
-
/* eslint-disable-next-line no-console */
|
|
158
|
-
if (__DEV__) console.log("[i18n] Default language:", DEFAULT_LANGUAGE);
|
|
46
|
+
interpolation: {
|
|
47
|
+
escapeValue: false, // React already escapes values
|
|
48
|
+
},
|
|
159
49
|
|
|
160
|
-
|
|
161
|
-
|
|
162
|
-
|
|
163
|
-
throw new Error("initReactI18next is undefined");
|
|
164
|
-
}
|
|
165
|
-
/* eslint-disable-next-line no-console */
|
|
166
|
-
if (__DEV__) console.log("[i18n] initReactI18next found");
|
|
167
|
-
|
|
168
|
-
/* eslint-disable-next-line no-console */
|
|
169
|
-
if (__DEV__) console.log("[i18n] Using initReactI18next...");
|
|
170
|
-
i18n.use(initReactI18next);
|
|
171
|
-
/* eslint-disable-next-line no-console */
|
|
172
|
-
if (__DEV__) console.log("[i18n] initReactI18next applied");
|
|
173
|
-
|
|
174
|
-
/* eslint-disable-next-line no-console */
|
|
175
|
-
if (__DEV__) console.log("[i18n] Calling i18n.init...");
|
|
176
|
-
i18n.init({
|
|
177
|
-
resources,
|
|
178
|
-
lng: DEFAULT_LANGUAGE,
|
|
179
|
-
fallbackLng: DEFAULT_LANGUAGE,
|
|
180
|
-
|
|
181
|
-
interpolation: {
|
|
182
|
-
escapeValue: false, // React already escapes values
|
|
183
|
-
},
|
|
184
|
-
|
|
185
|
-
react: {
|
|
186
|
-
useSuspense: false, // Disable suspense for React Native
|
|
187
|
-
},
|
|
188
|
-
|
|
189
|
-
compatibilityJSON: "v4", // Use i18next v4 JSON format
|
|
190
|
-
});
|
|
191
|
-
/* eslint-disable-next-line no-console */
|
|
192
|
-
if (__DEV__) console.log("[i18n] i18n.init completed");
|
|
193
|
-
|
|
194
|
-
isInitialized = true;
|
|
195
|
-
/* eslint-disable-next-line no-console */
|
|
196
|
-
if (__DEV__) console.log("[i18n] i18next initialized successfully");
|
|
197
|
-
} catch (error) {
|
|
198
|
-
/* eslint-disable-next-line no-console */
|
|
199
|
-
if (__DEV__) console.error("[i18n] Initialization error:", error);
|
|
200
|
-
/* eslint-disable-next-line no-console */
|
|
201
|
-
if (__DEV__)
|
|
202
|
-
console.error("[i18n] Error details:", {
|
|
203
|
-
message: error instanceof Error ? error.message : String(error),
|
|
204
|
-
stack: error instanceof Error ? error.stack : "No stack",
|
|
205
|
-
name: error instanceof Error ? error.name : "Unknown",
|
|
206
|
-
});
|
|
207
|
-
// Don't throw - allow app to continue without i18n
|
|
208
|
-
/* eslint-disable-next-line no-console */
|
|
209
|
-
if (__DEV__) console.warn("[i18n] Continuing without i18n initialization");
|
|
210
|
-
}
|
|
211
|
-
};
|
|
50
|
+
react: {
|
|
51
|
+
useSuspense: false, // Disable suspense for React Native
|
|
52
|
+
},
|
|
212
53
|
|
|
213
|
-
//
|
|
214
|
-
|
|
215
|
-
if (typeof global !== "undefined") {
|
|
216
|
-
// Use setTimeout to defer initialization
|
|
217
|
-
setTimeout(() => {
|
|
218
|
-
initializeI18n();
|
|
219
|
-
}, 0);
|
|
220
|
-
} else {
|
|
221
|
-
// Fallback: initialize immediately
|
|
222
|
-
initializeI18n();
|
|
223
|
-
}
|
|
54
|
+
compatibilityJSON: 'v4', // Use i18next v4 JSON format
|
|
55
|
+
});
|
|
224
56
|
|
|
225
57
|
export default i18n;
|
|
@@ -15,7 +15,7 @@
|
|
|
15
15
|
|
|
16
16
|
import * as Localization from 'expo-localization';
|
|
17
17
|
import { LANGUAGES } from './languagesData';
|
|
18
|
-
import
|
|
18
|
+
import { Language } from '../../domain/repositories/ILocalizationRepository';
|
|
19
19
|
|
|
20
20
|
// Single source of truth for supported languages
|
|
21
21
|
export const SUPPORTED_LANGUAGES: Language[] = LANGUAGES;
|
|
@@ -211,8 +211,7 @@ export const getDefaultLanguage = (): Language => {
|
|
|
211
211
|
export const getDeviceLocale = (): string => {
|
|
212
212
|
try {
|
|
213
213
|
// Get device locale (e.g., "en-US", "tr-TR", or just "en")
|
|
214
|
-
const
|
|
215
|
-
const deviceLocale = locales[0]?.languageTag;
|
|
214
|
+
const deviceLocale = Localization.locale;
|
|
216
215
|
|
|
217
216
|
if (!deviceLocale) {
|
|
218
217
|
return DEFAULT_LANGUAGE;
|
|
@@ -240,6 +239,7 @@ export const getDeviceLocale = (): string => {
|
|
|
240
239
|
return DEFAULT_LANGUAGE;
|
|
241
240
|
} catch (error) {
|
|
242
241
|
// If any error occurs, fallback to default
|
|
242
|
+
console.warn('[Localization] Failed to detect device locale:', error);
|
|
243
243
|
return DEFAULT_LANGUAGE;
|
|
244
244
|
}
|
|
245
245
|
};
|
|
@@ -1,39 +1,71 @@
|
|
|
1
1
|
/**
|
|
2
|
-
*
|
|
3
|
-
*
|
|
2
|
+
* Language Configuration
|
|
3
|
+
* Strategic language support with 29 languages (top revenue markets only)
|
|
4
|
+
* Optimized for maximum monetization with minimal maintenance cost
|
|
5
|
+
* Generated from App Factory language_config.yaml
|
|
4
6
|
*/
|
|
5
7
|
|
|
6
|
-
|
|
8
|
+
export interface Language {
|
|
9
|
+
code: string;
|
|
10
|
+
name: string;
|
|
11
|
+
nativeName: string;
|
|
12
|
+
flag: string;
|
|
13
|
+
}
|
|
7
14
|
|
|
8
15
|
export const LANGUAGES: Language[] = [
|
|
9
|
-
{ code: 'ar-SA', name: 'Arabic', nativeName: 'العربية', flag: '🇸🇦'
|
|
10
|
-
{ code: 'bg-BG', name: 'Bulgarian', nativeName: 'Български', flag: '🇧🇬'
|
|
11
|
-
{ code: 'cs-CZ', name: 'Czech', nativeName: 'Čeština', flag: '🇨🇿'
|
|
12
|
-
{ code: 'da-DK', name: 'Danish', nativeName: 'Dansk', flag: '🇩🇰'
|
|
13
|
-
{ code: 'de-DE', name: 'German', nativeName: 'Deutsch', flag: '🇩🇪'
|
|
14
|
-
{ code: 'en-US', name: 'English', nativeName: 'English', flag: '🇺🇸'
|
|
15
|
-
{ code: 'es-ES', name: 'Spanish', nativeName: 'Español', flag: '🇪🇸'
|
|
16
|
-
{ code: 'fi-FI', name: 'Finnish', nativeName: 'Suomi', flag: '🇫🇮'
|
|
17
|
-
{ code: 'fr-FR', name: 'French', nativeName: 'Français', flag: '🇫🇷'
|
|
18
|
-
{ code: 'hi-IN', name: 'Hindi', nativeName: 'हिन्दी', flag: '🇮🇳'
|
|
19
|
-
{ code: 'hu-HU', name: 'Hungarian', nativeName: 'Magyar', flag: '🇭🇺'
|
|
20
|
-
{ code: 'id-ID', name: 'Indonesian', nativeName: 'Bahasa Indonesia', flag: '🇮🇩'
|
|
21
|
-
{ code: 'it-IT', name: 'Italian', nativeName: 'Italiano', flag: '🇮🇹'
|
|
22
|
-
{ code: 'ja-JP', name: 'Japanese', nativeName: '日本語', flag: '🇯🇵'
|
|
23
|
-
{ code: 'ko-KR', name: 'Korean', nativeName: '한국어', flag: '🇰🇷'
|
|
24
|
-
{ code: 'ms-MY', name: 'Malay', nativeName: 'Bahasa Melayu', flag: '🇲🇾'
|
|
25
|
-
{ code: 'nl-NL', name: 'Dutch', nativeName: 'Nederlands', flag: '🇳🇱'
|
|
26
|
-
{ code: 'no-NO', name: 'Norwegian', nativeName: 'Norsk', flag: '🇳🇴'
|
|
27
|
-
{ code: 'pl-PL', name: 'Polish', nativeName: 'Polski', flag: '🇵🇱'
|
|
28
|
-
{ code: 'pt-PT', name: 'Portuguese', nativeName: 'Português', flag: '🇵🇹'
|
|
29
|
-
{ code: 'ro-RO', name: 'Romanian', nativeName: 'Română', flag: '🇷🇴'
|
|
30
|
-
{ code: 'ru-RU', name: 'Russian', nativeName: 'Русский', flag: '🇷🇺'
|
|
31
|
-
{ code: 'sv-SE', name: 'Swedish', nativeName: 'Svenska', flag: '🇸🇪'
|
|
32
|
-
{ code: 'th-TH', name: 'Thai', nativeName: 'ไทย', flag: '🇹🇭'
|
|
33
|
-
{ code: 'tl-PH', name: 'Filipino', nativeName: 'Filipino', flag: '🇵🇭'
|
|
34
|
-
{ code: 'tr-TR', name: 'Turkish', nativeName: 'Türkçe', flag: '🇹🇷'
|
|
35
|
-
{ code: 'uk-UA', name: 'Ukrainian', nativeName: 'Українська', flag: '🇺🇦'
|
|
36
|
-
{ code: 'vi-VN', name: 'Vietnamese', nativeName: 'Tiếng Việt', flag: '🇻🇳'
|
|
37
|
-
{ code: 'zh-CN', name: 'Chinese (Simplified)', nativeName: '简体中文', flag: '🇨🇳'
|
|
16
|
+
{ code: 'ar-SA', name: 'Arabic', nativeName: 'العربية', flag: '🇸🇦' },
|
|
17
|
+
{ code: 'bg-BG', name: 'Bulgarian', nativeName: 'Български', flag: '🇧🇬' },
|
|
18
|
+
{ code: 'cs-CZ', name: 'Czech', nativeName: 'Čeština', flag: '🇨🇿' },
|
|
19
|
+
{ code: 'da-DK', name: 'Danish', nativeName: 'Dansk', flag: '🇩🇰' },
|
|
20
|
+
{ code: 'de-DE', name: 'German', nativeName: 'Deutsch', flag: '🇩🇪' },
|
|
21
|
+
{ code: 'en-US', name: 'English', nativeName: 'English', flag: '🇺🇸' },
|
|
22
|
+
{ code: 'es-ES', name: 'Spanish', nativeName: 'Español', flag: '🇪🇸' },
|
|
23
|
+
{ code: 'fi-FI', name: 'Finnish', nativeName: 'Suomi', flag: '🇫🇮' },
|
|
24
|
+
{ code: 'fr-FR', name: 'French', nativeName: 'Français', flag: '🇫🇷' },
|
|
25
|
+
{ code: 'hi-IN', name: 'Hindi', nativeName: 'हिन्दी', flag: '🇮🇳' },
|
|
26
|
+
{ code: 'hu-HU', name: 'Hungarian', nativeName: 'Magyar', flag: '🇭🇺' },
|
|
27
|
+
{ code: 'id-ID', name: 'Indonesian', nativeName: 'Bahasa Indonesia', flag: '🇮🇩' },
|
|
28
|
+
{ code: 'it-IT', name: 'Italian', nativeName: 'Italiano', flag: '🇮🇹' },
|
|
29
|
+
{ code: 'ja-JP', name: 'Japanese', nativeName: '日本語', flag: '🇯🇵' },
|
|
30
|
+
{ code: 'ko-KR', name: 'Korean', nativeName: '한국어', flag: '🇰🇷' },
|
|
31
|
+
{ code: 'ms-MY', name: 'Malay', nativeName: 'Bahasa Melayu', flag: '🇲🇾' },
|
|
32
|
+
{ code: 'nl-NL', name: 'Dutch', nativeName: 'Nederlands', flag: '🇳🇱' },
|
|
33
|
+
{ code: 'no-NO', name: 'Norwegian', nativeName: 'Norsk', flag: '🇳🇴' },
|
|
34
|
+
{ code: 'pl-PL', name: 'Polish', nativeName: 'Polski', flag: '🇵🇱' },
|
|
35
|
+
{ code: 'pt-PT', name: 'Portuguese', nativeName: 'Português', flag: '🇵🇹' },
|
|
36
|
+
{ code: 'ro-RO', name: 'Romanian', nativeName: 'Română', flag: '🇷🇴' },
|
|
37
|
+
{ code: 'ru-RU', name: 'Russian', nativeName: 'Русский', flag: '🇷🇺' },
|
|
38
|
+
{ code: 'sv-SE', name: 'Swedish', nativeName: 'Svenska', flag: '🇸🇪' },
|
|
39
|
+
{ code: 'th-TH', name: 'Thai', nativeName: 'ไทย', flag: '🇹🇭' },
|
|
40
|
+
{ code: 'tl-PH', name: 'Filipino', nativeName: 'Filipino', flag: '🇵🇭' },
|
|
41
|
+
{ code: 'tr-TR', name: 'Turkish', nativeName: 'Türkçe', flag: '🇹🇷' },
|
|
42
|
+
{ code: 'uk-UA', name: 'Ukrainian', nativeName: 'Українська', flag: '🇺🇦' },
|
|
43
|
+
{ code: 'vi-VN', name: 'Vietnamese', nativeName: 'Tiếng Việt', flag: '🇻🇳' },
|
|
44
|
+
{ code: 'zh-CN', name: 'Chinese (Simplified)', nativeName: '简体中文', flag: '🇨🇳' },
|
|
38
45
|
];
|
|
39
46
|
|
|
47
|
+
/**
|
|
48
|
+
* Get language by code
|
|
49
|
+
*/
|
|
50
|
+
export const getLanguageByCode = (code: string): Language | undefined => {
|
|
51
|
+
return LANGUAGES.find(lang => lang.code === code);
|
|
52
|
+
};
|
|
53
|
+
|
|
54
|
+
/**
|
|
55
|
+
* Get default language (en-US)
|
|
56
|
+
*/
|
|
57
|
+
export const getDefaultLanguage = (): Language => {
|
|
58
|
+
return LANGUAGES.find(lang => lang.code === 'en-US')!;
|
|
59
|
+
};
|
|
60
|
+
|
|
61
|
+
/**
|
|
62
|
+
* Search languages by name or native name
|
|
63
|
+
*/
|
|
64
|
+
export const searchLanguages = (query: string): Language[] => {
|
|
65
|
+
const lowerQuery = query.toLowerCase();
|
|
66
|
+
return LANGUAGES.filter(
|
|
67
|
+
lang =>
|
|
68
|
+
lang.name.toLowerCase().includes(lowerQuery) ||
|
|
69
|
+
lang.nativeName.toLowerCase().includes(lowerQuery)
|
|
70
|
+
);
|
|
71
|
+
};
|
|
@@ -15,8 +15,7 @@ export const StorageWrapper = {
|
|
|
15
15
|
const value = await AsyncStorage.getItem(key);
|
|
16
16
|
return value ?? defaultValue;
|
|
17
17
|
} catch (error) {
|
|
18
|
-
|
|
19
|
-
if (__DEV__) console.warn('[Localization] Failed to get storage value:', error);
|
|
18
|
+
console.warn('[Localization] Failed to get storage value:', error);
|
|
20
19
|
return defaultValue;
|
|
21
20
|
}
|
|
22
21
|
},
|
|
@@ -25,8 +24,7 @@ export const StorageWrapper = {
|
|
|
25
24
|
try {
|
|
26
25
|
await AsyncStorage.setItem(key, value);
|
|
27
26
|
} catch (error) {
|
|
28
|
-
|
|
29
|
-
if (__DEV__) console.warn('[Localization] Failed to set storage value:', error);
|
|
27
|
+
console.warn('[Localization] Failed to set storage value:', error);
|
|
30
28
|
}
|
|
31
29
|
},
|
|
32
30
|
};
|