@umituz/react-native-localization 2.2.2 → 2.3.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/package.json +1 -1
- package/src/infrastructure/config/I18nInitializer.ts +57 -51
- package/src/infrastructure/config/i18n.ts +2 -3
- package/src/infrastructure/hooks/useTranslation.ts +0 -49
- package/src/infrastructure/locales/en-US/common.json +57 -0
- package/src/infrastructure/locales/en-US/index.ts +27 -46
- package/src/infrastructure/storage/AsyncStorageWrapper.ts +8 -13
- package/src/infrastructure/storage/LanguageInitializer.ts +7 -26
- package/src/infrastructure/storage/LanguageSwitcher.ts +2 -48
package/package.json
CHANGED
|
@@ -1,117 +1,123 @@
|
|
|
1
1
|
/**
|
|
2
2
|
* i18n Initializer
|
|
3
3
|
*
|
|
4
|
-
* Handles i18n configuration and initialization
|
|
4
|
+
* Handles i18n configuration and initialization with namespace support
|
|
5
5
|
* - Auto-discovers project translations
|
|
6
|
-
* -
|
|
6
|
+
* - Namespace-based organization (common, auth, etc.)
|
|
7
7
|
* - React i18next integration
|
|
8
8
|
*/
|
|
9
9
|
|
|
10
10
|
import i18n from 'i18next';
|
|
11
11
|
import { initReactI18next } from 'react-i18next';
|
|
12
|
-
import { DEFAULT_LANGUAGE
|
|
12
|
+
import { DEFAULT_LANGUAGE } from './languages';
|
|
13
13
|
import { TranslationLoader } from './TranslationLoader';
|
|
14
14
|
|
|
15
|
+
const DEFAULT_NAMESPACE = 'common';
|
|
16
|
+
|
|
15
17
|
export class I18nInitializer {
|
|
16
18
|
private static reactI18nextInitialized = false;
|
|
17
19
|
|
|
18
20
|
/**
|
|
19
|
-
*
|
|
21
|
+
* Build resources object with namespace support
|
|
20
22
|
*/
|
|
21
|
-
private static
|
|
22
|
-
const
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
}
|
|
23
|
+
private static buildResources(): Record<string, Record<string, any>> {
|
|
24
|
+
const packageTranslations = TranslationLoader.loadPackageTranslations();
|
|
25
|
+
|
|
26
|
+
// Create namespace-based resources structure
|
|
27
|
+
const resources: Record<string, Record<string, any>> = {
|
|
28
|
+
'en-US': {},
|
|
29
|
+
};
|
|
30
|
+
|
|
31
|
+
// Package translations are already in namespace format (alerts, auth, etc.)
|
|
32
|
+
const enUSPackage = packageTranslations['en-US'] || {};
|
|
33
|
+
|
|
34
|
+
// Each key in packageTranslations is a namespace
|
|
35
|
+
for (const [namespace, translations] of Object.entries(enUSPackage)) {
|
|
36
|
+
resources['en-US'][namespace] = translations;
|
|
36
37
|
}
|
|
37
38
|
|
|
38
|
-
return
|
|
39
|
+
return resources;
|
|
39
40
|
}
|
|
40
41
|
|
|
41
42
|
/**
|
|
42
|
-
*
|
|
43
|
+
* Get all available namespaces from package translations
|
|
43
44
|
*/
|
|
44
|
-
private static
|
|
45
|
-
const resources: Record<string, { translation: any }> = {};
|
|
45
|
+
private static getNamespaces(): string[] {
|
|
46
46
|
const packageTranslations = TranslationLoader.loadPackageTranslations();
|
|
47
|
-
const
|
|
48
|
-
|
|
49
|
-
// For en-US, merge package and project translations
|
|
50
|
-
resources['en-US'] = {
|
|
51
|
-
translation: TranslationLoader.mergeTranslations(
|
|
52
|
-
packageTranslations['en-US'] || {},
|
|
53
|
-
projectTranslations
|
|
54
|
-
),
|
|
55
|
-
};
|
|
47
|
+
const enUSPackage = packageTranslations['en-US'] || {};
|
|
48
|
+
const namespaces = Object.keys(enUSPackage);
|
|
56
49
|
|
|
57
|
-
|
|
50
|
+
// Ensure default namespace is included
|
|
51
|
+
if (!namespaces.includes(DEFAULT_NAMESPACE)) {
|
|
52
|
+
namespaces.unshift(DEFAULT_NAMESPACE);
|
|
53
|
+
}
|
|
54
|
+
|
|
55
|
+
return namespaces;
|
|
58
56
|
}
|
|
59
57
|
|
|
60
58
|
/**
|
|
61
|
-
* Initialize i18next
|
|
59
|
+
* Initialize i18next with namespace support
|
|
62
60
|
*/
|
|
63
61
|
static initialize(): void {
|
|
64
|
-
// Prevent multiple initializations
|
|
65
62
|
if (i18n.isInitialized) {
|
|
66
63
|
return;
|
|
67
64
|
}
|
|
68
65
|
|
|
69
66
|
try {
|
|
70
|
-
// Use initReactI18next once
|
|
71
67
|
if (!this.reactI18nextInitialized) {
|
|
72
68
|
i18n.use(initReactI18next);
|
|
73
69
|
this.reactI18nextInitialized = true;
|
|
74
70
|
}
|
|
75
71
|
|
|
76
72
|
const resources = this.buildResources();
|
|
73
|
+
const namespaces = this.getNamespaces();
|
|
77
74
|
|
|
78
75
|
i18n.init({
|
|
79
76
|
resources,
|
|
80
77
|
lng: DEFAULT_LANGUAGE,
|
|
81
78
|
fallbackLng: DEFAULT_LANGUAGE,
|
|
79
|
+
ns: namespaces,
|
|
80
|
+
defaultNS: DEFAULT_NAMESPACE,
|
|
81
|
+
fallbackNS: DEFAULT_NAMESPACE,
|
|
82
82
|
|
|
83
83
|
interpolation: {
|
|
84
|
-
escapeValue: false,
|
|
84
|
+
escapeValue: false,
|
|
85
85
|
},
|
|
86
86
|
|
|
87
87
|
react: {
|
|
88
|
-
useSuspense: false,
|
|
88
|
+
useSuspense: false,
|
|
89
89
|
},
|
|
90
90
|
|
|
91
|
-
compatibilityJSON: 'v3',
|
|
92
|
-
pluralSeparator: '_',
|
|
93
|
-
keySeparator: '.',
|
|
91
|
+
compatibilityJSON: 'v3',
|
|
92
|
+
pluralSeparator: '_',
|
|
93
|
+
keySeparator: '.',
|
|
94
|
+
nsSeparator: ':',
|
|
95
|
+
|
|
96
|
+
saveMissing: false,
|
|
97
|
+
missingKeyHandler: false,
|
|
94
98
|
|
|
95
|
-
debug:
|
|
99
|
+
debug: false,
|
|
96
100
|
});
|
|
97
101
|
|
|
98
102
|
} catch (error) {
|
|
99
|
-
// Don't throw - allow app to continue without i18n
|
|
100
103
|
if (typeof __DEV__ !== 'undefined' && __DEV__) {
|
|
101
|
-
console.error('
|
|
104
|
+
console.error('[Localization] i18n initialization error:', error);
|
|
102
105
|
}
|
|
103
106
|
}
|
|
104
107
|
}
|
|
105
108
|
|
|
106
109
|
/**
|
|
107
|
-
* Add additional translation resources
|
|
110
|
+
* Add additional translation resources with namespace support
|
|
111
|
+
* @param languageCode - Language code (e.g., 'en-US')
|
|
112
|
+
* @param namespaceResources - Object with namespace keys and translation objects
|
|
108
113
|
*/
|
|
109
|
-
static addTranslationResources(
|
|
110
|
-
|
|
111
|
-
|
|
112
|
-
|
|
113
|
-
|
|
114
|
-
|
|
114
|
+
static addTranslationResources(
|
|
115
|
+
languageCode: string,
|
|
116
|
+
namespaceResources: Record<string, any>
|
|
117
|
+
): void {
|
|
118
|
+
for (const [namespace, translations] of Object.entries(namespaceResources)) {
|
|
119
|
+
if (translations && typeof translations === 'object') {
|
|
120
|
+
i18n.addResourceBundle(languageCode, namespace, translations, true, true);
|
|
115
121
|
}
|
|
116
122
|
}
|
|
117
123
|
}
|
|
@@ -1,15 +1,14 @@
|
|
|
1
1
|
/**
|
|
2
2
|
* i18n Configuration
|
|
3
3
|
*
|
|
4
|
-
* Auto-initializes i18n with
|
|
4
|
+
* Auto-initializes i18n with namespace support
|
|
5
|
+
* Usage: t('namespace:key') e.g., t('common:cancel')
|
|
5
6
|
*/
|
|
6
7
|
|
|
7
8
|
import { I18nInitializer } from './I18nInitializer';
|
|
8
9
|
import i18n from 'i18next';
|
|
9
10
|
|
|
10
|
-
// Initialize i18n automatically
|
|
11
11
|
I18nInitializer.initialize();
|
|
12
12
|
|
|
13
|
-
// Export for advanced usage
|
|
14
13
|
export const addTranslationResources = I18nInitializer.addTranslationResources;
|
|
15
14
|
export default i18n;
|
|
@@ -14,55 +14,6 @@ import i18n from '../config/i18n';
|
|
|
14
14
|
* Hook for translation functionality
|
|
15
15
|
*/
|
|
16
16
|
export const useTranslationFunction = (): ((key: string, options?: any) => string) => {
|
|
17
|
-
// Ensure settings translations are loaded
|
|
18
|
-
if (i18n.isInitialized && typeof i18n.t === 'function') {
|
|
19
|
-
const existingTranslations = i18n.getResourceBundle('en-US', 'translation') || {};
|
|
20
|
-
|
|
21
|
-
// Check if settings translations are missing
|
|
22
|
-
if (!existingTranslations.settings) {
|
|
23
|
-
// Add settings translations as fallback
|
|
24
|
-
const settingsTranslations = {
|
|
25
|
-
settings: {
|
|
26
|
-
editProfile: "Edit Profile",
|
|
27
|
-
sections: {
|
|
28
|
-
physicalInfoAndGoals: "Physical Info and Goals",
|
|
29
|
-
appSettings: "App Settings",
|
|
30
|
-
accountManagement: "Account Management"
|
|
31
|
-
},
|
|
32
|
-
personalInfo: {
|
|
33
|
-
title: "Personal Information",
|
|
34
|
-
subtitle: "Height, Weight, Age, Gender"
|
|
35
|
-
},
|
|
36
|
-
nutritionGoals: {
|
|
37
|
-
title: "Nutrition Goals",
|
|
38
|
-
subtitle: "Calories, Protein, Carbs, Fat"
|
|
39
|
-
},
|
|
40
|
-
notifications: {
|
|
41
|
-
title: "Notification Preferences",
|
|
42
|
-
subtitle: "Water and meal reminders"
|
|
43
|
-
},
|
|
44
|
-
darkMode: {
|
|
45
|
-
title: "Dark Mode",
|
|
46
|
-
subtitle: "Change app theme"
|
|
47
|
-
},
|
|
48
|
-
passwordChange: {
|
|
49
|
-
title: "Change Password"
|
|
50
|
-
},
|
|
51
|
-
saveChanges: "Save Changes",
|
|
52
|
-
logout: "Logout",
|
|
53
|
-
emptyState: {
|
|
54
|
-
title: "No recipes yet",
|
|
55
|
-
subtitle: "Create your first recipe to get started"
|
|
56
|
-
},
|
|
57
|
-
addToList: "Add to List"
|
|
58
|
-
}
|
|
59
|
-
};
|
|
60
|
-
|
|
61
|
-
const mergedTranslations = { ...existingTranslations, ...settingsTranslations };
|
|
62
|
-
i18n.addResourceBundle('en-US', 'translation', mergedTranslations, true, true);
|
|
63
|
-
}
|
|
64
|
-
}
|
|
65
|
-
|
|
66
17
|
// Use direct i18n.t for reliability (no React context issues)
|
|
67
18
|
return (key: string, options?: any): string => {
|
|
68
19
|
if (i18n.isInitialized && typeof i18n.t === 'function') {
|
|
@@ -0,0 +1,57 @@
|
|
|
1
|
+
{
|
|
2
|
+
"cancel": "Cancel",
|
|
3
|
+
"confirm": "Confirm",
|
|
4
|
+
"continue": "Continue",
|
|
5
|
+
"delete": "Delete",
|
|
6
|
+
"edit": "Edit",
|
|
7
|
+
"error": "Error",
|
|
8
|
+
"loading": "Loading...",
|
|
9
|
+
"notNow": "Not Now",
|
|
10
|
+
"ok": "OK",
|
|
11
|
+
"optional": "Optional",
|
|
12
|
+
"save": "Save",
|
|
13
|
+
"somethingWentWrong": "Something went wrong",
|
|
14
|
+
"success": "Success",
|
|
15
|
+
"close": "Close",
|
|
16
|
+
"done": "Done",
|
|
17
|
+
"next": "Next",
|
|
18
|
+
"previous": "Previous",
|
|
19
|
+
"skip": "Skip",
|
|
20
|
+
"retry": "Retry",
|
|
21
|
+
"refresh": "Refresh",
|
|
22
|
+
"filter": "Filter",
|
|
23
|
+
"sort": "Sort",
|
|
24
|
+
"add": "Add",
|
|
25
|
+
"remove": "Remove",
|
|
26
|
+
"update": "Update",
|
|
27
|
+
"create": "Create",
|
|
28
|
+
"view": "View",
|
|
29
|
+
"details": "Details",
|
|
30
|
+
"share": "Share",
|
|
31
|
+
"download": "Download",
|
|
32
|
+
"upload": "Upload",
|
|
33
|
+
"submit": "Submit",
|
|
34
|
+
"reset": "Reset",
|
|
35
|
+
"clear": "Clear",
|
|
36
|
+
"apply": "Apply",
|
|
37
|
+
"yes": "Yes",
|
|
38
|
+
"no": "No",
|
|
39
|
+
"or": "or",
|
|
40
|
+
"all": "All",
|
|
41
|
+
"none": "None",
|
|
42
|
+
"of": "of",
|
|
43
|
+
"select": "Select",
|
|
44
|
+
"selected": "Selected",
|
|
45
|
+
"required": "Required",
|
|
46
|
+
"empty": "Empty",
|
|
47
|
+
"noData": "No data available",
|
|
48
|
+
"noResults": "No results found",
|
|
49
|
+
"tryAgain": "Try again",
|
|
50
|
+
"learnMore": "Learn more",
|
|
51
|
+
"getStarted": "Get started",
|
|
52
|
+
"viewAll": "View all",
|
|
53
|
+
"showMore": "Show more",
|
|
54
|
+
"showLess": "Show less",
|
|
55
|
+
"back": "Back",
|
|
56
|
+
"start": "Start"
|
|
57
|
+
}
|
|
@@ -1,16 +1,19 @@
|
|
|
1
1
|
/**
|
|
2
|
-
* en-US
|
|
2
|
+
* Translation loader for en-US with namespace support
|
|
3
3
|
*
|
|
4
|
-
*
|
|
5
|
-
*
|
|
6
|
-
*
|
|
7
|
-
*
|
|
4
|
+
* Each JSON file represents a namespace that can be accessed via:
|
|
5
|
+
* t('namespace:key') or t('namespace:nested.key')
|
|
6
|
+
*
|
|
7
|
+
* Example:
|
|
8
|
+
* t('common:cancel') -> "Cancel"
|
|
9
|
+
* t('auth:login.title') -> "Sign In"
|
|
8
10
|
*/
|
|
9
11
|
|
|
10
12
|
import alerts from './alerts.json';
|
|
11
13
|
import auth from './auth.json';
|
|
12
14
|
import branding from './branding.json';
|
|
13
15
|
import clipboard from './clipboard.json';
|
|
16
|
+
import common from './common.json';
|
|
14
17
|
import datetime from './datetime.json';
|
|
15
18
|
import device from './device.json';
|
|
16
19
|
import editor from './editor.json';
|
|
@@ -26,48 +29,26 @@ import settings from './settings.json';
|
|
|
26
29
|
import sharing from './sharing.json';
|
|
27
30
|
import templates from './templates.json';
|
|
28
31
|
|
|
29
|
-
/**
|
|
30
|
-
* Flatten nested objects with dot notation
|
|
31
|
-
*/
|
|
32
|
-
const flattenObject = (
|
|
33
|
-
obj: Record<string, any>,
|
|
34
|
-
prefix = '',
|
|
35
|
-
): Record<string, string> => {
|
|
36
|
-
const flattened: Record<string, string> = {};
|
|
37
|
-
|
|
38
|
-
Object.keys(obj).forEach((key) => {
|
|
39
|
-
const newKey = prefix ? `${prefix}.${key}` : key;
|
|
40
|
-
|
|
41
|
-
if (obj[key] && typeof obj[key] === 'object' && !Array.isArray(obj[key])) {
|
|
42
|
-
Object.assign(flattened, flattenObject(obj[key], newKey));
|
|
43
|
-
} else {
|
|
44
|
-
flattened[newKey] = obj[key];
|
|
45
|
-
}
|
|
46
|
-
});
|
|
47
|
-
|
|
48
|
-
return flattened;
|
|
49
|
-
};
|
|
50
|
-
|
|
51
|
-
// Create flattened translations object
|
|
52
32
|
const translations = {
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
|
|
59
|
-
|
|
60
|
-
|
|
61
|
-
|
|
62
|
-
|
|
63
|
-
|
|
64
|
-
|
|
65
|
-
|
|
66
|
-
|
|
67
|
-
|
|
68
|
-
|
|
69
|
-
|
|
70
|
-
|
|
33
|
+
alerts,
|
|
34
|
+
auth,
|
|
35
|
+
branding,
|
|
36
|
+
clipboard,
|
|
37
|
+
common,
|
|
38
|
+
datetime,
|
|
39
|
+
device,
|
|
40
|
+
editor,
|
|
41
|
+
errors,
|
|
42
|
+
general,
|
|
43
|
+
goals,
|
|
44
|
+
haptics,
|
|
45
|
+
home,
|
|
46
|
+
navigation,
|
|
47
|
+
onboarding,
|
|
48
|
+
projects,
|
|
49
|
+
settings,
|
|
50
|
+
sharing,
|
|
51
|
+
templates,
|
|
71
52
|
};
|
|
72
53
|
|
|
73
54
|
export default translations;
|
|
@@ -1,9 +1,9 @@
|
|
|
1
1
|
/**
|
|
2
|
-
*
|
|
3
|
-
*
|
|
2
|
+
* Storage Wrapper
|
|
3
|
+
* Uses @umituz/react-native-storage for persistence
|
|
4
4
|
*/
|
|
5
5
|
|
|
6
|
-
import
|
|
6
|
+
import { storageRepository } from '@umituz/react-native-storage';
|
|
7
7
|
|
|
8
8
|
export const STORAGE_KEYS = {
|
|
9
9
|
LANGUAGE: '@localization:language',
|
|
@@ -11,19 +11,14 @@ export const STORAGE_KEYS = {
|
|
|
11
11
|
|
|
12
12
|
export const StorageWrapper = {
|
|
13
13
|
async getString(key: string, defaultValue: string): Promise<string> {
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
return
|
|
17
|
-
} catch (error) {
|
|
18
|
-
return defaultValue;
|
|
14
|
+
const result = await storageRepository.getString(key, defaultValue);
|
|
15
|
+
if (result.success && result.data !== null) {
|
|
16
|
+
return result.data;
|
|
19
17
|
}
|
|
18
|
+
return defaultValue;
|
|
20
19
|
},
|
|
21
20
|
|
|
22
21
|
async setString(key: string, value: string): Promise<void> {
|
|
23
|
-
|
|
24
|
-
await AsyncStorage.setItem(key, value);
|
|
25
|
-
} catch (error) {
|
|
26
|
-
// Ignore storage errors
|
|
27
|
-
}
|
|
22
|
+
await storageRepository.setString(key, value);
|
|
28
23
|
},
|
|
29
24
|
};
|
|
@@ -7,58 +7,42 @@
|
|
|
7
7
|
* - i18n setup
|
|
8
8
|
*/
|
|
9
9
|
|
|
10
|
-
import
|
|
10
|
+
import { storageRepository } from '@umituz/react-native-storage';
|
|
11
11
|
import i18n from '../config/i18n';
|
|
12
12
|
import { DEFAULT_LANGUAGE, getLanguageByCode, getDeviceLocale } from '../config/languages';
|
|
13
13
|
|
|
14
|
-
// Storage key for language preference
|
|
15
14
|
const LANGUAGE_STORAGE_KEY = '@localization:language';
|
|
16
15
|
|
|
17
16
|
export class LanguageInitializer {
|
|
18
17
|
/**
|
|
19
18
|
* Initialize localization system
|
|
20
|
-
* Detects device locale and sets up i18n
|
|
21
19
|
*/
|
|
22
20
|
static async initialize(): Promise<{
|
|
23
21
|
languageCode: string;
|
|
24
22
|
isRTL: boolean;
|
|
25
23
|
}> {
|
|
26
24
|
try {
|
|
27
|
-
|
|
28
|
-
const savedLanguage =
|
|
29
|
-
|
|
30
|
-
// Determine language code
|
|
25
|
+
const savedResult = await storageRepository.getString(LANGUAGE_STORAGE_KEY, DEFAULT_LANGUAGE);
|
|
26
|
+
const savedLanguage = savedResult.success && savedResult.data ? savedResult.data : DEFAULT_LANGUAGE;
|
|
31
27
|
const languageCode = await this.determineLanguageCode(savedLanguage);
|
|
32
|
-
|
|
33
|
-
// Validate and get language object
|
|
34
28
|
const finalLanguage = await this.validateAndSetupLanguage(languageCode);
|
|
35
29
|
|
|
36
30
|
return finalLanguage;
|
|
37
31
|
} catch (error) {
|
|
38
|
-
// Fallback to default language
|
|
39
32
|
return await this.setupFallbackLanguage();
|
|
40
33
|
}
|
|
41
34
|
}
|
|
42
35
|
|
|
43
|
-
/**
|
|
44
|
-
* Determine which language code to use
|
|
45
|
-
*/
|
|
46
36
|
private static async determineLanguageCode(savedLanguage: string): Promise<string> {
|
|
47
37
|
if (savedLanguage && savedLanguage !== DEFAULT_LANGUAGE) {
|
|
48
|
-
// User has previously selected a language
|
|
49
38
|
return savedLanguage;
|
|
50
|
-
} else {
|
|
51
|
-
// First launch - detect device locale
|
|
52
|
-
const deviceLocale = getDeviceLocale();
|
|
53
|
-
// Save detected locale for future launches
|
|
54
|
-
await AsyncStorage.setItem(LANGUAGE_STORAGE_KEY, deviceLocale);
|
|
55
|
-
return deviceLocale;
|
|
56
39
|
}
|
|
40
|
+
|
|
41
|
+
const deviceLocale = getDeviceLocale();
|
|
42
|
+
await storageRepository.setString(LANGUAGE_STORAGE_KEY, deviceLocale);
|
|
43
|
+
return deviceLocale;
|
|
57
44
|
}
|
|
58
45
|
|
|
59
|
-
/**
|
|
60
|
-
* Validate language and set it up in i18n
|
|
61
|
-
*/
|
|
62
46
|
private static async validateAndSetupLanguage(languageCode: string): Promise<{
|
|
63
47
|
languageCode: string;
|
|
64
48
|
isRTL: boolean;
|
|
@@ -75,9 +59,6 @@ export class LanguageInitializer {
|
|
|
75
59
|
};
|
|
76
60
|
}
|
|
77
61
|
|
|
78
|
-
/**
|
|
79
|
-
* Set up fallback language when initialization fails
|
|
80
|
-
*/
|
|
81
62
|
private static async setupFallbackLanguage(): Promise<{
|
|
82
63
|
languageCode: string;
|
|
83
64
|
isRTL: boolean;
|
|
@@ -3,15 +3,13 @@
|
|
|
3
3
|
*
|
|
4
4
|
* Handles switching between languages
|
|
5
5
|
* - Language validation
|
|
6
|
-
* - Dynamic resource loading
|
|
7
6
|
* - Persistence
|
|
8
7
|
*/
|
|
9
8
|
|
|
10
|
-
import
|
|
9
|
+
import { storageRepository } from '@umituz/react-native-storage';
|
|
11
10
|
import i18n from '../config/i18n';
|
|
12
11
|
import { getLanguageByCode } from '../config/languages';
|
|
13
12
|
|
|
14
|
-
// Storage key for language preference
|
|
15
13
|
const LANGUAGE_STORAGE_KEY = '@localization:language';
|
|
16
14
|
|
|
17
15
|
export class LanguageSwitcher {
|
|
@@ -24,60 +22,16 @@ export class LanguageSwitcher {
|
|
|
24
22
|
}> {
|
|
25
23
|
const language = getLanguageByCode(languageCode);
|
|
26
24
|
|
|
27
|
-
// Validate language exists
|
|
28
25
|
if (!language) {
|
|
29
26
|
throw new Error(`Unsupported language: ${languageCode}`);
|
|
30
27
|
}
|
|
31
28
|
|
|
32
|
-
// Load language resources if needed
|
|
33
|
-
await this.loadLanguageResources(languageCode);
|
|
34
|
-
|
|
35
|
-
// Update i18n
|
|
36
29
|
await i18n.changeLanguage(languageCode);
|
|
37
|
-
|
|
38
|
-
// Persist language preference
|
|
39
|
-
await AsyncStorage.setItem(LANGUAGE_STORAGE_KEY, languageCode);
|
|
30
|
+
await storageRepository.setString(LANGUAGE_STORAGE_KEY, languageCode);
|
|
40
31
|
|
|
41
32
|
return {
|
|
42
33
|
languageCode,
|
|
43
34
|
isRTL: language.rtl || false,
|
|
44
35
|
};
|
|
45
36
|
}
|
|
46
|
-
|
|
47
|
-
/**
|
|
48
|
-
* Load language resources dynamically
|
|
49
|
-
*/
|
|
50
|
-
private static async loadLanguageResources(languageCode: string): Promise<void> {
|
|
51
|
-
if (i18n.hasResourceBundle(languageCode, 'translation')) {
|
|
52
|
-
return; // Already loaded
|
|
53
|
-
}
|
|
54
|
-
|
|
55
|
-
try {
|
|
56
|
-
// Try to load project translations from common paths
|
|
57
|
-
let translations: any = null;
|
|
58
|
-
|
|
59
|
-
const loadPaths = [
|
|
60
|
-
`../../../../../../src/domains/localization/infrastructure/locales/${languageCode}`,
|
|
61
|
-
`../../../../../../domains/localization/infrastructure/locales/${languageCode}`,
|
|
62
|
-
`../../../../../../src/locales/${languageCode}`,
|
|
63
|
-
];
|
|
64
|
-
|
|
65
|
-
for (const path of loadPaths) {
|
|
66
|
-
try {
|
|
67
|
-
// eslint-disable-next-line @typescript-eslint/no-require-imports
|
|
68
|
-
translations = require(path);
|
|
69
|
-
break;
|
|
70
|
-
} catch {
|
|
71
|
-
// Try next path
|
|
72
|
-
}
|
|
73
|
-
}
|
|
74
|
-
|
|
75
|
-
if (translations) {
|
|
76
|
-
const translationData = translations.default || translations;
|
|
77
|
-
i18n.addResourceBundle(languageCode, 'translation', translationData, true, true);
|
|
78
|
-
}
|
|
79
|
-
} catch (loadError) {
|
|
80
|
-
// If loading fails, continue with changeLanguage (will fallback to en-US)
|
|
81
|
-
}
|
|
82
|
-
}
|
|
83
37
|
}
|