mirror of
https://github.com/mauriceboe/TREK.git
synced 2026-06-19 13:21:46 +00:00
feat: optimize routes around accommodation, confirm note deletions (#1123)
Optimize day routes around the accommodation
When a day has an accommodation set, the route optimizer now treats it as
the day's home base: it optimizes a loop that leaves the hotel and returns
to it, so the stop nearest the hotel comes first. On a transfer day -
checking out of one hotel and into another - the route runs from the first
hotel to the second instead.
The optimizer also gained a 2-opt pass on top of the nearest-neighbor
ordering, which removes the crossings the greedy pass used to leave behind.
A new display setting ("optimize route from accommodation", on by default)
lets you turn the anchoring off.
Confirm before deleting notes
Deleting a plan note or a collab note now asks for confirmation first. On
phones and tablets the edit and delete icons sit close together and were
easy to mis-tap, which deleted notes with no way back.
This commit is contained in:
@@ -175,7 +175,7 @@ describe('CollabNotes', () => {
|
|||||||
expect(document.body).toBeInTheDocument();
|
expect(document.body).toBeInTheDocument();
|
||||||
});
|
});
|
||||||
|
|
||||||
it('FE-COMP-NOTES-013: delete note calls DELETE API and removes it from grid', async () => {
|
it('FE-COMP-NOTES-013: deleting a note asks for confirmation, then calls DELETE API and removes it', async () => {
|
||||||
const user = userEvent.setup();
|
const user = userEvent.setup();
|
||||||
server.use(
|
server.use(
|
||||||
http.get('/api/trips/1/collab/notes', () =>
|
http.get('/api/trips/1/collab/notes', () =>
|
||||||
@@ -193,8 +193,11 @@ describe('CollabNotes', () => {
|
|||||||
);
|
);
|
||||||
render(<CollabNotes {...defaultProps} />);
|
render(<CollabNotes {...defaultProps} />);
|
||||||
await screen.findByText('Remove Me');
|
await screen.findByText('Remove Me');
|
||||||
const deleteBtn = screen.getByTitle('Delete');
|
await user.click(screen.getByTitle('Delete'));
|
||||||
await user.click(deleteBtn);
|
// Deleting now asks for confirmation first — the note stays until confirmed.
|
||||||
|
expect(screen.getByText('Delete note?')).toBeInTheDocument();
|
||||||
|
expect(screen.getByText('Remove Me')).toBeInTheDocument();
|
||||||
|
await user.click(document.querySelector('button.bg-red-600') as HTMLElement);
|
||||||
await waitFor(() => expect(screen.queryByText('Remove Me')).not.toBeInTheDocument());
|
await waitFor(() => expect(screen.queryByText('Remove Me')).not.toBeInTheDocument());
|
||||||
});
|
});
|
||||||
|
|
||||||
|
|||||||
@@ -10,6 +10,7 @@ import { useTripStore } from '../../store/tripStore'
|
|||||||
import { addListener, removeListener } from '../../api/websocket'
|
import { addListener, removeListener } from '../../api/websocket'
|
||||||
import { useTranslation } from '../../i18n'
|
import { useTranslation } from '../../i18n'
|
||||||
import { useToast } from '../shared/Toast'
|
import { useToast } from '../shared/Toast'
|
||||||
|
import ConfirmDialog from '../shared/ConfirmDialog'
|
||||||
import type { User } from '../../types'
|
import type { User } from '../../types'
|
||||||
import type { CollabNote } from './CollabNotes.types'
|
import type { CollabNote } from './CollabNotes.types'
|
||||||
import { FONT, NOTE_COLORS } from './CollabNotes.constants'
|
import { FONT, NOTE_COLORS } from './CollabNotes.constants'
|
||||||
@@ -44,6 +45,7 @@ function useCollabNotes({ tripId, currentUser }: CollabNotesProps) {
|
|||||||
const [previewFile, setPreviewFile] = useState(null)
|
const [previewFile, setPreviewFile] = useState(null)
|
||||||
const [showSettings, setShowSettings] = useState(false)
|
const [showSettings, setShowSettings] = useState(false)
|
||||||
const [activeCategory, setActiveCategory] = useState(null)
|
const [activeCategory, setActiveCategory] = useState(null)
|
||||||
|
const [pendingDeleteNoteId, setPendingDeleteNoteId] = useState<number | null>(null)
|
||||||
|
|
||||||
// Empty categories (no notes yet) stored in localStorage
|
// Empty categories (no notes yet) stored in localStorage
|
||||||
const [emptyCategories, setEmptyCategories] = useState(() => {
|
const [emptyCategories, setEmptyCategories] = useState(() => {
|
||||||
@@ -231,6 +233,7 @@ function useCollabNotes({ tripId, currentUser }: CollabNotesProps) {
|
|||||||
activeCategory, setActiveCategory, categoryColors, getCategoryColor,
|
activeCategory, setActiveCategory, categoryColors, getCategoryColor,
|
||||||
handleCreateNote, handleUpdateNote, saveCategoryColors, handleEditSubmit,
|
handleCreateNote, handleUpdateNote, saveCategoryColors, handleEditSubmit,
|
||||||
handleDeleteNoteFile, handleDeleteNote, categories, sortedNotes,
|
handleDeleteNoteFile, handleDeleteNote, categories, sortedNotes,
|
||||||
|
pendingDeleteNoteId, setPendingDeleteNoteId,
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -319,7 +322,7 @@ function CollabCategoryPills({ categories, activeCategory, setActiveCategory, t
|
|||||||
|
|
||||||
function CollabNotesGrid(S: NotesState) {
|
function CollabNotesGrid(S: NotesState) {
|
||||||
const {
|
const {
|
||||||
sortedNotes, currentUser, canEdit, handleUpdateNote, handleDeleteNote,
|
sortedNotes, currentUser, canEdit, handleUpdateNote, setPendingDeleteNoteId,
|
||||||
setEditingNote, setViewingNote, setPreviewFile, getCategoryColor, tripId, t,
|
setEditingNote, setViewingNote, setPreviewFile, getCategoryColor, tripId, t,
|
||||||
} = S
|
} = S
|
||||||
return (
|
return (
|
||||||
@@ -352,7 +355,7 @@ function CollabNotesGrid(S: NotesState) {
|
|||||||
currentUser={currentUser}
|
currentUser={currentUser}
|
||||||
canEdit={canEdit}
|
canEdit={canEdit}
|
||||||
onUpdate={handleUpdateNote}
|
onUpdate={handleUpdateNote}
|
||||||
onDelete={handleDeleteNote}
|
onDelete={setPendingDeleteNoteId}
|
||||||
onEdit={setEditingNote}
|
onEdit={setEditingNote}
|
||||||
onView={setViewingNote}
|
onView={setViewingNote}
|
||||||
onPreviewFile={setPreviewFile}
|
onPreviewFile={setPreviewFile}
|
||||||
@@ -470,6 +473,7 @@ export default function CollabNotes(props: CollabNotesProps) {
|
|||||||
viewingNote, showNewModal, editingNote, previewFile, showSettings,
|
viewingNote, showNewModal, editingNote, previewFile, showSettings,
|
||||||
setShowNewModal, setEditingNote, setPreviewFile, setShowSettings,
|
setShowNewModal, setEditingNote, setPreviewFile, setShowSettings,
|
||||||
handleCreateNote, handleEditSubmit, handleDeleteNoteFile, saveCategoryColors, handleUpdateNote,
|
handleCreateNote, handleEditSubmit, handleDeleteNoteFile, saveCategoryColors, handleUpdateNote,
|
||||||
|
handleDeleteNote, pendingDeleteNoteId, setPendingDeleteNoteId,
|
||||||
} = S
|
} = S
|
||||||
|
|
||||||
if (loading) return <CollabNotesLoading {...S} />
|
if (loading) return <CollabNotesLoading {...S} />
|
||||||
@@ -527,6 +531,15 @@ export default function CollabNotes(props: CollabNotesProps) {
|
|||||||
t={t}
|
t={t}
|
||||||
/>
|
/>
|
||||||
)}
|
)}
|
||||||
|
|
||||||
|
{/* Confirm: delete a collab note — guards against accidental deletion */}
|
||||||
|
<ConfirmDialog
|
||||||
|
isOpen={pendingDeleteNoteId !== null}
|
||||||
|
onClose={() => setPendingDeleteNoteId(null)}
|
||||||
|
onConfirm={() => { if (pendingDeleteNoteId !== null) handleDeleteNote(pendingDeleteNoteId) }}
|
||||||
|
title={t('collab.notes.confirmDeleteTitle')}
|
||||||
|
message={t('collab.notes.confirmDeleteBody')}
|
||||||
|
/>
|
||||||
</div>
|
</div>
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -16,7 +16,7 @@ interface NoteCardProps {
|
|||||||
currentUser: User
|
currentUser: User
|
||||||
canEdit: boolean
|
canEdit: boolean
|
||||||
onUpdate: (noteId: number, data: Partial<CollabNote>) => Promise<void>
|
onUpdate: (noteId: number, data: Partial<CollabNote>) => Promise<void>
|
||||||
onDelete: (noteId: number) => Promise<void>
|
onDelete: (noteId: number) => void
|
||||||
onEdit: (note: CollabNote) => void
|
onEdit: (note: CollabNote) => void
|
||||||
onView: (note: CollabNote) => void
|
onView: (note: CollabNote) => void
|
||||||
onPreviewFile: (file: NoteFile) => void
|
onPreviewFile: (file: NoteFile) => void
|
||||||
|
|||||||
@@ -161,6 +161,62 @@ describe('optimizeRoute', () => {
|
|||||||
expect(result[1]).toEqual(c)
|
expect(result[1]).toEqual(c)
|
||||||
expect(result[2]).toEqual(b)
|
expect(result[2]).toEqual(b)
|
||||||
})
|
})
|
||||||
|
|
||||||
|
it('FE-COMP-ROUTECALCULATOR-016: start anchor begins the chain at the anchor-nearest stop', () => {
|
||||||
|
const a = { lat: 10, lng: 1 }
|
||||||
|
const b = { lat: 2, lng: 1 }
|
||||||
|
const c = { lat: 5, lng: 1 }
|
||||||
|
// From the accommodation anchor (1,1): nearest is b(2,1), then c(5,1), then a(10,1)
|
||||||
|
const result = optimizeRoute([a, b, c], { start: { lat: 1, lng: 1 } })
|
||||||
|
expect(result).toEqual([b, c, a])
|
||||||
|
})
|
||||||
|
|
||||||
|
it('FE-COMP-ROUTECALCULATOR-017: start + end anchors reorder a shuffled day and keep the end-nearest stop last', () => {
|
||||||
|
const a = { lat: 2, lng: 1 }
|
||||||
|
const b = { lat: 5, lng: 1 }
|
||||||
|
const c = { lat: 8, lng: 1 }
|
||||||
|
// Transfer day: start at hotel A (1,1), end at hotel B (9,1). c is nearest B, so it must be last.
|
||||||
|
const result = optimizeRoute([c, a, b], { start: { lat: 1, lng: 1 }, end: { lat: 9, lng: 1 } })
|
||||||
|
expect(result).toEqual([a, b, c])
|
||||||
|
})
|
||||||
|
|
||||||
|
it('FE-COMP-ROUTECALCULATOR-018: an anchor makes even a two-stop day sortable', () => {
|
||||||
|
const a = { lat: 10, lng: 1 }
|
||||||
|
const b = { lat: 2, lng: 1 }
|
||||||
|
// Without anchors two stops are returned unchanged; the start anchor orders them by proximity.
|
||||||
|
const result = optimizeRoute([a, b], { start: { lat: 1, lng: 1 } })
|
||||||
|
expect(result).toEqual([b, a])
|
||||||
|
})
|
||||||
|
|
||||||
|
it('FE-COMP-ROUTECALCULATOR-019: 2-opt untangles a round-trip into a clean loop around the hotel', () => {
|
||||||
|
const hotel = { lat: 48.8668, lng: 2.3013 } // Rue Marbeuf
|
||||||
|
const stops = [
|
||||||
|
{ id: 1, lat: 48.8565, lng: 2.3324 },
|
||||||
|
{ id: 2, lat: 48.8813, lng: 2.3151 },
|
||||||
|
{ id: 3, lat: 48.8796, lng: 2.308 },
|
||||||
|
{ id: 4, lat: 48.8723, lng: 2.2926 },
|
||||||
|
{ id: 5, lat: 48.866, lng: 2.3102 }, // nearest the hotel
|
||||||
|
]
|
||||||
|
const d = (a: { lat: number; lng: number }, b: { lat: number; lng: number }) =>
|
||||||
|
Math.hypot(a.lat - b.lat, a.lng - b.lng)
|
||||||
|
const loop = (order: typeof stops) =>
|
||||||
|
d(hotel, order[0]) + order.slice(1).reduce((s, p, i) => s + d(order[i], p), 0) + d(order[order.length - 1], hotel)
|
||||||
|
|
||||||
|
const result = optimizeRoute(stops, { start: hotel, end: hotel })
|
||||||
|
// The optimized loop is no longer than the original order…
|
||||||
|
expect(loop(result)).toBeLessThanOrEqual(loop(stops) + 1e-9)
|
||||||
|
// …and the hotel-adjacent stop sits at one end of the loop, right next to the hotel.
|
||||||
|
expect([result[0].id, result[result.length - 1].id]).toContain(5)
|
||||||
|
})
|
||||||
|
|
||||||
|
it('FE-COMP-ROUTECALCULATOR-020: an end anchor without a start finishes at the stop nearest it', () => {
|
||||||
|
const a = { lat: 2, lng: 1 }
|
||||||
|
const b = { lat: 5, lng: 1 }
|
||||||
|
const c = { lat: 9, lng: 1 }
|
||||||
|
// a is nearest the end anchor, so the route must finish at a rather than start there.
|
||||||
|
const result = optimizeRoute([a, b, c], { end: { lat: 1, lng: 1 } })
|
||||||
|
expect(result[result.length - 1]).toEqual(a)
|
||||||
|
})
|
||||||
})
|
})
|
||||||
|
|
||||||
// ── generateGoogleMapsUrl ──────────────────────────────────────────────────────
|
// ── generateGoogleMapsUrl ──────────────────────────────────────────────────────
|
||||||
|
|||||||
@@ -1,4 +1,4 @@
|
|||||||
import type { RouteResult, RouteSegment, RouteWithLegs, Waypoint } from '../../types'
|
import type { RouteResult, RouteSegment, RouteWithLegs, Waypoint, RouteAnchors } from '../../types'
|
||||||
|
|
||||||
const OSRM_BASE = 'https://router.project-osrm.org/route/v1'
|
const OSRM_BASE = 'https://router.project-osrm.org/route/v1'
|
||||||
|
|
||||||
@@ -77,35 +77,98 @@ export function generateGoogleMapsUrl(places: Waypoint[]): string | null {
|
|||||||
return `https://www.google.com/maps/dir/${stops}`
|
return `https://www.google.com/maps/dir/${stops}`
|
||||||
}
|
}
|
||||||
|
|
||||||
/** Reorders waypoints using a nearest-neighbor heuristic to minimize total Euclidean distance. */
|
// Squared planar distance — enough for nearest-neighbor comparisons and cheaper than a full haversine.
|
||||||
export function optimizeRoute<T extends Waypoint>(places: T[]): T[] {
|
function sqDist(a: Waypoint, b: Waypoint): number {
|
||||||
const valid = places.filter((p) => p.lat && p.lng)
|
return (a.lat - b.lat) ** 2 + (a.lng - b.lng) ** 2
|
||||||
if (valid.length <= 2) return places
|
}
|
||||||
|
|
||||||
|
// Length of visiting `order` in sequence, optionally pinned to a fixed start and/or end anchor.
|
||||||
|
// With start === end this is a closed loop back to the anchor (a day out from and back to the hotel).
|
||||||
|
function tourLength(order: Waypoint[], start?: Waypoint, end?: Waypoint): number {
|
||||||
|
if (order.length === 0) return 0
|
||||||
|
let total = 0
|
||||||
|
if (start) total += Math.sqrt(sqDist(start, order[0]))
|
||||||
|
for (let i = 0; i < order.length - 1; i++) total += Math.sqrt(sqDist(order[i], order[i + 1]))
|
||||||
|
if (end) total += Math.sqrt(sqDist(order[order.length - 1], end))
|
||||||
|
return total
|
||||||
|
}
|
||||||
|
|
||||||
|
// Greedy nearest-neighbor ordering, seeded at the start anchor when there is one.
|
||||||
|
function nearestNeighborOrder<T extends Waypoint>(valid: T[], start?: Waypoint): T[] {
|
||||||
const visited = new Set<number>()
|
const visited = new Set<number>()
|
||||||
const result: T[] = []
|
const result: T[] = []
|
||||||
let current = valid[0]
|
let current: Waypoint
|
||||||
visited.add(0)
|
if (start) {
|
||||||
result.push(current)
|
current = start
|
||||||
|
} else {
|
||||||
|
current = valid[0]
|
||||||
|
visited.add(0)
|
||||||
|
result.push(valid[0])
|
||||||
|
}
|
||||||
while (result.length < valid.length) {
|
while (result.length < valid.length) {
|
||||||
let nearestIdx = -1
|
let nearestIdx = -1
|
||||||
let minDist = Infinity
|
let minDist = Infinity
|
||||||
for (let i = 0; i < valid.length; i++) {
|
for (let i = 0; i < valid.length; i++) {
|
||||||
if (visited.has(i)) continue
|
if (visited.has(i)) continue
|
||||||
const d = Math.sqrt(
|
const d = sqDist(valid[i], current)
|
||||||
Math.pow(valid[i].lat - current.lat, 2) + Math.pow(valid[i].lng - current.lng, 2)
|
|
||||||
)
|
|
||||||
if (d < minDist) { minDist = d; nearestIdx = i }
|
if (d < minDist) { minDist = d; nearestIdx = i }
|
||||||
}
|
}
|
||||||
if (nearestIdx === -1) break
|
if (nearestIdx === -1) break
|
||||||
visited.add(nearestIdx)
|
visited.add(nearestIdx)
|
||||||
current = valid[nearestIdx]
|
current = valid[nearestIdx]
|
||||||
result.push(current)
|
result.push(valid[nearestIdx])
|
||||||
}
|
}
|
||||||
return result
|
return result
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// 2-opt: repeatedly reverse a sub-segment whenever it shortens the tour. This removes the crossings
|
||||||
|
// a pure nearest-neighbor pass leaves behind. The start/end anchors stay fixed, so a round trip
|
||||||
|
// (start === end) is untangled into a clean loop rather than an open path.
|
||||||
|
function twoOptImprove<T extends Waypoint>(order: T[], start?: Waypoint, end?: Waypoint): T[] {
|
||||||
|
if (order.length < 3) return order
|
||||||
|
let best = order
|
||||||
|
let bestLen = tourLength(best, start, end)
|
||||||
|
let improved = true
|
||||||
|
while (improved) {
|
||||||
|
improved = false
|
||||||
|
for (let i = 0; i < best.length - 1; i++) {
|
||||||
|
for (let j = i + 1; j < best.length; j++) {
|
||||||
|
const candidate = best.slice(0, i).concat(best.slice(i, j + 1).reverse(), best.slice(j + 1))
|
||||||
|
const len = tourLength(candidate, start, end)
|
||||||
|
if (len < bestLen - 1e-12) {
|
||||||
|
best = candidate
|
||||||
|
bestLen = len
|
||||||
|
improved = true
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return best
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Reorders waypoints to minimize travel distance: a nearest-neighbor pass for a good starting order,
|
||||||
|
* then 2-opt to untangle crossings. Optional anchors (e.g. the day's accommodation) pin the route's
|
||||||
|
* ends — start === end makes it a loop out from and back to the hotel; a transfer day runs start → end.
|
||||||
|
*/
|
||||||
|
export function optimizeRoute<T extends Waypoint>(places: T[], anchors: RouteAnchors = {}): T[] {
|
||||||
|
const { start, end } = anchors
|
||||||
|
const valid = places.filter((p) => p.lat && p.lng)
|
||||||
|
if (valid.length <= 1) return places
|
||||||
|
// Two unanchored stops have no meaningful order to optimize; anchors can still flip them.
|
||||||
|
if (valid.length === 2 && !start && !end) return places
|
||||||
|
|
||||||
|
const order = twoOptImprove(nearestNeighborOrder(valid, start), start, end)
|
||||||
|
|
||||||
|
// A round trip's loop direction is arbitrary, so orient it to begin at the stop nearest the hotel —
|
||||||
|
// that reads naturally as "leave the hotel, head to the closest place, …, come back".
|
||||||
|
if (start && end && start.lat === end.lat && start.lng === end.lng && order.length > 1) {
|
||||||
|
if (sqDist(order[order.length - 1], start) < sqDist(order[0], start)) order.reverse()
|
||||||
|
}
|
||||||
|
|
||||||
|
return order
|
||||||
|
}
|
||||||
|
|
||||||
/** Fetches per-leg distance/duration from OSRM and returns segment metadata (midpoints, walking/driving times). */
|
/** Fetches per-leg distance/duration from OSRM and returns segment metadata (midpoints, walking/driving times). */
|
||||||
export async function calculateSegments(
|
export async function calculateSegments(
|
||||||
waypoints: Waypoint[],
|
waypoints: Waypoint[],
|
||||||
|
|||||||
@@ -982,7 +982,7 @@ describe('DayPlanSidebar', () => {
|
|||||||
}
|
}
|
||||||
})
|
})
|
||||||
|
|
||||||
it('FE-PLANNER-DAYPLAN-065: note card delete button calls deleteNote', async () => {
|
it('FE-PLANNER-DAYPLAN-065: deleting a note asks for confirmation before calling deleteNote', async () => {
|
||||||
const user = userEvent.setup()
|
const user = userEvent.setup()
|
||||||
const day = buildDay({ id: 10, date: '2025-06-01', title: 'Day 1' })
|
const day = buildDay({ id: 10, date: '2025-06-01', title: 'Day 1' })
|
||||||
const note = buildDayNote({ id: 55, day_id: 10, text: 'My note' })
|
const note = buildDayNote({ id: 55, day_id: 10, text: 'My note' })
|
||||||
@@ -992,6 +992,11 @@ describe('DayPlanSidebar', () => {
|
|||||||
const noteEditBtns = document.querySelectorAll('.note-edit-buttons button')
|
const noteEditBtns = document.querySelectorAll('.note-edit-buttons button')
|
||||||
if (noteEditBtns.length > 1) {
|
if (noteEditBtns.length > 1) {
|
||||||
await user.click(noteEditBtns[1] as HTMLElement)
|
await user.click(noteEditBtns[1] as HTMLElement)
|
||||||
|
// Clicking delete opens a confirmation dialog rather than deleting immediately.
|
||||||
|
expect(mockDayNotesState.deleteNote).not.toHaveBeenCalled()
|
||||||
|
expect(screen.getByText('Delete note?')).toBeInTheDocument()
|
||||||
|
// Confirming triggers the actual delete.
|
||||||
|
await user.click(screen.getByRole('button', { name: /^delete$/i }))
|
||||||
expect(mockDayNotesState.deleteNote).toHaveBeenCalled()
|
expect(mockDayNotesState.deleteNote).toHaveBeenCalled()
|
||||||
}
|
}
|
||||||
})
|
})
|
||||||
|
|||||||
@@ -7,6 +7,7 @@ import { ChevronDown, ChevronRight, ChevronUp, Navigation, RotateCcw, ExternalLi
|
|||||||
import { assignmentsApi, reservationsApi } from '../../api/client'
|
import { assignmentsApi, reservationsApi } from '../../api/client'
|
||||||
import { calculateRoute, calculateRouteWithLegs, optimizeRoute } from '../Map/RouteCalculator'
|
import { calculateRoute, calculateRouteWithLegs, optimizeRoute } from '../Map/RouteCalculator'
|
||||||
import PlaceAvatar from '../shared/PlaceAvatar'
|
import PlaceAvatar from '../shared/PlaceAvatar'
|
||||||
|
import ConfirmDialog from '../shared/ConfirmDialog'
|
||||||
import { useContextMenu, ContextMenu } from '../shared/ContextMenu'
|
import { useContextMenu, ContextMenu } from '../shared/ContextMenu'
|
||||||
import Markdown from 'react-markdown'
|
import Markdown from 'react-markdown'
|
||||||
import remarkGfm from 'remark-gfm'
|
import remarkGfm from 'remark-gfm'
|
||||||
@@ -17,7 +18,7 @@ import { useTripStore } from '../../store/tripStore'
|
|||||||
import { useCanDo } from '../../store/permissionsStore'
|
import { useCanDo } from '../../store/permissionsStore'
|
||||||
import { useSettingsStore } from '../../store/settingsStore'
|
import { useSettingsStore } from '../../store/settingsStore'
|
||||||
import { useTranslation } from '../../i18n'
|
import { useTranslation } from '../../i18n'
|
||||||
import { isDayInAccommodationRange } from '../../utils/dayOrder'
|
import { isDayInAccommodationRange, getAccommodationAnchors } from '../../utils/dayOrder'
|
||||||
import {
|
import {
|
||||||
TRANSPORT_TYPES, parseTimeToMinutes, getSpanPhase, getDisplayTimeForDay,
|
TRANSPORT_TYPES, parseTimeToMinutes, getSpanPhase, getDisplayTimeForDay,
|
||||||
getTransportForDay as _getTransportForDay, getMergedItems as _getMergedItems,
|
getTransportForDay as _getTransportForDay, getMergedItems as _getMergedItems,
|
||||||
@@ -451,6 +452,10 @@ function useDayPlanSidebar(props: DayPlanSidebarProps) {
|
|||||||
_openEditNote(dayId, note)
|
_openEditNote(dayId, note)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Deleting a note asks for confirmation first — the edit/delete icons sit close together and are
|
||||||
|
// easy to mis-tap on touch devices, where an accidental delete was previously unrecoverable.
|
||||||
|
const [pendingDeleteNote, setPendingDeleteNote] = useState<{ dayId: number; noteId: number } | null>(null)
|
||||||
|
|
||||||
const deleteNote = async (dayId: number, noteId: number, e?: React.MouseEvent) => {
|
const deleteNote = async (dayId: number, noteId: number, e?: React.MouseEvent) => {
|
||||||
e?.stopPropagation()
|
e?.stopPropagation()
|
||||||
await _deleteNote(dayId, noteId)
|
await _deleteNote(dayId, noteId)
|
||||||
@@ -703,8 +708,14 @@ function useDayPlanSidebar(props: DayPlanSidebarProps) {
|
|||||||
// Optimize only unlocked assignments (work on assignments, not places)
|
// Optimize only unlocked assignments (work on assignments, not places)
|
||||||
const unlockedWithCoords = unlocked.filter(a => a.place?.lat && a.place?.lng)
|
const unlockedWithCoords = unlocked.filter(a => a.place?.lat && a.place?.lng)
|
||||||
const unlockedNoCoords = unlocked.filter(a => !a.place?.lat || !a.place?.lng)
|
const unlockedNoCoords = unlocked.filter(a => !a.place?.lat || !a.place?.lng)
|
||||||
|
// Anchor the route on the day's accommodation (when enabled): a loop out from and back to the
|
||||||
|
// hotel, or — on a transfer day — a run from the hotel you leave to the one you arrive at.
|
||||||
|
const day = days.find(d => d.id === selectedDayId)
|
||||||
|
const anchors = day && useSettingsStore.getState().settings.optimize_from_accommodation !== false
|
||||||
|
? getAccommodationAnchors(day, days, accommodations)
|
||||||
|
: {}
|
||||||
const optimizedAssignments = unlockedWithCoords.length >= 2
|
const optimizedAssignments = unlockedWithCoords.length >= 2
|
||||||
? optimizeRoute(unlockedWithCoords.map(a => ({ ...a.place, _assignmentId: a.id }))).map(p => unlockedWithCoords.find(a => a.id === p._assignmentId)).filter(Boolean)
|
? optimizeRoute(unlockedWithCoords.map(a => ({ ...a.place, _assignmentId: a.id })), anchors).map(p => unlockedWithCoords.find(a => a.id === p._assignmentId)).filter(Boolean)
|
||||||
: unlockedWithCoords
|
: unlockedWithCoords
|
||||||
const optimizedQueue = [...optimizedAssignments, ...unlockedNoCoords]
|
const optimizedQueue = [...optimizedAssignments, ...unlockedNoCoords]
|
||||||
|
|
||||||
@@ -717,7 +728,8 @@ function useDayPlanSidebar(props: DayPlanSidebarProps) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
await onReorder(selectedDayId, result.map(a => a.id))
|
await onReorder(selectedDayId, result.map(a => a.id))
|
||||||
toast.success(t('dayplan.toast.routeOptimized'))
|
const usedHotel = !!(anchors.start || anchors.end)
|
||||||
|
toast.success(usedHotel ? t('dayplan.toast.routeOptimizedFromHotel') : t('dayplan.toast.routeOptimized'))
|
||||||
const capturedDayId = selectedDayId
|
const capturedDayId = selectedDayId
|
||||||
pushUndo?.(t('undo.optimize'), async () => {
|
pushUndo?.(t('undo.optimize'), async () => {
|
||||||
await tripActions.reorderAssignments(tripId, capturedDayId, prevIds)
|
await tripActions.reorderAssignments(tripId, capturedDayId, prevIds)
|
||||||
@@ -851,6 +863,8 @@ function useDayPlanSidebar(props: DayPlanSidebarProps) {
|
|||||||
cancelNote,
|
cancelNote,
|
||||||
saveNote,
|
saveNote,
|
||||||
deleteNote,
|
deleteNote,
|
||||||
|
pendingDeleteNote,
|
||||||
|
setPendingDeleteNote,
|
||||||
moveNote,
|
moveNote,
|
||||||
expandedDays,
|
expandedDays,
|
||||||
setExpandedDays,
|
setExpandedDays,
|
||||||
@@ -993,6 +1007,8 @@ const DayPlanSidebar = React.memo(function DayPlanSidebar(props: DayPlanSidebarP
|
|||||||
cancelNote,
|
cancelNote,
|
||||||
saveNote,
|
saveNote,
|
||||||
deleteNote,
|
deleteNote,
|
||||||
|
pendingDeleteNote,
|
||||||
|
setPendingDeleteNote,
|
||||||
moveNote,
|
moveNote,
|
||||||
expandedDays,
|
expandedDays,
|
||||||
setExpandedDays,
|
setExpandedDays,
|
||||||
@@ -1908,7 +1924,7 @@ const DayPlanSidebar = React.memo(function DayPlanSidebar(props: DayPlanSidebarP
|
|||||||
onContextMenu={canEditDays ? e => ctxMenu.open(e, [
|
onContextMenu={canEditDays ? e => ctxMenu.open(e, [
|
||||||
{ label: t('common.edit'), icon: Pencil, onClick: () => openEditNote(day.id, note) },
|
{ label: t('common.edit'), icon: Pencil, onClick: () => openEditNote(day.id, note) },
|
||||||
{ divider: true },
|
{ divider: true },
|
||||||
{ label: t('common.delete'), icon: Trash2, danger: true, onClick: () => deleteNote(day.id, note.id) },
|
{ label: t('common.delete'), icon: Trash2, danger: true, onClick: () => setPendingDeleteNote({ dayId: day.id, noteId: note.id }) },
|
||||||
]) : undefined}
|
]) : undefined}
|
||||||
onMouseEnter={e => {
|
onMouseEnter={e => {
|
||||||
const grip = e.currentTarget.querySelector('.dp-grip') as HTMLElement | null
|
const grip = e.currentTarget.querySelector('.dp-grip') as HTMLElement | null
|
||||||
@@ -1950,7 +1966,7 @@ const DayPlanSidebar = React.memo(function DayPlanSidebar(props: DayPlanSidebarP
|
|||||||
</div>
|
</div>
|
||||||
{canEditDays && <div className="note-edit-buttons" style={{ display: 'flex', gap: 1, flexShrink: 0, opacity: 0, transition: 'opacity 0.15s' }}>
|
{canEditDays && <div className="note-edit-buttons" style={{ display: 'flex', gap: 1, flexShrink: 0, opacity: 0, transition: 'opacity 0.15s' }}>
|
||||||
<button onClick={e => openEditNote(day.id, note, e)} className="text-content-faint" style={{ background: 'none', border: 'none', padding: 2, cursor: 'pointer', display: 'flex' }}><Pencil size={10} /></button>
|
<button onClick={e => openEditNote(day.id, note, e)} className="text-content-faint" style={{ background: 'none', border: 'none', padding: 2, cursor: 'pointer', display: 'flex' }}><Pencil size={10} /></button>
|
||||||
<button onClick={e => deleteNote(day.id, note.id, e)} className="text-content-faint" style={{ background: 'none', border: 'none', padding: 2, cursor: 'pointer', display: 'flex' }}><Trash2 size={10} /></button>
|
<button onClick={e => { e.stopPropagation(); setPendingDeleteNote({ dayId: day.id, noteId: note.id }) }} className="text-content-faint" style={{ background: 'none', border: 'none', padding: 2, cursor: 'pointer', display: 'flex' }}><Trash2 size={10} /></button>
|
||||||
</div>}
|
</div>}
|
||||||
{canEditDays && <div className="reorder-buttons" style={{ flexShrink: 0, display: 'flex', gap: 1, transition: 'opacity 0.15s' }}>
|
{canEditDays && <div className="reorder-buttons" style={{ flexShrink: 0, display: 'flex', gap: 1, transition: 'opacity 0.15s' }}>
|
||||||
<button onClick={e => { e.stopPropagation(); moveNote(day.id, note.id, 'up') }} disabled={noteIdx === 0} className={noteIdx === 0 ? 'text-[var(--border-primary)]' : 'text-content-faint'} style={{ background: 'none', border: 'none', padding: '1px 2px', cursor: noteIdx === 0 ? 'default' : 'pointer', display: 'flex', lineHeight: 1 }}><ChevronUp size={12} strokeWidth={2} /></button>
|
<button onClick={e => { e.stopPropagation(); moveNote(day.id, note.id, 'up') }} disabled={noteIdx === 0} className={noteIdx === 0 ? 'text-[var(--border-primary)]' : 'text-content-faint'} style={{ background: 'none', border: 'none', padding: '1px 2px', cursor: noteIdx === 0 ? 'default' : 'pointer', display: 'flex', lineHeight: 1 }}><ChevronUp size={12} strokeWidth={2} /></button>
|
||||||
@@ -2093,6 +2109,15 @@ const DayPlanSidebar = React.memo(function DayPlanSidebar(props: DayPlanSidebarP
|
|||||||
t={t}
|
t={t}
|
||||||
/>
|
/>
|
||||||
|
|
||||||
|
{/* Confirm: delete a day note — guards against accidental taps on touch devices */}
|
||||||
|
<ConfirmDialog
|
||||||
|
isOpen={!!pendingDeleteNote}
|
||||||
|
onClose={() => setPendingDeleteNote(null)}
|
||||||
|
onConfirm={() => { if (pendingDeleteNote) deleteNote(pendingDeleteNote.dayId, pendingDeleteNote.noteId) }}
|
||||||
|
title={t('dayplan.confirmDeleteNoteTitle')}
|
||||||
|
message={t('dayplan.confirmDeleteNoteBody')}
|
||||||
|
/>
|
||||||
|
|
||||||
{/* Transport-Detail-Modal */}
|
{/* Transport-Detail-Modal */}
|
||||||
<DayPlanSidebarTransportDetailModal
|
<DayPlanSidebarTransportDetailModal
|
||||||
transportDetail={transportDetail}
|
transportDetail={transportDetail}
|
||||||
|
|||||||
@@ -291,6 +291,37 @@ export default function DisplaySettingsTab(): React.ReactElement {
|
|||||||
))}
|
))}
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
|
{/* Optimize route from accommodation */}
|
||||||
|
<div>
|
||||||
|
<label className="block text-sm font-medium mb-2 text-content-secondary">{t('settings.optimizeFromAccommodation')}</label>
|
||||||
|
<div className="flex gap-3">
|
||||||
|
{[
|
||||||
|
{ value: true, label: t('settings.on') || 'On' },
|
||||||
|
{ value: false, label: t('settings.off') || 'Off' },
|
||||||
|
].map(opt => (
|
||||||
|
<button
|
||||||
|
key={String(opt.value)}
|
||||||
|
onClick={async () => {
|
||||||
|
try { await updateSetting('optimize_from_accommodation', opt.value) }
|
||||||
|
catch (e: unknown) { toast.error(e instanceof Error ? e.message : t('common.error')) }
|
||||||
|
}}
|
||||||
|
style={{
|
||||||
|
display: 'flex', alignItems: 'center', gap: 8,
|
||||||
|
padding: '10px 20px', borderRadius: 10, cursor: 'pointer',
|
||||||
|
fontFamily: 'inherit', fontSize: 14, fontWeight: 500,
|
||||||
|
border: (settings.optimize_from_accommodation !== false) === opt.value ? '2px solid var(--text-primary)' : '2px solid var(--border-primary)',
|
||||||
|
background: (settings.optimize_from_accommodation !== false) === opt.value ? 'var(--bg-hover)' : 'var(--bg-card)',
|
||||||
|
color: 'var(--text-primary)',
|
||||||
|
transition: 'all 0.15s',
|
||||||
|
}}
|
||||||
|
>
|
||||||
|
{opt.label}
|
||||||
|
</button>
|
||||||
|
))}
|
||||||
|
</div>
|
||||||
|
<p className="text-xs mt-1 text-content-faint">{t('settings.optimizeFromAccommodationHint')}</p>
|
||||||
|
</div>
|
||||||
</Section>
|
</Section>
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -32,6 +32,7 @@ export const useSettingsStore = create<SettingsState>((set, get) => ({
|
|||||||
temperature_unit: 'fahrenheit',
|
temperature_unit: 'fahrenheit',
|
||||||
time_format: '12h',
|
time_format: '12h',
|
||||||
show_place_description: false,
|
show_place_description: false,
|
||||||
|
optimize_from_accommodation: true,
|
||||||
map_provider: 'leaflet',
|
map_provider: 'leaflet',
|
||||||
mapbox_access_token: '',
|
mapbox_access_token: '',
|
||||||
mapbox_style: 'mapbox://styles/mapbox/standard',
|
mapbox_style: 'mapbox://styles/mapbox/standard',
|
||||||
|
|||||||
@@ -113,6 +113,7 @@ export interface Settings {
|
|||||||
show_place_description: boolean
|
show_place_description: boolean
|
||||||
blur_booking_codes?: boolean
|
blur_booking_codes?: boolean
|
||||||
map_booking_labels?: boolean
|
map_booking_labels?: boolean
|
||||||
|
optimize_from_accommodation?: boolean
|
||||||
map_provider?: 'leaflet' | 'mapbox-gl'
|
map_provider?: 'leaflet' | 'mapbox-gl'
|
||||||
mapbox_access_token?: string
|
mapbox_access_token?: string
|
||||||
mapbox_style?: string
|
mapbox_style?: string
|
||||||
@@ -162,6 +163,12 @@ export interface Waypoint {
|
|||||||
lng: number
|
lng: number
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Optional fixed start/end points for route optimization (e.g. the day's accommodation).
|
||||||
|
export interface RouteAnchors {
|
||||||
|
start?: Waypoint
|
||||||
|
end?: Waypoint
|
||||||
|
}
|
||||||
|
|
||||||
// User with optional OIDC fields
|
// User with optional OIDC fields
|
||||||
export interface UserWithOidc extends User {
|
export interface UserWithOidc extends User {
|
||||||
oidc_issuer?: string | null
|
oidc_issuer?: string | null
|
||||||
|
|||||||
@@ -0,0 +1,73 @@
|
|||||||
|
import { describe, it, expect } from 'vitest'
|
||||||
|
import type { Day, Accommodation } from '../types'
|
||||||
|
import { getDayOrder, isDayInAccommodationRange, getAccommodationAnchors } from './dayOrder'
|
||||||
|
|
||||||
|
const days = [
|
||||||
|
{ id: 10, day_number: 1 },
|
||||||
|
{ id: 20, day_number: 2 },
|
||||||
|
{ id: 30, day_number: 3 },
|
||||||
|
] as unknown as Day[]
|
||||||
|
|
||||||
|
const hotel = (over: Partial<Accommodation>): Accommodation =>
|
||||||
|
({ place_lat: 48.1, place_lng: 11.5, start_day_id: 10, end_day_id: 30, ...over }) as Accommodation
|
||||||
|
|
||||||
|
describe('getDayOrder', () => {
|
||||||
|
it('prefers day_number when present', () => {
|
||||||
|
expect(getDayOrder(days[1], days)).toBe(2)
|
||||||
|
})
|
||||||
|
it('falls back to array index when day_number is missing', () => {
|
||||||
|
const noNumber = [{ id: 5 }, { id: 6 }] as unknown as Day[]
|
||||||
|
expect(getDayOrder(noNumber[1], noNumber)).toBe(1)
|
||||||
|
})
|
||||||
|
})
|
||||||
|
|
||||||
|
describe('isDayInAccommodationRange', () => {
|
||||||
|
it('is inclusive of both the check-in and check-out day', () => {
|
||||||
|
expect(isDayInAccommodationRange(days[0], 10, 30, days)).toBe(true) // check-in morning
|
||||||
|
expect(isDayInAccommodationRange(days[1], 10, 30, days)).toBe(true) // mid-stay
|
||||||
|
expect(isDayInAccommodationRange(days[2], 10, 30, days)).toBe(true) // check-out day
|
||||||
|
})
|
||||||
|
it('excludes days outside the stay', () => {
|
||||||
|
expect(isDayInAccommodationRange(days[0], 20, 30, days)).toBe(false)
|
||||||
|
})
|
||||||
|
})
|
||||||
|
|
||||||
|
describe('getAccommodationAnchors', () => {
|
||||||
|
it('returns no anchors when the day has no accommodation', () => {
|
||||||
|
expect(getAccommodationAnchors(days[1], days, [])).toEqual({})
|
||||||
|
})
|
||||||
|
|
||||||
|
it('anchors both ends to the same hotel on a mid-stay day (round trip)', () => {
|
||||||
|
const accs = [hotel({ start_day_id: 10, end_day_id: 30, place_lat: 48.1, place_lng: 11.5 })]
|
||||||
|
expect(getAccommodationAnchors(days[1], days, accs)).toEqual({
|
||||||
|
start: { lat: 48.1, lng: 11.5 },
|
||||||
|
end: { lat: 48.1, lng: 11.5 },
|
||||||
|
})
|
||||||
|
})
|
||||||
|
|
||||||
|
it('loops a single hotel on its check-out day (home base for the day)', () => {
|
||||||
|
const accs = [hotel({ start_day_id: 10, end_day_id: 20, place_lat: 1, place_lng: 2 })]
|
||||||
|
expect(getAccommodationAnchors(days[1], days, accs)).toEqual({ start: { lat: 1, lng: 2 }, end: { lat: 1, lng: 2 } })
|
||||||
|
})
|
||||||
|
|
||||||
|
it('loops a single hotel on its check-in day (home base for the day)', () => {
|
||||||
|
const accs = [hotel({ start_day_id: 20, end_day_id: 30, place_lat: 3, place_lng: 4 })]
|
||||||
|
expect(getAccommodationAnchors(days[1], days, accs)).toEqual({ start: { lat: 3, lng: 4 }, end: { lat: 3, lng: 4 } })
|
||||||
|
})
|
||||||
|
|
||||||
|
it('uses the checked-out hotel as start and the checked-in hotel as end on a transfer day', () => {
|
||||||
|
const accs = [
|
||||||
|
hotel({ start_day_id: 10, end_day_id: 20, place_lat: 1, place_lng: 1 }), // checkout today
|
||||||
|
hotel({ start_day_id: 20, end_day_id: 30, place_lat: 9, place_lng: 9 }), // check-in today
|
||||||
|
]
|
||||||
|
expect(getAccommodationAnchors(days[1], days, accs)).toEqual({
|
||||||
|
start: { lat: 1, lng: 1 },
|
||||||
|
end: { lat: 9, lng: 9 },
|
||||||
|
})
|
||||||
|
})
|
||||||
|
|
||||||
|
it('ignores accommodations that have no coordinates', () => {
|
||||||
|
const accs = [hotel({ start_day_id: 10, end_day_id: 30, place_lat: null, place_lng: null })]
|
||||||
|
expect(getAccommodationAnchors(days[1], days, accs)).toEqual({})
|
||||||
|
})
|
||||||
|
})
|
||||||
@@ -1,8 +1,34 @@
|
|||||||
import type { Day } from '../types'
|
import type { Day, Accommodation, RouteAnchors } from '../types'
|
||||||
|
|
||||||
export const getDayOrder = (day: Day, days: Day[]): number =>
|
export const getDayOrder = (day: Day, days: Day[]): number =>
|
||||||
day.day_number ?? days.indexOf(day)
|
day.day_number ?? days.indexOf(day)
|
||||||
|
|
||||||
|
// Derives route anchors from the accommodation(s) active on a day. A single hotel is the day's home
|
||||||
|
// base, so the route is a loop that starts and ends there. A transfer day — checking out of one hotel
|
||||||
|
// and into another — instead runs from the morning hotel to the evening one.
|
||||||
|
export const getAccommodationAnchors = (
|
||||||
|
day: Day,
|
||||||
|
days: Day[],
|
||||||
|
accommodations: Accommodation[],
|
||||||
|
): RouteAnchors => {
|
||||||
|
const located = accommodations.filter(a =>
|
||||||
|
a.place_lat != null && a.place_lng != null &&
|
||||||
|
isDayInAccommodationRange(day, a.start_day_id, a.end_day_id, days),
|
||||||
|
)
|
||||||
|
if (located.length === 0) return {}
|
||||||
|
|
||||||
|
const toAnchor = (a: Accommodation) => ({ lat: a.place_lat as number, lng: a.place_lng as number })
|
||||||
|
|
||||||
|
const checkOut = located.find(a => a.end_day_id === day.id) // the hotel you leave this morning
|
||||||
|
const checkIn = located.find(a => a.start_day_id === day.id) // the hotel you arrive at tonight
|
||||||
|
if (checkOut && checkIn && checkOut !== checkIn) {
|
||||||
|
return { start: toAnchor(checkOut), end: toAnchor(checkIn) }
|
||||||
|
}
|
||||||
|
|
||||||
|
const hotel = toAnchor(located[0])
|
||||||
|
return { start: hotel, end: hotel }
|
||||||
|
}
|
||||||
|
|
||||||
export const isDayInAccommodationRange = (
|
export const isDayInAccommodationRange = (
|
||||||
day: Day,
|
day: Day,
|
||||||
startDayId: number,
|
startDayId: number,
|
||||||
|
|||||||
@@ -39,6 +39,8 @@ const collab: TranslationStrings = {
|
|||||||
'collab.notes.cancel': 'إلغاء',
|
'collab.notes.cancel': 'إلغاء',
|
||||||
'collab.notes.edit': 'تعديل',
|
'collab.notes.edit': 'تعديل',
|
||||||
'collab.notes.delete': 'حذف',
|
'collab.notes.delete': 'حذف',
|
||||||
|
'collab.notes.confirmDeleteTitle': 'حذف الملاحظة؟',
|
||||||
|
'collab.notes.confirmDeleteBody': 'سيتم حذف هذه الملاحظة نهائيًا.',
|
||||||
'collab.notes.pin': 'تثبيت',
|
'collab.notes.pin': 'تثبيت',
|
||||||
'collab.notes.unpin': 'إلغاء التثبيت',
|
'collab.notes.unpin': 'إلغاء التثبيت',
|
||||||
'collab.notes.daysAgo': 'منذ {n} يوم',
|
'collab.notes.daysAgo': 'منذ {n} يوم',
|
||||||
|
|||||||
@@ -9,6 +9,8 @@ const dayplan: TranslationStrings = {
|
|||||||
'dayplan.confirmRemoveTimeBody':
|
'dayplan.confirmRemoveTimeBody':
|
||||||
'هذا المكان له وقت ثابت ({time}). نقله سيزيل الوقت ويسمح بالترتيب الحر.',
|
'هذا المكان له وقت ثابت ({time}). نقله سيزيل الوقت ويسمح بالترتيب الحر.',
|
||||||
'dayplan.confirmRemoveTimeAction': 'إزالة الوقت ونقل',
|
'dayplan.confirmRemoveTimeAction': 'إزالة الوقت ونقل',
|
||||||
|
'dayplan.confirmDeleteNoteTitle': 'حذف الملاحظة؟',
|
||||||
|
'dayplan.confirmDeleteNoteBody': 'سيتم حذف هذه الملاحظة نهائيًا.',
|
||||||
'dayplan.cannotDropOnTimed':
|
'dayplan.cannotDropOnTimed':
|
||||||
'لا يمكن وضع العناصر بين الإدخالات المرتبطة بوقت',
|
'لا يمكن وضع العناصر بين الإدخالات المرتبطة بوقت',
|
||||||
'dayplan.cannotBreakChronology':
|
'dayplan.cannotBreakChronology':
|
||||||
@@ -29,6 +31,7 @@ const dayplan: TranslationStrings = {
|
|||||||
'dayplan.routeError': 'فشل حساب المسار',
|
'dayplan.routeError': 'فشل حساب المسار',
|
||||||
'dayplan.toast.needTwoPlaces': 'يلزم مكانان على الأقل لتحسين المسار',
|
'dayplan.toast.needTwoPlaces': 'يلزم مكانان على الأقل لتحسين المسار',
|
||||||
'dayplan.toast.routeOptimized': 'تم تحسين المسار',
|
'dayplan.toast.routeOptimized': 'تم تحسين المسار',
|
||||||
|
'dayplan.toast.routeOptimizedFromHotel': 'تم تحسين المسار انطلاقًا من مكان إقامتك',
|
||||||
'dayplan.toast.noGeoPlaces': 'لم يتم العثور على أماكن بإحداثيات لحساب المسار',
|
'dayplan.toast.noGeoPlaces': 'لم يتم العثور على أماكن بإحداثيات لحساب المسار',
|
||||||
'dayplan.confirmed': 'مؤكد',
|
'dayplan.confirmed': 'مؤكد',
|
||||||
'dayplan.pendingRes': 'قيد الانتظار',
|
'dayplan.pendingRes': 'قيد الانتظار',
|
||||||
|
|||||||
@@ -60,6 +60,9 @@ const settings: TranslationStrings = {
|
|||||||
'settings.bookingLabelsHint':
|
'settings.bookingLabelsHint':
|
||||||
'عرض أسماء المحطات/المطارات على الخريطة. عند الإيقاف، يتم عرض الرمز فقط.',
|
'عرض أسماء المحطات/المطارات على الخريطة. عند الإيقاف، يتم عرض الرمز فقط.',
|
||||||
'settings.blurBookingCodes': 'إخفاء رموز الحجز',
|
'settings.blurBookingCodes': 'إخفاء رموز الحجز',
|
||||||
|
'settings.optimizeFromAccommodation': 'تحسين المسار انطلاقًا من مكان الإقامة',
|
||||||
|
'settings.optimizeFromAccommodationHint':
|
||||||
|
'عند تحسين يوم ما، يبدأ المسار من الفندق الذي تستيقظ فيه وينتهي عند الفندق الذي تسجّل الوصول إليه في تلك الليلة.',
|
||||||
'settings.notifications': 'الإشعارات',
|
'settings.notifications': 'الإشعارات',
|
||||||
'settings.notifyTripInvite': 'دعوات الرحلات',
|
'settings.notifyTripInvite': 'دعوات الرحلات',
|
||||||
'settings.notifyBookingChange': 'تغييرات الحجز',
|
'settings.notifyBookingChange': 'تغييرات الحجز',
|
||||||
|
|||||||
@@ -41,6 +41,8 @@ const collab: TranslationStrings = {
|
|||||||
'collab.notes.cancel': 'Cancelar',
|
'collab.notes.cancel': 'Cancelar',
|
||||||
'collab.notes.edit': 'Editar',
|
'collab.notes.edit': 'Editar',
|
||||||
'collab.notes.delete': 'Excluir',
|
'collab.notes.delete': 'Excluir',
|
||||||
|
'collab.notes.confirmDeleteTitle': 'Excluir nota?',
|
||||||
|
'collab.notes.confirmDeleteBody': 'Esta nota será excluída permanentemente.',
|
||||||
'collab.notes.pin': 'Fixar',
|
'collab.notes.pin': 'Fixar',
|
||||||
'collab.notes.unpin': 'Desafixar',
|
'collab.notes.unpin': 'Desafixar',
|
||||||
'collab.notes.daysAgo': 'há {n} d',
|
'collab.notes.daysAgo': 'há {n} d',
|
||||||
|
|||||||
@@ -20,6 +20,8 @@ const dayplan: TranslationStrings = {
|
|||||||
'dayplan.toast.needTwoPlaces':
|
'dayplan.toast.needTwoPlaces':
|
||||||
'São necessários pelo menos dois lugares para otimizar a rota',
|
'São necessários pelo menos dois lugares para otimizar a rota',
|
||||||
'dayplan.toast.routeOptimized': 'Rota otimizada',
|
'dayplan.toast.routeOptimized': 'Rota otimizada',
|
||||||
|
'dayplan.toast.routeOptimizedFromHotel':
|
||||||
|
'Rota otimizada a partir da sua hospedagem',
|
||||||
'dayplan.toast.noGeoPlaces':
|
'dayplan.toast.noGeoPlaces':
|
||||||
'Nenhum lugar com coordenadas para calcular a rota',
|
'Nenhum lugar com coordenadas para calcular a rota',
|
||||||
'dayplan.confirmed': 'Confirmada',
|
'dayplan.confirmed': 'Confirmada',
|
||||||
@@ -33,6 +35,8 @@ const dayplan: TranslationStrings = {
|
|||||||
'dayplan.confirmRemoveTimeBody':
|
'dayplan.confirmRemoveTimeBody':
|
||||||
'Este lugar tem um horário fixo ({time}). Movê-lo removerá o horário e permitirá ordenação livre.',
|
'Este lugar tem um horário fixo ({time}). Movê-lo removerá o horário e permitirá ordenação livre.',
|
||||||
'dayplan.confirmRemoveTimeAction': 'Remover horário e mover',
|
'dayplan.confirmRemoveTimeAction': 'Remover horário e mover',
|
||||||
|
'dayplan.confirmDeleteNoteTitle': 'Excluir nota?',
|
||||||
|
'dayplan.confirmDeleteNoteBody': 'Esta nota será excluída permanentemente.',
|
||||||
'dayplan.cannotDropOnTimed':
|
'dayplan.cannotDropOnTimed':
|
||||||
'Itens não podem ser colocados entre entradas com horário fixo',
|
'Itens não podem ser colocados entre entradas com horário fixo',
|
||||||
'dayplan.cannotBreakChronology':
|
'dayplan.cannotBreakChronology':
|
||||||
|
|||||||
@@ -62,6 +62,9 @@ const settings: TranslationStrings = {
|
|||||||
'settings.temperature': 'Unidade de temperatura',
|
'settings.temperature': 'Unidade de temperatura',
|
||||||
'settings.timeFormat': 'Formato de hora',
|
'settings.timeFormat': 'Formato de hora',
|
||||||
'settings.blurBookingCodes': 'Ocultar códigos de reserva',
|
'settings.blurBookingCodes': 'Ocultar códigos de reserva',
|
||||||
|
'settings.optimizeFromAccommodation': 'Otimizar rota a partir da hospedagem',
|
||||||
|
'settings.optimizeFromAccommodationHint':
|
||||||
|
'Ao otimizar um dia, comece a rota no hotel onde você acorda e termine no hotel em que você faz check-in à noite.',
|
||||||
'settings.notifications': 'Notificações',
|
'settings.notifications': 'Notificações',
|
||||||
'settings.notifyTripInvite': 'Convites de viagem',
|
'settings.notifyTripInvite': 'Convites de viagem',
|
||||||
'settings.notifyBookingChange': 'Alterações de reserva',
|
'settings.notifyBookingChange': 'Alterações de reserva',
|
||||||
|
|||||||
@@ -36,6 +36,8 @@ const collab: TranslationStrings = {
|
|||||||
'collab.notes.cancel': 'Zrušit',
|
'collab.notes.cancel': 'Zrušit',
|
||||||
'collab.notes.edit': 'Upravit',
|
'collab.notes.edit': 'Upravit',
|
||||||
'collab.notes.delete': 'Smazat',
|
'collab.notes.delete': 'Smazat',
|
||||||
|
'collab.notes.confirmDeleteTitle': 'Smazat poznámku?',
|
||||||
|
'collab.notes.confirmDeleteBody': 'Tato poznámka bude trvale smazána.',
|
||||||
'collab.notes.pin': 'Připnout',
|
'collab.notes.pin': 'Připnout',
|
||||||
'collab.notes.unpin': 'Odepnout',
|
'collab.notes.unpin': 'Odepnout',
|
||||||
'collab.notes.daysAgo': 'před {n} dny',
|
'collab.notes.daysAgo': 'před {n} dny',
|
||||||
|
|||||||
@@ -20,6 +20,8 @@ const dayplan: TranslationStrings = {
|
|||||||
'dayplan.toast.needTwoPlaces':
|
'dayplan.toast.needTwoPlaces':
|
||||||
'Pro optimalizaci trasy jsou potřeba alespoň dvě místa',
|
'Pro optimalizaci trasy jsou potřeba alespoň dvě místa',
|
||||||
'dayplan.toast.routeOptimized': 'Trasa byla optimalizována',
|
'dayplan.toast.routeOptimized': 'Trasa byla optimalizována',
|
||||||
|
'dayplan.toast.routeOptimizedFromHotel':
|
||||||
|
'Trasa byla optimalizována od vašeho ubytování',
|
||||||
'dayplan.toast.noGeoPlaces':
|
'dayplan.toast.noGeoPlaces':
|
||||||
'Nebyla nalezena žádná místa se souřadnicemi pro výpočet trasy',
|
'Nebyla nalezena žádná místa se souřadnicemi pro výpočet trasy',
|
||||||
'dayplan.confirmed': 'Potvrzeno',
|
'dayplan.confirmed': 'Potvrzeno',
|
||||||
@@ -33,6 +35,8 @@ const dayplan: TranslationStrings = {
|
|||||||
'dayplan.confirmRemoveTimeBody':
|
'dayplan.confirmRemoveTimeBody':
|
||||||
'Toto místo má pevný čas ({time}). Přesunutím se čas odebere a povolí se volné řazení.',
|
'Toto místo má pevný čas ({time}). Přesunutím se čas odebere a povolí se volné řazení.',
|
||||||
'dayplan.confirmRemoveTimeAction': 'Odebrat čas a přesunout',
|
'dayplan.confirmRemoveTimeAction': 'Odebrat čas a přesunout',
|
||||||
|
'dayplan.confirmDeleteNoteTitle': 'Smazat poznámku?',
|
||||||
|
'dayplan.confirmDeleteNoteBody': 'Tato poznámka bude trvale smazána.',
|
||||||
'dayplan.cannotDropOnTimed':
|
'dayplan.cannotDropOnTimed':
|
||||||
'Položky nelze umístit mezi záznamy s pevným časem',
|
'Položky nelze umístit mezi záznamy s pevným časem',
|
||||||
'dayplan.cannotBreakChronology':
|
'dayplan.cannotBreakChronology':
|
||||||
|
|||||||
@@ -63,6 +63,9 @@ const settings: TranslationStrings = {
|
|||||||
'settings.temperature': 'Jednotky teploty',
|
'settings.temperature': 'Jednotky teploty',
|
||||||
'settings.timeFormat': 'Formát času',
|
'settings.timeFormat': 'Formát času',
|
||||||
'settings.blurBookingCodes': 'Skrýt rezervační kódy',
|
'settings.blurBookingCodes': 'Skrýt rezervační kódy',
|
||||||
|
'settings.optimizeFromAccommodation': 'Optimalizovat trasu od ubytování',
|
||||||
|
'settings.optimizeFromAccommodationHint':
|
||||||
|
'Při optimalizaci dne začne trasa v hotelu, ve kterém se ráno probudíte, a skončí v hotelu, do kterého se večer ubytujete.',
|
||||||
'settings.notifications': 'Oznámení',
|
'settings.notifications': 'Oznámení',
|
||||||
'settings.notifyTripInvite': 'Pozvánky na cesty',
|
'settings.notifyTripInvite': 'Pozvánky na cesty',
|
||||||
'settings.notifyBookingChange': 'Změny rezervací',
|
'settings.notifyBookingChange': 'Změny rezervací',
|
||||||
|
|||||||
@@ -41,6 +41,8 @@ const collab: TranslationStrings = {
|
|||||||
'collab.notes.cancel': 'Abbrechen',
|
'collab.notes.cancel': 'Abbrechen',
|
||||||
'collab.notes.edit': 'Bearbeiten',
|
'collab.notes.edit': 'Bearbeiten',
|
||||||
'collab.notes.delete': 'Löschen',
|
'collab.notes.delete': 'Löschen',
|
||||||
|
'collab.notes.confirmDeleteTitle': 'Notiz löschen?',
|
||||||
|
'collab.notes.confirmDeleteBody': 'Diese Notiz wird dauerhaft gelöscht.',
|
||||||
'collab.notes.pin': 'Anheften',
|
'collab.notes.pin': 'Anheften',
|
||||||
'collab.notes.unpin': 'Loslösen',
|
'collab.notes.unpin': 'Loslösen',
|
||||||
'collab.notes.daysAgo': 'vor {n} T.',
|
'collab.notes.daysAgo': 'vor {n} T.',
|
||||||
|
|||||||
@@ -9,6 +9,8 @@ const dayplan: TranslationStrings = {
|
|||||||
'dayplan.confirmRemoveTimeBody':
|
'dayplan.confirmRemoveTimeBody':
|
||||||
'Dieser Ort hat eine feste Uhrzeit ({time}). Durch das Verschieben wird die Uhrzeit entfernt und der Ort kann frei sortiert werden.',
|
'Dieser Ort hat eine feste Uhrzeit ({time}). Durch das Verschieben wird die Uhrzeit entfernt und der Ort kann frei sortiert werden.',
|
||||||
'dayplan.confirmRemoveTimeAction': 'Uhrzeit entfernen & verschieben',
|
'dayplan.confirmRemoveTimeAction': 'Uhrzeit entfernen & verschieben',
|
||||||
|
'dayplan.confirmDeleteNoteTitle': 'Notiz löschen?',
|
||||||
|
'dayplan.confirmDeleteNoteBody': 'Diese Notiz wird dauerhaft gelöscht.',
|
||||||
'dayplan.cannotDropOnTimed':
|
'dayplan.cannotDropOnTimed':
|
||||||
'Orte können nicht zwischen zeitgebundene Einträge geschoben werden',
|
'Orte können nicht zwischen zeitgebundene Einträge geschoben werden',
|
||||||
'dayplan.cannotBreakChronology':
|
'dayplan.cannotBreakChronology':
|
||||||
@@ -32,6 +34,7 @@ const dayplan: TranslationStrings = {
|
|||||||
'dayplan.toast.needTwoPlaces':
|
'dayplan.toast.needTwoPlaces':
|
||||||
'Mindestens zwei Orte für Routenoptimierung nötig',
|
'Mindestens zwei Orte für Routenoptimierung nötig',
|
||||||
'dayplan.toast.routeOptimized': 'Route optimiert',
|
'dayplan.toast.routeOptimized': 'Route optimiert',
|
||||||
|
'dayplan.toast.routeOptimizedFromHotel': 'Route ab deiner Unterkunft optimiert',
|
||||||
'dayplan.toast.noGeoPlaces':
|
'dayplan.toast.noGeoPlaces':
|
||||||
'Keine Orte mit Koordinaten für Routenberechnung gefunden',
|
'Keine Orte mit Koordinaten für Routenberechnung gefunden',
|
||||||
'dayplan.confirmed': 'Bestätigt',
|
'dayplan.confirmed': 'Bestätigt',
|
||||||
|
|||||||
@@ -65,6 +65,9 @@ const settings: TranslationStrings = {
|
|||||||
'settings.bookingLabelsHint':
|
'settings.bookingLabelsHint':
|
||||||
'Zeigt Bahnhofs-/Flughafennamen auf der Karte. Wenn aus, wird nur das Icon angezeigt.',
|
'Zeigt Bahnhofs-/Flughafennamen auf der Karte. Wenn aus, wird nur das Icon angezeigt.',
|
||||||
'settings.blurBookingCodes': 'Buchungscodes verbergen',
|
'settings.blurBookingCodes': 'Buchungscodes verbergen',
|
||||||
|
'settings.optimizeFromAccommodation': 'Route ab der Unterkunft optimieren',
|
||||||
|
'settings.optimizeFromAccommodationHint':
|
||||||
|
'Beim Optimieren eines Tages startet die Route an der Unterkunft, in der du aufwachst, und endet an der, in die du am Abend eincheckst.',
|
||||||
'settings.notifications': 'Mitteilungen',
|
'settings.notifications': 'Mitteilungen',
|
||||||
'settings.notifyTripInvite': 'Trip-Einladungen',
|
'settings.notifyTripInvite': 'Trip-Einladungen',
|
||||||
'settings.notifyBookingChange': 'Buchungsänderungen',
|
'settings.notifyBookingChange': 'Buchungsänderungen',
|
||||||
|
|||||||
@@ -40,6 +40,8 @@ const collab: TranslationStrings = {
|
|||||||
'collab.notes.cancel': 'Cancel',
|
'collab.notes.cancel': 'Cancel',
|
||||||
'collab.notes.edit': 'Edit',
|
'collab.notes.edit': 'Edit',
|
||||||
'collab.notes.delete': 'Delete',
|
'collab.notes.delete': 'Delete',
|
||||||
|
'collab.notes.confirmDeleteTitle': 'Delete note?',
|
||||||
|
'collab.notes.confirmDeleteBody': 'This note will be permanently deleted.',
|
||||||
'collab.notes.pin': 'Pin',
|
'collab.notes.pin': 'Pin',
|
||||||
'collab.notes.unpin': 'Unpin',
|
'collab.notes.unpin': 'Unpin',
|
||||||
'collab.notes.daysAgo': '{n}d ago',
|
'collab.notes.daysAgo': '{n}d ago',
|
||||||
|
|||||||
@@ -9,6 +9,8 @@ const dayplan: TranslationStrings = {
|
|||||||
'dayplan.confirmRemoveTimeBody':
|
'dayplan.confirmRemoveTimeBody':
|
||||||
'This place has a fixed time ({time}). Moving it will remove the time and allow free sorting.',
|
'This place has a fixed time ({time}). Moving it will remove the time and allow free sorting.',
|
||||||
'dayplan.confirmRemoveTimeAction': 'Remove time & move',
|
'dayplan.confirmRemoveTimeAction': 'Remove time & move',
|
||||||
|
'dayplan.confirmDeleteNoteTitle': 'Delete note?',
|
||||||
|
'dayplan.confirmDeleteNoteBody': 'This note will be permanently deleted.',
|
||||||
'dayplan.cannotDropOnTimed':
|
'dayplan.cannotDropOnTimed':
|
||||||
'Items cannot be placed between time-bound entries',
|
'Items cannot be placed between time-bound entries',
|
||||||
'dayplan.cannotBreakChronology':
|
'dayplan.cannotBreakChronology':
|
||||||
@@ -32,6 +34,7 @@ const dayplan: TranslationStrings = {
|
|||||||
'dayplan.toast.needTwoPlaces':
|
'dayplan.toast.needTwoPlaces':
|
||||||
'At least two places needed for route optimization',
|
'At least two places needed for route optimization',
|
||||||
'dayplan.toast.routeOptimized': 'Route optimized',
|
'dayplan.toast.routeOptimized': 'Route optimized',
|
||||||
|
'dayplan.toast.routeOptimizedFromHotel': 'Route optimized from your accommodation',
|
||||||
'dayplan.toast.noGeoPlaces':
|
'dayplan.toast.noGeoPlaces':
|
||||||
'No places with coordinates found for route calculation',
|
'No places with coordinates found for route calculation',
|
||||||
'dayplan.confirmed': 'Confirmed',
|
'dayplan.confirmed': 'Confirmed',
|
||||||
|
|||||||
@@ -64,6 +64,9 @@ const settings: TranslationStrings = {
|
|||||||
'settings.bookingLabelsHint':
|
'settings.bookingLabelsHint':
|
||||||
'Show station / airport names on the map. When off, only the icon is shown.',
|
'Show station / airport names on the map. When off, only the icon is shown.',
|
||||||
'settings.blurBookingCodes': 'Blur Booking Codes',
|
'settings.blurBookingCodes': 'Blur Booking Codes',
|
||||||
|
'settings.optimizeFromAccommodation': 'Optimize route from accommodation',
|
||||||
|
'settings.optimizeFromAccommodationHint':
|
||||||
|
'When optimizing a day, start the route at the hotel you wake up in and end it at the one you check into that evening.',
|
||||||
'settings.notifications': 'Notifications',
|
'settings.notifications': 'Notifications',
|
||||||
'settings.notifyTripInvite': 'Trip invitations',
|
'settings.notifyTripInvite': 'Trip invitations',
|
||||||
'settings.notifyBookingChange': 'Booking changes',
|
'settings.notifyBookingChange': 'Booking changes',
|
||||||
|
|||||||
@@ -41,6 +41,8 @@ const collab: TranslationStrings = {
|
|||||||
'collab.notes.cancel': 'Cancelar',
|
'collab.notes.cancel': 'Cancelar',
|
||||||
'collab.notes.edit': 'Editar',
|
'collab.notes.edit': 'Editar',
|
||||||
'collab.notes.delete': 'Eliminar',
|
'collab.notes.delete': 'Eliminar',
|
||||||
|
'collab.notes.confirmDeleteTitle': '¿Eliminar nota?',
|
||||||
|
'collab.notes.confirmDeleteBody': 'Esta nota se eliminará de forma permanente.',
|
||||||
'collab.notes.pin': 'Fijar',
|
'collab.notes.pin': 'Fijar',
|
||||||
'collab.notes.unpin': 'Desfijar',
|
'collab.notes.unpin': 'Desfijar',
|
||||||
'collab.notes.daysAgo': 'hace {n} d',
|
'collab.notes.daysAgo': 'hace {n} d',
|
||||||
|
|||||||
@@ -20,6 +20,7 @@ const dayplan: TranslationStrings = {
|
|||||||
'dayplan.toast.needTwoPlaces':
|
'dayplan.toast.needTwoPlaces':
|
||||||
'Se necesitan al menos dos lugares para optimizar la ruta',
|
'Se necesitan al menos dos lugares para optimizar la ruta',
|
||||||
'dayplan.toast.routeOptimized': 'Ruta optimizada',
|
'dayplan.toast.routeOptimized': 'Ruta optimizada',
|
||||||
|
'dayplan.toast.routeOptimizedFromHotel': 'Ruta optimizada desde tu alojamiento',
|
||||||
'dayplan.toast.noGeoPlaces':
|
'dayplan.toast.noGeoPlaces':
|
||||||
'No se encontraron lugares con coordenadas para calcular la ruta',
|
'No se encontraron lugares con coordenadas para calcular la ruta',
|
||||||
'dayplan.confirmed': 'Confirmado',
|
'dayplan.confirmed': 'Confirmado',
|
||||||
@@ -33,6 +34,8 @@ const dayplan: TranslationStrings = {
|
|||||||
'dayplan.confirmRemoveTimeBody':
|
'dayplan.confirmRemoveTimeBody':
|
||||||
'Este lugar tiene una hora fija ({time}). Al moverlo se eliminará la hora y se permitirá el orden libre.',
|
'Este lugar tiene una hora fija ({time}). Al moverlo se eliminará la hora y se permitirá el orden libre.',
|
||||||
'dayplan.confirmRemoveTimeAction': 'Eliminar hora y mover',
|
'dayplan.confirmRemoveTimeAction': 'Eliminar hora y mover',
|
||||||
|
'dayplan.confirmDeleteNoteTitle': '¿Eliminar nota?',
|
||||||
|
'dayplan.confirmDeleteNoteBody': 'Esta nota se eliminará de forma permanente.',
|
||||||
'dayplan.cannotDropOnTimed':
|
'dayplan.cannotDropOnTimed':
|
||||||
'No se pueden colocar elementos entre entradas con hora fija',
|
'No se pueden colocar elementos entre entradas con hora fija',
|
||||||
'dayplan.cannotBreakChronology':
|
'dayplan.cannotBreakChronology':
|
||||||
|
|||||||
@@ -62,6 +62,9 @@ const settings: TranslationStrings = {
|
|||||||
'settings.temperature': 'Unidad de temperatura',
|
'settings.temperature': 'Unidad de temperatura',
|
||||||
'settings.timeFormat': 'Formato de hora',
|
'settings.timeFormat': 'Formato de hora',
|
||||||
'settings.blurBookingCodes': 'Difuminar códigos de reserva',
|
'settings.blurBookingCodes': 'Difuminar códigos de reserva',
|
||||||
|
'settings.optimizeFromAccommodation': 'Optimizar la ruta desde el alojamiento',
|
||||||
|
'settings.optimizeFromAccommodationHint':
|
||||||
|
'Al optimizar un día, comienza la ruta en el hotel donde despiertas y termínala en aquel en el que te registras esa noche.',
|
||||||
'settings.notifications': 'Notificaciones',
|
'settings.notifications': 'Notificaciones',
|
||||||
'settings.notifyTripInvite': 'Invitaciones de viaje',
|
'settings.notifyTripInvite': 'Invitaciones de viaje',
|
||||||
'settings.notifyBookingChange': 'Cambios en reservas',
|
'settings.notifyBookingChange': 'Cambios en reservas',
|
||||||
|
|||||||
@@ -42,6 +42,8 @@ const collab: TranslationStrings = {
|
|||||||
'collab.notes.cancel': 'Annuler',
|
'collab.notes.cancel': 'Annuler',
|
||||||
'collab.notes.edit': 'Modifier',
|
'collab.notes.edit': 'Modifier',
|
||||||
'collab.notes.delete': 'Supprimer',
|
'collab.notes.delete': 'Supprimer',
|
||||||
|
'collab.notes.confirmDeleteTitle': 'Supprimer la note ?',
|
||||||
|
'collab.notes.confirmDeleteBody': 'Cette note sera définitivement supprimée.',
|
||||||
'collab.notes.pin': 'Épingler',
|
'collab.notes.pin': 'Épingler',
|
||||||
'collab.notes.unpin': 'Désépingler',
|
'collab.notes.unpin': 'Désépingler',
|
||||||
'collab.notes.daysAgo': 'il y a {n} j',
|
'collab.notes.daysAgo': 'il y a {n} j',
|
||||||
|
|||||||
@@ -20,6 +20,8 @@ const dayplan: TranslationStrings = {
|
|||||||
'dayplan.toast.needTwoPlaces':
|
'dayplan.toast.needTwoPlaces':
|
||||||
"Au moins deux lieux nécessaires pour optimiser l'itinéraire",
|
"Au moins deux lieux nécessaires pour optimiser l'itinéraire",
|
||||||
'dayplan.toast.routeOptimized': 'Itinéraire optimisé',
|
'dayplan.toast.routeOptimized': 'Itinéraire optimisé',
|
||||||
|
'dayplan.toast.routeOptimizedFromHotel':
|
||||||
|
'Itinéraire optimisé depuis votre hébergement',
|
||||||
'dayplan.toast.noGeoPlaces':
|
'dayplan.toast.noGeoPlaces':
|
||||||
"Aucun lieu avec des coordonnées trouvé pour le calcul d'itinéraire",
|
"Aucun lieu avec des coordonnées trouvé pour le calcul d'itinéraire",
|
||||||
'dayplan.confirmed': 'Confirmé',
|
'dayplan.confirmed': 'Confirmé',
|
||||||
@@ -33,6 +35,9 @@ const dayplan: TranslationStrings = {
|
|||||||
'dayplan.confirmRemoveTimeBody':
|
'dayplan.confirmRemoveTimeBody':
|
||||||
"Ce lieu a une heure fixe ({time}). Le déplacer supprimera l'heure et permettra un tri libre.",
|
"Ce lieu a une heure fixe ({time}). Le déplacer supprimera l'heure et permettra un tri libre.",
|
||||||
'dayplan.confirmRemoveTimeAction': "Supprimer l'heure et déplacer",
|
'dayplan.confirmRemoveTimeAction': "Supprimer l'heure et déplacer",
|
||||||
|
'dayplan.confirmDeleteNoteTitle': 'Supprimer la note ?',
|
||||||
|
'dayplan.confirmDeleteNoteBody':
|
||||||
|
'Cette note sera définitivement supprimée.',
|
||||||
'dayplan.cannotDropOnTimed':
|
'dayplan.cannotDropOnTimed':
|
||||||
'Les éléments ne peuvent pas être placés entre des entrées à heure fixe',
|
'Les éléments ne peuvent pas être placés entre des entrées à heure fixe',
|
||||||
'dayplan.cannotBreakChronology':
|
'dayplan.cannotBreakChronology':
|
||||||
|
|||||||
@@ -62,6 +62,10 @@ const settings: TranslationStrings = {
|
|||||||
'settings.temperature': 'Unité de température',
|
'settings.temperature': 'Unité de température',
|
||||||
'settings.timeFormat': "Format de l'heure",
|
'settings.timeFormat': "Format de l'heure",
|
||||||
'settings.blurBookingCodes': 'Masquer les codes de réservation',
|
'settings.blurBookingCodes': 'Masquer les codes de réservation',
|
||||||
|
'settings.optimizeFromAccommodation':
|
||||||
|
"Optimiser l'itinéraire depuis l'hébergement",
|
||||||
|
'settings.optimizeFromAccommodationHint':
|
||||||
|
"Lors de l'optimisation d'une journée, commencez l'itinéraire à l'hôtel où vous vous réveillez et terminez-le à celui où vous arrivez le soir.",
|
||||||
'settings.notifications': 'Notifications',
|
'settings.notifications': 'Notifications',
|
||||||
'settings.notifyTripInvite': 'Invitations de voyage',
|
'settings.notifyTripInvite': 'Invitations de voyage',
|
||||||
'settings.notifyBookingChange': 'Modifications de réservation',
|
'settings.notifyBookingChange': 'Modifications de réservation',
|
||||||
|
|||||||
@@ -41,6 +41,8 @@ const collab: TranslationStrings = {
|
|||||||
'collab.notes.cancel': 'Ακύρωση',
|
'collab.notes.cancel': 'Ακύρωση',
|
||||||
'collab.notes.edit': 'Επεξεργασία',
|
'collab.notes.edit': 'Επεξεργασία',
|
||||||
'collab.notes.delete': 'Διαγραφή',
|
'collab.notes.delete': 'Διαγραφή',
|
||||||
|
'collab.notes.confirmDeleteTitle': 'Διαγραφή σημείωσης;',
|
||||||
|
'collab.notes.confirmDeleteBody': 'Αυτή η σημείωση θα διαγραφεί οριστικά.',
|
||||||
'collab.notes.pin': 'Καρφίτσωμα',
|
'collab.notes.pin': 'Καρφίτσωμα',
|
||||||
'collab.notes.unpin': 'Ξεκαρφίτσωμα',
|
'collab.notes.unpin': 'Ξεκαρφίτσωμα',
|
||||||
'collab.notes.daysAgo': '{n}η πριν',
|
'collab.notes.daysAgo': '{n}η πριν',
|
||||||
|
|||||||
@@ -9,6 +9,9 @@ const dayplan: TranslationStrings = {
|
|||||||
'dayplan.confirmRemoveTimeBody':
|
'dayplan.confirmRemoveTimeBody':
|
||||||
'Αυτό το μέρος έχει σταθερή ώρα ({time}). Η μετακίνησή του θα αφαιρέσει την ώρα και θα επιτρέψει ελεύθερη ταξινόμηση.',
|
'Αυτό το μέρος έχει σταθερή ώρα ({time}). Η μετακίνησή του θα αφαιρέσει την ώρα και θα επιτρέψει ελεύθερη ταξινόμηση.',
|
||||||
'dayplan.confirmRemoveTimeAction': 'Αφαίρεση ώρας & μετακίνηση',
|
'dayplan.confirmRemoveTimeAction': 'Αφαίρεση ώρας & μετακίνηση',
|
||||||
|
'dayplan.confirmDeleteNoteTitle': 'Διαγραφή σημείωσης;',
|
||||||
|
'dayplan.confirmDeleteNoteBody':
|
||||||
|
'Αυτή η σημείωση θα διαγραφεί οριστικά.',
|
||||||
'dayplan.cannotDropOnTimed':
|
'dayplan.cannotDropOnTimed':
|
||||||
'Τα στοιχεία δεν μπορούν να τοποθετηθούν μεταξύ καταχωρήσεων με ώρα',
|
'Τα στοιχεία δεν μπορούν να τοποθετηθούν μεταξύ καταχωρήσεων με ώρα',
|
||||||
'dayplan.cannotBreakChronology':
|
'dayplan.cannotBreakChronology':
|
||||||
@@ -32,6 +35,8 @@ const dayplan: TranslationStrings = {
|
|||||||
'dayplan.toast.needTwoPlaces':
|
'dayplan.toast.needTwoPlaces':
|
||||||
'Χρειάζονται τουλάχιστον δύο μέρη για βελτιστοποίηση διαδρομής',
|
'Χρειάζονται τουλάχιστον δύο μέρη για βελτιστοποίηση διαδρομής',
|
||||||
'dayplan.toast.routeOptimized': 'Η διαδρομή βελτιστοποιήθηκε',
|
'dayplan.toast.routeOptimized': 'Η διαδρομή βελτιστοποιήθηκε',
|
||||||
|
'dayplan.toast.routeOptimizedFromHotel':
|
||||||
|
'Η διαδρομή βελτιστοποιήθηκε από το κατάλυμά σας',
|
||||||
'dayplan.toast.noGeoPlaces':
|
'dayplan.toast.noGeoPlaces':
|
||||||
'Δεν βρέθηκαν μέρη με συντεταγμένες για τον υπολογισμό διαδρομής',
|
'Δεν βρέθηκαν μέρη με συντεταγμένες για τον υπολογισμό διαδρομής',
|
||||||
'dayplan.confirmed': 'Επιβεβαιωμένο',
|
'dayplan.confirmed': 'Επιβεβαιωμένο',
|
||||||
|
|||||||
@@ -66,6 +66,9 @@ const settings: TranslationStrings = {
|
|||||||
'settings.bookingLabelsHint':
|
'settings.bookingLabelsHint':
|
||||||
'Εμφάνιση ονομάτων σταθμών / αεροδρομίων στον χάρτη. Όταν είναι απενεργοποιημένο, εμφανίζεται μόνο το εικονίδιο.',
|
'Εμφάνιση ονομάτων σταθμών / αεροδρομίων στον χάρτη. Όταν είναι απενεργοποιημένο, εμφανίζεται μόνο το εικονίδιο.',
|
||||||
'settings.blurBookingCodes': 'Θόλωμα Κωδικών Κρατήσεων',
|
'settings.blurBookingCodes': 'Θόλωμα Κωδικών Κρατήσεων',
|
||||||
|
'settings.optimizeFromAccommodation': 'Βελτιστοποίηση διαδρομής από το κατάλυμα',
|
||||||
|
'settings.optimizeFromAccommodationHint':
|
||||||
|
'Κατά τη βελτιστοποίηση μιας ημέρας, ξεκινήστε τη διαδρομή από το ξενοδοχείο στο οποίο ξυπνάτε και τερματίστε την σε αυτό στο οποίο κάνετε check-in το ίδιο βράδυ.',
|
||||||
'settings.notifications': 'Ειδοποιήσεις',
|
'settings.notifications': 'Ειδοποιήσεις',
|
||||||
'settings.notifyTripInvite': 'Προσκλήσεις ταξιδιού',
|
'settings.notifyTripInvite': 'Προσκλήσεις ταξιδιού',
|
||||||
'settings.notifyBookingChange': 'Αλλαγές κρατήσεων',
|
'settings.notifyBookingChange': 'Αλλαγές κρατήσεων',
|
||||||
|
|||||||
@@ -42,6 +42,8 @@ const collab: TranslationStrings = {
|
|||||||
'collab.notes.cancel': 'Mégse',
|
'collab.notes.cancel': 'Mégse',
|
||||||
'collab.notes.edit': 'Szerkesztés',
|
'collab.notes.edit': 'Szerkesztés',
|
||||||
'collab.notes.delete': 'Törlés',
|
'collab.notes.delete': 'Törlés',
|
||||||
|
'collab.notes.confirmDeleteTitle': 'Törli a jegyzetet?',
|
||||||
|
'collab.notes.confirmDeleteBody': 'Ez a jegyzet véglegesen törlődik.',
|
||||||
'collab.notes.pin': 'Kitűzés',
|
'collab.notes.pin': 'Kitűzés',
|
||||||
'collab.notes.unpin': 'Kitűzés eltávolítása',
|
'collab.notes.unpin': 'Kitűzés eltávolítása',
|
||||||
'collab.notes.daysAgo': '{n} napja',
|
'collab.notes.daysAgo': '{n} napja',
|
||||||
|
|||||||
@@ -20,6 +20,8 @@ const dayplan: TranslationStrings = {
|
|||||||
'dayplan.toast.needTwoPlaces':
|
'dayplan.toast.needTwoPlaces':
|
||||||
'Legalább két hely szükséges az útvonal-optimalizáláshoz',
|
'Legalább két hely szükséges az útvonal-optimalizáláshoz',
|
||||||
'dayplan.toast.routeOptimized': 'Útvonal optimalizálva',
|
'dayplan.toast.routeOptimized': 'Útvonal optimalizálva',
|
||||||
|
'dayplan.toast.routeOptimizedFromHotel':
|
||||||
|
'Útvonal optimalizálva a szállásodtól',
|
||||||
'dayplan.toast.noGeoPlaces':
|
'dayplan.toast.noGeoPlaces':
|
||||||
'Nem találhatók koordinátákkal rendelkező helyek az útvonalszámításhoz',
|
'Nem találhatók koordinátákkal rendelkező helyek az útvonalszámításhoz',
|
||||||
'dayplan.confirmed': 'Megerősítve',
|
'dayplan.confirmed': 'Megerősítve',
|
||||||
@@ -33,6 +35,8 @@ const dayplan: TranslationStrings = {
|
|||||||
'dayplan.confirmRemoveTimeBody':
|
'dayplan.confirmRemoveTimeBody':
|
||||||
'Ennek a helynek rögzített időpontja van ({time}). Az áthelyezéssel az időpont eltávolítódik és szabad rendezés válik lehetővé.',
|
'Ennek a helynek rögzített időpontja van ({time}). Az áthelyezéssel az időpont eltávolítódik és szabad rendezés válik lehetővé.',
|
||||||
'dayplan.confirmRemoveTimeAction': 'Időpont eltávolítása és áthelyezés',
|
'dayplan.confirmRemoveTimeAction': 'Időpont eltávolítása és áthelyezés',
|
||||||
|
'dayplan.confirmDeleteNoteTitle': 'Törli a jegyzetet?',
|
||||||
|
'dayplan.confirmDeleteNoteBody': 'Ez a jegyzet véglegesen törlődik.',
|
||||||
'dayplan.cannotDropOnTimed':
|
'dayplan.cannotDropOnTimed':
|
||||||
'Elemek nem helyezhetők rögzített időpontú bejegyzések közé',
|
'Elemek nem helyezhetők rögzített időpontú bejegyzések közé',
|
||||||
'dayplan.cannotBreakChronology':
|
'dayplan.cannotBreakChronology':
|
||||||
|
|||||||
@@ -63,6 +63,9 @@ const settings: TranslationStrings = {
|
|||||||
'settings.temperature': 'Hőmérséklet egység',
|
'settings.temperature': 'Hőmérséklet egység',
|
||||||
'settings.timeFormat': 'Időformátum',
|
'settings.timeFormat': 'Időformátum',
|
||||||
'settings.blurBookingCodes': 'Foglalási kódok elrejtése',
|
'settings.blurBookingCodes': 'Foglalási kódok elrejtése',
|
||||||
|
'settings.optimizeFromAccommodation': 'Útvonal optimalizálása a szállástól',
|
||||||
|
'settings.optimizeFromAccommodationHint':
|
||||||
|
'A nap optimalizálásakor az útvonal annál a szállásnál kezdődjön, ahol felébredsz, és annál érjen véget, ahova este bejelentkezel.',
|
||||||
'settings.notifications': 'Értesítések',
|
'settings.notifications': 'Értesítések',
|
||||||
'settings.notifyTripInvite': 'Utazási meghívók',
|
'settings.notifyTripInvite': 'Utazási meghívók',
|
||||||
'settings.notifyBookingChange': 'Foglalási változások',
|
'settings.notifyBookingChange': 'Foglalási változások',
|
||||||
|
|||||||
@@ -40,6 +40,8 @@ const collab: TranslationStrings = {
|
|||||||
'collab.notes.cancel': 'Batal',
|
'collab.notes.cancel': 'Batal',
|
||||||
'collab.notes.edit': 'Edit',
|
'collab.notes.edit': 'Edit',
|
||||||
'collab.notes.delete': 'Hapus',
|
'collab.notes.delete': 'Hapus',
|
||||||
|
'collab.notes.confirmDeleteTitle': 'Hapus catatan?',
|
||||||
|
'collab.notes.confirmDeleteBody': 'Catatan ini akan dihapus secara permanen.',
|
||||||
'collab.notes.pin': 'Sematkan',
|
'collab.notes.pin': 'Sematkan',
|
||||||
'collab.notes.unpin': 'Lepas sematan',
|
'collab.notes.unpin': 'Lepas sematan',
|
||||||
'collab.notes.daysAgo': '{n}h lalu',
|
'collab.notes.daysAgo': '{n}h lalu',
|
||||||
|
|||||||
@@ -9,6 +9,8 @@ const dayplan: TranslationStrings = {
|
|||||||
'dayplan.confirmRemoveTimeBody':
|
'dayplan.confirmRemoveTimeBody':
|
||||||
'Tempat ini memiliki waktu tetap ({time}). Memindahkannya akan menghapus waktu dan mengizinkan pengurutan bebas.',
|
'Tempat ini memiliki waktu tetap ({time}). Memindahkannya akan menghapus waktu dan mengizinkan pengurutan bebas.',
|
||||||
'dayplan.confirmRemoveTimeAction': 'Hapus waktu & pindahkan',
|
'dayplan.confirmRemoveTimeAction': 'Hapus waktu & pindahkan',
|
||||||
|
'dayplan.confirmDeleteNoteTitle': 'Hapus catatan?',
|
||||||
|
'dayplan.confirmDeleteNoteBody': 'Catatan ini akan dihapus secara permanen.',
|
||||||
'dayplan.cannotDropOnTimed':
|
'dayplan.cannotDropOnTimed':
|
||||||
'Item tidak dapat ditempatkan di antara entri yang terikat waktu',
|
'Item tidak dapat ditempatkan di antara entri yang terikat waktu',
|
||||||
'dayplan.cannotBreakChronology':
|
'dayplan.cannotBreakChronology':
|
||||||
@@ -30,6 +32,8 @@ const dayplan: TranslationStrings = {
|
|||||||
'dayplan.toast.needTwoPlaces':
|
'dayplan.toast.needTwoPlaces':
|
||||||
'Diperlukan minimal dua tempat untuk optimasi rute',
|
'Diperlukan minimal dua tempat untuk optimasi rute',
|
||||||
'dayplan.toast.routeOptimized': 'Rute dioptimalkan',
|
'dayplan.toast.routeOptimized': 'Rute dioptimalkan',
|
||||||
|
'dayplan.toast.routeOptimizedFromHotel':
|
||||||
|
'Rute dioptimalkan dari akomodasimu',
|
||||||
'dayplan.toast.noGeoPlaces':
|
'dayplan.toast.noGeoPlaces':
|
||||||
'Tidak ditemukan tempat dengan koordinat untuk kalkulasi rute',
|
'Tidak ditemukan tempat dengan koordinat untuk kalkulasi rute',
|
||||||
'dayplan.confirmed': 'Dikonfirmasi',
|
'dayplan.confirmed': 'Dikonfirmasi',
|
||||||
|
|||||||
@@ -62,6 +62,9 @@ const settings: TranslationStrings = {
|
|||||||
'settings.temperature': 'Satuan Suhu',
|
'settings.temperature': 'Satuan Suhu',
|
||||||
'settings.timeFormat': 'Format Waktu',
|
'settings.timeFormat': 'Format Waktu',
|
||||||
'settings.blurBookingCodes': 'Sembunyikan Kode Pemesanan',
|
'settings.blurBookingCodes': 'Sembunyikan Kode Pemesanan',
|
||||||
|
'settings.optimizeFromAccommodation': 'Optimalkan rute dari akomodasi',
|
||||||
|
'settings.optimizeFromAccommodationHint':
|
||||||
|
'Saat mengoptimalkan suatu hari, mulai rute dari hotel tempatmu bangun pagi dan akhiri di hotel tempatmu check-in malam itu.',
|
||||||
'settings.notifications': 'Notifikasi',
|
'settings.notifications': 'Notifikasi',
|
||||||
'settings.notifyTripInvite': 'Undangan perjalanan',
|
'settings.notifyTripInvite': 'Undangan perjalanan',
|
||||||
'settings.notifyBookingChange': 'Perubahan pemesanan',
|
'settings.notifyBookingChange': 'Perubahan pemesanan',
|
||||||
|
|||||||
@@ -41,6 +41,8 @@ const collab: TranslationStrings = {
|
|||||||
'collab.notes.cancel': 'Annulla',
|
'collab.notes.cancel': 'Annulla',
|
||||||
'collab.notes.edit': 'Modifica',
|
'collab.notes.edit': 'Modifica',
|
||||||
'collab.notes.delete': 'Elimina',
|
'collab.notes.delete': 'Elimina',
|
||||||
|
'collab.notes.confirmDeleteTitle': 'Eliminare la nota?',
|
||||||
|
'collab.notes.confirmDeleteBody': 'Questa nota verrà eliminata definitivamente.',
|
||||||
'collab.notes.pin': 'Fissa',
|
'collab.notes.pin': 'Fissa',
|
||||||
'collab.notes.unpin': 'Rimuovi',
|
'collab.notes.unpin': 'Rimuovi',
|
||||||
'collab.notes.daysAgo': '{n}g fa',
|
'collab.notes.daysAgo': '{n}g fa',
|
||||||
|
|||||||
@@ -20,6 +20,8 @@ const dayplan: TranslationStrings = {
|
|||||||
'dayplan.toast.needTwoPlaces':
|
'dayplan.toast.needTwoPlaces':
|
||||||
"Servono almeno due luoghi per l'ottimizzazione del percorso",
|
"Servono almeno due luoghi per l'ottimizzazione del percorso",
|
||||||
'dayplan.toast.routeOptimized': 'Percorso ottimizzato',
|
'dayplan.toast.routeOptimized': 'Percorso ottimizzato',
|
||||||
|
'dayplan.toast.routeOptimizedFromHotel':
|
||||||
|
'Percorso ottimizzato a partire dal tuo alloggio',
|
||||||
'dayplan.toast.noGeoPlaces':
|
'dayplan.toast.noGeoPlaces':
|
||||||
'Nessun luogo con coordinate trovato per il calcolo del percorso',
|
'Nessun luogo con coordinate trovato per il calcolo del percorso',
|
||||||
'dayplan.confirmed': 'Confermata',
|
'dayplan.confirmed': 'Confermata',
|
||||||
@@ -33,6 +35,9 @@ const dayplan: TranslationStrings = {
|
|||||||
'dayplan.confirmRemoveTimeBody':
|
'dayplan.confirmRemoveTimeBody':
|
||||||
"Questo luogo ha un orario fisso ({time}). Spostarlo rimuoverà l'orario e consentirà l'ordinamento libero.",
|
"Questo luogo ha un orario fisso ({time}). Spostarlo rimuoverà l'orario e consentirà l'ordinamento libero.",
|
||||||
'dayplan.confirmRemoveTimeAction': 'Rimuovi orario e sposta',
|
'dayplan.confirmRemoveTimeAction': 'Rimuovi orario e sposta',
|
||||||
|
'dayplan.confirmDeleteNoteTitle': 'Eliminare la nota?',
|
||||||
|
'dayplan.confirmDeleteNoteBody':
|
||||||
|
'Questa nota verrà eliminata definitivamente.',
|
||||||
'dayplan.cannotDropOnTimed':
|
'dayplan.cannotDropOnTimed':
|
||||||
'Gli elementi non possono essere posizionati tra voci con orario fisso',
|
'Gli elementi non possono essere posizionati tra voci con orario fisso',
|
||||||
'dayplan.cannotBreakChronology':
|
'dayplan.cannotBreakChronology':
|
||||||
|
|||||||
@@ -62,6 +62,9 @@ const settings: TranslationStrings = {
|
|||||||
'settings.temperature': 'Unità di Temperatura',
|
'settings.temperature': 'Unità di Temperatura',
|
||||||
'settings.timeFormat': 'Formato Ora',
|
'settings.timeFormat': 'Formato Ora',
|
||||||
'settings.blurBookingCodes': 'Nascondi codici di prenotazione',
|
'settings.blurBookingCodes': 'Nascondi codici di prenotazione',
|
||||||
|
'settings.optimizeFromAccommodation': "Ottimizza il percorso dall'alloggio",
|
||||||
|
'settings.optimizeFromAccommodationHint':
|
||||||
|
"Quando ottimizzi un giorno, fa iniziare il percorso dall'hotel in cui ti svegli e terminarlo in quello in cui fai il check-in quella sera.",
|
||||||
'settings.notifications': 'Notifiche',
|
'settings.notifications': 'Notifiche',
|
||||||
'settings.notifyTripInvite': 'Inviti di viaggio',
|
'settings.notifyTripInvite': 'Inviti di viaggio',
|
||||||
'settings.notifyBookingChange': 'Modifiche alle prenotazioni',
|
'settings.notifyBookingChange': 'Modifiche alle prenotazioni',
|
||||||
|
|||||||
@@ -40,6 +40,8 @@ const collab: TranslationStrings = {
|
|||||||
'collab.notes.cancel': 'キャンセル',
|
'collab.notes.cancel': 'キャンセル',
|
||||||
'collab.notes.edit': '編集',
|
'collab.notes.edit': '編集',
|
||||||
'collab.notes.delete': '削除',
|
'collab.notes.delete': '削除',
|
||||||
|
'collab.notes.confirmDeleteTitle': 'メモを削除しますか?',
|
||||||
|
'collab.notes.confirmDeleteBody': 'このメモは完全に削除されます。',
|
||||||
'collab.notes.pin': '固定',
|
'collab.notes.pin': '固定',
|
||||||
'collab.notes.unpin': '固定を解除',
|
'collab.notes.unpin': '固定を解除',
|
||||||
'collab.notes.daysAgo': '{n}日前',
|
'collab.notes.daysAgo': '{n}日前',
|
||||||
|
|||||||
@@ -8,6 +8,8 @@ const dayplan: TranslationStrings = {
|
|||||||
'dayplan.confirmRemoveTimeBody':
|
'dayplan.confirmRemoveTimeBody':
|
||||||
'この場所には固定時刻({time})があります。移動すると時刻が削除され、自由に並び替えできます。',
|
'この場所には固定時刻({time})があります。移動すると時刻が削除され、自由に並び替えできます。',
|
||||||
'dayplan.confirmRemoveTimeAction': '時刻を削除して移動',
|
'dayplan.confirmRemoveTimeAction': '時刻を削除して移動',
|
||||||
|
'dayplan.confirmDeleteNoteTitle': 'メモを削除しますか?',
|
||||||
|
'dayplan.confirmDeleteNoteBody': 'このメモは完全に削除されます。',
|
||||||
'dayplan.cannotDropOnTimed': '時刻指定の項目の間には配置できません',
|
'dayplan.cannotDropOnTimed': '時刻指定の項目の間には配置できません',
|
||||||
'dayplan.cannotBreakChronology': '時刻指定の項目や予約の時系列が崩れます',
|
'dayplan.cannotBreakChronology': '時刻指定の項目や予約の時系列が崩れます',
|
||||||
'dayplan.addNote': 'メモを追加',
|
'dayplan.addNote': 'メモを追加',
|
||||||
@@ -28,6 +30,7 @@ const dayplan: TranslationStrings = {
|
|||||||
'dayplan.routeError': 'ルートの計算に失敗しました',
|
'dayplan.routeError': 'ルートの計算に失敗しました',
|
||||||
'dayplan.toast.needTwoPlaces': 'ルート最適化には2つ以上の場所が必要です',
|
'dayplan.toast.needTwoPlaces': 'ルート最適化には2つ以上の場所が必要です',
|
||||||
'dayplan.toast.routeOptimized': 'ルートを最適化しました',
|
'dayplan.toast.routeOptimized': 'ルートを最適化しました',
|
||||||
|
'dayplan.toast.routeOptimizedFromHotel': '宿泊先を起点にルートを最適化しました',
|
||||||
'dayplan.toast.noGeoPlaces': '座標付きの場所がありません',
|
'dayplan.toast.noGeoPlaces': '座標付きの場所がありません',
|
||||||
'dayplan.confirmed': '確定',
|
'dayplan.confirmed': '確定',
|
||||||
'dayplan.pendingRes': '保留',
|
'dayplan.pendingRes': '保留',
|
||||||
|
|||||||
@@ -64,6 +64,9 @@ const settings: TranslationStrings = {
|
|||||||
'settings.bookingLabelsHint':
|
'settings.bookingLabelsHint':
|
||||||
'地図に駅・空港名を表示。オフ時はアイコンのみ。',
|
'地図に駅・空港名を表示。オフ時はアイコンのみ。',
|
||||||
'settings.blurBookingCodes': '予約コードをぼかす',
|
'settings.blurBookingCodes': '予約コードをぼかす',
|
||||||
|
'settings.optimizeFromAccommodation': '宿泊先を起点にルートを最適化',
|
||||||
|
'settings.optimizeFromAccommodationHint':
|
||||||
|
'その日を最適化する際、朝に目覚める宿泊先を起点にし、その晩にチェックインする宿泊先を終点としてルートを組みます。',
|
||||||
'settings.notifications': '通知',
|
'settings.notifications': '通知',
|
||||||
'settings.notifyTripInvite': '旅行の招待',
|
'settings.notifyTripInvite': '旅行の招待',
|
||||||
'settings.notifyBookingChange': '予約の変更',
|
'settings.notifyBookingChange': '予約の変更',
|
||||||
|
|||||||
@@ -39,6 +39,8 @@ const collab: TranslationStrings = {
|
|||||||
'collab.notes.cancel': '취소',
|
'collab.notes.cancel': '취소',
|
||||||
'collab.notes.edit': '편집',
|
'collab.notes.edit': '편집',
|
||||||
'collab.notes.delete': '삭제',
|
'collab.notes.delete': '삭제',
|
||||||
|
'collab.notes.confirmDeleteTitle': '메모를 삭제할까요?',
|
||||||
|
'collab.notes.confirmDeleteBody': '이 메모가 영구적으로 삭제됩니다.',
|
||||||
'collab.notes.pin': '고정',
|
'collab.notes.pin': '고정',
|
||||||
'collab.notes.unpin': '고정 해제',
|
'collab.notes.unpin': '고정 해제',
|
||||||
'collab.notes.daysAgo': '{n}일 전',
|
'collab.notes.daysAgo': '{n}일 전',
|
||||||
|
|||||||
@@ -9,6 +9,8 @@ const dayplan: TranslationStrings = {
|
|||||||
'dayplan.confirmRemoveTimeBody':
|
'dayplan.confirmRemoveTimeBody':
|
||||||
'이 장소에 고정된 시간 ({time})이 있습니다. 이동하면 시간이 제거되고 자유 정렬이 허용됩니다.',
|
'이 장소에 고정된 시간 ({time})이 있습니다. 이동하면 시간이 제거되고 자유 정렬이 허용됩니다.',
|
||||||
'dayplan.confirmRemoveTimeAction': '시간 제거 및 이동',
|
'dayplan.confirmRemoveTimeAction': '시간 제거 및 이동',
|
||||||
|
'dayplan.confirmDeleteNoteTitle': '메모를 삭제할까요?',
|
||||||
|
'dayplan.confirmDeleteNoteBody': '이 메모가 영구적으로 삭제됩니다.',
|
||||||
'dayplan.cannotDropOnTimed': '시간이 고정된 항목 사이에 배치할 수 없습니다',
|
'dayplan.cannotDropOnTimed': '시간이 고정된 항목 사이에 배치할 수 없습니다',
|
||||||
'dayplan.cannotBreakChronology':
|
'dayplan.cannotBreakChronology':
|
||||||
'이 작업은 시간 고정 항목과 예약의 시간 순서를 깨뜨립니다',
|
'이 작업은 시간 고정 항목과 예약의 시간 순서를 깨뜨립니다',
|
||||||
@@ -31,6 +33,7 @@ const dayplan: TranslationStrings = {
|
|||||||
'dayplan.toast.needTwoPlaces':
|
'dayplan.toast.needTwoPlaces':
|
||||||
'경로 최적화에는 최소 두 개의 장소가 필요합니다',
|
'경로 최적화에는 최소 두 개의 장소가 필요합니다',
|
||||||
'dayplan.toast.routeOptimized': '경로가 최적화되었습니다',
|
'dayplan.toast.routeOptimized': '경로가 최적화되었습니다',
|
||||||
|
'dayplan.toast.routeOptimizedFromHotel': '숙소를 기준으로 경로가 최적화되었습니다',
|
||||||
'dayplan.toast.noGeoPlaces': '경로 계산을 위한 좌표가 있는 장소가 없습니다',
|
'dayplan.toast.noGeoPlaces': '경로 계산을 위한 좌표가 있는 장소가 없습니다',
|
||||||
'dayplan.confirmed': '확정됨',
|
'dayplan.confirmed': '확정됨',
|
||||||
'dayplan.pendingRes': '대기 중',
|
'dayplan.pendingRes': '대기 중',
|
||||||
|
|||||||
@@ -65,6 +65,9 @@ const settings: TranslationStrings = {
|
|||||||
'settings.bookingLabelsHint':
|
'settings.bookingLabelsHint':
|
||||||
'지도에 역 / 공항 이름을 표시합니다. 끄면 아이콘만 표시됩니다.',
|
'지도에 역 / 공항 이름을 표시합니다. 끄면 아이콘만 표시됩니다.',
|
||||||
'settings.blurBookingCodes': '예약 코드 흐리게',
|
'settings.blurBookingCodes': '예약 코드 흐리게',
|
||||||
|
'settings.optimizeFromAccommodation': '숙소 기준으로 경로 최적화',
|
||||||
|
'settings.optimizeFromAccommodationHint':
|
||||||
|
'하루 일정을 최적화할 때, 아침에 머무는 숙소에서 경로를 시작하고 그날 저녁에 체크인하는 숙소에서 경로를 끝냅니다.',
|
||||||
'settings.notifications': '알림',
|
'settings.notifications': '알림',
|
||||||
'settings.notifyTripInvite': '여행 초대',
|
'settings.notifyTripInvite': '여행 초대',
|
||||||
'settings.notifyBookingChange': '예약 변경',
|
'settings.notifyBookingChange': '예약 변경',
|
||||||
|
|||||||
@@ -39,6 +39,8 @@ const collab: TranslationStrings = {
|
|||||||
'collab.notes.cancel': 'Annuleren',
|
'collab.notes.cancel': 'Annuleren',
|
||||||
'collab.notes.edit': 'Bewerken',
|
'collab.notes.edit': 'Bewerken',
|
||||||
'collab.notes.delete': 'Verwijderen',
|
'collab.notes.delete': 'Verwijderen',
|
||||||
|
'collab.notes.confirmDeleteTitle': 'Notitie verwijderen?',
|
||||||
|
'collab.notes.confirmDeleteBody': 'Deze notitie wordt definitief verwijderd.',
|
||||||
'collab.notes.pin': 'Vastpinnen',
|
'collab.notes.pin': 'Vastpinnen',
|
||||||
'collab.notes.unpin': 'Losmaken',
|
'collab.notes.unpin': 'Losmaken',
|
||||||
'collab.notes.daysAgo': '{n}d geleden',
|
'collab.notes.daysAgo': '{n}d geleden',
|
||||||
|
|||||||
@@ -20,6 +20,8 @@ const dayplan: TranslationStrings = {
|
|||||||
'dayplan.toast.needTwoPlaces':
|
'dayplan.toast.needTwoPlaces':
|
||||||
'Minimaal twee plaatsen nodig voor route-optimalisatie',
|
'Minimaal twee plaatsen nodig voor route-optimalisatie',
|
||||||
'dayplan.toast.routeOptimized': 'Route geoptimaliseerd',
|
'dayplan.toast.routeOptimized': 'Route geoptimaliseerd',
|
||||||
|
'dayplan.toast.routeOptimizedFromHotel':
|
||||||
|
'Route geoptimaliseerd vanaf je accommodatie',
|
||||||
'dayplan.toast.noGeoPlaces':
|
'dayplan.toast.noGeoPlaces':
|
||||||
'Geen plaatsen met coördinaten gevonden voor routeberekening',
|
'Geen plaatsen met coördinaten gevonden voor routeberekening',
|
||||||
'dayplan.confirmed': 'Bevestigd',
|
'dayplan.confirmed': 'Bevestigd',
|
||||||
@@ -33,6 +35,8 @@ const dayplan: TranslationStrings = {
|
|||||||
'dayplan.confirmRemoveTimeBody':
|
'dayplan.confirmRemoveTimeBody':
|
||||||
'Deze plek heeft een vast tijdstip ({time}). Verplaatsen verwijdert het tijdstip en maakt vrije sortering mogelijk.',
|
'Deze plek heeft een vast tijdstip ({time}). Verplaatsen verwijdert het tijdstip en maakt vrije sortering mogelijk.',
|
||||||
'dayplan.confirmRemoveTimeAction': 'Tijd verwijderen en verplaatsen',
|
'dayplan.confirmRemoveTimeAction': 'Tijd verwijderen en verplaatsen',
|
||||||
|
'dayplan.confirmDeleteNoteTitle': 'Notitie verwijderen?',
|
||||||
|
'dayplan.confirmDeleteNoteBody': 'Deze notitie wordt definitief verwijderd.',
|
||||||
'dayplan.cannotDropOnTimed':
|
'dayplan.cannotDropOnTimed':
|
||||||
'Items kunnen niet tussen tijdgebonden items worden geplaatst',
|
'Items kunnen niet tussen tijdgebonden items worden geplaatst',
|
||||||
'dayplan.cannotBreakChronology':
|
'dayplan.cannotBreakChronology':
|
||||||
|
|||||||
@@ -62,6 +62,9 @@ const settings: TranslationStrings = {
|
|||||||
'settings.temperature': 'Temperatuureenheid',
|
'settings.temperature': 'Temperatuureenheid',
|
||||||
'settings.timeFormat': 'Tijdnotatie',
|
'settings.timeFormat': 'Tijdnotatie',
|
||||||
'settings.blurBookingCodes': 'Boekingscodes vervagen',
|
'settings.blurBookingCodes': 'Boekingscodes vervagen',
|
||||||
|
'settings.optimizeFromAccommodation': 'Route optimaliseren vanaf accommodatie',
|
||||||
|
'settings.optimizeFromAccommodationHint':
|
||||||
|
'Begin bij het optimaliseren van een dag de route bij het hotel waar je wakker wordt en eindig bij het hotel waar je die avond incheckt.',
|
||||||
'settings.notifications': 'Meldingen',
|
'settings.notifications': 'Meldingen',
|
||||||
'settings.notifyTripInvite': 'Reisuitnodigingen',
|
'settings.notifyTripInvite': 'Reisuitnodigingen',
|
||||||
'settings.notifyBookingChange': 'Boekingswijzigingen',
|
'settings.notifyBookingChange': 'Boekingswijzigingen',
|
||||||
|
|||||||
@@ -40,6 +40,8 @@ const collab: TranslationStrings = {
|
|||||||
'collab.notes.cancel': 'Anuluj',
|
'collab.notes.cancel': 'Anuluj',
|
||||||
'collab.notes.edit': 'Edytuj',
|
'collab.notes.edit': 'Edytuj',
|
||||||
'collab.notes.delete': 'Usuń',
|
'collab.notes.delete': 'Usuń',
|
||||||
|
'collab.notes.confirmDeleteTitle': 'Usunąć notatkę?',
|
||||||
|
'collab.notes.confirmDeleteBody': 'Ta notatka zostanie trwale usunięta.',
|
||||||
'collab.notes.pin': 'Przypnij',
|
'collab.notes.pin': 'Przypnij',
|
||||||
'collab.notes.unpin': 'Odepnij',
|
'collab.notes.unpin': 'Odepnij',
|
||||||
'collab.notes.daysAgo': '{n}d temu',
|
'collab.notes.daysAgo': '{n}d temu',
|
||||||
|
|||||||
@@ -9,6 +9,8 @@ const dayplan: TranslationStrings = {
|
|||||||
'dayplan.confirmRemoveTimeBody':
|
'dayplan.confirmRemoveTimeBody':
|
||||||
'To miejsce ma określoną godzinę ({time}). Przeniesienie go usunie godzinę i umożliwi swobodne sortowanie.',
|
'To miejsce ma określoną godzinę ({time}). Przeniesienie go usunie godzinę i umożliwi swobodne sortowanie.',
|
||||||
'dayplan.confirmRemoveTimeAction': 'Usuń godzinę i przenieś',
|
'dayplan.confirmRemoveTimeAction': 'Usuń godzinę i przenieś',
|
||||||
|
'dayplan.confirmDeleteNoteTitle': 'Usunąć notatkę?',
|
||||||
|
'dayplan.confirmDeleteNoteBody': 'Ta notatka zostanie trwale usunięta.',
|
||||||
'dayplan.cannotDropOnTimed':
|
'dayplan.cannotDropOnTimed':
|
||||||
'Nie można umieszczać elementów pomiędzy wpisami z określoną godziną',
|
'Nie można umieszczać elementów pomiędzy wpisami z określoną godziną',
|
||||||
'dayplan.cannotBreakChronology':
|
'dayplan.cannotBreakChronology':
|
||||||
@@ -30,6 +32,8 @@ const dayplan: TranslationStrings = {
|
|||||||
'dayplan.toast.needTwoPlaces':
|
'dayplan.toast.needTwoPlaces':
|
||||||
'Potrzeba co najmniej dwóch miejsc, aby zoptymalizować trasę',
|
'Potrzeba co najmniej dwóch miejsc, aby zoptymalizować trasę',
|
||||||
'dayplan.toast.routeOptimized': 'Trasa została zoptymalizowana',
|
'dayplan.toast.routeOptimized': 'Trasa została zoptymalizowana',
|
||||||
|
'dayplan.toast.routeOptimizedFromHotel':
|
||||||
|
'Trasa została zoptymalizowana względem Twojego zakwaterowania',
|
||||||
'dayplan.toast.noGeoPlaces':
|
'dayplan.toast.noGeoPlaces':
|
||||||
'Nie znaleziono miejsc ze współrzędnymi do obliczenia trasy',
|
'Nie znaleziono miejsc ze współrzędnymi do obliczenia trasy',
|
||||||
'dayplan.confirmed': 'Potwierdzono',
|
'dayplan.confirmed': 'Potwierdzono',
|
||||||
|
|||||||
@@ -62,6 +62,9 @@ const settings: TranslationStrings = {
|
|||||||
'settings.temperature': 'Jednostka temperatury',
|
'settings.temperature': 'Jednostka temperatury',
|
||||||
'settings.timeFormat': 'Format czasu',
|
'settings.timeFormat': 'Format czasu',
|
||||||
'settings.blurBookingCodes': 'Rozmyj kody rezerwacji',
|
'settings.blurBookingCodes': 'Rozmyj kody rezerwacji',
|
||||||
|
'settings.optimizeFromAccommodation': 'Optymalizuj trasę od zakwaterowania',
|
||||||
|
'settings.optimizeFromAccommodationHint':
|
||||||
|
'Przy optymalizacji dnia rozpocznij trasę w hotelu, w którym się budzisz, a zakończ ją w tym, do którego się zameldujesz tego wieczoru.',
|
||||||
'settings.notifications': 'Powiadomienia',
|
'settings.notifications': 'Powiadomienia',
|
||||||
'settings.notifyTripInvite': 'Zaproszenia do podróży',
|
'settings.notifyTripInvite': 'Zaproszenia do podróży',
|
||||||
'settings.notifyBookingChange': 'Zmiany w rezerwacjach',
|
'settings.notifyBookingChange': 'Zmiany w rezerwacjach',
|
||||||
|
|||||||
@@ -41,6 +41,8 @@ const collab: TranslationStrings = {
|
|||||||
'collab.notes.cancel': 'Отмена',
|
'collab.notes.cancel': 'Отмена',
|
||||||
'collab.notes.edit': 'Редактировать',
|
'collab.notes.edit': 'Редактировать',
|
||||||
'collab.notes.delete': 'Удалить',
|
'collab.notes.delete': 'Удалить',
|
||||||
|
'collab.notes.confirmDeleteTitle': 'Удалить заметку?',
|
||||||
|
'collab.notes.confirmDeleteBody': 'Эта заметка будет удалена безвозвратно.',
|
||||||
'collab.notes.pin': 'Закрепить',
|
'collab.notes.pin': 'Закрепить',
|
||||||
'collab.notes.unpin': 'Открепить',
|
'collab.notes.unpin': 'Открепить',
|
||||||
'collab.notes.daysAgo': '{n} дн. назад',
|
'collab.notes.daysAgo': '{n} дн. назад',
|
||||||
|
|||||||
@@ -20,6 +20,8 @@ const dayplan: TranslationStrings = {
|
|||||||
'dayplan.toast.needTwoPlaces':
|
'dayplan.toast.needTwoPlaces':
|
||||||
'Для оптимизации маршрута нужно минимум два места',
|
'Для оптимизации маршрута нужно минимум два места',
|
||||||
'dayplan.toast.routeOptimized': 'Маршрут оптимизирован',
|
'dayplan.toast.routeOptimized': 'Маршрут оптимизирован',
|
||||||
|
'dayplan.toast.routeOptimizedFromHotel':
|
||||||
|
'Маршрут оптимизирован от места проживания',
|
||||||
'dayplan.toast.noGeoPlaces':
|
'dayplan.toast.noGeoPlaces':
|
||||||
'Не найдено мест с координатами для расчёта маршрута',
|
'Не найдено мест с координатами для расчёта маршрута',
|
||||||
'dayplan.confirmed': 'Подтверждено',
|
'dayplan.confirmed': 'Подтверждено',
|
||||||
@@ -33,6 +35,8 @@ const dayplan: TranslationStrings = {
|
|||||||
'dayplan.confirmRemoveTimeBody':
|
'dayplan.confirmRemoveTimeBody':
|
||||||
'У этого места фиксированное время ({time}). При перемещении время будет удалено, и станет доступна свободная сортировка.',
|
'У этого места фиксированное время ({time}). При перемещении время будет удалено, и станет доступна свободная сортировка.',
|
||||||
'dayplan.confirmRemoveTimeAction': 'Удалить время и переместить',
|
'dayplan.confirmRemoveTimeAction': 'Удалить время и переместить',
|
||||||
|
'dayplan.confirmDeleteNoteTitle': 'Удалить заметку?',
|
||||||
|
'dayplan.confirmDeleteNoteBody': 'Эта заметка будет удалена безвозвратно.',
|
||||||
'dayplan.cannotDropOnTimed':
|
'dayplan.cannotDropOnTimed':
|
||||||
'Элементы нельзя размещать между записями с фиксированным временем',
|
'Элементы нельзя размещать между записями с фиксированным временем',
|
||||||
'dayplan.cannotBreakChronology':
|
'dayplan.cannotBreakChronology':
|
||||||
|
|||||||
@@ -62,6 +62,9 @@ const settings: TranslationStrings = {
|
|||||||
'settings.temperature': 'Единица температуры',
|
'settings.temperature': 'Единица температуры',
|
||||||
'settings.timeFormat': 'Формат времени',
|
'settings.timeFormat': 'Формат времени',
|
||||||
'settings.blurBookingCodes': 'Скрыть коды бронирования',
|
'settings.blurBookingCodes': 'Скрыть коды бронирования',
|
||||||
|
'settings.optimizeFromAccommodation': 'Оптимизировать маршрут от места проживания',
|
||||||
|
'settings.optimizeFromAccommodationHint':
|
||||||
|
'При оптимизации дня маршрут начинается от отеля, в котором вы просыпаетесь, и заканчивается у того, в который вы заселяетесь вечером.',
|
||||||
'settings.notifications': 'Уведомления',
|
'settings.notifications': 'Уведомления',
|
||||||
'settings.notifyTripInvite': 'Приглашения в поездку',
|
'settings.notifyTripInvite': 'Приглашения в поездку',
|
||||||
'settings.notifyBookingChange': 'Изменения бронирований',
|
'settings.notifyBookingChange': 'Изменения бронирований',
|
||||||
|
|||||||
@@ -40,6 +40,8 @@ const collab: TranslationStrings = {
|
|||||||
'collab.notes.cancel': 'İptal etmek',
|
'collab.notes.cancel': 'İptal etmek',
|
||||||
'collab.notes.edit': 'Düzenle',
|
'collab.notes.edit': 'Düzenle',
|
||||||
'collab.notes.delete': 'Sil',
|
'collab.notes.delete': 'Sil',
|
||||||
|
'collab.notes.confirmDeleteTitle': 'Not silinsin mi?',
|
||||||
|
'collab.notes.confirmDeleteBody': 'Bu not kalıcı olarak silinecek.',
|
||||||
'collab.notes.pin': 'Sabitle',
|
'collab.notes.pin': 'Sabitle',
|
||||||
'collab.notes.unpin': 'Sabitlemeyi kaldır',
|
'collab.notes.unpin': 'Sabitlemeyi kaldır',
|
||||||
'collab.notes.daysAgo': '{n} gün önce',
|
'collab.notes.daysAgo': '{n} gün önce',
|
||||||
|
|||||||
@@ -9,6 +9,8 @@ const dayplan: TranslationStrings = {
|
|||||||
'dayplan.confirmRemoveTimeBody':
|
'dayplan.confirmRemoveTimeBody':
|
||||||
'Bu yerin sabit bir saati var ({time}). Taşımak saati kaldırır ve serbest sıralamaya izin verir.',
|
'Bu yerin sabit bir saati var ({time}). Taşımak saati kaldırır ve serbest sıralamaya izin verir.',
|
||||||
'dayplan.confirmRemoveTimeAction': 'Saati kaldır ve taşı',
|
'dayplan.confirmRemoveTimeAction': 'Saati kaldır ve taşı',
|
||||||
|
'dayplan.confirmDeleteNoteTitle': 'Not silinsin mi?',
|
||||||
|
'dayplan.confirmDeleteNoteBody': 'Bu not kalıcı olarak silinecek.',
|
||||||
'dayplan.cannotDropOnTimed':
|
'dayplan.cannotDropOnTimed':
|
||||||
'Öğeler saate bağlı girişler arasına yerleştirilemez',
|
'Öğeler saate bağlı girişler arasına yerleştirilemez',
|
||||||
'dayplan.cannotBreakChronology':
|
'dayplan.cannotBreakChronology':
|
||||||
@@ -32,6 +34,8 @@ const dayplan: TranslationStrings = {
|
|||||||
'dayplan.toast.needTwoPlaces':
|
'dayplan.toast.needTwoPlaces':
|
||||||
'Rota optimizasyonu için en az iki yer gerekli',
|
'Rota optimizasyonu için en az iki yer gerekli',
|
||||||
'dayplan.toast.routeOptimized': 'Rota optimize edildi',
|
'dayplan.toast.routeOptimized': 'Rota optimize edildi',
|
||||||
|
'dayplan.toast.routeOptimizedFromHotel':
|
||||||
|
'Rota konakladığınız yerden optimize edildi',
|
||||||
'dayplan.toast.noGeoPlaces': 'Rota için koordinatlı yer bulunamadı',
|
'dayplan.toast.noGeoPlaces': 'Rota için koordinatlı yer bulunamadı',
|
||||||
'dayplan.confirmed': 'Onaylandı',
|
'dayplan.confirmed': 'Onaylandı',
|
||||||
'dayplan.pendingRes': 'Beklemede',
|
'dayplan.pendingRes': 'Beklemede',
|
||||||
|
|||||||
@@ -66,6 +66,9 @@ const settings: TranslationStrings = {
|
|||||||
'settings.bookingLabelsHint':
|
'settings.bookingLabelsHint':
|
||||||
'Haritada istasyon / havalimanı adlarını göster. Kapalıyken yalnızca simge görünür.',
|
'Haritada istasyon / havalimanı adlarını göster. Kapalıyken yalnızca simge görünür.',
|
||||||
'settings.blurBookingCodes': 'Rezervasyon Kodlarını Bulanıklaştır',
|
'settings.blurBookingCodes': 'Rezervasyon Kodlarını Bulanıklaştır',
|
||||||
|
'settings.optimizeFromAccommodation': 'Rotayı konaklamadan optimize et',
|
||||||
|
'settings.optimizeFromAccommodationHint':
|
||||||
|
'Bir günü optimize ederken rotaya o sabah uyandığınız otelden başlayın ve akşam giriş yaptığınız otelde sonlandırın.',
|
||||||
'settings.notifications': 'Bildirimler',
|
'settings.notifications': 'Bildirimler',
|
||||||
'settings.notifyTripInvite': 'Seyahat davetleri',
|
'settings.notifyTripInvite': 'Seyahat davetleri',
|
||||||
'settings.notifyBookingChange': 'Rezervasyon değişiklikleri',
|
'settings.notifyBookingChange': 'Rezervasyon değişiklikleri',
|
||||||
|
|||||||
@@ -40,6 +40,8 @@ const collab: TranslationStrings = {
|
|||||||
'collab.notes.cancel': 'Скасувати',
|
'collab.notes.cancel': 'Скасувати',
|
||||||
'collab.notes.edit': 'Редагувати',
|
'collab.notes.edit': 'Редагувати',
|
||||||
'collab.notes.delete': 'Видалити',
|
'collab.notes.delete': 'Видалити',
|
||||||
|
'collab.notes.confirmDeleteTitle': 'Видалити нотатку?',
|
||||||
|
'collab.notes.confirmDeleteBody': 'Цю нотатку буде видалено назавжди.',
|
||||||
'collab.notes.pin': 'Закріпити',
|
'collab.notes.pin': 'Закріпити',
|
||||||
'collab.notes.unpin': 'Відкріпити',
|
'collab.notes.unpin': 'Відкріпити',
|
||||||
'collab.notes.daysAgo': '{n} дн. тому',
|
'collab.notes.daysAgo': '{n} дн. тому',
|
||||||
|
|||||||
@@ -22,6 +22,7 @@ const dayplan: TranslationStrings = {
|
|||||||
'dayplan.toast.needTwoPlaces':
|
'dayplan.toast.needTwoPlaces':
|
||||||
'Для оптимизации маршрута нужно минимум два места',
|
'Для оптимизации маршрута нужно минимум два места',
|
||||||
'dayplan.toast.routeOptimized': 'Маршрут оптимизирован',
|
'dayplan.toast.routeOptimized': 'Маршрут оптимизирован',
|
||||||
|
'dayplan.toast.routeOptimizedFromHotel': 'Маршрут оптимізовано від вашого житла',
|
||||||
'dayplan.toast.noGeoPlaces':
|
'dayplan.toast.noGeoPlaces':
|
||||||
'Не знайдено місць з координатами для розрахунку маршруту',
|
'Не знайдено місць з координатами для розрахунку маршруту',
|
||||||
'dayplan.confirmed': 'Подтверждено',
|
'dayplan.confirmed': 'Подтверждено',
|
||||||
@@ -35,6 +36,8 @@ const dayplan: TranslationStrings = {
|
|||||||
'dayplan.confirmRemoveTimeBody':
|
'dayplan.confirmRemoveTimeBody':
|
||||||
'У цього місця фіксований час ({time}). При переміщенні час буде видалено, і стане доступне вільне сортування.',
|
'У цього місця фіксований час ({time}). При переміщенні час буде видалено, і стане доступне вільне сортування.',
|
||||||
'dayplan.confirmRemoveTimeAction': 'Видалити час і перемістити',
|
'dayplan.confirmRemoveTimeAction': 'Видалити час і перемістити',
|
||||||
|
'dayplan.confirmDeleteNoteTitle': 'Видалити нотатку?',
|
||||||
|
'dayplan.confirmDeleteNoteBody': 'Цю нотатку буде видалено назавжди.',
|
||||||
'dayplan.cannotDropOnTimed':
|
'dayplan.cannotDropOnTimed':
|
||||||
'Елементи не можна розміщувати між записами з фіксованим часом',
|
'Елементи не можна розміщувати між записами з фіксованим часом',
|
||||||
'dayplan.cannotBreakChronology':
|
'dayplan.cannotBreakChronology':
|
||||||
|
|||||||
@@ -63,6 +63,9 @@ const settings: TranslationStrings = {
|
|||||||
'settings.temperature': 'Одиниця температури',
|
'settings.temperature': 'Одиниця температури',
|
||||||
'settings.timeFormat': 'Формат часу',
|
'settings.timeFormat': 'Формат часу',
|
||||||
'settings.blurBookingCodes': 'Приховати коди бронювання',
|
'settings.blurBookingCodes': 'Приховати коди бронювання',
|
||||||
|
'settings.optimizeFromAccommodation': 'Оптимізувати маршрут від житла',
|
||||||
|
'settings.optimizeFromAccommodationHint':
|
||||||
|
'Під час оптимізації дня починайте маршрут від готелю, у якому ви прокидаєтеся, і завершуйте його тим, у який ви заселяєтеся ввечері.',
|
||||||
'settings.notifications': 'Сповіщення',
|
'settings.notifications': 'Сповіщення',
|
||||||
'settings.notifyTripInvite': 'Запрошення до поїздки',
|
'settings.notifyTripInvite': 'Запрошення до поїздки',
|
||||||
'settings.notifyBookingChange': 'Зміни бронювань',
|
'settings.notifyBookingChange': 'Зміни бронювань',
|
||||||
|
|||||||
@@ -39,6 +39,8 @@ const collab: TranslationStrings = {
|
|||||||
'collab.notes.cancel': '取消',
|
'collab.notes.cancel': '取消',
|
||||||
'collab.notes.edit': '編輯',
|
'collab.notes.edit': '編輯',
|
||||||
'collab.notes.delete': '刪除',
|
'collab.notes.delete': '刪除',
|
||||||
|
'collab.notes.confirmDeleteTitle': '刪除筆記?',
|
||||||
|
'collab.notes.confirmDeleteBody': '此筆記將被永久刪除。',
|
||||||
'collab.notes.pin': '置頂',
|
'collab.notes.pin': '置頂',
|
||||||
'collab.notes.unpin': '取消置頂',
|
'collab.notes.unpin': '取消置頂',
|
||||||
'collab.notes.daysAgo': '{n} 天前',
|
'collab.notes.daysAgo': '{n} 天前',
|
||||||
|
|||||||
@@ -19,6 +19,7 @@ const dayplan: TranslationStrings = {
|
|||||||
'dayplan.routeError': '路線計算失敗',
|
'dayplan.routeError': '路線計算失敗',
|
||||||
'dayplan.toast.needTwoPlaces': '路線最佳化至少需要兩個地點',
|
'dayplan.toast.needTwoPlaces': '路線最佳化至少需要兩個地點',
|
||||||
'dayplan.toast.routeOptimized': '路線已最佳化',
|
'dayplan.toast.routeOptimized': '路線已最佳化',
|
||||||
|
'dayplan.toast.routeOptimizedFromHotel': '已從你的住宿地點最佳化路線',
|
||||||
'dayplan.toast.noGeoPlaces': '未找到有座標的地點用於路線計算',
|
'dayplan.toast.noGeoPlaces': '未找到有座標的地點用於路線計算',
|
||||||
'dayplan.confirmed': '已確認',
|
'dayplan.confirmed': '已確認',
|
||||||
'dayplan.pendingRes': '待確認',
|
'dayplan.pendingRes': '待確認',
|
||||||
@@ -30,6 +31,8 @@ const dayplan: TranslationStrings = {
|
|||||||
'dayplan.confirmRemoveTimeBody':
|
'dayplan.confirmRemoveTimeBody':
|
||||||
'此地點有固定時間({time})。移動後將移除時間並允許自由排序。',
|
'此地點有固定時間({time})。移動後將移除時間並允許自由排序。',
|
||||||
'dayplan.confirmRemoveTimeAction': '移除時間並移動',
|
'dayplan.confirmRemoveTimeAction': '移除時間並移動',
|
||||||
|
'dayplan.confirmDeleteNoteTitle': '刪除筆記?',
|
||||||
|
'dayplan.confirmDeleteNoteBody': '此筆記將被永久刪除。',
|
||||||
'dayplan.cannotDropOnTimed': '無法將專案放置在有固定時間的條目之間',
|
'dayplan.cannotDropOnTimed': '無法將專案放置在有固定時間的條目之間',
|
||||||
'dayplan.cannotBreakChronology': '這將打亂已計劃專案和預訂的時間順序',
|
'dayplan.cannotBreakChronology': '這將打亂已計劃專案和預訂的時間順序',
|
||||||
'dayplan.mobile.addPlace': '新增地點',
|
'dayplan.mobile.addPlace': '新增地點',
|
||||||
|
|||||||
@@ -59,6 +59,9 @@ const settings: TranslationStrings = {
|
|||||||
'settings.temperature': '溫度單位',
|
'settings.temperature': '溫度單位',
|
||||||
'settings.timeFormat': '時間格式',
|
'settings.timeFormat': '時間格式',
|
||||||
'settings.blurBookingCodes': '模糊預訂程式碼',
|
'settings.blurBookingCodes': '模糊預訂程式碼',
|
||||||
|
'settings.optimizeFromAccommodation': '從住宿地點最佳化路線',
|
||||||
|
'settings.optimizeFromAccommodationHint':
|
||||||
|
'最佳化某一天的行程時,路線從你早上起床的飯店出發,並在你當晚入住的飯店結束。',
|
||||||
'settings.notifications': '通知',
|
'settings.notifications': '通知',
|
||||||
'settings.notifyTripInvite': '旅行邀請',
|
'settings.notifyTripInvite': '旅行邀請',
|
||||||
'settings.notifyBookingChange': '預訂變更',
|
'settings.notifyBookingChange': '預訂變更',
|
||||||
|
|||||||
@@ -39,6 +39,8 @@ const collab: TranslationStrings = {
|
|||||||
'collab.notes.cancel': '取消',
|
'collab.notes.cancel': '取消',
|
||||||
'collab.notes.edit': '编辑',
|
'collab.notes.edit': '编辑',
|
||||||
'collab.notes.delete': '删除',
|
'collab.notes.delete': '删除',
|
||||||
|
'collab.notes.confirmDeleteTitle': '删除备注?',
|
||||||
|
'collab.notes.confirmDeleteBody': '此备注将被永久删除。',
|
||||||
'collab.notes.pin': '置顶',
|
'collab.notes.pin': '置顶',
|
||||||
'collab.notes.unpin': '取消置顶',
|
'collab.notes.unpin': '取消置顶',
|
||||||
'collab.notes.daysAgo': '{n} 天前',
|
'collab.notes.daysAgo': '{n} 天前',
|
||||||
|
|||||||
@@ -19,6 +19,7 @@ const dayplan: TranslationStrings = {
|
|||||||
'dayplan.routeError': '路线计算失败',
|
'dayplan.routeError': '路线计算失败',
|
||||||
'dayplan.toast.needTwoPlaces': '路线优化至少需要两个地点',
|
'dayplan.toast.needTwoPlaces': '路线优化至少需要两个地点',
|
||||||
'dayplan.toast.routeOptimized': '路线已优化',
|
'dayplan.toast.routeOptimized': '路线已优化',
|
||||||
|
'dayplan.toast.routeOptimizedFromHotel': '已根据您的住宿优化路线',
|
||||||
'dayplan.toast.noGeoPlaces': '未找到有坐标的地点用于路线计算',
|
'dayplan.toast.noGeoPlaces': '未找到有坐标的地点用于路线计算',
|
||||||
'dayplan.confirmed': '已确认',
|
'dayplan.confirmed': '已确认',
|
||||||
'dayplan.pendingRes': '待确认',
|
'dayplan.pendingRes': '待确认',
|
||||||
@@ -30,6 +31,8 @@ const dayplan: TranslationStrings = {
|
|||||||
'dayplan.confirmRemoveTimeBody':
|
'dayplan.confirmRemoveTimeBody':
|
||||||
'此地点有固定时间({time})。移动后将移除时间并允许自由排序。',
|
'此地点有固定时间({time})。移动后将移除时间并允许自由排序。',
|
||||||
'dayplan.confirmRemoveTimeAction': '移除时间并移动',
|
'dayplan.confirmRemoveTimeAction': '移除时间并移动',
|
||||||
|
'dayplan.confirmDeleteNoteTitle': '删除备注?',
|
||||||
|
'dayplan.confirmDeleteNoteBody': '此备注将被永久删除。',
|
||||||
'dayplan.cannotDropOnTimed': '无法将项目放置在有固定时间的条目之间',
|
'dayplan.cannotDropOnTimed': '无法将项目放置在有固定时间的条目之间',
|
||||||
'dayplan.cannotBreakChronology': '这将打乱已计划项目和预订的时间顺序',
|
'dayplan.cannotBreakChronology': '这将打乱已计划项目和预订的时间顺序',
|
||||||
'dayplan.mobile.addPlace': '添加地点',
|
'dayplan.mobile.addPlace': '添加地点',
|
||||||
|
|||||||
@@ -59,6 +59,9 @@ const settings: TranslationStrings = {
|
|||||||
'settings.temperature': '温度单位',
|
'settings.temperature': '温度单位',
|
||||||
'settings.timeFormat': '时间格式',
|
'settings.timeFormat': '时间格式',
|
||||||
'settings.blurBookingCodes': '模糊预订代码',
|
'settings.blurBookingCodes': '模糊预订代码',
|
||||||
|
'settings.optimizeFromAccommodation': '从住宿地优化路线',
|
||||||
|
'settings.optimizeFromAccommodationHint':
|
||||||
|
'优化某一天时,路线将从您醒来时所在的酒店出发,并在当晚入住的酒店结束。',
|
||||||
'settings.notifications': '通知',
|
'settings.notifications': '通知',
|
||||||
'settings.notifyTripInvite': '旅行邀请',
|
'settings.notifyTripInvite': '旅行邀请',
|
||||||
'settings.notifyBookingChange': '预订变更',
|
'settings.notifyBookingChange': '预订变更',
|
||||||
|
|||||||
Reference in New Issue
Block a user