From 248f8b4a6fd8d232f7e5a22fd4e91ef14b46da09 Mon Sep 17 00:00:00 2001 From: Paul Bienkowski Date: Sun, 24 Jul 2022 18:43:26 +0200 Subject: [PATCH] Translate MapPage --- frontend/src/pages/MapPage/LayerSidebar.tsx | 168 +++++++++------ frontend/src/pages/MapPage/RoadInfo.tsx | 214 ++++++++++++-------- frontend/src/translations/de.yaml | 63 ++++++ frontend/src/translations/en.yaml | 62 ++++++ 4 files changed, 359 insertions(+), 148 deletions(-) diff --git a/frontend/src/pages/MapPage/LayerSidebar.tsx b/frontend/src/pages/MapPage/LayerSidebar.tsx index 46167b2..0e4861f 100644 --- a/frontend/src/pages/MapPage/LayerSidebar.tsx +++ b/frontend/src/pages/MapPage/LayerSidebar.tsx @@ -1,53 +1,66 @@ -import React from 'react' -import _ from 'lodash' -import {connect} from 'react-redux' -import {List, Select, Input, Divider, Label, Checkbox, Header} from 'semantic-ui-react' +import React from "react"; +import _ from "lodash"; +import { connect } from "react-redux"; +import { + List, + Select, + Input, + Divider, + Label, + Checkbox, + Header, +} from "semantic-ui-react"; +import { useTranslation } from "react-i18next"; import { MapConfig, setMapConfigFlag as setMapConfigFlagAction, initialState as defaultMapConfig, -} from 'reducers/mapConfig' -import {colorByDistance, colorByCount, viridisSimpleHtml} from 'mapstyles' -import {ColorMapLegend, DiscreteColorMapLegend} from 'components' +} from "reducers/mapConfig"; +import { colorByDistance, colorByCount, viridisSimpleHtml } from "mapstyles"; +import { ColorMapLegend, DiscreteColorMapLegend } from "components"; -const BASEMAP_STYLE_OPTIONS = [ - {value: 'positron', key: 'positron', text: 'Positron'}, - {value: 'bright', key: 'bright', text: 'OSM Bright'}, -] +const BASEMAP_STYLE_OPTIONS = ["positron", "bright"]; const ROAD_ATTRIBUTE_OPTIONS = [ - {value: 'distance_overtaker_mean', key: 'distance_overtaker_mean', text: 'Overtaker distance mean'}, - {value: 'distance_overtaker_min', key: 'distance_overtaker_min', text: 'Overtaker distance minimum'}, - {value: 'distance_overtaker_max', key: 'distance_overtaker_max', text: 'Overtaker distance maximum'}, - {value: 'distance_overtaker_median', key: 'distance_overtaker_median', text: 'Overtaker distance median'}, - {value: 'overtaking_event_count', key: 'overtaking_event_count', text: 'Event count'}, - {value: 'usage_count', key: 'usage_count', text: 'Usage count'}, - {value: 'zone', key: 'zone', text: 'Overtaking distance zone'} -] + "distance_overtaker_mean", + "distance_overtaker_min", + "distance_overtaker_max", + "distance_overtaker_median", + "overtaking_event_count", + "usage_count", + "zone", +]; function LayerSidebar({ mapConfig, setMapConfigFlag, }: { - mapConfig: MapConfig - setMapConfigFlag: (flag: string, value: unknown) => void + mapConfig: MapConfig; + setMapConfigFlag: (flag: string, value: unknown) => void; }) { + const { t } = useTranslation(); const { - baseMap: {style}, - obsRoads: {show: showRoads, showUntagged, attribute, maxCount}, - obsEvents: {show: showEvents}, - } = mapConfig + baseMap: { style }, + obsRoads: { show: showRoads, showUntagged, attribute, maxCount }, + obsEvents: { show: showEvents }, + } = mapConfig; return (
- Basemap Style + {t("MapPage.sidebar.baseMap.style.label")} ({ + value, + key: value, + text: t(`MapPage.sidebar.obsRoads.attribute.${value}`), + }))} value={attribute} - onChange={(_e, {value}) => setMapConfigFlag('obsRoads.attribute', value)} + onChange={(_e, { value }) => + setMapConfigFlag("obsRoads.attribute", value) + } /> - {attribute.endsWith('_count') ? ( - <> + {attribute.endsWith("_count") ? ( + <> + + + {t("MapPage.sidebar.obsRoads.maxCount.label")} + + + setMapConfigFlag("obsRoads.maxCount", value) + } + /> + + + + + + ) : ( - Maximum value - setMapConfigFlag('obsRoads.maxCount', value)} + - - - ) : attribute.endsWith('zone') ? ( <> - - + + ) : ( <> - Urban + {_.startCase(t("general.urban"))} - Rural + {_.startCase(t("general.rural"))} @@ -124,29 +168,29 @@ function LayerSidebar({ toggle size="small" id="obsEvents.show" - style={{float: 'right'}} + style={{ float: "right" }} checked={showEvents} - onChange={() => setMapConfigFlag('obsEvents.show', !showEvents)} + onChange={() => setMapConfigFlag("obsEvents.show", !showEvents)} /> {showEvents && ( <> - Urban - - + {_.startCase(t('general.urban'))} + + - Rural - + {_.startCase(t('general.rural'))} + )}
- ) + ); } export default connect( @@ -158,6 +202,6 @@ export default connect( // ), }), - {setMapConfigFlag: setMapConfigFlagAction} + { setMapConfigFlag: setMapConfigFlagAction } // -)(LayerSidebar) +)(LayerSidebar); diff --git a/frontend/src/pages/MapPage/RoadInfo.tsx b/frontend/src/pages/MapPage/RoadInfo.tsx index 871279a..fb8938c 100644 --- a/frontend/src/pages/MapPage/RoadInfo.tsx +++ b/frontend/src/pages/MapPage/RoadInfo.tsx @@ -1,83 +1,95 @@ -import React, {useState, useCallback} from 'react' -import _ from 'lodash' -import {Segment, Menu, Header, Label, Icon, Table} from 'semantic-ui-react' -import {Layer, Source} from 'react-map-gl' -import {of, from, concat} from 'rxjs' -import {useObservable} from 'rxjs-hooks' -import {switchMap, distinctUntilChanged} from 'rxjs/operators' -import {Chart} from 'components' -import {pairwise} from 'utils' +import React, { useState, useCallback } from "react"; +import _ from "lodash"; +import { Segment, Menu, Header, Label, Icon, Table } from "semantic-ui-react"; +import { Layer, Source } from "react-map-gl"; +import { of, from, concat } from "rxjs"; +import { useObservable } from "rxjs-hooks"; +import { switchMap, distinctUntilChanged } from "rxjs/operators"; +import { Chart } from "components"; +import { pairwise } from "utils"; +import { useTranslation } from "react-i18next"; import api from 'api' import {colorByDistance, borderByZone} from 'mapstyles' -import styles from './styles.module.less' +import styles from "./styles.module.less"; function selectFromColorMap(colormap, value) { - let last = null + let last = null; for (let i = 0; i < colormap.length; i += 2) { if (colormap[i + 1] > value) { - return colormap[i] + return colormap[i]; } } - return colormap[colormap.length - 1] + return colormap[colormap.length - 1]; } -const UNITS = {distanceOvertaker: 'm', distanceStationary: 'm', speed: 'km/h'} -const LABELS = { - distanceOvertaker: 'Left', - distanceStationary: 'Right', - speed: 'Speed', - count: 'No. of Measurements', - min: 'Minimum', - median: 'Median', - max: 'Maximum', - mean: 'Average', -} -const ZONE_COLORS = {urban: 'blue', rural: 'cyan', motorway: 'purple'} -const CARDINAL_DIRECTIONS = ['north', 'north-east', 'east', 'south-east', 'south', 'south-west', 'west', 'north-west'] -const getCardinalDirection = (bearing) => - bearing == null - ? 'unknown' - : CARDINAL_DIRECTIONS[ - Math.floor(((bearing / 360.0) * CARDINAL_DIRECTIONS.length + 0.5) % CARDINAL_DIRECTIONS.length) - ] + ' bound' +const UNITS = { + distanceOvertaker: "m", + distanceStationary: "m", + speed: "km/h", +}; +const ZONE_COLORS = { urban: "blue", rural: "cyan", motorway: "purple" }; +const CARDINAL_DIRECTIONS = [ + "north", + "northEast", + "east", + "southEast", + "south", + "southWest", + "west", + "northWest", +]; +const getCardinalDirection = (t, bearing) => { + if (bearing == null) { + return t("MapPage.roadInfo.cardinalDirections.unknown"); + } else { + const n = CARDINAL_DIRECTIONS.length; + const i = Math.floor(((bearing / 360.0) * n + 0.5) % n); + const name = CARDINAL_DIRECTIONS[i]; + return t(`MapPage.roadInfo.cardinalDirections.${name}`); + } +}; -function RoadStatsTable({data}) { +function RoadStatsTable({ data }) { + const { t } = useTranslation(); return ( - {['distanceOvertaker', 'distanceStationary', 'speed'].map((prop) => ( + {["distanceOvertaker", "distanceStationary", "speed"].map((prop) => ( - {LABELS[prop]} + {t(`MapPage.roadInfo.${prop}`)} ))} - {['count', 'min', 'median', 'max', 'mean'].map((stat) => ( + {["count", "min", "median", "max", "mean"].map((stat) => ( - {LABELS[stat]} - {['distanceOvertaker', 'distanceStationary', 'speed'].map((prop) => ( - - {(data[prop]?.statistics?.[stat] * (prop === `speed` && stat != 'count' ? 3.6 : 1)).toFixed( - stat === 'count' ? 0 : 2 - )} - {stat !== 'count' && ` ${UNITS[prop]}`} - - ))} + {t(`MapPage.roadInfo.${stat}`)} + {["distanceOvertaker", "distanceStationary", "speed"].map( + (prop) => ( + + {( + data[prop]?.statistics?.[stat] * + (prop === `speed` && stat != "count" ? 3.6 : 1) + ).toFixed(stat === "count" ? 0 : 2)} + {stat !== "count" && ` ${UNITS[prop]}`} + + ) + )} ))}
- ) + ); } -function HistogramChart({bins, counts, zone}) { +function HistogramChart({ bins, counts, zone }) { const diff = bins[1] - bins[0] - const colortype = zone=="rural" ? 3:5; + const colortype = zone === "rural" ? 3 : 5; const data = _.zip( bins.slice(0, bins.length - 1).map((v) => v + diff / 2), counts @@ -88,19 +100,19 @@ function HistogramChart({bins, counts, zone}) { return ( `${Math.round(v * 100)} cm`}, + type: "value", + axisLabel: { formatter: (v) => `${Math.round(v * 100)} cm` }, min: 0, max: 2.5, }, yAxis: {}, series: [ { - type: 'bar', + type: "bar", data, barMaxWidth: 20, @@ -108,20 +120,21 @@ function HistogramChart({bins, counts, zone}) { ], }} /> - ) + ); } -export default function RoadInfo({clickLocation}) { - const [direction, setDirection] = useState('forwards') +export default function RoadInfo({ clickLocation }) { + const { t } = useTranslation(); + const [direction, setDirection] = useState("forwards"); const onClickDirection = useCallback( - (e, {name}) => { - e.preventDefault() - e.stopPropagation() - setDirection(name) + (e, { name }) => { + e.preventDefault(); + e.stopPropagation(); + setDirection(name); }, [setDirection] - ) + ); const info = useObservable( (_$, inputs$) => @@ -132,7 +145,7 @@ export default function RoadInfo({clickLocation}) { ? concat( of(null), from( - api.get('/mapdetails/road', { + api.get("/mapdetails/road", { query: { ...location, radius: 100, @@ -145,43 +158,60 @@ export default function RoadInfo({clickLocation}) { ), null, [clickLocation] - ) + ); if (!clickLocation) { - return null + return null; } - const loading = info == null + const loading = info == null; - const offsetDirection = info?.road?.oneway ? 0 : direction === 'forwards' ? 1 : -1 // TODO: change based on left-hand/right-hand traffic + const offsetDirection = info?.road?.oneway + ? 0 + : direction === "forwards" + ? 1 + : -1; // TODO: change based on left-hand/right-hand traffic const content = !loading && !info.road ? ( - 'No road found.' + "No road found." ) : ( <> -
{loading ? '...' : info?.road.name || 'Unnamed way'}
+
+ {loading + ? "..." + : info?.road.name || t("MapPage.roadInfo.unnamedWay")} +
{info?.road.zone && ( )} {info?.road.oneway && ( )} {info?.road.oneway ? null : ( - Direction - - {getCardinalDirection(info?.forwards?.bearing)} + {t("MapPage.roadInfo.direction")} + + {getCardinalDirection(t, info?.forwards?.bearing)} - - {getCardinalDirection(info?.backwards?.bearing)} + + {getCardinalDirection(t, info?.backwards?.bearing)} )} @@ -190,12 +220,16 @@ export default function RoadInfo({clickLocation}) { {info?.[direction]?.distanceOvertaker?.histogram && ( <> -
Overtaker distance distribution
- +
+ {t("MapPage.roadInfo.overtakerDistanceDistribution")} +
+ )} - ) + ); return ( <> @@ -205,14 +239,22 @@ export default function RoadInfo({clickLocation}) { id="route" type="line" paint={{ - 'line-width': ['interpolate', ['linear'], ['zoom'], 14, 6, 17, 12], - 'line-color': '#18FFFF', - 'line-opacity': 0.5, + "line-width": [ + "interpolate", + ["linear"], + ["zoom"], + 14, + 6, + 17, + 12, + ], + "line-color": "#18FFFF", + "line-opacity": 0.5, ...{ - 'line-offset': [ - 'interpolate', - ['exponential', 1.5], - ['zoom'], + "line-offset": [ + "interpolate", + ["exponential", 1.5], + ["zoom"], 12, offsetDirection, 19, @@ -230,5 +272,5 @@ export default function RoadInfo({clickLocation}) { )} - ) + ); } diff --git a/frontend/src/translations/de.yaml b/frontend/src/translations/de.yaml index 4137cee..5823817 100644 --- a/frontend/src/translations/de.yaml +++ b/frontend/src/translations/de.yaml @@ -1,6 +1,8 @@ general: loading: Lädt unnamedTrack: Unbenannte Fahrt + urban: innerorts + rural: außerorts public: Öffentlich private: Privat show: Anzeigen @@ -116,3 +118,64 @@ NotFoundPage: title: Seite nicht gefunden description: Den Fehler kennst du sicher... goBack: Zurückgehen + + +MapPage: + sidebar: + baseMap: + style: + label: Stil der Basiskarte + positron: Positron + bright: OSM Bright + + obsRoads: + title: Straßenabschnitte + showUntagged: + label: Abschnitte ohne Daten anzeigen + attribute: + label: Einfärben nach... + distance_overtaker_mean: Durchschnitt Überholabstand + distance_overtaker_min: Minimum Überholabstand + distance_overtaker_max: Maximum Überholabstand + distance_overtaker_median: Median Überholabstand + overtaking_event_count: Anzahl Überholvorgänge + usage_count: Anzahl Befahrungen + zone: Überholabstands-Zone + maxCount: + label: Maximalwert für Farbskala + + obsEvents: + title: Überholvorgänge + + + roadInfo: + unnamedWay: Unbenannter Weg + oneway: Einbahnstraße + direction: Richtung + + zone: + rural: außerorts + urban: innerorts + motorway: Autobahn + + distanceOvertaker: Links + distanceStationary: Rechts + speed: Geschwindigkeit + count: Anzahl Messungen + min: Minimum + median: Median + max: Maximum + mean: Durchschnitt + + overtakerDistanceDistribution: Verteilung der Überholabstände + + cardinalDirections: + unknown: unbekannt + north: nordwärts + northEast: nordostwärts + east: ostwärts + southEast: südostwärts + south: südwärts + southWest: südwestwärts + west: westwärts + northWest: nordwestwärts diff --git a/frontend/src/translations/en.yaml b/frontend/src/translations/en.yaml index b3137fe..16b2dae 100644 --- a/frontend/src/translations/en.yaml +++ b/frontend/src/translations/en.yaml @@ -5,6 +5,8 @@ locales: general: loading: Loading unnamedTrack: Unnamed track + urban: urban + rural: rural public: Public private: Private show: Show @@ -121,3 +123,63 @@ NotFoundPage: title: Page not found description: You know what that means... goBack: Go back + + +MapPage: + sidebar: + baseMap: + style: + label: Basemap Style + positron: Positron + bright: OSM Bright + + obsRoads: + title: Road segments + showUntagged: + label: Include roads without data + attribute: + label: Color based on... + distance_overtaker_mean: Overtaker distance mean + distance_overtaker_min: Overtaker distance minimum + distance_overtaker_max: Overtaker distance maximum + distance_overtaker_median: Overtaker distance median + overtaking_event_count: Event count + usage_count: Usage count + zone: Overtaking distance zone + maxCount: + label: Maximum value for color scale + + obsEvents: + title: Event points + + roadInfo: + unnamedWay: Unnamed way + oneway: oneway + direction: Direction + + zone: + rural: Rural + urban: Urban + motorway: Motorway + + distanceOvertaker: Left + distanceStationary: Right + speed: Speed + count: No. of Measurements + min: Minimum + median: Median + max: Maximum + mean: Average + + overtakerDistanceDistribution: Overtaker distance distribution + + cardinalDirections: + unknown: unknown + north: north bound + northEast: north-east bound + east: east bound + southEast: south-east bound + south: south bound + southWest: south-west bound + west: west bound + northWest: north-west bound