Commit af963183 authored by Sébastiaan Versteeg's avatar Sébastiaan Versteeg Committed by Sébastiaan Versteeg

Add components translations

parent 99cee02f
image: node:6 image: node:8
variables: variables:
GRADLE_USER_HOME: $CI_PROJECT_DIR/.gradle GRADLE_USER_HOME: $CI_PROJECT_DIR/.gradle
......
...@@ -6,6 +6,8 @@ import { ...@@ -6,6 +6,8 @@ import {
global.fetch = jest.fn().mockReturnValue( global.fetch = jest.fn().mockReturnValue(
Promise.resolve({ status: 200, json: () => 'responseJson' })); Promise.resolve({ status: 200, json: () => 'responseJson' }));
jest.mock('react-native-locale-detector', () => 'en');
describe('url helper', () => { describe('url helper', () => {
beforeEach(() => { beforeEach(() => {
}); });
...@@ -23,27 +25,38 @@ describe('url helper', () => { ...@@ -23,27 +25,38 @@ describe('url helper', () => {
it('should do a fetch request', () => { it('should do a fetch request', () => {
expect.assertions(2); expect.assertions(2);
return apiRequest('route', 'fetchOpts', null) return apiRequest('route', {}, null)
.then((response) => { .then((response) => {
expect(global.fetch).toBeCalledWith(`${apiUrl}/route/`, 'fetchOpts'); expect(global.fetch).toBeCalledWith(`${apiUrl}/route/`,
{ headers: { 'Accept-Language': 'en' } });
expect(response).toEqual('responseJson'); expect(response).toEqual('responseJson');
}); });
}); });
it('should do a fetch request with params', () => { it('should do a fetch request with params', () => {
expect.assertions(1); expect.assertions(1);
return apiRequest('route', 'fetchOpts', { return apiRequest('route', {}, {
params: 'value', params: 'value',
}).then(() => { }).then(() => {
expect(global.fetch).toBeCalledWith(`${apiUrl}/route/?params=value`, 'fetchOpts'); expect(global.fetch).toBeCalledWith(`${apiUrl}/route/?params=value`,
{ headers: { 'Accept-Language': 'en' } });
});
});
it('should do a fetch request with headers', () => {
expect.assertions(1);
return apiRequest('route', { headers: { Authorization: 'Token abc' } }, null).then(() => {
expect(global.fetch).toBeCalledWith(`${apiUrl}/route/`,
{ headers: { 'Accept-Language': 'en', Authorization: 'Token abc' } });
}); });
}); });
it('should generate the url parameters', () => { it('should generate the url parameters', () => {
expect.assertions(2); expect.assertions(2);
return apiRequest('route', 'fetchOpts', null) return apiRequest('route', {}, null)
.then((response) => { .then((response) => {
expect(global.fetch).toBeCalledWith(`${apiUrl}/route/`, 'fetchOpts'); expect(global.fetch).toBeCalledWith(`${apiUrl}/route/`,
{ headers: { 'Accept-Language': 'en' } });
expect(response).toEqual('responseJson'); expect(response).toEqual('responseJson');
}); });
}); });
...@@ -52,7 +65,7 @@ describe('url helper', () => { ...@@ -52,7 +65,7 @@ describe('url helper', () => {
expect.assertions(1); expect.assertions(1);
const response = { status: 404, json: () => 'responseJson' }; const response = { status: 404, json: () => 'responseJson' };
global.fetch.mockReturnValue(Promise.resolve(response)); global.fetch.mockReturnValue(Promise.resolve(response));
return apiRequest('route', 'fetchOpts', null) return apiRequest('route', {}, null)
.catch(e => expect(e).toEqual(new ServerError('Invalid status code: 404', response))); .catch(e => expect(e).toEqual(new ServerError('Invalid status code: 404', response)));
}); });
...@@ -60,7 +73,7 @@ describe('url helper', () => { ...@@ -60,7 +73,7 @@ describe('url helper', () => {
expect.assertions(1); expect.assertions(1);
const response = { status: 204, json: () => 'responseJson' }; const response = { status: 204, json: () => 'responseJson' };
global.fetch.mockReturnValue(Promise.resolve(response)); global.fetch.mockReturnValue(Promise.resolve(response));
return apiRequest('route', 'fetchOpts', null) return apiRequest('route', {}, null)
.then(res => expect(res).toEqual({})); .then(res => expect(res).toEqual({}));
}); });
}); });
...@@ -2,7 +2,9 @@ import React, { Component } from 'react'; ...@@ -2,7 +2,9 @@ import React, { Component } from 'react';
import PropTypes from 'prop-types'; import PropTypes from 'prop-types';
import { Text, View, SectionList, ScrollView, RefreshControl } from 'react-native'; import { Text, View, SectionList, ScrollView, RefreshControl } from 'react-native';
import { connect } from 'react-redux'; import { connect } from 'react-redux';
import { translate } from 'react-i18next';
import Moment from 'moment'; import Moment from 'moment';
import locale from 'react-native-locale-detector';
import * as calendarActions from '../actions/calendar'; import * as calendarActions from '../actions/calendar';
import EventCard from './EventCard'; import EventCard from './EventCard';
...@@ -28,7 +30,7 @@ const addEventToSection = (sections, date, event) => { ...@@ -28,7 +30,7 @@ const addEventToSection = (sections, date, event) => {
if (!(day in sections[sectionKey].data)) { if (!(day in sections[sectionKey].data)) {
sections[sectionKey].data[day] = { sections[sectionKey].data[day] = {
dayNumber: day, dayNumber: day,
dayOfWeek: date.format('dd'), dayOfWeek: locale.startsWith('nl') ? date.format('dd') : date.format('ddd'),
events: [], events: [],
}; };
} }
...@@ -41,7 +43,7 @@ const addEventToSection = (sections, date, event) => { ...@@ -41,7 +43,7 @@ const addEventToSection = (sections, date, event) => {
* Any event that spans multiple days will be split into separate events. * Any event that spans multiple days will be split into separate events.
* The list of sections is sorted at the end. * The list of sections is sorted at the end.
*/ */
const eventListToSections = (eventList) => { const eventListToSections = (eventList, t) => {
const sections = {}; const sections = {};
for (let i = 0; i < eventList.length; i += 1) { for (let i = 0; i < eventList.length; i += 1) {
const start = Moment(eventList[i].start); const start = Moment(eventList[i].start);
...@@ -57,7 +59,7 @@ const eventListToSections = (eventList) => { ...@@ -57,7 +59,7 @@ const eventListToSections = (eventList) => {
// Add start day // Add start day
addEventToSection(sections, start, { addEventToSection(sections, start, {
...eventList[i], ...eventList[i],
title: `${eventList[i].title} (dag 1/${daySpan})`, title: `${eventList[i].title} (${t('day')} 1/${daySpan})`,
end: null, end: null,
}); });
...@@ -67,13 +69,13 @@ const eventListToSections = (eventList) => { ...@@ -67,13 +69,13 @@ const eventListToSections = (eventList) => {
...eventList[i], ...eventList[i],
start: null, start: null,
end: null, end: null,
title: `${eventList[i].title} (dag ${j}/${daySpan})`, title: `${eventList[i].title} (${t('day')} ${j}/${daySpan})`,
}); });
} }
// Add end day // Add end day
addEventToSection(sections, end, { addEventToSection(sections, end, {
...eventList[i], ...eventList[i],
title: `${eventList[i].title} (dag ${daySpan}/${daySpan})`, title: `${eventList[i].title} (${t('day')} ${daySpan}/${daySpan})`,
start: null, start: null,
}); });
} }
...@@ -135,7 +137,7 @@ class Calendar extends Component { ...@@ -135,7 +137,7 @@ class Calendar extends Component {
/> />
)} )}
> >
<ErrorScreen message="Sorry! We couldn't load any data." /> <ErrorScreen message={this.props.t('Sorry, we couldn\'t load any data.')} />
</ScrollView> </ScrollView>
); );
} else if (this.props.eventList.length === 0) { } else if (this.props.eventList.length === 0) {
...@@ -149,7 +151,7 @@ class Calendar extends Component { ...@@ -149,7 +151,7 @@ class Calendar extends Component {
/> />
)} )}
> >
<ErrorScreen message="No events found!" /> <ErrorScreen message={this.props.t('No events found!')} />
</ScrollView> </ScrollView>
); );
} }
...@@ -161,7 +163,7 @@ class Calendar extends Component { ...@@ -161,7 +163,7 @@ class Calendar extends Component {
renderSectionHeader={ renderSectionHeader={
itemHeader => <Text style={styles.sectionHeader}>{itemHeader.section.key}</Text> itemHeader => <Text style={styles.sectionHeader}>{itemHeader.section.key}</Text>
} }
sections={eventListToSections(this.props.eventList)} sections={eventListToSections(this.props.eventList, this.props.t)}
keyExtractor={item => item.dayNumber} keyExtractor={item => item.dayNumber}
stickySectionHeadersEnabled stickySectionHeadersEnabled
onRefresh={this.handleRefresh} onRefresh={this.handleRefresh}
...@@ -189,6 +191,7 @@ Calendar.propTypes = { ...@@ -189,6 +191,7 @@ Calendar.propTypes = {
loading: PropTypes.bool.isRequired, loading: PropTypes.bool.isRequired,
status: PropTypes.string.isRequired, status: PropTypes.string.isRequired,
refresh: PropTypes.func.isRequired, refresh: PropTypes.func.isRequired,
t: PropTypes.func.isRequired,
}; };
const mapStateToProps = state => ({ const mapStateToProps = state => ({
...@@ -201,4 +204,4 @@ const mapDispatchToProps = dispatch => ({ ...@@ -201,4 +204,4 @@ const mapDispatchToProps = dispatch => ({
refresh: () => dispatch(calendarActions.refresh()), refresh: () => dispatch(calendarActions.refresh()),
}); });
export default connect(mapStateToProps, mapDispatchToProps)(Calendar); export default connect(mapStateToProps, mapDispatchToProps)(translate('calendar')(Calendar));
import React from 'react'; import React from 'react';
import { Image, Text, View } from 'react-native'; import { Image, Text, View } from 'react-native';
import { translate } from 'react-i18next';
import PropTypes from 'prop-types'; import PropTypes from 'prop-types';
import styles from './style/errorScreen'; import styles from './style/errorScreen';
...@@ -15,12 +16,13 @@ const ErrorScreen = props => ( ...@@ -15,12 +16,13 @@ const ErrorScreen = props => (
style={styles.image} style={styles.image}
/> />
<Text style={styles.text}>{props.message}</Text> <Text style={styles.text}>{props.message}</Text>
<Text style={styles.text}>Try again later.</Text> <Text style={styles.text}>{props.t('Try again later.')}</Text>
</View> </View>
); );
ErrorScreen.propTypes = { ErrorScreen.propTypes = {
message: PropTypes.string.isRequired, message: PropTypes.string.isRequired,
t: PropTypes.func.isRequired,
}; };
export default ErrorScreen; export default translate('errorScreen')(ErrorScreen);
...@@ -2,6 +2,7 @@ import React, { Component } from 'react'; ...@@ -2,6 +2,7 @@ import React, { Component } from 'react';
import PropTypes from 'prop-types'; import PropTypes from 'prop-types';
import { FlatList, Alert, Image, ScrollView, Text, View, RefreshControl, Button, TouchableHighlight, Platform, Linking } from 'react-native'; import { FlatList, Alert, Image, ScrollView, Text, View, RefreshControl, Button, TouchableHighlight, Platform, Linking } from 'react-native';
import { connect } from 'react-redux'; import { connect } from 'react-redux';
import { translate } from 'react-i18next';
import Moment from 'moment'; import Moment from 'moment';
import styles, { memberSize } from './style/event'; import styles, { memberSize } from './style/event';
...@@ -17,17 +18,16 @@ import * as pizzaActions from '../actions/pizza'; ...@@ -17,17 +18,16 @@ import * as pizzaActions from '../actions/pizza';
class Event extends Component { class Event extends Component {
cancelPrompt = (pk) => { cancelPrompt = (pk) => {
const cancelDeadlineDate = new Date(this.props.data.cancel_deadline); const cancelDeadlineDate = new Date(this.props.data.cancel_deadline);
let message = 'Are you sure you want to cancel your registration?'; let message = this.props.t('Are you sure you want to cancel your registration?');
if (this.props.data.cancel_deadline !== null && cancelDeadlineDate <= new Date()) { if (this.props.data.cancel_deadline !== null && cancelDeadlineDate <= new Date()) {
message = 'The deadline has passed, are you sure you want to cancel your registration and' message = this.props.t('The deadline has passed, are you sure you want to cancel your registration and pay the full costs of €{{ fine }}? You will not be able to undo this!', { fine: this.props.data.fine });
+ ` pay the full costs of €${this.props.data.fine}? You will not be able to undo this!`;
} }
return Alert.alert( return Alert.alert(
'Cancel registration?', this.props.t('Cancel registration?'),
message, message,
[ [
{ text: 'Dismiss' }, { text: this.props.t('No') },
{ text: 'Cancel registration', onPress: () => this.props.cancel(pk) }, { text: this.props.t('Yes'), onPress: () => this.props.cancel(pk) },
], ],
); );
}; };
...@@ -40,25 +40,25 @@ class Event extends Component { ...@@ -40,25 +40,25 @@ class Event extends Component {
infoTexts.push( infoTexts.push(
<View key="start-holder" style={styles.infoHolder}> <View key="start-holder" style={styles.infoHolder}>
<Text style={styles.infoText} key="start-title">Van:</Text> <Text style={styles.infoText} key="start-title">{this.props.t('From')}:</Text>
<Text style={styles.infoValueText} key="start-value">{startDate}</Text> <Text style={styles.infoValueText} key="start-value">{startDate}</Text>
</View>, </View>,
); );
infoTexts.push( infoTexts.push(
<View key="end-holder" style={styles.infoHolder}> <View key="end-holder" style={styles.infoHolder}>
<Text style={styles.infoText} key="end-title">Tot:</Text> <Text style={styles.infoText} key="end-title">{this.props.t('Until')}:</Text>
<Text style={styles.infoValueText} key="end-value">{endDate}</Text> <Text style={styles.infoValueText} key="end-value">{endDate}</Text>
</View>, </View>,
); );
infoTexts.push( infoTexts.push(
<View key="loc-holder" style={styles.infoHolder}> <View key="loc-holder" style={styles.infoHolder}>
<Text style={styles.infoText} key="loc-title">Locatie:</Text> <Text style={styles.infoText} key="loc-title">{this.props.t('Location')}:</Text>
<Text style={styles.infoValueText} key="loc-value">{data.location}</Text> <Text style={styles.infoValueText} key="loc-value">{data.location}</Text>
</View>, </View>,
); );
infoTexts.push( infoTexts.push(
<View key="price-holder" style={styles.infoHolder}> <View key="price-holder" style={styles.infoHolder}>
<Text style={styles.infoText} key="price-title">Prijs:</Text> <Text style={styles.infoText} key="price-title">{this.props.t('Price')}:</Text>
<Text style={styles.infoValueText} key="price-value">{data.price}</Text> <Text style={styles.infoValueText} key="price-value">{data.price}</Text>
</View>, </View>,
); );
...@@ -69,25 +69,25 @@ class Event extends Component { ...@@ -69,25 +69,25 @@ class Event extends Component {
infoTexts.push( infoTexts.push(
<View key="registrationend-holder" style={styles.infoHolder}> <View key="registrationend-holder" style={styles.infoHolder}>
<Text style={styles.infoText} key="registrationend-title">Aanmelddeadline:</Text> <Text style={styles.infoText} key="registrationend-title">{this.props.t('Registration deadline')}:</Text>
<Text style={styles.infoValueText} key="registrationend-value">{registrationDeadline}</Text> <Text style={styles.infoValueText} key="registrationend-value">{registrationDeadline}</Text>
</View>, </View>,
); );
infoTexts.push( infoTexts.push(
<View key="canceldeadline-holder" style={styles.infoHolder}> <View key="canceldeadline-holder" style={styles.infoHolder}>
<Text style={styles.infoText} key="canceldeadline-title">Afmelddeadline:</Text> <Text style={styles.infoText} key="canceldeadline-title">{this.props.t('Cancellation deadline')}:</Text>
<Text style={styles.infoValueText} key="canceldeadline-value">{cancelDeadline}</Text> <Text style={styles.infoValueText} key="canceldeadline-value">{cancelDeadline}</Text>
</View>, </View>,
); );
let participantsText = `${data.num_participants} aanmeldingen`; let participantsText = `${data.num_participants} ${this.props.t('registrations')}`;
if (data.max_participants) { if (data.max_participants) {
participantsText += ` (${data.max_participants} max)`; participantsText += ` (${data.max_participants} ${this.props.t('max')})`;
} }
infoTexts.push( infoTexts.push(
<View key="participants-holder" style={styles.infoHolder}> <View key="participants-holder" style={styles.infoHolder}>
<Text style={styles.infoText} key="participants-title">Aantal aanmeldingen:</Text> <Text style={styles.infoText} key="participants-title">{this.props.t('Number of registrations')}:</Text>
<Text style={styles.infoValueText} key="participants-value">{participantsText}</Text> <Text style={styles.infoValueText} key="participants-value">{participantsText}</Text>
</View>, </View>,
); );
...@@ -95,20 +95,20 @@ class Event extends Component { ...@@ -95,20 +95,20 @@ class Event extends Component {
if (data.user_registration) { if (data.user_registration) {
let registrationState; let registrationState;
if (data.user_registration.is_late_cancellation) { if (data.user_registration.is_late_cancellation) {
registrationState = 'Je bent afgemeld na de afmelddeadline'; registrationState = this.props.t('Your registration is cancelled after the cancellation deadline');
} else if (data.user_registration.is_cancelled) { } else if (data.user_registration.is_cancelled) {
registrationState = 'Je bent afgemeld'; registrationState = this.props.t('Your registration is cancelled');
} else if (data.user_registration.queue_position === null) { } else if (data.user_registration.queue_position === null) {
registrationState = 'Je bent aangemeld'; registrationState = this.props.t('You are registered');
} else if (data.user_registration.queue_position > 0) { } else if (data.user_registration.queue_position > 0) {
registrationState = `Wachtlijst positie ${data.user_registration.queue_position}`; registrationState = this.props.t('Queue position {{pos}}', { pos: data.user_registration.queue_position });
} else { } else {
registrationState = 'Je bent afgemeld'; registrationState = this.props.t('Your registration is cancelled');
} }
infoTexts.push( infoTexts.push(
<View key="status-holder" style={styles.infoHolder}> <View key="status-holder" style={styles.infoHolder}>
<Text style={styles.infoText} key="status-title">Aanmeldstatus:</Text> <Text style={styles.infoText} key="status-title">{this.props.t('Registration status')}:</Text>
<Text style={styles.infoValueText} key="status-value">{registrationState}</Text> <Text style={styles.infoValueText} key="status-value">{registrationState}</Text>
</View>, </View>,
); );
...@@ -149,23 +149,26 @@ class Event extends Component { ...@@ -149,23 +149,26 @@ class Event extends Component {
const afterCancelDeadline = event.cancel_deadline !== null && cancelDeadlineDate <= nowDate; const afterCancelDeadline = event.cancel_deadline !== null && cancelDeadlineDate <= nowDate;
if (!regRequired) { if (!regRequired) {
text = 'Geen aanmelding vereist.'; text = this.props.t('No registration required.');
if (event.no_registration_message) { if (event.no_registration_message) {
text = event.no_registration_message; text = event.no_registration_message;
} }
} else if (!regStarted) { } else if (!regStarted) {
const registrationStart = Moment(event.registration_start).format('D MMM YYYY, HH:mm'); const registrationStart = Moment(event.registration_start).format('D MMM YYYY, HH:m');
text = `Aanmelden opent ${registrationStart}.`; text = this.props.t('Registration will open {{start}}', { start: registrationStart });
} else if (!regAllowed) { } else if (!regAllowed) {
text = 'Aanmelden is niet meer mogelijk.'; text = this.props.t('Registration is not possible anymore.');
} }
if (afterCancelDeadline) { if (afterCancelDeadline) {
if (text.length > 0) { if (text.length > 0) {
text += ' '; text += ' ';
} }
text += `Afmelden is niet meer mogelijk zonder de volledige kosten van €${event.fine} te ` + text += this.props.t(
'betalen. Let op: je kunt je hierna niet meer aanmelden.'; 'Cancellation isn\'t possible anymore without having to pay the full ' +
'costs of €{{fine}}. Also note that you will be unable to re-register.',
{ fine: event.fine },
);
} }
if (text.length > 0) { if (text.length > 0) {
...@@ -188,7 +191,7 @@ class Event extends Component { ...@@ -188,7 +191,7 @@ class Event extends Component {
if (regAllowed) { if (regAllowed) {
if (event.user_registration === null || event.user_registration.is_cancelled) { if (event.user_registration === null || event.user_registration.is_cancelled) {
const text = event.max_participants && event.max_participants <= event.num_participants ? const text = event.max_participants && event.max_participants <= event.num_participants ?
'Zet me op de wachtlijst' : 'Aanmelden'; this.props.t('Put me on the waiting list') : this.props.t('Register');
return ( return (
<View style={styles.registrationActions}> <View style={styles.registrationActions}>
<Button <Button
...@@ -206,13 +209,13 @@ class Event extends Component { ...@@ -206,13 +209,13 @@ class Event extends Component {
<View style={styles.registrationActions}> <View style={styles.registrationActions}>
<Button <Button
color={colors.magenta} color={colors.magenta}
title="Aanmelding bijwerken" title={this.props.t('Update registration')}
onPress={() => this.props.fields(event.user_registration.pk)} onPress={() => this.props.fields(event.user_registration.pk)}
/> />
<View style={styles.secondButtonMargin}> <View style={styles.secondButtonMargin}>
<Button <Button
color={colors.magenta} color={colors.magenta}
title="Afmelden" title={this.props.t('Cancel registration')}
onPress={() => this.cancelPrompt(event.user_registration.pk)} onPress={() => this.cancelPrompt(event.user_registration.pk)}
/> />
</View> </View>
...@@ -221,7 +224,7 @@ class Event extends Component { ...@@ -221,7 +224,7 @@ class Event extends Component {
} }
return ( return (
<View style={styles.registrationActions}> <View style={styles.registrationActions}>
<Button color={colors.magenta} title="Afmelden" onPress={() => this.cancelPrompt(event.user_registration.pk)} /> <Button color={colors.magenta} title={this.props.t('Cancel registration')} onPress={() => this.cancelPrompt(event.user_registration.pk)} />
</View> </View>
); );
} }
...@@ -235,7 +238,7 @@ class Event extends Component { ...@@ -235,7 +238,7 @@ class Event extends Component {
return ( return (
<View> <View>
<View style={styles.divider} /> <View style={styles.divider} />
<Text style={styles.registrationsTitle}>Aanmeldingen</Text> <Text style={styles.registrationsTitle}>{this.props.t('Registrations')}</Text>
<FlatList <FlatList
numColumns={3} numColumns={3}
data={this.props.registrations} data={this.props.registrations}
...@@ -295,7 +298,7 @@ class Event extends Component { ...@@ -295,7 +298,7 @@ class Event extends Component {
{this.eventInfo(this.props.data)} {this.eventInfo(this.props.data)}
<View style={styles.divider} /> <View style={styles.divider} />
<Text style={styles.descText}>{this.props.data.description}</Text> <Text style={styles.descText}>{this.props.data.description}</Text>
{this.registrationsGrid(this.props.registrations)} {this.registrationsGrid(this.props.registrations, this.props.t)}
</ScrollView> </ScrollView>
); );
} }
...@@ -310,7 +313,7 @@ class Event extends Component { ...@@ -310,7 +313,7 @@ class Event extends Component {
/> />
)} )}
> >
<ErrorScreen message="Could not load the event..." /> <ErrorScreen message={this.props.t('Could not load the event...')} />
</ScrollView> </ScrollView>
); );
} }
...@@ -364,6 +367,7 @@ Event.propTypes = { ...@@ -364,6 +367,7 @@ Event.propTypes = {
fields: PropTypes.func.isRequired,