feat: six community requests — import dedup, accommodations in the feed, OAuth refresh race, visited-from-trip, journal jumps, phone packing rows (#1915)

* fix(places): match imports on the provider id so renamed places are not duplicated

Re-importing a Google Maps list duplicated every place the user had renamed.
buildDedupSet only ever collected names and, for unnamed places, coordinates,
and isPlaceDuplicate returns on the name before it reaches the coordinate
fallback, so a renamed place was findable by neither. The ids were stored all
along, and even backfilled onto matched places, they were simply never read.

Match on google_place_id / google_ftid / osm_id first, keeping name and
coordinates in their old roles below it. Widening the coordinate check instead
would have merged the restaurant and the bar in the same building.

The collection copy carried a hand-mirrored port of the same predicate; it now
uses the shared helpers and gets the fix with them.

Closes #1550

* feat(calendar): put accommodations into the subscribed feed as all-day stays

A hotel keeps its dates on the linked stay, not on the reservation: the booking
form writes reservation_time = NULL for type 'hotel', and day_accommodations
carries start day, end day and the check-in/out clock. The feed only ever read
the reservation, so a stay showed up once on the arrival day at best, and not at
all when it came from the booking form.

Join the stay in and emit the reservation as an all-day range over every night,
which also keeps it correct when days are reordered later — the dates now come
from the day rows rather than from the copy stamped at creation time. Check-in
and check-out become their own timed events when the stay records a clock,
because an all-day event cannot carry the time you actually need reminding of.
The check-in window, when set, becomes that event's DTEND.

Closes #1586

* fix(oauth): treat a concurrent refresh as a race, not as a stolen token

Refresh rotation is a race by construction. Several MCP clients share one
refresh token, both post it within the same second, and the loser was read as a
replay: chain revoked, MCP sessions torn down, browser window asking to log in
again. That is the daily re-authentication people were seeing, not a short
lifetime — access tokens last an hour and refresh tokens roll for thirty days.

A revoked refresh token now only counts as a replay once it is outside a
30-second grace window, and only when the rotation that revoked it left a
successor still alive. Inside the window the caller gets its own pair branched
off the same parent, so both clients walk away with a working token. Explicit
revocation leaves no live successor and a chain killed by a real replay has none
either, so neither can slip through the window.

Closes #1007

* feat(collections): mark a place visited from the trip it belongs to

A place saved in several lists had to be opened in each of them to say you had
been there, which is the wrong way round: you find out you have been somewhere
while looking at the trip, not while browsing the library.

The save picker now shows each list's own status and cycles it in place, with one
action for "visited, wherever this is saved". The planner's selection bar and the
mobile bulk toolbar carry the same thing for a whole selection — select all, one
tap, done. Matching happens on the server with the signals findMembership already
uses plus the source link a place saved from this trip carries, so a copy renamed
inside a list is still found. Lists you may only read are skipped rather than
refused; a shared list you cannot edit should not stop you marking your own.

Closes #1469

* feat(journey): jump to the top or back to the last entry in a long journal

Everything that adds to a journal sits at the top — the entry button, the
gallery upload — and where you left off sits at the bottom, so adding one photo
to a long journey meant scrolling the whole way twice.

Two buttons float over the middle of the feed, clear of the Add Entry column on
one side and the reorder arrows on the other, each appearing only when there is
more than a screenful to travel in that direction. They live in a zero-height
sticky box so they take no layout space and never push the timeline around.
While in the header, the status pill also picks up the height of the round
buttons beside it.

Closes #1088

* fix(packing): give the item name the row back on phones

An item name had roughly a third of the row to itself: a spelled-out sharing
sentence, an empty weight column and two round action buttons took the rest, so
anything longer than a word was cut off.

Sharing is now the icon it always was next to a label rather than a sentence in
the layout, an unset weight prints nothing instead of a dash, and edit/delete
sit behind one overflow button that opens the same inline menu the bag picker
already uses. The to-do list needed none of this — it wraps its chips onto a
second line already.

Closes #1525

* docs(wiki): cover the six community changes

- Calendar feeds: accommodations as an all-day range plus check-in/check-out
- Collections: setting the visited status from a trip, and how places are matched
- Journey: the jump buttons on a long journal
- Packing: what the phone row keeps and what moved behind the overflow button
- Places: re-importing a list no longer duplicates renamed places
- MCP setup: the rotation race no longer ends an OAuth session

* test(collections): assert the stored status instead of a private method

tests/ sits outside the build tsconfig, so reaching into getPlaceById only
showed up in the dedicated typecheck step. Reading the row is the better
assertion anyway — it pins what was persisted, not what a getter returns.
This commit is contained in:
Maurice
2026-08-12 17:43:22 +02:00
committed by GitHub
parent 813b9886b3
commit df3edac7b9
82 changed files with 1721 additions and 105 deletions
+34 -1
View File
@@ -206,6 +206,31 @@ export type CollectionPlaceUpdateRequest = z.infer<typeof collectionPlaceUpdateR
export const collectionSetStatusRequestSchema = z.object({ status: collectionStatusSchema });
export type CollectionSetStatusRequest = z.infer<typeof collectionSetStatusRequestSchema>;
/** Set one status on several saved places at once — the place dialog's "mark
* visited everywhere it is saved" (#1469). Ids are collection_places ids. */
export const collectionSetStatusManyRequestSchema = z.object({
ids: z.array(z.number()).min(1).max(1000),
status: collectionStatusSchema,
});
export type CollectionSetStatusManyRequest = z.infer<typeof collectionSetStatusManyRequestSchema>;
/** Same, but naming the places by their TRIP ids: the server resolves each one to
* the lists it is saved in. Drives the planner's bulk "mark as visited" (#1469). */
export const collectionSetStatusFromTripRequestSchema = z.object({
trip_id: z.number(),
place_ids: z.array(z.number()).min(1).max(1000),
status: collectionStatusSchema,
});
export type CollectionSetStatusFromTripRequest = z.infer<typeof collectionSetStatusFromTripRequestSchema>;
export const collectionSetStatusManyResponseSchema = z.object({
/** Saved places whose status actually changed. */
updated: z.number(),
/** Trip places that were found in at least one list (from-trip form only). */
places: z.number().optional(),
});
export type CollectionSetStatusManyResponse = z.infer<typeof collectionSetStatusManyResponseSchema>;
/** Bulk-delete saved places. Plain z.number() (no .min/.int) mirrors the legacy
* hand-rolled check the DTO ratchet replaced (same shape as placeBulkDeleteRequestSchema). */
export const collectionDeleteManyRequestSchema = z.object({
@@ -311,7 +336,15 @@ export type CollectionSaveResult = z.infer<typeof collectionSaveResultSchema>;
/** Library-wide "is this place already saved anywhere I can see?" lookup (inspector indicator). */
export const collectionMembershipSchema = z.object({
saved: z.boolean(),
lists: z.array(z.object({ collection_id: z.number(), name: z.string(), place_id: z.number() })),
lists: z.array(z.object({
collection_id: z.number(),
name: z.string(),
place_id: z.number(),
/** Per-list status, so the picker can show and change it without a second round trip (#1469). */
status: collectionStatusSchema,
/** False for a list the viewer may read but not edit — its status pill stays read-only. */
can_edit: z.boolean().default(true),
})),
});
export type CollectionMembership = z.infer<typeof collectionMembershipSchema>;
+7
View File
@@ -97,6 +97,13 @@ const collection: TranslationStrings = {
'collections.status.filterAll': 'الكل',
'collections.status.cycleHint': 'انقر للتغيير',
'collections.markVisited': 'وضع علامة كمَزار',
'collections.markVisitedAll': 'مَزار في كل القوائم',
'collections.markVisitedSelection': 'وضع علامة كمَزار في قوائمك',
'collections.markVisitedNone': 'لا يوجد أي من هذه الأماكن في أي قائمة',
'collections.markedVisited': 'تم وضع علامة كمَزار',
'collections.markedVisitedTrip': 'تم وضع علامة على {count} مكان كمَزار',
'collections.copyToTrip': 'نسخ إلى رحلة',
'collections.copyToTripTitle': 'نسخ إلى رحلة',
'collections.copyN': 'نسخ {count} إلى رحلة',
+2
View File
@@ -125,6 +125,8 @@ const journey: TranslationStrings = {
'journey.frontpage.places': 'places', // en-fallback
'journey.detail.syncedWithTrips': 'Synced with Trips', // en-fallback
'journey.detail.addEntry': 'Add Entry', // en-fallback
'journey.detail.jumpToTop': 'العودة إلى الأعلى',
'journey.detail.jumpToLast': 'الانتقال إلى آخر مدخل',
'journey.detail.newEntry': 'New Entry', // en-fallback
'journey.detail.editEntry': 'Edit Entry', // en-fallback
'journey.detail.noEntries': 'No entries yet', // en-fallback
+7
View File
@@ -97,6 +97,13 @@ const collection: TranslationStrings = {
'collections.status.filterAll': 'Todos',
'collections.status.cycleHint': 'toque para alterar',
'collections.markVisited': 'Marcar como visitado',
'collections.markVisitedAll': 'Visitado em todas',
'collections.markVisitedSelection': 'Marcar como visitado nas suas listas',
'collections.markVisitedNone': 'Nenhum desses lugares está salvo em uma lista',
'collections.markedVisited': 'Marcado como visitado',
'collections.markedVisitedTrip': '{count} lugares marcados como visitados',
'collections.copyToTrip': 'Copiar para viagem',
'collections.copyToTripTitle': 'Copiar para viagem',
'collections.copyN': 'Copiar {count} para viagem',
+2
View File
@@ -76,6 +76,8 @@ const journey: TranslationStrings = {
'journey.detail.backToJourney': 'Voltar à jornada',
'journey.detail.syncedWithTrips': 'Sincronizado com viagens',
'journey.detail.addEntry': 'Adicionar entrada',
'journey.detail.jumpToTop': 'Voltar ao topo',
'journey.detail.jumpToLast': 'Ir para a última entrada',
'journey.detail.newEntry': 'Nova entrada',
'journey.detail.editEntry': 'Editar entrada',
'journey.detail.noEntries': 'Nenhuma entrada ainda',
+7
View File
@@ -92,6 +92,13 @@ const collection: TranslationStrings = {
'collections.status.visited': 'Visitat',
'collections.status.filterAll': 'Tot',
'collections.status.cycleHint': 'toca per canviar',
'collections.markVisited': 'Marca com a visitat',
'collections.markVisitedAll': 'Visitat pertot',
'collections.markVisitedSelection': 'Marca com a visitat a les teves llistes',
'collections.markVisitedNone': 'Cap d\'aquests llocs no és desat en cap llista',
'collections.markedVisited': 'Marcat com a visitat',
'collections.markedVisitedTrip': '{count} llocs marcats com a visitats',
'collections.copyToTrip': 'Copiar al viatge',
'collections.copyToTripTitle': 'Copiar al viatge',
'collections.copyN': 'Copia {count} al viatge',
+2
View File
@@ -76,6 +76,8 @@ const journey: TranslationStrings = {
'journey.detail.backToJourney': 'Torna a la travesia',
'journey.detail.syncedWithTrips': 'Sincronitzat amb viatges',
'journey.detail.addEntry': 'Afegeix una entrada',
'journey.detail.jumpToTop': 'Torna a dalt',
'journey.detail.jumpToLast': 'Ves a l’última entrada',
'journey.detail.newEntry': 'Entrada nova',
'journey.detail.editEntry': "Edita l'entrada",
'journey.detail.noEntries': 'Encara no hi ha entrades',
+7
View File
@@ -96,6 +96,13 @@ const collection: TranslationStrings = {
'collections.status.filterAll': 'Vše',
'collections.status.cycleHint': 'klepnutím změníte',
'collections.markVisited': 'Označit jako navštívené',
'collections.markVisitedAll': 'Navštíveno všude',
'collections.markVisitedSelection': 'Označit jako navštívené v seznamech',
'collections.markVisitedNone': 'Žádné z těchto míst není uloženo v seznamu',
'collections.markedVisited': 'Označeno jako navštívené',
'collections.markedVisitedTrip': '{count} míst označeno jako navštívená',
'collections.copyToTrip': 'Kopírovat do výletu',
'collections.copyToTripTitle': 'Kopírovat do výletu',
'collections.copyN': 'Kopírovat {count} do výletu',
+2
View File
@@ -76,6 +76,8 @@ const journey: TranslationStrings = {
'journey.detail.backToJourney': 'Zpět na cestovní deník',
'journey.detail.syncedWithTrips': 'Synchronizováno s cestami',
'journey.detail.addEntry': 'Přidat záznam',
'journey.detail.jumpToTop': 'Zpět nahoru',
'journey.detail.jumpToLast': 'Přejít na poslední záznam',
'journey.detail.newEntry': 'Nový záznam',
'journey.detail.editEntry': 'Upravit záznam',
'journey.detail.noEntries': 'Zatím žádné záznamy',
+7
View File
@@ -98,6 +98,13 @@ const collection: TranslationStrings = {
'collections.status.filterAll': 'Alle',
'collections.status.cycleHint': 'tippen zum Ändern',
'collections.markVisited': 'Als besucht markieren',
'collections.markVisitedAll': 'Überall als besucht',
'collections.markVisitedSelection': 'In deinen Listen als besucht markieren',
'collections.markVisitedNone': 'Keiner dieser Orte ist in einer Liste gespeichert',
'collections.markedVisited': 'Als besucht markiert',
'collections.markedVisitedTrip': '{count} Orte als besucht markiert',
'collections.copyToTrip': 'In Reise kopieren',
'collections.copyToTripTitle': 'In Reise kopieren',
'collections.copyN': '{count} in Reise kopieren',
+2
View File
@@ -76,6 +76,8 @@ const journey: TranslationStrings = {
'journey.detail.backToJourney': 'Zurück zur Journey',
'journey.detail.syncedWithTrips': 'Mit Trips synchronisiert',
'journey.detail.addEntry': 'Eintrag hinzufügen',
'journey.detail.jumpToTop': 'Nach oben',
'journey.detail.jumpToLast': 'Zum letzten Eintrag',
'journey.detail.newEntry': 'Neuer Eintrag',
'journey.detail.editEntry': 'Eintrag bearbeiten',
'journey.detail.noEntries': 'Noch keine Einträge',
+7
View File
@@ -97,6 +97,13 @@ const collection: TranslationStrings = {
'collections.status.filterAll': 'All',
'collections.status.cycleHint': 'tap to change',
'collections.markVisited': 'Mark visited',
'collections.markVisitedAll': 'Visited everywhere',
'collections.markVisitedSelection': 'Mark visited in your lists',
'collections.markVisitedNone': 'None of these places are saved in a list',
'collections.markedVisited': 'Marked as visited',
'collections.markedVisitedTrip': '{count} places marked as visited',
'collections.copyToTrip': 'Copy to trip',
'collections.copyToTripTitle': 'Copy to trip',
'collections.copyN': 'Copy {count} to trip',
+2
View File
@@ -76,6 +76,8 @@ const journey: TranslationStrings = {
'journey.detail.backToJourney': 'Back to Journey',
'journey.detail.syncedWithTrips': 'Synced with Trips',
'journey.detail.addEntry': 'Add Entry',
'journey.detail.jumpToTop': 'Back to top',
'journey.detail.jumpToLast': 'Jump to the last entry',
'journey.detail.newEntry': 'New Entry',
'journey.detail.editEntry': 'Edit Entry',
'journey.detail.noEntries': 'No entries yet',
+7
View File
@@ -97,6 +97,13 @@ const collection: TranslationStrings = {
'collections.status.filterAll': 'Todos',
'collections.status.cycleHint': 'toca para cambiar',
'collections.markVisited': 'Marcar como visitado',
'collections.markVisitedAll': 'Visitado en todas',
'collections.markVisitedSelection': 'Marcar como visitado en tus listas',
'collections.markVisitedNone': 'Ninguno de estos lugares está guardado en una lista',
'collections.markedVisited': 'Marcado como visitado',
'collections.markedVisitedTrip': '{count} lugares marcados como visitados',
'collections.copyToTrip': 'Copiar al viaje',
'collections.copyToTripTitle': 'Copiar al viaje',
'collections.copyN': 'Copiar {count} al viaje',
+2
View File
@@ -76,6 +76,8 @@ const journey: TranslationStrings = {
'journey.detail.backToJourney': 'Volver a la travesía',
'journey.detail.syncedWithTrips': 'Sincronizado con viajes',
'journey.detail.addEntry': 'Añadir entrada',
'journey.detail.jumpToTop': 'Volver arriba',
'journey.detail.jumpToLast': 'Ir a la última entrada',
'journey.detail.newEntry': 'Nueva entrada',
'journey.detail.editEntry': 'Editar entrada',
'journey.detail.noEntries': 'Aún no hay entradas',
+7
View File
@@ -97,6 +97,13 @@ const collection: TranslationStrings = {
'collections.status.filterAll': 'Tous',
'collections.status.cycleHint': 'touchez pour changer',
'collections.markVisited': 'Marquer comme visité',
'collections.markVisitedAll': 'Visité partout',
'collections.markVisitedSelection': 'Marquer comme visité dans vos listes',
'collections.markVisitedNone': 'Aucun de ces lieux n\'est enregistré dans une liste',
'collections.markedVisited': 'Marqué comme visité',
'collections.markedVisitedTrip': '{count} lieux marqués comme visités',
'collections.copyToTrip': 'Copier vers un voyage',
'collections.copyToTripTitle': 'Copier vers un voyage',
'collections.copyN': 'Copier {count} vers un voyage',
+2
View File
@@ -76,6 +76,8 @@ const journey: TranslationStrings = {
'journey.detail.backToJourney': 'Retour au journal',
'journey.detail.syncedWithTrips': 'Synchronisé avec les voyages',
'journey.detail.addEntry': 'Ajouter une entrée',
'journey.detail.jumpToTop': 'Revenir en haut',
'journey.detail.jumpToLast': 'Aller à la dernière entrée',
'journey.detail.newEntry': 'Nouvelle entrée',
'journey.detail.editEntry': "Modifier l'entrée",
'journey.detail.noEntries': 'Aucune entrée pour le moment',
+7
View File
@@ -97,6 +97,13 @@ const collection: TranslationStrings = {
'collections.status.filterAll': 'Όλα',
'collections.status.cycleHint': 'πάτησε για αλλαγή',
'collections.markVisited': 'Σήμανση ως επισκεφθέν',
'collections.markVisitedAll': 'Επισκεφθέν παντού',
'collections.markVisitedSelection': 'Σήμανση ως επισκεφθέν στις λίστες σου',
'collections.markVisitedNone': 'Καμία από αυτές τις τοποθεσίες δεν είναι αποθηκευμένη σε λίστα',
'collections.markedVisited': 'Σημειώθηκε ως επισκεφθέν',
'collections.markedVisitedTrip': '{count} τοποθεσίες σημειώθηκαν ως επισκεφθείσες',
'collections.copyToTrip': 'Αντιγραφή σε ταξίδι',
'collections.copyToTripTitle': 'Αντιγραφή σε ταξίδι',
'collections.copyN': 'Αντιγραφή {count} σε ταξίδι',
+2
View File
@@ -76,6 +76,8 @@ const journey: TranslationStrings = {
'journey.detail.backToJourney': 'Πίσω στο Ταξίδι',
'journey.detail.syncedWithTrips': 'Συγχρονισμένο με Ταξίδια',
'journey.detail.addEntry': 'Προσθήκη Καταχώρησης',
'journey.detail.jumpToTop': 'Επιστροφή στην κορυφή',
'journey.detail.jumpToLast': 'Μετάβαση στην τελευταία καταχώρηση',
'journey.detail.newEntry': 'Νέα Καταχώρηση',
'journey.detail.editEntry': 'Επεξεργασία Καταχώρησης',
'journey.detail.noEntries': 'Δεν υπάρχουν καταχωρήσεις ακόμα',
+7
View File
@@ -98,6 +98,13 @@ const collection: TranslationStrings = {
'collections.status.filterAll': 'Összes',
'collections.status.cycleHint': 'koppints a módosításhoz',
'collections.markVisited': 'Megjelölés látogatottként',
'collections.markVisitedAll': 'Mindenhol látogatott',
'collections.markVisitedSelection': 'Megjelölés látogatottként a listáidban',
'collections.markVisitedNone': 'Ezek közül egy hely sincs listába mentve',
'collections.markedVisited': 'Látogatottként megjelölve',
'collections.markedVisitedTrip': '{count} hely megjelölve látogatottként',
'collections.copyToTrip': 'Másolás utazásba',
'collections.copyToTripTitle': 'Másolás utazásba',
'collections.copyN': '{count} másolása utazásba',
+2
View File
@@ -76,6 +76,8 @@ const journey: TranslationStrings = {
'journey.detail.backToJourney': 'Vissza az útinaplóhoz',
'journey.detail.syncedWithTrips': 'Szinkronizálva az utakkal',
'journey.detail.addEntry': 'Bejegyzés hozzáadása',
'journey.detail.jumpToTop': 'Vissza a tetejére',
'journey.detail.jumpToLast': 'Ugrás az utolsó bejegyzéshez',
'journey.detail.newEntry': 'Új bejegyzés',
'journey.detail.editEntry': 'Bejegyzés szerkesztése',
'journey.detail.noEntries': 'Még nincsenek bejegyzések',
+7
View File
@@ -97,6 +97,13 @@ const collection: TranslationStrings = {
'collections.status.filterAll': 'Semua',
'collections.status.cycleHint': 'ketuk untuk mengubah',
'collections.markVisited': 'Tandai dikunjungi',
'collections.markVisitedAll': 'Dikunjungi di semua',
'collections.markVisitedSelection': 'Tandai dikunjungi di daftar Anda',
'collections.markVisitedNone': 'Tidak ada tempat ini yang tersimpan di daftar',
'collections.markedVisited': 'Ditandai sebagai dikunjungi',
'collections.markedVisitedTrip': '{count} tempat ditandai sebagai dikunjungi',
'collections.copyToTrip': 'Salin ke perjalanan',
'collections.copyToTripTitle': 'Salin ke perjalanan',
'collections.copyN': 'Salin {count} ke perjalanan',
+2
View File
@@ -76,6 +76,8 @@ const journey: TranslationStrings = {
'journey.detail.backToJourney': 'Kembali ke Journey',
'journey.detail.syncedWithTrips': 'Tersinkron dengan Perjalanan',
'journey.detail.addEntry': 'Tambah Entri',
'journey.detail.jumpToTop': 'Kembali ke atas',
'journey.detail.jumpToLast': 'Ke entri terakhir',
'journey.detail.newEntry': 'Entri Baru',
'journey.detail.editEntry': 'Edit Entri',
'journey.detail.noEntries': 'Belum ada entri',
+7
View File
@@ -97,6 +97,13 @@ const collection: TranslationStrings = {
'collections.status.filterAll': 'Tutti',
'collections.status.cycleHint': 'tocca per cambiare',
'collections.markVisited': 'Segna come visitato',
'collections.markVisitedAll': 'Visitato ovunque',
'collections.markVisitedSelection': 'Segna come visitato nelle tue liste',
'collections.markVisitedNone': 'Nessuno di questi luoghi è salvato in una lista',
'collections.markedVisited': 'Segnato come visitato',
'collections.markedVisitedTrip': '{count} luoghi segnati come visitati',
'collections.copyToTrip': 'Copia nel viaggio',
'collections.copyToTripTitle': 'Copia nel viaggio',
'collections.copyN': 'Copia {count} nel viaggio',
+2
View File
@@ -76,6 +76,8 @@ const journey: TranslationStrings = {
'journey.detail.backToJourney': 'Torna al diario',
'journey.detail.syncedWithTrips': 'Sincronizzato con i viaggi',
'journey.detail.addEntry': 'Aggiungi voce',
'journey.detail.jumpToTop': 'Torna in alto',
'journey.detail.jumpToLast': 'Vai all’ultima voce',
'journey.detail.newEntry': 'Nuova voce',
'journey.detail.editEntry': 'Modifica voce',
'journey.detail.noEntries': 'Nessuna voce ancora',
+7
View File
@@ -96,6 +96,13 @@ const collection: TranslationStrings = {
'collections.status.filterAll': 'すべて',
'collections.status.cycleHint': 'タップで変更',
'collections.markVisited': '訪問済みにする',
'collections.markVisitedAll': 'すべてで訪問済み',
'collections.markVisitedSelection': 'リストで訪問済みにする',
'collections.markVisitedNone': 'これらの場所はどのリストにも保存されていません',
'collections.markedVisited': '訪問済みにしました',
'collections.markedVisitedTrip': '{count} 件の場所を訪問済みにしました',
'collections.copyToTrip': '旅行にコピー',
'collections.copyToTripTitle': '旅行にコピー',
'collections.copyN': '{count}件を旅行にコピー',
+2
View File
@@ -76,6 +76,8 @@ const journey: TranslationStrings = {
'journey.detail.backToJourney': '日記に戻る',
'journey.detail.syncedWithTrips': '旅行と同期済み',
'journey.detail.addEntry': 'エントリーを追加',
'journey.detail.jumpToTop': '先頭へ戻る',
'journey.detail.jumpToLast': '最後の記録へ',
'journey.detail.newEntry': '新しいエントリー',
'journey.detail.editEntry': 'エントリーを編集',
'journey.detail.noEntries': 'エントリーはまだありません',
+7
View File
@@ -96,6 +96,13 @@ const collection: TranslationStrings = {
'collections.status.filterAll': '전체',
'collections.status.cycleHint': '탭하여 변경',
'collections.markVisited': '방문함으로 표시',
'collections.markVisitedAll': '모든 목록에서 방문함',
'collections.markVisitedSelection': '내 목록에서 방문함으로 표시',
'collections.markVisitedNone': '이 장소들은 어떤 목록에도 저장되어 있지 않습니다',
'collections.markedVisited': '방문함으로 표시했습니다',
'collections.markedVisitedTrip': '장소 {count}곳을 방문함으로 표시했습니다',
'collections.copyToTrip': '여행에 복사',
'collections.copyToTripTitle': '여행에 복사',
'collections.copyN': '{count}개를 여행에 복사',
+2
View File
@@ -76,6 +76,8 @@ const journey: TranslationStrings = {
'journey.detail.backToJourney': 'Journey로 돌아가기',
'journey.detail.syncedWithTrips': '여행과 동기화됨',
'journey.detail.addEntry': '항목 추가',
'journey.detail.jumpToTop': '맨 위로',
'journey.detail.jumpToLast': '마지막 기록으로',
'journey.detail.newEntry': '새 항목',
'journey.detail.editEntry': '항목 편집',
'journey.detail.noEntries': '아직 항목이 없습니다',
+7
View File
@@ -97,6 +97,13 @@ const collection: TranslationStrings = {
'collections.status.filterAll': 'Alle',
'collections.status.cycleHint': 'tik om te wijzigen',
'collections.markVisited': 'Markeer als bezocht',
'collections.markVisitedAll': 'Overal bezocht',
'collections.markVisitedSelection': 'Markeer als bezocht in je lijsten',
'collections.markVisitedNone': 'Geen van deze plaatsen staat in een lijst',
'collections.markedVisited': 'Gemarkeerd als bezocht',
'collections.markedVisitedTrip': '{count} plaatsen gemarkeerd als bezocht',
'collections.copyToTrip': 'Naar reis kopiëren',
'collections.copyToTripTitle': 'Naar reis kopiëren',
'collections.copyN': '{count} naar reis kopiëren',
+2
View File
@@ -76,6 +76,8 @@ const journey: TranslationStrings = {
'journey.detail.backToJourney': 'Terug naar reisverslag',
'journey.detail.syncedWithTrips': 'Gesynchroniseerd met reizen',
'journey.detail.addEntry': 'Vermelding toevoegen',
'journey.detail.jumpToTop': 'Terug naar boven',
'journey.detail.jumpToLast': 'Naar de laatste notitie',
'journey.detail.newEntry': 'Nieuwe vermelding',
'journey.detail.editEntry': 'Vermelding bewerken',
'journey.detail.noEntries': 'Nog geen vermeldingen',
+7
View File
@@ -97,6 +97,13 @@ const collection: TranslationStrings = {
'collections.status.filterAll': 'Wszystkie',
'collections.status.cycleHint': 'dotknij, aby zmienić',
'collections.markVisited': 'Oznacz jako odwiedzone',
'collections.markVisitedAll': 'Odwiedzone wszędzie',
'collections.markVisitedSelection': 'Oznacz jako odwiedzone na listach',
'collections.markVisitedNone': 'Żadne z tych miejsc nie jest zapisane na liście',
'collections.markedVisited': 'Oznaczono jako odwiedzone',
'collections.markedVisitedTrip': 'Oznaczono {count} miejsc jako odwiedzone',
'collections.copyToTrip': 'Kopiuj do podróży',
'collections.copyToTripTitle': 'Kopiuj do podróży',
'collections.copyN': 'Kopiuj {count} do podróży',
+2
View File
@@ -76,6 +76,8 @@ const journey: TranslationStrings = {
'journey.detail.backToJourney': 'Powrót do dziennika podróży',
'journey.detail.syncedWithTrips': 'Zsynchronizowany z podróżami',
'journey.detail.addEntry': 'Dodaj wpis',
'journey.detail.jumpToTop': 'Powrót na górę',
'journey.detail.jumpToLast': 'Przejdź do ostatniego wpisu',
'journey.detail.newEntry': 'Nowy wpis',
'journey.detail.editEntry': 'Edytuj wpis',
'journey.detail.noEntries': 'Brak wpisów',
+7
View File
@@ -96,6 +96,13 @@ const collection: TranslationStrings = {
'collections.status.filterAll': 'Все',
'collections.status.cycleHint': 'нажмите, чтобы изменить',
'collections.markVisited': 'Отметить как посещённое',
'collections.markVisitedAll': 'Посещено везде',
'collections.markVisitedSelection': 'Отметить посещённым в списках',
'collections.markVisitedNone': 'Ни одно из этих мест не сохранено в списке',
'collections.markedVisited': 'Отмечено как посещённое',
'collections.markedVisitedTrip': 'Отмечено мест как посещённые: {count}',
'collections.copyToTrip': 'Скопировать в поездку',
'collections.copyToTripTitle': 'Скопировать в поездку',
'collections.copyN': 'Скопировать {count} в поездку',
+2
View File
@@ -76,6 +76,8 @@ const journey: TranslationStrings = {
'journey.detail.backToJourney': 'Назад к путешествию',
'journey.detail.syncedWithTrips': 'Синхронизировано с поездками',
'journey.detail.addEntry': 'Добавить запись',
'journey.detail.jumpToTop': 'Наверх',
'journey.detail.jumpToLast': 'К последней записи',
'journey.detail.newEntry': 'Новая запись',
'journey.detail.editEntry': 'Редактировать запись',
'journey.detail.noEntries': 'Пока нет записей',
+7
View File
@@ -96,6 +96,13 @@ const collection: TranslationStrings = {
'collections.status.filterAll': 'Alla',
'collections.status.cycleHint': 'tryck för att ändra',
'collections.markVisited': 'Markera som besökt',
'collections.markVisitedAll': 'Besökt överallt',
'collections.markVisitedSelection': 'Markera som besökt i dina listor',
'collections.markVisitedNone': 'Ingen av dessa platser finns i en lista',
'collections.markedVisited': 'Markerad som besökt',
'collections.markedVisitedTrip': '{count} platser markerade som besökta',
'collections.copyToTrip': 'Kopiera till resa',
'collections.copyToTripTitle': 'Kopiera till resa',
'collections.copyN': 'Kopiera {count} till resa',
+2
View File
@@ -76,6 +76,8 @@ const journey: TranslationStrings = {
'journey.detail.backToJourney': 'Tillbaka till Journey',
'journey.detail.syncedWithTrips': 'Synkroniserat med resor',
'journey.detail.addEntry': 'Lägg till inlägg',
'journey.detail.jumpToTop': 'Till toppen',
'journey.detail.jumpToLast': 'Till senaste inlägget',
'journey.detail.newEntry': 'Ny inlägg',
'journey.detail.editEntry': 'Redigera inlägg',
'journey.detail.noEntries': 'Inga inlägg än så länge',
+7
View File
@@ -97,6 +97,13 @@ const collection: TranslationStrings = {
'collections.status.filterAll': 'Tümü',
'collections.status.cycleHint': 'değiştirmek için dokun',
'collections.markVisited': 'Ziyaret edildi işaretle',
'collections.markVisitedAll': 'Her yerde ziyaret edildi',
'collections.markVisitedSelection': 'Listelerinde ziyaret edildi olarak işaretle',
'collections.markVisitedNone': 'Bu yerlerin hiçbiri bir listede kayıtlı değil',
'collections.markedVisited': 'Ziyaret edildi olarak işaretlendi',
'collections.markedVisitedTrip': '{count} yer ziyaret edildi olarak işaretlendi',
'collections.copyToTrip': 'Geziye kopyala',
'collections.copyToTripTitle': 'Geziye kopyala',
'collections.copyN': '{count} öğeyi geziye kopyala',
+2
View File
@@ -76,6 +76,8 @@ const journey: TranslationStrings = {
'journey.detail.backToJourney': "Journey'e dön",
'journey.detail.syncedWithTrips': 'Seyahatlerle senkronize',
'journey.detail.addEntry': 'Kayıt Ekle',
'journey.detail.jumpToTop': 'Başa dön',
'journey.detail.jumpToLast': 'Son girdiye git',
'journey.detail.newEntry': 'Yeni Kayıt',
'journey.detail.editEntry': 'Kaydı Düzenle',
'journey.detail.noEntries': 'Henüz kayıt yok',
+7
View File
@@ -96,6 +96,13 @@ const collection: TranslationStrings = {
'collections.status.filterAll': 'Усі',
'collections.status.cycleHint': 'торкніться, щоб змінити',
'collections.markVisited': 'Позначити як відвідане',
'collections.markVisitedAll': 'Відвідано всюди',
'collections.markVisitedSelection': 'Позначити відвіданим у списках',
'collections.markVisitedNone': 'Жодне з цих місць не збережене у списку',
'collections.markedVisited': 'Позначено як відвідане',
'collections.markedVisitedTrip': 'Позначено місць як відвідані: {count}',
'collections.copyToTrip': 'Скопіювати в подорож',
'collections.copyToTripTitle': 'Скопіювати в подорож',
'collections.copyN': 'Скопіювати {count} у подорож',
+2
View File
@@ -76,6 +76,8 @@ const journey: TranslationStrings = {
'journey.detail.backToJourney': 'Назад до подорожі',
'journey.detail.syncedWithTrips': 'Синхронізовано з поїздками',
'journey.detail.addEntry': 'Додати запис',
'journey.detail.jumpToTop': 'Нагору',
'journey.detail.jumpToLast': 'До останнього запису',
'journey.detail.newEntry': 'Новий запис',
'journey.detail.editEntry': 'Редагувати запис',
'journey.detail.noEntries': 'Поки що немає записів',
+7
View File
@@ -97,6 +97,13 @@ const collection: TranslationStrings = {
'collections.status.filterAll': 'Tất cả',
'collections.status.cycleHint': 'chạm để thay đổi',
'collections.markVisited': 'Đánh dấu đã đến',
'collections.markVisitedAll': 'Đã đến ở mọi danh sách',
'collections.markVisitedSelection': 'Đánh dấu đã đến trong danh sách của bạn',
'collections.markVisitedNone': 'Không có địa điểm nào trong số này được lưu vào danh sách',
'collections.markedVisited': 'Đã đánh dấu là đã đến',
'collections.markedVisitedTrip': 'Đã đánh dấu {count} địa điểm là đã đến',
'collections.copyToTrip': 'Sao chép vào chuyến đi',
'collections.copyToTripTitle': 'Sao chép vào chuyến đi',
'collections.copyN': 'Sao chép {count} vào chuyến đi',
+2
View File
@@ -76,6 +76,8 @@ const journey: TranslationStrings = {
'journey.detail.backToJourney': 'Quay lại Hành trình',
'journey.detail.syncedWithTrips': 'Đã đồng bộ hóa với Chuyến đi',
'journey.detail.addEntry': 'Thêm mục nhập',
'journey.detail.jumpToTop': 'Lên đầu trang',
'journey.detail.jumpToLast': 'Đến mục cuối cùng',
'journey.detail.newEntry': 'Mục mới',
'journey.detail.editEntry': 'Chỉnh sửa mục nhập',
'journey.detail.noEntries': 'Chưa có mục nào',
+7
View File
@@ -96,6 +96,13 @@ const collection: TranslationStrings = {
'collections.status.filterAll': '全部',
'collections.status.cycleHint': '點按切換',
'collections.markVisited': '標記為已造訪',
'collections.markVisitedAll': '在所有清單中標記',
'collections.markVisitedSelection': '在清單中標記為已造訪',
'collections.markVisitedNone': '這些地點都沒有儲存在任何清單中',
'collections.markedVisited': '已標記為造訪',
'collections.markedVisitedTrip': '已將 {count} 個地點標記為造訪',
'collections.copyToTrip': '複製到行程',
'collections.copyToTripTitle': '複製到行程',
'collections.copyN': '將 {count} 個複製到行程',
+2
View File
@@ -76,6 +76,8 @@ const journey: TranslationStrings = {
'journey.detail.backToJourney': '返回旅程',
'journey.detail.syncedWithTrips': '已與旅行同步',
'journey.detail.addEntry': '新增條目',
'journey.detail.jumpToTop': '回到頂部',
'journey.detail.jumpToLast': '跳到最後一則',
'journey.detail.newEntry': '新建條目',
'journey.detail.editEntry': '編輯條目',
'journey.detail.noEntries': '還沒有條目',
+7
View File
@@ -96,6 +96,13 @@ const collection: TranslationStrings = {
'collections.status.filterAll': '全部',
'collections.status.cycleHint': '点按切换',
'collections.markVisited': '标记为已去过',
'collections.markVisitedAll': '在所有清单中标记',
'collections.markVisitedSelection': '在清单中标记为已去过',
'collections.markVisitedNone': '这些地点都没有保存在任何清单中',
'collections.markedVisited': '已标记为去过',
'collections.markedVisitedTrip': '已将 {count} 个地点标记为去过',
'collections.copyToTrip': '复制到行程',
'collections.copyToTripTitle': '复制到行程',
'collections.copyN': '将 {count} 个复制到行程',
+2
View File
@@ -76,6 +76,8 @@ const journey: TranslationStrings = {
'journey.detail.backToJourney': '返回旅程',
'journey.detail.syncedWithTrips': '已与旅行同步',
'journey.detail.addEntry': '添加条目',
'journey.detail.jumpToTop': '回到顶部',
'journey.detail.jumpToLast': '跳到最后一条',
'journey.detail.newEntry': '新建条目',
'journey.detail.editEntry': '编辑条目',
'journey.detail.noEntries': '还没有条目',