diff --git a/app/(tabs)/_layout.tsx b/app/(tabs)/_layout.tsx index 75914cd..bb24762 100644 --- a/app/(tabs)/_layout.tsx +++ b/app/(tabs)/_layout.tsx @@ -3,6 +3,7 @@ import { Tabs } from 'expo-router'; import { useColorScheme } from 'react-native'; import Colors from '@/constants/Colors'; +import i18n from '@/i18n'; /** * You can explore the built-in icon families and icons on the web at https://icons.expo.fyi/ @@ -30,14 +31,14 @@ export default function TabLayout() { , }} /> , }} /> diff --git a/app/(tabs)/home.tsx b/app/(tabs)/home.tsx index 82dbf9a..3c21ac4 100644 --- a/app/(tabs)/home.tsx +++ b/app/(tabs)/home.tsx @@ -1,31 +1,82 @@ -import { StyleSheet, SafeAreaView, ScrollView } from 'react-native'; +import { StyleSheet, SafeAreaView, ScrollView, I18nManager, Modal, Pressable } from 'react-native'; import { Text, View } from '@/components/Themed'; import FeatureCard from '@/components/FeatureCard'; import PrayerTimeCard from '@/components/PrayerTimeCard'; import ServicesGrid from '@/components/ServicesGrid'; +import i18n from '@/i18n'; +import * as Updates from 'expo-updates'; +import AsyncStorage from '@react-native-async-storage/async-storage'; +import Colors from '@/constants/Colors'; +import { useState } from 'react'; + export default function HomeScreen() { + const [modalVisible, setModalVisible] = useState(false); + + const changeLanguage = async (lang: 'en' | 'tk' | 'ru') => { + if (!lang) return; + setModalVisible(false); + if (lang === i18n.locale.substring(0, 2)) return; + await AsyncStorage.setItem('user-language', lang); + i18n.locale = lang; + I18nManager.forceRTL(false); // Assuming LTR for all three languages + Updates.reloadAsync(); + }; + + const languages = [ + { label: 'English', value: 'en' }, + { label: 'Türkmen', value: 'tk' }, + { label: 'Русский', value: 'ru' }, + ]; + + const currentLanguage = languages.find(l => l.value === i18n.locale.substring(0, 2))?.label; + return ( + + {i18n.t('home')} + setModalVisible(true)} style={pickerSelectStyles.inputIOS}> + {currentLanguage} + + + { + setModalVisible(!modalVisible); + }}> + setModalVisible(false)}> + + {languages.map((lang) => ( + changeLanguage(lang.value as 'en' | 'tk' | 'ru')}> + {lang.label} + + ))} + + + - Home @@ -44,13 +95,81 @@ const styles = StyleSheet.create({ innerContainer: { paddingHorizontal: 15, }, + header: { + flexDirection: 'row', + justifyContent: 'space-between', + alignItems: 'center', + paddingHorizontal: 15, + }, title: { fontSize: 22, fontWeight: 'bold', marginVertical: 15, }, + langSwitcher: { + flexDirection: 'row', + }, + langText: { + color: '#fff', + paddingHorizontal: 5, + fontWeight: 'bold', + }, cardRow: { flexDirection: 'row', justifyContent: 'space-between', }, + modalOverlay: { + flex: 1, + justifyContent: 'center', + alignItems: 'center', + backgroundColor: 'rgba(0,0,0,0.5)', + }, + modalView: { + margin: 20, + backgroundColor: Colors.dark.secondary, + borderRadius: 20, + padding: 35, + alignItems: 'center', + shadowColor: '#000', + shadowOffset: { + width: 0, + height: 2, + }, + shadowOpacity: 0.25, + shadowRadius: 4, + elevation: 5, + }, + modalButton: { + padding: 10, + borderBottomWidth: 1, + borderBottomColor: '#333', + width: '100%', + }, + modalButtonText: { + color: 'white', + textAlign: 'center', + }, +}); + +const pickerSelectStyles = StyleSheet.create({ + inputIOS: { + fontSize: 16, + paddingVertical: 12, + paddingHorizontal: 10, + borderWidth: 1, + borderColor: Colors.dark.secondary, + borderRadius: 4, + color: 'white', + paddingRight: 30, // to ensure the text is never behind the icon + }, + inputAndroid: { + fontSize: 16, + paddingHorizontal: 10, + paddingVertical: 8, + borderWidth: 0.5, + borderColor: Colors.dark.secondary, + borderRadius: 8, + color: 'white', + paddingRight: 30, // to ensure the text is never behind the icon + }, }); diff --git a/app/(tabs)/services.tsx b/app/(tabs)/services.tsx index cd83519..54348ac 100644 --- a/app/(tabs)/services.tsx +++ b/app/(tabs)/services.tsx @@ -2,19 +2,20 @@ import { StyleSheet, SafeAreaView, ScrollView } from 'react-native'; import { Text, View } from '@/components/Themed'; import AdhkarCard from '@/components/AdhkarCard'; import SupplicationListItem from '@/components/SupplicationListItem'; +import i18n from '@/i18n'; const adhkarCategories = [ - 'Thikr said in the morning and evening', - 'Thikr before sleeping', - 'Thikr after salam', + 'morningEveningThikr', + 'beforeSleepingThikr', + 'afterSalamThikr', ]; const supplications = [ - 'Upon breaking fast', - 'Supplication said by one fasting when presented with food and does not break his fast', - 'When insulted while fasting', - 'Supplication upon seeing the early or premature fruit', - 'Supplication upon sneezing', + 'breakingFastSupplication', + 'fastingPersonSupplication', + 'insultedWhileFasting', + 'seeingFruitSupplication', + 'sneezingSupplication', ] export default function ServicesScreen() { @@ -22,15 +23,15 @@ export default function ServicesScreen() { - Services - Adhkar - {adhkarCategories.map((title, index) => ( - + {i18n.t('services')} + {i18n.t('adhkar')} + {adhkarCategories.map((titleKey, index) => ( + ))} - Hisn Al-Muslim - {supplications.map((text, index) => ( - {}} /> + {i18n.t('hisnAlMuslim')} + {supplications.map((textKey, index) => ( + {}} /> ))} diff --git a/app/_layout.tsx b/app/_layout.tsx index 40cdc48..4a8d119 100644 --- a/app/_layout.tsx +++ b/app/_layout.tsx @@ -3,10 +3,11 @@ import { DarkTheme, DefaultTheme, ThemeProvider } from '@react-navigation/native import { useFonts } from 'expo-font'; import { Stack } from 'expo-router'; import * as SplashScreen from 'expo-splash-screen'; -import { useEffect } from 'react'; +import React, { useEffect } from 'react'; import 'react-native-reanimated'; import { useColorScheme } from '@/components/useColorScheme'; +import { initializeLanguage } from '@/i18n'; export { // Catch any errors thrown by the Layout component. @@ -26,6 +27,8 @@ export default function RootLayout() { SpaceMono: require('../assets/fonts/SpaceMono-Regular.ttf'), ...FontAwesome.font, }); + const [langLoaded, setLangLoaded] = React.useState(false); + // Expo Router uses Error Boundaries to catch errors in the navigation tree. useEffect(() => { @@ -33,12 +36,18 @@ export default function RootLayout() { }, [error]); useEffect(() => { - if (loaded) { + initializeLanguage().then(() => { + setLangLoaded(true); + }); + }, []); + + useEffect(() => { + if (loaded && langLoaded) { SplashScreen.hideAsync(); } - }, [loaded]); + }, [loaded, langLoaded]); - if (!loaded) { + if (!loaded || !langLoaded) { return null; } diff --git a/components/FeatureCard.tsx b/components/FeatureCard.tsx index 5bcb3fc..2f76abd 100644 --- a/components/FeatureCard.tsx +++ b/components/FeatureCard.tsx @@ -2,6 +2,7 @@ import React from 'react'; import { View, Text, StyleSheet } from 'react-native'; import Colors from '@/constants/Colors'; import { FontAwesome } from '@expo/vector-icons'; +import i18n from '@/i18n'; type FeatureCardProps = { title: string; @@ -16,7 +17,7 @@ export default function FeatureCard({ title, description, isNew = false }: Featu {isNew && ( - New Experience + {i18n.t('newExperience')} )} diff --git a/components/PrayerTimeCard.tsx b/components/PrayerTimeCard.tsx index 4b660f5..56f1507 100644 --- a/components/PrayerTimeCard.tsx +++ b/components/PrayerTimeCard.tsx @@ -3,6 +3,7 @@ import { View, Text, StyleSheet, ImageBackground, Pressable } from 'react-native import Colors from '@/constants/Colors'; import MaterialCommunityIcons from 'react-native-vector-icons/MaterialCommunityIcons'; import prayerTimeCalculator, { cities } from '@/utils/prayerTimeCalculator'; +import i18n from '@/i18n'; type Prayer = { name: string; @@ -97,7 +98,7 @@ export default function PrayerTimeCard() { - {nextPrayer ? `Left on ${nextPrayer.name} prayer` : 'Prayer Times'} + {nextPrayer ? i18n.t('leftOnPrayer', { prayerName: nextPrayer.name }) : i18n.t('prayerTimes')} {remainingTime} diff --git a/components/ServicesGrid.tsx b/components/ServicesGrid.tsx index 326bc59..b4d1e47 100644 --- a/components/ServicesGrid.tsx +++ b/components/ServicesGrid.tsx @@ -2,11 +2,12 @@ import React from 'react'; import { View, Text, StyleSheet } from 'react-native'; import Colors from '@/constants/Colors'; import MaterialCommunityIcons from 'react-native-vector-icons/MaterialCommunityIcons'; +import i18n from '@/i18n'; const services = [ - { name: 'Qur\'an', icon: 'book-open-variant' }, - { name: 'Hadith', icon: 'book-open-page-variant' }, - { name: 'Du\'a', icon: 'human-greeting' }, + { name: 'quran', icon: 'book-open-variant' }, + { name: 'hadith', icon: 'book-open-page-variant' }, + { name: 'dua', icon: 'human-greeting' }, ]; export default function ServicesGrid() { @@ -14,14 +15,14 @@ export default function ServicesGrid() { return ( - Services to Enrich Your Spiritual Experience + {i18n.t('servicesToEnrich')} {services.map((service, index) => ( - {service.name} + {i18n.t(service.name)} ))} diff --git a/i18n.ts b/i18n.ts new file mode 100644 index 0000000..58c7728 --- /dev/null +++ b/i18n.ts @@ -0,0 +1,29 @@ +import { I18n } from 'i18n-js'; +import * as Localization from 'expo-localization'; +import AsyncStorage from '@react-native-async-storage/async-storage'; + +import en from './locales/en.json'; +import tk from './locales/tk.json'; +import ru from './locales/ru.json'; + +const i18n = new I18n({ + en, + tk, + ru, +}); + +i18n.enableFallback = true; + +// Function to initialize the language +export const initializeLanguage = async () => { + const savedLanguage = await AsyncStorage.getItem('user-language'); + if (savedLanguage) { + i18n.locale = savedLanguage; + } else { + // If no language is saved, detect from device and default to Turkmen + const userLanguageCode = Localization.getLocales()[0]?.languageCode; + i18n.locale = ['en', 'tk', 'ru'].includes(userLanguageCode || '') ? userLanguageCode! : 'tk'; + } +}; + +export default i18n; diff --git a/locales/en.json b/locales/en.json new file mode 100644 index 0000000..beef237 --- /dev/null +++ b/locales/en.json @@ -0,0 +1,26 @@ +{ + "home": "Home", + "services": "Services", + "yourJourneyToHajj": "Your Journey to Hajj", + "hajjEssentials": "Everything you need for Hajj essentials.", + "umrah": "Umrah", + "bookPermit": "Book Permit", + "nobleRawdah": "Noble Rawdah", + "newExperience": "New Experience", + "prayerTimes": "Prayer Times", + "leftOnPrayer": "Left on {{prayerName}} prayer", + "servicesToEnrich": "Services to Enrich Your Spiritual Experience", + "quran": "Qur'an", + "hadith": "Hadith", + "dua": "Du'a", + "adhkar": "Adhkar", + "hisnAlMuslim": "Hisn Al-Muslim", + "morningEveningThikr": "Thikr said in the morning and evening", + "beforeSleepingThikr": "Thikr before sleeping", + "afterSalamThikr": "Thikr after salam", + "breakingFastSupplication": "Upon breaking fast", + "fastingPersonSupplication": "Supplication said by one fasting when presented with food and does not break his fast", + "insultedWhileFasting": "When insulted while fasting", + "seeingFruitSupplication": "Supplication upon seeing the early or premature fruit", + "sneezingSupplication": "Supplication upon sneezing" +} diff --git a/locales/ru.json b/locales/ru.json new file mode 100644 index 0000000..28608fa --- /dev/null +++ b/locales/ru.json @@ -0,0 +1,26 @@ +{ + "home": "Главная", + "services": "Услуги", + "yourJourneyToHajj": "Ваше путешествие в Хадж", + "hajjEssentials": "Все, что вам нужно для Хаджа.", + "umrah": "Умра", + "bookPermit": "Забронировать разрешение", + "nobleRawdah": "Благородная Равда", + "newExperience": "Новый опыт", + "prayerTimes": "Время молитв", + "leftOnPrayer": "Осталось до молитвы {{prayerName}}", + "servicesToEnrich": "Услуги для обогащения вашего духовного опыта", + "quran": "Коран", + "hadith": "Хадисы", + "dua": "Дуа", + "adhkar": "Азкар", + "hisnAlMuslim": "Крепость мусульманина", + "morningEveningThikr": "Зикр, читаемый утром и вечером", + "beforeSleepingThikr": "Зикр перед сном", + "afterSalamThikr": "Зикр после салама", + "breakingFastSupplication": "При разговении", + "fastingPersonSupplication": "Мольба, произносимая постящимся, когда ему преподносят еду, и он не прерывает свой пост", + "insultedWhileFasting": "Когда оскорбляют во время поста", + "seeingFruitSupplication": "Мольба при виде ранних или незрелых плодов", + "sneezingSupplication": "Мольба при чихании" +} diff --git a/locales/tk.json b/locales/tk.json new file mode 100644 index 0000000..9f875ec --- /dev/null +++ b/locales/tk.json @@ -0,0 +1,26 @@ +{ + "home": "Baş sahypa", + "services": "Hyzmatlar", + "yourJourneyToHajj": "Haj syýahatyňyz", + "hajjEssentials": "Haj üçin zerur zatlar.", + "umrah": "Umra", + "bookPermit": "Rugsat kitaby", + "nobleRawdah": "Asylly Rawdah", + "newExperience": "Täze tejribe", + "prayerTimes": "Namaz wagtlary", + "leftOnPrayer": "{{prayerName}} namazyna çenli galdy", + "servicesToEnrich": "Ruhy tejribäňizi baýlaşdyrmak üçin hyzmatlar", + "quran": "Kuran", + "hadith": "Hadys", + "dua": "Doga", + "adhkar": "Zikir", + "hisnAlMuslim": "Musulmanyň goragy", + "morningEveningThikr": "Ertir we agşam aýdylýan zikir", + "beforeSleepingThikr": "Uklamazdan öňki zikir", + "afterSalamThikr": "Salamdan soňky zikir", + "breakingFastSupplication": "Agzyňy açanyňda", + "fastingPersonSupplication": "Agzy bekli adama iýmit hödürlenende we orazasyny bozmasa aýdylýan doga", + "insultedWhileFasting": "Orazaly wagtyň kemsidilende", + "seeingFruitSupplication": "Irki ýa-da bişmedik miwäni göreniňde aýdylýan doga", + "sneezingSupplication": "Asgyranyňda aýdylýan doga" +} diff --git a/package-lock.json b/package-lock.json index 2ca7522..61c2cb7 100644 --- a/package-lock.json +++ b/package-lock.json @@ -9,19 +9,24 @@ "version": "1.0.0", "dependencies": { "@expo/vector-icons": "^14.1.0", + "@react-native-async-storage/async-storage": "2.1.2", "@react-navigation/native": "^7.1.6", "expo": "~53.0.20", "expo-font": "~13.3.2", "expo-linking": "~7.1.7", + "expo-localization": "^16.1.6", "expo-router": "~5.1.4", "expo-splash-screen": "~0.30.10", "expo-status-bar": "~2.2.3", "expo-system-ui": "~5.0.10", + "expo-updates": "~0.28.17", "expo-web-browser": "~14.2.0", "hijri-date": "^0.2.2", + "i18n-js": "^4.5.1", "react": "19.0.0", "react-dom": "19.0.0", "react-native": "0.79.5", + "react-native-picker-select": "^9.3.1", "react-native-reanimated": "~3.17.4", "react-native-safe-area-context": "5.4.0", "react-native-screens": "~4.11.1", @@ -2454,6 +2459,30 @@ } } }, + "node_modules/@react-native-async-storage/async-storage": { + "version": "2.1.2", + "resolved": "https://registry.npmjs.org/@react-native-async-storage/async-storage/-/async-storage-2.1.2.tgz", + "integrity": "sha512-dvlNq4AlGWC+ehtH12p65+17V0Dx7IecOWl6WanF2ja38O1Dcjjvn7jVzkUHJ5oWkQBlyASurTPlTHgKXyYiow==", + "dependencies": { + "merge-options": "^3.0.4" + }, + "peerDependencies": { + "react-native": "^0.0.0-0 || >=0.65 <1.0" + } + }, + "node_modules/@react-native-picker/picker": { + "version": "2.11.1", + "resolved": "https://registry.npmjs.org/@react-native-picker/picker/-/picker-2.11.1.tgz", + "integrity": "sha512-ThklnkK4fV3yynnIIRBkxxjxR4IFbdMNJVF6tlLdOJ/zEFUEFUEdXY0KmH0iYzMwY8W4/InWsLiA7AkpAbnexA==", + "peer": true, + "workspaces": [ + "example" + ], + "peerDependencies": { + "react": "*", + "react-native": "*" + } + }, "node_modules/@react-native/assets-registry": { "version": "0.79.5", "resolved": "https://registry.npmjs.org/@react-native/assets-registry/-/assets-registry-0.79.5.tgz", @@ -3704,6 +3733,14 @@ "node": ">=0.6" } }, + "node_modules/bignumber.js": { + "version": "9.3.1", + "resolved": "https://registry.npmjs.org/bignumber.js/-/bignumber.js-9.3.1.tgz", + "integrity": "sha512-Ko0uX15oIUS7wJ3Rb30Fs6SkVbLmPBAKdlm7q9+ak9bbIeFf0MwuBsQV6z7+X768/cHsfg+WlysDWJcmthjsjQ==", + "engines": { + "node": "*" + } + }, "node_modules/bplist-creator": { "version": "0.1.0", "resolved": "https://registry.npmjs.org/bplist-creator/-/bplist-creator-0.1.0.tgz", @@ -4983,6 +5020,11 @@ "react-native": "*" } }, + "node_modules/expo-eas-client": { + "version": "0.14.4", + "resolved": "https://registry.npmjs.org/expo-eas-client/-/expo-eas-client-0.14.4.tgz", + "integrity": "sha512-TSL1BbBFIuXchJmPgbPnB7cGpOOuSGJcQ/L7gij/+zPjExwvKm5ckA5dlSulwoFhH8zQt4vb7bfISPSAWQVWBw==" + }, "node_modules/expo-file-system": { "version": "18.1.11", "resolved": "https://registry.npmjs.org/expo-file-system/-/expo-file-system-18.1.11.tgz", @@ -5004,6 +5046,11 @@ "react": "*" } }, + "node_modules/expo-json-utils": { + "version": "0.15.0", + "resolved": "https://registry.npmjs.org/expo-json-utils/-/expo-json-utils-0.15.0.tgz", + "integrity": "sha512-duRT6oGl80IDzH2LD2yEFWNwGIC2WkozsB6HF3cDYNoNNdUvFk6uN3YiwsTsqVM/D0z6LEAQ01/SlYvN+Fw0JQ==" + }, "node_modules/expo-keep-awake": { "version": "14.1.4", "resolved": "https://registry.npmjs.org/expo-keep-awake/-/expo-keep-awake-14.1.4.tgz", @@ -5026,6 +5073,30 @@ "react-native": "*" } }, + "node_modules/expo-localization": { + "version": "16.1.6", + "resolved": "https://registry.npmjs.org/expo-localization/-/expo-localization-16.1.6.tgz", + "integrity": "sha512-v4HwNzs8QvyKHwl40MvETNEKr77v1o9/eVC8WCBY++DIlBAvonHyJe2R9CfqpZbC4Tlpl7XV+07nLXc8O5PQsA==", + "dependencies": { + "rtl-detect": "^1.0.2" + }, + "peerDependencies": { + "expo": "*", + "react": "*" + } + }, + "node_modules/expo-manifests": { + "version": "0.16.6", + "resolved": "https://registry.npmjs.org/expo-manifests/-/expo-manifests-0.16.6.tgz", + "integrity": "sha512-1A+do6/mLUWF9xd3uCrlXr9QFDbjbfqAYmUy8UDLOjof1lMrOhyeC4Yi6WexA/A8dhZEpIxSMCKfn7G4aHAh4w==", + "dependencies": { + "@expo/config": "~11.0.12", + "expo-json-utils": "~0.15.0" + }, + "peerDependencies": { + "expo": "*" + } + }, "node_modules/expo-modules-autolinking": { "version": "2.1.14", "resolved": "https://registry.npmjs.org/expo-modules-autolinking/-/expo-modules-autolinking-2.1.14.tgz", @@ -5127,6 +5198,11 @@ "react-native": "*" } }, + "node_modules/expo-structured-headers": { + "version": "4.1.0", + "resolved": "https://registry.npmjs.org/expo-structured-headers/-/expo-structured-headers-4.1.0.tgz", + "integrity": "sha512-2X+aUNzC/qaw7/WyUhrVHNDB0uQ5rE12XA2H/rJXaAiYQSuOeU90ladaN0IJYV9I2XlhYrjXLktLXWbO7zgbag==" + }, "node_modules/expo-system-ui": { "version": "5.0.10", "resolved": "https://registry.npmjs.org/expo-system-ui/-/expo-system-ui-5.0.10.tgz", @@ -5146,6 +5222,46 @@ } } }, + "node_modules/expo-updates": { + "version": "0.28.17", + "resolved": "https://registry.npmjs.org/expo-updates/-/expo-updates-0.28.17.tgz", + "integrity": "sha512-OiKDrKk6EoBRP9AoK7/4tyj9lVtHw2IfaETIFeUCHMgx5xjgKGX/jjSwqhk8N9BJgLDIy0oD0Sb0MaEbSBb3lg==", + "dependencies": { + "@expo/code-signing-certificates": "0.0.5", + "@expo/config": "~11.0.13", + "@expo/config-plugins": "~10.1.2", + "@expo/spawn-async": "^1.7.2", + "arg": "4.1.0", + "chalk": "^4.1.2", + "expo-eas-client": "~0.14.4", + "expo-manifests": "~0.16.6", + "expo-structured-headers": "~4.1.0", + "expo-updates-interface": "~1.1.0", + "glob": "^10.4.2", + "ignore": "^5.3.1", + "resolve-from": "^5.0.0" + }, + "bin": { + "expo-updates": "bin/cli.js" + }, + "peerDependencies": { + "expo": "*", + "react": "*" + } + }, + "node_modules/expo-updates-interface": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/expo-updates-interface/-/expo-updates-interface-1.1.0.tgz", + "integrity": "sha512-DeB+fRe0hUDPZhpJ4X4bFMAItatFBUPjw/TVSbJsaf3Exeami+2qbbJhWkcTMoYHOB73nOIcaYcWXYJnCJXO0w==", + "peerDependencies": { + "expo": "*" + } + }, + "node_modules/expo-updates/node_modules/arg": { + "version": "4.1.0", + "resolved": "https://registry.npmjs.org/arg/-/arg-4.1.0.tgz", + "integrity": "sha512-ZWc51jO3qegGkVh8Hwpv636EkbesNV5ZNQPCtRa+0qytRYPEs9IYT9qITY9buezqUH5uqyzlWLcufrzU2rffdg==" + }, "node_modules/expo-web-browser": { "version": "14.2.0", "resolved": "https://registry.npmjs.org/expo-web-browser/-/expo-web-browser-14.2.0.tgz", @@ -5653,6 +5769,16 @@ "resolved": "https://registry.npmjs.org/hyphenate-style-name/-/hyphenate-style-name-1.1.0.tgz", "integrity": "sha512-WDC/ui2VVRrz3jOVi+XtjqkDjiVjTtFaAGiW37k6b+ohyQ5wYDOGkvCZa8+H0nx3gyvv0+BST9xuOgIyGQ00gw==" }, + "node_modules/i18n-js": { + "version": "4.5.1", + "resolved": "https://registry.npmjs.org/i18n-js/-/i18n-js-4.5.1.tgz", + "integrity": "sha512-n7jojFj1WC0tztgr0I8jqTXuIlY1xNzXnC3mjKX/YjJhimdM+jXM8vOmn9d3xQFNC6qDHJ4ovhdrGXrRXLIGkA==", + "dependencies": { + "bignumber.js": "*", + "lodash": "*", + "make-plural": "*" + } + }, "node_modules/iconv-lite": { "version": "0.6.3", "resolved": "https://registry.npmjs.org/iconv-lite/-/iconv-lite-0.6.3.tgz", @@ -5855,6 +5981,14 @@ "node": ">=0.12.0" } }, + "node_modules/is-plain-obj": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/is-plain-obj/-/is-plain-obj-2.1.0.tgz", + "integrity": "sha512-YWnfyRwxL/+SsrWYfOpUtz5b3YD+nyfkHvjbcanzk8zgyO4ASD67uVMRt8k5bM4lLMDnXfriRhOpemw+NfT1eA==", + "engines": { + "node": ">=8" + } + }, "node_modules/is-potential-custom-element-name": { "version": "1.0.1", "resolved": "https://registry.npmjs.org/is-potential-custom-element-name/-/is-potential-custom-element-name-1.0.1.tgz", @@ -7199,14 +7333,24 @@ "node_modules/lodash": { "version": "4.17.21", "resolved": "https://registry.npmjs.org/lodash/-/lodash-4.17.21.tgz", - "integrity": "sha512-v2kDEe57lecTulaDIuNTPy3Ry4gLGJ6Z1O3vE1krgXZNrsQ+LFTGHVxVjcXPs17LhbZVGedAJv8XZ1tvj5FvSg==", - "dev": true + "integrity": "sha512-v2kDEe57lecTulaDIuNTPy3Ry4gLGJ6Z1O3vE1krgXZNrsQ+LFTGHVxVjcXPs17LhbZVGedAJv8XZ1tvj5FvSg==" }, "node_modules/lodash.debounce": { "version": "4.0.8", "resolved": "https://registry.npmjs.org/lodash.debounce/-/lodash.debounce-4.0.8.tgz", "integrity": "sha512-FT1yDzDYEoYWhnSGnpE/4Kj1fLZkDFyqRb7fNt6FdYOSxlUWAtp42Eh6Wb0rGIv/m9Bgo7x4GhQbm5Ys4SG5ow==" }, + "node_modules/lodash.isequal": { + "version": "4.5.0", + "resolved": "https://registry.npmjs.org/lodash.isequal/-/lodash.isequal-4.5.0.tgz", + "integrity": "sha512-pDo3lu8Jhfjqls6GkMgpahsF9kCyayhgykjyLMNFTKWrpVdAQtYyB4muAMWozBB4ig/dtWAmsMxLEI8wuz+DYQ==", + "deprecated": "This package is deprecated. Use require('node:util').isDeepStrictEqual instead." + }, + "node_modules/lodash.isobject": { + "version": "3.0.2", + "resolved": "https://registry.npmjs.org/lodash.isobject/-/lodash.isobject-3.0.2.tgz", + "integrity": "sha512-3/Qptq2vr7WeJbB4KHUSKlq8Pl7ASXi3UG6CMbBm8WRtXi8+GHm7mKaU3urfpSEzWe2wCIChs6/sdocUsTKJiA==" + }, "node_modules/lodash.throttle": { "version": "4.1.1", "resolved": "https://registry.npmjs.org/lodash.throttle/-/lodash.throttle-4.1.1.tgz", @@ -7333,6 +7477,11 @@ "node": ">=10" } }, + "node_modules/make-plural": { + "version": "7.4.0", + "resolved": "https://registry.npmjs.org/make-plural/-/make-plural-7.4.0.tgz", + "integrity": "sha512-4/gC9KVNTV6pvYg2gFeQYTW3mWaoJt7WZE5vrp1KnQDgW92JtYZnzmZT81oj/dUTqAIu0ufI2x3dkgu3bB1tYg==" + }, "node_modules/makeerror": { "version": "1.0.12", "resolved": "https://registry.npmjs.org/makeerror/-/makeerror-1.0.12.tgz", @@ -7360,6 +7509,17 @@ "resolved": "https://registry.npmjs.org/memoize-one/-/memoize-one-5.2.1.tgz", "integrity": "sha512-zYiwtZUcYyXKo/np96AGZAckk+FWWsUdJ3cHGGmld7+AhvcWmQyGCYUh1hc4Q/pkOhb65dQR/pqCyK0cOaHz4Q==" }, + "node_modules/merge-options": { + "version": "3.0.4", + "resolved": "https://registry.npmjs.org/merge-options/-/merge-options-3.0.4.tgz", + "integrity": "sha512-2Sug1+knBjkaMsMgf1ctR1Ujx+Ayku4EdJN4Z+C2+JzoeF7A3OZ9KM2GY0CpQS51NR61LTurMJrRKPhSs3ZRTQ==", + "dependencies": { + "is-plain-obj": "^2.1.0" + }, + "engines": { + "node": ">=10" + } + }, "node_modules/merge-stream": { "version": "2.0.0", "resolved": "https://registry.npmjs.org/merge-stream/-/merge-stream-2.0.0.tgz", @@ -8812,6 +8972,18 @@ "react-native": "*" } }, + "node_modules/react-native-picker-select": { + "version": "9.3.1", + "resolved": "https://registry.npmjs.org/react-native-picker-select/-/react-native-picker-select-9.3.1.tgz", + "integrity": "sha512-o621HcsKJfJkpYeP/PZQiZTKbf8W7FT08niLFL0v1pGkIQyak5IfzfinV2t+/l1vktGwAH2Tt29LrP/Hc5fk3A==", + "dependencies": { + "lodash.isequal": "^4.5.0", + "lodash.isobject": "^3.0.2" + }, + "peerDependencies": { + "@react-native-picker/picker": "^2.4.0" + } + }, "node_modules/react-native-reanimated": { "version": "3.17.5", "resolved": "https://registry.npmjs.org/react-native-reanimated/-/react-native-reanimated-3.17.5.tgz", @@ -9356,6 +9528,11 @@ "node": "*" } }, + "node_modules/rtl-detect": { + "version": "1.1.2", + "resolved": "https://registry.npmjs.org/rtl-detect/-/rtl-detect-1.1.2.tgz", + "integrity": "sha512-PGMBq03+TTG/p/cRB7HCLKJ1MgDIi07+QU1faSjiYRfmY5UsAttV9Hs08jDAHVwcOwmVLcSJkpwyfXszVjWfIQ==" + }, "node_modules/safe-buffer": { "version": "5.2.1", "resolved": "https://registry.npmjs.org/safe-buffer/-/safe-buffer-5.2.1.tgz", diff --git a/package.json b/package.json index f2b76a3..b3c5407 100644 --- a/package.json +++ b/package.json @@ -14,19 +14,24 @@ }, "dependencies": { "@expo/vector-icons": "^14.1.0", + "@react-native-async-storage/async-storage": "2.1.2", "@react-navigation/native": "^7.1.6", "expo": "~53.0.20", "expo-font": "~13.3.2", "expo-linking": "~7.1.7", + "expo-localization": "^16.1.6", "expo-router": "~5.1.4", "expo-splash-screen": "~0.30.10", "expo-status-bar": "~2.2.3", "expo-system-ui": "~5.0.10", + "expo-updates": "~0.28.17", "expo-web-browser": "~14.2.0", "hijri-date": "^0.2.2", + "i18n-js": "^4.5.1", "react": "19.0.0", "react-dom": "19.0.0", "react-native": "0.79.5", + "react-native-picker-select": "^9.3.1", "react-native-reanimated": "~3.17.4", "react-native-safe-area-context": "5.4.0", "react-native-screens": "~4.11.1",