mirror of
https://github.com/mauriceboe/TREK.git
synced 2026-10-21 14:36:31 +00:00
feat(tours): plan a multi-day tour as one route split into stages
A hut-to-hut hike or a paddle trip over several days is now planned in the Tours planner as one tour: the whole route is planned as before, and stops along it are marked as stage ends (MDT-01). The stages are read off that one route in its order, never kept as copies, so a change inside a stage changes the whole tour and every figure on it. - A Day tour / Multi-day tour switch beside the tour type; a stop's menu ends a stage there or lets it go on. Back to a day tour removes every stage end after a confirmation, as one step undo brings back. - The list of points is headed per stage with its distance and moving time; a stop that ends a stage carries a moon at its number, on the map too. - The overview lists the stages from stop to stop with distance, climb, descent and time (hiking rule per stage, paddle estimate per stage, the router's time shared by distance otherwise), marks where each ends across the elevation profile, and a stage of more than a day on the move warns. - A stage end a change cannot keep (its stop removed, moved to an end or made a shaping point) is dropped and said so, with undo, never moved to another point. - A multi-day tour may take a day a stage, so its time limit grows with its stages; the tour keeps its one type, portages included. - The list and the MCP tools show the stage count and where stages end. Stage ends are a flag on the waypoint (migration 046000); a tour without one is a day tour, as every tour saved before.
This commit is contained in:
@@ -1292,5 +1292,39 @@ const tours: TranslationStrings = {
|
||||
'tours.stats.excludeFailed': 'تعذر تغيير الجولة',
|
||||
'tours.stats.unavailable': 'تعذر تحميل الجولات المنجزة',
|
||||
'tours.stats.openTrip': 'فتح الرحلة',
|
||||
// MDT-01: a multi-day tour, one route split into stages.
|
||||
'tours.stages.mode': 'الأيام',
|
||||
'tours.stages.mode.single': 'رحلة يوم',
|
||||
'tours.stages.mode.multi': 'عدة أيام',
|
||||
'tours.stages.emptyHint': 'من قائمة التوقف اختر «إنهاء المرحلة هنا» لتقسيم المسار.',
|
||||
'tours.stages.endHere': 'إنهاء المرحلة هنا',
|
||||
'tours.stages.removeEnd': 'إزالة نهاية المرحلة',
|
||||
'tours.stages.stage': 'المرحلة {number}',
|
||||
'tours.stages.endOf': 'نهاية المرحلة {number}',
|
||||
'tours.stages.title': 'المراحل',
|
||||
'tours.stages.count': 'المراحل: {count}',
|
||||
'tours.stages.count.zero': 'المراحل: {count}',
|
||||
'tours.stages.count.one': 'المراحل: {count}',
|
||||
'tours.stages.count.two': 'المراحل: {count}',
|
||||
'tours.stages.count.few': 'المراحل: {count}',
|
||||
'tours.stages.count.many': 'المراحل: {count}',
|
||||
'tours.stages.overDay': 'أكثر من يوم في الحركة',
|
||||
'tours.stages.timeInfo':
|
||||
'وقت الحركة لكل مرحلة دون الاستراحات. في جولات الدراجة والجولات في المدينة يُوزَّع وقت مخطط المسار حسب المسافة.',
|
||||
'tours.stages.lost': 'نهايات مراحل سقطت مع توقفاتها: {count}. صارت تلك المراحل مدمجة.',
|
||||
'tours.stages.lost.zero': 'نهايات مراحل سقطت مع توقفاتها: {count}. صارت تلك المراحل مدمجة.',
|
||||
'tours.stages.lost.one': 'نهايات مراحل سقطت مع توقفاتها: {count}. صارت تلك المراحل مدمجة.',
|
||||
'tours.stages.lost.two': 'نهايات مراحل سقطت مع توقفاتها: {count}. صارت تلك المراحل مدمجة.',
|
||||
'tours.stages.lost.few': 'نهايات مراحل سقطت مع توقفاتها: {count}. صارت تلك المراحل مدمجة.',
|
||||
'tours.stages.lost.many': 'نهايات مراحل سقطت مع توقفاتها: {count}. صارت تلك المراحل مدمجة.',
|
||||
'tours.stages.toDayTitle': 'تحويلها إلى رحلة يوم واحد؟',
|
||||
'tours.stages.toDayBody': 'نهايات المراحل التي ستُزال: {count}. يبقى المسار كما هو، والتراجع يعيدها.',
|
||||
'tours.stages.toDayBody.zero': 'نهايات المراحل التي ستُزال: {count}. يبقى المسار كما هو، والتراجع يعيدها.',
|
||||
'tours.stages.toDayBody.one': 'نهايات المراحل التي ستُزال: {count}. يبقى المسار كما هو، والتراجع يعيدها.',
|
||||
'tours.stages.toDayBody.two': 'نهايات المراحل التي ستُزال: {count}. يبقى المسار كما هو، والتراجع يعيدها.',
|
||||
'tours.stages.toDayBody.few': 'نهايات المراحل التي ستُزال: {count}. يبقى المسار كما هو، والتراجع يعيدها.',
|
||||
'tours.stages.toDayBody.many': 'نهايات المراحل التي ستُزال: {count}. يبقى المسار كما هو، والتراجع يعيدها.',
|
||||
'tours.stages.toDayConfirm': 'إزالة نهايات المراحل',
|
||||
'tours.stages.show': 'إظهار مكان نهاية هذه المرحلة',
|
||||
};
|
||||
export default tours;
|
||||
|
||||
@@ -1260,5 +1260,27 @@ const tours: TranslationStrings = {
|
||||
'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ç',
|
||||
// MDT-01: a multi-day tour, one route split into stages.
|
||||
'tours.stages.mode': 'Günlər',
|
||||
'tours.stages.mode.single': 'Bir günlük',
|
||||
'tours.stages.mode.multi': 'Çoxgünlük',
|
||||
'tours.stages.emptyHint': 'Marşrutu bölmək üçün dayanacağın menyusunda «Mərhələni burada bitir» seçin.',
|
||||
'tours.stages.endHere': 'Mərhələni burada bitir',
|
||||
'tours.stages.removeEnd': 'Mərhələ sonunu sil',
|
||||
'tours.stages.stage': 'Mərhələ {number}',
|
||||
'tours.stages.endOf': 'Mərhələ {number} sonu',
|
||||
'tours.stages.title': 'Mərhələlər',
|
||||
'tours.stages.count': '{count} mərhələ',
|
||||
'tours.stages.count.one': '{count} mərhələ',
|
||||
'tours.stages.overDay': 'Bir gündən çox hərəkətdə',
|
||||
'tours.stages.timeInfo':
|
||||
'Hər mərhələ üçün hərəkət vaxtı, fasilələr daxil deyil. Velosiped turlarında və şəhər gəzintilərində marşrut planlayıcısının vaxtı məsafəyə görə bölünür.',
|
||||
'tours.stages.lost': '{count} mərhələ sonu dayanacaqları ilə birlikdə itdi; həmin mərhələlər indi birləşib.',
|
||||
'tours.stages.lost.one': '{count} mərhələ sonu dayanacağı ilə birlikdə itdi; onun iki mərhələsi indi birləşib.',
|
||||
'tours.stages.toDayTitle': 'Bir günlük tura çevrilsin?',
|
||||
'tours.stages.toDayBody': '{count} mərhələ sonu silinir. Marşrut olduğu kimi qalır, Geri al onları qaytarır.',
|
||||
'tours.stages.toDayBody.one': '{count} mərhələ sonu silinir. Marşrut olduğu kimi qalır, Geri al onu qaytarır.',
|
||||
'tours.stages.toDayConfirm': 'Mərhələ sonlarını sil',
|
||||
'tours.stages.show': 'Bu mərhələnin harada bitdiyini göstər',
|
||||
};
|
||||
export default tours;
|
||||
|
||||
@@ -1272,5 +1272,28 @@ const tours: TranslationStrings = {
|
||||
'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',
|
||||
// MDT-01: a multi-day tour, one route split into stages.
|
||||
'tours.stages.mode': 'Dias',
|
||||
'tours.stages.mode.single': 'Um dia',
|
||||
'tours.stages.mode.multi': 'Vários dias',
|
||||
'tours.stages.emptyHint': 'No menu de uma parada, escolha “Encerrar etapa aqui” para dividir a rota.',
|
||||
'tours.stages.endHere': 'Encerrar etapa aqui',
|
||||
'tours.stages.removeEnd': 'Remover fim de etapa',
|
||||
'tours.stages.stage': 'Etapa {number}',
|
||||
'tours.stages.endOf': 'Fim da etapa {number}',
|
||||
'tours.stages.title': 'Etapas',
|
||||
'tours.stages.count': '{count} etapas',
|
||||
'tours.stages.count.one': '{count} etapa',
|
||||
'tours.stages.overDay': 'Mais de um dia em movimento',
|
||||
'tours.stages.timeInfo':
|
||||
'Tempo em movimento por etapa, sem pausas. Em passeios de bicicleta e caminhadas urbanas, o tempo do planejador de rotas é dividido pela distância.',
|
||||
'tours.stages.lost': '{count} fins de etapa sumiram com suas paradas; essas etapas agora estão unidas.',
|
||||
'tours.stages.lost.one': '{count} fim de etapa sumiu com sua parada; suas duas etapas agora estão unidas.',
|
||||
'tours.stages.toDayTitle': 'Transformar em passeio de um dia?',
|
||||
'tours.stages.toDayBody':
|
||||
'Os {count} fins de etapa são removidos. A rota fica como está, e Desfazer os traz de volta.',
|
||||
'tours.stages.toDayBody.one': '{count} fim de etapa é removido. A rota fica como está, e Desfazer o traz de volta.',
|
||||
'tours.stages.toDayConfirm': 'Remover fins de etapa',
|
||||
'tours.stages.show': 'Mostrar onde esta etapa termina',
|
||||
};
|
||||
export default tours;
|
||||
|
||||
@@ -1279,5 +1279,28 @@ const tours: TranslationStrings = {
|
||||
'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',
|
||||
// MDT-01: a multi-day tour, one route split into stages.
|
||||
'tours.stages.mode': 'Dies',
|
||||
'tours.stages.mode.single': 'Un dia',
|
||||
'tours.stages.mode.multi': 'Diversos dies',
|
||||
'tours.stages.emptyHint': 'Al menú d’una parada, tria «Acaba l’etapa aquí» per dividir la ruta.',
|
||||
'tours.stages.endHere': 'Acaba l’etapa aquí',
|
||||
'tours.stages.removeEnd': 'Treu el final d’etapa',
|
||||
'tours.stages.stage': 'Etapa {number}',
|
||||
'tours.stages.endOf': 'Final de l’etapa {number}',
|
||||
'tours.stages.title': 'Etapes',
|
||||
'tours.stages.count': '{count} etapes',
|
||||
'tours.stages.count.one': '{count} etapa',
|
||||
'tours.stages.overDay': 'Més d’un dia en moviment',
|
||||
'tours.stages.timeInfo':
|
||||
'Temps en moviment per etapa, sense pauses. En bici i en passejades per la ciutat, el temps del planificador de rutes es reparteix per distància.',
|
||||
'tours.stages.lost': '{count} finals d’etapa s’han perdut amb les seves parades; aquestes etapes ara estan unides.',
|
||||
'tours.stages.lost.one':
|
||||
'{count} final d’etapa s’ha perdut amb la seva parada; les seves dues etapes ara estan unides.',
|
||||
'tours.stages.toDayTitle': 'Convertir-la en una ruta d’un dia?',
|
||||
'tours.stages.toDayBody': 'Es treuen els {count} finals d’etapa. La ruta queda com està i Desfés els recupera.',
|
||||
'tours.stages.toDayBody.one': 'Es treu {count} final d’etapa. La ruta queda com està i Desfés el recupera.',
|
||||
'tours.stages.toDayConfirm': 'Treu els finals d’etapa',
|
||||
'tours.stages.show': 'Mostra on acaba aquesta etapa',
|
||||
};
|
||||
export default tours;
|
||||
|
||||
@@ -1258,5 +1258,30 @@ const tours: TranslationStrings = {
|
||||
'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',
|
||||
// MDT-01: a multi-day tour, one route split into stages.
|
||||
'tours.stages.mode': 'Dny',
|
||||
'tours.stages.mode.single': 'Jednodenní',
|
||||
'tours.stages.mode.multi': 'Vícedenní',
|
||||
'tours.stages.emptyHint': 'V nabídce zastávky zvolte „Ukončit etapu zde“ a rozdělte trasu.',
|
||||
'tours.stages.endHere': 'Ukončit etapu zde',
|
||||
'tours.stages.removeEnd': 'Odebrat konec etapy',
|
||||
'tours.stages.stage': 'Etapa {number}',
|
||||
'tours.stages.endOf': 'Konec etapy {number}',
|
||||
'tours.stages.title': 'Etapy',
|
||||
'tours.stages.count': '{count} etap',
|
||||
'tours.stages.count.one': '{count} etapa',
|
||||
'tours.stages.count.few': '{count} etapy',
|
||||
'tours.stages.overDay': 'Víc než den v pohybu',
|
||||
'tours.stages.timeInfo':
|
||||
'Čas v pohybu na etapu, bez přestávek. U cyklotras a městských procházek se čas plánovače tras dělí podle vzdálenosti.',
|
||||
'tours.stages.lost': '{count} konců etap zmizelo se svými zastávkami; tyto etapy jsou teď spojené.',
|
||||
'tours.stages.lost.one': '{count} konec etapy zmizel se svou zastávkou; jeho dvě etapy jsou teď spojené.',
|
||||
'tours.stages.lost.few': '{count} konce etap zmizely se svými zastávkami; tyto etapy jsou teď spojené.',
|
||||
'tours.stages.toDayTitle': 'Udělat z ní jednodenní túru?',
|
||||
'tours.stages.toDayBody': 'Odebere se {count} konců etap. Trasa zůstane, jak je, a Zpět je vrátí.',
|
||||
'tours.stages.toDayBody.one': 'Odebere se {count} konec etapy. Trasa zůstane, jak je, a Zpět ho vrátí.',
|
||||
'tours.stages.toDayBody.few': 'Odeberou se {count} konce etap. Trasa zůstane, jak je, a Zpět je vrátí.',
|
||||
'tours.stages.toDayConfirm': 'Odebrat konce etap',
|
||||
'tours.stages.show': 'Ukázat, kde etapa končí',
|
||||
};
|
||||
export default tours;
|
||||
|
||||
@@ -1286,6 +1286,32 @@ const tours: TranslationStrings = {
|
||||
'tours.stats.excludeFailed': 'Die Tour konnte nicht geändert werden',
|
||||
'tours.stats.unavailable': 'Touraktivitäten konnten nicht geladen werden',
|
||||
'tours.stats.openTrip': 'Reise öffnen',
|
||||
// MDT-01: a multi-day tour, one route split into stages.
|
||||
'tours.stages.mode': 'Tage',
|
||||
'tours.stages.mode.single': 'Tagestour',
|
||||
'tours.stages.mode.multi': 'Mehrtagestour',
|
||||
'tours.stages.emptyHint': 'Im Menü eines Stopps „Etappe hier beenden“ wählen, um die Route zu teilen.',
|
||||
'tours.stages.endHere': 'Etappe hier beenden',
|
||||
'tours.stages.removeEnd': 'Etappenende entfernen',
|
||||
'tours.stages.stage': 'Etappe {number}',
|
||||
'tours.stages.endOf': 'Ende von Etappe {number}',
|
||||
'tours.stages.title': 'Etappen',
|
||||
'tours.stages.count': '{count} Etappen',
|
||||
'tours.stages.count.one': '{count} Etappe',
|
||||
'tours.stages.overDay': 'Mehr als ein Tag unterwegs',
|
||||
'tours.stages.timeInfo':
|
||||
'Zeit in Bewegung je Etappe, ohne Pausen. Radtouren und Stadtspaziergänge teilen die Zeit des Routers nach Strecke auf.',
|
||||
'tours.stages.lost':
|
||||
'{count} Etappenenden sind mit ihren Stopps weggefallen; diese Etappen sind jetzt zusammengelegt.',
|
||||
'tours.stages.lost.one':
|
||||
'{count} Etappenende ist mit seinem Stopp weggefallen; seine zwei Etappen sind jetzt zusammengelegt.',
|
||||
'tours.stages.toDayTitle': 'Zur Tagestour machen?',
|
||||
'tours.stages.toDayBody':
|
||||
'Die {count} Etappenenden werden entfernt. Die Route bleibt, wie sie ist, und Rückgängig holt sie zurück.',
|
||||
'tours.stages.toDayBody.one':
|
||||
'{count} Etappenende wird entfernt. Die Route bleibt, wie sie ist, und Rückgängig holt es zurück.',
|
||||
'tours.stages.toDayConfirm': 'Etappenenden entfernen',
|
||||
'tours.stages.show': 'Zeigen, wo diese Etappe endet',
|
||||
};
|
||||
|
||||
export default tours;
|
||||
|
||||
@@ -1247,5 +1247,27 @@ const tours: TranslationStrings = {
|
||||
'tours.stats.excludeFailed': 'The tour could not be changed',
|
||||
'tours.stats.unavailable': 'Tour activities could not be loaded',
|
||||
'tours.stats.openTrip': 'Open trip',
|
||||
// MDT-01: a multi-day tour, one route split into stages.
|
||||
'tours.stages.mode': 'Days',
|
||||
'tours.stages.mode.single': 'Day tour',
|
||||
'tours.stages.mode.multi': 'Multi-day tour',
|
||||
'tours.stages.emptyHint': 'In the menu of a stop, choose End stage here to split the route.',
|
||||
'tours.stages.endHere': 'End stage here',
|
||||
'tours.stages.removeEnd': 'Remove stage end',
|
||||
'tours.stages.stage': 'Stage {number}',
|
||||
'tours.stages.endOf': 'End of stage {number}',
|
||||
'tours.stages.title': 'Stages',
|
||||
'tours.stages.count': '{count} stages',
|
||||
'tours.stages.count.one': '{count} stage',
|
||||
'tours.stages.overDay': 'More than a day on the move',
|
||||
'tours.stages.timeInfo':
|
||||
'Moving time of each stage, breaks not included. Bike tours and city walks share the router time by distance.',
|
||||
'tours.stages.lost': '{count} stage ends were dropped with their stops; those stages are joined now.',
|
||||
'tours.stages.lost.one': '{count} stage end was dropped with its stop; its two stages are joined now.',
|
||||
'tours.stages.toDayTitle': 'Make it a day tour?',
|
||||
'tours.stages.toDayBody': 'The {count} stage ends are removed. The route stays as it is, and undo brings them back.',
|
||||
'tours.stages.toDayBody.one': '{count} stage end is removed. The route stays as it is, and undo brings it back.',
|
||||
'tours.stages.toDayConfirm': 'Remove stage ends',
|
||||
'tours.stages.show': 'Show where this stage ends',
|
||||
};
|
||||
export default tours;
|
||||
|
||||
@@ -1272,5 +1272,27 @@ const tours: TranslationStrings = {
|
||||
'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',
|
||||
// MDT-01: a multi-day tour, one route split into stages.
|
||||
'tours.stages.mode': 'Días',
|
||||
'tours.stages.mode.single': 'Un día',
|
||||
'tours.stages.mode.multi': 'Varios días',
|
||||
'tours.stages.emptyHint': 'En el menú de una parada, elige «Terminar etapa aquí» para dividir la ruta.',
|
||||
'tours.stages.endHere': 'Terminar etapa aquí',
|
||||
'tours.stages.removeEnd': 'Quitar fin de etapa',
|
||||
'tours.stages.stage': 'Etapa {number}',
|
||||
'tours.stages.endOf': 'Fin de la etapa {number}',
|
||||
'tours.stages.title': 'Etapas',
|
||||
'tours.stages.count': '{count} etapas',
|
||||
'tours.stages.count.one': '{count} etapa',
|
||||
'tours.stages.overDay': 'Más de un día en movimiento',
|
||||
'tours.stages.timeInfo':
|
||||
'Tiempo en movimiento por etapa, sin pausas. En bici y en paseos urbanos, el tiempo del planificador de rutas se reparte por distancia.',
|
||||
'tours.stages.lost': '{count} finales de etapa se perdieron con sus paradas; esas etapas ahora están unidas.',
|
||||
'tours.stages.lost.one': '{count} final de etapa se perdió con su parada; sus dos etapas ahora están unidas.',
|
||||
'tours.stages.toDayTitle': '¿Convertirla en una ruta de un día?',
|
||||
'tours.stages.toDayBody': 'Se quitan los {count} finales de etapa. La ruta queda como está y Deshacer los recupera.',
|
||||
'tours.stages.toDayBody.one': 'Se quita {count} final de etapa. La ruta queda como está y Deshacer lo recupera.',
|
||||
'tours.stages.toDayConfirm': 'Quitar finales de etapa',
|
||||
'tours.stages.show': 'Mostrar dónde termina esta etapa',
|
||||
};
|
||||
export default tours;
|
||||
|
||||
@@ -1256,5 +1256,27 @@ const tours: TranslationStrings = {
|
||||
'tours.stats.excludeFailed': 'Retke ei saanud muuta',
|
||||
'tours.stats.unavailable': 'Tehtud retki ei saanud laadida',
|
||||
'tours.stats.openTrip': 'Ava reis',
|
||||
// MDT-01: a multi-day tour, one route split into stages.
|
||||
'tours.stages.mode': 'Päevad',
|
||||
'tours.stages.mode.single': 'Ühepäevane',
|
||||
'tours.stages.mode.multi': 'Mitmepäevane',
|
||||
'tours.stages.emptyHint': 'Marsruudi jagamiseks vali peatuse menüüst „Lõpeta etapp siin“.',
|
||||
'tours.stages.endHere': 'Lõpeta etapp siin',
|
||||
'tours.stages.removeEnd': 'Eemalda etapi lõpp',
|
||||
'tours.stages.stage': 'Etapp {number}',
|
||||
'tours.stages.endOf': 'Etapi {number} lõpp',
|
||||
'tours.stages.title': 'Etapid',
|
||||
'tours.stages.count': '{count} etappi',
|
||||
'tours.stages.count.one': '{count} etapp',
|
||||
'tours.stages.overDay': 'Üle päeva liikumises',
|
||||
'tours.stages.timeInfo':
|
||||
'Liikumisaeg etapi kohta, pausideta. Rattamatkadel ja linnajalutuskäikudel jagatakse teekonnaplaneerija aeg vahemaa järgi.',
|
||||
'tours.stages.lost': '{count} etapi lõppu kadus koos peatustega; need etapid on nüüd ühendatud.',
|
||||
'tours.stages.lost.one': '{count} etapi lõpp kadus koos peatusega; selle kaks etappi on nüüd ühendatud.',
|
||||
'tours.stages.toDayTitle': 'Teha sellest ühepäevane matk?',
|
||||
'tours.stages.toDayBody': '{count} etapi lõppu eemaldatakse. Marsruut jääb samaks ja Võta tagasi taastab need.',
|
||||
'tours.stages.toDayBody.one': '{count} etapi lõpp eemaldatakse. Marsruut jääb samaks ja Võta tagasi taastab selle.',
|
||||
'tours.stages.toDayConfirm': 'Eemalda etapi lõpud',
|
||||
'tours.stages.show': 'Näita, kus see etapp lõpeb',
|
||||
};
|
||||
export default tours;
|
||||
|
||||
@@ -1296,5 +1296,28 @@ const tours: TranslationStrings = {
|
||||
'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',
|
||||
// MDT-01: a multi-day tour, one route split into stages.
|
||||
'tours.stages.mode': 'Jours',
|
||||
'tours.stages.mode.single': 'Une journée',
|
||||
'tours.stages.mode.multi': 'Plusieurs jours',
|
||||
'tours.stages.emptyHint': 'Dans le menu d’un arrêt, choisissez « Terminer l’étape ici » pour découper l’itinéraire.',
|
||||
'tours.stages.endHere': 'Terminer l’étape ici',
|
||||
'tours.stages.removeEnd': 'Retirer la fin d’étape',
|
||||
'tours.stages.stage': 'Étape {number}',
|
||||
'tours.stages.endOf': 'Fin de l’étape {number}',
|
||||
'tours.stages.title': 'Étapes',
|
||||
'tours.stages.count': '{count} étapes',
|
||||
'tours.stages.count.one': '{count} étape',
|
||||
'tours.stages.overDay': 'Plus d’une journée en mouvement',
|
||||
'tours.stages.timeInfo':
|
||||
'Temps en mouvement par étape, pauses non comprises. Pour le vélo et les balades en ville, le temps du calculateur d’itinéraire est réparti selon la distance.',
|
||||
'tours.stages.lost': '{count} fins d’étape ont disparu avec leurs arrêts ; ces étapes sont désormais réunies.',
|
||||
'tours.stages.lost.one': '{count} fin d’étape a disparu avec son arrêt ; ses deux étapes sont désormais réunies.',
|
||||
'tours.stages.toDayTitle': 'En faire une sortie d’une journée ?',
|
||||
'tours.stages.toDayBody':
|
||||
'Les {count} fins d’étape sont retirées. L’itinéraire reste tel quel, et Annuler les rétablit.',
|
||||
'tours.stages.toDayBody.one': '{count} fin d’étape est retirée. L’itinéraire reste tel quel, et Annuler la rétablit.',
|
||||
'tours.stages.toDayConfirm': 'Retirer les fins d’étape',
|
||||
'tours.stages.show': 'Montrer où finit cette étape',
|
||||
};
|
||||
export default tours;
|
||||
|
||||
@@ -1299,5 +1299,28 @@ const tours: TranslationStrings = {
|
||||
'tours.stats.excludeFailed': 'Δεν ήταν δυνατή η αλλαγή της εκδρομής',
|
||||
'tours.stats.unavailable': 'Δεν ήταν δυνατή η φόρτωση των εκδρομών',
|
||||
'tours.stats.openTrip': 'Άνοιγμα ταξιδιού',
|
||||
// MDT-01: a multi-day tour, one route split into stages.
|
||||
'tours.stages.mode': 'Ημέρες',
|
||||
'tours.stages.mode.single': 'Μονοήμερη',
|
||||
'tours.stages.mode.multi': 'Πολυήμερη',
|
||||
'tours.stages.emptyHint': 'Στο μενού μιας στάσης επιλέξτε «Τέλος σκέλους εδώ» για να χωρίσετε τη διαδρομή.',
|
||||
'tours.stages.endHere': 'Τέλος σκέλους εδώ',
|
||||
'tours.stages.removeEnd': 'Αφαίρεση τέλους σκέλους',
|
||||
'tours.stages.stage': 'Σκέλος {number}',
|
||||
'tours.stages.endOf': 'Τέλος σκέλους {number}',
|
||||
'tours.stages.title': 'Σκέλη',
|
||||
'tours.stages.count': '{count} σκέλη',
|
||||
'tours.stages.count.one': '{count} σκέλος',
|
||||
'tours.stages.overDay': 'Πάνω από μία ημέρα σε κίνηση',
|
||||
'tours.stages.timeInfo':
|
||||
'Χρόνος κίνησης ανά σκέλος, χωρίς διαλείμματα. Σε ποδηλατικές διαδρομές και βόλτες στην πόλη ο χρόνος του σχεδιαστή διαδρομών μοιράζεται ανάλογα με την απόσταση.',
|
||||
'tours.stages.lost': '{count} τέλη σκελών χάθηκαν μαζί με τις στάσεις τους· αυτά τα σκέλη ενώθηκαν.',
|
||||
'tours.stages.lost.one': '{count} τέλος σκέλους χάθηκε μαζί με τη στάση του· τα δύο σκέλη του ενώθηκαν.',
|
||||
'tours.stages.toDayTitle': 'Να γίνει μονοήμερη;',
|
||||
'tours.stages.toDayBody': 'Αφαιρούνται {count} τέλη σκελών. Η διαδρομή μένει ως έχει και η Αναίρεση τα επαναφέρει.',
|
||||
'tours.stages.toDayBody.one':
|
||||
'Αφαιρείται {count} τέλος σκέλους. Η διαδρομή μένει ως έχει και η Αναίρεση το επαναφέρει.',
|
||||
'tours.stages.toDayConfirm': 'Αφαίρεση τελών σκελών',
|
||||
'tours.stages.show': 'Εμφάνιση του σημείου όπου τελειώνει το σκέλος',
|
||||
};
|
||||
export default tours;
|
||||
|
||||
@@ -1277,5 +1277,28 @@ const tours: TranslationStrings = {
|
||||
'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',
|
||||
// MDT-01: a multi-day tour, one route split into stages.
|
||||
'tours.stages.mode': 'Napok',
|
||||
'tours.stages.mode.single': 'Egynapos',
|
||||
'tours.stages.mode.multi': 'Többnapos',
|
||||
'tours.stages.emptyHint': 'Az útvonal felosztásához egy megálló menüjében válaszd a „Szakasz vége itt” lehetőséget.',
|
||||
'tours.stages.endHere': 'Szakasz vége itt',
|
||||
'tours.stages.removeEnd': 'Szakaszvég eltávolítása',
|
||||
'tours.stages.stage': '{number}. szakasz',
|
||||
'tours.stages.endOf': 'A(z) {number}. szakasz vége',
|
||||
'tours.stages.title': 'Szakaszok',
|
||||
'tours.stages.count': '{count} szakasz',
|
||||
'tours.stages.count.one': '{count} szakasz',
|
||||
'tours.stages.overDay': 'Több mint egy nap mozgásban',
|
||||
'tours.stages.timeInfo':
|
||||
'Mozgási idő szakaszonként, szünetek nélkül. Kerékpártúráknál és városi sétáknál az útvonaltervező idejét táv szerint osztjuk el.',
|
||||
'tours.stages.lost': '{count} szakaszvég eltűnt a megállóival; ezek a szakaszok most össze vannak vonva.',
|
||||
'tours.stages.lost.one': '{count} szakaszvég eltűnt a megállójával; a két szakasza most össze van vonva.',
|
||||
'tours.stages.toDayTitle': 'Legyen egynapos túra?',
|
||||
'tours.stages.toDayBody':
|
||||
'{count} szakaszvég törlődik. Az útvonal változatlan marad, a Visszavonás visszahozza őket.',
|
||||
'tours.stages.toDayBody.one': '{count} szakaszvég törlődik. Az útvonal változatlan marad, a Visszavonás visszahozza.',
|
||||
'tours.stages.toDayConfirm': 'Szakaszvégek eltávolítása',
|
||||
'tours.stages.show': 'Mutasd, hol ér véget a szakasz',
|
||||
};
|
||||
export default tours;
|
||||
|
||||
@@ -1258,5 +1258,24 @@ const tours: TranslationStrings = {
|
||||
'tours.stats.excludeFailed': 'Tur tidak dapat diubah',
|
||||
'tours.stats.unavailable': 'Tur yang sudah dilakukan tidak dapat dimuat',
|
||||
'tours.stats.openTrip': 'Buka perjalanan',
|
||||
// MDT-01: a multi-day tour, one route split into stages.
|
||||
'tours.stages.mode': 'Hari',
|
||||
'tours.stages.mode.single': 'Satu hari',
|
||||
'tours.stages.mode.multi': 'Beberapa hari',
|
||||
'tours.stages.emptyHint': 'Di menu sebuah perhentian, pilih "Akhiri etape di sini" untuk membagi rute.',
|
||||
'tours.stages.endHere': 'Akhiri etape di sini',
|
||||
'tours.stages.removeEnd': 'Hapus akhir etape',
|
||||
'tours.stages.stage': 'Etape {number}',
|
||||
'tours.stages.endOf': 'Akhir etape {number}',
|
||||
'tours.stages.title': 'Etape',
|
||||
'tours.stages.count': '{count} etape',
|
||||
'tours.stages.overDay': 'Lebih dari sehari bergerak',
|
||||
'tours.stages.timeInfo':
|
||||
'Waktu bergerak per etape, tanpa istirahat. Untuk tur sepeda dan jalan kaki di kota, waktu perencana rute dibagi menurut jarak.',
|
||||
'tours.stages.lost': '{count} akhir etape hilang bersama perhentiannya; etape-etape itu kini digabung.',
|
||||
'tours.stages.toDayTitle': 'Jadikan tur sehari?',
|
||||
'tours.stages.toDayBody': '{count} akhir etape dihapus. Rute tetap seperti semula, dan Urungkan mengembalikannya.',
|
||||
'tours.stages.toDayConfirm': 'Hapus semua akhir etape',
|
||||
'tours.stages.show': 'Tunjukkan tempat etape ini berakhir',
|
||||
};
|
||||
export default tours;
|
||||
|
||||
@@ -1286,5 +1286,27 @@ const tours: TranslationStrings = {
|
||||
'tours.stats.excludeFailed': 'Non è stato possibile modificare l’escursione',
|
||||
'tours.stats.unavailable': 'Non è stato possibile caricare le escursioni svolte',
|
||||
'tours.stats.openTrip': 'Apri viaggio',
|
||||
// MDT-01: a multi-day tour, one route split into stages.
|
||||
'tours.stages.mode': 'Giorni',
|
||||
'tours.stages.mode.single': 'Un giorno',
|
||||
'tours.stages.mode.multi': 'Più giorni',
|
||||
'tours.stages.emptyHint': 'Nel menu di una sosta, scegli «Termina la tappa qui» per dividere il percorso.',
|
||||
'tours.stages.endHere': 'Termina la tappa qui',
|
||||
'tours.stages.removeEnd': 'Togli fine tappa',
|
||||
'tours.stages.stage': 'Tappa {number}',
|
||||
'tours.stages.endOf': 'Fine della tappa {number}',
|
||||
'tours.stages.title': 'Tappe',
|
||||
'tours.stages.count': '{count} tappe',
|
||||
'tours.stages.count.one': '{count} tappa',
|
||||
'tours.stages.overDay': 'Più di un giorno in movimento',
|
||||
'tours.stages.timeInfo':
|
||||
'Tempo in movimento per tappa, pause escluse. Per bici e passeggiate in città il tempo del calcolo percorso è ripartito per distanza.',
|
||||
'tours.stages.lost': '{count} fini tappa sono sparite con le loro soste; quelle tappe ora sono unite.',
|
||||
'tours.stages.lost.one': '{count} fine tappa è sparita con la sua sosta; le sue due tappe ora sono unite.',
|
||||
'tours.stages.toDayTitle': 'Farne un giro di un giorno?',
|
||||
'tours.stages.toDayBody': 'Le {count} fini tappa vengono tolte. Il percorso resta com’è e Annulla le ripristina.',
|
||||
'tours.stages.toDayBody.one': '{count} fine tappa viene tolta. Il percorso resta com’è e Annulla la ripristina.',
|
||||
'tours.stages.toDayConfirm': 'Togli le fini tappa',
|
||||
'tours.stages.show': 'Mostra dove finisce questa tappa',
|
||||
};
|
||||
export default tours;
|
||||
|
||||
@@ -1222,5 +1222,23 @@ const tours: TranslationStrings = {
|
||||
'tours.stats.excludeFailed': 'ツアーを変更できませんでした',
|
||||
'tours.stats.unavailable': 'ツアー実績を読み込めませんでした',
|
||||
'tours.stats.openTrip': '旅行を開く',
|
||||
// MDT-01: a multi-day tour, one route split into stages.
|
||||
'tours.stages.mode': '日数',
|
||||
'tours.stages.mode.single': '日帰り',
|
||||
'tours.stages.mode.multi': '複数日',
|
||||
'tours.stages.emptyHint': 'ルートを分けるには、立ち寄り地点のメニューで「ここでステージを終える」を選びます。',
|
||||
'tours.stages.endHere': 'ここでステージを終える',
|
||||
'tours.stages.removeEnd': 'ステージの終点を外す',
|
||||
'tours.stages.stage': 'ステージ {number}',
|
||||
'tours.stages.endOf': 'ステージ {number} の終点',
|
||||
'tours.stages.title': 'ステージ',
|
||||
'tours.stages.count': '{count} ステージ',
|
||||
'tours.stages.overDay': '移動だけで1日を超えます',
|
||||
'tours.stages.timeInfo': 'ステージごとの移動時間で、休憩は含みません。自転車ツアーと街歩きでは、ルート計算の時間を距離で按分します。',
|
||||
'tours.stages.lost': '{count} 件のステージ終点が地点とともに消えたため、それらのステージは結合されました。',
|
||||
'tours.stages.toDayTitle': '日帰りツアーにしますか?',
|
||||
'tours.stages.toDayBody': '{count} 件のステージ終点を外します。ルートはそのままで、元に戻すで復元できます。',
|
||||
'tours.stages.toDayConfirm': 'ステージの終点をすべて外す',
|
||||
'tours.stages.show': 'このステージの終点を表示',
|
||||
};
|
||||
export default tours;
|
||||
|
||||
@@ -1220,5 +1220,23 @@ const tours: TranslationStrings = {
|
||||
'tours.stats.excludeFailed': '투어를 변경할 수 없습니다',
|
||||
'tours.stats.unavailable': '투어 기록을 불러올 수 없습니다',
|
||||
'tours.stats.openTrip': '여행 열기',
|
||||
// MDT-01: a multi-day tour, one route split into stages.
|
||||
'tours.stages.mode': '일수',
|
||||
'tours.stages.mode.single': '당일',
|
||||
'tours.stages.mode.multi': '여러 날',
|
||||
'tours.stages.emptyHint': '경로를 나누려면 정류 지점 메뉴에서 "여기서 구간 종료"를 선택하세요.',
|
||||
'tours.stages.endHere': '여기서 구간 종료',
|
||||
'tours.stages.removeEnd': '구간 끝 제거',
|
||||
'tours.stages.stage': '{number}구간',
|
||||
'tours.stages.endOf': '{number}구간 끝',
|
||||
'tours.stages.title': '구간',
|
||||
'tours.stages.count': '{count}개 구간',
|
||||
'tours.stages.overDay': '이동만 하루 이상',
|
||||
'tours.stages.timeInfo': '구간별 이동 시간이며 휴식은 포함하지 않습니다. 자전거 투어와 도시 산책은 경로 계산 시간을 거리에 따라 나눕니다.',
|
||||
'tours.stages.lost': '구간 끝 {count}개가 지점과 함께 사라져 해당 구간이 합쳐졌습니다.',
|
||||
'tours.stages.toDayTitle': '당일 투어로 바꿀까요?',
|
||||
'tours.stages.toDayBody': '구간 끝 {count}개를 제거합니다. 경로는 그대로이며 실행 취소로 되돌릴 수 있습니다.',
|
||||
'tours.stages.toDayConfirm': '구간 끝 모두 제거',
|
||||
'tours.stages.show': '이 구간이 끝나는 곳 보기',
|
||||
};
|
||||
export default tours;
|
||||
|
||||
@@ -1280,5 +1280,29 @@ const tours: TranslationStrings = {
|
||||
'tours.stats.excludeFailed': 'De tocht kon niet worden gewijzigd',
|
||||
'tours.stats.unavailable': 'Gemaakte tochten konden niet worden geladen',
|
||||
'tours.stats.openTrip': 'Reis openen',
|
||||
// MDT-01: a multi-day tour, one route split into stages.
|
||||
'tours.stages.mode': 'Dagen',
|
||||
'tours.stages.mode.single': 'Dagtocht',
|
||||
'tours.stages.mode.multi': 'Meerdaagse tocht',
|
||||
'tours.stages.emptyHint': 'Kies in het menu van een stop „Etappe hier beëindigen” om de route te verdelen.',
|
||||
'tours.stages.endHere': 'Etappe hier beëindigen',
|
||||
'tours.stages.removeEnd': 'Etappe-einde verwijderen',
|
||||
'tours.stages.stage': 'Etappe {number}',
|
||||
'tours.stages.endOf': 'Einde van etappe {number}',
|
||||
'tours.stages.title': 'Etappes',
|
||||
'tours.stages.count': '{count} etappes',
|
||||
'tours.stages.count.one': '{count} etappe',
|
||||
'tours.stages.overDay': 'Meer dan een dag onderweg',
|
||||
'tours.stages.timeInfo':
|
||||
'Bewegingstijd per etappe, zonder pauzes. Bij fietstochten en stadswandelingen wordt de tijd van de routeplanner naar afstand verdeeld.',
|
||||
'tours.stages.lost': '{count} etappe-eindes zijn met hun stops verdwenen; die etappes zijn nu samengevoegd.',
|
||||
'tours.stages.lost.one': '{count} etappe-einde is met zijn stop verdwenen; zijn twee etappes zijn nu samengevoegd.',
|
||||
'tours.stages.toDayTitle': 'Er een dagtocht van maken?',
|
||||
'tours.stages.toDayBody':
|
||||
'De {count} etappe-eindes worden verwijderd. De route blijft zoals hij is en Ongedaan maken zet ze terug.',
|
||||
'tours.stages.toDayBody.one':
|
||||
'{count} etappe-einde wordt verwijderd. De route blijft zoals hij is en Ongedaan maken zet het terug.',
|
||||
'tours.stages.toDayConfirm': 'Etappe-eindes verwijderen',
|
||||
'tours.stages.show': 'Toon waar deze etappe eindigt',
|
||||
};
|
||||
export default tours;
|
||||
|
||||
@@ -1300,5 +1300,33 @@ const tours: TranslationStrings = {
|
||||
'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óż',
|
||||
// MDT-01: a multi-day tour, one route split into stages.
|
||||
'tours.stages.mode': 'Dni',
|
||||
'tours.stages.mode.single': 'Jednodniowa',
|
||||
'tours.stages.mode.multi': 'Wielodniowa',
|
||||
'tours.stages.emptyHint': 'W menu przystanku wybierz „Zakończ etap tutaj”, aby podzielić trasę.',
|
||||
'tours.stages.endHere': 'Zakończ etap tutaj',
|
||||
'tours.stages.removeEnd': 'Usuń koniec etapu',
|
||||
'tours.stages.stage': 'Etap {number}',
|
||||
'tours.stages.endOf': 'Koniec etapu {number}',
|
||||
'tours.stages.title': 'Etapy',
|
||||
'tours.stages.count': '{count} etapu',
|
||||
'tours.stages.count.one': '{count} etap',
|
||||
'tours.stages.count.few': '{count} etapy',
|
||||
'tours.stages.count.many': '{count} etapów',
|
||||
'tours.stages.overDay': 'Ponad dzień w ruchu',
|
||||
'tours.stages.timeInfo':
|
||||
'Czas w ruchu na etap, bez przerw. Przy wycieczkach rowerowych i spacerach po mieście czas planera tras dzieli się według dystansu.',
|
||||
'tours.stages.lost': '{count} końca etapu zniknęło z przystankami; te etapy są teraz połączone.',
|
||||
'tours.stages.lost.one': '{count} koniec etapu zniknął ze swoim przystankiem; jego dwa etapy są teraz połączone.',
|
||||
'tours.stages.lost.few': '{count} końce etapów zniknęły z przystankami; te etapy są teraz połączone.',
|
||||
'tours.stages.lost.many': '{count} końców etapów zniknęło z przystankami; te etapy są teraz połączone.',
|
||||
'tours.stages.toDayTitle': 'Zmienić na wycieczkę jednodniową?',
|
||||
'tours.stages.toDayBody': 'Usuwamy {count} końca etapu. Trasa zostaje bez zmian, a Cofnij je przywróci.',
|
||||
'tours.stages.toDayBody.one': 'Usuwamy {count} koniec etapu. Trasa zostaje bez zmian, a Cofnij go przywróci.',
|
||||
'tours.stages.toDayBody.few': 'Usuwamy {count} końce etapów. Trasa zostaje bez zmian, a Cofnij je przywróci.',
|
||||
'tours.stages.toDayBody.many': 'Usuwamy {count} końców etapów. Trasa zostaje bez zmian, a Cofnij je przywróci.',
|
||||
'tours.stages.toDayConfirm': 'Usuń końce etapów',
|
||||
'tours.stages.show': 'Pokaż, gdzie kończy się ten etap',
|
||||
};
|
||||
export default tours;
|
||||
|
||||
@@ -1307,5 +1307,33 @@ const tours: TranslationStrings = {
|
||||
'tours.stats.excludeFailed': 'Не удалось изменить тур',
|
||||
'tours.stats.unavailable': 'Не удалось загрузить пройденные туры',
|
||||
'tours.stats.openTrip': 'Открыть поездку',
|
||||
// MDT-01: a multi-day tour, one route split into stages.
|
||||
'tours.stages.mode': 'Дни',
|
||||
'tours.stages.mode.single': 'Однодневный',
|
||||
'tours.stages.mode.multi': 'Многодневный',
|
||||
'tours.stages.emptyHint': 'В меню остановки выберите «Завершить этап здесь», чтобы разделить маршрут.',
|
||||
'tours.stages.endHere': 'Завершить этап здесь',
|
||||
'tours.stages.removeEnd': 'Убрать конец этапа',
|
||||
'tours.stages.stage': 'Этап {number}',
|
||||
'tours.stages.endOf': 'Конец этапа {number}',
|
||||
'tours.stages.title': 'Этапы',
|
||||
'tours.stages.count': '{count} этапа',
|
||||
'tours.stages.count.one': '{count} этап',
|
||||
'tours.stages.count.few': '{count} этапа',
|
||||
'tours.stages.count.many': '{count} этапов',
|
||||
'tours.stages.overDay': 'Больше дня в пути',
|
||||
'tours.stages.timeInfo':
|
||||
'Время в движении на этап, без перерывов. Для велотуров и городских прогулок время маршрутизатора делится по расстоянию.',
|
||||
'tours.stages.lost': '{count} конца этапа пропало вместе с остановками; эти этапы теперь объединены.',
|
||||
'tours.stages.lost.one': '{count} конец этапа пропал вместе с остановкой; два его этапа теперь объединены.',
|
||||
'tours.stages.lost.few': '{count} конца этапов пропали вместе с остановками; эти этапы теперь объединены.',
|
||||
'tours.stages.lost.many': '{count} концов этапов пропали вместе с остановками; эти этапы теперь объединены.',
|
||||
'tours.stages.toDayTitle': 'Сделать однодневным?',
|
||||
'tours.stages.toDayBody': 'Будет убрано {count} конца этапа. Маршрут останется прежним, а Отменить вернёт их.',
|
||||
'tours.stages.toDayBody.one': 'Будет убран {count} конец этапа. Маршрут останется прежним, а Отменить вернёт его.',
|
||||
'tours.stages.toDayBody.few': 'Будут убраны {count} конца этапов. Маршрут останется прежним, а Отменить вернёт их.',
|
||||
'tours.stages.toDayBody.many': 'Будут убраны {count} концов этапов. Маршрут останется прежним, а Отменить вернёт их.',
|
||||
'tours.stages.toDayConfirm': 'Убрать концы этапов',
|
||||
'tours.stages.show': 'Показать, где заканчивается этап',
|
||||
};
|
||||
export default tours;
|
||||
|
||||
@@ -1262,5 +1262,30 @@ const tours: TranslationStrings = {
|
||||
'tours.stats.excludeFailed': 'Túru sa nepodarilo zmeniť',
|
||||
'tours.stats.unavailable': 'Absolvované túry sa nepodarilo načítať',
|
||||
'tours.stats.openTrip': 'Otvoriť cestu',
|
||||
// MDT-01: a multi-day tour, one route split into stages.
|
||||
'tours.stages.mode': 'Dni',
|
||||
'tours.stages.mode.single': 'Jednodňová',
|
||||
'tours.stages.mode.multi': 'Viacdňová',
|
||||
'tours.stages.emptyHint': 'V ponuke zastávky zvoľte „Ukončiť etapu tu“ a rozdeľte trasu.',
|
||||
'tours.stages.endHere': 'Ukončiť etapu tu',
|
||||
'tours.stages.removeEnd': 'Odobrať koniec etapy',
|
||||
'tours.stages.stage': 'Etapa {number}',
|
||||
'tours.stages.endOf': 'Koniec etapy {number}',
|
||||
'tours.stages.title': 'Etapy',
|
||||
'tours.stages.count': '{count} etáp',
|
||||
'tours.stages.count.one': '{count} etapa',
|
||||
'tours.stages.count.few': '{count} etapy',
|
||||
'tours.stages.overDay': 'Viac ako deň v pohybe',
|
||||
'tours.stages.timeInfo':
|
||||
'Čas v pohybe na etapu, bez prestávok. Pri cyklotúrach a mestských prechádzkach sa čas plánovača trás delí podľa vzdialenosti.',
|
||||
'tours.stages.lost': '{count} koncov etáp zmizlo so zastávkami; tieto etapy sú teraz spojené.',
|
||||
'tours.stages.lost.one': '{count} koniec etapy zmizol so zastávkou; jeho dve etapy sú teraz spojené.',
|
||||
'tours.stages.lost.few': '{count} konce etáp zmizli so zastávkami; tieto etapy sú teraz spojené.',
|
||||
'tours.stages.toDayTitle': 'Urobiť z nej jednodňovú túru?',
|
||||
'tours.stages.toDayBody': 'Odoberie sa {count} koncov etáp. Trasa zostane, ako je, a Späť ich vráti.',
|
||||
'tours.stages.toDayBody.one': 'Odoberie sa {count} koniec etapy. Trasa zostane, ako je, a Späť ho vráti.',
|
||||
'tours.stages.toDayBody.few': 'Odoberú sa {count} konce etáp. Trasa zostane, ako je, a Späť ich vráti.',
|
||||
'tours.stages.toDayConfirm': 'Odobrať konce etáp',
|
||||
'tours.stages.show': 'Ukázať, kde etapa končí',
|
||||
};
|
||||
export default tours;
|
||||
|
||||
@@ -1251,5 +1251,27 @@ const tours: TranslationStrings = {
|
||||
'tours.stats.excludeFailed': 'Turen kunde inte ändras',
|
||||
'tours.stats.unavailable': 'Genomförda turer kunde inte laddas',
|
||||
'tours.stats.openTrip': 'Öppna resan',
|
||||
// MDT-01: a multi-day tour, one route split into stages.
|
||||
'tours.stages.mode': 'Dagar',
|
||||
'tours.stages.mode.single': 'Dagstur',
|
||||
'tours.stages.mode.multi': 'Flerdagarstur',
|
||||
'tours.stages.emptyHint': 'Välj ”Avsluta etappen här” i menyn för ett stopp för att dela upp rutten.',
|
||||
'tours.stages.endHere': 'Avsluta etappen här',
|
||||
'tours.stages.removeEnd': 'Ta bort etappslut',
|
||||
'tours.stages.stage': 'Etapp {number}',
|
||||
'tours.stages.endOf': 'Slutet på etapp {number}',
|
||||
'tours.stages.title': 'Etapper',
|
||||
'tours.stages.count': '{count} etapper',
|
||||
'tours.stages.count.one': '{count} etapp',
|
||||
'tours.stages.overDay': 'Mer än en dag i rörelse',
|
||||
'tours.stages.timeInfo':
|
||||
'Rörelsetid per etapp, utan pauser. För cykelturer och stadspromenader fördelas ruttplanerarens tid efter sträcka.',
|
||||
'tours.stages.lost': '{count} etappslut försvann med sina stopp; de etapperna är nu sammanslagna.',
|
||||
'tours.stages.lost.one': '{count} etappslut försvann med sitt stopp; dess två etapper är nu sammanslagna.',
|
||||
'tours.stages.toDayTitle': 'Göra det till en dagstur?',
|
||||
'tours.stages.toDayBody': 'De {count} etappsluten tas bort. Rutten förblir som den är, och Ångra tar tillbaka dem.',
|
||||
'tours.stages.toDayBody.one': '{count} etappslut tas bort. Rutten förblir som den är, och Ångra tar tillbaka det.',
|
||||
'tours.stages.toDayConfirm': 'Ta bort etappsluten',
|
||||
'tours.stages.show': 'Visa var etappen slutar',
|
||||
};
|
||||
export default tours;
|
||||
|
||||
@@ -1203,5 +1203,24 @@ const tours: TranslationStrings = {
|
||||
'tours.stats.excludeFailed': 'เปลี่ยนทัวร์ไม่ได้',
|
||||
'tours.stats.unavailable': 'โหลดทัวร์ที่ทำแล้วไม่ได้',
|
||||
'tours.stats.openTrip': 'เปิดทริป',
|
||||
// MDT-01: a multi-day tour, one route split into stages.
|
||||
'tours.stages.mode': 'จำนวนวัน',
|
||||
'tours.stages.mode.single': 'วันเดียว',
|
||||
'tours.stages.mode.multi': 'หลายวัน',
|
||||
'tours.stages.emptyHint': 'หากต้องการแบ่งเส้นทาง ให้เปิดเมนูของจุดแวะแล้วเลือก "จบช่วงที่นี่"',
|
||||
'tours.stages.endHere': 'จบช่วงที่นี่',
|
||||
'tours.stages.removeEnd': 'นำจุดจบช่วงออก',
|
||||
'tours.stages.stage': 'ช่วงที่ {number}',
|
||||
'tours.stages.endOf': 'จุดจบของช่วงที่ {number}',
|
||||
'tours.stages.title': 'ช่วงการเดินทาง',
|
||||
'tours.stages.count': '{count} ช่วง',
|
||||
'tours.stages.overDay': 'เคลื่อนที่นานกว่าหนึ่งวัน',
|
||||
'tours.stages.timeInfo':
|
||||
'เวลาเคลื่อนที่ของแต่ละช่วง ไม่รวมเวลาพัก สำหรับทริปจักรยานและการเดินในเมือง เวลาของตัววางเส้นทางจะแบ่งตามระยะทาง',
|
||||
'tours.stages.lost': 'จุดจบช่วง {count} จุดหายไปพร้อมจุดแวะ ช่วงเหล่านั้นจึงถูกรวมกันแล้ว',
|
||||
'tours.stages.toDayTitle': 'เปลี่ยนเป็นทริปวันเดียวไหม',
|
||||
'tours.stages.toDayBody': 'จะนำจุดจบช่วง {count} จุดออก เส้นทางยังคงเหมือนเดิม และเลิกทำจะนำกลับมาได้',
|
||||
'tours.stages.toDayConfirm': 'นำจุดจบช่วงทั้งหมดออก',
|
||||
'tours.stages.show': 'แสดงตำแหน่งที่ช่วงนี้จบ',
|
||||
};
|
||||
export default tours;
|
||||
|
||||
@@ -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 = 1144;
|
||||
const GENERAL_KEY_COUNT = 1161;
|
||||
|
||||
function literalKeys(source: string): string[] {
|
||||
const tree = ts.createSourceFile('locale.ts', source, ts.ScriptTarget.Latest, true);
|
||||
|
||||
@@ -1258,5 +1258,27 @@ const tours: TranslationStrings = {
|
||||
'tours.stats.excludeFailed': 'Tur değiştirilemedi',
|
||||
'tours.stats.unavailable': 'Tamamlanan turlar yüklenemedi',
|
||||
'tours.stats.openTrip': 'Seyahati aç',
|
||||
// MDT-01: a multi-day tour, one route split into stages.
|
||||
'tours.stages.mode': 'Günler',
|
||||
'tours.stages.mode.single': 'Günübirlik',
|
||||
'tours.stages.mode.multi': 'Çok günlü',
|
||||
'tours.stages.emptyHint': 'Rotayı bölmek için bir durağın menüsünde "Etabı burada bitir" seçeneğini seçin.',
|
||||
'tours.stages.endHere': 'Etabı burada bitir',
|
||||
'tours.stages.removeEnd': 'Etap sonunu kaldır',
|
||||
'tours.stages.stage': 'Etap {number}',
|
||||
'tours.stages.endOf': 'Etap {number} sonu',
|
||||
'tours.stages.title': 'Etaplar',
|
||||
'tours.stages.count': '{count} etap',
|
||||
'tours.stages.count.one': '{count} etap',
|
||||
'tours.stages.overDay': 'Bir günden fazla hareket',
|
||||
'tours.stages.timeInfo':
|
||||
'Etap başına hareket süresi, molalar hariç. Bisiklet turlarında ve şehir yürüyüşlerinde rota planlayıcısının süresi mesafeye göre bölünür.',
|
||||
'tours.stages.lost': '{count} etap sonu durakları ile birlikte kayboldu; bu etaplar artık birleşik.',
|
||||
'tours.stages.lost.one': '{count} etap sonu durağı ile birlikte kayboldu; iki etabı artık birleşik.',
|
||||
'tours.stages.toDayTitle': 'Günübirlik tura çevrilsin mi?',
|
||||
'tours.stages.toDayBody': '{count} etap sonu kaldırılır. Rota olduğu gibi kalır, Geri al bunları geri getirir.',
|
||||
'tours.stages.toDayBody.one': '{count} etap sonu kaldırılır. Rota olduğu gibi kalır, Geri al onu geri getirir.',
|
||||
'tours.stages.toDayConfirm': 'Etap sonlarını kaldır',
|
||||
'tours.stages.show': 'Bu etabın bittiği yeri göster',
|
||||
};
|
||||
export default tours;
|
||||
|
||||
@@ -1293,5 +1293,33 @@ const tours: TranslationStrings = {
|
||||
'tours.stats.excludeFailed': 'Не вдалося змінити тур',
|
||||
'tours.stats.unavailable': 'Не вдалося завантажити пройдені тури',
|
||||
'tours.stats.openTrip': 'Відкрити подорож',
|
||||
// MDT-01: a multi-day tour, one route split into stages.
|
||||
'tours.stages.mode': 'Дні',
|
||||
'tours.stages.mode.single': 'Одноденний',
|
||||
'tours.stages.mode.multi': 'Багатоденний',
|
||||
'tours.stages.emptyHint': 'У меню зупинки виберіть «Завершити етап тут», щоб розділити маршрут.',
|
||||
'tours.stages.endHere': 'Завершити етап тут',
|
||||
'tours.stages.removeEnd': 'Прибрати кінець етапу',
|
||||
'tours.stages.stage': 'Етап {number}',
|
||||
'tours.stages.endOf': 'Кінець етапу {number}',
|
||||
'tours.stages.title': 'Етапи',
|
||||
'tours.stages.count': '{count} етапу',
|
||||
'tours.stages.count.one': '{count} етап',
|
||||
'tours.stages.count.few': '{count} етапи',
|
||||
'tours.stages.count.many': '{count} етапів',
|
||||
'tours.stages.overDay': 'Понад день у русі',
|
||||
'tours.stages.timeInfo':
|
||||
'Час у русі на етап, без перерв. Для велотурів і міських прогулянок час маршрутизатора ділиться за відстанню.',
|
||||
'tours.stages.lost': '{count} кінця етапу зникло разом із зупинками; ці етапи тепер об’єднано.',
|
||||
'tours.stages.lost.one': '{count} кінець етапу зник разом із зупинкою; два його етапи тепер об’єднано.',
|
||||
'tours.stages.lost.few': '{count} кінці етапів зникли разом із зупинками; ці етапи тепер об’єднано.',
|
||||
'tours.stages.lost.many': '{count} кінців етапів зникли разом із зупинками; ці етапи тепер об’єднано.',
|
||||
'tours.stages.toDayTitle': 'Зробити одноденним?',
|
||||
'tours.stages.toDayBody': 'Буде прибрано {count} кінця етапу. Маршрут лишиться як є, а Скасувати поверне їх.',
|
||||
'tours.stages.toDayBody.one': 'Буде прибрано {count} кінець етапу. Маршрут лишиться як є, а Скасувати поверне його.',
|
||||
'tours.stages.toDayBody.few': 'Буде прибрано {count} кінці етапів. Маршрут лишиться як є, а Скасувати поверне їх.',
|
||||
'tours.stages.toDayBody.many': 'Буде прибрано {count} кінців етапів. Маршрут лишиться як є, а Скасувати поверне їх.',
|
||||
'tours.stages.toDayConfirm': 'Прибрати кінці етапів',
|
||||
'tours.stages.show': 'Показати, де закінчується етап',
|
||||
};
|
||||
export default tours;
|
||||
|
||||
@@ -1247,5 +1247,24 @@ const tours: TranslationStrings = {
|
||||
'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',
|
||||
// MDT-01: a multi-day tour, one route split into stages.
|
||||
'tours.stages.mode': 'Số ngày',
|
||||
'tours.stages.mode.single': 'Một ngày',
|
||||
'tours.stages.mode.multi': 'Nhiều ngày',
|
||||
'tours.stages.emptyHint': 'Để chia tuyến, mở menu của một điểm dừng và chọn "Kết thúc chặng tại đây".',
|
||||
'tours.stages.endHere': 'Kết thúc chặng tại đây',
|
||||
'tours.stages.removeEnd': 'Bỏ điểm cuối chặng',
|
||||
'tours.stages.stage': 'Chặng {number}',
|
||||
'tours.stages.endOf': 'Cuối chặng {number}',
|
||||
'tours.stages.title': 'Các chặng',
|
||||
'tours.stages.count': '{count} chặng',
|
||||
'tours.stages.overDay': 'Di chuyển hơn một ngày',
|
||||
'tours.stages.timeInfo':
|
||||
'Thời gian di chuyển của từng chặng, không tính nghỉ. Với tour xe đạp và dạo phố, thời gian của bộ định tuyến được chia theo quãng đường.',
|
||||
'tours.stages.lost': '{count} điểm cuối chặng đã mất cùng điểm dừng; các chặng đó giờ được gộp lại.',
|
||||
'tours.stages.toDayTitle': 'Chuyển thành tour một ngày?',
|
||||
'tours.stages.toDayBody': 'Bỏ {count} điểm cuối chặng. Tuyến đường giữ nguyên, và Hoàn tác sẽ khôi phục chúng.',
|
||||
'tours.stages.toDayConfirm': 'Bỏ các điểm cuối chặng',
|
||||
'tours.stages.show': 'Hiện nơi chặng này kết thúc',
|
||||
};
|
||||
export default tours;
|
||||
|
||||
@@ -1163,5 +1163,23 @@ const tours: TranslationStrings = {
|
||||
'tours.stats.excludeFailed': '無法變更遊程',
|
||||
'tours.stats.unavailable': '無法載入遊程紀錄',
|
||||
'tours.stats.openTrip': '開啟行程',
|
||||
// MDT-01: a multi-day tour, one route split into stages.
|
||||
'tours.stages.mode': '天數',
|
||||
'tours.stages.mode.single': '一日遊',
|
||||
'tours.stages.mode.multi': '多日',
|
||||
'tours.stages.emptyHint': '要拆分路線,請在停留點的選單中選擇「在此結束階段」。',
|
||||
'tours.stages.endHere': '在此結束階段',
|
||||
'tours.stages.removeEnd': '移除階段終點',
|
||||
'tours.stages.stage': '第 {number} 階段',
|
||||
'tours.stages.endOf': '第 {number} 階段終點',
|
||||
'tours.stages.title': '階段',
|
||||
'tours.stages.count': '{count} 個階段',
|
||||
'tours.stages.overDay': '移動時間超過一天',
|
||||
'tours.stages.timeInfo': '每個階段的移動時間,不含休息。單車與城市散步依距離分攤路線規劃的時間。',
|
||||
'tours.stages.lost': '{count} 個階段終點隨停留點一起消失,相關階段已合併。',
|
||||
'tours.stages.toDayTitle': '改為一日遊?',
|
||||
'tours.stages.toDayBody': '將移除 {count} 個階段終點。路線保持不變,復原可以還原。',
|
||||
'tours.stages.toDayConfirm': '移除全部階段終點',
|
||||
'tours.stages.show': '顯示此階段的終點',
|
||||
};
|
||||
export default tours;
|
||||
|
||||
@@ -1164,5 +1164,23 @@ const tours: TranslationStrings = {
|
||||
'tours.stats.excludeFailed': '无法更改游览',
|
||||
'tours.stats.unavailable': '无法加载游览记录',
|
||||
'tours.stats.openTrip': '打开行程',
|
||||
// MDT-01: a multi-day tour, one route split into stages.
|
||||
'tours.stages.mode': '天数',
|
||||
'tours.stages.mode.single': '一日游',
|
||||
'tours.stages.mode.multi': '多日',
|
||||
'tours.stages.emptyHint': '要拆分路线,请在停留点的菜单中选择“在此结束阶段”。',
|
||||
'tours.stages.endHere': '在此结束阶段',
|
||||
'tours.stages.removeEnd': '移除阶段终点',
|
||||
'tours.stages.stage': '第 {number} 阶段',
|
||||
'tours.stages.endOf': '第 {number} 阶段终点',
|
||||
'tours.stages.title': '阶段',
|
||||
'tours.stages.count': '{count} 个阶段',
|
||||
'tours.stages.overDay': '移动时间超过一天',
|
||||
'tours.stages.timeInfo': '每个阶段的移动时间,不含休息。骑行和城市漫步按距离分摊路线规划的时间。',
|
||||
'tours.stages.lost': '{count} 个阶段终点随停留点一起消失,相关阶段已合并。',
|
||||
'tours.stages.toDayTitle': '改为一日游?',
|
||||
'tours.stages.toDayBody': '将移除 {count} 个阶段终点。路线保持不变,撤销可以恢复。',
|
||||
'tours.stages.toDayConfirm': '移除全部阶段终点',
|
||||
'tours.stages.show': '显示此阶段的终点',
|
||||
};
|
||||
export default tours;
|
||||
|
||||
@@ -57,6 +57,7 @@ export * from './place/track-colors';
|
||||
export * from './tours/tours.schema';
|
||||
export * from './tours/run-pace';
|
||||
export * from './tours/ski.schema';
|
||||
export * from './tours/tour-stages';
|
||||
export * from './tours/tour-provenance.schema';
|
||||
export * from './tours/tour-activity.schema';
|
||||
export * from './tours/tour-activity-stats.schema';
|
||||
|
||||
@@ -0,0 +1,135 @@
|
||||
import { MAX_TOUR_STAGES, maxTourMinutesFor, TOUR_DAY_MINUTES, tourStageCountOf, tourStageIssues } from './tour-stages';
|
||||
import {
|
||||
MAX_PLANNED_TOUR_DURATION_MINUTES,
|
||||
plannedTimeLimitMessage,
|
||||
tourCreateRequestSchema,
|
||||
tourListItemSchema,
|
||||
tourWaypointSchema,
|
||||
} from './tours.schema';
|
||||
|
||||
import { describe, expect, it } from 'vitest';
|
||||
|
||||
type Role = 'start' | 'via' | 'end';
|
||||
|
||||
/** A straight run of points along a meridian, each a stop unless told otherwise. */
|
||||
function points(count: number, extra: (index: number) => Record<string, unknown> = () => ({})) {
|
||||
return Array.from({ length: count }, (_, sequence) => ({
|
||||
lat: 47 + sequence * 0.01,
|
||||
lng: 11,
|
||||
role: (sequence === 0 ? 'start' : sequence === count - 1 ? 'end' : 'via') as Role,
|
||||
sequence,
|
||||
...extra(sequence),
|
||||
}));
|
||||
}
|
||||
|
||||
function request(waypoints: ReturnType<typeof points>, fields: Record<string, unknown> = {}) {
|
||||
return {
|
||||
name: 'Hut to hut',
|
||||
route_geometry: waypoints.map((point) => [point.lat, point.lng, 1000]),
|
||||
waypoints,
|
||||
...fields,
|
||||
};
|
||||
}
|
||||
|
||||
describe('MDT-01 tour stages', () => {
|
||||
it('MDT-SH-001: counts one stage for a day tour and one more for every stage end', () => {
|
||||
expect(tourStageCountOf([])).toBe(1);
|
||||
expect(tourStageCountOf(points(3))).toBe(1);
|
||||
expect(tourStageCountOf(points(5, (i) => (i === 1 || i === 3 ? { stage_end: true } : {})))).toBe(3);
|
||||
expect(tourStageCountOf([{ stage_end: false }])).toBe(1);
|
||||
});
|
||||
|
||||
it('MDT-SH-002: plans a day a stage, and never less than one day', () => {
|
||||
expect(TOUR_DAY_MINUTES).toBe(MAX_PLANNED_TOUR_DURATION_MINUTES);
|
||||
expect(maxTourMinutesFor(1)).toBe(1440);
|
||||
expect(maxTourMinutesFor(3)).toBe(3 * 1440);
|
||||
expect(maxTourMinutesFor(0)).toBe(1440);
|
||||
});
|
||||
|
||||
it('MDT-SH-003: takes a stage end at a stop between start and end only', () => {
|
||||
expect(tourStageIssues(points(4, (i) => (i === 1 ? { stage_end: true } : {})))).toEqual([]);
|
||||
expect(tourStageIssues(points(3, (i) => (i === 0 ? { stage_end: true } : {})))).toEqual([
|
||||
{ path: ['waypoints', 0, 'stage_end'], message: 'a stage ends at a stop between start and end' },
|
||||
]);
|
||||
expect(tourStageIssues(points(3, (i) => (i === 2 ? { stage_end: true } : {})))[0]?.path).toEqual([
|
||||
'waypoints',
|
||||
2,
|
||||
'stage_end',
|
||||
]);
|
||||
expect(tourStageIssues(points(3, (i) => (i === 1 ? { stage_end: true, shaping: true } : {})))).toEqual([
|
||||
{ path: ['waypoints', 1, 'stage_end'], message: 'a shaping point ends no stage' },
|
||||
]);
|
||||
});
|
||||
|
||||
it('MDT-SH-004: refuses a tour of more stages than the limit', () => {
|
||||
const many = points(MAX_TOUR_STAGES + 1, () => ({ stage_end: true })).map((point, index, all) =>
|
||||
index === 0 || index === all.length - 1 ? { ...point, stage_end: undefined } : point,
|
||||
);
|
||||
expect(tourStageCountOf(many)).toBe(MAX_TOUR_STAGES);
|
||||
expect(tourStageIssues(many)).toEqual([]);
|
||||
const tooMany = points(MAX_TOUR_STAGES + 2, (i) => (i > 0 ? { stage_end: true } : {})).map((point, index, all) =>
|
||||
index === all.length - 1 ? { ...point, stage_end: undefined } : point,
|
||||
);
|
||||
expect(tourStageIssues(tooMany)).toContainEqual({
|
||||
path: ['waypoints'],
|
||||
message: `a tour has at most ${MAX_TOUR_STAGES} stages`,
|
||||
});
|
||||
});
|
||||
|
||||
it('MDT-SH-005: a waypoint carries its stage end, and one saved before reads as none', () => {
|
||||
expect(tourWaypointSchema.parse({ lat: 47, lng: 11, role: 'via', sequence: 1, stage_end: true })).toMatchObject({
|
||||
stage_end: true,
|
||||
});
|
||||
expect(tourWaypointSchema.parse({ lat: 47, lng: 11, role: 'via', sequence: 1 })).not.toHaveProperty('stage_end');
|
||||
expect(tourWaypointSchema.safeParse({ lat: 47, lng: 11, role: 'via', sequence: 1, stage_end: 'yes' }).success).toBe(
|
||||
false,
|
||||
);
|
||||
});
|
||||
|
||||
it('MDT-SH-006: saves a multi-day tour of any one type, and refuses a stage end out of place', () => {
|
||||
const hutToHut = points(4, (i) => (i === 1 ? { stage_end: true } : {}));
|
||||
expect(tourCreateRequestSchema.safeParse(request(hutToHut)).success).toBe(true);
|
||||
const paddle = request(hutToHut, { tour_type: 'kayak', boat_type: 'canoe', water_context: 'river' });
|
||||
expect(tourCreateRequestSchema.safeParse(paddle).success).toBe(true);
|
||||
const atEnd = tourCreateRequestSchema.safeParse(request(points(3, (i) => (i === 2 ? { stage_end: true } : {}))));
|
||||
expect(atEnd.success).toBe(false);
|
||||
expect(atEnd.error?.issues.map((issue) => issue.path.join('.'))).toContain('waypoints.2.stage_end');
|
||||
});
|
||||
|
||||
it('MDT-SH-007: holds a multi-day tour to a day a stage, a day tour to one day', () => {
|
||||
const twoDays = points(3, (i) => (i === 1 ? { stage_end: true } : {}));
|
||||
const long = { duration_seconds: 30 * 3600, break_additional_minutes: 60 };
|
||||
expect(tourCreateRequestSchema.safeParse(request(twoDays, long)).success).toBe(true);
|
||||
const tooLong = tourCreateRequestSchema.safeParse(request(twoDays, { ...long, duration_seconds: 48 * 3600 }));
|
||||
expect(tooLong.success).toBe(false);
|
||||
expect(tooLong.error?.issues[0]?.message).toBe(plannedTimeLimitMessage(2));
|
||||
const dayTour = tourCreateRequestSchema.safeParse(request(points(3), long));
|
||||
expect(dayTour.success).toBe(false);
|
||||
expect(dayTour.error?.issues[0]?.message).toBe(
|
||||
'walking time plus breaks must be 1440 minutes or less unless a total is overridden',
|
||||
);
|
||||
expect(plannedTimeLimitMessage(2)).toContain('2880 minutes or less, a day a stage');
|
||||
});
|
||||
|
||||
it('MDT-SH-008: lists the stage count of a multi-day tour, and reads a list cached before it', () => {
|
||||
const item = {
|
||||
place_id: 1,
|
||||
tour_type: 'hike',
|
||||
distance: 12,
|
||||
elevation_gain: null,
|
||||
elevation_loss: null,
|
||||
duration: null,
|
||||
difficulty: null,
|
||||
wanderer_ref: null,
|
||||
match_confidence: 1,
|
||||
max_hiking_difficulty: 2,
|
||||
name: 'Hut to hut',
|
||||
planned: false,
|
||||
caution: false,
|
||||
};
|
||||
expect(tourListItemSchema.parse({ ...item, stage_count: 3 }).stage_count).toBe(3);
|
||||
expect(tourListItemSchema.parse({ ...item, stage_count: null }).stage_count).toBeNull();
|
||||
expect(tourListItemSchema.parse(item).stage_count).toBeUndefined();
|
||||
expect(tourListItemSchema.safeParse({ ...item, stage_count: 1 }).success).toBe(false);
|
||||
});
|
||||
});
|
||||
@@ -0,0 +1,57 @@
|
||||
/**
|
||||
* MDT-01: a multi-day tour is one tour, planned whole, whose stops mark where
|
||||
* each day's stage ends. The stages are read off the route in its order: the
|
||||
* first runs from the start to the first stage end, the next from there on,
|
||||
* the last to the end of the tour. Nothing about a stage is stored besides the
|
||||
* mark on its last stop, so the whole route and its stages cannot drift apart.
|
||||
* A tour without a mark is a day tour, as every tour saved before was.
|
||||
*/
|
||||
|
||||
/** A day in minutes: the most one stage, and a day tour, is planned for. */
|
||||
export const TOUR_DAY_MINUTES = 24 * 60;
|
||||
|
||||
/** The most stages a tour is split into, two months of days. */
|
||||
export const MAX_TOUR_STAGES = 60;
|
||||
|
||||
/** A point as a stage end is read from: where it sits on the tour, and whether it steers only. */
|
||||
interface StagePoint {
|
||||
role: 'start' | 'via' | 'end';
|
||||
shaping?: boolean;
|
||||
stage_end?: boolean;
|
||||
}
|
||||
|
||||
/** Where a request's stage ends do not hold, and why. */
|
||||
interface StageIssue {
|
||||
path: Array<string | number>;
|
||||
message: string;
|
||||
}
|
||||
|
||||
/** How many stages the points make: one more than their stage ends, one for a day tour. */
|
||||
export function tourStageCountOf(points: ReadonlyArray<{ stage_end?: boolean }>): number {
|
||||
return 1 + points.filter((point) => point.stage_end === true).length;
|
||||
}
|
||||
|
||||
/** The longest moving time plus breaks a tour of so many stages is planned for: a day each. */
|
||||
export function maxTourMinutesFor(stageCount: number): number {
|
||||
return Math.max(1, stageCount) * TOUR_DAY_MINUTES;
|
||||
}
|
||||
|
||||
/**
|
||||
* Where a stage end may not sit: at the start or the end of the tour, which
|
||||
* begin and finish the first and the last stage anyway, and on a shaping
|
||||
* point, which is no stop to spend a night at. Beyond {@link MAX_TOUR_STAGES}
|
||||
* the tour is refused as a whole.
|
||||
*/
|
||||
export function tourStageIssues(points: readonly StagePoint[]): StageIssue[] {
|
||||
const issues: StageIssue[] = [];
|
||||
points.forEach((point, index) => {
|
||||
if (point.stage_end !== true) return;
|
||||
if (point.role !== 'via')
|
||||
issues.push({ path: ['waypoints', index, 'stage_end'], message: 'a stage ends at a stop between start and end' });
|
||||
else if (point.shaping === true)
|
||||
issues.push({ path: ['waypoints', index, 'stage_end'], message: 'a shaping point ends no stage' });
|
||||
});
|
||||
if (tourStageCountOf(points) > MAX_TOUR_STAGES)
|
||||
issues.push({ path: ['waypoints'], message: `a tour has at most ${MAX_TOUR_STAGES} stages` });
|
||||
return issues;
|
||||
}
|
||||
@@ -11,19 +11,21 @@ import { skiDisciplineSchema, skiTechniqueSchema } from './ski.schema';
|
||||
import { tourActivityRefSchema, tourActivitySchema } from './tour-activity.schema';
|
||||
import { tourPoiCategorySchema } from './tour-poi-category';
|
||||
import { tourOriginSchema, tourProvenanceSchema } from './tour-provenance.schema';
|
||||
import { maxTourMinutesFor, TOUR_DAY_MINUTES, tourStageCountOf, tourStageIssues } from './tour-stages';
|
||||
|
||||
import { z } from 'zod';
|
||||
|
||||
/**
|
||||
* Tours contracts — the generalisation of "hike" into a domain of self-contained,
|
||||
* single-day, non-motorized activities that follow a track (hike, bike, run, ski,
|
||||
* kayak, city walk, food tour, ...). Tour types use a controlled key list.
|
||||
* non-motorized activities that follow a track (hike, bike, run, ski, kayak,
|
||||
* city walk, food tour, ...), a day each or (MDT-01) one route split into
|
||||
* stages over several days. Tour types use a controlled key list.
|
||||
*
|
||||
* Isolated bounded context:
|
||||
* no columns on `places`/`days`/`users`. A tour is a Place (route_geometry reused
|
||||
* as-is) plus a day_assignments row (reused as-is, `assignPlaceToDay`) plus this
|
||||
* facet table keyed by place_id. Boundary vs. Road-Trip: Tours = non-motorized +
|
||||
* single-day, Road-Trip = motorized + multi-day.
|
||||
* facet table keyed by place_id. Boundary vs. Road-Trip: Tours = non-motorized,
|
||||
* Road-Trip = motorized.
|
||||
*/
|
||||
|
||||
export const tourTypeKeySchema = z.enum(['hike', 'bike', 'run', 'ski', 'kayak', 'walk', 'food', 'horse']);
|
||||
@@ -136,8 +138,8 @@ export function hikingPreferencesOf(value: HikingPreferences | null | undefined)
|
||||
return kept.length === 0 ? null : Object.fromEntries(kept.map((key) => [key, true]));
|
||||
}
|
||||
|
||||
/** Tours are single-day activities, so a planned total is bounded to 24 hours. */
|
||||
export const MAX_PLANNED_TOUR_DURATION_MINUTES = 24 * 60;
|
||||
/** A planned total is bounded to a day; a multi-day tour's moving time to a day a stage (MDT-01). */
|
||||
export const MAX_PLANNED_TOUR_DURATION_MINUTES = TOUR_DAY_MINUTES;
|
||||
export const plannedTourDurationMinutesSchema = z
|
||||
.number()
|
||||
.int()
|
||||
@@ -305,6 +307,11 @@ export const tourListItemSchema = tourSchema.extend({
|
||||
activity_tracks: z.array(tourActivityRefSchema).optional(),
|
||||
/** ACT-01: the tour this recording documents, when it is assigned to one; null otherwise. */
|
||||
activity_of: z.number().int().positive().nullable().optional(),
|
||||
/**
|
||||
* MDT-01: the stages a multi-day tour's stops split it into; null for a day
|
||||
* tour. Absent in a list cached before it existed.
|
||||
*/
|
||||
stage_count: z.number().int().min(2).nullable().optional(),
|
||||
});
|
||||
export type TourListItem = z.infer<typeof tourListItemSchema>;
|
||||
|
||||
@@ -448,6 +455,11 @@ export const tourWaypointSchema = z.object({
|
||||
* map drew the place. Null on a point set by hand.
|
||||
*/
|
||||
poi_category: tourPoiCategorySchema.nullable().optional(),
|
||||
/**
|
||||
* MDT-01: a stop where a stage of a multi-day tour ends and the next one
|
||||
* begins. Absent means the route goes on, as on every point saved before.
|
||||
*/
|
||||
stage_end: z.boolean().optional(),
|
||||
});
|
||||
export type TourWaypoint = z.infer<typeof tourWaypointSchema>;
|
||||
|
||||
@@ -511,15 +523,17 @@ export const tourCreateRequestSchema = z
|
||||
value.planned_duration_minutes == null &&
|
||||
value.duration_seconds != null &&
|
||||
value.break_additional_minutes != null &&
|
||||
Math.round(value.duration_seconds / 60) + value.break_additional_minutes > MAX_PLANNED_TOUR_DURATION_MINUTES
|
||||
Math.round(value.duration_seconds / 60) + value.break_additional_minutes >
|
||||
maxTourMinutesFor(tourStageCountOf(value.waypoints))
|
||||
) {
|
||||
ctx.addIssue({
|
||||
code: z.ZodIssueCode.custom,
|
||||
path: ['break_additional_minutes'],
|
||||
message: 'walking time plus breaks must be 1440 minutes or less unless a total is overridden',
|
||||
message: plannedTimeLimitMessage(tourStageCountOf(value.waypoints)),
|
||||
});
|
||||
}
|
||||
tourTypeIssues(value).forEach((issue) => ctx.addIssue({ code: z.ZodIssueCode.custom, ...issue }));
|
||||
tourStageIssues(value.waypoints).forEach((issue) => ctx.addIssue({ code: z.ZodIssueCode.custom, ...issue }));
|
||||
value.waypoints.forEach((point, index) => {
|
||||
const leg = point.fixed_leg;
|
||||
const next = value.waypoints[index + 1];
|
||||
@@ -572,6 +586,16 @@ const TYPE_NAMED: Record<PlannableTourType, string> = {
|
||||
horse: 'a horse tour',
|
||||
};
|
||||
|
||||
/**
|
||||
* What a save over its time limit is told: a day for a day tour, as it always
|
||||
* was, and a day a stage for a multi-day tour (MDT-01).
|
||||
*/
|
||||
export function plannedTimeLimitMessage(stageCount: number): string {
|
||||
return stageCount > 1
|
||||
? `walking time plus breaks must be ${maxTourMinutesFor(stageCount)} minutes or less, a day a stage, unless a total is overridden`
|
||||
: 'walking time plus breaks must be 1440 minutes or less unless a total is overridden';
|
||||
}
|
||||
|
||||
const PADDLE_FIELDS = [
|
||||
'boat_type',
|
||||
'water_context',
|
||||
|
||||
Reference in New Issue
Block a user