Client - application translation (wip)

This commit is contained in:
Sam 2019-09-16 10:26:02 +02:00
parent 745d102ee2
commit 77bc32d4a5
33 changed files with 651 additions and 374 deletions

View File

@ -1,15 +1,18 @@
import React from 'react' import React from 'react'
import { translateSports } from '../../utils/activities'
export default class ActivitiesFilter extends React.PureComponent { export default class ActivitiesFilter extends React.PureComponent {
render() { render() {
const { loadActivities, sports, updateParams } = this.props const { loadActivities, sports, t, updateParams } = this.props
const translatedSports = translateSports(sports, t)
return ( return (
<div className="card"> <div className="card">
<div className="card-body activity-filter"> <div className="card-body activity-filter">
<form onSubmit={event => event.preventDefault()}> <form onSubmit={event => event.preventDefault()}>
<div className="form-group"> <div className="form-group">
<label> <label>
From: {t('activities:From')}:
<input <input
className="form-control col-md" className="form-control col-md"
name="from" name="from"
@ -18,7 +21,7 @@ export default class ActivitiesFilter extends React.PureComponent {
/> />
</label> </label>
<label> <label>
To: {t('activities:To')}:
<input <input
className="form-control col-md" className="form-control col-md"
name="to" name="to"
@ -29,14 +32,14 @@ export default class ActivitiesFilter extends React.PureComponent {
</div> </div>
<div className="form-group"> <div className="form-group">
<label> <label>
Sport: {t('common:Sport')}:
<select <select
className="form-control input-lg" className="form-control input-lg"
name="sport_id" name="sport_id"
onChange={e => updateParams(e)} onChange={e => updateParams(e)}
> >
<option value="" /> <option value="" />
{sports.map(sport => ( {translatedSports.map(sport => (
<option key={sport.id} value={sport.id}> <option key={sport.id} value={sport.id}>
{sport.label} {sport.label}
</option> </option>
@ -46,7 +49,7 @@ export default class ActivitiesFilter extends React.PureComponent {
</div> </div>
<div className="form-group"> <div className="form-group">
<label> <label>
Distance (km): {t('activities:Distance')} (km):
<div className="container"> <div className="container">
<div className="row"> <div className="row">
<div className="col-5"> <div className="col-5">
@ -59,7 +62,9 @@ export default class ActivitiesFilter extends React.PureComponent {
type="number" type="number"
/> />
</div> </div>
<div className="col-2 align-middle text-center">to</div> <div className="col-2 align-middle text-center">
{t('common:to')}
</div>
<div className="col-5"> <div className="col-5">
<input <input
className="form-control" className="form-control"
@ -76,7 +81,7 @@ export default class ActivitiesFilter extends React.PureComponent {
</div> </div>
<div className="form-group"> <div className="form-group">
<label> <label>
Duration: {t('activities:Duration')}:
<div className="container"> <div className="container">
<div className="row"> <div className="row">
<div className="col-5"> <div className="col-5">
@ -89,7 +94,9 @@ export default class ActivitiesFilter extends React.PureComponent {
type="text" type="text"
/> />
</div> </div>
<div className="col-2 align-middle text-center">to</div> <div className="col-2 align-middle text-center">
{t('common:to')}
</div>
<div className="col-5"> <div className="col-5">
<input <input
className="form-control" className="form-control"
@ -106,7 +113,7 @@ export default class ActivitiesFilter extends React.PureComponent {
</div> </div>
<div className="form-group"> <div className="form-group">
<label> <label>
Average speed (km/h): {t('activities:Average speed')} (km/h):
<div className="container"> <div className="container">
<div className="row"> <div className="row">
<div className="col-5"> <div className="col-5">
@ -119,7 +126,9 @@ export default class ActivitiesFilter extends React.PureComponent {
type="number" type="number"
/> />
</div> </div>
<div className="col-2 align-middle text-center">to</div> <div className="col-2 align-middle text-center">
{t('common:to')}
</div>
<div className="col-5"> <div className="col-5">
<input <input
className="form-control" className="form-control"
@ -136,7 +145,7 @@ export default class ActivitiesFilter extends React.PureComponent {
</div> </div>
<div className="form-group"> <div className="form-group">
<label> <label>
Max speed (km/h): {t('activities:Max. speed')} (km/h):
<div className="container"> <div className="container">
<div className="row"> <div className="row">
<div className="col-5"> <div className="col-5">
@ -149,7 +158,9 @@ export default class ActivitiesFilter extends React.PureComponent {
type="number" type="number"
/> />
</div> </div>
<div className="col-2 align-middle text-center">to</div> <div className="col-2 align-middle text-center">
{t('common:to')}
</div>
<div className="col-5"> <div className="col-5">
<input <input
className="form-control" className="form-control"
@ -168,7 +179,7 @@ export default class ActivitiesFilter extends React.PureComponent {
className="btn btn-primary btn-lg btn-block" className="btn btn-primary btn-lg btn-block"
onClick={() => loadActivities()} onClick={() => loadActivities()}
type="submit" type="submit"
value="Filter" value={t('activities:Filter')}
/> />
</form> </form>
</div> </div>

View File

@ -7,7 +7,7 @@ import { getDateWithTZ } from '../../utils'
export default class ActivitiesList extends React.PureComponent { export default class ActivitiesList extends React.PureComponent {
render() { render() {
const { activities, sports, user } = this.props const { activities, sports, t, user } = this.props
return ( return (
<div className="card activity-card"> <div className="card activity-card">
<div className="card-body"> <div className="card-body">
@ -15,12 +15,12 @@ export default class ActivitiesList extends React.PureComponent {
<thead> <thead>
<tr> <tr>
<th scope="col" /> <th scope="col" />
<th scope="col">Workout</th> <th scope="col">{t('common:Workout')}</th>
<th scope="col">Date</th> <th scope="col">{t('activities:Date')}</th>
<th scope="col">Distance</th> <th scope="col">{t('activities:Distance')}</th>
<th scope="col">Duration</th> <th scope="col">{t('activities:Duration')}</th>
<th scope="col">Ave. speed</th> <th scope="col">{t('activities:Ave. speed')}</th>
<th scope="col">Max. speed</th> <th scope="col">{t('activities:Max. speed')}</th>
</tr> </tr>
</thead> </thead>
<tbody> <tbody>

View File

@ -1,10 +1,11 @@
import React from 'react' import React from 'react'
import { Helmet } from 'react-helmet' import { Helmet } from 'react-helmet'
import { withTranslation } from 'react-i18next'
import { connect } from 'react-redux' import { connect } from 'react-redux'
import { Link } from 'react-router-dom'
import ActivitiesFilter from './ActivitiesFilter' import ActivitiesFilter from './ActivitiesFilter'
import ActivitiesList from './ActivitiesList' import ActivitiesList from './ActivitiesList'
import NoActivities from '../Common/NoActivities'
import { getOrUpdateData } from '../../actions' import { getOrUpdateData } from '../../actions'
import { getMoreActivities } from '../../actions/activities' import { getMoreActivities } from '../../actions/activities'
@ -40,6 +41,7 @@ class Activities extends React.Component {
loadMoreActivities, loadMoreActivities,
message, message,
sports, sports,
t,
user, user,
} = this.props } = this.props
const { params } = this.state const { params } = this.state
@ -50,7 +52,7 @@ class Activities extends React.Component {
return ( return (
<div> <div>
<Helmet> <Helmet>
<title>FitTrackee - Workouts</title> <title>FitTrackee - {t('common:Workouts')}</title>
</Helmet> </Helmet>
{message ? ( {message ? (
<code>{message}</code> <code>{message}</code>
@ -61,6 +63,7 @@ class Activities extends React.Component {
<ActivitiesFilter <ActivitiesFilter
sports={sports} sports={sports}
loadActivities={() => loadActivities(params)} loadActivities={() => loadActivities(params)}
t={t}
updateParams={e => this.setParams(e)} updateParams={e => this.setParams(e)}
/> />
</div> </div>
@ -68,6 +71,7 @@ class Activities extends React.Component {
<ActivitiesList <ActivitiesList
activities={activities} activities={activities}
sports={sports} sports={sports}
t={t}
user={user} user={user}
/> />
{!paginationEnd && ( {!paginationEnd && (
@ -82,16 +86,7 @@ class Activities extends React.Component {
}} }}
/> />
)} )}
{activities.length === 0 && ( {activities.length === 0 && <NoActivities t={t} />}
<div className="card text-center">
<div className="card-body">
No workouts.{' '}
<Link to={{ pathname: '/activities/add' }}>
Upload one !
</Link>
</div>
</div>
)}
</div> </div>
</div> </div>
</div> </div>
@ -101,19 +96,21 @@ class Activities extends React.Component {
} }
} }
export default connect( export default withTranslation()(
state => ({ connect(
activities: state.activities.data, state => ({
message: state.message, activities: state.activities.data,
sports: state.sports.data, message: state.message,
user: state.user, sports: state.sports.data,
}), user: state.user,
dispatch => ({ }),
loadActivities: params => { dispatch => ({
dispatch(getOrUpdateData('getData', 'activities', params)) loadActivities: params => {
}, dispatch(getOrUpdateData('getData', 'activities', params))
loadMoreActivities: params => { },
dispatch(getMoreActivities(params)) loadMoreActivities: params => {
}, dispatch(getMoreActivities(params))
}) },
)(Activities) })
)(Activities)
)

View File

@ -1,5 +1,6 @@
import React from 'react' import React from 'react'
import { Helmet } from 'react-helmet' import { Helmet } from 'react-helmet'
import { withTranslation } from 'react-i18next'
import { connect } from 'react-redux' import { connect } from 'react-redux'
import FormWithGpx from './ActivityForms/FormWithGpx' import FormWithGpx from './ActivityForms/FormWithGpx'
@ -23,13 +24,16 @@ class ActivityAddEdit extends React.Component {
} }
render() { render() {
const { activity, loading, message, sports } = this.props const { activity, loading, message, sports, t } = this.props
const { withGpx } = this.state const { withGpx } = this.state
return ( return (
<div> <div>
<Helmet> <Helmet>
<title> <title>
FitTrackee - {activity ? 'Edit a workout' : 'Add a workout'} FitTrackee -{' '}
{activity
? t('activities:Edit a workout')
: t('activities:Add a workout')}
</title> </title>
</Helmet> </Helmet>
<br /> <br />
@ -41,14 +45,20 @@ class ActivityAddEdit extends React.Component {
<div className="col-md-8"> <div className="col-md-8">
<div className="card add-activity"> <div className="card add-activity">
<h2 className="card-header text-center"> <h2 className="card-header text-center">
{activity ? 'Edit a workout' : 'Add a workout'} {activity
? t('activities:Edit a workout')
: t('activities:Add a workout')}
</h2> </h2>
<div className="card-body"> <div className="card-body">
{activity ? ( {activity ? (
activity.with_gpx ? ( activity.with_gpx ? (
<FormWithGpx activity={activity} sports={sports} /> <FormWithGpx activity={activity} sports={sports} t={t} />
) : ( ) : (
<FormWithoutGpx activity={activity} sports={sports} /> <FormWithoutGpx
activity={activity}
sports={sports}
t={t}
/>
) )
) : ( ) : (
<div> <div>
@ -66,7 +76,7 @@ class ActivityAddEdit extends React.Component {
this.handleRadioChange(event) this.handleRadioChange(event)
} }
/> />
with gpx file {t('activities:with gpx file')}
</label> </label>
</div> </div>
<div className="col"> <div className="col">
@ -81,15 +91,15 @@ class ActivityAddEdit extends React.Component {
this.handleRadioChange(event) this.handleRadioChange(event)
} }
/> />
without gpx file {t('activities:without gpx file')}
</label> </label>
</div> </div>
</div> </div>
</form> </form>
{withGpx ? ( {withGpx ? (
<FormWithGpx sports={sports} /> <FormWithGpx sports={sports} t={t} />
) : ( ) : (
<FormWithoutGpx sports={sports} /> <FormWithoutGpx sports={sports} t={t} />
)} )}
</div> </div>
)} )}
@ -104,6 +114,8 @@ class ActivityAddEdit extends React.Component {
} }
} }
export default connect(state => ({ export default withTranslation()(
loading: state.loading, connect(state => ({
}))(ActivityAddEdit) loading: state.loading,
}))(ActivityAddEdit)
)

View File

@ -1,17 +1,21 @@
import React from 'react' import React from 'react'
import { Trans } from 'react-i18next'
import { connect } from 'react-redux' import { connect } from 'react-redux'
import { setLoading } from '../../../actions/index' import { setLoading } from '../../../actions/index'
import { addActivity, editActivity } from '../../../actions/activities' import { addActivity, editActivity } from '../../../actions/activities'
import { history } from '../../../index' import { history } from '../../../index'
import { fileSizeLimit, gpxLimit, zipSizeLimit } from '../../../utils' import { fileSizeLimit, gpxLimit, zipSizeLimit } from '../../../utils'
import { translateSports } from '../../../utils/activities'
function FormWithGpx(props) { function FormWithGpx(props) {
const { activity, loading, onAddActivity, onEditActivity, sports } = props const { activity, loading, onAddActivity, onEditActivity, sports, t } = props
const sportId = activity ? activity.sport_id : '' const sportId = activity ? activity.sport_id : ''
const translatedSports = translateSports(sports, t)
// prettier-ignore // prettier-ignore
const zipTooltip = const zipTooltip =
`no folder inside, ${gpxLimit} files max, max size: ${zipSizeLimit}` `${t('activities:no folder inside')}, ${gpxLimit} ${
t('activities:files max')}, ${t('activities:max size')}: ${zipSizeLimit}`
return ( return (
<form <form
encType="multipart/form-data" encType="multipart/form-data"
@ -20,7 +24,7 @@ function FormWithGpx(props) {
> >
<div className="form-group"> <div className="form-group">
<label> <label>
Sport: {t('common:Sport')}:
<select <select
className="form-control input-lg" className="form-control input-lg"
defaultValue={sportId} defaultValue={sportId}
@ -29,7 +33,7 @@ function FormWithGpx(props) {
required required
> >
<option value="" /> <option value="" />
{sports.map(sport => ( {translatedSports.map(sport => (
<option key={sport.id} value={sport.id}> <option key={sport.id} value={sport.id}>
{sport.label} {sport.label}
</option> </option>
@ -40,7 +44,7 @@ function FormWithGpx(props) {
{activity ? ( {activity ? (
<div className="form-group"> <div className="form-group">
<label> <label>
Title: {t('activities:Title')}:
<input <input
name="title" name="title"
defaultValue={activity ? activity.title : ''} defaultValue={activity ? activity.title : ''}
@ -52,17 +56,21 @@ function FormWithGpx(props) {
) : ( ) : (
<div className="form-group"> <div className="form-group">
<label> <label>
<strong>gpx</strong> file <Trans i18nKey="activities:gpxFile">
<strong>gpx</strong> file
</Trans>
<sup> <sup>
<i <i
className="fa fa-question-circle" className="fa fa-question-circle"
aria-hidden="true" aria-hidden="true"
data-toggle="tooltip" data-toggle="tooltip"
title={`max size: ${fileSizeLimit}`} title={`${t('activities:max size')}: ${fileSizeLimit}`}
/> />
</sup>{' '} </sup>{' '}
or <strong> zip</strong> file containing <strong>gpx </strong> <Trans i18nKey="activities:zipFile">
files or <strong> zip</strong> file containing <strong>gpx </strong>
files
</Trans>
<sup> <sup>
<i <i
className="fa fa-question-circle" className="fa fa-question-circle"
@ -86,7 +94,7 @@ function FormWithGpx(props) {
)} )}
<div className="form-group"> <div className="form-group">
<label> <label>
Notes: {t('activities:Notes')}:
<textarea <textarea
name="notes" name="notes"
defaultValue={activity ? activity.notes : ''} defaultValue={activity ? activity.notes : ''}
@ -106,13 +114,13 @@ function FormWithGpx(props) {
onClick={event => onClick={event =>
activity ? onEditActivity(event, activity) : onAddActivity(event) activity ? onEditActivity(event, activity) : onAddActivity(event)
} }
value="Submit" value={t('common:Submit')}
/> />
<input <input
type="submit" type="submit"
className="btn btn-secondary btn-lg btn-block" className="btn btn-secondary btn-lg btn-block"
onClick={() => history.push('/')} onClick={() => history.push('/')}
value="Cancel" value={t('common:Cancel')}
/> />
</div> </div>
)} )}

View File

@ -6,10 +6,11 @@ import {
editActivity, editActivity,
} from '../../../actions/activities' } from '../../../actions/activities'
import { history } from '../../../index' import { history } from '../../../index'
import { formatActivityDate } from '../../../utils/activities' import { formatActivityDate, translateSports } from '../../../utils/activities'
function FormWithoutGpx(props) { function FormWithoutGpx(props) {
const { activity, onAddOrEdit, sports } = props const { activity, onAddOrEdit, sports, t } = props
const translatedSports = translateSports(sports, t)
let activityDate, let activityDate,
activityTime, activityTime,
sportId = '' sportId = ''
@ -27,7 +28,7 @@ function FormWithoutGpx(props) {
<form onSubmit={event => event.preventDefault()}> <form onSubmit={event => event.preventDefault()}>
<div className="form-group"> <div className="form-group">
<label> <label>
Title: {t('activities:Title')}:
<input <input
name="title" name="title"
defaultValue={activity ? activity.title : ''} defaultValue={activity ? activity.title : ''}
@ -37,7 +38,7 @@ function FormWithoutGpx(props) {
</div> </div>
<div className="form-group"> <div className="form-group">
<label> <label>
Sport: {t('common:Sport')}:
<select <select
className="form-control input-lg" className="form-control input-lg"
defaultValue={sportId} defaultValue={sportId}
@ -45,7 +46,7 @@ function FormWithoutGpx(props) {
required required
> >
<option value="" /> <option value="" />
{sports.map(sport => ( {translatedSports.map(sport => (
<option key={sport.id} value={sport.id}> <option key={sport.id} value={sport.id}>
{sport.label} {sport.label}
</option> </option>
@ -55,7 +56,7 @@ function FormWithoutGpx(props) {
</div> </div>
<div className="form-group"> <div className="form-group">
<label> <label>
Activity Date: {t('activities:Activity Date')}:
<div className="container"> <div className="container">
<div className="row"> <div className="row">
<input <input
@ -78,7 +79,7 @@ function FormWithoutGpx(props) {
</div> </div>
<div className="form-group"> <div className="form-group">
<label> <label>
Duration: {t('activities:Duration')}:
<input <input
name="duration" name="duration"
defaultValue={activity ? activity.duration : ''} defaultValue={activity ? activity.duration : ''}
@ -92,7 +93,7 @@ function FormWithoutGpx(props) {
</div> </div>
<div className="form-group"> <div className="form-group">
<label> <label>
Distance (km): {t('activities:Distance')} (km):
<input <input
name="distance" name="distance"
defaultValue={activity ? activity.distance : ''} defaultValue={activity ? activity.distance : ''}
@ -106,7 +107,7 @@ function FormWithoutGpx(props) {
</div> </div>
<div className="form-group"> <div className="form-group">
<label> <label>
Notes: {t('activities:Notes')}:
<textarea <textarea
name="notes" name="notes"
defaultValue={activity ? activity.notes : ''} defaultValue={activity ? activity.notes : ''}
@ -119,13 +120,13 @@ function FormWithoutGpx(props) {
type="submit" type="submit"
className="btn btn-primary btn-lg btn-block" className="btn btn-primary btn-lg btn-block"
onClick={event => onAddOrEdit(event, activity)} onClick={event => onAddOrEdit(event, activity)}
value="Submit" value={t('common:Submit')}
/> />
<input <input
type="submit" type="submit"
className="btn btn-secondary btn-lg btn-block" className="btn btn-secondary btn-lg btn-block"
onClick={() => history.push('/')} onClick={() => history.push('/')}
value="Cancel" value={t('common:Cancel')}
/> />
</form> </form>
) )

View File

@ -400,7 +400,6 @@ label {
.time-frame label { .time-frame label {
float: left; float: left;
padding: 0 5px; padding: 0 5px;
width: 4em;
} }
.time-frame label input { .time-frame label input {
@ -413,7 +412,7 @@ label {
color: #7b7b7b; color: #7b7b7b;
display: block; display: block;
font-size: 0.9em; font-size: 0.9em;
padding: 2px 0; padding: 2px 6px;
text-align: center; text-align: center;
} }

View File

@ -0,0 +1,18 @@
import React from 'react'
import { Link } from 'react-router-dom'
export default class NoActivities extends React.PureComponent {
render() {
const { t } = this.props
return (
<div className="card text-center">
<div className="card-body">
{t('common:No workouts.')}{' '}
<Link to={{ pathname: '/activities/add' }}>
{t('dashboard:Upload one !')}
</Link>
</div>
</div>
)
}
}

View File

@ -28,9 +28,9 @@ export default class StatsCharts extends React.PureComponent {
render() { render() {
const { displayedData } = this.state const { displayedData } = this.state
const { sports, stats } = this.props const { sports, stats, t } = this.props
if (Object.keys(stats).length === 0) { if (Object.keys(stats).length === 0) {
return 'No workouts' return t('common:No workouts.')
} }
return ( return (
<div className="chart-stats"> <div className="chart-stats">
@ -42,7 +42,7 @@ export default class StatsCharts extends React.PureComponent {
checked={displayedData === 'distance'} checked={displayedData === 'distance'}
onChange={e => this.handleRadioChange(e)} onChange={e => this.handleRadioChange(e)}
/> />
distance {t('statistics:distance')}
</label> </label>
<label className="radioLabel col"> <label className="radioLabel col">
<input <input
@ -51,7 +51,7 @@ export default class StatsCharts extends React.PureComponent {
checked={displayedData === 'duration'} checked={displayedData === 'duration'}
onChange={e => this.handleRadioChange(e)} onChange={e => this.handleRadioChange(e)}
/> />
duration {t('statistics:duration')}
</label> </label>
<label className="radioLabel col"> <label className="radioLabel col">
<input <input
@ -60,7 +60,7 @@ export default class StatsCharts extends React.PureComponent {
checked={displayedData === 'activities'} checked={displayedData === 'activities'}
onChange={e => this.handleRadioChange(e)} onChange={e => this.handleRadioChange(e)}
/> />
activities {t('statistics:activities')}
</label> </label>
</div> </div>
<ResponsiveContainer height={300}> <ResponsiveContainer height={300}>

View File

@ -37,10 +37,11 @@ class Statistics extends React.PureComponent {
statistics, statistics,
statsParams, statsParams,
displayEmpty, displayEmpty,
t,
user, user,
} = this.props } = this.props
if (!displayEmpty && Object.keys(statistics).length === 0) { if (!displayEmpty && Object.keys(statistics).length === 0) {
return 'No workouts' return <span>{t('common:No workouts.')}</span>
} }
const stats = formatStats( const stats = formatStats(
statistics, statistics,
@ -49,7 +50,7 @@ class Statistics extends React.PureComponent {
displayedSports, displayedSports,
user.weekm user.weekm
) )
return <StatsChart sports={sports} stats={stats} /> return <StatsChart sports={sports} stats={stats} t={t} />
} }
} }

View File

@ -4,7 +4,7 @@ import { Link } from 'react-router-dom'
import { formatRecord } from '../../utils/activities' import { formatRecord } from '../../utils/activities'
export default function RecordsCard(props) { export default function RecordsCard(props) {
const { records, sports, user } = props const { records, sports, t, user } = props
const recordsBySport = records.reduce((sportList, record) => { const recordsBySport = records.reduce((sportList, record) => {
const sport = sports.find(s => s.id === record.sport_id) const sport = sports.find(s => s.id === record.sport_id)
if (sportList[sport.label] === void 0) { if (sportList[sport.label] === void 0) {
@ -22,7 +22,7 @@ export default function RecordsCard(props) {
<div className="card-header">Personal records</div> <div className="card-header">Personal records</div>
<div className="card-body"> <div className="card-body">
{Object.keys(recordsBySport).length === 0 {Object.keys(recordsBySport).length === 0
? 'No records' ? t('common:No records.')
: Object.keys(recordsBySport).map(sportLabel => ( : Object.keys(recordsBySport).map(sportLabel => (
<table <table
className="table table-borderless table-sm record-table" className="table table-borderless table-sm record-table"

View File

@ -16,11 +16,12 @@ export default class Statistics extends React.Component {
} }
render() { render() {
const { t } = this.props
return ( return (
<div className="card activity-card"> <div className="card activity-card">
<div className="card-header">This month</div> <div className="card-header">{t('dashboard:This month')}</div>
<div className="card-body"> <div className="card-body">
<Stats displayEmpty={false} statsParams={this.state} /> <Stats displayEmpty={false} statsParams={this.state} t={t} />
</div> </div>
</div> </div>
) )

View File

@ -1,10 +1,12 @@
import React from 'react' import React from 'react'
export default function UserStatistics(props) { export default function UserStatistics(props) {
const { user } = props const { t, user } = props
const days = user.total_duration.match(/day/g) const days = user.total_duration.match(/day/g)
? `${user.total_duration.split(',')[0]},` ? `${user.total_duration.split(' ')[0]} ${
: '0 days,' user.total_duration.match(/days/g) ? t('common:days') : t('common:day')
}`
: `0 ${t('common:days')},`
let duration = user.total_duration.match(/day/g) let duration = user.total_duration.match(/day/g)
? user.total_duration.split(', ')[1] ? user.total_duration.split(', ')[1]
: user.total_duration : user.total_duration
@ -19,7 +21,11 @@ export default function UserStatistics(props) {
</div> </div>
<div className="col-9 text-right"> <div className="col-9 text-right">
<div className="huge">{user.nb_activities}</div> <div className="huge">{user.nb_activities}</div>
<div>{`workout${user.nb_activities === 1 ? '' : 's'}`}</div> <div>{`${
user.nb_activities === 1
? t('common:workout')
: t('common:workouts')
}`}</div>
</div> </div>
</div> </div>
</div> </div>
@ -60,7 +66,9 @@ export default function UserStatistics(props) {
</div> </div>
<div className="col-9 text-right"> <div className="col-9 text-right">
<div className="huge">{user.nb_sports}</div> <div className="huge">{user.nb_sports}</div>
<div>{`sport${user.nb_sports === 1 ? '' : 's'}`}</div> <div>{`${
user.nb_sports === 1 ? t('common:sport') : t('common:sports')
}`}</div>
</div> </div>
</div> </div>
</div> </div>

View File

@ -1,10 +1,11 @@
import React from 'react' import React from 'react'
import { Helmet } from 'react-helmet' import { Helmet } from 'react-helmet'
import { withTranslation } from 'react-i18next'
import { connect } from 'react-redux' import { connect } from 'react-redux'
import { Link } from 'react-router-dom'
import ActivityCard from './ActivityCard' import ActivityCard from './ActivityCard'
import Calendar from './Calendar' import Calendar from './Calendar'
import NoActivities from '../Common/NoActivities'
import Records from './Records' import Records from './Records'
import Statistics from './Statistics' import Statistics from './Statistics'
import UserStatistics from './UserStatistics' import UserStatistics from './UserStatistics'
@ -30,6 +31,7 @@ class DashBoard extends React.Component {
message, message,
records, records,
sports, sports,
t,
user, user,
} = this.props } = this.props
const paginationEnd = const paginationEnd =
@ -40,7 +42,7 @@ class DashBoard extends React.Component {
return ( return (
<div> <div>
<Helmet> <Helmet>
<title>FitTrackee - Dashboard</title> <title>FitTrackee - {t('common:Dashboard')}</title>
</Helmet> </Helmet>
{message ? ( {message ? (
<code>{message}</code> <code>{message}</code>
@ -48,11 +50,16 @@ class DashBoard extends React.Component {
activities && activities &&
sports.length > 0 && ( sports.length > 0 && (
<div className="container dashboard"> <div className="container dashboard">
<UserStatistics user={user} /> <UserStatistics user={user} t={t} />
<div className="row"> <div className="row">
<div className="col-md-4"> <div className="col-md-4">
<Statistics /> <Statistics t={t} />
<Records records={records} sports={sports} user={user} /> <Records
t={t}
records={records}
sports={sports}
user={user}
/>
</div> </div>
<div className="col-md-8"> <div className="col-md-8">
<Calendar weekm={user.weekm} /> <Calendar weekm={user.weekm} />
@ -66,14 +73,7 @@ class DashBoard extends React.Component {
/> />
)) ))
) : ( ) : (
<div className="card text-center"> <NoActivities t={t} />
<div className="card-body">
No workouts.{' '}
<Link to={{ pathname: '/activities/add' }}>
Upload one !
</Link>
</div>
</div>
)} )}
{!paginationEnd && ( {!paginationEnd && (
<input <input
@ -96,21 +96,23 @@ class DashBoard extends React.Component {
} }
} }
export default connect( export default withTranslation()(
state => ({ connect(
activities: state.activities.data, state => ({
message: state.message, activities: state.activities.data,
records: state.records.data, message: state.message,
sports: state.sports.data, records: state.records.data,
user: state.user, sports: state.sports.data,
}), user: state.user,
dispatch => ({ }),
loadActivities: () => { dispatch => ({
dispatch(getOrUpdateData('getData', 'activities', { page: 1 })) loadActivities: () => {
dispatch(getOrUpdateData('getData', 'records')) dispatch(getOrUpdateData('getData', 'activities', { page: 1 }))
}, dispatch(getOrUpdateData('getData', 'records'))
loadMoreActivities: page => { },
dispatch(getMoreActivities({ page })) loadMoreActivities: page => {
}, dispatch(getMoreActivities({ page }))
}) },
)(DashBoard) })
)(DashBoard)
)

View File

@ -1,6 +1,6 @@
import React from 'react' import React from 'react'
import { Translation } from 'react-i18next'
import { connect } from 'react-redux' import { connect } from 'react-redux'
import { withTranslation } from 'react-i18next'
import { Link } from 'react-router-dom' import { Link } from 'react-router-dom'
import LanguageDropdown from './LanguageDropdown' import LanguageDropdown from './LanguageDropdown'
@ -8,163 +8,161 @@ import { apiUrl } from '../../utils'
class NavBar extends React.PureComponent { class NavBar extends React.PureComponent {
render() { render() {
const { id, isAuthenticated, picture, username } = this.props const { id, isAuthenticated, picture, t, username } = this.props
return ( return (
<Translation> <header>
{t => ( <nav className="navbar navbar-expand-lg navbar-light bg-light">
<header> <div className="container">
<nav className="navbar navbar-expand-lg navbar-light bg-light"> <span className="navbar-brand">FitTrackee</span>
<div className="container"> <button
<span className="navbar-brand">FitTrackee</span> className="navbar-toggler"
<button type="button"
className="navbar-toggler" data-toggle="collapse"
type="button" data-target="#navbarSupportedContent"
data-toggle="collapse" aria-controls="navbarSupportedContent"
data-target="#navbarSupportedContent" aria-expanded="false"
aria-controls="navbarSupportedContent" aria-label="Toggle navigation"
aria-expanded="false" >
aria-label="Toggle navigation" <span className="navbar-toggler-icon" />
> </button>
<span className="navbar-toggler-icon" /> <div
</button> className="collapse navbar-collapse"
<div id="navbarSupportedContent"
className="collapse navbar-collapse" >
id="navbarSupportedContent" <ul className="navbar-nav mr-auto">
> <li className="nav-item">
<ul className="navbar-nav mr-auto"> <Link
<li className="nav-item"> className="nav-link"
<Link to={{
className="nav-link" pathname: '/',
to={{ }}
pathname: '/',
}}
>
{t('common:Dashboard')}
</Link>
</li>
{isAuthenticated && (
<li className="nav-item">
<Link
className="nav-link"
to={{
pathname: '/activities/history',
}}
>
{t('Workouts')}
</Link>
</li>
)}
{isAuthenticated && (
<li className="nav-item">
<Link
className="nav-link"
to={{
pathname: '/activities/statistics',
}}
>
{t('common:Statistics')}
</Link>
</li>
)}
{isAuthenticated && (
<li className="nav-item">
<Link
className="nav-link"
to={{
pathname: '/activities/add',
}}
>
<strong>{t('common:Add workout')}</strong>
</Link>
</li>
)}
{/* {user.admin && ( */}
{/* <li className="nav-item"> */}
{/* <Link */}
{/* className="nav-link" */}
{/* to={{ */}
{/* pathname: '/admin', */}
{/* }} */}
{/* > */}
{/* Admin */}
{/* </Link> */}
{/* </li> */}
{/* )} */}
</ul>
{/* prettier-ignore */}
<ul
className="navbar-nav flex-row ml-md-auto d-none d-md-flex"
> >
{!isAuthenticated && ( {t('common:Dashboard')}
<li className="nav-item"> </Link>
<Link </li>
className="nav-link" {isAuthenticated && (
to={{ <li className="nav-item">
pathname: '/register', <Link
}} className="nav-link"
> to={{
{t('common:Register')} pathname: '/activities/history',
</Link> }}
</li> >
)} {t('Workouts')}
{!isAuthenticated && ( </Link>
<li className="nav-item"> </li>
<Link )}
className="nav-link" {isAuthenticated && (
to={{ <li className="nav-item">
pathname: '/login', <Link
}} className="nav-link"
> to={{
{t('common:Login')} pathname: '/activities/statistics',
</Link> }}
</li> >
)} {t('common:Statistics')}
{picture === true && ( </Link>
<img </li>
alt="Avatar" )}
src={`${apiUrl}users/${id}/picture?${Date.now()}`} {isAuthenticated && (
className="img-fluid App-nav-profile-img" <li className="nav-item">
/> <Link
)} className="nav-link"
{isAuthenticated && ( to={{
<li className="nav-item"> pathname: '/activities/add',
<Link }}
className="nav-link" >
to={{ <strong>{t('common:Add workout')}</strong>
pathname: '/profile', </Link>
}} </li>
> )}
{username} {/* {user.admin && ( */}
</Link> {/* <li className="nav-item"> */}
</li> {/* <Link */}
)} {/* className="nav-link" */}
{isAuthenticated && ( {/* to={{ */}
<li className="nav-item"> {/* pathname: '/admin', */}
<Link {/* }} */}
className="nav-link" {/* > */}
to={{ {/* Admin */}
pathname: '/logout', {/* </Link> */}
}} {/* </li> */}
> {/* )} */}
{t('common:Logout')} </ul>
</Link> {/* prettier-ignore */}
</li> <ul
)} className="navbar-nav flex-row ml-md-auto d-none d-md-flex"
<li><LanguageDropdown /></li> >
</ul> {!isAuthenticated && (
</div> <li className="nav-item">
</div> <Link
</nav> className="nav-link"
</header> to={{
)} pathname: '/register',
</Translation> }}
>
{t('common:Register')}
</Link>
</li>
)}
{!isAuthenticated && (
<li className="nav-item">
<Link
className="nav-link"
to={{
pathname: '/login',
}}
>
{t('common:Login')}
</Link>
</li>
)}
{picture === true && (
<img
alt="Avatar"
src={`${apiUrl}users/${id}/picture?${Date.now()}`}
className="img-fluid App-nav-profile-img"
/>
)}
{isAuthenticated && (
<li className="nav-item">
<Link
className="nav-link"
to={{
pathname: '/profile',
}}
>
{username}
</Link>
</li>
)}
{isAuthenticated && (
<li className="nav-item">
<Link
className="nav-link"
to={{
pathname: '/logout',
}}
>
{t('common:Logout')}
</Link>
</li>
)}
<li><LanguageDropdown /></li>
</ul>
</div>
</div>
</nav>
</header>
) )
} }
} }
export default connect(({ user }) => ({ export default withTranslation()(
id: user.id, connect(({ user }) => ({
isAuthenticated: user.isAuthenticated, id: user.id,
picture: user.picture, isAuthenticated: user.isAuthenticated,
username: user.username, picture: user.picture,
}))(NavBar) username: user.username,
}))(NavBar)
)

View File

@ -14,9 +14,10 @@ import {
} from 'date-fns' } from 'date-fns'
import React from 'react' import React from 'react'
import { Helmet } from 'react-helmet' import { Helmet } from 'react-helmet'
import { withTranslation } from 'react-i18next'
import { connect } from 'react-redux' import { connect } from 'react-redux'
import { activityColors } from '../../utils/activities' import { activityColors, translateSports } from '../../utils/activities'
import Stats from '../Common/Stats' import Stats from '../Common/Stats'
const durations = ['week', 'month', 'year'] const durations = ['week', 'month', 'year']
@ -114,15 +115,16 @@ class Statistics extends React.Component {
render() { render() {
const { displayedSports, statsParams } = this.state const { displayedSports, statsParams } = this.state
const { sports } = this.props const { sports, t } = this.props
const translatedSports = translateSports(sports, t)
return ( return (
<> <>
<Helmet> <Helmet>
<title>FitTrackee - Statistics</title> <title>FitTrackee - {t('statistics:Statistics')}</title>
</Helmet> </Helmet>
<div className="container dashboard"> <div className="container dashboard">
<div className="card activity-card"> <div className="card activity-card">
<div className="card-header">Statistics</div> <div className="card-header">{t('statistics:Statistics')}</div>
<div className="card-body"> <div className="card-body">
<div className="chart-filters row"> <div className="chart-filters row">
<div className="col chart-arrows"> <div className="col chart-arrows">
@ -145,7 +147,7 @@ class Statistics extends React.Component {
checked={d === statsParams.duration} checked={d === statsParams.duration}
onChange={e => this.handleOnChangeDuration(e)} onChange={e => this.handleOnChangeDuration(e)}
/> />
<span>{d}</span> <span>{t(`statistics:${d}`)}</span>
</label> </label>
</div> </div>
))} ))}
@ -164,9 +166,10 @@ class Statistics extends React.Component {
displayEmpty displayEmpty
displayedSports={displayedSports} displayedSports={displayedSports}
statsParams={statsParams} statsParams={statsParams}
t={t}
/> />
<div className="row chart-activities"> <div className="row chart-activities">
{sports.map(sport => ( {translatedSports.map(sport => (
<label className="col activity-label" key={sport.id}> <label className="col activity-label" key={sport.id}>
<input <input
type="checkbox" type="checkbox"
@ -188,6 +191,8 @@ class Statistics extends React.Component {
} }
} }
export default connect(state => ({ export default withTranslation()(
sports: state.sports.data, connect(state => ({
}))(Statistics) sports: state.sports.data,
}))(Statistics)
)

View File

@ -1,4 +1,5 @@
import React from 'react' import React from 'react'
import { Trans } from 'react-i18next'
import { connect } from 'react-redux' import { connect } from 'react-redux'
import { Link } from 'react-router-dom' import { Link } from 'react-router-dom'
@ -16,8 +17,10 @@ class Logout extends React.Component {
<div className="card col-8"> <div className="card col-8">
<div className="card-body"> <div className="card-body">
<div className="text-center"> <div className="text-center">
You are now logged out. Click <Link to="/login">here</Link> to <Trans i18nKey="user:loggedOut">
log back in. You are now logged out. Click <Link to="/login">here</Link> to
log back in.
</Trans>
</div> </div>
</div> </div>
</div> </div>

View File

@ -1,13 +1,14 @@
import { format } from 'date-fns' import { format } from 'date-fns'
import React from 'react' import React from 'react'
import { Helmet } from 'react-helmet' import { Helmet } from 'react-helmet'
import { withTranslation } from 'react-i18next'
import { connect } from 'react-redux' import { connect } from 'react-redux'
import { Link } from 'react-router-dom' import { Link } from 'react-router-dom'
import { deletePicture, uploadPicture } from '../../actions/user' import { deletePicture, uploadPicture } from '../../actions/user'
import { apiUrl, fileSizeLimit } from '../../utils' import { apiUrl, fileSizeLimit } from '../../utils'
function Profile({ message, onDeletePicture, onUploadPicture, user }) { function Profile({ message, onDeletePicture, onUploadPicture, t, user }) {
const createdAt = user.created_at const createdAt = user.created_at
? format(new Date(user.created_at), 'dd/MM/yyyy HH:mm') ? format(new Date(user.created_at), 'dd/MM/yyyy HH:mm')
: '' : ''
@ -17,11 +18,11 @@ function Profile({ message, onDeletePicture, onUploadPicture, user }) {
return ( return (
<div> <div>
<Helmet> <Helmet>
<title>FitTrackee - Profile</title> <title>FitTrackee - {t('user:Profile')}</title>
</Helmet> </Helmet>
{message !== '' && <code>{message}</code>} {message !== '' && <code>{message}</code>}
<div className="container"> <div className="container">
<h1 className="page-title">Profile</h1> <h1 className="page-title">{t('user:Profile')}</h1>
<div className="row"> <div className="row">
<div className="col-md-12"> <div className="col-md-12">
<div className="card"> <div className="card">
@ -38,15 +39,34 @@ function Profile({ message, onDeletePicture, onUploadPicture, user }) {
<div className="card-body"> <div className="card-body">
<div className="row"> <div className="row">
<div className="col-md-8"> <div className="col-md-8">
<p>Email: {user.email}</p> <p>
<p>Registration Date: {createdAt}</p> {t('user:Email')}: {user.email}
<p>First Name: {user.first_name}</p> </p>
<p>Last Name: {user.last_name}</p> <p>
<p>Birth Date: {birthDate}</p> {t('user:Registration Date')}: {createdAt}
<p>Location: {user.location}</p> </p>
<p>Bio: {user.bio}</p> <p>
<p>Time zone: {user.timezone}</p> {t('user:First Name')}: {user.first_name}
<p>First day of week: {user.weekm ? 'Monday' : 'Sunday'}</p> </p>
<p>
{t('user:Last Name')}: {user.last_name}
</p>
<p>
{t('user:Birth Date')}: {birthDate}
</p>
<p>
{t('user:Location')}: {user.location}
</p>
<p>
{t('user:Bio')}: {user.bio}
</p>
<p>
{t('user:Timezone')}: {user.timezone}
</p>
<p>
{t('user:First day of week')}:{' '}
{user.weekm ? t('user:Monday') : t('user:Sunday')}
</p>
</div> </div>
<div className="col-md-4"> <div className="col-md-4">
{user.picture === true && ( {user.picture === true && (
@ -61,7 +81,7 @@ function Profile({ message, onDeletePicture, onUploadPicture, user }) {
/> />
<br /> <br />
<button type="submit" onClick={() => onDeletePicture()}> <button type="submit" onClick={() => onDeletePicture()}>
Delete picture {t('user:Delete picture')}
</button> </button>
<br /> <br />
<br /> <br />
@ -77,8 +97,8 @@ function Profile({ message, onDeletePicture, onUploadPicture, user }) {
accept=".png,.jpg,.gif" accept=".png,.jpg,.gif"
/> />
<br /> <br />
<button type="submit">Send</button> (max. size:{' '} <button type="submit">{t('user:Send')}</button>
{fileSizeLimit}) {` (max. size: ${fileSizeLimit})`}
</form> </form>
</div> </div>
</div> </div>
@ -91,17 +111,19 @@ function Profile({ message, onDeletePicture, onUploadPicture, user }) {
) )
} }
export default connect( export default withTranslation()(
state => ({ connect(
message: state.message, state => ({
user: state.user, message: state.message,
}), user: state.user,
dispatch => ({ }),
onDeletePicture: () => { dispatch => ({
dispatch(deletePicture()) onDeletePicture: () => {
}, dispatch(deletePicture())
onUploadPicture: event => { },
dispatch(uploadPicture(event)) onUploadPicture: event => {
}, dispatch(uploadPicture(event))
}) },
)(Profile) })
)(Profile)
)

View File

@ -1,6 +1,7 @@
import { format } from 'date-fns' import { format } from 'date-fns'
import React from 'react' import React from 'react'
import { Helmet } from 'react-helmet' import { Helmet } from 'react-helmet'
import { withTranslation } from 'react-i18next'
import { connect } from 'react-redux' import { connect } from 'react-redux'
import TimezonePicker from 'react-timezone' import TimezonePicker from 'react-timezone'
@ -49,17 +50,17 @@ class ProfileEdit extends React.Component {
} }
render() { render() {
const { onHandleProfileFormSubmit, message, user } = this.props const { onHandleProfileFormSubmit, message, t, user } = this.props
const { formData } = this.state const { formData } = this.state
return ( return (
<div> <div>
<Helmet> <Helmet>
<title>FitTrackee - Edit Profile</title> <title>FitTrackee - {t('user:Profile Edition')}</title>
</Helmet> </Helmet>
{message !== '' && <code>{message}</code>} {message !== '' && <code>{message}</code>}
{formData.isAuthenticated && ( {formData.isAuthenticated && (
<div className="container"> <div className="container">
<h1 className="page-title">Profile Edition</h1> <h1 className="page-title">{t('user:Profile Edition')}</h1>
<div className="row"> <div className="row">
<div className="col-md-2" /> <div className="col-md-2" />
<div className="col-md-8"> <div className="col-md-8">
@ -76,7 +77,7 @@ class ProfileEdit extends React.Component {
> >
<div className="form-group"> <div className="form-group">
<label> <label>
Email: {t('user:Email')}:
<input <input
name="email" name="email"
className="form-control input-lg" className="form-control input-lg"
@ -88,7 +89,7 @@ class ProfileEdit extends React.Component {
</div> </div>
<div className="form-group"> <div className="form-group">
<label> <label>
Registration Date: {t('user:Registration Date')}:
<input <input
name="createdAt" name="createdAt"
className="form-control input-lg" className="form-control input-lg"
@ -100,7 +101,7 @@ class ProfileEdit extends React.Component {
</div> </div>
<div className="form-group"> <div className="form-group">
<label> <label>
Password: {t('user:Password')}:
<input <input
name="password" name="password"
className="form-control input-lg" className="form-control input-lg"
@ -111,7 +112,7 @@ class ProfileEdit extends React.Component {
</div> </div>
<div className="form-group"> <div className="form-group">
<label> <label>
Password Confirmation: {t('user:Password Confirmation')}:
<input <input
name="password_conf" name="password_conf"
className="form-control input-lg" className="form-control input-lg"
@ -123,7 +124,7 @@ class ProfileEdit extends React.Component {
<hr /> <hr />
<div className="form-group"> <div className="form-group">
<label> <label>
First Name: {t('user:First Name')}:
<input <input
name="first_name" name="first_name"
className="form-control input-lg" className="form-control input-lg"
@ -135,7 +136,7 @@ class ProfileEdit extends React.Component {
</div> </div>
<div className="form-group"> <div className="form-group">
<label> <label>
Last Name: {t('user:Last Name')}:
<input <input
name="last_name" name="last_name"
className="form-control input-lg" className="form-control input-lg"
@ -147,7 +148,7 @@ class ProfileEdit extends React.Component {
</div> </div>
<div className="form-group"> <div className="form-group">
<label> <label>
Birth Date {t('user:Birth Date')}
<input <input
name="birth_date" name="birth_date"
className="form-control input-lg" className="form-control input-lg"
@ -159,7 +160,7 @@ class ProfileEdit extends React.Component {
</div> </div>
<div className="form-group"> <div className="form-group">
<label> <label>
Location: {t('user:Location')}:
<input <input
name="location" name="location"
className="form-control input-lg" className="form-control input-lg"
@ -171,7 +172,7 @@ class ProfileEdit extends React.Component {
</div> </div>
<div className="form-group"> <div className="form-group">
<label> <label>
Bio: {t('user:Bio')}:
<textarea <textarea
name="bio" name="bio"
className="form-control input-lg" className="form-control input-lg"
@ -183,7 +184,7 @@ class ProfileEdit extends React.Component {
</div> </div>
<div className="form-group"> <div className="form-group">
<label> <label>
Timezone: {t('user:Timezone')}:
<TimezonePicker <TimezonePicker
className="form-control timezone-custom-height" className="form-control timezone-custom-height"
onChange={tz => { onChange={tz => {
@ -201,28 +202,32 @@ class ProfileEdit extends React.Component {
</div> </div>
<div className="form-group"> <div className="form-group">
<label> <label>
First day of week: {t('user:First day of week')}:
<select <select
name="weekm" name="weekm"
className="form-control input-lg" className="form-control input-lg"
value={formData.weekm ? 'Monday' : 'Sunday'} value={formData.weekm ? 'Monday' : 'Sunday'}
onChange={e => this.handleFormChange(e)} onChange={e => this.handleFormChange(e)}
> >
<option value="Sunday">Sunday</option> <option value="Sunday">
<option value="Monday">Monday</option> {t('user:Sunday')}
</option>
<option value="Monday">
{t('user:Monday')}
</option>
</select> </select>
</label> </label>
</div> </div>
<input <input
type="submit" type="submit"
className="btn btn-primary btn-lg btn-block" className="btn btn-primary btn-lg btn-block"
value="Submit" value={t('common:Submit')}
/> />
<input <input
type="submit" type="submit"
className="btn btn-secondary btn-lg btn-block" className="btn btn-secondary btn-lg btn-block"
onClick={() => history.push('/profile')} onClick={() => history.push('/profile')}
value="Cancel" value={t('common:Cancel')}
/> />
</form> </form>
</div> </div>
@ -239,15 +244,17 @@ class ProfileEdit extends React.Component {
} }
} }
export default connect( export default withTranslation(
state => ({ connect(
location: state.router.location, state => ({
message: state.message, location: state.router.location,
user: state.user, message: state.message,
}), user: state.user,
dispatch => ({ }),
onHandleProfileFormSubmit: formData => { dispatch => ({
dispatch(handleProfileFormSubmit(formData)) onHandleProfileFormSubmit: formData => {
}, dispatch(handleProfileFormSubmit(formData))
}) },
)(ProfileEdit) })
)(ProfileEdit)
)

View File

@ -2,9 +2,17 @@ import i18n from 'i18next'
import LanguageDetector from 'i18next-browser-languagedetector' import LanguageDetector from 'i18next-browser-languagedetector'
import XHR from 'i18next-xhr-backend' import XHR from 'i18next-xhr-backend'
import EnActivitiesTranslations from './locales/en/activities.json'
import EnCommonTranslations from './locales/en/common.json' import EnCommonTranslations from './locales/en/common.json'
import EnDashboardTranslations from './locales/en/dashboard.json'
import EnSportsTranslations from './locales/en/sports.json'
import EnStatisticsTranslations from './locales/en/statistics.json'
import EnUserTranslations from './locales/en/user.json' import EnUserTranslations from './locales/en/user.json'
import FrActivitiesTranslations from './locales/fr/activities.json'
import FrCommonTranslations from './locales/fr/common.json' import FrCommonTranslations from './locales/fr/common.json'
import FrDashboardTranslations from './locales/fr/dashboard.json'
import FrSportsTranslations from './locales/fr/sports.json'
import FrStatisticsTranslations from './locales/fr/statistics.json'
import FrUserTranslations from './locales/fr/user.json' import FrUserTranslations from './locales/fr/user.json'
i18n i18n
@ -20,11 +28,19 @@ i18n
}, },
resources: { resources: {
en: { en: {
activities: EnActivitiesTranslations,
common: EnCommonTranslations, common: EnCommonTranslations,
dashboard: EnDashboardTranslations,
sports: EnSportsTranslations,
statistics: EnStatisticsTranslations,
user: EnUserTranslations, user: EnUserTranslations,
}, },
fr: { fr: {
activities: FrActivitiesTranslations,
common: FrCommonTranslations, common: FrCommonTranslations,
dashboard: FrDashboardTranslations,
sports: FrSportsTranslations,
statistics: FrStatisticsTranslations,
user: FrUserTranslations, user: FrUserTranslations,
}, },
}, },

View File

@ -0,0 +1,23 @@
{
"Activity Date": "Activity Date",
"Add a workout": "Add a workout",
"Ave. speed": "Ave. speed",
"Average speed": "Average speed",
"Date": "Date",
"Distance": "Distance",
"Duration": "Duration",
"Edit a workout": "Edit a workout",
"Filter": "Filter",
"From": "From",
"gpxFile": "<strong>gpx</strong> file",
"Max. speed": "Max. speed",
"no folder inside": "no folder inside",
"files max": "files max",
"max size": "max size",
"Notes": "Notes",
"Title": "Title",
"To": "To",
"with gpx file": "with gpx file",
"without gpx file": "without gpx file",
"zipFile": "or <strong> zip</strong> file containing <strong>gpx </strong> files"
}

View File

@ -1,10 +1,23 @@
{ {
"Dashboard": "Dashboard",
"Workouts": "Workouts",
"Statistics": "Statistics",
"Add workout": "Add workout", "Add workout": "Add workout",
"Register": "Register", "Cancel": "Cancel",
"Dashboard": "Dashboard",
"day": "day",
"days": "days",
"Login": "Login", "Login": "Login",
"Logout": "Logout", "Logout": "Logout",
"Submit": "Submit" "No records.": "No records.",
"No workouts.": "No workouts.",
"Register": "Register",
"Statistics": "Statistics",
"Sport": "Sport",
"sport": "sport",
"Sports": "Sports",
"sports": "sports",
"Submit": "Submit",
"to": "to",
"Workout": "Workout",
"Workouts": "Workouts",
"workout": "workout",
"workouts": "workouts"
} }

View File

@ -0,0 +1,5 @@
{
"Personal records": "Personal records",
"This month": "This month",
"Upload one !": "Upload one !"
}

View File

@ -0,0 +1,8 @@
{
"Cycling (Sport)": "Cycling (Sport)",
"Cycling (Transport)": "Cycling (Transport)",
"Hiking": "Hiking",
"Mountain Biking": "Mountain Biking",
"Running": "Running",
"Walking": "Walking"
}

View File

@ -0,0 +1,9 @@
{
"activities": "activities",
"distance": "distance",
"duration": "duration",
"month": "month",
"Statistics": "Statistics",
"year": "year",
"week": "week"
}

View File

@ -1,8 +1,27 @@
{ {
"login": "login", "Bio": "Bio",
"register": "register", "Birth Date": "Birth Date",
"Delete picture": "Delete picture",
"Edit Profile": "Edit Profile",
"Email": "Email",
"Enter a username": "Enter a username", "Enter a username": "Enter a username",
"Enter an email address": "Enter an email address", "Enter an email address": "Enter an email address",
"Enter a password": "Enter a password", "Enter a password": "Enter a password",
"Enter the password confirmation": "Enter the password confirmation" "Enter the password confirmation": "Enter the password confirmation",
"First day of week": "First day of week",
"First Name": "First Name",
"Last Name": "Last Name",
"Location": "Location",
"loggedOut": "You are now logged out. Click <1>here</1> to log back in.",
"login": "login",
"Monday": "Monday",
"Password": "Password",
"Password Confirmation": "Password Confirmation",
"Profile": "Profile",
"Profile Edition": "Profile Edition",
"register": "register",
"Registration Date": "Registration Date",
"Send": "Send",
"Sunday": "Sunday",
"Timezone": "Timezone"
} }

View File

@ -0,0 +1,23 @@
{
"Activity Date": "Date de l'activité",
"Add a workout": "Ajouter une activité",
"Ave. speed": "Vitesse moyenne",
"Average speed": "Vitesse moyenne",
"Date": "Date",
"Distance": "Distance",
"Duration": "Durée",
"Edit a workout": "Editer une activité",
"Filter": "Filtrer",
"From": "A partir de",
"gpxFile": "fichier <strong>gpx</strong>",
"Max. speed": "Vitesse max",
"no folder inside": "pas de répertoire",
"files max": " fichiers max",
"max size": "taille max",
"Notes": "Notes",
"Title": "Titre",
"To": "Jusqu'au",
"with gpx file": "avec un fichier gpx",
"without gpx file": "sans fichier gpx",
"zipFile": "ou un fichier <strong> zip</strong> contenant des fichiers <strong>gpx</strong>"
}

View File

@ -1,10 +1,23 @@
{ {
"Dashboard": "Tableau de Bord",
"Workouts": "Activités",
"Statistics": "Statistiques",
"Add workout": "Ajouter une activité", "Add workout": "Ajouter une activité",
"Register": "S'inscrire", "Cancel": "Annuler",
"Dashboard": "Tableau de Bord",
"day": "jour",
"days": "jours",
"Login": "Se connecter", "Login": "Se connecter",
"Logout": "Se déconnecter", "Logout": "Se déconnecter",
"Submit": "Valider" "No records.": "Pas de records.",
"No workouts.": "Pas d'activités.",
"Register": "S'inscrire",
"Statistics": "Statistiques",
"Sport": "Sport",
"sport": "sport",
"Sports": "Sports",
"sports": "sports",
"Submit": "Valider",
"to": "à",
"Workout": "Activité",
"Workouts": "Activités",
"workout": "activité",
"workouts": "activités"
} }

View File

@ -0,0 +1,5 @@
{
"Personal records": "Mes records",
"This month": "Ce mois",
"Upload one !": "Ajoutez votre première activité !"
}

View File

@ -0,0 +1,8 @@
{
"Cycling (Sport)": "Vélo (Sport)",
"Cycling (Transport)": "Vélo (Transport)",
"Hiking": "Randonnée",
"Mountain Biking": "VTT",
"Running": "Course",
"Walking": "Marche"
}

View File

@ -0,0 +1,9 @@
{
"activities": "activités",
"distance": "distance",
"duration": "durée",
"month": "mois",
"Statistics": "Statistiques",
"year": "année",
"week": "semaine"
}

View File

@ -1,8 +1,27 @@
{ {
"login": "se connecter", "Bio": "Bio",
"register": "s'nscrire", "Birth Date": "Date de naissance",
"Delete picture": "Supprimer l'image",
"Edit Profile": "Editer le profil",
"Email": "Email",
"Enter a username": "Saisir un nom", "Enter a username": "Saisir un nom",
"Enter an email address": "Saisir une adresse e-mail", "Enter an email address": "Saisir une adresse e-mail",
"Enter a password": "Saisir un mot de passe", "Enter a password": "Saisir un mot de passe",
"Enter the password confirmation": "Confirmer le mot de passe" "Enter the password confirmation": "Confirmer le mot de passe",
"First day of week": "Premier jour de la semaine",
"First Name": "Prénom",
"Last Name": "Nom",
"Location": "Lieu",
"loggedOut": "Vous êtes déconnecté. Cliquez <1>ici</1> pour vous reconnecter.",
"login": "se connecter",
"Monday": "Lundi",
"Password": "Mot de passe",
"Password Confirmation": "Confirmation du mot de passe",
"Profile": "Profil",
"Profile Edition": "Edition du profil",
"register": "s'inscrire",
"Registration Date": "Date d'inscription",
"Send": "Envoyer",
"Sunday": "Dimanche",
"Timezone": "Fuseau horaire"
} }

View File

@ -86,3 +86,17 @@ export const formatRecord = (record, tz) => {
value: value, value: value,
} }
} }
const sortSports = (a, b) => {
const sportALabel = a.label.toLowerCase()
const sportBLabel = b.label.toLowerCase()
return sportALabel > sportBLabel ? 1 : sportALabel < sportBLabel ? -1 : 0
}
export const translateSports = (sports, t) =>
sports
.map(sport => ({
...sport,
label: t(`sports:${sport.label}`),
}))
.sort(sortSports)