Compare commits

...

7 Commits

Author SHA1 Message Date
elpatron 182ea497d8 chore: release v0.1.0.85 2026-06-01 19:43:24 +02:00
elpatron 837bcfe287 fix(ui): Disclaimer-Modal Scroll und Schließen-Button
Ein Scrollbereich im Hinweis-Dialog, Body-Lock beim Öffnen, X oben rechts an der Karte.

Co-authored-by: Cursor <cursoragent@cursor.com>
2026-06-01 19:42:56 +02:00
elpatron d261a1e7ca i18n: Mannschaft und Äquivalente zu Crew umbenennen
Einheitliche Crew-Terminologie in allen App-Sprachen (de, en, nb, sv, da).

Co-authored-by: Cursor <cursoragent@cursor.com>
2026-06-01 19:38:46 +02:00
elpatron 2ebc3e8a44 chore: release v0.1.0.84 2026-06-01 19:34:10 +02:00
elpatron 047a5b1bdb fix(crew): keep first legacy skipper when several are present
Prefer canonical skipper id and stop overwriting activeSkipperId during
legacy crew migration and read-only share conversion.

Co-authored-by: Cursor <cursoragent@cursor.com>
2026-06-01 19:34:01 +02:00
elpatron 7a7e9d5d28 chore: release v0.1.0.83 2026-06-01 19:31:20 +02:00
elpatron 39cbe707c7 fix(server): Docker build when prisma postinstall runs
Copy Prisma schema before npm ci in the builder image and skip
postinstall in the production stage since the client is copied from builder.

Co-authored-by: Cursor <cursoragent@cursor.com>
2026-06-01 19:30:53 +02:00
14 changed files with 315 additions and 215 deletions
+1 -1
View File
@@ -1 +1 @@
0.1.0.83
0.1.0.86
+20 -7
View File
@@ -567,18 +567,16 @@ html.scheme-dark .themed-select-option.is-selected {
}
.registration-disclaimer--modal {
position: relative;
width: min(560px, calc(100vw - 32px));
margin: 0;
}
.registration-disclaimer .auth-header {
position: relative;
}
.registration-disclaimer__close {
position: absolute;
top: 0;
right: 0;
top: 12px;
right: 12px;
z-index: 1;
display: inline-flex;
align-items: center;
justify-content: center;
@@ -604,6 +602,7 @@ html.scheme-dark .themed-select-option.is-selected {
align-items: center;
justify-content: center;
padding: 16px;
overflow: hidden;
background: rgba(2, 6, 23, 0.72);
}
@@ -611,7 +610,20 @@ html.scheme-dark .themed-select-option.is-selected {
width: 100%;
max-width: 560px;
max-height: min(90vh, 820px);
overflow-y: auto;
display: flex;
flex-direction: column;
min-height: 0;
overflow: hidden;
}
.disclaimer-modal-panel > .registration-disclaimer {
flex: 1 1 auto;
min-height: 0;
max-height: 100%;
width: 100%;
max-width: 100%;
overflow: hidden;
box-sizing: border-box;
}
.feedback-modal {
@@ -910,6 +922,7 @@ html.scheme-dark .themed-select-option.is-selected {
.registration-disclaimer__sections {
flex: 1;
min-height: 0;
overflow-y: auto;
display: flex;
flex-direction: column;
+6 -1
View File
@@ -13,7 +13,12 @@ export default function DisclaimerModal({ open, onClose }: DisclaimerModalProps)
if (event.key === 'Escape') onClose()
}
window.addEventListener('keydown', onKeyDown)
return () => window.removeEventListener('keydown', onKeyDown)
const prevOverflow = document.body.style.overflow
document.body.style.overflow = 'hidden'
return () => {
window.removeEventListener('keydown', onKeyDown)
document.body.style.overflow = prevOverflow
}
}, [open, onClose])
if (!open) return null
+2 -13
View File
@@ -7,7 +7,7 @@ import VesselForm from './VesselForm.tsx'
import LogbookCrewPicker from './LogbookCrewPicker.tsx'
import type { LogbookCrewSelectionData } from '../types/person.js'
import { emptyLogbookCrewSelection } from '../types/person.js'
import { personToSnapshot } from '../utils/personSnapshots.js'
import { legacyCrewRecordsToLogbookSelection } from '../utils/personSnapshots.js'
import type { PersonData } from '../types/person.js'
import LogEntriesList from './LogEntriesList.tsx'
import { Ship, Users, FileText, Lock, AlertCircle, Globe } from 'lucide-react'
@@ -112,18 +112,7 @@ export default function ReadOnlyViewer({ token, hexKey }: ReadOnlyViewerProps) {
setLegacyCrews(decCrews)
if (!data.logbookCrewSelection && decCrews.length > 0) {
const snapshotsById: LogbookCrewSelectionData['snapshotsById'] = {}
let activeSkipperId: string | null = null
const activeCrewIds: string[] = []
for (const c of decCrews) {
snapshotsById[c.payloadId] = personToSnapshot(c.payloadId, c.data)
if (c.payloadId === 'skipper' || c.data.role === 'skipper') {
activeSkipperId = c.payloadId
} else {
activeCrewIds.push(c.payloadId)
}
}
setLogbookCrewSelection({ activeSkipperId, activeCrewIds, snapshotsById })
setLogbookCrewSelection(legacyCrewRecordsToLogbookSelection(decCrews))
}
// Decrypt Entries
@@ -28,19 +28,19 @@ export default function RegistrationDisclaimer({
className={`auth-card glass registration-disclaimer${variant === 'view' ? ' registration-disclaimer--modal' : ''}`}
role="document"
>
{variant === 'view' && (
<button
type="button"
className="registration-disclaimer__close"
onClick={onDismiss}
aria-label={t('disclaimer.close')}
>
<X size={18} />
</button>
)}
<div className="auth-header">
<ScrollText className="auth-icon accent" size={48} />
<h2>{t('disclaimer.title')}</h2>
{variant === 'view' && (
<button
type="button"
className="registration-disclaimer__close"
onClick={onDismiss}
aria-label={t('disclaimer.close')}
>
<X size={18} />
</button>
)}
</div>
<p className="registration-disclaimer__intro">{t('disclaimer.intro')}</p>
+51 -51
View File
@@ -33,7 +33,7 @@
"nav": {
"dashboard": "Dashboard",
"vessel": "Skibsdata",
"crew": "Besætningsliste",
"crew": "Crew",
"deviation": "Tabel over distraktioner",
"logs": "Indlæg i logbogen",
"stats": "Statistik",
@@ -203,16 +203,16 @@
"sign_badge_skipper_title_valid": "Skipper har udgivet",
"sign_badge_skipper_title_invalid": "Skippers signatur er ugyldig - indholdet er blevet ændret",
"sign_classic_or_passkey": "Valgfrit: klassisk underskrift eller Passkey-frigivelse ovenfor",
"sign_crew_passkey_hint": "Besætningsmedlemmer med skriveadgang kan frigive via Passkey.",
"sign_crew_passkey_hint": "Crew-medlemmer med skriveadgang kan frigive via Passkey.",
"sign_offline_hint": "Passkey-Godkendelse kræver internet - klassisk underskrift mulig offline",
"sign_lock_notice": "Efter underskrivelsen kan der ikke foretages ændringer i logbogen (undtagen fotos), uden at skipper og besætning skal skrive under igen.",
"sign_lock_active": "Denne post er underskrevet. Ændringer i logbogen (undtagen fotos) fjerner automatisk skipperens og besætningens underskrifter.",
"sign_lock_notice": "Efter underskrivelsen kan der ikke foretages ændringer i logbogen (undtagen fotos), uden at skipper og crew skal skrive under igen.",
"sign_lock_active": "Denne post er underskrevet. Ændringer i logbogen (undtagen fotos) fjerner automatisk skipperens og crews underskrifter.",
"sign_lock_warning_title": "Bekræft underskrift",
"sign_lock_warning": "Efter underskrivelsen er det ikke længere muligt at foretage ændringer i logbogen (undtagen fotos), uden at skipper og besætning skal skrive under igen.\n\nVil du gerne fortsætte?",
"sign_lock_warning": "Efter underskrivelsen er det ikke længere muligt at foretage ændringer i logbogen (undtagen fotos), uden at skipper og crew skal skrive under igen.\n\nVil du gerne fortsætte?",
"sign_proceed": "Tegn",
"sign_cancel": "Annuller",
"sign_cleared_re_sign_title": "Underskrifter fjernet",
"sign_cleared_re_sign": "Logbogsoptegnelsen er blevet ændret. Skipperens og besætningens underskrifter er blevet fjernet. Underskriv venligst igen.",
"sign_cleared_re_sign": "Logbogsoptegnelsen er blevet ændret. Skipperens og crews underskrifter er blevet fjernet. Underskriv venligst igen.",
"no_entries": "Ingen logbogsposter fundet for denne yacht. Opret din første rejsedag!",
"back_to_list": "Tilbage til tidsskriftslisten",
"save": "Gem logbogsside",
@@ -387,12 +387,12 @@
"track_map_error": "Kortet kunne ikke indlæses.",
"exporting": "Eksport...",
"share_unsupported": "Deling understøttes ikke på denne enhed. Filen er blevet downloadet i stedet.",
"invite_crew": "Inviter besætningen",
"invite_crew": "Inviter crewen",
"invite_link_copied": "Invitationslink kopieret til udklipsholderen!",
"invite_link_desc": "Del dette link med besætningsmedlemmer for at give dem skriveadgang til denne logbog.",
"collaborators_list": "Medlemmer / besætning",
"invite_link_desc": "Del dette link med Crew-medlemmer for at give dem skriveadgang til denne logbog.",
"collaborators_list": "Medlemmer / crew",
"revoke": "Fjerne",
"revoke_confirm": "Er du sikker på, at du vil tilbagekalde dette besætningsmedlems adgang?",
"revoke_confirm": "Er du sikker på, at du vil tilbagekalde dette Crew-medlems adgang?",
"invite_role": "Rolle",
"invite_expires": "Linket er gyldigt i 48 timer",
"nmea_import_title": "Import NMEA log",
@@ -461,11 +461,11 @@
"delete_btn": "Slet logbog",
"section_owned": "Mine logbøger",
"section_shared": "Fælles logbøger",
"section_shared_hint": "Du er blevet inviteret som besætningsmedlem. Skipperprofil og indstillinger tilhører ejeren.",
"section_shared_hint": "Du er blevet inviteret som Crew-medlem. Skipperprofil og indstillinger tilhører ejeren.",
"role_owner": "Egen logbog",
"role_owner_hint": "Du er ejer og skipper af denne logbog",
"role_crew": "Adgang for besætning",
"role_crew_hint": "Inviteret logbog - du kan arbejde som besætning og underskrive den",
"role_crew": "Adgang for crew",
"role_crew_hint": "Inviteret logbog - du kan arbejde som crew og underskrive den",
"role_read": "Læs kun",
"role_read_hint": "Opdelt logbog - kun visning, ingen redigering",
"open_profile": "Åben profil af {{name}}",
@@ -600,8 +600,8 @@
"tour_desc": "Lad dig guide gennem de vigtigste områder i appen igen.",
"tour_restart": "Start turen igen",
"push_title": "Push-meddelelser",
"push_desc": "Som logbogsejer får du besked, når inviterede besætningsmedlemmer synkroniserer ændringer. Intet indhold overføres i ren tekst.",
"push_enable": "Giv os besked om ændringer i besætningen",
"push_desc": "Som logbogsejer får du besked, når inviterede Crew-medlemmer synkroniserer ændringer. Intet indhold overføres i ren tekst.",
"push_enable": "Giv os besked om ændringer i crewen",
"push_active": "Push-meddelelser er aktive på denne enhed.",
"push_unsupported": "Push-meddelelser understøttes ikke i denne browser.",
"push_denied_hint": "Notifikationer er blokeret. Tillad dem i browserens eller enhedens indstillinger.",
@@ -609,49 +609,49 @@
"push_error": "Push-meddelelser kunne ikke aktiveres."
},
"person_pool": {
"title": "Stambesætning og skippere",
"subtitle": "Administrer din personpulje her skippere og besætning til alle logbøger. Vælg aktiv besætning per logbog og rejsedag fra puljen.",
"title": "Stamm-Crew og skippere",
"subtitle": "Administrer din personpulje her skippere og crew til alle logbøger. Vælg aktiv crew per logbog og rejsedag fra puljen.",
"loading": "Indlæser personpulje…",
"skippers_section": "Skippere",
"crew_section": "Stambesætning",
"crew_section": "Stamm-Crew",
"add_skipper": "Tilføj skipper",
"add_crew": "Tilføj besætningsmedlem",
"add_crew": "Tilføj Crew-medlem",
"edit_skipper": "Rediger skipper",
"no_skippers": "Ingen skipper i puljen endnu.",
"no_crew": "Ingen besætningsmedlemmer i puljen endnu.",
"no_crew": "Ingen Crew-medlemmer i puljen endnu.",
"delete_confirm": "Fjern denne person fra puljen?"
},
"logbook_crew": {
"title": "Besætning for denne logbog",
"subtitle": "Vælg skipper og besætning for denne logbog. Nye rejsedage arver valget som standard.",
"loading": "Indlæser besætning…",
"title": "Crew for denne logbog",
"subtitle": "Vælg skipper og crew for denne logbog. Nye rejsedage arver valget som standard.",
"loading": "Indlæser crew…",
"active_skipper": "Skipper for denne logbog",
"active_crew": "Besætning for denne logbog",
"active_crew": "Crew for denne logbog",
"no_skippers_in_pool": "Ingen skipper i puljen tilføj i brugerprofilen først.",
"no_crew_in_pool": "Ingen besætning i puljen tilføj i brugerprofilen først.",
"no_crew_in_pool": "Ingen crew i puljen tilføj i brugerprofilen først.",
"no_skipper": "Ingen skipper valgt",
"unnamed": "Uden navn",
"save": "Gem besætning",
"saved": "Logbogbesætning gemt.",
"selection_only_hint": "Du ser den besætning ejeren har valgt (delt logbog)."
"save": "Gem crew",
"saved": "Logbog-Crew gemt.",
"selection_only_hint": "Du ser den crew ejeren har valgt (delt logbog)."
},
"entry_crew": {
"title": "Besætning på denne rejsedag",
"title": "Crew på denne rejsedag",
"subtitle": "Kan afvige fra logbogstandard. Følgende dage arver fra foregående dag.",
"day_skipper": "Skipper denne dag",
"day_crew": "Besætning denne dag",
"day_crew": "Crew denne dag",
"no_skipper": "Ingen skipper valgt",
"no_crew": "Ingen besætning valgt"
"no_crew": "Ingen crew valgt"
},
"crew": {
"title": "Skipper- og besætningsprofiler",
"title": "Skipper- og Crew-profiler",
"skipper_section": "Skipper-profil",
"skipper_read_only_hint": "Skipperprofilen kan kun redigeres af logbogens ejer.",
"crew_section": "Besætningsliste",
"add_crew": "Tilføj besætningsmedlem",
"edit_crew": "Rediger besætningsmedlem",
"no_crew": "Ingen besætningsmedlemmer tilføjet endnu.",
"max_crew": "Det maksimale antal på 12 besætningsmedlemmer i puljen er nået.",
"crew_section": "Crew-liste",
"add_crew": "Tilføj Crew-medlem",
"edit_crew": "Rediger Crew-medlem",
"no_crew": "Ingen Crew-medlemmer tilføjet endnu.",
"max_crew": "Det maksimale antal på 12 Crew-medlemmer i puljen er nået.",
"name": "Navn",
"address": "adresse",
"birthdate": "Fødselsdag",
@@ -664,8 +664,8 @@
"save": "Gem skipper-data",
"save_member": "Gem medlem",
"saved": "Skipperprofilen er blevet gemt!",
"loading": "Besætningsfilerne er indlæst.",
"delete_confirm": "Er du sikker på, at du vil fjerne dette besætningsmedlem?"
"loading": "Crew-filerne er indlæst.",
"delete_confirm": "Er du sikker på, at du vil fjerne dette Crew-medlem?"
},
"deviation": {
"title": "Tabel over kompasafvigelser",
@@ -687,7 +687,7 @@
"weather_date_mismatch": "Vejrdata kan kun hentes for i dag ({{today}}). Dette logbogsindlæg er dateret {{date}}.",
"gps_error": "Indtast en placering, eller find GPS-koordinaterne.",
"share_title": "Del logbog (skrivebeskyttet)",
"share_desc": "Aktivér denne mulighed for at oprette et offentligt, skrivebeskyttet link. Alle med linket kan se dine rejser, yachtprofiler og besætning. Krypteringsnøglerne overføres aldrig til serveren (de forbliver i hash-delen af URL'en).",
"share_desc": "Aktivér denne mulighed for at oprette et offentligt, skrivebeskyttet link. Alle med linket kan se dine rejser, yachtprofiler og crew. Krypteringsnøglerne overføres aldrig til serveren (de forbliver i hash-delen af URL'en).",
"share_privacy_warning": "Anbefaling: Del kun dette link privat (f.eks. via e-mail eller messenger), ikke på sociale medier.",
"share_enable": "Aktivér offentligt link",
"share_copied": "Link kopieret!",
@@ -695,7 +695,7 @@
"link_qr_hint": "Scan QR-koden med din telefon",
"link_qr_alt": "QR-kode til linket",
"danger_zone_title": "Farezone",
"danger_zone_desc": "Når du sletter din konto, slettes alle dine Passkey'er, logbøger, skibsdata, besætningsprofiler, rejseindlæg og E2E-nøgler uigenkaldeligt. Denne handling kan ikke fortrydes.",
"danger_zone_desc": "Når du sletter din konto, slettes alle dine Passkey'er, logbøger, skibsdata, Crew-profiler, rejseindlæg og E2E-nøgler uigenkaldeligt. Denne handling kan ikke fortrydes.",
"delete_account_btn": "Slet konto uigenkaldeligt",
"delete_account_confirm_title": "Slette konto?",
"delete_account_confirm_desc": "Er du helt sikker på, at du vil slette din konto uigenkaldeligt og alle tilknyttede logbøger og E2E-krypterede data?",
@@ -705,13 +705,13 @@
"delete_backup_hint": "Tip: Lav sikkerhedskopier af dine logbøger (.daagbok.json) i indstillingerne for hver logbog, før du sletter dem.",
"deleting_account": "Kontoen vil blive slettet...",
"invite_push_prompt_title": "Aktivere push-meddelelser?",
"invite_push_prompt_message": "Så snart inviterede besætningsmedlemmer synkroniserer ændringer, kan du blive informeret via push. Intet logbogsindhold sendes i almindelig tekst.",
"invite_push_prompt_ios_message": "Så snart besætningsmedlemmerne synkroniserer ændringer, kan du blive informeret via push. På iPhone/iPad: Føj appen til startskærmen (iOS 16.4+), og aktiver derefter push i brugerprofilen.",
"invite_push_prompt_message": "Så snart inviterede Crew-medlemmer synkroniserer ændringer, kan du blive informeret via push. Intet logbogsindhold sendes i almindelig tekst.",
"invite_push_prompt_ios_message": "Så snart Crew-medlemmerne synkroniserer ændringer, kan du blive informeret via push. På iPhone/iPad: Føj appen til startskærmen (iOS 16.4+), og aktiver derefter push i brugerprofilen.",
"invite_push_prompt_enable": "Aktiver nu",
"invite_push_prompt_later": "Senere",
"invite_push_prompt_success": "Push-meddelelser er aktive på denne enhed.",
"backup_title": "Sikkerhedskopiering og gendannelse",
"backup_desc": "Komplet krypteret backup af denne logbog (poster, fotos, GPS-spor, besætning, skib). Beskyttet med backup-passphrase - til gendannelse til denne eller en ny konto.",
"backup_desc": "Komplet krypteret backup af denne logbog (poster, fotos, GPS-spor, crew, skib). Beskyttet med backup-passphrase - til gendannelse til denne eller en ny konto.",
"backup_export_title": "Opret backup",
"backup_export_desc": "Downloader alle lokale data som .daagbok.json. Hold filen og adgangssætningen adskilt og sikker.",
"backup_restore_title": "Gendan sikkerhedskopi",
@@ -741,7 +741,7 @@
"backup_new_id_confirm": "Importere backup'en som en ny logbog med et nyt ID?",
"backup_stat_entries": "{{count}} Rejsedage",
"backup_stat_photos": "{{count}} Fotos",
"backup_stat_crew": "{{count}} Besætningens poster",
"backup_stat_crew": "{{count}} Crew-poster",
"backup_stat_tracks": "{{count}} GPS-spor",
"backup_exported_at": "Eksporteret: {{date}}"
},
@@ -883,7 +883,7 @@
},
"welcome_public": {
"title": "Velkommen om bord!",
"body": "Udforsk vores demo-logbog med tre dages rejse i Kielerfjorden - uden en konto. Denne korte tur viser dig skibsdata, besætning og logbogsposter."
"body": "Udforsk vores demo-logbog med tre dages rejse i Kielerfjorden - uden en konto. Denne korte tur viser dig skibsdata, crew og logbogsposter."
},
"nav_logs": {
"title": "Indlæg i logbogen",
@@ -906,12 +906,12 @@
"body": "Indtast navn, dimensioner og tekniske data for din yacht - udfyld én gang, tilgængelig for alle rejsedage."
},
"profile_crew_pool": {
"title": "Stambesætning og skippere",
"body": "I brugerprofilen vedligeholder du en personpulje flere skippere (f.eks. charter) og besætning til alle logbøger."
"title": "Stamm-Crew og skippere",
"body": "I brugerprofilen vedligeholder du en personpulje flere skippere (f.eks. charter) og crew til alle logbøger."
},
"nav_logbook_crew": {
"title": "Besætning per logbog",
"body": "Vælg skipper og besætning fra puljen til denne logbog. Rejsedage arver valget som standard."
"title": "Crew per logbog",
"body": "Vælg skipper og crew fra puljen til denne logbog. Rejsedage arver valget som standard."
},
"nav_stats": {
"title": "Statistik-dashboard",
@@ -937,7 +937,7 @@
},
"seo": {
"title": "Kapteins Daagbok - Gratis digital yachtlogbog (reklamefri)",
"description": "Gratis, reklamefri digital yachtlogbog med end-to-end-kryptering og Passkey-login. Dokumenter sikkert rejsedage, GPS-spor, besætnings- og skibsdata - også offline som PWA.",
"description": "Gratis, reklamefri digital yachtlogbog med end-to-end-kryptering og Passkey-login. Dokumenter sikkert rejsedage, GPS-spor, Crew- og skibsdata - også offline som PWA.",
"keywords": "Yachtlogbog, skibslogbog, logbog om bord, sejlads, Passkey, E2E-kryptering, GPS-spor, maritim logbog, gratis, reklamefri, gratis, uden reklame",
"ogImageAlt": "Kapteins Daagbok Logo"
}
+10 -10
View File
@@ -33,7 +33,7 @@
"nav": {
"dashboard": "Dashboard",
"vessel": "Schiffsdaten",
"crew": "Mannschaft",
"crew": "Crew",
"deviation": "Ablenkungstabelle",
"logs": "Logbucheinträge",
"stats": "Statistik",
@@ -610,7 +610,7 @@
},
"person_pool": {
"title": "Stammcrew & Skipper",
"subtitle": "Lege hier deinen Personen-Pool an Skipper und Crew für alle Logbücher. Aus diesem Pool wählst du pro Logbuch und Reisetag die aktive Mannschaft.",
"subtitle": "Lege hier deinen Personen-Pool an Skipper und Crew für alle Logbücher. Aus diesem Pool wählst du pro Logbuch und Reisetag die aktive Crew.",
"loading": "Personen-Pool wird geladen…",
"skippers_section": "Stammskipper",
"crew_section": "Stammcrew",
@@ -622,21 +622,21 @@
"delete_confirm": "Diese Person wirklich aus dem Pool entfernen?"
},
"logbook_crew": {
"title": "Mannschaft für dieses Logbuch",
"title": "Crew für dieses Logbuch",
"subtitle": "Wähle Skipper und Crew für dieses Logbuch. Neue Reisetage übernehmen diese Auswahl standardmäßig.",
"loading": "Mannschaft wird geladen…",
"loading": "Crew wird geladen…",
"active_skipper": "Skipper für dieses Logbuch",
"active_crew": "Crew für dieses Logbuch",
"no_skippers_in_pool": "Kein Skipper im Pool zuerst im Benutzerprofil anlegen.",
"no_crew_in_pool": "Keine Crew im Pool zuerst im Benutzerprofil anlegen.",
"no_skipper": "Kein Skipper gewählt",
"unnamed": "Unbenannt",
"save": "Mannschaft speichern",
"saved": "Mannschaft für das Logbuch gespeichert.",
"selection_only_hint": "Du siehst die vom Eigner festgelegte Mannschaft (geteiltes Logbuch)."
"save": "Crew speichern",
"saved": "Crew für das Logbuch gespeichert.",
"selection_only_hint": "Du siehst die vom Eigner festgelegte Crew (geteiltes Logbuch)."
},
"entry_crew": {
"title": "Mannschaft an diesem Reisetag",
"title": "Crew an diesem Reisetag",
"subtitle": "Kann vom Logbuch-Standard abweichen. Folge-Reisetage übernehmen den Vortag.",
"day_skipper": "Skipper an diesem Tag",
"day_crew": "Crew an diesem Tag",
@@ -883,7 +883,7 @@
},
"welcome_public": {
"title": "Willkommen an Bord!",
"body": "Erkunde unser Demo-Logbuch mit drei Reisetagen in der Kieler Förde ganz ohne Account. Diese Tour zeigt dir Schiffsdaten, Mannschaftsauswahl und Logbucheinträge. Die Stammcrew pflegst du später im Benutzerprofil."
"body": "Erkunde unser Demo-Logbuch mit drei Reisetagen in der Kieler Förde ganz ohne Account. Diese Tour zeigt dir Schiffsdaten, Crew-Auswahl und Logbucheinträge. Die Stammcrew pflegst du später im Benutzerprofil."
},
"nav_logs": {
"title": "Logbucheinträge",
@@ -910,7 +910,7 @@
"body": "Im Benutzerprofil pflegst du deinen Personen-Pool mehrere Skipper (z. B. Charter) und Crew-Mitglieder für alle Logbücher."
},
"nav_logbook_crew": {
"title": "Mannschaft pro Logbuch",
"title": "Crew pro Logbuch",
"body": "Wähle aus dem Pool, wer auf diesem Logbuch als Skipper und Crew gilt. Reisetage übernehmen diese Auswahl standardmäßig."
},
"nav_stats": {
+3 -3
View File
@@ -609,11 +609,11 @@
"push_error": "Could not enable push notifications."
},
"person_pool": {
"title": "Core crew & skippers",
"title": "Core Crew & skippers",
"subtitle": "Maintain your person pool here — skippers and crew for all logbooks. Select active crew per logbook and travel day from this pool.",
"loading": "Loading person pool…",
"skippers_section": "Skippers",
"crew_section": "Core crew",
"crew_section": "Core Crew",
"add_skipper": "Add skipper",
"add_crew": "Add crew member",
"edit_skipper": "Edit skipper",
@@ -906,7 +906,7 @@
"body": "Enter your yacht's name, dimensions, and technical details fill once, use on every travel day."
},
"profile_crew_pool": {
"title": "Core crew & skippers",
"title": "Core Crew & skippers",
"body": "In your user profile you maintain a person pool — multiple skippers (e.g. charter) and crew for all logbooks."
},
"nav_logbook_crew": {
+52 -52
View File
@@ -33,7 +33,7 @@
"nav": {
"dashboard": "Dashbord",
"vessel": "Skipsdata",
"crew": "Mannskapsliste",
"crew": "Crew",
"deviation": "Tabell over distraksjoner",
"logs": "Loggbokoppføringer",
"stats": "Statistikk",
@@ -183,7 +183,7 @@
"consumption": "Daglig forbruk",
"signatures": "Underskrifter / frigivelse",
"sign_skipper": "Skippers signatur",
"sign_crew": "Mannskapets signatur",
"sign_crew": "Crews signatur",
"sign_hint": "Signer med finger, penn eller mus",
"sign_clear": "Slett",
"sign_export_image": "[Signatur]",
@@ -203,16 +203,16 @@
"sign_badge_skipper_title_valid": "Skipper har gitt ut",
"sign_badge_skipper_title_invalid": "Skippersignaturen er ugyldig - innholdet har blitt endret",
"sign_classic_or_passkey": "Valgfritt: klassisk signatur eller Passkey utgivelse ovenfor",
"sign_crew_passkey_hint": "Besetningsmedlemmer med skrivetilgang kan frigjøre via Passkey.",
"sign_crew_passkey_hint": "Crew-medlemmer med skrivetilgang kan frigjøre via Passkey.",
"sign_offline_hint": "Passkey-Godkjenning krever Internett - klassisk signatur mulig offline",
"sign_lock_notice": "Etter signering er det ikke mulig å gjøre endringer i loggbokoppføringen (unntatt bilder) uten at skipper og mannskap må signere på nytt.",
"sign_lock_active": "Denne oppføringen er signert. Endringer i loggboken (unntatt bilder) fjerner automatisk skipperens og mannskapets signaturer.",
"sign_lock_notice": "Etter signering er det ikke mulig å gjøre endringer i loggbokoppføringen (unntatt bilder) uten at skipper og crew må signere på nytt.",
"sign_lock_active": "Denne oppføringen er signert. Endringer i loggboken (unntatt bilder) fjerner automatisk skipperens og crews signaturer.",
"sign_lock_warning_title": "Bekreft signatur",
"sign_lock_warning": "Etter signering er det ikke lenger mulig å gjøre endringer i loggbokoppføringen (unntatt bilder) uten at skipper og mannskap må signere på nytt.\n\nØnsker du å fortsette?",
"sign_lock_warning": "Etter signering er det ikke lenger mulig å gjøre endringer i loggbokoppføringen (unntatt bilder) uten at skipper og crew må signere på nytt.\n\nØnsker du å fortsette?",
"sign_proceed": "Skilt",
"sign_cancel": "Avbryt",
"sign_cleared_re_sign_title": "Signaturer fjernet",
"sign_cleared_re_sign": "Loggbokoppføringen har blitt endret. Skipperens og mannskapets signaturer er fjernet. Vennligst signer på nytt.",
"sign_cleared_re_sign": "Loggbokoppføringen har blitt endret. Skipperens og crews signaturer er fjernet. Vennligst signer på nytt.",
"no_entries": "Ingen loggbokoppføringer funnet for denne båten. Lag din første seilasdag!",
"back_to_list": "Tilbake til tidsskriftlisten",
"save": "Lagre loggbokside",
@@ -387,12 +387,12 @@
"track_map_error": "Kartet kunne ikke lastes inn.",
"exporting": "Eksport...",
"share_unsupported": "Deling støttes ikke på denne enheten. Filen har blitt lastet ned i stedet.",
"invite_crew": "Inviter mannskapet",
"invite_crew": "Inviter crewet",
"invite_link_copied": "Invitasjonslenke kopiert til utklippstavlen!",
"invite_link_desc": "Del denne lenken med besetningsmedlemmene for å gi dem skrivetilgang til loggboken.",
"collaborators_list": "Medlemmer / Besetning",
"invite_link_desc": "Del denne lenken med Crew-medlemmene for å gi dem skrivetilgang til loggboken.",
"collaborators_list": "Medlemmer / Crew",
"revoke": "Fjern",
"revoke_confirm": "Er du sikker på at du vil oppheve dette besetningsmedlemmets tilgang?",
"revoke_confirm": "Er du sikker på at du vil oppheve dette Crew-medlemmets tilgang?",
"invite_role": "Rolle",
"invite_expires": "Lenken er gyldig i 48 timer",
"nmea_import_title": "Import NMEA log",
@@ -461,11 +461,11 @@
"delete_btn": "Slett loggbok",
"section_owned": "Loggbøkene mine",
"section_shared": "Felles loggbøker",
"section_shared_hint": "Du er invitert som besetningsmedlem. Skipperprofil og innstillinger tilhører eieren.",
"section_shared_hint": "Du er invitert som Crew-medlem. Skipperprofil og innstillinger tilhører eieren.",
"role_owner": "Egen loggbok",
"role_owner_hint": "Du er eier og skipper av denne loggboken",
"role_crew": "Tilgang for mannskapet",
"role_crew_hint": "Loggbok med invitasjon - du kan jobbe som mannskap og signere den",
"role_crew": "Tilgang for crewet",
"role_crew_hint": "Loggbok med invitasjon - du kan jobbe som crew og signere den",
"role_read": "Bare les",
"role_read_hint": "Delt loggbok - kun visning, ingen redigering",
"open_profile": "Åpne profilen til {{name}}",
@@ -600,8 +600,8 @@
"tour_desc": "La deg veilede gjennom de viktigste områdene i appen på nytt.",
"tour_restart": "Start turen på nytt",
"push_title": "Push-varsler",
"push_desc": "Som loggbokseier vil du bli varslet når inviterte besetningsmedlemmer synkroniserer endringer. Ingen innhold overføres i ren tekst.",
"push_enable": "Gi oss beskjed om endringer i mannskapet",
"push_desc": "Som loggbokseier vil du bli varslet når inviterte Crew-medlemmer synkroniserer endringer. Ingen innhold overføres i ren tekst.",
"push_enable": "Gi oss beskjed om endringer i crewet",
"push_active": "Push-varsler er aktive på denne enheten.",
"push_unsupported": "Push-varsler støttes ikke i denne nettleseren.",
"push_denied_hint": "Varsler er blokkert. Tillat dem i innstillingene i nettleseren eller på enheten.",
@@ -609,49 +609,49 @@
"push_error": "Push-varsler kunne ikke aktiveres."
},
"person_pool": {
"title": "Stammmannskap og skippere",
"subtitle": "Hold personpoolen din her skippere og mannskap for alle loggbøker. Velg aktivt mannskap per loggbok og reisedag fra poolen.",
"title": "Stamm-Crew og skippere",
"subtitle": "Hold personpoolen din her skippere og crew for alle loggbøker. Velg aktivt crew per loggbok og reisedag fra poolen.",
"loading": "Laster personpool…",
"skippers_section": "Skippere",
"crew_section": "Stammmannskap",
"crew_section": "Stamm-Crew",
"add_skipper": "Legg til skipper",
"add_crew": "Legg til mannskapsmedlem",
"add_crew": "Legg til Crew-medlem",
"edit_skipper": "Rediger skipper",
"no_skippers": "Ingen skipper i poolen ennå.",
"no_crew": "Ingen mannskapsmedlemmer i poolen ennå.",
"no_crew": "Ingen Crew-medlemmer i poolen ennå.",
"delete_confirm": "Fjerne denne personen fra poolen?"
},
"logbook_crew": {
"title": "Mannskap for denne loggboken",
"subtitle": "Velg skipper og mannskap for denne loggboken. Nye reisedager arver valget som standard.",
"loading": "Laster mannskap…",
"title": "Crew for denne loggboken",
"subtitle": "Velg skipper og crew for denne loggboken. Nye reisedager arver valget som standard.",
"loading": "Laster crew…",
"active_skipper": "Skipper for denne loggboken",
"active_crew": "Mannskap for denne loggboken",
"active_crew": "Crew for denne loggboken",
"no_skippers_in_pool": "Ingen skipper i poolen legg til i brukerprofilen først.",
"no_crew_in_pool": "Ingen mannskap i poolen legg til i brukerprofilen først.",
"no_crew_in_pool": "Ingen crew i poolen legg til i brukerprofilen først.",
"no_skipper": "Ingen skipper valgt",
"unnamed": "Uten navn",
"save": "Lagre mannskap",
"saved": "Loggbokmannskap lagret.",
"selection_only_hint": "Du ser mannskapet eieren har valgt (delt loggbok)."
"save": "Lagre crew",
"saved": "Loggbok-Crew lagret.",
"selection_only_hint": "Du ser crewet eieren har valgt (delt loggbok)."
},
"entry_crew": {
"title": "Mannskap på denne reisedagen",
"title": "Crew på denne reisedagen",
"subtitle": "Kan avvike fra loggbokstandard. Følgende dager arver fra forrige dag.",
"day_skipper": "Skipper denne dagen",
"day_crew": "Mannskap denne dagen",
"day_crew": "Crew denne dagen",
"no_skipper": "Ingen skipper valgt",
"no_crew": "Ingen mannskap valgt"
"no_crew": "Ingen crew valgt"
},
"crew": {
"title": "Skipper- og mannskapsprofiler",
"title": "Skipper- og Crew-profiler",
"skipper_section": "Skipperprofil",
"skipper_read_only_hint": "Skipperprofilen kan bare redigeres av eieren av loggboken.",
"crew_section": "Mannskapsliste",
"add_crew": "Legg til besetningsmedlem",
"edit_crew": "Rediger besetningsmedlem",
"no_crew": "Ingen besetningsmedlemmer er lagt til ennå.",
"max_crew": "Maksimalt antall på 12 besetningsmedlemmer i poolen er nådd.",
"crew_section": "Crew-liste",
"add_crew": "Legg til Crew-medlem",
"edit_crew": "Rediger Crew-medlem",
"no_crew": "Ingen Crew-medlemmer er lagt til ennå.",
"max_crew": "Maksimalt antall på 12 Crew-medlemmer i poolen er nådd.",
"name": "Navn",
"address": "adresse",
"birthdate": "Bursdag",
@@ -664,8 +664,8 @@
"save": "Lagre skipperdata",
"save_member": "Lagre medlem",
"saved": "Skipperprofilen er vellykket lagret!",
"loading": "Mannskapsfilene er lastet inn...",
"delete_confirm": "Er du sikker på at du vil fjerne dette besetningsmedlemmet?"
"loading": "Crew-filene er lastet inn...",
"delete_confirm": "Er du sikker på at du vil fjerne dette Crew-medlemmet?"
},
"deviation": {
"title": "Tabell over kompassavvik",
@@ -687,7 +687,7 @@
"weather_date_mismatch": "Værdata kan bare hentes ut for i dag ({{today}}). Denne loggbokoppføringen er datert {{date}}.",
"gps_error": "Vennligst skriv inn en posisjon eller finn GPS-koordinatene.",
"share_title": "Del loggbok (skrivebeskyttet)",
"share_desc": "Aktiver dette alternativet for å opprette en offentlig, skrivebeskyttet lenke. Alle som har denne lenken, kan se seilasene, båtprofilene og mannskapet ditt. Krypteringsnøklene overføres aldri til serveren (de forblir i hash-delen av URL-en).",
"share_desc": "Aktiver dette alternativet for å opprette en offentlig, skrivebeskyttet lenke. Alle som har denne lenken, kan se seilasene, båtprofilene og crewet ditt. Krypteringsnøklene overføres aldri til serveren (de forblir i hash-delen av URL-en).",
"share_privacy_warning": "Anbefaling: Del denne lenken kun privat (f.eks. via e-post eller messenger), ikke på sosiale medier.",
"share_enable": "Aktiver offentlig lenke",
"share_copied": "Linken er kopiert!",
@@ -695,7 +695,7 @@
"link_qr_hint": "Skann QR-koden med telefonen",
"link_qr_alt": "QR-kode for lenken",
"danger_zone_title": "Faresone",
"danger_zone_desc": "Hvis du sletter kontoen din, slettes alle dine Passkeys, loggbøker, skipsdata, mannskapsprofiler, reiseoppføringer og E2E-nøkler ugjenkallelig. Denne handlingen kan ikke angres.",
"danger_zone_desc": "Hvis du sletter kontoen din, slettes alle dine Passkeys, loggbøker, skipsdata, Crew-profiler, reiseoppføringer og E2E-nøkler ugjenkallelig. Denne handlingen kan ikke angres.",
"delete_account_btn": "Slett konto ugjenkallelig",
"delete_account_confirm_title": "Slett konto?",
"delete_account_confirm_desc": "Er du helt sikker på at du vil slette kontoen din og alle tilknyttede loggbøker og E2E-krypterte data ugjenkallelig?",
@@ -705,13 +705,13 @@
"delete_backup_hint": "Tips: Lag sikkerhetskopier av loggbøkene dine (.daagbok.json) i innstillingene for hver loggbok før du sletter dem.",
"deleting_account": "Kontoen vil bli slettet...",
"invite_push_prompt_title": "Aktivere push-varsler?",
"invite_push_prompt_message": "Så snart inviterte besetningsmedlemmer synkroniserer endringer, kan du bli informert via push. Ingen loggbokinnhold sendes i ren tekst.",
"invite_push_prompt_ios_message": "Så snart besetningsmedlemmene synkroniserer endringer, kan du bli informert via push. På iPhone/iPad: Legg til appen på startskjermen (iOS 16.4+), og aktiver deretter push i brukerprofilen.",
"invite_push_prompt_message": "Så snart inviterte Crew-medlemmer synkroniserer endringer, kan du bli informert via push. Ingen loggbokinnhold sendes i ren tekst.",
"invite_push_prompt_ios_message": "Så snart Crew-medlemmene synkroniserer endringer, kan du bli informert via push. På iPhone/iPad: Legg til appen på startskjermen (iOS 16.4+), og aktiver deretter push i brukerprofilen.",
"invite_push_prompt_enable": "Aktiver nå",
"invite_push_prompt_later": "Senere",
"invite_push_prompt_success": "Push-varsler er aktive på denne enheten.",
"backup_title": "Sikkerhetskopiering og gjenoppretting",
"backup_desc": "Fullstendig kryptert sikkerhetskopi av denne loggboken (oppføringer, bilder, GPS-spor, mannskap, skip). Beskyttet med sikkerhetskopieringspassord - for gjenoppretting til denne eller en ny konto.",
"backup_desc": "Fullstendig kryptert sikkerhetskopi av denne loggboken (oppføringer, bilder, GPS-spor, crew, skip). Beskyttet med sikkerhetskopieringspassord - for gjenoppretting til denne eller en ny konto.",
"backup_export_title": "Opprett sikkerhetskopi",
"backup_export_desc": "Laster ned alle lokale data som .daagbok.json. Hold filen og passordfrasen adskilt og sikker.",
"backup_restore_title": "Gjenopprett sikkerhetskopi",
@@ -741,7 +741,7 @@
"backup_new_id_confirm": "Importere sikkerhetskopien som en ny loggbok med ny ID?",
"backup_stat_entries": "{{count}} Reisedager",
"backup_stat_photos": "{{count}} Bilder",
"backup_stat_crew": "{{count}} Mannskapsposter",
"backup_stat_crew": "{{count}} Crew-poster",
"backup_stat_tracks": "{{count}} GPS-spor",
"backup_exported_at": "Eksportert: {{date}}"
},
@@ -883,7 +883,7 @@
},
"welcome_public": {
"title": "Velkommen om bord!",
"body": "Utforsk vår demologgbok med tre dagers reise i Kielfjorden - uten konto. Denne korte omvisningen viser deg skipsdata, mannskap og loggbokoppføringer."
"body": "Utforsk vår demologgbok med tre dagers reise i Kielfjorden - uten konto. Denne korte omvisningen viser deg skipsdata, crew og loggbokoppføringer."
},
"nav_logs": {
"title": "Loggbokoppføringer",
@@ -906,12 +906,12 @@
"body": "Skriv inn navn, dimensjoner og tekniske data for båten din - fyll inn én gang, tilgjengelig for alle reisedager."
},
"profile_crew_pool": {
"title": "Stammmannskap og skippere",
"body": "I brukerprofilen vedlikeholder du en personpool flere skippere (f.eks. charter) og mannskap for alle loggbøker."
"title": "Stamm-Crew og skippere",
"body": "I brukerprofilen vedlikeholder du en personpool flere skippere (f.eks. charter) og crew for alle loggbøker."
},
"nav_logbook_crew": {
"title": "Mannskap per loggbok",
"body": "Velg skipper og mannskap fra poolen for denne loggboken. Reisedager arver valget som standard."
"title": "Crew per loggbok",
"body": "Velg skipper og crew fra poolen for denne loggboken. Reisedager arver valget som standard."
},
"nav_stats": {
"title": "Dashbord for statistikk",
@@ -937,7 +937,7 @@
},
"seo": {
"title": "Kapteins Daagbok - Gratis digital loggbok for fritidsbåter (uten reklame)",
"description": "Gratis, annonsefri digital loggbok med ende-til-ende-kryptering og Passkey-pålogging. Dokumenter seilingsdager, GPS-spor, mannskaps- og skipsdata på en sikker måte - også offline som PWA.",
"description": "Gratis, annonsefri digital loggbok med ende-til-ende-kryptering og Passkey-pålogging. Dokumenter seilingsdager, GPS-spor, Crew- og skipsdata på en sikker måte - også offline som PWA.",
"keywords": "Yachtloggbok, skipsloggbok, loggbok om bord, seiling, Passkey, E2E-kryptering, GPS-sporing, maritim loggbok, gratis, reklamefri, gratis, uten reklame",
"ogImageAlt": "Kapteins Daagbok Logo"
}
+53 -53
View File
@@ -33,7 +33,7 @@
"nav": {
"dashboard": "Instrumentpanel",
"vessel": "Fartygsdata",
"crew": "Besättningslista",
"crew": "Crew",
"deviation": "Distraktionsbord",
"logs": "Loggboksanteckningar",
"stats": "Statistik",
@@ -183,7 +183,7 @@
"consumption": "Daglig konsumtion",
"signatures": "Underskrifter / frisläppande",
"sign_skipper": "Skepparens signatur",
"sign_crew": "Besättningens signatur",
"sign_crew": "Crews signatur",
"sign_hint": "Signera med finger, penna eller mus",
"sign_clear": "Radera",
"sign_export_image": "[Signatur]",
@@ -203,16 +203,16 @@
"sign_badge_skipper_title_valid": "Skepparen har släppt",
"sign_badge_skipper_title_invalid": "Skippers signatur ogiltig - innehållet har ändrats",
"sign_classic_or_passkey": "Valfritt: klassisk signatur eller Passkey release ovan",
"sign_crew_passkey_hint": "Besättningsmedlemmar med skrivbehörighet kan frigöra via Passkey.",
"sign_crew_passkey_hint": "Crew-medlemmar med skrivbehörighet kan frigöra via Passkey.",
"sign_offline_hint": "Passkey-Godkännande kräver Internet - klassisk signatur möjlig offline",
"sign_lock_notice": "Efter undertecknandet är det inte möjligt att göra ändringar i loggboksanteckningen (utom foton) utan att skepparen och besättningen måste underteckna på nytt.",
"sign_lock_active": "Denna post är signerad. Ändringar i loggboken (utom foton) tar automatiskt bort skepparens och besättningens signaturer.",
"sign_lock_notice": "Efter undertecknandet är det inte möjligt att göra ändringar i loggboksanteckningen (utom foton) utan att skepparen och crewen måste underteckna på nytt.",
"sign_lock_active": "Denna post är signerad. Ändringar i loggboken (utom foton) tar automatiskt bort skepparens och crews signaturer.",
"sign_lock_warning_title": "Bekräfta underskrift",
"sign_lock_warning": "Efter undertecknandet är det inte längre möjligt att göra ändringar i loggboksanteckningen (utom foton) utan att skepparen och besättningen måste underteckna på nytt.\n\nVill du fortsätta?",
"sign_lock_warning": "Efter undertecknandet är det inte längre möjligt att göra ändringar i loggboksanteckningen (utom foton) utan att skepparen och crewen måste underteckna på nytt.\n\nVill du fortsätta?",
"sign_proceed": "Teckna",
"sign_cancel": "Avbryt",
"sign_cleared_re_sign_title": "Underskrifter borttagna",
"sign_cleared_re_sign": "Loggboksanteckningen har ändrats. Skepparens och besättningens namnteckningar har tagits bort. Vänligen underteckna igen.",
"sign_cleared_re_sign": "Loggboksanteckningen har ändrats. Skepparens och crews namnteckningar har tagits bort. Vänligen underteckna igen.",
"no_entries": "Inga loggboksposter hittade för denna yacht. Skapa din första resedag!",
"back_to_list": "Tillbaka till tidskriftslistan",
"save": "Spara loggbokssida",
@@ -387,12 +387,12 @@
"track_map_error": "Kartan kunde inte läsas in.",
"exporting": "Export...",
"share_unsupported": "Delning stöds inte på den här enheten. Filen har laddats ner istället.",
"invite_crew": "Bjud in besättningen",
"invite_crew": "Bjud in crewen",
"invite_link_copied": "Länk till inbjudan kopierad till urklipp!",
"invite_link_desc": "Dela den här länken med besättningsmedlemmar för att ge dem skrivrättigheter till loggboken.",
"collaborators_list": "Medlemmar / Besättning",
"invite_link_desc": "Dela den här länken med Crew-medlemmar för att ge dem skrivrättigheter till loggboken.",
"collaborators_list": "Medlemmar / Crew",
"revoke": "Ta bort",
"revoke_confirm": "Är du säker på att du vill återkalla den här besättningsmedlemmens åtkomst?",
"revoke_confirm": "Är du säker på att du vill återkalla den här Crew-medlemmens åtkomst?",
"invite_role": "Roll",
"invite_expires": "Länken är giltig i 48 timmar",
"nmea_import_title": "Import NMEA log",
@@ -461,11 +461,11 @@
"delete_btn": "Radera loggbok",
"section_owned": "Mina loggböcker",
"section_shared": "Delade loggböcker",
"section_shared_hint": "Du har blivit inbjuden som besättningsmedlem. Skepparens profil och inställningar tillhör ägaren.",
"section_shared_hint": "Du har blivit inbjuden som Crew-medlem. Skepparens profil och inställningar tillhör ägaren.",
"role_owner": "Egen loggbok",
"role_owner_hint": "Du är ägare och skeppare till denna loggbok",
"role_crew": "Tillträde för besättningen",
"role_crew_hint": "Inbjuden loggbok - du kan arbeta som besättning och underteckna den",
"role_crew": "Tillträde för crewen",
"role_crew_hint": "Inbjuden loggbok - du kan arbeta som crew och underteckna den",
"role_read": "Endast läsning",
"role_read_hint": "Delad loggbok - endast visning, ingen redigering",
"open_profile": "Öppna profil för {{name}}",
@@ -600,8 +600,8 @@
"tour_desc": "Låt dig vägledas genom de viktigaste områdena i appen igen.",
"tour_restart": "Starta resan igen",
"push_title": "Push-meddelanden",
"push_desc": "Som loggboksägare får du ett meddelande när inbjudna besättningsmedlemmar synkroniserar ändringar. Inget innehåll överförs i klartext.",
"push_enable": "Meddela oss om förändringar i besättningen",
"push_desc": "Som loggboksägare får du ett meddelande när inbjudna Crew-medlemmar synkroniserar ändringar. Inget innehåll överförs i klartext.",
"push_enable": "Meddela oss om förändringar i crewen",
"push_active": "Push-meddelanden är aktiva på den här enheten.",
"push_unsupported": "Push-meddelanden stöds inte i den här webbläsaren.",
"push_denied_hint": "Meddelanden är blockerade. Tillåt dem i webbläsarens eller enhetens inställningar.",
@@ -609,49 +609,49 @@
"push_error": "Push-meddelanden kunde inte aktiveras."
},
"person_pool": {
"title": "Stambesättning och skeppare",
"subtitle": "Underhåll din personpool här skeppare och besättning för alla loggböcker. Välj aktiv besättning per loggbok och resdag från poolen.",
"title": "Stamm-Crew och skeppare",
"subtitle": "Underhåll din personpool här skeppare och crew för alla loggböcker. Välj aktiv crew per loggbok och resdag från poolen.",
"loading": "Laddar personpool…",
"skippers_section": "Skeppare",
"crew_section": "Stambesättning",
"crew_section": "Stamm-Crew",
"add_skipper": "Lägg till skeppare",
"add_crew": "Lägg till besättningsmedlem",
"add_crew": "Lägg till Crew-medlem",
"edit_skipper": "Redigera skeppare",
"no_skippers": "Ingen skeppare i poolen ännu.",
"no_crew": "Inga besättningsmedlemmar i poolen ännu.",
"no_crew": "Inga Crew-medlemmar i poolen ännu.",
"delete_confirm": "Ta bort denna person från poolen?"
},
"logbook_crew": {
"title": "Besättning för denna loggbok",
"subtitle": "Välj skeppare och besättning för denna loggbok. Nya resdagar ärver valet som standard.",
"loading": "Laddar besättning…",
"title": "Crew för denna loggbok",
"subtitle": "Välj skeppare och crew för denna loggbok. Nya resdagar ärver valet som standard.",
"loading": "Laddar crew…",
"active_skipper": "Skeppare för denna loggbok",
"active_crew": "Besättning för denna loggbok",
"active_crew": "Crew för denna loggbok",
"no_skippers_in_pool": "Ingen skeppare i poolen lägg till i användarprofilen först.",
"no_crew_in_pool": "Ingen besättning i poolen lägg till i användarprofilen först.",
"no_crew_in_pool": "Ingen crew i poolen lägg till i användarprofilen först.",
"no_skipper": "Ingen skeppare vald",
"unnamed": "Namnlös",
"save": "Spara besättning",
"saved": "Loggbokbesättning sparad.",
"selection_only_hint": "Du ser den besättning ägaren valt (delad loggbok)."
"save": "Spara crew",
"saved": "Loggbok-Crew sparad.",
"selection_only_hint": "Du ser den crew ägaren valt (delad loggbok)."
},
"entry_crew": {
"title": "Besättning denna resdag",
"title": "Crew denna resdag",
"subtitle": "Kan skilja sig från loggboksstandard. Följande dagar ärver från föregående dag.",
"day_skipper": "Skeppare denna dag",
"day_crew": "Besättning denna dag",
"day_crew": "Crew denna dag",
"no_skipper": "Ingen skeppare vald",
"no_crew": "Ingen besättning vald"
"no_crew": "Ingen crew vald"
},
"crew": {
"title": "Profiler för skeppare och besättning",
"title": "Profiler för skeppare och crew",
"skipper_section": "Skepparens profil",
"skipper_read_only_hint": "Skepparens profil kan endast redigeras av loggbokens ägare.",
"crew_section": "Besättningslista",
"add_crew": "Lägg till besättningsmedlem",
"edit_crew": "Redigera besättningsmedlem",
"no_crew": "Inga besättningsmedlemmar har lagts till ännu.",
"max_crew": "Maximalt antal på 12 besättningsmedlemmar i poolen uppnått.",
"crew_section": "Crew-lista",
"add_crew": "Lägg till Crew-medlem",
"edit_crew": "Redigera Crew-medlem",
"no_crew": "Inga Crew-medlemmar har lagts till ännu.",
"max_crew": "Maximalt antal på 12 Crew-medlemmar i poolen uppnått.",
"name": "Namn",
"address": "adress",
"birthdate": "Födelsedag",
@@ -664,8 +664,8 @@
"save": "Spara skeppardata",
"save_member": "Spara medlem",
"saved": "Skepparens profil har sparats!",
"loading": "Besättningsfilerna är laddade...",
"delete_confirm": "Är du säker på att du vill ta bort den här besättningsmedlemmen?"
"loading": "Crew-filerna är laddade...",
"delete_confirm": "Är du säker på att du vill ta bort den här Crew-medlemmen?"
},
"deviation": {
"title": "Tabell för kompassavvikelse",
@@ -687,7 +687,7 @@
"weather_date_mismatch": "Väderdata kan endast hämtas för idag ({{today}}). Denna loggbokspost är daterad {{date}}.",
"gps_error": "Ange en plats eller bestäm GPS-koordinaterna.",
"share_title": "Aktieloggbok (skrivskyddad)",
"share_desc": "Aktivera det här alternativet för att skapa en publik, skrivskyddad länk. Alla som har länken kan se dina resor, båtprofiler och besättning. Krypteringsnycklarna överförs aldrig till servern (de finns kvar i hashdelen av URL:en).",
"share_desc": "Aktivera det här alternativet för att skapa en publik, skrivskyddad länk. Alla som har länken kan se dina resor, båtprofiler och crew. Krypteringsnycklarna överförs aldrig till servern (de finns kvar i hashdelen av URL:en).",
"share_privacy_warning": "Rekommendation: Dela endast den här länken privat (t.ex. via e-post eller messenger), inte på sociala medier.",
"share_enable": "Aktivera offentlig länk",
"share_copied": "Länk kopierad!",
@@ -695,7 +695,7 @@
"link_qr_hint": "Skanna QR-koden med mobilen",
"link_qr_alt": "QR-kod för länken",
"danger_zone_title": "Farlig zon",
"danger_zone_desc": "Om du raderar ditt konto raderas oåterkalleligen alla dina Passkey, loggböcker, fartygsdata, besättningsprofiler, reseanteckningar och E2E-nycklar. Denna åtgärd kan inte ångras.",
"danger_zone_desc": "Om du raderar ditt konto raderas oåterkalleligen alla dina Passkey, loggböcker, fartygsdata, Crew-profiler, reseanteckningar och E2E-nycklar. Denna åtgärd kan inte ångras.",
"delete_account_btn": "Ta bort konto oåterkalleligt",
"delete_account_confirm_title": "Radera konto?",
"delete_account_confirm_desc": "Är du helt säker på att du oåterkalleligen vill radera ditt konto och alla tillhörande loggböcker och E2E-krypterade data?",
@@ -705,13 +705,13 @@
"delete_backup_hint": "Tips: Skapa säkerhetskopior av dina loggböcker (.daagbok.json) i inställningarna för varje loggbok innan du raderar dem.",
"deleting_account": "Kontot kommer att raderas...",
"invite_push_prompt_title": "Aktivera push-meddelanden?",
"invite_push_prompt_message": "Så snart inbjudna besättningsmedlemmar synkroniserar ändringar kan du bli informerad via push. Inget loggboksinnehåll skickas i klartext.",
"invite_push_prompt_ios_message": "Så snart besättningsmedlemmar synkroniserar ändringar kan du bli informerad via push. På iPhone/iPad: Lägg till appen på startskärmen (iOS 16.4+) och aktivera sedan push i användarprofilen.",
"invite_push_prompt_message": "Så snart inbjudna Crew-medlemmar synkroniserar ändringar kan du bli informerad via push. Inget loggboksinnehåll skickas i klartext.",
"invite_push_prompt_ios_message": "Så snart Crew-medlemmar synkroniserar ändringar kan du bli informerad via push. På iPhone/iPad: Lägg till appen på startskärmen (iOS 16.4+) och aktivera sedan push i användarprofilen.",
"invite_push_prompt_enable": "Aktivera nu",
"invite_push_prompt_later": "Senare",
"invite_push_prompt_success": "Push-meddelanden är aktiva på den här enheten.",
"backup_title": "Säkerhetskopiering och återställning",
"backup_desc": "Komplett krypterad säkerhetskopia av denna loggbok (poster, foton, GPS-spår, besättning, fartyg). Skyddad med lösenfras för säkerhetskopian - för återställning till detta eller ett nytt konto.",
"backup_desc": "Komplett krypterad säkerhetskopia av denna loggbok (poster, foton, GPS-spår, crew, fartyg). Skyddad med lösenfras för säkerhetskopian - för återställning till detta eller ett nytt konto.",
"backup_export_title": "Skapa säkerhetskopia",
"backup_export_desc": "Laddar ner alla lokala data som .daagbok.json. Förvara filen och lösenfrasen separat och säkert.",
"backup_restore_title": "Återställ säkerhetskopian",
@@ -741,7 +741,7 @@
"backup_new_id_confirm": "Importera säkerhetskopian som en ny loggbok med ett nytt ID?",
"backup_stat_entries": "{{count}} Resdagar",
"backup_stat_photos": "{{count}} Foton",
"backup_stat_crew": "{{count}} Besättningens uppgifter",
"backup_stat_crew": "{{count}} Crew-poster",
"backup_stat_tracks": "{{count}} GPS-spår",
"backup_exported_at": "Exporterad: {{date}}"
},
@@ -819,7 +819,7 @@
"join_again": "Gå med igen",
"login_or_register_hint": "Logga in eller registrera ett konto för att gå med i loggboken.",
"or_sign_up": "ELLER REGISTRERA DIG IGEN",
"register_crew_account": "Skapa ett nytt konto för besättningen",
"register_crew_account": "Skapa ett nytt konto för crewen",
"username_label": "Användarens namn",
"create_passkey": "Skapa Passkey.",
"switch_language_en": "Engelska",
@@ -883,7 +883,7 @@
},
"welcome_public": {
"title": "Välkommen ombord!",
"body": "Utforska vår demologgbok med tre dagars resor i Kielfjorden - utan konto. Den här korta rundturen visar dig fartygsdata, besättning och loggboksanteckningar."
"body": "Utforska vår demologgbok med tre dagars resor i Kielfjorden - utan konto. Den här korta rundturen visar dig fartygsdata, crew och loggboksanteckningar."
},
"nav_logs": {
"title": "Loggboksanteckningar",
@@ -906,12 +906,12 @@
"body": "Ange namn, dimensioner och tekniska data för din yacht - fyll i en gång, tillgänglig för alla resdagar."
},
"profile_crew_pool": {
"title": "Stambesättning och skeppare",
"body": "I användarprofilen underhåller du en personpool flera skeppare (t.ex. charter) och besättning för alla loggböcker."
"title": "Stamm-Crew och skeppare",
"body": "I användarprofilen underhåller du en personpool flera skeppare (t.ex. charter) och crew för alla loggböcker."
},
"nav_logbook_crew": {
"title": "Besättning per loggbok",
"body": "Välj skeppare och besättning från poolen för denna loggbok. Resdagar ärver valet som standard."
"title": "Crew per loggbok",
"body": "Välj skeppare och crew från poolen för denna loggbok. Resdagar ärver valet som standard."
},
"nav_stats": {
"title": "Kontrollpanel för statistik",
@@ -937,7 +937,7 @@
},
"seo": {
"title": "Kapteins Daagbok - Gratis digital loggbok för båtar (reklamfri)",
"description": "Gratis, annonsfri digital loggbok för båtar med kryptering från början till slut och Passkey-inloggning. Dokumentera resdagar, GPS-spår, besättnings- och fartygsdata på ett säkert sätt - även offline som PWA.",
"description": "Gratis, annonsfri digital loggbok för båtar med kryptering från början till slut och Passkey-inloggning. Dokumentera resdagar, GPS-spår, Crew- och fartygsdata på ett säkert sätt - även offline som PWA.",
"keywords": "Yachtloggbok, skeppsdagbok, ombordloggbok, segling, Passkey, E2E kryptering, GPS-spår, sjöfartsloggbok, gratis, reklamfri, gratis, utan reklam",
"ogImageAlt": "Kapteins Daagbok Logotyp"
}
+11 -7
View File
@@ -3,7 +3,7 @@ import { getActiveMasterKey } from './auth.js'
import { decryptJson, encryptJson } from './crypto.js'
import { getLogbookKey } from './logbookKeys.js'
import type { PersonData } from '../types/person.js'
import { buildLogbookCrewSelection } from '../utils/personSnapshots.js'
import { buildLogbookCrewSelection, pickActiveSkipperId } from '../utils/personSnapshots.js'
import { entryCrewFromLogbookSelection } from '../utils/personSnapshots.js'
import { saveLogbookCrewSelection } from './logbookCrewSelection.js'
const MIGRATION_FLAG = 'crew_pool_migration_v1_done'
@@ -24,8 +24,8 @@ export async function migrateLegacyCrewToPoolIfNeeded(): Promise<void> {
const logbookKey = (await getLogbookKey(logbook.id)) || masterKey
const legacyCrews = await db.crews.where({ logbookId: logbook.id }).toArray()
const legacyIds: { skipperId: string | null; crewIds: string[] } = {
skipperId: null,
const legacyIds: { skipperIds: string[]; crewIds: string[] } = {
skipperIds: [],
crewIds: []
}
@@ -65,14 +65,18 @@ export async function migrateLegacyCrewToPoolIfNeeded(): Promise<void> {
poolData.set(poolId, personData)
}
if (role === 'skipper') legacyIds.skipperId = poolId
else legacyIds.crewIds.push(poolId)
if (role === 'skipper') {
if (!legacyIds.skipperIds.includes(poolId)) legacyIds.skipperIds.push(poolId)
} else {
legacyIds.crewIds.push(poolId)
}
}
const activeSkipperId = pickActiveSkipperId(legacyIds.skipperIds)
const existingSelection = await db.logbookCrewSelections.get(logbook.id)
if (!existingSelection && (legacyIds.skipperId || legacyIds.crewIds.length > 0)) {
if (!existingSelection && (activeSkipperId || legacyIds.crewIds.length > 0)) {
const selection = buildLogbookCrewSelection(
legacyIds.skipperId,
activeSkipperId,
legacyIds.crewIds,
poolData
)
+58
View File
@@ -0,0 +1,58 @@
import { describe, expect, it } from 'vitest'
import type { PersonData } from '../types/person.js'
import {
legacyCrewRecordsToLogbookSelection,
pickActiveSkipperId
} from './personSnapshots.js'
function person(overrides: Partial<PersonData> & { role: PersonData['role'] }): PersonData {
return {
name: overrides.name ?? 'Test',
address: '',
birthDate: '',
phone: '',
nationality: '',
passportNumber: '',
bloodType: '',
allergies: '',
diseases: '',
role: overrides.role
}
}
describe('pickActiveSkipperId', () => {
it('returns null for empty list', () => {
expect(pickActiveSkipperId([])).toBeNull()
})
it('prefers canonical skipper payload id', () => {
expect(pickActiveSkipperId(['other-skipper', 'skipper', 'third'])).toBe('skipper')
})
it('keeps first skipper when canonical id is absent', () => {
expect(pickActiveSkipperId(['alpha', 'beta'])).toBe('alpha')
})
})
describe('legacyCrewRecordsToLogbookSelection', () => {
it('does not let a later skipper overwrite the active skipper', () => {
const selection = legacyCrewRecordsToLogbookSelection([
{ payloadId: 'skipper', data: person({ role: 'skipper', name: 'Primary' }) },
{ payloadId: 'co-skipper', data: person({ role: 'skipper', name: 'Secondary' }) },
{ payloadId: 'crew-1', data: person({ role: 'crew', name: 'Crew' }) }
])
expect(selection.activeSkipperId).toBe('skipper')
expect(selection.activeCrewIds).toEqual(['crew-1'])
expect(Object.keys(selection.snapshotsById)).toEqual(['skipper', 'co-skipper', 'crew-1'])
})
it('uses first skipper when canonical id is missing', () => {
const selection = legacyCrewRecordsToLogbookSelection([
{ payloadId: 'first-skip', data: person({ role: 'skipper', name: 'First' }) },
{ payloadId: 'second-skip', data: person({ role: 'skipper', name: 'Second' }) }
])
expect(selection.activeSkipperId).toBe('first-skip')
})
})
+34
View File
@@ -1,5 +1,39 @@
import type { LogbookCrewSelectionData, PersonData, PersonSnapshot } from '../types/person.js'
/** Prefer canonical legacy id `skipper`, otherwise keep the first skipper encountered. */
export function pickActiveSkipperId(skipperIds: readonly string[]): string | null {
if (skipperIds.length === 0) return null
return skipperIds.find((id) => id === 'skipper') ?? skipperIds[0]
}
export function isSkipperRecord(payloadId: string, data: PersonData): boolean {
return payloadId === 'skipper' || data.role === 'skipper'
}
/** Build logbook crew selection from legacy per-logbook crew records (read-only share / migration). */
export function legacyCrewRecordsToLogbookSelection(
crews: Array<{ payloadId: string; data: PersonData }>
): LogbookCrewSelectionData {
const snapshotsById: Record<string, PersonSnapshot> = {}
const skipperIds: string[] = []
const activeCrewIds: string[] = []
for (const c of crews) {
snapshotsById[c.payloadId] = personToSnapshot(c.payloadId, c.data)
if (isSkipperRecord(c.payloadId, c.data)) {
if (!skipperIds.includes(c.payloadId)) skipperIds.push(c.payloadId)
} else {
activeCrewIds.push(c.payloadId)
}
}
return {
activeSkipperId: pickActiveSkipperId(skipperIds),
activeCrewIds,
snapshotsById
}
}
export function personToSnapshot(id: string, data: PersonData): PersonSnapshot {
return {
id,
+4 -7
View File
@@ -3,16 +3,13 @@ FROM node:20-alpine AS builder
WORKDIR /app
RUN apk add --no-cache openssl libc6-compat
# Copy package configurations
# Copy package configurations and Prisma schema (postinstall runs prisma generate)
COPY package*.json ./
COPY prisma ./prisma
# Install all dependencies (including devDependencies for tsc)
RUN npm ci
# Copy Prisma schema and generate Client code
COPY prisma ./prisma
RUN npx prisma generate
# Copy source and compile TypeScript
COPY src ./src
COPY tsconfig.json ./
@@ -26,8 +23,8 @@ RUN apk add --no-cache openssl libc6-compat
# Copy package configurations
COPY package*.json ./
# Install only production dependencies
RUN npm ci --omit=dev
# Install only production dependencies (Prisma client copied from builder; skip postinstall)
RUN npm ci --omit=dev --ignore-scripts
# Copy generated Prisma Client from builder stage
COPY --from=builder /app/node_modules/@prisma/client ./node_modules/@prisma/client