diff --git a/client/src/components/Tours/walk/walkSchedule.ts b/client/src/components/Tours/walk/walkSchedule.ts
new file mode 100644
index 000000000..a9ea92c27
--- /dev/null
+++ b/client/src/components/Tours/walk/walkSchedule.ts
@@ -0,0 +1,146 @@
+import type { TourPlannerWaypoint } from '../planner/tourPlannerWaypoints';
+
+/**
+ * CW-04: a city walk's stations against their opening hours on one day. The
+ * arrival is an estimate: the start of that day's walk, the walking time
+ * shared out along the route by distance, and the stays before. Hours are
+ * read only in OpenStreetMap's plain forms ("Mo-Fr 09:00-18:00; Sa 10:00-14:00;
+ * Su off", "24/7"); anything else stays unread rather than guessed, and a
+ * station without hours is unknown, never open.
+ */
+
+const DAYS = ['Mo', 'Tu', 'We', 'Th', 'Fr', 'Sa', 'Su'] as const;
+
+/** Minutes after midnight of a `HH:MM` time; null for anything else. */
+export function minutesOf(time: string | null | undefined): number | null {
+ const match = /^(\d{1,2}):(\d{2})/.exec(time ?? '');
+ if (!match) return null;
+ const hours = Number(match[1]);
+ const minutes = Number(match[2]);
+ return hours < 24 && minutes < 60 ? hours * 60 + minutes : null;
+}
+
+export function clockOf(minutes: number): string {
+ const day = ((Math.round(minutes) % 1440) + 1440) % 1440;
+ return `${String(Math.floor(day / 60)).padStart(2, '0')}:${String(day % 60).padStart(2, '0')}`;
+}
+
+/** Monday 0 to Sunday 6, of a `YYYY-MM-DD` date read as a calendar day. */
+function weekdayOf(date: string): number | null {
+ const match = /^(\d{4})-(\d{2})-(\d{2})$/.exec(date);
+ if (!match) return null;
+ const day = new Date(Date.UTC(Number(match[1]), Number(match[2]) - 1, Number(match[3]))).getUTCDay();
+ return (day + 6) % 7;
+}
+
+function daysOf(spec: string): Set | null {
+ const days = new Set();
+ for (const part of spec.split(',')) {
+ const [from, to] = part.split('-');
+ const start = DAYS.indexOf(from as (typeof DAYS)[number]);
+ const end = to === undefined ? start : DAYS.indexOf(to as (typeof DAYS)[number]);
+ if (start < 0 || end < 0) return null;
+ for (let day = start; ; day = (day + 1) % 7) {
+ days.add(day);
+ if (day === end) break;
+ }
+ }
+ return days;
+}
+
+/** The windows a place is open on a date, in minutes; null where the hours cannot be read. */
+export function openWindows(hours: string, date: string): Array<[number, number]> | null {
+ const weekday = weekdayOf(date);
+ if (weekday === null) return null;
+ const text = hours.trim();
+ if (text === '24/7') return [[0, 1440]];
+ let windows: Array<[number, number]> = [];
+ for (const raw of text.split(';')) {
+ const rule = raw.trim();
+ if (!rule) continue;
+ const match = /^([A-Za-z,-]+)\s+(.+)$/.exec(rule);
+ if (!match) return null;
+ const days = daysOf(match[1]);
+ if (!days) return null;
+ if (!days.has(weekday)) continue;
+ // A later rule for the day replaces an earlier one, as OpenStreetMap reads them.
+ if (match[2].trim() === 'off' || match[2].trim() === 'closed') {
+ windows = [];
+ continue;
+ }
+ const ranges: Array<[number, number]> = [];
+ for (const span of match[2].split(',')) {
+ const [open, close] = span.trim().split('-');
+ const from = minutesOf(open);
+ const to = close === '24:00' ? 1440 : minutesOf(close);
+ if (from === null || to === null || to <= from) return null;
+ ranges.push([from, to]);
+ }
+ windows = ranges;
+ }
+ return windows;
+}
+
+export type StationFit =
+ | { kind: 'unknown' }
+ | { kind: 'unread'; hours: string }
+ | { kind: 'ok' }
+ | { kind: 'closed'; arrival: number }
+ | { kind: 'closes'; arrival: number; leave: number; closes: number };
+
+/** Whether a station is open from its arrival to the end of its stay on that date. */
+export function stationFit(hours: string | null | undefined, date: string, arrival: number, stay: number): StationFit {
+ if (!hours) return { kind: 'unknown' };
+ const windows = openWindows(hours, date);
+ if (windows === null) return { kind: 'unread', hours };
+ const leave = arrival + stay;
+ const open = windows.find(([from, to]) => arrival >= from && arrival < to);
+ if (!open) return { kind: 'closed', arrival };
+ if (leave > open[1]) return { kind: 'closes', arrival, leave, closes: open[1] };
+ return { kind: 'ok' };
+}
+
+/** Metres between two points, close enough along a city walk. */
+function metres(a: readonly [number, number], b: readonly [number, number]): number {
+ const rad = Math.PI / 180;
+ const x = (b[1] - a[1]) * rad * Math.cos(((a[0] + b[0]) / 2) * rad);
+ const y = (b[0] - a[0]) * rad;
+ return Math.hypot(x, y) * 6_371_000;
+}
+
+/**
+ * The estimated arrival at each station, in minutes after midnight: the start,
+ * the walking time shared out by how far along the route the station lies,
+ * and the stays before it; a stay not known counts as nothing before it and
+ * makes every later arrival a lower bound. Null when the start, the walking
+ * time or the route is not known.
+ */
+export function stationArrivals(
+ stations: readonly TourPlannerWaypoint[],
+ route: ReadonlyArray,
+ start: number | null,
+ walkingMinutes: number | null
+): Array<{ arrival: number; atLeast: boolean }> | null {
+ if (start === null || walkingMinutes === null || route.length < 2) return null;
+ const along: number[] = [0];
+ for (let i = 1; i < route.length; i += 1) along.push(along[i - 1] + metres(route[i - 1], route[i]));
+ const total = along[along.length - 1] || 1;
+ let stays = 0;
+ let unknown = false;
+ return stations.map((station) => {
+ let nearest = 0;
+ let best = Infinity;
+ route.forEach((point, index) => {
+ const gap = metres(point, [station.lat, station.lng]);
+ if (gap < best) {
+ best = gap;
+ nearest = index;
+ }
+ });
+ const arrival = start + walkingMinutes * (along[nearest] / total) + stays;
+ const result = { arrival, atLeast: unknown };
+ if (station.stayMinutes == null) unknown = true;
+ else stays += station.stayMinutes;
+ return result;
+ });
+}
diff --git a/shared/src/i18n/ar/tours.ts b/shared/src/i18n/ar/tours.ts
index 12465b44a..05f5f36de 100644
--- a/shared/src/i18n/ar/tours.ts
+++ b/shared/src/i18n/ar/tours.ts
@@ -119,7 +119,7 @@ const tours: TranslationStrings = {
'tours.planner.discardChanges': 'تجاهل التغييرات',
'tours.planner.newTourConfirmTitle': 'هل تريد بدء جولة جديدة؟',
'tours.planner.newTourConfirmBody': 'ستُحذف تغييراتك غير المحفوظة على هذه الجولة. لن تتغيّر الجولات المحفوظة.',
- 'tours.planner.newTourConfirm': 'تجاهل التغييرات وبدء جولة جديدة',
+ 'tours.planner.newTourConfirm': 'تجاهل وابدأ',
'tours.planner.collapseElevation': 'طيّ مخطط الارتفاع',
'tours.planner.expandElevation': 'توسيع مخطط الارتفاع',
'tours.planner.name': 'اسم الجولة',
@@ -520,6 +520,16 @@ const tours: TranslationStrings = {
'tours.walk.themeHint.mixed': 'قليل من كل شيء: معالم ومتاحف وحدائق وإطلالات ومقاهٍ وأسواق.',
'tours.walk.themeHint.own': 'اختيارك الخاص من الفئات أدناه؛ يضبطها الموضوع لك.',
'tours.walk.themeReason': 'مقترح لموضوع {theme}',
+ 'tours.walk.arrival': 'الوصول نحو {time}',
+ 'tours.walk.arrivalAtLeast': 'الوصول في {time} على أقرب تقدير',
+ 'tours.walk.hours.closed': 'مغلق في {arrival} حسب ساعات العمل',
+ 'tours.walk.hours.closes': 'الوصول {arrival}، البقاء حتى {leave}، لكنه يغلق في {closes}',
+ 'tours.walk.hours.unread': 'لم تُقرأ ساعات العمل هنا: {text}',
+ 'tours.walk.hours.source':
+ 'ساعات العمل كما في OpenStreetMap مقابل وقت الوصول التقديري؛ تحقق قبل الذهاب. المحطة بلا ساعات غير معروفة، لا مفتوحة.',
+ 'tours.walk.hours.noStart': 'حدد وقتًا للجولة في هذا اليوم لرؤية أوقات الوصول وساعات العمل.',
+ 'tours.poi.categoriesNone': 'لم تُختر أي فئة',
+ 'tours.poi.categoriesMore': '{first} +{more}',
'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 a3bf4be65..8d418a277 100644
--- a/shared/src/i18n/az/tours.ts
+++ b/shared/src/i18n/az/tours.ts
@@ -106,7 +106,7 @@ const tours: TranslationStrings = {
'tours.planner.newTourConfirmTitle': 'Yeni tur başlansın?',
'tours.planner.newTourConfirmBody':
'Bu turda saxlanılmamış dəyişiklikləriniz ləğv ediləcək. Saxlanılmış turlar dəyişməyəcək.',
- 'tours.planner.newTourConfirm': 'Dəyişiklikləri ləğv et və yeni tur başlat',
+ 'tours.planner.newTourConfirm': 'Ləğv et və başla',
'tours.planner.collapseElevation': 'Yüksəklik profilini yığ',
'tours.planner.expandElevation': 'Yüksəklik profilini aç',
'tours.planner.name': 'Turun adı',
@@ -516,6 +516,16 @@ const tours: TranslationStrings = {
'Hər şeydən bir az: görməli yerlər, muzeylər, parklar, mənzərələr, kafelər və bazarlar.',
'tours.walk.themeHint.own': 'Aşağıda öz kateqoriya seçimin; mövzu onları sənin üçün qurur.',
'tours.walk.themeReason': '{theme} üçün təklif olunur',
+ 'tours.walk.arrival': 'Təxmini çatma {time}',
+ 'tours.walk.arrivalAtLeast': 'Ən tez {time} çatma',
+ 'tours.walk.hours.closed': 'Açılış saatlarına görə {arrival} vaxtı bağlıdır',
+ 'tours.walk.hours.closes': 'Çatma {arrival}, qalma {leave} qədər, amma {closes} vaxtı bağlanır',
+ 'tours.walk.hours.unread': 'Açılış saatları burada oxunmadı: {text}',
+ 'tours.walk.hours.source':
+ 'Açılış saatları OpenStreetMap-dakı kimi, təxmini çatma vaxtı ilə; getməzdən əvvəl yoxla. Saatı olmayan dayanacaq məlum deyil, açıq deyil.',
+ 'tours.walk.hours.noStart': 'Çatma vaxtlarını və açılış saatlarını görmək üçün gəzintiyə bu gün vaxt ver.',
+ 'tours.poi.categoriesNone': 'Kateqoriya seçilməyib',
+ 'tours.poi.categoriesMore': '{first} +{more}',
'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 2146724eb..c242a9823 100644
--- a/shared/src/i18n/br/tours.ts
+++ b/shared/src/i18n/br/tours.ts
@@ -110,7 +110,7 @@ const tours: TranslationStrings = {
'tours.planner.newTourConfirmTitle': 'Começar um novo passeio?',
'tours.planner.newTourConfirmBody':
'As alterações não salvas deste passeio serão descartadas. Os passeios salvos não serão alterados.',
- 'tours.planner.newTourConfirm': 'Descartar e começar novo passeio',
+ 'tours.planner.newTourConfirm': 'Descartar e começar',
'tours.planner.collapseElevation': 'Recolher perfil de elevação',
'tours.planner.expandElevation': 'Expandir perfil de elevação',
'tours.planner.name': 'Nome do passeio',
@@ -521,6 +521,16 @@ const tours: TranslationStrings = {
'tours.walk.themeHint.mixed': 'Um pouco de cada: pontos turísticos, museus, parques, vistas, cafeterias e feiras.',
'tours.walk.themeHint.own': 'Sua própria seleção de categorias abaixo; um tema as define para você.',
'tours.walk.themeReason': 'Sugerido para {theme}',
+ 'tours.walk.arrival': 'Chegada por volta de {time}',
+ 'tours.walk.arrivalAtLeast': 'Chegada a partir de {time}',
+ 'tours.walk.hours.closed': 'Fechado às {arrival} segundo o horário',
+ 'tours.walk.hours.closes': 'Chegada {arrival}, permanência até {leave}, mas fecha às {closes}',
+ 'tours.walk.hours.unread': 'Horário não lido aqui: {text}',
+ 'tours.walk.hours.source':
+ 'Horário como está no OpenStreetMap, comparado à chegada estimada; confira antes de ir. Uma parada sem horário é desconhecida, não aberta.',
+ 'tours.walk.hours.noStart': 'Dê ao passeio um horário neste dia para ver chegadas e horários.',
+ 'tours.poi.categoriesNone': 'Nenhuma categoria escolhida',
+ 'tours.poi.categoriesMore': '{first} +{more}',
'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 ec3a59565..1d4f94bb6 100644
--- a/shared/src/i18n/ca/tours.ts
+++ b/shared/src/i18n/ca/tours.ts
@@ -110,7 +110,7 @@ const tours: TranslationStrings = {
'tours.planner.newTourConfirmTitle': 'Vols començar una nova excursió?',
'tours.planner.newTourConfirmBody':
"Es descartaran els canvis sense desar d'aquesta excursió. Les excursions desades no es modificaran.",
- 'tours.planner.newTourConfirm': 'Descarta i comença una nova excursió',
+ 'tours.planner.newTourConfirm': 'Descarta i comença',
'tours.planner.collapseElevation': "Replega el perfil d'altitud",
'tours.planner.expandElevation': "Desplega el perfil d'altitud",
'tours.planner.name': "Nom de l'excursió",
@@ -522,6 +522,16 @@ const tours: TranslationStrings = {
'tours.walk.themeHint.mixed': "Una mica de cada: llocs d'interès, museus, parcs, vistes, cafeteries i mercats.",
'tours.walk.themeHint.own': 'La teva pròpia tria de categories a sota; un tema te les defineix.',
'tours.walk.themeReason': 'Suggerit per a {theme}',
+ 'tours.walk.arrival': 'Arribada cap a les {time}',
+ 'tours.walk.arrivalAtLeast': 'Arribada com a aviat a les {time}',
+ 'tours.walk.hours.closed': "Tancat a les {arrival} segons l'horari",
+ 'tours.walk.hours.closes': 'Arribada {arrival}, estada fins a les {leave}, però tanca a les {closes}',
+ 'tours.walk.hours.unread': 'Horari no llegit aquí: {text}',
+ 'tours.walk.hours.source':
+ "Horari tal com el té OpenStreetMap, contra l'arribada estimada; comprova-ho abans d'anar-hi. Una parada sense horari és desconeguda, no oberta.",
+ 'tours.walk.hours.noStart': 'Posa una hora a la ruta aquest dia per veure arribades i horaris.',
+ 'tours.poi.categoriesNone': 'Cap categoria triada',
+ 'tours.poi.categoriesMore': '{first} +{more}',
'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 202120300..11fdc06ae 100644
--- a/shared/src/i18n/cs/tours.ts
+++ b/shared/src/i18n/cs/tours.ts
@@ -109,7 +109,7 @@ const tours: TranslationStrings = {
'tours.planner.discardChanges': 'Zahodit změny',
'tours.planner.newTourConfirmTitle': 'Začít nový výlet?',
'tours.planner.newTourConfirmBody': 'Neuložené změny tohoto výletu budou zahozeny. Uložené výlety se nezmění.',
- 'tours.planner.newTourConfirm': 'Zahodit změny a začít nový výlet',
+ 'tours.planner.newTourConfirm': 'Zahodit a začít',
'tours.planner.collapseElevation': 'Sbalit výškový profil',
'tours.planner.expandElevation': 'Rozbalit výškový profil',
'tours.planner.name': 'Název výletu',
@@ -512,6 +512,16 @@ const tours: TranslationStrings = {
'tours.walk.themeHint.mixed': 'Od všeho trochu: památky, muzea, parky, výhledy, kavárny a trhy.',
'tours.walk.themeHint.own': 'Tvůj vlastní výběr kategorií níže; téma je nastaví za tebe.',
'tours.walk.themeReason': 'Navrženo pro {theme}',
+ 'tours.walk.arrival': 'Příchod kolem {time}',
+ 'tours.walk.arrivalAtLeast': 'Příchod nejdříve {time}',
+ 'tours.walk.hours.closed': 'V {arrival} podle otevírací doby zavřeno',
+ 'tours.walk.hours.closes': 'Příchod {arrival}, pobyt do {leave}, ale zavírá v {closes}',
+ 'tours.walk.hours.unread': 'Otevírací dobu zde nelze vyhodnotit: {text}',
+ 'tours.walk.hours.source':
+ 'Otevírací doba podle OpenStreetMap proti odhadovanému příchodu; před cestou si ji ověř. Zastávka bez časů je neznámá, ne otevřená.',
+ 'tours.walk.hours.noStart': 'Dej procházce v tento den čas, abys viděl příchody a otevírací doby.',
+ 'tours.poi.categoriesNone': 'Žádná kategorie',
+ 'tours.poi.categoriesMore': '{first} +{more}',
'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 d3e788ff5..644298a7b 100644
--- a/shared/src/i18n/de/tours.ts
+++ b/shared/src/i18n/de/tours.ts
@@ -111,7 +111,7 @@ const tours: TranslationStrings = {
'tours.planner.newTourConfirmTitle': 'Neue Tour beginnen?',
'tours.planner.newTourConfirmBody':
'Ungespeicherte Änderungen an dieser Tour gehen verloren. Gespeicherte Touren bleiben erhalten.',
- 'tours.planner.newTourConfirm': 'Änderungen verwerfen und neue Tour beginnen',
+ 'tours.planner.newTourConfirm': 'Verwerfen und neu',
'tours.planner.collapseElevation': 'Höhenprofil einklappen',
'tours.planner.expandElevation': 'Höhenprofil ausklappen',
'tours.planner.name': 'Name der Tour',
@@ -527,6 +527,16 @@ const tours: TranslationStrings = {
'tours.walk.themeHint.mixed': 'Von allem etwas: Sehenswürdigkeiten, Museen, Parks, Aussicht, Cafés und Märkte.',
'tours.walk.themeHint.own': 'Deine eigene Auswahl darunter; ein Thema stellt sie für dich zusammen.',
'tours.walk.themeReason': 'Vorgeschlagen für {theme}',
+ 'tours.walk.arrival': 'Ankunft ca. {time}',
+ 'tours.walk.arrivalAtLeast': 'Ankunft frühestens {time}',
+ 'tours.walk.hours.closed': 'Um {arrival} laut Öffnungszeiten geschlossen',
+ 'tours.walk.hours.closes': 'Ankunft {arrival}, Aufenthalt bis {leave}, aber Schließung um {closes}',
+ 'tours.walk.hours.unread': 'Öffnungszeiten hier nicht auswertbar: {text}',
+ 'tours.walk.hours.source':
+ 'Öffnungszeiten wie bei OpenStreetMap eingetragen, gegen die geschätzte Ankunft; vor Ort lieber prüfen. Eine Station ohne Zeiten ist unbekannt, nicht geöffnet.',
+ 'tours.walk.hours.noStart': 'Gib dem Walk an diesem Tag eine Uhrzeit, um Ankünfte und Öffnungszeiten zu sehen.',
+ 'tours.poi.categoriesNone': 'Keine Kategorie gewählt',
+ 'tours.poi.categoriesMore': '{first} +{more}',
'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 ccb4af1ac..4b1a6b73f 100644
--- a/shared/src/i18n/en/tours.ts
+++ b/shared/src/i18n/en/tours.ts
@@ -106,7 +106,7 @@ const tours: TranslationStrings = {
'tours.planner.newTourConfirmTitle': 'Start a new tour?',
'tours.planner.newTourConfirmBody':
'Your unsaved changes to this tour will be discarded. Saved tours will not be changed.',
- 'tours.planner.newTourConfirm': 'Discard changes and start new tour',
+ 'tours.planner.newTourConfirm': 'Discard and start',
'tours.planner.collapseElevation': 'Collapse elevation profile',
'tours.planner.expandElevation': 'Expand elevation profile',
'tours.planner.name': 'Tour name',
@@ -513,6 +513,16 @@ const tours: TranslationStrings = {
'tours.walk.themeHint.mixed': 'Some of each: sights, museums, parks, views, coffee shops and markets.',
'tours.walk.themeHint.own': 'Your own pick of categories below; a theme sets them for you.',
'tours.walk.themeReason': 'Suggested for {theme}',
+ 'tours.walk.arrival': 'Arrival about {time}',
+ 'tours.walk.arrivalAtLeast': 'Arrival {time} at the earliest',
+ 'tours.walk.hours.closed': 'Closed at {arrival} by its opening hours',
+ 'tours.walk.hours.closes': 'Arrival {arrival}, stay until {leave}, but it closes at {closes}',
+ 'tours.walk.hours.unread': 'Opening hours not read here: {text}',
+ 'tours.walk.hours.source':
+ 'Opening hours as OpenStreetMap has them, against the estimated arrival; check before you go. A stop without hours is unknown, not open.',
+ 'tours.walk.hours.noStart': 'Give the walk a time on this day to see arrivals and opening hours.',
+ 'tours.poi.categoriesNone': 'No category chosen',
+ 'tours.poi.categoriesMore': '{first} +{more}',
'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 103c7df54..e871b43d0 100644
--- a/shared/src/i18n/es/tours.ts
+++ b/shared/src/i18n/es/tours.ts
@@ -111,7 +111,7 @@ const tours: TranslationStrings = {
'tours.planner.newTourConfirmTitle': '¿Empezar una nueva excursión?',
'tours.planner.newTourConfirmBody':
'Se descartarán los cambios sin guardar de esta excursión. Las excursiones guardadas no se modificarán.',
- 'tours.planner.newTourConfirm': 'Descartar y empezar nueva excursión',
+ 'tours.planner.newTourConfirm': 'Descartar y empezar',
'tours.planner.collapseElevation': 'Contraer perfil de altitud',
'tours.planner.expandElevation': 'Expandir perfil de altitud',
'tours.planner.name': 'Nombre de la excursión',
@@ -520,6 +520,16 @@ const tours: TranslationStrings = {
'tours.walk.themeHint.mixed': 'Un poco de cada: lugares de interés, museos, parques, vistas, cafeterías y mercados.',
'tours.walk.themeHint.own': 'Tu propia selección de categorías abajo; un tema las elige por ti.',
'tours.walk.themeReason': 'Sugerido para {theme}',
+ 'tours.walk.arrival': 'Llegada hacia las {time}',
+ 'tours.walk.arrivalAtLeast': 'Llegada no antes de las {time}',
+ 'tours.walk.hours.closed': 'Cerrado a las {arrival} según su horario',
+ 'tours.walk.hours.closes': 'Llegada {arrival}, estancia hasta las {leave}, pero cierra a las {closes}',
+ 'tours.walk.hours.unread': 'Horario no interpretado aquí: {text}',
+ 'tours.walk.hours.source':
+ 'Horario tal como figura en OpenStreetMap, frente a la llegada estimada; compruébalo antes de ir. Una parada sin horario es desconocida, no abierta.',
+ 'tours.walk.hours.noStart': 'Pon al paseo una hora en este día para ver llegadas y horarios.',
+ 'tours.poi.categoriesNone': 'Ninguna categoría elegida',
+ 'tours.poi.categoriesMore': '{first} +{more}',
'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 97a12e18d..42edb375a 100644
--- a/shared/src/i18n/et/tours.ts
+++ b/shared/src/i18n/et/tours.ts
@@ -107,7 +107,7 @@ const tours: TranslationStrings = {
'tours.planner.newTourConfirmTitle': 'Kas alustada uut retke?',
'tours.planner.newTourConfirmBody':
'Selle retke salvestamata muudatused lähevad kaotsi. Salvestatud retki ei muudeta.',
- 'tours.planner.newTourConfirm': 'Loobu muudatustest ja alusta uut retke',
+ 'tours.planner.newTourConfirm': 'Loobu ja alusta',
'tours.planner.collapseElevation': 'Ahenda kõrgusprofiil',
'tours.planner.expandElevation': 'Laienda kõrgusprofiil',
'tours.planner.name': 'Retke nimi',
@@ -515,6 +515,16 @@ const tours: TranslationStrings = {
'tours.walk.themeHint.mixed': 'Igaühest natuke: vaatamisväärsused, muuseumid, pargid, vaated, kohvikud ja turud.',
'tours.walk.themeHint.own': 'Sinu enda kategooriate valik allpool; teema seab need sinu eest.',
'tours.walk.themeReason': 'Soovitatud teemale {theme}',
+ 'tours.walk.arrival': 'Saabumine umbes {time}',
+ 'tours.walk.arrivalAtLeast': 'Saabumine kõige varem {time}',
+ 'tours.walk.hours.closed': 'Lahtiolekuaegade järgi kell {arrival} suletud',
+ 'tours.walk.hours.closes': 'Saabumine {arrival}, peatus kuni {leave}, aga suletakse kell {closes}',
+ 'tours.walk.hours.unread': 'Lahtiolekuaegu siin ei loetud: {text}',
+ 'tours.walk.hours.source':
+ 'Lahtiolekuajad nagu OpenStreetMapis, võrreldes hinnangulise saabumisega; kontrolli enne minekut. Aegadeta peatus on teadmata, mitte avatud.',
+ 'tours.walk.hours.noStart': 'Anna jalutusele sel päeval kellaaeg, et näha saabumisi ja lahtiolekuaegu.',
+ 'tours.poi.categoriesNone': 'Kategooriat pole valitud',
+ 'tours.poi.categoriesMore': '{first} +{more}',
'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 cca0bb7b6..3e1133ca2 100644
--- a/shared/src/i18n/fr/tours.ts
+++ b/shared/src/i18n/fr/tours.ts
@@ -113,7 +113,7 @@ const tours: TranslationStrings = {
'tours.planner.newTourConfirmTitle': 'Commencer une nouvelle sortie ?',
'tours.planner.newTourConfirmBody':
'Les modifications non enregistrées de cette sortie seront perdues. Les sorties enregistrées resteront inchangées.',
- 'tours.planner.newTourConfirm': 'Abandonner et commencer une nouvelle sortie',
+ 'tours.planner.newTourConfirm': 'Abandonner et commencer',
'tours.planner.collapseElevation': 'Réduire le profil altimétrique',
'tours.planner.expandElevation': 'Déployer le profil altimétrique',
'tours.planner.name': 'Nom de la sortie',
@@ -530,6 +530,16 @@ const tours: TranslationStrings = {
'tours.walk.themeHint.mixed': 'Un peu de chaque : sites, musées, parcs, points de vue, cafés et marchés.',
'tours.walk.themeHint.own': 'Ta propre sélection de catégories ci-dessous ; un thème les choisit pour toi.',
'tours.walk.themeReason': 'Suggéré pour {theme}',
+ 'tours.walk.arrival': 'Arrivée vers {time}',
+ 'tours.walk.arrivalAtLeast': 'Arrivée au plus tôt à {time}',
+ 'tours.walk.hours.closed': 'Fermé à {arrival} selon ses horaires',
+ 'tours.walk.hours.closes': "Arrivée {arrival}, arrêt jusqu'à {leave}, mais fermeture à {closes}",
+ 'tours.walk.hours.unread': 'Horaires non lus ici : {text}',
+ 'tours.walk.hours.source':
+ "Horaires tels qu'indiqués sur OpenStreetMap, face à l'arrivée estimée ; vérifie avant d'y aller. Une étape sans horaires est inconnue, pas ouverte.",
+ 'tours.walk.hours.noStart': 'Donne une heure à la balade ce jour-là pour voir les arrivées et les horaires.',
+ 'tours.poi.categoriesNone': 'Aucune catégorie choisie',
+ 'tours.poi.categoriesMore': '{first} +{more}',
'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 ba06abcf2..ddcd3b758 100644
--- a/shared/src/i18n/gr/tours.ts
+++ b/shared/src/i18n/gr/tours.ts
@@ -111,7 +111,7 @@ const tours: TranslationStrings = {
'tours.planner.newTourConfirmTitle': 'Έναρξη νέας εκδρομής;',
'tours.planner.newTourConfirmBody':
'Οι μη αποθηκευμένες αλλαγές αυτής της εκδρομής θα απορριφθούν. Οι αποθηκευμένες εκδρομές δεν θα αλλάξουν.',
- 'tours.planner.newTourConfirm': 'Απόρριψη αλλαγών και έναρξη νέας εκδρομής',
+ 'tours.planner.newTourConfirm': 'Απόρριψη και έναρξη',
'tours.planner.collapseElevation': 'Σύμπτυξη υψομετρικού προφίλ',
'tours.planner.expandElevation': 'Ανάπτυξη υψομετρικού προφίλ',
'tours.planner.name': 'Όνομα εκδρομής',
@@ -530,6 +530,16 @@ const tours: TranslationStrings = {
'tours.walk.themeHint.mixed': "Λίγο απ' όλα: αξιοθέατα, μουσεία, πάρκα, θέα, καφετέριες και αγορές.",
'tours.walk.themeHint.own': 'Η δική σου επιλογή κατηγοριών παρακάτω· ένα θέμα τις ορίζει για σένα.',
'tours.walk.themeReason': 'Προτείνεται για {theme}',
+ 'tours.walk.arrival': 'Άφιξη περίπου {time}',
+ 'tours.walk.arrivalAtLeast': 'Άφιξη το νωρίτερο {time}',
+ 'tours.walk.hours.closed': 'Κλειστό στις {arrival} σύμφωνα με το ωράριο',
+ 'tours.walk.hours.closes': 'Άφιξη {arrival}, παραμονή έως {leave}, αλλά κλείνει στις {closes}',
+ 'tours.walk.hours.unread': 'Το ωράριο δεν διαβάστηκε εδώ: {text}',
+ 'tours.walk.hours.source':
+ 'Ωράριο όπως στο OpenStreetMap, σε σχέση με την εκτιμώμενη άφιξη· έλεγξε πριν πας. Στάση χωρίς ωράριο είναι άγνωστη, όχι ανοιχτή.',
+ 'tours.walk.hours.noStart': 'Δώσε ώρα στη βόλτα αυτή τη μέρα για να δεις αφίξεις και ωράρια.',
+ 'tours.poi.categoriesNone': 'Δεν επιλέχθηκε κατηγορία',
+ 'tours.poi.categoriesMore': '{first} +{more}',
'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 b6c32925b..02a5c0b9c 100644
--- a/shared/src/i18n/hu/tours.ts
+++ b/shared/src/i18n/hu/tours.ts
@@ -107,7 +107,7 @@ const tours: TranslationStrings = {
'tours.planner.discardChanges': 'Módosítások elvetése',
'tours.planner.newTourConfirmTitle': 'Új túrát kezdesz?',
'tours.planner.newTourConfirmBody': 'A túra nem mentett módosításai elvesznek. A mentett túrák nem változnak.',
- 'tours.planner.newTourConfirm': 'Módosítások elvetése és új túra kezdése',
+ 'tours.planner.newTourConfirm': 'Elvetés és új',
'tours.planner.collapseElevation': 'Magassági profil összecsukása',
'tours.planner.expandElevation': 'Magassági profil kibontása',
'tours.planner.name': 'Túra neve',
@@ -522,6 +522,16 @@ const tours: TranslationStrings = {
'tours.walk.themeHint.mixed': 'Mindenből egy kicsit: látnivalók, múzeumok, parkok, kilátás, kávézók és piacok.',
'tours.walk.themeHint.own': 'Saját kategóriaválasztásod lent; egy téma beállítja helyetted.',
'tours.walk.themeReason': 'Javasolva: {theme}',
+ 'tours.walk.arrival': 'Érkezés kb. {time}',
+ 'tours.walk.arrivalAtLeast': 'Érkezés legkorábban {time}',
+ 'tours.walk.hours.closed': '{arrival} órakor a nyitvatartás szerint zárva',
+ 'tours.walk.hours.closes': 'Érkezés {arrival}, tartózkodás {leave}-ig, de {closes}-kor zár',
+ 'tours.walk.hours.unread': 'A nyitvatartás itt nem értelmezhető: {text}',
+ 'tours.walk.hours.source':
+ 'Nyitvatartás az OpenStreetMap szerint, a becsült érkezéshez mérve; indulás előtt ellenőrizd. Nyitvatartás nélküli megálló ismeretlen, nem nyitott.',
+ 'tours.walk.hours.noStart': 'Adj időpontot a sétának ezen a napon, hogy lásd az érkezéseket és a nyitvatartást.',
+ 'tours.poi.categoriesNone': 'Nincs kategória kiválasztva',
+ 'tours.poi.categoriesMore': '{first} +{more}',
'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 9703c47c1..446319e79 100644
--- a/shared/src/i18n/id/tours.ts
+++ b/shared/src/i18n/id/tours.ts
@@ -105,7 +105,7 @@ const tours: TranslationStrings = {
'tours.planner.newTourConfirmTitle': 'Mulai tur baru?',
'tours.planner.newTourConfirmBody':
'Perubahan yang belum disimpan pada tur ini akan dibuang. Tur yang sudah disimpan tidak akan diubah.',
- 'tours.planner.newTourConfirm': 'Buang perubahan dan mulai tur baru',
+ 'tours.planner.newTourConfirm': 'Buang dan mulai',
'tours.planner.collapseElevation': 'Ciutkan profil elevasi',
'tours.planner.expandElevation': 'Perluas profil elevasi',
'tours.planner.name': 'Nama tur',
@@ -515,6 +515,16 @@ const tours: TranslationStrings = {
'Sedikit dari semuanya: tempat wisata, museum, taman, pemandangan, kedai kopi, dan pasar.',
'tours.walk.themeHint.own': 'Pilihan kategorimu sendiri di bawah; tema mengaturnya untukmu.',
'tours.walk.themeReason': 'Disarankan untuk {theme}',
+ 'tours.walk.arrival': 'Tiba sekitar {time}',
+ 'tours.walk.arrivalAtLeast': 'Tiba paling cepat {time}',
+ 'tours.walk.hours.closed': 'Tutup pada {arrival} menurut jam bukanya',
+ 'tours.walk.hours.closes': 'Tiba {arrival}, singgah sampai {leave}, tetapi tutup pukul {closes}',
+ 'tours.walk.hours.unread': 'Jam buka tidak terbaca di sini: {text}',
+ 'tours.walk.hours.source':
+ 'Jam buka sesuai OpenStreetMap, dibandingkan dengan perkiraan tiba; periksa sebelum berangkat. Titik tanpa jam buka tidak diketahui, bukan buka.',
+ 'tours.walk.hours.noStart': 'Beri rute waktu mulai di hari ini untuk melihat waktu tiba dan jam buka.',
+ 'tours.poi.categoriesNone': 'Belum ada kategori',
+ 'tours.poi.categoriesMore': '{first} +{more}',
'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 5613ec331..97083c9fc 100644
--- a/shared/src/i18n/it/tours.ts
+++ b/shared/src/i18n/it/tours.ts
@@ -109,7 +109,7 @@ const tours: TranslationStrings = {
'tours.planner.newTourConfirmTitle': 'Iniziare una nuova escursione?',
'tours.planner.newTourConfirmBody':
'Le modifiche non salvate a questa escursione verranno scartate. Le escursioni salvate non verranno modificate.',
- 'tours.planner.newTourConfirm': 'Scarta e inizia nuova escursione',
+ 'tours.planner.newTourConfirm': 'Scarta e inizia',
'tours.planner.collapseElevation': 'Comprimi profilo altimetrico',
'tours.planner.expandElevation': 'Espandi profilo altimetrico',
'tours.planner.name': "Nome dell'escursione",
@@ -521,6 +521,16 @@ const tours: TranslationStrings = {
'tours.walk.themeHint.mixed': "Un po' di tutto: attrazioni, musei, parchi, panorami, caffetterie e mercati.",
'tours.walk.themeHint.own': 'La tua scelta di categorie qui sotto; un tema le imposta per te.',
'tours.walk.themeReason': 'Suggerito per {theme}',
+ 'tours.walk.arrival': 'Arrivo verso le {time}',
+ 'tours.walk.arrivalAtLeast': 'Arrivo non prima delle {time}',
+ 'tours.walk.hours.closed': 'Chiuso alle {arrival} secondo gli orari',
+ 'tours.walk.hours.closes': 'Arrivo {arrival}, sosta fino alle {leave}, ma chiude alle {closes}',
+ 'tours.walk.hours.unread': 'Orari non interpretati qui: {text}',
+ 'tours.walk.hours.source':
+ "Orari come riportati su OpenStreetMap, rispetto all'arrivo stimato; verifica prima di andare. Una tappa senza orari è sconosciuta, non aperta.",
+ 'tours.walk.hours.noStart': 'Dai un orario al percorso in questo giorno per vedere arrivi e orari.',
+ 'tours.poi.categoriesNone': 'Nessuna categoria scelta',
+ 'tours.poi.categoriesMore': '{first} +{more}',
'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 621c483fe..45c7ca170 100644
--- a/shared/src/i18n/ja/tours.ts
+++ b/shared/src/i18n/ja/tours.ts
@@ -103,7 +103,7 @@ const tours: TranslationStrings = {
'tours.planner.discardChanges': '変更を破棄',
'tours.planner.newTourConfirmTitle': '新しいツアーを開始しますか?',
'tours.planner.newTourConfirmBody': 'このツアーの未保存の変更は破棄されます。保存済みのツアーは変更されません。',
- 'tours.planner.newTourConfirm': '変更を破棄して新しいツアーを開始',
+ 'tours.planner.newTourConfirm': '破棄して開始',
'tours.planner.collapseElevation': '標高グラフを折りたたむ',
'tours.planner.expandElevation': '標高グラフを展開',
'tours.planner.name': 'ツアー名',
@@ -507,6 +507,16 @@ const tours: TranslationStrings = {
'tours.walk.themeHint.mixed': '観光名所、美術館・博物館、公園、眺望、カフェ、市場を少しずつ。',
'tours.walk.themeHint.own': '下のカテゴリーは自分で選択中。テーマを選ぶと自動で設定されます。',
'tours.walk.themeReason': '{theme} のおすすめ',
+ 'tours.walk.arrival': '到着 {time} ごろ',
+ 'tours.walk.arrivalAtLeast': '到着は早くても {time}',
+ 'tours.walk.hours.closed': '営業時間では {arrival} は閉まっています',
+ 'tours.walk.hours.closes': '{arrival} 到着、{leave} まで滞在予定ですが {closes} に閉まります',
+ 'tours.walk.hours.unread': '営業時間はここでは読み取れません:{text}',
+ 'tours.walk.hours.source':
+ 'OpenStreetMap の営業時間を推定到着時刻と照合しています。出かける前に確認してください。営業時間のない立ち寄り先は「不明」で、営業中とはみなしません。',
+ 'tours.walk.hours.noStart': 'この日の開始時刻を設定すると、到着時刻と営業時間が表示されます。',
+ 'tours.poi.categoriesNone': 'カテゴリー未選択',
+ 'tours.poi.categoriesMore': '{first} +{more}',
'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 9b09af9cb..c8f6735ec 100644
--- a/shared/src/i18n/ko/tours.ts
+++ b/shared/src/i18n/ko/tours.ts
@@ -101,7 +101,7 @@ const tours: TranslationStrings = {
'tours.planner.newTourConfirmTitle': '새 투어를 시작할까요?',
'tours.planner.newTourConfirmBody':
'이 투어의 저장하지 않은 변경 사항은 삭제됩니다. 저장된 투어는 변경되지 않습니다.',
- 'tours.planner.newTourConfirm': '변경 사항을 버리고 새 투어 시작',
+ 'tours.planner.newTourConfirm': '버리고 시작',
'tours.planner.collapseElevation': '고도 그래프 접기',
'tours.planner.expandElevation': '고도 그래프 펼치기',
'tours.planner.name': '투어 이름',
@@ -502,6 +502,16 @@ const tours: TranslationStrings = {
'tours.walk.themeHint.mixed': '명소, 박물관, 공원, 전망, 카페, 시장을 골고루.',
'tours.walk.themeHint.own': '아래 카테고리는 직접 고른 것입니다. 테마를 고르면 자동으로 설정됩니다.',
'tours.walk.themeReason': '{theme} 추천',
+ 'tours.walk.arrival': '도착 약 {time}',
+ 'tours.walk.arrivalAtLeast': '도착 빨라야 {time}',
+ 'tours.walk.hours.closed': '영업시간상 {arrival}에는 문을 닫습니다',
+ 'tours.walk.hours.closes': '{arrival} 도착, {leave}까지 머물 예정이지만 {closes}에 문을 닫습니다',
+ 'tours.walk.hours.unread': '여기서는 영업시간을 읽지 못했습니다: {text}',
+ 'tours.walk.hours.source':
+ 'OpenStreetMap의 영업시간을 예상 도착 시간과 비교합니다. 가기 전에 확인하세요. 영업시간이 없는 정류지는 영업 중이 아니라 미상입니다.',
+ 'tours.walk.hours.noStart': '이날 산책에 시간을 지정하면 도착 시간과 영업시간을 볼 수 있습니다.',
+ 'tours.poi.categoriesNone': '선택한 카테고리 없음',
+ 'tours.poi.categoriesMore': '{first} +{more}',
'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 2fc7cd096..30f8cba04 100644
--- a/shared/src/i18n/nl/tours.ts
+++ b/shared/src/i18n/nl/tours.ts
@@ -111,7 +111,7 @@ const tours: TranslationStrings = {
'tours.planner.newTourConfirmTitle': 'Een nieuwe tocht starten?',
'tours.planner.newTourConfirmBody':
'Je niet-opgeslagen wijzigingen aan deze tocht worden verworpen. Opgeslagen tochten blijven ongewijzigd.',
- 'tours.planner.newTourConfirm': 'Verwerpen en nieuwe tocht starten',
+ 'tours.planner.newTourConfirm': 'Weggooien en beginnen',
'tours.planner.collapseElevation': 'Hoogteprofiel inklappen',
'tours.planner.expandElevation': 'Hoogteprofiel uitklappen',
'tours.planner.name': 'Naam van de tocht',
@@ -525,6 +525,16 @@ const tours: TranslationStrings = {
'tours.walk.themeHint.mixed': 'Van alles wat: bezienswaardigheden, musea, parken, uitzicht, koffiezaken en markten.',
'tours.walk.themeHint.own': 'Je eigen keuze van categorieën hieronder; een thema stelt ze voor je in.',
'tours.walk.themeReason': 'Voorgesteld voor {theme}',
+ 'tours.walk.arrival': 'Aankomst rond {time}',
+ 'tours.walk.arrivalAtLeast': 'Aankomst op zijn vroegst {time}',
+ 'tours.walk.hours.closed': 'Om {arrival} gesloten volgens de openingstijden',
+ 'tours.walk.hours.closes': 'Aankomst {arrival}, verblijf tot {leave}, maar sluit om {closes}',
+ 'tours.walk.hours.unread': 'Openingstijden hier niet gelezen: {text}',
+ 'tours.walk.hours.source':
+ 'Openingstijden zoals in OpenStreetMap, tegen de geschatte aankomst; controleer ze voor je gaat. Een halte zonder tijden is onbekend, niet open.',
+ 'tours.walk.hours.noStart': 'Geef de wandeling op deze dag een tijd om aankomsten en openingstijden te zien.',
+ 'tours.poi.categoriesNone': 'Geen categorie gekozen',
+ 'tours.poi.categoriesMore': '{first} +{more}',
'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 76e2b88e7..0fe21cf39 100644
--- a/shared/src/i18n/pl/tours.ts
+++ b/shared/src/i18n/pl/tours.ts
@@ -117,7 +117,7 @@ const tours: TranslationStrings = {
'tours.planner.newTourConfirmTitle': 'Rozpocząć nową wycieczkę?',
'tours.planner.newTourConfirmBody':
'Niezapisane zmiany tej wycieczki zostaną odrzucone. Zapisane wycieczki nie zostaną zmienione.',
- 'tours.planner.newTourConfirm': 'Odrzuć zmiany i rozpocznij nową wycieczkę',
+ 'tours.planner.newTourConfirm': 'Odrzuć i zacznij',
'tours.planner.collapseElevation': 'Zwiń profil wysokościowy',
'tours.planner.expandElevation': 'Rozwiń profil wysokościowy',
'tours.planner.name': 'Nazwa wycieczki',
@@ -529,6 +529,16 @@ const tours: TranslationStrings = {
'tours.walk.themeHint.mixed': 'Po trochu wszystkiego: atrakcje, muzea, parki, widoki, kawiarnie i targi.',
'tours.walk.themeHint.own': 'Twój własny wybór kategorii poniżej; motyw ustawi je za ciebie.',
'tours.walk.themeReason': 'Proponowane dla: {theme}',
+ 'tours.walk.arrival': 'Przyjście około {time}',
+ 'tours.walk.arrivalAtLeast': 'Przyjście najwcześniej {time}',
+ 'tours.walk.hours.closed': 'Według godzin otwarcia o {arrival} zamknięte',
+ 'tours.walk.hours.closes': 'Przyjście {arrival}, pobyt do {leave}, ale zamknięcie o {closes}',
+ 'tours.walk.hours.unread': 'Godziny otwarcia nieodczytane: {text}',
+ 'tours.walk.hours.source':
+ 'Godziny otwarcia z OpenStreetMap wobec szacowanego przyjścia; sprawdź przed wyjściem. Przystanek bez godzin jest nieznany, nie otwarty.',
+ 'tours.walk.hours.noStart': 'Ustaw godzinę trasy tego dnia, aby zobaczyć przyjścia i godziny otwarcia.',
+ 'tours.poi.categoriesNone': 'Nie wybrano kategorii',
+ 'tours.poi.categoriesMore': '{first} +{more}',
'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 970580999..c4216a7dd 100644
--- a/shared/src/i18n/ru/tours.ts
+++ b/shared/src/i18n/ru/tours.ts
@@ -118,7 +118,7 @@ const tours: TranslationStrings = {
'tours.planner.newTourConfirmTitle': 'Начать новый тур?',
'tours.planner.newTourConfirmBody':
'Несохранённые изменения этого тура будут отменены. Сохранённые туры не изменятся.',
- 'tours.planner.newTourConfirm': 'Отменить изменения и начать новый тур',
+ 'tours.planner.newTourConfirm': 'Сбросить и начать',
'tours.planner.collapseElevation': 'Свернуть профиль высот',
'tours.planner.expandElevation': 'Развернуть профиль высот',
'tours.planner.name': 'Название тура',
@@ -532,6 +532,16 @@ const tours: TranslationStrings = {
'tours.walk.themeHint.mixed': 'Всего понемногу: достопримечательности, музеи, парки, виды, кафе и рынки.',
'tours.walk.themeHint.own': 'Твой собственный выбор категорий ниже; тема задаст их за тебя.',
'tours.walk.themeReason': 'Предложено для темы {theme}',
+ 'tours.walk.arrival': 'Прибытие около {time}',
+ 'tours.walk.arrivalAtLeast': 'Прибытие не раньше {time}',
+ 'tours.walk.hours.closed': 'По часам работы в {arrival} закрыто',
+ 'tours.walk.hours.closes': 'Прибытие {arrival}, остановка до {leave}, но закрывается в {closes}',
+ 'tours.walk.hours.unread': 'Часы работы здесь не разобраны: {text}',
+ 'tours.walk.hours.source':
+ 'Часы работы как в OpenStreetMap, относительно расчётного прибытия; проверь перед выходом. Остановка без часов неизвестна, а не открыта.',
+ 'tours.walk.hours.noStart': 'Задай прогулке время в этот день, чтобы видеть прибытия и часы работы.',
+ 'tours.poi.categoriesNone': 'Категория не выбрана',
+ 'tours.poi.categoriesMore': '{first} +{more}',
'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 736c457d8..35c9d1b4a 100644
--- a/shared/src/i18n/sk/tours.ts
+++ b/shared/src/i18n/sk/tours.ts
@@ -110,7 +110,7 @@ const tours: TranslationStrings = {
'tours.planner.discardChanges': 'Zahodiť zmeny',
'tours.planner.newTourConfirmTitle': 'Začať novú túru?',
'tours.planner.newTourConfirmBody': 'Neuložené zmeny tejto túry sa zahodia. Uložené túry zostanú nezmenené.',
- 'tours.planner.newTourConfirm': 'Zahodiť zmeny a začať novú túru',
+ 'tours.planner.newTourConfirm': 'Zahodiť a začať',
'tours.planner.collapseElevation': 'Zbaliť výškový profil',
'tours.planner.expandElevation': 'Rozbaliť výškový profil',
'tours.planner.name': 'Názov túry',
@@ -514,6 +514,16 @@ const tours: TranslationStrings = {
'tours.walk.themeHint.mixed': 'Od všetkého trochu: pamiatky, múzeá, parky, výhľady, kaviarne a trhy.',
'tours.walk.themeHint.own': 'Tvoj vlastný výber kategórií nižšie; téma ich nastaví za teba.',
'tours.walk.themeReason': 'Navrhnuté pre {theme}',
+ 'tours.walk.arrival': 'Príchod okolo {time}',
+ 'tours.walk.arrivalAtLeast': 'Príchod najskôr {time}',
+ 'tours.walk.hours.closed': 'Podľa otváracích hodín o {arrival} zatvorené',
+ 'tours.walk.hours.closes': 'Príchod {arrival}, pobyt do {leave}, ale zatvára o {closes}',
+ 'tours.walk.hours.unread': 'Otváracie hodiny tu nemožno vyhodnotiť: {text}',
+ 'tours.walk.hours.source':
+ 'Otváracie hodiny podľa OpenStreetMap oproti odhadovanému príchodu; pred cestou si ich over. Zastávka bez časov je neznáma, nie otvorená.',
+ 'tours.walk.hours.noStart': 'Daj prechádzke v tento deň čas, aby si videl príchody a otváracie hodiny.',
+ 'tours.poi.categoriesNone': 'Žiadna kategória',
+ 'tours.poi.categoriesMore': '{first} +{more}',
'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 09ca72793..834725954 100644
--- a/shared/src/i18n/sv/tours.ts
+++ b/shared/src/i18n/sv/tours.ts
@@ -106,7 +106,7 @@ const tours: TranslationStrings = {
'tours.planner.discardChanges': 'Kasta ändringar',
'tours.planner.newTourConfirmTitle': 'Börja på en ny tur?',
'tours.planner.newTourConfirmBody': 'Dina osparade ändringar i den här turen kastas. Sparade turer ändras inte.',
- 'tours.planner.newTourConfirm': 'Kasta ändringar och börja på ny tur',
+ 'tours.planner.newTourConfirm': 'Släng och börja',
'tours.planner.collapseElevation': 'Fäll ihop höjdprofil',
'tours.planner.expandElevation': 'Fäll ut höjdprofil',
'tours.planner.name': 'Turens namn',
@@ -513,6 +513,16 @@ const tours: TranslationStrings = {
'tours.walk.themeHint.mixed': 'Lite av allt: sevärdheter, museer, parker, utsikter, kaféer och torghandel.',
'tours.walk.themeHint.own': 'Ditt eget urval av kategorier nedan; ett tema väljer dem åt dig.',
'tours.walk.themeReason': 'Föreslaget för {theme}',
+ 'tours.walk.arrival': 'Ankomst runt {time}',
+ 'tours.walk.arrivalAtLeast': 'Ankomst tidigast {time}',
+ 'tours.walk.hours.closed': 'Stängt kl. {arrival} enligt öppettiderna',
+ 'tours.walk.hours.closes': 'Ankomst {arrival}, vistelse till {leave}, men stänger kl. {closes}',
+ 'tours.walk.hours.unread': 'Öppettiderna kunde inte läsas här: {text}',
+ 'tours.walk.hours.source':
+ 'Öppettider enligt OpenStreetMap, mot beräknad ankomst; kontrollera innan du går. Ett stopp utan tider är okänt, inte öppet.',
+ 'tours.walk.hours.noStart': 'Ge promenaden en tid den här dagen för att se ankomster och öppettider.',
+ 'tours.poi.categoriesNone': 'Ingen kategori vald',
+ 'tours.poi.categoriesMore': '{first} +{more}',
'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 04f1659e0..6db7489f9 100644
--- a/shared/src/i18n/th/tours.ts
+++ b/shared/src/i18n/th/tours.ts
@@ -100,7 +100,7 @@ const tours: TranslationStrings = {
'tours.planner.newTourConfirmTitle': 'เริ่มทัวร์ใหม่ไหม?',
'tours.planner.newTourConfirmBody':
'การเปลี่ยนแปลงของทัวร์นี้ที่ยังไม่บันทึกจะถูกทิ้ง ทัวร์ที่บันทึกแล้วจะไม่เปลี่ยนแปลง',
- 'tours.planner.newTourConfirm': 'ทิ้งการเปลี่ยนแปลงและเริ่มทัวร์ใหม่',
+ 'tours.planner.newTourConfirm': 'ทิ้งแล้วเริ่มใหม่',
'tours.planner.collapseElevation': 'ยุบกราฟระดับความสูง',
'tours.planner.expandElevation': 'ขยายกราฟระดับความสูง',
'tours.planner.name': 'ชื่อทัวร์',
@@ -496,6 +496,16 @@ const tours: TranslationStrings = {
'tours.walk.themeHint.mixed': 'มีทุกอย่าง: สถานที่น่าสนใจ พิพิธภัณฑ์ สวน จุดชมวิว ร้านกาแฟ และตลาด',
'tours.walk.themeHint.own': 'หมวดหมู่ด้านล่างเลือกเอง ธีมจะตั้งค่าให้อัตโนมัติ',
'tours.walk.themeReason': 'แนะนำสำหรับ {theme}',
+ 'tours.walk.arrival': 'ถึงประมาณ {time}',
+ 'tours.walk.arrivalAtLeast': 'ถึงเร็วที่สุด {time}',
+ 'tours.walk.hours.closed': 'ตามเวลาทำการ ปิดเวลา {arrival}',
+ 'tours.walk.hours.closes': 'ถึง {arrival} แวะถึง {leave} แต่ปิด {closes}',
+ 'tours.walk.hours.unread': 'อ่านเวลาทำการไม่ได้: {text}',
+ 'tours.walk.hours.source':
+ 'เวลาทำการตาม OpenStreetMap เทียบกับเวลาถึงโดยประมาณ โปรดตรวจสอบก่อนไป จุดแวะที่ไม่มีเวลาทำการถือว่าไม่ทราบ ไม่ใช่เปิดอยู่',
+ 'tours.walk.hours.noStart': 'กำหนดเวลาให้ทริปในวันนี้เพื่อดูเวลาถึงและเวลาทำการ',
+ 'tours.poi.categoriesNone': 'ยังไม่ได้เลือกหมวดหมู่',
+ 'tours.poi.categoriesMore': '{first} +{more}',
'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 d7494d8f1..fa814cb47 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 = 756;
+const GENERAL_KEY_COUNT = 765;
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 f14ab3610..32831b96c 100644
--- a/shared/src/i18n/tr/tours.ts
+++ b/shared/src/i18n/tr/tours.ts
@@ -106,7 +106,7 @@ const tours: TranslationStrings = {
'tours.planner.newTourConfirmTitle': 'Yeni bir tur başlatılsın mı?',
'tours.planner.newTourConfirmBody':
'Bu turdaki kaydedilmemiş değişiklikler silinecek. Kaydedilmiş turlar değişmeyecek.',
- 'tours.planner.newTourConfirm': 'Değişiklikleri sil ve yeni tur başlat',
+ 'tours.planner.newTourConfirm': 'Vazgeç ve başla',
'tours.planner.collapseElevation': 'Yükseklik profilini daralt',
'tours.planner.expandElevation': 'Yükseklik profilini genişlet',
'tours.planner.name': 'Tur adı',
@@ -517,6 +517,16 @@ const tours: TranslationStrings = {
'Her şeyden biraz: görülecek yerler, müzeler, parklar, manzaralar, kafeler ve pazarlar.',
'tours.walk.themeHint.own': 'Aşağıdaki kategorileri kendin seçtin; bir tema onları senin için ayarlar.',
'tours.walk.themeReason': '{theme} için önerildi',
+ 'tours.walk.arrival': 'Varış yaklaşık {time}',
+ 'tours.walk.arrivalAtLeast': 'En erken varış {time}',
+ 'tours.walk.hours.closed': 'Açılış saatlerine göre {arrival} saatinde kapalı',
+ 'tours.walk.hours.closes': 'Varış {arrival}, kalış {leave} saatine kadar, ama {closes} saatinde kapanıyor',
+ 'tours.walk.hours.unread': 'Açılış saatleri burada okunamadı: {text}',
+ 'tours.walk.hours.source':
+ "Açılış saatleri OpenStreetMap'teki gibi, tahmini varışa göre; gitmeden önce kontrol et. Saati olmayan durak bilinmiyordur, açık değildir.",
+ 'tours.walk.hours.noStart': 'Varışları ve açılış saatlerini görmek için yürüyüşe bu gün bir saat ver.',
+ 'tours.poi.categoriesNone': 'Kategori seçilmedi',
+ 'tours.poi.categoriesMore': '{first} +{more}',
'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 e2a49feac..5b8d50879 100644
--- a/shared/src/i18n/uk/tours.ts
+++ b/shared/src/i18n/uk/tours.ts
@@ -115,7 +115,7 @@ const tours: TranslationStrings = {
'tours.planner.discardChanges': 'Скасувати зміни',
'tours.planner.newTourConfirmTitle': 'Почати новий тур?',
'tours.planner.newTourConfirmBody': 'Незбережені зміни цього туру буде скасовано. Збережені тури не зміняться.',
- 'tours.planner.newTourConfirm': 'Скасувати зміни й почати новий тур',
+ 'tours.planner.newTourConfirm': 'Скинути й почати',
'tours.planner.collapseElevation': 'Згорнути профіль висот',
'tours.planner.expandElevation': 'Розгорнути профіль висот',
'tours.planner.name': 'Назва туру',
@@ -528,6 +528,16 @@ const tours: TranslationStrings = {
'tours.walk.themeHint.mixed': "Усього потроху: пам'ятки, музеї, парки, краєвиди, кав'ярні та ринки.",
'tours.walk.themeHint.own': 'Твій власний вибір категорій нижче; тема задасть їх за тебе.',
'tours.walk.themeReason': 'Запропоновано для теми {theme}',
+ 'tours.walk.arrival': 'Прибуття близько {time}',
+ 'tours.walk.arrivalAtLeast': 'Прибуття не раніше {time}',
+ 'tours.walk.hours.closed': 'За годинами роботи о {arrival} зачинено',
+ 'tours.walk.hours.closes': 'Прибуття {arrival}, перебування до {leave}, але зачиняється о {closes}',
+ 'tours.walk.hours.unread': 'Години роботи тут не розібрано: {text}',
+ 'tours.walk.hours.source':
+ 'Години роботи як в OpenStreetMap, відносно розрахункового прибуття; перевір перед виходом. Зупинка без годин невідома, а не відкрита.',
+ 'tours.walk.hours.noStart': 'Задай прогулянці час цього дня, щоб бачити прибуття й години роботи.',
+ 'tours.poi.categoriesNone': 'Категорію не вибрано',
+ 'tours.poi.categoriesMore': '{first} +{more}',
'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 8fa3f3504..a63052ff8 100644
--- a/shared/src/i18n/vi/tours.ts
+++ b/shared/src/i18n/vi/tours.ts
@@ -107,7 +107,7 @@ const tours: TranslationStrings = {
'tours.planner.newTourConfirmTitle': 'Bắt đầu chuyến tham quan mới?',
'tours.planner.newTourConfirmBody':
'Các thay đổi chưa lưu của chuyến tham quan này sẽ bị bỏ. Các chuyến tham quan đã lưu sẽ không thay đổi.',
- 'tours.planner.newTourConfirm': 'Bỏ thay đổi và bắt đầu chuyến tham quan mới',
+ 'tours.planner.newTourConfirm': 'Hủy và bắt đầu',
'tours.planner.collapseElevation': 'Thu gọn biểu đồ độ cao',
'tours.planner.expandElevation': 'Mở rộng biểu đồ độ cao',
'tours.planner.name': 'Tên chuyến tham quan',
@@ -513,6 +513,16 @@ const tours: TranslationStrings = {
'tours.walk.themeHint.mixed': 'Mỗi thứ một chút: điểm tham quan, bảo tàng, công viên, cảnh đẹp, quán cà phê và chợ.',
'tours.walk.themeHint.own': 'Bạn đang tự chọn danh mục bên dưới; chủ đề sẽ chọn giúp bạn.',
'tours.walk.themeReason': 'Gợi ý cho {theme}',
+ 'tours.walk.arrival': 'Đến khoảng {time}',
+ 'tours.walk.arrivalAtLeast': 'Đến sớm nhất lúc {time}',
+ 'tours.walk.hours.closed': 'Theo giờ mở cửa, lúc {arrival} đóng cửa',
+ 'tours.walk.hours.closes': 'Đến {arrival}, ở đến {leave}, nhưng đóng cửa lúc {closes}',
+ 'tours.walk.hours.unread': 'Không đọc được giờ mở cửa ở đây: {text}',
+ 'tours.walk.hours.source':
+ 'Giờ mở cửa theo OpenStreetMap, so với giờ đến ước tính; hãy kiểm tra trước khi đi. Điểm dừng không có giờ là chưa rõ, không phải đang mở.',
+ 'tours.walk.hours.noStart': 'Đặt giờ cho lộ trình trong ngày này để xem giờ đến và giờ mở cửa.',
+ 'tours.poi.categoriesNone': 'Chưa chọn danh mục',
+ 'tours.poi.categoriesMore': '{first} +{more}',
'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 3aa1f26e3..707aab9be 100644
--- a/shared/src/i18n/zh-TW/tours.ts
+++ b/shared/src/i18n/zh-TW/tours.ts
@@ -96,7 +96,7 @@ const tours: TranslationStrings = {
'tours.planner.discardChanges': '捨棄變更',
'tours.planner.newTourConfirmTitle': '開始新的遊程?',
'tours.planner.newTourConfirmBody': '這個遊程中尚未儲存的變更將被捨棄。已儲存的遊程不會變更。',
- 'tours.planner.newTourConfirm': '捨棄變更並開始新遊程',
+ 'tours.planner.newTourConfirm': '放棄並開始',
'tours.planner.collapseElevation': '收合海拔剖面圖',
'tours.planner.expandElevation': '展開海拔剖面圖',
'tours.planner.name': '遊程名稱',
@@ -481,6 +481,16 @@ const tours: TranslationStrings = {
'tours.walk.themeHint.mixed': '各類都有一些:景點、博物館、公園、景觀、咖啡館和市集。',
'tours.walk.themeHint.own': '下方類別為你自選;選擇主題會自動設定。',
'tours.walk.themeReason': '推薦用於{theme}',
+ 'tours.walk.arrival': '約 {time} 抵達',
+ 'tours.walk.arrivalAtLeast': '最早 {time} 抵達',
+ 'tours.walk.hours.closed': '按營業時間,{arrival} 時已關門',
+ 'tours.walk.hours.closes': '{arrival} 抵達,停留至 {leave},但 {closes} 關門',
+ 'tours.walk.hours.unread': '此處無法解析營業時間:{text}',
+ 'tours.walk.hours.source':
+ '營業時間以 OpenStreetMap 為準,並與預計抵達時間對照;出發前請再確認。沒有營業時間的站點視為未知,而非營業中。',
+ 'tours.walk.hours.noStart': '為當天的漫步設定時間,即可查看抵達時間和營業時間。',
+ 'tours.poi.categoriesNone': '未選擇類別',
+ 'tours.poi.categoriesMore': '{first} +{more}',
'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 e12db79fd..4319c87a2 100644
--- a/shared/src/i18n/zh/tours.ts
+++ b/shared/src/i18n/zh/tours.ts
@@ -97,7 +97,7 @@ const tours: TranslationStrings = {
'tours.planner.discardChanges': '放弃更改',
'tours.planner.newTourConfirmTitle': '开始规划新游览路线?',
'tours.planner.newTourConfirmBody': '此游览路线中未保存的更改将被丢弃。已保存的游览路线不会改变。',
- 'tours.planner.newTourConfirm': '放弃更改并开始新游览路线',
+ 'tours.planner.newTourConfirm': '放弃并开始',
'tours.planner.collapseElevation': '收起海拔剖面图',
'tours.planner.expandElevation': '展开海拔剖面图',
'tours.planner.name': '游览名称',
@@ -482,6 +482,16 @@ const tours: TranslationStrings = {
'tours.walk.themeHint.mixed': '各类都有一些:景点、博物馆、公园、景观、咖啡馆和集市。',
'tours.walk.themeHint.own': '下方类别为你自选;选择主题会自动设置。',
'tours.walk.themeReason': '推荐用于{theme}',
+ 'tours.walk.arrival': '约 {time} 到达',
+ 'tours.walk.arrivalAtLeast': '最早 {time} 到达',
+ 'tours.walk.hours.closed': '按营业时间,{arrival} 时已关门',
+ 'tours.walk.hours.closes': '{arrival} 到达,停留至 {leave},但 {closes} 关门',
+ 'tours.walk.hours.unread': '此处无法解析营业时间:{text}',
+ 'tours.walk.hours.source':
+ '营业时间以 OpenStreetMap 为准,并与预计到达时间对照;出发前请再确认。没有营业时间的站点视为未知,而非营业中。',
+ 'tours.walk.hours.noStart': '为当天的漫步设定时间,即可查看到达时间和营业时间。',
+ 'tours.poi.categoriesNone': '未选择类别',
+ 'tours.poi.categoriesMore': '{first} +{more}',
'tours.planner.tourType': '路线类型',
'tours.paddle.boat': '船只',
'tours.paddle.boat.kayak': '皮划艇',