@fadyshawky/react-native-magic 1.0.8 → 1.0.9
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/.vscode/settings.json +7 -0
- package/package.json +1 -1
- package/template/App.tsx +30 -9
- package/template/package-lock.json +170 -123
- package/template/package.json +1 -0
- package/template/src/common/ImageResources.g.ts +3 -5
- package/template/src/common/components/Background.tsx +66 -28
- package/template/src/common/components/Cards.tsx +116 -0
- package/template/src/common/components/Container.tsx +145 -0
- package/template/src/common/components/FlatListWrapper.tsx +1 -0
- package/template/src/common/components/ImageCropPickerButton.tsx +1 -1
- package/template/src/common/components/OTPInput.tsx +107 -0
- package/template/src/common/components/PhotoTakingButton.tsx +1 -4
- package/template/src/common/components/PrimaryButton.tsx +171 -157
- package/template/src/common/components/RTLAwareText.tsx +42 -0
- package/template/src/common/components/RTLAwareView.tsx +179 -0
- package/template/src/common/components/RadioButton.tsx +1 -3
- package/template/src/common/components/RadioIcon.tsx +1 -2
- package/template/src/common/components/SearchBar.tsx +179 -0
- package/template/src/common/components/Separator.tsx +7 -4
- package/template/src/common/components/TouchablePlatform.tsx +1 -3
- package/template/src/common/components/TryAgain.tsx +3 -3
- package/template/src/common/helpers/inAppReviewHelper.ts +0 -1
- package/template/src/common/helpers/stringsHelpers.ts +10 -0
- package/template/src/common/hooks/useFlatListActions.ts +1 -1
- package/template/src/common/localization/LocalizationProvider.tsx +152 -0
- package/template/src/common/localization/README.md +488 -0
- package/template/src/common/localization/localization.ts +12 -0
- package/template/src/common/localization/translations/profileLocalization.ts +24 -0
- package/template/src/common/validations/errorValidations.ts +1 -6
- package/template/src/common/validations/examples/TextInputWithValidation.tsx +229 -0
- package/template/src/common/validations/index.ts +28 -0
- package/template/src/common/validations/regex.js +83 -0
- package/template/src/common/validations/regexValidator.ts +240 -0
- package/template/src/common/validations/validationConstants.ts +2 -2
- package/template/src/core/api/errorHandler.ts +39 -0
- package/template/src/core/api/responseHandlers.ts +1 -26
- package/template/src/core/api/serverHeaders.ts +13 -23
- package/template/src/core/store/app/appSlice.ts +1 -2
- package/template/src/core/theme/ThemeProvider.tsx +63 -0
- package/template/src/core/theme/colors.ts +31 -42
- package/template/src/core/theme/commonConsts.ts +1 -1
- package/template/src/core/theme/commonStyles.ts +267 -210
- package/template/src/core/theme/fonts.ts +17 -1
- package/template/src/core/theme/scaling.ts +101 -0
- package/template/src/core/theme/themes.ts +214 -0
- package/template/src/core/theme/types.ts +51 -0
- package/template/src/navigation/AuthStack.tsx +25 -30
- package/template/src/navigation/HeaderComponents.tsx +18 -58
- package/template/src/navigation/MainNavigation.tsx +5 -6
- package/template/src/navigation/MainStack.tsx +3 -28
- package/template/src/navigation/RootNavigation.tsx +1 -7
- package/template/src/navigation/TabBar.tsx +2 -2
- package/template/src/navigation/TopTabBar.tsx +1 -1
- package/template/src/screens/Login/Login.tsx +3 -3
- package/template/src/screens/home/components/CarouselSection.tsx +7 -8
- package/template/src/screens/home/components/FeaturedCarousel.tsx +5 -6
- package/template/src/screens/registration/RegistrationScreen.tsx +2 -2
- package/template/src/screens/resetPassword/ForgotPasswordScreen.tsx +2 -2
- package/template/src/utils/stringBuilder.js +25 -0
|
@@ -0,0 +1,179 @@
|
|
|
1
|
+
import React from 'react';
|
|
2
|
+
import {
|
|
3
|
+
I18nManager,
|
|
4
|
+
KeyboardTypeOptions,
|
|
5
|
+
Platform,
|
|
6
|
+
StyleProp,
|
|
7
|
+
StyleSheet,
|
|
8
|
+
TextInput,
|
|
9
|
+
TextInputProps,
|
|
10
|
+
TextStyle,
|
|
11
|
+
TouchableOpacity,
|
|
12
|
+
ViewStyle,
|
|
13
|
+
} from 'react-native';
|
|
14
|
+
import Icon from 'react-native-vector-icons/Ionicons';
|
|
15
|
+
import {CommonSizes} from '../../core/theme/commonSizes';
|
|
16
|
+
import {createThemedStyles} from '../../core/theme/commonStyles';
|
|
17
|
+
import {useTheme} from '../../core/theme/ThemeProvider';
|
|
18
|
+
import {Languages} from '../localization/localization';
|
|
19
|
+
import {
|
|
20
|
+
useLocalization,
|
|
21
|
+
useRTL,
|
|
22
|
+
useTranslation,
|
|
23
|
+
} from '../localization/LocalizationProvider';
|
|
24
|
+
import {RTLAwareView} from './RTLAwareView';
|
|
25
|
+
|
|
26
|
+
interface SearchBarProps {
|
|
27
|
+
value: string;
|
|
28
|
+
onChangeText: (text: string) => void;
|
|
29
|
+
placeholder?: string;
|
|
30
|
+
style?: StyleProp<ViewStyle>;
|
|
31
|
+
inputStyle?: StyleProp<TextStyle>;
|
|
32
|
+
onClear?: () => void;
|
|
33
|
+
autoFocus?: boolean;
|
|
34
|
+
}
|
|
35
|
+
|
|
36
|
+
export const SearchBar: React.FC<SearchBarProps> = ({
|
|
37
|
+
value,
|
|
38
|
+
onChangeText,
|
|
39
|
+
placeholder,
|
|
40
|
+
style,
|
|
41
|
+
inputStyle,
|
|
42
|
+
onClear,
|
|
43
|
+
autoFocus = false,
|
|
44
|
+
}) => {
|
|
45
|
+
const {theme} = useTheme();
|
|
46
|
+
const t = useTranslation();
|
|
47
|
+
const isRTL = useRTL();
|
|
48
|
+
const {currentLanguage} = useLocalization();
|
|
49
|
+
const isArabic = currentLanguage === Languages.ar;
|
|
50
|
+
|
|
51
|
+
// Create a ref to the TextInput to control it programmatically if needed
|
|
52
|
+
const inputRef = React.useRef<TextInput>(null);
|
|
53
|
+
|
|
54
|
+
const handleClear = () => {
|
|
55
|
+
onChangeText('');
|
|
56
|
+
if (onClear) {
|
|
57
|
+
onClear();
|
|
58
|
+
}
|
|
59
|
+
};
|
|
60
|
+
|
|
61
|
+
const containerStyle: ViewStyle = {
|
|
62
|
+
backgroundColor: `${theme.colors.white}20`,
|
|
63
|
+
borderColor: theme.colors.mutedLavender30,
|
|
64
|
+
};
|
|
65
|
+
|
|
66
|
+
// Set keyboard language specific properties
|
|
67
|
+
const getKeyboardProps = (): Partial<TextInputProps> => {
|
|
68
|
+
if (Platform.OS === 'ios') {
|
|
69
|
+
// For iOS, we use primaryLanguage to hint at the keyboard language
|
|
70
|
+
return {
|
|
71
|
+
keyboardType: 'default' as KeyboardTypeOptions,
|
|
72
|
+
textContentType: 'none',
|
|
73
|
+
// primaryLanguage property is iOS specific but not directly supported by RN types
|
|
74
|
+
// This custom property helps indicate preferred keyboard language to iOS
|
|
75
|
+
...(isArabic ? {primaryLanguage: 'ar'} : {primaryLanguage: 'en'}),
|
|
76
|
+
};
|
|
77
|
+
} else {
|
|
78
|
+
// Android keyboard language is system controlled, we can only hint at it
|
|
79
|
+
return {
|
|
80
|
+
keyboardType: 'default' as KeyboardTypeOptions,
|
|
81
|
+
};
|
|
82
|
+
}
|
|
83
|
+
};
|
|
84
|
+
|
|
85
|
+
// Switch to appropriate keyboard when language changes
|
|
86
|
+
React.useEffect(() => {
|
|
87
|
+
if (Platform.OS === 'android') {
|
|
88
|
+
// On Android, the keyboard language is managed system-wide
|
|
89
|
+
// We can only ensure our TextInput respects RTL settings
|
|
90
|
+
const shouldBeRTL = isArabic;
|
|
91
|
+
if (I18nManager.isRTL !== shouldBeRTL) {
|
|
92
|
+
}
|
|
93
|
+
}
|
|
94
|
+
|
|
95
|
+
// Reset input when language changes to ensure keyboard updates
|
|
96
|
+
if (inputRef.current && value.length > 0) {
|
|
97
|
+
// Force the keyboard to reload with new language by blurring/focusing
|
|
98
|
+
inputRef.current.blur();
|
|
99
|
+
setTimeout(() => {
|
|
100
|
+
if (inputRef.current) {
|
|
101
|
+
inputRef.current.focus();
|
|
102
|
+
}
|
|
103
|
+
}, 100);
|
|
104
|
+
}
|
|
105
|
+
}, [currentLanguage, value]);
|
|
106
|
+
|
|
107
|
+
return (
|
|
108
|
+
<RTLAwareView
|
|
109
|
+
style={[
|
|
110
|
+
styles.container,
|
|
111
|
+
containerStyle,
|
|
112
|
+
style as ViewStyle,
|
|
113
|
+
{
|
|
114
|
+
backgroundColor: theme.colors.surface,
|
|
115
|
+
...createThemedStyles(theme).dropShadow,
|
|
116
|
+
},
|
|
117
|
+
]}>
|
|
118
|
+
<Icon
|
|
119
|
+
name="search"
|
|
120
|
+
size={20}
|
|
121
|
+
color={theme.colors.mutedLavender}
|
|
122
|
+
style={styles.searchIcon}
|
|
123
|
+
/>
|
|
124
|
+
<TextInput
|
|
125
|
+
ref={inputRef}
|
|
126
|
+
{...getKeyboardProps()}
|
|
127
|
+
style={[
|
|
128
|
+
styles.input,
|
|
129
|
+
theme.text.SearchBar,
|
|
130
|
+
{
|
|
131
|
+
textAlign: isRTL ? 'right' : 'left',
|
|
132
|
+
},
|
|
133
|
+
inputStyle as TextStyle,
|
|
134
|
+
]}
|
|
135
|
+
placeholder={placeholder || t('search', 'common')}
|
|
136
|
+
placeholderTextColor={`${theme.colors.mutedLavender}80`}
|
|
137
|
+
value={value}
|
|
138
|
+
onChangeText={onChangeText}
|
|
139
|
+
autoCapitalize="none"
|
|
140
|
+
autoComplete="off"
|
|
141
|
+
autoCorrect={false}
|
|
142
|
+
autoFocus={autoFocus}
|
|
143
|
+
/>
|
|
144
|
+
{value.length > 0 && (
|
|
145
|
+
<TouchableOpacity onPressIn={handleClear} style={styles.clearButton}>
|
|
146
|
+
<Icon
|
|
147
|
+
name="close-circle"
|
|
148
|
+
size={20}
|
|
149
|
+
color={theme.colors.mutedLavender}
|
|
150
|
+
/>
|
|
151
|
+
</TouchableOpacity>
|
|
152
|
+
)}
|
|
153
|
+
</RTLAwareView>
|
|
154
|
+
);
|
|
155
|
+
};
|
|
156
|
+
|
|
157
|
+
const styles = StyleSheet.create({
|
|
158
|
+
container: {
|
|
159
|
+
flexDirection: 'row',
|
|
160
|
+
alignItems: 'center',
|
|
161
|
+
height: 48,
|
|
162
|
+
borderRadius: 16,
|
|
163
|
+
borderWidth: 1,
|
|
164
|
+
paddingHorizontal: CommonSizes.spacing.medium,
|
|
165
|
+
marginHorizontal: CommonSizes.spacing.large,
|
|
166
|
+
marginBottom: CommonSizes.spacing.large,
|
|
167
|
+
},
|
|
168
|
+
input: {
|
|
169
|
+
flex: 1,
|
|
170
|
+
paddingVertical: CommonSizes.spacing.medium,
|
|
171
|
+
paddingHorizontal: CommonSizes.spacing.small,
|
|
172
|
+
},
|
|
173
|
+
searchIcon: {
|
|
174
|
+
marginHorizontal: CommonSizes.spacing.small,
|
|
175
|
+
},
|
|
176
|
+
clearButton: {
|
|
177
|
+
padding: CommonSizes.spacing.small,
|
|
178
|
+
},
|
|
179
|
+
});
|
|
@@ -1,24 +1,27 @@
|
|
|
1
1
|
import React, {FC, useMemo} from 'react';
|
|
2
2
|
import {StyleSheet, View, ViewStyle} from 'react-native';
|
|
3
|
-
import {Colors} from '../../core/theme/colors';
|
|
4
3
|
import {hairlineWidth} from '../../core/theme/commonConsts';
|
|
5
4
|
import {CommonSizes} from '../../core/theme/commonSizes';
|
|
6
|
-
|
|
5
|
+
import {useTheme} from '../../core/theme/ThemeProvider';
|
|
7
6
|
interface IProps {
|
|
8
7
|
isFull?: boolean;
|
|
9
8
|
}
|
|
10
9
|
|
|
11
10
|
export const Separator: FC<IProps> = ({isFull = true}) => {
|
|
11
|
+
const {theme} = useTheme();
|
|
12
12
|
const containerStyle = useMemo(() => {
|
|
13
13
|
return isFull ? styles.fullContainer : styles.container;
|
|
14
14
|
}, [isFull]);
|
|
15
15
|
|
|
16
|
-
return
|
|
16
|
+
return (
|
|
17
|
+
<View
|
|
18
|
+
style={[containerStyle, {backgroundColor: theme.colors.indigoBlue}]}
|
|
19
|
+
/>
|
|
20
|
+
);
|
|
17
21
|
};
|
|
18
22
|
|
|
19
23
|
const sharedStyle: ViewStyle = {
|
|
20
24
|
height: hairlineWidth,
|
|
21
|
-
backgroundColor: Colors.gray,
|
|
22
25
|
};
|
|
23
26
|
|
|
24
27
|
const styles = StyleSheet.create({
|
|
@@ -7,7 +7,6 @@ import {
|
|
|
7
7
|
PressableStateCallbackType,
|
|
8
8
|
ViewStyle,
|
|
9
9
|
} from 'react-native';
|
|
10
|
-
import {Colors} from '../../core/theme/colors';
|
|
11
10
|
import {isAndroid} from '../../core/theme/commonConsts';
|
|
12
11
|
|
|
13
12
|
interface IProps extends PressableProps {
|
|
@@ -16,7 +15,7 @@ interface IProps extends PressableProps {
|
|
|
16
15
|
}
|
|
17
16
|
|
|
18
17
|
export const TouchablePlatform: FC<IProps> = memo(
|
|
19
|
-
({children, highlightColor
|
|
18
|
+
({children, highlightColor, style, ...props}) => {
|
|
20
19
|
const pressableStyle = useCallback(
|
|
21
20
|
(state: PressableStateCallbackType) => {
|
|
22
21
|
if (isAndroid) {
|
|
@@ -56,6 +55,5 @@ export const TouchablePlatform: FC<IProps> = memo(
|
|
|
56
55
|
);
|
|
57
56
|
|
|
58
57
|
const androidRippleConfig: PressableAndroidRippleConfig = {
|
|
59
|
-
color: Colors.white,
|
|
60
58
|
borderless: false,
|
|
61
59
|
};
|
|
@@ -6,7 +6,7 @@ import {
|
|
|
6
6
|
TouchableOpacity,
|
|
7
7
|
View,
|
|
8
8
|
} from 'react-native';
|
|
9
|
-
import {Colors
|
|
9
|
+
import {Colors} from '../../core/theme/colors';
|
|
10
10
|
import {CommonSizes} from '../../core/theme/commonSizes';
|
|
11
11
|
import {CommonStyles} from '../../core/theme/commonStyles';
|
|
12
12
|
import {localization} from '../localization/localization';
|
|
@@ -23,7 +23,7 @@ export const TryAgain: FC<IProps> = memo(
|
|
|
23
23
|
style={{...CommonStyles.flexCenter, backgroundColor: 'transparent'}}>
|
|
24
24
|
<Text style={styles.title}>{errorText}</Text>
|
|
25
25
|
{onPress != null && (
|
|
26
|
-
<TouchableOpacity
|
|
26
|
+
<TouchableOpacity onPressIn={onPress}>
|
|
27
27
|
<Text style={styles.description}>
|
|
28
28
|
{localization.errors.tryAgain}
|
|
29
29
|
</Text>
|
|
@@ -42,7 +42,7 @@ const styles = StyleSheet.create({
|
|
|
42
42
|
} as TextStyle,
|
|
43
43
|
description: {
|
|
44
44
|
...CommonStyles.normalText,
|
|
45
|
-
color:
|
|
45
|
+
color: Colors.blueNormalActive,
|
|
46
46
|
textAlign: 'center',
|
|
47
47
|
textDecorationLine: 'underline',
|
|
48
48
|
} as TextStyle,
|
|
@@ -13,3 +13,13 @@ export function removeEmojis(text: string): string {
|
|
|
13
13
|
'',
|
|
14
14
|
);
|
|
15
15
|
}
|
|
16
|
+
|
|
17
|
+
export function normalizeAndTrimWhitespace(text: string): string {
|
|
18
|
+
// 1. Remove leading whitespace with trimStart()
|
|
19
|
+
// 2. Remove trailing whitespace with trimEnd()
|
|
20
|
+
// 3. Replace multiple whitespaces (including tabs, newlines) with a single space
|
|
21
|
+
return text
|
|
22
|
+
?.trimStart() // Remove leading whitespace
|
|
23
|
+
?.trimEnd() // Remove trailing whitespace
|
|
24
|
+
?.replace(/\s+/g, ' '); // Replace multiple whitespaces with single space
|
|
25
|
+
}
|
|
@@ -0,0 +1,152 @@
|
|
|
1
|
+
import React, {createContext, useContext, useState, useEffect} from 'react';
|
|
2
|
+
import {I18nManager, Platform, NativeModules} from 'react-native';
|
|
3
|
+
import RNRestart from 'react-native-restart';
|
|
4
|
+
import {
|
|
5
|
+
localization,
|
|
6
|
+
Languages,
|
|
7
|
+
setLanguage as setLanguageUtil,
|
|
8
|
+
getInterfaceLanguage,
|
|
9
|
+
DEFAULT_LANGUAGE,
|
|
10
|
+
} from './localization';
|
|
11
|
+
import {useAppDispatch, useAppSelector} from '../../core/store/reduxHelpers';
|
|
12
|
+
import {setLanguage as setLanguageAction} from '../../core/store/app/appSlice';
|
|
13
|
+
|
|
14
|
+
interface LocalizationContextType {
|
|
15
|
+
currentLanguage: Languages;
|
|
16
|
+
changeLanguage: (language: Languages) => void;
|
|
17
|
+
t: (key: string, section?: keyof typeof localization) => string;
|
|
18
|
+
isRTL: boolean;
|
|
19
|
+
}
|
|
20
|
+
|
|
21
|
+
const LocalizationContext = createContext<LocalizationContextType | undefined>(
|
|
22
|
+
undefined,
|
|
23
|
+
);
|
|
24
|
+
|
|
25
|
+
interface LocalizationProviderProps {
|
|
26
|
+
children: React.ReactNode;
|
|
27
|
+
initialLanguage?: Languages;
|
|
28
|
+
}
|
|
29
|
+
|
|
30
|
+
export const LocalizationProvider: React.FC<LocalizationProviderProps> = ({
|
|
31
|
+
children,
|
|
32
|
+
initialLanguage,
|
|
33
|
+
}) => {
|
|
34
|
+
const dispatch = useAppDispatch();
|
|
35
|
+
const {language: storedLanguage, isRTL: storedIsRTL} = useAppSelector(
|
|
36
|
+
state => state.app,
|
|
37
|
+
);
|
|
38
|
+
|
|
39
|
+
const [currentLanguage, setCurrentLanguage] = useState<Languages>(
|
|
40
|
+
storedLanguage ||
|
|
41
|
+
initialLanguage ||
|
|
42
|
+
(getInterfaceLanguage() as Languages) ||
|
|
43
|
+
DEFAULT_LANGUAGE,
|
|
44
|
+
);
|
|
45
|
+
const [isRTL, setIsRTL] = useState<boolean>(
|
|
46
|
+
storedIsRTL !== undefined ? storedIsRTL : currentLanguage === Languages.ar,
|
|
47
|
+
);
|
|
48
|
+
|
|
49
|
+
useEffect(() => {
|
|
50
|
+
setLanguageUtil(currentLanguage);
|
|
51
|
+
setIsRTL(currentLanguage === Languages.ar);
|
|
52
|
+
|
|
53
|
+
// Force app restart on Android when changing RTL/LTR
|
|
54
|
+
if (Platform.OS === 'android') {
|
|
55
|
+
const shouldBeRTL = currentLanguage === Languages.ar;
|
|
56
|
+
if (I18nManager.isRTL !== shouldBeRTL) {
|
|
57
|
+
I18nManager.allowRTL(shouldBeRTL);
|
|
58
|
+
I18nManager.forceRTL(shouldBeRTL);
|
|
59
|
+
}
|
|
60
|
+
}
|
|
61
|
+
}, [currentLanguage]);
|
|
62
|
+
|
|
63
|
+
const changeLanguage = (language: Languages) => {
|
|
64
|
+
// Only restart if the language is actually changing
|
|
65
|
+
if (language !== currentLanguage) {
|
|
66
|
+
setCurrentLanguage(language);
|
|
67
|
+
dispatch(setLanguageAction(language));
|
|
68
|
+
|
|
69
|
+
// Set RTL configuration before restart
|
|
70
|
+
const shouldBeRTL = language === Languages.ar;
|
|
71
|
+
if (I18nManager.isRTL !== shouldBeRTL) {
|
|
72
|
+
I18nManager.allowRTL(shouldBeRTL);
|
|
73
|
+
I18nManager.forceRTL(shouldBeRTL);
|
|
74
|
+
|
|
75
|
+
// Restart the app to apply RTL/LTR changes properly
|
|
76
|
+
// Small delay to ensure settings are applied
|
|
77
|
+
setTimeout(() => {
|
|
78
|
+
try {
|
|
79
|
+
// Check if RNRestart is available
|
|
80
|
+
if (RNRestart && typeof RNRestart.restart === 'function') {
|
|
81
|
+
RNRestart.restart();
|
|
82
|
+
} else if (Platform.OS === 'android') {
|
|
83
|
+
// Fallback for Android using DevSettings
|
|
84
|
+
const DevSettings = NativeModules.DevSettings;
|
|
85
|
+
if (DevSettings && DevSettings.reload) {
|
|
86
|
+
DevSettings.reload();
|
|
87
|
+
}
|
|
88
|
+
}
|
|
89
|
+
} catch (error) {
|
|
90
|
+
console.error('Failed to restart the app:', error);
|
|
91
|
+
}
|
|
92
|
+
}, 100);
|
|
93
|
+
}
|
|
94
|
+
}
|
|
95
|
+
};
|
|
96
|
+
|
|
97
|
+
// Helper function to get translations
|
|
98
|
+
const t = (
|
|
99
|
+
key: string,
|
|
100
|
+
section: keyof typeof localization = 'common',
|
|
101
|
+
): string => {
|
|
102
|
+
try {
|
|
103
|
+
const keys = key.split('.');
|
|
104
|
+
let result: any = localization[section];
|
|
105
|
+
|
|
106
|
+
// If the key has dot notation (e.g., 'registration.title'), navigate through the object
|
|
107
|
+
if (keys.length > 1) {
|
|
108
|
+
for (const k of keys) {
|
|
109
|
+
result = result[k];
|
|
110
|
+
}
|
|
111
|
+
return result || key;
|
|
112
|
+
}
|
|
113
|
+
|
|
114
|
+
// Simple key
|
|
115
|
+
return result[key] || key;
|
|
116
|
+
} catch (error) {
|
|
117
|
+
console.warn(
|
|
118
|
+
`Translation not found for key: ${key} in section: ${section}`,
|
|
119
|
+
);
|
|
120
|
+
return key;
|
|
121
|
+
}
|
|
122
|
+
};
|
|
123
|
+
|
|
124
|
+
return (
|
|
125
|
+
<LocalizationContext.Provider
|
|
126
|
+
value={{currentLanguage, changeLanguage, t, isRTL}}>
|
|
127
|
+
{children}
|
|
128
|
+
</LocalizationContext.Provider>
|
|
129
|
+
);
|
|
130
|
+
};
|
|
131
|
+
|
|
132
|
+
export const useLocalization = () => {
|
|
133
|
+
const context = useContext(LocalizationContext);
|
|
134
|
+
if (!context) {
|
|
135
|
+
throw new Error(
|
|
136
|
+
'useLocalization must be used within a LocalizationProvider',
|
|
137
|
+
);
|
|
138
|
+
}
|
|
139
|
+
return context;
|
|
140
|
+
};
|
|
141
|
+
|
|
142
|
+
// Shorthand hook for translations
|
|
143
|
+
export const useTranslation = () => {
|
|
144
|
+
const {t} = useLocalization();
|
|
145
|
+
return t;
|
|
146
|
+
};
|
|
147
|
+
|
|
148
|
+
// Hook to get RTL status
|
|
149
|
+
export const useRTL = () => {
|
|
150
|
+
const {isRTL} = useLocalization();
|
|
151
|
+
return isRTL;
|
|
152
|
+
};
|