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': '餐饮',