feat(tours): the trip's tour totals by type above the tour list (STA-01)

The Tours tab's list of the trip's tours gains the filter the plan's tour
list has, and above the list a card with what the tours shown add up to:
how many, how many are on a day and how many not, then per type distance,
ascent and descent, and the three kinds of time apart, the router's for
tours planned in TREK, the one an import brought, and the planned one with
breaks. Nothing is added up across types. The filter narrows the list and
the totals alike.

Each tour counts once however many days it is on; the list is one row a
tour. A value no tour has is left out rather than shown as zero, and a sum
some tours lack is shown as at least that much, its info dot saying for how
many of how many it is known. Paddling sums no heights: on the water a climb
says nothing, and a planned paddle tour's heights would be the banks'.

The card folds to its counts, remembered in this browser. The empty state
for a filter that leaves nothing is shared by both lists now.
This commit is contained in:
Maurice
2026-10-19 13:55:07 +02:00
parent 06e6e25b25
commit a87ce65899
34 changed files with 703 additions and 13 deletions
@@ -177,3 +177,26 @@ export default function TourListFilterMenu({ filters }: { filters: TourListFilte
</div>
);
}
/** What a list says when its filters leave nothing: so, and a way to clear them. */
export function TourFilterEmpty({ filters }: { filters: TourListFilters }) {
const { t } = useTranslation();
return (
<div
role="status"
className="mx-1.5 mt-2 flex flex-col items-center gap-2 rounded-[12px] bg-surface-card px-4 py-5 text-center shadow-sm ring-1 ring-inset ring-edge-faint"
>
<p className="m-0 text-content-muted" style={fs(12.5, 'body')}>
{t('tours.filter.empty')}
</p>
<button
type="button"
onClick={filters.reset}
className="rounded-full bg-surface-secondary px-3 py-1 font-semibold text-content-secondary transition-colors hover:text-content focus-visible:outline focus-visible:outline-2 focus-visible:outline-accent"
style={fs(11.5, 'body')}
>
{t('tours.filter.reset')}
</button>
</div>
);
}
@@ -0,0 +1,175 @@
import type { TourListItem, TourTypeKey } from '@trek/shared';
import { ChartColumnBig, ChevronDown, Mountain, Route, Waves } from 'lucide-react';
import { useMemo, useState, type ComponentType } from 'react';
import { useTranslation } from '../../i18n';
import { useSettingsStore } from '../../store/settingsStore';
import { formatDistance, formatElevation } from '../../utils/units';
import { fs } from '../shared/DialogShell';
import { InfoDot, RailCard, RailFacts, type TourMetricField } from './tourParts';
import { formatPlannedTourDuration } from './tourPresentation';
import { tourScopeSummary, type ScopeSum, type TourTypeScope } from './tourScope';
type Translate = (key: string, params?: Record<string, unknown>) => string;
const TYPE_ICON: Partial<Record<TourTypeKey, ComponentType<{ size?: number; strokeWidth?: number }>>> = {
hike: Mountain,
kayak: Waves,
};
/** Whether the card is folded, kept per browser like other view conveniences; a blocked storage keeps it open. */
const FOLDED_KEY = 'trek.tours.scopeSummary.folded';
function readFolded(): boolean {
try {
return localStorage.getItem(FOLDED_KEY) === '1';
} catch {
return false;
}
}
function storeFolded(folded: boolean): void {
try {
localStorage.setItem(FOLDED_KEY, folded ? '1' : '0');
} catch {
// Private mode or blocked storage: the card simply opens again next time.
}
}
/** A count as a white badge of the card: the number, then what it counts. */
function CountBadge({ value, label, tone = 'neutral' }: { value: number; label: string; tone?: 'neutral' | 'faint' }) {
return (
<span
className={`inline-flex flex-none items-center gap-1 rounded-full bg-surface-card px-2 py-0.5 shadow-sm ring-1 ring-inset ring-edge-faint ${tone === 'faint' ? 'text-content-muted' : 'text-content-secondary'}`}
style={fs(11, 'body')}
>
<span className="font-geist font-semibold tabular-nums text-content">{value}</span>
{label}
</span>
);
}
/**
* A sum as a fact: nothing when no tour has the value, else the sum, marked as
* at least that much with the reason in its info dot when some tours lack it.
*/
function sumFact(
label: string,
sum: ScopeSum,
format: (value: number) => string,
t: Translate
): TourMetricField | null {
if (sum.value === null) return null;
const partial = sum.known < sum.of;
return {
label,
value: partial ? `≥ ${format(sum.value)}` : format(sum.value),
info: partial
? {
label: t('tours.scope.partial'),
description: t('tours.scope.partialBody', { known: sum.known, of: sum.of }),
}
: undefined,
};
}
function typeFacts(scope: TourTypeScope, unit: Parameters<typeof formatDistance>[1], t: Translate): TourMetricField[] {
const distance = (km: number) => formatDistance(km, unit);
const height = (m: number) => formatElevation(m, unit);
return [
sumFact(t('tours.detail.distance'), scope.distanceKm, distance, t),
sumFact(t('tours.detail.ascent'), scope.ascentM, height, t),
sumFact(t('tours.detail.descent'), scope.descentM, height, t),
sumFact(t('tours.scope.routed'), scope.routedMinutes, formatPlannedTourDuration, t),
sumFact(t('tours.scope.imported'), scope.importedMinutes, formatPlannedTourDuration, t),
sumFact(t('tours.scope.planned'), scope.plannedMinutes, formatPlannedTourDuration, t),
].filter((fact): fact is TourMetricField => fact !== null);
}
/** One tour type: its name with how many tours and how many are on a day, then its sums. */
function TypeScope({ scope }: { scope: TourTypeScope }) {
const { t } = useTranslation();
const unit = useSettingsStore((state) => state.settings.distance_unit) ?? 'metric';
const Icon = TYPE_ICON[scope.type] ?? Route;
const facts = typeFacts(scope, unit, t);
return (
<section
aria-label={t(`tourTypes.${scope.type}`)}
className="flex flex-col gap-2.5"
data-testid={`tour-scope-${scope.type}`}
>
<div className="flex min-w-0 items-center gap-1.5">
<span className="grid h-4 w-4 flex-none place-items-center text-content-muted">
<Icon size={13} strokeWidth={2} />
</span>
<span className="min-w-0 flex-1 truncate font-semibold text-content" style={fs(12, 'body')}>
{t(`tourTypes.${scope.type}`)}
</span>
<span className="font-geist tabular-nums text-content-faint" style={fs(11)}>
{scope.count}
</span>
</div>
{facts.length > 0 ? (
<RailFacts facts={facts} />
) : (
<p className="m-0 text-content-faint" style={fs(11.5, 'body')}>
{t('tours.scope.noMeasures')}
</p>
)}
</section>
);
}
/**
* STA-01: what the trip's tours add up to, above their list, by type: how many,
* how many are on a day, distance, ascent and descent, and the three kinds of
* time apart (the router's, an import's, the planned one with breaks), never
* added up across types. It reads the list as filtered, so a type chosen in the
* filter narrows both. A value no tour has is left out rather than shown as
* zero; a sum some tours lack is marked as at least that much. Folding it is
* remembered in this browser.
*/
export function TourScopeSummary({ tours }: { tours: readonly TourListItem[] }) {
const { t } = useTranslation();
const [folded, setFolded] = useState(readFolded);
const scope = useMemo(() => tourScopeSummary(tours), [tours]);
if (scope.count === 0) return null;
const toggle = () => {
setFolded((value) => {
storeFolded(!value);
return !value;
});
};
const toggleLabel = folded ? t('common.expand') : t('common.collapse');
return (
<RailCard
icon={<ChartColumnBig size={14} strokeWidth={2} />}
title={t('tours.scope.title')}
aside={
<span className="flex flex-none items-center gap-0.5">
<InfoDot label={t('tours.scope.title')} description={t('tours.scope.info')} />
<button
type="button"
onClick={toggle}
aria-expanded={!folded}
aria-label={toggleLabel}
data-testid="tour-scope-toggle"
className="grid h-6 w-6 place-items-center rounded-full text-content-faint transition-colors hover:bg-surface-hover hover:text-content focus-visible:outline focus-visible:outline-2 focus-visible:outline-accent"
>
<ChevronDown size={14} strokeWidth={2.2} className={`transition-transform ${folded ? '' : 'rotate-180'}`} />
</button>
</span>
}
>
<div className="flex flex-wrap items-center gap-1.5" data-testid="tour-scope-counts">
<CountBadge value={scope.count} label={t('tours.mode.tours')} />
<CountBadge value={scope.onDays} label={t('places.planned')} tone="faint" />
<CountBadge value={scope.count - scope.onDays} label={t('places.unplanned')} tone="faint" />
</div>
{!folded && (
<div className="mt-3.5 flex flex-col gap-4 border-t border-edge-faint pt-3.5">
{scope.types.map((typeScope) => (
<TypeScope key={typeScope.type} scope={typeScope} />
))}
</div>
)}
</RailCard>
);
}
+2 -9
View File
@@ -9,7 +9,7 @@ import { NEUTRAL_TINT, fs } from '../shared/DialogShell'
import { Tooltip } from '../shared/Tooltip'
import EmptyState from '../shared/EmptyState'
import { TourDayMenu } from './tourParts'
import TourListFilterMenu from './TourListFilterMenu'
import TourListFilterMenu, { TourFilterEmpty } from './TourListFilterMenu'
import { useTourListFilters } from './useTourListFilters'
import TourGpxPreviewDialog from './TourGpxPreviewDialog'
import { useTourGpxImport } from './useTourGpxImport'
@@ -97,14 +97,7 @@ export default function ToursSidebar({ tripId, days, tours, loading = false, sel
<div className="trek-stagger min-h-0 flex-1 overflow-y-auto px-2 pb-2 pt-1.5">
{!loading && filters.emptyByFilter ? (
<div role="status" className="mx-1.5 mt-2 flex flex-col items-center gap-2 rounded-[12px] bg-surface-card px-4 py-5 text-center shadow-sm ring-1 ring-inset ring-edge-faint">
<p className="m-0 text-content-muted" style={fs(12.5, 'body')}>{t('tours.filter.empty')}</p>
<button type="button" onClick={filters.reset}
className="rounded-full bg-surface-secondary px-3 py-1 font-semibold text-content-secondary transition-colors hover:text-content focus-visible:outline focus-visible:outline-2 focus-visible:outline-accent"
style={fs(11.5, 'body')}>
{t('tours.filter.reset')}
</button>
</div>
<TourFilterEmpty filters={filters} />
) : !loading && filters.filtered.length === 0 ? (
<EmptyState scene="tours" size={92} fill surface="var(--bg-secondary)" title={t('tours.empty.title')}
action={<p className="m-0 max-w-[260px] text-content-muted" style={fs(12, 'body')}>{t('tours.empty.body')}</p>} />
@@ -151,8 +151,9 @@ describe('TourPlannerToursRail', () => {
expect(screen.getByRole('complementary', { name: 'Tours of this trip' })).toBeInTheDocument()
expect(screen.getByRole('option', { name: /Saved ridge walk/i })).toHaveTextContent('4 km')
expect(screen.getByText('Ascent')).toBeInTheDocument()
expect(screen.getByText('50 m')).toBeInTheDocument()
const overview = screen.getByTestId('tour-planner-overview')
expect(within(overview).getByText('Ascent')).toBeInTheDocument()
expect(within(overview).getByText('50 m')).toBeInTheDocument()
expect(document.querySelector('svg[viewBox="0 0 440 100"]')).toBeInTheDocument()
expect(screen.queryByText('Routing is planning assistance, not a safety guarantee.')).not.toBeInTheDocument()
})
@@ -40,6 +40,9 @@ import { type AssignTourToDay, useTourDayAssignment } from '../useTourDayAssignm
import WandererMark from '../wanderer/WandererMark'
import { TourGpxExportBarButton } from '../TourGpxExportButton'
import { TourRemoteChangeNotice, TourRevisionConflictDialog } from './TourRevisionNotice'
import TourListFilterMenu, { TourFilterEmpty } from '../TourListFilterMenu'
import { TourScopeSummary } from '../TourScopeSummary'
import { useTourListFilters } from '../useTourListFilters'
const STATUS_KEY: Partial<Record<TourPlannerStatus, string>> = {
dirty: 'tours.planner.status.dirty',
@@ -645,6 +648,8 @@ export function TourPlannerToursRail({ planner, tours, days, loading, onAssignTo
const distanceUnit = useSettingsStore(state => state.settings.distance_unit)
const [pendingTour, setPendingTour] = useState<TourListItem | null>(null)
const dayAssignment = useTourDayAssignment({ canAssign, onAssignToDay })
// STA-01: one filter for the list and the summary above it, the same as the plan's tour list.
const filters = useTourListFilters(tours)
const isSaving = planner.isSaving
const hasRoute = (planner.mode.type === 'new-draft' || planner.mode.type === 'edit-saved') && planner.route !== null
// A paddle tour's heights are read off the banks, not the water: they stay unknown.
@@ -723,17 +728,21 @@ export function TourPlannerToursRail({ planner, tours, days, loading, onAssignTo
) : tours.length === 0 ? (
<p className="m-0 py-6 text-center text-content-faint" style={fs(12, 'body')}>{t('common.loading')}</p>
) : (
<>
<TourListFilterMenu filters={filters} />
<TourScopeSummary tours={filters.filtered} />
{filters.emptyByFilter ? <TourFilterEmpty filters={filters} /> : (
<TourListCard
icon={<Mountain size={14} strokeWidth={2} />}
title={t('tours.mode.tours')}
aside={
<span className="flex-none tabular-nums text-content-faint" style={fs(11.5, 'body')}>
{tours.length}
{filters.filtered.length}
</span>
}
label={t('tours.planner.tripTours')}
>
{tours.map(tour => (
{filters.filtered.map(tour => (
<TourListRow
key={tour.place_id}
tour={tour}
@@ -769,6 +778,8 @@ export function TourPlannerToursRail({ planner, tours, days, loading, onAssignTo
/>
))}
</TourListCard>
)}
</>
)}
</div>
@@ -0,0 +1,130 @@
import type { TourListItem } from '@trek/shared';
import { beforeEach, describe, expect, it } from 'vitest';
import { fireEvent, render, screen, within } from '../../../tests/helpers/render';
import { TourScopeSummary } from './TourScopeSummary';
import { tourScopeSummary } from './tourScope';
function tour(over: Partial<TourListItem> & Pick<TourListItem, 'place_id'>): TourListItem {
return {
tour_type: 'hike',
name: `Tour ${over.place_id}`,
distance: null,
elevation_gain: null,
elevation_loss: null,
duration: null,
difficulty: null,
wanderer_ref: null,
match_confidence: null,
max_hiking_difficulty: 2,
planned: false,
caution: false,
has_waypoints: true,
origin: 'trek',
...over,
} as TourListItem;
}
const HIKE_A = tour({
place_id: 1,
distance: 10,
elevation_gain: 600,
elevation_loss: 500,
duration: 200,
planned: true,
});
const HIKE_B = tour({
place_id: 2,
distance: 5.5,
elevation_gain: null,
elevation_loss: 100,
duration: 100,
break_additional_minutes: 30,
});
const GPX_HIKE = tour({
place_id: 3,
distance: 8,
elevation_gain: 300,
elevation_loss: 300,
duration: 150,
has_waypoints: false,
origin: 'gpx',
});
const PADDLE = tour({
place_id: 4,
tour_type: 'kayak',
distance: 12,
elevation_gain: 40,
elevation_loss: 40,
duration: 180,
planned: true,
});
describe('tourScopeSummary (STA-01)', () => {
it('counts each tour once, by type, the most tours first, with how many are on a day', () => {
const scope = tourScopeSummary([HIKE_A, PADDLE, HIKE_B, GPX_HIKE, HIKE_A]);
expect(scope.count).toBe(4);
expect(scope.onDays).toBe(2);
expect(scope.types.map((type) => [type.type, type.count, type.onDays])).toEqual([
['hike', 3, 1],
['kayak', 1, 1],
]);
});
it('sums what is known and says how much of the whole that is, never a zero for unknown', () => {
const [hike] = tourScopeSummary([HIKE_A, HIKE_B, GPX_HIKE]).types;
expect(hike.distanceKm).toEqual({ value: 23.5, known: 3, of: 3 });
expect(hike.ascentM).toEqual({ value: 900, known: 2, of: 3 });
const [none] = tourScopeSummary([tour({ place_id: 9 })]).types;
expect(none.distanceKm).toEqual({ value: null, known: 0, of: 1 });
});
it('keeps the routed, the imported and the planned time apart', () => {
const [hike] = tourScopeSummary([HIKE_A, HIKE_B, GPX_HIKE]).types;
expect(hike.routedMinutes).toEqual({ value: 300, known: 2, of: 2 });
expect(hike.importedMinutes).toEqual({ value: 150, known: 1, of: 1 });
// Planned: each tour's own time plus its breaks (HIKE_B's 30 min).
expect(hike.plannedMinutes).toEqual({ value: 480, known: 3, of: 3 });
});
it('sums no heights for paddling, not even those a recorded paddle track brought', () => {
const [paddle] = tourScopeSummary([PADDLE, { ...GPX_HIKE, place_id: 5, tour_type: 'kayak' }]).types;
expect(paddle.ascentM).toEqual({ value: null, known: 0, of: 0 });
expect(paddle.descentM).toEqual({ value: null, known: 0, of: 0 });
expect(paddle.distanceKm.value).toBe(20);
});
});
describe('TourScopeSummary', () => {
beforeEach(() => localStorage.clear());
it('shows the counts and each type with its sums, an incomplete one marked as at least', () => {
render(<TourScopeSummary tours={[HIKE_A, HIKE_B, PADDLE]} />);
const counts = screen.getByTestId('tour-scope-counts');
expect(counts).toHaveTextContent('3Tours');
expect(counts).toHaveTextContent('2Planned');
expect(counts).toHaveTextContent('1Unplanned');
const hike = screen.getByTestId('tour-scope-hike');
expect(within(hike).getByText('15.5 km')).toBeInTheDocument();
expect(within(hike).getByText('≥ 600 m')).toBeInTheDocument();
expect(within(hike).getByText('Routed time')).toBeInTheDocument();
expect(within(hike).queryByText('Time from imports')).toBeNull();
// The paddle tour has no heights: no ascent, rather than 0 m.
expect(within(screen.getByTestId('tour-scope-kayak')).queryByText('Ascent')).toBeNull();
});
it('folds to its counts, and remembers that in this browser', () => {
const { unmount } = render(<TourScopeSummary tours={[HIKE_A]} />);
fireEvent.click(screen.getByTestId('tour-scope-toggle'));
expect(screen.queryByTestId('tour-scope-hike')).toBeNull();
expect(screen.getByTestId('tour-scope-counts')).toBeInTheDocument();
unmount();
render(<TourScopeSummary tours={[HIKE_A]} />);
expect(screen.queryByTestId('tour-scope-hike')).toBeNull();
expect(screen.getByTestId('tour-scope-toggle')).toHaveAttribute('aria-expanded', 'false');
});
it('shows nothing for a trip without tours', () => {
const { container } = render(<TourScopeSummary tours={[]} />);
expect(container).toBeEmptyDOMElement();
});
});
+89
View File
@@ -0,0 +1,89 @@
import type { TourListItem, TourTypeKey } from '@trek/shared';
import { heightsUnknown } from './tourFacts';
import { tourPlannedTimes, tourSource } from './tourPresentation';
/**
* A sum over the tours that have the value, with how many have it of how many
* could: `value` is null when none has it, never a zero standing in for
* "unknown", and `known < of` says the sum is short of the whole.
*/
export interface ScopeSum {
value: number | null;
known: number;
of: number;
}
/** One tour type's share of the trip: how many tours, how many on a day, and their measures. */
export interface TourTypeScope {
type: TourTypeKey;
count: number;
onDays: number;
distanceKm: ScopeSum;
ascentM: ScopeSum;
descentM: ScopeSum;
/** The time the router worked out, for the tours planned in TREK. */
routedMinutes: ScopeSum;
/** The time an import brought with it (a GPX file, a wanderer trail). */
importedMinutes: ScopeSum;
/** The time planned for the day, breaks or the planner's own figure included. */
plannedMinutes: ScopeSum;
}
export interface TourScope {
count: number;
onDays: number;
/** By type, the most tours first; the time kinds are never added up across types. */
types: TourTypeScope[];
}
/** A measure a type has no use for: no value, and nothing it is short of. */
const NO_SUM: ScopeSum = { value: null, known: 0, of: 0 };
function sumOf(values: Array<number | null | undefined>): ScopeSum {
const known = values.filter((value): value is number => value != null && Number.isFinite(value));
return {
value: known.length ? known.reduce((total, value) => total + value, 0) : null,
known: known.length,
of: values.length,
};
}
function scopeOfType(type: TourTypeKey, tours: readonly TourListItem[]): TourTypeScope {
const planned = tours.filter((tour) => tourSource(tour) === 'trek');
const imported = tours.filter((tour) => tourSource(tour) !== 'trek');
// A paddle tour drawn in TREK has no true heights (they would be the banks'), so it does not count as having them.
const withHeights = tours.map((tour) => (heightsUnknown(tour) ? null : tour));
// On the water a climb says nothing: paddling sums no heights, whatever a recorded track brought.
const heights = type !== 'kayak';
return {
type,
count: tours.length,
onDays: tours.filter((tour) => tour.planned).length,
distanceKm: sumOf(tours.map((tour) => tour.distance)),
ascentM: heights ? sumOf(withHeights.map((tour) => tour?.elevation_gain)) : NO_SUM,
descentM: heights ? sumOf(withHeights.map((tour) => tour?.elevation_loss)) : NO_SUM,
routedMinutes: sumOf(planned.map((tour) => tour.duration)),
importedMinutes: sumOf(imported.map((tour) => tour.duration)),
plannedMinutes: sumOf(tours.map((tour) => tourPlannedTimes(tour).plannedTotalMinutes)),
};
}
/**
* STA-01: what the tours of a trip add up to, by type. Each tour counts once,
* however many days it is on; the list is the trip's tours, one row each, so
* a tour on three days is still one tour and one distance.
*/
export function tourScopeSummary(tours: readonly TourListItem[]): TourScope {
const byType = new Map<TourTypeKey, TourListItem[]>();
for (const tour of new Map(tours.map((tour) => [tour.place_id, tour])).values()) {
byType.set(tour.tour_type, [...(byType.get(tour.tour_type) ?? []), tour]);
}
const types = [...byType.entries()]
.map(([type, ofType]) => scopeOfType(type, ofType))
.sort((a, b) => b.count - a.count || a.type.localeCompare(b.type));
return {
count: types.reduce((total, scope) => total + scope.count, 0),
onDays: types.reduce((total, scope) => total + scope.onDays, 0),
types,
};
}
+10
View File
@@ -277,6 +277,16 @@ const tours: TranslationStrings = {
'tours.filter.type': 'النوع',
'tours.filter.empty': 'لا توجد جولة تطابق هذه المرشحات.',
'tours.filter.reset': 'إعادة ضبط المرشحات',
'tours.scope.title': 'إجمالي الرحلة',
'tours.scope.info':
'ما تجمعه الجولات المعروضة أدناه حسب النوع، والفلتر يحدّ من الاثنين. الجولة في عدة أيام تُحسب مرة واحدة. تبقى أنواع الوقت الثلاثة منفصلة: ما حسبه التوجيه، وما جاء مع الاستيراد، وما خُطط له مع الاستراحات. لا يُجمع شيء عبر الأنواع.',
'tours.scope.partial': 'مجموع غير مكتمل',
'tours.scope.partialBody':
'معروف لـ {known} من {of} جولات؛ الباقي بلا قيمة ومستبعد، لذا المجموع على الأقل بهذا القدر.',
'tours.scope.routed': 'وقت المسار المحسوب',
'tours.scope.imported': 'الوقت من الاستيراد',
'tours.scope.planned': 'المخطط مع الاستراحات',
'tours.scope.noMeasures': 'لا مسافة أو ارتفاعات أو أوقات معروفة بعد.',
'tours.copy.action': 'حفظ كجولة جديدة',
'tours.copy.hint': 'ينسخ هذه الجولة كجولة جديدة مستقلة دون أيامها. يبقى الأصل كما هو.',
'tours.copy.name': '{name} (نسخة)',
+10
View File
@@ -266,6 +266,16 @@ const tours: TranslationStrings = {
'tours.filter.type': 'Növ',
'tours.filter.empty': 'Bu filtrlərə uyğun tur yoxdur.',
'tours.filter.reset': 'Filtrləri sıfırla',
'tours.scope.title': 'Səfərin həcmi',
'tours.scope.info':
'Aşağıda göstərilən turların növə görə cəmi; filtr hər ikisini daraldır. Bir neçə gündə olan tur bir dəfə sayılır. Üç vaxt növü ayrı qalır: marşrutlaşdırmanın hesabladığı, idxalın gətirdiyi və fasilələrlə planlaşdırılan. Növlər arasında heç nə toplanmır.',
'tours.scope.partial': 'Natamam cəm',
'tours.scope.partialBody':
'{of} turdan {known} üçün məlumdur; qalanlarının dəyəri yoxdur və daxil edilmir, ona görə cəm ən azı bu qədərdir.',
'tours.scope.routed': 'Marşrut vaxtı',
'tours.scope.imported': 'İdxaldan vaxt',
'tours.scope.planned': 'Fasilələrlə planlaşdırılan',
'tours.scope.noMeasures': 'Hələ məsafə, hündürlük və ya vaxt məlum deyil.',
'tours.copy.action': 'Yeni tur kimi saxla',
'tours.copy.hint': 'Bu turu günləri olmadan yeni, müstəqil tur kimi kopyalayır. Orijinal olduğu kimi qalır.',
'tours.copy.name': '{name} (surət)',
+10
View File
@@ -271,6 +271,16 @@ const tours: TranslationStrings = {
'tours.filter.type': 'Tipo',
'tours.filter.empty': 'Nenhum passeio corresponde a esses filtros.',
'tours.filter.reset': 'Limpar filtros',
'tours.scope.title': 'Totais da viagem',
'tours.scope.info':
'O que os passeios mostrados abaixo somam, por tipo; o filtro restringe os dois. Um passeio em vários dias conta uma vez. Os três tipos de tempo ficam separados: o que o roteamento calculou, o que uma importação trouxe e o que está planejado com pausas. Nada é somado entre tipos.',
'tours.scope.partial': 'Soma incompleta',
'tours.scope.partialBody':
'Conhecido para {known} de {of} passeios; os outros não têm valor e ficam de fora, então o total é pelo menos isso.',
'tours.scope.routed': 'Tempo calculado',
'tours.scope.imported': 'Tempo das importações',
'tours.scope.planned': 'Planejado com pausas',
'tours.scope.noMeasures': 'Ainda sem distância, altitudes ou tempos conhecidos.',
'tours.copy.action': 'Salvar como novo passeio',
'tours.copy.hint':
'Copia este passeio como um novo passeio independente, sem os seus dias. O original fica como está.',
+10
View File
@@ -271,6 +271,16 @@ const tours: TranslationStrings = {
'tours.filter.type': 'Tipus',
'tours.filter.empty': 'Cap excursió no coincideix amb aquests filtres.',
'tours.filter.reset': 'Restableix els filtres',
'tours.scope.title': 'Totals del viatge',
'tours.scope.info':
"El que sumen les rutes mostrades a sota, per tipus; el filtre limita totes dues coses. Una ruta en diversos dies compta una vegada. Els tres tipus de temps es mantenen separats: el que ha calculat l'encaminament, el que ha portat una importació i el que està planificat amb pauses. No se suma res entre tipus.",
'tours.scope.partial': 'Suma incompleta',
'tours.scope.partialBody':
'Conegut per a {known} de {of} rutes; les altres no tenen valor i queden fora, així que el total és com a mínim això.',
'tours.scope.routed': 'Temps calculat',
'tours.scope.imported': 'Temps de les importacions',
'tours.scope.planned': 'Planificat amb pauses',
'tours.scope.noMeasures': 'Encara no hi ha distància, alçades ni temps coneguts.',
'tours.copy.action': 'Desa com a excursió nova',
'tours.copy.hint':
'Copia aquesta excursió com una excursió nova i independent, sense els seus dies. L’original es queda com està.',
+10
View File
@@ -266,6 +266,16 @@ const tours: TranslationStrings = {
'tours.filter.type': 'Druh',
'tours.filter.empty': 'Těmto filtrům neodpovídá žádná túra.',
'tours.filter.reset': 'Zrušit filtry',
'tours.scope.title': 'Rozsah cesty',
'tours.scope.info':
'Co dávají dohromady túry zobrazené níže, podle typu; filtr omezuje obojí. Túra na více dnech se počítá jednou. Tři druhy času zůstávají oddělené: co spočítalo trasování, co přinesl import a co je naplánováno s přestávkami. Napříč typy se nic nesčítá.',
'tours.scope.partial': 'Neúplný součet',
'tours.scope.partialBody':
'Známo pro {known} z {of} túr; ostatní hodnotu nemají a chybí, součet je tedy nejméně tolik.',
'tours.scope.routed': 'Vypočtený čas',
'tours.scope.imported': 'Čas z importů',
'tours.scope.planned': 'Plán s přestávkami',
'tours.scope.noMeasures': 'Zatím není známa vzdálenost, výšky ani časy.',
'tours.copy.action': 'Uložit jako novou túru',
'tours.copy.hint': 'Zkopíruje tuto túru jako novou, samostatnou túru bez jejích dnů. Originál zůstane, jak je.',
'tours.copy.name': '{name} (kopie)',
+10
View File
@@ -274,6 +274,16 @@ const tours: TranslationStrings = {
'tours.filter.type': 'Art',
'tours.filter.empty': 'Keine Tour passt zu diesen Filtern.',
'tours.filter.reset': 'Filter zurücksetzen',
'tours.scope.title': 'Umfang der Reise',
'tours.scope.info':
'Was die unten gezeigten Touren zusammen ergeben, nach Typ; der Filter grenzt beides ein. Eine Tour an mehreren Tagen zählt einmal. Die drei Zeitarten bleiben getrennt: was das Routing berechnet hat, was ein Import mitgebracht hat und was mit Pausen geplant ist. Über Typen hinweg wird nichts addiert.',
'tours.scope.partial': 'Unvollständige Summe',
'tours.scope.partialBody':
'Bekannt für {known} von {of} Touren; die übrigen haben keinen Wert und fehlen, die Summe ist also mindestens so groß.',
'tours.scope.routed': 'Routingzeit',
'tours.scope.imported': 'Zeit laut Import',
'tours.scope.planned': 'Geplant mit Pausen',
'tours.scope.noMeasures': 'Noch keine Distanz, Höhen oder Zeiten bekannt.',
'tours.copy.action': 'Als neue Tour speichern',
'tours.copy.hint': 'Kopiert diese Tour als neue, eigenständige Tour ohne ihre Tage. Das Original bleibt, wie es ist.',
'tours.copy.name': '{name} (Kopie)',
+10
View File
@@ -265,6 +265,16 @@ const tours: TranslationStrings = {
'tours.filter.type': 'Type',
'tours.filter.empty': 'No tour matches these filters.',
'tours.filter.reset': 'Reset filters',
'tours.scope.title': 'Trip totals',
'tours.scope.info':
'What the tours shown below add up to, by type; the filter narrows both. A tour on several days counts once. The three kinds of time stay apart: what the router worked out, what an import brought, and what is planned with breaks. Nothing is added up across types.',
'tours.scope.partial': 'Incomplete sum',
'tours.scope.partialBody':
'Known for {known} of {of} tours; the others have no value and are left out, so the total is at least this much.',
'tours.scope.routed': 'Routed time',
'tours.scope.imported': 'Time from imports',
'tours.scope.planned': 'Planned with breaks',
'tours.scope.noMeasures': 'No distance, heights or times known yet.',
'tours.copy.action': 'Save as new tour',
'tours.copy.hint': 'Copies this tour as a new, independent tour without its days. The original stays as it is.',
'tours.copy.name': '{name} (copy)',
+10
View File
@@ -270,6 +270,16 @@ const tours: TranslationStrings = {
'tours.filter.type': 'Tipo',
'tours.filter.empty': 'Ninguna excursión coincide con estos filtros.',
'tours.filter.reset': 'Restablecer filtros',
'tours.scope.title': 'Totales del viaje',
'tours.scope.info':
'Lo que suman las rutas mostradas abajo, por tipo; el filtro acota ambos. Una ruta en varios días cuenta una vez. Los tres tipos de tiempo se mantienen separados: lo que calculó el enrutamiento, lo que trajo una importación y lo planificado con pausas. No se suma nada entre tipos.',
'tours.scope.partial': 'Suma incompleta',
'tours.scope.partialBody':
'Conocido para {known} de {of} rutas; las demás no tienen valor y quedan fuera, así que el total es al menos esto.',
'tours.scope.routed': 'Tiempo calculado',
'tours.scope.imported': 'Tiempo de las importaciones',
'tours.scope.planned': 'Planificado con pausas',
'tours.scope.noMeasures': 'Aún no se conocen distancia, alturas ni tiempos.',
'tours.copy.action': 'Guardar como nueva excursión',
'tours.copy.hint':
'Copia esta excursión como una nueva excursión independiente, sin sus días. El original queda como está.',
+10
View File
@@ -268,6 +268,16 @@ const tours: TranslationStrings = {
'tours.filter.type': 'Liik',
'tours.filter.empty': 'Ükski matk ei vasta neile filtritele.',
'tours.filter.reset': 'Lähtesta filtrid',
'tours.scope.title': 'Reisi kogumaht',
'tours.scope.info':
'Mida allpool näidatud matkad tüübi kaupa kokku annavad; filter kitsendab mõlemat. Mitmel päeval olev matk loetakse üks kord. Kolm ajaliiki jäävad eraldi: mida marsruutija arvutas, mida import kaasa tõi ja mis on pausidega planeeritud. Tüüpide vahel midagi kokku ei liideta.',
'tours.scope.partial': 'Mittetäielik summa',
'tours.scope.partialBody':
'Teada {known} matka puhul {of}-st; ülejäänutel väärtus puudub ja need jäävad välja, seega on summa vähemalt nii suur.',
'tours.scope.routed': 'Arvutatud aeg',
'tours.scope.imported': 'Aeg importidest',
'tours.scope.planned': 'Planeeritud koos pausidega',
'tours.scope.noMeasures': 'Vahemaa, kõrgused ja ajad pole veel teada.',
'tours.copy.action': 'Salvesta uue matkana',
'tours.copy.hint': 'Kopeerib selle matka uue, iseseisva matkana ilma päevadeta. Originaal jääb samaks.',
'tours.copy.name': '{name} (koopia)',
+10
View File
@@ -276,6 +276,16 @@ const tours: TranslationStrings = {
'tours.filter.type': 'Type', // same-as-en
'tours.filter.empty': 'Aucune sortie ne correspond à ces filtres.',
'tours.filter.reset': 'Réinitialiser les filtres',
'tours.scope.title': 'Totaux du voyage',
'tours.scope.info':
"Ce que totalisent les itinéraires affichés ci-dessous, par type ; le filtre limite les deux. Un itinéraire sur plusieurs jours compte une fois. Les trois sortes de temps restent séparées : ce que le calcul d'itinéraire a donné, ce qu'un import a apporté et ce qui est prévu avec les pauses. Rien n'est additionné entre les types.",
'tours.scope.partial': 'Somme incomplète',
'tours.scope.partialBody':
"Connu pour {known} itinéraires sur {of} ; les autres n'ont pas de valeur et sont laissés de côté, le total est donc au moins celui-ci.",
'tours.scope.routed': 'Temps calculé',
'tours.scope.imported': 'Temps des imports',
'tours.scope.planned': 'Prévu avec pauses',
'tours.scope.noMeasures': "Aucune distance, altitude ni durée connue pour l'instant.",
'tours.copy.action': 'Enregistrer comme nouvelle sortie',
'tours.copy.hint':
'Copie cette sortie en une nouvelle sortie indépendante, sans ses jours. L’original reste tel quel.',
+10
View File
@@ -277,6 +277,16 @@ const tours: TranslationStrings = {
'tours.filter.type': 'Είδος',
'tours.filter.empty': 'Καμία εκδρομή δεν ταιριάζει με αυτά τα φίλτρα.',
'tours.filter.reset': 'Επαναφορά φίλτρων',
'tours.scope.title': 'Σύνολα ταξιδιού',
'tours.scope.info':
'Τι αθροίζουν οι διαδρομές που φαίνονται παρακάτω, ανά τύπο· το φίλτρο περιορίζει και τα δύο. Μια διαδρομή σε πολλές μέρες μετρά μία φορά. Τα τρία είδη χρόνου μένουν χωριστά: όσα υπολόγισε η δρομολόγηση, όσα έφερε μια εισαγωγή και όσα σχεδιάστηκαν με διαλείμματα. Τίποτα δεν προστίθεται μεταξύ τύπων.',
'tours.scope.partial': 'Ελλιπές άθροισμα',
'tours.scope.partialBody':
'Γνωστό για {known} από {of} διαδρομές· οι υπόλοιπες δεν έχουν τιμή και μένουν έξω, άρα το σύνολο είναι τουλάχιστον τόσο.',
'tours.scope.routed': 'Υπολογισμένος χρόνος',
'tours.scope.imported': 'Χρόνος από εισαγωγές',
'tours.scope.planned': 'Με διαλείμματα',
'tours.scope.noMeasures': 'Δεν είναι ακόμη γνωστά απόσταση, υψόμετρα ή χρόνοι.',
'tours.copy.action': 'Αποθήκευση ως νέα εκδρομή',
'tours.copy.hint':
'Αντιγράφει αυτή την εκδρομή ως νέα, ανεξάρτητη εκδρομή χωρίς τις μέρες της. Το πρωτότυπο μένει ως έχει.',
+10
View File
@@ -272,6 +272,16 @@ const tours: TranslationStrings = {
'tours.filter.type': 'Fajta',
'tours.filter.empty': 'Egy túra sem felel meg ezeknek a szűrőknek.',
'tours.filter.reset': 'Szűrők törlése',
'tours.scope.title': 'Az út összesítése',
'tours.scope.info':
'Amit az alább mutatott túrák típusonként kiadnak; a szűrő mindkettőt szűkíti. A több napon szereplő túra egyszer számít. A háromféle idő külön marad: amit az útvonaltervező számolt, amit egy import hozott, és ami szünetekkel van tervezve. Típusok között semmi sem adódik össze.',
'tours.scope.partial': 'Hiányos összeg',
'tours.scope.partialBody':
'{of} túrából {known} esetén ismert; a többinek nincs értéke és kimarad, így az összeg legalább ennyi.',
'tours.scope.routed': 'Számított idő',
'tours.scope.imported': 'Idő az importokból',
'tours.scope.planned': 'Tervezett, szünetekkel',
'tours.scope.noMeasures': 'Még nem ismert távolság, magasság vagy idő.',
'tours.copy.action': 'Mentés új túraként',
'tours.copy.hint': 'Ezt a túrát új, önálló túraként másolja a napjai nélkül. Az eredeti változatlan marad.',
'tours.copy.name': '{name} (másolat)',
+10
View File
@@ -265,6 +265,16 @@ const tours: TranslationStrings = {
'tours.filter.type': 'Jenis',
'tours.filter.empty': 'Tidak ada tur yang cocok dengan filter ini.',
'tours.filter.reset': 'Atur ulang filter',
'tours.scope.title': 'Total perjalanan',
'tours.scope.info':
'Jumlah tur yang ditampilkan di bawah, per jenis; filter mempersempit keduanya. Tur di beberapa hari dihitung sekali. Tiga jenis waktu tetap terpisah: yang dihitung perutean, yang dibawa impor, dan yang direncanakan dengan istirahat. Tidak ada yang dijumlahkan antar jenis.',
'tours.scope.partial': 'Jumlah tidak lengkap',
'tours.scope.partialBody':
'Diketahui untuk {known} dari {of} tur; yang lain tidak punya nilai dan tidak dihitung, jadi totalnya setidaknya sebesar ini.',
'tours.scope.routed': 'Waktu perutean',
'tours.scope.imported': 'Waktu dari impor',
'tours.scope.planned': 'Rencana dengan istirahat',
'tours.scope.noMeasures': 'Belum ada jarak, ketinggian, atau waktu yang diketahui.',
'tours.copy.action': 'Simpan sebagai tur baru',
'tours.copy.hint': 'Menyalin tur ini sebagai tur baru yang mandiri tanpa hari-harinya. Aslinya tetap seperti semula.',
'tours.copy.name': '{name} (salinan)',
+10
View File
@@ -272,6 +272,16 @@ const tours: TranslationStrings = {
'tours.filter.type': 'Tipo',
'tours.filter.empty': 'Nessun tour corrisponde a questi filtri.',
'tours.filter.reset': 'Reimposta filtri',
'tours.scope.title': 'Totali del viaggio',
'tours.scope.info':
"Quanto sommano i percorsi mostrati sotto, per tipo; il filtro limita entrambi. Un percorso su più giorni conta una volta. I tre tipi di tempo restano separati: quello calcolato dal routing, quello portato da un'importazione e quello pianificato con le pause. Nulla viene sommato tra tipi diversi.",
'tours.scope.partial': 'Somma incompleta',
'tours.scope.partialBody':
'Noto per {known} percorsi su {of}; gli altri non hanno un valore e restano fuori, quindi il totale è almeno questo.',
'tours.scope.routed': 'Tempo calcolato',
'tours.scope.imported': 'Tempo dalle importazioni',
'tours.scope.planned': 'Pianificato con pause',
'tours.scope.noMeasures': 'Nessuna distanza, quota o tempo ancora noto.',
'tours.copy.action': 'Salva come nuovo tour',
'tours.copy.hint': 'Copia questo tour come nuovo tour indipendente, senza i suoi giorni. L’originale resta com’è.',
'tours.copy.name': '{name} (copia)',
+10
View File
@@ -259,6 +259,16 @@ const tours: TranslationStrings = {
'tours.filter.type': '種類',
'tours.filter.empty': 'この条件に合うツアーはありません。',
'tours.filter.reset': 'フィルターをリセット',
'tours.scope.title': '旅の合計',
'tours.scope.info':
'下に表示されたツアーの種類ごとの合計です。フィルターは両方に適用されます。複数の日にあるツアーは 1 回だけ数えます。3 種類の時間は分けたままです:ルーティングが計算した時間、インポートに含まれていた時間、休憩を含めて計画した時間。種類をまたいで合算はしません。',
'tours.scope.partial': '不完全な合計',
'tours.scope.partialBody':
'{of} 件中 {known} 件のツアーで判明しています。残りは値がないため含まれず、合計は少なくともこの値です。',
'tours.scope.routed': 'ルート計算の時間',
'tours.scope.imported': 'インポートの時間',
'tours.scope.planned': '休憩込みの計画',
'tours.scope.noMeasures': '距離、高度、時間はまだわかりません。',
'tours.copy.action': '新しいツアーとして保存',
'tours.copy.hint': 'このツアーを日程なしの独立した新しいツアーとしてコピーします。元のツアーはそのままです。',
'tours.copy.name': '{name}(コピー)',
+10
View File
@@ -257,6 +257,16 @@ const tours: TranslationStrings = {
'tours.filter.type': '종류',
'tours.filter.empty': '이 필터에 맞는 투어가 없습니다.',
'tours.filter.reset': '필터 초기화',
'tours.scope.title': '여행 합계',
'tours.scope.info':
'아래 표시된 투어의 유형별 합계입니다. 필터는 둘 다에 적용됩니다. 여러 날에 있는 투어는 한 번만 셉니다. 세 가지 시간은 따로 유지됩니다: 경로 계산이 낸 시간, 가져오기에 포함된 시간, 휴식을 포함해 계획한 시간. 유형 간에는 더하지 않습니다.',
'tours.scope.partial': '불완전한 합계',
'tours.scope.partialBody':
'{of}개 투어 중 {known}개만 값이 있습니다. 나머지는 값이 없어 빠졌으므로 합계는 최소 이만큼입니다.',
'tours.scope.routed': '경로 계산 시간',
'tours.scope.imported': '가져오기 시간',
'tours.scope.planned': '휴식 포함 계획',
'tours.scope.noMeasures': '아직 알려진 거리, 고도, 시간이 없습니다.',
'tours.copy.action': '새 투어로 저장',
'tours.copy.hint': '이 투어를 날짜 없이 독립된 새 투어로 복사합니다. 원본은 그대로입니다.',
'tours.copy.name': '{name} (사본)',
+10
View File
@@ -272,6 +272,16 @@ const tours: TranslationStrings = {
'tours.filter.type': 'Soort',
'tours.filter.empty': 'Geen tocht past bij deze filters.',
'tours.filter.reset': 'Filters wissen',
'tours.scope.title': 'Totalen van de reis',
'tours.scope.info':
'Wat de hieronder getoonde tochten per type samen opleveren; het filter beperkt beide. Een tocht op meerdere dagen telt één keer. De drie soorten tijd blijven gescheiden: wat de routeplanner berekende, wat een import meebracht en wat met pauzes is gepland. Over types heen wordt niets opgeteld.',
'tours.scope.partial': 'Onvolledige som',
'tours.scope.partialBody':
'Bekend voor {known} van {of} tochten; de andere hebben geen waarde en blijven buiten beschouwing, dus het totaal is minstens zoveel.',
'tours.scope.routed': 'Berekende tijd',
'tours.scope.imported': 'Tijd uit imports',
'tours.scope.planned': 'Gepland met pauzes',
'tours.scope.noMeasures': 'Nog geen afstand, hoogtes of tijden bekend.',
'tours.copy.action': 'Opslaan als nieuwe tocht',
'tours.copy.hint':
'Kopieert deze tocht als nieuwe, zelfstandige tocht zonder zijn dagen. Het origineel blijft zoals het is.',
+10
View File
@@ -279,6 +279,16 @@ const tours: TranslationStrings = {
'tours.filter.type': 'Rodzaj',
'tours.filter.empty': 'Żadna trasa nie pasuje do tych filtrów.',
'tours.filter.reset': 'Wyczyść filtry',
'tours.scope.title': 'Podsumowanie podróży',
'tours.scope.info':
'Ile dają razem trasy pokazane poniżej, według typu; filtr zawęża jedno i drugie. Trasa na kilku dniach liczy się raz. Trzy rodzaje czasu pozostają osobno: to, co obliczyło wyznaczanie trasy, to, co przyniósł import, i to, co zaplanowano z przerwami. Nic nie jest sumowane między typami.',
'tours.scope.partial': 'Niepełna suma',
'tours.scope.partialBody':
'Znane dla {known} z {of} tras; pozostałe nie mają wartości i są pominięte, więc suma wynosi co najmniej tyle.',
'tours.scope.routed': 'Czas wyliczony',
'tours.scope.imported': 'Czas z importów',
'tours.scope.planned': 'Plan z przerwami',
'tours.scope.noMeasures': 'Brak jeszcze znanego dystansu, wysokości i czasów.',
'tours.copy.action': 'Zapisz jako nową trasę',
'tours.copy.hint': 'Kopiuje tę trasę jako nową, niezależną trasę bez jej dni. Oryginał zostaje bez zmian.',
'tours.copy.name': '{name} (kopia)',
+10
View File
@@ -282,6 +282,16 @@ const tours: TranslationStrings = {
'tours.filter.type': 'Тип',
'tours.filter.empty': 'Ни один поход не подходит под эти фильтры.',
'tours.filter.reset': 'Сбросить фильтры',
'tours.scope.title': 'Итоги поездки',
'tours.scope.info':
'Сколько в сумме дают маршруты ниже, по типам; фильтр ограничивает и то, и другое. Маршрут на нескольких днях считается один раз. Три вида времени остаются раздельными: рассчитанное построением маршрута, взятое из импорта и запланированное с перерывами. Между типами ничего не складывается.',
'tours.scope.partial': 'Неполная сумма',
'tours.scope.partialBody':
'Известно для {known} из {of} маршрутов; у остальных значения нет, и они не учтены, поэтому сумма не меньше этой.',
'tours.scope.routed': 'Расчётное время',
'tours.scope.imported': 'Время из импорта',
'tours.scope.planned': 'План с перерывами',
'tours.scope.noMeasures': 'Расстояние, высоты и время пока неизвестны.',
'tours.copy.action': 'Сохранить как новый поход',
'tours.copy.hint': 'Копирует этот поход как новый, самостоятельный поход без его дней. Оригинал остаётся как есть.',
'tours.copy.name': '{name} (копия)',
+10
View File
@@ -267,6 +267,16 @@ const tours: TranslationStrings = {
'tours.filter.type': 'Druh',
'tours.filter.empty': 'Týmto filtrom nezodpovedá žiadna túra.',
'tours.filter.reset': 'Zrušiť filtre',
'tours.scope.title': 'Rozsah cesty',
'tours.scope.info':
'Čo dávajú spolu túry zobrazené nižšie, podľa typu; filter obmedzuje oboje. Túra na viacerých dňoch sa počíta raz. Tri druhy času zostávajú oddelené: čo vypočítalo trasovanie, čo priniesol import a čo je naplánované s prestávkami. Naprieč typmi sa nič nesčítava.',
'tours.scope.partial': 'Neúplný súčet',
'tours.scope.partialBody':
'Známe pre {known} z {of} túr; ostatné hodnotu nemajú a chýbajú, súčet je teda najmenej toľko.',
'tours.scope.routed': 'Vypočítaný čas',
'tours.scope.imported': 'Čas z importov',
'tours.scope.planned': 'Plán s prestávkami',
'tours.scope.noMeasures': 'Zatiaľ nie je známa vzdialenosť, výšky ani časy.',
'tours.copy.action': 'Uložiť ako novú túru',
'tours.copy.hint': 'Skopíruje túto túru ako novú, samostatnú túru bez jej dní. Originál zostane, ako je.',
'tours.copy.name': '{name} (kópia)',
+10
View File
@@ -264,6 +264,16 @@ const tours: TranslationStrings = {
'tours.filter.type': 'Typ',
'tours.filter.empty': 'Ingen tur matchar de här filtren.',
'tours.filter.reset': 'Återställ filter',
'tours.scope.title': 'Resans omfattning',
'tours.scope.info':
'Vad turerna nedan blir tillsammans, per typ; filtret begränsar båda. En tur på flera dagar räknas en gång. De tre slagen av tid hålls isär: vad ruttplaneringen räknade fram, vad en import hade med sig och vad som är planerat med pauser. Inget läggs ihop mellan typer.',
'tours.scope.partial': 'Ofullständig summa',
'tours.scope.partialBody':
'Känt för {known} av {of} turer; de övriga saknar värde och räknas inte, så summan är minst så här mycket.',
'tours.scope.routed': 'Beräknad tid',
'tours.scope.imported': 'Tid från importer',
'tours.scope.planned': 'Planerat med pauser',
'tours.scope.noMeasures': 'Ingen sträcka, höjd eller tid känd ännu.',
'tours.copy.action': 'Spara som ny tur',
'tours.copy.hint': 'Kopierar turen som en ny, fristående tur utan dess dagar. Originalet förblir som det är.',
'tours.copy.name': '{name} (kopia)',
+10
View File
@@ -253,6 +253,16 @@ const tours: TranslationStrings = {
'tours.filter.type': 'ประเภท',
'tours.filter.empty': 'ไม่มีทัวร์ที่ตรงกับตัวกรองเหล่านี้',
'tours.filter.reset': 'รีเซ็ตตัวกรอง',
'tours.scope.title': 'ภาพรวมของทริป',
'tours.scope.info':
'ผลรวมของทัวร์ที่แสดงด้านล่างแยกตามประเภท ตัวกรองมีผลกับทั้งสองส่วน ทัวร์ที่อยู่หลายวันนับครั้งเดียว เวลาสามแบบแยกกันเสมอ: เวลาที่การคำนวณเส้นทางได้ เวลาที่มากับการนำเข้า และเวลาที่วางแผนรวมพัก ไม่มีการรวมข้ามประเภท',
'tours.scope.partial': 'ผลรวมไม่ครบ',
'tours.scope.partialBody':
'ทราบค่าของทัวร์ {known} จาก {of} รายการ ส่วนที่เหลือไม่มีค่าและไม่ได้นับ ผลรวมจึงอย่างน้อยเท่านี้',
'tours.scope.routed': 'เวลาจากการคำนวณเส้นทาง',
'tours.scope.imported': 'เวลาจากการนำเข้า',
'tours.scope.planned': 'วางแผนรวมพัก',
'tours.scope.noMeasures': 'ยังไม่ทราบระยะทาง ความสูง หรือเวลา',
'tours.copy.action': 'บันทึกเป็นทัวร์ใหม่',
'tours.copy.hint': 'คัดลอกทัวร์นี้เป็นทัวร์ใหม่ที่แยกเป็นอิสระโดยไม่รวมวัน ต้นฉบับจะคงเดิม',
'tours.copy.name': '{name} (สำเนา)',
+10
View File
@@ -266,6 +266,16 @@ const tours: TranslationStrings = {
'tours.filter.type': 'Tür',
'tours.filter.empty': 'Bu filtrelere uyan tur yok.',
'tours.filter.reset': 'Filtreleri sıfırla',
'tours.scope.title': 'Gezinin toplamı',
'tours.scope.info':
'Aşağıda gösterilen turların türe göre toplamı; filtre ikisini de daraltır. Birkaç günde olan bir tur bir kez sayılır. Üç zaman türü ayrı kalır: rotalamanın hesapladığı, bir içe aktarmanın getirdiği ve molalarla planlanan. Türler arasında hiçbir şey toplanmaz.',
'tours.scope.partial': 'Eksik toplam',
'tours.scope.partialBody':
'{of} turdan {known} için biliniyor; diğerlerinin değeri yok ve dışarıda kalıyor, bu yüzden toplam en az bu kadar.',
'tours.scope.routed': 'Hesaplanan süre',
'tours.scope.imported': 'İçe aktarmalardan süre',
'tours.scope.planned': 'Molalarla planlanan',
'tours.scope.noMeasures': 'Henüz bilinen mesafe, yükseklik veya süre yok.',
'tours.copy.action': 'Yeni tur olarak kaydet',
'tours.copy.hint': 'Bu turu günleri olmadan yeni ve bağımsız bir tur olarak kopyalar. Asıl tur olduğu gibi kalır.',
'tours.copy.name': '{name} (kopya)',
+10
View File
@@ -279,6 +279,16 @@ const tours: TranslationStrings = {
'tours.filter.type': 'Тип',
'tours.filter.empty': 'Жоден похід не відповідає цим фільтрам.',
'tours.filter.reset': 'Скинути фільтри',
'tours.scope.title': 'Підсумки подорожі',
'tours.scope.info':
'Скільки разом дають маршрути нижче, за типами; фільтр обмежує і те, і те. Маршрут на кількох днях рахується один раз. Три види часу лишаються окремо: розрахований побудовою маршруту, взятий з імпорту і запланований з перервами. Між типами нічого не додається.',
'tours.scope.partial': 'Неповна сума',
'tours.scope.partialBody':
'Відомо для {known} з {of} маршрутів; інші не мають значення й не враховані, тож сума щонайменше така.',
'tours.scope.routed': 'Розрахований час',
'tours.scope.imported': 'Час з імпорту',
'tours.scope.planned': 'План з перервами',
'tours.scope.noMeasures': 'Відстань, висоти й час поки невідомі.',
'tours.copy.action': 'Зберегти як новий похід',
'tours.copy.hint': 'Копіює цей похід як новий, самостійний похід без його днів. Оригінал лишається як є.',
'tours.copy.name': '{name} (копія)',
+10
View File
@@ -266,6 +266,16 @@ const tours: TranslationStrings = {
'tours.filter.type': 'Loại',
'tours.filter.empty': 'Không có chuyến đi bộ nào khớp với bộ lọc này.',
'tours.filter.reset': 'Đặt lại bộ lọc',
'tours.scope.title': 'Tổng của chuyến đi',
'tours.scope.info':
'Tổng của các chuyến hiển thị bên dưới, theo loại; bộ lọc thu hẹp cả hai. Một chuyến ở nhiều ngày chỉ tính một lần. Ba loại thời gian luôn tách riêng: thời gian định tuyến tính ra, thời gian đi kèm bản nhập và thời gian đã lên kế hoạch kể cả nghỉ. Không cộng gộp giữa các loại.',
'tours.scope.partial': 'Tổng chưa đầy đủ',
'tours.scope.partialBody':
'Đã biết cho {known} trên {of} chuyến; các chuyến còn lại không có giá trị và bị bỏ qua, nên tổng ít nhất là chừng này.',
'tours.scope.routed': 'Thời gian định tuyến',
'tours.scope.imported': 'Thời gian từ bản nhập',
'tours.scope.planned': 'Kế hoạch gồm nghỉ',
'tours.scope.noMeasures': 'Chưa biết quãng đường, độ cao hay thời gian.',
'tours.copy.action': 'Lưu thành chuyến đi bộ mới',
'tours.copy.hint': 'Sao chép chuyến này thành một chuyến đi bộ mới, độc lập, không kèm các ngày. Bản gốc giữ nguyên.',
'tours.copy.name': '{name} (bản sao)',
+9
View File
@@ -243,6 +243,15 @@ const tours: TranslationStrings = {
'tours.filter.type': '類型',
'tours.filter.empty': '沒有符合這些篩選條件的遊程。',
'tours.filter.reset': '重設篩選',
'tours.scope.title': '行程總覽',
'tours.scope.info':
'下方顯示的路線依類型的合計;篩選同時作用於兩者。在多天中的路線只計一次。三種時間分開顯示:路線規劃計算的時間、匯入自帶的時間,以及含休息的計畫時間。不同類型之間不相加。',
'tours.scope.partial': '不完整的合計',
'tours.scope.partialBody': '{of} 條路線中有 {known} 條有此值;其餘沒有數值未計入,因此合計至少為此數。',
'tours.scope.routed': '規劃時間',
'tours.scope.imported': '匯入時間',
'tours.scope.planned': '含休息的計畫',
'tours.scope.noMeasures': '尚無已知的距離、高度或時間。',
'tours.copy.action': '另存為新遊程',
'tours.copy.hint': '將此遊程複製為不含日程的獨立新遊程。原遊程保持不變。',
'tours.copy.name': '{name}(副本)',
+9
View File
@@ -244,6 +244,15 @@ const tours: TranslationStrings = {
'tours.filter.type': '类型',
'tours.filter.empty': '没有符合这些筛选条件的路线。',
'tours.filter.reset': '重置筛选',
'tours.scope.title': '行程汇总',
'tours.scope.info':
'下方显示的路线按类型的合计;筛选同时作用于两者。在多天中的路线只计一次。三种时间分开显示:路线规划计算的时间、导入自带的时间,以及含休息的计划时间。不同类型之间不相加。',
'tours.scope.partial': '不完整的合计',
'tours.scope.partialBody': '{of} 条路线中有 {known} 条有此值;其余没有数值未计入,因此合计至少为此数。',
'tours.scope.routed': '规划时间',
'tours.scope.imported': '导入时间',
'tours.scope.planned': '含休息的计划',
'tours.scope.noMeasures': '尚无已知的距离、高度或时间。',
'tours.copy.action': '另存为新路线',
'tours.copy.hint': '将此路线复制为不含日程的独立新路线。原路线保持不变。',
'tours.copy.name': '{name}(副本)',