feat(tours): take a recorded ride from a GPX file as it was ridden

A ride recorded elsewhere comes in through the one Tours GPX import
(HOR-02): pick the file, choose Horse ride in the preview and confirm.
Once a ride is chosen, the preview says in three badges what the import
will and will not do for it: the line stays as the file recorded it, no
riding time is worked out, and nothing checks whether its ways may be
ridden. Each badge gives its reason in a tooltip, reachable by keyboard.
Nothing is stored before the confirmation.

Opened again, the ride's rail carries a Recorded ride card with the same
badges above its measures, and names heights the file did not carry
instead of showing them as zero. Its origin and file name stay in the
tour's provenance, it goes on a day like any tour, and no editable
version is planned from it on foot.

The import itself was already canonical and needed no change; end to end
tests now pin what this card promises: the stored line equals the
recorded points, the ride keeps its origin, distance and heights and has
no riding time, a file without heights keeps them unknown, a failed or
refused import leaves an existing ride and its place untouched, and the
same file twice is skipped.
This commit is contained in:
Maurice
2026-10-20 13:07:41 +02:00
parent 78366a9024
commit 6577595058
34 changed files with 745 additions and 3 deletions
@@ -11,6 +11,7 @@ import { useTranslation } from '../../i18n';
import { LABEL, Segmented } from '../shared/dialogParts';
import { DialogSection } from '../shared/DialogShell';
import type { GpxImportChoicesState } from './gpxImportChoices';
import { RecordedRideFacts } from './horse/RecordedRideFacts';
import { TourTypeSelect } from './planner/TourTypeSelect';
import { RunProfileField } from './run/RunFields';
import { SkiClassificationFields } from './ski/SkiClassificationFields';
@@ -61,7 +62,8 @@ function PaddleImportFields({ choices }: { choices: GpxImportChoicesState }) {
/**
* The preview's "import as" section: the type, and below it what that type
* names: a bike or run profile, a paddle tour's boat and water, a ski track's
* discipline and technique (SKI-02).
* discipline and technique (SKI-02), and for a ride (HOR-02) what the import
* keeps and does not work out.
*/
export function GpxImportTypeSection({ choices }: { choices: GpxImportChoicesState }) {
const { t } = useTranslation();
@@ -86,6 +88,11 @@ export function GpxImportTypeSection({ choices }: { choices: GpxImportChoicesSta
{chosen.type === 'run' && <RunProfileField value={chosen.runProfile} onChange={choices.setRunProfile} />}
{chosen.type === 'kayak' && <PaddleImportFields choices={choices} />}
{chosen.type === 'ski' && <SkiClassificationFields choice={chosen.ski} onChange={choices.setSki} />}
{chosen.type === 'horse' && (
<div data-testid="gpx-preview-horse">
<RecordedRideFacts />
</div>
)}
</div>
</DialogSection>
);
@@ -64,7 +64,9 @@ function PreviewTrackRow({ name, track }: { name: string; track: GpxPreviewFile[
* map, every track with its measures, the tracks the trip already holds named
* as skipped, and the type to import them as; a paddle import names its boat
* and water, as a planned paddle tour does, a ski import its discipline
* (SKI-02). Closing it stores nothing.
* (SKI-02); a ride (HOR-02) says before anything is stored that its line stays
* as recorded, gets no riding time and is not checked for access. Closing it
* stores nothing.
*/
export default function TourGpxPreviewDialog({ gpxImport }: { gpxImport: TourGpxImport }) {
const { preview } = gpxImport;
@@ -0,0 +1,155 @@
import type { TourListItem } from '@trek/shared';
import { beforeEach, describe, expect, it, vi } from 'vitest';
import { fireEvent, render, screen, within } from '../../../../tests/helpers/render';
import { resetAllStores, seedStore } from '../../../../tests/helpers/store';
import { hikePlannerDefaults } from '../../../../tests/helpers/tourPlannerStub';
import { useSettingsStore } from '../../../store/settingsStore';
import { analyzeRouteGeometry } from '../../../utils/routeGeometry';
import { TourPlannerRail } from '../planner/TourPlannerPanels';
import type { TourPlannerController } from '../planner/useTourPlanner';
import TourGpxPreviewDialog from '../TourGpxPreviewDialog';
import type { TourGpxImport } from '../useTourGpxImport';
import { RecordedRideFacts } from './RecordedRideFacts';
// FE-HORSE-IMPORT-002 to FE-HORSE-IMPORT-006 (HOR-02: a ride taken from a GPX file)
vi.mock('../../Map/MapViewAuto', () => ({ MapViewAuto: () => <div data-testid="gpx-preview-map-stub" /> }));
beforeEach(() => {
resetAllStores();
seedStore(useSettingsStore, { settings: { distance_unit: 'metric' } });
});
const ride: TourListItem = {
place_id: 42,
name: 'Ausritt an der Isar',
tour_type: 'horse',
distance: 1.1,
elevation_gain: null,
elevation_loss: null,
duration: null,
difficulty: null,
wanderer_ref: null,
match_confidence: 0.3,
max_hiking_difficulty: null,
planned: false,
caution: true,
origin: 'gpx',
has_waypoints: false,
};
function viewing(tour: TourListItem, line: number[][]): TourPlannerController {
return {
...hikePlannerDefaults(),
mode: { type: 'view-gpx', placeId: tour.place_id, tour },
tripId: 1,
canEdit: true,
canAssign: true,
isSaving: false,
hasUnsavedChanges: false,
readOnlyGpxTour: { tour },
readOnlyGpxAnalysis: analyzeRouteGeometry(JSON.stringify(line)),
returnToNeutral: vi.fn(),
startNewTour: vi.fn(),
newTourConfirmationOpen: false,
cancelNewTour: vi.fn(),
elevationProfileExpanded: true,
toggleElevationProfile: vi.fn(),
routeProfileFocus: null,
setRouteProfileFocus: vi.fn(),
} as unknown as TourPlannerController;
}
function previewing(): TourGpxImport {
const file = new File(['<gpx/>'], 'ride.gpx', { type: 'application/gpx+xml' });
return {
importing: false,
importFiles: vi.fn(),
pick: vi.fn(),
preview: [
{
file,
tracks: [
{
name: 'Ride',
geometry: [
[48, 11],
[48.01, 11],
],
point_count: 2,
distance_km: 1.1,
elevation_gain_m: null,
elevation_loss_m: null,
duplicate: false,
},
],
},
],
confirm: vi.fn().mockResolvedValue(undefined),
cancel: vi.fn(),
};
}
describe('a ride taken from a GPX file (HOR-02)', () => {
it('FE-HORSE-IMPORT-002: says the line stays as recorded, gets no riding time and is not checked for access', () => {
render(<RecordedRideFacts />);
const facts = screen.getByTestId('recorded-ride-facts');
expect(facts).toHaveTextContent('As recorded');
expect(facts).toHaveTextContent('No riding time');
expect(facts).toHaveTextContent('Access not checked');
// Each reason is the badge's description, reachable without a pointer.
const access = screen.getByText('Access not checked').closest('[tabindex="0"]') as HTMLElement;
expect(access).toHaveAccessibleDescription(
'The import does not check whether the ways may be ridden. The signs on site count.'
);
expect(facts).not.toHaveTextContent('No heights in the file');
});
it('FE-HORSE-IMPORT-003: names heights the file did not carry instead of showing zero', () => {
render(<RecordedRideFacts heightsMissing />);
expect(screen.getByTestId('recorded-ride-facts')).toHaveTextContent('No heights in the file');
});
it('FE-HORSE-IMPORT-004: the preview says so once a ride is chosen, and imports nothing until confirmed', () => {
const gpxImport = previewing();
render(<TourGpxPreviewDialog gpxImport={gpxImport} />);
expect(screen.queryByTestId('gpx-preview-horse')).toBeNull();
fireEvent.click(screen.getByTestId('tour-type-select'));
fireEvent.click(screen.getByTestId('tour-type-option-horse'));
expect(within(screen.getByTestId('gpx-preview-horse')).getByText('Access not checked')).toBeInTheDocument();
expect(gpxImport.confirm).not.toHaveBeenCalled();
fireEvent.click(screen.getByTestId('gpx-preview-import'));
expect(gpxImport.confirm).toHaveBeenCalledWith({ tour_type: 'horse' });
});
it('FE-HORSE-IMPORT-005: a recorded ride in view carries the card, with the heights its file lacks named', () => {
render(
<TourPlannerRail
planner={viewing(ride, [
[48, 11],
[48.01, 11],
])}
/>
);
const card = screen.getByRole('region', { name: 'Recorded ride' });
expect(card).toHaveTextContent('No riding time');
expect(card).toHaveTextContent('No heights in the file');
// Nothing of a time is shown: an import of a ride gets none worked out.
expect(screen.queryByText('Riding time')).toBeNull();
});
it('FE-HORSE-IMPORT-006: heights the file has need no word, and a recorded hike gets no ride card', () => {
const line = [
[48, 11, 500],
[48.01, 11, 540],
];
const { unmount } = render(<TourPlannerRail planner={viewing(ride, line)} />);
expect(screen.getByRole('region', { name: 'Recorded ride' })).not.toHaveTextContent('No heights in the file');
unmount();
render(<TourPlannerRail planner={viewing({ ...ride, tour_type: 'hike' }, line)} />);
expect(screen.queryByRole('region', { name: 'Recorded ride' })).toBeNull();
});
});
@@ -0,0 +1,73 @@
import { useId, type ReactNode } from 'react';
import { useTranslation } from '../../../i18n';
import { SoftPill } from '../../Planner/planParts';
import { Tooltip } from '../../shared/Tooltip';
import { RailCard } from '../tourParts';
import { ACCESS_TONE_DOT } from './horseAccess';
import { Horseshoe } from './HorseshoeIcon';
/**
* HOR-02: what a ride taken from a GPX file is, said as badges with the why in
* their tooltips. Its line stays as the file recorded it, no riding time is
* worked out for it, and nothing checked whether its ways may be ridden: the
* import makes no ride allowed or passable. Heights the file did not carry are
* named as missing rather than shown as zero.
*/
/** One badge of the row; with a reason, it takes the focus and says the reason in a tooltip. */
function FactPill({ icon, label, info }: { icon?: ReactNode; label: string; info?: string }) {
const id = useId();
const pill = <SoftPill icon={icon}>{label}</SoftPill>;
if (!info) return pill;
return (
<Tooltip label={info}>
<span
tabIndex={0}
aria-describedby={id}
className="inline-flex rounded-full focus-visible:outline focus-visible:outline-2 focus-visible:outline-accent"
>
{pill}
<span id={id} className="sr-only">
{info}
</span>
</span>
</Tooltip>
);
}
export function RecordedRideFacts({ heightsMissing = false }: { heightsMissing?: boolean }) {
const { t } = useTranslation();
return (
<div data-testid="recorded-ride-facts" className="flex min-w-0 flex-wrap items-center gap-1.5">
<FactPill
icon={
<Horseshoe
size={11}
strokeWidth={2.4}
aria-hidden="true"
className="flex-none text-[color:var(--horse-route)]"
/>
}
label={t('tours.horse.recorded.asRecorded')}
info={t('tours.horse.recorded.asRecordedInfo')}
/>
<FactPill label={t('tours.horse.recorded.noTime')} info={t('tours.horse.recorded.noTimeInfo')} />
<FactPill
icon={<span aria-hidden="true" className={`h-2 w-2 flex-none rounded-full ${ACCESS_TONE_DOT.unknown}`} />}
label={t('tours.horse.recorded.accessUnchecked')}
info={t('tours.horse.recorded.accessUncheckedInfo')}
/>
{heightsMissing && <FactPill label={t('tours.import.previewNoHeights')} />}
</div>
);
}
/** The ride's card in the rail of a recorded ride in view, above its measures. */
export function RecordedRideCard({ heightsMissing }: { heightsMissing: boolean }) {
const { t } = useTranslation();
return (
<RailCard icon={<Horseshoe size={14} strokeWidth={2} />} title={t('tours.horse.recorded.title')}>
<RecordedRideFacts heightsMissing={heightsMissing} />
</RailCard>
);
}
@@ -52,6 +52,7 @@ import { TourPoiSearchCard } from '../poi/TourPoiSearchCard'
import { BikeWaysCard } from '../bike/BikeWaysCard'
import { RunWaysCard } from '../run/RunWaysCard'
import { HorseRoutesCard } from '../horse/HorseRoutesCard'
import { RecordedRideCard } from '../horse/RecordedRideFacts'
import { walkTimeFields } from '../walk/walkTimeFields'
const STATUS_KEY: Partial<Record<TourPlannerStatus, string>> = {
@@ -329,6 +330,8 @@ function GpxViewRail({
{tour.tour_type === 'kayak' && (
<PaddleWindSection placeId={tour.place_id} start={trackStart(analysis?.routeCoordinates)} />
)}
{/* HOR-02: a recorded ride says what the import did not do for it. */}
{tour.tour_type === 'horse' && <RecordedRideCard heightsMissing={!!analysis && analysis.gain == null} />}
<RailCard icon={<Mountain size={14} strokeWidth={2} />} title={t('tours.planner.inspector.title')}>
{analysis ? (
<div className="flex flex-col gap-3">
@@ -0,0 +1,269 @@
/**
* Tours e2e for rides taken from a GPX file (HOR-02): the canonical Tours GPX
* import, previewed and then confirmed as a ride, stored with its line as the
* file recorded it, its origin and only the measures the file carries, opened
* again and put on a day of the trip; a failed import leaves an existing ride
* as it was. Real JwtAuthGuard, AddonGuard and TripAccessGuard against a
* migrated and seeded temp SQLite db, as in tours-horse.e2e.test.ts; the
* permission check is a container spy and the broadcast goes to a
* FakeRealtimeService.
*
* Fixtures: user 1 owns trip 5 with day 3, user 2 is a member of it whom the
* permission spy denies 'place_edit'.
*/
import { DayAssignments } from '../../src/db/entities/DayAssignments.entity';
import { Days } from '../../src/db/entities/Days.entity';
import { Places } from '../../src/db/entities/Places.entity';
import { Tours } from '../../src/db/entities/Tours.entity';
import { Trips } from '../../src/db/entities/Trips.entity';
import { AssignmentsModule } from '../../src/nest/assignments/assignments.module';
import { TrekExceptionFilter } from '../../src/nest/common/trek-exception.filter';
import { ZodValidationPipe } from '../../src/nest/common/zod-validation.pipe';
import { JourneyDomainService } from '../../src/nest/journey/journey-domain.service';
import { MapsService } from '../../src/nest/maps/maps.service';
import { OverpassElementsClient } from '../../src/nest/overpass/overpass-elements.service';
import { PermissionsService } from '../../src/nest/permissions/permissions.service';
import { RealtimeModule } from '../../src/nest/realtime/realtime.module';
import { RealtimeService } from '../../src/nest/realtime/realtime.service';
import { ToursModule } from '../../src/nest/tours/tours.module';
import { createSnapshotTestDb } from '../helpers/db-mock';
import { countRows, deleteRows, findRow, insertRows } from '../helpers/factories/rows';
import { setAddonEnabled } from '../helpers/factories/settings';
import { addTripMember } from '../helpers/factories/trips';
import { makeUser } from '../helpers/factories/users';
import { FakeRealtimeService } from '../helpers/fake-realtime';
import { createTestMikroOrmModule, createTestOrm, type TestOrm } from '../helpers/test-orm';
import { TestUnitOfWorkModule } from '../helpers/test-uow';
import { sessionCookie } from './harness';
import type { NestExpressApplication } from '@nestjs/platform-express';
import { Test } from '@nestjs/testing';
import cookieParser from 'cookie-parser';
import type { Server } from 'http';
import request from 'supertest';
import { afterAll, beforeAll, beforeEach, describe, expect, it, vi, type MockInstance } from 'vitest';
vi.mock('../../src/nest/audit/audit-log.logger', () => ({
LOG_LEVEL: 'error',
logInfo: vi.fn(),
logDebug: vi.fn(),
logError: vi.fn(),
logWarn: vi.fn(),
}));
const { journeyHooks } = vi.hoisted(() => ({
journeyHooks: {
reconcileTripSkeletons: vi.fn().mockResolvedValue(undefined),
onPlaceCreated: vi.fn().mockResolvedValue(undefined),
onPlaceUpdated: vi.fn().mockResolvedValue(undefined),
onPlaceDeleted: vi.fn().mockResolvedValue(undefined),
},
}));
const db = createSnapshotTestDb();
const realtime = new FakeRealtimeService();
let orm: TestOrm;
let checkPermission: MockInstance;
const OWNER = 1;
const VIEWER = 2;
/** A ride as a watch records it: a few points, with heights. */
const RECORDED = [
[47, 11, 600],
[47.01, 11.01, 640],
[47.02, 11.03, 610],
];
const RIDE_GPX = `<?xml version="1.0"?>
<gpx version="1.1" creator="e2e"><trk><name>Ausritt an der Isar</name><trkseg>
${RECORDED.map(([lat, lon, ele]) => `<trkpt lat="${lat}" lon="${lon}"><ele>${ele}</ele></trkpt>`).join('\n')}
</trkseg></trk></gpx>`;
/** One without heights: the import has nothing to say about them. */
const FLAT_GPX = `<?xml version="1.0"?>
<gpx version="1.1" creator="e2e"><trk><name>Feldweg</name><trkseg>
<trkpt lat="48" lon="12"/><trkpt lat="48.01" lon="12.01"/>
</trkseg></trk></gpx>`;
describe('Rides imported from GPX e2e (real guards + temp SQLite)', () => {
let server: Server;
let app: Awaited<ReturnType<typeof build>>;
async function build() {
const moduleRef = await Test.createTestingModule({
imports: [
await TestUnitOfWorkModule.forRoot(db),
await createTestMikroOrmModule(db),
RealtimeModule,
ToursModule,
AssignmentsModule,
],
})
.overrideProvider(RealtimeService)
.useValue(realtime)
.overrideProvider(JourneyDomainService)
.useValue(journeyHooks)
.overrideProvider(MapsService)
.useValue({ pois: vi.fn() })
.overrideProvider(OverpassElementsClient)
// An import asks OpenStreetMap nothing: any call here would be a bug.
.useValue({ query: vi.fn().mockRejectedValue(new Error('no network in a test')) })
.compile();
const nest = moduleRef.createNestApplication<NestExpressApplication>();
nest.use(cookieParser());
nest.useGlobalPipes(new ZodValidationPipe());
nest.useGlobalFilters(new TrekExceptionFilter());
await nest.init();
return nest;
}
beforeAll(async () => {
orm = await createTestOrm(db);
// Pinned ids: the session cookies sign for exactly these users.
await makeUser(orm, { id: 1, username: 'owner', email: 'owner@example.test', role: 'user', password_version: 0 });
await makeUser(orm, { id: 2, username: 'viewer', email: 'viewer@example.test', role: 'user', password_version: 0 });
await insertRows(orm, Trips, [{ id: 5, user: 1, title: 'Isar' }]);
await addTripMember(orm, 5, 2);
await insertRows(orm, Days, [{ id: 3, trip: 5, day_number: 1, date: '2026-07-10' }]);
app = await build();
checkPermission = vi.spyOn(app.get(PermissionsService), 'checkPermission');
server = app.getHttpServer();
});
beforeEach(async () => {
checkPermission.mockImplementation(
(action: string, _role: string, _owner: number, userId: number) =>
!((action === 'place_edit' || action === 'day_edit') && userId === VIEWER),
);
await setAddonEnabled(orm, 'tours', true);
realtime.broadcastMock.mockClear();
await deleteRows(orm, DayAssignments);
await deleteRows(orm, Places);
});
afterAll(async () => {
await app.close();
await orm.close();
});
const upload = (
path: 'import/gpx' | 'import/gpx/preview',
xml: string,
fields: Record<string, string> = {},
userId = OWNER,
) => {
let req = request(server).post(`/api/trips/5/tours/${path}`).set('Cookie', sessionCookie(userId));
for (const [key, value] of Object.entries(fields)) req = req.field(key, value);
return req.attach('file', Buffer.from(xml), { filename: 'ride.gpx', contentType: 'application/gpx+xml' });
};
const importRide = async (xml = RIDE_GPX) => {
const res = await upload('import/gpx', xml, { tour_type: 'horse' });
expect(res.status).toBe(201);
return res.body.tours[0].place_id as number;
};
it('HORSE-IMPORT-E2E-001: the preview measures the file and stores nothing', async () => {
const res = await upload('import/gpx/preview', RIDE_GPX);
expect(res.status).toBe(200);
expect(res.body.tracks).toHaveLength(1);
expect(res.body.tracks[0]).toMatchObject({ name: 'Ausritt an der Isar', point_count: 3, duplicate: false });
expect(res.body.tracks[0].elevation_gain_m).toBeCloseTo(40);
expect(res.body.tracks[0].elevation_loss_m).toBeCloseTo(30);
expect(await countRows(orm, Tours)).toBe(0);
expect(await countRows(orm, Places)).toBe(0);
});
it('HORSE-IMPORT-E2E-002: confirmed as a ride, the line stays as recorded with its origin, heights and no riding time', async () => {
const placeId = await importRide();
const place = await findRow(orm, Places, { id: placeId });
expect(JSON.parse(String(place?.route_geometry))).toEqual(RECORDED);
expect(await findRow(orm, Tours, { place: placeId })).toMatchObject({
tour_type: 'horse',
origin: 'gpx',
origin_file_name: 'ride.gpx',
duration: null,
planned_duration_minutes: null,
max_hiking_difficulty: null,
bike_profile: null,
boat_type: null,
});
expect(realtime.broadcastMock.mock.calls.map((call) => call[1])).toEqual(
expect.arrayContaining(['tours:changed', 'place:created']),
);
});
it('HORSE-IMPORT-E2E-003: opens again as a ride, naming the file it came from, with nothing routed', async () => {
const placeId = await importRide();
const res = await request(server).get(`/api/trips/5/tours/${placeId}`).set('Cookie', sessionCookie(VIEWER));
expect(res.status).toBe(200);
expect(res.body.tour).toMatchObject({ tour_type: 'horse', origin: 'gpx', duration: null, caution: false });
expect(res.body.tour.elevation_gain).toBeCloseTo(40);
expect(res.body.provenance).toMatchObject({
origin: 'gpx',
source: { kind: 'gpx', fileName: 'ride.gpx' },
plannedOver: false,
});
// The two ends of the track stand for its points; no leg was planned or taken from a route.
expect(res.body.waypoints).toEqual([
{ lat: 47, lng: 11, role: 'start', sequence: 0 },
{ lat: 47.02, lng: 11.03, role: 'end', sequence: 1 },
]);
});
it('HORSE-IMPORT-E2E-004: a file without heights keeps them unknown and says so, never zero', async () => {
const res = await upload('import/gpx', FLAT_GPX, { tour_type: 'horse' });
expect(res.status).toBe(201);
expect(res.body.caution).toBe(true);
expect(res.body.tours[0]).toMatchObject({
tour_type: 'horse',
elevation_gain: null,
elevation_loss: null,
match_confidence: 0.3,
});
expect(res.body.tours[0].distance).toBeGreaterThan(0);
});
it('HORSE-IMPORT-E2E-005: goes on a day of the trip like any tour', async () => {
const placeId = await importRide();
const assigned = await request(server)
.post('/api/trips/5/days/3/assignments')
.set('Cookie', sessionCookie(OWNER))
.send({ place_id: placeId });
expect(assigned.status).toBe(201);
const list = await request(server).get('/api/trips/5/tours').set('Cookie', sessionCookie(OWNER));
expect(list.status).toBe(200);
expect(list.body.tours.find((tour: { place_id: number }) => tour.place_id === placeId)).toMatchObject({
tour_type: 'horse',
planned: true,
});
});
it('HORSE-IMPORT-E2E-006: a failed import leaves the rides there are as they were', async () => {
const placeId = await importRide();
const before = await findRow(orm, Tours, { place: placeId });
const placeBefore = await findRow(orm, Places, { id: placeId });
const refusals = [
// No track in the file.
await upload('import/gpx', '<gpx><wpt lat="47" lon="11"/></gpx>', { tour_type: 'horse' }),
// A field of another type on a ride.
await upload('import/gpx', FLAT_GPX, { tour_type: 'horse', bike_profile: 'road' }),
// A member who may only read.
await upload('import/gpx', FLAT_GPX, { tour_type: 'horse' }, VIEWER),
];
expect(refusals.map((res) => res.status)).toEqual([400, 400, 403]);
expect(await countRows(orm, Tours)).toBe(1);
expect(await countRows(orm, Places)).toBe(1);
expect(await findRow(orm, Tours, { place: placeId })).toEqual(before);
expect(await findRow(orm, Places, { id: placeId })).toEqual(placeBefore);
});
it('HORSE-IMPORT-E2E-007: the same recorded ride again is skipped, not stored twice', async () => {
await importRide();
const again = await upload('import/gpx', RIDE_GPX, { tour_type: 'horse' });
expect(again.status).toBe(201);
expect(again.body).toEqual({ tours: [], caution: false, skipped: 1 });
expect(await countRows(orm, Tours)).toBe(1);
});
});
+8
View File
@@ -1095,5 +1095,13 @@ const tours: TranslationStrings = {
'tours.horse.leg.info': 'مقطع من مسار فروسية معلّم من OpenStreetMap. وصول الخيل: {access}',
'tours.horse.leg.manual': 'يدوي، غير مُتحقق منه',
'tours.horse.leg.manualInfo': 'مرسوم باليد، مستقيم. لم يتحقق منه أي مسار فروسية ولا أي مخطط مسارات.',
'tours.horse.recorded.title': 'جولة مسجّلة',
'tours.horse.recorded.asRecorded': 'كما سُجّلت',
'tours.horse.recorded.asRecordedInfo': 'يبقى المسار كما سجّله الملف. لا يُعاد تخطيط أي جزء منه.',
'tours.horse.recorded.noTime': 'بلا وقت ركوب',
'tours.horse.recorded.noTimeInfo': 'لا يحسب TREK وقت ركوب للجولة: الإيقاع يحدده الحصان والفارس.',
'tours.horse.recorded.accessUnchecked': 'الوصول غير مُتحقق منه',
'tours.horse.recorded.accessUncheckedInfo':
'لا يتحقق الاستيراد مما إذا كان يُسمح بركوب الطرق. العبرة باللافتات في المكان.',
};
export default tours;
+8
View File
@@ -1061,5 +1061,13 @@ const tours: TranslationStrings = {
'tours.horse.leg.info': 'OpenStreetMap-dən nişanlanmış atçılıq marşrutunun hissəsi. At üçün giriş: {access}',
'tours.horse.leg.manual': 'Əl ilə, yoxlanılmayıb',
'tours.horse.leg.manualInfo': 'Əl ilə, düz çəkilib. Nə atçılıq marşrutu, nə də marşrut planlayıcısı yoxlayıb.',
'tours.horse.recorded.title': 'Qeydə alınmış gəzinti',
'tours.horse.recorded.asRecorded': 'Qeydə alındığı kimi',
'tours.horse.recorded.asRecordedInfo': 'Xətt faylın qeydə aldığı kimi qalır. Heç nə yenidən planlanmır.',
'tours.horse.recorded.noTime': 'Sürmə vaxtı yoxdur',
'tours.horse.recorded.noTimeInfo': 'TREK at gəzintisi üçün sürmə vaxtı hesablamır: tempi at və atlı müəyyən edir.',
'tours.horse.recorded.accessUnchecked': 'Giriş yoxlanılmayıb',
'tours.horse.recorded.accessUncheckedInfo':
'İdxal yollarda at sürməyə icazə olub-olmadığını yoxlamır. Yerdəki lövhələr əsasdır.',
};
export default tours;
+9
View File
@@ -1071,5 +1071,14 @@ const tours: TranslationStrings = {
'tours.horse.leg.info': 'Trecho de uma rota equestre sinalizada do OpenStreetMap. Acesso a cavalo: {access}',
'tours.horse.leg.manual': 'Manual, não verificado',
'tours.horse.leg.manualInfo': 'Traçado à mão, em linha reta. Nenhuma rota equestre e nenhum roteador o verificou.',
'tours.horse.recorded.title': 'Passeio gravado',
'tours.horse.recorded.asRecorded': 'Como gravado',
'tours.horse.recorded.asRecordedInfo': 'A linha fica como o arquivo a gravou. Nada é roteado de novo.',
'tours.horse.recorded.noTime': 'Sem tempo a cavalo',
'tours.horse.recorded.noTimeInfo':
'O TREK não calcula tempo a cavalo para um passeio: o ritmo é do cavalo e do cavaleiro.',
'tours.horse.recorded.accessUnchecked': 'Acesso não verificado',
'tours.horse.recorded.accessUncheckedInfo':
'A importação não verifica se as vias podem ser percorridas a cavalo. Valem as placas no local.',
};
export default tours;
+10
View File
@@ -1074,5 +1074,15 @@ const tours: TranslationStrings = {
'tours.horse.leg.manual': 'Manual, sense comprovar',
'tours.horse.leg.manualInfo':
"Traçat a mà, en línia recta. Ni una ruta eqüestre ni un planificador no l'ha comprovat.",
'tours.horse.recorded.title': 'Ruta enregistrada',
'tours.horse.recorded.asRecorded': 'Tal com es va enregistrar',
'tours.horse.recorded.asRecordedInfo':
'La línia queda tal com la va enregistrar el fitxer. No es torna a calcular res.',
'tours.horse.recorded.noTime': 'Sense temps a cavall',
'tours.horse.recorded.noTimeInfo':
'TREK no calcula temps a cavall per a una ruta: el ritme és del cavall i del genet.',
'tours.horse.recorded.accessUnchecked': 'Accés sense comprovar',
'tours.horse.recorded.accessUncheckedInfo':
'La importació no comprova si es pot anar a cavall pels camins. Valen els senyals sobre el terreny.',
};
export default tours;
+8
View File
@@ -1060,5 +1060,13 @@ const tours: TranslationStrings = {
'tours.horse.leg.info': 'Úsek značené jezdecké trasy z OpenStreetMap. Přístup pro koně: {access}',
'tours.horse.leg.manual': 'Ručně, neověřeno',
'tours.horse.leg.manualInfo': 'Nakresleno ručně, rovně. Neověřila ho žádná jezdecká trasa ani plánovač.',
'tours.horse.recorded.title': 'Zaznamenaná vyjížďka',
'tours.horse.recorded.asRecorded': 'Jak bylo zaznamenáno',
'tours.horse.recorded.asRecordedInfo': 'Trasa zůstává tak, jak ji soubor zaznamenal. Nic se znovu neplánuje.',
'tours.horse.recorded.noTime': 'Bez doby jízdy',
'tours.horse.recorded.noTimeInfo': 'TREK pro vyjížďku nepočítá dobu jízdy: tempo určuje kůň a jezdec.',
'tours.horse.recorded.accessUnchecked': 'Přístup neověřen',
'tours.horse.recorded.accessUncheckedInfo':
'Import neověřuje, zda se po cestách smí jezdit na koni. Platí značení na místě.',
};
export default tours;
+10
View File
@@ -1077,6 +1077,16 @@ const tours: TranslationStrings = {
'tours.horse.leg.info': 'Abschnitt einer ausgeschilderten Reitroute aus OpenStreetMap. Reitzugang: {access}',
'tours.horse.leg.manual': 'Manuell, ungeprüft',
'tours.horse.leg.manualInfo': 'Von Hand gezogen, gerade. Weder eine Reitroute noch ein Router hat ihn geprüft.',
'tours.horse.recorded.title': 'Aufgezeichneter Ausritt',
'tours.horse.recorded.asRecorded': 'Wie aufgezeichnet',
'tours.horse.recorded.asRecordedInfo':
'Die Linie bleibt, wie die Datei sie aufgezeichnet hat. Nichts wird neu geplant.',
'tours.horse.recorded.noTime': 'Keine Reitzeit',
'tours.horse.recorded.noTimeInfo':
'TREK berechnet für einen Ausritt keine Reitzeit: Das Tempo bestimmen Pferd und Reiter.',
'tours.horse.recorded.accessUnchecked': 'Zugang nicht geprüft',
'tours.horse.recorded.accessUncheckedInfo':
'Der Import prüft nicht, ob die Wege geritten werden dürfen. Es gilt die Beschilderung vor Ort.',
};
export default tours;
+9
View File
@@ -1048,5 +1048,14 @@ const tours: TranslationStrings = {
'tours.horse.leg.info': 'A section of a signposted horse route from OpenStreetMap. Riding access: {access}',
'tours.horse.leg.manual': 'Manual, unchecked',
'tours.horse.leg.manualInfo': 'Drawn by hand, straight. No horse route and no router checked it.',
'tours.horse.recorded.title': 'Recorded ride',
'tours.horse.recorded.asRecorded': 'As recorded',
'tours.horse.recorded.asRecordedInfo': 'The line stays as the file recorded it. Nothing is routed again.',
'tours.horse.recorded.noTime': 'No riding time',
'tours.horse.recorded.noTimeInfo':
"TREK works out no riding time for a ride: the pace is the horse's and the rider's.",
'tours.horse.recorded.accessUnchecked': 'Access not checked',
'tours.horse.recorded.accessUncheckedInfo':
'The import does not check whether the ways may be ridden. The signs on site count.',
};
export default tours;
+9
View File
@@ -1069,5 +1069,14 @@ const tours: TranslationStrings = {
'tours.horse.leg.info': 'Tramo de una ruta ecuestre señalizada de OpenStreetMap. Acceso a caballo: {access}',
'tours.horse.leg.manual': 'Manual, sin comprobar',
'tours.horse.leg.manualInfo': 'Trazado a mano, en línea recta. Ni una ruta ecuestre ni un planificador lo comprobó.',
'tours.horse.recorded.title': 'Ruta grabada',
'tours.horse.recorded.asRecorded': 'Tal como se grabó',
'tours.horse.recorded.asRecordedInfo': 'La línea queda tal como la grabó el archivo. No se vuelve a calcular nada.',
'tours.horse.recorded.noTime': 'Sin tiempo a caballo',
'tours.horse.recorded.noTimeInfo':
'TREK no calcula tiempo a caballo para una ruta: el ritmo es del caballo y del jinete.',
'tours.horse.recorded.accessUnchecked': 'Acceso sin comprobar',
'tours.horse.recorded.accessUncheckedInfo':
'La importación no comprueba si se puede ir a caballo por las vías. Valen las señales sobre el terreno.',
};
export default tours;
+8
View File
@@ -1056,5 +1056,13 @@ const tours: TranslationStrings = {
'tours.horse.leg.manual': 'Käsitsi, kontrollimata',
'tours.horse.leg.manualInfo':
'Käsitsi joonistatud, sirge. Seda ei kontrollinud ükski ratsarada ega marsruudiplaneerija.',
'tours.horse.recorded.title': 'Salvestatud ratsamatk',
'tours.horse.recorded.asRecorded': 'Nagu salvestatud',
'tours.horse.recorded.asRecordedInfo': 'Joon jääb selliseks, nagu fail selle salvestas. Midagi ei planeerita uuesti.',
'tours.horse.recorded.noTime': 'Ratsutamisaega pole',
'tours.horse.recorded.noTimeInfo': 'TREK ei arvuta ratsamatkale ratsutamisaega: tempo määravad hobune ja ratsanik.',
'tours.horse.recorded.accessUnchecked': 'Ligipääs kontrollimata',
'tours.horse.recorded.accessUncheckedInfo':
'Import ei kontrolli, kas teedel tohib ratsutada. Kohapeal kehtivad sildid.',
};
export default tours;
+9
View File
@@ -1090,5 +1090,14 @@ const tours: TranslationStrings = {
'tours.horse.leg.manual': 'Manuel, non vérifié',
'tours.horse.leg.manualInfo':
"Tracé à la main, en ligne droite. Ni un itinéraire équestre ni un calculateur d'itinéraire ne l'a vérifié.",
'tours.horse.recorded.title': 'Sortie enregistrée',
'tours.horse.recorded.asRecorded': 'Tel qu’enregistré',
'tours.horse.recorded.asRecordedInfo': "Le tracé reste tel que le fichier l'a enregistré. Rien n'est recalculé.",
'tours.horse.recorded.noTime': 'Pas de temps à cheval',
'tours.horse.recorded.noTimeInfo':
'TREK ne calcule aucun temps à cheval pour une sortie : le rythme est celui du cheval et du cavalier.',
'tours.horse.recorded.accessUnchecked': 'Accès non vérifié',
'tours.horse.recorded.accessUncheckedInfo':
"L'import ne vérifie pas si les voies peuvent être empruntées à cheval. Les panneaux sur place font foi.",
};
export default tours;
+9
View File
@@ -1089,5 +1089,14 @@ const tours: TranslationStrings = {
'tours.horse.leg.info': 'Τμήμα σηματοδοτημένης ιππικής διαδρομής από το OpenStreetMap. Πρόσβαση με άλογο: {access}',
'tours.horse.leg.manual': 'Χειροκίνητο, χωρίς έλεγχο',
'tours.horse.leg.manualInfo': 'Σχεδιασμένο με το χέρι, ευθύ. Ούτε ιππική διαδρομή ούτε δρομολογητής το έλεγξε.',
'tours.horse.recorded.title': 'Καταγεγραμμένη βόλτα',
'tours.horse.recorded.asRecorded': 'Όπως καταγράφηκε',
'tours.horse.recorded.asRecordedInfo': 'Η γραμμή μένει όπως την κατέγραψε το αρχείο. Τίποτα δεν δρομολογείται ξανά.',
'tours.horse.recorded.noTime': 'Χωρίς χρόνο ιππασίας',
'tours.horse.recorded.noTimeInfo':
'Το TREK δεν υπολογίζει χρόνο ιππασίας για μια βόλτα: τον ρυθμό τον ορίζουν το άλογο και ο αναβάτης.',
'tours.horse.recorded.accessUnchecked': 'Πρόσβαση χωρίς έλεγχο',
'tours.horse.recorded.accessUncheckedInfo':
'Η εισαγωγή δεν ελέγχει αν επιτρέπεται η ιππασία στους δρόμους. Ισχύουν οι πινακίδες επί τόπου.',
};
export default tours;
+9
View File
@@ -1071,5 +1071,14 @@ const tours: TranslationStrings = {
'tours.horse.leg.info': 'Jelzett lovas útvonal szakasza az OpenStreetMapből. Lovas hozzáférés: {access}',
'tours.horse.leg.manual': 'Kézi, ellenőrizetlen',
'tours.horse.leg.manualInfo': 'Kézzel, egyenesen húzva. Sem lovas útvonal, sem útvonaltervező nem ellenőrizte.',
'tours.horse.recorded.title': 'Rögzített lovastúra',
'tours.horse.recorded.asRecorded': 'Ahogy rögzítették',
'tours.horse.recorded.asRecordedInfo': 'A vonal úgy marad, ahogy a fájl rögzítette. Semmit nem tervez újra.',
'tours.horse.recorded.noTime': 'Nincs lovaglási idő',
'tours.horse.recorded.noTimeInfo':
'A TREK nem számol lovaglási időt egy lovastúrához: a tempót a ló és a lovas adja.',
'tours.horse.recorded.accessUnchecked': 'Hozzáférés ellenőrizetlen',
'tours.horse.recorded.accessUncheckedInfo':
'Az importálás nem ellenőrzi, hogy szabad-e lovagolni az utakon. A helyszíni táblák a mérvadók.',
};
export default tours;
+9
View File
@@ -1053,5 +1053,14 @@ const tours: TranslationStrings = {
'tours.horse.leg.manual': 'Manual, belum diperiksa',
'tours.horse.leg.manualInfo':
'Digambar tangan, lurus. Tidak ada rute berkuda maupun perencana rute yang memeriksanya.',
'tours.horse.recorded.title': 'Tur berkuda terekam',
'tours.horse.recorded.asRecorded': 'Sesuai rekaman',
'tours.horse.recorded.asRecordedInfo': 'Garis tetap seperti yang direkam berkas. Tidak ada yang dirutekan ulang.',
'tours.horse.recorded.noTime': 'Tanpa waktu berkuda',
'tours.horse.recorded.noTimeInfo':
'TREK tidak menghitung waktu berkuda untuk tur berkuda: lajunya milik kuda dan penunggangnya.',
'tours.horse.recorded.accessUnchecked': 'Akses belum diperiksa',
'tours.horse.recorded.accessUncheckedInfo':
'Impor tidak memeriksa apakah jalan boleh dilalui dengan berkuda. Rambu di lokasi yang berlaku.',
};
export default tours;
+9
View File
@@ -1079,5 +1079,14 @@ const tours: TranslationStrings = {
'tours.horse.leg.manual': 'Manuale, non verificato',
'tours.horse.leg.manualInfo':
'Tracciato a mano, dritto. Né un itinerario equestre né un pianificatore lo ha verificato.',
'tours.horse.recorded.title': 'Escursione registrata',
'tours.horse.recorded.asRecorded': 'Come registrato',
'tours.horse.recorded.asRecordedInfo': 'Il tracciato resta come lo ha registrato il file. Nulla viene ricalcolato.',
'tours.horse.recorded.noTime': 'Nessun tempo a cavallo',
'tours.horse.recorded.noTimeInfo':
'TREK non calcola un tempo a cavallo per un’escursione: il ritmo è quello del cavallo e del cavaliere.',
'tours.horse.recorded.accessUnchecked': 'Accesso non verificato',
'tours.horse.recorded.accessUncheckedInfo':
'L’importazione non verifica se le strade si possono percorrere a cavallo. Valgono i cartelli sul posto.',
};
export default tours;
+8
View File
@@ -1024,5 +1024,13 @@ const tours: TranslationStrings = {
'tours.horse.leg.info': 'OpenStreetMap の標識付き乗馬ルートの区間です。乗馬でのアクセス: {access}',
'tours.horse.leg.manual': '手動、未確認',
'tours.horse.leg.manualInfo': '手で直線に引いた区間です。乗馬ルートもルーターも確認していません。',
'tours.horse.recorded.title': '記録された乗馬',
'tours.horse.recorded.asRecorded': '記録のまま',
'tours.horse.recorded.asRecordedInfo': '線はファイルに記録されたままです。ルートを計算し直すことはありません。',
'tours.horse.recorded.noTime': '乗馬時間なし',
'tours.horse.recorded.noTimeInfo': 'TREK は乗馬ツアーの乗馬時間を計算しません。ペースは馬と乗り手次第です。',
'tours.horse.recorded.accessUnchecked': '通行可否は未確認',
'tours.horse.recorded.accessUncheckedInfo':
'インポートでは、道を馬で通れるかどうかを確認しません。現地の標識が優先されます。',
};
export default tours;
+9
View File
@@ -1021,5 +1021,14 @@ const tours: TranslationStrings = {
'tours.horse.leg.info': 'OpenStreetMap의 표지가 있는 승마 루트의 구간입니다. 말 통행: {access}',
'tours.horse.leg.manual': '수동, 확인되지 않음',
'tours.horse.leg.manualInfo': '손으로 그은 직선 구간입니다. 승마 루트도 경로 계산기도 확인하지 않았습니다.',
'tours.horse.recorded.title': '기록된 승마',
'tours.horse.recorded.asRecorded': '기록된 그대로',
'tours.horse.recorded.asRecordedInfo': '선은 파일에 기록된 그대로 유지됩니다. 경로를 다시 계산하지 않습니다.',
'tours.horse.recorded.noTime': '승마 시간 없음',
'tours.horse.recorded.noTimeInfo':
'TREK은 승마 투어의 승마 시간을 계산하지 않습니다. 속도는 말과 기수에게 달려 있습니다.',
'tours.horse.recorded.accessUnchecked': '통행 여부 미확인',
'tours.horse.recorded.accessUncheckedInfo':
'가져오기는 길을 말로 지나가도 되는지 확인하지 않습니다. 현장 표지판을 따르세요.',
};
export default tours;
+9
View File
@@ -1074,5 +1074,14 @@ const tours: TranslationStrings = {
'tours.horse.leg.manual': 'Handmatig, niet gecontroleerd',
'tours.horse.leg.manualInfo':
'Met de hand getrokken, recht. Geen ruiterroute en geen routeplanner heeft het gecontroleerd.',
'tours.horse.recorded.title': 'Opgenomen rit',
'tours.horse.recorded.asRecorded': 'Zoals opgenomen',
'tours.horse.recorded.asRecordedInfo':
'De lijn blijft zoals het bestand haar heeft opgenomen. Er wordt niets opnieuw gepland.',
'tours.horse.recorded.noTime': 'Geen rijtijd',
'tours.horse.recorded.noTimeInfo': 'TREK berekent geen rijtijd voor een rit: het tempo is aan paard en ruiter.',
'tours.horse.recorded.accessUnchecked': 'Toegang niet gecontroleerd',
'tours.horse.recorded.accessUncheckedInfo':
'De import controleert niet of de wegen bereden mogen worden. Ter plekke gelden de borden.',
};
export default tours;
+8
View File
@@ -1100,5 +1100,13 @@ const tours: TranslationStrings = {
'tours.horse.leg.info': 'Odcinek oznakowanego szlaku konnego z OpenStreetMap. Dostęp dla koni: {access}',
'tours.horse.leg.manual': 'Ręczny, niesprawdzony',
'tours.horse.leg.manualInfo': 'Narysowany ręcznie, prosto. Nie sprawdził go żaden szlak konny ani planer tras.',
'tours.horse.recorded.title': 'Nagrana przejażdżka',
'tours.horse.recorded.asRecorded': 'Tak jak nagrano',
'tours.horse.recorded.asRecordedInfo': 'Linia pozostaje taka, jak zapisał ją plik. Nic nie jest wyznaczane ponownie.',
'tours.horse.recorded.noTime': 'Bez czasu jazdy',
'tours.horse.recorded.noTimeInfo': 'TREK nie oblicza czasu jazdy dla przejażdżki: tempo zależy od konia i jeźdźca.',
'tours.horse.recorded.accessUnchecked': 'Dostęp niesprawdzony',
'tours.horse.recorded.accessUncheckedInfo':
'Import nie sprawdza, czy po drogach wolno jeździć konno. Obowiązują znaki na miejscu.',
};
export default tours;
+10
View File
@@ -1104,5 +1104,15 @@ const tours: TranslationStrings = {
'tours.horse.leg.info': 'Участок маркированного конного маршрута из OpenStreetMap. Доступ для лошадей: {access}',
'tours.horse.leg.manual': 'Вручную, не проверено',
'tours.horse.leg.manualInfo': 'Нарисован вручную, по прямой. Ни конный маршрут, ни маршрутизатор его не проверяли.',
'tours.horse.recorded.title': 'Записанная прогулка',
'tours.horse.recorded.asRecorded': 'Как записано',
'tours.horse.recorded.asRecordedInfo':
'Линия остаётся такой, какой её записал файл. Ничего не прокладывается заново.',
'tours.horse.recorded.noTime': 'Без времени в седле',
'tours.horse.recorded.noTimeInfo':
'TREK не рассчитывает время в седле для конной прогулки: темп задают лошадь и всадник.',
'tours.horse.recorded.accessUnchecked': 'Доступ не проверен',
'tours.horse.recorded.accessUncheckedInfo':
'Импорт не проверяет, можно ли ехать по этим дорогам верхом. На месте действуют знаки.',
};
export default tours;
+8
View File
@@ -1063,5 +1063,13 @@ const tours: TranslationStrings = {
'tours.horse.leg.info': 'Úsek značenej jazdeckej trasy z OpenStreetMap. Prístup pre kone: {access}',
'tours.horse.leg.manual': 'Ručne, neoverené',
'tours.horse.leg.manualInfo': 'Nakreslené ručne, rovno. Neoverila ho žiadna jazdecká trasa ani plánovač.',
'tours.horse.recorded.title': 'Zaznamenaná vychádzka',
'tours.horse.recorded.asRecorded': 'Ako bolo zaznamenané',
'tours.horse.recorded.asRecordedInfo': 'Trasa zostáva tak, ako ju súbor zaznamenal. Nič sa znova neplánuje.',
'tours.horse.recorded.noTime': 'Bez času jazdy',
'tours.horse.recorded.noTimeInfo': 'TREK pre vychádzku nepočíta čas jazdy: tempo určuje kôň a jazdec.',
'tours.horse.recorded.accessUnchecked': 'Prístup neoverený',
'tours.horse.recorded.accessUncheckedInfo':
'Import neoveruje, či sa po cestách smie jazdiť na koni. Platí značenie na mieste.',
};
export default tours;
+9
View File
@@ -1052,5 +1052,14 @@ const tours: TranslationStrings = {
'tours.horse.leg.info': 'Delsträcka av en skyltad ridled från OpenStreetMap. Tillträde för häst: {access}',
'tours.horse.leg.manual': 'Manuell, okontrollerad',
'tours.horse.leg.manualInfo': 'Dragen för hand, rakt. Ingen ridled och ingen ruttplanerare har kontrollerat den.',
'tours.horse.recorded.title': 'Inspelad ridtur',
'tours.horse.recorded.asRecorded': 'Som inspelad',
'tours.horse.recorded.asRecordedInfo': 'Linjen förblir som filen spelade in den. Inget planeras om.',
'tours.horse.recorded.noTime': 'Ingen ridtid',
'tours.horse.recorded.noTimeInfo':
'TREK räknar inte ut någon ridtid för en ridtur: takten bestäms av hästen och ryttaren.',
'tours.horse.recorded.accessUnchecked': 'Tillträde okontrollerat',
'tours.horse.recorded.accessUncheckedInfo':
'Importen kontrollerar inte om vägarna får ridas. Skyltarna på plats gäller.',
};
export default tours;
+8
View File
@@ -1008,5 +1008,13 @@ const tours: TranslationStrings = {
'tours.horse.leg.info': 'ช่วงหนึ่งของเส้นทางขี่ม้าที่มีป้ายบอกจาก OpenStreetMap การเข้าถึงสำหรับม้า: {access}',
'tours.horse.leg.manual': 'วาดเอง ยังไม่ตรวจสอบ',
'tours.horse.leg.manualInfo': 'วาดด้วยมือเป็นเส้นตรง ไม่มีเส้นทางขี่ม้าหรือตัววางเส้นทางใดตรวจสอบ',
'tours.horse.recorded.title': 'ทริปขี่ม้าที่บันทึกไว้',
'tours.horse.recorded.asRecorded': 'ตามที่บันทึก',
'tours.horse.recorded.asRecordedInfo': 'เส้นยังคงเป็นไปตามที่ไฟล์บันทึกไว้ ไม่มีการวางเส้นทางใหม่',
'tours.horse.recorded.noTime': 'ไม่มีเวลาขี่ม้า',
'tours.horse.recorded.noTimeInfo': 'TREK ไม่คำนวณเวลาขี่ม้าให้ทริปขี่ม้า จังหวะขึ้นอยู่กับม้าและผู้ขี่',
'tours.horse.recorded.accessUnchecked': 'ยังไม่ตรวจสอบสิทธิ์ผ่าน',
'tours.horse.recorded.accessUncheckedInfo':
'การนำเข้าไม่ได้ตรวจสอบว่าขี่ม้าผ่านเส้นทางได้หรือไม่ ให้ถือป้ายในพื้นที่เป็นหลัก',
};
export default tours;
+1 -1
View File
@@ -20,7 +20,7 @@ const tokens = (value: string) => [...value.matchAll(/\{[^{}]+\}/g)].map((match)
// placeholder spec; here only the general keys are.
const groups = pluralGroups(Object.keys(english));
const generalKeys = (keys: string[]) => keys.filter((key) => !pluralFormOf(key, groups));
const GENERAL_KEY_COUNT = 952;
const GENERAL_KEY_COUNT = 959;
function literalKeys(source: string): string[] {
const tree = ts.createSourceFile('locale.ts', source, ts.ScriptTarget.Latest, true);
+8
View File
@@ -1060,5 +1060,13 @@ const tours: TranslationStrings = {
'tours.horse.leg.info': "OpenStreetMap'teki işaretli bir binicilik rotasının bölümü. At erişimi: {access}",
'tours.horse.leg.manual': 'Elle, denetlenmemiş',
'tours.horse.leg.manualInfo': 'Elle, düz çizildi. Ne bir binicilik rotası ne de bir rota planlayıcı denetledi.',
'tours.horse.recorded.title': 'Kaydedilmiş at gezisi',
'tours.horse.recorded.asRecorded': 'Kaydedildiği gibi',
'tours.horse.recorded.asRecordedInfo': 'Çizgi, dosyanın kaydettiği gibi kalır. Hiçbir şey yeniden rotalanmaz.',
'tours.horse.recorded.noTime': 'Sürüş süresi yok',
'tours.horse.recorded.noTimeInfo': 'TREK at gezisi için sürüş süresi hesaplamaz: tempoyu at ve binici belirler.',
'tours.horse.recorded.accessUnchecked': 'Erişim denetlenmedi',
'tours.horse.recorded.accessUncheckedInfo':
'İçe aktarma, yollarda ata binilip binilemeyeceğini denetlemez. Yerindeki tabelalar geçerlidir.',
};
export default tours;
+9
View File
@@ -1093,5 +1093,14 @@ const tours: TranslationStrings = {
'tours.horse.leg.info': 'Ділянка маркованого кінного маршруту з OpenStreetMap. Доступ для коней: {access}',
'tours.horse.leg.manual': 'Вручну, не перевірено',
'tours.horse.leg.manualInfo': 'Намальовано вручну, по прямій. Ні кінний маршрут, ні маршрутизатор її не перевіряли.',
'tours.horse.recorded.title': 'Записана прогулянка',
'tours.horse.recorded.asRecorded': 'Як записано',
'tours.horse.recorded.asRecordedInfo': 'Лінія залишається такою, як її записав файл. Нічого не прокладається заново.',
'tours.horse.recorded.noTime': 'Без часу в сідлі',
'tours.horse.recorded.noTimeInfo':
'TREK не розраховує час у сідлі для кінної прогулянки: темп задають кінь і вершник.',
'tours.horse.recorded.accessUnchecked': 'Доступ не перевірено',
'tours.horse.recorded.accessUncheckedInfo':
'Імпорт не перевіряє, чи можна їхати цими дорогами верхи. На місці діють знаки.',
};
export default tours;
+9
View File
@@ -1044,5 +1044,14 @@ const tours: TranslationStrings = {
'tours.horse.leg.info': 'Một đoạn của tuyến cưỡi ngựa có biển chỉ dẫn từ OpenStreetMap. Quyền đi ngựa: {access}',
'tours.horse.leg.manual': 'Thủ công, chưa kiểm tra',
'tours.horse.leg.manualInfo': 'Vẽ tay, đường thẳng. Không tuyến cưỡi ngựa hay công cụ dẫn đường nào kiểm tra.',
'tours.horse.recorded.title': 'Chuyến cưỡi ngựa đã ghi',
'tours.horse.recorded.asRecorded': 'Như đã ghi',
'tours.horse.recorded.asRecordedInfo': 'Đường đi giữ nguyên như tệp đã ghi. Không có gì được dẫn đường lại.',
'tours.horse.recorded.noTime': 'Không có thời gian cưỡi ngựa',
'tours.horse.recorded.noTimeInfo':
'TREK không tính thời gian cưỡi ngựa cho chuyến cưỡi ngựa: nhịp độ là của ngựa và người cưỡi.',
'tours.horse.recorded.accessUnchecked': 'Chưa kiểm tra quyền đi',
'tours.horse.recorded.accessUncheckedInfo':
'Việc nhập không kiểm tra liệu có được cưỡi ngựa trên các con đường hay không. Biển báo tại chỗ mới là căn cứ.',
};
export default tours;
+7
View File
@@ -970,5 +970,12 @@ const tours: TranslationStrings = {
'tours.horse.leg.info': '來自 OpenStreetMap 有標示騎馬路線的一段。馬匹通行:{access}',
'tours.horse.leg.manual': '手動,未經檢查',
'tours.horse.leg.manualInfo': '手動繪製的直線。沒有任何騎馬路線或路線規劃器檢查過它。',
'tours.horse.recorded.title': '已記錄的騎馬遊',
'tours.horse.recorded.asRecorded': '依記錄保留',
'tours.horse.recorded.asRecordedInfo': '路線維持檔案記錄時的樣子,不會重新規劃。',
'tours.horse.recorded.noTime': '無騎乘時間',
'tours.horse.recorded.noTimeInfo': 'TREK 不為騎馬遊計算騎乘時間:節奏取決於馬和騎手。',
'tours.horse.recorded.accessUnchecked': '通行未經檢查',
'tours.horse.recorded.accessUncheckedInfo': '匯入不會檢查這些道路是否允許騎馬通行。以現場標誌為準。',
};
export default tours;
+7
View File
@@ -971,5 +971,12 @@ const tours: TranslationStrings = {
'tours.horse.leg.info': '来自 OpenStreetMap 有标识骑马路线的一段。马匹通行:{access}',
'tours.horse.leg.manual': '手动,未经检查',
'tours.horse.leg.manualInfo': '手动绘制的直线。没有任何骑马路线或路线规划器检查过它。',
'tours.horse.recorded.title': '已记录的骑马游',
'tours.horse.recorded.asRecorded': '按记录保留',
'tours.horse.recorded.asRecordedInfo': '路线保持文件记录时的样子,不会重新规划。',
'tours.horse.recorded.noTime': '无骑行时间',
'tours.horse.recorded.noTimeInfo': 'TREK 不为骑马游计算骑行时间:节奏取决于马和骑手。',
'tours.horse.recorded.accessUnchecked': '通行未经检查',
'tours.horse.recorded.accessUncheckedInfo': '导入不会检查这些道路是否允许骑马通行。以现场标志为准。',
};
export default tours;