@umituz/react-native-localization 3.5.49 → 3.5.51
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 +5 -3
- package/src/infrastructure/components/LanguageSwitcher.styles.ts +40 -0
- package/src/infrastructure/components/LanguageSwitcher.tsx +38 -63
- package/src/infrastructure/components/__tests__/LanguageSwitcher.test.tsx +2 -2
- package/src/infrastructure/components/useLanguageNavigation.ts +2 -2
- package/src/infrastructure/components/useLanguageSwitcher.ts +3 -3
- package/src/infrastructure/config/DeviceLocale.ts +2 -2
- package/src/infrastructure/config/LanguageQuery.ts +9 -9
- package/src/infrastructure/config/__tests__/languagesData.test.ts +17 -17
- package/src/infrastructure/config/constants/defaultLanguages.ts +43 -0
- package/src/infrastructure/config/languages.ts +5 -5
- package/src/infrastructure/config/languagesData.ts +17 -136
- package/src/infrastructure/repository/LanguageRepository.ts +53 -0
- package/src/infrastructure/storage/LanguageInitializer.ts +3 -3
- package/src/infrastructure/storage/LanguageSwitcher.ts +2 -2
- package/src/infrastructure/storage/LocalizationStore.ts +47 -59
- package/src/infrastructure/storage/types/Language.ts +13 -0
- package/src/infrastructure/storage/types/LocalizationState.ts +3 -7
- package/src/presentation/components/LanguageItem.styles.ts +44 -0
- package/src/presentation/components/LanguageItem.tsx +2 -40
- package/src/presentation/components/SearchInput.styles.ts +34 -0
- package/src/presentation/components/SearchInput.tsx +1 -30
- package/src/presentation/screens/LanguageSelectionScreen.styles.ts +15 -0
- package/src/presentation/screens/LanguageSelectionScreen.tsx +3 -12
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@umituz/react-native-localization",
|
|
3
|
-
"version": "3.5.
|
|
3
|
+
"version": "3.5.51",
|
|
4
4
|
"description": "Generic localization system for React Native apps with i18n support",
|
|
5
5
|
"main": "./src/index.ts",
|
|
6
6
|
"types": "./src/index.ts",
|
|
@@ -31,7 +31,8 @@
|
|
|
31
31
|
"i18next": ">=23.0.0",
|
|
32
32
|
"react": ">=18.2.0",
|
|
33
33
|
"react-i18next": ">=15.0.0",
|
|
34
|
-
"react-native": ">=0.74.0"
|
|
34
|
+
"react-native": ">=0.74.0",
|
|
35
|
+
"zustand": ">=4.0.0"
|
|
35
36
|
},
|
|
36
37
|
"devDependencies": {
|
|
37
38
|
"@react-native-async-storage/async-storage": "~2.1.2",
|
|
@@ -42,7 +43,8 @@
|
|
|
42
43
|
"react": "19.1.0",
|
|
43
44
|
"react-i18next": "^15.2.0",
|
|
44
45
|
"react-native": "0.81.5",
|
|
45
|
-
"typescript": "~5.9.2"
|
|
46
|
+
"typescript": "~5.9.2",
|
|
47
|
+
"zustand": "^4.5.0"
|
|
46
48
|
},
|
|
47
49
|
"publishConfig": {
|
|
48
50
|
"access": "public"
|
|
@@ -0,0 +1,40 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Language Switcher Component Styles
|
|
3
|
+
*/
|
|
4
|
+
|
|
5
|
+
import { StyleSheet } from 'react-native';
|
|
6
|
+
|
|
7
|
+
const DEFAULT_CONFIG = {
|
|
8
|
+
defaultIconSize: 20,
|
|
9
|
+
};
|
|
10
|
+
|
|
11
|
+
export const styles = StyleSheet.create({
|
|
12
|
+
container: {
|
|
13
|
+
flexDirection: 'row',
|
|
14
|
+
alignItems: 'center',
|
|
15
|
+
gap: 8,
|
|
16
|
+
paddingHorizontal: 4,
|
|
17
|
+
paddingVertical: 4,
|
|
18
|
+
},
|
|
19
|
+
disabled: {
|
|
20
|
+
opacity: 0.5,
|
|
21
|
+
},
|
|
22
|
+
flag: {
|
|
23
|
+
fontSize: DEFAULT_CONFIG.defaultIconSize,
|
|
24
|
+
textAlign: 'center',
|
|
25
|
+
},
|
|
26
|
+
languageName: {
|
|
27
|
+
fontSize: 14,
|
|
28
|
+
fontWeight: '600',
|
|
29
|
+
textAlign: 'center',
|
|
30
|
+
},
|
|
31
|
+
icon: {
|
|
32
|
+
fontSize: DEFAULT_CONFIG.defaultIconSize,
|
|
33
|
+
textAlign: 'center',
|
|
34
|
+
},
|
|
35
|
+
});
|
|
36
|
+
|
|
37
|
+
export const DEFAULT_CONFIG_VALUES = {
|
|
38
|
+
hitSlop: { top: 10, bottom: 10, left: 10, right: 10 } as const,
|
|
39
|
+
activeOpacity: 0.7,
|
|
40
|
+
};
|
|
@@ -4,8 +4,9 @@
|
|
|
4
4
|
*/
|
|
5
5
|
|
|
6
6
|
import React, { useMemo } from 'react';
|
|
7
|
-
import { TouchableOpacity, Text
|
|
7
|
+
import { TouchableOpacity, Text } from 'react-native';
|
|
8
8
|
import { useLanguageSwitcher } from './useLanguageSwitcher';
|
|
9
|
+
import { styles, DEFAULT_CONFIG_VALUES } from './LanguageSwitcher.styles';
|
|
9
10
|
|
|
10
11
|
export interface LanguageSwitcherProps {
|
|
11
12
|
showName?: boolean;
|
|
@@ -20,10 +21,39 @@ export interface LanguageSwitcherProps {
|
|
|
20
21
|
accessibilityLabel?: string;
|
|
21
22
|
}
|
|
22
23
|
|
|
23
|
-
const
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
|
|
24
|
+
const renderContent = (
|
|
25
|
+
showFlag: boolean,
|
|
26
|
+
showName: boolean,
|
|
27
|
+
flag: string | undefined,
|
|
28
|
+
nativeName: string,
|
|
29
|
+
color: string | undefined,
|
|
30
|
+
iconStyle: any,
|
|
31
|
+
textStyle: any
|
|
32
|
+
) => {
|
|
33
|
+
if (showFlag && showName) {
|
|
34
|
+
return (
|
|
35
|
+
<>
|
|
36
|
+
<Text style={[styles.flag, iconStyle]}>{flag}</Text>
|
|
37
|
+
<Text style={[styles.languageName, { color }, textStyle]}>
|
|
38
|
+
{nativeName}
|
|
39
|
+
</Text>
|
|
40
|
+
</>
|
|
41
|
+
);
|
|
42
|
+
}
|
|
43
|
+
|
|
44
|
+
if (showFlag) {
|
|
45
|
+
return <Text style={[styles.flag, iconStyle]}>{flag}</Text>;
|
|
46
|
+
}
|
|
47
|
+
|
|
48
|
+
if (showName) {
|
|
49
|
+
return (
|
|
50
|
+
<Text style={[styles.languageName, { color }, textStyle]}>
|
|
51
|
+
{nativeName}
|
|
52
|
+
</Text>
|
|
53
|
+
);
|
|
54
|
+
}
|
|
55
|
+
|
|
56
|
+
return <Text style={[styles.icon, { color }, iconStyle]}>🌐</Text>;
|
|
27
57
|
};
|
|
28
58
|
|
|
29
59
|
export const LanguageSwitcher: React.FC<LanguageSwitcherProps> = ({
|
|
@@ -40,8 +70,6 @@ export const LanguageSwitcher: React.FC<LanguageSwitcherProps> = ({
|
|
|
40
70
|
}) => {
|
|
41
71
|
const { currentLang, handlePress } = useLanguageSwitcher({ onPress, disabled });
|
|
42
72
|
|
|
43
|
-
const iconColor = color;
|
|
44
|
-
|
|
45
73
|
const accessibilityProps = useMemo(() => ({
|
|
46
74
|
accessibilityRole: 'button' as const,
|
|
47
75
|
accessibilityLabel: accessibilityLabel || `Current language: ${currentLang.nativeName}`,
|
|
@@ -49,72 +77,19 @@ export const LanguageSwitcher: React.FC<LanguageSwitcherProps> = ({
|
|
|
49
77
|
accessible: true,
|
|
50
78
|
}), [accessibilityLabel, currentLang.nativeName, disabled]);
|
|
51
79
|
|
|
52
|
-
const renderContent = () => {
|
|
53
|
-
if (showFlag && showName) {
|
|
54
|
-
return (
|
|
55
|
-
<>
|
|
56
|
-
<Text style={[styles.flag, iconStyle]}>{currentLang.flag}</Text>
|
|
57
|
-
<Text style={[styles.languageName, { color: iconColor }, textStyle]}>
|
|
58
|
-
{currentLang.nativeName}
|
|
59
|
-
</Text>
|
|
60
|
-
</>
|
|
61
|
-
);
|
|
62
|
-
}
|
|
63
|
-
|
|
64
|
-
if (showFlag) {
|
|
65
|
-
return <Text style={[styles.flag, iconStyle]}>{currentLang.flag}</Text>;
|
|
66
|
-
}
|
|
67
|
-
|
|
68
|
-
if (showName) {
|
|
69
|
-
return (
|
|
70
|
-
<Text style={[styles.languageName, { color: iconColor }, textStyle]}>
|
|
71
|
-
{currentLang.nativeName}
|
|
72
|
-
</Text>
|
|
73
|
-
);
|
|
74
|
-
}
|
|
75
|
-
|
|
76
|
-
return <Text style={[styles.icon, { color: iconColor }, iconStyle]}>🌐</Text>;
|
|
77
|
-
};
|
|
78
|
-
|
|
79
80
|
return (
|
|
80
81
|
<TouchableOpacity
|
|
81
82
|
style={[styles.container, style, disabled && styles.disabled]}
|
|
82
83
|
onPress={handlePress}
|
|
83
|
-
activeOpacity={disabled ? 1 :
|
|
84
|
-
hitSlop={
|
|
84
|
+
activeOpacity={disabled ? 1 : DEFAULT_CONFIG_VALUES.activeOpacity}
|
|
85
|
+
hitSlop={DEFAULT_CONFIG_VALUES.hitSlop}
|
|
85
86
|
testID={testID}
|
|
86
87
|
disabled={disabled}
|
|
87
88
|
{...accessibilityProps}
|
|
88
89
|
>
|
|
89
|
-
{renderContent()}
|
|
90
|
+
{renderContent(showFlag, showName, currentLang.flag, currentLang.nativeName, color, iconStyle, textStyle)}
|
|
90
91
|
</TouchableOpacity>
|
|
91
92
|
);
|
|
92
93
|
};
|
|
93
94
|
|
|
94
|
-
const styles = StyleSheet.create({
|
|
95
|
-
container: {
|
|
96
|
-
flexDirection: 'row',
|
|
97
|
-
alignItems: 'center',
|
|
98
|
-
gap: 8,
|
|
99
|
-
paddingHorizontal: 4,
|
|
100
|
-
paddingVertical: 4,
|
|
101
|
-
},
|
|
102
|
-
disabled: {
|
|
103
|
-
opacity: 0.5,
|
|
104
|
-
},
|
|
105
|
-
flag: {
|
|
106
|
-
fontSize: DEFAULT_CONFIG.defaultIconSize,
|
|
107
|
-
textAlign: 'center',
|
|
108
|
-
},
|
|
109
|
-
languageName: {
|
|
110
|
-
fontSize: 14,
|
|
111
|
-
fontWeight: '600',
|
|
112
|
-
textAlign: 'center',
|
|
113
|
-
},
|
|
114
|
-
icon: {
|
|
115
|
-
fontSize: DEFAULT_CONFIG.defaultIconSize,
|
|
116
|
-
textAlign: 'center',
|
|
117
|
-
},
|
|
118
|
-
});
|
|
119
|
-
|
|
120
95
|
export default LanguageSwitcher;
|
|
@@ -24,8 +24,8 @@ jest.mock('../../hooks/useLocalization', () => ({
|
|
|
24
24
|
}),
|
|
25
25
|
}));
|
|
26
26
|
|
|
27
|
-
jest.mock('../../
|
|
28
|
-
|
|
27
|
+
jest.mock('../../repository/LanguageRepository', () => ({
|
|
28
|
+
languageRepository: {
|
|
29
29
|
getLanguageByCode: jest.fn(() => ({
|
|
30
30
|
code: 'en-US',
|
|
31
31
|
name: 'English',
|
|
@@ -1,13 +1,13 @@
|
|
|
1
1
|
// @ts-ignore - Optional peer dependency
|
|
2
2
|
import { useNavigation } from '@react-navigation/native';
|
|
3
3
|
import { useLocalization } from '../hooks/useLocalization';
|
|
4
|
-
import {
|
|
4
|
+
import { languageRepository } from '../repository/LanguageRepository';
|
|
5
5
|
|
|
6
6
|
|
|
7
7
|
export const useLanguageNavigation = (navigationScreen: string) => {
|
|
8
8
|
const navigation = useNavigation();
|
|
9
9
|
const { currentLanguage } = useLocalization();
|
|
10
|
-
const currentLang =
|
|
10
|
+
const currentLang = languageRepository.getLanguageByCode(currentLanguage) || languageRepository.getDefaultLanguage();
|
|
11
11
|
|
|
12
12
|
const navigateToLanguageSelection = () => {
|
|
13
13
|
if (navigation && navigationScreen) {
|
|
@@ -5,8 +5,8 @@
|
|
|
5
5
|
|
|
6
6
|
import { useMemo, useCallback } from 'react';
|
|
7
7
|
import { useLocalization } from '../hooks/useLocalization';
|
|
8
|
-
import {
|
|
9
|
-
import type { Language } from '../storage/types/
|
|
8
|
+
import { languageRepository } from '../repository/LanguageRepository';
|
|
9
|
+
import type { Language } from '../storage/types/Language';
|
|
10
10
|
|
|
11
11
|
export interface UseLanguageSwitcherProps {
|
|
12
12
|
onPress?: () => void;
|
|
@@ -17,7 +17,7 @@ export const useLanguageSwitcher = ({ onPress, disabled }: UseLanguageSwitcherPr
|
|
|
17
17
|
const { currentLanguage } = useLocalization();
|
|
18
18
|
|
|
19
19
|
const currentLang = useMemo((): Language => {
|
|
20
|
-
return
|
|
20
|
+
return languageRepository.getLanguageByCode(currentLanguage) || languageRepository.getDefaultLanguage();
|
|
21
21
|
}, [currentLanguage]);
|
|
22
22
|
|
|
23
23
|
const handlePress = useCallback(() => {
|
|
@@ -5,7 +5,7 @@
|
|
|
5
5
|
|
|
6
6
|
import * as Localization from 'expo-localization';
|
|
7
7
|
import { LOCALE_MAPPING } from './LocaleMapping';
|
|
8
|
-
import {
|
|
8
|
+
import { languageRepository } from '../repository/LanguageRepository';
|
|
9
9
|
|
|
10
10
|
export const DEFAULT_LANGUAGE = 'en-US';
|
|
11
11
|
|
|
@@ -36,7 +36,7 @@ export const getDeviceLocale = (): string => {
|
|
|
36
36
|
}
|
|
37
37
|
|
|
38
38
|
// Check if directly supported
|
|
39
|
-
if (
|
|
39
|
+
if (languageRepository.isLanguageSupported(deviceLocale)) {
|
|
40
40
|
return deviceLocale;
|
|
41
41
|
}
|
|
42
42
|
|
|
@@ -3,31 +3,31 @@
|
|
|
3
3
|
* Provides functions to query and search languages
|
|
4
4
|
*/
|
|
5
5
|
|
|
6
|
-
import {
|
|
7
|
-
import type { Language } from '../storage/types/
|
|
6
|
+
import { languageRepository } from '../repository/LanguageRepository';
|
|
7
|
+
import type { Language } from '../storage/types/Language';
|
|
8
8
|
|
|
9
|
-
export const getSupportedLanguages = () =>
|
|
9
|
+
export const getSupportedLanguages = () => languageRepository.getLanguages();
|
|
10
10
|
|
|
11
11
|
export const getLanguageByCode = (code: string): Language | undefined => {
|
|
12
|
-
return
|
|
12
|
+
return languageRepository.getLanguageByCode(code);
|
|
13
13
|
};
|
|
14
14
|
|
|
15
15
|
export const isLanguageSupported = (code: string): boolean => {
|
|
16
|
-
return
|
|
16
|
+
return languageRepository.isLanguageSupported(code);
|
|
17
17
|
};
|
|
18
18
|
|
|
19
19
|
export const getDefaultLanguage = (): Language => {
|
|
20
|
-
const langs =
|
|
20
|
+
const langs = languageRepository.getLanguages();
|
|
21
21
|
const firstLang = langs[0];
|
|
22
22
|
if (firstLang) return firstLang;
|
|
23
23
|
|
|
24
|
-
// Final fallback to system defaults if
|
|
25
|
-
return
|
|
24
|
+
// Final fallback to system defaults if repository is empty
|
|
25
|
+
return languageRepository.getDefaultLanguage();
|
|
26
26
|
};
|
|
27
27
|
|
|
28
28
|
export const searchLanguages = (query: string): Language[] => {
|
|
29
29
|
const lowerQuery = query.toLowerCase();
|
|
30
|
-
return
|
|
30
|
+
return languageRepository.getLanguages().filter(
|
|
31
31
|
(lang) =>
|
|
32
32
|
lang.name.toLowerCase().includes(lowerQuery) ||
|
|
33
33
|
lang.nativeName.toLowerCase().includes(lowerQuery)
|
|
@@ -1,48 +1,48 @@
|
|
|
1
1
|
/**
|
|
2
|
-
* Simple tests for language
|
|
2
|
+
* Simple tests for language repository
|
|
3
3
|
*/
|
|
4
4
|
|
|
5
|
-
import {
|
|
5
|
+
import { languageRepository } from '../../repository/LanguageRepository';
|
|
6
6
|
|
|
7
|
-
describe('
|
|
7
|
+
describe('LanguageRepository', () => {
|
|
8
8
|
beforeEach(() => {
|
|
9
|
-
|
|
9
|
+
languageRepository.clearLanguages();
|
|
10
10
|
});
|
|
11
11
|
|
|
12
12
|
it('should get default language', () => {
|
|
13
|
-
const defaultLang =
|
|
13
|
+
const defaultLang = languageRepository.getDefaultLanguage();
|
|
14
14
|
expect(defaultLang).toBeDefined();
|
|
15
15
|
expect(defaultLang.code).toBe('en-US');
|
|
16
16
|
});
|
|
17
17
|
|
|
18
18
|
it('should check if language is supported', () => {
|
|
19
|
-
const supported =
|
|
19
|
+
const supported = languageRepository.isLanguageSupported('en-US');
|
|
20
20
|
expect(supported).toBe(true);
|
|
21
21
|
});
|
|
22
22
|
|
|
23
23
|
it('should check if language is not supported', () => {
|
|
24
|
-
const supported =
|
|
24
|
+
const supported = languageRepository.isLanguageSupported('unknown');
|
|
25
25
|
expect(supported).toBe(false);
|
|
26
26
|
});
|
|
27
27
|
|
|
28
28
|
it('should get language by code', () => {
|
|
29
|
-
const language =
|
|
29
|
+
const language = languageRepository.getLanguageByCode('en-US');
|
|
30
30
|
expect(language).toBeDefined();
|
|
31
31
|
expect(language?.code).toBe('en-US');
|
|
32
32
|
});
|
|
33
33
|
|
|
34
34
|
it('should return undefined for unknown code', () => {
|
|
35
|
-
const language =
|
|
35
|
+
const language = languageRepository.getLanguageByCode('unknown');
|
|
36
36
|
expect(language).toBeUndefined();
|
|
37
37
|
});
|
|
38
38
|
|
|
39
39
|
it('should search languages', () => {
|
|
40
|
-
const results =
|
|
40
|
+
const results = languageRepository.searchLanguages('english');
|
|
41
41
|
expect(Array.isArray(results)).toBe(true);
|
|
42
42
|
});
|
|
43
43
|
|
|
44
44
|
it('should get supported languages', () => {
|
|
45
|
-
const languages =
|
|
45
|
+
const languages = languageRepository.getLanguages();
|
|
46
46
|
expect(Array.isArray(languages)).toBe(true);
|
|
47
47
|
expect(languages.length).toBeGreaterThan(0);
|
|
48
48
|
// Should now support many languages (29+)
|
|
@@ -50,19 +50,19 @@ describe('LanguageRegistry', () => {
|
|
|
50
50
|
});
|
|
51
51
|
|
|
52
52
|
it('should support newly added languages', () => {
|
|
53
|
-
expect(
|
|
54
|
-
expect(
|
|
55
|
-
expect(
|
|
56
|
-
expect(
|
|
53
|
+
expect(languageRepository.isLanguageSupported('cs-CZ')).toBe(true);
|
|
54
|
+
expect(languageRepository.isLanguageSupported('pt-BR')).toBe(true);
|
|
55
|
+
expect(languageRepository.isLanguageSupported('zh-TW')).toBe(true);
|
|
56
|
+
expect(languageRepository.isLanguageSupported('el-GR')).toBe(true);
|
|
57
57
|
});
|
|
58
58
|
|
|
59
59
|
it('should find language attributes correctly', () => {
|
|
60
|
-
const czech =
|
|
60
|
+
const czech = languageRepository.getLanguageByCode('cs-CZ');
|
|
61
61
|
expect(czech).toBeDefined();
|
|
62
62
|
expect(czech?.name).toBe('Czech');
|
|
63
63
|
expect(czech?.flag).toBe('🇨🇿');
|
|
64
64
|
|
|
65
|
-
const brazil =
|
|
65
|
+
const brazil = languageRepository.getLanguageByCode('pt-BR');
|
|
66
66
|
expect(brazil).toBeDefined();
|
|
67
67
|
expect(brazil?.name).toBe('Portuguese (Brazil)');
|
|
68
68
|
});
|
|
@@ -0,0 +1,43 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Default Languages Configuration
|
|
3
|
+
* Applications can override this by providing their own language list
|
|
4
|
+
*/
|
|
5
|
+
|
|
6
|
+
import type { Language } from '../../storage/types/Language';
|
|
7
|
+
|
|
8
|
+
export const DEFAULT_LANGUAGES: Language[] = [
|
|
9
|
+
{ code: 'ar-SA', name: 'Arabic', nativeName: 'العربية', flag: '🇸🇦', isRTL: true },
|
|
10
|
+
{ code: 'bg-BG', name: 'Bulgarian', nativeName: 'Български', flag: '🇧🇬', isRTL: false },
|
|
11
|
+
{ code: 'cs-CZ', name: 'Czech', nativeName: 'Čeština', flag: '🇨🇿', isRTL: false },
|
|
12
|
+
{ code: 'da-DK', name: 'Danish', nativeName: 'Dansk', flag: '🇩🇰', isRTL: false },
|
|
13
|
+
{ code: 'de-DE', name: 'German', nativeName: 'Deutsch', flag: '🇩🇪', isRTL: false },
|
|
14
|
+
{ code: 'el-GR', name: 'Greek', nativeName: 'Ελληνικά', flag: '🇬🇷', isRTL: false },
|
|
15
|
+
{ code: 'en-US', name: 'English', nativeName: 'English', flag: '🇺🇸', isRTL: false },
|
|
16
|
+
{ code: 'es-ES', name: 'Spanish', nativeName: 'Español', flag: '🇪🇸', isRTL: false },
|
|
17
|
+
{ code: 'fi-FI', name: 'Finnish', nativeName: 'Suomi', flag: '🇫🇮', isRTL: false },
|
|
18
|
+
{ code: 'fr-FR', name: 'French', nativeName: 'Français', flag: '🇫🇷', isRTL: false },
|
|
19
|
+
{ code: 'hi-IN', name: 'Hindi', nativeName: 'हिन्दी', flag: '🇮🇳', isRTL: false },
|
|
20
|
+
{ code: 'hr-HR', name: 'Croatian', nativeName: 'Hrvatski', flag: '🇭🇷', isRTL: false },
|
|
21
|
+
{ code: 'hu-HU', name: 'Hungarian', nativeName: 'Magyar', flag: '🇭🇺', isRTL: false },
|
|
22
|
+
{ code: 'id-ID', name: 'Indonesian', nativeName: 'Bahasa Indonesia', flag: '🇮', isRTL: false },
|
|
23
|
+
{ code: 'it-IT', name: 'Italian', nativeName: 'Italiano', flag: '🇮🇹', isRTL: false },
|
|
24
|
+
{ code: 'ja-JP', name: 'Japanese', nativeName: '日本語', flag: '🇯🇵', isRTL: false },
|
|
25
|
+
{ code: 'ko-KR', name: 'Korean', nativeName: '한국어', flag: '🇰🇷', isRTL: false },
|
|
26
|
+
{ code: 'ms-MY', name: 'Malay', nativeName: 'Bahasa Melayu', flag: '🇲🇾', isRTL: false },
|
|
27
|
+
{ code: 'nl-NL', name: 'Dutch', nativeName: 'Nederlands', flag: '🇳🇱', isRTL: false },
|
|
28
|
+
{ code: 'no-NO', name: 'Norwegian', nativeName: 'Norsk', flag: '🇳🇴', isRTL: false },
|
|
29
|
+
{ code: 'pl-PL', name: 'Polish', nativeName: 'Polski', flag: '🇵🇱', isRTL: false },
|
|
30
|
+
{ code: 'pt-BR', name: 'Portuguese (Brazil)', nativeName: 'Português (Brasil)', flag: '🇧🇷', isRTL: false },
|
|
31
|
+
{ code: 'pt-PT', name: 'Portuguese', nativeName: 'Português', flag: '🇵🇹', isRTL: false },
|
|
32
|
+
{ code: 'ro-RO', name: 'Romanian', nativeName: 'Română', flag: '🇷🇴', isRTL: false },
|
|
33
|
+
{ code: 'ru-RU', name: 'Russian', nativeName: 'Русский', flag: '🇷🇺', isRTL: false },
|
|
34
|
+
{ code: 'sk-SK', name: 'Slovak', nativeName: 'Slovenčina', flag: '🇸🇰', isRTL: false },
|
|
35
|
+
{ code: 'sv-SE', name: 'Swedish', nativeName: 'Svenska', flag: '🇸🇪', isRTL: false },
|
|
36
|
+
{ code: 'th-TH', name: 'Thai', nativeName: 'ไทย', flag: '🇹🇭', isRTL: false },
|
|
37
|
+
{ code: 'tl-PH', name: 'Filipino', nativeName: 'Filipino', flag: '🇵🇭', isRTL: false },
|
|
38
|
+
{ code: 'tr-TR', name: 'Turkish', nativeName: 'Türkçe', flag: '🇹🇷', isRTL: false },
|
|
39
|
+
{ code: 'uk-UA', name: 'Ukrainian', nativeName: 'Українська', flag: '🇺🇦', isRTL: false },
|
|
40
|
+
{ code: 'vi-VN', name: 'Vietnamese', nativeName: 'Tiếng Việt', flag: '🇻🇳', isRTL: false },
|
|
41
|
+
{ code: 'zh-CN', name: 'Chinese (Simplified)', nativeName: '简体中文', flag: '🇨🇳', isRTL: false },
|
|
42
|
+
{ code: 'zh-TW', name: 'Chinese (Traditional)', nativeName: '繁體中文', flag: '🇹🇼', isRTL: false },
|
|
43
|
+
];
|
|
@@ -3,8 +3,8 @@
|
|
|
3
3
|
* Central export point for all language-related functionality
|
|
4
4
|
*/
|
|
5
5
|
|
|
6
|
-
import {
|
|
7
|
-
import type { Language } from '../storage/types/
|
|
6
|
+
import { languageRepository } from '../repository/LanguageRepository';
|
|
7
|
+
import type { Language } from '../storage/types/Language';
|
|
8
8
|
|
|
9
9
|
// Re-export from DeviceLocale
|
|
10
10
|
export { DEFAULT_LANGUAGE, getDeviceLocale } from './DeviceLocale';
|
|
@@ -22,7 +22,7 @@ export {
|
|
|
22
22
|
export { LOCALE_MAPPING } from './LocaleMapping';
|
|
23
23
|
|
|
24
24
|
// Backward compatibility
|
|
25
|
-
export const getSUPPORTED_LANGUAGES = () =>
|
|
26
|
-
export const getLANGUAGES = () =>
|
|
27
|
-
export const SUPPORTED_LANGUAGES: Language[] =
|
|
25
|
+
export const getSUPPORTED_LANGUAGES = () => languageRepository.getLanguages();
|
|
26
|
+
export const getLANGUAGES = () => languageRepository.getLanguages();
|
|
27
|
+
export const SUPPORTED_LANGUAGES: Language[] = languageRepository.getLanguages();
|
|
28
28
|
export const LANGUAGES = SUPPORTED_LANGUAGES;
|
|
@@ -1,145 +1,26 @@
|
|
|
1
1
|
/**
|
|
2
|
-
* Language
|
|
3
|
-
*
|
|
4
|
-
* This is a base configuration that can be extended by consuming applications
|
|
2
|
+
* Language Data Exports
|
|
3
|
+
* Centralized exports for language management
|
|
5
4
|
*/
|
|
6
5
|
|
|
7
|
-
|
|
8
|
-
|
|
9
|
-
|
|
10
|
-
nativeName: string;
|
|
11
|
-
flag?: string;
|
|
12
|
-
isRTL?: boolean;
|
|
13
|
-
}
|
|
6
|
+
import { languageRepository } from '../repository/LanguageRepository';
|
|
7
|
+
import { DEFAULT_LANGUAGES } from './constants/defaultLanguages';
|
|
8
|
+
import type { Language } from '../storage/types/Language';
|
|
14
9
|
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
*/
|
|
19
|
-
export const DEFAULT_LANGUAGES: Language[] = [
|
|
20
|
-
{ code: 'ar-SA', name: 'Arabic', nativeName: 'العربية', flag: '🇸🇦', isRTL: true },
|
|
21
|
-
{ code: 'bg-BG', name: 'Bulgarian', nativeName: 'Български', flag: '🇧🇬', isRTL: false },
|
|
22
|
-
{ code: 'cs-CZ', name: 'Czech', nativeName: 'Čeština', flag: '🇨🇿', isRTL: false },
|
|
23
|
-
{ code: 'da-DK', name: 'Danish', nativeName: 'Dansk', flag: '🇩🇰', isRTL: false },
|
|
24
|
-
{ code: 'de-DE', name: 'German', nativeName: 'Deutsch', flag: '🇩🇪', isRTL: false },
|
|
25
|
-
{ code: 'el-GR', name: 'Greek', nativeName: 'Ελληνικά', flag: '🇬🇷', isRTL: false },
|
|
26
|
-
{ code: 'en-US', name: 'English', nativeName: 'English', flag: '🇺🇸', isRTL: false },
|
|
27
|
-
{ code: 'es-ES', name: 'Spanish', nativeName: 'Español', flag: '🇪🇸', isRTL: false },
|
|
28
|
-
{ code: 'fi-FI', name: 'Finnish', nativeName: 'Suomi', flag: '🇫🇮', isRTL: false },
|
|
29
|
-
{ code: 'fr-FR', name: 'French', nativeName: 'Français', flag: '🇫🇷', isRTL: false },
|
|
30
|
-
{ code: 'hi-IN', name: 'Hindi', nativeName: 'हिन्दी', flag: '🇮🇳', isRTL: false },
|
|
31
|
-
{ code: 'hr-HR', name: 'Croatian', nativeName: 'Hrvatski', flag: '🇭🇷', isRTL: false },
|
|
32
|
-
{ code: 'hu-HU', name: 'Hungarian', nativeName: 'Magyar', flag: '🇭🇺', isRTL: false },
|
|
33
|
-
{ code: 'id-ID', name: 'Indonesian', nativeName: 'Bahasa Indonesia', flag: '🇮', isRTL: false },
|
|
34
|
-
{ code: 'it-IT', name: 'Italian', nativeName: 'Italiano', flag: '🇮🇹', isRTL: false },
|
|
35
|
-
{ code: 'ja-JP', name: 'Japanese', nativeName: '日本語', flag: '🇯🇵', isRTL: false },
|
|
36
|
-
{ code: 'ko-KR', name: 'Korean', nativeName: '한국어', flag: '🇰🇷', isRTL: false },
|
|
37
|
-
{ code: 'ms-MY', name: 'Malay', nativeName: 'Bahasa Melayu', flag: '🇲🇾', isRTL: false },
|
|
38
|
-
{ code: 'nl-NL', name: 'Dutch', nativeName: 'Nederlands', flag: '🇳🇱', isRTL: false },
|
|
39
|
-
{ code: 'no-NO', name: 'Norwegian', nativeName: 'Norsk', flag: '🇳🇴', isRTL: false },
|
|
40
|
-
{ code: 'pl-PL', name: 'Polish', nativeName: 'Polski', flag: '🇵🇱', isRTL: false },
|
|
41
|
-
{ code: 'pt-BR', name: 'Portuguese (Brazil)', nativeName: 'Português (Brasil)', flag: '🇧🇷', isRTL: false },
|
|
42
|
-
{ code: 'pt-PT', name: 'Portuguese', nativeName: 'Português', flag: '🇵🇹', isRTL: false },
|
|
43
|
-
{ code: 'ro-RO', name: 'Romanian', nativeName: 'Română', flag: '🇷🇴', isRTL: false },
|
|
44
|
-
{ code: 'ru-RU', name: 'Russian', nativeName: 'Русский', flag: '🇷🇺', isRTL: false },
|
|
45
|
-
{ code: 'sk-SK', name: 'Slovak', nativeName: 'Slovenčina', flag: '🇸🇰', isRTL: false },
|
|
46
|
-
{ code: 'sv-SE', name: 'Swedish', nativeName: 'Svenska', flag: '🇸🇪', isRTL: false },
|
|
47
|
-
{ code: 'th-TH', name: 'Thai', nativeName: 'ไทย', flag: '🇹🇭', isRTL: false },
|
|
48
|
-
{ code: 'tl-PH', name: 'Filipino', nativeName: 'Filipino', flag: '🇵🇭', isRTL: false },
|
|
49
|
-
{ code: 'tr-TR', name: 'Turkish', nativeName: 'Türkçe', flag: '🇹🇷', isRTL: false },
|
|
50
|
-
{ code: 'uk-UA', name: 'Ukrainian', nativeName: 'Українська', flag: '🇺🇦', isRTL: false },
|
|
51
|
-
{ code: 'vi-VN', name: 'Vietnamese', nativeName: 'Tiếng Việt', flag: '🇻🇳', isRTL: false },
|
|
52
|
-
{ code: 'zh-CN', name: 'Chinese (Simplified)', nativeName: '简体中文', flag: '🇨🇳', isRTL: false },
|
|
53
|
-
{ code: 'zh-TW', name: 'Chinese (Traditional)', nativeName: '繁體中文', flag: '🇹🇼', isRTL: false },
|
|
54
|
-
];
|
|
55
|
-
|
|
56
|
-
/**
|
|
57
|
-
* Language registry for dynamic language management
|
|
58
|
-
*/
|
|
59
|
-
class LanguageRegistry {
|
|
60
|
-
private languages: Language[] = [...DEFAULT_LANGUAGES];
|
|
61
|
-
|
|
62
|
-
/**
|
|
63
|
-
* Register new languages
|
|
64
|
-
*/
|
|
65
|
-
registerLanguages(languages: Language[]): void {
|
|
66
|
-
this.languages = [...this.languages, ...languages];
|
|
67
|
-
}
|
|
68
|
-
|
|
69
|
-
/**
|
|
70
|
-
* Get all registered languages
|
|
71
|
-
*/
|
|
72
|
-
getLanguages(): Language[] {
|
|
73
|
-
return [...this.languages];
|
|
74
|
-
}
|
|
75
|
-
|
|
76
|
-
/**
|
|
77
|
-
* Clear all languages (reset to default)
|
|
78
|
-
*/
|
|
79
|
-
clearLanguages(): void {
|
|
80
|
-
this.languages = [...DEFAULT_LANGUAGES];
|
|
81
|
-
}
|
|
82
|
-
|
|
83
|
-
/**
|
|
84
|
-
* Get language by code
|
|
85
|
-
*/
|
|
86
|
-
getLanguageByCode(code: string): Language | undefined {
|
|
87
|
-
return this.languages.find(lang => lang.code === code);
|
|
88
|
-
}
|
|
89
|
-
|
|
90
|
-
/**
|
|
91
|
-
* Search languages by name or native name
|
|
92
|
-
*/
|
|
93
|
-
searchLanguages(query: string): Language[] {
|
|
94
|
-
const lowerQuery = query.toLowerCase();
|
|
95
|
-
return this.languages.filter(
|
|
96
|
-
lang =>
|
|
97
|
-
lang.name.toLowerCase().includes(lowerQuery) ||
|
|
98
|
-
lang.nativeName.toLowerCase().includes(lowerQuery)
|
|
99
|
-
);
|
|
100
|
-
}
|
|
101
|
-
|
|
102
|
-
/**
|
|
103
|
-
* Check if language is supported
|
|
104
|
-
*/
|
|
105
|
-
isLanguageSupported(code: string): boolean {
|
|
106
|
-
return this.languages.some(lang => lang.code === code);
|
|
107
|
-
}
|
|
108
|
-
|
|
109
|
-
/**
|
|
110
|
-
* Get default language
|
|
111
|
-
* Prioritizes en-US, otherwise returns first available
|
|
112
|
-
*/
|
|
113
|
-
getDefaultLanguage(): Language {
|
|
114
|
-
const en = this.languages.find(l => l.code === 'en-US');
|
|
115
|
-
if (en) return en;
|
|
116
|
-
const first = this.languages[0];
|
|
117
|
-
if (first) return first;
|
|
118
|
-
const systemDefault = DEFAULT_LANGUAGES[0];
|
|
119
|
-
if (systemDefault) return systemDefault;
|
|
120
|
-
throw new Error('No languages registered in registry or defaults');
|
|
121
|
-
}
|
|
122
|
-
}
|
|
123
|
-
|
|
124
|
-
// Singleton instance
|
|
125
|
-
export const languageRegistry = new LanguageRegistry();
|
|
10
|
+
export { languageRepository };
|
|
11
|
+
export { DEFAULT_LANGUAGES };
|
|
12
|
+
export type { Language };
|
|
126
13
|
|
|
127
|
-
|
|
128
|
-
|
|
129
|
-
return languageRegistry.getLanguageByCode(code);
|
|
130
|
-
};
|
|
14
|
+
export const getLanguageByCode = (code: string) =>
|
|
15
|
+
languageRepository.getLanguageByCode(code);
|
|
131
16
|
|
|
132
|
-
export const searchLanguages = (query: string)
|
|
133
|
-
|
|
134
|
-
};
|
|
17
|
+
export const searchLanguages = (query: string) =>
|
|
18
|
+
languageRepository.searchLanguages(query);
|
|
135
19
|
|
|
136
|
-
export const isLanguageSupported = (code: string)
|
|
137
|
-
|
|
138
|
-
};
|
|
20
|
+
export const isLanguageSupported = (code: string) =>
|
|
21
|
+
languageRepository.isLanguageSupported(code);
|
|
139
22
|
|
|
140
|
-
export const getDefaultLanguage = ()
|
|
141
|
-
|
|
142
|
-
};
|
|
23
|
+
export const getDefaultLanguage = () =>
|
|
24
|
+
languageRepository.getDefaultLanguage();
|
|
143
25
|
|
|
144
|
-
|
|
145
|
-
export const LANGUAGES = languageRegistry.getLanguages();
|
|
26
|
+
export const LANGUAGES = languageRepository.getLanguages();
|
|
@@ -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();
|
|
@@ -9,7 +9,7 @@
|
|
|
9
9
|
|
|
10
10
|
import { storageRepository } from '@umituz/react-native-storage';
|
|
11
11
|
import i18n from '../config/i18n';
|
|
12
|
-
import {
|
|
12
|
+
import { languageRepository } from '../repository/LanguageRepository';
|
|
13
13
|
import { getDeviceLocale } from '../config/languages';
|
|
14
14
|
|
|
15
15
|
const LANGUAGE_STORAGE_KEY = '@localization:language';
|
|
@@ -49,9 +49,9 @@ export class LanguageInitializer {
|
|
|
49
49
|
languageCode: string;
|
|
50
50
|
isRTL: boolean;
|
|
51
51
|
}> {
|
|
52
|
-
const language =
|
|
52
|
+
const language = languageRepository.getLanguageByCode(languageCode);
|
|
53
53
|
const finalLanguageCode = language ? languageCode : 'en-US';
|
|
54
|
-
const finalLanguageObj =
|
|
54
|
+
const finalLanguageObj = languageRepository.getLanguageByCode(finalLanguageCode);
|
|
55
55
|
|
|
56
56
|
await i18n.changeLanguage(finalLanguageCode);
|
|
57
57
|
|
|
@@ -8,7 +8,7 @@
|
|
|
8
8
|
|
|
9
9
|
import { storageRepository } from '@umituz/react-native-storage';
|
|
10
10
|
import i18n from '../config/i18n';
|
|
11
|
-
import {
|
|
11
|
+
import { languageRepository } from '../repository/LanguageRepository';
|
|
12
12
|
|
|
13
13
|
const LANGUAGE_STORAGE_KEY = '@localization:language';
|
|
14
14
|
|
|
@@ -20,7 +20,7 @@ export class LanguageSwitcher {
|
|
|
20
20
|
languageCode: string;
|
|
21
21
|
isRTL: boolean;
|
|
22
22
|
}> {
|
|
23
|
-
const language =
|
|
23
|
+
const language = languageRepository.getLanguageByCode(languageCode);
|
|
24
24
|
|
|
25
25
|
await i18n.changeLanguage(languageCode);
|
|
26
26
|
await storageRepository.setString(LANGUAGE_STORAGE_KEY, languageCode);
|
|
@@ -3,85 +3,73 @@
|
|
|
3
3
|
* Creates and manages localization state with proper separation of concerns
|
|
4
4
|
*/
|
|
5
5
|
|
|
6
|
-
import {
|
|
6
|
+
import { create } from 'zustand';
|
|
7
7
|
import type { LocalizationState, LocalizationActions, LocalizationGetters } from './types/LocalizationState';
|
|
8
8
|
import { LanguageInitializer } from './LanguageInitializer';
|
|
9
9
|
import { LanguageSwitcher } from './LanguageSwitcher';
|
|
10
|
-
import {
|
|
10
|
+
import { languageRepository } from '../repository/LanguageRepository';
|
|
11
11
|
|
|
12
|
-
type
|
|
12
|
+
type LocalizationStoreType = LocalizationState & LocalizationActions & LocalizationGetters;
|
|
13
13
|
|
|
14
|
-
const
|
|
14
|
+
export const useLocalizationStore = create<LocalizationStoreType>((set, get) => ({
|
|
15
|
+
// State
|
|
15
16
|
currentLanguage: 'en-US',
|
|
16
17
|
isRTL: false,
|
|
17
18
|
isInitialized: false,
|
|
18
|
-
supportedLanguages:
|
|
19
|
-
};
|
|
19
|
+
supportedLanguages: languageRepository.getLanguages(),
|
|
20
20
|
|
|
21
|
-
//
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
// Actions
|
|
28
|
-
initialize: async () => {
|
|
29
|
-
const { isInitialized: alreadyInitialized } = get();
|
|
30
|
-
if (alreadyInitialized) {
|
|
31
|
-
return;
|
|
32
|
-
}
|
|
21
|
+
// Actions
|
|
22
|
+
initialize: async () => {
|
|
23
|
+
const { isInitialized: alreadyInitialized } = get();
|
|
24
|
+
if (alreadyInitialized) {
|
|
25
|
+
return;
|
|
26
|
+
}
|
|
33
27
|
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
set({
|
|
38
|
-
currentLanguage: result.languageCode,
|
|
39
|
-
isRTL: result.isRTL,
|
|
40
|
-
isInitialized: true,
|
|
41
|
-
});
|
|
42
|
-
} catch {
|
|
43
|
-
set({
|
|
44
|
-
currentLanguage: 'en-US',
|
|
45
|
-
isRTL: false,
|
|
46
|
-
isInitialized: true,
|
|
47
|
-
});
|
|
48
|
-
}
|
|
49
|
-
},
|
|
50
|
-
|
|
51
|
-
setLanguage: async (languageCode: string) => {
|
|
52
|
-
const result = await LanguageSwitcher.switchLanguage(languageCode);
|
|
28
|
+
try {
|
|
29
|
+
const result = await LanguageInitializer.initialize();
|
|
53
30
|
|
|
54
31
|
set({
|
|
55
32
|
currentLanguage: result.languageCode,
|
|
56
33
|
isRTL: result.isRTL,
|
|
34
|
+
isInitialized: true,
|
|
57
35
|
});
|
|
58
|
-
}
|
|
59
|
-
|
|
60
|
-
reset: () => {
|
|
36
|
+
} catch {
|
|
61
37
|
set({
|
|
62
38
|
currentLanguage: 'en-US',
|
|
63
39
|
isRTL: false,
|
|
64
|
-
isInitialized:
|
|
40
|
+
isInitialized: true,
|
|
65
41
|
});
|
|
66
|
-
}
|
|
42
|
+
}
|
|
43
|
+
},
|
|
67
44
|
|
|
68
|
-
|
|
69
|
-
|
|
70
|
-
const { currentLanguage } = get();
|
|
71
|
-
return languageRegistry.getLanguageByCode(currentLanguage);
|
|
72
|
-
},
|
|
45
|
+
setLanguage: async (languageCode: string) => {
|
|
46
|
+
const result = await LanguageSwitcher.switchLanguage(languageCode);
|
|
73
47
|
|
|
74
|
-
|
|
75
|
-
|
|
76
|
-
|
|
48
|
+
set({
|
|
49
|
+
currentLanguage: result.languageCode,
|
|
50
|
+
isRTL: result.isRTL,
|
|
51
|
+
});
|
|
52
|
+
},
|
|
77
53
|
|
|
78
|
-
|
|
79
|
-
|
|
80
|
-
|
|
81
|
-
|
|
82
|
-
|
|
54
|
+
reset: () => {
|
|
55
|
+
set({
|
|
56
|
+
currentLanguage: 'en-US',
|
|
57
|
+
isRTL: false,
|
|
58
|
+
isInitialized: false,
|
|
59
|
+
});
|
|
60
|
+
},
|
|
83
61
|
|
|
84
|
-
|
|
85
|
-
|
|
86
|
-
|
|
87
|
-
|
|
62
|
+
// Getters
|
|
63
|
+
getCurrentLanguage: () => {
|
|
64
|
+
const { currentLanguage } = get();
|
|
65
|
+
return languageRepository.getLanguageByCode(currentLanguage);
|
|
66
|
+
},
|
|
67
|
+
|
|
68
|
+
isLanguageSupported: (code: string) => {
|
|
69
|
+
return languageRepository.isLanguageSupported(code);
|
|
70
|
+
},
|
|
71
|
+
|
|
72
|
+
getSupportedLanguages: () => {
|
|
73
|
+
return languageRepository.getLanguages();
|
|
74
|
+
},
|
|
75
|
+
}));
|
|
@@ -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
|
+
}
|
|
@@ -3,13 +3,9 @@
|
|
|
3
3
|
* Defines the shape of localization state management
|
|
4
4
|
*/
|
|
5
5
|
|
|
6
|
-
|
|
7
|
-
|
|
8
|
-
|
|
9
|
-
nativeName: string;
|
|
10
|
-
flag?: string;
|
|
11
|
-
isRTL?: boolean;
|
|
12
|
-
}
|
|
6
|
+
import type { Language } from './Language';
|
|
7
|
+
|
|
8
|
+
export type { Language };
|
|
13
9
|
|
|
14
10
|
export interface LocalizationState {
|
|
15
11
|
currentLanguage: string;
|
|
@@ -0,0 +1,44 @@
|
|
|
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
|
+
padding: 16,
|
|
13
|
+
borderRadius: 12,
|
|
14
|
+
borderWidth: 1,
|
|
15
|
+
marginBottom: 8,
|
|
16
|
+
},
|
|
17
|
+
selectedLanguageItem: {
|
|
18
|
+
borderWidth: 2,
|
|
19
|
+
},
|
|
20
|
+
languageContent: {
|
|
21
|
+
flexDirection: 'row',
|
|
22
|
+
alignItems: 'center',
|
|
23
|
+
flex: 1,
|
|
24
|
+
},
|
|
25
|
+
flag: {
|
|
26
|
+
fontSize: 24,
|
|
27
|
+
marginRight: 16,
|
|
28
|
+
},
|
|
29
|
+
languageText: {
|
|
30
|
+
flex: 1,
|
|
31
|
+
},
|
|
32
|
+
nativeName: {
|
|
33
|
+
fontSize: 16,
|
|
34
|
+
fontWeight: '600',
|
|
35
|
+
marginBottom: 2,
|
|
36
|
+
},
|
|
37
|
+
languageName: {
|
|
38
|
+
fontSize: 14,
|
|
39
|
+
},
|
|
40
|
+
checkIcon: {
|
|
41
|
+
fontSize: 18,
|
|
42
|
+
fontWeight: 'bold',
|
|
43
|
+
},
|
|
44
|
+
});
|
|
@@ -17,7 +17,8 @@ import {
|
|
|
17
17
|
} from 'react-native';
|
|
18
18
|
// @ts-ignore - Optional peer dependency
|
|
19
19
|
import { useAppDesignTokens } from '@umituz/react-native-design-system';
|
|
20
|
-
import type { Language } from '../../infrastructure/storage/types/
|
|
20
|
+
import type { Language } from '../../infrastructure/storage/types/Language';
|
|
21
|
+
import { styles } from './LanguageItem.styles';
|
|
21
22
|
|
|
22
23
|
interface LanguageItemProps {
|
|
23
24
|
item: Language;
|
|
@@ -91,42 +92,3 @@ export const LanguageItem: React.FC<LanguageItemProps> = ({
|
|
|
91
92
|
</TouchableOpacity>
|
|
92
93
|
);
|
|
93
94
|
};
|
|
94
|
-
|
|
95
|
-
const styles = StyleSheet.create({
|
|
96
|
-
languageItem: {
|
|
97
|
-
flexDirection: 'row',
|
|
98
|
-
alignItems: 'center',
|
|
99
|
-
justifyContent: 'space-between',
|
|
100
|
-
padding: 16,
|
|
101
|
-
borderRadius: 12,
|
|
102
|
-
borderWidth: 1,
|
|
103
|
-
marginBottom: 8,
|
|
104
|
-
},
|
|
105
|
-
selectedLanguageItem: {
|
|
106
|
-
borderWidth: 2,
|
|
107
|
-
},
|
|
108
|
-
languageContent: {
|
|
109
|
-
flexDirection: 'row',
|
|
110
|
-
alignItems: 'center',
|
|
111
|
-
flex: 1,
|
|
112
|
-
},
|
|
113
|
-
flag: {
|
|
114
|
-
fontSize: 24,
|
|
115
|
-
marginRight: 16,
|
|
116
|
-
},
|
|
117
|
-
languageText: {
|
|
118
|
-
flex: 1,
|
|
119
|
-
},
|
|
120
|
-
nativeName: {
|
|
121
|
-
fontSize: 16,
|
|
122
|
-
fontWeight: '600',
|
|
123
|
-
marginBottom: 2,
|
|
124
|
-
},
|
|
125
|
-
languageName: {
|
|
126
|
-
fontSize: 14,
|
|
127
|
-
},
|
|
128
|
-
checkIcon: {
|
|
129
|
-
fontSize: 18,
|
|
130
|
-
fontWeight: 'bold',
|
|
131
|
-
},
|
|
132
|
-
});
|
|
@@ -0,0 +1,34 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Search Input Component Styles
|
|
3
|
+
*/
|
|
4
|
+
|
|
5
|
+
import { StyleSheet } from 'react-native';
|
|
6
|
+
|
|
7
|
+
export const styles = StyleSheet.create({
|
|
8
|
+
searchContainer: {
|
|
9
|
+
flexDirection: 'row',
|
|
10
|
+
alignItems: 'center',
|
|
11
|
+
marginHorizontal: 20,
|
|
12
|
+
marginBottom: 24,
|
|
13
|
+
paddingHorizontal: 16,
|
|
14
|
+
paddingVertical: 12,
|
|
15
|
+
borderRadius: 12,
|
|
16
|
+
borderWidth: 1,
|
|
17
|
+
},
|
|
18
|
+
searchIcon: {
|
|
19
|
+
marginRight: 12,
|
|
20
|
+
fontSize: 16,
|
|
21
|
+
},
|
|
22
|
+
searchInput: {
|
|
23
|
+
flex: 1,
|
|
24
|
+
fontSize: 16,
|
|
25
|
+
padding: 0,
|
|
26
|
+
fontWeight: '500',
|
|
27
|
+
},
|
|
28
|
+
clearButton: {
|
|
29
|
+
padding: 4,
|
|
30
|
+
},
|
|
31
|
+
clearIcon: {
|
|
32
|
+
fontSize: 14,
|
|
33
|
+
},
|
|
34
|
+
});
|
|
@@ -11,13 +11,13 @@ import {
|
|
|
11
11
|
TextInput,
|
|
12
12
|
TouchableOpacity,
|
|
13
13
|
Text,
|
|
14
|
-
StyleSheet,
|
|
15
14
|
type StyleProp,
|
|
16
15
|
type ViewStyle,
|
|
17
16
|
type TextStyle,
|
|
18
17
|
} from 'react-native';
|
|
19
18
|
// @ts-ignore - Optional peer dependency
|
|
20
19
|
import { useAppDesignTokens } from '@umituz/react-native-design-system';
|
|
20
|
+
import { styles } from './SearchInput.styles';
|
|
21
21
|
|
|
22
22
|
interface SearchInputProps {
|
|
23
23
|
value: string;
|
|
@@ -76,32 +76,3 @@ export const SearchInput: React.FC<SearchInputProps> = ({
|
|
|
76
76
|
</View>
|
|
77
77
|
);
|
|
78
78
|
};
|
|
79
|
-
|
|
80
|
-
const styles = StyleSheet.create({
|
|
81
|
-
searchContainer: {
|
|
82
|
-
flexDirection: 'row',
|
|
83
|
-
alignItems: 'center',
|
|
84
|
-
marginHorizontal: 20,
|
|
85
|
-
marginBottom: 24,
|
|
86
|
-
paddingHorizontal: 16,
|
|
87
|
-
paddingVertical: 12,
|
|
88
|
-
borderRadius: 12,
|
|
89
|
-
borderWidth: 1,
|
|
90
|
-
},
|
|
91
|
-
searchIcon: {
|
|
92
|
-
marginRight: 12,
|
|
93
|
-
fontSize: 16,
|
|
94
|
-
},
|
|
95
|
-
searchInput: {
|
|
96
|
-
flex: 1,
|
|
97
|
-
fontSize: 16,
|
|
98
|
-
padding: 0,
|
|
99
|
-
fontWeight: '500',
|
|
100
|
-
},
|
|
101
|
-
clearButton: {
|
|
102
|
-
padding: 4,
|
|
103
|
-
},
|
|
104
|
-
clearIcon: {
|
|
105
|
-
fontSize: 14,
|
|
106
|
-
},
|
|
107
|
-
});
|
|
@@ -11,7 +11,8 @@ import { useAppDesignTokens } from '@umituz/react-native-design-system';
|
|
|
11
11
|
import { useLanguageSelection } from '../../infrastructure/hooks/useLanguageSelection';
|
|
12
12
|
import { LanguageItem } from '../components/LanguageItem';
|
|
13
13
|
import { SearchInput } from '../components/SearchInput';
|
|
14
|
-
import type { Language } from '../../infrastructure/storage/types/
|
|
14
|
+
import type { Language } from '../../infrastructure/storage/types/Language';
|
|
15
|
+
import { styles } from './LanguageSelectionScreen.styles';
|
|
15
16
|
|
|
16
17
|
interface LanguageSelectionScreenProps {
|
|
17
18
|
renderLanguageItem?: (item: Language, isSelected: boolean, onSelect: (code: string) => void) => React.ReactNode;
|
|
@@ -105,14 +106,4 @@ export const LanguageSelectionScreen: React.FC<LanguageSelectionScreenProps> = (
|
|
|
105
106
|
return Container ? <Container>{content}</Container> : content;
|
|
106
107
|
};
|
|
107
108
|
|
|
108
|
-
|
|
109
|
-
container: {
|
|
110
|
-
flex: 1,
|
|
111
|
-
},
|
|
112
|
-
listContent: {
|
|
113
|
-
paddingHorizontal: 20,
|
|
114
|
-
paddingBottom: 32,
|
|
115
|
-
},
|
|
116
|
-
});
|
|
117
|
-
|
|
118
|
-
export default LanguageSelectionScreen;
|
|
109
|
+
export default LanguageSelectionScreen;
|