From 56266138c66fa3431907dd9cad9c5c853922017b Mon Sep 17 00:00:00 2001 From: Maurice Date: Fri, 4 Sep 2026 21:46:02 +0200 Subject: [PATCH] feat(roadtrip): show the halts a routing plugin puts on the drive MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit A plugin may already route a day in road trip mode, and the charging stops it answered with were thrown away: the map was handed an empty list and the rail never looked. That made "build it as a plugin" — the answer given to charging prices, to ABRP and to the AFIR data — untrue in the one mode where it matters. Each halt is measured along the run once and filed on the leg it is actually driven on, using the leg lengths the router reported rather than by projecting the stops too: leg distances are exact by construction, while two stops close together project onto each other's stretch and would put a charge the wrong side of a town. The rail row is deliberately the poorest of the four. No number, no editable stay, no arrival time, a hollow disc: the halt belongs to the provider, not to the traveller, and everything filled on that rail is something they put there. Nor is it ever written back — a plugin halt stored as a via would go out as a waypoint on the next run, and the plugin would plan around its own charging stop. No time on the row for a second reason: the SDK contract folds the stop into the leg duration, so the arrivals already account for it. Adding dwellSeconds to the schedule would push every arrival after a charge back by the charging time. --- .../Roadtrip/RoadtripCorridorPanel.test.tsx | 2 + .../Roadtrip/RoadtripSidebar.test.tsx | 2 + .../components/Roadtrip/RoadtripSidebar.tsx | 52 ++++++++++++++++ .../components/Roadtrip/roadtripModel.test.ts | 27 +++++++++ .../src/components/Roadtrip/roadtripModel.ts | 17 ++++++ .../Roadtrip/useRoadtripCorridor.test.ts | 4 +- .../components/Roadtrip/useRoadtripRoutes.ts | 60 +++++++++++++------ client/src/pages/TripPlannerPage.tsx | 2 +- shared/src/i18n/ar/roadtrip.ts | 1 + shared/src/i18n/br/roadtrip.ts | 1 + shared/src/i18n/ca/roadtrip.ts | 1 + shared/src/i18n/cs/roadtrip.ts | 1 + shared/src/i18n/de/roadtrip.ts | 1 + shared/src/i18n/en/roadtrip.ts | 1 + shared/src/i18n/es/roadtrip.ts | 1 + shared/src/i18n/fr/roadtrip.ts | 1 + shared/src/i18n/gr/roadtrip.ts | 1 + shared/src/i18n/hu/roadtrip.ts | 1 + shared/src/i18n/id/roadtrip.ts | 1 + shared/src/i18n/it/roadtrip.ts | 1 + shared/src/i18n/ja/roadtrip.ts | 1 + shared/src/i18n/ko/roadtrip.ts | 1 + shared/src/i18n/nl/roadtrip.ts | 1 + shared/src/i18n/pl/roadtrip.ts | 1 + shared/src/i18n/ru/roadtrip.ts | 1 + shared/src/i18n/sv/roadtrip.ts | 1 + shared/src/i18n/tr/roadtrip.ts | 1 + shared/src/i18n/uk/roadtrip.ts | 1 + shared/src/i18n/vi/roadtrip.ts | 1 + shared/src/i18n/zh-TW/roadtrip.ts | 1 + shared/src/i18n/zh/roadtrip.ts | 1 + 31 files changed, 168 insertions(+), 21 deletions(-) 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': '餐饮',