Client - application translation

This commit is contained in:
Sam 2019-09-16 12:51:44 +02:00
parent 77bc32d4a5
commit 131f315911
18 changed files with 219 additions and 111 deletions

View File

@ -11,6 +11,7 @@ export default function ActivityCardHeader(props) {
displayModal,
segmentId,
sport,
t,
title,
user,
} = props
@ -40,14 +41,14 @@ export default function ActivityCardHeader(props) {
<i
className="fa fa-chevron-left"
aria-hidden="true"
title={`See previous ${dataType}`}
title={t(`activities:See previous ${dataType}`)}
/>
</Link>
) : (
<i
className="fa fa-chevron-left inactive-link"
aria-hidden="true"
title={`No previous ${dataType}`}
title={t(`activities:No previous ${dataType}`)}
/>
)}
</div>
@ -62,14 +63,14 @@ export default function ActivityCardHeader(props) {
<i
className="fa fa-edit custom-fa"
aria-hidden="true"
title="Edit activity"
title={t('activities:Edit activity')}
/>
</Link>
<i
className="fa fa-trash custom-fa"
aria-hidden="true"
onClick={() => displayModal(true)}
title="Delete activity"
title={t('activities:Delete activity')}
/>
</>
) : (
@ -80,7 +81,7 @@ export default function ActivityCardHeader(props) {
>
{title}
</Link>{' '}
- segment {segmentId}
- {t('activities:segment')} {segmentId}
</>
)}
<br />
@ -96,14 +97,14 @@ export default function ActivityCardHeader(props) {
<i
className="fa fa-chevron-right"
aria-hidden="true"
title={`See next ${dataType}`}
title={t(`activities:See next ${dataType}`)}
/>
</Link>
) : (
<i
className="fa fa-chevron-right inactive-link"
aria-hidden="true"
title={`No next ${dataType}`}
title={t(`activities:No next ${dataType}`)}
/>
)}
</div>

View File

@ -79,7 +79,7 @@ class ActivityCharts extends React.Component {
}
render() {
const { chartData, updateCoordinates } = this.props
const { chartData, t, updateCoordinates } = this.props
const { displayDistance } = this.state
const xInterval = chartData ? parseInt(chartData.length / 10, 10) : 0
let xDataKey, xScale
@ -102,7 +102,7 @@ class ActivityCharts extends React.Component {
checked={displayDistance}
onChange={e => this.handleRadioChange(e)}
/>
distance
{t('activities:distance')}
</label>
<label className="radioLabel col-md-1">
<input
@ -111,7 +111,7 @@ class ActivityCharts extends React.Component {
checked={!displayDistance}
onChange={e => this.handleRadioChange(e)}
/>
duration
{t('activities:duration')}
</label>
</div>
<div className="row chart-radio">
@ -123,7 +123,7 @@ class ActivityCharts extends React.Component {
checked={this.displayData('speed')}
onChange={e => this.handleLegendChange(e)}
/>
speed
{t('activities:speed')}
</label>
<label className="radioLabel col-md-1">
<input
@ -132,7 +132,7 @@ class ActivityCharts extends React.Component {
checked={this.displayData('elevation')}
onChange={e => this.handleLegendChange(e)}
/>
elevation
{t('activities:elevation')}
</label>
<div className="col-md-5" />
</div>
@ -147,7 +147,11 @@ class ActivityCharts extends React.Component {
<XAxis
allowDecimals={false}
dataKey={xDataKey}
label={{ value: xDataKey, offset: 0, position: 'bottom' }}
label={{
value: t(`activities:${xDataKey}`),
offset: 0,
position: 'bottom',
}}
scale={xScale}
interval={xInterval}
tickFormatter={value =>
@ -157,7 +161,7 @@ class ActivityCharts extends React.Component {
/>
<YAxis
label={{
value: 'speed (km/h)',
value: `${t('activities:speed')} (km/h)`,
angle: -90,
position: 'left',
}}
@ -165,7 +169,7 @@ class ActivityCharts extends React.Component {
/>
<YAxis
label={{
value: 'altitude (m)',
value: `${t('activities:elevation')} (m)`,
angle: -90,
position: 'right',
}}
@ -177,6 +181,7 @@ class ActivityCharts extends React.Component {
yAxisId="right"
type="linear"
dataKey="elevation"
name={t('activities:elevation')}
fill="#e5e5e5"
stroke="#cccccc"
dot={false}
@ -188,6 +193,7 @@ class ActivityCharts extends React.Component {
yAxisId="left"
type="linear"
dataKey="speed"
name={t('activities:speed')}
stroke="#8884d8"
strokeWidth={2}
dot={false}
@ -197,19 +203,22 @@ class ActivityCharts extends React.Component {
<Tooltip
labelFormatter={value =>
displayDistance
? `distance: ${value} km`
: `duration: ${format(value, 'HH:mm:ss')}`
? `${t('activities:distance')}: ${value} km`
: `${t('activities:duration')}: ${format(
value,
'HH:mm:ss'
)}`
}
/>
</ComposedChart>
</ResponsiveContainer>
</div>
<div className="chart-info">
data from gpx, without any cleaning
{t('activities:data from gpx, without any cleaning')}
</div>
</div>
) : (
'No data to display'
t('activities:No data to display')
)}
</div>
)

View File

@ -3,13 +3,13 @@ import React from 'react'
import ActivityWeather from './ActivityWeather'
export default function ActivityDetails(props) {
const { activity } = props
const { activity, t } = props
const withPauses = activity.pauses !== '0:00:00' && activity.pauses !== null
return (
<div className="activity-details">
<p>
<i className="fa fa-clock-o custom-fa" aria-hidden="true" />
Duration: {activity.moving}
{t('activities:Duration')}: {activity.moving}
{activity.records &&
activity.records.find(r => r.record_type === 'LD') && (
<sup>
@ -18,14 +18,14 @@ export default function ActivityDetails(props) {
)}
{withPauses && (
<span>
<br />
(pauses: {activity.pauses}, total duration: {activity.duration})
<br />({t('activities:pauses')}: {activity.pauses},{' '}
{t('activities:total duration')}: {activity.duration})
</span>
)}
</p>
<p>
<i className="fa fa-road custom-fa" aria-hidden="true" />
Distance: {activity.distance} km
{t('activities:Distance')}: {activity.distance} km
{activity.records &&
activity.records.find(r => r.record_type === 'FD') && (
<sup>
@ -35,7 +35,7 @@ export default function ActivityDetails(props) {
</p>
<p>
<i className="fa fa-tachometer custom-fa" aria-hidden="true" />
Average speed: {activity.ave_speed} km/h
{t('activities:Average speed')}: {activity.ave_speed} km/h
{activity.records &&
activity.records.find(r => r.record_type === 'AS') && (
<sup>
@ -43,7 +43,7 @@ export default function ActivityDetails(props) {
</sup>
)}
<br />
Max speed : {activity.max_speed} km/h
{t('activities:Max. speed')}: {activity.max_speed} km/h
{activity.records &&
activity.records.find(r => r.record_type === 'MS') && (
<sup>
@ -54,20 +54,20 @@ export default function ActivityDetails(props) {
{activity.min_alt && activity.max_alt && (
<p>
<i className="fi-mountains custom-fa" />
Min altitude: {activity.min_alt}m
{t('activities:Min. altitude')}: {activity.min_alt}m
<br />
Max altitude: {activity.max_alt}m
{t('activities:Max. altitude')}: {activity.max_alt}m
</p>
)}
{activity.ascent && activity.descent && (
<p>
<i className="fa fa-location-arrow custom-fa" />
Ascent: {activity.ascent}m
{t('activities:Ascent')}: {activity.ascent}m
<br />
Descent: {activity.descent}m
{t('activities:Descent')}: {activity.descent}m
</p>
)}
<ActivityWeather activity={activity} />
<ActivityWeather activity={activity} t={t} />
</div>
)
}

View File

@ -1,5 +1,8 @@
import React from 'react'
export default function ActivityNoMap() {
return <div className="activity-no-map text-center">No Map</div>
export default function ActivityNoMap(props) {
const { t } = props
return (
<div className="activity-no-map text-center">{t('activities:No Map')}</div>
)
}

View File

@ -1,14 +1,16 @@
import React from 'react'
export default function ActivityNotes(props) {
const { notes } = props
const { notes, t } = props
return (
<div className="row">
<div className="col">
<div className="card activity-card">
<div className="card-body">
Notes
<div className="activity-notes">{notes ? notes : 'No notes'}</div>
<div className="activity-notes">
{notes ? notes : t('activities:No notes')}
</div>
</div>
</div>
</div>

View File

@ -2,13 +2,13 @@ import React from 'react'
import { Link } from 'react-router-dom'
export default function ActivitySegments(props) {
const { segments } = props
const { segments, t } = props
return (
<div className="row">
<div className="col">
<div className="card activity-card">
<div className="card-body">
Segments
{t('activities:Segments')}
<div className="activity-segments">
<ul>
{segments.map((segment, index) => (
@ -21,9 +21,10 @@ export default function ActivitySegments(props) {
to={`/activities/${segment.activity_id}/segment/${index +
1}`}
>
segment {index + 1}
{t('activities:segment')} {index + 1}
</Link>{' '}
({segment.distance} km, duration: {segment.duration})
({t('activities:distance')}: {segment.distance} km,{' '}
{t('activities:duration')}: {segment.duration})
</li>
))}
</ul>

View File

@ -1,7 +1,7 @@
import React from 'react'
export default function ActivityWeather(props) {
const { activity } = props
const { activity, t } = props
return (
<div className="container">
{activity.weather_start && activity.weather_end && (
@ -10,7 +10,7 @@ export default function ActivityWeather(props) {
<tr>
<th />
<th>
Start
{t('activities:Start')}
<br />
<img
className="weather-img"
@ -20,7 +20,7 @@ export default function ActivityWeather(props) {
/>
</th>
<th>
End
{t('activities:End')}
<br />
<img
className="weather-img"

View File

@ -1,5 +1,6 @@
import React from 'react'
import { Helmet } from 'react-helmet'
import { withTranslation } from 'react-i18next'
import { connect } from 'react-redux'
import ActivityCardHeader from './ActivityCardHeader'
@ -62,7 +63,14 @@ class ActivityDisplay extends React.Component {
}
render() {
const { activities, message, onDeleteActivity, sports, user } = this.props
const {
activities,
message,
onDeleteActivity,
sports,
t,
user,
} = this.props
const { coordinates, displayModal } = this.state
const [activity] = activities
const title = activity ? activity.title : 'Activity'
@ -82,8 +90,10 @@ class ActivityDisplay extends React.Component {
<div className="container">
{displayModal && (
<CustomModal
title="Confirmation"
text="Are you sure you want to delete this activity?"
title={t('activities:Confirmation')}
text={t(
'activities:Are you sure you want to delete this activity?'
)}
confirm={() => {
onDeleteActivity(activity.id)
this.displayModal(false)
@ -102,6 +112,7 @@ class ActivityDisplay extends React.Component {
dataType={dataType}
segmentId={segmentId}
sport={sport}
t={t}
title={title}
user={user}
displayModal={() => this.displayModal(true)}
@ -118,7 +129,7 @@ class ActivityDisplay extends React.Component {
segmentId={segmentId}
/>
) : (
<ActivityNoMap />
<ActivityNoMap t={t} />
)}
</div>
<div className="col">
@ -128,6 +139,7 @@ class ActivityDisplay extends React.Component {
? activity
: activity.segments[segmentId - 1]
}
t={t}
/>
</div>
</div>
@ -147,6 +159,7 @@ class ActivityDisplay extends React.Component {
activity={activity}
dataType={dataType}
segmentId={segmentId}
t={t}
updateCoordinates={e =>
this.updateCoordinates(e)
}
@ -160,9 +173,9 @@ class ActivityDisplay extends React.Component {
)}
{dataType === 'activity' && (
<>
<ActivityNotes notes={activity.notes} />
<ActivityNotes notes={activity.notes} t={t} />
{activity.segments.length > 1 && (
<ActivitySegments segments={activity.segments} />
<ActivitySegments segments={activity.segments} t={t} />
)}
</>
)}
@ -175,19 +188,21 @@ class ActivityDisplay extends React.Component {
}
}
export default connect(
state => ({
activities: state.activities.data,
message: state.message,
sports: state.sports.data,
user: state.user,
}),
dispatch => ({
loadActivity: activityId => {
dispatch(getOrUpdateData('getData', 'activities', { id: activityId }))
},
onDeleteActivity: activityId => {
dispatch(deleteActivity(activityId))
},
})
)(ActivityDisplay)
export default withTranslation()(
connect(
state => ({
activities: state.activities.data,
message: state.message,
sports: state.sports.data,
user: state.user,
}),
dispatch => ({
loadActivity: activityId => {
dispatch(getOrUpdateData('getData', 'activities', { id: activityId }))
},
onDeleteActivity: activityId => {
dispatch(deleteActivity(activityId))
},
})
)(ActivityDisplay)
)

View File

@ -1,6 +1,8 @@
import React from 'react'
import { useTranslation } from 'react-i18next'
export default function CustomModal(props) {
const { t } = useTranslation()
return (
<div className="custom-modal-backdrop">
<div className="custom-modal">
@ -11,7 +13,7 @@ export default function CustomModal(props) {
type="button"
className="close"
aria-label="Close"
onClick={() => props.close}
onClick={() => props.close()}
>
<span aria-hidden="true">&times;</span>
</button>
@ -25,14 +27,14 @@ export default function CustomModal(props) {
className="btn btn-primary"
onClick={() => props.confirm()}
>
Yes
{t('common:Yes')}
</button>
<button
type="button"
className="btn btn-secondary"
onClick={() => props.close()}
>
No
{t('common:No')}
</button>
</div>
</div>

View File

@ -89,6 +89,7 @@ export default class StatsCharts extends React.PureComponent {
''
)
}
name={t(`sports:${s.label}`)}
/>
))}
</BarChart>

View File

@ -6,7 +6,7 @@ import StaticMap from '../Common/StaticMap'
import { getDateWithTZ } from '../../utils'
export default function ActivityCard(props) {
const { activity, sports, user } = props
const { activity, sports, t, user } = props
return (
<div className="card activity-card text-center">
@ -14,7 +14,7 @@ export default function ActivityCard(props) {
<Link to={`/activities/${activity.id}`}>
{sports
.filter(sport => sport.id === activity.sport_id)
.map(sport => sport.label)}{' '}
.map(sport => t(`sports:${sport.label}`))}{' '}
-{' '}
{format(
getDateWithTZ(activity.activity_date, user.timezone),
@ -31,8 +31,8 @@ export default function ActivityCard(props) {
)}
<div className="col">
<p>
<i className="fa fa-clock-o" aria-hidden="true" /> Duration:{' '}
{activity.moving}
<i className="fa fa-clock-o" aria-hidden="true" />{' '}
{t('activities:Duration')}: {activity.moving}
{activity.map ? (
<span>
<br />
@ -41,8 +41,8 @@ export default function ActivityCard(props) {
) : (
' - '
)}
<i className="fa fa-road" aria-hidden="true" /> Distance:{' '}
{activity.distance} km
<i className="fa fa-road" aria-hidden="true" />{' '}
{t('activities:Distance')}: {activity.distance} km
</p>
</div>
</div>

View File

@ -1,12 +1,13 @@
import React from 'react'
import { Link } from 'react-router-dom'
import { formatRecord } from '../../utils/activities'
import { formatRecord, translateSports } from '../../utils/activities'
export default function RecordsCard(props) {
const { records, sports, t, user } = props
const translatedSports = translateSports(sports, t)
const recordsBySport = records.reduce((sportList, record) => {
const sport = sports.find(s => s.id === record.sport_id)
const sport = translatedSports.find(s => s.id === record.sport_id)
if (sportList[sport.label] === void 0) {
sportList[sport.label] = {
img: sport.img,
@ -19,42 +20,44 @@ export default function RecordsCard(props) {
return (
<div className="card activity-card">
<div className="card-header">Personal records</div>
<div className="card-header">{t('activities:Personal records')}</div>
<div className="card-body">
{Object.keys(recordsBySport).length === 0
? t('common:No records.')
: Object.keys(recordsBySport).map(sportLabel => (
<table
className="table table-borderless table-sm record-table"
key={sportLabel}
>
<thead>
<tr>
<th colSpan="3">
<img
alt={`${sportLabel} logo`}
className="record-logo"
src={recordsBySport[sportLabel].img}
/>
{sportLabel}
</th>
</tr>
</thead>
<tbody>
{recordsBySport[sportLabel].records.map(rec => (
<tr key={rec.id}>
<td>{rec.record_type}</td>
<td className="text-right">{rec.value}</td>
<td className="text-right">
<Link to={`/activities/${rec.activity_id}`}>
{rec.activity_date}
</Link>
</td>
: Object.keys(recordsBySport)
.sort()
.map(sportLabel => (
<table
className="table table-borderless table-sm record-table"
key={sportLabel}
>
<thead>
<tr>
<th colSpan="3">
<img
alt={`${sportLabel} logo`}
className="record-logo"
src={recordsBySport[sportLabel].img}
/>
{sportLabel}
</th>
</tr>
))}
</tbody>
</table>
))}
</thead>
<tbody>
{recordsBySport[sportLabel].records.map(rec => (
<tr key={rec.id}>
<td>{t(`activities:${rec.record_type}`)}</td>
<td className="text-right">{rec.value}</td>
<td className="text-right">
<Link to={`/activities/${rec.activity_id}`}>
{rec.activity_date}
</Link>
</td>
</tr>
))}
</tbody>
</table>
))}
</div>
</div>
)

View File

@ -69,6 +69,7 @@ class DashBoard extends React.Component {
activity={activity}
key={activity.id}
sports={sports}
t={t}
user={user}
/>
))

View File

@ -1,22 +1,55 @@
{
"Activity Date": "Activity Date",
"Add a workout": "Add a workout",
"Are you sure you want to delete this activity?": "Are you sure you want to delete this activity?",
"Ave. speed": "Ave. speed",
"Ascent": "Ascent",
"Average speed": "Average speed",
"Confirmation": "Confirmation",
"data from gpx, without any cleaning": "data from gpx, without any cleaning",
"Date": "Date",
"Delete activity": "Delete activity",
"Descent": "Descent",
"Distance": "Distance",
"distance": "distance",
"Duration": "Duration",
"duration": "duration",
"Edit a workout": "Edit a workout",
"Edit activity": "Edit activity",
"elevation": "elevation",
"End": "End",
"Farest distance": "Farest distance",
"Filter": "Filter",
"From": "From",
"gpxFile": "<strong>gpx</strong> file",
"Longest duration": "Longest duration",
"Max. altitude" : "Max. altitude",
"Max. speed": "Max. speed",
"Min. altitude": "Min. altitude",
"no folder inside": "no folder inside",
"files max": "files max",
"max size": "max size",
"No data to display": "No data to display",
"No Map": "No Map",
"No next activity": "No next activity",
"No next segment": "No next segment",
"No notes": "No notes",
"No previous activity": "No previous activity",
"No previous segment": "No previous segment",
"Notes": "Notes",
"pauses": "pauses",
"Personal records": "Personal records",
"See next activity": "See next activity",
"See next segment": "See next segment",
"See previous activity": "See previous activity",
"See previous segment": "See previous segment",
"segment": "segment",
"Segments": "Segments",
"speed": "speed",
"Start": "Start",
"Title": "Title",
"To": "To",
"total duration": "total duration",
"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

@ -6,6 +6,7 @@
"days": "days",
"Login": "Login",
"Logout": "Logout",
"No": "No",
"No records.": "No records.",
"No workouts.": "No workouts.",
"Register": "Register",
@ -19,5 +20,6 @@
"Workout": "Workout",
"Workouts": "Workouts",
"workout": "workout",
"workouts": "workouts"
"workouts": "workouts",
"Yes": "Yes"
}

View File

@ -1,22 +1,55 @@
{
"Activity Date": "Date de l'activité",
"Add a workout": "Ajouter une activité",
"Are you sure you want to delete this activity?": "Etes-vous sûr de vouloir supprimer cette activité ?",
"Ave. speed": "Vitesse moyenne",
"Ascent": "Dénivelé positif",
"Average speed": "Vitesse moyenne",
"Confirmation": "Confirmation",
"data from gpx, without any cleaning": "données issues du fichier gpx, sans correction",
"Date": "Date",
"Delete activity": "Supprimer l'activité",
"Descent": "Dénivelé négatif",
"Distance": "Distance",
"distance": "distance",
"Duration": "Durée",
"duration": "durée",
"Edit a workout": "Editer une activité",
"Edit activity": "Editer une activity",
"elevation": "altitude",
"End": "Arrivée",
"Farest distance": "Distance la + longue",
"Filter": "Filtrer",
"From": "A partir de",
"gpxFile": "fichier <strong>gpx</strong>",
"Longest duration": "Durée la + longue",
"Max. altitude" : "Altitude max",
"Max. speed": "Vitesse max",
"Min. altitude": "Altitude min",
"no folder inside": "pas de répertoire",
"files max": " fichiers max",
"max size": "taille max",
"No data to display": "Pas de données à afficher",
"No Map": "Pas de carte",
"No next activity": "Pas d'activité suivante",
"No next segment": "Pas de segment suivant",
"No notes": "Pas de notes",
"No previous activity": "Pas d'activité précédente",
"No previous segment": "Pas de segment précédent",
"Notes": "Notes",
"pauses": "pauses",
"Personal records": "Records personnels",
"See next activity": "Voir l'activité suivante",
"See next segment": "Voir le segment suivant",
"See previous activity": "Voir l'activité précédente",
"See previous segment": "Voir le segment précédent",
"segment": "segment",
"Segments": "Segments",
"Start": "Départ",
"speed": "vitesse",
"Title": "Titre",
"To": "Jusqu'au",
"total duration": "durée totale",
"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

@ -6,6 +6,7 @@
"days": "jours",
"Login": "Se connecter",
"Logout": "Se déconnecter",
"No": "Non",
"No records.": "Pas de records.",
"No workouts.": "Pas d'activités.",
"Register": "S'inscrire",
@ -19,5 +20,6 @@
"Workout": "Activité",
"Workouts": "Activités",
"workout": "activité",
"workouts": "activités"
"workouts": "activités",
"Yes": "Oui"
}

View File

@ -14,10 +14,10 @@ export const activityColors = [
]
export const recordsLabels = [
{ record_type: 'AS', label: 'Avg speed' },
{ record_type: 'AS', label: 'Ave. speed' },
{ record_type: 'FD', label: 'Farest distance' },
{ record_type: 'LD', label: 'Longest duration' },
{ record_type: 'MS', label: 'Max speed' },
{ record_type: 'MS', label: 'Max. speed' },
]
export const getGeoJson = gpxContent => {