diff --git a/src/common/config.ts b/src/common/config.ts index f7e0c14..83be572 100644 --- a/src/common/config.ts +++ b/src/common/config.ts @@ -2,7 +2,8 @@ const config = { api: 'https://api.voteswiper.org/api', apiVersion: 1, fallbackLocale: 'en', - locales: ['en', 'de', 'fr', 'fi', 'sv'], + locales: ['en', 'de', 'fr', 'fi', 'sv', 'ar'], + rtlLocales: ['ar', 'fa'], storyblokAccessToken: 'b7BTTUOEkSa786viucYnjwtt', }; diff --git a/src/components/ElectionPill/index.tsx b/src/components/ElectionPill/index.tsx index 0e03d89..dfb8c5d 100755 --- a/src/components/ElectionPill/index.tsx +++ b/src/components/ElectionPill/index.tsx @@ -10,6 +10,7 @@ import LinearGradient from 'react-native-linear-gradient'; import {Election} from 'types/api'; import moment from 'util/momentLocale'; import ArrowRightCircle from '../../icons/ArrowRightCircle'; +import rtl from '../../rtl'; import Txt from '../Txt'; import styles from './styles'; @@ -64,7 +65,12 @@ const ElectionPill: React.FC<Props> = ({ </Txt> </View> <View> - <ArrowRightCircle fill="#8186D7" width={20} height={20} /> + <ArrowRightCircle + fill="#8186D7" + width={20} + height={20} + style={rtl.mirror} + /> </View> </LinearGradient> </View> diff --git a/src/components/ResultBar/index.tsx b/src/components/ResultBar/index.tsx index 9d3cc51..4779475 100644 --- a/src/components/ResultBar/index.tsx +++ b/src/components/ResultBar/index.tsx @@ -2,6 +2,7 @@ import React from 'react'; import {GestureResponderEvent, TouchableOpacity, View} from 'react-native'; import Animated, {Easing} from 'react-native-reanimated'; import ChevronRight from '../../icons/ChevronRight'; +import rtl from '../../rtl'; import Txt from '../Txt'; import styles from './styles'; @@ -128,7 +129,7 @@ const ResultBar: React.FC<Props> = ({ </Txt> {shareBar !== true ? ( <View style={styles.icon}> - <ChevronRight /> + <ChevronRight style={rtl.mirror} /> </View> ) : null} </View> diff --git a/src/components/Txt/styles.ts b/src/components/Txt/styles.ts index 98cfacd..18d388f 100755 --- a/src/components/Txt/styles.ts +++ b/src/components/Txt/styles.ts @@ -1,9 +1,10 @@ -import {Platform, StyleSheet} from 'react-native'; +import {I18nManager, Platform, StyleSheet} from 'react-native'; export default StyleSheet.create({ text: { backgroundColor: 'transparent', fontFamily: Platform.isTV ? 'System' : 'Rubik-Regular', + writingDirection: I18nManager.isRTL ? 'rtl' : 'ltr', }, textMedium: { fontFamily: Platform.isTV ? 'System' : 'Rubik-Medium', diff --git a/src/rtl.ts b/src/rtl.ts new file mode 100644 index 0000000..b9772b7 --- /dev/null +++ b/src/rtl.ts @@ -0,0 +1,11 @@ +import {I18nManager, StyleSheet} from 'react-native'; + +export default StyleSheet.create({ + mirror: { + transform: [ + { + scaleX: I18nManager.isRTL ? -1 : 1, + }, + ], + }, +}); diff --git a/src/screens/ElectionsIndex/index.tsx b/src/screens/ElectionsIndex/index.tsx index 0ac2c09..6fe6410 100644 --- a/src/screens/ElectionsIndex/index.tsx +++ b/src/screens/ElectionsIndex/index.tsx @@ -15,6 +15,7 @@ import {Election, ElectionsData} from 'types/api'; import getCountryFlag from 'util/getCountryFlag'; import moment from 'util/momentLocale'; import ChevronRight from '../../icons/ChevronRight'; +import rtl from '../../rtl'; import styles from './styles'; const ElectionsIndex: React.FC = () => { @@ -43,7 +44,7 @@ const ElectionsIndex: React.FC = () => { <Txt style={styles.countryLinkText} medium> {country!.name} </Txt> - <ChevronRight /> + <ChevronRight style={rtl.mirror} /> </TouchableOpacity> ), title: '', diff --git a/src/screens/Settings/index.tsx b/src/screens/Settings/index.tsx index bb70d28..0cb211a 100644 --- a/src/screens/Settings/index.tsx +++ b/src/screens/Settings/index.tsx @@ -7,7 +7,7 @@ import Title from 'components/Title'; import Txt from 'components/Txt'; import {useApp} from 'contexts/app'; import React from 'react'; -import {TouchableOpacity, View} from 'react-native'; +import {I18nManager, TouchableOpacity, View} from 'react-native'; import RNRestart from 'react-native-restart'; import translations from 'translations'; import styles from './styles'; @@ -60,6 +60,7 @@ const Settings: React.FC = () => { <Txt copy>{t('settings.systemDefaultText')}</Txt> </TouchableOpacity> {config.locales.map((lang) => { + console.log(config.rtlLocales.indexOf(lang) > -1); return ( <TouchableOpacity onPress={() => { @@ -71,7 +72,14 @@ const Settings: React.FC = () => { }} style={[styles.language, activeStyle(lang)]} key={lang}> - <Txt copy medium> + <Txt + copy + medium + style={ + config.rtlLocales.indexOf(lang) > -1 + ? styles.rtl + : styles.ltr + }> {/* @ts-ignore */} {translations[lang].name} </Txt> @@ -89,6 +97,8 @@ const Settings: React.FC = () => { onPress={() => { setLocale(pick === 'default' ? null : pick); setTimeout(() => { + I18nManager.forceRTL(config.rtlLocales.indexOf(pick) > -1); + RNRestart.Restart(); }, 500); }} diff --git a/src/screens/Settings/styles.ts b/src/screens/Settings/styles.ts index e8eff33..072f6bf 100644 --- a/src/screens/Settings/styles.ts +++ b/src/screens/Settings/styles.ts @@ -27,4 +27,10 @@ export default StyleSheet.create({ padding: 15, backgroundColor: 'rgba(0,0,0,0.3)', }, + rtl: { + writingDirection: 'rtl', + }, + ltr: { + writingDirection: 'ltr', + }, }); diff --git a/src/screens/Swiper/components/Card/index.tsx b/src/screens/Swiper/components/Card/index.tsx index f8b3294..c81124c 100644 --- a/src/screens/Swiper/components/Card/index.tsx +++ b/src/screens/Swiper/components/Card/index.tsx @@ -10,6 +10,7 @@ import {Image, Platform, TouchableOpacity, View} from 'react-native'; import LinearGradient from 'react-native-linear-gradient'; import Animated, {Easing} from 'react-native-reanimated'; import {Question} from 'types/api'; +import rtl from '../../../../rtl'; import styles from './styles'; const Card: React.FC<Question> = ({ @@ -56,7 +57,7 @@ const Card: React.FC<Question> = ({ colors={['#DB67AE', '#8186D7']} style={styles.videoButton}> {video_url ? ( - <Play height={24} width={21} /> + <Play height={24} width={21} style={rtl.mirror} /> ) : ( <SvgCircleInfo style={styles.infoIcon} /> )} diff --git a/src/screens/Swiper/components/NavigationButton/index.tsx b/src/screens/Swiper/components/NavigationButton/index.tsx index 73e0b24..5d4662f 100644 --- a/src/screens/Swiper/components/NavigationButton/index.tsx +++ b/src/screens/Swiper/components/NavigationButton/index.tsx @@ -3,6 +3,7 @@ import ArrowRight from 'icons/ArrowRight'; import React from 'react'; import {GestureResponderEvent, TouchableOpacity} from 'react-native'; import LinearGradient from 'react-native-linear-gradient'; +import rtl from '../../../../rtl'; import styles from './styles'; interface Props { @@ -22,7 +23,11 @@ const NavigationButton: React.FC<Props> = ({onPress, disabled, type}) => { end={{x: 0, y: 0}} colors={['#464872', '#5D5D94']} style={[styles.bg, disabled ? styles.disabled : {}]}> - {type === 'previous' ? <ArrowLeft /> : <ArrowRight />} + {type === 'previous' ? ( + <ArrowLeft style={rtl.mirror} /> + ) : ( + <ArrowRight style={rtl.mirror} /> + )} </LinearGradient> </TouchableOpacity> ); diff --git a/src/screens/Swiper/index.tsx b/src/screens/Swiper/index.tsx index 6155c57..04c89df 100644 --- a/src/screens/Swiper/index.tsx +++ b/src/screens/Swiper/index.tsx @@ -18,6 +18,7 @@ import DeckSwiper from 'react-native-deck-swiper'; import {TouchableOpacity} from 'react-native-gesture-handler'; import {useSafeAreaInsets} from 'react-native-safe-area-context'; import {CountAnswerData, Question} from 'types/api'; +import rtl from '../../rtl'; import Card from './components/Card'; import ExitConfirmDialog from './components/ExitConfirmDialog'; import MainButton from './components/MainButton'; @@ -273,7 +274,7 @@ const Swiper: React.FC = () => { } }} style={styles.skip}> - <Skip width={20} height={20} /> + <Skip width={20} height={20} style={rtl.mirror} /> <Txt style={styles.skipText}>{t('swiper.skip')}</Txt> </TouchableOpacity> <MainButton diff --git a/src/screens/Swiper/styles.ts b/src/screens/Swiper/styles.ts index f33b603..1300592 100644 --- a/src/screens/Swiper/styles.ts +++ b/src/screens/Swiper/styles.ts @@ -1,5 +1,5 @@ import {sm} from 'common/breakpoints'; -import {Dimensions, StyleSheet} from 'react-native'; +import {Dimensions, I18nManager, StyleSheet} from 'react-native'; import {cardBorderRadius} from './components/Card/styles'; const {width} = Dimensions.get('window'); @@ -53,7 +53,7 @@ export default StyleSheet.create({ }, controls: { paddingTop: controlsPaddingTop, - flexDirection: 'row', + flexDirection: I18nManager.isRTL ? 'row-reverse' : 'row', justifyContent: 'space-around', alignItems: 'center', position: 'relative', diff --git a/src/translations/ar.ts b/src/translations/ar.ts new file mode 100644 index 0000000..fc6814b --- /dev/null +++ b/src/translations/ar.ts @@ -0,0 +1,115 @@ +const lang = { + name: 'عربى', + countryLanguage: 'arabic', + + 'navigation.backTitle': 'Zurück', + 'navigation.helpTitle': 'FAQ', + 'navigation.electionsTitle': 'WAHLEN', + 'navigation.infoTitle': 'INFOS', + + // Component:ElectionPill + 'electionPill.availableFrom': 'Verfügbar ab {1}', + + // Countdown + 'countdown.days': 'TAGE', + 'countdown.hours': 'STUNDEN', + 'countdown.minutes': 'MINUTEN', + 'countdown.seconds': 'SEKUNDEN', + + // Swiper + 'swiper.doubleWeight': 'Doppelt gewichten', + 'swiper.doubleWeighted': 'Doppelt gewichtet', + 'swiper.questionNumber': 'Frage {1} von {2}', + 'swiper.yes': 'Ja', + 'swiper.no': 'Nein', + 'swiper.none': 'Keine', + 'swiper.skip': 'Überspringen', + 'swiper.cancelTitle': 'Möchtest du wirklich abbrechen?', + 'swiper.cancelText': + 'Dein Fortschritt geht dann verloren und du musst von vorne beginnen.', + 'swiper.cancelActionNo': 'Nein, zurück', + 'swiper.cancelActionYes': 'Ja', + + 'swiperSelectParties.text': + 'Wähle nun noch die Parteien aus, mit denen du deine Antworten vergleichen möchtest. Du kannst beliebig viele Parteien auswählen.', + 'swiperSelectParties.checkAll': 'Alle Auswählen', + 'swiperSelectParties.uncheckAll': 'Alle abwählen', + 'swiperSelectParties.chooseMinOne': + 'Wähle mindestens eine Partei um fortzufahren', + 'swiperSelectParties.nextButton': 'Weiter', + + 'swiperResult.topmatch': 'Dein Top Match', + 'swiperResult.program': 'Wahlprogramm', + 'swiperResult.shareTitle': '#WahlSwiper Ergebnis', + 'swiperResult.shareMessage': 'Mein #WahlSwiper-Ergebnis zur {1}', + 'swiperResult.screenshotTitle': 'WahlSwiper-Ergebnis zur {1}', + + 'swiperResult.comparisonWith': 'Dein Vergleich mit »{1}«', + 'swiperResult.readReasoning': 'Begründung der Partei lesen »', + 'swiperResult.closeReasoning': 'Begründung schließen', + 'swiperResult.noReason': 'Die Partei hat die Entscheidung nicht begründet.', + 'swiperResult.yourAnswer': 'Deine Antwort', + 'swiperResult.party': 'Partei', + + 'swiperResult.yourResult': 'Dein Ergebnis', + 'swiperResult.chooseParties': 'Parteien auswählen', + 'swiperResult.share': 'Teilen', + 'swiperResult.parties': 'Parteien', + 'swiperResult.filterParties': 'Parteien filtern', + + 'swiperResult.editAnswers': 'Antworten bearbeiten', + + // Screen:SelectCountry, + 'selectCountry.title': 'Schön, dass du dabei bist!', + 'selectCountry.introText': + 'Um loszulegen, wähle das Land in dem du lebst oder in dem du wählen darfst.', + + // Screen:SettingsCountry + 'settingsCountry.title': 'Länder', + 'settingsCountry.boxTitle': 'Wechsle in ein anderes Land', + 'settingsCountry.boxText': + 'Wir haben die Fragen aus anderen Ländern neben den Landessprachen auch auf Englisch übersetzt, sodass du dich auch über die Parteien und ihre Standpunkte dort informieren kannst.', + + 'settings.title': 'Einstellungen', + 'settingsLanguage.boxTitle': 'App-Sprache', + 'settingsLanguage.boxText': + 'Ändere die Sprache, in der die App angezeigt wird.', + 'settings.systemDefault': 'System-Standard', + 'settings.systemDefaultText': + 'Nutzt die Sprache deines Telefons, falls eine Übersetzung existiert. Wenn nicht, dann Englisch.', + + // Screen:ElectionsIndex + 'electionsIndex.boxTitle': 'Aktuelle Wahlen', + 'electionsIndex.boxText': 'Wähle eine Wahl aus, für die du swipen möchtest.', + 'electionsIndex.boxPastTitle': 'Vergangene Wahlen', + 'electionsIndex.boxPastText': + 'Diese WahlSwiper sind für vergangene Wahlen. Bitte beachte, dass sich die Standpunkte der Parteien zu einzelnen Punkten dieser Umfragen geändert haben könnten.', + 'electionsIndex.noElections': + 'Für dieses Land sind keine aktuellen WahlSwiper verfügbar.', + + // Screen:ElectionDetails + 'electionDetails.countdownPast': 'Die Wahl fand statt am', + 'electionDetails.countdown': 'Countdown zur Wahl', + 'electionDetails.infoText': + 'Wir stellen dir einige Fragen zu politischen Themen. Beantworte die Fragen durch swipen und wir matchen dich mit einer Partei, die gut zu deinen Ansichten passt.', + 'electionDetails.startButtonText': 'Jetzt starten', + + // Screen:HelpIndex + 'helpIndex.title': 'FAQ', + + // Screen:InfosIndex + 'infosIndex.title': 'Informationen', + 'infosIndex.headline': 'Über den WahlSwiper', + 'infosIndex.paragraph1': + 'Sich eine Meinung zu Wahlen zu bilden soll einfach sein und Spaß machen – das ist unsere Mission beim WahlSwiper. Das Prinzip: Politische Fragen können mit einem einfachen Wisch nach links zu „Nein“ und rechts zu „Ja“ beantwortet werden. Der WahlSwiper errechnet dann die Übereinstimmung mit den Antworten der Parteien.', + 'infosIndex.paragraph2': + 'Wir stehen auf klare Kante. Bei uns gibt’s nur „Ja“ und „Nein“, kein „vielleicht“. Das bringt die Parteien manchmal ins Schwitzen, hilft dir aber bei der Entscheidung. Natürlich kannst du einzelne Fragen auch ohne Antwort überspringen.', + 'infosIndex.paragraph3': + 'Wählen gehen ist so einfach wie Online-Dating – der „Match“ hält dann jedoch mindestens eine Legislaturperiode lang.', + 'infosIndex.imprintButton': 'Impressum', + 'infosIndex.imprintLink': 'https://www.voteswiper.org/page/impressum', + 'infosIndex.privacyButton': 'Datenschutz', + 'infosIndex.privacyLink': 'https://www.voteswiper.org/page/datenschutz', +}; + +export default lang; diff --git a/src/translations/index.ts b/src/translations/index.ts index 1007494..19c1c12 100644 --- a/src/translations/index.ts +++ b/src/translations/index.ts @@ -1,3 +1,4 @@ +import ar from './ar'; import de from './de'; import en from './en'; import fi from './fi'; @@ -10,6 +11,7 @@ const lang = { fi: fi, fr: fr, sv: sv, + ar: ar, }; export default lang;