mirror of
https://github.com/mauriceboe/TREK.git
synced 2026-10-21 06:26:26 +00:00
feat(tours): show a multi-day tour's stages in the list and put each on a day
A multi-day tour in the planner's list of the trip's tours now unfolds into its stages (MDT-02), so the stages can be seen together and fitted into the trip without leaving the one route they are cut from. - The fold beside a multi-day tour lists its stages in route order, each from stop to stop with its distance, climb, descent and moving time, read off the saved tour the way the planner reads them, and below them what the whole tour adds up to. A value no stage tells is left out, a total some stages do not tell is marked as at least that much and says for how many, and a stage whose route is not planned yet says so. - Each stage has its own day: a pill opens the trip's days, moves the stage to another one or takes it off again. A stage without a day shows a dashed "No day" pill. A day holds a tour, or one of its stages, once, and a stage is on one day at most; the plan's own offline path writes it. - A stage opens in the planner: the tour opens as any tour does and the stage is shown once its points are in. - The day plan names the stage a day holds with its own figures, and the day's route runs to that stage's start and on from its end; the trip map draws that stretch only. The tour list still counts a tour once, however many days hold it or its stages. - A saved change that takes a stage away (its stage end gone, the tour a day tour again, back to the recorded track) takes it off its day in the same write and tells every client; the planner says so before the save. - REST and the assign_place_to_day tool take the stage by the key of the stop it starts at, with the same checks; list_tours names the stops between the stages and the stage each day holds. A day assignment remembers its stage by that key (migration 046010); every assignment made before holds the whole tour, as it did.
This commit is contained in:
@@ -28,6 +28,9 @@ describe('assignment Tours read-model merge', () => {
|
||||
incoming_leg_transport_mode: 'cycling',
|
||||
};
|
||||
expect(assignmentSchema.parse({ ...ordinary, ...fields })).toMatchObject(fields);
|
||||
// MDT-02: the stage of a multi-day tour the day holds, null for the whole tour.
|
||||
expect(assignmentSchema.parse({ ...ordinary, tour_stage_key: 'stop-1xx' }).tour_stage_key).toBe('stop-1xx');
|
||||
expect(assignmentSchema.parse({ ...ordinary, tour_stage_key: null }).tour_stage_key).toBeNull();
|
||||
expect(assignmentSchema.parse({ ...ordinary, tour_place_id: null, tour_route_geometry: null })).toMatchObject({
|
||||
tour_place_id: null,
|
||||
tour_route_geometry: null,
|
||||
@@ -55,6 +58,16 @@ describe('assignmentCreateRequestSchema', () => {
|
||||
expect(assignmentCreateRequestSchema.safeParse({ place_id: '2', notes: null }).success).toBe(true);
|
||||
expect(assignmentCreateRequestSchema.safeParse({}).success).toBe(false);
|
||||
});
|
||||
|
||||
it('MDT-SH-012: takes the stage of a multi-day tour by the key of the stop it starts at', () => {
|
||||
expect(assignmentCreateRequestSchema.parse({ place_id: 2, tour_stage_key: 'stop-1xx' })).toEqual({
|
||||
place_id: 2,
|
||||
tour_stage_key: 'stop-1xx',
|
||||
});
|
||||
for (const tour_stage_key of ['', 'short', 'not a key!', null, 3]) {
|
||||
expect(assignmentCreateRequestSchema.safeParse({ place_id: 2, tour_stage_key }).success).toBe(false);
|
||||
}
|
||||
});
|
||||
});
|
||||
|
||||
describe('assignmentMoveRequestSchema', () => {
|
||||
|
||||
@@ -1,4 +1,5 @@
|
||||
import { assignmentPlaceSchema } from '../place/place.schema';
|
||||
import { stationKeySchema } from '../tours/tours.schema';
|
||||
|
||||
import { z } from 'zod';
|
||||
|
||||
@@ -57,6 +58,11 @@ export const assignmentSchema = z.object({
|
||||
accommodation_id: z.number().nullable().optional(),
|
||||
tour_place_id: z.number().nullable().optional(),
|
||||
tour_route_geometry: z.string().nullable().optional(),
|
||||
/**
|
||||
* MDT-02: the stage of a multi-day tour this day holds, by the key of the
|
||||
* stop the stage starts at; null where the day holds the whole tour or a place.
|
||||
*/
|
||||
tour_stage_key: z.string().nullable().optional(),
|
||||
participants: z.array(assignmentParticipantSchema).optional(),
|
||||
created_at: z.string().optional(),
|
||||
place: assignmentPlaceSchema,
|
||||
@@ -66,6 +72,12 @@ export type Assignment = z.infer<typeof assignmentSchema>;
|
||||
export const assignmentCreateRequestSchema = z.object({
|
||||
place_id: z.union([z.number(), z.string()]),
|
||||
notes: z.string().nullable().optional(),
|
||||
/**
|
||||
* MDT-02: puts one stage of a multi-day tour on the day instead of the whole
|
||||
* tour, by the key of the stop the stage starts at (the tour list's
|
||||
* `stage_stops`). A stage is on one day at most.
|
||||
*/
|
||||
tour_stage_key: stationKeySchema.optional(),
|
||||
});
|
||||
export type AssignmentCreateRequest = z.infer<typeof assignmentCreateRequestSchema>;
|
||||
|
||||
|
||||
@@ -1326,5 +1326,22 @@ const tours: TranslationStrings = {
|
||||
'tours.stages.toDayBody.many': 'نهايات المراحل التي ستُزال: {count}. يبقى المسار كما هو، والتراجع يعيدها.',
|
||||
'tours.stages.toDayConfirm': 'إزالة نهايات المراحل',
|
||||
'tours.stages.show': 'إظهار مكان نهاية هذه المرحلة',
|
||||
'tours.stages.fold': 'إخفاء المراحل',
|
||||
'tours.stages.unfold': 'إظهار المراحل',
|
||||
'tours.stages.noDay': 'بلا يوم',
|
||||
'tours.stages.noDayHint': 'لم تُضف بعد إلى أي يوم من الرحلة',
|
||||
'tours.stages.dayFailed': 'تعذّر وضع المرحلة في ذلك اليوم.',
|
||||
'tours.stages.dayLost': 'مراحل لها يوم لم تعد موجودة: {count}. الحفظ يزيلها من أيامها.',
|
||||
'tours.stages.dayLost.zero': 'مراحل لها يوم لم تعد موجودة: {count}. الحفظ يزيلها من أيامها.',
|
||||
'tours.stages.dayLost.one': 'مرحلة لها يوم لم تعد موجودة: {count}. الحفظ يزيلها من يومها.',
|
||||
'tours.stages.dayLost.two': 'مرحلتان لهما يوم لم تعودا موجودتين: {count}. الحفظ يزيلهما من يوميهما.',
|
||||
'tours.stages.dayLost.few': 'مراحل لها يوم لم تعد موجودة: {count}. الحفظ يزيلها من أيامها.',
|
||||
'tours.stages.dayLost.many': 'مرحلة لها يوم لم تعد موجودة: {count}. الحفظ يزيلها من أيامها.',
|
||||
'tours.stages.unmeasured': 'لم تُقَس بعد',
|
||||
'tours.stages.unmeasuredHint': 'لم يُخطَّط مسار هذه المرحلة بعد.',
|
||||
'tours.stages.loadFailed': 'تعذّرت قراءة المراحل الآن.',
|
||||
'tours.stages.wholeTour': 'الجولة كاملة',
|
||||
'tours.stages.totalPartial': 'معروف لـ {known} من {of} مراحل',
|
||||
'tours.stages.openInPlanner': 'فتح هذه المرحلة في المخطط',
|
||||
};
|
||||
export default tours;
|
||||
|
||||
@@ -1282,5 +1282,18 @@ const tours: TranslationStrings = {
|
||||
'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',
|
||||
'tours.stages.fold': 'Mərhələləri gizlət',
|
||||
'tours.stages.unfold': 'Mərhələləri göstər',
|
||||
'tours.stages.noDay': 'Gün yoxdur',
|
||||
'tours.stages.noDayHint': 'Hələ səyahətin heç bir gününə salınmayıb',
|
||||
'tours.stages.dayFailed': 'Mərhələni həmin günə salmaq alınmadı.',
|
||||
'tours.stages.dayLost': 'Günə salınmış {count} mərhələ artıq yoxdur; saxlama onları günlərindən çıxarır.',
|
||||
'tours.stages.dayLost.one': 'Günə salınmış bir mərhələ artıq yoxdur; saxlama onu günündən çıxarır.',
|
||||
'tours.stages.unmeasured': 'Hələ ölçülməyib',
|
||||
'tours.stages.unmeasuredHint': 'Bu mərhələnin marşrutu hələ planlaşdırılmayıb.',
|
||||
'tours.stages.loadFailed': 'Mərhələləri indi oxumaq olmur.',
|
||||
'tours.stages.wholeTour': 'Bütün tur',
|
||||
'tours.stages.totalPartial': '{of} mərhələdən {known} üçün məlumdur',
|
||||
'tours.stages.openInPlanner': 'Bu mərhələni planlayıcıda aç',
|
||||
};
|
||||
export default tours;
|
||||
|
||||
@@ -1295,5 +1295,18 @@ const tours: TranslationStrings = {
|
||||
'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',
|
||||
'tours.stages.fold': 'Ocultar etapas',
|
||||
'tours.stages.unfold': 'Mostrar etapas',
|
||||
'tours.stages.noDay': 'Sem dia',
|
||||
'tours.stages.noDayHint': 'Ainda não está em nenhum dia da viagem',
|
||||
'tours.stages.dayFailed': 'Não foi possível colocar a etapa nesse dia.',
|
||||
'tours.stages.dayLost': '{count} etapas com dia não existem mais; ao salvar, elas perdem o dia.',
|
||||
'tours.stages.dayLost.one': '{count} etapa com dia não existe mais; ao salvar, ela perde o dia.',
|
||||
'tours.stages.unmeasured': 'Ainda não medida',
|
||||
'tours.stages.unmeasuredHint': 'A rota desta etapa ainda não foi planejada.',
|
||||
'tours.stages.loadFailed': 'Não é possível ler as etapas agora.',
|
||||
'tours.stages.wholeTour': 'Roteiro completo',
|
||||
'tours.stages.totalPartial': 'conhecido para {known} de {of} etapas',
|
||||
'tours.stages.openInPlanner': 'Abrir esta etapa no planejador',
|
||||
};
|
||||
export default tours;
|
||||
|
||||
@@ -1302,5 +1302,18 @@ const tours: TranslationStrings = {
|
||||
'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',
|
||||
'tours.stages.fold': 'Amaga les etapes',
|
||||
'tours.stages.unfold': 'Mostra les etapes',
|
||||
'tours.stages.noDay': 'Sense dia',
|
||||
'tours.stages.noDayHint': 'Encara no és en cap dia del viatge',
|
||||
'tours.stages.dayFailed': 'No s’ha pogut posar l’etapa en aquell dia.',
|
||||
'tours.stages.dayLost': '{count} etapes amb dia ja no existeixen; en desar perden el seu dia.',
|
||||
'tours.stages.dayLost.one': 'Una etapa amb dia ja no existeix; en desar perd el seu dia.',
|
||||
'tours.stages.unmeasured': 'Encara sense mesurar',
|
||||
'tours.stages.unmeasuredHint': 'La ruta d’aquesta etapa encara no està planificada.',
|
||||
'tours.stages.loadFailed': 'Ara no es poden llegir les etapes.',
|
||||
'tours.stages.wholeTour': 'Ruta sencera',
|
||||
'tours.stages.totalPartial': 'conegut per a {known} de {of} etapes',
|
||||
'tours.stages.openInPlanner': 'Obre aquesta etapa al planificador',
|
||||
};
|
||||
export default tours;
|
||||
|
||||
@@ -1283,5 +1283,19 @@ const tours: TranslationStrings = {
|
||||
'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čí',
|
||||
'tours.stages.fold': 'Skrýt etapy',
|
||||
'tours.stages.unfold': 'Zobrazit etapy',
|
||||
'tours.stages.noDay': 'Bez dne',
|
||||
'tours.stages.noDayHint': 'Zatím není v žádném dni cesty',
|
||||
'tours.stages.dayFailed': 'Etapu se nepodařilo zařadit do tohoto dne.',
|
||||
'tours.stages.dayLost': '{count} etap se dnem už neexistuje; uložením přijdou o svůj den.',
|
||||
'tours.stages.dayLost.one': 'Jedna etapa se dnem už neexistuje; uložením přijde o svůj den.',
|
||||
'tours.stages.dayLost.few': '{count} etapy se dnem už neexistují; uložením přijdou o svůj den.',
|
||||
'tours.stages.unmeasured': 'Zatím nezměřeno',
|
||||
'tours.stages.unmeasuredHint': 'Trasa této etapy ještě není naplánovaná.',
|
||||
'tours.stages.loadFailed': 'Etapy teď nelze načíst.',
|
||||
'tours.stages.wholeTour': 'Celá trasa',
|
||||
'tours.stages.totalPartial': 'známo pro {known} z {of} etap',
|
||||
'tours.stages.openInPlanner': 'Otevřít tuto etapu v plánovači',
|
||||
};
|
||||
export default tours;
|
||||
|
||||
@@ -1312,6 +1312,19 @@ const tours: TranslationStrings = {
|
||||
'{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',
|
||||
'tours.stages.fold': 'Etappen ausblenden',
|
||||
'tours.stages.unfold': 'Etappen zeigen',
|
||||
'tours.stages.noDay': 'Ohne Tag',
|
||||
'tours.stages.noDayHint': 'Noch keinem Reisetag zugeordnet',
|
||||
'tours.stages.dayFailed': 'Die Etappe konnte diesem Tag nicht zugeordnet werden.',
|
||||
'tours.stages.dayLost': '{count} Etappen mit Reisetag gibt es nicht mehr; beim Speichern verlieren sie ihren Tag.',
|
||||
'tours.stages.dayLost.one': 'Eine Etappe mit Reisetag gibt es nicht mehr; beim Speichern verliert sie ihren Tag.',
|
||||
'tours.stages.unmeasured': 'Noch nicht vermessen',
|
||||
'tours.stages.unmeasuredHint': 'Die Route dieser Etappe ist noch nicht geplant.',
|
||||
'tours.stages.loadFailed': 'Die Etappen lassen sich gerade nicht lesen.',
|
||||
'tours.stages.wholeTour': 'Ganze Tour',
|
||||
'tours.stages.totalPartial': 'bekannt für {known} von {of} Etappen',
|
||||
'tours.stages.openInPlanner': 'Diese Etappe im Planer öffnen',
|
||||
};
|
||||
|
||||
export default tours;
|
||||
|
||||
@@ -1269,5 +1269,18 @@ const tours: TranslationStrings = {
|
||||
'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',
|
||||
'tours.stages.fold': 'Hide stages',
|
||||
'tours.stages.unfold': 'Show stages',
|
||||
'tours.stages.noDay': 'No day',
|
||||
'tours.stages.noDayHint': 'Not on a day of the trip yet',
|
||||
'tours.stages.dayFailed': 'The stage could not be put on that day.',
|
||||
'tours.stages.dayLost': '{count} stages on a day no longer exist; saving takes them off their days.',
|
||||
'tours.stages.dayLost.one': 'A stage on a day no longer exists; saving takes it off its day.',
|
||||
'tours.stages.unmeasured': 'Not measured yet',
|
||||
'tours.stages.unmeasuredHint': 'The route of this stage is not planned yet.',
|
||||
'tours.stages.loadFailed': 'The stages cannot be read right now.',
|
||||
'tours.stages.wholeTour': 'Whole tour',
|
||||
'tours.stages.totalPartial': 'known for {known} of {of} stages',
|
||||
'tours.stages.openInPlanner': 'Open this stage in the planner',
|
||||
};
|
||||
export default tours;
|
||||
|
||||
@@ -1294,5 +1294,18 @@ const tours: TranslationStrings = {
|
||||
'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',
|
||||
'tours.stages.fold': 'Ocultar etapas',
|
||||
'tours.stages.unfold': 'Mostrar etapas',
|
||||
'tours.stages.noDay': 'Sin día',
|
||||
'tours.stages.noDayHint': 'Aún no está en ningún día del viaje',
|
||||
'tours.stages.dayFailed': 'No se pudo poner la etapa en ese día.',
|
||||
'tours.stages.dayLost': '{count} etapas con día ya no existen; al guardar pierden su día.',
|
||||
'tours.stages.dayLost.one': 'Una etapa con día ya no existe; al guardar pierde su día.',
|
||||
'tours.stages.unmeasured': 'Aún sin medir',
|
||||
'tours.stages.unmeasuredHint': 'La ruta de esta etapa aún no está planificada.',
|
||||
'tours.stages.loadFailed': 'Ahora no se pueden leer las etapas.',
|
||||
'tours.stages.wholeTour': 'Ruta completa',
|
||||
'tours.stages.totalPartial': 'conocido para {known} de {of} etapas',
|
||||
'tours.stages.openInPlanner': 'Abrir esta etapa en el planificador',
|
||||
};
|
||||
export default tours;
|
||||
|
||||
@@ -1278,5 +1278,18 @@ const tours: TranslationStrings = {
|
||||
'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',
|
||||
'tours.stages.fold': 'Peida etapid',
|
||||
'tours.stages.unfold': 'Näita etappe',
|
||||
'tours.stages.noDay': 'Päevata',
|
||||
'tours.stages.noDayHint': 'Pole veel ühelgi reisipäeval',
|
||||
'tours.stages.dayFailed': 'Etappi ei õnnestunud sellele päevale panna.',
|
||||
'tours.stages.dayLost': '{count} päevaga etappi pole enam olemas; salvestamisel kaotavad need oma päeva.',
|
||||
'tours.stages.dayLost.one': 'Üks päevaga etapp pole enam olemas; salvestamisel kaotab see oma päeva.',
|
||||
'tours.stages.unmeasured': 'Veel mõõtmata',
|
||||
'tours.stages.unmeasuredHint': 'Selle etapi marsruut pole veel planeeritud.',
|
||||
'tours.stages.loadFailed': 'Etappe ei saa praegu lugeda.',
|
||||
'tours.stages.wholeTour': 'Kogu matk',
|
||||
'tours.stages.totalPartial': 'teada {known} etapi kohta {of}-st',
|
||||
'tours.stages.openInPlanner': 'Ava see etapp planeerijas',
|
||||
};
|
||||
export default tours;
|
||||
|
||||
@@ -1319,5 +1319,20 @@ const tours: TranslationStrings = {
|
||||
'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',
|
||||
'tours.stages.fold': 'Masquer les étapes',
|
||||
'tours.stages.unfold': 'Afficher les étapes',
|
||||
'tours.stages.noDay': 'Sans jour',
|
||||
'tours.stages.noDayHint': 'Pas encore sur un jour du voyage',
|
||||
'tours.stages.dayFailed': 'L’étape n’a pas pu être placée sur ce jour.',
|
||||
'tours.stages.dayLost':
|
||||
'{count} étapes placées sur un jour n’existent plus ; l’enregistrement les retire de leur jour.',
|
||||
'tours.stages.dayLost.one':
|
||||
'{count} étape placée sur un jour n’existe plus ; l’enregistrement la retire de son jour.',
|
||||
'tours.stages.unmeasured': 'Pas encore mesurée',
|
||||
'tours.stages.unmeasuredHint': 'L’itinéraire de cette étape n’est pas encore planifié.',
|
||||
'tours.stages.loadFailed': 'Les étapes ne peuvent pas être lues pour le moment.',
|
||||
'tours.stages.wholeTour': 'Itinéraire complet',
|
||||
'tours.stages.totalPartial': 'connu pour {known} étapes sur {of}',
|
||||
'tours.stages.openInPlanner': 'Ouvrir cette étape dans le planificateur',
|
||||
};
|
||||
export default tours;
|
||||
|
||||
@@ -1322,5 +1322,18 @@ const tours: TranslationStrings = {
|
||||
'Αφαιρείται {count} τέλος σκέλους. Η διαδρομή μένει ως έχει και η Αναίρεση το επαναφέρει.',
|
||||
'tours.stages.toDayConfirm': 'Αφαίρεση τελών σκελών',
|
||||
'tours.stages.show': 'Εμφάνιση του σημείου όπου τελειώνει το σκέλος',
|
||||
'tours.stages.fold': 'Απόκρυψη σκελών',
|
||||
'tours.stages.unfold': 'Εμφάνιση σκελών',
|
||||
'tours.stages.noDay': 'Χωρίς ημέρα',
|
||||
'tours.stages.noDayHint': 'Δεν έχει ακόμη ημέρα του ταξιδιού',
|
||||
'tours.stages.dayFailed': 'Το σκέλος δεν μπόρεσε να μπει σε αυτή την ημέρα.',
|
||||
'tours.stages.dayLost': '{count} σκέλη με ημέρα δεν υπάρχουν πια· με την αποθήκευση χάνουν την ημέρα τους.',
|
||||
'tours.stages.dayLost.one': 'Ένα σκέλος με ημέρα δεν υπάρχει πια· με την αποθήκευση χάνει την ημέρα του.',
|
||||
'tours.stages.unmeasured': 'Δεν έχει μετρηθεί ακόμη',
|
||||
'tours.stages.unmeasuredHint': 'Η διαδρομή αυτού του σκέλους δεν έχει σχεδιαστεί ακόμη.',
|
||||
'tours.stages.loadFailed': 'Τα σκέλη δεν μπορούν να διαβαστούν αυτή τη στιγμή.',
|
||||
'tours.stages.wholeTour': 'Όλη η διαδρομή',
|
||||
'tours.stages.totalPartial': 'γνωστό για {known} από {of} σκέλη',
|
||||
'tours.stages.openInPlanner': 'Άνοιγμα αυτού του σκέλους στον σχεδιαστή',
|
||||
};
|
||||
export default tours;
|
||||
|
||||
@@ -1300,5 +1300,18 @@ const tours: TranslationStrings = {
|
||||
'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',
|
||||
'tours.stages.fold': 'Szakaszok elrejtése',
|
||||
'tours.stages.unfold': 'Szakaszok mutatása',
|
||||
'tours.stages.noDay': 'Nincs nap',
|
||||
'tours.stages.noDayHint': 'Még nincs az utazás egyik napján sem',
|
||||
'tours.stages.dayFailed': 'A szakaszt nem sikerült erre a napra tenni.',
|
||||
'tours.stages.dayLost': '{count} nappal rendelkező szakasz már nem létezik; mentéskor elvesztik a napjukat.',
|
||||
'tours.stages.dayLost.one': 'Egy nappal rendelkező szakasz már nem létezik; mentéskor elveszti a napját.',
|
||||
'tours.stages.unmeasured': 'Még nincs lemérve',
|
||||
'tours.stages.unmeasuredHint': 'A szakasz útvonala még nincs megtervezve.',
|
||||
'tours.stages.loadFailed': 'A szakaszok most nem olvashatók.',
|
||||
'tours.stages.wholeTour': 'Teljes túra',
|
||||
'tours.stages.totalPartial': 'ismert {known} szakasznál a {of} közül',
|
||||
'tours.stages.openInPlanner': 'A szakasz megnyitása a tervezőben',
|
||||
};
|
||||
export default tours;
|
||||
|
||||
@@ -1277,5 +1277,18 @@ const tours: TranslationStrings = {
|
||||
'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',
|
||||
'tours.stages.fold': 'Sembunyikan etape',
|
||||
'tours.stages.unfold': 'Tampilkan etape',
|
||||
'tours.stages.noDay': 'Tanpa hari',
|
||||
'tours.stages.noDayHint': 'Belum masuk hari perjalanan mana pun',
|
||||
'tours.stages.dayFailed': 'Etape tidak dapat dimasukkan ke hari itu.',
|
||||
'tours.stages.dayLost':
|
||||
'{count} etape yang punya hari sudah tidak ada; saat disimpan, etape itu dilepas dari harinya.',
|
||||
'tours.stages.unmeasured': 'Belum diukur',
|
||||
'tours.stages.unmeasuredHint': 'Rute etape ini belum direncanakan.',
|
||||
'tours.stages.loadFailed': 'Etape tidak dapat dibaca saat ini.',
|
||||
'tours.stages.wholeTour': 'Seluruh tur',
|
||||
'tours.stages.totalPartial': 'diketahui untuk {known} dari {of} etape',
|
||||
'tours.stages.openInPlanner': 'Buka etape ini di perencana',
|
||||
};
|
||||
export default tours;
|
||||
|
||||
@@ -1308,5 +1308,18 @@ const tours: TranslationStrings = {
|
||||
'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',
|
||||
'tours.stages.fold': 'Nascondi tappe',
|
||||
'tours.stages.unfold': 'Mostra tappe',
|
||||
'tours.stages.noDay': 'Senza giorno',
|
||||
'tours.stages.noDayHint': 'Non ancora in un giorno del viaggio',
|
||||
'tours.stages.dayFailed': 'Non è stato possibile mettere la tappa in quel giorno.',
|
||||
'tours.stages.dayLost': '{count} tappe assegnate a un giorno non esistono più; salvando perdono il loro giorno.',
|
||||
'tours.stages.dayLost.one': 'Una tappa assegnata a un giorno non esiste più; salvando perde il suo giorno.',
|
||||
'tours.stages.unmeasured': 'Non ancora misurata',
|
||||
'tours.stages.unmeasuredHint': 'Il percorso di questa tappa non è ancora pianificato.',
|
||||
'tours.stages.loadFailed': 'Al momento non è possibile leggere le tappe.',
|
||||
'tours.stages.wholeTour': 'Tour completo',
|
||||
'tours.stages.totalPartial': 'noto per {known} tappe su {of}',
|
||||
'tours.stages.openInPlanner': 'Apri questa tappa nel pianificatore',
|
||||
};
|
||||
export default tours;
|
||||
|
||||
@@ -1240,5 +1240,17 @@ const tours: TranslationStrings = {
|
||||
'tours.stages.toDayBody': '{count} 件のステージ終点を外します。ルートはそのままで、元に戻すで復元できます。',
|
||||
'tours.stages.toDayConfirm': 'ステージの終点をすべて外す',
|
||||
'tours.stages.show': 'このステージの終点を表示',
|
||||
'tours.stages.fold': 'ステージを隠す',
|
||||
'tours.stages.unfold': 'ステージを表示',
|
||||
'tours.stages.noDay': '日なし',
|
||||
'tours.stages.noDayHint': 'まだ旅行のどの日にも入っていません',
|
||||
'tours.stages.dayFailed': 'このステージをその日に入れられませんでした。',
|
||||
'tours.stages.dayLost': '日に入っている {count} 件のステージはもう存在しません。保存するとその日から外れます。',
|
||||
'tours.stages.unmeasured': 'まだ計測されていません',
|
||||
'tours.stages.unmeasuredHint': 'このステージのルートはまだ計画されていません。',
|
||||
'tours.stages.loadFailed': '現在ステージを読み込めません。',
|
||||
'tours.stages.wholeTour': 'ツアー全体',
|
||||
'tours.stages.totalPartial': '{of} 件中 {known} 件のステージで判明',
|
||||
'tours.stages.openInPlanner': 'このステージをプランナーで開く',
|
||||
};
|
||||
export default tours;
|
||||
|
||||
@@ -1238,5 +1238,17 @@ const tours: TranslationStrings = {
|
||||
'tours.stages.toDayBody': '구간 끝 {count}개를 제거합니다. 경로는 그대로이며 실행 취소로 되돌릴 수 있습니다.',
|
||||
'tours.stages.toDayConfirm': '구간 끝 모두 제거',
|
||||
'tours.stages.show': '이 구간이 끝나는 곳 보기',
|
||||
'tours.stages.fold': '구간 숨기기',
|
||||
'tours.stages.unfold': '구간 보기',
|
||||
'tours.stages.noDay': '날짜 없음',
|
||||
'tours.stages.noDayHint': '아직 여행의 어느 날에도 넣지 않음',
|
||||
'tours.stages.dayFailed': '이 구간을 그날에 넣지 못했습니다.',
|
||||
'tours.stages.dayLost': '날짜가 있는 구간 {count}개가 더 이상 없습니다. 저장하면 해당 날짜에서 빠집니다.',
|
||||
'tours.stages.unmeasured': '아직 측정되지 않음',
|
||||
'tours.stages.unmeasuredHint': '이 구간의 경로는 아직 계획되지 않았습니다.',
|
||||
'tours.stages.loadFailed': '지금은 구간을 읽을 수 없습니다.',
|
||||
'tours.stages.wholeTour': '전체 투어',
|
||||
'tours.stages.totalPartial': '{of}개 구간 중 {known}개에서 알려짐',
|
||||
'tours.stages.openInPlanner': '이 구간을 플래너에서 열기',
|
||||
};
|
||||
export default tours;
|
||||
|
||||
@@ -1304,5 +1304,18 @@ const tours: TranslationStrings = {
|
||||
'{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',
|
||||
'tours.stages.fold': 'Etappes verbergen',
|
||||
'tours.stages.unfold': 'Etappes tonen',
|
||||
'tours.stages.noDay': 'Geen dag',
|
||||
'tours.stages.noDayHint': 'Nog niet op een reisdag',
|
||||
'tours.stages.dayFailed': 'De etappe kon niet op die dag worden gezet.',
|
||||
'tours.stages.dayLost': '{count} etappes met een dag bestaan niet meer; bij opslaan verliezen ze hun dag.',
|
||||
'tours.stages.dayLost.one': 'Een etappe met een dag bestaat niet meer; bij opslaan verliest ze haar dag.',
|
||||
'tours.stages.unmeasured': 'Nog niet gemeten',
|
||||
'tours.stages.unmeasuredHint': 'De route van deze etappe is nog niet gepland.',
|
||||
'tours.stages.loadFailed': 'De etappes kunnen nu niet worden gelezen.',
|
||||
'tours.stages.wholeTour': 'Hele tocht',
|
||||
'tours.stages.totalPartial': 'bekend voor {known} van {of} etappes',
|
||||
'tours.stages.openInPlanner': 'Deze etappe openen in de planner',
|
||||
};
|
||||
export default tours;
|
||||
|
||||
@@ -1328,5 +1328,20 @@ const tours: TranslationStrings = {
|
||||
'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',
|
||||
'tours.stages.fold': 'Ukryj etapy',
|
||||
'tours.stages.unfold': 'Pokaż etapy',
|
||||
'tours.stages.noDay': 'Bez dnia',
|
||||
'tours.stages.noDayHint': 'Jeszcze nie przypisano do dnia podróży',
|
||||
'tours.stages.dayFailed': 'Nie udało się przypisać etapu do tego dnia.',
|
||||
'tours.stages.dayLost': '{count} etapu z przypisanym dniem już nie istnieje; zapis zdejmie je z ich dni.',
|
||||
'tours.stages.dayLost.one': 'Jednego etapu z przypisanym dniem już nie ma; zapis zdejmie go z jego dnia.',
|
||||
'tours.stages.dayLost.few': '{count} etapy z przypisanym dniem już nie istnieją; zapis zdejmie je z ich dni.',
|
||||
'tours.stages.dayLost.many': '{count} etapów z przypisanym dniem już nie istnieje; zapis zdejmie je z ich dni.',
|
||||
'tours.stages.unmeasured': 'Jeszcze niezmierzony',
|
||||
'tours.stages.unmeasuredHint': 'Trasa tego etapu nie jest jeszcze zaplanowana.',
|
||||
'tours.stages.loadFailed': 'Nie można teraz odczytać etapów.',
|
||||
'tours.stages.wholeTour': 'Cała trasa',
|
||||
'tours.stages.totalPartial': 'znane dla {known} z {of} etapów',
|
||||
'tours.stages.openInPlanner': 'Otwórz ten etap w planerze',
|
||||
};
|
||||
export default tours;
|
||||
|
||||
@@ -1335,5 +1335,20 @@ const tours: TranslationStrings = {
|
||||
'tours.stages.toDayBody.many': 'Будут убраны {count} концов этапов. Маршрут останется прежним, а Отменить вернёт их.',
|
||||
'tours.stages.toDayConfirm': 'Убрать концы этапов',
|
||||
'tours.stages.show': 'Показать, где заканчивается этап',
|
||||
'tours.stages.fold': 'Скрыть этапы',
|
||||
'tours.stages.unfold': 'Показать этапы',
|
||||
'tours.stages.noDay': 'Без дня',
|
||||
'tours.stages.noDayHint': 'Пока не назначен на день поездки',
|
||||
'tours.stages.dayFailed': 'Не удалось назначить этап на этот день.',
|
||||
'tours.stages.dayLost': '{count} этапа с днём больше нет; при сохранении они лишатся своих дней.',
|
||||
'tours.stages.dayLost.one': '{count} этап с днём больше не существует; при сохранении он лишится своего дня.',
|
||||
'tours.stages.dayLost.few': '{count} этапа с днём больше не существуют; при сохранении они лишатся своих дней.',
|
||||
'tours.stages.dayLost.many': '{count} этапов с днём больше не существуют; при сохранении они лишатся своих дней.',
|
||||
'tours.stages.unmeasured': 'Ещё не измерен',
|
||||
'tours.stages.unmeasuredHint': 'Маршрут этого этапа ещё не спланирован.',
|
||||
'tours.stages.loadFailed': 'Этапы сейчас не удаётся прочитать.',
|
||||
'tours.stages.wholeTour': 'Весь маршрут',
|
||||
'tours.stages.totalPartial': 'известно для {known} из {of} этапов',
|
||||
'tours.stages.openInPlanner': 'Открыть этот этап в планировщике',
|
||||
};
|
||||
export default tours;
|
||||
|
||||
@@ -1287,5 +1287,19 @@ const tours: TranslationStrings = {
|
||||
'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čí',
|
||||
'tours.stages.fold': 'Skryť etapy',
|
||||
'tours.stages.unfold': 'Zobraziť etapy',
|
||||
'tours.stages.noDay': 'Bez dňa',
|
||||
'tours.stages.noDayHint': 'Zatiaľ nie je v žiadnom dni cesty',
|
||||
'tours.stages.dayFailed': 'Etapu sa nepodarilo zaradiť do tohto dňa.',
|
||||
'tours.stages.dayLost': '{count} etáp s dňom už neexistuje; uložením prídu o svoj deň.',
|
||||
'tours.stages.dayLost.one': 'Jedna etapa s dňom už neexistuje; uložením príde o svoj deň.',
|
||||
'tours.stages.dayLost.few': '{count} etapy s dňom už neexistujú; uložením prídu o svoj deň.',
|
||||
'tours.stages.unmeasured': 'Zatiaľ nezmerané',
|
||||
'tours.stages.unmeasuredHint': 'Trasa tejto etapy ešte nie je naplánovaná.',
|
||||
'tours.stages.loadFailed': 'Etapy sa teraz nedajú načítať.',
|
||||
'tours.stages.wholeTour': 'Celá trasa',
|
||||
'tours.stages.totalPartial': 'známe pre {known} z {of} etáp',
|
||||
'tours.stages.openInPlanner': 'Otvoriť túto etapu v plánovači',
|
||||
};
|
||||
export default tours;
|
||||
|
||||
@@ -1273,5 +1273,18 @@ const tours: TranslationStrings = {
|
||||
'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',
|
||||
'tours.stages.fold': 'Dölj etapper',
|
||||
'tours.stages.unfold': 'Visa etapper',
|
||||
'tours.stages.noDay': 'Ingen dag',
|
||||
'tours.stages.noDayHint': 'Ännu inte på en dag i resan',
|
||||
'tours.stages.dayFailed': 'Etappen kunde inte läggas på den dagen.',
|
||||
'tours.stages.dayLost': '{count} etapper med en dag finns inte längre; när du sparar tas de bort från sina dagar.',
|
||||
'tours.stages.dayLost.one': 'En etapp med en dag finns inte längre; när du sparar tas den bort från sin dag.',
|
||||
'tours.stages.unmeasured': 'Inte uppmätt än',
|
||||
'tours.stages.unmeasuredHint': 'Rutten för den här etappen är inte planerad än.',
|
||||
'tours.stages.loadFailed': 'Etapperna kan inte läsas just nu.',
|
||||
'tours.stages.wholeTour': 'Hela turen',
|
||||
'tours.stages.totalPartial': 'känt för {known} av {of} etapper',
|
||||
'tours.stages.openInPlanner': 'Öppna etappen i planeraren',
|
||||
};
|
||||
export default tours;
|
||||
|
||||
@@ -1222,5 +1222,17 @@ const tours: TranslationStrings = {
|
||||
'tours.stages.toDayBody': 'จะนำจุดจบช่วง {count} จุดออก เส้นทางยังคงเหมือนเดิม และเลิกทำจะนำกลับมาได้',
|
||||
'tours.stages.toDayConfirm': 'นำจุดจบช่วงทั้งหมดออก',
|
||||
'tours.stages.show': 'แสดงตำแหน่งที่ช่วงนี้จบ',
|
||||
'tours.stages.fold': 'ซ่อนช่วง',
|
||||
'tours.stages.unfold': 'แสดงช่วง',
|
||||
'tours.stages.noDay': 'ยังไม่มีวัน',
|
||||
'tours.stages.noDayHint': 'ยังไม่ได้อยู่ในวันใดของทริป',
|
||||
'tours.stages.dayFailed': 'ไม่สามารถใส่ช่วงนี้ลงในวันนั้นได้',
|
||||
'tours.stages.dayLost': 'ช่วงที่มีวันแล้ว {count} ช่วงไม่มีอยู่อีกต่อไป เมื่อบันทึกจะถูกนำออกจากวันของมัน',
|
||||
'tours.stages.unmeasured': 'ยังไม่ได้วัด',
|
||||
'tours.stages.unmeasuredHint': 'ยังไม่ได้วางแผนเส้นทางของช่วงนี้',
|
||||
'tours.stages.loadFailed': 'ไม่สามารถอ่านช่วงได้ในขณะนี้',
|
||||
'tours.stages.wholeTour': 'ทั้งเส้นทาง',
|
||||
'tours.stages.totalPartial': 'ทราบสำหรับ {known} จาก {of} ช่วง',
|
||||
'tours.stages.openInPlanner': 'เปิดช่วงนี้ในตัววางแผน',
|
||||
};
|
||||
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 = 1161;
|
||||
const GENERAL_KEY_COUNT = 1173;
|
||||
|
||||
function literalKeys(source: string): string[] {
|
||||
const tree = ts.createSourceFile('locale.ts', source, ts.ScriptTarget.Latest, true);
|
||||
|
||||
@@ -1280,5 +1280,18 @@ const tours: TranslationStrings = {
|
||||
'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',
|
||||
'tours.stages.fold': 'Etapları gizle',
|
||||
'tours.stages.unfold': 'Etapları göster',
|
||||
'tours.stages.noDay': 'Gün yok',
|
||||
'tours.stages.noDayHint': 'Henüz yolculuğun bir gününde değil',
|
||||
'tours.stages.dayFailed': 'Etap o güne konamadı.',
|
||||
'tours.stages.dayLost': 'Bir güne atanmış {count} etap artık yok; kaydetmek onları günlerinden çıkarır.',
|
||||
'tours.stages.dayLost.one': 'Bir güne atanmış bir etap artık yok; kaydetmek onu gününden çıkarır.',
|
||||
'tours.stages.unmeasured': 'Henüz ölçülmedi',
|
||||
'tours.stages.unmeasuredHint': 'Bu etabın rotası henüz planlanmadı.',
|
||||
'tours.stages.loadFailed': 'Etaplar şu anda okunamıyor.',
|
||||
'tours.stages.wholeTour': 'Tüm tur',
|
||||
'tours.stages.totalPartial': '{of} etabın {known} tanesi için biliniyor',
|
||||
'tours.stages.openInPlanner': 'Bu etabı planlayıcıda aç',
|
||||
};
|
||||
export default tours;
|
||||
|
||||
@@ -1321,5 +1321,20 @@ const tours: TranslationStrings = {
|
||||
'tours.stages.toDayBody.many': 'Буде прибрано {count} кінців етапів. Маршрут лишиться як є, а Скасувати поверне їх.',
|
||||
'tours.stages.toDayConfirm': 'Прибрати кінці етапів',
|
||||
'tours.stages.show': 'Показати, де закінчується етап',
|
||||
'tours.stages.fold': 'Сховати етапи',
|
||||
'tours.stages.unfold': 'Показати етапи',
|
||||
'tours.stages.noDay': 'Без дня',
|
||||
'tours.stages.noDayHint': 'Ще не призначено на день подорожі',
|
||||
'tours.stages.dayFailed': 'Не вдалося призначити етап на цей день.',
|
||||
'tours.stages.dayLost': '{count} етапу з днем більше немає; під час збереження вони втратять свої дні.',
|
||||
'tours.stages.dayLost.one': '{count} етап із днем більше не існує; під час збереження він втратить свій день.',
|
||||
'tours.stages.dayLost.few': '{count} етапи з днем більше не існують; під час збереження вони втратять свої дні.',
|
||||
'tours.stages.dayLost.many': '{count} етапів з днем більше не існує; під час збереження вони втратять свої дні.',
|
||||
'tours.stages.unmeasured': 'Ще не виміряно',
|
||||
'tours.stages.unmeasuredHint': 'Маршрут цього етапу ще не сплановано.',
|
||||
'tours.stages.loadFailed': 'Етапи зараз не вдається прочитати.',
|
||||
'tours.stages.wholeTour': 'Увесь маршрут',
|
||||
'tours.stages.totalPartial': 'відомо для {known} з {of} етапів',
|
||||
'tours.stages.openInPlanner': 'Відкрити цей етап у планувальнику',
|
||||
};
|
||||
export default tours;
|
||||
|
||||
@@ -1266,5 +1266,17 @@ const tours: TranslationStrings = {
|
||||
'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',
|
||||
'tours.stages.fold': 'Ẩn các chặng',
|
||||
'tours.stages.unfold': 'Hiện các chặng',
|
||||
'tours.stages.noDay': 'Chưa có ngày',
|
||||
'tours.stages.noDayHint': 'Chưa được xếp vào ngày nào của chuyến đi',
|
||||
'tours.stages.dayFailed': 'Không thể xếp chặng vào ngày đó.',
|
||||
'tours.stages.dayLost': '{count} chặng đã có ngày nay không còn; khi lưu, các chặng đó sẽ bị gỡ khỏi ngày.',
|
||||
'tours.stages.unmeasured': 'Chưa đo',
|
||||
'tours.stages.unmeasuredHint': 'Lộ trình của chặng này chưa được lên kế hoạch.',
|
||||
'tours.stages.loadFailed': 'Hiện không thể đọc các chặng.',
|
||||
'tours.stages.wholeTour': 'Cả hành trình',
|
||||
'tours.stages.totalPartial': 'đã biết cho {known} trên {of} chặng',
|
||||
'tours.stages.openInPlanner': 'Mở chặng này trong trình lập kế hoạch',
|
||||
};
|
||||
export default tours;
|
||||
|
||||
@@ -1181,5 +1181,17 @@ const tours: TranslationStrings = {
|
||||
'tours.stages.toDayBody': '將移除 {count} 個階段終點。路線保持不變,復原可以還原。',
|
||||
'tours.stages.toDayConfirm': '移除全部階段終點',
|
||||
'tours.stages.show': '顯示此階段的終點',
|
||||
'tours.stages.fold': '隱藏階段',
|
||||
'tours.stages.unfold': '顯示階段',
|
||||
'tours.stages.noDay': '未定日期',
|
||||
'tours.stages.noDayHint': '尚未安排到行程中的某一天',
|
||||
'tours.stages.dayFailed': '無法將此階段安排到那一天。',
|
||||
'tours.stages.dayLost': '{count} 個已安排日期的階段已不存在;儲存後將從其日期中移除。',
|
||||
'tours.stages.unmeasured': '尚未測量',
|
||||
'tours.stages.unmeasuredHint': '此階段的路線尚未規劃。',
|
||||
'tours.stages.loadFailed': '暫時無法讀取階段。',
|
||||
'tours.stages.wholeTour': '整條路線',
|
||||
'tours.stages.totalPartial': '{of} 個階段中 {known} 個已知',
|
||||
'tours.stages.openInPlanner': '在規劃器中開啟此階段',
|
||||
};
|
||||
export default tours;
|
||||
|
||||
@@ -1182,5 +1182,17 @@ const tours: TranslationStrings = {
|
||||
'tours.stages.toDayBody': '将移除 {count} 个阶段终点。路线保持不变,撤销可以恢复。',
|
||||
'tours.stages.toDayConfirm': '移除全部阶段终点',
|
||||
'tours.stages.show': '显示此阶段的终点',
|
||||
'tours.stages.fold': '隐藏阶段',
|
||||
'tours.stages.unfold': '显示阶段',
|
||||
'tours.stages.noDay': '未定日期',
|
||||
'tours.stages.noDayHint': '尚未安排到行程中的某一天',
|
||||
'tours.stages.dayFailed': '无法将该阶段安排到那一天。',
|
||||
'tours.stages.dayLost': '{count} 个已安排日期的阶段已不存在;保存后将从其日期中移除。',
|
||||
'tours.stages.unmeasured': '尚未测量',
|
||||
'tours.stages.unmeasuredHint': '该阶段的路线尚未规划。',
|
||||
'tours.stages.loadFailed': '暂时无法读取阶段。',
|
||||
'tours.stages.wholeTour': '整条路线',
|
||||
'tours.stages.totalPartial': '{of} 个阶段中 {known} 个已知',
|
||||
'tours.stages.openInPlanner': '在规划器中打开此阶段',
|
||||
};
|
||||
export default tours;
|
||||
|
||||
@@ -1,4 +1,12 @@
|
||||
import { MAX_TOUR_STAGES, maxTourMinutesFor, TOUR_DAY_MINUTES, tourStageCountOf, tourStageIssues } from './tour-stages';
|
||||
import {
|
||||
MAX_TOUR_STAGES,
|
||||
maxTourMinutesFor,
|
||||
TOUR_DAY_MINUTES,
|
||||
tourStageCountOf,
|
||||
tourStageIssues,
|
||||
tourStageKeysOf,
|
||||
tourStageStopsOf,
|
||||
} from './tour-stages';
|
||||
import {
|
||||
MAX_PLANNED_TOUR_DURATION_MINUTES,
|
||||
plannedTimeLimitMessage,
|
||||
@@ -132,4 +140,51 @@ describe('MDT-01 tour stages', () => {
|
||||
expect(tourListItemSchema.parse(item).stage_count).toBeUndefined();
|
||||
expect(tourListItemSchema.safeParse({ ...item, stage_count: 1 }).success).toBe(false);
|
||||
});
|
||||
|
||||
it('MDT-SH-009: reads the stops between the stages, keyed, the start through the end', () => {
|
||||
const keyed = points(5, (i) => ({ key: `stop-${i}xx`, ...(i === 2 ? { stage_end: true } : {}) }));
|
||||
expect(tourStageStopsOf(keyed)).toEqual([
|
||||
{ key: 'stop-0xx', lat: 47, lng: 11 },
|
||||
{ key: 'stop-2xx', lat: 47.02, lng: 11 },
|
||||
{ key: 'stop-4xx', lat: 47.04, lng: 11 },
|
||||
]);
|
||||
// A stage is known by the stop it starts at: the end starts none.
|
||||
expect(tourStageKeysOf(keyed)).toEqual(['stop-0xx', 'stop-2xx']);
|
||||
});
|
||||
|
||||
it('MDT-SH-010: has no stage stops for a day tour, a lone point or a stop no save has keyed', () => {
|
||||
expect(tourStageStopsOf(points(3, (i) => ({ key: `stop-${i}xx` })))).toEqual([]);
|
||||
expect(tourStageStopsOf([{ lat: 47, lng: 11, role: 'start', key: 'stop-0xx' }])).toEqual([]);
|
||||
const unkeyed = points(3, (i) => (i === 1 ? { stage_end: true, key: 'stop-1xx' } : {}));
|
||||
expect(tourStageStopsOf(unkeyed)).toEqual([]);
|
||||
expect(tourStageKeysOf(unkeyed)).toEqual([]);
|
||||
});
|
||||
|
||||
it('MDT-SH-011: lists where the stages of a multi-day tour start and end, 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,
|
||||
};
|
||||
const stops = [
|
||||
{ key: 'stop-0xx', lat: 47, lng: 11 },
|
||||
{ key: 'stop-1xx', lat: 47.1, lng: 11 },
|
||||
{ key: 'stop-2xx', lat: 47.2, lng: 11 },
|
||||
];
|
||||
expect(tourListItemSchema.parse({ ...item, stage_stops: stops }).stage_stops).toEqual(stops);
|
||||
expect(tourListItemSchema.parse({ ...item, stage_stops: null }).stage_stops).toBeNull();
|
||||
expect(tourListItemSchema.parse(item).stage_stops).toBeUndefined();
|
||||
expect(tourListItemSchema.safeParse({ ...item, stage_stops: [stops[0]] }).success).toBe(false);
|
||||
expect(tourListItemSchema.safeParse({ ...item, stage_stops: [{ ...stops[0], key: '' }, stops[1]] }).success).toBe(false);
|
||||
});
|
||||
});
|
||||
|
||||
@@ -55,3 +55,42 @@ export function tourStageIssues(points: readonly StagePoint[]): StageIssue[] {
|
||||
issues.push({ path: ['waypoints'], message: `a tour has at most ${MAX_TOUR_STAGES} stages` });
|
||||
return issues;
|
||||
}
|
||||
|
||||
/** MDT-02: a stop a stage starts or ends at, as a list or a day plan places the stages. */
|
||||
export interface TourStageStop {
|
||||
/** The point's own key, kept across saves; a stage is known by the key of the stop it starts at. */
|
||||
key: string;
|
||||
lat: number;
|
||||
lng: number;
|
||||
}
|
||||
|
||||
/** A saved point as the stops between the stages are read from it. */
|
||||
interface StoredStagePoint extends StagePoint {
|
||||
key?: string;
|
||||
lat: number;
|
||||
lng: number;
|
||||
}
|
||||
|
||||
/**
|
||||
* MDT-02: the stops the stages of a multi-day tour run between, in route
|
||||
* order: the start, every stop that ends a stage, and the end, so stage `n`
|
||||
* runs from stop `n - 1` to stop `n`. A stage is put on a day by the key of
|
||||
* the stop it starts at, which a save keeps, so a stage end set or taken away
|
||||
* elsewhere leaves the other stages on their days. None for a day tour, and
|
||||
* none where a stop has no key yet (a point no save has kept), since such a
|
||||
* stage could not be found again.
|
||||
*/
|
||||
export function tourStageStopsOf(points: readonly StoredStagePoint[]): TourStageStop[] {
|
||||
if (points.length < 2 || tourStageCountOf(points) < 2) return [];
|
||||
const last = points.length - 1;
|
||||
const stops = points.filter((point, index) => index === 0 || index === last || point.stage_end === true);
|
||||
const keyed = stops.flatMap((point) => (point.key ? [{ key: point.key, lat: point.lat, lng: point.lng }] : []));
|
||||
return keyed.length === stops.length ? keyed : [];
|
||||
}
|
||||
|
||||
/** The keys a stage can be put on a day by: those of the stops the stages start at, the last stop starting none. */
|
||||
export function tourStageKeysOf(points: readonly StoredStagePoint[]): string[] {
|
||||
return tourStageStopsOf(points)
|
||||
.slice(0, -1)
|
||||
.map((stop) => stop.key);
|
||||
}
|
||||
|
||||
@@ -182,6 +182,16 @@ export const tourTypeSchema = z.object({
|
||||
});
|
||||
export type TourType = z.infer<typeof tourTypeSchema>;
|
||||
|
||||
/** CW-03: a station's key, the same across saves. */
|
||||
export const stationKeySchema = z.string().regex(/^[A-Za-z0-9_-]{6,64}$/);
|
||||
|
||||
/** MDT-02: a stop a stage of a multi-day tour starts or ends at, by its key and where it is. */
|
||||
export const tourStageStopSchema = z.object({
|
||||
key: stationKeySchema,
|
||||
lat: z.number(),
|
||||
lng: z.number(),
|
||||
});
|
||||
|
||||
export const tourSchema = z.object({
|
||||
place_id: z.number().int().positive(),
|
||||
tour_type: tourTypeKeySchema,
|
||||
@@ -312,6 +322,12 @@ export const tourListItemSchema = tourSchema.extend({
|
||||
* tour. Absent in a list cached before it existed.
|
||||
*/
|
||||
stage_count: z.number().int().min(2).nullable().optional(),
|
||||
/**
|
||||
* MDT-02: the stops the stages run between, in route order: the start, every
|
||||
* stop that ends a stage, and the end. A stage is put on a day by the key of
|
||||
* the stop it starts at. Null for a day tour; absent in a list cached before it existed.
|
||||
*/
|
||||
stage_stops: z.array(tourStageStopSchema).min(3).nullable().optional(),
|
||||
});
|
||||
export type TourListItem = z.infer<typeof tourListItemSchema>;
|
||||
|
||||
@@ -401,8 +417,6 @@ export const TOUR_STATION_HOURS_MAX = 500;
|
||||
* the server's for a city walk it plans on foot (CW-11).
|
||||
*/
|
||||
export const TOUR_MAX_SNAP_M = 300;
|
||||
/** CW-03: a station's key, the same across saves. */
|
||||
export const stationKeySchema = z.string().regex(/^[A-Za-z0-9_-]{6,64}$/);
|
||||
/** CW-03: how much a station matters, besides normal (null). */
|
||||
export const STATION_IMPORTANCES = ['optional', 'must'] as const;
|
||||
export const stationImportanceSchema = z.enum(STATION_IMPORTANCES);
|
||||
|
||||
Reference in New Issue
Block a user