From be6702f3ab6f2835ce9a8d6c9ad7dee34a8e6c0d Mon Sep 17 00:00:00 2001 From: Maurice Date: Tue, 20 Oct 2026 18:39:04 +0200 Subject: [PATCH] fix(tours): keep the shipped ski migrations and follow the ski tour through The winter label and four-discipline migrations are already on feat/tours-board-rest, so a database that ran them would refuse to boot or fail the old CHECK. 045500, 045520 and 046100 are back as they shipped, and a new migration drops both winter_activity columns and adds ski_discipline again with the six disciplines. The entity and table types follow the column to its new place. German, Hungarian, Indonesian and English no longer name the whole type with the ski touring word, and the info dot speaks of ski tours and tracks. search_tour_pois and its wiki row list rides and ski and winter tours. Snowshoeing gets a snowshoe glyph instead of the walk's footprints, a ski import without a discipline can be planned over (the planner asks for it), and an overlay shown by default tries its tiles afresh each time it comes on. --- .../Tours/activity/TourActivityBadge.test.tsx | 2 +- .../Tours/activity/TourActivitySection.tsx | 4 +- .../conversion/TourConversionSection.test.tsx | 19 +++-- .../Tours/conversion/useGpxConversion.ts | 6 +- .../src/components/Tours/ski/skiDiscipline.ts | 25 ++++-- .../components/Tours/ski/skiMarks.test.tsx | 8 +- .../Tours/useTourRasterOverlay.test.ts | 17 ++++ .../components/Tours/useTourRasterOverlay.ts | 8 +- server/src/db/entities/Tours.entity.ts | 20 ++--- server/src/db/kysely/Tours.table.ts | 2 +- ...hike_can_be_marked_as_a_winter_activity.ts | 16 ++++ ...0200101045520_a_tour_can_be_a_ski_track.ts | 16 ++-- ...01046100_a_collection_keeps_whole_tours.ts | 1 + ...g_and_winter_hiking_are_ski_disciplines.ts | 41 ++++++++++ server/src/middleware/globalMiddleware.ts | 2 +- server/src/nest/tours/tours.mcp.ts | 2 +- server/tests/unit/db/legacy-baseline.test.ts | 2 + server/tests/unit/db/tour-ski-columns.test.ts | 11 +-- .../db/tour-ski-winter-disciplines.test.ts | 81 +++++++++++++++++++ server/tests/unit/nest/tours.mcp.test.ts | 2 + shared/src/i18n/ar/tours.ts | 2 +- shared/src/i18n/az/tours.ts | 2 +- shared/src/i18n/br/tours.ts | 2 +- shared/src/i18n/ca/tours.ts | 2 +- shared/src/i18n/cs/tours.ts | 2 +- shared/src/i18n/de/tours.ts | 6 +- shared/src/i18n/en/tours.ts | 6 +- shared/src/i18n/es/tours.ts | 2 +- shared/src/i18n/et/tours.ts | 2 +- shared/src/i18n/fr/tours.ts | 2 +- shared/src/i18n/gr/tours.ts | 2 +- shared/src/i18n/hu/tours.ts | 6 +- shared/src/i18n/id/tours.ts | 6 +- shared/src/i18n/it/tours.ts | 2 +- shared/src/i18n/ja/tours.ts | 2 +- shared/src/i18n/ko/tours.ts | 2 +- shared/src/i18n/nl/tours.ts | 2 +- shared/src/i18n/pl/tours.ts | 2 +- shared/src/i18n/ru/tours.ts | 2 +- shared/src/i18n/sk/tours.ts | 2 +- shared/src/i18n/sv/tours.ts | 2 +- shared/src/i18n/th/tours.ts | 2 +- shared/src/i18n/tr/tours.ts | 2 +- shared/src/i18n/uk/tours.ts | 2 +- shared/src/i18n/vi/tours.ts | 2 +- shared/src/i18n/zh-TW/tours.ts | 2 +- shared/src/i18n/zh/tours.ts | 2 +- wiki/MCP-Addon-Tools.md | 2 +- 48 files changed, 273 insertions(+), 84 deletions(-) create mode 100644 server/src/db/migrations/Migration20200101045500_a_hike_can_be_marked_as_a_winter_activity.ts create mode 100644 server/src/db/migrations/Migration20200101046130_snowshoeing_and_winter_hiking_are_ski_disciplines.ts create mode 100644 server/tests/unit/db/tour-ski-winter-disciplines.test.ts diff --git a/client/src/components/Tours/activity/TourActivityBadge.test.tsx b/client/src/components/Tours/activity/TourActivityBadge.test.tsx index 3e7d21ae2..45e6a4ddb 100644 --- a/client/src/components/Tours/activity/TourActivityBadge.test.tsx +++ b/client/src/components/Tours/activity/TourActivityBadge.test.tsx @@ -11,7 +11,7 @@ describe('TourActivityBadge (ACT-01)', () => { ); expect(screen.getByTestId('tour-activity-badge')).toHaveAttribute('aria-label', 'tours.activity.badgeHint'); - // The done mark, never footprints, which name a snowshoe hike and a walk's moving time. + // The done mark, never footprints, which name a walk's moving time. expect(screen.getByTestId('tour-activity-badge').querySelector('svg.lucide-check-check')).not.toBeNull(); expect(screen.getByTestId('tour-activity-badge').querySelector('svg.lucide-footprints')).toBeNull(); diff --git a/client/src/components/Tours/activity/TourActivitySection.tsx b/client/src/components/Tours/activity/TourActivitySection.tsx index c972cbe8b..9cb558a2a 100644 --- a/client/src/components/Tours/activity/TourActivitySection.tsx +++ b/client/src/components/Tours/activity/TourActivitySection.tsx @@ -32,8 +32,8 @@ interface PlanSide { /** * The tile of a recording: the done mark every actual track goes by (on the * list row, in the tour's head and here), with a small dot at its foot that - * says it was recorded. Never footprints, which name a snowshoe hike and a - * walk's moving time. + * says it was recorded. Never footprints, which name a city walk and a walk's + * moving time. */ function RecordedTile() { return ( diff --git a/client/src/components/Tours/conversion/TourConversionSection.test.tsx b/client/src/components/Tours/conversion/TourConversionSection.test.tsx index 0fded6c53..acb12fa02 100644 --- a/client/src/components/Tours/conversion/TourConversionSection.test.tsx +++ b/client/src/components/Tours/conversion/TourConversionSection.test.tsx @@ -244,18 +244,25 @@ describe('useGpxConversion', () => { ); }); - it('SKI-02: a ski track that names no discipline is not planned over: nothing is offered and no router is asked', () => { + it('SKI-02: a ski track that names no discipline is planned over too, the planner asks for it', async () => { const ski = { ...imported, tour_type: 'ski' as const, ski_discipline: null, max_hiking_difficulty: null }; const controller = planner({ mode: { type: 'view-gpx', placeId: ski.place_id, tour: ski }, readOnlyGpxTour: { tour: ski, routeGeometry: null }, } as Partial); const { result } = renderHook(() => useGpxConversion(controller)); - expect(result.current.available).toBe(false); - const { container } = render(); - expect(container).toBeEmptyDOMElement(); - expect(routing.route).not.toHaveBeenCalled(); - expect(routing.run).not.toHaveBeenCalled(); + expect(result.current.available).toBe(true); + expect(result.current.ski).toBe(true); + await act(async () => { + await result.current.analyze(); + }); + act(() => { + result.current.adopt('new'); + }); + // Nothing is chosen for the skier: the draft opens without a discipline, and saving waits for one. + expect(controller.startDerivedTour).toHaveBeenCalledWith( + expect.objectContaining({ tourType: 'ski', ski: { discipline: null, technique: null } }) + ); }); it('PR1-05B: nothing is adopted before the suggestion is ready', () => { diff --git a/client/src/components/Tours/conversion/useGpxConversion.ts b/client/src/components/Tours/conversion/useGpxConversion.ts index 3781dcb9d..6c28002c7 100644 --- a/client/src/components/Tours/conversion/useGpxConversion.ts +++ b/client/src/components/Tours/conversion/useGpxConversion.ts @@ -5,7 +5,7 @@ import { isTourRoute } from '../planner/tourRouteOutcome'; import { routeBikeTour, routeRun, routeWalkingTour } from '../planner/tourRouting'; import { paddleFieldsOf } from '../planner/usePaddleFields'; import type { TourPlannerController } from '../planner/useTourPlanner'; -import { skiChoiceMissing, skiChoiceOf } from '../ski/skiChoice'; +import { skiChoiceOf } from '../ski/skiChoice'; import { compareRoutes, deriveControlPoints, type RouteComparison, type TrackPoint } from './gpxConversion'; /** The grade a planned version routes with when the import carries none: the paths a T3 walk may take. */ @@ -132,12 +132,12 @@ export function useGpxConversion(planner: TourPlannerController) { ...current, /** * The import shown can be planned from: it has a track and the user may add tours. A ski - * track (SKI-02) once it names its discipline, planned as a ski tour with its legs drawn. + * track (SKI-02) is planned as a ski tour with its legs drawn; one saved before it named a + * discipline is offered too, and the planner asks for the discipline before it saves. * A ride (HOR-01) never: no router plans one, and walking it again on foot would be no ride. */ available: tour !== null && - (ski === null || skiChoiceMissing(ski) === null) && tour.tour_type !== 'horse' && (track?.length ?? 0) >= 2 && planner.canEdit, diff --git a/client/src/components/Tours/ski/skiDiscipline.ts b/client/src/components/Tours/ski/skiDiscipline.ts index ecdeaef4b..ba1e1a99b 100644 --- a/client/src/components/Tours/ski/skiDiscipline.ts +++ b/client/src/components/Tours/ski/skiDiscipline.ts @@ -1,5 +1,5 @@ import type { SkiDiscipline, SkiTechnique, TourListItem } from '@trek/shared'; -import { CableCar, createLucideIcon, Footprints, Snowflake, Spline, TrendingUp, type LucideIcon } from 'lucide-react'; +import { CableCar, createLucideIcon, Snowflake, Spline, TrendingUp, type LucideIcon } from 'lucide-react'; /** * SKI-02: a snowboard seen from above, laid across the square with its two @@ -14,19 +14,34 @@ const Snowboard = createLucideIcon('snowboard', [ ['path', { d: 'M15.5 10v4', transform: BOARD, key: 'binding-back' }], ]); +/** + * SKI-02: a snowshoe seen from above, the racket frame narrowing to its tail, + * with the lacing across and the binding in the middle. Footprints already + * mean a city walk and a walk's moving time, so a snowshoe tile marked with + * them would read as a walk. + */ +export const Snowshoe = createLucideIcon('snowshoe', [ + ['path', { d: 'M12 3c3.3 0 5.5 2.8 5.5 6.5 0 4-2.4 7.6-5.5 12-3.1-4.4-5.5-8-5.5-12S8.7 3 12 3z', key: 'frame' }], + ['path', { d: 'M7 7.5h10', key: 'lacing-front' }], + ['path', { d: 'M7.3 13.5h9.4', key: 'lacing-back' }], + ['path', { d: 'M12 3v18.5', key: 'lacing-middle' }], + ['path', { d: 'M9.5 10.5h5', key: 'binding' }], +]); + /** * SKI-02: the icon each discipline goes by, each its own: a lift for alpine * skiing, a board for snowboarding, a winding trail for cross-country, the - * climb on skins for ski touring, footprints in the snow for snowshoeing and a - * snowflake for winter hiking. They all take the same corner of a tile, so no - * two share a glyph, and none is the tile's own mountain. + * climb on skins for ski touring, a snowshoe for snowshoeing and a snowflake + * for winter hiking. They all take the same corner of a tile, so no two share + * a glyph, none is the tile's own mountain, and none is the mark of another + * tour type or fact (footprints stay the walk's). */ export const SKI_DISCIPLINE_ICON: Record = { alpine: CableCar, snowboard: Snowboard, cross_country: Spline, touring: TrendingUp, - snowshoe: Footprints, + snowshoe: Snowshoe, winter_hike: Snowflake, }; diff --git a/client/src/components/Tours/ski/skiMarks.test.tsx b/client/src/components/Tours/ski/skiMarks.test.tsx index d33b32ea8..39b0e231d 100644 --- a/client/src/components/Tours/ski/skiMarks.test.tsx +++ b/client/src/components/Tours/ski/skiMarks.test.tsx @@ -78,12 +78,18 @@ describe('ski marks (SKI-02)', () => { expect(icons).toHaveLength(6); expect(new Set(icons).size).toBe(icons.length); expect(icons).not.toContain(tourDayRowIcon('ski')); + // No discipline borrows the mark of another tour type either. + for (const type of ['hike', 'bike', 'walk', 'run', 'kayak', 'horse']) { + expect(icons).not.toContain(tourDayRowIcon(type)); + } }); it('FE-TOUR-SKI-MARK-007: snowshoeing and winter hiking are ski tours with their own marks, a hike carries none', () => { const { rerender } = render(); const snowshoe = screen.getByRole('img', { name: 'tours.ski.discipline.snowshoe' }); - expect(snowshoe.querySelector('svg.lucide-footprints')).not.toBeNull(); + expect(snowshoe.querySelector('svg.lucide-snowshoe')).not.toBeNull(); + // Footprints are the city walk's mark, so a snowshoe tile never carries them. + expect(snowshoe.querySelector('svg.lucide-footprints')).toBeNull(); rerender(); const winterHike = screen.getByRole('img', { name: 'tours.ski.discipline.winter_hike' }); expect(winterHike.querySelector('svg.lucide-snowflake')).not.toBeNull(); diff --git a/client/src/components/Tours/useTourRasterOverlay.test.ts b/client/src/components/Tours/useTourRasterOverlay.test.ts index 4549bae5f..bcdeb6bd8 100644 --- a/client/src/components/Tours/useTourRasterOverlay.test.ts +++ b/client/src/components/Tours/useTourRasterOverlay.test.ts @@ -67,6 +67,23 @@ describe('useTourRasterOverlay', () => { expect(result.current.status).toBe('on'); }); + it('tries afresh each time it comes on without its switch, as the pistes of a ski tour do', () => { + const toggle = vi.fn(); + const { result, rerender } = renderHook(({ active }) => useTourRasterOverlay(SEAMARKS, true, active, toggle), { + initialProps: { active: true }, + }); + act(() => { + for (let i = 0; i < 4; i++) result.current.onTileError(); + }); + expect(result.current.status).toBe('down'); + // The tour closes, a later one shows the overlay by default: it opens on, not down. + rerender({ active: false }); + expect(result.current.status).toBe('off'); + rerender({ active: true }); + expect(result.current.status).toBe('on'); + expect(toggle).not.toHaveBeenCalled(); + }); + it('marks an overlay that is on while the map is further out than its first tiles, and nothing at an unknown zoom', () => { const { result, rerender } = renderHook( ({ active, zoom }: { active: boolean; zoom: number | null }) => diff --git a/client/src/components/Tours/useTourRasterOverlay.ts b/client/src/components/Tours/useTourRasterOverlay.ts index 6bf6c5fb5..6319cd2e3 100644 --- a/client/src/components/Tours/useTourRasterOverlay.ts +++ b/client/src/components/Tours/useTourRasterOverlay.ts @@ -44,7 +44,9 @@ export function useBaseMapHealth(baseLayer: string): BaseMapHealth | undefined { /** * An overlay the planner offers, off until switched on; whether it is on is * the planner's (`active`, `onToggle`), so it outlasts the map. Its tile - * health shows on the switch, and switching it off and on again tries afresh. + * health shows on the switch, and each time it comes on it tries afresh, + * whether switched on or shown by default (the pistes of a ski tour), so a + * source that failed earlier never opens a later tour already marked down. * While it is on and the map (`zoom`, on the raster tile scale) is further out * than its first tiles, it is marked so: nothing is drawn there, and the switch * says to zoom in. An unknown zoom marks nothing. @@ -60,6 +62,10 @@ export function useTourRasterOverlay( const on = active && offered; const belowMinZoom = on && zoom !== null && zoom < definition.minZoom; + useEffect(() => { + if (on) reset(); + }, [on, reset]); + const onToggle = useCallback(() => { reset(); toggle(); diff --git a/server/src/db/entities/Tours.entity.ts b/server/src/db/entities/Tours.entity.ts index 614b27c77..dd4ee4041 100644 --- a/server/src/db/entities/Tours.entity.ts +++ b/server/src/db/entities/Tours.entity.ts @@ -46,10 +46,10 @@ export class Tours { bike_profile?: TToursBikeProfile | null; run_profile?: TToursRunProfile | null; run_pace_seconds_per_km?: number | null; - ski_discipline?: TToursSkiDiscipline | null; ski_technique?: TToursSkiTechnique | null; track_started_at?: string | null; track_ended_at?: string | null; + ski_discipline?: TToursSkiDiscipline | null; tourTypeRef!: Ref; tour_activities_collection = new Collection(this); tour_activities_collection1 = new Collection(this); @@ -108,6 +108,14 @@ export const ToursRunProfile = { export type TToursRunProfile = (typeof ToursRunProfile)[keyof typeof ToursRunProfile]; +export const ToursSkiTechnique = { + CLASSIC: 'classic', + SKATING: 'skating', + UNKNOWN: 'unknown', +} as const; + +export type TToursSkiTechnique = (typeof ToursSkiTechnique)[keyof typeof ToursSkiTechnique]; + export const ToursSkiDiscipline = { ALPINE: 'alpine', SNOWBOARD: 'snowboard', @@ -119,14 +127,6 @@ export const ToursSkiDiscipline = { export type TToursSkiDiscipline = (typeof ToursSkiDiscipline)[keyof typeof ToursSkiDiscipline]; -export const ToursSkiTechnique = { - CLASSIC: 'classic', - SKATING: 'skating', - UNKNOWN: 'unknown', -} as const; - -export type TToursSkiTechnique = (typeof ToursSkiTechnique)[keyof typeof ToursSkiTechnique]; - export const ToursSchema = defineEntity({ class: Tours, repository: () => ToursRepository, @@ -177,10 +177,10 @@ export const ToursSchema = defineEntity({ bike_profile: p.enum(() => ToursBikeProfile).nullable(), run_profile: p.enum(() => ToursRunProfile).nullable(), run_pace_seconds_per_km: p.integer().nullable(), - ski_discipline: p.enum(() => ToursSkiDiscipline).nullable(), ski_technique: p.enum(() => ToursSkiTechnique).nullable(), track_started_at: p.type(DbTimestampType).nullable(), track_ended_at: p.type(DbTimestampType).nullable(), + ski_discipline: p.enum(() => ToursSkiDiscipline).nullable(), tourTypeRef: () => p.manyToOne(TourTypes).ref().joinColumn('tour_type').hidden().index('idx_tours_tour_type'), tour_activities_collection: () => p.oneToMany(TourActivities).mappedBy('place').hidden(), tour_activities_collection1: () => p.oneToMany(TourActivities).mappedBy('trackPlace').hidden(), diff --git a/server/src/db/kysely/Tours.table.ts b/server/src/db/kysely/Tours.table.ts index 722c0abca..b3cb34b77 100644 --- a/server/src/db/kysely/Tours.table.ts +++ b/server/src/db/kysely/Tours.table.ts @@ -33,8 +33,8 @@ export interface ToursTable { bike_profile: InsertOptional; run_profile: InsertOptional; run_pace_seconds_per_km: InsertOptional; - ski_discipline: InsertOptional; ski_technique: InsertOptional; track_started_at: InsertOptional; track_ended_at: InsertOptional; + ski_discipline: InsertOptional; } diff --git a/server/src/db/migrations/Migration20200101045500_a_hike_can_be_marked_as_a_winter_activity.ts b/server/src/db/migrations/Migration20200101045500_a_hike_can_be_marked_as_a_winter_activity.ts new file mode 100644 index 000000000..b94cb3397 --- /dev/null +++ b/server/src/db/migrations/Migration20200101045500_a_hike_can_be_marked_as_a_winter_activity.ts @@ -0,0 +1,16 @@ +import { Migration } from '@mikro-orm/migrations'; + +/** + * HIK-01: a hike can carry a winter label, winter hiking or snowshoeing. It + * is a column of the tour, NULL on every tour saved before and on every other + * type, so no existing tour changes, and nothing routes or times by it. + */ +export class Migration20200101045500_a_hike_can_be_marked_as_a_winter_activity extends Migration { + override name = 'Migration20200101045500_a_hike_can_be_marked_as_a_winter_activity'; + + override up(): void { + this.addSql( + "ALTER TABLE tours ADD COLUMN winter_activity TEXT CHECK (winter_activity IS NULL OR winter_activity IN ('winter_hike', 'snowshoe'))", + ); + } +} diff --git a/server/src/db/migrations/Migration20200101045520_a_tour_can_be_a_ski_track.ts b/server/src/db/migrations/Migration20200101045520_a_tour_can_be_a_ski_track.ts index f0daf4dcf..ef3b669a8 100644 --- a/server/src/db/migrations/Migration20200101045520_a_tour_can_be_a_ski_track.ts +++ b/server/src/db/migrations/Migration20200101045520_a_tour_can_be_a_ski_track.ts @@ -1,21 +1,19 @@ import { Migration } from '@mikro-orm/migrations'; /** - * SKI-02: a tour can be a ski and winter tour, planned in the planner or - * imported from a GPX file. The `ski` key has been in `tour_types`, disabled - * and without a routing profile, since every contract type was seeded; it is - * switched on here and keeps no routing profile: the planner draws its legs - * itself. The discipline (alpine, snowboard, cross-country, ski touring, - * snowshoe or winter hike) and, for cross-country, the technique are columns - * of the tour, NULL on every other type, so no existing tour changes its - * meaning. + * SKI-02: a tour can be a ski track, imported from a GPX file and kept as it + * was recorded. The `ski` key has been in `tour_types`, disabled and without + * a routing profile, since every contract type was seeded; it is switched on + * here and keeps no routing profile, since TREK routes no ski track. The + * discipline and, for cross-country, the technique are columns of the tour, + * NULL on every other type, so no existing tour changes its meaning. */ export class Migration20200101045520_a_tour_can_be_a_ski_track extends Migration { override name = 'Migration20200101045520_a_tour_can_be_a_ski_track'; override up(): void { this.addSql( - "ALTER TABLE tours ADD COLUMN ski_discipline TEXT CHECK (ski_discipline IS NULL OR ski_discipline IN ('alpine', 'snowboard', 'cross_country', 'touring', 'snowshoe', 'winter_hike'))", + "ALTER TABLE tours ADD COLUMN ski_discipline TEXT CHECK (ski_discipline IS NULL OR ski_discipline IN ('alpine', 'snowboard', 'cross_country', 'touring'))", ); this.addSql( "ALTER TABLE tours ADD COLUMN ski_technique TEXT CHECK (ski_technique IS NULL OR ski_technique IN ('classic', 'skating', 'unknown'))", diff --git a/server/src/db/migrations/Migration20200101046100_a_collection_keeps_whole_tours.ts b/server/src/db/migrations/Migration20200101046100_a_collection_keeps_whole_tours.ts index da388c2e9..635300a1d 100644 --- a/server/src/db/migrations/Migration20200101046100_a_collection_keeps_whole_tours.ts +++ b/server/src/db/migrations/Migration20200101046100_a_collection_keeps_whole_tours.ts @@ -36,6 +36,7 @@ export class Migration20200101046100_a_collection_keeps_whole_tours extends Migr match_confidence REAL, max_hiking_difficulty INTEGER, bike_profile TEXT, + winter_activity TEXT, run_profile TEXT, run_pace_seconds_per_km INTEGER, ski_discipline TEXT, diff --git a/server/src/db/migrations/Migration20200101046130_snowshoeing_and_winter_hiking_are_ski_disciplines.ts b/server/src/db/migrations/Migration20200101046130_snowshoeing_and_winter_hiking_are_ski_disciplines.ts new file mode 100644 index 000000000..8a0c4d921 --- /dev/null +++ b/server/src/db/migrations/Migration20200101046130_snowshoeing_and_winter_hiking_are_ski_disciplines.ts @@ -0,0 +1,41 @@ +import { columnNames } from '../migration-utils'; +import { Migration } from '@mikro-orm/migrations'; + +/** + * SKI-02: snowshoeing and winter hiking are disciplines of a ski and winter + * tour, no longer a winter label on a hike. A hike is summer only. + * + * `tours.winter_activity` (045500) and `collection_tours.winter_activity` + * (046100) go. A hike that carried the label stays a hike, without it: + * turning it into a ski tour would drop its routed legs, and nothing routes + * or times by the label. + * + * `tours.ski_discipline` (045520) takes the six disciplines. SQLite cannot + * change a CHECK in place, and rebuilding `tours` would mean rebuilding every + * table that references it, so the column is added again instead: its values + * wait in a plain helper column, the old column goes, the new one comes with + * the new CHECK and takes them back. Nothing indexes the column or names it in + * another constraint, so the drop is allowed. The column now sits last in the + * table, which the generated entity and table types follow. + */ +export class Migration20200101046130_snowshoeing_and_winter_hiking_are_ski_disciplines extends Migration { + override name = 'Migration20200101046130_snowshoeing_and_winter_hiking_are_ski_disciplines'; + + override async up(): Promise { + if ((await columnNames(this, 'tours')).has('winter_activity')) { + this.addSql('ALTER TABLE tours DROP COLUMN winter_activity'); + } + if ((await columnNames(this, 'collection_tours')).has('winter_activity')) { + this.addSql('ALTER TABLE collection_tours DROP COLUMN winter_activity'); + } + + this.addSql('ALTER TABLE tours ADD COLUMN ski_discipline_kept TEXT'); + this.addSql('UPDATE tours SET ski_discipline_kept = ski_discipline'); + this.addSql('ALTER TABLE tours DROP COLUMN ski_discipline'); + this.addSql( + "ALTER TABLE tours ADD COLUMN ski_discipline TEXT CHECK (ski_discipline IS NULL OR ski_discipline IN ('alpine', 'snowboard', 'cross_country', 'touring', 'snowshoe', 'winter_hike'))", + ); + this.addSql('UPDATE tours SET ski_discipline = ski_discipline_kept'); + this.addSql('ALTER TABLE tours DROP COLUMN ski_discipline_kept'); + } +} diff --git a/server/src/middleware/globalMiddleware.ts b/server/src/middleware/globalMiddleware.ts index 4190f8104..aacabcb7e 100644 --- a/server/src/middleware/globalMiddleware.ts +++ b/server/src/middleware/globalMiddleware.ts @@ -326,7 +326,7 @@ export function applyGlobalMiddleware( // through fetch and needs it here. 'https://tiles.openseamap.org', // OpenSnowMap's transparent piste layer (SKI-01), offered in the Tours planner with - // no tour open, for a hike and for a ski track; the GL map reads it through fetch. + // no tour open and for a ski tour; the GL map reads it through fetch. 'https://tiles.opensnowmap.org', // OpenHikingMap, the hiking base map of the Tours planner (MAP-01), loaded only // while a user shows it and the admin allows it; GL reads it through fetch. diff --git a/server/src/nest/tours/tours.mcp.ts b/server/src/nest/tours/tours.mcp.ts index af7bd0fea..350935e32 100644 --- a/server/src/nest/tours/tours.mcp.ts +++ b/server/src/nest/tours/tours.mcp.ts @@ -224,7 +224,7 @@ export class ToursMcp { @Tool({ name: 'search_tour_pois', description: - 'Search places along the saved line of a tour: what a hike, a run, a bike tour, a paddle tour or a city walk stops for (viewpoint, peak, shelter, drinking_water, food, toilets, picnic, campsite, sights, slipway, boat_rental, bike_repair for repair stations and bike shops, and for a city walk museum for museums and galleries, park for parks and gardens, cafe, restaurant and market), within corridor_m metres of the line. Answers each place with its source (TREK places index or OpenStreetMap), kind, address, website and phone where known, opening hours as the source has them (a hint to check, never a promise), distance_m (straight across to the line, not a detour) and along_m (from the start along the line); up to 60, the nearest to the line when more match, listed from the start. When sights are asked for, the best known places near the line (by the page views of their Wikidata item) are searched too: a square or landmark no category names is added as a sight, and the highlights among them carry highlight: true and come first, the best known first. A place OpenStreetMap has no name for (a tap, a bench) comes with an empty name. unavailable lists the categories no source could be searched for right now: their places are missing, not absent, so ask again later rather than reporting none. partial says some stretches could not be searched, capped that only the first part of a long line was, truncated that more places matched. A tour saved with its points only (route_pending) has no line to search along yet. Nothing is added to the tour or the trip.', + 'Search places along the saved line of a tour: what a hike, a run, a ride, a bike tour, a paddle tour, a ski and winter tour or a city walk stops for (viewpoint, peak, shelter, drinking_water, food, toilets, picnic, campsite, sights, slipway, boat_rental, bike_repair for repair stations and bike shops, for a ski and winter tour cafe and restaurant as well, and for a city walk museum for museums and galleries, park for parks and gardens, cafe, restaurant and market), within corridor_m metres of the line. Answers each place with its source (TREK places index or OpenStreetMap), kind, address, website and phone where known, opening hours as the source has them (a hint to check, never a promise), distance_m (straight across to the line, not a detour) and along_m (from the start along the line); up to 60, the nearest to the line when more match, listed from the start. When sights are asked for, the best known places near the line (by the page views of their Wikidata item) are searched too: a square or landmark no category names is added as a sight, and the highlights among them carry highlight: true and come first, the best known first. A place OpenStreetMap has no name for (a tap, a bench) comes with an empty name. unavailable lists the categories no source could be searched for right now: their places are missing, not absent, so ask again later rather than reporting none. partial says some stretches could not be searched, capped that only the first part of a long line was, truncated that more places matched. A tour saved with its points only (route_pending) has no line to search along yet. Nothing is added to the tour or the trip.', inputSchema: { tripId: idSchema, placeId: idSchema.describe('The place id of the tour, as list_tours gives it'), diff --git a/server/tests/unit/db/legacy-baseline.test.ts b/server/tests/unit/db/legacy-baseline.test.ts index 9af0e2c32..2f8c8d467 100644 --- a/server/tests/unit/db/legacy-baseline.test.ts +++ b/server/tests/unit/db/legacy-baseline.test.ts @@ -66,6 +66,7 @@ const UNNUMBERED = [ 'Migration20200101045200_a_tour_can_be_ridden', 'Migration20200101045300_a_tour_can_be_a_city_walk', 'Migration20200101045400_a_tour_waypoint_remembers_the_kind_of_place_it_was_found_as', + 'Migration20200101045500_a_hike_can_be_marked_as_a_winter_activity', 'Migration20200101045510_a_tour_can_be_a_run', 'Migration20200101045520_a_tour_can_be_a_ski_track', 'Migration20200101045600_a_tour_can_be_ridden_on_horseback', @@ -78,6 +79,7 @@ const UNNUMBERED = [ 'Migration20200101046100_a_collection_keeps_whole_tours', 'Migration20200101046110_a_tour_points_at_its_packing_list', 'Migration20200101046120_a_wanderer_connection_can_watch_its_sources', + 'Migration20200101046130_snowshoeing_and_winter_hiking_are_ski_disciplines', ]; // The last step the positional runner ever had; planLegacyBaseline refuses a // schema_version past it. diff --git a/server/tests/unit/db/tour-ski-columns.test.ts b/server/tests/unit/db/tour-ski-columns.test.ts index a8d549307..5240687c9 100644 --- a/server/tests/unit/db/tour-ski-columns.test.ts +++ b/server/tests/unit/db/tour-ski-columns.test.ts @@ -1,11 +1,11 @@ /** * SKI-02: `Migration20200101045520_a_tour_can_be_a_ski_track` switches the ski - * type on, without a routing profile, and gives a tour its ski discipline (six - * of them, snowshoeing and winter hiking among them) and cross-country - * technique. Both columns hold a CHECK list the entity carries + * type on, without a routing profile, and gives a tour its ski discipline and + * cross-country technique. Both columns hold a CHECK list the entity carries * as its enum and the contract holds too: a value one side takes and the * other refuses would be a ski track the import accepts and the database - * cannot store. + * cannot store. The discipline list is the one 046130 leaves behind, with + * snowshoeing and winter hiking (`tour-ski-winter-disciplines.test.ts`). */ import { ToursSkiDiscipline, ToursSkiTechnique } from '../../../src/db/entities/Tours.entity'; import { createMigrationOrm, migrateTo, pendingNames, rawExec, rawQuery } from '../../helpers/migration-step'; @@ -57,9 +57,6 @@ describe('ski columns match the contract', () => { orm, "INSERT INTO tours (place_id, tour_type, ski_discipline, ski_technique) VALUES (101, 'ski', 'cross_country', 'unknown')", ); - // Snowshoeing and winter hiking are disciplines of a ski tour, never labels of a hike. - await rawExec(orm, "UPDATE tours SET ski_discipline = 'snowshoe', ski_technique = NULL WHERE place_id = 101"); - await rawExec(orm, "UPDATE tours SET ski_discipline = 'winter_hike' WHERE place_id = 101"); await expect(rawExec(orm, "UPDATE tours SET ski_discipline = 'freeride' WHERE place_id = 101")).rejects.toThrow( /CHECK/, ); diff --git a/server/tests/unit/db/tour-ski-winter-disciplines.test.ts b/server/tests/unit/db/tour-ski-winter-disciplines.test.ts new file mode 100644 index 000000000..18185b486 --- /dev/null +++ b/server/tests/unit/db/tour-ski-winter-disciplines.test.ts @@ -0,0 +1,81 @@ +/** + * SKI-02: `Migration20200101046130_snowshoeing_and_winter_hiking_are_ski_disciplines` + * moves snowshoeing and winter hiking from the hike's winter label to the ski + * tour's discipline. A database that ran the earlier migrations as they + * shipped (045500 with the label, 045520 with four disciplines) ends with the + * same schema as a fresh one: no winter label anywhere, six disciplines, and + * every discipline a ski tour held before kept. + */ +import { createMigrationOrm, migrateTo, pendingNames, rawExec, rawQuery } from '../../helpers/migration-step'; +import type { MikroORM } from '@mikro-orm/sqlite'; + +import { describe, expect, it } from 'vitest'; + +const TARGET = 'Migration20200101046130_snowshoeing_and_winter_hiking_are_ski_disciplines'; + +async function ormBeforeTarget(): Promise { + const orm = await createMigrationOrm(); + const names = await pendingNames(orm); + const idx = names.indexOf(TARGET); + expect(idx).toBeGreaterThan(0); + await migrateTo(orm, names[idx - 1]); + return orm; +} + +async function columnsOf(orm: MikroORM, table: string): Promise { + const rows = await rawQuery<{ name: string }>(orm, 'SELECT name FROM pragma_table_info(?)', [table]); + return rows.map((r) => r.name); +} + +describe('snowshoeing and winter hiking become ski disciplines', () => { + it('TOURSKIWINTER-001: drops the winter label, keeps every discipline and takes the two new ones', async () => { + const orm = await ormBeforeTarget(); + try { + await rawExec( + orm, + "INSERT INTO users (id, username, email, password_hash) VALUES (1, 'owner', 'owner@test', 'x')", + ); + await rawExec(orm, "INSERT INTO trips (id, user_id, title) VALUES (10, 1, 'Alps')"); + await rawExec( + orm, + "INSERT INTO places (id, trip_id, name) VALUES (100, 10, 'Snowy ridge'), (101, 10, 'Piste day'), (102, 10, 'Summer walk')", + ); + await rawExec(orm, "INSERT INTO tours (place_id, tour_type, winter_activity) VALUES (100, 'hike', 'snowshoe')"); + await rawExec( + orm, + "INSERT INTO tours (place_id, tour_type, ski_discipline, ski_technique) VALUES (101, 'ski', 'cross_country', 'skating')", + ); + await rawExec(orm, "INSERT INTO tours (place_id, tour_type) VALUES (102, 'hike')"); + // The shipped CHECK of 045520 still refuses the two new disciplines here. + await expect(rawExec(orm, "UPDATE tours SET ski_discipline = 'snowshoe' WHERE place_id = 101")).rejects.toThrow( + /CHECK/, + ); + + await migrateTo(orm, TARGET); + + expect(await columnsOf(orm, 'tours')).not.toContain('winter_activity'); + expect(await columnsOf(orm, 'tours')).not.toContain('ski_discipline_kept'); + expect(await columnsOf(orm, 'collection_tours')).not.toContain('winter_activity'); + expect(await columnsOf(orm, 'collection_tours')).toContain('ski_discipline'); + + // A hike that carried the label stays a hike; the ski tour keeps its discipline. + const rows = await rawQuery( + orm, + 'SELECT place_id, tour_type, ski_discipline, ski_technique FROM tours ORDER BY place_id', + ); + expect(rows).toEqual([ + { place_id: 100, tour_type: 'hike', ski_discipline: null, ski_technique: null }, + { place_id: 101, tour_type: 'ski', ski_discipline: 'cross_country', ski_technique: 'skating' }, + { place_id: 102, tour_type: 'hike', ski_discipline: null, ski_technique: null }, + ]); + + await rawExec(orm, "UPDATE tours SET ski_discipline = 'snowshoe', ski_technique = NULL WHERE place_id = 101"); + await rawExec(orm, "UPDATE tours SET ski_discipline = 'winter_hike' WHERE place_id = 101"); + await expect(rawExec(orm, "UPDATE tours SET ski_discipline = 'freeride' WHERE place_id = 101")).rejects.toThrow( + /CHECK/, + ); + } finally { + await orm.close(true); + } + }, 30000); +}); diff --git a/server/tests/unit/nest/tours.mcp.test.ts b/server/tests/unit/nest/tours.mcp.test.ts index ac4a9ed7b..6ad13fe60 100644 --- a/server/tests/unit/nest/tours.mcp.test.ts +++ b/server/tests/unit/nest/tours.mcp.test.ts @@ -403,6 +403,8 @@ describe('ToursMcp', () => { expect(description(ToursMcp, 'getCityWalkProgress')).toMatch(/key .*opening hours/); expect(description(ToursMcp, 'searchTourPois')).toContain('route_pending'); expect(description(ToursMcp, 'searchTourPois')).toMatch(/best known places.*highlight: true/); + expect(description(ToursMcp, 'searchTourPois')).toContain('a ride, a bike tour'); + expect(description(ToursMcp, 'searchTourPois')).toContain('a ski and winter tour or a city walk'); expect(description(TourLibraryMcp, 'takeOverTour')).toContain('protection'); for (const method of ['deletePlace', 'bulkDeletePlaces']) { expect(description(PlacesMcp, method)).toContain( diff --git a/shared/src/i18n/ar/tours.ts b/shared/src/i18n/ar/tours.ts index 013d73ffa..153198f98 100644 --- a/shared/src/i18n/ar/tours.ts +++ b/shared/src/i18n/ar/tours.ts @@ -425,7 +425,7 @@ const tours: TranslationStrings = { 'tours.ski.drawnFreely': 'تُرسم كل مرحلة بخط مستقيم بين نقاطك. لا شيء يتحقق منها مقابل المنحدرات أو المصاعد أو المسارات.', 'tours.ski.chooseTechnique': 'اختر الأسلوب، أو غير معروف.', - 'tours.ski.infoLabel': 'ما يفعله TREK بمسار التزلج', + 'tours.ski.infoLabel': 'ما يفعله TREK بجولات ومسارات التزلج', 'tours.ski.info': 'في الجولة المخطَّطة تُرسم كل مرحلة بخط مستقيم بين نقاطها، ويبقى المسار المستورد كما سُجّل. لا يُوجَّه أيٌّ منهما على المنحدرات أو المصاعد أو المسارات. تأتي الارتفاعات من التضاريس أو من الملف. لا يحسب TREK وقتًا للتزلج، ولا شيء هنا، ومنه ألوان الانحدار في العرض ثلاثي الأبعاد، يقول شيئًا عن الصعوبة أو الثلج أو الانهيارات أو ما إذا كانت المسارات مفتوحة.', 'tours.ski.time': 'وقت التزلج', diff --git a/shared/src/i18n/az/tours.ts b/shared/src/i18n/az/tours.ts index c02cedbb9..0f4be22a1 100644 --- a/shared/src/i18n/az/tours.ts +++ b/shared/src/i18n/az/tours.ts @@ -414,7 +414,7 @@ const tours: TranslationStrings = { 'tours.ski.drawnFreely': 'Hər hissə nöqtələrin arasında düz xətlə çəkilir. Heç nə onu trassalar, liftlər və ya cığırlarla yoxlamır.', 'tours.ski.chooseTechnique': 'Texnikanı və ya Naməlum seç.', - 'tours.ski.infoLabel': 'TREK xizək izi ilə nə edir', + 'tours.ski.infoLabel': 'TREK xizək turları və izləri ilə nə edir', 'tours.ski.info': 'Planlaşdırılmış turda hər hissə nöqtələri arasında düz xətlə çəkilir, idxal olunmuş iz isə qeydə alındığı kimi qalır. Heç biri trassalar, liftlər və ya cığırlar boyunca marşrutlanmır. Hündürlüklər relyefdən və ya fayldan gəlir. TREK xizək vaxtı hesablamır və burada heç nə, 3D-dəki meyil rəngləri də daxil olmaqla, çətinlik, qar, uçqun və ya trassaların açıq olub-olmaması barədə heç nə demir.', 'tours.ski.time': 'Xizək vaxtı', diff --git a/shared/src/i18n/br/tours.ts b/shared/src/i18n/br/tours.ts index 8a27189c8..0d6b9c994 100644 --- a/shared/src/i18n/br/tours.ts +++ b/shared/src/i18n/br/tours.ts @@ -420,7 +420,7 @@ const tours: TranslationStrings = { 'tours.ski.drawnFreely': 'Cada trecho é desenhado em linha reta entre seus pontos. Nada o confere com pistas, teleféricos ou trilhas.', 'tours.ski.chooseTechnique': 'Escolha a técnica, ou Desconhecida.', - 'tours.ski.infoLabel': 'O que o TREK faz com uma trilha de esqui', + 'tours.ski.infoLabel': 'O que o TREK faz com passeios e trilhas de esqui', 'tours.ski.info': 'Um passeio planejado tem cada trecho desenhado em linha reta entre seus pontos, e uma trilha importada fica como foi gravada. Nenhum dos dois é roteado por pistas, teleféricos ou trilhas. As altitudes vêm do terreno ou do arquivo. O TREK não calcula tempo de esqui, e nada aqui, nem as cores de inclinação em 3D, diz algo sobre dificuldade, neve, avalanches ou se pistas e trilhas estão abertas.', 'tours.ski.time': 'Tempo de esqui', diff --git a/shared/src/i18n/ca/tours.ts b/shared/src/i18n/ca/tours.ts index e9a85b452..326843896 100644 --- a/shared/src/i18n/ca/tours.ts +++ b/shared/src/i18n/ca/tours.ts @@ -421,7 +421,7 @@ const tours: TranslationStrings = { 'tours.ski.drawnFreely': 'Cada tram es dibuixa en línia recta entre els teus punts. Res no el comprova amb pistes, remuntadors o senders.', 'tours.ski.chooseTechnique': 'Tria la tècnica, o Desconeguda.', - 'tours.ski.infoLabel': 'Què fa TREK amb una traça d’esquí', + 'tours.ski.infoLabel': 'Què fa TREK amb les rutes i traces d’esquí', 'tours.ski.info': 'Una ruta planificada té cada tram dibuixat en línia recta entre els seus punts, i una traça importada es manté tal com es va gravar. Cap de les dues no s’enruta per pistes, remuntadors o senders. Les altituds vénen del terreny o del fitxer. TREK no calcula cap temps d’esquí, i res d’aquí, ni els colors de pendent en 3D, diu res de dificultat, neu, allaus o si les pistes estan obertes.', 'tours.ski.time': 'Temps d’esquí', diff --git a/shared/src/i18n/cs/tours.ts b/shared/src/i18n/cs/tours.ts index 345901e83..7bbac58f5 100644 --- a/shared/src/i18n/cs/tours.ts +++ b/shared/src/i18n/cs/tours.ts @@ -413,7 +413,7 @@ const tours: TranslationStrings = { 'tours.ski.drawnFreely': 'Každý úsek se kreslí rovně mezi vašimi body. Nic ho neporovnává se sjezdovkami, vleky ani stopami.', 'tours.ski.chooseTechnique': 'Vyberte techniku, nebo Neznámá.', - 'tours.ski.infoLabel': 'Co TREK dělá s lyžařskou trasou', + 'tours.ski.infoLabel': 'Co TREK dělá s lyžařskými túrami a trasami', 'tours.ski.info': 'Naplánovaná túra má každý úsek nakreslený rovně mezi svými body, importovaná trasa zůstává tak, jak byla zaznamenána. Ani jedna se nevede po sjezdovkách, vlecích ani stopách. Výšky pocházejí z terénu nebo ze souboru. TREK nepočítá žádný čas na lyžích a nic tady, ani barvy sklonu ve 3D, neříká nic o obtížnosti, sněhu, lavinách ani o tom, zda jsou sjezdovky a stopy otevřené.', 'tours.ski.time': 'Čas na lyžích', diff --git a/shared/src/i18n/de/tours.ts b/shared/src/i18n/de/tours.ts index 3e0886911..6af8137f3 100644 --- a/shared/src/i18n/de/tours.ts +++ b/shared/src/i18n/de/tours.ts @@ -423,7 +423,7 @@ const tours: TranslationStrings = { 'tours.ski.drawnFreely': 'Jeder Abschnitt wird gerade zwischen deinen Punkten gezeichnet. Nichts gleicht ihn mit Pisten, Liften oder Loipen ab.', 'tours.ski.chooseTechnique': 'Wähle die Technik oder Unbekannt.', - 'tours.ski.infoLabel': 'Was TREK mit einer Skistrecke macht', + 'tours.ski.infoLabel': 'Was TREK mit Wintertouren und Skistrecken macht', 'tours.ski.info': 'Bei einer geplanten Tour ist jeder Abschnitt gerade zwischen ihren Punkten gezeichnet, eine importierte Strecke bleibt wie aufgezeichnet. Keine von beiden wird über Pisten, Lifte oder Loipen geroutet. Die Höhen stammen aus dem Gelände oder aus der Datei. TREK berechnet keine Skizeit, und nichts hier, auch nicht die Steigungsfarben in 3D, sagt etwas über Schwierigkeit, Schnee, Lawinen oder geöffnete Pisten und Loipen.', 'tours.ski.time': 'Skizeit', @@ -822,7 +822,7 @@ const tours: TranslationStrings = { 'tours.conversion.introPaddle': 'Ein Import ist eine aufgezeichnete Strecke. Plane eine bearbeitbare Paddeltour: TREK nimmt Kontrollpunkte daraus, verbindet sie mit geraden Linien, wie eine Paddeltour gezeichnet wird, und zeigt, wie nah das herankommt. Nichts ändert sich, bis du dich entscheidest.', 'tours.conversion.introSki': - 'Ein Import ist eine aufgezeichnete Strecke. Plane eine bearbeitbare Skitour: TREK nimmt Kontrollpunkte daraus, verbindet sie mit geraden Linien, wie eine Skitour gezeichnet wird, und zeigt, wie nah das herankommt. Nichts ändert sich, bis du dich entscheidest.', + 'Ein Import ist eine aufgezeichnete Strecke. Plane eine bearbeitbare Ski- und Wintertour: TREK nimmt Kontrollpunkte daraus, verbindet sie mit geraden Linien, wie eine Ski- und Wintertour gezeichnet wird, und zeigt, wie nah das herankommt. Nichts ändert sich, bis du dich entscheidest.', 'tours.conversion.closePaddle': 'Der Vorschlag folgt der Strecke eng. Seine Punkte sind mit geraden Linien verbunden, daher ist es nicht dieselbe Linie.', 'tours.planner.moreActions': 'Weitere Aktionen', @@ -1146,7 +1146,7 @@ const tours: TranslationStrings = { 'tourTypes.horse': 'Ausritt', 'tours.planner.tourTypeHint.horse': 'Zu Pferd, auf ausgeschilderten Reitrouten', 'tours.planner.namePlaceholder.horse': 'Unbenannter Ausritt', - 'tours.planner.namePlaceholder.ski': 'Unbenannte Skitour', + 'tours.planner.namePlaceholder.ski': 'Unbenannte Wintertour', 'tours.horse.time': 'Reitzeit', 'tours.horse.timeInfoLabel': 'Warum es keine Reitzeit gibt', 'tours.horse.timeInfo': diff --git a/shared/src/i18n/en/tours.ts b/shared/src/i18n/en/tours.ts index cbae5f39a..eb8e30ead 100644 --- a/shared/src/i18n/en/tours.ts +++ b/shared/src/i18n/en/tours.ts @@ -413,7 +413,7 @@ const tours: TranslationStrings = { 'tours.ski.drawnFreely': 'Each leg is drawn straight between your points. Nothing checks it against pistes, lifts or trails.', 'tours.ski.chooseTechnique': 'Choose the technique, or Unknown.', - 'tours.ski.infoLabel': 'What TREK does with a ski track', + 'tours.ski.infoLabel': 'What TREK does with ski tours and tracks', 'tours.ski.info': 'A planned tour has each leg drawn straight between its points, an imported track is kept as recorded. Neither is routed along pistes, lifts or trails. Heights come from the terrain or the file. TREK works out no ski time, and nothing here, the gradient colours in 3D included, says anything about difficulty, snow, avalanches or whether runs and trails are open.', 'tours.ski.time': 'Ski time', @@ -800,7 +800,7 @@ const tours: TranslationStrings = { 'tours.conversion.introPaddle': 'An import is a recorded track. Plan an editable paddle tour: TREK takes control points from it, joins them with straight lines as a paddle tour is drawn, and shows how close that comes. Nothing changes until you choose.', 'tours.conversion.introSki': - 'An import is a recorded track. Plan an editable ski tour: TREK takes control points from it, joins them with straight lines as a ski tour is drawn, and shows how close that comes. Nothing changes until you choose.', + 'An import is a recorded track. Plan an editable ski and winter tour: TREK takes control points from it, joins them with straight lines as a ski and winter tour is drawn, and shows how close that comes. Nothing changes until you choose.', 'tours.conversion.closePaddle': 'The suggestion follows the track closely. Its points are joined by straight lines, so it is not the same line.', 'tours.planner.moreActions': 'More actions', @@ -1109,7 +1109,7 @@ const tours: TranslationStrings = { 'tourTypes.horse': 'Horse ride', 'tours.planner.tourTypeHint.horse': 'On horseback, along signposted horse routes', 'tours.planner.namePlaceholder.horse': 'Untitled ride', - 'tours.planner.namePlaceholder.ski': 'Untitled ski tour', + 'tours.planner.namePlaceholder.ski': 'Untitled ski and winter tour', 'tours.horse.time': 'Riding time', 'tours.horse.timeInfoLabel': 'Why there is no riding time', 'tours.horse.timeInfo': diff --git a/shared/src/i18n/es/tours.ts b/shared/src/i18n/es/tours.ts index a516c39f2..c1a4e667d 100644 --- a/shared/src/i18n/es/tours.ts +++ b/shared/src/i18n/es/tours.ts @@ -420,7 +420,7 @@ const tours: TranslationStrings = { 'tours.ski.drawnFreely': 'Cada tramo se dibuja en línea recta entre tus puntos. Nada lo comprueba con pistas, remontes ni senderos.', 'tours.ski.chooseTechnique': 'Elige la técnica, o Desconocida.', - 'tours.ski.infoLabel': 'Qué hace TREK con una ruta de esquí', + 'tours.ski.infoLabel': 'Qué hace TREK con rutas y trazas de esquí', 'tours.ski.info': 'Una ruta planificada tiene cada tramo dibujado en línea recta entre sus puntos, y un recorrido importado se conserva tal como se grabó. Ninguno se enruta por pistas, remontes ni senderos. Las alturas vienen del terreno o del archivo. TREK no calcula ningún tiempo de esquí, y nada de aquí, ni los colores de pendiente en 3D, dice nada sobre dificultad, nieve, aludes o si las pistas están abiertas.', 'tours.ski.time': 'Tiempo de esquí', diff --git a/shared/src/i18n/et/tours.ts b/shared/src/i18n/et/tours.ts index 1c3229782..77a1eddd6 100644 --- a/shared/src/i18n/et/tours.ts +++ b/shared/src/i18n/et/tours.ts @@ -415,7 +415,7 @@ const tours: TranslationStrings = { 'tours.ski.drawnFreely': 'Iga lõik joonistatakse sinu punktide vahele sirgelt. Miski ei võrdle seda nõlvade, tõstukite ega radadega.', 'tours.ski.chooseTechnique': 'Vali tehnika või Teadmata.', - 'tours.ski.infoLabel': 'Mida TREK suusarajaga teeb', + 'tours.ski.infoLabel': 'Mida TREK suusamatkade ja -radadega teeb', 'tours.ski.info': 'Planeeritud matkal joonistatakse iga lõik punktide vahele sirgelt, imporditud rada jääb nii, nagu salvestati. Kumbagi ei marsruudita mööda nõlvu, tõstukeid ega radu. Kõrgused tulevad maastikust või failist. TREK ei arvuta suusaaega ning miski siin, ka 3D kallakuvärvid, ei ütle midagi raskuse, lume, laviinide ega nõlvade ja radade avatuse kohta.', 'tours.ski.time': 'Suusaaeg', diff --git a/shared/src/i18n/fr/tours.ts b/shared/src/i18n/fr/tours.ts index cab016dbc..c23441e8e 100644 --- a/shared/src/i18n/fr/tours.ts +++ b/shared/src/i18n/fr/tours.ts @@ -426,7 +426,7 @@ const tours: TranslationStrings = { 'tours.ski.drawnFreely': 'Chaque tronçon est tracé en ligne droite entre vos points. Rien ne le vérifie par rapport aux pistes, remontées ou sentiers.', 'tours.ski.chooseTechnique': 'Choisissez la technique, ou Inconnue.', - 'tours.ski.infoLabel': 'Ce que TREK fait d’une trace de ski', + 'tours.ski.infoLabel': 'Ce que TREK fait des sorties et traces de ski', 'tours.ski.info': 'Une sortie planifiée a chaque tronçon tracé en ligne droite entre ses points, une trace importée est conservée telle qu’enregistrée. Aucune n’est calculée le long des pistes, remontées ou sentiers. Les altitudes viennent du terrain ou du fichier. TREK ne calcule aucun temps de ski, et rien ici, pas même les couleurs de pente en 3D, ne dit quoi que ce soit sur la difficulté, la neige, les avalanches ou l’ouverture des pistes.', 'tours.ski.time': 'Temps de ski', diff --git a/shared/src/i18n/gr/tours.ts b/shared/src/i18n/gr/tours.ts index fef4ab66c..438ddadd8 100644 --- a/shared/src/i18n/gr/tours.ts +++ b/shared/src/i18n/gr/tours.ts @@ -428,7 +428,7 @@ const tours: TranslationStrings = { 'tours.ski.drawnFreely': 'Κάθε σκέλος σχεδιάζεται ευθεία ανάμεσα στα σημεία σας. Τίποτα δεν το ελέγχει με πίστες, αναβατήρες ή μονοπάτια.', 'tours.ski.chooseTechnique': 'Επιλέξτε τεχνική ή Άγνωστη.', - 'tours.ski.infoLabel': 'Τι κάνει το TREK με μια διαδρομή σκι', + 'tours.ski.infoLabel': 'Τι κάνει το TREK με διαδρομές και ίχνη σκι', 'tours.ski.info': 'Σε μια σχεδιασμένη διαδρομή κάθε σκέλος σχεδιάζεται ευθεία ανάμεσα στα σημεία της, ενώ ένα εισαγόμενο ίχνος μένει όπως καταγράφηκε. Κανένα δεν δρομολογείται σε πίστες, αναβατήρες ή μονοπάτια. Τα υψόμετρα προέρχονται από το έδαφος ή το αρχείο. Το TREK δεν υπολογίζει χρόνο σκι, και τίποτα εδώ, ούτε τα χρώματα κλίσης στο 3D, δεν λέει κάτι για δυσκολία, χιόνι, χιονοστιβάδες ή αν οι πίστες είναι ανοιχτές.', 'tours.ski.time': 'Χρόνος σκι', diff --git a/shared/src/i18n/hu/tours.ts b/shared/src/i18n/hu/tours.ts index 8c02db029..5b3e7e86e 100644 --- a/shared/src/i18n/hu/tours.ts +++ b/shared/src/i18n/hu/tours.ts @@ -420,7 +420,7 @@ const tours: TranslationStrings = { 'tours.ski.drawnFreely': 'Minden szakaszt egyenesen rajzolunk a pontjaid közé. Semmi sem veti össze pályákkal, felvonókkal vagy nyomvonalakkal.', 'tours.ski.chooseTechnique': 'Válaszd ki a technikát, vagy az Ismeretlent.', - 'tours.ski.infoLabel': 'Mit kezd a TREK egy sínyomvonallal', + 'tours.ski.infoLabel': 'Mit kezd a TREK a sí- és téli túrákkal és a sínyomvonalakkal', 'tours.ski.info': 'Egy tervezett túra minden szakasza egyenesen fut a pontjai között, egy importált nyomvonal pedig úgy marad, ahogy rögzítették. Egyiket sem vezetjük pályákon, felvonókon vagy nyomvonalakon. A magasságok a terepből vagy a fájlból származnak. A TREK nem számol síidőt, és semmi itt, a 3D lejtőszínei sem, nem mond semmit a nehézségről, a hóról, a lavinákról vagy arról, hogy nyitva vannak-e a pályák.', 'tours.ski.time': 'Síidő', @@ -817,7 +817,7 @@ const tours: TranslationStrings = { 'tours.conversion.introPaddle': 'Az import egy rögzített nyomvonal. Tervezz belőle szerkeszthető evezős túrát: a TREK kontrollpontokat vesz ki belőle, egyenes vonalakkal köti össze őket, ahogy az evezős túrát rajzolod, és megmutatja, mennyire közelíti meg. Semmi sem változik, amíg nem döntesz.', 'tours.conversion.introSki': - 'Az import egy rögzített nyomvonal. Tervezz belőle szerkeszthető sítúrát: a TREK kontrollpontokat vesz ki belőle, egyenes vonalakkal köti össze őket, ahogy a sítúrát rajzolod, és megmutatja, mennyire közelíti meg. Semmi sem változik, amíg nem döntesz.', + 'Az import egy rögzített nyomvonal. Tervezz belőle szerkeszthető sí- és téli túrát: a TREK kontrollpontokat vesz ki belőle, egyenes vonalakkal köti össze őket, ahogy a sí- és téli túrát rajzolod, és megmutatja, mennyire közelíti meg. Semmi sem változik, amíg nem döntesz.', 'tours.conversion.closePaddle': 'A javaslat szorosan követi a nyomvonalat. Pontjait egyenes vonalak kötik össze, így nem ugyanaz a vonal.', 'tours.planner.moreActions': 'További műveletek', @@ -1137,7 +1137,7 @@ const tours: TranslationStrings = { 'tourTypes.horse': 'Lovastúra', 'tours.planner.tourTypeHint.horse': 'Lóháton, jelzett lovas útvonalakon', 'tours.planner.namePlaceholder.horse': 'Névtelen lovastúra', - 'tours.planner.namePlaceholder.ski': 'Névtelen sítúra', + 'tours.planner.namePlaceholder.ski': 'Névtelen sí- és téli túra', 'tours.horse.time': 'Lovaglási idő', 'tours.horse.timeInfoLabel': 'Miért nincs lovaglási idő', 'tours.horse.timeInfo': diff --git a/shared/src/i18n/id/tours.ts b/shared/src/i18n/id/tours.ts index b94c66e46..1f262d26c 100644 --- a/shared/src/i18n/id/tours.ts +++ b/shared/src/i18n/id/tours.ts @@ -415,7 +415,7 @@ const tours: TranslationStrings = { 'tours.ski.drawnFreely': 'Setiap ruas digambar lurus di antara titik-titikmu. Tidak ada yang memeriksanya terhadap lintasan, lift, atau jalur.', 'tours.ski.chooseTechnique': 'Pilih teknik, atau Tidak diketahui.', - 'tours.ski.infoLabel': 'Apa yang TREK lakukan dengan jalur ski', + 'tours.ski.infoLabel': 'Apa yang TREK lakukan dengan tur dan jalur ski', 'tours.ski.info': 'Tur yang direncanakan menggambar setiap ruas lurus di antara titik-titiknya, jalur yang diimpor tetap seperti yang direkam. Keduanya tidak dirutekan lewat lintasan, lift, atau jalur. Ketinggian berasal dari medan atau berkas. TREK tidak menghitung waktu ski, dan tidak ada apa pun di sini, termasuk warna kemiringan dalam 3D, yang mengatakan sesuatu tentang kesulitan, salju, longsoran, atau apakah lintasan sedang dibuka.', 'tours.ski.time': 'Waktu ski', @@ -801,7 +801,7 @@ const tours: TranslationStrings = { 'tours.conversion.introPaddle': 'Impor adalah jejak yang direkam. Rencanakan tur dayung yang bisa diedit: TREK mengambil titik kontrol darinya, menghubungkannya dengan garis lurus seperti tur dayung digambar, dan menunjukkan seberapa dekat hasilnya. Tidak ada yang berubah sampai kamu memilih.', 'tours.conversion.introSki': - 'Impor adalah jejak yang direkam. Rencanakan tur ski yang bisa diedit: TREK mengambil titik kontrol darinya, menghubungkannya dengan garis lurus seperti tur ski digambar, dan menunjukkan seberapa dekat hasilnya. Tidak ada yang berubah sampai kamu memilih.', + 'Impor adalah jejak yang direkam. Rencanakan tur ski dan musim dingin yang bisa diedit: TREK mengambil titik kontrol darinya, menghubungkannya dengan garis lurus seperti tur ski dan musim dingin digambar, dan menunjukkan seberapa dekat hasilnya. Tidak ada yang berubah sampai kamu memilih.', 'tours.conversion.closePaddle': 'Usulan mengikuti jejak dengan dekat. Titik-titiknya dihubungkan garis lurus, jadi bukan garis yang sama.', 'tours.planner.moreActions': 'Tindakan lainnya', @@ -1114,7 +1114,7 @@ const tours: TranslationStrings = { 'tourTypes.horse': 'Tur berkuda', 'tours.planner.tourTypeHint.horse': 'Menunggang kuda, menyusuri rute berkuda bertanda', 'tours.planner.namePlaceholder.horse': 'Tur berkuda tanpa judul', - 'tours.planner.namePlaceholder.ski': 'Tur ski tanpa judul', + 'tours.planner.namePlaceholder.ski': 'Tur ski dan musim dingin tanpa judul', 'tours.horse.time': 'Waktu berkuda', 'tours.horse.timeInfoLabel': 'Mengapa tidak ada waktu berkuda', 'tours.horse.timeInfo': diff --git a/shared/src/i18n/it/tours.ts b/shared/src/i18n/it/tours.ts index 2388fe272..3537f2ed3 100644 --- a/shared/src/i18n/it/tours.ts +++ b/shared/src/i18n/it/tours.ts @@ -423,7 +423,7 @@ const tours: TranslationStrings = { 'tours.ski.drawnFreely': 'Ogni tratto è disegnato in linea retta tra i tuoi punti. Nulla lo confronta con piste, impianti o sentieri.', 'tours.ski.chooseTechnique': 'Scegli la tecnica, oppure Sconosciuta.', - 'tours.ski.infoLabel': 'Cosa fa TREK con una traccia di sci', + 'tours.ski.infoLabel': 'Cosa fa TREK con tour e tracce di sci', 'tours.ski.info': 'Un tour pianificato ha ogni tratto disegnato in linea retta tra i suoi punti, una traccia importata resta come registrata. Nessuno dei due viene instradato lungo piste, impianti o sentieri. Le quote vengono dal terreno o dal file. TREK non calcola alcun tempo di sci, e nulla qui, nemmeno i colori della pendenza in 3D, dice qualcosa su difficoltà, neve, valanghe o apertura di piste e anelli.', 'tours.ski.time': 'Tempo di sci', diff --git a/shared/src/i18n/ja/tours.ts b/shared/src/i18n/ja/tours.ts index c2fe6cc47..ed305e9da 100644 --- a/shared/src/i18n/ja/tours.ts +++ b/shared/src/i18n/ja/tours.ts @@ -404,7 +404,7 @@ const tours: TranslationStrings = { 'tours.ski.chooseDisciplineToSave': '保存するには種目を選んでください。', 'tours.ski.drawnFreely': '各区間はポイントの間を直線で描きます。コース、リフト、トレイルとの照合は行いません。', 'tours.ski.chooseTechnique': '走法を選ぶか、不明を選んでください。', - 'tours.ski.infoLabel': 'TREK がスキーのトラックで行うこと', + 'tours.ski.infoLabel': 'TREK がスキーツアーとトラックで行うこと', 'tours.ski.info': '計画したツアーは各区間をポイントの間の直線で描き、取り込んだトラックは記録されたまま保持します。どちらもコース、リフト、トレイルに沿ったルート検索は行いません。標高は地形またはファイルの値です。TREK はスキーの所要時間を計算せず、3D の勾配の色を含めここにあるものは、難易度、積雪、雪崩、コースの営業状況について何も示しません。', 'tours.ski.time': 'スキー時間', diff --git a/shared/src/i18n/ko/tours.ts b/shared/src/i18n/ko/tours.ts index 70e2db051..5a6550f11 100644 --- a/shared/src/i18n/ko/tours.ts +++ b/shared/src/i18n/ko/tours.ts @@ -402,7 +402,7 @@ const tours: TranslationStrings = { 'tours.ski.chooseDisciplineToSave': '투어를 저장하려면 종목을 선택하세요.', 'tours.ski.drawnFreely': '각 구간은 지점 사이를 직선으로 그립니다. 슬로프, 리프트, 트레일과 대조하지 않습니다.', 'tours.ski.chooseTechnique': '주법을 선택하거나 알 수 없음을 선택하세요.', - 'tours.ski.infoLabel': 'TREK이 스키 트랙으로 하는 일', + 'tours.ski.infoLabel': 'TREK이 스키 투어와 트랙으로 하는 일', 'tours.ski.info': '계획한 투어는 각 구간을 지점 사이의 직선으로 그리고, 가져온 트랙은 기록된 그대로 유지합니다. 어느 쪽도 슬로프, 리프트, 트레일을 따라 경로를 계산하지 않습니다. 고도는 지형 또는 파일에서 가져옵니다. TREK은 스키 시간을 계산하지 않으며, 3D 경사 색상을 포함해 여기의 어떤 것도 난이도, 눈, 눈사태, 슬로프 운영 여부에 대해 알려 주지 않습니다.', 'tours.ski.time': '스키 시간', diff --git a/shared/src/i18n/nl/tours.ts b/shared/src/i18n/nl/tours.ts index 389cd8a91..30d70a8b5 100644 --- a/shared/src/i18n/nl/tours.ts +++ b/shared/src/i18n/nl/tours.ts @@ -422,7 +422,7 @@ const tours: TranslationStrings = { 'tours.ski.drawnFreely': 'Elk deel wordt recht tussen je punten getekend. Niets toetst het aan pistes, liften of paden.', 'tours.ski.chooseTechnique': 'Kies de techniek, of Onbekend.', - 'tours.ski.infoLabel': 'Wat TREK met een skispoor doet', + 'tours.ski.infoLabel': 'Wat TREK met skitochten en skisporen doet', 'tours.ski.info': 'Bij een geplande tocht is elk deel recht tussen de punten getekend, een geïmporteerd spoor blijft zoals opgenomen. Geen van beide wordt over pistes, liften of paden gerouteerd. Hoogtes komen uit het terrein of het bestand. TREK berekent geen skitijd, en niets hier, ook de hellingkleuren in 3D niet, zegt iets over moeilijkheid, sneeuw, lawines of of pistes en loipes open zijn.', 'tours.ski.time': 'Skitijd', diff --git a/shared/src/i18n/pl/tours.ts b/shared/src/i18n/pl/tours.ts index 47a9fbe07..db54834df 100644 --- a/shared/src/i18n/pl/tours.ts +++ b/shared/src/i18n/pl/tours.ts @@ -429,7 +429,7 @@ const tours: TranslationStrings = { 'tours.ski.drawnFreely': 'Każdy odcinek jest rysowany prosto między twoimi punktami. Nic nie sprawdza go względem tras, wyciągów ani szlaków.', 'tours.ski.chooseTechnique': 'Wybierz technikę lub Nieznana.', - 'tours.ski.infoLabel': 'Co TREK robi ze śladem narciarskim', + 'tours.ski.infoLabel': 'Co TREK robi z wycieczkami i śladami narciarskimi', 'tours.ski.info': 'Zaplanowana trasa ma każdy odcinek narysowany prosto między swoimi punktami, a zaimportowany ślad pozostaje taki, jak został nagrany. Żaden nie jest wyznaczany po trasach, wyciągach ani szlakach. Wysokości pochodzą z terenu lub z pliku. TREK nie wylicza czasu na nartach i nic tutaj, także kolory nachylenia w 3D, nie mówi nic o trudności, śniegu, lawinach ani o tym, czy trasy są otwarte.', 'tours.ski.time': 'Czas na nartach', diff --git a/shared/src/i18n/ru/tours.ts b/shared/src/i18n/ru/tours.ts index 02dcb5694..07d6bc95b 100644 --- a/shared/src/i18n/ru/tours.ts +++ b/shared/src/i18n/ru/tours.ts @@ -433,7 +433,7 @@ const tours: TranslationStrings = { 'tours.ski.drawnFreely': 'Каждый участок рисуется прямой между вашими точками. Ничто не сверяет его с трассами, подъёмниками или тропами.', 'tours.ski.chooseTechnique': 'Выберите технику или «Неизвестно».', - 'tours.ski.infoLabel': 'Что TREK делает с лыжным треком', + 'tours.ski.infoLabel': 'Что TREK делает с лыжными маршрутами и треками', 'tours.ski.info': 'В запланированном маршруте каждый участок нарисован прямой между его точками, импортированный трек остаётся таким, как записан. Ни один из них не прокладывается по трассам, подъёмникам или тропам. Высоты берутся из рельефа или из файла. TREK не рассчитывает время на лыжах, и ничто здесь, включая цвета уклона в 3D, не говорит о сложности, снеге, лавинах или о том, открыты ли трассы и лыжни.', 'tours.ski.time': 'Время на лыжах', diff --git a/shared/src/i18n/sk/tours.ts b/shared/src/i18n/sk/tours.ts index b2992e7f0..73bfd3c8a 100644 --- a/shared/src/i18n/sk/tours.ts +++ b/shared/src/i18n/sk/tours.ts @@ -414,7 +414,7 @@ const tours: TranslationStrings = { 'tours.ski.drawnFreely': 'Každý úsek sa kreslí rovno medzi vašimi bodmi. Nič ho neporovnáva so zjazdovkami, vlekmi ani stopami.', 'tours.ski.chooseTechnique': 'Vyberte techniku, alebo Neznáma.', - 'tours.ski.infoLabel': 'Čo TREK robí s lyžiarskou trasou', + 'tours.ski.infoLabel': 'Čo TREK robí s lyžiarskymi túrami a trasami', 'tours.ski.info': 'Naplánovaná túra má každý úsek nakreslený rovno medzi svojimi bodmi, importovaná trasa zostáva tak, ako bola zaznamenaná. Ani jedna nevedie po zjazdovkách, vlekoch ani stopách. Výšky pochádzajú z terénu alebo zo súboru. TREK nepočíta žiadny čas na lyžiach a nič tu, ani farby sklonu v 3D, nehovorí nič o náročnosti, snehu, lavínach ani o tom, či sú zjazdovky a stopy otvorené.', 'tours.ski.time': 'Čas na lyžiach', diff --git a/shared/src/i18n/sv/tours.ts b/shared/src/i18n/sv/tours.ts index e4ee0b473..7ae620d50 100644 --- a/shared/src/i18n/sv/tours.ts +++ b/shared/src/i18n/sv/tours.ts @@ -412,7 +412,7 @@ const tours: TranslationStrings = { 'tours.ski.drawnFreely': 'Varje etapp ritas rakt mellan dina punkter. Inget stämmer av den mot backar, liftar eller leder.', 'tours.ski.chooseTechnique': 'Välj teknik, eller Okänd.', - 'tours.ski.infoLabel': 'Vad TREK gör med ett skidspår', + 'tours.ski.infoLabel': 'Vad TREK gör med skidturer och skidspår', 'tours.ski.info': 'En planerad tur har varje etapp ritad rakt mellan sina punkter, ett importerat spår behålls som det spelades in. Inget av dem ruttplaneras längs backar, liftar eller leder. Höjderna kommer från terrängen eller filen. TREK räknar inte ut någon skidtid, och inget här, inte heller lutningsfärgerna i 3D, säger något om svårighet, snö, laviner eller om backar och spår är öppna.', 'tours.ski.time': 'Skidtid', diff --git a/shared/src/i18n/th/tours.ts b/shared/src/i18n/th/tours.ts index fc761ef1f..183b0ab53 100644 --- a/shared/src/i18n/th/tours.ts +++ b/shared/src/i18n/th/tours.ts @@ -393,7 +393,7 @@ const tours: TranslationStrings = { 'tours.ski.chooseDisciplineToSave': 'เลือกประเภทเพื่อบันทึกทริป', 'tours.ski.drawnFreely': 'แต่ละช่วงจะวาดเป็นเส้นตรงระหว่างจุดของคุณ ไม่มีการตรวจสอบกับลานสกี ลิฟต์ หรือเส้นทาง', 'tours.ski.chooseTechnique': 'เลือกเทคนิค หรือไม่ทราบ', - 'tours.ski.infoLabel': 'TREK ทำอะไรกับเส้นทางสกี', + 'tours.ski.infoLabel': 'TREK ทำอะไรกับทริปและเส้นทางสกี', 'tours.ski.info': 'ทริปที่วางแผนจะวาดแต่ละช่วงเป็นเส้นตรงระหว่างจุด ส่วนแทร็กที่นำเข้าจะเก็บไว้ตามที่บันทึก ทั้งสองแบบไม่ได้คำนวณเส้นทางตามลานสกี ลิฟต์ หรือเส้นทางเดิน ความสูงมาจากภูมิประเทศหรือไฟล์ TREK ไม่คำนวณเวลาเล่นสกี และไม่มีสิ่งใดในที่นี้ รวมถึงสีความชันในมุมมอง 3D ที่บอกอะไรเกี่ยวกับความยาก หิมะ หิมะถล่ม หรือว่าลานสกีเปิดอยู่หรือไม่', 'tours.ski.time': 'เวลาเล่นสกี', diff --git a/shared/src/i18n/tr/tours.ts b/shared/src/i18n/tr/tours.ts index 4e5390a1f..250feb097 100644 --- a/shared/src/i18n/tr/tours.ts +++ b/shared/src/i18n/tr/tours.ts @@ -414,7 +414,7 @@ const tours: TranslationStrings = { 'tours.ski.drawnFreely': 'Her bölüm noktaların arasında düz çizilir. Hiçbir şey onu pistler, teleferikler veya parkurlarla karşılaştırmaz.', 'tours.ski.chooseTechnique': 'Tekniği ya da Bilinmiyor seçeneğini seç.', - 'tours.ski.infoLabel': 'TREK bir kayak iziyle ne yapar', + 'tours.ski.infoLabel': 'TREK kayak turları ve izleriyle ne yapar', 'tours.ski.info': 'Planlanan bir turda her bölüm noktaları arasında düz çizilir, içe aktarılan bir iz ise kaydedildiği gibi kalır. Hiçbiri pistler, teleferikler veya parkurlar boyunca rotalanmaz. Yükseklikler araziden ya da dosyadan gelir. TREK kayak süresi hesaplamaz; buradaki hiçbir şey, 3D eğim renkleri de dahil, zorluk, kar, çığ ya da pist ve parkurların açık olup olmadığı hakkında bir şey söylemez.', 'tours.ski.time': 'Kayak süresi', diff --git a/shared/src/i18n/uk/tours.ts b/shared/src/i18n/uk/tours.ts index 10c72b2ec..d7acccee1 100644 --- a/shared/src/i18n/uk/tours.ts +++ b/shared/src/i18n/uk/tours.ts @@ -428,7 +428,7 @@ const tours: TranslationStrings = { 'tours.ski.drawnFreely': 'Кожна ділянка малюється прямою між вашими точками. Ніщо не звіряє її з трасами, підйомниками чи стежками.', 'tours.ski.chooseTechnique': 'Виберіть техніку або «Невідомо».', - 'tours.ski.infoLabel': 'Що TREK робить з лижним треком', + 'tours.ski.infoLabel': 'Що TREK робить з лижними маршрутами і треками', 'tours.ski.info': 'У запланованому маршруті кожна ділянка намальована прямою між його точками, імпортований трек лишається таким, як записано. Жоден не прокладається трасами, підйомниками чи стежками. Висоти взято з рельєфу або з файлу. TREK не розраховує час на лижах, і ніщо тут, зокрема кольори нахилу в 3D, не каже про складність, сніг, лавини чи про те, чи відкриті траси й лижні.', 'tours.ski.time': 'Час на лижах', diff --git a/shared/src/i18n/vi/tours.ts b/shared/src/i18n/vi/tours.ts index bf272fbde..bdb96d780 100644 --- a/shared/src/i18n/vi/tours.ts +++ b/shared/src/i18n/vi/tours.ts @@ -412,7 +412,7 @@ const tours: TranslationStrings = { 'tours.ski.drawnFreely': 'Mỗi chặng được vẽ thẳng giữa các điểm của bạn. Không có gì đối chiếu nó với đường trượt, cáp treo hay lối mòn.', 'tours.ski.chooseTechnique': 'Chọn kỹ thuật, hoặc Không rõ.', - 'tours.ski.infoLabel': 'TREK làm gì với một đường trượt tuyết', + 'tours.ski.infoLabel': 'TREK làm gì với chuyến và đường trượt tuyết', 'tours.ski.info': 'Chuyến đi đã lập có mỗi chặng được vẽ thẳng giữa các điểm của nó, còn vết được nhập giữ nguyên như đã ghi. Không cái nào được định tuyến theo đường trượt, cáp treo hay lối mòn. Độ cao lấy từ địa hình hoặc từ tệp. TREK không tính thời gian trượt, và không gì ở đây, kể cả màu độ dốc trong 3D, nói gì về độ khó, tuyết, tuyết lở hay việc đường trượt có mở hay không.', 'tours.ski.time': 'Thời gian trượt', diff --git a/shared/src/i18n/zh-TW/tours.ts b/shared/src/i18n/zh-TW/tours.ts index 1bf659919..71268e026 100644 --- a/shared/src/i18n/zh-TW/tours.ts +++ b/shared/src/i18n/zh-TW/tours.ts @@ -378,7 +378,7 @@ const tours: TranslationStrings = { 'tours.ski.chooseDisciplineToSave': '請選擇項目以儲存路線。', 'tours.ski.drawnFreely': '每段都在你的點之間以直線繪製,不會與雪道、纜車或步道進行核對。', 'tours.ski.chooseTechnique': '請選擇技術,或選擇未知。', - 'tours.ski.infoLabel': 'TREK 如何處理滑雪軌跡', + 'tours.ski.infoLabel': 'TREK 如何處理滑雪路線與軌跡', 'tours.ski.info': '規劃的路線中每段都在其點之間以直線繪製,匯入的軌跡保持記錄原樣。兩者都不會沿雪道、纜車或步道規劃路線。海拔來自地形或檔案。TREK 不計算滑雪時間,這裡的任何內容,包括 3D 中的坡度顏色,都不代表難度、積雪、雪崩或雪道是否開放。', 'tours.ski.time': '滑雪時間', diff --git a/shared/src/i18n/zh/tours.ts b/shared/src/i18n/zh/tours.ts index 88429fcf9..88cc77fb0 100644 --- a/shared/src/i18n/zh/tours.ts +++ b/shared/src/i18n/zh/tours.ts @@ -379,7 +379,7 @@ const tours: TranslationStrings = { 'tours.ski.chooseDisciplineToSave': '请选择项目以保存路线。', 'tours.ski.drawnFreely': '每段都在你的点之间以直线绘制,不会与雪道、缆车或小径进行核对。', 'tours.ski.chooseTechnique': '请选择技术,或选择未知。', - 'tours.ski.infoLabel': 'TREK 如何处理滑雪轨迹', + 'tours.ski.infoLabel': 'TREK 如何处理滑雪路线与轨迹', 'tours.ski.info': '计划的路线中每段都在其点之间以直线绘制,导入的轨迹保持记录原样。两者都不会沿雪道、缆车或小径规划路线。海拔来自地形或文件。TREK 不计算滑雪用时,这里的任何内容,包括 3D 中的坡度颜色,都不代表难度、积雪、雪崩或雪道是否开放。', 'tours.ski.time': '滑雪用时', diff --git a/wiki/MCP-Addon-Tools.md b/wiki/MCP-Addon-Tools.md index 23078e584..a8a482265 100644 --- a/wiki/MCP-Addon-Tools.md +++ b/wiki/MCP-Addon-Tools.md @@ -242,7 +242,7 @@ The saved tours of a trip, as the Tours list and the tour detail show them, how | `find_tours_for_day` | The tours put on one day of a trip, with the same facts as `list_tours` | `trips:read` | | `get_city_walk_progress` | How far a city walk got on one day it is on: every station in order with its key, its status that day (planned, visited or skipped, as checked off in the day plan), importance, protection, stay and opening hours, what is still open, and the walking time of the whole walk. Visits belong to that day only. Reading changes nothing | `trips:read` | | `export_tour_gpx` | One saved tour as a GPX file: its whole saved line as one track, with the heights it carries and none it lacks, and a file name. A tour without a line cannot be exported | `trips:read` | -| `search_tour_pois` | Places along the saved line of a tour within a corridor (viewpoints, peaks, shelters, drinking water, food, toilets, picnic, campsites, sights, slipways, boat rental, bike repair, and for a city walk museums and galleries, parks and gardens, coffee shops, places to eat and markets): source (TREK places index or OpenStreetMap), kind, address, website, phone, opening hours as the source has them (a hint, never a promise), the distance straight across to the line (not a detour) and how far along the line; up to 60, the nearest to the line when more match, listed from the start. When sights are asked for, the best known places near the line (by the page views of their Wikidata item) are searched too, so a famous square no category names is found as a sight, and the highlights among them carry `highlight: true` and come first. A place OpenStreetMap has no name for comes with an empty name. `unavailable` names the categories no source could be searched for right now, so their places are missing rather than absent. Says when stretches could not be searched, only the first part of a long line was, or more places matched. A tour saved with its points only (`route_pending`) has no line to search along yet. Nothing is added | `trips:read` | +| `search_tour_pois` | Places along the saved line of a hike, run, ride, bike tour, paddle tour, ski and winter tour or city walk within a corridor (viewpoints, peaks, shelters, drinking water, food, toilets, picnic, campsites, sights, slipways, boat rental, bike repair, for a ski and winter tour coffee shops and places to eat as well, and for a city walk museums and galleries, parks and gardens, coffee shops, places to eat and markets): source (TREK places index or OpenStreetMap), kind, address, website, phone, opening hours as the source has them (a hint, never a promise), the distance straight across to the line (not a detour) and how far along the line; up to 60, the nearest to the line when more match, listed from the start. When sights are asked for, the best known places near the line (by the page views of their Wikidata item) are searched too, so a famous square no category names is found as a sight, and the highlights among them carry `highlight: true` and come first. A place OpenStreetMap has no name for comes with an empty name. `unavailable` names the categories no source could be searched for right now, so their places are missing rather than absent. Says when stretches could not be searched, only the first part of a long line was, or more places matched. A tour saved with its points only (`route_pending`) has no line to search along yet. Nothing is added | `trips:read` | | `list_tour_library` | The saved tours of every trip the user can open, to choose one to take over into another trip: name, type, distance, ascent, descent, planned times and the trip each is in. Narrowed by a part of the name, a type or a trip; the trip being planned can be left out. Up to 100 tours, `truncated` says there were more | `trips:read` | | `assign_tour_track` | Links an actual track to a saved tour, once the user chose it: another tour of the same trip that came in through the GPX import or from wanderer and was not planned over, for one of the tour's day assignments or for none. A day that already has one gets this one instead; a track belongs to one tour only. The planned line, measures and times of the tour do not change. Needs `place_edit` | `trips:write` | | `unassign_tour_track` | Unlinks an actual track from a tour again; the track stays in the trip as the import it is and the tour does not change. Needs `place_edit` | `trips:write` |