From fe85d07c98f15359634a3364ec06182356c0f0fc Mon Sep 17 00:00:00 2001 From: binh-dam-ibigroup <56846598+binh-dam-ibigroup@users.noreply.github.com> Date: Mon, 25 Mar 2024 10:41:10 -0400 Subject: [PATCH 01/20] fix(SavedTripScreen): Pass GraphQL plan vars to middleware for existence check --- lib/actions/apiV2.js | 3 ++- lib/components/user/monitored-trip/saved-trip-screen.js | 4 +++- lib/components/user/types.ts | 3 ++- 3 files changed, 7 insertions(+), 3 deletions(-) diff --git a/lib/actions/apiV2.js b/lib/actions/apiV2.js index 66d8be91b..3f00e72e0 100644 --- a/lib/actions/apiV2.js +++ b/lib/actions/apiV2.js @@ -1087,7 +1087,8 @@ export function routingQuery(searchId = null, updateSearchInReducer) { } } }) - ?.map(convertGraphQLResponseToLegacy) + ?.map(convertGraphQLResponseToLegacy), + modeCombination: combo }) ) diff --git a/lib/components/user/monitored-trip/saved-trip-screen.js b/lib/components/user/monitored-trip/saved-trip-screen.js index fa839558d..08cb3f8ad 100644 --- a/lib/components/user/monitored-trip/saved-trip-screen.js +++ b/lib/components/user/monitored-trip/saved-trip-screen.js @@ -74,14 +74,16 @@ class SavedTripScreen extends Component { itinerary, homeTimezone ) + const { modeCombination, ...otherItineraryProps } = itinerary return { ...arrayToDayFields(monitoredDays), arrivalVarianceMinutesThreshold: 5, departureVarianceMinutesThreshold: 5, excludeFederalHolidays: true, isActive: true, - itinerary, + itinerary: otherItineraryProps, leadTimeInMinutes: 30, + otp2QueryParams: modeCombination, // when creating a monitored trip, the query params will be changed on the // backend so that the modes parameter will reflect the modes seen in the // itinerary diff --git a/lib/components/user/types.ts b/lib/components/user/types.ts index f09a3abcf..763b13b89 100644 --- a/lib/components/user/types.ts +++ b/lib/components/user/types.ts @@ -66,7 +66,8 @@ export type MonitoredTrip = Record & { itinerary: Itinerary itineraryExistence?: ItineraryExistence leadTimeInMinutes: number - queryParams: string + otp2QueryParams: Record + queryParams: Record tripName: string userId: string } From 7daa6906c69c60e8aa6920faa06a0523552aea23 Mon Sep 17 00:00:00 2001 From: binh-dam-ibigroup <56846598+binh-dam-ibigroup@users.noreply.github.com> Date: Mon, 25 Mar 2024 10:42:37 -0400 Subject: [PATCH 02/20] refactor(apiV2): Drop irrelevant params from GraphQL plan variables. --- lib/actions/apiV2.js | 4 +--- 1 file changed, 1 insertion(+), 3 deletions(-) diff --git a/lib/actions/apiV2.js b/lib/actions/apiV2.js index 3f00e72e0..4bad3f796 100644 --- a/lib/actions/apiV2.js +++ b/lib/actions/apiV2.js @@ -986,9 +986,7 @@ export function routingQuery(searchId = null, updateSearchInReducer) { modeSettings, numItineraries: numItineraries || config?.modes?.numItineraries || 7, time, - to: currentQuery.to, - // TODO: Does this break everything? - ...currentQuery + to: currentQuery.to } // Generate combinations if the modes for query are not specified in the query // FIXME: BICYCLE_RENT does not appear in this list unless TRANSIT is also enabled. From 9ff8d1397cbe984271fdfb97ef47ffa28e187518 Mon Sep 17 00:00:00 2001 From: binh-dam-ibigroup <56846598+binh-dam-ibigroup@users.noreply.github.com> Date: Mon, 25 Mar 2024 11:08:12 -0400 Subject: [PATCH 03/20] refactor(SavedTripScreen): Pass query.variables from apiV2 directly to monitored trip. --- lib/actions/apiV2.js | 2 +- lib/components/user/monitored-trip/saved-trip-screen.js | 4 ++-- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/lib/actions/apiV2.js b/lib/actions/apiV2.js index 4bad3f796..80abd305d 100644 --- a/lib/actions/apiV2.js +++ b/lib/actions/apiV2.js @@ -1086,7 +1086,7 @@ export function routingQuery(searchId = null, updateSearchInReducer) { } }) ?.map(convertGraphQLResponseToLegacy), - modeCombination: combo + otp2QueryParams: query.variables }) ) diff --git a/lib/components/user/monitored-trip/saved-trip-screen.js b/lib/components/user/monitored-trip/saved-trip-screen.js index 08cb3f8ad..5a86c31f6 100644 --- a/lib/components/user/monitored-trip/saved-trip-screen.js +++ b/lib/components/user/monitored-trip/saved-trip-screen.js @@ -74,7 +74,7 @@ class SavedTripScreen extends Component { itinerary, homeTimezone ) - const { modeCombination, ...otherItineraryProps } = itinerary + const { otp2QueryParams, ...otherItineraryProps } = itinerary return { ...arrayToDayFields(monitoredDays), arrivalVarianceMinutesThreshold: 5, @@ -83,7 +83,7 @@ class SavedTripScreen extends Component { isActive: true, itinerary: otherItineraryProps, leadTimeInMinutes: 30, - otp2QueryParams: modeCombination, + otp2QueryParams, // when creating a monitored trip, the query params will be changed on the // backend so that the modes parameter will reflect the modes seen in the // itinerary From 7e18f4e88471949954d4e299963031871ffd86fa Mon Sep 17 00:00:00 2001 From: binh-dam-ibigroup <56846598+binh-dam-ibigroup@users.noreply.github.com> Date: Mon, 25 Mar 2024 11:11:30 -0400 Subject: [PATCH 04/20] refactor(SavedTripScreen): Remove unused prop. --- lib/components/user/monitored-trip/saved-trip-screen.js | 1 - 1 file changed, 1 deletion(-) diff --git a/lib/components/user/monitored-trip/saved-trip-screen.js b/lib/components/user/monitored-trip/saved-trip-screen.js index 5a86c31f6..24cf40d08 100644 --- a/lib/components/user/monitored-trip/saved-trip-screen.js +++ b/lib/components/user/monitored-trip/saved-trip-screen.js @@ -250,7 +250,6 @@ const mapStateToProps = (state, ownProps) => { const itineraries = getActiveItineraries(state) || [] const tripId = ownProps.match.params.id return { - activeSearchId: state.otp.activeSearchId, homeTimezone: state.otp.config.homeTimezone, isCreating: tripId === 'new', itinerary: itineraries[activeItinerary], From b92f82fd2a70e646b9ea9f8fb7059afdb990ce36 Mon Sep 17 00:00:00 2001 From: binh-dam-ibigroup <56846598+binh-dam-ibigroup@users.noreply.github.com> Date: Mon, 22 Jul 2024 17:21:56 -0400 Subject: [PATCH 05/20] refactor(apiV2): Use OTP2 route response more intentionally. --- lib/actions/apiV2.js | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/lib/actions/apiV2.js b/lib/actions/apiV2.js index 425470d71..679f39fe1 100644 --- a/lib/actions/apiV2.js +++ b/lib/actions/apiV2.js @@ -1103,7 +1103,10 @@ export function routingQuery(searchId = null, updateSearchInReducer) { } } }) - ?.map(convertGraphQLResponseToLegacy), + ?.map((leg) => ({ + ...convertGraphQLResponseToLegacy(leg), + route: leg.transitLeg ? leg.route : undefined + })), otp2QueryParams: query.variables }) ) From 8b074de897f6321418fc612912b7b86a01f89537 Mon Sep 17 00:00:00 2001 From: binh-dam-ibigroup <56846598+binh-dam-ibigroup@users.noreply.github.com> Date: Wed, 24 Jul 2024 18:17:22 -0400 Subject: [PATCH 06/20] refactor(actions/user): Parse OTP2 query params --- lib/actions/user.js | 14 +++++++++++--- 1 file changed, 11 insertions(+), 3 deletions(-) diff --git a/lib/actions/user.js b/lib/actions/user.js index 9ec2d9965..13c6e6584 100644 --- a/lib/actions/user.js +++ b/lib/actions/user.js @@ -156,12 +156,17 @@ export function fetchAuth0Token(auth0, intl) { */ function convertRequestToSearch(config) { return function (tripRequest) { - const { dateCreated, id, requestParameters = {} } = tripRequest + const { + dateCreated, + id, + otp2QueryParams = {}, + requestParameters = {} // Deprecated OTP1 + } = tripRequest const { host, path } = config.api return { canDelete: false, id, - query: planParamsToQuery(requestParameters || {}), + query: planParamsToQuery(otp2QueryParams || requestParameters || {}), timestamp: dateCreated, url: `${host}${path}/plan?${qs.stringify(requestParameters)}` } @@ -219,9 +224,12 @@ export function fetchTripRequests() { if (status === 'success') { // Convert tripRequests to search format. const { config } = getState().otp + console.log(trips.data) const convertedTrips = trips.data .map(convertRequestToSearch(config)) - .filter((tripReq) => !isEmpty(tripReq.query)) + .filter( + (tripReq) => !isEmpty(tripReq.query) || !!tripReq.otp2QueryParams + ) .filter(removeDuplicateRequestsFromBatch()) dispatch(setCurrentUserTripRequests(convertedTrips)) From 337685b970e61350017e67767eb949833f7c0bbf Mon Sep 17 00:00:00 2001 From: binh-dam-ibigroup <56846598+binh-dam-ibigroup@users.noreply.github.com> Date: Thu, 25 Jul 2024 12:22:51 -0400 Subject: [PATCH 07/20] fix(actions/users): Better filter duplicate entries --- lib/actions/user.js | 46 +++++++++++++++++++++++++++++---------------- 1 file changed, 30 insertions(+), 16 deletions(-) diff --git a/lib/actions/user.js b/lib/actions/user.js index 13c6e6584..8678ccde6 100644 --- a/lib/actions/user.js +++ b/lib/actions/user.js @@ -173,23 +173,38 @@ function convertRequestToSearch(config) { } } +// TODO move to a utils file +function areRequestsSame(qp1, qp2) { + // Everything except modes. + // Exclude banned/preferred/unpreferred at this time. + return ( + qp1.bikeReluctance === qp2.bikeReluctance && + qp1.carReluctance === qp2.carReluctance && + qp1.date === qp2.date && + qp1.departArrive === qp2.departArrive && + qp1.from?.name === qp2.from?.name && + qp1.departArrive === qp2.departArrive && + qp1.time === qp2.time && + qp1.to?.name === qp2.to?.name && + qp1.walkReluctance === qp2.walkReluctance && + qp1.walkSpeed === qp2.walkSpeed && + qp1.wheelchair === qp2.wheelchair + ) +} + /** - * Removes duplicate requests saved from batch queries, - * so that only one request is displayed per batch. - * (Except for the mode, all query params in the same batch are the same.) + * Removes duplicate requests so that only one request is displayed per "batch". */ -function removeDuplicateRequestsFromBatch() { - const batches = {} - return function ({ query }) { - if (!batches[query.batchId]) { - batches[query.batchId] = true - - // Remove the mode for display purposes - query.mode = '' - return true - } - return false +function removeDuplicateRequests(filtered, tripRequest) { + // Compare one trip request to the next one. + if (filtered.length == 0) { + filtered.push(tripRequest) + } else if ( + !areRequestsSame(filtered[filtered.length - 1].query, tripRequest.query) + ) { + filtered.push(tripRequest) } + return filtered } /** @@ -224,13 +239,12 @@ export function fetchTripRequests() { if (status === 'success') { // Convert tripRequests to search format. const { config } = getState().otp - console.log(trips.data) const convertedTrips = trips.data .map(convertRequestToSearch(config)) .filter( (tripReq) => !isEmpty(tripReq.query) || !!tripReq.otp2QueryParams ) - .filter(removeDuplicateRequestsFromBatch()) + .reduce(removeDuplicateRequests, []) dispatch(setCurrentUserTripRequests(convertedTrips)) } From 5bf5fa9706f372955fa0c97a3ee0a909f019dcf9 Mon Sep 17 00:00:00 2001 From: binh-dam-ibigroup <56846598+binh-dam-ibigroup@users.noreply.github.com> Date: Thu, 25 Jul 2024 12:40:06 -0400 Subject: [PATCH 08/20] refactor(user-settings-i18n): Combine modes for displaying --- lib/actions/user.js | 4 +++- lib/components/form/user-settings-i18n.js | 7 +++++-- 2 files changed, 8 insertions(+), 3 deletions(-) diff --git a/lib/actions/user.js b/lib/actions/user.js index 8678ccde6..149d7cf9a 100644 --- a/lib/actions/user.js +++ b/lib/actions/user.js @@ -197,12 +197,14 @@ function areRequestsSame(qp1, qp2) { */ function removeDuplicateRequests(filtered, tripRequest) { // Compare one trip request to the next one. - if (filtered.length == 0) { + if (filtered.length === 0) { filtered.push(tripRequest) } else if ( !areRequestsSame(filtered[filtered.length - 1].query, tripRequest.query) ) { filtered.push(tripRequest) + } else { + filtered[filtered.length - 1].query.modes.push(...tripRequest.query.modes) } return filtered } diff --git a/lib/components/form/user-settings-i18n.js b/lib/components/form/user-settings-i18n.js index c1a639a00..e6a89aa0d 100644 --- a/lib/components/form/user-settings-i18n.js +++ b/lib/components/form/user-settings-i18n.js @@ -50,15 +50,18 @@ export function summarizeQuery(query, intl, locations = []) { ) } + const modes = query.modes + .map(({ mode, qualifier }) => `${mode}${qualifier ? `_${qualifier}` : ''}`) + .join(',') const from = findLocationType(intl, query.from, locations) || stripAllButNameOfAddress(query.from.name) const to = findLocationType(intl, query.to, locations) || stripAllButNameOfAddress(query.to.name) - const mode = hasTransit(query.mode) + const mode = hasTransit(modes) ? intl.formatMessage({ id: 'common.modes.transit' }) - : toSentenceCase(query.mode) + : toSentenceCase(modes) return intl.formatMessage( { id: 'components.UserSettings.recentSearchSummary' }, { from, mode, to } From 4594839270352a0ebeff1be94b024051657717c2 Mon Sep 17 00:00:00 2001 From: binh-dam-ibigroup <56846598+binh-dam-ibigroup@users.noreply.github.com> Date: Fri, 26 Jul 2024 16:34:41 -0400 Subject: [PATCH 09/20] fix: Fix parsing errors --- lib/actions/user.js | 16 ++++++++++++++++ lib/components/form/user-settings-i18n.js | 9 ++++++--- 2 files changed, 22 insertions(+), 3 deletions(-) diff --git a/lib/actions/user.js b/lib/actions/user.js index 149d7cf9a..14287dec5 100644 --- a/lib/actions/user.js +++ b/lib/actions/user.js @@ -163,6 +163,22 @@ function convertRequestToSearch(config) { requestParameters = {} // Deprecated OTP1 } = tripRequest const { host, path } = config.api + + // FIXME: remove if OTP-UI no longer includes trailing bracket + // Don't use ?. syntax, fromPlace and toPlace need to be defined in order to be replaced. + if (otp2QueryParams.fromPlace && otp2QueryParams.fromPlace.endsWith('}')) { + otp2QueryParams.fromPlace = otp2QueryParams.fromPlace.substring( + 0, + otp2QueryParams.fromPlace.length - 2 + ) + } + if (otp2QueryParams.toPlace && otp2QueryParams.toPlace.endsWith('}')) { + otp2QueryParams.toPlace = otp2QueryParams.toPlace.substring( + 0, + otp2QueryParams.toPlace.length - 2 + ) + } + return { canDelete: false, id, diff --git a/lib/components/form/user-settings-i18n.js b/lib/components/form/user-settings-i18n.js index e6a89aa0d..246baea68 100644 --- a/lib/components/form/user-settings-i18n.js +++ b/lib/components/form/user-settings-i18n.js @@ -50,9 +50,12 @@ export function summarizeQuery(query, intl, locations = []) { ) } - const modes = query.modes - .map(({ mode, qualifier }) => `${mode}${qualifier ? `_${qualifier}` : ''}`) - .join(',') + const modes = + query.modes + ?.map( + ({ mode, qualifier }) => `${mode}${qualifier ? `_${qualifier}` : ''}` + ) + .join(',') || '' const from = findLocationType(intl, query.from, locations) || stripAllButNameOfAddress(query.from.name) From 7c361c11f657595b3c565478556723c20ecac079 Mon Sep 17 00:00:00 2001 From: binh-dam-ibigroup <56846598+binh-dam-ibigroup@users.noreply.github.com> Date: Fri, 2 Aug 2024 17:46:46 -0400 Subject: [PATCH 10/20] chore(deps): Update core-utils version for bug fix --- package.json | 2 +- yarn.lock | 20 +++++++++++++++++++- 2 files changed, 20 insertions(+), 2 deletions(-) diff --git a/package.json b/package.json index 7e4c69365..48e9c2886 100644 --- a/package.json +++ b/package.json @@ -44,7 +44,7 @@ "@floating-ui/react": "^0.19.2", "@opentripplanner/base-map": "^3.2.0", "@opentripplanner/building-blocks": "^1.1.0", - "@opentripplanner/core-utils": "^11.4.2", + "@opentripplanner/core-utils": "^11.4.3", "@opentripplanner/endpoints-overlay": "^2.1.1", "@opentripplanner/from-to-location-picker": "^2.1.13", "@opentripplanner/geocoder": "^3.0.1", diff --git a/yarn.lock b/yarn.lock index 1c0bb7eb6..a1728d568 100644 --- a/yarn.lock +++ b/yarn.lock @@ -2398,7 +2398,7 @@ resolved "https://registry.yarnpkg.com/@opentripplanner/building-blocks/-/building-blocks-1.1.0.tgz#ef9fe862ce0a3e92c9a6c2c2db749a9a02deebd5" integrity sha512-nx7pU1zIZzJcSkCFYyZ7gt+jd0gXj7bjx8rXn1msgF5uLWmtN/70dsmYNEApeA7haC076KOO3B/Jh44YfXG95g== -"@opentripplanner/core-utils@^11.2.3", "@opentripplanner/core-utils@^11.4.0", "@opentripplanner/core-utils@^11.4.2": +"@opentripplanner/core-utils@^11.2.3", "@opentripplanner/core-utils@^11.4.0": version "11.4.2" resolved "https://registry.yarnpkg.com/@opentripplanner/core-utils/-/core-utils-11.4.2.tgz#cc6034fb80ccda44e50f7f0a1e80a7bad8387f84" integrity sha512-EVYVN73Cgf9IC+uya49843MFJnVkmv0nHAjsQwmPGSx/w5fY49X4fSpDprL7Bn+MTzk58U2udDsn6OzKmV0JdA== @@ -2416,6 +2416,24 @@ lodash.isequal "^4.5.0" qs "^6.9.1" +"@opentripplanner/core-utils@^11.4.3": + version "11.4.3" + resolved "https://registry.yarnpkg.com/@opentripplanner/core-utils/-/core-utils-11.4.3.tgz#4655f9a3bef1977e53abd81a4a0eae966f977c60" + integrity sha512-GbvchRsLfEi9JygUx6ypU+Iqv2hELseC53yQyQ/XdnB1kcHzN71BtBbz+qpD5/jk8IuM92j1taRnGMeu5ni6yA== + dependencies: + "@conveyal/lonlat" "^1.4.1" + "@mapbox/polyline" "^1.1.0" + "@opentripplanner/geocoder" "^3.0.0" + "@styled-icons/foundation" "^10.34.0" + "@turf/along" "^6.0.1" + chroma-js "^2.4.2" + date-fns "^2.28.0" + date-fns-tz "^1.2.2" + graphql "^16.6.0" + lodash.clonedeep "^4.5.0" + lodash.isequal "^4.5.0" + qs "^6.9.1" + "@opentripplanner/endpoints-overlay@^2.1.1": version "2.1.1" resolved "https://registry.yarnpkg.com/@opentripplanner/endpoints-overlay/-/endpoints-overlay-2.1.1.tgz#e7029d95bd13436aacbc6f854c243d1fcf7e8570" From 9053e97d5f3e1730abc2961519fc7c840690f616 Mon Sep 17 00:00:00 2001 From: binh-dam-ibigroup <56846598+binh-dam-ibigroup@users.noreply.github.com> Date: Fri, 2 Aug 2024 17:52:02 -0400 Subject: [PATCH 11/20] refactor(actions/users): Remove obsolete location parsing code. --- lib/actions/user.js | 16 ---------------- 1 file changed, 16 deletions(-) diff --git a/lib/actions/user.js b/lib/actions/user.js index 14287dec5..149d7cf9a 100644 --- a/lib/actions/user.js +++ b/lib/actions/user.js @@ -163,22 +163,6 @@ function convertRequestToSearch(config) { requestParameters = {} // Deprecated OTP1 } = tripRequest const { host, path } = config.api - - // FIXME: remove if OTP-UI no longer includes trailing bracket - // Don't use ?. syntax, fromPlace and toPlace need to be defined in order to be replaced. - if (otp2QueryParams.fromPlace && otp2QueryParams.fromPlace.endsWith('}')) { - otp2QueryParams.fromPlace = otp2QueryParams.fromPlace.substring( - 0, - otp2QueryParams.fromPlace.length - 2 - ) - } - if (otp2QueryParams.toPlace && otp2QueryParams.toPlace.endsWith('}')) { - otp2QueryParams.toPlace = otp2QueryParams.toPlace.substring( - 0, - otp2QueryParams.toPlace.length - 2 - ) - } - return { canDelete: false, id, From ad5a491e841b9af4126a003e8ed5d945fd68f96e Mon Sep 17 00:00:00 2001 From: binh-dam-ibigroup <56846598+binh-dam-ibigroup@users.noreply.github.com> Date: Mon, 5 Aug 2024 11:29:02 -0400 Subject: [PATCH 12/20] chore(i18n): Revert formatting changes --- i18n/es.yml | 34 +-- i18n/zh_Hant.yml | 580 ++++++++++++++++++++++------------------------- 2 files changed, 295 insertions(+), 319 deletions(-) diff --git a/i18n/es.yml b/i18n/es.yml index 930a2bd9d..23318b41a 100644 --- a/i18n/es.yml +++ b/i18n/es.yml @@ -30,7 +30,8 @@ actions: No se puede guardar el plan: este plan no se pudo guardar debido a la falta de capacidad en uno o más vehículos. Por favor, vuelva a planificar su viaje. - maxTripRequestsExceeded: Número de solicitudes de viaje superadas sin resultados válidos + maxTripRequestsExceeded: Número de solicitudes de viaje superadas sin resultados + válidos saveItinerariesError: "No se pudieron guardar los itinerarios: {err}" setDateError: "Error al establecer la fecha:" setGroupSizeError: "No se pudo establecer el tamaño del grupo:" @@ -52,10 +53,13 @@ actions: authTokenError: Error al obtener un token de autorización. confirmDeleteMonitoredTrip: ¿Desea eliminar este viaje? confirmDeletePlace: ¿Quiere eliminar este lugar? - emailVerificationResent: El mensaje de verificación de correo electrónico ha sido reenviado. + emailVerificationResent: El mensaje de verificación de correo electrónico ha sido + reenviado. genericError: "Se ha encontrado un error: {err}" - itineraryExistenceCheckFailed: Comprobación de errores para ver si el viaje seleccionado es posible. - mustAcceptTermsToSavePlace: Acepte los Términos de uso (en Mi Cuenta) para guardar las ubicaciones. + itineraryExistenceCheckFailed: Comprobación de errores para ver si el viaje seleccionado + es posible. + mustAcceptTermsToSavePlace: Acepte los Términos de uso (en Mi Cuenta) para guardar + las ubicaciones. mustBeLoggedInToSavePlace: Por favor, inicia la sesión para guardar las ubicaciones. placeRemembered: La configuración de este lugar se ha guardado. preferencesSaved: Sus preferencias se han guardado. @@ -208,7 +212,8 @@ components: a incluir el transporte publico en la selección de modos. origin: origen planTripTooltip: Planificar viaje - validationMessage: "Por favor, defina los siguientes campos para planificar un viaje: {issues}" + validationMessage: "Por favor, defina los siguientes campos para planificar un + viaje: {issues}" BeforeSignInScreen: mainTitle: Iniciando sesión message: > @@ -364,12 +369,12 @@ components: disponibles}} companyBicycle: Bicicleta {company} companyScooter: Scooter {company} - distanceAway: "{localizedDistanceString} de distancia" error: Se ha producido un error al cargar los servicios cercanos. header: Ver cerca nearbyListIntro: Lista de {count} entidades cercanas. nothingNearby: No ubicaciónes cercanas. spacesAvailable: "{spacesAvailable} espacios libres disponibles" + distanceAway: '{localizedDistanceString} de distancia' NewAccountWizard: createNewAccount: Crear una nueva cuenta finish: ¡Configuración de la cuenta completa! @@ -450,8 +455,7 @@ components: minutos. verified: Verificado verify: Verificar - verifySms: >- - Por favor, complete el proceso de verificación para configurar las + verifySms: Por favor, complete el proceso de verificación para configurar las notificaciones por SMS. Place: deleteThisPlace: Borrar este lugar @@ -558,7 +562,8 @@ components: header: ¡La sesión está a punto de terminar! keepSession: Continuar sesión SimpleRealtimeAnnotation: - usingRealtimeInfo: Este viaje utiliza información de tráfico y retrasos en tiempo real + usingRealtimeInfo: Este viaje utiliza información de tráfico y retrasos en tiempo + real StackedPaneDisplay: savePreferences: Guardar preferencias StopScheduleTable: @@ -620,15 +625,18 @@ components: travelingAt: Viajando a {milesPerHour} vehicleName: Vehículo {vehicleNumber} TripBasicsPane: - checkingItineraryExistence: Comprobación de la existencia de itinerarios para cada día de la semana… + checkingItineraryExistence: Comprobación de la existencia de itinerarios para + cada día de la semana… tripDaysPrompt: ¿Qué días hace este viaje? - tripIsAvailableOnDaysIndicated: Su viaje está disponible en los días de la semana indicados anteriormente. + tripIsAvailableOnDaysIndicated: Su viaje está disponible en los días de la semana + indicados anteriormente. tripNamePrompt: "Por favor, indique un nombre para este viaje:" tripNotAvailableOnDay: El viaje no está disponible el {repeatedDay} unsavedChangesExistingTrip: >- Todavía no ha guardado su viaje. Si abandona la página, los cambios se perderán. - unsavedChangesNewTrip: Todavía no ha guardado su nuevo viaje. Si abandona la página, se perderá. + unsavedChangesNewTrip: Todavía no ha guardado su nuevo viaje. Si abandona la página, + se perderá. TripNotificationsPane: advancedSettings: Configuración avanzada altRouteRecommended: Se recomienda una ruta alternativa o un punto de transferencia @@ -706,10 +714,10 @@ components: leaveAt: "Salida a las " TripSummaryPane: happensOnDays: "Ocurre en: {days}" - monitoredTripDays: Días de viaje monitoreados notifications: "{leadTimeInMinutes} minutos antes de la salida programada" notificationsDisabled: Notificaciones desactivadas timeAndDuration: Tiempo y duración del viaje + monitoredTripDays: Días de viaje monitoreados TripTools: copyLink: Copiar enlace header: Herramientas diff --git a/i18n/zh_Hant.yml b/i18n/zh_Hant.yml index eb4307f2d..3cc8aebc8 100644 --- a/i18n/zh_Hant.yml +++ b/i18n/zh_Hant.yml @@ -2,19 +2,17 @@ _id: zh-Hant _name: Chinese (Traditional) actions: callTaker: + queryFetchError: 擷取查詢時發生錯誤:{err} callQuerySaveError: 儲存通話查詢時發生錯誤:{err} callSaveError: 無法儲存通話:{err} checkSessionError: 建立授權工作階段時發生錯誤:{err} couldNotFindCallError: 找不到通話。正在取消儲存查詢請求。 fetchCallsError: 擷取通話時發生錯誤:{err} - queryFetchError: 擷取查詢時發生錯誤:{err} fieldTrip: addNoteError: 新增實地考察備註時發生錯誤: - confirmOverwriteItineraries: | - 此動作將覆蓋此請求之前規劃的{outbound, select, - true {外出} - other {返回} - }預定行程。您想繼續嗎? + confirmOverwriteItineraries: "此動作將覆蓋此請求之前規劃的{outbound, select,\n true {外出}\n other + {返回}\n }預定行程。您想繼續嗎?\n" + setGroupSizeError: 設定群組大小時發生錯誤: deleteItinerariesError: 刪除實地考察計畫時發生錯誤: deleteNoteError: 刪除實地考察備註時發生錯誤: editSubmitterNotesError: 編輯提交者備註時發生錯誤: @@ -26,16 +24,8 @@ actions: maxTripRequestsExceeded: 行程請求數量過多,沒有有效結果 saveItinerariesError: 無法儲存預定行程:{err} setDateError: 設定日期時發生錯誤: - setGroupSizeError: 設定群組大小時發生錯誤: setPaymentError: 設定付款資訊時發生錯誤: setRequestStatusError: 設定請求狀態時發生錯誤: - location: - deniedAccessAlert: | - 系統已封鎖您存取地點的權限。 - 若要使用目前的地點,請從瀏覽器啟用地點權限,然後再重新載入頁面。 - geolocationNotSupportedError: 您的瀏覽器不支援地理位置 - unknownPositionError: 取得位置時發生不明錯誤 - userDeniedPermission: 使用者拒絕提供權限 map: currentLocation: (目前位置) user: @@ -45,36 +35,29 @@ actions: confirmDeletePlace: 是否要移除此地點? emailVerificationResent: 電子郵件驗證訊息已重新傳送。 genericError: 發生錯誤:{err} + mustBeLoggedInToSavePlace: 請登入,以儲存地點。 itineraryExistenceCheckFailed: 檢查您選取的行程是否可行時發生錯誤。 mustAcceptTermsToSavePlace: 請接受使用條款 (在我的帳戶下),以儲存地點。 - mustBeLoggedInToSavePlace: 請登入,以儲存地點。 placeRemembered: 此地點的設定已儲存。 preferencesSaved: 您的偏好已儲存。 smsInvalidCode: 您輸入的代碼無效。請再試一次。 smsResendThrottled: 驗證簡訊已在不到一分鐘前傳送到指定的電話號碼。請稍後再試一次。 smsVerificationFailed: 您的電話無法驗證。您輸入的代碼可能已失效。請要求新的代碼,然後再試一次。 + location: + deniedAccessAlert: "系統已封鎖您存取地點的權限。\n若要使用目前的地點,請從瀏覽器啟用地點權限,然後再重新載入頁面。 \n" + geolocationNotSupportedError: 您的瀏覽器不支援地理位置 + unknownPositionError: 取得位置時發生不明錯誤 + userDeniedPermission: 使用者拒絕提供權限 common: - coordinates: "{lat}; {lon}" - dateExpressions: - today: 今天 - tomorrow: 明天 - yesterday: 昨天 - daysOfWeek: - friday: 星期五 - monday: 星期一 - saturday: 星期六 - sunday: 星期日 - thursday: 星期四 - tuesday: 星期二 - wednesday: 星期三 + coordinates: '{lat}; {lon}' daysOfWeekCompact: - friday: 週五 - monday: 週一 - saturday: 週六 sunday: 週日 thursday: 週四 tuesday: 週二 wednesday: 週三 + friday: 週五 + monday: 週一 + saturday: 週六 daysOfWeekPlural: friday: 每星期五 monday: 每星期一 @@ -83,39 +66,16 @@ common: thursday: 每星期四 tuesday: 每星期二 wednesday: 每星期三 - forms: - back: 返回 - cancel: 取消 - close: 關閉 - defaultValue: "{value} (預設)" - edit: 編輯 - error: 錯誤! - finish: 完成 - next: 前進 - "no": 否 - print: 列印 - save: 儲存 - startOver: 重新開始 - submitting: 正在提交… - "yes": 是 - itineraryDescriptions: - calories: "{calories, number}卡" - fareUnknown: 無票價資訊 - noItineraryToDisplay: 沒有預定行程可顯示。 - relativeCo2: | - {co2}比單獨開車排放的CO₂{isMore, select, true {要多} other {少}} - transfers: "{transfers}次轉乘" - linkOpensNewWindow: (開啟新視窗) modes: + car_park: 停車轉乘 + drive: 駕車 + ferry: 渡船 + flex: 彈性路線 bicycle_rent: 自行車共享 bike: 騎行 bus: 搭乘公車 cable_car: 纜車 car: 汽車 - car_park: 停車轉乘 - drive: 駕車 - ferry: 渡船 - flex: 彈性路線 funicular: 纜索鐵路 gondola: 空中纜車 micromobility: 電動滑板車 @@ -127,206 +87,155 @@ common: transit: 公共交通 walk: 步行 notifications: - email: 電子郵件 push: 推播通知 sms: 簡訊 + email: 電子郵件 places: custom: 自訂 dining: 餐廳 home: 住家 work: 工作 - routing: - routeAndNumber: 路線{routeId} searchForms: - click: 按一下 enterDestination: 輸入目的地或{mapAction} 地圖…… enterStartLocation: 輸入開始位置或{mapAction} 地圖…… + click: 按一下 tap: 輕觸 + dateExpressions: + today: 今天 + tomorrow: 明天 + yesterday: 昨天 + daysOfWeek: + friday: 星期五 + monday: 星期一 + saturday: 星期六 + sunday: 星期日 + thursday: 星期四 + tuesday: 星期二 + wednesday: 星期三 + forms: + back: 返回 + cancel: 取消 + close: 關閉 + defaultValue: '{value} (預設)' + edit: 編輯 + error: 錯誤! + finish: 完成 + next: 前進 + "no": 否 + print: 列印 + save: 儲存 + startOver: 重新開始 + submitting: 正在提交… + "yes": 是 + itineraryDescriptions: + calories: '{calories, number}卡' + fareUnknown: 無票價資訊 + noItineraryToDisplay: 沒有預定行程可顯示。 + relativeCo2: "{co2}比單獨開車排放的CO₂{isMore, select, true {要多} other {少}}\n" + transfers: '{transfers}次轉乘' + linkOpensNewWindow: (開啟新視窗) + routing: + routeAndNumber: 路線{routeId} time: - departureArrivalTimes: "{startTime, time, short}—{endTime, time, short}" + departureArrivalTimes: '{startTime, time, short}—{endTime, time, short}' duration: aFewSeconds: 幾秒鐘 - nDays: "{days}天" - nHours: "{hours}小時" - nMinutes: "{minutes}分鐘" - durationAgo: "{duration}前" - tripDurationFormat: >- - {hours, plural, =0 {} other {# 小時 }}{minutes} 分鐘 { seconds, plural, =0 {} - other {# 秒鐘}} + nDays: '{days}天' + nHours: '{hours}小時' + nMinutes: '{minutes}分鐘' + durationAgo: '{duration}前' + tripDurationFormat: '{hours, plural, =0 {} other {# 小時 }}{minutes} 分鐘 { seconds, + plural, =0 {} other {# 秒鐘}}' components: - A11yPrefs: - accessibilityRoutingByDefault: 預設優先選擇無障礙行程 AccountSetupFinishPane: message: 您已經準備好開始規劃行程。 - AddPlaceButton: - addPlace: 新增地點 - needOriginDestination: 定義起點/目的地以新增中間地點 - tooManyPlaces: 已達到中間地點數量上限 - AdvancedOptions: - bannedRoutes: 選取禁止的路線…… - preferredRoutes: 選取首選路線…… AfterSignInScreen: mainTitle: 正在重新導向…… message: 如果頁面未在幾秒鐘後載入,請按一下這裡。 AppMenu: callHistory: 通話記錄 closeMenu: 關閉功能表 - fieldTrip: 實地考察 mailables: 可郵寄 openMenu: 開啟功能表 skipNavigation: 跳過導航 + fieldTrip: 實地考察 BackToTripPlanner: backToTripPlanner: 返回行程規劃器 BatchResultsScreen: - expandMap: 展開地圖 showResults: 顯示結果 + expandMap: 展開地圖 BatchRoutingPanel: shortTitle: 規劃行程 - BatchSearchScreen: - header: 規劃您的行程 - modeSelectorLabel: 選取出行模式 - BatchSettings: - destination: 目的地 - invalidModeSelection: 無法使用所選模式規劃行程。嘗試將公共交通納入您的模式選取內容。 - origin: 起點 - planTripTooltip: 規劃行程 - validationMessage: 請定義以下欄位以規劃行程:{issues} BeforeSignInScreen: mainTitle: 登入中 - message: | - 您必須登入才能存取此頁面。我們正在將您重新導向到登入頁面,請稍候…… - CallTakerPanel: - advancedOptions: 進階選項 - groupSize: 群組大小: - intermediateDestination: 輸入中間目的地 + message: "您必須登入才能存取此頁面。我們正在將您重新導向到登入頁面,請稍候……\n" DateTimeOptions: - arriveBy: 最晚抵達時間 departAt: 出發時間 now: 現在 + arriveBy: 最晚抵達時間 DateTimePreview: - arriveAt: "{arriveTime, time, short}抵達" + leaveNow: 現在出發 + arriveAt: '{arriveTime, time, short}抵達' dayLastWeek: 上{formattedDayOfWeek} - departAt: "{departTime, time, short}出發" + departAt: '{departTime, time, short}出發' editDepartOrArrival: 編輯出發或抵達時間 - leaveNow: 現在出發 - DateTimeScreen: - header: 設定日期/時間 DefaultItinerary: clickDetails: 按一下以檢視詳細資料 - multiModeSummary: "{accessMode} + {transitMode}" nonTransit: 替代選項 - DeleteUser: - deleteMyAccount: 刪除我的帳戶 - ErrorMessage: - header: 無法規劃行程 - warning: 警告 + multiModeSummary: '{accessMode} + {transitMode}' ExistingAccountDisplay: - a11y: 可到達性 - mainTitle: 我的設定 notifications: 通知 places: 最愛的地點 terms: 條款 + a11y: 可到達性 + mainTitle: 我的設定 FavoritePlaceList: addAnotherPlace: 新增其他地點 - description: 新增您經常使用的地點,以節省規劃行程的時間: editThisPlace: 編輯此地點 setAddressForPlaceType: 設定您的{placeType}地址 + description: 新增您經常使用的地點,以節省規劃行程的時間: FavoritePlaceScreen: + placeNotFound: 找不到地點 addNewPlace: 新增地點 editPlace: 編輯{placeName} editPlaceGeneric: 編輯地點 invalidAddress: 請為此地點設定位置。 invalidName: 請為此地點輸入名稱。 nameAlreadyUsed: 您已經對其他地點使用此名稱。請輸入其他名稱。 - placeNotFound: 找不到地點 placeNotFoundDescription: 很抱歉,找不到請求的地點。 - FormNavigationButtons: - ariaLabel: 表單導航 - ItinerarySummary: - itineraryDetails: 路線詳細資訊 - minMaxFare: "{minTotalFare} - {maxTotalFare}" - LocationSearch: - enterLocation: 輸入位置 - setDestination: 設定目的地 - setOrigin: 設定起點 MapLayers: - bike-rental: "{companies}共享自行車" - car-rental: 自行車租賃位置 - micromobility-rental: "{companies}電動滑板車" - park-and-ride: 停車轉乘位置 satellite: 衛星 shared-vehicles: 共享車輛 stops: 公共交通車站 streets: 街道 - MapillaryFrame: - title: 街道圖像 + bike-rental: '{companies}共享自行車' + car-rental: 自行車租賃位置 + micromobility-rental: '{companies}電動滑板車' + park-and-ride: 停車轉乘位置 MetroUI: + originallyScheduledTime: (原定 {originalTime}) + singleModeItineraryDescription: '{time} {mode} 路線' arriveAt: 您已抵達目的地 - itineraryDescription: "{time}預定行程,使用{routes}" + itineraryDescription: '{time}預定行程,使用{routes}' leaveAt: 您出發 multipleOptions: 多個選項 orAlternatives: 或相同方向的其他路線 - originallyScheduledTime: (原定 {originalTime}) - singleModeItineraryDescription: "{time} {mode} 路線" timeWalking: 步行{time} - MobileOptions: - header: 設定搜尋選項 - NarrativeItinerariesHeader: - changeSortDir: 變更排序方向 - howToFindResults: 要檢視結果,請參閱下方「找到的預定行程」標題。 - itinerariesFound: "{itineraryNum}個預定行程已找到" - numIssues: "{issueNum}個問題" - resultsSortedBy: 行程結果目前依{sortSelected}排序。如需變更結果的排序方式,請使用下方「排序結果」按鈕。 - searching: 正在尋找您的選項…… - selectArrivalTime: 抵達時間 - selectBest: 最佳選項 - selectCost: 費用 - selectDepartureTime: 出發時間 - selectDuration: 期間 - selectWalkTime: 步行時間 - sortResults: 對結果排序 - viewAll: 檢視所有選項 - NavLoginButton: - help: 說明 - myAccount: 我的帳戶 - signIn: 登入 - signOut: 登出 - NearbyView: - bikeRentalStation: 自行車租車站 - bikesAvailable: "{bikesAvailable}輛自行車可用" - companyBicycle: "{company} 自行車" - companyScooter: "{company} 滑板車" - error: 載入鄰近設施時發生錯誤。 - header: 檢視附近 - spacesAvailable: "{spacesAvailable} 有未佔用的空間" - NewAccountWizard: - createNewAccount: 建立新帳戶 - finish: 帳戶設定完成! - notifications: 通知偏好 - places: 新增您的位置 - verify: 驗證您的電子郵件地址 - NotFound: - description: 您要求的內容無法使用。 - header: 找不到內容 - NotificationPrefsPane: - devicesRegistered: "{count}個裝置 已註冊" - noDeviceForPush: 使用行動應用程式註冊您的裝置以存取推播通知。 - notificationChannelPrompt: 透過以下方式接收有關您已儲存之行程的通知: OTP2ErrorRenderer: - LOCATION_NOT_FOUND: - body: "{inputFields} 地點 未鄰近任何街道。" - header: 地點無法到達 NO_STOPS_IN_RANGE: - body: "{inputFields} 地點 未鄰近任何公共交通車站。" + body: '{inputFields} 地點 未鄰近任何公共交通車站。' header: 範圍內無車站 NO_TRANSIT_CONNECTION: body: 在所選服務日當天使用所選車輛類型進行搜尋後,在您的出發地和目的地之間找不到公共交通連接路線。 header: 無公共交通連接路線 + LOCATION_NOT_FOUND: + body: '{inputFields} 地點 未鄰近任何街道。' + header: 地點無法到達 NO_TRANSIT_CONNECTION_IN_SEARCH_WINDOW: body: 找到一個公共交通連接路線,但此路線超出搜尋視窗範圍,請使用您所選擇的車輛類型試著調整您的搜尋視窗。 header: 搜尋視窗內無公共交通連接路線 OUTSIDE_BOUNDS: - body: "{inputFields} 地點 不在行程規劃器的範圍內。" + body: '{inputFields} 地點 不在行程規劃器的範圍內。' header: 地點超出範圍 OUTSIDE_SERVICE_PERIOD: body: 指定的日期超出目前載入行程規劃器中的資料範圍。 @@ -341,6 +250,8 @@ components: FROM: 起點 TO: 目的地 PhoneNumberEditor: + requestNewCode: 要求新代碼 + sendVerificationText: 傳送驗證簡訊 changeNumber: 變更號碼 invalidCode: 請輸入6位數的驗證碼。 invalidPhone: 請輸入有效的電話號碼。 @@ -349,20 +260,11 @@ components: phoneNumberVerified: 已順利驗證電話號碼 {phoneNumber} 。 placeholder: 輸入您的電話號碼 prompt: 輸入您用來接收簡訊通知的電話號碼: - requestNewCode: 要求新代碼 - sendVerificationText: 傳送驗證簡訊 - smsConsent: | - 一旦提供您的電話號碼,即表示您同意接收驗證和行程監控簡訊。您的行動電信商可能會收取額外費用。 + smsConsent: "一旦提供您的電話號碼,即表示您同意接收驗證和行程監控簡訊。您的行動電信商可能會收取額外費用。\n" verificationCode: 驗證碼: - verificationInstructions: | - 請查看您手機的簡訊應用程式是否收到含有驗證碼的簡訊,並在下方輸入該驗證碼 (驗證碼將在10分鐘後失效)。 + verificationInstructions: "請查看您手機的簡訊應用程式是否收到含有驗證碼的簡訊,並在下方輸入該驗證碼 (驗證碼將在10分鐘後失效)。\n" verified: 已驗證 verify: 驗證 - Place: - deleteThisPlace: 刪除此地點 - enterAlert: | - 請在表單內輸入起點/目的地 (或按一下地圖來設定),然後按一下產生的標記以設定為{placeType}位置。 - viewStop: 檢視車站 PlaceEditor: addressPrompt: 地址: genericLocationPlaceholder: 搜尋位置 @@ -371,10 +273,10 @@ components: nameExample: 我的咖啡館 namePrompt: 定地名: PlanFirstLastButtons: + previous: 上一個 first: 第一個 last: 最後一個 next: 前進 - previous: 上一個 PlanTripButton: planTrip: 規劃行程 PointPopup: @@ -382,53 +284,28 @@ components: PrintLayout: itinerary: 預定行程 toggleMap: 切換地圖 - RealtimeAnnotation: - delaysShownInResults: | - 您的行程結果已依據即時資訊調整。在一般情況下,此形成使用以下路線會需要{normalDuration}:{routes}。 - serviceUpdate: 服務更新 RealtimeStatusLabel: early: 提早{minutes} late: 延後{minutes} onTime: 準時 scheduled: 預定 - ResultsError: - backToSearch: 返回搜尋 ResultsHeader: noTripFound: 找不到行程 tripsFound: 我們找到了{count} 個選項 waiting: 等待中…… - RouteDetails: - headsignTo: "{headsign} ({lastStop})" - moreDetails: 更多詳細資訊 - operatedBy: 由{agencyName}營運 - selectADirection: 選取方向…… - stopsTo: 朝向 RouteViewer: - agencyFilter: 機構篩選 allAgencies: 所有機構 allModes: 所有模式 - details: " " + details: ' ' findARoute: 尋找路線 header: 路線檢視器 modeFilter: 模式篩選 noFilteredRoutesFound: 沒有路線符合您的篩選條件! + agencyFilter: 機構篩選 openPatternViewer: 檢視預定行程詳細資訊 shortTitle: 檢視路線 stopsInDirectionOfTravel: 此行駛方向的沿路車站: title: 路線檢視器 - SaveTripButton: - cantSaveText: 無法儲存 - cantSaveTooltip: 僅能監控包括公共交通且沒有租賃或叫車服務的預定行程。 - saveTripText: 儲存行程 - signInText: 登入以儲存行程 - signInTooltip: 請登入以儲存行程。 - SavedTripEditor: - editSavedTrip: 編輯儲存的行程 - saveNewTrip: 儲存新行程 - tripInformation: 行程資訊 - tripNotFound: 找不到行程 - tripNotFoundDescription: 很抱歉,找不到請求的行程。 - tripNotifications: 行程通知 SavedTripList: fromTo: 從 {from} 到 {to} myTrips: 我的行程 @@ -436,19 +313,6 @@ components: noSavedTripsInstructions: 請先從地圖進行行程搜尋。 pause: 暫停 resume: 繼續 - SavedTripScreen: - itineraryLoaded: 路線已載入 - itineraryLoading: 正在載入路線 - tooManyTrips: | - 您已經達到五個儲存行程上限。請將未使用的行程從已儲存行程中移除,然後再試一次。 - tripNameAlreadyUsed: 已經有另一個儲存的行程使用此名稱。請選擇其他名稱。 - tripNameRequired: 請輸入行程名稱。 - SequentialPaneDisplay: - stepNumber: 第{step}步,共{total}步 - SessionTimeout: - body: 您的工作階段將在一分鐘內失效。請按下「Continue Session」(繼續工作階段) 以保留搜尋。 - header: 工作階段即將逾時! - keepSession: 繼續工作階段 SimpleRealtimeAnnotation: usingRealtimeInfo: 此行程使用即時交通和延誤資訊。 StackedPaneDisplay: @@ -458,19 +322,15 @@ components: departure: 出發 destination: 至 route: 路線 - StopTimeCell: - imminentArrival: 到 - realtime: 依據即時資料 - scheduled: 依據排程資料 StopViewer: displayStopId: 車站ID:{stopId} findSchedule: 依日期尋找排程 - flexStop: 這是彈性車站。車輛將依要求在彈性區域讓乘客上下車。您在此地區可能需要事先呼叫服務。 forStop: 針對 {stopName} + flexStop: 這是彈性車站。車輛將依要求在彈性區域讓乘客上下車。您在此地區可能需要事先呼叫服務。 header: 車站檢視器 loadingText: 正在載入車站…… noStopsFound: 此日期找不到車站時間。 - operatorLogoAriaLabel: "{operatorName} 車站:" + operatorLogoAriaLabel: '{operatorName} 車站:' schedule: 時程表 timezoneWarning: 出發時間是以{timezoneCode}顯示。 titleBarStopId: 車站{stopId} @@ -478,30 +338,28 @@ components: viewSchedule: 檢視時程 zoomToStop: 縮放到車站 SubNav: - languageSelector: 選取語言 - languages: 語言 - myAccount: 我的帳戶 selectALanguage: 選取語言 settings: 設定 trips: 行程 userMenu: 設定檔選項 + languageSelector: 選取語言 + languages: 語言 + myAccount: 我的帳戶 SwitchButton: defaultContent: 切換 switchLocations: 切換位置 TermsOfUsePane: - confirmDeletionPrompt: | - 如果移除您對儲存歷史行程的同意,將導致系統刪除您的行程記錄。確定要繼續嗎? mustAgreeToTerms: 您必須同意服務條款才能繼續。 - termsOfServiceStatement: | - 我確認我已年滿18歲,且我以閱讀並同意使用行程規劃器的服務條款。 - termsOfStorageStatement: > - 選擇性:我同意行程規劃器儲存我的歷史規劃行程,以改善我所在地區的公共交通服務。更多資訊…… + termsOfServiceStatement: "我確認我已年滿18歲,且我以閱讀並同意使用行程規劃器的服務條款。\n" + termsOfStorageStatement: "選擇性:我同意行程規劃器儲存我的歷史規劃行程,以改善我所在地區的公共交通服務。更多資訊……\n" + confirmDeletionPrompt: "如果移除您對儲存歷史行程的同意,將導致系統刪除您的行程記錄。確定要繼續嗎?\n" TransitVehicleOverlay: + travelingAt: 以{milesPerHour}前進 + vehicleName: 車輛{vehicleNumber} in_transit_to: 下一個車站:{stop} incoming_at: 即將抵達:{stop} stopped_at: 車門將在{stop}開啟 - travelingAt: 以{milesPerHour}前進 - vehicleName: 車輛{vehicleNumber} TripBasicsPane: checkingItineraryExistence: 正在檢查一週中每一天是否存在預定行程…… tripDaysPrompt: 您在哪些日子使用此行程? @@ -512,20 +370,15 @@ components: unsavedChangesNewTrip: 您尚未儲存新的行程。如果離開,該行程就會遺失。 TripNotificationsPane: advancedSettings: 進階設定 + notifyViaChannelWhen: 以下情形時透過{channel}通知我: altRouteRecommended: 建議使用備選方案路線或轉乘點 delaysAboveThreshold: 延誤或中斷超過 - howToReceiveAlerts: | - 若要針對您儲存的行程接收警示,請在帳戶設定中啟用通知,然後嘗試再次儲存行程。 + howToReceiveAlerts: "若要針對您儲存的行程接收警示,請在帳戶設定中啟用通知,然後嘗試再次儲存行程。\n" monitorThisTrip: 開始前先監控此行程: notificationsTurnedOff: 您的帳戶已關閉通知。 - notifyViaChannelWhen: 以下情形時透過{channel}通知我: oneHour: 1小時 realtimeAlertFlagged: 我的旅程上有一個即時警示標記 - timeBefore: "{time} 之前" - TripStatus: - alerts: "{alerts}個警示!" - deleteTrip: 刪除行程 - planNewTrip: 規劃新行程 + timeBefore: '{time} 之前' TripStatusRenderers: active: delayedHeading: 行程正在進行中,而且已延誤{formattedDuration}! @@ -534,23 +387,21 @@ components: noDataHeading: 行程正在進行中 (沒有可用的即時更新)。 onTimeHeading: 行程正在進行中,而且大約準時。 base: - lastCheckedDefaultText: 最後檢查時間未知 lastCheckedText: 最後檢查時間:{formattedDuration}前 togglePause: 暫停 tripIsNotSnoozed: 今天整天延遲 tripIsSnoozed: 取消延遲行程分析 unknownState: 未知的行程狀態 untogglePause: 繼續 + lastCheckedDefaultText: 最後檢查時間未知 inactive: description: 繼續行程監控,以查看更新的狀態 heading: 行程監控已暫停 nextTripNotPossible: - description: | - 行程規劃器無法找到您今天的行程。請嘗試重新規劃預定行程,以尋找替代路線。 + description: "行程規劃器無法找到您今天的行程。請嘗試重新規劃預定行程,以尋找替代路線。\n" heading: 行程今天不可行 noLongerPossible: - description: | - 行程規劃器無法找到您在週間任一選取日期的行程。請嘗試重新規劃預定行程,以尋找替代路線。 + description: "行程規劃器無法找到您在週間任一選取日期的行程。請嘗試重新規劃預定行程,以尋找替代路線。\n" heading: 行程已不再可行 notCalculated: awaiting: 正在等待計算…… @@ -560,112 +411,229 @@ components: description: 取消延遲行程監控,以查看更新的狀態。 heading: 已為今天延遲行程監控 upcoming: - nextTripBegins: >- - 下一個行程將在{tripDatetime, date, ::eeeee yyyyMMdd}的{tripDatetime, time, - short}開始。 - tripBegins: >- - 行程預計在{tripStart, time, short}開始。(即時監控將在{monitoringStart, time, - short}開始。) + nextTripBegins: 下一個行程將在{tripDatetime, date, ::eeeee yyyyMMdd}的{tripDatetime, + time, short}開始。 + tripBegins: 行程預計在{tripStart, time, short}開始。(即時監控將在{monitoringStart, time, short}開始。) tripStartIsDelayed: 行程開始時間延誤${duration}! tripStartIsEarly: 行程開始時間比預期提早${duration}! tripStartsSoonNoUpdates: 行程即將開始 (沒有可用的即時更新)。 tripStartsSoonOnTime: 行程即將開始且大約準時。 TripSummary: - arriveAt: "抵達 " - leaveAt: "離開點 " + leaveAt: '離開點 ' + arriveAt: '抵達 ' TripSummaryPane: happensOnDays: 發生於:{days} notifications: 預定出發前{leadTimeInMinutes}分鐘 notificationsDisabled: 通知已停用 TripTools: copyLink: 複製連結 + reportEmailSubject: 回報OpenTripPlanner的問題 header: 行程工具 linkCopied: 已複製 - reportEmailSubject: 回報OpenTripPlanner的問題 - reportEmailTemplate: | - "*** 給使用者的指示 *** - 此功能可讓您透過電子郵件寄送報告給網站管理員進行審查。 - 請填寫下方的提示部分,並使用您平常的電子郵件程式寄送。 - - *** 請填寫以下內容 *** - - 遇到的問題: - - 您想要使用的行程類型 (例如步行+公共交通、自行車+公共交通、汽車+公共交通): - - *** 技術詳細資訊 ***" + reportEmailTemplate: "\"*** 給使用者的指示 ***\n此功能可讓您透過電子郵件寄送報告給網站管理員進行審查。\n 請填寫下方的提示部分,並使用您平常的電子郵件程式寄送。\n + \n*** 請填寫以下內容 *** \n \n 遇到的問題:\n \n 您想要使用的行程類型 (例如步行+公共交通、自行車+公共交通、汽車+公共交通):\n + \n*** 技術詳細資訊 ***\"\n" reportIssue: 回報問題 TripViewer: + listOfRouteStops: 此路線上的車站清單 + startOfTrip: 行程從這裡開始 accessible: 無障礙 bicyclesAllowed: 已允許 endOfTrip: 行程到此結束 header: 行程檢視器 - listOfRouteStops: 此路線上的車站清單 routeHeader: 路線:{routeShortName} {routeLongName} - startOfTrip: 行程從這裡開始 tripDescription: 在 {boardAtStop} 上車,在 {disembarkAtStop} 下車 viewStop: 檢視 UserAccountScreen: confirmDelete: 您確定要刪除使用者帳戶嗎?一旦刪除,就無法復原。 errorUpdatingProfile: 更新設定檔時發生錯誤。 fieldUpdated: 此設定已更新。 + updating: 正在更新 fields: storeTripHistory: 儲存行程歷史紀錄 - updating: 正在更新 UserSettings: - confirmDeletion: 您有儲存的最近的搜尋和/或地點。停用儲存最近的地點/搜尋將移除這些項目。是否要繼續? - favoriteStops: 最愛的車站 - myPreferences: 我的偏好 mySavedPlaces: 我的已儲存地點 (管理) noFavoriteStops: 沒有最愛的車站 recentPlaces: 最近的地點 - recentSearchSummary: "{mode},從{from}到{to}" + recentSearchSummary: '{mode},從{from}到{to}' recentSearches: 最近的搜尋 rememberSearches: 要記住最近的搜尋/地點嗎? stopId: 車站ID:{stopId} - storageDisclaimer: > - 您選擇儲存的任何偏好、地點或設定都將儲存在您瀏覽器的本機儲存空間。TriMet無法存取有關您住家、工作或任何其他位置的資訊。您可以隨時選擇不要記住最近的地點/搜尋,並清除您的已儲存住家/工作位置和最愛的車站。 - UserTripSettings: - forgetOptions: 忘記我的選項 - rememberOptions: 記住行程選項 - restoreDefaults: 還原預設值 - restoreMyDefaults: 還原我的預設值 + confirmDeletion: 您有儲存的最近的搜尋和/或地點。停用儲存最近的地點/搜尋將移除這些項目。是否要繼續? + favoriteStops: 最愛的車站 + myPreferences: 我的偏好 + storageDisclaimer: "您選擇儲存的任何偏好、地點或設定都將儲存在您瀏覽器的本機儲存空間。TriMet無法存取有關您住家、工作或任何其他位置的資訊。您可以隨時選擇不要記住最近的地點/搜尋,並清除您的已儲存住家/工作位置和最愛的車站。\n" VerifyEmailPane: - emailIsVerified: 我的電子郵件已驗證! - instructions1: | - 請查看您的電子郵件收件匣,並前往信件中的連結以驗證您的電子郵件地址,再完成帳戶設定。 instructions2: 驗證之後,請按下方按鈕以繼續。 resendVerification: 重新寄送驗證電子郵件 + emailIsVerified: 我的電子郵件已驗證! + instructions1: "請查看您的電子郵件收件匣,並前往信件中的連結以驗證您的電子郵件地址,再完成帳戶設定。\n" ViewSwitcher: nearby: 檢視附近 switcher: 切換工具 WelcomeScreen: prompt: 您想要去哪裡? + A11yPrefs: + accessibilityRoutingByDefault: 預設優先選擇無障礙行程 + AddPlaceButton: + addPlace: 新增地點 + needOriginDestination: 定義起點/目的地以新增中間地點 + tooManyPlaces: 已達到中間地點數量上限 + AdvancedOptions: + bannedRoutes: 選取禁止的路線…… + preferredRoutes: 選取首選路線…… + BatchSearchScreen: + header: 規劃您的行程 + modeSelectorLabel: 選取出行模式 + BatchSettings: + destination: 目的地 + invalidModeSelection: 無法使用所選模式規劃行程。嘗試將公共交通納入您的模式選取內容。 + origin: 起點 + planTripTooltip: 規劃行程 + validationMessage: 請定義以下欄位以規劃行程:{issues} + CallTakerPanel: + advancedOptions: 進階選項 + groupSize: 群組大小: + intermediateDestination: 輸入中間目的地 + DateTimeScreen: + header: 設定日期/時間 + DeleteUser: + deleteMyAccount: 刪除我的帳戶 + ErrorMessage: + header: 無法規劃行程 + warning: 警告 + FormNavigationButtons: + ariaLabel: 表單導航 + ItinerarySummary: + itineraryDetails: 路線詳細資訊 + minMaxFare: '{minTotalFare} - {maxTotalFare}' + LocationSearch: + enterLocation: 輸入位置 + setDestination: 設定目的地 + setOrigin: 設定起點 + MapillaryFrame: + title: 街道圖像 + MobileOptions: + header: 設定搜尋選項 + NarrativeItinerariesHeader: + changeSortDir: 變更排序方向 + howToFindResults: 要檢視結果,請參閱下方「找到的預定行程」標題。 + itinerariesFound: '{itineraryNum}個預定行程已找到' + numIssues: '{issueNum}個問題' + resultsSortedBy: 行程結果目前依{sortSelected}排序。如需變更結果的排序方式,請使用下方「排序結果」按鈕。 + searching: 正在尋找您的選項…… + selectArrivalTime: 抵達時間 + selectBest: 最佳選項 + selectCost: 費用 + selectDepartureTime: 出發時間 + selectDuration: 期間 + selectWalkTime: 步行時間 + sortResults: 對結果排序 + viewAll: 檢視所有選項 + NavLoginButton: + help: 說明 + myAccount: 我的帳戶 + signIn: 登入 + signOut: 登出 + NearbyView: + bikeRentalStation: 自行車租車站 + bikesAvailable: '{bikesAvailable}輛自行車可用' + companyBicycle: '{company} 自行車' + companyScooter: '{company} 滑板車' + error: 載入鄰近設施時發生錯誤。 + header: 檢視附近 + spacesAvailable: '{spacesAvailable} 有未佔用的空間' + NewAccountWizard: + createNewAccount: 建立新帳戶 + finish: 帳戶設定完成! + notifications: 通知偏好 + places: 新增您的位置 + verify: 驗證您的電子郵件地址 + NotFound: + description: 您要求的內容無法使用。 + header: 找不到內容 + NotificationPrefsPane: + devicesRegistered: '{count}個裝置 已註冊' + noDeviceForPush: 使用行動應用程式註冊您的裝置以存取推播通知。 + notificationChannelPrompt: 透過以下方式接收有關您已儲存之行程的通知: + Place: + deleteThisPlace: 刪除此地點 + enterAlert: "請在表單內輸入起點/目的地 (或按一下地圖來設定),然後按一下產生的標記以設定為{placeType}位置。\n" + viewStop: 檢視車站 + RealtimeAnnotation: + delaysShownInResults: "您的行程結果已依據即時資訊調整。在一般情況下,此形成使用以下路線會需要{normalDuration}:{routes}。\n" + serviceUpdate: 服務更新 + ResultsError: + backToSearch: 返回搜尋 + RouteDetails: + headsignTo: '{headsign} ({lastStop})' + moreDetails: 更多詳細資訊 + operatedBy: 由{agencyName}營運 + selectADirection: 選取方向…… + stopsTo: 朝向 + SaveTripButton: + cantSaveText: 無法儲存 + cantSaveTooltip: 僅能監控包括公共交通且沒有租賃或叫車服務的預定行程。 + saveTripText: 儲存行程 + signInText: 登入以儲存行程 + signInTooltip: 請登入以儲存行程。 + SavedTripEditor: + editSavedTrip: 編輯儲存的行程 + saveNewTrip: 儲存新行程 + tripInformation: 行程資訊 + tripNotFound: 找不到行程 + tripNotFoundDescription: 很抱歉,找不到請求的行程。 + tripNotifications: 行程通知 + SavedTripScreen: + itineraryLoaded: 路線已載入 + itineraryLoading: 正在載入路線 + tooManyTrips: "您已經達到五個儲存行程上限。請將未使用的行程從已儲存行程中移除,然後再試一次。\n" + tripNameAlreadyUsed: 已經有另一個儲存的行程使用此名稱。請選擇其他名稱。 + tripNameRequired: 請輸入行程名稱。 + SequentialPaneDisplay: + stepNumber: 第{step}步,共{total}步 + SessionTimeout: + body: 您的工作階段將在一分鐘內失效。請按下「Continue Session」(繼續工作階段) 以保留搜尋。 + header: 工作階段即將逾時! + keepSession: 繼續工作階段 + StopTimeCell: + imminentArrival: 到 + realtime: 依據即時資料 + scheduled: 依據排程資料 + TripStatus: + alerts: '{alerts}個警示!' + deleteTrip: 刪除行程 + planNewTrip: 規劃新行程 + UserTripSettings: + forgetOptions: 忘記我的選項 + rememberOptions: 記住行程選項 + restoreDefaults: 還原預設值 + restoreMyDefaults: 還原我的預設值 config: accessModes: - bicycle: 公共交通+個人自行車 - bicycle_rent: 公共交通+共享自行車 car_hail: 叫車服務 car_park: 停車轉乘 micromobility: 公共交通+個人滑板車 micromobility_rent: 公共交通+電動滑板車租賃 + bicycle: 公共交通+個人自行車 + bicycle_rent: 公共交通+共享自行車 bicycleModes: bicycle: 自有自行車 bicycle_rent: 自行車共享 + micromobilityModes: + micromobility: 自有電動滑板車 + micromobility_rent: 租賃電動滑板車 flex: both: 請查看預定行程底部以瞭解詳細資訊 call-ahead: 致電預約 continuous-dropoff: 與業者溝通車站事宜 flex-service: 彈性服務 flex-service-colon: 彈性服務: - micromobilityModes: - micromobility: 自有電動滑板車 - micromobility_rent: 租賃電動滑板車 util: state: + noTripFoundReason: 指定的最大距離內或指定的時間可能沒有公共交通服務,或是您的起點或終點可能無法安全前往。 + noTripFoundWithReason: '{noTripFound} {reason}' errorPlanningTrip: 規劃行程時發生錯誤。 - networkUnavailable: "{network}網路目前無法使用。" + networkUnavailable: '{network}網路目前無法使用。' noTripFound: 找不到行程。 noTripFoundForMode: 找不到{modes}的行程。 - noTripFoundReason: 指定的最大距離內或指定的時間可能沒有公共交通服務,或是您的起點或終點可能無法安全前往。 - noTripFoundWithReason: "{noTripFound} {reason}" From 4d1792ad48965d9cddfeb21025e42ff2da54edf5 Mon Sep 17 00:00:00 2001 From: binh-dam-ibigroup <56846598+binh-dam-ibigroup@users.noreply.github.com> Date: Fri, 6 Sep 2024 08:45:47 -0400 Subject: [PATCH 13/20] fix: Add new originalMode field to legs. --- lib/actions/user.js | 6 ++++++ lib/reducers/create-otp-reducer.js | 15 ++------------- lib/util/itinerary.tsx | 26 ++++++++++++++++++++++++++ 3 files changed, 34 insertions(+), 13 deletions(-) diff --git a/lib/actions/user.js b/lib/actions/user.js index ea2069d36..7c038abb9 100644 --- a/lib/actions/user.js +++ b/lib/actions/user.js @@ -5,6 +5,7 @@ import isEmpty from 'lodash.isempty' import qs from 'qs' import toast from 'react-hot-toast' +import { applyRouteModeOverrides } from '../util/itinerary' import { convertToPlace, getPersistenceMode, @@ -128,6 +129,11 @@ export function fetchMonitoredTrips() { 'GET' ) if (status === 'success') { + const { routeModeOverrides } = getState().otp.config + trips.data.forEach((trip) => { + applyRouteModeOverrides(trip.itinerary, routeModeOverrides) + }) + dispatch(setCurrentUserMonitoredTrips(trips.data)) } } diff --git a/lib/reducers/create-otp-reducer.js b/lib/reducers/create-otp-reducer.js index 0bc899936..fa0f082ef 100644 --- a/lib/reducers/create-otp-reducer.js +++ b/lib/reducers/create-otp-reducer.js @@ -6,7 +6,7 @@ import deepmerge from 'deepmerge' import objectPath from 'object-path' import update from 'immutability-helper' -import { checkForRouteModeOverride } from '../util/config' +import { applyRouteModeOverrides } from '../util/itinerary' import { FETCH_STATUS, PERSIST_TO_LOCAL_STORAGE, @@ -311,18 +311,7 @@ function createOtpReducer(config) { response.requestId = requestId response.plan.itineraries = response.plan?.itineraries?.map( (itinerary) => { - itinerary.legs = itinerary.legs.map((leg) => { - if (leg.routeId) { - leg.mode = checkForRouteModeOverride( - { - id: leg.routeId, - mode: leg.mode - }, - state.config?.routeModeOverrides - ) - } - return leg - }) + applyRouteModeOverrides(itinerary, state.config.routeModeOverrides) return itinerary } ) diff --git a/lib/util/itinerary.tsx b/lib/util/itinerary.tsx index 6cebee5b3..6b05bc3c8 100644 --- a/lib/util/itinerary.tsx +++ b/lib/util/itinerary.tsx @@ -6,6 +6,7 @@ import hash from 'object-hash' import memoize from 'lodash.memoize' import { AppConfig, CO2Config } from './config-types' +import { checkForRouteModeOverride } from './config' import { WEEKDAYS, WEEKEND_DAYS } from './monitored-trip' export interface ItineraryStartTime { @@ -449,3 +450,28 @@ export function addSortingCosts( totalFare } } + +interface LegWithOriginalMode extends Leg { + originalMode?: string +} + +/** Applies route mode overrides to a list of itineraries. */ +export function applyRouteModeOverrides( + itinerary: Itinerary, + routeModeOverrides: Record +): void { + itinerary.legs.forEach((leg: LegWithOriginalMode) => { + // Use OTP2 leg route first, fallback on legacy leg routeId. + const routeId = leg.route?.id || leg.routeId + if (routeId) { + leg.originalMode = leg.mode + leg.mode = checkForRouteModeOverride( + { + id: routeId, + mode: leg.mode + }, + routeModeOverrides + ) + } + }) +} From 5428f46e51811cee31a201002e9be258d6b91517 Mon Sep 17 00:00:00 2001 From: binh-dam-ibigroup <56846598+binh-dam-ibigroup@users.noreply.github.com> Date: Fri, 6 Sep 2024 08:55:42 -0400 Subject: [PATCH 14/20] refactor(itinerary): Fix comment --- lib/util/itinerary.tsx | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/lib/util/itinerary.tsx b/lib/util/itinerary.tsx index 6b05bc3c8..a76aa4466 100644 --- a/lib/util/itinerary.tsx +++ b/lib/util/itinerary.tsx @@ -455,7 +455,7 @@ interface LegWithOriginalMode extends Leg { originalMode?: string } -/** Applies route mode overrides to a list of itineraries. */ +/** Applies route mode overrides to an itinerary. */ export function applyRouteModeOverrides( itinerary: Itinerary, routeModeOverrides: Record From 8bfd731c7e8db57511867ae95c2e6b11da0df6d5 Mon Sep 17 00:00:00 2001 From: binh-dam-ibigroup <56846598+binh-dam-ibigroup@users.noreply.github.com> Date: Fri, 6 Sep 2024 10:04:57 -0400 Subject: [PATCH 15/20] fix(SavedTripScreen): Remove route mode overrides when saving trip. --- .../user/monitored-trip/saved-trip-screen.js | 14 +++++++++++--- lib/util/itinerary.tsx | 13 +++++++++++++ 2 files changed, 24 insertions(+), 3 deletions(-) diff --git a/lib/components/user/monitored-trip/saved-trip-screen.js b/lib/components/user/monitored-trip/saved-trip-screen.js index c46c3184a..cb7f61ebe 100644 --- a/lib/components/user/monitored-trip/saved-trip-screen.js +++ b/lib/components/user/monitored-trip/saved-trip-screen.js @@ -11,8 +11,11 @@ import * as formActions from '../../../actions/form' import * as uiActions from '../../../actions/ui' import * as userActions from '../../../actions/user' import { arrayToDayFields } from '../../../util/monitored-trip' +import { + copyAndRemoveRouteModeOverrides, + getItineraryDefaultMonitoredDays +} from '../../../util/itinerary' import { getActiveItineraries, getActiveSearch } from '../../../util/state' -import { getItineraryDefaultMonitoredDays } from '../../../util/itinerary' import { RETURN_TO_CURRENT_ROUTE } from '../../../util/ui' import { TRIPS_PATH } from '../../../util/constants' import AccountPage from '../account-page' @@ -67,7 +70,7 @@ class SavedTripScreen extends Component { departureVarianceMinutesThreshold: 5, excludeFederalHolidays: true, isActive: true, - itinerary: otherItineraryProps, + itinerary: copyAndRemoveRouteModeOverrides(otherItineraryProps), leadTimeInMinutes: 30, otp2QueryParams, // when creating a monitored trip, the query params will be changed on the @@ -89,8 +92,13 @@ class SavedTripScreen extends Component { */ _updateMonitoredTrip = (monitoredTrip) => { const { createOrUpdateUserMonitoredTrip, intl, isCreating } = this.props + const tripToSave = { + ...monitoredTrip, + itinerary: copyAndRemoveRouteModeOverrides(monitoredTrip.itinerary) + } + createOrUpdateUserMonitoredTrip( - monitoredTrip, + tripToSave, isCreating, undefined, undefined, diff --git a/lib/util/itinerary.tsx b/lib/util/itinerary.tsx index a76aa4466..be5bfe172 100644 --- a/lib/util/itinerary.tsx +++ b/lib/util/itinerary.tsx @@ -475,3 +475,16 @@ export function applyRouteModeOverrides( } }) } + +/** Remove mode overrides from an itinerary */ +export function copyAndRemoveRouteModeOverrides( + itinerary: Itinerary +): Itinerary { + return { + ...itinerary, + legs: itinerary.legs.map((leg) => ({ + ...leg, + mode: leg.originalMode || leg.mode + })) + } +} From cf68453eb944d70ed90ced8747fccd1d4d9251b7 Mon Sep 17 00:00:00 2001 From: binh-dam-ibigroup <56846598+binh-dam-ibigroup@users.noreply.github.com> Date: Fri, 6 Sep 2024 10:05:44 -0400 Subject: [PATCH 16/20] fix(DefaultRouteRenderer): Prefer OTP2 leg route data. --- lib/components/narrative/metro/default-route-renderer.tsx | 8 ++++++-- 1 file changed, 6 insertions(+), 2 deletions(-) diff --git a/lib/components/narrative/metro/default-route-renderer.tsx b/lib/components/narrative/metro/default-route-renderer.tsx index c16d100ad..b1733073d 100644 --- a/lib/components/narrative/metro/default-route-renderer.tsx +++ b/lib/components/narrative/metro/default-route-renderer.tsx @@ -37,10 +37,14 @@ const DefaultRouteRenderer = ({ leg, style }: RouteRendererProps): JSX.Element => { - const routeTitle = leg.routeShortName || leg.routeLongName + const routeTitle = + leg.route?.shortName || + leg.route?.longName || + leg.routeShortName || + leg.routeLongName return ( Date: Fri, 6 Sep 2024 11:30:59 -0400 Subject: [PATCH 17/20] refactor(itinerary): Fix types --- lib/util/itinerary.tsx | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/lib/util/itinerary.tsx b/lib/util/itinerary.tsx index be5bfe172..e77cf8dec 100644 --- a/lib/util/itinerary.tsx +++ b/lib/util/itinerary.tsx @@ -462,7 +462,7 @@ export function applyRouteModeOverrides( ): void { itinerary.legs.forEach((leg: LegWithOriginalMode) => { // Use OTP2 leg route first, fallback on legacy leg routeId. - const routeId = leg.route?.id || leg.routeId + const routeId = typeof leg.route === 'object' ? leg.route.id : leg.routeId if (routeId) { leg.originalMode = leg.mode leg.mode = checkForRouteModeOverride( @@ -482,7 +482,7 @@ export function copyAndRemoveRouteModeOverrides( ): Itinerary { return { ...itinerary, - legs: itinerary.legs.map((leg) => ({ + legs: itinerary.legs.map((leg: LegWithOriginalMode) => ({ ...leg, mode: leg.originalMode || leg.mode })) From 54c72078c3d884f1784ec6ff2a27b5a94500b3bb Mon Sep 17 00:00:00 2001 From: binh-dam-ibigroup <56846598+binh-dam-ibigroup@users.noreply.github.com> Date: Fri, 6 Sep 2024 11:32:22 -0400 Subject: [PATCH 18/20] refactor(DefaultRouteRenderer): Fix types --- .../narrative/metro/default-route-renderer.tsx | 12 +++++++----- 1 file changed, 7 insertions(+), 5 deletions(-) diff --git a/lib/components/narrative/metro/default-route-renderer.tsx b/lib/components/narrative/metro/default-route-renderer.tsx index b1733073d..728ae9036 100644 --- a/lib/components/narrative/metro/default-route-renderer.tsx +++ b/lib/components/narrative/metro/default-route-renderer.tsx @@ -38,13 +38,15 @@ const DefaultRouteRenderer = ({ style }: RouteRendererProps): JSX.Element => { const routeTitle = - leg.route?.shortName || - leg.route?.longName || - leg.routeShortName || - leg.routeLongName + typeof leg.route === 'object' + ? leg.route.shortName || leg.route.longName + : leg.routeShortName || leg.routeLongName return ( Date: Fri, 13 Sep 2024 09:05:43 -0400 Subject: [PATCH 19/20] refactor(actions/user): Rename function and tweak comments --- lib/actions/user.js | 13 ++++++++++--- 1 file changed, 10 insertions(+), 3 deletions(-) diff --git a/lib/actions/user.js b/lib/actions/user.js index 7c038abb9..60fd42d47 100644 --- a/lib/actions/user.js +++ b/lib/actions/user.js @@ -175,8 +175,12 @@ function convertRequestToSearch(config) { } // TODO move to a utils file -function areRequestsSame(qp1, qp2) { - // Everything except modes. +/** + * Determines whether two GraphQL sets of variables are for the same trip request/search. + * Modes are excluded from the comparison because the UI triggers multiple queries + * with the same GraphQL variables but with different combinations of modes. + */ +function areRequestsSameExceptModes(qp1, qp2) { // Exclude banned/preferred/unpreferred at this time. return ( qp1.bikeReluctance === qp2.bikeReluctance && @@ -201,7 +205,10 @@ function removeDuplicateRequests(filtered, tripRequest) { if (filtered.length === 0) { filtered.push(tripRequest) } else if ( - !areRequestsSame(filtered[filtered.length - 1].query, tripRequest.query) + !areRequestsSameExceptModes( + filtered[filtered.length - 1].query, + tripRequest.query + ) ) { filtered.push(tripRequest) } else { From ed5f0ad46b92ac710b956ccf09ad6b3d9ebf4054 Mon Sep 17 00:00:00 2001 From: binh-dam-ibigroup <56846598+binh-dam-ibigroup@users.noreply.github.com> Date: Fri, 13 Sep 2024 09:32:56 -0400 Subject: [PATCH 20/20] refactor(actions/user): Use lodash.isEqual for comparison. --- lib/actions/user.js | 22 +++++++--------------- 1 file changed, 7 insertions(+), 15 deletions(-) diff --git a/lib/actions/user.js b/lib/actions/user.js index 60fd42d47..1a1e9e993 100644 --- a/lib/actions/user.js +++ b/lib/actions/user.js @@ -2,6 +2,7 @@ import { createAction } from 'redux-actions' import clone from 'clone' import coreUtils from '@opentripplanner/core-utils' import isEmpty from 'lodash.isempty' +import isEqual from 'lodash.isequal' import qs from 'qs' import toast from 'react-hot-toast' @@ -174,27 +175,18 @@ function convertRequestToSearch(config) { } } -// TODO move to a utils file /** * Determines whether two GraphQL sets of variables are for the same trip request/search. + * * Modes are excluded from the comparison because the UI triggers multiple queries * with the same GraphQL variables but with different combinations of modes. + * Modes exclusion also means that if someone makes the same search with different mode settings + * (e.g. excludes/adds transit modes), that search will also be combined with the previous ones. */ function areRequestsSameExceptModes(qp1, qp2) { - // Exclude banned/preferred/unpreferred at this time. - return ( - qp1.bikeReluctance === qp2.bikeReluctance && - qp1.carReluctance === qp2.carReluctance && - qp1.date === qp2.date && - qp1.departArrive === qp2.departArrive && - qp1.from?.name === qp2.from?.name && - qp1.departArrive === qp2.departArrive && - qp1.time === qp2.time && - qp1.to?.name === qp2.to?.name && - qp1.walkReluctance === qp2.walkReluctance && - qp1.walkSpeed === qp2.walkSpeed && - qp1.wheelchair === qp2.wheelchair - ) + const { modes: modes1, ...otherParams1 } = qp1 + const { modes: modes2, ...otherParams2 } = qp2 + return isEqual(otherParams1, otherParams2) } /**