feat(atlas): sum up the tours you have done, by period and type (STA-02)

With the Tours addon on, the Atlas panel gets a third tab, Tours, beside
Stats and Bucket List: what you have done on your tours across all your
trips. A period (all time, this year, the last 12 months or an earlier year)
and a tour type narrow it; the tab shows the distance large with the number
of tours at its corner, the ascent and descent, and the types with their
distance. "Show tours" opens every tour of the period in a dialog with the
same filters, the sums, the split by type and one row per tour.

What counts is said, never guessed. A tour counts once the day it is on is
over, with the values of the planning, badged "Planned" and explained as
not measured. A linked actual track (ACT-01) replaces those values for its
execution, badged "Recorded" and named, and is never added on top nor
counted as a tour of its own. A tour on several days counts once, a whole
route does not count beside the stages planned from it, a paddle tour sums
no heights, and a value nobody knows stays out of the sum, which then reads
"at least". Only the user's own part counts: a day names them among its
participants, or names nobody on a trip of their own; being able to open
somebody else's trip makes none of its tours theirs. Tours still ahead, on
no day or only on undated days are named as not counting. A tour that fell
through can be left out of the sums ("Leave out"): it stays in the plan
and in the list, dimmed, and "Count again" brings it back. Only the user's
own numbers change.

Server: GET /api/tours/activity-stats answers the user's activity entries
across every trip they can open, and PUT /api/tours/activity-stats/exclusions
leaves a tour out or counts it again (the tour must be in such a trip, else
404). A tour_activity_exclusions table keeps the exclusions per user and
tour. The sums are added up by a shared summary in @trek/shared, so the
Atlas and the assistant say the same numbers. MCP: get_tour_activity_stats
and set_tour_activity_excluded over the same service. The tab reads live
and needs a connection, like the rest of the Atlas statistics.
This commit is contained in:
Maurice
2026-10-20 13:43:12 +02:00
parent ae79ab8dad
commit bde2ad2ca8
76 changed files with 4717 additions and 16 deletions
+32
View File
@@ -1260,5 +1260,37 @@ const tours: TranslationStrings = {
'tours.activity.compare.show': 'قارن على الخريطة',
'tours.activity.compare.showHint': 'يرسم المسار الفعلي منقطًا بجانب المخطط. لا يتغير شيء.',
'tours.activity.compare.hide': 'إنهاء المقارنة',
'tours.stats.title': 'جولاتي المنجزة',
'tours.stats.sub': 'بناءً على تخطيط رحلتك؛ وتحل المسارات الفعلية المرتبطة محله',
'tours.stats.basisInfo':
'تُحتسب الجولة بعد انتهاء يومها، بقيم تخطيطك. ويحل المسار الفعلي المرتبط محلها. لا تُحتسب إلا الأيام التي تشارك فيها.',
'tours.stats.basisPlan': 'مخطط',
'tours.stats.basisPlanHint': 'من تخطيط الرحلة، غير مقيس',
'tours.stats.basisTrack': 'مسجّل',
'tours.stats.basisTrackHint': 'من المسار الفعلي المرتبط {name}',
'tours.stats.basisTrackCountHint': 'تحل المسارات الفعلية المرتبطة محل القيم المخططة',
'tours.stats.period': 'الفترة',
'tours.stats.period.all': 'كل الأوقات',
'tours.stats.period.thisYear': 'هذا العام',
'tours.stats.period.last12': 'آخر 12 شهرًا',
'tours.stats.allTypes': 'كل الأنواع',
'tours.stats.byType': 'حسب النوع',
'tours.stats.showTours': 'عرض الجولات',
'tours.stats.none': 'لا جولات منجزة في هذه الفترة',
'tours.stats.empty': 'لا جولات منجزة بعد',
'tours.stats.upcoming': 'لم تأتِ بعد',
'tours.stats.upcomingHint': 'تُحتسب الجولة بعد انتهاء يومها',
'tours.stats.unassigned': 'بلا يوم',
'tours.stats.unassignedHint': 'جولات رحلاتك التي ليست في أي يوم لا تُحتسب',
'tours.stats.undated': 'بلا تاريخ',
'tours.stats.undatedHint': 'الجولات في أيام بلا تاريخ لا تُحتسب',
'tours.stats.multiDayHint': 'مخططة على عدة أيام، تُحتسب مرة واحدة',
'tours.stats.exclude': 'عدم الاحتساب',
'tours.stats.excludeHint': 'لجولة أُلغيت. تبقى في الخطة وتتوقف فقط عن الاحتساب هنا',
'tours.stats.include': 'احتسابها مجددًا',
'tours.stats.excluded': 'غير محتسبة',
'tours.stats.excludeFailed': 'تعذر تغيير الجولة',
'tours.stats.unavailable': 'تعذر تحميل الجولات المنجزة',
'tours.stats.openTrip': 'فتح الرحلة',
};
export default tours;
+32
View File
@@ -1228,5 +1228,37 @@ const tours: TranslationStrings = {
'tours.activity.compare.show': 'Xəritədə müqayisə et',
'tours.activity.compare.showHint': 'Faktiki izi planın yanında nöqtəli çəkir. Heç nə dəyişmir.',
'tours.activity.compare.hide': 'Müqayisəni bitir',
'tours.stats.title': 'Tamamladığım turlar',
'tours.stats.sub': 'Səyahət planına əsasən; əlaqələndirilmiş faktiki izlər onu əvəz edir',
'tours.stats.basisInfo':
'Tur günü bitəndə planlamanın dəyərləri ilə sayılır. Əlaqələndirilmiş faktiki iz onları əvəz edir. Yalnız iştirak etdiyin günlər sayılır.',
'tours.stats.basisPlan': 'Planlaşdırılıb',
'tours.stats.basisPlanHint': 'Səyahət planından, ölçülməyib',
'tours.stats.basisTrack': 'Qeydə alınıb',
'tours.stats.basisTrackHint': 'Əlaqələndirilmiş faktiki izdən: {name}',
'tours.stats.basisTrackCountHint': 'Əlaqələndirilmiş faktiki izlər planlaşdırılmış dəyərləri əvəz edir',
'tours.stats.period': 'Dövr',
'tours.stats.period.all': 'Bütün dövr',
'tours.stats.period.thisYear': 'Bu il',
'tours.stats.period.last12': 'Son 12 ay',
'tours.stats.allTypes': 'Bütün növlər',
'tours.stats.byType': 'Növə görə',
'tours.stats.showTours': 'Turları göstər',
'tours.stats.none': 'Bu dövrdə tamamlanmış tur yoxdur',
'tours.stats.empty': 'Hələ tamamlanmış tur yoxdur',
'tours.stats.upcoming': 'Hələ qarşıda',
'tours.stats.upcomingHint': 'Tur günü bitəndə sayılır',
'tours.stats.unassigned': 'Günsüz',
'tours.stats.unassignedHint': 'Səyahətlərinin heç bir gününə qoyulmamış turlar sayılmır',
'tours.stats.undated': 'Tarixsiz',
'tours.stats.undatedHint': 'Tarixi olmayan günlərdəki turlar sayılmır',
'tours.stats.multiDayHint': 'Bir neçə günə planlaşdırılıb, bir dəfə sayılıb',
'tours.stats.exclude': 'Sayma',
'tours.stats.excludeHint': 'Baş tutmayan tur üçün. Planda qalır, sadəcə burada sayılmır',
'tours.stats.include': 'Yenidən say',
'tours.stats.excluded': 'Sayılmayıb',
'tours.stats.excludeFailed': 'Turu dəyişmək mümkün olmadı',
'tours.stats.unavailable': 'Tamamlanmış turları yükləmək mümkün olmadı',
'tours.stats.openTrip': 'Səyahəti aç',
};
export default tours;
+32
View File
@@ -1240,5 +1240,37 @@ const tours: TranslationStrings = {
'tours.activity.compare.show': 'Comparar no mapa',
'tours.activity.compare.showHint': 'Desenha a trilha real pontilhada ao lado do plano. Nada muda.',
'tours.activity.compare.hide': 'Encerrar comparação',
'tours.stats.title': 'Meus passeios realizados',
'tours.stats.sub': 'Com base no planejamento da viagem; trilhas reais vinculadas o substituem',
'tours.stats.basisInfo':
'Um passeio conta quando o dia dele termina, com os valores do seu planejamento. Uma trilha real vinculada os substitui. Só contam os dias em que você participa.',
'tours.stats.basisPlan': 'Planejado',
'tours.stats.basisPlanHint': 'Do planejamento da viagem, não medido',
'tours.stats.basisTrack': 'Gravado',
'tours.stats.basisTrackHint': 'Da trilha real vinculada {name}',
'tours.stats.basisTrackCountHint': 'Trilhas reais vinculadas substituem os valores planejados',
'tours.stats.period': 'Período',
'tours.stats.period.all': 'Desde sempre',
'tours.stats.period.thisYear': 'Este ano',
'tours.stats.period.last12': 'Últimos 12 meses',
'tours.stats.allTypes': 'Todos os tipos',
'tours.stats.byType': 'Por tipo',
'tours.stats.showTours': 'Ver passeios',
'tours.stats.none': 'Nenhum passeio concluído neste período',
'tours.stats.empty': 'Ainda nenhum passeio concluído',
'tours.stats.upcoming': 'Ainda por vir',
'tours.stats.upcomingHint': 'Um passeio conta quando o dia dele termina',
'tours.stats.unassigned': 'Sem dia',
'tours.stats.unassignedHint': 'Passeios das suas viagens que não estão em nenhum dia não contam',
'tours.stats.undated': 'Sem data',
'tours.stats.undatedHint': 'Passeios em dias sem data não contam',
'tours.stats.multiDayHint': 'Planejado em vários dias, contado uma vez',
'tours.stats.exclude': 'Não contar',
'tours.stats.excludeHint': 'Para um passeio que não aconteceu. Ele continua no plano e só deixa de contar aqui',
'tours.stats.include': 'Contar de novo',
'tours.stats.excluded': 'Não contado',
'tours.stats.excludeFailed': 'Não foi possível alterar o passeio',
'tours.stats.unavailable': 'Não foi possível carregar os passeios realizados',
'tours.stats.openTrip': 'Abrir viagem',
};
export default tours;
+32
View File
@@ -1247,5 +1247,37 @@ const tours: TranslationStrings = {
'tours.activity.compare.show': 'Compara al mapa',
'tours.activity.compare.showHint': 'Dibuixa el traç real puntejat al costat del pla. No canvia res.',
'tours.activity.compare.hide': 'Acaba la comparació',
'tours.stats.title': 'Les meves excursions fetes',
'tours.stats.sub': 'Segons la planificació del viatge; els traços reals vinculats la substitueixen',
'tours.stats.basisInfo':
'Una excursió compta quan el seu dia s’ha acabat, amb els valors de la teva planificació. Un traç real vinculat els substitueix. Només compten els dies en què participes.',
'tours.stats.basisPlan': 'Planificat',
'tours.stats.basisPlanHint': 'De la planificació del viatge, no mesurat',
'tours.stats.basisTrack': 'Enregistrat',
'tours.stats.basisTrackHint': 'Del traç real vinculat {name}',
'tours.stats.basisTrackCountHint': 'Els traços reals vinculats substitueixen els valors planificats',
'tours.stats.period': 'Període',
'tours.stats.period.all': 'Des de sempre',
'tours.stats.period.thisYear': 'Aquest any',
'tours.stats.period.last12': 'Últims 12 mesos',
'tours.stats.allTypes': 'Tots els tipus',
'tours.stats.byType': 'Per tipus',
'tours.stats.showTours': 'Mostra les excursions',
'tours.stats.none': 'Cap excursió acabada en aquest període',
'tours.stats.empty': 'Encara cap excursió acabada',
'tours.stats.upcoming': 'Per fer',
'tours.stats.upcomingHint': 'Una excursió compta quan el seu dia s’ha acabat',
'tours.stats.unassigned': 'Sense dia',
'tours.stats.unassignedHint': 'Les excursions dels teus viatges que no són en cap dia no compten',
'tours.stats.undated': 'Sense data',
'tours.stats.undatedHint': 'Les excursions en dies sense data no compten',
'tours.stats.multiDayHint': 'Planificada en diversos dies, comptada un cop',
'tours.stats.exclude': 'No comptar',
'tours.stats.excludeHint': 'Per a una excursió que no es va fer. Es queda al pla i només deixa de comptar aquí',
'tours.stats.include': 'Torna a comptar',
'tours.stats.excluded': 'No comptada',
'tours.stats.excludeFailed': 'No s’ha pogut canviar l’excursió',
'tours.stats.unavailable': 'No s’han pogut carregar les excursions fetes',
'tours.stats.openTrip': 'Obre el viatge',
};
export default tours;
+32
View File
@@ -1226,5 +1226,37 @@ const tours: TranslationStrings = {
'tours.activity.compare.show': 'Porovnat na mapě',
'tours.activity.compare.showHint': 'Vykreslí skutečnou trasu tečkovaně vedle plánu. Nic se nemění.',
'tours.activity.compare.hide': 'Ukončit porovnání',
'tours.stats.title': 'Moje absolvované výlety',
'tours.stats.sub': 'Podle plánu cesty; propojené skutečné trasy ho nahrazují',
'tours.stats.basisInfo':
'Výlet se započítá, jakmile jeho den skončí, s hodnotami z tvého plánu. Propojená skutečná trasa je nahradí. Počítají se jen dny, kterých se účastníš.',
'tours.stats.basisPlan': 'Plánováno',
'tours.stats.basisPlanHint': 'Z plánu cesty, neměřeno',
'tours.stats.basisTrack': 'Zaznamenáno',
'tours.stats.basisTrackHint': 'Z propojené skutečné trasy {name}',
'tours.stats.basisTrackCountHint': 'Propojené skutečné trasy nahrazují plánované hodnoty',
'tours.stats.period': 'Období',
'tours.stats.period.all': 'Celkem',
'tours.stats.period.thisYear': 'Letos',
'tours.stats.period.last12': 'Posledních 12 měsíců',
'tours.stats.allTypes': 'Všechny typy',
'tours.stats.byType': 'Podle druhu',
'tours.stats.showTours': 'Zobrazit výlety',
'tours.stats.none': 'V tomto období žádné absolvované výlety',
'tours.stats.empty': 'Zatím žádné absolvované výlety',
'tours.stats.upcoming': 'Teprve čeká',
'tours.stats.upcomingHint': 'Výlet se započítá, jakmile jeho den skončí',
'tours.stats.unassigned': 'Bez dne',
'tours.stats.unassignedHint': 'Výlety tvých cest, které nejsou v žádném dni, se nepočítají',
'tours.stats.undated': 'Bez data',
'tours.stats.undatedHint': 'Výlety ve dnech bez data se nepočítají',
'tours.stats.multiDayHint': 'Naplánováno na více dní, započteno jednou',
'tours.stats.exclude': 'Nezapočítávat',
'tours.stats.excludeHint': 'Pro výlet, který se nekonal. Zůstane v plánu a jen se tu přestane počítat',
'tours.stats.include': 'Znovu započítat',
'tours.stats.excluded': 'Nezapočteno',
'tours.stats.excludeFailed': 'Výlet se nepodařilo změnit',
'tours.stats.unavailable': 'Absolvované výlety se nepodařilo načíst',
'tours.stats.openTrip': 'Otevřít cestu',
};
export default tours;
+32
View File
@@ -1254,6 +1254,38 @@ const tours: TranslationStrings = {
'tours.activity.compare.show': 'Auf der Karte vergleichen',
'tours.activity.compare.showHint': 'Zeichnet den Ist-Track gepunktet neben die Planung. Es ändert sich nichts.',
'tours.activity.compare.hide': 'Vergleich beenden',
'tours.stats.title': 'Meine Touraktivitäten',
'tours.stats.sub': 'Auf Basis deiner Reiseplanung; verknüpfte Ist-Tracks ersetzen sie',
'tours.stats.basisInfo':
'Eine Tour zählt, sobald ihr Tag vorbei ist, mit den Werten deiner Planung. Ein verknüpfter Ist-Track ersetzt sie. Es zählen nur Tage, an denen du teilnimmst.',
'tours.stats.basisPlan': 'Geplant',
'tours.stats.basisPlanHint': 'Aus deiner Reiseplanung, nicht gemessen',
'tours.stats.basisTrack': 'Aufgezeichnet',
'tours.stats.basisTrackHint': 'Aus dem verknüpften Ist-Track {name}',
'tours.stats.basisTrackCountHint': 'Verknüpfte Ist-Tracks ersetzen die geplanten Werte',
'tours.stats.period': 'Zeitraum',
'tours.stats.period.all': 'Gesamt',
'tours.stats.period.thisYear': 'Dieses Jahr',
'tours.stats.period.last12': 'Letzte 12 Monate',
'tours.stats.allTypes': 'Alle Arten',
'tours.stats.byType': 'Nach Art',
'tours.stats.showTours': 'Touren anzeigen',
'tours.stats.none': 'Keine abgeschlossenen Touren in diesem Zeitraum',
'tours.stats.empty': 'Noch keine abgeschlossenen Touren',
'tours.stats.upcoming': 'Noch offen',
'tours.stats.upcomingHint': 'Eine Tour zählt, sobald ihr Tag vorbei ist',
'tours.stats.unassigned': 'Ohne Tag',
'tours.stats.unassignedHint': 'Touren deiner Reisen, die auf keinem Tag liegen, zählen nicht',
'tours.stats.undated': 'Ohne Datum',
'tours.stats.undatedHint': 'Touren auf Tagen ohne Datum zählen nicht',
'tours.stats.multiDayHint': 'An mehreren Tagen geplant, einmal gezählt',
'tours.stats.exclude': 'Nicht mitzählen',
'tours.stats.excludeHint': 'Für eine ausgefallene Tour. Sie bleibt im Plan und zählt nur hier nicht mehr',
'tours.stats.include': 'Wieder mitzählen',
'tours.stats.excluded': 'Nicht gezählt',
'tours.stats.excludeFailed': 'Die Tour konnte nicht geändert werden',
'tours.stats.unavailable': 'Touraktivitäten konnten nicht geladen werden',
'tours.stats.openTrip': 'Reise öffnen',
};
export default tours;
+32
View File
@@ -1215,5 +1215,37 @@ const tours: TranslationStrings = {
'tours.activity.compare.show': 'Compare on map',
'tours.activity.compare.showHint': 'Draws the actual track dotted beside the plan. Nothing changes.',
'tours.activity.compare.hide': 'End comparison',
'tours.stats.title': 'My tour activities',
'tours.stats.sub': 'Based on your trip planning; linked actual tracks replace it',
'tours.stats.basisInfo':
'A tour counts once its day is over, with the values of your planning. A linked actual track replaces them. Only days you take part in count.',
'tours.stats.basisPlan': 'Planned',
'tours.stats.basisPlanHint': 'From your trip planning, not measured',
'tours.stats.basisTrack': 'Recorded',
'tours.stats.basisTrackHint': 'From the linked actual track {name}',
'tours.stats.basisTrackCountHint': 'Linked actual tracks replace the planned values',
'tours.stats.period': 'Period',
'tours.stats.period.all': 'All time',
'tours.stats.period.thisYear': 'This year',
'tours.stats.period.last12': 'Last 12 months',
'tours.stats.allTypes': 'All types',
'tours.stats.byType': 'By type',
'tours.stats.showTours': 'Show tours',
'tours.stats.none': 'No completed tours in this period',
'tours.stats.empty': 'No completed tours yet',
'tours.stats.upcoming': 'Still ahead',
'tours.stats.upcomingHint': 'A tour counts once its day is over',
'tours.stats.unassigned': 'On no day',
'tours.stats.unassignedHint': 'Tours of your trips that are on no day do not count',
'tours.stats.undated': 'No date',
'tours.stats.undatedHint': 'Tours on days without a date do not count',
'tours.stats.multiDayHint': 'Planned on several days, counted once',
'tours.stats.exclude': 'Leave out',
'tours.stats.excludeHint': 'For a tour that fell through. It stays in the plan and only stops counting here',
'tours.stats.include': 'Count again',
'tours.stats.excluded': 'Left out',
'tours.stats.excludeFailed': 'The tour could not be changed',
'tours.stats.unavailable': 'Tour activities could not be loaded',
'tours.stats.openTrip': 'Open trip',
};
export default tours;
+32
View File
@@ -1240,5 +1240,37 @@ const tours: TranslationStrings = {
'tours.activity.compare.show': 'Comparar en el mapa',
'tours.activity.compare.showHint': 'Dibuja el track real punteado junto al plan. No cambia nada.',
'tours.activity.compare.hide': 'Terminar la comparación',
'tours.stats.title': 'Mis excursiones realizadas',
'tours.stats.sub': 'Según la planificación de tu viaje; los tracks reales vinculados la sustituyen',
'tours.stats.basisInfo':
'Una excursión cuenta cuando su día ha terminado, con los valores de tu planificación. Un track real vinculado los sustituye. Solo cuentan los días en los que participas.',
'tours.stats.basisPlan': 'Planificado',
'tours.stats.basisPlanHint': 'Según la planificación del viaje, no medido',
'tours.stats.basisTrack': 'Grabado',
'tours.stats.basisTrackHint': 'Del track real vinculado {name}',
'tours.stats.basisTrackCountHint': 'Los tracks reales vinculados sustituyen los valores planificados',
'tours.stats.period': 'Periodo',
'tours.stats.period.all': 'Desde siempre',
'tours.stats.period.thisYear': 'Este año',
'tours.stats.period.last12': 'Últimos 12 meses',
'tours.stats.allTypes': 'Todos los tipos',
'tours.stats.byType': 'Por tipo',
'tours.stats.showTours': 'Ver excursiones',
'tours.stats.none': 'No hay excursiones terminadas en este periodo',
'tours.stats.empty': 'Todavía no hay excursiones terminadas',
'tours.stats.upcoming': 'Pendientes',
'tours.stats.upcomingHint': 'Una excursión cuenta cuando su día ha terminado',
'tours.stats.unassigned': 'Sin día',
'tours.stats.unassignedHint': 'Las excursiones de tus viajes que no están en ningún día no cuentan',
'tours.stats.undated': 'Sin fecha',
'tours.stats.undatedHint': 'Las excursiones en días sin fecha no cuentan',
'tours.stats.multiDayHint': 'Planificada en varios días, contada una vez',
'tours.stats.exclude': 'No contar',
'tours.stats.excludeHint': 'Para una excursión que se canceló. Sigue en el plan y solo deja de contar aquí',
'tours.stats.include': 'Volver a contar',
'tours.stats.excluded': 'No contada',
'tours.stats.excludeFailed': 'No se pudo cambiar la excursión',
'tours.stats.unavailable': 'No se pudieron cargar las excursiones realizadas',
'tours.stats.openTrip': 'Abrir viaje',
};
export default tours;
+32
View File
@@ -1224,5 +1224,37 @@ const tours: TranslationStrings = {
'tours.activity.compare.show': 'Võrdle kaardil',
'tours.activity.compare.showHint': 'Joonistab tegeliku raja plaani kõrvale punktiirina. Midagi ei muutu.',
'tours.activity.compare.hide': 'Lõpeta võrdlus',
'tours.stats.title': 'Minu tehtud retked',
'tours.stats.sub': 'Reisi planeerimise põhjal; lingitud tegelikud rajad asendavad selle',
'tours.stats.basisInfo':
'Retk läheb arvesse, kui selle päev on läbi, sinu planeerimise väärtustega. Lingitud tegelik rada asendab need. Arvesse lähevad ainult päevad, millest võtad osa.',
'tours.stats.basisPlan': 'Planeeritud',
'tours.stats.basisPlanHint': 'Reisi planeerimisest, mõõtmata',
'tours.stats.basisTrack': 'Salvestatud',
'tours.stats.basisTrackHint': 'Lingitud tegelikult rajalt {name}',
'tours.stats.basisTrackCountHint': 'Lingitud tegelikud rajad asendavad planeeritud väärtused',
'tours.stats.period': 'Ajavahemik',
'tours.stats.period.all': 'Kogu aeg',
'tours.stats.period.thisYear': 'Sel aastal',
'tours.stats.period.last12': 'Viimased 12 kuud',
'tours.stats.allTypes': 'Kõik tüübid',
'tours.stats.byType': 'Liigi järgi',
'tours.stats.showTours': 'Näita retki',
'tours.stats.none': 'Selles ajavahemikus pole tehtud retki',
'tours.stats.empty': 'Tehtud retki veel pole',
'tours.stats.upcoming': 'Veel ees',
'tours.stats.upcomingHint': 'Retk läheb arvesse, kui selle päev on läbi',
'tours.stats.unassigned': 'Päevata',
'tours.stats.unassignedHint': 'Sinu reiside retked, mis pole üheski päevas, ei lähe arvesse',
'tours.stats.undated': 'Kuupäevata',
'tours.stats.undatedHint': 'Kuupäevata päevade retked ei lähe arvesse',
'tours.stats.multiDayHint': 'Planeeritud mitmele päevale, arvestatud üks kord',
'tours.stats.exclude': 'Jäta välja',
'tours.stats.excludeHint': 'Ära jäänud retke jaoks. See jääb plaani ja lakkab ainult siin arvesse minemast',
'tours.stats.include': 'Arvesta uuesti',
'tours.stats.excluded': 'Välja jäetud',
'tours.stats.excludeFailed': 'Retke ei saanud muuta',
'tours.stats.unavailable': 'Tehtud retki ei saanud laadida',
'tours.stats.openTrip': 'Ava reis',
};
export default tours;
+32
View File
@@ -1264,5 +1264,37 @@ const tours: TranslationStrings = {
'tours.activity.compare.show': 'Comparer sur la carte',
'tours.activity.compare.showHint': 'Dessine la trace réelle en pointillés à côté du plan. Rien ne change.',
'tours.activity.compare.hide': 'Terminer la comparaison',
'tours.stats.title': 'Mes sorties réalisées',
'tours.stats.sub': 'D’après la planification de ton voyage ; les traces réelles liées la remplacent',
'tours.stats.basisInfo':
'Une sortie compte une fois sa journée terminée, avec les valeurs de ta planification. Une trace réelle liée les remplace. Seuls les jours auxquels tu participes comptent.',
'tours.stats.basisPlan': 'Planifié',
'tours.stats.basisPlanHint': 'D’après la planification du voyage, non mesuré',
'tours.stats.basisTrack': 'Enregistré',
'tours.stats.basisTrackHint': 'D’après la trace réelle liée {name}',
'tours.stats.basisTrackCountHint': 'Les traces réelles liées remplacent les valeurs planifiées',
'tours.stats.period': 'Période',
'tours.stats.period.all': 'Depuis le début',
'tours.stats.period.thisYear': 'Cette année',
'tours.stats.period.last12': '12 derniers mois',
'tours.stats.allTypes': 'Tous les types',
'tours.stats.byType': 'Par type',
'tours.stats.showTours': 'Voir les sorties',
'tours.stats.none': 'Aucune sortie terminée sur cette période',
'tours.stats.empty': 'Aucune sortie terminée pour l’instant',
'tours.stats.upcoming': 'À venir',
'tours.stats.upcomingHint': 'Une sortie compte une fois sa journée terminée',
'tours.stats.unassigned': 'Sans jour',
'tours.stats.unassignedHint': 'Les sorties de tes voyages placées sur aucun jour ne comptent pas',
'tours.stats.undated': 'Sans date',
'tours.stats.undatedHint': 'Les sorties sur des jours sans date ne comptent pas',
'tours.stats.multiDayHint': 'Planifiée sur plusieurs jours, comptée une fois',
'tours.stats.exclude': 'Ne pas compter',
'tours.stats.excludeHint': 'Pour une sortie annulée. Elle reste dans le plan et cesse seulement de compter ici',
'tours.stats.include': 'Compter à nouveau',
'tours.stats.excluded': 'Non comptée',
'tours.stats.excludeFailed': 'La sortie n’a pas pu être modifiée',
'tours.stats.unavailable': 'Les sorties réalisées n’ont pas pu être chargées',
'tours.stats.openTrip': 'Ouvrir le voyage',
};
export default tours;
+32
View File
@@ -1267,5 +1267,37 @@ const tours: TranslationStrings = {
'tours.activity.compare.showHint':
'Σχεδιάζει την πραγματική διαδρομή με τελείες δίπλα στο σχέδιο. Τίποτα δεν αλλάζει.',
'tours.activity.compare.hide': 'Τέλος σύγκρισης',
'tours.stats.title': 'Οι εκδρομές μου',
'tours.stats.sub': 'Με βάση τον σχεδιασμό του ταξιδιού· οι συνδεδεμένες πραγματικές διαδρομές τον αντικαθιστούν',
'tours.stats.basisInfo':
'Μια εκδρομή μετράει μόλις τελειώσει η μέρα της, με τις τιμές του σχεδιασμού σου. Μια συνδεδεμένη πραγματική διαδρομή τις αντικαθιστά. Μετράνε μόνο οι μέρες στις οποίες συμμετέχεις.',
'tours.stats.basisPlan': 'Σχεδιασμένο',
'tours.stats.basisPlanHint': 'Από τον σχεδιασμό του ταξιδιού, χωρίς μέτρηση',
'tours.stats.basisTrack': 'Καταγεγραμμένο',
'tours.stats.basisTrackHint': 'Από τη συνδεδεμένη πραγματική διαδρομή {name}',
'tours.stats.basisTrackCountHint': 'Οι συνδεδεμένες πραγματικές διαδρομές αντικαθιστούν τις σχεδιασμένες τιμές',
'tours.stats.period': 'Περίοδος',
'tours.stats.period.all': 'Όλη η περίοδος',
'tours.stats.period.thisYear': 'Φέτος',
'tours.stats.period.last12': 'Τελευταίοι 12 μήνες',
'tours.stats.allTypes': 'Όλοι οι τύποι',
'tours.stats.byType': 'Ανά είδος',
'tours.stats.showTours': 'Εμφάνιση εκδρομών',
'tours.stats.none': 'Καμία ολοκληρωμένη εκδρομή σε αυτή την περίοδο',
'tours.stats.empty': 'Καμία ολοκληρωμένη εκδρομή ακόμα',
'tours.stats.upcoming': 'Επερχόμενες',
'tours.stats.upcomingHint': 'Μια εκδρομή μετράει μόλις τελειώσει η μέρα της',
'tours.stats.unassigned': 'Χωρίς μέρα',
'tours.stats.unassignedHint': 'Οι εκδρομές των ταξιδιών σου που δεν είναι σε καμία μέρα δεν μετράνε',
'tours.stats.undated': 'Χωρίς ημερομηνία',
'tours.stats.undatedHint': 'Οι εκδρομές σε μέρες χωρίς ημερομηνία δεν μετράνε',
'tours.stats.multiDayHint': 'Σχεδιασμένη σε πολλές μέρες, μετριέται μία φορά',
'tours.stats.exclude': 'Να μη μετράει',
'tours.stats.excludeHint': 'Για μια εκδρομή που δεν έγινε. Μένει στο σχέδιο και απλώς σταματά να μετράει εδώ',
'tours.stats.include': 'Να μετράει ξανά',
'tours.stats.excluded': 'Δεν μετράει',
'tours.stats.excludeFailed': 'Δεν ήταν δυνατή η αλλαγή της εκδρομής',
'tours.stats.unavailable': 'Δεν ήταν δυνατή η φόρτωση των εκδρομών',
'tours.stats.openTrip': 'Άνοιγμα ταξιδιού',
};
export default tours;
+32
View File
@@ -1245,5 +1245,37 @@ const tours: TranslationStrings = {
'tours.activity.compare.show': 'Összehasonlítás a térképen',
'tours.activity.compare.showHint': 'A tényleges nyomvonalat pontozva rajzolja a terv mellé. Semmi sem változik.',
'tours.activity.compare.hide': 'Összehasonlítás befejezése',
'tours.stats.title': 'Teljesített túráim',
'tours.stats.sub': 'Az utazás tervezése alapján; az összekapcsolt tényleges nyomvonalak felváltják',
'tours.stats.basisInfo':
'Egy túra akkor számít, ha a napja véget ért, a tervezésed értékeivel. Egy összekapcsolt tényleges nyomvonal felváltja ezeket. Csak azok a napok számítanak, amelyeken részt veszel.',
'tours.stats.basisPlan': 'Tervezett',
'tours.stats.basisPlanHint': 'Az utazás tervezéséből, nem mért',
'tours.stats.basisTrack': 'Rögzített',
'tours.stats.basisTrackHint': 'Az összekapcsolt tényleges nyomvonalból: {name}',
'tours.stats.basisTrackCountHint': 'Az összekapcsolt tényleges nyomvonalak felváltják a tervezett értékeket',
'tours.stats.period': 'Időszak',
'tours.stats.period.all': 'Mindenkori',
'tours.stats.period.thisYear': 'Idén',
'tours.stats.period.last12': 'Elmúlt 12 hónap',
'tours.stats.allTypes': 'Minden típus',
'tours.stats.byType': 'Fajta szerint',
'tours.stats.showTours': 'Túrák megjelenítése',
'tours.stats.none': 'Ebben az időszakban nincs teljesített túra',
'tours.stats.empty': 'Még nincs teljesített túra',
'tours.stats.upcoming': 'Még hátravan',
'tours.stats.upcomingHint': 'Egy túra akkor számít, ha a napja véget ért',
'tours.stats.unassigned': 'Nap nélkül',
'tours.stats.unassignedHint': 'Az utazásaid egyetlen napon sem szereplő túrái nem számítanak',
'tours.stats.undated': 'Dátum nélkül',
'tours.stats.undatedHint': 'A dátum nélküli napokon lévő túrák nem számítanak',
'tours.stats.multiDayHint': 'Több napra tervezve, egyszer számolva',
'tours.stats.exclude': 'Ne számítson',
'tours.stats.excludeHint': 'Elmaradt túrához. A tervben marad, csak itt nem számít tovább',
'tours.stats.include': 'Számítson újra',
'tours.stats.excluded': 'Nem számít',
'tours.stats.excludeFailed': 'A túrát nem sikerült módosítani',
'tours.stats.unavailable': 'A teljesített túrákat nem sikerült betölteni',
'tours.stats.openTrip': 'Utazás megnyitása',
};
export default tours;
+32
View File
@@ -1226,5 +1226,37 @@ const tours: TranslationStrings = {
'tours.activity.compare.show': 'Bandingkan di peta',
'tours.activity.compare.showHint': 'Menggambar jejak aktual bertitik di samping rencana. Tidak ada yang berubah.',
'tours.activity.compare.hide': 'Akhiri perbandingan',
'tours.stats.title': 'Tur yang sudah kulakukan',
'tours.stats.sub': 'Berdasarkan perencanaan perjalananmu; jejak aktual yang ditautkan menggantikannya',
'tours.stats.basisInfo':
'Tur dihitung setelah harinya berakhir, dengan nilai dari perencanaanmu. Jejak aktual yang ditautkan menggantikannya. Hanya hari yang kamu ikuti yang dihitung.',
'tours.stats.basisPlan': 'Direncanakan',
'tours.stats.basisPlanHint': 'Dari perencanaan perjalanan, tidak diukur',
'tours.stats.basisTrack': 'Terekam',
'tours.stats.basisTrackHint': 'Dari jejak aktual yang ditautkan {name}',
'tours.stats.basisTrackCountHint': 'Jejak aktual yang ditautkan menggantikan nilai rencana',
'tours.stats.period': 'Periode',
'tours.stats.period.all': 'Sepanjang waktu',
'tours.stats.period.thisYear': 'Tahun ini',
'tours.stats.period.last12': '12 bulan terakhir',
'tours.stats.allTypes': 'Semua jenis',
'tours.stats.byType': 'Menurut jenis',
'tours.stats.showTours': 'Tampilkan tur',
'tours.stats.none': 'Tidak ada tur selesai pada periode ini',
'tours.stats.empty': 'Belum ada tur yang selesai',
'tours.stats.upcoming': 'Masih akan datang',
'tours.stats.upcomingHint': 'Tur dihitung setelah harinya berakhir',
'tours.stats.unassigned': 'Tanpa hari',
'tours.stats.unassignedHint': 'Tur di perjalananmu yang tidak berada di hari mana pun tidak dihitung',
'tours.stats.undated': 'Tanpa tanggal',
'tours.stats.undatedHint': 'Tur pada hari tanpa tanggal tidak dihitung',
'tours.stats.multiDayHint': 'Direncanakan di beberapa hari, dihitung sekali',
'tours.stats.exclude': 'Jangan hitung',
'tours.stats.excludeHint': 'Untuk tur yang batal. Tur tetap ada di rencana dan hanya berhenti dihitung di sini',
'tours.stats.include': 'Hitung lagi',
'tours.stats.excluded': 'Tidak dihitung',
'tours.stats.excludeFailed': 'Tur tidak dapat diubah',
'tours.stats.unavailable': 'Tur yang sudah dilakukan tidak dapat dimuat',
'tours.stats.openTrip': 'Buka perjalanan',
};
export default tours;
+32
View File
@@ -1254,5 +1254,37 @@ const tours: TranslationStrings = {
'tours.activity.compare.show': 'Confronta sulla mappa',
'tours.activity.compare.showHint': 'Disegna la traccia effettiva a puntini accanto al piano. Non cambia nulla.',
'tours.activity.compare.hide': 'Termina il confronto',
'tours.stats.title': 'Le mie escursioni svolte',
'tours.stats.sub': 'In base alla pianificazione del viaggio; le tracce effettive collegate la sostituiscono',
'tours.stats.basisInfo':
'Un’escursione conta quando il suo giorno è finito, con i valori della tua pianificazione. Una traccia effettiva collegata li sostituisce. Contano solo i giorni a cui partecipi.',
'tours.stats.basisPlan': 'Pianificato',
'tours.stats.basisPlanHint': 'Dalla pianificazione del viaggio, non misurato',
'tours.stats.basisTrack': 'Registrato',
'tours.stats.basisTrackHint': 'Dalla traccia effettiva collegata {name}',
'tours.stats.basisTrackCountHint': 'Le tracce effettive collegate sostituiscono i valori pianificati',
'tours.stats.period': 'Periodo',
'tours.stats.period.all': 'Da sempre',
'tours.stats.period.thisYear': 'Quest’anno',
'tours.stats.period.last12': 'Ultimi 12 mesi',
'tours.stats.allTypes': 'Tutti i tipi',
'tours.stats.byType': 'Per tipo',
'tours.stats.showTours': 'Mostra escursioni',
'tours.stats.none': 'Nessuna escursione conclusa in questo periodo',
'tours.stats.empty': 'Ancora nessuna escursione conclusa',
'tours.stats.upcoming': 'Ancora da fare',
'tours.stats.upcomingHint': 'Un’escursione conta quando il suo giorno è finito',
'tours.stats.unassigned': 'Senza giorno',
'tours.stats.unassignedHint': 'Le escursioni dei tuoi viaggi che non sono in nessun giorno non contano',
'tours.stats.undated': 'Senza data',
'tours.stats.undatedHint': 'Le escursioni nei giorni senza data non contano',
'tours.stats.multiDayHint': 'Pianificata su più giorni, contata una volta',
'tours.stats.exclude': 'Non contare',
'tours.stats.excludeHint': 'Per un’escursione saltata. Resta nel piano e smette solo di contare qui',
'tours.stats.include': 'Conta di nuovo',
'tours.stats.excluded': 'Non contata',
'tours.stats.excludeFailed': 'Non è stato possibile modificare l’escursione',
'tours.stats.unavailable': 'Non è stato possibile caricare le escursioni svolte',
'tours.stats.openTrip': 'Apri viaggio',
};
export default tours;
+32
View File
@@ -1190,5 +1190,37 @@ const tours: TranslationStrings = {
'tours.activity.compare.show': '地図で比較',
'tours.activity.compare.showHint': '実際のトラックを予定の横に点線で描きます。何も変更されません。',
'tours.activity.compare.hide': '比較を終了',
'tours.stats.title': '自分のツアー実績',
'tours.stats.sub': '旅行の計画に基づく値。リンクした実際のトラックがあればそちらに置き換わります',
'tours.stats.basisInfo':
'ツアーはその日が終わると、計画の値で集計されます。リンクした実際のトラックがあればその値に置き換わります。参加している日だけが集計されます。',
'tours.stats.basisPlan': '計画',
'tours.stats.basisPlanHint': '旅行の計画による値で、計測値ではありません',
'tours.stats.basisTrack': '記録',
'tours.stats.basisTrackHint': 'リンクした実際のトラック {name} の値',
'tours.stats.basisTrackCountHint': 'リンクした実際のトラックが計画の値に置き換わります',
'tours.stats.period': '期間',
'tours.stats.period.all': '全期間',
'tours.stats.period.thisYear': '今年',
'tours.stats.period.last12': '過去12か月',
'tours.stats.allTypes': 'すべての種類',
'tours.stats.byType': '種類別',
'tours.stats.showTours': 'ツアーを表示',
'tours.stats.none': 'この期間に完了したツアーはありません',
'tours.stats.empty': '完了したツアーはまだありません',
'tours.stats.upcoming': 'これから',
'tours.stats.upcomingHint': 'ツアーはその日が終わると集計されます',
'tours.stats.unassigned': '日なし',
'tours.stats.unassignedHint': 'どの日にも入っていない旅行のツアーは集計されません',
'tours.stats.undated': '日付なし',
'tours.stats.undatedHint': '日付のない日のツアーは集計されません',
'tours.stats.multiDayHint': '複数の日に計画、1回として集計',
'tours.stats.exclude': '集計しない',
'tours.stats.excludeHint': '中止になったツアー向け。計画には残り、ここで集計されなくなるだけです',
'tours.stats.include': '再び集計',
'tours.stats.excluded': '集計外',
'tours.stats.excludeFailed': 'ツアーを変更できませんでした',
'tours.stats.unavailable': 'ツアー実績を読み込めませんでした',
'tours.stats.openTrip': '旅行を開く',
};
export default tours;
+32
View File
@@ -1188,5 +1188,37 @@ const tours: TranslationStrings = {
'tours.activity.compare.show': '지도에서 비교',
'tours.activity.compare.showHint': '실제 트랙을 계획 옆에 점선으로 그립니다. 아무것도 바뀌지 않습니다.',
'tours.activity.compare.hide': '비교 종료',
'tours.stats.title': '나의 투어 기록',
'tours.stats.sub': '여행 계획 기준이며, 연결된 실제 트랙이 있으면 그것으로 대체됩니다',
'tours.stats.basisInfo':
'투어는 해당 날짜가 지나면 계획 값으로 집계됩니다. 연결된 실제 트랙이 있으면 그 값으로 대체됩니다. 참여하는 날만 집계됩니다.',
'tours.stats.basisPlan': '계획',
'tours.stats.basisPlanHint': '여행 계획의 값이며 측정값이 아닙니다',
'tours.stats.basisTrack': '기록',
'tours.stats.basisTrackHint': '연결된 실제 트랙 {name}의 값',
'tours.stats.basisTrackCountHint': '연결된 실제 트랙이 계획 값을 대체합니다',
'tours.stats.period': '기간',
'tours.stats.period.all': '전체 기간',
'tours.stats.period.thisYear': '올해',
'tours.stats.period.last12': '최근 12개월',
'tours.stats.allTypes': '모든 유형',
'tours.stats.byType': '종류별',
'tours.stats.showTours': '투어 보기',
'tours.stats.none': '이 기간에 완료한 투어가 없습니다',
'tours.stats.empty': '아직 완료한 투어가 없습니다',
'tours.stats.upcoming': '예정',
'tours.stats.upcomingHint': '투어는 해당 날짜가 지나면 집계됩니다',
'tours.stats.unassigned': '날짜 미지정',
'tours.stats.unassignedHint': '어느 날에도 배정되지 않은 여행의 투어는 집계되지 않습니다',
'tours.stats.undated': '날짜 없음',
'tours.stats.undatedHint': '날짜가 없는 날의 투어는 집계되지 않습니다',
'tours.stats.multiDayHint': '여러 날에 계획, 한 번만 집계',
'tours.stats.exclude': '집계 제외',
'tours.stats.excludeHint': '취소된 투어용입니다. 계획에는 남아 있고 여기서만 집계되지 않습니다',
'tours.stats.include': '다시 집계',
'tours.stats.excluded': '집계 제외됨',
'tours.stats.excludeFailed': '투어를 변경할 수 없습니다',
'tours.stats.unavailable': '투어 기록을 불러올 수 없습니다',
'tours.stats.openTrip': '여행 열기',
};
export default tours;
+33
View File
@@ -1247,5 +1247,38 @@ const tours: TranslationStrings = {
'tours.activity.compare.show': 'Vergelijken op de kaart',
'tours.activity.compare.showHint': 'Tekent de werkelijke track gestippeld naast de planning. Er verandert niets.',
'tours.activity.compare.hide': 'Vergelijking beëindigen',
'tours.stats.title': 'Mijn gemaakte tochten',
'tours.stats.sub': 'Op basis van je reisplanning; gekoppelde werkelijke tracks vervangen die',
'tours.stats.basisInfo':
'Een tocht telt zodra zijn dag voorbij is, met de waarden van je planning. Een gekoppelde werkelijke track vervangt ze. Alleen dagen waaraan je meedoet tellen.',
'tours.stats.basisPlan': 'Gepland',
'tours.stats.basisPlanHint': 'Uit je reisplanning, niet gemeten',
'tours.stats.basisTrack': 'Opgenomen',
'tours.stats.basisTrackHint': 'Uit de gekoppelde werkelijke track {name}',
'tours.stats.basisTrackCountHint': 'Gekoppelde werkelijke tracks vervangen de geplande waarden',
'tours.stats.period': 'Periode',
'tours.stats.period.all': 'Altijd',
'tours.stats.period.thisYear': 'Dit jaar',
'tours.stats.period.last12': 'Afgelopen 12 maanden',
'tours.stats.allTypes': 'Alle soorten',
'tours.stats.byType': 'Per soort',
'tours.stats.showTours': 'Tochten tonen',
'tours.stats.none': 'Geen afgeronde tochten in deze periode',
'tours.stats.empty': 'Nog geen afgeronde tochten',
'tours.stats.upcoming': 'Nog te gaan',
'tours.stats.upcomingHint': 'Een tocht telt zodra zijn dag voorbij is',
'tours.stats.unassigned': 'Zonder dag',
'tours.stats.unassignedHint': 'Tochten van je reizen die op geen enkele dag staan, tellen niet',
'tours.stats.undated': 'Zonder datum',
'tours.stats.undatedHint': 'Tochten op dagen zonder datum tellen niet',
'tours.stats.multiDayHint': 'Op meerdere dagen gepland, één keer geteld',
'tours.stats.exclude': 'Niet meetellen',
'tours.stats.excludeHint':
'Voor een tocht die niet doorging. Hij blijft in de planning en telt alleen hier niet meer',
'tours.stats.include': 'Weer meetellen',
'tours.stats.excluded': 'Niet geteld',
'tours.stats.excludeFailed': 'De tocht kon niet worden gewijzigd',
'tours.stats.unavailable': 'Gemaakte tochten konden niet worden geladen',
'tours.stats.openTrip': 'Reis openen',
};
export default tours;
+32
View File
@@ -1268,5 +1268,37 @@ const tours: TranslationStrings = {
'tours.activity.compare.show': 'Porównaj na mapie',
'tours.activity.compare.showHint': 'Rysuje rzeczywistą trasę kropkami obok planu. Nic się nie zmienia.',
'tours.activity.compare.hide': 'Zakończ porównanie',
'tours.stats.title': 'Moje odbyte wycieczki',
'tours.stats.sub': 'Na podstawie planu podróży; połączone rzeczywiste trasy go zastępują',
'tours.stats.basisInfo':
'Wycieczka liczy się, gdy jej dzień się skończy, z wartościami z twojego planu. Połączona rzeczywista trasa je zastępuje. Liczą się tylko dni, w których bierzesz udział.',
'tours.stats.basisPlan': 'Zaplanowane',
'tours.stats.basisPlanHint': 'Z planu podróży, nie zmierzone',
'tours.stats.basisTrack': 'Nagrane',
'tours.stats.basisTrackHint': 'Z połączonej rzeczywistej trasy {name}',
'tours.stats.basisTrackCountHint': 'Połączone rzeczywiste trasy zastępują zaplanowane wartości',
'tours.stats.period': 'Okres',
'tours.stats.period.all': 'Od początku',
'tours.stats.period.thisYear': 'Ten rok',
'tours.stats.period.last12': 'Ostatnie 12 miesięcy',
'tours.stats.allTypes': 'Wszystkie typy',
'tours.stats.byType': 'Według rodzaju',
'tours.stats.showTours': 'Pokaż wycieczki',
'tours.stats.none': 'Brak ukończonych wycieczek w tym okresie',
'tours.stats.empty': 'Brak ukończonych wycieczek',
'tours.stats.upcoming': 'Jeszcze przed tobą',
'tours.stats.upcomingHint': 'Wycieczka liczy się, gdy jej dzień się skończy',
'tours.stats.unassigned': 'Bez dnia',
'tours.stats.unassignedHint': 'Wycieczki twoich podróży, które nie są w żadnym dniu, się nie liczą',
'tours.stats.undated': 'Bez daty',
'tours.stats.undatedHint': 'Wycieczki w dniach bez daty się nie liczą',
'tours.stats.multiDayHint': 'Zaplanowana na kilka dni, liczona raz',
'tours.stats.exclude': 'Nie liczyć',
'tours.stats.excludeHint': 'Dla wycieczki, która się nie odbyła. Zostaje w planie i tylko tutaj przestaje się liczyć',
'tours.stats.include': 'Licz ponownie',
'tours.stats.excluded': 'Nieliczona',
'tours.stats.excludeFailed': 'Nie udało się zmienić wycieczki',
'tours.stats.unavailable': 'Nie udało się wczytać odbytych wycieczek',
'tours.stats.openTrip': 'Otwórz podróż',
};
export default tours;
+32
View File
@@ -1275,5 +1275,37 @@ const tours: TranslationStrings = {
'tours.activity.compare.show': 'Сравнить на карте',
'tours.activity.compare.showHint': 'Рисует фактический трек пунктиром рядом с планом. Ничего не меняется.',
'tours.activity.compare.hide': 'Завершить сравнение',
'tours.stats.title': 'Мои пройденные туры',
'tours.stats.sub': 'По планированию поездки; привязанные фактические треки заменяют его',
'tours.stats.basisInfo':
'Тур засчитывается, когда его день закончился, со значениями из твоего планирования. Привязанный фактический трек их заменяет. Учитываются только дни, в которых ты участвуешь.',
'tours.stats.basisPlan': 'Запланировано',
'tours.stats.basisPlanHint': 'Из планирования поездки, не измерено',
'tours.stats.basisTrack': 'Записано',
'tours.stats.basisTrackHint': 'Из привязанного фактического трека {name}',
'tours.stats.basisTrackCountHint': 'Привязанные фактические треки заменяют запланированные значения',
'tours.stats.period': 'Период',
'tours.stats.period.all': 'За всё время',
'tours.stats.period.thisYear': 'В этом году',
'tours.stats.period.last12': 'Последние 12 месяцев',
'tours.stats.allTypes': 'Все типы',
'tours.stats.byType': 'По типам',
'tours.stats.showTours': 'Показать туры',
'tours.stats.none': 'Нет пройденных туров за этот период',
'tours.stats.empty': 'Пока нет пройденных туров',
'tours.stats.upcoming': 'Ещё впереди',
'tours.stats.upcomingHint': 'Тур засчитывается, когда его день закончился',
'tours.stats.unassigned': 'Без дня',
'tours.stats.unassignedHint': 'Туры твоих поездок, которые не стоят ни на одном дне, не учитываются',
'tours.stats.undated': 'Без даты',
'tours.stats.undatedHint': 'Туры в днях без даты не учитываются',
'tours.stats.multiDayHint': 'Запланирован на несколько дней, учтён один раз',
'tours.stats.exclude': 'Не учитывать',
'tours.stats.excludeHint': 'Для тура, который не состоялся. Он остаётся в плане и только перестаёт учитываться здесь',
'tours.stats.include': 'Учитывать снова',
'tours.stats.excluded': 'Не учтён',
'tours.stats.excludeFailed': 'Не удалось изменить тур',
'tours.stats.unavailable': 'Не удалось загрузить пройденные туры',
'tours.stats.openTrip': 'Открыть поездку',
};
export default tours;
+32
View File
@@ -1230,5 +1230,37 @@ const tours: TranslationStrings = {
'tours.activity.compare.show': 'Porovnať na mape',
'tours.activity.compare.showHint': 'Vykreslí skutočnú trasu bodkovane vedľa plánu. Nič sa nemení.',
'tours.activity.compare.hide': 'Ukončiť porovnanie',
'tours.stats.title': 'Moje absolvované túry',
'tours.stats.sub': 'Podľa plánu cesty; prepojené skutočné trasy ho nahrádzajú',
'tours.stats.basisInfo':
'Túra sa započíta, keď jej deň skončí, s hodnotami z tvojho plánu. Prepojená skutočná trasa ich nahradí. Počítajú sa len dni, na ktorých sa zúčastňuješ.',
'tours.stats.basisPlan': 'Plánované',
'tours.stats.basisPlanHint': 'Z plánu cesty, nemerané',
'tours.stats.basisTrack': 'Zaznamenané',
'tours.stats.basisTrackHint': 'Z prepojenej skutočnej trasy {name}',
'tours.stats.basisTrackCountHint': 'Prepojené skutočné trasy nahrádzajú plánované hodnoty',
'tours.stats.period': 'Obdobie',
'tours.stats.period.all': 'Celkovo',
'tours.stats.period.thisYear': 'Tento rok',
'tours.stats.period.last12': 'Posledných 12 mesiacov',
'tours.stats.allTypes': 'Všetky typy',
'tours.stats.byType': 'Podľa druhu',
'tours.stats.showTours': 'Zobraziť túry',
'tours.stats.none': 'V tomto období žiadne absolvované túry',
'tours.stats.empty': 'Zatiaľ žiadne absolvované túry',
'tours.stats.upcoming': 'Ešte pred tebou',
'tours.stats.upcomingHint': 'Túra sa započíta, keď jej deň skončí',
'tours.stats.unassigned': 'Bez dňa',
'tours.stats.unassignedHint': 'Túry tvojich ciest, ktoré nie sú v žiadnom dni, sa nepočítajú',
'tours.stats.undated': 'Bez dátumu',
'tours.stats.undatedHint': 'Túry v dňoch bez dátumu sa nepočítajú',
'tours.stats.multiDayHint': 'Naplánovaná na viac dní, započítaná raz',
'tours.stats.exclude': 'Nezapočítavať',
'tours.stats.excludeHint': 'Pre túru, ktorá sa nekonala. Zostane v pláne a len sa tu prestane počítať',
'tours.stats.include': 'Znova započítať',
'tours.stats.excluded': 'Nezapočítané',
'tours.stats.excludeFailed': 'Túru sa nepodarilo zmeniť',
'tours.stats.unavailable': 'Absolvované túry sa nepodarilo načítať',
'tours.stats.openTrip': 'Otvoriť cestu',
};
export default tours;
+32
View File
@@ -1219,5 +1219,37 @@ const tours: TranslationStrings = {
'tours.activity.compare.show': 'Jämför på kartan',
'tours.activity.compare.showHint': 'Ritar det faktiska spåret prickat bredvid planen. Inget ändras.',
'tours.activity.compare.hide': 'Avsluta jämförelsen',
'tours.stats.title': 'Mina genomförda turer',
'tours.stats.sub': 'Utifrån din reseplanering; länkade faktiska spår ersätter den',
'tours.stats.basisInfo':
'En tur räknas när dess dag är över, med värdena från din planering. Ett länkat faktiskt spår ersätter dem. Bara dagar du deltar i räknas.',
'tours.stats.basisPlan': 'Planerat',
'tours.stats.basisPlanHint': 'Från reseplaneringen, inte uppmätt',
'tours.stats.basisTrack': 'Inspelat',
'tours.stats.basisTrackHint': 'Från det länkade faktiska spåret {name}',
'tours.stats.basisTrackCountHint': 'Länkade faktiska spår ersätter de planerade värdena',
'tours.stats.period': 'Period', // same-as-en
'tours.stats.period.all': 'Hela tiden',
'tours.stats.period.thisYear': 'I år',
'tours.stats.period.last12': 'Senaste 12 månaderna',
'tours.stats.allTypes': 'Alla typer',
'tours.stats.byType': 'Per typ',
'tours.stats.showTours': 'Visa turer',
'tours.stats.none': 'Inga genomförda turer under den här perioden',
'tours.stats.empty': 'Inga genomförda turer än',
'tours.stats.upcoming': 'Ligger framför',
'tours.stats.upcomingHint': 'En tur räknas när dess dag är över',
'tours.stats.unassigned': 'Utan dag',
'tours.stats.unassignedHint': 'Turer i dina resor som inte ligger på någon dag räknas inte',
'tours.stats.undated': 'Utan datum',
'tours.stats.undatedHint': 'Turer på dagar utan datum räknas inte',
'tours.stats.multiDayHint': 'Planerad på flera dagar, räknad en gång',
'tours.stats.exclude': 'Räkna inte',
'tours.stats.excludeHint': 'För en tur som blev inställd. Den finns kvar i planen och slutar bara räknas här',
'tours.stats.include': 'Räkna igen',
'tours.stats.excluded': 'Inte räknad',
'tours.stats.excludeFailed': 'Turen kunde inte ändras',
'tours.stats.unavailable': 'Genomförda turer kunde inte laddas',
'tours.stats.openTrip': 'Öppna resan',
};
export default tours;
+32
View File
@@ -1171,5 +1171,37 @@ const tours: TranslationStrings = {
'tours.activity.compare.show': 'เปรียบเทียบบนแผนที่',
'tours.activity.compare.showHint': 'วาดเส้นทางจริงเป็นจุดข้างแผน ไม่มีอะไรเปลี่ยน',
'tours.activity.compare.hide': 'จบการเปรียบเทียบ',
'tours.stats.title': 'ทัวร์ที่ฉันทำแล้ว',
'tours.stats.sub': 'อิงจากการวางแผนทริป เส้นทางจริงที่เชื่อมไว้จะใช้แทน',
'tours.stats.basisInfo':
'ทัวร์จะนับเมื่อวันของทัวร์ผ่านไปแล้ว โดยใช้ค่าจากการวางแผนของคุณ เส้นทางจริงที่เชื่อมไว้จะใช้แทนค่าเหล่านั้น นับเฉพาะวันที่คุณเข้าร่วม',
'tours.stats.basisPlan': 'ตามแผน',
'tours.stats.basisPlanHint': 'จากการวางแผนทริป ไม่ได้วัดจริง',
'tours.stats.basisTrack': 'บันทึกจริง',
'tours.stats.basisTrackHint': 'จากเส้นทางจริงที่เชื่อมไว้ {name}',
'tours.stats.basisTrackCountHint': 'เส้นทางจริงที่เชื่อมไว้ใช้แทนค่าตามแผน',
'tours.stats.period': 'ช่วงเวลา',
'tours.stats.period.all': 'ทั้งหมด',
'tours.stats.period.thisYear': 'ปีนี้',
'tours.stats.period.last12': '12 เดือนล่าสุด',
'tours.stats.allTypes': 'ทุกประเภท',
'tours.stats.byType': 'ตามประเภท',
'tours.stats.showTours': 'แสดงทัวร์',
'tours.stats.none': 'ไม่มีทัวร์ที่เสร็จแล้วในช่วงนี้',
'tours.stats.empty': 'ยังไม่มีทัวร์ที่เสร็จแล้ว',
'tours.stats.upcoming': 'ยังมาไม่ถึง',
'tours.stats.upcomingHint': 'ทัวร์จะนับเมื่อวันของทัวร์ผ่านไปแล้ว',
'tours.stats.unassigned': 'ไม่มีวัน',
'tours.stats.unassignedHint': 'ทัวร์ในทริปของคุณที่ไม่ได้อยู่ในวันใดจะไม่ถูกนับ',
'tours.stats.undated': 'ไม่มีวันที่',
'tours.stats.undatedHint': 'ทัวร์ในวันที่ไม่มีวันที่จะไม่ถูกนับ',
'tours.stats.multiDayHint': 'วางแผนไว้หลายวัน นับครั้งเดียว',
'tours.stats.exclude': 'ไม่นับ',
'tours.stats.excludeHint': 'สำหรับทัวร์ที่ยกเลิก ทัวร์ยังอยู่ในแผนและเพียงไม่ถูกนับที่นี่',
'tours.stats.include': 'นับอีกครั้ง',
'tours.stats.excluded': 'ไม่นับแล้ว',
'tours.stats.excludeFailed': 'เปลี่ยนทัวร์ไม่ได้',
'tours.stats.unavailable': 'โหลดทัวร์ที่ทำแล้วไม่ได้',
'tours.stats.openTrip': 'เปิดทริป',
};
export default tours;
+1 -1
View File
@@ -20,7 +20,7 @@ const tokens = (value: string) => [...value.matchAll(/\{[^{}]+\}/g)].map((match)
// placeholder spec; here only the general keys are.
const groups = pluralGroups(Object.keys(english));
const generalKeys = (keys: string[]) => keys.filter((key) => !pluralFormOf(key, groups));
const GENERAL_KEY_COUNT = 1113;
const GENERAL_KEY_COUNT = 1144;
function literalKeys(source: string): string[] {
const tree = ts.createSourceFile('locale.ts', source, ts.ScriptTarget.Latest, true);
+32
View File
@@ -1226,5 +1226,37 @@ const tours: TranslationStrings = {
'tours.activity.compare.show': 'Haritada karşılaştır',
'tours.activity.compare.showHint': 'Gerçek izi planın yanına noktalı çizer. Hiçbir şey değişmez.',
'tours.activity.compare.hide': 'Karşılaştırmayı bitir',
'tours.stats.title': 'Tamamladığım turlar',
'tours.stats.sub': 'Seyahat planına göre; bağlanan gerçek izler onun yerini alır',
'tours.stats.basisInfo':
'Bir tur, günü bittiğinde planlamandaki değerlerle sayılır. Bağlanan gerçek bir iz bunların yerini alır. Yalnızca katıldığın günler sayılır.',
'tours.stats.basisPlan': 'Planlanan',
'tours.stats.basisPlanHint': 'Seyahat planından, ölçülmedi',
'tours.stats.basisTrack': 'Kaydedilen',
'tours.stats.basisTrackHint': 'Bağlanan gerçek izden: {name}',
'tours.stats.basisTrackCountHint': 'Bağlanan gerçek izler planlanan değerlerin yerini alır',
'tours.stats.period': 'Dönem',
'tours.stats.period.all': 'Tüm zamanlar',
'tours.stats.period.thisYear': 'Bu yıl',
'tours.stats.period.last12': 'Son 12 ay',
'tours.stats.allTypes': 'Tüm türler',
'tours.stats.byType': 'Türe göre',
'tours.stats.showTours': 'Turları göster',
'tours.stats.none': 'Bu dönemde tamamlanan tur yok',
'tours.stats.empty': 'Henüz tamamlanan tur yok',
'tours.stats.upcoming': 'Önünde',
'tours.stats.upcomingHint': 'Bir tur, günü bittiğinde sayılır',
'tours.stats.unassigned': 'Günsüz',
'tours.stats.unassignedHint': 'Seyahatlerinde hiçbir günde olmayan turlar sayılmaz',
'tours.stats.undated': 'Tarihsiz',
'tours.stats.undatedHint': 'Tarihi olmayan günlerdeki turlar sayılmaz',
'tours.stats.multiDayHint': 'Birkaç güne planlandı, bir kez sayıldı',
'tours.stats.exclude': 'Sayma',
'tours.stats.excludeHint': 'Gerçekleşmeyen bir tur için. Planda kalır, yalnızca burada sayılmayı bırakır',
'tours.stats.include': 'Yeniden say',
'tours.stats.excluded': 'Sayılmadı',
'tours.stats.excludeFailed': 'Tur değiştirilemedi',
'tours.stats.unavailable': 'Tamamlanan turlar yüklenemedi',
'tours.stats.openTrip': 'Seyahati aç',
};
export default tours;
+32
View File
@@ -1261,5 +1261,37 @@ const tours: TranslationStrings = {
'tours.activity.compare.show': 'Порівняти на карті',
'tours.activity.compare.showHint': 'Малює фактичний трек пунктиром поруч із планом. Нічого не змінюється.',
'tours.activity.compare.hide': 'Завершити порівняння',
'tours.stats.title': 'Мої пройдені тури',
'tours.stats.sub': 'За плануванням подорожі; прив’язані фактичні треки замінюють його',
'tours.stats.basisInfo':
'Тур зараховується, коли його день завершився, зі значеннями з твого планування. Прив’язаний фактичний трек їх замінює. Враховуються лише дні, у яких ти береш участь.',
'tours.stats.basisPlan': 'Заплановано',
'tours.stats.basisPlanHint': 'З планування подорожі, не виміряно',
'tours.stats.basisTrack': 'Записано',
'tours.stats.basisTrackHint': 'З прив’язаного фактичного треку {name}',
'tours.stats.basisTrackCountHint': 'Прив’язані фактичні треки замінюють заплановані значення',
'tours.stats.period': 'Період',
'tours.stats.period.all': 'За весь час',
'tours.stats.period.thisYear': 'Цього року',
'tours.stats.period.last12': 'Останні 12 місяців',
'tours.stats.allTypes': 'Усі типи',
'tours.stats.byType': 'За типами',
'tours.stats.showTours': 'Показати тури',
'tours.stats.none': 'Немає пройдених турів за цей період',
'tours.stats.empty': 'Поки немає пройдених турів',
'tours.stats.upcoming': 'Ще попереду',
'tours.stats.upcomingHint': 'Тур зараховується, коли його день завершився',
'tours.stats.unassigned': 'Без дня',
'tours.stats.unassignedHint': 'Тури твоїх подорожей, які не стоять у жодному дні, не враховуються',
'tours.stats.undated': 'Без дати',
'tours.stats.undatedHint': 'Тури в днях без дати не враховуються',
'tours.stats.multiDayHint': 'Заплановано на кілька днів, враховано один раз',
'tours.stats.exclude': 'Не враховувати',
'tours.stats.excludeHint': 'Для туру, який не відбувся. Він залишається в плані й лише перестає враховуватися тут',
'tours.stats.include': 'Враховувати знову',
'tours.stats.excluded': 'Не враховано',
'tours.stats.excludeFailed': 'Не вдалося змінити тур',
'tours.stats.unavailable': 'Не вдалося завантажити пройдені тури',
'tours.stats.openTrip': 'Відкрити подорож',
};
export default tours;
+32
View File
@@ -1215,5 +1215,37 @@ const tours: TranslationStrings = {
'tours.activity.compare.show': 'So sánh trên bản đồ',
'tours.activity.compare.showHint': 'Vẽ lộ trình thực tế bằng nét chấm cạnh kế hoạch. Không có gì thay đổi.',
'tours.activity.compare.hide': 'Kết thúc so sánh',
'tours.stats.title': 'Các chuyến tôi đã đi',
'tours.stats.sub': 'Dựa trên kế hoạch chuyến đi; lộ trình thực tế được liên kết sẽ thay thế',
'tours.stats.basisInfo':
'Một chuyến được tính khi ngày của nó đã qua, với các giá trị từ kế hoạch của bạn. Lộ trình thực tế được liên kết sẽ thay thế chúng. Chỉ tính những ngày bạn tham gia.',
'tours.stats.basisPlan': 'Theo kế hoạch',
'tours.stats.basisPlanHint': 'Từ kế hoạch chuyến đi, không đo thực tế',
'tours.stats.basisTrack': 'Đã ghi',
'tours.stats.basisTrackHint': 'Từ lộ trình thực tế được liên kết {name}',
'tours.stats.basisTrackCountHint': 'Lộ trình thực tế được liên kết thay thế các giá trị kế hoạch',
'tours.stats.period': 'Khoảng thời gian',
'tours.stats.period.all': 'Mọi lúc',
'tours.stats.period.thisYear': 'Năm nay',
'tours.stats.period.last12': '12 tháng qua',
'tours.stats.allTypes': 'Tất cả loại',
'tours.stats.byType': 'Theo loại',
'tours.stats.showTours': 'Xem các chuyến',
'tours.stats.none': 'Không có chuyến hoàn thành trong khoảng này',
'tours.stats.empty': 'Chưa có chuyến nào hoàn thành',
'tours.stats.upcoming': 'Sắp tới',
'tours.stats.upcomingHint': 'Một chuyến được tính khi ngày của nó đã qua',
'tours.stats.unassigned': 'Không có ngày',
'tours.stats.unassignedHint': 'Các chuyến trong chuyến đi của bạn không nằm ở ngày nào sẽ không được tính',
'tours.stats.undated': 'Không có ngày tháng',
'tours.stats.undatedHint': 'Các chuyến vào ngày không có ngày tháng sẽ không được tính',
'tours.stats.multiDayHint': 'Lên kế hoạch nhiều ngày, chỉ tính một lần',
'tours.stats.exclude': 'Không tính',
'tours.stats.excludeHint': 'Dành cho chuyến bị hủy. Chuyến vẫn ở trong kế hoạch và chỉ không được tính ở đây',
'tours.stats.include': 'Tính lại',
'tours.stats.excluded': 'Không được tính',
'tours.stats.excludeFailed': 'Không thể thay đổi chuyến',
'tours.stats.unavailable': 'Không thể tải các chuyến đã đi',
'tours.stats.openTrip': 'Mở chuyến đi',
};
export default tours;
+31
View File
@@ -1131,5 +1131,36 @@ const tours: TranslationStrings = {
'tours.activity.compare.show': '在地圖上比較',
'tours.activity.compare.showHint': '將實際軌跡以點線畫在計畫旁。不會變更任何內容。',
'tours.activity.compare.hide': '結束比較',
'tours.stats.title': '我的遊程紀錄',
'tours.stats.sub': '以你的行程規劃為準;已連結的實際軌跡會取代規劃值',
'tours.stats.basisInfo': '遊程在其日期結束後計入,使用你的規劃值。已連結的實際軌跡會取代這些值。只計入你參加的日子。',
'tours.stats.basisPlan': '規劃',
'tours.stats.basisPlanHint': '來自行程規劃,並非實測',
'tours.stats.basisTrack': '已記錄',
'tours.stats.basisTrackHint': '來自已連結的實際軌跡 {name}',
'tours.stats.basisTrackCountHint': '已連結的實際軌跡取代規劃值',
'tours.stats.period': '時間範圍',
'tours.stats.period.all': '全部時間',
'tours.stats.period.thisYear': '今年',
'tours.stats.period.last12': '最近 12 個月',
'tours.stats.allTypes': '所有類型',
'tours.stats.byType': '依類型',
'tours.stats.showTours': '查看遊程',
'tours.stats.none': '此時間範圍內沒有已完成的遊程',
'tours.stats.empty': '尚無已完成的遊程',
'tours.stats.upcoming': '即將進行',
'tours.stats.upcomingHint': '遊程在其日期結束後計入',
'tours.stats.unassigned': '未排入日期',
'tours.stats.unassignedHint': '行程中未排入任何一天的遊程不計入',
'tours.stats.undated': '無日期',
'tours.stats.undatedHint': '沒有日期的日子裡的遊程不計入',
'tours.stats.multiDayHint': '排在多天,只計一次',
'tours.stats.exclude': '不計入',
'tours.stats.excludeHint': '用於取消的遊程。它仍保留在規劃中,只是不再在這裡計入',
'tours.stats.include': '重新計入',
'tours.stats.excluded': '未計入',
'tours.stats.excludeFailed': '無法變更遊程',
'tours.stats.unavailable': '無法載入遊程紀錄',
'tours.stats.openTrip': '開啟行程',
};
export default tours;
+31
View File
@@ -1132,5 +1132,36 @@ const tours: TranslationStrings = {
'tours.activity.compare.show': '在地图上比较',
'tours.activity.compare.showHint': '将实际轨迹以点线画在计划旁。不会更改任何内容。',
'tours.activity.compare.hide': '结束比较',
'tours.stats.title': '我的游览记录',
'tours.stats.sub': '基于你的行程规划;已关联的实际轨迹会替代规划值',
'tours.stats.basisInfo': '游览在其日期结束后计入,使用你的规划值。已关联的实际轨迹会替代这些值。只计入你参加的日子。',
'tours.stats.basisPlan': '规划',
'tours.stats.basisPlanHint': '来自行程规划,并非实测',
'tours.stats.basisTrack': '已记录',
'tours.stats.basisTrackHint': '来自已关联的实际轨迹 {name}',
'tours.stats.basisTrackCountHint': '已关联的实际轨迹替代规划值',
'tours.stats.period': '时间段',
'tours.stats.period.all': '全部时间',
'tours.stats.period.thisYear': '今年',
'tours.stats.period.last12': '最近 12 个月',
'tours.stats.allTypes': '所有类型',
'tours.stats.byType': '按类型',
'tours.stats.showTours': '查看游览',
'tours.stats.none': '此时间段内没有已完成的游览',
'tours.stats.empty': '还没有已完成的游览',
'tours.stats.upcoming': '即将进行',
'tours.stats.upcomingHint': '游览在其日期结束后计入',
'tours.stats.unassigned': '未安排日期',
'tours.stats.unassignedHint': '行程中未安排到任何一天的游览不计入',
'tours.stats.undated': '无日期',
'tours.stats.undatedHint': '没有日期的日子里的游览不计入',
'tours.stats.multiDayHint': '安排在多天,只计一次',
'tours.stats.exclude': '不计入',
'tours.stats.excludeHint': '用于取消的游览。它仍保留在规划中,只是不再在这里计入',
'tours.stats.include': '重新计入',
'tours.stats.excluded': '未计入',
'tours.stats.excludeFailed': '无法更改游览',
'tours.stats.unavailable': '无法加载游览记录',
'tours.stats.openTrip': '打开行程',
};
export default tours;
+2
View File
@@ -59,6 +59,8 @@ export * from './tours/run-pace';
export * from './tours/ski.schema';
export * from './tours/tour-provenance.schema';
export * from './tours/tour-activity.schema';
export * from './tours/tour-activity-stats.schema';
export * from './tours/tour-activity-stats';
export * from './tours/tour-library.schema';
export * from './tours/tour-poi-search.schema';
export * from './tours/tour-trail-grade.schema';
@@ -0,0 +1,89 @@
import { idSchema } from '../common/primitives.schema';
import { tourTypeKeySchema } from './tours.schema';
import { z } from 'zod';
/**
* STA-02: the user's own tour activities, as the Atlas sums them. One entry is
* one tour the user did on one day of a trip, read from what the trip holds:
*
* - The plan's values stand in for the tour while nothing was recorded
* (`basis: 'plan'`), and say so. A tour on several days still counts once
* (`day_count` says on how many), on the first of them.
* - A recorded track linked to an execution of the tour (ACT-01) takes the
* plan's place (`basis: 'track'`), one entry per recording; the plan of that
* tour is then not counted besides it, and the recording is never counted as
* a tour of its own.
* - An import some other tour of the trip was planned from (the whole route a
* multi-day tour's stages were cut from) is left out while one of those
* tours counts, so the route and its stages are never added up together.
* - Only the user's own part counts: an execution names the user as one of its
* participants, or names none and the trip is the user's own. Being able to
* open someone else's trip makes none of its tours the user's.
*
* Whether an entry is done is the reader's question (`date` before today, in
* the reader's own time zone): the server answers every entry it found, the
* upcoming ones too, so the app can say how many are still ahead.
*/
/** Where an entry's values come from: the trip's planning, or a track recorded while doing it. */
export const TOUR_ACTIVITY_BASES = ['plan', 'track'] as const;
export const tourActivityBasisSchema = z.enum(TOUR_ACTIVITY_BASES);
export type TourActivityBasis = z.infer<typeof tourActivityBasisSchema>;
/** A calendar day, YYYY-MM-DD, as a trip's days carry it. */
export const tourActivityDateSchema = z.string().regex(/^\d{4}-\d{2}-\d{2}$/);
export const tourActivityStatsEntrySchema = z.object({
place_id: idSchema,
name: z.string(),
tour_type: tourTypeKeySchema,
trip: z.object({ id: idSchema, title: z.string() }),
/** The day it was done on: the recording's day, else the first of the user's dated days the tour is on. */
date: tourActivityDateSchema,
basis: tourActivityBasisSchema,
/** The recording whose values these are; null for the plan. */
track: z.object({ place_id: idSchema, name: z.string() }).nullable(),
/** On how many of the user's dated days the tour is planned; it counts once however many. */
day_count: z.number().int().positive(),
/** Null where the source does not know the value; a paddle tour never has heights. */
distance_km: z.number().nonnegative().nullable(),
ascent_m: z.number().nonnegative().nullable(),
descent_m: z.number().nonnegative().nullable(),
/** Left out of the user's sums on purpose (a tour that fell through); the plan stays as it is. */
excluded: z.boolean(),
});
export type TourActivityStatsEntry = z.infer<typeof tourActivityStatsEntrySchema>;
export const tourActivityStatsResponseSchema = z.object({
/** The newest first. */
entries: z.array(tourActivityStatsEntrySchema),
/**
* Tours of the user's own trips that cannot count as done: on no day at all,
* or only on days without a date. Said, so a missing tour is not a mystery.
*/
uncounted: z.object({
unassigned: z.number().int().nonnegative(),
undated: z.number().int().nonnegative(),
}),
});
export type TourActivityStatsResponse = z.infer<typeof tourActivityStatsResponseSchema>;
/** Leaving a tour out of the user's own sums, or counting it again. Nothing of the tour changes. */
export const tourActivityExclusionRequestSchema = z
.object({
place_id: idSchema,
excluded: z.boolean(),
})
.strict();
export type TourActivityExclusionRequest = z.infer<typeof tourActivityExclusionRequestSchema>;
/** What a reader narrows the sums by: a period (both days included) and one type. */
export const tourActivityStatsQuerySchema = z
.object({
from: tourActivityDateSchema.optional(),
to: tourActivityDateSchema.optional(),
type: tourTypeKeySchema.optional(),
})
.strict();
export type TourActivityStatsQuery = z.infer<typeof tourActivityStatsQuerySchema>;
@@ -0,0 +1,186 @@
import { isTourActivityDone, summarizeTourActivities } from './tour-activity-stats';
import {
tourActivityExclusionRequestSchema,
tourActivityStatsQuerySchema,
tourActivityStatsResponseSchema,
type TourActivityStatsEntry,
} from './tour-activity-stats.schema';
import { describe, expect, it } from 'vitest';
function entry(overrides: Partial<TourActivityStatsEntry> = {}): TourActivityStatsEntry {
return {
place_id: 1,
name: 'Ridge',
tour_type: 'hike',
trip: { id: 5, title: 'Alps' },
date: '2026-07-02',
basis: 'plan',
track: null,
day_count: 1,
distance_km: 10,
ascent_m: 500,
descent_m: 400,
excluded: false,
...overrides,
};
}
const TODAY = '2026-10-20';
describe('STA-02 tour activity stats contract', () => {
it('STA2-SCHEMA-001: reads entries of either basis with unknown values kept unknown', () => {
const body = {
entries: [
entry(),
entry({
place_id: 2,
basis: 'track',
track: { place_id: 9, name: 'Watch export' },
distance_km: null,
ascent_m: null,
descent_m: null,
}),
],
uncounted: { unassigned: 1, undated: 0 },
};
expect(tourActivityStatsResponseSchema.parse(body)).toEqual(body);
});
it('STA2-SCHEMA-002: refuses a date that is not a calendar day, a negative distance and an unknown basis', () => {
const uncounted = { unassigned: 0, undated: 0 };
const parses = (entries: unknown[]) => tourActivityStatsResponseSchema.safeParse({ entries, uncounted }).success;
expect(parses([entry({ date: '02.07.2026' })])).toBe(false);
expect(parses([entry({ distance_km: -1 })])).toBe(false);
expect(parses([{ ...entry(), basis: 'guess' }])).toBe(false);
});
it('STA2-SCHEMA-003: the exclusion names one tour and a yes or no, nothing else', () => {
expect(tourActivityExclusionRequestSchema.parse({ place_id: 4, excluded: true })).toEqual({
place_id: 4,
excluded: true,
});
expect(tourActivityExclusionRequestSchema.safeParse({ place_id: 4 }).success).toBe(false);
const withNote = { place_id: 4, excluded: true, note: 'rain' };
expect(tourActivityExclusionRequestSchema.safeParse(withNote).success).toBe(false);
});
it('STA2-SCHEMA-004: the query takes a period and a type, all of them optional', () => {
expect(tourActivityStatsQuerySchema.parse({})).toEqual({});
expect(tourActivityStatsQuerySchema.parse({ from: '2026-01-01', to: '2026-12-31', type: 'kayak' })).toEqual({
from: '2026-01-01',
to: '2026-12-31',
type: 'kayak',
});
expect(tourActivityStatsQuerySchema.safeParse({ type: 'car' }).success).toBe(false);
});
});
describe('STA-02 summarizeTourActivities', () => {
it('STA2-SUM-001: a tour counts from the day after its day, not on it', () => {
expect(isTourActivityDone(entry({ date: '2026-10-19' }), TODAY)).toBe(true);
expect(isTourActivityDone(entry({ date: TODAY }), TODAY)).toBe(false);
const summary = summarizeTourActivities([entry({ date: '2026-10-19' }), entry({ place_id: 2, date: TODAY })], {
today: TODAY,
});
expect(summary.totals.count).toBe(1);
expect(summary.upcoming).toBe(1);
});
it('STA2-SUM-002: sums distance and heights, says how many rest on the plan and how many on a track', () => {
const summary = summarizeTourActivities(
[
entry(),
entry({
place_id: 2,
basis: 'track',
track: { place_id: 9, name: 'Watch' },
distance_km: 12.5,
ascent_m: 650,
descent_m: 600,
}),
],
{ today: TODAY },
);
expect(summary.totals).toEqual({
count: 2,
distanceKm: { value: 22.5, known: 2, of: 2 },
ascentM: { value: 1150, known: 2, of: 2 },
descentM: { value: 1000, known: 2, of: 2 },
byBasis: { plan: 1, track: 1 },
});
});
it('STA2-SUM-003: an unknown value is no zero: the sum says it is short of the whole, or null when nobody knows', () => {
const summary = summarizeTourActivities(
[
entry({ ascent_m: null, descent_m: null }),
entry({ place_id: 2, distance_km: null, ascent_m: null, descent_m: null }),
],
{ today: TODAY },
);
expect(summary.totals.distanceKm).toEqual({ value: 10, known: 1, of: 2 });
expect(summary.totals.ascentM).toEqual({ value: null, known: 0, of: 2 });
});
it('STA2-SUM-004: paddling sums no heights, so a paddle tour leaves the height sums complete', () => {
const summary = summarizeTourActivities(
[entry(), entry({ place_id: 2, tour_type: 'kayak', distance_km: 8, ascent_m: null, descent_m: null })],
{ today: TODAY },
);
expect(summary.totals.distanceKm).toEqual({ value: 18, known: 2, of: 2 });
expect(summary.totals.ascentM).toEqual({ value: 500, known: 1, of: 1 });
expect(summary.types.find((type) => type.type === 'kayak')?.ascentM).toEqual({ value: null, known: 0, of: 0 });
});
it('STA2-SUM-005: narrows by period (both days included) and by type, the most tours of a type first', () => {
const entries = [
entry({ place_id: 1, date: '2025-12-31' }),
entry({ place_id: 2, date: '2026-01-01', tour_type: 'bike' }),
entry({ place_id: 3, date: '2026-06-30', tour_type: 'kayak' }),
entry({ place_id: 4, date: '2026-07-01', tour_type: 'kayak' }),
];
const year = summarizeTourActivities(entries, { today: TODAY, from: '2026-01-01', to: '2026-12-31' });
expect(year.totals.count).toBe(3);
expect(year.types.map((type) => [type.type, type.count])).toEqual([
['kayak', 2],
['bike', 1],
]);
const paddled = summarizeTourActivities(entries, { today: TODAY, type: 'kayak' });
expect(paddled.totals.distanceKm.value).toBe(20);
expect(paddled.done.map((item) => item.place_id)).toEqual([4, 3]);
});
it('STA2-SUM-006: an excluded tour is listed among the done ones but never summed, nor counted as upcoming', () => {
const summary = summarizeTourActivities(
[
entry({ excluded: true }),
entry({ place_id: 2, date: '2026-07-03' }),
entry({ place_id: 3, date: '2026-12-01', excluded: true }),
],
{ today: TODAY },
);
expect(summary.totals.count).toBe(1);
expect(summary.done.map((item) => [item.place_id, item.excluded])).toEqual([
[2, false],
[1, true],
]);
expect(summary.upcoming).toBe(0);
});
it('STA2-SUM-007: nothing done is an empty summary, not a row of zeros', () => {
const summary = summarizeTourActivities([], { today: TODAY });
expect(summary.totals.count).toBe(0);
expect(summary.totals.distanceKm.value).toBeNull();
expect(summary.types).toEqual([]);
expect(summary.done).toEqual([]);
});
it('STA2-SUM-008: the same day sorts by name, so the list keeps its order between reads', () => {
const summary = summarizeTourActivities(
[entry({ place_id: 1, name: 'Zugspitze' }), entry({ place_id: 2, name: 'Alpspitze' })],
{ today: TODAY },
);
expect(summary.done.map((item) => item.name)).toEqual(['Alpspitze', 'Zugspitze']);
});
});
+99
View File
@@ -0,0 +1,99 @@
import type { TourActivityBasis, TourActivityStatsEntry } from './tour-activity-stats.schema';
import type { TourTypeKey } from './tours.schema';
/**
* STA-02: what the user's done tours add up to, for the Atlas and for the
* assistant alike, so both say the same numbers. A sum knows how many entries
* had the value and of how many could: `value` is null when none had it, never
* a zero standing in for "unknown", and `known < of` says it is short of the
* whole. Paddling sums no heights (a climb on the water says nothing), as in
* the trip's own tour totals (STA-01).
*/
export interface TourActivitySum {
value: number | null;
known: number;
of: number;
}
export interface TourActivityTotals {
count: number;
distanceKm: TourActivitySum;
ascentM: TourActivitySum;
descentM: TourActivitySum;
/** How many of the entries rest on the plan and how many on a recorded track. */
byBasis: Record<TourActivityBasis, number>;
}
export interface TourActivityFilter {
/** Today in the reader's own calendar, YYYY-MM-DD: a tour counts once its day is over. */
today: string;
/** The period, both days included; left out, open on that side. */
from?: string | null;
to?: string | null;
/** One type; left out, all of them. */
type?: TourTypeKey | null;
}
export interface TourActivitySummary {
totals: TourActivityTotals;
/** By type, the most tours first; empty when nothing counts. */
types: Array<TourActivityTotals & { type: TourTypeKey }>;
/** Every entry done in the period, the newest first; those the user left out are among them, flagged and not summed. */
done: TourActivityStatsEntry[];
/** In the period and of the type, but their day is not over yet. */
upcoming: number;
}
/** Whether the day an entry is on is over: it counts from the next day on. */
export function isTourActivityDone(entry: Pick<TourActivityStatsEntry, 'date'>, today: string): boolean {
return entry.date < today;
}
function inPeriod(date: string, filter: TourActivityFilter): boolean {
return (!filter.from || date >= filter.from) && (!filter.to || date <= filter.to);
}
function sumOf(values: ReadonlyArray<number | null>): TourActivitySum {
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 totalsOf(entries: readonly TourActivityStatsEntry[]): TourActivityTotals {
const withHeights = entries.filter((entry) => entry.tour_type !== 'kayak');
return {
count: entries.length,
distanceKm: sumOf(entries.map((entry) => entry.distance_km)),
ascentM: sumOf(withHeights.map((entry) => entry.ascent_m)),
descentM: sumOf(withHeights.map((entry) => entry.descent_m)),
byBasis: {
plan: entries.filter((entry) => entry.basis === 'plan').length,
track: entries.filter((entry) => entry.basis === 'track').length,
},
};
}
/** The entries of the period and type, done and counted, summed as a whole and by type. */
export function summarizeTourActivities(
entries: readonly TourActivityStatsEntry[],
filter: TourActivityFilter,
): TourActivitySummary {
const matching = entries.filter(
(entry) => inPeriod(entry.date, filter) && (!filter.type || entry.tour_type === filter.type),
);
const done = matching.filter((entry) => isTourActivityDone(entry, filter.today));
const counted = done.filter((entry) => !entry.excluded);
const byType = new Map<TourTypeKey, TourActivityStatsEntry[]>();
for (const entry of counted) byType.set(entry.tour_type, [...(byType.get(entry.tour_type) ?? []), entry]);
return {
totals: totalsOf(counted),
types: [...byType.entries()]
.map(([type, ofType]) => ({ type, ...totalsOf(ofType) }))
.sort((a, b) => b.count - a.count || a.type.localeCompare(b.type)),
done: [...done].sort((a, b) => b.date.localeCompare(a.date) || a.name.localeCompare(b.name)),
upcoming: matching.filter((entry) => !entry.excluded && !isTourActivityDone(entry, filter.today)).length,
};
}