Add multilanguage support

This commit is contained in:
Maarten 2024-08-12 16:48:41 +02:00
parent f8cbcb2908
commit fc53bb14a0
15 changed files with 489 additions and 92 deletions

View file

@ -4,8 +4,7 @@ import {
SafeAreaView,
StatusBar,
Image,
TouchableOpacity,
View,
TouchableOpacity
} from 'react-native';
import React, { useState, useEffect, useRef } from 'react';
@ -13,6 +12,7 @@ import { router } from 'expo-router';
import type { AutocompleteDropdownRef } from 'react-native-autocomplete-dropdown'
import { AutocompleteDropdown } from 'react-native-autocomplete-dropdown';
import Modal from "react-native-modal";
import { useTranslation } from 'react-i18next';
import { ThemedText } from '@/lib/components/ThemedText';
import { ThemedView } from '@/lib/components/ThemedView';
@ -25,6 +25,7 @@ import { setViewCategory } from '@/lib/store/dataStore';
export default function ExploreScreen() {
const colorScheme = useColorScheme() ?? 'light';
const { t } = useTranslation();
const [ categories, setCategories ] = useState( [] );
const [ types, setTypes ] = useState( [] );
const [ activeCategory, setActiveCategory ] = useState<any | null>( null );
@ -78,29 +79,29 @@ export default function ExploreScreen() {
<ThemedView>
<ScrollView style={styles.container}>
<ThemedView style={styles.titleContainer}>
<ThemedText type="title">Wat moet waar?</ThemedText>
<ThemedText type="title">{t( "what-where" )}</ThemedText>
</ThemedView>
<ThemedView style={styles.titleContainer}>
<ThemedText type="title" style={{ color: Colors[ colorScheme ].tint }}>en waarom?</ThemedText>
<ThemedText type="title" style={{ color: Colors[ colorScheme ].tint }}>{t( "what-why" )}</ThemedText>
</ThemedView>
<ThemedView style={styles.searchContainer}>
<ThemedText type="defaultSemiBold">Wat wilt u scheiden?</ThemedText>
<ThemedText type="defaultSemiBold">{t( "what-separate" )}</ThemedText>
<AutocompleteDropdown
ref={searchRef}
controller={controller => {
dropdownController.current = controller
}}
textInputProps={{
placeholder: 'Wat wilt u scheiden?',
placeholder: t( "what-separate" ),
autoCorrect: false,
autoCapitalize: 'none',
}}
clearOnFocus={false}
closeOnBlur={false}
closeOnSubmit={false}
emptyResultText={'Niks gevonden'}
emptyResultText={t( "nothing-found" )}
onSelectItem={selectItem}
dataSet={categories}
showClear={false}
@ -108,7 +109,7 @@ export default function ExploreScreen() {
</ThemedView>
<ThemedView style={styles.categoriesContainer}>
<ThemedText type="defaultSemiBold">Of kies een categorie:</ThemedText>
<ThemedText type="defaultSemiBold">{t( "choose-category" )}:</ThemedText>
</ThemedView>
<ThemedView style={styles.listContainer}>
@ -132,7 +133,7 @@ export default function ExploreScreen() {
</ScrollView>
</ThemedView>
<Modal isVisible={activeCategory !== null} useNativeDriverForBackdrop={true}>
<Modal isVisible={activeCategory !== null} useNativeDriverForBackdrop={true}>
<ThemedView style={{ ...styles.modalView, backgroundColor: Colors[ colorScheme ].background }}>
<ThemedText type="subtitle" style={{ marginBottom: 30, textAlign: 'center' }}>{activeCategory?.name}</ThemedText>
@ -146,14 +147,14 @@ export default function ExploreScreen() {
}
<ThemedView style={{ alignItems: 'center' }}>
<ThemedText type="defaultSemiBold">Beschrijving:</ThemedText>
<ThemedText type="defaultSemiBold">{t( "description" )}:</ThemedText>
<ThemedText>{activeCategory?.description}</ThemedText>
</ThemedView>
<TouchableOpacity
onPress={() => setActiveCategory( null )}
style={{ ...styles.modalClose, backgroundColor: Colors[ colorScheme ].tint }}>
<ThemedText style={{ color: '#fff' }}>Sluiten</ThemedText>
<ThemedText style={{ color: '#fff' }}>{t( "close" )}</ThemedText>
</TouchableOpacity>
</ThemedView>
</Modal>

View file

@ -5,6 +5,7 @@ import CalendarPicker from 'react-native-calendar-picker';
import { useIsFocused } from '@react-navigation/core';
import { useSelector } from 'react-redux';
import { LogLevel, OneSignal } from 'react-native-onesignal';
import { useTranslation } from 'react-i18next';
import { ThemedText } from '@/lib/components/ThemedText';
import { ThemedView } from '@/lib/components/ThemedView';
@ -19,9 +20,10 @@ import { ThemedIcon } from '@/lib/components/ThemedIcon';
export default function HomeScreen() {
const colorScheme = useColorScheme() ?? 'light';
const isFocused = useIsFocused();
const { t } = useTranslation();
const session = useSelector( (state: any) => state.data.session );
const reloadCalendar = useSelector( (state: any) => state.data.reloadCalendar );
const { token, isLoading } = useToken();
const { token } = useToken();
const [ name, setName ] = useState( ' ' ); // Default empty space to prevent layout shifting
const [ dates, setDates ] = useState<any | null>( [] );
const [ types, setTypes ] = useState<any | null>( [] );
@ -68,6 +70,21 @@ export default function HomeScreen() {
}
}, [ reloadCalendar, isFocused ] );
const getGreeting = () => {
const myDate = new Date();
const hours = myDate.getHours();
if (hours < 12) {
return t( "greeting.morning" );
} else if (hours >= 12 && hours <= 17) {
return t( "greeting.afternoon" );
} else if (hours >= 17 && hours <= 24) {
return t( "greeting.evening" );
}
return t( "greeting.hello" );
}
// Load calendar data
const loadCalendar = () => {
Request.post( 'calendar', { token: token } ).then( (response) => {
@ -116,26 +133,34 @@ export default function HomeScreen() {
nextComponent={
<ThemedIcon size={28} name="chevron-forward"/>
}
weekdays={[ "Ma", "Di", "Woe", "Do", "Vrij", "Zat", "Zo" ]}
weekdays={[
t( "days.mon" ),
t( "days.tue" ),
t( "days.wed" ),
t( "days.thu" ),
t( "days.fri" ),
t( "days.sat" ),
t( "days.sun" ),
]}
months={[
"Januari",
"Februari",
"Maart",
"April",
"Mei",
"Juni",
"Juli",
"Augustus",
"September",
"Oktober",
"November",
"December",
t( "months.january" ),
t( "months.february" ),
t( "months.march" ),
t( "months.april" ),
t( "months.may" ),
t( "months.june" ),
t( "months.july" ),
t( "months.august" ),
t( "months.september" ),
t( "months.october" ),
t( "months.november" ),
t( "months.december" ),
]}
/>
</ThemedView>
<ThemedView style={styles.legendaContainer}>
<ThemedText type='subtitle'>Legenda:</ThemedText>
<ThemedText type='subtitle'>{t( "legenda" )}:</ThemedText>
<List
data={types}
viewStyle={styles.legendaList}
@ -153,21 +178,6 @@ export default function HomeScreen() {
);
}
function getGreeting() {
const myDate = new Date();
const hours = myDate.getHours();
if (hours < 12) {
return 'Goedemorgen';
} else if (hours >= 12 && hours <= 17) {
return 'Goedemiddag';
} else if (hours >= 17 && hours <= 24) {
return 'Goedenavond';
}
return 'Hallo';
}
const styles = StyleSheet.create( {
container: {
padding: 25,

View file

@ -3,6 +3,7 @@ import { Image, SafeAreaView, ScrollView, StatusBar, StyleSheet, Switch, View, D
import Mapbox, { Callout, Camera, MapView, PointAnnotation } from "@rnmapbox/maps";
import { useSelector } from 'react-redux';
import { useIsFocused } from '@react-navigation/core';
import { useTranslation } from 'react-i18next';
Mapbox.setAccessToken( "pk.eyJ1IjoibWFhcnRlbnZyOTgiLCJhIjoiY2x6ZDFqMGp1MGVyejJrczhqcXpvYm9iYiJ9.XvYcL62dWiJQiFmG6mOoug" );
@ -17,6 +18,7 @@ export default function MapScreen() {
const colorScheme = useColorScheme() ?? 'light';
const isFocused = useIsFocused();
const session = useSelector((state: any) => state.data.session);
const { t } = useTranslation();
const [ types, setTypes ] = useState<any>( [] );
const [ coordinates, setCoordinates ] = useState<any>( [] );
@ -85,11 +87,11 @@ export default function MapScreen() {
<ThemedView style={styles.container}>
<ThemedView>
<ThemedView style={styles.titleContainer}>
<ThemedText type="title">Afvalcontainers</ThemedText>
<ThemedText type="title">{t( "garbage-bins" )}</ThemedText>
</ThemedView>
<ThemedView style={styles.titleContainer}>
<ThemedText type="title" style={{ color: Colors[ colorScheme ].tint }}>in de buurt</ThemedText>
<ThemedText type="title" style={{ color: Colors[ colorScheme ].tint }}>{t( "nearby" )}</ThemedText>
</ThemedView>
<ThemedView style={styles.mapContainer}>

View file

@ -1,14 +1,17 @@
import React, { useEffect, useState } from 'react';
import React, { useEffect, useRef, useState } from 'react';
import {
StyleSheet,
ScrollView,
SafeAreaView,
StatusBar,
TouchableOpacity,
Alert,
Alert, Image,
} from 'react-native';
import { useRouter } from 'expo-router';
import { useSelector } from 'react-redux';
import { useTranslation } from 'react-i18next';
import { BottomSheet, BottomSheetRefType } from 'react-native-select-bottom-list';
import { ThemedText } from '@/lib/components/ThemedText';
import { ThemedView } from '@/lib/components/ThemedView';
@ -21,10 +24,12 @@ import CustomModal from '@/lib/components/EditModal';
import { store } from '@/lib/store/store';
import { setSession, setReloadCalendar } from '@/lib/store/dataStore';
import { ThemedIcon } from '@/lib/components/ThemedIcon';
import List from '@/lib/components/List';
export default function SettingsScreen() {
const colorScheme = useColorScheme() ?? 'light';
const { token, setToken } = useToken();
const { t, i18n } = useTranslation();
const session = useSelector( (state: any) => state.data.session );
const router = useRouter();
@ -39,6 +44,10 @@ export default function SettingsScreen() {
const [ city, setCity ] = React.useState( '3' );
const [ addressModalVisible, setAddressModalVisible ] = useState( false );
// Language
const [ language, setLanguage ] = useState( 'nl' );
const sheetRef = useRef<BottomSheetRefType>( null );
useEffect( () => {
setSessionData( session );
}, [ session ] );
@ -53,6 +62,9 @@ export default function SettingsScreen() {
setHouseNumber( session.address.houseNumber );
setStreet( session.address.street );
setCity( session.address.city );
// Language
setLanguage(session.language);
}
// Handle save settings
@ -75,6 +87,10 @@ export default function SettingsScreen() {
addressChanged = true;
}
if (inputValues.language) {
postData[ 'language' ] = inputValues.language;
}
Request.post( 'sessions/update', postData )
.then( (response) => {
if (response.success) {
@ -84,32 +100,46 @@ export default function SettingsScreen() {
store.dispatch( setSession( response.session ) )
store.dispatch( setReloadCalendar( addressChanged ) )
Message.success( 'Opgeslagen!' )
Message.success( t( "saved" ) )
} else {
Message.error( response.message );
}
} );
};
const openLanguageSelect = () => {
sheetRef.current?.open();
}
const changeLanguage = (lang: string) => {
sheetRef.current?.close();
i18n.changeLanguage( lang ).then( () => {
setLanguage(lang);
handleSave( { language: lang } );
} );
}
// Remove session data and logout
const logout = () => {
Alert.alert( 'Uitloggen', 'Weet je het zeker?', [
Alert.alert( t( "logout" ), t( "are-you-sure" ), [
{
text: 'Annuleren',
text: t( "cancel" ),
style: 'cancel',
},
{
text: 'Ja',
text: t( "ja" ),
onPress: () => {
Request.post( 'sessions/delete' ).then( (response) => {
if (!response.success) {
Message.success( 'Je bent uitgelogd' );
Message.success( t( "logged-out" ) );
setToken( null );
router.replace( '/(onboarding)/start' );
} else {
Message.error( 'Er is iets mis gegaan. Probeer het later opnieuw!' )
Message.error( t( "something-went-wrong" ) )
}
} )
}
@ -122,14 +152,14 @@ export default function SettingsScreen() {
<ThemedView style={styles.container}>
<ScrollView>
<ThemedView style={styles.titleContainer}>
<ThemedText type="title">Instellingen</ThemedText>
<ThemedText type="title">{t( "settings" )}</ThemedText>
</ThemedView>
<ThemedView style={styles.listContainer}>
<ThemedView style={styles.listItem}>
<ThemedView style={styles.listTitle}>
<ThemedIcon name="person-outline" size={20} style={styles.listIcon}/>
<ThemedText type="defaultSemiBold">Naam</ThemedText>
<ThemedText type="defaultSemiBold">{t( "name" )}</ThemedText>
</ThemedView>
<TouchableOpacity style={styles.listEdit} onPress={() => setNameModalVisible( true )}>
@ -141,7 +171,7 @@ export default function SettingsScreen() {
<ThemedView style={styles.listItem}>
<ThemedView style={styles.listTitle}>
<ThemedIcon size={20} name="trail-sign-outline" style={styles.listIcon}/>
<ThemedText type="defaultSemiBold">Adres</ThemedText>
<ThemedText type="defaultSemiBold">{t( "address" )}</ThemedText>
</ThemedView>
<TouchableOpacity style={styles.listEdit} onPress={() => setAddressModalVisible( true )}>
@ -153,11 +183,23 @@ export default function SettingsScreen() {
<ThemedView style={styles.listItem}>
<ThemedView style={styles.listTitle}>
<ThemedIcon size={20} name="notifications-outline" style={styles.listIcon}/>
<ThemedText type="defaultSemiBold">Notificaties</ThemedText>
<ThemedText type="defaultSemiBold">{t( "notifications" )}</ThemedText>
</ThemedView>
<TouchableOpacity style={styles.listEdit} onPress={() => router.push( '/(settings)/notifications' )}>
<ThemedText style={styles.listEditText}>Wijzigen</ThemedText>
<ThemedText style={styles.listEditText}>{t( "change" )}</ThemedText>
<ThemedIcon size={18} name="chevron-forward" style={styles.listEditIcon}/>
</TouchableOpacity>
</ThemedView>
<ThemedView style={styles.listItem}>
<ThemedView style={styles.listTitle}>
<ThemedIcon size={20} name="language-outline" style={styles.listIcon}/>
<ThemedText type="defaultSemiBold">{t( "language" )}</ThemedText>
</ThemedView>
<TouchableOpacity style={styles.listEdit} onPress={openLanguageSelect}>
<ThemedText style={styles.listEditText}>{t( "languages." + language )}</ThemedText>
<ThemedIcon size={18} name="chevron-forward" style={styles.listEditIcon}/>
</TouchableOpacity>
</ThemedView>
@ -166,7 +208,7 @@ export default function SettingsScreen() {
<ThemedText>
<TouchableOpacity onPress={logout}>
<ThemedText type="defaultSemiBold" style={styles.logout}>
Uitloggen
{t( "logout" )}
</ThemedText>
</TouchableOpacity>
</ThemedText>
@ -174,39 +216,63 @@ export default function SettingsScreen() {
</ThemedView>
<CustomModal
title="Naam wijzigen"
title={t( "modal.name.title" )}
visible={nameModalVisible}
onClose={() => setNameModalVisible( false )}
onSave={handleSave}
fields={[
{
name: 'name',
placeholder: 'Je naam',
placeholder: t( "modal.name.your-name" ),
defaultValue: name,
},
]}
/>
<CustomModal
title="Adres wijzigen"
title={t( "modal.address.title" )}
visible={addressModalVisible}
onClose={() => setAddressModalVisible( false )}
onSave={handleSave}
fields={[
{
name: 'zipcode',
title: 'Postcode',
placeholder: 'Je postcode',
title: t( "modal.address.zipcode" ),
placeholder: t( "modal.address.your-zipcode" ),
defaultValue: zipcode,
},
{
name: 'houseNumber',
title: 'Huisnummer',
placeholder: 'Je huis nummer',
title: t( "modal.address.house-number" ),
placeholder: t( "modal.address.your-house-number" ),
defaultValue: houseNumber,
},
]}
/>
<BottomSheet ref={sheetRef} presentationStyle={'overFullScreen'}>
<ThemedView>
<List
data={[
{
name: t( "languages.nl" ),
key: "nl",
},
{
name: t( "languages.en" ),
key: "en",
}
]}
viewStyle={styles.languagesList}
renderItem={(item: any, index: any) => (
<TouchableOpacity style={styles.languagesListItem} key={index} onPress={() => changeLanguage( item.key )}>
<ThemedText type="defaultSemiBold">{item.name}</ThemedText>
<ThemedIcon name={'chevron-forward'} size={18}/>
</TouchableOpacity>
)}
/>
</ThemedView>
</BottomSheet>
</SafeAreaView>
);
}
@ -262,4 +328,20 @@ const styles = StyleSheet.create( {
logout: {
color: Colors.red,
},
languagesList: {
display: 'flex',
flexDirection: 'column',
padding: 25,
},
languagesListItem: {
display: 'flex',
gap: 8,
flexDirection: 'row',
alignItems: 'center',
justifyContent: 'space-between',
paddingBottom: 15,
marginBottom: 15,
borderBottomWidth: 1,
borderBottomColor: '#f2f2f2',
}
} );