Compare commits

...
Author SHA1 Message Date
advplyr c86f336536 Update delete session to return status 200 instead of json 2026-07-26 17:40:14 -05:00
advplyr 1876bfbfe7 Add delete auth session endpoint to logout individual session 2026-07-26 17:36:36 -05:00
advplyr a7b380cd7a Add pagination to /me/sessions endpoint & sessions account page table 2026-07-26 17:11:03 -05:00
advplyrandGitHub 71c5d27ea5 Merge pull request #5269 from weblate/weblate-audiobookshelf-abs-web-client
Translations update from Hosted Weblate
2026-07-26 08:45:00 -04:00
FiendFEARingandHosted Weblate 83707e08bf Translated using Weblate (Chinese (Simplified Han script))
Currently translated at 100.0% (1164 of 1164 strings)

Translation: Audiobookshelf/Abs Web Client
Translate-URL: https://hosted.weblate.org/projects/audiobookshelf/abs-web-client/zh_Hans/
2026-07-25 22:44:45 +00:00
Igor DobračaandHosted Weblate e8d1b58bd2 Translated using Weblate (Croatian)
Currently translated at 100.0% (1164 of 1164 strings)

Translation: Audiobookshelf/Abs Web Client
Translate-URL: https://hosted.weblate.org/projects/audiobookshelf/abs-web-client/hr/
2026-07-25 22:44:44 +00:00
FiendFEARingandHosted Weblate aff79b5579 Translated using Weblate (Chinese (Simplified Han script))
Currently translated at 100.0% (1164 of 1164 strings)

Translation: Audiobookshelf/Abs Web Client
Translate-URL: https://hosted.weblate.org/projects/audiobookshelf/abs-web-client/zh_Hans/
2026-07-25 22:44:43 +00:00
Mona LisaandHosted Weblate 321d8bfda1 Translated using Weblate (Swedish)
Currently translated at 100.0% (1163 of 1163 strings)

Translation: Audiobookshelf/Abs Web Client
Translate-URL: https://hosted.weblate.org/projects/audiobookshelf/abs-web-client/sv/
2026-07-25 22:44:42 +00:00
Axel BjörnssonandHosted Weblate a3ff41f90e Translated using Weblate (Icelandic)
Currently translated at 2.5% (30 of 1163 strings)

Translation: Audiobookshelf/Abs Web Client
Translate-URL: https://hosted.weblate.org/projects/audiobookshelf/abs-web-client/is/
2026-07-25 22:44:40 +00:00
Mona LisaandHosted Weblate 60f9ac93f7 Translated using Weblate (Swedish)
Currently translated at 100.0% (1163 of 1163 strings)

Translation: Audiobookshelf/Abs Web Client
Translate-URL: https://hosted.weblate.org/projects/audiobookshelf/abs-web-client/sv/
2026-07-25 22:44:38 +00:00
Mona LisaandHosted Weblate bd61a43458 Translated using Weblate (Swedish)
Currently translated at 100.0% (1163 of 1163 strings)

Translation: Audiobookshelf/Abs Web Client
Translate-URL: https://hosted.weblate.org/projects/audiobookshelf/abs-web-client/sv/
2026-07-25 22:44:36 +00:00
FiendFEARingandHosted Weblate df6ec07f18 Translated using Weblate (Chinese (Simplified Han script))
Currently translated at 100.0% (1163 of 1163 strings)

Translation: Audiobookshelf/Abs Web Client
Translate-URL: https://hosted.weblate.org/projects/audiobookshelf/abs-web-client/zh_Hans/
2026-07-25 22:44:35 +00:00
FiendFEARingandHosted Weblate 6d02aa3aa0 Translated using Weblate (Chinese (Simplified Han script))
Currently translated at 100.0% (1163 of 1163 strings)

Translation: Audiobookshelf/Abs Web Client
Translate-URL: https://hosted.weblate.org/projects/audiobookshelf/abs-web-client/zh_Hans/
2026-07-25 22:44:33 +00:00
FiendFEARingandHosted Weblate 144fe743c6 Translated using Weblate (Chinese (Simplified Han script))
Currently translated at 100.0% (1163 of 1163 strings)

Translation: Audiobookshelf/Abs Web Client
Translate-URL: https://hosted.weblate.org/projects/audiobookshelf/abs-web-client/zh_Hans/
2026-07-25 22:44:31 +00:00
FiendFEARingandHosted Weblate 0851b346d0 Translated using Weblate (Chinese (Simplified Han script))
Currently translated at 100.0% (1163 of 1163 strings)

Translation: Audiobookshelf/Abs Web Client
Translate-URL: https://hosted.weblate.org/projects/audiobookshelf/abs-web-client/zh_Hans/
2026-07-25 22:44:29 +00:00
FiendFEARingandHosted Weblate 53e904314e Translated using Weblate (Chinese (Simplified Han script))
Currently translated at 100.0% (1163 of 1163 strings)

Translation: Audiobookshelf/Abs Web Client
Translate-URL: https://hosted.weblate.org/projects/audiobookshelf/abs-web-client/zh_Hans/
2026-07-25 22:44:25 +00:00
FiendFEARingandHosted Weblate 84f80f1903 Translated using Weblate (Chinese (Simplified Han script))
Currently translated at 100.0% (1163 of 1163 strings)

Translation: Audiobookshelf/Abs Web Client
Translate-URL: https://hosted.weblate.org/projects/audiobookshelf/abs-web-client/zh_Hans/
2026-07-25 22:44:20 +00:00
FiendFEARingandHosted Weblate 98b1f4abad Translated using Weblate (Chinese (Simplified Han script))
Currently translated at 100.0% (1163 of 1163 strings)

Translation: Audiobookshelf/Abs Web Client
Translate-URL: https://hosted.weblate.org/projects/audiobookshelf/abs-web-client/zh_Hans/
2026-07-25 22:44:18 +00:00
FiendFEARingandHosted Weblate ec0d147d6d Translated using Weblate (Chinese (Simplified Han script))
Currently translated at 100.0% (1163 of 1163 strings)

Translation: Audiobookshelf/Abs Web Client
Translate-URL: https://hosted.weblate.org/projects/audiobookshelf/abs-web-client/zh_Hans/
2026-07-25 22:44:16 +00:00
FiendFEARingandHosted Weblate a7825bb461 Translated using Weblate (Chinese (Simplified Han script))
Currently translated at 100.0% (1163 of 1163 strings)

Translation: Audiobookshelf/Abs Web Client
Translate-URL: https://hosted.weblate.org/projects/audiobookshelf/abs-web-client/zh_Hans/
2026-07-25 22:44:15 +00:00
தமிழ்நேரம்andHosted Weblate 09be7799f6 Translated using Weblate (Tamil)
Currently translated at 100.0% (1163 of 1163 strings)

Translation: Audiobookshelf/Abs Web Client
Translate-URL: https://hosted.weblate.org/projects/audiobookshelf/abs-web-client/ta/
2026-07-25 22:44:14 +00:00
தமிழ்நேரம்andHosted Weblate dc6a8a7292 Translated using Weblate (Tamil)
Currently translated at 16.7% (195 of 1163 strings)

Translation: Audiobookshelf/Abs Web Client
Translate-URL: https://hosted.weblate.org/projects/audiobookshelf/abs-web-client/ta/
2026-07-25 22:44:12 +00:00
GabrielandHosted Weblate 0c075674a0 Translated using Weblate (Portuguese (Brazil))
Currently translated at 100.0% (1163 of 1163 strings)

Translation: Audiobookshelf/Abs Web Client
Translate-URL: https://hosted.weblate.org/projects/audiobookshelf/abs-web-client/pt_BR/
2026-07-25 22:44:11 +00:00
advplyrandHosted Weblate 5b13820cb4 Added translation using Weblate (Tamil) 2026-07-25 22:44:10 +00:00
Hans Henrik JuhlandHosted Weblate d5b4d57fe3 Translated using Weblate (Danish)
Currently translated at 99.3% (1156 of 1163 strings)

Translation: Audiobookshelf/Abs Web Client
Translate-URL: https://hosted.weblate.org/projects/audiobookshelf/abs-web-client/da/
2026-07-25 22:44:07 +00:00
Mikkel Dupont OlesenandHosted Weblate f12b64495a Translated using Weblate (Danish)
Currently translated at 99.7% (1160 of 1163 strings)

Translation: Audiobookshelf/Abs Web Client
Translate-URL: https://hosted.weblate.org/projects/audiobookshelf/abs-web-client/da/
2026-07-25 22:44:05 +00:00
Hans Henrik JuhlandHosted Weblate 43f3150a52 Translated using Weblate (Danish)
Currently translated at 99.7% (1160 of 1163 strings)

Translation: Audiobookshelf/Abs Web Client
Translate-URL: https://hosted.weblate.org/projects/audiobookshelf/abs-web-client/da/
2026-07-25 22:44:01 +00:00
Mikkel Dupont OlesenandHosted Weblate 65d3a668e3 Translated using Weblate (Danish)
Currently translated at 99.7% (1160 of 1163 strings)

Translation: Audiobookshelf/Abs Web Client
Translate-URL: https://hosted.weblate.org/projects/audiobookshelf/abs-web-client/da/
2026-07-25 22:43:58 +00:00
Mikkel Dupont OlesenandHosted Weblate 2b5297bee8 Translated using Weblate (Danish)
Currently translated at 99.7% (1160 of 1163 strings)

Translation: Audiobookshelf/Abs Web Client
Translate-URL: https://hosted.weblate.org/projects/audiobookshelf/abs-web-client/da/
2026-07-25 22:43:57 +00:00
no1taandHosted Weblate 08caeb5975 Translated using Weblate (Greek)
Currently translated at 29.2% (340 of 1163 strings)

Translation: Audiobookshelf/Abs Web Client
Translate-URL: https://hosted.weblate.org/projects/audiobookshelf/abs-web-client/el/
2026-07-25 22:43:55 +00:00
Igor DobračaandHosted Weblate 6c170a5064 Translated using Weblate (Croatian)
Currently translated at 100.0% (1163 of 1163 strings)

Translation: Audiobookshelf/Abs Web Client
Translate-URL: https://hosted.weblate.org/projects/audiobookshelf/abs-web-client/hr/
2026-07-25 22:43:52 +00:00
advplyrandHosted Weblate 4b62f90964 Added translation using Weblate (Uzbek) 2026-07-25 22:43:50 +00:00
advplyrandGitHub e9ea511c90 Merge pull request #5400 from advplyr/account_sessions_table
Account sessions table
2026-07-25 18:43:26 -04:00
advplyr c6afeb7dc9 Fix logout button logging out all devices 2026-07-25 17:20:32 -05:00
advplyr 35a2c5e87c Add /me/sessions endpoint and show sessions table on account page 2026-07-25 17:18:38 -05:00
advplyrandGitHub 4c71a076e7 Merge pull request #5395 from advplyr/logout_all_devices
Add logout all devices button and extend auth logout endpoint
2026-07-24 18:51:35 -04:00
advplyr e56c1099e6 Add logout all devices button and extend auth logout endpoint 2026-07-24 17:44:03 -05:00
advplyrandGitHub dc0b24a03f Merge pull request #5393 from advplyr/pw_change_invalidates_sessions
User password changes invalidate sessions
2026-07-23 18:40:30 -04:00
16 changed files with 2545 additions and 1056 deletions
+142 -5
View File
@@ -1,6 +1,6 @@
<template>
<div id="page-wrapper" class="page p-6 overflow-y-auto relative" :class="streamLibraryItem ? 'streaming' : ''">
<div class="w-full max-w-xl mx-auto">
<div class="w-full max-w-2xl mx-auto">
<h1 class="text-2xl">{{ $strings.HeaderAccount }}</h1>
<div class="my-4">
@@ -69,8 +69,52 @@
</app-settings-content>
</div>
<div class="py-4 mt-8 flex">
<ui-btn color="bg-primary flex items-center text-lg" @click="logout"><span class="material-symbols mr-4 icon-text">logout</span>{{ $strings.ButtonLogout }}</ui-btn>
<div v-if="!isGuest">
<div class="w-full h-px bg-white/10 my-4" />
<app-settings-content :header-text="$strings.HeaderSessions">
<table v-if="authSessions.length" class="tracksTable mt-4 table-fixed w-full">
<tr>
<th class="text-left">{{ $strings.LabelDeviceInfo }}</th>
<th class="text-left hidden sm:table-cell w-36">{{ $strings.LabelIpAddress }}</th>
<th class="text-left w-30 sm:w-40">{{ $strings.LabelLastUpdate }}</th>
<th class="w-12"></th>
</tr>
<tr v-for="session in authSessions" :key="session.id">
<td class="max-w-0">
<div class="flex items-center gap-0.5 min-w-0">
<span class="text-sm text-gray-100 truncate min-w-0" :title="session.userAgent">{{ getSessionDeviceLabel(session) }}</span>
<ui-tooltip v-if="session.current" direction="top" :text="$strings.LabelCurrent" class="inline-flex shrink-0">
<span class="material-symbols text-success text-sm leading-none">check</span>
</ui-tooltip>
</div>
</td>
<td class="hidden sm:table-cell w-36">
<p class="text-sm text-gray-100 truncate" :title="session.ipAddress">{{ session.ipAddress || '-' }}</p>
</td>
<td class="w-30 sm:w-40">
<ui-tooltip v-if="session.updatedAt" direction="top" :text="$formatDatetime(session.updatedAt, dateFormat, timeFormat)">
<p class="text-xs sm:text-sm text-gray-100">{{ $dateDistanceFromNow(session.updatedAt) }}</p>
</ui-tooltip>
</td>
<td class="w-12">
<div class="flex justify-end items-center h-10">
<ui-icon-btn icon="delete" borderless :size="8" icon-font-size="1.1rem" :disabled="deletingSessionId === session.id || loadingAuthSessions" @click="deleteAuthSessionClick(session)" />
</div>
</td>
</tr>
</table>
<div v-if="authSessionsNumPages > 1" class="flex items-center justify-end py-1">
<ui-icon-btn icon="arrow_back_ios_new" :size="7" icon-font-size="1rem" class="mx-1" :disabled="loadingAuthSessions || authSessionsPage === 0" @click="prevAuthSessionsPage" />
<p class="text-sm mx-1">{{ $getString('LabelPaginationPageXOfY', [authSessionsPage + 1, authSessionsNumPages]) }}</p>
<ui-icon-btn icon="arrow_forward_ios" :size="7" icon-font-size="1rem" class="mx-1" :disabled="loadingAuthSessions || authSessionsPage >= authSessionsNumPages - 1" @click="nextAuthSessionsPage" />
</div>
</app-settings-content>
</div>
<div class="py-4 mt-8 flex flex-wrap gap-2">
<ui-btn v-if="!isGuest" color="bg-primary flex items-center text-lg" :disabled="loggingOut" @click="logout(true)"> <span class="material-symbols mr-4 icon-text">devices</span>{{ $strings.ButtonLogoutAllDevices }} </ui-btn>
<ui-btn color="bg-primary flex items-center text-lg" :disabled="loggingOut" @click="logout(false)"><span class="material-symbols mr-4 icon-text">logout</span>{{ $strings.ButtonLogout }}</ui-btn>
</div>
<modals-emails-user-e-reader-device-modal v-model="showEReaderDeviceModal" :existing-devices="revisedEreaderDevices" :ereader-device="selectedEReaderDevice" @update="ereaderDevicesUpdated" />
@@ -87,6 +131,13 @@ export default {
newPassword: null,
confirmPassword: null,
changingPassword: false,
loggingOut: false,
authSessions: [],
authSessionsPage: 0,
authSessionsNumPages: 0,
authSessionsItemsPerPage: 10,
loadingAuthSessions: false,
deletingSessionId: null,
selectedLanguage: '',
newEReaderDevice: {
name: '',
@@ -129,13 +180,19 @@ export default {
},
revisedEreaderDevices() {
return this.ereaderDevices.filter((device) => device.users?.length === 1)
},
dateFormat() {
return this.$store.getters['getServerSetting']('dateFormat')
},
timeFormat() {
return this.$store.getters['getServerSetting']('timeFormat')
}
},
methods: {
updateLocalLanguage(lang) {
this.$setLanguageCode(lang)
},
logout() {
logout(allDevices = false) {
// Disconnect from socket
if (this.$root.socket) {
console.log('Disconnecting from socket', this.$root.socket.id)
@@ -149,8 +206,10 @@ export default {
this.$store.commit('libraries/setUserPlaylists', [])
this.$store.commit('libraries/setCollections', [])
this.loggingOut = true
const url = allDevices ? '/logout?allDevices=1' : '/logout'
this.$axios
.$post('/logout')
.$post(url)
.then((logoutPayload) => {
const redirect_url = logoutPayload.redirect_url
@@ -163,6 +222,9 @@ export default {
.catch((error) => {
console.error(error)
})
.finally(() => {
this.loggingOut = false
})
},
resetForm() {
this.password = null
@@ -238,11 +300,86 @@ export default {
},
ereaderDevicesUpdated(ereaderDevices) {
this.ereaderDevices = ereaderDevices
},
loadAuthSessions(page = 0) {
if (this.loadingAuthSessions) return
if (page < 0) return
if (this.authSessionsNumPages > 0 && page > this.authSessionsNumPages - 1) return
this.loadingAuthSessions = true
this.$axios
.$get(`/api/me/sessions?page=${page}&itemsPerPage=${this.authSessionsItemsPerPage}`)
.then((data) => {
this.authSessions = data.sessions || []
this.authSessionsPage = data.page ?? page
this.authSessionsNumPages = data.numPages ?? 0
})
.catch((error) => {
console.error('Failed to load sessions', error)
})
.finally(() => {
this.loadingAuthSessions = false
})
},
prevAuthSessionsPage() {
if (this.authSessionsPage <= 0) return
this.loadAuthSessions(this.authSessionsPage - 1)
},
nextAuthSessionsPage() {
if (this.authSessionsPage >= this.authSessionsNumPages - 1) return
this.loadAuthSessions(this.authSessionsPage + 1)
},
deleteAuthSessionClick(session) {
this.$store.commit('globals/setConfirmPrompt', {
message: this.$getString('MessageConfirmLogoutDevice', [this.getSessionDeviceLabel(session)]),
callback: (confirmed) => {
if (confirmed) this.deleteAuthSession(session)
},
type: 'yesNo'
})
},
deleteAuthSession(session) {
// Call logout instead for current session
if (session.current) {
this.logout(false)
return
}
this.deletingSessionId = session.id
this.$axios
.$delete(`/api/me/sessions/${session.id}`)
.then(() => {
if (this.authSessions.length === 1 && this.authSessionsPage > 0) {
this.loadAuthSessions(this.authSessionsPage - 1)
} else {
this.loadAuthSessions(this.authSessionsPage)
}
})
.catch((error) => {
console.error('Failed to delete session', error)
this.$toast.error(this.$strings.ToastFailedToDelete)
})
.finally(() => {
this.deletingSessionId = null
})
},
getSessionDeviceLabel(session) {
const deviceInfo = session.deviceInfo
if (!deviceInfo) return session.userAgent || '-'
const parts = []
if (deviceInfo.model) parts.push(deviceInfo.model)
if (deviceInfo.osName) parts.push(`${deviceInfo.osName}${deviceInfo.osVersion ? ` ${deviceInfo.osVersion}` : ''}`)
if (deviceInfo.browserName) parts.push(deviceInfo.browserName)
return parts.join(' / ') || session.userAgent || '-'
}
},
mounted() {
this.selectedLanguage = this.$languageCodes.current
this.ereaderDevices = this.$store.state.libraries.ereaderDevices || []
if (!this.isGuest) {
this.loadAuthSessions()
}
}
}
</script>
+249 -249
View File
@@ -3,7 +3,7 @@
"ButtonAddApiKey": "Tilføj API-nøgle",
"ButtonAddChapters": "Tilføj kapitler",
"ButtonAddDevice": "Tilføj enhed",
"ButtonAddLibrary": "Tilføj Bibliotek",
"ButtonAddLibrary": "Tilføj bibliotek",
"ButtonAddPodcasts": "Tilføj podcasts",
"ButtonAddUser": "Tilføj bruger",
"ButtonAddYourFirstLibrary": "Tilføj dit første bibliotek",
@@ -11,21 +11,21 @@
"ButtonApplyChapters": "Anvend kapitler",
"ButtonAuthors": "Forfattere",
"ButtonBack": "Tilbage",
"ButtonBatchEditPopulateFromExisting": "Opret fra eksisterende",
"ButtonBatchEditPopulateMapDetails": "Opret fra kortlægnings detaljer",
"ButtonBatchEditPopulateFromExisting": "Kopiér fra eksisterende",
"ButtonBatchEditPopulateMapDetails": "Kopiér til masseredigering",
"ButtonBrowseForFolder": "Gennemse mappe",
"ButtonCancel": "Annuller",
"ButtonCancelEncode": "Annuller kodning",
"ButtonChangeRootPassword": "Ændr rodadgangskode",
"ButtonCheckAndDownloadNewEpisodes": "Tjek og download nye episoder",
"ButtonCheckAndDownloadNewEpisodes": "Tjek og hent nye afsnit",
"ButtonChooseAFolder": "Vælg en mappe",
"ButtonChooseFiles": "Vælg filer",
"ButtonClearFilter": "Ryd filter",
"ButtonClose": "Luk",
"ButtonCloseFeed": "Luk feed",
"ButtonCloseSession": "Luk Åben Session",
"ButtonCloseSession": "Luk åben session",
"ButtonCollections": "Samlinger",
"ButtonConfigureScanner": "Konfigurer scanner",
"ButtonConfigureScanner": "Konfigurer skanner",
"ButtonCreate": "Opret",
"ButtonCreateBackup": "Opret sikkerhedskopi",
"ButtonDelete": "Slet",
@@ -33,10 +33,10 @@
"ButtonEdit": "Rediger",
"ButtonEditChapters": "Rediger kapitler",
"ButtonEditPodcast": "Rediger podcast",
"ButtonEnable": "Aktiver",
"ButtonEnable": "Aktivér",
"ButtonFireAndFail": "Affyring Og Fejl",
"ButtonFireOnTest": "Affyring vedTest begivenhed",
"ButtonForceReScan": "Tving genindlæsning",
"ButtonFireOnTest": "Udløs onTest-hændelse",
"ButtonForceReScan": "Tving genskanning",
"ButtonFullPath": "Fuld sti",
"ButtonHide": "Skjul",
"ButtonHome": "Hjem",
@@ -53,321 +53,321 @@
"ButtonMatchBooks": "Match bøger",
"ButtonNevermind": "Glem det",
"ButtonNext": "Næste",
"ButtonNextChapter": "Næste Kapitel",
"ButtonNextItemInQueue": "Næste Element i Køen",
"ButtonNextChapter": "Næste kapitel",
"ButtonNextItemInQueue": "Næste element i køen",
"ButtonOk": "Ok",
"ButtonOpenFeed": "Åbn feed",
"ButtonOpenManager": "Åbn manager",
"ButtonPause": "Pause",
"ButtonPlay": "Afspil",
"ButtonPlayAll": "Afspil Alle",
"ButtonPlayAll": "Afspil alle",
"ButtonPlaying": "Afspiller",
"ButtonPlaylists": "Afspilningslister",
"ButtonPrevious": "Sidste",
"ButtonPreviousChapter": "Sidste Kapitel",
"ButtonProbeAudioFile": "Undersøg Lydfil",
"ButtonPurgeAllCache": "Ryd al cache",
"ButtonPurgeItemsCache": "Ryd elementcache",
"ButtonQueueAddItem": "Tilføj til kø",
"ButtonPrevious": "Forrige",
"ButtonPreviousChapter": "Forrige kapitel",
"ButtonProbeAudioFile": "Undersøg lydfil",
"ButtonPurgeAllCache": "Ryd alle mellemlagre",
"ButtonPurgeItemsCache": "Ryd mediemellemlager",
"ButtonQueueAddItem": "Sæt i kø",
"ButtonQueueRemoveItem": "Fjern fra kø",
"ButtonQuickEmbed": "Hurtig Indlejring",
"ButtonQuickEmbedMetadata": "Hurtig Indlejring af Metadata",
"ButtonQuickEmbed": "Hurtig indlejring",
"ButtonQuickEmbedMetadata": "Hurtig indlejring af metadata",
"ButtonQuickMatch": "Hurtig Match",
"ButtonReScan": "Gen-scan",
"ButtonReScan": "Skan igen",
"ButtonRead": "Læs",
"ButtonReadLess": "Se mindre",
"ButtonReadMore": "Se mere",
"ButtonRefresh": "Genindlæs",
"ButtonRemove": "Fjern",
"ButtonRemoveAll": "Fjern Alle",
"ButtonRemoveAllLibraryItems": "Fjern Alle Bibliotekselementer",
"ButtonRemoveFromContinueListening": "Fjern fra Fortsæt Lytning",
"ButtonRemoveFromContinueReading": "Fjern fra Fortsæt Læsning",
"ButtonRemoveSeriesFromContinueSeries": "Fjern Serie fra Fortsæt Serie",
"ButtonRemoveAll": "Fjern alle",
"ButtonRemoveAllLibraryItems": "Fjern alle bibliotekselementer",
"ButtonRemoveFromContinueListening": "Fjern fra »Fortsæt med at lytte«",
"ButtonRemoveFromContinueReading": "Fjern fra »Fortsæt med at læse«",
"ButtonRemoveSeriesFromContinueSeries": "Fjern Serie fra »Fortsæt serie«",
"ButtonReset": "Nulstil",
"ButtonResetToDefault": "Nulstil til standard",
"ButtonRestore": "Gendan",
"ButtonSave": "Gem",
"ButtonSaveAndClose": "Gem & Luk",
"ButtonSaveTracklist": "Gem Sporliste",
"ButtonScan": "Scan",
"ButtonScanLibrary": "Scan Bibliotek",
"ButtonScrollLeft": "Rul til Venstre",
"ButtonScrollRight": "Rul til Højre",
"ButtonSaveAndClose": "Gem og luk",
"ButtonSaveTracklist": "Gem sporliste",
"ButtonScan": "Skan",
"ButtonScanLibrary": "Skan bibliotek",
"ButtonScrollLeft": "Rul til venstre",
"ButtonScrollRight": "Rul til højre",
"ButtonSearch": "Søg",
"ButtonSelectFolderPath": "Vælg Mappe Sti",
"ButtonSelectFolderPath": "Vælg mappesti",
"ButtonSeries": "Serier",
"ButtonSetChaptersFromTracks": "Sæt kapitler fra spor",
"ButtonShare": "Del",
"ButtonShiftTimes": "Skift Tider",
"ButtonShiftTimes": "Forskyd tider",
"ButtonShow": "Vis",
"ButtonStartM4BEncode": "Start M4B Kode",
"ButtonStartMetadataEmbed": "Start Metadata Indlejring",
"ButtonStartM4BEncode": "Start konvertering til M4B",
"ButtonStartMetadataEmbed": "Start metadataindlejring",
"ButtonStats": "Statistik",
"ButtonSubmit": "Send",
"ButtonTest": "Test",
"ButtonUnlinkOpenId": "Afkobl OpenID",
"ButtonUpload": "Upload",
"ButtonUploadBackup": "Upload Backup",
"ButtonUploadCover": "Upload Omslag",
"ButtonUploadOPMLFile": "Upload OPML Fil",
"ButtonUpload": "Overfør",
"ButtonUploadBackup": "Overfør sikkerhedskopi",
"ButtonUploadCover": "Overfør omslag",
"ButtonUploadOPMLFile": "Overfør OPML-fil",
"ButtonUserDelete": "Slet bruger {0}",
"ButtonUserEdit": "Rediger bruger {0}",
"ButtonViewAll": "Vis Alle",
"ButtonViewAll": "Vis alle",
"ButtonYes": "Ja",
"ErrorUploadFetchMetadataAPI": "Fejl henter metadata",
"ErrorUploadFetchMetadataNoResults": "Kunne ikke hente metadata - prøv at uploade title og/eller forfatter",
"ErrorUploadLacksTitle": "Skal have en title",
"ErrorUploadFetchMetadataAPI": "Fejl ved hentning af metadata",
"ErrorUploadFetchMetadataNoResults": "Kunne ikke hente metadata - prøv at ændre titel og/eller forfatter",
"ErrorUploadLacksTitle": "Titel kræves",
"HeaderAccount": "Konto",
"HeaderAddCustomMetadataProvider": "Tilføj Brugerdefineret Metadataudbyder",
"HeaderAddCustomMetadataProvider": "Tilføj brugerdefineret metadataudbyder",
"HeaderAdvanced": "Avanceret",
"HeaderApiKeys": "API-nøgler",
"HeaderAppriseNotificationSettings": "Apprise Notifikationsindstillinger",
"HeaderAppriseNotificationSettings": "Apprise-notifikationsindstillinger",
"HeaderAudioTracks": "Lydspor",
"HeaderAudiobookTools": "Audiobog Filhåndteringsværktøjer",
"HeaderAuthentication": "Autentificering",
"HeaderAudiobookTools": "Håndtering af lydbogsfiler",
"HeaderAuthentication": "Loginindstillinger",
"HeaderBackups": "Sikkerhedskopier",
"HeaderBulkChapterModal": "Tilføj flere kapitler",
"HeaderChangePassword": "Skift Adgangskode",
"HeaderChangePassword": "Skift adgangskode",
"HeaderChapters": "Kapitler",
"HeaderChooseAFolder": "Vælg en Mappe",
"HeaderChooseAFolder": "Vælg en mappe",
"HeaderCollection": "Samling",
"HeaderCollectionItems": "Samlingselementer",
"HeaderCollectionItems": "Bøger i samlingen",
"HeaderCover": "Omslag",
"HeaderCurrentDownloads": "Nuværende Downloads",
"HeaderCustomMessageOnLogin": "Brugerdefineret Besked ved Login",
"HeaderCustomMetadataProviders": "Brugerdefineret Metadataudbyder",
"HeaderCurrentDownloads": "Aktuelle downloads",
"HeaderCustomMessageOnLogin": "Brugerdefineret besked ved logind",
"HeaderCustomMetadataProviders": "Brugerdefineret metadataudbyder",
"HeaderDetails": "Detaljer",
"HeaderDownloadQueue": "Download Kø",
"HeaderDownloadQueue": "Downloadkø",
"HeaderEbookFiles": "E-bogsfiler",
"HeaderEmail": "Email",
"HeaderEmailSettings": "Email Indstillinger",
"HeaderEpisodes": "Episoder",
"HeaderEreaderDevices": "E-læser Enheder",
"HeaderEreaderSettings": "E-læser Indstillinger",
"HeaderEmailSettings": "Emailindstillinger",
"HeaderEpisodes": "Afsnit",
"HeaderEreaderDevices": "E-bogslæsere",
"HeaderEreaderSettings": "Indstillinger for e-bogslæsere",
"HeaderFiles": "Filer",
"HeaderFindChapters": "Find Kapitler",
"HeaderIgnoredFiles": "Ignorerede Filer",
"HeaderItemFiles": "Emnefiler",
"HeaderItemMetadataUtils": "Emne Metadata Værktøjer",
"HeaderLastListeningSession": "Seneste Lyttesession",
"HeaderLatestEpisodes": "Seneste episoder",
"HeaderFindChapters": "Find kapitler",
"HeaderIgnoredFiles": "Ignorerede filer",
"HeaderItemFiles": "Filer",
"HeaderItemMetadataUtils": "Metadata-værktøjer",
"HeaderLastListeningSession": "Seneste lyttesession",
"HeaderLatestEpisodes": "Nyeste afsnit",
"HeaderLibraries": "Biblioteker",
"HeaderLibraryFiles": "Biblioteksfiler",
"HeaderLibraryStats": "Biblioteksstatistik",
"HeaderListeningSessions": "Lyttesessioner",
"HeaderListeningStats": "Lyttestatistik",
"HeaderLogin": "Log ind",
"HeaderLogs": "Logs",
"HeaderManageGenres": "Administrer Genrer",
"HeaderManageTags": "Administrer Tags",
"HeaderMapDetails": "Kort Detaljer",
"HeaderMatch": "Match",
"HeaderLogs": "Logfiler",
"HeaderManageGenres": "Administrer genrer",
"HeaderManageTags": "Administrer emneord",
"HeaderMapDetails": "Masseredigering",
"HeaderMatch": "Find match",
"HeaderMetadataOrderOfPrecedence": "Metadata-prioritet",
"HeaderMetadataToEmbed": "Metadata til indlejring",
"HeaderNewAccount": "Ny Konto",
"HeaderNewAccount": "Ny konto",
"HeaderNewApiKey": "Ny API-nøgle",
"HeaderNewLibrary": "Nyt Bibliotek",
"HeaderNotificationCreate": "Opret Notifikation",
"HeaderNewLibrary": "Nyt bibliotek",
"HeaderNotificationCreate": "Opret notifikation",
"HeaderNotificationUpdate": "Updater Notifikation",
"HeaderNotifications": "Meddelelser",
"HeaderOpenIDConnectAuthentication": "OpenID Connect-autentificering",
"HeaderOpenIDConnectAuthentication": "Log ind med OpenID Connect",
"HeaderOpenListeningSessions": "Åbne lyttesessioner",
"HeaderOpenRSSFeed": "Åbn RSS Feed",
"HeaderOtherFiles": "Andre Filer",
"HeaderPasswordAuthentication": "Adgangskodeautentificering",
"HeaderOpenRSSFeed": "Åbn RSS-feed",
"HeaderOtherFiles": "Andre filer",
"HeaderPasswordAuthentication": "Log ind med adgangskode",
"HeaderPermissions": "Tilladelser",
"HeaderPlayerQueue": "Afspilningskø",
"HeaderPlayerSettings": "Afspiller Indstillinger",
"HeaderPlayerSettings": "Afspilningsindstillinger",
"HeaderPlaylist": "Afspilningsliste",
"HeaderPlaylistItems": "Afspilningsliste Elementer",
"HeaderPodcastsToAdd": "Podcasts til Tilføjelse",
"HeaderPlaylistItems": "Elementer i afspilningslisten",
"HeaderPodcastsToAdd": "Podcasts som skal tilføjes",
"HeaderPresets": "Forudindstillinger",
"HeaderPreviewCover": "Forhåndsvis Omslag",
"HeaderRSSFeedGeneral": "RSS Detaljer",
"HeaderRSSFeedIsOpen": "RSS Feed er Åben",
"HeaderRSSFeeds": "RSS-Feeds",
"HeaderRemoveEpisode": "Fjern Episode",
"HeaderRemoveEpisodes": "Fjern {0} Episoder",
"HeaderSavedMediaProgress": "Gemt Medieforløb",
"HeaderPreviewCover": "Forhåndsvis omslag",
"HeaderRSSFeedGeneral": "RSS-detaljer",
"HeaderRSSFeedIsOpen": "RSS-feedet er åbent",
"HeaderRSSFeeds": "RSS-feeds",
"HeaderRemoveEpisode": "Fjern afsnit",
"HeaderRemoveEpisodes": "Fjern {0} afsnit",
"HeaderSavedMediaProgress": "Gemt lyttefremgang",
"HeaderSchedule": "Planlæg",
"HeaderScheduleEpisodeDownloads": "Planlæg Automatisk Episode-Download",
"HeaderScheduleLibraryScans": "Planlæg Automatiske Biblioteksscanninger",
"HeaderScheduleEpisodeDownloads": "Planlæg automatisk hentning af afsnit",
"HeaderScheduleLibraryScans": "Planlæg automatisk biblioteksskanning",
"HeaderSession": "Session",
"HeaderSetBackupSchedule": "Indstil Sikkerhedskopieringsplan",
"HeaderSetBackupSchedule": "Indstil tidsplan for sikkerhedskopiering",
"HeaderSettings": "Indstillinger",
"HeaderSettingsDisplay": "Skærm",
"HeaderSettingsExperimental": "Eksperimentelle Funktioner",
"HeaderSettingsDisplay": "Visning",
"HeaderSettingsExperimental": "Eksperimentelle funktioner",
"HeaderSettingsGeneral": "Generelt",
"HeaderSettingsScanner": "Scanner",
"HeaderSettingsScanner": "Skanner",
"HeaderSettingsSecurity": "Sikkerhed",
"HeaderSettingsWebClient": "Webklient",
"HeaderSleepTimer": "Søvntimer",
"HeaderStatsLargestItems": "Største Elementer",
"HeaderStatsLongestItems": "Længste Elementer (timer)",
"HeaderStatsMinutesListeningChart": "Minutter Lyttet (sidste 7 dage)",
"HeaderStatsRecentSessions": "Seneste Sessions",
"HeaderStatsTop10Authors": "Top 10 Forfattere",
"HeaderStatsTop5Genres": "Top 5 Genrer",
"HeaderSleepTimer": "Sovefunktion",
"HeaderStatsLargestItems": "Største bøger",
"HeaderStatsLongestItems": "Længste bøger (timer)",
"HeaderStatsMinutesListeningChart": "Minutter lyttet (sidste 7 dage)",
"HeaderStatsRecentSessions": "Seneste sessioner",
"HeaderStatsTop10Authors": "Top 10 forfattere",
"HeaderStatsTop5Genres": "Top 5 genrer",
"HeaderTableOfContents": "Indholdsfortegnelse",
"HeaderTools": "Værktøjer",
"HeaderUpdateAccount": "Opdater Konto",
"HeaderUpdateAccount": "Opdater konto",
"HeaderUpdateApiKey": "Opdater API-nøgle",
"HeaderUpdateAuthor": "Opdater Forfatter",
"HeaderUpdateDetails": "Opdater Detaljer",
"HeaderUpdateLibrary": "Opdater Bibliotek",
"HeaderUpdateAuthor": "Opdater forfatter",
"HeaderUpdateDetails": "Opdater detaljer",
"HeaderUpdateLibrary": "Opdater bibliotek",
"HeaderUsers": "Brugere",
"HeaderYearReview": "Gennemgang af År {0}",
"HeaderYourStats": "Dine Statistikker",
"LabelAbridged": "Forkortet",
"LabelAbridgedChecked": "Forkortet (kontrolleret)",
"LabelAbridgedUnchecked": "Uforkortet (ikke kontrolleret)",
"LabelAccessibleBy": "Tilgængelig af",
"LabelAccountType": "Brugertype",
"HeaderYearReview": "Året {0} opsummeret",
"HeaderYourStats": "Din statistik",
"LabelAbridged": "Forkortet udgave",
"LabelAbridgedChecked": "Forkortet udgave (bekræftet)",
"LabelAbridgedUnchecked": "Uforkortet udgave (ikke bekræftet)",
"LabelAccessibleBy": "Tilgængelig for",
"LabelAccountType": "Kontotype",
"LabelAccountTypeAdmin": "Administrator",
"LabelAccountTypeGuest": "Gæst",
"LabelAccountTypeUser": "Bruger",
"LabelActivities": "Aktiviteter",
"LabelActivity": "Aktivitet",
"LabelAddToCollection": "Tilføj til Samling",
"LabelAddToCollectionBatch": "Tilføj {0} Bøger til Samling",
"LabelAddToPlaylist": "Tilføj til Afspilningsliste",
"LabelAddToPlaylistBatch": "Tilføj {0} Elementer til Afspilningsliste",
"LabelAddToCollection": "Tilføj til samling",
"LabelAddToCollectionBatch": "Tilføj {0} bøger til samling",
"LabelAddToPlaylist": "Tilføj til afspilningsliste",
"LabelAddToPlaylistBatch": "Tilføj {0} elementer til afspilningsliste",
"LabelAddedAt": "Tilføjet",
"LabelAddedDate": "Tilføjet {0}",
"LabelAdminUsersOnly": "Kun Administratorer",
"LabelAddedDate": "Tilføjede {0}",
"LabelAdminUsersOnly": "Kun administratorer",
"LabelAll": "Alle",
"LabelAllEpisodesDownloaded": "Alle episoder hentet",
"LabelAllUsers": "Alle Brugere",
"LabelAllUsersExcludingGuests": "Alle bruger eksklusiv gæster",
"LabelAllUsersIncludingGuests": "Alle bruger inklusiv gæster",
"LabelAllEpisodesDownloaded": "Alle afsnit hentet",
"LabelAllUsers": "Alle brugere",
"LabelAllUsersExcludingGuests": "Alle brugere, bortset fra gæster",
"LabelAllUsersIncludingGuests": "Alle brugere, også gæster",
"LabelAlreadyInYourLibrary": "Allerede i dit bibliotek",
"LabelApiKeyCreated": "API-nøgle\"{0}\" oprettet succesfuldt.",
"LabelApiKeyCreated": "API-nøglen \"{0}\" blev oprettet.",
"LabelApiKeyCreatedDescription": "Sørg for at kopiere API-nøglen nu, da du ikke vil kunne se den igen.",
"LabelApiKeyUser": "Ret på vegne af brugeren",
"LabelApiKeyUserDescription": "Denne API-nøgle vil have de samme tilladelser som den bruger, den handler på vegne af. Dette vil fremgå på samme måde i logfiler, som hvis brugeren foretog anmodningen.",
"LabelApiToken": "API Token",
"LabelAppend": "Tilføj",
"LabelApiKeyUser": "Handl på vegne af bruger",
"LabelApiKeyUserDescription": "Denne API-nøgle vil have de samme tilladelser som den bruger, den handler på vegne af. Dette vil fremgå på samme måde i logfiler, som hvis brugeren selv foretog anmodningen.",
"LabelApiToken": "API-nøgle",
"LabelAppend": "Vedhæft",
"LabelAudioBitrate": "Lydbitrate (f.eks. 128k)",
"LabelAudioChannels": "Lydkanaler (1 eller 2)",
"LabelAudioCodec": "Lydkodek",
"LabelAudioCodec": "Lyd-codec",
"LabelAuthor": "Forfatter",
"LabelAuthorFirstLast": "Forfatter (Fornavn Efternavn)",
"LabelAuthorLastFirst": "Forfatter (Efternavn, Fornavn)",
"LabelAuthors": "Forfattere",
"LabelAutoDownloadEpisodes": "Auto Download Episoder",
"LabelAutoFetchMetadata": "Automatisk Hent Metadata",
"LabelAutoFetchMetadataHelp": "Henter metadata for titler, forfatter og serier for at strømligne uploading. Ekstra metadata har måske brug for at blive matchet efter upload.",
"LabelAutoLaunch": "Åben Automatisk",
"LabelAutoDownloadEpisodes": "Hent automatisk afsnit",
"LabelAutoFetchMetadata": "Hent automatisk metadata",
"LabelAutoFetchMetadataHelp": "Henter metadata for titler, forfatter og serie for at strømline uploading. Ekstra metadata har måske brug for at blive matchet efter upload.",
"LabelAutoLaunch": "Automatisk start",
"LabelAutoLaunchDescription": "Viderestil automatisk til login-udbyderen ved navigation til login-siden (manuel overstyring via <code>/login?autoLaunch=0</code>)",
"LabelAutoRegister": "Registrer Automatisk",
"LabelAutoRegister": "Registrer automatisk",
"LabelAutoRegisterDescription": "Automatisk oprettelse af nye brugere efter login",
"LabelBackToUser": "Tilbage til Bruger",
"LabelBackupAudioFiles": "Sikkerhedskopier lydfiler",
"LabelBackupLocation": "Backup Placering",
"LabelBackToUser": "Tilbage til bruger",
"LabelBackupAudioFiles": "Sikkerhedskopiér lydfiler",
"LabelBackupLocation": "Placering af sikkerhedskopi",
"LabelBackupsEnableAutomaticBackups": "Automatisk sikkerhedskopiering",
"LabelBackupsEnableAutomaticBackupsHelp": "Sikkerhedskopier gemt i /metadata/backups",
"LabelBackupsMaxBackupSize": "Maksimal sikkerhedskopistørrelse (i GB) (0 for ubegrænset)",
"LabelBackupsMaxBackupSizeHelp": "Som en beskyttelse mod fejlkonfiguration fejler sikkerhedskopier, hvis de overstiger den konfigurerede størrelse.",
"LabelBackupsNumberToKeep": "Antal sikkerhedskopier at beholde",
"LabelBackupsNumberToKeepHelp": "Kun 1 sikkerhedskopi fjernes ad gangen, så hvis du allerede har flere sikkerhedskopier end dette, skal du fjerne dem manuelt.",
"LabelBackupsNumberToKeep": "Antal sikkerhedskopier som skal beholdes",
"LabelBackupsNumberToKeepHelp": "Kun én sikkerhedskopi fjernes ad gangen, så hvis du allerede har flere sikkerhedskopier end dette, skal du fjerne dem manuelt.",
"LabelBitrate": "Bitrate",
"LabelBonus": "Bonus",
"LabelBooks": "Bøger",
"LabelButtonText": "Knap tekst",
"LabelByAuthor": "Efter Forfatter",
"LabelChangePassword": "Ændre Adgangskode",
"LabelButtonText": "Tekst på knap",
"LabelByAuthor": "af {0}",
"LabelChangePassword": "Ændr adgangskode",
"LabelChannels": "Kanaler",
"LabelChapterCount": "{0} Kapitler",
"LabelChapterTitle": "Kapitel Titel",
"LabelChapterCount": "{0} kapitler",
"LabelChapterTitle": "Kapiteltitel",
"LabelChapters": "Kapitler",
"LabelChaptersFound": "fundne kapitler",
"LabelChaptersFound": "kapitler fundet",
"LabelClickForMoreInfo": "Klik for mere info",
"LabelClickToUseCurrentValue": "Klik for at bruge nuværende værdi",
"LabelClosePlayer": "Luk afspiller",
"LabelCodec": "Kodeks",
"LabelCollapseSeries": "Fold Serier Sammen",
"LabelCodec": "Codec",
"LabelCollapseSeries": "Fold serier sammen",
"LabelCollapseSubSeries": "Fold underserie sammen",
"LabelCollection": "Samling",
"LabelCollections": "Samlinger",
"LabelComplete": "Fuldfør",
"LabelConfirmPassword": "Bekræft Adgangskode",
"LabelComplete": "Afsluttet",
"LabelConfirmPassword": "Bekræft adgangskode",
"LabelContinueListening": "Fortsæt med at lytte",
"LabelContinueReading": "Fortsæt med at læse",
"LabelContinueSeries": "Fortsæt Serien",
"LabelContinueSeries": "Fortsæt serien",
"LabelCorsAllowed": "Tilladte CORS-oprindelser",
"LabelCover": "Omslag",
"LabelCoverImageURL": "Omslagsbillede URL",
"LabelCoverProvider": "Cover billede udbyder",
"LabelCreatedAt": "Oprettet d.",
"LabelCronExpression": "Cron Udtryk",
"LabelCoverImageURL": "Omslagsbilledes-URL",
"LabelCoverProvider": "Kilde til omslagsbillede",
"LabelCreatedAt": "Oprettet",
"LabelCronExpression": "Cron-kommando",
"LabelCurrent": "Aktuel",
"LabelCurrently": "Aktuelt:",
"LabelCustomCronExpression": "Brugerdefineret Cron Udtryk:",
"LabelDatetime": "Dato og Tid",
"LabelCurrently": "Nuværende:",
"LabelCustomCronExpression": "Brugerdefineret Cron-kommando:",
"LabelDatetime": "Dato og tid",
"LabelDays": "Dage",
"LabelDeleteFromFileSystemCheckbox": "Slet fra filsystem (afmarker kun for at fjerne fra databasen)",
"LabelDeleteFromFileSystemCheckbox": "Slet fra filsystem (afmarkér for udelukkende at fjerne fra databasen)",
"LabelDescription": "Beskrivelse",
"LabelDeselectAll": "Fravælg Alle",
"LabelDeselectAll": "Fravælg alle",
"LabelDetectedPattern": "Identificeret mønster:",
"LabelDevice": "Enheds",
"LabelDevice": "Enhed",
"LabelDeviceInfo": "Enhedsinformation",
"LabelDeviceIsAvailableTo": "Enhed er tilgængelig for...",
"LabelDeviceIsAvailableTo": "Enheden er tilgængelig for...",
"LabelDirectory": "Mappe",
"LabelDiscFromFilename": "Disk fra Filnavn",
"LabelDiscFromMetadata": "Disk fra Metadata",
"LabelDiscFromFilename": "Disk fra filnavn",
"LabelDiscFromMetadata": "Disk fra metadata",
"LabelDiscover": "Opdag",
"LabelDownload": "Download",
"LabelDownloadNEpisodes": "Download {0} episoder",
"LabelDownloadable": "Downloadbar",
"LabelDownload": "Hent",
"LabelDownloadNEpisodes": "Hent {0} afsnit",
"LabelDownloadable": "Kan hentes",
"LabelDuration": "Varighed",
"LabelDurationComparisonExactMatch": "(præcis match)",
"LabelDurationComparisonExactMatch": "(præcist match)",
"LabelDurationComparisonLonger": "({0} længere)",
"LabelDurationComparisonShorter": "({0} kortere)",
"LabelDurationFound": "Fundet varighed:",
"LabelEbook": "E-bog",
"LabelEbooks": "E-bøger",
"LabelEdit": "Rediger",
"LabelEmail": "E-mail",
"LabelEmailSettingsFromAddress": "Fra Adresse",
"LabelEmailSettingsRejectUnauthorized": "Afvis uautoriserede certifikater",
"LabelEmailSettingsRejectUnauthorizedHelp": "Deaktivering af SSL certifikat validering kan udsætte din forbindelse for sikkerhedsrisici, eksempelvis man-in-the-middle angreb. Deaktiver kun denne indstilling hvis du forstår de potentielle implikationer og stoler på den mailserver du forbinder til.",
"LabelEmailSettingsSecure": "Sikker",
"LabelEmailSettingsSecureHelp": "Hvis sandt, vil forbindelsen bruge TLS ved tilslutning til serveren. Hvis falsk, bruges TLS, hvis serveren understøtter STARTTLS-udvidelsen. I de fleste tilfælde skal denne værdi sættes til sandt, hvis du tilslutter til port 465. Til port 587 eller 25 skal du holde det falsk. (fra nodemailer.com/smtp/#authentication)",
"LabelEmailSettingsTestAddress": "Test Adresse",
"LabelEmbeddedCover": "Indlejret Omslag",
"LabelEmail": "Email",
"LabelEmailSettingsFromAddress": "Afsenderadresse",
"LabelEmailSettingsRejectUnauthorized": "Afvis ukendte sikkerhedscertifikater",
"LabelEmailSettingsRejectUnauthorizedHelp": "Hvis du slår SSL-certifikatkontrollen fra, risikerer du at uvedkommende kan opsnappe dine data. Slå kun dette fra, hvis du er helt tryg ved situationen og har fuld tillid til den mailserver, du forbinder til.",
"LabelEmailSettingsSecure": "Sikker forbindelse",
"LabelEmailSettingsSecureHelp": "Når denne er slået til, bruger forbindelsen altid fuld kryptering (TLS). Hvis den er slået fra, bruges kryptering kun, hvis serveren tillader det (via STARTTLS). Lad dette være slået til, hvis du bruger port 465. Slå det fra, hvis du bruger port 587 eller 25",
"LabelEmailSettingsTestAddress": "Modtageradresse til test",
"LabelEmbeddedCover": "Indlejret omslag",
"LabelEnable": "Aktivér",
"LabelEncodingBackupLocation": "En sikkerhedskopi af dine originale lydfiler vil blive gemt under:",
"LabelEncodingChaptersNotEmbedded": "Kapitler er ikke indlejret i multi spors lydbøger.",
"LabelEncodingClearItemCache": "Sørg for periodisk at rense indholdscachen.",
"LabelEncodingFinishedM4B": "Færdiggjort M4B som vil blive placeret i din lydbogsmappe ved:",
"LabelEncodingInfoEmbedded": "Metadata vil blive indlejret i lydfiler i lydbogsmappen.",
"LabelEncodingStartedNavigation": "Når opgaven er startet kan du navigere væk fra denne side.",
"LabelEncodingTimeWarning": "Indkodning kan tage op til 30 minutter.",
"LabelEncodingWarningAdvancedSettings": "Advarsel: Opdater ikke disse indstillinger med mindre du kender til ffmpeg indkodningsindstillinger.",
"LabelEncodingWatcherDisabled": "Hvis du har watcheren deaktiveret skal du gen-scanne denne lydbog bagefter.",
"LabelEncodingBackupLocation": "En sikkerhedskopi af dine originale lydfiler vil blive gemt i mappen:",
"LabelEncodingChaptersNotEmbedded": "Kapitler bliver ikke indlejret i lydbøger som består af flere lydfiler.",
"LabelEncodingClearItemCache": "Sørg for jævnligt at rense mellemlageret.",
"LabelEncodingFinishedM4B": "Den færdige M4B-fil vil blive placeret i lydbogsmappen:",
"LabelEncodingInfoEmbedded": "Metadata vil blive indlejret i lydfilerne i lydbogsmappen.",
"LabelEncodingStartedNavigation": "Når opgaven er sat i gang kan du navigere væk fra denne side.",
"LabelEncodingTimeWarning": "Omkodningen kan tage op til 30 minutter.",
"LabelEncodingWarningAdvancedSettings": "Advarsel: Ændr ikke disse indstillinger med mindre du kender til ffmpeg-indkodningsindstillinger.",
"LabelEncodingWatcherDisabled": "Hvis du har slået automatisk biblioteksovervågning fra skal du genskanne denne lydbog bagefter.",
"LabelEnd": "Slut",
"LabelEndOfChapter": "Slutningen af kapitel",
"LabelEpisode": "Afsnit",
"LabelEpisodeNotLinkedToRssFeed": "Afsnit er ikke koblet til RSS feed",
"LabelEpisodeNotLinkedToRssFeed": "Afsnit er ikke koblet til RSS-feed",
"LabelEpisodeNumber": "Afsnit #{0}",
"LabelEpisodeTitle": "Episodetitel",
"LabelEpisodeType": "Episodetype",
"LabelEpisodeUrlFromRssFeed": "Afsnit URL fra RSS feed",
"LabelEpisodeTitle": "Afsnitstitel",
"LabelEpisodeType": "Afsnitstype",
"LabelEpisodeUrlFromRssFeed": "Afsnits-URL fra RSS-feed",
"LabelEpisodes": "Afsnit",
"LabelEpisodic": "Afsnit",
"LabelEpisodic": "Episodisk",
"LabelExample": "Eksempel",
"LabelExpandSeries": "Udfold serie",
"LabelExpandSubSeries": "Udfold underserie",
"LabelExpandSeries": "Fold serie ud",
"LabelExpandSubSeries": "Fold underserie ud",
"LabelExpired": "Udløbet",
"LabelExpiresAt": "Udløbsdato",
"LabelExpiresInSeconds": "Udløber om (seconds)",
"LabelExpiresNever": "Aldrig",
"LabelExplicit": "Eksplisit",
"LabelExplicitChecked": "Eksplicit (markeret)",
"LabelExplicitUnchecked": "Ikke eksplicit (ikke markeret)",
"LabelExplicit": "Voksenindhold",
"LabelExplicitChecked": "Voksenindhold (markeret)",
"LabelExplicitUnchecked": "Ikke voksenindhold (ikke markeret)",
"LabelExportOPML": "Eksport OPML",
"LabelFeedURL": "Feed URL",
"LabelFeedURL": "Feed-URL",
"LabelFetchingMetadata": "Henter metadata",
"LabelFile": "Fil",
"LabelFileBirthtime": "Oprettelsestidspunkt for fil",
@@ -391,7 +391,7 @@
"LabelFull": "Fuld",
"LabelGenre": "Genre",
"LabelGenres": "Genrer",
"LabelHardDeleteFile": "Permanent slet fil",
"LabelHardDeleteFile": "Slet fil permanent",
"LabelHasEbook": "Har e-bog",
"LabelHasSupplementaryEbook": "Har supplerende e-bog",
"LabelHideSubtitles": "Skjul undertitler",
@@ -400,7 +400,7 @@
"LabelHour": "Time",
"LabelHours": "Timer",
"LabelIcon": "Ikon",
"LabelImageURLFromTheWeb": "Billede URL fra nettet",
"LabelImageURLFromTheWeb": "Billede-URL fra nettet",
"LabelInProgress": "I gang",
"LabelIncludeInTracklist": "Inkluder i afspilningsliste",
"LabelIncomplete": "Ufuldstændig",
@@ -414,22 +414,22 @@
"LabelIntervalEveryDay": "Hver dag",
"LabelIntervalEveryHour": "Hver time",
"LabelIntervalEveryMinute": "Hvert minut",
"LabelInvert": "Inverter",
"LabelInvert": "Omvendt begrænsning",
"LabelItem": "Element",
"LabelJumpBackwardAmount": "Spring bagud mængde",
"LabelJumpForwardAmount": "Spring fremad mængde",
"LabelJumpBackwardAmount": "Tilbagespolingstid",
"LabelJumpForwardAmount": "Fremadspolingstid",
"LabelLanguage": "Sprog",
"LabelLanguageDefaultServer": "Standard server sprog",
"LabelLanguageDefaultServer": "Serverens standardsprog",
"LabelLanguages": "Sprog",
"LabelLastBookAdded": "Senest tilføjede bog",
"LabelLastBookUpdated": "Senest opdaterede bog",
"LabelLastProgressDate": "Sidste fremgang: {0}",
"LabelLastProgressDate": "Seneste fremgang: {0}",
"LabelLastSeen": "Sidst set",
"LabelLastTime": "Sidste gang",
"LabelLastUpdate": "Seneste opdatering",
"LabelLayout": "Layout",
"LabelLastTime": "Sidst lyttet",
"LabelLastUpdate": "Sidst ændret",
"LabelLayout": "Udseende",
"LabelLayoutSinglePage": "Enkeltside",
"LabelLayoutSplitPage": "Opdelt side",
"LabelLayoutSplitPage": "Opdelte sider",
"LabelLess": "Mindre",
"LabelLibrariesAccessibleToUser": "Biblioteker tilgængelige for bruger",
"LabelLibrary": "Bibliotek",
@@ -445,14 +445,14 @@
"LabelLogLevelDebug": "Fejlsøgning",
"LabelLogLevelInfo": "Information",
"LabelLogLevelWarn": "Advarsel",
"LabelLookForNewEpisodesAfterDate": "Søg efter nye episoder efter denne dato",
"LabelLookForNewEpisodesAfterDate": "Søg efter nye afsnit efter denne dato",
"LabelLowestPriority": "Laveste prioritet",
"LabelMatchConfidence": "Confidens",
"LabelMatchExistingUsersBy": "Match eksisterende brugere ved",
"LabelMatchExistingUsersByDescription": "Anvendt for at forbinde brugere. Når forbundet, brugere vil blive matchet ved unikt id fra din SSO udbyder",
"LabelMaxEpisodesToDownload": "Max # afsnit for at downloade. Anvend 0 for ubegrænset.",
"LabelMaxEpisodesToDownloadPerCheck": "Max # afsnit til at downloade per check",
"LabelMaxEpisodesToKeep": "Max # afsnit at beholde",
"LabelMatchConfidence": "Nøjagtighed",
"LabelMatchExistingUsersBy": "Forbind eksisterende brugere med",
"LabelMatchExistingUsersByDescription": "Anvendes til at tilslutte eksisterende brugere. Ved tilslutningen kobles brugeren til et unikt ID fra din SSO-udbyder",
"LabelMaxEpisodesToDownload": "Maksimalt antal afsnit som skal hentes. Anvend 0 for ubegrænset.",
"LabelMaxEpisodesToDownloadPerCheck": "Maksimalt antal afsnit som skal hentes per tjek",
"LabelMaxEpisodesToKeep": "Maksimalt antal afsnit som skal beholdes",
"LabelMaxEpisodesToKeepHelp": "Værdi af 0 sætter intet maks begrænsning. After et nyt afsnit er automatisk downloaded vil det ældste afsnit blive slettet hvis du har mere end X afsnit. Dette vil kun slette 1 afsnit for hvert nye download.",
"LabelMediaPlayer": "Medieafspiller",
"LabelMediaType": "Medietype",
@@ -463,20 +463,20 @@
"LabelMinute": "Minut",
"LabelMinutes": "Minutter",
"LabelMissing": "Mangler",
"LabelMissingEbook": "Har ingen ebog",
"LabelMissingSupplementaryEbook": "Har ingen tillægsbog",
"LabelMissingEbook": "Har ingen e-bog",
"LabelMissingSupplementaryEbook": "Har ingen supplerende e-bog",
"LabelMobileRedirectURIs": "Godkendte mobil redirect URI'er",
"LabelMobileRedirectURIsDescription": "Dete vil whiteliste en gyldig omdirigerings URL for mobile apps. Den standarde er <code>audiobookshelf://oauth</code> som du kan fjerne eller supplere med flere URI'er for tredjeparts app integration. Anvend en stjerne (<code>*</code>) som den eneste indstilling for at tilade en hvilkensomhelst URI.",
"LabelMore": "Mere",
"LabelMoreInfo": "Mere info",
"LabelName": "Navn",
"LabelNarrator": "Fortæller",
"LabelNarrators": "Fortællere",
"LabelNarrator": "Oplæser",
"LabelNarrators": "Oplæsere",
"LabelNew": "Ny",
"LabelNewPassword": "Ny adgangskode",
"LabelNewestAuthors": "Nyeste forfattere",
"LabelNewestEpisodes": "Nyeste episoder",
"LabelNextBackupDate": "Næste sikkerhedskopi dato",
"LabelNewestEpisodes": "Nyeste afsnit",
"LabelNextBackupDate": "Dato for næste sikkerhedskopi",
"LabelNextChapters": "Næste kapitler vil være:",
"LabelNextScheduledRun": "Næste planlagte kørsel",
"LabelNoApiKeys": "Ingen API-nøgler",
@@ -526,28 +526,28 @@
"LabelPodcasts": "Podcast",
"LabelPort": "Port",
"LabelPrefixesToIgnore": "Præfikser der skal ignoreres (skal ikke skelne mellem store og små bogstaver)",
"LabelPreventIndexing": "Forhindrer, at dit feed bliver indekseret af iTunes og Google podcastkataloger",
"LabelPreventIndexing": "Forhindrer at dit feed bliver indekseret af iTunes- og Google-podcastkataloger",
"LabelPrimaryEbook": "Primær e-bog",
"LabelProgress": "Fremskridt",
"LabelProvider": "Udbyder",
"LabelProviderAuthorizationValue": "Authorization Header værdi",
"LabelPubDate": "Udgivelsesdato",
"LabelPublishYear": "Udgivelsesår",
"LabelPublishedDate": "Publiceret {0}",
"LabelPublishedDecade": "Publiceret årti",
"LabelPublishedDecades": "Publiceret årtier",
"LabelPublishedDate": "Udgivet {0}",
"LabelPublishedDecade": "Udgivelsesårti",
"LabelPublishedDecades": "Udgivelsesårtier",
"LabelPublisher": "Forlag",
"LabelPublishers": "Forlag",
"LabelRSSFeedCustomOwnerEmail": "Brugerdefineret ejerens e-mail",
"LabelRSSFeedCustomOwnerName": "Brugerdefineret ejerens navn",
"LabelRSSFeedOpen": "RSS-feed åbent",
"LabelRSSFeedPreventIndexing": "Forhindrer indeksering",
"LabelRSSFeedPreventIndexing": "Forhindr indeksering",
"LabelRSSFeedSlug": "RSS-feed-slug",
"LabelRSSFeedURL": "RSS-feed-URL",
"LabelRandomly": "Tilfældigt",
"LabelReAddSeriesToContinueListening": "Gentilføj serier til Fortsæt Lytning",
"LabelReAddSeriesToContinueListening": "Gentilføj serier til »Fortsæt med at lytte«",
"LabelRead": "Læst",
"LabelReadAgain": "Læs Igen",
"LabelReadAgain": "Læs igen",
"LabelReadEbookWithoutProgress": "Læs e-bog uden at følge fremskridt",
"LabelRecentSeries": "Seneste serier",
"LabelRecentlyAdded": "Senest tilføjet",
@@ -555,9 +555,9 @@
"LabelRedo": "Gøre igen",
"LabelRegion": "Region",
"LabelReleaseDate": "Udgivelsesdato",
"LabelRemoveAllMetadataAbs": "Fjern alle metadata.abs filer",
"LabelRemoveAllMetadataJson": "Fjern alle metadata.json filer",
"LabelRemoveAudibleBranding": "Fjern Audible intro og outro fra kapitler",
"LabelRemoveAllMetadataAbs": "Fjern alle \"metadata.abs\"-filer",
"LabelRemoveAllMetadataJson": "Fjern alle \"metadata.json\"-filer",
"LabelRemoveAudibleBranding": "Fjern Audible-intro og -outro fra kapitler",
"LabelRemoveCover": "Fjern omslag",
"LabelRemoveMetadataFile": "Fjern alle metadata filer i biblioteksmapper",
"LabelRemoveMetadataFileHelp": "Fjern alle metadata.json og metadata.abs filer i dine {0} mapper.",
@@ -571,7 +571,7 @@
"LabelSelectAllEpisodes": "Vælg alle episoder",
"LabelSelectEpisodesShowing": "Vælg {0} episoder vist",
"LabelSelectUser": "Vælg bruger",
"LabelSelectUsers": "Valgte brugere",
"LabelSelectUsers": "Vælg brugere",
"LabelSendEbookToDevice": "Send e-bog til...",
"LabelSequence": "Sekvens",
"LabelSerial": "Seriel",
@@ -582,21 +582,21 @@
"LabelServerYearReview": "Server år i review ({0})",
"LabelSetEbookAsPrimary": "Indstil som primær",
"LabelSetEbookAsSupplementary": "Indstil som supplerende",
"LabelSettingsAllowIframe": "Tillad embedding i en iframe",
"LabelSettingsAllowIframe": "Tillad indlejring i en iframe",
"LabelSettingsAudiobooksOnly": "Kun lydbøger",
"LabelSettingsAudiobooksOnlyHelp": "Aktivering af denne indstilling vil ignorere e-bogsfiler, medmindre de er inde i en lydbogmappe, hvor de vil blive indstillet som supplerende e-bøger",
"LabelSettingsBookshelfViewHelp": "Skeumorfisk design med træhylder",
"LabelSettingsChromecastSupport": "Chromecast-understøttelse",
"LabelSettingsDateFormat": "Datoformat",
"LabelSettingsEnableWatcher": "Automatisk biblioteksovervåger",
"LabelSettingsEnableWatcher": "Automatisk biblioteksovervågning",
"LabelSettingsEnableWatcherForLibrary": "Automatisk biblioteksovervåger",
"LabelSettingsEnableWatcherHelp": "Aktiverer automatisk tilføjelse/opdatering af elementer, når filændringer registreres. *Kræver servergenstart",
"LabelSettingsEnableWatcherHelp": "Aktiverer automatisk opdatering af kataloget, når filændringer registreres. *Kræver genstart af serveren",
"LabelSettingsEpubsAllowScriptedContent": "Tillad scriptet indhold i epub",
"LabelSettingsEpubsAllowScriptedContentHelp": "Tillad epub filer at køre scripts. Det anbefales at holde denne indstilling deaktiveret med mindre du stoler på kilderne af epub filerne.",
"LabelSettingsExperimentalFeatures": "Eksperimentelle funktioner",
"LabelSettingsExperimentalFeaturesHelp": "Funktioner under udvikling, der kunne bruge din feedback og hjælp til test. Klik for at åbne Github-diskussionen.",
"LabelSettingsFindCovers": "Find omslag",
"LabelSettingsFindCoversHelp": "Hvis din lydbog ikke har et indlejret omslag eller et omslagsbillede i mappen, vil skanneren forsøge at finde et omslag.<br>Bemærk: Dette vil forlænge scanntiden",
"LabelSettingsFindCoversHelp": "Hvis din lydbog ikke har et indlejret omslag eller et omslagsbillede i mappen, vil skanneren forsøge at finde et omslag.<br>Bemærk: Dette vil forlænge skanningens varighed",
"LabelSettingsHideSingleBookSeries": "Skjul enkeltbogsserier",
"LabelSettingsHideSingleBookSeriesHelp": "Serier med en enkelt bog vil blive skjult fra serie-siden og hjemmesidehylder.",
"LabelSettingsHomePageBookshelfView": "Brug bogreolvisning på startside",
@@ -607,7 +607,7 @@
"LabelSettingsOnlyShowLaterBooksInContinueSeries": "Spring tidligere bøger i Fortsæt serie over",
"LabelSettingsOnlyShowLaterBooksInContinueSeriesHelp": "Fortsæt Serien siden viser den første bog som ikke er startet i serier med mindst en bog som ikke er startet og hvor ingen bøger i gang. Aktivering af denne indstilling vil fortsætte serien fra den sidst gennemførte bog i stedet for fra den første ikke startede bog.",
"LabelSettingsParseSubtitles": "Fortolk undertitler",
"LabelSettingsParseSubtitlesHelp": "Udtræk undertekster fra lydbogsmappenavne.<br>Undertitler skal adskilles af \" - \"<br>f.eks. \"Bogtitel - En undertitel her\" har undertitlen \"En undertitel her\"",
"LabelSettingsParseSubtitlesHelp": "Udtræk undertitler fra lydbogsmappenavne.<br>Undertitler skal adskilles af \" - \"<br>f.eks. \"Bogtitel - En undertitel her\" har undertitlen \"En undertitel her\"",
"LabelSettingsPreferMatchedMetadata": "Foretræk matchede metadata",
"LabelSettingsPreferMatchedMetadataHelp": "Matchede data vil tilsidesætte elementdetaljer ved brug af Hurtig Match. Som standard udfylder Hurtig Match kun manglende detaljer.",
"LabelSettingsSkipMatchingBooksWithASIN": "Ignorer matchende bøger, der allerede har en ASIN",
@@ -647,14 +647,14 @@
"LabelStatsDaysListened": "Dage hørt",
"LabelStatsHours": "Timer",
"LabelStatsInARow": "i træk",
"LabelStatsItemsFinished": "Elementer færdige",
"LabelStatsItemsInLibrary": "Elementer i biblioteket",
"LabelStatsItemsFinished": "færdiglæste bøger",
"LabelStatsItemsInLibrary": "bøger i biblioteket",
"LabelStatsMinutes": "minutter",
"LabelStatsMinutesListening": "Minutter hørt",
"LabelStatsOverallDays": "Samlede dage",
"LabelStatsOverallHours": "Samlede timer",
"LabelStatsWeekListening": "Ugens lytning",
"LabelSubtitle": "Undertekst",
"LabelSubtitle": "Undertitel",
"LabelSupportedFileTypes": "Understøttede filtyper",
"LabelTag": "Mærke",
"LabelTags": "Mærker",
@@ -663,7 +663,7 @@
"LabelTasks": "Kører opgaver",
"LabelTextEditorBulletedList": "Punktopstilling",
"LabelTextEditorLink": "Link",
"LabelTextEditorNumberedList": "Nummeropstilling",
"LabelTextEditorNumberedList": "Nummereret liste",
"LabelTextEditorUnlink": "Aflink",
"LabelTheme": "Tema",
"LabelThemeDark": "Mørk",
@@ -735,17 +735,17 @@
"LabelYourPlaylists": "Dine spillelister",
"LabelYourProgress": "Din fremgang",
"MessageAddToPlayerQueue": "Tilføj til afspilningskø",
"MessageAppriseDescription": "For at bruge denne funktion skal du have en instans af <a href=\"https://github.com/caronc/apprise-api\" target=\"_blank\">Apprise API</a> kørende eller en API, der håndterer de samme anmodninger. <br /> Apprise API-webadressen skal være den fulde URL-sti for at sende underretningen, f.eks. hvis din API-instans er tilgængelig på <code>http://192.168.1.1:8337</code>, så skal du bruge <code>http://192.168.1.1:8337/notify</code>.",
"MessageAppriseDescription": "For at bruge denne funktion skal du have en instans af <a href=\"https://github.com/caronc/apprise-api\" target=\"_blank\">Apprise API</a> kørende eller en API, der kan håndtere de samme anmodninger. <br />Apprise API-webadressen skal være den fulde URL-sti for at sende underretningen, f.eks. hvis din API-instans er tilgængelig på <code>http://192.168.1.1:8337</code>, så skal du bruge <code>http://192.168.1.1:8337/notify</code>.",
"MessageAsinCheck": "Sikr dig at du bruger ASIN fra den korrekte Audible region, ikke Amazon.",
"MessageAuthenticationLegacyTokenWarning": "Ældre API tokens vil blive fjernet i fremtiden. Brug <a href=\"/config/api-keys\">API-nøgler</a> i stedet.",
"MessageAuthenticationOIDCChangesRestart": "Genstart sin server efter du har gemt for at bekræfte OIDC ændringer.",
"MessageAuthenticationSecurityMessage": "Autentificeringen er blevet forbedret af sikkerhedsmæssige årsager. Alle brugere skal logge ind igen.",
"MessageAuthenticationSecurityMessage": "Brugerlogin er blevet forbedret af sikkerhedsmæssige årsager. Alle brugere skal logge ind igen.",
"MessageBackupsDescription": "Backups inkluderer brugere, brugerfremskridt, biblioteksvareoplysninger, serverindstillinger og billeder gemt i <code>/metadata/items</code> og <code>/metadata/authors</code>. Backups inkluderer <strong>ikke</strong> nogen filer gemt i dine biblioteksmapper.",
"MessageBackupsLocationEditNote": "Note: Opdatering af backup sti vil ikke fjerne eller modificere eksisterende backups",
"MessageBackupsLocationNoEditNote": "Note: Backup sti er sat igennem miljøvariabel og kan ikke ændres her.",
"MessageBackupsLocationPathEmpty": "Backup sti kan ikke være tom",
"MessageBatchEditPopulateMapDetailsAllHelp": "Opret felter slået til med data fra alle genstande. Felter med flere værdier vil blive sammenflettet",
"MessageBatchEditPopulateMapDetailsItemHelp": "Opret kort med værdier der er slået til fra felter med data fra denne genstand",
"MessageBatchEditPopulateMapDetailsAllHelp": "Udfyld valgte felter med data fra alle bøger. Felter med flere værdier vil blive sammenflettet",
"MessageBatchEditPopulateMapDetailsItemHelp": "Udfyld de felter, som er tilvalgt i masseredigering med data fra denne bog",
"MessageBatchQuickMatchDescription": "Quick Match vil forsøge at tilføje manglende omslag og metadata til de valgte elementer. Aktivér indstillingerne nedenfor for at tillade Quick Match at overskrive eksisterende omslag og/eller metadata.",
"MessageBookshelfNoCollections": "Du har ikke oprettet nogen samlinger endnu",
"MessageBookshelfNoCollectionsHelp": "Samlinger er offentlige. Alle brugere med adgang til biblioteket kan se dem.",
@@ -888,7 +888,7 @@
"MessageResetChaptersConfirm": "Er du sikker på, at du vil nulstille kapitler og annullere ændringerne, du har foretaget?",
"MessageRestoreBackupConfirm": "Er du sikker på, at du vil gendanne sikkerhedskopien oprettet den",
"MessageRestoreBackupWarning": "Gendannelse af en sikkerhedskopi vil overskrive hele databasen, som er placeret på /config, og omslagsbilleder i /metadata/items & /metadata/authors.<br /><br />Sikkerhedskopier ændrer ikke nogen filer i dine biblioteksmapper. Hvis du har aktiveret serverindstillinger for at gemme omslagskunst og metadata i dine biblioteksmapper, sikkerhedskopieres eller overskrives disse ikke.<br /><br />Alle klienter, der bruger din server, opdateres automatisk.",
"MessageScheduleLibraryScanNote": "For de fleste brugere er det anbefalet, at efterlade denne funktion deaktiveret, og lade biblioteksovervågeren være aktiveret - den vil automatisk opdage ændringer i dine biblioteksmapper. Aktiver denne funktion, hvis biblioteksovervågeren ikke virker med dit filsystem (f. eks. NFS).",
"MessageScheduleLibraryScanNote": "For de fleste brugere er det anbefalet, at lade denne funktion være deaktiveret, og lade biblioteksovervågning være aktiveret - den vil automatisk opdage ændringer i dine biblioteksmapper. Aktiver denne funktion, hvis biblioteksovervågning ikke virker med dit filsystem (f. eks. NFS).",
"MessageScheduleRunEveryWeekdayAtTime": "Kør hvert {0} af {1}",
"MessageSearchResultsFor": "Søgeresultater for",
"MessageSelected": "{0} valgt",
@@ -915,7 +915,7 @@
"MessageTaskFailedToWriteMetadataFile": "Fejlede i at skrive metadata fil",
"MessageTaskMatchingBooksInLibrary": "Matchede bøger i bibliotek \"{0}\"",
"MessageTaskNoFilesToScan": "Ingen filer at scanne",
"MessageTaskOpmlImport": "OPML import",
"MessageTaskOpmlImport": "OPML-import",
"MessageTaskOpmlImportDescription": "Oprettelse af podcasts fra {0} RSS feeds",
"MessageTaskOpmlImportFeed": "OPML importering fejlede",
"MessageTaskOpmlImportFeedDescription": "Importering af RSS feed \"{0}\"",
@@ -939,13 +939,13 @@
"MessageUploaderItemSuccess": "Uploadet med succes!",
"MessageUploading": "Uploader...",
"MessageValidCronExpression": "Gyldigt cron-udtryk",
"MessageWatcherIsDisabledGlobally": "Watcher er deaktiveret globalt i serverindstillinger",
"MessageWatcherIsDisabledGlobally": "Automatisk biblioteksovervågning er deaktiveret globalt i serverens indstillinger",
"MessageXLibraryIsEmpty": "{0} bibliotek er tomt!",
"MessageYourAudiobookDurationIsLonger": "Din lydbogsvarighed er længere end den fundne varighed",
"MessageYourAudiobookDurationIsShorter": "Din lydbogsvarighed er kortere end den fundne varighed",
"NoteChangeRootPassword": "Root-brugeren er den eneste bruger, der kan have en tom adgangskode",
"NoteChapterEditorTimes": "Bemærk: Første kapitel starttidspunkt skal forblive kl. 0:00, og det sidste kapitel starttidspunkt må ikke overstige denne lydbogs varighed.",
"NoteFolderPicker": "Bemærk: Mapper, der allerede er mappet, vises ikke",
"NoteFolderPicker": "Bemærk: Allerede kortlagte mapper vises ikke",
"NoteRSSFeedPodcastAppsHttps": "Advarsel: De fleste podcast-apps kræver, at RSS-feedets URL bruger HTTPS",
"NoteRSSFeedPodcastAppsPubDate": "Advarsel: En eller flere af dine episoder har ikke en Pub Date. Nogle podcast-apps kræver dette.",
"NoteUploaderFoldersWithMediaFiles": "Mapper med mediefiler håndteres som separate bibliotekselementer.",
@@ -1018,8 +1018,8 @@
"ToastChapterStartTimeAdjusted": "Kapitelstarttid justeret med {0} sekunder",
"ToastChaptersAllLocked": "Alle kapitler er låst. Lås op for nogle kapitler for at ændre deres tider.",
"ToastChaptersHaveErrors": "Kapitler har fejl",
"ToastChaptersInvalidShiftAmountLast": "Ugyldig ændring. Det sidste kapitels starttid ville fortsætte længere end varigheden på denne lydbog.",
"ToastChaptersInvalidShiftAmountStart": "Ugyldig ændring. Første kapitel ville have en længde på nul eller negativt og ville blive overskrevet af andet kapitel. Udvid startvarigheden andet kapitel.",
"ToastChaptersInvalidShiftAmountLast": "Forskydningen er ikke mulig. Det sidste kapitels starttidspunkt ville strække sig ud over denne lydbogs varighed.",
"ToastChaptersInvalidShiftAmountStart": "Forskydningen er ikke mulig. Det første kapitel ville have en længde på nul eller negativ længde og ville blive overskrevet af det andet kapitel. Øg startvarigheden af det andet kapitel.",
"ToastChaptersMustHaveTitles": "Kapitler skal have titler",
"ToastChaptersRemoved": "Kapitler fjernet",
"ToastChaptersUpdated": "Kapitler opdateret",
@@ -1044,7 +1044,7 @@
"ToastEpisodeUpdateSuccess": "{0} afsnit opdateret",
"ToastErrorCannotShare": "Kan ikke dele på denne enhed",
"ToastFailedToCreate": "Oprettelsen mislykkedes",
"ToastFailedToDelete": "Sletning fejlede",
"ToastFailedToDelete": "Kunne ikke slette",
"ToastFailedToLoadData": "Indlæsning af data fejlede",
"ToastFailedToMatch": "Fejlet match",
"ToastFailedToShare": "Deling fejlede",
@@ -1109,7 +1109,7 @@
"ToastProgressIsNotBeingSynced": "Fremskridt ikke synkroniseret, genstart afspilning",
"ToastProviderCreatedFailed": "Fejlede at tilføje udbyder",
"ToastProviderCreatedSuccess": "Ny udbyder tilføjet",
"ToastProviderNameAndUrlRequired": "Navn og URL krævet",
"ToastProviderNameAndUrlRequired": "Navn og URL kræves",
"ToastProviderRemoveSuccess": "Udbyder fjernet",
"ToastRSSFeedCloseFailed": "Mislykkedes lukning af RSS-feed",
"ToastRSSFeedCloseSuccess": "RSS-feed lukket",
+22 -4
View File
@@ -242,12 +242,13 @@
"LabelDescription": "Περιγραφή",
"LabelDevice": "Συσκευή",
"LabelDeviceInfo": "Πληροφορίες Συσκευής",
"LabelDiscover": "Ανακάλυψη",
"LabelDownload": "Λήψη",
"LabelDownloadNEpisodes": "Λήψη {0} επεισοδίων",
"LabelDuration": "Διάρκεια",
"LabelDurationComparisonExactMatch": "(ακριβής ταύτιση)",
"LabelEbook": "Ebook",
"LabelEbooks": "Ebooks",
"LabelEbook": "Ηλεκτρονικό Βιβλίο/ Ebook",
"LabelEbooks": "Ηλεκτρονικά Βιβλία / Ebooks",
"LabelEdit": "Επεξεργασία",
"LabelEmail": "Ηλεκτρονικό Ταχυδρομίο",
"LabelEmailSettingsFromAddress": "Από Διεύθυνση",
@@ -258,19 +259,34 @@
"LabelEnd": "Τέλος",
"LabelEndOfChapter": "Τέλος Κεφαλαίου",
"LabelEpisode": "Επεισόδιο",
"LabelExplicit": "Άσεμνο περιεχόμενο",
"LabelFeedURL": "URL ροής",
"LabelFile": "Αρχείο",
"LabelFileBirthtime": "Ώρα δημιουργίας αρχείου",
"LabelFileModified": "Τροποποιημένο αρχείο",
"LabelFilename": "Όνομα Αρχείου",
"LabelFinished": "Ολοκληρώθηκε",
"LabelFolder": "Φάκελος",
"LabelFontBoldness": "Ένταση γραμματοσειράς",
"LabelFontFamily": "Οικογένεια Γραμματοσειράς",
"LabelGenre": "Είδος",
"LabelGenres": "Είδη",
"LabelFontScale": "Μέγεθος γραμματοσειράς",
"LabelGenre": "Λογοτεχνικό Είδος",
"LabelGenres": "Λογοτεχνικά Είδη",
"LabelHasEbook": "Έχει ebook",
"LabelHasSupplementaryEbook": "Διαθέτει συμπληρωματικό ηλεκτρονικό βιβλίο",
"LabelHost": "Διακομιστής",
"LabelInProgress": "Σε Εξέλιξη",
"LabelIncomplete": "Ελειπής",
"LabelLanguage": "Γλώσσα",
"LabelLayout": "Διάταξη",
"LabelLayoutSinglePage": "Μονή Σελίδα",
"LabelLibrarySortByProgress": "Πρόοδος: Τελευταία ενημέρωση",
"LabelLibrarySortByProgressFinished": "Πρόοδος: Ολοκληρώθηκε",
"LabelLibrarySortByProgressStarted": "Πρόοδος: Έναρξη",
"LabelLineSpacing": "Διάστημα γραμμών",
"LabelListenAgain": "Επανάληψη Ακρόασης",
"LabelMediaType": "Τύπος Πολυμέσων",
"LabelMissing": "Λείπει η ετικέτα",
"LabelMore": "Περισσότερα",
"LabelMoreInfo": "Περισσότερες Πληροφορίες",
"LabelName": "Όνομα",
@@ -278,11 +294,13 @@
"LabelNarrators": "Αφηγητές",
"LabelNewestAuthors": "Πρόσφατοι Συγγραφείς",
"LabelNewestEpisodes": "Πρόσφατα Επεισόδια",
"LabelNotFinished": "Δεν έχει ολοκληρωθεί",
"LabelNotStarted": "Δεν Έχει Ξεκινήσει",
"LabelNumberOfEpisodes": "# Επεισοδίων",
"LabelPassword": "Κωδικός Πρόσβασης",
"LabelPath": "Διαδρομή",
"LabelProgress": "Πρόοδος",
"LabelPubDate": "Ημερομηνία έκδοσης",
"LabelPublishYear": "Χρονολογία Έκδοσης",
"LabelPublishedDate": "Εκδόθηκε {0}",
"LabelRandomly": "Τυχαία",
+4
View File
@@ -46,6 +46,7 @@
"ButtonLatest": "Latest",
"ButtonLibrary": "Library",
"ButtonLogout": "Logout",
"ButtonLogoutAllDevices": "Logout All Devices",
"ButtonLookup": "Lookup",
"ButtonManageTracks": "Manage Tracks",
"ButtonMapChapterTitles": "Map Chapter Titles",
@@ -194,6 +195,7 @@
"HeaderScheduleEpisodeDownloads": "Schedule Automatic Episode Downloads",
"HeaderScheduleLibraryScans": "Schedule Automatic Library Scans",
"HeaderSession": "Session",
"HeaderSessions": "Sessions",
"HeaderSetBackupSchedule": "Set Backup Schedule",
"HeaderSettings": "Settings",
"HeaderSettingsDisplay": "Display",
@@ -415,6 +417,7 @@
"LabelIntervalEveryHour": "Every hour",
"LabelIntervalEveryMinute": "Every minute",
"LabelInvert": "Invert",
"LabelIpAddress": "IP Address",
"LabelItem": "Item",
"LabelJumpBackwardAmount": "Jump backward amount",
"LabelJumpForwardAmount": "Jump forward amount",
@@ -774,6 +777,7 @@
"MessageConfirmDeleteSession": "Are you sure you want to delete this session?",
"MessageConfirmEmbedMetadataInAudioFiles": "Are you sure you want to embed metadata in {0} audio files?",
"MessageConfirmForceReScan": "Are you sure you want to force re-scan?",
"MessageConfirmLogoutDevice": "Are you sure you want to log out of \"{0}\"?",
"MessageConfirmMarkAllEpisodesFinished": "Are you sure you want to mark all episodes as finished?",
"MessageConfirmMarkAllEpisodesNotFinished": "Are you sure you want to mark all episodes as not finished?",
"MessageConfirmMarkItemFinished": "Are you sure you want to mark \"{0}\" as finished?",
+2 -1
View File
@@ -46,6 +46,7 @@
"ButtonLatest": "Najnovije",
"ButtonLibrary": "Knjižnica",
"ButtonLogout": "Odjavi se",
"ButtonLogoutAllDevices": "Odjava sa svih uređaja",
"ButtonLookup": "Potraži",
"ButtonManageTracks": "Upravljanje zvučnim zapisima",
"ButtonMapChapterTitles": "Mapiraj naslove poglavlja",
@@ -154,7 +155,7 @@
"HeaderLatestEpisodes": "Najnoviji nastavci",
"HeaderLibraries": "Knjižnice",
"HeaderLibraryFiles": "Datoteke knjižnice",
"HeaderLibraryStats": "Statistika knjižnice",
"HeaderLibraryStats": "Knjižnična statistika",
"HeaderListeningSessions": "Sesije slušanja",
"HeaderListeningStats": "Statistika slušanja",
"HeaderLogin": "Prijava",
+32 -1
View File
@@ -1 +1,32 @@
{}
{
"ButtonAdd": "Bæta við",
"ButtonAddApiKey": "Bæta við API-lykli",
"ButtonAddChapters": "Bæta við köflum",
"ButtonAddDevice": "Bæta við tæki",
"ButtonAddLibrary": "Bæta við bókasafni",
"ButtonAddPodcasts": "Bæta við hlaðvörpum",
"ButtonAddUser": "Bæta við notanda",
"ButtonAddYourFirstLibrary": "Bættu fyrsta bókasafninu þínu við",
"ButtonApply": "Nota",
"ButtonApplyChapters": "Skrá kafla",
"ButtonAuthors": "Rithöfundar",
"ButtonBack": "Bakka",
"ButtonBatchEditPopulateFromExisting": "Afrita frá núverandi",
"ButtonBatchEditPopulateMapDetails": "Bæta við upplýsingum",
"ButtonBrowseForFolder": "Skoða möppu",
"ButtonCancel": "Hætta við",
"ButtonCancelEncode": "Hætta við Encoding",
"ButtonChangeRootPassword": "Breyta Root lykilorði",
"ButtonCheckAndDownloadNewEpisodes": "Skoða og sækja nýja þætti",
"ButtonChooseAFolder": "Velja möppu",
"ButtonChooseFiles": "Velja skrár",
"ButtonClearFilter": "Hreinsa síu",
"ButtonClose": "Loka",
"ButtonCloseSession": "Loka opnu sessioni",
"ButtonCollections": "Söfn",
"ButtonConfigureScanner": "Stilla skannara",
"ButtonCreate": "Stofna",
"ButtonCreateBackup": "Búa til backup",
"ButtonDelete": "Eyða",
"ButtonDownloadQueue": "Röð"
}
+2 -2
View File
@@ -325,8 +325,8 @@
"LabelDurationComparisonLonger": "({0} maior)",
"LabelDurationComparisonShorter": "({0} menor)",
"LabelDurationFound": "Duração comprovada:",
"LabelEbook": "Ebook",
"LabelEbooks": "Ebooks",
"LabelEbook": "E-book",
"LabelEbooks": "E-books",
"LabelEdit": "Editar",
"LabelEmail": "Email",
"LabelEmailSettingsFromAddress": "Remetente",
+533 -533
View File
File diff suppressed because it is too large Load Diff
File diff suppressed because it is too large Load Diff
+1
View File
@@ -0,0 +1 @@
{}
+258 -257
View File
File diff suppressed because it is too large Load Diff
+8 -3
View File
@@ -471,18 +471,23 @@ class Auth {
res.json(openIdIssuerConfig)
})
// Logout route
/**
* Logout route
* Use ?allDevices=1 to destroy every session for this user instead of just the current one
*/
router.post('/logout', async (req, res) => {
// Refresh token be alternatively be sent in the header
const refreshToken = req.cookies.refresh_token || req.headers['x-refresh-token']
const allDevices = req.query.allDevices === '1'
// Clear refresh token cookie
res.clearCookie('refresh_token', {
path: '/'
})
// Invalidate the session in database using refresh token
if (refreshToken) {
if (allDevices) {
await this.tokenManager.invalidateAllSessionsForRefreshToken(refreshToken)
} else if (refreshToken) {
await this.tokenManager.invalidateRefreshToken(refreshToken)
} else {
Logger.info(`[Auth] logout: No refresh token on request`)
+19
View File
@@ -505,6 +505,25 @@ class TokenManager {
return null
}
/**
* Destroy all JWT sessions for the user that owns this refresh token
*
* @param {string} refreshToken
*/
async invalidateAllSessionsForRefreshToken(refreshToken) {
if (!refreshToken) return
const session = await Database.sessionModel.findOne({
where: {
[Op.or]: [{ refreshToken: refreshToken }, { lastRefreshToken: refreshToken }]
}
})
if (!session) return
const numDeleted = await Database.sessionModel.destroy({ where: { userId: session.userId } })
Logger.info(`[TokenManager] Invalidated all JWT sessions for user ${session.userId}, ${numDeleted} deleted`)
}
/**
* Invalidate a refresh token - used for logout
*
+78 -1
View File
@@ -1,10 +1,12 @@
const { Request, Response } = require('express')
const { Op } = require('sequelize')
const Logger = require('../Logger')
const SocketAuthority = require('../SocketAuthority')
const Database = require('../Database')
const { sort } = require('../libs/fastSort')
const { toNumber, isNullOrNaN } = require('../utils/index')
const { toNumber, isNullOrNaN, isUUID } = require('../utils/index')
const userStats = require('../utils/queries/userStats')
const parseUserAgent = require('../utils/parsers/parseUserAgent')
/**
* @typedef RequestUserObject
@@ -26,6 +28,81 @@ class MeController {
res.json(req.user.toOldJSONForBrowser())
}
/**
* GET: /api/me/sessions
*
* @param {RequestWithUser} req
* @param {Response} res
*/
async getSessions(req, res) {
const page = Math.max(0, toNumber(req.query.page, 0))
const itemsPerPage = Math.max(1, toNumber(req.query.itemsPerPage, 10))
if (req.user.isGuest) {
return res.json({ sessions: [], total: 0, numPages: 0, page, itemsPerPage })
}
const refreshToken = req.cookies.refresh_token || req.headers['x-refresh-token']
const { rows, count } = await Database.sessionModel.findAndCountAll({
where: {
userId: req.user.id,
expiresAt: { [Op.gt]: new Date() }
},
order: [['updatedAt', 'DESC']],
limit: itemsPerPage,
offset: itemsPerPage * page
})
res.json({
total: count,
numPages: Math.ceil(count / itemsPerPage),
page,
itemsPerPage,
sessions: rows.map((session) => ({
id: session.id,
ipAddress: session.ipAddress,
userAgent: session.userAgent,
// For display convenience
deviceInfo: parseUserAgent(session.userAgent),
createdAt: session.createdAt?.valueOf() ?? null,
updatedAt: session.updatedAt?.valueOf() ?? null,
current: !!refreshToken && (session.refreshToken === refreshToken || session.lastRefreshToken === refreshToken)
}))
})
}
/**
* DELETE: /api/me/sessions/:id
*
* @param {RequestWithUser} req
* @param {Response} res
*/
async deleteSession(req, res) {
if (req.user.isGuest) {
return res.sendStatus(403)
}
if (!isUUID(req.params.id)) {
return res.sendStatus(400)
}
const session = await Database.sessionModel.findOne({
where: {
id: req.params.id,
userId: req.user.id
}
})
if (!session) {
return res.sendStatus(404)
}
await Database.sessionModel.destroy({ where: { id: session.id } })
Logger.info(`[MeController] User ${req.user.username} deleted auth session ${session.id}`)
res.sendStatus(200)
}
/**
* GET: /api/me/progress
*
+2
View File
@@ -171,6 +171,8 @@ class ApiRouter {
// Current User Routes (Me)
//
this.router.get('/me', MeController.getCurrentUser.bind(this))
this.router.get('/me/sessions', MeController.getSessions.bind(this))
this.router.delete('/me/sessions/:id', MeController.deleteSession.bind(this))
this.router.get('/me/progress', MeController.getAllMediaProgress.bind(this))
this.router.get('/me/bookmarks', MeController.getAllBookmarks.bind(this))
this.router.get('/me/bookmarks/:libraryItemId', MeController.getBookmarksForLibraryItem.bind(this))
+28
View File
@@ -0,0 +1,28 @@
const uaParserJs = require('../../libs/uaParser')
/**
* @param {string|null|undefined} userAgent
* @returns {{ browserName?:string, browserVersion?:string, osName?:string, osVersion?:string, deviceType?:string, model?:string, vendor?:string }|null}
*/
function parseUserAgent(userAgent) {
if (!userAgent) return null
const ua = uaParserJs(userAgent)
const deviceInfo = {
browserName: ua?.browser?.name || undefined,
browserVersion: ua?.browser?.version || undefined,
osName: ua?.os?.name || undefined,
osVersion: ua?.os?.version || undefined,
deviceType: ua?.device?.type || undefined,
model: ua?.device?.model || undefined,
vendor: ua?.device?.vendor || undefined
}
for (const key in deviceInfo) {
if (deviceInfo[key] === undefined) delete deviceInfo[key]
}
return Object.keys(deviceInfo).length ? deviceInfo : null
}
module.exports = parseUserAgent