feat(tours): a city walk's arrivals against its stops' opening hours, and a compact place search (CW-04, CW-05)

Opened from a day with a time, each stop shows its estimated arrival: the
start, the walking time shared out along the route, and the stays before
it, at the earliest while a stay is unknown. A stop whose opening hours say
it is closed then, or closes before the stay ends, says so with the times;
hours that cannot be read are shown as they are, and a stop without hours is
unknown, never open. The themes are a two by two grid and the categories one
line with a list to tick, so the search no longer fills the rail. The button
that discards a draft for a new tour says so in two words.
This commit is contained in:
Maurice
2026-10-19 23:15:18 +02:00
parent 422197f97e
commit e4b6644be8
32 changed files with 747 additions and 117 deletions
@@ -654,7 +654,22 @@ export default function TourDetailDialog({
/>
)}
{tour.tour_type === 'walk' && (
<WalkStationsCard tour={tour} tripId={place.trip_id} assignmentId={assignment?.id ?? null} canMark={canAssign} />
<WalkStationsCard
tour={tour}
tripId={place.trip_id}
assignmentId={assignment?.id ?? null}
canMark={canAssign}
day={
assignment
? {
date: days.find((day) => day.id === assignment.day_id)?.date ?? null,
start: assignment.assignment_time ?? place.place_time ?? null,
route: analysis?.routeCoordinates ?? [],
walkingMinutes: tour.duration ?? null,
}
: null
}
/>
)}
{tour.tour_type === 'kayak' && <PaddleCheckSection tour={tour} />}
{tour.tour_type === 'kayak' && <PaddleWindSection placeId={place.id} start={placeStart(place)} />}
@@ -1,16 +1,22 @@
import type { TourPoi, TourPoiCategory, WalkTheme } from '@trek/shared';
import type { TourPoi, WalkTheme } from '@trek/shared';
import { TOUR_POI_CORRIDORS_M, WALK_THEMES } from '@trek/shared';
import {
AlertTriangle,
ChevronDown,
Clock,
Coffee,
ExternalLink,
Landmark,
Loader2,
MoveHorizontal,
Plus,
RefreshCw,
Route,
Search,
Shuffle,
Trees,
WifiOff,
type LucideIcon,
} from 'lucide-react';
import { useEffect, useRef } from 'react';
import { useTranslation } from '../../../i18n';
@@ -18,9 +24,11 @@ import { useSettingsStore } from '../../../store/settingsStore';
import { useTourPoiStore } from '../../../store/tourPoiStore';
import { formatDistance } from '../../../utils/units';
import { TOUR_POI_LOOKS } from '../../Map/poiCategories';
import { PopoverItem } from '../../Packing/PackingPopover';
import { WhiteBadge } from '../../Planner/planParts';
import { PillSelect, Segmented } from '../../shared/dialogParts';
import { PillSelect, PopoverListPanel, Segmented } from '../../shared/dialogParts';
import { fs } from '../../shared/DialogShell';
import { usePopoverList } from '../../shared/usePopoverList';
import type { TourPlannerController } from '../planner/useTourPlanner';
import { useWaypointPlaceNames } from '../planner/useWaypointPlaceNames';
import { RailCard, TourNotice } from '../tourParts';
@@ -33,21 +41,114 @@ type Unit = Parameters<typeof formatDistance>[1];
const km = (metres: number) => metres / 1000;
/** A category to search for, as a chip: its icon in its map colour, ticked or not. */
function CategoryChip({ category, on, onToggle }: { category: TourPoiCategory; on: boolean; onToggle: () => void }) {
/**
* What is searched for, in one line: the chosen categories' icons and names,
* with every category the tour type offers in a list that stays open while
* they are ticked. A theme of a city walk sets them all at once above it.
*/
function CategoryPicker({ search }: { search: TourPoiSearch }) {
const { t } = useTranslation();
const look = TOUR_POI_LOOKS[category];
const list = usePopoverList(0, { estimatedHeight: 320 });
const { open, toggle, triggerRef, listId } = list;
const chosen = search.offered.filter((category) => search.categories.includes(category));
const names = chosen.map((category) => t(TOUR_POI_LOOKS[category].labelKey));
const summary =
names.length === 0
? t('tours.poi.categoriesNone')
: names.length <= 2
? names.join(', ')
: t('tours.poi.categoriesMore', { first: names.slice(0, 2).join(', '), more: names.length - 2 });
return (
<button
type="button"
onClick={onToggle}
aria-pressed={on}
data-testid={`tour-poi-category-${category}`}
className={`inline-flex items-center gap-1.5 rounded-full px-2.5 py-1 font-medium transition-colors ${on ? 'bg-surface-card text-content shadow-sm ring-1 ring-inset ring-edge-faint' : 'bg-surface-tertiary text-content-muted hover:text-content'}`}
style={fs(11.5, 'body')}
>
<look.Icon size={12} strokeWidth={2.2} style={{ color: on ? look.color : undefined }} />
{t(look.labelKey)}
</button>
<>
<button
ref={triggerRef}
type="button"
onClick={toggle}
aria-expanded={open}
aria-controls={open ? listId : undefined}
data-testid="tour-poi-categories"
className="flex w-full min-w-0 items-center gap-2 rounded-[10px] border border-edge bg-surface-input px-2.5 py-1.5 text-start hover:border-content-faint"
>
<span className="sr-only">{t('tours.poi.categories')}: </span>
<span className="flex flex-none -space-x-1 rtl:space-x-reverse" aria-hidden="true">
{chosen.slice(0, 3).map((category) => {
const look = TOUR_POI_LOOKS[category];
return (
<span
key={category}
className="grid h-5 w-5 place-items-center rounded-full bg-surface-card ring-2 ring-[color:var(--bg-card)]"
style={{ color: look.color }}
>
<look.Icon size={11} strokeWidth={2.2} />
</span>
);
})}
</span>
<span className="min-w-0 flex-1 truncate font-medium text-content" style={fs(12, 'body')}>
{summary}
</span>
<ChevronDown
size={14}
strokeWidth={2.2}
aria-hidden="true"
className={`flex-none text-content-faint transition-transform ${open ? 'rotate-180' : ''}`}
/>
</button>
<PopoverListPanel list={list} label={t('tours.poi.categories')} className="min-w-[240px]">
{search.offered.map((category) => {
const look = TOUR_POI_LOOKS[category];
return (
<PopoverItem
key={category}
icon={<look.Icon size={13} strokeWidth={2.2} style={{ color: look.color }} />}
label={t(look.labelKey)}
active={search.categories.includes(category)}
onClick={() => search.toggleCategory(category)}
/>
);
})}
</PopoverListPanel>
</>
);
}
const THEME_ICONS: Record<WalkTheme, LucideIcon> = { culture: Landmark, parks: Trees, food: Coffee, mixed: Shuffle };
/**
* CW-05: a city walk's four themes as a grid of two by two, each naming
* itself in full; the one the categories below match is on.
*/
function ThemeGrid({
themes,
active,
onChoose,
}: {
themes: readonly WalkTheme[];
active: WalkTheme | null;
onChoose: (theme: WalkTheme) => void;
}) {
const { t } = useTranslation();
return (
<div className="grid grid-cols-2 gap-1.5" role="group" aria-label={t('tours.walk.themes')}>
{themes.map((theme) => {
const Icon = THEME_ICONS[theme];
const on = theme === active;
return (
<button
key={theme}
type="button"
onClick={() => onChoose(theme)}
aria-pressed={on}
data-testid={`tour-poi-theme-${theme}`}
className={`flex min-w-0 items-center gap-2 rounded-[10px] px-2.5 py-2 text-start font-medium leading-tight transition-colors ${on ? 'bg-surface-card text-content shadow-sm ring-1 ring-inset ring-edge-faint' : 'bg-surface-tertiary text-content-muted hover:text-content'}`}
style={fs(11.5, 'body')}
>
<Icon size={13} strokeWidth={2.2} className="flex-none" aria-hidden="true" />
<span className="min-w-0">{t(`tours.walk.theme.${theme}`)}</span>
</button>
);
})}
</div>
);
}
@@ -313,28 +414,13 @@ export function TourPoiSearchCard({ planner }: { planner: TourPlannerController
</p>
{search.themes.length > 0 && (
<div className="flex flex-col gap-1.5" data-testid="tour-poi-themes">
<Segmented<string>
fill
value={search.theme ?? ''}
onChange={(key) => search.chooseTheme(key as WalkTheme)}
label={t('tours.walk.themes')}
options={search.themes.map((key) => ({ value: key, label: t(`tours.walk.theme.${key}`) }))}
/>
<ThemeGrid themes={search.themes} active={search.theme} onChoose={search.chooseTheme} />
<span className="text-content-faint" style={fs(11, 'body')}>
{search.theme ? t(`tours.walk.themeHint.${search.theme}`) : t('tours.walk.themeHint.own')}
</span>
</div>
)}
<div className="flex flex-wrap gap-1.5" role="group" aria-label={t('tours.poi.categories')}>
{search.offered.map((category) => (
<CategoryChip
key={category}
category={category}
on={search.categories.includes(category)}
onToggle={() => search.toggleCategory(category)}
/>
))}
</div>
<CategoryPicker search={search} />
<div className="flex flex-col gap-1.5">
<span className="text-content-muted" style={fs(11, 'body')}>
{t('tours.poi.within')}
@@ -381,7 +467,11 @@ export function TourPoiSearchCard({ planner }: { planner: TourPlannerController
planner={planner}
insertion={insertion}
unit={unit}
theme={search.theme && (WALK_THEMES[search.theme] as readonly string[]).includes(poi.category) ? search.theme : null}
theme={
search.theme && (WALK_THEMES[search.theme] as readonly string[]).includes(poi.category)
? search.theme
: null
}
/>
))}
</ul>
+164 -55
View File
@@ -1,5 +1,5 @@
import type { TourListItem, TourVisitStatus } from '@trek/shared';
import { Check, CheckCheck, Redo2, SkipForward, Undo2 } from 'lucide-react';
import { AlertTriangle, Check, CheckCheck, Clock, Redo2, SkipForward, Undo2 } from 'lucide-react';
import { useEffect, useState } from 'react';
import { useTranslation } from '../../../i18n';
import { tourRepo } from '../../../repo/tourRepo';
@@ -10,7 +10,8 @@ import { useToast } from '../../shared/Toast';
import { StationPills } from '../planner/stationParts';
import { plannerWaypointsOf, type TourPlannerWaypoint } from '../planner/tourPlannerWaypoints';
import { TourDayRowPill } from '../TourDayRowFacts';
import { CardAction } from '../tourParts';
import { CardAction, TourNotice } from '../tourParts';
import { clockOf, minutesOf, stationArrivals, stationFit, type StationFit } from './walkSchedule';
let nextId = 0;
const freshId = () => `station-${++nextId}`;
@@ -81,6 +82,141 @@ function StatusActions({
);
}
/** CW-04: what the arrival at a station meets, said only where it can be known. */
function StationHours({ fit, arrival, atLeast }: { fit: StationFit; arrival: number; atLeast: boolean }) {
const { t } = useTranslation();
const time = clockOf(arrival);
const conflict =
fit.kind === 'closed'
? t('tours.walk.hours.closed', { arrival: time })
: fit.kind === 'closes'
? t('tours.walk.hours.closes', { arrival: time, leave: clockOf(fit.leave), closes: clockOf(fit.closes) })
: null;
return (
<span className="flex flex-col gap-1">
<span className="inline-flex items-center gap-1 text-content-muted" style={fs(11, 'body')}>
<Clock size={11} strokeWidth={2.2} aria-hidden="true" />
{t(atLeast ? 'tours.walk.arrivalAtLeast' : 'tours.walk.arrival', { time })}
</span>
{conflict && (
<span
className="inline-flex items-start gap-1 text-warning"
style={fs(11, 'body')}
data-testid="walk-station-conflict"
>
<AlertTriangle size={11} strokeWidth={2.2} className="mt-0.5 flex-none" aria-hidden="true" />
{conflict}
</span>
)}
{fit.kind === 'unread' && (
<span className="text-content-faint" style={fs(10.5, 'body')}>
{t('tours.walk.hours.unread', { text: fit.hours })}
</span>
)}
</span>
);
}
/** One station of the list: its number or mark, name, stay and the day's arrival, and what can be marked. */
function StationRow({
station,
index,
status,
arrival,
date,
onMark,
}: {
station: TourPlannerWaypoint;
index: number;
status: TourVisitStatus | undefined;
arrival: { arrival: number; atLeast: boolean } | null;
date: string | null;
onMark: ((status: TourVisitStatus | 'planned') => void) | null;
}) {
const { t } = useTranslation();
return (
<li
data-testid="walk-station"
className={`flex items-start gap-2.5 rounded-[10px] px-1.5 py-1.5 ${status ? 'opacity-70' : ''}`}
>
<span
className="grid h-6 w-6 flex-none place-items-center rounded-full bg-surface-tertiary font-geist tabular-nums text-content-secondary"
style={fs(11)}
>
{status === 'visited' ? (
<Check size={12} strokeWidth={2.4} />
) : status === 'skipped' ? (
<Redo2 size={12} strokeWidth={2} />
) : (
index + 1
)}
</span>
<span className="flex min-w-0 flex-1 flex-col gap-1">
<span
className={`truncate font-semibold text-content ${status === 'skipped' ? 'line-through' : ''}`}
style={fs(12.5, 'body')}
>
{station.name ?? t('tours.walk.stationNumbered', { number: index + 1 })}
</span>
<StationPills point={station} />
{arrival && date && (
<StationHours
fit={stationFit(station.openingHours, date, arrival.arrival, station.stayMinutes ?? 0)}
arrival={arrival.arrival}
atLeast={arrival.atLeast}
/>
)}
{status && (
<span className="text-content-faint" style={fs(11, 'body')}>
{t(status === 'visited' ? 'tours.walk.visit.isVisited' : 'tours.walk.visit.isSkipped')}
</span>
)}
</span>
{onMark && <StatusActions status={status} onMark={onMark} />}
</li>
);
}
/** The lines above the stations: loading, what is missing for arrivals, where the hours come from, where to check off. */
function WalkStationsNotes({
loading,
any,
noStart,
hoursShown,
fromDay,
}: {
loading: boolean;
any: boolean;
noStart: boolean;
hoursShown: boolean;
fromDay: boolean;
}) {
const { t } = useTranslation();
const quiet = (text: string) => (
<span className="text-content-faint" style={fs(11, 'body')}>
{text}
</span>
);
if (loading) return quiet(t('common.loading'));
return (
<>
{any && noStart && quiet(t('tours.walk.hours.noStart'))}
{hoursShown && <TourNotice icon={<Clock size={13} strokeWidth={2} />}>{t('tours.walk.hours.source')}</TourNotice>}
{any && fromDay && quiet(t('tours.walk.visit.fromDay'))}
</>
);
}
/** CW-04: what the stations are checked against: the day, its start and the route. */
export interface WalkDay {
/** `YYYY-MM-DD`; null on a day without a date. */
date: string | null;
/** `HH:MM` the walk starts that day; null when none is set. */
start: string | null;
route: ReadonlyArray<readonly [number, number]>;
walkingMinutes: number | null;
}
/**
* CW-03: a city walk's stations in its detail, in order, each with its stay,
* importance and protection; opened from a day, each can be marked visited or
@@ -91,12 +227,15 @@ export function WalkStationsCard({
tripId,
assignmentId,
canMark,
day = null,
}: {
tour: TourListItem;
tripId: number | string;
/** The day the dialog was opened from; without one there is nothing to check off. */
assignmentId: number | null;
canMark: boolean;
/** CW-04: the day it was opened from, for arrivals and opening hours. */
day?: WalkDay | null;
}) {
const { t } = useTranslation();
const toast = useToast();
@@ -108,9 +247,11 @@ export function WalkStationsCard({
if (assignmentId === null) return;
mark(tripId, assignmentId, key, status).catch(() => toast.error(t('tours.walk.visit.failed')));
};
const visited = stations
? stations.filter((station) => station.key && visits?.[station.key] === 'visited').length
: 0;
const start = minutesOf(day?.start);
const arrivals = stations && day?.date ? stationArrivals(stations, day.route, start, day.walkingMinutes) : null;
const anyHours = stations?.some((station) => station.openingHours) === true;
const visited = (stations ?? []).filter((station) => station.key && visits?.[station.key] === 'visited').length;
const canCheck = canMark && assignmentId !== null;
return (
<DialogSection
className="flex-none"
@@ -122,57 +263,25 @@ export function WalkStationsCard({
}
>
<div data-testid="walk-stations" className="flex flex-col gap-2">
{stations === null && (
<span className="text-content-muted" style={fs(11.5, 'body')}>
{t('common.loading')}
</span>
)}
{assignmentId === null && stations && stations.length > 0 && (
<span className="text-content-faint" style={fs(11, 'body')}>
{t('tours.walk.visit.fromDay')}
</span>
)}
<WalkStationsNotes
loading={stations === null}
any={(stations?.length ?? 0) > 0}
noStart={!!day && start === null}
hoursShown={!!arrivals && anyHours}
fromDay={assignmentId === null}
/>
<ol className="m-0 flex list-none flex-col gap-1.5 p-0">
{(stations ?? []).map((station, index) => {
const status = station.key ? visits?.[station.key] : undefined;
return (
<li
key={station.id}
data-testid="walk-station"
className={`flex items-start gap-2.5 rounded-[10px] px-1.5 py-1.5 ${status ? 'opacity-70' : ''}`}
>
<span
className="grid h-6 w-6 flex-none place-items-center rounded-full bg-surface-tertiary font-geist tabular-nums text-content-secondary"
style={fs(11)}
>
{status === 'visited' ? (
<Check size={12} strokeWidth={2.4} />
) : status === 'skipped' ? (
<Redo2 size={12} strokeWidth={2} />
) : (
index + 1
)}
</span>
<span className="flex min-w-0 flex-1 flex-col gap-1">
<span
className={`truncate font-semibold text-content ${status === 'skipped' ? 'line-through' : ''}`}
style={fs(12.5, 'body')}
>
{station.name ?? t('tours.walk.stationNumbered', { number: index + 1 })}
</span>
<StationPills point={station} />
{status && (
<span className="text-content-faint" style={fs(11, 'body')}>
{t(status === 'visited' ? 'tours.walk.visit.isVisited' : 'tours.walk.visit.isSkipped')}
</span>
)}
</span>
{canMark && assignmentId !== null && station.key && (
<StatusActions status={status} onMark={(next) => onMark(station.key ?? '', next)} />
)}
</li>
);
})}
{(stations ?? []).map((station, index) => (
<StationRow
key={station.id}
station={station}
index={index}
status={station.key ? visits?.[station.key] : undefined}
arrival={arrivals?.[index] ?? null}
date={day?.date ?? null}
onMark={canCheck && station.key ? (next) => onMark(station.key ?? '', next) : null}
/>
))}
</ol>
</div>
</DialogSection>
@@ -0,0 +1,146 @@
import type { TourPlannerWaypoint } from '../planner/tourPlannerWaypoints';
/**
* CW-04: a city walk's stations against their opening hours on one day. The
* arrival is an estimate: the start of that day's walk, the walking time
* shared out along the route by distance, and the stays before. Hours are
* read only in OpenStreetMap's plain forms ("Mo-Fr 09:00-18:00; Sa 10:00-14:00;
* Su off", "24/7"); anything else stays unread rather than guessed, and a
* station without hours is unknown, never open.
*/
const DAYS = ['Mo', 'Tu', 'We', 'Th', 'Fr', 'Sa', 'Su'] as const;
/** Minutes after midnight of a `HH:MM` time; null for anything else. */
export function minutesOf(time: string | null | undefined): number | null {
const match = /^(\d{1,2}):(\d{2})/.exec(time ?? '');
if (!match) return null;
const hours = Number(match[1]);
const minutes = Number(match[2]);
return hours < 24 && minutes < 60 ? hours * 60 + minutes : null;
}
export function clockOf(minutes: number): string {
const day = ((Math.round(minutes) % 1440) + 1440) % 1440;
return `${String(Math.floor(day / 60)).padStart(2, '0')}:${String(day % 60).padStart(2, '0')}`;
}
/** Monday 0 to Sunday 6, of a `YYYY-MM-DD` date read as a calendar day. */
function weekdayOf(date: string): number | null {
const match = /^(\d{4})-(\d{2})-(\d{2})$/.exec(date);
if (!match) return null;
const day = new Date(Date.UTC(Number(match[1]), Number(match[2]) - 1, Number(match[3]))).getUTCDay();
return (day + 6) % 7;
}
function daysOf(spec: string): Set<number> | null {
const days = new Set<number>();
for (const part of spec.split(',')) {
const [from, to] = part.split('-');
const start = DAYS.indexOf(from as (typeof DAYS)[number]);
const end = to === undefined ? start : DAYS.indexOf(to as (typeof DAYS)[number]);
if (start < 0 || end < 0) return null;
for (let day = start; ; day = (day + 1) % 7) {
days.add(day);
if (day === end) break;
}
}
return days;
}
/** The windows a place is open on a date, in minutes; null where the hours cannot be read. */
export function openWindows(hours: string, date: string): Array<[number, number]> | null {
const weekday = weekdayOf(date);
if (weekday === null) return null;
const text = hours.trim();
if (text === '24/7') return [[0, 1440]];
let windows: Array<[number, number]> = [];
for (const raw of text.split(';')) {
const rule = raw.trim();
if (!rule) continue;
const match = /^([A-Za-z,-]+)\s+(.+)$/.exec(rule);
if (!match) return null;
const days = daysOf(match[1]);
if (!days) return null;
if (!days.has(weekday)) continue;
// A later rule for the day replaces an earlier one, as OpenStreetMap reads them.
if (match[2].trim() === 'off' || match[2].trim() === 'closed') {
windows = [];
continue;
}
const ranges: Array<[number, number]> = [];
for (const span of match[2].split(',')) {
const [open, close] = span.trim().split('-');
const from = minutesOf(open);
const to = close === '24:00' ? 1440 : minutesOf(close);
if (from === null || to === null || to <= from) return null;
ranges.push([from, to]);
}
windows = ranges;
}
return windows;
}
export type StationFit =
| { kind: 'unknown' }
| { kind: 'unread'; hours: string }
| { kind: 'ok' }
| { kind: 'closed'; arrival: number }
| { kind: 'closes'; arrival: number; leave: number; closes: number };
/** Whether a station is open from its arrival to the end of its stay on that date. */
export function stationFit(hours: string | null | undefined, date: string, arrival: number, stay: number): StationFit {
if (!hours) return { kind: 'unknown' };
const windows = openWindows(hours, date);
if (windows === null) return { kind: 'unread', hours };
const leave = arrival + stay;
const open = windows.find(([from, to]) => arrival >= from && arrival < to);
if (!open) return { kind: 'closed', arrival };
if (leave > open[1]) return { kind: 'closes', arrival, leave, closes: open[1] };
return { kind: 'ok' };
}
/** Metres between two points, close enough along a city walk. */
function metres(a: readonly [number, number], b: readonly [number, number]): number {
const rad = Math.PI / 180;
const x = (b[1] - a[1]) * rad * Math.cos(((a[0] + b[0]) / 2) * rad);
const y = (b[0] - a[0]) * rad;
return Math.hypot(x, y) * 6_371_000;
}
/**
* The estimated arrival at each station, in minutes after midnight: the start,
* the walking time shared out by how far along the route the station lies,
* and the stays before it; a stay not known counts as nothing before it and
* makes every later arrival a lower bound. Null when the start, the walking
* time or the route is not known.
*/
export function stationArrivals(
stations: readonly TourPlannerWaypoint[],
route: ReadonlyArray<readonly [number, number]>,
start: number | null,
walkingMinutes: number | null
): Array<{ arrival: number; atLeast: boolean }> | null {
if (start === null || walkingMinutes === null || route.length < 2) return null;
const along: number[] = [0];
for (let i = 1; i < route.length; i += 1) along.push(along[i - 1] + metres(route[i - 1], route[i]));
const total = along[along.length - 1] || 1;
let stays = 0;
let unknown = false;
return stations.map((station) => {
let nearest = 0;
let best = Infinity;
route.forEach((point, index) => {
const gap = metres(point, [station.lat, station.lng]);
if (gap < best) {
best = gap;
nearest = index;
}
});
const arrival = start + walkingMinutes * (along[nearest] / total) + stays;
const result = { arrival, atLeast: unknown };
if (station.stayMinutes == null) unknown = true;
else stays += station.stayMinutes;
return result;
});
}
+11 -1
View File
@@ -119,7 +119,7 @@ const tours: TranslationStrings = {
'tours.planner.discardChanges': 'تجاهل التغييرات',
'tours.planner.newTourConfirmTitle': 'هل تريد بدء جولة جديدة؟',
'tours.planner.newTourConfirmBody': 'ستُحذف تغييراتك غير المحفوظة على هذه الجولة. لن تتغيّر الجولات المحفوظة.',
'tours.planner.newTourConfirm': 'تجاهل التغييرات وبدء جولة جديدة',
'tours.planner.newTourConfirm': 'تجاهل وابدأ',
'tours.planner.collapseElevation': 'طيّ مخطط الارتفاع',
'tours.planner.expandElevation': 'توسيع مخطط الارتفاع',
'tours.planner.name': 'اسم الجولة',
@@ -520,6 +520,16 @@ const tours: TranslationStrings = {
'tours.walk.themeHint.mixed': 'قليل من كل شيء: معالم ومتاحف وحدائق وإطلالات ومقاهٍ وأسواق.',
'tours.walk.themeHint.own': 'اختيارك الخاص من الفئات أدناه؛ يضبطها الموضوع لك.',
'tours.walk.themeReason': 'مقترح لموضوع {theme}',
'tours.walk.arrival': 'الوصول نحو {time}',
'tours.walk.arrivalAtLeast': 'الوصول في {time} على أقرب تقدير',
'tours.walk.hours.closed': 'مغلق في {arrival} حسب ساعات العمل',
'tours.walk.hours.closes': 'الوصول {arrival}، البقاء حتى {leave}، لكنه يغلق في {closes}',
'tours.walk.hours.unread': 'لم تُقرأ ساعات العمل هنا: {text}',
'tours.walk.hours.source':
'ساعات العمل كما في OpenStreetMap مقابل وقت الوصول التقديري؛ تحقق قبل الذهاب. المحطة بلا ساعات غير معروفة، لا مفتوحة.',
'tours.walk.hours.noStart': 'حدد وقتًا للجولة في هذا اليوم لرؤية أوقات الوصول وساعات العمل.',
'tours.poi.categoriesNone': 'لم تُختر أي فئة',
'tours.poi.categoriesMore': '{first} +{more}',
'tours.planner.tourType': 'نوع الجولة',
'tours.paddle.boat': 'القارب',
'tours.paddle.boat.kayak': 'كاياك',
+11 -1
View File
@@ -106,7 +106,7 @@ const tours: TranslationStrings = {
'tours.planner.newTourConfirmTitle': 'Yeni tur başlansın?',
'tours.planner.newTourConfirmBody':
'Bu turda saxlanılmamış dəyişiklikləriniz ləğv ediləcək. Saxlanılmış turlar dəyişməyəcək.',
'tours.planner.newTourConfirm': 'Dəyişiklikləri ləğv et və yeni tur başlat',
'tours.planner.newTourConfirm': 'Ləğv et və başla',
'tours.planner.collapseElevation': 'Yüksəklik profilini yığ',
'tours.planner.expandElevation': 'Yüksəklik profilini aç',
'tours.planner.name': 'Turun adı',
@@ -516,6 +516,16 @@ const tours: TranslationStrings = {
'Hər şeydən bir az: görməli yerlər, muzeylər, parklar, mənzərələr, kafelər və bazarlar.',
'tours.walk.themeHint.own': 'Aşağıda öz kateqoriya seçimin; mövzu onları sənin üçün qurur.',
'tours.walk.themeReason': '{theme} üçün təklif olunur',
'tours.walk.arrival': 'Təxmini çatma {time}',
'tours.walk.arrivalAtLeast': 'Ən tez {time} çatma',
'tours.walk.hours.closed': 'Açılış saatlarına görə {arrival} vaxtı bağlıdır',
'tours.walk.hours.closes': 'Çatma {arrival}, qalma {leave} qədər, amma {closes} vaxtı bağlanır',
'tours.walk.hours.unread': 'Açılış saatları burada oxunmadı: {text}',
'tours.walk.hours.source':
'Açılış saatları OpenStreetMap-dakı kimi, təxmini çatma vaxtı ilə; getməzdən əvvəl yoxla. Saatı olmayan dayanacaq məlum deyil, açıq deyil.',
'tours.walk.hours.noStart': 'Çatma vaxtlarını və açılış saatlarını görmək üçün gəzintiyə bu gün vaxt ver.',
'tours.poi.categoriesNone': 'Kateqoriya seçilməyib',
'tours.poi.categoriesMore': '{first} +{more}',
'tours.planner.tourType': 'Tur növü',
'tours.paddle.boat': 'Qayıq',
'tours.paddle.boat.kayak': 'Kayak', // same-as-en
+11 -1
View File
@@ -110,7 +110,7 @@ const tours: TranslationStrings = {
'tours.planner.newTourConfirmTitle': 'Começar um novo passeio?',
'tours.planner.newTourConfirmBody':
'As alterações não salvas deste passeio serão descartadas. Os passeios salvos não serão alterados.',
'tours.planner.newTourConfirm': 'Descartar e começar novo passeio',
'tours.planner.newTourConfirm': 'Descartar e começar',
'tours.planner.collapseElevation': 'Recolher perfil de elevação',
'tours.planner.expandElevation': 'Expandir perfil de elevação',
'tours.planner.name': 'Nome do passeio',
@@ -521,6 +521,16 @@ const tours: TranslationStrings = {
'tours.walk.themeHint.mixed': 'Um pouco de cada: pontos turísticos, museus, parques, vistas, cafeterias e feiras.',
'tours.walk.themeHint.own': 'Sua própria seleção de categorias abaixo; um tema as define para você.',
'tours.walk.themeReason': 'Sugerido para {theme}',
'tours.walk.arrival': 'Chegada por volta de {time}',
'tours.walk.arrivalAtLeast': 'Chegada a partir de {time}',
'tours.walk.hours.closed': 'Fechado às {arrival} segundo o horário',
'tours.walk.hours.closes': 'Chegada {arrival}, permanência até {leave}, mas fecha às {closes}',
'tours.walk.hours.unread': 'Horário não lido aqui: {text}',
'tours.walk.hours.source':
'Horário como está no OpenStreetMap, comparado à chegada estimada; confira antes de ir. Uma parada sem horário é desconhecida, não aberta.',
'tours.walk.hours.noStart': 'Dê ao passeio um horário neste dia para ver chegadas e horários.',
'tours.poi.categoriesNone': 'Nenhuma categoria escolhida',
'tours.poi.categoriesMore': '{first} +{more}',
'tours.planner.tourType': 'Tipo de passeio',
'tours.paddle.boat': 'Embarcação',
'tours.paddle.boat.kayak': 'Caiaque',
+11 -1
View File
@@ -110,7 +110,7 @@ const tours: TranslationStrings = {
'tours.planner.newTourConfirmTitle': 'Vols començar una nova excursió?',
'tours.planner.newTourConfirmBody':
"Es descartaran els canvis sense desar d'aquesta excursió. Les excursions desades no es modificaran.",
'tours.planner.newTourConfirm': 'Descarta i comença una nova excursió',
'tours.planner.newTourConfirm': 'Descarta i comença',
'tours.planner.collapseElevation': "Replega el perfil d'altitud",
'tours.planner.expandElevation': "Desplega el perfil d'altitud",
'tours.planner.name': "Nom de l'excursió",
@@ -522,6 +522,16 @@ const tours: TranslationStrings = {
'tours.walk.themeHint.mixed': "Una mica de cada: llocs d'interès, museus, parcs, vistes, cafeteries i mercats.",
'tours.walk.themeHint.own': 'La teva pròpia tria de categories a sota; un tema te les defineix.',
'tours.walk.themeReason': 'Suggerit per a {theme}',
'tours.walk.arrival': 'Arribada cap a les {time}',
'tours.walk.arrivalAtLeast': 'Arribada com a aviat a les {time}',
'tours.walk.hours.closed': "Tancat a les {arrival} segons l'horari",
'tours.walk.hours.closes': 'Arribada {arrival}, estada fins a les {leave}, però tanca a les {closes}',
'tours.walk.hours.unread': 'Horari no llegit aquí: {text}',
'tours.walk.hours.source':
"Horari tal com el té OpenStreetMap, contra l'arribada estimada; comprova-ho abans d'anar-hi. Una parada sense horari és desconeguda, no oberta.",
'tours.walk.hours.noStart': 'Posa una hora a la ruta aquest dia per veure arribades i horaris.',
'tours.poi.categoriesNone': 'Cap categoria triada',
'tours.poi.categoriesMore': '{first} +{more}',
'tours.planner.tourType': 'Tipus de ruta',
'tours.paddle.boat': 'Embarcació',
'tours.paddle.boat.kayak': 'Caiac',
+11 -1
View File
@@ -109,7 +109,7 @@ const tours: TranslationStrings = {
'tours.planner.discardChanges': 'Zahodit změny',
'tours.planner.newTourConfirmTitle': 'Začít nový výlet?',
'tours.planner.newTourConfirmBody': 'Neuložené změny tohoto výletu budou zahozeny. Uložené výlety se nezmění.',
'tours.planner.newTourConfirm': 'Zahodit změny a začít nový výlet',
'tours.planner.newTourConfirm': 'Zahodit a začít',
'tours.planner.collapseElevation': 'Sbalit výškový profil',
'tours.planner.expandElevation': 'Rozbalit výškový profil',
'tours.planner.name': 'Název výletu',
@@ -512,6 +512,16 @@ const tours: TranslationStrings = {
'tours.walk.themeHint.mixed': 'Od všeho trochu: památky, muzea, parky, výhledy, kavárny a trhy.',
'tours.walk.themeHint.own': 'Tvůj vlastní výběr kategorií níže; téma je nastaví za tebe.',
'tours.walk.themeReason': 'Navrženo pro {theme}',
'tours.walk.arrival': 'Příchod kolem {time}',
'tours.walk.arrivalAtLeast': 'Příchod nejdříve {time}',
'tours.walk.hours.closed': 'V {arrival} podle otevírací doby zavřeno',
'tours.walk.hours.closes': 'Příchod {arrival}, pobyt do {leave}, ale zavírá v {closes}',
'tours.walk.hours.unread': 'Otevírací dobu zde nelze vyhodnotit: {text}',
'tours.walk.hours.source':
'Otevírací doba podle OpenStreetMap proti odhadovanému příchodu; před cestou si ji ověř. Zastávka bez časů je neznámá, ne otevřená.',
'tours.walk.hours.noStart': 'Dej procházce v tento den čas, abys viděl příchody a otevírací doby.',
'tours.poi.categoriesNone': 'Žádná kategorie',
'tours.poi.categoriesMore': '{first} +{more}',
'tours.planner.tourType': 'Typ túry',
'tours.paddle.boat': 'Loď',
'tours.paddle.boat.kayak': 'Kajak',
+11 -1
View File
@@ -111,7 +111,7 @@ const tours: TranslationStrings = {
'tours.planner.newTourConfirmTitle': 'Neue Tour beginnen?',
'tours.planner.newTourConfirmBody':
'Ungespeicherte Änderungen an dieser Tour gehen verloren. Gespeicherte Touren bleiben erhalten.',
'tours.planner.newTourConfirm': 'Änderungen verwerfen und neue Tour beginnen',
'tours.planner.newTourConfirm': 'Verwerfen und neu',
'tours.planner.collapseElevation': 'Höhenprofil einklappen',
'tours.planner.expandElevation': 'Höhenprofil ausklappen',
'tours.planner.name': 'Name der Tour',
@@ -527,6 +527,16 @@ const tours: TranslationStrings = {
'tours.walk.themeHint.mixed': 'Von allem etwas: Sehenswürdigkeiten, Museen, Parks, Aussicht, Cafés und Märkte.',
'tours.walk.themeHint.own': 'Deine eigene Auswahl darunter; ein Thema stellt sie für dich zusammen.',
'tours.walk.themeReason': 'Vorgeschlagen für {theme}',
'tours.walk.arrival': 'Ankunft ca. {time}',
'tours.walk.arrivalAtLeast': 'Ankunft frühestens {time}',
'tours.walk.hours.closed': 'Um {arrival} laut Öffnungszeiten geschlossen',
'tours.walk.hours.closes': 'Ankunft {arrival}, Aufenthalt bis {leave}, aber Schließung um {closes}',
'tours.walk.hours.unread': 'Öffnungszeiten hier nicht auswertbar: {text}',
'tours.walk.hours.source':
'Öffnungszeiten wie bei OpenStreetMap eingetragen, gegen die geschätzte Ankunft; vor Ort lieber prüfen. Eine Station ohne Zeiten ist unbekannt, nicht geöffnet.',
'tours.walk.hours.noStart': 'Gib dem Walk an diesem Tag eine Uhrzeit, um Ankünfte und Öffnungszeiten zu sehen.',
'tours.poi.categoriesNone': 'Keine Kategorie gewählt',
'tours.poi.categoriesMore': '{first} +{more}',
'tours.planner.tourType': 'Tourtyp',
'tours.paddle.boat': 'Boot',
'tours.paddle.boat.kayak': 'Kajak',
+11 -1
View File
@@ -106,7 +106,7 @@ const tours: TranslationStrings = {
'tours.planner.newTourConfirmTitle': 'Start a new tour?',
'tours.planner.newTourConfirmBody':
'Your unsaved changes to this tour will be discarded. Saved tours will not be changed.',
'tours.planner.newTourConfirm': 'Discard changes and start new tour',
'tours.planner.newTourConfirm': 'Discard and start',
'tours.planner.collapseElevation': 'Collapse elevation profile',
'tours.planner.expandElevation': 'Expand elevation profile',
'tours.planner.name': 'Tour name',
@@ -513,6 +513,16 @@ const tours: TranslationStrings = {
'tours.walk.themeHint.mixed': 'Some of each: sights, museums, parks, views, coffee shops and markets.',
'tours.walk.themeHint.own': 'Your own pick of categories below; a theme sets them for you.',
'tours.walk.themeReason': 'Suggested for {theme}',
'tours.walk.arrival': 'Arrival about {time}',
'tours.walk.arrivalAtLeast': 'Arrival {time} at the earliest',
'tours.walk.hours.closed': 'Closed at {arrival} by its opening hours',
'tours.walk.hours.closes': 'Arrival {arrival}, stay until {leave}, but it closes at {closes}',
'tours.walk.hours.unread': 'Opening hours not read here: {text}',
'tours.walk.hours.source':
'Opening hours as OpenStreetMap has them, against the estimated arrival; check before you go. A stop without hours is unknown, not open.',
'tours.walk.hours.noStart': 'Give the walk a time on this day to see arrivals and opening hours.',
'tours.poi.categoriesNone': 'No category chosen',
'tours.poi.categoriesMore': '{first} +{more}',
'tours.planner.tourType': 'Tour type',
'tours.paddle.boat': 'Boat',
'tours.paddle.boat.kayak': 'Kayak',
+11 -1
View File
@@ -111,7 +111,7 @@ const tours: TranslationStrings = {
'tours.planner.newTourConfirmTitle': '¿Empezar una nueva excursión?',
'tours.planner.newTourConfirmBody':
'Se descartarán los cambios sin guardar de esta excursión. Las excursiones guardadas no se modificarán.',
'tours.planner.newTourConfirm': 'Descartar y empezar nueva excursión',
'tours.planner.newTourConfirm': 'Descartar y empezar',
'tours.planner.collapseElevation': 'Contraer perfil de altitud',
'tours.planner.expandElevation': 'Expandir perfil de altitud',
'tours.planner.name': 'Nombre de la excursión',
@@ -520,6 +520,16 @@ const tours: TranslationStrings = {
'tours.walk.themeHint.mixed': 'Un poco de cada: lugares de interés, museos, parques, vistas, cafeterías y mercados.',
'tours.walk.themeHint.own': 'Tu propia selección de categorías abajo; un tema las elige por ti.',
'tours.walk.themeReason': 'Sugerido para {theme}',
'tours.walk.arrival': 'Llegada hacia las {time}',
'tours.walk.arrivalAtLeast': 'Llegada no antes de las {time}',
'tours.walk.hours.closed': 'Cerrado a las {arrival} según su horario',
'tours.walk.hours.closes': 'Llegada {arrival}, estancia hasta las {leave}, pero cierra a las {closes}',
'tours.walk.hours.unread': 'Horario no interpretado aquí: {text}',
'tours.walk.hours.source':
'Horario tal como figura en OpenStreetMap, frente a la llegada estimada; compruébalo antes de ir. Una parada sin horario es desconocida, no abierta.',
'tours.walk.hours.noStart': 'Pon al paseo una hora en este día para ver llegadas y horarios.',
'tours.poi.categoriesNone': 'Ninguna categoría elegida',
'tours.poi.categoriesMore': '{first} +{more}',
'tours.planner.tourType': 'Tipo de ruta',
'tours.paddle.boat': 'Embarcación',
'tours.paddle.boat.kayak': 'Kayak', // same-as-en
+11 -1
View File
@@ -107,7 +107,7 @@ const tours: TranslationStrings = {
'tours.planner.newTourConfirmTitle': 'Kas alustada uut retke?',
'tours.planner.newTourConfirmBody':
'Selle retke salvestamata muudatused lähevad kaotsi. Salvestatud retki ei muudeta.',
'tours.planner.newTourConfirm': 'Loobu muudatustest ja alusta uut retke',
'tours.planner.newTourConfirm': 'Loobu ja alusta',
'tours.planner.collapseElevation': 'Ahenda kõrgusprofiil',
'tours.planner.expandElevation': 'Laienda kõrgusprofiil',
'tours.planner.name': 'Retke nimi',
@@ -515,6 +515,16 @@ const tours: TranslationStrings = {
'tours.walk.themeHint.mixed': 'Igaühest natuke: vaatamisväärsused, muuseumid, pargid, vaated, kohvikud ja turud.',
'tours.walk.themeHint.own': 'Sinu enda kategooriate valik allpool; teema seab need sinu eest.',
'tours.walk.themeReason': 'Soovitatud teemale {theme}',
'tours.walk.arrival': 'Saabumine umbes {time}',
'tours.walk.arrivalAtLeast': 'Saabumine kõige varem {time}',
'tours.walk.hours.closed': 'Lahtiolekuaegade järgi kell {arrival} suletud',
'tours.walk.hours.closes': 'Saabumine {arrival}, peatus kuni {leave}, aga suletakse kell {closes}',
'tours.walk.hours.unread': 'Lahtiolekuaegu siin ei loetud: {text}',
'tours.walk.hours.source':
'Lahtiolekuajad nagu OpenStreetMapis, võrreldes hinnangulise saabumisega; kontrolli enne minekut. Aegadeta peatus on teadmata, mitte avatud.',
'tours.walk.hours.noStart': 'Anna jalutusele sel päeval kellaaeg, et näha saabumisi ja lahtiolekuaegu.',
'tours.poi.categoriesNone': 'Kategooriat pole valitud',
'tours.poi.categoriesMore': '{first} +{more}',
'tours.planner.tourType': 'Matka tüüp',
'tours.paddle.boat': 'Paat',
'tours.paddle.boat.kayak': 'Süst',
+11 -1
View File
@@ -113,7 +113,7 @@ const tours: TranslationStrings = {
'tours.planner.newTourConfirmTitle': 'Commencer une nouvelle sortie ?',
'tours.planner.newTourConfirmBody':
'Les modifications non enregistrées de cette sortie seront perdues. Les sorties enregistrées resteront inchangées.',
'tours.planner.newTourConfirm': 'Abandonner et commencer une nouvelle sortie',
'tours.planner.newTourConfirm': 'Abandonner et commencer',
'tours.planner.collapseElevation': 'Réduire le profil altimétrique',
'tours.planner.expandElevation': 'Déployer le profil altimétrique',
'tours.planner.name': 'Nom de la sortie',
@@ -530,6 +530,16 @@ const tours: TranslationStrings = {
'tours.walk.themeHint.mixed': 'Un peu de chaque : sites, musées, parcs, points de vue, cafés et marchés.',
'tours.walk.themeHint.own': 'Ta propre sélection de catégories ci-dessous ; un thème les choisit pour toi.',
'tours.walk.themeReason': 'Suggéré pour {theme}',
'tours.walk.arrival': 'Arrivée vers {time}',
'tours.walk.arrivalAtLeast': 'Arrivée au plus tôt à {time}',
'tours.walk.hours.closed': 'Fermé à {arrival} selon ses horaires',
'tours.walk.hours.closes': "Arrivée {arrival}, arrêt jusqu'à {leave}, mais fermeture à {closes}",
'tours.walk.hours.unread': 'Horaires non lus ici : {text}',
'tours.walk.hours.source':
"Horaires tels qu'indiqués sur OpenStreetMap, face à l'arrivée estimée ; vérifie avant d'y aller. Une étape sans horaires est inconnue, pas ouverte.",
'tours.walk.hours.noStart': 'Donne une heure à la balade ce jour-là pour voir les arrivées et les horaires.',
'tours.poi.categoriesNone': 'Aucune catégorie choisie',
'tours.poi.categoriesMore': '{first} +{more}',
'tours.planner.tourType': 'Type de sortie',
'tours.paddle.boat': 'Embarcation',
'tours.paddle.boat.kayak': 'Kayak', // same-as-en
+11 -1
View File
@@ -111,7 +111,7 @@ const tours: TranslationStrings = {
'tours.planner.newTourConfirmTitle': 'Έναρξη νέας εκδρομής;',
'tours.planner.newTourConfirmBody':
'Οι μη αποθηκευμένες αλλαγές αυτής της εκδρομής θα απορριφθούν. Οι αποθηκευμένες εκδρομές δεν θα αλλάξουν.',
'tours.planner.newTourConfirm': 'Απόρριψη αλλαγών και έναρξη νέας εκδρομής',
'tours.planner.newTourConfirm': 'Απόρριψη και έναρξη',
'tours.planner.collapseElevation': 'Σύμπτυξη υψομετρικού προφίλ',
'tours.planner.expandElevation': 'Ανάπτυξη υψομετρικού προφίλ',
'tours.planner.name': 'Όνομα εκδρομής',
@@ -530,6 +530,16 @@ const tours: TranslationStrings = {
'tours.walk.themeHint.mixed': "Λίγο απ' όλα: αξιοθέατα, μουσεία, πάρκα, θέα, καφετέριες και αγορές.",
'tours.walk.themeHint.own': 'Η δική σου επιλογή κατηγοριών παρακάτω· ένα θέμα τις ορίζει για σένα.',
'tours.walk.themeReason': 'Προτείνεται για {theme}',
'tours.walk.arrival': 'Άφιξη περίπου {time}',
'tours.walk.arrivalAtLeast': 'Άφιξη το νωρίτερο {time}',
'tours.walk.hours.closed': 'Κλειστό στις {arrival} σύμφωνα με το ωράριο',
'tours.walk.hours.closes': 'Άφιξη {arrival}, παραμονή έως {leave}, αλλά κλείνει στις {closes}',
'tours.walk.hours.unread': 'Το ωράριο δεν διαβάστηκε εδώ: {text}',
'tours.walk.hours.source':
'Ωράριο όπως στο OpenStreetMap, σε σχέση με την εκτιμώμενη άφιξη· έλεγξε πριν πας. Στάση χωρίς ωράριο είναι άγνωστη, όχι ανοιχτή.',
'tours.walk.hours.noStart': 'Δώσε ώρα στη βόλτα αυτή τη μέρα για να δεις αφίξεις και ωράρια.',
'tours.poi.categoriesNone': 'Δεν επιλέχθηκε κατηγορία',
'tours.poi.categoriesMore': '{first} +{more}',
'tours.planner.tourType': 'Τύπος διαδρομής',
'tours.paddle.boat': 'Σκάφος',
'tours.paddle.boat.kayak': 'Καγιάκ',
+11 -1
View File
@@ -107,7 +107,7 @@ const tours: TranslationStrings = {
'tours.planner.discardChanges': 'Módosítások elvetése',
'tours.planner.newTourConfirmTitle': 'Új túrát kezdesz?',
'tours.planner.newTourConfirmBody': 'A túra nem mentett módosításai elvesznek. A mentett túrák nem változnak.',
'tours.planner.newTourConfirm': 'Módosítások elvetése és új túra kezdése',
'tours.planner.newTourConfirm': 'Elvetés és új',
'tours.planner.collapseElevation': 'Magassági profil összecsukása',
'tours.planner.expandElevation': 'Magassági profil kibontása',
'tours.planner.name': 'Túra neve',
@@ -522,6 +522,16 @@ const tours: TranslationStrings = {
'tours.walk.themeHint.mixed': 'Mindenből egy kicsit: látnivalók, múzeumok, parkok, kilátás, kávézók és piacok.',
'tours.walk.themeHint.own': 'Saját kategóriaválasztásod lent; egy téma beállítja helyetted.',
'tours.walk.themeReason': 'Javasolva: {theme}',
'tours.walk.arrival': 'Érkezés kb. {time}',
'tours.walk.arrivalAtLeast': 'Érkezés legkorábban {time}',
'tours.walk.hours.closed': '{arrival} órakor a nyitvatartás szerint zárva',
'tours.walk.hours.closes': 'Érkezés {arrival}, tartózkodás {leave}-ig, de {closes}-kor zár',
'tours.walk.hours.unread': 'A nyitvatartás itt nem értelmezhető: {text}',
'tours.walk.hours.source':
'Nyitvatartás az OpenStreetMap szerint, a becsült érkezéshez mérve; indulás előtt ellenőrizd. Nyitvatartás nélküli megálló ismeretlen, nem nyitott.',
'tours.walk.hours.noStart': 'Adj időpontot a sétának ezen a napon, hogy lásd az érkezéseket és a nyitvatartást.',
'tours.poi.categoriesNone': 'Nincs kategória kiválasztva',
'tours.poi.categoriesMore': '{first} +{more}',
'tours.planner.tourType': 'Túratípus',
'tours.paddle.boat': 'Hajó',
'tours.paddle.boat.kayak': 'Kajak',
+11 -1
View File
@@ -105,7 +105,7 @@ const tours: TranslationStrings = {
'tours.planner.newTourConfirmTitle': 'Mulai tur baru?',
'tours.planner.newTourConfirmBody':
'Perubahan yang belum disimpan pada tur ini akan dibuang. Tur yang sudah disimpan tidak akan diubah.',
'tours.planner.newTourConfirm': 'Buang perubahan dan mulai tur baru',
'tours.planner.newTourConfirm': 'Buang dan mulai',
'tours.planner.collapseElevation': 'Ciutkan profil elevasi',
'tours.planner.expandElevation': 'Perluas profil elevasi',
'tours.planner.name': 'Nama tur',
@@ -515,6 +515,16 @@ const tours: TranslationStrings = {
'Sedikit dari semuanya: tempat wisata, museum, taman, pemandangan, kedai kopi, dan pasar.',
'tours.walk.themeHint.own': 'Pilihan kategorimu sendiri di bawah; tema mengaturnya untukmu.',
'tours.walk.themeReason': 'Disarankan untuk {theme}',
'tours.walk.arrival': 'Tiba sekitar {time}',
'tours.walk.arrivalAtLeast': 'Tiba paling cepat {time}',
'tours.walk.hours.closed': 'Tutup pada {arrival} menurut jam bukanya',
'tours.walk.hours.closes': 'Tiba {arrival}, singgah sampai {leave}, tetapi tutup pukul {closes}',
'tours.walk.hours.unread': 'Jam buka tidak terbaca di sini: {text}',
'tours.walk.hours.source':
'Jam buka sesuai OpenStreetMap, dibandingkan dengan perkiraan tiba; periksa sebelum berangkat. Titik tanpa jam buka tidak diketahui, bukan buka.',
'tours.walk.hours.noStart': 'Beri rute waktu mulai di hari ini untuk melihat waktu tiba dan jam buka.',
'tours.poi.categoriesNone': 'Belum ada kategori',
'tours.poi.categoriesMore': '{first} +{more}',
'tours.planner.tourType': 'Jenis tur',
'tours.paddle.boat': 'Perahu',
'tours.paddle.boat.kayak': 'Kayak', // same-as-en
+11 -1
View File
@@ -109,7 +109,7 @@ const tours: TranslationStrings = {
'tours.planner.newTourConfirmTitle': 'Iniziare una nuova escursione?',
'tours.planner.newTourConfirmBody':
'Le modifiche non salvate a questa escursione verranno scartate. Le escursioni salvate non verranno modificate.',
'tours.planner.newTourConfirm': 'Scarta e inizia nuova escursione',
'tours.planner.newTourConfirm': 'Scarta e inizia',
'tours.planner.collapseElevation': 'Comprimi profilo altimetrico',
'tours.planner.expandElevation': 'Espandi profilo altimetrico',
'tours.planner.name': "Nome dell'escursione",
@@ -521,6 +521,16 @@ const tours: TranslationStrings = {
'tours.walk.themeHint.mixed': "Un po' di tutto: attrazioni, musei, parchi, panorami, caffetterie e mercati.",
'tours.walk.themeHint.own': 'La tua scelta di categorie qui sotto; un tema le imposta per te.',
'tours.walk.themeReason': 'Suggerito per {theme}',
'tours.walk.arrival': 'Arrivo verso le {time}',
'tours.walk.arrivalAtLeast': 'Arrivo non prima delle {time}',
'tours.walk.hours.closed': 'Chiuso alle {arrival} secondo gli orari',
'tours.walk.hours.closes': 'Arrivo {arrival}, sosta fino alle {leave}, ma chiude alle {closes}',
'tours.walk.hours.unread': 'Orari non interpretati qui: {text}',
'tours.walk.hours.source':
"Orari come riportati su OpenStreetMap, rispetto all'arrivo stimato; verifica prima di andare. Una tappa senza orari è sconosciuta, non aperta.",
'tours.walk.hours.noStart': 'Dai un orario al percorso in questo giorno per vedere arrivi e orari.',
'tours.poi.categoriesNone': 'Nessuna categoria scelta',
'tours.poi.categoriesMore': '{first} +{more}',
'tours.planner.tourType': 'Tipo di tour',
'tours.paddle.boat': 'Imbarcazione',
'tours.paddle.boat.kayak': 'Kayak', // same-as-en
+11 -1
View File
@@ -103,7 +103,7 @@ const tours: TranslationStrings = {
'tours.planner.discardChanges': '変更を破棄',
'tours.planner.newTourConfirmTitle': '新しいツアーを開始しますか?',
'tours.planner.newTourConfirmBody': 'このツアーの未保存の変更は破棄されます。保存済みのツアーは変更されません。',
'tours.planner.newTourConfirm': '変更を破棄して新しいツアーを開始',
'tours.planner.newTourConfirm': '破棄して開始',
'tours.planner.collapseElevation': '標高グラフを折りたたむ',
'tours.planner.expandElevation': '標高グラフを展開',
'tours.planner.name': 'ツアー名',
@@ -507,6 +507,16 @@ const tours: TranslationStrings = {
'tours.walk.themeHint.mixed': '観光名所、美術館・博物館、公園、眺望、カフェ、市場を少しずつ。',
'tours.walk.themeHint.own': '下のカテゴリーは自分で選択中。テーマを選ぶと自動で設定されます。',
'tours.walk.themeReason': '{theme} のおすすめ',
'tours.walk.arrival': '到着 {time} ごろ',
'tours.walk.arrivalAtLeast': '到着は早くても {time}',
'tours.walk.hours.closed': '営業時間では {arrival} は閉まっています',
'tours.walk.hours.closes': '{arrival} 到着、{leave} まで滞在予定ですが {closes} に閉まります',
'tours.walk.hours.unread': '営業時間はここでは読み取れません:{text}',
'tours.walk.hours.source':
'OpenStreetMap の営業時間を推定到着時刻と照合しています。出かける前に確認してください。営業時間のない立ち寄り先は「不明」で、営業中とはみなしません。',
'tours.walk.hours.noStart': 'この日の開始時刻を設定すると、到着時刻と営業時間が表示されます。',
'tours.poi.categoriesNone': 'カテゴリー未選択',
'tours.poi.categoriesMore': '{first} +{more}',
'tours.planner.tourType': 'ツアーの種類',
'tours.paddle.boat': 'ボート',
'tours.paddle.boat.kayak': 'カヤック',
+11 -1
View File
@@ -101,7 +101,7 @@ const tours: TranslationStrings = {
'tours.planner.newTourConfirmTitle': '새 투어를 시작할까요?',
'tours.planner.newTourConfirmBody':
'이 투어의 저장하지 않은 변경 사항은 삭제됩니다. 저장된 투어는 변경되지 않습니다.',
'tours.planner.newTourConfirm': '변경 사항을 버리고 새 투어 시작',
'tours.planner.newTourConfirm': '버리고 시작',
'tours.planner.collapseElevation': '고도 그래프 접기',
'tours.planner.expandElevation': '고도 그래프 펼치기',
'tours.planner.name': '투어 이름',
@@ -502,6 +502,16 @@ const tours: TranslationStrings = {
'tours.walk.themeHint.mixed': '명소, 박물관, 공원, 전망, 카페, 시장을 골고루.',
'tours.walk.themeHint.own': '아래 카테고리는 직접 고른 것입니다. 테마를 고르면 자동으로 설정됩니다.',
'tours.walk.themeReason': '{theme} 추천',
'tours.walk.arrival': '도착 약 {time}',
'tours.walk.arrivalAtLeast': '도착 빨라야 {time}',
'tours.walk.hours.closed': '영업시간상 {arrival}에는 문을 닫습니다',
'tours.walk.hours.closes': '{arrival} 도착, {leave}까지 머물 예정이지만 {closes}에 문을 닫습니다',
'tours.walk.hours.unread': '여기서는 영업시간을 읽지 못했습니다: {text}',
'tours.walk.hours.source':
'OpenStreetMap의 영업시간을 예상 도착 시간과 비교합니다. 가기 전에 확인하세요. 영업시간이 없는 정류지는 영업 중이 아니라 미상입니다.',
'tours.walk.hours.noStart': '이날 산책에 시간을 지정하면 도착 시간과 영업시간을 볼 수 있습니다.',
'tours.poi.categoriesNone': '선택한 카테고리 없음',
'tours.poi.categoriesMore': '{first} +{more}',
'tours.planner.tourType': '투어 유형',
'tours.paddle.boat': '보트',
'tours.paddle.boat.kayak': '카약',
+11 -1
View File
@@ -111,7 +111,7 @@ const tours: TranslationStrings = {
'tours.planner.newTourConfirmTitle': 'Een nieuwe tocht starten?',
'tours.planner.newTourConfirmBody':
'Je niet-opgeslagen wijzigingen aan deze tocht worden verworpen. Opgeslagen tochten blijven ongewijzigd.',
'tours.planner.newTourConfirm': 'Verwerpen en nieuwe tocht starten',
'tours.planner.newTourConfirm': 'Weggooien en beginnen',
'tours.planner.collapseElevation': 'Hoogteprofiel inklappen',
'tours.planner.expandElevation': 'Hoogteprofiel uitklappen',
'tours.planner.name': 'Naam van de tocht',
@@ -525,6 +525,16 @@ const tours: TranslationStrings = {
'tours.walk.themeHint.mixed': 'Van alles wat: bezienswaardigheden, musea, parken, uitzicht, koffiezaken en markten.',
'tours.walk.themeHint.own': 'Je eigen keuze van categorieën hieronder; een thema stelt ze voor je in.',
'tours.walk.themeReason': 'Voorgesteld voor {theme}',
'tours.walk.arrival': 'Aankomst rond {time}',
'tours.walk.arrivalAtLeast': 'Aankomst op zijn vroegst {time}',
'tours.walk.hours.closed': 'Om {arrival} gesloten volgens de openingstijden',
'tours.walk.hours.closes': 'Aankomst {arrival}, verblijf tot {leave}, maar sluit om {closes}',
'tours.walk.hours.unread': 'Openingstijden hier niet gelezen: {text}',
'tours.walk.hours.source':
'Openingstijden zoals in OpenStreetMap, tegen de geschatte aankomst; controleer ze voor je gaat. Een halte zonder tijden is onbekend, niet open.',
'tours.walk.hours.noStart': 'Geef de wandeling op deze dag een tijd om aankomsten en openingstijden te zien.',
'tours.poi.categoriesNone': 'Geen categorie gekozen',
'tours.poi.categoriesMore': '{first} +{more}',
'tours.planner.tourType': 'Soort tocht',
'tours.paddle.boat': 'Boot',
'tours.paddle.boat.kayak': 'Kajak',
+11 -1
View File
@@ -117,7 +117,7 @@ const tours: TranslationStrings = {
'tours.planner.newTourConfirmTitle': 'Rozpocząć nową wycieczkę?',
'tours.planner.newTourConfirmBody':
'Niezapisane zmiany tej wycieczki zostaną odrzucone. Zapisane wycieczki nie zostaną zmienione.',
'tours.planner.newTourConfirm': 'Odrzuć zmiany i rozpocznij nową wycieczkę',
'tours.planner.newTourConfirm': 'Odrzuć i zacznij',
'tours.planner.collapseElevation': 'Zwiń profil wysokościowy',
'tours.planner.expandElevation': 'Rozwiń profil wysokościowy',
'tours.planner.name': 'Nazwa wycieczki',
@@ -529,6 +529,16 @@ const tours: TranslationStrings = {
'tours.walk.themeHint.mixed': 'Po trochu wszystkiego: atrakcje, muzea, parki, widoki, kawiarnie i targi.',
'tours.walk.themeHint.own': 'Twój własny wybór kategorii poniżej; motyw ustawi je za ciebie.',
'tours.walk.themeReason': 'Proponowane dla: {theme}',
'tours.walk.arrival': 'Przyjście około {time}',
'tours.walk.arrivalAtLeast': 'Przyjście najwcześniej {time}',
'tours.walk.hours.closed': 'Według godzin otwarcia o {arrival} zamknięte',
'tours.walk.hours.closes': 'Przyjście {arrival}, pobyt do {leave}, ale zamknięcie o {closes}',
'tours.walk.hours.unread': 'Godziny otwarcia nieodczytane: {text}',
'tours.walk.hours.source':
'Godziny otwarcia z OpenStreetMap wobec szacowanego przyjścia; sprawdź przed wyjściem. Przystanek bez godzin jest nieznany, nie otwarty.',
'tours.walk.hours.noStart': 'Ustaw godzinę trasy tego dnia, aby zobaczyć przyjścia i godziny otwarcia.',
'tours.poi.categoriesNone': 'Nie wybrano kategorii',
'tours.poi.categoriesMore': '{first} +{more}',
'tours.planner.tourType': 'Rodzaj wycieczki',
'tours.paddle.boat': 'Łódź',
'tours.paddle.boat.kayak': 'Kajak',
+11 -1
View File
@@ -118,7 +118,7 @@ const tours: TranslationStrings = {
'tours.planner.newTourConfirmTitle': 'Начать новый тур?',
'tours.planner.newTourConfirmBody':
'Несохранённые изменения этого тура будут отменены. Сохранённые туры не изменятся.',
'tours.planner.newTourConfirm': 'Отменить изменения и начать новый тур',
'tours.planner.newTourConfirm': 'Сбросить и начать',
'tours.planner.collapseElevation': 'Свернуть профиль высот',
'tours.planner.expandElevation': 'Развернуть профиль высот',
'tours.planner.name': 'Название тура',
@@ -532,6 +532,16 @@ const tours: TranslationStrings = {
'tours.walk.themeHint.mixed': 'Всего понемногу: достопримечательности, музеи, парки, виды, кафе и рынки.',
'tours.walk.themeHint.own': 'Твой собственный выбор категорий ниже; тема задаст их за тебя.',
'tours.walk.themeReason': 'Предложено для темы {theme}',
'tours.walk.arrival': 'Прибытие около {time}',
'tours.walk.arrivalAtLeast': 'Прибытие не раньше {time}',
'tours.walk.hours.closed': 'По часам работы в {arrival} закрыто',
'tours.walk.hours.closes': 'Прибытие {arrival}, остановка до {leave}, но закрывается в {closes}',
'tours.walk.hours.unread': 'Часы работы здесь не разобраны: {text}',
'tours.walk.hours.source':
'Часы работы как в OpenStreetMap, относительно расчётного прибытия; проверь перед выходом. Остановка без часов неизвестна, а не открыта.',
'tours.walk.hours.noStart': 'Задай прогулке время в этот день, чтобы видеть прибытия и часы работы.',
'tours.poi.categoriesNone': 'Категория не выбрана',
'tours.poi.categoriesMore': '{first} +{more}',
'tours.planner.tourType': 'Тип тура',
'tours.paddle.boat': 'Лодка',
'tours.paddle.boat.kayak': 'Каяк',
+11 -1
View File
@@ -110,7 +110,7 @@ const tours: TranslationStrings = {
'tours.planner.discardChanges': 'Zahodiť zmeny',
'tours.planner.newTourConfirmTitle': 'Začať novú túru?',
'tours.planner.newTourConfirmBody': 'Neuložené zmeny tejto túry sa zahodia. Uložené túry zostanú nezmenené.',
'tours.planner.newTourConfirm': 'Zahodiť zmeny a začať novú túru',
'tours.planner.newTourConfirm': 'Zahodiť a začať',
'tours.planner.collapseElevation': 'Zbaliť výškový profil',
'tours.planner.expandElevation': 'Rozbaliť výškový profil',
'tours.planner.name': 'Názov túry',
@@ -514,6 +514,16 @@ const tours: TranslationStrings = {
'tours.walk.themeHint.mixed': 'Od všetkého trochu: pamiatky, múzeá, parky, výhľady, kaviarne a trhy.',
'tours.walk.themeHint.own': 'Tvoj vlastný výber kategórií nižšie; téma ich nastaví za teba.',
'tours.walk.themeReason': 'Navrhnuté pre {theme}',
'tours.walk.arrival': 'Príchod okolo {time}',
'tours.walk.arrivalAtLeast': 'Príchod najskôr {time}',
'tours.walk.hours.closed': 'Podľa otváracích hodín o {arrival} zatvorené',
'tours.walk.hours.closes': 'Príchod {arrival}, pobyt do {leave}, ale zatvára o {closes}',
'tours.walk.hours.unread': 'Otváracie hodiny tu nemožno vyhodnotiť: {text}',
'tours.walk.hours.source':
'Otváracie hodiny podľa OpenStreetMap oproti odhadovanému príchodu; pred cestou si ich over. Zastávka bez časov je neznáma, nie otvorená.',
'tours.walk.hours.noStart': 'Daj prechádzke v tento deň čas, aby si videl príchody a otváracie hodiny.',
'tours.poi.categoriesNone': 'Žiadna kategória',
'tours.poi.categoriesMore': '{first} +{more}',
'tours.planner.tourType': 'Typ túry',
'tours.paddle.boat': 'Loď',
'tours.paddle.boat.kayak': 'Kajak',
+11 -1
View File
@@ -106,7 +106,7 @@ const tours: TranslationStrings = {
'tours.planner.discardChanges': 'Kasta ändringar',
'tours.planner.newTourConfirmTitle': 'Börja på en ny tur?',
'tours.planner.newTourConfirmBody': 'Dina osparade ändringar i den här turen kastas. Sparade turer ändras inte.',
'tours.planner.newTourConfirm': 'Kasta ändringar och börja på ny tur',
'tours.planner.newTourConfirm': 'Släng och börja',
'tours.planner.collapseElevation': 'Fäll ihop höjdprofil',
'tours.planner.expandElevation': 'Fäll ut höjdprofil',
'tours.planner.name': 'Turens namn',
@@ -513,6 +513,16 @@ const tours: TranslationStrings = {
'tours.walk.themeHint.mixed': 'Lite av allt: sevärdheter, museer, parker, utsikter, kaféer och torghandel.',
'tours.walk.themeHint.own': 'Ditt eget urval av kategorier nedan; ett tema väljer dem åt dig.',
'tours.walk.themeReason': 'Föreslaget för {theme}',
'tours.walk.arrival': 'Ankomst runt {time}',
'tours.walk.arrivalAtLeast': 'Ankomst tidigast {time}',
'tours.walk.hours.closed': 'Stängt kl. {arrival} enligt öppettiderna',
'tours.walk.hours.closes': 'Ankomst {arrival}, vistelse till {leave}, men stänger kl. {closes}',
'tours.walk.hours.unread': 'Öppettiderna kunde inte läsas här: {text}',
'tours.walk.hours.source':
'Öppettider enligt OpenStreetMap, mot beräknad ankomst; kontrollera innan du går. Ett stopp utan tider är okänt, inte öppet.',
'tours.walk.hours.noStart': 'Ge promenaden en tid den här dagen för att se ankomster och öppettider.',
'tours.poi.categoriesNone': 'Ingen kategori vald',
'tours.poi.categoriesMore': '{first} +{more}',
'tours.planner.tourType': 'Turtyp',
'tours.paddle.boat': 'Båt',
'tours.paddle.boat.kayak': 'Kajak',
+11 -1
View File
@@ -100,7 +100,7 @@ const tours: TranslationStrings = {
'tours.planner.newTourConfirmTitle': 'เริ่มทัวร์ใหม่ไหม?',
'tours.planner.newTourConfirmBody':
'การเปลี่ยนแปลงของทัวร์นี้ที่ยังไม่บันทึกจะถูกทิ้ง ทัวร์ที่บันทึกแล้วจะไม่เปลี่ยนแปลง',
'tours.planner.newTourConfirm': 'ทิ้งการเปลี่ยนแปลงและเริ่มทัวร์ใหม่',
'tours.planner.newTourConfirm': 'ทิ้งแล้วเริ่มใหม่',
'tours.planner.collapseElevation': 'ยุบกราฟระดับความสูง',
'tours.planner.expandElevation': 'ขยายกราฟระดับความสูง',
'tours.planner.name': 'ชื่อทัวร์',
@@ -496,6 +496,16 @@ const tours: TranslationStrings = {
'tours.walk.themeHint.mixed': 'มีทุกอย่าง: สถานที่น่าสนใจ พิพิธภัณฑ์ สวน จุดชมวิว ร้านกาแฟ และตลาด',
'tours.walk.themeHint.own': 'หมวดหมู่ด้านล่างเลือกเอง ธีมจะตั้งค่าให้อัตโนมัติ',
'tours.walk.themeReason': 'แนะนำสำหรับ {theme}',
'tours.walk.arrival': 'ถึงประมาณ {time}',
'tours.walk.arrivalAtLeast': 'ถึงเร็วที่สุด {time}',
'tours.walk.hours.closed': 'ตามเวลาทำการ ปิดเวลา {arrival}',
'tours.walk.hours.closes': 'ถึง {arrival} แวะถึง {leave} แต่ปิด {closes}',
'tours.walk.hours.unread': 'อ่านเวลาทำการไม่ได้: {text}',
'tours.walk.hours.source':
'เวลาทำการตาม OpenStreetMap เทียบกับเวลาถึงโดยประมาณ โปรดตรวจสอบก่อนไป จุดแวะที่ไม่มีเวลาทำการถือว่าไม่ทราบ ไม่ใช่เปิดอยู่',
'tours.walk.hours.noStart': 'กำหนดเวลาให้ทริปในวันนี้เพื่อดูเวลาถึงและเวลาทำการ',
'tours.poi.categoriesNone': 'ยังไม่ได้เลือกหมวดหมู่',
'tours.poi.categoriesMore': '{first} +{more}',
'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 = 756;
const GENERAL_KEY_COUNT = 765;
function literalKeys(source: string): string[] {
const tree = ts.createSourceFile('locale.ts', source, ts.ScriptTarget.Latest, true);
+11 -1
View File
@@ -106,7 +106,7 @@ const tours: TranslationStrings = {
'tours.planner.newTourConfirmTitle': 'Yeni bir tur başlatılsın mı?',
'tours.planner.newTourConfirmBody':
'Bu turdaki kaydedilmemiş değişiklikler silinecek. Kaydedilmiş turlar değişmeyecek.',
'tours.planner.newTourConfirm': 'Değişiklikleri sil ve yeni tur başlat',
'tours.planner.newTourConfirm': 'Vazgeç ve başla',
'tours.planner.collapseElevation': 'Yükseklik profilini daralt',
'tours.planner.expandElevation': 'Yükseklik profilini genişlet',
'tours.planner.name': 'Tur adı',
@@ -517,6 +517,16 @@ const tours: TranslationStrings = {
'Her şeyden biraz: görülecek yerler, müzeler, parklar, manzaralar, kafeler ve pazarlar.',
'tours.walk.themeHint.own': 'Aşağıdaki kategorileri kendin seçtin; bir tema onları senin için ayarlar.',
'tours.walk.themeReason': '{theme} için önerildi',
'tours.walk.arrival': 'Varış yaklaşık {time}',
'tours.walk.arrivalAtLeast': 'En erken varış {time}',
'tours.walk.hours.closed': 'Açılış saatlerine göre {arrival} saatinde kapalı',
'tours.walk.hours.closes': 'Varış {arrival}, kalış {leave} saatine kadar, ama {closes} saatinde kapanıyor',
'tours.walk.hours.unread': 'Açılış saatleri burada okunamadı: {text}',
'tours.walk.hours.source':
"Açılış saatleri OpenStreetMap'teki gibi, tahmini varışa göre; gitmeden önce kontrol et. Saati olmayan durak bilinmiyordur, açık değildir.",
'tours.walk.hours.noStart': 'Varışları ve açılış saatlerini görmek için yürüyüşe bu gün bir saat ver.',
'tours.poi.categoriesNone': 'Kategori seçilmedi',
'tours.poi.categoriesMore': '{first} +{more}',
'tours.planner.tourType': 'Tur türü',
'tours.paddle.boat': 'Tekne',
'tours.paddle.boat.kayak': 'Kano (kayak)',
+11 -1
View File
@@ -115,7 +115,7 @@ const tours: TranslationStrings = {
'tours.planner.discardChanges': 'Скасувати зміни',
'tours.planner.newTourConfirmTitle': 'Почати новий тур?',
'tours.planner.newTourConfirmBody': 'Незбережені зміни цього туру буде скасовано. Збережені тури не зміняться.',
'tours.planner.newTourConfirm': 'Скасувати зміни й почати новий тур',
'tours.planner.newTourConfirm': 'Скинути й почати',
'tours.planner.collapseElevation': 'Згорнути профіль висот',
'tours.planner.expandElevation': 'Розгорнути профіль висот',
'tours.planner.name': 'Назва туру',
@@ -528,6 +528,16 @@ const tours: TranslationStrings = {
'tours.walk.themeHint.mixed': "Усього потроху: пам'ятки, музеї, парки, краєвиди, кав'ярні та ринки.",
'tours.walk.themeHint.own': 'Твій власний вибір категорій нижче; тема задасть їх за тебе.',
'tours.walk.themeReason': 'Запропоновано для теми {theme}',
'tours.walk.arrival': 'Прибуття близько {time}',
'tours.walk.arrivalAtLeast': 'Прибуття не раніше {time}',
'tours.walk.hours.closed': 'За годинами роботи о {arrival} зачинено',
'tours.walk.hours.closes': 'Прибуття {arrival}, перебування до {leave}, але зачиняється о {closes}',
'tours.walk.hours.unread': 'Години роботи тут не розібрано: {text}',
'tours.walk.hours.source':
'Години роботи як в OpenStreetMap, відносно розрахункового прибуття; перевір перед виходом. Зупинка без годин невідома, а не відкрита.',
'tours.walk.hours.noStart': 'Задай прогулянці час цього дня, щоб бачити прибуття й години роботи.',
'tours.poi.categoriesNone': 'Категорію не вибрано',
'tours.poi.categoriesMore': '{first} +{more}',
'tours.planner.tourType': 'Тип туру',
'tours.paddle.boat': 'Човен',
'tours.paddle.boat.kayak': 'Каяк',
+11 -1
View File
@@ -107,7 +107,7 @@ const tours: TranslationStrings = {
'tours.planner.newTourConfirmTitle': 'Bắt đầu chuyến tham quan mới?',
'tours.planner.newTourConfirmBody':
'Các thay đổi chưa lưu của chuyến tham quan này sẽ bị bỏ. Các chuyến tham quan đã lưu sẽ không thay đổi.',
'tours.planner.newTourConfirm': 'Bỏ thay đổi và bắt đầu chuyến tham quan mới',
'tours.planner.newTourConfirm': 'Hủy và bắt đầu',
'tours.planner.collapseElevation': 'Thu gọn biểu đồ độ cao',
'tours.planner.expandElevation': 'Mở rộng biểu đồ độ cao',
'tours.planner.name': 'Tên chuyến tham quan',
@@ -513,6 +513,16 @@ const tours: TranslationStrings = {
'tours.walk.themeHint.mixed': 'Mỗi thứ một chút: điểm tham quan, bảo tàng, công viên, cảnh đẹp, quán cà phê và chợ.',
'tours.walk.themeHint.own': 'Bạn đang tự chọn danh mục bên dưới; chủ đề sẽ chọn giúp bạn.',
'tours.walk.themeReason': 'Gợi ý cho {theme}',
'tours.walk.arrival': 'Đến khoảng {time}',
'tours.walk.arrivalAtLeast': 'Đến sớm nhất lúc {time}',
'tours.walk.hours.closed': 'Theo giờ mở cửa, lúc {arrival} đóng cửa',
'tours.walk.hours.closes': 'Đến {arrival}, ở đến {leave}, nhưng đóng cửa lúc {closes}',
'tours.walk.hours.unread': 'Không đọc được giờ mở cửa ở đây: {text}',
'tours.walk.hours.source':
'Giờ mở cửa theo OpenStreetMap, so với giờ đến ước tính; hãy kiểm tra trước khi đi. Điểm dừng không có giờ là chưa rõ, không phải đang mở.',
'tours.walk.hours.noStart': 'Đặt giờ cho lộ trình trong ngày này để xem giờ đến và giờ mở cửa.',
'tours.poi.categoriesNone': 'Chưa chọn danh mục',
'tours.poi.categoriesMore': '{first} +{more}',
'tours.planner.tourType': 'Loại chuyến',
'tours.paddle.boat': 'Thuyền',
'tours.paddle.boat.kayak': 'Thuyền kayak',
+11 -1
View File
@@ -96,7 +96,7 @@ const tours: TranslationStrings = {
'tours.planner.discardChanges': '捨棄變更',
'tours.planner.newTourConfirmTitle': '開始新的遊程?',
'tours.planner.newTourConfirmBody': '這個遊程中尚未儲存的變更將被捨棄。已儲存的遊程不會變更。',
'tours.planner.newTourConfirm': '捨棄變更並開始新遊程',
'tours.planner.newTourConfirm': '放棄並開始',
'tours.planner.collapseElevation': '收合海拔剖面圖',
'tours.planner.expandElevation': '展開海拔剖面圖',
'tours.planner.name': '遊程名稱',
@@ -481,6 +481,16 @@ const tours: TranslationStrings = {
'tours.walk.themeHint.mixed': '各類都有一些:景點、博物館、公園、景觀、咖啡館和市集。',
'tours.walk.themeHint.own': '下方類別為你自選;選擇主題會自動設定。',
'tours.walk.themeReason': '推薦用於{theme}',
'tours.walk.arrival': '約 {time} 抵達',
'tours.walk.arrivalAtLeast': '最早 {time} 抵達',
'tours.walk.hours.closed': '按營業時間,{arrival} 時已關門',
'tours.walk.hours.closes': '{arrival} 抵達,停留至 {leave},但 {closes} 關門',
'tours.walk.hours.unread': '此處無法解析營業時間:{text}',
'tours.walk.hours.source':
'營業時間以 OpenStreetMap 為準,並與預計抵達時間對照;出發前請再確認。沒有營業時間的站點視為未知,而非營業中。',
'tours.walk.hours.noStart': '為當天的漫步設定時間,即可查看抵達時間和營業時間。',
'tours.poi.categoriesNone': '未選擇類別',
'tours.poi.categoriesMore': '{first} +{more}',
'tours.planner.tourType': '路線類型',
'tours.paddle.boat': '船隻',
'tours.paddle.boat.kayak': '獨木舟(Kayak)',
+11 -1
View File
@@ -97,7 +97,7 @@ const tours: TranslationStrings = {
'tours.planner.discardChanges': '放弃更改',
'tours.planner.newTourConfirmTitle': '开始规划新游览路线?',
'tours.planner.newTourConfirmBody': '此游览路线中未保存的更改将被丢弃。已保存的游览路线不会改变。',
'tours.planner.newTourConfirm': '放弃更改并开始新游览路线',
'tours.planner.newTourConfirm': '放弃并开始',
'tours.planner.collapseElevation': '收起海拔剖面图',
'tours.planner.expandElevation': '展开海拔剖面图',
'tours.planner.name': '游览名称',
@@ -482,6 +482,16 @@ const tours: TranslationStrings = {
'tours.walk.themeHint.mixed': '各类都有一些:景点、博物馆、公园、景观、咖啡馆和集市。',
'tours.walk.themeHint.own': '下方类别为你自选;选择主题会自动设置。',
'tours.walk.themeReason': '推荐用于{theme}',
'tours.walk.arrival': '约 {time} 到达',
'tours.walk.arrivalAtLeast': '最早 {time} 到达',
'tours.walk.hours.closed': '按营业时间,{arrival} 时已关门',
'tours.walk.hours.closes': '{arrival} 到达,停留至 {leave},但 {closes} 关门',
'tours.walk.hours.unread': '此处无法解析营业时间:{text}',
'tours.walk.hours.source':
'营业时间以 OpenStreetMap 为准,并与预计到达时间对照;出发前请再确认。没有营业时间的站点视为未知,而非营业中。',
'tours.walk.hours.noStart': '为当天的漫步设定时间,即可查看到达时间和营业时间。',
'tours.poi.categoriesNone': '未选择类别',
'tours.poi.categoriesMore': '{first} +{more}',
'tours.planner.tourType': '路线类型',
'tours.paddle.boat': '船只',
'tours.paddle.boat.kayak': '皮划艇',