@umituz/react-native-localization 1.2.0 → 1.2.2

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.2",
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,179 +1,62 @@
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
40
  * Deferred initialization to avoid React Native renderer conflicts
137
- * Only initializes if dependencies are available
138
41
  */
139
42
  let isInitialized = false;
140
43
 
141
44
  const initializeI18n = () => {
142
45
  if (isInitialized) return;
143
46
 
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
- }
150
-
151
47
  try {
152
48
  /* 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);
159
-
160
- /* eslint-disable-next-line no-console */
161
- if (__DEV__) console.log("[i18n] Checking initReactI18next...");
49
+ if (__DEV__) console.log('[i18n] Initializing i18next...');
50
+
51
+ // Check if initReactI18next is available
162
52
  if (!initReactI18next) {
163
- throw new Error("initReactI18next is undefined");
53
+ throw new Error('initReactI18next is undefined');
164
54
  }
55
+
165
56
  /* 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({
57
+ if (__DEV__) console.log('[i18n] initReactI18next found, initializing...');
58
+
59
+ i18n.use(initReactI18next).init({
177
60
  resources,
178
61
  lng: DEFAULT_LANGUAGE,
179
62
  fallbackLng: DEFAULT_LANGUAGE,
@@ -186,33 +69,24 @@ const initializeI18n = () => {
186
69
  useSuspense: false, // Disable suspense for React Native
187
70
  },
188
71
 
189
- compatibilityJSON: "v4", // Use i18next v4 JSON format
72
+ compatibilityJSON: 'v4', // Use i18next v4 JSON format
190
73
  });
191
- /* eslint-disable-next-line no-console */
192
- if (__DEV__) console.log("[i18n] i18n.init completed");
193
-
74
+
194
75
  isInitialized = true;
195
76
  /* eslint-disable-next-line no-console */
196
- if (__DEV__) console.log("[i18n] i18next initialized successfully");
77
+ if (__DEV__) console.log('[i18n] i18next initialized successfully');
197
78
  } catch (error) {
198
79
  /* 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
- });
80
+ if (__DEV__) console.error('[i18n] Initialization error:', error);
207
81
  // Don't throw - allow app to continue without i18n
208
82
  /* eslint-disable-next-line no-console */
209
- if (__DEV__) console.warn("[i18n] Continuing without i18n initialization");
83
+ if (__DEV__) console.warn('[i18n] Continuing without i18n initialization');
210
84
  }
211
85
  };
212
86
 
213
87
  // Defer initialization until React is ready
214
88
  // React Native doesn't have window, so we check for global
215
- if (typeof global !== "undefined") {
89
+ if (typeof global !== 'undefined') {
216
90
  // Use setTimeout to defer initialization
217
91
  setTimeout(() => {
218
92
  initializeI18n();
@@ -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
  };