diff --git a/client/src/components/Tours/TourDetailDialog.test.tsx b/client/src/components/Tours/TourDetailDialog.test.tsx
index ab8b82876..a6cfc541f 100644
--- a/client/src/components/Tours/TourDetailDialog.test.tsx
+++ b/client/src/components/Tours/TourDetailDialog.test.tsx
@@ -192,7 +192,7 @@ describe('TourDetailDialog', () => {
}
const view = render()
- expect(screen.getByTestId('walk-day')).toHaveTextContent('Day 1, start 09:00')
+ expect(screen.getByTestId('walk-day')).toHaveTextContent(/^Day 1, Mon, Oct 19(, 2026)?, start 09:00$/)
expect(screen.queryByTestId('walk-day-picker')).not.toBeInTheDocument()
expect(await screen.findByText('Arrival about 09:00, stay until 09:30')).toBeInTheDocument()
expect(screen.getByTestId('walk-station-conflict'))
@@ -200,7 +200,7 @@ describe('TourDetailDialog', () => {
expect(screen.getByText('Mark visited')).toBeInTheDocument()
view.rerender()
- expect(screen.getByTestId('walk-day-picker')).toHaveTextContent('Day 1, start 09:00')
+ expect(screen.getByTestId('walk-day-picker')).toHaveTextContent(/^Day 1, Mon, Oct 19(, 2026)?, start 09:00$/)
expect(screen.queryByText('Mark visited')).not.toBeInTheDocument()
fireEvent.click(screen.getByTestId('walk-day-picker'))
fireEvent.click(screen.getByRole('button', { name: /^Day 2, .*start 11:00$/ }))
diff --git a/client/src/components/Tours/planner/TourWaypointList.test.tsx b/client/src/components/Tours/planner/TourWaypointList.test.tsx
index a7b279266..5e03704bd 100644
--- a/client/src/components/Tours/planner/TourWaypointList.test.tsx
+++ b/client/src/components/Tours/planner/TourWaypointList.test.tsx
@@ -294,7 +294,7 @@ describe('TourWaypointList', () => {
it('FE-TOUR-WAYPOINT-LIST-009: names the day above the stops and the conflict at the stop it is about', () => {
render();
- expect(screen.getByTestId('walk-day')).toHaveTextContent('Day 1, start 17:20');
+ expect(screen.getByTestId('walk-day')).toHaveTextContent(/^Day 1, Mon, Oct 19(, 2026)?, start 17:20$/);
const rows = screen.getAllByTestId('tour-planner-waypoint-row');
const first = within(at(rows, 0)).getByTestId('walk-station-schedule');
expect(within(first).getByText('Arrival about 17:20, stay until 18:20')).toBeInTheDocument();
@@ -312,7 +312,7 @@ describe('TourWaypointList', () => {
it('FE-TOUR-WAYPOINT-LIST-010: shows no arrival while the route is not planned for the points as they are', () => {
render();
- expect(screen.getByTestId('walk-day')).toHaveTextContent('Day 1, start 17:20');
+ expect(screen.getByTestId('walk-day')).toHaveTextContent(/^Day 1, Mon, Oct 19(, 2026)?, start 17:20$/);
expect(screen.queryByTestId('walk-station-schedule')).not.toBeInTheDocument();
expect(screen.queryByTestId('walk-station-conflict')).not.toBeInTheDocument();
});
diff --git a/client/src/components/Tours/walk/StationSchedule.test.tsx b/client/src/components/Tours/walk/StationSchedule.test.tsx
index d095fdf8c..a96b7a16e 100644
--- a/client/src/components/Tours/walk/StationSchedule.test.tsx
+++ b/client/src/components/Tours/walk/StationSchedule.test.tsx
@@ -36,12 +36,16 @@ describe('StationScheduleLine', () => {
it('follows the 12 hour format, a window to midnight included', () => {
seedStore(useSettingsStore, { settings: { time_format: '12h' } });
- line(at({ leave: 1130, fit: { kind: 'closes', source: 'place', window: [600, 1080] } }));
+ const { unmount } = line(at({ leave: 1130, fit: { kind: 'closes', source: 'place', window: [600, 1080] } }));
expect(screen.getByText('Arrival about 5:20 PM, stay until 6:50 PM')).toBeInTheDocument();
expect(screen.getByTestId('walk-station-conflict')).toHaveTextContent(
'Lenbachhaus closes at 6:00 PM, during the stay from 5:20 PM to 6:50 PM (open 10:00 AM to 6:00 PM)'
);
expect(screen.getByTestId('walk-hours-source')).toHaveTextContent('Trip place');
+ // In 12 hours a window to midnight ends at 12:00 AM, never at 24:00.
+ unmount();
+ line(at({ fit: { kind: 'open', source: 'place', window: [600, 1440] } }));
+ expect(screen.getByText(/Open 10:00 AM to 12:00 AM/)).toBeInTheDocument();
});
it('shows the window an open station is checked against; one to midnight reads 24:00', () => {
diff --git a/client/src/components/Tours/walk/WalkDayLine.test.tsx b/client/src/components/Tours/walk/WalkDayLine.test.tsx
index d7a93ec7a..2889ae785 100644
--- a/client/src/components/Tours/walk/WalkDayLine.test.tsx
+++ b/client/src/components/Tours/walk/WalkDayLine.test.tsx
@@ -44,7 +44,8 @@ describe('WalkDayLine', () => {
render();
expect(screen.getByText('Times for')).toBeInTheDocument();
const picker = screen.getByTestId('walk-day-picker');
- expect(picker).toHaveTextContent('Day 2, start 09:30');
+ // The pill names the date the hours are for, as the menu does.
+ expect(picker).toHaveTextContent(/^Day 2, Tue, Oct 20(, 2026)?, start 09:30$/);
expect(picker).toHaveAttribute('aria-expanded', 'false');
fireEvent.click(picker);
@@ -56,7 +57,9 @@ describe('WalkDayLine', () => {
]);
expect(items.map((item) => !!item.querySelector('svg'))).toEqual([false, true]);
fireEvent.click(screen.getByRole('button', { name: /^Day 1, / }));
- expect(screen.getByTestId('walk-day-picker')).toHaveTextContent('Day 1, no start time');
+ expect(screen.getByTestId('walk-day-picker')).toHaveTextContent(
+ /^Day 1, Mon, Oct 19(, 2026)?: Old town, no start time$/
+ );
});
it('closes the menu again from its pill', () => {
@@ -72,7 +75,7 @@ describe('WalkDayLine', () => {
const { rerender } = render(
);
- expect(screen.getByTestId('walk-day')).toHaveTextContent('Day 2, start 09:30');
+ expect(screen.getByTestId('walk-day')).toHaveTextContent(/^Day 2, Tue, Oct 20(, 2026)?, start 09:30$/);
expect(screen.queryByTestId('walk-day-picker')).not.toBeInTheDocument();
rerender(
{
stationCount={3}
/>
);
- expect(screen.getByTestId('walk-day')).toHaveTextContent('Day 1, no start time');
+ expect(screen.getByTestId('walk-day')).toHaveTextContent(/^Day 1, .+: Old town, no start time$/);
expect(screen.getByText('Give the walk a time on this day to see arrivals and opening hours.')).toBeInTheDocument();
});
@@ -102,7 +105,7 @@ describe('WalkDayLine', () => {
const { rerender, container } = render(
);
- expect(screen.getByText(/Opening hours from OpenStreetMap or your own place entries/)).toBeInTheDocument();
+ expect(screen.getByText(/Opening hours from OpenStreetMap or the trip's places/)).toBeInTheDocument();
rerender();
expect(screen.queryByText(/Opening hours from OpenStreetMap/)).not.toBeInTheDocument();
rerender();
diff --git a/client/src/components/Tours/walk/WalkDayLine.tsx b/client/src/components/Tours/walk/WalkDayLine.tsx
index b031eb5bb..eb54a706c 100644
--- a/client/src/components/Tours/walk/WalkDayLine.tsx
+++ b/client/src/components/Tours/walk/WalkDayLine.tsx
@@ -15,7 +15,7 @@ type Translate = ReturnType['t'];
const PILL =
'inline-flex min-w-0 items-center gap-1 rounded-full bg-surface-card px-2 py-0.5 text-start font-medium text-content-secondary shadow-sm';
-/** "Day 2, start 09:00", or that it has no start time; `day` names the day as the place needs it. */
+/** "Day 2, Tue, Jul 8, start 09:00", or that it has no start time; `day` names the day with its date. */
function optionLabel(option: WalkDayOption, day: string, t: Translate, is12h: boolean): string {
return option.start
? t('tours.walk.day.start', { day, time: formatClockTime(option.start, is12h) })
@@ -30,12 +30,15 @@ function missingNote(state: WalkScheduleState, choice: WalkDayChoice, stationCou
return null;
}
-/** The day as a pill: a menu of the walk's days where there is a choice, its name alone where there is none. */
+/**
+ * The day as a pill: a menu of the walk's days where there is a choice, its name alone where there is none.
+ * It names the day with its date as the menu does, so the opening hours below read for a day that is in view.
+ */
function DayPill({ choice, day }: { choice: WalkDayChoice; day: WalkDayOption }) {
const { t, locale } = useTranslation();
const is12h = useSettingsStore((state) => state.settings.time_format) === '12h';
const menu = useContextMenu();
- const label = optionLabel(day, t('dayplan.dayN', { n: day.index + 1 }), t, is12h);
+ const label = optionLabel(day, dayMenuLabel(day.day, day.index, t, locale), t, is12h);
if (choice.fixed || choice.options.length < 2) {
return (
diff --git a/client/src/components/Tours/walk/WalkStations.test.tsx b/client/src/components/Tours/walk/WalkStations.test.tsx
index 9e34c1f03..de95b4ad3 100644
--- a/client/src/components/Tours/walk/WalkStations.test.tsx
+++ b/client/src/components/Tours/walk/WalkStations.test.tsx
@@ -206,7 +206,7 @@ describe('WalkStationsCard', () => {
it('on the day it was opened from, shows each arrival, the end of each stay and what the opening hours say', async () => {
card(onMonday());
- expect(screen.getByTestId('walk-day')).toHaveTextContent('Day 1, start 09:00');
+ expect(screen.getByTestId('walk-day')).toHaveTextContent(/^Day 1, .+, start 09:00$/);
expect(screen.queryByTestId('walk-day-picker')).not.toBeInTheDocument();
const museum = await rowOf('Museum');
expect(within(museum).getByText('Arrival about 09:00, stay until 09:30')).toBeInTheDocument();
@@ -224,7 +224,7 @@ describe('WalkStationsCard', () => {
within(cafe).getByText('Opening hours not checked: a stay before this stop is not entered yet.')
).toBeInTheDocument();
expect(within(cafe).queryByTestId('walk-station-conflict')).not.toBeInTheDocument();
- expect(screen.getByText(/Opening hours from OpenStreetMap or your own place entries/)).toBeInTheDocument();
+ expect(screen.getByText(/Opening hours from OpenStreetMap or the trip's places/)).toBeInTheDocument();
});
it("names a stop by the trip place it stands for and checks the place's own hours: 17:20 to 18:20 against 18:00", async () => {
@@ -259,7 +259,7 @@ describe('WalkStationsCard', () => {
seedStore(useSettingsStore, { settings: { time_format: '12h' } });
card(onMonday());
expect(within(await rowOf('Museum')).getByText('Arrival about 9:00 AM, stay until 9:30 AM')).toBeInTheDocument();
- expect(screen.getByTestId('walk-day')).toHaveTextContent('Day 1, start 9:00 AM');
+ expect(screen.getByTestId('walk-day')).toHaveTextContent(/^Day 1, .+, start 9:00 AM$/);
});
it('on a day without a start, asks for one and leaves arrivals and hours unsaid', async () => {
@@ -267,7 +267,7 @@ describe('WalkStationsCard', () => {
expect(
await screen.findByText('Give the walk a time on this day to see arrivals and opening hours.')
).toBeInTheDocument();
- expect(screen.getByTestId('walk-day')).toHaveTextContent('Day 1, no start time');
+ expect(screen.getByTestId('walk-day')).toHaveTextContent(/^Day 1, .+, no start time$/);
await screen.findAllByTestId('walk-station');
expect(screen.queryByText(/Arrival/)).not.toBeInTheDocument();
expect(screen.queryByText(/Opening hours from OpenStreetMap/)).not.toBeInTheDocument();
@@ -306,7 +306,7 @@ describe('WalkStationsCard', () => {
route: ROUTE,
});
const picker = screen.getByTestId('walk-day-picker');
- expect(picker).toHaveTextContent('Day 2, start 10:00');
+ expect(picker).toHaveTextContent(/^Day 2, .+, start 10:00$/);
// Tuesday from 10:00: the museum closed at 09:20 already.
expect(within(await rowOf('Museum')).getByTestId('walk-station-conflict')).toHaveTextContent(
'Museum is closed at 10:00, the stay would last until 10:30; open 09:00 to 09:20'
@@ -319,7 +319,7 @@ describe('WalkStationsCard', () => {
fireEvent.click(picker);
const monday = screen.getByRole('button', { name: /^Day 1, .*no start time$/ });
fireEvent.click(monday);
- expect(screen.getByTestId('walk-day-picker')).toHaveTextContent('Day 1, no start time');
+ expect(screen.getByTestId('walk-day-picker')).toHaveTextContent(/^Day 1, .+, no start time$/);
expect(screen.getByText('Give the walk a time on this day to see arrivals and opening hours.')).toBeInTheDocument();
expect(screen.queryByText(/Arrival/)).not.toBeInTheDocument();
});
diff --git a/server/src/nest/maps/maps-index.helpers.ts b/server/src/nest/maps/maps-index.helpers.ts
index 3c031d63f..bf9ed09e3 100644
--- a/server/src/nest/maps/maps-index.helpers.ts
+++ b/server/src/nest/maps/maps-index.helpers.ts
@@ -23,6 +23,9 @@ import type { MapsAutocompleteResult } from '@trek/shared';
type PoiBbox = { south: number; west: number; north: number; east: number };
+/** Every term the index is asked by, whichever kind asks for it. */
+const EVERY_TERM = Object.values(POI_CATEGORY_TO_TREK).flat();
+
/** What one POI request asks the index, and how its answer is labelled. */
export interface IndexPoiPlan {
wanted: string[];
@@ -162,9 +165,7 @@ export function ownedByAsked(
row: { category: string | null; categoryPath: string | null },
asked: readonly string[],
): boolean {
- // Every term the index is asked by, whichever kind asks for it; read here, not
- // when the module loads, so a suite that stubs the index client stays loadable.
- const term = indexRowTerm(Object.values(POI_CATEGORY_TO_TREK).flat(), row.category, row.categoryPath);
+ const term = indexRowTerm(EVERY_TERM, row.category, row.categoryPath);
return term === undefined || asked.some((kind) => POI_CATEGORY_TO_TREK[kind]?.includes(term));
}
diff --git a/server/tests/unit/db/repositories/TourWaypoints.repository.test.ts b/server/tests/unit/db/repositories/TourWaypoints.repository.test.ts
index 373719211..9b457df21 100644
--- a/server/tests/unit/db/repositories/TourWaypoints.repository.test.ts
+++ b/server/tests/unit/db/repositories/TourWaypoints.repository.test.ts
@@ -10,6 +10,7 @@ import type {
} from '../../../../src/db/repositories/TourWaypoints.repository';
import { createSnapshotTestDb } from '../../../helpers/db-mock';
import { createPlace, createTrip, createUser } from '../../../helpers/factories';
+import { makePlace } from '../../../helpers/factories/places';
import { resetTestDb } from '../../../helpers/test-db';
import { createTestOrm, type TestOrm } from '../../../helpers/test-orm';
import { createTour } from '../../../helpers/tours-repos';
@@ -222,9 +223,9 @@ describe('TourWaypointsRepository', () => {
it('TWREPO-010: listForPlaces reads the waypoints of the tours asked for, by place and sequence, and none for none (CW-02)', async () => {
const { trip, place } = tourInTrip();
- const second = createPlace(testDb, trip.id);
+ const second = await makePlace(t, trip.id);
createTour(testDb, second.id);
- const unasked = createPlace(testDb, trip.id);
+ const unasked = await makePlace(t, trip.id);
createTour(testDb, unasked.id);
await repo.insertForPlace(second.id, [{ lat: 47.2, lng: 11.2, role: 'start', sequence: 0, stay_minutes: 30 }]);
await repo.insertForPlace(place.id, ROUTE);
diff --git a/server/tests/unit/db/repositories/Tours.repository.test.ts b/server/tests/unit/db/repositories/Tours.repository.test.ts
index 602ea9121..bad29348b 100644
--- a/server/tests/unit/db/repositories/Tours.repository.test.ts
+++ b/server/tests/unit/db/repositories/Tours.repository.test.ts
@@ -10,7 +10,9 @@ import { Tours } from '../../../../src/db/entities/Tours.entity';
import type { ToursRepository } from '../../../../src/db/repositories/Tours.repository';
import { createSnapshotTestDb } from '../../../helpers/db-mock';
import { createDay, createDayAssignment, createPlace, createTrip, createUser } from '../../../helpers/factories';
+import { makePlace } from '../../../helpers/factories/places';
import { insertRow, updateRows } from '../../../helpers/factories/rows';
+import { makeDay } from '../../../helpers/factories/trips';
import { resetTestDb } from '../../../helpers/test-db';
import { createTestOrm, type TestOrm } from '../../../helpers/test-orm';
import { createTour } from '../../../helpers/tours-repos';
@@ -167,8 +169,8 @@ describe('ToursRepository reads', () => {
it('TOURREPO-014: the list and the detail say whether the owning place still has no line (CW-11)', async () => {
const { trip } = fixture();
- const pending = createPlace(testDb, trip.id, { name: 'Stations only' });
- const drawn = createPlace(testDb, trip.id, { name: 'Drawn' });
+ const pending = await makePlace(t, trip.id, { name: 'Stations only' });
+ const drawn = await makePlace(t, trip.id, { name: 'Drawn' });
createTour(testDb, pending.id);
createTour(testDb, drawn.id);
await updateRows(t, Places, { id: drawn.id }, { route_geometry: '[[47,11,500],[47.1,11.1,520]]' });
@@ -181,9 +183,9 @@ describe('ToursRepository reads', () => {
it('TOURREPO-015: dayOfTrip reads a day of the trip with its date, and nothing for a day of another trip', async () => {
const { trip, other } = fixture();
- const dated = createDay(testDb, trip.id, { date: '2026-07-12' });
- const undated = createDay(testDb, trip.id);
- const elsewhere = createDay(testDb, other.id, { date: '2026-07-12' });
+ const dated = await makeDay(t, trip.id, { date: '2026-07-12' });
+ const undated = await makeDay(t, trip.id);
+ const elsewhere = await makeDay(t, other.id, { date: '2026-07-12' });
expect(await repo.dayOfTrip(trip.id, dated.id)).toEqual({ id: dated.id, date: '2026-07-12' });
expect(await repo.dayOfTrip(trip.id, undated.id)).toEqual({ id: undated.id, date: null });
diff --git a/server/tests/unit/nest/place-enrichment.description.test.ts b/server/tests/unit/nest/place-enrichment.description.test.ts
index 2998eed56..f50874b7d 100644
--- a/server/tests/unit/nest/place-enrichment.description.test.ts
+++ b/server/tests/unit/nest/place-enrichment.description.test.ts
@@ -47,7 +47,8 @@ vi.mock('../../../src/utils/ssrfGuard', () => ({
const { mockTrekPlacesById } = vi.hoisted(() => ({
mockTrekPlacesById: vi.fn(async (_gers: string): Promise => null),
}));
-vi.mock('../../../src/nest/maps/trek-places.client', () => ({
+vi.mock('../../../src/nest/maps/trek-places.client', async (importOriginal) => ({
+ ...(await importOriginal()),
trekPlacesById: mockTrekPlacesById,
}));
diff --git a/server/tests/unit/nest/tour-library.mcp.test.ts b/server/tests/unit/nest/tour-library.mcp.test.ts
index 144d91330..a96069278 100644
--- a/server/tests/unit/nest/tour-library.mcp.test.ts
+++ b/server/tests/unit/nest/tour-library.mcp.test.ts
@@ -147,7 +147,7 @@ describe('TourLibraryMcp', () => {
importance: 'must',
protected: 1,
opening_hours: 'Mo-Su 09:00-18:00',
- linkedPlace: createPlace(db, alps.id, { name: 'Museum' }).id,
+ linkedPlace: (await makePlace(orm, alps.id, { name: 'Museum' })).id,
},
{ lat: 48.11, lng: 11.51, station_key: 'stop-park', importance: 'optional' },
]);
diff --git a/shared/src/i18n/ar/tours.ts b/shared/src/i18n/ar/tours.ts
index fc5ba1c06..9bda580c6 100644
--- a/shared/src/i18n/ar/tours.ts
+++ b/shared/src/i18n/ar/tours.ts
@@ -572,7 +572,7 @@ const tours: TranslationStrings = {
'tours.walk.hours.closes': '{name} يغلق في {closes}، أثناء البقاء من {arrival} إلى {leave} (مفتوح {window})',
'tours.walk.hours.unread': 'لم تُقرأ ساعات العمل هنا: {text}',
'tours.walk.hours.source':
- 'ساعات العمل من OpenStreetMap أو من أماكنك التي أدخلتها بنفسك، مقارنةً بوقت الوصول المقدّر بالتوقيت المحلي للخطة؛ تحقق منها قبل الذهاب. المحطة بلا ساعات عمل غير معروفة، وليست مفتوحة.',
+ 'ساعات العمل من OpenStreetMap أو من أماكن الرحلة، مقارنةً بوقت الوصول المقدّر بالتوقيت المحلي للخطة؛ تحقق منها قبل الذهاب. المحطة بلا ساعات عمل غير معروفة، وليست مفتوحة.',
'tours.walk.hours.noStart': 'حدد وقتًا للجولة في هذا اليوم لرؤية أوقات الوصول وساعات العمل.',
'tours.walk.day.label': 'الأوقات حسب',
'tours.walk.day.start': '{day}، البدء {time}',
diff --git a/shared/src/i18n/az/tours.ts b/shared/src/i18n/az/tours.ts
index 6762224dd..c1dc98c4b 100644
--- a/shared/src/i18n/az/tours.ts
+++ b/shared/src/i18n/az/tours.ts
@@ -571,7 +571,7 @@ const tours: TranslationStrings = {
'{name} {closes} vaxtı bağlanır, {arrival} ilə {leave} arası qalma zamanı (açıqdır {window})',
'tours.walk.hours.unread': 'Açılış saatları burada oxunmadı: {text}',
'tours.walk.hours.source':
- 'Açılış saatları OpenStreetMap-dən və ya öz yerlərindən götürülür, planın yerli vaxtı ilə təxmini çatma vaxtına qarşı müqayisə olunur; getməzdən əvvəl yoxla. Saatları olmayan dayanacaq naməlumdur, açıq deyil.',
+ 'Açılış saatları OpenStreetMap-dən və ya səyahətin yerlərindən götürülür, planın yerli vaxtı ilə təxmini çatma vaxtına qarşı müqayisə olunur; getməzdən əvvəl yoxla. Saatları olmayan dayanacaq naməlumdur, 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.walk.day.label': 'Vaxtlar üçün gün',
'tours.walk.day.start': '{day}, başlanğıc {time}',
diff --git a/shared/src/i18n/br/tours.ts b/shared/src/i18n/br/tours.ts
index 65cc0a690..2f9947266 100644
--- a/shared/src/i18n/br/tours.ts
+++ b/shared/src/i18n/br/tours.ts
@@ -576,7 +576,7 @@ const tours: TranslationStrings = {
'tours.walk.hours.closes': '{name} fecha às {closes}, durante a permanência das {arrival} às {leave} (abre {window})',
'tours.walk.hours.unread': 'Horário não lido aqui: {text}',
'tours.walk.hours.source':
- 'Horários do OpenStreetMap ou dos seus próprios lugares, comparados com a chegada estimada no horário local do plano; confira antes de ir. Uma parada sem horário é desconhecida, não aberta.',
+ 'Horários do OpenStreetMap ou dos lugares da viagem, comparados com a chegada estimada no horário local do plano; 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.walk.day.label': 'Horários para',
'tours.walk.day.start': '{day}, saída às {time}',
diff --git a/shared/src/i18n/ca/tours.ts b/shared/src/i18n/ca/tours.ts
index 390c9eba9..abcdc884f 100644
--- a/shared/src/i18n/ca/tours.ts
+++ b/shared/src/i18n/ca/tours.ts
@@ -577,7 +577,7 @@ const tours: TranslationStrings = {
'tours.walk.hours.closes': "{name} tanca a les {closes}, durant l'estada de {arrival} a {leave} (obre {window})",
'tours.walk.hours.unread': 'Horari no llegit aquí: {text}',
'tours.walk.hours.source':
- "Horaris d'OpenStreetMap o dels teus propis llocs, comparats amb l'arribada estimada en l'hora local del pla; comprova'ls abans d'anar-hi. Una parada sense horari és desconeguda, no oberta.",
+ "Horaris d'OpenStreetMap o dels llocs del viatge, comparats amb l'arribada estimada en l'hora local del pla; comprova'ls 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.walk.day.label': 'Horaris per a',
'tours.walk.day.start': '{day}, sortida a les {time}',
diff --git a/shared/src/i18n/cs/tours.ts b/shared/src/i18n/cs/tours.ts
index 91dc27370..6e03dba16 100644
--- a/shared/src/i18n/cs/tours.ts
+++ b/shared/src/i18n/cs/tours.ts
@@ -566,7 +566,7 @@ const tours: TranslationStrings = {
'tours.walk.hours.closes': '{name}: zavírá v {closes}, během pobytu od {arrival} do {leave} (otevřeno {window})',
'tours.walk.hours.unread': 'Otevírací dobu zde nelze vyhodnotit: {text}',
'tours.walk.hours.source':
- 'Otevírací doby z OpenStreetMap nebo z tvých vlastních míst, porovnané s odhadovaným příchodem v místním čase plánu; před cestou si je ověř. Zastávka bez časů je neznámá, ne otevřená.',
+ 'Otevírací doby z OpenStreetMap nebo z míst výletu, porovnané s odhadovaným příchodem v místním čase plánu; před cestou si je 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.walk.day.label': 'Časy pro',
'tours.walk.day.start': '{day}, začátek {time}',
diff --git a/shared/src/i18n/de/tours.ts b/shared/src/i18n/de/tours.ts
index d110e9ee7..9f0e51342 100644
--- a/shared/src/i18n/de/tours.ts
+++ b/shared/src/i18n/de/tours.ts
@@ -582,7 +582,7 @@ const tours: TranslationStrings = {
'{name} schließt um {closes}, während des Aufenthalts von {arrival} bis {leave} (geöffnet {window})',
'tours.walk.hours.unread': 'Öffnungszeiten hier nicht auswertbar: {text}',
'tours.walk.hours.source':
- 'Öffnungszeiten aus OpenStreetMap oder aus deinen eigenen Ortsangaben, verglichen mit der geschätzten Ankunft in der Ortszeit des Plans; vor Ort lieber prüfen. Eine Station ohne Zeiten ist unbekannt, nicht geöffnet.',
+ 'Öffnungszeiten aus OpenStreetMap oder den Orten der Reise, verglichen mit der geschätzten Ankunft in der Ortszeit des Plans; 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.walk.day.label': 'Zeiten für',
'tours.walk.day.start': '{day}, Beginn {time}',
diff --git a/shared/src/i18n/en/tours.ts b/shared/src/i18n/en/tours.ts
index d250c93e1..c114179aa 100644
--- a/shared/src/i18n/en/tours.ts
+++ b/shared/src/i18n/en/tours.ts
@@ -567,7 +567,7 @@ const tours: TranslationStrings = {
'tours.walk.hours.closes': '{name} closes at {closes}, during the stay from {arrival} to {leave} (open {window})',
'tours.walk.hours.unread': 'Opening hours not read here: {text}',
'tours.walk.hours.source':
- 'Opening hours from OpenStreetMap or your own place entries, compared with the estimated arrival in the local time of the plan; check before you go. A stop without hours is unknown, not open.',
+ "Opening hours from OpenStreetMap or the trip's places, compared with the estimated arrival in the local time of the plan; 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.walk.day.label': 'Times for',
'tours.walk.day.start': '{day}, start {time}',
diff --git a/shared/src/i18n/es/tours.ts b/shared/src/i18n/es/tours.ts
index 13858566f..bceb3edd9 100644
--- a/shared/src/i18n/es/tours.ts
+++ b/shared/src/i18n/es/tours.ts
@@ -576,7 +576,7 @@ const tours: TranslationStrings = {
'tours.walk.hours.closes': '{name} cierra a las {closes}, durante la estancia de {arrival} a {leave} (abre {window})',
'tours.walk.hours.unread': 'Horario no interpretado aquí: {text}',
'tours.walk.hours.source':
- 'Horarios de OpenStreetMap o de tus propios lugares, comparados con la llegada estimada en la hora local del plan; compruébalos antes de ir. Una parada sin horario es desconocida, no abierta.',
+ 'Horarios de OpenStreetMap o de los lugares del viaje, comparados con la llegada estimada en la hora local del plan; compruébalos 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.walk.day.label': 'Horarios para',
'tours.walk.day.start': '{day}, salida a las {time}',
diff --git a/shared/src/i18n/et/tours.ts b/shared/src/i18n/et/tours.ts
index 2d96dc290..d2777b41b 100644
--- a/shared/src/i18n/et/tours.ts
+++ b/shared/src/i18n/et/tours.ts
@@ -568,7 +568,7 @@ const tours: TranslationStrings = {
'tours.walk.hours.closes': '{name} suletakse kell {closes}, viibimise ajal {arrival} kuni {leave} (avatud {window})',
'tours.walk.hours.unread': 'Lahtiolekuaegu siin ei loetud: {text}',
'tours.walk.hours.source':
- 'Lahtiolekuajad OpenStreetMapist või sinu enda kohtadest, võrreldud hinnangulise saabumisega plaani kohaliku aja järgi; kontrolli enne minekut. Ajata peatus on teadmata, mitte avatud.',
+ 'Lahtiolekuajad OpenStreetMapist või reisi kohtadest, võrreldud hinnangulise saabumisega plaani kohaliku aja järgi; kontrolli enne minekut. Ajata peatus on teadmata, mitte avatud.',
'tours.walk.hours.noStart': 'Anna jalutusele sel päeval kellaaeg, et näha saabumisi ja lahtiolekuaegu.',
'tours.walk.day.label': 'Ajad päeval',
'tours.walk.day.start': '{day}, algus {time}',
diff --git a/shared/src/i18n/fr/tours.ts b/shared/src/i18n/fr/tours.ts
index 81fe08a99..30a1fe803 100644
--- a/shared/src/i18n/fr/tours.ts
+++ b/shared/src/i18n/fr/tours.ts
@@ -584,7 +584,7 @@ const tours: TranslationStrings = {
'tours.walk.hours.closes': "{name} ferme à {closes}, pendant l'arrêt de {arrival} à {leave} (ouvert {window})",
'tours.walk.hours.unread': 'Horaires non lus ici : {text}',
'tours.walk.hours.source':
- "Horaires d'OpenStreetMap ou de tes propres lieux, comparés à l'arrivée estimée à l'heure locale du plan ; vérifie avant d'y aller. Une étape sans horaires est inconnue, pas ouverte.",
+ "Horaires d'OpenStreetMap ou des lieux du voyage, comparés à l'arrivée estimée à l'heure locale du plan ; 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.walk.day.label': 'Horaires pour',
'tours.walk.day.start': '{day}, départ à {time}',
diff --git a/shared/src/i18n/gr/tours.ts b/shared/src/i18n/gr/tours.ts
index c588c2f40..8a9ba7890 100644
--- a/shared/src/i18n/gr/tours.ts
+++ b/shared/src/i18n/gr/tours.ts
@@ -587,7 +587,7 @@ const tours: TranslationStrings = {
'{name}: κλείνει στις {closes}, κατά την παραμονή από {arrival} έως {leave} (ανοιχτό {window})',
'tours.walk.hours.unread': 'Το ωράριο δεν διαβάστηκε εδώ: {text}',
'tours.walk.hours.source':
- 'Ωράρια από το OpenStreetMap ή από τα δικά σου μέρη, σε σύγκριση με την εκτιμώμενη άφιξη στην τοπική ώρα του πλάνου. Έλεγξε πριν πας. Μια στάση χωρίς ωράριο είναι άγνωστη, όχι ανοιχτή.',
+ 'Ωράρια από το OpenStreetMap ή από τα μέρη του ταξιδιού, σε σύγκριση με την εκτιμώμενη άφιξη στην τοπική ώρα του πλάνου. Έλεγξε πριν πας. Μια στάση χωρίς ωράριο είναι άγνωστη, όχι ανοιχτή.',
'tours.walk.hours.noStart': 'Δώσε ώρα στη βόλτα αυτή τη μέρα για να δεις αφίξεις και ωράρια.',
'tours.walk.day.label': 'Ώρες για',
'tours.walk.day.start': '{day}, έναρξη {time}',
diff --git a/shared/src/i18n/hu/tours.ts b/shared/src/i18n/hu/tours.ts
index 31dd12a41..98f09281b 100644
--- a/shared/src/i18n/hu/tours.ts
+++ b/shared/src/i18n/hu/tours.ts
@@ -577,7 +577,7 @@ const tours: TranslationStrings = {
'{name} {closes} órakor zár, a {arrival} és {leave} közötti tartózkodás alatt (nyitva {window})',
'tours.walk.hours.unread': 'A nyitvatartás itt nem értelmezhető: {text}',
'tours.walk.hours.source':
- 'Nyitvatartás az OpenStreetMapből vagy a saját helyeidből, a becsült érkezéshez mérve a terv helyi ideje szerint; indulás előtt ellenőrizd. Az időpontok nélküli megálló ismeretlen, nem nyitva.',
+ 'Nyitvatartás az OpenStreetMapből vagy az utazás helyeiből, a becsült érkezéshez mérve a terv helyi ideje szerint; indulás előtt ellenőrizd. Az időpontok nélküli megálló ismeretlen, nem nyitva.',
'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.walk.day.label': 'Időpontok:',
'tours.walk.day.start': '{day}, kezdés {time}',
diff --git a/shared/src/i18n/id/tours.ts b/shared/src/i18n/id/tours.ts
index 3898b6591..67aca3c3c 100644
--- a/shared/src/i18n/id/tours.ts
+++ b/shared/src/i18n/id/tours.ts
@@ -571,7 +571,7 @@ const tours: TranslationStrings = {
'tours.walk.hours.closes': '{name} tutup pukul {closes}, saat singgah dari {arrival} sampai {leave} (buka {window})',
'tours.walk.hours.unread': 'Jam buka tidak terbaca di sini: {text}',
'tours.walk.hours.source':
- 'Jam buka dari OpenStreetMap atau dari tempat yang kamu isi sendiri, dibandingkan dengan perkiraan waktu tiba menurut waktu setempat rencana; periksa sebelum berangkat. Titik tanpa jam buka berarti tidak diketahui, bukan buka.',
+ 'Jam buka dari OpenStreetMap atau dari tempat-tempat perjalanan, dibandingkan dengan perkiraan waktu tiba menurut waktu setempat rencana; periksa sebelum berangkat. Titik tanpa jam buka berarti tidak diketahui, bukan buka.',
'tours.walk.hours.noStart': 'Beri rute waktu mulai di hari ini untuk melihat waktu tiba dan jam buka.',
'tours.walk.day.label': 'Waktu untuk',
'tours.walk.day.start': '{day}, mulai {time}',
diff --git a/shared/src/i18n/it/tours.ts b/shared/src/i18n/it/tours.ts
index 460eac51c..ccef7ba0a 100644
--- a/shared/src/i18n/it/tours.ts
+++ b/shared/src/i18n/it/tours.ts
@@ -578,7 +578,7 @@ const tours: TranslationStrings = {
'{name} chiude alle {closes}, durante la sosta dalle {arrival} alle {leave} (aperto {window})',
'tours.walk.hours.unread': 'Orari non interpretati qui: {text}',
'tours.walk.hours.source':
- "Orari da OpenStreetMap o dai tuoi luoghi, confrontati con l'arrivo stimato nell'ora locale del piano; verifica prima di andare. Una tappa senza orari è sconosciuta, non aperta.",
+ "Orari da OpenStreetMap o dai luoghi del viaggio, confrontati con l'arrivo stimato nell'ora locale del piano; 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.walk.day.label': 'Orari per',
'tours.walk.day.start': '{day}, partenza alle {time}',
diff --git a/shared/src/i18n/ja/tours.ts b/shared/src/i18n/ja/tours.ts
index 889c92703..eeba6b001 100644
--- a/shared/src/i18n/ja/tours.ts
+++ b/shared/src/i18n/ja/tours.ts
@@ -558,7 +558,7 @@ const tours: TranslationStrings = {
'tours.walk.hours.closes': '{name} は {closes} に閉まります。{arrival} から {leave} の滞在中です(営業 {window})',
'tours.walk.hours.unread': '営業時間はここでは読み取れません:{text}',
'tours.walk.hours.source':
- 'OpenStreetMap または自分で入力した場所の営業時間を、計画の現地時刻での推定到着時刻と照合しています。出かける前に確認してください。営業時間のない立ち寄り先は「不明」で、営業中とはみなしません。',
+ 'OpenStreetMap または旅行の場所の営業時間を、計画の現地時刻での推定到着時刻と照合しています。出かける前に確認してください。営業時間のない立ち寄り先は「不明」で、営業中とはみなしません。',
'tours.walk.hours.noStart': 'この日の開始時刻を設定すると、到着時刻と営業時間が表示されます。',
'tours.walk.day.label': '時刻の基準日',
'tours.walk.day.start': '{day}、開始 {time}',
diff --git a/shared/src/i18n/ko/tours.ts b/shared/src/i18n/ko/tours.ts
index 6fcadc5a1..f8c26a0d0 100644
--- a/shared/src/i18n/ko/tours.ts
+++ b/shared/src/i18n/ko/tours.ts
@@ -555,7 +555,7 @@ const tours: TranslationStrings = {
'{name}은(는) {closes}에 문을 닫습니다. {arrival}부터 {leave}까지 머무는 중입니다(영업 {window})',
'tours.walk.hours.unread': '여기서는 영업시간을 읽지 못했습니다: {text}',
'tours.walk.hours.source':
- 'OpenStreetMap 또는 직접 입력한 장소의 영업시간을 계획 현지 시간 기준의 예상 도착 시간과 비교합니다. 가기 전에 확인하세요. 영업시간이 없는 정류지는 영업 중이 아니라 미상입니다.',
+ 'OpenStreetMap 또는 여행 장소의 영업시간을 계획 현지 시간 기준의 예상 도착 시간과 비교합니다. 가기 전에 확인하세요. 영업시간이 없는 정류지는 영업 중이 아니라 미상입니다.',
'tours.walk.hours.noStart': '이날 산책에 시간을 지정하면 도착 시간과 영업시간을 볼 수 있습니다.',
'tours.walk.day.label': '시간 기준',
'tours.walk.day.start': '{day}, 시작 {time}',
diff --git a/shared/src/i18n/nl/tours.ts b/shared/src/i18n/nl/tours.ts
index 38b82a0fa..8504f2e9b 100644
--- a/shared/src/i18n/nl/tours.ts
+++ b/shared/src/i18n/nl/tours.ts
@@ -579,7 +579,7 @@ const tours: TranslationStrings = {
'{name} sluit om {closes}, tijdens het verblijf van {arrival} tot {leave} (geopend {window})',
'tours.walk.hours.unread': 'Openingstijden hier niet gelezen: {text}',
'tours.walk.hours.source':
- 'Openingstijden uit OpenStreetMap of je eigen plekken, vergeleken met de geschatte aankomst in de lokale tijd van het plan; controleer ze voor je gaat. Een halte zonder tijden is onbekend, niet open.',
+ 'Openingstijden uit OpenStreetMap of de plekken van de reis, vergeleken met de geschatte aankomst in de lokale tijd van het plan; 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.walk.day.label': 'Tijden voor',
'tours.walk.day.start': '{day}, vertrek {time}',
diff --git a/shared/src/i18n/pl/tours.ts b/shared/src/i18n/pl/tours.ts
index c0550f294..fe48b87bb 100644
--- a/shared/src/i18n/pl/tours.ts
+++ b/shared/src/i18n/pl/tours.ts
@@ -586,7 +586,7 @@ const tours: TranslationStrings = {
'{name}: zamknięcie o {closes}, w trakcie pobytu od {arrival} do {leave} (otwarte {window})',
'tours.walk.hours.unread': 'Godziny otwarcia nieodczytane: {text}',
'tours.walk.hours.source':
- 'Godziny otwarcia z OpenStreetMap lub z twoich własnych miejsc, porównane z szacowanym przyjściem w czasie lokalnym planu; sprawdź przed wyjściem. Przystanek bez godzin jest nieznany, nie otwarty.',
+ 'Godziny otwarcia z OpenStreetMap lub z miejsc podróży, porównane z szacowanym przyjściem w czasie lokalnym planu; 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.walk.day.label': 'Godziny dla',
'tours.walk.day.start': '{day}, początek {time}',
diff --git a/shared/src/i18n/ru/tours.ts b/shared/src/i18n/ru/tours.ts
index 225d1f160..bb6b3ccec 100644
--- a/shared/src/i18n/ru/tours.ts
+++ b/shared/src/i18n/ru/tours.ts
@@ -588,7 +588,7 @@ const tours: TranslationStrings = {
'{name}: закрывается в {closes}, во время пребывания с {arrival} до {leave} (открыто {window})',
'tours.walk.hours.unread': 'Часы работы здесь не разобраны: {text}',
'tours.walk.hours.source':
- 'Часы работы из OpenStreetMap или из твоих собственных мест, относительно расчётного прибытия по местному времени плана; проверь перед выходом. Остановка без часов неизвестна, а не открыта.',
+ 'Часы работы из OpenStreetMap или из мест поездки, относительно расчётного прибытия по местному времени плана; проверь перед выходом. Остановка без часов неизвестна, а не открыта.',
'tours.walk.hours.noStart': 'Задай прогулке время в этот день, чтобы видеть прибытия и часы работы.',
'tours.walk.day.label': 'Время для',
'tours.walk.day.start': '{day}, начало в {time}',
diff --git a/shared/src/i18n/sk/tours.ts b/shared/src/i18n/sk/tours.ts
index 35e6098a9..8ad7f27f1 100644
--- a/shared/src/i18n/sk/tours.ts
+++ b/shared/src/i18n/sk/tours.ts
@@ -568,7 +568,7 @@ const tours: TranslationStrings = {
'tours.walk.hours.closes': '{name}: zatvára o {closes}, počas pobytu od {arrival} do {leave} (otvorené {window})',
'tours.walk.hours.unread': 'Otváracie hodiny tu nemožno vyhodnotiť: {text}',
'tours.walk.hours.source':
- 'Otváracie hodiny z OpenStreetMap alebo z tvojich vlastných miest, porovnané s odhadovaným príchodom v miestnom čase plánu; pred cestou si ich over. Zastávka bez časov je neznáma, nie otvorená.',
+ 'Otváracie hodiny z OpenStreetMap alebo z miest výletu, porovnané s odhadovaným príchodom v miestnom čase plánu; 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.walk.day.label': 'Časy pre',
'tours.walk.day.start': '{day}, začiatok {time}',
diff --git a/shared/src/i18n/sv/tours.ts b/shared/src/i18n/sv/tours.ts
index 06ccd739c..a8272be1a 100644
--- a/shared/src/i18n/sv/tours.ts
+++ b/shared/src/i18n/sv/tours.ts
@@ -567,7 +567,7 @@ const tours: TranslationStrings = {
'tours.walk.hours.closes': '{name} stänger kl. {closes}, under vistelsen {arrival} till {leave} (öppet {window})',
'tours.walk.hours.unread': 'Öppettiderna kunde inte läsas här: {text}',
'tours.walk.hours.source':
- 'Öppettider från OpenStreetMap eller dina egna platser, jämförda med beräknad ankomst i planens lokala tid; kontrollera innan du går. Ett stopp utan tider är okänt, inte öppet.',
+ 'Öppettider från OpenStreetMap eller resans platser, jämförda med beräknad ankomst i planens lokala tid; 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.walk.day.label': 'Tider för',
'tours.walk.day.start': '{day}, start kl. {time}',
diff --git a/shared/src/i18n/th/tours.ts b/shared/src/i18n/th/tours.ts
index e998cdaa0..5558452a2 100644
--- a/shared/src/i18n/th/tours.ts
+++ b/shared/src/i18n/th/tours.ts
@@ -542,7 +542,7 @@ const tours: TranslationStrings = {
'tours.walk.hours.closes': '{name} ปิด {closes} ระหว่างแวะตั้งแต่ {arrival} ถึง {leave} (เปิด {window})',
'tours.walk.hours.unread': 'อ่านเวลาทำการไม่ได้: {text}',
'tours.walk.hours.source':
- 'เวลาทำการจาก OpenStreetMap หรือจากสถานที่ที่คุณใส่เอง เทียบกับเวลาถึงโดยประมาณตามเวลาท้องถิ่นของแผน โปรดตรวจสอบก่อนไป จุดแวะที่ไม่มีเวลาทำการถือว่าไม่ทราบ ไม่ใช่เปิดอยู่',
+ 'เวลาทำการจาก OpenStreetMap หรือจากสถานที่ในทริป เทียบกับเวลาถึงโดยประมาณตามเวลาท้องถิ่นของแผน โปรดตรวจสอบก่อนไป จุดแวะที่ไม่มีเวลาทำการถือว่าไม่ทราบ ไม่ใช่เปิดอยู่',
'tours.walk.hours.noStart': 'กำหนดเวลาให้ทริปในวันนี้เพื่อดูเวลาถึงและเวลาทำการ',
'tours.walk.day.label': 'เวลาสำหรับ',
'tours.walk.day.start': '{day} เริ่ม {time}',
diff --git a/shared/src/i18n/tr/tours.ts b/shared/src/i18n/tr/tours.ts
index 0759e4847..023f690c6 100644
--- a/shared/src/i18n/tr/tours.ts
+++ b/shared/src/i18n/tr/tours.ts
@@ -572,7 +572,7 @@ const tours: TranslationStrings = {
'{name} {closes} saatinde kapanıyor, {arrival} ile {leave} arasındaki kalış sırasında (açık {window})',
'tours.walk.hours.unread': 'Açılış saatleri burada okunamadı: {text}',
'tours.walk.hours.source':
- "Açılış saatleri OpenStreetMap'ten veya kendi yerlerinden, planın yerel saatine göre tahmini varışla karşılaştırılır; gitmeden önce kontrol et. Saati olmayan durak bilinmiyordur, açık değildir.",
+ "Açılış saatleri OpenStreetMap'ten veya seyahatin yerlerinden, planın yerel saatine göre tahmini varışla karşılaştırılır; 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.walk.day.label': 'Saatler:',
'tours.walk.day.start': '{day}, başlangıç {time}',
diff --git a/shared/src/i18n/uk/tours.ts b/shared/src/i18n/uk/tours.ts
index f77b7cd43..5e6a8dada 100644
--- a/shared/src/i18n/uk/tours.ts
+++ b/shared/src/i18n/uk/tours.ts
@@ -584,7 +584,7 @@ const tours: TranslationStrings = {
'{name}: зачиняється о {closes}, під час перебування з {arrival} до {leave} (відчинено {window})',
'tours.walk.hours.unread': 'Години роботи тут не розібрано: {text}',
'tours.walk.hours.source':
- 'Години роботи з OpenStreetMap або з твоїх власних місць, відносно розрахункового прибуття за місцевим часом плану; перевір перед виходом. Зупинка без годин невідома, а не відкрита.',
+ 'Години роботи з OpenStreetMap або з місць подорожі, відносно розрахункового прибуття за місцевим часом плану; перевір перед виходом. Зупинка без годин невідома, а не відкрита.',
'tours.walk.hours.noStart': 'Задай прогулянці час цього дня, щоб бачити прибуття й години роботи.',
'tours.walk.day.label': 'Час для',
'tours.walk.day.start': '{day}, початок о {time}',
diff --git a/shared/src/i18n/vi/tours.ts b/shared/src/i18n/vi/tours.ts
index f03d8076b..ff0488e64 100644
--- a/shared/src/i18n/vi/tours.ts
+++ b/shared/src/i18n/vi/tours.ts
@@ -566,7 +566,7 @@ const tours: TranslationStrings = {
'tours.walk.hours.closes': '{name} đóng cửa lúc {closes}, trong lúc ở từ {arrival} đến {leave} (mở {window})',
'tours.walk.hours.unread': 'Không đọc được giờ mở cửa ở đây: {text}',
'tours.walk.hours.source':
- 'Giờ mở cửa từ OpenStreetMap hoặc từ các địa điểm bạn tự nhập, so với giờ đến ước tính theo giờ địa phương của kế hoạch; 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ở.',
+ 'Giờ mở cửa từ OpenStreetMap hoặc từ các địa điểm của chuyến đi, so với giờ đến ước tính theo giờ địa phương của kế hoạch; 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.walk.day.label': 'Thời gian theo',
'tours.walk.day.start': '{day}, bắt đầu {time}',
diff --git a/shared/src/i18n/zh-TW/tours.ts b/shared/src/i18n/zh-TW/tours.ts
index f29bc7664..dcda33da4 100644
--- a/shared/src/i18n/zh-TW/tours.ts
+++ b/shared/src/i18n/zh-TW/tours.ts
@@ -522,7 +522,7 @@ const tours: TranslationStrings = {
'tours.walk.hours.closes': '{name} 在 {closes} 關門,正值 {arrival} 至 {leave} 的停留期間(營業時間 {window})',
'tours.walk.hours.unread': '此處無法解析營業時間:{text}',
'tours.walk.hours.source':
- '營業時間來自 OpenStreetMap 或你自己填寫的地點,並按計畫的當地時間與預計抵達時間對照;出發前請再確認。沒有營業時間的站點視為未知,而非營業中。',
+ '營業時間來自 OpenStreetMap 或行程中的地點,並按計畫的當地時間與預計抵達時間對照;出發前請再確認。沒有營業時間的站點視為未知,而非營業中。',
'tours.walk.hours.noStart': '為當天的漫步設定時間,即可查看抵達時間和營業時間。',
'tours.walk.day.label': '時間依據',
'tours.walk.day.start': '{day},{time} 出發',
diff --git a/shared/src/i18n/zh/tours.ts b/shared/src/i18n/zh/tours.ts
index da99ca533..de58b1cf5 100644
--- a/shared/src/i18n/zh/tours.ts
+++ b/shared/src/i18n/zh/tours.ts
@@ -523,7 +523,7 @@ const tours: TranslationStrings = {
'tours.walk.hours.closes': '{name} 在 {closes} 关门,正值 {arrival} 至 {leave} 的停留期间(营业时间 {window})',
'tours.walk.hours.unread': '此处无法解析营业时间:{text}',
'tours.walk.hours.source':
- '营业时间来自 OpenStreetMap 或你自己填写的地点,并按计划的当地时间与预计到达时间对照;出发前请再确认。没有营业时间的站点视为未知,而非营业中。',
+ '营业时间来自 OpenStreetMap 或行程中的地点,并按计划的当地时间与预计到达时间对照;出发前请再确认。没有营业时间的站点视为未知,而非营业中。',
'tours.walk.hours.noStart': '为当天的漫步设定时间,即可查看到达时间和营业时间。',
'tours.walk.day.label': '时间依据',
'tours.walk.day.start': '{day},{time} 出发',
diff --git a/shared/src/tours/walk-hours.spec.ts b/shared/src/tours/walk-hours.spec.ts
index 390e86f4a..f5af1d3eb 100644
--- a/shared/src/tours/walk-hours.spec.ts
+++ b/shared/src/tours/walk-hours.spec.ts
@@ -21,7 +21,11 @@ const SATURDAY = '2026-10-24';
const SUNDAY = '2026-10-25';
const at = (clock: string, atLeast = false) => ({ arrival: minutesOf(clock) ?? Number.NaN, atLeast });
-const osm = (windows: Array<[number, number]>): StationHours => ({ source: 'osm', windows });
+const osm = (windows: Array<[number, number]>, continues = false): StationHours => ({
+ source: 'osm',
+ windows,
+ continues,
+});
describe('minutesOf and clockOf', () => {
it('reads a time as TREK stores it, with or without seconds or a leading zero', () => {
@@ -92,6 +96,8 @@ describe('openWindows (OpenStreetMap)', () => {
expect(openWindows(text, MONDAY)).toBeNull();
}
expect(openWindows(week, '19.10.2026')).toBeNull();
+ expect(openWindows(week, '2026-02-31')).toBeNull();
+ expect(openWindows(week, '2026-13-01')).toBeNull();
});
});
@@ -148,6 +154,7 @@ describe('stationHoursOn', () => {
expect(stationHoursOn({ place, osm: 'Mo-Fr 09:00-18:00' }, MONDAY)).toEqual({
source: 'place',
windows: [[660, 1020]],
+ continues: false,
});
});
@@ -155,13 +162,42 @@ describe('stationHoursOn', () => {
expect(stationHoursOn({ place, osm: 'Mo-Fr 09:00-18:00' }, TUESDAY)).toEqual({
source: 'osm',
windows: [[540, 1080]],
+ continues: false,
});
expect(stationHoursOn({ osm: 'sunrise-sunset' }, TUESDAY)).toEqual({ source: 'osm', unread: 'sunrise-sunset' });
});
- it('is null where neither says anything', () => {
+ it('is null where neither says anything, or for a date it cannot read', () => {
expect(stationHoursOn({ place, osm: ' ' }, TUESDAY)).toBeNull();
expect(stationHoursOn({}, MONDAY)).toBeNull();
+ expect(stationHoursOn({ place, osm: 'Mo-Fr 09:00-18:00' }, '2026-02-31')).toBeNull();
+ });
+
+ it('runs an OpenStreetMap last window on past midnight only where the next date opens at midnight', () => {
+ const continuesOn = (text: string, date: string) => {
+ const hours = stationHoursOn({ osm: text }, date);
+ return hours && 'continues' in hours ? hours.continues : null;
+ };
+ expect(continuesOn('24/7', SUNDAY)).toBe(true);
+ expect(continuesOn('Mo 18:00-24:00; Tu 00:00-02:00', MONDAY)).toBe(true);
+ expect(continuesOn('Mo-Su 10:00-24:00', MONDAY)).toBe(false);
+ expect(continuesOn('Mo-Su 10:00-18:00', MONDAY)).toBe(false);
+ expect(continuesOn('Mo 18:00-24:00; Tu off', MONDAY)).toBe(false);
+ });
+
+ it("runs a trip place's last window on past midnight only where the next date opens at midnight", () => {
+ const week = (monday: object, tuesday: object) =>
+ JSON.stringify([monday, tuesday, ...Array.from({ length: 5 }, () => ({ closed: true }))]);
+ const continuesOn = (raw: string) => {
+ const hours = stationHoursOn({ place: raw }, MONDAY);
+ return hours && 'continues' in hours ? hours.continues : null;
+ };
+ expect(continuesOn(week({ closed: false, open: '18:00', close: '02:00' }, { closed: true }))).toBe(true);
+ const allDay = { closed: false, open: '00:00', close: '00:00' };
+ expect(continuesOn(week(allDay, allDay))).toBe(true);
+ expect(continuesOn(week({ closed: false, open: '18:00', close: '00:00' }, { closed: true }))).toBe(false);
+ expect(continuesOn(week({ closed: false, open: '18:00', close: '00:00' }, { closed: false }))).toBe(false);
+ expect(continuesOn(week({ closed: true }, allDay))).toBe(false);
});
});
@@ -181,14 +217,27 @@ describe('stationFit', () => {
it('is closed at an arrival outside the windows, with the windows of the day', () => {
expect(stationFit(museum, at('09:00'), 30)).toEqual({ kind: 'closed', source: 'osm', windows: [[600, 1080]] });
expect(stationFit(museum, at('18:00'), null)).toEqual({ kind: 'closed', source: 'osm', windows: [[600, 1080]] });
- expect(stationFit({ source: 'place', windows: [] }, at('12:00'), 30)).toEqual({
+ expect(stationFit({ source: 'place', windows: [], continues: false }, at('12:00'), 30)).toEqual({
kind: 'closedAllDay',
source: 'place',
});
});
- it('takes a window running to midnight as open past it', () => {
- expect(stationFit(osm([[600, 1440]]), at('23:20'), 90).kind).toBe('open');
+ it('takes a window to midnight as open past it only where the hours run on into the next date', () => {
+ expect(stationFit(osm([[600, 1440]], true), at('23:20'), 90).kind).toBe('open');
+ expect(stationFit(osm([[600, 1440]]), at('23:30'), 60)).toEqual({
+ kind: 'closes',
+ source: 'osm',
+ window: [600, 1440],
+ });
+ expect(stationFit(osm([[600, 1440]]), at('23:20'), 30).kind).toBe('open');
+ expect(stationFit(osm([[600, 1440]]), at('23:30', true), 60).kind).toBe('closes');
+ });
+
+ it('names the closing at midnight of OpenStreetMap hours ending at 24:00 for a stay past it', () => {
+ const hours = stationHoursOn({ osm: 'Mo-Su 10:00-24:00' }, MONDAY);
+ expect(stationFit(hours, at('23:30'), 60)).toEqual({ kind: 'closes', source: 'osm', window: [600, 1440] });
+ expect(stationFit(stationHoursOn({ osm: '24/7' }, MONDAY), at('23:30'), 60).kind).toBe('open');
});
it('says nothing for hours not known, and gives unread hours back as written', () => {
diff --git a/shared/src/tours/walk-hours.ts b/shared/src/tours/walk-hours.ts
index b122f348b..cd7871ad5 100644
--- a/shared/src/tours/walk-hours.ts
+++ b/shared/src/tours/walk-hours.ts
@@ -28,8 +28,14 @@ export type HoursWindow = readonly [number, number];
/** Where a stop's hours come from: OpenStreetMap, or the trip place's own entry. */
export type HoursSource = 'osm' | 'place';
-/** A stop's hours on one date: its open windows (none when closed all day), or text that could not be read. */
-export type StationHours = { source: HoursSource; windows: readonly HoursWindow[] } | { source: 'osm'; unread: string };
+/**
+ * A stop's hours on one date: its open windows (none when closed all day), or
+ * text that could not be read. `continues` says the last window, ending at
+ * midnight, runs on into the next date, as it does around the clock or for a
+ * night past midnight; a place closing at midnight does not.
+ */
+export type StationHours =
+ { source: HoursSource; windows: readonly HoursWindow[]; continues: boolean } | { source: 'osm'; unread: string };
function clockMinutes(hours: number, minutes: number): number | null {
return hours < 24 && minutes < 60 ? hours * 60 + minutes : null;
@@ -58,8 +64,12 @@ export function clockOf(minutes: number): string {
function weekdayOf(date: string): number | null {
const match = ISO_DATE.exec(date);
if (!match) return null;
- const day = new Date(Date.UTC(Number(match[1]), Number(match[2]) - 1, Number(match[3]))).getUTCDay();
- return Number.isNaN(day) ? null : (day + 6) % 7;
+ const month = Number(match[2]) - 1;
+ const dayOfMonth = Number(match[3]);
+ const day = new Date(Date.UTC(Number(match[1]), month, dayOfMonth));
+ // A date that does not exist, such as 2026-02-31, would roll over into the next month.
+ if (day.getUTCMonth() !== month || day.getUTCDate() !== dayOfMonth) return null;
+ return (day.getUTCDay() + 6) % 7;
}
/** The weekdays of an OpenStreetMap day list such as `Mo-Fr,Su`; a range may wrap round the week (`Sa-Mo`). */
@@ -106,8 +116,11 @@ function ruleOf(rule: string): { days: Set; windows: HoursWindow[] } | n
/** The windows OpenStreetMap's hours give a date, in minutes; null where they cannot be read. */
export function openWindows(hours: string, date: string): HoursWindow[] | null {
const weekday = weekdayOf(date);
- if (weekday === null) return null;
- const text = hours.trim();
+ return weekday === null ? null : osmWindows(hours.trim(), weekday);
+}
+
+/** The windows OpenStreetMap's hours give a weekday, Monday 0; null where they cannot be read. */
+function osmWindows(text: string, weekday: number): HoursWindow[] | null {
if (text === '24/7') return [[0, DAY_MINUTES]];
let windows: HoursWindow[] = [];
for (const raw of text.split(';')) {
@@ -147,7 +160,11 @@ function spillOf(day: PlaceHoursDay | undefined): HoursWindow | null {
export function placeWindows(raw: string | null | undefined, date: string): HoursWindow[] | null {
const week = parsePlaceHours(raw);
const weekday = weekdayOf(date);
- if (!week || weekday === null) return null;
+ return week && weekday !== null ? placeWeekWindows(week, weekday) : null;
+}
+
+/** The windows of one weekday of a place's own week, Monday 0, with the night of the day before. */
+function placeWeekWindows(week: readonly PlaceHoursDay[], weekday: number): HoursWindow[] | null {
const today = dayWindows(week[weekday]);
if (today === null) return null;
const spill = spillOf(week[(weekday + 6) % 7]);
@@ -157,18 +174,35 @@ export function placeWindows(raw: string | null | undefined, date: string): Hour
/**
* A stop's hours on a date: the trip place it stands for first, as the
* travellers entered them, else what OpenStreetMap had for it; null where
- * neither says anything.
+ * neither says anything or the date cannot be read.
*/
export function stationHoursOn(
hours: { place?: string | null; osm?: string | null },
date: string,
): StationHours | null {
- const own = placeWindows(hours.place, date);
- if (own) return { source: 'place', windows: own };
+ const weekday = weekdayOf(date);
+ if (weekday === null) return null;
+ const tomorrow = (weekday + 1) % 7;
+ const week = parsePlaceHours(hours.place);
+ const own = week && placeWeekWindows(week, weekday);
+ if (week && own) {
+ return { source: 'place', windows: own, continues: runsOn(own, placeWeekWindows(week, tomorrow)) };
+ }
const text = hours.osm?.trim();
if (!text) return null;
- const windows = openWindows(text, date);
- return windows ? { source: 'osm', windows } : { source: 'osm', unread: text };
+ const windows = osmWindows(text, weekday);
+ return windows
+ ? { source: 'osm', windows, continues: runsOn(windows, osmWindows(text, tomorrow)) }
+ : { source: 'osm', unread: text };
+}
+
+/**
+ * Whether a window of the day ends at midnight and the next day opens at
+ * midnight, so the hours run on past it. Every window is looked at, as the
+ * hours text keeps the order it was written in.
+ */
+function runsOn(today: readonly HoursWindow[], tomorrow: readonly HoursWindow[] | null): boolean {
+ return today.some(([, to]) => to === DAY_MINUTES) && (tomorrow?.some(([from]) => from === 0) ?? false);
}
/**
@@ -202,10 +236,10 @@ function fitInWindow(
source: HoursSource,
window: HoursWindow,
leave: number | null,
- later: { atLeast: boolean; windowAfter: boolean },
+ later: { atLeast: boolean; windowAfter: boolean; continues: boolean },
): StationFit {
- // A window running to midnight is taken as open past it.
- const closes = leave !== null && leave > window[1] && window[1] !== DAY_MINUTES;
+ // A window to midnight is open past it only where the hours run on into the next date.
+ const closes = leave !== null && leave > window[1] && !(window[1] === DAY_MINUTES && later.continues);
// An arrival that may come later can still miss the closing, or reach the next window instead.
if (later.atLeast)
return closes && !later.windowAfter ? { kind: 'closes', source, window } : { kind: 'uncertain', source };
@@ -221,12 +255,12 @@ export function stationFit(hours: StationHours | null, at: WalkArrival, stay: nu
if (at.arrival >= DAY_MINUTES) return { kind: 'nextDay' };
if (!hours) return { kind: 'unknown' };
if ('unread' in hours) return { kind: 'unread', text: hours.unread };
- const { source, windows } = hours;
+ const { source, windows, continues } = hours;
if (windows.length === 0) return { kind: 'closedAllDay', source };
const leave = stay === null ? null : at.arrival + stay;
const window = windows.find(([from, to]) => at.arrival >= from && at.arrival < to);
const windowAfter = windows.some(([from]) => from > at.arrival);
- if (window) return fitInWindow(source, window, leave, { atLeast: at.atLeast, windowAfter });
+ if (window) return fitInWindow(source, window, leave, { atLeast: at.atLeast, windowAfter, continues });
return at.atLeast && windowAfter ? { kind: 'uncertain', source } : { kind: 'closed', source, windows };
}