mirror of
https://github.com/mauriceboe/TREK.git
synced 2026-10-20 22:16:26 +00:00
fix(tours): show a run's ways in the hike's trail details card
A run described its ways in a card of its own, with the bike's sections, the bike's paint for the SAC grades (T5 and T6 merged) and its own words, so the same path read differently on a hike and on a run. The run now uses the hike's card, matched as a run, and keeps only the unpaved share and the hardest grade in its head. The run's grade labels and the now unused SAC paint go.
This commit is contained in:
@@ -1,5 +1,5 @@
|
||||
import { useMapPaint } from '../../Map/mapPaint';
|
||||
import type { SacClass, SurfaceClass, TrailClass } from './routeWays';
|
||||
import type { SurfaceClass, TrailClass } from './routeWays';
|
||||
|
||||
/**
|
||||
* The colours of a bike tour's or a run's surfaces and grades (styles/bike.css),
|
||||
@@ -30,25 +30,6 @@ export function trailPaintKey(key: TrailClass): BikePaintKey {
|
||||
return key === 'none' || key === 'unasked' ? 'unknown' : key;
|
||||
}
|
||||
|
||||
/**
|
||||
* RUN-02: the paint of a hiking grade, on the same scale from easy to hard as
|
||||
* the trail grades (T1 as S0, T5 and up as S4), so a map never shows two
|
||||
* colour scales for one idea; a way without a grade in the grey of the unknown.
|
||||
*/
|
||||
const SAC_PAINT: Record<SacClass, BikePaintKey> = {
|
||||
t1: 's0',
|
||||
t2: 's1',
|
||||
t3: 's2',
|
||||
t4: 's3',
|
||||
t5: 's4',
|
||||
t6: 's4',
|
||||
none: 'unknown',
|
||||
};
|
||||
|
||||
export function sacPaintKey(key: SacClass): BikePaintKey {
|
||||
return SAC_PAINT[key];
|
||||
}
|
||||
|
||||
/** The paint of a surface class. */
|
||||
export function surfacePaintKey(key: SurfaceClass): BikePaintKey {
|
||||
return key;
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
import { describe, expect, it } from 'vitest';
|
||||
import { encodePolyline } from '../../../../tests/helpers/polyline';
|
||||
import { bikePaintCss, sacPaintKey, surfacePaintKey, trailPaintKey } from './bikePaint';
|
||||
import { bikePaintCss, surfacePaintKey, trailPaintKey } from './bikePaint';
|
||||
import { withRouteHighlight } from './routeHighlight';
|
||||
import {
|
||||
routeWaysFrom,
|
||||
@@ -154,12 +154,6 @@ describe('the paints and the highlight (BIK-01 to BIK-05)', () => {
|
||||
expect(bikePaintCss('s1')).toBe('var(--bike-trail-s1)');
|
||||
});
|
||||
|
||||
it('FE-TOUR-WAYS-012: a hiking grade takes the paint of the trail grade it matches, T5 and up the hardest (RUN-02)', () => {
|
||||
const grades = ['t1', 't2', 't3', 't4', 't5', 't6'] as const;
|
||||
expect(grades.map(sacPaintKey)).toEqual(['s0', 's1', 's2', 's3', 's4', 's4']);
|
||||
expect(sacPaintKey('none')).toBe('unknown');
|
||||
});
|
||||
|
||||
const paint = { gravel: '#a0522d', way: '#111', casing: '#fff' } as Parameters<typeof withRouteHighlight>[4];
|
||||
|
||||
it('FE-TOUR-WAYS-009: lays the highlighted stretches over the route in their paint, solid', () => {
|
||||
|
||||
@@ -12,7 +12,7 @@ vi.mock('./useHikeWays', () => ({
|
||||
},
|
||||
}));
|
||||
|
||||
// FE-HIKE-WAYSCARD-001 to FE-HIKE-WAYSCARD-012
|
||||
// FE-HIKE-WAYSCARD-001 to FE-HIKE-WAYSCARD-013
|
||||
|
||||
const LINE: [number, number][] = [
|
||||
[47.0, 11.0],
|
||||
@@ -52,6 +52,7 @@ function ways(overrides: Partial<HikeWays> = {}): HikeWays {
|
||||
focus: null,
|
||||
focusOn: vi.fn(),
|
||||
retry: vi.fn(),
|
||||
routeWays: null,
|
||||
...overrides,
|
||||
};
|
||||
}
|
||||
@@ -73,7 +74,7 @@ describe('HikeWaysCard (MAP-03, MAP-04)', () => {
|
||||
it('FE-HIKE-WAYSCARD-001: without a line to describe there is no card', () => {
|
||||
show(ways({ available: false, shown: false, status: 'off', rows: [] }));
|
||||
expect(screen.queryByTestId('hike-ways-card')).toBeNull();
|
||||
expect(hook.input).toEqual({ tripId: 4, route: LINE, current: true });
|
||||
expect(hook.input).toEqual({ tripId: 4, route: LINE, current: true, matching: 'foot' });
|
||||
});
|
||||
|
||||
it('FE-HIKE-WAYSCARD-002: switched off it only offers the switch, and the switch turns it on', () => {
|
||||
@@ -202,4 +203,20 @@ describe('HikeWaysCard (MAP-03, MAP-04)', () => {
|
||||
expect(screen.getByTestId('sac-row-unmatched')).toBeInTheDocument();
|
||||
expect(screen.queryByRole('button', { name: /try again/i })).toBeNull();
|
||||
});
|
||||
|
||||
it('FE-HIKE-WAYSCARD-013: a run asks for its ways matched as a run and puts its own badges in the head (RUN-02)', () => {
|
||||
hook.ways = ways();
|
||||
render(
|
||||
<HikeWaysCard
|
||||
tripId={4}
|
||||
route={LINE}
|
||||
routingLimit={null}
|
||||
matching="run"
|
||||
aside={(state) => <span data-testid="run-aside">{state.facet}</span>}
|
||||
/>
|
||||
);
|
||||
expect(hook.input).toMatchObject({ tripId: 4, route: LINE, matching: 'run' });
|
||||
expect(screen.getByTestId('run-aside')).toHaveTextContent('sac');
|
||||
expect(screen.queryByTestId('sac-routing-limit')).toBeNull();
|
||||
});
|
||||
});
|
||||
|
||||
@@ -1,4 +1,5 @@
|
||||
import { Footprints, Layers, MountainSnow, Signpost } from 'lucide-react';
|
||||
import type { ReactNode } from 'react';
|
||||
import { useTranslation } from '../../../i18n';
|
||||
import ToggleSwitch from '../../Settings/ToggleSwitch';
|
||||
import { Segmented, type SegmentedOption } from '../../shared/dialogParts';
|
||||
@@ -7,7 +8,7 @@ import { ReadStatusNotice } from '../ReadStatusNotice';
|
||||
import { RailCard } from '../tourParts';
|
||||
import type { HikeFacet } from './hikeFacetSpecs';
|
||||
import { HikeWaysList } from './HikeWaysList';
|
||||
import { useHikeWays } from './useHikeWays';
|
||||
import { useHikeWays, type HikeWays } from './useHikeWays';
|
||||
|
||||
const ICON = { size: 13, strokeWidth: 2 } as const;
|
||||
|
||||
@@ -18,13 +19,16 @@ const ICON = { size: 13, strokeWidth: 2 } as const;
|
||||
* surface or the kind of way, three things kept apart, each class with its
|
||||
* share; the unknown stays unknown and says why. The routing limit, where the
|
||||
* hike was planned with one, sits apart as the setting it is. Nothing here
|
||||
* changes the tour.
|
||||
* changes the tour. A run (RUN-02) shows its ways in this same card, matched
|
||||
* as a run, with what a runner reads first in the card's head.
|
||||
*/
|
||||
export function HikeWaysCard({
|
||||
tripId,
|
||||
route,
|
||||
routeCurrent = true,
|
||||
routingLimit,
|
||||
matching = 'foot',
|
||||
aside,
|
||||
}: {
|
||||
tripId: number | string;
|
||||
/** The tour's own line, or null while there is none. */
|
||||
@@ -33,9 +37,13 @@ export function HikeWaysCard({
|
||||
routeCurrent?: boolean;
|
||||
/** The routing limit the hike is planned with, or null where it was not routed (an import). */
|
||||
routingLimit: number | null;
|
||||
/** How the line is matched to the ways: on foot for a hike, as a run for a run. */
|
||||
matching?: 'foot' | 'run';
|
||||
/** What the card's head shows beside its title, from what has been read. */
|
||||
aside?: (ways: HikeWays) => ReactNode;
|
||||
}) {
|
||||
const { t } = useTranslation();
|
||||
const ways = useHikeWays({ tripId, route, current: routeCurrent });
|
||||
const ways = useHikeWays({ tripId, route, current: routeCurrent, matching });
|
||||
if (!ways.available) return null;
|
||||
const facets: SegmentedOption<HikeFacet>[] = [
|
||||
{ value: 'sac', label: t('tours.hike.ways.facet.sac'), icon: <MountainSnow {...ICON} /> },
|
||||
@@ -43,7 +51,12 @@ export function HikeWaysCard({
|
||||
{ value: 'way', label: t('tours.hike.ways.facet.way'), icon: <Signpost {...ICON} /> },
|
||||
];
|
||||
return (
|
||||
<RailCard icon={<Footprints size={14} strokeWidth={2} />} title={t('tours.hike.ways.title')} foldId="hike-ways">
|
||||
<RailCard
|
||||
icon={<Footprints size={14} strokeWidth={2} />}
|
||||
title={t('tours.hike.ways.title')}
|
||||
foldId={matching === 'run' ? 'run-ways' : 'hike-ways'}
|
||||
aside={aside?.(ways)}
|
||||
>
|
||||
<div data-testid="hike-ways-card" className="flex min-w-0 flex-col gap-3">
|
||||
<div className="flex items-center gap-3">
|
||||
<span aria-hidden="true" className="min-w-0 flex-1 text-content-secondary" style={fs(12, 'body')}>
|
||||
|
||||
@@ -18,7 +18,7 @@ vi.mock('../bike/useRouteWays', () => ({
|
||||
},
|
||||
}));
|
||||
|
||||
// FE-HIKE-WAYS-HOOK-001 to FE-HIKE-WAYS-HOOK-011
|
||||
// FE-HIKE-WAYS-HOOK-001 to FE-HIKE-WAYS-HOOK-012
|
||||
|
||||
const ROUTE: [number, number][] = [
|
||||
[47.0, 11.0],
|
||||
@@ -228,4 +228,14 @@ describe('useHikeWays (MAP-03, MAP-04)', () => {
|
||||
expect(hook.input).toEqual(asked);
|
||||
expect(result.current.status).toBe('ready');
|
||||
});
|
||||
|
||||
it('FE-HIKE-WAYS-HOOK-012: a run is read the same way, matched as a run, and hands the router answer on once read (RUN-02)', () => {
|
||||
hook.state = READY;
|
||||
const { result } = renderHook(() => useHikeWays({ tripId: 5, route: ROUTE, matching: 'run' }));
|
||||
expect(result.current.routeWays).toBeNull();
|
||||
act(() => result.current.toggle());
|
||||
expect(hook.input).toMatchObject({ active: true, matching: 'run', withGrades: true });
|
||||
expect(result.current.rows.map((row) => row.key)).toEqual(['t2', 't4', 'untagged']);
|
||||
expect(result.current.routeWays).toBe(WAYS);
|
||||
});
|
||||
});
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
import { useCallback, useEffect, useMemo, useState } from 'react';
|
||||
import { useTourRouteHighlightStore } from '../../../store/tourRouteHighlightStore';
|
||||
import type { RouteWayEdge } from '../bike/routeWays';
|
||||
import type { RouteWayEdge, RouteWays } from '../bike/routeWays';
|
||||
import { useRouteWays, type RouteWaysState } from '../bike/useRouteWays';
|
||||
import { HIKE_FACETS, type HikeFacet } from './hikeFacetSpecs';
|
||||
import { facetClassifier, facetOverlay, facetRows, isUnknownClass, knownShareOf, type FacetRow } from './hikeFacets';
|
||||
@@ -30,6 +30,8 @@ export interface HikeWays {
|
||||
focus: string | null;
|
||||
focusOn: (key: string) => void;
|
||||
retry: () => void;
|
||||
/** The router's description of the ways under the line, once read; null before. */
|
||||
routeWays: RouteWays | null;
|
||||
}
|
||||
|
||||
const NO_STRAYS: ReadonlySet<RouteWayEdge> = new Set();
|
||||
@@ -57,7 +59,8 @@ function mostlyOf(rows: readonly FacetRow[]): FacetRow | null {
|
||||
* once; the facet picked (SAC grade, surface, kind of way) only decides how
|
||||
* the stretches are sorted and drawn along the line on the planner map,
|
||||
* every class at once or the one picked alone. Nothing here changes the
|
||||
* tour, its places or its days; switched off, the map is as it was.
|
||||
* tour, its places or its days; switched off, the map is as it was. A run
|
||||
* (RUN-02) is read the same way, its line matched as a run on foot.
|
||||
*/
|
||||
export function useHikeWays(input: {
|
||||
tripId: number | string;
|
||||
@@ -65,15 +68,17 @@ export function useHikeWays(input: {
|
||||
route: [number, number][] | null;
|
||||
/** False while the line is being planned anew: the card stays, and waits for the new one. */
|
||||
current?: boolean;
|
||||
/** How the line is matched to the ways: on foot for a hike, as a run for a run. */
|
||||
matching?: 'foot' | 'run';
|
||||
}): HikeWays {
|
||||
const { tripId, route, current = true } = input;
|
||||
const { tripId, route, current = true, matching = 'foot' } = input;
|
||||
const available = !!route && route.length >= 2;
|
||||
const [shown, setShown] = useState(false);
|
||||
const [facet, setFacet] = useState<HikeFacet>('sac');
|
||||
const [picked, setPicked] = useState<string | null>(null);
|
||||
const on = available && shown;
|
||||
const line = current ? route : null;
|
||||
const state = useRouteWays({ tripId, active: on, route: line, matching: 'foot', withGrades: true });
|
||||
const state = useRouteWays({ tripId, active: on, route: line, matching, withGrades: true });
|
||||
const { ways, grades } = state;
|
||||
const spec = HIKE_FACETS[facet];
|
||||
const strays = useMemo(() => (ways && line ? straysFrom(ways, line) : NO_STRAYS), [ways, line]);
|
||||
@@ -120,5 +125,6 @@ export function useHikeWays(input: {
|
||||
focus,
|
||||
focusOn,
|
||||
retry: state.retry,
|
||||
routeWays: status === 'ready' ? ways : null,
|
||||
};
|
||||
}
|
||||
|
||||
@@ -1,5 +1,6 @@
|
||||
import type { TourTrailGrade } from '@trek/shared';
|
||||
import { afterEach, beforeEach, describe, expect, it, vi } from 'vitest';
|
||||
import { fireEvent, render, screen, within } from '../../../../tests/helpers/render';
|
||||
import { fireEvent, render, screen } from '../../../../tests/helpers/render';
|
||||
import { useTourRouteHighlightStore } from '../../../store/tourRouteHighlightStore';
|
||||
import type { RouteWays } from '../bike/routeWays';
|
||||
import type { RouteWaysState } from '../bike/useRouteWays';
|
||||
@@ -13,7 +14,7 @@ vi.mock('../bike/useRouteWays', () => ({
|
||||
},
|
||||
}));
|
||||
|
||||
// FE-TOUR-RUNWAYS-001 to FE-TOUR-RUNWAYS-007
|
||||
// FE-TOUR-RUNWAYS-001 to FE-TOUR-RUNWAYS-005
|
||||
|
||||
type Planner = Parameters<typeof RunWaysCard>[0]['planner'];
|
||||
|
||||
@@ -31,6 +32,30 @@ const WAYS: RouteWays = {
|
||||
{ from: 2, to: 3, lengthM: 100, surface: 'ground', way: 'path', sac: 't2', wayId: 3 },
|
||||
],
|
||||
};
|
||||
const grade = (id: number, values: Partial<TourTrailGrade>): TourTrailGrade => ({
|
||||
id,
|
||||
mtb_scale: null,
|
||||
mtb_scale_uphill: null,
|
||||
mtb_scale_imba: null,
|
||||
sac_scale: null,
|
||||
sac_scale_unclear: null,
|
||||
surface: null,
|
||||
highway: null,
|
||||
...values,
|
||||
});
|
||||
const READY: RouteWaysState = {
|
||||
status: 'ready',
|
||||
ways: WAYS,
|
||||
grades: {
|
||||
status: 'ready',
|
||||
byWay: new Map([
|
||||
[1, grade(1, { surface: 'asphalt', highway: 'residential' })],
|
||||
[2, grade(2, { sac_scale: 'hiking', surface: 'gravel', highway: 'track' })],
|
||||
[3, grade(3, { sac_scale: 'mountain_hiking', surface: 'ground', highway: 'path' })],
|
||||
]),
|
||||
unasked: new Set(),
|
||||
},
|
||||
};
|
||||
|
||||
function planner(overrides: Partial<Planner> = {}): Planner {
|
||||
return {
|
||||
@@ -43,11 +68,13 @@ function planner(overrides: Partial<Planner> = {}): Planner {
|
||||
} as Planner;
|
||||
}
|
||||
|
||||
function show(state: Partial<RouteWaysState>, overrides: Partial<Planner> = {}) {
|
||||
hook.state = { status: 'ready', ways: WAYS, grades: null, ...state };
|
||||
function show(state: Partial<RouteWaysState> = {}, overrides: Partial<Planner> = {}) {
|
||||
hook.state = { ...READY, ...state };
|
||||
return render(<RunWaysCard planner={planner(overrides)} />);
|
||||
}
|
||||
|
||||
const switchOn = () => fireEvent.click(screen.getByRole('button', { name: 'Show along the route' }));
|
||||
|
||||
beforeEach(() => {
|
||||
localStorage.clear();
|
||||
useTourRouteHighlightStore.getState().clear();
|
||||
@@ -55,83 +82,50 @@ beforeEach(() => {
|
||||
afterEach(() => vi.clearAllMocks());
|
||||
|
||||
describe('RunWaysCard (RUN-02)', () => {
|
||||
it('FE-TOUR-RUNWAYS-001: shows the surfaces, the hiking grades and the kinds of way, the ungraded named as unknown', () => {
|
||||
show({});
|
||||
expect(within(screen.getByTestId('route-ways-row-surface-paved')).getByText('60 %')).toBeInTheDocument();
|
||||
expect(screen.getByTestId('route-ways-row-surface-gravel')).toHaveTextContent('300 m');
|
||||
expect(screen.getByTestId('route-ways-row-grade-t1')).toHaveTextContent('T1, hiking');
|
||||
expect(screen.getByTestId('route-ways-row-grade-t2')).toHaveTextContent('10 %');
|
||||
expect(screen.getByTestId('route-ways-row-grade-none')).toHaveTextContent('No grade in the data');
|
||||
expect(screen.getByTestId('route-ways-row-way-track')).toBeInTheDocument();
|
||||
expect(screen.getByRole('button', { name: 'About the path difficulty' })).toHaveAccessibleDescription(
|
||||
/unknown, not easy/
|
||||
);
|
||||
it('FE-TOUR-RUNWAYS-001: the trail details a hike shows, switched on by the runner and matched as a run', () => {
|
||||
show();
|
||||
expect(screen.getByRole('region', { name: 'Trail details' })).toBeInTheDocument();
|
||||
expect(hook.input).toMatchObject({ active: false, matching: 'run' });
|
||||
expect(screen.queryByTestId('sac-row-t1')).toBeNull();
|
||||
switchOn();
|
||||
expect(hook.input).toMatchObject({ tripId: 4, active: true, route: LINE, matching: 'run', withGrades: true });
|
||||
// The same words and the same paint as a hike's, never a raw tag in a label.
|
||||
expect(screen.getByTestId('sac-row-t1')).toHaveTextContent('Hiking');
|
||||
expect(screen.getByTestId('sac-row-t2')).toHaveTextContent('Mountain hiking');
|
||||
expect(screen.queryByText(/sac_scale\)/)).toBeNull();
|
||||
expect(useTourRouteHighlightStore.getState().highlight).toMatchObject({
|
||||
id: 'sac',
|
||||
paints: ['t1', 't2', 'unknown'],
|
||||
});
|
||||
});
|
||||
|
||||
it('FE-TOUR-RUNWAYS-002: the head says how much is unpaved and the hardest graded path, in words for assistive tech', () => {
|
||||
show({});
|
||||
it('FE-TOUR-RUNWAYS-002: once read, the head says how much is unpaved and the hardest graded path, in words for assistive tech', () => {
|
||||
show();
|
||||
expect(screen.queryByText(/unpaved/)).toBeNull();
|
||||
switchOn();
|
||||
expect(screen.getByText('40 % unpaved')).toBeInTheDocument();
|
||||
const hardest = screen.getByTestId('run-ways-hardest');
|
||||
expect(hardest).toHaveTextContent('T2');
|
||||
expect(hardest).toHaveTextContent('Hardest graded path: T2, mountain hiking');
|
||||
expect(hardest.textContent).not.toMatch(/[\u2013\u2014]/);
|
||||
expect(hardest).toHaveTextContent('Hardest graded path: Mountain hiking');
|
||||
expect(hardest.textContent).not.toMatch(/[–—]/);
|
||||
});
|
||||
|
||||
it('FE-TOUR-RUNWAYS-003: a grade row points at its stretches on the map in the grade colour, and lets go', () => {
|
||||
show({});
|
||||
const row = screen.getByTestId('route-ways-row-grade-t2');
|
||||
fireEvent.click(row);
|
||||
expect(useTourRouteHighlightStore.getState().highlight).toEqual({
|
||||
id: 'grade-t2',
|
||||
lines: [LINE.slice(2, 4)],
|
||||
paint: 's1',
|
||||
});
|
||||
fireEvent.click(row);
|
||||
expect(useTourRouteHighlightStore.getState().highlight).toBeNull();
|
||||
});
|
||||
|
||||
it('FE-TOUR-RUNWAYS-004: a route all paved and ungraded shows no head badges, and its grades as unknown', () => {
|
||||
const paved: RouteWays = { line: LINE, edges: [WAYS.edges[0]] };
|
||||
show({ ways: paved });
|
||||
it('FE-TOUR-RUNWAYS-003: a route all paved and ungraded shows no head badges', () => {
|
||||
show({ ways: { line: LINE, edges: [WAYS.edges[0]] } });
|
||||
switchOn();
|
||||
expect(screen.queryByText(/unpaved/)).toBeNull();
|
||||
expect(screen.queryByTestId('run-ways-hardest')).toBeNull();
|
||||
expect(screen.getByTestId('route-ways-row-grade-none')).toHaveTextContent('100 %');
|
||||
});
|
||||
|
||||
it('FE-TOUR-RUNWAYS-005: says when it is still asking, offline, or failed with a way to ask again', () => {
|
||||
const loading = show({ status: 'loading', ways: null });
|
||||
expect(screen.getByTestId('run-ways-card')).toHaveTextContent(/./);
|
||||
loading.unmount();
|
||||
const offline = show({ status: 'offline', ways: null });
|
||||
expect(screen.queryByRole('button', { name: /try again/i })).toBeNull();
|
||||
offline.unmount();
|
||||
show({ status: 'failed', ways: null });
|
||||
fireEvent.click(screen.getByRole('button', { name: /try again/i }));
|
||||
expect(hook.retry).toHaveBeenCalledTimes(1);
|
||||
it('FE-TOUR-RUNWAYS-004: only for a run being edited', () => {
|
||||
const other = show({}, { isRun: false });
|
||||
expect(screen.queryByRole('region', { name: 'Trail details' })).toBeNull();
|
||||
other.unmount();
|
||||
show({}, { mode: { type: 'view-gpx' } as Planner['mode'] });
|
||||
expect(screen.queryByRole('region', { name: 'Trail details' })).toBeNull();
|
||||
});
|
||||
|
||||
it('FE-TOUR-RUNWAYS-006: matched on foot without trail grades, and only for a run being edited with its route as planned now', () => {
|
||||
const run = show({});
|
||||
expect(hook.input).toMatchObject({
|
||||
tripId: 4,
|
||||
active: true,
|
||||
route: LINE,
|
||||
matching: 'run',
|
||||
withGrades: false,
|
||||
});
|
||||
run.unmount();
|
||||
const idle = show({ status: 'idle', ways: null });
|
||||
expect(screen.queryByTestId('run-ways-card')).toBeNull();
|
||||
idle.unmount();
|
||||
const bike = show({}, { isRun: false });
|
||||
expect(screen.queryByTestId('run-ways-card')).toBeNull();
|
||||
bike.unmount();
|
||||
show({}, { routeCurrent: false, mode: { type: 'view-gpx' } as Planner['mode'] });
|
||||
expect(screen.queryByTestId('run-ways-card')).toBeNull();
|
||||
expect(hook.input).toMatchObject({ active: false, route: null });
|
||||
});
|
||||
|
||||
it('FE-TOUR-RUNWAYS-007: the hardest graded step along the route, a stretch of no length left out', () => {
|
||||
it('FE-TOUR-RUNWAYS-005: the hardest graded step along the route, a stretch of no length left out', () => {
|
||||
expect(hardestSac(WAYS)).toBe('t2');
|
||||
expect(hardestSac({ line: LINE, edges: [{ ...WAYS.edges[2], sac: 't5', lengthM: 0 }, WAYS.edges[1]] })).toBe('t1');
|
||||
expect(hardestSac({ line: LINE, edges: [WAYS.edges[0]] })).toBeNull();
|
||||
|
||||
@@ -1,37 +1,18 @@
|
||||
import { Layers, Mountain } from 'lucide-react';
|
||||
import { Mountain } from 'lucide-react';
|
||||
import { useMemo } from 'react';
|
||||
import { useTranslation } from '../../../i18n';
|
||||
import { SoftPill } from '../../Planner/planParts';
|
||||
import { Tooltip } from '../../shared/Tooltip';
|
||||
import { sacPaintKey } from '../bike/bikePaint';
|
||||
import {
|
||||
percent,
|
||||
RouteWaysProgress,
|
||||
RouteWaysSource,
|
||||
ShareBar,
|
||||
ShareList,
|
||||
SurfaceSection,
|
||||
WaySection,
|
||||
WaysSection,
|
||||
} from '../bike/RouteWaySections';
|
||||
import { sharesBy, unpavedShareOf, type RouteWayEdge, type RouteWays, type SacClass } from '../bike/routeWays';
|
||||
import { useRouteWays } from '../bike/useRouteWays';
|
||||
import { percent } from '../bike/RouteWaySections';
|
||||
import { unpavedShareOf, type RouteWays, type SacClass } from '../bike/routeWays';
|
||||
import { HikeWaysCard } from '../hike/HikeWaysCard';
|
||||
import type { TourPlannerController } from '../planner/useTourPlanner';
|
||||
import { RailCard } from '../tourParts';
|
||||
|
||||
type GradedClass = Exclude<SacClass, 'none'>;
|
||||
|
||||
const sacOf = (edge: RouteWayEdge) => edge.sac;
|
||||
|
||||
/** The steps from easy to hard, so the hardest one along the route is the last one found. */
|
||||
const SAC_STEPS: readonly GradedClass[] = ['t1', 't2', 't3', 't4', 't5', 't6'];
|
||||
|
||||
/** A hiking grade in the card's own words, a way without one as unknown. */
|
||||
function useSacLabel() {
|
||||
const { t } = useTranslation();
|
||||
return (key: SacClass) => (key === 'none' ? t('tours.bike.trail.none') : t(`tours.run.ways.sac.${key}`));
|
||||
}
|
||||
|
||||
/** The hardest graded step along the route, or null when no way on it carries a grade. */
|
||||
export function hardestSac(ways: RouteWays): GradedClass | null {
|
||||
const found = new Set(ways.edges.filter((edge) => edge.lengthM > 0).map((edge) => edge.sac));
|
||||
@@ -39,34 +20,15 @@ export function hardestSac(ways: RouteWays): GradedClass | null {
|
||||
}
|
||||
|
||||
/**
|
||||
* RUN-02: the hiking grades along a run (sac_scale) as OpenStreetMap mappers
|
||||
* gave them, by share, each pointing at its stretches on the map. A way
|
||||
* without a grade is its own row: unknown, never easy. What the grades are
|
||||
* and are not sits behind the info dot, not in a line of its own.
|
||||
* The card's head: how much of the run is known to be unpaved, and the
|
||||
* hardest graded path on it, named in the words the hike's trail details use.
|
||||
*/
|
||||
function PathGradeSection({ ways }: { ways: RouteWays }) {
|
||||
const { t } = useTranslation();
|
||||
const labelOf = useSacLabel();
|
||||
const rows = useMemo(() => sharesBy<SacClass>(ways, sacOf), [ways]);
|
||||
return (
|
||||
<WaysSection
|
||||
label={t('tours.run.ways.grade')}
|
||||
info={{ label: t('tours.run.ways.gradeInfoLabel'), description: t('tours.run.ways.gradeInfo') }}
|
||||
>
|
||||
<ShareBar rows={rows} paintOf={sacPaintKey} />
|
||||
<ShareList ways={ways} rows={rows} idPrefix="grade" classOf={sacOf} labelOf={labelOf} paintOf={sacPaintKey} />
|
||||
</WaysSection>
|
||||
);
|
||||
}
|
||||
|
||||
/** The card's head: how much of the run is known to be unpaved, and the hardest graded path on it. */
|
||||
function RunWaysBadges({ ways }: { ways: RouteWays | null }) {
|
||||
const { t } = useTranslation();
|
||||
const labelOf = useSacLabel();
|
||||
const unpaved = useMemo(() => (ways ? unpavedShareOf(ways) : null), [ways]);
|
||||
const hardest = useMemo(() => (ways ? hardestSac(ways) : null), [ways]);
|
||||
if (unpaved === null && hardest === null) return null;
|
||||
const hardestText = hardest ? t('tours.run.ways.gradeTop', { grade: labelOf(hardest) }) : '';
|
||||
const hardestText = hardest ? t('tours.run.ways.gradeTop', { grade: t(`tours.hike.sac.${hardest}`) }) : '';
|
||||
return (
|
||||
<span className="flex flex-none items-center gap-1">
|
||||
{unpaved !== null && <SoftPill>{t('tours.bike.ways.unpavedBadge', { share: percent(unpaved) })}</SoftPill>}
|
||||
@@ -87,41 +49,24 @@ function RunWaysBadges({ ways }: { ways: RouteWays | null }) {
|
||||
type RunWaysPlanner = Pick<TourPlannerController, 'tripId' | 'mode' | 'isRun' | 'route' | 'routeCurrent'>;
|
||||
|
||||
/**
|
||||
* RUN-02: what the run being planned runs on: its surfaces, the hiking grades
|
||||
* of its paths and its kinds of way, read off the router's description of the
|
||||
* route matched on foot. Where the data says nothing the card says unknown,
|
||||
* and nothing here grades the run, changes its time or changes the tour.
|
||||
* RUN-02: what the run being planned runs on, in the same card a hike shows
|
||||
* its trail details in: switched on by the runner, the SAC grade, the surface
|
||||
* or the kind of way along the line, drawn on the map in the same colours and
|
||||
* named in the same words, the line matched as a run. The head adds what a
|
||||
* runner reads first: the unpaved share and the hardest graded path. Nothing
|
||||
* here grades the run, changes its time or changes the tour.
|
||||
*/
|
||||
export function RunWaysCard({ planner }: { planner: RunWaysPlanner }) {
|
||||
const { t } = useTranslation();
|
||||
const editing = planner.mode.type === 'new-draft' || planner.mode.type === 'edit-saved';
|
||||
const state = useRouteWays({
|
||||
tripId: planner.tripId,
|
||||
active: planner.isRun && editing,
|
||||
route: planner.routeCurrent ? planner.route : null,
|
||||
matching: 'run',
|
||||
withGrades: false,
|
||||
});
|
||||
const { ways } = state;
|
||||
if (!planner.isRun || !editing || state.status === 'idle') return null;
|
||||
if (!planner.isRun || !editing) return null;
|
||||
return (
|
||||
<RailCard
|
||||
icon={<Layers size={14} strokeWidth={2} />}
|
||||
title={t('tours.bike.ways.title')}
|
||||
foldId="run-ways"
|
||||
aside={<RunWaysBadges ways={ways} />}
|
||||
>
|
||||
<div data-testid="run-ways-card" className="flex min-w-0 flex-col gap-3">
|
||||
<RouteWaysProgress state={state} />
|
||||
{ways && (
|
||||
<>
|
||||
<SurfaceSection ways={ways} />
|
||||
<PathGradeSection ways={ways} />
|
||||
<WaySection ways={ways} />
|
||||
<RouteWaysSource />
|
||||
</>
|
||||
)}
|
||||
</div>
|
||||
</RailCard>
|
||||
<HikeWaysCard
|
||||
tripId={planner.tripId}
|
||||
route={planner.route}
|
||||
routeCurrent={planner.routeCurrent}
|
||||
routingLimit={null}
|
||||
matching="run"
|
||||
aside={(ways) => <RunWaysBadges ways={ways.routeWays} />}
|
||||
/>
|
||||
);
|
||||
}
|
||||
|
||||
@@ -459,17 +459,7 @@ const tours: TranslationStrings = {
|
||||
'tours.run.timeSum': '{distance} بوتيرة {pace} دقيقة/كم تساوي {time}',
|
||||
'tours.run.profile.trail': 'الجري على المسارات الطبيعية',
|
||||
'tours.run.profileHint.trail': 'يفضّل طرق الغابات والممرات الطبيعية، حتى الممرات الجبلية (T2).',
|
||||
'tours.run.ways.grade': 'صعوبة المسار (sac_scale)',
|
||||
'tours.run.ways.gradeInfoLabel': 'حول صعوبة المسار',
|
||||
'tours.run.ways.gradeInfo':
|
||||
'درجات المشي كما حددها مساهمو OpenStreetMap. الطريق بلا درجة مجهول، وليس سهلًا. لا تقول شيئًا عن إمكانية الجري عليه أو مدى ملاءمته لك.',
|
||||
'tours.run.ways.gradeTop': 'أصعب مسار مصنّف: {grade}',
|
||||
'tours.run.ways.sac.t1': 'T1، المشي الطويل',
|
||||
'tours.run.ways.sac.t2': 'T2، المشي الجبلي',
|
||||
'tours.run.ways.sac.t3': 'T3، المشي الجبلي الشاق',
|
||||
'tours.run.ways.sac.t4': 'T4، المشي في التضاريس الألبية',
|
||||
'tours.run.ways.sac.t5': 'T5، المشي الشاق في التضاريس الألبية',
|
||||
'tours.run.ways.sac.t6': 'T6، المشي الصعب في التضاريس الألبية',
|
||||
'tours.bike.profile': 'الملف',
|
||||
'tours.bike.profile.touring': 'تجوال',
|
||||
'tours.bike.profile.road': 'طريق',
|
||||
|
||||
@@ -449,17 +449,7 @@ const tours: TranslationStrings = {
|
||||
'tours.run.timeSum': '{distance}, {pace} dəq/km tempdə: {time}',
|
||||
'tours.run.profile.trail': 'Trail qaçışı',
|
||||
'tours.run.profileHint.trail': 'Meşə yollarına və təbii cığırlara üstünlük verir, dağ cığırlarına (T2) qədər.',
|
||||
'tours.run.ways.grade': 'Yolun çətinliyi (sac_scale)',
|
||||
'tours.run.ways.gradeInfoLabel': 'Yolun çətinliyi haqqında',
|
||||
'tours.run.ways.gradeInfo':
|
||||
'OpenStreetMap xəritəçilərinin verdiyi yürüş dərəcələri. Dərəcəsi olmayan yol asan deyil, naməlumdur. Onlar yolun qaçmağa yararlı olub-olmadığı və ya sizə uyğun olub-olmadığı barədə heç nə demir.',
|
||||
'tours.run.ways.gradeTop': 'Ən çətin qiymətləndirilmiş cığır: {grade}',
|
||||
'tours.run.ways.sac.t1': 'T1, yürüş',
|
||||
'tours.run.ways.sac.t2': 'T2, dağ yürüşü',
|
||||
'tours.run.ways.sac.t3': 'T3, yüksək hazırlıq tələb edən dağ yürüşü',
|
||||
'tours.run.ways.sac.t4': 'T4, alp yürüşü',
|
||||
'tours.run.ways.sac.t5': 'T5, yüksək hazırlıq tələb edən alp yürüşü',
|
||||
'tours.run.ways.sac.t6': 'T6, çətin alp yürüşü',
|
||||
'tours.bike.profile': 'Profil',
|
||||
'tours.bike.profile.touring': 'Turinq',
|
||||
'tours.bike.profile.road': 'Yol',
|
||||
|
||||
@@ -455,17 +455,7 @@ const tours: TranslationStrings = {
|
||||
'tours.run.timeSum': '{distance} a {pace} min/km dão {time}',
|
||||
'tours.run.profile.trail': 'Corrida em trilha',
|
||||
'tours.run.profileHint.trail': 'Prefere estradas florestais e trilhas naturais, até trilhas de montanha (T2).',
|
||||
'tours.run.ways.grade': 'Dificuldade da trilha (sac_scale)',
|
||||
'tours.run.ways.gradeInfoLabel': 'Sobre a dificuldade da trilha',
|
||||
'tours.run.ways.gradeInfo':
|
||||
'Graus de caminhada como os mapeadores do OpenStreetMap os informaram. Um caminho sem grau é desconhecido, não fácil. Eles não dizem se dá para correr no caminho nem se ele combina com você.',
|
||||
'tours.run.ways.gradeTop': 'Trilha classificada mais difícil: {grade}',
|
||||
'tours.run.ways.sac.t1': 'T1, caminhada',
|
||||
'tours.run.ways.sac.t2': 'T2, caminhada em montanha',
|
||||
'tours.run.ways.sac.t3': 'T3, caminhada exigente em montanha',
|
||||
'tours.run.ways.sac.t4': 'T4, caminhada alpina',
|
||||
'tours.run.ways.sac.t5': 'T5, caminhada alpina exigente',
|
||||
'tours.run.ways.sac.t6': 'T6, caminhada alpina difícil',
|
||||
'tours.bike.profile': 'Perfil',
|
||||
'tours.bike.profile.touring': 'Cicloturismo',
|
||||
'tours.bike.profile.road': 'Estrada',
|
||||
|
||||
@@ -456,17 +456,7 @@ const tours: TranslationStrings = {
|
||||
'tours.run.timeSum': '{distance} a {pace} min/km són {time}',
|
||||
'tours.run.profile.trail': 'Curses de muntanya',
|
||||
'tours.run.profileHint.trail': 'Prefereix pistes forestals i corriols naturals, fins a camins de muntanya (T2).',
|
||||
'tours.run.ways.grade': 'Dificultat del camí (sac_scale)',
|
||||
'tours.run.ways.gradeInfoLabel': 'Sobre la dificultat del camí',
|
||||
'tours.run.ways.gradeInfo':
|
||||
'Graus d’excursionisme tal com els han posat els mapejadors d’OpenStreetMap. Un camí sense grau és desconegut, no fàcil. No diuen si un camí es pot córrer ni si et convé.',
|
||||
'tours.run.ways.gradeTop': 'Camí classificat més difícil: {grade}',
|
||||
'tours.run.ways.sac.t1': 'T1, senderisme',
|
||||
'tours.run.ways.sac.t2': 'T2, senderisme de muntanya',
|
||||
'tours.run.ways.sac.t3': 'T3, senderisme de muntanya exigent',
|
||||
'tours.run.ways.sac.t4': 'T4, senderisme alpí',
|
||||
'tours.run.ways.sac.t5': 'T5, senderisme alpí exigent',
|
||||
'tours.run.ways.sac.t6': 'T6, senderisme alpí difícil',
|
||||
'tours.bike.profile': 'Perfil',
|
||||
'tours.bike.profile.touring': 'Cicloturisme',
|
||||
'tours.bike.profile.road': 'Carretera',
|
||||
|
||||
@@ -448,17 +448,7 @@ const tours: TranslationStrings = {
|
||||
'tours.run.timeSum': '{distance} při {pace} min/km dává {time}',
|
||||
'tours.run.profile.trail': 'Trailový běh',
|
||||
'tours.run.profileHint.trail': 'Upřednostňuje lesní cesty a přírodní stezky, až po horské stezky (T2).',
|
||||
'tours.run.ways.grade': 'Obtížnost cesty (sac_scale)',
|
||||
'tours.run.ways.gradeInfoLabel': 'O obtížnosti cesty',
|
||||
'tours.run.ways.gradeInfo':
|
||||
'Turistické stupně, jak je zadali mapovači OpenStreetMap. Cesta bez stupně je neznámá, ne snadná. Neříkají nic o tom, zda se po cestě dá běžet nebo zda se pro tebe hodí.',
|
||||
'tours.run.ways.gradeTop': 'Nejtěžší hodnocená cesta: {grade}',
|
||||
'tours.run.ways.sac.t1': 'T1, pěší turistika',
|
||||
'tours.run.ways.sac.t2': 'T2, horská turistika',
|
||||
'tours.run.ways.sac.t3': 'T3, náročná horská turistika',
|
||||
'tours.run.ways.sac.t4': 'T4, vysokohorská turistika',
|
||||
'tours.run.ways.sac.t5': 'T5, náročná vysokohorská turistika',
|
||||
'tours.run.ways.sac.t6': 'T6, obtížná vysokohorská turistika',
|
||||
'tours.bike.profile': 'Profil',
|
||||
'tours.bike.profile.touring': 'Treking',
|
||||
'tours.bike.profile.road': 'Silnice',
|
||||
|
||||
@@ -458,17 +458,7 @@ const tours: TranslationStrings = {
|
||||
'tours.run.timeSum': '{distance} bei {pace} min/km ergibt {time}',
|
||||
'tours.run.profile.trail': 'Trailrunning',
|
||||
'tours.run.profileHint.trail': 'Bevorzugt Forstwege und naturbelassene Pfade, bis zu Bergwegen (T2).',
|
||||
'tours.run.ways.grade': 'Wegschwierigkeit (sac_scale)',
|
||||
'tours.run.ways.gradeInfoLabel': 'Zur Wegschwierigkeit',
|
||||
'tours.run.ways.gradeInfo':
|
||||
'Wanderskala, wie OpenStreetMap-Mapper sie vergeben haben. Ein Weg ohne Angabe ist unbekannt, nicht leicht. Sie sagt nichts darüber, ob ein Weg laufbar ist oder zu dir passt.',
|
||||
'tours.run.ways.gradeTop': 'Schwierigster bewerteter Weg: {grade}',
|
||||
'tours.run.ways.sac.t1': 'T1, Wandern',
|
||||
'tours.run.ways.sac.t2': 'T2, Bergwandern',
|
||||
'tours.run.ways.sac.t3': 'T3, Anspruchsvolles Bergwandern',
|
||||
'tours.run.ways.sac.t4': 'T4, Alpinwandern',
|
||||
'tours.run.ways.sac.t5': 'T5, Anspruchsvolles Alpinwandern',
|
||||
'tours.run.ways.sac.t6': 'T6, Schwieriges Alpinwandern',
|
||||
'tours.bike.profile': 'Profil',
|
||||
'tours.bike.profile.touring': 'Touring', // same-as-en
|
||||
'tours.bike.profile.road': 'Rennrad',
|
||||
|
||||
@@ -447,17 +447,7 @@ const tours: TranslationStrings = {
|
||||
'tours.run.timeSum': '{distance} at {pace} min/km is {time}',
|
||||
'tours.run.profile.trail': 'Trail running',
|
||||
'tours.run.profileHint.trail': 'Prefers forest tracks and natural paths, up to mountain paths (T2).',
|
||||
'tours.run.ways.grade': 'Path difficulty (sac_scale)',
|
||||
'tours.run.ways.gradeInfoLabel': 'About the path difficulty',
|
||||
'tours.run.ways.gradeInfo':
|
||||
'Hiking grades as OpenStreetMap mappers gave them. A way without one is unknown, not easy. They say nothing about whether a way can be run or suits you.',
|
||||
'tours.run.ways.gradeTop': 'Hardest graded path: {grade}',
|
||||
'tours.run.ways.sac.t1': 'T1, hiking',
|
||||
'tours.run.ways.sac.t2': 'T2, mountain hiking',
|
||||
'tours.run.ways.sac.t3': 'T3, demanding mountain hiking',
|
||||
'tours.run.ways.sac.t4': 'T4, alpine hiking',
|
||||
'tours.run.ways.sac.t5': 'T5, demanding alpine hiking',
|
||||
'tours.run.ways.sac.t6': 'T6, difficult alpine hiking',
|
||||
'tours.bike.profile': 'Profile',
|
||||
'tours.bike.profile.touring': 'Touring',
|
||||
'tours.bike.profile.road': 'Road',
|
||||
|
||||
@@ -455,17 +455,7 @@ const tours: TranslationStrings = {
|
||||
'tours.run.timeSum': '{distance} a {pace} min/km son {time}',
|
||||
'tours.run.profile.trail': 'Carrera de montaña',
|
||||
'tours.run.profileHint.trail': 'Prefiere pistas forestales y senderos naturales, hasta senderos de montaña (T2).',
|
||||
'tours.run.ways.grade': 'Dificultad del camino (sac_scale)',
|
||||
'tours.run.ways.gradeInfoLabel': 'Sobre la dificultad del camino',
|
||||
'tours.run.ways.gradeInfo':
|
||||
'Grados de senderismo tal como los asignaron los mapeadores de OpenStreetMap. Un camino sin grado es desconocido, no fácil. No dicen si un camino se puede correr ni si te conviene.',
|
||||
'tours.run.ways.gradeTop': 'Camino graduado más difícil: {grade}',
|
||||
'tours.run.ways.sac.t1': 'T1, senderismo',
|
||||
'tours.run.ways.sac.t2': 'T2, senderismo de montaña',
|
||||
'tours.run.ways.sac.t3': 'T3, senderismo de montaña exigente',
|
||||
'tours.run.ways.sac.t4': 'T4, senderismo alpino',
|
||||
'tours.run.ways.sac.t5': 'T5, senderismo alpino exigente',
|
||||
'tours.run.ways.sac.t6': 'T6, senderismo alpino difícil',
|
||||
'tours.bike.profile': 'Perfil',
|
||||
'tours.bike.profile.touring': 'Cicloturismo',
|
||||
'tours.bike.profile.road': 'Carretera',
|
||||
|
||||
@@ -450,17 +450,7 @@ const tours: TranslationStrings = {
|
||||
'tours.run.timeSum': '{distance} tempos {pace} min/km teeb {time}',
|
||||
'tours.run.profile.trail': 'Rajajooks',
|
||||
'tours.run.profileHint.trail': 'Eelistab metsateid ja looduslikke radu, kuni mägiradadeni (T2).',
|
||||
'tours.run.ways.grade': 'Raja raskus (sac_scale)',
|
||||
'tours.run.ways.gradeInfoLabel': 'Raja raskusest',
|
||||
'tours.run.ways.gradeInfo':
|
||||
'Matkaraskused, nagu OpenStreetMapi kaardistajad need andsid. Ilma raskuseta tee on teadmata, mitte lihtne. Need ei ütle midagi selle kohta, kas teel saab joosta või kas see sulle sobib.',
|
||||
'tours.run.ways.gradeTop': 'Raskeim hinnatud rada: {grade}',
|
||||
'tours.run.ways.sac.t1': 'T1, matkamine',
|
||||
'tours.run.ways.sac.t2': 'T2, mägimatkamine',
|
||||
'tours.run.ways.sac.t3': 'T3, nõudlik mägimatkamine',
|
||||
'tours.run.ways.sac.t4': 'T4, alpimatkamine',
|
||||
'tours.run.ways.sac.t5': 'T5, nõudlik alpimatkamine',
|
||||
'tours.run.ways.sac.t6': 'T6, raske alpimatkamine',
|
||||
'tours.bike.profile': 'Profiil',
|
||||
'tours.bike.profile.touring': 'Matk',
|
||||
'tours.bike.profile.road': 'Maantee',
|
||||
|
||||
@@ -462,17 +462,7 @@ const tours: TranslationStrings = {
|
||||
'tours.run.profile.trail': 'Trail',
|
||||
'tours.run.profileHint.trail':
|
||||
'Privilégie les chemins forestiers et les sentiers naturels, jusqu’aux sentiers de montagne (T2).',
|
||||
'tours.run.ways.grade': 'Difficulté du chemin (sac_scale)',
|
||||
'tours.run.ways.gradeInfoLabel': 'À propos de la difficulté du chemin',
|
||||
'tours.run.ways.gradeInfo':
|
||||
'Cotations de randonnée telles que les contributeurs OpenStreetMap les ont saisies. Un chemin sans cotation est inconnu, pas facile. Elles ne disent pas si un chemin se court ni s’il vous convient.',
|
||||
'tours.run.ways.gradeTop': 'Chemin coté le plus difficile : {grade}',
|
||||
'tours.run.ways.sac.t1': 'T1, randonnée',
|
||||
'tours.run.ways.sac.t2': 'T2, randonnée en montagne',
|
||||
'tours.run.ways.sac.t3': 'T3, randonnée en montagne exigeante',
|
||||
'tours.run.ways.sac.t4': 'T4, randonnée alpine',
|
||||
'tours.run.ways.sac.t5': 'T5, randonnée alpine exigeante',
|
||||
'tours.run.ways.sac.t6': 'T6, randonnée alpine difficile',
|
||||
'tours.bike.profile': 'Profil',
|
||||
'tours.bike.profile.touring': 'Voyage',
|
||||
'tours.bike.profile.road': 'Route',
|
||||
|
||||
@@ -464,17 +464,7 @@ const tours: TranslationStrings = {
|
||||
'tours.run.timeSum': '{distance} με {pace} λεπ/km κάνει {time}',
|
||||
'tours.run.profile.trail': 'Ορεινό τρέξιμο',
|
||||
'tours.run.profileHint.trail': 'Προτιμά δασικούς δρόμους και φυσικά μονοπάτια, έως ορεινά μονοπάτια (T2).',
|
||||
'tours.run.ways.grade': 'Δυσκολία μονοπατιού (sac_scale)',
|
||||
'tours.run.ways.gradeInfoLabel': 'Σχετικά με τη δυσκολία μονοπατιού',
|
||||
'tours.run.ways.gradeInfo':
|
||||
'Βαθμοί πεζοπορίας όπως τους έδωσαν οι χαρτογράφοι του OpenStreetMap. Ένας δρόμος χωρίς βαθμό είναι άγνωστος, όχι εύκολος. Δεν λένε τίποτα για το αν ένας δρόμος τρέχεται ή αν σου ταιριάζει.',
|
||||
'tours.run.ways.gradeTop': 'Δυσκολότερο βαθμολογημένο μονοπάτι: {grade}',
|
||||
'tours.run.ways.sac.t1': 'T1, πεζοπορία',
|
||||
'tours.run.ways.sac.t2': 'T2, ορεινή πεζοπορία',
|
||||
'tours.run.ways.sac.t3': 'T3, απαιτητική ορεινή πεζοπορία',
|
||||
'tours.run.ways.sac.t4': 'T4, αλπική πεζοπορία',
|
||||
'tours.run.ways.sac.t5': 'T5, απαιτητική αλπική πεζοπορία',
|
||||
'tours.run.ways.sac.t6': 'T6, δύσκολη αλπική πεζοπορία',
|
||||
'tours.bike.profile': 'Προφίλ',
|
||||
'tours.bike.profile.touring': 'Περιήγηση',
|
||||
'tours.bike.profile.road': 'Δρόμου',
|
||||
|
||||
@@ -456,17 +456,7 @@ const tours: TranslationStrings = {
|
||||
'tours.run.profile.trail': 'Terepfutás',
|
||||
'tours.run.profileHint.trail':
|
||||
'Előnyben részesíti az erdei utakat és a természetes ösvényeket, a hegyi ösvényekig (T2).',
|
||||
'tours.run.ways.grade': 'Útnehézség (sac_scale)',
|
||||
'tours.run.ways.gradeInfoLabel': 'Az útnehézségről',
|
||||
'tours.run.ways.gradeInfo':
|
||||
'Túranehézségi fokozatok, ahogy az OpenStreetMap térképezői megadták. A fokozat nélküli út ismeretlen, nem könnyű. Semmit sem mondanak arról, hogy egy út futható-e, vagy illik-e hozzád.',
|
||||
'tours.run.ways.gradeTop': 'Legnehezebb besorolt ösvény: {grade}',
|
||||
'tours.run.ways.sac.t1': 'T1, gyalogtúra',
|
||||
'tours.run.ways.sac.t2': 'T2, hegyi túra',
|
||||
'tours.run.ways.sac.t3': 'T3, megerőltető hegyi túra',
|
||||
'tours.run.ways.sac.t4': 'T4, magashegyi túra',
|
||||
'tours.run.ways.sac.t5': 'T5, megerőltető magashegyi túra',
|
||||
'tours.run.ways.sac.t6': 'T6, nehéz magashegyi túra',
|
||||
'tours.bike.profile': 'Profil',
|
||||
'tours.bike.profile.touring': 'Túra',
|
||||
'tours.bike.profile.road': 'Országúti',
|
||||
|
||||
@@ -450,17 +450,7 @@ const tours: TranslationStrings = {
|
||||
'tours.run.timeSum': '{distance} dengan pace {pace} mnt/km menjadi {time}',
|
||||
'tours.run.profile.trail': 'Lari trail',
|
||||
'tours.run.profileHint.trail': 'Mengutamakan jalan hutan dan jalur alami, hingga jalur gunung (T2).',
|
||||
'tours.run.ways.grade': 'Kesulitan jalur (sac_scale)',
|
||||
'tours.run.ways.gradeInfoLabel': 'Tentang kesulitan jalur',
|
||||
'tours.run.ways.gradeInfo':
|
||||
'Tingkat pendakian seperti yang diberikan pemeta OpenStreetMap. Jalan tanpa tingkat tidak diketahui, bukan mudah. Tingkat ini tidak menyatakan apakah jalan bisa dilari atau cocok untukmu.',
|
||||
'tours.run.ways.gradeTop': 'Jalur bertingkat tersulit: {grade}',
|
||||
'tours.run.ways.sac.t1': 'T1, pendakian',
|
||||
'tours.run.ways.sac.t2': 'T2, pendakian gunung',
|
||||
'tours.run.ways.sac.t3': 'T3, pendakian gunung menantang',
|
||||
'tours.run.ways.sac.t4': 'T4, pendakian alpin',
|
||||
'tours.run.ways.sac.t5': 'T5, pendakian alpin menantang',
|
||||
'tours.run.ways.sac.t6': 'T6, pendakian alpin sulit',
|
||||
'tours.bike.profile': 'Profil',
|
||||
'tours.bike.profile.touring': 'Touring', // same-as-en
|
||||
'tours.bike.profile.road': 'Road', // same-as-en
|
||||
|
||||
@@ -458,17 +458,7 @@ const tours: TranslationStrings = {
|
||||
'tours.run.timeSum': '{distance} a {pace} min/km fanno {time}',
|
||||
'tours.run.profile.trail': 'Corsa su sentiero',
|
||||
'tours.run.profileHint.trail': 'Preferisce strade forestali e sentieri naturali, fino ai sentieri di montagna (T2).',
|
||||
'tours.run.ways.grade': 'Difficoltà del sentiero (sac_scale)',
|
||||
'tours.run.ways.gradeInfoLabel': 'Sulla difficoltà del sentiero',
|
||||
'tours.run.ways.gradeInfo':
|
||||
'Gradi escursionistici come li hanno assegnati i mappatori di OpenStreetMap. Un sentiero senza grado è sconosciuto, non facile. Non dicono se un sentiero si può correre né se fa per te.',
|
||||
'tours.run.ways.gradeTop': 'Sentiero classificato più difficile: {grade}',
|
||||
'tours.run.ways.sac.t1': 'T1, escursionismo',
|
||||
'tours.run.ways.sac.t2': 'T2, escursionismo di montagna',
|
||||
'tours.run.ways.sac.t3': 'T3, escursionismo di montagna impegnativo',
|
||||
'tours.run.ways.sac.t4': 'T4, escursionismo alpino',
|
||||
'tours.run.ways.sac.t5': 'T5, escursionismo alpino impegnativo',
|
||||
'tours.run.ways.sac.t6': 'T6, escursionismo alpino difficile',
|
||||
'tours.bike.profile': 'Profilo',
|
||||
'tours.bike.profile.touring': 'Turismo',
|
||||
'tours.bike.profile.road': 'Strada',
|
||||
|
||||
@@ -437,16 +437,7 @@ const tours: TranslationStrings = {
|
||||
'tours.run.timeSum': '{distance} を {pace} 分/km で {time}',
|
||||
'tours.run.profile.trail': 'トレイルランニング',
|
||||
'tours.run.profileHint.trail': '林道や自然の小道を優先し、山道 (T2) まで含みます。',
|
||||
'tours.run.ways.grade': '道の難易度 (sac_scale)',
|
||||
'tours.run.ways.gradeInfoLabel': '道の難易度について',
|
||||
'tours.run.ways.gradeInfo': 'OpenStreetMap のマッパーが付けたハイキングの難易度です。難易度のない道は簡単という意味ではなく、不明です。道を走れるかどうか、あなたに合うかどうかは示しません。',
|
||||
'tours.run.ways.gradeTop': '最も難しい格付けの道: {grade}',
|
||||
'tours.run.ways.sac.t1': 'T1、ハイキング',
|
||||
'tours.run.ways.sac.t2': 'T2、山岳ハイキング',
|
||||
'tours.run.ways.sac.t3': 'T3、上級山岳ハイキング',
|
||||
'tours.run.ways.sac.t4': 'T4、アルパインハイキング',
|
||||
'tours.run.ways.sac.t5': 'T5、上級アルパインハイキング',
|
||||
'tours.run.ways.sac.t6': 'T6、難度の高いアルパインハイキング',
|
||||
'tours.bike.profile': 'プロファイル',
|
||||
'tours.bike.profile.touring': 'ツーリング',
|
||||
'tours.bike.profile.road': 'ロード',
|
||||
|
||||
@@ -435,17 +435,7 @@ const tours: TranslationStrings = {
|
||||
'tours.run.timeSum': '{distance}, {pace}분/km 페이스로 {time}',
|
||||
'tours.run.profile.trail': '트레일 러닝',
|
||||
'tours.run.profileHint.trail': '임도와 자연 오솔길을 우선하며, 산길(T2)까지 포함합니다.',
|
||||
'tours.run.ways.grade': '길 난이도 (sac_scale)',
|
||||
'tours.run.ways.gradeInfoLabel': '길 난이도 안내',
|
||||
'tours.run.ways.gradeInfo':
|
||||
'OpenStreetMap 매퍼가 매긴 하이킹 등급입니다. 등급이 없는 길은 쉬운 것이 아니라 알 수 없는 것입니다. 길을 달릴 수 있는지, 나에게 맞는지는 알려 주지 않습니다.',
|
||||
'tours.run.ways.gradeTop': '가장 어려운 등급의 길: {grade}',
|
||||
'tours.run.ways.sac.t1': 'T1, 하이킹',
|
||||
'tours.run.ways.sac.t2': 'T2, 산악 하이킹',
|
||||
'tours.run.ways.sac.t3': 'T3, 고난도 산악 하이킹',
|
||||
'tours.run.ways.sac.t4': 'T4, 고산 하이킹',
|
||||
'tours.run.ways.sac.t5': 'T5, 고난도 고산 하이킹',
|
||||
'tours.run.ways.sac.t6': 'T6, 매우 어려운 고산 하이킹',
|
||||
'tours.bike.profile': '프로필',
|
||||
'tours.bike.profile.touring': '투어링',
|
||||
'tours.bike.profile.road': '로드',
|
||||
|
||||
@@ -457,17 +457,7 @@ const tours: TranslationStrings = {
|
||||
'tours.run.timeSum': '{distance} bij {pace} min/km is {time}',
|
||||
'tours.run.profile.trail': 'Trailrunning',
|
||||
'tours.run.profileHint.trail': 'Geeft de voorkeur aan bospaden en onverharde paden, tot bergpaden (T2).',
|
||||
'tours.run.ways.grade': 'Moeilijkheid van het pad (sac_scale)',
|
||||
'tours.run.ways.gradeInfoLabel': 'Over de moeilijkheid van het pad',
|
||||
'tours.run.ways.gradeInfo':
|
||||
'Wandelgraden zoals OpenStreetMap-mappers ze hebben ingevoerd. Een pad zonder graad is onbekend, niet makkelijk. Ze zeggen niets over of een pad te lopen is of bij je past.',
|
||||
'tours.run.ways.gradeTop': 'Moeilijkste gegradeerde pad: {grade}',
|
||||
'tours.run.ways.sac.t1': 'T1, wandelen',
|
||||
'tours.run.ways.sac.t2': 'T2, bergwandelen',
|
||||
'tours.run.ways.sac.t3': 'T3, veeleisend bergwandelen',
|
||||
'tours.run.ways.sac.t4': 'T4, alpien wandelen',
|
||||
'tours.run.ways.sac.t5': 'T5, veeleisend alpien wandelen',
|
||||
'tours.run.ways.sac.t6': 'T6, moeilijk alpien wandelen',
|
||||
'tours.bike.profile': 'Profiel',
|
||||
'tours.bike.profile.touring': 'Toer',
|
||||
'tours.bike.profile.road': 'Racefiets',
|
||||
|
||||
@@ -464,17 +464,7 @@ const tours: TranslationStrings = {
|
||||
'tours.run.timeSum': '{distance} przy {pace} min/km to {time}',
|
||||
'tours.run.profile.trail': 'Bieganie terenowe',
|
||||
'tours.run.profileHint.trail': 'Preferuje drogi leśne i naturalne ścieżki, aż po ścieżki górskie (T2).',
|
||||
'tours.run.ways.grade': 'Trudność ścieżki (sac_scale)',
|
||||
'tours.run.ways.gradeInfoLabel': 'O trudności ścieżki',
|
||||
'tours.run.ways.gradeInfo':
|
||||
'Stopnie trudności szlaków nadane przez maperów OpenStreetMap. Droga bez stopnia jest nieznana, a nie łatwa. Nie mówią, czy da się nią biec ani czy jest dla ciebie.',
|
||||
'tours.run.ways.gradeTop': 'Najtrudniejsza oceniona ścieżka: {grade}',
|
||||
'tours.run.ways.sac.t1': 'T1, turystyka piesza',
|
||||
'tours.run.ways.sac.t2': 'T2, turystyka górska',
|
||||
'tours.run.ways.sac.t3': 'T3, wymagająca turystyka górska',
|
||||
'tours.run.ways.sac.t4': 'T4, turystyka wysokogórska',
|
||||
'tours.run.ways.sac.t5': 'T5, wymagająca turystyka wysokogórska',
|
||||
'tours.run.ways.sac.t6': 'T6, trudna turystyka wysokogórska',
|
||||
'tours.bike.profile': 'Profil',
|
||||
'tours.bike.profile.touring': 'Turystyka',
|
||||
'tours.bike.profile.road': 'Szosa',
|
||||
|
||||
@@ -468,17 +468,7 @@ const tours: TranslationStrings = {
|
||||
'tours.run.timeSum': '{distance} в темпе {pace} мин/км: {time}',
|
||||
'tours.run.profile.trail': 'Трейлраннинг',
|
||||
'tours.run.profileHint.trail': 'Предпочитает лесные дороги и природные тропы, вплоть до горных троп (T2).',
|
||||
'tours.run.ways.grade': 'Сложность тропы (sac_scale)',
|
||||
'tours.run.ways.gradeInfoLabel': 'О сложности тропы',
|
||||
'tours.run.ways.gradeInfo':
|
||||
'Категории сложности троп, как их указали участники OpenStreetMap. Путь без категории неизвестен, а не прост. Они ничего не говорят о том, можно ли по пути бежать и подходит ли он вам.',
|
||||
'tours.run.ways.gradeTop': 'Самая сложная оцененная тропа: {grade}',
|
||||
'tours.run.ways.sac.t1': 'T1, пеший поход',
|
||||
'tours.run.ways.sac.t2': 'T2, горный поход',
|
||||
'tours.run.ways.sac.t3': 'T3, сложный горный поход',
|
||||
'tours.run.ways.sac.t4': 'T4, высокогорный поход',
|
||||
'tours.run.ways.sac.t5': 'T5, сложный высокогорный поход',
|
||||
'tours.run.ways.sac.t6': 'T6, очень сложный высокогорный поход',
|
||||
'tours.bike.profile': 'Профиль',
|
||||
'tours.bike.profile.touring': 'Туризм',
|
||||
'tours.bike.profile.road': 'Шоссе',
|
||||
|
||||
@@ -449,17 +449,7 @@ const tours: TranslationStrings = {
|
||||
'tours.run.timeSum': '{distance} pri {pace} min/km dáva {time}',
|
||||
'tours.run.profile.trail': 'Trailový beh',
|
||||
'tours.run.profileHint.trail': 'Uprednostňuje lesné cesty a prírodné chodníky, až po horské chodníky (T2).',
|
||||
'tours.run.ways.grade': 'Náročnosť cesty (sac_scale)',
|
||||
'tours.run.ways.gradeInfoLabel': 'O náročnosti cesty',
|
||||
'tours.run.ways.gradeInfo':
|
||||
'Turistické stupne, ako ich zadali mapovači OpenStreetMap. Cesta bez stupňa je neznáma, nie ľahká. Nehovoria nič o tom, či sa po ceste dá bežať alebo či sa pre teba hodí.',
|
||||
'tours.run.ways.gradeTop': 'Najnáročnejšia hodnotená cesta: {grade}',
|
||||
'tours.run.ways.sac.t1': 'T1, pešia turistika',
|
||||
'tours.run.ways.sac.t2': 'T2, horská turistika',
|
||||
'tours.run.ways.sac.t3': 'T3, náročná horská turistika',
|
||||
'tours.run.ways.sac.t4': 'T4, vysokohorská turistika',
|
||||
'tours.run.ways.sac.t5': 'T5, náročná vysokohorská turistika',
|
||||
'tours.run.ways.sac.t6': 'T6, veľmi náročná vysokohorská turistika',
|
||||
'tours.bike.profile': 'Profil',
|
||||
'tours.bike.profile.touring': 'Trekking',
|
||||
'tours.bike.profile.road': 'Cestný',
|
||||
|
||||
@@ -446,17 +446,7 @@ const tours: TranslationStrings = {
|
||||
'tours.run.timeSum': '{distance} i {pace} min/km blir {time}',
|
||||
'tours.run.profile.trail': 'Traillöpning',
|
||||
'tours.run.profileHint.trail': 'Föredrar skogsvägar och naturstigar, upp till fjällstigar (T2).',
|
||||
'tours.run.ways.grade': 'Ledens svårighet (sac_scale)',
|
||||
'tours.run.ways.gradeInfoLabel': 'Om ledens svårighet',
|
||||
'tours.run.ways.gradeInfo':
|
||||
'Vandringsgrader som OpenStreetMap-kartläggare har angett dem. En väg utan grad är okänd, inte lätt. De säger inget om huruvida en väg går att springa eller passar dig.',
|
||||
'tours.run.ways.gradeTop': 'Svåraste graderade led: {grade}',
|
||||
'tours.run.ways.sac.t1': 'T1, vandring',
|
||||
'tours.run.ways.sac.t2': 'T2, bergsvandring',
|
||||
'tours.run.ways.sac.t3': 'T3, krävande bergsvandring',
|
||||
'tours.run.ways.sac.t4': 'T4, alpin vandring',
|
||||
'tours.run.ways.sac.t5': 'T5, krävande alpin vandring',
|
||||
'tours.run.ways.sac.t6': 'T6, svår alpin vandring',
|
||||
'tours.bike.profile': 'Profil',
|
||||
'tours.bike.profile.touring': 'Tur',
|
||||
'tours.bike.profile.road': 'Landsväg',
|
||||
|
||||
@@ -427,17 +427,7 @@ const tours: TranslationStrings = {
|
||||
'tours.run.timeSum': '{distance} ที่เพซ {pace} นาที/km เท่ากับ {time}',
|
||||
'tours.run.profile.trail': 'วิ่งเทรล',
|
||||
'tours.run.profileHint.trail': 'เน้นถนนในป่าและทางเดินธรรมชาติ จนถึงทางเดินบนภูเขา (T2)',
|
||||
'tours.run.ways.grade': 'ความยากของเส้นทาง (sac_scale)',
|
||||
'tours.run.ways.gradeInfoLabel': 'เกี่ยวกับความยากของเส้นทาง',
|
||||
'tours.run.ways.gradeInfo':
|
||||
'ระดับการเดินป่าตามที่ผู้ทำแผนที่ OpenStreetMap ระบุไว้ ทางที่ไม่มีระดับคือไม่ทราบ ไม่ใช่ง่าย และไม่ได้บอกว่าทางนั้นวิ่งได้หรือเหมาะกับคุณหรือไม่',
|
||||
'tours.run.ways.gradeTop': 'ทางที่มีระดับยากที่สุด: {grade}',
|
||||
'tours.run.ways.sac.t1': 'T1 เดินป่า',
|
||||
'tours.run.ways.sac.t2': 'T2 เดินป่าบนภูเขา',
|
||||
'tours.run.ways.sac.t3': 'T3 เดินป่าบนภูเขาระดับท้าทาย',
|
||||
'tours.run.ways.sac.t4': 'T4 เดินป่าแบบอัลไพน์',
|
||||
'tours.run.ways.sac.t5': 'T5 เดินป่าแบบอัลไพน์ระดับท้าทาย',
|
||||
'tours.run.ways.sac.t6': 'T6 เดินป่าแบบอัลไพน์ระดับยาก',
|
||||
'tours.bike.profile': 'โปรไฟล์',
|
||||
'tours.bike.profile.touring': 'ทัวริ่ง',
|
||||
'tours.bike.profile.road': 'เสือหมอบ',
|
||||
|
||||
@@ -20,7 +20,7 @@ const toursModules = import.meta.glob<{ default: TranslationStrings }>('./*/tour
|
||||
const TOUR_DOMAINS: { file: string; generalKeyCount: number; modules: DomainModules }[] = [
|
||||
{
|
||||
file: 'tours',
|
||||
generalKeyCount: 1182,
|
||||
generalKeyCount: 1173,
|
||||
modules: toursModules,
|
||||
},
|
||||
{
|
||||
|
||||
@@ -449,17 +449,7 @@ const tours: TranslationStrings = {
|
||||
'tours.run.timeSum': '{distance}, {pace} dk/km tempoyla {time}',
|
||||
'tours.run.profile.trail': 'Patika koşusu',
|
||||
'tours.run.profileHint.trail': 'Orman yollarını ve doğal patikaları tercih eder, dağ patikalarına (T2) kadar.',
|
||||
'tours.run.ways.grade': 'Patika zorluğu (sac_scale)',
|
||||
'tours.run.ways.gradeInfoLabel': 'Patika zorluğu hakkında',
|
||||
'tours.run.ways.gradeInfo':
|
||||
'OpenStreetMap haritacılarının verdiği yürüyüş dereceleri. Derecesi olmayan bir yol kolay değil, bilinmiyor demektir. Bir yolun koşulabilir olup olmadığı ya da sana uyup uymadığı hakkında bir şey söylemezler.',
|
||||
'tours.run.ways.gradeTop': 'En zor derecelendirilmiş patika: {grade}',
|
||||
'tours.run.ways.sac.t1': 'T1, doğa yürüyüşü',
|
||||
'tours.run.ways.sac.t2': 'T2, dağ yürüyüşü',
|
||||
'tours.run.ways.sac.t3': 'T3, zorlu dağ yürüyüşü',
|
||||
'tours.run.ways.sac.t4': 'T4, yüksek dağ yürüyüşü',
|
||||
'tours.run.ways.sac.t5': 'T5, zorlu yüksek dağ yürüyüşü',
|
||||
'tours.run.ways.sac.t6': 'T6, çok zor yüksek dağ yürüyüşü',
|
||||
'tours.bike.profile': 'Profil',
|
||||
'tours.bike.profile.touring': 'Tur',
|
||||
'tours.bike.profile.road': 'Yol',
|
||||
|
||||
@@ -463,17 +463,7 @@ const tours: TranslationStrings = {
|
||||
'tours.run.timeSum': '{distance} у темпі {pace} хв/км: {time}',
|
||||
'tours.run.profile.trail': 'Трейлранінг',
|
||||
'tours.run.profileHint.trail': 'Надає перевагу лісовим дорогам і природним стежкам, аж до гірських стежок (T2).',
|
||||
'tours.run.ways.grade': 'Складність стежки (sac_scale)',
|
||||
'tours.run.ways.gradeInfoLabel': 'Про складність стежки',
|
||||
'tours.run.ways.gradeInfo':
|
||||
'Категорії складності стежок, як їх вказали учасники OpenStreetMap. Шлях без категорії невідомий, а не легкий. Вони нічого не кажуть про те, чи можна ним бігти і чи підходить він вам.',
|
||||
'tours.run.ways.gradeTop': 'Найскладніша оцінена стежка: {grade}',
|
||||
'tours.run.ways.sac.t1': 'T1, піший похід',
|
||||
'tours.run.ways.sac.t2': 'T2, гірський похід',
|
||||
'tours.run.ways.sac.t3': 'T3, складний гірський похід',
|
||||
'tours.run.ways.sac.t4': 'T4, високогірний похід',
|
||||
'tours.run.ways.sac.t5': 'T5, складний високогірний похід',
|
||||
'tours.run.ways.sac.t6': 'T6, дуже складний високогірний похід',
|
||||
'tours.bike.profile': 'Профіль',
|
||||
'tours.bike.profile.touring': 'Туризм',
|
||||
'tours.bike.profile.road': 'Шосе',
|
||||
|
||||
@@ -447,17 +447,7 @@ const tours: TranslationStrings = {
|
||||
'tours.run.timeSum': '{distance} với nhịp {pace} phút/km là {time}',
|
||||
'tours.run.profile.trail': 'Chạy địa hình',
|
||||
'tours.run.profileHint.trail': 'Ưu tiên đường rừng và lối mòn tự nhiên, đến cả đường núi (T2).',
|
||||
'tours.run.ways.grade': 'Độ khó của đường (sac_scale)',
|
||||
'tours.run.ways.gradeInfoLabel': 'Về độ khó của đường',
|
||||
'tours.run.ways.gradeInfo':
|
||||
'Cấp độ đi bộ đường dài do người vẽ bản đồ OpenStreetMap gán. Đường không có cấp là chưa rõ, không phải dễ. Chúng không cho biết đường có chạy được hay có hợp với bạn không.',
|
||||
'tours.run.ways.gradeTop': 'Đường được xếp cấp khó nhất: {grade}',
|
||||
'tours.run.ways.sac.t1': 'T1, đi bộ đường dài',
|
||||
'tours.run.ways.sac.t2': 'T2, đi bộ đường dài trên núi',
|
||||
'tours.run.ways.sac.t3': 'T3, đi bộ đường dài trên núi đòi hỏi kỹ năng cao',
|
||||
'tours.run.ways.sac.t4': 'T4, đi bộ đường dài trên núi cao',
|
||||
'tours.run.ways.sac.t5': 'T5, đi bộ đường dài trên núi cao đòi hỏi kỹ năng cao',
|
||||
'tours.run.ways.sac.t6': 'T6, đi bộ đường dài trên núi cao rất khó',
|
||||
'tours.bike.profile': 'Hồ sơ',
|
||||
'tours.bike.profile.touring': 'Du lịch',
|
||||
'tours.bike.profile.road': 'Đường trường',
|
||||
|
||||
@@ -407,16 +407,7 @@ const tours: TranslationStrings = {
|
||||
'tours.run.timeSum': '{distance},配速 {pace} 分/公里,用時 {time}',
|
||||
'tours.run.profile.trail': '越野跑',
|
||||
'tours.run.profileHint.trail': '優先選擇林道與自然小徑,最高到山地步道 (T2)。',
|
||||
'tours.run.ways.grade': '路徑難度 (sac_scale)',
|
||||
'tours.run.ways.gradeInfoLabel': '關於路徑難度',
|
||||
'tours.run.ways.gradeInfo': 'OpenStreetMap 繪圖者標註的健行難度。沒有難度的路是未知,而不是容易。它們不說明一條路能否跑,也不說明是否適合你。',
|
||||
'tours.run.ways.gradeTop': '最難的已評級路徑:{grade}',
|
||||
'tours.run.ways.sac.t1': 'T1,健行',
|
||||
'tours.run.ways.sac.t2': 'T2,山區健行',
|
||||
'tours.run.ways.sac.t3': 'T3,具挑戰性的山區健行',
|
||||
'tours.run.ways.sac.t4': 'T4,高山健行',
|
||||
'tours.run.ways.sac.t5': 'T5,具挑戰性的高山健行',
|
||||
'tours.run.ways.sac.t6': 'T6,高難度高山健行',
|
||||
'tours.bike.profile': '類型',
|
||||
'tours.bike.profile.touring': '旅行',
|
||||
'tours.bike.profile.road': '公路',
|
||||
|
||||
@@ -408,16 +408,7 @@ const tours: TranslationStrings = {
|
||||
'tours.run.timeSum': '{distance},配速 {pace} 分/公里,用时 {time}',
|
||||
'tours.run.profile.trail': '越野跑',
|
||||
'tours.run.profileHint.trail': '优先选择林道和自然小径,最高到山地步道 (T2)。',
|
||||
'tours.run.ways.grade': '路径难度 (sac_scale)',
|
||||
'tours.run.ways.gradeInfoLabel': '关于路径难度',
|
||||
'tours.run.ways.gradeInfo': 'OpenStreetMap 绘图者标注的徒步难度。没有难度的路是未知,而不是容易。它们不说明一条路能否跑,也不说明是否适合你。',
|
||||
'tours.run.ways.gradeTop': '最难的已评级路径:{grade}',
|
||||
'tours.run.ways.sac.t1': 'T1,徒步',
|
||||
'tours.run.ways.sac.t2': 'T2,山地徒步',
|
||||
'tours.run.ways.sac.t3': 'T3,挑战性山地徒步',
|
||||
'tours.run.ways.sac.t4': 'T4,高山徒步',
|
||||
'tours.run.ways.sac.t5': 'T5,挑战性高山徒步',
|
||||
'tours.run.ways.sac.t6': 'T6,高难度高山徒步',
|
||||
'tours.bike.profile': '类型',
|
||||
'tours.bike.profile.touring': '旅行',
|
||||
'tours.bike.profile.road': '公路',
|
||||
|
||||
Reference in New Issue
Block a user