@umituz/react-native-settings 4.23.35 → 4.23.36
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 +12 -5
- package/src/domains/appearance/presentation/screens/AppearanceScreen.tsx +1 -1
- package/src/domains/disclaimer/presentation/components/DisclaimerSetting.test.tsx +1 -1
- package/src/domains/disclaimer/presentation/components/DisclaimerSetting.tsx +1 -1
- package/src/domains/disclaimer/presentation/screens/DisclaimerScreen.tsx +1 -1
- package/src/domains/localization/domain/repositories/ILocalizationRepository.ts +18 -0
- package/src/domains/localization/index.ts +33 -0
- package/src/domains/localization/infrastructure/components/LanguageSwitcher.styles.ts +40 -0
- package/src/domains/localization/infrastructure/components/LanguageSwitcher.tsx +88 -0
- package/src/domains/localization/infrastructure/components/__tests__/LanguageSwitcher.test.tsx +91 -0
- package/src/domains/localization/infrastructure/components/useLanguageNavigation.ts +20 -0
- package/src/domains/localization/infrastructure/components/useLanguageSwitcher.ts +34 -0
- package/src/domains/localization/infrastructure/config/DeviceLocale.ts +47 -0
- package/src/domains/localization/infrastructure/config/I18nInitializer.ts +73 -0
- package/src/domains/localization/infrastructure/config/LanguageQuery.ts +35 -0
- package/src/domains/localization/infrastructure/config/LocaleMapping.ts +78 -0
- package/src/domains/localization/infrastructure/config/NamespaceResolver.ts +54 -0
- package/src/domains/localization/infrastructure/config/ResourceBuilder.ts +72 -0
- package/src/domains/localization/infrastructure/config/TranslationLoader.ts +46 -0
- package/src/domains/localization/infrastructure/config/__tests__/languagesData.test.ts +69 -0
- package/src/domains/localization/infrastructure/config/constants/defaultLanguages.ts +43 -0
- package/src/domains/localization/infrastructure/config/i18n.ts +9 -0
- package/src/domains/localization/infrastructure/config/languages.ts +28 -0
- package/src/domains/localization/infrastructure/config/languagesData.ts +26 -0
- package/src/domains/localization/infrastructure/hooks/TranslationHook.ts +39 -0
- package/src/domains/localization/infrastructure/hooks/__tests__/useTranslation.test.ts +52 -0
- package/src/domains/localization/infrastructure/hooks/useLanguageSelection.ts +44 -0
- package/src/domains/localization/infrastructure/hooks/useLocalization.ts +41 -0
- package/src/domains/localization/infrastructure/hooks/useTranslation.ts +94 -0
- package/src/domains/localization/infrastructure/repository/LanguageRepository.ts +53 -0
- package/src/domains/localization/infrastructure/storage/AsyncStorageWrapper.ts +24 -0
- package/src/domains/localization/infrastructure/storage/LanguageInitializer.ts +81 -0
- package/src/domains/localization/infrastructure/storage/LanguageSwitcher.ts +52 -0
- package/src/domains/localization/infrastructure/storage/LocalizationStore.ts +142 -0
- package/src/domains/localization/infrastructure/storage/types/Language.ts +13 -0
- package/src/domains/localization/infrastructure/storage/types/LocalizationState.ts +27 -0
- package/src/domains/localization/presentation/components/LanguageItem.styles.ts +40 -0
- package/src/domains/localization/presentation/components/LanguageItem.tsx +106 -0
- package/src/domains/localization/presentation/components/LanguageSection.tsx +83 -0
- package/src/domains/localization/presentation/components/__tests__/LanguageItem.test.tsx +106 -0
- package/src/domains/localization/presentation/screens/LanguageSelectionScreen.styles.ts +16 -0
- package/src/domains/localization/presentation/screens/LanguageSelectionScreen.tsx +132 -0
- package/src/domains/localization/presentation/screens/LanguageSelectionScreen.types.ts +27 -0
- package/src/domains/localization/presentation/screens/__tests__/LanguageSelectionScreen.test.tsx +165 -0
- package/src/domains/localization/scripts/prepublish.js +36 -0
- package/src/domains/localization/scripts/setup-languages.js +60 -0
- package/src/domains/localization/scripts/sync-translations.js +124 -0
- package/src/domains/localization/scripts/translate-missing.js +92 -0
- package/src/domains/localization/scripts/utils/file-parser.js +78 -0
- package/src/domains/localization/scripts/utils/key-detector.js +45 -0
- package/src/domains/localization/scripts/utils/key-extractor.js +105 -0
- package/src/domains/localization/scripts/utils/object-helper.js +29 -0
- package/src/domains/localization/scripts/utils/sync-helper.js +49 -0
- package/src/domains/localization/scripts/utils/translation-config.js +116 -0
- package/src/domains/localization/scripts/utils/translator.js +83 -0
- package/src/domains/notifications/presentation/components/NotificationsSection.tsx +1 -1
- package/src/domains/notifications/presentation/screens/NotificationSettingsScreen.tsx +1 -1
- package/src/index.ts +2 -0
- package/src/presentation/components/SettingsErrorBoundary.tsx +1 -1
- package/src/presentation/navigation/SettingsStackNavigator.tsx +1 -1
- package/src/presentation/screens/components/SettingsContent.tsx +1 -1
- package/src/presentation/screens/components/SettingsHeader.tsx +1 -1
- package/src/presentation/screens/components/sections/FeatureSettingsSection.tsx +1 -1
- package/src/presentation/screens/components/sections/IdentitySettingsSection.tsx +1 -1
- package/src/presentation/screens/components/sections/ProfileSectionLoader.tsx +1 -1
- package/src/presentation/screens/components/sections/SupportSettingsSection.tsx +1 -1
|
@@ -0,0 +1,53 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Language Repository
|
|
3
|
+
* Manages language data with repository pattern
|
|
4
|
+
* Provides dynamic language management for multiple applications
|
|
5
|
+
*/
|
|
6
|
+
|
|
7
|
+
import type { Language } from '../storage/types/Language';
|
|
8
|
+
import { DEFAULT_LANGUAGES } from '../config/constants/defaultLanguages';
|
|
9
|
+
|
|
10
|
+
class LanguageRepository {
|
|
11
|
+
private languages: Language[] = [...DEFAULT_LANGUAGES];
|
|
12
|
+
|
|
13
|
+
registerLanguages(languages: Language[]): void {
|
|
14
|
+
this.languages = [...this.languages, ...languages];
|
|
15
|
+
}
|
|
16
|
+
|
|
17
|
+
getLanguages(): Language[] {
|
|
18
|
+
return [...this.languages];
|
|
19
|
+
}
|
|
20
|
+
|
|
21
|
+
clearLanguages(): void {
|
|
22
|
+
this.languages = [...DEFAULT_LANGUAGES];
|
|
23
|
+
}
|
|
24
|
+
|
|
25
|
+
getLanguageByCode(code: string): Language | undefined {
|
|
26
|
+
return this.languages.find(lang => lang.code === code);
|
|
27
|
+
}
|
|
28
|
+
|
|
29
|
+
searchLanguages(query: string): Language[] {
|
|
30
|
+
const lowerQuery = query.toLowerCase();
|
|
31
|
+
return this.languages.filter(
|
|
32
|
+
lang =>
|
|
33
|
+
lang.name.toLowerCase().includes(lowerQuery) ||
|
|
34
|
+
lang.nativeName.toLowerCase().includes(lowerQuery)
|
|
35
|
+
);
|
|
36
|
+
}
|
|
37
|
+
|
|
38
|
+
isLanguageSupported(code: string): boolean {
|
|
39
|
+
return this.languages.some(lang => lang.code === code);
|
|
40
|
+
}
|
|
41
|
+
|
|
42
|
+
getDefaultLanguage(): Language {
|
|
43
|
+
const en = this.languages.find(l => l.code === 'en-US');
|
|
44
|
+
if (en) return en;
|
|
45
|
+
const first = this.languages[0];
|
|
46
|
+
if (first) return first;
|
|
47
|
+
const systemDefault = DEFAULT_LANGUAGES[0];
|
|
48
|
+
if (systemDefault) return systemDefault;
|
|
49
|
+
throw new Error('No languages registered in repository or defaults');
|
|
50
|
+
}
|
|
51
|
+
}
|
|
52
|
+
|
|
53
|
+
export const languageRepository = new LanguageRepository();
|
|
@@ -0,0 +1,24 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Storage Wrapper
|
|
3
|
+
* Uses @umituz/react-native-design-system for persistence
|
|
4
|
+
*/
|
|
5
|
+
|
|
6
|
+
import { storageRepository } from '@umituz/react-native-design-system';
|
|
7
|
+
|
|
8
|
+
export const STORAGE_KEYS = {
|
|
9
|
+
LANGUAGE: '@localization:language',
|
|
10
|
+
} as const;
|
|
11
|
+
|
|
12
|
+
export const StorageWrapper = {
|
|
13
|
+
async getString(key: string, defaultValue: string): Promise<string> {
|
|
14
|
+
const result = await storageRepository.getString(key, defaultValue);
|
|
15
|
+
if (result.success && result.data !== null) {
|
|
16
|
+
return result.data;
|
|
17
|
+
}
|
|
18
|
+
return defaultValue;
|
|
19
|
+
},
|
|
20
|
+
|
|
21
|
+
async setString(key: string, value: string): Promise<void> {
|
|
22
|
+
await storageRepository.setString(key, value);
|
|
23
|
+
},
|
|
24
|
+
};
|
|
@@ -0,0 +1,81 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Language Initializer
|
|
3
|
+
*
|
|
4
|
+
* Handles the initialization of localization system
|
|
5
|
+
* - Device locale detection
|
|
6
|
+
* - Language validation and fallback
|
|
7
|
+
* - i18n setup
|
|
8
|
+
*/
|
|
9
|
+
|
|
10
|
+
declare const __DEV__: boolean;
|
|
11
|
+
|
|
12
|
+
import { storageRepository } from '@umituz/react-native-design-system';
|
|
13
|
+
import i18n from '../config/i18n';
|
|
14
|
+
import { languageRepository } from '../repository/LanguageRepository';
|
|
15
|
+
import { getDeviceLocale } from '../config/languages';
|
|
16
|
+
|
|
17
|
+
const LANGUAGE_STORAGE_KEY = '@localization:language';
|
|
18
|
+
const DEFAULT_LANGUAGE = 'en-US';
|
|
19
|
+
|
|
20
|
+
export class LanguageInitializer {
|
|
21
|
+
/**
|
|
22
|
+
* Initialize localization system
|
|
23
|
+
*/
|
|
24
|
+
static async initialize(): Promise<{
|
|
25
|
+
languageCode: string;
|
|
26
|
+
isRTL: boolean;
|
|
27
|
+
}> {
|
|
28
|
+
try {
|
|
29
|
+
const savedResult = await storageRepository.getString(LANGUAGE_STORAGE_KEY, DEFAULT_LANGUAGE);
|
|
30
|
+
const savedLanguage = savedResult.success && savedResult.data ? savedResult.data : DEFAULT_LANGUAGE;
|
|
31
|
+
const languageCode = await this.determineLanguageCode(savedLanguage);
|
|
32
|
+
const finalLanguage = await this.validateAndSetupLanguage(languageCode);
|
|
33
|
+
|
|
34
|
+
return finalLanguage;
|
|
35
|
+
} catch {
|
|
36
|
+
if (typeof __DEV__ !== "undefined" && __DEV__) {
|
|
37
|
+
console.warn("[LanguageInitializer] Failed to restore language, falling back to device locale");
|
|
38
|
+
}
|
|
39
|
+
return await this.setupFallbackLanguage();
|
|
40
|
+
}
|
|
41
|
+
}
|
|
42
|
+
|
|
43
|
+
private static async determineLanguageCode(savedLanguage: string): Promise<string> {
|
|
44
|
+
if (savedLanguage && savedLanguage !== DEFAULT_LANGUAGE) {
|
|
45
|
+
return savedLanguage;
|
|
46
|
+
}
|
|
47
|
+
|
|
48
|
+
const deviceLocale = getDeviceLocale();
|
|
49
|
+
await storageRepository.setString(LANGUAGE_STORAGE_KEY, deviceLocale);
|
|
50
|
+
return deviceLocale;
|
|
51
|
+
}
|
|
52
|
+
|
|
53
|
+
private static async validateAndSetupLanguage(languageCode: string): Promise<{
|
|
54
|
+
languageCode: string;
|
|
55
|
+
isRTL: boolean;
|
|
56
|
+
}> {
|
|
57
|
+
const language = languageRepository.getLanguageByCode(languageCode);
|
|
58
|
+
const finalLanguageCode = language ? languageCode : 'en-US';
|
|
59
|
+
const finalLanguageObj = languageRepository.getLanguageByCode(finalLanguageCode);
|
|
60
|
+
|
|
61
|
+
await i18n.changeLanguage(finalLanguageCode);
|
|
62
|
+
|
|
63
|
+
return {
|
|
64
|
+
languageCode: finalLanguageCode,
|
|
65
|
+
isRTL: finalLanguageObj?.isRTL || false,
|
|
66
|
+
};
|
|
67
|
+
}
|
|
68
|
+
|
|
69
|
+
private static async setupFallbackLanguage(): Promise<{
|
|
70
|
+
languageCode: string;
|
|
71
|
+
isRTL: boolean;
|
|
72
|
+
}> {
|
|
73
|
+
// No try-catch needed - let errors propagate naturally
|
|
74
|
+
// This preserves the full stack trace
|
|
75
|
+
await i18n.changeLanguage(DEFAULT_LANGUAGE);
|
|
76
|
+
return {
|
|
77
|
+
languageCode: DEFAULT_LANGUAGE,
|
|
78
|
+
isRTL: false,
|
|
79
|
+
};
|
|
80
|
+
}
|
|
81
|
+
}
|
|
@@ -0,0 +1,52 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Language Switcher
|
|
3
|
+
*
|
|
4
|
+
* Handles switching between languages
|
|
5
|
+
* - Language validation
|
|
6
|
+
* - Persistence
|
|
7
|
+
*/
|
|
8
|
+
|
|
9
|
+
declare const __DEV__: boolean;
|
|
10
|
+
|
|
11
|
+
import { storageRepository } from '@umituz/react-native-design-system';
|
|
12
|
+
import i18n from '../config/i18n';
|
|
13
|
+
import { languageRepository } from '../repository/LanguageRepository';
|
|
14
|
+
|
|
15
|
+
const LANGUAGE_STORAGE_KEY = '@localization:language';
|
|
16
|
+
|
|
17
|
+
export class LanguageSwitcher {
|
|
18
|
+
/**
|
|
19
|
+
* Switch to a new language
|
|
20
|
+
*/
|
|
21
|
+
static async switchLanguage(languageCode: string): Promise<{
|
|
22
|
+
languageCode: string;
|
|
23
|
+
isRTL: boolean;
|
|
24
|
+
}> {
|
|
25
|
+
if (typeof __DEV__ !== "undefined" && __DEV__) {
|
|
26
|
+
console.log('[LanguageSwitcher] switchLanguage called:', languageCode);
|
|
27
|
+
}
|
|
28
|
+
|
|
29
|
+
const language = languageRepository.getLanguageByCode(languageCode);
|
|
30
|
+
|
|
31
|
+
if (typeof __DEV__ !== "undefined" && __DEV__) {
|
|
32
|
+
console.log('[LanguageSwitcher] Language object:', language);
|
|
33
|
+
}
|
|
34
|
+
|
|
35
|
+
await i18n.changeLanguage(languageCode);
|
|
36
|
+
|
|
37
|
+
if (typeof __DEV__ !== "undefined" && __DEV__) {
|
|
38
|
+
console.log('[LanguageSwitcher] i18n language changed to:', i18n.language);
|
|
39
|
+
}
|
|
40
|
+
|
|
41
|
+
await storageRepository.setString(LANGUAGE_STORAGE_KEY, languageCode);
|
|
42
|
+
|
|
43
|
+
if (typeof __DEV__ !== "undefined" && __DEV__) {
|
|
44
|
+
console.log('[LanguageSwitcher] Saved to storage');
|
|
45
|
+
}
|
|
46
|
+
|
|
47
|
+
return {
|
|
48
|
+
languageCode,
|
|
49
|
+
isRTL: language?.isRTL || false,
|
|
50
|
+
};
|
|
51
|
+
}
|
|
52
|
+
}
|
|
@@ -0,0 +1,142 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Localization Store Factory
|
|
3
|
+
* Creates and manages localization state with proper separation of concerns
|
|
4
|
+
*/
|
|
5
|
+
|
|
6
|
+
import { create } from 'zustand';
|
|
7
|
+
import type { LocalizationState, LocalizationActions, LocalizationGetters } from './types/LocalizationState';
|
|
8
|
+
import { LanguageInitializer } from './LanguageInitializer';
|
|
9
|
+
import { LanguageSwitcher } from './LanguageSwitcher';
|
|
10
|
+
import { languageRepository } from '../repository/LanguageRepository';
|
|
11
|
+
|
|
12
|
+
declare const __DEV__: boolean;
|
|
13
|
+
|
|
14
|
+
type LocalizationStoreType = LocalizationState & LocalizationActions & LocalizationGetters;
|
|
15
|
+
|
|
16
|
+
// Mutex to prevent race condition in initialize
|
|
17
|
+
let initializeInProgress = false;
|
|
18
|
+
// Debounce timer for language switching
|
|
19
|
+
let languageSwitchTimer: ReturnType<typeof setTimeout> | null = null;
|
|
20
|
+
const LANGUAGE_SWITCH_DEBOUNCE_MS = 300;
|
|
21
|
+
// Track pending promise resolvers to ensure all get resolved
|
|
22
|
+
let pendingResolvers: Array<() => void> = [];
|
|
23
|
+
|
|
24
|
+
export const useLocalizationStore = create<LocalizationStoreType>((set, get) => ({
|
|
25
|
+
// State
|
|
26
|
+
currentLanguage: 'en-US',
|
|
27
|
+
isRTL: false,
|
|
28
|
+
isInitialized: false,
|
|
29
|
+
supportedLanguages: languageRepository.getLanguages(),
|
|
30
|
+
|
|
31
|
+
// Actions
|
|
32
|
+
initialize: async () => {
|
|
33
|
+
const { isInitialized: alreadyInitialized } = get();
|
|
34
|
+
|
|
35
|
+
// Atomic check: both state flag AND in-progress mutex
|
|
36
|
+
if (alreadyInitialized || initializeInProgress) {
|
|
37
|
+
return;
|
|
38
|
+
}
|
|
39
|
+
|
|
40
|
+
// Set mutex immediately (synchronous)
|
|
41
|
+
initializeInProgress = true;
|
|
42
|
+
|
|
43
|
+
try {
|
|
44
|
+
const result = await LanguageInitializer.initialize();
|
|
45
|
+
|
|
46
|
+
set({
|
|
47
|
+
currentLanguage: result.languageCode,
|
|
48
|
+
isRTL: result.isRTL,
|
|
49
|
+
isInitialized: true,
|
|
50
|
+
});
|
|
51
|
+
} catch {
|
|
52
|
+
set({
|
|
53
|
+
currentLanguage: 'en-US',
|
|
54
|
+
isRTL: false,
|
|
55
|
+
isInitialized: true,
|
|
56
|
+
});
|
|
57
|
+
} finally {
|
|
58
|
+
// Reset mutex after completion (success or failure)
|
|
59
|
+
initializeInProgress = false;
|
|
60
|
+
}
|
|
61
|
+
},
|
|
62
|
+
|
|
63
|
+
setLanguage: async (languageCode: string) => {
|
|
64
|
+
// Debounce rapid language switches
|
|
65
|
+
if (languageSwitchTimer) {
|
|
66
|
+
clearTimeout(languageSwitchTimer);
|
|
67
|
+
}
|
|
68
|
+
|
|
69
|
+
return new Promise<void>((resolve) => {
|
|
70
|
+
// Add this resolver to pending list
|
|
71
|
+
pendingResolvers.push(resolve);
|
|
72
|
+
|
|
73
|
+
languageSwitchTimer = setTimeout(async () => {
|
|
74
|
+
if (typeof __DEV__ !== "undefined" && __DEV__) {
|
|
75
|
+
console.log('[LocalizationStore] setLanguage called:', languageCode);
|
|
76
|
+
}
|
|
77
|
+
|
|
78
|
+
try {
|
|
79
|
+
const result = await LanguageSwitcher.switchLanguage(languageCode);
|
|
80
|
+
|
|
81
|
+
if (typeof __DEV__ !== "undefined" && __DEV__) {
|
|
82
|
+
console.log('[LocalizationStore] LanguageSwitcher result:', result);
|
|
83
|
+
}
|
|
84
|
+
|
|
85
|
+
set({
|
|
86
|
+
currentLanguage: result.languageCode,
|
|
87
|
+
isRTL: result.isRTL,
|
|
88
|
+
});
|
|
89
|
+
|
|
90
|
+
if (typeof __DEV__ !== "undefined" && __DEV__) {
|
|
91
|
+
console.log('[LocalizationStore] Store updated with new language:', result.languageCode);
|
|
92
|
+
}
|
|
93
|
+
} catch (error) {
|
|
94
|
+
if (typeof __DEV__ !== "undefined" && __DEV__) {
|
|
95
|
+
console.error('[LocalizationStore] Language switch failed:', error);
|
|
96
|
+
}
|
|
97
|
+
}
|
|
98
|
+
|
|
99
|
+
languageSwitchTimer = null;
|
|
100
|
+
|
|
101
|
+
// Resolve ALL pending promises (not just the latest)
|
|
102
|
+
const resolvers = [...pendingResolvers];
|
|
103
|
+
pendingResolvers = [];
|
|
104
|
+
resolvers.forEach(r => r());
|
|
105
|
+
}, LANGUAGE_SWITCH_DEBOUNCE_MS);
|
|
106
|
+
});
|
|
107
|
+
},
|
|
108
|
+
|
|
109
|
+
reset: () => {
|
|
110
|
+
// Clear any pending language switch
|
|
111
|
+
if (languageSwitchTimer) {
|
|
112
|
+
clearTimeout(languageSwitchTimer);
|
|
113
|
+
languageSwitchTimer = null;
|
|
114
|
+
}
|
|
115
|
+
// Resolve any pending promises to prevent hanging
|
|
116
|
+
const resolvers = [...pendingResolvers];
|
|
117
|
+
pendingResolvers = [];
|
|
118
|
+
resolvers.forEach(r => r());
|
|
119
|
+
// Reset mutex
|
|
120
|
+
initializeInProgress = false;
|
|
121
|
+
|
|
122
|
+
set({
|
|
123
|
+
currentLanguage: 'en-US',
|
|
124
|
+
isRTL: false,
|
|
125
|
+
isInitialized: false,
|
|
126
|
+
});
|
|
127
|
+
},
|
|
128
|
+
|
|
129
|
+
// Getters
|
|
130
|
+
getCurrentLanguage: () => {
|
|
131
|
+
const { currentLanguage } = get();
|
|
132
|
+
return languageRepository.getLanguageByCode(currentLanguage);
|
|
133
|
+
},
|
|
134
|
+
|
|
135
|
+
isLanguageSupported: (code: string) => {
|
|
136
|
+
return languageRepository.isLanguageSupported(code);
|
|
137
|
+
},
|
|
138
|
+
|
|
139
|
+
getSupportedLanguages: () => {
|
|
140
|
+
return languageRepository.getLanguages();
|
|
141
|
+
},
|
|
142
|
+
}));
|
|
@@ -0,0 +1,13 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Language Type
|
|
3
|
+
* Generic language interface for localization packages
|
|
4
|
+
* This is a base configuration that can be extended by consuming applications
|
|
5
|
+
*/
|
|
6
|
+
|
|
7
|
+
export interface Language {
|
|
8
|
+
code: string;
|
|
9
|
+
name: string;
|
|
10
|
+
nativeName: string;
|
|
11
|
+
flag?: string;
|
|
12
|
+
isRTL?: boolean;
|
|
13
|
+
}
|
|
@@ -0,0 +1,27 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Localization State Interface
|
|
3
|
+
* Defines the shape of localization state management
|
|
4
|
+
*/
|
|
5
|
+
|
|
6
|
+
import type { Language } from './Language';
|
|
7
|
+
|
|
8
|
+
export type { Language };
|
|
9
|
+
|
|
10
|
+
export interface LocalizationState {
|
|
11
|
+
currentLanguage: string;
|
|
12
|
+
isRTL: boolean;
|
|
13
|
+
isInitialized: boolean;
|
|
14
|
+
supportedLanguages: Language[];
|
|
15
|
+
}
|
|
16
|
+
|
|
17
|
+
export interface LocalizationActions {
|
|
18
|
+
initialize: () => Promise<void>;
|
|
19
|
+
setLanguage: (languageCode: string) => Promise<void>;
|
|
20
|
+
reset: () => void;
|
|
21
|
+
}
|
|
22
|
+
|
|
23
|
+
export interface LocalizationGetters {
|
|
24
|
+
getCurrentLanguage: () => Language | undefined;
|
|
25
|
+
isLanguageSupported: (code: string) => boolean;
|
|
26
|
+
getSupportedLanguages: () => Language[];
|
|
27
|
+
}
|
|
@@ -0,0 +1,40 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Language Item Component Styles
|
|
3
|
+
*/
|
|
4
|
+
|
|
5
|
+
import { StyleSheet } from 'react-native';
|
|
6
|
+
|
|
7
|
+
export const styles = StyleSheet.create({
|
|
8
|
+
languageItem: {
|
|
9
|
+
flexDirection: 'row',
|
|
10
|
+
alignItems: 'center',
|
|
11
|
+
justifyContent: 'space-between',
|
|
12
|
+
borderWidth: 1,
|
|
13
|
+
},
|
|
14
|
+
selectedLanguageItem: {
|
|
15
|
+
borderWidth: 2,
|
|
16
|
+
},
|
|
17
|
+
languageContent: {
|
|
18
|
+
flexDirection: 'row',
|
|
19
|
+
alignItems: 'center',
|
|
20
|
+
flex: 1,
|
|
21
|
+
},
|
|
22
|
+
flag: {
|
|
23
|
+
fontSize: 24,
|
|
24
|
+
marginRight: 16,
|
|
25
|
+
},
|
|
26
|
+
languageText: {
|
|
27
|
+
flex: 1,
|
|
28
|
+
flexShrink: 1,
|
|
29
|
+
},
|
|
30
|
+
nativeName: {
|
|
31
|
+
// Styling moved to themedStyles in component for token support
|
|
32
|
+
},
|
|
33
|
+
languageName: {
|
|
34
|
+
// Styling moved to themedStyles in component for token support
|
|
35
|
+
},
|
|
36
|
+
checkIcon: {
|
|
37
|
+
// Replaced by AtomicIcon
|
|
38
|
+
},
|
|
39
|
+
});
|
|
40
|
+
|
|
@@ -0,0 +1,106 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Language Item Component
|
|
3
|
+
*
|
|
4
|
+
* Renders a single language item in the language selection list
|
|
5
|
+
* Theme-aware component that adapts to light/dark mode
|
|
6
|
+
*/
|
|
7
|
+
|
|
8
|
+
import React, { useMemo } from 'react';
|
|
9
|
+
import {
|
|
10
|
+
View,
|
|
11
|
+
TouchableOpacity,
|
|
12
|
+
type StyleProp,
|
|
13
|
+
type ViewStyle,
|
|
14
|
+
type TextStyle,
|
|
15
|
+
} from 'react-native';
|
|
16
|
+
// @ts-ignore - Optional peer dependency
|
|
17
|
+
import { useAppDesignTokens, AtomicText, AtomicIcon } from '@umituz/react-native-design-system';
|
|
18
|
+
import type { Language } from '../../infrastructure/storage/types/Language';
|
|
19
|
+
import { styles } from './LanguageItem.styles';
|
|
20
|
+
|
|
21
|
+
interface LanguageItemProps {
|
|
22
|
+
item: Language;
|
|
23
|
+
isSelected: boolean;
|
|
24
|
+
onSelect: (code: string) => void;
|
|
25
|
+
customStyles?: {
|
|
26
|
+
languageItem?: StyleProp<ViewStyle>;
|
|
27
|
+
languageContent?: StyleProp<ViewStyle>;
|
|
28
|
+
languageText?: StyleProp<ViewStyle>;
|
|
29
|
+
flag?: StyleProp<TextStyle>;
|
|
30
|
+
nativeName?: StyleProp<TextStyle>;
|
|
31
|
+
};
|
|
32
|
+
}
|
|
33
|
+
|
|
34
|
+
export const LanguageItem: React.FC<LanguageItemProps> = ({
|
|
35
|
+
item,
|
|
36
|
+
isSelected,
|
|
37
|
+
onSelect,
|
|
38
|
+
customStyles,
|
|
39
|
+
}) => {
|
|
40
|
+
const tokens = useAppDesignTokens();
|
|
41
|
+
|
|
42
|
+
const themedStyles = useMemo(() => ({
|
|
43
|
+
languageItem: {
|
|
44
|
+
padding: tokens.spacing.md,
|
|
45
|
+
borderRadius: tokens.borders.radius.md,
|
|
46
|
+
backgroundColor: tokens.colors.backgroundSecondary,
|
|
47
|
+
borderColor: tokens.colors.border,
|
|
48
|
+
marginBottom: tokens.spacing.sm,
|
|
49
|
+
} as ViewStyle,
|
|
50
|
+
selectedLanguageItem: {
|
|
51
|
+
borderColor: tokens.colors.primary,
|
|
52
|
+
backgroundColor: tokens.colors.primaryContainer,
|
|
53
|
+
borderWidth: 2,
|
|
54
|
+
} as ViewStyle,
|
|
55
|
+
nativeName: {
|
|
56
|
+
color: tokens.colors.textPrimary,
|
|
57
|
+
marginBottom: tokens.spacing.xs / 2,
|
|
58
|
+
} as TextStyle,
|
|
59
|
+
languageName: {
|
|
60
|
+
color: tokens.colors.textSecondary,
|
|
61
|
+
} as TextStyle,
|
|
62
|
+
}), [tokens]);
|
|
63
|
+
|
|
64
|
+
return (
|
|
65
|
+
<TouchableOpacity
|
|
66
|
+
testID="language-item-test"
|
|
67
|
+
style={[
|
|
68
|
+
styles.languageItem,
|
|
69
|
+
themedStyles.languageItem,
|
|
70
|
+
isSelected ? [styles.selectedLanguageItem, themedStyles.selectedLanguageItem] : undefined,
|
|
71
|
+
customStyles?.languageItem,
|
|
72
|
+
]}
|
|
73
|
+
onPress={() => {
|
|
74
|
+
if (__DEV__) {
|
|
75
|
+
console.log('[LanguageItem] TouchableOpacity pressed:', item.code, item.nativeName);
|
|
76
|
+
}
|
|
77
|
+
onSelect(item.code);
|
|
78
|
+
}}
|
|
79
|
+
activeOpacity={0.7}
|
|
80
|
+
>
|
|
81
|
+
<View style={[styles.languageContent, customStyles?.languageContent]}>
|
|
82
|
+
<AtomicText style={[styles.flag, customStyles?.flag]}>
|
|
83
|
+
{item.flag || '🌐'}
|
|
84
|
+
</AtomicText>
|
|
85
|
+
<View style={[styles.languageText, { gap: tokens.spacing.xs / 2 }, customStyles?.languageText]}>
|
|
86
|
+
<AtomicText
|
|
87
|
+
type="bodyMedium"
|
|
88
|
+
style={[themedStyles.nativeName, { fontWeight: '700' }, customStyles?.nativeName]}
|
|
89
|
+
>
|
|
90
|
+
{item.nativeName}
|
|
91
|
+
</AtomicText>
|
|
92
|
+
<AtomicText
|
|
93
|
+
type="labelMedium"
|
|
94
|
+
style={[themedStyles.languageName, customStyles?.nativeName]}
|
|
95
|
+
>
|
|
96
|
+
{item.name}
|
|
97
|
+
</AtomicText>
|
|
98
|
+
</View>
|
|
99
|
+
</View>
|
|
100
|
+
{isSelected && (
|
|
101
|
+
<AtomicIcon name="checkmark" size="sm" color="primary" />
|
|
102
|
+
)}
|
|
103
|
+
</TouchableOpacity>
|
|
104
|
+
);
|
|
105
|
+
};
|
|
106
|
+
|
|
@@ -0,0 +1,83 @@
|
|
|
1
|
+
import React from 'react';
|
|
2
|
+
import { View, StyleSheet, type ViewStyle } from 'react-native';
|
|
3
|
+
// @ts-ignore - Optional peer dependency
|
|
4
|
+
import {
|
|
5
|
+
useAppDesignTokens,
|
|
6
|
+
AtomicText,
|
|
7
|
+
ListItem,
|
|
8
|
+
useAppNavigation,
|
|
9
|
+
} from '@umituz/react-native-design-system';
|
|
10
|
+
import { useLocalization } from '../../infrastructure/hooks/useLocalization';
|
|
11
|
+
import { getLanguageByCode } from '../../infrastructure/config/languages';
|
|
12
|
+
|
|
13
|
+
export interface LanguageSectionConfig {
|
|
14
|
+
route?: string;
|
|
15
|
+
onPress?: () => void;
|
|
16
|
+
title?: string;
|
|
17
|
+
description?: string;
|
|
18
|
+
defaultLanguageDisplay?: string;
|
|
19
|
+
}
|
|
20
|
+
|
|
21
|
+
export interface LanguageSectionProps {
|
|
22
|
+
config?: LanguageSectionConfig;
|
|
23
|
+
containerStyle?: ViewStyle;
|
|
24
|
+
sectionTitle?: string;
|
|
25
|
+
}
|
|
26
|
+
|
|
27
|
+
export const LanguageSection: React.FC<LanguageSectionProps> = ({
|
|
28
|
+
config,
|
|
29
|
+
containerStyle,
|
|
30
|
+
sectionTitle,
|
|
31
|
+
}) => {
|
|
32
|
+
const tokens = useAppDesignTokens();
|
|
33
|
+
const { t, currentLanguage } = useLocalization();
|
|
34
|
+
const navigation = useAppNavigation();
|
|
35
|
+
|
|
36
|
+
const route = config?.route || 'LanguageSelection';
|
|
37
|
+
const title = config?.title || t('settings.languageSelection.title') || 'Language';
|
|
38
|
+
const displaySectionTitle = sectionTitle || title;
|
|
39
|
+
|
|
40
|
+
const currentLang = getLanguageByCode(currentLanguage);
|
|
41
|
+
const defaultLanguageDisplay = config?.defaultLanguageDisplay || 'English';
|
|
42
|
+
const languageDisplay = currentLang
|
|
43
|
+
? `${currentLang.flag} ${currentLang.nativeName}`
|
|
44
|
+
: defaultLanguageDisplay;
|
|
45
|
+
|
|
46
|
+
const handlePress = () => {
|
|
47
|
+
if (config?.onPress) {
|
|
48
|
+
config.onPress();
|
|
49
|
+
} else {
|
|
50
|
+
navigation.navigate(route as never);
|
|
51
|
+
}
|
|
52
|
+
};
|
|
53
|
+
|
|
54
|
+
return (
|
|
55
|
+
<View style={[styles.sectionContainer, { backgroundColor: tokens.colors.surface }, containerStyle]}>
|
|
56
|
+
<View style={styles.headerContainer}>
|
|
57
|
+
<AtomicText type="titleMedium" color="primary">
|
|
58
|
+
{displaySectionTitle}
|
|
59
|
+
</AtomicText>
|
|
60
|
+
</View>
|
|
61
|
+
<ListItem
|
|
62
|
+
title={title}
|
|
63
|
+
subtitle={languageDisplay}
|
|
64
|
+
leftIcon="globe"
|
|
65
|
+
rightIcon="chevron-forward"
|
|
66
|
+
onPress={handlePress}
|
|
67
|
+
/>
|
|
68
|
+
</View>
|
|
69
|
+
);
|
|
70
|
+
};
|
|
71
|
+
|
|
72
|
+
const styles = StyleSheet.create({
|
|
73
|
+
sectionContainer: {
|
|
74
|
+
marginBottom: 16,
|
|
75
|
+
borderRadius: 12,
|
|
76
|
+
overflow: 'hidden',
|
|
77
|
+
},
|
|
78
|
+
headerContainer: {
|
|
79
|
+
paddingHorizontal: 16,
|
|
80
|
+
paddingTop: 16,
|
|
81
|
+
paddingBottom: 8,
|
|
82
|
+
},
|
|
83
|
+
});
|