diff --git a/client/src/api/tourVisits.ts b/client/src/api/tourVisits.ts new file mode 100644 index 000000000..550204873 --- /dev/null +++ b/client/src/api/tourVisits.ts @@ -0,0 +1,17 @@ +import type { TourVisitListResponse, TourVisitUpdateRequest } from '@trek/shared'; +import apiClient from './client'; + +/** CW-03: the stations of a trip's city walks visited or skipped on their days. */ +export const tourVisitsApi = { + list: (tripId: number | string): Promise => + apiClient.get(`/trips/${tripId}/tour-visits`).then((r) => r.data), + update: ( + tripId: number | string, + assignmentId: number, + stationKey: string, + body: TourVisitUpdateRequest + ): Promise => + apiClient + .put(`/trips/${tripId}/tour-visits/${assignmentId}/${encodeURIComponent(stationKey)}`, body) + .then((r) => r.data), +}; diff --git a/client/src/api/wsEventPolicy.ts b/client/src/api/wsEventPolicy.ts index b7c510062..0d016abac 100644 --- a/client/src/api/wsEventPolicy.ts +++ b/client/src/api/wsEventPolicy.ts @@ -19,6 +19,8 @@ export const HANDLED_OUTSIDE_TRIP_STORE = [ // Tours facet invalidation — hooks/useTripWebSocket forwards tours:changed to // useTourPlaceIds so the active trip refreshes its Tours/Places read model. 'tours:changed', + // CW-03 — components/Tours/walk/useTourVisitsSync keeps the visits store current. + 'tours:visits', // Collab — Collab/MCollab components + useTripWebSocket's collabFileSync 'collab:note:created', 'collab:note:updated', diff --git a/client/src/components/Planner/DayPlanPlaceSubline.tsx b/client/src/components/Planner/DayPlanPlaceSubline.tsx index a7cb79505..8d40d4cb1 100644 --- a/client/src/components/Planner/DayPlanPlaceSubline.tsx +++ b/client/src/components/Planner/DayPlanPlaceSubline.tsx @@ -7,6 +7,7 @@ import type { Category, Place } from '../../types'; import { formatTime } from '../../utils/formatters'; import { fs } from '../shared/DialogShell'; import { TourDayRowPills } from '../Tours/TourDayRowFacts'; +import { WalkProgressPill } from '../Tours/walk/WalkStations'; import { movingTimeLabel } from '../Tours/tourFacts'; import { formatPlannedTourDuration, type TourPlannedTimes } from '../Tours/tourPresentation'; import { TimePill } from './planParts'; @@ -19,6 +20,8 @@ interface DayPlanPlaceSublineProps { tourTimes: DayRowTourTimes | undefined; isTourPlace: boolean; timeFormat: string; + /** CW-03: the row's day assignment, which a city walk's progress belongs to. */ + assignmentId?: number; } function hasPlannedTimes(tourTimes: DayRowTourTimes | undefined): boolean { @@ -90,7 +93,7 @@ function SublineDescription({ text, isTourPlace }: { text: string; isTourPlace: // Second line of a place row in the day plan: time, tour facts or planned // tour durations, then the clamped description. -export function DayPlanPlaceSubline({ place, cat, tourTimes, isTourPlace, timeFormat }: DayPlanPlaceSublineProps) { +export function DayPlanPlaceSubline({ place, cat, tourTimes, isTourPlace, timeFormat, assignmentId }: DayPlanPlaceSublineProps) { const { locale } = useTranslation(); const dayRowTour = tourTimes?.tour; const description = place.description || place.address || cat?.name; @@ -106,6 +109,7 @@ export function DayPlanPlaceSubline({ place, cat, tourTimes, isTourPlace, timeFo )} {dayRowTour ? : tourTimes && } + {dayRowTour && } {description && } ); diff --git a/client/src/components/Planner/DayPlanSidebar.tsx b/client/src/components/Planner/DayPlanSidebar.tsx index 8d8ecf710..5f817d295 100644 --- a/client/src/components/Planner/DayPlanSidebar.tsx +++ b/client/src/components/Planner/DayPlanSidebar.tsx @@ -2033,7 +2033,7 @@ const DayPlanSidebar = React.memo(function DayPlanSidebar(props: DayPlanSidebarP )} - + {assignment.notes && ( // Day-specific note on this stop (#2163) — one muted // caption line so the timeline shows the note exists diff --git a/client/src/components/Tours/TourDetailDialog.tsx b/client/src/components/Tours/TourDetailDialog.tsx index 6f578a81d..a4f8261e5 100644 --- a/client/src/components/Tours/TourDetailDialog.tsx +++ b/client/src/components/Tours/TourDetailDialog.tsx @@ -64,6 +64,7 @@ import { } from './tourPresentation'; import { useTourDayAssignment } from './useTourDayAssignment'; import { useTourPermissions, type TourPermissionProps } from './useTourPermissions'; +import { WalkStationsCard } from './walk/WalkStations'; interface TourDetailDialogProps extends TourPermissionProps { tour: TourListItem; @@ -652,6 +653,9 @@ export default function TourDetailDialog({ placeId={place.id} /> )} + {tour.tour_type === 'walk' && ( + + )} {tour.tour_type === 'kayak' && } {tour.tour_type === 'kayak' && } diff --git a/client/src/components/Tours/walk/WalkStations.tsx b/client/src/components/Tours/walk/WalkStations.tsx new file mode 100644 index 000000000..e2233fc66 --- /dev/null +++ b/client/src/components/Tours/walk/WalkStations.tsx @@ -0,0 +1,180 @@ +import type { TourListItem, TourVisitStatus } from '@trek/shared'; +import { Check, CheckCheck, Redo2, SkipForward, Undo2 } from 'lucide-react'; +import { useEffect, useState } from 'react'; +import { useTranslation } from '../../../i18n'; +import { tourRepo } from '../../../repo/tourRepo'; +import { useTourVisitStore } from '../../../store/tourVisitStore'; +import { SoftPill } from '../../Planner/planParts'; +import { DialogSection, fs } from '../../shared/DialogShell'; +import { useToast } from '../../shared/Toast'; +import { StationPills } from '../planner/stationParts'; +import { plannerWaypointsOf, type TourPlannerWaypoint } from '../planner/tourPlannerWaypoints'; +import { TourDayRowPill } from '../TourDayRowFacts'; +import { CardAction } from '../tourParts'; + +let nextId = 0; +const freshId = () => `station-${++nextId}`; + +/** CW-03: a saved walk's stations in order, read once for the dialog; null while they load or could not be read. */ +function useWalkStations(tripId: number | string, placeId: number): TourPlannerWaypoint[] | null { + const [stations, setStations] = useState<{ placeId: number; points: TourPlannerWaypoint[] } | null>(null); + useEffect(() => { + const controller = new AbortController(); + tourRepo + .detail(tripId, placeId, controller.signal) + .then((detail) => { + if (controller.signal.aborted) return; + const points = plannerWaypointsOf(detail.waypoints, freshId).filter((point) => !point.shaping); + setStations({ placeId, points }); + }) + .catch(() => { + if (!controller.signal.aborted) setStations({ placeId, points: [] }); + }); + return () => controller.abort(); + }, [placeId, tripId]); + return stations?.placeId === placeId ? stations.points : null; +} + +/** The day's status of each station, by its key. */ +function useDayVisits(assignmentId: number | null) { + return useTourVisitStore((state) => (assignmentId === null ? undefined : state.byAssignment[assignmentId])); +} + +/** + * CW-03: a walk's progress on one of its days, as a pill of the day row: + * how many of its stations were visited. Nothing is shown before any is. + */ +export function WalkProgressPill({ tour, assignmentId }: { tour: TourListItem; assignmentId: number | undefined }) { + const { t } = useTranslation(); + const visits = useDayVisits(assignmentId ?? null); + const total = tour.station_count ?? 0; + if (tour.tour_type !== 'walk' || total === 0 || !visits) return null; + const visited = Object.values(visits).filter((status) => status === 'visited').length; + if (visited === 0 && Object.keys(visits).length === 0) return null; + const label = t('tours.walk.progress', { visited, total }); + return ; +} + +function StatusActions({ + status, + onMark, +}: { + status: TourVisitStatus | undefined; + onMark: (status: TourVisitStatus | 'planned') => void; +}) { + const { t } = useTranslation(); + if (status) + return ( + } onClick={() => onMark('planned')}> + {t('tours.walk.visit.undo')} + + ); + return ( + + } onClick={() => onMark('visited')}> + {t('tours.walk.visit.visited')} + + } onClick={() => onMark('skipped')}> + {t('tours.walk.visit.skip')} + + + ); +} + +/** + * CW-03: a city walk's stations in its detail, in order, each with its stay, + * importance and protection; opened from a day, each can be marked visited or + * skipped for that day and taken back. Marking changes no route, place or order. + */ +export function WalkStationsCard({ + tour, + tripId, + assignmentId, + canMark, +}: { + tour: TourListItem; + tripId: number | string; + /** The day the dialog was opened from; without one there is nothing to check off. */ + assignmentId: number | null; + canMark: boolean; +}) { + const { t } = useTranslation(); + const toast = useToast(); + const stations = useWalkStations(tripId, tour.place_id); + const visits = useDayVisits(assignmentId); + const mark = useTourVisitStore((state) => state.mark); + if (tour.tour_type !== 'walk') return null; + const onMark = (key: string, status: TourVisitStatus | 'planned') => { + if (assignmentId === null) return; + mark(tripId, assignmentId, key, status).catch(() => toast.error(t('tours.walk.visit.failed'))); + }; + const visited = stations + ? stations.filter((station) => station.key && visits?.[station.key] === 'visited').length + : 0; + return ( + {t('tours.walk.progress', { visited, total: stations.length })} + ) : undefined + } + > +
+ {stations === null && ( + + {t('common.loading')} + + )} + {assignmentId === null && stations && stations.length > 0 && ( + + {t('tours.walk.visit.fromDay')} + + )} +
    + {(stations ?? []).map((station, index) => { + const status = station.key ? visits?.[station.key] : undefined; + return ( +
  1. + + {status === 'visited' ? ( + + ) : status === 'skipped' ? ( + + ) : ( + index + 1 + )} + + + + {station.name ?? t('tours.walk.stationNumbered', { number: index + 1 })} + + + {status && ( + + {t(status === 'visited' ? 'tours.walk.visit.isVisited' : 'tours.walk.visit.isSkipped')} + + )} + + {canMark && assignmentId !== null && station.key && ( + onMark(station.key ?? '', next)} /> + )} +
  2. + ); + })} +
+
+
+ ); +} diff --git a/client/src/components/Tours/walk/useTourVisitsSync.ts b/client/src/components/Tours/walk/useTourVisitsSync.ts new file mode 100644 index 000000000..ec9185466 --- /dev/null +++ b/client/src/components/Tours/walk/useTourVisitsSync.ts @@ -0,0 +1,39 @@ +import { tourVisitSchema } from '@trek/shared'; +import { useEffect } from 'react'; +import { addListener, removeListener } from '../../../api/websocket'; +import { useTourVisitStore } from '../../../store/tourVisitStore'; +import type { WebSocketEvent } from '../../../types'; + +/** + * CW-03: the visits of a trip's city walks, read when the trip opens and kept + * current by its socket, so a station another traveller checks off shows on + * every day plan at once. Offline the day plan shows none until it is back. + */ +export function useTourVisitsSync(tripId: number | string | undefined, enabled: boolean) { + useEffect(() => { + if (!enabled || tripId === undefined) return; + let current = true; + useTourVisitStore + .getState() + .load(tripId) + .catch(() => { + // Offline or refused, the day plan shows no progress rather than another trip's. + if (current) useTourVisitStore.getState().reset(tripId); + }); + const onEvent = (event: WebSocketEvent) => { + if (event?.type !== 'tours:visits' || String(event.tripId ?? '') !== String(tripId)) return; + const visits = Array.isArray(event.visits) + ? event.visits.flatMap((raw) => { + const parsed = tourVisitSchema.safeParse(raw); + return parsed.success ? [parsed.data] : []; + }) + : []; + useTourVisitStore.getState().applyRemote(Number(event.assignmentId), visits); + }; + addListener(onEvent); + return () => { + current = false; + removeListener(onEvent); + }; + }, [enabled, tripId]); +} diff --git a/client/src/pages/tripPlanner/useTripPlanner.ts b/client/src/pages/tripPlanner/useTripPlanner.ts index 3a0803a7b..030155549 100644 --- a/client/src/pages/tripPlanner/useTripPlanner.ts +++ b/client/src/pages/tripPlanner/useTripPlanner.ts @@ -1,3 +1,4 @@ +import { useTourVisitsSync } from '../../components/Tours/walk/useTourVisitsSync' import { useRoadtripSettings } from '../../hooks/useRoadtripSettings' import { useState, useEffect, useCallback, useMemo, useRef } from 'react' import { useParams, useNavigate, useSearchParams } from 'react-router' @@ -284,6 +285,8 @@ export function useTripPlanner() { const [toursMode, setToursMode] = useState(false) const previousToursModeRef = useRef(false) const { tours, toursLoading, tourDataReady, tourPlaceIds, reloadTourPlaceIds, invalidateTourPlaceIds, upsertTour } = useTourPlaceIds(tripId, toursEnabled) + // CW-03: how far the trip's city walks got on their days, for the day plan and the walks' stations. + useTourVisitsSync(tripId, toursEnabled) useEffect(() => { if (!toursEnabled) { diff --git a/client/src/repo/tourVisitRepo.ts b/client/src/repo/tourVisitRepo.ts new file mode 100644 index 000000000..368b316c7 --- /dev/null +++ b/client/src/repo/tourVisitRepo.ts @@ -0,0 +1,24 @@ +import type { TourVisitListResponse, TourVisitUpdateRequest } from '@trek/shared'; +import { tourVisitsApi } from '../api/tourVisits'; +import { requireOnline } from './onlineOnly'; + +/** + * CW-03, online only for now: a station checked off is the server's at once, + * so everyone on the trip sees the same day. Offline the station says so and + * waits; the client keeps no copy of its own. + */ +export const tourVisitRepo = { + async list(tripId: number | string): Promise { + requireOnline(); + return await tourVisitsApi.list(tripId); + }, + async update( + tripId: number | string, + assignmentId: number, + stationKey: string, + body: TourVisitUpdateRequest + ): Promise { + requireOnline(); + return await tourVisitsApi.update(tripId, assignmentId, stationKey, body); + }, +}; diff --git a/client/src/store/tourVisitStore.ts b/client/src/store/tourVisitStore.ts new file mode 100644 index 000000000..12d069ba7 --- /dev/null +++ b/client/src/store/tourVisitStore.ts @@ -0,0 +1,68 @@ +import type { TourVisit, TourVisitStatus } from '@trek/shared'; +import { create } from 'zustand'; +import { tourVisitRepo } from '../repo/tourVisitRepo'; + +/** A station's status on one day of its walk; a station not listed is planned. */ +type VisitsByStation = Readonly>; + +interface TourVisitState { + /** The trip the visits were read for. */ + tripId: string | null; + /** Per day assignment, the stations visited or skipped. */ + byAssignment: Readonly>; + load: (tripId: number | string) => Promise; + /** Nothing known for this trip: its visits could not be read. */ + reset: (tripId: number | string) => void; + /** Marks a station, at once on screen; a write the server refuses puts it back and throws. */ + mark: ( + tripId: number | string, + assignmentId: number, + stationKey: string, + status: TourVisitStatus | 'planned' + ) => Promise; + /** Another traveller marked a station: the day's visits as the server has them now. */ + applyRemote: (assignmentId: number, visits: readonly TourVisit[]) => void; +} + +function byStation(visits: readonly TourVisit[]): VisitsByStation { + return Object.fromEntries(visits.map((visit) => [visit.station_key, visit.status])); +} + +function withStatus(day: VisitsByStation | undefined, key: string, status: TourVisitStatus | 'planned') { + const next = { ...day }; + if (status === 'planned') delete next[key]; + else next[key] = status; + return next; +} + +/** + * CW-03: how far each city walk got on its days, for the day plan's rows and + * the walk's stations. Read once a trip opens, kept current by the trip's + * socket, and written one station at a time. + */ +export const useTourVisitStore = create((set, get) => ({ + tripId: null, + byAssignment: {}, + load: async (tripId) => { + const { visits } = await tourVisitRepo.list(tripId); + const grouped: Record> = {}; + for (const visit of visits) (grouped[visit.assignment_id] ??= {})[visit.station_key] = visit.status; + set({ tripId: String(tripId), byAssignment: grouped }); + }, + reset: (tripId) => set({ tripId: String(tripId), byAssignment: {} }), + mark: async (tripId, assignmentId, stationKey, status) => { + const before = get().byAssignment[assignmentId]; + set((state) => ({ + byAssignment: { ...state.byAssignment, [assignmentId]: withStatus(before, stationKey, status) }, + })); + try { + const { visits } = await tourVisitRepo.update(tripId, assignmentId, stationKey, { status }); + get().applyRemote(assignmentId, visits); + } catch (failure) { + set((state) => ({ byAssignment: { ...state.byAssignment, [assignmentId]: before ?? {} } })); + throw failure; + } + }, + applyRemote: (assignmentId, visits) => + set((state) => ({ byAssignment: { ...state.byAssignment, [assignmentId]: byStation(visits) } })), +})); diff --git a/client/tests/helpers/storeRegistry.ts b/client/tests/helpers/storeRegistry.ts index e1950889d..06ffdf946 100644 --- a/client/tests/helpers/storeRegistry.ts +++ b/client/tests/helpers/storeRegistry.ts @@ -62,5 +62,6 @@ export const UNRESET_STORES: Record = { useTourPoiStore: 'the places found along a tour; the tests that read it clear it in their own beforeEach', useTourRouteHighlightStore: 'the stretches a bike card points at; the tests that read it clear it in their own beforeEach', + useTourVisitStore: 'the visits of city walks per day; the tests that read it reset it in their own beforeEach', useTripJournalLinkStore: 'the journal link per trip; the tests that read it reset it in their own beforeEach', }; diff --git a/shared/src/i18n/ar/tours.ts b/shared/src/i18n/ar/tours.ts index bda6de518..28ad8910b 100644 --- a/shared/src/i18n/ar/tours.ts +++ b/shared/src/i18n/ar/tours.ts @@ -499,6 +499,16 @@ const tours: TranslationStrings = { 'tours.walk.timeEstimatedInfo': 'وقت المسار سيرًا بين المحطات، دون مدد البقاء.', 'tours.walk.stays': 'مدد البقاء', 'tours.walk.totalAtLeast': '{time} على الأقل', + 'tours.walk.stations': 'محطات الجولة', + 'tours.walk.stationNumbered': 'المحطة {number}', + 'tours.walk.progress': 'تمت زيارة {visited} من {total}', + 'tours.walk.visit.visited': 'تمت الزيارة', + 'tours.walk.visit.skip': 'تخطي', + 'tours.walk.visit.undo': 'تراجع', + 'tours.walk.visit.isVisited': 'تمت زيارتها في هذا اليوم', + 'tours.walk.visit.isSkipped': 'تم تخطيها في هذا اليوم', + 'tours.walk.visit.fromDay': 'افتح الجولة من يوم في الخطة لتأشير محطاتها لذلك اليوم.', + 'tours.walk.visit.failed': 'تعذر تأشير المحطة. يتطلب ذلك اتصالًا؛ حاول مجددًا بعد قليل.', '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 be3526668..17986d22f 100644 --- a/shared/src/i18n/az/tours.ts +++ b/shared/src/i18n/az/tours.ts @@ -494,6 +494,16 @@ const tours: TranslationStrings = { '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.walk.stations': 'Gəzintinin dayanacaqları', + 'tours.walk.stationNumbered': 'Dayanacaq {number}', + 'tours.walk.progress': '{total} dayanacaqdan {visited} ziyarət edildi', + 'tours.walk.visit.visited': 'Ziyarət edildi', + 'tours.walk.visit.skip': 'Keç', + 'tours.walk.visit.undo': 'Geri al', + 'tours.walk.visit.isVisited': 'Bu gün ziyarət edildi', + 'tours.walk.visit.isSkipped': 'Bu gün keçildi', + 'tours.walk.visit.fromDay': 'Dayanacaqları o gün üçün qeyd etmək üçün gəzintini plandakı bir gündən aç.', + 'tours.walk.visit.failed': 'Dayanacaq qeyd edilə bilmədi. Bunun üçün bağlantı lazımdır; bir azdan yenidən cəhd et.', '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 3ffb4b878..8d0b32ab6 100644 --- a/shared/src/i18n/br/tours.ts +++ b/shared/src/i18n/br/tours.ts @@ -500,6 +500,16 @@ const tours: TranslationStrings = { '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.walk.stations': 'Paradas do passeio', + 'tours.walk.stationNumbered': 'Parada {number}', + 'tours.walk.progress': '{visited} de {total} visitadas', + 'tours.walk.visit.visited': 'Visitada', + 'tours.walk.visit.skip': 'Pular', + 'tours.walk.visit.undo': 'Desfazer', + 'tours.walk.visit.isVisited': 'Visitada neste dia', + 'tours.walk.visit.isSkipped': 'Pulada neste dia', + 'tours.walk.visit.fromDay': 'Abra o passeio a partir de um dia do plano para marcar as paradas daquele dia.', + 'tours.walk.visit.failed': 'Não foi possível marcar a parada. É preciso conexão; tente de novo em instantes.', '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 b9d44b449..c39b15e5a 100644 --- a/shared/src/i18n/ca/tours.ts +++ b/shared/src/i18n/ca/tours.ts @@ -501,6 +501,16 @@ const tours: TranslationStrings = { '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.walk.stations': 'Parades de la ruta', + 'tours.walk.stationNumbered': 'Parada {number}', + 'tours.walk.progress': '{visited} de {total} visitades', + 'tours.walk.visit.visited': 'Visitada', + 'tours.walk.visit.skip': 'Omet', + 'tours.walk.visit.undo': 'Desfés', + 'tours.walk.visit.isVisited': 'Visitada aquest dia', + 'tours.walk.visit.isSkipped': 'Omesa aquest dia', + 'tours.walk.visit.fromDay': "Obre la ruta des d'un dia del pla per marcar-ne les parades d'aquell dia.", + 'tours.walk.visit.failed': "No s'ha pogut marcar la parada. Cal connexió; torna-ho a provar d'aquí a una estona.", '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 32d0ff989..d0272812a 100644 --- a/shared/src/i18n/cs/tours.ts +++ b/shared/src/i18n/cs/tours.ts @@ -491,6 +491,16 @@ const tours: TranslationStrings = { '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.walk.stations': 'Zastávky procházky', + 'tours.walk.stationNumbered': 'Zastávka {number}', + 'tours.walk.progress': 'Navštíveno {visited} z {total}', + 'tours.walk.visit.visited': 'Navštíveno', + 'tours.walk.visit.skip': 'Přeskočit', + 'tours.walk.visit.undo': 'Vrátit', + 'tours.walk.visit.isVisited': 'Navštíveno v tento den', + 'tours.walk.visit.isSkipped': 'Přeskočeno v tento den', + 'tours.walk.visit.fromDay': 'Otevři procházku ze dne v plánu, abys mohl odškrtat zastávky pro ten den.', + 'tours.walk.visit.failed': 'Zastávku se nepodařilo označit. Je potřeba připojení; zkus to za chvíli znovu.', '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 d4fd96c74..8feab6c8b 100644 --- a/shared/src/i18n/de/tours.ts +++ b/shared/src/i18n/de/tours.ts @@ -505,6 +505,17 @@ const tours: TranslationStrings = { '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.walk.stations': 'Stationen', + 'tours.walk.stationNumbered': 'Station {number}', + 'tours.walk.progress': '{visited} von {total} besucht', + 'tours.walk.visit.visited': 'Besucht', + 'tours.walk.visit.skip': 'Überspringen', + 'tours.walk.visit.undo': 'Zurücknehmen', + 'tours.walk.visit.isVisited': 'An diesem Tag besucht', + 'tours.walk.visit.isSkipped': 'An diesem Tag übersprungen', + 'tours.walk.visit.fromDay': 'Öffne den Walk über einen Tag im Plan, um seine Stationen für diesen Tag abzuhaken.', + 'tours.walk.visit.failed': + 'Die Station konnte nicht markiert werden. Dafür braucht es eine Verbindung; versuch es gleich noch einmal.', '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 3d371576a..cd202eaff 100644 --- a/shared/src/i18n/en/tours.ts +++ b/shared/src/i18n/en/tours.ts @@ -492,6 +492,16 @@ const tours: TranslationStrings = { '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.walk.stations': 'Stops of the walk', + 'tours.walk.stationNumbered': 'Stop {number}', + 'tours.walk.progress': '{visited} of {total} visited', + 'tours.walk.visit.visited': 'Mark visited', + 'tours.walk.visit.skip': 'Skip', + 'tours.walk.visit.undo': 'Take back', + 'tours.walk.visit.isVisited': 'Visited on this day', + 'tours.walk.visit.isSkipped': 'Skipped on this day', + 'tours.walk.visit.fromDay': 'Open the walk from a day of the plan to check its stops off for that day.', + 'tours.walk.visit.failed': 'The stop could not be marked. It needs a connection; try again in a moment.', '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 5cf6385cf..90971ffde 100644 --- a/shared/src/i18n/es/tours.ts +++ b/shared/src/i18n/es/tours.ts @@ -499,6 +499,16 @@ const tours: TranslationStrings = { '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.walk.stations': 'Paradas del paseo', + 'tours.walk.stationNumbered': 'Parada {number}', + 'tours.walk.progress': '{visited} de {total} visitadas', + 'tours.walk.visit.visited': 'Visitada', + 'tours.walk.visit.skip': 'Omitir', + 'tours.walk.visit.undo': 'Deshacer', + 'tours.walk.visit.isVisited': 'Visitada este día', + 'tours.walk.visit.isSkipped': 'Omitida este día', + 'tours.walk.visit.fromDay': 'Abre el paseo desde un día del plan para marcar sus paradas de ese día.', + 'tours.walk.visit.failed': 'No se pudo marcar la parada. Hace falta conexión; inténtalo de nuevo en un momento.', '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 ba96203e0..16d61ad36 100644 --- a/shared/src/i18n/et/tours.ts +++ b/shared/src/i18n/et/tours.ts @@ -494,6 +494,16 @@ const tours: TranslationStrings = { 'tours.walk.timeEstimatedInfo': 'Marsruutija kõndimisaeg peatuste vahel, ilma peatumisteta.', 'tours.walk.stays': 'Peatumised', 'tours.walk.totalAtLeast': 'vähemalt {time}', + 'tours.walk.stations': 'Jalutuse peatused', + 'tours.walk.stationNumbered': 'Peatus {number}', + 'tours.walk.progress': '{visited} / {total} külastatud', + 'tours.walk.visit.visited': 'Külastatud', + 'tours.walk.visit.skip': 'Jäta vahele', + 'tours.walk.visit.undo': 'Võta tagasi', + 'tours.walk.visit.isVisited': 'Sel päeval külastatud', + 'tours.walk.visit.isSkipped': 'Sel päeval vahele jäetud', + 'tours.walk.visit.fromDay': 'Ava jalutus plaani päevast, et selle päeva peatused ära märkida.', + 'tours.walk.visit.failed': 'Peatust ei õnnestunud märkida. Selleks on vaja ühendust; proovi kohe uuesti.', '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 d314b0b3d..d0faf1df3 100644 --- a/shared/src/i18n/fr/tours.ts +++ b/shared/src/i18n/fr/tours.ts @@ -509,6 +509,16 @@ const tours: TranslationStrings = { '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.walk.stations': 'Étapes de la balade', + 'tours.walk.stationNumbered': 'Étape {number}', + 'tours.walk.progress': '{visited} sur {total} visitées', + 'tours.walk.visit.visited': 'Visitée', + 'tours.walk.visit.skip': 'Passer', + 'tours.walk.visit.undo': 'Annuler', + 'tours.walk.visit.isVisited': 'Visitée ce jour-là', + 'tours.walk.visit.isSkipped': 'Passée ce jour-là', + 'tours.walk.visit.fromDay': 'Ouvre la balade depuis un jour du plan pour cocher ses étapes ce jour-là.', + 'tours.walk.visit.failed': "L'étape n'a pas pu être cochée. Il faut une connexion ; réessaie dans un instant.", '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 732d69bf0..98acbf794 100644 --- a/shared/src/i18n/gr/tours.ts +++ b/shared/src/i18n/gr/tours.ts @@ -509,6 +509,16 @@ const tours: TranslationStrings = { 'tours.walk.timeEstimatedInfo': 'Ο χρόνος με τα πόδια του δρομολογητή ανάμεσα στις στάσεις, χωρίς τις παραμονές.', 'tours.walk.stays': 'Παραμονές', 'tours.walk.totalAtLeast': 'τουλάχιστον {time}', + 'tours.walk.stations': 'Στάσεις της βόλτας', + 'tours.walk.stationNumbered': 'Στάση {number}', + 'tours.walk.progress': '{visited} από {total} επισκέψεις', + 'tours.walk.visit.visited': 'Επισκέφθηκα', + 'tours.walk.visit.skip': 'Παράλειψη', + 'tours.walk.visit.undo': 'Αναίρεση', + 'tours.walk.visit.isVisited': 'Επισκέφθηκε αυτή τη μέρα', + 'tours.walk.visit.isSkipped': 'Παραλείφθηκε αυτή τη μέρα', + 'tours.walk.visit.fromDay': 'Άνοιξε τη βόλτα από μια μέρα του πλάνου για να τσεκάρεις τις στάσεις εκείνης της μέρας.', + 'tours.walk.visit.failed': 'Η στάση δεν σημειώθηκε. Χρειάζεται σύνδεση· δοκίμασε ξανά σε λίγο.', '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 6e0a5cd54..b44f36000 100644 --- a/shared/src/i18n/hu/tours.ts +++ b/shared/src/i18n/hu/tours.ts @@ -501,6 +501,16 @@ const tours: TranslationStrings = { '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.walk.stations': 'A séta megállói', + 'tours.walk.stationNumbered': '{number}. megálló', + 'tours.walk.progress': '{total} megállóból {visited} meglátogatva', + 'tours.walk.visit.visited': 'Meglátogatva', + 'tours.walk.visit.skip': 'Kihagyás', + 'tours.walk.visit.undo': 'Visszavonás', + 'tours.walk.visit.isVisited': 'Ezen a napon meglátogatva', + 'tours.walk.visit.isSkipped': 'Ezen a napon kihagyva', + 'tours.walk.visit.fromDay': 'Nyisd meg a sétát a terv egyik napjáról, hogy arra a napra kipipáld a megállókat.', + 'tours.walk.visit.failed': 'A megállót nem sikerült megjelölni. Ehhez kapcsolat kell; próbáld újra kicsit később.', '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 dc517ab51..bf00cffce 100644 --- a/shared/src/i18n/id/tours.ts +++ b/shared/src/i18n/id/tours.ts @@ -493,6 +493,16 @@ const tours: TranslationStrings = { '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.walk.stations': 'Titik singgah rute', + 'tours.walk.stationNumbered': 'Titik {number}', + 'tours.walk.progress': '{visited} dari {total} dikunjungi', + 'tours.walk.visit.visited': 'Sudah dikunjungi', + 'tours.walk.visit.skip': 'Lewati', + 'tours.walk.visit.undo': 'Batalkan', + 'tours.walk.visit.isVisited': 'Dikunjungi pada hari ini', + 'tours.walk.visit.isSkipped': 'Dilewati pada hari ini', + 'tours.walk.visit.fromDay': 'Buka rute dari salah satu hari di rencana untuk mencentang titiknya pada hari itu.', + 'tours.walk.visit.failed': 'Titik tidak dapat ditandai. Perlu koneksi; coba lagi sebentar lagi.', '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 5c6c37689..1e3aea9cf 100644 --- a/shared/src/i18n/it/tours.ts +++ b/shared/src/i18n/it/tours.ts @@ -500,6 +500,16 @@ const tours: TranslationStrings = { '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.walk.stations': 'Tappe del percorso', + 'tours.walk.stationNumbered': 'Tappa {number}', + 'tours.walk.progress': '{visited} su {total} visitate', + 'tours.walk.visit.visited': 'Visitata', + 'tours.walk.visit.skip': 'Salta', + 'tours.walk.visit.undo': 'Annulla', + 'tours.walk.visit.isVisited': 'Visitata in questo giorno', + 'tours.walk.visit.isSkipped': 'Saltata in questo giorno', + 'tours.walk.visit.fromDay': 'Apri il percorso da un giorno del piano per spuntare le tappe di quel giorno.', + 'tours.walk.visit.failed': 'Non è stato possibile segnare la tappa. Serve una connessione; riprova tra poco.', '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 4c3f03832..d551fba26 100644 --- a/shared/src/i18n/ja/tours.ts +++ b/shared/src/i18n/ja/tours.ts @@ -485,6 +485,17 @@ const tours: TranslationStrings = { 'tours.walk.timeEstimatedInfo': 'ルート計算による立ち寄り先間の徒歩時間。滞在時間は含みません。', 'tours.walk.stays': '滞在', 'tours.walk.totalAtLeast': '{time} 以上', + 'tours.walk.stations': '立ち寄り先', + 'tours.walk.stationNumbered': '立ち寄り先 {number}', + 'tours.walk.progress': '{total} 件中 {visited} 件訪問済み', + 'tours.walk.visit.visited': '訪問済み', + 'tours.walk.visit.skip': 'スキップ', + 'tours.walk.visit.undo': '取り消す', + 'tours.walk.visit.isVisited': 'この日に訪問済み', + 'tours.walk.visit.isSkipped': 'この日はスキップ', + 'tours.walk.visit.fromDay': '計画の日から開くと、その日の立ち寄り先をチェックできます。', + 'tours.walk.visit.failed': + '立ち寄り先をチェックできませんでした。接続が必要です。少ししてからもう一度お試しください。', '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 add32e409..0b8b12da6 100644 --- a/shared/src/i18n/ko/tours.ts +++ b/shared/src/i18n/ko/tours.ts @@ -481,6 +481,16 @@ const tours: TranslationStrings = { 'tours.walk.timeEstimatedInfo': '경로 계산기가 낸 정류지 사이 도보 시간이며, 머무는 시간은 빠져 있습니다.', 'tours.walk.stays': '머무는 시간', 'tours.walk.totalAtLeast': '최소 {time}', + 'tours.walk.stations': '산책 정류지', + 'tours.walk.stationNumbered': '정류지 {number}', + 'tours.walk.progress': '{total}곳 중 {visited}곳 방문', + 'tours.walk.visit.visited': '방문함', + 'tours.walk.visit.skip': '건너뛰기', + 'tours.walk.visit.undo': '되돌리기', + 'tours.walk.visit.isVisited': '이날 방문함', + 'tours.walk.visit.isSkipped': '이날 건너뜀', + 'tours.walk.visit.fromDay': '계획의 날짜에서 열면 그날의 정류지를 체크할 수 있습니다.', + 'tours.walk.visit.failed': '정류지를 표시하지 못했습니다. 연결이 필요하니 잠시 후 다시 시도하세요.', '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 71ba43948..be19fa7fa 100644 --- a/shared/src/i18n/nl/tours.ts +++ b/shared/src/i18n/nl/tours.ts @@ -503,6 +503,17 @@ const tours: TranslationStrings = { 'tours.walk.timeEstimatedInfo': 'De looptijd van de routeplanner tussen de haltes, zonder het verblijf.', 'tours.walk.stays': 'Verblijven', 'tours.walk.totalAtLeast': 'minstens {time}', + 'tours.walk.stations': 'Haltes van de wandeling', + 'tours.walk.stationNumbered': 'Halte {number}', + 'tours.walk.progress': '{visited} van {total} bezocht', + 'tours.walk.visit.visited': 'Bezocht', + 'tours.walk.visit.skip': 'Overslaan', + 'tours.walk.visit.undo': 'Terugnemen', + 'tours.walk.visit.isVisited': 'Op deze dag bezocht', + 'tours.walk.visit.isSkipped': 'Op deze dag overgeslagen', + 'tours.walk.visit.fromDay': 'Open de wandeling vanuit een dag in het plan om de haltes voor die dag af te vinken.', + 'tours.walk.visit.failed': + 'De halte kon niet worden gemarkeerd. Daar is verbinding voor nodig; probeer het zo opnieuw.', '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 cf25832f9..6911afa09 100644 --- a/shared/src/i18n/pl/tours.ts +++ b/shared/src/i18n/pl/tours.ts @@ -508,6 +508,16 @@ const tours: TranslationStrings = { '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.walk.stations': 'Przystanki trasy', + 'tours.walk.stationNumbered': 'Przystanek {number}', + 'tours.walk.progress': 'Odwiedzono {visited} z {total}', + 'tours.walk.visit.visited': 'Odwiedzony', + 'tours.walk.visit.skip': 'Pomiń', + 'tours.walk.visit.undo': 'Cofnij', + 'tours.walk.visit.isVisited': 'Odwiedzony tego dnia', + 'tours.walk.visit.isSkipped': 'Pominięty tego dnia', + 'tours.walk.visit.fromDay': 'Otwórz trasę z dnia w planie, aby odhaczyć przystanki na ten dzień.', + 'tours.walk.visit.failed': 'Nie udało się oznaczyć przystanku. Potrzebne jest połączenie; spróbuj za chwilę.', '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 d5440e0aa..30dad0018 100644 --- a/shared/src/i18n/ru/tours.ts +++ b/shared/src/i18n/ru/tours.ts @@ -511,6 +511,16 @@ const tours: TranslationStrings = { 'tours.walk.timeEstimatedInfo': 'Время пешком между остановками по расчёту маршрута, без остановок на месте.', 'tours.walk.stays': 'Остановки', 'tours.walk.totalAtLeast': 'не меньше {time}', + 'tours.walk.stations': 'Остановки прогулки', + 'tours.walk.stationNumbered': 'Остановка {number}', + 'tours.walk.progress': 'Посещено {visited} из {total}', + 'tours.walk.visit.visited': 'Посещено', + 'tours.walk.visit.skip': 'Пропустить', + 'tours.walk.visit.undo': 'Отменить', + 'tours.walk.visit.isVisited': 'Посещена в этот день', + 'tours.walk.visit.isSkipped': 'Пропущена в этот день', + 'tours.walk.visit.fromDay': 'Открой прогулку из дня в плане, чтобы отмечать остановки за этот день.', + 'tours.walk.visit.failed': 'Не удалось отметить остановку. Нужна связь; попробуй чуть позже.', '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 5fee76cd4..89d1223ec 100644 --- a/shared/src/i18n/sk/tours.ts +++ b/shared/src/i18n/sk/tours.ts @@ -493,6 +493,16 @@ const tours: TranslationStrings = { '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.walk.stations': 'Zastávky prechádzky', + 'tours.walk.stationNumbered': 'Zastávka {number}', + 'tours.walk.progress': 'Navštívené {visited} z {total}', + 'tours.walk.visit.visited': 'Navštívené', + 'tours.walk.visit.skip': 'Preskočiť', + 'tours.walk.visit.undo': 'Vrátiť', + 'tours.walk.visit.isVisited': 'Navštívené v tento deň', + 'tours.walk.visit.isSkipped': 'Preskočené v tento deň', + 'tours.walk.visit.fromDay': 'Otvor prechádzku z dňa v pláne, aby si odškrtol zastávky na ten deň.', + 'tours.walk.visit.failed': 'Zastávku sa nepodarilo označiť. Je potrebné pripojenie; skús to o chvíľu znova.', '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 7febfc75c..635ea6009 100644 --- a/shared/src/i18n/sv/tours.ts +++ b/shared/src/i18n/sv/tours.ts @@ -492,6 +492,16 @@ const tours: TranslationStrings = { 'tours.walk.timeEstimatedInfo': 'Ruttplanerarens gångtid mellan stoppen, utan vistelserna.', 'tours.walk.stays': 'Vistelser', 'tours.walk.totalAtLeast': 'minst {time}', + 'tours.walk.stations': 'Promenadens stopp', + 'tours.walk.stationNumbered': 'Stopp {number}', + 'tours.walk.progress': '{visited} av {total} besökta', + 'tours.walk.visit.visited': 'Besökt', + 'tours.walk.visit.skip': 'Hoppa över', + 'tours.walk.visit.undo': 'Ångra', + 'tours.walk.visit.isVisited': 'Besökt den här dagen', + 'tours.walk.visit.isSkipped': 'Överhoppad den här dagen', + 'tours.walk.visit.fromDay': 'Öppna promenaden från en dag i planen för att bocka av stoppen den dagen.', + 'tours.walk.visit.failed': 'Stoppet kunde inte markeras. Det kräver en anslutning; försök igen om en stund.', '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 475b7c9b8..1f935c3ac 100644 --- a/shared/src/i18n/th/tours.ts +++ b/shared/src/i18n/th/tours.ts @@ -475,6 +475,16 @@ const tours: TranslationStrings = { 'tours.walk.timeEstimatedInfo': 'เวลาเดินระหว่างจุดแวะจากตัวคำนวณเส้นทาง ไม่รวมเวลาแวะพัก', 'tours.walk.stays': 'เวลาแวะพัก', 'tours.walk.totalAtLeast': 'อย่างน้อย {time}', + 'tours.walk.stations': 'จุดแวะของทริป', + 'tours.walk.stationNumbered': 'จุดแวะที่ {number}', + 'tours.walk.progress': 'แวะแล้ว {visited} จาก {total}', + 'tours.walk.visit.visited': 'แวะแล้ว', + 'tours.walk.visit.skip': 'ข้าม', + 'tours.walk.visit.undo': 'ยกเลิก', + 'tours.walk.visit.isVisited': 'แวะแล้วในวันนี้', + 'tours.walk.visit.isSkipped': 'ข้ามไปในวันนี้', + 'tours.walk.visit.fromDay': 'เปิดทริปจากวันในแผนเพื่อติ๊กจุดแวะของวันนั้น', + 'tours.walk.visit.failed': 'ทำเครื่องหมายจุดแวะไม่ได้ ต้องมีการเชื่อมต่อ ลองใหม่อีกครั้งในอีกสักครู่', '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 95c5fd8c5..04d1249b3 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 = 735; +const GENERAL_KEY_COUNT = 745; 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 0cc37b1b8..74c453236 100644 --- a/shared/src/i18n/tr/tours.ts +++ b/shared/src/i18n/tr/tours.ts @@ -495,6 +495,16 @@ const tours: TranslationStrings = { '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.walk.stations': 'Yürüyüşün durakları', + 'tours.walk.stationNumbered': 'Durak {number}', + 'tours.walk.progress': '{total} duraktan {visited} ziyaret edildi', + 'tours.walk.visit.visited': 'Ziyaret edildi', + 'tours.walk.visit.skip': 'Atla', + 'tours.walk.visit.undo': 'Geri al', + 'tours.walk.visit.isVisited': 'Bu gün ziyaret edildi', + 'tours.walk.visit.isSkipped': 'Bu gün atlandı', + 'tours.walk.visit.fromDay': 'Duraklarını o gün için işaretlemek için yürüyüşü plandaki bir günden aç.', + 'tours.walk.visit.failed': 'Durak işaretlenemedi. Bunun için bağlantı gerekir; birazdan yeniden dene.', '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 7f1e1b12f..3d6aa86a3 100644 --- a/shared/src/i18n/uk/tours.ts +++ b/shared/src/i18n/uk/tours.ts @@ -507,6 +507,16 @@ const tours: TranslationStrings = { 'tours.walk.timeEstimatedInfo': 'Час пішки між зупинками за розрахунком маршруту, без перебування.', 'tours.walk.stays': 'Перебування', 'tours.walk.totalAtLeast': 'щонайменше {time}', + 'tours.walk.stations': 'Зупинки прогулянки', + 'tours.walk.stationNumbered': 'Зупинка {number}', + 'tours.walk.progress': 'Відвідано {visited} з {total}', + 'tours.walk.visit.visited': 'Відвідано', + 'tours.walk.visit.skip': 'Пропустити', + 'tours.walk.visit.undo': 'Скасувати', + 'tours.walk.visit.isVisited': 'Відвідано цього дня', + 'tours.walk.visit.isSkipped': 'Пропущено цього дня', + 'tours.walk.visit.fromDay': 'Відкрий прогулянку з дня в плані, щоб відмічати зупинки на цей день.', + 'tours.walk.visit.failed': "Не вдалося позначити зупинку. Потрібне з'єднання; спробуй трохи згодом.", '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 87aa5533a..2d8c1aa9d 100644 --- a/shared/src/i18n/vi/tours.ts +++ b/shared/src/i18n/vi/tours.ts @@ -492,6 +492,16 @@ const tours: TranslationStrings = { '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.walk.stations': 'Các điểm dừng', + 'tours.walk.stationNumbered': 'Điểm dừng {number}', + 'tours.walk.progress': 'Đã ghé {visited}/{total}', + 'tours.walk.visit.visited': 'Đã ghé', + 'tours.walk.visit.skip': 'Bỏ qua', + 'tours.walk.visit.undo': 'Hoàn tác', + 'tours.walk.visit.isVisited': 'Đã ghé trong ngày này', + 'tours.walk.visit.isSkipped': 'Đã bỏ qua trong ngày này', + 'tours.walk.visit.fromDay': 'Mở lộ trình từ một ngày trong kế hoạch để đánh dấu các điểm dừng của ngày đó.', + 'tours.walk.visit.failed': 'Không thể đánh dấu điểm dừng. Cần có kết nối; hãy thử lại sau giây lát.', '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 30460a14b..2af3184e3 100644 --- a/shared/src/i18n/zh-TW/tours.ts +++ b/shared/src/i18n/zh-TW/tours.ts @@ -460,6 +460,16 @@ const tours: TranslationStrings = { 'tours.walk.timeEstimatedInfo': '路線規劃給出的站點間步行時間,不含停留時間。', 'tours.walk.stays': '停留', 'tours.walk.totalAtLeast': '至少 {time}', + 'tours.walk.stations': '漫步站點', + 'tours.walk.stationNumbered': '第 {number} 站', + 'tours.walk.progress': '已到訪 {visited}/{total}', + 'tours.walk.visit.visited': '已到訪', + 'tours.walk.visit.skip': '略過', + 'tours.walk.visit.undo': '復原', + 'tours.walk.visit.isVisited': '當天已到訪', + 'tours.walk.visit.isSkipped': '當天已略過', + 'tours.walk.visit.fromDay': '從計畫中的某一天打開漫步,即可勾選當天的站點。', + 'tours.walk.visit.failed': '無法標記該站點。需要網路連線,請稍後再試。', '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 6f0939643..2c1f2705c 100644 --- a/shared/src/i18n/zh/tours.ts +++ b/shared/src/i18n/zh/tours.ts @@ -461,6 +461,16 @@ const tours: TranslationStrings = { 'tours.walk.timeEstimatedInfo': '路线规划给出的站点间步行时间,不含停留时间。', 'tours.walk.stays': '停留', 'tours.walk.totalAtLeast': '至少 {time}', + 'tours.walk.stations': '漫步站点', + 'tours.walk.stationNumbered': '第 {number} 站', + 'tours.walk.progress': '已到访 {visited}/{total}', + 'tours.walk.visit.visited': '已到访', + 'tours.walk.visit.skip': '跳过', + 'tours.walk.visit.undo': '撤销', + 'tours.walk.visit.isVisited': '当天已到访', + 'tours.walk.visit.isSkipped': '当天已跳过', + 'tours.walk.visit.fromDay': '从计划中的某一天打开漫步,即可勾选当天的站点。', + 'tours.walk.visit.failed': '无法标记该站点。需要网络连接,请稍后再试。', 'tours.planner.tourType': '路线类型', 'tours.paddle.boat': '船只', 'tours.paddle.boat.kayak': '皮划艇',