mirror of
https://github.com/mauriceboe/TREK.git
synced 2026-10-21 14:36:31 +00:00
feat(tours): show the known surfaces and way types along a hike (MAP-04)
The hike's "SAC grades" card becomes "Trail details": one switch, then a segmented choice between SAC, Surface and Way type, three things kept apart and drawn along the route on the planner map one at a time. Every facet reads from the same answer: the ways under the line are matched on foot once, their OpenStreetMap values fetched once through the trail-grades read MAP-03 already uses, and each stretch is placed against the tour's own line once. Switching facets asks nothing new. The server now hands on a way's surface and highway tags as OpenStreetMap wrote them, only trimmed and bounded. The planner groups them (asphalt or concrete, paving stones, compacted, gravel, natural ground, grass, rock, wood or metal, unpaved without a kind, other; path, footway, steps, farm or forest track, cycle path, minor road, road, via ferrata, other), and each row's tooltip names the values recorded, such as surface=gravel, surface=pebblestone. The router's own surface reading is never used: it is normalised and a default where nothing is recorded. What is not known stays unknown and says why, in every facet: no value recorded, a value no group takes or two at once (unclear, never the nearest group), no way clearly under the tour's line, or a way that could not be asked about. Imported tracks and hand-drawn stretches keep their geometry; a matched way only counts where it keeps within 25 m of it. A summary badge gives the known share and the most common class; the SAC facet keeps its highest step and routing limit badges. Nothing about the tour, its places or its days changes, and no routing preference is set.
This commit is contained in:
@@ -51,6 +51,8 @@ const grade = (id: number, mtb: string | null, uphill: string | null = null): To
|
||||
mtb_scale_imba: null,
|
||||
sac_scale: null,
|
||||
sac_scale_unclear: null,
|
||||
surface: null,
|
||||
highway: null,
|
||||
});
|
||||
|
||||
function show(state: Partial<RouteWaysState>, overrides: Partial<TourPlannerController> = {}) {
|
||||
|
||||
@@ -76,7 +76,16 @@ describe('useRouteWays (BIK-01 to BIK-05, RUN-02)', () => {
|
||||
it('FE-TOUR-WAYS-HOOK-003: a mountain bike tour asks the grades of its ways, and keeps what could not be asked apart', async () => {
|
||||
gradesRepo.grades.mockResolvedValue({
|
||||
ways: [
|
||||
{ id: 11, mtb_scale: '2', mtb_scale_uphill: null, mtb_scale_imba: null, sac_scale: null, sac_scale_unclear: null },
|
||||
{
|
||||
id: 11,
|
||||
mtb_scale: '2',
|
||||
mtb_scale_uphill: null,
|
||||
mtb_scale_imba: null,
|
||||
sac_scale: null,
|
||||
sac_scale_unclear: null,
|
||||
surface: null,
|
||||
highway: null,
|
||||
},
|
||||
],
|
||||
unanswered: [12],
|
||||
});
|
||||
|
||||
@@ -22,7 +22,7 @@ export interface TrailGrades {
|
||||
export interface RouteWaysState {
|
||||
status: RouteWaysStatus;
|
||||
ways: RouteWays | null;
|
||||
/** BIK-05 and MAP-03, where asked for: the trail grades of its ways. */
|
||||
/** BIK-05, MAP-03 and MAP-04, where asked for: what OpenStreetMap records for its ways. */
|
||||
grades: TrailGrades | null;
|
||||
}
|
||||
|
||||
@@ -67,9 +67,10 @@ async function askRouteWays(
|
||||
}
|
||||
|
||||
/**
|
||||
* BIK-01 to BIK-05 and MAP-03: what the tour being planned runs on, asked of
|
||||
* the router once its route has settled, and where asked for the trail grades
|
||||
* of its ways (a mountain bike tour's, a hike's SAC grades). A new route makes
|
||||
* BIK-01 to BIK-05, MAP-03 and MAP-04: what the tour being planned runs on,
|
||||
* asked of the router once its route has settled, and where asked for what
|
||||
* OpenStreetMap records for its ways (a mountain bike tour's grades; a hike's
|
||||
* SAC grades, surfaces and kinds of way). A new route makes
|
||||
* the answer on its way stale and takes the map's highlight down; nothing of
|
||||
* it changes the tour.
|
||||
*/
|
||||
|
||||
@@ -0,0 +1,193 @@
|
||||
import { beforeEach, describe, expect, it, vi } from 'vitest';
|
||||
import { fireEvent, render, screen, within } from '../../../../tests/helpers/render';
|
||||
import type { FacetRow } from './hikeFacets';
|
||||
import { HikeWaysCard } from './HikeWaysCard';
|
||||
import type { HikeWays } from './useHikeWays';
|
||||
|
||||
const hook = vi.hoisted(() => ({ ways: null as unknown, input: null as unknown }));
|
||||
vi.mock('./useHikeWays', () => ({
|
||||
useHikeWays: (input: unknown) => {
|
||||
hook.input = input;
|
||||
return hook.ways;
|
||||
},
|
||||
}));
|
||||
|
||||
// FE-HIKE-WAYSCARD-001 to FE-HIKE-WAYSCARD-011
|
||||
|
||||
const LINE: [number, number][] = [
|
||||
[47.0, 11.0],
|
||||
[47.01, 11.0],
|
||||
];
|
||||
const SAC_ROWS: FacetRow[] = [
|
||||
{ key: 't2', lengthM: 1200, share: 0.4, tags: ['mountain_hiking'] },
|
||||
{ key: 't4', lengthM: 600, share: 0.2, tags: ['alpine_hiking'] },
|
||||
{ key: 'untagged', lengthM: 700, share: 0.2, tags: [] },
|
||||
{ key: 'unclear', lengthM: 300, share: 0.1, tags: ['T3', 'hiking;mountain_hiking'] },
|
||||
{ key: 'unmatched', lengthM: 100, share: 0.05, tags: [] },
|
||||
{ key: 'unasked', lengthM: 100, share: 0.05, tags: [] },
|
||||
];
|
||||
const SURFACE_ROWS: FacetRow[] = [
|
||||
{ key: 'gravel', lengthM: 1500, share: 0.5, tags: ['gravel', 'pebblestone'] },
|
||||
{ key: 'rock', lengthM: 600, share: 0.2, tags: ['rock'] },
|
||||
{ key: 'untagged', lengthM: 600, share: 0.2, tags: [] },
|
||||
{ key: 'unclear', lengthM: 300, share: 0.1, tags: ['gravel;ground'] },
|
||||
];
|
||||
const WAY_ROWS: FacetRow[] = [
|
||||
{ key: 'path', lengthM: 2000, share: 0.7, tags: ['path'] },
|
||||
{ key: 'track', lengthM: 900, share: 0.3, tags: ['track'] },
|
||||
];
|
||||
|
||||
function ways(overrides: Partial<HikeWays> = {}): HikeWays {
|
||||
return {
|
||||
available: true,
|
||||
shown: true,
|
||||
toggle: vi.fn(),
|
||||
facet: 'sac',
|
||||
chooseFacet: vi.fn(),
|
||||
status: 'ready',
|
||||
rows: SAC_ROWS,
|
||||
knownShare: 0.6,
|
||||
mostly: SAC_ROWS[0],
|
||||
highest: 4,
|
||||
focus: null,
|
||||
focusOn: vi.fn(),
|
||||
retry: vi.fn(),
|
||||
...overrides,
|
||||
};
|
||||
}
|
||||
|
||||
function show(state: HikeWays, routingLimit: number | null = 3) {
|
||||
hook.ways = state;
|
||||
return render(<HikeWaysCard tripId={4} route={LINE} routingLimit={routingLimit} />);
|
||||
}
|
||||
|
||||
async function tooltipOf(testId: string, text: string | RegExp) {
|
||||
fireEvent.mouseEnter(screen.getByTestId(testId));
|
||||
expect(await screen.findByText(text)).toBeInTheDocument();
|
||||
fireEvent.mouseLeave(screen.getByTestId(testId));
|
||||
}
|
||||
|
||||
beforeEach(() => localStorage.clear());
|
||||
|
||||
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 });
|
||||
});
|
||||
|
||||
it('FE-HIKE-WAYSCARD-002: switched off it only offers the switch, and the switch turns it on', () => {
|
||||
const state = ways({ shown: false, status: 'off', rows: [] });
|
||||
show(state);
|
||||
const toggle = screen.getByRole('button', { name: 'Show along the route' });
|
||||
expect(toggle).toHaveAttribute('aria-pressed', 'false');
|
||||
expect(screen.queryByRole('group', { name: 'What to show' })).toBeNull();
|
||||
expect(screen.queryByTestId('sac-row-t2')).toBeNull();
|
||||
fireEvent.click(toggle);
|
||||
expect(state.toggle).toHaveBeenCalledTimes(1);
|
||||
});
|
||||
|
||||
it('FE-HIKE-WAYSCARD-003: switched on it offers the SAC grade, the surface and the kind of way apart', () => {
|
||||
const state = ways();
|
||||
show(state);
|
||||
const facets = screen.getByRole('group', { name: 'What to show' });
|
||||
expect(within(facets).getByRole('button', { name: 'SAC' })).toHaveAttribute('aria-pressed', 'true');
|
||||
expect(within(facets).getByRole('button', { name: 'Surface' })).toHaveAttribute('aria-pressed', 'false');
|
||||
fireEvent.click(within(facets).getByRole('button', { name: 'Way type' }));
|
||||
expect(state.chooseFacet).toHaveBeenCalledWith('way');
|
||||
});
|
||||
|
||||
it('FE-HIKE-WAYSCARD-004: lists the known steps with their share, the unknown apart, and the routing limit as a setting', () => {
|
||||
show(ways());
|
||||
expect(within(screen.getByTestId('sac-row-t2')).getByText('T2')).toBeInTheDocument();
|
||||
expect(screen.getByTestId('sac-row-t2')).toHaveTextContent('Mountain hiking');
|
||||
expect(screen.getByTestId('sac-row-t2')).toHaveTextContent('40 %');
|
||||
expect(within(screen.getByTestId('sac-row-untagged')).getByText('?')).toBeInTheDocument();
|
||||
expect(screen.getByText('Unknown')).toBeInTheDocument();
|
||||
expect(screen.getByTestId('sac-known')).toHaveTextContent('60 %');
|
||||
expect(screen.getByTestId('sac-highest')).toHaveTextContent('T4');
|
||||
expect(screen.queryByTestId('sac-mostly')).toBeNull();
|
||||
expect(screen.getByTestId('sac-routing-limit')).toHaveTextContent('Routing limit');
|
||||
expect(screen.getByTestId('sac-routing-limit')).toHaveTextContent('T3');
|
||||
// Only a known step above the limit says so; the unknown never does.
|
||||
expect(within(screen.getByTestId('sac-row-t4')).getByText('Above the limit')).toBeInTheDocument();
|
||||
expect(within(screen.getByTestId('sac-row-t2')).queryByText('Above the limit')).toBeNull();
|
||||
expect(within(screen.getByTestId('sac-row-untagged')).queryByText('Above the limit')).toBeNull();
|
||||
});
|
||||
|
||||
it('FE-HIKE-WAYSCARD-005: an import, planned without a routing limit, shows none and nothing above it', () => {
|
||||
show(ways({ highest: null, rows: SAC_ROWS.slice(2), knownShare: 0, mostly: null }), null);
|
||||
expect(screen.queryByTestId('sac-routing-limit')).toBeNull();
|
||||
expect(screen.queryByTestId('sac-highest')).toBeNull();
|
||||
expect(screen.queryByText('Above the limit')).toBeNull();
|
||||
expect(screen.getByTestId('sac-known')).toHaveTextContent('0 %');
|
||||
});
|
||||
|
||||
it('FE-HIKE-WAYSCARD-006: a row draws its class alone on the map, and shows that it does', () => {
|
||||
const state = ways({ focus: 't4' });
|
||||
show(state);
|
||||
expect(screen.getByTestId('sac-row-t4')).toHaveAttribute('aria-pressed', 'true');
|
||||
expect(screen.getByTestId('sac-row-t2')).toHaveAttribute('aria-pressed', 'false');
|
||||
fireEvent.click(screen.getByTestId('sac-row-t2'));
|
||||
expect(state.focusOn).toHaveBeenCalledWith('t2');
|
||||
});
|
||||
|
||||
it('FE-HIKE-WAYSCARD-007: says when it is asking, offline, or failed with a way to ask again', () => {
|
||||
const loading = show(ways({ status: 'loading', rows: [] }));
|
||||
expect(screen.getByRole('status')).toHaveTextContent('Reading the ways along the hike');
|
||||
loading.unmount();
|
||||
const offline = show(ways({ status: 'offline', rows: [] }));
|
||||
expect(screen.getByText('The trail details need a connection.')).toBeInTheDocument();
|
||||
expect(screen.queryByRole('button', { name: /try again/i })).toBeNull();
|
||||
offline.unmount();
|
||||
const state = ways({ status: 'failed', rows: [] });
|
||||
show(state);
|
||||
expect(screen.getByText(/The tour is unchanged/)).toBeInTheDocument();
|
||||
fireEvent.click(screen.getByRole('button', { name: /try again/i }));
|
||||
expect(state.retry).toHaveBeenCalledTimes(1);
|
||||
});
|
||||
|
||||
it('FE-HIKE-WAYSCARD-008: an SAC row names its scale and its source, an unclear one what OpenStreetMap wrote', async () => {
|
||||
show(ways());
|
||||
await tooltipOf('sac-row-t2', 'SAC hiking scale T2, from OpenStreetMap: sac_scale=mountain_hiking');
|
||||
await tooltipOf('sac-row-unclear', /T3, hiking;mountain_hiking/);
|
||||
await tooltipOf('sac-row-untagged', /Unknown does not mean easy/);
|
||||
await tooltipOf('sac-row-unmatched', /no way lies clearly under the line/i);
|
||||
// A way that could not be asked about says so in its row; there is nothing more to name.
|
||||
expect(screen.getByTestId('sac-row-unasked')).toHaveTextContent('Could not be asked this time');
|
||||
});
|
||||
|
||||
it('FE-HIKE-WAYSCARD-009: the surfaces list their groups, the most common one, and no grade or routing limit', () => {
|
||||
show(ways({ facet: 'surface', rows: SURFACE_ROWS, knownShare: 0.7, mostly: SURFACE_ROWS[0], highest: null }));
|
||||
expect(screen.getByTestId('surface-row-gravel')).toHaveTextContent('Gravel');
|
||||
expect(screen.getByTestId('surface-row-gravel')).toHaveTextContent('50 %');
|
||||
// A known group carries its colour only; the question mark is the unknown's.
|
||||
expect(within(screen.getByTestId('surface-row-gravel')).queryByText('?')).toBeNull();
|
||||
expect(within(screen.getByTestId('surface-row-untagged')).getByText('?')).toBeInTheDocument();
|
||||
expect(screen.getByTestId('surface-row-untagged')).toHaveTextContent('No surface in OpenStreetMap');
|
||||
expect(screen.getByTestId('surface-known')).toHaveTextContent('70 %');
|
||||
expect(screen.getByTestId('surface-mostly')).toHaveTextContent('Gravel');
|
||||
expect(screen.queryByTestId('sac-routing-limit')).toBeNull();
|
||||
expect(screen.queryByTestId('sac-highest')).toBeNull();
|
||||
expect(screen.queryByText('Above the limit')).toBeNull();
|
||||
expect(screen.getByText(/router's own reading is not used/)).toBeInTheDocument();
|
||||
});
|
||||
|
||||
it('FE-HIKE-WAYSCARD-010: a surface row names the values OpenStreetMap recorded, the untagged one that nothing was', async () => {
|
||||
show(ways({ facet: 'surface', rows: SURFACE_ROWS, knownShare: 0.7, mostly: SURFACE_ROWS[0], highest: null }));
|
||||
await tooltipOf('surface-row-gravel', 'Recorded in OpenStreetMap as surface=gravel, surface=pebblestone');
|
||||
await tooltipOf('surface-row-unclear', /surface=gravel;ground/);
|
||||
await tooltipOf('surface-row-untagged', /A router may assume one/);
|
||||
});
|
||||
|
||||
it('FE-HIKE-WAYSCARD-011: the kinds of way read under their own names and source', async () => {
|
||||
const state = ways({ facet: 'way', rows: WAY_ROWS, knownShare: 1, mostly: WAY_ROWS[0], highest: null });
|
||||
show(state);
|
||||
expect(screen.getByTestId('way-row-track')).toHaveTextContent('Farm or forest track');
|
||||
expect(screen.getByTestId('way-mostly')).toHaveTextContent('Path');
|
||||
expect(screen.queryByText('Unknown')).toBeNull();
|
||||
await tooltipOf('way-row-path', 'Recorded in OpenStreetMap as highway=path');
|
||||
fireEvent.click(screen.getByTestId('way-row-track'));
|
||||
expect(state.focusOn).toHaveBeenCalledWith('track');
|
||||
});
|
||||
});
|
||||
@@ -0,0 +1,78 @@
|
||||
import { Footprints, Layers, MountainSnow, Signpost } from 'lucide-react';
|
||||
import { useTranslation } from '../../../i18n';
|
||||
import ToggleSwitch from '../../Settings/ToggleSwitch';
|
||||
import { Segmented, type SegmentedOption } from '../../shared/dialogParts';
|
||||
import { fs } from '../../shared/DialogShell';
|
||||
import { ReadStatusNotice } from '../ReadStatusNotice';
|
||||
import { RailCard } from '../tourParts';
|
||||
import type { HikeFacet } from './hikeFacetSpecs';
|
||||
import { HikeWaysList } from './HikeWaysList';
|
||||
import { useHikeWays } from './useHikeWays';
|
||||
|
||||
const ICON = { size: 13, strokeWidth: 2 } as const;
|
||||
|
||||
/**
|
||||
* MAP-03 and MAP-04: what OpenStreetMap records for the ways along a hike, on
|
||||
* the traveller's word. Switched on, the ways under the line are read once
|
||||
* and drawn along it on the map by the facet picked: the SAC grade, the
|
||||
* 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.
|
||||
*/
|
||||
export function HikeWaysCard({
|
||||
tripId,
|
||||
route,
|
||||
routeCurrent = true,
|
||||
routingLimit,
|
||||
}: {
|
||||
tripId: number | string;
|
||||
/** The tour's own line, or null while there is none. */
|
||||
route: [number, number][] | null;
|
||||
/** False while the line is being planned anew. */
|
||||
routeCurrent?: boolean;
|
||||
/** The routing limit the hike is planned with, or null where it was not routed (an import). */
|
||||
routingLimit: number | null;
|
||||
}) {
|
||||
const { t } = useTranslation();
|
||||
const ways = useHikeWays({ tripId, route, current: routeCurrent });
|
||||
if (!ways.available) return null;
|
||||
const facets: SegmentedOption<HikeFacet>[] = [
|
||||
{ value: 'sac', label: t('tours.hike.ways.facet.sac'), icon: <MountainSnow {...ICON} /> },
|
||||
{ value: 'surface', label: t('tours.hike.ways.facet.surface'), icon: <Layers {...ICON} /> },
|
||||
{ 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">
|
||||
<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')}>
|
||||
{t('tours.hike.ways.show')}
|
||||
</span>
|
||||
<ToggleSwitch on={ways.shown} onToggle={ways.toggle} label={t('tours.hike.ways.show')} />
|
||||
</div>
|
||||
{ways.shown && (
|
||||
<>
|
||||
<Segmented<HikeFacet>
|
||||
value={ways.facet}
|
||||
options={facets}
|
||||
onChange={ways.chooseFacet}
|
||||
label={t('tours.hike.ways.facets')}
|
||||
fill
|
||||
/>
|
||||
<ReadStatusNotice
|
||||
status={ways.status}
|
||||
messages={{
|
||||
loading: t('tours.hike.ways.loading'),
|
||||
offline: t('tours.hike.ways.offline'),
|
||||
failed: t('tours.hike.ways.failed'),
|
||||
}}
|
||||
onRetry={ways.retry}
|
||||
/>
|
||||
</>
|
||||
)}
|
||||
{ways.shown && ways.status === 'ready' && <HikeWaysList ways={ways} limit={routingLimit} />}
|
||||
</div>
|
||||
</RailCard>
|
||||
);
|
||||
}
|
||||
@@ -0,0 +1,222 @@
|
||||
import { Info, SlidersHorizontal } from 'lucide-react';
|
||||
import { useTranslation } from '../../../i18n';
|
||||
import { useSettingsStore } from '../../../store/settingsStore';
|
||||
import { formatDistance } from '../../../utils/units';
|
||||
import { SoftPill } from '../../Planner/planParts';
|
||||
import { fs } from '../../shared/DialogShell';
|
||||
import { Tooltip } from '../../shared/Tooltip';
|
||||
import { bikePaintCss } from '../bike/bikePaint';
|
||||
import { FigureBadge } from '../paddle/FigureBadge';
|
||||
import { HIKE_FACETS, type HikeFacet } from './hikeFacetSpecs';
|
||||
import { facetPaint, isUnknownClass, type FacetRow } from './hikeFacets';
|
||||
import { hikePaintCss } from './hikePaint';
|
||||
import { sacStepOfClass } from './sacGrades';
|
||||
import type { HikeWays } from './useHikeWays';
|
||||
|
||||
const percent = (share: number) => `${Math.round(share * 100)} %`;
|
||||
const stepName = (step: number) => `T${step}`;
|
||||
|
||||
const sacKey = (name: string) => `tours.hike.sac.${name}`;
|
||||
const surfaceKey = (name: string) => `tours.hike.surface.${name}`;
|
||||
const wayKey = (name: string) => `tours.hike.way.${name}`;
|
||||
const sharedKey = (name: string) => `tours.hike.ways.${name}`;
|
||||
|
||||
/** A facet's own texts; a way not matched or not asked reads the same in every facet. */
|
||||
const FACET_KEY: Readonly<Record<HikeFacet, (name: string) => string>> = {
|
||||
sac: sacKey,
|
||||
surface: surfaceKey,
|
||||
way: wayKey,
|
||||
};
|
||||
|
||||
/** The OpenStreetMap key a facet's values are written under, named in the tooltips. */
|
||||
const FACET_TAG: Readonly<Record<HikeFacet, string>> = { sac: 'sac_scale', surface: 'surface', way: 'highway' };
|
||||
|
||||
function labelKey(facet: HikeFacet, key: string): string {
|
||||
return key === 'unmatched' || key === 'unasked' ? sharedKey(key) : FACET_KEY[facet](key);
|
||||
}
|
||||
|
||||
/** The colour a class is drawn in on the map, as a CSS variable: its own, the grey of the unknown otherwise. */
|
||||
function paintOf(facet: HikeFacet, key: string): string {
|
||||
const paint = facetPaint(HIKE_FACETS[facet], key);
|
||||
return paint === 'unknown' ? bikePaintCss('unknown') : hikePaintCss(paint);
|
||||
}
|
||||
|
||||
/** What OpenStreetMap wrote for a row, as `key=value`, each once. */
|
||||
const writtenTags = (facet: HikeFacet, row: FacetRow) => row.tags.map((tag) => `${FACET_TAG[facet]}=${tag}`).join(', ');
|
||||
|
||||
/**
|
||||
* A class as a small white chip: a dot in its map colour, with the step of an
|
||||
* SAC grade, or a question mark for the unknown.
|
||||
*/
|
||||
function ClassChip({ facet, row }: { facet: HikeFacet; row: FacetRow }) {
|
||||
const step = sacStepOfClass(row.key);
|
||||
const unknown = isUnknownClass(row.key);
|
||||
return (
|
||||
<span className="inline-flex h-[18px] w-[34px] flex-none items-center justify-center gap-1 rounded-full bg-surface-card font-geist font-semibold tabular-nums text-content shadow-sm ring-1 ring-inset ring-edge-faint">
|
||||
<span
|
||||
aria-hidden="true"
|
||||
className="h-2 w-2 flex-none rounded-full"
|
||||
style={{ background: paintOf(facet, row.key) }}
|
||||
/>
|
||||
{(step !== null || unknown) && (
|
||||
<span className={unknown ? 'text-content-faint' : ''} style={fs(10.5)}>
|
||||
{step === null ? '?' : stepName(step)}
|
||||
</span>
|
||||
)}
|
||||
</span>
|
||||
);
|
||||
}
|
||||
|
||||
/** Where a row's class comes from, said in its tooltip: what OpenStreetMap wrote, or why it is unknown. */
|
||||
function useRowSource(facet: HikeFacet, row: FacetRow): string {
|
||||
const { t } = useTranslation();
|
||||
const step = sacStepOfClass(row.key);
|
||||
if (step !== null) return t('tours.hike.sac.stepTip', { step: stepName(step), tag: row.tags[0] ?? '' });
|
||||
if (row.key === 'untagged') return t(FACET_KEY[facet]('untaggedTip'));
|
||||
if (row.key === 'unmatched') return t('tours.hike.ways.unmatchedTip');
|
||||
if (row.key === 'unasked') return '';
|
||||
if (row.key === 'unclear' && facet === 'sac') return t('tours.hike.sac.unclearTip', { tags: row.tags.join(', ') });
|
||||
if (row.key === 'unclear') return t('tours.hike.ways.unclearTip', { tags: writtenTags(facet, row) });
|
||||
return t('tours.hike.ways.recordedTip', { tags: writtenTags(facet, row) });
|
||||
}
|
||||
|
||||
/**
|
||||
* One class along the hike: its chip, its name, how far and how much of the
|
||||
* route it covers. Pressed, the map draws its stretches alone; pressed again,
|
||||
* every class again.
|
||||
*/
|
||||
function ClassRow({ row, ways, limit }: { row: FacetRow; ways: HikeWays; limit: number | null }) {
|
||||
const { t } = useTranslation();
|
||||
const unit = useSettingsStore((state) => state.settings.distance_unit) ?? 'metric';
|
||||
const source = useRowSource(ways.facet, row);
|
||||
const step = sacStepOfClass(row.key);
|
||||
const pressed = ways.focus === row.key;
|
||||
return (
|
||||
<li>
|
||||
<Tooltip label={source} disabled={!source} placement="top">
|
||||
<button
|
||||
type="button"
|
||||
aria-pressed={pressed}
|
||||
data-testid={`${ways.facet}-row-${row.key}`}
|
||||
onClick={() => ways.focusOn(row.key)}
|
||||
className={`flex w-full min-w-0 items-center gap-2 rounded-lg px-1.5 py-1 text-start transition-colors hover:bg-surface-tertiary focus-visible:outline focus-visible:outline-2 focus-visible:outline-accent ${pressed ? 'bg-surface-tertiary' : ''}`}
|
||||
>
|
||||
<ClassChip facet={ways.facet} row={row} />
|
||||
<span
|
||||
className={`min-w-0 flex-1 truncate ${isUnknownClass(row.key) ? 'text-content-muted' : 'text-content'}`}
|
||||
style={fs(12, 'body')}
|
||||
>
|
||||
{t(labelKey(ways.facet, row.key))}
|
||||
</span>
|
||||
{step !== null && limit !== null && step > limit && (
|
||||
<SoftPill tone="warning">{t('tours.hike.sac.aboveLimit')}</SoftPill>
|
||||
)}
|
||||
<span className="flex-none font-geist tabular-nums text-content-faint" style={fs(11)}>
|
||||
{formatDistance(row.lengthM / 1000, unit)}
|
||||
</span>
|
||||
<span className="w-10 flex-none text-end font-geist tabular-nums text-content-secondary" style={fs(11)}>
|
||||
{percent(row.share)}
|
||||
</span>
|
||||
</button>
|
||||
</Tooltip>
|
||||
</li>
|
||||
);
|
||||
}
|
||||
|
||||
/** The classes as one bar along the route's length, in the map's colours, so the mix reads at a glance. */
|
||||
function ClassBar({ ways }: { ways: HikeWays }) {
|
||||
return (
|
||||
<span aria-hidden="true" className="flex h-2 w-full overflow-hidden rounded-full bg-surface-tertiary">
|
||||
{ways.rows.map((row) => (
|
||||
<span
|
||||
key={row.key}
|
||||
className="h-full"
|
||||
style={{ width: `${row.share * 100}%`, background: paintOf(ways.facet, row.key) }}
|
||||
/>
|
||||
))}
|
||||
</span>
|
||||
);
|
||||
}
|
||||
|
||||
/**
|
||||
* What is known at a glance: how much of the route the facet knows, and the
|
||||
* class covering the most of it, or for the SAC grades the highest step and,
|
||||
* apart from it and marked as a setting, the routing limit the hike was
|
||||
* planned with, which says nothing about the ways themselves.
|
||||
*/
|
||||
function FacetSummary({ ways, limit }: { ways: HikeWays; limit: number | null }) {
|
||||
const { t } = useTranslation();
|
||||
const { facet } = ways;
|
||||
return (
|
||||
<div className="flex flex-wrap items-center gap-1.5">
|
||||
<FigureBadge
|
||||
label={t('tours.hike.ways.known')}
|
||||
value={percent(ways.knownShare)}
|
||||
info={t(FACET_KEY[facet]('knownTip'))}
|
||||
testId={`${facet}-known`}
|
||||
/>
|
||||
{facet !== 'sac' && ways.mostly && (
|
||||
<FigureBadge
|
||||
label={t('tours.hike.ways.mostly')}
|
||||
value={t(labelKey(facet, ways.mostly.key))}
|
||||
testId={`${facet}-mostly`}
|
||||
/>
|
||||
)}
|
||||
{ways.highest !== null && (
|
||||
<FigureBadge label={t('tours.hike.sac.highest')} value={stepName(ways.highest)} testId="sac-highest" />
|
||||
)}
|
||||
{facet === 'sac' && limit !== null && (
|
||||
<FigureBadge
|
||||
label={t('tours.hike.sac.routingLimit')}
|
||||
value={stepName(limit)}
|
||||
icon={<SlidersHorizontal size={10} strokeWidth={2.2} className="flex-none text-content-faint" />}
|
||||
info={t('tours.hike.sac.routingLimitTip')}
|
||||
testId="sac-routing-limit"
|
||||
/>
|
||||
)}
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
||||
/**
|
||||
* MAP-03 and MAP-04: one facet of the hike laid out: the summary, the mix as
|
||||
* a bar, the known classes, the unknown apart under their own heading, and
|
||||
* where the values come from.
|
||||
*/
|
||||
export function HikeWaysList({ ways, limit }: { ways: HikeWays; limit: number | null }) {
|
||||
const { t } = useTranslation();
|
||||
const known = ways.rows.filter((row) => !isUnknownClass(row.key));
|
||||
const unknown = ways.rows.filter((row) => isUnknownClass(row.key));
|
||||
return (
|
||||
<>
|
||||
<FacetSummary ways={ways} limit={limit} />
|
||||
<ClassBar ways={ways} />
|
||||
{known.length > 0 && (
|
||||
<ul className="m-0 flex list-none flex-col p-0">
|
||||
{known.map((row) => (
|
||||
<ClassRow key={row.key} row={row} ways={ways} limit={limit} />
|
||||
))}
|
||||
</ul>
|
||||
)}
|
||||
{unknown.length > 0 && (
|
||||
<div className="flex min-w-0 flex-col gap-1">
|
||||
<span
|
||||
className="font-geist font-bold uppercase tracking-[.08em] text-content-faint"
|
||||
style={fs(9.5, 'caption')}
|
||||
>
|
||||
{t('tours.hike.ways.unknown')}
|
||||
</span>
|
||||
<ul className="m-0 flex list-none flex-col p-0">
|
||||
{unknown.map((row) => (
|
||||
<ClassRow key={row.key} row={row} ways={ways} limit={limit} />
|
||||
))}
|
||||
</ul>
|
||||
</div>
|
||||
)}
|
||||
<span className="inline-flex gap-1.5 text-content-faint" style={fs(11, 'body')}>
|
||||
<Info size={12} strokeWidth={2} className="mt-0.5 flex-none" />
|
||||
{t(FACET_KEY[ways.facet]('source'))}
|
||||
</span>
|
||||
</>
|
||||
);
|
||||
}
|
||||
@@ -1,135 +0,0 @@
|
||||
import { beforeEach, describe, expect, it, vi } from 'vitest';
|
||||
import { fireEvent, render, screen, within } from '../../../../tests/helpers/render';
|
||||
import { SacGradesCard } from './SacGradesCard';
|
||||
import type { SacGradeRow, SacGrades } from './useSacGrades';
|
||||
|
||||
const hook = vi.hoisted(() => ({ grades: null as unknown, input: null as unknown }));
|
||||
vi.mock('./useSacGrades', () => ({
|
||||
useSacGrades: (input: unknown) => {
|
||||
hook.input = input;
|
||||
return hook.grades;
|
||||
},
|
||||
}));
|
||||
|
||||
// FE-HIKE-SACCARD-001 to FE-HIKE-SACCARD-007
|
||||
|
||||
const LINE: [number, number][] = [
|
||||
[47.0, 11.0],
|
||||
[47.01, 11.0],
|
||||
];
|
||||
const ROWS: SacGradeRow[] = [
|
||||
{ key: 't2', lengthM: 1200, share: 0.4, tags: ['mountain_hiking'] },
|
||||
{ key: 't4', lengthM: 600, share: 0.2, tags: ['alpine_hiking'] },
|
||||
{ key: 'unclear', lengthM: 300, share: 0.1, tags: ['T3', 'hiking;mountain_hiking'] },
|
||||
{ key: 'untagged', lengthM: 700, share: 0.2, tags: [] },
|
||||
{ key: 'unmatched', lengthM: 100, share: 0.05, tags: [] },
|
||||
{ key: 'unasked', lengthM: 100, share: 0.05, tags: [] },
|
||||
];
|
||||
|
||||
function grades(overrides: Partial<SacGrades> = {}): SacGrades {
|
||||
return {
|
||||
available: true,
|
||||
shown: true,
|
||||
toggle: vi.fn(),
|
||||
status: 'ready',
|
||||
rows: ROWS,
|
||||
knownShare: 0.6,
|
||||
highest: 4,
|
||||
focus: null,
|
||||
focusOn: vi.fn(),
|
||||
retry: vi.fn(),
|
||||
...overrides,
|
||||
};
|
||||
}
|
||||
|
||||
function show(state: SacGrades, routingLimit: number | null = 3) {
|
||||
hook.grades = state;
|
||||
return render(<SacGradesCard tripId={4} route={LINE} routingLimit={routingLimit} />);
|
||||
}
|
||||
|
||||
beforeEach(() => localStorage.clear());
|
||||
|
||||
describe('SacGradesCard (MAP-03)', () => {
|
||||
it('FE-HIKE-SACCARD-001: without a line to grade there is no card', () => {
|
||||
show(grades({ available: false, shown: false, status: 'off', rows: [] }));
|
||||
expect(screen.queryByTestId('sac-grades-card')).toBeNull();
|
||||
expect(hook.input).toEqual({ tripId: 4, route: LINE, current: true });
|
||||
});
|
||||
|
||||
it('FE-HIKE-SACCARD-002: switched off it only offers the switch, and the switch turns it on', () => {
|
||||
const state = grades({ shown: false, status: 'off', rows: [] });
|
||||
show(state);
|
||||
const toggle = screen.getByRole('button', { name: 'Show along the route' });
|
||||
expect(toggle).toHaveAttribute('aria-pressed', 'false');
|
||||
expect(screen.queryByTestId('sac-row-t2')).toBeNull();
|
||||
fireEvent.click(toggle);
|
||||
expect(state.toggle).toHaveBeenCalledTimes(1);
|
||||
});
|
||||
|
||||
it('FE-HIKE-SACCARD-003: lists the known steps with their share, the unknown apart, and the routing limit as a setting', () => {
|
||||
show(grades());
|
||||
expect(within(screen.getByTestId('sac-row-t2')).getByText('T2')).toBeInTheDocument();
|
||||
expect(screen.getByTestId('sac-row-t2')).toHaveTextContent('Mountain hiking');
|
||||
expect(screen.getByTestId('sac-row-t2')).toHaveTextContent('40 %');
|
||||
expect(within(screen.getByTestId('sac-row-untagged')).getByText('?')).toBeInTheDocument();
|
||||
expect(screen.getByText('Unknown')).toBeInTheDocument();
|
||||
expect(screen.getByTestId('sac-known')).toHaveTextContent('60 %');
|
||||
expect(screen.getByTestId('sac-highest')).toHaveTextContent('T4');
|
||||
expect(screen.getByTestId('sac-routing-limit')).toHaveTextContent('Routing limit');
|
||||
expect(screen.getByTestId('sac-routing-limit')).toHaveTextContent('T3');
|
||||
// Only a known step above the limit says so; the unknown never does.
|
||||
expect(within(screen.getByTestId('sac-row-t4')).getByText('Above the limit')).toBeInTheDocument();
|
||||
expect(within(screen.getByTestId('sac-row-t2')).queryByText('Above the limit')).toBeNull();
|
||||
expect(within(screen.getByTestId('sac-row-untagged')).queryByText('Above the limit')).toBeNull();
|
||||
});
|
||||
|
||||
it('FE-HIKE-SACCARD-004: an import, planned without a routing limit, shows none and nothing above it', () => {
|
||||
show(grades({ highest: null, rows: ROWS.slice(2), knownShare: 0 }), null);
|
||||
expect(screen.queryByTestId('sac-routing-limit')).toBeNull();
|
||||
expect(screen.queryByTestId('sac-highest')).toBeNull();
|
||||
expect(screen.queryByText('Above the limit')).toBeNull();
|
||||
expect(screen.getByTestId('sac-known')).toHaveTextContent('0 %');
|
||||
});
|
||||
|
||||
it('FE-HIKE-SACCARD-005: a row draws its class alone on the map, and shows that it does', () => {
|
||||
const state = grades({ focus: 't4' });
|
||||
show(state);
|
||||
expect(screen.getByTestId('sac-row-t4')).toHaveAttribute('aria-pressed', 'true');
|
||||
expect(screen.getByTestId('sac-row-t2')).toHaveAttribute('aria-pressed', 'false');
|
||||
fireEvent.click(screen.getByTestId('sac-row-t2'));
|
||||
expect(state.focusOn).toHaveBeenCalledWith('t2');
|
||||
});
|
||||
|
||||
it('FE-HIKE-SACCARD-006: says when it is asking, offline, or failed with a way to ask again', () => {
|
||||
const loading = show(grades({ status: 'loading', rows: [] }));
|
||||
expect(screen.getByRole('status')).toHaveTextContent('Reading the SAC grades');
|
||||
loading.unmount();
|
||||
const offline = show(grades({ status: 'offline', rows: [] }));
|
||||
expect(screen.getByText('The SAC grades need a connection.')).toBeInTheDocument();
|
||||
expect(screen.queryByRole('button', { name: /try again/i })).toBeNull();
|
||||
offline.unmount();
|
||||
const state = grades({ status: 'failed', rows: [] });
|
||||
show(state);
|
||||
expect(screen.getByText(/The tour is unchanged/)).toBeInTheDocument();
|
||||
fireEvent.click(screen.getByRole('button', { name: /try again/i }));
|
||||
expect(state.retry).toHaveBeenCalledTimes(1);
|
||||
});
|
||||
|
||||
it('FE-HIKE-SACCARD-007: a row names its scale and its source, an unclear one what OpenStreetMap wrote', async () => {
|
||||
show(grades());
|
||||
fireEvent.mouseEnter(screen.getByTestId('sac-row-t2'));
|
||||
const source = 'SAC hiking scale T2, from OpenStreetMap: sac_scale=mountain_hiking';
|
||||
expect(await screen.findByText(source)).toBeInTheDocument();
|
||||
fireEvent.mouseLeave(screen.getByTestId('sac-row-t2'));
|
||||
fireEvent.mouseEnter(screen.getByTestId('sac-row-unclear'));
|
||||
expect(await screen.findByText(/T3, hiking;mountain_hiking/)).toBeInTheDocument();
|
||||
fireEvent.mouseLeave(screen.getByTestId('sac-row-unclear'));
|
||||
fireEvent.mouseEnter(screen.getByTestId('sac-row-untagged'));
|
||||
expect(await screen.findByText(/Unknown does not mean easy/)).toBeInTheDocument();
|
||||
fireEvent.mouseLeave(screen.getByTestId('sac-row-untagged'));
|
||||
fireEvent.mouseEnter(screen.getByTestId('sac-row-unmatched'));
|
||||
expect(await screen.findByText(/no way lies clearly under the line/i)).toBeInTheDocument();
|
||||
// A way that could not be asked about says so in its row; there is nothing more to name.
|
||||
expect(screen.getByTestId('sac-row-unasked')).toHaveTextContent('Could not be asked this time');
|
||||
});
|
||||
});
|
||||
@@ -1,218 +0,0 @@
|
||||
import { Info, MountainSnow, SlidersHorizontal } from 'lucide-react';
|
||||
import { useTranslation } from '../../../i18n';
|
||||
import { useSettingsStore } from '../../../store/settingsStore';
|
||||
import { formatDistance } from '../../../utils/units';
|
||||
import { SoftPill } from '../../Planner/planParts';
|
||||
import ToggleSwitch from '../../Settings/ToggleSwitch';
|
||||
import { fs } from '../../shared/DialogShell';
|
||||
import { Tooltip } from '../../shared/Tooltip';
|
||||
import { bikePaintCss } from '../bike/bikePaint';
|
||||
import { FigureBadge } from '../paddle/FigureBadge';
|
||||
import { ReadStatusNotice } from '../ReadStatusNotice';
|
||||
import { RailCard } from '../tourParts';
|
||||
import { hikePaintCss } from './hikePaint';
|
||||
import { isSacStep, sacStepOfClass, type SacClass } from './sacGrades';
|
||||
import { useSacGrades, type SacGradeRow, type SacGrades } from './useSacGrades';
|
||||
|
||||
const percent = (share: number) => `${Math.round(share * 100)} %`;
|
||||
const stepName = (step: number) => `T${step}`;
|
||||
|
||||
/** The colour a class is drawn in on the map, as a CSS variable: the step's own, the grey of the unknown otherwise. */
|
||||
const paintOf = (key: SacClass) => (isSacStep(key) ? hikePaintCss(key) : bikePaintCss('unknown'));
|
||||
|
||||
/** A grade as a small white chip: a dot in its map colour and the step, or a question mark for the unknown. */
|
||||
function GradeChip({ grade }: { grade: SacClass }) {
|
||||
const step = sacStepOfClass(grade);
|
||||
return (
|
||||
<span className="inline-flex h-[18px] w-[34px] flex-none items-center justify-center gap-1 rounded-full bg-surface-card font-geist font-semibold tabular-nums text-content shadow-sm ring-1 ring-inset ring-edge-faint">
|
||||
<span aria-hidden="true" className="h-2 w-2 flex-none rounded-full" style={{ background: paintOf(grade) }} />
|
||||
<span className={step === null ? 'text-content-faint' : ''} style={fs(10.5)}>
|
||||
{step === null ? '?' : stepName(step)}
|
||||
</span>
|
||||
</span>
|
||||
);
|
||||
}
|
||||
|
||||
/** Where a row's grade comes from, said in its tooltip: the scale and the tag, or why it is unknown. */
|
||||
function useRowSource(row: SacGradeRow): string {
|
||||
const { t } = useTranslation();
|
||||
const step = sacStepOfClass(row.key);
|
||||
if (step !== null) return t('tours.hike.sac.stepTip', { step: stepName(step), tag: row.tags[0] ?? '' });
|
||||
if (row.key === 'untagged') return t('tours.hike.sac.untaggedTip');
|
||||
if (row.key === 'unclear') return t('tours.hike.sac.unclearTip', { tags: row.tags.join(', ') });
|
||||
if (row.key === 'unmatched') return t('tours.hike.sac.unmatchedTip');
|
||||
return '';
|
||||
}
|
||||
|
||||
/**
|
||||
* One class of the hike: its chip, its name, how far and how much of the
|
||||
* route it covers. Pressed, the map draws its stretches alone; pressed again,
|
||||
* every grade again.
|
||||
*/
|
||||
function GradeRow({ row, grades, limit }: { row: SacGradeRow; grades: SacGrades; limit: number | null }) {
|
||||
const { t } = useTranslation();
|
||||
const unit = useSettingsStore((state) => state.settings.distance_unit) ?? 'metric';
|
||||
const source = useRowSource(row);
|
||||
const step = sacStepOfClass(row.key);
|
||||
const pressed = grades.focus === row.key;
|
||||
return (
|
||||
<li>
|
||||
<Tooltip label={source} disabled={!source} placement="top">
|
||||
<button
|
||||
type="button"
|
||||
aria-pressed={pressed}
|
||||
data-testid={`sac-row-${row.key}`}
|
||||
onClick={() => grades.focusOn(row.key)}
|
||||
className={`flex w-full min-w-0 items-center gap-2 rounded-lg px-1.5 py-1 text-start transition-colors hover:bg-surface-tertiary focus-visible:outline focus-visible:outline-2 focus-visible:outline-accent ${pressed ? 'bg-surface-tertiary' : ''}`}
|
||||
>
|
||||
<GradeChip grade={row.key} />
|
||||
<span
|
||||
className={`min-w-0 flex-1 truncate ${step === null ? 'text-content-muted' : 'text-content'}`}
|
||||
style={fs(12, 'body')}
|
||||
>
|
||||
{t(`tours.hike.sac.${row.key}`)}
|
||||
</span>
|
||||
{step !== null && limit !== null && step > limit && (
|
||||
<SoftPill tone="warning">{t('tours.hike.sac.aboveLimit')}</SoftPill>
|
||||
)}
|
||||
<span className="flex-none font-geist tabular-nums text-content-faint" style={fs(11)}>
|
||||
{formatDistance(row.lengthM / 1000, unit)}
|
||||
</span>
|
||||
<span className="w-10 flex-none text-end font-geist tabular-nums text-content-secondary" style={fs(11)}>
|
||||
{percent(row.share)}
|
||||
</span>
|
||||
</button>
|
||||
</Tooltip>
|
||||
</li>
|
||||
);
|
||||
}
|
||||
|
||||
/** The classes as one bar along the route's length, in the map's colours, so the mix reads at a glance. */
|
||||
function GradeBar({ rows }: { rows: SacGradeRow[] }) {
|
||||
return (
|
||||
<span aria-hidden="true" className="flex h-2 w-full overflow-hidden rounded-full bg-surface-tertiary">
|
||||
{rows.map((row) => (
|
||||
<span key={row.key} className="h-full" style={{ width: `${row.share * 100}%`, background: paintOf(row.key) }} />
|
||||
))}
|
||||
</span>
|
||||
);
|
||||
}
|
||||
|
||||
/**
|
||||
* What is known at a glance: how much of the route has a grade, the highest
|
||||
* one, and apart from them, marked as a setting, the routing limit the hike
|
||||
* was planned with, which says nothing about the ways themselves.
|
||||
*/
|
||||
function GradeSummary({ grades, limit }: { grades: SacGrades; limit: number | null }) {
|
||||
const { t } = useTranslation();
|
||||
return (
|
||||
<div className="flex flex-wrap items-center gap-1.5">
|
||||
<FigureBadge
|
||||
label={t('tours.hike.sac.known')}
|
||||
value={percent(grades.knownShare)}
|
||||
info={t('tours.hike.sac.knownTip')}
|
||||
testId="sac-known"
|
||||
/>
|
||||
{grades.highest !== null && (
|
||||
<FigureBadge label={t('tours.hike.sac.highest')} value={stepName(grades.highest)} testId="sac-highest" />
|
||||
)}
|
||||
{limit !== null && (
|
||||
<FigureBadge
|
||||
label={t('tours.hike.sac.routingLimit')}
|
||||
value={stepName(limit)}
|
||||
icon={<SlidersHorizontal size={10} strokeWidth={2.2} className="flex-none text-content-faint" />}
|
||||
info={t('tours.hike.sac.routingLimitTip')}
|
||||
testId="sac-routing-limit"
|
||||
/>
|
||||
)}
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
||||
function GradeList({ grades, limit }: { grades: SacGrades; limit: number | null }) {
|
||||
const { t } = useTranslation();
|
||||
const known = grades.rows.filter((row) => isSacStep(row.key));
|
||||
const unknown = grades.rows.filter((row) => !isSacStep(row.key));
|
||||
return (
|
||||
<>
|
||||
<GradeSummary grades={grades} limit={limit} />
|
||||
<GradeBar rows={grades.rows} />
|
||||
{known.length > 0 && (
|
||||
<ul className="m-0 flex list-none flex-col p-0">
|
||||
{known.map((row) => (
|
||||
<GradeRow key={row.key} row={row} grades={grades} limit={limit} />
|
||||
))}
|
||||
</ul>
|
||||
)}
|
||||
{unknown.length > 0 && (
|
||||
<div className="flex min-w-0 flex-col gap-1">
|
||||
<span
|
||||
className="font-geist font-bold uppercase tracking-[.08em] text-content-faint"
|
||||
style={fs(9.5, 'caption')}
|
||||
>
|
||||
{t('tours.hike.sac.unknown')}
|
||||
</span>
|
||||
<ul className="m-0 flex list-none flex-col p-0">
|
||||
{unknown.map((row) => (
|
||||
<GradeRow key={row.key} row={row} grades={grades} limit={limit} />
|
||||
))}
|
||||
</ul>
|
||||
</div>
|
||||
)}
|
||||
<span className="inline-flex gap-1.5 text-content-faint" style={fs(11, 'body')}>
|
||||
<Info size={12} strokeWidth={2} className="mt-0.5 flex-none" />
|
||||
{t('tours.hike.sac.source')}
|
||||
</span>
|
||||
</>
|
||||
);
|
||||
}
|
||||
|
||||
/**
|
||||
* MAP-03: the SAC grades along a hike, on the traveller's word. Switched on,
|
||||
* the grades OpenStreetMap gives the ways under the line are read and drawn
|
||||
* along it on the map, 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.
|
||||
*/
|
||||
export function SacGradesCard({
|
||||
tripId,
|
||||
route,
|
||||
routeCurrent = true,
|
||||
routingLimit,
|
||||
}: {
|
||||
tripId: number | string;
|
||||
/** The tour's own line, or null while there is none. */
|
||||
route: [number, number][] | null;
|
||||
/** False while the line is being planned anew. */
|
||||
routeCurrent?: boolean;
|
||||
/** The routing limit the hike is planned with, or null where it was not routed (an import). */
|
||||
routingLimit: number | null;
|
||||
}) {
|
||||
const { t } = useTranslation();
|
||||
const grades = useSacGrades({ tripId, route, current: routeCurrent });
|
||||
if (!grades.available) return null;
|
||||
return (
|
||||
<RailCard icon={<MountainSnow size={14} strokeWidth={2} />} title={t('tours.hike.sac.title')} foldId="hike-sac">
|
||||
<div data-testid="sac-grades-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')}>
|
||||
{t('tours.hike.sac.show')}
|
||||
</span>
|
||||
<ToggleSwitch on={grades.shown} onToggle={grades.toggle} label={t('tours.hike.sac.show')} />
|
||||
</div>
|
||||
{grades.shown && (
|
||||
<ReadStatusNotice
|
||||
status={grades.status}
|
||||
messages={{
|
||||
loading: t('tours.hike.sac.loading'),
|
||||
offline: t('tours.hike.sac.offline'),
|
||||
failed: t('tours.hike.sac.failed'),
|
||||
}}
|
||||
onRetry={grades.retry}
|
||||
/>
|
||||
)}
|
||||
{grades.shown && grades.status === 'ready' && <GradeList grades={grades} limit={routingLimit} />}
|
||||
</div>
|
||||
</RailCard>
|
||||
);
|
||||
}
|
||||
@@ -0,0 +1,12 @@
|
||||
import type { HikeFacetSpec } from './hikeFacets';
|
||||
import { SAC_FACET } from './sacGrades';
|
||||
import { SURFACE_FACET, WAY_FACET } from './wayTagFacets';
|
||||
|
||||
/** What a hike's card reads along the line, one at a time: the SAC grade, the surface or the kind of way. */
|
||||
export type HikeFacet = 'sac' | 'surface' | 'way';
|
||||
|
||||
export const HIKE_FACETS: Readonly<Record<HikeFacet, HikeFacetSpec<string>>> = {
|
||||
sac: SAC_FACET,
|
||||
surface: SURFACE_FACET,
|
||||
way: WAY_FACET,
|
||||
};
|
||||
@@ -0,0 +1,131 @@
|
||||
import type { TourTrailGrade } from '@trek/shared';
|
||||
import { describe, expect, it } from 'vitest';
|
||||
import type { RouteWayEdge, RouteWays } from '../bike/routeWays';
|
||||
import { facetClassifier, facetOverlay, facetPaint, facetRows, isUnknownClass, knownShareOf } from './hikeFacets';
|
||||
import { SAC_FACET } from './sacGrades';
|
||||
import { SURFACE_FACET } from './wayTagFacets';
|
||||
|
||||
// FE-HIKE-FACET-001 to FE-HIKE-FACET-008
|
||||
|
||||
const LINE: [number, number][] = Array.from({ length: 8 }, (_, index): [number, number] => [47 + index / 1000, 11]);
|
||||
|
||||
const edge = (from: number, wayId: number | null, lengthM = 100): RouteWayEdge => ({
|
||||
from,
|
||||
to: from + 1,
|
||||
lengthM,
|
||||
surface: 'paved',
|
||||
way: 'path',
|
||||
wayId,
|
||||
});
|
||||
|
||||
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,
|
||||
});
|
||||
|
||||
// One stretch of each kind: T3 on gravel (twice as long), T1 on pebbles, unclear, untagged, unasked, no way,
|
||||
// and a T6 way on rock that strays from the tour's own line.
|
||||
const WAYS: RouteWays = {
|
||||
line: LINE,
|
||||
edges: [edge(0, 1, 200), edge(1, 2), edge(2, 3), edge(3, 4), edge(4, 5), edge(5, null), edge(6, 7)],
|
||||
};
|
||||
const GRADES = {
|
||||
byWay: new Map([
|
||||
[1, grade(1, { sac_scale: 'demanding_mountain_hiking', surface: 'gravel' })],
|
||||
[2, grade(2, { sac_scale: 'hiking', surface: 'pebblestone' })],
|
||||
[3, grade(3, { sac_scale_unclear: 'T3;T4', surface: 'gravel;ground' })],
|
||||
[7, grade(7, { sac_scale: 'difficult_alpine_hiking', surface: 'rock' })],
|
||||
]),
|
||||
unasked: new Set([5]),
|
||||
};
|
||||
const STRAYS = new Set([WAYS.edges[6]]);
|
||||
|
||||
describe('hikeFacets (MAP-03, MAP-04)', () => {
|
||||
it('FE-HIKE-FACET-001: reads each stretch to its class, and every unknown to its own reason, in every facet', () => {
|
||||
expect(WAYS.edges.map(facetClassifier(SAC_FACET, GRADES, STRAYS))).toEqual([
|
||||
't3',
|
||||
't1',
|
||||
'unclear',
|
||||
'untagged',
|
||||
'unasked',
|
||||
'unmatched',
|
||||
'unmatched',
|
||||
]);
|
||||
expect(WAYS.edges.map(facetClassifier(SURFACE_FACET, GRADES, STRAYS))).toEqual([
|
||||
'gravel',
|
||||
'gravel',
|
||||
'unclear',
|
||||
'untagged',
|
||||
'unasked',
|
||||
'unmatched',
|
||||
'unmatched',
|
||||
]);
|
||||
});
|
||||
|
||||
it('FE-HIKE-FACET-002: the rows come in the facet order, the known first, each with what OpenStreetMap wrote', () => {
|
||||
const rows = facetRows(SAC_FACET, WAYS, GRADES, facetClassifier(SAC_FACET, GRADES, STRAYS));
|
||||
expect(rows.map((row) => [row.key, row.tags])).toEqual([
|
||||
['t1', ['hiking']],
|
||||
['t3', ['demanding_mountain_hiking']],
|
||||
['untagged', []],
|
||||
['unclear', ['T3;T4']],
|
||||
['unmatched', []],
|
||||
['unasked', []],
|
||||
]);
|
||||
expect(rows.find((row) => row.key === 't3')).toMatchObject({ lengthM: 200, share: 0.25 });
|
||||
// The stray T6 way is not the tour's: its grade is neither counted nor named.
|
||||
expect(rows.find((row) => row.key === 'unmatched')).toMatchObject({ lengthM: 200, tags: [] });
|
||||
});
|
||||
|
||||
it('FE-HIKE-FACET-003: a group names every value it took, each once', () => {
|
||||
const rows = facetRows(SURFACE_FACET, WAYS, GRADES, facetClassifier(SURFACE_FACET, GRADES, STRAYS));
|
||||
expect(rows[0]).toMatchObject({ key: 'gravel', lengthM: 300, tags: ['gravel', 'pebblestone'] });
|
||||
expect(rows.find((row) => row.key === 'unclear')?.tags).toEqual(['gravel;ground']);
|
||||
});
|
||||
|
||||
it('FE-HIKE-FACET-004: the known share counts the known classes only', () => {
|
||||
const rows = facetRows(SAC_FACET, WAYS, GRADES, facetClassifier(SAC_FACET, GRADES, STRAYS));
|
||||
expect(knownShareOf(rows)).toBeCloseTo(3 / 8);
|
||||
expect(knownShareOf([])).toBe(0);
|
||||
});
|
||||
|
||||
it('FE-HIKE-FACET-005: tells the unknown classes from the known', () => {
|
||||
expect(['untagged', 'unclear', 'unmatched', 'unasked'].every(isUnknownClass)).toBe(true);
|
||||
expect(isUnknownClass('t1')).toBe(false);
|
||||
expect(isUnknownClass('gravel')).toBe(false);
|
||||
});
|
||||
|
||||
it('FE-HIKE-FACET-006: paints a known class in its facet colour and every unknown in the grey of the unknown', () => {
|
||||
expect(facetPaint(SAC_FACET, 't2')).toBe('t2');
|
||||
expect(facetPaint(SURFACE_FACET, 'gravel')).toBe('surface:gravel');
|
||||
expect(facetPaint(SURFACE_FACET, 'unasked')).toBe('unknown');
|
||||
expect(facetPaint(SAC_FACET, 'untagged')).toBe('unknown');
|
||||
});
|
||||
|
||||
it('FE-HIKE-FACET-007: draws every class along the line in its paint, neighbours of a class joined', () => {
|
||||
const classOf = facetClassifier(SAC_FACET, GRADES, STRAYS);
|
||||
const overlay = facetOverlay(SAC_FACET, WAYS, facetRows(SAC_FACET, WAYS, GRADES, classOf), classOf);
|
||||
expect(overlay.paints).toEqual(['t1', 't3', 'unknown', 'unknown', 'unknown', 'unknown']);
|
||||
expect(overlay.lines).toHaveLength(6);
|
||||
// The no-way stretch and the stray one sit side by side and are one stretch of their class.
|
||||
expect(overlay.lines[4]).toEqual(LINE.slice(5, 8));
|
||||
// On the surface facet the two gravel ways side by side are one stretch.
|
||||
const surfaceOf = facetClassifier(SURFACE_FACET, GRADES, STRAYS);
|
||||
const surfaces = facetOverlay(SURFACE_FACET, WAYS, facetRows(SURFACE_FACET, WAYS, GRADES, surfaceOf), surfaceOf);
|
||||
expect(surfaces.lines[0]).toEqual(LINE.slice(0, 3));
|
||||
expect(surfaces.paints[0]).toBe('surface:gravel');
|
||||
});
|
||||
|
||||
it('FE-HIKE-FACET-008: draws one class alone when it is picked', () => {
|
||||
const classOf = facetClassifier(SAC_FACET, GRADES, STRAYS);
|
||||
const overlay = facetOverlay(SAC_FACET, WAYS, facetRows(SAC_FACET, WAYS, GRADES, classOf), classOf, 't3');
|
||||
expect(overlay).toEqual({ lines: [LINE.slice(0, 2)], paints: ['t3'] });
|
||||
});
|
||||
});
|
||||
@@ -0,0 +1,123 @@
|
||||
import type { TourTrailGrade } from '@trek/shared';
|
||||
import { sharesBy, stretchesOf, type ClassShare, type RouteWayEdge, type RouteWays } from '../bike/routeWays';
|
||||
import type { TrailGrades } from '../bike/useRouteWays';
|
||||
import type { HikePaintKey } from './hikePaint';
|
||||
|
||||
/**
|
||||
* MAP-03 and MAP-04: what a hike's card reads along the line, one facet at a
|
||||
* time (the SAC grade, the surface, the kind of way), from the values
|
||||
* OpenStreetMap records for the ways under it. Each facet sorts a way's value
|
||||
* into its own classes; what is not known stays unknown, for the same four
|
||||
* reasons in every facet: the way carries no such value (`untagged`), one
|
||||
* the facet cannot place (`unclear`), no way lies clearly under the tour's
|
||||
* line (`unmatched`), or OpenStreetMap could not be asked this time
|
||||
* (`unasked`). The router's own reading of a way is never one of the values:
|
||||
* it may be normalised or a default. Pure, so the classes, their shares and
|
||||
* their stretches are tested apart from the requests.
|
||||
*/
|
||||
|
||||
export type UnknownClass = 'untagged' | 'unclear' | 'unmatched' | 'unasked';
|
||||
|
||||
const UNKNOWN_CLASSES: readonly UnknownClass[] = ['untagged', 'unclear', 'unmatched', 'unasked'];
|
||||
|
||||
export function isUnknownClass(key: string): key is UnknownClass {
|
||||
return (UNKNOWN_CLASSES as readonly string[]).includes(key);
|
||||
}
|
||||
|
||||
/** What a facet reads of one way: the class its value falls in, and the value as OpenStreetMap wrote it. */
|
||||
interface FacetReading<K extends string> {
|
||||
key: K | 'untagged' | 'unclear';
|
||||
written: string | null;
|
||||
}
|
||||
|
||||
export interface HikeFacetSpec<K extends string> {
|
||||
/** The known classes, in the order the card lists them. */
|
||||
classes: readonly K[];
|
||||
read(grade: TourTrailGrade | undefined): FacetReading<K>;
|
||||
/** The paint a known class is drawn in; the unknown is always the grey of the unknown. */
|
||||
paint(key: K): HikePaintKey;
|
||||
}
|
||||
|
||||
type FacetPaint = HikePaintKey | 'unknown';
|
||||
|
||||
export interface FacetRow<K extends string = string> extends ClassShare<K | UnknownClass> {
|
||||
/** What OpenStreetMap wrote for this class's ways, each value once; none for a way not matched or not asked. */
|
||||
tags: string[];
|
||||
}
|
||||
|
||||
type Grades = Pick<TrailGrades, 'byWay' | 'unasked'>;
|
||||
|
||||
/** How each stretch of the matched line reads in one facet, given the edges that stray from the tour's own line. */
|
||||
export function facetClassifier<K extends string>(
|
||||
spec: HikeFacetSpec<K>,
|
||||
grades: Grades,
|
||||
strays: ReadonlySet<RouteWayEdge>
|
||||
): (edge: RouteWayEdge) => K | UnknownClass {
|
||||
return (edge) => {
|
||||
if (edge.wayId === null || strays.has(edge)) return 'unmatched';
|
||||
if (grades.unasked.has(edge.wayId)) return 'unasked';
|
||||
return spec.read(grades.byWay.get(edge.wayId)).key;
|
||||
};
|
||||
}
|
||||
|
||||
/** The paint of a class in its facet. */
|
||||
export function facetPaint<K extends string>(spec: HikeFacetSpec<K>, key: K | UnknownClass): FacetPaint {
|
||||
return isUnknownClass(key) ? 'unknown' : spec.paint(key);
|
||||
}
|
||||
|
||||
/**
|
||||
* The share of each class along the line in the card's order, the known
|
||||
* classes first, each with the values OpenStreetMap wrote for it; classes the
|
||||
* line has none of are left out.
|
||||
*/
|
||||
export function facetRows<K extends string>(
|
||||
spec: HikeFacetSpec<K>,
|
||||
ways: RouteWays,
|
||||
grades: Grades,
|
||||
classOf: (edge: RouteWayEdge) => K | UnknownClass
|
||||
): FacetRow<K>[] {
|
||||
const order: readonly string[] = [...spec.classes, ...UNKNOWN_CLASSES];
|
||||
const tags = new Map<string, Set<string>>();
|
||||
for (const edge of ways.edges) {
|
||||
const key = classOf(edge);
|
||||
if (edge.wayId === null || key === 'unmatched' || key === 'unasked') continue;
|
||||
const written = spec.read(grades.byWay.get(edge.wayId)).written;
|
||||
if (written) tags.set(key, (tags.get(key) ?? new Set()).add(written));
|
||||
}
|
||||
return sharesBy(ways, classOf)
|
||||
.sort((a, b) => order.indexOf(a.key) - order.indexOf(b.key))
|
||||
.map((row) => ({ ...row, tags: [...(tags.get(row.key) ?? [])] }));
|
||||
}
|
||||
|
||||
/** Of the whole line, 0 to 1, the share whose class is known. */
|
||||
export function knownShareOf(rows: readonly FacetRow[]): number {
|
||||
return rows.filter((row) => !isUnknownClass(row.key)).reduce((sum, row) => sum + row.share, 0);
|
||||
}
|
||||
|
||||
interface FacetOverlay {
|
||||
lines: [number, number][][];
|
||||
paints: FacetPaint[];
|
||||
}
|
||||
|
||||
/**
|
||||
* Every class's stretches with their paints, for drawing the facet along the
|
||||
* whole line at once; `only` draws one class alone.
|
||||
*/
|
||||
export function facetOverlay<K extends string>(
|
||||
spec: HikeFacetSpec<K>,
|
||||
ways: RouteWays,
|
||||
rows: readonly FacetRow<K>[],
|
||||
classOf: (edge: RouteWayEdge) => K | UnknownClass,
|
||||
only: string | null = null
|
||||
): FacetOverlay {
|
||||
const lines: [number, number][][] = [];
|
||||
const paints: FacetPaint[] = [];
|
||||
for (const row of rows) {
|
||||
if (only && row.key !== only) continue;
|
||||
for (const line of stretchesOf(ways, (edge) => classOf(edge) === row.key)) {
|
||||
lines.push(line);
|
||||
paints.push(facetPaint(spec, row.key));
|
||||
}
|
||||
}
|
||||
return { lines, paints };
|
||||
}
|
||||
@@ -1,11 +1,13 @@
|
||||
import { useMapPaint } from '../../Map/mapPaint';
|
||||
|
||||
/**
|
||||
* MAP-03: the colours of the SAC grades along a hike (styles/hike.css), by
|
||||
* name. The card takes the CSS variable, which follows the theme on its own;
|
||||
* the map reads them resolved (`useHikePaint`). A way without a known grade
|
||||
* MAP-03 and MAP-04: the colours of what a hike's card draws along the line
|
||||
* (styles/hike.css), by name: the SAC grades, the surfaces and the kinds of
|
||||
* way. The card takes the CSS variable, which follows the theme on its own;
|
||||
* the map reads them resolved (`useHikePaint`). A way whose class is unknown
|
||||
* is drawn in the bike paints' grey of the unknown, the one grey the route
|
||||
* paints use for what the data does not say.
|
||||
* paints use for what the data does not say. The surface and way keys carry
|
||||
* their facet's prefix, so they never meet the bike paints' own names.
|
||||
*/
|
||||
const HIKE_PAINT_TOKENS = {
|
||||
t1: '--hike-sac-t1',
|
||||
@@ -14,6 +16,25 @@ const HIKE_PAINT_TOKENS = {
|
||||
t4: '--hike-sac-t4',
|
||||
t5: '--hike-sac-t5',
|
||||
t6: '--hike-sac-t6',
|
||||
'surface:paved': '--hike-surface-paved',
|
||||
'surface:sett': '--hike-surface-sett',
|
||||
'surface:compacted': '--hike-surface-compacted',
|
||||
'surface:gravel': '--hike-surface-gravel',
|
||||
'surface:ground': '--hike-surface-ground',
|
||||
'surface:grass': '--hike-surface-grass',
|
||||
'surface:rock': '--hike-surface-rock',
|
||||
'surface:wood': '--hike-surface-wood',
|
||||
'surface:unpaved': '--hike-surface-unpaved',
|
||||
'surface:other': '--hike-surface-other',
|
||||
'way:path': '--hike-way-path',
|
||||
'way:footway': '--hike-way-footway',
|
||||
'way:steps': '--hike-way-steps',
|
||||
'way:track': '--hike-way-track',
|
||||
'way:cycleway': '--hike-way-cycleway',
|
||||
'way:quietRoad': '--hike-way-quiet-road',
|
||||
'way:road': '--hike-way-road',
|
||||
'way:viaFerrata': '--hike-way-via-ferrata',
|
||||
'way:other': '--hike-way-other',
|
||||
} as const;
|
||||
|
||||
export type HikePaintKey = keyof typeof HIKE_PAINT_TOKENS;
|
||||
|
||||
@@ -1,115 +1,51 @@
|
||||
import type { TourTrailGrade } from '@trek/shared';
|
||||
import { describe, expect, it } from 'vitest';
|
||||
import type { RouteWayEdge, RouteWays } from '../bike/routeWays';
|
||||
import {
|
||||
highestStep,
|
||||
isSacStep,
|
||||
sacClassifier,
|
||||
sacOverlay,
|
||||
sacPaintKey,
|
||||
sacShares,
|
||||
sacStepOfClass,
|
||||
sacTagOf,
|
||||
unclearTags,
|
||||
} from './sacGrades';
|
||||
import { highestStep, SAC_FACET, sacStepOfClass } from './sacGrades';
|
||||
|
||||
// FE-HIKE-SAC-001 to FE-HIKE-SAC-007
|
||||
// FE-HIKE-SAC-001 to FE-HIKE-SAC-004
|
||||
|
||||
const LINE: [number, number][] = Array.from({ length: 8 }, (_, index): [number, number] => [47 + index / 1000, 11]);
|
||||
|
||||
const edge = (from: number, wayId: number | null, lengthM = 100): RouteWayEdge => ({
|
||||
from,
|
||||
to: from + 1,
|
||||
lengthM,
|
||||
surface: 'unknown',
|
||||
way: 'path',
|
||||
wayId,
|
||||
});
|
||||
|
||||
const grade = (id: number, sac: Partial<Pick<TourTrailGrade, 'sac_scale' | 'sac_scale_unclear'>>): TourTrailGrade => ({
|
||||
id,
|
||||
const grade = (values: Partial<TourTrailGrade>): TourTrailGrade => ({
|
||||
id: 1,
|
||||
mtb_scale: null,
|
||||
mtb_scale_uphill: null,
|
||||
mtb_scale_imba: null,
|
||||
sac_scale: null,
|
||||
sac_scale_unclear: null,
|
||||
...sac,
|
||||
surface: null,
|
||||
highway: null,
|
||||
...values,
|
||||
});
|
||||
|
||||
// One stretch of each kind: T3 (twice as long), T1, unclear, untagged, unasked, no way, a T6 way that strays.
|
||||
const WAYS: RouteWays = {
|
||||
line: LINE,
|
||||
edges: [
|
||||
edge(0, 1, 200),
|
||||
edge(1, 2),
|
||||
edge(2, 3),
|
||||
edge(3, 4),
|
||||
edge(4, 5),
|
||||
edge(5, null),
|
||||
edge(6, 7),
|
||||
],
|
||||
};
|
||||
const GRADES = {
|
||||
byWay: new Map([
|
||||
[1, grade(1, { sac_scale: 'demanding_mountain_hiking' })],
|
||||
[2, grade(2, { sac_scale: 'hiking' })],
|
||||
[3, grade(3, { sac_scale_unclear: 'T3;T4' })],
|
||||
[7, grade(7, { sac_scale: 'difficult_alpine_hiking' })],
|
||||
]),
|
||||
unasked: new Set([5]),
|
||||
};
|
||||
const STRAYS = new Set([WAYS.edges[6]]);
|
||||
|
||||
describe('sacGrades (MAP-03)', () => {
|
||||
it('FE-HIKE-SAC-001: reads each stretch to its step, and every unknown to its own reason, never to T1', () => {
|
||||
const classOf = sacClassifier(GRADES, STRAYS);
|
||||
expect(WAYS.edges.map(classOf)).toEqual(['t3', 't1', 'unclear', 'untagged', 'unasked', 'unmatched', 'unmatched']);
|
||||
it('FE-HIKE-SAC-001: reads a way to its step as OpenStreetMap wrote it, an unclear grade as unclear, none as untagged', () => {
|
||||
expect(SAC_FACET.read(grade({ sac_scale: 'demanding_mountain_hiking' }))).toEqual({
|
||||
key: 't3',
|
||||
written: 'demanding_mountain_hiking',
|
||||
});
|
||||
expect(SAC_FACET.read(grade({ sac_scale: 'hiking' })).key).toBe('t1');
|
||||
expect(SAC_FACET.read(grade({ sac_scale_unclear: 'T3;T4' }))).toEqual({ key: 'unclear', written: 'T3;T4' });
|
||||
// A surface or a kind of way says nothing about the grade.
|
||||
expect(SAC_FACET.read(grade({ surface: 'rock', highway: 'path' }))).toEqual({ key: 'untagged', written: null });
|
||||
expect(SAC_FACET.read(undefined)).toEqual({ key: 'untagged', written: null });
|
||||
});
|
||||
|
||||
it('FE-HIKE-SAC-002: the shares come in the scale order, the steps first, then the unknown', () => {
|
||||
const rows = sacShares(WAYS, sacClassifier(GRADES, STRAYS));
|
||||
expect(rows.map((row) => row.key)).toEqual(['t1', 't3', 'untagged', 'unclear', 'unmatched', 'unasked']);
|
||||
expect(rows.find((row) => row.key === 't3')).toMatchObject({ lengthM: 200, share: 0.25 });
|
||||
expect(rows.find((row) => row.key === 'unmatched')?.lengthM).toBe(200);
|
||||
it('FE-HIKE-SAC-002: names a step by its class and paints it in its own colour', () => {
|
||||
expect(SAC_FACET.classes).toEqual(['t1', 't2', 't3', 't4', 't5', 't6']);
|
||||
expect(SAC_FACET.paint('t2')).toBe('t2');
|
||||
});
|
||||
|
||||
it('FE-HIKE-SAC-003: names a step by its class, its value in OpenStreetMap and its paint', () => {
|
||||
expect(isSacStep('t4')).toBe(true);
|
||||
expect(isSacStep('unclear')).toBe(false);
|
||||
expect(isSacStep('untagged')).toBe(false);
|
||||
it('FE-HIKE-SAC-003: a class stands for its step, the unknown and other facets for none', () => {
|
||||
expect(sacStepOfClass('t6')).toBe(6);
|
||||
expect(sacStepOfClass('t1')).toBe(1);
|
||||
expect(sacStepOfClass('untagged')).toBeNull();
|
||||
expect(sacTagOf('t1')).toBe('hiking');
|
||||
expect(sacTagOf('t6')).toBe('difficult_alpine_hiking');
|
||||
expect(sacPaintKey('t2')).toBe('t2');
|
||||
expect(sacPaintKey('unasked')).toBe('unknown');
|
||||
expect(sacStepOfClass('unclear')).toBeNull();
|
||||
expect(sacStepOfClass('gravel')).toBeNull();
|
||||
expect(sacStepOfClass('t7')).toBeNull();
|
||||
});
|
||||
|
||||
it('FE-HIKE-SAC-004: hands on an unclear grade as OpenStreetMap wrote it, each value once', () => {
|
||||
const twice: RouteWays = { line: LINE, edges: [edge(0, 3), edge(1, 3), edge(2, 1)] };
|
||||
expect(unclearTags(twice, GRADES, () => true)).toEqual(['T3;T4']);
|
||||
expect(unclearTags(twice, GRADES, () => false)).toEqual([]);
|
||||
expect(unclearTags({ line: LINE, edges: [edge(0, null)] }, GRADES, () => true)).toEqual([]);
|
||||
});
|
||||
|
||||
it('FE-HIKE-SAC-005: the highest step known, or none where no step is', () => {
|
||||
const rows = sacShares(WAYS, sacClassifier(GRADES, STRAYS));
|
||||
expect(highestStep(rows)).toBe(3);
|
||||
expect(highestStep(rows.filter((row) => !isSacStep(row.key)))).toBeNull();
|
||||
});
|
||||
|
||||
it('FE-HIKE-SAC-006: draws every class along the line in its paint, neighbours of a class joined', () => {
|
||||
const classOf = sacClassifier(GRADES, STRAYS);
|
||||
const overlay = sacOverlay(WAYS, sacShares(WAYS, classOf), classOf);
|
||||
expect(overlay.paints).toEqual(['t1', 't3', 'unknown', 'unknown', 'unknown', 'unknown']);
|
||||
expect(overlay.lines).toHaveLength(6);
|
||||
// The no-way stretch and the stray one sit side by side and are one stretch of their class.
|
||||
expect(overlay.lines[4]).toEqual(LINE.slice(5, 8));
|
||||
});
|
||||
|
||||
it('FE-HIKE-SAC-007: draws one class alone when it is picked', () => {
|
||||
const classOf = sacClassifier(GRADES, STRAYS);
|
||||
const overlay = sacOverlay(WAYS, sacShares(WAYS, classOf), classOf, 't3');
|
||||
expect(overlay).toEqual({ lines: [LINE.slice(0, 2)], paints: ['t3'] });
|
||||
it('FE-HIKE-SAC-004: the highest step known, or none where no step is', () => {
|
||||
expect(highestStep([{ key: 't1' }, { key: 't3' }, { key: 'untagged' }])).toBe(3);
|
||||
expect(highestStep([{ key: 'unclear' }, { key: 'unmatched' }])).toBeNull();
|
||||
expect(highestStep([])).toBeNull();
|
||||
});
|
||||
});
|
||||
|
||||
@@ -1,122 +1,36 @@
|
||||
import { SAC_SCALE_VALUES, sacStepOf, type SacScale, type TourTrailGrade } from '@trek/shared';
|
||||
import { sharesBy, stretchesOf, type ClassShare, type RouteWayEdge, type RouteWays } from '../bike/routeWays';
|
||||
import type { TrailGrades } from '../bike/useRouteWays';
|
||||
import type { HikePaintKey } from './hikePaint';
|
||||
import { sacStepOf } from '@trek/shared';
|
||||
import type { HikeFacetSpec } from './hikeFacets';
|
||||
|
||||
/**
|
||||
* MAP-03: the SAC grades along a hike, read stretch by stretch from the
|
||||
* `sac_scale` OpenStreetMap gives the ways under it. Pure, so the classes,
|
||||
* their shares and their stretches are tested apart from the requests.
|
||||
*
|
||||
* A step (`t1` to `t6`) is only ever what OpenStreetMap wrote for that way.
|
||||
* Everything else is unknown, and the reason stays apart: the way has no
|
||||
* grade (`untagged`), a grade that names no single step (`unclear`), no way
|
||||
* clearly under the tour's line (`unmatched`), or OpenStreetMap could not be
|
||||
* asked this time (`unasked`). None of them is ever read as T1.
|
||||
* `sac_scale` OpenStreetMap gives the ways under it. A step (`t1` to `t6`)
|
||||
* is only ever what OpenStreetMap wrote for that way; a grade that names no
|
||||
* single step is unclear, and none of the unknown is ever read as T1.
|
||||
*/
|
||||
|
||||
type SacStepClass = 't1' | 't2' | 't3' | 't4' | 't5' | 't6';
|
||||
type SacUnknownClass = 'untagged' | 'unclear' | 'unmatched' | 'unasked';
|
||||
export type SacClass = SacStepClass | SacUnknownClass;
|
||||
|
||||
/** The order the card lists the classes in: the steps from easy to hard, then the unknown. */
|
||||
const SAC_CLASSES: readonly SacClass[] = [
|
||||
't1',
|
||||
't2',
|
||||
't3',
|
||||
't4',
|
||||
't5',
|
||||
't6',
|
||||
'untagged',
|
||||
'unclear',
|
||||
'unmatched',
|
||||
'unasked',
|
||||
];
|
||||
|
||||
export function isSacStep(key: SacClass): key is SacStepClass {
|
||||
function isSacStep(key: string): key is SacStepClass {
|
||||
return /^t[1-6]$/.test(key);
|
||||
}
|
||||
|
||||
/** The step a class stands for, 1 to 6; null for the unknown. */
|
||||
export function sacStepOfClass(key: SacClass): number | null {
|
||||
export function sacStepOfClass(key: string): number | null {
|
||||
return isSacStep(key) ? Number(key.slice(1)) : null;
|
||||
}
|
||||
|
||||
/** The `sac_scale` value OpenStreetMap writes for a step's class. */
|
||||
export function sacTagOf(key: SacStepClass): SacScale {
|
||||
return SAC_SCALE_VALUES[Number(key.slice(1)) - 1];
|
||||
}
|
||||
|
||||
/** The paint of a class: the step's own, and the grey of the unknown for every kind of unknown. */
|
||||
export function sacPaintKey(key: SacClass): HikePaintKey | 'unknown' {
|
||||
return isSacStep(key) ? key : 'unknown';
|
||||
}
|
||||
|
||||
function stepClassOf(grade: TourTrailGrade | undefined): SacClass {
|
||||
if (grade?.sac_scale) return `t${sacStepOf(grade.sac_scale)}`;
|
||||
return grade?.sac_scale_unclear ? 'unclear' : 'untagged';
|
||||
}
|
||||
|
||||
/** How each stretch of the matched line reads, given the grades and the edges that stray from the tour's own line. */
|
||||
export function sacClassifier(
|
||||
grades: Pick<TrailGrades, 'byWay' | 'unasked'>,
|
||||
strays: ReadonlySet<RouteWayEdge>
|
||||
): (edge: RouteWayEdge) => SacClass {
|
||||
return (edge) => {
|
||||
if (edge.wayId === null || strays.has(edge)) return 'unmatched';
|
||||
if (grades.unasked.has(edge.wayId)) return 'unasked';
|
||||
return stepClassOf(grades.byWay.get(edge.wayId));
|
||||
};
|
||||
}
|
||||
|
||||
/** The share of each class along the line, in the card's order; classes the line has none of are left out. */
|
||||
export function sacShares(ways: RouteWays, classOf: (edge: RouteWayEdge) => SacClass): ClassShare<SacClass>[] {
|
||||
return sharesBy(ways, classOf).sort((a, b) => SAC_CLASSES.indexOf(a.key) - SAC_CLASSES.indexOf(b.key));
|
||||
}
|
||||
|
||||
/** The grade's tags as OpenStreetMap wrote them where they name no single step, each once. */
|
||||
export function unclearTags(
|
||||
ways: RouteWays,
|
||||
grades: Pick<TrailGrades, 'byWay'>,
|
||||
inClass: (edge: RouteWayEdge) => boolean
|
||||
): string[] {
|
||||
const tags = new Set<string>();
|
||||
for (const edge of ways.edges) {
|
||||
const written = edge.wayId === null ? null : grades.byWay.get(edge.wayId)?.sac_scale_unclear;
|
||||
if (written && inClass(edge)) tags.add(written);
|
||||
}
|
||||
return [...tags];
|
||||
}
|
||||
|
||||
/** The highest step known anywhere along the line, or null where none is. */
|
||||
export function highestStep(rows: readonly ClassShare<SacClass>[]): number | null {
|
||||
/** The highest step known among the classes along the line, or null where none is. */
|
||||
export function highestStep(rows: readonly { key: string }[]): number | null {
|
||||
const steps = rows.map((row) => sacStepOfClass(row.key)).filter((step): step is number => step !== null);
|
||||
return steps.length ? Math.max(...steps) : null;
|
||||
}
|
||||
|
||||
interface SacOverlay {
|
||||
lines: [number, number][][];
|
||||
paints: (HikePaintKey | 'unknown')[];
|
||||
}
|
||||
|
||||
/**
|
||||
* Every class's stretches with their paints, for drawing the grades along the
|
||||
* whole line at once; `only` draws one class alone.
|
||||
*/
|
||||
export function sacOverlay(
|
||||
ways: RouteWays,
|
||||
rows: readonly ClassShare<SacClass>[],
|
||||
classOf: (edge: RouteWayEdge) => SacClass,
|
||||
only: SacClass | null = null
|
||||
): SacOverlay {
|
||||
const lines: [number, number][][] = [];
|
||||
const paints: (HikePaintKey | 'unknown')[] = [];
|
||||
for (const row of rows) {
|
||||
if (only && row.key !== only) continue;
|
||||
for (const line of stretchesOf(ways, (edge) => classOf(edge) === row.key)) {
|
||||
lines.push(line);
|
||||
paints.push(sacPaintKey(row.key));
|
||||
}
|
||||
}
|
||||
return { lines, paints };
|
||||
}
|
||||
export const SAC_FACET: HikeFacetSpec<SacStepClass> = {
|
||||
classes: ['t1', 't2', 't3', 't4', 't5', 't6'],
|
||||
read: (grade) => {
|
||||
if (grade?.sac_scale) return { key: `t${sacStepOf(grade.sac_scale)}`, written: grade.sac_scale };
|
||||
if (grade?.sac_scale_unclear) return { key: 'unclear', written: grade.sac_scale_unclear };
|
||||
return { key: 'untagged', written: null };
|
||||
},
|
||||
paint: (key) => key,
|
||||
};
|
||||
|
||||
+65
-21
@@ -4,7 +4,7 @@ import { beforeEach, describe, expect, it, vi } from 'vitest';
|
||||
import { useTourRouteHighlightStore } from '../../../store/tourRouteHighlightStore';
|
||||
import type { RouteWayEdge, RouteWays } from '../bike/routeWays';
|
||||
import type { RouteWaysState } from '../bike/useRouteWays';
|
||||
import { useSacGrades } from './useSacGrades';
|
||||
import { useHikeWays } from './useHikeWays';
|
||||
|
||||
const hook = vi.hoisted(() => ({
|
||||
state: null as unknown,
|
||||
@@ -18,7 +18,7 @@ vi.mock('../bike/useRouteWays', () => ({
|
||||
},
|
||||
}));
|
||||
|
||||
// FE-HIKE-SAC-HOOK-001 to FE-HIKE-SAC-HOOK-008
|
||||
// FE-HIKE-WAYS-HOOK-001 to FE-HIKE-WAYS-HOOK-011
|
||||
|
||||
const ROUTE: [number, number][] = [
|
||||
[47.0, 11.0],
|
||||
@@ -30,19 +30,22 @@ const edge = (from: number, wayId: number): RouteWayEdge => ({
|
||||
from,
|
||||
to: from + 1,
|
||||
lengthM: 100,
|
||||
surface: 'unknown',
|
||||
// The router's own reading, which no facet may take for a recorded value.
|
||||
surface: 'paved',
|
||||
way: 'path',
|
||||
wayId,
|
||||
});
|
||||
const WAYS: RouteWays = { line: ROUTE, edges: [edge(0, 1), edge(1, 2), edge(2, 3)] };
|
||||
const grade = (id: number, sac: Partial<TourTrailGrade>): TourTrailGrade => ({
|
||||
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,
|
||||
...sac,
|
||||
surface: null,
|
||||
highway: null,
|
||||
...values,
|
||||
});
|
||||
const READY: RouteWaysState = {
|
||||
status: 'ready',
|
||||
@@ -50,8 +53,8 @@ const READY: RouteWaysState = {
|
||||
grades: {
|
||||
status: 'ready',
|
||||
byWay: new Map([
|
||||
[1, grade(1, { sac_scale: 'mountain_hiking' })],
|
||||
[2, grade(2, { sac_scale: 'alpine_hiking' })],
|
||||
[1, grade(1, { sac_scale: 'mountain_hiking', surface: 'gravel', highway: 'track' })],
|
||||
[2, grade(2, { sac_scale: 'alpine_hiking', surface: 'rock', highway: 'path' })],
|
||||
]),
|
||||
unasked: new Set(),
|
||||
},
|
||||
@@ -59,7 +62,7 @@ const READY: RouteWaysState = {
|
||||
|
||||
function shownHook(state: RouteWaysState, route: [number, number][] | null = ROUTE) {
|
||||
hook.state = state;
|
||||
const view = renderHook((props: { route: [number, number][] | null }) => useSacGrades({ tripId: 5, ...props }), {
|
||||
const view = renderHook((props: { route: [number, number][] | null }) => useHikeWays({ tripId: 5, ...props }), {
|
||||
initialProps: { route },
|
||||
});
|
||||
act(() => view.result.current.toggle());
|
||||
@@ -73,19 +76,19 @@ beforeEach(() => {
|
||||
useTourRouteHighlightStore.getState().clear();
|
||||
});
|
||||
|
||||
describe('useSacGrades (MAP-03)', () => {
|
||||
it('FE-HIKE-SAC-HOOK-001: without a line of two points there is nothing to grade and nothing is asked', () => {
|
||||
describe('useHikeWays (MAP-03, MAP-04)', () => {
|
||||
it('FE-HIKE-WAYS-HOOK-001: without a line of two points there is nothing to read and nothing is asked', () => {
|
||||
for (const route of [null, ROUTE.slice(0, 1)]) {
|
||||
const { result } = renderHook(() => useSacGrades({ tripId: 5, route }));
|
||||
const { result } = renderHook(() => useHikeWays({ tripId: 5, route }));
|
||||
expect(result.current.available).toBe(false);
|
||||
expect(result.current.status).toBe('off');
|
||||
expect(hook.input).toMatchObject({ active: false });
|
||||
}
|
||||
});
|
||||
|
||||
it('FE-HIKE-SAC-HOOK-002: off until switched on, then asks for the ways on foot with their grades', () => {
|
||||
const { result } = renderHook(() => useSacGrades({ tripId: 5, route: ROUTE }));
|
||||
expect(result.current).toMatchObject({ available: true, shown: false, status: 'off', rows: [] });
|
||||
it('FE-HIKE-WAYS-HOOK-002: off until switched on, then asks for the ways on foot with what OpenStreetMap records', () => {
|
||||
const { result } = renderHook(() => useHikeWays({ tripId: 5, route: ROUTE }));
|
||||
expect(result.current).toMatchObject({ available: true, shown: false, status: 'off', facet: 'sac', rows: [] });
|
||||
expect(hook.input).toMatchObject({ tripId: 5, active: false, route: ROUTE, matching: 'foot', withGrades: true });
|
||||
hook.state = { status: 'loading', ways: null, grades: null };
|
||||
act(() => result.current.toggle());
|
||||
@@ -94,7 +97,7 @@ describe('useSacGrades (MAP-03)', () => {
|
||||
expect(hook.input).toMatchObject({ active: true });
|
||||
});
|
||||
|
||||
it('FE-HIKE-SAC-HOOK-003: says when the ways or the grades are on their way, offline or failed', () => {
|
||||
it('FE-HIKE-WAYS-HOOK-003: says when the ways or their values are on their way, offline or failed', () => {
|
||||
const cases: [RouteWaysState, string][] = [
|
||||
[{ status: 'offline', ways: null, grades: null }, 'offline'],
|
||||
[{ status: 'failed', ways: null, grades: null }, 'failed'],
|
||||
@@ -111,7 +114,7 @@ describe('useSacGrades (MAP-03)', () => {
|
||||
expect(useTourRouteHighlightStore.getState().highlight).toBeNull();
|
||||
});
|
||||
|
||||
it('FE-HIKE-SAC-HOOK-004: lists the known steps and the unknown, and draws them all along the line', () => {
|
||||
it('FE-HIKE-WAYS-HOOK-004: lists the known SAC steps and the unknown, and draws them all along the line', () => {
|
||||
const { result } = shownHook(READY);
|
||||
expect(result.current.status).toBe('ready');
|
||||
expect(result.current.rows.map((row) => [row.key, row.tags])).toEqual([
|
||||
@@ -128,7 +131,7 @@ describe('useSacGrades (MAP-03)', () => {
|
||||
});
|
||||
});
|
||||
|
||||
it('FE-HIKE-SAC-HOOK-005: a picked class is drawn alone, picked again every class is drawn again', () => {
|
||||
it('FE-HIKE-WAYS-HOOK-005: a picked class is drawn alone, picked again every class is drawn again', () => {
|
||||
const { result } = shownHook(READY);
|
||||
act(() => result.current.focusOn('t4'));
|
||||
expect(result.current.focus).toBe('t4');
|
||||
@@ -142,7 +145,7 @@ describe('useSacGrades (MAP-03)', () => {
|
||||
expect(useTourRouteHighlightStore.getState().highlight?.id).toBe('sac');
|
||||
});
|
||||
|
||||
it('FE-HIKE-SAC-HOOK-006: a hike nobody could be asked about fails once with a retry rather than one unknown row', () => {
|
||||
it('FE-HIKE-WAYS-HOOK-006: a hike nobody could be asked about fails once with a retry rather than one unknown row', () => {
|
||||
const { result } = shownHook({
|
||||
...READY,
|
||||
grades: { status: 'ready', byWay: new Map(), unasked: new Set([1, 2, 3]) },
|
||||
@@ -154,7 +157,7 @@ describe('useSacGrades (MAP-03)', () => {
|
||||
expect(hook.retry).toHaveBeenCalledTimes(1);
|
||||
});
|
||||
|
||||
it('FE-HIKE-SAC-HOOK-007: switched off or left, the map is as it was; a way off the line is never graded', () => {
|
||||
it('FE-HIKE-WAYS-HOOK-007: switched off or left, the map is as it was; a way off the line is never read', () => {
|
||||
const { result, unmount } = shownHook(READY);
|
||||
act(() => result.current.focusOn('t2'));
|
||||
act(() => result.current.toggle());
|
||||
@@ -171,11 +174,14 @@ describe('useSacGrades (MAP-03)', () => {
|
||||
const off = shownHook(READY, elsewhere);
|
||||
expect(off.result.current.rows.map((row) => row.key)).toEqual(['unmatched']);
|
||||
expect(off.result.current.highest).toBeNull();
|
||||
act(() => off.result.current.chooseFacet('surface'));
|
||||
expect(off.result.current.rows.map((row) => row.key)).toEqual(['unmatched']);
|
||||
expect(off.result.current.mostly).toBeNull();
|
||||
});
|
||||
|
||||
it('FE-HIKE-SAC-HOOK-008: while the line is planned anew the card waits for it and grades nothing of the old one', () => {
|
||||
it('FE-HIKE-WAYS-HOOK-008: while the line is planned anew the card waits for it and reads nothing of the old one', () => {
|
||||
hook.state = READY;
|
||||
const { result } = renderHook(() => useSacGrades({ tripId: 5, route: ROUTE, current: false }));
|
||||
const { result } = renderHook(() => useHikeWays({ tripId: 5, route: ROUTE, current: false }));
|
||||
act(() => result.current.toggle());
|
||||
expect(result.current.available).toBe(true);
|
||||
expect(result.current.status).toBe('loading');
|
||||
@@ -183,4 +189,42 @@ describe('useSacGrades (MAP-03)', () => {
|
||||
expect(hook.input).toMatchObject({ active: true, route: null });
|
||||
expect(useTourRouteHighlightStore.getState().highlight).toBeNull();
|
||||
});
|
||||
|
||||
it('FE-HIKE-WAYS-HOOK-009: the surfaces are the recorded ones, never the router reading, and draw in their own paints', () => {
|
||||
const { result } = shownHook(READY);
|
||||
act(() => result.current.chooseFacet('surface'));
|
||||
expect(result.current.facet).toBe('surface');
|
||||
expect(result.current.rows.map((row) => [row.key, row.tags])).toEqual([
|
||||
['gravel', ['gravel']],
|
||||
['rock', ['rock']],
|
||||
['untagged', []],
|
||||
]);
|
||||
expect(result.current.highest).toBeNull();
|
||||
expect(result.current.mostly?.key).toBe('gravel');
|
||||
expect(useTourRouteHighlightStore.getState().highlight).toMatchObject({
|
||||
id: 'surface',
|
||||
paints: ['surface:gravel', 'surface:rock', 'unknown'],
|
||||
});
|
||||
});
|
||||
|
||||
it('FE-HIKE-WAYS-HOOK-010: the kinds of way sit apart from the surfaces and the grades; a new facet lets the picked class go', () => {
|
||||
const { result } = shownHook(READY);
|
||||
act(() => result.current.focusOn('t2'));
|
||||
act(() => result.current.chooseFacet('way'));
|
||||
expect(result.current.focus).toBeNull();
|
||||
expect(result.current.rows.map((row) => row.key)).toEqual(['path', 'track', 'untagged']);
|
||||
expect(useTourRouteHighlightStore.getState().highlight?.id).toBe('way');
|
||||
act(() => result.current.focusOn('track'));
|
||||
expect(useTourRouteHighlightStore.getState().highlight).toMatchObject({ id: 'way-track', paints: ['way:track'] });
|
||||
});
|
||||
|
||||
it('FE-HIKE-WAYS-HOOK-011: every facet is read from the one answer, asked once on foot', () => {
|
||||
const { result } = shownHook(READY);
|
||||
const asked = hook.input;
|
||||
act(() => result.current.chooseFacet('surface'));
|
||||
act(() => result.current.chooseFacet('way'));
|
||||
// The same question on every render: switching facets asks nothing new.
|
||||
expect(hook.input).toEqual(asked);
|
||||
expect(result.current.status).toBe('ready');
|
||||
});
|
||||
});
|
||||
@@ -0,0 +1,124 @@
|
||||
import { useCallback, useEffect, useMemo, useState } from 'react';
|
||||
import { useTourRouteHighlightStore } from '../../../store/tourRouteHighlightStore';
|
||||
import type { RouteWayEdge } 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';
|
||||
import { straysFrom } from './routeMatch';
|
||||
import { highestStep } from './sacGrades';
|
||||
|
||||
type HikeWaysStatus = 'off' | 'loading' | 'offline' | 'failed' | 'ready';
|
||||
|
||||
export interface HikeWays {
|
||||
/** Whether the card has a line to describe at all. */
|
||||
available: boolean;
|
||||
/** Whether the card is switched on: only then is anything asked for and drawn on the map. */
|
||||
shown: boolean;
|
||||
toggle: () => void;
|
||||
facet: HikeFacet;
|
||||
chooseFacet: (facet: HikeFacet) => void;
|
||||
status: HikeWaysStatus;
|
||||
/** The facet's classes along the line, the known first. */
|
||||
rows: FacetRow[];
|
||||
/** Of the whole line, 0 to 1, the share whose class is known. */
|
||||
knownShare: number;
|
||||
/** The known class covering the most of the line, or null. */
|
||||
mostly: FacetRow | null;
|
||||
/** On the SAC facet, the highest step known along the line, or null. */
|
||||
highest: number | null;
|
||||
/** The class drawn alone on the map, or null while every class is. */
|
||||
focus: string | null;
|
||||
focusOn: (key: string) => void;
|
||||
retry: () => void;
|
||||
}
|
||||
|
||||
const NO_STRAYS: ReadonlySet<RouteWayEdge> = new Set();
|
||||
|
||||
function statusOf(state: RouteWaysState, nothingAnswered: boolean): HikeWaysStatus {
|
||||
if (state.status === 'idle') return 'off';
|
||||
if (state.status !== 'ready') return state.status;
|
||||
const grades = state.grades?.status ?? 'failed';
|
||||
if (grades === 'ready') return nothingAnswered ? 'failed' : 'ready';
|
||||
return grades;
|
||||
}
|
||||
|
||||
function mostlyOf(rows: readonly FacetRow[]): FacetRow | null {
|
||||
return rows.reduce<FacetRow | null>(
|
||||
(best, row) => (!isUnknownClass(row.key) && (!best || row.lengthM > best.lengthM) ? row : best),
|
||||
null
|
||||
);
|
||||
}
|
||||
|
||||
/**
|
||||
* MAP-03 and MAP-04: what OpenStreetMap records for the ways along a hike,
|
||||
* switched on by the traveller. One read serves every facet: the ways under
|
||||
* the line are asked of the router once, matched on foot, and their values of
|
||||
* OpenStreetMap once, and each stretch is placed against the tour's own line
|
||||
* 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.
|
||||
*/
|
||||
export function useHikeWays(input: {
|
||||
tripId: number | string;
|
||||
/** The tour's own line, or null while there is none. */
|
||||
route: [number, number][] | null;
|
||||
/** False while the line is being planned anew: the card stays, and waits for the new one. */
|
||||
current?: boolean;
|
||||
}): HikeWays {
|
||||
const { tripId, route, current = true } = 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 { ways, grades } = state;
|
||||
const spec = HIKE_FACETS[facet];
|
||||
const strays = useMemo(() => (ways && line ? straysFrom(ways, line) : NO_STRAYS), [ways, line]);
|
||||
const classOf = useMemo(() => (grades ? facetClassifier(spec, grades, strays) : null), [spec, grades, strays]);
|
||||
const rows = useMemo(
|
||||
() => (ways && grades?.status === 'ready' && classOf ? facetRows(spec, ways, grades, classOf) : []),
|
||||
[spec, ways, grades, classOf]
|
||||
);
|
||||
// Not a single way could be asked about: said once with a way to ask again, not as a row covering the whole hike.
|
||||
const nothingAnswered = rows.length > 0 && rows.every((row) => row.key === 'unasked');
|
||||
const status = !on ? 'off' : current ? statusOf(state, nothingAnswered) : 'loading';
|
||||
const focus = rows.some((row) => row.key === picked) ? picked : null;
|
||||
|
||||
useEffect(() => {
|
||||
if (status !== 'ready' || !ways || !classOf) return;
|
||||
const overlay = facetOverlay(spec, ways, rows, classOf, focus);
|
||||
const highlight = useTourRouteHighlightStore.getState();
|
||||
highlight.show({ id: focus ? `${facet}-${focus}` : facet, ...overlay, paint: 'unknown' });
|
||||
return () => highlight.clear();
|
||||
}, [status, spec, facet, ways, rows, classOf, focus]);
|
||||
|
||||
const toggle = useCallback(() => {
|
||||
setShown((value) => !value);
|
||||
setPicked(null);
|
||||
}, []);
|
||||
const chooseFacet = useCallback((next: HikeFacet) => {
|
||||
setFacet(next);
|
||||
setPicked(null);
|
||||
}, []);
|
||||
const focusOn = useCallback((key: string) => setPicked((now) => (now === key ? null : key)), []);
|
||||
|
||||
const shownRows = status === 'ready' ? rows : [];
|
||||
return {
|
||||
available,
|
||||
shown: on,
|
||||
toggle,
|
||||
facet,
|
||||
chooseFacet,
|
||||
status,
|
||||
rows: shownRows,
|
||||
knownShare: knownShareOf(shownRows),
|
||||
mostly: mostlyOf(shownRows),
|
||||
highest: facet === 'sac' ? highestStep(shownRows) : null,
|
||||
focus,
|
||||
focusOn,
|
||||
retry: state.retry,
|
||||
};
|
||||
}
|
||||
@@ -1,124 +0,0 @@
|
||||
import { useCallback, useEffect, useMemo, useState } from 'react';
|
||||
import { useTourRouteHighlightStore } from '../../../store/tourRouteHighlightStore';
|
||||
import type { ClassShare, RouteWayEdge, RouteWays } from '../bike/routeWays';
|
||||
import { useRouteWays, type RouteWaysState } from '../bike/useRouteWays';
|
||||
import { straysFrom } from './routeMatch';
|
||||
import {
|
||||
highestStep,
|
||||
isSacStep,
|
||||
sacClassifier,
|
||||
sacOverlay,
|
||||
sacShares,
|
||||
sacTagOf,
|
||||
unclearTags,
|
||||
type SacClass,
|
||||
} from './sacGrades';
|
||||
|
||||
type SacGradesStatus = 'off' | 'loading' | 'offline' | 'failed' | 'ready';
|
||||
|
||||
export interface SacGradeRow extends ClassShare<SacClass> {
|
||||
/** What OpenStreetMap wrote in `sac_scale` for this class's ways: the step's value, or the unclear ones. */
|
||||
tags: string[];
|
||||
}
|
||||
|
||||
export interface SacGrades {
|
||||
/** Whether the card has a line to describe at all. */
|
||||
available: boolean;
|
||||
/** Whether the grades are switched on: only then are they asked for and drawn on the map. */
|
||||
shown: boolean;
|
||||
toggle: () => void;
|
||||
status: SacGradesStatus;
|
||||
rows: SacGradeRow[];
|
||||
/** Of the whole line, 0 to 1, the share whose step is known. */
|
||||
knownShare: number;
|
||||
/** The highest step known along the line, or null. */
|
||||
highest: number | null;
|
||||
/** The class drawn alone on the map, or null while every class is. */
|
||||
focus: SacClass | null;
|
||||
focusOn: (key: SacClass) => void;
|
||||
retry: () => void;
|
||||
}
|
||||
|
||||
const NO_STRAYS: ReadonlySet<RouteWayEdge> = new Set();
|
||||
|
||||
function statusOf(state: RouteWaysState, nothingAnswered: boolean): SacGradesStatus {
|
||||
if (state.status === 'idle') return 'off';
|
||||
if (state.status !== 'ready') return state.status;
|
||||
const grades = state.grades?.status ?? 'failed';
|
||||
if (grades === 'ready') return nothingAnswered ? 'failed' : 'ready';
|
||||
return grades;
|
||||
}
|
||||
|
||||
function rowsOf(
|
||||
ways: RouteWays,
|
||||
grades: NonNullable<RouteWaysState['grades']>,
|
||||
classOf: (edge: RouteWayEdge) => SacClass
|
||||
): SacGradeRow[] {
|
||||
return sacShares(ways, classOf).map((row) => ({
|
||||
...row,
|
||||
tags: isSacStep(row.key) ? [sacTagOf(row.key)] : unclearTags(ways, grades, (edge) => classOf(edge) === row.key),
|
||||
}));
|
||||
}
|
||||
|
||||
/**
|
||||
* MAP-03: the SAC grades along a hike, switched on by the traveller. Only
|
||||
* then are the ways under the line asked of the router and their grades of
|
||||
* OpenStreetMap, and the grades are 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.
|
||||
*/
|
||||
export function useSacGrades(input: {
|
||||
tripId: number | string;
|
||||
/** The tour's own line, or null while there is none. */
|
||||
route: [number, number][] | null;
|
||||
/** False while the line is being planned anew: the card stays, and waits for the new one. */
|
||||
current?: boolean;
|
||||
}): SacGrades {
|
||||
const { tripId, route, current = true } = input;
|
||||
const available = !!route && route.length >= 2;
|
||||
const [shown, setShown] = useState(false);
|
||||
const [picked, setPicked] = useState<SacClass | null>(null);
|
||||
const on = available && shown;
|
||||
const line = current ? route : null;
|
||||
const state = useRouteWays({ tripId, active: on, route: line, matching: 'foot', withGrades: true });
|
||||
const { ways, grades } = state;
|
||||
const strays = useMemo(() => (ways && line ? straysFrom(ways, line) : NO_STRAYS), [ways, line]);
|
||||
const classOf = useMemo(() => (grades ? sacClassifier(grades, strays) : null), [grades, strays]);
|
||||
const rows = useMemo(
|
||||
() => (ways && grades?.status === 'ready' && classOf ? rowsOf(ways, grades, classOf) : []),
|
||||
[ways, grades, classOf]
|
||||
);
|
||||
// Not a single way could be asked about: said once with a way to ask again, not as a row covering the whole hike.
|
||||
const nothingAnswered = rows.length > 0 && rows.every((row) => row.key === 'unasked');
|
||||
const status = !on ? 'off' : current ? statusOf(state, nothingAnswered) : 'loading';
|
||||
const focus = rows.some((row) => row.key === picked) ? picked : null;
|
||||
|
||||
useEffect(() => {
|
||||
if (status !== 'ready' || !ways || !classOf) return;
|
||||
const overlay = sacOverlay(ways, rows, classOf, focus);
|
||||
const highlight = useTourRouteHighlightStore.getState();
|
||||
const id = focus ? `sac-${focus}` : 'sac';
|
||||
highlight.show({ id, lines: overlay.lines, paint: 'unknown', paints: overlay.paints });
|
||||
return () => highlight.clear();
|
||||
}, [status, ways, rows, classOf, focus]);
|
||||
|
||||
const toggle = useCallback(() => {
|
||||
setShown((value) => !value);
|
||||
setPicked(null);
|
||||
}, []);
|
||||
const focusOn = useCallback((key: SacClass) => setPicked((current) => (current === key ? null : key)), []);
|
||||
|
||||
const shownRows = status === 'ready' ? rows : [];
|
||||
return {
|
||||
available,
|
||||
shown: on,
|
||||
toggle,
|
||||
status,
|
||||
rows: shownRows,
|
||||
knownShare: shownRows.filter((row) => isSacStep(row.key)).reduce((sum, row) => sum + row.share, 0),
|
||||
highest: highestStep(shownRows),
|
||||
focus,
|
||||
focusOn,
|
||||
retry: state.retry,
|
||||
};
|
||||
}
|
||||
@@ -0,0 +1,98 @@
|
||||
import type { TourTrailGrade } from '@trek/shared';
|
||||
import { describe, expect, it } from 'vitest';
|
||||
import { SURFACE_FACET, WAY_FACET } from './wayTagFacets';
|
||||
|
||||
// FE-HIKE-WAYTAG-001 to FE-HIKE-WAYTAG-006
|
||||
|
||||
const grade = (values: Partial<TourTrailGrade>): TourTrailGrade => ({
|
||||
id: 1,
|
||||
mtb_scale: null,
|
||||
mtb_scale_uphill: null,
|
||||
mtb_scale_imba: null,
|
||||
sac_scale: null,
|
||||
sac_scale_unclear: null,
|
||||
surface: null,
|
||||
highway: null,
|
||||
...values,
|
||||
});
|
||||
|
||||
const surfaceOf = (surface: string) => SURFACE_FACET.read(grade({ surface })).key;
|
||||
const wayOf = (highway: string) => WAY_FACET.read(grade({ highway })).key;
|
||||
|
||||
describe('wayTagFacets (MAP-04)', () => {
|
||||
it('FE-HIKE-WAYTAG-001: sorts the recorded surfaces into their groups', () => {
|
||||
expect(['asphalt', 'concrete:plates', 'paved'].map(surfaceOf)).toEqual(['paved', 'paved', 'paved']);
|
||||
expect(['sett', 'cobblestone', 'paving_stones'].map(surfaceOf)).toEqual(['sett', 'sett', 'sett']);
|
||||
expect(['compacted', 'fine_gravel'].map(surfaceOf)).toEqual(['compacted', 'compacted']);
|
||||
expect(['gravel', 'pebblestone'].map(surfaceOf)).toEqual(['gravel', 'gravel']);
|
||||
expect(['ground', 'dirt', 'earth', 'mud', 'sand'].map(surfaceOf)).toEqual(Array(5).fill('ground'));
|
||||
expect(['grass', 'rock', 'stepping_stones', 'wood', 'metal_grid', 'unpaved', 'snow'].map(surfaceOf)).toEqual([
|
||||
'grass',
|
||||
'rock',
|
||||
'rock',
|
||||
'wood',
|
||||
'wood',
|
||||
'unpaved',
|
||||
'other',
|
||||
]);
|
||||
});
|
||||
|
||||
it('FE-HIKE-WAYTAG-002: sorts the recorded kinds of way into their groups', () => {
|
||||
expect(['path', 'bridleway', 'footway', 'pedestrian', 'steps', 'track'].map(wayOf)).toEqual([
|
||||
'path',
|
||||
'path',
|
||||
'footway',
|
||||
'footway',
|
||||
'steps',
|
||||
'track',
|
||||
]);
|
||||
expect(['cycleway', 'residential', 'service', 'secondary', 'road', 'via_ferrata', 'corridor'].map(wayOf)).toEqual([
|
||||
'cycleway',
|
||||
'quietRoad',
|
||||
'quietRoad',
|
||||
'road',
|
||||
'road',
|
||||
'viaFerrata',
|
||||
'other',
|
||||
]);
|
||||
});
|
||||
|
||||
it('FE-HIKE-WAYTAG-003: a value no group takes, or two values at once, is unclear, never the nearest group', () => {
|
||||
expect(surfaceOf('gravel;ground')).toBe('unclear');
|
||||
expect(surfaceOf('gravvel')).toBe('unclear');
|
||||
expect(wayOf('path;track')).toBe('unclear');
|
||||
expect(wayOf('motorway')).toBe('unclear');
|
||||
});
|
||||
|
||||
it('FE-HIKE-WAYTAG-004: looks a value up as OpenStreetMap writes it, and hands it on as it was written', () => {
|
||||
expect(SURFACE_FACET.read(grade({ surface: 'Asphalt' }))).toEqual({ key: 'paved', written: 'Asphalt' });
|
||||
expect(WAY_FACET.read(grade({ highway: ' Track ' }))).toEqual({ key: 'track', written: ' Track ' });
|
||||
});
|
||||
|
||||
it('FE-HIKE-WAYTAG-005: a way without the value is untagged, whatever else it records', () => {
|
||||
expect(SURFACE_FACET.read(grade({ highway: 'path', sac_scale: 'hiking' }))).toEqual({
|
||||
key: 'untagged',
|
||||
written: null,
|
||||
});
|
||||
expect(WAY_FACET.read(grade({ surface: 'gravel' })).key).toBe('untagged');
|
||||
expect(WAY_FACET.read(undefined).key).toBe('untagged');
|
||||
});
|
||||
|
||||
it('FE-HIKE-WAYTAG-006: lists the groups in order and paints each under its facet name', () => {
|
||||
expect(SURFACE_FACET.classes[0]).toBe('paved');
|
||||
expect(SURFACE_FACET.classes).toContain('unpaved');
|
||||
expect(WAY_FACET.classes).toEqual([
|
||||
'path',
|
||||
'footway',
|
||||
'steps',
|
||||
'track',
|
||||
'cycleway',
|
||||
'quietRoad',
|
||||
'road',
|
||||
'viaFerrata',
|
||||
'other',
|
||||
]);
|
||||
expect(SURFACE_FACET.paint('ground')).toBe('surface:ground');
|
||||
expect(WAY_FACET.paint('quietRoad')).toBe('way:quietRoad');
|
||||
});
|
||||
});
|
||||
@@ -0,0 +1,92 @@
|
||||
import type { TourTrailGrade } from '@trek/shared';
|
||||
import type { HikeFacetSpec } from './hikeFacets';
|
||||
import type { HikePaintKey } from './hikePaint';
|
||||
|
||||
/**
|
||||
* MAP-04: the surfaces and the kinds of way along a hike, read from the
|
||||
* `surface` and `highway` values OpenStreetMap records for the ways under
|
||||
* it, never from the router's reading of them (normalised, and a default
|
||||
* where nothing is recorded). Each group lists the OpenStreetMap values it
|
||||
* takes, and only those: a value outside every group, or two joined by `;`,
|
||||
* is unclear rather than placed in the nearest group. The examples a group
|
||||
* is named after promise nothing about coverage.
|
||||
*/
|
||||
|
||||
type HikeSurfaceClass =
|
||||
| 'paved'
|
||||
| 'sett'
|
||||
| 'compacted'
|
||||
| 'gravel'
|
||||
| 'ground'
|
||||
| 'grass'
|
||||
| 'rock'
|
||||
| 'wood'
|
||||
| 'unpaved'
|
||||
| 'other';
|
||||
|
||||
type HikeWayClass = 'path' | 'footway' | 'steps' | 'track' | 'cycleway' | 'quietRoad' | 'road' | 'viaFerrata' | 'other';
|
||||
|
||||
/** The `surface` values of each group, smooth and sealed first, then loose, natural and built over. */
|
||||
const SURFACE_GROUPS: Readonly<Record<HikeSurfaceClass, readonly string[]>> = {
|
||||
paved: ['asphalt', 'concrete', 'concrete:plates', 'concrete:lanes', 'chipseal', 'paved'],
|
||||
sett: ['paving_stones', 'paving_stones:lanes', 'sett', 'cobblestone', 'unhewn_cobblestone', 'bricks', 'grass_paver'],
|
||||
compacted: ['compacted', 'fine_gravel'],
|
||||
gravel: ['gravel', 'pebblestone', 'shells'],
|
||||
ground: ['ground', 'dirt', 'earth', 'soil', 'mud', 'sand', 'woodchips'],
|
||||
grass: ['grass'],
|
||||
rock: ['rock', 'bare_rock', 'stone', 'stepping_stones'],
|
||||
wood: ['wood', 'metal', 'metal_grid'],
|
||||
// OpenStreetMap only says the way is not paved, not what it is instead.
|
||||
unpaved: ['unpaved'],
|
||||
other: ['artificial_turf', 'tartan', 'rubber', 'clay', 'acrylic', 'carpet', 'plastic', 'snow', 'ice', 'salt'],
|
||||
};
|
||||
|
||||
/** The `highway` values of each group, the ways made for walking first, then those shared with traffic. */
|
||||
const WAY_GROUPS: Readonly<Record<HikeWayClass, readonly string[]>> = {
|
||||
path: ['path', 'bridleway'],
|
||||
footway: ['footway', 'pedestrian'],
|
||||
steps: ['steps'],
|
||||
track: ['track'],
|
||||
cycleway: ['cycleway'],
|
||||
quietRoad: ['residential', 'living_street', 'service', 'unclassified'],
|
||||
road: [
|
||||
'tertiary',
|
||||
'tertiary_link',
|
||||
'secondary',
|
||||
'secondary_link',
|
||||
'primary',
|
||||
'primary_link',
|
||||
'trunk',
|
||||
'trunk_link',
|
||||
'road',
|
||||
],
|
||||
viaFerrata: ['via_ferrata'],
|
||||
other: ['corridor', 'platform', 'busway', 'construction', 'elevator'],
|
||||
};
|
||||
|
||||
/**
|
||||
* A facet that sorts one written value by its groups. The value is looked up
|
||||
* as OpenStreetMap writes its values, in lower case and trimmed; what the
|
||||
* card shows is the value as it was written.
|
||||
*/
|
||||
function groupedFacet<K extends string>(
|
||||
field: 'surface' | 'highway',
|
||||
groups: Readonly<Record<K, readonly string[]>>,
|
||||
paint: (key: K) => HikePaintKey
|
||||
): HikeFacetSpec<K> {
|
||||
const classes = Object.keys(groups) as K[];
|
||||
const groupOf = new Map<string, K>(classes.flatMap((key) => groups[key].map((value) => [value, key] as const)));
|
||||
return {
|
||||
classes,
|
||||
read: (grade: TourTrailGrade | undefined) => {
|
||||
const written = grade?.[field] ?? null;
|
||||
if (!written) return { key: 'untagged', written: null };
|
||||
return { key: groupOf.get(written.trim().toLowerCase()) ?? 'unclear', written };
|
||||
},
|
||||
paint,
|
||||
};
|
||||
}
|
||||
|
||||
export const SURFACE_FACET = groupedFacet<HikeSurfaceClass>('surface', SURFACE_GROUPS, (key) => `surface:${key}`);
|
||||
|
||||
export const WAY_FACET = groupedFacet<HikeWayClass>('highway', WAY_GROUPS, (key) => `way:${key}`);
|
||||
@@ -23,10 +23,10 @@ vi.mock('../paddle/PaddleCheckCard', () => ({
|
||||
),
|
||||
}))
|
||||
|
||||
// MAP-03: the SAC card is its own suite's; here it only has to sit where the rails put it, with what they hand it.
|
||||
vi.mock('../hike/SacGradesCard', () => ({
|
||||
SacGradesCard: ({ route, routeCurrent, routingLimit }: { route: [number, number][] | null; routeCurrent?: boolean; routingLimit: number | null }) => (
|
||||
<div data-testid="sac-grades-card" data-route={route ? route.length : 'none'} data-current={String(routeCurrent ?? true)}
|
||||
// MAP-03 and MAP-04: the hike's ways card is its own suite's; here it only has to sit where the rails put it, with what they hand it.
|
||||
vi.mock('../hike/HikeWaysCard', () => ({
|
||||
HikeWaysCard: ({ route, routeCurrent, routingLimit }: { route: [number, number][] | null; routeCurrent?: boolean; routingLimit: number | null }) => (
|
||||
<div data-testid="hike-ways-card" data-route={route ? route.length : 'none'} data-current={String(routeCurrent ?? true)}
|
||||
data-limit={String(routingLimit)} />
|
||||
),
|
||||
}))
|
||||
@@ -1024,16 +1024,16 @@ describe('a run in the planner rails (RUN-01)', () => {
|
||||
})
|
||||
})
|
||||
|
||||
describe('SAC grades in the planner rails (MAP-03)', () => {
|
||||
describe('the hike ways card in the planner rails (MAP-03, MAP-04)', () => {
|
||||
it('MAP03-RAIL-001: a hike being edited carries the card with its line, whether it is planned now, and its routing limit', () => {
|
||||
const { unmount } = render(<TourPlannerRail planner={planner({ mode: { type: 'new-draft' }, routeCurrent: true, maxHikingDifficulty: 3 })} />)
|
||||
const card = screen.getByTestId('sac-grades-card')
|
||||
const card = screen.getByTestId('hike-ways-card')
|
||||
expect(card).toHaveAttribute('data-route', '2')
|
||||
expect(card).toHaveAttribute('data-current', 'true')
|
||||
expect(card).toHaveAttribute('data-limit', '3')
|
||||
unmount()
|
||||
render(<TourPlannerRail planner={planner({ mode: { type: 'new-draft' }, routeCurrent: false })} />)
|
||||
expect(screen.getByTestId('sac-grades-card')).toHaveAttribute('data-current', 'false')
|
||||
expect(screen.getByTestId('hike-ways-card')).toHaveAttribute('data-current', 'false')
|
||||
})
|
||||
|
||||
it('MAP03-RAIL-002: a hike import carries it without a routing limit; a bike tour and a bike import do not', () => {
|
||||
@@ -1043,13 +1043,13 @@ describe('SAC grades in the planner rails (MAP-03)', () => {
|
||||
readOnlyGpxAnalysis: analyzeRouteGeometry(geometry),
|
||||
})
|
||||
const hike = render(<TourPlannerRail planner={imported('hike')} />)
|
||||
expect(screen.getByTestId('sac-grades-card')).toHaveAttribute('data-route', '3')
|
||||
expect(screen.getByTestId('sac-grades-card')).toHaveAttribute('data-limit', 'null')
|
||||
expect(screen.getByTestId('hike-ways-card')).toHaveAttribute('data-route', '3')
|
||||
expect(screen.getByTestId('hike-ways-card')).toHaveAttribute('data-limit', 'null')
|
||||
hike.unmount()
|
||||
const bikeImport = render(<TourPlannerRail planner={imported('bike')} />)
|
||||
expect(screen.queryByTestId('sac-grades-card')).toBeNull()
|
||||
expect(screen.queryByTestId('hike-ways-card')).toBeNull()
|
||||
bikeImport.unmount()
|
||||
render(<TourPlannerRail planner={planner({ mode: { type: 'new-draft' }, tourType: 'bike', isBike: true, routeCurrent: false })} />)
|
||||
expect(screen.queryByTestId('sac-grades-card')).toBeNull()
|
||||
expect(screen.queryByTestId('hike-ways-card')).toBeNull()
|
||||
})
|
||||
})
|
||||
|
||||
@@ -53,7 +53,7 @@ import { BikeWaysCard } from '../bike/BikeWaysCard'
|
||||
import { RunWaysCard } from '../run/RunWaysCard'
|
||||
import { HorseRoutesCard } from '../horse/HorseRoutesCard'
|
||||
import { RecordedRideCard } from '../horse/RecordedRideFacts'
|
||||
import { SacGradesCard } from '../hike/SacGradesCard'
|
||||
import { HikeWaysCard } from '../hike/HikeWaysCard'
|
||||
import { walkTimeFields } from '../walk/walkTimeFields'
|
||||
|
||||
const STATUS_KEY: Partial<Record<TourPlannerStatus, string>> = {
|
||||
@@ -351,7 +351,7 @@ function GpxViewRail({
|
||||
)}
|
||||
</RailCard>
|
||||
{tour.tour_type === 'hike' && (
|
||||
<SacGradesCard tripId={planner.tripId} route={analysis?.routeCoordinates ?? null} routingLimit={null} />
|
||||
<HikeWaysCard tripId={planner.tripId} route={analysis?.routeCoordinates ?? null} routingLimit={null} />
|
||||
)}
|
||||
</div>
|
||||
{tour.tour_type === 'kayak' && (
|
||||
@@ -475,7 +475,7 @@ export function TourPlannerRail({ planner,
|
||||
<HorseRoutesCard planner={planner} />
|
||||
|
||||
{planner.tourType === 'hike' && (
|
||||
<SacGradesCard tripId={planner.tripId} route={planner.route} routeCurrent={planner.routeCurrent}
|
||||
<HikeWaysCard tripId={planner.tripId} route={planner.route} routeCurrent={planner.routeCurrent}
|
||||
routingLimit={planner.maxHikingDifficulty} />
|
||||
)}
|
||||
|
||||
|
||||
@@ -1,10 +1,11 @@
|
||||
import { create } from 'zustand';
|
||||
|
||||
/**
|
||||
* BIK-01 to BIK-05, RUN-02 and MAP-03: the stretches of the planned route a
|
||||
* card points at (a surface, a kind of way, a trail or hiking grade, a hike's
|
||||
* SAC grades), drawn over the route on the planner map in the colour of what
|
||||
* they are. `paint` names a token of the route paints (bike and hike),
|
||||
* BIK-01 to BIK-05, RUN-02, MAP-03 and MAP-04: the stretches of the planned
|
||||
* route a card points at (a surface, a kind of way, a trail or hiking grade,
|
||||
* and on a hike its SAC grades, surfaces or kinds of way as OpenStreetMap
|
||||
* records them), drawn over the route on the planner map in the colour of
|
||||
* what they are. `paint` names a token of the route paints (bike and hike),
|
||||
* resolved where the map is drawn, so the theme decides the colour.
|
||||
*/
|
||||
export interface RouteHighlight {
|
||||
@@ -12,7 +13,7 @@ export interface RouteHighlight {
|
||||
id: string;
|
||||
lines: [number, number][][];
|
||||
paint: string;
|
||||
/** One paint per line where the lines differ, as when every SAC grade is drawn at once; else `paint` for all. */
|
||||
/** One paint per line where they differ, as when a hike's whole facet is drawn at once; else `paint` for all. */
|
||||
paints?: readonly string[];
|
||||
}
|
||||
|
||||
|
||||
@@ -1,7 +1,8 @@
|
||||
/* ============================================================================
|
||||
Hikes on the planner map and in its cards (MAP-03): the SAC hiking grades
|
||||
OpenStreetMap gives the ways along a hike, T1 to T6. Global, since the 2D,
|
||||
GL and 3D maps and the planner's cards all read the tokens.
|
||||
Hikes on the planner map and in its cards (MAP-03, MAP-04): the SAC hiking
|
||||
grades OpenStreetMap gives the ways along a hike, T1 to T6, and the
|
||||
surfaces and kinds of way it records for them. Global, since the 2D, GL and
|
||||
3D maps and the planner's cards all read the tokens.
|
||||
========================================================================== */
|
||||
|
||||
/* One ramp of a single hue, from light to deep as the steps grow harder, so
|
||||
@@ -28,3 +29,56 @@
|
||||
--hike-sac-t5: #a78bfa;
|
||||
--hike-sac-t6: #ddd6fe;
|
||||
}
|
||||
|
||||
/* The surfaces OpenStreetMap records along a hike. Where a group means what a
|
||||
bike tour's surface means (asphalt, compacted, gravel, natural ground) it
|
||||
takes the bike's colour, so one surface reads the same on every tour; the
|
||||
groups only a hike shows get their own. A colour sorts the surface, it
|
||||
never says a way can or cannot be walked. */
|
||||
:root {
|
||||
--hike-surface-paved: var(--bike-surface-paved);
|
||||
--hike-surface-sett: var(--bike-surface-rough);
|
||||
--hike-surface-compacted: var(--bike-surface-compacted);
|
||||
--hike-surface-gravel: var(--bike-surface-gravel);
|
||||
--hike-surface-ground: var(--bike-surface-ground);
|
||||
--hike-surface-grass: #65a30d;
|
||||
--hike-surface-rock: #57534e;
|
||||
--hike-surface-wood: #be123c;
|
||||
--hike-surface-unpaved: #ca8a04;
|
||||
--hike-surface-other: #c026d3;
|
||||
}
|
||||
|
||||
.dark {
|
||||
--hike-surface-grass: #a3e635;
|
||||
--hike-surface-rock: #d6d3d1;
|
||||
--hike-surface-wood: #fb7185;
|
||||
--hike-surface-unpaved: #facc15;
|
||||
--hike-surface-other: #e879f9;
|
||||
}
|
||||
|
||||
/* The kinds of way OpenStreetMap records along a hike: the ways made for
|
||||
walking in warm colours, those shared with bikes and traffic in cool ones,
|
||||
the roads darkest. */
|
||||
:root {
|
||||
--hike-way-path: #e11d48;
|
||||
--hike-way-footway: #f97316;
|
||||
--hike-way-steps: #7c3aed;
|
||||
--hike-way-track: #a16207;
|
||||
--hike-way-cycleway: #2563eb;
|
||||
--hike-way-quiet-road: #0d9488;
|
||||
--hike-way-road: #334155;
|
||||
--hike-way-via-ferrata: #c026d3;
|
||||
--hike-way-other: #0891b2;
|
||||
}
|
||||
|
||||
.dark {
|
||||
--hike-way-path: #fb7185;
|
||||
--hike-way-footway: #fdba74;
|
||||
--hike-way-steps: #c4b5fd;
|
||||
--hike-way-track: #facc15;
|
||||
--hike-way-cycleway: #60a5fa;
|
||||
--hike-way-quiet-road: #2dd4bf;
|
||||
--hike-way-road: #e2e8f0;
|
||||
--hike-way-via-ferrata: #f0abfc;
|
||||
--hike-way-other: #67e8f9;
|
||||
}
|
||||
|
||||
@@ -10,9 +10,11 @@ import { Body, Controller, HttpCode, Post, UseGuards } from '@nestjs/common';
|
||||
import { tourTrailGradeResponseSchema } from '@trek/shared';
|
||||
|
||||
/**
|
||||
* BIK-05 and MAP-03 POST /api/trips/:tripId/tours/trail-grades: the trail
|
||||
* grades of the ways a tour runs on, mountain bike and SAC alike. A read, so anyone who sees the trip may
|
||||
* ask; the ways come in the body because the draft may not be saved yet.
|
||||
* BIK-05, MAP-03 and MAP-04 POST /api/trips/:tripId/tours/trail-grades: what
|
||||
* OpenStreetMap records for the ways a tour runs on, the mountain bike and SAC
|
||||
* grades, the surface and the kind of way alike. A read, so anyone who sees
|
||||
* the trip may ask; the ways come in the body because the draft may not be
|
||||
* saved yet.
|
||||
*/
|
||||
@Controller('api/trips/:tripId/tours')
|
||||
@UseGuards(AddonGuard, JwtAuthGuard, TripAccessGuard)
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
import { OverpassElementsClient } from '../overpass/overpass-elements.service';
|
||||
import { Injectable } from '@nestjs/common';
|
||||
import {
|
||||
SAC_SCALE_UNCLEAR_MAX,
|
||||
OSM_TAG_VALUE_MAX,
|
||||
sacScaleSchema,
|
||||
type TourTrailGrade,
|
||||
type TourTrailGradeRequest,
|
||||
@@ -30,6 +30,15 @@ function step(value: string | undefined, top: number, signed: boolean): string |
|
||||
return signed ? `${match[1]}${match[2] ?? ''}` : match[1];
|
||||
}
|
||||
|
||||
/**
|
||||
* MAP-04: a tag's value as OpenStreetMap wrote it, only trimmed and bounded,
|
||||
* never normalised: the planner groups it and says what was written.
|
||||
*/
|
||||
function written(value: string | undefined): string | null {
|
||||
const trimmed = value?.trim() ?? '';
|
||||
return trimmed ? trimmed.slice(0, OSM_TAG_VALUE_MAX) : null;
|
||||
}
|
||||
|
||||
/**
|
||||
* MAP-03: a way's `sac_scale` as one of the scale's six values, read as
|
||||
* OpenStreetMap wrote it. A value that names no single step (`T3`,
|
||||
@@ -37,31 +46,33 @@ function step(value: string | undefined, top: number, signed: boolean): string |
|
||||
* a step, so the planner says unclear rather than picking one.
|
||||
*/
|
||||
function sacOf(value: string | undefined): Pick<TourTrailGrade, 'sac_scale' | 'sac_scale_unclear'> {
|
||||
const written = value?.trim() ?? '';
|
||||
if (!written) return { sac_scale: null, sac_scale_unclear: null };
|
||||
const known = sacScaleSchema.safeParse(written.toLowerCase());
|
||||
const text = written(value);
|
||||
if (!text) return { sac_scale: null, sac_scale_unclear: null };
|
||||
const known = sacScaleSchema.safeParse(text.toLowerCase());
|
||||
if (known.success) return { sac_scale: known.data, sac_scale_unclear: null };
|
||||
return { sac_scale: null, sac_scale_unclear: written.slice(0, SAC_SCALE_UNCLEAR_MAX) };
|
||||
return { sac_scale: null, sac_scale_unclear: text };
|
||||
}
|
||||
|
||||
function gradeOf(id: number, tags: Record<string, string> = {}): TourTrailGrade | null {
|
||||
const grade = {
|
||||
const grade: TourTrailGrade = {
|
||||
id,
|
||||
mtb_scale: step(tags['mtb:scale'], 6, true),
|
||||
mtb_scale_uphill: step(tags['mtb:scale:uphill'], 5, true),
|
||||
mtb_scale_imba: step(tags['mtb:scale:imba'], 4, false),
|
||||
...sacOf(tags['sac_scale']),
|
||||
surface: written(tags['surface']),
|
||||
highway: written(tags['highway']),
|
||||
};
|
||||
const graded = grade.mtb_scale || grade.mtb_scale_uphill || grade.mtb_scale_imba;
|
||||
return graded || grade.sac_scale || grade.sac_scale_unclear ? grade : null;
|
||||
// A way none of whose values is recorded is left out: the planner reads its absence as unknown.
|
||||
return Object.entries(grade).some(([key, value]) => key !== 'id' && value !== null) ? grade : null;
|
||||
}
|
||||
|
||||
/**
|
||||
* BIK-05 and MAP-03: the trail grades of the ways a tour runs on (the mountain
|
||||
* bike scales, and the SAC hiking scale of a hike), read from OpenStreetMap
|
||||
* through the operator's Overpass mirrors. What was read is kept
|
||||
* a day, so the same tour asked about again costs the mirrors nothing; ways a
|
||||
* mirror could not answer are said, never guessed.
|
||||
* BIK-05, MAP-03 and MAP-04: what OpenStreetMap records for the ways a tour
|
||||
* runs on (the mountain bike scales; the SAC hiking scale, the surface and the
|
||||
* kind of way of a hike), read through the operator's Overpass mirrors. What
|
||||
* was read is kept a day, so the same tour asked about again costs the mirrors
|
||||
* nothing; ways a mirror could not answer are said, never guessed.
|
||||
*/
|
||||
@Injectable()
|
||||
export class TourTrailGradeService {
|
||||
|
||||
@@ -283,7 +283,7 @@ describe('Bike tours e2e (real guards + temp SQLite)', () => {
|
||||
it('answers the grades of the ways asked about to anyone who sees the trip', async () => {
|
||||
overpassQuery.mockResolvedValueOnce([
|
||||
{ type: 'way', id: 11, tags: { 'mtb:scale': '2', 'mtb:scale:uphill': '1' } },
|
||||
{ type: 'way', id: 12, tags: { sac_scale: 'mountain_hiking' } },
|
||||
{ type: 'way', id: 12, tags: { sac_scale: 'mountain_hiking', surface: 'ground', highway: 'path' } },
|
||||
]);
|
||||
const res = await ask({ way_ids: [11, 12] }, VIEWER);
|
||||
expect(res.status).toBe(200);
|
||||
@@ -296,6 +296,8 @@ describe('Bike tours e2e (real guards + temp SQLite)', () => {
|
||||
mtb_scale_imba: null,
|
||||
sac_scale: null,
|
||||
sac_scale_unclear: null,
|
||||
surface: null,
|
||||
highway: null,
|
||||
},
|
||||
{
|
||||
id: 12,
|
||||
@@ -304,6 +306,8 @@ describe('Bike tours e2e (real guards + temp SQLite)', () => {
|
||||
mtb_scale_imba: null,
|
||||
sac_scale: 'mountain_hiking',
|
||||
sac_scale_unclear: null,
|
||||
surface: 'ground',
|
||||
highway: 'path',
|
||||
},
|
||||
],
|
||||
unanswered: [],
|
||||
|
||||
@@ -4,7 +4,7 @@ import { Test } from '@nestjs/testing';
|
||||
|
||||
import { beforeEach, describe, expect, it, vi } from 'vitest';
|
||||
|
||||
// TOUR-GRADE-001 to TOUR-GRADE-008
|
||||
// TOUR-GRADE-001 to TOUR-GRADE-010
|
||||
|
||||
const query = vi.fn<(q: string, caller: string) => Promise<unknown[]>>();
|
||||
let service: TourTrailGradeService;
|
||||
@@ -20,20 +20,21 @@ beforeEach(async () => {
|
||||
const way = (id: number, tags: Record<string, string>) => ({ type: 'way', id, tags });
|
||||
const NO_SAC = { sac_scale: null, sac_scale_unclear: null };
|
||||
const NO_MTB = { mtb_scale: null, mtb_scale_uphill: null, mtb_scale_imba: null };
|
||||
const NO_WAY = { surface: null, highway: null };
|
||||
|
||||
describe('TourTrailGradeService (BIK-05)', () => {
|
||||
it('TOUR-GRADE-001: reads each scale to its steps and leaves a way without one out', async () => {
|
||||
it('TOUR-GRADE-001: reads each scale to its steps and leaves a way without any value out', async () => {
|
||||
query.mockResolvedValue([
|
||||
way(1, { 'mtb:scale': '2+', 'mtb:scale:uphill': '3' }),
|
||||
way(2, { 'mtb:scale': 'S1', 'mtb:scale:imba': '2' }),
|
||||
way(3, { highway: 'track' }),
|
||||
way(3, { name: 'Forstweg' }),
|
||||
way(4, { 'mtb:scale': '9', 'mtb:scale:imba': 'hard' }),
|
||||
]);
|
||||
const answer = await service.grades({ way_ids: [1, 2, 3, 4, 5] });
|
||||
expect(answer).toEqual({
|
||||
ways: [
|
||||
{ ...NO_SAC, id: 1, mtb_scale: '2+', mtb_scale_uphill: '3', mtb_scale_imba: null },
|
||||
{ ...NO_SAC, id: 2, mtb_scale: '1', mtb_scale_uphill: null, mtb_scale_imba: '2' },
|
||||
{ ...NO_SAC, ...NO_WAY, id: 1, mtb_scale: '2+', mtb_scale_uphill: '3', mtb_scale_imba: null },
|
||||
{ ...NO_SAC, ...NO_WAY, id: 2, mtb_scale: '1', mtb_scale_uphill: null, mtb_scale_imba: '2' },
|
||||
],
|
||||
unanswered: [],
|
||||
});
|
||||
@@ -105,11 +106,11 @@ describe('TourTrailGradeService (BIK-05)', () => {
|
||||
]);
|
||||
const answer = await service.grades({ way_ids: [1, 2, 3, 4, 5, 6] });
|
||||
expect(answer.ways).toEqual([
|
||||
{ ...NO_MTB, id: 1, sac_scale: 'demanding_mountain_hiking', sac_scale_unclear: null },
|
||||
{ ...NO_MTB, id: 2, mtb_scale: '3', sac_scale: 'alpine_hiking', sac_scale_unclear: null },
|
||||
{ ...NO_MTB, id: 3, sac_scale: null, sac_scale_unclear: 'T3' },
|
||||
{ ...NO_MTB, id: 4, sac_scale: null, sac_scale_unclear: 'hiking;mountain_hiking' },
|
||||
{ ...NO_MTB, id: 6, sac_scale: null, sac_scale_unclear: `x${'y'.repeat(63)}` },
|
||||
{ ...NO_MTB, ...NO_WAY, id: 1, sac_scale: 'demanding_mountain_hiking', sac_scale_unclear: null },
|
||||
{ ...NO_MTB, ...NO_WAY, id: 2, mtb_scale: '3', sac_scale: 'alpine_hiking', sac_scale_unclear: null },
|
||||
{ ...NO_MTB, ...NO_WAY, id: 3, sac_scale: null, sac_scale_unclear: 'T3' },
|
||||
{ ...NO_MTB, ...NO_WAY, id: 4, sac_scale: null, sac_scale_unclear: 'hiking;mountain_hiking' },
|
||||
{ ...NO_MTB, ...NO_WAY, id: 6, sac_scale: null, sac_scale_unclear: `x${'y'.repeat(63)}` },
|
||||
]);
|
||||
});
|
||||
|
||||
@@ -117,6 +118,30 @@ describe('TourTrailGradeService (BIK-05)', () => {
|
||||
query.mockResolvedValue([
|
||||
way(1, { trail_visibility: 'bad', 'osmc:symbol': 'blue:white:blue_bar', highway: 'path', incline: '30%' }),
|
||||
]);
|
||||
expect(await service.grades({ way_ids: [1] })).toEqual({
|
||||
ways: [{ ...NO_MTB, ...NO_SAC, id: 1, surface: null, highway: 'path' }],
|
||||
unanswered: [],
|
||||
});
|
||||
});
|
||||
|
||||
it('TOUR-GRADE-009: hands the surface and the kind of way on as written, only trimmed and bounded (MAP-04)', async () => {
|
||||
query.mockResolvedValue([
|
||||
way(1, { surface: 'fine_gravel', highway: 'track' }),
|
||||
way(2, { surface: ' Asphalt ', highway: 'footway' }),
|
||||
way(3, { surface: 'gravel;ground' }),
|
||||
way(4, { surface: ' ', highway: `x${'y'.repeat(80)}` }),
|
||||
]);
|
||||
const answer = await service.grades({ way_ids: [1, 2, 3, 4] });
|
||||
expect(answer.ways).toEqual([
|
||||
{ ...NO_MTB, ...NO_SAC, id: 1, surface: 'fine_gravel', highway: 'track' },
|
||||
{ ...NO_MTB, ...NO_SAC, id: 2, surface: 'Asphalt', highway: 'footway' },
|
||||
{ ...NO_MTB, ...NO_SAC, id: 3, surface: 'gravel;ground', highway: null },
|
||||
{ ...NO_MTB, ...NO_SAC, id: 4, surface: null, highway: `x${'y'.repeat(63)}` },
|
||||
]);
|
||||
});
|
||||
|
||||
it('TOUR-GRADE-010: never reads a surface from anything but the surface tag (MAP-04)', async () => {
|
||||
query.mockResolvedValue([way(1, { tracktype: 'grade1', smoothness: 'good', 'surface:note': 'gravel' })]);
|
||||
expect(await service.grades({ way_ids: [1] })).toEqual({ ways: [], unanswered: [] });
|
||||
});
|
||||
});
|
||||
|
||||
+49
-10
@@ -521,8 +521,7 @@ const tours: TranslationStrings = {
|
||||
'الدرجات كما وضعها مساهمو OpenStreetMap. الطريق بلا درجة مجهول وليس سهلًا. ولا تقول شيئًا عن ملاءمة المسار لك.',
|
||||
'tours.bike.trail.loading': 'جارٍ قراءة درجات المسارات…',
|
||||
'tours.bike.trail.failed': 'تعذرت قراءة درجات المسارات هذه المرة.',
|
||||
'tours.hike.sac.title': 'درجات SAC',
|
||||
'tours.hike.sac.show': 'عرض على طول المسار',
|
||||
'tours.hike.ways.show': 'عرض على طول المسار',
|
||||
'tours.hike.sac.t1': 'المشي الطويل',
|
||||
'tours.hike.sac.t2': 'المشي الجبلي',
|
||||
'tours.hike.sac.t3': 'المشي الجبلي الشاق',
|
||||
@@ -531,23 +530,63 @@ const tours: TranslationStrings = {
|
||||
'tours.hike.sac.t6': 'المشي الصعب في التضاريس الألبية',
|
||||
'tours.hike.sac.untagged': 'لا درجة في OpenStreetMap',
|
||||
'tours.hike.sac.unclear': 'درجة غير واضحة',
|
||||
'tours.hike.sac.unmatched': 'الطريق غير مطابق بوضوح',
|
||||
'tours.hike.sac.unasked': 'تعذّر الاستعلام هذه المرة',
|
||||
'tours.hike.sac.unknown': 'غير معروف',
|
||||
'tours.hike.ways.unmatched': 'الطريق غير مطابق بوضوح',
|
||||
'tours.hike.ways.unasked': 'تعذّر الاستعلام هذه المرة',
|
||||
'tours.hike.ways.unknown': 'غير معروف',
|
||||
'tours.hike.sac.stepTip': 'مقياس المشي SAC {step}، من OpenStreetMap: sac_scale={tag}',
|
||||
'tours.hike.sac.untaggedTip': 'لا يعطي OpenStreetMap هذه الطرق قيمة sac_scale. غير معروف لا يعني سهلًا.',
|
||||
'tours.hike.sac.unclearTip': 'يكتب OpenStreetMap هنا {tags}، وهذا لا يحدد درجة واحدة.',
|
||||
'tours.hike.sac.unmatchedTip': 'لا يقع هنا أي طريق بوضوح تحت خط الجولة، لذلك لا تظهر درجة.',
|
||||
'tours.hike.sac.known': 'معروف',
|
||||
'tours.hike.ways.unmatchedTip': 'لا يقع هنا أي طريق بوضوح تحت خط الجولة، لذلك لا يُعرض له شيء.',
|
||||
'tours.hike.ways.known': 'معروف',
|
||||
'tours.hike.sac.knownTip': 'جزء المسار الذي يعطي OpenStreetMap درجته في SAC. الباقي غير معروف.',
|
||||
'tours.hike.sac.highest': 'الأعلى المعروف',
|
||||
'tours.hike.sac.routingLimit': 'حد التوجيه',
|
||||
'tours.hike.sac.routingLimitTip': 'أقصى ما يجوز لبحث المسار استخدامه: إعداد، وليس صعوبة الطرق.',
|
||||
'tours.hike.sac.aboveLimit': 'فوق الحد',
|
||||
'tours.hike.sac.loading': 'جارٍ قراءة درجات SAC…',
|
||||
'tours.hike.sac.offline': 'تحتاج درجات SAC إلى اتصال.',
|
||||
'tours.hike.sac.failed': 'تعذّرت قراءة درجات SAC. الجولة لم تتغير.',
|
||||
'tours.hike.sac.source': 'من OpenStreetMap. قد توجد فجوات؛ وليس تقييمًا للسلامة.',
|
||||
'tours.hike.ways.title': 'تفاصيل الممر',
|
||||
'tours.hike.ways.facets': 'ما يُعرض',
|
||||
'tours.hike.ways.facet.sac': 'SAC',
|
||||
'tours.hike.ways.facet.surface': 'السطح',
|
||||
'tours.hike.ways.facet.way': 'نوع الطريق',
|
||||
'tours.hike.ways.mostly': 'غالبًا',
|
||||
'tours.hike.ways.recordedTip': 'مسجّل في OpenStreetMap بالقيمة {tags}',
|
||||
'tours.hike.ways.unclearTip': 'يكتب OpenStreetMap هنا {tags}، ولا يندرج ذلك في أي مجموعة.',
|
||||
'tours.hike.ways.loading': 'جارٍ قراءة الطرق على طول المسار…',
|
||||
'tours.hike.ways.offline': 'تحتاج تفاصيل الممر إلى اتصال.',
|
||||
'tours.hike.ways.failed': 'تعذّرت قراءة تفاصيل الممر. الجولة لم تتغير.',
|
||||
'tours.hike.surface.paved': 'أسفلت أو خرسانة',
|
||||
'tours.hike.surface.sett': 'بلاط أو حجارة مرصوفة',
|
||||
'tours.hike.surface.compacted': 'مدكوك أو حصى ناعم',
|
||||
'tours.hike.surface.gravel': 'حصى',
|
||||
'tours.hike.surface.ground': 'أرض طبيعية',
|
||||
'tours.hike.surface.grass': 'عشب',
|
||||
'tours.hike.surface.rock': 'صخر',
|
||||
'tours.hike.surface.wood': 'خشب أو معدن',
|
||||
'tours.hike.surface.unpaved': 'غير معبّد، نوعه غير مسجّل',
|
||||
'tours.hike.surface.other': 'سطح آخر',
|
||||
'tours.hike.surface.untagged': 'لا سطح في OpenStreetMap',
|
||||
'tours.hike.surface.unclear': 'السطح غير واضح',
|
||||
'tours.hike.surface.untaggedTip':
|
||||
'لا يسجّل OpenStreetMap سطحًا لهذه الطرق. قد يفترض الموجّه سطحًا؛ وهذه ليست قيمة مسجّلة.',
|
||||
'tours.hike.surface.knownTip': 'جزء المسار الذي يسجّل OpenStreetMap سطحه. الباقي غير معروف.',
|
||||
'tours.hike.surface.source':
|
||||
'الأسطح كما يسجّلها OpenStreetMap، في مجموعات؛ ولا تُستخدم قراءة الموجّه الخاصة. ليس تقييمًا للطريق.',
|
||||
'tours.hike.way.path': 'ممر',
|
||||
'tours.hike.way.footway': 'ممر مشاة',
|
||||
'tours.hike.way.steps': 'درج',
|
||||
'tours.hike.way.track': 'طريق زراعي أو حرجي',
|
||||
'tours.hike.way.cycleway': 'مسار دراجات',
|
||||
'tours.hike.way.quietRoad': 'طريق فرعي',
|
||||
'tours.hike.way.road': 'طريق',
|
||||
'tours.hike.way.viaFerrata': 'فيا فيراتا',
|
||||
'tours.hike.way.other': 'طريق آخر',
|
||||
'tours.hike.way.untagged': 'لا نوع طريق في OpenStreetMap',
|
||||
'tours.hike.way.unclear': 'نوع الطريق غير واضح',
|
||||
'tours.hike.way.untaggedTip': 'لا يسجّل OpenStreetMap هنا نوع طريق.',
|
||||
'tours.hike.way.knownTip': 'جزء المسار الذي يسجّل OpenStreetMap نوع طريقه. الباقي غير معروف.',
|
||||
'tours.hike.way.source':
|
||||
'أنواع الطرق كما يسجّلها OpenStreetMap (highway)، في مجموعات. لا تقول شيئًا عن السلامة أو الملاءمة.',
|
||||
'tours.copy.action': 'حفظ كجولة جديدة',
|
||||
'tours.copy.hint': 'ينسخ هذه الجولة كجولة جديدة مستقلة دون أيامها. يبقى الأصل كما هو.',
|
||||
'tours.copy.name': '{name} (نسخة)',
|
||||
|
||||
+50
-10
@@ -512,8 +512,7 @@ const tours: TranslationStrings = {
|
||||
'Dərəcələr OpenStreetMap redaktorlarının verdiyi kimidir. Dərəcəsiz yol asan deyil, naməlumdur. Cığırın sənə uyğun olub-olmadığını demir.',
|
||||
'tours.bike.trail.loading': 'Cığır çətinlikləri oxunur…',
|
||||
'tours.bike.trail.failed': 'Cığır çətinlikləri bu dəfə oxunmadı.',
|
||||
'tours.hike.sac.title': 'SAC dərəcələri',
|
||||
'tours.hike.sac.show': 'Marşrut boyunca göstər',
|
||||
'tours.hike.ways.show': 'Marşrut boyunca göstər',
|
||||
'tours.hike.sac.t1': 'Yürüş',
|
||||
'tours.hike.sac.t2': 'Dağ yürüşü',
|
||||
'tours.hike.sac.t3': 'Yüksək hazırlıq tələb edən dağ yürüşü',
|
||||
@@ -522,24 +521,65 @@ const tours: TranslationStrings = {
|
||||
'tours.hike.sac.t6': 'Çətin alp yürüşü',
|
||||
'tours.hike.sac.untagged': 'OpenStreetMap-də dərəcə yoxdur',
|
||||
'tours.hike.sac.unclear': 'Dərəcə aydın deyil',
|
||||
'tours.hike.sac.unmatched': 'Yol birmənalı uyğunlaşdırılmayıb',
|
||||
'tours.hike.sac.unasked': 'Bu dəfə sorğu alınmadı',
|
||||
'tours.hike.sac.unknown': 'Naməlum',
|
||||
'tours.hike.ways.unmatched': 'Yol birmənalı uyğunlaşdırılmayıb',
|
||||
'tours.hike.ways.unasked': 'Bu dəfə sorğu alınmadı',
|
||||
'tours.hike.ways.unknown': 'Naməlum',
|
||||
'tours.hike.sac.stepTip': 'SAC piyada şkalası {step}, OpenStreetMap-dən: sac_scale={tag}',
|
||||
'tours.hike.sac.untaggedTip': 'OpenStreetMap bu yollara sac_scale vermir. Naməlum asan demək deyil.',
|
||||
'tours.hike.sac.unclearTip': 'OpenStreetMap burada {tags} yazır, bu isə tək bir dərəcə bildirmir.',
|
||||
'tours.hike.sac.unmatchedTip': 'Burada turun xətti altında aydın bir yol yoxdur, ona görə dərəcə göstərilmir.',
|
||||
'tours.hike.sac.known': 'Məlum',
|
||||
'tours.hike.ways.unmatchedTip':
|
||||
'Burada turun xətti altında aydın bir yol yoxdur, ona görə onun üçün heç nə göstərilmir.',
|
||||
'tours.hike.ways.known': 'Məlum',
|
||||
'tours.hike.sac.knownTip': 'Marşrutun OpenStreetMap-in SAC dərəcəsi verdiyi hissəsi. Qalanı naməlumdur.',
|
||||
'tours.hike.sac.highest': 'Ən yüksək məlum',
|
||||
'tours.hike.sac.routingLimit': 'Marşrut həddi',
|
||||
'tours.hike.sac.routingLimitTip':
|
||||
'Marşrut axtarışının istifadə edə biləcəyi ən çox: ayardır, yolların çətinliyi deyil.',
|
||||
'tours.hike.sac.aboveLimit': 'Həddən yuxarı',
|
||||
'tours.hike.sac.loading': 'SAC dərəcələri oxunur…',
|
||||
'tours.hike.sac.offline': 'SAC dərəcələri üçün bağlantı lazımdır.',
|
||||
'tours.hike.sac.failed': 'SAC dərəcələri oxuna bilmədi. Tur dəyişməyib.',
|
||||
'tours.hike.sac.source': 'OpenStreetMap-dən. Boşluqlar ola bilər; təhlükəsizlik qiymətləndirməsi deyil.',
|
||||
'tours.hike.ways.title': 'Cığır təfərrüatları',
|
||||
'tours.hike.ways.facets': 'Nə göstərilsin',
|
||||
'tours.hike.ways.facet.sac': 'SAC',
|
||||
'tours.hike.ways.facet.surface': 'Örtük',
|
||||
'tours.hike.ways.facet.way': 'Yol növü',
|
||||
'tours.hike.ways.mostly': 'Əsasən',
|
||||
'tours.hike.ways.recordedTip': 'OpenStreetMap-də belə qeyd olunub: {tags}',
|
||||
'tours.hike.ways.unclearTip': 'OpenStreetMap burada {tags} yazır, bu heç bir qrupa uyğun gəlmir.',
|
||||
'tours.hike.ways.loading': 'Yürüş boyunca yollar oxunur…',
|
||||
'tours.hike.ways.offline': 'Cığır təfərrüatları üçün bağlantı lazımdır.',
|
||||
'tours.hike.ways.failed': 'Cığır təfərrüatları oxuna bilmədi. Tur dəyişməyib.',
|
||||
'tours.hike.surface.paved': 'Asfalt və ya beton',
|
||||
'tours.hike.surface.sett': 'Səki daşı və ya daş döşəmə',
|
||||
'tours.hike.surface.compacted': 'Sıxlaşdırılmış və ya xırda çınqıl',
|
||||
'tours.hike.surface.gravel': 'Çınqıl',
|
||||
'tours.hike.surface.ground': 'Təbii torpaq',
|
||||
'tours.hike.surface.grass': 'Ot',
|
||||
'tours.hike.surface.rock': 'Qaya',
|
||||
'tours.hike.surface.wood': 'Taxta və ya metal',
|
||||
'tours.hike.surface.unpaved': 'Örtüksüz, növü qeyd olunmayıb',
|
||||
'tours.hike.surface.other': 'Başqa örtük',
|
||||
'tours.hike.surface.untagged': 'OpenStreetMap-də örtük yoxdur',
|
||||
'tours.hike.surface.unclear': 'Örtük aydın deyil',
|
||||
'tours.hike.surface.untaggedTip':
|
||||
'OpenStreetMap bu yollar üçün örtük qeyd etmir. Marşrut planlayıcısı birini fərz edə bilər; bu qeyd olunmuş dəyər deyil.',
|
||||
'tours.hike.surface.knownTip': 'Marşrutun OpenStreetMap-in örtüyünü qeyd etdiyi hissəsi. Qalanı naməlumdur.',
|
||||
'tours.hike.surface.source':
|
||||
'Örtüklər OpenStreetMap-in qeyd etdiyi kimi, qruplarla; planlayıcının öz oxunuşu istifadə olunmur. Yolun qiymətləndirilməsi deyil.',
|
||||
'tours.hike.way.path': 'Cığır',
|
||||
'tours.hike.way.footway': 'Piyada yolu',
|
||||
'tours.hike.way.steps': 'Pilləkən',
|
||||
'tours.hike.way.track': 'Təsərrüfat və ya meşə yolu',
|
||||
'tours.hike.way.cycleway': 'Velosiped yolu',
|
||||
'tours.hike.way.quietRoad': 'İkinci dərəcəli yol',
|
||||
'tours.hike.way.road': 'Avtomobil yolu',
|
||||
'tours.hike.way.viaFerrata': 'Via ferrata marşrutu',
|
||||
'tours.hike.way.other': 'Başqa yol',
|
||||
'tours.hike.way.untagged': 'OpenStreetMap-də yol növü yoxdur',
|
||||
'tours.hike.way.unclear': 'Yol növü aydın deyil',
|
||||
'tours.hike.way.untaggedTip': 'OpenStreetMap burada yol növü qeyd etmir.',
|
||||
'tours.hike.way.knownTip': 'Marşrutun OpenStreetMap-in yol növünü qeyd etdiyi hissəsi. Qalanı naməlumdur.',
|
||||
'tours.hike.way.source':
|
||||
'Yol növləri OpenStreetMap-in qeyd etdiyi kimi (highway), qruplarla. Təhlükəsizlik və ya uyğunluq haqqında heç nə demir.',
|
||||
'tours.copy.action': 'Yeni tur kimi saxla',
|
||||
'tours.copy.hint': 'Bu turu günləri olmadan yeni, müstəqil tur kimi kopyalayır. Orijinal olduğu kimi qalır.',
|
||||
'tours.copy.name': '{name} (surət)',
|
||||
|
||||
+50
-11
@@ -518,8 +518,7 @@ const tours: TranslationStrings = {
|
||||
'Graus como os mapeadores do OpenStreetMap os deram. Uma via sem grau é desconhecida, não fácil. Eles não dizem se a trilha combina com você.',
|
||||
'tours.bike.trail.loading': 'Lendo os graus das trilhas…',
|
||||
'tours.bike.trail.failed': 'Não foi possível ler os graus das trilhas agora.',
|
||||
'tours.hike.sac.title': 'Graus SAC',
|
||||
'tours.hike.sac.show': 'Mostrar ao longo da rota',
|
||||
'tours.hike.ways.show': 'Mostrar ao longo da rota',
|
||||
'tours.hike.sac.t1': 'Caminhada',
|
||||
'tours.hike.sac.t2': 'Caminhada em montanha',
|
||||
'tours.hike.sac.t3': 'Caminhada exigente em montanha',
|
||||
@@ -528,24 +527,64 @@ const tours: TranslationStrings = {
|
||||
'tours.hike.sac.t6': 'Caminhada alpina difícil',
|
||||
'tours.hike.sac.untagged': 'Sem grau no OpenStreetMap',
|
||||
'tours.hike.sac.unclear': 'Grau incerto',
|
||||
'tours.hike.sac.unmatched': 'Trilha sem correspondência clara',
|
||||
'tours.hike.sac.unasked': 'Não foi possível consultar desta vez',
|
||||
'tours.hike.sac.unknown': 'Desconhecido',
|
||||
'tours.hike.ways.unmatched': 'Trilha sem correspondência clara',
|
||||
'tours.hike.ways.unasked': 'Não foi possível consultar desta vez',
|
||||
'tours.hike.ways.unknown': 'Desconhecido',
|
||||
'tours.hike.sac.stepTip': 'Escala de caminhada SAC {step}, do OpenStreetMap: sac_scale={tag}',
|
||||
'tours.hike.sac.untaggedTip': 'O OpenStreetMap não dá sac_scale a estas trilhas. Desconhecido não quer dizer fácil.',
|
||||
'tours.hike.sac.unclearTip': 'O OpenStreetMap indica aqui {tags}, que não nomeia um único grau.',
|
||||
'tours.hike.sac.unmatchedTip':
|
||||
'Aqui nenhuma trilha fica claramente sob a linha do passeio, então nenhum grau é mostrado.',
|
||||
'tours.hike.sac.known': 'Conhecido',
|
||||
'tours.hike.ways.unmatchedTip':
|
||||
'Aqui nenhuma trilha fica claramente sob a linha do passeio, então nada é mostrado para ela.',
|
||||
'tours.hike.ways.known': 'Conhecido',
|
||||
'tours.hike.sac.knownTip': 'A parte da rota cujo grau SAC o OpenStreetMap informa. O resto é desconhecido.',
|
||||
'tours.hike.sac.highest': 'Maior conhecido',
|
||||
'tours.hike.sac.routingLimit': 'Limite de roteamento',
|
||||
'tours.hike.sac.routingLimitTip': 'O máximo que a busca de rota pode usar: uma configuração, não o grau das trilhas.',
|
||||
'tours.hike.sac.aboveLimit': 'Acima do limite',
|
||||
'tours.hike.sac.loading': 'Lendo os graus SAC…',
|
||||
'tours.hike.sac.offline': 'Os graus SAC precisam de conexão.',
|
||||
'tours.hike.sac.failed': 'Não foi possível ler os graus SAC. O passeio não foi alterado.',
|
||||
'tours.hike.sac.source': 'Do OpenStreetMap. Pode haver lacunas; não é uma avaliação de segurança.',
|
||||
'tours.hike.ways.title': 'Detalhes da trilha',
|
||||
'tours.hike.ways.facets': 'O que mostrar',
|
||||
'tours.hike.ways.facet.sac': 'SAC',
|
||||
'tours.hike.ways.facet.surface': 'Superfície',
|
||||
'tours.hike.ways.facet.way': 'Tipo de via',
|
||||
'tours.hike.ways.mostly': 'Principalmente',
|
||||
'tours.hike.ways.recordedTip': 'Registrado no OpenStreetMap como {tags}',
|
||||
'tours.hike.ways.unclearTip': 'O OpenStreetMap indica aqui {tags}, que não cabe em nenhum grupo.',
|
||||
'tours.hike.ways.loading': 'Lendo as vias ao longo da caminhada…',
|
||||
'tours.hike.ways.offline': 'Os detalhes da trilha precisam de conexão.',
|
||||
'tours.hike.ways.failed': 'Não foi possível ler os detalhes da trilha. O passeio não foi alterado.',
|
||||
'tours.hike.surface.paved': 'Asfalto ou concreto',
|
||||
'tours.hike.surface.sett': 'Paralelepípedo ou calçamento',
|
||||
'tours.hike.surface.compacted': 'Compactado ou cascalho fino',
|
||||
'tours.hike.surface.gravel': 'Cascalho',
|
||||
'tours.hike.surface.ground': 'Terra',
|
||||
'tours.hike.surface.grass': 'Grama',
|
||||
'tours.hike.surface.rock': 'Rocha',
|
||||
'tours.hike.surface.wood': 'Madeira ou metal',
|
||||
'tours.hike.surface.unpaved': 'Sem pavimento, tipo não registrado',
|
||||
'tours.hike.surface.other': 'Outra superfície',
|
||||
'tours.hike.surface.untagged': 'Sem superfície no OpenStreetMap',
|
||||
'tours.hike.surface.unclear': 'Superfície incerta',
|
||||
'tours.hike.surface.untaggedTip':
|
||||
'O OpenStreetMap não registra superfície para estas vias. Um roteador pode supor uma; isso não é um valor registrado.',
|
||||
'tours.hike.surface.knownTip': 'A parte da rota cuja superfície o OpenStreetMap registra. O resto é desconhecido.',
|
||||
'tours.hike.surface.source':
|
||||
'Superfícies como o OpenStreetMap as registra, em grupos; a leitura própria do roteador não é usada. Não avalia a via.',
|
||||
'tours.hike.way.path': 'Trilha',
|
||||
'tours.hike.way.footway': 'Caminho de pedestres',
|
||||
'tours.hike.way.steps': 'Escadas',
|
||||
'tours.hike.way.track': 'Estrada rural ou florestal',
|
||||
'tours.hike.way.cycleway': 'Ciclovia',
|
||||
'tours.hike.way.quietRoad': 'Rua secundária',
|
||||
'tours.hike.way.road': 'Estrada',
|
||||
'tours.hike.way.viaFerrata': 'Trilha de via ferrata',
|
||||
'tours.hike.way.other': 'Outra via',
|
||||
'tours.hike.way.untagged': 'Sem tipo de via no OpenStreetMap',
|
||||
'tours.hike.way.unclear': 'Tipo de via incerto',
|
||||
'tours.hike.way.untaggedTip': 'O OpenStreetMap não registra aqui nenhum tipo de via.',
|
||||
'tours.hike.way.knownTip': 'A parte da rota cujo tipo de via o OpenStreetMap registra. O resto é desconhecido.',
|
||||
'tours.hike.way.source':
|
||||
'Tipos de via como o OpenStreetMap os registra (highway), em grupos. Não diz nada sobre segurança ou adequação.',
|
||||
'tours.copy.action': 'Salvar como novo passeio',
|
||||
'tours.copy.hint':
|
||||
'Copia este passeio como um novo passeio independente, sem os seus dias. O original fica como está.',
|
||||
|
||||
+52
-11
@@ -519,8 +519,7 @@ const tours: TranslationStrings = {
|
||||
"Graus tal com els han posat els mapadors d'OpenStreetMap. Un camí sense grau és desconegut, no fàcil. No diuen res de si un corriol et convé.",
|
||||
'tours.bike.trail.loading': "S'estan llegint els graus dels corriols…",
|
||||
'tours.bike.trail.failed': "Aquesta vegada no s'han pogut llegir els graus dels corriols.",
|
||||
'tours.hike.sac.title': 'Graus SAC',
|
||||
'tours.hike.sac.show': 'Mostra al llarg de la ruta',
|
||||
'tours.hike.ways.show': 'Mostra al llarg de la ruta',
|
||||
'tours.hike.sac.t1': 'Senderisme',
|
||||
'tours.hike.sac.t2': 'Senderisme de muntanya',
|
||||
'tours.hike.sac.t3': 'Senderisme de muntanya exigent',
|
||||
@@ -529,24 +528,66 @@ const tours: TranslationStrings = {
|
||||
'tours.hike.sac.t6': 'Senderisme alpí difícil',
|
||||
'tours.hike.sac.untagged': 'Sense grau a OpenStreetMap',
|
||||
'tours.hike.sac.unclear': 'Grau poc clar',
|
||||
'tours.hike.sac.unmatched': 'Camí sense assignació clara',
|
||||
'tours.hike.sac.unasked': 'No s’ha pogut consultar aquesta vegada',
|
||||
'tours.hike.sac.unknown': 'Desconegut',
|
||||
'tours.hike.ways.unmatched': 'Camí sense assignació clara',
|
||||
'tours.hike.ways.unasked': 'No s’ha pogut consultar aquesta vegada',
|
||||
'tours.hike.ways.unknown': 'Desconegut',
|
||||
'tours.hike.sac.stepTip': 'Escala d’excursionisme SAC {step}, segons OpenStreetMap: sac_scale={tag}',
|
||||
'tours.hike.sac.untaggedTip': 'OpenStreetMap no dona sac_scale a aquests camins. Desconegut no vol dir fàcil.',
|
||||
'tours.hike.sac.unclearTip': 'OpenStreetMap hi indica {tags}, que no anomena cap grau concret.',
|
||||
'tours.hike.sac.unmatchedTip':
|
||||
'Aquí cap camí queda clarament sota la línia de la ruta, així que no es mostra cap grau.',
|
||||
'tours.hike.sac.known': 'Conegut',
|
||||
'tours.hike.ways.unmatchedTip':
|
||||
'Aquí cap camí queda clarament sota la línia de la ruta, així que no se’n mostra res.',
|
||||
'tours.hike.ways.known': 'Conegut',
|
||||
'tours.hike.sac.knownTip': 'La part de la ruta de la qual OpenStreetMap dona el grau SAC. La resta és desconeguda.',
|
||||
'tours.hike.sac.highest': 'Màxim conegut',
|
||||
'tours.hike.sac.routingLimit': 'Límit de càlcul',
|
||||
'tours.hike.sac.routingLimitTip': 'El màxim que pot fer servir la cerca de ruta: un ajust, no el grau dels camins.',
|
||||
'tours.hike.sac.aboveLimit': 'Per sobre del límit',
|
||||
'tours.hike.sac.loading': 'S’estan llegint els graus SAC…',
|
||||
'tours.hike.sac.offline': 'Els graus SAC necessiten connexió.',
|
||||
'tours.hike.sac.failed': 'No s’han pogut llegir els graus SAC. La ruta no ha canviat.',
|
||||
'tours.hike.sac.source': 'Segons OpenStreetMap. Hi pot haver buits; no és una valoració de seguretat.',
|
||||
'tours.hike.ways.title': 'Detalls del camí',
|
||||
'tours.hike.ways.facets': 'Què mostrar',
|
||||
'tours.hike.ways.facet.sac': 'SAC',
|
||||
'tours.hike.ways.facet.surface': 'Superfície',
|
||||
'tours.hike.ways.facet.way': 'Tipus de via',
|
||||
'tours.hike.ways.mostly': 'Sobretot',
|
||||
'tours.hike.ways.recordedTip': 'Registrat a OpenStreetMap com a {tags}',
|
||||
'tours.hike.ways.unclearTip': 'OpenStreetMap hi indica {tags}, que no encaixa en cap grup.',
|
||||
'tours.hike.ways.loading': 'Llegint les vies al llarg de la ruta…',
|
||||
'tours.hike.ways.offline': 'Els detalls del camí necessiten connexió.',
|
||||
'tours.hike.ways.failed': "No s'han pogut llegir els detalls del camí. La ruta no ha canviat.",
|
||||
'tours.hike.surface.paved': 'Asfalt o formigó',
|
||||
'tours.hike.surface.sett': 'Llambordes',
|
||||
'tours.hike.surface.compacted': 'Compactat o grava fina',
|
||||
'tours.hike.surface.gravel': 'Grava',
|
||||
'tours.hike.surface.ground': 'Terra natural',
|
||||
'tours.hike.surface.grass': 'Herba',
|
||||
'tours.hike.surface.rock': 'Roca',
|
||||
'tours.hike.surface.wood': 'Fusta o metall',
|
||||
'tours.hike.surface.unpaved': 'Sense pavimentar, tipus no registrat',
|
||||
'tours.hike.surface.other': 'Altra superfície',
|
||||
'tours.hike.surface.untagged': 'Sense superfície a OpenStreetMap',
|
||||
'tours.hike.surface.unclear': 'Superfície poc clara',
|
||||
'tours.hike.surface.untaggedTip':
|
||||
'OpenStreetMap no registra cap superfície per a aquestes vies. Un enrutador en pot suposar una; no és un valor registrat.',
|
||||
'tours.hike.surface.knownTip':
|
||||
'La part de la ruta de la qual OpenStreetMap registra la superfície. La resta és desconeguda.',
|
||||
'tours.hike.surface.source':
|
||||
'Superfícies tal com les registra OpenStreetMap, agrupades; no es fa servir la lectura pròpia de l’enrutador. No valora la via.',
|
||||
'tours.hike.way.path': 'Sender',
|
||||
'tours.hike.way.footway': 'Camí de vianants',
|
||||
'tours.hike.way.steps': 'Escales',
|
||||
'tours.hike.way.track': 'Camí agrícola o forestal',
|
||||
'tours.hike.way.cycleway': 'Carril bici',
|
||||
'tours.hike.way.quietRoad': 'Carretera secundària',
|
||||
'tours.hike.way.road': 'Carretera',
|
||||
'tours.hike.way.viaFerrata': 'Via ferrada',
|
||||
'tours.hike.way.other': 'Altra via',
|
||||
'tours.hike.way.untagged': 'Sense tipus de via a OpenStreetMap',
|
||||
'tours.hike.way.unclear': 'Tipus de via poc clar',
|
||||
'tours.hike.way.untaggedTip': 'OpenStreetMap no hi registra cap tipus de via.',
|
||||
'tours.hike.way.knownTip':
|
||||
'La part de la ruta de la qual OpenStreetMap registra el tipus de via. La resta és desconeguda.',
|
||||
'tours.hike.way.source':
|
||||
'Tipus de via tal com els registra OpenStreetMap (highway), agrupats. No diu res de seguretat ni d’idoneïtat.',
|
||||
'tours.copy.action': 'Desa com a excursió nova',
|
||||
'tours.copy.hint':
|
||||
'Copia aquesta excursió com una excursió nova i independent, sense els seus dies. L’original es queda com està.',
|
||||
|
||||
+49
-10
@@ -510,8 +510,7 @@ const tours: TranslationStrings = {
|
||||
'Stupně, jak je zadali mapovači OpenStreetMap. Cesta bez stupně je neznámá, ne snadná. Neříkají nic o tom, zda se trať hodí pro tebe.',
|
||||
'tours.bike.trail.loading': 'Načítá se obtížnost tratí…',
|
||||
'tours.bike.trail.failed': 'Obtížnost tratí se tentokrát nepodařilo načíst.',
|
||||
'tours.hike.sac.title': 'Obtížnost SAC',
|
||||
'tours.hike.sac.show': 'Zobrazit podél trasy',
|
||||
'tours.hike.ways.show': 'Zobrazit podél trasy',
|
||||
'tours.hike.sac.t1': 'Pěší turistika',
|
||||
'tours.hike.sac.t2': 'Horská turistika',
|
||||
'tours.hike.sac.t3': 'Náročná horská turistika',
|
||||
@@ -520,23 +519,63 @@ const tours: TranslationStrings = {
|
||||
'tours.hike.sac.t6': 'Obtížná vysokohorská turistika',
|
||||
'tours.hike.sac.untagged': 'Bez stupně v OpenStreetMap',
|
||||
'tours.hike.sac.unclear': 'Stupeň nejasný',
|
||||
'tours.hike.sac.unmatched': 'Cesta není jednoznačně přiřazena',
|
||||
'tours.hike.sac.unasked': 'Tentokrát se nepodařilo zjistit',
|
||||
'tours.hike.sac.unknown': 'Neznámé',
|
||||
'tours.hike.ways.unmatched': 'Cesta není jednoznačně přiřazena',
|
||||
'tours.hike.ways.unasked': 'Tentokrát se nepodařilo zjistit',
|
||||
'tours.hike.ways.unknown': 'Neznámé',
|
||||
'tours.hike.sac.stepTip': 'Turistická stupnice SAC {step}, z OpenStreetMap: sac_scale={tag}',
|
||||
'tours.hike.sac.untaggedTip': 'OpenStreetMap těmto cestám nedává sac_scale. Neznámé neznamená snadné.',
|
||||
'tours.hike.sac.unclearTip': 'OpenStreetMap zde uvádí {tags}, což neoznačuje jediný stupeň.',
|
||||
'tours.hike.sac.unmatchedTip': 'Zde pod linií túry jednoznačně neleží žádná cesta, proto se stupeň nezobrazuje.',
|
||||
'tours.hike.sac.known': 'Známé',
|
||||
'tours.hike.ways.unmatchedTip': 'Zde pod linií túry jednoznačně neleží žádná cesta, proto se pro ni nic nezobrazuje.',
|
||||
'tours.hike.ways.known': 'Známé',
|
||||
'tours.hike.sac.knownTip': 'Podíl trasy, pro který OpenStreetMap uvádí stupeň SAC. Zbytek je neznámý.',
|
||||
'tours.hike.sac.highest': 'Nejvyšší známý',
|
||||
'tours.hike.sac.routingLimit': 'Limit trasování',
|
||||
'tours.hike.sac.routingLimitTip': 'Nejvíc, co smí hledání trasy použít: nastavení, ne obtížnost cest.',
|
||||
'tours.hike.sac.aboveLimit': 'Nad limitem',
|
||||
'tours.hike.sac.loading': 'Načítají se stupně SAC…',
|
||||
'tours.hike.sac.offline': 'Stupně SAC potřebují připojení.',
|
||||
'tours.hike.sac.failed': 'Stupně SAC se nepodařilo načíst. Túra zůstává beze změny.',
|
||||
'tours.hike.sac.source': 'Z OpenStreetMap. Mohou chybět údaje; nejde o hodnocení bezpečnosti.',
|
||||
'tours.hike.ways.title': 'Podrobnosti o cestě',
|
||||
'tours.hike.ways.facets': 'Co zobrazit',
|
||||
'tours.hike.ways.facet.sac': 'SAC',
|
||||
'tours.hike.ways.facet.surface': 'Povrch',
|
||||
'tours.hike.ways.facet.way': 'Typ cesty',
|
||||
'tours.hike.ways.mostly': 'Převážně',
|
||||
'tours.hike.ways.recordedTip': 'V OpenStreetMap zapsáno jako {tags}',
|
||||
'tours.hike.ways.unclearTip': 'OpenStreetMap zde uvádí {tags}, což nepatří do žádné skupiny.',
|
||||
'tours.hike.ways.loading': 'Načítají se cesty podél túry…',
|
||||
'tours.hike.ways.offline': 'Podrobnosti o cestě potřebují připojení.',
|
||||
'tours.hike.ways.failed': 'Podrobnosti o cestě se nepodařilo načíst. Túra zůstává beze změny.',
|
||||
'tours.hike.surface.paved': 'Asfalt nebo beton',
|
||||
'tours.hike.surface.sett': 'Dlažba nebo kočičí hlavy',
|
||||
'tours.hike.surface.compacted': 'Zhutněný povrch nebo jemný štěrk',
|
||||
'tours.hike.surface.gravel': 'Štěrk',
|
||||
'tours.hike.surface.ground': 'Přírodní povrch',
|
||||
'tours.hike.surface.grass': 'Tráva',
|
||||
'tours.hike.surface.rock': 'Skála',
|
||||
'tours.hike.surface.wood': 'Dřevo nebo kov',
|
||||
'tours.hike.surface.unpaved': 'Nezpevněný, druh nezapsán',
|
||||
'tours.hike.surface.other': 'Jiný povrch',
|
||||
'tours.hike.surface.untagged': 'Žádný povrch v OpenStreetMap',
|
||||
'tours.hike.surface.unclear': 'Povrch nejasný',
|
||||
'tours.hike.surface.untaggedTip':
|
||||
'OpenStreetMap pro tyto cesty neuvádí povrch. Plánovač tras může nějaký předpokládat; to není zapsaná hodnota.',
|
||||
'tours.hike.surface.knownTip': 'Část trasy, jejíž povrch OpenStreetMap uvádí. Zbytek je neznámý.',
|
||||
'tours.hike.surface.source':
|
||||
'Povrchy tak, jak je zapisuje OpenStreetMap, ve skupinách; vlastní čtení plánovače se nepoužívá. Nejde o hodnocení cesty.',
|
||||
'tours.hike.way.path': 'Stezka',
|
||||
'tours.hike.way.footway': 'Chodník',
|
||||
'tours.hike.way.steps': 'Schody',
|
||||
'tours.hike.way.track': 'Polní nebo lesní cesta',
|
||||
'tours.hike.way.cycleway': 'Cyklostezka',
|
||||
'tours.hike.way.quietRoad': 'Vedlejší silnice',
|
||||
'tours.hike.way.road': 'Silnice',
|
||||
'tours.hike.way.viaFerrata': 'Zajištěná cesta',
|
||||
'tours.hike.way.other': 'Jiná cesta',
|
||||
'tours.hike.way.untagged': 'Žádný typ cesty v OpenStreetMap',
|
||||
'tours.hike.way.unclear': 'Typ cesty nejasný',
|
||||
'tours.hike.way.untaggedTip': 'OpenStreetMap zde neuvádí žádný typ cesty.',
|
||||
'tours.hike.way.knownTip': 'Část trasy, jejíž typ cesty OpenStreetMap uvádí. Zbytek je neznámý.',
|
||||
'tours.hike.way.source':
|
||||
'Typy cest tak, jak je zapisuje OpenStreetMap (highway), ve skupinách. Neříká nic o bezpečnosti ani vhodnosti.',
|
||||
'tours.copy.action': 'Uložit jako novou túru',
|
||||
'tours.copy.hint': 'Zkopíruje tuto túru jako novou, samostatnou túru bez jejích dnů. Originál zůstane, jak je.',
|
||||
'tours.copy.name': '{name} (kopie)',
|
||||
|
||||
+51
-11
@@ -522,8 +522,7 @@ const tours: TranslationStrings = {
|
||||
'Schwierigkeiten, wie OpenStreetMap-Mapper sie vergeben haben. Ein Weg ohne Angabe ist unbekannt, nicht leicht. Sie sagen nichts darüber, ob ein Trail zu dir passt.',
|
||||
'tours.bike.trail.loading': 'Trail-Schwierigkeiten werden gelesen…',
|
||||
'tours.bike.trail.failed': 'Die Trail-Schwierigkeiten konnten diesmal nicht gelesen werden.',
|
||||
'tours.hike.sac.title': 'SAC-Schwierigkeit',
|
||||
'tours.hike.sac.show': 'Entlang der Route zeigen',
|
||||
'tours.hike.ways.show': 'Entlang der Route zeigen',
|
||||
'tours.hike.sac.t1': 'Wandern',
|
||||
'tours.hike.sac.t2': 'Bergwandern',
|
||||
'tours.hike.sac.t3': 'Anspruchsvolles Bergwandern',
|
||||
@@ -532,15 +531,15 @@ const tours: TranslationStrings = {
|
||||
'tours.hike.sac.t6': 'Schwieriges Alpinwandern',
|
||||
'tours.hike.sac.untagged': 'Keine Angabe in OpenStreetMap',
|
||||
'tours.hike.sac.unclear': 'Angabe unklar',
|
||||
'tours.hike.sac.unmatched': 'Weg nicht eindeutig zugeordnet',
|
||||
'tours.hike.sac.unasked': 'Diesmal nicht abfragbar',
|
||||
'tours.hike.sac.unknown': 'Unbekannt',
|
||||
'tours.hike.ways.unmatched': 'Weg nicht eindeutig zugeordnet',
|
||||
'tours.hike.ways.unasked': 'Diesmal nicht abfragbar',
|
||||
'tours.hike.ways.unknown': 'Unbekannt',
|
||||
'tours.hike.sac.stepTip': 'SAC-Wanderskala {step}, aus OpenStreetMap: sac_scale={tag}',
|
||||
'tours.hike.sac.untaggedTip': 'OpenStreetMap gibt diesen Wegen kein sac_scale. Unbekannt heißt nicht leicht.',
|
||||
'tours.hike.sac.unclearTip': 'OpenStreetMap schreibt hier {tags}, das benennt keine einzelne Stufe.',
|
||||
'tours.hike.sac.unmatchedTip':
|
||||
'Hier liegt kein Weg eindeutig unter der Linie der Tour, daher wird keine Stufe gezeigt.',
|
||||
'tours.hike.sac.known': 'Bekannt',
|
||||
'tours.hike.ways.unmatchedTip':
|
||||
'Hier liegt kein Weg eindeutig unter der Linie der Tour, daher wird dafür nichts gezeigt.',
|
||||
'tours.hike.ways.known': 'Bekannt',
|
||||
'tours.hike.sac.knownTip':
|
||||
'Der Anteil der Route, für den OpenStreetMap eine SAC-Stufe angibt. Der Rest ist unbekannt.',
|
||||
'tours.hike.sac.highest': 'Höchste bekannte',
|
||||
@@ -548,10 +547,51 @@ const tours: TranslationStrings = {
|
||||
'tours.hike.sac.routingLimitTip':
|
||||
'Das Höchste, was die Routensuche nutzen darf: eine Einstellung, nicht die Schwierigkeit der Wege.',
|
||||
'tours.hike.sac.aboveLimit': 'Über der Grenze',
|
||||
'tours.hike.sac.loading': 'SAC-Stufen werden gelesen…',
|
||||
'tours.hike.sac.offline': 'Die SAC-Stufen brauchen eine Verbindung.',
|
||||
'tours.hike.sac.failed': 'Die SAC-Stufen konnten nicht gelesen werden. Die Tour bleibt unverändert.',
|
||||
'tours.hike.sac.source': 'Aus OpenStreetMap. Lücken sind möglich; keine Sicherheitsbewertung.',
|
||||
'tours.hike.ways.title': 'Wegdetails',
|
||||
'tours.hike.ways.facets': 'Was zeigen',
|
||||
'tours.hike.ways.facet.sac': 'SAC',
|
||||
'tours.hike.ways.facet.surface': 'Oberfläche',
|
||||
'tours.hike.ways.facet.way': 'Wegtyp',
|
||||
'tours.hike.ways.mostly': 'Meist',
|
||||
'tours.hike.ways.recordedTip': 'In OpenStreetMap erfasst als {tags}',
|
||||
'tours.hike.ways.unclearTip': 'OpenStreetMap schreibt hier {tags}, das passt in keine Gruppe.',
|
||||
'tours.hike.ways.loading': 'Wege entlang der Tour werden gelesen…',
|
||||
'tours.hike.ways.offline': 'Die Wegdetails brauchen eine Verbindung.',
|
||||
'tours.hike.ways.failed': 'Die Wegdetails konnten nicht gelesen werden. Die Tour bleibt unverändert.',
|
||||
'tours.hike.surface.paved': 'Asphalt oder Beton',
|
||||
'tours.hike.surface.sett': 'Pflaster oder Kopfsteinpflaster',
|
||||
'tours.hike.surface.compacted': 'Verdichtet oder feiner Schotter',
|
||||
'tours.hike.surface.gravel': 'Schotter',
|
||||
'tours.hike.surface.ground': 'Naturboden',
|
||||
'tours.hike.surface.grass': 'Wiese',
|
||||
'tours.hike.surface.rock': 'Fels',
|
||||
'tours.hike.surface.wood': 'Holz oder Metall',
|
||||
'tours.hike.surface.unpaved': 'Unbefestigt, Art nicht erfasst',
|
||||
'tours.hike.surface.other': 'Andere Oberfläche',
|
||||
'tours.hike.surface.untagged': 'Keine Oberfläche in OpenStreetMap',
|
||||
'tours.hike.surface.unclear': 'Oberfläche unklar',
|
||||
'tours.hike.surface.untaggedTip':
|
||||
'OpenStreetMap erfasst für diese Wege keine Oberfläche. Ein Router nimmt vielleicht eine an; das ist kein erfasster Wert.',
|
||||
'tours.hike.surface.knownTip':
|
||||
'Der Anteil der Route, dessen Oberfläche OpenStreetMap erfasst. Der Rest ist unbekannt.',
|
||||
'tours.hike.surface.source':
|
||||
'Oberflächen, wie OpenStreetMap sie erfasst, in Gruppen; die eigene Lesart des Routers zählt nicht. Keine Bewertung des Wegs.',
|
||||
'tours.hike.way.path': 'Pfad',
|
||||
'tours.hike.way.footway': 'Fußweg',
|
||||
'tours.hike.way.steps': 'Treppe',
|
||||
'tours.hike.way.track': 'Wirtschaftsweg',
|
||||
'tours.hike.way.cycleway': 'Radweg',
|
||||
'tours.hike.way.quietRoad': 'Nebenstraße',
|
||||
'tours.hike.way.road': 'Straße',
|
||||
'tours.hike.way.viaFerrata': 'Klettersteig',
|
||||
'tours.hike.way.other': 'Anderer Weg',
|
||||
'tours.hike.way.untagged': 'Kein Wegtyp in OpenStreetMap',
|
||||
'tours.hike.way.unclear': 'Wegtyp unklar',
|
||||
'tours.hike.way.untaggedTip': 'OpenStreetMap erfasst hier keinen Wegtyp.',
|
||||
'tours.hike.way.knownTip': 'Der Anteil der Route, dessen Wegtyp OpenStreetMap erfasst. Der Rest ist unbekannt.',
|
||||
'tours.hike.way.source':
|
||||
'Wegtypen, wie OpenStreetMap sie erfasst (highway), in Gruppen. Sagt nichts über Sicherheit oder Eignung.',
|
||||
'tours.copy.action': 'Als neue Tour speichern',
|
||||
'tours.copy.hint': 'Kopiert diese Tour als neue, eigenständige Tour ohne ihre Tage. Das Original bleibt, wie es ist.',
|
||||
'tours.copy.name': '{name} (Kopie)',
|
||||
|
||||
+49
-10
@@ -510,8 +510,7 @@ const tours: TranslationStrings = {
|
||||
'Grades as OpenStreetMap mappers gave them. A way without one is unknown, not easy. They say nothing about whether a trail suits you.',
|
||||
'tours.bike.trail.loading': 'Reading the trail grades…',
|
||||
'tours.bike.trail.failed': 'The trail grades could not be read this time.',
|
||||
'tours.hike.sac.title': 'SAC grades',
|
||||
'tours.hike.sac.show': 'Show along the route',
|
||||
'tours.hike.ways.show': 'Show along the route',
|
||||
'tours.hike.sac.t1': 'Hiking',
|
||||
'tours.hike.sac.t2': 'Mountain hiking',
|
||||
'tours.hike.sac.t3': 'Demanding mountain hiking',
|
||||
@@ -520,23 +519,63 @@ const tours: TranslationStrings = {
|
||||
'tours.hike.sac.t6': 'Difficult alpine hiking',
|
||||
'tours.hike.sac.untagged': 'No grade in OpenStreetMap',
|
||||
'tours.hike.sac.unclear': 'Grade unclear',
|
||||
'tours.hike.sac.unmatched': 'Way not clearly matched',
|
||||
'tours.hike.sac.unasked': 'Could not be asked this time',
|
||||
'tours.hike.sac.unknown': 'Unknown',
|
||||
'tours.hike.ways.unmatched': 'Way not clearly matched',
|
||||
'tours.hike.ways.unasked': 'Could not be asked this time',
|
||||
'tours.hike.ways.unknown': 'Unknown',
|
||||
'tours.hike.sac.stepTip': 'SAC hiking scale {step}, from OpenStreetMap: sac_scale={tag}',
|
||||
'tours.hike.sac.untaggedTip': 'OpenStreetMap gives these ways no sac_scale. Unknown does not mean easy.',
|
||||
'tours.hike.sac.unclearTip': 'OpenStreetMap writes {tags} here, which names no single step.',
|
||||
'tours.hike.sac.unmatchedTip': 'No way lies clearly under the line of the tour here, so no grade is shown.',
|
||||
'tours.hike.sac.known': 'Known',
|
||||
'tours.hike.ways.unmatchedTip': 'No way lies clearly under the line of the tour here, so nothing is shown for it.',
|
||||
'tours.hike.ways.known': 'Known',
|
||||
'tours.hike.sac.knownTip': 'The share of the route whose SAC grade OpenStreetMap gives. The rest is unknown.',
|
||||
'tours.hike.sac.highest': 'Highest known',
|
||||
'tours.hike.sac.routingLimit': 'Routing limit',
|
||||
'tours.hike.sac.routingLimitTip': 'The most the route search may use: a setting, not the grade of the ways.',
|
||||
'tours.hike.sac.aboveLimit': 'Above the limit',
|
||||
'tours.hike.sac.loading': 'Reading the SAC grades…',
|
||||
'tours.hike.sac.offline': 'The SAC grades need a connection.',
|
||||
'tours.hike.sac.failed': 'The SAC grades could not be read. The tour is unchanged.',
|
||||
'tours.hike.sac.source': 'From OpenStreetMap. Gaps are possible; not a safety rating.',
|
||||
'tours.hike.ways.title': 'Trail details',
|
||||
'tours.hike.ways.facets': 'What to show',
|
||||
'tours.hike.ways.facet.sac': 'SAC',
|
||||
'tours.hike.ways.facet.surface': 'Surface',
|
||||
'tours.hike.ways.facet.way': 'Way type',
|
||||
'tours.hike.ways.mostly': 'Mostly',
|
||||
'tours.hike.ways.recordedTip': 'Recorded in OpenStreetMap as {tags}',
|
||||
'tours.hike.ways.unclearTip': 'OpenStreetMap writes {tags} here, which fits no group.',
|
||||
'tours.hike.ways.loading': 'Reading the ways along the hike…',
|
||||
'tours.hike.ways.offline': 'The trail details need a connection.',
|
||||
'tours.hike.ways.failed': 'The trail details could not be read. The tour is unchanged.',
|
||||
'tours.hike.surface.paved': 'Asphalt or concrete',
|
||||
'tours.hike.surface.sett': 'Paving stones or cobbles',
|
||||
'tours.hike.surface.compacted': 'Compacted or fine gravel',
|
||||
'tours.hike.surface.gravel': 'Gravel',
|
||||
'tours.hike.surface.ground': 'Natural ground',
|
||||
'tours.hike.surface.grass': 'Grass',
|
||||
'tours.hike.surface.rock': 'Rock',
|
||||
'tours.hike.surface.wood': 'Wood or metal',
|
||||
'tours.hike.surface.unpaved': 'Unpaved, kind not recorded',
|
||||
'tours.hike.surface.other': 'Other surface',
|
||||
'tours.hike.surface.untagged': 'No surface in OpenStreetMap',
|
||||
'tours.hike.surface.unclear': 'Surface unclear',
|
||||
'tours.hike.surface.untaggedTip':
|
||||
'OpenStreetMap records no surface for these ways. A router may assume one; that is no recorded value.',
|
||||
'tours.hike.surface.knownTip': 'The share of the route whose surface OpenStreetMap records. The rest is unknown.',
|
||||
'tours.hike.surface.source':
|
||||
"Surfaces as OpenStreetMap records them, in groups; the router's own reading is not used. Not a rating of the way.",
|
||||
'tours.hike.way.path': 'Path',
|
||||
'tours.hike.way.footway': 'Footway',
|
||||
'tours.hike.way.steps': 'Steps',
|
||||
'tours.hike.way.track': 'Farm or forest track',
|
||||
'tours.hike.way.cycleway': 'Cycle path',
|
||||
'tours.hike.way.quietRoad': 'Minor road',
|
||||
'tours.hike.way.road': 'Road',
|
||||
'tours.hike.way.viaFerrata': 'Via ferrata',
|
||||
'tours.hike.way.other': 'Other way',
|
||||
'tours.hike.way.untagged': 'No way type in OpenStreetMap',
|
||||
'tours.hike.way.unclear': 'Way type unclear',
|
||||
'tours.hike.way.untaggedTip': 'OpenStreetMap records no kind of way here.',
|
||||
'tours.hike.way.knownTip': 'The share of the route whose way type OpenStreetMap records. The rest is unknown.',
|
||||
'tours.hike.way.source':
|
||||
'Way types as OpenStreetMap records them (highway), in groups. Says nothing about safety or suitability.',
|
||||
'tours.copy.action': 'Save as new tour',
|
||||
'tours.copy.hint': 'Copies this tour as a new, independent tour without its days. The original stays as it is.',
|
||||
'tours.copy.name': '{name} (copy)',
|
||||
|
||||
+50
-11
@@ -518,8 +518,7 @@ const tours: TranslationStrings = {
|
||||
'Grados tal como los dieron los mapeadores de OpenStreetMap. Una vía sin grado es desconocida, no fácil. No dicen nada sobre si un sendero te conviene.',
|
||||
'tours.bike.trail.loading': 'Leyendo los grados de los senderos…',
|
||||
'tours.bike.trail.failed': 'Esta vez no se pudieron leer los grados de los senderos.',
|
||||
'tours.hike.sac.title': 'Grados SAC',
|
||||
'tours.hike.sac.show': 'Mostrar a lo largo de la ruta',
|
||||
'tours.hike.ways.show': 'Mostrar a lo largo de la ruta',
|
||||
'tours.hike.sac.t1': 'Senderismo',
|
||||
'tours.hike.sac.t2': 'Senderismo de montaña',
|
||||
'tours.hike.sac.t3': 'Senderismo de montaña exigente',
|
||||
@@ -528,25 +527,65 @@ const tours: TranslationStrings = {
|
||||
'tours.hike.sac.t6': 'Senderismo alpino difícil',
|
||||
'tours.hike.sac.untagged': 'Sin grado en OpenStreetMap',
|
||||
'tours.hike.sac.unclear': 'Grado poco claro',
|
||||
'tours.hike.sac.unmatched': 'Camino sin asignación clara',
|
||||
'tours.hike.sac.unasked': 'No se pudo consultar esta vez',
|
||||
'tours.hike.sac.unknown': 'Desconocido',
|
||||
'tours.hike.ways.unmatched': 'Camino sin asignación clara',
|
||||
'tours.hike.ways.unasked': 'No se pudo consultar esta vez',
|
||||
'tours.hike.ways.unknown': 'Desconocido',
|
||||
'tours.hike.sac.stepTip': 'Escala de senderismo SAC {step}, según OpenStreetMap: sac_scale={tag}',
|
||||
'tours.hike.sac.untaggedTip': 'OpenStreetMap no da sac_scale a estos caminos. Desconocido no significa fácil.',
|
||||
'tours.hike.sac.unclearTip': 'OpenStreetMap indica aquí {tags}, que no nombra un único grado.',
|
||||
'tours.hike.sac.unmatchedTip':
|
||||
'Aquí ningún camino queda claramente bajo la línea de la ruta, así que no se muestra grado.',
|
||||
'tours.hike.sac.known': 'Conocido',
|
||||
'tours.hike.ways.unmatchedTip':
|
||||
'Aquí ningún camino queda claramente bajo la línea de la ruta, así que no se muestra nada.',
|
||||
'tours.hike.ways.known': 'Conocido',
|
||||
'tours.hike.sac.knownTip': 'La parte de la ruta cuyo grado SAC da OpenStreetMap. El resto es desconocido.',
|
||||
'tours.hike.sac.highest': 'Máximo conocido',
|
||||
'tours.hike.sac.routingLimit': 'Límite de cálculo',
|
||||
'tours.hike.sac.routingLimitTip':
|
||||
'Lo máximo que puede usar la búsqueda de ruta: un ajuste, no el grado de los caminos.',
|
||||
'tours.hike.sac.aboveLimit': 'Por encima del límite',
|
||||
'tours.hike.sac.loading': 'Leyendo los grados SAC…',
|
||||
'tours.hike.sac.offline': 'Los grados SAC necesitan conexión.',
|
||||
'tours.hike.sac.failed': 'No se pudieron leer los grados SAC. La ruta no ha cambiado.',
|
||||
'tours.hike.sac.source': 'Según OpenStreetMap. Puede haber huecos; no es una valoración de seguridad.',
|
||||
'tours.hike.ways.title': 'Detalles del sendero',
|
||||
'tours.hike.ways.facets': 'Qué mostrar',
|
||||
'tours.hike.ways.facet.sac': 'SAC',
|
||||
'tours.hike.ways.facet.surface': 'Superficie',
|
||||
'tours.hike.ways.facet.way': 'Tipo de vía',
|
||||
'tours.hike.ways.mostly': 'Sobre todo',
|
||||
'tours.hike.ways.recordedTip': 'Registrado en OpenStreetMap como {tags}',
|
||||
'tours.hike.ways.unclearTip': 'OpenStreetMap indica aquí {tags}, que no encaja en ningún grupo.',
|
||||
'tours.hike.ways.loading': 'Leyendo las vías a lo largo de la ruta…',
|
||||
'tours.hike.ways.offline': 'Los detalles del sendero necesitan conexión.',
|
||||
'tours.hike.ways.failed': 'No se pudieron leer los detalles del sendero. La ruta no ha cambiado.',
|
||||
'tours.hike.surface.paved': 'Asfalto u hormigón',
|
||||
'tours.hike.surface.sett': 'Adoquines',
|
||||
'tours.hike.surface.compacted': 'Compactado o grava fina',
|
||||
'tours.hike.surface.gravel': 'Grava',
|
||||
'tours.hike.surface.ground': 'Tierra natural',
|
||||
'tours.hike.surface.grass': 'Hierba',
|
||||
'tours.hike.surface.rock': 'Roca',
|
||||
'tours.hike.surface.wood': 'Madera o metal',
|
||||
'tours.hike.surface.unpaved': 'Sin pavimentar, tipo no registrado',
|
||||
'tours.hike.surface.other': 'Otra superficie',
|
||||
'tours.hike.surface.untagged': 'Sin superficie en OpenStreetMap',
|
||||
'tours.hike.surface.unclear': 'Superficie poco clara',
|
||||
'tours.hike.surface.untaggedTip':
|
||||
'OpenStreetMap no registra superficie para estas vías. Un enrutador puede suponer una; no es un valor registrado.',
|
||||
'tours.hike.surface.knownTip': 'La parte de la ruta cuya superficie registra OpenStreetMap. El resto es desconocido.',
|
||||
'tours.hike.surface.source':
|
||||
'Superficies tal como las registra OpenStreetMap, agrupadas; no se usa la lectura propia del enrutador. No valora la vía.',
|
||||
'tours.hike.way.path': 'Sendero',
|
||||
'tours.hike.way.footway': 'Camino peatonal',
|
||||
'tours.hike.way.steps': 'Escaleras',
|
||||
'tours.hike.way.track': 'Camino agrícola o forestal',
|
||||
'tours.hike.way.cycleway': 'Carril bici',
|
||||
'tours.hike.way.quietRoad': 'Carretera secundaria',
|
||||
'tours.hike.way.road': 'Carretera',
|
||||
'tours.hike.way.viaFerrata': 'Vía ferrata',
|
||||
'tours.hike.way.other': 'Otra vía',
|
||||
'tours.hike.way.untagged': 'Sin tipo de vía en OpenStreetMap',
|
||||
'tours.hike.way.unclear': 'Tipo de vía poco claro',
|
||||
'tours.hike.way.untaggedTip': 'OpenStreetMap no registra aquí ningún tipo de vía.',
|
||||
'tours.hike.way.knownTip': 'La parte de la ruta cuyo tipo de vía registra OpenStreetMap. El resto es desconocido.',
|
||||
'tours.hike.way.source':
|
||||
'Tipos de vía tal como los registra OpenStreetMap (highway), agrupados. No dice nada sobre seguridad o idoneidad.',
|
||||
'tours.copy.action': 'Guardar como nueva excursión',
|
||||
'tours.copy.hint':
|
||||
'Copia esta excursión como una nueva excursión independiente, sin sus días. El original queda como está.',
|
||||
|
||||
+50
-10
@@ -513,8 +513,7 @@ const tours: TranslationStrings = {
|
||||
'Raskused nii, nagu OpenStreetMapi kaardistajad need andsid. Rada ilma raskuseta on teadmata, mitte kerge. Need ei ütle midagi selle kohta, kas rada sulle sobib.',
|
||||
'tours.bike.trail.loading': 'Loen radade raskust…',
|
||||
'tours.bike.trail.failed': 'Radade raskust ei õnnestunud seekord lugeda.',
|
||||
'tours.hike.sac.title': 'SAC raskusastmed',
|
||||
'tours.hike.sac.show': 'Näita mööda marsruuti',
|
||||
'tours.hike.ways.show': 'Näita mööda marsruuti',
|
||||
'tours.hike.sac.t1': 'Matkamine',
|
||||
'tours.hike.sac.t2': 'Mägimatkamine',
|
||||
'tours.hike.sac.t3': 'Nõudlik mägimatkamine',
|
||||
@@ -523,23 +522,64 @@ const tours: TranslationStrings = {
|
||||
'tours.hike.sac.t6': 'Raske alpimatkamine',
|
||||
'tours.hike.sac.untagged': 'OpenStreetMapis raskusaste puudub',
|
||||
'tours.hike.sac.unclear': 'Raskusaste ebaselge',
|
||||
'tours.hike.sac.unmatched': 'Rada pole üheselt sobitatud',
|
||||
'tours.hike.sac.unasked': 'Seekord ei õnnestunud pärida',
|
||||
'tours.hike.sac.unknown': 'Teadmata',
|
||||
'tours.hike.ways.unmatched': 'Rada pole üheselt sobitatud',
|
||||
'tours.hike.ways.unasked': 'Seekord ei õnnestunud pärida',
|
||||
'tours.hike.ways.unknown': 'Teadmata',
|
||||
'tours.hike.sac.stepTip': 'SAC matkaskaala {step}, OpenStreetMapist: sac_scale={tag}',
|
||||
'tours.hike.sac.untaggedTip': 'OpenStreetMap ei anna neile radadele sac_scale väärtust. Teadmata ei tähenda kerget.',
|
||||
'tours.hike.sac.unclearTip': 'OpenStreetMap märgib siin {tags}, mis ei nimeta ühte astet.',
|
||||
'tours.hike.sac.unmatchedTip': 'Siin ei asu ükski rada selgelt matka joone all, seega raskusastet ei näidata.',
|
||||
'tours.hike.sac.known': 'Teada',
|
||||
'tours.hike.ways.unmatchedTip':
|
||||
'Siin ei asu ükski rada selgelt matka joone all, seega selle kohta midagi ei näidata.',
|
||||
'tours.hike.ways.known': 'Teada',
|
||||
'tours.hike.sac.knownTip': 'Marsruudi osa, mille SAC raskusastme OpenStreetMap annab. Ülejäänu on teadmata.',
|
||||
'tours.hike.sac.highest': 'Kõrgeim teadaolev',
|
||||
'tours.hike.sac.routingLimit': 'Marsruutimise piir',
|
||||
'tours.hike.sac.routingLimitTip': 'Kõige rohkem, mida marsruudiotsing tohib kasutada: seade, mitte radade raskus.',
|
||||
'tours.hike.sac.aboveLimit': 'Üle piiri',
|
||||
'tours.hike.sac.loading': 'SAC raskusastmete lugemine…',
|
||||
'tours.hike.sac.offline': 'SAC raskusastmed vajavad ühendust.',
|
||||
'tours.hike.sac.failed': 'SAC raskusastmeid ei õnnestunud lugeda. Matk on muutmata.',
|
||||
'tours.hike.sac.source': 'OpenStreetMapist. Lüngad on võimalikud; see pole ohutushinnang.',
|
||||
'tours.hike.ways.title': 'Raja üksikasjad',
|
||||
'tours.hike.ways.facets': 'Mida näidata',
|
||||
'tours.hike.ways.facet.sac': 'SAC',
|
||||
'tours.hike.ways.facet.surface': 'Kate',
|
||||
'tours.hike.ways.facet.way': 'Teetüüp',
|
||||
'tours.hike.ways.mostly': 'Peamiselt',
|
||||
'tours.hike.ways.recordedTip': 'OpenStreetMapis märgitud kui {tags}',
|
||||
'tours.hike.ways.unclearTip': 'OpenStreetMap kirjutab siin {tags}, mis ei sobi ühtegi rühma.',
|
||||
'tours.hike.ways.loading': 'Matka teid loetakse…',
|
||||
'tours.hike.ways.offline': 'Raja üksikasjad vajavad ühendust.',
|
||||
'tours.hike.ways.failed': 'Raja üksikasju ei õnnestunud lugeda. Matk jäi muutmata.',
|
||||
'tours.hike.surface.paved': 'Asfalt või betoon',
|
||||
'tours.hike.surface.sett': 'Sillutis või munakivi',
|
||||
'tours.hike.surface.compacted': 'Tihendatud või peen kruus',
|
||||
'tours.hike.surface.gravel': 'Kruus',
|
||||
'tours.hike.surface.ground': 'Pinnas',
|
||||
'tours.hike.surface.grass': 'Muru',
|
||||
'tours.hike.surface.rock': 'Kalju',
|
||||
'tours.hike.surface.wood': 'Puit või metall',
|
||||
'tours.hike.surface.unpaved': 'Katteta, liik märkimata',
|
||||
'tours.hike.surface.other': 'Muu kate',
|
||||
'tours.hike.surface.untagged': 'OpenStreetMapis kate puudub',
|
||||
'tours.hike.surface.unclear': 'Kate ebaselge',
|
||||
'tours.hike.surface.untaggedTip':
|
||||
'OpenStreetMap ei märgi nende teede katet. Teekonnaplaneerija võib mõne eeldada; see ei ole märgitud väärtus.',
|
||||
'tours.hike.surface.knownTip': 'Marsruudi osa, mille katte OpenStreetMap märgib. Ülejäänu on teadmata.',
|
||||
'tours.hike.surface.source':
|
||||
'Katted nii, nagu OpenStreetMap need märgib, rühmadena; planeerija enda tõlgendust ei kasutata. Ei hinda teed.',
|
||||
'tours.hike.way.path': 'Rada',
|
||||
'tours.hike.way.footway': 'Jalgtee',
|
||||
'tours.hike.way.steps': 'Trepp',
|
||||
'tours.hike.way.track': 'Põllu- või metsatee',
|
||||
'tours.hike.way.cycleway': 'Rattatee',
|
||||
'tours.hike.way.quietRoad': 'Kõrvaltee',
|
||||
'tours.hike.way.road': 'Maantee',
|
||||
'tours.hike.way.viaFerrata': 'Turvarada',
|
||||
'tours.hike.way.other': 'Muu tee',
|
||||
'tours.hike.way.untagged': 'OpenStreetMapis teetüüp puudub',
|
||||
'tours.hike.way.unclear': 'Teetüüp ebaselge',
|
||||
'tours.hike.way.untaggedTip': 'OpenStreetMap ei märgi siin teetüüpi.',
|
||||
'tours.hike.way.knownTip': 'Marsruudi osa, mille teetüübi OpenStreetMap märgib. Ülejäänu on teadmata.',
|
||||
'tours.hike.way.source':
|
||||
'Teetüübid nii, nagu OpenStreetMap need märgib (highway), rühmadena. Ei ütle midagi ohutuse ega sobivuse kohta.',
|
||||
'tours.copy.action': 'Salvesta uue matkana',
|
||||
'tours.copy.hint': 'Kopeerib selle matka uue, iseseisva matkana ilma päevadeta. Originaal jääb samaks.',
|
||||
'tours.copy.name': '{name} (koopia)',
|
||||
|
||||
+51
-11
@@ -526,8 +526,7 @@ const tours: TranslationStrings = {
|
||||
"Difficultés telles que les contributeurs d'OpenStreetMap les ont notées. Une voie sans note est inconnue, pas facile. Elles ne disent rien de ce qui te convient.",
|
||||
'tours.bike.trail.loading': 'Lecture des difficultés des sentiers…',
|
||||
'tours.bike.trail.failed': "Les difficultés des sentiers n'ont pas pu être lues cette fois.",
|
||||
'tours.hike.sac.title': 'Cotations SAC',
|
||||
'tours.hike.sac.show': 'Afficher le long de l’itinéraire',
|
||||
'tours.hike.ways.show': 'Afficher le long de l’itinéraire',
|
||||
'tours.hike.sac.t1': 'Randonnée',
|
||||
'tours.hike.sac.t2': 'Randonnée en montagne',
|
||||
'tours.hike.sac.t3': 'Randonnée en montagne exigeante',
|
||||
@@ -536,26 +535,67 @@ const tours: TranslationStrings = {
|
||||
'tours.hike.sac.t6': 'Randonnée alpine difficile',
|
||||
'tours.hike.sac.untagged': 'Aucune cotation dans OpenStreetMap',
|
||||
'tours.hike.sac.unclear': 'Cotation incertaine',
|
||||
'tours.hike.sac.unmatched': 'Chemin non attribué avec certitude',
|
||||
'tours.hike.sac.unasked': 'Interrogation impossible cette fois',
|
||||
'tours.hike.sac.unknown': 'Inconnu',
|
||||
'tours.hike.ways.unmatched': 'Chemin non attribué avec certitude',
|
||||
'tours.hike.ways.unasked': 'Interrogation impossible cette fois',
|
||||
'tours.hike.ways.unknown': 'Inconnu',
|
||||
'tours.hike.sac.stepTip': 'Échelle de randonnée SAC {step}, d’après OpenStreetMap : sac_scale={tag}',
|
||||
'tours.hike.sac.untaggedTip':
|
||||
'OpenStreetMap ne donne aucun sac_scale à ces chemins. Inconnu ne veut pas dire facile.',
|
||||
'tours.hike.sac.unclearTip': 'OpenStreetMap indique ici {tags}, ce qui ne désigne aucun degré précis.',
|
||||
'tours.hike.sac.unmatchedTip':
|
||||
'Aucun chemin ne se trouve clairement sous le tracé ici, donc aucune cotation n’est affichée.',
|
||||
'tours.hike.sac.known': 'Connu',
|
||||
'tours.hike.ways.unmatchedTip': 'Aucun chemin ne se trouve clairement sous le tracé ici, donc rien n’y est affiché.',
|
||||
'tours.hike.ways.known': 'Connu',
|
||||
'tours.hike.sac.knownTip': 'La part de l’itinéraire dont OpenStreetMap donne la cotation SAC. Le reste est inconnu.',
|
||||
'tours.hike.sac.highest': 'Plus haute connue',
|
||||
'tours.hike.sac.routingLimit': 'Limite de calcul',
|
||||
'tours.hike.sac.routingLimitTip':
|
||||
'Le maximum que la recherche d’itinéraire peut emprunter : un réglage, pas la difficulté des chemins.',
|
||||
'tours.hike.sac.aboveLimit': 'Au-dessus de la limite',
|
||||
'tours.hike.sac.loading': 'Lecture des cotations SAC…',
|
||||
'tours.hike.sac.offline': 'Les cotations SAC nécessitent une connexion.',
|
||||
'tours.hike.sac.failed': 'Les cotations SAC n’ont pas pu être lues. La randonnée reste inchangée.',
|
||||
'tours.hike.sac.source': 'D’après OpenStreetMap. Des lacunes sont possibles ; pas une évaluation de sécurité.',
|
||||
'tours.hike.ways.title': 'Détails du sentier',
|
||||
'tours.hike.ways.facets': 'Afficher',
|
||||
'tours.hike.ways.facet.sac': 'SAC',
|
||||
'tours.hike.ways.facet.surface': 'Revêtement',
|
||||
'tours.hike.ways.facet.way': 'Type de voie',
|
||||
'tours.hike.ways.mostly': 'Surtout',
|
||||
'tours.hike.ways.recordedTip': 'Enregistré dans OpenStreetMap comme {tags}',
|
||||
'tours.hike.ways.unclearTip': "OpenStreetMap indique ici {tags}, ce qui n'entre dans aucun groupe.",
|
||||
'tours.hike.ways.loading': 'Lecture des voies le long de la randonnée…',
|
||||
'tours.hike.ways.offline': 'Les détails du sentier nécessitent une connexion.',
|
||||
'tours.hike.ways.failed': "Les détails du sentier n'ont pas pu être lus. Le parcours reste inchangé.",
|
||||
'tours.hike.surface.paved': 'Asphalte ou béton',
|
||||
'tours.hike.surface.sett': 'Pavés',
|
||||
'tours.hike.surface.compacted': 'Stabilisé ou gravier fin',
|
||||
'tours.hike.surface.gravel': 'Gravier',
|
||||
'tours.hike.surface.ground': 'Sol naturel',
|
||||
'tours.hike.surface.grass': 'Herbe',
|
||||
'tours.hike.surface.rock': 'Roche',
|
||||
'tours.hike.surface.wood': 'Bois ou métal',
|
||||
'tours.hike.surface.unpaved': 'Non revêtu, nature non indiquée',
|
||||
'tours.hike.surface.other': 'Autre revêtement',
|
||||
'tours.hike.surface.untagged': 'Aucun revêtement dans OpenStreetMap',
|
||||
'tours.hike.surface.unclear': 'Revêtement incertain',
|
||||
'tours.hike.surface.untaggedTip':
|
||||
"OpenStreetMap n'indique aucun revêtement pour ces voies. Un calculateur d'itinéraire peut en supposer un ; ce n'est pas une valeur enregistrée.",
|
||||
'tours.hike.surface.knownTip':
|
||||
"La part de l'itinéraire dont OpenStreetMap indique le revêtement. Le reste est inconnu.",
|
||||
'tours.hike.surface.source':
|
||||
"Revêtements tels qu'OpenStreetMap les enregistre, regroupés ; la lecture propre au calculateur n'est pas utilisée. Pas une évaluation de la voie.",
|
||||
'tours.hike.way.path': 'Sentier',
|
||||
'tours.hike.way.footway': 'Chemin piéton',
|
||||
'tours.hike.way.steps': 'Escaliers',
|
||||
'tours.hike.way.track': 'Chemin agricole ou forestier',
|
||||
'tours.hike.way.cycleway': 'Piste cyclable',
|
||||
'tours.hike.way.quietRoad': 'Petite route',
|
||||
'tours.hike.way.road': 'Route',
|
||||
'tours.hike.way.viaFerrata': 'Itinéraire de via ferrata',
|
||||
'tours.hike.way.other': 'Autre voie',
|
||||
'tours.hike.way.untagged': 'Aucun type de voie dans OpenStreetMap',
|
||||
'tours.hike.way.unclear': 'Type de voie incertain',
|
||||
'tours.hike.way.untaggedTip': "OpenStreetMap n'indique ici aucun type de voie.",
|
||||
'tours.hike.way.knownTip':
|
||||
"La part de l'itinéraire dont OpenStreetMap indique le type de voie. Le reste est inconnu.",
|
||||
'tours.hike.way.source':
|
||||
"Types de voie tels qu'OpenStreetMap les enregistre (highway), regroupés. Ne dit rien de la sécurité ni de l'adéquation.",
|
||||
'tours.copy.action': 'Enregistrer comme nouvelle sortie',
|
||||
'tours.copy.hint':
|
||||
'Copie cette sortie en une nouvelle sortie indépendante, sans ses jours. L’original reste tel quel.',
|
||||
|
||||
+52
-11
@@ -528,8 +528,7 @@ const tours: TranslationStrings = {
|
||||
'Βαθμοί όπως τους έδωσαν οι χαρτογράφοι του OpenStreetMap. Δρόμος χωρίς βαθμό είναι άγνωστος, όχι εύκολος. Δεν λένε αν ένα μονοπάτι σου ταιριάζει.',
|
||||
'tours.bike.trail.loading': 'Διαβάζονται οι βαθμοί δυσκολίας…',
|
||||
'tours.bike.trail.failed': 'Οι βαθμοί δυσκολίας δεν διαβάστηκαν αυτή τη φορά.',
|
||||
'tours.hike.sac.title': 'Βαθμοί SAC',
|
||||
'tours.hike.sac.show': 'Εμφάνιση κατά μήκος της διαδρομής',
|
||||
'tours.hike.ways.show': 'Εμφάνιση κατά μήκος της διαδρομής',
|
||||
'tours.hike.sac.t1': 'Πεζοπορία',
|
||||
'tours.hike.sac.t2': 'Ορεινή πεζοπορία',
|
||||
'tours.hike.sac.t3': 'Απαιτητική ορεινή πεζοπορία',
|
||||
@@ -538,16 +537,16 @@ const tours: TranslationStrings = {
|
||||
'tours.hike.sac.t6': 'Δύσκολη αλπική πεζοπορία',
|
||||
'tours.hike.sac.untagged': 'Χωρίς βαθμό στο OpenStreetMap',
|
||||
'tours.hike.sac.unclear': 'Ασαφής βαθμός',
|
||||
'tours.hike.sac.unmatched': 'Το μονοπάτι δεν αντιστοιχίστηκε σαφώς',
|
||||
'tours.hike.sac.unasked': 'Δεν ήταν δυνατή η αναζήτηση αυτή τη φορά',
|
||||
'tours.hike.sac.unknown': 'Άγνωστο',
|
||||
'tours.hike.ways.unmatched': 'Το μονοπάτι δεν αντιστοιχίστηκε σαφώς',
|
||||
'tours.hike.ways.unasked': 'Δεν ήταν δυνατή η αναζήτηση αυτή τη φορά',
|
||||
'tours.hike.ways.unknown': 'Άγνωστο',
|
||||
'tours.hike.sac.stepTip': 'Κλίμακα πεζοπορίας SAC {step}, από το OpenStreetMap: sac_scale={tag}',
|
||||
'tours.hike.sac.untaggedTip':
|
||||
'Το OpenStreetMap δεν δίνει sac_scale σε αυτά τα μονοπάτια. Άγνωστο δεν σημαίνει εύκολο.',
|
||||
'tours.hike.sac.unclearTip': 'Το OpenStreetMap γράφει εδώ {tags}, που δεν ορίζει έναν μόνο βαθμό.',
|
||||
'tours.hike.sac.unmatchedTip':
|
||||
'Εδώ κανένα μονοπάτι δεν βρίσκεται σαφώς κάτω από τη γραμμή της διαδρομής, οπότε δεν εμφανίζεται βαθμός.',
|
||||
'tours.hike.sac.known': 'Γνωστό',
|
||||
'tours.hike.ways.unmatchedTip':
|
||||
'Εδώ κανένα μονοπάτι δεν βρίσκεται σαφώς κάτω από τη γραμμή της διαδρομής, οπότε δεν εμφανίζεται τίποτα.',
|
||||
'tours.hike.ways.known': 'Γνωστό',
|
||||
'tours.hike.sac.knownTip':
|
||||
'Το μέρος της διαδρομής για το οποίο το OpenStreetMap δίνει βαθμό SAC. Το υπόλοιπο είναι άγνωστο.',
|
||||
'tours.hike.sac.highest': 'Υψηλότερος γνωστός',
|
||||
@@ -555,10 +554,52 @@ const tours: TranslationStrings = {
|
||||
'tours.hike.sac.routingLimitTip':
|
||||
'Το μέγιστο που επιτρέπεται στην αναζήτηση διαδρομής: μια ρύθμιση, όχι η δυσκολία των μονοπατιών.',
|
||||
'tours.hike.sac.aboveLimit': 'Πάνω από το όριο',
|
||||
'tours.hike.sac.loading': 'Ανάγνωση βαθμών SAC…',
|
||||
'tours.hike.sac.offline': 'Οι βαθμοί SAC χρειάζονται σύνδεση.',
|
||||
'tours.hike.sac.failed': 'Οι βαθμοί SAC δεν διαβάστηκαν. Η διαδρομή δεν άλλαξε.',
|
||||
'tours.hike.sac.source': 'Από το OpenStreetMap. Μπορεί να λείπουν στοιχεία· δεν είναι αξιολόγηση ασφάλειας.',
|
||||
'tours.hike.ways.title': 'Λεπτομέρειες μονοπατιού',
|
||||
'tours.hike.ways.facets': 'Τι να εμφανίζεται',
|
||||
'tours.hike.ways.facet.sac': 'SAC',
|
||||
'tours.hike.ways.facet.surface': 'Επιφάνεια',
|
||||
'tours.hike.ways.facet.way': 'Τύπος οδού',
|
||||
'tours.hike.ways.mostly': 'Κυρίως',
|
||||
'tours.hike.ways.recordedTip': 'Καταγεγραμμένο στο OpenStreetMap ως {tags}',
|
||||
'tours.hike.ways.unclearTip': 'Το OpenStreetMap γράφει εδώ {tags}, που δεν ανήκει σε καμία ομάδα.',
|
||||
'tours.hike.ways.loading': 'Ανάγνωση των δρόμων κατά μήκος της πεζοπορίας…',
|
||||
'tours.hike.ways.offline': 'Οι λεπτομέρειες μονοπατιού χρειάζονται σύνδεση.',
|
||||
'tours.hike.ways.failed': 'Δεν ήταν δυνατή η ανάγνωση των λεπτομερειών μονοπατιού. Η διαδρομή δεν άλλαξε.',
|
||||
'tours.hike.surface.paved': 'Άσφαλτος ή σκυρόδεμα',
|
||||
'tours.hike.surface.sett': 'Πλακόστρωτο ή καλντερίμι',
|
||||
'tours.hike.surface.compacted': 'Συμπιεσμένο ή λεπτό χαλίκι',
|
||||
'tours.hike.surface.gravel': 'Χαλίκι',
|
||||
'tours.hike.surface.ground': 'Φυσικό έδαφος',
|
||||
'tours.hike.surface.grass': 'Γρασίδι',
|
||||
'tours.hike.surface.rock': 'Βράχος',
|
||||
'tours.hike.surface.wood': 'Ξύλο ή μέταλλο',
|
||||
'tours.hike.surface.unpaved': 'Χωρίς επίστρωση, είδος άγνωστο',
|
||||
'tours.hike.surface.other': 'Άλλη επιφάνεια',
|
||||
'tours.hike.surface.untagged': 'Καμία επιφάνεια στο OpenStreetMap',
|
||||
'tours.hike.surface.unclear': 'Ασαφής επιφάνεια',
|
||||
'tours.hike.surface.untaggedTip':
|
||||
'Το OpenStreetMap δεν καταγράφει επιφάνεια για αυτούς τους δρόμους. Ένας δρομολογητής μπορεί να υποθέσει μία· δεν είναι καταγεγραμμένη τιμή.',
|
||||
'tours.hike.surface.knownTip':
|
||||
'Το μέρος της διαδρομής του οποίου την επιφάνεια καταγράφει το OpenStreetMap. Το υπόλοιπο είναι άγνωστο.',
|
||||
'tours.hike.surface.source':
|
||||
'Επιφάνειες όπως τις καταγράφει το OpenStreetMap, σε ομάδες· η δική ανάγνωση του δρομολογητή δεν χρησιμοποιείται. Δεν αξιολογεί τον δρόμο.',
|
||||
'tours.hike.way.path': 'Μονοπάτι',
|
||||
'tours.hike.way.footway': 'Πεζόδρομος',
|
||||
'tours.hike.way.steps': 'Σκαλοπάτια',
|
||||
'tours.hike.way.track': 'Αγροτικός ή δασικός δρόμος',
|
||||
'tours.hike.way.cycleway': 'Ποδηλατόδρομος',
|
||||
'tours.hike.way.quietRoad': 'Δευτερεύων δρόμος',
|
||||
'tours.hike.way.road': 'Δρόμος',
|
||||
'tours.hike.way.viaFerrata': 'Βία φεράτα',
|
||||
'tours.hike.way.other': 'Άλλος δρόμος',
|
||||
'tours.hike.way.untagged': 'Κανένας τύπος οδού στο OpenStreetMap',
|
||||
'tours.hike.way.unclear': 'Ασαφής τύπος οδού',
|
||||
'tours.hike.way.untaggedTip': 'Το OpenStreetMap δεν καταγράφει εδώ τύπο οδού.',
|
||||
'tours.hike.way.knownTip':
|
||||
'Το μέρος της διαδρομής του οποίου τον τύπο οδού καταγράφει το OpenStreetMap. Το υπόλοιπο είναι άγνωστο.',
|
||||
'tours.hike.way.source':
|
||||
'Τύποι οδών όπως τους καταγράφει το OpenStreetMap (highway), σε ομάδες. Δεν λέει τίποτα για ασφάλεια ή καταλληλότητα.',
|
||||
'tours.copy.action': 'Αποθήκευση ως νέα εκδρομή',
|
||||
'tours.copy.hint':
|
||||
'Αντιγράφει αυτή την εκδρομή ως νέα, ανεξάρτητη εκδρομή χωρίς τις μέρες της. Το πρωτότυπο μένει ως έχει.',
|
||||
|
||||
+51
-11
@@ -520,8 +520,7 @@ const tours: TranslationStrings = {
|
||||
'A fokok az OpenStreetMap-térképezők szerint. A fok nélküli út ismeretlen, nem könnyű. Semmit sem mondanak arról, hogy egy ösvény neked való-e.',
|
||||
'tours.bike.trail.loading': 'Nehézségi fokok beolvasása…',
|
||||
'tours.bike.trail.failed': 'A nehézségi fokokat ezúttal nem sikerült beolvasni.',
|
||||
'tours.hike.sac.title': 'SAC-fokozatok',
|
||||
'tours.hike.sac.show': 'Megjelenítés az útvonal mentén',
|
||||
'tours.hike.ways.show': 'Megjelenítés az útvonal mentén',
|
||||
'tours.hike.sac.t1': 'Gyalogtúra',
|
||||
'tours.hike.sac.t2': 'Hegyi túra',
|
||||
'tours.hike.sac.t3': 'Megerőltető hegyi túra',
|
||||
@@ -530,25 +529,66 @@ const tours: TranslationStrings = {
|
||||
'tours.hike.sac.t6': 'Nehéz magashegyi túra',
|
||||
'tours.hike.sac.untagged': 'Nincs fokozat az OpenStreetMapben',
|
||||
'tours.hike.sac.unclear': 'Bizonytalan fokozat',
|
||||
'tours.hike.sac.unmatched': 'Az út nem egyértelműen illesztett',
|
||||
'tours.hike.sac.unasked': 'Ezúttal nem sikerült lekérdezni',
|
||||
'tours.hike.sac.unknown': 'Ismeretlen',
|
||||
'tours.hike.ways.unmatched': 'Az út nem egyértelműen illesztett',
|
||||
'tours.hike.ways.unasked': 'Ezúttal nem sikerült lekérdezni',
|
||||
'tours.hike.ways.unknown': 'Ismeretlen',
|
||||
'tours.hike.sac.stepTip': 'SAC túraskála {step}, az OpenStreetMapből: sac_scale={tag}',
|
||||
'tours.hike.sac.untaggedTip':
|
||||
'Az OpenStreetMap nem ad sac_scale értéket ezeknek az utaknak. Az ismeretlen nem jelent könnyűt.',
|
||||
'tours.hike.sac.unclearTip': 'Az OpenStreetMap itt ezt írja: {tags}, ami nem egyetlen fokozatot jelöl.',
|
||||
'tours.hike.sac.unmatchedTip':
|
||||
'Itt egyetlen út sem fut egyértelműen a túra vonala alatt, ezért nincs megjelenített fokozat.',
|
||||
'tours.hike.sac.known': 'Ismert',
|
||||
'tours.hike.ways.unmatchedTip':
|
||||
'Itt egyetlen út sem fut egyértelműen a túra vonala alatt, ezért ehhez nem jelenik meg semmi.',
|
||||
'tours.hike.ways.known': 'Ismert',
|
||||
'tours.hike.sac.knownTip': 'Az útvonal azon része, amelyhez az OpenStreetMap SAC-fokozatot ad. A többi ismeretlen.',
|
||||
'tours.hike.sac.highest': 'Legmagasabb ismert',
|
||||
'tours.hike.sac.routingLimit': 'Útvonaltervezési határ',
|
||||
'tours.hike.sac.routingLimitTip': 'A legtöbb, amit az útvonalkeresés használhat: beállítás, nem az utak nehézsége.',
|
||||
'tours.hike.sac.aboveLimit': 'A határ felett',
|
||||
'tours.hike.sac.loading': 'SAC-fokozatok beolvasása…',
|
||||
'tours.hike.sac.offline': 'A SAC-fokozatokhoz kapcsolat kell.',
|
||||
'tours.hike.sac.failed': 'A SAC-fokozatokat nem sikerült beolvasni. A túra változatlan.',
|
||||
'tours.hike.sac.source': 'Az OpenStreetMapből. Hiányok lehetnek; nem biztonsági értékelés.',
|
||||
'tours.hike.ways.title': 'Útrészletek',
|
||||
'tours.hike.ways.facets': 'Mit mutasson',
|
||||
'tours.hike.ways.facet.sac': 'SAC',
|
||||
'tours.hike.ways.facet.surface': 'Burkolat',
|
||||
'tours.hike.ways.facet.way': 'Úttípus',
|
||||
'tours.hike.ways.mostly': 'Főleg',
|
||||
'tours.hike.ways.recordedTip': 'Az OpenStreetMapben így rögzítve: {tags}',
|
||||
'tours.hike.ways.unclearTip': 'Az OpenStreetMap itt ezt írja: {tags}, ez egyik csoportba sem illik.',
|
||||
'tours.hike.ways.loading': 'A túra menti utak beolvasása…',
|
||||
'tours.hike.ways.offline': 'Az útrészletekhez kapcsolat kell.',
|
||||
'tours.hike.ways.failed': 'Az útrészleteket nem sikerült beolvasni. A túra nem változott.',
|
||||
'tours.hike.surface.paved': 'Aszfalt vagy beton',
|
||||
'tours.hike.surface.sett': 'Térkő vagy macskakő',
|
||||
'tours.hike.surface.compacted': 'Tömörített vagy finom zúzalék',
|
||||
'tours.hike.surface.gravel': 'Kavics',
|
||||
'tours.hike.surface.ground': 'Természetes talaj',
|
||||
'tours.hike.surface.grass': 'Fű',
|
||||
'tours.hike.surface.rock': 'Szikla',
|
||||
'tours.hike.surface.wood': 'Fa vagy fém',
|
||||
'tours.hike.surface.unpaved': 'Burkolatlan, fajtája nincs rögzítve',
|
||||
'tours.hike.surface.other': 'Egyéb burkolat',
|
||||
'tours.hike.surface.untagged': 'Nincs burkolat az OpenStreetMapben',
|
||||
'tours.hike.surface.unclear': 'Burkolat nem egyértelmű',
|
||||
'tours.hike.surface.untaggedTip':
|
||||
'Az OpenStreetMap nem rögzít burkolatot ezekhez az utakhoz. Egy útvonaltervező feltételezhet egyet; az nem rögzített érték.',
|
||||
'tours.hike.surface.knownTip':
|
||||
'Az útvonal azon része, amelynek burkolatát az OpenStreetMap rögzíti. A többi ismeretlen.',
|
||||
'tours.hike.surface.source':
|
||||
'Burkolatok úgy, ahogy az OpenStreetMap rögzíti őket, csoportosítva; az útvonaltervező saját olvasata nem számít. Nem az út értékelése.',
|
||||
'tours.hike.way.path': 'Ösvény',
|
||||
'tours.hike.way.footway': 'Gyalogút',
|
||||
'tours.hike.way.steps': 'Lépcső',
|
||||
'tours.hike.way.track': 'Mező- vagy erdei út',
|
||||
'tours.hike.way.cycleway': 'Kerékpárút',
|
||||
'tours.hike.way.quietRoad': 'Mellékút',
|
||||
'tours.hike.way.road': 'Közút',
|
||||
'tours.hike.way.viaFerrata': 'Vasalt út',
|
||||
'tours.hike.way.other': 'Egyéb út',
|
||||
'tours.hike.way.untagged': 'Nincs úttípus az OpenStreetMapben',
|
||||
'tours.hike.way.unclear': 'Úttípus nem egyértelmű',
|
||||
'tours.hike.way.untaggedTip': 'Az OpenStreetMap itt nem rögzít úttípust.',
|
||||
'tours.hike.way.knownTip': 'Az útvonal azon része, amelynek úttípusát az OpenStreetMap rögzíti. A többi ismeretlen.',
|
||||
'tours.hike.way.source':
|
||||
'Úttípusok úgy, ahogy az OpenStreetMap rögzíti őket (highway), csoportosítva. Semmit sem mond a biztonságról vagy alkalmasságról.',
|
||||
'tours.copy.action': 'Mentés új túraként',
|
||||
'tours.copy.hint': 'Ezt a túrát új, önálló túraként másolja a napjai nélkül. Az eredeti változatlan marad.',
|
||||
'tours.copy.name': '{name} (másolat)',
|
||||
|
||||
+50
-11
@@ -513,8 +513,7 @@ const tours: TranslationStrings = {
|
||||
'Tingkat sebagaimana diberikan pemeta OpenStreetMap. Jalan tanpa tingkat tidak diketahui, bukan mudah. Ini tidak menyatakan apakah jalur cocok untukmu.',
|
||||
'tours.bike.trail.loading': 'Membaca tingkat jalur…',
|
||||
'tours.bike.trail.failed': 'Tingkat jalur tidak dapat dibaca kali ini.',
|
||||
'tours.hike.sac.title': 'Tingkat SAC',
|
||||
'tours.hike.sac.show': 'Tampilkan di sepanjang rute',
|
||||
'tours.hike.ways.show': 'Tampilkan di sepanjang rute',
|
||||
'tours.hike.sac.t1': 'Pendakian',
|
||||
'tours.hike.sac.t2': 'Pendakian gunung',
|
||||
'tours.hike.sac.t3': 'Pendakian gunung menantang',
|
||||
@@ -523,26 +522,66 @@ const tours: TranslationStrings = {
|
||||
'tours.hike.sac.t6': 'Pendakian alpin sulit',
|
||||
'tours.hike.sac.untagged': 'Tidak ada tingkat di OpenStreetMap',
|
||||
'tours.hike.sac.unclear': 'Tingkat tidak jelas',
|
||||
'tours.hike.sac.unmatched': 'Jalur tidak dicocokkan dengan jelas',
|
||||
'tours.hike.sac.unasked': 'Tidak dapat ditanyakan kali ini',
|
||||
'tours.hike.sac.unknown': 'Tidak diketahui',
|
||||
'tours.hike.ways.unmatched': 'Jalur tidak dicocokkan dengan jelas',
|
||||
'tours.hike.ways.unasked': 'Tidak dapat ditanyakan kali ini',
|
||||
'tours.hike.ways.unknown': 'Tidak diketahui',
|
||||
'tours.hike.sac.stepTip': 'Skala pendakian SAC {step}, dari OpenStreetMap: sac_scale={tag}',
|
||||
'tours.hike.sac.untaggedTip':
|
||||
'OpenStreetMap tidak memberi sac_scale pada jalur ini. Tidak diketahui bukan berarti mudah.',
|
||||
'tours.hike.sac.unclearTip': 'OpenStreetMap menulis {tags} di sini, yang tidak menyebut satu tingkat.',
|
||||
'tours.hike.sac.unmatchedTip':
|
||||
'Di sini tidak ada jalur yang jelas berada di bawah garis tur, jadi tidak ada tingkat yang ditampilkan.',
|
||||
'tours.hike.sac.known': 'Diketahui',
|
||||
'tours.hike.ways.unmatchedTip':
|
||||
'Di sini tidak ada jalur yang jelas berada di bawah garis tur, jadi tidak ada yang ditampilkan.',
|
||||
'tours.hike.ways.known': 'Diketahui',
|
||||
'tours.hike.sac.knownTip': 'Bagian rute yang tingkat SAC-nya diberikan OpenStreetMap. Sisanya tidak diketahui.',
|
||||
'tours.hike.sac.highest': 'Tertinggi diketahui',
|
||||
'tours.hike.sac.routingLimit': 'Batas perutean',
|
||||
'tours.hike.sac.routingLimitTip':
|
||||
'Batas terbanyak yang boleh dipakai pencarian rute: sebuah pengaturan, bukan tingkat jalurnya.',
|
||||
'tours.hike.sac.aboveLimit': 'Di atas batas',
|
||||
'tours.hike.sac.loading': 'Membaca tingkat SAC…',
|
||||
'tours.hike.sac.offline': 'Tingkat SAC memerlukan koneksi.',
|
||||
'tours.hike.sac.failed': 'Tingkat SAC tidak dapat dibaca. Tur tidak berubah.',
|
||||
'tours.hike.sac.source': 'Dari OpenStreetMap. Mungkin ada celah; bukan penilaian keselamatan.',
|
||||
'tours.hike.ways.title': 'Detail jalur',
|
||||
'tours.hike.ways.facets': 'Yang ditampilkan',
|
||||
'tours.hike.ways.facet.sac': 'SAC',
|
||||
'tours.hike.ways.facet.surface': 'Permukaan',
|
||||
'tours.hike.ways.facet.way': 'Jenis jalan',
|
||||
'tours.hike.ways.mostly': 'Sebagian besar',
|
||||
'tours.hike.ways.recordedTip': 'Tercatat di OpenStreetMap sebagai {tags}',
|
||||
'tours.hike.ways.unclearTip': 'OpenStreetMap menulis {tags} di sini, yang tidak masuk kelompok mana pun.',
|
||||
'tours.hike.ways.loading': 'Membaca jalan di sepanjang pendakian…',
|
||||
'tours.hike.ways.offline': 'Detail jalur memerlukan koneksi.',
|
||||
'tours.hike.ways.failed': 'Detail jalur tidak dapat dibaca. Tur tidak berubah.',
|
||||
'tours.hike.surface.paved': 'Aspal atau beton',
|
||||
'tours.hike.surface.sett': 'Paving atau batu bulat',
|
||||
'tours.hike.surface.compacted': 'Dipadatkan atau kerikil halus',
|
||||
'tours.hike.surface.gravel': 'Kerikil',
|
||||
'tours.hike.surface.ground': 'Tanah alami',
|
||||
'tours.hike.surface.grass': 'Rumput',
|
||||
'tours.hike.surface.rock': 'Batuan',
|
||||
'tours.hike.surface.wood': 'Kayu atau logam',
|
||||
'tours.hike.surface.unpaved': 'Tidak diperkeras, jenis tidak tercatat',
|
||||
'tours.hike.surface.other': 'Permukaan lain',
|
||||
'tours.hike.surface.untagged': 'Tidak ada permukaan di OpenStreetMap',
|
||||
'tours.hike.surface.unclear': 'Permukaan tidak jelas',
|
||||
'tours.hike.surface.untaggedTip':
|
||||
'OpenStreetMap tidak mencatat permukaan untuk jalan ini. Perencana rute mungkin mengasumsikan satu; itu bukan nilai tercatat.',
|
||||
'tours.hike.surface.knownTip': 'Bagian rute yang permukaannya dicatat OpenStreetMap. Sisanya tidak diketahui.',
|
||||
'tours.hike.surface.source':
|
||||
'Permukaan seperti yang dicatat OpenStreetMap, dalam kelompok; bacaan perencana rute sendiri tidak dipakai. Bukan penilaian jalan.',
|
||||
'tours.hike.way.path': 'Jalan setapak',
|
||||
'tours.hike.way.footway': 'Jalur pejalan kaki',
|
||||
'tours.hike.way.steps': 'Tangga',
|
||||
'tours.hike.way.track': 'Jalan tani atau hutan',
|
||||
'tours.hike.way.cycleway': 'Jalur sepeda',
|
||||
'tours.hike.way.quietRoad': 'Jalan kecil',
|
||||
'tours.hike.way.road': 'Jalan raya',
|
||||
'tours.hike.way.viaFerrata': 'Jalur via ferrata',
|
||||
'tours.hike.way.other': 'Jalan lain',
|
||||
'tours.hike.way.untagged': 'Tidak ada jenis jalan di OpenStreetMap',
|
||||
'tours.hike.way.unclear': 'Jenis jalan tidak jelas',
|
||||
'tours.hike.way.untaggedTip': 'OpenStreetMap tidak mencatat jenis jalan di sini.',
|
||||
'tours.hike.way.knownTip': 'Bagian rute yang jenis jalannya dicatat OpenStreetMap. Sisanya tidak diketahui.',
|
||||
'tours.hike.way.source':
|
||||
'Jenis jalan seperti yang dicatat OpenStreetMap (highway), dalam kelompok. Tidak mengatakan apa pun tentang keamanan atau kesesuaian.',
|
||||
'tours.copy.action': 'Simpan sebagai tur baru',
|
||||
'tours.copy.hint': 'Menyalin tur ini sebagai tur baru yang mandiri tanpa hari-harinya. Aslinya tetap seperti semula.',
|
||||
'tours.copy.name': '{name} (salinan)',
|
||||
|
||||
+52
-11
@@ -521,8 +521,7 @@ const tours: TranslationStrings = {
|
||||
"Gradi come li hanno assegnati i mappatori di OpenStreetMap. Un percorso senza grado è sconosciuto, non facile. Non dicono nulla sull'adattezza del sentiero a te.",
|
||||
'tours.bike.trail.loading': 'Lettura delle difficoltà dei sentieri…',
|
||||
'tours.bike.trail.failed': 'Questa volta non è stato possibile leggere le difficoltà dei sentieri.',
|
||||
'tours.hike.sac.title': 'Gradi SAC',
|
||||
'tours.hike.sac.show': 'Mostra lungo il percorso',
|
||||
'tours.hike.ways.show': 'Mostra lungo il percorso',
|
||||
'tours.hike.sac.t1': 'Escursionismo',
|
||||
'tours.hike.sac.t2': 'Escursionismo di montagna',
|
||||
'tours.hike.sac.t3': 'Escursionismo di montagna impegnativo',
|
||||
@@ -531,26 +530,68 @@ const tours: TranslationStrings = {
|
||||
'tours.hike.sac.t6': 'Escursionismo alpino difficile',
|
||||
'tours.hike.sac.untagged': 'Nessun grado in OpenStreetMap',
|
||||
'tours.hike.sac.unclear': 'Grado non chiaro',
|
||||
'tours.hike.sac.unmatched': 'Sentiero non abbinato con certezza',
|
||||
'tours.hike.sac.unasked': 'Non interrogabile questa volta',
|
||||
'tours.hike.sac.unknown': 'Sconosciuto',
|
||||
'tours.hike.ways.unmatched': 'Sentiero non abbinato con certezza',
|
||||
'tours.hike.ways.unasked': 'Non interrogabile questa volta',
|
||||
'tours.hike.ways.unknown': 'Sconosciuto',
|
||||
'tours.hike.sac.stepTip': 'Scala escursionistica SAC {step}, da OpenStreetMap: sac_scale={tag}',
|
||||
'tours.hike.sac.untaggedTip':
|
||||
'OpenStreetMap non assegna sac_scale a questi sentieri. Sconosciuto non significa facile.',
|
||||
'tours.hike.sac.unclearTip': 'Qui OpenStreetMap riporta {tags}, che non indica un singolo grado.',
|
||||
'tours.hike.sac.unmatchedTip':
|
||||
'Qui nessun sentiero sta chiaramente sotto la linea del percorso, quindi non viene mostrato alcun grado.',
|
||||
'tours.hike.sac.known': 'Noto',
|
||||
'tours.hike.ways.unmatchedTip':
|
||||
'Qui nessun sentiero sta chiaramente sotto la linea del percorso, quindi non viene mostrato nulla.',
|
||||
'tours.hike.ways.known': 'Noto',
|
||||
'tours.hike.sac.knownTip': 'La parte del percorso di cui OpenStreetMap indica il grado SAC. Il resto è sconosciuto.',
|
||||
'tours.hike.sac.highest': 'Massimo noto',
|
||||
'tours.hike.sac.routingLimit': 'Limite di calcolo',
|
||||
'tours.hike.sac.routingLimitTip':
|
||||
'Il massimo che la ricerca del percorso può usare: un’impostazione, non il grado dei sentieri.',
|
||||
'tours.hike.sac.aboveLimit': 'Oltre il limite',
|
||||
'tours.hike.sac.loading': 'Lettura dei gradi SAC…',
|
||||
'tours.hike.sac.offline': 'I gradi SAC richiedono una connessione.',
|
||||
'tours.hike.sac.failed': 'Non è stato possibile leggere i gradi SAC. Il percorso non è cambiato.',
|
||||
'tours.hike.sac.source': 'Da OpenStreetMap. Possibili lacune; non è una valutazione di sicurezza.',
|
||||
'tours.hike.ways.title': 'Dettagli del sentiero',
|
||||
'tours.hike.ways.facets': 'Cosa mostrare',
|
||||
'tours.hike.ways.facet.sac': 'SAC',
|
||||
'tours.hike.ways.facet.surface': 'Fondo',
|
||||
'tours.hike.ways.facet.way': 'Tipo di via',
|
||||
'tours.hike.ways.mostly': 'Per lo più',
|
||||
'tours.hike.ways.recordedTip': 'Registrato in OpenStreetMap come {tags}',
|
||||
'tours.hike.ways.unclearTip': 'Qui OpenStreetMap scrive {tags}, che non rientra in alcun gruppo.',
|
||||
'tours.hike.ways.loading': 'Lettura delle vie lungo l’escursione…',
|
||||
'tours.hike.ways.offline': 'I dettagli del sentiero richiedono una connessione.',
|
||||
'tours.hike.ways.failed': 'Impossibile leggere i dettagli del sentiero. Il tour non è cambiato.',
|
||||
'tours.hike.surface.paved': 'Asfalto o cemento',
|
||||
'tours.hike.surface.sett': 'Lastricato o acciottolato',
|
||||
'tours.hike.surface.compacted': 'Battuto o ghiaietto',
|
||||
'tours.hike.surface.gravel': 'Ghiaia',
|
||||
'tours.hike.surface.ground': 'Terreno naturale',
|
||||
'tours.hike.surface.grass': 'Erba',
|
||||
'tours.hike.surface.rock': 'Roccia',
|
||||
'tours.hike.surface.wood': 'Legno o metallo',
|
||||
'tours.hike.surface.unpaved': 'Sterrato, tipo non registrato',
|
||||
'tours.hike.surface.other': 'Altro fondo',
|
||||
'tours.hike.surface.untagged': 'Nessun fondo in OpenStreetMap',
|
||||
'tours.hike.surface.unclear': 'Fondo non chiaro',
|
||||
'tours.hike.surface.untaggedTip':
|
||||
'OpenStreetMap non registra il fondo di queste vie. Un router può ipotizzarne uno; non è un valore registrato.',
|
||||
'tours.hike.surface.knownTip':
|
||||
'La parte del percorso di cui OpenStreetMap registra il fondo. Il resto è sconosciuto.',
|
||||
'tours.hike.surface.source':
|
||||
'Fondi come li registra OpenStreetMap, raggruppati; la lettura propria del router non è usata. Non è una valutazione della via.',
|
||||
'tours.hike.way.path': 'Sentiero',
|
||||
'tours.hike.way.footway': 'Percorso pedonale',
|
||||
'tours.hike.way.steps': 'Scalinata',
|
||||
'tours.hike.way.track': 'Strada agricola o forestale',
|
||||
'tours.hike.way.cycleway': 'Pista ciclabile',
|
||||
'tours.hike.way.quietRoad': 'Strada secondaria',
|
||||
'tours.hike.way.road': 'Strada',
|
||||
'tours.hike.way.viaFerrata': 'Ferrata',
|
||||
'tours.hike.way.other': 'Altra via',
|
||||
'tours.hike.way.untagged': 'Nessun tipo di via in OpenStreetMap',
|
||||
'tours.hike.way.unclear': 'Tipo di via non chiaro',
|
||||
'tours.hike.way.untaggedTip': 'Qui OpenStreetMap non registra alcun tipo di via.',
|
||||
'tours.hike.way.knownTip':
|
||||
'La parte del percorso di cui OpenStreetMap registra il tipo di via. Il resto è sconosciuto.',
|
||||
'tours.hike.way.source':
|
||||
'Tipi di via come li registra OpenStreetMap (highway), raggruppati. Non dice nulla su sicurezza o idoneità.',
|
||||
'tours.copy.action': 'Salva come nuovo tour',
|
||||
'tours.copy.hint': 'Copia questo tour come nuovo tour indipendente, senza i suoi giorni. L’originale resta com’è.',
|
||||
'tours.copy.name': '{name} (copia)',
|
||||
|
||||
+49
-10
@@ -499,8 +499,7 @@ const tours: TranslationStrings = {
|
||||
'OpenStreetMap のマッパーが付けた難度です。難度のない道は簡単ではなく不明です。そのトレイルがあなたに合うかどうかは示しません。',
|
||||
'tours.bike.trail.loading': 'トレイル難度を読み込み中…',
|
||||
'tours.bike.trail.failed': '今回はトレイル難度を読み込めませんでした。',
|
||||
'tours.hike.sac.title': 'SAC難易度',
|
||||
'tours.hike.sac.show': 'ルートに沿って表示',
|
||||
'tours.hike.ways.show': 'ルートに沿って表示',
|
||||
'tours.hike.sac.t1': 'ハイキング',
|
||||
'tours.hike.sac.t2': '山岳ハイキング',
|
||||
'tours.hike.sac.t3': '上級山岳ハイキング',
|
||||
@@ -509,23 +508,63 @@ const tours: TranslationStrings = {
|
||||
'tours.hike.sac.t6': '難度の高いアルパインハイキング',
|
||||
'tours.hike.sac.untagged': 'OpenStreetMapに難易度なし',
|
||||
'tours.hike.sac.unclear': '難易度が不明確',
|
||||
'tours.hike.sac.unmatched': '道が明確に一致しない',
|
||||
'tours.hike.sac.unasked': '今回は問い合わせできませんでした',
|
||||
'tours.hike.sac.unknown': '不明',
|
||||
'tours.hike.ways.unmatched': '道が明確に一致しない',
|
||||
'tours.hike.ways.unasked': '今回は問い合わせできませんでした',
|
||||
'tours.hike.ways.unknown': '不明',
|
||||
'tours.hike.sac.stepTip': 'SACハイキングスケール {step}、OpenStreetMapより: sac_scale={tag}',
|
||||
'tours.hike.sac.untaggedTip': 'OpenStreetMapはこれらの道にsac_scaleを付けていません。不明は易しいという意味ではありません。',
|
||||
'tours.hike.sac.unclearTip': 'OpenStreetMapはここに{tags}と記載しており、単一の段階を示していません。',
|
||||
'tours.hike.sac.unmatchedTip': 'ここではツアーの線の下に明確に道がないため、難易度は表示されません。',
|
||||
'tours.hike.sac.known': '判明',
|
||||
'tours.hike.ways.unmatchedTip': 'ここではツアーの線の下に明確に道がないため、何も表示されません。',
|
||||
'tours.hike.ways.known': '判明',
|
||||
'tours.hike.sac.knownTip': 'OpenStreetMapがSAC難易度を示しているルートの割合です。残りは不明です。',
|
||||
'tours.hike.sac.highest': '判明している最高',
|
||||
'tours.hike.sac.routingLimit': 'ルート探索の上限',
|
||||
'tours.hike.sac.routingLimitTip': 'ルート探索が使える上限です。設定であり、道の難易度ではありません。',
|
||||
'tours.hike.sac.aboveLimit': '上限を超えています',
|
||||
'tours.hike.sac.loading': 'SAC難易度を読み込み中…',
|
||||
'tours.hike.sac.offline': 'SAC難易度には接続が必要です。',
|
||||
'tours.hike.sac.failed': 'SAC難易度を読み込めませんでした。ツアーは変更されていません。',
|
||||
'tours.hike.sac.source': 'OpenStreetMapより。欠けがある場合があり、安全性の評価ではありません。',
|
||||
'tours.hike.ways.title': '道の詳細',
|
||||
'tours.hike.ways.facets': '表示する内容',
|
||||
'tours.hike.ways.facet.sac': 'SAC',
|
||||
'tours.hike.ways.facet.surface': '路面',
|
||||
'tours.hike.ways.facet.way': '道の種類',
|
||||
'tours.hike.ways.mostly': '主に',
|
||||
'tours.hike.ways.recordedTip': 'OpenStreetMapでの記録: {tags}',
|
||||
'tours.hike.ways.unclearTip': 'OpenStreetMapではここに{tags}と書かれていて、どのグループにも当てはまりません。',
|
||||
'tours.hike.ways.loading': 'ハイキング沿いの道を読み込み中…',
|
||||
'tours.hike.ways.offline': '道の詳細には接続が必要です。',
|
||||
'tours.hike.ways.failed': '道の詳細を読み込めませんでした。ツアーは変更されていません。',
|
||||
'tours.hike.surface.paved': 'アスファルト・コンクリート',
|
||||
'tours.hike.surface.sett': '石畳・敷石',
|
||||
'tours.hike.surface.compacted': '締め固め・細かい砂利',
|
||||
'tours.hike.surface.gravel': '砂利',
|
||||
'tours.hike.surface.ground': '未舗装の土',
|
||||
'tours.hike.surface.grass': '草地',
|
||||
'tours.hike.surface.rock': '岩',
|
||||
'tours.hike.surface.wood': '木・金属',
|
||||
'tours.hike.surface.unpaved': '未舗装 (種類の記録なし)',
|
||||
'tours.hike.surface.other': 'その他の路面',
|
||||
'tours.hike.surface.untagged': 'OpenStreetMapに路面なし',
|
||||
'tours.hike.surface.unclear': '路面が不明確',
|
||||
'tours.hike.surface.untaggedTip':
|
||||
'OpenStreetMapはこれらの道の路面を記録していません。ルート検索が路面を仮定することがありますが、記録された値ではありません。',
|
||||
'tours.hike.surface.knownTip': 'OpenStreetMapが路面を記録しているルートの割合。残りは不明です。',
|
||||
'tours.hike.surface.source':
|
||||
'OpenStreetMapが記録した路面をグループ分けしたもので、ルート検索独自の解釈は使いません。道の評価ではありません。',
|
||||
'tours.hike.way.path': '小道',
|
||||
'tours.hike.way.footway': '歩道',
|
||||
'tours.hike.way.steps': '階段',
|
||||
'tours.hike.way.track': '農道・林道',
|
||||
'tours.hike.way.cycleway': '自転車道',
|
||||
'tours.hike.way.quietRoad': '生活道路',
|
||||
'tours.hike.way.road': '道路',
|
||||
'tours.hike.way.viaFerrata': 'ヴィア・フェラータ',
|
||||
'tours.hike.way.other': 'その他の道',
|
||||
'tours.hike.way.untagged': 'OpenStreetMapに道の種類なし',
|
||||
'tours.hike.way.unclear': '道の種類が不明確',
|
||||
'tours.hike.way.untaggedTip': 'OpenStreetMapはここで道の種類を記録していません。',
|
||||
'tours.hike.way.knownTip': 'OpenStreetMapが道の種類を記録しているルートの割合。残りは不明です。',
|
||||
'tours.hike.way.source':
|
||||
'OpenStreetMapが記録した道の種類 (highway) をグループ分けしたものです。安全性や適性については何も示しません。',
|
||||
'tours.copy.action': '新しいツアーとして保存',
|
||||
'tours.copy.hint': 'このツアーを日程なしの独立した新しいツアーとしてコピーします。元のツアーはそのままです。',
|
||||
'tours.copy.name': '{name}(コピー)',
|
||||
|
||||
+49
-10
@@ -497,8 +497,7 @@ const tours: TranslationStrings = {
|
||||
'OpenStreetMap 매퍼가 매긴 난이도입니다. 난이도가 없는 길은 쉬운 것이 아니라 알 수 없는 것입니다. 그 트레일이 당신에게 맞는지는 알려 주지 않습니다.',
|
||||
'tours.bike.trail.loading': '트레일 난이도를 읽는 중…',
|
||||
'tours.bike.trail.failed': '이번에는 트레일 난이도를 읽지 못했습니다.',
|
||||
'tours.hike.sac.title': 'SAC 등급',
|
||||
'tours.hike.sac.show': '경로를 따라 표시',
|
||||
'tours.hike.ways.show': '경로를 따라 표시',
|
||||
'tours.hike.sac.t1': '하이킹',
|
||||
'tours.hike.sac.t2': '산악 하이킹',
|
||||
'tours.hike.sac.t3': '고난도 산악 하이킹',
|
||||
@@ -507,23 +506,63 @@ const tours: TranslationStrings = {
|
||||
'tours.hike.sac.t6': '매우 어려운 고산 하이킹',
|
||||
'tours.hike.sac.untagged': 'OpenStreetMap에 등급 없음',
|
||||
'tours.hike.sac.unclear': '등급 불분명',
|
||||
'tours.hike.sac.unmatched': '길이 명확히 일치하지 않음',
|
||||
'tours.hike.sac.unasked': '이번에는 조회할 수 없음',
|
||||
'tours.hike.sac.unknown': '알 수 없음',
|
||||
'tours.hike.ways.unmatched': '길이 명확히 일치하지 않음',
|
||||
'tours.hike.ways.unasked': '이번에는 조회할 수 없음',
|
||||
'tours.hike.ways.unknown': '알 수 없음',
|
||||
'tours.hike.sac.stepTip': 'SAC 하이킹 등급 {step}, OpenStreetMap 기준: sac_scale={tag}',
|
||||
'tours.hike.sac.untaggedTip': 'OpenStreetMap은 이 길들에 sac_scale을 주지 않습니다. 알 수 없음은 쉬움이 아닙니다.',
|
||||
'tours.hike.sac.unclearTip': 'OpenStreetMap은 여기에 {tags}라고 적어 하나의 등급을 가리키지 않습니다.',
|
||||
'tours.hike.sac.unmatchedTip': '여기서는 투어 선 아래에 명확히 놓인 길이 없어 등급을 표시하지 않습니다.',
|
||||
'tours.hike.sac.known': '알려짐',
|
||||
'tours.hike.ways.unmatchedTip': '여기서는 투어 선 아래에 명확히 놓인 길이 없어 아무것도 표시하지 않습니다.',
|
||||
'tours.hike.ways.known': '알려짐',
|
||||
'tours.hike.sac.knownTip': 'OpenStreetMap이 SAC 등급을 제공하는 경로의 비율입니다. 나머지는 알 수 없습니다.',
|
||||
'tours.hike.sac.highest': '알려진 최고',
|
||||
'tours.hike.sac.routingLimit': '경로 탐색 한도',
|
||||
'tours.hike.sac.routingLimitTip': '경로 탐색이 사용할 수 있는 최대치입니다. 설정이며 길의 난이도가 아닙니다.',
|
||||
'tours.hike.sac.aboveLimit': '한도 초과',
|
||||
'tours.hike.sac.loading': 'SAC 등급을 읽는 중…',
|
||||
'tours.hike.sac.offline': 'SAC 등급에는 연결이 필요합니다.',
|
||||
'tours.hike.sac.failed': 'SAC 등급을 읽을 수 없습니다. 투어는 그대로입니다.',
|
||||
'tours.hike.sac.source': 'OpenStreetMap 기준. 빈 곳이 있을 수 있으며 안전 평가가 아닙니다.',
|
||||
'tours.hike.ways.title': '길 정보',
|
||||
'tours.hike.ways.facets': '표시할 항목',
|
||||
'tours.hike.ways.facet.sac': 'SAC',
|
||||
'tours.hike.ways.facet.surface': '노면',
|
||||
'tours.hike.ways.facet.way': '길 유형',
|
||||
'tours.hike.ways.mostly': '주로',
|
||||
'tours.hike.ways.recordedTip': 'OpenStreetMap 기록: {tags}',
|
||||
'tours.hike.ways.unclearTip': '여기서 OpenStreetMap은 {tags}라고 적고 있어 어느 그룹에도 맞지 않습니다.',
|
||||
'tours.hike.ways.loading': '하이킹 경로의 길을 읽는 중…',
|
||||
'tours.hike.ways.offline': '길 정보에는 연결이 필요합니다.',
|
||||
'tours.hike.ways.failed': '길 정보를 읽지 못했습니다. 투어는 바뀌지 않았습니다.',
|
||||
'tours.hike.surface.paved': '아스팔트 또는 콘크리트',
|
||||
'tours.hike.surface.sett': '포장석 또는 자갈 포장',
|
||||
'tours.hike.surface.compacted': '다짐길 또는 잔자갈',
|
||||
'tours.hike.surface.gravel': '자갈',
|
||||
'tours.hike.surface.ground': '자연 노면',
|
||||
'tours.hike.surface.grass': '풀밭',
|
||||
'tours.hike.surface.rock': '바위',
|
||||
'tours.hike.surface.wood': '나무 또는 금속',
|
||||
'tours.hike.surface.unpaved': '비포장, 종류 기록 없음',
|
||||
'tours.hike.surface.other': '기타 노면',
|
||||
'tours.hike.surface.untagged': 'OpenStreetMap에 노면 없음',
|
||||
'tours.hike.surface.unclear': '노면 불분명',
|
||||
'tours.hike.surface.untaggedTip':
|
||||
'OpenStreetMap은 이 길들의 노면을 기록하지 않습니다. 경로 탐색기가 노면을 가정할 수 있지만 기록된 값은 아닙니다.',
|
||||
'tours.hike.surface.knownTip': 'OpenStreetMap이 노면을 기록한 경로의 비율입니다. 나머지는 알 수 없습니다.',
|
||||
'tours.hike.surface.source':
|
||||
'OpenStreetMap이 기록한 노면을 그룹으로 묶은 것이며, 경로 탐색기 자체 해석은 쓰지 않습니다. 길에 대한 평가가 아닙니다.',
|
||||
'tours.hike.way.path': '오솔길',
|
||||
'tours.hike.way.footway': '보행로',
|
||||
'tours.hike.way.steps': '계단',
|
||||
'tours.hike.way.track': '농로·임도',
|
||||
'tours.hike.way.cycleway': '자전거도로',
|
||||
'tours.hike.way.quietRoad': '소로',
|
||||
'tours.hike.way.road': '도로',
|
||||
'tours.hike.way.viaFerrata': '비아 페라타',
|
||||
'tours.hike.way.other': '기타 길',
|
||||
'tours.hike.way.untagged': 'OpenStreetMap에 길 유형 없음',
|
||||
'tours.hike.way.unclear': '길 유형 불분명',
|
||||
'tours.hike.way.untaggedTip': 'OpenStreetMap은 여기서 길 유형을 기록하지 않습니다.',
|
||||
'tours.hike.way.knownTip': 'OpenStreetMap이 길 유형을 기록한 경로의 비율입니다. 나머지는 알 수 없습니다.',
|
||||
'tours.hike.way.source':
|
||||
'OpenStreetMap이 기록한 길 유형(highway)을 그룹으로 묶은 것입니다. 안전이나 적합성에 대해서는 아무것도 말하지 않습니다.',
|
||||
'tours.copy.action': '새 투어로 저장',
|
||||
'tours.copy.hint': '이 투어를 날짜 없이 독립된 새 투어로 복사합니다. 원본은 그대로입니다.',
|
||||
'tours.copy.name': '{name} (사본)',
|
||||
|
||||
+51
-11
@@ -520,8 +520,7 @@ const tours: TranslationStrings = {
|
||||
'Graden zoals OpenStreetMap-mappers ze gaven. Een weg zonder graad is onbekend, niet makkelijk. Ze zeggen niets over of een trail bij je past.',
|
||||
'tours.bike.trail.loading': 'Trailgraden worden gelezen…',
|
||||
'tours.bike.trail.failed': 'De trailgraden konden deze keer niet worden gelezen.',
|
||||
'tours.hike.sac.title': 'SAC-graden',
|
||||
'tours.hike.sac.show': 'Langs de route tonen',
|
||||
'tours.hike.ways.show': 'Langs de route tonen',
|
||||
'tours.hike.sac.t1': 'Wandelen',
|
||||
'tours.hike.sac.t2': 'Bergwandelen',
|
||||
'tours.hike.sac.t3': 'Veeleisend bergwandelen',
|
||||
@@ -530,25 +529,66 @@ const tours: TranslationStrings = {
|
||||
'tours.hike.sac.t6': 'Moeilijk alpien wandelen',
|
||||
'tours.hike.sac.untagged': 'Geen graad in OpenStreetMap',
|
||||
'tours.hike.sac.unclear': 'Graad onduidelijk',
|
||||
'tours.hike.sac.unmatched': 'Pad niet eenduidig gekoppeld',
|
||||
'tours.hike.sac.unasked': 'Kon deze keer niet worden opgevraagd',
|
||||
'tours.hike.sac.unknown': 'Onbekend',
|
||||
'tours.hike.ways.unmatched': 'Pad niet eenduidig gekoppeld',
|
||||
'tours.hike.ways.unasked': 'Kon deze keer niet worden opgevraagd',
|
||||
'tours.hike.ways.unknown': 'Onbekend',
|
||||
'tours.hike.sac.stepTip': 'SAC-wandelschaal {step}, uit OpenStreetMap: sac_scale={tag}',
|
||||
'tours.hike.sac.untaggedTip': 'OpenStreetMap geeft deze paden geen sac_scale. Onbekend betekent niet makkelijk.',
|
||||
'tours.hike.sac.unclearTip': 'OpenStreetMap vermeldt hier {tags}, wat geen enkele graad aanduidt.',
|
||||
'tours.hike.sac.unmatchedTip':
|
||||
'Hier ligt geen pad duidelijk onder de lijn van de tocht, dus er wordt geen graad getoond.',
|
||||
'tours.hike.sac.known': 'Bekend',
|
||||
'tours.hike.ways.unmatchedTip':
|
||||
'Hier ligt geen pad duidelijk onder de lijn van de tocht, dus er wordt niets getoond.',
|
||||
'tours.hike.ways.known': 'Bekend',
|
||||
'tours.hike.sac.knownTip': 'Het deel van de route waarvoor OpenStreetMap een SAC-graad geeft. De rest is onbekend.',
|
||||
'tours.hike.sac.highest': 'Hoogste bekende',
|
||||
'tours.hike.sac.routingLimit': 'Routeringsgrens',
|
||||
'tours.hike.sac.routingLimitTip':
|
||||
'Het hoogste dat de routezoeker mag gebruiken: een instelling, niet de graad van de paden.',
|
||||
'tours.hike.sac.aboveLimit': 'Boven de grens',
|
||||
'tours.hike.sac.loading': 'SAC-graden worden gelezen…',
|
||||
'tours.hike.sac.offline': 'De SAC-graden hebben een verbinding nodig.',
|
||||
'tours.hike.sac.failed': 'De SAC-graden konden niet worden gelezen. De tocht is ongewijzigd.',
|
||||
'tours.hike.sac.source': 'Uit OpenStreetMap. Hiaten zijn mogelijk; geen veiligheidsoordeel.',
|
||||
'tours.hike.ways.title': 'Padgegevens',
|
||||
'tours.hike.ways.facets': 'Wat tonen',
|
||||
'tours.hike.ways.facet.sac': 'SAC',
|
||||
'tours.hike.ways.facet.surface': 'Ondergrond',
|
||||
'tours.hike.ways.facet.way': 'Wegtype',
|
||||
'tours.hike.ways.mostly': 'Vooral',
|
||||
'tours.hike.ways.recordedTip': 'In OpenStreetMap vastgelegd als {tags}',
|
||||
'tours.hike.ways.unclearTip': 'OpenStreetMap schrijft hier {tags}, wat in geen groep past.',
|
||||
'tours.hike.ways.loading': 'Wegen langs de wandeling worden gelezen…',
|
||||
'tours.hike.ways.offline': 'De padgegevens hebben een verbinding nodig.',
|
||||
'tours.hike.ways.failed': 'De padgegevens konden niet worden gelezen. De tocht is ongewijzigd.',
|
||||
'tours.hike.surface.paved': 'Asfalt of beton',
|
||||
'tours.hike.surface.sett': 'Klinkers of kasseien',
|
||||
'tours.hike.surface.compacted': 'Halfverhard of fijn grind',
|
||||
'tours.hike.surface.gravel': 'Grind',
|
||||
'tours.hike.surface.ground': 'Natuurlijke ondergrond',
|
||||
'tours.hike.surface.grass': 'Gras',
|
||||
'tours.hike.surface.rock': 'Rots',
|
||||
'tours.hike.surface.wood': 'Hout of metaal',
|
||||
'tours.hike.surface.unpaved': 'Onverhard, soort niet vastgelegd',
|
||||
'tours.hike.surface.other': 'Andere ondergrond',
|
||||
'tours.hike.surface.untagged': 'Geen ondergrond in OpenStreetMap',
|
||||
'tours.hike.surface.unclear': 'Ondergrond onduidelijk',
|
||||
'tours.hike.surface.untaggedTip':
|
||||
'OpenStreetMap legt voor deze wegen geen ondergrond vast. Een router kan er een aannemen; dat is geen vastgelegde waarde.',
|
||||
'tours.hike.surface.knownTip':
|
||||
'Het deel van de route waarvan OpenStreetMap de ondergrond vastlegt. De rest is onbekend.',
|
||||
'tours.hike.surface.source':
|
||||
'Ondergronden zoals OpenStreetMap ze vastlegt, in groepen; de eigen lezing van de router telt niet. Geen beoordeling van de weg.',
|
||||
'tours.hike.way.path': 'Pad',
|
||||
'tours.hike.way.footway': 'Voetpad',
|
||||
'tours.hike.way.steps': 'Trappen',
|
||||
'tours.hike.way.track': 'Land- of bosweg',
|
||||
'tours.hike.way.cycleway': 'Fietspad',
|
||||
'tours.hike.way.quietRoad': 'Kleine weg',
|
||||
'tours.hike.way.road': 'Weg',
|
||||
'tours.hike.way.viaFerrata': 'Klettersteig',
|
||||
'tours.hike.way.other': 'Andere weg',
|
||||
'tours.hike.way.untagged': 'Geen wegtype in OpenStreetMap',
|
||||
'tours.hike.way.unclear': 'Wegtype onduidelijk',
|
||||
'tours.hike.way.untaggedTip': 'OpenStreetMap legt hier geen wegtype vast.',
|
||||
'tours.hike.way.knownTip': 'Het deel van de route waarvan OpenStreetMap het wegtype vastlegt. De rest is onbekend.',
|
||||
'tours.hike.way.source':
|
||||
'Wegtypen zoals OpenStreetMap ze vastlegt (highway), in groepen. Zegt niets over veiligheid of geschiktheid.',
|
||||
'tours.copy.action': 'Opslaan als nieuwe tocht',
|
||||
'tours.copy.hint':
|
||||
'Kopieert deze tocht als nieuwe, zelfstandige tocht zonder zijn dagen. Het origineel blijft zoals het is.',
|
||||
|
||||
+50
-11
@@ -527,8 +527,7 @@ const tours: TranslationStrings = {
|
||||
'Stopnie nadane przez maperów OpenStreetMap. Droga bez stopnia jest nieznana, a nie łatwa. Nie mówią, czy szlak jest dla ciebie odpowiedni.',
|
||||
'tours.bike.trail.loading': 'Odczytywanie stopni trudności…',
|
||||
'tours.bike.trail.failed': 'Tym razem nie udało się odczytać stopni trudności.',
|
||||
'tours.hike.sac.title': 'Stopnie SAC',
|
||||
'tours.hike.sac.show': 'Pokaż wzdłuż trasy',
|
||||
'tours.hike.ways.show': 'Pokaż wzdłuż trasy',
|
||||
'tours.hike.sac.t1': 'Turystyka piesza',
|
||||
'tours.hike.sac.t2': 'Turystyka górska',
|
||||
'tours.hike.sac.t3': 'Wymagająca turystyka górska',
|
||||
@@ -537,24 +536,64 @@ const tours: TranslationStrings = {
|
||||
'tours.hike.sac.t6': 'Trudna turystyka wysokogórska',
|
||||
'tours.hike.sac.untagged': 'Brak stopnia w OpenStreetMap',
|
||||
'tours.hike.sac.unclear': 'Stopień niejasny',
|
||||
'tours.hike.sac.unmatched': 'Droga nie jest jednoznacznie dopasowana',
|
||||
'tours.hike.sac.unasked': 'Tym razem nie udało się sprawdzić',
|
||||
'tours.hike.sac.unknown': 'Nieznane',
|
||||
'tours.hike.ways.unmatched': 'Droga nie jest jednoznacznie dopasowana',
|
||||
'tours.hike.ways.unasked': 'Tym razem nie udało się sprawdzić',
|
||||
'tours.hike.ways.unknown': 'Nieznane',
|
||||
'tours.hike.sac.stepTip': 'Skala turystyczna SAC {step}, z OpenStreetMap: sac_scale={tag}',
|
||||
'tours.hike.sac.untaggedTip': 'OpenStreetMap nie podaje dla tych dróg sac_scale. Nieznane nie znaczy łatwe.',
|
||||
'tours.hike.sac.unclearTip': 'OpenStreetMap podaje tu {tags}, co nie wskazuje jednego stopnia.',
|
||||
'tours.hike.sac.unmatchedTip':
|
||||
'Tutaj żadna droga nie leży wyraźnie pod linią wycieczki, więc stopień nie jest pokazywany.',
|
||||
'tours.hike.sac.known': 'Znane',
|
||||
'tours.hike.ways.unmatchedTip':
|
||||
'Tutaj żadna droga nie leży wyraźnie pod linią wycieczki, więc nic nie jest pokazywane.',
|
||||
'tours.hike.ways.known': 'Znane',
|
||||
'tours.hike.sac.knownTip': 'Część trasy, dla której OpenStreetMap podaje stopień SAC. Reszta jest nieznana.',
|
||||
'tours.hike.sac.highest': 'Najwyższy znany',
|
||||
'tours.hike.sac.routingLimit': 'Limit wyznaczania trasy',
|
||||
'tours.hike.sac.routingLimitTip': 'Najwięcej, ile może użyć wyszukiwanie trasy: ustawienie, a nie trudność dróg.',
|
||||
'tours.hike.sac.aboveLimit': 'Powyżej limitu',
|
||||
'tours.hike.sac.loading': 'Odczytywanie stopni SAC…',
|
||||
'tours.hike.sac.offline': 'Stopnie SAC wymagają połączenia.',
|
||||
'tours.hike.sac.failed': 'Nie udało się odczytać stopni SAC. Wycieczka pozostaje bez zmian.',
|
||||
'tours.hike.sac.source': 'Z OpenStreetMap. Możliwe luki; to nie jest ocena bezpieczeństwa.',
|
||||
'tours.hike.ways.title': 'Szczegóły szlaku',
|
||||
'tours.hike.ways.facets': 'Co pokazać',
|
||||
'tours.hike.ways.facet.sac': 'SAC',
|
||||
'tours.hike.ways.facet.surface': 'Nawierzchnia',
|
||||
'tours.hike.ways.facet.way': 'Rodzaj drogi',
|
||||
'tours.hike.ways.mostly': 'Głównie',
|
||||
'tours.hike.ways.recordedTip': 'Zapisane w OpenStreetMap jako {tags}',
|
||||
'tours.hike.ways.unclearTip': 'OpenStreetMap podaje tu {tags}, co nie pasuje do żadnej grupy.',
|
||||
'tours.hike.ways.loading': 'Odczytywanie dróg wzdłuż trasy…',
|
||||
'tours.hike.ways.offline': 'Szczegóły szlaku wymagają połączenia.',
|
||||
'tours.hike.ways.failed': 'Nie udało się odczytać szczegółów szlaku. Wycieczka się nie zmieniła.',
|
||||
'tours.hike.surface.paved': 'Asfalt lub beton',
|
||||
'tours.hike.surface.sett': 'Kostka lub bruk',
|
||||
'tours.hike.surface.compacted': 'Utwardzona lub drobny żwir',
|
||||
'tours.hike.surface.gravel': 'Szuter',
|
||||
'tours.hike.surface.ground': 'Grunt naturalny',
|
||||
'tours.hike.surface.grass': 'Trawa',
|
||||
'tours.hike.surface.rock': 'Skała',
|
||||
'tours.hike.surface.wood': 'Drewno lub metal',
|
||||
'tours.hike.surface.unpaved': 'Nieutwardzona, rodzaj niezapisany',
|
||||
'tours.hike.surface.other': 'Inna nawierzchnia',
|
||||
'tours.hike.surface.untagged': 'Brak nawierzchni w OpenStreetMap',
|
||||
'tours.hike.surface.unclear': 'Nawierzchnia niejasna',
|
||||
'tours.hike.surface.untaggedTip':
|
||||
'OpenStreetMap nie zapisuje nawierzchni tych dróg. Router może jakąś założyć; to nie jest zapisana wartość.',
|
||||
'tours.hike.surface.knownTip': 'Część trasy, której nawierzchnię zapisuje OpenStreetMap. Reszta jest nieznana.',
|
||||
'tours.hike.surface.source':
|
||||
'Nawierzchnie tak, jak zapisuje je OpenStreetMap, w grupach; własny odczyt routera nie jest używany. To nie ocena drogi.',
|
||||
'tours.hike.way.path': 'Ścieżka',
|
||||
'tours.hike.way.footway': 'Chodnik',
|
||||
'tours.hike.way.steps': 'Schody',
|
||||
'tours.hike.way.track': 'Droga polna lub leśna',
|
||||
'tours.hike.way.cycleway': 'Droga rowerowa',
|
||||
'tours.hike.way.quietRoad': 'Droga lokalna',
|
||||
'tours.hike.way.road': 'Droga',
|
||||
'tours.hike.way.viaFerrata': 'Ferrata',
|
||||
'tours.hike.way.other': 'Inna droga',
|
||||
'tours.hike.way.untagged': 'Brak rodzaju drogi w OpenStreetMap',
|
||||
'tours.hike.way.unclear': 'Rodzaj drogi niejasny',
|
||||
'tours.hike.way.untaggedTip': 'OpenStreetMap nie zapisuje tu rodzaju drogi.',
|
||||
'tours.hike.way.knownTip': 'Część trasy, której rodzaj drogi zapisuje OpenStreetMap. Reszta jest nieznana.',
|
||||
'tours.hike.way.source':
|
||||
'Rodzaje dróg tak, jak zapisuje je OpenStreetMap (highway), w grupach. Nie mówi nic o bezpieczeństwie ani przydatności.',
|
||||
'tours.copy.action': 'Zapisz jako nową trasę',
|
||||
'tours.copy.hint': 'Kopiuje tę trasę jako nową, niezależną trasę bez jej dni. Oryginał zostaje bez zmian.',
|
||||
'tours.copy.name': '{name} (kopia)',
|
||||
|
||||
+50
-11
@@ -531,8 +531,7 @@ const tours: TranslationStrings = {
|
||||
'Сложность, как её указали участники OpenStreetMap. Дорога без оценки неизвестна, а не проста. Это ничего не говорит о том, подходит ли тебе трейл.',
|
||||
'tours.bike.trail.loading': 'Читаем сложность троп…',
|
||||
'tours.bike.trail.failed': 'На этот раз не удалось прочитать сложность троп.',
|
||||
'tours.hike.sac.title': 'Категории SAC',
|
||||
'tours.hike.sac.show': 'Показать вдоль маршрута',
|
||||
'tours.hike.ways.show': 'Показать вдоль маршрута',
|
||||
'tours.hike.sac.t1': 'Пеший поход',
|
||||
'tours.hike.sac.t2': 'Горный поход',
|
||||
'tours.hike.sac.t3': 'Сложный горный поход',
|
||||
@@ -541,25 +540,65 @@ const tours: TranslationStrings = {
|
||||
'tours.hike.sac.t6': 'Очень сложный высокогорный поход',
|
||||
'tours.hike.sac.untagged': 'Нет категории в OpenStreetMap',
|
||||
'tours.hike.sac.unclear': 'Категория неясна',
|
||||
'tours.hike.sac.unmatched': 'Путь не сопоставлен однозначно',
|
||||
'tours.hike.sac.unasked': 'В этот раз не удалось запросить',
|
||||
'tours.hike.sac.unknown': 'Неизвестно',
|
||||
'tours.hike.ways.unmatched': 'Путь не сопоставлен однозначно',
|
||||
'tours.hike.ways.unasked': 'В этот раз не удалось запросить',
|
||||
'tours.hike.ways.unknown': 'Неизвестно',
|
||||
'tours.hike.sac.stepTip': 'Шкала пеших маршрутов SAC {step}, из OpenStreetMap: sac_scale={tag}',
|
||||
'tours.hike.sac.untaggedTip': 'OpenStreetMap не указывает sac_scale для этих путей. Неизвестно не значит легко.',
|
||||
'tours.hike.sac.unclearTip': 'OpenStreetMap указывает здесь {tags}, что не означает одну категорию.',
|
||||
'tours.hike.sac.unmatchedTip':
|
||||
'Здесь ни один путь однозначно не лежит под линией маршрута, поэтому категория не показана.',
|
||||
'tours.hike.sac.known': 'Известно',
|
||||
'tours.hike.ways.unmatchedTip':
|
||||
'Здесь ни один путь однозначно не лежит под линией маршрута, поэтому для него ничего не показано.',
|
||||
'tours.hike.ways.known': 'Известно',
|
||||
'tours.hike.sac.knownTip': 'Доля маршрута, для которой OpenStreetMap указывает категорию SAC. Остальное неизвестно.',
|
||||
'tours.hike.sac.highest': 'Высшая известная',
|
||||
'tours.hike.sac.routingLimit': 'Предел маршрутизации',
|
||||
'tours.hike.sac.routingLimitTip':
|
||||
'Максимум, который может использовать поиск маршрута: настройка, а не сложность путей.',
|
||||
'tours.hike.sac.aboveLimit': 'Выше предела',
|
||||
'tours.hike.sac.loading': 'Загрузка категорий SAC…',
|
||||
'tours.hike.sac.offline': 'Для категорий SAC нужно подключение.',
|
||||
'tours.hike.sac.failed': 'Не удалось загрузить категории SAC. Маршрут не изменён.',
|
||||
'tours.hike.sac.source': 'Из OpenStreetMap. Возможны пробелы; это не оценка безопасности.',
|
||||
'tours.hike.ways.title': 'Сведения о тропе',
|
||||
'tours.hike.ways.facets': 'Что показать',
|
||||
'tours.hike.ways.facet.sac': 'SAC',
|
||||
'tours.hike.ways.facet.surface': 'Покрытие',
|
||||
'tours.hike.ways.facet.way': 'Тип пути',
|
||||
'tours.hike.ways.mostly': 'В основном',
|
||||
'tours.hike.ways.recordedTip': 'В OpenStreetMap записано как {tags}',
|
||||
'tours.hike.ways.unclearTip': 'OpenStreetMap пишет здесь {tags}, это не входит ни в одну группу.',
|
||||
'tours.hike.ways.loading': 'Читаем пути вдоль похода…',
|
||||
'tours.hike.ways.offline': 'Для сведений о тропе нужно подключение.',
|
||||
'tours.hike.ways.failed': 'Не удалось прочитать сведения о тропе. Маршрут не изменился.',
|
||||
'tours.hike.surface.paved': 'Асфальт или бетон',
|
||||
'tours.hike.surface.sett': 'Брусчатка или булыжник',
|
||||
'tours.hike.surface.compacted': 'Уплотнённое или мелкий гравий',
|
||||
'tours.hike.surface.gravel': 'Гравий',
|
||||
'tours.hike.surface.ground': 'Грунт',
|
||||
'tours.hike.surface.grass': 'Трава',
|
||||
'tours.hike.surface.rock': 'Скала',
|
||||
'tours.hike.surface.wood': 'Дерево или металл',
|
||||
'tours.hike.surface.unpaved': 'Без покрытия, вид не указан',
|
||||
'tours.hike.surface.other': 'Другое покрытие',
|
||||
'tours.hike.surface.untagged': 'Нет покрытия в OpenStreetMap',
|
||||
'tours.hike.surface.unclear': 'Покрытие неясно',
|
||||
'tours.hike.surface.untaggedTip':
|
||||
'OpenStreetMap не указывает покрытие этих путей. Маршрутизатор может его предположить; это не записанное значение.',
|
||||
'tours.hike.surface.knownTip': 'Доля маршрута, покрытие которой указано в OpenStreetMap. Остальное неизвестно.',
|
||||
'tours.hike.surface.source':
|
||||
'Покрытия так, как их записывает OpenStreetMap, по группам; собственное прочтение маршрутизатора не используется. Это не оценка пути.',
|
||||
'tours.hike.way.path': 'Тропа',
|
||||
'tours.hike.way.footway': 'Пешеходная дорожка',
|
||||
'tours.hike.way.steps': 'Лестница',
|
||||
'tours.hike.way.track': 'Полевая или лесная дорога',
|
||||
'tours.hike.way.cycleway': 'Велодорожка',
|
||||
'tours.hike.way.quietRoad': 'Второстепенная дорога',
|
||||
'tours.hike.way.road': 'Дорога',
|
||||
'tours.hike.way.viaFerrata': 'Виа феррата',
|
||||
'tours.hike.way.other': 'Другой путь',
|
||||
'tours.hike.way.untagged': 'Нет типа пути в OpenStreetMap',
|
||||
'tours.hike.way.unclear': 'Тип пути неясен',
|
||||
'tours.hike.way.untaggedTip': 'OpenStreetMap не указывает здесь тип пути.',
|
||||
'tours.hike.way.knownTip': 'Доля маршрута, тип пути которой указан в OpenStreetMap. Остальное неизвестно.',
|
||||
'tours.hike.way.source':
|
||||
'Типы путей так, как их записывает OpenStreetMap (highway), по группам. Ничего не говорит о безопасности или пригодности.',
|
||||
'tours.copy.action': 'Сохранить как новый поход',
|
||||
'tours.copy.hint': 'Копирует этот поход как новый, самостоятельный поход без его дней. Оригинал остаётся как есть.',
|
||||
'tours.copy.name': '{name} (копия)',
|
||||
|
||||
+50
-10
@@ -512,8 +512,7 @@ const tours: TranslationStrings = {
|
||||
'Stupne, ako ich zadali mapovači OpenStreetMap. Cesta bez stupňa je neznáma, nie ľahká. Nehovoria nič o tom, či sa trail hodí pre teba.',
|
||||
'tours.bike.trail.loading': 'Načítava sa obtiažnosť trailov…',
|
||||
'tours.bike.trail.failed': 'Obtiažnosť trailov sa tentoraz nepodarilo načítať.',
|
||||
'tours.hike.sac.title': 'Obtiažnosť SAC',
|
||||
'tours.hike.sac.show': 'Zobraziť pozdĺž trasy',
|
||||
'tours.hike.ways.show': 'Zobraziť pozdĺž trasy',
|
||||
'tours.hike.sac.t1': 'Pešia turistika',
|
||||
'tours.hike.sac.t2': 'Horská turistika',
|
||||
'tours.hike.sac.t3': 'Náročná horská turistika',
|
||||
@@ -522,23 +521,64 @@ const tours: TranslationStrings = {
|
||||
'tours.hike.sac.t6': 'Veľmi náročná vysokohorská turistika',
|
||||
'tours.hike.sac.untagged': 'Bez stupňa v OpenStreetMap',
|
||||
'tours.hike.sac.unclear': 'Stupeň nejasný',
|
||||
'tours.hike.sac.unmatched': 'Cesta nie je jednoznačne priradená',
|
||||
'tours.hike.sac.unasked': 'Tentoraz sa nepodarilo zistiť',
|
||||
'tours.hike.sac.unknown': 'Neznáme',
|
||||
'tours.hike.ways.unmatched': 'Cesta nie je jednoznačne priradená',
|
||||
'tours.hike.ways.unasked': 'Tentoraz sa nepodarilo zistiť',
|
||||
'tours.hike.ways.unknown': 'Neznáme',
|
||||
'tours.hike.sac.stepTip': 'Turistická stupnica SAC {step}, z OpenStreetMap: sac_scale={tag}',
|
||||
'tours.hike.sac.untaggedTip': 'OpenStreetMap týmto cestám nedáva sac_scale. Neznáme neznamená ľahké.',
|
||||
'tours.hike.sac.unclearTip': 'OpenStreetMap tu uvádza {tags}, čo neoznačuje jediný stupeň.',
|
||||
'tours.hike.sac.unmatchedTip': 'Tu pod líniou túry jednoznačne neleží žiadna cesta, preto sa stupeň nezobrazuje.',
|
||||
'tours.hike.sac.known': 'Známe',
|
||||
'tours.hike.ways.unmatchedTip':
|
||||
'Tu pod líniou túry jednoznačne neleží žiadna cesta, preto sa pre ňu nič nezobrazuje.',
|
||||
'tours.hike.ways.known': 'Známe',
|
||||
'tours.hike.sac.knownTip': 'Podiel trasy, pre ktorý OpenStreetMap uvádza stupeň SAC. Zvyšok je neznámy.',
|
||||
'tours.hike.sac.highest': 'Najvyšší známy',
|
||||
'tours.hike.sac.routingLimit': 'Limit trasovania',
|
||||
'tours.hike.sac.routingLimitTip': 'Najviac, čo smie hľadanie trasy použiť: nastavenie, nie obtiažnosť ciest.',
|
||||
'tours.hike.sac.aboveLimit': 'Nad limitom',
|
||||
'tours.hike.sac.loading': 'Načítavajú sa stupne SAC…',
|
||||
'tours.hike.sac.offline': 'Stupne SAC potrebujú pripojenie.',
|
||||
'tours.hike.sac.failed': 'Stupne SAC sa nepodarilo načítať. Túra zostáva bez zmeny.',
|
||||
'tours.hike.sac.source': 'Z OpenStreetMap. Môžu chýbať údaje; nejde o hodnotenie bezpečnosti.',
|
||||
'tours.hike.ways.title': 'Podrobnosti o ceste',
|
||||
'tours.hike.ways.facets': 'Čo zobraziť',
|
||||
'tours.hike.ways.facet.sac': 'SAC',
|
||||
'tours.hike.ways.facet.surface': 'Povrch',
|
||||
'tours.hike.ways.facet.way': 'Typ cesty',
|
||||
'tours.hike.ways.mostly': 'Prevažne',
|
||||
'tours.hike.ways.recordedTip': 'V OpenStreetMap zapísané ako {tags}',
|
||||
'tours.hike.ways.unclearTip': 'OpenStreetMap tu uvádza {tags}, čo nepatrí do žiadnej skupiny.',
|
||||
'tours.hike.ways.loading': 'Načítavajú sa cesty pozdĺž túry…',
|
||||
'tours.hike.ways.offline': 'Podrobnosti o ceste potrebujú pripojenie.',
|
||||
'tours.hike.ways.failed': 'Podrobnosti o ceste sa nepodarilo načítať. Túra zostáva bez zmeny.',
|
||||
'tours.hike.surface.paved': 'Asfalt alebo betón',
|
||||
'tours.hike.surface.sett': 'Dlažba alebo mačacie hlavy',
|
||||
'tours.hike.surface.compacted': 'Zhutnený povrch alebo jemný štrk',
|
||||
'tours.hike.surface.gravel': 'Štrk',
|
||||
'tours.hike.surface.ground': 'Prírodný povrch',
|
||||
'tours.hike.surface.grass': 'Tráva',
|
||||
'tours.hike.surface.rock': 'Skala',
|
||||
'tours.hike.surface.wood': 'Drevo alebo kov',
|
||||
'tours.hike.surface.unpaved': 'Nespevnený, druh nezapísaný',
|
||||
'tours.hike.surface.other': 'Iný povrch',
|
||||
'tours.hike.surface.untagged': 'Žiadny povrch v OpenStreetMap',
|
||||
'tours.hike.surface.unclear': 'Povrch nejasný',
|
||||
'tours.hike.surface.untaggedTip':
|
||||
'OpenStreetMap pre tieto cesty neuvádza povrch. Plánovač trás môže nejaký predpokladať; to nie je zapísaná hodnota.',
|
||||
'tours.hike.surface.knownTip': 'Časť trasy, ktorej povrch OpenStreetMap uvádza. Zvyšok je neznámy.',
|
||||
'tours.hike.surface.source':
|
||||
'Povrchy tak, ako ich zapisuje OpenStreetMap, v skupinách; vlastné čítanie plánovača sa nepoužíva. Nejde o hodnotenie cesty.',
|
||||
'tours.hike.way.path': 'Chodník',
|
||||
'tours.hike.way.footway': 'Chodník pre peších',
|
||||
'tours.hike.way.steps': 'Schody',
|
||||
'tours.hike.way.track': 'Poľná alebo lesná cesta',
|
||||
'tours.hike.way.cycleway': 'Cyklotrasa',
|
||||
'tours.hike.way.quietRoad': 'Vedľajšia cesta',
|
||||
'tours.hike.way.road': 'Cesta',
|
||||
'tours.hike.way.viaFerrata': 'Zaistená cesta',
|
||||
'tours.hike.way.other': 'Iná cesta',
|
||||
'tours.hike.way.untagged': 'Žiadny typ cesty v OpenStreetMap',
|
||||
'tours.hike.way.unclear': 'Typ cesty nejasný',
|
||||
'tours.hike.way.untaggedTip': 'OpenStreetMap tu neuvádza žiadny typ cesty.',
|
||||
'tours.hike.way.knownTip': 'Časť trasy, ktorej typ cesty OpenStreetMap uvádza. Zvyšok je neznámy.',
|
||||
'tours.hike.way.source':
|
||||
'Typy ciest tak, ako ich zapisuje OpenStreetMap (highway), v skupinách. Nehovorí nič o bezpečnosti ani vhodnosti.',
|
||||
'tours.copy.action': 'Uložiť ako novú túru',
|
||||
'tours.copy.hint': 'Skopíruje túto túru ako novú, samostatnú túru bez jej dní. Originál zostane, ako je.',
|
||||
'tours.copy.name': '{name} (kópia)',
|
||||
|
||||
+49
-10
@@ -509,8 +509,7 @@ const tours: TranslationStrings = {
|
||||
'Grader som OpenStreetMaps kartläggare satt dem. En väg utan grad är okänd, inte lätt. De säger inget om huruvida en stig passar dig.',
|
||||
'tours.bike.trail.loading': 'Läser svårighetsgraderna…',
|
||||
'tours.bike.trail.failed': 'Svårighetsgraderna kunde inte läsas den här gången.',
|
||||
'tours.hike.sac.title': 'SAC-grader',
|
||||
'tours.hike.sac.show': 'Visa längs rutten',
|
||||
'tours.hike.ways.show': 'Visa längs rutten',
|
||||
'tours.hike.sac.t1': 'Vandring',
|
||||
'tours.hike.sac.t2': 'Bergsvandring',
|
||||
'tours.hike.sac.t3': 'Krävande bergsvandring',
|
||||
@@ -519,23 +518,63 @@ const tours: TranslationStrings = {
|
||||
'tours.hike.sac.t6': 'Svår alpin vandring',
|
||||
'tours.hike.sac.untagged': 'Ingen grad i OpenStreetMap',
|
||||
'tours.hike.sac.unclear': 'Graden oklar',
|
||||
'tours.hike.sac.unmatched': 'Vägen inte entydigt matchad',
|
||||
'tours.hike.sac.unasked': 'Kunde inte frågas den här gången',
|
||||
'tours.hike.sac.unknown': 'Okänt',
|
||||
'tours.hike.ways.unmatched': 'Vägen inte entydigt matchad',
|
||||
'tours.hike.ways.unasked': 'Kunde inte frågas den här gången',
|
||||
'tours.hike.ways.unknown': 'Okänt',
|
||||
'tours.hike.sac.stepTip': 'SAC-vandringsskala {step}, från OpenStreetMap: sac_scale={tag}',
|
||||
'tours.hike.sac.untaggedTip': 'OpenStreetMap ger dessa vägar ingen sac_scale. Okänt betyder inte lätt.',
|
||||
'tours.hike.sac.unclearTip': 'OpenStreetMap anger här {tags}, vilket inte pekar ut en enda grad.',
|
||||
'tours.hike.sac.unmatchedTip': 'Här ligger ingen väg tydligt under turens linje, så ingen grad visas.',
|
||||
'tours.hike.sac.known': 'Känt',
|
||||
'tours.hike.ways.unmatchedTip': 'Här ligger ingen väg tydligt under turens linje, så inget visas för den.',
|
||||
'tours.hike.ways.known': 'Känt',
|
||||
'tours.hike.sac.knownTip': 'Den del av rutten där OpenStreetMap anger en SAC-grad. Resten är okänd.',
|
||||
'tours.hike.sac.highest': 'Högsta kända',
|
||||
'tours.hike.sac.routingLimit': 'Ruttgräns',
|
||||
'tours.hike.sac.routingLimitTip': 'Det mesta ruttsökningen får använda: en inställning, inte vägarnas svårighet.',
|
||||
'tours.hike.sac.aboveLimit': 'Över gränsen',
|
||||
'tours.hike.sac.loading': 'Läser SAC-graderna…',
|
||||
'tours.hike.sac.offline': 'SAC-graderna behöver en anslutning.',
|
||||
'tours.hike.sac.failed': 'SAC-graderna kunde inte läsas. Turen är oförändrad.',
|
||||
'tours.hike.sac.source': 'Från OpenStreetMap. Luckor kan finnas; ingen säkerhetsbedömning.',
|
||||
'tours.hike.ways.title': 'Leddetaljer',
|
||||
'tours.hike.ways.facets': 'Vad som visas',
|
||||
'tours.hike.ways.facet.sac': 'SAC',
|
||||
'tours.hike.ways.facet.surface': 'Underlag',
|
||||
'tours.hike.ways.facet.way': 'Vägtyp',
|
||||
'tours.hike.ways.mostly': 'Mest',
|
||||
'tours.hike.ways.recordedTip': 'Registrerat i OpenStreetMap som {tags}',
|
||||
'tours.hike.ways.unclearTip': 'OpenStreetMap anger här {tags}, vilket inte passar i någon grupp.',
|
||||
'tours.hike.ways.loading': 'Läser vägarna längs vandringen…',
|
||||
'tours.hike.ways.offline': 'Leddetaljerna behöver en anslutning.',
|
||||
'tours.hike.ways.failed': 'Leddetaljerna kunde inte läsas. Turen är oförändrad.',
|
||||
'tours.hike.surface.paved': 'Asfalt eller betong',
|
||||
'tours.hike.surface.sett': 'Gatsten eller kullersten',
|
||||
'tours.hike.surface.compacted': 'Packat grus eller stenmjöl',
|
||||
'tours.hike.surface.gravel': 'Grus',
|
||||
'tours.hike.surface.ground': 'Naturmark',
|
||||
'tours.hike.surface.grass': 'Gräs',
|
||||
'tours.hike.surface.rock': 'Berghäll',
|
||||
'tours.hike.surface.wood': 'Trä eller metall',
|
||||
'tours.hike.surface.unpaved': 'Obelagd, typ ej registrerad',
|
||||
'tours.hike.surface.other': 'Annat underlag',
|
||||
'tours.hike.surface.untagged': 'Inget underlag i OpenStreetMap',
|
||||
'tours.hike.surface.unclear': 'Underlag oklart',
|
||||
'tours.hike.surface.untaggedTip':
|
||||
'OpenStreetMap anger inget underlag för dessa vägar. En ruttplanerare kan anta ett; det är inget registrerat värde.',
|
||||
'tours.hike.surface.knownTip': 'Den del av rutten vars underlag OpenStreetMap anger. Resten är okänt.',
|
||||
'tours.hike.surface.source':
|
||||
'Underlag som OpenStreetMap registrerar dem, i grupper; ruttplanerarens egen tolkning används inte. Ingen bedömning av vägen.',
|
||||
'tours.hike.way.path': 'Stig',
|
||||
'tours.hike.way.footway': 'Gångväg',
|
||||
'tours.hike.way.steps': 'Trappor',
|
||||
'tours.hike.way.track': 'Skogs- eller jordbruksväg',
|
||||
'tours.hike.way.cycleway': 'Cykelväg',
|
||||
'tours.hike.way.quietRoad': 'Mindre väg',
|
||||
'tours.hike.way.road': 'Väg',
|
||||
'tours.hike.way.viaFerrata': 'Klätterled',
|
||||
'tours.hike.way.other': 'Annan väg',
|
||||
'tours.hike.way.untagged': 'Ingen vägtyp i OpenStreetMap',
|
||||
'tours.hike.way.unclear': 'Vägtyp oklar',
|
||||
'tours.hike.way.untaggedTip': 'OpenStreetMap anger ingen vägtyp här.',
|
||||
'tours.hike.way.knownTip': 'Den del av rutten vars vägtyp OpenStreetMap anger. Resten är okänt.',
|
||||
'tours.hike.way.source':
|
||||
'Vägtyper som OpenStreetMap registrerar dem (highway), i grupper. Säger inget om säkerhet eller lämplighet.',
|
||||
'tours.copy.action': 'Spara som ny tur',
|
||||
'tours.copy.hint': 'Kopierar turen som en ny, fristående tur utan dess dagar. Originalet förblir som det är.',
|
||||
'tours.copy.name': '{name} (kopia)',
|
||||
|
||||
+47
-10
@@ -490,8 +490,7 @@ const tours: TranslationStrings = {
|
||||
'ระดับตามที่ผู้ทำแผนที่ OpenStreetMap กำหนด ทางที่ไม่มีระดับคือไม่ทราบ ไม่ใช่ง่าย และไม่ได้บอกว่าเส้นทางเหมาะกับคุณหรือไม่',
|
||||
'tours.bike.trail.loading': 'กำลังอ่านระดับความยาก…',
|
||||
'tours.bike.trail.failed': 'ครั้งนี้อ่านระดับความยากไม่ได้',
|
||||
'tours.hike.sac.title': 'ระดับ SAC',
|
||||
'tours.hike.sac.show': 'แสดงตามเส้นทาง',
|
||||
'tours.hike.ways.show': 'แสดงตามเส้นทาง',
|
||||
'tours.hike.sac.t1': 'เดินป่า',
|
||||
'tours.hike.sac.t2': 'เดินป่าบนภูเขา',
|
||||
'tours.hike.sac.t3': 'เดินป่าบนภูเขาระดับท้าทาย',
|
||||
@@ -500,23 +499,61 @@ const tours: TranslationStrings = {
|
||||
'tours.hike.sac.t6': 'เดินป่าแบบอัลไพน์ระดับยาก',
|
||||
'tours.hike.sac.untagged': 'ไม่มีระดับใน OpenStreetMap',
|
||||
'tours.hike.sac.unclear': 'ระดับไม่ชัดเจน',
|
||||
'tours.hike.sac.unmatched': 'ทางไม่ตรงกันอย่างชัดเจน',
|
||||
'tours.hike.sac.unasked': 'ครั้งนี้สอบถามไม่ได้',
|
||||
'tours.hike.sac.unknown': 'ไม่ทราบ',
|
||||
'tours.hike.ways.unmatched': 'ทางไม่ตรงกันอย่างชัดเจน',
|
||||
'tours.hike.ways.unasked': 'ครั้งนี้สอบถามไม่ได้',
|
||||
'tours.hike.ways.unknown': 'ไม่ทราบ',
|
||||
'tours.hike.sac.stepTip': 'มาตราการเดินป่า SAC {step} จาก OpenStreetMap: sac_scale={tag}',
|
||||
'tours.hike.sac.untaggedTip': 'OpenStreetMap ไม่ได้ระบุ sac_scale ให้ทางเหล่านี้ ไม่ทราบไม่ได้แปลว่าง่าย',
|
||||
'tours.hike.sac.unclearTip': 'OpenStreetMap เขียนไว้ที่นี่ว่า {tags} ซึ่งไม่ได้ระบุระดับเดียว',
|
||||
'tours.hike.sac.unmatchedTip': 'ที่นี่ไม่มีทางใดอยู่ใต้เส้นของทัวร์อย่างชัดเจน จึงไม่แสดงระดับ',
|
||||
'tours.hike.sac.known': 'ทราบแล้ว',
|
||||
'tours.hike.ways.unmatchedTip': 'ที่นี่ไม่มีทางใดอยู่ใต้เส้นของทัวร์อย่างชัดเจน จึงไม่แสดงสิ่งใด',
|
||||
'tours.hike.ways.known': 'ทราบแล้ว',
|
||||
'tours.hike.sac.knownTip': 'สัดส่วนของเส้นทางที่ OpenStreetMap ระบุระดับ SAC ส่วนที่เหลือไม่ทราบ',
|
||||
'tours.hike.sac.highest': 'สูงสุดที่ทราบ',
|
||||
'tours.hike.sac.routingLimit': 'ขีดจำกัดการหาเส้นทาง',
|
||||
'tours.hike.sac.routingLimitTip': 'ค่าสูงสุดที่การค้นหาเส้นทางใช้ได้ เป็นการตั้งค่า ไม่ใช่ระดับความยากของทาง',
|
||||
'tours.hike.sac.aboveLimit': 'เกินขีดจำกัด',
|
||||
'tours.hike.sac.loading': 'กำลังอ่านระดับ SAC…',
|
||||
'tours.hike.sac.offline': 'ระดับ SAC ต้องใช้การเชื่อมต่อ',
|
||||
'tours.hike.sac.failed': 'อ่านระดับ SAC ไม่ได้ ทัวร์ไม่เปลี่ยนแปลง',
|
||||
'tours.hike.sac.source': 'จาก OpenStreetMap อาจมีช่องว่าง ไม่ใช่การประเมินความปลอดภัย',
|
||||
'tours.hike.ways.title': 'รายละเอียดเส้นทางเดิน',
|
||||
'tours.hike.ways.facets': 'สิ่งที่แสดง',
|
||||
'tours.hike.ways.facet.sac': 'SAC',
|
||||
'tours.hike.ways.facet.surface': 'พื้นผิว',
|
||||
'tours.hike.ways.facet.way': 'ประเภททาง',
|
||||
'tours.hike.ways.mostly': 'ส่วนใหญ่',
|
||||
'tours.hike.ways.recordedTip': 'บันทึกใน OpenStreetMap เป็น {tags}',
|
||||
'tours.hike.ways.unclearTip': 'OpenStreetMap เขียนว่า {tags} ที่นี่ ซึ่งไม่เข้ากลุ่มใดเลย',
|
||||
'tours.hike.ways.loading': 'กำลังอ่านทางตลอดเส้นทางเดินป่า…',
|
||||
'tours.hike.ways.offline': 'รายละเอียดเส้นทางเดินต้องใช้การเชื่อมต่อ',
|
||||
'tours.hike.ways.failed': 'อ่านรายละเอียดเส้นทางเดินไม่ได้ ทัวร์ไม่เปลี่ยนแปลง',
|
||||
'tours.hike.surface.paved': 'ลาดยางหรือคอนกรีต',
|
||||
'tours.hike.surface.sett': 'อิฐตัวหนอนหรือหินปู',
|
||||
'tours.hike.surface.compacted': 'บดอัดหรือกรวดละเอียด',
|
||||
'tours.hike.surface.gravel': 'กรวด',
|
||||
'tours.hike.surface.ground': 'ดินธรรมชาติ',
|
||||
'tours.hike.surface.grass': 'หญ้า',
|
||||
'tours.hike.surface.rock': 'หิน',
|
||||
'tours.hike.surface.wood': 'ไม้หรือโลหะ',
|
||||
'tours.hike.surface.unpaved': 'ไม่ลาดยาง ไม่ได้บันทึกชนิด',
|
||||
'tours.hike.surface.other': 'พื้นผิวอื่น',
|
||||
'tours.hike.surface.untagged': 'ไม่มีพื้นผิวใน OpenStreetMap',
|
||||
'tours.hike.surface.unclear': 'พื้นผิวไม่ชัดเจน',
|
||||
'tours.hike.surface.untaggedTip': 'OpenStreetMap ไม่ได้บันทึกพื้นผิวของทางเหล่านี้ ระบบหาเส้นทางอาจสมมติขึ้นมา แต่นั่นไม่ใช่ค่าที่บันทึกไว้',
|
||||
'tours.hike.surface.knownTip': 'ส่วนของเส้นทางที่ OpenStreetMap บันทึกพื้นผิวไว้ ที่เหลือไม่ทราบ',
|
||||
'tours.hike.surface.source': 'พื้นผิวตามที่ OpenStreetMap บันทึก จัดเป็นกลุ่ม ไม่ใช้การตีความของระบบหาเส้นทางเอง ไม่ใช่การประเมินทาง',
|
||||
'tours.hike.way.path': 'ทางเดิน',
|
||||
'tours.hike.way.footway': 'ทางเท้า',
|
||||
'tours.hike.way.steps': 'บันได',
|
||||
'tours.hike.way.track': 'ทางเกษตรหรือทางป่า',
|
||||
'tours.hike.way.cycleway': 'ทางจักรยาน',
|
||||
'tours.hike.way.quietRoad': 'ถนนสายรอง',
|
||||
'tours.hike.way.road': 'ถนน',
|
||||
'tours.hike.way.viaFerrata': 'เวียเฟอร์ราตา',
|
||||
'tours.hike.way.other': 'ทางอื่น',
|
||||
'tours.hike.way.untagged': 'ไม่มีประเภททางใน OpenStreetMap',
|
||||
'tours.hike.way.unclear': 'ประเภททางไม่ชัดเจน',
|
||||
'tours.hike.way.untaggedTip': 'OpenStreetMap ไม่ได้บันทึกประเภททางที่นี่',
|
||||
'tours.hike.way.knownTip': 'ส่วนของเส้นทางที่ OpenStreetMap บันทึกประเภททางไว้ ที่เหลือไม่ทราบ',
|
||||
'tours.hike.way.source':
|
||||
'ประเภททางตามที่ OpenStreetMap บันทึก (highway) จัดเป็นกลุ่ม ไม่ได้บอกอะไรเรื่องความปลอดภัยหรือความเหมาะสม',
|
||||
'tours.copy.action': 'บันทึกเป็นทัวร์ใหม่',
|
||||
'tours.copy.hint': 'คัดลอกทัวร์นี้เป็นทัวร์ใหม่ที่แยกเป็นอิสระโดยไม่รวมวัน ต้นฉบับจะคงเดิม',
|
||||
'tours.copy.name': '{name} (สำเนา)',
|
||||
|
||||
@@ -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 = 986;
|
||||
const GENERAL_KEY_COUNT = 1022;
|
||||
|
||||
function literalKeys(source: string): string[] {
|
||||
const tree = ts.createSourceFile('locale.ts', source, ts.ScriptTarget.Latest, true);
|
||||
|
||||
+49
-10
@@ -513,8 +513,7 @@ const tours: TranslationStrings = {
|
||||
'Zorluklar OpenStreetMap haritacılarının verdiği gibidir. Zorluğu olmayan bir yol kolay değil, bilinmezdir. Parkurun sana uygun olup olmadığını söylemez.',
|
||||
'tours.bike.trail.loading': 'Parkur zorlukları okunuyor…',
|
||||
'tours.bike.trail.failed': 'Parkur zorlukları bu kez okunamadı.',
|
||||
'tours.hike.sac.title': 'SAC dereceleri',
|
||||
'tours.hike.sac.show': 'Rota boyunca göster',
|
||||
'tours.hike.ways.show': 'Rota boyunca göster',
|
||||
'tours.hike.sac.t1': 'Doğa yürüyüşü',
|
||||
'tours.hike.sac.t2': 'Dağ yürüyüşü',
|
||||
'tours.hike.sac.t3': 'Zorlu dağ yürüyüşü',
|
||||
@@ -523,23 +522,63 @@ const tours: TranslationStrings = {
|
||||
'tours.hike.sac.t6': 'Çok zor yüksek dağ yürüyüşü',
|
||||
'tours.hike.sac.untagged': 'OpenStreetMap’te derece yok',
|
||||
'tours.hike.sac.unclear': 'Derece belirsiz',
|
||||
'tours.hike.sac.unmatched': 'Yol net eşleşmedi',
|
||||
'tours.hike.sac.unasked': 'Bu sefer sorgulanamadı',
|
||||
'tours.hike.sac.unknown': 'Bilinmiyor',
|
||||
'tours.hike.ways.unmatched': 'Yol net eşleşmedi',
|
||||
'tours.hike.ways.unasked': 'Bu sefer sorgulanamadı',
|
||||
'tours.hike.ways.unknown': 'Bilinmiyor',
|
||||
'tours.hike.sac.stepTip': 'SAC yürüyüş ölçeği {step}, OpenStreetMap’ten: sac_scale={tag}',
|
||||
'tours.hike.sac.untaggedTip': 'OpenStreetMap bu yollara sac_scale vermiyor. Bilinmiyor kolay demek değildir.',
|
||||
'tours.hike.sac.unclearTip': 'OpenStreetMap burada {tags} yazıyor; bu tek bir derece belirtmiyor.',
|
||||
'tours.hike.sac.unmatchedTip': 'Burada turun çizgisinin altında net bir yol yok, bu yüzden derece gösterilmiyor.',
|
||||
'tours.hike.sac.known': 'Bilinen',
|
||||
'tours.hike.ways.unmatchedTip': 'Burada turun çizgisinin altında net bir yol yok, bu yüzden bir şey gösterilmiyor.',
|
||||
'tours.hike.ways.known': 'Bilinen',
|
||||
'tours.hike.sac.knownTip': 'Rotanın, OpenStreetMap’in SAC derecesi verdiği payı. Gerisi bilinmiyor.',
|
||||
'tours.hike.sac.highest': 'En yüksek bilinen',
|
||||
'tours.hike.sac.routingLimit': 'Rota sınırı',
|
||||
'tours.hike.sac.routingLimitTip': 'Rota aramasının kullanabileceği en fazla değer: bir ayar, yolların zorluğu değil.',
|
||||
'tours.hike.sac.aboveLimit': 'Sınırın üstünde',
|
||||
'tours.hike.sac.loading': 'SAC dereceleri okunuyor…',
|
||||
'tours.hike.sac.offline': 'SAC dereceleri için bağlantı gerekir.',
|
||||
'tours.hike.sac.failed': 'SAC dereceleri okunamadı. Tur değişmedi.',
|
||||
'tours.hike.sac.source': 'OpenStreetMap’ten. Boşluklar olabilir; bir güvenlik değerlendirmesi değildir.',
|
||||
'tours.hike.ways.title': 'Parkur ayrıntıları',
|
||||
'tours.hike.ways.facets': 'Ne gösterilsin',
|
||||
'tours.hike.ways.facet.sac': 'SAC',
|
||||
'tours.hike.ways.facet.surface': 'Zemin',
|
||||
'tours.hike.ways.facet.way': 'Yol türü',
|
||||
'tours.hike.ways.mostly': 'Çoğunlukla',
|
||||
'tours.hike.ways.recordedTip': "OpenStreetMap'te şöyle kayıtlı: {tags}",
|
||||
'tours.hike.ways.unclearTip': 'OpenStreetMap burada {tags} yazıyor; bu hiçbir gruba uymuyor.',
|
||||
'tours.hike.ways.loading': 'Yürüyüş boyunca yollar okunuyor…',
|
||||
'tours.hike.ways.offline': 'Parkur ayrıntıları için bağlantı gerekir.',
|
||||
'tours.hike.ways.failed': 'Parkur ayrıntıları okunamadı. Tur değişmedi.',
|
||||
'tours.hike.surface.paved': 'Asfalt veya beton',
|
||||
'tours.hike.surface.sett': 'Parke taşı veya arnavut kaldırımı',
|
||||
'tours.hike.surface.compacted': 'Sıkıştırılmış veya ince çakıl',
|
||||
'tours.hike.surface.gravel': 'Çakıl',
|
||||
'tours.hike.surface.ground': 'Doğal zemin',
|
||||
'tours.hike.surface.grass': 'Çim',
|
||||
'tours.hike.surface.rock': 'Kaya',
|
||||
'tours.hike.surface.wood': 'Ahşap veya metal',
|
||||
'tours.hike.surface.unpaved': 'Kaplamasız, türü kayıtlı değil',
|
||||
'tours.hike.surface.other': 'Diğer zemin',
|
||||
'tours.hike.surface.untagged': "OpenStreetMap'te zemin yok",
|
||||
'tours.hike.surface.unclear': 'Zemin belirsiz',
|
||||
'tours.hike.surface.untaggedTip':
|
||||
'OpenStreetMap bu yollar için zemin kaydetmiyor. Bir rota planlayıcı bir zemin varsayabilir; bu kayıtlı bir değer değildir.',
|
||||
'tours.hike.surface.knownTip': "Rotanın zeminini OpenStreetMap'in kaydettiği kısmı. Gerisi bilinmiyor.",
|
||||
'tours.hike.surface.source':
|
||||
"Zeminler OpenStreetMap'in kaydettiği gibi, gruplar halinde; planlayıcının kendi okuması kullanılmaz. Yolun bir değerlendirmesi değildir.",
|
||||
'tours.hike.way.path': 'Patika',
|
||||
'tours.hike.way.footway': 'Yaya yolu',
|
||||
'tours.hike.way.steps': 'Merdiven',
|
||||
'tours.hike.way.track': 'Tarla veya orman yolu',
|
||||
'tours.hike.way.cycleway': 'Bisiklet yolu',
|
||||
'tours.hike.way.quietRoad': 'Tali yol',
|
||||
'tours.hike.way.road': 'Karayolu',
|
||||
'tours.hike.way.viaFerrata': 'Via ferrata parkuru',
|
||||
'tours.hike.way.other': 'Diğer yol',
|
||||
'tours.hike.way.untagged': "OpenStreetMap'te yol türü yok",
|
||||
'tours.hike.way.unclear': 'Yol türü belirsiz',
|
||||
'tours.hike.way.untaggedTip': 'OpenStreetMap burada yol türü kaydetmiyor.',
|
||||
'tours.hike.way.knownTip': "Rotanın yol türünü OpenStreetMap'in kaydettiği kısmı. Gerisi bilinmiyor.",
|
||||
'tours.hike.way.source':
|
||||
"Yol türleri OpenStreetMap'in kaydettiği gibi (highway), gruplar halinde. Güvenlik veya uygunluk hakkında bir şey söylemez.",
|
||||
'tours.copy.action': 'Yeni tur olarak kaydet',
|
||||
'tours.copy.hint': 'Bu turu günleri olmadan yeni ve bağımsız bir tur olarak kopyalar. Asıl tur olduğu gibi kalır.',
|
||||
'tours.copy.name': '{name} (kopya)',
|
||||
|
||||
+50
-10
@@ -526,8 +526,7 @@ const tours: TranslationStrings = {
|
||||
'Складність, як її вказали учасники OpenStreetMap. Дорога без оцінки невідома, а не легка. Це нічого не каже про те, чи підходить тобі трейл.',
|
||||
'tours.bike.trail.loading': 'Читаємо складність трейлів…',
|
||||
'tours.bike.trail.failed': 'Цього разу не вдалося прочитати складність трейлів.',
|
||||
'tours.hike.sac.title': 'Категорії SAC',
|
||||
'tours.hike.sac.show': 'Показати вздовж маршруту',
|
||||
'tours.hike.ways.show': 'Показати вздовж маршруту',
|
||||
'tours.hike.sac.t1': 'Піший похід',
|
||||
'tours.hike.sac.t2': 'Гірський похід',
|
||||
'tours.hike.sac.t3': 'Складний гірський похід',
|
||||
@@ -536,24 +535,65 @@ const tours: TranslationStrings = {
|
||||
'tours.hike.sac.t6': 'Дуже складний високогірний похід',
|
||||
'tours.hike.sac.untagged': 'Немає категорії в OpenStreetMap',
|
||||
'tours.hike.sac.unclear': 'Категорія неясна',
|
||||
'tours.hike.sac.unmatched': 'Шлях не зіставлено однозначно',
|
||||
'tours.hike.sac.unasked': 'Цього разу не вдалося запитати',
|
||||
'tours.hike.sac.unknown': 'Невідомо',
|
||||
'tours.hike.ways.unmatched': 'Шлях не зіставлено однозначно',
|
||||
'tours.hike.ways.unasked': 'Цього разу не вдалося запитати',
|
||||
'tours.hike.ways.unknown': 'Невідомо',
|
||||
'tours.hike.sac.stepTip': 'Шкала пішохідних маршрутів SAC {step}, з OpenStreetMap: sac_scale={tag}',
|
||||
'tours.hike.sac.untaggedTip': 'OpenStreetMap не вказує sac_scale для цих шляхів. Невідомо не означає легко.',
|
||||
'tours.hike.sac.unclearTip': 'OpenStreetMap вказує тут {tags}, що не означає одну категорію.',
|
||||
'tours.hike.sac.unmatchedTip': 'Тут жоден шлях однозначно не лежить під лінією маршруту, тому категорія не показана.',
|
||||
'tours.hike.sac.known': 'Відомо',
|
||||
'tours.hike.ways.unmatchedTip':
|
||||
'Тут жоден шлях однозначно не лежить під лінією маршруту, тому для нього нічого не показано.',
|
||||
'tours.hike.ways.known': 'Відомо',
|
||||
'tours.hike.sac.knownTip': 'Частка маршруту, для якої OpenStreetMap вказує категорію SAC. Решта невідома.',
|
||||
'tours.hike.sac.highest': 'Найвища відома',
|
||||
'tours.hike.sac.routingLimit': 'Межа маршрутизації',
|
||||
'tours.hike.sac.routingLimitTip':
|
||||
'Найбільше, що може використати пошук маршруту: налаштування, а не складність шляхів.',
|
||||
'tours.hike.sac.aboveLimit': 'Вище межі',
|
||||
'tours.hike.sac.loading': 'Завантаження категорій SAC…',
|
||||
'tours.hike.sac.offline': 'Для категорій SAC потрібне з’єднання.',
|
||||
'tours.hike.sac.failed': 'Не вдалося завантажити категорії SAC. Маршрут не змінено.',
|
||||
'tours.hike.sac.source': 'З OpenStreetMap. Можливі прогалини; це не оцінка безпеки.',
|
||||
'tours.hike.ways.title': 'Відомості про стежку',
|
||||
'tours.hike.ways.facets': 'Що показати',
|
||||
'tours.hike.ways.facet.sac': 'SAC',
|
||||
'tours.hike.ways.facet.surface': 'Покриття',
|
||||
'tours.hike.ways.facet.way': 'Тип шляху',
|
||||
'tours.hike.ways.mostly': 'Здебільшого',
|
||||
'tours.hike.ways.recordedTip': 'В OpenStreetMap записано як {tags}',
|
||||
'tours.hike.ways.unclearTip': 'OpenStreetMap пише тут {tags}, це не входить до жодної групи.',
|
||||
'tours.hike.ways.loading': 'Читаємо шляхи вздовж походу…',
|
||||
'tours.hike.ways.offline': 'Для відомостей про стежку потрібне з’єднання.',
|
||||
'tours.hike.ways.failed': 'Не вдалося прочитати відомості про стежку. Маршрут не змінився.',
|
||||
'tours.hike.surface.paved': 'Асфальт або бетон',
|
||||
'tours.hike.surface.sett': 'Бруківка або кругляк',
|
||||
'tours.hike.surface.compacted': 'Ущільнене або дрібний гравій',
|
||||
'tours.hike.surface.gravel': 'Гравій',
|
||||
'tours.hike.surface.ground': 'Ґрунт',
|
||||
'tours.hike.surface.grass': 'Трава',
|
||||
'tours.hike.surface.rock': 'Скеля',
|
||||
'tours.hike.surface.wood': 'Дерево або метал',
|
||||
'tours.hike.surface.unpaved': 'Без покриття, вид не вказано',
|
||||
'tours.hike.surface.other': 'Інше покриття',
|
||||
'tours.hike.surface.untagged': 'Немає покриття в OpenStreetMap',
|
||||
'tours.hike.surface.unclear': 'Покриття неясне',
|
||||
'tours.hike.surface.untaggedTip':
|
||||
'OpenStreetMap не вказує покриття цих шляхів. Маршрутизатор може його припустити; це не записане значення.',
|
||||
'tours.hike.surface.knownTip': 'Частка маршруту, покриття якої вказано в OpenStreetMap. Решта невідома.',
|
||||
'tours.hike.surface.source':
|
||||
'Покриття так, як їх записує OpenStreetMap, за групами; власне прочитання маршрутизатора не використовується. Це не оцінка шляху.',
|
||||
'tours.hike.way.path': 'Стежка',
|
||||
'tours.hike.way.footway': 'Пішохідна доріжка',
|
||||
'tours.hike.way.steps': 'Сходи',
|
||||
'tours.hike.way.track': 'Польова або лісова дорога',
|
||||
'tours.hike.way.cycleway': 'Велодоріжка',
|
||||
'tours.hike.way.quietRoad': 'Другорядна дорога',
|
||||
'tours.hike.way.road': 'Дорога',
|
||||
'tours.hike.way.viaFerrata': 'Віа ферата',
|
||||
'tours.hike.way.other': 'Інший шлях',
|
||||
'tours.hike.way.untagged': 'Немає типу шляху в OpenStreetMap',
|
||||
'tours.hike.way.unclear': 'Тип шляху неясний',
|
||||
'tours.hike.way.untaggedTip': 'OpenStreetMap не вказує тут тип шляху.',
|
||||
'tours.hike.way.knownTip': 'Частка маршруту, тип шляху якої вказано в OpenStreetMap. Решта невідома.',
|
||||
'tours.hike.way.source':
|
||||
'Типи шляхів так, як їх записує OpenStreetMap (highway), за групами. Нічого не каже про безпеку чи придатність.',
|
||||
'tours.copy.action': 'Зберегти як новий похід',
|
||||
'tours.copy.hint': 'Копіює цей похід як новий, самостійний похід без його днів. Оригінал лишається як є.',
|
||||
'tours.copy.name': '{name} (копія)',
|
||||
|
||||
+49
-10
@@ -510,8 +510,7 @@ const tours: TranslationStrings = {
|
||||
'Cấp độ theo những người lập bản đồ OpenStreetMap ghi. Đường không có cấp độ là chưa rõ, không phải dễ. Chúng không cho biết đường mòn có hợp với bạn không.',
|
||||
'tours.bike.trail.loading': 'Đang đọc cấp độ đường mòn…',
|
||||
'tours.bike.trail.failed': 'Lần này không đọc được cấp độ đường mòn.',
|
||||
'tours.hike.sac.title': 'Cấp độ SAC',
|
||||
'tours.hike.sac.show': 'Hiển thị dọc tuyến đường',
|
||||
'tours.hike.ways.show': 'Hiển thị dọc tuyến đường',
|
||||
'tours.hike.sac.t1': 'Đi bộ đường dài',
|
||||
'tours.hike.sac.t2': 'Đi bộ đường dài trên núi',
|
||||
'tours.hike.sac.t3': 'Đi bộ đường dài trên núi đòi hỏi kỹ năng cao',
|
||||
@@ -520,24 +519,64 @@ const tours: TranslationStrings = {
|
||||
'tours.hike.sac.t6': 'Đi bộ đường dài trên núi cao rất khó',
|
||||
'tours.hike.sac.untagged': 'Không có cấp độ trong OpenStreetMap',
|
||||
'tours.hike.sac.unclear': 'Cấp độ không rõ',
|
||||
'tours.hike.sac.unmatched': 'Đường chưa khớp rõ ràng',
|
||||
'tours.hike.sac.unasked': 'Lần này không truy vấn được',
|
||||
'tours.hike.sac.unknown': 'Không rõ',
|
||||
'tours.hike.ways.unmatched': 'Đường chưa khớp rõ ràng',
|
||||
'tours.hike.ways.unasked': 'Lần này không truy vấn được',
|
||||
'tours.hike.ways.unknown': 'Không rõ',
|
||||
'tours.hike.sac.stepTip': 'Thang leo núi SAC {step}, từ OpenStreetMap: sac_scale={tag}',
|
||||
'tours.hike.sac.untaggedTip': 'OpenStreetMap không gán sac_scale cho các đường này. Không rõ không có nghĩa là dễ.',
|
||||
'tours.hike.sac.unclearTip': 'OpenStreetMap ghi {tags} ở đây, không chỉ một cấp độ duy nhất.',
|
||||
'tours.hike.sac.unmatchedTip': 'Ở đây không có đường nào nằm rõ dưới tuyến của chuyến đi, nên không hiển thị cấp độ.',
|
||||
'tours.hike.sac.known': 'Đã biết',
|
||||
'tours.hike.ways.unmatchedTip': 'Ở đây không có đường nào nằm rõ dưới tuyến của chuyến đi, nên không hiển thị gì.',
|
||||
'tours.hike.ways.known': 'Đã biết',
|
||||
'tours.hike.sac.knownTip': 'Phần tuyến đường mà OpenStreetMap cho biết cấp độ SAC. Phần còn lại không rõ.',
|
||||
'tours.hike.sac.highest': 'Cao nhất đã biết',
|
||||
'tours.hike.sac.routingLimit': 'Giới hạn định tuyến',
|
||||
'tours.hike.sac.routingLimitTip':
|
||||
'Mức cao nhất mà tìm đường được phép dùng: một thiết lập, không phải độ khó của đường.',
|
||||
'tours.hike.sac.aboveLimit': 'Vượt giới hạn',
|
||||
'tours.hike.sac.loading': 'Đang đọc cấp độ SAC…',
|
||||
'tours.hike.sac.offline': 'Cấp độ SAC cần kết nối mạng.',
|
||||
'tours.hike.sac.failed': 'Không đọc được cấp độ SAC. Chuyến đi không thay đổi.',
|
||||
'tours.hike.sac.source': 'Từ OpenStreetMap. Có thể thiếu dữ liệu; không phải đánh giá an toàn.',
|
||||
'tours.hike.ways.title': 'Chi tiết đường mòn',
|
||||
'tours.hike.ways.facets': 'Hiển thị',
|
||||
'tours.hike.ways.facet.sac': 'SAC',
|
||||
'tours.hike.ways.facet.surface': 'Mặt đường',
|
||||
'tours.hike.ways.facet.way': 'Loại đường',
|
||||
'tours.hike.ways.mostly': 'Chủ yếu',
|
||||
'tours.hike.ways.recordedTip': 'Được ghi trong OpenStreetMap là {tags}',
|
||||
'tours.hike.ways.unclearTip': 'OpenStreetMap ghi {tags} ở đây, không thuộc nhóm nào.',
|
||||
'tours.hike.ways.loading': 'Đang đọc các con đường dọc chuyến đi bộ…',
|
||||
'tours.hike.ways.offline': 'Chi tiết đường mòn cần kết nối.',
|
||||
'tours.hike.ways.failed': 'Không đọc được chi tiết đường mòn. Chuyến đi không thay đổi.',
|
||||
'tours.hike.surface.paved': 'Nhựa đường hoặc bê tông',
|
||||
'tours.hike.surface.sett': 'Gạch lát hoặc đá cuội',
|
||||
'tours.hike.surface.compacted': 'Nén chặt hoặc sỏi mịn',
|
||||
'tours.hike.surface.gravel': 'Sỏi',
|
||||
'tours.hike.surface.ground': 'Đất tự nhiên',
|
||||
'tours.hike.surface.grass': 'Cỏ',
|
||||
'tours.hike.surface.rock': 'Đá',
|
||||
'tours.hike.surface.wood': 'Gỗ hoặc kim loại',
|
||||
'tours.hike.surface.unpaved': 'Không trải nhựa, loại chưa được ghi',
|
||||
'tours.hike.surface.other': 'Mặt đường khác',
|
||||
'tours.hike.surface.untagged': 'Không có mặt đường trong OpenStreetMap',
|
||||
'tours.hike.surface.unclear': 'Mặt đường không rõ',
|
||||
'tours.hike.surface.untaggedTip':
|
||||
'OpenStreetMap không ghi mặt đường cho những con đường này. Trình tìm đường có thể giả định một loại; đó không phải giá trị đã ghi.',
|
||||
'tours.hike.surface.knownTip': 'Phần tuyến đường có mặt đường được OpenStreetMap ghi. Phần còn lại chưa biết.',
|
||||
'tours.hike.surface.source':
|
||||
'Mặt đường theo cách OpenStreetMap ghi, chia nhóm; cách đọc riêng của trình tìm đường không được dùng. Không phải đánh giá con đường.',
|
||||
'tours.hike.way.path': 'Lối mòn',
|
||||
'tours.hike.way.footway': 'Đường đi bộ',
|
||||
'tours.hike.way.steps': 'Bậc thang',
|
||||
'tours.hike.way.track': 'Đường nông nghiệp hoặc đường rừng',
|
||||
'tours.hike.way.cycleway': 'Đường dành cho xe đạp',
|
||||
'tours.hike.way.quietRoad': 'Đường nhỏ',
|
||||
'tours.hike.way.road': 'Đường lớn',
|
||||
'tours.hike.way.viaFerrata': 'Tuyến via ferrata',
|
||||
'tours.hike.way.other': 'Đường khác',
|
||||
'tours.hike.way.untagged': 'Không có loại đường trong OpenStreetMap',
|
||||
'tours.hike.way.unclear': 'Loại đường không rõ',
|
||||
'tours.hike.way.untaggedTip': 'OpenStreetMap không ghi loại đường ở đây.',
|
||||
'tours.hike.way.knownTip': 'Phần tuyến đường có loại đường được OpenStreetMap ghi. Phần còn lại chưa biết.',
|
||||
'tours.hike.way.source':
|
||||
'Loại đường theo cách OpenStreetMap ghi (highway), chia nhóm. Không nói gì về an toàn hay mức phù hợp.',
|
||||
'tours.copy.action': 'Lưu thành chuyến đi bộ mới',
|
||||
'tours.copy.hint': 'Sao chép chuyến này thành một chuyến đi bộ mới, độc lập, không kèm các ngày. Bản gốc giữ nguyên.',
|
||||
'tours.copy.name': '{name} (bản sao)',
|
||||
|
||||
@@ -466,8 +466,7 @@ const tours: TranslationStrings = {
|
||||
'tours.bike.trail.note': '難度為 OpenStreetMap 繪圖者所標註。無難度的道路為未知,而非容易。它不說明路徑是否適合你。',
|
||||
'tours.bike.trail.loading': '正在讀取路徑難度…',
|
||||
'tours.bike.trail.failed': '本次無法讀取路徑難度。',
|
||||
'tours.hike.sac.title': 'SAC 難度',
|
||||
'tours.hike.sac.show': '沿路線顯示',
|
||||
'tours.hike.ways.show': '沿路線顯示',
|
||||
'tours.hike.sac.t1': '健行',
|
||||
'tours.hike.sac.t2': '山區健行',
|
||||
'tours.hike.sac.t3': '具挑戰性的山區健行',
|
||||
@@ -476,23 +475,60 @@ const tours: TranslationStrings = {
|
||||
'tours.hike.sac.t6': '高難度高山健行',
|
||||
'tours.hike.sac.untagged': 'OpenStreetMap 中無難度',
|
||||
'tours.hike.sac.unclear': '難度不明確',
|
||||
'tours.hike.sac.unmatched': '道路未明確對應',
|
||||
'tours.hike.sac.unasked': '本次無法查詢',
|
||||
'tours.hike.sac.unknown': '未知',
|
||||
'tours.hike.ways.unmatched': '道路未明確對應',
|
||||
'tours.hike.ways.unasked': '本次無法查詢',
|
||||
'tours.hike.ways.unknown': '未知',
|
||||
'tours.hike.sac.stepTip': 'SAC 健行難度 {step},來自 OpenStreetMap:sac_scale={tag}',
|
||||
'tours.hike.sac.untaggedTip': 'OpenStreetMap 未替這些道路標註 sac_scale。未知並不代表容易。',
|
||||
'tours.hike.sac.unclearTip': 'OpenStreetMap 在此處寫的是 {tags},並未指出單一等級。',
|
||||
'tours.hike.sac.unmatchedTip': '此處沒有道路明確位於路線下方,因此不顯示難度。',
|
||||
'tours.hike.sac.known': '已知',
|
||||
'tours.hike.ways.unmatchedTip': '此處沒有道路明確位於路線下方,因此不顯示任何內容。',
|
||||
'tours.hike.ways.known': '已知',
|
||||
'tours.hike.sac.knownTip': 'OpenStreetMap 提供 SAC 難度的路線比例。其餘為未知。',
|
||||
'tours.hike.sac.highest': '已知最高',
|
||||
'tours.hike.sac.routingLimit': '路線規劃上限',
|
||||
'tours.hike.sac.routingLimitTip': '路線搜尋最多可使用的難度:這是一項設定,不是道路的難度。',
|
||||
'tours.hike.sac.aboveLimit': '超出上限',
|
||||
'tours.hike.sac.loading': '正在讀取 SAC 難度…',
|
||||
'tours.hike.sac.offline': 'SAC 難度需要網路連線。',
|
||||
'tours.hike.sac.failed': '無法讀取 SAC 難度。路線未改變。',
|
||||
'tours.hike.sac.source': '來自 OpenStreetMap。可能有缺漏;並非安全評估。',
|
||||
'tours.hike.ways.title': '路徑詳情',
|
||||
'tours.hike.ways.facets': '顯示內容',
|
||||
'tours.hike.ways.facet.sac': 'SAC',
|
||||
'tours.hike.ways.facet.surface': '路面',
|
||||
'tours.hike.ways.facet.way': '道路類型',
|
||||
'tours.hike.ways.mostly': '主要',
|
||||
'tours.hike.ways.recordedTip': 'OpenStreetMap 記錄為 {tags}',
|
||||
'tours.hike.ways.unclearTip': 'OpenStreetMap 在此寫的是 {tags},不屬於任何分組。',
|
||||
'tours.hike.ways.loading': '正在讀取健行沿線的道路…',
|
||||
'tours.hike.ways.offline': '路徑詳情需要網路連線。',
|
||||
'tours.hike.ways.failed': '無法讀取路徑詳情。路線未變更。',
|
||||
'tours.hike.surface.paved': '柏油或混凝土',
|
||||
'tours.hike.surface.sett': '鋪磚或卵石路',
|
||||
'tours.hike.surface.compacted': '壓實路面或細礫石',
|
||||
'tours.hike.surface.gravel': '礫石',
|
||||
'tours.hike.surface.ground': '自然地面',
|
||||
'tours.hike.surface.grass': '草地',
|
||||
'tours.hike.surface.rock': '岩石',
|
||||
'tours.hike.surface.wood': '木材或金屬',
|
||||
'tours.hike.surface.unpaved': '未鋪裝,類型未記錄',
|
||||
'tours.hike.surface.other': '其他路面',
|
||||
'tours.hike.surface.untagged': 'OpenStreetMap 中無路面',
|
||||
'tours.hike.surface.unclear': '路面不明',
|
||||
'tours.hike.surface.untaggedTip': 'OpenStreetMap 未記錄這些道路的路面。路線規劃器可能會假定一種,但那不是記錄的值。',
|
||||
'tours.hike.surface.knownTip': 'OpenStreetMap 記錄了路面的路線比例。其餘未知。',
|
||||
'tours.hike.surface.source': '依 OpenStreetMap 的記錄分組顯示路面;不使用路線規劃器自己的解讀。不是對道路的評價。',
|
||||
'tours.hike.way.path': '小徑',
|
||||
'tours.hike.way.footway': '人行道',
|
||||
'tours.hike.way.steps': '階梯',
|
||||
'tours.hike.way.track': '農用或林間道路',
|
||||
'tours.hike.way.cycleway': '自行車道',
|
||||
'tours.hike.way.quietRoad': '支線道路',
|
||||
'tours.hike.way.road': '公路',
|
||||
'tours.hike.way.viaFerrata': '鐵索攀登路線',
|
||||
'tours.hike.way.other': '其他道路',
|
||||
'tours.hike.way.untagged': 'OpenStreetMap 中無道路類型',
|
||||
'tours.hike.way.unclear': '道路類型不明',
|
||||
'tours.hike.way.untaggedTip': 'OpenStreetMap 在此未記錄道路類型。',
|
||||
'tours.hike.way.knownTip': 'OpenStreetMap 記錄了道路類型的路線比例。其餘未知。',
|
||||
'tours.hike.way.source': '依 OpenStreetMap 的記錄(highway)分組顯示道路類型。與安全性或適宜性無關。',
|
||||
'tours.copy.action': '另存為新遊程',
|
||||
'tours.copy.hint': '將此遊程複製為不含日程的獨立新遊程。原遊程保持不變。',
|
||||
'tours.copy.name': '{name}(副本)',
|
||||
|
||||
+46
-10
@@ -467,8 +467,7 @@ const tours: TranslationStrings = {
|
||||
'tours.bike.trail.note': '难度为 OpenStreetMap 绘图者所标注。无难度的道路为未知,而非容易。它不说明路径是否适合你。',
|
||||
'tours.bike.trail.loading': '正在读取路径难度…',
|
||||
'tours.bike.trail.failed': '本次无法读取路径难度。',
|
||||
'tours.hike.sac.title': 'SAC 难度',
|
||||
'tours.hike.sac.show': '沿路线显示',
|
||||
'tours.hike.ways.show': '沿路线显示',
|
||||
'tours.hike.sac.t1': '徒步',
|
||||
'tours.hike.sac.t2': '山地徒步',
|
||||
'tours.hike.sac.t3': '挑战性山地徒步',
|
||||
@@ -477,23 +476,60 @@ const tours: TranslationStrings = {
|
||||
'tours.hike.sac.t6': '高难度高山徒步',
|
||||
'tours.hike.sac.untagged': 'OpenStreetMap 中无难度',
|
||||
'tours.hike.sac.unclear': '难度不明确',
|
||||
'tours.hike.sac.unmatched': '道路未明确匹配',
|
||||
'tours.hike.sac.unasked': '本次无法查询',
|
||||
'tours.hike.sac.unknown': '未知',
|
||||
'tours.hike.ways.unmatched': '道路未明确匹配',
|
||||
'tours.hike.ways.unasked': '本次无法查询',
|
||||
'tours.hike.ways.unknown': '未知',
|
||||
'tours.hike.sac.stepTip': 'SAC 徒步难度 {step},来自 OpenStreetMap:sac_scale={tag}',
|
||||
'tours.hike.sac.untaggedTip': 'OpenStreetMap 未给这些道路标注 sac_scale。未知并不代表容易。',
|
||||
'tours.hike.sac.unclearTip': 'OpenStreetMap 在此处写的是 {tags},并未指明单一等级。',
|
||||
'tours.hike.sac.unmatchedTip': '此处没有道路明确位于路线下方,因此不显示难度。',
|
||||
'tours.hike.sac.known': '已知',
|
||||
'tours.hike.ways.unmatchedTip': '此处没有道路明确位于路线下方,因此不显示任何内容。',
|
||||
'tours.hike.ways.known': '已知',
|
||||
'tours.hike.sac.knownTip': 'OpenStreetMap 给出 SAC 难度的路线比例。其余为未知。',
|
||||
'tours.hike.sac.highest': '已知最高',
|
||||
'tours.hike.sac.routingLimit': '路线规划上限',
|
||||
'tours.hike.sac.routingLimitTip': '路线搜索最多可使用的难度:这是一项设置,不是道路的难度。',
|
||||
'tours.hike.sac.aboveLimit': '超出上限',
|
||||
'tours.hike.sac.loading': '正在读取 SAC 难度…',
|
||||
'tours.hike.sac.offline': 'SAC 难度需要网络连接。',
|
||||
'tours.hike.sac.failed': '无法读取 SAC 难度。路线未改变。',
|
||||
'tours.hike.sac.source': '来自 OpenStreetMap。可能有缺失;并非安全评估。',
|
||||
'tours.hike.ways.title': '路径详情',
|
||||
'tours.hike.ways.facets': '显示内容',
|
||||
'tours.hike.ways.facet.sac': 'SAC',
|
||||
'tours.hike.ways.facet.surface': '路面',
|
||||
'tours.hike.ways.facet.way': '道路类型',
|
||||
'tours.hike.ways.mostly': '主要',
|
||||
'tours.hike.ways.recordedTip': 'OpenStreetMap 记录为 {tags}',
|
||||
'tours.hike.ways.unclearTip': 'OpenStreetMap 在此写的是 {tags},不属于任何分组。',
|
||||
'tours.hike.ways.loading': '正在读取徒步沿线的道路…',
|
||||
'tours.hike.ways.offline': '路径详情需要网络连接。',
|
||||
'tours.hike.ways.failed': '无法读取路径详情。路线未改变。',
|
||||
'tours.hike.surface.paved': '柏油或混凝土',
|
||||
'tours.hike.surface.sett': '铺砖或卵石路',
|
||||
'tours.hike.surface.compacted': '压实路面或细砾石',
|
||||
'tours.hike.surface.gravel': '砾石',
|
||||
'tours.hike.surface.ground': '自然地面',
|
||||
'tours.hike.surface.grass': '草地',
|
||||
'tours.hike.surface.rock': '岩石',
|
||||
'tours.hike.surface.wood': '木材或金属',
|
||||
'tours.hike.surface.unpaved': '未铺装,类型未记录',
|
||||
'tours.hike.surface.other': '其他路面',
|
||||
'tours.hike.surface.untagged': 'OpenStreetMap 中无路面',
|
||||
'tours.hike.surface.unclear': '路面不明',
|
||||
'tours.hike.surface.untaggedTip': 'OpenStreetMap 未记录这些道路的路面。路线规划器可能会假定一种,但那不是记录的值。',
|
||||
'tours.hike.surface.knownTip': 'OpenStreetMap 记录了路面的路线占比。其余未知。',
|
||||
'tours.hike.surface.source': '按 OpenStreetMap 的记录分组显示路面;不使用路线规划器自己的解读。不是对道路的评价。',
|
||||
'tours.hike.way.path': '小径',
|
||||
'tours.hike.way.footway': '人行道',
|
||||
'tours.hike.way.steps': '台阶',
|
||||
'tours.hike.way.track': '农用或林间道路',
|
||||
'tours.hike.way.cycleway': '自行车道',
|
||||
'tours.hike.way.quietRoad': '支路',
|
||||
'tours.hike.way.road': '公路',
|
||||
'tours.hike.way.viaFerrata': '铁索攀登路线',
|
||||
'tours.hike.way.other': '其他道路',
|
||||
'tours.hike.way.untagged': 'OpenStreetMap 中无道路类型',
|
||||
'tours.hike.way.unclear': '道路类型不明',
|
||||
'tours.hike.way.untaggedTip': 'OpenStreetMap 在此未记录道路类型。',
|
||||
'tours.hike.way.knownTip': 'OpenStreetMap 记录了道路类型的路线占比。其余未知。',
|
||||
'tours.hike.way.source': '按 OpenStreetMap 的记录(highway)分组显示道路类型。与安全性或适宜性无关。',
|
||||
'tours.copy.action': '另存为新路线',
|
||||
'tours.copy.hint': '将此路线复制为不含日程的独立新路线。原路线保持不变。',
|
||||
'tours.copy.name': '{name}(副本)',
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
import {
|
||||
SAC_SCALE_UNCLEAR_MAX,
|
||||
OSM_TAG_VALUE_MAX,
|
||||
SAC_SCALE_VALUES,
|
||||
sacStepOf,
|
||||
tourTrailGradeRequestSchema,
|
||||
@@ -16,9 +16,11 @@ const grade = {
|
||||
mtb_scale_imba: null,
|
||||
sac_scale: null,
|
||||
sac_scale_unclear: null,
|
||||
surface: null,
|
||||
highway: null,
|
||||
};
|
||||
|
||||
describe('tour trail grades (BIK-05, MAP-03)', () => {
|
||||
describe('tour trail grades (BIK-05, MAP-03, MAP-04)', () => {
|
||||
it('TRAIL-GRADE-SCHEMA-001: reads the six sac_scale values to T1 to T6 in order', () => {
|
||||
expect(SAC_SCALE_VALUES.map(sacStepOf)).toEqual([1, 2, 3, 4, 5, 6]);
|
||||
expect(sacStepOf('hiking')).toBe(1);
|
||||
@@ -31,7 +33,7 @@ describe('tour trail grades (BIK-05, MAP-03)', () => {
|
||||
expect(tourTrailGradeSchema.safeParse({ ...grade, sac_scale: 'T3' }).success).toBe(false);
|
||||
expect(tourTrailGradeSchema.safeParse({ ...grade, sac_scale_unclear: '' }).success).toBe(false);
|
||||
expect(
|
||||
tourTrailGradeSchema.safeParse({ ...grade, sac_scale_unclear: 'x'.repeat(SAC_SCALE_UNCLEAR_MAX + 1) }).success,
|
||||
tourTrailGradeSchema.safeParse({ ...grade, sac_scale_unclear: 'x'.repeat(OSM_TAG_VALUE_MAX + 1) }).success,
|
||||
).toBe(false);
|
||||
});
|
||||
|
||||
@@ -46,4 +48,14 @@ describe('tour trail grades (BIK-05, MAP-03)', () => {
|
||||
expect(tourTrailGradeRequestSchema.safeParse({ way_ids: [0] }).success).toBe(false);
|
||||
expect(tourTrailGradeRequestSchema.safeParse({ way_ids: [1], extra: true }).success).toBe(false);
|
||||
});
|
||||
|
||||
it('TRAIL-GRADE-SCHEMA-005: carries the surface and the kind of way as written, within the same bound (MAP-04)', () => {
|
||||
const written = tourTrailGradeSchema.parse({ ...grade, surface: 'fine_gravel', highway: 'path' });
|
||||
expect(written).toMatchObject({ surface: 'fine_gravel', highway: 'path' });
|
||||
expect(tourTrailGradeSchema.safeParse({ ...grade, surface: '' }).success).toBe(false);
|
||||
expect(
|
||||
tourTrailGradeSchema.safeParse({ ...grade, highway: 'x'.repeat(OSM_TAG_VALUE_MAX + 1) }).success,
|
||||
).toBe(false);
|
||||
expect(tourTrailGradeSchema.safeParse({ ...grade, surface: undefined }).success).toBe(false);
|
||||
});
|
||||
});
|
||||
|
||||
@@ -1,11 +1,12 @@
|
||||
import { z } from 'zod';
|
||||
|
||||
/**
|
||||
* BIK-05 and MAP-03: the trail grades OpenStreetMap carries for the ways a
|
||||
* tour runs on, asked for by the ways' ids: the mountain bike scales for a
|
||||
* bike tour, the SAC hiking scale for a hike. A read: nothing about the tour
|
||||
* changes. A way without a grade is unknown, never easy; a way that could not
|
||||
* be asked about is named, so the planner can say so.
|
||||
* BIK-05, MAP-03 and MAP-04: what OpenStreetMap records for the ways a tour
|
||||
* runs on, asked for by the ways' ids: the mountain bike scales for a bike
|
||||
* tour, the SAC hiking scale, the surface and the kind of way for a hike. A
|
||||
* read: nothing about the tour changes. A way without a grade is unknown,
|
||||
* never easy; a way that could not be asked about is named, so the planner
|
||||
* can say so.
|
||||
*/
|
||||
|
||||
/** The most ways one question names; a longer tour asks for its first ones. */
|
||||
@@ -39,8 +40,10 @@ export function sacStepOf(value: SacScale): SacStep {
|
||||
return (SAC_SCALE_VALUES.indexOf(value) + 1) as SacStep;
|
||||
}
|
||||
|
||||
/** The most of an unclear `sac_scale` tag that is handed on, so a stray long value cannot bloat the answer. */
|
||||
export const SAC_SCALE_UNCLEAR_MAX = 64;
|
||||
/** The most of a tag's value that is handed on as written, so a stray long value cannot bloat the answer. */
|
||||
export const OSM_TAG_VALUE_MAX = 64;
|
||||
|
||||
const writtenValue = z.string().min(1).max(OSM_TAG_VALUE_MAX).nullable();
|
||||
|
||||
/**
|
||||
* One way's grades as OpenStreetMap tags them, read to the scale's own steps:
|
||||
@@ -50,6 +53,11 @@ export const SAC_SCALE_UNCLEAR_MAX = 64;
|
||||
* `sac_scale` that is there but names no single step (`T3`, two values joined
|
||||
* by `;`) is handed on as written in `sac_scale_unclear`, so the planner can
|
||||
* say the grade is unclear rather than missing; it is never read to a step.
|
||||
*
|
||||
* MAP-04: `surface` and `highway` are handed on as OpenStreetMap wrote them
|
||||
* (trimmed, never normalised), so the planner can tell a recorded surface
|
||||
* from the router's own reading, which may be a default. Null where the way
|
||||
* carries no such tag.
|
||||
*/
|
||||
export const tourTrailGradeSchema = z.object({
|
||||
id: z.number().int().positive(),
|
||||
@@ -66,12 +74,14 @@ export const tourTrailGradeSchema = z.object({
|
||||
.regex(/^[0-4]$/)
|
||||
.nullable(),
|
||||
sac_scale: sacScaleSchema.nullable(),
|
||||
sac_scale_unclear: z.string().min(1).max(SAC_SCALE_UNCLEAR_MAX).nullable(),
|
||||
sac_scale_unclear: writtenValue,
|
||||
surface: writtenValue,
|
||||
highway: writtenValue,
|
||||
});
|
||||
export type TourTrailGrade = z.infer<typeof tourTrailGradeSchema>;
|
||||
|
||||
export const tourTrailGradeResponseSchema = z.object({
|
||||
/** The ways asked about that carry at least one grade. */
|
||||
/** The ways asked about that carry at least one of the values read. */
|
||||
ways: z.array(tourTrailGradeSchema),
|
||||
/** The ways the data could not be asked about this time: their grade is unknown, not absent. */
|
||||
unanswered: z.array(z.number().int().positive()),
|
||||
|
||||
Reference in New Issue
Block a user