diff --git a/client/src/components/Tours/TourGpxPreviewDialog.test.tsx b/client/src/components/Tours/TourGpxPreviewDialog.test.tsx index 4cb876dc8..9dcea28b8 100644 --- a/client/src/components/Tours/TourGpxPreviewDialog.test.tsx +++ b/client/src/components/Tours/TourGpxPreviewDialog.test.tsx @@ -1,8 +1,8 @@ -// FE-GPX-PREVIEW-001 to FE-GPX-PREVIEW-004 +// FE-GPX-PREVIEW-001 to FE-GPX-PREVIEW-007 // The GPX preview as ACT-01 uses it: preset to the tour's type, a city walk // imported as one, and when a track was recorded shown where it carried times. import { fireEvent, render, screen } from '@testing-library/react'; -import type { TourGpxPreviewTrack } from '@trek/shared'; +import type { TourFileLeftOut, TourGpxPreviewTrack } from '@trek/shared'; import { describe, expect, it, vi } from 'vitest'; import TourGpxPreviewDialog from './TourGpxPreviewDialog'; import type { TourGpxImport } from './useTourGpxImport'; @@ -27,10 +27,10 @@ function track(overrides: Partial = {}): TourGpxPreviewTrac }; } -function gpxImport(tracks: TourGpxPreviewTrack[]): TourGpxImport { +function gpxImport(tracks: TourGpxPreviewTrack[], leftOut?: TourFileLeftOut): TourGpxImport { return { importing: false, - preview: [{ file: new File([''], 'morning.gpx'), tracks }], + preview: [{ file: new File([''], 'morning.gpx'), tracks, leftOut }], confirm: vi.fn(), cancel: vi.fn(), pick: vi.fn(), @@ -63,19 +63,54 @@ describe('TourGpxPreviewDialog (ACT-01)', () => { /> ); fireEvent.click(screen.getByTestId('gpx-preview-import')); - expect(current.confirm).toHaveBeenCalledWith({ tour_type: 'kayak', boat_type: 'sup', water_context: 'lake' }); + expect(current.confirm).toHaveBeenCalledWith( + { tour_type: 'kayak', boat_type: 'sup', water_context: 'lake' }, + { points: false } + ); }); it('FE-GPX-PREVIEW-003: a city walk is imported as a city walk, and without a preset as a hike', () => { const walk = gpxImport([track()]); const { unmount } = render(); fireEvent.click(screen.getByTestId('gpx-preview-import')); - expect(walk.confirm).toHaveBeenCalledWith({ tour_type: 'walk' }); + expect(walk.confirm).toHaveBeenCalledWith({ tour_type: 'walk' }, { points: false }); unmount(); const hike = gpxImport([track()]); render(); fireEvent.click(screen.getByTestId('gpx-preview-import')); - expect(hike.confirm).toHaveBeenCalledWith({ tour_type: 'hike' }); + expect(hike.confirm).toHaveBeenCalledWith({ tour_type: 'hike' }, { points: false }); + }); +}); + +describe('TourGpxPreviewDialog with points beside the lines (#2969)', () => { + it('FE-GPX-PREVIEW-005: offers the points as places, on by default, and imports them with the tour', () => { + const current = gpxImport([track()], { points: 4, areas: 0, other: 0 }); + render(); + expect(screen.getByTestId('gpx-preview-points')).toBeInTheDocument(); + // Added as places, they are not listed as left out. + expect(screen.queryByTestId('gpx-preview-left-out')).toBeNull(); + fireEvent.click(screen.getByTestId('gpx-preview-import')); + expect(current.confirm).toHaveBeenCalledWith({ tour_type: 'hike' }, { points: true }); + }); + + it('FE-GPX-PREVIEW-006: switched off, the points are named as left out and not imported', () => { + const current = gpxImport([track()], { points: 4, areas: 1, other: 0 }); + render(); + fireEvent.click(screen.getByRole('button', { name: /tours\.import\.pointsAsPlaces|Add the points as places/ })); + expect(screen.getByTestId('gpx-preview-left-out')).toHaveTextContent(/tours\.import\.leftOutPoints|4 pins/); + fireEvent.click(screen.getByTestId('gpx-preview-import')); + expect(current.confirm).toHaveBeenCalledWith({ tour_type: 'hike' }, { points: false }); + }); + + it('FE-GPX-PREVIEW-007: a file whose every track the trip holds can still bring its points', () => { + const current = gpxImport([track({ duplicate: true })], { points: 2, areas: 0, other: 0 }); + render(); + const button = screen.getByTestId('gpx-preview-import'); + expect(button).toBeEnabled(); + expect(button).toHaveTextContent(/tours\.import\.pointsConfirm|Add places/); + expect(screen.queryByText(/tours\.import\.previewAllDuplicates|nothing to import/)).toBeNull(); + fireEvent.click(button); + expect(current.confirm).toHaveBeenCalledWith({ tour_type: 'hike' }, { points: true }); }); }); diff --git a/client/src/components/Tours/TourGpxPreviewDialog.tsx b/client/src/components/Tours/TourGpxPreviewDialog.tsx index a0b947f4a..c580db8dd 100644 --- a/client/src/components/Tours/TourGpxPreviewDialog.tsx +++ b/client/src/components/Tours/TourGpxPreviewDialog.tsx @@ -1,6 +1,6 @@ import type { TourFileLeftOut, TourGpxImportFields } from '@trek/shared'; import { AlertTriangle, Ban, Clock, FileDown, Layers, MapPin, Shapes } from 'lucide-react'; -import { useId, useMemo } from 'react'; +import { useId, useMemo, useState } from 'react'; import { OFM_POSITRON } from '../../constants/mapDefaults'; import { useTileUrl } from '../../hooks/useTileUrl'; import { useTranslation } from '../../i18n'; @@ -8,6 +8,7 @@ import { useSettingsStore } from '../../store/settingsStore'; import { formatDistance, formatElevation } from '../../utils/units'; import { MapViewAuto } from '../Map/MapViewAuto'; import { SoftPill } from '../Planner/planParts'; +import ToggleSwitch from '../Settings/ToggleSwitch'; import { DialogButton, DialogFooter, @@ -98,8 +99,8 @@ function PreviewTrackRow({ ); } -/** What all the files held besides their lines, summed by kind; null when they held nothing else. */ -function leftOutOf(files: readonly GpxPreviewFile[]): TourFileLeftOut | null { +/** What all the files held besides their lines, summed by kind. */ +function leftOutOf(files: readonly GpxPreviewFile[]): TourFileLeftOut { const sum = { points: 0, areas: 0, other: 0 }; for (const { leftOut } of files) { if (!leftOut) continue; @@ -107,18 +108,46 @@ function leftOutOf(files: readonly GpxPreviewFile[]): TourFileLeftOut | null { sum.areas += leftOut.areas; sum.other += leftOut.other; } - return sum.points + sum.areas + sum.other > 0 ? sum : null; + return sum; } /** - * IO-02: what a KML or KMZ file held besides its lines, named before the - * import so nothing goes missing unseen: a dashed card with each kind as a - * counted badge, and why in the info dot. + * #2969: the single points of the files (GPX waypoints, KML pins), which no + * tour is made of, offered as places of the trip. On by default: a file with + * a route and its stops is usually meant as both. */ -function PreviewLeftOut({ files }: { files: readonly GpxPreviewFile[] }) { +function PreviewPoints({ count, on, onToggle }: { count: number; on: boolean; onToggle: () => void }) { const { t } = useTranslation(); - const leftOut = leftOutOf(files); - if (!leftOut) return null; + const hintId = useId(); + return ( +
+
+ ); +} + +/** + * IO-02: what a file held besides its lines and does not take in, named + * before the import so nothing goes missing unseen: a dashed card with each + * kind as a counted badge, and why in the info dot. Its points only while + * they are not added as places. + */ +function PreviewLeftOut({ leftOut }: { leftOut: TourFileLeftOut }) { + const { t } = useTranslation(); + if (leftOut.points + leftOut.areas + leftOut.other === 0) return null; const kinds = [ { key: 'points', count: leftOut.points, icon: MapPin, labelKey: 'tours.import.leftOutPoints' }, { key: 'areas', count: leftOut.areas, icon: Shapes, labelKey: 'tours.import.leftOutAreas' }, @@ -183,6 +212,9 @@ function PreviewDialog({ const labelId = useId(); const tileUrl = useTileUrl(OFM_POSITRON); const choices = useGpxImportChoices(initial); + const leftOut = useMemo(() => leftOutOf(files), [files]); + const [withPoints, setWithPoints] = useState(true); + const addsPoints = withPoints && leftOut.points > 0; const tracks = files.flatMap((file) => file.tracks.map((track) => ({ file: file.file, track }))); const fresh = tracks.filter(({ track }) => !track.duplicate).length; @@ -194,7 +226,8 @@ function PreviewDialog({ [files] ); const focusPoints = useMemo(() => lines.flat(), [lines]); - const canImport = fresh > 0 && importMissing(choices.chosen) === null && !gpxImport.importing; + const canImport = + (fresh > 0 || addsPoints) && (fresh === 0 || importMissing(choices.chosen) === null) && !gpxImport.importing; const fields = importFields(choices.chosen); const title = files.length === 1 ? files[0].file.name : t('tours.import.previewFiles', { count: files.length }); @@ -231,9 +264,9 @@ function PreviewDialog({ data-testid="gpx-preview-import" icon={} disabled={!canImport} - onClick={() => void gpxImport.confirm(fields)} + onClick={() => void gpxImport.confirm(fields, { points: addsPoints })} > - {t('tours.import.previewConfirm', { count: fresh })} + {fresh > 0 ? t('tours.import.previewConfirm', { count: fresh }) : t('tours.import.pointsConfirm')} } @@ -258,8 +291,11 @@ function PreviewDialog({ ))} - - {fresh === 0 && ( + {leftOut.points > 0 && ( + setWithPoints((on) => !on)} /> + )} + + {fresh === 0 && !addsPoints && (
}> {t('tours.import.previewAllDuplicates')} diff --git a/client/src/components/Tours/useTourGpxImport.test.ts b/client/src/components/Tours/useTourGpxImport.test.ts index 7fd4cc958..efb44d98b 100644 --- a/client/src/components/Tours/useTourGpxImport.test.ts +++ b/client/src/components/Tours/useTourGpxImport.test.ts @@ -5,7 +5,9 @@ import { act, renderHook } from '@testing-library/react'; import { afterEach, beforeEach, describe, expect, it, vi } from 'vitest'; import { resetAllStores } from '../../../tests/helpers/store'; +import { placesApi } from '../../api/client'; import { tourRepo } from '../../repo/tourRepo'; +import { useTripStore } from '../../store/tripStore'; import { useTourGpxImport } from './useTourGpxImport'; // The toasts are caught at useToast, so no window global has to be set for them. @@ -67,3 +69,66 @@ describe('useTourGpxImport without a line in the file', () => { expect(addToast).toHaveBeenCalledWith('tours.import.unreadableIn', 'error', undefined); }); }); + +describe('useTourGpxImport with points beside the lines (#2969)', () => { + const route = new File([''], 'sygic.gpx'); + const pins = new File([''], 'pins.kml'); + const line = { + name: 'Route', + geometry: [[47, 11] as [number, number], [47.01, 11] as [number, number]], + point_count: 2, + distance_km: 1, + elevation_gain_m: null, + elevation_loss_m: null, + duplicate: false, + }; + + beforeEach(() => { + useTripStore.setState({ refreshPlaces: vi.fn(async () => {}) } as never); + vi.spyOn(tourRepo, 'importGpx').mockResolvedValue({ tours: [], skipped: 0, caution: false } as never); + }); + + const previewed = async (preview: (picked: File) => object) => { + vi.spyOn(tourRepo, 'previewGpx').mockImplementation(async (_tripId, picked) => preview(picked as File) as never); + const hook = renderHook(() => useTourGpxImport({ tripId: 2, canEdit: true })); + await act(() => hook.result.current.importFiles([route, pins])); + return hook; + }; + + it('adds the points of each file as places through the places import, waypoints only', async () => { + const importGpx = vi.spyOn(placesApi, 'importGpx').mockResolvedValue({ count: 3 }); + const importMap = vi.spyOn(placesApi, 'importMapFile').mockResolvedValue({ count: 2 }); + const { result } = await previewed((picked) => ({ + tracks: [line], + left_out: { points: picked === route ? 3 : 2, areas: 0, other: 0 }, + })); + await act(() => result.current.confirm({ tour_type: 'hike' }, { points: true })); + expect(importGpx).toHaveBeenCalledWith(2, route, { waypoints: true, routes: false, tracks: false }); + expect(importMap).toHaveBeenCalledWith(2, pins, { points: true, paths: false }); + expect(tourRepo.importGpx).toHaveBeenCalledTimes(2); + expect(addToast).toHaveBeenCalledWith('tours.import.pointsImported', 'success', undefined); + }); + + it('leaves the points out when they are not asked for, and skips the Tours import for a file of known lines', async () => { + const importGpx = vi.spyOn(placesApi, 'importGpx').mockResolvedValue({ count: 3 }); + const { result } = await previewed(() => ({ + tracks: [{ ...line, duplicate: true }], + left_out: { points: 3, areas: 0, other: 0 }, + })); + await act(() => result.current.confirm({ tour_type: 'hike' })); + expect(importGpx).not.toHaveBeenCalled(); + expect(tourRepo.importGpx).not.toHaveBeenCalled(); + }); + + it('names a file whose points could not be added, and still takes in its tour', async () => { + vi.spyOn(placesApi, 'importGpx').mockRejectedValue(new Error('offline')); + vi.spyOn(placesApi, 'importMapFile').mockResolvedValue({ count: 0 }); + const { result } = await previewed((picked) => ({ + tracks: [line], + ...(picked === route ? { left_out: { points: 1, areas: 0, other: 0 } } : {}), + })); + await act(() => result.current.confirm({ tour_type: 'hike' }, { points: true })); + expect(addToast).toHaveBeenCalledWith('tours.import.pointsFailedIn', 'error', undefined); + expect(tourRepo.importGpx).toHaveBeenCalledTimes(2); + }); +}); diff --git a/client/src/components/Tours/useTourGpxImport.ts b/client/src/components/Tours/useTourGpxImport.ts index 435bb0ae9..60b4c7eec 100644 --- a/client/src/components/Tours/useTourGpxImport.ts +++ b/client/src/components/Tours/useTourGpxImport.ts @@ -1,5 +1,6 @@ import type { TourFileLeftOut, TourGpxImportFields, TourGpxPreviewTrack, TourListItem } from '@trek/shared'; import { useCallback, useState } from 'react'; +import { placesApi } from '../../api/client'; import { useTranslation } from '../../i18n'; import { tourRepo } from '../../repo/tourRepo'; import { useTripStore } from '../../store/tripStore'; @@ -40,10 +41,25 @@ function fileErrorMessage(error: unknown, name: string, t: Translate): string { export interface GpxPreviewFile { file: File; tracks: TourGpxPreviewTrack[]; - /** What a KML or KMZ file held besides its lines, which no tour is made of; absent for GPX. */ + /** What the file held besides its lines, which no tour is made of: GPX waypoints, KML pins, areas and the rest. */ leftOut?: TourFileLeftOut; } +/** The points of one file added as places of the trip, through the places import that has always taken them. */ +async function importPoints(tripId: number, file: File): Promise { + const isGpx = file.name.toLowerCase().endsWith('.gpx'); + const result = isGpx + ? await placesApi.importGpx(tripId, file, { waypoints: true, routes: false, tracks: false }) + : await placesApi.importMapFile(tripId, file, { points: true, paths: false }); + return result.count ?? 0; +} + +/** What to do with the previewed files besides their lines. */ +export interface TourImportExtras { + /** Add each file's points (GPX waypoints, KML pins) as places of the trip (#2969). */ + points?: boolean; +} + interface TourGpxImportOptions { tripId: number; canEdit: boolean; @@ -115,26 +131,46 @@ export function useTourGpxImport({ tripId, canEdit, onToursChanged, onImported, [canEdit, fileError, importing, onNoTrack, preview, t, toast, tripId] ); - /** The previewed files imported as the chosen type, summed up in one set of toasts. */ + /** + * The previewed files imported as the chosen type, summed up in one set of + * toasts. With `points`, what each file holds as single points comes in as + * places as well, so a file with a route and its stops loses neither (#2969). + * A file whose every line the trip already holds is not sent to the Tours + * import at all, only for its points. + */ const confirm = useCallback( - async (fields: TourGpxImportFields) => { + async (fields: TourGpxImportFields, extras: TourImportExtras = {}) => { if (!canEdit || !preview || importing) return; setImporting(true); const imported: TourListItem[] = []; let skipped = 0; let caution = false; + let places = 0; try { - for (const { file } of preview) { - try { - const result = await tourRepo.importGpx(tripId, file, fields); - imported.push(...result.tours); - skipped += result.skipped; - caution ||= result.caution; - } catch (error: unknown) { - fileError(error, file); + for (const { file, tracks, leftOut } of preview) { + if (tracks.some((track) => !track.duplicate)) { + try { + const result = await tourRepo.importGpx(tripId, file, fields); + imported.push(...result.tours); + skipped += result.skipped; + caution ||= result.caution; + } catch (error: unknown) { + fileError(error, file); + } + } + if (extras.points && (leftOut?.points ?? 0) > 0) { + try { + places += await importPoints(tripId, file); + } catch { + toast.error(t('tours.import.pointsFailedIn', { name: file.name })); + } } } setPreview(null); + if (places > 0) { + if (imported.length === 0) await refreshPlaces(tripId); + toast.success(t('tours.import.pointsImported', { count: places })); + } if (imported.length > 0) { // The server's place:created skips this socket, so the places are read // again here for the new ones and their lines. Only the places: a whole diff --git a/server/src/nest/tours/tour-file-import.helpers.ts b/server/src/nest/tours/tour-file-import.helpers.ts index 39727cea3..944ba7b16 100644 --- a/server/src/nest/tours/tour-file-import.helpers.ts +++ b/server/src/nest/tours/tour-file-import.helpers.ts @@ -3,8 +3,9 @@ * A GPX file gives its tracks and routes, a KML or KMZ file its lines; both * come back as the same rows, so the preview, the duplicate rule and the one * transaction that stores them are the GPX import's own. Loose points are - * never a tour: a GPX file's waypoints are skipped as they always were, and a - * KML file's pins, areas and the rest are counted for the preview to name. + * never a tour: a GPX file's waypoints and a KML file's pins, areas and the + * rest are counted for the preview, which offers to add the points as places + * of the trip through the places import (#2969). */ import { DomainError } from '../common/domain-error'; import type { PlaceImportService } from '../place-import/place-import.service'; @@ -13,7 +14,7 @@ import type { TourFileLeftOut } from '@trek/shared'; type TourFileFormat = 'gpx' | 'kml' | 'kmz'; -/** A file read for a Tours import: the rows it would store, and for a KML or KMZ file what it holds besides. */ +/** A file read for a Tours import: the rows it would store, and what the file holds besides. */ export interface TourFileRead { rows: PreparedGpxPlace[]; leftOut?: TourFileLeftOut; @@ -56,7 +57,14 @@ export async function readTourFile( importTracks: true, defaultName: fileName, }); - return { rows }; + if (rows.length === 0) return { rows }; + const points = placeImport.readGpx(fileBuffer, { + importWaypoints: true, + importRoutes: false, + importTracks: false, + defaultName: fileName, + }).length; + return points > 0 ? { rows, leftOut: { points, areas: 0, other: 0 } } : { rows }; } try { const { tracks, leftOut } = await placeImport.readMapFileTracks(fileBuffer, fileName ?? ''); diff --git a/server/src/nest/tours/tours.service.ts b/server/src/nest/tours/tours.service.ts index ecc6b766f..4ff0d080c 100644 --- a/server/src/nest/tours/tours.service.ts +++ b/server/src/nest/tours/tours.service.ts @@ -943,8 +943,8 @@ export class ToursService { /** * PAD-02: what a file would import, read with the import's own parser and * duplicate rule and written nowhere. Null when the file holds no track or - * route, the import's own refusal. A KML or KMZ file also says what it holds - * besides its lines (IO-02). + * route, the import's own refusal. The preview also says what the file holds + * besides its lines (IO-02), a GPX file its waypoints (#2969). */ async previewGpx(tripId: string, fileBuffer: Buffer, fileName?: string): Promise { const { rows, leftOut } = await readTourFile(this.placeImport, fileBuffer, fileName); diff --git a/server/tests/e2e/tours.e2e.test.ts b/server/tests/e2e/tours.e2e.test.ts index b1025573e..071587805 100644 --- a/server/tests/e2e/tours.e2e.test.ts +++ b/server/tests/e2e/tours.e2e.test.ts @@ -1049,6 +1049,18 @@ describe('Tours e2e (real guards + temp SQLite)', () => { expect(broadcast).not.toHaveBeenCalled(); }); + it('counts the waypoints beside the track, which no tour takes in (#2969)', async () => { + expect((await previewGpx(OWNER, TRACK_GPX)).body.left_out).toBeUndefined(); + const withPoints = TRACK_GPX.replace( + '', + 'HutLake', + ); + const res = await previewGpx(OWNER, withPoints); + expect(res.body.tracks).toHaveLength(1); + expect(res.body.left_out).toEqual({ points: 2, areas: 0, other: 0 }); + expect(await countRows(orm, Places)).toBe(0); + }); + it('leaves heights it does not have unknown, and names a track the trip already holds', async () => { const flat = await previewGpx(OWNER, FLAT_GPX); expect(flat.body.tracks[0]).toMatchObject({ elevation_gain_m: null, elevation_loss_m: null }); diff --git a/shared/src/i18n/ar/tours.ts b/shared/src/i18n/ar/tours.ts index 8a04af13e..91da46c49 100644 --- a/shared/src/i18n/ar/tours.ts +++ b/shared/src/i18n/ar/tours.ts @@ -967,6 +967,11 @@ const tours: TranslationStrings = { 'tours.import.leftOutOther.two': 'عنصران آخران', 'tours.import.leftOutOther.few': '{count} عناصر أخرى', 'tours.import.leftOutOther.many': '{count} عنصرًا آخر', + 'tours.import.pointsAsPlaces': 'أضف النقاط كأماكن', + 'tours.import.pointsAsPlacesHint': 'النقاط المفردة ليست جزءًا من الجولة، بل تُضاف إلى أماكن الرحلة.', + 'tours.import.pointsConfirm': 'أضف الأماكن', + 'tours.import.pointsFailedIn': 'تعذّرت إضافة النقاط في {name} كأماكن.', + 'tours.import.pointsImported': 'الأماكن المضافة: {count}', 'tours.import.unreadableIn': 'تعذّرت قراءة {name}: الملف تالف أو ليس GPX أو KML أو KMZ', 'tours.import.archiveNoKmlIn': 'لم يُعثر على ملف KML في {name}', 'tours.import.archiveTooLargeIn': '{name} كبير جدًا بعد فك الضغط', diff --git a/shared/src/i18n/az/tours.ts b/shared/src/i18n/az/tours.ts index c64547394..62bd4f92e 100644 --- a/shared/src/i18n/az/tours.ts +++ b/shared/src/i18n/az/tours.ts @@ -956,6 +956,11 @@ const tours: TranslationStrings = { 'tours.import.leftOutAreas.one': '{count} sahə', 'tours.import.leftOutOther': '{count} digər element', 'tours.import.leftOutOther.one': '{count} digər element', + 'tours.import.pointsAsPlaces': 'Nöqtələri məkan kimi əlavə et', + 'tours.import.pointsAsPlacesHint': 'Tək nöqtələr turun bir hissəsi deyil. Onlar əvəzinə səyahətin məkanlarına əlavə olunur.', + 'tours.import.pointsConfirm': 'Məkanları əlavə et', + 'tours.import.pointsFailedIn': '{name} faylındakı nöqtələr məkan kimi əlavə edilə bilmədi.', + 'tours.import.pointsImported': 'Əlavə edilən məkanlar: {count}', 'tours.import.unreadableIn': '{name} oxunmadı: fayl zədələnib və ya GPX, KML, KMZ deyil', 'tours.import.archiveNoKmlIn': '{name} içində KML faylı tapılmadı', 'tours.import.archiveTooLargeIn': '{name} açıldıqdan sonra çox böyükdür', diff --git a/shared/src/i18n/br/tours.ts b/shared/src/i18n/br/tours.ts index ab9dbca6b..288d0b40b 100644 --- a/shared/src/i18n/br/tours.ts +++ b/shared/src/i18n/br/tours.ts @@ -962,6 +962,11 @@ const tours: TranslationStrings = { 'tours.import.leftOutAreas.one': '{count} área', 'tours.import.leftOutOther': '{count} outros itens', 'tours.import.leftOutOther.one': '{count} outro item', + 'tours.import.pointsAsPlaces': 'Adicionar os pontos como lugares', + 'tours.import.pointsAsPlacesHint': 'Pontos avulsos não fazem parte de um passeio. Eles entram nos lugares da viagem.', + 'tours.import.pointsConfirm': 'Adicionar lugares', + 'tours.import.pointsFailedIn': 'Não foi possível adicionar os pontos de {name} como lugares.', + 'tours.import.pointsImported': 'Lugares adicionados: {count}', 'tours.import.unreadableIn': 'Não foi possível ler {name}: o arquivo está danificado ou não é GPX, KML nem KMZ', 'tours.import.archiveNoKmlIn': 'Nenhum arquivo KML encontrado em {name}', 'tours.import.archiveTooLargeIn': '{name} é grande demais depois de descompactado', diff --git a/shared/src/i18n/ca/tours.ts b/shared/src/i18n/ca/tours.ts index a2ab02eae..a76090a1c 100644 --- a/shared/src/i18n/ca/tours.ts +++ b/shared/src/i18n/ca/tours.ts @@ -964,6 +964,11 @@ const tours: TranslationStrings = { 'tours.import.leftOutAreas.one': '{count} àrea', 'tours.import.leftOutOther': '{count} elements més', 'tours.import.leftOutOther.one': '{count} element més', + 'tours.import.pointsAsPlaces': 'Afegeix els punts com a llocs', + 'tours.import.pointsAsPlacesHint': 'Els punts solts no formen part de cap ruta. S’afegeixen als llocs del viatge.', + 'tours.import.pointsConfirm': 'Afegeix llocs', + 'tours.import.pointsFailedIn': 'No s’han pogut afegir els punts de {name} com a llocs.', + 'tours.import.pointsImported': 'Llocs afegits: {count}', 'tours.import.unreadableIn': "No s'ha pogut llegir {name}: el fitxer està malmès o no és GPX, KML ni KMZ", 'tours.import.archiveNoKmlIn': "No s'ha trobat cap fitxer KML a {name}", 'tours.import.archiveTooLargeIn': '{name} és massa gran un cop descomprimit', diff --git a/shared/src/i18n/cs/tours.ts b/shared/src/i18n/cs/tours.ts index f91389d78..19d0adeaa 100644 --- a/shared/src/i18n/cs/tours.ts +++ b/shared/src/i18n/cs/tours.ts @@ -950,6 +950,11 @@ const tours: TranslationStrings = { 'tours.import.leftOutOther': '{count} dalších položek', 'tours.import.leftOutOther.one': '{count} další položka', 'tours.import.leftOutOther.few': '{count} další položky', + 'tours.import.pointsAsPlaces': 'Přidat body jako místa', + 'tours.import.pointsAsPlacesHint': 'Samostatné body nejsou součástí trasy. Přidají se místo toho k místům cesty.', + 'tours.import.pointsConfirm': 'Přidat místa', + 'tours.import.pointsFailedIn': 'Body ze souboru {name} se nepodařilo přidat jako místa.', + 'tours.import.pointsImported': 'Přidaná místa: {count}', 'tours.import.unreadableIn': 'Soubor {name} nelze přečíst: je poškozený nebo to není GPX, KML ani KMZ', 'tours.import.archiveNoKmlIn': 'V souboru {name} není žádný soubor KML', 'tours.import.archiveTooLargeIn': 'Soubor {name} je po rozbalení příliš velký', diff --git a/shared/src/i18n/de/tours.ts b/shared/src/i18n/de/tours.ts index 1d2fdd289..72d7488c6 100644 --- a/shared/src/i18n/de/tours.ts +++ b/shared/src/i18n/de/tours.ts @@ -972,6 +972,11 @@ const tours: TranslationStrings = { 'tours.import.leftOutAreas.one': '{count} Fläche', 'tours.import.leftOutOther': '{count} weitere Elemente', 'tours.import.leftOutOther.one': '{count} weiteres Element', + 'tours.import.pointsAsPlaces': 'Punkte als Orte hinzufügen', + 'tours.import.pointsAsPlacesHint': 'Einzelne Punkte gehören zu keiner Tour. Sie kommen stattdessen zu den Orten der Reise.', + 'tours.import.pointsConfirm': 'Orte hinzufügen', + 'tours.import.pointsFailedIn': 'Die Punkte aus {name} konnten nicht als Orte hinzugefügt werden.', + 'tours.import.pointsImported': 'Orte hinzugefügt: {count}', 'tours.import.unreadableIn': '{name} konnte nicht gelesen werden: Die Datei ist beschädigt oder kein GPX, KML oder KMZ', 'tours.import.archiveNoKmlIn': 'Keine KML-Datei in {name} gefunden', diff --git a/shared/src/i18n/en/tours.ts b/shared/src/i18n/en/tours.ts index 241247b40..134389c73 100644 --- a/shared/src/i18n/en/tours.ts +++ b/shared/src/i18n/en/tours.ts @@ -944,6 +944,11 @@ const tours: TranslationStrings = { 'tours.import.leftOutAreas.one': '{count} area', 'tours.import.leftOutOther': '{count} other items', 'tours.import.leftOutOther.one': '{count} other item', + 'tours.import.pointsAsPlaces': 'Add the points as places', + 'tours.import.pointsAsPlacesHint': 'Single points are not part of a tour. They join the places of the trip instead.', + 'tours.import.pointsConfirm': 'Add places', + 'tours.import.pointsFailedIn': 'The points in {name} could not be added as places.', + 'tours.import.pointsImported': 'Places added: {count}', 'tours.import.unreadableIn': 'Could not read {name}: the file is damaged or not GPX, KML or KMZ', 'tours.import.archiveNoKmlIn': 'No KML file found in {name}', 'tours.import.archiveTooLargeIn': '{name} is too large once unpacked', diff --git a/shared/src/i18n/es/tours.ts b/shared/src/i18n/es/tours.ts index 71aeb34f9..ed7ff0780 100644 --- a/shared/src/i18n/es/tours.ts +++ b/shared/src/i18n/es/tours.ts @@ -961,6 +961,11 @@ const tours: TranslationStrings = { 'tours.import.leftOutAreas.one': '{count} área', 'tours.import.leftOutOther': '{count} elementos más', 'tours.import.leftOutOther.one': '{count} elemento más', + 'tours.import.pointsAsPlaces': 'Añadir los puntos como lugares', + 'tours.import.pointsAsPlacesHint': 'Los puntos sueltos no forman parte de una ruta. Se añaden a los lugares del viaje.', + 'tours.import.pointsConfirm': 'Añadir lugares', + 'tours.import.pointsFailedIn': 'No se pudieron añadir los puntos de {name} como lugares.', + 'tours.import.pointsImported': 'Lugares añadidos: {count}', 'tours.import.unreadableIn': 'No se ha podido leer {name}: el archivo está dañado o no es GPX, KML ni KMZ', 'tours.import.archiveNoKmlIn': 'No hay ningún archivo KML en {name}', 'tours.import.archiveTooLargeIn': '{name} es demasiado grande una vez descomprimido', diff --git a/shared/src/i18n/et/tours.ts b/shared/src/i18n/et/tours.ts index 24cdac66d..e52e143fd 100644 --- a/shared/src/i18n/et/tours.ts +++ b/shared/src/i18n/et/tours.ts @@ -946,6 +946,11 @@ const tours: TranslationStrings = { 'tours.import.leftOutAreas.one': '{count} ala', 'tours.import.leftOutOther': '{count} muud elementi', 'tours.import.leftOutOther.one': '{count} muu element', + 'tours.import.pointsAsPlaces': 'Lisa punktid kohtadena', + 'tours.import.pointsAsPlacesHint': 'Üksikud punktid ei kuulu matka juurde. Need lisatakse hoopis reisi kohtade hulka.', + 'tours.import.pointsConfirm': 'Lisa kohad', + 'tours.import.pointsFailedIn': 'Faili {name} punkte ei õnnestunud kohtadena lisada.', + 'tours.import.pointsImported': 'Lisatud kohti: {count}', 'tours.import.unreadableIn': 'Faili {name} ei saanud lugeda: see on vigane või pole GPX, KML ega KMZ', 'tours.import.archiveNoKmlIn': 'Failis {name} pole KML-faili', 'tours.import.archiveTooLargeIn': 'Fail {name} on lahtipakituna liiga suur', diff --git a/shared/src/i18n/fr/tours.ts b/shared/src/i18n/fr/tours.ts index 409df365c..c73928472 100644 --- a/shared/src/i18n/fr/tours.ts +++ b/shared/src/i18n/fr/tours.ts @@ -979,6 +979,11 @@ const tours: TranslationStrings = { 'tours.import.leftOutAreas.one': '{count} zone', 'tours.import.leftOutOther': '{count} autres éléments', 'tours.import.leftOutOther.one': '{count} autre élément', + 'tours.import.pointsAsPlaces': 'Ajouter les points comme lieux', + 'tours.import.pointsAsPlacesHint': 'Les points isolés ne font pas partie d’un parcours. Ils rejoignent les lieux du voyage.', + 'tours.import.pointsConfirm': 'Ajouter les lieux', + 'tours.import.pointsFailedIn': 'Les points de {name} n’ont pas pu être ajoutés comme lieux.', + 'tours.import.pointsImported': 'Lieux ajoutés : {count}', 'tours.import.unreadableIn': 'Impossible de lire {name} : le fichier est endommagé ou n’est pas un GPX, KML ou KMZ', 'tours.import.archiveNoKmlIn': 'Aucun fichier KML trouvé dans {name}', 'tours.import.archiveTooLargeIn': '{name} est trop volumineux une fois décompressé', diff --git a/shared/src/i18n/gr/tours.ts b/shared/src/i18n/gr/tours.ts index 0034c3e69..89bc86101 100644 --- a/shared/src/i18n/gr/tours.ts +++ b/shared/src/i18n/gr/tours.ts @@ -983,6 +983,11 @@ const tours: TranslationStrings = { 'tours.import.leftOutAreas.one': '{count} περιοχή', 'tours.import.leftOutOther': '{count} άλλα στοιχεία', 'tours.import.leftOutOther.one': '{count} άλλο στοιχείο', + 'tours.import.pointsAsPlaces': 'Προσθήκη των σημείων ως τοποθεσίες', + 'tours.import.pointsAsPlacesHint': 'Τα μεμονωμένα σημεία δεν ανήκουν σε διαδρομή. Προστίθενται στις τοποθεσίες του ταξιδιού.', + 'tours.import.pointsConfirm': 'Προσθήκη τοποθεσιών', + 'tours.import.pointsFailedIn': 'Δεν ήταν δυνατή η προσθήκη των σημείων του {name} ως τοποθεσίες.', + 'tours.import.pointsImported': 'Τοποθεσίες που προστέθηκαν: {count}', 'tours.import.unreadableIn': 'Δεν ήταν δυνατή η ανάγνωση του {name}: το αρχείο είναι κατεστραμμένο ή δεν είναι GPX, KML ή KMZ', 'tours.import.archiveNoKmlIn': 'Δεν βρέθηκε αρχείο KML στο {name}', diff --git a/shared/src/i18n/he/tours.ts b/shared/src/i18n/he/tours.ts index 58fd8fa47..e1a16f9a4 100644 --- a/shared/src/i18n/he/tours.ts +++ b/shared/src/i18n/he/tours.ts @@ -943,6 +943,11 @@ const tours: TranslationStrings = { 'tours.import.leftOutOther': '{count} פריטים אחרים', 'tours.import.leftOutOther.one': 'פריט אחר אחד', 'tours.import.leftOutOther.two': '{count} פריטים אחרים', + 'tours.import.pointsAsPlaces': 'הוספת הנקודות כמקומות', + 'tours.import.pointsAsPlacesHint': 'נקודות בודדות אינן חלק ממסלול. הן מתווספות למקומות של הטיול.', + 'tours.import.pointsConfirm': 'הוספת מקומות', + 'tours.import.pointsFailedIn': 'לא ניתן היה להוסיף את הנקודות מ-{name} כמקומות.', + 'tours.import.pointsImported': 'מקומות שנוספו: {count}', 'tours.import.unreadableIn': 'לא ניתן לקרוא את {name}: הקובץ פגום או שאינו בפורמט GPX, KML או KMZ', 'tours.import.archiveNoKmlIn': 'לא נמצא קובץ KML בתוך {name}', 'tours.import.archiveTooLargeIn': '{name} גדול מדי לאחר חילוץ', diff --git a/shared/src/i18n/hu/tours.ts b/shared/src/i18n/hu/tours.ts index 409061d71..43940643f 100644 --- a/shared/src/i18n/hu/tours.ts +++ b/shared/src/i18n/hu/tours.ts @@ -961,6 +961,11 @@ const tours: TranslationStrings = { 'tours.import.leftOutAreas.one': '{count} terület', 'tours.import.leftOutOther': '{count} egyéb elem', 'tours.import.leftOutOther.one': '{count} egyéb elem', + 'tours.import.pointsAsPlaces': 'Pontok hozzáadása helyekként', + 'tours.import.pointsAsPlacesHint': 'Az önálló pontok nem részei egy túrának. Helyettük az utazás helyei közé kerülnek.', + 'tours.import.pointsConfirm': 'Helyek hozzáadása', + 'tours.import.pointsFailedIn': 'A(z) {name} pontjait nem sikerült helyekként hozzáadni.', + 'tours.import.pointsImported': 'Hozzáadott helyek: {count}', 'tours.import.unreadableIn': 'A(z) {name} nem olvasható: a fájl sérült, vagy nem GPX, KML vagy KMZ', 'tours.import.archiveNoKmlIn': 'A(z) {name} nem tartalmaz KML-fájlt', 'tours.import.archiveTooLargeIn': 'A(z) {name} kicsomagolva túl nagy', diff --git a/shared/src/i18n/id/tours.ts b/shared/src/i18n/id/tours.ts index 67be5a83f..76a74ba6f 100644 --- a/shared/src/i18n/id/tours.ts +++ b/shared/src/i18n/id/tours.ts @@ -952,6 +952,11 @@ const tours: TranslationStrings = { 'tours.import.leftOutPoints': '{count} penanda', 'tours.import.leftOutAreas': '{count} wilayah', 'tours.import.leftOutOther': '{count} item lain', + 'tours.import.pointsAsPlaces': 'Tambahkan titik sebagai tempat', + 'tours.import.pointsAsPlacesHint': 'Titik tunggal bukan bagian dari tur. Titik itu masuk ke tempat-tempat perjalanan.', + 'tours.import.pointsConfirm': 'Tambahkan tempat', + 'tours.import.pointsFailedIn': 'Titik di {name} tidak dapat ditambahkan sebagai tempat.', + 'tours.import.pointsImported': 'Tempat ditambahkan: {count}', 'tours.import.unreadableIn': '{name} tidak dapat dibaca: berkas rusak atau bukan GPX, KML, atau KMZ', 'tours.import.archiveNoKmlIn': 'Tidak ada berkas KML di {name}', 'tours.import.archiveTooLargeIn': '{name} terlalu besar setelah diekstrak', diff --git a/shared/src/i18n/it/tours.ts b/shared/src/i18n/it/tours.ts index b349ee1e8..1040c8da6 100644 --- a/shared/src/i18n/it/tours.ts +++ b/shared/src/i18n/it/tours.ts @@ -968,6 +968,11 @@ const tours: TranslationStrings = { 'tours.import.leftOutAreas.one': '{count} superficie', 'tours.import.leftOutOther': '{count} altri elementi', 'tours.import.leftOutOther.one': '{count} altro elemento', + 'tours.import.pointsAsPlaces': 'Aggiungi i punti come luoghi', + 'tours.import.pointsAsPlacesHint': 'I punti singoli non fanno parte di un percorso. Vengono aggiunti ai luoghi del viaggio.', + 'tours.import.pointsConfirm': 'Aggiungi luoghi', + 'tours.import.pointsFailedIn': 'Non è stato possibile aggiungere i punti di {name} come luoghi.', + 'tours.import.pointsImported': 'Luoghi aggiunti: {count}', 'tours.import.unreadableIn': 'Impossibile leggere {name}: il file è danneggiato o non è GPX, KML o KMZ', 'tours.import.archiveNoKmlIn': 'Nessun file KML trovato in {name}', 'tours.import.archiveTooLargeIn': '{name} è troppo grande una volta decompresso', diff --git a/shared/src/i18n/ja/tours.ts b/shared/src/i18n/ja/tours.ts index dcb1f3a93..888d9760e 100644 --- a/shared/src/i18n/ja/tours.ts +++ b/shared/src/i18n/ja/tours.ts @@ -928,6 +928,11 @@ const tours: TranslationStrings = { 'tours.import.leftOutPoints': 'ピン {count} 個', 'tours.import.leftOutAreas': 'エリア {count} 件', 'tours.import.leftOutOther': 'その他 {count} 件', + 'tours.import.pointsAsPlaces': 'ポイントを場所として追加', + 'tours.import.pointsAsPlacesHint': '単独のポイントはツアーに含まれません。代わりに旅行の場所に追加されます。', + 'tours.import.pointsConfirm': '場所を追加', + 'tours.import.pointsFailedIn': '{name} のポイントを場所として追加できませんでした。', + 'tours.import.pointsImported': '追加した場所: {count}', 'tours.import.unreadableIn': '{name}を読み込めませんでした:ファイルが破損しているか、GPX、KML、KMZではありません', 'tours.import.archiveNoKmlIn': '{name}にKMLファイルが見つかりません', 'tours.import.archiveTooLargeIn': '{name}は展開すると大きすぎます', diff --git a/shared/src/i18n/ko/tours.ts b/shared/src/i18n/ko/tours.ts index 58e8ef350..0d5fe7ae5 100644 --- a/shared/src/i18n/ko/tours.ts +++ b/shared/src/i18n/ko/tours.ts @@ -920,6 +920,11 @@ const tours: TranslationStrings = { 'tours.import.leftOutPoints': '핀 {count}개', 'tours.import.leftOutAreas': '영역 {count}개', 'tours.import.leftOutOther': '기타 항목 {count}개', + 'tours.import.pointsAsPlaces': '포인트를 장소로 추가', + 'tours.import.pointsAsPlacesHint': '단일 포인트는 투어에 포함되지 않습니다. 대신 여행의 장소에 추가됩니다.', + 'tours.import.pointsConfirm': '장소 추가', + 'tours.import.pointsFailedIn': '{name}의 포인트를 장소로 추가하지 못했습니다.', + 'tours.import.pointsImported': '추가된 장소: {count}', 'tours.import.unreadableIn': '{name}을(를) 읽을 수 없습니다: 파일이 손상되었거나 GPX, KML, KMZ가 아닙니다', 'tours.import.archiveNoKmlIn': '{name}에 KML 파일이 없습니다', 'tours.import.archiveTooLargeIn': '{name}은(는) 압축을 풀면 너무 큽니다', diff --git a/shared/src/i18n/nl/tours.ts b/shared/src/i18n/nl/tours.ts index 6de9039de..4cf11d6a7 100644 --- a/shared/src/i18n/nl/tours.ts +++ b/shared/src/i18n/nl/tours.ts @@ -965,6 +965,11 @@ const tours: TranslationStrings = { 'tours.import.leftOutAreas.one': '{count} gebied', 'tours.import.leftOutOther': '{count} andere items', 'tours.import.leftOutOther.one': '{count} ander item', + 'tours.import.pointsAsPlaces': 'Punten als plekken toevoegen', + 'tours.import.pointsAsPlacesHint': 'Losse punten horen niet bij een tocht. Ze komen bij de plekken van de reis.', + 'tours.import.pointsConfirm': 'Plekken toevoegen', + 'tours.import.pointsFailedIn': 'De punten uit {name} konden niet als plekken worden toegevoegd.', + 'tours.import.pointsImported': 'Plekken toegevoegd: {count}', 'tours.import.unreadableIn': '{name} kon niet worden gelezen: het bestand is beschadigd of geen GPX, KML of KMZ', 'tours.import.archiveNoKmlIn': 'Geen KML-bestand gevonden in {name}', 'tours.import.archiveTooLargeIn': '{name} is uitgepakt te groot', diff --git a/shared/src/i18n/pl/tours.ts b/shared/src/i18n/pl/tours.ts index fdce46fc0..73ab1c87a 100644 --- a/shared/src/i18n/pl/tours.ts +++ b/shared/src/i18n/pl/tours.ts @@ -983,6 +983,11 @@ const tours: TranslationStrings = { 'tours.import.leftOutOther.one': '{count} inny element', 'tours.import.leftOutOther.few': '{count} inne elementy', 'tours.import.leftOutOther.many': '{count} innych elementów', + 'tours.import.pointsAsPlaces': 'Dodaj punkty jako miejsca', + 'tours.import.pointsAsPlacesHint': 'Pojedyncze punkty nie należą do trasy. Trafiają do miejsc podróży.', + 'tours.import.pointsConfirm': 'Dodaj miejsca', + 'tours.import.pointsFailedIn': 'Nie udało się dodać punktów z {name} jako miejsc.', + 'tours.import.pointsImported': 'Dodane miejsca: {count}', 'tours.import.unreadableIn': 'Nie można odczytać pliku {name}: jest uszkodzony albo nie jest plikiem GPX, KML ani KMZ', 'tours.import.archiveNoKmlIn': 'W pliku {name} nie ma pliku KML', diff --git a/shared/src/i18n/ru/tours.ts b/shared/src/i18n/ru/tours.ts index cb4106955..d5cb67b18 100644 --- a/shared/src/i18n/ru/tours.ts +++ b/shared/src/i18n/ru/tours.ts @@ -987,6 +987,11 @@ const tours: TranslationStrings = { 'tours.import.leftOutOther.one': '{count} другой элемент', 'tours.import.leftOutOther.few': '{count} других элемента', 'tours.import.leftOutOther.many': '{count} других элементов', + 'tours.import.pointsAsPlaces': 'Добавить точки как места', + 'tours.import.pointsAsPlacesHint': 'Отдельные точки не входят в маршрут. Они добавляются к местам поездки.', + 'tours.import.pointsConfirm': 'Добавить места', + 'tours.import.pointsFailedIn': 'Не удалось добавить точки из {name} как места.', + 'tours.import.pointsImported': 'Добавлено мест: {count}', 'tours.import.unreadableIn': 'Не удалось прочитать {name}: файл повреждён или это не GPX, KML или KMZ', 'tours.import.archiveNoKmlIn': 'В {name} не найден файл KML', 'tours.import.archiveTooLargeIn': '{name} слишком велик после распаковки', diff --git a/shared/src/i18n/sk/tours.ts b/shared/src/i18n/sk/tours.ts index ecfa42787..2a1e27e6d 100644 --- a/shared/src/i18n/sk/tours.ts +++ b/shared/src/i18n/sk/tours.ts @@ -952,6 +952,11 @@ const tours: TranslationStrings = { 'tours.import.leftOutOther': '{count} ďalších položiek', 'tours.import.leftOutOther.one': '{count} ďalšia položka', 'tours.import.leftOutOther.few': '{count} ďalšie položky', + 'tours.import.pointsAsPlaces': 'Pridať body ako miesta', + 'tours.import.pointsAsPlacesHint': 'Samostatné body nie sú súčasťou trasy. Pridajú sa namiesto toho k miestam cesty.', + 'tours.import.pointsConfirm': 'Pridať miesta', + 'tours.import.pointsFailedIn': 'Body zo súboru {name} sa nepodarilo pridať ako miesta.', + 'tours.import.pointsImported': 'Pridané miesta: {count}', 'tours.import.unreadableIn': 'Súbor {name} sa nedá prečítať: je poškodený alebo nie je GPX, KML ani KMZ', 'tours.import.archiveNoKmlIn': 'V súbore {name} nie je žiadny súbor KML', 'tours.import.archiveTooLargeIn': 'Súbor {name} je po rozbalení príliš veľký', diff --git a/shared/src/i18n/sv/tours.ts b/shared/src/i18n/sv/tours.ts index a8acab38e..c57031907 100644 --- a/shared/src/i18n/sv/tours.ts +++ b/shared/src/i18n/sv/tours.ts @@ -943,6 +943,11 @@ const tours: TranslationStrings = { 'tours.import.leftOutAreas.one': '{count} område', 'tours.import.leftOutOther': '{count} andra objekt', 'tours.import.leftOutOther.one': '{count} annat objekt', + 'tours.import.pointsAsPlaces': 'Lägg till punkterna som platser', + 'tours.import.pointsAsPlacesHint': 'Enskilda punkter ingår inte i en tur. De läggs till bland resans platser i stället.', + 'tours.import.pointsConfirm': 'Lägg till platser', + 'tours.import.pointsFailedIn': 'Punkterna i {name} kunde inte läggas till som platser.', + 'tours.import.pointsImported': 'Tillagda platser: {count}', 'tours.import.unreadableIn': 'Det gick inte att läsa {name}: filen är skadad eller inte GPX, KML eller KMZ', 'tours.import.archiveNoKmlIn': 'Ingen KML-fil hittades i {name}', 'tours.import.archiveTooLargeIn': '{name} är för stor när den packats upp', diff --git a/shared/src/i18n/th/tours.ts b/shared/src/i18n/th/tours.ts index cf4b9d551..819d5c6ce 100644 --- a/shared/src/i18n/th/tours.ts +++ b/shared/src/i18n/th/tours.ts @@ -905,6 +905,11 @@ const tours: TranslationStrings = { 'tours.import.leftOutPoints': 'หมุด {count} รายการ', 'tours.import.leftOutAreas': 'พื้นที่ {count} รายการ', 'tours.import.leftOutOther': 'รายการอื่น {count} รายการ', + 'tours.import.pointsAsPlaces': 'เพิ่มจุดเป็นสถานที่', + 'tours.import.pointsAsPlacesHint': 'จุดเดี่ยวไม่ได้เป็นส่วนหนึ่งของทัวร์ แต่จะถูกเพิ่มเข้าไปในสถานที่ของทริปแทน', + 'tours.import.pointsConfirm': 'เพิ่มสถานที่', + 'tours.import.pointsFailedIn': 'ไม่สามารถเพิ่มจุดใน {name} เป็นสถานที่ได้', + 'tours.import.pointsImported': 'เพิ่มสถานที่แล้ว: {count}', 'tours.import.unreadableIn': 'ไม่สามารถอ่าน {name} ได้: ไฟล์เสียหายหรือไม่ใช่ GPX, KML หรือ KMZ', 'tours.import.archiveNoKmlIn': 'ไม่พบไฟล์ KML ใน {name}', 'tours.import.archiveTooLargeIn': '{name} ใหญ่เกินไปเมื่อแตกไฟล์', diff --git a/shared/src/i18n/tr/tours.ts b/shared/src/i18n/tr/tours.ts index 325699370..cc22db4ed 100644 --- a/shared/src/i18n/tr/tours.ts +++ b/shared/src/i18n/tr/tours.ts @@ -954,6 +954,11 @@ const tours: TranslationStrings = { 'tours.import.leftOutAreas.one': '{count} alan', 'tours.import.leftOutOther': '{count} diğer öğe', 'tours.import.leftOutOther.one': '{count} diğer öğe', + 'tours.import.pointsAsPlaces': 'Noktaları yer olarak ekle', + 'tours.import.pointsAsPlacesHint': 'Tekil noktalar bir turun parçası değildir. Bunun yerine seyahatin yerlerine eklenir.', + 'tours.import.pointsConfirm': 'Yerleri ekle', + 'tours.import.pointsFailedIn': '{name} içindeki noktalar yer olarak eklenemedi.', + 'tours.import.pointsImported': 'Eklenen yerler: {count}', 'tours.import.unreadableIn': '{name} okunamadı: dosya bozuk ya da GPX, KML veya KMZ değil', 'tours.import.archiveNoKmlIn': '{name} içinde KML dosyası bulunamadı', 'tours.import.archiveTooLargeIn': '{name} açıldığında çok büyük', diff --git a/shared/src/i18n/uk/tours.ts b/shared/src/i18n/uk/tours.ts index b05d25adb..377f1dcbf 100644 --- a/shared/src/i18n/uk/tours.ts +++ b/shared/src/i18n/uk/tours.ts @@ -978,6 +978,11 @@ const tours: TranslationStrings = { 'tours.import.leftOutOther.one': '{count} інший елемент', 'tours.import.leftOutOther.few': '{count} інші елементи', 'tours.import.leftOutOther.many': '{count} інших елементів', + 'tours.import.pointsAsPlaces': 'Додати точки як місця', + 'tours.import.pointsAsPlacesHint': 'Окремі точки не входять до маршруту. Вони додаються до місць подорожі.', + 'tours.import.pointsConfirm': 'Додати місця', + 'tours.import.pointsFailedIn': 'Не вдалося додати точки з {name} як місця.', + 'tours.import.pointsImported': 'Додано місць: {count}', 'tours.import.unreadableIn': 'Не вдалося прочитати {name}: файл пошкоджено або це не GPX, KML чи KMZ', 'tours.import.archiveNoKmlIn': 'У {name} не знайдено файлу KML', 'tours.import.archiveTooLargeIn': '{name} завеликий після розпакування', diff --git a/shared/src/i18n/vi/tours.ts b/shared/src/i18n/vi/tours.ts index 06befb012..5ccd09945 100644 --- a/shared/src/i18n/vi/tours.ts +++ b/shared/src/i18n/vi/tours.ts @@ -939,6 +939,11 @@ const tours: TranslationStrings = { 'tours.import.leftOutPoints': '{count} ghim', 'tours.import.leftOutAreas': '{count} vùng', 'tours.import.leftOutOther': '{count} mục khác', + 'tours.import.pointsAsPlaces': 'Thêm các điểm làm địa điểm', + 'tours.import.pointsAsPlacesHint': 'Các điểm đơn lẻ không thuộc về chuyến đi bộ. Chúng được thêm vào các địa điểm của chuyến đi.', + 'tours.import.pointsConfirm': 'Thêm địa điểm', + 'tours.import.pointsFailedIn': 'Không thể thêm các điểm trong {name} làm địa điểm.', + 'tours.import.pointsImported': 'Đã thêm địa điểm: {count}', 'tours.import.unreadableIn': 'Không thể đọc {name}: tệp bị hỏng hoặc không phải GPX, KML hay KMZ', 'tours.import.archiveNoKmlIn': 'Không tìm thấy tệp KML trong {name}', 'tours.import.archiveTooLargeIn': '{name} quá lớn sau khi giải nén', diff --git a/shared/src/i18n/zh-TW/tours.ts b/shared/src/i18n/zh-TW/tours.ts index ad2a9d84d..dc3da4e97 100644 --- a/shared/src/i18n/zh-TW/tours.ts +++ b/shared/src/i18n/zh-TW/tours.ts @@ -873,6 +873,11 @@ const tours: TranslationStrings = { 'tours.import.leftOutPoints': '{count} 個圖釘', 'tours.import.leftOutAreas': '{count} 個區域', 'tours.import.leftOutOther': '其他 {count} 項', + 'tours.import.pointsAsPlaces': '將點新增為地點', + 'tours.import.pointsAsPlacesHint': '單一點不屬於路線,而是新增到行程的地點中。', + 'tours.import.pointsConfirm': '新增地點', + 'tours.import.pointsFailedIn': '無法將 {name} 中的點新增為地點。', + 'tours.import.pointsImported': '已新增地點:{count}', 'tours.import.unreadableIn': '無法讀取 {name}:檔案已損毀,或不是 GPX、KML 或 KMZ', 'tours.import.archiveNoKmlIn': '{name} 中沒有 KML 檔案', 'tours.import.archiveTooLargeIn': '{name} 解壓縮後過大', diff --git a/shared/src/i18n/zh/tours.ts b/shared/src/i18n/zh/tours.ts index 8c7e119b9..d03e14da0 100644 --- a/shared/src/i18n/zh/tours.ts +++ b/shared/src/i18n/zh/tours.ts @@ -874,6 +874,11 @@ const tours: TranslationStrings = { 'tours.import.leftOutPoints': '{count} 个图钉', 'tours.import.leftOutAreas': '{count} 个区域', 'tours.import.leftOutOther': '其他 {count} 项', + 'tours.import.pointsAsPlaces': '将点添加为地点', + 'tours.import.pointsAsPlacesHint': '单个点不属于路线,而是添加到行程的地点中。', + 'tours.import.pointsConfirm': '添加地点', + 'tours.import.pointsFailedIn': '无法将 {name} 中的点添加为地点。', + 'tours.import.pointsImported': '已添加地点:{count}', 'tours.import.unreadableIn': '无法读取 {name}:文件已损坏,或不是 GPX、KML 或 KMZ', 'tours.import.archiveNoKmlIn': '{name} 中没有 KML 文件', 'tours.import.archiveTooLargeIn': '{name} 解压后过大', diff --git a/shared/src/tours/tour-gpx-import.schema.ts b/shared/src/tours/tour-gpx-import.schema.ts index 17b1be0d8..af7dfcb54 100644 --- a/shared/src/tours/tour-gpx-import.schema.ts +++ b/shared/src/tours/tour-gpx-import.schema.ts @@ -51,9 +51,9 @@ export type TourGpxPreviewTrack = z.infer; const leftOutCount = z.number().int().nonnegative(); -/** What a KML or KMZ file held besides its lines, counted by kind; none of it is imported. */ +/** What a file held besides its lines, counted by kind; none of it becomes a tour. */ export const tourFileLeftOutSchema = z.object({ - /** Single points (pins). */ + /** Single points: a KML file's pins, a GPX file's waypoints. The client can add them as places. */ points: leftOutCount, /** Polygons. */ areas: leftOutCount, @@ -64,7 +64,7 @@ export type TourFileLeftOut = z.infer; export const tourGpxPreviewResponseSchema = z.object({ tracks: z.array(tourGpxPreviewTrackSchema), - /** Only a KML or KMZ preview counts what it leaves out; a GPX preview never counted its loose waypoints. */ + /** Absent when the file held nothing besides its lines. A GPX file only ever has points here. */ left_out: tourFileLeftOutSchema.optional(), }); export type TourGpxPreviewResponse = z.infer;