feat(tours): plan a city walk with its stops, stays, importance and protection (CW-01, CW-02, CW-10)

City walk is a type of the planner: routed on foot without a hiking limit, on
the standard map. Each stop is edited in its own dialog, its name, its stay
(empty is not known, 0 a point passed through), its importance and whether a
proposed change must leave it; none of it plans the route again. The walk's
time is walking plus the stays, at least that much while a stay is unknown,
with no breaks on top.
This commit is contained in:
Maurice
2026-10-19 22:28:12 +02:00
parent a2c2430af6
commit 4bbc92de26
41 changed files with 1298 additions and 45 deletions
+7 -1
View File
@@ -1,4 +1,4 @@
import { Utensils, Coffee, Wine, BedDouble, Camera, Landmark, Trees, Ticket, Fuel, Zap, ParkingSquare, Tent, Binoculars, Mountain, Warehouse, Droplet, Toilet, Sandwich, Anchor, Sailboat, Wrench, type LucideIcon } from 'lucide-react'
import { Utensils, Coffee, Wine, BedDouble, Camera, Landmark, Trees, Ticket, Fuel, Zap, ParkingSquare, Tent, Binoculars, Mountain, Warehouse, Droplet, Toilet, Sandwich, Anchor, Sailboat, Wrench, Store, type LucideIcon } from 'lucide-react'
import type { TourPoiCategory } from '@trek/shared'
import { parsePluginPoiCategoryKey, type PluginPoiDetail } from '@trek/shared'
// The road-trip kinds take their colour from the one place that defines it, so the
@@ -62,6 +62,12 @@ export const TOUR_POI_LOOKS: Record<TourPoiCategory, PoiCategory> = {
slipway: { key: 'slipway', labelKey: 'tours.poi.cat.slipway', Icon: Anchor, color: coreColor('museum') },
boat_rental: { key: 'boat_rental', labelKey: 'tours.poi.cat.boat_rental', Icon: Sailboat, color: coreColor('cafe') },
bike_repair: { key: 'bike_repair', labelKey: 'tours.poi.cat.bike_repair', Icon: Wrench, color: coreColor('activity') },
// CW-05: a city walk's own, each in the colour of the explore category it comes from.
museum: { key: 'museum', labelKey: 'tours.poi.cat.museum', Icon: Landmark, color: coreColor('museum') },
park: { key: 'park', labelKey: 'tours.poi.cat.park', Icon: Trees, color: coreColor('nature') },
cafe: { key: 'cafe', labelKey: 'tours.poi.cat.cafe', Icon: Coffee, color: coreColor('cafe') },
restaurant: { key: 'restaurant', labelKey: 'tours.poi.cat.restaurant', Icon: Utensils, color: coreColor('restaurant') },
market: { key: 'market', labelKey: 'tours.poi.cat.market', Icon: Store, color: coreColor('shopping') },
}
// A Map rather than an object, so a category named after an Object.prototype member
@@ -0,0 +1,152 @@
import type { StationImportance } from '@trek/shared';
import { MapPin, ShieldCheck } from 'lucide-react';
import { useId, useState } from 'react';
import { useTranslation } from '../../../i18n';
import ToggleSwitch from '../../Settings/ToggleSwitch';
import { EditorField, INPUT, LABEL, Segmented } from '../../shared/dialogParts';
import {
DialogButton,
DialogFooter,
DialogHeader,
DialogShell,
DialogTile,
FooterSpacer,
NEUTRAL_TINT,
fs,
} from '../../shared/DialogShell';
import { parseStay, withStationFields } from './stationModel';
import type { TourPlannerWaypoint } from './tourPlannerWaypoints';
type Importance = StationImportance | 'normal';
const IMPORTANCES: readonly Importance[] = ['normal', 'optional', 'must'];
/**
* CW-02, CW-03, CW-10: one station of a city walk, as a dialog: its name, how
* long it is stayed at (empty is unknown, 0 a point passed through), how much
* it matters and whether a proposed change must leave it. Saving is one step
* of the history; nothing here plans the route again.
*/
export function StationDialog({
point,
fallbackName,
onSave,
onClose,
}: {
point: TourPlannerWaypoint;
/** The name the row shows while the station has none of its own. */
fallbackName?: string;
onSave: (next: TourPlannerWaypoint) => void;
onClose: () => void;
}) {
const { t } = useTranslation();
const labelId = useId();
const nameId = useId();
const stayId = useId();
const [name, setName] = useState(point.name ?? '');
const [stay, setStay] = useState(point.stayMinutes == null ? '' : String(point.stayMinutes));
const [importance, setImportance] = useState<Importance>(point.importance ?? 'normal');
const [protectedStation, setProtectedStation] = useState(point.protected === true);
const stayMinutes = parseStay(stay);
const stayInvalid = stayMinutes === undefined;
const save = () => {
if (stayInvalid) return;
onSave(
withStationFields(point, {
name,
stayMinutes,
importance: importance === 'normal' ? null : importance,
protected: protectedStation,
})
);
};
return (
<DialogShell
onClose={onClose}
labelledBy={labelId}
width="narrow"
onSubmit={save}
header={
<DialogHeader
tile={
<DialogTile>
<MapPin size={16} strokeWidth={2} />
</DialogTile>
}
tint={NEUTRAL_TINT}
labelId={labelId}
onClose={onClose}
eyebrow={t('tours.walk.station.eyebrow')}
title={name.trim() || fallbackName || t('tours.walk.station.unnamed')}
/>
}
footer={
<DialogFooter>
<FooterSpacer />
<DialogButton onClick={onClose}>{t('common.cancel')}</DialogButton>
<DialogButton variant="primary" type="submit" disabled={stayInvalid} data-testid="station-save">
{t('common.save')}
</DialogButton>
</DialogFooter>
}
>
<div className="flex flex-col gap-4" data-testid="station-dialog">
<EditorField label={t('tours.walk.station.name')} htmlFor={nameId}>
<input
id={nameId}
className={INPUT}
value={name}
maxLength={200}
placeholder={fallbackName ?? t('tours.walk.station.namePlaceholder')}
onChange={(event) => setName(event.target.value)}
/>
</EditorField>
<EditorField
label={t('tours.walk.station.stay')}
htmlFor={stayId}
hint={t('tours.walk.station.stayHint')}
error={stayInvalid ? t('tours.walk.station.stayInvalid') : undefined}
>
<input
id={stayId}
className={INPUT}
inputMode="numeric"
value={stay}
placeholder={t('tours.walk.station.stayUnknown')}
onChange={(event) => setStay(event.target.value)}
/>
</EditorField>
<div className="min-w-0">
<span className={LABEL}>{t('tours.walk.station.importance')}</span>
<Segmented<Importance>
value={importance}
onChange={setImportance}
label={t('tours.walk.station.importance')}
fill
options={IMPORTANCES.map((value) => ({ value, label: t(`tours.walk.importance.${value}`) }))}
/>
<span className="mt-1.5 block text-content-faint" style={fs(11, 'body')}>
{t(`tours.walk.importanceHint.${importance}`)}
</span>
</div>
<div className="flex items-start gap-2.5">
<ShieldCheck size={15} strokeWidth={2} className="mt-0.5 flex-none text-content-faint" />
<span className="flex min-w-0 flex-1 flex-col">
<span className="font-semibold text-content" style={fs(12.5, 'body')}>
{t('tours.walk.station.protected')}
</span>
<span className="leading-snug text-content-faint" style={fs(11, 'body')}>
{t('tours.walk.station.protectedHint')}
</span>
</span>
<ToggleSwitch
on={protectedStation}
onToggle={() => setProtectedStation((value) => !value)}
label={t('tours.walk.station.protected')}
/>
</div>
</div>
</DialogShell>
);
}
@@ -157,31 +157,34 @@ function TourTimeFields({ planner, locked }: TourFieldProps) {
const manual = planner.plannedDurationMinutes != null;
return (
<div data-testid="tour-planner-time-grid" className="grid grid-cols-2 gap-3">
<div className="min-w-0">
<FieldLabel
htmlFor="tour-planner-breaks"
aside={
<InfoDot
label={t('tours.planner.breaksAdditionalInfoLabel')}
description={t('tours.planner.breaksAdditionalInfo')}
/>
}
>
{t('tours.planner.breaksShort')}
</FieldLabel>
<TourDurationInput
id="tour-planner-breaks"
label={t('tours.planner.breaksAdditional')}
value={planner.breakAdditionalMinutes}
invalid={planner.breakAdditionalInvalid}
errorId="tour-planner-breaks-error"
locked={locked}
onCommit={planner.setBreakAdditionalMinutes}
/>
{planner.breakAdditionalInvalid && (
<FieldError id="tour-planner-breaks-error">{t('tours.planner.timeValueInvalid')}</FieldError>
)}
</div>
{/* CW-02: a city walk stops at its stations; their stays take the place of breaks. */}
{planner.tourType !== 'walk' && (
<div className="min-w-0">
<FieldLabel
htmlFor="tour-planner-breaks"
aside={
<InfoDot
label={t('tours.planner.breaksAdditionalInfoLabel')}
description={t('tours.planner.breaksAdditionalInfo')}
/>
}
>
{t('tours.planner.breaksShort')}
</FieldLabel>
<TourDurationInput
id="tour-planner-breaks"
label={t('tours.planner.breaksAdditional')}
value={planner.breakAdditionalMinutes}
invalid={planner.breakAdditionalInvalid}
errorId="tour-planner-breaks-error"
locked={locked}
onCommit={planner.setBreakAdditionalMinutes}
/>
{planner.breakAdditionalInvalid && (
<FieldError id="tour-planner-breaks-error">{t('tours.planner.timeValueInvalid')}</FieldError>
)}
</div>
)}
<div className="min-w-0">
<FieldLabel
htmlFor="tour-planner-planned-duration"
@@ -612,6 +612,7 @@ const ESTIMATE_KEYS = {
hike: { labelKey: 'tours.planner.walkingEstimated', shortKey: 'tours.planner.walkingEstimatedShort', infoLabelKey: 'tours.planner.walkingEstimatedInfoLabel', infoKey: 'tours.planner.walkingEstimatedInfo' },
bike: { labelKey: 'tours.bike.timeEstimated', shortKey: 'tours.bike.timeEstimatedShort', infoLabelKey: 'tours.bike.timeEstimatedInfoLabel', infoKey: 'tours.bike.timeEstimatedInfo' },
kayak: { labelKey: 'tours.paddle.timeEstimated', shortKey: 'tours.paddle.timeEstimatedShort', infoLabelKey: 'tours.paddle.timeEstimatedInfoLabel', infoKey: 'tours.paddle.timeEstimatedInfo' },
walk: { labelKey: 'tours.walk.timeEstimated', shortKey: 'tours.walk.timeEstimatedShort', infoLabelKey: 'tours.walk.timeEstimatedInfoLabel', infoKey: 'tours.walk.timeEstimatedInfo' },
} as const
/**
@@ -645,6 +646,7 @@ function UnreachableNotice({ planner }: { planner: TourPlannerController }) {
/** The time facts of the overview: the estimate (walking, riding or paddling), the breaks and the planned total. */
function plannerTimeFields(planner: TourPlannerController, t: Translate) {
const keys = ESTIMATE_KEYS[planner.tourType]
const stays = planner.stationStays
return [
{
label: t(keys.labelKey),
@@ -654,13 +656,25 @@ function plannerTimeFields(planner: TourPlannerController, t: Translate) {
? t('tours.planner.walkingTimeUnknown')
: formatPlannedTourDuration(planner.walkingDurationMinutes),
},
{
label: t('tours.planner.breaksShort'),
value: planner.breakAdditionalMinutes == null ? '—' : formatPlannedTourDuration(planner.breakAdditionalMinutes),
},
stays
? {
// CW-02: a city walk's stays in place of breaks, the unknown ones named, never counted as nothing.
label: t('tours.walk.stays'),
value: stays.unknownStays > 0
? t('tours.walk.totalAtLeast', { time: formatPlannedTourDuration(stays.stayMinutes) })
: formatPlannedTourDuration(stays.stayMinutes),
}
: {
label: t('tours.planner.breaksShort'),
value: planner.breakAdditionalMinutes == null ? '—' : formatPlannedTourDuration(planner.breakAdditionalMinutes),
},
{
label: `${t('tours.planner.plannedShort')}${planner.plannedDurationMinutes != null ? ` (${t('tours.planner.plannedTotalManual')})` : ''}`,
value: planner.plannedTotalMinutes == null ? t('tours.planner.walkingTimeUnknown') : formatPlannedTourDuration(planner.plannedTotalMinutes),
value: planner.plannedTotalMinutes == null
? t('tours.planner.walkingTimeUnknown')
: stays?.partial && planner.plannedDurationMinutes == null
? t('tours.walk.totalAtLeast', { time: formatPlannedTourDuration(planner.plannedTotalMinutes) })
: formatPlannedTourDuration(planner.plannedTotalMinutes),
},
]
}
@@ -17,6 +17,7 @@ import {
Navigation,
RefreshCw,
Repeat,
SquarePen,
Spline,
Trash2,
Unlink,
@@ -46,6 +47,8 @@ import { usePaddleRegionLoad } from '../paddle/usePaddleRegionLoad';
import { usePaddleWaypointActions } from '../paddle/usePaddleWaypointActions';
import { usePortageRouting, usePortageWay } from '../paddle/usePortageRouting';
import { RailCard } from '../tourParts';
import { StationDialog } from './StationDialog';
import { StationPills } from './stationParts';
import { paddlePlanPoints, paddleRolesById } from './paddlePlanOf';
import type { PaddleLegFailure } from './paddleRouting';
import { lineLengthM } from './tourFixedLegs';
@@ -297,6 +300,7 @@ function WaypointRowLabel({
loopEnd,
paddleRole,
linked = false,
station = false,
}: {
point: TourPlannerController['waypoints'][number];
placeName: string | undefined;
@@ -305,10 +309,12 @@ function WaypointRowLabel({
paddleRole?: PaddleRole | null;
/** PR1-10: the point stands for this place of the trip, named after it. */
linked?: boolean;
/** CW-01: a city walk's station, which shows its stay, importance and protection. */
station?: boolean;
}) {
const { t } = useTranslation();
const coordinates = `${point.lat.toFixed(5)}, ${point.lng.toFixed(5)}`;
const name = loopEnd ? t('tours.planner.loopEnd') : placeName;
const name = loopEnd ? t('tours.planner.loopEnd') : (point.name ?? placeName);
return (
<>
<span className="flex w-full min-w-0 items-center gap-1.5">
@@ -332,6 +338,7 @@ function WaypointRowLabel({
)}
{point.auto && <AutoPointPill />}
</span>
{station && !loopEnd && <StationPills point={point} />}
{linked && !loopEnd ? (
// A stop standing for a place of the trip says so where the coordinates would be (PR1-10).
<Tooltip label={t('tours.placePicker.linkedHint')}>
@@ -451,6 +458,7 @@ function WaypointRowActions({
selected,
isSaving,
onUnlink,
stationName,
}: {
planner: TourPlannerController;
point: WaypointPoint;
@@ -460,11 +468,21 @@ function WaypointRowActions({
isSaving: boolean;
/** PR1-10: given for a point that stands for a place; the place itself stays. */
onUnlink?: () => void;
/** CW-01: the name the row shows, for a station without one of its own. */
stationName?: string;
}) {
const { t } = useTranslation();
const menu = useContextMenu();
const paddle = usePaddleWaypointActions(planner);
const [stationOpen, setStationOpen] = useState(false);
const station = planner.tourType === 'walk' && !point.shaping;
const items: MenuItems = [
...(station
? [
{ label: t('tours.walk.station.edit'), icon: SquarePen, onClick: () => setStationOpen(true) },
{ divider: true } as const,
]
: []),
{
label: t('tours.planner.moveUp'),
icon: ArrowUp,
@@ -505,6 +523,17 @@ function WaypointRowActions({
<Trash2 size={13} strokeWidth={2} />
</WaypointAction>
<ContextMenu menu={menu.menu} onClose={menu.close} />
{stationOpen && (
<StationDialog
point={point}
fallbackName={stationName}
onClose={() => setStationOpen(false)}
onSave={(next) => {
planner.editWaypoints((current) => current.map((item) => (item.id === next.id ? next : item)));
setStationOpen(false);
}}
/>
)}
</span>
);
}
@@ -927,6 +956,7 @@ export default function TourWaypointList({
loopEnd={loopEnd}
paddleRole={paddleRoleOf(point.id)}
linked={!!linkedPlace}
station={planner.tourType === 'walk' && !point.shaping}
/>
</button>
{canEdit && (
@@ -938,6 +968,7 @@ export default function TourWaypointList({
selected={selected}
isSaving={isSaving}
onUnlink={point.linkedPlaceId ? () => tripPlaces.unlink(point.id) : undefined}
stationName={linkedPlace?.name ?? placeNames[point.id]}
/>
)}
</div>
@@ -0,0 +1,62 @@
import type { TourPlannerWaypoint } from './tourPlannerWaypoints';
/**
* CW-02: a city walk's time, walking and staying kept apart. A stay nobody
* entered is unknown, never nothing: the total then counts only what is
* known and says it is at least that much. A point passed through is a stay
* of 0, entered on purpose.
*/
export interface WalkTimes {
walkingMinutes: number | null;
/** The stays entered, summed. */
stayMinutes: number;
/** Stations whose stay nobody entered. */
unknownStays: number;
/** Walking and the known stays; null while the walking time is unknown. */
totalMinutes: number | null;
/** The total leaves out a stay or the walking time that is not known. */
partial: boolean;
}
/** The stations of a city walk: its points that are no shaping points. */
export function stationsOf(points: readonly TourPlannerWaypoint[]): TourPlannerWaypoint[] {
return points.filter((point) => !point.shaping);
}
export function walkTimes(points: readonly TourPlannerWaypoint[], walkingMinutes: number | null): WalkTimes {
const stations = stationsOf(points);
const stayMinutes = stations.reduce((sum, station) => sum + (station.stayMinutes ?? 0), 0);
const unknownStays = stations.filter((station) => station.stayMinutes == null).length;
return {
walkingMinutes,
stayMinutes,
unknownStays,
totalMinutes: walkingMinutes === null ? null : walkingMinutes + stayMinutes,
partial: unknownStays > 0,
};
}
/** What the station dialog edits; the rest of the point stays as it is. */
export type StationFields = Pick<TourPlannerWaypoint, 'name' | 'stayMinutes' | 'importance' | 'protected'>;
/** The point with the fields as edited, each one left out where it is empty. */
export function withStationFields(point: TourPlannerWaypoint, fields: StationFields): TourPlannerWaypoint {
const { name: _name, stayMinutes: _stay, importance: _importance, protected: _protected, ...rest } = point;
const name = fields.name?.trim();
return {
...rest,
...(name ? { name } : {}),
...(fields.stayMinutes != null ? { stayMinutes: fields.stayMinutes } : {}),
...(fields.importance ? { importance: fields.importance } : {}),
...(fields.protected ? { protected: true } : {}),
};
}
/** A stay as typed: empty is unknown, a whole number from 0 to a day is minutes, anything else is not taken. */
export function parseStay(text: string): number | null | undefined {
const trimmed = text.trim();
if (trimmed === '') return null;
if (!/^\d{1,4}$/.test(trimmed)) return undefined;
const minutes = Number(trimmed);
return minutes <= 24 * 60 ? minutes : undefined;
}
@@ -0,0 +1,38 @@
import { Clock, ShieldCheck } from 'lucide-react';
import { useTranslation } from '../../../i18n';
import { SoftPill } from '../../Planner/planParts';
import { Tooltip } from '../../shared/Tooltip';
import { formatPlannedTourDuration } from '../tourPresentation';
import type { TourPlannerWaypoint } from './tourPlannerWaypoints';
/**
* CW-02, CW-03, CW-10: what a city walk's station row says besides its name:
* the stay (or that it is not known), an importance other than normal, and a
* protection, each a quiet pill.
*/
export function StationPills({ point }: { point: TourPlannerWaypoint }) {
const { t } = useTranslation();
return (
<span className="flex flex-wrap items-center gap-1" data-testid="station-pills">
<SoftPill icon={<Clock size={10} strokeWidth={2.2} aria-hidden="true" />}>
{point.stayMinutes == null
? t('tours.walk.station.stayUnknownShort')
: point.stayMinutes === 0
? t('tours.walk.station.passThrough')
: formatPlannedTourDuration(point.stayMinutes)}
</SoftPill>
{point.importance && (
<SoftPill tone={point.importance === 'must' ? 'warning' : undefined}>
{t(`tours.walk.importance.${point.importance}`)}
</SoftPill>
)}
{point.protected && (
<Tooltip label={t('tours.walk.station.protectedHint')}>
<span className="inline-flex" aria-label={t('tours.walk.station.protected')}>
<SoftPill icon={<ShieldCheck size={10} strokeWidth={2.2} aria-hidden="true" />} />
</span>
</Tooltip>
)}
</span>
);
}
@@ -52,6 +52,8 @@ export interface TourPlannerFactsInput {
/** A paddle tour's estimate on the water; null for a hike. */
paddleEstimate: { waterMinutes: number | null; estimateMinutes: number | null } | null;
routeAnalysis: RouteGeometryAnalysis | null;
/** CW-02: a city walk's stays, summed as far as they are known; null for any other type, which takes its breaks. */
stationStayMinutes?: number | null;
/** The router's own time, seconds: a bike tour's riding time; stands in for a hike until the heights are in. */
durationSeconds: number | null;
}
@@ -69,7 +71,8 @@ function invalidMinutes(value: number | null): boolean {
function movingMinutes(input: TourPlannerFactsInput, routerMinutes: number | null): number | null {
const { paddleEstimate, routeAnalysis } = input;
if (paddleEstimate) return paddleEstimate.waterMinutes === null ? null : paddleEstimate.estimateMinutes;
if (input.tourType === 'bike') return routerMinutes;
// A bike tour is timed by the router for its profile, a city walk by the router on foot.
if (input.tourType === 'bike' || input.tourType === 'walk') return routerMinutes;
return routeAnalysis?.gain != null && routeAnalysis.loss != null
? hikingTimeMinutes(routeAnalysis.distanceKm, routeAnalysis.gain, routeAnalysis.loss)
: routerMinutes;
@@ -92,16 +95,17 @@ function typeFieldsOf(input: TourPlannerFactsInput) {
hiking_preferences: null,
};
}
const isHike = input.tourType === 'hike';
return {
tour_type: input.tourType,
max_hiking_difficulty: isBike ? null : input.maxHikingDifficulty,
max_hiking_difficulty: isHike ? input.maxHikingDifficulty : null,
bike_profile: isBike ? input.bikeProfile : null,
boat_type: null,
water_context: null,
paddle_speed_kmh: null,
portage_speed_kmh: null,
paddle_lake_style: null,
hiking_preferences: isBike ? null : input.hikingPreferences,
hiking_preferences: isHike ? input.hikingPreferences : null,
};
}
@@ -114,8 +118,9 @@ export function tourPlannerFacts(input: TourPlannerFactsInput) {
const { durationSeconds, breakAdditionalMinutes, plannedDurationMinutes } = input;
const routerMinutes = durationSeconds === null ? null : Math.round(durationSeconds / 60);
const walkingDurationMinutes = movingMinutes(input, routerMinutes);
const automaticPlannedTotalMinutes =
walkingDurationMinutes === null ? null : walkingDurationMinutes + (breakAdditionalMinutes ?? 0);
// A city walk's stops are its stays; it has no breaks on top, so nothing counts twice.
const onTop = input.tourType === 'walk' ? (input.stationStayMinutes ?? 0) : (breakAdditionalMinutes ?? 0);
const automaticPlannedTotalMinutes = walkingDurationMinutes === null ? null : walkingDurationMinutes + onTop;
const { boatType, waterContext } = input.paddle;
return {
isPaddle,
@@ -1,4 +1,4 @@
import type { PaddleLock, TourFixedLeg, TourWaypoint, TourWaypointRole } from '@trek/shared';
import type { PaddleLock, PlannableTourType, StationImportance, TourFixedLeg, TourWaypoint, TourWaypointRole } from '@trek/shared';
import type { AutoBase } from './paddleRouting';
import { keepJoinedFixedLegs } from './tourFixedLegs';
import { alignFreeLegs } from './tourRouteActions';
@@ -21,6 +21,18 @@ export interface TourPlannerWaypoint {
carryNext?: boolean;
/** PR1-10: the place of the trip this point stands for, chosen on purpose; the point keeps its own spot. */
linkedPlaceId?: number;
/** CW-03: the point's saved key, the same across saves, which a city walk's visits hang on. */
key?: string;
/** The point's own name: a city walk's station, a place found along the route. */
name?: string | null;
/** CW-02: a station's stay; null not known, 0 a point passed through. */
stayMinutes?: number | null;
/** CW-03: how much a station matters; absent is normal. */
importance?: StationImportance | null;
/** CW-10: a station a proposed change must leave where it is. */
protected?: boolean;
/** CW-04: the station's opening hours as OpenStreetMap writes them. */
openingHours?: string | null;
}
/** A saved tour's waypoints as the editor holds them, each under a fresh id. */
@@ -34,6 +46,12 @@ export function plannerWaypointsOf(saved: readonly TourWaypoint[], newId: () =>
...(point.lock ? { lock: point.lock } : {}),
...(point.shaping ? { shaping: true } : {}),
...(point.linked_place_id ? { linkedPlaceId: point.linked_place_id } : {}),
...(point.key ? { key: point.key } : {}),
...(point.name ? { name: point.name } : {}),
...(point.stay_minutes != null ? { stayMinutes: point.stay_minutes } : {}),
...(point.importance ? { importance: point.importance } : {}),
...(point.protected ? { protected: true } : {}),
...(point.opening_hours ? { openingHours: point.opening_hours } : {}),
}));
}
@@ -42,7 +60,9 @@ export function plannerWaypointsOf(saved: readonly TourWaypoint[], newId: () =>
* keeps and nothing of the editor's own (ids, automatic marks, a portage
* waiting for its next point).
*/
export function savedWaypointsOf(points: readonly TourPlannerWaypoint[]): TourWaypoint[] {
export function savedWaypointsOf(points: readonly TourPlannerWaypoint[], tourType?: PlannableTourType): TourWaypoint[] {
// A stay, an importance and a protection belong to a city walk's stations; another type sends none.
const walk = tourType === 'walk';
return points.map((point, sequence) => ({
lat: point.lat,
lng: point.lng,
@@ -52,6 +72,12 @@ export function savedWaypointsOf(points: readonly TourPlannerWaypoint[]): TourWa
...(point.lock ? { lock: point.lock } : {}),
...(point.shaping ? { shaping: true } : {}),
...(point.linkedPlaceId ? { linked_place_id: point.linkedPlaceId } : {}),
...(point.key ? { key: point.key } : {}),
...(point.name ? { name: point.name } : {}),
...(walk && point.stayMinutes != null ? { stay_minutes: point.stayMinutes } : {}),
...(walk && point.importance ? { importance: point.importance } : {}),
...(walk && point.protected ? { protected: true } : {}),
...(point.openingHours ? { opening_hours: point.openingHours } : {}),
}));
}
@@ -103,6 +103,18 @@ export async function routeWalkingTour(
}, signal)
}
/**
* CW-01: a city walk, on foot between its stations. No hiking limit is sent,
* so the router keeps to the ways it takes for walking anyway (T1), and no
* route choice of a hike.
*/
export async function routeCityWalk(
waypoints: LandWaypoint[],
signal?: AbortSignal,
): Promise<TourRouteResult | TourRouteUnreachable | null> {
return routeOnLand(waypoints, 'walking', {}, signal)
}
/** BIK-01: a bike tour, routed for its profile; a leg drawn by hand is kept as drawn. */
export async function routeBikeTour(
waypoints: LandWaypoint[],
@@ -5,6 +5,7 @@ import { tourRepo } from '../../../repo/tourRepo'
import { randomId } from '../../../utils/randomId'
import { analyzeRouteGeometry, type RouteProfileFocus } from '../../../utils/routeGeometry'
import { closeLoop, isClosedLoop, reverseRoute, toggleFreeLeg, withPointAppended } from './tourRouteActions'
import { walkTimes } from './stationModel'
import { hasCompleteElevation, useTourRoute } from './useTourRoute'
import { normalizePlannerWaypoints, plannerWaypointsOf, ridablePoints, sameWaypointSnapshot, savedWaypointsOf, unlinkedPoint, type TourPlannerWaypoint } from './tourPlannerWaypoints'
import { openedTourFields, tourPlannerFacts } from './tourPlannerFacts'
@@ -478,12 +479,14 @@ export function useTourPlanner({ tripId, active = true, onSaved, canEdit: editPe
const routedLegLengths = paddleRoute && routeCurrent ? paddleRoute.legLengths : undefined
const paddleEstimate = useMemo(() => isPaddle ? paddleEstimateOf(waypoints, { effectiveSpeedKmh, portageSpeedKmh }, paddleSpeedInvalid, routedLegLengths) : null,
[effectiveSpeedKmh, isPaddle, paddleSpeedInvalid, portageSpeedKmh, routedLegLengths, waypoints])
// CW-02: a city walk's stays, what of them is known.
const stationStays = useMemo(() => (tourType === 'walk' ? walkTimes(waypoints, null) : null), [tourType, waypoints])
const facts = useMemo(() => tourPlannerFacts({
tourType, maxHikingDifficulty, hikingPreferences, bikeProfile, website, plannedDurationMinutes, breakAdditionalMinutes,
paddle: { boatType, waterContext, speedKmh, portageSpeedKmh, lakeStyle },
paddleSpeedInvalid, paddleEstimate, routeAnalysis, durationSeconds,
paddleSpeedInvalid, paddleEstimate, routeAnalysis, durationSeconds, stationStayMinutes: stationStays?.stayMinutes ?? null,
}), [bikeProfile, boatType, breakAdditionalMinutes, durationSeconds, hikingPreferences, lakeStyle, maxHikingDifficulty, paddleEstimate, paddleSpeedInvalid,
plannedDurationMinutes, portageSpeedKmh, routeAnalysis, speedKmh, tourType, waterContext, website])
plannedDurationMinutes, portageSpeedKmh, routeAnalysis, speedKmh, stationStays, tourType, waterContext, website])
const { websiteInvalid, plannedDurationInvalid, breakAdditionalInvalid, paddleIncomplete, walkingDurationMinutes,
plannedTotalMinutes, plannedTotalInvalid, savedDurationSeconds, typeFields } = facts
@@ -774,7 +777,7 @@ export function useTourPlanner({ tripId, active = true, onSaved, canEdit: editPe
website: parsedWebsiteOrNull(website),
...typeFields,
route_geometry: enrichedGeometry.map(point => [point[0], point[1], point[2]]),
waypoints: savedWaypointsOf(waypoints),
waypoints: savedWaypointsOf(waypoints, tourType),
duration_seconds: savedDurationSeconds,
planned_duration_minutes: plannedDurationMinutes,
break_additional_minutes: breakAdditionalMinutes,
@@ -834,7 +837,7 @@ export function useTourPlanner({ tripId, active = true, onSaved, canEdit: editPe
description, setDescription, website, setWebsite, websiteInvalid,
plannedDurationMinutes, setPlannedDurationMinutes, plannedDurationInvalid,
breakAdditionalMinutes, setBreakAdditionalMinutes, breakAdditionalInvalid,
walkingDurationMinutes, plannedTotalMinutes, plannedTotalInvalid,
walkingDurationMinutes, plannedTotalMinutes, plannedTotalInvalid, stationStays,
waypoints, selectedWaypointId, setSelectedWaypointId,
addWaypoint, appendFixedLeg, setWaypointPosition, removeWaypoint, moveWaypoint, reorderWaypoint, editWaypoints: commitWaypoints,
reverse, returnToStart, isLoop: isClosedLoop(waypoints), toggleWaypointFreeLeg, editPaddlePoints, amendWaypoints, paddleEstimate,
@@ -14,6 +14,7 @@ import { isTourRoute, type TourRouteUnreachable } from './tourRouteOutcome';
import {
enrichTourElevations,
routeBikeTour,
routeCityWalk,
routeWalkingTour,
type TourElevationPoint,
type TourRoutePart,
@@ -104,7 +105,9 @@ async function routeOnLand({ inputs, plannedKey, signal, stale, setPlanned, setU
const routed =
tourType === 'bike'
? await routeBikeTour(waypoints, bikeProfile, signal)
: await routeWalkingTour(waypoints, signal, maxHikingDifficulty, hikingPreferences);
: tourType === 'walk'
? await routeCityWalk(waypoints, signal)
: await routeWalkingTour(waypoints, signal, maxHikingDifficulty, hikingPreferences);
if (stale()) return;
if (!isTourRoute(routed)) {
setUnreachable(routed);
@@ -0,0 +1,21 @@
import { TourStationVisitsStatus } from '../../../src/db/entities/TourStationVisits.entity';
import { TourWaypointsImportance } from '../../../src/db/entities/TourWaypoints.entity';
import { STATION_IMPORTANCES, TOUR_VISIT_STATUSES } from '@trek/shared';
import { describe, expect, it } from 'vitest';
/**
* CW-03: a station's importance and a visit's status are CHECK lists
* (migration 045300) the entities carry as enums, and the contract holds its
* own. A value one side knows and the other refuses would be a station the
* planner can save and the database cannot store.
*/
describe('city walk columns match the contract', () => {
it('knows the same importances as the station contract', () => {
expect(Object.values(TourWaypointsImportance).sort()).toEqual([...STATION_IMPORTANCES].sort());
});
it('knows the same visit statuses as the visit contract', () => {
expect(Object.values(TourStationVisitsStatus).sort()).toEqual([...TOUR_VISIT_STATUSES].sort());
});
});
+32
View File
@@ -467,6 +467,38 @@ const tours: TranslationStrings = {
'tours.preferences.avoid_stepsHint': 'الدرج فقط حيث لا يوجد طريق بديل.',
'tours.preferences.avoid_ferries': 'تجنّب العبّارات',
'tours.preferences.avoid_ferriesHint': 'عبر البر حيث يوجد طريق بديل، حتى لو كان أطول.',
'tours.planner.tourTypeHint.walk': 'سيرًا من محطة إلى محطة، دون حد للمشي الجبلي',
'tours.poi.cat.museum': 'متاحف ومعارض',
'tours.poi.cat.park': 'حدائق ومنتزهات',
'tours.poi.cat.cafe': 'مقاهٍ',
'tours.poi.cat.restaurant': 'مطاعم',
'tours.poi.cat.market': 'أسواق',
'tours.walk.station.eyebrow': 'محطة',
'tours.walk.station.unnamed': 'محطة بلا اسم',
'tours.walk.station.name': 'الاسم',
'tours.walk.station.namePlaceholder': 'ما الموجود هنا',
'tours.walk.station.stay': 'مدة البقاء بالدقائق',
'tours.walk.station.stayHint': 'الفارغ يعني غير معروف بعد؛ 0 لنقطة تمر بها فقط.',
'tours.walk.station.stayInvalid': 'أدخل دقائق كاملة، حتى يوم واحد.',
'tours.walk.station.stayUnknown': 'غير معروف بعد',
'tours.walk.station.stayUnknownShort': 'مدة غير معروفة',
'tours.walk.station.passThrough': 'مرور فقط',
'tours.walk.station.importance': 'الأهمية',
'tours.walk.station.protected': 'محمية',
'tours.walk.station.protectedHint': 'أي تعديل مقترح يُبقي هذه المحطة مكانها. لا يزال بإمكانك تعديلها بنفسك.',
'tours.walk.station.edit': 'تعديل المحطة',
'tours.walk.importance.normal': 'عادية',
'tours.walk.importance.optional': 'اختيارية',
'tours.walk.importance.must': 'لا بد من زيارتها',
'tours.walk.importanceHint.normal': 'تُزار ضمن سير الجولة.',
'tours.walk.importanceHint.optional': 'يمكن تجاوزها إذا ضاق الوقت.',
'tours.walk.importanceHint.must': 'أي تعديل مقترح لاحقًا يُبقيها في الجولة.',
'tours.walk.timeEstimated': 'وقت المشي التقديري',
'tours.walk.timeEstimatedShort': 'المشي',
'tours.walk.timeEstimatedInfoLabel': 'كيف يُحسب وقت المشي',
'tours.walk.timeEstimatedInfo': 'وقت المسار سيرًا بين المحطات، دون مدد البقاء.',
'tours.walk.stays': 'مدد البقاء',
'tours.walk.totalAtLeast': '{time} على الأقل',
'tours.planner.tourType': 'نوع الجولة',
'tours.paddle.boat': 'القارب',
'tours.paddle.boat.kayak': 'كاياك',
+33
View File
@@ -461,6 +461,39 @@ const tours: TranslationStrings = {
'tours.preferences.avoid_stepsHint': 'Pilləkən yalnız dolama yol olmayan yerdə.',
'tours.preferences.avoid_ferries': 'Bərələrdən qaç',
'tours.preferences.avoid_ferriesHint': 'Dolama yol varsa, daha uzun olsa da quru ilə.',
'tours.planner.tourTypeHint.walk': 'Dayanacaqdan dayanacağa piyada, yürüyüş həddi olmadan',
'tours.poi.cat.museum': 'Muzeylər və qalereyalar',
'tours.poi.cat.park': 'Parklar və bağlar',
'tours.poi.cat.cafe': 'Kafelər',
'tours.poi.cat.restaurant': 'Yemək yerləri',
'tours.poi.cat.market': 'Bazarlar',
'tours.walk.station.eyebrow': 'Dayanacaq',
'tours.walk.station.unnamed': 'Adsız dayanacaq',
'tours.walk.station.name': 'Ad',
'tours.walk.station.namePlaceholder': 'Burada nə var',
'tours.walk.station.stay': 'Qalma müddəti, dəqiqə',
'tours.walk.station.stayHint': 'Boş hələ məlum deyil deməkdir; yalnız keçdiyin nöqtə üçün 0.',
'tours.walk.station.stayInvalid': 'Tam dəqiqə daxil et, ən çox bir gün.',
'tours.walk.station.stayUnknown': 'Hələ məlum deyil',
'tours.walk.station.stayUnknownShort': 'Müddət bilinmir',
'tours.walk.station.passThrough': 'Keçid',
'tours.walk.station.importance': 'Əhəmiyyət',
'tours.walk.station.protected': 'Qorunur',
'tours.walk.station.protectedHint':
'Təklif olunan dəyişiklik bu dayanacağı yerində saxlayır. Özün yenə də redaktə edə bilərsən.',
'tours.walk.station.edit': 'Dayanacağı redaktə et',
'tours.walk.importance.normal': 'Adi',
'tours.walk.importance.optional': 'İstəyə bağlı',
'tours.walk.importance.must': 'Mütləq baxılmalı',
'tours.walk.importanceHint.normal': 'Gəzinti boyunca ziyarət olunur.',
'tours.walk.importanceHint.optional': 'Vaxt az olanda buraxıla bilər.',
'tours.walk.importanceHint.must': 'Sonradan təklif olunan dəyişiklik onu gəzintidə saxlayır.',
'tours.walk.timeEstimated': 'Təxmini yürüş vaxtı',
'tours.walk.timeEstimatedShort': 'Yürüş',
'tours.walk.timeEstimatedInfoLabel': 'Yürüş vaxtı necə hesablanır',
'tours.walk.timeEstimatedInfo': 'Dayanacaqlar arasında marşrutlaşdırıcının piyada vaxtı, qalma müddətləri olmadan.',
'tours.walk.stays': 'Qalmalar',
'tours.walk.totalAtLeast': 'ən azı {time}',
'tours.planner.tourType': 'Tur növü',
'tours.paddle.boat': 'Qayıq',
'tours.paddle.boat.kayak': 'Kayak', // same-as-en
+32
View File
@@ -468,6 +468,38 @@ const tours: TranslationStrings = {
'tours.preferences.avoid_stepsHint': 'Escadas só onde não há caminho alternativo.',
'tours.preferences.avoid_ferries': 'Evitar balsas',
'tours.preferences.avoid_ferriesHint': 'Por terra onde houver caminho alternativo, mesmo mais longo.',
'tours.planner.tourTypeHint.walk': 'A pé de parada em parada, sem limite de trilha',
'tours.poi.cat.museum': 'Museus e galerias',
'tours.poi.cat.park': 'Parques e jardins',
'tours.poi.cat.cafe': 'Cafeterias',
'tours.poi.cat.restaurant': 'Lugares para comer',
'tours.poi.cat.market': 'Feiras',
'tours.walk.station.eyebrow': 'Parada',
'tours.walk.station.unnamed': 'Parada sem nome',
'tours.walk.station.name': 'Nome',
'tours.walk.station.namePlaceholder': 'O que há aqui',
'tours.walk.station.stay': 'Permanência em minutos',
'tours.walk.station.stayHint': 'Vazio significa ainda desconhecido; 0 para um ponto pelo qual você só passa.',
'tours.walk.station.stayInvalid': 'Informe minutos inteiros, até um dia.',
'tours.walk.station.stayUnknown': 'Ainda desconhecido',
'tours.walk.station.stayUnknownShort': 'Permanência em aberto',
'tours.walk.station.passThrough': 'Só de passagem',
'tours.walk.station.importance': 'Importância',
'tours.walk.station.protected': 'Protegida',
'tours.walk.station.protectedHint': 'Uma alteração proposta mantém esta parada onde está. Você ainda pode editá-la.',
'tours.walk.station.edit': 'Editar parada',
'tours.walk.importance.normal': 'Comum',
'tours.walk.importance.optional': 'Opcional',
'tours.walk.importance.must': 'Imperdível',
'tours.walk.importanceHint.normal': 'Visitada conforme o passeio segue.',
'tours.walk.importanceHint.optional': 'Pode ficar de fora se faltar tempo.',
'tours.walk.importanceHint.must': 'Uma alteração proposta depois a mantém no passeio.',
'tours.walk.timeEstimated': 'Tempo de caminhada estimado',
'tours.walk.timeEstimatedShort': 'Caminhada',
'tours.walk.timeEstimatedInfoLabel': 'Como o tempo de caminhada é calculado',
'tours.walk.timeEstimatedInfo': 'O tempo a pé do roteador entre as paradas, sem as permanências.',
'tours.walk.stays': 'Permanências',
'tours.walk.totalAtLeast': 'pelo menos {time}',
'tours.planner.tourType': 'Tipo de passeio',
'tours.paddle.boat': 'Embarcação',
'tours.paddle.boat.kayak': 'Caiaque',
+32
View File
@@ -469,6 +469,38 @@ const tours: TranslationStrings = {
'tours.preferences.avoid_stepsHint': 'Escales només on no hi ha cap volta possible.',
'tours.preferences.avoid_ferries': 'Evita transbordadors',
'tours.preferences.avoid_ferriesHint': 'Per terra on hi hagi una volta, encara que sigui més llarga.',
'tours.planner.tourTypeHint.walk': "A peu de parada en parada, sense límit d'excursionisme",
'tours.poi.cat.museum': 'Museus i galeries',
'tours.poi.cat.park': 'Parcs i jardins',
'tours.poi.cat.cafe': 'Cafeteries',
'tours.poi.cat.restaurant': 'Llocs per menjar',
'tours.poi.cat.market': 'Mercats',
'tours.walk.station.eyebrow': 'Parada',
'tours.walk.station.unnamed': 'Parada sense nom',
'tours.walk.station.name': 'Nom',
'tours.walk.station.namePlaceholder': 'Què hi ha aquí',
'tours.walk.station.stay': 'Estada en minuts',
'tours.walk.station.stayHint': 'Buit vol dir encara desconegut; 0 per a un punt pel qual només passes.',
'tours.walk.station.stayInvalid': 'Introdueix minuts sencers, fins a un dia.',
'tours.walk.station.stayUnknown': 'Encara desconegut',
'tours.walk.station.stayUnknownShort': 'Estada oberta',
'tours.walk.station.passThrough': 'Només de pas',
'tours.walk.station.importance': 'Importància',
'tours.walk.station.protected': 'Protegida',
'tours.walk.station.protectedHint': 'Un canvi proposat manté aquesta parada on és. Tu encara la pots editar.',
'tours.walk.station.edit': 'Edita la parada',
'tours.walk.importance.normal': 'Habitual',
'tours.walk.importance.optional': 'Opcional',
'tours.walk.importance.must': 'Imprescindible',
'tours.walk.importanceHint.normal': 'Es visita segons avança la ruta.',
'tours.walk.importanceHint.optional': 'Es pot ometre si falta temps.',
'tours.walk.importanceHint.must': 'Un canvi proposat més endavant la manté a la ruta.',
'tours.walk.timeEstimated': 'Temps a peu estimat',
'tours.walk.timeEstimatedShort': 'A peu',
'tours.walk.timeEstimatedInfoLabel': 'Com es calcula el temps a peu',
'tours.walk.timeEstimatedInfo': 'El temps a peu del càlcul de ruta entre parades, sense les estades.',
'tours.walk.stays': 'Estades',
'tours.walk.totalAtLeast': 'com a mínim {time}',
'tours.planner.tourType': 'Tipus de ruta',
'tours.paddle.boat': 'Embarcació',
'tours.paddle.boat.kayak': 'Caiac',
+32
View File
@@ -459,6 +459,38 @@ const tours: TranslationStrings = {
'tours.preferences.avoid_stepsHint': 'Schody jen tam, kde není cesta kolem.',
'tours.preferences.avoid_ferries': 'Vyhnout se přívozům',
'tours.preferences.avoid_ferriesHint': 'Po souši, kde vede cesta kolem, i když je delší.',
'tours.planner.tourTypeHint.walk': 'Pěšky od zastávky k zastávce, bez turistického limitu',
'tours.poi.cat.museum': 'Muzea a galerie',
'tours.poi.cat.park': 'Parky a zahrady',
'tours.poi.cat.cafe': 'Kavárny',
'tours.poi.cat.restaurant': 'Kde se najíst',
'tours.poi.cat.market': 'Trhy',
'tours.walk.station.eyebrow': 'Zastávka',
'tours.walk.station.unnamed': 'Zastávka bez názvu',
'tours.walk.station.name': 'Název',
'tours.walk.station.namePlaceholder': 'Co tu je',
'tours.walk.station.stay': 'Pobyt v minutách',
'tours.walk.station.stayHint': 'Prázdné znamená zatím neznámé; 0 pro místo, kterým jen projdeš.',
'tours.walk.station.stayInvalid': 'Zadej celé minuty, nejvýše jeden den.',
'tours.walk.station.stayUnknown': 'Zatím neznámé',
'tours.walk.station.stayUnknownShort': 'Pobyt neznámý',
'tours.walk.station.passThrough': 'Jen průchod',
'tours.walk.station.importance': 'Důležitost',
'tours.walk.station.protected': 'Chráněná',
'tours.walk.station.protectedHint': 'Navržená změna nechá tuto zastávku na místě. Sám ji upravit můžeš dál.',
'tours.walk.station.edit': 'Upravit zastávku',
'tours.walk.importance.normal': 'Běžná',
'tours.walk.importance.optional': 'Volitelná',
'tours.walk.importance.must': 'Musíš vidět',
'tours.walk.importanceHint.normal': 'Navštíví se během procházky.',
'tours.walk.importanceHint.optional': 'Lze vynechat, když dochází čas.',
'tours.walk.importanceHint.must': 'Pozdější navržená změna ji v procházce ponechá.',
'tours.walk.timeEstimated': 'Odhadovaná doba chůze',
'tours.walk.timeEstimatedShort': 'Chůze',
'tours.walk.timeEstimatedInfoLabel': 'Jak se počítá doba chůze',
'tours.walk.timeEstimatedInfo': 'Doba chůze mezi zastávkami podle plánovače trasy, bez pobytů.',
'tours.walk.stays': 'Pobyty',
'tours.walk.totalAtLeast': 'nejméně {time}',
'tours.planner.tourType': 'Typ túry',
'tours.paddle.boat': 'Loď',
'tours.paddle.boat.kayak': 'Kajak',
+33
View File
@@ -472,6 +472,39 @@ const tours: TranslationStrings = {
'tours.preferences.avoid_stepsHint': 'Treppen nur, wo es keinen Weg außen herum gibt.',
'tours.preferences.avoid_ferries': 'Fähren meiden',
'tours.preferences.avoid_ferriesHint': 'Über Land, wo es einen Weg außen herum gibt, auch wenn er länger ist.',
'tours.planner.tourTypeHint.walk': 'Zu Fuß von Station zu Station, ohne Wanderlimit',
'tours.poi.cat.museum': 'Museen und Galerien',
'tours.poi.cat.park': 'Parks und Gärten',
'tours.poi.cat.cafe': 'Cafés',
'tours.poi.cat.restaurant': 'Gaststätten',
'tours.poi.cat.market': 'Märkte',
'tours.walk.station.eyebrow': 'Station',
'tours.walk.station.unnamed': 'Station ohne Namen',
'tours.walk.station.name': 'Bezeichnung',
'tours.walk.station.namePlaceholder': 'Was hier ist',
'tours.walk.station.stay': 'Aufenthalt in Minuten',
'tours.walk.station.stayHint': 'Leer heißt noch unbekannt; 0 für einen Punkt, den du nur passierst.',
'tours.walk.station.stayInvalid': 'Ganze Minuten eingeben, höchstens einen Tag.',
'tours.walk.station.stayUnknown': 'Noch unbekannt',
'tours.walk.station.stayUnknownShort': 'Aufenthalt offen',
'tours.walk.station.passThrough': 'Durchgang',
'tours.walk.station.importance': 'Wichtigkeit',
'tours.walk.station.protected': 'Geschützt',
'tours.walk.station.protectedHint':
'Ein Änderungsvorschlag lässt diese Station, wo sie ist. Du selbst kannst sie weiter bearbeiten.',
'tours.walk.station.edit': 'Station bearbeiten',
'tours.walk.importance.normal': 'Standard',
'tours.walk.importance.optional': 'Kann entfallen',
'tours.walk.importance.must': 'Unbedingt besuchen',
'tours.walk.importanceHint.normal': 'Wird besucht, wie der Weg es ergibt.',
'tours.walk.importanceHint.optional': 'Kann entfallen, wenn die Zeit knapp wird.',
'tours.walk.importanceHint.must': 'Ein späterer Änderungsvorschlag behält sie im Walk.',
'tours.walk.timeEstimated': 'Gehzeit (geschätzt)',
'tours.walk.timeEstimatedShort': 'Gehzeit',
'tours.walk.timeEstimatedInfoLabel': 'Wie die Gehzeit entsteht',
'tours.walk.timeEstimatedInfo': 'Die Zeit des Routers zu Fuß zwischen den Stationen, ohne die Aufenthalte.',
'tours.walk.stays': 'Aufenthalte',
'tours.walk.totalAtLeast': 'mindestens {time}',
'tours.planner.tourType': 'Tourtyp',
'tours.paddle.boat': 'Boot',
'tours.paddle.boat.kayak': 'Kajak',
+32
View File
@@ -460,6 +460,38 @@ const tours: TranslationStrings = {
'tours.preferences.avoid_stepsHint': 'Steps and stairs only where there is no way round.',
'tours.preferences.avoid_ferries': 'Avoid ferries',
'tours.preferences.avoid_ferriesHint': 'Over land where there is a way round, even when it is longer.',
'tours.planner.tourTypeHint.walk': 'On foot from stop to stop, without a hiking limit',
'tours.poi.cat.museum': 'Museums and galleries',
'tours.poi.cat.park': 'Parks and gardens',
'tours.poi.cat.cafe': 'Coffee shops',
'tours.poi.cat.restaurant': 'Places to eat',
'tours.poi.cat.market': 'Markets',
'tours.walk.station.eyebrow': 'Stop',
'tours.walk.station.unnamed': 'Unnamed stop',
'tours.walk.station.name': 'Label',
'tours.walk.station.namePlaceholder': 'What is here',
'tours.walk.station.stay': 'Stay in minutes',
'tours.walk.station.stayHint': 'Empty means not known yet; 0 for a point you only pass.',
'tours.walk.station.stayInvalid': 'Enter whole minutes, up to a day.',
'tours.walk.station.stayUnknown': 'Not known yet',
'tours.walk.station.stayUnknownShort': 'Stay unknown',
'tours.walk.station.passThrough': 'Passing through',
'tours.walk.station.importance': 'Importance',
'tours.walk.station.protected': 'Protected',
'tours.walk.station.protectedHint': 'A proposed change keeps this stop where it is. You can still edit it yourself.',
'tours.walk.station.edit': 'Edit stop',
'tours.walk.importance.normal': 'Regular',
'tours.walk.importance.optional': 'Optional',
'tours.walk.importance.must': 'Must see',
'tours.walk.importanceHint.normal': 'Visited as the walk goes.',
'tours.walk.importanceHint.optional': 'Can be left out when time runs short.',
'tours.walk.importanceHint.must': 'A change proposed later keeps it in the walk.',
'tours.walk.timeEstimated': 'Est. walking time',
'tours.walk.timeEstimatedShort': 'Walking',
'tours.walk.timeEstimatedInfoLabel': 'How the walking time is worked out',
'tours.walk.timeEstimatedInfo': "The router's time on foot between the stops, without the stays.",
'tours.walk.stays': 'Stays',
'tours.walk.totalAtLeast': 'at least {time}',
'tours.planner.tourType': 'Tour type',
'tours.paddle.boat': 'Boat',
'tours.paddle.boat.kayak': 'Kayak',
+32
View File
@@ -467,6 +467,38 @@ const tours: TranslationStrings = {
'tours.preferences.avoid_stepsHint': 'Escaleras solo donde no hay forma de rodearlas.',
'tours.preferences.avoid_ferries': 'Evitar ferris',
'tours.preferences.avoid_ferriesHint': 'Por tierra donde haya una forma de rodear, aunque sea más larga.',
'tours.planner.tourTypeHint.walk': 'A pie de parada en parada, sin límite de senderismo',
'tours.poi.cat.museum': 'Museos y galerías',
'tours.poi.cat.park': 'Parques y jardines',
'tours.poi.cat.cafe': 'Cafeterías',
'tours.poi.cat.restaurant': 'Sitios para comer',
'tours.poi.cat.market': 'Mercados',
'tours.walk.station.eyebrow': 'Parada',
'tours.walk.station.unnamed': 'Parada sin nombre',
'tours.walk.station.name': 'Nombre',
'tours.walk.station.namePlaceholder': 'Qué hay aquí',
'tours.walk.station.stay': 'Estancia en minutos',
'tours.walk.station.stayHint': 'Vacío significa aún desconocido; 0 para un punto por el que solo pasas.',
'tours.walk.station.stayInvalid': 'Introduce minutos enteros, hasta un día.',
'tours.walk.station.stayUnknown': 'Aún desconocido',
'tours.walk.station.stayUnknownShort': 'Estancia abierta',
'tours.walk.station.passThrough': 'Solo de paso',
'tours.walk.station.importance': 'Importancia',
'tours.walk.station.protected': 'Protegida',
'tours.walk.station.protectedHint': 'Un cambio propuesto deja esta parada donde está. Tú aún puedes editarla.',
'tours.walk.station.edit': 'Editar parada',
'tours.walk.importance.normal': 'Habitual',
'tours.walk.importance.optional': 'Opcional',
'tours.walk.importance.must': 'Imprescindible',
'tours.walk.importanceHint.normal': 'Se visita según avanza el paseo.',
'tours.walk.importanceHint.optional': 'Se puede omitir si falta tiempo.',
'tours.walk.importanceHint.must': 'Un cambio propuesto más tarde la mantiene en el paseo.',
'tours.walk.timeEstimated': 'Tiempo a pie estimado',
'tours.walk.timeEstimatedShort': 'A pie',
'tours.walk.timeEstimatedInfoLabel': 'Cómo se calcula el tiempo a pie',
'tours.walk.timeEstimatedInfo': 'El tiempo a pie del planificador entre paradas, sin las estancias.',
'tours.walk.stays': 'Estancias',
'tours.walk.totalAtLeast': 'al menos {time}',
'tours.planner.tourType': 'Tipo de ruta',
'tours.paddle.boat': 'Embarcación',
'tours.paddle.boat.kayak': 'Kayak', // same-as-en
+32
View File
@@ -462,6 +462,38 @@ const tours: TranslationStrings = {
'tours.preferences.avoid_stepsHint': 'Trepid ainult seal, kus ringi minna ei saa.',
'tours.preferences.avoid_ferries': 'Väldi praame',
'tours.preferences.avoid_ferriesHint': 'Mööda maad, kus saab ringi minna, isegi kui see on pikem.',
'tours.planner.tourTypeHint.walk': 'Jalgsi peatusest peatusesse, ilma matkapiiranguta',
'tours.poi.cat.museum': 'Muuseumid ja galeriid',
'tours.poi.cat.park': 'Pargid ja aiad',
'tours.poi.cat.cafe': 'Kohvikud',
'tours.poi.cat.restaurant': 'Söögikohad',
'tours.poi.cat.market': 'Turud',
'tours.walk.station.eyebrow': 'Peatus',
'tours.walk.station.unnamed': 'Nimetu peatus',
'tours.walk.station.name': 'Nimi',
'tours.walk.station.namePlaceholder': 'Mis siin on',
'tours.walk.station.stay': 'Peatumine minutites',
'tours.walk.station.stayHint': 'Tühi tähendab veel teadmata; 0 punkti jaoks, millest ainult möödud.',
'tours.walk.station.stayInvalid': 'Sisesta täisminutid, kuni üks päev.',
'tours.walk.station.stayUnknown': 'Veel teadmata',
'tours.walk.station.stayUnknownShort': 'Peatus teadmata',
'tours.walk.station.passThrough': 'Läbisõit',
'tours.walk.station.importance': 'Tähtsus',
'tours.walk.station.protected': 'Kaitstud',
'tours.walk.station.protectedHint': 'Pakutud muudatus jätab selle peatuse paika. Ise saad seda endiselt muuta.',
'tours.walk.station.edit': 'Muuda peatust',
'tours.walk.importance.normal': 'Tavaline',
'tours.walk.importance.optional': 'Valikuline',
'tours.walk.importance.must': 'Kindlasti näha',
'tours.walk.importanceHint.normal': 'Külastatakse jalutuse käigus.',
'tours.walk.importanceHint.optional': 'Võib ära jätta, kui aega jääb väheks.',
'tours.walk.importanceHint.must': 'Hilisem muudatusettepanek jätab selle jalutusse.',
'tours.walk.timeEstimated': 'Hinnanguline kõndimisaeg',
'tours.walk.timeEstimatedShort': 'Kõnd',
'tours.walk.timeEstimatedInfoLabel': 'Kuidas kõndimisaeg arvutatakse',
'tours.walk.timeEstimatedInfo': 'Marsruutija kõndimisaeg peatuste vahel, ilma peatumisteta.',
'tours.walk.stays': 'Peatumised',
'tours.walk.totalAtLeast': 'vähemalt {time}',
'tours.planner.tourType': 'Matka tüüp',
'tours.paddle.boat': 'Paat',
'tours.paddle.boat.kayak': 'Süst',
+33
View File
@@ -476,6 +476,39 @@ const tours: TranslationStrings = {
'tours.preferences.avoid_stepsHint': 'Des escaliers seulement là où on ne peut pas les contourner.',
'tours.preferences.avoid_ferries': 'Éviter les bacs',
'tours.preferences.avoid_ferriesHint': 'Par voie terrestre là où on peut contourner, même si c’est plus long.',
'tours.planner.tourTypeHint.walk': "À pied d'étape en étape, sans limite de randonnée",
'tours.poi.cat.museum': 'Musées et galeries',
'tours.poi.cat.park': 'Parcs et jardins',
'tours.poi.cat.cafe': 'Salons de café',
'tours.poi.cat.restaurant': 'Où manger',
'tours.poi.cat.market': 'Marchés',
'tours.walk.station.eyebrow': 'Étape',
'tours.walk.station.unnamed': 'Étape sans nom',
'tours.walk.station.name': 'Intitulé',
'tours.walk.station.namePlaceholder': "Ce qu'il y a ici",
'tours.walk.station.stay': 'Arrêt en minutes',
'tours.walk.station.stayHint': 'Vide signifie encore inconnu ; 0 pour un point où tu ne fais que passer.',
'tours.walk.station.stayInvalid': "Saisis des minutes entières, jusqu'à une journée.",
'tours.walk.station.stayUnknown': 'Encore inconnu',
'tours.walk.station.stayUnknownShort': 'Arrêt inconnu',
'tours.walk.station.passThrough': 'Simple passage',
'tours.walk.station.importance': "Importance de l'étape",
'tours.walk.station.protected': 'Protégée',
'tours.walk.station.protectedHint':
'Une modification proposée laisse cette étape à sa place. Tu peux toujours la modifier toi-même.',
'tours.walk.station.edit': "Modifier l'étape",
'tours.walk.importance.normal': 'Normale',
'tours.walk.importance.optional': 'Facultative',
'tours.walk.importance.must': 'Incontournable',
'tours.walk.importanceHint.normal': 'Visitée au fil de la balade.',
'tours.walk.importanceHint.optional': 'Peut sauter si le temps manque.',
'tours.walk.importanceHint.must': 'Une modification proposée plus tard la garde dans la balade.',
'tours.walk.timeEstimated': 'Temps de marche estimé',
'tours.walk.timeEstimatedShort': 'Marche',
'tours.walk.timeEstimatedInfoLabel': 'Comment le temps de marche est calculé',
'tours.walk.timeEstimatedInfo': "Le temps à pied du calcul d'itinéraire entre les étapes, sans les arrêts.",
'tours.walk.stays': 'Arrêts',
'tours.walk.totalAtLeast': 'au moins {time}',
'tours.planner.tourType': 'Type de sortie',
'tours.paddle.boat': 'Embarcation',
'tours.paddle.boat.kayak': 'Kayak', // same-as-en
+33
View File
@@ -476,6 +476,39 @@ const tours: TranslationStrings = {
'tours.preferences.avoid_stepsHint': 'Σκαλοπάτια μόνο όπου δεν υπάρχει παράκαμψη.',
'tours.preferences.avoid_ferries': 'Αποφυγή πορθμείων',
'tours.preferences.avoid_ferriesHint': 'Από ξηράς όπου υπάρχει παράκαμψη, ακόμη κι αν είναι μεγαλύτερη.',
'tours.planner.tourTypeHint.walk': 'Με τα πόδια από στάση σε στάση, χωρίς όριο πεζοπορίας',
'tours.poi.cat.museum': 'Μουσεία και γκαλερί',
'tours.poi.cat.park': 'Πάρκα και κήποι',
'tours.poi.cat.cafe': 'Καφετέριες',
'tours.poi.cat.restaurant': 'Μέρη για φαγητό',
'tours.poi.cat.market': 'Αγορές',
'tours.walk.station.eyebrow': 'Στάση',
'tours.walk.station.unnamed': 'Στάση χωρίς όνομα',
'tours.walk.station.name': 'Όνομα',
'tours.walk.station.namePlaceholder': 'Τι υπάρχει εδώ',
'tours.walk.station.stay': 'Παραμονή σε λεπτά',
'tours.walk.station.stayHint': 'Κενό σημαίνει άγνωστο ακόμη· 0 για σημείο από το οποίο απλώς περνάς.',
'tours.walk.station.stayInvalid': 'Δώσε ακέραια λεπτά, έως μία ημέρα.',
'tours.walk.station.stayUnknown': 'Άγνωστο ακόμη',
'tours.walk.station.stayUnknownShort': 'Παραμονή άγνωστη',
'tours.walk.station.passThrough': 'Απλή διέλευση',
'tours.walk.station.importance': 'Σημασία',
'tours.walk.station.protected': 'Προστατευμένη',
'tours.walk.station.protectedHint':
'Μια προτεινόμενη αλλαγή αφήνει αυτή τη στάση στη θέση της. Εσύ μπορείς ακόμη να την επεξεργαστείς.',
'tours.walk.station.edit': 'Επεξεργασία στάσης',
'tours.walk.importance.normal': 'Κανονική',
'tours.walk.importance.optional': 'Προαιρετική',
'tours.walk.importance.must': 'Οπωσδήποτε',
'tours.walk.importanceHint.normal': 'Επισκέπτεται καθώς προχωρά η βόλτα.',
'tours.walk.importanceHint.optional': 'Μπορεί να παραλειφθεί αν πιέζει ο χρόνος.',
'tours.walk.importanceHint.must': 'Μια μεταγενέστερη προτεινόμενη αλλαγή την κρατά στη βόλτα.',
'tours.walk.timeEstimated': 'Εκτιμώμενος χρόνος περπατήματος',
'tours.walk.timeEstimatedShort': 'Περπάτημα',
'tours.walk.timeEstimatedInfoLabel': 'Πώς υπολογίζεται ο χρόνος περπατήματος',
'tours.walk.timeEstimatedInfo': 'Ο χρόνος με τα πόδια του δρομολογητή ανάμεσα στις στάσεις, χωρίς τις παραμονές.',
'tours.walk.stays': 'Παραμονές',
'tours.walk.totalAtLeast': 'τουλάχιστον {time}',
'tours.planner.tourType': 'Τύπος διαδρομής',
'tours.paddle.boat': 'Σκάφος',
'tours.paddle.boat.kayak': 'Καγιάκ',
+33
View File
@@ -468,6 +468,39 @@ const tours: TranslationStrings = {
'tours.preferences.avoid_stepsHint': 'Lépcső csak ott, ahol nincs kerülőút.',
'tours.preferences.avoid_ferries': 'Kompok kerülése',
'tours.preferences.avoid_ferriesHint': 'Szárazföldön, ahol van kerülőút, akkor is, ha hosszabb.',
'tours.planner.tourTypeHint.walk': 'Gyalog megállóról megállóra, túrakorlát nélkül',
'tours.poi.cat.museum': 'Múzeumok és galériák',
'tours.poi.cat.park': 'Parkok és kertek',
'tours.poi.cat.cafe': 'Kávézók',
'tours.poi.cat.restaurant': 'Étkezőhelyek',
'tours.poi.cat.market': 'Piacok',
'tours.walk.station.eyebrow': 'Megálló',
'tours.walk.station.unnamed': 'Névtelen megálló',
'tours.walk.station.name': 'Megnevezés',
'tours.walk.station.namePlaceholder': 'Mi van itt',
'tours.walk.station.stay': 'Tartózkodás percben',
'tours.walk.station.stayHint': 'Üres: még nem ismert; 0 egy pontnál, amelyen csak áthaladsz.',
'tours.walk.station.stayInvalid': 'Egész perceket adj meg, legfeljebb egy napot.',
'tours.walk.station.stayUnknown': 'Még nem ismert',
'tours.walk.station.stayUnknownShort': 'Tartózkodás nyitott',
'tours.walk.station.passThrough': 'Csak áthaladás',
'tours.walk.station.importance': 'Fontosság',
'tours.walk.station.protected': 'Védett',
'tours.walk.station.protectedHint':
'Egy javasolt változtatás a helyén hagyja ezt a megállót. Te továbbra is szerkesztheted.',
'tours.walk.station.edit': 'Megálló szerkesztése',
'tours.walk.importance.normal': 'Szokásos',
'tours.walk.importance.optional': 'Elhagyható',
'tours.walk.importance.must': 'Kihagyhatatlan',
'tours.walk.importanceHint.normal': 'A séta során meglátogatjuk.',
'tours.walk.importanceHint.optional': 'Kimaradhat, ha szűkül az idő.',
'tours.walk.importanceHint.must': 'Egy későbbi javasolt változtatás bent hagyja a sétában.',
'tours.walk.timeEstimated': 'Becsült gyaloglási idő',
'tours.walk.timeEstimatedShort': 'Gyaloglás',
'tours.walk.timeEstimatedInfoLabel': 'Hogyan számoljuk a gyaloglási időt',
'tours.walk.timeEstimatedInfo': 'Az útvonaltervező gyalogos ideje a megállók között, a tartózkodások nélkül.',
'tours.walk.stays': 'Tartózkodások',
'tours.walk.totalAtLeast': 'legalább {time}',
'tours.planner.tourType': 'Túratípus',
'tours.paddle.boat': 'Hajó',
'tours.paddle.boat.kayak': 'Kajak',
+33
View File
@@ -460,6 +460,39 @@ const tours: TranslationStrings = {
'tours.preferences.avoid_stepsHint': 'Tangga hanya jika tidak ada jalan memutar.',
'tours.preferences.avoid_ferries': 'Hindari feri',
'tours.preferences.avoid_ferriesHint': 'Lewat darat jika ada jalan memutar, meski lebih panjang.',
'tours.planner.tourTypeHint.walk': 'Berjalan dari titik ke titik, tanpa batas pendakian',
'tours.poi.cat.museum': 'Museum dan galeri',
'tours.poi.cat.park': 'Taman dan kebun',
'tours.poi.cat.cafe': 'Kedai kopi',
'tours.poi.cat.restaurant': 'Tempat makan',
'tours.poi.cat.market': 'Pasar',
'tours.walk.station.eyebrow': 'Titik singgah',
'tours.walk.station.unnamed': 'Titik tanpa nama',
'tours.walk.station.name': 'Nama',
'tours.walk.station.namePlaceholder': 'Apa yang ada di sini',
'tours.walk.station.stay': 'Lama singgah dalam menit',
'tours.walk.station.stayHint': 'Kosong berarti belum diketahui; 0 untuk titik yang hanya kamu lewati.',
'tours.walk.station.stayInvalid': 'Masukkan menit bulat, paling lama satu hari.',
'tours.walk.station.stayUnknown': 'Belum diketahui',
'tours.walk.station.stayUnknownShort': 'Lama singgah belum ada',
'tours.walk.station.passThrough': 'Hanya lewat',
'tours.walk.station.importance': 'Tingkat penting',
'tours.walk.station.protected': 'Dilindungi',
'tours.walk.station.protectedHint':
'Perubahan yang diusulkan membiarkan titik ini di tempatnya. Kamu tetap bisa mengeditnya.',
'tours.walk.station.edit': 'Edit titik singgah',
'tours.walk.importance.normal': 'Biasa',
'tours.walk.importance.optional': 'Opsional',
'tours.walk.importance.must': 'Wajib dikunjungi',
'tours.walk.importanceHint.normal': 'Dikunjungi sesuai jalannya rute.',
'tours.walk.importanceHint.optional': 'Bisa dilewati jika waktu mepet.',
'tours.walk.importanceHint.must': 'Perubahan yang diusulkan nanti tetap menyertakannya.',
'tours.walk.timeEstimated': 'Perkiraan waktu jalan',
'tours.walk.timeEstimatedShort': 'Berjalan',
'tours.walk.timeEstimatedInfoLabel': 'Cara waktu jalan dihitung',
'tours.walk.timeEstimatedInfo': 'Waktu jalan kaki dari perute di antara titik singgah, tanpa lama singgah.',
'tours.walk.stays': 'Lama singgah',
'tours.walk.totalAtLeast': 'setidaknya {time}',
'tours.planner.tourType': 'Jenis tur',
'tours.paddle.boat': 'Perahu',
'tours.paddle.boat.kayak': 'Kayak', // same-as-en
+32
View File
@@ -468,6 +468,38 @@ const tours: TranslationStrings = {
'tours.preferences.avoid_stepsHint': 'Scale solo dove non c’è modo di aggirarle.',
'tours.preferences.avoid_ferries': 'Evita i traghetti',
'tours.preferences.avoid_ferriesHint': 'Via terra dove c’è un modo di aggirare, anche se più lungo.',
'tours.planner.tourTypeHint.walk': 'A piedi di tappa in tappa, senza limite escursionistico',
'tours.poi.cat.museum': 'Musei e gallerie',
'tours.poi.cat.park': 'Parchi e giardini',
'tours.poi.cat.cafe': 'Caffetterie',
'tours.poi.cat.restaurant': 'Dove mangiare',
'tours.poi.cat.market': 'Mercati',
'tours.walk.station.eyebrow': 'Tappa',
'tours.walk.station.unnamed': 'Tappa senza nome',
'tours.walk.station.name': 'Denominazione',
'tours.walk.station.namePlaceholder': "Cosa c'è qui",
'tours.walk.station.stay': 'Sosta in minuti',
'tours.walk.station.stayHint': 'Vuoto significa ancora sconosciuto; 0 per un punto da cui passi soltanto.',
'tours.walk.station.stayInvalid': 'Inserisci minuti interi, fino a un giorno.',
'tours.walk.station.stayUnknown': 'Ancora sconosciuto',
'tours.walk.station.stayUnknownShort': 'Sosta da definire',
'tours.walk.station.passThrough': 'Solo di passaggio',
'tours.walk.station.importance': 'Importanza',
'tours.walk.station.protected': 'Protetta',
'tours.walk.station.protectedHint': "Una modifica proposta lascia questa tappa dov'è. Tu puoi comunque modificarla.",
'tours.walk.station.edit': 'Modifica tappa',
'tours.walk.importance.normal': 'Normale',
'tours.walk.importance.optional': 'Facoltativa',
'tours.walk.importance.must': 'Da non perdere',
'tours.walk.importanceHint.normal': 'Si visita lungo il percorso.',
'tours.walk.importanceHint.optional': 'Si può saltare se il tempo stringe.',
'tours.walk.importanceHint.must': 'Una modifica proposta più avanti la mantiene nel percorso.',
'tours.walk.timeEstimated': 'Tempo a piedi stimato',
'tours.walk.timeEstimatedShort': 'A piedi',
'tours.walk.timeEstimatedInfoLabel': 'Come si calcola il tempo a piedi',
'tours.walk.timeEstimatedInfo': 'Il tempo a piedi del calcolo percorso tra le tappe, senza le soste.',
'tours.walk.stays': 'Soste',
'tours.walk.totalAtLeast': 'almeno {time}',
'tours.planner.tourType': 'Tipo di tour',
'tours.paddle.boat': 'Imbarcazione',
'tours.paddle.boat.kayak': 'Kayak', // same-as-en
+33
View File
@@ -452,6 +452,39 @@ const tours: TranslationStrings = {
'tours.preferences.avoid_stepsHint': '回り道がない場所だけ階段を通ります。',
'tours.preferences.avoid_ferries': 'フェリーを避ける',
'tours.preferences.avoid_ferriesHint': '回り道があれば、長くても陸路を通ります。',
'tours.planner.tourTypeHint.walk': '立ち寄り先から立ち寄り先へ徒歩で。ハイキングの難易度制限なし',
'tours.poi.cat.museum': '美術館・博物館',
'tours.poi.cat.park': '公園と庭園',
'tours.poi.cat.cafe': 'カフェ',
'tours.poi.cat.restaurant': '食事処',
'tours.poi.cat.market': '市場',
'tours.walk.station.eyebrow': '立ち寄り先',
'tours.walk.station.unnamed': '名前のない立ち寄り先',
'tours.walk.station.name': '名前',
'tours.walk.station.namePlaceholder': 'ここにあるもの',
'tours.walk.station.stay': '滞在時間(分)',
'tours.walk.station.stayHint': '空欄はまだ不明、通過するだけの地点は 0。',
'tours.walk.station.stayInvalid': '整数の分で入力してください(最大1日)。',
'tours.walk.station.stayUnknown': 'まだ不明',
'tours.walk.station.stayUnknownShort': '滞在時間未定',
'tours.walk.station.passThrough': '通過のみ',
'tours.walk.station.importance': '重要度',
'tours.walk.station.protected': '保護',
'tours.walk.station.protectedHint':
'提案された変更でもこの立ち寄り先はそのまま残ります。自分では引き続き編集できます。',
'tours.walk.station.edit': '立ち寄り先を編集',
'tours.walk.importance.normal': '通常',
'tours.walk.importance.optional': '任意',
'tours.walk.importance.must': '必見',
'tours.walk.importanceHint.normal': '歩く流れで立ち寄ります。',
'tours.walk.importanceHint.optional': '時間が足りなければ省けます。',
'tours.walk.importanceHint.must': '後から提案される変更でも外されません。',
'tours.walk.timeEstimated': '推定歩行時間',
'tours.walk.timeEstimatedShort': '歩行',
'tours.walk.timeEstimatedInfoLabel': '歩行時間の求め方',
'tours.walk.timeEstimatedInfo': 'ルート計算による立ち寄り先間の徒歩時間。滞在時間は含みません。',
'tours.walk.stays': '滞在',
'tours.walk.totalAtLeast': '{time} 以上',
'tours.planner.tourType': 'ツアーの種類',
'tours.paddle.boat': 'ボート',
'tours.paddle.boat.kayak': 'カヤック',
+32
View File
@@ -449,6 +449,38 @@ const tours: TranslationStrings = {
'tours.preferences.avoid_stepsHint': '돌아갈 길이 없을 때만 계단을 지납니다.',
'tours.preferences.avoid_ferries': '페리 피하기',
'tours.preferences.avoid_ferriesHint': '돌아가는 길이 있으면 더 길어도 육로로 갑니다.',
'tours.planner.tourTypeHint.walk': '정류지에서 정류지로 도보 이동, 하이킹 제한 없음',
'tours.poi.cat.museum': '박물관과 갤러리',
'tours.poi.cat.park': '공원과 정원',
'tours.poi.cat.cafe': '카페',
'tours.poi.cat.restaurant': '식당',
'tours.poi.cat.market': '시장',
'tours.walk.station.eyebrow': '정류지',
'tours.walk.station.unnamed': '이름 없는 정류지',
'tours.walk.station.name': '이름',
'tours.walk.station.namePlaceholder': '이곳에 있는 것',
'tours.walk.station.stay': '머무는 시간(분)',
'tours.walk.station.stayHint': '비워 두면 아직 모름, 지나가기만 하는 곳은 0.',
'tours.walk.station.stayInvalid': '정수 분으로 입력하세요(최대 하루).',
'tours.walk.station.stayUnknown': '아직 모름',
'tours.walk.station.stayUnknownShort': '머무는 시간 미정',
'tours.walk.station.passThrough': '지나가기만',
'tours.walk.station.importance': '중요도',
'tours.walk.station.protected': '보호됨',
'tours.walk.station.protectedHint': '제안된 변경에서도 이 정류지는 그대로 유지됩니다. 직접 수정하는 것은 가능합니다.',
'tours.walk.station.edit': '정류지 편집',
'tours.walk.importance.normal': '보통',
'tours.walk.importance.optional': '선택',
'tours.walk.importance.must': '꼭 방문',
'tours.walk.importanceHint.normal': '걷는 흐름에 따라 들릅니다.',
'tours.walk.importanceHint.optional': '시간이 부족하면 건너뛸 수 있습니다.',
'tours.walk.importanceHint.must': '나중에 제안되는 변경에서도 유지됩니다.',
'tours.walk.timeEstimated': '예상 도보 시간',
'tours.walk.timeEstimatedShort': '도보',
'tours.walk.timeEstimatedInfoLabel': '도보 시간 계산 방법',
'tours.walk.timeEstimatedInfo': '경로 계산기가 낸 정류지 사이 도보 시간이며, 머무는 시간은 빠져 있습니다.',
'tours.walk.stays': '머무는 시간',
'tours.walk.totalAtLeast': '최소 {time}',
'tours.planner.tourType': '투어 유형',
'tours.paddle.boat': '보트',
'tours.paddle.boat.kayak': '카약',
+33
View File
@@ -470,6 +470,39 @@ const tours: TranslationStrings = {
'tours.preferences.avoid_stepsHint': 'Trappen alleen waar er geen weg omheen is.',
'tours.preferences.avoid_ferries': 'Veerponten vermijden',
'tours.preferences.avoid_ferriesHint': 'Over land waar er een weg omheen is, ook als die langer is.',
'tours.planner.tourTypeHint.walk': 'Te voet van stop naar stop, zonder wandelgrens',
'tours.poi.cat.museum': 'Musea en galerieën',
'tours.poi.cat.park': 'Parken en tuinen',
'tours.poi.cat.cafe': 'Koffiezaken',
'tours.poi.cat.restaurant': 'Eetgelegenheden',
'tours.poi.cat.market': 'Markten',
'tours.walk.station.eyebrow': 'Halte',
'tours.walk.station.unnamed': 'Halte zonder naam',
'tours.walk.station.name': 'Benaming',
'tours.walk.station.namePlaceholder': 'Wat hier is',
'tours.walk.station.stay': 'Verblijf in minuten',
'tours.walk.station.stayHint': 'Leeg betekent nog onbekend; 0 voor een punt waar je alleen langs loopt.',
'tours.walk.station.stayInvalid': 'Vul hele minuten in, tot een dag.',
'tours.walk.station.stayUnknown': 'Nog onbekend',
'tours.walk.station.stayUnknownShort': 'Verblijf open',
'tours.walk.station.passThrough': 'Alleen doorgaan',
'tours.walk.station.importance': 'Belang',
'tours.walk.station.protected': 'Beschermd',
'tours.walk.station.protectedHint':
'Een voorgestelde wijziging laat deze halte waar hij is. Zelf kun je hem nog bewerken.',
'tours.walk.station.edit': 'Halte bewerken',
'tours.walk.importance.normal': 'Gewoon',
'tours.walk.importance.optional': 'Optioneel',
'tours.walk.importance.must': 'Niet missen',
'tours.walk.importanceHint.normal': 'Wordt bezocht zoals de wandeling loopt.',
'tours.walk.importanceHint.optional': 'Kan wegvallen als de tijd krap wordt.',
'tours.walk.importanceHint.must': 'Een later voorgestelde wijziging houdt hem in de wandeling.',
'tours.walk.timeEstimated': 'Geschatte looptijd',
'tours.walk.timeEstimatedShort': 'Lopen',
'tours.walk.timeEstimatedInfoLabel': 'Hoe de looptijd wordt berekend',
'tours.walk.timeEstimatedInfo': 'De looptijd van de routeplanner tussen de haltes, zonder het verblijf.',
'tours.walk.stays': 'Verblijven',
'tours.walk.totalAtLeast': 'minstens {time}',
'tours.planner.tourType': 'Soort tocht',
'tours.paddle.boat': 'Boot',
'tours.paddle.boat.kayak': 'Kajak',
+33
View File
@@ -475,6 +475,39 @@ const tours: TranslationStrings = {
'tours.preferences.avoid_stepsHint': 'Schody tylko tam, gdzie nie da się ich obejść.',
'tours.preferences.avoid_ferries': 'Unikaj promów',
'tours.preferences.avoid_ferriesHint': 'Lądem, gdzie da się obejść, nawet jeśli dłużej.',
'tours.planner.tourTypeHint.walk': 'Pieszo od przystanku do przystanku, bez limitu szlaku',
'tours.poi.cat.museum': 'Muzea i galerie',
'tours.poi.cat.park': 'Parki i ogrody',
'tours.poi.cat.cafe': 'Kawiarnie',
'tours.poi.cat.restaurant': 'Gdzie zjeść',
'tours.poi.cat.market': 'Targi',
'tours.walk.station.eyebrow': 'Przystanek',
'tours.walk.station.unnamed': 'Przystanek bez nazwy',
'tours.walk.station.name': 'Nazwa',
'tours.walk.station.namePlaceholder': 'Co tu jest',
'tours.walk.station.stay': 'Pobyt w minutach',
'tours.walk.station.stayHint': 'Puste oznacza jeszcze nieznane; 0 dla punktu, przez który tylko przechodzisz.',
'tours.walk.station.stayInvalid': 'Wpisz pełne minuty, maksymalnie jeden dzień.',
'tours.walk.station.stayUnknown': 'Jeszcze nieznane',
'tours.walk.station.stayUnknownShort': 'Pobyt nieznany',
'tours.walk.station.passThrough': 'Tylko przejście',
'tours.walk.station.importance': 'Ważność',
'tours.walk.station.protected': 'Chroniony',
'tours.walk.station.protectedHint':
'Proponowana zmiana zostawia ten przystanek na miejscu. Ty nadal możesz go edytować.',
'tours.walk.station.edit': 'Edytuj przystanek',
'tours.walk.importance.normal': 'Zwykły',
'tours.walk.importance.optional': 'Opcjonalny',
'tours.walk.importance.must': 'Koniecznie',
'tours.walk.importanceHint.normal': 'Odwiedzany po drodze.',
'tours.walk.importanceHint.optional': 'Można pominąć, gdy brakuje czasu.',
'tours.walk.importanceHint.must': 'Późniejsza proponowana zmiana zostawi go w trasie.',
'tours.walk.timeEstimated': 'Szacowany czas marszu',
'tours.walk.timeEstimatedShort': 'Marsz',
'tours.walk.timeEstimatedInfoLabel': 'Jak liczony jest czas marszu',
'tours.walk.timeEstimatedInfo': 'Czas marszu między przystankami według planera trasy, bez postojów.',
'tours.walk.stays': 'Postoje',
'tours.walk.totalAtLeast': 'co najmniej {time}',
'tours.planner.tourType': 'Rodzaj wycieczki',
'tours.paddle.boat': 'Łódź',
'tours.paddle.boat.kayak': 'Kajak',
+33
View File
@@ -478,6 +478,39 @@ const tours: TranslationStrings = {
'tours.preferences.avoid_stepsHint': 'Лестницы только там, где их не обойти.',
'tours.preferences.avoid_ferries': 'Избегать паромов',
'tours.preferences.avoid_ferriesHint': 'По суше, где есть обход, даже если он длиннее.',
'tours.planner.tourTypeHint.walk': 'Пешком от остановки к остановке, без ограничения по сложности',
'tours.poi.cat.museum': 'Музеи и галереи',
'tours.poi.cat.park': 'Парки и сады',
'tours.poi.cat.cafe': 'Кафе',
'tours.poi.cat.restaurant': 'Где поесть',
'tours.poi.cat.market': 'Рынки',
'tours.walk.station.eyebrow': 'Остановка',
'tours.walk.station.unnamed': 'Остановка без названия',
'tours.walk.station.name': 'Название',
'tours.walk.station.namePlaceholder': 'Что здесь',
'tours.walk.station.stay': 'Остановка в минутах',
'tours.walk.station.stayHint': 'Пусто значит пока неизвестно; 0 для точки, которую ты только проходишь.',
'tours.walk.station.stayInvalid': 'Введи целые минуты, не больше суток.',
'tours.walk.station.stayUnknown': 'Пока неизвестно',
'tours.walk.station.stayUnknownShort': 'Остановка неизвестна',
'tours.walk.station.passThrough': 'Только мимо',
'tours.walk.station.importance': 'Важность',
'tours.walk.station.protected': 'Защищена',
'tours.walk.station.protectedHint':
'Предложенное изменение оставит эту остановку на месте. Сам ты по-прежнему можешь её править.',
'tours.walk.station.edit': 'Изменить остановку',
'tours.walk.importance.normal': 'Обычная',
'tours.walk.importance.optional': 'Необязательная',
'tours.walk.importance.must': 'Обязательно',
'tours.walk.importanceHint.normal': 'Посещается по ходу прогулки.',
'tours.walk.importanceHint.optional': 'Можно пропустить, если не хватает времени.',
'tours.walk.importanceHint.must': 'Предложенное позже изменение оставит её в прогулке.',
'tours.walk.timeEstimated': 'Расчётное время в пути',
'tours.walk.timeEstimatedShort': 'Пешком',
'tours.walk.timeEstimatedInfoLabel': 'Как считается время в пути',
'tours.walk.timeEstimatedInfo': 'Время пешком между остановками по расчёту маршрута, без остановок на месте.',
'tours.walk.stays': 'Остановки',
'tours.walk.totalAtLeast': 'не меньше {time}',
'tours.planner.tourType': 'Тип тура',
'tours.paddle.boat': 'Лодка',
'tours.paddle.boat.kayak': 'Каяк',
+32
View File
@@ -461,6 +461,38 @@ const tours: TranslationStrings = {
'tours.preferences.avoid_stepsHint': 'Schody len tam, kde nie je cesta okolo.',
'tours.preferences.avoid_ferries': 'Vyhnúť sa prievozom',
'tours.preferences.avoid_ferriesHint': 'Po súši, kde vedie cesta okolo, aj keď je dlhšia.',
'tours.planner.tourTypeHint.walk': 'Pešo od zastávky k zastávke, bez turistického limitu',
'tours.poi.cat.museum': 'Múzeá a galérie',
'tours.poi.cat.park': 'Parky a záhrady',
'tours.poi.cat.cafe': 'Kaviarne',
'tours.poi.cat.restaurant': 'Kde sa najesť',
'tours.poi.cat.market': 'Trhy',
'tours.walk.station.eyebrow': 'Zastávka',
'tours.walk.station.unnamed': 'Zastávka bez názvu',
'tours.walk.station.name': 'Názov',
'tours.walk.station.namePlaceholder': 'Čo je tu',
'tours.walk.station.stay': 'Pobyt v minútach',
'tours.walk.station.stayHint': 'Prázdne znamená zatiaľ neznáme; 0 pre miesto, ktorým len prejdeš.',
'tours.walk.station.stayInvalid': 'Zadaj celé minúty, najviac jeden deň.',
'tours.walk.station.stayUnknown': 'Zatiaľ neznáme',
'tours.walk.station.stayUnknownShort': 'Pobyt neznámy',
'tours.walk.station.passThrough': 'Len prechod',
'tours.walk.station.importance': 'Dôležitosť',
'tours.walk.station.protected': 'Chránená',
'tours.walk.station.protectedHint': 'Navrhovaná zmena nechá túto zastávku na mieste. Sám ju môžeš ďalej upravovať.',
'tours.walk.station.edit': 'Upraviť zastávku',
'tours.walk.importance.normal': 'Bežná',
'tours.walk.importance.optional': 'Voliteľná',
'tours.walk.importance.must': 'Musíš vidieť',
'tours.walk.importanceHint.normal': 'Navštívi sa počas prechádzky.',
'tours.walk.importanceHint.optional': 'Dá sa vynechať, keď chýba čas.',
'tours.walk.importanceHint.must': 'Neskoršia navrhovaná zmena ju v prechádzke ponechá.',
'tours.walk.timeEstimated': 'Odhadovaný čas chôdze',
'tours.walk.timeEstimatedShort': 'Chôdza',
'tours.walk.timeEstimatedInfoLabel': 'Ako sa počíta čas chôdze',
'tours.walk.timeEstimatedInfo': 'Čas chôdze medzi zastávkami podľa plánovača trasy, bez pobytov.',
'tours.walk.stays': 'Pobyty',
'tours.walk.totalAtLeast': 'najmenej {time}',
'tours.planner.tourType': 'Typ túry',
'tours.paddle.boat': 'Loď',
'tours.paddle.boat.kayak': 'Kajak',
+33
View File
@@ -459,6 +459,39 @@ const tours: TranslationStrings = {
'tours.preferences.avoid_stepsHint': 'Trappor bara där det inte finns någon väg runt.',
'tours.preferences.avoid_ferries': 'Undvik färjor',
'tours.preferences.avoid_ferriesHint': 'Över land där det finns en väg runt, även om den är längre.',
'tours.planner.tourTypeHint.walk': 'Till fots från stopp till stopp, utan vandringsgräns',
'tours.poi.cat.museum': 'Museer och gallerier',
'tours.poi.cat.park': 'Parker och trädgårdar',
'tours.poi.cat.cafe': 'Kaféer',
'tours.poi.cat.restaurant': 'Matställen',
'tours.poi.cat.market': 'Torghandel',
'tours.walk.station.eyebrow': 'Stopp',
'tours.walk.station.unnamed': 'Namnlöst stopp',
'tours.walk.station.name': 'Benämning',
'tours.walk.station.namePlaceholder': 'Vad som finns här',
'tours.walk.station.stay': 'Vistelse i minuter',
'tours.walk.station.stayHint': 'Tomt betyder ännu okänt; 0 för en punkt du bara passerar.',
'tours.walk.station.stayInvalid': 'Ange hela minuter, högst ett dygn.',
'tours.walk.station.stayUnknown': 'Ännu okänt',
'tours.walk.station.stayUnknownShort': 'Vistelse okänd',
'tours.walk.station.passThrough': 'Bara förbi',
'tours.walk.station.importance': 'Vikt',
'tours.walk.station.protected': 'Skyddad',
'tours.walk.station.protectedHint':
'Ett föreslaget ändringsförslag låter stoppet vara kvar. Du kan fortfarande redigera det själv.',
'tours.walk.station.edit': 'Redigera stopp',
'tours.walk.importance.normal': 'Vanlig',
'tours.walk.importance.optional': 'Valfri',
'tours.walk.importance.must': 'Måste ses',
'tours.walk.importanceHint.normal': 'Besöks under promenaden.',
'tours.walk.importanceHint.optional': 'Kan strykas om tiden blir knapp.',
'tours.walk.importanceHint.must': 'Ett senare förslag behåller den i promenaden.',
'tours.walk.timeEstimated': 'Beräknad gångtid',
'tours.walk.timeEstimatedShort': 'Gång',
'tours.walk.timeEstimatedInfoLabel': 'Hur gångtiden räknas fram',
'tours.walk.timeEstimatedInfo': 'Ruttplanerarens gångtid mellan stoppen, utan vistelserna.',
'tours.walk.stays': 'Vistelser',
'tours.walk.totalAtLeast': 'minst {time}',
'tours.planner.tourType': 'Turtyp',
'tours.paddle.boat': 'Båt',
'tours.paddle.boat.kayak': 'Kajak',
+32
View File
@@ -443,6 +443,38 @@ const tours: TranslationStrings = {
'tours.preferences.avoid_stepsHint': 'ผ่านบันไดเฉพาะเมื่อไม่มีทางอ้อม',
'tours.preferences.avoid_ferries': 'หลีกเลี่ยงเรือข้ามฟาก',
'tours.preferences.avoid_ferriesHint': 'ไปทางบกเมื่อมีทางอ้อม แม้จะไกลกว่า',
'tours.planner.tourTypeHint.walk': 'เดินเท้าจากจุดแวะหนึ่งไปอีกจุด ไม่มีขีดจำกัดเส้นทางเดินป่า',
'tours.poi.cat.museum': 'พิพิธภัณฑ์และแกลเลอรี',
'tours.poi.cat.park': 'สวนสาธารณะและสวน',
'tours.poi.cat.cafe': 'ร้านกาแฟ',
'tours.poi.cat.restaurant': 'ร้านอาหาร',
'tours.poi.cat.market': 'ตลาด',
'tours.walk.station.eyebrow': 'จุดแวะ',
'tours.walk.station.unnamed': 'จุดแวะที่ไม่มีชื่อ',
'tours.walk.station.name': 'ชื่อ',
'tours.walk.station.namePlaceholder': 'ที่นี่มีอะไร',
'tours.walk.station.stay': 'เวลาแวะพัก (นาที)',
'tours.walk.station.stayHint': 'เว้นว่างหมายถึงยังไม่ทราบ ใส่ 0 สำหรับจุดที่แค่เดินผ่าน',
'tours.walk.station.stayInvalid': 'กรอกเป็นนาทีเต็ม ไม่เกินหนึ่งวัน',
'tours.walk.station.stayUnknown': 'ยังไม่ทราบ',
'tours.walk.station.stayUnknownShort': 'ยังไม่ระบุเวลาแวะ',
'tours.walk.station.passThrough': 'แค่เดินผ่าน',
'tours.walk.station.importance': 'ความสำคัญ',
'tours.walk.station.protected': 'ป้องกันไว้',
'tours.walk.station.protectedHint': 'การเปลี่ยนแปลงที่เสนอจะคงจุดแวะนี้ไว้ที่เดิม คุณยังแก้ไขเองได้',
'tours.walk.station.edit': 'แก้ไขจุดแวะ',
'tours.walk.importance.normal': 'ปกติ',
'tours.walk.importance.optional': 'ไม่บังคับ',
'tours.walk.importance.must': 'ต้องไป',
'tours.walk.importanceHint.normal': 'แวะตามเส้นทางเดิน',
'tours.walk.importanceHint.optional': 'ข้ามได้หากเวลาไม่พอ',
'tours.walk.importanceHint.must': 'การเปลี่ยนแปลงที่เสนอภายหลังจะคงไว้ในทริป',
'tours.walk.timeEstimated': 'เวลาเดินโดยประมาณ',
'tours.walk.timeEstimatedShort': 'เดิน',
'tours.walk.timeEstimatedInfoLabel': 'วิธีคำนวณเวลาเดิน',
'tours.walk.timeEstimatedInfo': 'เวลาเดินระหว่างจุดแวะจากตัวคำนวณเส้นทาง ไม่รวมเวลาแวะพัก',
'tours.walk.stays': 'เวลาแวะพัก',
'tours.walk.totalAtLeast': 'อย่างน้อย {time}',
'tours.planner.tourType': 'ประเภททริป',
'tours.paddle.boat': 'เรือ',
'tours.paddle.boat.kayak': 'เรือคายัก',
+1 -1
View File
@@ -20,7 +20,7 @@ const tokens = (value: string) => [...value.matchAll(/\{[^{}]+\}/g)].map((match)
// placeholder spec; here only the general keys are.
const groups = pluralGroups(Object.keys(english));
const generalKeys = (keys: string[]) => keys.filter((key) => !pluralFormOf(key, groups));
const GENERAL_KEY_COUNT = 703;
const GENERAL_KEY_COUNT = 735;
function literalKeys(source: string): string[] {
const tree = ts.createSourceFile('locale.ts', source, ts.ScriptTarget.Latest, true);
+33
View File
@@ -462,6 +462,39 @@ const tours: TranslationStrings = {
'tours.preferences.avoid_stepsHint': 'Merdiven yalnızca dolaşacak yol yoksa.',
'tours.preferences.avoid_ferries': 'Feribotlardan kaçın',
'tours.preferences.avoid_ferriesHint': 'Dolaşacak yol varsa daha uzun olsa da karadan.',
'tours.planner.tourTypeHint.walk': 'Duraktan durağa yürüyerek, yürüyüş sınırı olmadan',
'tours.poi.cat.museum': 'Müzeler ve galeriler',
'tours.poi.cat.park': 'Parklar ve bahçeler',
'tours.poi.cat.cafe': 'Kafeler',
'tours.poi.cat.restaurant': 'Yemek yerleri',
'tours.poi.cat.market': 'Pazarlar',
'tours.walk.station.eyebrow': 'Durak',
'tours.walk.station.unnamed': 'Adsız durak',
'tours.walk.station.name': 'Ad',
'tours.walk.station.namePlaceholder': 'Burada ne var',
'tours.walk.station.stay': 'Kalış süresi (dakika)',
'tours.walk.station.stayHint': 'Boş, henüz bilinmiyor demektir; yalnızca geçtiğin bir nokta için 0.',
'tours.walk.station.stayInvalid': 'Tam dakika gir, en fazla bir gün.',
'tours.walk.station.stayUnknown': 'Henüz bilinmiyor',
'tours.walk.station.stayUnknownShort': 'Kalış belirsiz',
'tours.walk.station.passThrough': 'Sadece geçiş',
'tours.walk.station.importance': 'Önem',
'tours.walk.station.protected': 'Korunuyor',
'tours.walk.station.protectedHint':
'Önerilen bir değişiklik bu durağı yerinde bırakır. Sen yine de düzenleyebilirsin.',
'tours.walk.station.edit': 'Durağı düzenle',
'tours.walk.importance.normal': 'Olağan',
'tours.walk.importance.optional': 'İsteğe bağlı',
'tours.walk.importance.must': 'Mutlaka görülmeli',
'tours.walk.importanceHint.normal': 'Yürüyüş akışında uğranır.',
'tours.walk.importanceHint.optional': 'Zaman daralırsa atlanabilir.',
'tours.walk.importanceHint.must': 'Sonradan önerilen bir değişiklik onu yürüyüşte tutar.',
'tours.walk.timeEstimated': 'Tahmini yürüme süresi',
'tours.walk.timeEstimatedShort': 'Yürüme',
'tours.walk.timeEstimatedInfoLabel': 'Yürüme süresi nasıl hesaplanır',
'tours.walk.timeEstimatedInfo': 'Rota hesaplayıcının duraklar arasındaki yürüme süresi, kalışlar hariç.',
'tours.walk.stays': 'Kalışlar',
'tours.walk.totalAtLeast': 'en az {time}',
'tours.planner.tourType': 'Tur türü',
'tours.paddle.boat': 'Tekne',
'tours.paddle.boat.kayak': 'Kano (kayak)',
+33
View File
@@ -474,6 +474,39 @@ const tours: TranslationStrings = {
'tours.preferences.avoid_stepsHint': 'Сходи лише там, де їх не обійти.',
'tours.preferences.avoid_ferries': 'Уникати поромів',
'tours.preferences.avoid_ferriesHint': 'Суходолом, де є обхід, навіть якщо він довший.',
'tours.planner.tourTypeHint.walk': 'Пішки від зупинки до зупинки, без обмеження за складністю',
'tours.poi.cat.museum': 'Музеї та галереї',
'tours.poi.cat.park': 'Парки та сади',
'tours.poi.cat.cafe': "Кав'ярні",
'tours.poi.cat.restaurant': 'Де поїсти',
'tours.poi.cat.market': 'Ринки',
'tours.walk.station.eyebrow': 'Зупинка',
'tours.walk.station.unnamed': 'Зупинка без назви',
'tours.walk.station.name': 'Назва',
'tours.walk.station.namePlaceholder': 'Що тут',
'tours.walk.station.stay': 'Перебування у хвилинах',
'tours.walk.station.stayHint': 'Порожньо означає поки невідомо; 0 для точки, яку ти лише проходиш.',
'tours.walk.station.stayInvalid': 'Введи цілі хвилини, не більше доби.',
'tours.walk.station.stayUnknown': 'Поки невідомо',
'tours.walk.station.stayUnknownShort': 'Перебування невідоме',
'tours.walk.station.passThrough': 'Лише мимо',
'tours.walk.station.importance': 'Важливість',
'tours.walk.station.protected': 'Захищена',
'tours.walk.station.protectedHint':
'Запропонована зміна залишить цю зупинку на місці. Сам ти все ще можеш її редагувати.',
'tours.walk.station.edit': 'Редагувати зупинку',
'tours.walk.importance.normal': 'Звичайна',
'tours.walk.importance.optional': "Необов'язкова",
'tours.walk.importance.must': "Обов'язково",
'tours.walk.importanceHint.normal': 'Відвідується по ходу прогулянки.',
'tours.walk.importanceHint.optional': 'Можна пропустити, якщо бракує часу.',
'tours.walk.importanceHint.must': 'Запропонована пізніше зміна залишить її у прогулянці.',
'tours.walk.timeEstimated': 'Орієнтовний час ходьби',
'tours.walk.timeEstimatedShort': 'Ходьба',
'tours.walk.timeEstimatedInfoLabel': 'Як рахується час ходьби',
'tours.walk.timeEstimatedInfo': 'Час пішки між зупинками за розрахунком маршруту, без перебування.',
'tours.walk.stays': 'Перебування',
'tours.walk.totalAtLeast': 'щонайменше {time}',
'tours.planner.tourType': 'Тип туру',
'tours.paddle.boat': 'Човен',
'tours.paddle.boat.kayak': 'Каяк',
+32
View File
@@ -460,6 +460,38 @@ const tours: TranslationStrings = {
'tours.preferences.avoid_stepsHint': 'Chỉ đi bậc thang khi không có đường vòng.',
'tours.preferences.avoid_ferries': 'Tránh phà',
'tours.preferences.avoid_ferriesHint': 'Đi đường bộ nơi có đường vòng, kể cả khi dài hơn.',
'tours.planner.tourTypeHint.walk': 'Đi bộ từ điểm dừng này sang điểm dừng khác, không giới hạn leo núi',
'tours.poi.cat.museum': 'Bảo tàng và phòng tranh',
'tours.poi.cat.park': 'Công viên và vườn',
'tours.poi.cat.cafe': 'Quán cà phê',
'tours.poi.cat.restaurant': 'Chỗ ăn uống',
'tours.poi.cat.market': 'Chợ',
'tours.walk.station.eyebrow': 'Điểm dừng',
'tours.walk.station.unnamed': 'Điểm dừng chưa đặt tên',
'tours.walk.station.name': 'Tên',
'tours.walk.station.namePlaceholder': 'Ở đây có gì',
'tours.walk.station.stay': 'Thời gian dừng (phút)',
'tours.walk.station.stayHint': 'Để trống nghĩa là chưa biết; nhập 0 cho điểm chỉ đi ngang qua.',
'tours.walk.station.stayInvalid': 'Nhập số phút nguyên, tối đa một ngày.',
'tours.walk.station.stayUnknown': 'Chưa biết',
'tours.walk.station.stayUnknownShort': 'Chưa rõ thời gian dừng',
'tours.walk.station.passThrough': 'Chỉ đi qua',
'tours.walk.station.importance': 'Mức độ quan trọng',
'tours.walk.station.protected': 'Được bảo vệ',
'tours.walk.station.protectedHint': 'Thay đổi được đề xuất sẽ giữ nguyên điểm dừng này. Bạn vẫn có thể tự chỉnh sửa.',
'tours.walk.station.edit': 'Sửa điểm dừng',
'tours.walk.importance.normal': 'Bình thường',
'tours.walk.importance.optional': 'Tùy chọn',
'tours.walk.importance.must': 'Nhất định phải đến',
'tours.walk.importanceHint.normal': 'Ghé thăm theo lộ trình đi bộ.',
'tours.walk.importanceHint.optional': 'Có thể bỏ qua khi thiếu thời gian.',
'tours.walk.importanceHint.must': 'Thay đổi đề xuất sau này vẫn giữ điểm này.',
'tours.walk.timeEstimated': 'Thời gian đi bộ ước tính',
'tours.walk.timeEstimatedShort': 'Đi bộ',
'tours.walk.timeEstimatedInfoLabel': 'Cách tính thời gian đi bộ',
'tours.walk.timeEstimatedInfo': 'Thời gian đi bộ giữa các điểm dừng do bộ định tuyến tính, không gồm thời gian dừng.',
'tours.walk.stays': 'Thời gian dừng',
'tours.walk.totalAtLeast': 'ít nhất {time}',
'tours.planner.tourType': 'Loại chuyến',
'tours.paddle.boat': 'Thuyền',
'tours.paddle.boat.kayak': 'Thuyền kayak',
+32
View File
@@ -428,6 +428,38 @@ const tours: TranslationStrings = {
'tours.preferences.avoid_stepsHint': '只在無法繞行時經過階梯。',
'tours.preferences.avoid_ferries': '避開渡輪',
'tours.preferences.avoid_ferriesHint': '有繞行陸路時走陸路,即使更遠。',
'tours.planner.tourTypeHint.walk': '從一站步行到下一站,不設健行難度限制',
'tours.poi.cat.museum': '博物館和畫廊',
'tours.poi.cat.park': '公園和花園',
'tours.poi.cat.cafe': '咖啡館',
'tours.poi.cat.restaurant': '餐館',
'tours.poi.cat.market': '市集',
'tours.walk.station.eyebrow': '站點',
'tours.walk.station.unnamed': '未命名站點',
'tours.walk.station.name': '名稱',
'tours.walk.station.namePlaceholder': '這裡有什麼',
'tours.walk.station.stay': '停留時間(分鐘)',
'tours.walk.station.stayHint': '留空表示尚不清楚;只經過的地點填 0。',
'tours.walk.station.stayInvalid': '請輸入整數分鐘,最多一天。',
'tours.walk.station.stayUnknown': '尚不清楚',
'tours.walk.station.stayUnknownShort': '停留時間未定',
'tours.walk.station.passThrough': '僅經過',
'tours.walk.station.importance': '重要程度',
'tours.walk.station.protected': '受保護',
'tours.walk.station.protectedHint': '提議的變更會保留此站點原位。你自己仍可編輯。',
'tours.walk.station.edit': '編輯站點',
'tours.walk.importance.normal': '普通',
'tours.walk.importance.optional': '可選',
'tours.walk.importance.must': '必去',
'tours.walk.importanceHint.normal': '按路線順路遊覽。',
'tours.walk.importanceHint.optional': '時間緊時可以跳過。',
'tours.walk.importanceHint.must': '之後提議的變更會保留它。',
'tours.walk.timeEstimated': '預估步行時間',
'tours.walk.timeEstimatedShort': '步行',
'tours.walk.timeEstimatedInfoLabel': '步行時間如何計算',
'tours.walk.timeEstimatedInfo': '路線規劃給出的站點間步行時間,不含停留時間。',
'tours.walk.stays': '停留',
'tours.walk.totalAtLeast': '至少 {time}',
'tours.planner.tourType': '路線類型',
'tours.paddle.boat': '船隻',
'tours.paddle.boat.kayak': '獨木舟(Kayak)',
+32
View File
@@ -429,6 +429,38 @@ const tours: TranslationStrings = {
'tours.preferences.avoid_stepsHint': '只在无法绕行时经过台阶。',
'tours.preferences.avoid_ferries': '避开渡轮',
'tours.preferences.avoid_ferriesHint': '有绕行陆路时走陆路,即使更远。',
'tours.planner.tourTypeHint.walk': '从一站步行到下一站,不设徒步难度限制',
'tours.poi.cat.museum': '博物馆和画廊',
'tours.poi.cat.park': '公园和花园',
'tours.poi.cat.cafe': '咖啡馆',
'tours.poi.cat.restaurant': '餐馆',
'tours.poi.cat.market': '集市',
'tours.walk.station.eyebrow': '站点',
'tours.walk.station.unnamed': '未命名站点',
'tours.walk.station.name': '名称',
'tours.walk.station.namePlaceholder': '这里有什么',
'tours.walk.station.stay': '停留时间(分钟)',
'tours.walk.station.stayHint': '留空表示尚不清楚;只经过的地点填 0。',
'tours.walk.station.stayInvalid': '请输入整数分钟,最多一天。',
'tours.walk.station.stayUnknown': '尚不清楚',
'tours.walk.station.stayUnknownShort': '停留时间未定',
'tours.walk.station.passThrough': '仅经过',
'tours.walk.station.importance': '重要程度',
'tours.walk.station.protected': '受保护',
'tours.walk.station.protectedHint': '提议的更改会保留此站点原位。你自己仍可编辑。',
'tours.walk.station.edit': '编辑站点',
'tours.walk.importance.normal': '普通',
'tours.walk.importance.optional': '可选',
'tours.walk.importance.must': '必去',
'tours.walk.importanceHint.normal': '按路线顺路游览。',
'tours.walk.importanceHint.optional': '时间紧时可以跳过。',
'tours.walk.importanceHint.must': '之后提议的更改会保留它。',
'tours.walk.timeEstimated': '预计步行时间',
'tours.walk.timeEstimatedShort': '步行',
'tours.walk.timeEstimatedInfoLabel': '步行时间如何计算',
'tours.walk.timeEstimatedInfo': '路线规划给出的站点间步行时间,不含停留时间。',
'tours.walk.stays': '停留',
'tours.walk.totalAtLeast': '至少 {time}',
'tours.planner.tourType': '路线类型',
'tours.paddle.boat': '船只',
'tours.paddle.boat.kayak': '皮划艇',