diff --git a/client/src/components/Tours/TourListFilterMenu.tsx b/client/src/components/Tours/TourListFilterMenu.tsx
index ea702b629..54f352bc1 100644
--- a/client/src/components/Tours/TourListFilterMenu.tsx
+++ b/client/src/components/Tours/TourListFilterMenu.tsx
@@ -177,3 +177,26 @@ export default function TourListFilterMenu({ filters }: { filters: TourListFilte
);
}
+
+/** 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 (
+
+
+ {t('tours.filter.empty')}
+
+
+
+ );
+}
diff --git a/client/src/components/Tours/TourScopeSummary.tsx b/client/src/components/Tours/TourScopeSummary.tsx
new file mode 100644
index 000000000..227e04af2
--- /dev/null
+++ b/client/src/components/Tours/TourScopeSummary.tsx
@@ -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;
+
+const TYPE_ICON: Partial>> = {
+ 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 (
+
+ {value}
+ {label}
+
+ );
+}
+
+/**
+ * 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[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 (
+
+
+ )}
+
+ );
+}
+
+/**
+ * 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 (
+ }
+ title={t('tours.scope.title')}
+ aside={
+
+
+
+
+ }
+ >
+
+
+
+
+
+ {!folded && (
+
+ {scope.types.map((typeScope) => (
+
+ ))}
+
+ )}
+
+ );
+}
diff --git a/client/src/components/Tours/ToursSidebar.tsx b/client/src/components/Tours/ToursSidebar.tsx
index a289d8816..2f9f8d657 100644
--- a/client/src/components/Tours/ToursSidebar.tsx
+++ b/client/src/components/Tours/ToursSidebar.tsx
@@ -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
{!loading && filters.emptyByFilter ? (
-
-
{t('tours.filter.empty')}
-
-
+
) : !loading && filters.filtered.length === 0 ? (
{t('tours.empty.body')}} />
diff --git a/client/src/components/Tours/planner/TourPlannerPanels.test.tsx b/client/src/components/Tours/planner/TourPlannerPanels.test.tsx
index 853e21ee0..3790eb439 100644
--- a/client/src/components/Tours/planner/TourPlannerPanels.test.tsx
+++ b/client/src/components/Tours/planner/TourPlannerPanels.test.tsx
@@ -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()
})
diff --git a/client/src/components/Tours/planner/TourPlannerPanels.tsx b/client/src/components/Tours/planner/TourPlannerPanels.tsx
index e2d0fd51e..c2976bb2b 100644
--- a/client/src/components/Tours/planner/TourPlannerPanels.tsx
+++ b/client/src/components/Tours/planner/TourPlannerPanels.tsx
@@ -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> = {
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(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 ? (
diff --git a/client/src/components/Tours/tourScope.test.tsx b/client/src/components/Tours/tourScope.test.tsx
new file mode 100644
index 000000000..737129975
--- /dev/null
+++ b/client/src/components/Tours/tourScope.test.tsx
@@ -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 & Pick): 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();
+ 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();
+ fireEvent.click(screen.getByTestId('tour-scope-toggle'));
+ expect(screen.queryByTestId('tour-scope-hike')).toBeNull();
+ expect(screen.getByTestId('tour-scope-counts')).toBeInTheDocument();
+ unmount();
+ render();
+ 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();
+ expect(container).toBeEmptyDOMElement();
+ });
+});
diff --git a/client/src/components/Tours/tourScope.ts b/client/src/components/Tours/tourScope.ts
new file mode 100644
index 000000000..5756ba372
--- /dev/null
+++ b/client/src/components/Tours/tourScope.ts
@@ -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): 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();
+ 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,
+ };
+}
diff --git a/shared/src/i18n/ar/tours.ts b/shared/src/i18n/ar/tours.ts
index 6b525af6b..9478272fa 100644
--- a/shared/src/i18n/ar/tours.ts
+++ b/shared/src/i18n/ar/tours.ts
@@ -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} (نسخة)',
diff --git a/shared/src/i18n/az/tours.ts b/shared/src/i18n/az/tours.ts
index 5e010a181..0988d18f4 100644
--- a/shared/src/i18n/az/tours.ts
+++ b/shared/src/i18n/az/tours.ts
@@ -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)',
diff --git a/shared/src/i18n/br/tours.ts b/shared/src/i18n/br/tours.ts
index eefc7e45d..8baff857a 100644
--- a/shared/src/i18n/br/tours.ts
+++ b/shared/src/i18n/br/tours.ts
@@ -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á.',
diff --git a/shared/src/i18n/ca/tours.ts b/shared/src/i18n/ca/tours.ts
index e964226bd..cb985f4dd 100644
--- a/shared/src/i18n/ca/tours.ts
+++ b/shared/src/i18n/ca/tours.ts
@@ -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à.',
diff --git a/shared/src/i18n/cs/tours.ts b/shared/src/i18n/cs/tours.ts
index bc8bf6ee9..6cdff071d 100644
--- a/shared/src/i18n/cs/tours.ts
+++ b/shared/src/i18n/cs/tours.ts
@@ -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)',
diff --git a/shared/src/i18n/de/tours.ts b/shared/src/i18n/de/tours.ts
index 98d1e3a55..40762e373 100644
--- a/shared/src/i18n/de/tours.ts
+++ b/shared/src/i18n/de/tours.ts
@@ -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)',
diff --git a/shared/src/i18n/en/tours.ts b/shared/src/i18n/en/tours.ts
index 7319f7c72..e4a360063 100644
--- a/shared/src/i18n/en/tours.ts
+++ b/shared/src/i18n/en/tours.ts
@@ -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)',
diff --git a/shared/src/i18n/es/tours.ts b/shared/src/i18n/es/tours.ts
index ff859f017..0f9d9e958 100644
--- a/shared/src/i18n/es/tours.ts
+++ b/shared/src/i18n/es/tours.ts
@@ -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á.',
diff --git a/shared/src/i18n/et/tours.ts b/shared/src/i18n/et/tours.ts
index c5c09cccd..6fb649fea 100644
--- a/shared/src/i18n/et/tours.ts
+++ b/shared/src/i18n/et/tours.ts
@@ -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)',
diff --git a/shared/src/i18n/fr/tours.ts b/shared/src/i18n/fr/tours.ts
index d2f0f5019..b4086740e 100644
--- a/shared/src/i18n/fr/tours.ts
+++ b/shared/src/i18n/fr/tours.ts
@@ -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.',
diff --git a/shared/src/i18n/gr/tours.ts b/shared/src/i18n/gr/tours.ts
index 6a5fc06c8..f0711efa4 100644
--- a/shared/src/i18n/gr/tours.ts
+++ b/shared/src/i18n/gr/tours.ts
@@ -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':
'Αντιγράφει αυτή την εκδρομή ως νέα, ανεξάρτητη εκδρομή χωρίς τις μέρες της. Το πρωτότυπο μένει ως έχει.',
diff --git a/shared/src/i18n/hu/tours.ts b/shared/src/i18n/hu/tours.ts
index 047c02ea1..122fc3a73 100644
--- a/shared/src/i18n/hu/tours.ts
+++ b/shared/src/i18n/hu/tours.ts
@@ -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)',
diff --git a/shared/src/i18n/id/tours.ts b/shared/src/i18n/id/tours.ts
index 771afb08d..a2d4ff0b1 100644
--- a/shared/src/i18n/id/tours.ts
+++ b/shared/src/i18n/id/tours.ts
@@ -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)',
diff --git a/shared/src/i18n/it/tours.ts b/shared/src/i18n/it/tours.ts
index 6a5ab638a..ec17ac577 100644
--- a/shared/src/i18n/it/tours.ts
+++ b/shared/src/i18n/it/tours.ts
@@ -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)',
diff --git a/shared/src/i18n/ja/tours.ts b/shared/src/i18n/ja/tours.ts
index 2c0f12242..eec8e2324 100644
--- a/shared/src/i18n/ja/tours.ts
+++ b/shared/src/i18n/ja/tours.ts
@@ -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}(コピー)',
diff --git a/shared/src/i18n/ko/tours.ts b/shared/src/i18n/ko/tours.ts
index b15a1b7af..db996c209 100644
--- a/shared/src/i18n/ko/tours.ts
+++ b/shared/src/i18n/ko/tours.ts
@@ -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} (사본)',
diff --git a/shared/src/i18n/nl/tours.ts b/shared/src/i18n/nl/tours.ts
index 6a3c7802b..c6ed9ad58 100644
--- a/shared/src/i18n/nl/tours.ts
+++ b/shared/src/i18n/nl/tours.ts
@@ -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.',
diff --git a/shared/src/i18n/pl/tours.ts b/shared/src/i18n/pl/tours.ts
index 92d9cd2da..c8016d048 100644
--- a/shared/src/i18n/pl/tours.ts
+++ b/shared/src/i18n/pl/tours.ts
@@ -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)',
diff --git a/shared/src/i18n/ru/tours.ts b/shared/src/i18n/ru/tours.ts
index 84a98a2a2..b03d56a60 100644
--- a/shared/src/i18n/ru/tours.ts
+++ b/shared/src/i18n/ru/tours.ts
@@ -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} (копия)',
diff --git a/shared/src/i18n/sk/tours.ts b/shared/src/i18n/sk/tours.ts
index 62c7a9b2d..6eb8b7a7c 100644
--- a/shared/src/i18n/sk/tours.ts
+++ b/shared/src/i18n/sk/tours.ts
@@ -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)',
diff --git a/shared/src/i18n/sv/tours.ts b/shared/src/i18n/sv/tours.ts
index 43a1ebd1c..ab294dfca 100644
--- a/shared/src/i18n/sv/tours.ts
+++ b/shared/src/i18n/sv/tours.ts
@@ -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)',
diff --git a/shared/src/i18n/th/tours.ts b/shared/src/i18n/th/tours.ts
index 64ec0ac6b..d452534b3 100644
--- a/shared/src/i18n/th/tours.ts
+++ b/shared/src/i18n/th/tours.ts
@@ -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} (สำเนา)',
diff --git a/shared/src/i18n/tr/tours.ts b/shared/src/i18n/tr/tours.ts
index 164132c1d..04bd9d3c6 100644
--- a/shared/src/i18n/tr/tours.ts
+++ b/shared/src/i18n/tr/tours.ts
@@ -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)',
diff --git a/shared/src/i18n/uk/tours.ts b/shared/src/i18n/uk/tours.ts
index ef0325c55..00f93dd14 100644
--- a/shared/src/i18n/uk/tours.ts
+++ b/shared/src/i18n/uk/tours.ts
@@ -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} (копія)',
diff --git a/shared/src/i18n/vi/tours.ts b/shared/src/i18n/vi/tours.ts
index f8e5a5e79..284b8fc71 100644
--- a/shared/src/i18n/vi/tours.ts
+++ b/shared/src/i18n/vi/tours.ts
@@ -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)',
diff --git a/shared/src/i18n/zh-TW/tours.ts b/shared/src/i18n/zh-TW/tours.ts
index 5e9a891a9..1856d0a04 100644
--- a/shared/src/i18n/zh-TW/tours.ts
+++ b/shared/src/i18n/zh-TW/tours.ts
@@ -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}(副本)',
diff --git a/shared/src/i18n/zh/tours.ts b/shared/src/i18n/zh/tours.ts
index d364f3385..3a851600b 100644
--- a/shared/src/i18n/zh/tours.ts
+++ b/shared/src/i18n/zh/tours.ts
@@ -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}(副本)',