@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.
@@ -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 "zustand";
11
- import { StorageWrapper, STORAGE_KEYS } from "./AsyncStorageWrapper";
12
- import i18n from "../config/i18n";
13
- import { SUPPORTED_LANGUAGES, DEFAULT_LANGUAGE, getLanguageByCode, getDeviceLocale, } from "../config/languages";
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
- try {
28
- /* eslint-disable-next-line no-console */
29
- if (__DEV__)
30
- console.log("[LocalizationStore] Starting initialization...");
31
- // ✅ CRITICAL FIX: Don't reset isInitialized if already initialized
32
- // This prevents UI flash on re-initialization
33
- const { isInitialized: alreadyInitialized } = get();
34
- if (alreadyInitialized) {
35
- /* eslint-disable-next-line no-console */
36
- if (__DEV__)
37
- console.log("[LocalizationStore] Already initialized, skipping");
38
- return;
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
- catch (error) {
104
- /* eslint-disable-next-line no-console */
105
- if (__DEV__)
106
- console.error("[LocalizationStore] Initialization error:", error);
107
- /* eslint-disable-next-line no-console */
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 (only if available)
131
- if (i18n && typeof i18n.changeLanguage === "function") {
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
- // DRY: Single line replaces try/catch/logging
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":"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"}
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.0",
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
- "peerDependencies": {
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 "i18next";
10
- import { initReactI18next } from "react-i18next";
11
- import { SUPPORTED_LANGUAGES, DEFAULT_LANGUAGE } from "./languages";
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 branding.json, datetime.json, errors.json, flashcards.json, general.json, navigation.json, onboarding.json, settings.json)
40
- * tr-TR/
41
- * index.ts (merges all translation modules)
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 all locale translations
46
- import arSA from "../locales/ar-SA";
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, flashcards.home.welcome
28
+ * Example: settings.theme.title, onboarding.welcome.title
91
29
  */
92
30
  const resources = {
93
- "ar-SA": { translation: arSA },
94
- "bg-BG": { translation: bgBG },
95
- "cs-CZ": { translation: csCZ },
96
- "da-DK": { translation: daDK },
97
- "de-DE": { translation: deDE },
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
- let isInitialized = false;
140
-
141
- const initializeI18n = () => {
142
- if (isInitialized) return;
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
- try {
152
- /* eslint-disable-next-line no-console */
153
- if (__DEV__) console.log("[i18n] Initializing i18next...");
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
- /* eslint-disable-next-line no-console */
161
- if (__DEV__) console.log("[i18n] Checking initReactI18next...");
162
- if (!initReactI18next) {
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
- // Defer initialization until React is ready
214
- // React Native doesn't have window, so we check for global
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 type { Language } from '../../domain/repositories/ILocalizationRepository';
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 locales = Localization.getLocales();
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
- * Languages Data
3
- * Complete list of supported languages with metadata
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
- import type { Language } from '../../domain/repositories/ILocalizationRepository';
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: '🇸🇦', rtl: true },
10
- { code: 'bg-BG', name: 'Bulgarian', nativeName: 'Български', flag: '🇧🇬', rtl: false },
11
- { code: 'cs-CZ', name: 'Czech', nativeName: 'Čeština', flag: '🇨🇿', rtl: false },
12
- { code: 'da-DK', name: 'Danish', nativeName: 'Dansk', flag: '🇩🇰', rtl: false },
13
- { code: 'de-DE', name: 'German', nativeName: 'Deutsch', flag: '🇩🇪', rtl: false },
14
- { code: 'en-US', name: 'English', nativeName: 'English', flag: '🇺🇸', rtl: false },
15
- { code: 'es-ES', name: 'Spanish', nativeName: 'Español', flag: '🇪🇸', rtl: false },
16
- { code: 'fi-FI', name: 'Finnish', nativeName: 'Suomi', flag: '🇫🇮', rtl: false },
17
- { code: 'fr-FR', name: 'French', nativeName: 'Français', flag: '🇫🇷', rtl: false },
18
- { code: 'hi-IN', name: 'Hindi', nativeName: 'हिन्दी', flag: '🇮🇳', rtl: false },
19
- { code: 'hu-HU', name: 'Hungarian', nativeName: 'Magyar', flag: '🇭🇺', rtl: false },
20
- { code: 'id-ID', name: 'Indonesian', nativeName: 'Bahasa Indonesia', flag: '🇮🇩', rtl: false },
21
- { code: 'it-IT', name: 'Italian', nativeName: 'Italiano', flag: '🇮🇹', rtl: false },
22
- { code: 'ja-JP', name: 'Japanese', nativeName: '日本語', flag: '🇯🇵', rtl: false },
23
- { code: 'ko-KR', name: 'Korean', nativeName: '한국어', flag: '🇰🇷', rtl: false },
24
- { code: 'ms-MY', name: 'Malay', nativeName: 'Bahasa Melayu', flag: '🇲🇾', rtl: false },
25
- { code: 'nl-NL', name: 'Dutch', nativeName: 'Nederlands', flag: '🇳🇱', rtl: false },
26
- { code: 'no-NO', name: 'Norwegian', nativeName: 'Norsk', flag: '🇳🇴', rtl: false },
27
- { code: 'pl-PL', name: 'Polish', nativeName: 'Polski', flag: '🇵🇱', rtl: false },
28
- { code: 'pt-PT', name: 'Portuguese', nativeName: 'Português', flag: '🇵🇹', rtl: false },
29
- { code: 'ro-RO', name: 'Romanian', nativeName: 'Română', flag: '🇷🇴', rtl: false },
30
- { code: 'ru-RU', name: 'Russian', nativeName: 'Русский', flag: '🇷🇺', rtl: false },
31
- { code: 'sv-SE', name: 'Swedish', nativeName: 'Svenska', flag: '🇸🇪', rtl: false },
32
- { code: 'th-TH', name: 'Thai', nativeName: 'ไทย', flag: '🇹🇭', rtl: false },
33
- { code: 'tl-PH', name: 'Filipino', nativeName: 'Filipino', flag: '🇵🇭', rtl: false },
34
- { code: 'tr-TR', name: 'Turkish', nativeName: 'Türkçe', flag: '🇹🇷', rtl: false },
35
- { code: 'uk-UA', name: 'Ukrainian', nativeName: 'Українська', flag: '🇺🇦', rtl: false },
36
- { code: 'vi-VN', name: 'Vietnamese', nativeName: 'Tiếng Việt', flag: '🇻🇳', rtl: false },
37
- { code: 'zh-CN', name: 'Chinese (Simplified)', nativeName: '简体中文', flag: '🇨🇳', rtl: false },
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
- /* eslint-disable-next-line no-console */
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
- /* eslint-disable-next-line no-console */
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
  };