diff --git a/.github/workflows/codespell.yml b/.github/workflows/codespell.yml
index ff497a107..f40c59996 100644
--- a/.github/workflows/codespell.yml
+++ b/.github/workflows/codespell.yml
@@ -12,6 +12,7 @@ jobs:
- uses: codespell-project/actions-codespell@master
with:
check_filenames: true
- # The a11y test file has a false positive and the ignore list does not work
+ # skip git, yarn, and i18n non-english resources.
+ # Also, the a11y test file has a false positive and the ignore list does not work
# see https://github.com/opentripplanner/otp-react-redux/pull/436/checks?check_run_id=3369380014
- skip: ./.git,yarn.lock,./a11y/a11y.test.js
+ skip: ./.git,yarn.lock,./a11y/a11y.test.js,./i18n/fr*
diff --git a/__tests__/components/viewers/__snapshots__/stop-viewer.js.snap b/__tests__/components/viewers/__snapshots__/stop-viewer.js.snap
index 81e21e621..e83ba8e43 100644
--- a/__tests__/components/viewers/__snapshots__/stop-viewer.js.snap
+++ b/__tests__/components/viewers/__snapshots__/stop-viewer.js.snap
@@ -235,12 +235,10 @@ exports[`components > viewers > stop viewer should render countdown times after
@@ -294,12 +292,10 @@ exports[`components > viewers > stop viewer should render countdown times after
@@ -494,7 +490,7 @@ exports[`components > viewers > stop viewer should render countdown times after
viewers > stop viewer should render countdown times after
"marginRight": 2,
}
}
- type="clock-o"
>
viewers > stop viewer should render countdown times after
"marginRight": 2,
}
}
- type="clock-o"
/>
@@ -944,12 +938,10 @@ exports[`components > viewers > stop viewer should render countdown times after
@@ -1030,12 +1022,10 @@ exports[`components > viewers > stop viewer should render countdown times after
className=""
fixedWidth={true}
name="refresh"
- type="refresh"
>
@@ -1189,12 +1179,10 @@ exports[`components > viewers > stop viewer should render countdown times for st
@@ -1248,12 +1236,10 @@ exports[`components > viewers > stop viewer should render countdown times for st
@@ -1448,7 +1434,7 @@ exports[`components > viewers > stop viewer should render countdown times for st
viewers > stop viewer should render countdown times for st
"marginRight": 2,
}
}
- type="clock-o"
>
viewers > stop viewer should render countdown times for st
"marginRight": 2,
}
}
- type="clock-o"
/>
@@ -1709,12 +1693,10 @@ exports[`components > viewers > stop viewer should render countdown times for st
@@ -1795,12 +1777,10 @@ exports[`components > viewers > stop viewer should render countdown times for st
className=""
fixedWidth={true}
name="refresh"
- type="refresh"
>
@@ -2053,12 +2033,10 @@ exports[`components > viewers > stop viewer should render times after midnight w
@@ -2112,12 +2090,10 @@ exports[`components > viewers > stop viewer should render times after midnight w
@@ -2312,7 +2288,7 @@ exports[`components > viewers > stop viewer should render times after midnight w
viewers > stop viewer should render times after midnight w
"marginRight": 2,
}
}
- type="clock-o"
>
viewers > stop viewer should render times after midnight w
"marginRight": 2,
}
}
- type="clock-o"
/>
@@ -2771,12 +2745,10 @@ exports[`components > viewers > stop viewer should render times after midnight w
@@ -2857,12 +2829,10 @@ exports[`components > viewers > stop viewer should render times after midnight w
className=""
fixedWidth={true}
name="refresh"
- type="refresh"
>
@@ -3373,12 +3343,10 @@ exports[`components > viewers > stop viewer should render with OTP transit index
@@ -3432,12 +3400,10 @@ exports[`components > viewers > stop viewer should render with OTP transit index
@@ -3632,7 +3598,7 @@ exports[`components > viewers > stop viewer should render with OTP transit index
viewers > stop viewer should render with OTP transit index
"marginRight": 2,
}
}
- type="clock-o"
>
viewers > stop viewer should render with OTP transit index
"marginRight": 2,
}
}
- type="clock-o"
/>
@@ -4347,12 +4311,10 @@ exports[`components > viewers > stop viewer should render with OTP transit index
@@ -4549,7 +4511,6 @@ exports[`components > viewers > stop viewer should render with OTP transit index
"marginRight": 2,
}
}
- type="clock-o"
>
viewers > stop viewer should render with OTP transit index
"marginRight": 2,
}
}
- type="clock-o"
/>
@@ -4606,12 +4566,10 @@ exports[`components > viewers > stop viewer should render with OTP transit index
@@ -4808,7 +4766,6 @@ exports[`components > viewers > stop viewer should render with OTP transit index
"marginRight": 2,
}
}
- type="clock-o"
>
viewers > stop viewer should render with OTP transit index
"marginRight": 2,
}
}
- type="clock-o"
/>
@@ -4865,12 +4821,10 @@ exports[`components > viewers > stop viewer should render with OTP transit index
@@ -5121,7 +5075,6 @@ exports[`components > viewers > stop viewer should render with OTP transit index
"marginRight": 2,
}
}
- type="clock-o"
>
viewers > stop viewer should render with OTP transit index
"marginRight": 2,
}
}
- type="clock-o"
/>
@@ -5178,12 +5130,10 @@ exports[`components > viewers > stop viewer should render with OTP transit index
@@ -5264,12 +5214,10 @@ exports[`components > viewers > stop viewer should render with OTP transit index
className=""
fixedWidth={true}
name="refresh"
- type="refresh"
>
@@ -5775,12 +5723,10 @@ exports[`components > viewers > stop viewer should render with TriMet transit in
@@ -5834,12 +5780,10 @@ exports[`components > viewers > stop viewer should render with TriMet transit in
@@ -6034,7 +5978,7 @@ exports[`components > viewers > stop viewer should render with TriMet transit in
viewers > stop viewer should render with TriMet transit in
"marginRight": 2,
}
}
- type="clock-o"
>
viewers > stop viewer should render with TriMet transit in
"marginRight": 2,
}
}
- type="clock-o"
/>
@@ -6746,12 +6688,10 @@ exports[`components > viewers > stop viewer should render with TriMet transit in
@@ -6832,12 +6772,10 @@ exports[`components > viewers > stop viewer should render with TriMet transit in
className=""
fixedWidth={true}
name="refresh"
- type="refresh"
>
@@ -6925,12 +6863,10 @@ exports[`components > viewers > stop viewer should render with initial stop id a
diff --git a/i18n/en-US.yml b/i18n/en-US.yml
index f8a220fad..7e5711a5e 100644
--- a/i18n/en-US.yml
+++ b/i18n/en-US.yml
@@ -28,16 +28,88 @@ _name: English
components:
DefaultItinerary:
clickDetails: Click to view details
- # Use ordered placeholders for the departure-arrival string
- # (this will accommodate right-to-left languages by swapping the order in this string).
- departureArrivalTimes: "{startTime}—{endTime}"
# Use ordered placeholders when multiple modes are involved
# (this will accommodate right-to-left languages by swapping the order/separator in this string).
multiModeSummary: "{accessMode} to {transitMode}"
- # If trip is less than one hour only display the minutes.
- tripDurationFormatZeroHours: "{minutes, number} min"
- # TODO: Distinguish between one hour (singular) and 2 hours or more?
- tripDurationFormat: "{hours, number} hr {minutes, number} min"
+ ItinerarySummary:
+ fareCost: "{useMaxFare, select,
+ true {{minTotalFare} - {maxTotalFare}}
+ other {{minTotalFare}}
+ }"
+ NarrativeItinerariesHeader:
+ numIssues: "{issueNum, number} issues"
+ resultText: "{pending, select,
+ true {Finding your options...}
+ other {
+ {itineraryNum, number} {itineraryNum, plural,
+ one {itinerary found}
+ other {itineraries found}
+ }
+ }
+ }"
+ selectArrivalTime: Arrival time
+ selectBest: Best option
+ selectCost: Cost
+ selectDepartureTime: Departure time
+ selectDuration: Duration
+ selectWalkTime: Walk time
+ titleText: "{pending, select,
+ true {Finding your options...}
+ other {
+ {itineraryNum, number} {itineraryNum, plural,
+ one {itinerary}
+ other {itineraries}}
+ {issueNum, plural,
+ =0 {found}
+ one {(and {issueNum, number} issue) found}
+ other {(and {issueNum, number} issues) found}
+ }
+ }
+ }"
+ viewAll: View all options
+ PlanFirstLastButtons:
+ # Note to translator: these values are width-constrained.
+ first: First
+ last: Last
+ next: Next
+ previous: Previous
+ RealtimeAnnotation:
+ ignoreServiceDelays: Apply service delays
+ delaysNotShownInResults: "Your trip results are currently being affected by service delays.
+ These delays do not factor into travel times shown below."
+ delaysShownInResults: "Your trip results have been adjusted based on real-time
+ information. Under normal conditions, this trip would take {normalDuration}
+ using the following routes: {routes}."
+ ignoreServiceDelays: Ignore service delays
+ serviceUpdate: Service update
+ SaveTripButton:
+ cantSaveText: Cannot save
+ cantSaveTooltip: Only itineraries that include transit and no rentals or ride hailing can be monitored.
+ saveTripText: Save trip
+ signInText: Sign in to save trip
+ signInTooltip: Please sign in to save trip.
+ SimpleRealtimeAnnotation:
+ usingRealtimeInfo: This trip uses real-time traffic and delay information
+ TabbedItineraries:
+ optionNumber: "Option {optionNum, number}"
+ fareCost: "{hasMaxFare, select,
+ true {{minTotalFare}+}
+ other {{minTotalFare}}
+ }"
+ TripTools:
+ # Note to translator: copyLink, linkCopied, print, reportIssue,
+ # and startOver are width-constrained.
+ copyLink: Copy link
+ # Text that replaces the copyLink button text after user clicks it.
+ linkCopied: Copied
+ print: Print
+ reportIssue: Report Issue
+ reportEmailSubject: Reporting an Issue with OpenTripPlanner
+ reportEmailTemplate: " *** INSTRUCTIONS TO USER ***
+ This feature allows you to email a report to site administrators for review.
+ Please add any additional feedback for this trip under the 'Additional Comments'
+ section below and send using your regular email program."
+ startOver: Start Over # TODO: move to other category (common with hamburger 'Start Over' item)
# Common messages that appear in multiple components and modules
# are grouped below by topic.
@@ -49,7 +121,11 @@ common:
drive: Drive
micromobility: E-Scooter
micromobilityRent: Rental E-Scooter
- walk: Walk
+ walk: Walk
+
+ itineraryDescriptions:
+ calories: "{calories, number} Cal"
+ transfers: "{transfers, plural, =0 {} one {{transfers} transfer} other {{transfers} transfers}}"
# OTP transit modes
# Note that identifiers are OTP modes converted to lowercase.
@@ -63,3 +139,12 @@ common:
cable_car: Cable Car
gondola: Gondola
funicular: Funicular
+
+ time:
+ # Use ordered placeholders for the departure-arrival string
+ # (this will accommodate right-to-left languages by swapping the order in this string).
+ departureArrivalTimes: "{startTime, time, short}—{endTime, time, short}"
+ tripDurationFormat: "{hours, plural,
+ =0 {{minutes, number} min}
+ other {{hours, number} hr {minutes, number} min}}"
+
\ No newline at end of file
diff --git a/i18n/fr-FR.yml b/i18n/fr-FR.yml
index cdbf33b27..ad0874e2e 100644
--- a/i18n/fr-FR.yml
+++ b/i18n/fr-FR.yml
@@ -4,10 +4,85 @@ _name: Unofficial French Translations!
components:
DefaultItinerary:
clickDetails: Cliquez pour afficher les détails
- departureArrivalTimes: "{startTime}—{endTime}"
multiModeSummary: "{accessMode} + {transitMode}"
- tripDurationFormatZeroHours: "{minutes, number} mn"
- tripDurationFormat: "{hours, number} h, {minutes, number} mn"
+ ItinerarySummary:
+ fareCost: "{useMaxFare, select,
+ true {{minTotalFare} - {maxTotalFare}}
+ other {{minTotalFare}}
+ }"
+ NarrativeItinerariesHeader:
+ numIssues: "{issueNum, number} problèmes"
+ resultText: "{pending, select,
+ true {Recherche de vos options en cours...}
+ other {
+ {itineraryNum, number} {itineraryNum, plural,
+ one {trajet trouvé}
+ other {trajets trouvés}
+ }
+ }
+ }"
+ selectArrivalTime: Heure d'arrivée
+ selectBest: Meilleure option
+ selectCost: Prix
+ selectDepartureTime: Heure de départ
+ selectDuration: Durée
+ selectWalkTime: Temps de marche
+ titleText: "{pending, select,
+ true {Recherche de vos options en cours...}
+ other {
+ {itineraryNum, number} {itineraryNum, plural,
+ one {trajet}
+ other {trajets}}
+ {issueNum, plural,
+ =0 {trouvé}
+ one {(et {issueNum, number} problème) trouvé}
+ other {(and {issueNum, number} problèmes) trouvés}
+ }
+ }
+ }"
+ viewAll: Voir toutes les options
+ PlanFirstLastButtons:
+ # Note to translator: these values are width-constrained.
+ first: Premier
+ last: Dernier
+ next: Suivant
+ previous: Précédent
+ RealtimeAnnotation:
+ ignoreServiceDelays: Appliquer les retards
+ delaysNotShownInResults: "Vos trajets recherchés sont perturbés par des retards.
+ Ces retards ne sont pas pris en compte dans les temps de trajet ci-dessous."
+ delaysShownInResults: "Vos trajets recherchés ont été mis à jour avec les conditions en temps réel.
+ En temps normal, ce trajet prendrait {normalDuration} en empruntant les lignes: {routes}."
+ ignoreServiceDelays: Ignorer les retards
+ serviceUpdate: Information sur le service
+ SaveTripButton:
+ cantSaveText: Impossible d'enregistrer
+ cantSaveTooltip: Seuls les trajets en transports en commun sans location de véhicules et sans course en voiture peuvent être suivis.
+ saveTripText: Enregistrer
+ signInText: Connectez-vous pour enregistrer
+ signInTooltip: Veuillez vous connecter pour enregistrer ce trajet.
+ SimpleRealtimeAnnotation:
+ usingRealtimeInfo: Ce trajet utilise les informations en temps réel sur le trafic et les retards
+ TabbedItineraries:
+ optionNumber: "Option {optionNum, number}"
+ fareCost: "{hasMaxFare, select,
+ true {À partir de {minTotalFare}}
+ other {{minTotalFare}}
+ }"
+ TripTools:
+ # Note to translator: copyLink, linkCopied, print, reportIssue,
+ # and startOver are width-constrained.
+ copyLink: Copier le lien
+ # Text that replaces the copyLink button text after user clicks it.
+ linkCopied: Copié
+ print: Imprimer
+ reportIssue: Un problème ? # "Signaler un problème" does not fit.
+ reportEmailSubject: Signaler un problème avec OpenTripPlanner
+ reportEmailTemplate: " *** A L'ATTENTION DE L'UTILISATEUR ***
+ Vous pouvez communiquer votre problème en détail aux administrateurs de ce site, par courriel.
+ Veuillez ajouter toute remarque sur cet itinéraire dans la section 'Additional Comments'
+ ci-dessous, puis envoyez depuis votre logiciel de messagerie usuel."
+ startOver: Recommencer
common:
accessModes:
@@ -16,7 +91,12 @@ common:
drive: Voiture
micromobility: Trottinette électrique
micromobilityRent: Trottinette électrique en libre-service
- walk: Marche
+ walk: À pied
+
+ itineraryDescriptions:
+ calories: "{calories, number} kcal" # SI unit
+ transfers: "{transfers, plural, =0 {} one {{transfers} correspondance} other {{transfers} correspondances}}"
+
otpTransitModes:
tram: Tram
@@ -27,3 +107,9 @@ common:
cable_car: Tram tiré par câble
gondola: Téléphérique
funicular: Funiculaire
+
+ time:
+ departureArrivalTimes: "{startTime, time, short}—{endTime, time, short}"
+ tripDurationFormat: "{hours, plural,
+ =0 {{minutes, number} mn}
+ other {{hours, number} h {minutes, number} mn}}"
diff --git a/lib/components/admin/call-history-window.js b/lib/components/admin/call-history-window.js
index 1f72e8a5a..4bbf897ce 100644
--- a/lib/components/admin/call-history-window.js
+++ b/lib/components/admin/call-history-window.js
@@ -2,7 +2,7 @@ import React from 'react'
import { connect } from 'react-redux'
import * as callTakerActions from '../../actions/call-taker'
-import Icon from '../narrative/icon'
+import Icon from '../util/icon'
import CallRecord from './call-record'
import DraggableWindow from './draggable-window'
diff --git a/lib/components/admin/call-record.js b/lib/components/admin/call-record.js
index e4d821616..3f37491cd 100644
--- a/lib/components/admin/call-record.js
+++ b/lib/components/admin/call-record.js
@@ -2,8 +2,8 @@ import humanizeDuration from 'humanize-duration'
import moment from 'moment'
import React, { Component } from 'react'
-import Icon from '../narrative/icon'
import {searchToQuery} from '../../util/call-taker'
+import Icon from '../util/icon'
import CallTimeCounter from './call-time-counter'
import QueryRecord from './query-record'
diff --git a/lib/components/admin/call-taker-controls.js b/lib/components/admin/call-taker-controls.js
index 23ef687ad..3fbd38458 100644
--- a/lib/components/admin/call-taker-controls.js
+++ b/lib/components/admin/call-taker-controls.js
@@ -5,8 +5,8 @@ import * as apiActions from '../../actions/api'
import * as callTakerActions from '../../actions/call-taker'
import * as fieldTripActions from '../../actions/field-trip'
import * as uiActions from '../../actions/ui'
-import Icon from '../narrative/icon'
import { isModuleEnabled, Modules } from '../../util/config'
+import Icon from '../util/icon'
import {
CallHistoryButton,
diff --git a/lib/components/admin/draggable-window.js b/lib/components/admin/draggable-window.js
index 2d6aba12f..75532fd2f 100644
--- a/lib/components/admin/draggable-window.js
+++ b/lib/components/admin/draggable-window.js
@@ -1,7 +1,7 @@
import React, { Component } from 'react'
import Draggable from 'react-draggable'
-import Icon from '../narrative/icon'
+import Icon from '../util/icon'
const noop = () => {}
diff --git a/lib/components/admin/field-trip-details.js b/lib/components/admin/field-trip-details.js
index 2707fb64d..48c125435 100644
--- a/lib/components/admin/field-trip-details.js
+++ b/lib/components/admin/field-trip-details.js
@@ -6,7 +6,6 @@ import { connect } from 'react-redux'
import styled from 'styled-components'
import * as fieldTripActions from '../../actions/field-trip'
-import Icon from '../narrative/icon'
import {
getActiveFieldTripRequest,
getGroupSize,
@@ -14,6 +13,7 @@ import {
PAYMENT_FIELDS,
TICKET_TYPES
} from '../../util/call-taker'
+import Icon from '../util/icon'
import DraggableWindow from './draggable-window'
import EditableSection from './editable-section'
diff --git a/lib/components/admin/field-trip-itinerary-group-size.js b/lib/components/admin/field-trip-itinerary-group-size.js
index 27e3015f7..40d7bfd18 100644
--- a/lib/components/admin/field-trip-itinerary-group-size.js
+++ b/lib/components/admin/field-trip-itinerary-group-size.js
@@ -1,7 +1,7 @@
import React from 'react'
import { Badge } from 'react-bootstrap'
-import Icon from '../narrative/icon'
+import Icon from '../util/icon'
export default function FieldTripGroupSize ({ itinerary }) {
return itinerary.fieldTripGroupSize > 0 && (
diff --git a/lib/components/admin/field-trip-list.js b/lib/components/admin/field-trip-list.js
index 2cabcd1cb..399351eaa 100644
--- a/lib/components/admin/field-trip-list.js
+++ b/lib/components/admin/field-trip-list.js
@@ -5,10 +5,10 @@ import { Badge, Button } from 'react-bootstrap'
import { connect } from 'react-redux'
import * as fieldTripActions from '../../actions/field-trip'
-import Icon from '../narrative/icon'
import Loading from '../narrative/loading'
import {getVisibleRequests, TABS} from '../../util/call-taker'
import {FETCH_STATUS} from '../../util/constants'
+import Icon from '../util/icon'
import FieldTripStatusIcon from './field-trip-status-icon'
import {FieldTripRecordButton, WindowHeader} from './styled'
diff --git a/lib/components/admin/field-trip-notes.js b/lib/components/admin/field-trip-notes.js
index a190f5f5d..c0f2be4a4 100644
--- a/lib/components/admin/field-trip-notes.js
+++ b/lib/components/admin/field-trip-notes.js
@@ -2,7 +2,8 @@ import React, { Component } from 'react'
import { Badge, Button as BsButton } from 'react-bootstrap'
import styled from 'styled-components'
-import Icon from '../narrative/icon'
+import Icon from '../util/icon'
+
import {
Button,
Full,
diff --git a/lib/components/admin/field-trip-status-icon.js b/lib/components/admin/field-trip-status-icon.js
index e7ed9b0eb..901a73939 100644
--- a/lib/components/admin/field-trip-status-icon.js
+++ b/lib/components/admin/field-trip-status-icon.js
@@ -1,6 +1,6 @@
import React from 'react'
-import Icon from '../narrative/icon'
+import Icon from '../util/icon'
const FieldTripStatusIcon = ({ ok }) => (
ok
diff --git a/lib/components/admin/mailables-window.js b/lib/components/admin/mailables-window.js
index 55f558c54..80f8112e6 100644
--- a/lib/components/admin/mailables-window.js
+++ b/lib/components/admin/mailables-window.js
@@ -3,8 +3,8 @@ import {Badge, Button} from 'react-bootstrap'
import {connect} from 'react-redux'
import * as callTakerActions from '../../actions/call-taker'
-import Icon from '../narrative/icon'
import {getModuleConfig, isModuleEnabled, Modules} from '../../util/config'
+import Icon from '../util/icon'
import {createLetter, LETTER_FIELDS} from '../../util/mailables'
import {
diff --git a/lib/components/admin/styled.js b/lib/components/admin/styled.js
index 6c9ecdb5b..7ee01dbe4 100644
--- a/lib/components/admin/styled.js
+++ b/lib/components/admin/styled.js
@@ -1,7 +1,7 @@
import { Button as BsButton } from 'react-bootstrap'
import styled, {css} from 'styled-components'
-import Icon from '../narrative/icon'
+import Icon from '../util/icon'
import DefaultCounter from './call-time-counter'
diff --git a/lib/components/app/app-menu.js b/lib/components/app/app-menu.js
index ccbf42fa1..159d22446 100644
--- a/lib/components/app/app-menu.js
+++ b/lib/components/app/app-menu.js
@@ -5,11 +5,11 @@ import { connect } from 'react-redux'
import { DropdownButton, MenuItem } from 'react-bootstrap'
import { withRouter } from 'react-router'
-import Icon from '../narrative/icon'
import * as callTakerActions from '../../actions/call-taker'
import * as fieldTripActions from '../../actions/field-trip'
import { MainPanelContent, setMainPanelContent } from '../../actions/ui'
import { isModuleEnabled, Modules } from '../../util/config'
+import Icon from '../util/icon'
// TODO: make menu items configurable via props/config
diff --git a/lib/components/form/batch-settings.js b/lib/components/form/batch-settings.js
index 6e22168cc..f4150a576 100644
--- a/lib/components/form/batch-settings.js
+++ b/lib/components/form/batch-settings.js
@@ -5,7 +5,7 @@ import styled from 'styled-components'
import * as apiActions from '../../actions/api'
import * as formActions from '../../actions/form'
-import Icon from '../narrative/icon'
+import Icon from '../util/icon'
import { hasValidLocation, getActiveSearch, getShowUserSettings } from '../../util/state'
import BatchPreferences from './batch-preferences'
diff --git a/lib/components/form/mode-buttons.js b/lib/components/form/mode-buttons.js
index eed382be2..3362cf979 100644
--- a/lib/components/form/mode-buttons.js
+++ b/lib/components/form/mode-buttons.js
@@ -2,8 +2,8 @@ import React, { useContext } from 'react'
import { OverlayTrigger, Tooltip } from 'react-bootstrap'
import styled from 'styled-components'
-import Icon from '../narrative/icon'
import { ComponentContext } from '../../util/contexts'
+import Icon from '../util/icon'
import {buttonCss} from './batch-styled'
diff --git a/lib/components/form/user-settings.js b/lib/components/form/user-settings.js
index d68b22e8c..870161e7c 100644
--- a/lib/components/form/user-settings.js
+++ b/lib/components/form/user-settings.js
@@ -4,11 +4,11 @@ import React, { Component } from 'react'
import { Button } from 'react-bootstrap'
import { connect } from 'react-redux'
-import Icon from '../narrative/icon'
import { forgetSearch, toggleTracking } from '../../actions/api'
import { setQueryParam } from '../../actions/form'
import { forgetPlace, forgetStop, setLocation } from '../../actions/map'
import { setViewedStop } from '../../actions/ui'
+import Icon from '../util/icon'
const { formatStoredPlaceName, getDetailText, matchLatLon } = coreUtils.map
const { summarizeQuery } = coreUtils.query
diff --git a/lib/components/form/user-trip-settings.js b/lib/components/form/user-trip-settings.js
index 0844203ba..d681e2cb8 100644
--- a/lib/components/form/user-trip-settings.js
+++ b/lib/components/form/user-trip-settings.js
@@ -3,12 +3,12 @@ import React, { Component } from 'react'
import { Button } from 'react-bootstrap'
import { connect } from 'react-redux'
-import Icon from '../narrative/icon'
import {
clearDefaultSettings,
resetForm,
storeDefaultSettings
} from '../../actions/form'
+import Icon from '../util/icon'
/**
* This component contains the `Remember/Forget my trip options` and `Restore defaults` commands
diff --git a/lib/components/mobile/batch-results-screen.js b/lib/components/mobile/batch-results-screen.js
index 273c53bbb..d9be6ce0b 100644
--- a/lib/components/mobile/batch-results-screen.js
+++ b/lib/components/mobile/batch-results-screen.js
@@ -6,8 +6,8 @@ import styled, { css } from 'styled-components'
import * as uiActions from '../../actions/ui'
import Map from '../map/map'
-import Icon from '../narrative/icon'
import NarrativeItineraries from '../narrative/narrative-itineraries'
+import Icon from '../util/icon'
import {
getActiveItineraries,
getActiveSearch,
diff --git a/lib/components/mobile/navigation-bar.js b/lib/components/mobile/navigation-bar.js
index cdd845530..3c4f57e65 100644
--- a/lib/components/mobile/navigation-bar.js
+++ b/lib/components/mobile/navigation-bar.js
@@ -6,9 +6,9 @@ import { connect } from 'react-redux'
import { setMobileScreen } from '../../actions/ui'
import AppMenu from '../app/app-menu'
import NavLoginButtonAuth0 from '../../components/user/nav-login-button-auth0'
-import Icon from '../narrative/icon'
import { accountLinks, getAuth0Config } from '../../util/auth'
import { ComponentContext } from '../../util/contexts'
+import Icon from '../util/icon'
class MobileNavigationBar extends Component {
static propTypes = {
diff --git a/lib/components/narrative/default/access-leg.js b/lib/components/narrative/default/access-leg.js
index e6687def5..9e370a06d 100644
--- a/lib/components/narrative/default/access-leg.js
+++ b/lib/components/narrative/default/access-leg.js
@@ -3,7 +3,7 @@ import { humanizeDistanceString } from '@opentripplanner/humanize-distance'
import PropTypes from 'prop-types'
import React, {Component} from 'react'
-import Icon from '../icon'
+import Icon from '../../util/icon'
import LegDiagramPreview from '../leg-diagram-preview'
/**
diff --git a/lib/components/narrative/default/default-itinerary.js b/lib/components/narrative/default/default-itinerary.js
index 599a18ebf..6dcecd092 100644
--- a/lib/components/narrative/default/default-itinerary.js
+++ b/lib/components/narrative/default/default-itinerary.js
@@ -1,19 +1,43 @@
-import moment from 'moment-timezone'
import coreUtils from '@opentripplanner/core-utils'
import React from 'react'
-import { FormattedMessage, FormattedNumber } from 'react-intl'
+import { FormattedMessage, FormattedNumber, FormattedTime } from 'react-intl'
import { connect } from 'react-redux'
+import styled from 'styled-components'
import FieldTripGroupSize from '../../admin/field-trip-itinerary-group-size'
import NarrativeItinerary from '../narrative-itinerary'
import ItineraryBody from '../line-itin/connected-itinerary-body'
import SimpleRealtimeAnnotation from '../simple-realtime-annotation'
+import FormattedDuration from '../../util/formatted-duration'
+import FormattedTimeRange from '../../util/formatted-time-range'
import { getTotalFare } from '../../../util/state'
import ItinerarySummary from './itinerary-summary'
const { isBicycle, isMicromobility, isTransit } = coreUtils.itinerary
+// Styled components
+const LegIconWrapper = styled.div`
+ display: inline-block;
+ height: 20px;
+ padding-bottom: 6px;
+ padding-left: 2px;
+ width: 20px;
+
+ /* Equivalent of a single space before the leg icon. */
+ &::before {
+ content: "";
+ margin: 0 0.125em;
+ }
+`
+
+const DetailsHint = styled.div`
+ clear: both;
+ color: #685C5C;
+ font-size: small;
+ text-align: center;
+`
+
/**
* Obtains the description of an itinerary in the given locale.
*/
@@ -46,42 +70,6 @@ function ItineraryDescription ({itinerary}) {
: mainMode
}
-/**
- * Formats the given duration according to the selected locale.
- */
-function FormattedDuration ({duration}) {
- const dur = moment.duration(duration, 'seconds')
- const hours = dur.hours()
- const minutes = dur.minutes()
- if (hours === 0) {
- return (
-
- )
- } else {
- return (
-
- )
- }
-}
-
-function FormattedTime ({endTime, startTime, timeFormat}) {
- return (
-
- )
-}
-
const ITINERARY_ATTRIBUTES = [
{
alias: 'best',
@@ -98,26 +86,15 @@ const ITINERARY_ATTRIBUTES = [
render: (itinerary, options) => {
if (options.isSelected) {
if (options.selection === 'ARRIVALTIME') {
- return (
-
- )
+ return
} else {
- return (
-
- )
+ return
}
}
return (
-
)
}
@@ -144,18 +121,12 @@ const ITINERARY_ATTRIBUTES = [
const {LegIcon} = options
return (
// FIXME: For CAR mode, walk time considers driving time.
-
- {' '}
-
+ <>
+
+
-
-
+
+ >
)
}
}
@@ -200,8 +171,7 @@ class DefaultItinerary extends NarrativeItinerary {
LegIcon,
setActiveLeg,
showRealtimeAnnotation,
- timeFormat,
- use24HourFormat
+ timeFormat
} = this.props
const timeOptions = {
format: timeFormat,
@@ -214,11 +184,6 @@ class DefaultItinerary extends NarrativeItinerary {
onMouseEnter={this._onMouseEnter}
onMouseLeave={this._onMouseLeave}
role='presentation'
- // FIXME: Move style to css
- style={{
- backgroundColor: expanded ? 'white' : undefined,
- borderLeft: active && !expanded ? '4px teal solid' : undefined
- }}
>
{(active && !expanded) &&
-
+
-
+
}
{(active && expanded) &&
@@ -281,8 +245,7 @@ const mapStateToProps = (state, ownProps) => {
// The configured (ambient) currency is needed for rendering the cost
// of itineraries whether they include a fare or not, in which case
// we show $0.00 or its equivalent in the configured currency and selected locale.
- currency: state.otp.config.localization?.currency || 'USD',
- use24HourFormat: state.user.loggedInUser?.use24HourFormat ?? false
+ currency: state.otp.config.localization?.currency || 'USD'
}
}
diff --git a/lib/components/narrative/default/itinerary.css b/lib/components/narrative/default/itinerary.css
index b4203ba0f..63bdb7bec 100644
--- a/lib/components/narrative/default/itinerary.css
+++ b/lib/components/narrative/default/itinerary.css
@@ -5,7 +5,7 @@
}
/* If child component is focused, highlight itinerary option */
-.otp .option.default-itin:focus-within {
+.otp .option.default-itin:focus-within:not(.expanded) {
background-color: var(--hover-color);
}
@@ -19,6 +19,11 @@
border-top: 1px solid grey;
}
+/* Show side border if active and not expanded */
+.otp .option.default-itin.active:not(.expanded) {
+ border-left: 4px teal solid;
+}
+
/* FIXME: don't highlight if not active */
.otp .option.default-itin:hover:not(.active) {
background-color: var(--hover-color);
diff --git a/lib/components/narrative/default/transit-leg.js b/lib/components/narrative/default/transit-leg.js
index 2e2f3023d..c70107c5e 100644
--- a/lib/components/narrative/default/transit-leg.js
+++ b/lib/components/narrative/default/transit-leg.js
@@ -2,7 +2,7 @@ import coreUtils from '@opentripplanner/core-utils'
import PropTypes from 'prop-types'
import React, { Component } from 'react'
-import Icon from '../icon'
+import Icon from '../../util/icon'
import ViewTripButton from '../../viewers/view-trip-button'
import ViewStopButton from '../../viewers/view-stop-button'
diff --git a/lib/components/narrative/icon.js b/lib/components/narrative/icon.js
deleted file mode 100644
index cca4799b3..000000000
--- a/lib/components/narrative/icon.js
+++ /dev/null
@@ -1,16 +0,0 @@
-import React, { Component } from 'react'
-import FontAwesome from 'react-fontawesome'
-
-export default class Icon extends Component {
- static propTypes = {
- // type: PropTypes.string.required
- }
- render () {
- return (
- )
- }
-}
diff --git a/lib/components/narrative/itinerary-carousel.js b/lib/components/narrative/itinerary-carousel.js
index f6b125edb..02fcee418 100644
--- a/lib/components/narrative/itinerary-carousel.js
+++ b/lib/components/narrative/itinerary-carousel.js
@@ -8,8 +8,8 @@ import SwipeableViews from 'react-swipeable-views'
import { setActiveItinerary, setActiveLeg, setActiveStep } from '../../actions/narrative'
import { ComponentContext } from '../../util/contexts'
import { getActiveItineraries, getActiveSearch } from '../../util/state'
+import Icon from '../util/icon'
-import Icon from './icon'
import Loading from './loading'
class ItineraryCarousel extends Component {
diff --git a/lib/components/narrative/line-itin/itin-summary.js b/lib/components/narrative/line-itin/itin-summary.js
index b457e95ac..96f130dab 100644
--- a/lib/components/narrative/line-itin/itin-summary.js
+++ b/lib/components/narrative/line-itin/itin-summary.js
@@ -2,8 +2,12 @@ import coreUtils from '@opentripplanner/core-utils'
import PropTypes from 'prop-types'
import React, { Component } from 'react'
import styled from 'styled-components'
+import { connect } from 'react-redux'
+import { FormattedNumber, FormattedMessage } from 'react-intl'
import { ComponentContext } from '../../../util/contexts'
+import FormattedDuration from '../../util/formatted-duration'
+import FormattedTimeRange from '../../util/formatted-time-range'
// TODO: make this a prop
const defaultRouteColor = '#008'
@@ -71,7 +75,7 @@ const ShortName = styled.div`
width: 30px;
`
-export default class ItinerarySummary extends Component {
+export class ItinerarySummary extends Component {
static propTypes = {
itinerary: PropTypes.object
}
@@ -83,11 +87,10 @@ export default class ItinerarySummary extends Component {
}
render () {
- const { itinerary, timeOptions } = this.props
+ const { currency, itinerary } = this.props
const { LegIcon } = this.context
const {
- centsToString,
maxTNCFare,
minTNCFare,
transitFare
@@ -97,34 +100,59 @@ export default class ItinerarySummary extends Component {
const maxTotalFare = maxTNCFare * 100 + transitFare
const { caloriesBurned } = coreUtils.itinerary.calculatePhysicalActivity(itinerary)
-
return (
{/* Travel time in hrs/mins */}
- {coreUtils.time.formatDuration(itinerary.duration)}
+
+
+
{/* Duration as time range */}
- {coreUtils.time.formatTime(itinerary.startTime, timeOptions)} - {coreUtils.time.formatTime(itinerary.endTime, timeOptions)}
+
{/* Fare / Calories */}
{minTotalFare > 0 &&
- {centsToString(minTotalFare)}
- {minTotalFare !== maxTotalFare && - {centsToString(maxTotalFare)}}
+
+ ),
+ minTotalFare: (
+
+ ),
+ useMaxFare: minTotalFare !== maxTotalFare ? 'true' : 'false'
+ }}
+ />
• }
- {Math.round(caloriesBurned)} Cals
+
{/* Number of transfers, if applicable */}
- {itinerary.transfers > 0 && (
-
- {itinerary.transfers} transfer{itinerary.transfers > 1 ? 's' : ''}
-
- )}
+
+
+
@@ -179,3 +207,10 @@ function getRouteNameForBadge (leg) {
function getRouteColorForBadge (leg) {
return leg.routeColor ? '#' + leg.routeColor : defaultRouteColor
}
+
+const mapStateToProps = (state, ownProps) => {
+ return {
+ currency: state.otp.config.localization?.currency || 'USD'
+ }
+}
+export default connect(mapStateToProps)(ItinerarySummary)
diff --git a/lib/components/narrative/loading.js b/lib/components/narrative/loading.js
index adc668564..efc749562 100644
--- a/lib/components/narrative/loading.js
+++ b/lib/components/narrative/loading.js
@@ -1,6 +1,6 @@
import React, { Component } from 'react'
-import Icon from './icon'
+import Icon from '../util/icon'
export default class Loading extends Component {
render () {
diff --git a/lib/components/narrative/mode-icon.js b/lib/components/narrative/mode-icon.js
index eacb1436e..fd88b3054 100644
--- a/lib/components/narrative/mode-icon.js
+++ b/lib/components/narrative/mode-icon.js
@@ -1,7 +1,7 @@
import React, { Component } from 'react'
import PropTypes from 'prop-types'
-import Icon from './icon'
+import Icon from '../util/icon'
export default class ModeIcon extends Component {
static propTypes = {
diff --git a/lib/components/narrative/narrative-itineraries-errors.js b/lib/components/narrative/narrative-itineraries-errors.js
index 37ca1a098..fa94c77af 100644
--- a/lib/components/narrative/narrative-itineraries-errors.js
+++ b/lib/components/narrative/narrative-itineraries-errors.js
@@ -1,7 +1,7 @@
import { getCompanyIcon } from '@opentripplanner/icons/lib/companies'
import styled from 'styled-components'
-import Icon from '../narrative/icon'
+import Icon from '../util/icon'
import { getErrorMessage } from '../../util/state'
const IssueContainer = styled.div`
diff --git a/lib/components/narrative/narrative-itineraries-header.js b/lib/components/narrative/narrative-itineraries-header.js
index 54300bc81..e462de55d 100644
--- a/lib/components/narrative/narrative-itineraries-header.js
+++ b/lib/components/narrative/narrative-itineraries-header.js
@@ -1,6 +1,7 @@
import styled from 'styled-components'
+import { FormattedMessage, useIntl } from 'react-intl'
-import Icon from '../narrative/icon'
+import Icon from '../util/icon'
import PlanFirstLastButtons from './plan-first-last-buttons'
import SaveTripButton from './save-trip-button'
@@ -26,22 +27,7 @@ export default function NarrativeItinerariesHeader ({
showingErrors,
sort
}) {
- let resultText, titleText
- if (pending) {
- resultText = 'Finding your options...'
- titleText = 'Finding your options...'
- } else {
- const itineraryPlural = itineraries.length === 1
- ? 'itinerary'
- : 'itineraries'
- const issuePlural = errors.length === 1
- ? 'issue'
- : 'issues'
- resultText = `${itineraries.length} ${itineraryPlural} found.`
- titleText = errors.length > 0
- ? `${itineraries.length} ${itineraryPlural} (and ${errors.length} ${issuePlural}) found`
- : resultText
- }
+ const intl = useIntl()
return (
- View all options
+
{itineraryIsExpanded && (
// marginLeft: auto is a way of making something "float right"
@@ -72,15 +58,33 @@ export default function NarrativeItinerariesHeader ({
: <>
diff --git a/lib/components/narrative/narrative.css b/lib/components/narrative/narrative.css
index fd0d6534d..db5f6e664 100644
--- a/lib/components/narrative/narrative.css
+++ b/lib/components/narrative/narrative.css
@@ -198,6 +198,10 @@
color: #685c5c;
}
+.otp .tabbed-itineraries .tab-button .details > span {
+ display: block;
+}
+
.otp .tabbed-itineraries .tab-button:hover .title {
border-bottom: 3px solid #ddd;
}
diff --git a/lib/components/narrative/plan-first-last-buttons.js b/lib/components/narrative/plan-first-last-buttons.js
index fae5e4bf9..9209696e1 100644
--- a/lib/components/narrative/plan-first-last-buttons.js
+++ b/lib/components/narrative/plan-first-last-buttons.js
@@ -1,5 +1,6 @@
import React from 'react'
import {Button} from 'react-bootstrap'
+import { FormattedMessage } from 'react-intl'
import {connect} from 'react-redux'
import * as planActions from '../../actions/plan'
@@ -15,16 +16,16 @@ function PlanFirstLastButtons (props) {
return (
)
diff --git a/lib/components/narrative/realtime-annotation.js b/lib/components/narrative/realtime-annotation.js
index 325fe8167..a8662cc33 100644
--- a/lib/components/narrative/realtime-annotation.js
+++ b/lib/components/narrative/realtime-annotation.js
@@ -1,7 +1,10 @@
-import coreUtils from '@opentripplanner/core-utils'
import PropTypes from 'prop-types'
import React, { Component } from 'react'
import { Button, OverlayTrigger, Popover } from 'react-bootstrap'
+import { FormattedList, FormattedMessage } from 'react-intl'
+
+import FormattedDuration from '../util/formatted-duration'
+import Icon from '../util/icon'
export default class RealtimeAnnotation extends Component {
static propTypes = {
@@ -25,28 +28,30 @@ export default class RealtimeAnnotation extends Component {
const innerContent =
- Service update
+
+
-
+
{useRealtime
- ?
- Your trip results have been adjusted based on real-time
- information. Under normal conditions, this trip would take{' '}
- {coreUtils.time.formatDuration(realtimeEffects.normalDuration)}
- using the following routes:{' '}
- {filteredRoutes
- .map((route, idx) => (
-
- {route}
- {filteredRoutes.length - 1 > idx && ', '}
-
- ))
- }.
-
- :
- Your trip results are currently being affected by service delays.
- These delays do not factor into travel times shown below.
-
+ ? (
+
+
+
+ ),
+ routes: (
+ {route})}
+ />
+ )
+ }}
+ />
+ )
+ :
}
diff --git a/lib/components/narrative/save-trip-button.js b/lib/components/narrative/save-trip-button.js
index d5cabe2e7..5fe5f84a5 100644
--- a/lib/components/narrative/save-trip-button.js
+++ b/lib/components/narrative/save-trip-button.js
@@ -1,9 +1,11 @@
import React from 'react'
import { OverlayTrigger, Tooltip } from 'react-bootstrap'
+import { FormattedMessage, useIntl } from 'react-intl'
import { connect } from 'react-redux'
import { LinkContainerWithQuery } from '../form/connected-links'
import { CREATE_TRIP_PATH } from '../../util/constants'
+import Icon from '../util/icon'
import { itineraryCanBeMonitored } from '../../util/itinerary'
import { getActiveItinerary } from '../../util/state'
@@ -16,6 +18,7 @@ const SaveTripButton = ({
loggedInUser,
persistence
}) => {
+ const intl = useIntl()
// We are dealing with the following states:
// 1. Persistence disabled => just return null
// 2. User is not logged in => render something like: "Please sign in to save trip".
@@ -24,23 +27,23 @@ const SaveTripButton = ({
let buttonDisabled
let buttonText
let tooltipText
- let icon
+ let iconType
if (!persistence || !persistence.enabled) {
return null
} else if (!loggedInUser) {
buttonDisabled = true
- buttonText = 'Sign in to save trip'
- icon = 'fa fa-lock'
- tooltipText = 'Please sign in to save trip.'
+ buttonText =
+ iconType = 'lock'
+ tooltipText = intl.formatMessage({id: 'components.SaveTripButton.signInTooltip'})
} else if (!itineraryCanBeMonitored(itinerary)) {
buttonDisabled = true
- buttonText = 'Cannot save'
- icon = 'fa fa-ban'
- tooltipText = 'Only itineraries that include transit and no rentals or ride hailing can be monitored.'
+ buttonText =
+ iconType = 'ban'
+ tooltipText = intl.formatMessage({id: 'components.SaveTripButton.cantSaveTooltip'})
} else {
- buttonText = 'Save trip'
- icon = 'fa fa-plus-circle'
+ buttonText =
+ iconType = 'plus-circle'
}
const button = (
)
// Show tooltip with help text if button is disabled.
if (buttonDisabled) {
return (
{tooltipText}}
+ overlay={(
+
+ {/* Must get text using intl.formatMessage here because the rendering
+ of OverlayTrigger seems to occur outside of the IntlProvider context. */}
+ {tooltipText}
+
+ )}
placement='top'
>
-
+ {/* An active element around the disabled button is necessary
+ for the OverlayTrigger to render. */}
+
{button}
diff --git a/lib/components/narrative/simple-realtime-annotation.js b/lib/components/narrative/simple-realtime-annotation.js
index 07e1a4e7b..254714278 100644
--- a/lib/components/narrative/simple-realtime-annotation.js
+++ b/lib/components/narrative/simple-realtime-annotation.js
@@ -1,9 +1,13 @@
-import React, { Component } from 'react'
+import React from 'react'
+import { FormattedMessage } from 'react-intl'
-export default class SimpleRealtimeAnnotation extends Component {
- render () {
- return
- This trip uses real-time traffic and delay information
-