Translate TrackPage
This commit is contained in:
parent
ab6cc6f6d0
commit
6f7c8d54f2
18
frontend/src/components/Visibility.tsx
Normal file
18
frontend/src/components/Visibility.tsx
Normal file
|
@ -0,0 +1,18 @@
|
||||||
|
import React from "react";
|
||||||
|
import { Icon } from "semantic-ui-react";
|
||||||
|
import { useTranslation } from "react-i18next";
|
||||||
|
|
||||||
|
export default function Visibility({ public: public_ }: { public: boolean }) {
|
||||||
|
const { t } = useTranslation();
|
||||||
|
const icon = public_ ? (
|
||||||
|
<Icon color="blue" name="eye" fitted />
|
||||||
|
) : (
|
||||||
|
<Icon name="eye slash" fitted />
|
||||||
|
);
|
||||||
|
const text = public_ ? t("general.public") : t("general.private");
|
||||||
|
return (
|
||||||
|
<>
|
||||||
|
{icon} {text}
|
||||||
|
</>
|
||||||
|
);
|
||||||
|
}
|
|
@ -9,3 +9,4 @@ export {default as Page} from './Page'
|
||||||
export {default as Stats} from './Stats'
|
export {default as Stats} from './Stats'
|
||||||
export {default as StripMarkdown} from './StripMarkdown'
|
export {default as StripMarkdown} from './StripMarkdown'
|
||||||
export {default as Chart} from './Chart'
|
export {default as Chart} from './Chart'
|
||||||
|
export {default as Visibility} from './Visibility'
|
||||||
|
|
|
@ -1,40 +1,40 @@
|
||||||
import React from 'react'
|
import React from 'react'
|
||||||
import {Link} from 'react-router-dom'
|
import {Link} from 'react-router-dom'
|
||||||
import {Icon, Popup, Button, Dropdown} from 'semantic-ui-react'
|
import {Icon, Popup, Button, Dropdown} from 'semantic-ui-react'
|
||||||
|
import { useTranslation } from "react-i18next";
|
||||||
|
|
||||||
export default function TrackActions({slug, isAuthor, onDownload}) {
|
export default function TrackActions({slug, isAuthor, onDownload}) {
|
||||||
|
const { t } = useTranslation();
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<>
|
<>
|
||||||
{isAuthor && (
|
|
||||||
<Link to={`/tracks/${slug}/edit`}>
|
|
||||||
<Button primary>Edit track</Button>
|
|
||||||
</Link>
|
|
||||||
)}
|
|
||||||
|
|
||||||
<Dropdown text="Download" button upward>
|
|
||||||
<Dropdown.Menu>
|
|
||||||
<Dropdown.Item text="Original" onClick={() => onDownload('original.csv')} disabled={!isAuthor} />
|
|
||||||
<Dropdown.Item text="Track (GPX)" onClick={() => onDownload('track.gpx')} />
|
|
||||||
</Dropdown.Menu>
|
|
||||||
</Dropdown>
|
|
||||||
|
|
||||||
<Popup
|
<Popup
|
||||||
trigger={<Icon name="info circle" />}
|
trigger={<Icon name="info circle" />}
|
||||||
offset={[12, 0]}
|
offset={[12, 0]}
|
||||||
content={
|
content={
|
||||||
isAuthor ? (
|
isAuthor ? (
|
||||||
<>
|
<>
|
||||||
<p>Only you, the author of this track, can download the original file.</p>
|
<p>{t('TrackPage.actions.hintAuthorOnly')}</p>
|
||||||
<p>
|
<p>{t('TrackPage.actions.hintOriginal')}</p>
|
||||||
This is the file as it was uploaded to the server, without modifications, and it can be used with other
|
|
||||||
tools.
|
|
||||||
</p>
|
|
||||||
</>
|
</>
|
||||||
) : (
|
) : (
|
||||||
<p>Only the author of this track can download the original file.</p>
|
<p>{t('TrackPage.actions.hintAuthorOnlyOthers')}</p>
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
/>
|
/>
|
||||||
|
|
||||||
|
<Dropdown text={t('TrackPage.actions.download')} button>
|
||||||
|
<Dropdown.Menu>
|
||||||
|
<Dropdown.Item text={t('TrackPage.actions.original')}onClick={() => onDownload('original.csv')} disabled={!isAuthor} />
|
||||||
|
<Dropdown.Item text={t('TrackPage.actions.gpx')} onClick={() => onDownload('track.gpx')} />
|
||||||
|
</Dropdown.Menu>
|
||||||
|
</Dropdown>
|
||||||
|
|
||||||
|
{isAuthor && (
|
||||||
|
<Link to={`/tracks/${slug}/edit`}>
|
||||||
|
<Button primary>{t('TrackPage.actions.edit')}</Button>
|
||||||
|
</Link>
|
||||||
|
)}
|
||||||
</>
|
</>
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
|
@ -1,31 +1,57 @@
|
||||||
import React from 'react'
|
import React from "react";
|
||||||
import {Message, Segment, Form, Button, Loader, Header, Comment} from 'semantic-ui-react'
|
import {
|
||||||
import Markdown from 'react-markdown'
|
Message,
|
||||||
|
Segment,
|
||||||
|
Form,
|
||||||
|
Button,
|
||||||
|
Loader,
|
||||||
|
Header,
|
||||||
|
Comment,
|
||||||
|
} from "semantic-ui-react";
|
||||||
|
import Markdown from "react-markdown";
|
||||||
|
import { useTranslation } from "react-i18next";
|
||||||
|
|
||||||
import {Avatar, FormattedDate} from 'components'
|
import { Avatar, FormattedDate } from "components";
|
||||||
|
|
||||||
function CommentForm({onSubmit}) {
|
function CommentForm({ onSubmit }) {
|
||||||
const [body, setBody] = React.useState('')
|
const { t } = useTranslation();
|
||||||
|
const [body, setBody] = React.useState("");
|
||||||
|
|
||||||
const onSubmitComment = React.useCallback(() => {
|
const onSubmitComment = React.useCallback(() => {
|
||||||
onSubmit({body})
|
onSubmit({ body });
|
||||||
setBody('')
|
setBody("");
|
||||||
}, [onSubmit, body])
|
}, [onSubmit, body]);
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<Form reply onSubmit={onSubmitComment}>
|
<Form reply onSubmit={onSubmitComment}>
|
||||||
<Form.TextArea rows={4} value={body} onChange={(e) => setBody(e.target.value)} />
|
<Form.TextArea
|
||||||
<Button content="Post comment" labelPosition="left" icon="edit" primary />
|
rows={4}
|
||||||
|
value={body}
|
||||||
|
onChange={(e) => setBody(e.target.value)}
|
||||||
|
/>
|
||||||
|
<Button
|
||||||
|
content={t("TrackPage.comments.post")}
|
||||||
|
labelPosition="left"
|
||||||
|
icon="edit"
|
||||||
|
primary
|
||||||
|
/>
|
||||||
</Form>
|
</Form>
|
||||||
)
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
export default function TrackComments({comments, onSubmit, onDelete, login, hideLoader}) {
|
export default function TrackComments({
|
||||||
|
comments,
|
||||||
|
onSubmit,
|
||||||
|
onDelete,
|
||||||
|
login,
|
||||||
|
hideLoader,
|
||||||
|
}) {
|
||||||
|
const { t } = useTranslation();
|
||||||
return (
|
return (
|
||||||
<>
|
<>
|
||||||
<Comment.Group>
|
<Comment.Group>
|
||||||
<Header as="h2" dividing>
|
<Header as="h2" dividing>
|
||||||
Comments
|
{t("TrackPage.comments.title")}
|
||||||
</Header>
|
</Header>
|
||||||
|
|
||||||
<Loader active={!hideLoader && comments == null} inline />
|
<Loader active={!hideLoader && comments == null} inline />
|
||||||
|
@ -47,11 +73,11 @@ export default function TrackComments({comments, onSubmit, onDelete, login, hide
|
||||||
<Comment.Actions>
|
<Comment.Actions>
|
||||||
<Comment.Action
|
<Comment.Action
|
||||||
onClick={(e) => {
|
onClick={(e) => {
|
||||||
onDelete(comment.id)
|
onDelete(comment.id);
|
||||||
e.preventDefault()
|
e.preventDefault();
|
||||||
}}
|
}}
|
||||||
>
|
>
|
||||||
Delete
|
{t('general.delete')}
|
||||||
</Comment.Action>
|
</Comment.Action>
|
||||||
</Comment.Actions>
|
</Comment.Actions>
|
||||||
)}
|
)}
|
||||||
|
@ -59,10 +85,12 @@ export default function TrackComments({comments, onSubmit, onDelete, login, hide
|
||||||
</Comment>
|
</Comment>
|
||||||
))}
|
))}
|
||||||
|
|
||||||
{comments != null && !comments.length && <Message>Nobody commented... yet</Message>}
|
{comments != null && !comments.length && (
|
||||||
|
<Message>{t("TrackPage.comments.empty")}</Message>
|
||||||
|
)}
|
||||||
|
|
||||||
{login && comments != null && <CommentForm onSubmit={onSubmit} />}
|
{login && comments != null && <CommentForm onSubmit={onSubmit} />}
|
||||||
</Comment.Group>
|
</Comment.Group>
|
||||||
</>
|
</>
|
||||||
)
|
);
|
||||||
}
|
}
|
||||||
|
|
|
@ -1,80 +1,85 @@
|
||||||
import React from 'react'
|
import React from "react";
|
||||||
import {List} from 'semantic-ui-react'
|
import _ from "lodash";
|
||||||
import {Duration} from 'luxon'
|
import { List, Header, Grid } from "semantic-ui-react";
|
||||||
|
import { Duration } from "luxon";
|
||||||
|
import { useTranslation } from "react-i18next";
|
||||||
|
|
||||||
import {FormattedDate} from 'components'
|
import { FormattedDate, Visibility } from "components";
|
||||||
|
|
||||||
function formatDuration(seconds) {
|
function formatDuration(seconds) {
|
||||||
return Duration.fromMillis((seconds ?? 0) * 1000).toFormat("h'h' mm'm'")
|
return Duration.fromMillis((seconds ?? 0) * 1000).toFormat("h'h' mm'm'");
|
||||||
}
|
}
|
||||||
|
|
||||||
export default function TrackDetails({track, isAuthor}) {
|
export default function TrackDetails({ track, isAuthor }) {
|
||||||
|
const { t } = useTranslation();
|
||||||
|
|
||||||
|
const items = [
|
||||||
|
track.public != null &&
|
||||||
|
isAuthor && [
|
||||||
|
t("TrackPage.details.visibility"),
|
||||||
|
<Visibility public={track.public} />,
|
||||||
|
],
|
||||||
|
|
||||||
|
track.uploadedByUserAgent != null && [
|
||||||
|
t("TrackPage.details.uploadedWith"),
|
||||||
|
track.uploadedByUserAgent,
|
||||||
|
],
|
||||||
|
|
||||||
|
track.duration != null && [
|
||||||
|
t("TrackPage.details.duration"),
|
||||||
|
formatDuration(track.duration),
|
||||||
|
],
|
||||||
|
|
||||||
|
track.createdAt != null && [
|
||||||
|
t("TrackPage.details.uploadedDate"),
|
||||||
|
<FormattedDate date={track.createdAt} />,
|
||||||
|
],
|
||||||
|
|
||||||
|
track?.recordedAt != null && [
|
||||||
|
t("TrackPage.details.recordedDate"),
|
||||||
|
<FormattedDate date={track?.recordedAt} />,
|
||||||
|
],
|
||||||
|
|
||||||
|
track?.numEvents != null && [
|
||||||
|
t("TrackPage.details.numEvents"),
|
||||||
|
track?.numEvents,
|
||||||
|
],
|
||||||
|
|
||||||
|
track?.length != null && [
|
||||||
|
t("TrackPage.details.length"),
|
||||||
|
`${(track?.length / 1000).toFixed(2)} km`,
|
||||||
|
],
|
||||||
|
|
||||||
|
track?.processingStatus != null &&
|
||||||
|
track?.processingStatus != "error" && [
|
||||||
|
t("TrackPage.details.processingStatus"),
|
||||||
|
track.processingStatus,
|
||||||
|
],
|
||||||
|
|
||||||
|
track.originalFileName != null && [
|
||||||
|
t("TrackPage.details.originalFileName"),
|
||||||
|
<code>{track.originalFileName}</code>,
|
||||||
|
],
|
||||||
|
].filter(Boolean);
|
||||||
|
|
||||||
|
const COLUMNS = 4;
|
||||||
|
const chunkSize = Math.ceil(items.length / COLUMNS)
|
||||||
return (
|
return (
|
||||||
<List horizontal relaxed>
|
<Grid>
|
||||||
{track.public != null && isAuthor && (
|
<Grid.Row columns={COLUMNS}>
|
||||||
<List.Item>
|
{_.chunk(items, chunkSize).map((chunkItems, idx) => (
|
||||||
<List.Header>Visibility</List.Header>
|
<Grid.Column key={idx}>
|
||||||
{track.public ? 'Public' : 'Private'}
|
|
||||||
</List.Item>
|
|
||||||
)}
|
|
||||||
|
|
||||||
{track.originalFileName != null && (
|
<List>
|
||||||
<List.Item>
|
{chunkItems.map(([title, value]) => (
|
||||||
{isAuthor && <div style={{float: 'right'}}></div>}
|
<List.Item key={title}>
|
||||||
|
<List.Header>{title}</List.Header>
|
||||||
<List.Header>Original Filename</List.Header>
|
<List.Description>{value}</List.Description>
|
||||||
<code>{track.originalFileName}</code>
|
</List.Item>))}
|
||||||
</List.Item>
|
|
||||||
)}
|
|
||||||
|
|
||||||
{track.uploadedByUserAgent != null && (
|
|
||||||
<List.Item>
|
|
||||||
<List.Header>Uploaded with</List.Header>
|
|
||||||
{track.uploadedByUserAgent}
|
|
||||||
</List.Item>
|
|
||||||
)}
|
|
||||||
|
|
||||||
{track.duration != null && (
|
|
||||||
<List.Item>
|
|
||||||
<List.Header>Duration</List.Header>
|
|
||||||
{formatDuration(track.duration)}
|
|
||||||
</List.Item>
|
|
||||||
)}
|
|
||||||
|
|
||||||
{track.createdAt != null && (
|
|
||||||
<List.Item>
|
|
||||||
<List.Header>Uploaded on</List.Header>
|
|
||||||
<FormattedDate date={track.createdAt} />
|
|
||||||
</List.Item>
|
|
||||||
)}
|
|
||||||
|
|
||||||
{track?.recordedAt != null && (
|
|
||||||
<List.Item>
|
|
||||||
<List.Header>Recorded on</List.Header>
|
|
||||||
<FormattedDate date={track?.recordedAt} />
|
|
||||||
</List.Item>
|
|
||||||
)}
|
|
||||||
|
|
||||||
{track?.numEvents != null && (
|
|
||||||
<List.Item>
|
|
||||||
<List.Header>Confirmed events</List.Header>
|
|
||||||
{track?.numEvents}
|
|
||||||
</List.Item>
|
|
||||||
)}
|
|
||||||
|
|
||||||
{track?.length != null && (
|
|
||||||
<List.Item>
|
|
||||||
<List.Header>Length</List.Header>
|
|
||||||
{(track?.length / 1000).toFixed(2)} km
|
|
||||||
</List.Item>
|
|
||||||
)}
|
|
||||||
|
|
||||||
{track?.processingStatus != null && track?.processingStatus != 'error' && (
|
|
||||||
<List.Item>
|
|
||||||
<List.Header>Processing</List.Header>
|
|
||||||
{track.processingStatus}
|
|
||||||
</List.Item>
|
|
||||||
)}
|
|
||||||
</List>
|
</List>
|
||||||
)
|
</Grid.Column>
|
||||||
|
))}
|
||||||
|
</Grid.Row>
|
||||||
|
</Grid>
|
||||||
|
);
|
||||||
}
|
}
|
||||||
|
|
|
@ -25,6 +25,7 @@ import {
|
||||||
} from "rxjs/operators";
|
} from "rxjs/operators";
|
||||||
import { useObservable } from "rxjs-hooks";
|
import { useObservable } from "rxjs-hooks";
|
||||||
import Markdown from "react-markdown";
|
import Markdown from "react-markdown";
|
||||||
|
import { useTranslation } from "react-i18next";
|
||||||
|
|
||||||
import api from "api";
|
import api from "api";
|
||||||
import { Page } from "components";
|
import { Page } from "components";
|
||||||
|
@ -52,16 +53,17 @@ function TrackMapSettings({
|
||||||
side,
|
side,
|
||||||
setSide,
|
setSide,
|
||||||
}) {
|
}) {
|
||||||
|
const { t } = useTranslation();
|
||||||
return (
|
return (
|
||||||
<>
|
<>
|
||||||
<Header as="h4">Map settings</Header>
|
<Header as="h4">{t("TrackPage.mapSettings.title")}</Header>
|
||||||
<List>
|
<List>
|
||||||
<List.Item>
|
<List.Item>
|
||||||
<Checkbox
|
<Checkbox
|
||||||
checked={showTrack}
|
checked={showTrack}
|
||||||
onChange={(e, d) => setShowTrack(d.checked)}
|
onChange={(e, d) => setShowTrack(d.checked)}
|
||||||
/>{" "}
|
/>{" "}
|
||||||
Show track
|
{t("TrackPage.mapSettings.showTrack")}
|
||||||
<div style={{ marginTop: 8 }}>
|
<div style={{ marginTop: 8 }}>
|
||||||
<span
|
<span
|
||||||
style={{
|
style={{
|
||||||
|
@ -73,7 +75,7 @@ function TrackMapSettings({
|
||||||
marginRight: 4,
|
marginRight: 4,
|
||||||
}}
|
}}
|
||||||
/>
|
/>
|
||||||
GPS track
|
{t("TrackPage.mapSettings.gpsTrack")}
|
||||||
</div>
|
</div>
|
||||||
<div>
|
<div>
|
||||||
<span
|
<span
|
||||||
|
@ -86,11 +88,11 @@ function TrackMapSettings({
|
||||||
marginRight: 4,
|
marginRight: 4,
|
||||||
}}
|
}}
|
||||||
/>
|
/>
|
||||||
Snapped to road
|
{t("TrackPage.mapSettings.snappedTrack")}
|
||||||
</div>
|
</div>
|
||||||
</List.Item>
|
</List.Item>
|
||||||
<List.Item>
|
<List.Item>
|
||||||
<List.Header>Points</List.Header>
|
<List.Header> {t("TrackPage.mapSettings.points")} </List.Header>
|
||||||
<Dropdown
|
<Dropdown
|
||||||
selection
|
selection
|
||||||
value={pointsMode}
|
value={pointsMode}
|
||||||
|
@ -100,18 +102,18 @@ function TrackMapSettings({
|
||||||
{
|
{
|
||||||
key: "overtakingEvents",
|
key: "overtakingEvents",
|
||||||
value: "overtakingEvents",
|
value: "overtakingEvents",
|
||||||
text: "Confirmed",
|
text: t("TrackPage.mapSettings.confirmedPoints"),
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
key: "measurements",
|
key: "measurements",
|
||||||
value: "measurements",
|
value: "measurements",
|
||||||
text: "All measurements",
|
text: t("TrackPage.mapSettings.allPoints"),
|
||||||
},
|
},
|
||||||
]}
|
]}
|
||||||
/>
|
/>
|
||||||
</List.Item>
|
</List.Item>
|
||||||
<List.Item>
|
<List.Item>
|
||||||
<List.Header>Side (for color)</List.Header>
|
<List.Header>{t("TrackPage.mapSettings.side")}</List.Header>
|
||||||
<Dropdown
|
<Dropdown
|
||||||
selection
|
selection
|
||||||
value={side}
|
value={side}
|
||||||
|
@ -120,12 +122,12 @@ function TrackMapSettings({
|
||||||
{
|
{
|
||||||
key: "overtaker",
|
key: "overtaker",
|
||||||
value: "overtaker",
|
value: "overtaker",
|
||||||
text: "Overtaker (Left)",
|
text: t("TrackPage.mapSettings.overtakerSide"),
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
key: "stationary",
|
key: "stationary",
|
||||||
value: "stationary",
|
value: "stationary",
|
||||||
text: "Stationary (Right)",
|
text: t("TrackPage.mapSettings.stationarySide"),
|
||||||
},
|
},
|
||||||
]}
|
]}
|
||||||
/>
|
/>
|
||||||
|
@ -138,6 +140,7 @@ function TrackMapSettings({
|
||||||
const TrackPage = connect((state) => ({ login: state.login }))(
|
const TrackPage = connect((state) => ({ login: state.login }))(
|
||||||
function TrackPage({ login }) {
|
function TrackPage({ login }) {
|
||||||
const { slug } = useParams();
|
const { slug } = useParams();
|
||||||
|
const { t } = useTranslation();
|
||||||
|
|
||||||
const [reloadComments, reloadComments$] = useTriggerSubject();
|
const [reloadComments, reloadComments$] = useTriggerSubject();
|
||||||
const history = useHistory();
|
const history = useHistory();
|
||||||
|
@ -234,9 +237,7 @@ const TrackPage = connect((state) => ({ login: state.login }))(
|
||||||
await api.downloadFile(`/tracks/${slug}/download/${filename}`);
|
await api.downloadFile(`/tracks/${slug}/download/${filename}`);
|
||||||
} catch (err) {
|
} catch (err) {
|
||||||
if (/Failed to fetch/.test(String(err))) {
|
if (/Failed to fetch/.test(String(err))) {
|
||||||
setDownloadError(
|
setDownloadError(t("TrackPage.downloadError"));
|
||||||
"The track probably has not been imported correctly or recently enough. Please ask your administrator for assistance."
|
|
||||||
);
|
|
||||||
} else {
|
} else {
|
||||||
setDownloadError(String(err));
|
setDownloadError(String(err));
|
||||||
}
|
}
|
||||||
|
@ -259,7 +260,7 @@ const TrackPage = connect((state) => ({ login: state.login }))(
|
||||||
const [pointsMode, setPointsMode] = React.useState("overtakingEvents"); // none|overtakingEvents|measurements
|
const [pointsMode, setPointsMode] = React.useState("overtakingEvents"); // none|overtakingEvents|measurements
|
||||||
const [side, setSide] = React.useState("overtaker"); // overtaker|stationary
|
const [side, setSide] = React.useState("overtaker"); // overtaker|stationary
|
||||||
|
|
||||||
const title = track ? track.title || "Unnamed track" : null;
|
const title = track ? track.title || t("general.unnamedTrack") : null;
|
||||||
return (
|
return (
|
||||||
<Page
|
<Page
|
||||||
title={title}
|
title={title}
|
||||||
|
@ -268,16 +269,23 @@ const TrackPage = connect((state) => ({ login: state.login }))(
|
||||||
<Container>
|
<Container>
|
||||||
{track && (
|
{track && (
|
||||||
<Segment basic>
|
<Segment basic>
|
||||||
<div style={{display: 'flex', alignItems: 'baseline', marginBlockStart: 32, marginBlockEnd: 16}}>
|
<div
|
||||||
<Header as="h1">{title}</Header>
|
style={{
|
||||||
<div style={{marginLeft: 'auto'}}>
|
display: "flex",
|
||||||
|
alignItems: "baseline",
|
||||||
|
marginBlockStart: 32,
|
||||||
|
marginBlockEnd: 16,
|
||||||
|
}}
|
||||||
|
>
|
||||||
|
<Header as="h1">{title}</Header>
|
||||||
|
<div style={{ marginLeft: "auto" }}>
|
||||||
<TrackActions {...{ isAuthor, onDownload, slug }} />
|
<TrackActions {...{ isAuthor, onDownload, slug }} />
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<div style={{marginBlockEnd: 16}}>
|
<div style={{ marginBlockEnd: 16 }}>
|
||||||
<TrackDetails {...{ track, isAuthor }} />
|
<TrackDetails {...{ track, isAuthor }} />
|
||||||
</div>
|
</div>
|
||||||
</Segment>
|
</Segment>
|
||||||
)}
|
)}
|
||||||
</Container>
|
</Container>
|
||||||
|
@ -307,8 +315,7 @@ const TrackPage = connect((state) => ({ login: state.login }))(
|
||||||
{processing && (
|
{processing && (
|
||||||
<Message warning>
|
<Message warning>
|
||||||
<Message.Content>
|
<Message.Content>
|
||||||
Track data is still being processed, please reload page in
|
{t("TrackPage.processing")}
|
||||||
a while.
|
|
||||||
</Message.Content>
|
</Message.Content>
|
||||||
</Message>
|
</Message>
|
||||||
)}
|
)}
|
||||||
|
@ -316,8 +323,7 @@ const TrackPage = connect((state) => ({ login: state.login }))(
|
||||||
{error && (
|
{error && (
|
||||||
<Message error>
|
<Message error>
|
||||||
<Message.Content>
|
<Message.Content>
|
||||||
The processing of this track failed, please ask your site
|
{t("TrackPage.processingError")}
|
||||||
administrator for help in debugging the issue.
|
|
||||||
</Message.Content>
|
</Message.Content>
|
||||||
</Message>
|
</Message>
|
||||||
)}
|
)}
|
||||||
|
@ -328,7 +334,7 @@ const TrackPage = connect((state) => ({ login: state.login }))(
|
||||||
{track?.description && (
|
{track?.description && (
|
||||||
<>
|
<>
|
||||||
<Header as="h2" dividing>
|
<Header as="h2" dividing>
|
||||||
Description
|
{t("TrackPage.description")}
|
||||||
</Header>
|
</Header>
|
||||||
<Markdown>{track.description}</Markdown>
|
<Markdown>{track.description}</Markdown>
|
||||||
</>
|
</>
|
||||||
|
@ -347,7 +353,7 @@ const TrackPage = connect((state) => ({ login: state.login }))(
|
||||||
open={downloadError != null}
|
open={downloadError != null}
|
||||||
cancelButton={false}
|
cancelButton={false}
|
||||||
onConfirm={hideDownloadError}
|
onConfirm={hideDownloadError}
|
||||||
header="Download failed"
|
header={t("TrackPage.downloadFailed")}
|
||||||
content={String(downloadError)}
|
content={String(downloadError)}
|
||||||
/>
|
/>
|
||||||
</Page>
|
</Page>
|
||||||
|
|
|
@ -114,15 +114,7 @@ export function TrackListItem({track, privateTracks = false}) {
|
||||||
</Item.Description>
|
</Item.Description>
|
||||||
{privateTracks && (
|
{privateTracks && (
|
||||||
<Item.Extra>
|
<Item.Extra>
|
||||||
{track.public ? (
|
<Visibility public={track.public} />
|
||||||
<>
|
|
||||||
<Icon color="blue" name="eye" fitted /> {t('general.public')}
|
|
||||||
</>
|
|
||||||
) : (
|
|
||||||
<>
|
|
||||||
<Icon name="eye slash" fitted /> {t('general.private')}
|
|
||||||
</>
|
|
||||||
)}
|
|
||||||
|
|
||||||
<span style={{marginLeft: '1em'}}>
|
<span style={{marginLeft: '1em'}}>
|
||||||
<Icon color={COLOR_BY_STATUS[track.processingStatus]} name="bolt" fitted />
|
<Icon color={COLOR_BY_STATUS[track.processingStatus]} name="bolt" fitted />
|
||||||
|
|
|
@ -7,6 +7,8 @@ general:
|
||||||
private: Privat
|
private: Privat
|
||||||
show: Anzeigen
|
show: Anzeigen
|
||||||
edit: Bearbeiten
|
edit: Bearbeiten
|
||||||
|
save: Speichern
|
||||||
|
delete: Löschen
|
||||||
|
|
||||||
App:
|
App:
|
||||||
footer:
|
footer:
|
||||||
|
@ -221,3 +223,49 @@ SettingsPage:
|
||||||
|
|
||||||
generate: Neuen API-Schlüssel erstellen
|
generate: Neuen API-Schlüssel erstellen
|
||||||
|
|
||||||
|
TrackPage:
|
||||||
|
downloadFailed: Download fehlgeschlagen
|
||||||
|
downloadError: Diese Fahrt wurde vermutlich nicht korrekt importiert, oder in letzter Zeit nicht aktualisiert. Bitte frage den Administrator um Hilfe mit diesem Problem.
|
||||||
|
processing: Diese Fahrt wird gerade importiert, bitte lade die Seite später neu.
|
||||||
|
processingError: Beim Import dieser Fahrt ist ein Fehler aufgetreten, bitte frage den Administrator um Hilfe mit diesem Problem.
|
||||||
|
description: Beschreibung
|
||||||
|
|
||||||
|
mapSettings:
|
||||||
|
title: Karteneinstellungen
|
||||||
|
showTrack: Route anzeigen
|
||||||
|
gpsTrack: GPS-Route
|
||||||
|
snappedTrack: Erkannte Straßenroute
|
||||||
|
|
||||||
|
points: Punkte
|
||||||
|
confirmedPoints: Bestätigte Überholungen
|
||||||
|
allPoints: Alle Messungen
|
||||||
|
|
||||||
|
side: Seite für Einfärbung
|
||||||
|
overtakerSide: Überholung (links)
|
||||||
|
stationarySide: Ruhender Verkehr (rechts)
|
||||||
|
|
||||||
|
details:
|
||||||
|
visibility: Sichtbarkeit
|
||||||
|
originalFileName: Original Dateiname
|
||||||
|
uploadedWith: Hochgeladen mit
|
||||||
|
duration: Dauer
|
||||||
|
uploadedDate: Hochgeladen am
|
||||||
|
recordedDate: Aufgezeichnet am
|
||||||
|
numEvents: Bestätigte Überholungen
|
||||||
|
length: Länge
|
||||||
|
processingStatus: Verarbeitung
|
||||||
|
|
||||||
|
actions:
|
||||||
|
edit: Fahrt bearbeiten
|
||||||
|
|
||||||
|
download: Herunterladen
|
||||||
|
original: Original
|
||||||
|
gpx: GPX-Track
|
||||||
|
hintAuthorOnly: Nur du, als Autor:in dieser Fahrt, kannst die Originaldatei herunterladen.
|
||||||
|
hintOriginal: Dies ist die Originaldatei, wie sie auf den Server hochgeladen wurde, und kann mit anderen Werkzeugen verwendet werden.
|
||||||
|
hintAuthorOnlyOthers: Nur der:die Autor:in dieser Fahrt kann die Originaldatei herunterladen.
|
||||||
|
|
||||||
|
comments:
|
||||||
|
title: Kommentare
|
||||||
|
post: Kommentar abschicken
|
||||||
|
empty: Bisher hat niemand diese Fahrt kommentiert.
|
||||||
|
|
|
@ -12,6 +12,7 @@ general:
|
||||||
show: Show
|
show: Show
|
||||||
edit: Edit
|
edit: Edit
|
||||||
save: Save
|
save: Save
|
||||||
|
delete: Delete
|
||||||
|
|
||||||
copied: Copied.
|
copied: Copied.
|
||||||
copyError: Failed to copy.
|
copyError: Failed to copy.
|
||||||
|
@ -226,3 +227,51 @@ SettingsPage:
|
||||||
|
|
||||||
generate: Generate new API key
|
generate: Generate new API key
|
||||||
|
|
||||||
|
|
||||||
|
TrackPage:
|
||||||
|
downloadFailed: Download failed
|
||||||
|
downloadError: The track probably has not been imported correctly or recently enough. Please ask your administrator for assistance.
|
||||||
|
processing: Track data is still being processed, please reload page in a while.
|
||||||
|
processingError: The processing of this track failed, please ask your site administrator for help in debugging the issue.
|
||||||
|
description: Description
|
||||||
|
|
||||||
|
|
||||||
|
mapSettings:
|
||||||
|
title: Map settings
|
||||||
|
showTrack: Show track
|
||||||
|
gpsTrack: GPS track
|
||||||
|
snappedTrack: Snapped to road
|
||||||
|
|
||||||
|
points: Points
|
||||||
|
confirmedPoints: Confirmed
|
||||||
|
allPoints: All measurements
|
||||||
|
|
||||||
|
side: Side (for color)
|
||||||
|
overtakerSide: Overtaker (Left)
|
||||||
|
stationarySide: Stationary (Right)
|
||||||
|
|
||||||
|
details:
|
||||||
|
visibility: Visibility
|
||||||
|
originalFileName: Original Filename
|
||||||
|
uploadedWith: Uploaded with
|
||||||
|
duration: Duration
|
||||||
|
uploadedDate: Uploaded on
|
||||||
|
recordedDate: Recorded on
|
||||||
|
numEvents: Confirmed events
|
||||||
|
length: Length
|
||||||
|
processingStatus: Processing
|
||||||
|
|
||||||
|
actions:
|
||||||
|
edit: Edit track
|
||||||
|
|
||||||
|
download: Download
|
||||||
|
original: Original
|
||||||
|
gpx: Track (GPX)
|
||||||
|
hintAuthorOnly: Only you, the author of this track, can download the original file.
|
||||||
|
hintOriginal: This is the file as it was uploaded to the server, without modifications, and it can be used with other tools.
|
||||||
|
hintAuthorOnlyOthers: Only the author of this track can download the original file.
|
||||||
|
|
||||||
|
comments:
|
||||||
|
title: Comments
|
||||||
|
post: Post comment
|
||||||
|
empty: Nobody commented... yet
|
||||||
|
|
Loading…
Reference in a new issue