diff --git a/client/src/components/Map/poiCategories.ts b/client/src/components/Map/poiCategories.ts index b6c54d955..ef53e7198 100644 --- a/client/src/components/Map/poiCategories.ts +++ b/client/src/components/Map/poiCategories.ts @@ -1,4 +1,4 @@ -import { Utensils, Coffee, Wine, BedDouble, Camera, Landmark, Trees, Ticket, Fuel, Zap, ParkingSquare, Tent, Binoculars, Mountain, Warehouse, Droplet, Toilet, Sandwich, Anchor, Sailboat, Wrench, type LucideIcon } from 'lucide-react' +import { Utensils, Coffee, Wine, BedDouble, Camera, Landmark, Trees, Ticket, Fuel, Zap, ParkingSquare, Tent, Binoculars, Mountain, Warehouse, Droplet, Toilet, Sandwich, Anchor, Sailboat, Wrench, Store, type LucideIcon } from 'lucide-react' import type { TourPoiCategory } from '@trek/shared' import { parsePluginPoiCategoryKey, type PluginPoiDetail } from '@trek/shared' // The road-trip kinds take their colour from the one place that defines it, so the @@ -62,6 +62,12 @@ export const TOUR_POI_LOOKS: Record = { slipway: { key: 'slipway', labelKey: 'tours.poi.cat.slipway', Icon: Anchor, color: coreColor('museum') }, boat_rental: { key: 'boat_rental', labelKey: 'tours.poi.cat.boat_rental', Icon: Sailboat, color: coreColor('cafe') }, bike_repair: { key: 'bike_repair', labelKey: 'tours.poi.cat.bike_repair', Icon: Wrench, color: coreColor('activity') }, + // CW-05: a city walk's own, each in the colour of the explore category it comes from. + museum: { key: 'museum', labelKey: 'tours.poi.cat.museum', Icon: Landmark, color: coreColor('museum') }, + park: { key: 'park', labelKey: 'tours.poi.cat.park', Icon: Trees, color: coreColor('nature') }, + cafe: { key: 'cafe', labelKey: 'tours.poi.cat.cafe', Icon: Coffee, color: coreColor('cafe') }, + restaurant: { key: 'restaurant', labelKey: 'tours.poi.cat.restaurant', Icon: Utensils, color: coreColor('restaurant') }, + market: { key: 'market', labelKey: 'tours.poi.cat.market', Icon: Store, color: coreColor('shopping') }, } // A Map rather than an object, so a category named after an Object.prototype member diff --git a/client/src/components/Tours/planner/StationDialog.tsx b/client/src/components/Tours/planner/StationDialog.tsx new file mode 100644 index 000000000..aa5714224 --- /dev/null +++ b/client/src/components/Tours/planner/StationDialog.tsx @@ -0,0 +1,152 @@ +import type { StationImportance } from '@trek/shared'; +import { MapPin, ShieldCheck } from 'lucide-react'; +import { useId, useState } from 'react'; +import { useTranslation } from '../../../i18n'; +import ToggleSwitch from '../../Settings/ToggleSwitch'; +import { EditorField, INPUT, LABEL, Segmented } from '../../shared/dialogParts'; +import { + DialogButton, + DialogFooter, + DialogHeader, + DialogShell, + DialogTile, + FooterSpacer, + NEUTRAL_TINT, + fs, +} from '../../shared/DialogShell'; +import { parseStay, withStationFields } from './stationModel'; +import type { TourPlannerWaypoint } from './tourPlannerWaypoints'; + +type Importance = StationImportance | 'normal'; +const IMPORTANCES: readonly Importance[] = ['normal', 'optional', 'must']; + +/** + * CW-02, CW-03, CW-10: one station of a city walk, as a dialog: its name, how + * long it is stayed at (empty is unknown, 0 a point passed through), how much + * it matters and whether a proposed change must leave it. Saving is one step + * of the history; nothing here plans the route again. + */ +export function StationDialog({ + point, + fallbackName, + onSave, + onClose, +}: { + point: TourPlannerWaypoint; + /** The name the row shows while the station has none of its own. */ + fallbackName?: string; + onSave: (next: TourPlannerWaypoint) => void; + onClose: () => void; +}) { + const { t } = useTranslation(); + const labelId = useId(); + const nameId = useId(); + const stayId = useId(); + const [name, setName] = useState(point.name ?? ''); + const [stay, setStay] = useState(point.stayMinutes == null ? '' : String(point.stayMinutes)); + const [importance, setImportance] = useState(point.importance ?? 'normal'); + const [protectedStation, setProtectedStation] = useState(point.protected === true); + const stayMinutes = parseStay(stay); + const stayInvalid = stayMinutes === undefined; + + const save = () => { + if (stayInvalid) return; + onSave( + withStationFields(point, { + name, + stayMinutes, + importance: importance === 'normal' ? null : importance, + protected: protectedStation, + }) + ); + }; + + return ( + + + + } + tint={NEUTRAL_TINT} + labelId={labelId} + onClose={onClose} + eyebrow={t('tours.walk.station.eyebrow')} + title={name.trim() || fallbackName || t('tours.walk.station.unnamed')} + /> + } + footer={ + + + {t('common.cancel')} + + {t('common.save')} + + + } + > +
+ + setName(event.target.value)} + /> + + + setStay(event.target.value)} + /> + +
+ {t('tours.walk.station.importance')} + + value={importance} + onChange={setImportance} + label={t('tours.walk.station.importance')} + fill + options={IMPORTANCES.map((value) => ({ value, label: t(`tours.walk.importance.${value}`) }))} + /> + + {t(`tours.walk.importanceHint.${importance}`)} + +
+
+ + + + {t('tours.walk.station.protected')} + + + {t('tours.walk.station.protectedHint')} + + + setProtectedStation((value) => !value)} + label={t('tours.walk.station.protected')} + /> +
+
+
+ ); +} diff --git a/client/src/components/Tours/planner/TourEditorFields.tsx b/client/src/components/Tours/planner/TourEditorFields.tsx index 32ed172f6..b34c7e122 100644 --- a/client/src/components/Tours/planner/TourEditorFields.tsx +++ b/client/src/components/Tours/planner/TourEditorFields.tsx @@ -157,31 +157,34 @@ function TourTimeFields({ planner, locked }: TourFieldProps) { const manual = planner.plannedDurationMinutes != null; return (
-
- - } - > - {t('tours.planner.breaksShort')} - - - {planner.breakAdditionalInvalid && ( - {t('tours.planner.timeValueInvalid')} - )} -
+ {/* CW-02: a city walk stops at its stations; their stays take the place of breaks. */} + {planner.tourType !== 'walk' && ( +
+ + } + > + {t('tours.planner.breaksShort')} + + + {planner.breakAdditionalInvalid && ( + {t('tours.planner.timeValueInvalid')} + )} +
+ )}
0 + ? t('tours.walk.totalAtLeast', { time: formatPlannedTourDuration(stays.stayMinutes) }) + : formatPlannedTourDuration(stays.stayMinutes), + } + : { + label: t('tours.planner.breaksShort'), + value: planner.breakAdditionalMinutes == null ? '—' : formatPlannedTourDuration(planner.breakAdditionalMinutes), + }, { label: `${t('tours.planner.plannedShort')}${planner.plannedDurationMinutes != null ? ` (${t('tours.planner.plannedTotalManual')})` : ''}`, - value: planner.plannedTotalMinutes == null ? t('tours.planner.walkingTimeUnknown') : formatPlannedTourDuration(planner.plannedTotalMinutes), + value: planner.plannedTotalMinutes == null + ? t('tours.planner.walkingTimeUnknown') + : stays?.partial && planner.plannedDurationMinutes == null + ? t('tours.walk.totalAtLeast', { time: formatPlannedTourDuration(planner.plannedTotalMinutes) }) + : formatPlannedTourDuration(planner.plannedTotalMinutes), }, ] } diff --git a/client/src/components/Tours/planner/TourWaypointList.tsx b/client/src/components/Tours/planner/TourWaypointList.tsx index bb5160c4a..10112b543 100644 --- a/client/src/components/Tours/planner/TourWaypointList.tsx +++ b/client/src/components/Tours/planner/TourWaypointList.tsx @@ -17,6 +17,7 @@ import { Navigation, RefreshCw, Repeat, + SquarePen, Spline, Trash2, Unlink, @@ -46,6 +47,8 @@ import { usePaddleRegionLoad } from '../paddle/usePaddleRegionLoad'; import { usePaddleWaypointActions } from '../paddle/usePaddleWaypointActions'; import { usePortageRouting, usePortageWay } from '../paddle/usePortageRouting'; import { RailCard } from '../tourParts'; +import { StationDialog } from './StationDialog'; +import { StationPills } from './stationParts'; import { paddlePlanPoints, paddleRolesById } from './paddlePlanOf'; import type { PaddleLegFailure } from './paddleRouting'; import { lineLengthM } from './tourFixedLegs'; @@ -297,6 +300,7 @@ function WaypointRowLabel({ loopEnd, paddleRole, linked = false, + station = false, }: { point: TourPlannerController['waypoints'][number]; placeName: string | undefined; @@ -305,10 +309,12 @@ function WaypointRowLabel({ paddleRole?: PaddleRole | null; /** PR1-10: the point stands for this place of the trip, named after it. */ linked?: boolean; + /** CW-01: a city walk's station, which shows its stay, importance and protection. */ + station?: boolean; }) { const { t } = useTranslation(); const coordinates = `${point.lat.toFixed(5)}, ${point.lng.toFixed(5)}`; - const name = loopEnd ? t('tours.planner.loopEnd') : placeName; + const name = loopEnd ? t('tours.planner.loopEnd') : (point.name ?? placeName); return ( <> @@ -332,6 +338,7 @@ function WaypointRowLabel({ )} {point.auto && } + {station && !loopEnd && } {linked && !loopEnd ? ( // A stop standing for a place of the trip says so where the coordinates would be (PR1-10). @@ -451,6 +458,7 @@ function WaypointRowActions({ selected, isSaving, onUnlink, + stationName, }: { planner: TourPlannerController; point: WaypointPoint; @@ -460,11 +468,21 @@ function WaypointRowActions({ isSaving: boolean; /** PR1-10: given for a point that stands for a place; the place itself stays. */ onUnlink?: () => void; + /** CW-01: the name the row shows, for a station without one of its own. */ + stationName?: string; }) { const { t } = useTranslation(); const menu = useContextMenu(); const paddle = usePaddleWaypointActions(planner); + const [stationOpen, setStationOpen] = useState(false); + const station = planner.tourType === 'walk' && !point.shaping; const items: MenuItems = [ + ...(station + ? [ + { label: t('tours.walk.station.edit'), icon: SquarePen, onClick: () => setStationOpen(true) }, + { divider: true } as const, + ] + : []), { label: t('tours.planner.moveUp'), icon: ArrowUp, @@ -505,6 +523,17 @@ function WaypointRowActions({ + {stationOpen && ( + setStationOpen(false)} + onSave={(next) => { + planner.editWaypoints((current) => current.map((item) => (item.id === next.id ? next : item))); + setStationOpen(false); + }} + /> + )} ); } @@ -927,6 +956,7 @@ export default function TourWaypointList({ loopEnd={loopEnd} paddleRole={paddleRoleOf(point.id)} linked={!!linkedPlace} + station={planner.tourType === 'walk' && !point.shaping} /> {canEdit && ( @@ -938,6 +968,7 @@ export default function TourWaypointList({ selected={selected} isSaving={isSaving} onUnlink={point.linkedPlaceId ? () => tripPlaces.unlink(point.id) : undefined} + stationName={linkedPlace?.name ?? placeNames[point.id]} /> )}
diff --git a/client/src/components/Tours/planner/stationModel.ts b/client/src/components/Tours/planner/stationModel.ts new file mode 100644 index 000000000..b1e831373 --- /dev/null +++ b/client/src/components/Tours/planner/stationModel.ts @@ -0,0 +1,62 @@ +import type { TourPlannerWaypoint } from './tourPlannerWaypoints'; + +/** + * CW-02: a city walk's time, walking and staying kept apart. A stay nobody + * entered is unknown, never nothing: the total then counts only what is + * known and says it is at least that much. A point passed through is a stay + * of 0, entered on purpose. + */ +export interface WalkTimes { + walkingMinutes: number | null; + /** The stays entered, summed. */ + stayMinutes: number; + /** Stations whose stay nobody entered. */ + unknownStays: number; + /** Walking and the known stays; null while the walking time is unknown. */ + totalMinutes: number | null; + /** The total leaves out a stay or the walking time that is not known. */ + partial: boolean; +} + +/** The stations of a city walk: its points that are no shaping points. */ +export function stationsOf(points: readonly TourPlannerWaypoint[]): TourPlannerWaypoint[] { + return points.filter((point) => !point.shaping); +} + +export function walkTimes(points: readonly TourPlannerWaypoint[], walkingMinutes: number | null): WalkTimes { + const stations = stationsOf(points); + const stayMinutes = stations.reduce((sum, station) => sum + (station.stayMinutes ?? 0), 0); + const unknownStays = stations.filter((station) => station.stayMinutes == null).length; + return { + walkingMinutes, + stayMinutes, + unknownStays, + totalMinutes: walkingMinutes === null ? null : walkingMinutes + stayMinutes, + partial: unknownStays > 0, + }; +} + +/** What the station dialog edits; the rest of the point stays as it is. */ +export type StationFields = Pick; + +/** The point with the fields as edited, each one left out where it is empty. */ +export function withStationFields(point: TourPlannerWaypoint, fields: StationFields): TourPlannerWaypoint { + const { name: _name, stayMinutes: _stay, importance: _importance, protected: _protected, ...rest } = point; + const name = fields.name?.trim(); + return { + ...rest, + ...(name ? { name } : {}), + ...(fields.stayMinutes != null ? { stayMinutes: fields.stayMinutes } : {}), + ...(fields.importance ? { importance: fields.importance } : {}), + ...(fields.protected ? { protected: true } : {}), + }; +} + +/** A stay as typed: empty is unknown, a whole number from 0 to a day is minutes, anything else is not taken. */ +export function parseStay(text: string): number | null | undefined { + const trimmed = text.trim(); + if (trimmed === '') return null; + if (!/^\d{1,4}$/.test(trimmed)) return undefined; + const minutes = Number(trimmed); + return minutes <= 24 * 60 ? minutes : undefined; +} diff --git a/client/src/components/Tours/planner/stationParts.tsx b/client/src/components/Tours/planner/stationParts.tsx new file mode 100644 index 000000000..2812435e7 --- /dev/null +++ b/client/src/components/Tours/planner/stationParts.tsx @@ -0,0 +1,38 @@ +import { Clock, ShieldCheck } from 'lucide-react'; +import { useTranslation } from '../../../i18n'; +import { SoftPill } from '../../Planner/planParts'; +import { Tooltip } from '../../shared/Tooltip'; +import { formatPlannedTourDuration } from '../tourPresentation'; +import type { TourPlannerWaypoint } from './tourPlannerWaypoints'; + +/** + * CW-02, CW-03, CW-10: what a city walk's station row says besides its name: + * the stay (or that it is not known), an importance other than normal, and a + * protection, each a quiet pill. + */ +export function StationPills({ point }: { point: TourPlannerWaypoint }) { + const { t } = useTranslation(); + return ( + + + {point.importance && ( + + {t(`tours.walk.importance.${point.importance}`)} + + )} + {point.protected && ( + + + + + )} + + ); +} diff --git a/client/src/components/Tours/planner/tourPlannerFacts.ts b/client/src/components/Tours/planner/tourPlannerFacts.ts index 833164a7f..e551f147c 100644 --- a/client/src/components/Tours/planner/tourPlannerFacts.ts +++ b/client/src/components/Tours/planner/tourPlannerFacts.ts @@ -52,6 +52,8 @@ export interface TourPlannerFactsInput { /** A paddle tour's estimate on the water; null for a hike. */ paddleEstimate: { waterMinutes: number | null; estimateMinutes: number | null } | null; routeAnalysis: RouteGeometryAnalysis | null; + /** CW-02: a city walk's stays, summed as far as they are known; null for any other type, which takes its breaks. */ + stationStayMinutes?: number | null; /** The router's own time, seconds: a bike tour's riding time; stands in for a hike until the heights are in. */ durationSeconds: number | null; } @@ -69,7 +71,8 @@ function invalidMinutes(value: number | null): boolean { function movingMinutes(input: TourPlannerFactsInput, routerMinutes: number | null): number | null { const { paddleEstimate, routeAnalysis } = input; if (paddleEstimate) return paddleEstimate.waterMinutes === null ? null : paddleEstimate.estimateMinutes; - if (input.tourType === 'bike') return routerMinutes; + // A bike tour is timed by the router for its profile, a city walk by the router on foot. + if (input.tourType === 'bike' || input.tourType === 'walk') return routerMinutes; return routeAnalysis?.gain != null && routeAnalysis.loss != null ? hikingTimeMinutes(routeAnalysis.distanceKm, routeAnalysis.gain, routeAnalysis.loss) : routerMinutes; @@ -92,16 +95,17 @@ function typeFieldsOf(input: TourPlannerFactsInput) { hiking_preferences: null, }; } + const isHike = input.tourType === 'hike'; return { tour_type: input.tourType, - max_hiking_difficulty: isBike ? null : input.maxHikingDifficulty, + max_hiking_difficulty: isHike ? input.maxHikingDifficulty : null, bike_profile: isBike ? input.bikeProfile : null, boat_type: null, water_context: null, paddle_speed_kmh: null, portage_speed_kmh: null, paddle_lake_style: null, - hiking_preferences: isBike ? null : input.hikingPreferences, + hiking_preferences: isHike ? input.hikingPreferences : null, }; } @@ -114,8 +118,9 @@ export function tourPlannerFacts(input: TourPlannerFactsInput) { const { durationSeconds, breakAdditionalMinutes, plannedDurationMinutes } = input; const routerMinutes = durationSeconds === null ? null : Math.round(durationSeconds / 60); const walkingDurationMinutes = movingMinutes(input, routerMinutes); - const automaticPlannedTotalMinutes = - walkingDurationMinutes === null ? null : walkingDurationMinutes + (breakAdditionalMinutes ?? 0); + // A city walk's stops are its stays; it has no breaks on top, so nothing counts twice. + const onTop = input.tourType === 'walk' ? (input.stationStayMinutes ?? 0) : (breakAdditionalMinutes ?? 0); + const automaticPlannedTotalMinutes = walkingDurationMinutes === null ? null : walkingDurationMinutes + onTop; const { boatType, waterContext } = input.paddle; return { isPaddle, diff --git a/client/src/components/Tours/planner/tourPlannerWaypoints.ts b/client/src/components/Tours/planner/tourPlannerWaypoints.ts index 1058dc21d..96fa61a86 100644 --- a/client/src/components/Tours/planner/tourPlannerWaypoints.ts +++ b/client/src/components/Tours/planner/tourPlannerWaypoints.ts @@ -1,4 +1,4 @@ -import type { PaddleLock, TourFixedLeg, TourWaypoint, TourWaypointRole } from '@trek/shared'; +import type { PaddleLock, PlannableTourType, StationImportance, TourFixedLeg, TourWaypoint, TourWaypointRole } from '@trek/shared'; import type { AutoBase } from './paddleRouting'; import { keepJoinedFixedLegs } from './tourFixedLegs'; import { alignFreeLegs } from './tourRouteActions'; @@ -21,6 +21,18 @@ export interface TourPlannerWaypoint { carryNext?: boolean; /** PR1-10: the place of the trip this point stands for, chosen on purpose; the point keeps its own spot. */ linkedPlaceId?: number; + /** CW-03: the point's saved key, the same across saves, which a city walk's visits hang on. */ + key?: string; + /** The point's own name: a city walk's station, a place found along the route. */ + name?: string | null; + /** CW-02: a station's stay; null not known, 0 a point passed through. */ + stayMinutes?: number | null; + /** CW-03: how much a station matters; absent is normal. */ + importance?: StationImportance | null; + /** CW-10: a station a proposed change must leave where it is. */ + protected?: boolean; + /** CW-04: the station's opening hours as OpenStreetMap writes them. */ + openingHours?: string | null; } /** A saved tour's waypoints as the editor holds them, each under a fresh id. */ @@ -34,6 +46,12 @@ export function plannerWaypointsOf(saved: readonly TourWaypoint[], newId: () => ...(point.lock ? { lock: point.lock } : {}), ...(point.shaping ? { shaping: true } : {}), ...(point.linked_place_id ? { linkedPlaceId: point.linked_place_id } : {}), + ...(point.key ? { key: point.key } : {}), + ...(point.name ? { name: point.name } : {}), + ...(point.stay_minutes != null ? { stayMinutes: point.stay_minutes } : {}), + ...(point.importance ? { importance: point.importance } : {}), + ...(point.protected ? { protected: true } : {}), + ...(point.opening_hours ? { openingHours: point.opening_hours } : {}), })); } @@ -42,7 +60,9 @@ export function plannerWaypointsOf(saved: readonly TourWaypoint[], newId: () => * keeps and nothing of the editor's own (ids, automatic marks, a portage * waiting for its next point). */ -export function savedWaypointsOf(points: readonly TourPlannerWaypoint[]): TourWaypoint[] { +export function savedWaypointsOf(points: readonly TourPlannerWaypoint[], tourType?: PlannableTourType): TourWaypoint[] { + // A stay, an importance and a protection belong to a city walk's stations; another type sends none. + const walk = tourType === 'walk'; return points.map((point, sequence) => ({ lat: point.lat, lng: point.lng, @@ -52,6 +72,12 @@ export function savedWaypointsOf(points: readonly TourPlannerWaypoint[]): TourWa ...(point.lock ? { lock: point.lock } : {}), ...(point.shaping ? { shaping: true } : {}), ...(point.linkedPlaceId ? { linked_place_id: point.linkedPlaceId } : {}), + ...(point.key ? { key: point.key } : {}), + ...(point.name ? { name: point.name } : {}), + ...(walk && point.stayMinutes != null ? { stay_minutes: point.stayMinutes } : {}), + ...(walk && point.importance ? { importance: point.importance } : {}), + ...(walk && point.protected ? { protected: true } : {}), + ...(point.openingHours ? { opening_hours: point.openingHours } : {}), })); } diff --git a/client/src/components/Tours/planner/tourRouting.ts b/client/src/components/Tours/planner/tourRouting.ts index a719a9c5b..10cf5e6c2 100644 --- a/client/src/components/Tours/planner/tourRouting.ts +++ b/client/src/components/Tours/planner/tourRouting.ts @@ -103,6 +103,18 @@ export async function routeWalkingTour( }, signal) } +/** + * CW-01: a city walk, on foot between its stations. No hiking limit is sent, + * so the router keeps to the ways it takes for walking anyway (T1), and no + * route choice of a hike. + */ +export async function routeCityWalk( + waypoints: LandWaypoint[], + signal?: AbortSignal, +): Promise { + return routeOnLand(waypoints, 'walking', {}, signal) +} + /** BIK-01: a bike tour, routed for its profile; a leg drawn by hand is kept as drawn. */ export async function routeBikeTour( waypoints: LandWaypoint[], diff --git a/client/src/components/Tours/planner/useTourPlanner.ts b/client/src/components/Tours/planner/useTourPlanner.ts index 88dc42eab..fb6968664 100644 --- a/client/src/components/Tours/planner/useTourPlanner.ts +++ b/client/src/components/Tours/planner/useTourPlanner.ts @@ -5,6 +5,7 @@ import { tourRepo } from '../../../repo/tourRepo' import { randomId } from '../../../utils/randomId' import { analyzeRouteGeometry, type RouteProfileFocus } from '../../../utils/routeGeometry' import { closeLoop, isClosedLoop, reverseRoute, toggleFreeLeg, withPointAppended } from './tourRouteActions' +import { walkTimes } from './stationModel' import { hasCompleteElevation, useTourRoute } from './useTourRoute' import { normalizePlannerWaypoints, plannerWaypointsOf, ridablePoints, sameWaypointSnapshot, savedWaypointsOf, unlinkedPoint, type TourPlannerWaypoint } from './tourPlannerWaypoints' import { openedTourFields, tourPlannerFacts } from './tourPlannerFacts' @@ -478,12 +479,14 @@ export function useTourPlanner({ tripId, active = true, onSaved, canEdit: editPe const routedLegLengths = paddleRoute && routeCurrent ? paddleRoute.legLengths : undefined const paddleEstimate = useMemo(() => isPaddle ? paddleEstimateOf(waypoints, { effectiveSpeedKmh, portageSpeedKmh }, paddleSpeedInvalid, routedLegLengths) : null, [effectiveSpeedKmh, isPaddle, paddleSpeedInvalid, portageSpeedKmh, routedLegLengths, waypoints]) + // CW-02: a city walk's stays, what of them is known. + const stationStays = useMemo(() => (tourType === 'walk' ? walkTimes(waypoints, null) : null), [tourType, waypoints]) const facts = useMemo(() => tourPlannerFacts({ tourType, maxHikingDifficulty, hikingPreferences, bikeProfile, website, plannedDurationMinutes, breakAdditionalMinutes, paddle: { boatType, waterContext, speedKmh, portageSpeedKmh, lakeStyle }, - paddleSpeedInvalid, paddleEstimate, routeAnalysis, durationSeconds, + paddleSpeedInvalid, paddleEstimate, routeAnalysis, durationSeconds, stationStayMinutes: stationStays?.stayMinutes ?? null, }), [bikeProfile, boatType, breakAdditionalMinutes, durationSeconds, hikingPreferences, lakeStyle, maxHikingDifficulty, paddleEstimate, paddleSpeedInvalid, - plannedDurationMinutes, portageSpeedKmh, routeAnalysis, speedKmh, tourType, waterContext, website]) + plannedDurationMinutes, portageSpeedKmh, routeAnalysis, speedKmh, stationStays, tourType, waterContext, website]) const { websiteInvalid, plannedDurationInvalid, breakAdditionalInvalid, paddleIncomplete, walkingDurationMinutes, plannedTotalMinutes, plannedTotalInvalid, savedDurationSeconds, typeFields } = facts @@ -774,7 +777,7 @@ export function useTourPlanner({ tripId, active = true, onSaved, canEdit: editPe website: parsedWebsiteOrNull(website), ...typeFields, route_geometry: enrichedGeometry.map(point => [point[0], point[1], point[2]]), - waypoints: savedWaypointsOf(waypoints), + waypoints: savedWaypointsOf(waypoints, tourType), duration_seconds: savedDurationSeconds, planned_duration_minutes: plannedDurationMinutes, break_additional_minutes: breakAdditionalMinutes, @@ -834,7 +837,7 @@ export function useTourPlanner({ tripId, active = true, onSaved, canEdit: editPe description, setDescription, website, setWebsite, websiteInvalid, plannedDurationMinutes, setPlannedDurationMinutes, plannedDurationInvalid, breakAdditionalMinutes, setBreakAdditionalMinutes, breakAdditionalInvalid, - walkingDurationMinutes, plannedTotalMinutes, plannedTotalInvalid, + walkingDurationMinutes, plannedTotalMinutes, plannedTotalInvalid, stationStays, waypoints, selectedWaypointId, setSelectedWaypointId, addWaypoint, appendFixedLeg, setWaypointPosition, removeWaypoint, moveWaypoint, reorderWaypoint, editWaypoints: commitWaypoints, reverse, returnToStart, isLoop: isClosedLoop(waypoints), toggleWaypointFreeLeg, editPaddlePoints, amendWaypoints, paddleEstimate, diff --git a/client/src/components/Tours/planner/useTourRoute.ts b/client/src/components/Tours/planner/useTourRoute.ts index 642e44b6b..d2e6c8f0c 100644 --- a/client/src/components/Tours/planner/useTourRoute.ts +++ b/client/src/components/Tours/planner/useTourRoute.ts @@ -14,6 +14,7 @@ import { isTourRoute, type TourRouteUnreachable } from './tourRouteOutcome'; import { enrichTourElevations, routeBikeTour, + routeCityWalk, routeWalkingTour, type TourElevationPoint, type TourRoutePart, @@ -104,7 +105,9 @@ async function routeOnLand({ inputs, plannedKey, signal, stale, setPlanned, setU const routed = tourType === 'bike' ? await routeBikeTour(waypoints, bikeProfile, signal) - : await routeWalkingTour(waypoints, signal, maxHikingDifficulty, hikingPreferences); + : tourType === 'walk' + ? await routeCityWalk(waypoints, signal) + : await routeWalkingTour(waypoints, signal, maxHikingDifficulty, hikingPreferences); if (stale()) return; if (!isTourRoute(routed)) { setUnreachable(routed); diff --git a/server/tests/unit/db/tour-station-columns.test.ts b/server/tests/unit/db/tour-station-columns.test.ts new file mode 100644 index 000000000..bf5d3eb01 --- /dev/null +++ b/server/tests/unit/db/tour-station-columns.test.ts @@ -0,0 +1,21 @@ +import { TourStationVisitsStatus } from '../../../src/db/entities/TourStationVisits.entity'; +import { TourWaypointsImportance } from '../../../src/db/entities/TourWaypoints.entity'; +import { STATION_IMPORTANCES, TOUR_VISIT_STATUSES } from '@trek/shared'; + +import { describe, expect, it } from 'vitest'; + +/** + * CW-03: a station's importance and a visit's status are CHECK lists + * (migration 045300) the entities carry as enums, and the contract holds its + * own. A value one side knows and the other refuses would be a station the + * planner can save and the database cannot store. + */ +describe('city walk columns match the contract', () => { + it('knows the same importances as the station contract', () => { + expect(Object.values(TourWaypointsImportance).sort()).toEqual([...STATION_IMPORTANCES].sort()); + }); + + it('knows the same visit statuses as the visit contract', () => { + expect(Object.values(TourStationVisitsStatus).sort()).toEqual([...TOUR_VISIT_STATUSES].sort()); + }); +}); diff --git a/shared/src/i18n/ar/tours.ts b/shared/src/i18n/ar/tours.ts index e9a64400e..bda6de518 100644 --- a/shared/src/i18n/ar/tours.ts +++ b/shared/src/i18n/ar/tours.ts @@ -467,6 +467,38 @@ const tours: TranslationStrings = { 'tours.preferences.avoid_stepsHint': 'الدرج فقط حيث لا يوجد طريق بديل.', 'tours.preferences.avoid_ferries': 'تجنّب العبّارات', 'tours.preferences.avoid_ferriesHint': 'عبر البر حيث يوجد طريق بديل، حتى لو كان أطول.', + 'tours.planner.tourTypeHint.walk': 'سيرًا من محطة إلى محطة، دون حد للمشي الجبلي', + 'tours.poi.cat.museum': 'متاحف ومعارض', + 'tours.poi.cat.park': 'حدائق ومنتزهات', + 'tours.poi.cat.cafe': 'مقاهٍ', + 'tours.poi.cat.restaurant': 'مطاعم', + 'tours.poi.cat.market': 'أسواق', + 'tours.walk.station.eyebrow': 'محطة', + 'tours.walk.station.unnamed': 'محطة بلا اسم', + 'tours.walk.station.name': 'الاسم', + 'tours.walk.station.namePlaceholder': 'ما الموجود هنا', + 'tours.walk.station.stay': 'مدة البقاء بالدقائق', + 'tours.walk.station.stayHint': 'الفارغ يعني غير معروف بعد؛ 0 لنقطة تمر بها فقط.', + 'tours.walk.station.stayInvalid': 'أدخل دقائق كاملة، حتى يوم واحد.', + 'tours.walk.station.stayUnknown': 'غير معروف بعد', + 'tours.walk.station.stayUnknownShort': 'مدة غير معروفة', + 'tours.walk.station.passThrough': 'مرور فقط', + 'tours.walk.station.importance': 'الأهمية', + 'tours.walk.station.protected': 'محمية', + 'tours.walk.station.protectedHint': 'أي تعديل مقترح يُبقي هذه المحطة مكانها. لا يزال بإمكانك تعديلها بنفسك.', + 'tours.walk.station.edit': 'تعديل المحطة', + 'tours.walk.importance.normal': 'عادية', + 'tours.walk.importance.optional': 'اختيارية', + 'tours.walk.importance.must': 'لا بد من زيارتها', + 'tours.walk.importanceHint.normal': 'تُزار ضمن سير الجولة.', + 'tours.walk.importanceHint.optional': 'يمكن تجاوزها إذا ضاق الوقت.', + 'tours.walk.importanceHint.must': 'أي تعديل مقترح لاحقًا يُبقيها في الجولة.', + 'tours.walk.timeEstimated': 'وقت المشي التقديري', + 'tours.walk.timeEstimatedShort': 'المشي', + 'tours.walk.timeEstimatedInfoLabel': 'كيف يُحسب وقت المشي', + 'tours.walk.timeEstimatedInfo': 'وقت المسار سيرًا بين المحطات، دون مدد البقاء.', + 'tours.walk.stays': 'مدد البقاء', + 'tours.walk.totalAtLeast': '{time} على الأقل', 'tours.planner.tourType': 'نوع الجولة', 'tours.paddle.boat': 'القارب', 'tours.paddle.boat.kayak': 'كاياك', diff --git a/shared/src/i18n/az/tours.ts b/shared/src/i18n/az/tours.ts index b59693bda..be3526668 100644 --- a/shared/src/i18n/az/tours.ts +++ b/shared/src/i18n/az/tours.ts @@ -461,6 +461,39 @@ const tours: TranslationStrings = { 'tours.preferences.avoid_stepsHint': 'Pilləkən yalnız dolama yol olmayan yerdə.', 'tours.preferences.avoid_ferries': 'Bərələrdən qaç', 'tours.preferences.avoid_ferriesHint': 'Dolama yol varsa, daha uzun olsa da quru ilə.', + 'tours.planner.tourTypeHint.walk': 'Dayanacaqdan dayanacağa piyada, yürüyüş həddi olmadan', + 'tours.poi.cat.museum': 'Muzeylər və qalereyalar', + 'tours.poi.cat.park': 'Parklar və bağlar', + 'tours.poi.cat.cafe': 'Kafelər', + 'tours.poi.cat.restaurant': 'Yemək yerləri', + 'tours.poi.cat.market': 'Bazarlar', + 'tours.walk.station.eyebrow': 'Dayanacaq', + 'tours.walk.station.unnamed': 'Adsız dayanacaq', + 'tours.walk.station.name': 'Ad', + 'tours.walk.station.namePlaceholder': 'Burada nə var', + 'tours.walk.station.stay': 'Qalma müddəti, dəqiqə', + 'tours.walk.station.stayHint': 'Boş hələ məlum deyil deməkdir; yalnız keçdiyin nöqtə üçün 0.', + 'tours.walk.station.stayInvalid': 'Tam dəqiqə daxil et, ən çox bir gün.', + 'tours.walk.station.stayUnknown': 'Hələ məlum deyil', + 'tours.walk.station.stayUnknownShort': 'Müddət bilinmir', + 'tours.walk.station.passThrough': 'Keçid', + 'tours.walk.station.importance': 'Əhəmiyyət', + 'tours.walk.station.protected': 'Qorunur', + 'tours.walk.station.protectedHint': + 'Təklif olunan dəyişiklik bu dayanacağı yerində saxlayır. Özün yenə də redaktə edə bilərsən.', + 'tours.walk.station.edit': 'Dayanacağı redaktə et', + 'tours.walk.importance.normal': 'Adi', + 'tours.walk.importance.optional': 'İstəyə bağlı', + 'tours.walk.importance.must': 'Mütləq baxılmalı', + 'tours.walk.importanceHint.normal': 'Gəzinti boyunca ziyarət olunur.', + 'tours.walk.importanceHint.optional': 'Vaxt az olanda buraxıla bilər.', + 'tours.walk.importanceHint.must': 'Sonradan təklif olunan dəyişiklik onu gəzintidə saxlayır.', + 'tours.walk.timeEstimated': 'Təxmini yürüş vaxtı', + 'tours.walk.timeEstimatedShort': 'Yürüş', + 'tours.walk.timeEstimatedInfoLabel': 'Yürüş vaxtı necə hesablanır', + 'tours.walk.timeEstimatedInfo': 'Dayanacaqlar arasında marşrutlaşdırıcının piyada vaxtı, qalma müddətləri olmadan.', + 'tours.walk.stays': 'Qalmalar', + 'tours.walk.totalAtLeast': 'ən azı {time}', 'tours.planner.tourType': 'Tur növü', 'tours.paddle.boat': 'Qayıq', 'tours.paddle.boat.kayak': 'Kayak', // same-as-en diff --git a/shared/src/i18n/br/tours.ts b/shared/src/i18n/br/tours.ts index 959d5b8fe..3ffb4b878 100644 --- a/shared/src/i18n/br/tours.ts +++ b/shared/src/i18n/br/tours.ts @@ -468,6 +468,38 @@ const tours: TranslationStrings = { 'tours.preferences.avoid_stepsHint': 'Escadas só onde não há caminho alternativo.', 'tours.preferences.avoid_ferries': 'Evitar balsas', 'tours.preferences.avoid_ferriesHint': 'Por terra onde houver caminho alternativo, mesmo mais longo.', + 'tours.planner.tourTypeHint.walk': 'A pé de parada em parada, sem limite de trilha', + 'tours.poi.cat.museum': 'Museus e galerias', + 'tours.poi.cat.park': 'Parques e jardins', + 'tours.poi.cat.cafe': 'Cafeterias', + 'tours.poi.cat.restaurant': 'Lugares para comer', + 'tours.poi.cat.market': 'Feiras', + 'tours.walk.station.eyebrow': 'Parada', + 'tours.walk.station.unnamed': 'Parada sem nome', + 'tours.walk.station.name': 'Nome', + 'tours.walk.station.namePlaceholder': 'O que há aqui', + 'tours.walk.station.stay': 'Permanência em minutos', + 'tours.walk.station.stayHint': 'Vazio significa ainda desconhecido; 0 para um ponto pelo qual você só passa.', + 'tours.walk.station.stayInvalid': 'Informe minutos inteiros, até um dia.', + 'tours.walk.station.stayUnknown': 'Ainda desconhecido', + 'tours.walk.station.stayUnknownShort': 'Permanência em aberto', + 'tours.walk.station.passThrough': 'Só de passagem', + 'tours.walk.station.importance': 'Importância', + 'tours.walk.station.protected': 'Protegida', + 'tours.walk.station.protectedHint': 'Uma alteração proposta mantém esta parada onde está. Você ainda pode editá-la.', + 'tours.walk.station.edit': 'Editar parada', + 'tours.walk.importance.normal': 'Comum', + 'tours.walk.importance.optional': 'Opcional', + 'tours.walk.importance.must': 'Imperdível', + 'tours.walk.importanceHint.normal': 'Visitada conforme o passeio segue.', + 'tours.walk.importanceHint.optional': 'Pode ficar de fora se faltar tempo.', + 'tours.walk.importanceHint.must': 'Uma alteração proposta depois a mantém no passeio.', + 'tours.walk.timeEstimated': 'Tempo de caminhada estimado', + 'tours.walk.timeEstimatedShort': 'Caminhada', + 'tours.walk.timeEstimatedInfoLabel': 'Como o tempo de caminhada é calculado', + 'tours.walk.timeEstimatedInfo': 'O tempo a pé do roteador entre as paradas, sem as permanências.', + 'tours.walk.stays': 'Permanências', + 'tours.walk.totalAtLeast': 'pelo menos {time}', 'tours.planner.tourType': 'Tipo de passeio', 'tours.paddle.boat': 'Embarcação', 'tours.paddle.boat.kayak': 'Caiaque', diff --git a/shared/src/i18n/ca/tours.ts b/shared/src/i18n/ca/tours.ts index 2888f8fec..b9d44b449 100644 --- a/shared/src/i18n/ca/tours.ts +++ b/shared/src/i18n/ca/tours.ts @@ -469,6 +469,38 @@ const tours: TranslationStrings = { 'tours.preferences.avoid_stepsHint': 'Escales només on no hi ha cap volta possible.', 'tours.preferences.avoid_ferries': 'Evita transbordadors', 'tours.preferences.avoid_ferriesHint': 'Per terra on hi hagi una volta, encara que sigui més llarga.', + 'tours.planner.tourTypeHint.walk': "A peu de parada en parada, sense límit d'excursionisme", + 'tours.poi.cat.museum': 'Museus i galeries', + 'tours.poi.cat.park': 'Parcs i jardins', + 'tours.poi.cat.cafe': 'Cafeteries', + 'tours.poi.cat.restaurant': 'Llocs per menjar', + 'tours.poi.cat.market': 'Mercats', + 'tours.walk.station.eyebrow': 'Parada', + 'tours.walk.station.unnamed': 'Parada sense nom', + 'tours.walk.station.name': 'Nom', + 'tours.walk.station.namePlaceholder': 'Què hi ha aquí', + 'tours.walk.station.stay': 'Estada en minuts', + 'tours.walk.station.stayHint': 'Buit vol dir encara desconegut; 0 per a un punt pel qual només passes.', + 'tours.walk.station.stayInvalid': 'Introdueix minuts sencers, fins a un dia.', + 'tours.walk.station.stayUnknown': 'Encara desconegut', + 'tours.walk.station.stayUnknownShort': 'Estada oberta', + 'tours.walk.station.passThrough': 'Només de pas', + 'tours.walk.station.importance': 'Importància', + 'tours.walk.station.protected': 'Protegida', + 'tours.walk.station.protectedHint': 'Un canvi proposat manté aquesta parada on és. Tu encara la pots editar.', + 'tours.walk.station.edit': 'Edita la parada', + 'tours.walk.importance.normal': 'Habitual', + 'tours.walk.importance.optional': 'Opcional', + 'tours.walk.importance.must': 'Imprescindible', + 'tours.walk.importanceHint.normal': 'Es visita segons avança la ruta.', + 'tours.walk.importanceHint.optional': 'Es pot ometre si falta temps.', + 'tours.walk.importanceHint.must': 'Un canvi proposat més endavant la manté a la ruta.', + 'tours.walk.timeEstimated': 'Temps a peu estimat', + 'tours.walk.timeEstimatedShort': 'A peu', + 'tours.walk.timeEstimatedInfoLabel': 'Com es calcula el temps a peu', + 'tours.walk.timeEstimatedInfo': 'El temps a peu del càlcul de ruta entre parades, sense les estades.', + 'tours.walk.stays': 'Estades', + 'tours.walk.totalAtLeast': 'com a mínim {time}', 'tours.planner.tourType': 'Tipus de ruta', 'tours.paddle.boat': 'Embarcació', 'tours.paddle.boat.kayak': 'Caiac', diff --git a/shared/src/i18n/cs/tours.ts b/shared/src/i18n/cs/tours.ts index 84004bd97..32d0ff989 100644 --- a/shared/src/i18n/cs/tours.ts +++ b/shared/src/i18n/cs/tours.ts @@ -459,6 +459,38 @@ const tours: TranslationStrings = { 'tours.preferences.avoid_stepsHint': 'Schody jen tam, kde není cesta kolem.', 'tours.preferences.avoid_ferries': 'Vyhnout se přívozům', 'tours.preferences.avoid_ferriesHint': 'Po souši, kde vede cesta kolem, i když je delší.', + 'tours.planner.tourTypeHint.walk': 'Pěšky od zastávky k zastávce, bez turistického limitu', + 'tours.poi.cat.museum': 'Muzea a galerie', + 'tours.poi.cat.park': 'Parky a zahrady', + 'tours.poi.cat.cafe': 'Kavárny', + 'tours.poi.cat.restaurant': 'Kde se najíst', + 'tours.poi.cat.market': 'Trhy', + 'tours.walk.station.eyebrow': 'Zastávka', + 'tours.walk.station.unnamed': 'Zastávka bez názvu', + 'tours.walk.station.name': 'Název', + 'tours.walk.station.namePlaceholder': 'Co tu je', + 'tours.walk.station.stay': 'Pobyt v minutách', + 'tours.walk.station.stayHint': 'Prázdné znamená zatím neznámé; 0 pro místo, kterým jen projdeš.', + 'tours.walk.station.stayInvalid': 'Zadej celé minuty, nejvýše jeden den.', + 'tours.walk.station.stayUnknown': 'Zatím neznámé', + 'tours.walk.station.stayUnknownShort': 'Pobyt neznámý', + 'tours.walk.station.passThrough': 'Jen průchod', + 'tours.walk.station.importance': 'Důležitost', + 'tours.walk.station.protected': 'Chráněná', + 'tours.walk.station.protectedHint': 'Navržená změna nechá tuto zastávku na místě. Sám ji upravit můžeš dál.', + 'tours.walk.station.edit': 'Upravit zastávku', + 'tours.walk.importance.normal': 'Běžná', + 'tours.walk.importance.optional': 'Volitelná', + 'tours.walk.importance.must': 'Musíš vidět', + 'tours.walk.importanceHint.normal': 'Navštíví se během procházky.', + 'tours.walk.importanceHint.optional': 'Lze vynechat, když dochází čas.', + 'tours.walk.importanceHint.must': 'Pozdější navržená změna ji v procházce ponechá.', + 'tours.walk.timeEstimated': 'Odhadovaná doba chůze', + 'tours.walk.timeEstimatedShort': 'Chůze', + 'tours.walk.timeEstimatedInfoLabel': 'Jak se počítá doba chůze', + 'tours.walk.timeEstimatedInfo': 'Doba chůze mezi zastávkami podle plánovače trasy, bez pobytů.', + 'tours.walk.stays': 'Pobyty', + 'tours.walk.totalAtLeast': 'nejméně {time}', 'tours.planner.tourType': 'Typ túry', 'tours.paddle.boat': 'Loď', 'tours.paddle.boat.kayak': 'Kajak', diff --git a/shared/src/i18n/de/tours.ts b/shared/src/i18n/de/tours.ts index 0f7eca06e..d4fd96c74 100644 --- a/shared/src/i18n/de/tours.ts +++ b/shared/src/i18n/de/tours.ts @@ -472,6 +472,39 @@ const tours: TranslationStrings = { 'tours.preferences.avoid_stepsHint': 'Treppen nur, wo es keinen Weg außen herum gibt.', 'tours.preferences.avoid_ferries': 'Fähren meiden', 'tours.preferences.avoid_ferriesHint': 'Über Land, wo es einen Weg außen herum gibt, auch wenn er länger ist.', + 'tours.planner.tourTypeHint.walk': 'Zu Fuß von Station zu Station, ohne Wanderlimit', + 'tours.poi.cat.museum': 'Museen und Galerien', + 'tours.poi.cat.park': 'Parks und Gärten', + 'tours.poi.cat.cafe': 'Cafés', + 'tours.poi.cat.restaurant': 'Gaststätten', + 'tours.poi.cat.market': 'Märkte', + 'tours.walk.station.eyebrow': 'Station', + 'tours.walk.station.unnamed': 'Station ohne Namen', + 'tours.walk.station.name': 'Bezeichnung', + 'tours.walk.station.namePlaceholder': 'Was hier ist', + 'tours.walk.station.stay': 'Aufenthalt in Minuten', + 'tours.walk.station.stayHint': 'Leer heißt noch unbekannt; 0 für einen Punkt, den du nur passierst.', + 'tours.walk.station.stayInvalid': 'Ganze Minuten eingeben, höchstens einen Tag.', + 'tours.walk.station.stayUnknown': 'Noch unbekannt', + 'tours.walk.station.stayUnknownShort': 'Aufenthalt offen', + 'tours.walk.station.passThrough': 'Durchgang', + 'tours.walk.station.importance': 'Wichtigkeit', + 'tours.walk.station.protected': 'Geschützt', + 'tours.walk.station.protectedHint': + 'Ein Änderungsvorschlag lässt diese Station, wo sie ist. Du selbst kannst sie weiter bearbeiten.', + 'tours.walk.station.edit': 'Station bearbeiten', + 'tours.walk.importance.normal': 'Standard', + 'tours.walk.importance.optional': 'Kann entfallen', + 'tours.walk.importance.must': 'Unbedingt besuchen', + 'tours.walk.importanceHint.normal': 'Wird besucht, wie der Weg es ergibt.', + 'tours.walk.importanceHint.optional': 'Kann entfallen, wenn die Zeit knapp wird.', + 'tours.walk.importanceHint.must': 'Ein späterer Änderungsvorschlag behält sie im Walk.', + 'tours.walk.timeEstimated': 'Gehzeit (geschätzt)', + 'tours.walk.timeEstimatedShort': 'Gehzeit', + 'tours.walk.timeEstimatedInfoLabel': 'Wie die Gehzeit entsteht', + 'tours.walk.timeEstimatedInfo': 'Die Zeit des Routers zu Fuß zwischen den Stationen, ohne die Aufenthalte.', + 'tours.walk.stays': 'Aufenthalte', + 'tours.walk.totalAtLeast': 'mindestens {time}', 'tours.planner.tourType': 'Tourtyp', 'tours.paddle.boat': 'Boot', 'tours.paddle.boat.kayak': 'Kajak', diff --git a/shared/src/i18n/en/tours.ts b/shared/src/i18n/en/tours.ts index 8e9525124..3d371576a 100644 --- a/shared/src/i18n/en/tours.ts +++ b/shared/src/i18n/en/tours.ts @@ -460,6 +460,38 @@ const tours: TranslationStrings = { 'tours.preferences.avoid_stepsHint': 'Steps and stairs only where there is no way round.', 'tours.preferences.avoid_ferries': 'Avoid ferries', 'tours.preferences.avoid_ferriesHint': 'Over land where there is a way round, even when it is longer.', + 'tours.planner.tourTypeHint.walk': 'On foot from stop to stop, without a hiking limit', + 'tours.poi.cat.museum': 'Museums and galleries', + 'tours.poi.cat.park': 'Parks and gardens', + 'tours.poi.cat.cafe': 'Coffee shops', + 'tours.poi.cat.restaurant': 'Places to eat', + 'tours.poi.cat.market': 'Markets', + 'tours.walk.station.eyebrow': 'Stop', + 'tours.walk.station.unnamed': 'Unnamed stop', + 'tours.walk.station.name': 'Label', + 'tours.walk.station.namePlaceholder': 'What is here', + 'tours.walk.station.stay': 'Stay in minutes', + 'tours.walk.station.stayHint': 'Empty means not known yet; 0 for a point you only pass.', + 'tours.walk.station.stayInvalid': 'Enter whole minutes, up to a day.', + 'tours.walk.station.stayUnknown': 'Not known yet', + 'tours.walk.station.stayUnknownShort': 'Stay unknown', + 'tours.walk.station.passThrough': 'Passing through', + 'tours.walk.station.importance': 'Importance', + 'tours.walk.station.protected': 'Protected', + 'tours.walk.station.protectedHint': 'A proposed change keeps this stop where it is. You can still edit it yourself.', + 'tours.walk.station.edit': 'Edit stop', + 'tours.walk.importance.normal': 'Regular', + 'tours.walk.importance.optional': 'Optional', + 'tours.walk.importance.must': 'Must see', + 'tours.walk.importanceHint.normal': 'Visited as the walk goes.', + 'tours.walk.importanceHint.optional': 'Can be left out when time runs short.', + 'tours.walk.importanceHint.must': 'A change proposed later keeps it in the walk.', + 'tours.walk.timeEstimated': 'Est. walking time', + 'tours.walk.timeEstimatedShort': 'Walking', + 'tours.walk.timeEstimatedInfoLabel': 'How the walking time is worked out', + 'tours.walk.timeEstimatedInfo': "The router's time on foot between the stops, without the stays.", + 'tours.walk.stays': 'Stays', + 'tours.walk.totalAtLeast': 'at least {time}', 'tours.planner.tourType': 'Tour type', 'tours.paddle.boat': 'Boat', 'tours.paddle.boat.kayak': 'Kayak', diff --git a/shared/src/i18n/es/tours.ts b/shared/src/i18n/es/tours.ts index e27b7f3c6..5cf6385cf 100644 --- a/shared/src/i18n/es/tours.ts +++ b/shared/src/i18n/es/tours.ts @@ -467,6 +467,38 @@ const tours: TranslationStrings = { 'tours.preferences.avoid_stepsHint': 'Escaleras solo donde no hay forma de rodearlas.', 'tours.preferences.avoid_ferries': 'Evitar ferris', 'tours.preferences.avoid_ferriesHint': 'Por tierra donde haya una forma de rodear, aunque sea más larga.', + 'tours.planner.tourTypeHint.walk': 'A pie de parada en parada, sin límite de senderismo', + 'tours.poi.cat.museum': 'Museos y galerías', + 'tours.poi.cat.park': 'Parques y jardines', + 'tours.poi.cat.cafe': 'Cafeterías', + 'tours.poi.cat.restaurant': 'Sitios para comer', + 'tours.poi.cat.market': 'Mercados', + 'tours.walk.station.eyebrow': 'Parada', + 'tours.walk.station.unnamed': 'Parada sin nombre', + 'tours.walk.station.name': 'Nombre', + 'tours.walk.station.namePlaceholder': 'Qué hay aquí', + 'tours.walk.station.stay': 'Estancia en minutos', + 'tours.walk.station.stayHint': 'Vacío significa aún desconocido; 0 para un punto por el que solo pasas.', + 'tours.walk.station.stayInvalid': 'Introduce minutos enteros, hasta un día.', + 'tours.walk.station.stayUnknown': 'Aún desconocido', + 'tours.walk.station.stayUnknownShort': 'Estancia abierta', + 'tours.walk.station.passThrough': 'Solo de paso', + 'tours.walk.station.importance': 'Importancia', + 'tours.walk.station.protected': 'Protegida', + 'tours.walk.station.protectedHint': 'Un cambio propuesto deja esta parada donde está. Tú aún puedes editarla.', + 'tours.walk.station.edit': 'Editar parada', + 'tours.walk.importance.normal': 'Habitual', + 'tours.walk.importance.optional': 'Opcional', + 'tours.walk.importance.must': 'Imprescindible', + 'tours.walk.importanceHint.normal': 'Se visita según avanza el paseo.', + 'tours.walk.importanceHint.optional': 'Se puede omitir si falta tiempo.', + 'tours.walk.importanceHint.must': 'Un cambio propuesto más tarde la mantiene en el paseo.', + 'tours.walk.timeEstimated': 'Tiempo a pie estimado', + 'tours.walk.timeEstimatedShort': 'A pie', + 'tours.walk.timeEstimatedInfoLabel': 'Cómo se calcula el tiempo a pie', + 'tours.walk.timeEstimatedInfo': 'El tiempo a pie del planificador entre paradas, sin las estancias.', + 'tours.walk.stays': 'Estancias', + 'tours.walk.totalAtLeast': 'al menos {time}', 'tours.planner.tourType': 'Tipo de ruta', 'tours.paddle.boat': 'Embarcación', 'tours.paddle.boat.kayak': 'Kayak', // same-as-en diff --git a/shared/src/i18n/et/tours.ts b/shared/src/i18n/et/tours.ts index b5bdabf26..ba96203e0 100644 --- a/shared/src/i18n/et/tours.ts +++ b/shared/src/i18n/et/tours.ts @@ -462,6 +462,38 @@ const tours: TranslationStrings = { 'tours.preferences.avoid_stepsHint': 'Trepid ainult seal, kus ringi minna ei saa.', 'tours.preferences.avoid_ferries': 'Väldi praame', 'tours.preferences.avoid_ferriesHint': 'Mööda maad, kus saab ringi minna, isegi kui see on pikem.', + 'tours.planner.tourTypeHint.walk': 'Jalgsi peatusest peatusesse, ilma matkapiiranguta', + 'tours.poi.cat.museum': 'Muuseumid ja galeriid', + 'tours.poi.cat.park': 'Pargid ja aiad', + 'tours.poi.cat.cafe': 'Kohvikud', + 'tours.poi.cat.restaurant': 'Söögikohad', + 'tours.poi.cat.market': 'Turud', + 'tours.walk.station.eyebrow': 'Peatus', + 'tours.walk.station.unnamed': 'Nimetu peatus', + 'tours.walk.station.name': 'Nimi', + 'tours.walk.station.namePlaceholder': 'Mis siin on', + 'tours.walk.station.stay': 'Peatumine minutites', + 'tours.walk.station.stayHint': 'Tühi tähendab veel teadmata; 0 punkti jaoks, millest ainult möödud.', + 'tours.walk.station.stayInvalid': 'Sisesta täisminutid, kuni üks päev.', + 'tours.walk.station.stayUnknown': 'Veel teadmata', + 'tours.walk.station.stayUnknownShort': 'Peatus teadmata', + 'tours.walk.station.passThrough': 'Läbisõit', + 'tours.walk.station.importance': 'Tähtsus', + 'tours.walk.station.protected': 'Kaitstud', + 'tours.walk.station.protectedHint': 'Pakutud muudatus jätab selle peatuse paika. Ise saad seda endiselt muuta.', + 'tours.walk.station.edit': 'Muuda peatust', + 'tours.walk.importance.normal': 'Tavaline', + 'tours.walk.importance.optional': 'Valikuline', + 'tours.walk.importance.must': 'Kindlasti näha', + 'tours.walk.importanceHint.normal': 'Külastatakse jalutuse käigus.', + 'tours.walk.importanceHint.optional': 'Võib ära jätta, kui aega jääb väheks.', + 'tours.walk.importanceHint.must': 'Hilisem muudatusettepanek jätab selle jalutusse.', + 'tours.walk.timeEstimated': 'Hinnanguline kõndimisaeg', + 'tours.walk.timeEstimatedShort': 'Kõnd', + 'tours.walk.timeEstimatedInfoLabel': 'Kuidas kõndimisaeg arvutatakse', + 'tours.walk.timeEstimatedInfo': 'Marsruutija kõndimisaeg peatuste vahel, ilma peatumisteta.', + 'tours.walk.stays': 'Peatumised', + 'tours.walk.totalAtLeast': 'vähemalt {time}', 'tours.planner.tourType': 'Matka tüüp', 'tours.paddle.boat': 'Paat', 'tours.paddle.boat.kayak': 'Süst', diff --git a/shared/src/i18n/fr/tours.ts b/shared/src/i18n/fr/tours.ts index f74ac60ca..d314b0b3d 100644 --- a/shared/src/i18n/fr/tours.ts +++ b/shared/src/i18n/fr/tours.ts @@ -476,6 +476,39 @@ const tours: TranslationStrings = { 'tours.preferences.avoid_stepsHint': 'Des escaliers seulement là où on ne peut pas les contourner.', 'tours.preferences.avoid_ferries': 'Éviter les bacs', 'tours.preferences.avoid_ferriesHint': 'Par voie terrestre là où on peut contourner, même si c’est plus long.', + 'tours.planner.tourTypeHint.walk': "À pied d'étape en étape, sans limite de randonnée", + 'tours.poi.cat.museum': 'Musées et galeries', + 'tours.poi.cat.park': 'Parcs et jardins', + 'tours.poi.cat.cafe': 'Salons de café', + 'tours.poi.cat.restaurant': 'Où manger', + 'tours.poi.cat.market': 'Marchés', + 'tours.walk.station.eyebrow': 'Étape', + 'tours.walk.station.unnamed': 'Étape sans nom', + 'tours.walk.station.name': 'Intitulé', + 'tours.walk.station.namePlaceholder': "Ce qu'il y a ici", + 'tours.walk.station.stay': 'Arrêt en minutes', + 'tours.walk.station.stayHint': 'Vide signifie encore inconnu ; 0 pour un point où tu ne fais que passer.', + 'tours.walk.station.stayInvalid': "Saisis des minutes entières, jusqu'à une journée.", + 'tours.walk.station.stayUnknown': 'Encore inconnu', + 'tours.walk.station.stayUnknownShort': 'Arrêt inconnu', + 'tours.walk.station.passThrough': 'Simple passage', + 'tours.walk.station.importance': "Importance de l'étape", + 'tours.walk.station.protected': 'Protégée', + 'tours.walk.station.protectedHint': + 'Une modification proposée laisse cette étape à sa place. Tu peux toujours la modifier toi-même.', + 'tours.walk.station.edit': "Modifier l'étape", + 'tours.walk.importance.normal': 'Normale', + 'tours.walk.importance.optional': 'Facultative', + 'tours.walk.importance.must': 'Incontournable', + 'tours.walk.importanceHint.normal': 'Visitée au fil de la balade.', + 'tours.walk.importanceHint.optional': 'Peut sauter si le temps manque.', + 'tours.walk.importanceHint.must': 'Une modification proposée plus tard la garde dans la balade.', + 'tours.walk.timeEstimated': 'Temps de marche estimé', + 'tours.walk.timeEstimatedShort': 'Marche', + 'tours.walk.timeEstimatedInfoLabel': 'Comment le temps de marche est calculé', + 'tours.walk.timeEstimatedInfo': "Le temps à pied du calcul d'itinéraire entre les étapes, sans les arrêts.", + 'tours.walk.stays': 'Arrêts', + 'tours.walk.totalAtLeast': 'au moins {time}', 'tours.planner.tourType': 'Type de sortie', 'tours.paddle.boat': 'Embarcation', 'tours.paddle.boat.kayak': 'Kayak', // same-as-en diff --git a/shared/src/i18n/gr/tours.ts b/shared/src/i18n/gr/tours.ts index 79da6d5dd..732d69bf0 100644 --- a/shared/src/i18n/gr/tours.ts +++ b/shared/src/i18n/gr/tours.ts @@ -476,6 +476,39 @@ const tours: TranslationStrings = { 'tours.preferences.avoid_stepsHint': 'Σκαλοπάτια μόνο όπου δεν υπάρχει παράκαμψη.', 'tours.preferences.avoid_ferries': 'Αποφυγή πορθμείων', 'tours.preferences.avoid_ferriesHint': 'Από ξηράς όπου υπάρχει παράκαμψη, ακόμη κι αν είναι μεγαλύτερη.', + 'tours.planner.tourTypeHint.walk': 'Με τα πόδια από στάση σε στάση, χωρίς όριο πεζοπορίας', + 'tours.poi.cat.museum': 'Μουσεία και γκαλερί', + 'tours.poi.cat.park': 'Πάρκα και κήποι', + 'tours.poi.cat.cafe': 'Καφετέριες', + 'tours.poi.cat.restaurant': 'Μέρη για φαγητό', + 'tours.poi.cat.market': 'Αγορές', + 'tours.walk.station.eyebrow': 'Στάση', + 'tours.walk.station.unnamed': 'Στάση χωρίς όνομα', + 'tours.walk.station.name': 'Όνομα', + 'tours.walk.station.namePlaceholder': 'Τι υπάρχει εδώ', + 'tours.walk.station.stay': 'Παραμονή σε λεπτά', + 'tours.walk.station.stayHint': 'Κενό σημαίνει άγνωστο ακόμη· 0 για σημείο από το οποίο απλώς περνάς.', + 'tours.walk.station.stayInvalid': 'Δώσε ακέραια λεπτά, έως μία ημέρα.', + 'tours.walk.station.stayUnknown': 'Άγνωστο ακόμη', + 'tours.walk.station.stayUnknownShort': 'Παραμονή άγνωστη', + 'tours.walk.station.passThrough': 'Απλή διέλευση', + 'tours.walk.station.importance': 'Σημασία', + 'tours.walk.station.protected': 'Προστατευμένη', + 'tours.walk.station.protectedHint': + 'Μια προτεινόμενη αλλαγή αφήνει αυτή τη στάση στη θέση της. Εσύ μπορείς ακόμη να την επεξεργαστείς.', + 'tours.walk.station.edit': 'Επεξεργασία στάσης', + 'tours.walk.importance.normal': 'Κανονική', + 'tours.walk.importance.optional': 'Προαιρετική', + 'tours.walk.importance.must': 'Οπωσδήποτε', + 'tours.walk.importanceHint.normal': 'Επισκέπτεται καθώς προχωρά η βόλτα.', + 'tours.walk.importanceHint.optional': 'Μπορεί να παραλειφθεί αν πιέζει ο χρόνος.', + 'tours.walk.importanceHint.must': 'Μια μεταγενέστερη προτεινόμενη αλλαγή την κρατά στη βόλτα.', + 'tours.walk.timeEstimated': 'Εκτιμώμενος χρόνος περπατήματος', + 'tours.walk.timeEstimatedShort': 'Περπάτημα', + 'tours.walk.timeEstimatedInfoLabel': 'Πώς υπολογίζεται ο χρόνος περπατήματος', + 'tours.walk.timeEstimatedInfo': 'Ο χρόνος με τα πόδια του δρομολογητή ανάμεσα στις στάσεις, χωρίς τις παραμονές.', + 'tours.walk.stays': 'Παραμονές', + 'tours.walk.totalAtLeast': 'τουλάχιστον {time}', 'tours.planner.tourType': 'Τύπος διαδρομής', 'tours.paddle.boat': 'Σκάφος', 'tours.paddle.boat.kayak': 'Καγιάκ', diff --git a/shared/src/i18n/hu/tours.ts b/shared/src/i18n/hu/tours.ts index 7fd059b98..6e0a5cd54 100644 --- a/shared/src/i18n/hu/tours.ts +++ b/shared/src/i18n/hu/tours.ts @@ -468,6 +468,39 @@ const tours: TranslationStrings = { 'tours.preferences.avoid_stepsHint': 'Lépcső csak ott, ahol nincs kerülőút.', 'tours.preferences.avoid_ferries': 'Kompok kerülése', 'tours.preferences.avoid_ferriesHint': 'Szárazföldön, ahol van kerülőút, akkor is, ha hosszabb.', + 'tours.planner.tourTypeHint.walk': 'Gyalog megállóról megállóra, túrakorlát nélkül', + 'tours.poi.cat.museum': 'Múzeumok és galériák', + 'tours.poi.cat.park': 'Parkok és kertek', + 'tours.poi.cat.cafe': 'Kávézók', + 'tours.poi.cat.restaurant': 'Étkezőhelyek', + 'tours.poi.cat.market': 'Piacok', + 'tours.walk.station.eyebrow': 'Megálló', + 'tours.walk.station.unnamed': 'Névtelen megálló', + 'tours.walk.station.name': 'Megnevezés', + 'tours.walk.station.namePlaceholder': 'Mi van itt', + 'tours.walk.station.stay': 'Tartózkodás percben', + 'tours.walk.station.stayHint': 'Üres: még nem ismert; 0 egy pontnál, amelyen csak áthaladsz.', + 'tours.walk.station.stayInvalid': 'Egész perceket adj meg, legfeljebb egy napot.', + 'tours.walk.station.stayUnknown': 'Még nem ismert', + 'tours.walk.station.stayUnknownShort': 'Tartózkodás nyitott', + 'tours.walk.station.passThrough': 'Csak áthaladás', + 'tours.walk.station.importance': 'Fontosság', + 'tours.walk.station.protected': 'Védett', + 'tours.walk.station.protectedHint': + 'Egy javasolt változtatás a helyén hagyja ezt a megállót. Te továbbra is szerkesztheted.', + 'tours.walk.station.edit': 'Megálló szerkesztése', + 'tours.walk.importance.normal': 'Szokásos', + 'tours.walk.importance.optional': 'Elhagyható', + 'tours.walk.importance.must': 'Kihagyhatatlan', + 'tours.walk.importanceHint.normal': 'A séta során meglátogatjuk.', + 'tours.walk.importanceHint.optional': 'Kimaradhat, ha szűkül az idő.', + 'tours.walk.importanceHint.must': 'Egy későbbi javasolt változtatás bent hagyja a sétában.', + 'tours.walk.timeEstimated': 'Becsült gyaloglási idő', + 'tours.walk.timeEstimatedShort': 'Gyaloglás', + 'tours.walk.timeEstimatedInfoLabel': 'Hogyan számoljuk a gyaloglási időt', + 'tours.walk.timeEstimatedInfo': 'Az útvonaltervező gyalogos ideje a megállók között, a tartózkodások nélkül.', + 'tours.walk.stays': 'Tartózkodások', + 'tours.walk.totalAtLeast': 'legalább {time}', 'tours.planner.tourType': 'Túratípus', 'tours.paddle.boat': 'Hajó', 'tours.paddle.boat.kayak': 'Kajak', diff --git a/shared/src/i18n/id/tours.ts b/shared/src/i18n/id/tours.ts index e19a573fe..dc517ab51 100644 --- a/shared/src/i18n/id/tours.ts +++ b/shared/src/i18n/id/tours.ts @@ -460,6 +460,39 @@ const tours: TranslationStrings = { 'tours.preferences.avoid_stepsHint': 'Tangga hanya jika tidak ada jalan memutar.', 'tours.preferences.avoid_ferries': 'Hindari feri', 'tours.preferences.avoid_ferriesHint': 'Lewat darat jika ada jalan memutar, meski lebih panjang.', + 'tours.planner.tourTypeHint.walk': 'Berjalan dari titik ke titik, tanpa batas pendakian', + 'tours.poi.cat.museum': 'Museum dan galeri', + 'tours.poi.cat.park': 'Taman dan kebun', + 'tours.poi.cat.cafe': 'Kedai kopi', + 'tours.poi.cat.restaurant': 'Tempat makan', + 'tours.poi.cat.market': 'Pasar', + 'tours.walk.station.eyebrow': 'Titik singgah', + 'tours.walk.station.unnamed': 'Titik tanpa nama', + 'tours.walk.station.name': 'Nama', + 'tours.walk.station.namePlaceholder': 'Apa yang ada di sini', + 'tours.walk.station.stay': 'Lama singgah dalam menit', + 'tours.walk.station.stayHint': 'Kosong berarti belum diketahui; 0 untuk titik yang hanya kamu lewati.', + 'tours.walk.station.stayInvalid': 'Masukkan menit bulat, paling lama satu hari.', + 'tours.walk.station.stayUnknown': 'Belum diketahui', + 'tours.walk.station.stayUnknownShort': 'Lama singgah belum ada', + 'tours.walk.station.passThrough': 'Hanya lewat', + 'tours.walk.station.importance': 'Tingkat penting', + 'tours.walk.station.protected': 'Dilindungi', + 'tours.walk.station.protectedHint': + 'Perubahan yang diusulkan membiarkan titik ini di tempatnya. Kamu tetap bisa mengeditnya.', + 'tours.walk.station.edit': 'Edit titik singgah', + 'tours.walk.importance.normal': 'Biasa', + 'tours.walk.importance.optional': 'Opsional', + 'tours.walk.importance.must': 'Wajib dikunjungi', + 'tours.walk.importanceHint.normal': 'Dikunjungi sesuai jalannya rute.', + 'tours.walk.importanceHint.optional': 'Bisa dilewati jika waktu mepet.', + 'tours.walk.importanceHint.must': 'Perubahan yang diusulkan nanti tetap menyertakannya.', + 'tours.walk.timeEstimated': 'Perkiraan waktu jalan', + 'tours.walk.timeEstimatedShort': 'Berjalan', + 'tours.walk.timeEstimatedInfoLabel': 'Cara waktu jalan dihitung', + 'tours.walk.timeEstimatedInfo': 'Waktu jalan kaki dari perute di antara titik singgah, tanpa lama singgah.', + 'tours.walk.stays': 'Lama singgah', + 'tours.walk.totalAtLeast': 'setidaknya {time}', 'tours.planner.tourType': 'Jenis tur', 'tours.paddle.boat': 'Perahu', 'tours.paddle.boat.kayak': 'Kayak', // same-as-en diff --git a/shared/src/i18n/it/tours.ts b/shared/src/i18n/it/tours.ts index 2721b4753..5c6c37689 100644 --- a/shared/src/i18n/it/tours.ts +++ b/shared/src/i18n/it/tours.ts @@ -468,6 +468,38 @@ const tours: TranslationStrings = { 'tours.preferences.avoid_stepsHint': 'Scale solo dove non c’è modo di aggirarle.', 'tours.preferences.avoid_ferries': 'Evita i traghetti', 'tours.preferences.avoid_ferriesHint': 'Via terra dove c’è un modo di aggirare, anche se più lungo.', + 'tours.planner.tourTypeHint.walk': 'A piedi di tappa in tappa, senza limite escursionistico', + 'tours.poi.cat.museum': 'Musei e gallerie', + 'tours.poi.cat.park': 'Parchi e giardini', + 'tours.poi.cat.cafe': 'Caffetterie', + 'tours.poi.cat.restaurant': 'Dove mangiare', + 'tours.poi.cat.market': 'Mercati', + 'tours.walk.station.eyebrow': 'Tappa', + 'tours.walk.station.unnamed': 'Tappa senza nome', + 'tours.walk.station.name': 'Denominazione', + 'tours.walk.station.namePlaceholder': "Cosa c'è qui", + 'tours.walk.station.stay': 'Sosta in minuti', + 'tours.walk.station.stayHint': 'Vuoto significa ancora sconosciuto; 0 per un punto da cui passi soltanto.', + 'tours.walk.station.stayInvalid': 'Inserisci minuti interi, fino a un giorno.', + 'tours.walk.station.stayUnknown': 'Ancora sconosciuto', + 'tours.walk.station.stayUnknownShort': 'Sosta da definire', + 'tours.walk.station.passThrough': 'Solo di passaggio', + 'tours.walk.station.importance': 'Importanza', + 'tours.walk.station.protected': 'Protetta', + 'tours.walk.station.protectedHint': "Una modifica proposta lascia questa tappa dov'è. Tu puoi comunque modificarla.", + 'tours.walk.station.edit': 'Modifica tappa', + 'tours.walk.importance.normal': 'Normale', + 'tours.walk.importance.optional': 'Facoltativa', + 'tours.walk.importance.must': 'Da non perdere', + 'tours.walk.importanceHint.normal': 'Si visita lungo il percorso.', + 'tours.walk.importanceHint.optional': 'Si può saltare se il tempo stringe.', + 'tours.walk.importanceHint.must': 'Una modifica proposta più avanti la mantiene nel percorso.', + 'tours.walk.timeEstimated': 'Tempo a piedi stimato', + 'tours.walk.timeEstimatedShort': 'A piedi', + 'tours.walk.timeEstimatedInfoLabel': 'Come si calcola il tempo a piedi', + 'tours.walk.timeEstimatedInfo': 'Il tempo a piedi del calcolo percorso tra le tappe, senza le soste.', + 'tours.walk.stays': 'Soste', + 'tours.walk.totalAtLeast': 'almeno {time}', 'tours.planner.tourType': 'Tipo di tour', 'tours.paddle.boat': 'Imbarcazione', 'tours.paddle.boat.kayak': 'Kayak', // same-as-en diff --git a/shared/src/i18n/ja/tours.ts b/shared/src/i18n/ja/tours.ts index 8cd417447..4c3f03832 100644 --- a/shared/src/i18n/ja/tours.ts +++ b/shared/src/i18n/ja/tours.ts @@ -452,6 +452,39 @@ const tours: TranslationStrings = { 'tours.preferences.avoid_stepsHint': '回り道がない場所だけ階段を通ります。', 'tours.preferences.avoid_ferries': 'フェリーを避ける', 'tours.preferences.avoid_ferriesHint': '回り道があれば、長くても陸路を通ります。', + 'tours.planner.tourTypeHint.walk': '立ち寄り先から立ち寄り先へ徒歩で。ハイキングの難易度制限なし', + 'tours.poi.cat.museum': '美術館・博物館', + 'tours.poi.cat.park': '公園と庭園', + 'tours.poi.cat.cafe': 'カフェ', + 'tours.poi.cat.restaurant': '食事処', + 'tours.poi.cat.market': '市場', + 'tours.walk.station.eyebrow': '立ち寄り先', + 'tours.walk.station.unnamed': '名前のない立ち寄り先', + 'tours.walk.station.name': '名前', + 'tours.walk.station.namePlaceholder': 'ここにあるもの', + 'tours.walk.station.stay': '滞在時間(分)', + 'tours.walk.station.stayHint': '空欄はまだ不明、通過するだけの地点は 0。', + 'tours.walk.station.stayInvalid': '整数の分で入力してください(最大1日)。', + 'tours.walk.station.stayUnknown': 'まだ不明', + 'tours.walk.station.stayUnknownShort': '滞在時間未定', + 'tours.walk.station.passThrough': '通過のみ', + 'tours.walk.station.importance': '重要度', + 'tours.walk.station.protected': '保護', + 'tours.walk.station.protectedHint': + '提案された変更でもこの立ち寄り先はそのまま残ります。自分では引き続き編集できます。', + 'tours.walk.station.edit': '立ち寄り先を編集', + 'tours.walk.importance.normal': '通常', + 'tours.walk.importance.optional': '任意', + 'tours.walk.importance.must': '必見', + 'tours.walk.importanceHint.normal': '歩く流れで立ち寄ります。', + 'tours.walk.importanceHint.optional': '時間が足りなければ省けます。', + 'tours.walk.importanceHint.must': '後から提案される変更でも外されません。', + 'tours.walk.timeEstimated': '推定歩行時間', + 'tours.walk.timeEstimatedShort': '歩行', + 'tours.walk.timeEstimatedInfoLabel': '歩行時間の求め方', + 'tours.walk.timeEstimatedInfo': 'ルート計算による立ち寄り先間の徒歩時間。滞在時間は含みません。', + 'tours.walk.stays': '滞在', + 'tours.walk.totalAtLeast': '{time} 以上', 'tours.planner.tourType': 'ツアーの種類', 'tours.paddle.boat': 'ボート', 'tours.paddle.boat.kayak': 'カヤック', diff --git a/shared/src/i18n/ko/tours.ts b/shared/src/i18n/ko/tours.ts index 852e1dd88..add32e409 100644 --- a/shared/src/i18n/ko/tours.ts +++ b/shared/src/i18n/ko/tours.ts @@ -449,6 +449,38 @@ const tours: TranslationStrings = { 'tours.preferences.avoid_stepsHint': '돌아갈 길이 없을 때만 계단을 지납니다.', 'tours.preferences.avoid_ferries': '페리 피하기', 'tours.preferences.avoid_ferriesHint': '돌아가는 길이 있으면 더 길어도 육로로 갑니다.', + 'tours.planner.tourTypeHint.walk': '정류지에서 정류지로 도보 이동, 하이킹 제한 없음', + 'tours.poi.cat.museum': '박물관과 갤러리', + 'tours.poi.cat.park': '공원과 정원', + 'tours.poi.cat.cafe': '카페', + 'tours.poi.cat.restaurant': '식당', + 'tours.poi.cat.market': '시장', + 'tours.walk.station.eyebrow': '정류지', + 'tours.walk.station.unnamed': '이름 없는 정류지', + 'tours.walk.station.name': '이름', + 'tours.walk.station.namePlaceholder': '이곳에 있는 것', + 'tours.walk.station.stay': '머무는 시간(분)', + 'tours.walk.station.stayHint': '비워 두면 아직 모름, 지나가기만 하는 곳은 0.', + 'tours.walk.station.stayInvalid': '정수 분으로 입력하세요(최대 하루).', + 'tours.walk.station.stayUnknown': '아직 모름', + 'tours.walk.station.stayUnknownShort': '머무는 시간 미정', + 'tours.walk.station.passThrough': '지나가기만', + 'tours.walk.station.importance': '중요도', + 'tours.walk.station.protected': '보호됨', + 'tours.walk.station.protectedHint': '제안된 변경에서도 이 정류지는 그대로 유지됩니다. 직접 수정하는 것은 가능합니다.', + 'tours.walk.station.edit': '정류지 편집', + 'tours.walk.importance.normal': '보통', + 'tours.walk.importance.optional': '선택', + 'tours.walk.importance.must': '꼭 방문', + 'tours.walk.importanceHint.normal': '걷는 흐름에 따라 들릅니다.', + 'tours.walk.importanceHint.optional': '시간이 부족하면 건너뛸 수 있습니다.', + 'tours.walk.importanceHint.must': '나중에 제안되는 변경에서도 유지됩니다.', + 'tours.walk.timeEstimated': '예상 도보 시간', + 'tours.walk.timeEstimatedShort': '도보', + 'tours.walk.timeEstimatedInfoLabel': '도보 시간 계산 방법', + 'tours.walk.timeEstimatedInfo': '경로 계산기가 낸 정류지 사이 도보 시간이며, 머무는 시간은 빠져 있습니다.', + 'tours.walk.stays': '머무는 시간', + 'tours.walk.totalAtLeast': '최소 {time}', 'tours.planner.tourType': '투어 유형', 'tours.paddle.boat': '보트', 'tours.paddle.boat.kayak': '카약', diff --git a/shared/src/i18n/nl/tours.ts b/shared/src/i18n/nl/tours.ts index 0bf5e96df..71ba43948 100644 --- a/shared/src/i18n/nl/tours.ts +++ b/shared/src/i18n/nl/tours.ts @@ -470,6 +470,39 @@ const tours: TranslationStrings = { 'tours.preferences.avoid_stepsHint': 'Trappen alleen waar er geen weg omheen is.', 'tours.preferences.avoid_ferries': 'Veerponten vermijden', 'tours.preferences.avoid_ferriesHint': 'Over land waar er een weg omheen is, ook als die langer is.', + 'tours.planner.tourTypeHint.walk': 'Te voet van stop naar stop, zonder wandelgrens', + 'tours.poi.cat.museum': 'Musea en galerieën', + 'tours.poi.cat.park': 'Parken en tuinen', + 'tours.poi.cat.cafe': 'Koffiezaken', + 'tours.poi.cat.restaurant': 'Eetgelegenheden', + 'tours.poi.cat.market': 'Markten', + 'tours.walk.station.eyebrow': 'Halte', + 'tours.walk.station.unnamed': 'Halte zonder naam', + 'tours.walk.station.name': 'Benaming', + 'tours.walk.station.namePlaceholder': 'Wat hier is', + 'tours.walk.station.stay': 'Verblijf in minuten', + 'tours.walk.station.stayHint': 'Leeg betekent nog onbekend; 0 voor een punt waar je alleen langs loopt.', + 'tours.walk.station.stayInvalid': 'Vul hele minuten in, tot een dag.', + 'tours.walk.station.stayUnknown': 'Nog onbekend', + 'tours.walk.station.stayUnknownShort': 'Verblijf open', + 'tours.walk.station.passThrough': 'Alleen doorgaan', + 'tours.walk.station.importance': 'Belang', + 'tours.walk.station.protected': 'Beschermd', + 'tours.walk.station.protectedHint': + 'Een voorgestelde wijziging laat deze halte waar hij is. Zelf kun je hem nog bewerken.', + 'tours.walk.station.edit': 'Halte bewerken', + 'tours.walk.importance.normal': 'Gewoon', + 'tours.walk.importance.optional': 'Optioneel', + 'tours.walk.importance.must': 'Niet missen', + 'tours.walk.importanceHint.normal': 'Wordt bezocht zoals de wandeling loopt.', + 'tours.walk.importanceHint.optional': 'Kan wegvallen als de tijd krap wordt.', + 'tours.walk.importanceHint.must': 'Een later voorgestelde wijziging houdt hem in de wandeling.', + 'tours.walk.timeEstimated': 'Geschatte looptijd', + 'tours.walk.timeEstimatedShort': 'Lopen', + 'tours.walk.timeEstimatedInfoLabel': 'Hoe de looptijd wordt berekend', + 'tours.walk.timeEstimatedInfo': 'De looptijd van de routeplanner tussen de haltes, zonder het verblijf.', + 'tours.walk.stays': 'Verblijven', + 'tours.walk.totalAtLeast': 'minstens {time}', 'tours.planner.tourType': 'Soort tocht', 'tours.paddle.boat': 'Boot', 'tours.paddle.boat.kayak': 'Kajak', diff --git a/shared/src/i18n/pl/tours.ts b/shared/src/i18n/pl/tours.ts index 86372fe1e..cf25832f9 100644 --- a/shared/src/i18n/pl/tours.ts +++ b/shared/src/i18n/pl/tours.ts @@ -475,6 +475,39 @@ const tours: TranslationStrings = { 'tours.preferences.avoid_stepsHint': 'Schody tylko tam, gdzie nie da się ich obejść.', 'tours.preferences.avoid_ferries': 'Unikaj promów', 'tours.preferences.avoid_ferriesHint': 'Lądem, gdzie da się obejść, nawet jeśli dłużej.', + 'tours.planner.tourTypeHint.walk': 'Pieszo od przystanku do przystanku, bez limitu szlaku', + 'tours.poi.cat.museum': 'Muzea i galerie', + 'tours.poi.cat.park': 'Parki i ogrody', + 'tours.poi.cat.cafe': 'Kawiarnie', + 'tours.poi.cat.restaurant': 'Gdzie zjeść', + 'tours.poi.cat.market': 'Targi', + 'tours.walk.station.eyebrow': 'Przystanek', + 'tours.walk.station.unnamed': 'Przystanek bez nazwy', + 'tours.walk.station.name': 'Nazwa', + 'tours.walk.station.namePlaceholder': 'Co tu jest', + 'tours.walk.station.stay': 'Pobyt w minutach', + 'tours.walk.station.stayHint': 'Puste oznacza jeszcze nieznane; 0 dla punktu, przez który tylko przechodzisz.', + 'tours.walk.station.stayInvalid': 'Wpisz pełne minuty, maksymalnie jeden dzień.', + 'tours.walk.station.stayUnknown': 'Jeszcze nieznane', + 'tours.walk.station.stayUnknownShort': 'Pobyt nieznany', + 'tours.walk.station.passThrough': 'Tylko przejście', + 'tours.walk.station.importance': 'Ważność', + 'tours.walk.station.protected': 'Chroniony', + 'tours.walk.station.protectedHint': + 'Proponowana zmiana zostawia ten przystanek na miejscu. Ty nadal możesz go edytować.', + 'tours.walk.station.edit': 'Edytuj przystanek', + 'tours.walk.importance.normal': 'Zwykły', + 'tours.walk.importance.optional': 'Opcjonalny', + 'tours.walk.importance.must': 'Koniecznie', + 'tours.walk.importanceHint.normal': 'Odwiedzany po drodze.', + 'tours.walk.importanceHint.optional': 'Można pominąć, gdy brakuje czasu.', + 'tours.walk.importanceHint.must': 'Późniejsza proponowana zmiana zostawi go w trasie.', + 'tours.walk.timeEstimated': 'Szacowany czas marszu', + 'tours.walk.timeEstimatedShort': 'Marsz', + 'tours.walk.timeEstimatedInfoLabel': 'Jak liczony jest czas marszu', + 'tours.walk.timeEstimatedInfo': 'Czas marszu między przystankami według planera trasy, bez postojów.', + 'tours.walk.stays': 'Postoje', + 'tours.walk.totalAtLeast': 'co najmniej {time}', 'tours.planner.tourType': 'Rodzaj wycieczki', 'tours.paddle.boat': 'Łódź', 'tours.paddle.boat.kayak': 'Kajak', diff --git a/shared/src/i18n/ru/tours.ts b/shared/src/i18n/ru/tours.ts index 540099304..d5440e0aa 100644 --- a/shared/src/i18n/ru/tours.ts +++ b/shared/src/i18n/ru/tours.ts @@ -478,6 +478,39 @@ const tours: TranslationStrings = { 'tours.preferences.avoid_stepsHint': 'Лестницы только там, где их не обойти.', 'tours.preferences.avoid_ferries': 'Избегать паромов', 'tours.preferences.avoid_ferriesHint': 'По суше, где есть обход, даже если он длиннее.', + 'tours.planner.tourTypeHint.walk': 'Пешком от остановки к остановке, без ограничения по сложности', + 'tours.poi.cat.museum': 'Музеи и галереи', + 'tours.poi.cat.park': 'Парки и сады', + 'tours.poi.cat.cafe': 'Кафе', + 'tours.poi.cat.restaurant': 'Где поесть', + 'tours.poi.cat.market': 'Рынки', + 'tours.walk.station.eyebrow': 'Остановка', + 'tours.walk.station.unnamed': 'Остановка без названия', + 'tours.walk.station.name': 'Название', + 'tours.walk.station.namePlaceholder': 'Что здесь', + 'tours.walk.station.stay': 'Остановка в минутах', + 'tours.walk.station.stayHint': 'Пусто значит пока неизвестно; 0 для точки, которую ты только проходишь.', + 'tours.walk.station.stayInvalid': 'Введи целые минуты, не больше суток.', + 'tours.walk.station.stayUnknown': 'Пока неизвестно', + 'tours.walk.station.stayUnknownShort': 'Остановка неизвестна', + 'tours.walk.station.passThrough': 'Только мимо', + 'tours.walk.station.importance': 'Важность', + 'tours.walk.station.protected': 'Защищена', + 'tours.walk.station.protectedHint': + 'Предложенное изменение оставит эту остановку на месте. Сам ты по-прежнему можешь её править.', + 'tours.walk.station.edit': 'Изменить остановку', + 'tours.walk.importance.normal': 'Обычная', + 'tours.walk.importance.optional': 'Необязательная', + 'tours.walk.importance.must': 'Обязательно', + 'tours.walk.importanceHint.normal': 'Посещается по ходу прогулки.', + 'tours.walk.importanceHint.optional': 'Можно пропустить, если не хватает времени.', + 'tours.walk.importanceHint.must': 'Предложенное позже изменение оставит её в прогулке.', + 'tours.walk.timeEstimated': 'Расчётное время в пути', + 'tours.walk.timeEstimatedShort': 'Пешком', + 'tours.walk.timeEstimatedInfoLabel': 'Как считается время в пути', + 'tours.walk.timeEstimatedInfo': 'Время пешком между остановками по расчёту маршрута, без остановок на месте.', + 'tours.walk.stays': 'Остановки', + 'tours.walk.totalAtLeast': 'не меньше {time}', 'tours.planner.tourType': 'Тип тура', 'tours.paddle.boat': 'Лодка', 'tours.paddle.boat.kayak': 'Каяк', diff --git a/shared/src/i18n/sk/tours.ts b/shared/src/i18n/sk/tours.ts index b32efbc0a..5fee76cd4 100644 --- a/shared/src/i18n/sk/tours.ts +++ b/shared/src/i18n/sk/tours.ts @@ -461,6 +461,38 @@ const tours: TranslationStrings = { 'tours.preferences.avoid_stepsHint': 'Schody len tam, kde nie je cesta okolo.', 'tours.preferences.avoid_ferries': 'Vyhnúť sa prievozom', 'tours.preferences.avoid_ferriesHint': 'Po súši, kde vedie cesta okolo, aj keď je dlhšia.', + 'tours.planner.tourTypeHint.walk': 'Pešo od zastávky k zastávke, bez turistického limitu', + 'tours.poi.cat.museum': 'Múzeá a galérie', + 'tours.poi.cat.park': 'Parky a záhrady', + 'tours.poi.cat.cafe': 'Kaviarne', + 'tours.poi.cat.restaurant': 'Kde sa najesť', + 'tours.poi.cat.market': 'Trhy', + 'tours.walk.station.eyebrow': 'Zastávka', + 'tours.walk.station.unnamed': 'Zastávka bez názvu', + 'tours.walk.station.name': 'Názov', + 'tours.walk.station.namePlaceholder': 'Čo je tu', + 'tours.walk.station.stay': 'Pobyt v minútach', + 'tours.walk.station.stayHint': 'Prázdne znamená zatiaľ neznáme; 0 pre miesto, ktorým len prejdeš.', + 'tours.walk.station.stayInvalid': 'Zadaj celé minúty, najviac jeden deň.', + 'tours.walk.station.stayUnknown': 'Zatiaľ neznáme', + 'tours.walk.station.stayUnknownShort': 'Pobyt neznámy', + 'tours.walk.station.passThrough': 'Len prechod', + 'tours.walk.station.importance': 'Dôležitosť', + 'tours.walk.station.protected': 'Chránená', + 'tours.walk.station.protectedHint': 'Navrhovaná zmena nechá túto zastávku na mieste. Sám ju môžeš ďalej upravovať.', + 'tours.walk.station.edit': 'Upraviť zastávku', + 'tours.walk.importance.normal': 'Bežná', + 'tours.walk.importance.optional': 'Voliteľná', + 'tours.walk.importance.must': 'Musíš vidieť', + 'tours.walk.importanceHint.normal': 'Navštívi sa počas prechádzky.', + 'tours.walk.importanceHint.optional': 'Dá sa vynechať, keď chýba čas.', + 'tours.walk.importanceHint.must': 'Neskoršia navrhovaná zmena ju v prechádzke ponechá.', + 'tours.walk.timeEstimated': 'Odhadovaný čas chôdze', + 'tours.walk.timeEstimatedShort': 'Chôdza', + 'tours.walk.timeEstimatedInfoLabel': 'Ako sa počíta čas chôdze', + 'tours.walk.timeEstimatedInfo': 'Čas chôdze medzi zastávkami podľa plánovača trasy, bez pobytov.', + 'tours.walk.stays': 'Pobyty', + 'tours.walk.totalAtLeast': 'najmenej {time}', 'tours.planner.tourType': 'Typ túry', 'tours.paddle.boat': 'Loď', 'tours.paddle.boat.kayak': 'Kajak', diff --git a/shared/src/i18n/sv/tours.ts b/shared/src/i18n/sv/tours.ts index 5d1bd583a..7febfc75c 100644 --- a/shared/src/i18n/sv/tours.ts +++ b/shared/src/i18n/sv/tours.ts @@ -459,6 +459,39 @@ const tours: TranslationStrings = { 'tours.preferences.avoid_stepsHint': 'Trappor bara där det inte finns någon väg runt.', 'tours.preferences.avoid_ferries': 'Undvik färjor', 'tours.preferences.avoid_ferriesHint': 'Över land där det finns en väg runt, även om den är längre.', + 'tours.planner.tourTypeHint.walk': 'Till fots från stopp till stopp, utan vandringsgräns', + 'tours.poi.cat.museum': 'Museer och gallerier', + 'tours.poi.cat.park': 'Parker och trädgårdar', + 'tours.poi.cat.cafe': 'Kaféer', + 'tours.poi.cat.restaurant': 'Matställen', + 'tours.poi.cat.market': 'Torghandel', + 'tours.walk.station.eyebrow': 'Stopp', + 'tours.walk.station.unnamed': 'Namnlöst stopp', + 'tours.walk.station.name': 'Benämning', + 'tours.walk.station.namePlaceholder': 'Vad som finns här', + 'tours.walk.station.stay': 'Vistelse i minuter', + 'tours.walk.station.stayHint': 'Tomt betyder ännu okänt; 0 för en punkt du bara passerar.', + 'tours.walk.station.stayInvalid': 'Ange hela minuter, högst ett dygn.', + 'tours.walk.station.stayUnknown': 'Ännu okänt', + 'tours.walk.station.stayUnknownShort': 'Vistelse okänd', + 'tours.walk.station.passThrough': 'Bara förbi', + 'tours.walk.station.importance': 'Vikt', + 'tours.walk.station.protected': 'Skyddad', + 'tours.walk.station.protectedHint': + 'Ett föreslaget ändringsförslag låter stoppet vara kvar. Du kan fortfarande redigera det själv.', + 'tours.walk.station.edit': 'Redigera stopp', + 'tours.walk.importance.normal': 'Vanlig', + 'tours.walk.importance.optional': 'Valfri', + 'tours.walk.importance.must': 'Måste ses', + 'tours.walk.importanceHint.normal': 'Besöks under promenaden.', + 'tours.walk.importanceHint.optional': 'Kan strykas om tiden blir knapp.', + 'tours.walk.importanceHint.must': 'Ett senare förslag behåller den i promenaden.', + 'tours.walk.timeEstimated': 'Beräknad gångtid', + 'tours.walk.timeEstimatedShort': 'Gång', + 'tours.walk.timeEstimatedInfoLabel': 'Hur gångtiden räknas fram', + 'tours.walk.timeEstimatedInfo': 'Ruttplanerarens gångtid mellan stoppen, utan vistelserna.', + 'tours.walk.stays': 'Vistelser', + 'tours.walk.totalAtLeast': 'minst {time}', 'tours.planner.tourType': 'Turtyp', 'tours.paddle.boat': 'Båt', 'tours.paddle.boat.kayak': 'Kajak', diff --git a/shared/src/i18n/th/tours.ts b/shared/src/i18n/th/tours.ts index d82423f32..475b7c9b8 100644 --- a/shared/src/i18n/th/tours.ts +++ b/shared/src/i18n/th/tours.ts @@ -443,6 +443,38 @@ const tours: TranslationStrings = { 'tours.preferences.avoid_stepsHint': 'ผ่านบันไดเฉพาะเมื่อไม่มีทางอ้อม', 'tours.preferences.avoid_ferries': 'หลีกเลี่ยงเรือข้ามฟาก', 'tours.preferences.avoid_ferriesHint': 'ไปทางบกเมื่อมีทางอ้อม แม้จะไกลกว่า', + 'tours.planner.tourTypeHint.walk': 'เดินเท้าจากจุดแวะหนึ่งไปอีกจุด ไม่มีขีดจำกัดเส้นทางเดินป่า', + 'tours.poi.cat.museum': 'พิพิธภัณฑ์และแกลเลอรี', + 'tours.poi.cat.park': 'สวนสาธารณะและสวน', + 'tours.poi.cat.cafe': 'ร้านกาแฟ', + 'tours.poi.cat.restaurant': 'ร้านอาหาร', + 'tours.poi.cat.market': 'ตลาด', + 'tours.walk.station.eyebrow': 'จุดแวะ', + 'tours.walk.station.unnamed': 'จุดแวะที่ไม่มีชื่อ', + 'tours.walk.station.name': 'ชื่อ', + 'tours.walk.station.namePlaceholder': 'ที่นี่มีอะไร', + 'tours.walk.station.stay': 'เวลาแวะพัก (นาที)', + 'tours.walk.station.stayHint': 'เว้นว่างหมายถึงยังไม่ทราบ ใส่ 0 สำหรับจุดที่แค่เดินผ่าน', + 'tours.walk.station.stayInvalid': 'กรอกเป็นนาทีเต็ม ไม่เกินหนึ่งวัน', + 'tours.walk.station.stayUnknown': 'ยังไม่ทราบ', + 'tours.walk.station.stayUnknownShort': 'ยังไม่ระบุเวลาแวะ', + 'tours.walk.station.passThrough': 'แค่เดินผ่าน', + 'tours.walk.station.importance': 'ความสำคัญ', + 'tours.walk.station.protected': 'ป้องกันไว้', + 'tours.walk.station.protectedHint': 'การเปลี่ยนแปลงที่เสนอจะคงจุดแวะนี้ไว้ที่เดิม คุณยังแก้ไขเองได้', + 'tours.walk.station.edit': 'แก้ไขจุดแวะ', + 'tours.walk.importance.normal': 'ปกติ', + 'tours.walk.importance.optional': 'ไม่บังคับ', + 'tours.walk.importance.must': 'ต้องไป', + 'tours.walk.importanceHint.normal': 'แวะตามเส้นทางเดิน', + 'tours.walk.importanceHint.optional': 'ข้ามได้หากเวลาไม่พอ', + 'tours.walk.importanceHint.must': 'การเปลี่ยนแปลงที่เสนอภายหลังจะคงไว้ในทริป', + 'tours.walk.timeEstimated': 'เวลาเดินโดยประมาณ', + 'tours.walk.timeEstimatedShort': 'เดิน', + 'tours.walk.timeEstimatedInfoLabel': 'วิธีคำนวณเวลาเดิน', + 'tours.walk.timeEstimatedInfo': 'เวลาเดินระหว่างจุดแวะจากตัวคำนวณเส้นทาง ไม่รวมเวลาแวะพัก', + 'tours.walk.stays': 'เวลาแวะพัก', + 'tours.walk.totalAtLeast': 'อย่างน้อย {time}', 'tours.planner.tourType': 'ประเภททริป', 'tours.paddle.boat': 'เรือ', 'tours.paddle.boat.kayak': 'เรือคายัก', diff --git a/shared/src/i18n/tours-locales.spec.ts b/shared/src/i18n/tours-locales.spec.ts index fa529fff9..95c5fd8c5 100644 --- a/shared/src/i18n/tours-locales.spec.ts +++ b/shared/src/i18n/tours-locales.spec.ts @@ -20,7 +20,7 @@ const tokens = (value: string) => [...value.matchAll(/\{[^{}]+\}/g)].map((match) // placeholder spec; here only the general keys are. const groups = pluralGroups(Object.keys(english)); const generalKeys = (keys: string[]) => keys.filter((key) => !pluralFormOf(key, groups)); -const GENERAL_KEY_COUNT = 703; +const GENERAL_KEY_COUNT = 735; function literalKeys(source: string): string[] { const tree = ts.createSourceFile('locale.ts', source, ts.ScriptTarget.Latest, true); diff --git a/shared/src/i18n/tr/tours.ts b/shared/src/i18n/tr/tours.ts index 952869658..0cc37b1b8 100644 --- a/shared/src/i18n/tr/tours.ts +++ b/shared/src/i18n/tr/tours.ts @@ -462,6 +462,39 @@ const tours: TranslationStrings = { 'tours.preferences.avoid_stepsHint': 'Merdiven yalnızca dolaşacak yol yoksa.', 'tours.preferences.avoid_ferries': 'Feribotlardan kaçın', 'tours.preferences.avoid_ferriesHint': 'Dolaşacak yol varsa daha uzun olsa da karadan.', + 'tours.planner.tourTypeHint.walk': 'Duraktan durağa yürüyerek, yürüyüş sınırı olmadan', + 'tours.poi.cat.museum': 'Müzeler ve galeriler', + 'tours.poi.cat.park': 'Parklar ve bahçeler', + 'tours.poi.cat.cafe': 'Kafeler', + 'tours.poi.cat.restaurant': 'Yemek yerleri', + 'tours.poi.cat.market': 'Pazarlar', + 'tours.walk.station.eyebrow': 'Durak', + 'tours.walk.station.unnamed': 'Adsız durak', + 'tours.walk.station.name': 'Ad', + 'tours.walk.station.namePlaceholder': 'Burada ne var', + 'tours.walk.station.stay': 'Kalış süresi (dakika)', + 'tours.walk.station.stayHint': 'Boş, henüz bilinmiyor demektir; yalnızca geçtiğin bir nokta için 0.', + 'tours.walk.station.stayInvalid': 'Tam dakika gir, en fazla bir gün.', + 'tours.walk.station.stayUnknown': 'Henüz bilinmiyor', + 'tours.walk.station.stayUnknownShort': 'Kalış belirsiz', + 'tours.walk.station.passThrough': 'Sadece geçiş', + 'tours.walk.station.importance': 'Önem', + 'tours.walk.station.protected': 'Korunuyor', + 'tours.walk.station.protectedHint': + 'Önerilen bir değişiklik bu durağı yerinde bırakır. Sen yine de düzenleyebilirsin.', + 'tours.walk.station.edit': 'Durağı düzenle', + 'tours.walk.importance.normal': 'Olağan', + 'tours.walk.importance.optional': 'İsteğe bağlı', + 'tours.walk.importance.must': 'Mutlaka görülmeli', + 'tours.walk.importanceHint.normal': 'Yürüyüş akışında uğranır.', + 'tours.walk.importanceHint.optional': 'Zaman daralırsa atlanabilir.', + 'tours.walk.importanceHint.must': 'Sonradan önerilen bir değişiklik onu yürüyüşte tutar.', + 'tours.walk.timeEstimated': 'Tahmini yürüme süresi', + 'tours.walk.timeEstimatedShort': 'Yürüme', + 'tours.walk.timeEstimatedInfoLabel': 'Yürüme süresi nasıl hesaplanır', + 'tours.walk.timeEstimatedInfo': 'Rota hesaplayıcının duraklar arasındaki yürüme süresi, kalışlar hariç.', + 'tours.walk.stays': 'Kalışlar', + 'tours.walk.totalAtLeast': 'en az {time}', 'tours.planner.tourType': 'Tur türü', 'tours.paddle.boat': 'Tekne', 'tours.paddle.boat.kayak': 'Kano (kayak)', diff --git a/shared/src/i18n/uk/tours.ts b/shared/src/i18n/uk/tours.ts index 9ea63da45..7f1e1b12f 100644 --- a/shared/src/i18n/uk/tours.ts +++ b/shared/src/i18n/uk/tours.ts @@ -474,6 +474,39 @@ const tours: TranslationStrings = { 'tours.preferences.avoid_stepsHint': 'Сходи лише там, де їх не обійти.', 'tours.preferences.avoid_ferries': 'Уникати поромів', 'tours.preferences.avoid_ferriesHint': 'Суходолом, де є обхід, навіть якщо він довший.', + 'tours.planner.tourTypeHint.walk': 'Пішки від зупинки до зупинки, без обмеження за складністю', + 'tours.poi.cat.museum': 'Музеї та галереї', + 'tours.poi.cat.park': 'Парки та сади', + 'tours.poi.cat.cafe': "Кав'ярні", + 'tours.poi.cat.restaurant': 'Де поїсти', + 'tours.poi.cat.market': 'Ринки', + 'tours.walk.station.eyebrow': 'Зупинка', + 'tours.walk.station.unnamed': 'Зупинка без назви', + 'tours.walk.station.name': 'Назва', + 'tours.walk.station.namePlaceholder': 'Що тут', + 'tours.walk.station.stay': 'Перебування у хвилинах', + 'tours.walk.station.stayHint': 'Порожньо означає поки невідомо; 0 для точки, яку ти лише проходиш.', + 'tours.walk.station.stayInvalid': 'Введи цілі хвилини, не більше доби.', + 'tours.walk.station.stayUnknown': 'Поки невідомо', + 'tours.walk.station.stayUnknownShort': 'Перебування невідоме', + 'tours.walk.station.passThrough': 'Лише мимо', + 'tours.walk.station.importance': 'Важливість', + 'tours.walk.station.protected': 'Захищена', + 'tours.walk.station.protectedHint': + 'Запропонована зміна залишить цю зупинку на місці. Сам ти все ще можеш її редагувати.', + 'tours.walk.station.edit': 'Редагувати зупинку', + 'tours.walk.importance.normal': 'Звичайна', + 'tours.walk.importance.optional': "Необов'язкова", + 'tours.walk.importance.must': "Обов'язково", + 'tours.walk.importanceHint.normal': 'Відвідується по ходу прогулянки.', + 'tours.walk.importanceHint.optional': 'Можна пропустити, якщо бракує часу.', + 'tours.walk.importanceHint.must': 'Запропонована пізніше зміна залишить її у прогулянці.', + 'tours.walk.timeEstimated': 'Орієнтовний час ходьби', + 'tours.walk.timeEstimatedShort': 'Ходьба', + 'tours.walk.timeEstimatedInfoLabel': 'Як рахується час ходьби', + 'tours.walk.timeEstimatedInfo': 'Час пішки між зупинками за розрахунком маршруту, без перебування.', + 'tours.walk.stays': 'Перебування', + 'tours.walk.totalAtLeast': 'щонайменше {time}', 'tours.planner.tourType': 'Тип туру', 'tours.paddle.boat': 'Човен', 'tours.paddle.boat.kayak': 'Каяк', diff --git a/shared/src/i18n/vi/tours.ts b/shared/src/i18n/vi/tours.ts index e47b74d95..87aa5533a 100644 --- a/shared/src/i18n/vi/tours.ts +++ b/shared/src/i18n/vi/tours.ts @@ -460,6 +460,38 @@ const tours: TranslationStrings = { 'tours.preferences.avoid_stepsHint': 'Chỉ đi bậc thang khi không có đường vòng.', 'tours.preferences.avoid_ferries': 'Tránh phà', 'tours.preferences.avoid_ferriesHint': 'Đi đường bộ nơi có đường vòng, kể cả khi dài hơn.', + 'tours.planner.tourTypeHint.walk': 'Đi bộ từ điểm dừng này sang điểm dừng khác, không giới hạn leo núi', + 'tours.poi.cat.museum': 'Bảo tàng và phòng tranh', + 'tours.poi.cat.park': 'Công viên và vườn', + 'tours.poi.cat.cafe': 'Quán cà phê', + 'tours.poi.cat.restaurant': 'Chỗ ăn uống', + 'tours.poi.cat.market': 'Chợ', + 'tours.walk.station.eyebrow': 'Điểm dừng', + 'tours.walk.station.unnamed': 'Điểm dừng chưa đặt tên', + 'tours.walk.station.name': 'Tên', + 'tours.walk.station.namePlaceholder': 'Ở đây có gì', + 'tours.walk.station.stay': 'Thời gian dừng (phút)', + 'tours.walk.station.stayHint': 'Để trống nghĩa là chưa biết; nhập 0 cho điểm chỉ đi ngang qua.', + 'tours.walk.station.stayInvalid': 'Nhập số phút nguyên, tối đa một ngày.', + 'tours.walk.station.stayUnknown': 'Chưa biết', + 'tours.walk.station.stayUnknownShort': 'Chưa rõ thời gian dừng', + 'tours.walk.station.passThrough': 'Chỉ đi qua', + 'tours.walk.station.importance': 'Mức độ quan trọng', + 'tours.walk.station.protected': 'Được bảo vệ', + 'tours.walk.station.protectedHint': 'Thay đổi được đề xuất sẽ giữ nguyên điểm dừng này. Bạn vẫn có thể tự chỉnh sửa.', + 'tours.walk.station.edit': 'Sửa điểm dừng', + 'tours.walk.importance.normal': 'Bình thường', + 'tours.walk.importance.optional': 'Tùy chọn', + 'tours.walk.importance.must': 'Nhất định phải đến', + 'tours.walk.importanceHint.normal': 'Ghé thăm theo lộ trình đi bộ.', + 'tours.walk.importanceHint.optional': 'Có thể bỏ qua khi thiếu thời gian.', + 'tours.walk.importanceHint.must': 'Thay đổi đề xuất sau này vẫn giữ điểm này.', + 'tours.walk.timeEstimated': 'Thời gian đi bộ ước tính', + 'tours.walk.timeEstimatedShort': 'Đi bộ', + 'tours.walk.timeEstimatedInfoLabel': 'Cách tính thời gian đi bộ', + 'tours.walk.timeEstimatedInfo': 'Thời gian đi bộ giữa các điểm dừng do bộ định tuyến tính, không gồm thời gian dừng.', + 'tours.walk.stays': 'Thời gian dừng', + 'tours.walk.totalAtLeast': 'ít nhất {time}', 'tours.planner.tourType': 'Loại chuyến', 'tours.paddle.boat': 'Thuyền', 'tours.paddle.boat.kayak': 'Thuyền kayak', diff --git a/shared/src/i18n/zh-TW/tours.ts b/shared/src/i18n/zh-TW/tours.ts index ec3503fcc..30460a14b 100644 --- a/shared/src/i18n/zh-TW/tours.ts +++ b/shared/src/i18n/zh-TW/tours.ts @@ -428,6 +428,38 @@ const tours: TranslationStrings = { 'tours.preferences.avoid_stepsHint': '只在無法繞行時經過階梯。', 'tours.preferences.avoid_ferries': '避開渡輪', 'tours.preferences.avoid_ferriesHint': '有繞行陸路時走陸路,即使更遠。', + 'tours.planner.tourTypeHint.walk': '從一站步行到下一站,不設健行難度限制', + 'tours.poi.cat.museum': '博物館和畫廊', + 'tours.poi.cat.park': '公園和花園', + 'tours.poi.cat.cafe': '咖啡館', + 'tours.poi.cat.restaurant': '餐館', + 'tours.poi.cat.market': '市集', + 'tours.walk.station.eyebrow': '站點', + 'tours.walk.station.unnamed': '未命名站點', + 'tours.walk.station.name': '名稱', + 'tours.walk.station.namePlaceholder': '這裡有什麼', + 'tours.walk.station.stay': '停留時間(分鐘)', + 'tours.walk.station.stayHint': '留空表示尚不清楚;只經過的地點填 0。', + 'tours.walk.station.stayInvalid': '請輸入整數分鐘,最多一天。', + 'tours.walk.station.stayUnknown': '尚不清楚', + 'tours.walk.station.stayUnknownShort': '停留時間未定', + 'tours.walk.station.passThrough': '僅經過', + 'tours.walk.station.importance': '重要程度', + 'tours.walk.station.protected': '受保護', + 'tours.walk.station.protectedHint': '提議的變更會保留此站點原位。你自己仍可編輯。', + 'tours.walk.station.edit': '編輯站點', + 'tours.walk.importance.normal': '普通', + 'tours.walk.importance.optional': '可選', + 'tours.walk.importance.must': '必去', + 'tours.walk.importanceHint.normal': '按路線順路遊覽。', + 'tours.walk.importanceHint.optional': '時間緊時可以跳過。', + 'tours.walk.importanceHint.must': '之後提議的變更會保留它。', + 'tours.walk.timeEstimated': '預估步行時間', + 'tours.walk.timeEstimatedShort': '步行', + 'tours.walk.timeEstimatedInfoLabel': '步行時間如何計算', + 'tours.walk.timeEstimatedInfo': '路線規劃給出的站點間步行時間,不含停留時間。', + 'tours.walk.stays': '停留', + 'tours.walk.totalAtLeast': '至少 {time}', 'tours.planner.tourType': '路線類型', 'tours.paddle.boat': '船隻', 'tours.paddle.boat.kayak': '獨木舟(Kayak)', diff --git a/shared/src/i18n/zh/tours.ts b/shared/src/i18n/zh/tours.ts index 192a0f8ae..6f0939643 100644 --- a/shared/src/i18n/zh/tours.ts +++ b/shared/src/i18n/zh/tours.ts @@ -429,6 +429,38 @@ const tours: TranslationStrings = { 'tours.preferences.avoid_stepsHint': '只在无法绕行时经过台阶。', 'tours.preferences.avoid_ferries': '避开渡轮', 'tours.preferences.avoid_ferriesHint': '有绕行陆路时走陆路,即使更远。', + 'tours.planner.tourTypeHint.walk': '从一站步行到下一站,不设徒步难度限制', + 'tours.poi.cat.museum': '博物馆和画廊', + 'tours.poi.cat.park': '公园和花园', + 'tours.poi.cat.cafe': '咖啡馆', + 'tours.poi.cat.restaurant': '餐馆', + 'tours.poi.cat.market': '集市', + 'tours.walk.station.eyebrow': '站点', + 'tours.walk.station.unnamed': '未命名站点', + 'tours.walk.station.name': '名称', + 'tours.walk.station.namePlaceholder': '这里有什么', + 'tours.walk.station.stay': '停留时间(分钟)', + 'tours.walk.station.stayHint': '留空表示尚不清楚;只经过的地点填 0。', + 'tours.walk.station.stayInvalid': '请输入整数分钟,最多一天。', + 'tours.walk.station.stayUnknown': '尚不清楚', + 'tours.walk.station.stayUnknownShort': '停留时间未定', + 'tours.walk.station.passThrough': '仅经过', + 'tours.walk.station.importance': '重要程度', + 'tours.walk.station.protected': '受保护', + 'tours.walk.station.protectedHint': '提议的更改会保留此站点原位。你自己仍可编辑。', + 'tours.walk.station.edit': '编辑站点', + 'tours.walk.importance.normal': '普通', + 'tours.walk.importance.optional': '可选', + 'tours.walk.importance.must': '必去', + 'tours.walk.importanceHint.normal': '按路线顺路游览。', + 'tours.walk.importanceHint.optional': '时间紧时可以跳过。', + 'tours.walk.importanceHint.must': '之后提议的更改会保留它。', + 'tours.walk.timeEstimated': '预计步行时间', + 'tours.walk.timeEstimatedShort': '步行', + 'tours.walk.timeEstimatedInfoLabel': '步行时间如何计算', + 'tours.walk.timeEstimatedInfo': '路线规划给出的站点间步行时间,不含停留时间。', + 'tours.walk.stays': '停留', + 'tours.walk.totalAtLeast': '至少 {time}', 'tours.planner.tourType': '路线类型', 'tours.paddle.boat': '船只', 'tours.paddle.boat.kayak': '皮划艇',