From 36ac5de84cbed1c4ccefdfda277017534dc48431 Mon Sep 17 00:00:00 2001 From: Maurice Date: Fri, 4 Sep 2026 23:44:16 +0200 Subject: [PATCH] feat(roadtrip): narrow the corridor to one stretch of the drive A seven-hundred-kilometre day turns up seventy hits spread over the whole of it, and the traveller wanting a break near Berlin has to read all seventy and work out from "245 km along the route" which ones are anywhere near. That is the wrong way round: you decide roughly where to stop, and then look at what is there. Picking a point narrows the list to what lies within 25, 50 or 100 km of it. This is a subtraction rather than a second search, because the stops and the hits are already measured along the same thinned line. The midpoints of the legs are offered alongside the stops, and they are the point of the feature: a break on a five-hour drive belongs in the middle of the drive, not at either end. Asking only about stops would answer "what is near Berlin" when the question is "what is near the halfway mark". The chosen point is stored as which anchor it is rather than as a distance. alongKm is measured against a line thinned with a tolerance derived from the corridor width, so a kilometre figure kept here would quietly drift when the width goes from 5 to 10; resolved through the stops it lands on the same place again. It also names its day, so switching days cannot apply yesterday's break to today's drive. --- .../Roadtrip/RoadtripCorridorPanel.test.tsx | 5 + .../Roadtrip/RoadtripCorridorPanel.tsx | 51 +++++++- .../components/Roadtrip/roadtripModel.test.ts | 20 ++++ .../src/components/Roadtrip/roadtripModel.ts | 26 +++++ .../Roadtrip/useRoadtripCorridor.test.ts | 29 +++++ .../Roadtrip/useRoadtripCorridor.ts | 110 +++++++++++++----- shared/src/i18n/ar/roadtrip.ts | 2 + shared/src/i18n/br/roadtrip.ts | 2 + shared/src/i18n/ca/roadtrip.ts | 2 + shared/src/i18n/cs/roadtrip.ts | 2 + shared/src/i18n/de/roadtrip.ts | 2 + shared/src/i18n/en/roadtrip.ts | 2 + shared/src/i18n/es/roadtrip.ts | 2 + shared/src/i18n/fr/roadtrip.ts | 2 + shared/src/i18n/gr/roadtrip.ts | 2 + shared/src/i18n/hu/roadtrip.ts | 2 + shared/src/i18n/id/roadtrip.ts | 2 + shared/src/i18n/it/roadtrip.ts | 2 + shared/src/i18n/ja/roadtrip.ts | 2 + shared/src/i18n/ko/roadtrip.ts | 2 + shared/src/i18n/nl/roadtrip.ts | 2 + shared/src/i18n/pl/roadtrip.ts | 2 + shared/src/i18n/ru/roadtrip.ts | 2 + shared/src/i18n/sv/roadtrip.ts | 2 + shared/src/i18n/tr/roadtrip.ts | 2 + shared/src/i18n/uk/roadtrip.ts | 2 + shared/src/i18n/vi/roadtrip.ts | 2 + shared/src/i18n/zh-TW/roadtrip.ts | 2 + shared/src/i18n/zh/roadtrip.ts | 2 + 29 files changed, 254 insertions(+), 33 deletions(-) diff --git a/client/src/components/Roadtrip/RoadtripCorridorPanel.test.tsx b/client/src/components/Roadtrip/RoadtripCorridorPanel.test.tsx index 556e98934..c9dd9b830 100644 --- a/client/src/components/Roadtrip/RoadtripCorridorPanel.test.tsx +++ b/client/src/components/Roadtrip/RoadtripCorridorPanel.test.tsx @@ -91,6 +91,11 @@ function corridor(over: Partial = {}, search: Partial + {/* Around one point of the drive, which is how a break is actually planned: you + decide roughly where to stop and then look at what is there, rather than + reading seventy hits spread over seven hundred kilometres. The midpoint of + a leg is offered alongside the stops, because a break on a five-hour drive + belongs in the middle of it and not at either end. */} + {corridor.anchors.length > 1 && corridor.day ? ( +
+ { + const raw = String(value) + if (!raw) { corridor.setSection(null); return } + const [kind, index] = raw.split(':') + corridor.setSection({ + dayId: corridor.day!.dayId, + kind: kind as 'stop' | 'leg', + index: Number(index), + }) + }} + options={[ + { value: '', label: t('roadtrip.poi.wholeDay') }, + ...corridor.anchors.map(a => ({ + value: `${a.kind}:${a.index}`, + label: a.kind === 'stop' + ? (corridor.day!.stops[a.index]?.name ?? t('roadtrip.poi.wholeDay')) + : t('roadtrip.poi.midLeg', { + from: corridor.day!.stops[a.index]?.name ?? '', + to: corridor.day!.stops[a.index + 1]?.name ?? '', + }), + })), + ]} + style={{ flex: 1 }} + size="sm" + /> + {corridor.section ? ( + corridor.setSectionKm(Number(value))} + options={CORRIDOR_SECTION_KM.map(km => ({ + value: String(km), + label: `± ${formatDistance(km, distanceUnit)}`, + }))} + style={{ width: 110 }} + size="sm" + /> + ) : null} +
+ ) : null} + {/* Only where there is a charger to narrow. Two answers OSM actually carries often enough to filter on: which plug, and how fast. A station that states neither stays in the list, because roughly two thirds of them state no diff --git a/client/src/components/Roadtrip/roadtripModel.test.ts b/client/src/components/Roadtrip/roadtripModel.test.ts index 3f33ff957..b88731e1a 100644 --- a/client/src/components/Roadtrip/roadtripModel.test.ts +++ b/client/src/components/Roadtrip/roadtripModel.test.ts @@ -7,6 +7,7 @@ import { deriveDriveWarnings, legIndexForAlong, refuelsRange, + sectionAnchors, parseClock, splitIntoRuns, sumLegSeconds, @@ -421,3 +422,22 @@ describe('deriveDriveWarnings', () => { expect(out.carryKm).toBe(0) }) }) + +describe('sectionAnchors', () => { + it('FE-ROADTRIP-MODEL-080: every stop, and the middle of every leg between them', () => { + // The leg midpoints are the point of the feature: a break on a five-hour drive is + // planned in the middle of the drive, not at either end of it. + expect(sectionAnchors([0, 100, 250])).toEqual([ + { kind: 'stop', index: 0, alongKm: 0 }, + { kind: 'leg', index: 0, alongKm: 50 }, + { kind: 'stop', index: 1, alongKm: 100 }, + { kind: 'leg', index: 1, alongKm: 175 }, + { kind: 'stop', index: 2, alongKm: 250 }, + ]) + }) + + it('FE-ROADTRIP-MODEL-081: a single stop has no leg to sit in the middle of', () => { + expect(sectionAnchors([42])).toEqual([{ kind: 'stop', index: 0, alongKm: 42 }]) + expect(sectionAnchors([])).toEqual([]) + }) +}) diff --git a/client/src/components/Roadtrip/roadtripModel.ts b/client/src/components/Roadtrip/roadtripModel.ts index 9b8ec45e8..18d89026d 100644 --- a/client/src/components/Roadtrip/roadtripModel.ts +++ b/client/src/components/Roadtrip/roadtripModel.ts @@ -373,6 +373,32 @@ export function legIndexForAlong(legEndMeters: number[], alongMeters: number): n return legEndMeters.length - 1 } +/** + * The places on a day somebody would plan a break around. + * + * Every stop, and the middle of every leg between two of them. The leg midpoints are the + * point of the whole thing: a break on a five-hour drive is planned in the middle of the + * drive, not at either end of it, and offering only the stops would mean asking "what is + * near Berlin" when the question is "what is near the halfway mark". + */ +export interface SectionAnchor { + kind: 'stop' | 'leg' + /** Index of the stop, or of the stop the leg leaves. */ + index: number + alongKm: number +} + +export function sectionAnchors(stopsAlongKm: number[]): SectionAnchor[] { + const out: SectionAnchor[] = [] + for (let i = 0; i < stopsAlongKm.length; i++) { + out.push({ kind: 'stop', index: i, alongKm: stopsAlongKm[i] }) + if (i + 1 < stopsAlongKm.length) { + out.push({ kind: 'leg', index: i, alongKm: (stopsAlongKm[i] + stopsAlongKm[i + 1]) / 2 }) + } + } + return out +} + 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 7fc157bc8..8690f1615 100644 --- a/client/src/components/Roadtrip/useRoadtripCorridor.test.ts +++ b/client/src/components/Roadtrip/useRoadtripCorridor.test.ts @@ -142,4 +142,33 @@ describe('useRoadtripCorridor', () => { // Both map renderers rebuild every marker when this array's identity changes. expect(result.current.visible).toBe(before) }) + + it('FE-ROADTRIP-CORRIDORSTATE-020: narrowing to one point keeps only what is near it', () => { + // A seven-hundred-kilometre day turns up hits along the whole of it. Picking the point + // you mean to break at is a subtraction, because both figures are measured against + // the same thinned line rather than being asked for separately. + useCorridorPois.mockReturnValue(searchWith([ + poi({ osm_id: 'near', name: 'Near', alongKm: 30 }), + poi({ osm_id: 'far', name: 'Far', alongKm: 600 }), + ])) + const { result } = renderHook(() => useRoadtripCorridor(routes([day()]))) + + act(() => { result.current.setSectionKm(50) }) + act(() => { result.current.setSection({ dayId: 1, kind: 'stop', index: 0 }) }) + + // Stop 0 sits at the start of the drive, so only the hit within 50 km survives. + expect(result.current.visible.map(p => p.osm_id)).toEqual(['near']) + }) + + it('FE-ROADTRIP-CORRIDORSTATE-021: an anchor belonging to another day narrows nothing', () => { + // The section names a day as well as an index, so switching days cannot silently + // apply yesterday's break to today's drive. + const hits = [poi({ osm_id: 'a', name: 'A', alongKm: 10 }), poi({ osm_id: 'b', name: 'B', alongKm: 600 })] + useCorridorPois.mockReturnValue(searchWith(hits)) + const { result } = renderHook(() => useRoadtripCorridor(routes([day()]))) + + act(() => { result.current.setSection({ dayId: 99, kind: 'stop', index: 0 }) }) + + expect(result.current.visible).toBe(hits) + }) }) diff --git a/client/src/components/Roadtrip/useRoadtripCorridor.ts b/client/src/components/Roadtrip/useRoadtripCorridor.ts index 5e77c8c78..2ae2290f9 100644 --- a/client/src/components/Roadtrip/useRoadtripCorridor.ts +++ b/client/src/components/Roadtrip/useRoadtripCorridor.ts @@ -1,6 +1,7 @@ import { useCallback, useEffect, useMemo, useRef, useState } from 'react' import { useCorridorPois, type CorridorPoi, type CorridorSearch } from './useCorridorPois' -import { insertIndexForAlong } from './roadtripModel' +import { sectionAnchors, insertIndexForAlong } from './roadtripModel' +import type { SectionAnchor } from './roadtripModel' import { projectOntoRoute, type LatLng } from './corridor' import type { RoadtripDay, RoadtripRoutes } from './useRoadtripRoutes' @@ -11,6 +12,9 @@ export { CORRIDOR_CATEGORY_KEYS } from './stopKinds' /** Corridor widths offered, in kilometres. */ export const CORRIDOR_WIDTHS_KM = [2, 5, 10] +/** How far either side of a chosen point the list is narrowed to, in kilometres. */ +export const CORRIDOR_SECTION_KM = [25, 50, 100] + export interface RoadtripCorridor { /** Which day's drive is being searched. */ dayId: string @@ -23,6 +27,14 @@ export interface RoadtripCorridor { search: CorridorSearch /** Narrows what was found by name or brand. Empty means everything. */ nameFilter: string + /** The stops and leg midpoints a break can be planned around. */ + anchors: SectionAnchor[] + /** Which of them the list is narrowed to, or null for the whole day. */ + section: { dayId: number; kind: 'stop' | 'leg'; index: number } | null + setSection: (value: { dayId: number; kind: 'stop' | 'leg'; index: number } | null) => void + /** How far either side of it, in kilometres. */ + sectionKm: number + setSectionKm: (value: number) => void /** Socket family the charging hits are narrowed to, or empty for any. */ socketFilter: string setSocketFilter: (value: string) => void @@ -56,6 +68,17 @@ export function useRoadtripCorridor(routes: RoadtripRoutes): RoadtripCorridor { const [categories, setCategories] = useState(['fuel']) const [widthKm, setWidthKm] = useState(5) const [nameFilter, setNameFilter] = useState('') + /** + * Which point of the day the list is narrowed around, if any. + * + * Stored as the anchor's identity rather than as a distance, on purpose: `alongKm` is + * measured against the thinned spine, whose length depends on the corridor width + * (`useCorridorPois` thins with a tolerance derived from it). A kilometre figure kept + * here would quietly shift when the width changes from 5 to 10; resolved through + * `stopsAlongKm` it lands on the same stop again. + */ + const [section, setSection] = useState<{ dayId: number; kind: 'stop' | 'leg'; index: number } | null>(null) + const [sectionKm, setSectionKm] = useState(50) /** A socket family, or empty for any. Only ever applied to charging hits. */ const [socketFilter, setSocketFilter] = useState('') /** Minimum kW, or 0 for any. */ @@ -92,37 +115,6 @@ export function useRoadtripCorridor(routes: RoadtripRoutes): RoadtripCorridor { wasSearching.current = searching }, [searching]) - /** - * Memoised on purpose, not filtered where it is drawn: both map renderers tear down and - * rebuild every POI marker whenever the array's identity changes, so a `.filter()` in a - * render body would make the pins flicker on each keystroke and on every unrelated - * re-render. - * - * Brand counts as a name here because the server folds `operator` into `brand`, and - * "Shell" is what someone types when the OSM name is "Shell Autohof Nord". - */ - const visible = useMemo(() => { - const needle = nameFilter.trim().toLowerCase() - // The same array when nothing narrows it, not a copy of it: the map redraws off this - // reference, and handing it a fresh array on every render moves every pin. - if (!needle && !socketFilter && !minKw) return search.results - return search.results.filter(p => { - if (needle && !(p.name.toLowerCase().includes(needle) || (p.brand ?? '').toLowerCase().includes(needle))) { - return false - } - // The charging filters only ever hide charging hits. A rest area does not have a - // socket and is not answering the question, so filtering the whole list by one - // would empty it of everything the search also found. - if (p.category !== 'charging') return true - if (socketFilter && !p.charging?.sockets.some(s => s.type === socketFilter)) return false - // A station that does not state its power is kept. Roughly two thirds of them do - // not, and reading silence as "too slow" would throw away most of the map. - if (minKw && p.charging?.sockets.some(s => s.kw != null) && !p.charging.sockets.some(s => (s.kw ?? 0) >= minKw)) { - return false - } - return true - }) - }, [search.results, nameFilter, socketFilter, minKw]) /** * Where a hit belongs in the day's chain, as an index among its stops. @@ -142,6 +134,55 @@ export function useRoadtripCorridor(routes: RoadtripRoutes): RoadtripCorridor { }) }, [day, search.spine]) + /** + * The stops and leg midpoints of the current day, and the distance of the chosen one. + * + * Resolved here rather than stored: `section` names an anchor, and its kilometre figure + * is looked up fresh, so changing the corridor width does not move the point the list + * is narrowed around. + */ + const anchors = useMemo(() => sectionAnchors(stopsAlongKm), [stopsAlongKm]) + const anchorKm = useMemo(() => { + if (!section || !day || section.dayId !== day.dayId) return null + return anchors.find(a => a.kind === section.kind && a.index === section.index)?.alongKm ?? null + }, [section, day, anchors]) + + /** + * Memoised on purpose, not filtered where it is drawn: both map renderers tear down and + * rebuild every POI marker whenever the array's identity changes, so a `.filter()` in a + * render body would make the pins flicker on each keystroke and on every unrelated + * re-render. + * + * Brand counts as a name here because the server folds `operator` into `brand`, and + * "Shell" is what someone types when the OSM name is "Shell Autohof Nord". + */ + const visible = useMemo(() => { + const needle = nameFilter.trim().toLowerCase() + // The same array when nothing narrows it, not a copy of it: the map redraws off this + // reference, and handing it a fresh array on every render moves every pin. + if (!needle && !socketFilter && !minKw && anchorKm === null) return search.results + return search.results.filter(p => { + // Around one point of the drive, when one has been picked. The two figures are + // measured against the same thinned line, so this really is a subtraction and not + // a second search. + if (anchorKm !== null && Math.abs(p.alongKm - anchorKm) > sectionKm) return false + if (needle && !(p.name.toLowerCase().includes(needle) || (p.brand ?? '').toLowerCase().includes(needle))) { + return false + } + // The charging filters only ever hide charging hits. A rest area does not have a + // socket and is not answering the question, so filtering the whole list by one + // would empty it of everything the search also found. + if (p.category !== 'charging') return true + if (socketFilter && !p.charging?.sockets.some(s => s.type === socketFilter)) return false + // A station that does not state its power is kept. Roughly two thirds of them do + // not, and reading silence as "too slow" would throw away most of the map. + if (minKw && p.charging?.sockets.some(s => s.kw != null) && !p.charging.sockets.some(s => (s.kw ?? 0) >= minKw)) { + return false + } + return true + }) + }, [search.results, nameFilter, socketFilter, minKw, anchorKm, sectionKm]) + const insertIndexFor = useCallback( (poi: CorridorPoi) => insertIndexForAlong(stopsAlongKm, poi.alongKm), [stopsAlongKm], @@ -157,6 +198,11 @@ export function useRoadtripCorridor(routes: RoadtripRoutes): RoadtripCorridor { setWidthKm, search, nameFilter, + anchors, + section, + setSection, + sectionKm, + setSectionKm, socketFilter, setSocketFilter, minKw, diff --git a/shared/src/i18n/ar/roadtrip.ts b/shared/src/i18n/ar/roadtrip.ts index b94e62d4b..139cb02ef 100644 --- a/shared/src/i18n/ar/roadtrip.ts +++ b/shared/src/i18n/ar/roadtrip.ts @@ -44,6 +44,8 @@ const roadtrip: TranslationStrings = { 'roadtrip.poi.anySocket': 'أي قابس', 'roadtrip.poi.anyPower': 'أي قدرة', 'roadtrip.poi.free': 'مجاني', + 'roadtrip.poi.wholeDay': 'اليوم كله', + 'roadtrip.poi.midLeg': 'في المنتصف، {from} إلى {to}', '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 0633bcfd4..52369017d 100644 --- a/shared/src/i18n/br/roadtrip.ts +++ b/shared/src/i18n/br/roadtrip.ts @@ -44,6 +44,8 @@ const roadtrip: TranslationStrings = { 'roadtrip.poi.anySocket': 'Qualquer tomada', 'roadtrip.poi.anyPower': 'Qualquer potência', 'roadtrip.poi.free': 'Gratuito', + 'roadtrip.poi.wholeDay': 'O dia todo', + 'roadtrip.poi.midLeg': 'Na metade, {from} ate {to}', '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 46fd31476..f0312b826 100644 --- a/shared/src/i18n/ca/roadtrip.ts +++ b/shared/src/i18n/ca/roadtrip.ts @@ -44,6 +44,8 @@ const roadtrip: TranslationStrings = { 'roadtrip.poi.anySocket': 'Qualsevol connector', 'roadtrip.poi.anyPower': 'Qualsevol potència', 'roadtrip.poi.free': 'Gratuït', + 'roadtrip.poi.wholeDay': 'Tot el dia', + 'roadtrip.poi.midLeg': 'A mig cami, {from} a {to}', '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 3ef776484..119cd4cdb 100644 --- a/shared/src/i18n/cs/roadtrip.ts +++ b/shared/src/i18n/cs/roadtrip.ts @@ -44,6 +44,8 @@ const roadtrip: TranslationStrings = { 'roadtrip.poi.anySocket': 'Libovolná zásuvka', 'roadtrip.poi.anyPower': 'Libovolný výkon', 'roadtrip.poi.free': 'Zdarma', + 'roadtrip.poi.wholeDay': 'Cely den', + 'roadtrip.poi.midLeg': 'V polovine, {from} do {to}', '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 0dce5a690..fd45bd5a3 100644 --- a/shared/src/i18n/de/roadtrip.ts +++ b/shared/src/i18n/de/roadtrip.ts @@ -44,6 +44,8 @@ const roadtrip: TranslationStrings = { 'roadtrip.poi.anySocket': 'Alle Stecker', 'roadtrip.poi.anyPower': 'Alle Leistungen', 'roadtrip.poi.free': 'Kostenlos', + 'roadtrip.poi.wholeDay': 'Ganzer Tag', + 'roadtrip.poi.midLeg': 'Auf halber Strecke, {from} bis {to}', '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 8da570095..7f75f3f50 100644 --- a/shared/src/i18n/en/roadtrip.ts +++ b/shared/src/i18n/en/roadtrip.ts @@ -44,6 +44,8 @@ const roadtrip: TranslationStrings = { 'roadtrip.poi.anySocket': 'Any plug', 'roadtrip.poi.anyPower': 'Any power', 'roadtrip.poi.free': 'Free', + 'roadtrip.poi.wholeDay': 'The whole day', + 'roadtrip.poi.midLeg': 'Halfway, {from} to {to}', '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 ff7bb41b5..68b928356 100644 --- a/shared/src/i18n/es/roadtrip.ts +++ b/shared/src/i18n/es/roadtrip.ts @@ -44,6 +44,8 @@ const roadtrip: TranslationStrings = { 'roadtrip.poi.anySocket': 'Cualquier conector', 'roadtrip.poi.anyPower': 'Cualquier potencia', 'roadtrip.poi.free': 'Gratis', + 'roadtrip.poi.wholeDay': 'Todo el dia', + 'roadtrip.poi.midLeg': 'A mitad, {from} a {to}', '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 711f375ae..01f8ed8f7 100644 --- a/shared/src/i18n/fr/roadtrip.ts +++ b/shared/src/i18n/fr/roadtrip.ts @@ -44,6 +44,8 @@ const roadtrip: TranslationStrings = { 'roadtrip.poi.anySocket': 'Toute prise', 'roadtrip.poi.anyPower': 'Toute puissance', 'roadtrip.poi.free': 'Gratuit', + 'roadtrip.poi.wholeDay': 'Toute la journee', + 'roadtrip.poi.midLeg': 'A mi-chemin, {from} vers {to}', '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 bbc90ad4c..afc4d5a6f 100644 --- a/shared/src/i18n/gr/roadtrip.ts +++ b/shared/src/i18n/gr/roadtrip.ts @@ -44,6 +44,8 @@ const roadtrip: TranslationStrings = { 'roadtrip.poi.anySocket': 'Κάθε βύσμα', 'roadtrip.poi.anyPower': 'Κάθε ισχύς', 'roadtrip.poi.free': 'Δωρεάν', + 'roadtrip.poi.wholeDay': 'Ολη η ημερα', + 'roadtrip.poi.midLeg': 'Στα μισα, {from} προς {to}', '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 1ba6c03c6..b6ceff83b 100644 --- a/shared/src/i18n/hu/roadtrip.ts +++ b/shared/src/i18n/hu/roadtrip.ts @@ -44,6 +44,8 @@ const roadtrip: TranslationStrings = { 'roadtrip.poi.anySocket': 'Bármely csatlakozó', 'roadtrip.poi.anyPower': 'Bármely teljesítmény', 'roadtrip.poi.free': 'Ingyenes', + 'roadtrip.poi.wholeDay': 'A teljes nap', + 'roadtrip.poi.midLeg': 'Felúton, {from} és {to} kozott', '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 db1ff47b1..34c63c856 100644 --- a/shared/src/i18n/id/roadtrip.ts +++ b/shared/src/i18n/id/roadtrip.ts @@ -44,6 +44,8 @@ const roadtrip: TranslationStrings = { 'roadtrip.poi.anySocket': 'Semua colokan', 'roadtrip.poi.anyPower': 'Semua daya', 'roadtrip.poi.free': 'Gratis', + 'roadtrip.poi.wholeDay': 'Sepanjang hari', + 'roadtrip.poi.midLeg': 'Di tengah, {from} ke {to}', '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 9b50a916c..b11deae75 100644 --- a/shared/src/i18n/it/roadtrip.ts +++ b/shared/src/i18n/it/roadtrip.ts @@ -44,6 +44,8 @@ const roadtrip: TranslationStrings = { 'roadtrip.poi.anySocket': 'Qualsiasi presa', 'roadtrip.poi.anyPower': 'Qualsiasi potenza', 'roadtrip.poi.free': 'Gratuito', + 'roadtrip.poi.wholeDay': 'Tutta la giornata', + 'roadtrip.poi.midLeg': 'A meta strada, {from} verso {to}', '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 7158f518d..3fba77cd0 100644 --- a/shared/src/i18n/ja/roadtrip.ts +++ b/shared/src/i18n/ja/roadtrip.ts @@ -44,6 +44,8 @@ const roadtrip: TranslationStrings = { 'roadtrip.poi.anySocket': 'すべてのプラグ', 'roadtrip.poi.anyPower': 'すべての出力', 'roadtrip.poi.free': '無料', + 'roadtrip.poi.wholeDay': '一日全体', + 'roadtrip.poi.midLeg': '中間地点、{from}から{to}', '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 3a515c9fe..147f494e6 100644 --- a/shared/src/i18n/ko/roadtrip.ts +++ b/shared/src/i18n/ko/roadtrip.ts @@ -44,6 +44,8 @@ const roadtrip: TranslationStrings = { 'roadtrip.poi.anySocket': '모든 플러그', 'roadtrip.poi.anyPower': '모든 출력', 'roadtrip.poi.free': '무료', + 'roadtrip.poi.wholeDay': '하루 전체', + 'roadtrip.poi.midLeg': '중간 지점, {from}에서 {to}', '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 4aec8ac2b..9f315b5df 100644 --- a/shared/src/i18n/nl/roadtrip.ts +++ b/shared/src/i18n/nl/roadtrip.ts @@ -44,6 +44,8 @@ const roadtrip: TranslationStrings = { 'roadtrip.poi.anySocket': 'Elke stekker', 'roadtrip.poi.anyPower': 'Elk vermogen', 'roadtrip.poi.free': 'Gratis', + 'roadtrip.poi.wholeDay': 'De hele dag', + 'roadtrip.poi.midLeg': 'Halverwege, {from} naar {to}', '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 9a2ccb5e7..a8ef76395 100644 --- a/shared/src/i18n/pl/roadtrip.ts +++ b/shared/src/i18n/pl/roadtrip.ts @@ -44,6 +44,8 @@ const roadtrip: TranslationStrings = { 'roadtrip.poi.anySocket': 'Dowolna wtyczka', 'roadtrip.poi.anyPower': 'Dowolna moc', 'roadtrip.poi.free': 'Bezpłatnie', + 'roadtrip.poi.wholeDay': 'Caly dzien', + 'roadtrip.poi.midLeg': 'W polowie, {from} do {to}', '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 75f746ec5..f31ad401f 100644 --- a/shared/src/i18n/ru/roadtrip.ts +++ b/shared/src/i18n/ru/roadtrip.ts @@ -44,6 +44,8 @@ const roadtrip: TranslationStrings = { 'roadtrip.poi.anySocket': 'Любой разъём', 'roadtrip.poi.anyPower': 'Любая мощность', 'roadtrip.poi.free': 'Бесплатно', + 'roadtrip.poi.wholeDay': 'Весь день', + 'roadtrip.poi.midLeg': 'На полпути, {from} до {to}', '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 1a1b36fea..1c55dfb4a 100644 --- a/shared/src/i18n/sv/roadtrip.ts +++ b/shared/src/i18n/sv/roadtrip.ts @@ -44,6 +44,8 @@ const roadtrip: TranslationStrings = { 'roadtrip.poi.anySocket': 'Alla kontakter', 'roadtrip.poi.anyPower': 'Alla effekter', 'roadtrip.poi.free': 'Gratis', + 'roadtrip.poi.wholeDay': 'Hela dagen', + 'roadtrip.poi.midLeg': 'Halvvags, {from} till {to}', '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 b2d42db81..9a53bbd1b 100644 --- a/shared/src/i18n/tr/roadtrip.ts +++ b/shared/src/i18n/tr/roadtrip.ts @@ -44,6 +44,8 @@ const roadtrip: TranslationStrings = { 'roadtrip.poi.anySocket': 'Tum soketler', 'roadtrip.poi.anyPower': 'Tum gucler', 'roadtrip.poi.free': 'Ucretsiz', + 'roadtrip.poi.wholeDay': 'Tum gun', + 'roadtrip.poi.midLeg': 'Yolun yarisi, {from} - {to}', '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 43a764a9e..a09278079 100644 --- a/shared/src/i18n/uk/roadtrip.ts +++ b/shared/src/i18n/uk/roadtrip.ts @@ -44,6 +44,8 @@ const roadtrip: TranslationStrings = { 'roadtrip.poi.anySocket': 'Будь-який роз’єм', 'roadtrip.poi.anyPower': 'Будь-яка потужність', 'roadtrip.poi.free': 'Безкоштовно', + 'roadtrip.poi.wholeDay': 'Весь день', + 'roadtrip.poi.midLeg': 'На пiвдорозi, {from} до {to}', '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 ad3e1a69b..d4589115c 100644 --- a/shared/src/i18n/vi/roadtrip.ts +++ b/shared/src/i18n/vi/roadtrip.ts @@ -44,6 +44,8 @@ const roadtrip: TranslationStrings = { 'roadtrip.poi.anySocket': 'Mọi loại phích', 'roadtrip.poi.anyPower': 'Mọi công suất', 'roadtrip.poi.free': 'Miễn phí', + 'roadtrip.poi.wholeDay': 'Cả ngày', + 'roadtrip.poi.midLeg': 'Giữa đường, {from} đến {to}', '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 a400887a7..42c76541b 100644 --- a/shared/src/i18n/zh-TW/roadtrip.ts +++ b/shared/src/i18n/zh-TW/roadtrip.ts @@ -44,6 +44,8 @@ const roadtrip: TranslationStrings = { 'roadtrip.poi.anySocket': '任意接口', 'roadtrip.poi.anyPower': '任意功率', 'roadtrip.poi.free': '免費', + 'roadtrip.poi.wholeDay': '全天', + 'roadtrip.poi.midLeg': '中途,{from} 至 {to}', '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 d6ddd157d..f57d3aa28 100644 --- a/shared/src/i18n/zh/roadtrip.ts +++ b/shared/src/i18n/zh/roadtrip.ts @@ -44,6 +44,8 @@ const roadtrip: TranslationStrings = { 'roadtrip.poi.anySocket': '任意接口', 'roadtrip.poi.anyPower': '任意功率', 'roadtrip.poi.free': '免费', + 'roadtrip.poi.wholeDay': '全天', + 'roadtrip.poi.midLeg': '中途,{from} 至 {to}', 'roadtrip.poi.rest': '服务区', 'roadtrip.poi.campsite': '露营地', 'roadtrip.poi.food': '餐饮',