diff --git a/client/src/components/Roadtrip/RoadtripCorridorPanel.test.tsx b/client/src/components/Roadtrip/RoadtripCorridorPanel.test.tsx index 51fb33c3e..204e4a064 100644 --- a/client/src/components/Roadtrip/RoadtripCorridorPanel.test.tsx +++ b/client/src/components/Roadtrip/RoadtripCorridorPanel.test.tsx @@ -30,6 +30,7 @@ const day = (dayId: number, dayNumber: number): RoadtripDay => ({ stops: [stop(dayId * 10 + 1, 'Hamburg'), stop(dayId * 10 + 2, 'Berlin')], legs: [], schedule: { entries: [], warnings: [] }, + legVias: [], geometry: [], distance: 0, duration: 0, @@ -39,6 +40,7 @@ const routes = (days: RoadtripDay[]): RoadtripRoutes => ({ days, lines: [], accessLines: [], + vias: [], segments: [], totalDistance: 0, totalDuration: 0, diff --git a/client/src/components/Roadtrip/RoadtripSidebar.test.tsx b/client/src/components/Roadtrip/RoadtripSidebar.test.tsx index cc010f326..ad0b3b65a 100644 --- a/client/src/components/Roadtrip/RoadtripSidebar.test.tsx +++ b/client/src/components/Roadtrip/RoadtripSidebar.test.tsx @@ -38,6 +38,7 @@ function day(over: Partial = {}): RoadtripDay { title: null, stops, legs: [leg()], + legVias: [], schedule: { entries: stops.map(() => ({ arrival: null, departure: null, anchored: false, dayOffset: 0 })), warnings: [] }, geometry: [], distance: 100000, @@ -52,6 +53,7 @@ function routes(over: Partial = {}): RoadtripRoutes { lines: [], segments: [], accessLines: [], + vias: [], totalDistance: 100000, totalDuration: 3600, totalStops: 2, diff --git a/client/src/components/Roadtrip/RoadtripSidebar.tsx b/client/src/components/Roadtrip/RoadtripSidebar.tsx index a757255ac..efbfa73d4 100644 --- a/client/src/components/Roadtrip/RoadtripSidebar.tsx +++ b/client/src/components/Roadtrip/RoadtripSidebar.tsx @@ -12,6 +12,7 @@ import { formatDurationShort, isServiceStopType, serviceColor, type ScheduleEntr import { STOP_KIND_BY_KEY } from './stopKinds' import { spurWorthLabelling } from './accessSpur' import type { QuietDay, RoadtripDay, RoadtripRoutes, RoadtripStop } from './useRoadtripRoutes' +import type { RouteVia } from '../../types' import { FS } from './typeScale' import type { RouteSegment } from '../../types' @@ -375,6 +376,52 @@ function ServiceStop({ stop, entry, selected, onSelect, onEditStay }: { ) } +/** + * A halt a routing plugin put on this leg, such as a charge on the way. + * + * Read-only, and that is the point rather than a shortcut. The halt belongs to the + * provider, not to the traveller: it is not a place in the database, it has no number, no + * editable stay and no arrival time. Writing it back would send it out as a waypoint on + * the next run, and the plugin would then plan around its own charging stop. + * + * No clock on purpose. The stay is already inside the leg duration the plugin reported, + * so the arrivals in the rail already account for it; printing a time here would mean + * guessing how the plugin split the driving, and driving time is not linear in distance. + */ +function RouteViaStop({ via }: { via: RouteVia }): React.ReactElement { + const { t } = useTranslation() + return ( +
+ + + {/* Hollow rather than filled: everything filled on this rail is something the + traveller put there. */} + + + + + + + + + {via.label || t('roadtrip.via.plugin')} + + {via.dwellSeconds != null ? ( + + {formatDurationShort(via.dwellSeconds)} + + ) : null} + + +
+ ) +} + /** * Midnight, marked where it happens — in the chain, between the two stops the drive runs * between, rather than as a fourth badge hanging off one of them. @@ -678,6 +725,11 @@ function DaySection({ day, selectedAssignmentId, onSelectStop, onReorderStop, on alternativesOpen={openAlternatives?.dayId === day.dayId && openAlternatives.index === i} /> ) : null} + {/* After the band, because a plugin halt happens on the drive it describes + rather than before setting off. */} + {i < last ? (day.legVias[i] ?? []).map((via, vi) => ( + + )) : null} ) })} diff --git a/client/src/components/Roadtrip/roadtripModel.test.ts b/client/src/components/Roadtrip/roadtripModel.test.ts index 6de899e79..df276c3c5 100644 --- a/client/src/components/Roadtrip/roadtripModel.test.ts +++ b/client/src/components/Roadtrip/roadtripModel.test.ts @@ -4,6 +4,7 @@ import { formatClock, formatDurationShort, insertIndexForAlong, + legIndexForAlong, parseClock, splitIntoRuns, sumLegSeconds, @@ -249,3 +250,29 @@ describe('insertIndexForAlong', () => { expect(insertIndexForAlong([0], 10)).toBe(1) }) }) + +describe('legIndexForAlong', () => { + // Against the legs' own lengths rather than by projecting the stops as well: leg + // distances come from the router and are exact, while two stops close together project + // onto each other's stretch and would put a charging halt the wrong side of a town. + const legs = [10_000, 25_000, 40_000] + + it('FE-ROADTRIP-MODEL-050: a point inside a leg belongs to that leg', () => { + expect(legIndexForAlong(legs, 0)).toBe(0) + expect(legIndexForAlong(legs, 9_999)).toBe(0) + expect(legIndexForAlong(legs, 10_000)).toBe(1) + expect(legIndexForAlong(legs, 24_999)).toBe(1) + expect(legIndexForAlong(legs, 25_000)).toBe(2) + }) + + it('FE-ROADTRIP-MODEL-051: a point past the end lands in the last leg, not nowhere', () => { + // A rounding edge, not a missing leg: the projection and the router measure the same + // line two different ways and disagree by metres at the very end. + expect(legIndexForAlong(legs, 40_000)).toBe(2) + expect(legIndexForAlong(legs, 99_999)).toBe(2) + }) + + it('FE-ROADTRIP-MODEL-052: a day with no routed leg has nowhere to put anything', () => { + expect(legIndexForAlong([], 100)).toBe(-1) + }) +}) diff --git a/client/src/components/Roadtrip/roadtripModel.ts b/client/src/components/Roadtrip/roadtripModel.ts index db7fbc103..f7554fad6 100644 --- a/client/src/components/Roadtrip/roadtripModel.ts +++ b/client/src/components/Roadtrip/roadtripModel.ts @@ -231,6 +231,23 @@ export function sumLegSeconds(legSeconds: (number | undefined)[]): number { * slightly before the first stop or past the last, and neither "before where you set off" * nor "after where you arrive" is a place a fuel stop can go. */ +/** + * Which leg a point on the drive belongs to, given where each leg ends. + * + * Measured against the legs' own lengths rather than by projecting the stops as well: the + * router's leg distances are exact by construction, while two stops close together + * project onto each other's stretch and would put a charging halt on the wrong side of a + * town. A point past the end lands in the last leg rather than nowhere, because that is + * a rounding edge and not a missing leg. + */ +export function legIndexForAlong(legEndMeters: number[], alongMeters: number): number { + if (!legEndMeters.length) return -1 + for (let i = 0; i < legEndMeters.length; i++) { + if (alongMeters < legEndMeters[i]) return i + } + return legEndMeters.length - 1 +} + export function insertIndexForAlong(stopsAlongKm: number[], hitAlongKm: number): number { if (stopsAlongKm.length < 2) return stopsAlongKm.length let i = 0 diff --git a/client/src/components/Roadtrip/useRoadtripCorridor.test.ts b/client/src/components/Roadtrip/useRoadtripCorridor.test.ts index 7b07b5bc2..5100f28dc 100644 --- a/client/src/components/Roadtrip/useRoadtripCorridor.test.ts +++ b/client/src/components/Roadtrip/useRoadtripCorridor.test.ts @@ -24,7 +24,7 @@ function day(over: Partial = {}): RoadtripDay { { assignmentId: 1, placeId: 10, name: 'A', lat: 53.5, lng: 9.9, time: null, dwellMinutes: null, legMode: null, incomingLegMode: null, stopType: null }, { assignmentId: 2, placeId: 20, name: 'B', lat: 52.5, lng: 13.4, time: null, dwellMinutes: null, legMode: null, incomingLegMode: null, stopType: null }, ], - legs: [], schedule: { entries: [], warnings: [] }, + legs: [], legVias: [], schedule: { entries: [], warnings: [] }, geometry: [[53.5, 9.9], [53.0, 11.0], [52.5, 13.4]], distance: 0, duration: 0, ...over, @@ -32,7 +32,7 @@ function day(over: Partial = {}): RoadtripDay { } const routes = (days: RoadtripDay[]): RoadtripRoutes => ({ - days, lines: [], segments: [], accessLines: [], totalDistance: 0, totalDuration: 0, totalStops: 0, quietDays: [], loading: false, + days, lines: [], segments: [], accessLines: [], vias: [], totalDistance: 0, totalDuration: 0, totalStops: 0, quietDays: [], loading: false, }) /** The search state the mocked hook hands back, results included. */ diff --git a/client/src/components/Roadtrip/useRoadtripRoutes.ts b/client/src/components/Roadtrip/useRoadtripRoutes.ts index 7bc345c06..ffbf9285f 100644 --- a/client/src/components/Roadtrip/useRoadtripRoutes.ts +++ b/client/src/components/Roadtrip/useRoadtripRoutes.ts @@ -1,9 +1,10 @@ import { useEffect, useMemo, useRef, useState } from 'react' import { calculateRouteWithLegs, RoutingRefusedError } from '../Map/RouteCalculator' import { resolveLegMode } from '../Planner/legMode' -import { computeSchedule, isServiceStopType, splitIntoRuns, type Schedule } from './roadtripModel' +import { computeSchedule, isServiceStopType, legIndexForAlong, splitIntoRuns, type Schedule } from './roadtripModel' +import { projectOntoRoute } from './corridor' import { useSettingsStore } from '../../store/settingsStore' -import type { Assignment, AssignmentsMap, Day, RouteSegment, SnappedWaypoint } from '../../types' +import type { Assignment, AssignmentsMap, Day, RouteSegment, RouteVia, SnappedWaypoint } from '../../types' import { spurFor } from './accessSpur' import type { RoadtripVia } from '@trek/shared' @@ -41,6 +42,14 @@ export interface RoadtripDay { legs: (RouteSegment | undefined)[] /** Arrival and departure per stop, walked forward from the day's first pinned time. */ schedule: Schedule + /** + * Halts a routing plugin placed on each leg, indexed alongside `legs`. + * + * Read-only and never written back: a plugin halt that became a via would go out as a + * waypoint on the next run, and the plugin would then optimise around its own charging + * stop. Kept out of `RouteSegment` because that type is read by half the map. + */ + legVias: RouteVia[][] /** * The roads actually driven that day, as [lat, lng] — not the straight lines between * stops. Anything asking "what is along this day" has to use this: between Hamburg and @@ -109,6 +118,8 @@ export interface RoadtripRoutes { * looking like a route that cuts across open country. */ accessLines: AccessSpur[] + /** Every plugin halt of the trip, flat, which is the shape the map draws. */ + vias: RouteVia[] /** The routed legs themselves, so the map can label each connector. */ segments: RouteSegment[] totalDistance: number @@ -158,8 +169,8 @@ interface RoutedLeg { * looks like it is on the route when the drive really starts hundreds of metres away. */ snapped?: { from: SnappedPoint | null; to: SnappedPoint | null } - /** Points a routing plugin made this leg pass through, in order, if it provided any. */ - vias?: LegViaPoint[] + /** Halts a routing plugin placed on this leg, in the order they are driven through. */ + vias: RouteVia[] } /** A waypoint as the router resolved it, beside the coordinate that was asked for. */ @@ -170,18 +181,6 @@ export interface SnappedPoint { offRoadMeters: number } -/** A point a routing plugin routed through, with the stop it means if it means one. */ -export interface LegViaPoint { - lat: number - lng: number - name?: string - /** - * Time spent here, if the plugin reports one. Already contained in the leg duration - * the plugin returned, so it is shown rather than added to the schedule. - */ - dwellSeconds?: number -} - /** * What makes a stop the same stop as far as routing is concerned. `planKey` is built from * these, and so are the keys the routed legs are filed under, so the two can never drift @@ -273,6 +272,7 @@ export function useRoadtripRoutes( title: d.title ?? null, stops, legs: [], + legVias: [], schedule: { entries: [], warnings: [] }, geometry: [], distance: 0, @@ -383,8 +383,22 @@ export function useRoadtripRoutes( if (s) daySnaps[stopKey(stop)] = s }) } + // Where a plugin's halts sit along this run, measured once. Only the ones + // that say something get projected — a bare coordinate has nothing to show + // in the rail, and projection is not free. + const spine = r.coordinates.map(([la, ln]) => ({ lat: la, lng: ln })) + const viaAlong = (r.vias ?? []) + .filter(v => v.label || v.dwellSeconds != null) + .map(v => ({ via: v, alongMeters: (projectOntoRoute({ lat: v.lat, lng: v.lng }, spine)?.alongKm ?? 0) * 1000 })) + // Fold the router's per-waypoint legs back onto the stop pairs: a stop pair // with a via between it comes back as two legs, and the rail shows one. + // A running metre count comes with it, so each halt lands on the leg it is + // actually driven on rather than on the nearest stop, which two towns close + // together would get wrong. + const legEndMeters: number[] = [] + const legKeys: string[] = [] + let travelled = 0 for (let i = 0; i < run.length - 1; i++) { const from = run[i] const to = run[i + 1] @@ -396,7 +410,15 @@ export function useRoadtripRoutes( distance: parts.reduce((sum, l) => sum + (l.distance ?? 0), 0), duration: parts.reduce((sum, l) => sum + (l.duration ?? 0), 0), } - dayLegs[legKey(from, to)] = { seg: { ...merged, mode }, line: i === 0 ? r.coordinates : [] } + travelled += merged.distance ?? 0 + legEndMeters.push(travelled) + const key = legKey(from, to) + legKeys.push(key) + dayLegs[key] = { seg: { ...merged, mode }, line: i === 0 ? r.coordinates : [], vias: [] } + } + for (const { via, alongMeters } of viaAlong) { + const idx = legIndexForAlong(legEndMeters, alongMeters) + if (idx >= 0) dayLegs[legKeys[idx]].vias.push(via) } return } catch (err) { @@ -468,19 +490,21 @@ export function useRoadtripRoutes( day.stops.map(s => ({ anchor: s.time, dwellMinutes: s.dwellMinutes })), legs.map(l => l?.duration), ) + const legVias = routed.map(l => l?.vias ?? []) const stops = day.stops.map(s => { const snap = daySnaps[stopKey(s)] const line = spurFor(snap) if (line) accessLines.push({ line, meters: snap.meters, stopKey: stopKey(s) }) return { ...s, offRoadMeters: line ? snap.meters : null } }) - out.push({ ...day, stops, legs, schedule, geometry, distance, duration }) + out.push({ ...day, stops, legs, legVias, schedule, geometry, distance, duration }) } return { days: out, lines, segments, accessLines, + vias: out.flatMap(d => d.legVias.flat()), totalDistance: out.reduce((s, d) => s + d.distance, 0), totalDuration: out.reduce((s, d) => s + d.duration, 0), // Service stops are not stops in this sense — a charger on the way is part of the diff --git a/client/src/pages/TripPlannerPage.tsx b/client/src/pages/TripPlannerPage.tsx index c1e09b13d..cf8d7a70d 100644 --- a/client/src/pages/TripPlannerPage.tsx +++ b/client/src/pages/TripPlannerPage.tsx @@ -390,7 +390,7 @@ function TripPlannerPageDesktop(): React.ReactElement | null { places={mapPlaces} dayPlaces={dayPlaces} route={roadtripActive ? roadtripRoutes.lines : route} - routeVias={roadtripActive ? [] : routeVias} + routeVias={roadtripActive ? roadtripRoutes.vias : routeVias} accessLines={roadtripActive ? roadtripRoutes.accessLines : undefined} showTransitRoutes={transitRoutesShown} // The route toggle belongs to one day, so the map needs that day to diff --git a/shared/src/i18n/ar/roadtrip.ts b/shared/src/i18n/ar/roadtrip.ts index 1bc3f2080..dfc9ce058 100644 --- a/shared/src/i18n/ar/roadtrip.ts +++ b/shared/src/i18n/ar/roadtrip.ts @@ -18,6 +18,7 @@ const roadtrip: TranslationStrings = { 'roadtrip.poi.fuel': 'وقود', 'roadtrip.poi.charging': 'شحن', 'roadtrip.stop.offRoad': '{distance} من الطريق', + 'roadtrip.via.plugin': 'توقف في الطريق', 'roadtrip.poi.rest': 'استراحة', 'roadtrip.poi.campsite': 'مخيم', 'roadtrip.poi.food': 'طعام', diff --git a/shared/src/i18n/br/roadtrip.ts b/shared/src/i18n/br/roadtrip.ts index 328cc939f..2f7535d97 100644 --- a/shared/src/i18n/br/roadtrip.ts +++ b/shared/src/i18n/br/roadtrip.ts @@ -18,6 +18,7 @@ const roadtrip: TranslationStrings = { 'roadtrip.poi.fuel': 'Combustível', 'roadtrip.poi.charging': 'Carregamento', 'roadtrip.stop.offRoad': '{distance} da estrada', + 'roadtrip.via.plugin': 'Parada no caminho', 'roadtrip.poi.rest': 'Área de descanso', 'roadtrip.poi.campsite': 'Camping', 'roadtrip.poi.food': 'Comida', diff --git a/shared/src/i18n/ca/roadtrip.ts b/shared/src/i18n/ca/roadtrip.ts index 7f815fcd5..a8b081f9d 100644 --- a/shared/src/i18n/ca/roadtrip.ts +++ b/shared/src/i18n/ca/roadtrip.ts @@ -18,6 +18,7 @@ const roadtrip: TranslationStrings = { 'roadtrip.poi.fuel': 'Combustible', 'roadtrip.poi.charging': 'Càrrega', 'roadtrip.stop.offRoad': '{distance} des de la carretera', + 'roadtrip.via.plugin': 'Parada al camí', 'roadtrip.poi.rest': 'Àrea de descans', 'roadtrip.poi.campsite': 'Càmping', 'roadtrip.poi.food': 'Menjar', diff --git a/shared/src/i18n/cs/roadtrip.ts b/shared/src/i18n/cs/roadtrip.ts index 24d5a2fcd..ad77da054 100644 --- a/shared/src/i18n/cs/roadtrip.ts +++ b/shared/src/i18n/cs/roadtrip.ts @@ -18,6 +18,7 @@ const roadtrip: TranslationStrings = { 'roadtrip.poi.fuel': 'Palivo', 'roadtrip.poi.charging': 'Nabíjení', 'roadtrip.stop.offRoad': '{distance} od silnice', + 'roadtrip.via.plugin': 'Zastávka po cestě', 'roadtrip.poi.rest': 'Odpočívadlo', 'roadtrip.poi.campsite': 'Kemp', 'roadtrip.poi.food': 'Jídlo', diff --git a/shared/src/i18n/de/roadtrip.ts b/shared/src/i18n/de/roadtrip.ts index 0d73fd3ac..5793d3065 100644 --- a/shared/src/i18n/de/roadtrip.ts +++ b/shared/src/i18n/de/roadtrip.ts @@ -18,6 +18,7 @@ const roadtrip: TranslationStrings = { 'roadtrip.poi.fuel': 'Tanken', 'roadtrip.poi.charging': 'Laden', 'roadtrip.stop.offRoad': '{distance} von der Straße', + 'roadtrip.via.plugin': 'Halt unterwegs', 'roadtrip.poi.rest': 'Rastplatz', 'roadtrip.poi.campsite': 'Campingplatz', 'roadtrip.poi.food': 'Essen', diff --git a/shared/src/i18n/en/roadtrip.ts b/shared/src/i18n/en/roadtrip.ts index 5f55a2028..d2e19d57f 100644 --- a/shared/src/i18n/en/roadtrip.ts +++ b/shared/src/i18n/en/roadtrip.ts @@ -18,6 +18,7 @@ const roadtrip: TranslationStrings = { 'roadtrip.poi.fuel': 'Fuel', 'roadtrip.poi.charging': 'Charging', 'roadtrip.stop.offRoad': '{distance} from the road', + 'roadtrip.via.plugin': 'Stop on the way', 'roadtrip.poi.rest': 'Rest area', 'roadtrip.poi.campsite': 'Campsite', 'roadtrip.poi.food': 'Food', diff --git a/shared/src/i18n/es/roadtrip.ts b/shared/src/i18n/es/roadtrip.ts index 633e1531e..38634e9bc 100644 --- a/shared/src/i18n/es/roadtrip.ts +++ b/shared/src/i18n/es/roadtrip.ts @@ -18,6 +18,7 @@ const roadtrip: TranslationStrings = { 'roadtrip.poi.fuel': 'Gasolina', 'roadtrip.poi.charging': 'Recarga', 'roadtrip.stop.offRoad': '{distance} desde la carretera', + 'roadtrip.via.plugin': 'Parada en el camino', 'roadtrip.poi.rest': 'Área de descanso', 'roadtrip.poi.campsite': 'Camping', 'roadtrip.poi.food': 'Comida', diff --git a/shared/src/i18n/fr/roadtrip.ts b/shared/src/i18n/fr/roadtrip.ts index 77918b195..09b7be8f5 100644 --- a/shared/src/i18n/fr/roadtrip.ts +++ b/shared/src/i18n/fr/roadtrip.ts @@ -18,6 +18,7 @@ const roadtrip: TranslationStrings = { 'roadtrip.poi.fuel': 'Carburant', 'roadtrip.poi.charging': 'Recharge', 'roadtrip.stop.offRoad': '{distance} de la route', + 'roadtrip.via.plugin': 'Arrêt en chemin', 'roadtrip.poi.rest': 'Aire de repos', 'roadtrip.poi.campsite': 'Camping', 'roadtrip.poi.food': 'Restauration', diff --git a/shared/src/i18n/gr/roadtrip.ts b/shared/src/i18n/gr/roadtrip.ts index 37643f19f..f1b847474 100644 --- a/shared/src/i18n/gr/roadtrip.ts +++ b/shared/src/i18n/gr/roadtrip.ts @@ -18,6 +18,7 @@ const roadtrip: TranslationStrings = { 'roadtrip.poi.fuel': 'Καύσιμα', 'roadtrip.poi.charging': 'Φόρτιση', 'roadtrip.stop.offRoad': '{distance} από τον δρόμο', + 'roadtrip.via.plugin': 'Στάση στον δρόμο', 'roadtrip.poi.rest': 'Χώρος ανάπαυσης', 'roadtrip.poi.campsite': 'Κάμπινγκ', 'roadtrip.poi.food': 'Φαγητό', diff --git a/shared/src/i18n/hu/roadtrip.ts b/shared/src/i18n/hu/roadtrip.ts index 2609b5240..a34b6453a 100644 --- a/shared/src/i18n/hu/roadtrip.ts +++ b/shared/src/i18n/hu/roadtrip.ts @@ -18,6 +18,7 @@ const roadtrip: TranslationStrings = { 'roadtrip.poi.fuel': 'Üzemanyag', 'roadtrip.poi.charging': 'Töltés', 'roadtrip.stop.offRoad': '{distance} az úttól', + 'roadtrip.via.plugin': 'Megálló útközben', 'roadtrip.poi.rest': 'Pihenőhely', 'roadtrip.poi.campsite': 'Kemping', 'roadtrip.poi.food': 'Étkezés', diff --git a/shared/src/i18n/id/roadtrip.ts b/shared/src/i18n/id/roadtrip.ts index 90f806060..feae282f5 100644 --- a/shared/src/i18n/id/roadtrip.ts +++ b/shared/src/i18n/id/roadtrip.ts @@ -18,6 +18,7 @@ const roadtrip: TranslationStrings = { 'roadtrip.poi.fuel': 'Bahan bakar', 'roadtrip.poi.charging': 'Pengisian daya', 'roadtrip.stop.offRoad': '{distance} dari jalan', + 'roadtrip.via.plugin': 'Berhenti di jalan', 'roadtrip.poi.rest': 'Tempat istirahat', 'roadtrip.poi.campsite': 'Perkemahan', 'roadtrip.poi.food': 'Makanan', diff --git a/shared/src/i18n/it/roadtrip.ts b/shared/src/i18n/it/roadtrip.ts index ca7f71e23..20a08b33f 100644 --- a/shared/src/i18n/it/roadtrip.ts +++ b/shared/src/i18n/it/roadtrip.ts @@ -18,6 +18,7 @@ const roadtrip: TranslationStrings = { 'roadtrip.poi.fuel': 'Carburante', 'roadtrip.poi.charging': 'Ricarica', 'roadtrip.stop.offRoad': '{distance} dalla strada', + 'roadtrip.via.plugin': 'Sosta lungo il percorso', 'roadtrip.poi.rest': 'Area di sosta', 'roadtrip.poi.campsite': 'Campeggio', 'roadtrip.poi.food': 'Ristoro', diff --git a/shared/src/i18n/ja/roadtrip.ts b/shared/src/i18n/ja/roadtrip.ts index f4c1ff30c..33ad0cb61 100644 --- a/shared/src/i18n/ja/roadtrip.ts +++ b/shared/src/i18n/ja/roadtrip.ts @@ -18,6 +18,7 @@ const roadtrip: TranslationStrings = { 'roadtrip.poi.fuel': '給油', 'roadtrip.poi.charging': '充電', 'roadtrip.stop.offRoad': '道路から{distance}', + 'roadtrip.via.plugin': '途中の立ち寄り', 'roadtrip.poi.rest': 'サービスエリア', 'roadtrip.poi.campsite': 'キャンプ場', 'roadtrip.poi.food': '食事', diff --git a/shared/src/i18n/ko/roadtrip.ts b/shared/src/i18n/ko/roadtrip.ts index e2da977c7..6b405d223 100644 --- a/shared/src/i18n/ko/roadtrip.ts +++ b/shared/src/i18n/ko/roadtrip.ts @@ -18,6 +18,7 @@ const roadtrip: TranslationStrings = { 'roadtrip.poi.fuel': '주유', 'roadtrip.poi.charging': '충전', 'roadtrip.stop.offRoad': '도로에서 {distance}', + 'roadtrip.via.plugin': '경유 정차', 'roadtrip.poi.rest': '휴게소', 'roadtrip.poi.campsite': '캠핑장', 'roadtrip.poi.food': '식사', diff --git a/shared/src/i18n/nl/roadtrip.ts b/shared/src/i18n/nl/roadtrip.ts index fe1b1a0f9..b061b15e1 100644 --- a/shared/src/i18n/nl/roadtrip.ts +++ b/shared/src/i18n/nl/roadtrip.ts @@ -18,6 +18,7 @@ const roadtrip: TranslationStrings = { 'roadtrip.poi.fuel': 'Tanken', 'roadtrip.poi.charging': 'Laden', 'roadtrip.stop.offRoad': '{distance} vanaf de weg', + 'roadtrip.via.plugin': 'Stop onderweg', 'roadtrip.poi.rest': 'Rustplaats', 'roadtrip.poi.campsite': 'Camping', 'roadtrip.poi.food': 'Eten', diff --git a/shared/src/i18n/pl/roadtrip.ts b/shared/src/i18n/pl/roadtrip.ts index 085c7ffde..ff4f79d96 100644 --- a/shared/src/i18n/pl/roadtrip.ts +++ b/shared/src/i18n/pl/roadtrip.ts @@ -18,6 +18,7 @@ const roadtrip: TranslationStrings = { 'roadtrip.poi.fuel': 'Paliwo', 'roadtrip.poi.charging': 'Ładowanie', 'roadtrip.stop.offRoad': '{distance} od drogi', + 'roadtrip.via.plugin': 'Postojój po drodze', 'roadtrip.poi.rest': 'Miejsce odpoczynku', 'roadtrip.poi.campsite': 'Kemping', 'roadtrip.poi.food': 'Jedzenie', diff --git a/shared/src/i18n/ru/roadtrip.ts b/shared/src/i18n/ru/roadtrip.ts index 0aea3e957..dab3d4c57 100644 --- a/shared/src/i18n/ru/roadtrip.ts +++ b/shared/src/i18n/ru/roadtrip.ts @@ -18,6 +18,7 @@ const roadtrip: TranslationStrings = { 'roadtrip.poi.fuel': 'Заправка', 'roadtrip.poi.charging': 'Зарядка', 'roadtrip.stop.offRoad': '{distance} от дороги', + 'roadtrip.via.plugin': 'Остановка в пути', 'roadtrip.poi.rest': 'Зона отдыха', 'roadtrip.poi.campsite': 'Кемпинг', 'roadtrip.poi.food': 'Еда', diff --git a/shared/src/i18n/sv/roadtrip.ts b/shared/src/i18n/sv/roadtrip.ts index f654de8ac..e1474a91d 100644 --- a/shared/src/i18n/sv/roadtrip.ts +++ b/shared/src/i18n/sv/roadtrip.ts @@ -18,6 +18,7 @@ const roadtrip: TranslationStrings = { 'roadtrip.poi.fuel': 'Bensin', 'roadtrip.poi.charging': 'Laddning', 'roadtrip.stop.offRoad': '{distance} från vägen', + 'roadtrip.via.plugin': 'Stopp på vägen', 'roadtrip.poi.rest': 'Rastplats', 'roadtrip.poi.campsite': 'Campingplats', 'roadtrip.poi.food': 'Mat', diff --git a/shared/src/i18n/tr/roadtrip.ts b/shared/src/i18n/tr/roadtrip.ts index aa07e6154..d10ff3c44 100644 --- a/shared/src/i18n/tr/roadtrip.ts +++ b/shared/src/i18n/tr/roadtrip.ts @@ -18,6 +18,7 @@ const roadtrip: TranslationStrings = { 'roadtrip.poi.fuel': 'Yakıt', 'roadtrip.poi.charging': 'Şarj', 'roadtrip.stop.offRoad': 'yoldan {distance}', + 'roadtrip.via.plugin': 'Yol üzerinde mola', 'roadtrip.poi.rest': 'Dinlenme alanı', 'roadtrip.poi.campsite': 'Kamp alanı', 'roadtrip.poi.food': 'Yemek', diff --git a/shared/src/i18n/uk/roadtrip.ts b/shared/src/i18n/uk/roadtrip.ts index f5938928d..bc1a1a34c 100644 --- a/shared/src/i18n/uk/roadtrip.ts +++ b/shared/src/i18n/uk/roadtrip.ts @@ -18,6 +18,7 @@ const roadtrip: TranslationStrings = { 'roadtrip.poi.fuel': 'Заправка', 'roadtrip.poi.charging': 'Зарядка', 'roadtrip.stop.offRoad': '{distance} від дороги', + 'roadtrip.via.plugin': 'Зупинка в дорозі', 'roadtrip.poi.rest': 'Зона відпочинку', 'roadtrip.poi.campsite': 'Кемпінг', 'roadtrip.poi.food': 'Їжа', diff --git a/shared/src/i18n/vi/roadtrip.ts b/shared/src/i18n/vi/roadtrip.ts index 3d1968233..d541f1657 100644 --- a/shared/src/i18n/vi/roadtrip.ts +++ b/shared/src/i18n/vi/roadtrip.ts @@ -18,6 +18,7 @@ const roadtrip: TranslationStrings = { 'roadtrip.poi.fuel': 'Xăng dầu', 'roadtrip.poi.charging': 'Sạc điện', 'roadtrip.stop.offRoad': '{distance} từ đường', + 'roadtrip.via.plugin': 'Dừng chân trên đường', 'roadtrip.poi.rest': 'Trạm dừng nghỉ', 'roadtrip.poi.campsite': 'Khu cắm trại', 'roadtrip.poi.food': 'Đồ ăn', diff --git a/shared/src/i18n/zh-TW/roadtrip.ts b/shared/src/i18n/zh-TW/roadtrip.ts index 8867489a7..0770581d8 100644 --- a/shared/src/i18n/zh-TW/roadtrip.ts +++ b/shared/src/i18n/zh-TW/roadtrip.ts @@ -18,6 +18,7 @@ const roadtrip: TranslationStrings = { 'roadtrip.poi.fuel': '加油', 'roadtrip.poi.charging': '充電', 'roadtrip.stop.offRoad': '距道路 {distance}', + 'roadtrip.via.plugin': '途中停靠', 'roadtrip.poi.rest': '休息站', 'roadtrip.poi.campsite': '露營地', 'roadtrip.poi.food': '餐飲', diff --git a/shared/src/i18n/zh/roadtrip.ts b/shared/src/i18n/zh/roadtrip.ts index c12463903..f895039db 100644 --- a/shared/src/i18n/zh/roadtrip.ts +++ b/shared/src/i18n/zh/roadtrip.ts @@ -18,6 +18,7 @@ const roadtrip: TranslationStrings = { 'roadtrip.poi.fuel': '加油', 'roadtrip.poi.charging': '充电', 'roadtrip.stop.offRoad': '距道路 {distance}', + 'roadtrip.via.plugin': '途中停靠', 'roadtrip.poi.rest': '服务区', 'roadtrip.poi.campsite': '露营地', 'roadtrip.poi.food': '餐饮',